From 63345435ac53949dc07b4ee7f31be4137b4132d1 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Mon, 10 Feb 2020 14:09:50 +0100 Subject: watchdog: f71808e: only print reset reason if one's indicated On most resets, a "reset reason: unknown" would clutter the console, because the watchdog doesn't support differentiating between POR and RST. Reduce the clutter by only printing the message when we have something interesting to say (i.e. watchdog reset). Signed-off-by: Ahmad Fatoum Signed-off-by: Sascha Hauer --- drivers/watchdog/f71808e_wdt.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers') 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; -- cgit v1.2.3 From 0b1ffec27b0dca8d57d8bfd6a9c3da62fe851812 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Mon, 10 Feb 2020 14:09:51 +0100 Subject: reset_source: migrate from reset_source_name to reset_source_to_string Whether reset_source_name() returns the just set reset_source is dependent on probe order and the priorities of prior reset sources in relation to the current one. Make this more robust by using the new reset_source_to_string. Signed-off-by: Ahmad Fatoum Signed-off-by: Sascha Hauer --- arch/arm/mach-imx/imx.c | 2 +- drivers/watchdog/stm32_iwdg.c | 2 +- drivers/watchdog/stpmic1_wdt.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/arch/arm/mach-imx/imx.c b/arch/arm/mach-imx/imx.c index 0942d50695..d5ce25d457 100644 --- a/arch/arm/mach-imx/imx.c +++ b/arch/arm/mach-imx/imx.c @@ -202,5 +202,5 @@ void imx_set_reset_reason(void __iomem *srsr, reset_source_set_prinst(type, RESET_SOURCE_DEFAULT_PRIORITY, instance); pr_info("i.MX reset reason %s (SRSR: 0x%08x)\n", - reset_source_name(), reg); + reset_source_to_string(type), reg); } diff --git a/drivers/watchdog/stm32_iwdg.c b/drivers/watchdog/stm32_iwdg.c index c7a5cb9caa..4cd36a67fa 100644 --- a/drivers/watchdog/stm32_iwdg.c +++ b/drivers/watchdog/stm32_iwdg.c @@ -185,7 +185,7 @@ static int stm32_set_reset_reason(struct regmap *rcc) 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); + reset_source_to_string(type), reg); 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; } -- cgit v1.2.3 From 2f925b37f04f4d5a54b92f8b5f481618f6866ffa Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Mon, 10 Feb 2020 14:09:52 +0100 Subject: reset: stm32: migrate restart reason and handler from stm32_iwdg The watchdog driver will remain unprobed if the driver or the OF node is disabled, but the reset reason is useful even then. System reset and reset source determination is achieved with the RCC peripheral for which we have a reset controller driver. Move the code over there, so reset reason and reset are available always. Signed-off-by: Ahmad Fatoum Signed-off-by: Sascha Hauer --- drivers/reset/reset-stm32.c | 117 ++++++++++++++++++++++++++++++++++++++++-- drivers/watchdog/stm32_iwdg.c | 99 ----------------------------------- 2 files changed, 113 insertions(+), 103 deletions(-) (limited to 'drivers') 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 #include #include +#include +#include #include #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/watchdog/stm32_iwdg.c b/drivers/watchdog/stm32_iwdg.c index 4cd36a67fa..9e38f1a669 100644 --- a/drivers/watchdog/stm32_iwdg.c +++ b/drivers/watchdog/stm32_iwdg.c @@ -6,14 +6,11 @@ #include #include #include -#include #include #include #include #include #include -#include -#include /* 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_to_string(type), 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; } -- cgit v1.2.3 From 4695b7bbba2b19011fee0c34236234c7cefde323 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Thu, 20 Feb 2020 11:01:06 +0100 Subject: regulator: import Linux regulator_bulk API Linux v5.6-rc1 contains 168 references to regultor_bulk_get, which allows getting multiple regulators to set at once. Instead of open coding them when porting code, port over the helpers to barebox. Signed-off-by: Ahmad Fatoum Signed-off-by: Sascha Hauer --- drivers/regulator/core.c | 139 +++++++++++++++++++++++++++++++++++++++++++++++ include/regulator.h | 49 +++++++++++++++++ 2 files changed, 188 insertions(+) (limited to 'drivers') 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/include/regulator.h b/include/regulator.h index d01535df52..dfa808d662 100644 --- a/include/regulator.h +++ b/include/regulator.h @@ -5,6 +5,23 @@ /* struct regulator is an opaque object for consumers */ struct regulator; +/** + * struct regulator_bulk_data - Data used for bulk regulator operations. + * + * @supply: The name of the supply. Initialised by the user before + * using the bulk regulator APIs. + * @consumer: The regulator consumer for the supply. This will be managed + * by the bulk API. + * + * The regulator APIs provide a series of regulator_bulk_() API calls as + * a convenience to consumers which require multiple supplies. This + * structure is used to manage data for these calls. + */ +struct regulator_bulk_data { + const char *supply; + struct regulator *consumer; +}; + /** * struct regulator_desc - Static regulator descriptor * @@ -136,6 +153,14 @@ int regulator_list_voltage_linear_range(struct regulator_dev *rdev, int regulator_get_voltage_sel_regmap(struct regulator_dev *rdev); int regulator_map_voltage_iterate(struct regulator_dev *rdev, int min_uV, int max_uV); +int regulator_bulk_get(struct device_d *dev, int num_consumers, + struct regulator_bulk_data *consumers); +int regulator_bulk_enable(int num_consumers, + struct regulator_bulk_data *consumers); +int regulator_bulk_disable(int num_consumers, + struct regulator_bulk_data *consumers); +void regulator_bulk_free(int num_consumers, + struct regulator_bulk_data *consumers); /* * Helper functions intended to be used by regulator drivers prior registering @@ -166,6 +191,30 @@ static inline int regulator_set_voltage(struct regulator *regulator, return 0; } +static inline int regulator_bulk_get(struct device_d *dev, int num_consumers, + struct regulator_bulk_data *consumers) +{ + return 0; +} + +static inline int regulator_bulk_enable(int num_consumers, + struct regulator_bulk_data *consumers) +{ + return 0; +} + +static inline int regulator_bulk_disable(int num_consumers, + struct regulator_bulk_data *consumers) +{ + return 0; +} + +static inline void regulator_bulk_free(int num_consumers, + struct regulator_bulk_data *consumers) +{ + return 0; +} + #endif #endif /* __REGULATOR_H */ -- cgit v1.2.3 From fa920fa5717b7b7e29cfc8eeb78c5057d860a3e6 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Thu, 20 Feb 2020 11:01:07 +0100 Subject: phy: remove unused init_data parameter Linux has since migrated to a new lookup API that lacks the init_data parameter. As it's unused in barebox, follow suit. Signed-off-by: Ahmad Fatoum Signed-off-by: Sascha Hauer --- drivers/phy/freescale/phy-fsl-imx8mq-usb.c | 2 +- drivers/phy/phy-core.c | 5 +---- drivers/phy/usb-nop-xceiv.c | 2 +- drivers/pinctrl/pinctrl-tegra-xusb.c | 4 ++-- drivers/usb/imx/imx-usb-phy.c | 2 +- include/linux/phy/phy.h | 8 ++------ 6 files changed, 8 insertions(+), 15 deletions(-) (limited to 'drivers') 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..d1f3c7fde4 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,6 @@ 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; ret = register_device(&phy->dev); if (ret) 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/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/include/linux/phy/phy.h b/include/linux/phy/phy.h index 5d96e02df4..df480d4634 100644 --- a/include/linux/phy/phy.h +++ b/include/linux/phy/phy.h @@ -49,7 +49,6 @@ struct phy_attrs { * @dev: phy device * @id: id of the phy device * @ops: function pointers for performing phy operations - * @init_data: list of PHY consumers (non-dt only) * @mutex: mutex to protect phy_ops * @init_count: used to protect when the PHY is used by multiple consumers * @power_count: used to protect when the PHY is used by multiple consumers @@ -59,7 +58,6 @@ struct phy { struct device_d dev; int id; const struct phy_ops *ops; - struct phy_init_data *init_data; int init_count; int power_count; struct phy_attrs attrs; @@ -144,8 +142,7 @@ struct phy *of_phy_get(struct device_node *np, const char *con_id); struct phy *of_phy_simple_xlate(struct device_d *dev, struct of_phandle_args *args); 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); void phy_destroy(struct phy *phy); struct phy_provider *__of_phy_provider_register(struct device_d *dev, struct phy * (*of_xlate)(struct device_d *dev, @@ -225,8 +222,7 @@ static inline struct phy *of_phy_simple_xlate(struct device_d *dev, static inline 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) { return ERR_PTR(-ENOSYS); } -- cgit v1.2.3 From 45f82cde7cc1b85adbf6214452374b60d177e9e1 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Thu, 20 Feb 2020 11:01:08 +0100 Subject: phy: populate existing ->pwr member with phy-supply barebox already enables/disables the ->pwr regulator at the correct places, but doesn't assign a value anywhere. Initialize it with the phy-supply regulator like Linux does. Signed-off-by: Ahmad Fatoum Signed-off-by: Sascha Hauer --- drivers/phy/phy-core.c | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'drivers') diff --git a/drivers/phy/phy-core.c b/drivers/phy/phy-core.c index d1f3c7fde4..ad1e814788 100644 --- a/drivers/phy/phy-core.c +++ b/drivers/phy/phy-core.c @@ -51,6 +51,16 @@ struct phy *phy_create(struct device_d *dev, struct device_node *node, phy->id = id; phy->ops = ops; + /* 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) goto free_ida; -- cgit v1.2.3 From c4d7a0118e2b58eccc93267525105e368cb4e8f6 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Thu, 20 Feb 2020 11:01:09 +0100 Subject: phy: introduce phy_get_by_index The upstream (v5.6-rc1) device tree node of the stm32mp157c-dk2's OHCI has a phys property, but not phy-names. We have no API to reference such a phy easily (passing NULL isn't allowed). Add one. Signed-off-by: Ahmad Fatoum Signed-off-by: Sascha Hauer --- drivers/phy/phy-core.c | 14 ++++++++++++++ include/linux/phy/phy.h | 6 ++++++ 2 files changed, 20 insertions(+) (limited to 'drivers') diff --git a/drivers/phy/phy-core.c b/drivers/phy/phy-core.c index ad1e814788..ff6e35d160 100644 --- a/drivers/phy/phy-core.c +++ b/drivers/phy/phy-core.c @@ -371,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/include/linux/phy/phy.h b/include/linux/phy/phy.h index df480d4634..8a28b8e068 100644 --- a/include/linux/phy/phy.h +++ b/include/linux/phy/phy.h @@ -149,6 +149,7 @@ struct phy_provider *__of_phy_provider_register(struct device_d *dev, struct of_phandle_args *args)); void of_phy_provider_unregister(struct phy_provider *phy_provider); struct usb_phy *phy_to_usbphy(struct phy *phy); +struct phy *phy_get_by_index(struct device_d *dev, int index); #else static inline int phy_init(struct phy *phy) { @@ -247,6 +248,11 @@ static inline struct usb_phy *phy_to_usbphy(struct phy *phy) return NULL; } +static struct phy *phy_get_by_index(struct device_d *dev, int index) +{ + return ERR_PTR(-ENODEV); +} + #endif #endif /* __DRIVERS_PHY_H */ -- cgit v1.2.3 From 3477974f9eb21d8a3ecfb330af3b5ef0c08acee2 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Thu, 20 Feb 2020 11:01:10 +0100 Subject: regulator: port over Linux stm32 PWR regulator driver This driver supports internal regulators (1V1, 1V8, 3V3) in the STMicroelectronics STM32 chips. Control of these regulators will be required when adding USB support later on. Imported here is the Linux v5.6-rc1 state of the driver. Signed-off-by: Ahmad Fatoum Signed-off-by: Sascha Hauer --- drivers/regulator/Kconfig | 7 ++ drivers/regulator/Makefile | 1 + drivers/regulator/stm32-pwr.c | 215 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 223 insertions(+) create mode 100644 drivers/regulator/stm32-pwr.c (limited to 'drivers') 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/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 +// Pascal Paillet . + +#include +#include +#include +#include +#include +#include + +/* + * 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 "); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From a2d10b46f39e36c0e19bfde0737e27d2b0bf43ba Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Thu, 20 Feb 2020 11:01:11 +0100 Subject: phy: port over Linux stm32 usbphyc driver This ports over the Linux v5.6-rc1 state of the driver. It'll be needed for both EHCI and DWC2 usb connectivity on the stm32mp1. Signed-off-by: Ahmad Fatoum Signed-off-by: Sascha Hauer --- drivers/phy/Kconfig | 13 ++ drivers/phy/Makefile | 1 + drivers/phy/phy-stm32-usbphyc.c | 434 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 448 insertions(+) create mode 100644 drivers/phy/phy-stm32-usbphyc.c (limited to 'drivers') 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/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 . + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 "); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3