|  | /* | 
|  | * TI keystone reboot driver | 
|  | * | 
|  | * Copyright (C) 2014 Texas Instruments Incorporated. http://www.ti.com/ | 
|  | * | 
|  | * Author: Ivan Khoronzhuk <ivan.khoronzhuk@ti.com> | 
|  | * | 
|  | * This program is free software; you can redistribute it and/or modify | 
|  | * it under the terms of the GNU General Public License version 2 as | 
|  | * published by the Free Software Foundation. | 
|  | */ | 
|  |  | 
|  | #include <linux/io.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/notifier.h> | 
|  | #include <linux/reboot.h> | 
|  | #include <linux/regmap.h> | 
|  | #include <linux/mfd/syscon.h> | 
|  | #include <linux/of_platform.h> | 
|  |  | 
|  | #define RSTYPE_RG			0x0 | 
|  | #define RSCTRL_RG			0x4 | 
|  | #define RSCFG_RG			0x8 | 
|  | #define RSISO_RG			0xc | 
|  |  | 
|  | #define RSCTRL_KEY_MASK			0x0000ffff | 
|  | #define RSCTRL_RESET_MASK		BIT(16) | 
|  | #define RSCTRL_KEY			0x5a69 | 
|  |  | 
|  | #define RSMUX_OMODE_MASK		0xe | 
|  | #define RSMUX_OMODE_RESET_ON		0xa | 
|  | #define RSMUX_OMODE_RESET_OFF		0x0 | 
|  | #define RSMUX_LOCK_MASK			0x1 | 
|  | #define RSMUX_LOCK_SET			0x1 | 
|  |  | 
|  | #define RSCFG_RSTYPE_SOFT		0x300f | 
|  | #define RSCFG_RSTYPE_HARD		0x0 | 
|  |  | 
|  | #define WDT_MUX_NUMBER			0x4 | 
|  |  | 
|  | static int rspll_offset; | 
|  | static struct regmap *pllctrl_regs; | 
|  |  | 
|  | /** | 
|  | * rsctrl_enable_rspll_write - enable access to RSCTRL, RSCFG | 
|  | * To be able to access to RSCTRL, RSCFG registers | 
|  | * we have to write a key before | 
|  | */ | 
|  | static inline int rsctrl_enable_rspll_write(void) | 
|  | { | 
|  | return regmap_update_bits(pllctrl_regs, rspll_offset + RSCTRL_RG, | 
|  | RSCTRL_KEY_MASK, RSCTRL_KEY); | 
|  | } | 
|  |  | 
|  | static int rsctrl_restart_handler(struct notifier_block *this, | 
|  | unsigned long mode, void *cmd) | 
|  | { | 
|  | /* enable write access to RSTCTRL */ | 
|  | rsctrl_enable_rspll_write(); | 
|  |  | 
|  | /* reset the SOC */ | 
|  | regmap_update_bits(pllctrl_regs, rspll_offset + RSCTRL_RG, | 
|  | RSCTRL_RESET_MASK, 0); | 
|  |  | 
|  | return NOTIFY_DONE; | 
|  | } | 
|  |  | 
|  | static struct notifier_block rsctrl_restart_nb = { | 
|  | .notifier_call = rsctrl_restart_handler, | 
|  | .priority = 128, | 
|  | }; | 
|  |  | 
|  | static const struct of_device_id rsctrl_of_match[] = { | 
|  | {.compatible = "ti,keystone-reset", }, | 
|  | {}, | 
|  | }; | 
|  |  | 
|  | static int rsctrl_probe(struct platform_device *pdev) | 
|  | { | 
|  | int i; | 
|  | int ret; | 
|  | u32 val; | 
|  | unsigned int rg; | 
|  | u32 rsmux_offset; | 
|  | struct regmap *devctrl_regs; | 
|  | struct device *dev = &pdev->dev; | 
|  | struct device_node *np = dev->of_node; | 
|  |  | 
|  | if (!np) | 
|  | return -ENODEV; | 
|  |  | 
|  | /* get regmaps */ | 
|  | pllctrl_regs = syscon_regmap_lookup_by_phandle(np, "ti,syscon-pll"); | 
|  | if (IS_ERR(pllctrl_regs)) | 
|  | return PTR_ERR(pllctrl_regs); | 
|  |  | 
|  | devctrl_regs = syscon_regmap_lookup_by_phandle(np, "ti,syscon-dev"); | 
|  | if (IS_ERR(devctrl_regs)) | 
|  | return PTR_ERR(devctrl_regs); | 
|  |  | 
|  | ret = of_property_read_u32_index(np, "ti,syscon-pll", 1, &rspll_offset); | 
|  | if (ret) { | 
|  | dev_err(dev, "couldn't read the reset pll offset!\n"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | ret = of_property_read_u32_index(np, "ti,syscon-dev", 1, &rsmux_offset); | 
|  | if (ret) { | 
|  | dev_err(dev, "couldn't read the rsmux offset!\n"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | /* set soft/hard reset */ | 
|  | val = of_property_read_bool(np, "ti,soft-reset"); | 
|  | val = val ? RSCFG_RSTYPE_SOFT : RSCFG_RSTYPE_HARD; | 
|  |  | 
|  | ret = rsctrl_enable_rspll_write(); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | ret = regmap_write(pllctrl_regs, rspll_offset + RSCFG_RG, val); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | /* disable a reset isolation for all module clocks */ | 
|  | ret = regmap_write(pllctrl_regs, rspll_offset + RSISO_RG, 0); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | /* enable a reset for watchdogs from wdt-list */ | 
|  | for (i = 0; i < WDT_MUX_NUMBER; i++) { | 
|  | ret = of_property_read_u32_index(np, "ti,wdt-list", i, &val); | 
|  | if (ret == -EOVERFLOW && !i) { | 
|  | dev_err(dev, "ti,wdt-list property has to contain at" | 
|  | "least one entry\n"); | 
|  | return -EINVAL; | 
|  | } else if (ret) { | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (val >= WDT_MUX_NUMBER) { | 
|  | dev_err(dev, "ti,wdt-list property can contain " | 
|  | "only numbers < 4\n"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | rg = rsmux_offset + val * 4; | 
|  |  | 
|  | ret = regmap_update_bits(devctrl_regs, rg, RSMUX_OMODE_MASK, | 
|  | RSMUX_OMODE_RESET_ON | | 
|  | RSMUX_LOCK_SET); | 
|  | if (ret) | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | ret = register_restart_handler(&rsctrl_restart_nb); | 
|  | if (ret) | 
|  | dev_err(dev, "cannot register restart handler (err=%d)\n", ret); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static struct platform_driver rsctrl_driver = { | 
|  | .probe = rsctrl_probe, | 
|  | .driver = { | 
|  | .name = KBUILD_MODNAME, | 
|  | .of_match_table = rsctrl_of_match, | 
|  | }, | 
|  | }; | 
|  | module_platform_driver(rsctrl_driver); | 
|  |  | 
|  | MODULE_AUTHOR("Ivan Khoronzhuk <ivan.khoronzhuk@ti.com>"); | 
|  | MODULE_DESCRIPTION("Texas Instruments keystone reset driver"); | 
|  | MODULE_LICENSE("GPL v2"); | 
|  | MODULE_ALIAS("platform:" KBUILD_MODNAME); |