brintos

brintos / linux-shallow public Read only

0
0
Text · 7.9 KiB · 006f9c6 Raw
344 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/* Copyright (c) 2014, The Linux Foundation. All rights reserved.3 */4#include <linux/bits.h>5#include <linux/clk.h>6#include <linux/delay.h>7#include <linux/interrupt.h>8#include <linux/io.h>9#include <linux/kernel.h>10#include <linux/module.h>11#include <linux/of.h>12#include <linux/platform_device.h>13#include <linux/watchdog.h>14 15enum wdt_reg {16	WDT_RST,17	WDT_EN,18	WDT_STS,19	WDT_BARK_TIME,20	WDT_BITE_TIME,21};22 23#define QCOM_WDT_ENABLE		BIT(0)24 25static const u32 reg_offset_data_apcs_tmr[] = {26	[WDT_RST] = 0x38,27	[WDT_EN] = 0x40,28	[WDT_STS] = 0x44,29	[WDT_BARK_TIME] = 0x4C,30	[WDT_BITE_TIME] = 0x5C,31};32 33static const u32 reg_offset_data_kpss[] = {34	[WDT_RST] = 0x4,35	[WDT_EN] = 0x8,36	[WDT_STS] = 0xC,37	[WDT_BARK_TIME] = 0x10,38	[WDT_BITE_TIME] = 0x14,39};40 41struct qcom_wdt_match_data {42	const u32 *offset;43	bool pretimeout;44	u32 max_tick_count;45};46 47struct qcom_wdt {48	struct watchdog_device	wdd;49	unsigned long		rate;50	void __iomem		*base;51	const u32		*layout;52};53 54static void __iomem *wdt_addr(struct qcom_wdt *wdt, enum wdt_reg reg)55{56	return wdt->base + wdt->layout[reg];57}58 59static inline60struct qcom_wdt *to_qcom_wdt(struct watchdog_device *wdd)61{62	return container_of(wdd, struct qcom_wdt, wdd);63}64 65static irqreturn_t qcom_wdt_isr(int irq, void *arg)66{67	struct watchdog_device *wdd = arg;68 69	watchdog_notify_pretimeout(wdd);70 71	return IRQ_HANDLED;72}73 74static int qcom_wdt_start(struct watchdog_device *wdd)75{76	struct qcom_wdt *wdt = to_qcom_wdt(wdd);77	unsigned int bark = wdd->timeout - wdd->pretimeout;78 79	writel(0, wdt_addr(wdt, WDT_EN));80	writel(1, wdt_addr(wdt, WDT_RST));81	writel(bark * wdt->rate, wdt_addr(wdt, WDT_BARK_TIME));82	writel(wdd->timeout * wdt->rate, wdt_addr(wdt, WDT_BITE_TIME));83	writel(QCOM_WDT_ENABLE, wdt_addr(wdt, WDT_EN));84	return 0;85}86 87static int qcom_wdt_stop(struct watchdog_device *wdd)88{89	struct qcom_wdt *wdt = to_qcom_wdt(wdd);90 91	writel(0, wdt_addr(wdt, WDT_EN));92	return 0;93}94 95static int qcom_wdt_ping(struct watchdog_device *wdd)96{97	struct qcom_wdt *wdt = to_qcom_wdt(wdd);98 99	writel(1, wdt_addr(wdt, WDT_RST));100	return 0;101}102 103static int qcom_wdt_set_timeout(struct watchdog_device *wdd,104				unsigned int timeout)105{106	wdd->timeout = timeout;107	return qcom_wdt_start(wdd);108}109 110static int qcom_wdt_set_pretimeout(struct watchdog_device *wdd,111				   unsigned int timeout)112{113	wdd->pretimeout = timeout;114	return qcom_wdt_start(wdd);115}116 117static int qcom_wdt_restart(struct watchdog_device *wdd, unsigned long action,118			    void *data)119{120	struct qcom_wdt *wdt = to_qcom_wdt(wdd);121	u32 timeout;122 123	/*124	 * Trigger watchdog bite:125	 *    Setup BITE_TIME to be 128ms, and enable WDT.126	 */127	timeout = 128 * wdt->rate / 1000;128 129	writel(0, wdt_addr(wdt, WDT_EN));130	writel(1, wdt_addr(wdt, WDT_RST));131	writel(timeout, wdt_addr(wdt, WDT_BARK_TIME));132	writel(timeout, wdt_addr(wdt, WDT_BITE_TIME));133	writel(QCOM_WDT_ENABLE, wdt_addr(wdt, WDT_EN));134 135	/*136	 * Actually make sure the above sequence hits hardware before sleeping.137	 */138	wmb();139 140	mdelay(150);141	return 0;142}143 144static int qcom_wdt_is_running(struct watchdog_device *wdd)145{146	struct qcom_wdt *wdt = to_qcom_wdt(wdd);147 148	return (readl(wdt_addr(wdt, WDT_EN)) & QCOM_WDT_ENABLE);149}150 151static const struct watchdog_ops qcom_wdt_ops = {152	.start		= qcom_wdt_start,153	.stop		= qcom_wdt_stop,154	.ping		= qcom_wdt_ping,155	.set_timeout	= qcom_wdt_set_timeout,156	.set_pretimeout	= qcom_wdt_set_pretimeout,157	.restart        = qcom_wdt_restart,158	.owner		= THIS_MODULE,159};160 161static const struct watchdog_info qcom_wdt_info = {162	.options	= WDIOF_KEEPALIVEPING163			| WDIOF_MAGICCLOSE164			| WDIOF_SETTIMEOUT165			| WDIOF_CARDRESET,166	.identity	= KBUILD_MODNAME,167};168 169static const struct watchdog_info qcom_wdt_pt_info = {170	.options	= WDIOF_KEEPALIVEPING171			| WDIOF_MAGICCLOSE172			| WDIOF_SETTIMEOUT173			| WDIOF_PRETIMEOUT174			| WDIOF_CARDRESET,175	.identity	= KBUILD_MODNAME,176};177 178static const struct qcom_wdt_match_data match_data_apcs_tmr = {179	.offset = reg_offset_data_apcs_tmr,180	.pretimeout = false,181	.max_tick_count = 0x10000000U,182};183 184static const struct qcom_wdt_match_data match_data_kpss = {185	.offset = reg_offset_data_kpss,186	.pretimeout = true,187	.max_tick_count = 0xFFFFFU,188};189 190static int qcom_wdt_probe(struct platform_device *pdev)191{192	struct device *dev = &pdev->dev;193	struct qcom_wdt *wdt;194	struct resource *res;195	struct device_node *np = dev->of_node;196	const struct qcom_wdt_match_data *data;197	u32 percpu_offset;198	int irq, ret;199	struct clk *clk;200 201	data = of_device_get_match_data(dev);202	if (!data) {203		dev_err(dev, "Unsupported QCOM WDT module\n");204		return -ENODEV;205	}206 207	wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);208	if (!wdt)209		return -ENOMEM;210 211	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);212	if (!res)213		return -ENOMEM;214 215	/* We use CPU0's DGT for the watchdog */216	if (of_property_read_u32(np, "cpu-offset", &percpu_offset))217		percpu_offset = 0;218 219	res->start += percpu_offset;220	res->end += percpu_offset;221 222	wdt->base = devm_ioremap_resource(dev, res);223	if (IS_ERR(wdt->base))224		return PTR_ERR(wdt->base);225 226	clk = devm_clk_get_enabled(dev, NULL);227	if (IS_ERR(clk)) {228		dev_err(dev, "failed to get input clock\n");229		return PTR_ERR(clk);230	}231 232	/*233	 * We use the clock rate to calculate the max timeout, so ensure it's234	 * not zero to avoid a divide-by-zero exception.235	 *236	 * WATCHDOG_CORE assumes units of seconds, if the WDT is clocked such237	 * that it would bite before a second elapses it's usefulness is238	 * limited.  Bail if this is the case.239	 */240	wdt->rate = clk_get_rate(clk);241	if (wdt->rate == 0 ||242	    wdt->rate > data->max_tick_count) {243		dev_err(dev, "invalid clock rate\n");244		return -EINVAL;245	}246 247	/* check if there is pretimeout support */248	irq = platform_get_irq_optional(pdev, 0);249	if (data->pretimeout && irq > 0) {250		ret = devm_request_irq(dev, irq, qcom_wdt_isr, 0,251				       "wdt_bark", &wdt->wdd);252		if (ret)253			return ret;254 255		wdt->wdd.info = &qcom_wdt_pt_info;256		wdt->wdd.pretimeout = 1;257	} else {258		if (irq == -EPROBE_DEFER)259			return -EPROBE_DEFER;260 261		wdt->wdd.info = &qcom_wdt_info;262	}263 264	wdt->wdd.ops = &qcom_wdt_ops;265	wdt->wdd.min_timeout = 1;266	wdt->wdd.max_timeout = data->max_tick_count / wdt->rate;267	wdt->wdd.parent = dev;268	wdt->layout = data->offset;269 270	if (readl(wdt_addr(wdt, WDT_STS)) & 1)271		wdt->wdd.bootstatus = WDIOF_CARDRESET;272 273	/*274	 * If 'timeout-sec' unspecified in devicetree, assume a 30 second275	 * default, unless the max timeout is less than 30 seconds, then use276	 * the max instead.277	 */278	wdt->wdd.timeout = min(wdt->wdd.max_timeout, 30U);279	watchdog_init_timeout(&wdt->wdd, 0, dev);280 281	/*282	 * If WDT is already running, call WDT start which283	 * will stop the WDT, set timeouts as bootloader284	 * might use different ones and set running bit285	 * to inform the WDT subsystem to ping the WDT286	 */287	if (qcom_wdt_is_running(&wdt->wdd)) {288		qcom_wdt_start(&wdt->wdd);289		set_bit(WDOG_HW_RUNNING, &wdt->wdd.status);290	}291 292	ret = devm_watchdog_register_device(dev, &wdt->wdd);293	if (ret)294		return ret;295 296	platform_set_drvdata(pdev, wdt);297	return 0;298}299 300static int __maybe_unused qcom_wdt_suspend(struct device *dev)301{302	struct qcom_wdt *wdt = dev_get_drvdata(dev);303 304	if (watchdog_active(&wdt->wdd))305		qcom_wdt_stop(&wdt->wdd);306 307	return 0;308}309 310static int __maybe_unused qcom_wdt_resume(struct device *dev)311{312	struct qcom_wdt *wdt = dev_get_drvdata(dev);313 314	if (watchdog_active(&wdt->wdd))315		qcom_wdt_start(&wdt->wdd);316 317	return 0;318}319 320static const struct dev_pm_ops qcom_wdt_pm_ops = {321	SET_LATE_SYSTEM_SLEEP_PM_OPS(qcom_wdt_suspend, qcom_wdt_resume)322};323 324static const struct of_device_id qcom_wdt_of_table[] = {325	{ .compatible = "qcom,kpss-timer", .data = &match_data_apcs_tmr },326	{ .compatible = "qcom,scss-timer", .data = &match_data_apcs_tmr },327	{ .compatible = "qcom,kpss-wdt", .data = &match_data_kpss },328	{ },329};330MODULE_DEVICE_TABLE(of, qcom_wdt_of_table);331 332static struct platform_driver qcom_watchdog_driver = {333	.probe	= qcom_wdt_probe,334	.driver	= {335		.name		= KBUILD_MODNAME,336		.of_match_table	= qcom_wdt_of_table,337		.pm		= &qcom_wdt_pm_ops,338	},339};340module_platform_driver(qcom_watchdog_driver);341 342MODULE_DESCRIPTION("QCOM KPSS Watchdog Driver");343MODULE_LICENSE("GPL v2");344