diff options
author | Andrew Jeffery <andrew@aj.id.au> | 2017-08-02 00:45:29 -0400 |
---|---|---|
committer | Wim Van Sebroeck <wim@iguana.be> | 2017-09-09 15:05:07 -0400 |
commit | 012c04601f9dc6a268ebff87a890b339af6d25bf (patch) | |
tree | 69197ddf19790273d3bed79de37ea6e8245eca46 /drivers/watchdog | |
parent | e654f19165483adfc30eeabe69a1efc23182bf0e (diff) |
watchdog: aspeed: Support configuration of external signal properties
Add support for configuring the drive strength and polarity on the
AST2500, and the pulse duration on both the AST2400 and AST2500.
Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
Tested-by: Matt Spinler <mspinler@linux.vnet.ibm.com>
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: Wim Van Sebroeck <wim@iguana.be>
Diffstat (limited to 'drivers/watchdog')
-rw-r--r-- | drivers/watchdog/aspeed_wdt.c | 105 |
1 files changed, 102 insertions, 3 deletions
diff --git a/drivers/watchdog/aspeed_wdt.c b/drivers/watchdog/aspeed_wdt.c index c707ab647922..79cc766cd30f 100644 --- a/drivers/watchdog/aspeed_wdt.c +++ b/drivers/watchdog/aspeed_wdt.c | |||
@@ -23,9 +23,21 @@ struct aspeed_wdt { | |||
23 | u32 ctrl; | 23 | u32 ctrl; |
24 | }; | 24 | }; |
25 | 25 | ||
26 | struct aspeed_wdt_config { | ||
27 | u32 ext_pulse_width_mask; | ||
28 | }; | ||
29 | |||
30 | static const struct aspeed_wdt_config ast2400_config = { | ||
31 | .ext_pulse_width_mask = 0xff, | ||
32 | }; | ||
33 | |||
34 | static const struct aspeed_wdt_config ast2500_config = { | ||
35 | .ext_pulse_width_mask = 0xfffff, | ||
36 | }; | ||
37 | |||
26 | static const struct of_device_id aspeed_wdt_of_table[] = { | 38 | static const struct of_device_id aspeed_wdt_of_table[] = { |
27 | { .compatible = "aspeed,ast2400-wdt" }, | 39 | { .compatible = "aspeed,ast2400-wdt", .data = &ast2400_config }, |
28 | { .compatible = "aspeed,ast2500-wdt" }, | 40 | { .compatible = "aspeed,ast2500-wdt", .data = &ast2500_config }, |
29 | { }, | 41 | { }, |
30 | }; | 42 | }; |
31 | MODULE_DEVICE_TABLE(of, aspeed_wdt_of_table); | 43 | MODULE_DEVICE_TABLE(of, aspeed_wdt_of_table); |
@@ -43,6 +55,38 @@ MODULE_DEVICE_TABLE(of, aspeed_wdt_of_table); | |||
43 | #define WDT_CTRL_RESET_SYSTEM BIT(1) | 55 | #define WDT_CTRL_RESET_SYSTEM BIT(1) |
44 | #define WDT_CTRL_ENABLE BIT(0) | 56 | #define WDT_CTRL_ENABLE BIT(0) |
45 | 57 | ||
58 | /* | ||
59 | * WDT_RESET_WIDTH controls the characteristics of the external pulse (if | ||
60 | * enabled), specifically: | ||
61 | * | ||
62 | * * Pulse duration | ||
63 | * * Drive mode: push-pull vs open-drain | ||
64 | * * Polarity: Active high or active low | ||
65 | * | ||
66 | * Pulse duration configuration is available on both the AST2400 and AST2500, | ||
67 | * though the field changes between SoCs: | ||
68 | * | ||
69 | * AST2400: Bits 7:0 | ||
70 | * AST2500: Bits 19:0 | ||
71 | * | ||
72 | * This difference is captured in struct aspeed_wdt_config. | ||
73 | * | ||
74 | * The AST2500 exposes the drive mode and polarity options, but not in a | ||
75 | * regular fashion. For read purposes, bit 31 represents active high or low, | ||
76 | * and bit 30 represents push-pull or open-drain. With respect to write, magic | ||
77 | * values need to be written to the top byte to change the state of the drive | ||
78 | * mode and polarity bits. Any other value written to the top byte has no | ||
79 | * effect on the state of the drive mode or polarity bits. However, the pulse | ||
80 | * width value must be preserved (as desired) if written. | ||
81 | */ | ||
82 | #define WDT_RESET_WIDTH 0x18 | ||
83 | #define WDT_RESET_WIDTH_ACTIVE_HIGH BIT(31) | ||
84 | #define WDT_ACTIVE_HIGH_MAGIC (0xA5 << 24) | ||
85 | #define WDT_ACTIVE_LOW_MAGIC (0x5A << 24) | ||
86 | #define WDT_RESET_WIDTH_PUSH_PULL BIT(30) | ||
87 | #define WDT_PUSH_PULL_MAGIC (0xA8 << 24) | ||
88 | #define WDT_OPEN_DRAIN_MAGIC (0x8A << 24) | ||
89 | |||
46 | #define WDT_RESTART_MAGIC 0x4755 | 90 | #define WDT_RESTART_MAGIC 0x4755 |
47 | 91 | ||
48 | /* 32 bits at 1MHz, in milliseconds */ | 92 | /* 32 bits at 1MHz, in milliseconds */ |
@@ -139,10 +183,13 @@ static const struct watchdog_info aspeed_wdt_info = { | |||
139 | 183 | ||
140 | static int aspeed_wdt_probe(struct platform_device *pdev) | 184 | static int aspeed_wdt_probe(struct platform_device *pdev) |
141 | { | 185 | { |
186 | const struct aspeed_wdt_config *config; | ||
187 | const struct of_device_id *ofdid; | ||
142 | struct aspeed_wdt *wdt; | 188 | struct aspeed_wdt *wdt; |
143 | struct resource *res; | 189 | struct resource *res; |
144 | struct device_node *np; | 190 | struct device_node *np; |
145 | const char *reset_type; | 191 | const char *reset_type; |
192 | u32 duration; | ||
146 | int ret; | 193 | int ret; |
147 | 194 | ||
148 | wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL); | 195 | wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL); |
@@ -167,13 +214,19 @@ static int aspeed_wdt_probe(struct platform_device *pdev) | |||
167 | wdt->wdd.timeout = WDT_DEFAULT_TIMEOUT; | 214 | wdt->wdd.timeout = WDT_DEFAULT_TIMEOUT; |
168 | watchdog_init_timeout(&wdt->wdd, 0, &pdev->dev); | 215 | watchdog_init_timeout(&wdt->wdd, 0, &pdev->dev); |
169 | 216 | ||
217 | np = pdev->dev.of_node; | ||
218 | |||
219 | ofdid = of_match_node(aspeed_wdt_of_table, np); | ||
220 | if (!ofdid) | ||
221 | return -EINVAL; | ||
222 | config = ofdid->data; | ||
223 | |||
170 | wdt->ctrl = WDT_CTRL_1MHZ_CLK; | 224 | wdt->ctrl = WDT_CTRL_1MHZ_CLK; |
171 | 225 | ||
172 | /* | 226 | /* |
173 | * Control reset on a per-device basis to ensure the | 227 | * Control reset on a per-device basis to ensure the |
174 | * host is not affected by a BMC reboot | 228 | * host is not affected by a BMC reboot |
175 | */ | 229 | */ |
176 | np = pdev->dev.of_node; | ||
177 | ret = of_property_read_string(np, "aspeed,reset-type", &reset_type); | 230 | ret = of_property_read_string(np, "aspeed,reset-type", &reset_type); |
178 | if (ret) { | 231 | if (ret) { |
179 | wdt->ctrl |= WDT_CTRL_RESET_MODE_SOC | WDT_CTRL_RESET_SYSTEM; | 232 | wdt->ctrl |= WDT_CTRL_RESET_MODE_SOC | WDT_CTRL_RESET_SYSTEM; |
@@ -197,6 +250,52 @@ static int aspeed_wdt_probe(struct platform_device *pdev) | |||
197 | set_bit(WDOG_HW_RUNNING, &wdt->wdd.status); | 250 | set_bit(WDOG_HW_RUNNING, &wdt->wdd.status); |
198 | } | 251 | } |
199 | 252 | ||
253 | if (of_device_is_compatible(np, "aspeed,ast2500-wdt")) { | ||
254 | u32 reg = readl(wdt->base + WDT_RESET_WIDTH); | ||
255 | |||
256 | reg &= config->ext_pulse_width_mask; | ||
257 | if (of_property_read_bool(np, "aspeed,ext-push-pull")) | ||
258 | reg |= WDT_PUSH_PULL_MAGIC; | ||
259 | else | ||
260 | reg |= WDT_OPEN_DRAIN_MAGIC; | ||
261 | |||
262 | writel(reg, wdt->base + WDT_RESET_WIDTH); | ||
263 | |||
264 | reg &= config->ext_pulse_width_mask; | ||
265 | if (of_property_read_bool(np, "aspeed,ext-active-high")) | ||
266 | reg |= WDT_ACTIVE_HIGH_MAGIC; | ||
267 | else | ||
268 | reg |= WDT_ACTIVE_LOW_MAGIC; | ||
269 | |||
270 | writel(reg, wdt->base + WDT_RESET_WIDTH); | ||
271 | } | ||
272 | |||
273 | if (!of_property_read_u32(np, "aspeed,ext-pulse-duration", &duration)) { | ||
274 | u32 max_duration = config->ext_pulse_width_mask + 1; | ||
275 | |||
276 | if (duration == 0 || duration > max_duration) { | ||
277 | dev_err(&pdev->dev, "Invalid pulse duration: %uus\n", | ||
278 | duration); | ||
279 | duration = max(1U, min(max_duration, duration)); | ||
280 | dev_info(&pdev->dev, "Pulse duration set to %uus\n", | ||
281 | duration); | ||
282 | } | ||
283 | |||
284 | /* | ||
285 | * The watchdog is always configured with a 1MHz source, so | ||
286 | * there is no need to scale the microsecond value. However we | ||
287 | * need to offset it - from the datasheet: | ||
288 | * | ||
289 | * "This register decides the asserting duration of wdt_ext and | ||
290 | * wdt_rstarm signal. The default value is 0xFF. It means the | ||
291 | * default asserting duration of wdt_ext and wdt_rstarm is | ||
292 | * 256us." | ||
293 | * | ||
294 | * This implies a value of 0 gives a 1us pulse. | ||
295 | */ | ||
296 | writel(duration - 1, wdt->base + WDT_RESET_WIDTH); | ||
297 | } | ||
298 | |||
200 | ret = devm_watchdog_register_device(&pdev->dev, &wdt->wdd); | 299 | ret = devm_watchdog_register_device(&pdev->dev, &wdt->wdd); |
201 | if (ret) { | 300 | if (ret) { |
202 | dev_err(&pdev->dev, "failed to register\n"); | 301 | dev_err(&pdev->dev, "failed to register\n"); |