diff options
author | Leonard Crestez <leonard.crestez@nxp.com> | 2018-05-24 15:27:21 +0300 |
---|---|---|
committer | Jason Liu <jason.hui.liu@nxp.com> | 2019-02-12 10:31:51 +0800 |
commit | 10d46dad7c28ac9ca13d6abb7239312bb1ce811e (patch) | |
tree | 140eeebd83d76e907bbda78afc38a9ec30656443 /drivers/soc | |
parent | 70eedd753a03b7322c96aea9d658e805853b5b2c (diff) |
MLK-18181-2 ARM: imx: Move gpcv2 regulator notifiers to drivers/soc
The vendor tree does imx7 PGC management through regulator notifiers
while upstream implemented the same features using power domains. These
two drivers have entirely different interfaces with higher-level IP
blocks.
Resolve this conflict by moving the old code to drivers/soc and
supporting both power-domain and regulator interfaces. This effectively
merges the two drivers and is similar to how imx6sx implements both
power domains and a regulator notifier for pcie specifically.
Supporting both interfaces allows consumes to switch one-by-one, for
example by having PCI work with a power-domains reference while usb hsic
still uses the regulator enable/disable interface.
Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com>
Diffstat (limited to 'drivers/soc')
-rw-r--r-- | drivers/soc/imx/gpcv2.c | 205 |
1 files changed, 204 insertions, 1 deletions
diff --git a/drivers/soc/imx/gpcv2.c b/drivers/soc/imx/gpcv2.c index 4f8bb69b25eb..5373eba6be85 100644 --- a/drivers/soc/imx/gpcv2.c +++ b/drivers/soc/imx/gpcv2.c @@ -18,6 +18,7 @@ #include <linux/pm_domain.h> #include <linux/regmap.h> #include <linux/regulator/consumer.h> +#include <linux/io.h> #include <dt-bindings/power/imx7-power.h> #define GPC_LPCR_A7_BSC 0x000 @@ -259,6 +260,207 @@ static struct platform_driver imx7_pgc_domain_driver = { }; builtin_platform_driver(imx7_pgc_domain_driver) +/* Some drivers rely on regulator notifiers to switch pgc on/off */ + +#define GPC_PGC_MIPI_PHY 0xc00 +#define GPC_PGC_PCIE_PHY 0xc40 +#define GPC_PGC_USB_OTG1_PHY 0xc80 +#define GPC_PGC_USB_OTG2_PHY 0xcc0 +#define GPC_PGC_USB_HSIC_PHY 0xd00 + +#define BM_GPC_PGC_PCG 0x1 + +static void __iomem *gpc_base; +static struct notifier_block nb_mipi, nb_pcie, nb_usb_hsic; + +static void imx_gpcv2_set_m_core_pgc(bool enable, u32 offset) +{ + u32 val = readl_relaxed(gpc_base + offset) & (~BM_GPC_PGC_PCG); + + if (enable) + val |= BM_GPC_PGC_PCG; + + writel_relaxed(val, gpc_base + offset); +} + +static int imx_mipi_regulator_notify(struct notifier_block *nb, + unsigned long event, + void *ignored) +{ + u32 val = 0; + + val = readl_relaxed(gpc_base + GPC_PGC_CPU_MAPPING); + writel_relaxed(val | BIT(2), gpc_base + GPC_PGC_CPU_MAPPING); + + switch (event) { + case REGULATOR_EVENT_AFT_DO_ENABLE: + /* + * For imx7d pcie phy, VDD18 turn on time has to wait + * at least 0.1 .s after VDD10 turns on. + */ + udelay(1); + val = readl_relaxed(gpc_base + GPC_PU_PGC_SW_PUP_REQ); + writel_relaxed(val | BIT(0), gpc_base + GPC_PU_PGC_SW_PUP_REQ); + while (readl_relaxed(gpc_base + GPC_PU_PGC_SW_PUP_REQ) & BIT(0)) + ; + break; + case REGULATOR_EVENT_PRE_DO_DISABLE: + /* only disable phy need to set PGC bit, enable does NOT need */ + imx_gpcv2_set_m_core_pgc(true, GPC_PGC_MIPI_PHY); + val = readl_relaxed(gpc_base + GPC_PU_PGC_SW_PDN_REQ); + writel_relaxed(val | BIT(0), gpc_base + GPC_PU_PGC_SW_PDN_REQ); + while (readl_relaxed(gpc_base + GPC_PU_PGC_SW_PDN_REQ) & BIT(0)) + ; + imx_gpcv2_set_m_core_pgc(false, GPC_PGC_MIPI_PHY); + /* + * For imx7d pcie phy, VDD18 turn off time has to advance + * at least 0.1 .s before VDD10 turns off. + */ + udelay(1); + break; + default: + break; + } + + val = readl_relaxed(gpc_base + GPC_PGC_CPU_MAPPING); + writel_relaxed(val & ~BIT(2), gpc_base + GPC_PGC_CPU_MAPPING); + + return NOTIFY_OK; +} + +static int imx_pcie_regulator_notify(struct notifier_block *nb, + unsigned long event, + void *ignored) +{ + u32 val = 0; + + val = readl_relaxed(gpc_base + GPC_PGC_CPU_MAPPING); + writel_relaxed(val | BIT(3), gpc_base + GPC_PGC_CPU_MAPPING); + + switch (event) { + case REGULATOR_EVENT_AFT_DO_ENABLE: + /* + * For imx7d pcie phy, VDD18 turn on time has to wait + * at least 0.1 .s after VDD10 turns on. + */ + udelay(1); + val = readl_relaxed(gpc_base + GPC_PU_PGC_SW_PUP_REQ); + writel_relaxed(val | BIT(1), gpc_base + GPC_PU_PGC_SW_PUP_REQ); + while (readl_relaxed(gpc_base + GPC_PU_PGC_SW_PUP_REQ) & BIT(1)) + ; + break; + case REGULATOR_EVENT_PRE_DO_DISABLE: + /* only disable phy need to set PGC bit, enable does NOT need */ + imx_gpcv2_set_m_core_pgc(true, GPC_PGC_PCIE_PHY); + val = readl_relaxed(gpc_base + GPC_PU_PGC_SW_PDN_REQ); + writel_relaxed(val | BIT(1), gpc_base + GPC_PU_PGC_SW_PDN_REQ); + while (readl_relaxed(gpc_base + GPC_PU_PGC_SW_PDN_REQ) & BIT(1)) + ; + imx_gpcv2_set_m_core_pgc(false, GPC_PGC_PCIE_PHY); + /* + * For imx7d pcie phy, VDD18 turn off time has to advance + * at least 0.1 .s before VDD10 turns off. + */ + udelay(1); + break; + default: + break; + } + + val = readl_relaxed(gpc_base + GPC_PGC_CPU_MAPPING); + writel_relaxed(val & ~BIT(3), gpc_base + GPC_PGC_CPU_MAPPING); + + return NOTIFY_OK; +} + +static int imx_usb_hsic_regulator_notify(struct notifier_block *nb, + unsigned long event, + void *ignored) +{ + u32 val = 0; + + val = readl_relaxed(gpc_base + GPC_PGC_CPU_MAPPING); + writel_relaxed(val | BIT(6), gpc_base + GPC_PGC_CPU_MAPPING); + + switch (event) { + case REGULATOR_EVENT_PRE_DO_ENABLE: + val = readl_relaxed(gpc_base + GPC_PU_PGC_SW_PUP_REQ); + writel_relaxed(val | BIT(4), gpc_base + GPC_PU_PGC_SW_PUP_REQ); + while (readl_relaxed(gpc_base + GPC_PU_PGC_SW_PUP_REQ) & BIT(4)) + ; + break; + case REGULATOR_EVENT_PRE_DO_DISABLE: + /* only disable phy need to set PGC bit, enable does NOT need */ + imx_gpcv2_set_m_core_pgc(true, GPC_PGC_USB_HSIC_PHY); + val = readl_relaxed(gpc_base + GPC_PU_PGC_SW_PDN_REQ); + writel_relaxed(val | BIT(4), gpc_base + GPC_PU_PGC_SW_PDN_REQ); + while (readl_relaxed(gpc_base + GPC_PU_PGC_SW_PDN_REQ) & BIT(4)) + ; + imx_gpcv2_set_m_core_pgc(false, GPC_PGC_USB_HSIC_PHY); + break; + default: + break; + } + + val = readl_relaxed(gpc_base + GPC_PGC_CPU_MAPPING); + writel_relaxed(val & ~BIT(6), gpc_base + GPC_PGC_CPU_MAPPING); + + return NOTIFY_OK; +} + +static int imx_gpcv2_init_regnb(struct platform_device *pdev) +{ + int ret; + struct regulator *mipi_reg, *pcie_reg, *usb_hsic_reg; + + mipi_reg = devm_regulator_get(&pdev->dev, "mipi-phy"); + if (IS_ERR(mipi_reg)) { + ret = PTR_ERR(mipi_reg); + dev_info(&pdev->dev, "mipi regulator not ready.\n"); + return ret; + } + nb_mipi.notifier_call = &imx_mipi_regulator_notify; + + ret = regulator_register_notifier(mipi_reg, &nb_mipi); + if (ret) { + dev_err(&pdev->dev, + "mipi regulator notifier request failed.\n"); + return ret; + } + + pcie_reg = devm_regulator_get(&pdev->dev, "pcie-phy"); + if (IS_ERR(pcie_reg)) { + ret = PTR_ERR(pcie_reg); + dev_info(&pdev->dev, "pcie regulator not ready.\n"); + return ret; + } + nb_pcie.notifier_call = &imx_pcie_regulator_notify; + + ret = regulator_register_notifier(pcie_reg, &nb_pcie); + if (ret) { + dev_err(&pdev->dev, + "pcie regulator notifier request failed\n"); + return ret; + } + + usb_hsic_reg = devm_regulator_get(&pdev->dev, "vcc"); + if (IS_ERR(usb_hsic_reg)) { + ret = PTR_ERR(usb_hsic_reg); + dev_err(&pdev->dev, "usb hsic regulator not ready.\n"); + return ret; + } + nb_usb_hsic.notifier_call = &imx_usb_hsic_regulator_notify; + + ret = regulator_register_notifier(usb_hsic_reg, &nb_usb_hsic); + if (ret) { + dev_err(&pdev->dev, + "usb hsic regulator notifier request failed\n"); + return ret; + } + + return 0; +} + static int imx_gpcv2_probe(struct platform_device *pdev) { static const struct regmap_range yes_ranges[] = { @@ -300,6 +502,7 @@ static int imx_gpcv2_probe(struct platform_device *pdev) base = devm_ioremap_resource(dev, res); if (IS_ERR(base)) return PTR_ERR(base); + gpc_base = base; regmap = devm_regmap_init_mmio(dev, base, ®map_config); if (IS_ERR(regmap)) { @@ -360,7 +563,7 @@ static int imx_gpcv2_probe(struct platform_device *pdev) } } - return 0; + return imx_gpcv2_init_regnb(pdev); } static const struct of_device_id imx_gpcv2_dt_ids[] = { |