Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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>

authored by

Eric Anholt and committed by
Stefan Wahren
670c6726 5e6acc3e

+734 -4
+32 -4
drivers/mfd/bcm2835-pm.c
··· 3 3 * PM MFD driver for Broadcom BCM2835 4 4 * 5 5 * This driver binds to the PM block and creates the MFD device for 6 - * the WDT driver. 6 + * the WDT and power drivers. 7 7 */ 8 8 9 9 #include <linux/delay.h> ··· 21 21 { .name = "bcm2835-wdt" }, 22 22 }; 23 23 24 + static const struct mfd_cell bcm2835_power_devs[] = { 25 + { .name = "bcm2835-power" }, 26 + }; 27 + 24 28 static int bcm2835_pm_probe(struct platform_device *pdev) 25 29 { 26 30 struct resource *res; 27 31 struct device *dev = &pdev->dev; 28 32 struct bcm2835_pm *pm; 33 + int ret; 29 34 30 35 pm = devm_kzalloc(dev, sizeof(*pm), GFP_KERNEL); 31 36 if (!pm) ··· 44 39 if (IS_ERR(pm->base)) 45 40 return PTR_ERR(pm->base); 46 41 47 - return devm_mfd_add_devices(dev, -1, 48 - bcm2835_pm_devs, ARRAY_SIZE(bcm2835_pm_devs), 49 - NULL, 0, NULL); 42 + ret = devm_mfd_add_devices(dev, -1, 43 + bcm2835_pm_devs, ARRAY_SIZE(bcm2835_pm_devs), 44 + NULL, 0, NULL); 45 + if (ret) 46 + return ret; 47 + 48 + /* We'll use the presence of the AXI ASB regs in the 49 + * bcm2835-pm binding as the key for whether we can reference 50 + * the full PM register range and support power domains. 51 + */ 52 + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); 53 + if (res) { 54 + pm->asb = devm_ioremap_resource(dev, res); 55 + if (IS_ERR(pm->asb)) 56 + return PTR_ERR(pm->asb); 57 + 58 + ret = devm_mfd_add_devices(dev, -1, 59 + bcm2835_power_devs, 60 + ARRAY_SIZE(bcm2835_power_devs), 61 + NULL, 0, NULL); 62 + if (ret) 63 + return ret; 64 + } 65 + 66 + return 0; 50 67 } 51 68 52 69 static const struct of_device_id bcm2835_pm_of_match[] = { 53 70 { .compatible = "brcm,bcm2835-pm-wdt", }, 71 + { .compatible = "brcm,bcm2835-pm", }, 54 72 {}, 55 73 }; 56 74 MODULE_DEVICE_TABLE(of, bcm2835_pm_of_match);
+11
drivers/soc/bcm/Kconfig
··· 1 1 menu "Broadcom SoC drivers" 2 2 3 + config BCM2835_POWER 4 + bool "BCM2835 power domain driver" 5 + depends on ARCH_BCM2835 || (COMPILE_TEST && OF) 6 + select PM_GENERIC_DOMAINS if PM 7 + select RESET_CONTROLLER 8 + help 9 + This enables support for the BCM2835 power domains and reset 10 + controller. Any usage of power domains by the Raspberry Pi 11 + firmware means that Linux usage of the same power domain 12 + must be accessed using the RASPBERRYPI_POWER driver 13 + 3 14 config RASPBERRYPI_POWER 4 15 bool "Raspberry Pi power domain driver" 5 16 depends on ARCH_BCM2835 || (COMPILE_TEST && OF)
+1
drivers/soc/bcm/Makefile
··· 1 + obj-$(CONFIG_BCM2835_POWER) += bcm2835-power.o 1 2 obj-$(CONFIG_RASPBERRYPI_POWER) += raspberrypi-power.o 2 3 obj-$(CONFIG_SOC_BRCMSTB) += brcmstb/
+661
drivers/soc/bcm/bcm2835-power.c
··· 1 + // SPDX-License-Identifier: GPL-2.0+ 2 + /* 3 + * Power domain driver for Broadcom BCM2835 4 + * 5 + * Copyright (C) 2018 Broadcom 6 + */ 7 + 8 + #include <dt-bindings/soc/bcm2835-pm.h> 9 + #include <linux/clk.h> 10 + #include <linux/delay.h> 11 + #include <linux/io.h> 12 + #include <linux/mfd/bcm2835-pm.h> 13 + #include <linux/module.h> 14 + #include <linux/platform_device.h> 15 + #include <linux/pm_domain.h> 16 + #include <linux/reset-controller.h> 17 + #include <linux/types.h> 18 + 19 + #define PM_GNRIC 0x00 20 + #define PM_AUDIO 0x04 21 + #define PM_STATUS 0x18 22 + #define PM_RSTC 0x1c 23 + #define PM_RSTS 0x20 24 + #define PM_WDOG 0x24 25 + #define PM_PADS0 0x28 26 + #define PM_PADS2 0x2c 27 + #define PM_PADS3 0x30 28 + #define PM_PADS4 0x34 29 + #define PM_PADS5 0x38 30 + #define PM_PADS6 0x3c 31 + #define PM_CAM0 0x44 32 + #define PM_CAM0_LDOHPEN BIT(2) 33 + #define PM_CAM0_LDOLPEN BIT(1) 34 + #define PM_CAM0_CTRLEN BIT(0) 35 + 36 + #define PM_CAM1 0x48 37 + #define PM_CAM1_LDOHPEN BIT(2) 38 + #define PM_CAM1_LDOLPEN BIT(1) 39 + #define PM_CAM1_CTRLEN BIT(0) 40 + 41 + #define PM_CCP2TX 0x4c 42 + #define PM_CCP2TX_LDOEN BIT(1) 43 + #define PM_CCP2TX_CTRLEN BIT(0) 44 + 45 + #define PM_DSI0 0x50 46 + #define PM_DSI0_LDOHPEN BIT(2) 47 + #define PM_DSI0_LDOLPEN BIT(1) 48 + #define PM_DSI0_CTRLEN BIT(0) 49 + 50 + #define PM_DSI1 0x54 51 + #define PM_DSI1_LDOHPEN BIT(2) 52 + #define PM_DSI1_LDOLPEN BIT(1) 53 + #define PM_DSI1_CTRLEN BIT(0) 54 + 55 + #define PM_HDMI 0x58 56 + #define PM_HDMI_RSTDR BIT(19) 57 + #define PM_HDMI_LDOPD BIT(1) 58 + #define PM_HDMI_CTRLEN BIT(0) 59 + 60 + #define PM_USB 0x5c 61 + /* The power gates must be enabled with this bit before enabling the LDO in the 62 + * USB block. 63 + */ 64 + #define PM_USB_CTRLEN BIT(0) 65 + 66 + #define PM_PXLDO 0x60 67 + #define PM_PXBG 0x64 68 + #define PM_DFT 0x68 69 + #define PM_SMPS 0x6c 70 + #define PM_XOSC 0x70 71 + #define PM_SPAREW 0x74 72 + #define PM_SPARER 0x78 73 + #define PM_AVS_RSTDR 0x7c 74 + #define PM_AVS_STAT 0x80 75 + #define PM_AVS_EVENT 0x84 76 + #define PM_AVS_INTEN 0x88 77 + #define PM_DUMMY 0xfc 78 + 79 + #define PM_IMAGE 0x108 80 + #define PM_GRAFX 0x10c 81 + #define PM_PROC 0x110 82 + #define PM_ENAB BIT(12) 83 + #define PM_ISPRSTN BIT(8) 84 + #define PM_H264RSTN BIT(7) 85 + #define PM_PERIRSTN BIT(6) 86 + #define PM_V3DRSTN BIT(6) 87 + #define PM_ISFUNC BIT(5) 88 + #define PM_MRDONE BIT(4) 89 + #define PM_MEMREP BIT(3) 90 + #define PM_ISPOW BIT(2) 91 + #define PM_POWOK BIT(1) 92 + #define PM_POWUP BIT(0) 93 + #define PM_INRUSH_SHIFT 13 94 + #define PM_INRUSH_3_5_MA 0 95 + #define PM_INRUSH_5_MA 1 96 + #define PM_INRUSH_10_MA 2 97 + #define PM_INRUSH_20_MA 3 98 + #define PM_INRUSH_MASK (3 << PM_INRUSH_SHIFT) 99 + 100 + #define PM_PASSWORD 0x5a000000 101 + 102 + #define PM_WDOG_TIME_SET 0x000fffff 103 + #define PM_RSTC_WRCFG_CLR 0xffffffcf 104 + #define PM_RSTS_HADWRH_SET 0x00000040 105 + #define PM_RSTC_WRCFG_SET 0x00000030 106 + #define PM_RSTC_WRCFG_FULL_RESET 0x00000020 107 + #define PM_RSTC_RESET 0x00000102 108 + 109 + #define PM_READ(reg) readl(power->base + (reg)) 110 + #define PM_WRITE(reg, val) writel(PM_PASSWORD | (val), power->base + (reg)) 111 + 112 + #define ASB_BRDG_VERSION 0x00 113 + #define ASB_CPR_CTRL 0x04 114 + 115 + #define ASB_V3D_S_CTRL 0x08 116 + #define ASB_V3D_M_CTRL 0x0c 117 + #define ASB_ISP_S_CTRL 0x10 118 + #define ASB_ISP_M_CTRL 0x14 119 + #define ASB_H264_S_CTRL 0x18 120 + #define ASB_H264_M_CTRL 0x1c 121 + 122 + #define ASB_REQ_STOP BIT(0) 123 + #define ASB_ACK BIT(1) 124 + #define ASB_EMPTY BIT(2) 125 + #define ASB_FULL BIT(3) 126 + 127 + #define ASB_AXI_BRDG_ID 0x20 128 + 129 + #define ASB_READ(reg) readl(power->asb + (reg)) 130 + #define ASB_WRITE(reg, val) writel(PM_PASSWORD | (val), power->asb + (reg)) 131 + 132 + struct bcm2835_power_domain { 133 + struct generic_pm_domain base; 134 + struct bcm2835_power *power; 135 + u32 domain; 136 + struct clk *clk; 137 + }; 138 + 139 + struct bcm2835_power { 140 + struct device *dev; 141 + /* PM registers. */ 142 + void __iomem *base; 143 + /* AXI Async bridge registers. */ 144 + void __iomem *asb; 145 + 146 + struct genpd_onecell_data pd_xlate; 147 + struct bcm2835_power_domain domains[BCM2835_POWER_DOMAIN_COUNT]; 148 + struct reset_controller_dev reset; 149 + }; 150 + 151 + static int bcm2835_asb_enable(struct bcm2835_power *power, u32 reg) 152 + { 153 + u64 start = ktime_get_ns(); 154 + 155 + /* Enable the module's async AXI bridges. */ 156 + ASB_WRITE(reg, ASB_READ(reg) & ~ASB_REQ_STOP); 157 + while (ASB_READ(reg) & ASB_ACK) { 158 + cpu_relax(); 159 + if (ktime_get_ns() - start >= 1000) 160 + return -ETIMEDOUT; 161 + } 162 + 163 + return 0; 164 + } 165 + 166 + static int bcm2835_asb_disable(struct bcm2835_power *power, u32 reg) 167 + { 168 + u64 start = ktime_get_ns(); 169 + 170 + /* Enable the module's async AXI bridges. */ 171 + ASB_WRITE(reg, ASB_READ(reg) | ASB_REQ_STOP); 172 + while (!(ASB_READ(reg) & ASB_ACK)) { 173 + cpu_relax(); 174 + if (ktime_get_ns() - start >= 1000) 175 + return -ETIMEDOUT; 176 + } 177 + 178 + return 0; 179 + } 180 + 181 + static int bcm2835_power_power_off(struct bcm2835_power_domain *pd, u32 pm_reg) 182 + { 183 + struct bcm2835_power *power = pd->power; 184 + 185 + /* Enable functional isolation */ 186 + PM_WRITE(pm_reg, PM_READ(pm_reg) & ~PM_ISFUNC); 187 + 188 + /* Enable electrical isolation */ 189 + PM_WRITE(pm_reg, PM_READ(pm_reg) & ~PM_ISPOW); 190 + 191 + /* Open the power switches. */ 192 + PM_WRITE(pm_reg, PM_READ(pm_reg) & ~PM_POWUP); 193 + 194 + return 0; 195 + } 196 + 197 + static int bcm2835_power_power_on(struct bcm2835_power_domain *pd, u32 pm_reg) 198 + { 199 + struct bcm2835_power *power = pd->power; 200 + struct device *dev = power->dev; 201 + u64 start; 202 + int ret; 203 + int inrush; 204 + bool powok; 205 + 206 + /* If it was already powered on by the fw, leave it that way. */ 207 + if (PM_READ(pm_reg) & PM_POWUP) 208 + return 0; 209 + 210 + /* Enable power. Allowing too much current at once may result 211 + * in POWOK never getting set, so start low and ramp it up as 212 + * necessary to succeed. 213 + */ 214 + powok = false; 215 + for (inrush = PM_INRUSH_3_5_MA; inrush <= PM_INRUSH_20_MA; inrush++) { 216 + PM_WRITE(pm_reg, 217 + (PM_READ(pm_reg) & ~PM_INRUSH_MASK) | 218 + (inrush << PM_INRUSH_SHIFT) | 219 + PM_POWUP); 220 + 221 + start = ktime_get_ns(); 222 + while (!(powok = !!(PM_READ(pm_reg) & PM_POWOK))) { 223 + cpu_relax(); 224 + if (ktime_get_ns() - start >= 3000) 225 + break; 226 + } 227 + } 228 + if (!powok) { 229 + dev_err(dev, "Timeout waiting for %s power OK\n", 230 + pd->base.name); 231 + ret = -ETIMEDOUT; 232 + goto err_disable_powup; 233 + } 234 + 235 + /* Disable electrical isolation */ 236 + PM_WRITE(pm_reg, PM_READ(pm_reg) | PM_ISPOW); 237 + 238 + /* Repair memory */ 239 + PM_WRITE(pm_reg, PM_READ(pm_reg) | PM_MEMREP); 240 + start = ktime_get_ns(); 241 + while (!(PM_READ(pm_reg) & PM_MRDONE)) { 242 + cpu_relax(); 243 + if (ktime_get_ns() - start >= 1000) { 244 + dev_err(dev, "Timeout waiting for %s memory repair\n", 245 + pd->base.name); 246 + ret = -ETIMEDOUT; 247 + goto err_disable_ispow; 248 + } 249 + } 250 + 251 + /* Disable functional isolation */ 252 + PM_WRITE(pm_reg, PM_READ(pm_reg) | PM_ISFUNC); 253 + 254 + return 0; 255 + 256 + err_disable_ispow: 257 + PM_WRITE(pm_reg, PM_READ(pm_reg) & ~PM_ISPOW); 258 + err_disable_powup: 259 + PM_WRITE(pm_reg, PM_READ(pm_reg) & ~(PM_POWUP | PM_INRUSH_MASK)); 260 + return ret; 261 + } 262 + 263 + static int bcm2835_asb_power_on(struct bcm2835_power_domain *pd, 264 + u32 pm_reg, 265 + u32 asb_m_reg, 266 + u32 asb_s_reg, 267 + u32 reset_flags) 268 + { 269 + struct bcm2835_power *power = pd->power; 270 + int ret; 271 + 272 + ret = clk_prepare_enable(pd->clk); 273 + if (ret) { 274 + dev_err(power->dev, "Failed to enable clock for %s\n", 275 + pd->base.name); 276 + return ret; 277 + } 278 + 279 + /* Wait 32 clocks for reset to propagate, 1 us will be enough */ 280 + udelay(1); 281 + 282 + clk_disable_unprepare(pd->clk); 283 + 284 + /* Deassert the resets. */ 285 + PM_WRITE(pm_reg, PM_READ(pm_reg) | reset_flags); 286 + 287 + ret = clk_prepare_enable(pd->clk); 288 + if (ret) { 289 + dev_err(power->dev, "Failed to enable clock for %s\n", 290 + pd->base.name); 291 + goto err_enable_resets; 292 + } 293 + 294 + ret = bcm2835_asb_enable(power, asb_m_reg); 295 + if (ret) { 296 + dev_err(power->dev, "Failed to enable ASB master for %s\n", 297 + pd->base.name); 298 + goto err_disable_clk; 299 + } 300 + ret = bcm2835_asb_enable(power, asb_s_reg); 301 + if (ret) { 302 + dev_err(power->dev, "Failed to enable ASB slave for %s\n", 303 + pd->base.name); 304 + goto err_disable_asb_master; 305 + } 306 + 307 + return 0; 308 + 309 + err_disable_asb_master: 310 + bcm2835_asb_disable(power, asb_m_reg); 311 + err_disable_clk: 312 + clk_disable_unprepare(pd->clk); 313 + err_enable_resets: 314 + PM_WRITE(pm_reg, PM_READ(pm_reg) & ~reset_flags); 315 + return ret; 316 + } 317 + 318 + static int bcm2835_asb_power_off(struct bcm2835_power_domain *pd, 319 + u32 pm_reg, 320 + u32 asb_m_reg, 321 + u32 asb_s_reg, 322 + u32 reset_flags) 323 + { 324 + struct bcm2835_power *power = pd->power; 325 + int ret; 326 + 327 + ret = bcm2835_asb_disable(power, asb_s_reg); 328 + if (ret) { 329 + dev_warn(power->dev, "Failed to disable ASB slave for %s\n", 330 + pd->base.name); 331 + return ret; 332 + } 333 + ret = bcm2835_asb_disable(power, asb_m_reg); 334 + if (ret) { 335 + dev_warn(power->dev, "Failed to disable ASB master for %s\n", 336 + pd->base.name); 337 + bcm2835_asb_enable(power, asb_s_reg); 338 + return ret; 339 + } 340 + 341 + clk_disable_unprepare(pd->clk); 342 + 343 + /* Assert the resets. */ 344 + PM_WRITE(pm_reg, PM_READ(pm_reg) & ~reset_flags); 345 + 346 + return 0; 347 + } 348 + 349 + static int bcm2835_power_pd_power_on(struct generic_pm_domain *domain) 350 + { 351 + struct bcm2835_power_domain *pd = 352 + container_of(domain, struct bcm2835_power_domain, base); 353 + struct bcm2835_power *power = pd->power; 354 + 355 + switch (pd->domain) { 356 + case BCM2835_POWER_DOMAIN_GRAFX: 357 + return bcm2835_power_power_on(pd, PM_GRAFX); 358 + 359 + case BCM2835_POWER_DOMAIN_GRAFX_V3D: 360 + return bcm2835_asb_power_on(pd, PM_GRAFX, 361 + ASB_V3D_M_CTRL, ASB_V3D_S_CTRL, 362 + PM_V3DRSTN); 363 + 364 + case BCM2835_POWER_DOMAIN_IMAGE: 365 + return bcm2835_power_power_on(pd, PM_IMAGE); 366 + 367 + case BCM2835_POWER_DOMAIN_IMAGE_PERI: 368 + return bcm2835_asb_power_on(pd, PM_IMAGE, 369 + 0, 0, 370 + PM_PERIRSTN); 371 + 372 + case BCM2835_POWER_DOMAIN_IMAGE_ISP: 373 + return bcm2835_asb_power_on(pd, PM_IMAGE, 374 + ASB_ISP_M_CTRL, ASB_ISP_S_CTRL, 375 + PM_ISPRSTN); 376 + 377 + case BCM2835_POWER_DOMAIN_IMAGE_H264: 378 + return bcm2835_asb_power_on(pd, PM_IMAGE, 379 + ASB_H264_M_CTRL, ASB_H264_S_CTRL, 380 + PM_H264RSTN); 381 + 382 + case BCM2835_POWER_DOMAIN_USB: 383 + PM_WRITE(PM_USB, PM_USB_CTRLEN); 384 + return 0; 385 + 386 + case BCM2835_POWER_DOMAIN_DSI0: 387 + PM_WRITE(PM_DSI0, PM_DSI0_CTRLEN); 388 + PM_WRITE(PM_DSI0, PM_DSI0_CTRLEN | PM_DSI0_LDOHPEN); 389 + return 0; 390 + 391 + case BCM2835_POWER_DOMAIN_DSI1: 392 + PM_WRITE(PM_DSI1, PM_DSI1_CTRLEN); 393 + PM_WRITE(PM_DSI1, PM_DSI1_CTRLEN | PM_DSI1_LDOHPEN); 394 + return 0; 395 + 396 + case BCM2835_POWER_DOMAIN_CCP2TX: 397 + PM_WRITE(PM_CCP2TX, PM_CCP2TX_CTRLEN); 398 + PM_WRITE(PM_CCP2TX, PM_CCP2TX_CTRLEN | PM_CCP2TX_LDOEN); 399 + return 0; 400 + 401 + case BCM2835_POWER_DOMAIN_HDMI: 402 + PM_WRITE(PM_HDMI, PM_READ(PM_HDMI) | PM_HDMI_RSTDR); 403 + PM_WRITE(PM_HDMI, PM_READ(PM_HDMI) | PM_HDMI_CTRLEN); 404 + PM_WRITE(PM_HDMI, PM_READ(PM_HDMI) & ~PM_HDMI_LDOPD); 405 + usleep_range(100, 200); 406 + PM_WRITE(PM_HDMI, PM_READ(PM_HDMI) & ~PM_HDMI_RSTDR); 407 + return 0; 408 + 409 + default: 410 + dev_err(power->dev, "Invalid domain %d\n", pd->domain); 411 + return -EINVAL; 412 + } 413 + } 414 + 415 + static int bcm2835_power_pd_power_off(struct generic_pm_domain *domain) 416 + { 417 + struct bcm2835_power_domain *pd = 418 + container_of(domain, struct bcm2835_power_domain, base); 419 + struct bcm2835_power *power = pd->power; 420 + 421 + switch (pd->domain) { 422 + case BCM2835_POWER_DOMAIN_GRAFX: 423 + return bcm2835_power_power_off(pd, PM_GRAFX); 424 + 425 + case BCM2835_POWER_DOMAIN_GRAFX_V3D: 426 + return bcm2835_asb_power_off(pd, PM_GRAFX, 427 + ASB_V3D_M_CTRL, ASB_V3D_S_CTRL, 428 + PM_V3DRSTN); 429 + 430 + case BCM2835_POWER_DOMAIN_IMAGE: 431 + return bcm2835_power_power_off(pd, PM_IMAGE); 432 + 433 + case BCM2835_POWER_DOMAIN_IMAGE_PERI: 434 + return bcm2835_asb_power_off(pd, PM_IMAGE, 435 + 0, 0, 436 + PM_PERIRSTN); 437 + 438 + case BCM2835_POWER_DOMAIN_IMAGE_ISP: 439 + return bcm2835_asb_power_off(pd, PM_IMAGE, 440 + ASB_ISP_M_CTRL, ASB_ISP_S_CTRL, 441 + PM_ISPRSTN); 442 + 443 + case BCM2835_POWER_DOMAIN_IMAGE_H264: 444 + return bcm2835_asb_power_off(pd, PM_IMAGE, 445 + ASB_H264_M_CTRL, ASB_H264_S_CTRL, 446 + PM_H264RSTN); 447 + 448 + case BCM2835_POWER_DOMAIN_USB: 449 + PM_WRITE(PM_USB, 0); 450 + return 0; 451 + 452 + case BCM2835_POWER_DOMAIN_DSI0: 453 + PM_WRITE(PM_DSI0, PM_DSI0_CTRLEN); 454 + PM_WRITE(PM_DSI0, 0); 455 + return 0; 456 + 457 + case BCM2835_POWER_DOMAIN_DSI1: 458 + PM_WRITE(PM_DSI1, PM_DSI1_CTRLEN); 459 + PM_WRITE(PM_DSI1, 0); 460 + return 0; 461 + 462 + case BCM2835_POWER_DOMAIN_CCP2TX: 463 + PM_WRITE(PM_CCP2TX, PM_CCP2TX_CTRLEN); 464 + PM_WRITE(PM_CCP2TX, 0); 465 + return 0; 466 + 467 + case BCM2835_POWER_DOMAIN_HDMI: 468 + PM_WRITE(PM_HDMI, PM_READ(PM_HDMI) | PM_HDMI_LDOPD); 469 + PM_WRITE(PM_HDMI, PM_READ(PM_HDMI) & ~PM_HDMI_CTRLEN); 470 + return 0; 471 + 472 + default: 473 + dev_err(power->dev, "Invalid domain %d\n", pd->domain); 474 + return -EINVAL; 475 + } 476 + } 477 + 478 + static void 479 + bcm2835_init_power_domain(struct bcm2835_power *power, 480 + int pd_xlate_index, const char *name) 481 + { 482 + struct device *dev = power->dev; 483 + struct bcm2835_power_domain *dom = &power->domains[pd_xlate_index]; 484 + 485 + dom->clk = devm_clk_get(dev->parent, name); 486 + 487 + dom->base.name = name; 488 + dom->base.power_on = bcm2835_power_pd_power_on; 489 + dom->base.power_off = bcm2835_power_pd_power_off; 490 + 491 + dom->domain = pd_xlate_index; 492 + dom->power = power; 493 + 494 + /* XXX: on/off at boot? */ 495 + pm_genpd_init(&dom->base, NULL, true); 496 + 497 + power->pd_xlate.domains[pd_xlate_index] = &dom->base; 498 + } 499 + 500 + /** bcm2835_reset_reset - Resets a block that has a reset line in the 501 + * PM block. 502 + * 503 + * The consumer of the reset controller must have the power domain up 504 + * -- there's no reset ability with the power domain down. To reset 505 + * the sub-block, we just disable its access to memory through the 506 + * ASB, reset, and re-enable. 507 + */ 508 + static int bcm2835_reset_reset(struct reset_controller_dev *rcdev, 509 + unsigned long id) 510 + { 511 + struct bcm2835_power *power = container_of(rcdev, struct bcm2835_power, 512 + reset); 513 + struct bcm2835_power_domain *pd; 514 + int ret; 515 + 516 + switch (id) { 517 + case BCM2835_RESET_V3D: 518 + pd = &power->domains[BCM2835_POWER_DOMAIN_GRAFX_V3D]; 519 + break; 520 + case BCM2835_RESET_H264: 521 + pd = &power->domains[BCM2835_POWER_DOMAIN_IMAGE_H264]; 522 + break; 523 + case BCM2835_RESET_ISP: 524 + pd = &power->domains[BCM2835_POWER_DOMAIN_IMAGE_ISP]; 525 + break; 526 + default: 527 + dev_err(power->dev, "Bad reset id %ld\n", id); 528 + return -EINVAL; 529 + } 530 + 531 + ret = bcm2835_power_pd_power_off(&pd->base); 532 + if (ret) 533 + return ret; 534 + 535 + return bcm2835_power_pd_power_on(&pd->base); 536 + } 537 + 538 + static int bcm2835_reset_status(struct reset_controller_dev *rcdev, 539 + unsigned long id) 540 + { 541 + struct bcm2835_power *power = container_of(rcdev, struct bcm2835_power, 542 + reset); 543 + 544 + switch (id) { 545 + case BCM2835_RESET_V3D: 546 + return !PM_READ(PM_GRAFX & PM_V3DRSTN); 547 + case BCM2835_RESET_H264: 548 + return !PM_READ(PM_IMAGE & PM_H264RSTN); 549 + case BCM2835_RESET_ISP: 550 + return !PM_READ(PM_IMAGE & PM_ISPRSTN); 551 + default: 552 + return -EINVAL; 553 + } 554 + } 555 + 556 + const struct reset_control_ops bcm2835_reset_ops = { 557 + .reset = bcm2835_reset_reset, 558 + .status = bcm2835_reset_status, 559 + }; 560 + 561 + static const char *const power_domain_names[] = { 562 + [BCM2835_POWER_DOMAIN_GRAFX] = "grafx", 563 + [BCM2835_POWER_DOMAIN_GRAFX_V3D] = "v3d", 564 + 565 + [BCM2835_POWER_DOMAIN_IMAGE] = "image", 566 + [BCM2835_POWER_DOMAIN_IMAGE_PERI] = "peri_image", 567 + [BCM2835_POWER_DOMAIN_IMAGE_H264] = "h264", 568 + [BCM2835_POWER_DOMAIN_IMAGE_ISP] = "isp", 569 + 570 + [BCM2835_POWER_DOMAIN_USB] = "usb", 571 + [BCM2835_POWER_DOMAIN_DSI0] = "dsi0", 572 + [BCM2835_POWER_DOMAIN_DSI1] = "dsi1", 573 + [BCM2835_POWER_DOMAIN_CAM0] = "cam0", 574 + [BCM2835_POWER_DOMAIN_CAM1] = "cam1", 575 + [BCM2835_POWER_DOMAIN_CCP2TX] = "ccp2tx", 576 + [BCM2835_POWER_DOMAIN_HDMI] = "hdmi", 577 + }; 578 + 579 + static int bcm2835_power_probe(struct platform_device *pdev) 580 + { 581 + struct bcm2835_pm *pm = dev_get_drvdata(pdev->dev.parent); 582 + struct device *dev = &pdev->dev; 583 + struct bcm2835_power *power; 584 + static const struct { 585 + int parent, child; 586 + } domain_deps[] = { 587 + { BCM2835_POWER_DOMAIN_GRAFX, BCM2835_POWER_DOMAIN_GRAFX_V3D }, 588 + { BCM2835_POWER_DOMAIN_IMAGE, BCM2835_POWER_DOMAIN_IMAGE_PERI }, 589 + { BCM2835_POWER_DOMAIN_IMAGE, BCM2835_POWER_DOMAIN_IMAGE_H264 }, 590 + { BCM2835_POWER_DOMAIN_IMAGE, BCM2835_POWER_DOMAIN_IMAGE_ISP }, 591 + { BCM2835_POWER_DOMAIN_IMAGE_PERI, BCM2835_POWER_DOMAIN_USB }, 592 + { BCM2835_POWER_DOMAIN_IMAGE_PERI, BCM2835_POWER_DOMAIN_CAM0 }, 593 + { BCM2835_POWER_DOMAIN_IMAGE_PERI, BCM2835_POWER_DOMAIN_CAM1 }, 594 + }; 595 + int ret, i; 596 + u32 id; 597 + 598 + power = devm_kzalloc(dev, sizeof(*power), GFP_KERNEL); 599 + if (!power) 600 + return -ENOMEM; 601 + platform_set_drvdata(pdev, power); 602 + 603 + power->dev = dev; 604 + power->base = pm->base; 605 + power->asb = pm->asb; 606 + 607 + id = ASB_READ(ASB_AXI_BRDG_ID); 608 + if (id != 0x62726467 /* "BRDG" */) { 609 + dev_err(dev, "ASB register ID returned 0x%08x\n", id); 610 + return -ENODEV; 611 + } 612 + 613 + power->pd_xlate.domains = devm_kcalloc(dev, 614 + ARRAY_SIZE(power_domain_names), 615 + sizeof(*power->pd_xlate.domains), 616 + GFP_KERNEL); 617 + if (!power->pd_xlate.domains) 618 + return -ENOMEM; 619 + 620 + power->pd_xlate.num_domains = ARRAY_SIZE(power_domain_names); 621 + 622 + for (i = 0; i < ARRAY_SIZE(power_domain_names); i++) 623 + bcm2835_init_power_domain(power, i, power_domain_names[i]); 624 + 625 + for (i = 0; i < ARRAY_SIZE(domain_deps); i++) { 626 + pm_genpd_add_subdomain(&power->domains[domain_deps[i].parent].base, 627 + &power->domains[domain_deps[i].child].base); 628 + } 629 + 630 + power->reset.owner = THIS_MODULE; 631 + power->reset.nr_resets = BCM2835_RESET_COUNT; 632 + power->reset.ops = &bcm2835_reset_ops; 633 + power->reset.of_node = dev->parent->of_node; 634 + 635 + ret = devm_reset_controller_register(dev, &power->reset); 636 + if (ret) 637 + return ret; 638 + 639 + of_genpd_add_provider_onecell(dev->parent->of_node, &power->pd_xlate); 640 + 641 + dev_info(dev, "Broadcom BCM2835 power domains driver"); 642 + return 0; 643 + } 644 + 645 + static int bcm2835_power_remove(struct platform_device *pdev) 646 + { 647 + return 0; 648 + } 649 + 650 + static struct platform_driver bcm2835_power_driver = { 651 + .probe = bcm2835_power_probe, 652 + .remove = bcm2835_power_remove, 653 + .driver = { 654 + .name = "bcm2835-power", 655 + }, 656 + }; 657 + module_platform_driver(bcm2835_power_driver); 658 + 659 + MODULE_AUTHOR("Eric Anholt <eric@anholt.net>"); 660 + MODULE_DESCRIPTION("Driver for Broadcom BCM2835 PM power domains and reset"); 661 + MODULE_LICENSE("GPL");
+28
include/dt-bindings/soc/bcm2835-pm.h
··· 1 + /* SPDX-License-Identifier: (GPL-2.0+ OR MIT) */ 2 + 3 + #ifndef _DT_BINDINGS_ARM_BCM2835_PM_H 4 + #define _DT_BINDINGS_ARM_BCM2835_PM_H 5 + 6 + #define BCM2835_POWER_DOMAIN_GRAFX 0 7 + #define BCM2835_POWER_DOMAIN_GRAFX_V3D 1 8 + #define BCM2835_POWER_DOMAIN_IMAGE 2 9 + #define BCM2835_POWER_DOMAIN_IMAGE_PERI 3 10 + #define BCM2835_POWER_DOMAIN_IMAGE_ISP 4 11 + #define BCM2835_POWER_DOMAIN_IMAGE_H264 5 12 + #define BCM2835_POWER_DOMAIN_USB 6 13 + #define BCM2835_POWER_DOMAIN_DSI0 7 14 + #define BCM2835_POWER_DOMAIN_DSI1 8 15 + #define BCM2835_POWER_DOMAIN_CAM0 9 16 + #define BCM2835_POWER_DOMAIN_CAM1 10 17 + #define BCM2835_POWER_DOMAIN_CCP2TX 11 18 + #define BCM2835_POWER_DOMAIN_HDMI 12 19 + 20 + #define BCM2835_POWER_DOMAIN_COUNT 13 21 + 22 + #define BCM2835_RESET_V3D 0 23 + #define BCM2835_RESET_ISP 1 24 + #define BCM2835_RESET_H264 2 25 + 26 + #define BCM2835_RESET_COUNT 3 27 + 28 + #endif /* _DT_BINDINGS_ARM_BCM2835_PM_H */
+1
include/linux/mfd/bcm2835-pm.h
··· 8 8 struct bcm2835_pm { 9 9 struct device *dev; 10 10 void __iomem *base; 11 + void __iomem *asb; 11 12 }; 12 13 13 14 #endif /* BCM2835_MFD_PM_H */