From 670c672608a1ffcbc7ac0f872734843593bb8b15 Mon Sep 17 00:00:00 2001 From: Eric Anholt Date: Wed, 12 Dec 2018 15:51:48 -0800 Subject: 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 Acked-by: Rob Herring Acked-by: Stefan Wahren Signed-off-by: Stefan Wahren --- drivers/soc/bcm/Kconfig | 11 + drivers/soc/bcm/Makefile | 1 + drivers/soc/bcm/bcm2835-power.c | 661 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 673 insertions(+) create mode 100644 drivers/soc/bcm/bcm2835-power.c (limited to 'drivers/soc') diff --git a/drivers/soc/bcm/Kconfig b/drivers/soc/bcm/Kconfig index 055a845ed979..fe1af29560e9 100644 --- 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) diff --git a/drivers/soc/bcm/Makefile b/drivers/soc/bcm/Makefile index dc4fced72d21..c81df4b2403c 100644 --- 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/ diff --git a/drivers/soc/bcm/bcm2835-power.c b/drivers/soc/bcm/bcm2835-power.c new file mode 100644 index 000000000000..48412957ec7a --- /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 +#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 = 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 "); +MODULE_DESCRIPTION("Driver for Broadcom BCM2835 PM power domains and reset"); +MODULE_LICENSE("GPL"); -- cgit From b798d5a1b0eaf276f463262284e58a29b451063c Mon Sep 17 00:00:00 2001 From: Lucas Stach Date: Mon, 17 Dec 2018 16:31:51 +0100 Subject: soc: imx: gpcv2: handle additional power-down bits in handshake register Some of the i.MX8MQ domains have an additional control bit in the PU handshake (HSK) register. Documentation about this bit is a bit sparse at the moment, but it seems that it controls a power-down request to the AMBA domain bridge (ADB-400) attached to those domains. As the documentation doesn't desribe the usage of this bit yet, handle it in the same way as done in the ATF implementation. Signed-off-by: Lucas Stach Signed-off-by: Shawn Guo --- drivers/soc/imx/gpcv2.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) (limited to 'drivers/soc') diff --git a/drivers/soc/imx/gpcv2.c b/drivers/soc/imx/gpcv2.c index 8b4f48a2ca57..a8dd0cddb3d2 100644 --- a/drivers/soc/imx/gpcv2.c +++ b/drivers/soc/imx/gpcv2.c @@ -65,6 +65,12 @@ #define GPC_M4_PU_PDN_FLG 0x1bc +#define GPC_PU_PWRHSK 0x1fc + +#define IMX8M_GPU_HSK_PWRDNREQN BIT(6) +#define IMX8M_VPU_HSK_PWRDNREQN BIT(5) +#define IMX8M_DISP_HSK_PWRDNREQN BIT(4) + /* * The PGC offset values in Reference Manual * (Rev. 1, 01/2018 and the older ones) GPC chapter's @@ -102,6 +108,7 @@ struct imx_pgc_domain { const struct { u32 pxx; u32 map; + u32 hsk; } bits; const int voltage; @@ -142,6 +149,10 @@ static int imx_gpc_pu_pgc_sw_pxx_req(struct generic_pm_domain *genpd, regmap_update_bits(domain->regmap, GPC_PGC_CTRL(domain->pgc), GPC_PGC_CTRL_PCR, GPC_PGC_CTRL_PCR); + if (domain->bits.hsk) + regmap_update_bits(domain->regmap, GPC_PU_PWRHSK, + domain->bits.hsk, on ? domain->bits.hsk : 0); + regmap_update_bits(domain->regmap, offset, domain->bits.pxx, domain->bits.pxx); @@ -328,6 +339,7 @@ static const struct imx_pgc_domain imx8m_pgc_domains[] = { .bits = { .pxx = IMX8M_GPU_SW_Pxx_REQ, .map = IMX8M_GPU_A53_DOMAIN, + .hsk = IMX8M_GPU_HSK_PWRDNREQN, }, .pgc = IMX8M_PGC_GPU, }, @@ -339,6 +351,7 @@ static const struct imx_pgc_domain imx8m_pgc_domains[] = { .bits = { .pxx = IMX8M_VPU_SW_Pxx_REQ, .map = IMX8M_VPU_A53_DOMAIN, + .hsk = IMX8M_VPU_HSK_PWRDNREQN, }, .pgc = IMX8M_PGC_VPU, }, @@ -350,6 +363,7 @@ static const struct imx_pgc_domain imx8m_pgc_domains[] = { .bits = { .pxx = IMX8M_DISP_SW_Pxx_REQ, .map = IMX8M_DISP_A53_DOMAIN, + .hsk = IMX8M_DISP_HSK_PWRDNREQN, }, .pgc = IMX8M_PGC_DISP, }, @@ -390,7 +404,7 @@ static const struct imx_pgc_domain imx8m_pgc_domains[] = { static const struct regmap_range imx8m_yes_ranges[] = { regmap_reg_range(GPC_LPCR_A_CORE_BSC, - GPC_M4_PU_PDN_FLG), + GPC_PU_PWRHSK), regmap_reg_range(GPC_PGC_CTRL(IMX8M_PGC_MIPI), GPC_PGC_SR(IMX8M_PGC_MIPI)), regmap_reg_range(GPC_PGC_CTRL(IMX8M_PGC_PCIE1), -- cgit From ae1d2add26a4a0d34dc0615d304be2f341202e77 Mon Sep 17 00:00:00 2001 From: Lucas Stach Date: Mon, 17 Dec 2018 16:31:52 +0100 Subject: soc: imx: gpcv2: handle reset clocks Some power domains handled by the GPCv2 driver need to enable the clocks for devies inside the domain, so that the reset propagation and proper power-up sequencing happens. Handle them in the same way as on GPCv1. Signed-off-by: Lucas Stach Acked-by: Rob Herring Signed-off-by: Shawn Guo --- drivers/soc/imx/gpcv2.c | 60 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) (limited to 'drivers/soc') diff --git a/drivers/soc/imx/gpcv2.c b/drivers/soc/imx/gpcv2.c index a8dd0cddb3d2..176f473127b6 100644 --- a/drivers/soc/imx/gpcv2.c +++ b/drivers/soc/imx/gpcv2.c @@ -8,6 +8,7 @@ * Copyright 2015-2017 Pengutronix, Lucas Stach */ +#include #include #include #include @@ -98,10 +99,14 @@ #define GPC_PGC_CTRL_PCR BIT(0) +#define GPC_CLK_MAX 6 + struct imx_pgc_domain { struct generic_pm_domain genpd; struct regmap *regmap; struct regulator *regulator; + struct clk *clk[GPC_CLK_MAX]; + int num_clks; unsigned int pgc; @@ -132,7 +137,7 @@ static int imx_gpc_pu_pgc_sw_pxx_req(struct generic_pm_domain *genpd, const bool enable_power_control = !on; const bool has_regulator = !IS_ERR(domain->regulator); unsigned long deadline; - int ret = 0; + int i, ret = 0; regmap_update_bits(domain->regmap, GPC_PGC_CPU_MAPPING, domain->bits.map, domain->bits.map); @@ -145,6 +150,10 @@ static int imx_gpc_pu_pgc_sw_pxx_req(struct generic_pm_domain *genpd, } } + /* Enable reset clocks for all devices in the domain */ + for (i = 0; i < domain->num_clks; i++) + clk_prepare_enable(domain->clk[i]); + if (enable_power_control) regmap_update_bits(domain->regmap, GPC_PGC_CTRL(domain->pgc), GPC_PGC_CTRL_PCR, GPC_PGC_CTRL_PCR); @@ -190,6 +199,10 @@ static int imx_gpc_pu_pgc_sw_pxx_req(struct generic_pm_domain *genpd, regmap_update_bits(domain->regmap, GPC_PGC_CTRL(domain->pgc), GPC_PGC_CTRL_PCR, 0); + /* Disable reset clocks for all devices in the domain */ + for (i = 0; i < domain->num_clks; i++) + clk_disable_unprepare(domain->clk[i]); + if (has_regulator && !on) { int err; @@ -440,6 +453,41 @@ static const struct imx_pgc_domain_data imx8m_pgc_domain_data = { .reg_access_table = &imx8m_access_table, }; +static int imx_pgc_get_clocks(struct imx_pgc_domain *domain) +{ + int i, ret; + + for (i = 0; ; i++) { + struct clk *clk = of_clk_get(domain->dev->of_node, i); + if (IS_ERR(clk)) + break; + if (i >= GPC_CLK_MAX) { + dev_err(domain->dev, "more than %d clocks\n", + GPC_CLK_MAX); + ret = -EINVAL; + goto clk_err; + } + domain->clk[i] = clk; + } + domain->num_clks = i; + + return 0; + +clk_err: + while (i--) + clk_put(domain->clk[i]); + + return ret; +} + +static void imx_pgc_put_clocks(struct imx_pgc_domain *domain) +{ + int i; + + for (i = domain->num_clks - 1; i >= 0; i--) + clk_put(domain->clk[i]); +} + static int imx_pgc_domain_probe(struct platform_device *pdev) { struct imx_pgc_domain *domain = pdev->dev.platform_data; @@ -459,9 +507,17 @@ static int imx_pgc_domain_probe(struct platform_device *pdev) domain->voltage, domain->voltage); } + ret = imx_pgc_get_clocks(domain); + if (ret) { + if (ret != -EPROBE_DEFER) + dev_err(domain->dev, "Failed to get domain's clocks\n"); + return ret; + } + ret = pm_genpd_init(&domain->genpd, NULL, true); if (ret) { dev_err(domain->dev, "Failed to init power domain\n"); + imx_pgc_put_clocks(domain); return ret; } @@ -470,6 +526,7 @@ static int imx_pgc_domain_probe(struct platform_device *pdev) if (ret) { dev_err(domain->dev, "Failed to add genpd provider\n"); pm_genpd_remove(&domain->genpd); + imx_pgc_put_clocks(domain); } return ret; @@ -481,6 +538,7 @@ static int imx_pgc_domain_remove(struct platform_device *pdev) of_genpd_del_provider(domain->dev->of_node); pm_genpd_remove(&domain->genpd); + imx_pgc_put_clocks(domain); return 0; } -- cgit From f1e250bf365962519c96a1255b2fe34b6c0d6c60 Mon Sep 17 00:00:00 2001 From: Ioana Ciocoi Radulescu Date: Fri, 14 Dec 2018 16:04:05 +0000 Subject: soc: fsl: dpio: Add prefetch instruction In dpaa2_io_store_next(), if the current dequeue entry is not the last, prefetch the next one as most likely it will be requested shortly by the consumer driver. This brings a ~3% improvement for dpaa2 ethernet driver IP forwarding with small size frames. Signed-off-by: Ioana Radulescu Signed-off-by: Li Yang --- drivers/soc/fsl/dpio/dpio-service.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/soc') diff --git a/drivers/soc/fsl/dpio/dpio-service.c b/drivers/soc/fsl/dpio/dpio-service.c index bc801934602a..5389336463c0 100644 --- a/drivers/soc/fsl/dpio/dpio-service.c +++ b/drivers/soc/fsl/dpio/dpio-service.c @@ -630,6 +630,7 @@ struct dpaa2_dq *dpaa2_io_store_next(struct dpaa2_io_store *s, int *is_last) if (!(dpaa2_dq_flags(ret) & DPAA2_DQ_STAT_VALIDFRAME)) ret = NULL; } else { + prefetch(&s->vaddr[s->idx]); *is_last = 0; } -- cgit From 8a533a7de21aa69cf9a3e55223c819858fbc7da2 Mon Sep 17 00:00:00 2001 From: Ioana Ciocoi Radulescu Date: Fri, 14 Dec 2018 16:04:06 +0000 Subject: soc: fsl: dpio: Change bpid type to u16 In all QBMan registers, the buffer pool id field is two bytes long. The low level qbman APIs reflect this, but the high level DPIO ones use u32. Modify them in order to avoid implicit downcasts. Signed-off-by: Ioana Radulescu Signed-off-by: Li Yang --- drivers/soc/fsl/dpio/dpio-service.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/soc') diff --git a/drivers/soc/fsl/dpio/dpio-service.c b/drivers/soc/fsl/dpio/dpio-service.c index 5389336463c0..b9539ef2c3cd 100644 --- a/drivers/soc/fsl/dpio/dpio-service.c +++ b/drivers/soc/fsl/dpio/dpio-service.c @@ -473,7 +473,7 @@ EXPORT_SYMBOL_GPL(dpaa2_io_service_enqueue_qd); * Return 0 for success, and negative error code for failure. */ int dpaa2_io_service_release(struct dpaa2_io *d, - u32 bpid, + u16 bpid, const u64 *buffers, unsigned int num_buffers) { @@ -502,7 +502,7 @@ EXPORT_SYMBOL_GPL(dpaa2_io_service_release); * Eg. if the buffer pool is empty, this will return zero. */ int dpaa2_io_service_acquire(struct dpaa2_io *d, - u32 bpid, + u16 bpid, u64 *buffers, unsigned int num_buffers) { -- cgit From 3c0d64e867ed78a782a8a00d3b519396d9c5a2db Mon Sep 17 00:00:00 2001 From: Li Yang Date: Fri, 11 Jan 2019 15:55:57 -0600 Subject: soc: fsl: guts: reuse machine name from device tree Reuse the string machine from the device tree data structure instead of duplicating the data again. This also prevents a potential memory allocation failure that was not handled previously. Also fixes an early put of root device node. Reported-by: Nicholas Mc Guire Signed-off-by: Li Yang --- drivers/soc/fsl/guts.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'drivers/soc') diff --git a/drivers/soc/fsl/guts.c b/drivers/soc/fsl/guts.c index 302e0c8d69d9..4f9655087bd7 100644 --- a/drivers/soc/fsl/guts.c +++ b/drivers/soc/fsl/guts.c @@ -32,6 +32,7 @@ struct fsl_soc_die_attr { static struct guts *guts; static struct soc_device_attribute soc_dev_attr; static struct soc_device *soc_dev; +static struct device_node *root; /* SoC die attribute definition for QorIQ platform */ @@ -132,7 +133,7 @@ EXPORT_SYMBOL(fsl_guts_get_svr); static int fsl_guts_probe(struct platform_device *pdev) { - struct device_node *root, *np = pdev->dev.of_node; + struct device_node *np = pdev->dev.of_node; struct device *dev = &pdev->dev; struct resource *res; const struct fsl_soc_die_attr *soc_die; @@ -155,9 +156,8 @@ static int fsl_guts_probe(struct platform_device *pdev) root = of_find_node_by_path("/"); if (of_property_read_string(root, "model", &machine)) of_property_read_string_index(root, "compatible", 0, &machine); - of_node_put(root); if (machine) - soc_dev_attr.machine = devm_kstrdup(dev, machine, GFP_KERNEL); + soc_dev_attr.machine = machine; svr = fsl_guts_get_svr(); soc_die = fsl_soc_die_match(svr, fsl_soc_die); @@ -192,6 +192,7 @@ static int fsl_guts_probe(struct platform_device *pdev) static int fsl_guts_remove(struct platform_device *dev) { soc_device_unregister(soc_dev); + of_node_put(root); return 0; } -- cgit From 9b0bb07328f2375b2b6d3f8343edb1ee1ed39307 Mon Sep 17 00:00:00 2001 From: Abel Vesa Date: Sat, 12 Jan 2019 08:56:31 +0000 Subject: soc: imx: Break dependency on SOC_IMX8MQ for GPCv2 Since this is going to be used on more SoCs than just i.MX8MQ, make the dependency here more generic by using ARCH_MXC instead. Also remove the SOC_IMX7D since it is also included by the ARCH_MXC. Signed-off-by: Abel Vesa Reviewed-by: Dong Aisheng Signed-off-by: Shawn Guo --- drivers/soc/imx/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/soc') diff --git a/drivers/soc/imx/Kconfig b/drivers/soc/imx/Kconfig index 2112d18dbb7b..d80f899d22f9 100644 --- a/drivers/soc/imx/Kconfig +++ b/drivers/soc/imx/Kconfig @@ -2,7 +2,7 @@ menu "i.MX SoC drivers" config IMX_GPCV2_PM_DOMAINS bool "i.MX GPCv2 PM domains" - depends on SOC_IMX7D || SOC_IMX8MQ || (COMPILE_TEST && OF) + depends on ARCH_MXC || (COMPILE_TEST && OF) depends on PM select PM_GENERIC_DOMAINS default y if SOC_IMX7D -- cgit From bbe3a66c3f5a65fb3d702351bac2a6033944d389 Mon Sep 17 00:00:00 2001 From: Rajendra Nayak Date: Thu, 10 Jan 2019 09:32:04 +0530 Subject: soc: qcom: rpmpd: Add a Power domain driver to model corners The Power domains for corners just pass the performance state set by the consumers to the RPM (Remote Power manager) which then takes care of setting the appropriate voltage on the corresponding rails to meet the performance needs. We add all power domain data needed on msm8996 here. This driver can easily be extended by adding data for other qualcomm SoCs as well. Reviewed-by: Ulf Hansson Acked-by: Rob Herring Reviewed-by: Stephen Boyd Signed-off-by: Rajendra Nayak Signed-off-by: Viresh Kumar Signed-off-by: Bjorn Andersson Signed-off-by: Andy Gross --- drivers/soc/qcom/Kconfig | 9 ++ drivers/soc/qcom/Makefile | 1 + drivers/soc/qcom/rpmpd.c | 282 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 292 insertions(+) create mode 100644 drivers/soc/qcom/rpmpd.c (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index fcbf8a2e4080..df5cd9fa0d5e 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -98,6 +98,15 @@ config QCOM_RPMH of hardware components aggregate requests for these resources and help apply the aggregated state on the resource. +config QCOM_RPMPD + bool "Qualcomm RPM Power domain driver" + depends on MFD_QCOM_RPM && QCOM_SMD_RPM + help + QCOM RPM Power domain driver to support power-domains with + performance states. The driver communicates a performance state + value to RPM which then translates it into corresponding voltage + for the voltage rail. + config QCOM_SMEM tristate "Qualcomm Shared Memory Manager (SMEM)" depends on ARCH_QCOM || COMPILE_TEST diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index f25b54cd6cf8..f1b25fdcf2ad 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -21,3 +21,4 @@ obj-$(CONFIG_QCOM_WCNSS_CTRL) += wcnss_ctrl.o obj-$(CONFIG_QCOM_APR) += apr.o obj-$(CONFIG_QCOM_LLCC) += llcc-slice.o obj-$(CONFIG_QCOM_SDM845_LLCC) += llcc-sdm845.o +obj-$(CONFIG_QCOM_RPMPD) += rpmpd.o diff --git a/drivers/soc/qcom/rpmpd.c b/drivers/soc/qcom/rpmpd.c new file mode 100644 index 000000000000..7715ba7c9157 --- /dev/null +++ b/drivers/soc/qcom/rpmpd.c @@ -0,0 +1,282 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2017-2018, The Linux Foundation. All rights reserved. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define domain_to_rpmpd(domain) container_of(domain, struct rpmpd, pd) + +/* Resource types */ +#define RPMPD_SMPA 0x61706d73 +#define RPMPD_LDOA 0x616f646c + +/* Operation Keys */ +#define KEY_CORNER 0x6e726f63 /* corn */ +#define KEY_ENABLE 0x6e657773 /* swen */ +#define KEY_FLOOR_CORNER 0x636676 /* vfc */ + +#define DEFINE_RPMPD_CORNER_SMPA(_platform, _name, _active, r_id) \ + static struct rpmpd _platform##_##_active; \ + static struct rpmpd _platform##_##_name = { \ + .pd = { .name = #_name, }, \ + .peer = &_platform##_##_active, \ + .res_type = RPMPD_SMPA, \ + .res_id = r_id, \ + .key = KEY_CORNER, \ + }; \ + static struct rpmpd _platform##_##_active = { \ + .pd = { .name = #_active, }, \ + .peer = &_platform##_##_name, \ + .active_only = true, \ + .res_type = RPMPD_SMPA, \ + .res_id = r_id, \ + .key = KEY_CORNER, \ + } + +#define DEFINE_RPMPD_CORNER_LDOA(_platform, _name, r_id) \ + static struct rpmpd _platform##_##_name = { \ + .pd = { .name = #_name, }, \ + .res_type = RPMPD_LDOA, \ + .res_id = r_id, \ + .key = KEY_CORNER, \ + } + +#define DEFINE_RPMPD_VFC(_platform, _name, r_id, r_type) \ + static struct rpmpd _platform##_##_name = { \ + .pd = { .name = #_name, }, \ + .res_type = r_type, \ + .res_id = r_id, \ + .key = KEY_FLOOR_CORNER, \ + } + +#define DEFINE_RPMPD_VFC_SMPA(_platform, _name, r_id) \ + DEFINE_RPMPD_VFC(_platform, _name, r_id, RPMPD_SMPA) + +#define DEFINE_RPMPD_VFC_LDOA(_platform, _name, r_id) \ + DEFINE_RPMPD_VFC(_platform, _name, r_id, RPMPD_LDOA) + +struct rpmpd_req { + __le32 key; + __le32 nbytes; + __le32 value; +}; + +struct rpmpd { + struct generic_pm_domain pd; + struct rpmpd *peer; + const bool active_only; + unsigned int corner; + bool enabled; + const char *res_name; + const int res_type; + const int res_id; + struct qcom_smd_rpm *rpm; + __le32 key; +}; + +struct rpmpd_desc { + struct rpmpd **rpmpds; + size_t num_pds; +}; + +static DEFINE_MUTEX(rpmpd_lock); + +/* msm8996 RPM Power domains */ +DEFINE_RPMPD_CORNER_SMPA(msm8996, vddcx, vddcx_ao, 1); +DEFINE_RPMPD_CORNER_SMPA(msm8996, vddmx, vddmx_ao, 2); +DEFINE_RPMPD_CORNER_LDOA(msm8996, vddsscx, 26); + +DEFINE_RPMPD_VFC_SMPA(msm8996, vddcx_vfc, 1); +DEFINE_RPMPD_VFC_LDOA(msm8996, vddsscx_vfc, 26); + +static struct rpmpd *msm8996_rpmpds[] = { + [MSM8996_VDDCX] = &msm8996_vddcx, + [MSM8996_VDDCX_AO] = &msm8996_vddcx_ao, + [MSM8996_VDDCX_VFC] = &msm8996_vddcx_vfc, + [MSM8996_VDDMX] = &msm8996_vddmx, + [MSM8996_VDDMX_AO] = &msm8996_vddmx_ao, + [MSM8996_VDDSSCX] = &msm8996_vddsscx, + [MSM8996_VDDSSCX_VFC] = &msm8996_vddsscx_vfc, +}; + +static const struct rpmpd_desc msm8996_desc = { + .rpmpds = msm8996_rpmpds, + .num_pds = ARRAY_SIZE(msm8996_rpmpds), +}; + +static const struct of_device_id rpmpd_match_table[] = { + { .compatible = "qcom,msm8996-rpmpd", .data = &msm8996_desc }, + { } +}; + +static int rpmpd_send_enable(struct rpmpd *pd, bool enable) +{ + struct rpmpd_req req = { + .key = KEY_ENABLE, + .nbytes = cpu_to_le32(sizeof(u32)), + .value = cpu_to_le32(enable), + }; + + return qcom_rpm_smd_write(pd->rpm, QCOM_RPM_ACTIVE_STATE, pd->res_type, + pd->res_id, &req, sizeof(req)); +} + +static int rpmpd_send_corner(struct rpmpd *pd, int state, unsigned int corner) +{ + struct rpmpd_req req = { + .key = pd->key, + .nbytes = cpu_to_le32(sizeof(u32)), + .value = cpu_to_le32(corner), + }; + + return qcom_rpm_smd_write(pd->rpm, state, pd->res_type, pd->res_id, + &req, sizeof(req)); +}; + +static void to_active_sleep(struct rpmpd *pd, unsigned int corner, + unsigned int *active, unsigned int *sleep) +{ + *active = corner; + + if (pd->active_only) + *sleep = 0; + else + *sleep = *active; +} + +static int rpmpd_aggregate_corner(struct rpmpd *pd) +{ + int ret; + struct rpmpd *peer = pd->peer; + unsigned int active_corner, sleep_corner; + unsigned int this_active_corner = 0, this_sleep_corner = 0; + unsigned int peer_active_corner = 0, peer_sleep_corner = 0; + + to_active_sleep(pd, pd->corner, &this_active_corner, &this_sleep_corner); + + if (peer && peer->enabled) + to_active_sleep(peer, peer->corner, &peer_active_corner, + &peer_sleep_corner); + + active_corner = max(this_active_corner, peer_active_corner); + + ret = rpmpd_send_corner(pd, QCOM_RPM_ACTIVE_STATE, active_corner); + if (ret) + return ret; + + sleep_corner = max(this_sleep_corner, peer_sleep_corner); + + return rpmpd_send_corner(pd, QCOM_RPM_SLEEP_STATE, sleep_corner); +} + +static int rpmpd_power_on(struct generic_pm_domain *domain) +{ + int ret; + struct rpmpd *pd = domain_to_rpmpd(domain); + + mutex_lock(&rpmpd_lock); + + ret = rpmpd_send_enable(pd, true); + if (ret) + goto out; + + pd->enabled = true; + + if (pd->corner) + ret = rpmpd_aggregate_corner(pd); + +out: + mutex_unlock(&rpmpd_lock); + + return ret; +} + +static int rpmpd_power_off(struct generic_pm_domain *domain) +{ + int ret; + struct rpmpd *pd = domain_to_rpmpd(domain); + + mutex_lock(&rpmpd_lock); + + ret = rpmpd_send_enable(pd, false); + if (!ret) + pd->enabled = false; + + mutex_unlock(&rpmpd_lock); + + return ret; +} + +static int rpmpd_probe(struct platform_device *pdev) +{ + int i; + size_t num; + struct genpd_onecell_data *data; + struct qcom_smd_rpm *rpm; + struct rpmpd **rpmpds; + const struct rpmpd_desc *desc; + + rpm = dev_get_drvdata(pdev->dev.parent); + if (!rpm) { + dev_err(&pdev->dev, "Unable to retrieve handle to RPM\n"); + return -ENODEV; + } + + desc = of_device_get_match_data(&pdev->dev); + if (!desc) + return -EINVAL; + + rpmpds = desc->rpmpds; + num = desc->num_pds; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->domains = devm_kcalloc(&pdev->dev, num, sizeof(*data->domains), + GFP_KERNEL); + data->num_domains = num; + + for (i = 0; i < num; i++) { + if (!rpmpds[i]) { + dev_warn(&pdev->dev, "rpmpds[] with empty entry at index=%d\n", + i); + continue; + } + + rpmpds[i]->rpm = rpm; + rpmpds[i]->pd.power_off = rpmpd_power_off; + rpmpds[i]->pd.power_on = rpmpd_power_on; + pm_genpd_init(&rpmpds[i]->pd, NULL, true); + + data->domains[i] = &rpmpds[i]->pd; + } + + return of_genpd_add_provider_onecell(pdev->dev.of_node, data); +} + +static struct platform_driver rpmpd_driver = { + .driver = { + .name = "qcom-rpmpd", + .of_match_table = rpmpd_match_table, + .suppress_bind_attrs = true, + }, + .probe = rpmpd_probe, +}; + +static int __init rpmpd_init(void) +{ + return platform_driver_register(&rpmpd_driver); +} +core_initcall(rpmpd_init); -- cgit From 075d3db8d10d82bb86777df3f81e1b83caeb630a Mon Sep 17 00:00:00 2001 From: Rajendra Nayak Date: Thu, 10 Jan 2019 09:32:05 +0530 Subject: soc: qcom: rpmpd: Add support for get/set performance state Add support for the .set_performace_state() and .opp_to_performance_state() callbacks in the rpmpd driver. Reviewed-by: Ulf Hansson Reviewed-by: Stephen Boyd Signed-off-by: Rajendra Nayak Signed-off-by: Viresh Kumar Signed-off-by: Bjorn Andersson Signed-off-by: Andy Gross --- drivers/soc/qcom/rpmpd.c | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/rpmpd.c b/drivers/soc/qcom/rpmpd.c index 7715ba7c9157..35a711017d6f 100644 --- a/drivers/soc/qcom/rpmpd.c +++ b/drivers/soc/qcom/rpmpd.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -26,6 +27,8 @@ #define KEY_ENABLE 0x6e657773 /* swen */ #define KEY_FLOOR_CORNER 0x636676 /* vfc */ +#define MAX_RPMPD_STATE 6 + #define DEFINE_RPMPD_CORNER_SMPA(_platform, _name, _active, r_id) \ static struct rpmpd _platform##_##_active; \ static struct rpmpd _platform##_##_name = { \ @@ -218,6 +221,36 @@ static int rpmpd_power_off(struct generic_pm_domain *domain) return ret; } +static int rpmpd_set_performance(struct generic_pm_domain *domain, + unsigned int state) +{ + int ret = 0; + struct rpmpd *pd = domain_to_rpmpd(domain); + + if (state > MAX_RPMPD_STATE) + goto out; + + mutex_lock(&rpmpd_lock); + + pd->corner = state; + + if (!pd->enabled && pd->key != KEY_FLOOR_CORNER) + goto out; + + ret = rpmpd_aggregate_corner(pd); + +out: + mutex_unlock(&rpmpd_lock); + + return ret; +} + +static unsigned int rpmpd_get_performance(struct generic_pm_domain *genpd, + struct dev_pm_opp *opp) +{ + return dev_pm_opp_get_level(opp); +} + static int rpmpd_probe(struct platform_device *pdev) { int i; @@ -258,6 +291,8 @@ static int rpmpd_probe(struct platform_device *pdev) rpmpds[i]->rpm = rpm; rpmpds[i]->pd.power_off = rpmpd_power_off; rpmpds[i]->pd.power_on = rpmpd_power_on; + rpmpds[i]->pd.set_performance_state = rpmpd_set_performance; + rpmpds[i]->pd.opp_to_performance_state = rpmpd_get_performance; pm_genpd_init(&rpmpds[i]->pd, NULL, true); data->domains[i] = &rpmpds[i]->pd; -- cgit From 279b7e8a62cc4f524dac49ac3ab5bc401a965422 Mon Sep 17 00:00:00 2001 From: Rajendra Nayak Date: Thu, 10 Jan 2019 09:32:07 +0530 Subject: soc: qcom: rpmhpd: Add RPMh power domain driver The RPMh power domain driver aggregates the corner votes from various consumers for the ARC resources and communicates it to RPMh. With RPMh we use 2 different numbering space for corners, one used by the clients to express their performance needs, and another used to communicate to RPMh hardware. The clients express their performance requirements using a sparse numbering space which are mapped to meaningful levels like RET, SVS, NOMINAL, TURBO etc which then get mapped to another number space between 0 and 15 which is communicated to RPMh. The sparse number space, also referred to as vlvl is mapped to the continuous number space of 0 to 15, also referred to as hlvl, using command DB. Some power domain clients could request a performance state only while the CPU is active, while some others could request for a certain performance state all the time regardless of the state of the CPU. We handle this by internally aggregating the votes from both type of clients and then send the aggregated votes to RPMh. There are also 3 different types of votes that are comunicated to RPMh for every resource. 1. ACTIVE_ONLY: This specifies the requirement for the resource when the CPU is active 2. SLEEP: This specifies the requirement for the resource when the CPU is going to sleep 3. WAKE_ONLY: This specifies the requirement for the resource when the CPU is coming out of sleep to active state We add data for all power domains on sdm845 SoC as part of the patch. The driver can be extended to support other SoCs which support RPMh Reviewed-by: Ulf Hansson Reviewed-by: Stephen Boyd Signed-off-by: Rajendra Nayak Signed-off-by: Bjorn Andersson Signed-off-by: Andy Gross --- drivers/soc/qcom/Kconfig | 9 ++ drivers/soc/qcom/Makefile | 1 + drivers/soc/qcom/rpmhpd.c | 395 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 405 insertions(+) create mode 100644 drivers/soc/qcom/rpmhpd.c (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index df5cd9fa0d5e..6241d3e3b115 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -98,6 +98,15 @@ config QCOM_RPMH of hardware components aggregate requests for these resources and help apply the aggregated state on the resource. +config QCOM_RPMHPD + bool "Qualcomm RPMh Power domain driver" + depends on QCOM_RPMH && QCOM_COMMAND_DB + help + QCOM RPMh Power domain driver to support power-domains with + performance states. The driver communicates a performance state + value to RPMh which then translates it into corresponding voltage + for the voltage rail. + config QCOM_RPMPD bool "Qualcomm RPM Power domain driver" depends on MFD_QCOM_RPM && QCOM_SMD_RPM diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index f1b25fdcf2ad..ffe519b0cb66 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -21,4 +21,5 @@ obj-$(CONFIG_QCOM_WCNSS_CTRL) += wcnss_ctrl.o obj-$(CONFIG_QCOM_APR) += apr.o obj-$(CONFIG_QCOM_LLCC) += llcc-slice.o obj-$(CONFIG_QCOM_SDM845_LLCC) += llcc-sdm845.o +obj-$(CONFIG_QCOM_RPMHPD) += rpmhpd.o obj-$(CONFIG_QCOM_RPMPD) += rpmpd.o diff --git a/drivers/soc/qcom/rpmhpd.c b/drivers/soc/qcom/rpmhpd.c new file mode 100644 index 000000000000..d3f819c1a90c --- /dev/null +++ b/drivers/soc/qcom/rpmhpd.c @@ -0,0 +1,395 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2018, The Linux Foundation. All rights reserved.*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define domain_to_rpmhpd(domain) container_of(domain, struct rpmhpd, pd) + +#define RPMH_ARC_MAX_LEVELS 16 + +/** + * struct rpmhpd - top level RPMh power domain resource data structure + * @dev: rpmh power domain controller device + * @pd: generic_pm_domain corrresponding to the power domain + * @peer: A peer power domain in case Active only Voting is + * supported + * @active_only: True if it represents an Active only peer + * @level: An array of level (vlvl) to corner (hlvl) mappings + * derived from cmd-db + * @level_count: Number of levels supported by the power domain. max + * being 16 (0 - 15) + * @enabled: true if the power domain is enabled + * @res_name: Resource name used for cmd-db lookup + * @addr: Resource address as looped up using resource name from + * cmd-db + */ +struct rpmhpd { + struct device *dev; + struct generic_pm_domain pd; + struct generic_pm_domain *parent; + struct rpmhpd *peer; + const bool active_only; + unsigned int corner; + unsigned int active_corner; + u32 level[RPMH_ARC_MAX_LEVELS]; + size_t level_count; + bool enabled; + const char *res_name; + u32 addr; +}; + +struct rpmhpd_desc { + struct rpmhpd **rpmhpds; + size_t num_pds; +}; + +static DEFINE_MUTEX(rpmhpd_lock); + +/* SDM845 RPMH powerdomains */ + +static struct rpmhpd sdm845_ebi = { + .pd = { .name = "ebi", }, + .res_name = "ebi.lvl", +}; + +static struct rpmhpd sdm845_lmx = { + .pd = { .name = "lmx", }, + .res_name = "lmx.lvl", +}; + +static struct rpmhpd sdm845_lcx = { + .pd = { .name = "lcx", }, + .res_name = "lcx.lvl", +}; + +static struct rpmhpd sdm845_gfx = { + .pd = { .name = "gfx", }, + .res_name = "gfx.lvl", +}; + +static struct rpmhpd sdm845_mss = { + .pd = { .name = "mss", }, + .res_name = "mss.lvl", +}; + +static struct rpmhpd sdm845_mx_ao; +static struct rpmhpd sdm845_mx = { + .pd = { .name = "mx", }, + .peer = &sdm845_mx_ao, + .res_name = "mx.lvl", +}; + +static struct rpmhpd sdm845_mx_ao = { + .pd = { .name = "mx_ao", }, + .peer = &sdm845_mx, + .res_name = "mx.lvl", +}; + +static struct rpmhpd sdm845_cx_ao; +static struct rpmhpd sdm845_cx = { + .pd = { .name = "cx", }, + .peer = &sdm845_cx_ao, + .res_name = "cx.lvl", +}; + +static struct rpmhpd sdm845_cx_ao = { + .pd = { .name = "cx_ao", }, + .peer = &sdm845_cx, + .res_name = "cx.lvl", +}; + +static struct rpmhpd *sdm845_rpmhpds[] = { + [SDM845_EBI] = &sdm845_ebi, + [SDM845_MX] = &sdm845_mx, + [SDM845_MX_AO] = &sdm845_mx_ao, + [SDM845_CX] = &sdm845_cx, + [SDM845_CX_AO] = &sdm845_cx_ao, + [SDM845_LMX] = &sdm845_lmx, + [SDM845_LCX] = &sdm845_lcx, + [SDM845_GFX] = &sdm845_gfx, + [SDM845_MSS] = &sdm845_mss, +}; + +static const struct rpmhpd_desc sdm845_desc = { + .rpmhpds = sdm845_rpmhpds, + .num_pds = ARRAY_SIZE(sdm845_rpmhpds), +}; + +static const struct of_device_id rpmhpd_match_table[] = { + { .compatible = "qcom,sdm845-rpmhpd", .data = &sdm845_desc }, + { } +}; + +static int rpmhpd_send_corner(struct rpmhpd *pd, int state, + unsigned int corner, bool sync) +{ + struct tcs_cmd cmd = { + .addr = pd->addr, + .data = corner, + }; + + /* + * Wait for an ack only when we are increasing the + * perf state of the power domain + */ + if (sync) + return rpmh_write(pd->dev, state, &cmd, 1); + else + return rpmh_write_async(pd->dev, state, &cmd, 1); +} + +static void to_active_sleep(struct rpmhpd *pd, unsigned int corner, + unsigned int *active, unsigned int *sleep) +{ + *active = corner; + + if (pd->active_only) + *sleep = 0; + else + *sleep = *active; +} + +/* + * This function is used to aggregate the votes across the active only + * resources and its peers. The aggregated votes are sent to RPMh as + * ACTIVE_ONLY votes (which take effect immediately), as WAKE_ONLY votes + * (applied by RPMh on system wakeup) and as SLEEP votes (applied by RPMh + * on system sleep). + * We send ACTIVE_ONLY votes for resources without any peers. For others, + * which have an active only peer, all 3 votes are sent. + */ +static int rpmhpd_aggregate_corner(struct rpmhpd *pd, unsigned int corner) +{ + int ret; + struct rpmhpd *peer = pd->peer; + unsigned int active_corner, sleep_corner; + unsigned int this_active_corner = 0, this_sleep_corner = 0; + unsigned int peer_active_corner = 0, peer_sleep_corner = 0; + + to_active_sleep(pd, corner, &this_active_corner, &this_sleep_corner); + + if (peer && peer->enabled) + to_active_sleep(peer, peer->corner, &peer_active_corner, + &peer_sleep_corner); + + active_corner = max(this_active_corner, peer_active_corner); + + ret = rpmhpd_send_corner(pd, RPMH_ACTIVE_ONLY_STATE, active_corner, + active_corner > pd->active_corner); + if (ret) + return ret; + + pd->active_corner = active_corner; + + if (peer) { + peer->active_corner = active_corner; + + ret = rpmhpd_send_corner(pd, RPMH_WAKE_ONLY_STATE, + active_corner, false); + if (ret) + return ret; + + sleep_corner = max(this_sleep_corner, peer_sleep_corner); + + return rpmhpd_send_corner(pd, RPMH_SLEEP_STATE, sleep_corner, + false); + } + + return ret; +} + +static int rpmhpd_power_on(struct generic_pm_domain *domain) +{ + struct rpmhpd *pd = domain_to_rpmhpd(domain); + int ret = 0; + + mutex_lock(&rpmhpd_lock); + + if (pd->corner) + ret = rpmhpd_aggregate_corner(pd, pd->corner); + + if (!ret) + pd->enabled = true; + + mutex_unlock(&rpmhpd_lock); + + return ret; +} + +static int rpmhpd_power_off(struct generic_pm_domain *domain) +{ + struct rpmhpd *pd = domain_to_rpmhpd(domain); + int ret = 0; + + mutex_lock(&rpmhpd_lock); + + ret = rpmhpd_aggregate_corner(pd, pd->level[0]); + + if (!ret) + pd->enabled = false; + + mutex_unlock(&rpmhpd_lock); + + return ret; +} + +static int rpmhpd_set_performance_state(struct generic_pm_domain *domain, + unsigned int level) +{ + struct rpmhpd *pd = domain_to_rpmhpd(domain); + int ret = 0, i; + + mutex_lock(&rpmhpd_lock); + + for (i = 0; i < pd->level_count; i++) + if (level <= pd->level[i]) + break; + + /* + * If the level requested is more than that supported by the + * max corner, just set it to max anyway. + */ + if (i == pd->level_count) + i--; + + if (pd->enabled) { + ret = rpmhpd_aggregate_corner(pd, i); + if (ret) + goto out; + } + + pd->corner = i; +out: + mutex_unlock(&rpmhpd_lock); + + return ret; +} + +static unsigned int rpmhpd_get_performance_state(struct generic_pm_domain *genpd, + struct dev_pm_opp *opp) +{ + return dev_pm_opp_get_level(opp); +} + +static int rpmhpd_update_level_mapping(struct rpmhpd *rpmhpd) +{ + int i; + const u16 *buf; + + buf = cmd_db_read_aux_data(rpmhpd->res_name, &rpmhpd->level_count); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + /* 2 bytes used for each command DB aux data entry */ + rpmhpd->level_count >>= 1; + + if (rpmhpd->level_count > RPMH_ARC_MAX_LEVELS) + return -EINVAL; + + for (i = 0; i < rpmhpd->level_count; i++) { + rpmhpd->level[i] = buf[i]; + + /* + * The AUX data may be zero padded. These 0 valued entries at + * the end of the map must be ignored. + */ + if (i > 0 && rpmhpd->level[i] == 0) { + rpmhpd->level_count = i; + break; + } + pr_debug("%s: ARC hlvl=%2d --> vlvl=%4u\n", rpmhpd->res_name, i, + rpmhpd->level[i]); + } + + return 0; +} + +static int rpmhpd_probe(struct platform_device *pdev) +{ + int i, ret; + size_t num_pds; + struct device *dev = &pdev->dev; + struct genpd_onecell_data *data; + struct rpmhpd **rpmhpds; + const struct rpmhpd_desc *desc; + + desc = of_device_get_match_data(dev); + if (!desc) + return -EINVAL; + + rpmhpds = desc->rpmhpds; + num_pds = desc->num_pds; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->domains = devm_kcalloc(dev, num_pds, sizeof(*data->domains), + GFP_KERNEL); + if (!data->domains) + return -ENOMEM; + + data->num_domains = num_pds; + + for (i = 0; i < num_pds; i++) { + if (!rpmhpds[i]) { + dev_warn(dev, "rpmhpds[%d] is empty\n", i); + continue; + } + + rpmhpds[i]->dev = dev; + rpmhpds[i]->addr = cmd_db_read_addr(rpmhpds[i]->res_name); + if (!rpmhpds[i]->addr) { + dev_err(dev, "Could not find RPMh address for resource %s\n", + rpmhpds[i]->res_name); + return -ENODEV; + } + + ret = cmd_db_read_slave_id(rpmhpds[i]->res_name); + if (ret != CMD_DB_HW_ARC) { + dev_err(dev, "RPMh slave ID mismatch\n"); + return -EINVAL; + } + + ret = rpmhpd_update_level_mapping(rpmhpds[i]); + if (ret) + return ret; + + rpmhpds[i]->pd.power_off = rpmhpd_power_off; + rpmhpds[i]->pd.power_on = rpmhpd_power_on; + rpmhpds[i]->pd.set_performance_state = rpmhpd_set_performance_state; + rpmhpds[i]->pd.opp_to_performance_state = rpmhpd_get_performance_state; + pm_genpd_init(&rpmhpds[i]->pd, NULL, true); + + data->domains[i] = &rpmhpds[i]->pd; + } + + return of_genpd_add_provider_onecell(pdev->dev.of_node, data); +} + +static struct platform_driver rpmhpd_driver = { + .driver = { + .name = "qcom-rpmhpd", + .of_match_table = rpmhpd_match_table, + .suppress_bind_attrs = true, + }, + .probe = rpmhpd_probe, +}; + +static int __init rpmhpd_init(void) +{ + return platform_driver_register(&rpmhpd_driver); +} +core_initcall(rpmhpd_init); -- cgit From 0503aec22c1483d02b2d8bbd8594c5b9713294b8 Mon Sep 17 00:00:00 2001 From: Rajendra Nayak Date: Thu, 10 Jan 2019 09:32:09 +0530 Subject: soc: qcom: rpmhpd: Mark mx as a parent for cx Specify the active + sleep and active-only MX power domains as the parents of the corresponding CX power domains. This will ensure that performance state requests on CX automatically generate equivalent requests on MX power domains. This is used to enforce a requirement that exists for various hardware blocks on SDM845 that MX performance state >= CX performance state for a given operating frequency. Acked-by: Viresh Kumar Reviewed-by: Stephen Boyd Reviewed-by: Ulf Hansson Signed-off-by: Rajendra Nayak Signed-off-by: Bjorn Andersson Signed-off-by: Andy Gross --- drivers/soc/qcom/rpmhpd.c | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/rpmhpd.c b/drivers/soc/qcom/rpmhpd.c index d3f819c1a90c..5741ec3fa814 100644 --- a/drivers/soc/qcom/rpmhpd.c +++ b/drivers/soc/qcom/rpmhpd.c @@ -101,12 +101,14 @@ static struct rpmhpd sdm845_cx_ao; static struct rpmhpd sdm845_cx = { .pd = { .name = "cx", }, .peer = &sdm845_cx_ao, + .parent = &sdm845_mx.pd, .res_name = "cx.lvl", }; static struct rpmhpd sdm845_cx_ao = { .pd = { .name = "cx_ao", }, .peer = &sdm845_cx, + .parent = &sdm845_mx_ao.pd, .res_name = "cx.lvl", }; @@ -376,6 +378,15 @@ static int rpmhpd_probe(struct platform_device *pdev) data->domains[i] = &rpmhpds[i]->pd; } + /* Add subdomains */ + for (i = 0; i < num_pds; i++) { + if (!rpmhpds[i]) + continue; + if (rpmhpds[i]->parent) + pm_genpd_add_subdomain(rpmhpds[i]->parent, + &rpmhpds[i]->pd); + } + return of_genpd_add_provider_onecell(pdev->dev.of_node, data); } -- cgit From 41c5bb767ecbc986c93df312769f3f49189f8ee0 Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Wed, 16 Jan 2019 21:11:20 -0800 Subject: soc: qcom: rpmpd: Drop family A RPM dependency The MFD_QCOM_RPM is the RPM in family A, but the rpmpd driver only implements support for SMD based devices. Drop the dependency and remove includes of the family A headers. No functional change. Signed-off-by: Bjorn Andersson Reviewed-by: Rajendra Nayak Signed-off-by: Andy Gross --- drivers/soc/qcom/Kconfig | 2 +- drivers/soc/qcom/rpmpd.c | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 6241d3e3b115..a5d5167c3f16 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -109,7 +109,7 @@ config QCOM_RPMHPD config QCOM_RPMPD bool "Qualcomm RPM Power domain driver" - depends on MFD_QCOM_RPM && QCOM_SMD_RPM + depends on QCOM_SMD_RPM help QCOM RPM Power domain driver to support power-domains with performance states. The driver communicates a performance state diff --git a/drivers/soc/qcom/rpmpd.c b/drivers/soc/qcom/rpmpd.c index 35a711017d6f..005326050c23 100644 --- a/drivers/soc/qcom/rpmpd.c +++ b/drivers/soc/qcom/rpmpd.c @@ -6,14 +6,12 @@ #include #include #include -#include #include #include #include #include #include -#include #include #define domain_to_rpmpd(domain) container_of(domain, struct rpmpd, pd) @@ -131,8 +129,8 @@ static int rpmpd_send_enable(struct rpmpd *pd, bool enable) .value = cpu_to_le32(enable), }; - return qcom_rpm_smd_write(pd->rpm, QCOM_RPM_ACTIVE_STATE, pd->res_type, - pd->res_id, &req, sizeof(req)); + return qcom_rpm_smd_write(pd->rpm, QCOM_SMD_RPM_ACTIVE_STATE, + pd->res_type, pd->res_id, &req, sizeof(req)); } static int rpmpd_send_corner(struct rpmpd *pd, int state, unsigned int corner) @@ -174,13 +172,13 @@ static int rpmpd_aggregate_corner(struct rpmpd *pd) active_corner = max(this_active_corner, peer_active_corner); - ret = rpmpd_send_corner(pd, QCOM_RPM_ACTIVE_STATE, active_corner); + ret = rpmpd_send_corner(pd, QCOM_SMD_RPM_ACTIVE_STATE, active_corner); if (ret) return ret; sleep_corner = max(this_sleep_corner, peer_sleep_corner); - return rpmpd_send_corner(pd, QCOM_RPM_SLEEP_STATE, sleep_corner); + return rpmpd_send_corner(pd, QCOM_SMD_RPM_SLEEP_STATE, sleep_corner); } static int rpmpd_power_on(struct generic_pm_domain *domain) -- cgit From 4eccc154b3866e59358f1102045d23a301dffe0a Mon Sep 17 00:00:00 2001 From: Rajendra Nayak Date: Fri, 18 Jan 2019 10:18:01 +0530 Subject: soc: qcom: update config dependencies for QCOM_RPMPD Since QCOM_RPMPD is bool and it depends on QCOM_SMD_RPM which is tristate, configurations such as arm64:allmodconfig result in CONFIG_QCOM_RPMPD=y CONFIG_QCOM_SMD_RPM=m This in turn results in drivers/soc/qcom/rpmpd.o: In function `rpmpd_send_corner': rpmpd.c:(.text+0x10c): undefined reference to `qcom_rpm_smd_write' drivers/soc/qcom/rpmpd.o: In function `rpmpd_power_on': rpmpd.c:(.text+0x3b4): undefined reference to `qcom_rpm_smd_write' drivers/soc/qcom/rpmpd.o: In function `rpmpd_power_off': rpmpd.c:(.text+0x520): undefined reference to `qcom_rpm_smd_write' make: *** [vmlinux] Error 1 Fix it by making QCOM_RPMPD depend on QCOM_SMD_RPM=y Reported-by: Guenter Roeck Signed-off-by: Rajendra Nayak Signed-off-by: Andy Gross --- drivers/soc/qcom/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index a5d5167c3f16..1ee298f6bf17 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -109,7 +109,7 @@ config QCOM_RPMHPD config QCOM_RPMPD bool "Qualcomm RPM Power domain driver" - depends on QCOM_SMD_RPM + depends on QCOM_SMD_RPM=y help QCOM RPM Power domain driver to support power-domains with performance states. The driver communicates a performance state -- cgit From f494caa480f7089ff9bba16dd865e190cee7e545 Mon Sep 17 00:00:00 2001 From: Stefan Wahren Date: Thu, 24 Jan 2019 18:59:15 +0100 Subject: soc: bcm: Make PM driver default for BCM2835 Since commit 52a4adbaebcc ("ARM: bcm283x: Switch V3D over to using the PM driver instead of firmware.") VC4 on BCM2835 requires the power driver. Otherwise the driver won't probe and HDMI output stays black: vc4_v3d 20c00000.v3d: ignoring dependency for device, assuming no driver Signed-off-by: Stefan Wahren Reviewed-by: Eric Anholt --- drivers/soc/bcm/Kconfig | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/soc') diff --git a/drivers/soc/bcm/Kconfig b/drivers/soc/bcm/Kconfig index fe1af29560e9..03fa91fbe2da 100644 --- a/drivers/soc/bcm/Kconfig +++ b/drivers/soc/bcm/Kconfig @@ -3,6 +3,7 @@ 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 -- cgit From 51294bf6b9e897d595466dcda5a3f2751906a200 Mon Sep 17 00:00:00 2001 From: Timo Alho Date: Sun, 30 Dec 2018 17:58:08 +0200 Subject: soc/tegra: fuse: Fix illegal free of IO base address On cases where device tree entries for fuse and clock provider are in different order, fuse driver needs to defer probing. This leads to freeing incorrect IO base address as the fuse->base variable gets overwritten once during first probe invocation. This leads to the following spew during boot: [ 3.082285] Trying to vfree() nonexistent vm area (00000000cfe8fd94) [ 3.082308] WARNING: CPU: 5 PID: 126 at /hdd/l4t/kernel/stable/mm/vmalloc.c:1511 __vunmap+0xcc/0xd8 [ 3.082318] Modules linked in: [ 3.082330] CPU: 5 PID: 126 Comm: kworker/5:1 Tainted: G S 4.19.7-tegra-gce119d3 #1 [ 3.082340] Hardware name: quill (DT) [ 3.082353] Workqueue: events deferred_probe_work_func [ 3.082364] pstate: 40000005 (nZcv daif -PAN -UAO) [ 3.082372] pc : __vunmap+0xcc/0xd8 [ 3.082379] lr : __vunmap+0xcc/0xd8 [ 3.082385] sp : ffff00000a1d3b60 [ 3.082391] x29: ffff00000a1d3b60 x28: 0000000000000000 [ 3.082402] x27: 0000000000000000 x26: ffff000008e8b610 [ 3.082413] x25: 0000000000000000 x24: 0000000000000009 [ 3.082423] x23: ffff000009221a90 x22: ffff000009f6d000 [ 3.082432] x21: 0000000000000000 x20: 0000000000000000 [ 3.082442] x19: ffff000009f6d000 x18: ffffffffffffffff [ 3.082452] x17: 0000000000000000 x16: 0000000000000000 [ 3.082462] x15: ffff0000091396c8 x14: 0720072007200720 [ 3.082471] x13: 0720072007200720 x12: 0720072907340739 [ 3.082481] x11: 0764076607380765 x10: 0766076307300730 [ 3.082491] x9 : 0730073007300730 x8 : 0730073007280720 [ 3.082501] x7 : 0761076507720761 x6 : 0000000000000102 [ 3.082510] x5 : 0000000000000000 x4 : 0000000000000000 [ 3.082519] x3 : ffffffffffffffff x2 : ffff000009150ff8 [ 3.082528] x1 : 3d95b1429fff5200 x0 : 0000000000000000 [ 3.082538] Call trace: [ 3.082545] __vunmap+0xcc/0xd8 [ 3.082552] vunmap+0x24/0x30 [ 3.082561] __iounmap+0x2c/0x38 [ 3.082569] tegra_fuse_probe+0xc8/0x118 [ 3.082577] platform_drv_probe+0x50/0xa0 [ 3.082585] really_probe+0x1b0/0x288 [ 3.082593] driver_probe_device+0x58/0x100 [ 3.082601] __device_attach_driver+0x98/0xf0 [ 3.082609] bus_for_each_drv+0x64/0xc8 [ 3.082616] __device_attach+0xd8/0x130 [ 3.082624] device_initial_probe+0x10/0x18 [ 3.082631] bus_probe_device+0x90/0x98 [ 3.082638] deferred_probe_work_func+0x74/0xb0 [ 3.082649] process_one_work+0x1e0/0x318 [ 3.082656] worker_thread+0x228/0x450 [ 3.082664] kthread+0x128/0x130 [ 3.082672] ret_from_fork+0x10/0x18 [ 3.082678] ---[ end trace 0810fe6ba772c1c7 ]--- Fix this by retaining the value of fuse->base until driver has successfully probed. Signed-off-by: Timo Alho Acked-by: Jon Hunter Signed-off-by: Thierry Reding --- drivers/soc/tegra/fuse/fuse-tegra.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'drivers/soc') diff --git a/drivers/soc/tegra/fuse/fuse-tegra.c b/drivers/soc/tegra/fuse/fuse-tegra.c index a33ee8ef8b6b..51625703399e 100644 --- a/drivers/soc/tegra/fuse/fuse-tegra.c +++ b/drivers/soc/tegra/fuse/fuse-tegra.c @@ -137,13 +137,17 @@ static int tegra_fuse_probe(struct platform_device *pdev) res = platform_get_resource(pdev, IORESOURCE_MEM, 0); fuse->phys = res->start; fuse->base = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(fuse->base)) - return PTR_ERR(fuse->base); + if (IS_ERR(fuse->base)) { + err = PTR_ERR(fuse->base); + fuse->base = base; + return err; + } fuse->clk = devm_clk_get(&pdev->dev, "fuse"); if (IS_ERR(fuse->clk)) { dev_err(&pdev->dev, "failed to get FUSE clock: %ld", PTR_ERR(fuse->clk)); + fuse->base = base; return PTR_ERR(fuse->clk); } @@ -152,8 +156,10 @@ static int tegra_fuse_probe(struct platform_device *pdev) if (fuse->soc->probe) { err = fuse->soc->probe(fuse); - if (err < 0) + if (err < 0) { + fuse->base = base; return err; + } } if (tegra_fuse_create_sysfs(&pdev->dev, fuse->soc->info->size, -- cgit From d94da0dd686d249ab0f6500d41169a7eff9a4b21 Mon Sep 17 00:00:00 2001 From: Joseph Lo Date: Fri, 4 Jan 2019 15:16:46 +0800 Subject: soc/tegra: fuse: Fix typo in tegra210_init_speedo_data Fix typo when reading SoC speedo value from fuse SoC speedo register. Reported-by: Mark Zhang Signed-off-by: Joseph Lo Signed-off-by: Thierry Reding --- drivers/soc/tegra/fuse/speedo-tegra210.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/soc') diff --git a/drivers/soc/tegra/fuse/speedo-tegra210.c b/drivers/soc/tegra/fuse/speedo-tegra210.c index 5373f4c16b54..8ed35d9851f8 100644 --- a/drivers/soc/tegra/fuse/speedo-tegra210.c +++ b/drivers/soc/tegra/fuse/speedo-tegra210.c @@ -131,7 +131,7 @@ void __init tegra210_init_speedo_data(struct tegra_sku_info *sku_info) soc_speedo[0] = tegra_fuse_read_early(FUSE_SOC_SPEEDO_0); soc_speedo[1] = tegra_fuse_read_early(FUSE_SOC_SPEEDO_1); - soc_speedo[2] = tegra_fuse_read_early(FUSE_CPU_SPEEDO_2); + soc_speedo[2] = tegra_fuse_read_early(FUSE_SOC_SPEEDO_2); cpu_iddq = tegra_fuse_read_early(FUSE_CPU_IDDQ) * 4; soc_iddq = tegra_fuse_read_early(FUSE_SOC_IDDQ) * 4; -- cgit From 532700ed0a9b3565d5fd246f317724d05ee986b3 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Wed, 23 Jan 2019 12:38:43 +0100 Subject: soc/tegra: pmc: Use TEGRA186_ prefix for GPIO names The new prefix allows the GPIOs to be uniquely identified on a per-chip basis, which makes it easier to distinguish Tegra186 specific GPIOs from those of later chips such as Tegra194 which supports a very different set of GPIOs. Signed-off-by: Thierry Reding --- drivers/soc/tegra/pmc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/soc') diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c index 7ea3280279ff..13ae8cb5b6b0 100644 --- a/drivers/soc/tegra/pmc.c +++ b/drivers/soc/tegra/pmc.c @@ -2438,7 +2438,7 @@ static void tegra186_pmc_setup_irq_polarity(struct tegra_pmc *pmc, } static const struct tegra_wake_event tegra186_wake_events[] = { - TEGRA_WAKE_GPIO("power", 29, 1, TEGRA_AON_GPIO(FF, 0)), + TEGRA_WAKE_GPIO("power", 29, 1, TEGRA186_AON_GPIO(FF, 0)), TEGRA_WAKE_IRQ("rtc", 73, 10), }; -- cgit From 4659db5e6fdfde6d58731560eec43e103eb671c5 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 25 Jan 2019 11:22:49 +0100 Subject: soc/tegra: pmc: Sort includes alphabetically This will make it easier to insert new includes in the right place in subsequent patches. Signed-off-by: Thierry Reding Acked-by: Jon Hunter --- drivers/soc/tegra/pmc.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'drivers/soc') diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c index 13ae8cb5b6b0..0ce790176769 100644 --- a/drivers/soc/tegra/pmc.c +++ b/drivers/soc/tegra/pmc.c @@ -20,7 +20,6 @@ #define pr_fmt(fmt) "tegra-pmc: " fmt -#include #include #include #include @@ -30,16 +29,17 @@ #include #include #include -#include #include -#include +#include +#include #include #include +#include #include #include -#include -#include #include +#include +#include #include #include #include -- cgit From bbe5af60041cae1970f6722323557e23bc1e5b83 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 25 Jan 2019 11:22:50 +0100 Subject: soc/tegra: pmc: Add missing kerneldoc Some of the fields in struct tegra_pmc had not been documented when they were added. Add the missing kerneldoc. Signed-off-by: Thierry Reding Acked-by: Jon Hunter --- drivers/soc/tegra/pmc.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'drivers/soc') diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c index 0ce790176769..50ebf98e04ee 100644 --- a/drivers/soc/tegra/pmc.c +++ b/drivers/soc/tegra/pmc.c @@ -273,6 +273,9 @@ static const char * const tegra30_reset_sources[] = { * struct tegra_pmc - NVIDIA Tegra PMC * @dev: pointer to PMC device structure * @base: pointer to I/O remapped register region + * @wake: pointer to I/O remapped region for WAKE registers + * @aotag: pointer to I/O remapped region for AOTAG registers + * @scratch: pointer to I/O remapped region for scratch registers * @clk: pointer to pclk clock * @soc: pointer to SoC data structure * @debugfs: pointer to debugfs entry @@ -291,6 +294,9 @@ static const char * const tegra30_reset_sources[] = { * @lp0_vec_size: size of the LP0 warm boot code * @powergates_available: Bitmap of available power gates * @powergates_lock: mutex for power gate register access + * @pctl_dev: pin controller exposed by the PMC + * @domain: IRQ domain provided by the PMC + * @irq: chip implementation for the IRQ domain */ struct tegra_pmc { struct device *dev; -- cgit From d32dde2c5a110f2d69add70d7a9f1c20d82d0ef1 Mon Sep 17 00:00:00 2001 From: Jon Hunter Date: Fri, 25 Jan 2019 11:22:51 +0100 Subject: soc/tegra: pmc: Make tegra_powergate_is_powered() a local function Now there are no more external users of tegra_powergate_is_powered(), make this a local function. Signed-off-by: Jon Hunter Signed-off-by: Thierry Reding --- drivers/soc/tegra/pmc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/soc') diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c index 50ebf98e04ee..986f2171ff38 100644 --- a/drivers/soc/tegra/pmc.c +++ b/drivers/soc/tegra/pmc.c @@ -637,7 +637,7 @@ EXPORT_SYMBOL(tegra_powergate_power_off); * tegra_powergate_is_powered() - check if partition is powered * @id: partition ID */ -int tegra_powergate_is_powered(unsigned int id) +static int tegra_powergate_is_powered(unsigned int id) { if (!tegra_powergate_is_valid(id)) return -EINVAL; -- cgit From 589997a157df82fa04fec63ef4d01e73545fd415 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 25 Jan 2019 11:22:52 +0100 Subject: soc/tegra: pmc: Pass struct tegra_pmc * where possible Instead of using the global pmc variable, pass around a pointer where possible. Also, replace most occurrences of pr_*() functions by their equivalent dev_*() functions, reusing the pmc->dev pointer. It's not possible to get completely rid of the global variable because some of the public API that this driver exposes still relies on it. Signed-off-by: Thierry Reding Acked-by: Jon Hunter --- drivers/soc/tegra/pmc.c | 267 +++++++++++++++++++++++++++--------------------- 1 file changed, 152 insertions(+), 115 deletions(-) (limited to 'drivers/soc') diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c index 986f2171ff38..27b0a0310abc 100644 --- a/drivers/soc/tegra/pmc.c +++ b/drivers/soc/tegra/pmc.c @@ -344,30 +344,36 @@ to_powergate(struct generic_pm_domain *domain) return container_of(domain, struct tegra_powergate, genpd); } -static u32 tegra_pmc_readl(unsigned long offset) +static u32 tegra_pmc_readl(struct tegra_pmc *pmc, unsigned long offset) { return readl(pmc->base + offset); } -static void tegra_pmc_writel(u32 value, unsigned long offset) +static void tegra_pmc_writel(struct tegra_pmc *pmc, u32 value, + unsigned long offset) { writel(value, pmc->base + offset); } +/* + * TODO Figure out a way to call this with the struct tegra_pmc * passed in. + * This currently doesn't work because readx_poll_timeout() can only operate + * on functions that take a single argument. + */ static inline bool tegra_powergate_state(int id) { if (id == TEGRA_POWERGATE_3D && pmc->soc->has_gpu_clamps) - return (tegra_pmc_readl(GPU_RG_CNTRL) & 0x1) == 0; + return (tegra_pmc_readl(pmc, GPU_RG_CNTRL) & 0x1) == 0; else - return (tegra_pmc_readl(PWRGATE_STATUS) & BIT(id)) != 0; + return (tegra_pmc_readl(pmc, PWRGATE_STATUS) & BIT(id)) != 0; } -static inline bool tegra_powergate_is_valid(int id) +static inline bool tegra_powergate_is_valid(struct tegra_pmc *pmc, int id) { return (pmc->soc && pmc->soc->powergates[id]); } -static inline bool tegra_powergate_is_available(int id) +static inline bool tegra_powergate_is_available(struct tegra_pmc *pmc, int id) { return test_bit(id, pmc->powergates_available); } @@ -380,7 +386,7 @@ static int tegra_powergate_lookup(struct tegra_pmc *pmc, const char *name) return -EINVAL; for (i = 0; i < pmc->soc->num_powergates; i++) { - if (!tegra_powergate_is_valid(i)) + if (!tegra_powergate_is_valid(pmc, i)) continue; if (!strcmp(name, pmc->soc->powergates[i])) @@ -392,10 +398,12 @@ static int tegra_powergate_lookup(struct tegra_pmc *pmc, const char *name) /** * tegra_powergate_set() - set the state of a partition + * @pmc: power management controller * @id: partition ID * @new_state: new state of the partition */ -static int tegra_powergate_set(unsigned int id, bool new_state) +static int tegra_powergate_set(struct tegra_pmc *pmc, unsigned int id, + bool new_state) { bool status; int err; @@ -410,7 +418,7 @@ static int tegra_powergate_set(unsigned int id, bool new_state) return 0; } - tegra_pmc_writel(PWRGATE_TOGGLE_START | id, PWRGATE_TOGGLE); + tegra_pmc_writel(pmc, PWRGATE_TOGGLE_START | id, PWRGATE_TOGGLE); err = readx_poll_timeout(tegra_powergate_state, id, status, status == new_state, 10, 100000); @@ -420,7 +428,8 @@ static int tegra_powergate_set(unsigned int id, bool new_state) return err; } -static int __tegra_powergate_remove_clamping(unsigned int id) +static int __tegra_powergate_remove_clamping(struct tegra_pmc *pmc, + unsigned int id) { u32 mask; @@ -432,7 +441,7 @@ static int __tegra_powergate_remove_clamping(unsigned int id) */ if (id == TEGRA_POWERGATE_3D) { if (pmc->soc->has_gpu_clamps) { - tegra_pmc_writel(0, GPU_RG_CNTRL); + tegra_pmc_writel(pmc, 0, GPU_RG_CNTRL); goto out; } } @@ -448,7 +457,7 @@ static int __tegra_powergate_remove_clamping(unsigned int id) else mask = (1 << id); - tegra_pmc_writel(mask, REMOVE_CLAMPING); + tegra_pmc_writel(pmc, mask, REMOVE_CLAMPING); out: mutex_unlock(&pmc->powergates_lock); @@ -500,7 +509,7 @@ static int tegra_powergate_power_up(struct tegra_powergate *pg, usleep_range(10, 20); - err = tegra_powergate_set(pg->id, true); + err = tegra_powergate_set(pg->pmc, pg->id, true); if (err < 0) return err; @@ -512,7 +521,7 @@ static int tegra_powergate_power_up(struct tegra_powergate *pg, usleep_range(10, 20); - err = __tegra_powergate_remove_clamping(pg->id); + err = __tegra_powergate_remove_clamping(pg->pmc, pg->id); if (err) goto disable_clks; @@ -539,7 +548,7 @@ disable_clks: usleep_range(10, 20); powergate_off: - tegra_powergate_set(pg->id, false); + tegra_powergate_set(pg->pmc, pg->id, false); return err; } @@ -564,7 +573,7 @@ static int tegra_powergate_power_down(struct tegra_powergate *pg) usleep_range(10, 20); - err = tegra_powergate_set(pg->id, false); + err = tegra_powergate_set(pg->pmc, pg->id, false); if (err) goto assert_resets; @@ -585,12 +594,13 @@ disable_clks: static int tegra_genpd_power_on(struct generic_pm_domain *domain) { struct tegra_powergate *pg = to_powergate(domain); + struct device *dev = pg->pmc->dev; int err; err = tegra_powergate_power_up(pg, true); if (err) - pr_err("failed to turn on PM domain %s: %d\n", pg->genpd.name, - err); + dev_err(dev, "failed to turn on PM domain %s: %d\n", + pg->genpd.name, err); return err; } @@ -598,12 +608,13 @@ static int tegra_genpd_power_on(struct generic_pm_domain *domain) static int tegra_genpd_power_off(struct generic_pm_domain *domain) { struct tegra_powergate *pg = to_powergate(domain); + struct device *dev = pg->pmc->dev; int err; err = tegra_powergate_power_down(pg); if (err) - pr_err("failed to turn off PM domain %s: %d\n", - pg->genpd.name, err); + dev_err(dev, "failed to turn off PM domain %s: %d\n", + pg->genpd.name, err); return err; } @@ -614,10 +625,10 @@ static int tegra_genpd_power_off(struct generic_pm_domain *domain) */ int tegra_powergate_power_on(unsigned int id) { - if (!tegra_powergate_is_available(id)) + if (!tegra_powergate_is_available(pmc, id)) return -EINVAL; - return tegra_powergate_set(id, true); + return tegra_powergate_set(pmc, id, true); } /** @@ -626,20 +637,21 @@ int tegra_powergate_power_on(unsigned int id) */ int tegra_powergate_power_off(unsigned int id) { - if (!tegra_powergate_is_available(id)) + if (!tegra_powergate_is_available(pmc, id)) return -EINVAL; - return tegra_powergate_set(id, false); + return tegra_powergate_set(pmc, id, false); } EXPORT_SYMBOL(tegra_powergate_power_off); /** * tegra_powergate_is_powered() - check if partition is powered + * @pmc: power management controller * @id: partition ID */ -static int tegra_powergate_is_powered(unsigned int id) +static int tegra_powergate_is_powered(struct tegra_pmc *pmc, unsigned int id) { - if (!tegra_powergate_is_valid(id)) + if (!tegra_powergate_is_valid(pmc, id)) return -EINVAL; return tegra_powergate_state(id); @@ -651,10 +663,10 @@ static int tegra_powergate_is_powered(unsigned int id) */ int tegra_powergate_remove_clamping(unsigned int id) { - if (!tegra_powergate_is_available(id)) + if (!tegra_powergate_is_available(pmc, id)) return -EINVAL; - return __tegra_powergate_remove_clamping(id); + return __tegra_powergate_remove_clamping(pmc, id); } EXPORT_SYMBOL(tegra_powergate_remove_clamping); @@ -672,7 +684,7 @@ int tegra_powergate_sequence_power_up(unsigned int id, struct clk *clk, struct tegra_powergate *pg; int err; - if (!tegra_powergate_is_available(id)) + if (!tegra_powergate_is_available(pmc, id)) return -EINVAL; pg = kzalloc(sizeof(*pg), GFP_KERNEL); @@ -687,7 +699,8 @@ int tegra_powergate_sequence_power_up(unsigned int id, struct clk *clk, err = tegra_powergate_power_up(pg, false); if (err) - pr_err("failed to turn on partition %d: %d\n", id, err); + dev_err(pmc->dev, "failed to turn on partition %d: %d\n", id, + err); kfree(pg); @@ -697,12 +710,14 @@ EXPORT_SYMBOL(tegra_powergate_sequence_power_up); /** * tegra_get_cpu_powergate_id() - convert from CPU ID to partition ID + * @pmc: power management controller * @cpuid: CPU partition ID * * Returns the partition ID corresponding to the CPU partition ID or a * negative error code on failure. */ -static int tegra_get_cpu_powergate_id(unsigned int cpuid) +static int tegra_get_cpu_powergate_id(struct tegra_pmc *pmc, + unsigned int cpuid) { if (pmc->soc && cpuid < pmc->soc->num_cpu_powergates) return pmc->soc->cpu_powergates[cpuid]; @@ -718,11 +733,11 @@ bool tegra_pmc_cpu_is_powered(unsigned int cpuid) { int id; - id = tegra_get_cpu_powergate_id(cpuid); + id = tegra_get_cpu_powergate_id(pmc, cpuid); if (id < 0) return false; - return tegra_powergate_is_powered(id); + return tegra_powergate_is_powered(pmc, id); } /** @@ -733,11 +748,11 @@ int tegra_pmc_cpu_power_on(unsigned int cpuid) { int id; - id = tegra_get_cpu_powergate_id(cpuid); + id = tegra_get_cpu_powergate_id(pmc, cpuid); if (id < 0) return id; - return tegra_powergate_set(id, true); + return tegra_powergate_set(pmc, id, true); } /** @@ -748,7 +763,7 @@ int tegra_pmc_cpu_remove_clamping(unsigned int cpuid) { int id; - id = tegra_get_cpu_powergate_id(cpuid); + id = tegra_get_cpu_powergate_id(pmc, cpuid); if (id < 0) return id; @@ -778,9 +793,9 @@ static int tegra_pmc_restart_notify(struct notifier_block *this, writel(value, pmc->scratch + pmc->soc->regs->scratch0); /* reset everything but PMC_SCRATCH0 and PMC_RST_STATUS */ - value = tegra_pmc_readl(PMC_CNTRL); + value = tegra_pmc_readl(pmc, PMC_CNTRL); value |= PMC_CNTRL_MAIN_RST; - tegra_pmc_writel(value, PMC_CNTRL); + tegra_pmc_writel(pmc, value, PMC_CNTRL); return NOTIFY_DONE; } @@ -799,7 +814,7 @@ static int powergate_show(struct seq_file *s, void *data) seq_printf(s, "------------------\n"); for (i = 0; i < pmc->soc->num_powergates; i++) { - status = tegra_powergate_is_powered(i); + status = tegra_powergate_is_powered(pmc, i); if (status < 0) continue; @@ -861,12 +876,13 @@ err: static int tegra_powergate_of_get_resets(struct tegra_powergate *pg, struct device_node *np, bool off) { + struct device *dev = pg->pmc->dev; int err; pg->reset = of_reset_control_array_get_exclusive(np); if (IS_ERR(pg->reset)) { err = PTR_ERR(pg->reset); - pr_err("failed to get device resets: %d\n", err); + dev_err(dev, "failed to get device resets: %d\n", err); return err; } @@ -883,6 +899,7 @@ static int tegra_powergate_of_get_resets(struct tegra_powergate *pg, static void tegra_powergate_add(struct tegra_pmc *pmc, struct device_node *np) { + struct device *dev = pmc->dev; struct tegra_powergate *pg; int id, err; bool off; @@ -893,7 +910,7 @@ static void tegra_powergate_add(struct tegra_pmc *pmc, struct device_node *np) id = tegra_powergate_lookup(pmc, np->name); if (id < 0) { - pr_err("powergate lookup failed for %pOFn: %d\n", np, id); + dev_err(dev, "powergate lookup failed for %pOFn: %d\n", np, id); goto free_mem; } @@ -909,17 +926,17 @@ static void tegra_powergate_add(struct tegra_pmc *pmc, struct device_node *np) pg->genpd.power_on = tegra_genpd_power_on; pg->pmc = pmc; - off = !tegra_powergate_is_powered(pg->id); + off = !tegra_powergate_is_powered(pmc, pg->id); err = tegra_powergate_of_get_clks(pg, np); if (err < 0) { - pr_err("failed to get clocks for %pOFn: %d\n", np, err); + dev_err(dev, "failed to get clocks for %pOFn: %d\n", np, err); goto set_available; } err = tegra_powergate_of_get_resets(pg, np, off); if (err < 0) { - pr_err("failed to get resets for %pOFn: %d\n", np, err); + dev_err(dev, "failed to get resets for %pOFn: %d\n", np, err); goto remove_clks; } @@ -932,19 +949,19 @@ static void tegra_powergate_add(struct tegra_pmc *pmc, struct device_node *np) err = pm_genpd_init(&pg->genpd, NULL, off); if (err < 0) { - pr_err("failed to initialise PM domain %pOFn: %d\n", np, + dev_err(dev, "failed to initialise PM domain %pOFn: %d\n", np, err); goto remove_resets; } err = of_genpd_add_provider_simple(np, &pg->genpd); if (err < 0) { - pr_err("failed to add PM domain provider for %pOFn: %d\n", - np, err); + dev_err(dev, "failed to add PM domain provider for %pOFn: %d\n", + np, err); goto remove_genpd; } - pr_debug("added PM domain %s\n", pg->genpd.name); + dev_dbg(dev, "added PM domain %s\n", pg->genpd.name); return; @@ -1000,7 +1017,8 @@ tegra_io_pad_find(struct tegra_pmc *pmc, enum tegra_io_pad id) return NULL; } -static int tegra_io_pad_get_dpd_register_bit(enum tegra_io_pad id, +static int tegra_io_pad_get_dpd_register_bit(struct tegra_pmc *pmc, + enum tegra_io_pad id, unsigned long *request, unsigned long *status, u32 *mask) @@ -1009,7 +1027,7 @@ static int tegra_io_pad_get_dpd_register_bit(enum tegra_io_pad id, pad = tegra_io_pad_find(pmc, id); if (!pad) { - pr_err("invalid I/O pad ID %u\n", id); + dev_err(pmc->dev, "invalid I/O pad ID %u\n", id); return -ENOENT; } @@ -1029,43 +1047,44 @@ static int tegra_io_pad_get_dpd_register_bit(enum tegra_io_pad id, return 0; } -static int tegra_io_pad_prepare(enum tegra_io_pad id, unsigned long *request, - unsigned long *status, u32 *mask) +static int tegra_io_pad_prepare(struct tegra_pmc *pmc, enum tegra_io_pad id, + unsigned long *request, unsigned long *status, + u32 *mask) { unsigned long rate, value; int err; - err = tegra_io_pad_get_dpd_register_bit(id, request, status, mask); + err = tegra_io_pad_get_dpd_register_bit(pmc, id, request, status, mask); if (err) return err; if (pmc->clk) { rate = clk_get_rate(pmc->clk); if (!rate) { - pr_err("failed to get clock rate\n"); + dev_err(pmc->dev, "failed to get clock rate\n"); return -ENODEV; } - tegra_pmc_writel(DPD_SAMPLE_ENABLE, DPD_SAMPLE); + tegra_pmc_writel(pmc, DPD_SAMPLE_ENABLE, DPD_SAMPLE); /* must be at least 200 ns, in APB (PCLK) clock cycles */ value = DIV_ROUND_UP(1000000000, rate); value = DIV_ROUND_UP(200, value); - tegra_pmc_writel(value, SEL_DPD_TIM); + tegra_pmc_writel(pmc, value, SEL_DPD_TIM); } return 0; } -static int tegra_io_pad_poll(unsigned long offset, u32 mask, - u32 val, unsigned long timeout) +static int tegra_io_pad_poll(struct tegra_pmc *pmc, unsigned long offset, + u32 mask, u32 val, unsigned long timeout) { u32 value; timeout = jiffies + msecs_to_jiffies(timeout); while (time_after(timeout, jiffies)) { - value = tegra_pmc_readl(offset); + value = tegra_pmc_readl(pmc, offset); if ((value & mask) == val) return 0; @@ -1075,10 +1094,10 @@ static int tegra_io_pad_poll(unsigned long offset, u32 mask, return -ETIMEDOUT; } -static void tegra_io_pad_unprepare(void) +static void tegra_io_pad_unprepare(struct tegra_pmc *pmc) { if (pmc->clk) - tegra_pmc_writel(DPD_SAMPLE_DISABLE, DPD_SAMPLE); + tegra_pmc_writel(pmc, DPD_SAMPLE_DISABLE, DPD_SAMPLE); } /** @@ -1095,21 +1114,21 @@ int tegra_io_pad_power_enable(enum tegra_io_pad id) mutex_lock(&pmc->powergates_lock); - err = tegra_io_pad_prepare(id, &request, &status, &mask); + err = tegra_io_pad_prepare(pmc, id, &request, &status, &mask); if (err < 0) { - pr_err("failed to prepare I/O pad: %d\n", err); + dev_err(pmc->dev, "failed to prepare I/O pad: %d\n", err); goto unlock; } - tegra_pmc_writel(IO_DPD_REQ_CODE_OFF | mask, request); + tegra_pmc_writel(pmc, IO_DPD_REQ_CODE_OFF | mask, request); - err = tegra_io_pad_poll(status, mask, 0, 250); + err = tegra_io_pad_poll(pmc, status, mask, 0, 250); if (err < 0) { - pr_err("failed to enable I/O pad: %d\n", err); + dev_err(pmc->dev, "failed to enable I/O pad: %d\n", err); goto unlock; } - tegra_io_pad_unprepare(); + tegra_io_pad_unprepare(pmc); unlock: mutex_unlock(&pmc->powergates_lock); @@ -1131,21 +1150,21 @@ int tegra_io_pad_power_disable(enum tegra_io_pad id) mutex_lock(&pmc->powergates_lock); - err = tegra_io_pad_prepare(id, &request, &status, &mask); + err = tegra_io_pad_prepare(pmc, id, &request, &status, &mask); if (err < 0) { - pr_err("failed to prepare I/O pad: %d\n", err); + dev_err(pmc->dev, "failed to prepare I/O pad: %d\n", err); goto unlock; } - tegra_pmc_writel(IO_DPD_REQ_CODE_ON | mask, request); + tegra_pmc_writel(pmc, IO_DPD_REQ_CODE_ON | mask, request); - err = tegra_io_pad_poll(status, mask, mask, 250); + err = tegra_io_pad_poll(pmc, status, mask, mask, 250); if (err < 0) { - pr_err("failed to disable I/O pad: %d\n", err); + dev_err(pmc->dev, "failed to disable I/O pad: %d\n", err); goto unlock; } - tegra_io_pad_unprepare(); + tegra_io_pad_unprepare(pmc); unlock: mutex_unlock(&pmc->powergates_lock); @@ -1153,22 +1172,24 @@ unlock: } EXPORT_SYMBOL(tegra_io_pad_power_disable); -static int tegra_io_pad_is_powered(enum tegra_io_pad id) +static int tegra_io_pad_is_powered(struct tegra_pmc *pmc, enum tegra_io_pad id) { unsigned long request, status; u32 mask, value; int err; - err = tegra_io_pad_get_dpd_register_bit(id, &request, &status, &mask); + err = tegra_io_pad_get_dpd_register_bit(pmc, id, &request, &status, + &mask); if (err) return err; - value = tegra_pmc_readl(status); + value = tegra_pmc_readl(pmc, status); return !(value & mask); } -static int tegra_io_pad_set_voltage(enum tegra_io_pad id, int voltage) +static int tegra_io_pad_set_voltage(struct tegra_pmc *pmc, enum tegra_io_pad id, + int voltage) { const struct tegra_io_pad_soc *pad; u32 value; @@ -1183,29 +1204,29 @@ static int tegra_io_pad_set_voltage(enum tegra_io_pad id, int voltage) mutex_lock(&pmc->powergates_lock); if (pmc->soc->has_impl_33v_pwr) { - value = tegra_pmc_readl(PMC_IMPL_E_33V_PWR); + value = tegra_pmc_readl(pmc, PMC_IMPL_E_33V_PWR); if (voltage == TEGRA_IO_PAD_VOLTAGE_1V8) value &= ~BIT(pad->voltage); else value |= BIT(pad->voltage); - tegra_pmc_writel(value, PMC_IMPL_E_33V_PWR); + tegra_pmc_writel(pmc, value, PMC_IMPL_E_33V_PWR); } else { /* write-enable PMC_PWR_DET_VALUE[pad->voltage] */ - value = tegra_pmc_readl(PMC_PWR_DET); + value = tegra_pmc_readl(pmc, PMC_PWR_DET); value |= BIT(pad->voltage); - tegra_pmc_writel(value, PMC_PWR_DET); + tegra_pmc_writel(pmc, value, PMC_PWR_DET); /* update I/O voltage */ - value = tegra_pmc_readl(PMC_PWR_DET_VALUE); + value = tegra_pmc_readl(pmc, PMC_PWR_DET_VALUE); if (voltage == TEGRA_IO_PAD_VOLTAGE_1V8) value &= ~BIT(pad->voltage); else value |= BIT(pad->voltage); - tegra_pmc_writel(value, PMC_PWR_DET_VALUE); + tegra_pmc_writel(pmc, value, PMC_PWR_DET_VALUE); } mutex_unlock(&pmc->powergates_lock); @@ -1215,7 +1236,7 @@ static int tegra_io_pad_set_voltage(enum tegra_io_pad id, int voltage) return 0; } -static int tegra_io_pad_get_voltage(enum tegra_io_pad id) +static int tegra_io_pad_get_voltage(struct tegra_pmc *pmc, enum tegra_io_pad id) { const struct tegra_io_pad_soc *pad; u32 value; @@ -1228,9 +1249,9 @@ static int tegra_io_pad_get_voltage(enum tegra_io_pad id) return -ENOTSUPP; if (pmc->soc->has_impl_33v_pwr) - value = tegra_pmc_readl(PMC_IMPL_E_33V_PWR); + value = tegra_pmc_readl(pmc, PMC_IMPL_E_33V_PWR); else - value = tegra_pmc_readl(PMC_PWR_DET_VALUE); + value = tegra_pmc_readl(pmc, PMC_PWR_DET_VALUE); if ((value & BIT(pad->voltage)) == 0) return TEGRA_IO_PAD_VOLTAGE_1V8; @@ -1302,21 +1323,21 @@ void tegra_pmc_enter_suspend_mode(enum tegra_suspend_mode mode) ticks = pmc->cpu_good_time * rate + USEC_PER_SEC - 1; do_div(ticks, USEC_PER_SEC); - tegra_pmc_writel(ticks, PMC_CPUPWRGOOD_TIMER); + tegra_pmc_writel(pmc, ticks, PMC_CPUPWRGOOD_TIMER); ticks = pmc->cpu_off_time * rate + USEC_PER_SEC - 1; do_div(ticks, USEC_PER_SEC); - tegra_pmc_writel(ticks, PMC_CPUPWROFF_TIMER); + tegra_pmc_writel(pmc, ticks, PMC_CPUPWROFF_TIMER); wmb(); pmc->rate = rate; } - value = tegra_pmc_readl(PMC_CNTRL); + value = tegra_pmc_readl(pmc, PMC_CNTRL); value &= ~PMC_CNTRL_SIDE_EFFECT_LP0; value |= PMC_CNTRL_CPU_PWRREQ_OE; - tegra_pmc_writel(value, PMC_CNTRL); + tegra_pmc_writel(pmc, value, PMC_CNTRL); } #endif @@ -1438,13 +1459,13 @@ static void tegra_pmc_init_tsense_reset(struct tegra_pmc *pmc) if (of_property_read_u32(np, "nvidia,pinmux-id", &pinmux)) pinmux = 0; - value = tegra_pmc_readl(PMC_SENSOR_CTRL); + value = tegra_pmc_readl(pmc, PMC_SENSOR_CTRL); value |= PMC_SENSOR_CTRL_SCRATCH_WRITE; - tegra_pmc_writel(value, PMC_SENSOR_CTRL); + tegra_pmc_writel(pmc, value, PMC_SENSOR_CTRL); value = (reg_data << PMC_SCRATCH54_DATA_SHIFT) | (reg_addr << PMC_SCRATCH54_ADDR_SHIFT); - tegra_pmc_writel(value, PMC_SCRATCH54); + tegra_pmc_writel(pmc, value, PMC_SCRATCH54); value = PMC_SCRATCH55_RESET_TEGRA; value |= ctrl_id << PMC_SCRATCH55_CNTRL_ID_SHIFT; @@ -1462,11 +1483,11 @@ static void tegra_pmc_init_tsense_reset(struct tegra_pmc *pmc) value |= checksum << PMC_SCRATCH55_CHECKSUM_SHIFT; - tegra_pmc_writel(value, PMC_SCRATCH55); + tegra_pmc_writel(pmc, value, PMC_SCRATCH55); - value = tegra_pmc_readl(PMC_SENSOR_CTRL); + value = tegra_pmc_readl(pmc, PMC_SENSOR_CTRL); value |= PMC_SENSOR_CTRL_ENABLE_RST; - tegra_pmc_writel(value, PMC_SENSOR_CTRL); + tegra_pmc_writel(pmc, value, PMC_SENSOR_CTRL); dev_info(pmc->dev, "emergency thermal reset enabled\n"); @@ -1476,12 +1497,16 @@ out: static int tegra_io_pad_pinctrl_get_groups_count(struct pinctrl_dev *pctl_dev) { + struct tegra_pmc *pmc = pinctrl_dev_get_drvdata(pctl_dev); + return pmc->soc->num_io_pads; } static const char *tegra_io_pad_pinctrl_get_group_name( struct pinctrl_dev *pctl, unsigned int group) { + struct tegra_pmc *pmc = pinctrl_dev_get_drvdata(pctl); + return pmc->soc->io_pads[group].name; } @@ -1490,6 +1515,8 @@ static int tegra_io_pad_pinctrl_get_group_pins(struct pinctrl_dev *pctl_dev, const unsigned int **pins, unsigned int *num_pins) { + struct tegra_pmc *pmc = pinctrl_dev_get_drvdata(pctl_dev); + *pins = &pmc->soc->io_pads[group].id; *num_pins = 1; return 0; @@ -1506,23 +1533,25 @@ static const struct pinctrl_ops tegra_io_pad_pinctrl_ops = { static int tegra_io_pad_pinconf_get(struct pinctrl_dev *pctl_dev, unsigned int pin, unsigned long *config) { - const struct tegra_io_pad_soc *pad = tegra_io_pad_find(pmc, pin); enum pin_config_param param = pinconf_to_config_param(*config); + struct tegra_pmc *pmc = pinctrl_dev_get_drvdata(pctl_dev); + const struct tegra_io_pad_soc *pad; int ret; u32 arg; + pad = tegra_io_pad_find(pmc, pin); if (!pad) return -EINVAL; switch (param) { case PIN_CONFIG_POWER_SOURCE: - ret = tegra_io_pad_get_voltage(pad->id); + ret = tegra_io_pad_get_voltage(pmc, pad->id); if (ret < 0) return ret; arg = ret; break; case PIN_CONFIG_LOW_POWER_MODE: - ret = tegra_io_pad_is_powered(pad->id); + ret = tegra_io_pad_is_powered(pmc, pad->id); if (ret < 0) return ret; arg = !ret; @@ -1540,12 +1569,14 @@ static int tegra_io_pad_pinconf_set(struct pinctrl_dev *pctl_dev, unsigned int pin, unsigned long *configs, unsigned int num_configs) { - const struct tegra_io_pad_soc *pad = tegra_io_pad_find(pmc, pin); + struct tegra_pmc *pmc = pinctrl_dev_get_drvdata(pctl_dev); + const struct tegra_io_pad_soc *pad; enum pin_config_param param; unsigned int i; int err; u32 arg; + pad = tegra_io_pad_find(pmc, pin); if (!pad) return -EINVAL; @@ -1566,7 +1597,7 @@ static int tegra_io_pad_pinconf_set(struct pinctrl_dev *pctl_dev, if (arg != TEGRA_IO_PAD_VOLTAGE_1V8 && arg != TEGRA_IO_PAD_VOLTAGE_3V3) return -EINVAL; - err = tegra_io_pad_set_voltage(pad->id, arg); + err = tegra_io_pad_set_voltage(pmc, pad->id, arg); if (err) return err; break; @@ -1615,7 +1646,7 @@ static ssize_t reset_reason_show(struct device *dev, { u32 value, rst_src; - value = tegra_pmc_readl(pmc->soc->regs->rst_status); + value = tegra_pmc_readl(pmc, pmc->soc->regs->rst_status); rst_src = (value & pmc->soc->regs->rst_source_mask) >> pmc->soc->regs->rst_source_shift; @@ -1629,7 +1660,7 @@ static ssize_t reset_level_show(struct device *dev, { u32 value, rst_lvl; - value = tegra_pmc_readl(pmc->soc->regs->rst_status); + value = tegra_pmc_readl(pmc, pmc->soc->regs->rst_status); rst_lvl = (value & pmc->soc->regs->rst_level_mask) >> pmc->soc->regs->rst_level_shift; @@ -1926,6 +1957,8 @@ static int tegra_pmc_probe(struct platform_device *pdev) pmc->base = base; mutex_unlock(&pmc->powergates_lock); + platform_set_drvdata(pdev, pmc); + return 0; cleanup_restart_handler: @@ -1938,14 +1971,18 @@ cleanup_debugfs: #if defined(CONFIG_PM_SLEEP) && defined(CONFIG_ARM) static int tegra_pmc_suspend(struct device *dev) { - tegra_pmc_writel(virt_to_phys(tegra_resume), PMC_SCRATCH41); + struct tegra_pmc *pmc = dev_get_drvdata(dev); + + tegra_pmc_writel(pmc, virt_to_phys(tegra_resume), PMC_SCRATCH41); return 0; } static int tegra_pmc_resume(struct device *dev) { - tegra_pmc_writel(0x0, PMC_SCRATCH41); + struct tegra_pmc *pmc = dev_get_drvdata(dev); + + tegra_pmc_writel(pmc, 0x0, PMC_SCRATCH41); return 0; } @@ -1982,11 +2019,11 @@ static void tegra20_pmc_init(struct tegra_pmc *pmc) u32 value; /* Always enable CPU power request */ - value = tegra_pmc_readl(PMC_CNTRL); + value = tegra_pmc_readl(pmc, PMC_CNTRL); value |= PMC_CNTRL_CPU_PWRREQ_OE; - tegra_pmc_writel(value, PMC_CNTRL); + tegra_pmc_writel(pmc, value, PMC_CNTRL); - value = tegra_pmc_readl(PMC_CNTRL); + value = tegra_pmc_readl(pmc, PMC_CNTRL); if (pmc->sysclkreq_high) value &= ~PMC_CNTRL_SYSCLK_POLARITY; @@ -1994,12 +2031,12 @@ static void tegra20_pmc_init(struct tegra_pmc *pmc) value |= PMC_CNTRL_SYSCLK_POLARITY; /* configure the output polarity while the request is tristated */ - tegra_pmc_writel(value, PMC_CNTRL); + tegra_pmc_writel(pmc, value, PMC_CNTRL); /* now enable the request */ - value = tegra_pmc_readl(PMC_CNTRL); + value = tegra_pmc_readl(pmc, PMC_CNTRL); value |= PMC_CNTRL_SYSCLK_OE; - tegra_pmc_writel(value, PMC_CNTRL); + tegra_pmc_writel(pmc, value, PMC_CNTRL); } static void tegra20_pmc_setup_irq_polarity(struct tegra_pmc *pmc, @@ -2008,14 +2045,14 @@ static void tegra20_pmc_setup_irq_polarity(struct tegra_pmc *pmc, { u32 value; - value = tegra_pmc_readl(PMC_CNTRL); + value = tegra_pmc_readl(pmc, PMC_CNTRL); if (invert) value |= PMC_CNTRL_INTR_POLARITY; else value &= ~PMC_CNTRL_INTR_POLARITY; - tegra_pmc_writel(value, PMC_CNTRL); + tegra_pmc_writel(pmc, value, PMC_CNTRL); } static const struct tegra_pmc_soc tegra20_pmc_soc = { @@ -2419,7 +2456,7 @@ static void tegra186_pmc_setup_irq_polarity(struct tegra_pmc *pmc, index = of_property_match_string(np, "reg-names", "wake"); if (index < 0) { - pr_err("failed to find PMC wake registers\n"); + dev_err(pmc->dev, "failed to find PMC wake registers\n"); return; } @@ -2427,7 +2464,7 @@ static void tegra186_pmc_setup_irq_polarity(struct tegra_pmc *pmc, wake = ioremap_nocache(regs.start, resource_size(®s)); if (!wake) { - pr_err("failed to map PMC wake registers\n"); + dev_err(pmc->dev, "failed to map PMC wake registers\n"); return; } -- cgit From f1d912996fc054f1d7059b23371cd623692724ce Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 25 Jan 2019 11:22:53 +0100 Subject: soc/tegra: pmc: Make alignment consistent Some recently added code used weird alignment and indentation. Fix these occurrences to make them consistent with the rest of the code. Signed-off-by: Thierry Reding Acked-by: Jon Hunter --- drivers/soc/tegra/pmc.c | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) (limited to 'drivers/soc') diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c index 27b0a0310abc..da0b6680c772 100644 --- a/drivers/soc/tegra/pmc.c +++ b/drivers/soc/tegra/pmc.c @@ -1502,8 +1502,8 @@ static int tegra_io_pad_pinctrl_get_groups_count(struct pinctrl_dev *pctl_dev) return pmc->soc->num_io_pads; } -static const char *tegra_io_pad_pinctrl_get_group_name( - struct pinctrl_dev *pctl, unsigned int group) +static const char *tegra_io_pad_pinctrl_get_group_name(struct pinctrl_dev *pctl, + unsigned int group) { struct tegra_pmc *pmc = pinctrl_dev_get_drvdata(pctl); @@ -1519,6 +1519,7 @@ static int tegra_io_pad_pinctrl_get_group_pins(struct pinctrl_dev *pctl_dev, *pins = &pmc->soc->io_pads[group].id; *num_pins = 1; + return 0; } @@ -1548,14 +1549,18 @@ static int tegra_io_pad_pinconf_get(struct pinctrl_dev *pctl_dev, ret = tegra_io_pad_get_voltage(pmc, pad->id); if (ret < 0) return ret; + arg = ret; break; + case PIN_CONFIG_LOW_POWER_MODE: ret = tegra_io_pad_is_powered(pmc, pad->id); if (ret < 0) return ret; + arg = !ret; break; + default: return -EINVAL; } @@ -1622,7 +1627,7 @@ static struct pinctrl_desc tegra_pmc_pctl_desc = { static int tegra_pmc_pinctrl_init(struct tegra_pmc *pmc) { - int err = 0; + int err; if (!pmc->soc->num_pin_descs) return 0; @@ -1635,14 +1640,16 @@ static int tegra_pmc_pinctrl_init(struct tegra_pmc *pmc) pmc); if (IS_ERR(pmc->pctl_dev)) { err = PTR_ERR(pmc->pctl_dev); - dev_err(pmc->dev, "unable to register pinctrl, %d\n", err); + dev_err(pmc->dev, "failed to register pin controller: %d\n", + err); + return err; } - return err; + return 0; } static ssize_t reset_reason_show(struct device *dev, - struct device_attribute *attr, char *buf) + struct device_attribute *attr, char *buf) { u32 value, rst_src; @@ -1656,7 +1663,7 @@ static ssize_t reset_reason_show(struct device *dev, static DEVICE_ATTR_RO(reset_reason); static ssize_t reset_level_show(struct device *dev, - struct device_attribute *attr, char *buf) + struct device_attribute *attr, char *buf) { u32 value, rst_lvl; @@ -1678,16 +1685,16 @@ static void tegra_pmc_reset_sysfs_init(struct tegra_pmc *pmc) err = device_create_file(dev, &dev_attr_reset_reason); if (err < 0) dev_warn(dev, - "failed to create attr \"reset_reason\": %d\n", - err); + "failed to create attr \"reset_reason\": %d\n", + err); } if (pmc->soc->reset_levels) { err = device_create_file(dev, &dev_attr_reset_level); if (err < 0) dev_warn(dev, - "failed to create attr \"reset_level\": %d\n", - err); + "failed to create attr \"reset_level\": %d\n", + err); } } -- cgit From fa3bc04ef8ccccfe47db1f4030a7a43569956402 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 25 Jan 2019 11:22:54 +0100 Subject: soc/tegra: pmc: Explicitly initialize all fields It's not strictly necessary to initialize the fields in struct tegra_pmc_soc if they are 0/false. However, we already initialize them explicitly even if unnecessary, so keep doing that for consistency. Signed-off-by: Thierry Reding Acked-by: Jon Hunter --- drivers/soc/tegra/pmc.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'drivers/soc') diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c index da0b6680c772..dba89fc81a04 100644 --- a/drivers/soc/tegra/pmc.c +++ b/drivers/soc/tegra/pmc.c @@ -2069,6 +2069,8 @@ static const struct tegra_pmc_soc tegra20_pmc_soc = { .cpu_powergates = NULL, .has_tsense_reset = false, .has_gpu_clamps = false, + .needs_mbist_war = false, + .has_impl_33v_pwr = false, .num_io_pads = 0, .io_pads = NULL, .num_pin_descs = 0, @@ -2113,6 +2115,7 @@ static const struct tegra_pmc_soc tegra30_pmc_soc = { .cpu_powergates = tegra30_cpu_powergates, .has_tsense_reset = true, .has_gpu_clamps = false, + .needs_mbist_war = false, .has_impl_33v_pwr = false, .num_io_pads = 0, .io_pads = NULL, @@ -2162,6 +2165,7 @@ static const struct tegra_pmc_soc tegra114_pmc_soc = { .cpu_powergates = tegra114_cpu_powergates, .has_tsense_reset = true, .has_gpu_clamps = false, + .needs_mbist_war = false, .has_impl_33v_pwr = false, .num_io_pads = 0, .io_pads = NULL, @@ -2271,6 +2275,7 @@ static const struct tegra_pmc_soc tegra124_pmc_soc = { .cpu_powergates = tegra124_cpu_powergates, .has_tsense_reset = true, .has_gpu_clamps = true, + .needs_mbist_war = false, .has_impl_33v_pwr = false, .num_io_pads = ARRAY_SIZE(tegra124_io_pads), .io_pads = tegra124_io_pads, @@ -2375,8 +2380,8 @@ static const struct tegra_pmc_soc tegra210_pmc_soc = { .cpu_powergates = tegra210_cpu_powergates, .has_tsense_reset = true, .has_gpu_clamps = true, - .has_impl_33v_pwr = false, .needs_mbist_war = true, + .has_impl_33v_pwr = false, .num_io_pads = ARRAY_SIZE(tegra210_io_pads), .io_pads = tegra210_io_pads, .num_pin_descs = ARRAY_SIZE(tegra210_pin_descs), @@ -2499,6 +2504,7 @@ static const struct tegra_pmc_soc tegra186_pmc_soc = { .cpu_powergates = NULL, .has_tsense_reset = false, .has_gpu_clamps = false, + .needs_mbist_war = false, .has_impl_33v_pwr = true, .num_io_pads = ARRAY_SIZE(tegra186_io_pads), .io_pads = tegra186_io_pads, @@ -2577,6 +2583,8 @@ static const struct tegra_pmc_soc tegra194_pmc_soc = { .cpu_powergates = NULL, .has_tsense_reset = false, .has_gpu_clamps = false, + .needs_mbist_war = false, + .has_impl_33v_pwr = false, .num_io_pads = ARRAY_SIZE(tegra194_io_pads), .io_pads = tegra194_io_pads, .regs = &tegra186_pmc_regs, -- cgit From e247deae1a55089cb04cc25c91faeba083d0c39c Mon Sep 17 00:00:00 2001 From: Mikko Perttunen Date: Fri, 25 Jan 2019 11:22:55 +0100 Subject: soc/tegra: pmc: Support systems where PMC is marked secure On Tegra210 systems with new enough boot software, direct register accesses to PMC register space from the non-secure world are not allowed. Instead a monitor call may be used to read and write PMC registers. Add code to detect such a system by attempting to write a scratch register and detecting if the write happened or not. If not, we switch to doing all register accesses through the monitor call. Signed-off-by: Mikko Perttunen Signed-off-by: Thierry Reding Acked-by: Jon Hunter --- drivers/soc/tegra/pmc.c | 100 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 97 insertions(+), 3 deletions(-) (limited to 'drivers/soc') diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c index dba89fc81a04..0df258518693 100644 --- a/drivers/soc/tegra/pmc.c +++ b/drivers/soc/tegra/pmc.c @@ -20,6 +20,7 @@ #define pr_fmt(fmt) "tegra-pmc: " fmt +#include #include #include #include @@ -145,6 +146,11 @@ #define WAKE_AOWAKE_CTRL 0x4f4 #define WAKE_AOWAKE_CTRL_INTR_POLARITY BIT(0) +/* for secure PMC */ +#define TEGRA_SMC_PMC 0xc2fffe00 +#define TEGRA_SMC_PMC_READ 0xaa +#define TEGRA_SMC_PMC_WRITE 0xbb + struct tegra_powergate { struct generic_pm_domain genpd; struct tegra_pmc *pmc; @@ -216,6 +222,7 @@ struct tegra_pmc_soc { bool has_gpu_clamps; bool needs_mbist_war; bool has_impl_33v_pwr; + bool maybe_tz_only; const struct tegra_io_pad_soc *io_pads; unsigned int num_io_pads; @@ -278,6 +285,7 @@ static const char * const tegra30_reset_sources[] = { * @scratch: pointer to I/O remapped region for scratch registers * @clk: pointer to pclk clock * @soc: pointer to SoC data structure + * @tz_only: flag specifying if the PMC can only be accessed via TrustZone * @debugfs: pointer to debugfs entry * @rate: currently configured rate of pclk * @suspend_mode: lowest suspend mode available @@ -308,6 +316,7 @@ struct tegra_pmc { struct dentry *debugfs; const struct tegra_pmc_soc *soc; + bool tz_only; unsigned long rate; @@ -346,13 +355,62 @@ to_powergate(struct generic_pm_domain *domain) static u32 tegra_pmc_readl(struct tegra_pmc *pmc, unsigned long offset) { + struct arm_smccc_res res; + + if (pmc->tz_only) { + arm_smccc_smc(TEGRA_SMC_PMC, TEGRA_SMC_PMC_READ, offset, 0, 0, + 0, 0, 0, &res); + if (res.a0) { + if (pmc->dev) + dev_warn(pmc->dev, "%s(): SMC failed: %lu\n", + __func__, res.a0); + else + pr_warn("%s(): SMC failed: %lu\n", __func__, + res.a0); + } + + return res.a1; + } + return readl(pmc->base + offset); } static void tegra_pmc_writel(struct tegra_pmc *pmc, u32 value, unsigned long offset) { - writel(value, pmc->base + offset); + struct arm_smccc_res res; + + if (pmc->tz_only) { + arm_smccc_smc(TEGRA_SMC_PMC, TEGRA_SMC_PMC_WRITE, offset, + value, 0, 0, 0, 0, &res); + if (res.a0) { + if (pmc->dev) + dev_warn(pmc->dev, "%s(): SMC failed: %lu\n", + __func__, res.a0); + else + pr_warn("%s(): SMC failed: %lu\n", __func__, + res.a0); + } + } else { + writel(value, pmc->base + offset); + } +} + +static u32 tegra_pmc_scratch_readl(struct tegra_pmc *pmc, unsigned long offset) +{ + if (pmc->tz_only) + return tegra_pmc_readl(pmc, offset); + + return readl(pmc->scratch + offset); +} + +static void tegra_pmc_scratch_writel(struct tegra_pmc *pmc, u32 value, + unsigned long offset) +{ + if (pmc->tz_only) + tegra_pmc_writel(pmc, value, offset); + else + writel(value, pmc->scratch + offset); } /* @@ -776,7 +834,7 @@ static int tegra_pmc_restart_notify(struct notifier_block *this, const char *cmd = data; u32 value; - value = readl(pmc->scratch + pmc->soc->regs->scratch0); + value = tegra_pmc_scratch_readl(pmc, pmc->soc->regs->scratch0); value &= ~PMC_SCRATCH0_MODE_MASK; if (cmd) { @@ -790,7 +848,7 @@ static int tegra_pmc_restart_notify(struct notifier_block *this, value |= PMC_SCRATCH0_MODE_RCM; } - writel(value, pmc->scratch + pmc->soc->regs->scratch0); + tegra_pmc_scratch_writel(pmc, value, pmc->soc->regs->scratch0); /* reset everything but PMC_SCRATCH0 and PMC_RST_STATUS */ value = tegra_pmc_readl(pmc, PMC_CNTRL); @@ -2071,6 +2129,7 @@ static const struct tegra_pmc_soc tegra20_pmc_soc = { .has_gpu_clamps = false, .needs_mbist_war = false, .has_impl_33v_pwr = false, + .maybe_tz_only = false, .num_io_pads = 0, .io_pads = NULL, .num_pin_descs = 0, @@ -2117,6 +2176,7 @@ static const struct tegra_pmc_soc tegra30_pmc_soc = { .has_gpu_clamps = false, .needs_mbist_war = false, .has_impl_33v_pwr = false, + .maybe_tz_only = false, .num_io_pads = 0, .io_pads = NULL, .num_pin_descs = 0, @@ -2167,6 +2227,7 @@ static const struct tegra_pmc_soc tegra114_pmc_soc = { .has_gpu_clamps = false, .needs_mbist_war = false, .has_impl_33v_pwr = false, + .maybe_tz_only = false, .num_io_pads = 0, .io_pads = NULL, .num_pin_descs = 0, @@ -2277,6 +2338,7 @@ static const struct tegra_pmc_soc tegra124_pmc_soc = { .has_gpu_clamps = true, .needs_mbist_war = false, .has_impl_33v_pwr = false, + .maybe_tz_only = false, .num_io_pads = ARRAY_SIZE(tegra124_io_pads), .io_pads = tegra124_io_pads, .num_pin_descs = ARRAY_SIZE(tegra124_pin_descs), @@ -2382,6 +2444,7 @@ static const struct tegra_pmc_soc tegra210_pmc_soc = { .has_gpu_clamps = true, .needs_mbist_war = true, .has_impl_33v_pwr = false, + .maybe_tz_only = true, .num_io_pads = ARRAY_SIZE(tegra210_io_pads), .io_pads = tegra210_io_pads, .num_pin_descs = ARRAY_SIZE(tegra210_pin_descs), @@ -2506,6 +2569,7 @@ static const struct tegra_pmc_soc tegra186_pmc_soc = { .has_gpu_clamps = false, .needs_mbist_war = false, .has_impl_33v_pwr = true, + .maybe_tz_only = false, .num_io_pads = ARRAY_SIZE(tegra186_io_pads), .io_pads = tegra186_io_pads, .num_pin_descs = ARRAY_SIZE(tegra186_pin_descs), @@ -2585,6 +2649,7 @@ static const struct tegra_pmc_soc tegra194_pmc_soc = { .has_gpu_clamps = false, .needs_mbist_war = false, .has_impl_33v_pwr = false, + .maybe_tz_only = false, .num_io_pads = ARRAY_SIZE(tegra194_io_pads), .io_pads = tegra194_io_pads, .regs = &tegra186_pmc_regs, @@ -2619,6 +2684,32 @@ static struct platform_driver tegra_pmc_driver = { }; builtin_platform_driver(tegra_pmc_driver); +static bool __init tegra_pmc_detect_tz_only(struct tegra_pmc *pmc) +{ + u32 value, saved; + + saved = readl(pmc->base + pmc->soc->regs->scratch0); + value = saved ^ 0xffffffff; + + if (value == 0xffffffff) + value = 0xdeadbeef; + + /* write pattern and read it back */ + writel(value, pmc->base + pmc->soc->regs->scratch0); + value = readl(pmc->base + pmc->soc->regs->scratch0); + + /* if we read all-zeroes, access is restricted to TZ only */ + if (value == 0) { + pr_info("access to PMC is restricted to TZ\n"); + return true; + } + + /* restore original value */ + writel(saved, pmc->base + pmc->soc->regs->scratch0); + + return false; +} + /* * Early initialization to allow access to registers in the very early boot * process. @@ -2681,6 +2772,9 @@ static int __init tegra_pmc_early_init(void) if (np) { pmc->soc = match->data; + if (pmc->soc->maybe_tz_only) + pmc->tz_only = tegra_pmc_detect_tz_only(pmc); + tegra_powergate_init(pmc, np); /* -- cgit From 2b13ef1f4261688c2dc5d67dbee69ed2fdddf53e Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Wed, 30 Jan 2019 10:21:44 -0800 Subject: soc: ti: knav_dma: Use proper enum in pktdma_init_chan Clang warns when one enumerated type is implicitly converted to another: drivers/soc/ti/knav_dma.c:601:20: warning: implicit conversion from enumeration type 'enum dma_data_direction' to different enumeration type 'enum dma_transfer_direction' [-Wenum-conversion] chan->direction = DMA_NONE; ~ ^~~~~~~~ 1 warning generated. While DMA_NONE and DMA_TRANS_NONE have different values, there is no functional change because direction is never checked against DMA_NONE, only against DMA_MEM_TO_DEV and DMA_DEV_TO_MEM. Signed-off-by: Nathan Chancellor Signed-off-by: Santosh Shilimkar --- drivers/soc/ti/knav_dma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/soc') diff --git a/drivers/soc/ti/knav_dma.c b/drivers/soc/ti/knav_dma.c index e05ab16d9a9e..6285cd8efb21 100644 --- a/drivers/soc/ti/knav_dma.c +++ b/drivers/soc/ti/knav_dma.c @@ -598,7 +598,7 @@ static int pktdma_init_chan(struct knav_dma_device *dma, INIT_LIST_HEAD(&chan->list); chan->dma = dma; - chan->direction = DMA_NONE; + chan->direction = DMA_TRANS_NONE; atomic_set(&chan->ref_count, 0); spin_lock_init(&chan->lock); -- cgit From 722f761084bd1e1a26ba096b48ba8c09048413a1 Mon Sep 17 00:00:00 2001 From: Wei Yongjun Date: Wed, 16 Jan 2019 05:44:17 +0000 Subject: soc: bcm: bcm2835-pm: Make local symbol static Fixes the following sparse warning: drivers/soc/bcm/bcm2835-power.c:556:32: warning: symbol 'bcm2835_reset_ops' was not declared. Should it be static? Fixes: 670c672608a1 ("soc: bcm: bcm2835-pm: Add support for power domains under a new binding.") Signed-off-by: Wei Yongjun Acked-by: Scott Branden Signed-off-by: Florian Fainelli --- drivers/soc/bcm/bcm2835-power.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/soc') diff --git a/drivers/soc/bcm/bcm2835-power.c b/drivers/soc/bcm/bcm2835-power.c index 48412957ec7a..9351349cf0a9 100644 --- a/drivers/soc/bcm/bcm2835-power.c +++ b/drivers/soc/bcm/bcm2835-power.c @@ -553,7 +553,7 @@ static int bcm2835_reset_status(struct reset_controller_dev *rcdev, } } -const struct reset_control_ops bcm2835_reset_ops = { +static const struct reset_control_ops bcm2835_reset_ops = { .reset = bcm2835_reset_reset, .status = bcm2835_reset_status, }; -- cgit From b4aa93bca91318a8cf832b9fd34a830e44660d50 Mon Sep 17 00:00:00 2001 From: Evan Green Date: Wed, 2 Jan 2019 16:02:12 -0800 Subject: soc: qcom: rmtfs-mem: Add class to enable uevents Currently the qcom_rmtfs_memN devices are entirely invisible to the udev world. Add a class to the rmtfs device so that uevents fire when the device is added. Reviewed-by: Brian Norris Reviewed-by: Bjorn Andersson Signed-off-by: Evan Green Signed-off-by: Bjorn Andersson Signed-off-by: Andy Gross --- drivers/soc/qcom/rmtfs_mem.c | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/rmtfs_mem.c b/drivers/soc/qcom/rmtfs_mem.c index 97bb5989aa21..b2fa8fc8016c 100644 --- a/drivers/soc/qcom/rmtfs_mem.c +++ b/drivers/soc/qcom/rmtfs_mem.c @@ -132,6 +132,11 @@ static int qcom_rmtfs_mem_release(struct inode *inode, struct file *filp) return 0; } +static struct class rmtfs_class = { + .owner = THIS_MODULE, + .name = "rmtfs", +}; + static const struct file_operations qcom_rmtfs_mem_fops = { .owner = THIS_MODULE, .open = qcom_rmtfs_mem_open, @@ -199,6 +204,7 @@ static int qcom_rmtfs_mem_probe(struct platform_device *pdev) dev_set_name(&rmtfs_mem->dev, "qcom_rmtfs_mem%d", client_id); rmtfs_mem->dev.id = client_id; + rmtfs_mem->dev.class = &rmtfs_class; rmtfs_mem->dev.devt = MKDEV(MAJOR(qcom_rmtfs_mem_major), client_id); ret = cdev_device_add(&rmtfs_mem->cdev, &rmtfs_mem->dev); @@ -277,32 +283,42 @@ static struct platform_driver qcom_rmtfs_mem_driver = { }, }; -static int qcom_rmtfs_mem_init(void) +static int __init qcom_rmtfs_mem_init(void) { int ret; + ret = class_register(&rmtfs_class); + if (ret) + return ret; + ret = alloc_chrdev_region(&qcom_rmtfs_mem_major, 0, QCOM_RMTFS_MEM_DEV_MAX, "qcom_rmtfs_mem"); if (ret < 0) { pr_err("qcom_rmtfs_mem: failed to allocate char dev region\n"); - return ret; + goto unregister_class; } ret = platform_driver_register(&qcom_rmtfs_mem_driver); if (ret < 0) { pr_err("qcom_rmtfs_mem: failed to register rmtfs_mem driver\n"); - unregister_chrdev_region(qcom_rmtfs_mem_major, - QCOM_RMTFS_MEM_DEV_MAX); + goto unregister_chrdev; } + return 0; + +unregister_chrdev: + unregister_chrdev_region(qcom_rmtfs_mem_major, QCOM_RMTFS_MEM_DEV_MAX); +unregister_class: + class_unregister(&rmtfs_class); return ret; } module_init(qcom_rmtfs_mem_init); -static void qcom_rmtfs_mem_exit(void) +static void __exit qcom_rmtfs_mem_exit(void) { platform_driver_unregister(&qcom_rmtfs_mem_driver); unregister_chrdev_region(qcom_rmtfs_mem_major, QCOM_RMTFS_MEM_DEV_MAX); + class_unregister(&rmtfs_class); } module_exit(qcom_rmtfs_mem_exit); -- cgit From f58b0f9d9dd6d033396561517bb191d904df9e54 Mon Sep 17 00:00:00 2001 From: Evan Green Date: Wed, 2 Jan 2019 16:02:13 -0800 Subject: soc: qcom: rmtfs-mem: Make sysfs attributes world-readable In order to run an rmtfs daemon as an unprivileged user, that user would need access to the phys_addr and size sysfs attributes. Sharing these attributes with unprivileged users doesn't really leak anything sensitive, since if you have access to physical memory, the jig is up anyway. Make those attributes readable by all. Reviewed-by: Brian Norris Reviewed-by: Bjorn Andersson Signed-off-by: Evan Green Signed-off-by: Bjorn Andersson Signed-off-by: Andy Gross --- drivers/soc/qcom/rmtfs_mem.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/rmtfs_mem.c b/drivers/soc/qcom/rmtfs_mem.c index b2fa8fc8016c..7200d762a951 100644 --- a/drivers/soc/qcom/rmtfs_mem.c +++ b/drivers/soc/qcom/rmtfs_mem.c @@ -45,9 +45,9 @@ static ssize_t qcom_rmtfs_mem_show(struct device *dev, struct device_attribute *attr, char *buf); -static DEVICE_ATTR(phys_addr, 0400, qcom_rmtfs_mem_show, NULL); -static DEVICE_ATTR(size, 0400, qcom_rmtfs_mem_show, NULL); -static DEVICE_ATTR(client_id, 0400, qcom_rmtfs_mem_show, NULL); +static DEVICE_ATTR(phys_addr, 0444, qcom_rmtfs_mem_show, NULL); +static DEVICE_ATTR(size, 0444, qcom_rmtfs_mem_show, NULL); +static DEVICE_ATTR(client_id, 0444, qcom_rmtfs_mem_show, NULL); static ssize_t qcom_rmtfs_mem_show(struct device *dev, struct device_attribute *attr, -- cgit From 7300241926e8cf5b5f01059b0c07896fb1c74d9e Mon Sep 17 00:00:00 2001 From: "Raju P.L.S.S.S.N" Date: Tue, 23 Oct 2018 22:59:08 +0530 Subject: drivers: qcom: rpmh: avoid sending sleep/wake sets immediately Fix the redundant call being made to send the sleep and wake requests immediately to the controller. As per the patch [1], the sleep and wake request votes are cached in rpmh controller and sent during rpmh_flush(). These requests needs to be sent only during entry of deeper system low power modes or suspend. [1] https://patchwork.kernel.org/patch/10477533/ Reviewed-by: Lina Iyer Reviewed-by: Evan Green Signed-off-by: Raju P.L.S.S.S.N Signed-off-by: Bjorn Andersson Signed-off-by: Andy Gross --- drivers/soc/qcom/rpmh.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/rpmh.c b/drivers/soc/qcom/rpmh.c index c7beb6841289..9fb25e627698 100644 --- a/drivers/soc/qcom/rpmh.c +++ b/drivers/soc/qcom/rpmh.c @@ -192,9 +192,8 @@ static int __rpmh_write(const struct device *dev, enum rpmh_state state, WARN_ON(irqs_disabled()); ret = rpmh_rsc_send_data(ctrlr_to_drv(ctrlr), &rpm_msg->msg); } else { - ret = rpmh_rsc_write_ctrl_data(ctrlr_to_drv(ctrlr), - &rpm_msg->msg); /* Clean up our call by spoofing tx_done */ + ret = 0; rpmh_tx_done(&rpm_msg->msg, ret); } -- cgit From baef1c90aac7e5bf13f0360a3b334825a23d31a1 Mon Sep 17 00:00:00 2001 From: Stephen Boyd Date: Tue, 15 Jan 2019 14:54:47 -0800 Subject: soc: qcom: rpmh: Avoid accessing freed memory from batch API Using the batch API from the interconnect driver sometimes leads to a KASAN error due to an access to freed memory. This is easier to trigger with threadirqs on the kernel commandline. BUG: KASAN: use-after-free in rpmh_tx_done+0x114/0x12c Read of size 1 at addr fffffff51414ad84 by task irq/110-apps_rs/57 CPU: 0 PID: 57 Comm: irq/110-apps_rs Tainted: G W 4.19.10 #72 Call trace: dump_backtrace+0x0/0x2f8 show_stack+0x20/0x2c __dump_stack+0x20/0x28 dump_stack+0xcc/0x10c print_address_description+0x74/0x240 kasan_report+0x250/0x26c __asan_report_load1_noabort+0x20/0x2c rpmh_tx_done+0x114/0x12c tcs_tx_done+0x450/0x768 irq_forced_thread_fn+0x58/0x9c irq_thread+0x120/0x1dc kthread+0x248/0x260 ret_from_fork+0x10/0x18 Allocated by task 385: kasan_kmalloc+0xac/0x148 __kmalloc+0x170/0x1e4 rpmh_write_batch+0x174/0x540 qcom_icc_set+0x8dc/0x9ac icc_set+0x288/0x2e8 a6xx_gmu_stop+0x320/0x3c0 a6xx_pm_suspend+0x108/0x124 adreno_suspend+0x50/0x60 pm_generic_runtime_suspend+0x60/0x78 __rpm_callback+0x214/0x32c rpm_callback+0x54/0x184 rpm_suspend+0x3f8/0xa90 pm_runtime_work+0xb4/0x178 process_one_work+0x544/0xbc0 worker_thread+0x514/0x7d0 kthread+0x248/0x260 ret_from_fork+0x10/0x18 Freed by task 385: __kasan_slab_free+0x12c/0x1e0 kasan_slab_free+0x10/0x1c kfree+0x134/0x588 rpmh_write_batch+0x49c/0x540 qcom_icc_set+0x8dc/0x9ac icc_set+0x288/0x2e8 a6xx_gmu_stop+0x320/0x3c0 a6xx_pm_suspend+0x108/0x124 adreno_suspend+0x50/0x60 cr50_spi spi5.0: SPI transfer timed out pm_generic_runtime_suspend+0x60/0x78 __rpm_callback+0x214/0x32c rpm_callback+0x54/0x184 rpm_suspend+0x3f8/0xa90 pm_runtime_work+0xb4/0x178 process_one_work+0x544/0xbc0 worker_thread+0x514/0x7d0 kthread+0x248/0x260 ret_from_fork+0x10/0x18 The buggy address belongs to the object at fffffff51414ac80 which belongs to the cache kmalloc-512 of size 512 The buggy address is located 260 bytes inside of 512-byte region [fffffff51414ac80, fffffff51414ae80) The buggy address belongs to the page: page:ffffffbfd4505200 count:1 mapcount:0 mapping:fffffff51e00c680 index:0x0 compound_mapcount: 0 flags: 0x4000000000008100(slab|head) raw: 4000000000008100 ffffffbfd4529008 ffffffbfd44f9208 fffffff51e00c680 raw: 0000000000000000 0000000000200020 00000001ffffffff 0000000000000000 page dumped because: kasan: bad access detected Memory state around the buggy address: fffffff51414ac80: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fffffff51414ad00: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb >fffffff51414ad80: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb ^ fffffff51414ae00: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fffffff51414ae80: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc The batch API sets the same completion for each rpmh message that's sent and then loops through all the messages and waits for that single completion declared on the stack to be completed before returning from the function and freeing the message structures. Unfortunately, some messages may still be in process and 'stuck' in the TCS. At some later point, the tcs_tx_done() interrupt will run and try to process messages that have already been freed at the end of rpmh_write_batch(). This will in turn access the 'needs_free' member of the rpmh_request structure and cause KASAN to complain. Furthermore, if there's a message that's completed in rpmh_tx_done() and freed immediately after the complete() call is made we'll be racing with potentially freed memory when accessing the 'needs_free' member: CPU0 CPU1 ---- ---- rpmh_tx_done() complete(&compl) wait_for_completion(&compl) kfree(rpm_msg) if (rpm_msg->needs_free) Let's fix this by allocating a chunk of completions for each message and waiting for all of them to be completed before returning from the batch API. Alternatively, we could wait for the last message in the batch, but that may be a more complicated change because it looks like tcs_tx_done() just iterates through the indices of the queue and completes each message instead of tracking the last inserted message and completing that first. Fixes: c8790cb6da58 ("drivers: qcom: rpmh: add support for batch RPMH request") Cc: Lina Iyer Cc: "Raju P.L.S.S.S.N" Cc: Matthias Kaehlcke Cc: Evan Green Cc: stable@vger.kernel.org Reviewed-by: Lina Iyer Reviewed-by: Evan Green Signed-off-by: Stephen Boyd Signed-off-by: Bjorn Andersson Signed-off-by: Andy Gross --- drivers/soc/qcom/rpmh.c | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/rpmh.c b/drivers/soc/qcom/rpmh.c index 9fb25e627698..035091fd44b8 100644 --- a/drivers/soc/qcom/rpmh.c +++ b/drivers/soc/qcom/rpmh.c @@ -80,6 +80,7 @@ void rpmh_tx_done(const struct tcs_request *msg, int r) struct rpmh_request *rpm_msg = container_of(msg, struct rpmh_request, msg); struct completion *compl = rpm_msg->completion; + bool free = rpm_msg->needs_free; rpm_msg->err = r; @@ -94,7 +95,7 @@ void rpmh_tx_done(const struct tcs_request *msg, int r) complete(compl); exit: - if (rpm_msg->needs_free) + if (free) kfree(rpm_msg); } @@ -347,11 +348,12 @@ int rpmh_write_batch(const struct device *dev, enum rpmh_state state, { struct batch_cache_req *req; struct rpmh_request *rpm_msgs; - DECLARE_COMPLETION_ONSTACK(compl); + struct completion *compls; struct rpmh_ctrlr *ctrlr = get_rpmh_ctrlr(dev); unsigned long time_left; int count = 0; - int ret, i, j; + int ret, i; + void *ptr; if (!cmd || !n) return -EINVAL; @@ -361,10 +363,15 @@ int rpmh_write_batch(const struct device *dev, enum rpmh_state state, if (!count) return -EINVAL; - req = kzalloc(sizeof(*req) + count * sizeof(req->rpm_msgs[0]), + ptr = kzalloc(sizeof(*req) + + count * (sizeof(req->rpm_msgs[0]) + sizeof(*compls)), GFP_ATOMIC); - if (!req) + if (!ptr) return -ENOMEM; + + req = ptr; + compls = ptr + sizeof(*req) + count * sizeof(*rpm_msgs); + req->count = count; rpm_msgs = req->rpm_msgs; @@ -379,25 +386,26 @@ int rpmh_write_batch(const struct device *dev, enum rpmh_state state, } for (i = 0; i < count; i++) { - rpm_msgs[i].completion = &compl; + struct completion *compl = &compls[i]; + + init_completion(compl); + rpm_msgs[i].completion = compl; ret = rpmh_rsc_send_data(ctrlr_to_drv(ctrlr), &rpm_msgs[i].msg); if (ret) { pr_err("Error(%d) sending RPMH message addr=%#x\n", ret, rpm_msgs[i].msg.cmds[0].addr); - for (j = i; j < count; j++) - rpmh_tx_done(&rpm_msgs[j].msg, ret); break; } } time_left = RPMH_TIMEOUT_MS; - for (i = 0; i < count; i++) { - time_left = wait_for_completion_timeout(&compl, time_left); + while (i--) { + time_left = wait_for_completion_timeout(&compls[i], time_left); if (!time_left) { /* * Better hope they never finish because they'll signal - * the completion on our stack and that's bad once - * we've returned from the function. + * the completion that we're going to free once + * we've returned from this function. */ WARN_ON(1); ret = -ETIMEDOUT; @@ -406,7 +414,7 @@ int rpmh_write_batch(const struct device *dev, enum rpmh_state state, } exit: - kfree(req); + kfree(ptr); return ret; } -- cgit From 8cd09a3dd3e176c62da67efcd477a44a8d87185e Mon Sep 17 00:00:00 2001 From: Alexey Khoroshilov Date: Sat, 8 Dec 2018 01:57:04 +0300 Subject: soc: qcom: gsbi: Fix error handling in gsbi_probe() If of_platform_populate() fails in gsbi_probe(), gsbi->hclk is left undisabled. Found by Linux Driver Verification project (linuxtesting.org). Signed-off-by: Alexey Khoroshilov Signed-off-by: Bjorn Andersson Signed-off-by: Andy Gross --- drivers/soc/qcom/qcom_gsbi.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/qcom_gsbi.c b/drivers/soc/qcom/qcom_gsbi.c index 09c669e70d63..038abc377fdb 100644 --- a/drivers/soc/qcom/qcom_gsbi.c +++ b/drivers/soc/qcom/qcom_gsbi.c @@ -138,7 +138,7 @@ static int gsbi_probe(struct platform_device *pdev) struct resource *res; void __iomem *base; struct gsbi_info *gsbi; - int i; + int i, ret; u32 mask, gsbi_num; const struct crci_config *config = NULL; @@ -221,7 +221,10 @@ static int gsbi_probe(struct platform_device *pdev) platform_set_drvdata(pdev, gsbi); - return of_platform_populate(node, NULL, NULL, &pdev->dev); + ret = of_platform_populate(node, NULL, NULL, &pdev->dev); + if (ret) + clk_disable_unprepare(gsbi->hclk); + return ret; } static int gsbi_remove(struct platform_device *pdev) -- cgit From 4e2256d31f0f24107c36dae35a1d84dff0fced30 Mon Sep 17 00:00:00 2001 From: Craig Tatlor Date: Sat, 11 Aug 2018 17:24:17 +0100 Subject: soc: qcom: smd-rpm: Add sdm660 compatible Add the compatible for the RPM in SDM660, so that rpm resources can be made available. Signed-off-by: Craig Tatlor Signed-off-by: Bjorn Andersson Signed-off-by: Andy Gross --- drivers/soc/qcom/smd-rpm.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/smd-rpm.c b/drivers/soc/qcom/smd-rpm.c index b8e63724a49d..9956bb2c63f2 100644 --- a/drivers/soc/qcom/smd-rpm.c +++ b/drivers/soc/qcom/smd-rpm.c @@ -227,6 +227,7 @@ static const struct of_device_id qcom_smd_rpm_of_match[] = { { .compatible = "qcom,rpm-msm8974" }, { .compatible = "qcom,rpm-msm8996" }, { .compatible = "qcom,rpm-msm8998" }, + { .compatible = "qcom,rpm-sdm660" }, { .compatible = "qcom,rpm-qcs404" }, {} }; -- cgit From c1959066ac02bd123201352ff5a8c506469a1645 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Mon, 4 Feb 2019 17:10:05 +0300 Subject: soc: fsl: dpio: Use after free in dpaa2_dpio_remove() The dpaa2_io_down(priv->io) call frees "priv->io" so I've shifted the code around a little bit to avoid the use after free. Fixes: 991e873223e9 ("soc: fsl: dpio: use a cpumask to identify which cpus are unused") Signed-off-by: Dan Carpenter Signed-off-by: Li Yang --- drivers/soc/fsl/dpio/dpio-driver.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/soc') diff --git a/drivers/soc/fsl/dpio/dpio-driver.c b/drivers/soc/fsl/dpio/dpio-driver.c index 2d4af32a0dec..a28799b62d53 100644 --- a/drivers/soc/fsl/dpio/dpio-driver.c +++ b/drivers/soc/fsl/dpio/dpio-driver.c @@ -220,12 +220,12 @@ static int dpaa2_dpio_remove(struct fsl_mc_device *dpio_dev) dev = &dpio_dev->dev; priv = dev_get_drvdata(dev); + cpu = dpaa2_io_get_cpu(priv->io); dpaa2_io_down(priv->io); dpio_teardown_irqs(dpio_dev); - cpu = dpaa2_io_get_cpu(priv->io); cpumask_set_cpu(cpu, cpus_unused_mask); err = dpio_open(dpio_dev->mc_io, 0, dpio_dev->obj_desc.id, -- cgit From 99e5a8df8b3627239ecef09547931a81618d0851 Mon Sep 17 00:00:00 2001 From: wen yang Date: Tue, 5 Feb 2019 05:07:26 +0000 Subject: soc: amlogic: add missing of_node_put() The call to of_parse_phandle returns a node pointer with refcount incremented thus it must be explicitly decremented here after the last usage. Signed-off-by: Wen Yang Reviewed-by: Neil Armstrong Fixes: d4983983d987 ("soc: amlogic: add meson-canvas driver") Signed-off-by: Kevin Hilman --- drivers/soc/amlogic/meson-canvas.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'drivers/soc') diff --git a/drivers/soc/amlogic/meson-canvas.c b/drivers/soc/amlogic/meson-canvas.c index fce33ca76bb6..87a6c6e23591 100644 --- a/drivers/soc/amlogic/meson-canvas.c +++ b/drivers/soc/amlogic/meson-canvas.c @@ -57,9 +57,12 @@ struct meson_canvas *meson_canvas_get(struct device *dev) return ERR_PTR(-ENODEV); canvas_pdev = of_find_device_by_node(canvas_node); - if (!canvas_pdev) + if (!canvas_pdev) { + of_node_put(canvas_node); return ERR_PTR(-EPROBE_DEFER); + } + of_node_put(canvas_node); return dev_get_drvdata(&canvas_pdev->dev); } EXPORT_SYMBOL_GPL(meson_canvas_get); -- cgit From 382f8be04551d60c3e0c4103ce2941c3f335279e Mon Sep 17 00:00:00 2001 From: Neil Armstrong Date: Mon, 4 Feb 2019 10:49:37 +0100 Subject: soc: amlogic: canvas: Fix meson_canvas_get when probe failed When probe fails, a platforn_device is still associated to the node, but dev_get_drvdata() returns NULL. Handle this case by returning a consistent error. Fixes: d4983983d987 ("soc: amlogic: add meson-canvas driver") Signed-off-by: Neil Armstrong Reviewed-by: Maxime Jourdan [khilman: fixed minor typo in comment ] Signed-off-by: Kevin Hilman --- drivers/soc/amlogic/meson-canvas.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'drivers/soc') diff --git a/drivers/soc/amlogic/meson-canvas.c b/drivers/soc/amlogic/meson-canvas.c index 87a6c6e23591..be95a37c3fec 100644 --- a/drivers/soc/amlogic/meson-canvas.c +++ b/drivers/soc/amlogic/meson-canvas.c @@ -51,6 +51,7 @@ struct meson_canvas *meson_canvas_get(struct device *dev) { struct device_node *canvas_node; struct platform_device *canvas_pdev; + struct meson_canvas *canvas; canvas_node = of_parse_phandle(dev->of_node, "amlogic,canvas", 0); if (!canvas_node) @@ -63,7 +64,17 @@ struct meson_canvas *meson_canvas_get(struct device *dev) } of_node_put(canvas_node); - return dev_get_drvdata(&canvas_pdev->dev); + + /* + * If priv is NULL, it's probably because the canvas hasn't + * properly initialized. Bail out with -EINVAL because, in the + * current state, this driver probe cannot return -EPROBE_DEFER + */ + canvas = dev_get_drvdata(&canvas_pdev->dev); + if (!canvas) + return ERR_PTR(-EINVAL); + + return canvas; } EXPORT_SYMBOL_GPL(meson_canvas_get); -- cgit From 19e0bde7bf6a18a910e5d633ef606d6ff298c2cb Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Fri, 18 Jan 2019 11:34:21 +0100 Subject: soc: amlogic: clk-measure: add axg and g12a support Add support for the axg and g12a SoC family in amlogic clk measure Signed-off-by: Jerome Brunet Reviewed-by: Martin Blumenstingl [khilman: squashed some fixups from Martin] Signed-off-by: Kevin Hilman --- drivers/soc/amlogic/meson-clk-measure.c | 196 ++++++++++++++++++++++++++++++++ 1 file changed, 196 insertions(+) (limited to 'drivers/soc') diff --git a/drivers/soc/amlogic/meson-clk-measure.c b/drivers/soc/amlogic/meson-clk-measure.c index daea191a66fa..19d4cbc93a17 100644 --- a/drivers/soc/amlogic/meson-clk-measure.c +++ b/drivers/soc/amlogic/meson-clk-measure.c @@ -165,6 +165,194 @@ static struct meson_msr_id clk_msr_gx[CLK_MSR_MAX] = { CLK_MSR_ID(82, "ge2d"), }; +static struct meson_msr_id clk_msr_axg[CLK_MSR_MAX] = { + CLK_MSR_ID(0, "ring_osc_out_ee_0"), + CLK_MSR_ID(1, "ring_osc_out_ee_1"), + CLK_MSR_ID(2, "ring_osc_out_ee_2"), + CLK_MSR_ID(3, "a53_ring_osc"), + CLK_MSR_ID(4, "gp0_pll"), + CLK_MSR_ID(5, "gp1_pll"), + CLK_MSR_ID(7, "clk81"), + CLK_MSR_ID(9, "encl"), + CLK_MSR_ID(17, "sys_pll_div16"), + CLK_MSR_ID(18, "sys_cpu_div16"), + CLK_MSR_ID(20, "rtc_osc_out"), + CLK_MSR_ID(23, "mmc_clk"), + CLK_MSR_ID(28, "sar_adc"), + CLK_MSR_ID(31, "mpll_test_out"), + CLK_MSR_ID(40, "mod_eth_tx_clk"), + CLK_MSR_ID(41, "mod_eth_rx_clk_rmii"), + CLK_MSR_ID(42, "mp0_out"), + CLK_MSR_ID(43, "fclk_div5"), + CLK_MSR_ID(44, "pwm_b"), + CLK_MSR_ID(45, "pwm_a"), + CLK_MSR_ID(46, "vpu"), + CLK_MSR_ID(47, "ddr_dpll_pt"), + CLK_MSR_ID(48, "mp1_out"), + CLK_MSR_ID(49, "mp2_out"), + CLK_MSR_ID(50, "mp3_out"), + CLK_MSR_ID(51, "sd_emmm_c"), + CLK_MSR_ID(52, "sd_emmc_b"), + CLK_MSR_ID(61, "gpio_msr"), + CLK_MSR_ID(66, "audio_slv_lrclk_c"), + CLK_MSR_ID(67, "audio_slv_lrclk_b"), + CLK_MSR_ID(68, "audio_slv_lrclk_a"), + CLK_MSR_ID(69, "audio_slv_sclk_c"), + CLK_MSR_ID(70, "audio_slv_sclk_b"), + CLK_MSR_ID(71, "audio_slv_sclk_a"), + CLK_MSR_ID(72, "pwm_d"), + CLK_MSR_ID(73, "pwm_c"), + CLK_MSR_ID(74, "wifi_beacon"), + CLK_MSR_ID(75, "tdmin_lb_lrcl"), + CLK_MSR_ID(76, "tdmin_lb_sclk"), + CLK_MSR_ID(77, "rng_ring_osc_0"), + CLK_MSR_ID(78, "rng_ring_osc_1"), + CLK_MSR_ID(79, "rng_ring_osc_2"), + CLK_MSR_ID(80, "rng_ring_osc_3"), + CLK_MSR_ID(81, "vapb"), + CLK_MSR_ID(82, "ge2d"), + CLK_MSR_ID(84, "audio_resample"), + CLK_MSR_ID(85, "audio_pdm_sys"), + CLK_MSR_ID(86, "audio_spdifout"), + CLK_MSR_ID(87, "audio_spdifin"), + CLK_MSR_ID(88, "audio_lrclk_f"), + CLK_MSR_ID(89, "audio_lrclk_e"), + CLK_MSR_ID(90, "audio_lrclk_d"), + CLK_MSR_ID(91, "audio_lrclk_c"), + CLK_MSR_ID(92, "audio_lrclk_b"), + CLK_MSR_ID(93, "audio_lrclk_a"), + CLK_MSR_ID(94, "audio_sclk_f"), + CLK_MSR_ID(95, "audio_sclk_e"), + CLK_MSR_ID(96, "audio_sclk_d"), + CLK_MSR_ID(97, "audio_sclk_c"), + CLK_MSR_ID(98, "audio_sclk_b"), + CLK_MSR_ID(99, "audio_sclk_a"), + CLK_MSR_ID(100, "audio_mclk_f"), + CLK_MSR_ID(101, "audio_mclk_e"), + CLK_MSR_ID(102, "audio_mclk_d"), + CLK_MSR_ID(103, "audio_mclk_c"), + CLK_MSR_ID(104, "audio_mclk_b"), + CLK_MSR_ID(105, "audio_mclk_a"), + CLK_MSR_ID(106, "pcie_refclk_n"), + CLK_MSR_ID(107, "pcie_refclk_p"), + CLK_MSR_ID(108, "audio_locker_out"), + CLK_MSR_ID(109, "audio_locker_in"), +}; + +static struct meson_msr_id clk_msr_g12a[CLK_MSR_MAX] = { + CLK_MSR_ID(0, "ring_osc_out_ee_0"), + CLK_MSR_ID(1, "ring_osc_out_ee_1"), + CLK_MSR_ID(2, "ring_osc_out_ee_2"), + CLK_MSR_ID(3, "sys_cpu_ring_osc"), + CLK_MSR_ID(4, "gp0_pll"), + CLK_MSR_ID(6, "enci"), + CLK_MSR_ID(7, "clk81"), + CLK_MSR_ID(8, "encp"), + CLK_MSR_ID(9, "encl"), + CLK_MSR_ID(10, "vdac"), + CLK_MSR_ID(11, "eth_tx"), + CLK_MSR_ID(12, "hifi_pll"), + CLK_MSR_ID(13, "mod_tcon"), + CLK_MSR_ID(14, "fec_0"), + CLK_MSR_ID(15, "fec_1"), + CLK_MSR_ID(16, "fec_2"), + CLK_MSR_ID(17, "sys_pll_div16"), + CLK_MSR_ID(18, "sys_cpu_div16"), + CLK_MSR_ID(19, "lcd_an_ph2"), + CLK_MSR_ID(20, "rtc_osc_out"), + CLK_MSR_ID(21, "lcd_an_ph3"), + CLK_MSR_ID(22, "eth_phy_ref"), + CLK_MSR_ID(23, "mpll_50m"), + CLK_MSR_ID(24, "eth_125m"), + CLK_MSR_ID(25, "eth_rmii"), + CLK_MSR_ID(26, "sc_int"), + CLK_MSR_ID(27, "in_mac"), + CLK_MSR_ID(28, "sar_adc"), + CLK_MSR_ID(29, "pcie_inp"), + CLK_MSR_ID(30, "pcie_inn"), + CLK_MSR_ID(31, "mpll_test_out"), + CLK_MSR_ID(32, "vdec"), + CLK_MSR_ID(33, "sys_cpu_ring_osc_1"), + CLK_MSR_ID(34, "eth_mpll_50m"), + CLK_MSR_ID(35, "mali"), + CLK_MSR_ID(36, "hdmi_tx_pixel"), + CLK_MSR_ID(37, "cdac"), + CLK_MSR_ID(38, "vdin_meas"), + CLK_MSR_ID(39, "bt656"), + CLK_MSR_ID(41, "eth_rx_or_rmii"), + CLK_MSR_ID(42, "mp0_out"), + CLK_MSR_ID(43, "fclk_div5"), + CLK_MSR_ID(44, "pwm_b"), + CLK_MSR_ID(45, "pwm_a"), + CLK_MSR_ID(46, "vpu"), + CLK_MSR_ID(47, "ddr_dpll_pt"), + CLK_MSR_ID(48, "mp1_out"), + CLK_MSR_ID(49, "mp2_out"), + CLK_MSR_ID(50, "mp3_out"), + CLK_MSR_ID(51, "sd_emmc_c"), + CLK_MSR_ID(52, "sd_emmc_b"), + CLK_MSR_ID(53, "sd_emmc_a"), + CLK_MSR_ID(54, "vpu_clkc"), + CLK_MSR_ID(55, "vid_pll_div_out"), + CLK_MSR_ID(56, "wave420l_a"), + CLK_MSR_ID(57, "wave420l_c"), + CLK_MSR_ID(58, "wave420l_b"), + CLK_MSR_ID(59, "hcodec"), + CLK_MSR_ID(61, "gpio_msr"), + CLK_MSR_ID(62, "hevcb"), + CLK_MSR_ID(63, "dsi_meas"), + CLK_MSR_ID(64, "spicc_1"), + CLK_MSR_ID(65, "spicc_0"), + CLK_MSR_ID(66, "vid_lock"), + CLK_MSR_ID(67, "dsi_phy"), + CLK_MSR_ID(68, "hdcp22_esm"), + CLK_MSR_ID(69, "hdcp22_skp"), + CLK_MSR_ID(70, "pwm_f"), + CLK_MSR_ID(71, "pwm_e"), + CLK_MSR_ID(72, "pwm_d"), + CLK_MSR_ID(73, "pwm_c"), + CLK_MSR_ID(75, "hevcf"), + CLK_MSR_ID(77, "rng_ring_osc_0"), + CLK_MSR_ID(78, "rng_ring_osc_1"), + CLK_MSR_ID(79, "rng_ring_osc_2"), + CLK_MSR_ID(80, "rng_ring_osc_3"), + CLK_MSR_ID(81, "vapb"), + CLK_MSR_ID(82, "ge2d"), + CLK_MSR_ID(83, "co_rx"), + CLK_MSR_ID(84, "co_tx"), + CLK_MSR_ID(89, "hdmi_todig"), + CLK_MSR_ID(90, "hdmitx_sys"), + CLK_MSR_ID(94, "eth_phy_rx"), + CLK_MSR_ID(95, "eth_phy_pll"), + CLK_MSR_ID(96, "vpu_b"), + CLK_MSR_ID(97, "cpu_b_tmp"), + CLK_MSR_ID(98, "ts"), + CLK_MSR_ID(99, "ring_osc_out_ee_3"), + CLK_MSR_ID(100, "ring_osc_out_ee_4"), + CLK_MSR_ID(101, "ring_osc_out_ee_5"), + CLK_MSR_ID(102, "ring_osc_out_ee_6"), + CLK_MSR_ID(103, "ring_osc_out_ee_7"), + CLK_MSR_ID(104, "ring_osc_out_ee_8"), + CLK_MSR_ID(105, "ring_osc_out_ee_9"), + CLK_MSR_ID(106, "ephy_test"), + CLK_MSR_ID(107, "au_dac_g128x"), + CLK_MSR_ID(108, "audio_locker_out"), + CLK_MSR_ID(109, "audio_locker_in"), + CLK_MSR_ID(110, "audio_tdmout_c_sclk"), + CLK_MSR_ID(111, "audio_tdmout_b_sclk"), + CLK_MSR_ID(112, "audio_tdmout_a_sclk"), + CLK_MSR_ID(113, "audio_tdmin_lb_sclk"), + CLK_MSR_ID(114, "audio_tdmin_c_sclk"), + CLK_MSR_ID(115, "audio_tdmin_b_sclk"), + CLK_MSR_ID(116, "audio_tdmin_a_sclk"), + CLK_MSR_ID(117, "audio_resample"), + CLK_MSR_ID(118, "audio_pdm_sys"), + CLK_MSR_ID(119, "audio_spdifout_b"), + CLK_MSR_ID(120, "audio_spdifout"), + CLK_MSR_ID(121, "audio_spdifin"), + CLK_MSR_ID(122, "audio_pdm_dclk"), +}; + static int meson_measure_id(struct meson_msr_id *clk_msr_id, unsigned int duration) { @@ -337,6 +525,14 @@ static const struct of_device_id meson_msr_match_table[] = { .compatible = "amlogic,meson8b-clk-measure", .data = (void *)clk_msr_m8, }, + { + .compatible = "amlogic,meson-axg-clk-measure", + .data = (void *)clk_msr_axg, + }, + { + .compatible = "amlogic,meson-g12a-clk-measure", + .data = (void *)clk_msr_g12a, + }, { /* sentinel */ } }; -- cgit From ab272643d723f531b9caa8a4ac5f85fcd26840d2 Mon Sep 17 00:00:00 2001 From: Rajan Vaja Date: Tue, 29 Jan 2019 12:38:21 -0800 Subject: drivers: soc: xilinx: Add ZynqMP PM driver Add ZynqMP PM driver. PM driver provides power management support for ZynqMP. Signed-off-by: Rajan Vaja Signed-off-by: Jolly Shah Signed-off-by: Michal Simek --- drivers/soc/xilinx/Kconfig | 11 +++ drivers/soc/xilinx/Makefile | 1 + drivers/soc/xilinx/zynqmp_power.c | 178 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 190 insertions(+) create mode 100644 drivers/soc/xilinx/zynqmp_power.c (limited to 'drivers/soc') diff --git a/drivers/soc/xilinx/Kconfig b/drivers/soc/xilinx/Kconfig index 687c8f3cd955..5025e0e4fa36 100644 --- a/drivers/soc/xilinx/Kconfig +++ b/drivers/soc/xilinx/Kconfig @@ -17,4 +17,15 @@ config XILINX_VCU To compile this driver as a module, choose M here: the module will be called xlnx_vcu. +config ZYNQMP_POWER + bool "Enable Xilinx Zynq MPSoC Power Management driver" + depends on PM && ARCH_ZYNQMP + default y + help + Say yes to enable power management support for ZyqnMP SoC. + This driver uses firmware driver as an interface for power + management request to firmware. It registers isr to handle + power management callbacks from firmware. + If in doubt, say N. + endmenu diff --git a/drivers/soc/xilinx/Makefile b/drivers/soc/xilinx/Makefile index dee8fd51e303..428b9dbb1f2c 100644 --- a/drivers/soc/xilinx/Makefile +++ b/drivers/soc/xilinx/Makefile @@ -1,2 +1,3 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_XILINX_VCU) += xlnx_vcu.o +obj-$(CONFIG_ZYNQMP_POWER) += zynqmp_power.o diff --git a/drivers/soc/xilinx/zynqmp_power.c b/drivers/soc/xilinx/zynqmp_power.c new file mode 100644 index 000000000000..771cb59b9d22 --- /dev/null +++ b/drivers/soc/xilinx/zynqmp_power.c @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Xilinx Zynq MPSoC Power Management + * + * Copyright (C) 2014-2018 Xilinx, Inc. + * + * Davorin Mista + * Jolly Shah + * Rajan Vaja + */ + +#include +#include +#include +#include +#include + +#include + +enum pm_suspend_mode { + PM_SUSPEND_MODE_FIRST = 0, + PM_SUSPEND_MODE_STD = PM_SUSPEND_MODE_FIRST, + PM_SUSPEND_MODE_POWER_OFF, +}; + +#define PM_SUSPEND_MODE_FIRST PM_SUSPEND_MODE_STD + +static const char *const suspend_modes[] = { + [PM_SUSPEND_MODE_STD] = "standard", + [PM_SUSPEND_MODE_POWER_OFF] = "power-off", +}; + +static enum pm_suspend_mode suspend_mode = PM_SUSPEND_MODE_STD; + +enum pm_api_cb_id { + PM_INIT_SUSPEND_CB = 30, + PM_ACKNOWLEDGE_CB, + PM_NOTIFY_CB, +}; + +static void zynqmp_pm_get_callback_data(u32 *buf) +{ + zynqmp_pm_invoke_fn(GET_CALLBACK_DATA, 0, 0, 0, 0, buf); +} + +static irqreturn_t zynqmp_pm_isr(int irq, void *data) +{ + u32 payload[CB_PAYLOAD_SIZE]; + + zynqmp_pm_get_callback_data(payload); + + /* First element is callback API ID, others are callback arguments */ + if (payload[0] == PM_INIT_SUSPEND_CB) { + switch (payload[1]) { + case SUSPEND_SYSTEM_SHUTDOWN: + orderly_poweroff(true); + break; + case SUSPEND_POWER_REQUEST: + pm_suspend(PM_SUSPEND_MEM); + break; + default: + pr_err("%s Unsupported InitSuspendCb reason " + "code %d\n", __func__, payload[1]); + } + } + + return IRQ_HANDLED; +} + +static ssize_t suspend_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + char *s = buf; + int md; + + for (md = PM_SUSPEND_MODE_FIRST; md < ARRAY_SIZE(suspend_modes); md++) + if (suspend_modes[md]) { + if (md == suspend_mode) + s += sprintf(s, "[%s] ", suspend_modes[md]); + else + s += sprintf(s, "%s ", suspend_modes[md]); + } + + /* Convert last space to newline */ + if (s != buf) + *(s - 1) = '\n'; + return (s - buf); +} + +static ssize_t suspend_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int md, ret = -EINVAL; + const struct zynqmp_eemi_ops *eemi_ops = zynqmp_pm_get_eemi_ops(); + + if (!eemi_ops || !eemi_ops->set_suspend_mode) + return ret; + + for (md = PM_SUSPEND_MODE_FIRST; md < ARRAY_SIZE(suspend_modes); md++) + if (suspend_modes[md] && + sysfs_streq(suspend_modes[md], buf)) { + ret = 0; + break; + } + + if (!ret && md != suspend_mode) { + ret = eemi_ops->set_suspend_mode(md); + if (likely(!ret)) + suspend_mode = md; + } + + return ret ? ret : count; +} + +static DEVICE_ATTR_RW(suspend_mode); + +static int zynqmp_pm_probe(struct platform_device *pdev) +{ + int ret, irq; + u32 pm_api_version; + + const struct zynqmp_eemi_ops *eemi_ops = zynqmp_pm_get_eemi_ops(); + + if (!eemi_ops || !eemi_ops->get_api_version || !eemi_ops->init_finalize) + return -ENXIO; + + eemi_ops->init_finalize(); + eemi_ops->get_api_version(&pm_api_version); + + /* Check PM API version number */ + if (pm_api_version < ZYNQMP_PM_VERSION) + return -ENODEV; + + irq = platform_get_irq(pdev, 0); + if (irq <= 0) + return -ENXIO; + + ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, zynqmp_pm_isr, + IRQF_NO_SUSPEND | IRQF_ONESHOT, + dev_name(&pdev->dev), &pdev->dev); + if (ret) { + dev_err(&pdev->dev, "devm_request_threaded_irq '%d' failed " + "with %d\n", irq, ret); + return ret; + } + + ret = sysfs_create_file(&pdev->dev.kobj, &dev_attr_suspend_mode.attr); + if (ret) { + dev_err(&pdev->dev, "unable to create sysfs interface\n"); + return ret; + } + + return 0; +} + +static int zynqmp_pm_remove(struct platform_device *pdev) +{ + sysfs_remove_file(&pdev->dev.kobj, &dev_attr_suspend_mode.attr); + + return 0; +} + +static const struct of_device_id pm_of_match[] = { + { .compatible = "xlnx,zynqmp-power", }, + { /* end of table */ }, +}; +MODULE_DEVICE_TABLE(of, pm_of_match); + +static struct platform_driver zynqmp_pm_platform_driver = { + .probe = zynqmp_pm_probe, + .remove = zynqmp_pm_remove, + .driver = { + .name = "zynqmp_power", + .of_match_table = pm_of_match, + }, +}; +module_platform_driver(zynqmp_pm_platform_driver); -- cgit From e23d9c6d0d4912fab12cd2d56070b0a8199772f0 Mon Sep 17 00:00:00 2001 From: Jolly Shah Date: Fri, 1 Feb 2019 14:08:50 -0800 Subject: drivers: soc: xilinx: Add ZynqMP power domain driver The zynqmp-genpd driver communicates the usage requirements for logical power domains / devices to the platform FW. FW is responsible for choosing appropriate power states, taking Linux' usage information into account. Signed-off-by: Rajan Vaja Signed-off-by: Jolly Shah Signed-off-by: Michal Simek --- drivers/soc/xilinx/Kconfig | 9 + drivers/soc/xilinx/Makefile | 1 + drivers/soc/xilinx/zynqmp_pm_domains.c | 321 +++++++++++++++++++++++++++++++++ 3 files changed, 331 insertions(+) create mode 100644 drivers/soc/xilinx/zynqmp_pm_domains.c (limited to 'drivers/soc') diff --git a/drivers/soc/xilinx/Kconfig b/drivers/soc/xilinx/Kconfig index 5025e0e4fa36..01e76b58dd78 100644 --- a/drivers/soc/xilinx/Kconfig +++ b/drivers/soc/xilinx/Kconfig @@ -28,4 +28,13 @@ config ZYNQMP_POWER power management callbacks from firmware. If in doubt, say N. +config ZYNQMP_PM_DOMAINS + bool "Enable Zynq MPSoC generic PM domains" + default y + depends on PM && ARCH_ZYNQMP && ZYNQMP_FIRMWARE + select PM_GENERIC_DOMAINS + help + Say yes to enable device power management through PM domains + If in doubt, say N. + endmenu diff --git a/drivers/soc/xilinx/Makefile b/drivers/soc/xilinx/Makefile index 428b9dbb1f2c..f66bfea5de17 100644 --- a/drivers/soc/xilinx/Makefile +++ b/drivers/soc/xilinx/Makefile @@ -1,3 +1,4 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_XILINX_VCU) += xlnx_vcu.o obj-$(CONFIG_ZYNQMP_POWER) += zynqmp_power.o +obj-$(CONFIG_ZYNQMP_PM_DOMAINS) += zynqmp_pm_domains.o diff --git a/drivers/soc/xilinx/zynqmp_pm_domains.c b/drivers/soc/xilinx/zynqmp_pm_domains.c new file mode 100644 index 000000000000..354d256e6e00 --- /dev/null +++ b/drivers/soc/xilinx/zynqmp_pm_domains.c @@ -0,0 +1,321 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ZynqMP Generic PM domain support + * + * Copyright (C) 2015-2018 Xilinx, Inc. + * + * Davorin Mista + * Jolly Shah + * Rajan Vaja + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#define ZYNQMP_NUM_DOMAINS (100) +/* Flag stating if PM nodes mapped to the PM domain has been requested */ +#define ZYNQMP_PM_DOMAIN_REQUESTED BIT(0) + +/** + * struct zynqmp_pm_domain - Wrapper around struct generic_pm_domain + * @gpd: Generic power domain + * @node_id: PM node ID corresponding to device inside PM domain + * @flags: ZynqMP PM domain flags + */ +struct zynqmp_pm_domain { + struct generic_pm_domain gpd; + u32 node_id; + u8 flags; +}; + +/** + * zynqmp_gpd_is_active_wakeup_path() - Check if device is in wakeup source + * path + * @dev: Device to check for wakeup source path + * @not_used: Data member (not required) + * + * This function is checks device's child hierarchy and checks if any device is + * set as wakeup source. + * + * Return: 1 if device is in wakeup source path else 0 + */ +static int zynqmp_gpd_is_active_wakeup_path(struct device *dev, void *not_used) +{ + int may_wakeup; + + may_wakeup = device_may_wakeup(dev); + if (may_wakeup) + return may_wakeup; + + return device_for_each_child(dev, NULL, + zynqmp_gpd_is_active_wakeup_path); +} + +/** + * zynqmp_gpd_power_on() - Power on PM domain + * @domain: Generic PM domain + * + * This function is called before devices inside a PM domain are resumed, to + * power on PM domain. + * + * Return: 0 on success, error code otherwise + */ +static int zynqmp_gpd_power_on(struct generic_pm_domain *domain) +{ + int ret; + struct zynqmp_pm_domain *pd; + const struct zynqmp_eemi_ops *eemi_ops = zynqmp_pm_get_eemi_ops(); + + if (!eemi_ops || !eemi_ops->set_requirement) + return -ENXIO; + + pd = container_of(domain, struct zynqmp_pm_domain, gpd); + ret = eemi_ops->set_requirement(pd->node_id, + ZYNQMP_PM_CAPABILITY_ACCESS, + ZYNQMP_PM_MAX_QOS, + ZYNQMP_PM_REQUEST_ACK_BLOCKING); + if (ret) { + pr_err("%s() %s set requirement for node %d failed: %d\n", + __func__, domain->name, pd->node_id, ret); + return ret; + } + + pr_debug("%s() Powered on %s domain\n", __func__, domain->name); + return 0; +} + +/** + * zynqmp_gpd_power_off() - Power off PM domain + * @domain: Generic PM domain + * + * This function is called after devices inside a PM domain are suspended, to + * power off PM domain. + * + * Return: 0 on success, error code otherwise + */ +static int zynqmp_gpd_power_off(struct generic_pm_domain *domain) +{ + int ret; + struct pm_domain_data *pdd, *tmp; + struct zynqmp_pm_domain *pd; + u32 capabilities = 0; + bool may_wakeup; + const struct zynqmp_eemi_ops *eemi_ops = zynqmp_pm_get_eemi_ops(); + + if (!eemi_ops || !eemi_ops->set_requirement) + return -ENXIO; + + pd = container_of(domain, struct zynqmp_pm_domain, gpd); + + /* If domain is already released there is nothing to be done */ + if (!(pd->flags & ZYNQMP_PM_DOMAIN_REQUESTED)) { + pr_debug("%s() %s domain is already released\n", + __func__, domain->name); + return 0; + } + + list_for_each_entry_safe(pdd, tmp, &domain->dev_list, list_node) { + /* If device is in wakeup path, set capability to WAKEUP */ + may_wakeup = zynqmp_gpd_is_active_wakeup_path(pdd->dev, NULL); + if (may_wakeup) { + dev_dbg(pdd->dev, "device is in wakeup path in %s\n", + domain->name); + capabilities = ZYNQMP_PM_CAPABILITY_WAKEUP; + break; + } + } + + ret = eemi_ops->set_requirement(pd->node_id, capabilities, 0, + ZYNQMP_PM_REQUEST_ACK_NO); + /** + * If powering down of any node inside this domain fails, + * report and return the error + */ + if (ret) { + pr_err("%s() %s set requirement for node %d failed: %d\n", + __func__, domain->name, pd->node_id, ret); + return ret; + } + + pr_debug("%s() Powered off %s domain\n", __func__, domain->name); + return 0; +} + +/** + * zynqmp_gpd_attach_dev() - Attach device to the PM domain + * @domain: Generic PM domain + * @dev: Device to attach + * + * Return: 0 on success, error code otherwise + */ +static int zynqmp_gpd_attach_dev(struct generic_pm_domain *domain, + struct device *dev) +{ + int ret; + struct zynqmp_pm_domain *pd; + const struct zynqmp_eemi_ops *eemi_ops = zynqmp_pm_get_eemi_ops(); + + if (!eemi_ops || !eemi_ops->request_node) + return -ENXIO; + + pd = container_of(domain, struct zynqmp_pm_domain, gpd); + + /* If this is not the first device to attach there is nothing to do */ + if (domain->device_count) + return 0; + + ret = eemi_ops->request_node(pd->node_id, 0, 0, + ZYNQMP_PM_REQUEST_ACK_BLOCKING); + /* If requesting a node fails print and return the error */ + if (ret) { + pr_err("%s() %s request failed for node %d: %d\n", + __func__, domain->name, pd->node_id, ret); + return ret; + } + + pd->flags |= ZYNQMP_PM_DOMAIN_REQUESTED; + + pr_debug("%s() %s attached to %s domain\n", __func__, + dev_name(dev), domain->name); + return 0; +} + +/** + * zynqmp_gpd_detach_dev() - Detach device from the PM domain + * @domain: Generic PM domain + * @dev: Device to detach + */ +static void zynqmp_gpd_detach_dev(struct generic_pm_domain *domain, + struct device *dev) +{ + int ret; + struct zynqmp_pm_domain *pd; + const struct zynqmp_eemi_ops *eemi_ops = zynqmp_pm_get_eemi_ops(); + + if (!eemi_ops || !eemi_ops->release_node) + return; + + pd = container_of(domain, struct zynqmp_pm_domain, gpd); + + /* If this is not the last device to detach there is nothing to do */ + if (domain->device_count) + return; + + ret = eemi_ops->release_node(pd->node_id); + /* If releasing a node fails print the error and return */ + if (ret) { + pr_err("%s() %s release failed for node %d: %d\n", + __func__, domain->name, pd->node_id, ret); + return; + } + + pd->flags &= ~ZYNQMP_PM_DOMAIN_REQUESTED; + + pr_debug("%s() %s detached from %s domain\n", __func__, + dev_name(dev), domain->name); +} + +static struct generic_pm_domain *zynqmp_gpd_xlate + (struct of_phandle_args *genpdspec, void *data) +{ + struct genpd_onecell_data *genpd_data = data; + unsigned int i, idx = genpdspec->args[0]; + struct zynqmp_pm_domain *pd; + + pd = container_of(genpd_data->domains[0], struct zynqmp_pm_domain, gpd); + + if (genpdspec->args_count != 1) + return ERR_PTR(-EINVAL); + + /* Check for existing pm domains */ + for (i = 0; i < ZYNQMP_NUM_DOMAINS; i++) { + if (pd[i].node_id == idx) + goto done; + } + + /** + * Add index in empty node_id of power domain list as no existing + * power domain found for current index. + */ + for (i = 0; i < ZYNQMP_NUM_DOMAINS; i++) { + if (pd[i].node_id == 0) { + pd[i].node_id = idx; + break; + } + } + +done: + if (!genpd_data->domains[i] || i == ZYNQMP_NUM_DOMAINS) + return ERR_PTR(-ENOENT); + + return genpd_data->domains[i]; +} + +static int zynqmp_gpd_probe(struct platform_device *pdev) +{ + int i; + struct genpd_onecell_data *zynqmp_pd_data; + struct generic_pm_domain **domains; + struct zynqmp_pm_domain *pd; + struct device *dev = &pdev->dev; + + pd = devm_kcalloc(dev, ZYNQMP_NUM_DOMAINS, sizeof(*pd), GFP_KERNEL); + if (!pd) + return -ENOMEM; + + zynqmp_pd_data = devm_kzalloc(dev, sizeof(*zynqmp_pd_data), GFP_KERNEL); + if (!zynqmp_pd_data) + return -ENOMEM; + + zynqmp_pd_data->xlate = zynqmp_gpd_xlate; + + domains = devm_kcalloc(dev, ZYNQMP_NUM_DOMAINS, sizeof(*domains), + GFP_KERNEL); + if (!domains) + return -ENOMEM; + + for (i = 0; i < ZYNQMP_NUM_DOMAINS; i++, pd++) { + pd->node_id = 0; + pd->gpd.name = kasprintf(GFP_KERNEL, "domain%d", i); + pd->gpd.power_off = zynqmp_gpd_power_off; + pd->gpd.power_on = zynqmp_gpd_power_on; + pd->gpd.attach_dev = zynqmp_gpd_attach_dev; + pd->gpd.detach_dev = zynqmp_gpd_detach_dev; + + domains[i] = &pd->gpd; + + /* Mark all PM domains as initially powered off */ + pm_genpd_init(&pd->gpd, NULL, true); + } + + zynqmp_pd_data->domains = domains; + zynqmp_pd_data->num_domains = ZYNQMP_NUM_DOMAINS; + of_genpd_add_provider_onecell(dev->parent->of_node, zynqmp_pd_data); + + return 0; +} + +static int zynqmp_gpd_remove(struct platform_device *pdev) +{ + of_genpd_del_provider(pdev->dev.parent->of_node); + + return 0; +} + +static struct platform_driver zynqmp_power_domain_driver = { + .driver = { + .name = "zynqmp_power_controller", + }, + .probe = zynqmp_gpd_probe, + .remove = zynqmp_gpd_remove, +}; +module_platform_driver(zynqmp_power_domain_driver); + +MODULE_ALIAS("platform:zynqmp_power_controller"); -- cgit From 72d1cd033154f50e77cd4feb4e16c227b598632e Mon Sep 17 00:00:00 2001 From: Jordan Crouse Date: Tue, 11 Dec 2018 13:07:45 -0700 Subject: qcom: soc: llcc-slice: Clear the global drv_data pointer on error Currently the data structure for llc-slice is devm allocated and stored as a global but never cleared if the probe function fails. This is a problem because devm managed memory gets freed on probe failure the API functions could access the pointer after it has been freed. Initialize the drv_data pointer to an error and reset it to an error on probe failure or device destroy and add protection to the API functions to make sure the memory doesn't get accessed. Signed-off-by: Jordan Crouse Signed-off-by: Andy Gross --- drivers/soc/qcom/llcc-sdm845.c | 6 ++++ drivers/soc/qcom/llcc-slice.c | 71 ++++++++++++++++++++++++++++++++---------- 2 files changed, 60 insertions(+), 17 deletions(-) (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/llcc-sdm845.c b/drivers/soc/qcom/llcc-sdm845.c index 2e1e4f0a5db8..86600d97c36d 100644 --- a/drivers/soc/qcom/llcc-sdm845.c +++ b/drivers/soc/qcom/llcc-sdm845.c @@ -71,6 +71,11 @@ static struct llcc_slice_config sdm845_data[] = { SCT_ENTRY(LLCC_AUDHW, 22, 1024, 1, 1, 0xffc, 0x2, 0, 0, 1, 1, 0), }; +static int sdm845_qcom_llcc_remove(struct platform_device *pdev) +{ + return qcom_llcc_remove(pdev); +} + static int sdm845_qcom_llcc_probe(struct platform_device *pdev) { return qcom_llcc_probe(pdev, sdm845_data, ARRAY_SIZE(sdm845_data)); @@ -87,6 +92,7 @@ static struct platform_driver sdm845_qcom_llcc_driver = { .of_match_table = sdm845_qcom_llcc_of_match, }, .probe = sdm845_qcom_llcc_probe, + .remove = sdm845_qcom_llcc_remove, }; module_platform_driver(sdm845_qcom_llcc_driver); diff --git a/drivers/soc/qcom/llcc-slice.c b/drivers/soc/qcom/llcc-slice.c index 80667f7be52c..8390bc006a31 100644 --- a/drivers/soc/qcom/llcc-slice.c +++ b/drivers/soc/qcom/llcc-slice.c @@ -46,7 +46,7 @@ #define BANK_OFFSET_STRIDE 0x80000 -static struct llcc_drv_data *drv_data; +static struct llcc_drv_data *drv_data = (void *) -EPROBE_DEFER; static const struct regmap_config llcc_regmap_config = { .reg_bits = 32, @@ -68,6 +68,9 @@ struct llcc_slice_desc *llcc_slice_getd(u32 uid) struct llcc_slice_desc *desc; u32 sz, count; + if (IS_ERR(drv_data)) + return ERR_CAST(drv_data); + cfg = drv_data->cfg; sz = drv_data->cfg_size; @@ -108,6 +111,9 @@ static int llcc_update_act_ctrl(u32 sid, u32 slice_status; int ret; + if (IS_ERR(drv_data)) + return PTR_ERR(drv_data); + act_ctrl_reg = LLCC_TRP_ACT_CTRLn(sid); status_reg = LLCC_TRP_STATUSn(sid); @@ -143,6 +149,9 @@ int llcc_slice_activate(struct llcc_slice_desc *desc) int ret; u32 act_ctrl_val; + If (IS_ERR(drv_data)) + return PTR_ERR(drv_data); + if (IS_ERR_OR_NULL(desc)) return -EINVAL; @@ -180,6 +189,9 @@ int llcc_slice_deactivate(struct llcc_slice_desc *desc) u32 act_ctrl_val; int ret; + If (IS_ERR(drv_data)) + return PTR_ERR(drv_data); + if (IS_ERR_OR_NULL(desc)) return -EINVAL; @@ -289,6 +301,14 @@ static int qcom_llcc_cfg_program(struct platform_device *pdev) return ret; } +int qcom_llcc_remove(struct platform_device *pdev) +{ + /* Set the global pointer to a error code to avoid referencing it */ + drv_data = ERR_PTR(-ENODEV); + return 0; +} +EXPORT_SYMBOL_GPL(qcom_llcc_remove); + int qcom_llcc_probe(struct platform_device *pdev, const struct llcc_slice_config *llcc_cfg, u32 sz) { @@ -300,35 +320,45 @@ int qcom_llcc_probe(struct platform_device *pdev, struct platform_device *llcc_edac; drv_data = devm_kzalloc(dev, sizeof(*drv_data), GFP_KERNEL); - if (!drv_data) - return -ENOMEM; + if (!drv_data) { + ret = -ENOMEM; + goto err; + } llcc_banks_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "llcc_base"); llcc_banks_base = devm_ioremap_resource(&pdev->dev, llcc_banks_res); - if (IS_ERR(llcc_banks_base)) - return PTR_ERR(llcc_banks_base); + if (IS_ERR(llcc_banks_base)) { + ret = PTR_ERR(llcc_banks_base); + goto err; + } drv_data->regmap = devm_regmap_init_mmio(dev, llcc_banks_base, &llcc_regmap_config); - if (IS_ERR(drv_data->regmap)) - return PTR_ERR(drv_data->regmap); + if (IS_ERR(drv_data->regmap)) { + ret = PTR_ERR(drv_data->regmap); + goto err; + } llcc_bcast_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "llcc_broadcast_base"); llcc_bcast_base = devm_ioremap_resource(&pdev->dev, llcc_bcast_res); - if (IS_ERR(llcc_bcast_base)) - return PTR_ERR(llcc_bcast_base); + if (IS_ERR(llcc_bcast_base)) { + ret = PTR_ERR(llcc_bcast_base); + goto err; + } drv_data->bcast_regmap = devm_regmap_init_mmio(dev, llcc_bcast_base, &llcc_regmap_config); - if (IS_ERR(drv_data->bcast_regmap)) - return PTR_ERR(drv_data->bcast_regmap); + if (IS_ERR(drv_data->bcast_regmap)) { + ret = PTR_ERR(drv_data->bcast_regmap); + goto err; + } ret = regmap_read(drv_data->regmap, LLCC_COMMON_STATUS0, &num_banks); if (ret) - return ret; + goto err; num_banks &= LLCC_LB_CNT_MASK; num_banks >>= LLCC_LB_CNT_SHIFT; @@ -340,8 +370,10 @@ int qcom_llcc_probe(struct platform_device *pdev, drv_data->offsets = devm_kcalloc(dev, num_banks, sizeof(u32), GFP_KERNEL); - if (!drv_data->offsets) - return -ENOMEM; + if (!drv_data->offsets) { + ret = -ENOMEM; + goto err; + } for (i = 0; i < num_banks; i++) drv_data->offsets[i] = i * BANK_OFFSET_STRIDE; @@ -349,8 +381,10 @@ int qcom_llcc_probe(struct platform_device *pdev, drv_data->bitmap = devm_kcalloc(dev, BITS_TO_LONGS(drv_data->max_slices), sizeof(unsigned long), GFP_KERNEL); - if (!drv_data->bitmap) - return -ENOMEM; + if (!drv_data->bitmap) { + ret = -ENOMEM; + goto err; + } drv_data->cfg = llcc_cfg; drv_data->cfg_size = sz; @@ -359,7 +393,7 @@ int qcom_llcc_probe(struct platform_device *pdev, ret = qcom_llcc_cfg_program(pdev); if (ret) - return ret; + goto err; drv_data->ecc_irq = platform_get_irq(pdev, 0); if (drv_data->ecc_irq >= 0) { @@ -370,6 +404,9 @@ int qcom_llcc_probe(struct platform_device *pdev, dev_err(dev, "Failed to register llcc edac driver\n"); } + return 0; +err: + drv_data = ERR_PTR(-ENODEV); return ret; } EXPORT_SYMBOL_GPL(qcom_llcc_probe); -- cgit From ed10a259faa1bbdf77b8c2153b274b70c90cbb35 Mon Sep 17 00:00:00 2001 From: Jordan Crouse Date: Tue, 11 Dec 2018 13:07:46 -0700 Subject: qcom: soc: llcc-slice: Consolidate some code Make the code a little bit clearer (and use less gotos) by consolidating some of the initialization. Signed-off-by: Jordan Crouse Reviewed-by: Douglas Anderson Signed-off-by: Andy Gross --- drivers/soc/qcom/llcc-slice.c | 42 ++++++++++++++++++++---------------------- 1 file changed, 20 insertions(+), 22 deletions(-) (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/llcc-slice.c b/drivers/soc/qcom/llcc-slice.c index 8390bc006a31..83a263926a25 100644 --- a/drivers/soc/qcom/llcc-slice.c +++ b/drivers/soc/qcom/llcc-slice.c @@ -309,13 +309,28 @@ int qcom_llcc_remove(struct platform_device *pdev) } EXPORT_SYMBOL_GPL(qcom_llcc_remove); +static struct regmap *qcom_llcc_init_mmio(struct platform_device *pdev, + const char *name) +{ + struct resource *res; + void __iomem *base; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, name); + if (!res) + return ERR_PTR(-ENODEV); + + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return ERR_CAST(base); + + return devm_regmap_init_mmio(&pdev->dev, base, &llcc_regmap_config); +} + int qcom_llcc_probe(struct platform_device *pdev, const struct llcc_slice_config *llcc_cfg, u32 sz) { u32 num_banks; struct device *dev = &pdev->dev; - struct resource *llcc_banks_res, *llcc_bcast_res; - void __iomem *llcc_banks_base, *llcc_bcast_base; int ret, i; struct platform_device *llcc_edac; @@ -325,31 +340,14 @@ int qcom_llcc_probe(struct platform_device *pdev, goto err; } - llcc_banks_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, - "llcc_base"); - llcc_banks_base = devm_ioremap_resource(&pdev->dev, llcc_banks_res); - if (IS_ERR(llcc_banks_base)) { - ret = PTR_ERR(llcc_banks_base); - goto err; - } - - drv_data->regmap = devm_regmap_init_mmio(dev, llcc_banks_base, - &llcc_regmap_config); + drv_data->regmap = qcom_llcc_init_mmio(pdev, "llcc_base"); if (IS_ERR(drv_data->regmap)) { ret = PTR_ERR(drv_data->regmap); goto err; } - llcc_bcast_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, - "llcc_broadcast_base"); - llcc_bcast_base = devm_ioremap_resource(&pdev->dev, llcc_bcast_res); - if (IS_ERR(llcc_bcast_base)) { - ret = PTR_ERR(llcc_bcast_base); - goto err; - } - - drv_data->bcast_regmap = devm_regmap_init_mmio(dev, llcc_bcast_base, - &llcc_regmap_config); + drv_data->bcast_regmap = + qcom_llcc_init_mmio(pdev, "llcc_broadcast_base"); if (IS_ERR(drv_data->bcast_regmap)) { ret = PTR_ERR(drv_data->bcast_regmap); goto err; -- cgit From 32616b2121f75f40f66eefa04cc49052200c4181 Mon Sep 17 00:00:00 2001 From: Andy Gross Date: Fri, 15 Feb 2019 16:30:34 -0600 Subject: soc: qcom: llcc-slice: Fix typos This patch fixes typos in the llcc-slice driver. Fixes: 72d1cd033154 ("qcom: soc: llcc-slice: Clear the global drv_data pointer on error") Signed-off-by: Andy Gross --- drivers/soc/qcom/llcc-slice.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/llcc-slice.c b/drivers/soc/qcom/llcc-slice.c index 83a263926a25..9090ea12eaf3 100644 --- a/drivers/soc/qcom/llcc-slice.c +++ b/drivers/soc/qcom/llcc-slice.c @@ -149,7 +149,7 @@ int llcc_slice_activate(struct llcc_slice_desc *desc) int ret; u32 act_ctrl_val; - If (IS_ERR(drv_data)) + if (IS_ERR(drv_data)) return PTR_ERR(drv_data); if (IS_ERR_OR_NULL(desc)) @@ -189,7 +189,7 @@ int llcc_slice_deactivate(struct llcc_slice_desc *desc) u32 act_ctrl_val; int ret; - If (IS_ERR(drv_data)) + if (IS_ERR(drv_data)) return PTR_ERR(drv_data); if (IS_ERR_OR_NULL(desc)) -- cgit From bd3bd3b36df725645036748e58a8c35c8d2cbf91 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Tue, 19 Feb 2019 14:05:17 +0000 Subject: soc: fsl: dpio: fix memory leak of a struct qbman on error exit path Currently the error check for a null reg leaks a struct qbman that was allocated earlier. Fix this by kfree'ing p on the error exit path. Signed-off-by: Colin Ian King Signed-off-by: Li Yang --- drivers/soc/fsl/dpio/qbman-portal.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/soc') diff --git a/drivers/soc/fsl/dpio/qbman-portal.c b/drivers/soc/fsl/dpio/qbman-portal.c index 0bddb85c0ae5..5a73397ae79e 100644 --- a/drivers/soc/fsl/dpio/qbman-portal.c +++ b/drivers/soc/fsl/dpio/qbman-portal.c @@ -180,6 +180,7 @@ struct qbman_swp *qbman_swp_init(const struct qbman_swp_desc *d) reg = qbman_read_register(p, QBMAN_CINH_SWP_CFG); if (!reg) { pr_err("qbman: the portal is not enabled!\n"); + kfree(p); return NULL; } -- cgit From ae23a0fe58887a1c0518062b49bf8ac30209c26c Mon Sep 17 00:00:00 2001 From: Horia Geantă Date: Thu, 21 Feb 2019 12:37:31 +0200 Subject: soc: fsl: guts: make fsl_guts_get_svr() static MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The export of fsl_guts_get_svr() is a left-over, it's currently used only internally and users needing SoC information should use the generic soc_device infrastructure. Signed-off-by: Horia Geantă Acked-by: Yangbo Lu Signed-off-by: Li Yang --- drivers/soc/fsl/guts.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'drivers/soc') diff --git a/drivers/soc/fsl/guts.c b/drivers/soc/fsl/guts.c index 4f9655087bd7..63f6df86f9e5 100644 --- a/drivers/soc/fsl/guts.c +++ b/drivers/soc/fsl/guts.c @@ -115,7 +115,7 @@ static const struct fsl_soc_die_attr *fsl_soc_die_match( return NULL; } -u32 fsl_guts_get_svr(void) +static u32 fsl_guts_get_svr(void) { u32 svr = 0; @@ -129,7 +129,6 @@ u32 fsl_guts_get_svr(void) return svr; } -EXPORT_SYMBOL(fsl_guts_get_svr); static int fsl_guts_probe(struct platform_device *pdev) { -- cgit From 390bf02d275cee595d551fc041bb62d71728cbfa Mon Sep 17 00:00:00 2001 From: Ioana Ciornei Date: Sat, 23 Feb 2019 08:48:48 +0000 Subject: soc: fsl: dpio: enable frame data cache stashing per software portal Enable cache stashing on the frame data dequeued using this software portal. Also, enable dropping a stash request transaction when the target request queue is almost full. Signed-off-by: Ioana Ciornei Signed-off-by: Li Yang --- drivers/soc/fsl/dpio/qbman-portal.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/soc') diff --git a/drivers/soc/fsl/dpio/qbman-portal.c b/drivers/soc/fsl/dpio/qbman-portal.c index 5a73397ae79e..d02013556a1b 100644 --- a/drivers/soc/fsl/dpio/qbman-portal.c +++ b/drivers/soc/fsl/dpio/qbman-portal.c @@ -169,9 +169,9 @@ struct qbman_swp *qbman_swp_init(const struct qbman_swp_desc *d) 3, /* RPM: Valid bit mode, RCR in array mode */ 2, /* DCM: Discrete consumption ack mode */ 3, /* EPM: Valid bit mode, EQCR in array mode */ - 0, /* mem stashing drop enable == FALSE */ + 1, /* mem stashing drop enable == TRUE */ 1, /* mem stashing priority == TRUE */ - 0, /* mem stashing enable == FALSE */ + 1, /* mem stashing enable == TRUE */ 1, /* dequeue stashing priority == TRUE */ 0, /* dequeue stashing enable == FALSE */ 0); /* EQCR_CI stashing priority == FALSE */ -- cgit From 51da14e96e9b5187bd42148e37628e59486e267f Mon Sep 17 00:00:00 2001 From: Ioana Ciornei Date: Sat, 23 Feb 2019 08:48:52 +0000 Subject: soc: fsl: dpio: configure cache stashing destination Depending on the SoC version and the CPU id, configure the cache stashing destination for a specific dpio. Signed-off-by: Ioana Ciornei Signed-off-by: Li Yang --- drivers/soc/fsl/Kconfig | 1 + drivers/soc/fsl/dpio/dpio-cmd.h | 5 ++++ drivers/soc/fsl/dpio/dpio-driver.c | 52 ++++++++++++++++++++++++++++++++++++++ drivers/soc/fsl/dpio/dpio.c | 16 ++++++++++++ drivers/soc/fsl/dpio/dpio.h | 5 ++++ 5 files changed, 79 insertions(+) (limited to 'drivers/soc') diff --git a/drivers/soc/fsl/Kconfig b/drivers/soc/fsl/Kconfig index 8f80e8bbf29e..61f8e1433d0a 100644 --- a/drivers/soc/fsl/Kconfig +++ b/drivers/soc/fsl/Kconfig @@ -22,6 +22,7 @@ config FSL_GUTS config FSL_MC_DPIO tristate "QorIQ DPAA2 DPIO driver" depends on FSL_MC_BUS + select SOC_BUS help Driver for the DPAA2 DPIO object. A DPIO provides queue and buffer management facilities for software to interact with diff --git a/drivers/soc/fsl/dpio/dpio-cmd.h b/drivers/soc/fsl/dpio/dpio-cmd.h index 5814d2f395a4..e13fd3ac1939 100644 --- a/drivers/soc/fsl/dpio/dpio-cmd.h +++ b/drivers/soc/fsl/dpio/dpio-cmd.h @@ -26,6 +26,7 @@ #define DPIO_CMDID_DISABLE DPIO_CMD(0x003) #define DPIO_CMDID_GET_ATTR DPIO_CMD(0x004) #define DPIO_CMDID_RESET DPIO_CMD(0x005) +#define DPIO_CMDID_SET_STASHING_DEST DPIO_CMD(0x120) struct dpio_cmd_open { __le32 dpio_id; @@ -47,4 +48,8 @@ struct dpio_rsp_get_attr { __le32 qbman_version; }; +struct dpio_stashing_dest { + u8 sdest; +}; + #endif /* _FSL_DPIO_CMD_H */ diff --git a/drivers/soc/fsl/dpio/dpio-driver.c b/drivers/soc/fsl/dpio/dpio-driver.c index a28799b62d53..c0cdc8946031 100644 --- a/drivers/soc/fsl/dpio/dpio-driver.c +++ b/drivers/soc/fsl/dpio/dpio-driver.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -32,6 +33,46 @@ struct dpio_priv { static cpumask_var_t cpus_unused_mask; +static const struct soc_device_attribute ls1088a_soc[] = { + {.family = "QorIQ LS1088A"}, + { /* sentinel */ } +}; + +static const struct soc_device_attribute ls2080a_soc[] = { + {.family = "QorIQ LS2080A"}, + { /* sentinel */ } +}; + +static const struct soc_device_attribute ls2088a_soc[] = { + {.family = "QorIQ LS2088A"}, + { /* sentinel */ } +}; + +static const struct soc_device_attribute lx2160a_soc[] = { + {.family = "QorIQ LX2160A"}, + { /* sentinel */ } +}; + +static int dpaa2_dpio_get_cluster_sdest(struct fsl_mc_device *dpio_dev, int cpu) +{ + int cluster_base, cluster_size; + + if (soc_device_match(ls1088a_soc)) { + cluster_base = 2; + cluster_size = 4; + } else if (soc_device_match(ls2080a_soc) || + soc_device_match(ls2088a_soc) || + soc_device_match(lx2160a_soc)) { + cluster_base = 0; + cluster_size = 2; + } else { + dev_err(&dpio_dev->dev, "unknown SoC version\n"); + return -1; + } + + return cluster_base + cpu / cluster_size; +} + static irqreturn_t dpio_irq_handler(int irq_num, void *arg) { struct device *dev = (struct device *)arg; @@ -89,6 +130,7 @@ static int dpaa2_dpio_probe(struct fsl_mc_device *dpio_dev) int err = -ENOMEM; struct device *dev = &dpio_dev->dev; int possible_next_cpu; + int sdest; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) @@ -145,6 +187,16 @@ static int dpaa2_dpio_probe(struct fsl_mc_device *dpio_dev) desc.cpu = possible_next_cpu; cpumask_clear_cpu(possible_next_cpu, cpus_unused_mask); + sdest = dpaa2_dpio_get_cluster_sdest(dpio_dev, desc.cpu); + if (sdest >= 0) { + err = dpio_set_stashing_destination(dpio_dev->mc_io, 0, + dpio_dev->mc_handle, + sdest); + if (err) + dev_err(dev, "dpio_set_stashing_destination failed for cpu%d\n", + desc.cpu); + } + /* * Set the CENA regs to be the cache inhibited area of the portal to * avoid coherency issues if a user migrates to another core. diff --git a/drivers/soc/fsl/dpio/dpio.c b/drivers/soc/fsl/dpio/dpio.c index 521bc6946317..af74c597a675 100644 --- a/drivers/soc/fsl/dpio/dpio.c +++ b/drivers/soc/fsl/dpio/dpio.c @@ -166,6 +166,22 @@ int dpio_get_attributes(struct fsl_mc_io *mc_io, return 0; } +int dpio_set_stashing_destination(struct fsl_mc_io *mc_io, + u32 cmd_flags, + u16 token, + u8 sdest) +{ + struct fsl_mc_command cmd = { 0 }; + struct dpio_stashing_dest *dpio_cmd; + + cmd.header = mc_encode_cmd_header(DPIO_CMDID_SET_STASHING_DEST, + cmd_flags, token); + dpio_cmd = (struct dpio_stashing_dest *)cmd.params; + dpio_cmd->sdest = sdest; + + return mc_send_command(mc_io, &cmd); +} + /** * dpio_get_api_version - Get Data Path I/O API version * @mc_io: Pointer to MC portal's DPIO object diff --git a/drivers/soc/fsl/dpio/dpio.h b/drivers/soc/fsl/dpio/dpio.h index b2ac4ba4fb8e..da06f7258098 100644 --- a/drivers/soc/fsl/dpio/dpio.h +++ b/drivers/soc/fsl/dpio/dpio.h @@ -75,6 +75,11 @@ int dpio_get_attributes(struct fsl_mc_io *mc_io, u16 token, struct dpio_attr *attr); +int dpio_set_stashing_destination(struct fsl_mc_io *mc_io, + u32 cmd_flags, + u16 token, + u8 dest); + int dpio_get_api_version(struct fsl_mc_io *mc_io, u32 cmd_flags, u16 *major_ver, -- cgit