2019-06-20 19:28:46 +03:00
// SPDX-License-Identifier: GPL-2.0+
2005-04-17 02:20:36 +04:00
/*
* Watchdog driver for the SA11x0 / PXA2xx
*
2009-03-18 11:35:09 +03:00
* ( c ) Copyright 2000 Oleg Drokin < green @ crimea . edu >
* Based on SoftDog driver by Alan Cox < alan @ lxorguk . ukuu . org . uk >
2005-04-17 02:20:36 +04:00
*
* Neither Oleg Drokin nor iXcelerator . com admit liability nor provide
* warranty for any of this software . This material is provided
* " AS-IS " and at no charge .
*
* ( c ) Copyright 2000 Oleg Drokin < green @ crimea . edu >
*
2009-03-18 11:35:09 +03:00
* 27 / 11 / 2000 Initial release
2005-04-17 02:20:36 +04:00
*/
2012-02-16 03:06:19 +04:00
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2005-04-17 02:20:36 +04:00
# include <linux/module.h>
# include <linux/moduleparam.h>
2016-09-19 22:12:14 +03:00
# include <linux/clk.h>
2005-04-17 02:20:36 +04:00
# include <linux/types.h>
# include <linux/kernel.h>
# include <linux/fs.h>
2019-09-08 23:33:51 +03:00
# include <linux/platform_device.h>
2005-04-17 02:20:36 +04:00
# include <linux/miscdevice.h>
# include <linux/watchdog.h>
# include <linux/init.h>
2012-03-20 23:33:19 +04:00
# include <linux/io.h>
2007-10-19 10:40:25 +04:00
# include <linux/bitops.h>
2008-05-19 17:08:05 +04:00
# include <linux/uaccess.h>
2008-11-29 20:35:51 +03:00
# include <linux/timex.h>
2005-04-17 02:20:36 +04:00
2019-09-08 23:33:51 +03:00
# define REG_OSMR0 0x0000 /* OS timer Match Reg. 0 */
# define REG_OSMR1 0x0004 /* OS timer Match Reg. 1 */
# define REG_OSMR2 0x0008 /* OS timer Match Reg. 2 */
# define REG_OSMR3 0x000c /* OS timer Match Reg. 3 */
# define REG_OSCR 0x0010 /* OS timer Counter Reg. */
# define REG_OSSR 0x0014 /* OS timer Status Reg. */
# define REG_OWER 0x0018 /* OS timer Watch-dog Enable Reg. */
# define REG_OIER 0x001C /* OS timer Interrupt Enable Reg. */
2005-04-17 02:20:36 +04:00
2019-09-08 23:33:51 +03:00
# define OSSR_M3 (1 << 3) /* Match status channel 3 */
# define OSSR_M2 (1 << 2) /* Match status channel 2 */
# define OSSR_M1 (1 << 1) /* Match status channel 1 */
# define OSSR_M0 (1 << 0) /* Match status channel 0 */
# define OWER_WME (1 << 0) /* Watchdog Match Enable */
# define OIER_E3 (1 << 3) /* Interrupt enable channel 3 */
# define OIER_E2 (1 << 2) /* Interrupt enable channel 2 */
# define OIER_E1 (1 << 1) /* Interrupt enable channel 1 */
# define OIER_E0 (1 << 0) /* Interrupt enable channel 0 */
2005-04-17 02:20:36 +04:00
2008-12-24 06:32:45 +03:00
static unsigned long oscr_freq ;
2005-04-17 02:20:36 +04:00
static unsigned long sa1100wdt_users ;
2009-06-29 21:56:52 +04:00
static unsigned int pre_margin ;
2005-04-17 02:20:36 +04:00
static int boot_status ;
2019-09-08 23:33:51 +03:00
static void __iomem * reg_base ;
static inline void sa1100_wr ( u32 val , u32 offset )
{
writel_relaxed ( val , reg_base + offset ) ;
}
static inline u32 sa1100_rd ( u32 offset )
{
return readl_relaxed ( reg_base + offset ) ;
}
2005-04-17 02:20:36 +04:00
/*
* Allow only one person to hold it open
*/
static int sa1100dog_open ( struct inode * inode , struct file * file )
{
2008-05-19 17:08:05 +04:00
if ( test_and_set_bit ( 1 , & sa1100wdt_users ) )
2005-04-17 02:20:36 +04:00
return - EBUSY ;
/* Activate SA1100 Watchdog timer */
2019-09-08 23:33:51 +03:00
sa1100_wr ( sa1100_rd ( REG_OSCR ) + pre_margin , REG_OSMR3 ) ;
sa1100_wr ( OSSR_M3 , REG_OSSR ) ;
sa1100_wr ( OWER_WME , REG_OWER ) ;
sa1100_wr ( sa1100_rd ( REG_OIER ) | OIER_E3 , REG_OIER ) ;
2019-03-26 23:51:19 +03:00
return stream_open ( inode , file ) ;
2005-04-17 02:20:36 +04:00
}
/*
2005-08-03 23:34:52 +04:00
* The watchdog cannot be disabled .
*
* Previous comments suggested that turning off the interrupt by
2019-09-08 23:33:51 +03:00
* clearing REG_OIER [ E3 ] would prevent the watchdog timing out but this
2005-08-03 23:34:52 +04:00
* does not appear to be true ( at least on the PXA255 ) .
2005-04-17 02:20:36 +04:00
*/
static int sa1100dog_release ( struct inode * inode , struct file * file )
{
2012-02-16 03:06:19 +04:00
pr_crit ( " Device closed - timer will not stop \n " ) ;
2005-04-17 02:20:36 +04:00
clear_bit ( 1 , & sa1100wdt_users ) ;
return 0 ;
}
2008-05-19 17:08:05 +04:00
static ssize_t sa1100dog_write ( struct file * file , const char __user * data ,
size_t len , loff_t * ppos )
2005-04-17 02:20:36 +04:00
{
2005-08-03 23:34:52 +04:00
if ( len )
2005-04-17 02:20:36 +04:00
/* Refresh OSMR3 timer. */
2019-09-08 23:33:51 +03:00
sa1100_wr ( sa1100_rd ( REG_OSCR ) + pre_margin , REG_OSMR3 ) ;
2005-04-17 02:20:36 +04:00
return len ;
}
2008-05-19 17:08:05 +04:00
static const struct watchdog_info ident = {
. options = WDIOF_CARDRESET | WDIOF_SETTIMEOUT
| WDIOF_KEEPALIVEPING ,
2005-08-03 23:34:52 +04:00
. identity = " SA1100/PXA255 Watchdog " ,
2009-06-29 21:56:52 +04:00
. firmware_version = 1 ,
2005-04-17 02:20:36 +04:00
} ;
2008-05-19 17:08:05 +04:00
static long sa1100dog_ioctl ( struct file * file , unsigned int cmd ,
unsigned long arg )
2005-04-17 02:20:36 +04:00
{
2006-09-09 19:34:31 +04:00
int ret = - ENOTTY ;
2005-04-17 02:20:36 +04:00
int time ;
2005-11-07 13:21:24 +03:00
void __user * argp = ( void __user * ) arg ;
int __user * p = argp ;
2005-04-17 02:20:36 +04:00
switch ( cmd ) {
case WDIOC_GETSUPPORT :
2005-11-07 13:21:24 +03:00
ret = copy_to_user ( argp , & ident ,
2005-04-17 02:20:36 +04:00
sizeof ( ident ) ) ? - EFAULT : 0 ;
break ;
case WDIOC_GETSTATUS :
2005-11-07 13:21:24 +03:00
ret = put_user ( 0 , p ) ;
2005-04-17 02:20:36 +04:00
break ;
case WDIOC_GETBOOTSTATUS :
2005-11-07 13:21:24 +03:00
ret = put_user ( boot_status , p ) ;
2005-04-17 02:20:36 +04:00
break ;
2008-07-18 15:41:17 +04:00
case WDIOC_KEEPALIVE :
2019-09-08 23:33:51 +03:00
sa1100_wr ( sa1100_rd ( REG_OSCR ) + pre_margin , REG_OSMR3 ) ;
2008-07-18 15:41:17 +04:00
ret = 0 ;
break ;
2005-04-17 02:20:36 +04:00
case WDIOC_SETTIMEOUT :
2005-11-07 13:21:24 +03:00
ret = get_user ( time , p ) ;
2005-04-17 02:20:36 +04:00
if ( ret )
break ;
2009-06-29 21:56:52 +04:00
if ( time < = 0 | | ( oscr_freq * ( long long ) time > = 0xffffffff ) ) {
2005-04-17 02:20:36 +04:00
ret = - EINVAL ;
break ;
}
2008-12-24 06:32:45 +03:00
pre_margin = oscr_freq * time ;
2019-09-08 23:33:51 +03:00
sa1100_wr ( sa1100_rd ( REG_OSCR ) + pre_margin , REG_OSMR3 ) ;
2020-07-07 20:11:21 +03:00
fallthrough ;
2005-04-17 02:20:36 +04:00
case WDIOC_GETTIMEOUT :
2008-12-24 06:32:45 +03:00
ret = put_user ( pre_margin / oscr_freq , p ) ;
2005-04-17 02:20:36 +04:00
break ;
}
return ret ;
}
2008-05-19 17:08:05 +04:00
static const struct file_operations sa1100dog_fops = {
2005-04-17 02:20:36 +04:00
. owner = THIS_MODULE ,
. llseek = no_llseek ,
. write = sa1100dog_write ,
2008-05-19 17:08:05 +04:00
. unlocked_ioctl = sa1100dog_ioctl ,
2019-06-03 15:23:09 +03:00
. compat_ioctl = compat_ptr_ioctl ,
2005-04-17 02:20:36 +04:00
. open = sa1100dog_open ,
. release = sa1100dog_release ,
} ;
2008-05-19 17:08:05 +04:00
static struct miscdevice sa1100dog_miscdev = {
2005-04-17 02:20:36 +04:00
. minor = WATCHDOG_MINOR ,
2005-08-03 23:34:52 +04:00
. name = " watchdog " ,
2005-04-17 02:20:36 +04:00
. fops = & sa1100dog_fops ,
} ;
2019-09-08 23:33:51 +03:00
static int margin = 60 ; /* (secs) Default is 1 minute */
2016-09-19 22:12:14 +03:00
static struct clk * clk ;
2005-04-17 02:20:36 +04:00
2019-09-08 23:33:51 +03:00
static int sa1100dog_probe ( struct platform_device * pdev )
2005-04-17 02:20:36 +04:00
{
int ret ;
2019-09-08 23:33:51 +03:00
int * platform_data ;
struct resource * res ;
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
if ( ! res )
return - ENXIO ;
reg_base = devm_ioremap ( & pdev - > dev , res - > start , resource_size ( res ) ) ;
ret = PTR_ERR_OR_ZERO ( reg_base ) ;
if ( ret )
return ret ;
2005-04-17 02:20:36 +04:00
2016-09-19 22:12:14 +03:00
clk = clk_get ( NULL , " OSTIMER0 " ) ;
if ( IS_ERR ( clk ) ) {
pr_err ( " SA1100/PXA2xx Watchdog Timer: clock not found: %d \n " ,
( int ) PTR_ERR ( clk ) ) ;
return PTR_ERR ( clk ) ;
}
ret = clk_prepare_enable ( clk ) ;
if ( ret ) {
pr_err ( " SA1100/PXA2xx Watchdog Timer: clock failed to prepare+enable: %d \n " ,
ret ) ;
goto err ;
}
oscr_freq = clk_get_rate ( clk ) ;
2008-12-24 06:32:45 +03:00
2019-09-08 23:33:51 +03:00
platform_data = pdev - > dev . platform_data ;
if ( platform_data & & * platform_data )
boot_status = WDIOF_CARDRESET ;
2008-12-24 06:32:45 +03:00
pre_margin = oscr_freq * margin ;
2005-04-17 02:20:36 +04:00
ret = misc_register ( & sa1100dog_miscdev ) ;
2016-12-21 04:18:16 +03:00
if ( ret = = 0 ) {
2012-02-16 03:06:19 +04:00
pr_info ( " SA1100/PXA2xx Watchdog Timer: timer margin %d sec \n " ,
margin ) ;
2016-12-21 04:18:16 +03:00
return 0 ;
}
2016-09-19 22:12:14 +03:00
clk_disable_unprepare ( clk ) ;
2016-12-21 04:18:16 +03:00
err :
2016-09-19 22:12:14 +03:00
clk_put ( clk ) ;
return ret ;
2005-04-17 02:20:36 +04:00
}
2023-03-04 00:37:11 +03:00
static void sa1100dog_remove ( struct platform_device * pdev )
2005-04-17 02:20:36 +04:00
{
misc_deregister ( & sa1100dog_miscdev ) ;
2016-09-19 22:12:14 +03:00
clk_disable_unprepare ( clk ) ;
clk_put ( clk ) ;
2005-04-17 02:20:36 +04:00
}
2022-08-26 11:52:43 +03:00
static struct platform_driver sa1100dog_driver = {
2019-09-08 23:33:51 +03:00
. driver . name = " sa1100_wdt " ,
. probe = sa1100dog_probe ,
2023-03-04 00:37:11 +03:00
. remove_new = sa1100dog_remove ,
2019-09-08 23:33:51 +03:00
} ;
module_platform_driver ( sa1100dog_driver ) ;
2005-04-17 02:20:36 +04:00
MODULE_AUTHOR ( " Oleg Drokin <green@crimea.edu> " ) ;
MODULE_DESCRIPTION ( " SA1100/PXA2xx Watchdog " ) ;
module_param ( margin , int , 0 ) ;
MODULE_PARM_DESC ( margin , " Watchdog margin in seconds (default 60s) " ) ;
MODULE_LICENSE ( " GPL " ) ;