diff options
Diffstat (limited to 'drivers/watchdog/s3c2410_wdt.c')
-rw-r--r-- | drivers/watchdog/s3c2410_wdt.c | 338 |
1 files changed, 253 insertions, 85 deletions
diff --git a/drivers/watchdog/s3c2410_wdt.c b/drivers/watchdog/s3c2410_wdt.c index 2395f353e52d..6db22f2e3a4f 100644 --- a/drivers/watchdog/s3c2410_wdt.c +++ b/drivers/watchdog/s3c2410_wdt.c @@ -56,13 +56,58 @@ #define EXYNOS5_RST_STAT_REG_OFFSET 0x0404 #define EXYNOS5_WDT_DISABLE_REG_OFFSET 0x0408 #define EXYNOS5_WDT_MASK_RESET_REG_OFFSET 0x040c -#define QUIRK_HAS_PMU_CONFIG (1 << 0) -#define QUIRK_HAS_RST_STAT (1 << 1) -#define QUIRK_HAS_WTCLRINT_REG (1 << 2) +#define EXYNOS850_CLUSTER0_NONCPU_OUT 0x1220 +#define EXYNOS850_CLUSTER0_NONCPU_INT_EN 0x1244 +#define EXYNOS850_CLUSTER1_NONCPU_OUT 0x1620 +#define EXYNOS850_CLUSTER1_NONCPU_INT_EN 0x1644 + +#define EXYNOS850_CLUSTER0_WDTRESET_BIT 24 +#define EXYNOS850_CLUSTER1_WDTRESET_BIT 23 + +/** + * DOC: Quirk flags for different Samsung watchdog IP-cores + * + * This driver supports multiple Samsung SoCs, each of which might have + * different set of registers and features supported. As watchdog block + * sometimes requires modifying PMU registers for proper functioning, register + * differences in both watchdog and PMU IP-cores should be accounted for. Quirk + * flags described below serve the purpose of telling the driver about mentioned + * SoC traits, and can be specified in driver data for each particular supported + * device. + * + * %QUIRK_HAS_WTCLRINT_REG: Watchdog block has WTCLRINT register. It's used to + * clear the interrupt once the interrupt service routine is complete. It's + * write-only, writing any values to this register clears the interrupt, but + * reading is not permitted. + * + * %QUIRK_HAS_PMU_MASK_RESET: PMU block has the register for disabling/enabling + * WDT reset request. On old SoCs it's usually called MASK_WDT_RESET_REQUEST, + * new SoCs have CLUSTERx_NONCPU_INT_EN register, which 'mask_bit' value is + * inverted compared to the former one. + * + * %QUIRK_HAS_PMU_RST_STAT: PMU block has RST_STAT (reset status) register, + * which contains bits indicating the reason for most recent CPU reset. If + * present, driver will use this register to check if previous reboot was due to + * watchdog timer reset. + * + * %QUIRK_HAS_PMU_AUTO_DISABLE: PMU block has AUTOMATIC_WDT_RESET_DISABLE + * register. If 'mask_bit' bit is set, PMU will disable WDT reset when + * corresponding processor is in reset state. + * + * %QUIRK_HAS_PMU_CNT_EN: PMU block has some register (e.g. CLUSTERx_NONCPU_OUT) + * with "watchdog counter enable" bit. That bit should be set to make watchdog + * counter running. + */ +#define QUIRK_HAS_WTCLRINT_REG (1 << 0) +#define QUIRK_HAS_PMU_MASK_RESET (1 << 1) +#define QUIRK_HAS_PMU_RST_STAT (1 << 2) +#define QUIRK_HAS_PMU_AUTO_DISABLE (1 << 3) +#define QUIRK_HAS_PMU_CNT_EN (1 << 4) /* These quirks require that we have a PMU register map */ -#define QUIRKS_HAVE_PMUREG (QUIRK_HAS_PMU_CONFIG | \ - QUIRK_HAS_RST_STAT) +#define QUIRKS_HAVE_PMUREG \ + (QUIRK_HAS_PMU_MASK_RESET | QUIRK_HAS_PMU_RST_STAT | \ + QUIRK_HAS_PMU_AUTO_DISABLE | QUIRK_HAS_PMU_CNT_EN) static bool nowayout = WATCHDOG_NOWAYOUT; static int tmr_margin; @@ -90,26 +135,33 @@ MODULE_PARM_DESC(soft_noboot, "Watchdog action, set to 1 to ignore reboots, 0 to * timer reset functionality. * @mask_reset_reg: Offset in pmureg for the register that masks the watchdog * timer reset functionality. + * @mask_reset_inv: If set, mask_reset_reg value will have inverted meaning. * @mask_bit: Bit number for the watchdog timer in the disable register and the * mask reset register. * @rst_stat_reg: Offset in pmureg for the register that has the reset status. * @rst_stat_bit: Bit number in the rst_stat register indicating a watchdog * reset. + * @cnt_en_reg: Offset in pmureg for the register that enables WDT counter. + * @cnt_en_bit: Bit number for "watchdog counter enable" in cnt_en register. * @quirks: A bitfield of quirks. */ struct s3c2410_wdt_variant { int disable_reg; int mask_reset_reg; + bool mask_reset_inv; int mask_bit; int rst_stat_reg; int rst_stat_bit; + int cnt_en_reg; + int cnt_en_bit; u32 quirks; }; struct s3c2410_wdt { struct device *dev; - struct clk *clock; + struct clk *bus_clk; /* for register interface (PCLK) */ + struct clk *src_clk; /* for WDT counter */ void __iomem *reg_base; unsigned int count; spinlock_t lock; @@ -136,8 +188,8 @@ static const struct s3c2410_wdt_variant drv_data_exynos5250 = { .mask_bit = 20, .rst_stat_reg = EXYNOS5_RST_STAT_REG_OFFSET, .rst_stat_bit = 20, - .quirks = QUIRK_HAS_PMU_CONFIG | QUIRK_HAS_RST_STAT \ - | QUIRK_HAS_WTCLRINT_REG, + .quirks = QUIRK_HAS_WTCLRINT_REG | QUIRK_HAS_PMU_MASK_RESET | \ + QUIRK_HAS_PMU_RST_STAT | QUIRK_HAS_PMU_AUTO_DISABLE, }; static const struct s3c2410_wdt_variant drv_data_exynos5420 = { @@ -146,8 +198,8 @@ static const struct s3c2410_wdt_variant drv_data_exynos5420 = { .mask_bit = 0, .rst_stat_reg = EXYNOS5_RST_STAT_REG_OFFSET, .rst_stat_bit = 9, - .quirks = QUIRK_HAS_PMU_CONFIG | QUIRK_HAS_RST_STAT \ - | QUIRK_HAS_WTCLRINT_REG, + .quirks = QUIRK_HAS_WTCLRINT_REG | QUIRK_HAS_PMU_MASK_RESET | \ + QUIRK_HAS_PMU_RST_STAT | QUIRK_HAS_PMU_AUTO_DISABLE, }; static const struct s3c2410_wdt_variant drv_data_exynos7 = { @@ -156,8 +208,32 @@ static const struct s3c2410_wdt_variant drv_data_exynos7 = { .mask_bit = 23, .rst_stat_reg = EXYNOS5_RST_STAT_REG_OFFSET, .rst_stat_bit = 23, /* A57 WDTRESET */ - .quirks = QUIRK_HAS_PMU_CONFIG | QUIRK_HAS_RST_STAT \ - | QUIRK_HAS_WTCLRINT_REG, + .quirks = QUIRK_HAS_WTCLRINT_REG | QUIRK_HAS_PMU_MASK_RESET | \ + QUIRK_HAS_PMU_RST_STAT | QUIRK_HAS_PMU_AUTO_DISABLE, +}; + +static const struct s3c2410_wdt_variant drv_data_exynos850_cl0 = { + .mask_reset_reg = EXYNOS850_CLUSTER0_NONCPU_INT_EN, + .mask_bit = 2, + .mask_reset_inv = true, + .rst_stat_reg = EXYNOS5_RST_STAT_REG_OFFSET, + .rst_stat_bit = EXYNOS850_CLUSTER0_WDTRESET_BIT, + .cnt_en_reg = EXYNOS850_CLUSTER0_NONCPU_OUT, + .cnt_en_bit = 7, + .quirks = QUIRK_HAS_WTCLRINT_REG | QUIRK_HAS_PMU_MASK_RESET | \ + QUIRK_HAS_PMU_RST_STAT | QUIRK_HAS_PMU_CNT_EN, +}; + +static const struct s3c2410_wdt_variant drv_data_exynos850_cl1 = { + .mask_reset_reg = EXYNOS850_CLUSTER1_NONCPU_INT_EN, + .mask_bit = 2, + .mask_reset_inv = true, + .rst_stat_reg = EXYNOS5_RST_STAT_REG_OFFSET, + .rst_stat_bit = EXYNOS850_CLUSTER1_WDTRESET_BIT, + .cnt_en_reg = EXYNOS850_CLUSTER1_NONCPU_OUT, + .cnt_en_bit = 7, + .quirks = QUIRK_HAS_WTCLRINT_REG | QUIRK_HAS_PMU_MASK_RESET | \ + QUIRK_HAS_PMU_RST_STAT | QUIRK_HAS_PMU_CNT_EN, }; static const struct of_device_id s3c2410_wdt_match[] = { @@ -171,6 +247,8 @@ static const struct of_device_id s3c2410_wdt_match[] = { .data = &drv_data_exynos5420 }, { .compatible = "samsung,exynos7-wdt", .data = &drv_data_exynos7 }, + { .compatible = "samsung,exynos850-wdt", + .data = &drv_data_exynos850_cl0 }, {}, }; MODULE_DEVICE_TABLE(of, s3c2410_wdt_match); @@ -187,9 +265,14 @@ MODULE_DEVICE_TABLE(platform, s3c2410_wdt_ids); /* functions */ -static inline unsigned int s3c2410wdt_max_timeout(struct clk *clock) +static inline unsigned long s3c2410wdt_get_freq(struct s3c2410_wdt *wdt) { - unsigned long freq = clk_get_rate(clock); + return clk_get_rate(wdt->src_clk ? wdt->src_clk : wdt->bus_clk); +} + +static inline unsigned int s3c2410wdt_max_timeout(struct s3c2410_wdt *wdt) +{ + const unsigned long freq = s3c2410wdt_get_freq(wdt); return S3C2410_WTCNT_MAXCNT / (freq / (S3C2410_WTCON_PRESCALE_MAX + 1) / S3C2410_WTCON_MAXDIV); @@ -200,35 +283,74 @@ static inline struct s3c2410_wdt *freq_to_wdt(struct notifier_block *nb) return container_of(nb, struct s3c2410_wdt, freq_transition); } -static int s3c2410wdt_mask_and_disable_reset(struct s3c2410_wdt *wdt, bool mask) +static int s3c2410wdt_disable_wdt_reset(struct s3c2410_wdt *wdt, bool mask) { + const u32 mask_val = BIT(wdt->drv_data->mask_bit); + const u32 val = mask ? mask_val : 0; int ret; - u32 mask_val = 1 << wdt->drv_data->mask_bit; - u32 val = 0; - /* No need to do anything if no PMU CONFIG needed */ - if (!(wdt->drv_data->quirks & QUIRK_HAS_PMU_CONFIG)) - return 0; + ret = regmap_update_bits(wdt->pmureg, wdt->drv_data->disable_reg, + mask_val, val); + if (ret < 0) + dev_err(wdt->dev, "failed to update reg(%d)\n", ret); - if (mask) - val = mask_val; + return ret; +} + +static int s3c2410wdt_mask_wdt_reset(struct s3c2410_wdt *wdt, bool mask) +{ + const u32 mask_val = BIT(wdt->drv_data->mask_bit); + const bool val_inv = wdt->drv_data->mask_reset_inv; + const u32 val = (mask ^ val_inv) ? mask_val : 0; + int ret; - ret = regmap_update_bits(wdt->pmureg, - wdt->drv_data->disable_reg, - mask_val, val); + ret = regmap_update_bits(wdt->pmureg, wdt->drv_data->mask_reset_reg, + mask_val, val); if (ret < 0) - goto error; + dev_err(wdt->dev, "failed to update reg(%d)\n", ret); + + return ret; +} - ret = regmap_update_bits(wdt->pmureg, - wdt->drv_data->mask_reset_reg, - mask_val, val); - error: +static int s3c2410wdt_enable_counter(struct s3c2410_wdt *wdt, bool en) +{ + const u32 mask_val = BIT(wdt->drv_data->cnt_en_bit); + const u32 val = en ? mask_val : 0; + int ret; + + ret = regmap_update_bits(wdt->pmureg, wdt->drv_data->cnt_en_reg, + mask_val, val); if (ret < 0) dev_err(wdt->dev, "failed to update reg(%d)\n", ret); return ret; } +static int s3c2410wdt_enable(struct s3c2410_wdt *wdt, bool en) +{ + int ret; + + if (wdt->drv_data->quirks & QUIRK_HAS_PMU_AUTO_DISABLE) { + ret = s3c2410wdt_disable_wdt_reset(wdt, !en); + if (ret < 0) + return ret; + } + + if (wdt->drv_data->quirks & QUIRK_HAS_PMU_MASK_RESET) { + ret = s3c2410wdt_mask_wdt_reset(wdt, !en); + if (ret < 0) + return ret; + } + + if (wdt->drv_data->quirks & QUIRK_HAS_PMU_CNT_EN) { + ret = s3c2410wdt_enable_counter(wdt, en); + if (ret < 0) + return ret; + } + + return 0; +} + static int s3c2410wdt_keepalive(struct watchdog_device *wdd) { struct s3c2410_wdt *wdt = watchdog_get_drvdata(wdd); @@ -300,7 +422,7 @@ static int s3c2410wdt_set_heartbeat(struct watchdog_device *wdd, unsigned int timeout) { struct s3c2410_wdt *wdt = watchdog_get_drvdata(wdd); - unsigned long freq = clk_get_rate(wdt->clock); + unsigned long freq = s3c2410wdt_get_freq(wdt); unsigned int count; unsigned int divisor = 1; unsigned long wtcon; @@ -482,7 +604,7 @@ static inline unsigned int s3c2410wdt_get_bootstatus(struct s3c2410_wdt *wdt) unsigned int rst_stat; int ret; - if (!(wdt->drv_data->quirks & QUIRK_HAS_RST_STAT)) + if (!(wdt->drv_data->quirks & QUIRK_HAS_PMU_RST_STAT)) return 0; ret = regmap_read(wdt->pmureg, wdt->drv_data->rst_stat_reg, &rst_stat); @@ -498,14 +620,40 @@ static inline const struct s3c2410_wdt_variant * s3c2410_get_wdt_drv_data(struct platform_device *pdev) { const struct s3c2410_wdt_variant *variant; + struct device *dev = &pdev->dev; - variant = of_device_get_match_data(&pdev->dev); + variant = of_device_get_match_data(dev); if (!variant) { /* Device matched by platform_device_id */ variant = (struct s3c2410_wdt_variant *) platform_get_device_id(pdev)->driver_data; } +#ifdef CONFIG_OF + /* Choose Exynos850 driver data w.r.t. cluster index */ + if (variant == &drv_data_exynos850_cl0) { + u32 index; + int err; + + err = of_property_read_u32(dev->of_node, + "samsung,cluster-index", &index); + if (err) { + dev_err(dev, "failed to get cluster index\n"); + return NULL; + } + + switch (index) { + case 0: + return &drv_data_exynos850_cl0; + case 1: + return &drv_data_exynos850_cl1; + default: + dev_err(dev, "wrong cluster index: %u\n", index); + return NULL; + } + } +#endif + return variant; } @@ -513,9 +661,8 @@ static int s3c2410wdt_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct s3c2410_wdt *wdt; - struct resource *wdt_irq; unsigned int wtcon; - int started = 0; + int wdt_irq; int ret; wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL); @@ -527,6 +674,9 @@ static int s3c2410wdt_probe(struct platform_device *pdev) wdt->wdt_device = s3c2410_wdd; wdt->drv_data = s3c2410_get_wdt_drv_data(pdev); + if (!wdt->drv_data) + return -EINVAL; + if (wdt->drv_data->quirks & QUIRKS_HAVE_PMUREG) { wdt->pmureg = syscon_regmap_lookup_by_phandle(dev->of_node, "samsung,syscon-phandle"); @@ -536,40 +686,52 @@ static int s3c2410wdt_probe(struct platform_device *pdev) } } - wdt_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); - if (wdt_irq == NULL) { - dev_err(dev, "no irq resource specified\n"); - ret = -ENOENT; - goto err; - } + wdt_irq = platform_get_irq(pdev, 0); + if (wdt_irq < 0) + return wdt_irq; /* get the memory region for the watchdog timer */ wdt->reg_base = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(wdt->reg_base)) { - ret = PTR_ERR(wdt->reg_base); - goto err; - } + if (IS_ERR(wdt->reg_base)) + return PTR_ERR(wdt->reg_base); - wdt->clock = devm_clk_get(dev, "watchdog"); - if (IS_ERR(wdt->clock)) { - dev_err(dev, "failed to find watchdog clock source\n"); - ret = PTR_ERR(wdt->clock); - goto err; + wdt->bus_clk = devm_clk_get(dev, "watchdog"); + if (IS_ERR(wdt->bus_clk)) { + dev_err(dev, "failed to find bus clock\n"); + return PTR_ERR(wdt->bus_clk); } - ret = clk_prepare_enable(wdt->clock); + ret = clk_prepare_enable(wdt->bus_clk); if (ret < 0) { - dev_err(dev, "failed to enable clock\n"); + dev_err(dev, "failed to enable bus clock\n"); return ret; } + /* + * "watchdog_src" clock is optional; if it's not present -- just skip it + * and use "watchdog" clock as both bus and source clock. + */ + wdt->src_clk = devm_clk_get_optional(dev, "watchdog_src"); + if (IS_ERR(wdt->src_clk)) { + dev_err_probe(dev, PTR_ERR(wdt->src_clk), + "failed to get source clock\n"); + ret = PTR_ERR(wdt->src_clk); + goto err_bus_clk; + } + + ret = clk_prepare_enable(wdt->src_clk); + if (ret) { + dev_err(dev, "failed to enable source clock\n"); + goto err_bus_clk; + } + wdt->wdt_device.min_timeout = 1; - wdt->wdt_device.max_timeout = s3c2410wdt_max_timeout(wdt->clock); + wdt->wdt_device.max_timeout = s3c2410wdt_max_timeout(wdt); ret = s3c2410wdt_cpufreq_register(wdt); if (ret < 0) { dev_err(dev, "failed to register cpufreq\n"); - goto err_clk; + goto err_src_clk; } watchdog_set_drvdata(&wdt->wdt_device, wdt); @@ -581,19 +743,19 @@ static int s3c2410wdt_probe(struct platform_device *pdev) ret = s3c2410wdt_set_heartbeat(&wdt->wdt_device, wdt->wdt_device.timeout); if (ret) { - started = s3c2410wdt_set_heartbeat(&wdt->wdt_device, - S3C2410_WATCHDOG_DEFAULT_TIME); - - if (started == 0) - dev_info(dev, - "tmr_margin value out of range, default %d used\n", + ret = s3c2410wdt_set_heartbeat(&wdt->wdt_device, + S3C2410_WATCHDOG_DEFAULT_TIME); + if (ret == 0) { + dev_warn(dev, "tmr_margin value out of range, default %d used\n", S3C2410_WATCHDOG_DEFAULT_TIME); - else - dev_info(dev, "default timer value is out of range, cannot start\n"); + } else { + dev_err(dev, "failed to use default timeout\n"); + goto err_cpufreq; + } } - ret = devm_request_irq(dev, wdt_irq->start, s3c2410wdt_irq, 0, - pdev->name, pdev); + ret = devm_request_irq(dev, wdt_irq, s3c2410wdt_irq, 0, + pdev->name, pdev); if (ret != 0) { dev_err(dev, "failed to install irq (%d)\n", ret); goto err_cpufreq; @@ -605,25 +767,29 @@ static int s3c2410wdt_probe(struct platform_device *pdev) wdt->wdt_device.bootstatus = s3c2410wdt_get_bootstatus(wdt); wdt->wdt_device.parent = dev; + /* + * If "tmr_atboot" param is non-zero, start the watchdog right now. Also + * set WDOG_HW_RUNNING bit, so that watchdog core can kick the watchdog. + * + * If we're not enabling the watchdog, then ensure it is disabled if it + * has been left running from the bootloader or other source. + */ + if (tmr_atboot) { + dev_info(dev, "starting watchdog timer\n"); + s3c2410wdt_start(&wdt->wdt_device); + set_bit(WDOG_HW_RUNNING, &wdt->wdt_device.status); + } else { + s3c2410wdt_stop(&wdt->wdt_device); + } + ret = watchdog_register_device(&wdt->wdt_device); if (ret) goto err_cpufreq; - ret = s3c2410wdt_mask_and_disable_reset(wdt, false); + ret = s3c2410wdt_enable(wdt, true); if (ret < 0) goto err_unregister; - if (tmr_atboot && started == 0) { - dev_info(dev, "starting watchdog timer\n"); - s3c2410wdt_start(&wdt->wdt_device); - } else if (!tmr_atboot) { - /* if we're not enabling the watchdog, then ensure it is - * disabled if it has been left running from the bootloader - * or other source */ - - s3c2410wdt_stop(&wdt->wdt_device); - } - platform_set_drvdata(pdev, wdt); /* print out a statement of readiness */ @@ -643,10 +809,12 @@ static int s3c2410wdt_probe(struct platform_device *pdev) err_cpufreq: s3c2410wdt_cpufreq_deregister(wdt); - err_clk: - clk_disable_unprepare(wdt->clock); + err_src_clk: + clk_disable_unprepare(wdt->src_clk); + + err_bus_clk: + clk_disable_unprepare(wdt->bus_clk); - err: return ret; } @@ -655,7 +823,7 @@ static int s3c2410wdt_remove(struct platform_device *dev) int ret; struct s3c2410_wdt *wdt = platform_get_drvdata(dev); - ret = s3c2410wdt_mask_and_disable_reset(wdt, true); + ret = s3c2410wdt_enable(wdt, false); if (ret < 0) return ret; @@ -663,7 +831,8 @@ static int s3c2410wdt_remove(struct platform_device *dev) s3c2410wdt_cpufreq_deregister(wdt); - clk_disable_unprepare(wdt->clock); + clk_disable_unprepare(wdt->src_clk); + clk_disable_unprepare(wdt->bus_clk); return 0; } @@ -672,8 +841,7 @@ static void s3c2410wdt_shutdown(struct platform_device *dev) { struct s3c2410_wdt *wdt = platform_get_drvdata(dev); - s3c2410wdt_mask_and_disable_reset(wdt, true); - + s3c2410wdt_enable(wdt, false); s3c2410wdt_stop(&wdt->wdt_device); } @@ -688,7 +856,7 @@ static int s3c2410wdt_suspend(struct device *dev) wdt->wtcon_save = readl(wdt->reg_base + S3C2410_WTCON); wdt->wtdat_save = readl(wdt->reg_base + S3C2410_WTDAT); - ret = s3c2410wdt_mask_and_disable_reset(wdt, true); + ret = s3c2410wdt_enable(wdt, false); if (ret < 0) return ret; @@ -708,7 +876,7 @@ static int s3c2410wdt_resume(struct device *dev) writel(wdt->wtdat_save, wdt->reg_base + S3C2410_WTCNT);/* Reset count */ writel(wdt->wtcon_save, wdt->reg_base + S3C2410_WTCON); - ret = s3c2410wdt_mask_and_disable_reset(wdt, false); + ret = s3c2410wdt_enable(wdt, true); if (ret < 0) return ret; |