| .. | .. | 
|---|
|  | 1 | +// SPDX-License-Identifier: GPL-2.0-or-later | 
|---|
| 1 | 2 | /* | 
|---|
| 2 | 3 | *  Copyright (C) 2010, Paul Cercueil <paul@crapouillou.net> | 
|---|
| 3 | 4 | *  JZ4740 Watchdog driver | 
|---|
| 4 |  | - * | 
|---|
| 5 |  | - *  This program is free software; you can redistribute it and/or modify it | 
|---|
| 6 |  | - *  under  the terms of the GNU General  Public License as published by the | 
|---|
| 7 |  | - *  Free Software Foundation;  either version 2 of the License, or (at your | 
|---|
| 8 |  | - *  option) any later version. | 
|---|
| 9 |  | - * | 
|---|
| 10 |  | - *  You should have received a copy of the GNU General Public License along | 
|---|
| 11 |  | - *  with this program; if not, write to the Free Software Foundation, Inc., | 
|---|
| 12 |  | - *  675 Mass Ave, Cambridge, MA 02139, USA. | 
|---|
| 13 |  | - * | 
|---|
| 14 | 5 | */ | 
|---|
| 15 | 6 |  | 
|---|
|  | 7 | +#include <linux/mfd/ingenic-tcu.h> | 
|---|
|  | 8 | +#include <linux/mfd/syscon.h> | 
|---|
| 16 | 9 | #include <linux/module.h> | 
|---|
| 17 | 10 | #include <linux/moduleparam.h> | 
|---|
| 18 | 11 | #include <linux/types.h> | 
|---|
| .. | .. | 
|---|
| 25 | 18 | #include <linux/slab.h> | 
|---|
| 26 | 19 | #include <linux/err.h> | 
|---|
| 27 | 20 | #include <linux/of.h> | 
|---|
| 28 |  | - | 
|---|
| 29 |  | -#include <asm/mach-jz4740/timer.h> | 
|---|
| 30 |  | - | 
|---|
| 31 |  | -#define JZ_REG_WDT_TIMER_DATA     0x0 | 
|---|
| 32 |  | -#define JZ_REG_WDT_COUNTER_ENABLE 0x4 | 
|---|
| 33 |  | -#define JZ_REG_WDT_TIMER_COUNTER  0x8 | 
|---|
| 34 |  | -#define JZ_REG_WDT_TIMER_CONTROL  0xC | 
|---|
| 35 |  | - | 
|---|
| 36 |  | -#define JZ_WDT_CLOCK_PCLK 0x1 | 
|---|
| 37 |  | -#define JZ_WDT_CLOCK_RTC  0x2 | 
|---|
| 38 |  | -#define JZ_WDT_CLOCK_EXT  0x4 | 
|---|
| 39 |  | - | 
|---|
| 40 |  | -#define JZ_WDT_CLOCK_DIV_SHIFT   3 | 
|---|
| 41 |  | - | 
|---|
| 42 |  | -#define JZ_WDT_CLOCK_DIV_1    (0 << JZ_WDT_CLOCK_DIV_SHIFT) | 
|---|
| 43 |  | -#define JZ_WDT_CLOCK_DIV_4    (1 << JZ_WDT_CLOCK_DIV_SHIFT) | 
|---|
| 44 |  | -#define JZ_WDT_CLOCK_DIV_16   (2 << JZ_WDT_CLOCK_DIV_SHIFT) | 
|---|
| 45 |  | -#define JZ_WDT_CLOCK_DIV_64   (3 << JZ_WDT_CLOCK_DIV_SHIFT) | 
|---|
| 46 |  | -#define JZ_WDT_CLOCK_DIV_256  (4 << JZ_WDT_CLOCK_DIV_SHIFT) | 
|---|
| 47 |  | -#define JZ_WDT_CLOCK_DIV_1024 (5 << JZ_WDT_CLOCK_DIV_SHIFT) | 
|---|
|  | 21 | +#include <linux/regmap.h> | 
|---|
| 48 | 22 |  | 
|---|
| 49 | 23 | #define DEFAULT_HEARTBEAT 5 | 
|---|
| 50 | 24 | #define MAX_HEARTBEAT     2048 | 
|---|
| .. | .. | 
|---|
| 64 | 38 |  | 
|---|
| 65 | 39 | struct jz4740_wdt_drvdata { | 
|---|
| 66 | 40 | struct watchdog_device wdt; | 
|---|
| 67 |  | -	void __iomem *base; | 
|---|
| 68 |  | -	struct clk *rtc_clk; | 
|---|
|  | 41 | +	struct regmap *map; | 
|---|
|  | 42 | +	struct clk *clk; | 
|---|
|  | 43 | +	unsigned long clk_rate; | 
|---|
| 69 | 44 | }; | 
|---|
| 70 | 45 |  | 
|---|
| 71 | 46 | static int jz4740_wdt_ping(struct watchdog_device *wdt_dev) | 
|---|
| 72 | 47 | { | 
|---|
| 73 | 48 | struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); | 
|---|
| 74 | 49 |  | 
|---|
| 75 |  | -	writew(0x0, drvdata->base + JZ_REG_WDT_TIMER_COUNTER); | 
|---|
|  | 50 | +	regmap_write(drvdata->map, TCU_REG_WDT_TCNT, 0); | 
|---|
|  | 51 | + | 
|---|
| 76 | 52 | return 0; | 
|---|
| 77 | 53 | } | 
|---|
| 78 | 54 |  | 
|---|
| .. | .. | 
|---|
| 80 | 56 | unsigned int new_timeout) | 
|---|
| 81 | 57 | { | 
|---|
| 82 | 58 | struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); | 
|---|
| 83 |  | -	unsigned int rtc_clk_rate; | 
|---|
| 84 |  | -	unsigned int timeout_value; | 
|---|
| 85 |  | -	unsigned short clock_div = JZ_WDT_CLOCK_DIV_1; | 
|---|
|  | 59 | +	u16 timeout_value = (u16)(drvdata->clk_rate * new_timeout); | 
|---|
|  | 60 | +	unsigned int tcer; | 
|---|
| 86 | 61 |  | 
|---|
| 87 |  | -	rtc_clk_rate = clk_get_rate(drvdata->rtc_clk); | 
|---|
|  | 62 | +	regmap_read(drvdata->map, TCU_REG_WDT_TCER, &tcer); | 
|---|
|  | 63 | +	regmap_write(drvdata->map, TCU_REG_WDT_TCER, 0); | 
|---|
| 88 | 64 |  | 
|---|
| 89 |  | -	timeout_value = rtc_clk_rate * new_timeout; | 
|---|
| 90 |  | -	while (timeout_value > 0xffff) { | 
|---|
| 91 |  | -		if (clock_div == JZ_WDT_CLOCK_DIV_1024) { | 
|---|
| 92 |  | -			/* Requested timeout too high; | 
|---|
| 93 |  | -			* use highest possible value. */ | 
|---|
| 94 |  | -			timeout_value = 0xffff; | 
|---|
| 95 |  | -			break; | 
|---|
| 96 |  | -		} | 
|---|
| 97 |  | -		timeout_value >>= 2; | 
|---|
| 98 |  | -		clock_div += (1 << JZ_WDT_CLOCK_DIV_SHIFT); | 
|---|
| 99 |  | -	} | 
|---|
|  | 65 | +	regmap_write(drvdata->map, TCU_REG_WDT_TDR, timeout_value); | 
|---|
|  | 66 | +	regmap_write(drvdata->map, TCU_REG_WDT_TCNT, 0); | 
|---|
| 100 | 67 |  | 
|---|
| 101 |  | -	writeb(0x0, drvdata->base + JZ_REG_WDT_COUNTER_ENABLE); | 
|---|
| 102 |  | -	writew(clock_div, drvdata->base + JZ_REG_WDT_TIMER_CONTROL); | 
|---|
| 103 |  | - | 
|---|
| 104 |  | -	writew((u16)timeout_value, drvdata->base + JZ_REG_WDT_TIMER_DATA); | 
|---|
| 105 |  | -	writew(0x0, drvdata->base + JZ_REG_WDT_TIMER_COUNTER); | 
|---|
| 106 |  | -	writew(clock_div | JZ_WDT_CLOCK_RTC, | 
|---|
| 107 |  | -		drvdata->base + JZ_REG_WDT_TIMER_CONTROL); | 
|---|
| 108 |  | - | 
|---|
| 109 |  | -	writeb(0x1, drvdata->base + JZ_REG_WDT_COUNTER_ENABLE); | 
|---|
|  | 68 | +	if (tcer & TCU_WDT_TCER_TCEN) | 
|---|
|  | 69 | +		regmap_write(drvdata->map, TCU_REG_WDT_TCER, TCU_WDT_TCER_TCEN); | 
|---|
| 110 | 70 |  | 
|---|
| 111 | 71 | wdt_dev->timeout = new_timeout; | 
|---|
| 112 | 72 | return 0; | 
|---|
| .. | .. | 
|---|
| 114 | 74 |  | 
|---|
| 115 | 75 | static int jz4740_wdt_start(struct watchdog_device *wdt_dev) | 
|---|
| 116 | 76 | { | 
|---|
| 117 |  | -	jz4740_timer_enable_watchdog(); | 
|---|
|  | 77 | +	struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); | 
|---|
|  | 78 | +	unsigned int tcer; | 
|---|
|  | 79 | +	int ret; | 
|---|
|  | 80 | + | 
|---|
|  | 81 | +	ret = clk_prepare_enable(drvdata->clk); | 
|---|
|  | 82 | +	if (ret) | 
|---|
|  | 83 | +		return ret; | 
|---|
|  | 84 | + | 
|---|
|  | 85 | +	regmap_read(drvdata->map, TCU_REG_WDT_TCER, &tcer); | 
|---|
|  | 86 | + | 
|---|
| 118 | 87 | jz4740_wdt_set_timeout(wdt_dev, wdt_dev->timeout); | 
|---|
|  | 88 | + | 
|---|
|  | 89 | +	/* Start watchdog if it wasn't started already */ | 
|---|
|  | 90 | +	if (!(tcer & TCU_WDT_TCER_TCEN)) | 
|---|
|  | 91 | +		regmap_write(drvdata->map, TCU_REG_WDT_TCER, TCU_WDT_TCER_TCEN); | 
|---|
| 119 | 92 |  | 
|---|
| 120 | 93 | return 0; | 
|---|
| 121 | 94 | } | 
|---|
| .. | .. | 
|---|
| 124 | 97 | { | 
|---|
| 125 | 98 | struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); | 
|---|
| 126 | 99 |  | 
|---|
| 127 |  | -	writeb(0x0, drvdata->base + JZ_REG_WDT_COUNTER_ENABLE); | 
|---|
| 128 |  | -	jz4740_timer_disable_watchdog(); | 
|---|
|  | 100 | +	regmap_write(drvdata->map, TCU_REG_WDT_TCER, 0); | 
|---|
|  | 101 | +	clk_disable_unprepare(drvdata->clk); | 
|---|
| 129 | 102 |  | 
|---|
| 130 | 103 | return 0; | 
|---|
| 131 | 104 | } | 
|---|
| .. | .. | 
|---|
| 163 | 136 |  | 
|---|
| 164 | 137 | static int jz4740_wdt_probe(struct platform_device *pdev) | 
|---|
| 165 | 138 | { | 
|---|
|  | 139 | +	struct device *dev = &pdev->dev; | 
|---|
| 166 | 140 | struct jz4740_wdt_drvdata *drvdata; | 
|---|
| 167 | 141 | struct watchdog_device *jz4740_wdt; | 
|---|
| 168 |  | -	struct resource	*res; | 
|---|
|  | 142 | +	long rate; | 
|---|
| 169 | 143 | int ret; | 
|---|
| 170 | 144 |  | 
|---|
| 171 |  | -	drvdata = devm_kzalloc(&pdev->dev, sizeof(struct jz4740_wdt_drvdata), | 
|---|
|  | 145 | +	drvdata = devm_kzalloc(dev, sizeof(struct jz4740_wdt_drvdata), | 
|---|
| 172 | 146 | GFP_KERNEL); | 
|---|
| 173 | 147 | if (!drvdata) | 
|---|
| 174 | 148 | return -ENOMEM; | 
|---|
| 175 | 149 |  | 
|---|
| 176 |  | -	if (heartbeat < 1 || heartbeat > MAX_HEARTBEAT) | 
|---|
| 177 |  | -		heartbeat = DEFAULT_HEARTBEAT; | 
|---|
|  | 150 | +	drvdata->clk = devm_clk_get(&pdev->dev, "wdt"); | 
|---|
|  | 151 | +	if (IS_ERR(drvdata->clk)) { | 
|---|
|  | 152 | +		dev_err(&pdev->dev, "cannot find WDT clock\n"); | 
|---|
|  | 153 | +		return PTR_ERR(drvdata->clk); | 
|---|
|  | 154 | +	} | 
|---|
| 178 | 155 |  | 
|---|
|  | 156 | +	/* Set smallest clock possible */ | 
|---|
|  | 157 | +	rate = clk_round_rate(drvdata->clk, 1); | 
|---|
|  | 158 | +	if (rate < 0) | 
|---|
|  | 159 | +		return rate; | 
|---|
|  | 160 | + | 
|---|
|  | 161 | +	ret = clk_set_rate(drvdata->clk, rate); | 
|---|
|  | 162 | +	if (ret) | 
|---|
|  | 163 | +		return ret; | 
|---|
|  | 164 | + | 
|---|
|  | 165 | +	drvdata->clk_rate = rate; | 
|---|
| 179 | 166 | jz4740_wdt = &drvdata->wdt; | 
|---|
| 180 | 167 | jz4740_wdt->info = &jz4740_wdt_info; | 
|---|
| 181 | 168 | jz4740_wdt->ops = &jz4740_wdt_ops; | 
|---|
| 182 |  | -	jz4740_wdt->timeout = heartbeat; | 
|---|
| 183 | 169 | jz4740_wdt->min_timeout = 1; | 
|---|
| 184 |  | -	jz4740_wdt->max_timeout = MAX_HEARTBEAT; | 
|---|
| 185 |  | -	jz4740_wdt->parent = &pdev->dev; | 
|---|
|  | 170 | +	jz4740_wdt->max_timeout = 0xffff / rate; | 
|---|
|  | 171 | +	jz4740_wdt->timeout = clamp(heartbeat, | 
|---|
|  | 172 | +				    jz4740_wdt->min_timeout, | 
|---|
|  | 173 | +				    jz4740_wdt->max_timeout); | 
|---|
|  | 174 | +	jz4740_wdt->parent = dev; | 
|---|
| 186 | 175 | watchdog_set_nowayout(jz4740_wdt, nowayout); | 
|---|
| 187 | 176 | watchdog_set_drvdata(jz4740_wdt, drvdata); | 
|---|
| 188 | 177 |  | 
|---|
| 189 |  | -	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | 
|---|
| 190 |  | -	drvdata->base = devm_ioremap_resource(&pdev->dev, res); | 
|---|
| 191 |  | -	if (IS_ERR(drvdata->base)) | 
|---|
| 192 |  | -		return PTR_ERR(drvdata->base); | 
|---|
| 193 |  | - | 
|---|
| 194 |  | -	drvdata->rtc_clk = devm_clk_get(&pdev->dev, "rtc"); | 
|---|
| 195 |  | -	if (IS_ERR(drvdata->rtc_clk)) { | 
|---|
| 196 |  | -		dev_err(&pdev->dev, "cannot find RTC clock\n"); | 
|---|
| 197 |  | -		return PTR_ERR(drvdata->rtc_clk); | 
|---|
|  | 178 | +	drvdata->map = device_node_to_regmap(dev->parent->of_node); | 
|---|
|  | 179 | +	if (IS_ERR(drvdata->map)) { | 
|---|
|  | 180 | +		dev_err(dev, "regmap not found\n"); | 
|---|
|  | 181 | +		return PTR_ERR(drvdata->map); | 
|---|
| 198 | 182 | } | 
|---|
| 199 | 183 |  | 
|---|
| 200 |  | -	ret = devm_watchdog_register_device(&pdev->dev, &drvdata->wdt); | 
|---|
| 201 |  | -	if (ret < 0) | 
|---|
| 202 |  | -		return ret; | 
|---|
| 203 |  | - | 
|---|
| 204 |  | -	platform_set_drvdata(pdev, drvdata); | 
|---|
| 205 |  | - | 
|---|
| 206 |  | -	return 0; | 
|---|
|  | 184 | +	return devm_watchdog_register_device(dev, &drvdata->wdt); | 
|---|
| 207 | 185 | } | 
|---|
| 208 | 186 |  | 
|---|
| 209 | 187 | static struct platform_driver jz4740_wdt_driver = { | 
|---|