This pull request contains Broadcom ARM/ARM64/MIPS based SoCs changes

for 5.1, please pull the following:
 
 - Stefan updates the BCM2835 SoC driver with downstream properties and
   uses that to implement a reboot notifier to tell the VC4 firmware when
   Linux on the ARM CPU is rebooting
 
 - Eric adds a proper power domain driver for the BCM283x SoCs and
   updates a bunch of drivers to have a better and clearer Device Tree
   definition to support power domains/breaking up of functionality. This
   requires converting the existing watchdog driver into a MFD and then
   breaking up the functionality into separate drivers and finally
   updating the DTS files to leverage the power domains information.
 
 - Wei provides a fix for making a symbol static
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEEm+Rq3+YGJdiR9yuFh9CWnEQHBwQFAlxV0IYACgkQh9CWnEQH
 BwQIHA//TL12EfHws6C64EKspqsg9NE5/+K3HRRJbgwtuBQ/n6oYBMyJx+6x6Nwc
 7QjuCkZ/VWKxy0Fn+ToGnX0JgrQL5kND3Cm0d8cyZ0VJ6juppizyAa1YkFplShSr
 l0XlYaJo1HrB3hBd/+YbGZnLp9flbii5d3MIcy8ZoWmN7zLeHnQbHaf8jcbJC+Iw
 Mal8ojk2ru0rMimMQieTiPzWwiec08wtSIYs2590rOVWFyhGIn/KmHqpG6iYjdwj
 oQbr86R0jMPCb/g3SXRttxW8wFbtYdmILdkzhOaEd4JyJEwUCNDciM3E04OyE9VN
 fNMc1l0zh7dfyo9bFRpgS6AAxYQVj3led+B1NGtpnjDPybVWU10gipGdgFt9UPRE
 pJnS1LcPbAJ1FdbcYFU0TsiViWLZehm2cbc4rPYvqKp1Y+82FJZTYyu0GmBOUwB6
 jpM5ZVvET8k3nw6ImeE3jjT3kBfF31u552+iO4RQvKHRm/GBMtyTDrFZVUwgqMFE
 NEKnj3/VLSCxP3dnQImw1ro2493piZNdlBEs6mAugFUGqcb+40KOtOOpWiGMFH7h
 BZN0kj128ryG/YCVKDOnZSbYRLhpxc1VcVYJ3rYJgn8mrFFmNo/fjDgaRogrJN/s
 LmCiSIqsmuy8f36/IWd+aHk6ex5yskJe7x5M/7tlmz03oXg1viQ=
 =t5+u
 -----END PGP SIGNATURE-----

Merge tag 'arm-soc/for-5.1/drivers' of https://github.com/Broadcom/stblinux into arm/drivers

This pull request contains Broadcom ARM/ARM64/MIPS based SoCs changes
for 5.1, please pull the following:

- Stefan updates the BCM2835 SoC driver with downstream properties and
  uses that to implement a reboot notifier to tell the VC4 firmware when
  Linux on the ARM CPU is rebooting

- Eric adds a proper power domain driver for the BCM283x SoCs and
  updates a bunch of drivers to have a better and clearer Device Tree
  definition to support power domains/breaking up of functionality. This
  requires converting the existing watchdog driver into a MFD and then
  breaking up the functionality into separate drivers and finally
  updating the DTS files to leverage the power domains information.

- Wei provides a fix for making a symbol static

* tag 'arm-soc/for-5.1/drivers' of https://github.com/Broadcom/stblinux:
  ARM: bcm283x: Switch V3D over to using the PM driver instead of firmware.
  ARM: bcm283x: Extend the WDT DT node out to cover the whole PM block. (v4)
  soc: bcm: bcm2835-pm: Make local symbol static
  soc: bcm: Make PM driver default for BCM2835
  soc: bcm: bcm2835-pm: Add support for power domains under a new binding.
  bcm2835-pm: Move bcm2835-watchdog's DT probe to an MFD.
  dt-bindings: soc: Add a new binding for the BCM2835 PM node. (v4)
  firmware: raspberrypi: notify VC4 firmware of a reboot
  soc: bcm2835: sync firmware properties with downstream

