229 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * PIC32 watchdog driver4 *5 * Joshua Henderson <joshua.henderson@microchip.com>6 * Copyright (c) 2016, Microchip Technology Inc.7 */8#include <linux/clk.h>9#include <linux/device.h>10#include <linux/err.h>11#include <linux/io.h>12#include <linux/kernel.h>13#include <linux/module.h>14#include <linux/of.h>15#include <linux/platform_device.h>16#include <linux/pm.h>17#include <linux/watchdog.h>18 19#include <asm/mach-pic32/pic32.h>20 21/* Watchdog Timer Registers */22#define WDTCON_REG 0x0023 24/* Watchdog Timer Control Register fields */25#define WDTCON_WIN_EN BIT(0)26#define WDTCON_RMCS_MASK 0x000327#define WDTCON_RMCS_SHIFT 0x000628#define WDTCON_RMPS_MASK 0x001F29#define WDTCON_RMPS_SHIFT 0x000830#define WDTCON_ON BIT(15)31#define WDTCON_CLR_KEY 0x574332 33/* Reset Control Register fields for watchdog */34#define RESETCON_TIMEOUT_IDLE BIT(2)35#define RESETCON_TIMEOUT_SLEEP BIT(3)36#define RESETCON_WDT_TIMEOUT BIT(4)37 38struct pic32_wdt {39 void __iomem *regs;40 void __iomem *rst_base;41 struct clk *clk;42};43 44static inline bool pic32_wdt_is_win_enabled(struct pic32_wdt *wdt)45{46 return !!(readl(wdt->regs + WDTCON_REG) & WDTCON_WIN_EN);47}48 49static inline u32 pic32_wdt_get_post_scaler(struct pic32_wdt *wdt)50{51 u32 v = readl(wdt->regs + WDTCON_REG);52 53 return (v >> WDTCON_RMPS_SHIFT) & WDTCON_RMPS_MASK;54}55 56static inline u32 pic32_wdt_get_clk_id(struct pic32_wdt *wdt)57{58 u32 v = readl(wdt->regs + WDTCON_REG);59 60 return (v >> WDTCON_RMCS_SHIFT) & WDTCON_RMCS_MASK;61}62 63static int pic32_wdt_bootstatus(struct pic32_wdt *wdt)64{65 u32 v = readl(wdt->rst_base);66 67 writel(RESETCON_WDT_TIMEOUT, PIC32_CLR(wdt->rst_base));68 69 return v & RESETCON_WDT_TIMEOUT;70}71 72static u32 pic32_wdt_get_timeout_secs(struct pic32_wdt *wdt, struct device *dev)73{74 unsigned long rate;75 u32 period, ps, terminal;76 77 rate = clk_get_rate(wdt->clk);78 79 dev_dbg(dev, "wdt: clk_id %d, clk_rate %lu (prescale)\n",80 pic32_wdt_get_clk_id(wdt), rate);81 82 /* default, prescaler of 32 (i.e. div-by-32) is implicit. */83 rate >>= 5;84 if (!rate)85 return 0;86 87 /* calculate terminal count from postscaler. */88 ps = pic32_wdt_get_post_scaler(wdt);89 terminal = BIT(ps);90 91 /* find time taken (in secs) to reach terminal count */92 period = terminal / rate;93 dev_dbg(dev,94 "wdt: clk_rate %lu (postscale) / terminal %d, timeout %dsec\n",95 rate, terminal, period);96 97 return period;98}99 100static void pic32_wdt_keepalive(struct pic32_wdt *wdt)101{102 /* write key through single half-word */103 writew(WDTCON_CLR_KEY, wdt->regs + WDTCON_REG + 2);104}105 106static int pic32_wdt_start(struct watchdog_device *wdd)107{108 struct pic32_wdt *wdt = watchdog_get_drvdata(wdd);109 110 writel(WDTCON_ON, PIC32_SET(wdt->regs + WDTCON_REG));111 pic32_wdt_keepalive(wdt);112 113 return 0;114}115 116static int pic32_wdt_stop(struct watchdog_device *wdd)117{118 struct pic32_wdt *wdt = watchdog_get_drvdata(wdd);119 120 writel(WDTCON_ON, PIC32_CLR(wdt->regs + WDTCON_REG));121 122 /*123 * Cannot touch registers in the CPU cycle following clearing the124 * ON bit.125 */126 nop();127 128 return 0;129}130 131static int pic32_wdt_ping(struct watchdog_device *wdd)132{133 struct pic32_wdt *wdt = watchdog_get_drvdata(wdd);134 135 pic32_wdt_keepalive(wdt);136 137 return 0;138}139 140static const struct watchdog_ops pic32_wdt_fops = {141 .owner = THIS_MODULE,142 .start = pic32_wdt_start,143 .stop = pic32_wdt_stop,144 .ping = pic32_wdt_ping,145};146 147static const struct watchdog_info pic32_wdt_ident = {148 .options = WDIOF_KEEPALIVEPING |149 WDIOF_MAGICCLOSE | WDIOF_CARDRESET,150 .identity = "PIC32 Watchdog",151};152 153static struct watchdog_device pic32_wdd = {154 .info = &pic32_wdt_ident,155 .ops = &pic32_wdt_fops,156};157 158static const struct of_device_id pic32_wdt_dt_ids[] = {159 { .compatible = "microchip,pic32mzda-wdt", },160 { /* sentinel */ }161};162MODULE_DEVICE_TABLE(of, pic32_wdt_dt_ids);163 164static int pic32_wdt_drv_probe(struct platform_device *pdev)165{166 struct device *dev = &pdev->dev;167 int ret;168 struct watchdog_device *wdd = &pic32_wdd;169 struct pic32_wdt *wdt;170 171 wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);172 if (!wdt)173 return -ENOMEM;174 175 wdt->regs = devm_platform_ioremap_resource(pdev, 0);176 if (IS_ERR(wdt->regs))177 return PTR_ERR(wdt->regs);178 179 wdt->rst_base = devm_ioremap(dev, PIC32_BASE_RESET, 0x10);180 if (!wdt->rst_base)181 return -ENOMEM;182 183 wdt->clk = devm_clk_get_enabled(dev, NULL);184 if (IS_ERR(wdt->clk)) {185 dev_err(dev, "clk not found\n");186 return PTR_ERR(wdt->clk);187 }188 189 if (pic32_wdt_is_win_enabled(wdt)) {190 dev_err(dev, "windowed-clear mode is not supported.\n");191 return -ENODEV;192 }193 194 wdd->timeout = pic32_wdt_get_timeout_secs(wdt, dev);195 if (!wdd->timeout) {196 dev_err(dev, "failed to read watchdog register timeout\n");197 return -EINVAL;198 }199 200 dev_info(dev, "timeout %d\n", wdd->timeout);201 202 wdd->bootstatus = pic32_wdt_bootstatus(wdt) ? WDIOF_CARDRESET : 0;203 204 watchdog_set_nowayout(wdd, WATCHDOG_NOWAYOUT);205 watchdog_set_drvdata(wdd, wdt);206 207 ret = devm_watchdog_register_device(dev, wdd);208 if (ret)209 return ret;210 211 platform_set_drvdata(pdev, wdd);212 213 return 0;214}215 216static struct platform_driver pic32_wdt_driver = {217 .probe = pic32_wdt_drv_probe,218 .driver = {219 .name = "pic32-wdt",220 .of_match_table = of_match_ptr(pic32_wdt_dt_ids),221 }222};223 224module_platform_driver(pic32_wdt_driver);225 226MODULE_AUTHOR("Joshua Henderson <joshua.henderson@microchip.com>");227MODULE_DESCRIPTION("Microchip PIC32 Watchdog Timer");228MODULE_LICENSE("GPL");229