The Synopsys DesignWare watchdog is found in several ARM based systems
and provides a choice of 16 timeout periods depending on the clock
input. The watchdog cannot be disabled once started.
v3:
- convert pm to dev_pm_ops
- use devres for resource allocation
v2:
- constify fops
- request_mem_region() before ioremap()
- disable clk if misc_register() fails
Cc: Wim Van Sebroeck <redacted>
Signed-off-by: Jamie Iles <redacted>
---
drivers/watchdog/Kconfig | 9 ++
drivers/watchdog/Makefile | 1 +
drivers/watchdog/dw_wdt.c | 300 +++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 310 insertions(+), 0 deletions(-)
create mode 100644 drivers/watchdog/dw_wdt.c
@@ -0,0 +1,300 @@+/*+*Copyright2010PicochipLtd.,JamieIles+*http://www.picochip.com+*+*Thisprogramisfreesoftware;youcanredistributeitand/or+*modifyitunderthetermsoftheGNUGeneralPublicLicense+*aspublishedbytheFreeSoftwareFoundation;eitherversion+*2oftheLicense,or(atyouroption)anylaterversion.+*+*ThisfileimplementsadriverfortheSynopsysDesignWarewatchdogdevice+*inthemanyARMsubsystems.Thewatchdoghas16differenttimeoutperiods+*andtheseareafunctionoftheinputclockfrequency.+*/+#define pr_fmt(fmt) "dw_wdt: " fmt++#include<linux/clk.h>+#include<linux/device.h>+#include<linux/err.h>+#include<linux/fs.h>+#include<linux/io.h>+#include<linux/kernel.h>+#include<linux/miscdevice.h>+#include<linux/module.h>+#include<linux/pm.h>+#include<linux/platform_device.h>+#include<linux/spinlock.h>+#include<linux/uaccess.h>+#include<linux/watchdog.h>++#define WDOG_CONTROL_REG_OFFSET 0x00+#define WDOG_TIMEOUT_RANGE_REG_OFFSET 0x04+#define WDOG_CURRENT_COUNT_REG_OFFSET 0x08+#define WDOG_COUNTER_RESTART_REG_OFFSET 0x0c++/* The maximum TOP (timeout period) value that can be set in the watchdog. */+#define DW_WDT_MAX_TOP 15++staticstruct{+spinlock_tlock;+void__iomem*regs;+structclk*clk;+}dw_wdt;++staticinlineintdw_wdt_is_enabled(void)+{+#define WDOG_CONTROL_REG_WDT_EN_MASK 0x01+returnreadl(dw_wdt.regs+WDOG_CONTROL_REG_OFFSET)&+WDOG_CONTROL_REG_WDT_EN_MASK;+}++staticinlineintdw_wdt_top_in_seconds(unsignedtop)+{+/*+*Thereare16possibletimeoutvaluesin0..15wherethenumberof+*cyclesis2^(16+i)andthewatchdogcountsdown.+*/+return(1<<(16+top))/clk_get_rate(dw_wdt.clk);+}++staticintdw_wdt_set_top(unsignedtop_s)+{+inti,top_val=-1;++/*+*Iterateoverthetimeoutvaluesuntilwefindtheclosestmatch.We+*alwayslookfor>=.+*/+for(i=0;i<=DW_WDT_MAX_TOP;++i)+if(dw_wdt_top_in_seconds(i)>=top_s){+top_val=i;+break;+}++/*+*Ifwedidn'tfindasuitablevalue,itmusthavebeentoolarge.Go+*withthebiggestthatwecan.+*/+if(top_val<0)+top_val=DW_WDT_MAX_TOP;++/* Set the new value in the watchdog. */+writel(top_val,dw_wdt.regs+WDOG_TIMEOUT_RANGE_REG_OFFSET);++returndw_wdt_top_in_seconds(top_val);+}++staticintdw_wdt_get_top(void)+{+inttop=readl(dw_wdt.regs+WDOG_TIMEOUT_RANGE_REG_OFFSET)&0xF;++returndw_wdt_top_in_seconds(top);+}++staticvoiddw_wdt_keepalive(void)+{+#define WDOG_COUNTER_RESTART_KICK_VALUE 0x76+writel(WDOG_COUNTER_RESTART_KICK_VALUE,dw_wdt.regs++WDOG_COUNTER_RESTART_REG_OFFSET);+}++staticintdw_wdt_open(structinode*inode,structfile*filp)+{+/* Make sure we don't get unloaded. */+__module_get(THIS_MODULE);++spin_lock(&dw_wdt.lock);+if(!dw_wdt_is_enabled()){+/*+*Thewatchdogisnotcurrentlyenabled.Setthetimeoutto+*themaximumandthenstartit.+*/+dw_wdt_set_top(DW_WDT_MAX_TOP);+writel(WDOG_CONTROL_REG_WDT_EN_MASK,+dw_wdt.regs+WDOG_CONTROL_REG_OFFSET);+}+spin_unlock(&dw_wdt.lock);++returnnonseekable_open(inode,filp);+}++ssize_tdw_wdt_write(structfile*filp,constchar__user*buf,size_tlen,+loff_t*offset)+{+dw_wdt_keepalive();++returnlen;+}++staticu32dw_wdt_time_left(void)+{+returnreadl(dw_wdt.regs+WDOG_CURRENT_COUNT_REG_OFFSET)/+clk_get_rate(dw_wdt.clk);+}++staticconststructwatchdog_infodw_wdt_ident={+.options=WDIOF_KEEPALIVEPING|WDIOF_SETTIMEOUT,+.identity="Synopsys DesignWare Watchdog",+};++staticlongdw_wdt_ioctl(structfile*filp,unsignedintcmd,unsignedlongarg)+{+unsignedlongval;++switch(cmd){+caseWDIOC_GETSUPPORT:+returncopy_to_user((structwatchdog_info*)arg,&dw_wdt_ident,+sizeof(dw_wdt_ident))?-EFAULT:0;++caseWDIOC_GETSTATUS:+caseWDIOC_GETBOOTSTATUS:+returnput_user(0,(int*)arg);++caseWDIOC_KEEPALIVE:+dw_wdt_keepalive();+return0;++caseWDIOC_SETTIMEOUT:+if(get_user(val,(int__user*)arg))+return-EFAULT;+returnput_user(dw_wdt_set_top(val),(int__user*)arg);++caseWDIOC_GETTIMEOUT:+returnput_user(dw_wdt_get_top(),(int__user*)arg);++caseWDIOC_GETTIMELEFT:+/* Get the time left until expiry. */+if(get_user(val,(int__user*)arg))+return-EFAULT;+returnput_user(dw_wdt_time_left(),(int__user*)arg);++default:+return-ENOTTY;+}+}++staticintdw_wdt_release(structinode*inode,structfile*filp)+{+pr_crit("WATCHDOG: device closed - timer will not stop\n");++return0;+}++#ifdef CONFIG_PM+staticintdw_wdt_suspend(structdevice*dev)+{+clk_disable(dw_wdt.clk);++return0;+}++staticintdw_wdt_resume(structdevice*dev)+{+interr=clk_enable(dw_wdt.clk);++if(err)+returnerr;++dw_wdt_keepalive();++return0;+}++staticconststructdev_pm_opsdw_wdt_pm_ops={+.suspend=dw_wdt_suspend,+.resume=dw_wdt_resume,+};++#define DW_WDT_PM_OPS (&dw_wdt_pm_ops)+#else /* CONFIG_PM */+#define DW_WDT_PM_OPS NULL+#endif /* CONFIG_PM */++staticconststructfile_operationswdt_fops={+.owner=THIS_MODULE,+.llseek=no_llseek,+.open=dw_wdt_open,+.write=dw_wdt_write,+.unlocked_ioctl=dw_wdt_ioctl,+.release=dw_wdt_release+};++staticstructmiscdevicedw_wdt_miscdev={+.fops=&wdt_fops,+.name="watchdog",+.minor=WATCHDOG_MINOR,+};++staticint__devinitdw_wdt_drv_probe(structplatform_device*pdev)+{+intret;+structresource*mem=platform_get_resource(pdev,IORESOURCE_MEM,0);++if(!mem)+return-EINVAL;++if(!devm_request_mem_region(&pdev->dev,mem->start,resource_size(mem),+"iomem"))+return-ENOMEM;++dw_wdt.regs=devm_ioremap(&pdev->dev,mem->start,+resource_size(mem));+if(!dw_wdt.regs)+return-ENOMEM;++dw_wdt.clk=clk_get(&pdev->dev,NULL);+if(IS_ERR_OR_NULL(dw_wdt.clk))+return-ENODEV;+clk_enable(dw_wdt.clk);++ret=misc_register(&dw_wdt_miscdev);+if(ret)+gotoregister_failed;++return0;++register_failed:+clk_disable(dw_wdt.clk);+clk_put(dw_wdt.clk);++returnret;+}++staticint__devexitdw_wdt_drv_remove(structplatform_device*pdev)+{+clk_disable(dw_wdt.clk);+clk_put(dw_wdt.clk);++misc_deregister(&dw_wdt_miscdev);++return0;+}++staticstructplatform_driverdw_wdt_driver={+.probe=dw_wdt_drv_probe,+.remove=__devexit_p(dw_wdt_drv_remove),+.driver={+.name="dw_wdt",+.owner=THIS_MODULE,+.pm=DW_WDT_PM_OPS,+},+};++staticint__initdw_wdt_watchdog_init(void)+{+spin_lock_init(&dw_wdt.lock);++returnplatform_driver_register(&dw_wdt_driver);+}++staticvoid__exitdw_wdt_watchdog_exit(void)+{+platform_driver_unregister(&dw_wdt_driver);+}++module_init(dw_wdt_watchdog_init);+module_exit(dw_wdt_watchdog_exit);++MODULE_AUTHOR("Jamie Iles");+MODULE_DESCRIPTION("Synopsys DesignWare Watchdog Driver");+MODULE_LICENSE("GPL");
should release mem_region and free ioremaped space.
Also, may be we can continue here in case of error too.
Some platforms might nor support clock framework. You can enable and
disable clk's
if dw_wdt.clk is !NULL
should release mem_region and free ioremaped space.
Also, may be we can continue here in case of error too.
Some platforms might nor support clock framework. You can enable and
disable clk's
if dw_wdt.clk is !NULL
And some platforms have been known to return NULL for clk_get() as they
don't support more than one clock.
arch/arm/mach-aaec2000/core.c:struct clk *clk_get(struct device *dev, const char *id)
arch/arm/mach-aaec2000/core.c-{
arch/arm/mach-aaec2000/core.c- return dev && strcmp(dev_name(dev), "mb:16") ==
0 ? NULL : ERR_PTR(-ENOENT);
--
arch/arm/mach-at91/at91x40.c:struct clk *clk_get(struct device *dev, const char
*id)
arch/arm/mach-at91/at91x40.c-{
arch/arm/mach-at91/at91x40.c- return NULL;
--
arch/arm/mach-netx/fb.c:struct clk *clk_get(struct device *dev, const char *id)
arch/arm/mach-netx/fb.c-{
arch/arm/mach-netx/fb.c- return dev && strcmp(dev_name(dev), "fb") == 0 ? NULL : ERR_PTR(-ENOENT);
Please stick to the conventions of the API in the driver. IS_ERR() values
mean we failed. Anything else must be considered success. Don't assume
NULL means we failed.
+ ? ? ? writel(WDOG_COUNTER_RESTART_KICK_VALUE, dw_wdt.regs +
+ ? ? ? ? ? ? ?WDOG_COUNTER_RESTART_REG_OFFSET);
+}
+
+static int dw_wdt_open(struct inode *inode, struct file *filp)
+{
+ ? ? ? /* Make sure we don't get unloaded. */
+ ? ? ? __module_get(THIS_MODULE);
+
+ ? ? ? spin_lock(&dw_wdt.lock);
+ ? ? ? if (!dw_wdt_is_enabled()) {
+ ? ? ? ? ? ? ? /*
+ ? ? ? ? ? ? ? ?* The watchdog is not currently enabled. Set the timeout to
+ ? ? ? ? ? ? ? ?* the maximum and then start it.
+ ? ? ? ? ? ? ? ?*/
+ ? ? ? ? ? ? ? dw_wdt_set_top(DW_WDT_MAX_TOP);
shouldn't we check return value here??
No, dw_wdt_set_top() can't fail, it just returns the timeout period that
it set in seconds. We use this when the user changes the timeout period
as we may not be able to select the exact timeout period they chose, but
here we just select the maximum timeout.
should release mem_region and free ioremaped space.
We're using devres for the ioremap and region request so that takes care
of the cleanup for us. This also means we don't need to iounmap and
release in the release method.
Also, may be we can continue here in case of error too.
Some platforms might nor support clock framework. You can enable and
disable clk's if dw_wdt.clk is !NULL
The DesignWare watchdog has 16 timeout periods and these are derived
from the clock frequency input to the WDT. If we don't have a clk then
we don't know how long the timeout periods. The only alternative would
be to have a 'struct dw_wdt_platform_data' that includes the input clock
frequency which we use if we can't get a clk and get the rate.
On Fri, Jan 7, 2011 at 10:38 PM, Jamie Iles [off-list ref] wrote:
quoted
quoted
+static int dw_wdt_open(struct inode *inode, struct file *filp)
+{
+ ? ? ? /* Make sure we don't get unloaded. */
+ ? ? ? __module_get(THIS_MODULE);
+
+ ? ? ? spin_lock(&dw_wdt.lock);
+ ? ? ? if (!dw_wdt_is_enabled()) {
+ ? ? ? ? ? ? ? /*
+ ? ? ? ? ? ? ? ?* The watchdog is not currently enabled. Set the timeout to
+ ? ? ? ? ? ? ? ?* the maximum and then start it.
+ ? ? ? ? ? ? ? ?*/
+ ? ? ? ? ? ? ? dw_wdt_set_top(DW_WDT_MAX_TOP);
shouldn't we check return value here??
No, dw_wdt_set_top() can't fail, it just returns the timeout period that
it set in seconds. ?We use this when the user changes the timeout period
as we may not be able to select the exact timeout period they chose, but
here we just select the maximum timeout.
should release mem_region and free ioremaped space.
We're using devres for the ioremap and region request so that takes care
of the cleanup for us. ?This also means we don't need to iounmap and
release in the release method.
ok
quoted
Also, may be we can continue here in case of error too.
Some platforms might nor support clock framework. You can enable and
disable clk's if dw_wdt.clk is !NULL
The DesignWare watchdog has 16 timeout periods and these are derived
from the clock frequency input to the WDT. ?If we don't have a clk then
we don't know how long the timeout periods. ?The only alternative would
be to have a 'struct dw_wdt_platform_data' that includes the input clock
frequency which we use if we can't get a clk and get the rate.
should release mem_region and free ioremaped space.
Also, may be we can continue here in case of error too.
Some platforms might nor support clock framework. You can enable and
disable clk's
if dw_wdt.clk is !NULL
And some platforms have been known to return NULL for clk_get() as they
don't support more than one clock.
arch/arm/mach-aaec2000/core.c:struct clk *clk_get(struct device *dev, const char *id)
arch/arm/mach-aaec2000/core.c-{
arch/arm/mach-aaec2000/core.c- return dev && strcmp(dev_name(dev), "mb:16") ==
0 ? NULL : ERR_PTR(-ENOENT);
--
arch/arm/mach-at91/at91x40.c:struct clk *clk_get(struct device *dev, const char
*id)
arch/arm/mach-at91/at91x40.c-{
arch/arm/mach-at91/at91x40.c- return NULL;
--
arch/arm/mach-netx/fb.c:struct clk *clk_get(struct device *dev, const char *id)
arch/arm/mach-netx/fb.c-{
arch/arm/mach-netx/fb.c- return dev && strcmp(dev_name(dev), "fb") == 0 ? NULL : ERR_PTR(-ENOENT);
Please stick to the conventions of the API in the driver. IS_ERR() values
mean we failed. Anything else must be considered success. Don't assume
NULL means we failed.
Ok, so I'll change the driver to get the clock rate of the watchdog
through platform data for those that don't provide a struct clk and
clk_get_rate() returns 0.
Looking at mach-aaec2000 and mach-netx they don't provide a
clk_get_rate() which from linux/clk.h doesn't look like it's optional.
Should these platforms have some kind of stub clk_get_rate()?
Jamie
should release mem_region and free ioremaped space.
Also, may be we can continue here in case of error too.
Some platforms might nor support clock framework. You can enable and
disable clk's
if dw_wdt.clk is !NULL
And some platforms have been known to return NULL for clk_get() as they
don't support more than one clock.
arch/arm/mach-aaec2000/core.c:struct clk *clk_get(struct device *dev, const char *id)
arch/arm/mach-aaec2000/core.c-{
arch/arm/mach-aaec2000/core.c- return dev && strcmp(dev_name(dev), "mb:16") ==
0 ? NULL : ERR_PTR(-ENOENT);
--
arch/arm/mach-at91/at91x40.c:struct clk *clk_get(struct device *dev, const char
*id)
arch/arm/mach-at91/at91x40.c-{
arch/arm/mach-at91/at91x40.c- return NULL;
--
arch/arm/mach-netx/fb.c:struct clk *clk_get(struct device *dev, const char *id)
arch/arm/mach-netx/fb.c-{
arch/arm/mach-netx/fb.c- return dev && strcmp(dev_name(dev), "fb") == 0 ? NULL : ERR_PTR(-ENOENT);
Please stick to the conventions of the API in the driver. IS_ERR() values
mean we failed. Anything else must be considered success. Don't assume
NULL means we failed.
Ok, so I'll change the driver to get the clock rate of the watchdog
through platform data for those that don't provide a struct clk and
clk_get_rate() returns 0.
Looking at mach-aaec2000 and mach-netx they don't provide a
clk_get_rate() which from linux/clk.h doesn't look like it's optional.
Should these platforms have some kind of stub clk_get_rate()?
Probably, though I don't think aaec2000 is maintained anymore (I think it
was a dump-and-run thing) so should probably be deleted from the kernel
tree.
netx - since December 2008, it's only received updates when other stuff
has changed (eg, on my clocksource sweep, or when I've noticed it no
longer building.) So I think that's also a candidate for deletion
unless someone speaks up.
Wonder if anyone wants to volunteer to delete them... ;)
On Fri, Jan 07, 2011 at 06:09:24PM +0000, Russell King - ARM Linux wrote:
Probably, though I don't think aaec2000 is maintained anymore (I think it
was a dump-and-run thing) so should probably be deleted from the kernel
tree.
netx - since December 2008, it's only received updates when other stuff
has changed (eg, on my clocksource sweep, or when I've noticed it no
longer building.) So I think that's also a candidate for deletion
unless someone speaks up.
Wonder if anyone wants to volunteer to delete them... ;)
I'm happy to do that for aaec2000, patch to follow. I'm willing to do
the same for netx too or would you like to give people chance to shout
first?
Jamie
From: Wim Van Sebroeck <hidden> Date: 2011-01-09 10:07:32
Hi Jamie,
On top of other comments from Viresh:
+static int dw_wdt_open(struct inode *inode, struct file *filp)
+{
+ /* Make sure we don't get unloaded. */
+ __module_get(THIS_MODULE);
+
+ spin_lock(&dw_wdt.lock);
+ if (!dw_wdt_is_enabled()) {
+ /*
+ * The watchdog is not currently enabled. Set the timeout to
+ * the maximum and then start it.
+ */
+ dw_wdt_set_top(DW_WDT_MAX_TOP);
+ writel(WDOG_CONTROL_REG_WDT_EN_MASK,
+ dw_wdt.regs + WDOG_CONTROL_REG_OFFSET);
+ }
+ spin_unlock(&dw_wdt.lock);
+
+ return nonseekable_open(inode, filp);
+}
Would be nice to have the open /dev/watchdog once protection here also.