lenovo_se30_wdt.c 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. // SPDX-License-Identifier: GPL-2.0-or-later
  2. /*
  3. * WDT driver for Lenovo SE30 device
  4. */
  5. #define dev_fmt(fmt) KBUILD_MODNAME ": " fmt
  6. #include <linux/io.h>
  7. #include <linux/dmi.h>
  8. #include <linux/delay.h>
  9. #include <linux/iommu.h>
  10. #include <linux/kernel.h>
  11. #include <linux/module.h>
  12. #include <linux/moduleparam.h>
  13. #include <linux/platform_device.h>
  14. #include <linux/watchdog.h>
  15. #define IOREGION_OFFSET 4 /* Use EC port 1 */
  16. #define IOREGION_LENGTH 4
  17. #define WATCHDOG_TIMEOUT 60
  18. #define MIN_TIMEOUT 1
  19. #define MAX_TIMEOUT 255
  20. #define MAX_WAIT 10
  21. static int timeout; /* in seconds */
  22. module_param(timeout, int, 0);
  23. MODULE_PARM_DESC(timeout,
  24. "Watchdog timeout in seconds. 1 <= timeout <= 255, default="
  25. __MODULE_STRING(WATCHDOG_TIMEOUT) ".");
  26. static bool nowayout = WATCHDOG_NOWAYOUT;
  27. module_param(nowayout, bool, 0);
  28. MODULE_PARM_DESC(nowayout,
  29. "Watchdog cannot be stopped once started (default="
  30. __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
  31. #define LNV_SE30_NAME "lenovo-se30-wdt"
  32. #define LNV_SE30_ID 0x0110
  33. #define CHIPID_MASK 0xFFF0
  34. #define CHIPID_REG 0x20
  35. #define SIO_REG 0x2e
  36. #define LDN_REG 0x07
  37. #define UNLOCK_KEY 0x87
  38. #define LOCK_KEY 0xAA
  39. #define LD_NUM_SHM 0x0F
  40. #define LD_BASE_ADDR 0xF8
  41. #define WDT_MODULE 0x10
  42. #define WDT_CFG_INDEX 0x15 /* WD configuration register */
  43. #define WDT_CNT_INDEX 0x16 /* WD timer count register */
  44. #define WDT_CFG_RESET 0x2
  45. /* Host Interface WIN2 offset definition */
  46. #define SHM_WIN_SIZE 0xFF
  47. #define SHM_WIN_MOD_OFFSET 0x01
  48. #define SHM_WIN_CMD_OFFSET 0x02
  49. #define SHM_WIN_SEL_OFFSET 0x03
  50. #define SHM_WIN_CTL_OFFSET 0x04
  51. #define VAL_SHM_WIN_CTRL_WR 0x40
  52. #define VAL_SHM_WIN_CTRL_RD 0x80
  53. #define SHM_WIN_ID_OFFSET 0x08
  54. #define SHM_WIN_DAT_OFFSET 0x10
  55. struct nct6692_reg {
  56. unsigned char mod;
  57. unsigned char cmd;
  58. unsigned char sel;
  59. unsigned int idx;
  60. };
  61. /* Watchdog is based on NCT6692 device */
  62. struct lenovo_se30_wdt {
  63. unsigned char __iomem *shm_base_addr;
  64. struct nct6692_reg wdt_cfg;
  65. struct nct6692_reg wdt_cnt;
  66. struct watchdog_device wdt;
  67. };
  68. static inline void superio_outb(int ioreg, int reg, int val)
  69. {
  70. outb(reg, ioreg);
  71. outb(val, ioreg + 1);
  72. }
  73. static inline int superio_inb(int ioreg, int reg)
  74. {
  75. outb(reg, ioreg);
  76. return inb(ioreg + 1);
  77. }
  78. static inline int superio_enter(int key, int addr, const char *name)
  79. {
  80. if (!request_muxed_region(addr, 2, name)) {
  81. pr_err("I/O address 0x%04x already in use\n", addr);
  82. return -EBUSY;
  83. }
  84. outb(key, addr); /* Enter extended function mode */
  85. outb(key, addr); /* Again according to manual */
  86. return 0;
  87. }
  88. static inline void superio_exit(int key, int addr)
  89. {
  90. outb(key, addr); /* Leave extended function mode */
  91. release_region(addr, 2);
  92. }
  93. static int shm_get_ready(unsigned char __iomem *shm_base_addr,
  94. const struct nct6692_reg *reg)
  95. {
  96. unsigned char pre_id, new_id;
  97. int loop = 0;
  98. iowrite8(reg->mod, shm_base_addr + SHM_WIN_MOD_OFFSET);
  99. iowrite8(reg->cmd, shm_base_addr + SHM_WIN_CMD_OFFSET);
  100. iowrite8(reg->sel, shm_base_addr + SHM_WIN_SEL_OFFSET);
  101. pre_id = ioread8(shm_base_addr + SHM_WIN_ID_OFFSET);
  102. iowrite8(VAL_SHM_WIN_CTRL_RD, shm_base_addr + SHM_WIN_CTL_OFFSET);
  103. /* Loop checking when interface is ready */
  104. while (loop < MAX_WAIT) {
  105. new_id = ioread8(shm_base_addr + SHM_WIN_ID_OFFSET);
  106. if (new_id != pre_id)
  107. return 0;
  108. loop++;
  109. usleep_range(10, 125);
  110. }
  111. return -ETIMEDOUT;
  112. }
  113. static int read_shm_win(unsigned char __iomem *shm_base_addr,
  114. const struct nct6692_reg *reg,
  115. unsigned char idx_offset,
  116. unsigned char *data)
  117. {
  118. int err = shm_get_ready(shm_base_addr, reg);
  119. if (err)
  120. return err;
  121. *data = ioread8(shm_base_addr + SHM_WIN_DAT_OFFSET + reg->idx + idx_offset);
  122. return 0;
  123. }
  124. static int write_shm_win(unsigned char __iomem *shm_base_addr,
  125. const struct nct6692_reg *reg,
  126. unsigned char idx_offset,
  127. unsigned char val)
  128. {
  129. int err = shm_get_ready(shm_base_addr, reg);
  130. if (err)
  131. return err;
  132. iowrite8(val, shm_base_addr + SHM_WIN_DAT_OFFSET + reg->idx + idx_offset);
  133. iowrite8(VAL_SHM_WIN_CTRL_WR, shm_base_addr + SHM_WIN_CTL_OFFSET);
  134. err = shm_get_ready(shm_base_addr, reg);
  135. return err;
  136. }
  137. static int lenovo_se30_wdt_enable(struct lenovo_se30_wdt *data, unsigned int timeout)
  138. {
  139. if (timeout) {
  140. int err = write_shm_win(data->shm_base_addr, &data->wdt_cfg, 0, WDT_CFG_RESET);
  141. if (err)
  142. return err;
  143. }
  144. return write_shm_win(data->shm_base_addr, &data->wdt_cnt, 0, timeout);
  145. }
  146. static int lenovo_se30_wdt_start(struct watchdog_device *wdog)
  147. {
  148. struct lenovo_se30_wdt *data = watchdog_get_drvdata(wdog);
  149. return lenovo_se30_wdt_enable(data, wdog->timeout);
  150. }
  151. static int lenovo_se30_wdt_stop(struct watchdog_device *wdog)
  152. {
  153. struct lenovo_se30_wdt *data = watchdog_get_drvdata(wdog);
  154. return lenovo_se30_wdt_enable(data, 0);
  155. }
  156. static unsigned int lenovo_se30_wdt_get_timeleft(struct watchdog_device *wdog)
  157. {
  158. struct lenovo_se30_wdt *data = watchdog_get_drvdata(wdog);
  159. unsigned char timeleft;
  160. int err;
  161. err = read_shm_win(data->shm_base_addr, &data->wdt_cnt, 0, &timeleft);
  162. if (err)
  163. return 0;
  164. return timeleft;
  165. }
  166. static int lenovo_se30_wdt_ping(struct watchdog_device *wdt)
  167. {
  168. struct lenovo_se30_wdt *data = watchdog_get_drvdata(wdt);
  169. int err = 0;
  170. /*
  171. * Device does not support refreshing WDT_TIMER_REG register when
  172. * the watchdog is active. Need to disable, feed and enable again
  173. */
  174. err = lenovo_se30_wdt_enable(data, 0);
  175. if (err)
  176. return err;
  177. err = write_shm_win(data->shm_base_addr, &data->wdt_cnt, 0, wdt->timeout);
  178. if (!err)
  179. err = lenovo_se30_wdt_enable(data, wdt->timeout);
  180. return err;
  181. }
  182. static const struct watchdog_info lenovo_se30_wdt_info = {
  183. .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
  184. WDIOF_MAGICCLOSE,
  185. .identity = "Lenovo SE30 watchdog",
  186. };
  187. static const struct watchdog_ops lenovo_se30_wdt_ops = {
  188. .owner = THIS_MODULE,
  189. .start = lenovo_se30_wdt_start,
  190. .stop = lenovo_se30_wdt_stop,
  191. .ping = lenovo_se30_wdt_ping,
  192. .get_timeleft = lenovo_se30_wdt_get_timeleft,
  193. };
  194. static int lenovo_se30_wdt_probe(struct platform_device *pdev)
  195. {
  196. struct device *dev = &pdev->dev;
  197. struct lenovo_se30_wdt *priv;
  198. unsigned long base_phys;
  199. unsigned short val;
  200. int err;
  201. err = superio_enter(UNLOCK_KEY, SIO_REG, LNV_SE30_NAME);
  202. if (err)
  203. return err;
  204. val = superio_inb(SIO_REG, CHIPID_REG) << 8;
  205. val |= superio_inb(SIO_REG, CHIPID_REG + 1);
  206. if ((val & CHIPID_MASK) != LNV_SE30_ID) {
  207. superio_exit(LOCK_KEY, SIO_REG);
  208. return -ENODEV;
  209. }
  210. superio_outb(SIO_REG, LDN_REG, LD_NUM_SHM);
  211. base_phys = (superio_inb(SIO_REG, LD_BASE_ADDR) |
  212. (superio_inb(SIO_REG, LD_BASE_ADDR + 1) << 8) |
  213. (superio_inb(SIO_REG, LD_BASE_ADDR + 2) << 16) |
  214. (superio_inb(SIO_REG, LD_BASE_ADDR + 3) << 24)) &
  215. 0xFFFFFFFF;
  216. superio_exit(LOCK_KEY, SIO_REG);
  217. if (base_phys == 0xFFFFFFFF || base_phys == 0)
  218. return -ENODEV;
  219. priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
  220. if (!priv)
  221. return -ENOMEM;
  222. if (!devm_request_mem_region(dev, base_phys, SHM_WIN_SIZE, LNV_SE30_NAME))
  223. return -EBUSY;
  224. priv->shm_base_addr = devm_ioremap(dev, base_phys, SHM_WIN_SIZE);
  225. if (!priv->shm_base_addr)
  226. return -ENOMEM;
  227. priv->wdt_cfg.mod = WDT_MODULE;
  228. priv->wdt_cfg.idx = WDT_CFG_INDEX;
  229. priv->wdt_cnt.mod = WDT_MODULE;
  230. priv->wdt_cnt.idx = WDT_CNT_INDEX;
  231. priv->wdt.ops = &lenovo_se30_wdt_ops;
  232. priv->wdt.info = &lenovo_se30_wdt_info;
  233. priv->wdt.timeout = WATCHDOG_TIMEOUT; /* Set default timeout */
  234. priv->wdt.min_timeout = MIN_TIMEOUT;
  235. priv->wdt.max_timeout = MAX_TIMEOUT;
  236. priv->wdt.parent = dev;
  237. watchdog_init_timeout(&priv->wdt, timeout, dev);
  238. watchdog_set_drvdata(&priv->wdt, priv);
  239. watchdog_set_nowayout(&priv->wdt, nowayout);
  240. watchdog_stop_on_reboot(&priv->wdt);
  241. watchdog_stop_on_unregister(&priv->wdt);
  242. return devm_watchdog_register_device(dev, &priv->wdt);
  243. }
  244. static struct platform_device *pdev;
  245. static struct platform_driver lenovo_se30_wdt_driver = {
  246. .driver = {
  247. .name = LNV_SE30_NAME,
  248. },
  249. .probe = lenovo_se30_wdt_probe,
  250. };
  251. static int lenovo_se30_create_platform_device(const struct dmi_system_id *id)
  252. {
  253. int err;
  254. pdev = platform_device_alloc(LNV_SE30_NAME, -1);
  255. if (!pdev)
  256. return -ENOMEM;
  257. err = platform_device_add(pdev);
  258. if (err)
  259. platform_device_put(pdev);
  260. return err;
  261. }
  262. static const struct dmi_system_id lenovo_se30_wdt_dmi_table[] __initconst = {
  263. {
  264. .ident = "LENOVO-SE30",
  265. .matches = {
  266. DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
  267. DMI_MATCH(DMI_PRODUCT_NAME, "11NA"),
  268. },
  269. .callback = lenovo_se30_create_platform_device,
  270. },
  271. {
  272. .ident = "LENOVO-SE30",
  273. .matches = {
  274. DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
  275. DMI_MATCH(DMI_PRODUCT_NAME, "11NB"),
  276. },
  277. .callback = lenovo_se30_create_platform_device,
  278. },
  279. {
  280. .ident = "LENOVO-SE30",
  281. .matches = {
  282. DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
  283. DMI_MATCH(DMI_PRODUCT_NAME, "11NC"),
  284. },
  285. .callback = lenovo_se30_create_platform_device,
  286. },
  287. {
  288. .ident = "LENOVO-SE30",
  289. .matches = {
  290. DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
  291. DMI_MATCH(DMI_PRODUCT_NAME, "11NH"),
  292. },
  293. .callback = lenovo_se30_create_platform_device,
  294. },
  295. {
  296. .ident = "LENOVO-SE30",
  297. .matches = {
  298. DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
  299. DMI_MATCH(DMI_PRODUCT_NAME, "11NJ"),
  300. },
  301. .callback = lenovo_se30_create_platform_device,
  302. },
  303. {
  304. .ident = "LENOVO-SE30",
  305. .matches = {
  306. DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
  307. DMI_MATCH(DMI_PRODUCT_NAME, "11NK"),
  308. },
  309. .callback = lenovo_se30_create_platform_device,
  310. },
  311. {}
  312. };
  313. MODULE_DEVICE_TABLE(dmi, lenovo_se30_wdt_dmi_table);
  314. static int __init lenovo_se30_wdt_init(void)
  315. {
  316. if (!dmi_check_system(lenovo_se30_wdt_dmi_table))
  317. return -ENODEV;
  318. return platform_driver_register(&lenovo_se30_wdt_driver);
  319. }
  320. static void __exit lenovo_se30_wdt_exit(void)
  321. {
  322. if (pdev)
  323. platform_device_unregister(pdev);
  324. platform_driver_unregister(&lenovo_se30_wdt_driver);
  325. }
  326. module_init(lenovo_se30_wdt_init);
  327. module_exit(lenovo_se30_wdt_exit);
  328. MODULE_AUTHOR("Mark Pearson <mpearson-lenovo@squebb.ca>");
  329. MODULE_AUTHOR("David Ober <dober@lenovo.com>");
  330. MODULE_DESCRIPTION("Lenovo SE30 watchdog driver");
  331. MODULE_LICENSE("GPL");