Signed-off-by: Arnd Bergmann <arnd@arndb.de>
This commit is contained in:
Arnd Bergmann 2019-02-15 18:00:42 +01:00
commit 187b4ac7df
14 changed files with 894 additions and 24 deletions

View file

@ -0,0 +1,46 @@
BCM2835 PM (Power domains, watchdog)
The PM block controls power domains and some reset lines, and includes
a watchdog timer. This binding supersedes the brcm,bcm2835-pm-wdt
binding which covered some of PM's register range and functionality.
Required properties:
- compatible: Should be "brcm,bcm2835-pm"
- reg: Specifies base physical address and size of the two
register ranges ("PM" and "ASYNC_BRIDGE" in that
order)
- clocks: a) v3d: The V3D clock from CPRMAN
b) peri_image: The PERI_IMAGE clock from CPRMAN
c) h264: The H264 clock from CPRMAN
d) isp: The ISP clock from CPRMAN
- #reset-cells: Should be 1. This property follows the reset controller
bindings[1].
- #power-domain-cells: Should be 1. This property follows the power domain
bindings[2].
Optional properties:
- timeout-sec: Contains the watchdog timeout in seconds
- system-power-controller: Whether the watchdog is controlling the
system power. This node follows the power controller bindings[3].
[1] Documentation/devicetree/bindings/reset/reset.txt
[2] Documentation/devicetree/bindings/power/power_domain.txt
[3] Documentation/devicetree/bindings/power/power-controller.txt
Example:
pm {
compatible = "brcm,bcm2835-pm", "brcm,bcm2835-pm-wdt";
#power-domain-cells = <1>;
#reset-cells = <1>;
reg = <0x7e100000 0x114>,
<0x7e00a000 0x24>;
clocks = <&clocks BCM2835_CLOCK_V3D>,
<&clocks BCM2835_CLOCK_PERI_IMAGE>,
<&clocks BCM2835_CLOCK_H264>,
<&clocks BCM2835_CLOCK_ISP>;
clock-names = "v3d", "peri_image", "h264", "isp";
system-power-controller;
};

View file

