Commit 5033263992eece84e19946d2cab940c86ec862ba
Committed by
Lee Jones
1 parent
dfbdcd7cef
watchdog: menf21bmc_wdt: Introduce MEN 14F021P00 BMC Watchdog driver
Added driver to support the 14F021P00 BMC Watchdog. The BMC is a Board Management Controller including watchdog functionality. Signed-off-by: Andreas Werner <andreas.werner@men.de> Reviewed-by: Guenter Roeck <linux@roeck-us.net> Signed-off-by: Lee Jones <lee.jones@linaro.org>
Showing 3 changed files with 214 additions and 0 deletions Side-by-side Diff
drivers/watchdog/Kconfig
... | ... | @@ -95,6 +95,16 @@ |
95 | 95 | If you say yes here you get support for watchdog device |
96 | 96 | controlled through GPIO-line. |
97 | 97 | |
98 | +config MENF21BMC_WATCHDOG | |
99 | + tristate "MEN 14F021P00 BMC Watchdog" | |
100 | + depends on MFD_MENF21BMC | |
101 | + select WATCHDOG_CORE | |
102 | + help | |
103 | + Say Y here to include support for the MEN 14F021P00 BMC Watchdog. | |
104 | + | |
105 | + This driver can also be built as a module. If so the module | |
106 | + will be called menf21bmc_wdt. | |
107 | + | |
98 | 108 | config WM831X_WATCHDOG |
99 | 109 | tristate "WM831x watchdog" |
100 | 110 | depends on MFD_WM831X |
drivers/watchdog/Makefile
drivers/watchdog/menf21bmc_wdt.c
1 | +/* | |
2 | + * MEN 14F021P00 Board Management Controller (BMC) Watchdog Driver. | |
3 | + * | |
4 | + * Copyright (C) 2014 MEN Mikro Elektronik Nuernberg GmbH | |
5 | + * | |
6 | + * This program is free software; you can redistribute it and/or modify it | |
7 | + * under the terms of the GNU General Public License as published by the | |
8 | + * Free Software Foundation; either version 2 of the License, or (at your | |
9 | + * option) any later version. | |
10 | + */ | |
11 | + | |
12 | +#include <linux/kernel.h> | |
13 | +#include <linux/device.h> | |
14 | +#include <linux/module.h> | |
15 | +#include <linux/watchdog.h> | |
16 | +#include <linux/platform_device.h> | |
17 | +#include <linux/i2c.h> | |
18 | + | |
19 | +#define DEVNAME "menf21bmc_wdt" | |
20 | + | |
21 | +#define BMC_CMD_WD_ON 0x11 | |
22 | +#define BMC_CMD_WD_OFF 0x12 | |
23 | +#define BMC_CMD_WD_TRIG 0x13 | |
24 | +#define BMC_CMD_WD_TIME 0x14 | |
25 | +#define BMC_CMD_WD_STATE 0x17 | |
26 | +#define BMC_WD_OFF_VAL 0x69 | |
27 | +#define BMC_CMD_RST_RSN 0x92 | |
28 | + | |
29 | +#define BMC_WD_TIMEOUT_MIN 1 /* in sec */ | |
30 | +#define BMC_WD_TIMEOUT_MAX 6553 /* in sec */ | |
31 | + | |
32 | +static bool nowayout = WATCHDOG_NOWAYOUT; | |
33 | +module_param(nowayout, bool, 0); | |
34 | +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" | |
35 | + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | |
36 | + | |
37 | +struct menf21bmc_wdt { | |
38 | + struct watchdog_device wdt; | |
39 | + struct i2c_client *i2c_client; | |
40 | +}; | |
41 | + | |
42 | +static int menf21bmc_wdt_set_bootstatus(struct menf21bmc_wdt *data) | |
43 | +{ | |
44 | + int rst_rsn; | |
45 | + | |
46 | + rst_rsn = i2c_smbus_read_byte_data(data->i2c_client, BMC_CMD_RST_RSN); | |
47 | + if (rst_rsn < 0) | |
48 | + return rst_rsn; | |
49 | + | |
50 | + if (rst_rsn == 0x02) | |
51 | + data->wdt.bootstatus |= WDIOF_CARDRESET; | |
52 | + else if (rst_rsn == 0x05) | |
53 | + data->wdt.bootstatus |= WDIOF_EXTERN1; | |
54 | + else if (rst_rsn == 0x06) | |
55 | + data->wdt.bootstatus |= WDIOF_EXTERN2; | |
56 | + else if (rst_rsn == 0x0A) | |
57 | + data->wdt.bootstatus |= WDIOF_POWERUNDER; | |
58 | + | |
59 | + return 0; | |
60 | +} | |
61 | + | |
62 | +static int menf21bmc_wdt_start(struct watchdog_device *wdt) | |
63 | +{ | |
64 | + struct menf21bmc_wdt *drv_data = watchdog_get_drvdata(wdt); | |
65 | + | |
66 | + return i2c_smbus_write_byte(drv_data->i2c_client, BMC_CMD_WD_ON); | |
67 | +} | |
68 | + | |
69 | +static int menf21bmc_wdt_stop(struct watchdog_device *wdt) | |
70 | +{ | |
71 | + struct menf21bmc_wdt *drv_data = watchdog_get_drvdata(wdt); | |
72 | + | |
73 | + return i2c_smbus_write_byte_data(drv_data->i2c_client, | |
74 | + BMC_CMD_WD_OFF, BMC_WD_OFF_VAL); | |
75 | +} | |
76 | + | |
77 | +static int | |
78 | +menf21bmc_wdt_settimeout(struct watchdog_device *wdt, unsigned int timeout) | |
79 | +{ | |
80 | + int ret; | |
81 | + struct menf21bmc_wdt *drv_data = watchdog_get_drvdata(wdt); | |
82 | + | |
83 | + /* | |
84 | + * BMC Watchdog does have a resolution of 100ms. | |
85 | + * Watchdog API defines the timeout in seconds, so we have to | |
86 | + * multiply the value. | |
87 | + */ | |
88 | + ret = i2c_smbus_write_word_data(drv_data->i2c_client, | |
89 | + BMC_CMD_WD_TIME, timeout * 10); | |
90 | + if (ret < 0) | |
91 | + return ret; | |
92 | + | |
93 | + wdt->timeout = timeout; | |
94 | + | |
95 | + return 0; | |
96 | +} | |
97 | + | |
98 | +static int menf21bmc_wdt_ping(struct watchdog_device *wdt) | |
99 | +{ | |
100 | + struct menf21bmc_wdt *drv_data = watchdog_get_drvdata(wdt); | |
101 | + | |
102 | + return i2c_smbus_write_byte(drv_data->i2c_client, BMC_CMD_WD_TRIG); | |
103 | +} | |
104 | + | |
105 | +static const struct watchdog_info menf21bmc_wdt_info = { | |
106 | + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, | |
107 | + .identity = DEVNAME, | |
108 | +}; | |
109 | + | |
110 | +static const struct watchdog_ops menf21bmc_wdt_ops = { | |
111 | + .owner = THIS_MODULE, | |
112 | + .start = menf21bmc_wdt_start, | |
113 | + .stop = menf21bmc_wdt_stop, | |
114 | + .ping = menf21bmc_wdt_ping, | |
115 | + .set_timeout = menf21bmc_wdt_settimeout, | |
116 | +}; | |
117 | + | |
118 | +static int menf21bmc_wdt_probe(struct platform_device *pdev) | |
119 | +{ | |
120 | + int ret, bmc_timeout; | |
121 | + struct menf21bmc_wdt *drv_data; | |
122 | + struct i2c_client *i2c_client = to_i2c_client(pdev->dev.parent); | |
123 | + | |
124 | + drv_data = devm_kzalloc(&pdev->dev, | |
125 | + sizeof(struct menf21bmc_wdt), GFP_KERNEL); | |
126 | + if (!drv_data) | |
127 | + return -ENOMEM; | |
128 | + | |
129 | + drv_data->wdt.ops = &menf21bmc_wdt_ops; | |
130 | + drv_data->wdt.info = &menf21bmc_wdt_info; | |
131 | + drv_data->wdt.min_timeout = BMC_WD_TIMEOUT_MIN; | |
132 | + drv_data->wdt.max_timeout = BMC_WD_TIMEOUT_MAX; | |
133 | + drv_data->i2c_client = i2c_client; | |
134 | + | |
135 | + /* | |
136 | + * Get the current wdt timeout value from the BMC because | |
137 | + * the BMC will save the value set before if the system restarts. | |
138 | + */ | |
139 | + bmc_timeout = i2c_smbus_read_word_data(drv_data->i2c_client, | |
140 | + BMC_CMD_WD_TIME); | |
141 | + if (bmc_timeout < 0) { | |
142 | + dev_err(&pdev->dev, "failed to get current WDT timeout\n"); | |
143 | + return bmc_timeout; | |
144 | + } | |
145 | + | |
146 | + watchdog_init_timeout(&drv_data->wdt, bmc_timeout / 10, &pdev->dev); | |
147 | + watchdog_set_nowayout(&drv_data->wdt, nowayout); | |
148 | + watchdog_set_drvdata(&drv_data->wdt, drv_data); | |
149 | + platform_set_drvdata(pdev, drv_data); | |
150 | + | |
151 | + ret = menf21bmc_wdt_set_bootstatus(drv_data); | |
152 | + if (ret < 0) { | |
153 | + dev_err(&pdev->dev, "failed to set Watchdog bootstatus\n"); | |
154 | + return ret; | |
155 | + } | |
156 | + | |
157 | + ret = watchdog_register_device(&drv_data->wdt); | |
158 | + if (ret) { | |
159 | + dev_err(&pdev->dev, "failed to register Watchdog device\n"); | |
160 | + return ret; | |
161 | + } | |
162 | + | |
163 | + dev_info(&pdev->dev, "MEN 14F021P00 BMC Watchdog device enabled\n"); | |
164 | + | |
165 | + return 0; | |
166 | +} | |
167 | + | |
168 | +static int menf21bmc_wdt_remove(struct platform_device *pdev) | |
169 | +{ | |
170 | + struct menf21bmc_wdt *drv_data = platform_get_drvdata(pdev); | |
171 | + | |
172 | + dev_warn(&pdev->dev, | |
173 | + "Unregister MEN 14F021P00 BMC Watchdog device, board may reset\n"); | |
174 | + | |
175 | + watchdog_unregister_device(&drv_data->wdt); | |
176 | + | |
177 | + return 0; | |
178 | +} | |
179 | + | |
180 | +static void menf21bmc_wdt_shutdown(struct platform_device *pdev) | |
181 | +{ | |
182 | + struct menf21bmc_wdt *drv_data = platform_get_drvdata(pdev); | |
183 | + | |
184 | + i2c_smbus_write_word_data(drv_data->i2c_client, | |
185 | + BMC_CMD_WD_OFF, BMC_WD_OFF_VAL); | |
186 | +} | |
187 | + | |
188 | +static struct platform_driver menf21bmc_wdt = { | |
189 | + .driver = { | |
190 | + .owner = THIS_MODULE, | |
191 | + .name = DEVNAME, | |
192 | + }, | |
193 | + .probe = menf21bmc_wdt_probe, | |
194 | + .remove = menf21bmc_wdt_remove, | |
195 | + .shutdown = menf21bmc_wdt_shutdown, | |
196 | +}; | |
197 | + | |
198 | +module_platform_driver(menf21bmc_wdt); | |
199 | + | |
200 | +MODULE_DESCRIPTION("MEN 14F021P00 BMC Watchdog driver"); | |
201 | +MODULE_AUTHOR("Andreas Werner <andreas.werner@men.de>"); | |
202 | +MODULE_LICENSE("GPL v2"); | |
203 | +MODULE_ALIAS("platform:menf21bmc_wdt"); |