Commit | Line | Data |
---|---|---|
a912e80b | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
f865c352 PC |
2 | /* |
3 | * Copyright (C) 2010, Paul Cercueil <paul@crapouillou.net> | |
4 | * JZ4740 Watchdog driver | |
f865c352 PC |
5 | */ |
6 | ||
df04cce3 | 7 | #include <linux/mfd/ingenic-tcu.h> |
f865c352 PC |
8 | #include <linux/module.h> |
9 | #include <linux/moduleparam.h> | |
10 | #include <linux/types.h> | |
11 | #include <linux/kernel.h> | |
f865c352 | 12 | #include <linux/watchdog.h> |
f865c352 | 13 | #include <linux/platform_device.h> |
f865c352 PC |
14 | #include <linux/io.h> |
15 | #include <linux/device.h> | |
16 | #include <linux/clk.h> | |
17 | #include <linux/slab.h> | |
85f6df14 | 18 | #include <linux/err.h> |
6b96c722 | 19 | #include <linux/of.h> |
f865c352 PC |
20 | |
21 | #include <asm/mach-jz4740/timer.h> | |
22 | ||
f865c352 PC |
23 | #define JZ_WDT_CLOCK_PCLK 0x1 |
24 | #define JZ_WDT_CLOCK_RTC 0x2 | |
25 | #define JZ_WDT_CLOCK_EXT 0x4 | |
26 | ||
df04cce3 PC |
27 | #define JZ_WDT_CLOCK_DIV_1 (0 << TCU_TCSR_PRESCALE_LSB) |
28 | #define JZ_WDT_CLOCK_DIV_4 (1 << TCU_TCSR_PRESCALE_LSB) | |
29 | #define JZ_WDT_CLOCK_DIV_16 (2 << TCU_TCSR_PRESCALE_LSB) | |
30 | #define JZ_WDT_CLOCK_DIV_64 (3 << TCU_TCSR_PRESCALE_LSB) | |
31 | #define JZ_WDT_CLOCK_DIV_256 (4 << TCU_TCSR_PRESCALE_LSB) | |
32 | #define JZ_WDT_CLOCK_DIV_1024 (5 << TCU_TCSR_PRESCALE_LSB) | |
f865c352 PC |
33 | |
34 | #define DEFAULT_HEARTBEAT 5 | |
35 | #define MAX_HEARTBEAT 2048 | |
36 | ||
85f6df14 AL |
37 | static bool nowayout = WATCHDOG_NOWAYOUT; |
38 | module_param(nowayout, bool, 0); | |
39 | MODULE_PARM_DESC(nowayout, | |
40 | "Watchdog cannot be stopped once started (default=" | |
41 | __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | |
f865c352 | 42 | |
85f6df14 AL |
43 | static unsigned int heartbeat = DEFAULT_HEARTBEAT; |
44 | module_param(heartbeat, uint, 0); | |
45 | MODULE_PARM_DESC(heartbeat, | |
46 | "Watchdog heartbeat period in seconds from 1 to " | |
47 | __MODULE_STRING(MAX_HEARTBEAT) ", default " | |
48 | __MODULE_STRING(DEFAULT_HEARTBEAT)); | |
f865c352 | 49 | |
85f6df14 AL |
50 | struct jz4740_wdt_drvdata { |
51 | struct watchdog_device wdt; | |
52 | void __iomem *base; | |
53 | struct clk *rtc_clk; | |
54 | }; | |
f865c352 | 55 | |
85f6df14 | 56 | static int jz4740_wdt_ping(struct watchdog_device *wdt_dev) |
f865c352 | 57 | { |
85f6df14 AL |
58 | struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); |
59 | ||
df04cce3 | 60 | writew(0x0, drvdata->base + TCU_REG_WDT_TCNT); |
85f6df14 | 61 | return 0; |
f865c352 PC |
62 | } |
63 | ||
85f6df14 AL |
64 | static int jz4740_wdt_set_timeout(struct watchdog_device *wdt_dev, |
65 | unsigned int new_timeout) | |
f865c352 | 66 | { |
85f6df14 | 67 | struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); |
f865c352 PC |
68 | unsigned int rtc_clk_rate; |
69 | unsigned int timeout_value; | |
70 | unsigned short clock_div = JZ_WDT_CLOCK_DIV_1; | |
9b346118 | 71 | u8 tcer; |
f865c352 | 72 | |
85f6df14 | 73 | rtc_clk_rate = clk_get_rate(drvdata->rtc_clk); |
f865c352 | 74 | |
85f6df14 | 75 | timeout_value = rtc_clk_rate * new_timeout; |
f865c352 PC |
76 | while (timeout_value > 0xffff) { |
77 | if (clock_div == JZ_WDT_CLOCK_DIV_1024) { | |
78 | /* Requested timeout too high; | |
79 | * use highest possible value. */ | |
80 | timeout_value = 0xffff; | |
81 | break; | |
82 | } | |
83 | timeout_value >>= 2; | |
df04cce3 | 84 | clock_div += (1 << TCU_TCSR_PRESCALE_LSB); |
f865c352 PC |
85 | } |
86 | ||
9b346118 | 87 | tcer = readb(drvdata->base + TCU_REG_WDT_TCER); |
df04cce3 PC |
88 | writeb(0x0, drvdata->base + TCU_REG_WDT_TCER); |
89 | writew(clock_div, drvdata->base + TCU_REG_WDT_TCSR); | |
f865c352 | 90 | |
df04cce3 PC |
91 | writew((u16)timeout_value, drvdata->base + TCU_REG_WDT_TDR); |
92 | writew(0x0, drvdata->base + TCU_REG_WDT_TCNT); | |
93 | writew(clock_div | JZ_WDT_CLOCK_RTC, drvdata->base + TCU_REG_WDT_TCSR); | |
f865c352 | 94 | |
9b346118 PC |
95 | if (tcer & TCU_WDT_TCER_TCEN) |
96 | writeb(TCU_WDT_TCER_TCEN, drvdata->base + TCU_REG_WDT_TCER); | |
f865c352 | 97 | |
0197c1c4 | 98 | wdt_dev->timeout = new_timeout; |
85f6df14 | 99 | return 0; |
f865c352 PC |
100 | } |
101 | ||
85f6df14 | 102 | static int jz4740_wdt_start(struct watchdog_device *wdt_dev) |
f865c352 | 103 | { |
9b346118 PC |
104 | struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); |
105 | u8 tcer; | |
106 | ||
107 | tcer = readb(drvdata->base + TCU_REG_WDT_TCER); | |
108 | ||
85f6df14 AL |
109 | jz4740_timer_enable_watchdog(); |
110 | jz4740_wdt_set_timeout(wdt_dev, wdt_dev->timeout); | |
f865c352 | 111 | |
9b346118 PC |
112 | /* Start watchdog if it wasn't started already */ |
113 | if (!(tcer & TCU_WDT_TCER_TCEN)) | |
114 | writeb(TCU_WDT_TCER_TCEN, drvdata->base + TCU_REG_WDT_TCER); | |
115 | ||
85f6df14 | 116 | return 0; |
f865c352 PC |
117 | } |
118 | ||
85f6df14 | 119 | static int jz4740_wdt_stop(struct watchdog_device *wdt_dev) |
f865c352 | 120 | { |
85f6df14 | 121 | struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); |
742e4b63 | 122 | |
df04cce3 | 123 | writeb(0x0, drvdata->base + TCU_REG_WDT_TCER); |
212c1054 | 124 | jz4740_timer_disable_watchdog(); |
f865c352 | 125 | |
85f6df14 | 126 | return 0; |
f865c352 PC |
127 | } |
128 | ||
b4918057 PC |
129 | static int jz4740_wdt_restart(struct watchdog_device *wdt_dev, |
130 | unsigned long action, void *data) | |
131 | { | |
132 | wdt_dev->timeout = 0; | |
133 | jz4740_wdt_start(wdt_dev); | |
134 | return 0; | |
135 | } | |
136 | ||
85f6df14 AL |
137 | static const struct watchdog_info jz4740_wdt_info = { |
138 | .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, | |
f865c352 PC |
139 | .identity = "jz4740 Watchdog", |
140 | }; | |
141 | ||
85f6df14 | 142 | static const struct watchdog_ops jz4740_wdt_ops = { |
f865c352 | 143 | .owner = THIS_MODULE, |
85f6df14 AL |
144 | .start = jz4740_wdt_start, |
145 | .stop = jz4740_wdt_stop, | |
146 | .ping = jz4740_wdt_ping, | |
147 | .set_timeout = jz4740_wdt_set_timeout, | |
b4918057 | 148 | .restart = jz4740_wdt_restart, |
f865c352 PC |
149 | }; |
150 | ||
6b96c722 ZLK |
151 | #ifdef CONFIG_OF |
152 | static const struct of_device_id jz4740_wdt_of_matches[] = { | |
153 | { .compatible = "ingenic,jz4740-watchdog", }, | |
71246c35 | 154 | { .compatible = "ingenic,jz4780-watchdog", }, |
6b96c722 ZLK |
155 | { /* sentinel */ } |
156 | }; | |
35ffa961 | 157 | MODULE_DEVICE_TABLE(of, jz4740_wdt_of_matches); |
6b96c722 ZLK |
158 | #endif |
159 | ||
2d991a16 | 160 | static int jz4740_wdt_probe(struct platform_device *pdev) |
f865c352 | 161 | { |
02189bb9 | 162 | struct device *dev = &pdev->dev; |
85f6df14 AL |
163 | struct jz4740_wdt_drvdata *drvdata; |
164 | struct watchdog_device *jz4740_wdt; | |
85f6df14 AL |
165 | int ret; |
166 | ||
02189bb9 | 167 | drvdata = devm_kzalloc(dev, sizeof(struct jz4740_wdt_drvdata), |
85f6df14 | 168 | GFP_KERNEL); |
e26e74b1 | 169 | if (!drvdata) |
85f6df14 | 170 | return -ENOMEM; |
f865c352 | 171 | |
85f6df14 AL |
172 | if (heartbeat < 1 || heartbeat > MAX_HEARTBEAT) |
173 | heartbeat = DEFAULT_HEARTBEAT; | |
f865c352 | 174 | |
85f6df14 AL |
175 | jz4740_wdt = &drvdata->wdt; |
176 | jz4740_wdt->info = &jz4740_wdt_info; | |
177 | jz4740_wdt->ops = &jz4740_wdt_ops; | |
178 | jz4740_wdt->timeout = heartbeat; | |
179 | jz4740_wdt->min_timeout = 1; | |
180 | jz4740_wdt->max_timeout = MAX_HEARTBEAT; | |
02189bb9 | 181 | jz4740_wdt->parent = dev; |
85f6df14 AL |
182 | watchdog_set_nowayout(jz4740_wdt, nowayout); |
183 | watchdog_set_drvdata(jz4740_wdt, drvdata); | |
184 | ||
0f0a6a28 | 185 | drvdata->base = devm_platform_ioremap_resource(pdev, 0); |
6bdbc1f7 PC |
186 | if (IS_ERR(drvdata->base)) |
187 | return PTR_ERR(drvdata->base); | |
f865c352 | 188 | |
02189bb9 | 189 | drvdata->rtc_clk = devm_clk_get(dev, "rtc"); |
85f6df14 | 190 | if (IS_ERR(drvdata->rtc_clk)) { |
02189bb9 | 191 | dev_err(dev, "cannot find RTC clock\n"); |
6bdbc1f7 | 192 | return PTR_ERR(drvdata->rtc_clk); |
f865c352 PC |
193 | } |
194 | ||
9ee644c9 | 195 | return devm_watchdog_register_device(dev, &drvdata->wdt); |
f865c352 PC |
196 | } |
197 | ||
f865c352 PC |
198 | static struct platform_driver jz4740_wdt_driver = { |
199 | .probe = jz4740_wdt_probe, | |
f865c352 PC |
200 | .driver = { |
201 | .name = "jz4740-wdt", | |
6b96c722 | 202 | .of_match_table = of_match_ptr(jz4740_wdt_of_matches), |
f865c352 PC |
203 | }, |
204 | }; | |
205 | ||
b8ec6118 | 206 | module_platform_driver(jz4740_wdt_driver); |
f865c352 PC |
207 | |
208 | MODULE_AUTHOR("Paul Cercueil <paul@crapouillou.net>"); | |
209 | MODULE_DESCRIPTION("jz4740 Watchdog Driver"); | |
f865c352 | 210 | MODULE_LICENSE("GPL"); |
f865c352 | 211 | MODULE_ALIAS("platform:jz4740-wdt"); |