@ -87,10 +87,6 @@ &usb {
power-domains = <&power RPI_POWER_DOMAIN_USB>;
};
&v3d {
power-domains = <&power RPI_POWER_DOMAIN_V3D>;
};
&hdmi {
power-domains = <&power RPI_POWER_DOMAIN_HDMI>;
status = "okay";

View file

@ -3,6 +3,7 @@
#include <dt-bindings/clock/bcm2835-aux.h>
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/interrupt-controller/irq.h>
#include <dt-bindings/soc/bcm2835-pm.h>
/* firmware-provided startup stubs live here, where the secondary CPUs are
* spinning.
@ -120,9 +121,18 @@ intc: interrupt-controller@7e00b200 {
#interrupt-cells = <2>;
};
watchdog@7e100000 {
compatible = "brcm,bcm2835-pm-wdt";
reg = <0x7e100000 0x28>;
pm: watchdog@7e100000 {
compatible = "brcm,bcm2835-pm", "brcm,bcm2835-pm-wdt";
#power-domain-cells = <1>;
#reset-cells = <1>;
reg = <0x7e100000 0x114>,
<0x7e00a000 0x24>;
clocks = <&clocks BCM2835_CLOCK_V3D>,
<&clocks BCM2835_CLOCK_PERI_IMAGE>,
<&clocks BCM2835_CLOCK_H264>,
<&clocks BCM2835_CLOCK_ISP>;
clock-names = "v3d", "peri_image", "h264", "isp";
system-power-controller;
};
clocks: cprman@7e101000 {
@ -629,6 +639,7 @@ v3d: v3d@7ec00000 {
compatible = "brcm,bcm2835-v3d";
reg = <0x7ec00000 0x1000>;
interrupts = <1 10>;
power-domains = <&pm BCM2835_POWER_DOMAIN_GRAFX_V3D>;
};
vc4: gpu {

View file

@ -167,6 +167,7 @@ config ARCH_BCM2835
select BCM2835_TIMER
select PINCTRL
select PINCTRL_BCM2835
select MFD_CORE
help
This enables support for the Broadcom BCM2835 and BCM2836 SoCs.
This SoC is used in the Raspberry Pi and Roku 2 devices.

View file

@ -238,6 +238,16 @@ static int rpi_firmware_probe(struct platform_device *pdev)
return 0;
}
static void rpi_firmware_shutdown(struct platform_device *pdev)
{
struct rpi_firmware *fw = platform_get_drvdata(pdev);
if (!fw)
return;
rpi_firmware_property(fw, RPI_FIRMWARE_NOTIFY_REBOOT, NULL, 0);
}
static int rpi_firmware_remove(struct platform_device *pdev)
{
struct rpi_firmware *fw = platform_get_drvdata(pdev);
@ -278,6 +288,7 @@ static struct platform_driver rpi_firmware_driver = {
.of_match_table = rpi_firmware_of_match,
},
.probe = rpi_firmware_probe,
.shutdown = rpi_firmware_shutdown,
.remove = rpi_firmware_remove,
};
module_platform_driver(rpi_firmware_driver);

View file

@ -10,6 +10,7 @@ obj-$(CONFIG_MFD_88PM805) += 88pm805.o 88pm80x.o
obj-$(CONFIG_MFD_ACT8945A) += act8945a.o
obj-$(CONFIG_MFD_SM501) += sm501.o
obj-$(CONFIG_MFD_ASIC3) += asic3.o tmio_core.o
obj-$(CONFIG_ARCH_BCM2835) += bcm2835-pm.o
obj-$(CONFIG_MFD_BCM590XX) += bcm590xx.o
obj-$(CONFIG_MFD_BD9571MWV) += bd9571mwv.o
cros_ec_core-objs := cros_ec.o

92
drivers/mfd/bcm2835-pm.c Normal file
View file

@ -0,0 +1,92 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* PM MFD driver for Broadcom BCM2835
*
* This driver binds to the PM block and creates the MFD device for
* the WDT and power drivers.
*/
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/mfd/bcm2835-pm.h>
#include <linux/mfd/core.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/types.h>
#include <linux/watchdog.h>
static const struct mfd_cell bcm2835_pm_devs[] = {
{ .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)
return -ENOMEM;
platform_set_drvdata(pdev, pm);
pm->dev = dev;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
pm->base = devm_ioremap_resource(dev, res);
if (IS_ERR(pm->base))
return PTR_ERR(pm->base);
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);
static struct platform_driver bcm2835_pm_driver = {
.probe = bcm2835_pm_probe,
.driver = {
.name = "bcm2835-pm",
.of_match_table = bcm2835_pm_of_match,
},
};
module_platform_driver(bcm2835_pm_driver);
MODULE_AUTHOR("Eric Anholt <eric@anholt.net>");
MODULE_DESCRIPTION("Driver for Broadcom BCM2835 PM MFD");
MODULE_LICENSE("GPL");

View file

@ -1,5 +1,17 @@
menu "Broadcom SoC drivers"
config BCM2835_POWER
bool "BCM2835 power domain driver"
depends on ARCH_BCM2835 || (COMPILE_TEST && OF)
default y if ARCH_BCM2835
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)

View file

@ -1,2 +1,3 @@
obj-$(CONFIG_BCM2835_POWER) += bcm2835-power.o
obj-$(CONFIG_RASPBERRYPI_POWER) += raspberrypi-power.o
obj-$(CONFIG_SOC_BRCMSTB) += brcmstb/

View file

@ -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;
}
}
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, 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");

View file

