mirror of
https://github.com/openwrt/openwrt.git
synced 2024-12-27 17:18:59 +00:00
7d7aa2fd92
This change makes the names of Broadcom targets consistent by using the common notation based on SoC/CPU ID (which is used internally anyway), bcmXXXX instead of brcmXXXX. This is even used for target TITLE in make menuconfig already, only the short target name used brcm so far. Despite, since subtargets range from bcm2708 to bcm2711, it seems appropriate to use bcm27xx instead of bcm2708 (again, as already done for BOARDNAME). This also renames the packages brcm2708-userland and brcm2708-gpu-fw. Signed-off-by: Adrian Schmutzler <freifunk@adrianschmutzler.de> Acked-by: Álvaro Fernández Rojas <noltari@gmail.com>
826 lines
22 KiB
Diff
826 lines
22 KiB
Diff
From fd8ca458728baabe9cae37836088a33c8642d420 Mon Sep 17 00:00:00 2001
|
|
From: Eric Anholt <eric@anholt.net>
|
|
Date: Wed, 12 Dec 2018 15:51:48 -0800
|
|
Subject: [PATCH] soc: bcm: bcm2835-pm: Add support for power domains
|
|
under a new binding.
|
|
|
|
This provides a free software alternative to raspberrypi-power.c's
|
|
firmware calls to manage power domains. It also exposes a reset line,
|
|
where previously the vc4 driver had to try to force power off the
|
|
domain in order to trigger a reset.
|
|
|
|
Signed-off-by: Eric Anholt <eric@anholt.net>
|
|
Acked-by: Rob Herring <robh@kernel.org>
|
|
Acked-by: Stefan Wahren <stefan.wahren@i2se.com>
|
|
Signed-off-by: Stefan Wahren <stefan.wahren@i2se.com>
|
|
(cherry picked from commit 670c672608a1ffcbc7ac0f872734843593bb8b15)
|
|
---
|
|
drivers/mfd/bcm2835-pm.c | 36 +-
|
|
drivers/soc/bcm/Kconfig | 11 +
|
|
drivers/soc/bcm/Makefile | 1 +
|
|
drivers/soc/bcm/bcm2835-power.c | 661 +++++++++++++++++++++++++++
|
|
include/dt-bindings/soc/bcm2835-pm.h | 28 ++
|
|
include/linux/mfd/bcm2835-pm.h | 1 +
|
|
6 files changed, 734 insertions(+), 4 deletions(-)
|
|
create mode 100644 drivers/soc/bcm/bcm2835-power.c
|
|
create mode 100644 include/dt-bindings/soc/bcm2835-pm.h
|
|
|
|
--- a/drivers/mfd/bcm2835-pm.c
|
|
+++ b/drivers/mfd/bcm2835-pm.c
|
|
@@ -3,7 +3,7 @@
|
|
* PM MFD driver for Broadcom BCM2835
|
|
*
|
|
* This driver binds to the PM block and creates the MFD device for
|
|
- * the WDT driver.
|
|
+ * the WDT and power drivers.
|
|
*/
|
|
|
|
#include <linux/delay.h>
|
|
@@ -21,11 +21,16 @@ static const struct mfd_cell bcm2835_pm_
|
|
{ .name = "bcm2835-wdt" },
|
|
};
|
|
|
|
+static const struct mfd_cell bcm2835_power_devs[] = {
|
|
+ { .name = "bcm2835-power" },
|
|
+};
|
|
+
|
|
static int bcm2835_pm_probe(struct platform_device *pdev)
|
|
{
|
|
struct resource *res;
|
|
struct device *dev = &pdev->dev;
|
|
struct bcm2835_pm *pm;
|
|
+ int ret;
|
|
|
|
pm = devm_kzalloc(dev, sizeof(*pm), GFP_KERNEL);
|
|
if (!pm)
|
|
@@ -39,13 +44,36 @@ static int bcm2835_pm_probe(struct platf
|
|
if (IS_ERR(pm->base))
|
|
return PTR_ERR(pm->base);
|
|
|
|
- return devm_mfd_add_devices(dev, -1,
|
|
- bcm2835_pm_devs, ARRAY_SIZE(bcm2835_pm_devs),
|
|
- NULL, 0, NULL);
|
|
+ ret = devm_mfd_add_devices(dev, -1,
|
|
+ bcm2835_pm_devs, ARRAY_SIZE(bcm2835_pm_devs),
|
|
+ NULL, 0, NULL);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ /* We'll use the presence of the AXI ASB regs in the
|
|
+ * bcm2835-pm binding as the key for whether we can reference
|
|
+ * the full PM register range and support power domains.
|
|
+ */
|
|
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
|
|
+ if (res) {
|
|
+ pm->asb = devm_ioremap_resource(dev, res);
|
|
+ if (IS_ERR(pm->asb))
|
|
+ return PTR_ERR(pm->asb);
|
|
+
|
|
+ ret = devm_mfd_add_devices(dev, -1,
|
|
+ bcm2835_power_devs,
|
|
+ ARRAY_SIZE(bcm2835_power_devs),
|
|
+ NULL, 0, NULL);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
}
|
|
|
|
static const struct of_device_id bcm2835_pm_of_match[] = {
|
|
{ .compatible = "brcm,bcm2835-pm-wdt", },
|
|
+ { .compatible = "brcm,bcm2835-pm", },
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, bcm2835_pm_of_match);
|
|
--- a/drivers/soc/bcm/Kconfig
|
|
+++ b/drivers/soc/bcm/Kconfig
|
|
@@ -1,5 +1,16 @@
|
|
menu "Broadcom SoC drivers"
|
|
|
|
+config BCM2835_POWER
|
|
+ bool "BCM2835 power domain driver"
|
|
+ depends on ARCH_BCM2835 || (COMPILE_TEST && OF)
|
|
+ select PM_GENERIC_DOMAINS if PM
|
|
+ select RESET_CONTROLLER
|
|
+ help
|
|
+ This enables support for the BCM2835 power domains and reset
|
|
+ controller. Any usage of power domains by the Raspberry Pi
|
|
+ firmware means that Linux usage of the same power domain
|
|
+ must be accessed using the RASPBERRYPI_POWER driver
|
|
+
|
|
config RASPBERRYPI_POWER
|
|
bool "Raspberry Pi power domain driver"
|
|
depends on ARCH_BCM2835 || (COMPILE_TEST && OF)
|
|
--- a/drivers/soc/bcm/Makefile
|
|
+++ b/drivers/soc/bcm/Makefile
|
|
@@ -1,2 +1,3 @@
|
|
+obj-$(CONFIG_BCM2835_POWER) += bcm2835-power.o
|
|
obj-$(CONFIG_RASPBERRYPI_POWER) += raspberrypi-power.o
|
|
obj-$(CONFIG_SOC_BRCMSTB) += brcmstb/
|
|
--- /dev/null
|
|
+++ b/drivers/soc/bcm/bcm2835-power.c
|
|
@@ -0,0 +1,661 @@
|
|
+// SPDX-License-Identifier: GPL-2.0+
|
|
+/*
|
|
+ * Power domain driver for Broadcom BCM2835
|
|
+ *
|
|
+ * Copyright (C) 2018 Broadcom
|
|
+ */
|
|
+
|
|
+#include <dt-bindings/soc/bcm2835-pm.h>
|
|
+#include <linux/clk.h>
|
|
+#include <linux/delay.h>
|
|
+#include <linux/io.h>
|
|
+#include <linux/mfd/bcm2835-pm.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/pm_domain.h>
|
|
+#include <linux/reset-controller.h>
|
|
+#include <linux/types.h>
|
|
+
|
|
+#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 = 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 = 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 void
|
|
+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);
|
|
+
|
|
+ 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;
|
|
+}
|
|
+
|
|
+/** 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;
|
|
+ }
|
|
+}
|
|
+
|
|
+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, 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++)
|
|
+ bcm2835_init_power_domain(power, i, power_domain_names[i]);
|
|
+
|
|
+ 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)
|
|
+ return ret;
|
|
+
|
|
+ of_genpd_add_provider_onecell(dev->parent->of_node, &power->pd_xlate);
|
|
+
|
|
+ dev_info(dev, "Broadcom BCM2835 power domains driver");
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+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 <eric@anholt.net>");
|
|
+MODULE_DESCRIPTION("Driver for Broadcom BCM2835 PM power domains and reset");
|
|
+MODULE_LICENSE("GPL");
|
|
--- /dev/null
|
|
+++ b/include/dt-bindings/soc/bcm2835-pm.h
|
|
@@ -0,0 +1,28 @@
|
|
+/* SPDX-License-Identifier: (GPL-2.0+ OR MIT) */
|
|
+
|
|
+#ifndef _DT_BINDINGS_ARM_BCM2835_PM_H
|
|
+#define _DT_BINDINGS_ARM_BCM2835_PM_H
|
|
+
|
|
+#define BCM2835_POWER_DOMAIN_GRAFX 0
|
|
+#define BCM2835_POWER_DOMAIN_GRAFX_V3D 1
|
|
+#define BCM2835_POWER_DOMAIN_IMAGE 2
|
|
+#define BCM2835_POWER_DOMAIN_IMAGE_PERI 3
|
|
+#define BCM2835_POWER_DOMAIN_IMAGE_ISP 4
|
|
+#define BCM2835_POWER_DOMAIN_IMAGE_H264 5
|
|
+#define BCM2835_POWER_DOMAIN_USB 6
|
|
+#define BCM2835_POWER_DOMAIN_DSI0 7
|
|
+#define BCM2835_POWER_DOMAIN_DSI1 8
|
|
+#define BCM2835_POWER_DOMAIN_CAM0 9
|
|
+#define BCM2835_POWER_DOMAIN_CAM1 10
|
|
+#define BCM2835_POWER_DOMAIN_CCP2TX 11
|
|
+#define BCM2835_POWER_DOMAIN_HDMI 12
|
|
+
|
|
+#define BCM2835_POWER_DOMAIN_COUNT 13
|
|
+
|
|
+#define BCM2835_RESET_V3D 0
|
|
+#define BCM2835_RESET_ISP 1
|
|
+#define BCM2835_RESET_H264 2
|
|
+
|
|
+#define BCM2835_RESET_COUNT 3
|
|
+
|
|
+#endif /* _DT_BINDINGS_ARM_BCM2835_PM_H */
|
|
--- a/include/linux/mfd/bcm2835-pm.h
|
|
+++ b/include/linux/mfd/bcm2835-pm.h
|
|
@@ -8,6 +8,7 @@
|
|
struct bcm2835_pm {
|
|
struct device *dev;
|
|
void __iomem *base;
|
|
+ void __iomem *asb;
|
|
};
|
|
|
|
#endif /* BCM2835_MFD_PM_H */
|