// SPDX-License-Identifier: GPL-2.0+ /* * Power domain driver for Broadcom BCM2835 * * Copyright (C) 2018 Broadcom */ #include #include #include #include #include #include #include #include #include #include #define PM_GNRIC 0x00 #define PM_AUDIO 0x04 #define PM_STATUS 0x18 #define PM_RSTC 0x1c #define PM_RSTS 0x20 #define PM_WDOG 0x24 #define PM_PADS0 0x28 #define PM_PADS2 0x2c #define PM_PADS3 0x30 #define PM_PADS4 0x34 #define PM_PADS5 0x38 #define PM_PADS6 0x3c #define PM_CAM0 0x44 #define PM_CAM0_LDOHPEN BIT(2) #define PM_CAM0_LDOLPEN BIT(1) #define PM_CAM0_CTRLEN BIT(0) #define PM_CAM1 0x48 #define PM_CAM1_LDOHPEN BIT(2) #define PM_CAM1_LDOLPEN BIT(1) #define PM_CAM1_CTRLEN BIT(0) #define PM_CCP2TX 0x4c #define PM_CCP2TX_LDOEN BIT(1) #define PM_CCP2TX_CTRLEN BIT(0) #define PM_DSI0 0x50 #define PM_DSI0_LDOHPEN BIT(2) #define PM_DSI0_LDOLPEN BIT(1) #define PM_DSI0_CTRLEN BIT(0) #define PM_DSI1 0x54 #define PM_DSI1_LDOHPEN BIT(2) #define PM_DSI1_LDOLPEN BIT(1) #define PM_DSI1_CTRLEN BIT(0) #define PM_HDMI 0x58 #define PM_HDMI_RSTDR BIT(19) #define PM_HDMI_LDOPD BIT(1) #define PM_HDMI_CTRLEN BIT(0) #define PM_USB 0x5c /* The power gates must be enabled with this bit before enabling the LDO in the * USB block. */ #define PM_USB_CTRLEN BIT(0) #define PM_PXLDO 0x60 #define PM_PXBG 0x64 #define PM_DFT 0x68 #define PM_SMPS 0x6c #define PM_XOSC 0x70 #define PM_SPAREW 0x74 #define PM_SPARER 0x78 #define PM_AVS_RSTDR 0x7c #define PM_AVS_STAT 0x80 #define PM_AVS_EVENT 0x84 #define PM_AVS_INTEN 0x88 #define PM_DUMMY 0xfc #define PM_IMAGE 0x108 #define PM_GRAFX 0x10c #define PM_PROC 0x110 #define PM_ENAB BIT(12) #define PM_ISPRSTN BIT(8) #define PM_H264RSTN BIT(7) #define PM_PERIRSTN BIT(6) #define PM_V3DRSTN BIT(6) #define PM_ISFUNC BIT(5) #define PM_MRDONE BIT(4) #define PM_MEMREP BIT(3) #define PM_ISPOW BIT(2) #define PM_POWOK BIT(1) #define PM_POWUP BIT(0) #define PM_INRUSH_SHIFT 13 #define PM_INRUSH_3_5_MA 0 #define PM_INRUSH_5_MA 1 #define PM_INRUSH_10_MA 2 #define PM_INRUSH_20_MA 3 #define PM_INRUSH_MASK (3 << PM_INRUSH_SHIFT) #define PM_PASSWORD 0x5a000000 #define PM_WDOG_TIME_SET 0x000fffff #define PM_RSTC_WRCFG_CLR 0xffffffcf #define PM_RSTS_HADWRH_SET 0x00000040 #define PM_RSTC_WRCFG_SET 0x00000030 #define PM_RSTC_WRCFG_FULL_RESET 0x00000020 #define PM_RSTC_RESET 0x00000102 #define PM_READ(reg) readl(power->base + (reg)) #define PM_WRITE(reg, val) writel(PM_PASSWORD | (val), power->base + (reg)) #define ASB_BRDG_VERSION 0x00 #define ASB_CPR_CTRL 0x04 #define ASB_V3D_S_CTRL 0x08 #define ASB_V3D_M_CTRL 0x0c #define ASB_ISP_S_CTRL 0x10 #define ASB_ISP_M_CTRL 0x14 #define ASB_H264_S_CTRL 0x18 #define ASB_H264_M_CTRL 0x1c #define ASB_REQ_STOP BIT(0) #define ASB_ACK BIT(1) #define ASB_EMPTY BIT(2) #define ASB_FULL BIT(3) #define ASB_AXI_BRDG_ID 0x20 #define ASB_READ(reg) readl(power->asb + (reg)) #define ASB_WRITE(reg, val) writel(PM_PASSWORD | (val), power->asb + (reg)) struct bcm2835_power_domain { struct generic_pm_domain base; struct bcm2835_power *power; u32 domain; struct clk *clk; }; struct bcm2835_power { struct device *dev; /* PM registers. */ void __iomem *base; /* AXI Async bridge registers. */ void __iomem *asb; struct genpd_onecell_data pd_xlate; struct bcm2835_power_domain domains[BCM2835_POWER_DOMAIN_COUNT]; struct reset_controller_dev reset; }; static int bcm2835_asb_enable(struct bcm2835_power *power, u32 reg) { u64 start; if (!reg) return 0; start = ktime_get_ns(); /* Enable the module's async AXI bridges. */ ASB_WRITE(reg, ASB_READ(reg) & ~ASB_REQ_STOP); while (ASB_READ(reg) & ASB_ACK) { cpu_relax(); if (ktime_get_ns() - start >= 1000) return -ETIMEDOUT; } return 0; } static int bcm2835_asb_disable(struct bcm2835_power *power, u32 reg) { u64 start; if (!reg) return 0; start = ktime_get_ns(); /* Enable the module's async AXI bridges. */ ASB_WRITE(reg, ASB_READ(reg) | ASB_REQ_STOP); while (!(ASB_READ(reg) & ASB_ACK)) { cpu_relax(); if (ktime_get_ns() - start >= 1000) return -ETIMEDOUT; } return 0; } static int bcm2835_power_power_off(struct bcm2835_power_domain *pd, u32 pm_reg) { struct bcm2835_power *power = pd->power; /* Enable functional isolation */ PM_WRITE(pm_reg, PM_READ(pm_reg) & ~PM_ISFUNC); /* Enable electrical isolation */ PM_WRITE(pm_reg, PM_READ(pm_reg) & ~PM_ISPOW); /* Open the power switches. */ PM_WRITE(pm_reg, PM_READ(pm_reg) & ~PM_POWUP); return 0; } static int bcm2835_power_power_on(struct bcm2835_power_domain *pd, u32 pm_reg) { struct bcm2835_power *power = pd->power; struct device *dev = power->dev; u64 start; int ret; int inrush; bool powok; /* If it was already powered on by the fw, leave it that way. */ if (PM_READ(pm_reg) & PM_POWUP) return 0; /* Enable power. Allowing too much current at once may result * in POWOK never getting set, so start low and ramp it up as * necessary to succeed. */ powok = false; for (inrush = PM_INRUSH_3_5_MA; inrush <= PM_INRUSH_20_MA; inrush++) { PM_WRITE(pm_reg, (PM_READ(pm_reg) & ~PM_INRUSH_MASK) | (inrush << PM_INRUSH_SHIFT) | PM_POWUP); start = ktime_get_ns(); while (!(powok = !!(PM_READ(pm_reg) & PM_POWOK))) { cpu_relax(); if (ktime_get_ns() - start >= 3000) break; } } if (!powok) { dev_err(dev, "Timeout waiting for %s power OK\n", pd->base.name); ret = -ETIMEDOUT; goto err_disable_powup; } /* Disable electrical isolation */ PM_WRITE(pm_reg, PM_READ(pm_reg) | PM_ISPOW); /* Repair memory */ PM_WRITE(pm_reg, PM_READ(pm_reg) | PM_MEMREP); start = ktime_get_ns(); while (!(PM_READ(pm_reg) & PM_MRDONE)) { cpu_relax(); if (ktime_get_ns() - start >= 1000) { dev_err(dev, "Timeout waiting for %s memory repair\n", pd->base.name); ret = -ETIMEDOUT; goto err_disable_ispow; } } /* Disable functional isolation */ PM_WRITE(pm_reg, PM_READ(pm_reg) | PM_ISFUNC); return 0; err_disable_ispow: PM_WRITE(pm_reg, PM_READ(pm_reg) & ~PM_ISPOW); err_disable_powup: PM_WRITE(pm_reg, PM_READ(pm_reg) & ~(PM_POWUP | PM_INRUSH_MASK)); return ret; } static int bcm2835_asb_power_on(struct bcm2835_power_domain *pd, u32 pm_reg, u32 asb_m_reg, u32 asb_s_reg, u32 reset_flags) { struct bcm2835_power *power = pd->power; int ret; ret = clk_prepare_enable(pd->clk); if (ret) { dev_err(power->dev, "Failed to enable clock for %s\n", pd->base.name); return ret; } /* Wait 32 clocks for reset to propagate, 1 us will be enough */ udelay(1); clk_disable_unprepare(pd->clk); /* Deassert the resets. */ PM_WRITE(pm_reg, PM_READ(pm_reg) | reset_flags); ret = clk_prepare_enable(pd->clk); if (ret) { dev_err(power->dev, "Failed to enable clock for %s\n", pd->base.name); goto err_enable_resets; } ret = bcm2835_asb_enable(power, asb_m_reg); if (ret) { dev_err(power->dev, "Failed to enable ASB master for %s\n", pd->base.name); goto err_disable_clk; } ret = bcm2835_asb_enable(power, asb_s_reg); if (ret) { dev_err(power->dev, "Failed to enable ASB slave for %s\n", pd->base.name); goto err_disable_asb_master; } return 0; err_disable_asb_master: bcm2835_asb_disable(power, asb_m_reg); err_disable_clk: clk_disable_unprepare(pd->clk); err_enable_resets: PM_WRITE(pm_reg, PM_READ(pm_reg) & ~reset_flags); return ret; } static int bcm2835_asb_power_off(struct bcm2835_power_domain *pd, u32 pm_reg, u32 asb_m_reg, u32 asb_s_reg, u32 reset_flags) { struct bcm2835_power *power = pd->power; int ret; ret = bcm2835_asb_disable(power, asb_s_reg); if (ret) { dev_warn(power->dev, "Failed to disable ASB slave for %s\n", pd->base.name); return ret; } ret = bcm2835_asb_disable(power, asb_m_reg); if (ret) { dev_warn(power->dev, "Failed to disable ASB master for %s\n", pd->base.name); bcm2835_asb_enable(power, asb_s_reg); return ret; } clk_disable_unprepare(pd->clk); /* Assert the resets. */ PM_WRITE(pm_reg, PM_READ(pm_reg) & ~reset_flags); return 0; } static int bcm2835_power_pd_power_on(struct generic_pm_domain *domain) { struct bcm2835_power_domain *pd = container_of(domain, struct bcm2835_power_domain, base); struct bcm2835_power *power = pd->power; switch (pd->domain) { case BCM2835_POWER_DOMAIN_GRAFX: return bcm2835_power_power_on(pd, PM_GRAFX); case BCM2835_POWER_DOMAIN_GRAFX_V3D: return bcm2835_asb_power_on(pd, PM_GRAFX, ASB_V3D_M_CTRL, ASB_V3D_S_CTRL, PM_V3DRSTN); case BCM2835_POWER_DOMAIN_IMAGE: return bcm2835_power_power_on(pd, PM_IMAGE); case BCM2835_POWER_DOMAIN_IMAGE_PERI: return bcm2835_asb_power_on(pd, PM_IMAGE, 0, 0, PM_PERIRSTN); case BCM2835_POWER_DOMAIN_IMAGE_ISP: return bcm2835_asb_power_on(pd, PM_IMAGE, ASB_ISP_M_CTRL, ASB_ISP_S_CTRL, PM_ISPRSTN); case BCM2835_POWER_DOMAIN_IMAGE_H264: return bcm2835_asb_power_on(pd, PM_IMAGE, ASB_H264_M_CTRL, ASB_H264_S_CTRL, PM_H264RSTN); case BCM2835_POWER_DOMAIN_USB: PM_WRITE(PM_USB, PM_USB_CTRLEN); return 0; case BCM2835_POWER_DOMAIN_DSI0: PM_WRITE(PM_DSI0, PM_DSI0_CTRLEN); PM_WRITE(PM_DSI0, PM_DSI0_CTRLEN | PM_DSI0_LDOHPEN); return 0; case BCM2835_POWER_DOMAIN_DSI1: PM_WRITE(PM_DSI1, PM_DSI1_CTRLEN); PM_WRITE(PM_DSI1, PM_DSI1_CTRLEN | PM_DSI1_LDOHPEN); return 0; case BCM2835_POWER_DOMAIN_CCP2TX: PM_WRITE(PM_CCP2TX, PM_CCP2TX_CTRLEN); PM_WRITE(PM_CCP2TX, PM_CCP2TX_CTRLEN | PM_CCP2TX_LDOEN); return 0; case BCM2835_POWER_DOMAIN_HDMI: PM_WRITE(PM_HDMI, PM_READ(PM_HDMI) | PM_HDMI_RSTDR); PM_WRITE(PM_HDMI, PM_READ(PM_HDMI) | PM_HDMI_CTRLEN); PM_WRITE(PM_HDMI, PM_READ(PM_HDMI) & ~PM_HDMI_LDOPD); usleep_range(100, 200); PM_WRITE(PM_HDMI, PM_READ(PM_HDMI) & ~PM_HDMI_RSTDR); return 0; default: dev_err(power->dev, "Invalid domain %d\n", pd->domain); return -EINVAL; } } static int bcm2835_power_pd_power_off(struct generic_pm_domain *domain) { struct bcm2835_power_domain *pd = container_of(domain, struct bcm2835_power_domain, base); struct bcm2835_power *power = pd->power; switch (pd->domain) { case BCM2835_POWER_DOMAIN_GRAFX: return bcm2835_power_power_off(pd, PM_GRAFX); case BCM2835_POWER_DOMAIN_GRAFX_V3D: return bcm2835_asb_power_off(pd, PM_GRAFX, ASB_V3D_M_CTRL, ASB_V3D_S_CTRL, PM_V3DRSTN); case BCM2835_POWER_DOMAIN_IMAGE: return bcm2835_power_power_off(pd, PM_IMAGE); case BCM2835_POWER_DOMAIN_IMAGE_PERI: return bcm2835_asb_power_off(pd, PM_IMAGE, 0, 0, PM_PERIRSTN); case BCM2835_POWER_DOMAIN_IMAGE_ISP: return bcm2835_asb_power_off(pd, PM_IMAGE, ASB_ISP_M_CTRL, ASB_ISP_S_CTRL, PM_ISPRSTN); case BCM2835_POWER_DOMAIN_IMAGE_H264: return bcm2835_asb_power_off(pd, PM_IMAGE, ASB_H264_M_CTRL, ASB_H264_S_CTRL, PM_H264RSTN); case BCM2835_POWER_DOMAIN_USB: PM_WRITE(PM_USB, 0); return 0; case BCM2835_POWER_DOMAIN_DSI0: PM_WRITE(PM_DSI0, PM_DSI0_CTRLEN); PM_WRITE(PM_DSI0, 0); return 0; case BCM2835_POWER_DOMAIN_DSI1: PM_WRITE(PM_DSI1, PM_DSI1_CTRLEN); PM_WRITE(PM_DSI1, 0); return 0; case BCM2835_POWER_DOMAIN_CCP2TX: PM_WRITE(PM_CCP2TX, PM_CCP2TX_CTRLEN); PM_WRITE(PM_CCP2TX, 0); return 0; case BCM2835_POWER_DOMAIN_HDMI: PM_WRITE(PM_HDMI, PM_READ(PM_HDMI) | PM_HDMI_LDOPD); PM_WRITE(PM_HDMI, PM_READ(PM_HDMI) & ~PM_HDMI_CTRLEN); return 0; default: dev_err(power->dev, "Invalid domain %d\n", pd->domain); return -EINVAL; } } static int bcm2835_init_power_domain(struct bcm2835_power *power, int pd_xlate_index, const char *name) { struct device *dev = power->dev; struct bcm2835_power_domain *dom = &power->domains[pd_xlate_index]; dom->clk = devm_clk_get(dev->parent, name); if (IS_ERR(dom->clk)) { int ret = PTR_ERR(dom->clk); if (ret == -EPROBE_DEFER) return ret; /* Some domains don't have a clk, so make sure that we * don't deref an error pointer later. */ dom->clk = NULL; } dom->base.name = name; dom->base.power_on = bcm2835_power_pd_power_on; dom->base.power_off = bcm2835_power_pd_power_off; dom->domain = pd_xlate_index; dom->power = power; /* XXX: on/off at boot? */ pm_genpd_init(&dom->base, NULL, true); power->pd_xlate.domains[pd_xlate_index] = &dom->base; return 0; } /** bcm2835_reset_reset - Resets a block that has a reset line in the * PM block. * * The consumer of the reset controller must have the power domain up * -- there's no reset ability with the power domain down. To reset * the sub-block, we just disable its access to memory through the * ASB, reset, and re-enable. */ static int bcm2835_reset_reset(struct reset_controller_dev *rcdev, unsigned long id) { struct bcm2835_power *power = container_of(rcdev, struct bcm2835_power, reset); struct bcm2835_power_domain *pd; int ret; switch (id) { case BCM2835_RESET_V3D: pd = &power->domains[BCM2835_POWER_DOMAIN_GRAFX_V3D]; break; case BCM2835_RESET_H264: pd = &power->domains[BCM2835_POWER_DOMAIN_IMAGE_H264]; break; case BCM2835_RESET_ISP: pd = &power->domains[BCM2835_POWER_DOMAIN_IMAGE_ISP]; break; default: dev_err(power->dev, "Bad reset id %ld\n", id); return -EINVAL; } ret = bcm2835_power_pd_power_off(&pd->base); if (ret) return ret; return bcm2835_power_pd_power_on(&pd->base); } static int bcm2835_reset_status(struct reset_controller_dev *rcdev, unsigned long id) { struct bcm2835_power *power = container_of(rcdev, struct bcm2835_power, reset); switch (id) { case BCM2835_RESET_V3D: return !PM_READ(PM_GRAFX & PM_V3DRSTN); case BCM2835_RESET_H264: return !PM_READ(PM_IMAGE & PM_H264RSTN); case BCM2835_RESET_ISP: return !PM_READ(PM_IMAGE & PM_ISPRSTN); default: return -EINVAL; } } static const struct reset_control_ops bcm2835_reset_ops = { .reset = bcm2835_reset_reset, .status = bcm2835_reset_status, }; static const char *const power_domain_names[] = { [BCM2835_POWER_DOMAIN_GRAFX] = "grafx", [BCM2835_POWER_DOMAIN_GRAFX_V3D] = "v3d", [BCM2835_POWER_DOMAIN_IMAGE] = "image", [BCM2835_POWER_DOMAIN_IMAGE_PERI] = "peri_image", [BCM2835_POWER_DOMAIN_IMAGE_H264] = "h264", [BCM2835_POWER_DOMAIN_IMAGE_ISP] = "isp", [BCM2835_POWER_DOMAIN_USB] = "usb", [BCM2835_POWER_DOMAIN_DSI0] = "dsi0", [BCM2835_POWER_DOMAIN_DSI1] = "dsi1", [BCM2835_POWER_DOMAIN_CAM0] = "cam0", [BCM2835_POWER_DOMAIN_CAM1] = "cam1", [BCM2835_POWER_DOMAIN_CCP2TX] = "ccp2tx", [BCM2835_POWER_DOMAIN_HDMI] = "hdmi", }; static int bcm2835_power_probe(struct platform_device *pdev) { struct bcm2835_pm *pm = dev_get_drvdata(pdev->dev.parent); struct device *dev = &pdev->dev; struct bcm2835_power *power; static const struct { int parent, child; } domain_deps[] = { { BCM2835_POWER_DOMAIN_GRAFX, BCM2835_POWER_DOMAIN_GRAFX_V3D }, { BCM2835_POWER_DOMAIN_IMAGE, BCM2835_POWER_DOMAIN_IMAGE_PERI }, { BCM2835_POWER_DOMAIN_IMAGE, BCM2835_POWER_DOMAIN_IMAGE_H264 }, { BCM2835_POWER_DOMAIN_IMAGE, BCM2835_POWER_DOMAIN_IMAGE_ISP }, { BCM2835_POWER_DOMAIN_IMAGE_PERI, BCM2835_POWER_DOMAIN_USB }, { BCM2835_POWER_DOMAIN_IMAGE_PERI, BCM2835_POWER_DOMAIN_CAM0 }, { BCM2835_POWER_DOMAIN_IMAGE_PERI, BCM2835_POWER_DOMAIN_CAM1 }, }; int ret = 0, i; u32 id; power = devm_kzalloc(dev, sizeof(*power), GFP_KERNEL); if (!power) return -ENOMEM; platform_set_drvdata(pdev, power); power->dev = dev; power->base = pm->base; power->asb = pm->asb; id = ASB_READ(ASB_AXI_BRDG_ID); if (id != 0x62726467 /* "BRDG" */) { dev_err(dev, "ASB register ID returned 0x%08x\n", id); return -ENODEV; } power->pd_xlate.domains = devm_kcalloc(dev, ARRAY_SIZE(power_domain_names), sizeof(*power->pd_xlate.domains), GFP_KERNEL); if (!power->pd_xlate.domains) return -ENOMEM; power->pd_xlate.num_domains = ARRAY_SIZE(power_domain_names); for (i = 0; i < ARRAY_SIZE(power_domain_names); i++) { ret = bcm2835_init_power_domain(power, i, power_domain_names[i]); if (ret) goto fail; } for (i = 0; i < ARRAY_SIZE(domain_deps); i++) { pm_genpd_add_subdomain(&power->domains[domain_deps[i].parent].base, &power->domains[domain_deps[i].child].base); } power->reset.owner = THIS_MODULE; power->reset.nr_resets = BCM2835_RESET_COUNT; power->reset.ops = &bcm2835_reset_ops; power->reset.of_node = dev->parent->of_node; ret = devm_reset_controller_register(dev, &power->reset); if (ret) goto fail; of_genpd_add_provider_onecell(dev->parent->of_node, &power->pd_xlate); dev_info(dev, "Broadcom BCM2835 power domains driver"); return 0; fail: for (i = 0; i < ARRAY_SIZE(power_domain_names); i++) { struct generic_pm_domain *dom = &power->domains[i].base; if (dom->name) pm_genpd_remove(dom); } return ret; } static int bcm2835_power_remove(struct platform_device *pdev) { return 0; } static struct platform_driver bcm2835_power_driver = { .probe = bcm2835_power_probe, .remove = bcm2835_power_remove, .driver = { .name = "bcm2835-power", }, }; module_platform_driver(bcm2835_power_driver); MODULE_AUTHOR("Eric Anholt "); MODULE_DESCRIPTION("Driver for Broadcom BCM2835 PM power domains and reset"); MODULE_LICENSE("GPL");