@ -12,6 +12,7 @@
#include <linux/delay.h>
#include <linux/types.h>
#include <linux/mfd/bcm2835-pm.h>
#include <linux/module.h>
#include <linux/io.h>
#include <linux/watchdog.h>
@ -47,6 +48,8 @@ struct bcm2835_wdt {
spinlock_t lock;
};
static struct bcm2835_wdt *bcm2835_power_off_wdt;
static unsigned int heartbeat;
static bool nowayout = WATCHDOG_NOWAYOUT;
@ -148,10 +151,7 @@ static struct watchdog_device bcm2835_wdt_wdd = {
*/
static void bcm2835_power_off(void)
{
struct device_node *np =
of_find_compatible_node(NULL, NULL, "brcm,bcm2835-pm-wdt");
struct platform_device *pdev = of_find_device_by_node(np);
struct bcm2835_wdt *wdt = platform_get_drvdata(pdev);
struct bcm2835_wdt *wdt = bcm2835_power_off_wdt;
u32 val;
/*
@ -169,7 +169,7 @@ static void bcm2835_power_off(void)
static int bcm2835_wdt_probe(struct platform_device *pdev)
{
struct resource *res;
struct bcm2835_pm *pm = dev_get_drvdata(pdev->dev.parent);
struct device *dev = &pdev->dev;
struct bcm2835_wdt *wdt;
int err;
@ -181,10 +181,7 @@ static int bcm2835_wdt_probe(struct platform_device *pdev)
spin_lock_init(&wdt->lock);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
wdt->base = devm_ioremap_resource(dev, res);
if (IS_ERR(wdt->base))
return PTR_ERR(wdt->base);
wdt->base = pm->base;
watchdog_set_drvdata(&bcm2835_wdt_wdd, wdt);
watchdog_init_timeout(&bcm2835_wdt_wdd, heartbeat, dev);
@ -211,8 +208,10 @@ static int bcm2835_wdt_probe(struct platform_device *pdev)
return err;
}
if (pm_power_off == NULL)
if (pm_power_off == NULL) {
pm_power_off = bcm2835_power_off;
bcm2835_power_off_wdt = wdt;
}
dev_info(dev, "Broadcom BCM2835 watchdog timer");
return 0;
@ -226,18 +225,11 @@ static int bcm2835_wdt_remove(struct platform_device *pdev)
return 0;
}
static const struct of_device_id bcm2835_wdt_of_match[] = {
{ .compatible = "brcm,bcm2835-pm-wdt", },
{},
};
MODULE_DEVICE_TABLE(of, bcm2835_wdt_of_match);
static struct platform_driver bcm2835_wdt_driver = {
.probe = bcm2835_wdt_probe,
.remove = bcm2835_wdt_remove,
.driver = {
.name = "bcm2835-wdt",
.of_match_table = bcm2835_wdt_of_match,
},
};
module_platform_driver(bcm2835_wdt_driver);

View file

@ -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 */

View file

@ -0,0 +1,14 @@
/* SPDX-License-Identifier: GPL-2.0+ */
#ifndef BCM2835_MFD_PM_H
#define BCM2835_MFD_PM_H
#include <linux/regmap.h>
struct bcm2835_pm {
struct device *dev;
void __iomem *base;
void __iomem *asb;
};
#endif /* BCM2835_MFD_PM_H */

View file

@ -73,6 +73,8 @@ enum rpi_firmware_property_tag {
RPI_FIRMWARE_GET_CUSTOMER_OTP = 0x00030021,
RPI_FIRMWARE_GET_DOMAIN_STATE = 0x00030030,
RPI_FIRMWARE_GET_THROTTLED = 0x00030046,
RPI_FIRMWARE_GET_CLOCK_MEASURED = 0x00030047,
RPI_FIRMWARE_NOTIFY_REBOOT = 0x00030048,
RPI_FIRMWARE_SET_CLOCK_STATE = 0x00038001,
RPI_FIRMWARE_SET_CLOCK_RATE = 0x00038002,
RPI_FIRMWARE_SET_VOLTAGE = 0x00038003,
@ -86,6 +88,8 @@ enum rpi_firmware_property_tag {
RPI_FIRMWARE_SET_GPIO_CONFIG = 0x00038043,
RPI_FIRMWARE_GET_PERIPH_REG = 0x00030045,
RPI_FIRMWARE_SET_PERIPH_REG = 0x00038045,
RPI_FIRMWARE_GET_POE_HAT_VAL = 0x00030049,
RPI_FIRMWARE_SET_POE_HAT_VAL = 0x00030050,
/* Dispmanx TAGS */