brintos

brintos / linux-shallow public Read only

0
0
Text · 7.0 KiB · 007ed13 Raw
278 lines · c
1// SPDX-License-Identifier: GPL-2.02#include <linux/bitops.h>3#include <linux/interrupt.h>4#include <linux/kernel.h>5#include <linux/module.h>6#include <linux/of.h>7#include <linux/property.h>8#include <linux/platform_device.h>9#include <linux/regmap.h>10#include <linux/watchdog.h>11 12#define PON_POFF_REASON1		0x0c13#define PON_POFF_REASON1_PMIC_WD	BIT(2)14#define PON_POFF_REASON2		0x0d15#define PON_POFF_REASON2_UVLO		BIT(5)16#define PON_POFF_REASON2_OTST3		BIT(6)17 18#define PON_INT_RT_STS			0x1019#define PMIC_WD_BARK_STS_BIT		BIT(6)20 21#define PON_PMIC_WD_RESET_S1_TIMER	0x5422#define PON_PMIC_WD_RESET_S2_TIMER	0x5523 24#define PON_PMIC_WD_RESET_S2_CTL	0x5625#define RESET_TYPE_WARM			0x0126#define RESET_TYPE_SHUTDOWN		0x0427#define RESET_TYPE_HARD			0x0728 29#define PON_PMIC_WD_RESET_S2_CTL2	0x5730#define S2_RESET_EN_BIT			BIT(7)31 32#define PON_PMIC_WD_RESET_PET		0x5833#define WATCHDOG_PET_BIT		BIT(0)34 35#define PM8916_WDT_DEFAULT_TIMEOUT	3236#define PM8916_WDT_MIN_TIMEOUT		137#define PM8916_WDT_MAX_TIMEOUT		12738 39struct pm8916_wdt {40	struct regmap *regmap;41	struct watchdog_device wdev;42	u32 baseaddr;43};44 45static int pm8916_wdt_start(struct watchdog_device *wdev)46{47	struct pm8916_wdt *wdt = watchdog_get_drvdata(wdev);48 49	return regmap_update_bits(wdt->regmap,50				  wdt->baseaddr + PON_PMIC_WD_RESET_S2_CTL2,51				  S2_RESET_EN_BIT, S2_RESET_EN_BIT);52}53 54static int pm8916_wdt_stop(struct watchdog_device *wdev)55{56	struct pm8916_wdt *wdt = watchdog_get_drvdata(wdev);57 58	return regmap_update_bits(wdt->regmap,59				  wdt->baseaddr + PON_PMIC_WD_RESET_S2_CTL2,60				  S2_RESET_EN_BIT, 0);61}62 63static int pm8916_wdt_ping(struct watchdog_device *wdev)64{65	struct pm8916_wdt *wdt = watchdog_get_drvdata(wdev);66 67	return regmap_write(wdt->regmap, wdt->baseaddr + PON_PMIC_WD_RESET_PET,68			    WATCHDOG_PET_BIT);69}70 71static int pm8916_wdt_configure_timers(struct watchdog_device *wdev)72{73	struct pm8916_wdt *wdt = watchdog_get_drvdata(wdev);74	int err;75 76	err = regmap_write(wdt->regmap,77			   wdt->baseaddr + PON_PMIC_WD_RESET_S1_TIMER,78			   wdev->timeout - wdev->pretimeout);79	if (err)80		return err;81 82	return regmap_write(wdt->regmap,83			    wdt->baseaddr + PON_PMIC_WD_RESET_S2_TIMER,84			    wdev->pretimeout);85}86 87static int pm8916_wdt_set_timeout(struct watchdog_device *wdev,88				  unsigned int timeout)89{90	wdev->timeout = timeout;91 92	return pm8916_wdt_configure_timers(wdev);93}94 95static int pm8916_wdt_set_pretimeout(struct watchdog_device *wdev,96				     unsigned int pretimeout)97{98	wdev->pretimeout = pretimeout;99 100	return pm8916_wdt_configure_timers(wdev);101}102 103static irqreturn_t pm8916_wdt_isr(int irq, void *arg)104{105	struct pm8916_wdt *wdt = arg;106	int err, sts;107 108	err = regmap_read(wdt->regmap, wdt->baseaddr + PON_INT_RT_STS, &sts);109	if (err)110		return IRQ_HANDLED;111 112	if (sts & PMIC_WD_BARK_STS_BIT)113		watchdog_notify_pretimeout(&wdt->wdev);114 115	return IRQ_HANDLED;116}117 118static const struct watchdog_info pm8916_wdt_ident = {119	.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE |120		   WDIOF_OVERHEAT | WDIOF_CARDRESET | WDIOF_POWERUNDER,121	.identity = "QCOM PM8916 PON WDT",122};123 124static const struct watchdog_info pm8916_wdt_pt_ident = {125	.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE |126		   WDIOF_OVERHEAT | WDIOF_CARDRESET | WDIOF_POWERUNDER |127		   WDIOF_PRETIMEOUT,128	.identity = "QCOM PM8916 PON WDT",129};130 131static const struct watchdog_ops pm8916_wdt_ops = {132	.owner = THIS_MODULE,133	.start = pm8916_wdt_start,134	.stop = pm8916_wdt_stop,135	.ping = pm8916_wdt_ping,136	.set_timeout = pm8916_wdt_set_timeout,137	.set_pretimeout = pm8916_wdt_set_pretimeout,138};139 140static int pm8916_wdt_probe(struct platform_device *pdev)141{142	struct device *dev = &pdev->dev;143	struct pm8916_wdt *wdt;144	struct device *parent;145	unsigned int val;146	int err, irq;147	u8 poff[2];148 149	wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);150	if (!wdt)151		return -ENOMEM;152 153	parent = dev->parent;154 155	/*156	 * The pm8916-pon-wdt is a child of the pon device, which is a child157	 * of the pm8916 mfd device. We want access to the pm8916 registers.158	 * Retrieve regmap from pm8916 (parent->parent) and base address159	 * from pm8916-pon (pon).160	 */161	wdt->regmap = dev_get_regmap(parent->parent, NULL);162	if (!wdt->regmap) {163		dev_err(dev, "failed to locate regmap\n");164		return -ENODEV;165	}166 167	err = device_property_read_u32(parent, "reg", &wdt->baseaddr);168	if (err) {169		dev_err(dev, "failed to get pm8916-pon address\n");170		return err;171	}172 173	irq = platform_get_irq(pdev, 0);174	if (irq > 0) {175		err = devm_request_irq(dev, irq, pm8916_wdt_isr, 0,176				       "pm8916_wdt", wdt);177		if (err)178			return err;179 180		wdt->wdev.info = &pm8916_wdt_pt_ident;181	} else {182		if (irq == -EPROBE_DEFER)183			return -EPROBE_DEFER;184 185		wdt->wdev.info = &pm8916_wdt_ident;186	}187 188	err = regmap_bulk_read(wdt->regmap, wdt->baseaddr + PON_POFF_REASON1,189			       &poff, ARRAY_SIZE(poff));190	if (err) {191		dev_err(dev, "failed to read POFF reason: %d\n", err);192		return err;193	}194 195	dev_dbg(dev, "POFF reason: %#x %#x\n", poff[0], poff[1]);196	if (poff[0] & PON_POFF_REASON1_PMIC_WD)197		wdt->wdev.bootstatus |= WDIOF_CARDRESET;198	if (poff[1] & PON_POFF_REASON2_UVLO)199		wdt->wdev.bootstatus |= WDIOF_POWERUNDER;200	if (poff[1] & PON_POFF_REASON2_OTST3)201		wdt->wdev.bootstatus |= WDIOF_OVERHEAT;202 203	err = regmap_read(wdt->regmap, wdt->baseaddr + PON_PMIC_WD_RESET_S2_CTL2,204			  &val);205	if (err)  {206		dev_err(dev, "failed to check if watchdog is active: %d\n", err);207		return err;208	}209	if (val & S2_RESET_EN_BIT)210		set_bit(WDOG_HW_RUNNING, &wdt->wdev.status);211 212	/* Configure watchdog to hard-reset mode */213	err = regmap_write(wdt->regmap,214			   wdt->baseaddr + PON_PMIC_WD_RESET_S2_CTL,215			   RESET_TYPE_HARD);216	if (err) {217		dev_err(dev, "failed configure watchdog\n");218		return err;219	}220 221	wdt->wdev.ops = &pm8916_wdt_ops;222	wdt->wdev.parent = dev;223	wdt->wdev.min_timeout = PM8916_WDT_MIN_TIMEOUT;224	wdt->wdev.max_timeout = PM8916_WDT_MAX_TIMEOUT;225	wdt->wdev.timeout = PM8916_WDT_DEFAULT_TIMEOUT;226	wdt->wdev.pretimeout = 0;227	watchdog_set_drvdata(&wdt->wdev, wdt);228	platform_set_drvdata(pdev, wdt);229 230	watchdog_init_timeout(&wdt->wdev, 0, dev);231	pm8916_wdt_configure_timers(&wdt->wdev);232 233	return devm_watchdog_register_device(dev, &wdt->wdev);234}235 236static int __maybe_unused pm8916_wdt_suspend(struct device *dev)237{238	struct pm8916_wdt *wdt = dev_get_drvdata(dev);239 240	if (watchdog_active(&wdt->wdev))241		return pm8916_wdt_stop(&wdt->wdev);242 243	return 0;244}245 246static int __maybe_unused pm8916_wdt_resume(struct device *dev)247{248	struct pm8916_wdt *wdt = dev_get_drvdata(dev);249 250	if (watchdog_active(&wdt->wdev))251		return pm8916_wdt_start(&wdt->wdev);252 253	return 0;254}255 256static SIMPLE_DEV_PM_OPS(pm8916_wdt_pm_ops, pm8916_wdt_suspend,257			 pm8916_wdt_resume);258 259static const struct of_device_id pm8916_wdt_id_table[] = {260	{ .compatible = "qcom,pm8916-wdt" },261	{ }262};263MODULE_DEVICE_TABLE(of, pm8916_wdt_id_table);264 265static struct platform_driver pm8916_wdt_driver = {266	.probe = pm8916_wdt_probe,267	.driver = {268		.name = "pm8916-wdt",269		.of_match_table = pm8916_wdt_id_table,270		.pm = &pm8916_wdt_pm_ops,271	},272};273module_platform_driver(pm8916_wdt_driver);274 275MODULE_AUTHOR("Loic Poulain <loic.poulain@linaro.org>");276MODULE_DESCRIPTION("Qualcomm pm8916 watchdog driver");277MODULE_LICENSE("GPL v2");278