Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-or-later
0002 /*
0003  * Watchdog driver for Alphascale ASM9260.
0004  *
0005  * Copyright (c) 2014 Oleksij Rempel <linux@rempel-privat.de>
0006  */
0007 
0008 #include <linux/bitops.h>
0009 #include <linux/clk.h>
0010 #include <linux/delay.h>
0011 #include <linux/interrupt.h>
0012 #include <linux/io.h>
0013 #include <linux/module.h>
0014 #include <linux/of.h>
0015 #include <linux/platform_device.h>
0016 #include <linux/reset.h>
0017 #include <linux/watchdog.h>
0018 
0019 #define CLOCK_FREQ  1000000
0020 
0021 /* Watchdog Mode register */
0022 #define HW_WDMOD            0x00
0023 /* Wake interrupt. Set by HW, can't be cleared. */
0024 #define BM_MOD_WDINT            BIT(3)
0025 /* This bit set if timeout reached. Cleared by SW. */
0026 #define BM_MOD_WDTOF            BIT(2)
0027 /* HW Reset on timeout */
0028 #define BM_MOD_WDRESET          BIT(1)
0029 /* WD enable */
0030 #define BM_MOD_WDEN         BIT(0)
0031 
0032 /*
0033  * Watchdog Timer Constant register
0034  * Minimal value is 0xff, the meaning of this value
0035  * depends on used clock: T = WDCLK * (0xff + 1) * 4
0036  */
0037 #define HW_WDTC             0x04
0038 #define BM_WDTC_MAX(freq)       (0x7fffffff / (freq))
0039 
0040 /* Watchdog Feed register */
0041 #define HW_WDFEED           0x08
0042 
0043 /* Watchdog Timer Value register */
0044 #define HW_WDTV             0x0c
0045 
0046 #define ASM9260_WDT_DEFAULT_TIMEOUT 30
0047 
0048 enum asm9260_wdt_mode {
0049     HW_RESET,
0050     SW_RESET,
0051     DEBUG,
0052 };
0053 
0054 struct asm9260_wdt_priv {
0055     struct device       *dev;
0056     struct watchdog_device  wdd;
0057     struct clk      *clk;
0058     struct clk      *clk_ahb;
0059     struct reset_control    *rst;
0060 
0061     void __iomem        *iobase;
0062     int         irq;
0063     unsigned long       wdt_freq;
0064     enum asm9260_wdt_mode   mode;
0065 };
0066 
0067 static int asm9260_wdt_feed(struct watchdog_device *wdd)
0068 {
0069     struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
0070 
0071     iowrite32(0xaa, priv->iobase + HW_WDFEED);
0072     iowrite32(0x55, priv->iobase + HW_WDFEED);
0073 
0074     return 0;
0075 }
0076 
0077 static unsigned int asm9260_wdt_gettimeleft(struct watchdog_device *wdd)
0078 {
0079     struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
0080     u32 counter;
0081 
0082     counter = ioread32(priv->iobase + HW_WDTV);
0083 
0084     return counter / priv->wdt_freq;
0085 }
0086 
0087 static int asm9260_wdt_updatetimeout(struct watchdog_device *wdd)
0088 {
0089     struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
0090     u32 counter;
0091 
0092     counter = wdd->timeout * priv->wdt_freq;
0093 
0094     iowrite32(counter, priv->iobase + HW_WDTC);
0095 
0096     return 0;
0097 }
0098 
0099 static int asm9260_wdt_enable(struct watchdog_device *wdd)
0100 {
0101     struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
0102     u32 mode = 0;
0103 
0104     if (priv->mode == HW_RESET)
0105         mode = BM_MOD_WDRESET;
0106 
0107     iowrite32(BM_MOD_WDEN | mode, priv->iobase + HW_WDMOD);
0108 
0109     asm9260_wdt_updatetimeout(wdd);
0110 
0111     asm9260_wdt_feed(wdd);
0112 
0113     return 0;
0114 }
0115 
0116 static int asm9260_wdt_disable(struct watchdog_device *wdd)
0117 {
0118     struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
0119 
0120     /* The only way to disable WD is to reset it. */
0121     reset_control_assert(priv->rst);
0122     reset_control_deassert(priv->rst);
0123 
0124     return 0;
0125 }
0126 
0127 static int asm9260_wdt_settimeout(struct watchdog_device *wdd, unsigned int to)
0128 {
0129     wdd->timeout = to;
0130     asm9260_wdt_updatetimeout(wdd);
0131 
0132     return 0;
0133 }
0134 
0135 static void asm9260_wdt_sys_reset(struct asm9260_wdt_priv *priv)
0136 {
0137     /* init WD if it was not started */
0138 
0139     iowrite32(BM_MOD_WDEN | BM_MOD_WDRESET, priv->iobase + HW_WDMOD);
0140 
0141     iowrite32(0xff, priv->iobase + HW_WDTC);
0142     /* first pass correct sequence */
0143     asm9260_wdt_feed(&priv->wdd);
0144     /*
0145      * Then write wrong pattern to the feed to trigger reset
0146      * ASAP.
0147      */
0148     iowrite32(0xff, priv->iobase + HW_WDFEED);
0149 
0150     mdelay(1000);
0151 }
0152 
0153 static irqreturn_t asm9260_wdt_irq(int irq, void *devid)
0154 {
0155     struct asm9260_wdt_priv *priv = devid;
0156     u32 stat;
0157 
0158     stat = ioread32(priv->iobase + HW_WDMOD);
0159     if (!(stat & BM_MOD_WDINT))
0160         return IRQ_NONE;
0161 
0162     if (priv->mode == DEBUG) {
0163         dev_info(priv->dev, "Watchdog Timeout. Do nothing.\n");
0164     } else {
0165         dev_info(priv->dev, "Watchdog Timeout. Doing SW Reset.\n");
0166         asm9260_wdt_sys_reset(priv);
0167     }
0168 
0169     return IRQ_HANDLED;
0170 }
0171 
0172 static int asm9260_restart(struct watchdog_device *wdd, unsigned long action,
0173                void *data)
0174 {
0175     struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
0176 
0177     asm9260_wdt_sys_reset(priv);
0178 
0179     return 0;
0180 }
0181 
0182 static const struct watchdog_info asm9260_wdt_ident = {
0183     .options          =     WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING
0184                 | WDIOF_MAGICCLOSE,
0185     .identity         = "Alphascale asm9260 Watchdog",
0186 };
0187 
0188 static const struct watchdog_ops asm9260_wdt_ops = {
0189     .owner      = THIS_MODULE,
0190     .start      = asm9260_wdt_enable,
0191     .stop       = asm9260_wdt_disable,
0192     .get_timeleft   = asm9260_wdt_gettimeleft,
0193     .ping       = asm9260_wdt_feed,
0194     .set_timeout    = asm9260_wdt_settimeout,
0195     .restart    = asm9260_restart,
0196 };
0197 
0198 static void asm9260_clk_disable_unprepare(void *data)
0199 {
0200     clk_disable_unprepare(data);
0201 }
0202 
0203 static int asm9260_wdt_get_dt_clks(struct asm9260_wdt_priv *priv)
0204 {
0205     int err;
0206     unsigned long clk;
0207 
0208     priv->clk = devm_clk_get(priv->dev, "mod");
0209     if (IS_ERR(priv->clk)) {
0210         dev_err(priv->dev, "Failed to get \"mod\" clk\n");
0211         return PTR_ERR(priv->clk);
0212     }
0213 
0214     /* configure AHB clock */
0215     priv->clk_ahb = devm_clk_get(priv->dev, "ahb");
0216     if (IS_ERR(priv->clk_ahb)) {
0217         dev_err(priv->dev, "Failed to get \"ahb\" clk\n");
0218         return PTR_ERR(priv->clk_ahb);
0219     }
0220 
0221     err = clk_prepare_enable(priv->clk_ahb);
0222     if (err) {
0223         dev_err(priv->dev, "Failed to enable ahb_clk!\n");
0224         return err;
0225     }
0226     err = devm_add_action_or_reset(priv->dev,
0227                        asm9260_clk_disable_unprepare,
0228                        priv->clk_ahb);
0229     if (err)
0230         return err;
0231 
0232     err = clk_set_rate(priv->clk, CLOCK_FREQ);
0233     if (err) {
0234         dev_err(priv->dev, "Failed to set rate!\n");
0235         return err;
0236     }
0237 
0238     err = clk_prepare_enable(priv->clk);
0239     if (err) {
0240         dev_err(priv->dev, "Failed to enable clk!\n");
0241         return err;
0242     }
0243     err = devm_add_action_or_reset(priv->dev,
0244                        asm9260_clk_disable_unprepare,
0245                        priv->clk);
0246     if (err)
0247         return err;
0248 
0249     /* wdt has internal divider */
0250     clk = clk_get_rate(priv->clk);
0251     if (!clk) {
0252         dev_err(priv->dev, "Failed, clk is 0!\n");
0253         return -EINVAL;
0254     }
0255 
0256     priv->wdt_freq = clk / 2;
0257 
0258     return 0;
0259 }
0260 
0261 static void asm9260_wdt_get_dt_mode(struct asm9260_wdt_priv *priv)
0262 {
0263     const char *tmp;
0264     int ret;
0265 
0266     /* default mode */
0267     priv->mode = HW_RESET;
0268 
0269     ret = of_property_read_string(priv->dev->of_node,
0270                       "alphascale,mode", &tmp);
0271     if (ret < 0)
0272         return;
0273 
0274     if (!strcmp(tmp, "hw"))
0275         priv->mode = HW_RESET;
0276     else if (!strcmp(tmp, "sw"))
0277         priv->mode = SW_RESET;
0278     else if (!strcmp(tmp, "debug"))
0279         priv->mode = DEBUG;
0280     else
0281         dev_warn(priv->dev, "unknown reset-type: %s. Using default \"hw\" mode.",
0282              tmp);
0283 }
0284 
0285 static int asm9260_wdt_probe(struct platform_device *pdev)
0286 {
0287     struct device *dev = &pdev->dev;
0288     struct asm9260_wdt_priv *priv;
0289     struct watchdog_device *wdd;
0290     int ret;
0291     static const char * const mode_name[] = { "hw", "sw", "debug", };
0292 
0293     priv = devm_kzalloc(dev, sizeof(struct asm9260_wdt_priv), GFP_KERNEL);
0294     if (!priv)
0295         return -ENOMEM;
0296 
0297     priv->dev = dev;
0298 
0299     priv->iobase = devm_platform_ioremap_resource(pdev, 0);
0300     if (IS_ERR(priv->iobase))
0301         return PTR_ERR(priv->iobase);
0302 
0303     priv->rst = devm_reset_control_get_exclusive(dev, "wdt_rst");
0304     if (IS_ERR(priv->rst))
0305         return PTR_ERR(priv->rst);
0306 
0307     ret = asm9260_wdt_get_dt_clks(priv);
0308     if (ret)
0309         return ret;
0310 
0311     wdd = &priv->wdd;
0312     wdd->info = &asm9260_wdt_ident;
0313     wdd->ops = &asm9260_wdt_ops;
0314     wdd->min_timeout = 1;
0315     wdd->max_timeout = BM_WDTC_MAX(priv->wdt_freq);
0316     wdd->parent = dev;
0317 
0318     watchdog_set_drvdata(wdd, priv);
0319 
0320     /*
0321      * If 'timeout-sec' unspecified in devicetree, assume a 30 second
0322      * default, unless the max timeout is less than 30 seconds, then use
0323      * the max instead.
0324      */
0325     wdd->timeout = ASM9260_WDT_DEFAULT_TIMEOUT;
0326     watchdog_init_timeout(wdd, 0, dev);
0327 
0328     asm9260_wdt_get_dt_mode(priv);
0329 
0330     if (priv->mode != HW_RESET)
0331         priv->irq = platform_get_irq(pdev, 0);
0332 
0333     if (priv->irq > 0) {
0334         /*
0335          * Not all supported platforms specify an interrupt for the
0336          * watchdog, so let's make it optional.
0337          */
0338         ret = devm_request_irq(dev, priv->irq, asm9260_wdt_irq, 0,
0339                        pdev->name, priv);
0340         if (ret < 0)
0341             dev_warn(dev, "failed to request IRQ\n");
0342     }
0343 
0344     watchdog_set_restart_priority(wdd, 128);
0345 
0346     watchdog_stop_on_reboot(wdd);
0347     watchdog_stop_on_unregister(wdd);
0348     ret = devm_watchdog_register_device(dev, wdd);
0349     if (ret)
0350         return ret;
0351 
0352     platform_set_drvdata(pdev, priv);
0353 
0354     dev_info(dev, "Watchdog enabled (timeout: %d sec, mode: %s)\n",
0355          wdd->timeout, mode_name[priv->mode]);
0356     return 0;
0357 }
0358 
0359 static const struct of_device_id asm9260_wdt_of_match[] = {
0360     { .compatible = "alphascale,asm9260-wdt"},
0361     {},
0362 };
0363 MODULE_DEVICE_TABLE(of, asm9260_wdt_of_match);
0364 
0365 static struct platform_driver asm9260_wdt_driver = {
0366     .driver = {
0367         .name = "asm9260-wdt",
0368         .of_match_table = asm9260_wdt_of_match,
0369     },
0370     .probe = asm9260_wdt_probe,
0371 };
0372 module_platform_driver(asm9260_wdt_driver);
0373 
0374 MODULE_DESCRIPTION("asm9260 WatchDog Timer Driver");
0375 MODULE_AUTHOR("Oleksij Rempel <linux@rempel-privat.de>");
0376 MODULE_LICENSE("GPL");