diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/phy/Kconfig | 13 | ||||
-rw-r--r-- | drivers/phy/Makefile | 1 | ||||
-rw-r--r-- | drivers/phy/freescale/phy-fsl-imx8mq-usb.c | 2 | ||||
-rw-r--r-- | drivers/phy/phy-core.c | 29 | ||||
-rw-r--r-- | drivers/phy/phy-stm32-usbphyc.c | 434 | ||||
-rw-r--r-- | drivers/phy/usb-nop-xceiv.c | 2 | ||||
-rw-r--r-- | drivers/pinctrl/pinctrl-tegra-xusb.c | 4 | ||||
-rw-r--r-- | drivers/regulator/Kconfig | 7 | ||||
-rw-r--r-- | drivers/regulator/Makefile | 1 | ||||
-rw-r--r-- | drivers/regulator/core.c | 139 | ||||
-rw-r--r-- | drivers/regulator/stm32-pwr.c | 215 | ||||
-rw-r--r-- | drivers/reset/reset-stm32.c | 117 | ||||
-rw-r--r-- | drivers/usb/imx/imx-usb-phy.c | 2 | ||||
-rw-r--r-- | drivers/watchdog/f71808e_wdt.c | 8 | ||||
-rw-r--r-- | drivers/watchdog/stm32_iwdg.c | 99 | ||||
-rw-r--r-- | drivers/watchdog/stpmic1_wdt.c | 2 |
16 files changed, 958 insertions, 117 deletions
diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index b5cefb2ff3..b5c8e98b9e 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -24,4 +24,17 @@ config USB_NOP_XCEIV source "drivers/phy/freescale/Kconfig" +config PHY_STM32_USBPHYC + tristate "STM32 USB HS PHY Controller" + depends on ARCH_STM32MP + help + Enable this to support the High-Speed USB transceivers that are part + of some STMicroelectronics STM32 SoCs. + + This driver controls the entire USB PHY block: the USB PHY controller + (USBPHYC) and the two 8-bit wide UTMI+ interfaces. First interface is + used by an HS USB Host controller, and the second one is shared + between an HS USB OTG controller and an HS USB Host controller, + selected by a USB switch. + endif diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile index 179c55e60a..684aaed75a 100644 --- a/drivers/phy/Makefile +++ b/drivers/phy/Makefile @@ -5,3 +5,4 @@ obj-$(CONFIG_GENERIC_PHY) += phy-core.o obj-$(CONFIG_USB_NOP_XCEIV) += usb-nop-xceiv.o obj-y += freescale/ +obj-$(CONFIG_PHY_STM32_USBPHYC) += phy-stm32-usbphyc.o diff --git a/drivers/phy/freescale/phy-fsl-imx8mq-usb.c b/drivers/phy/freescale/phy-fsl-imx8mq-usb.c index 1aef2b3004..6d60eacd7f 100644 --- a/drivers/phy/freescale/phy-fsl-imx8mq-usb.c +++ b/drivers/phy/freescale/phy-fsl-imx8mq-usb.c @@ -106,7 +106,7 @@ static int imx8mq_usb_phy_probe(struct device_d *dev) if (IS_ERR(imx_phy->base)) return PTR_ERR(imx_phy->base); - imx_phy->phy = phy_create(dev, NULL, &imx8mq_usb_phy_ops, NULL); + imx_phy->phy = phy_create(dev, NULL, &imx8mq_usb_phy_ops); if (IS_ERR(imx_phy->phy)) return PTR_ERR(imx_phy->phy); diff --git a/drivers/phy/phy-core.c b/drivers/phy/phy-core.c index 066a887a22..ff6e35d160 100644 --- a/drivers/phy/phy-core.c +++ b/drivers/phy/phy-core.c @@ -25,13 +25,11 @@ static int phy_ida; * @dev: device that is creating the new phy * @node: device node of the phy * @ops: function pointers for performing phy operations - * @init_data: contains the list of PHY consumers or NULL * * Called to create a phy using phy framework. */ struct phy *phy_create(struct device_d *dev, struct device_node *node, - const struct phy_ops *ops, - struct phy_init_data *init_data) + const struct phy_ops *ops) { int ret; int id; @@ -52,7 +50,16 @@ struct phy *phy_create(struct device_d *dev, struct device_node *node, phy->dev.device_node = node ?: dev->device_node; phy->id = id; phy->ops = ops; - phy->init_data = init_data; + + /* phy-supply */ + phy->pwr = regulator_get(&phy->dev, "phy"); + if (IS_ERR(phy->pwr)) { + ret = PTR_ERR(phy->pwr); + if (ret == -EPROBE_DEFER) + goto free_ida; + + phy->pwr = NULL; + } ret = register_device(&phy->dev); if (ret) @@ -364,3 +371,17 @@ struct phy *phy_optional_get(struct device_d *dev, const char *string) return phy; } +/** + * phy_get_by_index() - lookup and obtain a reference to a phy by index. + * @dev: device with node that references this phy + * @index: index of the phy + * + * Gets the phy using _of_phy_get() + */ +struct phy *phy_get_by_index(struct device_d *dev, int index) +{ + if (!dev->device_node) + return ERR_PTR(-ENODEV); + + return _of_phy_get(dev->device_node, index); +} diff --git a/drivers/phy/phy-stm32-usbphyc.c b/drivers/phy/phy-stm32-usbphyc.c new file mode 100644 index 0000000000..093842fe14 --- /dev/null +++ b/drivers/phy/phy-stm32-usbphyc.c @@ -0,0 +1,434 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * STMicroelectronics STM32 USB PHY Controller driver + * + * Copyright (C) 2018 STMicroelectronics + * Author(s): Amelie Delaunay <amelie.delaunay@st.com>. + */ +#include <common.h> +#include <init.h> +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <io.h> +#include <linux/phy/phy.h> +#include <linux/reset.h> +#include <asm-generic/div64.h> +#include <usb/phy.h> + +#define STM32_USBPHYC_PLL 0x0 +#define STM32_USBPHYC_MISC 0x8 +#define STM32_USBPHYC_VERSION 0x3F4 + +/* STM32_USBPHYC_PLL bit fields */ +#define PLLNDIV GENMASK(6, 0) +#define PLLFRACIN GENMASK(25, 10) +#define PLLEN BIT(26) +#define PLLSTRB BIT(27) +#define PLLSTRBYP BIT(28) +#define PLLFRACCTL BIT(29) +#define PLLDITHEN0 BIT(30) +#define PLLDITHEN1 BIT(31) + +/* STM32_USBPHYC_MISC bit fields */ +#define SWITHOST BIT(0) + +/* STM32_USBPHYC_VERSION bit fields */ +#define MINREV GENMASK(3, 0) +#define MAJREV GENMASK(7, 4) + +static const char * const supplies_names[] = { + "vdda1v1", /* 1V1 */ + "vdda1v8", /* 1V8 */ +}; + +#define NUM_SUPPLIES ARRAY_SIZE(supplies_names) + +#define PLL_LOCK_TIME_US 100 +#define PLL_PWR_DOWN_TIME_US 5 +#define PLL_FVCO_MHZ 2880 +#define PLL_INFF_MIN_RATE_HZ 19200000 +#define PLL_INFF_MAX_RATE_HZ 38400000 +#define HZ_PER_MHZ 1000000L + +struct pll_params { + u8 ndiv; + u16 frac; +}; + +struct stm32_usbphyc_phy { + struct phy *phy; + struct stm32_usbphyc *usbphyc; + struct regulator_bulk_data supplies[NUM_SUPPLIES]; + u32 index; + bool active; +}; + +struct stm32_usbphyc { + struct device_d *dev; + void __iomem *base; + struct clk *clk; + struct stm32_usbphyc_phy **phys; + int nphys; + int switch_setup; +}; + +static inline void stm32_usbphyc_set_bits(void __iomem *reg, u32 bits) +{ + writel(readl(reg) | bits, reg); +} + +static inline void stm32_usbphyc_clr_bits(void __iomem *reg, u32 bits) +{ + writel(readl(reg) & ~bits, reg); +} + +static void stm32_usbphyc_get_pll_params(u32 clk_rate, + struct pll_params *pll_params) +{ + unsigned long long fvco, ndiv, frac; + + /* _ + * | FVCO = INFF*2*(NDIV + FRACT/2^16) when DITHER_DISABLE[1] = 1 + * | FVCO = 2880MHz + * < + * | NDIV = integer part of input bits to set the LDF + * |_FRACT = fractional part of input bits to set the LDF + * => PLLNDIV = integer part of (FVCO / (INFF*2)) + * => PLLFRACIN = fractional part of(FVCO / INFF*2) * 2^16 + * <=> PLLFRACIN = ((FVCO / (INFF*2)) - PLLNDIV) * 2^16 + */ + fvco = (unsigned long long)PLL_FVCO_MHZ * HZ_PER_MHZ; + + ndiv = fvco; + do_div(ndiv, (clk_rate * 2)); + pll_params->ndiv = (u8)ndiv; + + frac = fvco * (1 << 16); + do_div(frac, (clk_rate * 2)); + frac = frac - (ndiv * (1 << 16)); + pll_params->frac = (u16)frac; +} + +static int stm32_usbphyc_pll_init(struct stm32_usbphyc *usbphyc) +{ + struct pll_params pll_params; + u32 clk_rate = clk_get_rate(usbphyc->clk); + u32 ndiv, frac; + u32 usbphyc_pll; + + if ((clk_rate < PLL_INFF_MIN_RATE_HZ) || + (clk_rate > PLL_INFF_MAX_RATE_HZ)) { + dev_err(usbphyc->dev, "input clk freq (%dHz) out of range\n", + clk_rate); + return -EINVAL; + } + + stm32_usbphyc_get_pll_params(clk_rate, &pll_params); + ndiv = FIELD_PREP(PLLNDIV, pll_params.ndiv); + frac = FIELD_PREP(PLLFRACIN, pll_params.frac); + + usbphyc_pll = PLLDITHEN1 | PLLDITHEN0 | PLLSTRBYP | ndiv; + + if (pll_params.frac) + usbphyc_pll |= PLLFRACCTL | frac; + + writel(usbphyc_pll, usbphyc->base + STM32_USBPHYC_PLL); + + dev_dbg(usbphyc->dev, "input clk freq=%dHz, ndiv=%lu, frac=%lu\n", + clk_rate, FIELD_GET(PLLNDIV, usbphyc_pll), + FIELD_GET(PLLFRACIN, usbphyc_pll)); + + return 0; +} + +static bool stm32_usbphyc_has_one_phy_active(struct stm32_usbphyc *usbphyc) +{ + int i; + + for (i = 0; i < usbphyc->nphys; i++) + if (usbphyc->phys[i]->active) + return true; + + return false; +} + +static int stm32_usbphyc_pll_enable(struct stm32_usbphyc *usbphyc) +{ + void __iomem *pll_reg = usbphyc->base + STM32_USBPHYC_PLL; + bool pllen = (readl(pll_reg) & PLLEN); + int ret; + + /* Check if one phy port has already configured the pll */ + if (pllen && stm32_usbphyc_has_one_phy_active(usbphyc)) + return 0; + + if (pllen) { + stm32_usbphyc_clr_bits(pll_reg, PLLEN); + /* Wait for minimum width of powerdown pulse (ENABLE = Low) */ + udelay(PLL_PWR_DOWN_TIME_US); + } + + ret = stm32_usbphyc_pll_init(usbphyc); + if (ret) + return ret; + + stm32_usbphyc_set_bits(pll_reg, PLLEN); + + /* Wait for maximum lock time */ + udelay(PLL_LOCK_TIME_US); + + if (!(readl(pll_reg) & PLLEN)) { + dev_err(usbphyc->dev, "PLLEN not set\n"); + return -EIO; + } + + return 0; +} + +static int stm32_usbphyc_pll_disable(struct stm32_usbphyc *usbphyc) +{ + void __iomem *pll_reg = usbphyc->base + STM32_USBPHYC_PLL; + + /* Check if other phy port active */ + if (stm32_usbphyc_has_one_phy_active(usbphyc)) + return 0; + + stm32_usbphyc_clr_bits(pll_reg, PLLEN); + /* Wait for minimum width of powerdown pulse (ENABLE = Low) */ + udelay(PLL_PWR_DOWN_TIME_US); + + if (readl(pll_reg) & PLLEN) { + dev_err(usbphyc->dev, "PLL not reset\n"); + return -EIO; + } + + return 0; +} + +static int stm32_usbphyc_phy_init(struct phy *phy) +{ + struct stm32_usbphyc_phy *usbphyc_phy = phy_get_drvdata(phy); + struct stm32_usbphyc *usbphyc = usbphyc_phy->usbphyc; + int ret; + + ret = stm32_usbphyc_pll_enable(usbphyc); + if (ret) + return ret; + + usbphyc_phy->active = true; + + return 0; +} + +static int stm32_usbphyc_phy_exit(struct phy *phy) +{ + struct stm32_usbphyc_phy *usbphyc_phy = phy_get_drvdata(phy); + struct stm32_usbphyc *usbphyc = usbphyc_phy->usbphyc; + + usbphyc_phy->active = false; + + return stm32_usbphyc_pll_disable(usbphyc); +} + +static int stm32_usbphyc_phy_power_on(struct phy *phy) +{ + struct stm32_usbphyc_phy *usbphyc_phy = phy_get_drvdata(phy); + + return regulator_bulk_enable(NUM_SUPPLIES, usbphyc_phy->supplies); +} + +static int stm32_usbphyc_phy_power_off(struct phy *phy) +{ + struct stm32_usbphyc_phy *usbphyc_phy = phy_get_drvdata(phy); + + return regulator_bulk_disable(NUM_SUPPLIES, usbphyc_phy->supplies); +} + +static const struct phy_ops stm32_usbphyc_phy_ops = { + .init = stm32_usbphyc_phy_init, + .exit = stm32_usbphyc_phy_exit, + .power_on = stm32_usbphyc_phy_power_on, + .power_off = stm32_usbphyc_phy_power_off, +}; + +static void stm32_usbphyc_switch_setup(struct stm32_usbphyc *usbphyc, + u32 utmi_switch) +{ + if (!utmi_switch) + stm32_usbphyc_clr_bits(usbphyc->base + STM32_USBPHYC_MISC, + SWITHOST); + else + stm32_usbphyc_set_bits(usbphyc->base + STM32_USBPHYC_MISC, + SWITHOST); + usbphyc->switch_setup = utmi_switch; +} + +static struct phy *stm32_usbphyc_of_xlate(struct device_d *dev, + struct of_phandle_args *args) +{ + struct stm32_usbphyc *usbphyc = dev->priv; + struct stm32_usbphyc_phy *usbphyc_phy = NULL; + struct device_node *phynode = args->np; + int port = 0; + + for (port = 0; port < usbphyc->nphys; port++) { + if (phynode == usbphyc->phys[port]->phy->dev.device_node) { + usbphyc_phy = usbphyc->phys[port]; + break; + } + } + + if (!usbphyc_phy) { + dev_err(dev, "failed to find phy\n"); + return ERR_PTR(-EINVAL); + } + + if (((usbphyc_phy->index == 0) && (args->args_count != 0)) || + ((usbphyc_phy->index == 1) && (args->args_count != 1))) { + dev_err(dev, "invalid number of cells for phy port%d\n", + usbphyc_phy->index); + return ERR_PTR(-EINVAL); + } + + /* Configure the UTMI switch for PHY port#2 */ + if (usbphyc_phy->index == 1) { + if (usbphyc->switch_setup < 0) { + stm32_usbphyc_switch_setup(usbphyc, args->args[0]); + } else { + if (args->args[0] != usbphyc->switch_setup) { + dev_err(dev, "phy port1 already used\n"); + return ERR_PTR(-EBUSY); + } + } + } + + return usbphyc_phy->phy; +} + +static int stm32_usbphyc_probe(struct device_d *dev) +{ + struct stm32_usbphyc *usbphyc; + struct device_node *child, *np = dev->device_node; + struct resource *iores; + struct phy_provider *phy_provider; + u32 version; + int ret, port = 0; + + usbphyc = xzalloc(sizeof(*usbphyc)); + + usbphyc->dev = dev; + dev->priv = usbphyc; + + iores = dev_request_mem_resource(dev, 0); + if (IS_ERR(iores)) + return PTR_ERR(iores); + usbphyc->base = IOMEM(iores->start); + + usbphyc->clk = clk_get(dev, 0); + if (IS_ERR(usbphyc->clk)) { + ret = PTR_ERR(usbphyc->clk); + dev_err(dev, "clk get failed: %d\n", ret); + return ret; + } + + ret = clk_enable(usbphyc->clk); + if (ret) { + dev_err(dev, "clk enable failed: %d\n", ret); + return ret; + } + + device_reset_us(dev, 2); + + usbphyc->switch_setup = -EINVAL; + usbphyc->nphys = of_get_child_count(np); + usbphyc->phys = xzalloc(usbphyc->nphys * sizeof(*usbphyc->phys)); + + for_each_child_of_node(np, child) { + struct stm32_usbphyc_phy *usbphyc_phy; + struct phy *phy; + u32 index; + int i; + + phy = phy_create(dev, child, &stm32_usbphyc_phy_ops); + if (IS_ERR(phy)) { + ret = PTR_ERR(phy); + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to create phy%d: %d\n", + port, ret); + goto clk_disable; + } + + usbphyc_phy = xzalloc(sizeof(*usbphyc_phy)); + + for (i = 0; i < NUM_SUPPLIES; i++) + usbphyc_phy->supplies[i].supply = supplies_names[i]; + + ret = regulator_bulk_get(&phy->dev, NUM_SUPPLIES, + usbphyc_phy->supplies); + if (ret) { + if (ret != -EPROBE_DEFER) + dev_err(&phy->dev, + "failed to get regulators: %d\n", ret); + goto clk_disable; + } + + ret = of_property_read_u32(child, "reg", &index); + if (ret || index > usbphyc->nphys) { + dev_err(&phy->dev, "invalid reg property: %d\n", ret); + goto clk_disable; + } + + usbphyc->phys[port] = usbphyc_phy; + phy_set_bus_width(phy, 8); + phy_set_drvdata(phy, usbphyc_phy); + + usbphyc->phys[port]->phy = phy; + usbphyc->phys[port]->usbphyc = usbphyc; + usbphyc->phys[port]->index = index; + usbphyc->phys[port]->active = false; + + port++; + } + + phy_provider = of_phy_provider_register(dev, stm32_usbphyc_of_xlate); + if (IS_ERR(phy_provider)) { + ret = PTR_ERR(phy_provider); + dev_err(dev, "failed to register phy provider: %d\n", ret); + goto clk_disable; + } + + version = readl(usbphyc->base + STM32_USBPHYC_VERSION); + dev_info(dev, "registered rev: %lu.%lu\n", + FIELD_GET(MAJREV, version), FIELD_GET(MINREV, version)); + + return 0; + +clk_disable: + clk_disable(usbphyc->clk); + + return ret; +} + +static void stm32_usbphyc_remove(struct device_d *dev) +{ + struct stm32_usbphyc *usbphyc = dev->priv; + + clk_disable(usbphyc->clk); +} + +static const struct of_device_id stm32_usbphyc_of_match[] = { + { .compatible = "st,stm32mp1-usbphyc", }, + { /* sentinel */ }, +}; + +static struct driver_d stm32_usbphyc_driver = { + .name = "stm32-usbphyc", + .probe = stm32_usbphyc_probe, + .remove = stm32_usbphyc_remove, + .of_compatible = stm32_usbphyc_of_match, +}; +device_platform_driver(stm32_usbphyc_driver); + +MODULE_DESCRIPTION("STMicroelectronics STM32 USBPHYC driver"); +MODULE_AUTHOR("Amelie Delaunay <amelie.delaunay@st.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/usb-nop-xceiv.c b/drivers/phy/usb-nop-xceiv.c index b124e6c0c4..a9031fa7f8 100644 --- a/drivers/phy/usb-nop-xceiv.c +++ b/drivers/phy/usb-nop-xceiv.c @@ -107,7 +107,7 @@ static int nop_usbphy_probe(struct device_d *dev) /* FIXME: Add vbus-detect-gpio support */ nopphy->usb_phy.dev = dev; - nopphy->phy = phy_create(dev, NULL, &nop_phy_ops, NULL); + nopphy->phy = phy_create(dev, NULL, &nop_phy_ops); if (IS_ERR(nopphy->phy)) { ret = PTR_ERR(nopphy->phy); goto release_gpio; diff --git a/drivers/pinctrl/pinctrl-tegra-xusb.c b/drivers/pinctrl/pinctrl-tegra-xusb.c index e477280e62..c4d3bbe8d4 100644 --- a/drivers/pinctrl/pinctrl-tegra-xusb.c +++ b/drivers/pinctrl/pinctrl-tegra-xusb.c @@ -415,7 +415,7 @@ static int pinctrl_tegra_xusb_probe(struct device_d *dev) goto reset; } - phy = phy_create(dev, NULL, &pcie_phy_ops, NULL); + phy = phy_create(dev, NULL, &pcie_phy_ops); if (IS_ERR(phy)) { err = PTR_ERR(phy); goto unregister; @@ -424,7 +424,7 @@ static int pinctrl_tegra_xusb_probe(struct device_d *dev) padctl->phys[TEGRA_XUSB_PADCTL_PCIE] = phy; phy_set_drvdata(phy, padctl); - phy = phy_create(dev, NULL, &sata_phy_ops, NULL); + phy = phy_create(dev, NULL, &sata_phy_ops); if (IS_ERR(phy)) { err = PTR_ERR(phy); goto unregister; diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index f47a115da2..1ce057180a 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -21,6 +21,13 @@ config REGULATOR_PFUZE depends on I2C depends on ARCH_IMX6 || ARCH_IMX8MQ +config REGULATOR_STM32_PWR + bool "STMicroelectronics STM32 PWR" + depends on ARCH_STM32MP + help + This driver supports internal regulators (1V1, 1V8, 3V3) in the + STMicroelectronics STM32 chips. + config REGULATOR_STPMIC1 tristate "STMicroelectronics STPMIC1 PMIC Regulators" depends on MFD_STPMIC1 diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index e27e155cf6..4d0bba6c52 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -5,3 +5,4 @@ obj-$(CONFIG_REGULATOR_BCM283X) += bcm2835.o obj-$(CONFIG_REGULATOR_PFUZE) += pfuze.o obj-$(CONFIG_REGULATOR_STPMIC1) += stpmic1_regulator.o obj-$(CONFIG_REGULATOR_ANATOP) += anatop-regulator.o +obj-$(CONFIG_REGULATOR_STM32_PWR) += stm32-pwr.o diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index f0de7a52e3..f459d072a9 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -397,6 +397,145 @@ int regulator_set_voltage(struct regulator *r, int min_uV, int max_uV) return regulator_set_voltage_internal(r->ri, min_uV, max_uV); } +/** + * regulator_bulk_get - get multiple regulator consumers + * + * @dev: Device to supply + * @num_consumers: Number of consumers to register + * @consumers: Configuration of consumers; clients are stored here. + * + * @return 0 on success, an errno on failure. + * + * This helper function allows drivers to get several regulator + * consumers in one operation. If any of the regulators cannot be + * acquired then any regulators that were allocated will be freed + * before returning to the caller. + */ +int regulator_bulk_get(struct device_d *dev, int num_consumers, + struct regulator_bulk_data *consumers) +{ + int i; + int ret; + + for (i = 0; i < num_consumers; i++) + consumers[i].consumer = NULL; + + for (i = 0; i < num_consumers; i++) { + consumers[i].consumer = regulator_get(dev, + consumers[i].supply); + if (IS_ERR(consumers[i].consumer)) { + ret = PTR_ERR(consumers[i].consumer); + consumers[i].consumer = NULL; + goto err; + } + } + + return 0; + +err: + if (ret != -EPROBE_DEFER) + dev_err(dev, "Failed to get supply '%s': %d\n", + consumers[i].supply, ret); + else + dev_dbg(dev, "Failed to get supply '%s', deferring\n", + consumers[i].supply); + + return ret; +} +EXPORT_SYMBOL_GPL(regulator_bulk_get); + +/** + * regulator_bulk_enable - enable multiple regulator consumers + * + * @num_consumers: Number of consumers + * @consumers: Consumer data; clients are stored here. + * @return 0 on success, an errno on failure + * + * This convenience API allows consumers to enable multiple regulator + * clients in a single API call. If any consumers cannot be enabled + * then any others that were enabled will be disabled again prior to + * return. + */ +int regulator_bulk_enable(int num_consumers, + struct regulator_bulk_data *consumers) +{ + int ret; + int i; + + for (i = 0; i < num_consumers; i++) { + ret = regulator_enable(consumers[i].consumer); + if (ret) + goto err; + } + + return 0; + +err: + while (--i >= 0) + regulator_disable(consumers[i].consumer); + + return ret; +} +EXPORT_SYMBOL_GPL(regulator_bulk_enable); + +/** + * regulator_bulk_disable - disable multiple regulator consumers + * + * @num_consumers: Number of consumers + * @consumers: Consumer data; clients are stored here. + * @return 0 on success, an errno on failure + * + * This convenience API allows consumers to disable multiple regulator + * clients in a single API call. If any consumers cannot be disabled + * then any others that were disabled will be enabled again prior to + * return. + */ +int regulator_bulk_disable(int num_consumers, + struct regulator_bulk_data *consumers) +{ + int i; + int ret, r; + + for (i = num_consumers - 1; i >= 0; --i) { + ret = regulator_disable(consumers[i].consumer); + if (ret != 0) + goto err; + } + + return 0; + +err: + pr_err("Failed to disable %s: %d\n", consumers[i].supply, ret); + for (++i; i < num_consumers; ++i) { + r = regulator_enable(consumers[i].consumer); + if (r != 0) + pr_err("Failed to re-enable %s: %d\n", + consumers[i].supply, r); + } + + return ret; +} +EXPORT_SYMBOL_GPL(regulator_bulk_disable); + +/** + * regulator_bulk_free - free multiple regulator consumers + * + * @num_consumers: Number of consumers + * @consumers: Consumer data; clients are stored here. + * + * This convenience API allows consumers to free multiple regulator + * clients in a single API call. + */ +void regulator_bulk_free(int num_consumers, + struct regulator_bulk_data *consumers) +{ + int i; + + for (i = 0; i < num_consumers; i++) + consumers[i].consumer = NULL; +} +EXPORT_SYMBOL_GPL(regulator_bulk_free); + static void regulator_print_one(struct regulator_internal *ri) { struct regulator *r; diff --git a/drivers/regulator/stm32-pwr.c b/drivers/regulator/stm32-pwr.c new file mode 100644 index 0000000000..296f95bc4c --- /dev/null +++ b/drivers/regulator/stm32-pwr.c @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) STMicroelectronics 2019 +// Authors: Gabriel Fernandez <gabriel.fernandez@st.com> +// Pascal Paillet <p.paillet@st.com>. + +#include <common.h> +#include <init.h> +#include <driver.h> +#include <linux/iopoll.h> +#include <of.h> +#include <regulator.h> + +/* + * Registers description + */ +#define REG_PWR_CR3 0x0C + +#define USB_3_3_EN BIT(24) +#define USB_3_3_RDY BIT(26) +#define REG_1_8_EN BIT(28) +#define REG_1_8_RDY BIT(29) +#define REG_1_1_EN BIT(30) +#define REG_1_1_RDY BIT(31) + +/* list of supported regulators */ +enum { + PWR_REG11, + PWR_REG18, + PWR_USB33, + STM32PWR_REG_NUM_REGS +}; + +struct stm32_pwr_desc { + struct regulator_desc desc; + const char *name; + const char *supply_name; +}; + +static u32 ready_mask_table[STM32PWR_REG_NUM_REGS] = { + [PWR_REG11] = REG_1_1_RDY, + [PWR_REG18] = REG_1_8_RDY, + [PWR_USB33] = USB_3_3_RDY, +}; + +struct stm32_pwr_reg { + void __iomem *base; + struct device_d *dev; + u32 ready_mask; + struct regulator_dev rdev; + struct regulator *supply; +}; + +static inline struct stm32_pwr_reg *to_pwr_reg(struct regulator_dev *rdev) +{ + return container_of(rdev, struct stm32_pwr_reg, rdev); +} + +static inline struct stm32_pwr_desc *to_desc(struct regulator_dev *rdev) +{ + return container_of(rdev->desc, struct stm32_pwr_desc, desc); +} + +static int stm32_pwr_reg_is_ready(struct regulator_dev *rdev) +{ + struct stm32_pwr_reg *priv = to_pwr_reg(rdev); + u32 val; + + val = readl(priv->base + REG_PWR_CR3); + + return (val & priv->ready_mask); +} + +static int stm32_pwr_reg_is_enabled(struct regulator_dev *rdev) +{ + struct stm32_pwr_reg *priv = to_pwr_reg(rdev); + u32 val; + + val = readl(priv->base + REG_PWR_CR3); + + return (val & rdev->desc->enable_mask); +} + +static int stm32_pwr_reg_enable(struct regulator_dev *rdev) +{ + struct stm32_pwr_reg *priv = to_pwr_reg(rdev); + struct stm32_pwr_desc *desc = to_desc(rdev); + int ret; + u32 val; + + regulator_enable(priv->supply); + + val = readl(priv->base + REG_PWR_CR3); + val |= rdev->desc->enable_mask; + writel(val, priv->base + REG_PWR_CR3); + + /* use an arbitrary timeout of 20ms */ + ret = readx_poll_timeout(stm32_pwr_reg_is_ready, rdev, val, val, + 20 * USEC_PER_MSEC); + if (ret) + dev_err(priv->dev, "%s: regulator enable timed out!\n", + desc->name); + + return ret; +} + +static int stm32_pwr_reg_disable(struct regulator_dev *rdev) +{ + struct stm32_pwr_reg *priv = to_pwr_reg(rdev); + struct stm32_pwr_desc *desc = to_desc(rdev); + int ret; + u32 val; + + val = readl(priv->base + REG_PWR_CR3); + val &= ~rdev->desc->enable_mask; + writel(val, priv->base + REG_PWR_CR3); + + /* use an arbitrary timeout of 20ms */ + ret = readx_poll_timeout(stm32_pwr_reg_is_ready, rdev, val, !val, + 20 * USEC_PER_MSEC); + if (ret) + dev_err(priv->dev, "%s: regulator disable timed out!\n", + desc->name); + + regulator_disable(priv->supply); + + return ret; +} + +static const struct regulator_ops stm32_pwr_reg_ops = { + .enable = stm32_pwr_reg_enable, + .disable = stm32_pwr_reg_disable, + .is_enabled = stm32_pwr_reg_is_enabled, +}; + +#define PWR_REG(_id, _name, _volt, _en, _supply) \ + [_id] = { { \ + .n_voltages = 1, \ + .ops = &stm32_pwr_reg_ops, \ + .min_uV = _volt, \ + .enable_mask = _en, \ + }, .name = _name, .supply_name = _supply, } + +static const struct stm32_pwr_desc stm32_pwr_desc[] = { + PWR_REG(PWR_REG11, "reg11", 1100000, REG_1_1_EN, "vdd"), + PWR_REG(PWR_REG18, "reg18", 1800000, REG_1_8_EN, "vdd"), + PWR_REG(PWR_USB33, "usb33", 3300000, USB_3_3_EN, "vdd_3v3_usbfs"), +}; + +static int stm32_pwr_regulator_probe(struct device_d *dev) +{ + struct stm32_pwr_reg *priv; + struct device_node *child; + struct resource *iores; + int i, ret; + + iores = dev_request_mem_resource(dev, 0); + if (IS_ERR(iores)) + return PTR_ERR(iores); + + for_each_child_of_node(dev->device_node, child) { + const struct stm32_pwr_desc *desc = NULL; + + for (i = 0; i < STM32PWR_REG_NUM_REGS; i++) { + const char *name; + + name = of_get_property(child, "regulator-name", NULL); + if (name && strcmp(stm32_pwr_desc[i].name, name)) { + desc = &stm32_pwr_desc[i]; + break; + } + } + + if (!desc) { + dev_warn(dev, "Skipping unknown child node %s\n", + child->name); + continue; + } + + priv = xzalloc(sizeof(*priv)); + priv->base = IOMEM(iores->start); + priv->ready_mask = ready_mask_table[i]; + priv->dev = dev; + + priv->rdev.desc = &desc->desc; + + priv->supply = regulator_get(dev, desc->supply_name); + if (IS_ERR(priv->supply)) + return PTR_ERR(priv->supply); + + ret = of_regulator_register(&priv->rdev, child); + if (ret) { + dev_err(dev, "%s: Failed to register regulator: %d\n", + desc->name, ret); + return ret; + } + } + + return 0; +} + +static const struct of_device_id stm32_pwr_of_match[] = { + { .compatible = "st,stm32mp1,pwr-reg", }, + { /* sentinel */ }, +}; + +static struct driver_d stm32_pwr_driver = { + .probe = stm32_pwr_regulator_probe, + .name = "stm32-pwr-regulator", + .of_compatible = stm32_pwr_of_match, +}; +device_platform_driver(stm32_pwr_driver); + +MODULE_DESCRIPTION("STM32MP1 PWR voltage regulator driver"); +MODULE_AUTHOR("Pascal Paillet <p.paillet@st.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/reset/reset-stm32.c b/drivers/reset/reset-stm32.c index 5689dfd488..6c62633563 100644 --- a/drivers/reset/reset-stm32.c +++ b/drivers/reset/reset-stm32.c @@ -9,14 +9,48 @@ #include <init.h> #include <linux/err.h> #include <linux/reset-controller.h> +#include <restart.h> +#include <reset_source.h> #include <asm/io.h> #define RCC_CL 0x4 +#define RCC_MP_GRSTCSETR 0x404 +#define RCC_MP_RSTSCLRR 0x408 + +#define STM32MP_RCC_RSTF_POR BIT(0) +#define STM32MP_RCC_RSTF_BOR BIT(1) +#define STM32MP_RCC_RSTF_PAD BIT(2) +#define STM32MP_RCC_RSTF_HCSS BIT(3) +#define STM32MP_RCC_RSTF_VCORE BIT(4) + +#define STM32MP_RCC_RSTF_MPSYS BIT(6) +#define STM32MP_RCC_RSTF_MCSYS BIT(7) +#define STM32MP_RCC_RSTF_IWDG1 BIT(8) +#define STM32MP_RCC_RSTF_IWDG2 BIT(9) + +#define STM32MP_RCC_RSTF_STDBY BIT(11) +#define STM32MP_RCC_RSTF_CSTDBY BIT(12) +#define STM32MP_RCC_RSTF_MPUP0 BIT(13) +#define STM32MP_RCC_RSTF_MPUP1 BIT(14) + +struct stm32_reset_reason { + uint32_t mask; + enum reset_src_type type; + int instance; +}; + struct stm32_reset { void __iomem *base; struct reset_controller_dev rcdev; + struct restart_handler restart; + const struct stm32_reset_ops *ops; +}; + +struct stm32_reset_ops { void (*reset)(void __iomem *reg, unsigned offset, bool assert); + void __noreturn (*sys_reset)(struct restart_handler *rst); + const struct stm32_reset_reason *reset_reasons; }; static struct stm32_reset *to_stm32_reset(struct reset_controller_dev *rcdev) @@ -40,12 +74,40 @@ static void stm32mcu_reset(void __iomem *reg, unsigned offset, bool assert) clrbits_le32(reg, BIT(offset)); } +static u32 stm32_reset_status(struct stm32_reset *priv, unsigned long bank) +{ + return readl(priv->base + bank); +} + static void stm32_reset(struct stm32_reset *priv, unsigned long id, bool assert) { int bank = (id / BITS_PER_LONG) * 4; int offset = id % BITS_PER_LONG; - priv->reset(priv->base + bank, offset, assert); + priv->ops->reset(priv->base + bank, offset, assert); +} + +static void stm32_set_reset_reason(struct stm32_reset *priv, + const struct stm32_reset_reason *reasons) +{ + enum reset_src_type type = RESET_UKWN; + u32 reg; + int i, instance = 0; + + reg = stm32_reset_status(priv, RCC_MP_RSTSCLRR); + + for (i = 0; reasons[i].mask; i++) { + if (reg & reasons[i].mask) { + type = reasons[i].type; + instance = reasons[i].instance; + break; + } + } + + reset_source_set_prinst(type, RESET_SOURCE_DEFAULT_PRIORITY, instance); + + pr_info("STM32 RCC reset reason %s (MP_RSTSR: 0x%08x)\n", + reset_source_to_string(type), reg); } static int stm32_reset_assert(struct reset_controller_dev *rcdev, @@ -74,7 +136,7 @@ static int stm32_reset_probe(struct device_d *dev) int ret; priv = xzalloc(sizeof(*priv)); - ret = dev_get_drvdata(dev, (const void **)&priv->reset); + ret = dev_get_drvdata(dev, (const void **)&priv->ops); if (ret) return ret; @@ -87,12 +149,59 @@ static int stm32_reset_probe(struct device_d *dev) priv->rcdev.ops = &stm32_reset_ops; priv->rcdev.of_node = dev->device_node; + if (priv->ops->sys_reset) { + priv->restart.name = "stm32-rcc"; + priv->restart.restart = priv->ops->sys_reset; + priv->restart.priority = 200; + + ret = restart_handler_register(&priv->restart); + if (ret) + dev_warn(dev, "Cannot register restart handler\n"); + } + + if (priv->ops->reset_reasons) + stm32_set_reset_reason(priv, priv->ops->reset_reasons); + return reset_controller_register(&priv->rcdev); } +static void __noreturn stm32mp_rcc_restart_handler(struct restart_handler *rst) +{ + struct stm32_reset *priv = container_of(rst, struct stm32_reset, restart); + + stm32_reset(priv, RCC_MP_GRSTCSETR * BITS_PER_BYTE, true); + + mdelay(1000); + hang(); +} + +static const struct stm32_reset_reason stm32mp_reset_reasons[] = { + { STM32MP_RCC_RSTF_POR, RESET_POR, 0 }, + { STM32MP_RCC_RSTF_BOR, RESET_BROWNOUT, 0 }, + { STM32MP_RCC_RSTF_STDBY, RESET_WKE, 0 }, + { STM32MP_RCC_RSTF_CSTDBY, RESET_WKE, 1 }, + { STM32MP_RCC_RSTF_MPSYS, RESET_RST, 2 }, + { STM32MP_RCC_RSTF_MPUP0, RESET_RST, 0 }, + { STM32MP_RCC_RSTF_MPUP1, RESET_RST, 1 }, + { STM32MP_RCC_RSTF_IWDG1, RESET_WDG, 0 }, + { STM32MP_RCC_RSTF_IWDG2, RESET_WDG, 1 }, + { STM32MP_RCC_RSTF_PAD, RESET_EXT, 1 }, + { /* sentinel */ } +}; + +static const struct stm32_reset_ops stm32mp1_reset_ops = { + .reset = stm32mp_reset, + .sys_reset = stm32mp_rcc_restart_handler, + .reset_reasons = stm32mp_reset_reasons, +}; + +static const struct stm32_reset_ops stm32mcu_reset_ops = { + .reset = stm32mcu_reset, +}; + static const struct of_device_id stm32_rcc_reset_dt_ids[] = { - { .compatible = "st,stm32mp1-rcc", .data = stm32mp_reset }, - { .compatible = "st,stm32-rcc", .data = stm32mcu_reset }, + { .compatible = "st,stm32mp1-rcc", .data = &stm32mp1_reset_ops }, + { .compatible = "st,stm32-rcc", .data = &stm32mcu_reset_ops }, { /* sentinel */ }, }; diff --git a/drivers/usb/imx/imx-usb-phy.c b/drivers/usb/imx/imx-usb-phy.c index d4562285c0..069dddcacb 100644 --- a/drivers/usb/imx/imx-usb-phy.c +++ b/drivers/usb/imx/imx-usb-phy.c @@ -174,7 +174,7 @@ static int imx_usbphy_probe(struct device_d *dev) imxphy->usb_phy.dev = dev; imxphy->usb_phy.notify_connect = imx_usbphy_notify_connect; imxphy->usb_phy.notify_disconnect = imx_usbphy_notify_disconnect; - imxphy->phy = phy_create(dev, NULL, &imx_phy_ops, NULL); + imxphy->phy = phy_create(dev, NULL, &imx_phy_ops); if (IS_ERR(imxphy->phy)) { ret = PTR_ERR(imxphy->phy); goto err_clk; diff --git a/drivers/watchdog/f71808e_wdt.c b/drivers/watchdog/f71808e_wdt.c index 5307ab0b3e..6f2d30ec77 100644 --- a/drivers/watchdog/f71808e_wdt.c +++ b/drivers/watchdog/f71808e_wdt.c @@ -256,11 +256,11 @@ static int f71808e_wdt_init(struct f71808e_wdt *wd, struct device_d *dev) wdd->set_timeout = &f71808e_wdt_set_timeout; wdd->timeout_max = WATCHDOG_MAX_TIMEOUT; - if (wdt_conf & BIT(F71808FG_FLAG_WDTMOUT_STS)) - reset_source_set_priority(RESET_WDG, - RESET_SOURCE_DEFAULT_PRIORITY); + if (wdt_conf & BIT(F71808FG_FLAG_WDTMOUT_STS)) { + reset_source_set_priority(RESET_WDG, RESET_SOURCE_DEFAULT_PRIORITY); + dev_info(dev, "reset reason: WDT\n"); + } - dev_info(dev, "reset reason: %s\n", reset_source_name()); if (test_bit(F71808FG_FLAG_WD_EN, &wdt_conf)) wdd->running = WDOG_HW_RUNNING; diff --git a/drivers/watchdog/stm32_iwdg.c b/drivers/watchdog/stm32_iwdg.c index c7a5cb9caa..9e38f1a669 100644 --- a/drivers/watchdog/stm32_iwdg.c +++ b/drivers/watchdog/stm32_iwdg.c @@ -6,14 +6,11 @@ #include <common.h> #include <init.h> #include <watchdog.h> -#include <restart.h> #include <asm/io.h> #include <of_device.h> #include <linux/log2.h> #include <linux/iopoll.h> #include <linux/clk.h> -#include <mfd/syscon.h> -#include <reset_source.h> /* IWDG registers */ #define IWDG_KR 0x00 /* Key register */ @@ -36,40 +33,12 @@ #define SR_PVU BIT(0) /* Watchdog prescaler value update */ #define SR_RVU BIT(1) /* Watchdog counter reload value update */ -#define RCC_MP_GRSTCSETR 0x404 -#define RCC_MP_RSTSCLRR 0x408 -#define RCC_MP_GRSTCSETR_MPSYSRST BIT(0) - -#define STM32MP_RCC_RSTF_POR BIT(0) -#define STM32MP_RCC_RSTF_BOR BIT(1) -#define STM32MP_RCC_RSTF_PAD BIT(2) -#define STM32MP_RCC_RSTF_HCSS BIT(3) -#define STM32MP_RCC_RSTF_VCORE BIT(4) - -#define STM32MP_RCC_RSTF_MPSYS BIT(6) -#define STM32MP_RCC_RSTF_MCSYS BIT(7) -#define STM32MP_RCC_RSTF_IWDG1 BIT(8) -#define STM32MP_RCC_RSTF_IWDG2 BIT(9) - -#define STM32MP_RCC_RSTF_STDBY BIT(11) -#define STM32MP_RCC_RSTF_CSTDBY BIT(12) -#define STM32MP_RCC_RSTF_MPUP0 BIT(13) -#define STM32MP_RCC_RSTF_MPUP1 BIT(14) - /* set timeout to 100 ms */ #define TIMEOUT_US 100000 -struct stm32_reset_reason { - uint32_t mask; - enum reset_src_type type; - int instance; -}; - struct stm32_iwdg { struct watchdog wdd; - struct restart_handler restart; void __iomem *iwdg_base; - struct regmap *rcc_regmap; unsigned int timeout; unsigned int rate; }; @@ -79,17 +48,6 @@ static inline struct stm32_iwdg *to_stm32_iwdg(struct watchdog *wdd) return container_of(wdd, struct stm32_iwdg, wdd); } -static void __noreturn stm32_iwdg_restart_handler(struct restart_handler *rst) -{ - struct stm32_iwdg *wd = container_of(rst, struct stm32_iwdg, restart); - - regmap_update_bits(wd->rcc_regmap, RCC_MP_GRSTCSETR, - RCC_MP_GRSTCSETR_MPSYSRST, RCC_MP_GRSTCSETR_MPSYSRST); - - mdelay(1000); - hang(); -} - static void stm32_iwdg_ping(struct stm32_iwdg *wd) { writel(KR_KEY_RELOAD, wd->iwdg_base + IWDG_KR); @@ -149,47 +107,6 @@ static int stm32_iwdg_set_timeout(struct watchdog *wdd, unsigned int timeout) return 0; } -static const struct stm32_reset_reason stm32_reset_reasons[] = { - { STM32MP_RCC_RSTF_POR, RESET_POR, 0 }, - { STM32MP_RCC_RSTF_BOR, RESET_BROWNOUT, 0 }, - { STM32MP_RCC_RSTF_STDBY, RESET_WKE, 0 }, - { STM32MP_RCC_RSTF_CSTDBY, RESET_WKE, 1 }, - { STM32MP_RCC_RSTF_MPSYS, RESET_RST, 2 }, - { STM32MP_RCC_RSTF_MPUP0, RESET_RST, 0 }, - { STM32MP_RCC_RSTF_MPUP1, RESET_RST, 1 }, - { STM32MP_RCC_RSTF_IWDG1, RESET_WDG, 0 }, - { STM32MP_RCC_RSTF_IWDG2, RESET_WDG, 1 }, - { STM32MP_RCC_RSTF_PAD, RESET_EXT, 1 }, - { /* sentinel */ } -}; - -static int stm32_set_reset_reason(struct regmap *rcc) -{ - enum reset_src_type type = RESET_UKWN; - u32 reg; - int ret; - int i, instance = 0; - - ret = regmap_read(rcc, RCC_MP_RSTSCLRR, ®); - if (ret) - return ret; - - for (i = 0; stm32_reset_reasons[i].mask; i++) { - if (reg & stm32_reset_reasons[i].mask) { - type = stm32_reset_reasons[i].type; - instance = stm32_reset_reasons[i].instance; - break; - } - } - - reset_source_set_prinst(type, RESET_SOURCE_DEFAULT_PRIORITY, instance); - - pr_info("STM32 RCC reset reason %s (MP_RSTSR: 0x%08x)\n", - reset_source_name(), reg); - - return 0; -} - struct stm32_iwdg_data { bool has_pclk; u32 max_prescaler; @@ -264,22 +181,6 @@ static int stm32_iwdg_probe(struct device_d *dev) return ret; } - wd->restart.name = "stm32-iwdg"; - wd->restart.restart = stm32_iwdg_restart_handler; - wd->restart.priority = 200; - - wd->rcc_regmap = syscon_regmap_lookup_by_compatible("st,stm32mp1-rcc"); - if (IS_ERR(wd->rcc_regmap)) - dev_warn(dev, "Cannot register restart handler\n"); - - ret = restart_handler_register(&wd->restart); - if (ret) - dev_warn(dev, "Cannot register restart handler\n"); - - ret = stm32_set_reset_reason(wd->rcc_regmap); - if (ret) - dev_warn(dev, "Cannot determine reset reason\n"); - dev_info(dev, "probed\n"); return 0; } diff --git a/drivers/watchdog/stpmic1_wdt.c b/drivers/watchdog/stpmic1_wdt.c index 40273ffc4c..458c5c16a3 100644 --- a/drivers/watchdog/stpmic1_wdt.c +++ b/drivers/watchdog/stpmic1_wdt.c @@ -152,7 +152,7 @@ static int stpmic1_set_reset_reason(struct regmap *map) reset_source_set_prinst(type, 400, instance); pr_info("STPMIC1 reset reason %s (RREQ_STATE_SR: 0x%08x)\n", - reset_source_name(), reg); + reset_source_to_string(type), reg); return 0; } |