.. | .. |
---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later |
---|
1 | 2 | /* |
---|
2 | 3 | * Copyright (C) 2010, Paul Cercueil <paul@crapouillou.net> |
---|
3 | 4 | * JZ4740 Watchdog driver |
---|
4 | | - * |
---|
5 | | - * This program is free software; you can redistribute it and/or modify it |
---|
6 | | - * under the terms of the GNU General Public License as published by the |
---|
7 | | - * Free Software Foundation; either version 2 of the License, or (at your |
---|
8 | | - * option) any later version. |
---|
9 | | - * |
---|
10 | | - * You should have received a copy of the GNU General Public License along |
---|
11 | | - * with this program; if not, write to the Free Software Foundation, Inc., |
---|
12 | | - * 675 Mass Ave, Cambridge, MA 02139, USA. |
---|
13 | | - * |
---|
14 | 5 | */ |
---|
15 | 6 | |
---|
| 7 | +#include <linux/mfd/ingenic-tcu.h> |
---|
| 8 | +#include <linux/mfd/syscon.h> |
---|
16 | 9 | #include <linux/module.h> |
---|
17 | 10 | #include <linux/moduleparam.h> |
---|
18 | 11 | #include <linux/types.h> |
---|
.. | .. |
---|
25 | 18 | #include <linux/slab.h> |
---|
26 | 19 | #include <linux/err.h> |
---|
27 | 20 | #include <linux/of.h> |
---|
28 | | - |
---|
29 | | -#include <asm/mach-jz4740/timer.h> |
---|
30 | | - |
---|
31 | | -#define JZ_REG_WDT_TIMER_DATA 0x0 |
---|
32 | | -#define JZ_REG_WDT_COUNTER_ENABLE 0x4 |
---|
33 | | -#define JZ_REG_WDT_TIMER_COUNTER 0x8 |
---|
34 | | -#define JZ_REG_WDT_TIMER_CONTROL 0xC |
---|
35 | | - |
---|
36 | | -#define JZ_WDT_CLOCK_PCLK 0x1 |
---|
37 | | -#define JZ_WDT_CLOCK_RTC 0x2 |
---|
38 | | -#define JZ_WDT_CLOCK_EXT 0x4 |
---|
39 | | - |
---|
40 | | -#define JZ_WDT_CLOCK_DIV_SHIFT 3 |
---|
41 | | - |
---|
42 | | -#define JZ_WDT_CLOCK_DIV_1 (0 << JZ_WDT_CLOCK_DIV_SHIFT) |
---|
43 | | -#define JZ_WDT_CLOCK_DIV_4 (1 << JZ_WDT_CLOCK_DIV_SHIFT) |
---|
44 | | -#define JZ_WDT_CLOCK_DIV_16 (2 << JZ_WDT_CLOCK_DIV_SHIFT) |
---|
45 | | -#define JZ_WDT_CLOCK_DIV_64 (3 << JZ_WDT_CLOCK_DIV_SHIFT) |
---|
46 | | -#define JZ_WDT_CLOCK_DIV_256 (4 << JZ_WDT_CLOCK_DIV_SHIFT) |
---|
47 | | -#define JZ_WDT_CLOCK_DIV_1024 (5 << JZ_WDT_CLOCK_DIV_SHIFT) |
---|
| 21 | +#include <linux/regmap.h> |
---|
48 | 22 | |
---|
49 | 23 | #define DEFAULT_HEARTBEAT 5 |
---|
50 | 24 | #define MAX_HEARTBEAT 2048 |
---|
.. | .. |
---|
64 | 38 | |
---|
65 | 39 | struct jz4740_wdt_drvdata { |
---|
66 | 40 | struct watchdog_device wdt; |
---|
67 | | - void __iomem *base; |
---|
68 | | - struct clk *rtc_clk; |
---|
| 41 | + struct regmap *map; |
---|
| 42 | + struct clk *clk; |
---|
| 43 | + unsigned long clk_rate; |
---|
69 | 44 | }; |
---|
70 | 45 | |
---|
71 | 46 | static int jz4740_wdt_ping(struct watchdog_device *wdt_dev) |
---|
72 | 47 | { |
---|
73 | 48 | struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); |
---|
74 | 49 | |
---|
75 | | - writew(0x0, drvdata->base + JZ_REG_WDT_TIMER_COUNTER); |
---|
| 50 | + regmap_write(drvdata->map, TCU_REG_WDT_TCNT, 0); |
---|
| 51 | + |
---|
76 | 52 | return 0; |
---|
77 | 53 | } |
---|
78 | 54 | |
---|
.. | .. |
---|
80 | 56 | unsigned int new_timeout) |
---|
81 | 57 | { |
---|
82 | 58 | struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); |
---|
83 | | - unsigned int rtc_clk_rate; |
---|
84 | | - unsigned int timeout_value; |
---|
85 | | - unsigned short clock_div = JZ_WDT_CLOCK_DIV_1; |
---|
| 59 | + u16 timeout_value = (u16)(drvdata->clk_rate * new_timeout); |
---|
| 60 | + unsigned int tcer; |
---|
86 | 61 | |
---|
87 | | - rtc_clk_rate = clk_get_rate(drvdata->rtc_clk); |
---|
| 62 | + regmap_read(drvdata->map, TCU_REG_WDT_TCER, &tcer); |
---|
| 63 | + regmap_write(drvdata->map, TCU_REG_WDT_TCER, 0); |
---|
88 | 64 | |
---|
89 | | - timeout_value = rtc_clk_rate * new_timeout; |
---|
90 | | - while (timeout_value > 0xffff) { |
---|
91 | | - if (clock_div == JZ_WDT_CLOCK_DIV_1024) { |
---|
92 | | - /* Requested timeout too high; |
---|
93 | | - * use highest possible value. */ |
---|
94 | | - timeout_value = 0xffff; |
---|
95 | | - break; |
---|
96 | | - } |
---|
97 | | - timeout_value >>= 2; |
---|
98 | | - clock_div += (1 << JZ_WDT_CLOCK_DIV_SHIFT); |
---|
99 | | - } |
---|
| 65 | + regmap_write(drvdata->map, TCU_REG_WDT_TDR, timeout_value); |
---|
| 66 | + regmap_write(drvdata->map, TCU_REG_WDT_TCNT, 0); |
---|
100 | 67 | |
---|
101 | | - writeb(0x0, drvdata->base + JZ_REG_WDT_COUNTER_ENABLE); |
---|
102 | | - writew(clock_div, drvdata->base + JZ_REG_WDT_TIMER_CONTROL); |
---|
103 | | - |
---|
104 | | - writew((u16)timeout_value, drvdata->base + JZ_REG_WDT_TIMER_DATA); |
---|
105 | | - writew(0x0, drvdata->base + JZ_REG_WDT_TIMER_COUNTER); |
---|
106 | | - writew(clock_div | JZ_WDT_CLOCK_RTC, |
---|
107 | | - drvdata->base + JZ_REG_WDT_TIMER_CONTROL); |
---|
108 | | - |
---|
109 | | - writeb(0x1, drvdata->base + JZ_REG_WDT_COUNTER_ENABLE); |
---|
| 68 | + if (tcer & TCU_WDT_TCER_TCEN) |
---|
| 69 | + regmap_write(drvdata->map, TCU_REG_WDT_TCER, TCU_WDT_TCER_TCEN); |
---|
110 | 70 | |
---|
111 | 71 | wdt_dev->timeout = new_timeout; |
---|
112 | 72 | return 0; |
---|
.. | .. |
---|
114 | 74 | |
---|
115 | 75 | static int jz4740_wdt_start(struct watchdog_device *wdt_dev) |
---|
116 | 76 | { |
---|
117 | | - jz4740_timer_enable_watchdog(); |
---|
| 77 | + struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); |
---|
| 78 | + unsigned int tcer; |
---|
| 79 | + int ret; |
---|
| 80 | + |
---|
| 81 | + ret = clk_prepare_enable(drvdata->clk); |
---|
| 82 | + if (ret) |
---|
| 83 | + return ret; |
---|
| 84 | + |
---|
| 85 | + regmap_read(drvdata->map, TCU_REG_WDT_TCER, &tcer); |
---|
| 86 | + |
---|
118 | 87 | jz4740_wdt_set_timeout(wdt_dev, wdt_dev->timeout); |
---|
| 88 | + |
---|
| 89 | + /* Start watchdog if it wasn't started already */ |
---|
| 90 | + if (!(tcer & TCU_WDT_TCER_TCEN)) |
---|
| 91 | + regmap_write(drvdata->map, TCU_REG_WDT_TCER, TCU_WDT_TCER_TCEN); |
---|
119 | 92 | |
---|
120 | 93 | return 0; |
---|
121 | 94 | } |
---|
.. | .. |
---|
124 | 97 | { |
---|
125 | 98 | struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); |
---|
126 | 99 | |
---|
127 | | - writeb(0x0, drvdata->base + JZ_REG_WDT_COUNTER_ENABLE); |
---|
128 | | - jz4740_timer_disable_watchdog(); |
---|
| 100 | + regmap_write(drvdata->map, TCU_REG_WDT_TCER, 0); |
---|
| 101 | + clk_disable_unprepare(drvdata->clk); |
---|
129 | 102 | |
---|
130 | 103 | return 0; |
---|
131 | 104 | } |
---|
.. | .. |
---|
163 | 136 | |
---|
164 | 137 | static int jz4740_wdt_probe(struct platform_device *pdev) |
---|
165 | 138 | { |
---|
| 139 | + struct device *dev = &pdev->dev; |
---|
166 | 140 | struct jz4740_wdt_drvdata *drvdata; |
---|
167 | 141 | struct watchdog_device *jz4740_wdt; |
---|
168 | | - struct resource *res; |
---|
| 142 | + long rate; |
---|
169 | 143 | int ret; |
---|
170 | 144 | |
---|
171 | | - drvdata = devm_kzalloc(&pdev->dev, sizeof(struct jz4740_wdt_drvdata), |
---|
| 145 | + drvdata = devm_kzalloc(dev, sizeof(struct jz4740_wdt_drvdata), |
---|
172 | 146 | GFP_KERNEL); |
---|
173 | 147 | if (!drvdata) |
---|
174 | 148 | return -ENOMEM; |
---|
175 | 149 | |
---|
176 | | - if (heartbeat < 1 || heartbeat > MAX_HEARTBEAT) |
---|
177 | | - heartbeat = DEFAULT_HEARTBEAT; |
---|
| 150 | + drvdata->clk = devm_clk_get(&pdev->dev, "wdt"); |
---|
| 151 | + if (IS_ERR(drvdata->clk)) { |
---|
| 152 | + dev_err(&pdev->dev, "cannot find WDT clock\n"); |
---|
| 153 | + return PTR_ERR(drvdata->clk); |
---|
| 154 | + } |
---|
178 | 155 | |
---|
| 156 | + /* Set smallest clock possible */ |
---|
| 157 | + rate = clk_round_rate(drvdata->clk, 1); |
---|
| 158 | + if (rate < 0) |
---|
| 159 | + return rate; |
---|
| 160 | + |
---|
| 161 | + ret = clk_set_rate(drvdata->clk, rate); |
---|
| 162 | + if (ret) |
---|
| 163 | + return ret; |
---|
| 164 | + |
---|
| 165 | + drvdata->clk_rate = rate; |
---|
179 | 166 | jz4740_wdt = &drvdata->wdt; |
---|
180 | 167 | jz4740_wdt->info = &jz4740_wdt_info; |
---|
181 | 168 | jz4740_wdt->ops = &jz4740_wdt_ops; |
---|
182 | | - jz4740_wdt->timeout = heartbeat; |
---|
183 | 169 | jz4740_wdt->min_timeout = 1; |
---|
184 | | - jz4740_wdt->max_timeout = MAX_HEARTBEAT; |
---|
185 | | - jz4740_wdt->parent = &pdev->dev; |
---|
| 170 | + jz4740_wdt->max_timeout = 0xffff / rate; |
---|
| 171 | + jz4740_wdt->timeout = clamp(heartbeat, |
---|
| 172 | + jz4740_wdt->min_timeout, |
---|
| 173 | + jz4740_wdt->max_timeout); |
---|
| 174 | + jz4740_wdt->parent = dev; |
---|
186 | 175 | watchdog_set_nowayout(jz4740_wdt, nowayout); |
---|
187 | 176 | watchdog_set_drvdata(jz4740_wdt, drvdata); |
---|
188 | 177 | |
---|
189 | | - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
---|
190 | | - drvdata->base = devm_ioremap_resource(&pdev->dev, res); |
---|
191 | | - if (IS_ERR(drvdata->base)) |
---|
192 | | - return PTR_ERR(drvdata->base); |
---|
193 | | - |
---|
194 | | - drvdata->rtc_clk = devm_clk_get(&pdev->dev, "rtc"); |
---|
195 | | - if (IS_ERR(drvdata->rtc_clk)) { |
---|
196 | | - dev_err(&pdev->dev, "cannot find RTC clock\n"); |
---|
197 | | - return PTR_ERR(drvdata->rtc_clk); |
---|
| 178 | + drvdata->map = device_node_to_regmap(dev->parent->of_node); |
---|
| 179 | + if (IS_ERR(drvdata->map)) { |
---|
| 180 | + dev_err(dev, "regmap not found\n"); |
---|
| 181 | + return PTR_ERR(drvdata->map); |
---|
198 | 182 | } |
---|
199 | 183 | |
---|
200 | | - ret = devm_watchdog_register_device(&pdev->dev, &drvdata->wdt); |
---|
201 | | - if (ret < 0) |
---|
202 | | - return ret; |
---|
203 | | - |
---|
204 | | - platform_set_drvdata(pdev, drvdata); |
---|
205 | | - |
---|
206 | | - return 0; |
---|
| 184 | + return devm_watchdog_register_device(dev, &drvdata->wdt); |
---|
207 | 185 | } |
---|
208 | 186 | |
---|
209 | 187 | static struct platform_driver jz4740_wdt_driver = { |
---|