From c6275e4b12bfb53097bca852dd49c15b1af50cb4 Mon Sep 17 00:00:00 2001 From: Stefan Wahren Date: Fri, 7 Dec 2018 19:21:10 +0100 Subject: [PATCH 001/113] soc: bcm2835: sync firmware properties with downstream Add latest firmware property tags from the latest Raspberry Pi downstream kernel. This is needed to use the reboot notify in the following commit. Signed-off-by: Stefan Wahren Reviewed-by: Eric Anholt --- include/soc/bcm2835/raspberrypi-firmware.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/soc/bcm2835/raspberrypi-firmware.h b/include/soc/bcm2835/raspberrypi-firmware.h index 4be1aa4435ae..7800e12ee042 100644 --- a/include/soc/bcm2835/raspberrypi-firmware.h +++ b/include/soc/bcm2835/raspberrypi-firmware.h @@ -73,6 +73,8 @@ enum rpi_firmware_property_tag { RPI_FIRMWARE_GET_CUSTOMER_OTP = 0x00030021, RPI_FIRMWARE_GET_DOMAIN_STATE = 0x00030030, RPI_FIRMWARE_GET_THROTTLED = 0x00030046, + RPI_FIRMWARE_GET_CLOCK_MEASURED = 0x00030047, + RPI_FIRMWARE_NOTIFY_REBOOT = 0x00030048, RPI_FIRMWARE_SET_CLOCK_STATE = 0x00038001, RPI_FIRMWARE_SET_CLOCK_RATE = 0x00038002, RPI_FIRMWARE_SET_VOLTAGE = 0x00038003, @@ -86,6 +88,8 @@ enum rpi_firmware_property_tag { RPI_FIRMWARE_SET_GPIO_CONFIG = 0x00038043, RPI_FIRMWARE_GET_PERIPH_REG = 0x00030045, RPI_FIRMWARE_SET_PERIPH_REG = 0x00038045, + RPI_FIRMWARE_GET_POE_HAT_VAL = 0x00030049, + RPI_FIRMWARE_SET_POE_HAT_VAL = 0x00030050, /* Dispmanx TAGS */ From b80ec7c0ef8de5d04b893310ecae29219143a454 Mon Sep 17 00:00:00 2001 From: Stefan Wahren Date: Fri, 7 Dec 2018 19:21:11 +0100 Subject: [PATCH 002/113] firmware: raspberrypi: notify VC4 firmware of a reboot The firmware-owned GPIO expander on RPi 3 B+ must be in same state after a reboot as initial power on. Otherwise this would cause a network boot failure of the BOOTROM. So inform the VC4 firmware to restore the expander before doing a reboot. Signed-off-by: Stefan Wahren Tested-by: Phil Elwell Reviewed-by: Eric Anholt --- drivers/firmware/raspberrypi.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/firmware/raspberrypi.c b/drivers/firmware/raspberrypi.c index a13558154ac3..61be15d9df7d 100644 --- a/drivers/firmware/raspberrypi.c +++ b/drivers/firmware/raspberrypi.c @@ -238,6 +238,16 @@ static int rpi_firmware_probe(struct platform_device *pdev) return 0; } +static void rpi_firmware_shutdown(struct platform_device *pdev) +{ + struct rpi_firmware *fw = platform_get_drvdata(pdev); + + if (!fw) + return; + + rpi_firmware_property(fw, RPI_FIRMWARE_NOTIFY_REBOOT, NULL, 0); +} + static int rpi_firmware_remove(struct platform_device *pdev) { struct rpi_firmware *fw = platform_get_drvdata(pdev); @@ -278,6 +288,7 @@ static struct platform_driver rpi_firmware_driver = { .of_match_table = rpi_firmware_of_match, }, .probe = rpi_firmware_probe, + .shutdown = rpi_firmware_shutdown, .remove = rpi_firmware_remove, }; module_platform_driver(rpi_firmware_driver); From fbeab182b1ae41c3e2e9c05808c01e8f65883e61 Mon Sep 17 00:00:00 2001 From: Eric Anholt Date: Wed, 12 Dec 2018 15:51:46 -0800 Subject: [PATCH 003/113] dt-bindings: soc: Add a new binding for the BCM2835 PM node. (v4) This binding supersedes the bcm2835-pm-wdt binding which only covered enough to provide a watchdog, but the HW block is actually mostly about power domains. Signed-off-by: Eric Anholt Reviewed-by: Rob Herring (v3) Acked-by: Stefan Wahren Signed-off-by: Stefan Wahren --- .../bindings/soc/bcm/brcm,bcm2835-pm.txt | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 Documentation/devicetree/bindings/soc/bcm/brcm,bcm2835-pm.txt diff --git a/Documentation/devicetree/bindings/soc/bcm/brcm,bcm2835-pm.txt b/Documentation/devicetree/bindings/soc/bcm/brcm,bcm2835-pm.txt new file mode 100644 index 000000000000..3b7d32956391 --- /dev/null +++ b/Documentation/devicetree/bindings/soc/bcm/brcm,bcm2835-pm.txt @@ -0,0 +1,46 @@ +BCM2835 PM (Power domains, watchdog) + +The PM block controls power domains and some reset lines, and includes +a watchdog timer. This binding supersedes the brcm,bcm2835-pm-wdt +binding which covered some of PM's register range and functionality. + +Required properties: + +- compatible: Should be "brcm,bcm2835-pm" +- reg: Specifies base physical address and size of the two + register ranges ("PM" and "ASYNC_BRIDGE" in that + order) +- clocks: a) v3d: The V3D clock from CPRMAN + b) peri_image: The PERI_IMAGE clock from CPRMAN + c) h264: The H264 clock from CPRMAN + d) isp: The ISP clock from CPRMAN +- #reset-cells: Should be 1. This property follows the reset controller + bindings[1]. +- #power-domain-cells: Should be 1. This property follows the power domain + bindings[2]. + +Optional properties: + +- timeout-sec: Contains the watchdog timeout in seconds +- system-power-controller: Whether the watchdog is controlling the + system power. This node follows the power controller bindings[3]. + +[1] Documentation/devicetree/bindings/reset/reset.txt +[2] Documentation/devicetree/bindings/power/power_domain.txt +[3] Documentation/devicetree/bindings/power/power-controller.txt + +Example: + +pm { + compatible = "brcm,bcm2835-pm", "brcm,bcm2835-pm-wdt"; + #power-domain-cells = <1>; + #reset-cells = <1>; + reg = <0x7e100000 0x114>, + <0x7e00a000 0x24>; + clocks = <&clocks BCM2835_CLOCK_V3D>, + <&clocks BCM2835_CLOCK_PERI_IMAGE>, + <&clocks BCM2835_CLOCK_H264>, + <&clocks BCM2835_CLOCK_ISP>; + clock-names = "v3d", "peri_image", "h264", "isp"; + system-power-controller; +}; From 5e6acc3e678ed3db746ab4fb53a980861cd711b6 Mon Sep 17 00:00:00 2001 From: Eric Anholt Date: Wed, 12 Dec 2018 15:51:47 -0800 Subject: [PATCH 004/113] bcm2835-pm: Move bcm2835-watchdog's DT probe to an MFD. The PM block that the wdt driver was binding to actually has multiple features we want to expose (power domains, reset, watchdog). Move the DT attachment to a MFD driver and make WDT probe against MFD. Signed-off-by: Eric Anholt Reviewed-by: Guenter Roeck Acked-by: Stefan Wahren Signed-off-by: Stefan Wahren --- arch/arm/mach-bcm/Kconfig | 1 + drivers/mfd/Makefile | 1 + drivers/mfd/bcm2835-pm.c | 64 ++++++++++++++++++++++++++++++++++ drivers/watchdog/bcm2835_wdt.c | 26 +++++--------- include/linux/mfd/bcm2835-pm.h | 13 +++++++ 5 files changed, 88 insertions(+), 17 deletions(-) create mode 100644 drivers/mfd/bcm2835-pm.c create mode 100644 include/linux/mfd/bcm2835-pm.h diff --git a/arch/arm/mach-bcm/Kconfig b/arch/arm/mach-bcm/Kconfig index a067adf9f1ee..4ef1e55f4a0b 100644 --- a/arch/arm/mach-bcm/Kconfig +++ b/arch/arm/mach-bcm/Kconfig @@ -167,6 +167,7 @@ config ARCH_BCM2835 select BCM2835_TIMER select PINCTRL select PINCTRL_BCM2835 + select MFD_CORE help This enables support for the Broadcom BCM2835 and BCM2836 SoCs. This SoC is used in the Raspberry Pi and Roku 2 devices. diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 12980a4ad460..ee6fb6af655e 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -10,6 +10,7 @@ obj-$(CONFIG_MFD_88PM805) += 88pm805.o 88pm80x.o obj-$(CONFIG_MFD_ACT8945A) += act8945a.o obj-$(CONFIG_MFD_SM501) += sm501.o obj-$(CONFIG_MFD_ASIC3) += asic3.o tmio_core.o +obj-$(CONFIG_ARCH_BCM2835) += bcm2835-pm.o obj-$(CONFIG_MFD_BCM590XX) += bcm590xx.o obj-$(CONFIG_MFD_BD9571MWV) += bd9571mwv.o cros_ec_core-objs := cros_ec.o diff --git a/drivers/mfd/bcm2835-pm.c b/drivers/mfd/bcm2835-pm.c new file mode 100644 index 000000000000..53839e6a81e7 --- /dev/null +++ b/drivers/mfd/bcm2835-pm.c @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * PM MFD driver for Broadcom BCM2835 + * + * This driver binds to the PM block and creates the MFD device for + * the WDT driver. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const struct mfd_cell bcm2835_pm_devs[] = { + { .name = "bcm2835-wdt" }, +}; + +static int bcm2835_pm_probe(struct platform_device *pdev) +{ + struct resource *res; + struct device *dev = &pdev->dev; + struct bcm2835_pm *pm; + + pm = devm_kzalloc(dev, sizeof(*pm), GFP_KERNEL); + if (!pm) + return -ENOMEM; + platform_set_drvdata(pdev, pm); + + pm->dev = dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + pm->base = devm_ioremap_resource(dev, res); + if (IS_ERR(pm->base)) + return PTR_ERR(pm->base); + + return devm_mfd_add_devices(dev, -1, + bcm2835_pm_devs, ARRAY_SIZE(bcm2835_pm_devs), + NULL, 0, NULL); +} + +static const struct of_device_id bcm2835_pm_of_match[] = { + { .compatible = "brcm,bcm2835-pm-wdt", }, + {}, +}; +MODULE_DEVICE_TABLE(of, bcm2835_pm_of_match); + +static struct platform_driver bcm2835_pm_driver = { + .probe = bcm2835_pm_probe, + .driver = { + .name = "bcm2835-pm", + .of_match_table = bcm2835_pm_of_match, + }, +}; +module_platform_driver(bcm2835_pm_driver); + +MODULE_AUTHOR("Eric Anholt "); +MODULE_DESCRIPTION("Driver for Broadcom BCM2835 PM MFD"); +MODULE_LICENSE("GPL"); diff --git a/drivers/watchdog/bcm2835_wdt.c b/drivers/watchdog/bcm2835_wdt.c index ed05514cc2dc..1834524ae373 100644 --- a/drivers/watchdog/bcm2835_wdt.c +++ b/drivers/watchdog/bcm2835_wdt.c @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -47,6 +48,8 @@ struct bcm2835_wdt { spinlock_t lock; }; +static struct bcm2835_wdt *bcm2835_power_off_wdt; + static unsigned int heartbeat; static bool nowayout = WATCHDOG_NOWAYOUT; @@ -148,10 +151,7 @@ static struct watchdog_device bcm2835_wdt_wdd = { */ static void bcm2835_power_off(void) { - struct device_node *np = - of_find_compatible_node(NULL, NULL, "brcm,bcm2835-pm-wdt"); - struct platform_device *pdev = of_find_device_by_node(np); - struct bcm2835_wdt *wdt = platform_get_drvdata(pdev); + struct bcm2835_wdt *wdt = bcm2835_power_off_wdt; u32 val; /* @@ -169,7 +169,7 @@ static void bcm2835_power_off(void) static int bcm2835_wdt_probe(struct platform_device *pdev) { - struct resource *res; + struct bcm2835_pm *pm = dev_get_drvdata(pdev->dev.parent); struct device *dev = &pdev->dev; struct bcm2835_wdt *wdt; int err; @@ -181,10 +181,7 @@ static int bcm2835_wdt_probe(struct platform_device *pdev) spin_lock_init(&wdt->lock); - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - wdt->base = devm_ioremap_resource(dev, res); - if (IS_ERR(wdt->base)) - return PTR_ERR(wdt->base); + wdt->base = pm->base; watchdog_set_drvdata(&bcm2835_wdt_wdd, wdt); watchdog_init_timeout(&bcm2835_wdt_wdd, heartbeat, dev); @@ -211,8 +208,10 @@ static int bcm2835_wdt_probe(struct platform_device *pdev) return err; } - if (pm_power_off == NULL) + if (pm_power_off == NULL) { pm_power_off = bcm2835_power_off; + bcm2835_power_off_wdt = wdt; + } dev_info(dev, "Broadcom BCM2835 watchdog timer"); return 0; @@ -226,18 +225,11 @@ static int bcm2835_wdt_remove(struct platform_device *pdev) return 0; } -static const struct of_device_id bcm2835_wdt_of_match[] = { - { .compatible = "brcm,bcm2835-pm-wdt", }, - {}, -}; -MODULE_DEVICE_TABLE(of, bcm2835_wdt_of_match); - static struct platform_driver bcm2835_wdt_driver = { .probe = bcm2835_wdt_probe, .remove = bcm2835_wdt_remove, .driver = { .name = "bcm2835-wdt", - .of_match_table = bcm2835_wdt_of_match, }, }; module_platform_driver(bcm2835_wdt_driver); diff --git a/include/linux/mfd/bcm2835-pm.h b/include/linux/mfd/bcm2835-pm.h new file mode 100644 index 000000000000..b7d0ee1feffa --- /dev/null +++ b/include/linux/mfd/bcm2835-pm.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ + +#ifndef BCM2835_MFD_PM_H +#define BCM2835_MFD_PM_H + +#include + +struct bcm2835_pm { + struct device *dev; + void __iomem *base; +}; + +#endif /* BCM2835_MFD_PM_H */ From 670c672608a1ffcbc7ac0f872734843593bb8b15 Mon Sep 17 00:00:00 2001 From: Eric Anholt Date: Wed, 12 Dec 2018 15:51:48 -0800 Subject: [PATCH 005/113] 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/mfd/bcm2835-pm.c | 36 +- drivers/soc/bcm/Kconfig | 11 + drivers/soc/bcm/Makefile | 1 + drivers/soc/bcm/bcm2835-power.c | 661 +++++++++++++++++++++++++++ include/dt-bindings/soc/bcm2835-pm.h | 28 ++ include/linux/mfd/bcm2835-pm.h | 1 + 6 files changed, 734 insertions(+), 4 deletions(-) create mode 100644 drivers/soc/bcm/bcm2835-power.c create mode 100644 include/dt-bindings/soc/bcm2835-pm.h diff --git a/drivers/mfd/bcm2835-pm.c b/drivers/mfd/bcm2835-pm.c index 53839e6a81e7..42fe67f1538e 100644 --- a/drivers/mfd/bcm2835-pm.c +++ b/drivers/mfd/bcm2835-pm.c @@ -3,7 +3,7 @@ * PM MFD driver for Broadcom BCM2835 * * This driver binds to the PM block and creates the MFD device for - * the WDT driver. + * the WDT and power drivers. */ #include @@ -21,11 +21,16 @@ static const struct mfd_cell bcm2835_pm_devs[] = { { .name = "bcm2835-wdt" }, }; +static const struct mfd_cell bcm2835_power_devs[] = { + { .name = "bcm2835-power" }, +}; + static int bcm2835_pm_probe(struct platform_device *pdev) { struct resource *res; struct device *dev = &pdev->dev; struct bcm2835_pm *pm; + int ret; pm = devm_kzalloc(dev, sizeof(*pm), GFP_KERNEL); if (!pm) @@ -39,13 +44,36 @@ static int bcm2835_pm_probe(struct platform_device *pdev) if (IS_ERR(pm->base)) return PTR_ERR(pm->base); - return devm_mfd_add_devices(dev, -1, - bcm2835_pm_devs, ARRAY_SIZE(bcm2835_pm_devs), - NULL, 0, NULL); + ret = devm_mfd_add_devices(dev, -1, + bcm2835_pm_devs, ARRAY_SIZE(bcm2835_pm_devs), + NULL, 0, NULL); + if (ret) + return ret; + + /* We'll use the presence of the AXI ASB regs in the + * bcm2835-pm binding as the key for whether we can reference + * the full PM register range and support power domains. + */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (res) { + pm->asb = devm_ioremap_resource(dev, res); + if (IS_ERR(pm->asb)) + return PTR_ERR(pm->asb); + + ret = devm_mfd_add_devices(dev, -1, + bcm2835_power_devs, + ARRAY_SIZE(bcm2835_power_devs), + NULL, 0, NULL); + if (ret) + return ret; + } + + return 0; } static const struct of_device_id bcm2835_pm_of_match[] = { { .compatible = "brcm,bcm2835-pm-wdt", }, + { .compatible = "brcm,bcm2835-pm", }, {}, }; MODULE_DEVICE_TABLE(of, bcm2835_pm_of_match); 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"); diff --git a/include/dt-bindings/soc/bcm2835-pm.h b/include/dt-bindings/soc/bcm2835-pm.h new file mode 100644 index 000000000000..153d75b8d99f --- /dev/null +++ b/include/dt-bindings/soc/bcm2835-pm.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: (GPL-2.0+ OR MIT) */ + +#ifndef _DT_BINDINGS_ARM_BCM2835_PM_H +#define _DT_BINDINGS_ARM_BCM2835_PM_H + +#define BCM2835_POWER_DOMAIN_GRAFX 0 +#define BCM2835_POWER_DOMAIN_GRAFX_V3D 1 +#define BCM2835_POWER_DOMAIN_IMAGE 2 +#define BCM2835_POWER_DOMAIN_IMAGE_PERI 3 +#define BCM2835_POWER_DOMAIN_IMAGE_ISP 4 +#define BCM2835_POWER_DOMAIN_IMAGE_H264 5 +#define BCM2835_POWER_DOMAIN_USB 6 +#define BCM2835_POWER_DOMAIN_DSI0 7 +#define BCM2835_POWER_DOMAIN_DSI1 8 +#define BCM2835_POWER_DOMAIN_CAM0 9 +#define BCM2835_POWER_DOMAIN_CAM1 10 +#define BCM2835_POWER_DOMAIN_CCP2TX 11 +#define BCM2835_POWER_DOMAIN_HDMI 12 + +#define BCM2835_POWER_DOMAIN_COUNT 13 + +#define BCM2835_RESET_V3D 0 +#define BCM2835_RESET_ISP 1 +#define BCM2835_RESET_H264 2 + +#define BCM2835_RESET_COUNT 3 + +#endif /* _DT_BINDINGS_ARM_BCM2835_PM_H */ diff --git a/include/linux/mfd/bcm2835-pm.h b/include/linux/mfd/bcm2835-pm.h index b7d0ee1feffa..ed37dc40e82a 100644 --- a/include/linux/mfd/bcm2835-pm.h +++ b/include/linux/mfd/bcm2835-pm.h @@ -8,6 +8,7 @@ struct bcm2835_pm { struct device *dev; void __iomem *base; + void __iomem *asb; }; #endif /* BCM2835_MFD_PM_H */ From b798d5a1b0eaf276f463262284e58a29b451063c Mon Sep 17 00:00:00 2001 From: Lucas Stach Date: Mon, 17 Dec 2018 16:31:51 +0100 Subject: [PATCH 006/113] 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(-) 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), From ae1d2add26a4a0d34dc0615d304be2f341202e77 Mon Sep 17 00:00:00 2001 From: Lucas Stach Date: Mon, 17 Dec 2018 16:31:52 +0100 Subject: [PATCH 007/113] 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 --- .../bindings/power/fsl,imx-gpcv2.txt | 3 + drivers/soc/imx/gpcv2.c | 60 ++++++++++++++++++- 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/power/fsl,imx-gpcv2.txt b/Documentation/devicetree/bindings/power/fsl,imx-gpcv2.txt index 7c947a996df1..7c7e972aaa42 100644 --- a/Documentation/devicetree/bindings/power/fsl,imx-gpcv2.txt +++ b/Documentation/devicetree/bindings/power/fsl,imx-gpcv2.txt @@ -32,6 +32,9 @@ Required properties: Optional properties: - power-supply: Power supply used to power the domain +- clocks: a number of phandles to clocks that need to be enabled during + domain power-up sequencing to ensure reset propagation into devices + located inside this power domain Example: 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; } From 4c783b010467db8eadd65da40b26f566d0d4d5cb Mon Sep 17 00:00:00 2001 From: Sven Van Asbroeck Date: Mon, 17 Dec 2018 10:47:58 -0500 Subject: [PATCH 008/113] dt-bindings: bus: imx-weim: document multiple address ranges per child node The imx-weim driver was patched to allow correct WEIM configuration when multiple address ranges are used in a child node. Update the dt-bindings to reflect this. Reviewed-by: Rob Herring Signed-off-by: Sven Van Asbroeck Signed-off-by: Shawn Guo --- .../devicetree/bindings/bus/imx-weim.txt | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/bus/imx-weim.txt b/Documentation/devicetree/bindings/bus/imx-weim.txt index 683eaf3aed79..dda7d6d66479 100644 --- a/Documentation/devicetree/bindings/bus/imx-weim.txt +++ b/Documentation/devicetree/bindings/bus/imx-weim.txt @@ -47,9 +47,9 @@ Optional properties: Timing property for child nodes. It is mandatory, not optional. - fsl,weim-cs-timing: The timing array, contains timing values for the - child node. We can get the CS index from the child - node's "reg" property. The number of registers depends - on the selected chip. + child node. We get the CS indexes from the address + ranges in the child node's "reg" property. + The number of registers depends on the selected chip: For i.MX1, i.MX21 ("fsl,imx1-weim") there are two registers: CSxU, CSxL. For i.MX25, i.MX27, i.MX31 and i.MX35 ("fsl,imx27-weim") @@ -80,3 +80,29 @@ Example for an imx6q-sabreauto board, the NOR flash connected to the WEIM: 0x0000c000 0x1404a38e 0x00000000>; }; }; + +Example for an imx6q-based board, a multi-chipselect device connected to WEIM: + +In this case, both chip select 0 and 1 will be configured with the same timing +array values. + + weim: weim@21b8000 { + compatible = "fsl,imx6q-weim"; + reg = <0x021b8000 0x4000>; + clocks = <&clks 196>; + #address-cells = <2>; + #size-cells = <1>; + ranges = <0 0 0x08000000 0x02000000 + 1 0 0x0a000000 0x02000000 + 2 0 0x0c000000 0x02000000 + 3 0 0x0e000000 0x02000000>; + fsl,weim-cs-gpr = <&gpr>; + + acme@0 { + compatible = "acme,whatever"; + reg = <0 0 0x100>, <0 0x400000 0x800>, + <1 0x400000 0x800>; + fsl,weim-cs-timing = <0x024400b1 0x00001010 0x20081100 + 0x00000000 0xa0000240 0x00000000>; + }; + }; From 8b8cb52af34da2faa293614b2554c8eac30faeaa Mon Sep 17 00:00:00 2001 From: Sven Van Asbroeck Date: Mon, 17 Dec 2018 10:47:59 -0500 Subject: [PATCH 009/113] bus: imx-weim: support multiple address ranges per child node Ensure that timing values for the child node are applied to all chip selects in the child's address ranges. Note that this does not support multiple timing settings per child; this can be added in the future if required. Example: &weim { acme@0 { compatible = "acme,whatever"; reg = <0 0 0x100>, <0 0x400000 0x800>, <1 0x400000 0x800>; fsl,weim-cs-timing = <0x024400b1 0x00001010 0x20081100 0x00000000 0xa0000240 0x00000000>; }; }; Signed-off-by: Sven Van Asbroeck Signed-off-by: Shawn Guo --- drivers/bus/imx-weim.c | 37 ++++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/drivers/bus/imx-weim.c b/drivers/bus/imx-weim.c index d84996a4528e..1a0e0277a404 100644 --- a/drivers/bus/imx-weim.c +++ b/drivers/bus/imx-weim.c @@ -46,6 +46,7 @@ static const struct imx_weim_devtype imx51_weim_devtype = { }; #define MAX_CS_REGS_COUNT 6 +#define OF_REG_SIZE 3 static const struct of_device_id weim_id_table[] = { /* i.MX1/21 */ @@ -116,26 +117,40 @@ static int __init weim_timing_setup(struct device_node *np, void __iomem *base, { u32 cs_idx, value[MAX_CS_REGS_COUNT]; int i, ret; + int reg_idx, num_regs; if (WARN_ON(devtype->cs_regs_count > MAX_CS_REGS_COUNT)) return -EINVAL; - /* get the CS index from this child node's "reg" property. */ - ret = of_property_read_u32(np, "reg", &cs_idx); - if (ret) - return ret; - - if (cs_idx >= devtype->cs_count) - return -EINVAL; - ret = of_property_read_u32_array(np, "fsl,weim-cs-timing", value, devtype->cs_regs_count); if (ret) return ret; - /* set the timing for WEIM */ - for (i = 0; i < devtype->cs_regs_count; i++) - writel(value[i], base + cs_idx * devtype->cs_stride + i * 4); + /* + * the child node's "reg" property may contain multiple address ranges, + * extract the chip select for each. + */ + num_regs = of_property_count_elems_of_size(np, "reg", OF_REG_SIZE); + if (num_regs < 0) + return num_regs; + if (!num_regs) + return -EINVAL; + for (reg_idx = 0; reg_idx < num_regs; reg_idx++) { + /* get the CS index from this child node's "reg" property. */ + ret = of_property_read_u32_index(np, "reg", + reg_idx * OF_REG_SIZE, &cs_idx); + if (ret) + break; + + if (cs_idx >= devtype->cs_count) + return -EINVAL; + + /* set the timing for WEIM */ + for (i = 0; i < devtype->cs_regs_count; i++) + writel(value[i], + base + cs_idx * devtype->cs_stride + i * 4); + } return 0; } From c7995bcb36ef61e8b4136efab31ecf3c9b1633f9 Mon Sep 17 00:00:00 2001 From: Sven Van Asbroeck Date: Mon, 17 Dec 2018 10:48:00 -0500 Subject: [PATCH 010/113] bus: imx-weim: guard against timing configuration conflicts When specifying weim child devices, there is a risk that more than one timing setting is specified for the same chip select. The driver cannot support such a configuration. In case of conflict, this patch will print a warning to the log, and will ignore the child node in question. In this example, node acme@1 will be ignored, as it tries to modify timing settings for CS0: &weim { acme@0 { compatible = "acme,whatever"; reg = <0 0 0x100>; fsl,weim-cs-timing = ; }; acme@1 { compatible = "acme,whatnot"; reg = <0 0x500 0x100>; fsl,weim-cs-timing = ; }; }; However in this example, the driver will be happy: &weim { acme@0 { compatible = "acme,whatever"; reg = <0 0 0x100>; fsl,weim-cs-timing = ; }; acme@1 { compatible = "acme,whatnot"; reg = <0 0x500 0x100>; fsl,weim-cs-timing = ; }; }; Signed-off-by: Sven Van Asbroeck Signed-off-by: Shawn Guo --- drivers/bus/imx-weim.c | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/drivers/bus/imx-weim.c b/drivers/bus/imx-weim.c index 1a0e0277a404..db74334ca5ef 100644 --- a/drivers/bus/imx-weim.c +++ b/drivers/bus/imx-weim.c @@ -46,8 +46,18 @@ static const struct imx_weim_devtype imx51_weim_devtype = { }; #define MAX_CS_REGS_COUNT 6 +#define MAX_CS_COUNT 6 #define OF_REG_SIZE 3 +struct cs_timing { + bool is_applied; + u32 regs[MAX_CS_REGS_COUNT]; +}; + +struct cs_timing_state { + struct cs_timing cs[MAX_CS_COUNT]; +}; + static const struct of_device_id weim_id_table[] = { /* i.MX1/21 */ { .compatible = "fsl,imx1-weim", .data = &imx1_weim_devtype, }, @@ -112,15 +122,20 @@ static int __init imx_weim_gpr_setup(struct platform_device *pdev) } /* Parse and set the timing for this device. */ -static int __init weim_timing_setup(struct device_node *np, void __iomem *base, - const struct imx_weim_devtype *devtype) +static int __init weim_timing_setup(struct device *dev, + struct device_node *np, void __iomem *base, + const struct imx_weim_devtype *devtype, + struct cs_timing_state *ts) { u32 cs_idx, value[MAX_CS_REGS_COUNT]; int i, ret; int reg_idx, num_regs; + struct cs_timing *cst; if (WARN_ON(devtype->cs_regs_count > MAX_CS_REGS_COUNT)) return -EINVAL; + if (WARN_ON(devtype->cs_count > MAX_CS_COUNT)) + return -EINVAL; ret = of_property_read_u32_array(np, "fsl,weim-cs-timing", value, devtype->cs_regs_count); @@ -146,10 +161,23 @@ static int __init weim_timing_setup(struct device_node *np, void __iomem *base, if (cs_idx >= devtype->cs_count) return -EINVAL; + /* prevent re-configuring a CS that's already been configured */ + cst = &ts->cs[cs_idx]; + if (cst->is_applied && memcmp(value, cst->regs, + devtype->cs_regs_count * sizeof(u32))) { + dev_err(dev, "fsl,weim-cs-timing conflict on %pOF", np); + return -EINVAL; + } + /* set the timing for WEIM */ for (i = 0; i < devtype->cs_regs_count; i++) writel(value[i], base + cs_idx * devtype->cs_stride + i * 4); + if (!cst->is_applied) { + cst->is_applied = true; + memcpy(cst->regs, value, + devtype->cs_regs_count * sizeof(u32)); + } } return 0; @@ -163,6 +191,7 @@ static int __init weim_parse_dt(struct platform_device *pdev, const struct imx_weim_devtype *devtype = of_id->data; struct device_node *child; int ret, have_child = 0; + struct cs_timing_state ts = {}; if (devtype == &imx50_weim_devtype) { ret = imx_weim_gpr_setup(pdev); @@ -171,7 +200,7 @@ static int __init weim_parse_dt(struct platform_device *pdev, } for_each_available_child_of_node(pdev->dev.of_node, child) { - ret = weim_timing_setup(child, base, devtype); + ret = weim_timing_setup(&pdev->dev, child, base, devtype, &ts); if (ret) dev_warn(&pdev->dev, "%pOF set timing failed.\n", child); From f1e250bf365962519c96a1255b2fe34b6c0d6c60 Mon Sep 17 00:00:00 2001 From: Ioana Ciocoi Radulescu Date: Fri, 14 Dec 2018 16:04:05 +0000 Subject: [PATCH 011/113] 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(+) 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; } From 8a533a7de21aa69cf9a3e55223c819858fbc7da2 Mon Sep 17 00:00:00 2001 From: Ioana Ciocoi Radulescu Date: Fri, 14 Dec 2018 16:04:06 +0000 Subject: [PATCH 012/113] 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 ++-- include/soc/fsl/dpaa2-io.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) 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) { diff --git a/include/soc/fsl/dpaa2-io.h b/include/soc/fsl/dpaa2-io.h index 3447fd10a3e6..672cfb58046f 100644 --- a/include/soc/fsl/dpaa2-io.h +++ b/include/soc/fsl/dpaa2-io.h @@ -111,9 +111,9 @@ int dpaa2_io_service_enqueue_fq(struct dpaa2_io *d, u32 fqid, const struct dpaa2_fd *fd); int dpaa2_io_service_enqueue_qd(struct dpaa2_io *d, u32 qdid, u8 prio, u16 qdbin, const struct dpaa2_fd *fd); -int dpaa2_io_service_release(struct dpaa2_io *d, u32 bpid, +int dpaa2_io_service_release(struct dpaa2_io *d, u16 bpid, const u64 *buffers, unsigned int num_buffers); -int dpaa2_io_service_acquire(struct dpaa2_io *d, u32 bpid, +int dpaa2_io_service_acquire(struct dpaa2_io *d, u16 bpid, u64 *buffers, unsigned int num_buffers); struct dpaa2_io_store *dpaa2_io_store_create(unsigned int max_frames, From 3c0d64e867ed78a782a8a00d3b519396d9c5a2db Mon Sep 17 00:00:00 2001 From: Li Yang Date: Fri, 11 Jan 2019 15:55:57 -0600 Subject: [PATCH 013/113] 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(-) 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; } From d80eebeb5dc6e0679f5d4187e9a8eb3895d13242 Mon Sep 17 00:00:00 2001 From: Aisheng Dong Date: Tue, 18 Dec 2018 16:01:20 +0000 Subject: [PATCH 014/113] dt-bindings: fsl: scu: add fallback compatible string for power domain SCU power domain can be used in the same way by IMX8QXP and IMX8QM SoCs. Let's add a "fsl,scu-pd" fallback compatible string to allow other SoCs to reuse the common part. Cc: Ulf Hansson Cc: Rob Herring Cc: Shawn Guo Cc: Sascha Hauer Cc: Fabio Estevam Cc: Mark Rutland Cc: devicetree@vger.kernel.org Signed-off-by: Dong Aisheng Reviewed-by: Rob Herring Reviewed-by: Ulf Hansson Signed-off-by: Shawn Guo --- .../devicetree/bindings/arm/freescale/fsl,scu.txt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/arm/freescale/fsl,scu.txt b/Documentation/devicetree/bindings/arm/freescale/fsl,scu.txt index 27784b6edfed..4a833c93e5a5 100644 --- a/Documentation/devicetree/bindings/arm/freescale/fsl,scu.txt +++ b/Documentation/devicetree/bindings/arm/freescale/fsl,scu.txt @@ -58,7 +58,10 @@ This binding for the SCU power domain providers uses the generic power domain binding[2]. Required properties: -- compatible: Should be "fsl,imx8qxp-scu-pd". +- compatible: Should be one of: + "fsl,imx8qxp-scu-pd" + followed by "fsl,scu-pd" + - #power-domain-cells: Must be 1. Contains the Resource ID used by SCU commands. See detailed Resource ID list from: @@ -154,7 +157,7 @@ firmware { }; pd: imx8qx-pd { - compatible = "fsl,imx8qxp-scu-pd"; + compatible = "fsl,imx8qxp-scu-pd", "fsl,scu-pd"; #power-domain-cells = <1>; }; From 8ae170cfad9af1798522f5d9929bd2a09cc3f21a Mon Sep 17 00:00:00 2001 From: Aisheng Dong Date: Tue, 18 Dec 2018 16:01:25 +0000 Subject: [PATCH 015/113] dt-bindings: fsl: scu: add imx8qm scu power domain support Add imx8qm scu power domain support Cc: Ulf Hansson Cc: Rob Herring Cc: Shawn Guo Cc: Sascha Hauer Cc: Fabio Estevam Cc: Mark Rutland Cc: devicetree@vger.kernel.org Signed-off-by: Dong Aisheng Reviewed-by: Rob Herring Reviewed-by: Ulf Hansson Signed-off-by: Shawn Guo --- Documentation/devicetree/bindings/arm/freescale/fsl,scu.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/arm/freescale/fsl,scu.txt b/Documentation/devicetree/bindings/arm/freescale/fsl,scu.txt index 4a833c93e5a5..fd2bed23e0e3 100644 --- a/Documentation/devicetree/bindings/arm/freescale/fsl,scu.txt +++ b/Documentation/devicetree/bindings/arm/freescale/fsl,scu.txt @@ -59,6 +59,7 @@ domain binding[2]. Required properties: - compatible: Should be one of: + "fsl,imx8qm-scu-pd", "fsl,imx8qxp-scu-pd" followed by "fsl,scu-pd" From e59e59b80e33b62a4c6f18435907ebd2317e2200 Mon Sep 17 00:00:00 2001 From: Aisheng Dong Date: Tue, 18 Dec 2018 16:01:29 +0000 Subject: [PATCH 016/113] firmware: imx: scu-pd: add fallback compatible string support SCU power domain can be used in the same way by IMX8QXP and IMX8QM SoCs. Make the driver support the fallback compatible string "fsl,scu-pd" to allow other SoCs to reuse the common part. Cc: Ulf Hansson Cc: Rob Herring Cc: Shawn Guo Cc: Sascha Hauer Cc: Fabio Estevam Cc: "Rafael J. Wysocki" Cc: Kevin Hilman Cc: linux-pm@vger.kernel.org Signed-off-by: Dong Aisheng Reviewed-by: Ulf Hansson Signed-off-by: Shawn Guo --- drivers/firmware/imx/scu-pd.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/firmware/imx/scu-pd.c b/drivers/firmware/imx/scu-pd.c index 407245f2efd0..39a94c7177fc 100644 --- a/drivers/firmware/imx/scu-pd.c +++ b/drivers/firmware/imx/scu-pd.c @@ -322,6 +322,7 @@ static int imx_sc_pd_probe(struct platform_device *pdev) static const struct of_device_id imx_sc_pd_match[] = { { .compatible = "fsl,imx8qxp-scu-pd", &imx8qxp_scu_pd}, + { .compatible = "fsl,scu-pd", &imx8qxp_scu_pd}, { /* sentinel */ } }; From 9b0bb07328f2375b2b6d3f8343edb1ee1ed39307 Mon Sep 17 00:00:00 2001 From: Abel Vesa Date: Sat, 12 Jan 2019 08:56:31 +0000 Subject: [PATCH 017/113] 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(-) 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 From cea8e2f3938c510f6041c632fe19e4e95296ae04 Mon Sep 17 00:00:00 2001 From: Rajendra Nayak Date: Thu, 10 Jan 2019 09:32:01 +0530 Subject: [PATCH 018/113] dt-bindings: opp: Introduce opp-level bindings Add opp-level as an additional property in the OPP node to describe the performance level of the device. On some SoCs (especially from Qualcomm and MediaTek) this value is communicated to a remote microprocessor by the CPU, which then takes some actions (like adjusting voltage values across various rails) based on the value passed. Reviewed-by: Stephen Boyd Reviewed-by: Douglas Anderson Reviewed-by: Rob Herring Acked-by: Viresh Kumar Signed-off-by: Rajendra Nayak Signed-off-by: Bjorn Andersson Signed-off-by: Andy Gross --- Documentation/devicetree/bindings/opp/opp.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/opp/opp.txt b/Documentation/devicetree/bindings/opp/opp.txt index c396c4c0af92..76b6c79604a5 100644 --- a/Documentation/devicetree/bindings/opp/opp.txt +++ b/Documentation/devicetree/bindings/opp/opp.txt @@ -129,6 +129,9 @@ Optional properties: - opp-microamp-: Named opp-microamp property. Similar to opp-microvolt- property, but for microamp instead. +- opp-level: A value representing the performance level of the device, + expressed as a 32-bit integer. + - clock-latency-ns: Specifies the maximum possible transition latency (in nanoseconds) for switching to this OPP from any other OPP. From 5b93ac542301026eff8954589cf59f801d03db3e Mon Sep 17 00:00:00 2001 From: Rajendra Nayak Date: Thu, 10 Jan 2019 09:32:02 +0530 Subject: [PATCH 019/113] OPP: Add support for parsing the 'opp-level' property Now that the OPP bindings are updated to include an optional 'opp-level' property, add support to parse it from device tree and store it as part of dev_pm_opp structure. Also add and export an helper 'dev_pm_opp_get_level()' that can be used to get the level value read from device tree when present. Reviewed-by: Stephen Boyd Acked-by: Viresh Kumar Signed-off-by: Rajendra Nayak Signed-off-by: Bjorn Andersson Signed-off-by: Andy Gross --- drivers/opp/core.c | 18 ++++++++++++++++++ drivers/opp/of.c | 2 ++ drivers/opp/opp.h | 2 ++ include/linux/pm_opp.h | 7 +++++++ 4 files changed, 29 insertions(+) diff --git a/drivers/opp/core.c b/drivers/opp/core.c index e5507add8f04..90b78a122be9 100644 --- a/drivers/opp/core.c +++ b/drivers/opp/core.c @@ -130,6 +130,24 @@ unsigned long dev_pm_opp_get_freq(struct dev_pm_opp *opp) } EXPORT_SYMBOL_GPL(dev_pm_opp_get_freq); +/** + * dev_pm_opp_get_level() - Gets the level corresponding to an available opp + * @opp: opp for which level value has to be returned for + * + * Return: level read from device tree corresponding to the opp, else + * return 0. + */ +unsigned int dev_pm_opp_get_level(struct dev_pm_opp *opp) +{ + if (IS_ERR_OR_NULL(opp) || !opp->available) { + pr_err("%s: Invalid parameters\n", __func__); + return 0; + } + + return opp->level; +} +EXPORT_SYMBOL_GPL(dev_pm_opp_get_level); + /** * dev_pm_opp_is_turbo() - Returns if opp is turbo OPP or not * @opp: opp for which turbo mode is being verified diff --git a/drivers/opp/of.c b/drivers/opp/of.c index 06f0f632ec47..1779f2c93291 100644 --- a/drivers/opp/of.c +++ b/drivers/opp/of.c @@ -594,6 +594,8 @@ static struct dev_pm_opp *_opp_add_static_v2(struct opp_table *opp_table, new_opp->rate = (unsigned long)rate; } + of_property_read_u32(np, "opp-level", &new_opp->level); + /* Check if the OPP supports hardware's hierarchy of versions or not */ if (!_opp_is_supported(dev, opp_table, np)) { dev_dbg(dev, "OPP not supported by hardware: %llu\n", rate); diff --git a/drivers/opp/opp.h b/drivers/opp/opp.h index e24d81497375..4458175aa661 100644 --- a/drivers/opp/opp.h +++ b/drivers/opp/opp.h @@ -60,6 +60,7 @@ extern struct list_head opp_tables; * @suspend: true if suspend OPP * @pstate: Device's power domain's performance state. * @rate: Frequency in hertz + * @level: Performance level * @supplies: Power supplies voltage/current values * @clock_latency_ns: Latency (in nanoseconds) of switching to this OPP's * frequency from any other OPP's frequency. @@ -80,6 +81,7 @@ struct dev_pm_opp { bool suspend; unsigned int pstate; unsigned long rate; + unsigned int level; struct dev_pm_opp_supply *supplies; diff --git a/include/linux/pm_opp.h b/include/linux/pm_opp.h index 0a2a88e5a383..473d2c7516f0 100644 --- a/include/linux/pm_opp.h +++ b/include/linux/pm_opp.h @@ -86,6 +86,8 @@ unsigned long dev_pm_opp_get_voltage(struct dev_pm_opp *opp); unsigned long dev_pm_opp_get_freq(struct dev_pm_opp *opp); +unsigned int dev_pm_opp_get_level(struct dev_pm_opp *opp); + bool dev_pm_opp_is_turbo(struct dev_pm_opp *opp); int dev_pm_opp_get_opp_count(struct device *dev); @@ -157,6 +159,11 @@ static inline unsigned long dev_pm_opp_get_freq(struct dev_pm_opp *opp) return 0; } +static inline unsigned int dev_pm_opp_get_level(struct dev_pm_opp *opp) +{ + return 0; +} + static inline bool dev_pm_opp_is_turbo(struct dev_pm_opp *opp) { return false; From c6e6eff4d48f8b7180f9f94f1615ffe345fd7e5c Mon Sep 17 00:00:00 2001 From: Rajendra Nayak Date: Thu, 10 Jan 2019 09:32:03 +0530 Subject: [PATCH 020/113] dt-bindings: power: Add qcom rpm power domain driver bindings Add DT bindings to describe the rpm/rpmh power domains found on Qualcomm Technologies, Inc. SoCs. These power domains communicate a performance state to RPM/RPMh, which then translates it into corresponding voltage on a PMIC rail. Reviewed-by: Ulf Hansson Reviewed-by: Stephen Boyd Reviewed-by: Rob Herring Signed-off-by: Rajendra Nayak Signed-off-by: Viresh Kumar Signed-off-by: Bjorn Andersson Signed-off-by: Andy Gross --- .../devicetree/bindings/power/qcom,rpmpd.txt | 145 ++++++++++++++++++ include/dt-bindings/power/qcom-rpmpd.h | 39 +++++ 2 files changed, 184 insertions(+) create mode 100644 Documentation/devicetree/bindings/power/qcom,rpmpd.txt create mode 100644 include/dt-bindings/power/qcom-rpmpd.h diff --git a/Documentation/devicetree/bindings/power/qcom,rpmpd.txt b/Documentation/devicetree/bindings/power/qcom,rpmpd.txt new file mode 100644 index 000000000000..980e5413d18f --- /dev/null +++ b/Documentation/devicetree/bindings/power/qcom,rpmpd.txt @@ -0,0 +1,145 @@ +Qualcomm RPM/RPMh Power domains + +For RPM/RPMh Power domains, we communicate a performance state to RPM/RPMh +which then translates it into a corresponding voltage on a rail + +Required Properties: + - compatible: Should be one of the following + * qcom,msm8996-rpmpd: RPM Power domain for the msm8996 family of SoC + * qcom,sdm845-rpmhpd: RPMh Power domain for the sdm845 family of SoC + - #power-domain-cells: number of cells in Power domain specifier + must be 1. + - operating-points-v2: Phandle to the OPP table for the Power domain. + Refer to Documentation/devicetree/bindings/power/power_domain.txt + and Documentation/devicetree/bindings/opp/opp.txt for more details + +Refer to for the level values for +various OPPs for different platforms as well as Power domain indexes + +Example: rpmh power domain controller and OPP table + +#include + +opp-level values specified in the OPP tables for RPMh power domains +should use the RPMH_REGULATOR_LEVEL_* constants from + + + rpmhpd: power-controller { + compatible = "qcom,sdm845-rpmhpd"; + #power-domain-cells = <1>; + operating-points-v2 = <&rpmhpd_opp_table>; + + rpmhpd_opp_table: opp-table { + compatible = "operating-points-v2"; + + rpmhpd_opp_ret: opp1 { + opp-level = ; + }; + + rpmhpd_opp_min_svs: opp2 { + opp-level = ; + }; + + rpmhpd_opp_low_svs: opp3 { + opp-level = ; + }; + + rpmhpd_opp_svs: opp4 { + opp-level = ; + }; + + rpmhpd_opp_svs_l1: opp5 { + opp-level = ; + }; + + rpmhpd_opp_nom: opp6 { + opp-level = ; + }; + + rpmhpd_opp_nom_l1: opp7 { + opp-level = ; + }; + + rpmhpd_opp_nom_l2: opp8 { + opp-level = ; + }; + + rpmhpd_opp_turbo: opp9 { + opp-level = ; + }; + + rpmhpd_opp_turbo_l1: opp10 { + opp-level = ; + }; + }; + }; + +Example: rpm power domain controller and OPP table + + rpmpd: power-controller { + compatible = "qcom,msm8996-rpmpd"; + #power-domain-cells = <1>; + operating-points-v2 = <&rpmpd_opp_table>; + + rpmpd_opp_table: opp-table { + compatible = "operating-points-v2"; + + rpmpd_opp_low: opp1 { + opp-level = <1>; + }; + + rpmpd_opp_ret: opp2 { + opp-level = <2>; + }; + + rpmpd_opp_svs: opp3 { + opp-level = <3>; + }; + + rpmpd_opp_normal: opp4 { + opp-level = <4>; + }; + + rpmpd_opp_high: opp5 { + opp-level = <5>; + }; + + rpmpd_opp_turbo: opp6 { + opp-level = <6>; + }; + }; + }; + +Example: Client/Consumer device using OPP table + + leaky-device0@12350000 { + compatible = "foo,i-leak-current"; + reg = <0x12350000 0x1000>; + power-domains = <&rpmhpd SDM845_MX>; + operating-points-v2 = <&leaky_opp_table>; + }; + + + leaky_opp_table: opp-table { + compatible = "operating-points-v2"; + + opp1 { + opp-hz = /bits/ 64 <144000>; + required-opps = <&rpmhpd_opp_low>; + }; + + opp2 { + opp-hz = /bits/ 64 <400000>; + required-opps = <&rpmhpd_opp_ret>; + }; + + opp3 { + opp-hz = /bits/ 64 <20000000>; + required-opps = <&rpmpd_opp_svs>; + }; + + opp4 { + opp-hz = /bits/ 64 <25000000>; + required-opps = <&rpmpd_opp_normal>; + }; + }; diff --git a/include/dt-bindings/power/qcom-rpmpd.h b/include/dt-bindings/power/qcom-rpmpd.h new file mode 100644 index 000000000000..87d9c6611682 --- /dev/null +++ b/include/dt-bindings/power/qcom-rpmpd.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (c) 2018, The Linux Foundation. All rights reserved. */ + +#ifndef _DT_BINDINGS_POWER_QCOM_RPMPD_H +#define _DT_BINDINGS_POWER_QCOM_RPMPD_H + +/* SDM845 Power Domain Indexes */ +#define SDM845_EBI 0 +#define SDM845_MX 1 +#define SDM845_MX_AO 2 +#define SDM845_CX 3 +#define SDM845_CX_AO 4 +#define SDM845_LMX 5 +#define SDM845_LCX 6 +#define SDM845_GFX 7 +#define SDM845_MSS 8 + +/* SDM845 Power Domain performance levels */ +#define RPMH_REGULATOR_LEVEL_RETENTION 16 +#define RPMH_REGULATOR_LEVEL_MIN_SVS 48 +#define RPMH_REGULATOR_LEVEL_LOW_SVS 64 +#define RPMH_REGULATOR_LEVEL_SVS 128 +#define RPMH_REGULATOR_LEVEL_SVS_L1 192 +#define RPMH_REGULATOR_LEVEL_NOM 256 +#define RPMH_REGULATOR_LEVEL_NOM_L1 320 +#define RPMH_REGULATOR_LEVEL_NOM_L2 336 +#define RPMH_REGULATOR_LEVEL_TURBO 384 +#define RPMH_REGULATOR_LEVEL_TURBO_L1 416 + +/* MSM8996 Power Domain Indexes */ +#define MSM8996_VDDCX 0 +#define MSM8996_VDDCX_AO 1 +#define MSM8996_VDDCX_VFC 2 +#define MSM8996_VDDMX 3 +#define MSM8996_VDDMX_AO 4 +#define MSM8996_VDDSSCX 5 +#define MSM8996_VDDSSCX_VFC 6 + +#endif From bbe3a66c3f5a65fb3d702351bac2a6033944d389 Mon Sep 17 00:00:00 2001 From: Rajendra Nayak Date: Thu, 10 Jan 2019 09:32:04 +0530 Subject: [PATCH 021/113] 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 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); From 075d3db8d10d82bb86777df3f81e1b83caeb630a Mon Sep 17 00:00:00 2001 From: Rajendra Nayak Date: Thu, 10 Jan 2019 09:32:05 +0530 Subject: [PATCH 022/113] 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(+) 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; From 279b7e8a62cc4f524dac49ac3ab5bc401a965422 Mon Sep 17 00:00:00 2001 From: Rajendra Nayak Date: Thu, 10 Jan 2019 09:32:07 +0530 Subject: [PATCH 023/113] 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 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); From 0503aec22c1483d02b2d8bbd8594c5b9713294b8 Mon Sep 17 00:00:00 2001 From: Rajendra Nayak Date: Thu, 10 Jan 2019 09:32:09 +0530 Subject: [PATCH 024/113] 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(+) 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); } From e31f941cf977f4ed7882358de2f513cac5865330 Mon Sep 17 00:00:00 2001 From: Amit Kucheria Date: Tue, 15 Jan 2019 01:15:49 +0530 Subject: [PATCH 025/113] MAINTAINERS: update list of qcom drivers Several drivers didn't have a specific maintainer (other than the subsystem maintainer). Add some generic regex patterns to capture most qcom drivers in the list of supported drivers. For the rest, add explicit filenames. Sort the entries, while we're at it. Signed-off-by: Amit Kucheria Signed-off-by: Andy Gross --- MAINTAINERS | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index 32d444476a90..a2b21a622ae1 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1948,19 +1948,37 @@ M: David Brown L: linux-arm-msm@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/soc/qcom/ +F: Documentation/devicetree/bindings/*/qcom* F: arch/arm/boot/dts/qcom-*.dts F: arch/arm/boot/dts/qcom-*.dtsi F: arch/arm/mach-qcom/ -F: arch/arm64/boot/dts/qcom/* -F: drivers/i2c/busses/i2c-qup.c -F: drivers/clk/qcom/ -F: drivers/dma/qcom/ -F: drivers/soc/qcom/ -F: drivers/spi/spi-qup.c -F: drivers/tty/serial/msm_serial.c +F: arch/arm64/boot/dts/qcom/ +F: drivers/*/qcom/ +F: drivers/*/qcom* +F: drivers/*/*/qcom/ +F: drivers/*/*/qcom* F: drivers/*/pm8???-* +F: drivers/bluetooth/btqcomsmd.c +F: drivers/clocksource/timer-qcom.c +F: drivers/extcon/extcon-qcom* +F: drivers/iommu/msm* +F: drivers/i2c/busses/i2c-qup.c +F: drivers/i2c/busses/i2c-qcom-geni.c F: drivers/mfd/ssbi.c -F: drivers/firmware/qcom_scm* +F: drivers/mmc/host/mmci_qcom* +F: drivers/mmc/host/sdhci_msm.c +F: drivers/pci/controller/dwc/pcie-qcom.c +F: drivers/phy/qualcomm/ +F: drivers/power/*/msm* +F: drivers/reset/reset-qcom-* +F: drivers/scsi/ufs/ufs-qcom.* +F: drivers/spi/spi-qup.c +F: drivers/spi/spi-geni-qcom.c +F: drivers/spi/spi-qcom-qspi.c +F: drivers/tty/serial/msm_serial.c +F: drivers/usb/dwc3/dwc3-qcom.c +F: include/dt-bindings/*/qcom* +F: include/linux/*/qcom* T: git git://git.kernel.org/pub/scm/linux/kernel/git/agross/linux.git ARM/RADISYS ENP2611 MACHINE SUPPORT From 41c5bb767ecbc986c93df312769f3f49189f8ee0 Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Wed, 16 Jan 2019 21:11:20 -0800 Subject: [PATCH 026/113] 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(-) 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) From 4eccc154b3866e59358f1102045d23a301dffe0a Mon Sep 17 00:00:00 2001 From: Rajendra Nayak Date: Fri, 18 Jan 2019 10:18:01 +0530 Subject: [PATCH 027/113] 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(-) 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 From f494caa480f7089ff9bba16dd865e190cee7e545 Mon Sep 17 00:00:00 2001 From: Stefan Wahren Date: Thu, 24 Jan 2019 18:59:15 +0100 Subject: [PATCH 028/113] 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(+) 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 From 51294bf6b9e897d595466dcda5a3f2751906a200 Mon Sep 17 00:00:00 2001 From: Timo Alho Date: Sun, 30 Dec 2018 17:58:08 +0200 Subject: [PATCH 029/113] 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(-) 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, From d94da0dd686d249ab0f6500d41169a7eff9a4b21 Mon Sep 17 00:00:00 2001 From: Joseph Lo Date: Fri, 4 Jan 2019 15:16:46 +0800 Subject: [PATCH 030/113] 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(-) 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; From 532700ed0a9b3565d5fd246f317724d05ee986b3 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Wed, 23 Jan 2019 12:38:43 +0100 Subject: [PATCH 031/113] 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(-) 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), }; From 4659db5e6fdfde6d58731560eec43e103eb671c5 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 25 Jan 2019 11:22:49 +0100 Subject: [PATCH 032/113] 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(-) 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 From bbe5af60041cae1970f6722323557e23bc1e5b83 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 25 Jan 2019 11:22:50 +0100 Subject: [PATCH 033/113] 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(+) 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; From d32dde2c5a110f2d69add70d7a9f1c20d82d0ef1 Mon Sep 17 00:00:00 2001 From: Jon Hunter Date: Fri, 25 Jan 2019 11:22:51 +0100 Subject: [PATCH 034/113] 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 +- include/soc/tegra/pmc.h | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) 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; diff --git a/include/soc/tegra/pmc.h b/include/soc/tegra/pmc.h index a9db1b501de1..b32ee5d82dd4 100644 --- a/include/soc/tegra/pmc.h +++ b/include/soc/tegra/pmc.h @@ -161,7 +161,6 @@ enum tegra_io_pad { #define TEGRA_IO_RAIL_LVDS TEGRA_IO_PAD_LVDS #ifdef CONFIG_SOC_TEGRA_PMC -int tegra_powergate_is_powered(unsigned int id); int tegra_powergate_power_on(unsigned int id); int tegra_powergate_power_off(unsigned int id); int tegra_powergate_remove_clamping(unsigned int id); @@ -182,11 +181,6 @@ void tegra_pmc_set_suspend_mode(enum tegra_suspend_mode mode); void tegra_pmc_enter_suspend_mode(enum tegra_suspend_mode mode); #else -static inline int tegra_powergate_is_powered(unsigned int id) -{ - return -ENOSYS; -} - static inline int tegra_powergate_power_on(unsigned int id) { return -ENOSYS; From 165ce6e01d2d00bff2f10eba8dd679cee44163bd Mon Sep 17 00:00:00 2001 From: Timo Alho Date: Thu, 24 Jan 2019 19:03:52 +0200 Subject: [PATCH 035/113] firmware: tegra: Reword messaging terminology As a preparatory change to refactor BPMP driver to support other than Tegra186 and Tegra194 chip generations, reword and slightly refactor some of the functions to better match with what is actually happening in the wire-level protocol. The communication with BPMP is essentially a Remote Procedure Call consisting of "request" and "response". Either side (BPMP or CPU) can initiate the communication. The state machine for communication consists of following steps (from Linux point of view): Linux initiating the call: 1) check that channel is free to transmit a request (is_request_channel_free) 2) copy request message payload to shared location 3) post the request in channel (post_request) 4) notify BPMP that channel state has been updated (ring_doorbell) 5) wait for response (is_response_ready) 6) copy response message payload from shared location 7) acknowledge the response in channel (ack_response) BPMP initiating the call: 1) wait for request (is_request_ready) 2) copy request message payload from shared location 3) acknowledge the request in channel (ack_request) 4) check that channel is free to transmit response (is_response_channel_free) 5) copy response message payload to shared location 6) post the response message to channel (post_response) 7) notify BPMP that channel state has been updated (ring_doorbell) Signed-off-by: Timo Alho Acked-by: Jon Hunter Signed-off-by: Thierry Reding --- drivers/firmware/tegra/bpmp.c | 109 ++++++++++++++++++++++------------ 1 file changed, 71 insertions(+), 38 deletions(-) diff --git a/drivers/firmware/tegra/bpmp.c b/drivers/firmware/tegra/bpmp.c index 689478b92bce..e7fd68ebedd0 100644 --- a/drivers/firmware/tegra/bpmp.c +++ b/drivers/firmware/tegra/bpmp.c @@ -96,7 +96,7 @@ static bool tegra_bpmp_message_valid(const struct tegra_bpmp_message *msg) (msg->rx.size == 0 || msg->rx.data); } -static bool tegra_bpmp_master_acked(struct tegra_bpmp_channel *channel) +static bool tegra_bpmp_is_response_ready(struct tegra_bpmp_channel *channel) { void *frame; @@ -111,7 +111,12 @@ static bool tegra_bpmp_master_acked(struct tegra_bpmp_channel *channel) return true; } -static int tegra_bpmp_wait_ack(struct tegra_bpmp_channel *channel) +static bool tegra_bpmp_is_request_ready(struct tegra_bpmp_channel *channel) +{ + return tegra_bpmp_is_response_ready(channel); +} + +static int tegra_bpmp_wait_response(struct tegra_bpmp_channel *channel) { unsigned long timeout = channel->bpmp->soc->channels.cpu_tx.timeout; ktime_t end; @@ -119,14 +124,25 @@ static int tegra_bpmp_wait_ack(struct tegra_bpmp_channel *channel) end = ktime_add_us(ktime_get(), timeout); do { - if (tegra_bpmp_master_acked(channel)) + if (tegra_bpmp_is_response_ready(channel)) return 0; } while (ktime_before(ktime_get(), end)); return -ETIMEDOUT; } -static bool tegra_bpmp_master_free(struct tegra_bpmp_channel *channel) +static int tegra_bpmp_ack_response(struct tegra_bpmp_channel *channel) +{ + return tegra_ivc_read_advance(channel->ivc); +} + +static int tegra_bpmp_ack_request(struct tegra_bpmp_channel *channel) +{ + return tegra_ivc_read_advance(channel->ivc); +} + +static bool +tegra_bpmp_is_request_channel_free(struct tegra_bpmp_channel *channel) { void *frame; @@ -141,7 +157,14 @@ static bool tegra_bpmp_master_free(struct tegra_bpmp_channel *channel) return true; } -static int tegra_bpmp_wait_master_free(struct tegra_bpmp_channel *channel) +static bool +tegra_bpmp_is_response_channel_free(struct tegra_bpmp_channel *channel) +{ + return tegra_bpmp_is_request_channel_free(channel); +} + +static int +tegra_bpmp_wait_request_channel_free(struct tegra_bpmp_channel *channel) { unsigned long timeout = channel->bpmp->soc->channels.cpu_tx.timeout; ktime_t start, now; @@ -149,7 +172,7 @@ static int tegra_bpmp_wait_master_free(struct tegra_bpmp_channel *channel) start = ns_to_ktime(local_clock()); do { - if (tegra_bpmp_master_free(channel)) + if (tegra_bpmp_is_request_channel_free(channel)) return 0; now = ns_to_ktime(local_clock()); @@ -158,6 +181,32 @@ static int tegra_bpmp_wait_master_free(struct tegra_bpmp_channel *channel) return -ETIMEDOUT; } +static int tegra_bpmp_post_request(struct tegra_bpmp_channel *channel) +{ + return tegra_ivc_write_advance(channel->ivc); +} + +static int tegra_bpmp_post_response(struct tegra_bpmp_channel *channel) +{ + return tegra_ivc_write_advance(channel->ivc); +} + +static int tegra_bpmp_ring_doorbell(struct tegra_bpmp *bpmp) +{ + int err; + + if (WARN_ON(bpmp->mbox.channel == NULL)) + return -EINVAL; + + err = mbox_send_message(bpmp->mbox.channel, NULL); + if (err < 0) + return err; + + mbox_client_txdone(bpmp->mbox.channel, 0); + + return 0; +} + static ssize_t __tegra_bpmp_channel_read(struct tegra_bpmp_channel *channel, void *data, size_t size, int *ret) { @@ -166,7 +215,7 @@ static ssize_t __tegra_bpmp_channel_read(struct tegra_bpmp_channel *channel, if (data && size > 0) memcpy(data, channel->ib->data, size); - err = tegra_ivc_read_advance(channel->ivc); + err = tegra_bpmp_ack_response(channel); if (err < 0) return err; @@ -210,7 +259,7 @@ static ssize_t __tegra_bpmp_channel_write(struct tegra_bpmp_channel *channel, if (data && size > 0) memcpy(channel->ob->data, data, size); - return tegra_ivc_write_advance(channel->ivc); + return tegra_bpmp_post_request(channel); } static struct tegra_bpmp_channel * @@ -238,7 +287,7 @@ tegra_bpmp_write_threaded(struct tegra_bpmp *bpmp, unsigned int mrq, channel = &bpmp->threaded_channels[index]; - if (!tegra_bpmp_master_free(channel)) { + if (!tegra_bpmp_is_request_channel_free(channel)) { err = -EBUSY; goto unlock; } @@ -270,7 +319,7 @@ static ssize_t tegra_bpmp_channel_write(struct tegra_bpmp_channel *channel, { int err; - err = tegra_bpmp_wait_master_free(channel); + err = tegra_bpmp_wait_request_channel_free(channel); if (err < 0) return err; @@ -302,13 +351,11 @@ int tegra_bpmp_transfer_atomic(struct tegra_bpmp *bpmp, spin_unlock(&bpmp->atomic_tx_lock); - err = mbox_send_message(bpmp->mbox.channel, NULL); + err = tegra_bpmp_ring_doorbell(bpmp); if (err < 0) return err; - mbox_client_txdone(bpmp->mbox.channel, 0); - - err = tegra_bpmp_wait_ack(channel); + err = tegra_bpmp_wait_response(channel); if (err < 0) return err; @@ -335,12 +382,10 @@ int tegra_bpmp_transfer(struct tegra_bpmp *bpmp, if (IS_ERR(channel)) return PTR_ERR(channel); - err = mbox_send_message(bpmp->mbox.channel, NULL); + err = tegra_bpmp_ring_doorbell(bpmp); if (err < 0) return err; - mbox_client_txdone(bpmp->mbox.channel, 0); - timeout = usecs_to_jiffies(bpmp->soc->channels.thread.timeout); err = wait_for_completion_timeout(&channel->completion, timeout); @@ -369,38 +414,34 @@ void tegra_bpmp_mrq_return(struct tegra_bpmp_channel *channel, int code, { unsigned long flags = channel->ib->flags; struct tegra_bpmp *bpmp = channel->bpmp; - struct tegra_bpmp_mb_data *frame; int err; if (WARN_ON(size > MSG_DATA_MIN_SZ)) return; - err = tegra_ivc_read_advance(channel->ivc); + err = tegra_bpmp_ack_request(channel); if (WARN_ON(err < 0)) return; if ((flags & MSG_ACK) == 0) return; - frame = tegra_ivc_write_get_next_frame(channel->ivc); - if (WARN_ON(IS_ERR(frame))) + if (WARN_ON(!tegra_bpmp_is_response_channel_free(channel))) return; - frame->code = code; + channel->ob->code = code; if (data && size > 0) - memcpy(frame->data, data, size); + memcpy(channel->ob->data, data, size); - err = tegra_ivc_write_advance(channel->ivc); + err = tegra_bpmp_post_response(channel); if (WARN_ON(err < 0)) return; if (flags & MSG_RING) { - err = mbox_send_message(bpmp->mbox.channel, NULL); + err = tegra_bpmp_ring_doorbell(bpmp); if (WARN_ON(err < 0)) return; - - mbox_client_txdone(bpmp->mbox.channel, 0); } } EXPORT_SYMBOL_GPL(tegra_bpmp_mrq_return); @@ -638,7 +679,7 @@ static void tegra_bpmp_handle_rx(struct mbox_client *client, void *data) count = bpmp->soc->channels.thread.count; busy = bpmp->threaded.busy; - if (tegra_bpmp_master_acked(channel)) + if (tegra_bpmp_is_request_ready(channel)) tegra_bpmp_handle_mrq(bpmp, channel->ib->code, channel); spin_lock(&bpmp->lock); @@ -648,7 +689,7 @@ static void tegra_bpmp_handle_rx(struct mbox_client *client, void *data) channel = &bpmp->threaded_channels[i]; - if (tegra_bpmp_master_acked(channel)) { + if (tegra_bpmp_is_response_ready(channel)) { tegra_bpmp_channel_signal(channel); clear_bit(i, busy); } @@ -660,16 +701,8 @@ static void tegra_bpmp_handle_rx(struct mbox_client *client, void *data) static void tegra_bpmp_ivc_notify(struct tegra_ivc *ivc, void *data) { struct tegra_bpmp *bpmp = data; - int err; - if (WARN_ON(bpmp->mbox.channel == NULL)) - return; - - err = mbox_send_message(bpmp->mbox.channel, NULL); - if (err < 0) - return; - - mbox_client_txdone(bpmp->mbox.channel, 0); + WARN_ON(tegra_bpmp_ring_doorbell(bpmp)); } static int tegra_bpmp_channel_init(struct tegra_bpmp_channel *channel, From cdfa358b248efd36c6a9cb4d4d0a3ba7509f8387 Mon Sep 17 00:00:00 2001 From: Timo Alho Date: Thu, 24 Jan 2019 19:03:53 +0200 Subject: [PATCH 036/113] firmware: tegra: Refactor BPMP driver Split BPMP driver into common and chip specific parts to facilitate adding support for previous and future Tegra chips that are using BPMP as co-processor. Signed-off-by: Timo Alho Acked-by: Jon Hunter Signed-off-by: Thierry Reding --- drivers/firmware/tegra/Makefile | 1 + drivers/firmware/tegra/bpmp-private.h | 28 +++ drivers/firmware/tegra/bpmp-tegra186.c | 310 +++++++++++++++++++++++++ drivers/firmware/tegra/bpmp.c | 257 +++++--------------- include/soc/tegra/bpmp.h | 12 +- 5 files changed, 400 insertions(+), 208 deletions(-) create mode 100644 drivers/firmware/tegra/bpmp-private.h create mode 100644 drivers/firmware/tegra/bpmp-tegra186.c diff --git a/drivers/firmware/tegra/Makefile b/drivers/firmware/tegra/Makefile index 1b826dcca719..559355674bca 100644 --- a/drivers/firmware/tegra/Makefile +++ b/drivers/firmware/tegra/Makefile @@ -1,4 +1,5 @@ tegra-bpmp-y = bpmp.o +tegra-bpmp-$(CONFIG_ARCH_TEGRA_186_SOC) += bpmp-tegra186.o tegra-bpmp-$(CONFIG_DEBUG_FS) += bpmp-debugfs.o obj-$(CONFIG_TEGRA_BPMP) += tegra-bpmp.o obj-$(CONFIG_TEGRA_IVC) += ivc.o diff --git a/drivers/firmware/tegra/bpmp-private.h b/drivers/firmware/tegra/bpmp-private.h new file mode 100644 index 000000000000..6fb10f1cfb8f --- /dev/null +++ b/drivers/firmware/tegra/bpmp-private.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2018, NVIDIA CORPORATION. + */ + +#ifndef __FIRMWARE_TEGRA_BPMP_PRIVATE_H +#define __FIRMWARE_TEGRA_BPMP_PRIVATE_H + +#include + +struct tegra_bpmp_ops { + int (*init)(struct tegra_bpmp *bpmp); + void (*deinit)(struct tegra_bpmp *bpmp); + bool (*is_response_ready)(struct tegra_bpmp_channel *channel); + bool (*is_request_ready)(struct tegra_bpmp_channel *channel); + int (*ack_response)(struct tegra_bpmp_channel *channel); + int (*ack_request)(struct tegra_bpmp_channel *channel); + bool (*is_response_channel_free)(struct tegra_bpmp_channel *channel); + bool (*is_request_channel_free)(struct tegra_bpmp_channel *channel); + int (*post_response)(struct tegra_bpmp_channel *channel); + int (*post_request)(struct tegra_bpmp_channel *channel); + int (*ring_doorbell)(struct tegra_bpmp *bpmp); + int (*resume)(struct tegra_bpmp *bpmp); +}; + +extern const struct tegra_bpmp_ops tegra186_bpmp_ops; + +#endif diff --git a/drivers/firmware/tegra/bpmp-tegra186.c b/drivers/firmware/tegra/bpmp-tegra186.c new file mode 100644 index 000000000000..89bb32b97345 --- /dev/null +++ b/drivers/firmware/tegra/bpmp-tegra186.c @@ -0,0 +1,310 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2018, NVIDIA CORPORATION. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "bpmp-private.h" + +struct tegra186_bpmp { + struct tegra_bpmp *parent; + + struct { + struct gen_pool *pool; + dma_addr_t phys; + void *virt; + } tx, rx; + + struct { + struct mbox_client client; + struct mbox_chan *channel; + } mbox; +}; + +static inline struct tegra_bpmp * +mbox_client_to_bpmp(struct mbox_client *client) +{ + struct tegra186_bpmp *priv; + + priv = container_of(client, struct tegra186_bpmp, mbox.client); + + return priv->parent; +} + +static bool tegra186_bpmp_is_message_ready(struct tegra_bpmp_channel *channel) +{ + void *frame; + + frame = tegra_ivc_read_get_next_frame(channel->ivc); + if (IS_ERR(frame)) { + channel->ib = NULL; + return false; + } + + channel->ib = frame; + + return true; +} + +static bool tegra186_bpmp_is_channel_free(struct tegra_bpmp_channel *channel) +{ + void *frame; + + frame = tegra_ivc_write_get_next_frame(channel->ivc); + if (IS_ERR(frame)) { + channel->ob = NULL; + return false; + } + + channel->ob = frame; + + return true; +} + +static int tegra186_bpmp_ack_message(struct tegra_bpmp_channel *channel) +{ + return tegra_ivc_read_advance(channel->ivc); +} + +static int tegra186_bpmp_post_message(struct tegra_bpmp_channel *channel) +{ + return tegra_ivc_write_advance(channel->ivc); +} + +static int tegra186_bpmp_ring_doorbell(struct tegra_bpmp *bpmp) +{ + struct tegra186_bpmp *priv = bpmp->priv; + int err; + + err = mbox_send_message(priv->mbox.channel, NULL); + if (err < 0) + return err; + + mbox_client_txdone(priv->mbox.channel, 0); + + return 0; +} + +static void tegra186_bpmp_ivc_notify(struct tegra_ivc *ivc, void *data) +{ + struct tegra_bpmp *bpmp = data; + struct tegra186_bpmp *priv = bpmp->priv; + + if (WARN_ON(priv->mbox.channel == NULL)) + return; + + tegra186_bpmp_ring_doorbell(bpmp); +} + +static int tegra186_bpmp_channel_init(struct tegra_bpmp_channel *channel, + struct tegra_bpmp *bpmp, + unsigned int index) +{ + struct tegra186_bpmp *priv = bpmp->priv; + size_t message_size, queue_size; + unsigned int offset; + int err; + + channel->ivc = devm_kzalloc(bpmp->dev, sizeof(*channel->ivc), + GFP_KERNEL); + if (!channel->ivc) + return -ENOMEM; + + message_size = tegra_ivc_align(MSG_MIN_SZ); + queue_size = tegra_ivc_total_queue_size(message_size); + offset = queue_size * index; + + err = tegra_ivc_init(channel->ivc, NULL, + priv->rx.virt + offset, priv->rx.phys + offset, + priv->tx.virt + offset, priv->tx.phys + offset, + 1, message_size, tegra186_bpmp_ivc_notify, + bpmp); + if (err < 0) { + dev_err(bpmp->dev, "failed to setup IVC for channel %u: %d\n", + index, err); + return err; + } + + init_completion(&channel->completion); + channel->bpmp = bpmp; + + return 0; +} + +static void tegra186_bpmp_channel_reset(struct tegra_bpmp_channel *channel) +{ + /* reset the channel state */ + tegra_ivc_reset(channel->ivc); + + /* sync the channel state with BPMP */ + while (tegra_ivc_notified(channel->ivc)) + ; +} + +static void tegra186_bpmp_channel_cleanup(struct tegra_bpmp_channel *channel) +{ + tegra_ivc_cleanup(channel->ivc); +} + +static void mbox_handle_rx(struct mbox_client *client, void *data) +{ + struct tegra_bpmp *bpmp = mbox_client_to_bpmp(client); + + tegra_bpmp_handle_rx(bpmp); +} + +static int tegra186_bpmp_init(struct tegra_bpmp *bpmp) +{ + struct tegra186_bpmp *priv; + unsigned int i; + int err; + + priv = devm_kzalloc(bpmp->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + bpmp->priv = priv; + priv->parent = bpmp; + + priv->tx.pool = of_gen_pool_get(bpmp->dev->of_node, "shmem", 0); + if (!priv->tx.pool) { + dev_err(bpmp->dev, "TX shmem pool not found\n"); + return -ENOMEM; + } + + priv->tx.virt = gen_pool_dma_alloc(priv->tx.pool, 4096, &priv->tx.phys); + if (!priv->tx.virt) { + dev_err(bpmp->dev, "failed to allocate from TX pool\n"); + return -ENOMEM; + } + + priv->rx.pool = of_gen_pool_get(bpmp->dev->of_node, "shmem", 1); + if (!priv->rx.pool) { + dev_err(bpmp->dev, "RX shmem pool not found\n"); + err = -ENOMEM; + goto free_tx; + } + + priv->rx.virt = gen_pool_dma_alloc(priv->rx.pool, 4096, &priv->rx.phys); + if (!priv->rx.virt) { + dev_err(bpmp->dev, "failed to allocate from RX pool\n"); + err = -ENOMEM; + goto free_tx; + } + + err = tegra186_bpmp_channel_init(bpmp->tx_channel, bpmp, + bpmp->soc->channels.cpu_tx.offset); + if (err < 0) + goto free_rx; + + err = tegra186_bpmp_channel_init(bpmp->rx_channel, bpmp, + bpmp->soc->channels.cpu_rx.offset); + if (err < 0) + goto cleanup_tx_channel; + + for (i = 0; i < bpmp->threaded.count; i++) { + unsigned int index = bpmp->soc->channels.thread.offset + i; + + err = tegra186_bpmp_channel_init(&bpmp->threaded_channels[i], + bpmp, index); + if (err < 0) + goto cleanup_channels; + } + + /* mbox registration */ + priv->mbox.client.dev = bpmp->dev; + priv->mbox.client.rx_callback = mbox_handle_rx; + priv->mbox.client.tx_block = false; + priv->mbox.client.knows_txdone = false; + + priv->mbox.channel = mbox_request_channel(&priv->mbox.client, 0); + if (IS_ERR(priv->mbox.channel)) { + err = PTR_ERR(priv->mbox.channel); + dev_err(bpmp->dev, "failed to get HSP mailbox: %d\n", err); + goto cleanup_channels; + } + + tegra186_bpmp_channel_reset(bpmp->tx_channel); + tegra186_bpmp_channel_reset(bpmp->rx_channel); + + for (i = 0; i < bpmp->threaded.count; i++) + tegra186_bpmp_channel_reset(&bpmp->threaded_channels[i]); + + return 0; + +cleanup_channels: + for (i = 0; i < bpmp->threaded.count; i++) { + if (!bpmp->threaded_channels[i].bpmp) + continue; + + tegra186_bpmp_channel_cleanup(&bpmp->threaded_channels[i]); + } + + tegra186_bpmp_channel_cleanup(bpmp->rx_channel); +cleanup_tx_channel: + tegra186_bpmp_channel_cleanup(bpmp->tx_channel); +free_rx: + gen_pool_free(priv->rx.pool, (unsigned long)priv->rx.virt, 4096); +free_tx: + gen_pool_free(priv->tx.pool, (unsigned long)priv->tx.virt, 4096); + + return err; +} + +static void tegra186_bpmp_deinit(struct tegra_bpmp *bpmp) +{ + struct tegra186_bpmp *priv = bpmp->priv; + unsigned int i; + + mbox_free_channel(priv->mbox.channel); + + for (i = 0; i < bpmp->threaded.count; i++) + tegra186_bpmp_channel_cleanup(&bpmp->threaded_channels[i]); + + tegra186_bpmp_channel_cleanup(bpmp->rx_channel); + tegra186_bpmp_channel_cleanup(bpmp->tx_channel); + + gen_pool_free(priv->rx.pool, (unsigned long)priv->rx.virt, 4096); + gen_pool_free(priv->tx.pool, (unsigned long)priv->tx.virt, 4096); +} + +static int tegra186_bpmp_resume(struct tegra_bpmp *bpmp) +{ + unsigned int i; + + /* reset message channels */ + tegra186_bpmp_channel_reset(bpmp->tx_channel); + tegra186_bpmp_channel_reset(bpmp->rx_channel); + + for (i = 0; i < bpmp->threaded.count; i++) + tegra186_bpmp_channel_reset(&bpmp->threaded_channels[i]); + + return 0; +} + +const struct tegra_bpmp_ops tegra186_bpmp_ops = { + .init = tegra186_bpmp_init, + .deinit = tegra186_bpmp_deinit, + .is_response_ready = tegra186_bpmp_is_message_ready, + .is_request_ready = tegra186_bpmp_is_message_ready, + .ack_response = tegra186_bpmp_ack_message, + .ack_request = tegra186_bpmp_ack_message, + .is_response_channel_free = tegra186_bpmp_is_channel_free, + .is_request_channel_free = tegra186_bpmp_is_channel_free, + .post_response = tegra186_bpmp_post_message, + .post_request = tegra186_bpmp_post_message, + .ring_doorbell = tegra186_bpmp_ring_doorbell, + .resume = tegra186_bpmp_resume, +}; diff --git a/drivers/firmware/tegra/bpmp.c b/drivers/firmware/tegra/bpmp.c index e7fd68ebedd0..2b498deacdbc 100644 --- a/drivers/firmware/tegra/bpmp.c +++ b/drivers/firmware/tegra/bpmp.c @@ -26,6 +26,8 @@ #include #include +#include "bpmp-private.h" + #define MSG_ACK BIT(0) #define MSG_RING BIT(1) #define TAG_SZ 32 @@ -36,6 +38,14 @@ mbox_client_to_bpmp(struct mbox_client *client) return container_of(client, struct tegra_bpmp, mbox.client); } +static inline const struct tegra_bpmp_ops * +channel_to_ops(struct tegra_bpmp_channel *channel) +{ + struct tegra_bpmp *bpmp = channel->bpmp; + + return bpmp->soc->ops; +} + struct tegra_bpmp *tegra_bpmp_get(struct device *dev) { struct platform_device *pdev; @@ -98,22 +108,16 @@ static bool tegra_bpmp_message_valid(const struct tegra_bpmp_message *msg) static bool tegra_bpmp_is_response_ready(struct tegra_bpmp_channel *channel) { - void *frame; + const struct tegra_bpmp_ops *ops = channel_to_ops(channel); - frame = tegra_ivc_read_get_next_frame(channel->ivc); - if (IS_ERR(frame)) { - channel->ib = NULL; - return false; - } - - channel->ib = frame; - - return true; + return ops->is_response_ready(channel); } static bool tegra_bpmp_is_request_ready(struct tegra_bpmp_channel *channel) { - return tegra_bpmp_is_response_ready(channel); + const struct tegra_bpmp_ops *ops = channel_to_ops(channel); + + return ops->is_request_ready(channel); } static int tegra_bpmp_wait_response(struct tegra_bpmp_channel *channel) @@ -133,34 +137,32 @@ static int tegra_bpmp_wait_response(struct tegra_bpmp_channel *channel) static int tegra_bpmp_ack_response(struct tegra_bpmp_channel *channel) { - return tegra_ivc_read_advance(channel->ivc); + const struct tegra_bpmp_ops *ops = channel_to_ops(channel); + + return ops->ack_response(channel); } static int tegra_bpmp_ack_request(struct tegra_bpmp_channel *channel) { - return tegra_ivc_read_advance(channel->ivc); + const struct tegra_bpmp_ops *ops = channel_to_ops(channel); + + return ops->ack_request(channel); } static bool tegra_bpmp_is_request_channel_free(struct tegra_bpmp_channel *channel) { - void *frame; + const struct tegra_bpmp_ops *ops = channel_to_ops(channel); - frame = tegra_ivc_write_get_next_frame(channel->ivc); - if (IS_ERR(frame)) { - channel->ob = NULL; - return false; - } - - channel->ob = frame; - - return true; + return ops->is_request_channel_free(channel); } static bool tegra_bpmp_is_response_channel_free(struct tegra_bpmp_channel *channel) { - return tegra_bpmp_is_request_channel_free(channel); + const struct tegra_bpmp_ops *ops = channel_to_ops(channel); + + return ops->is_response_channel_free(channel); } static int @@ -183,28 +185,21 @@ tegra_bpmp_wait_request_channel_free(struct tegra_bpmp_channel *channel) static int tegra_bpmp_post_request(struct tegra_bpmp_channel *channel) { - return tegra_ivc_write_advance(channel->ivc); + const struct tegra_bpmp_ops *ops = channel_to_ops(channel); + + return ops->post_request(channel); } static int tegra_bpmp_post_response(struct tegra_bpmp_channel *channel) { - return tegra_ivc_write_advance(channel->ivc); + const struct tegra_bpmp_ops *ops = channel_to_ops(channel); + + return ops->post_response(channel); } static int tegra_bpmp_ring_doorbell(struct tegra_bpmp *bpmp) { - int err; - - if (WARN_ON(bpmp->mbox.channel == NULL)) - return -EINVAL; - - err = mbox_send_message(bpmp->mbox.channel, NULL); - if (err < 0) - return err; - - mbox_client_txdone(bpmp->mbox.channel, 0); - - return 0; + return bpmp->soc->ops->ring_doorbell(bpmp); } static ssize_t __tegra_bpmp_channel_read(struct tegra_bpmp_channel *channel, @@ -668,9 +663,8 @@ static void tegra_bpmp_channel_signal(struct tegra_bpmp_channel *channel) complete(&channel->completion); } -static void tegra_bpmp_handle_rx(struct mbox_client *client, void *data) +void tegra_bpmp_handle_rx(struct tegra_bpmp *bpmp) { - struct tegra_bpmp *bpmp = mbox_client_to_bpmp(client); struct tegra_bpmp_channel *channel; unsigned int i, count; unsigned long *busy; @@ -698,66 +692,9 @@ static void tegra_bpmp_handle_rx(struct mbox_client *client, void *data) spin_unlock(&bpmp->lock); } -static void tegra_bpmp_ivc_notify(struct tegra_ivc *ivc, void *data) -{ - struct tegra_bpmp *bpmp = data; - - WARN_ON(tegra_bpmp_ring_doorbell(bpmp)); -} - -static int tegra_bpmp_channel_init(struct tegra_bpmp_channel *channel, - struct tegra_bpmp *bpmp, - unsigned int index) -{ - size_t message_size, queue_size; - unsigned int offset; - int err; - - channel->ivc = devm_kzalloc(bpmp->dev, sizeof(*channel->ivc), - GFP_KERNEL); - if (!channel->ivc) - return -ENOMEM; - - message_size = tegra_ivc_align(MSG_MIN_SZ); - queue_size = tegra_ivc_total_queue_size(message_size); - offset = queue_size * index; - - err = tegra_ivc_init(channel->ivc, NULL, - bpmp->rx.virt + offset, bpmp->rx.phys + offset, - bpmp->tx.virt + offset, bpmp->tx.phys + offset, - 1, message_size, tegra_bpmp_ivc_notify, - bpmp); - if (err < 0) { - dev_err(bpmp->dev, "failed to setup IVC for channel %u: %d\n", - index, err); - return err; - } - - init_completion(&channel->completion); - channel->bpmp = bpmp; - - return 0; -} - -static void tegra_bpmp_channel_reset(struct tegra_bpmp_channel *channel) -{ - /* reset the channel state */ - tegra_ivc_reset(channel->ivc); - - /* sync the channel state with BPMP */ - while (tegra_ivc_notified(channel->ivc)) - ; -} - -static void tegra_bpmp_channel_cleanup(struct tegra_bpmp_channel *channel) -{ - tegra_ivc_cleanup(channel->ivc); -} - static int tegra_bpmp_probe(struct platform_device *pdev) { struct tegra_bpmp *bpmp; - unsigned int i; char tag[TAG_SZ]; size_t size; int err; @@ -769,32 +706,6 @@ static int tegra_bpmp_probe(struct platform_device *pdev) bpmp->soc = of_device_get_match_data(&pdev->dev); bpmp->dev = &pdev->dev; - bpmp->tx.pool = of_gen_pool_get(pdev->dev.of_node, "shmem", 0); - if (!bpmp->tx.pool) { - dev_err(&pdev->dev, "TX shmem pool not found\n"); - return -ENOMEM; - } - - bpmp->tx.virt = gen_pool_dma_alloc(bpmp->tx.pool, 4096, &bpmp->tx.phys); - if (!bpmp->tx.virt) { - dev_err(&pdev->dev, "failed to allocate from TX pool\n"); - return -ENOMEM; - } - - bpmp->rx.pool = of_gen_pool_get(pdev->dev.of_node, "shmem", 1); - if (!bpmp->rx.pool) { - dev_err(&pdev->dev, "RX shmem pool not found\n"); - err = -ENOMEM; - goto free_tx; - } - - bpmp->rx.virt = gen_pool_dma_alloc(bpmp->rx.pool, 4096, &bpmp->rx.phys); - if (!bpmp->rx.virt) { - dev_err(&pdev->dev, "failed to allocate from RX pool\n"); - err = -ENOMEM; - goto free_tx; - } - INIT_LIST_HEAD(&bpmp->mrqs); spin_lock_init(&bpmp->lock); @@ -804,81 +715,38 @@ static int tegra_bpmp_probe(struct platform_device *pdev) size = BITS_TO_LONGS(bpmp->threaded.count) * sizeof(long); bpmp->threaded.allocated = devm_kzalloc(&pdev->dev, size, GFP_KERNEL); - if (!bpmp->threaded.allocated) { - err = -ENOMEM; - goto free_rx; - } + if (!bpmp->threaded.allocated) + return -ENOMEM; bpmp->threaded.busy = devm_kzalloc(&pdev->dev, size, GFP_KERNEL); - if (!bpmp->threaded.busy) { - err = -ENOMEM; - goto free_rx; - } + if (!bpmp->threaded.busy) + return -ENOMEM; spin_lock_init(&bpmp->atomic_tx_lock); bpmp->tx_channel = devm_kzalloc(&pdev->dev, sizeof(*bpmp->tx_channel), GFP_KERNEL); - if (!bpmp->tx_channel) { - err = -ENOMEM; - goto free_rx; - } + if (!bpmp->tx_channel) + return -ENOMEM; bpmp->rx_channel = devm_kzalloc(&pdev->dev, sizeof(*bpmp->rx_channel), GFP_KERNEL); - if (!bpmp->rx_channel) { - err = -ENOMEM; - goto free_rx; - } + if (!bpmp->rx_channel) + return -ENOMEM; bpmp->threaded_channels = devm_kcalloc(&pdev->dev, bpmp->threaded.count, sizeof(*bpmp->threaded_channels), GFP_KERNEL); - if (!bpmp->threaded_channels) { - err = -ENOMEM; - goto free_rx; - } + if (!bpmp->threaded_channels) + return -ENOMEM; - err = tegra_bpmp_channel_init(bpmp->tx_channel, bpmp, - bpmp->soc->channels.cpu_tx.offset); + err = bpmp->soc->ops->init(bpmp); if (err < 0) - goto free_rx; - - err = tegra_bpmp_channel_init(bpmp->rx_channel, bpmp, - bpmp->soc->channels.cpu_rx.offset); - if (err < 0) - goto cleanup_tx_channel; - - for (i = 0; i < bpmp->threaded.count; i++) { - err = tegra_bpmp_channel_init( - &bpmp->threaded_channels[i], bpmp, - bpmp->soc->channels.thread.offset + i); - if (err < 0) - goto cleanup_threaded_channels; - } - - /* mbox registration */ - bpmp->mbox.client.dev = &pdev->dev; - bpmp->mbox.client.rx_callback = tegra_bpmp_handle_rx; - bpmp->mbox.client.tx_block = false; - bpmp->mbox.client.knows_txdone = false; - - bpmp->mbox.channel = mbox_request_channel(&bpmp->mbox.client, 0); - if (IS_ERR(bpmp->mbox.channel)) { - err = PTR_ERR(bpmp->mbox.channel); - dev_err(&pdev->dev, "failed to get HSP mailbox: %d\n", err); - goto cleanup_threaded_channels; - } - - /* reset message channels */ - tegra_bpmp_channel_reset(bpmp->tx_channel); - tegra_bpmp_channel_reset(bpmp->rx_channel); - for (i = 0; i < bpmp->threaded.count; i++) - tegra_bpmp_channel_reset(&bpmp->threaded_channels[i]); + return err; err = tegra_bpmp_request_mrq(bpmp, MRQ_PING, tegra_bpmp_mrq_handle_ping, bpmp); if (err < 0) - goto free_mbox; + goto deinit; err = tegra_bpmp_ping(bpmp); if (err < 0) { @@ -920,37 +788,21 @@ static int tegra_bpmp_probe(struct platform_device *pdev) free_mrq: tegra_bpmp_free_mrq(bpmp, MRQ_PING, bpmp); -free_mbox: - mbox_free_channel(bpmp->mbox.channel); -cleanup_threaded_channels: - for (i = 0; i < bpmp->threaded.count; i++) { - if (bpmp->threaded_channels[i].bpmp) - tegra_bpmp_channel_cleanup(&bpmp->threaded_channels[i]); - } +deinit: + if (bpmp->soc->ops->deinit) + bpmp->soc->ops->deinit(bpmp); - tegra_bpmp_channel_cleanup(bpmp->rx_channel); -cleanup_tx_channel: - tegra_bpmp_channel_cleanup(bpmp->tx_channel); -free_rx: - gen_pool_free(bpmp->rx.pool, (unsigned long)bpmp->rx.virt, 4096); -free_tx: - gen_pool_free(bpmp->tx.pool, (unsigned long)bpmp->tx.virt, 4096); return err; } static int __maybe_unused tegra_bpmp_resume(struct device *dev) { struct tegra_bpmp *bpmp = dev_get_drvdata(dev); - unsigned int i; - /* reset message channels */ - tegra_bpmp_channel_reset(bpmp->tx_channel); - tegra_bpmp_channel_reset(bpmp->rx_channel); - - for (i = 0; i < bpmp->threaded.count; i++) - tegra_bpmp_channel_reset(&bpmp->threaded_channels[i]); - - return 0; + if (bpmp->soc->ops->resume) + return bpmp->soc->ops->resume(bpmp); + else + return 0; } static SIMPLE_DEV_PM_OPS(tegra_bpmp_pm_ops, NULL, tegra_bpmp_resume); @@ -971,6 +823,7 @@ static const struct tegra_bpmp_soc tegra186_soc = { .timeout = 0, }, }, + .ops = &tegra186_bpmp_ops, .num_resets = 193, }; diff --git a/include/soc/tegra/bpmp.h b/include/soc/tegra/bpmp.h index b02f926a0216..bdd1bd107aba 100644 --- a/include/soc/tegra/bpmp.h +++ b/include/soc/tegra/bpmp.h @@ -23,6 +23,7 @@ #include struct tegra_bpmp_clk; +struct tegra_bpmp_ops; struct tegra_bpmp_soc { struct { @@ -32,6 +33,8 @@ struct tegra_bpmp_soc { unsigned int timeout; } cpu_tx, thread, cpu_rx; } channels; + + const struct tegra_bpmp_ops *ops; unsigned int num_resets; }; @@ -63,12 +66,7 @@ struct tegra_bpmp_mrq { struct tegra_bpmp { const struct tegra_bpmp_soc *soc; struct device *dev; - - struct { - struct gen_pool *pool; - dma_addr_t phys; - void *virt; - } tx, rx; + void *priv; struct { struct mbox_client client; @@ -173,6 +171,8 @@ static inline bool tegra_bpmp_mrq_is_supported(struct tegra_bpmp *bpmp, } #endif +void tegra_bpmp_handle_rx(struct tegra_bpmp *bpmp); + #if IS_ENABLED(CONFIG_CLK_TEGRA_BPMP) int tegra_bpmp_init_clocks(struct tegra_bpmp *bpmp); #else From 139251fc220830cc49b71331d281a8ad03a08ab7 Mon Sep 17 00:00:00 2001 From: Timo Alho Date: Thu, 24 Jan 2019 19:03:54 +0200 Subject: [PATCH 037/113] firmware: tegra: add bpmp driver for Tegra210 This patch adds driver for Tegra210 BPMP firmware. The BPMP is a specific processor in Tegra210 chip, which runs firmware for assisting in entering deep low power states (suspend to ram), and offloading DRAM memory clock scaling on some platforms. Based on work by Sivaram Nair Signed-off-by: Timo Alho Acked-by: Jon Hunter Signed-off-by: Thierry Reding --- drivers/firmware/tegra/Makefile | 1 + drivers/firmware/tegra/bpmp-private.h | 1 + drivers/firmware/tegra/bpmp-tegra210.c | 243 +++++++++++++++++++++++++ drivers/firmware/tegra/bpmp.c | 46 ++++- include/soc/tegra/bpmp.h | 1 + 5 files changed, 283 insertions(+), 9 deletions(-) create mode 100644 drivers/firmware/tegra/bpmp-tegra210.c diff --git a/drivers/firmware/tegra/Makefile b/drivers/firmware/tegra/Makefile index 559355674bca..ba45e58f7647 100644 --- a/drivers/firmware/tegra/Makefile +++ b/drivers/firmware/tegra/Makefile @@ -1,4 +1,5 @@ tegra-bpmp-y = bpmp.o +tegra-bpmp-$(CONFIG_ARCH_TEGRA_210_SOC) += bpmp-tegra210.o tegra-bpmp-$(CONFIG_ARCH_TEGRA_186_SOC) += bpmp-tegra186.o tegra-bpmp-$(CONFIG_DEBUG_FS) += bpmp-debugfs.o obj-$(CONFIG_TEGRA_BPMP) += tegra-bpmp.o diff --git a/drivers/firmware/tegra/bpmp-private.h b/drivers/firmware/tegra/bpmp-private.h index 6fb10f1cfb8f..07c3d46abb87 100644 --- a/drivers/firmware/tegra/bpmp-private.h +++ b/drivers/firmware/tegra/bpmp-private.h @@ -24,5 +24,6 @@ struct tegra_bpmp_ops { }; extern const struct tegra_bpmp_ops tegra186_bpmp_ops; +extern const struct tegra_bpmp_ops tegra210_bpmp_ops; #endif diff --git a/drivers/firmware/tegra/bpmp-tegra210.c b/drivers/firmware/tegra/bpmp-tegra210.c new file mode 100644 index 000000000000..ae15940a078e --- /dev/null +++ b/drivers/firmware/tegra/bpmp-tegra210.c @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2018, NVIDIA CORPORATION. + */ + +#include +#include +#include +#include +#include + +#include + +#include "bpmp-private.h" + +#define TRIGGER_OFFSET 0x000 +#define RESULT_OFFSET(id) (0xc00 + id * 4) +#define TRIGGER_ID_SHIFT 16 +#define TRIGGER_CMD_GET 4 + +#define STA_OFFSET 0 +#define SET_OFFSET 4 +#define CLR_OFFSET 8 + +#define CH_MASK(ch) (0x3 << ((ch) * 2)) +#define SL_SIGL(ch) (0x0 << ((ch) * 2)) +#define SL_QUED(ch) (0x1 << ((ch) * 2)) +#define MA_FREE(ch) (0x2 << ((ch) * 2)) +#define MA_ACKD(ch) (0x3 << ((ch) * 2)) + +struct tegra210_bpmp { + void __iomem *atomics; + void __iomem *arb_sema; + struct irq_data *tx_irq_data; +}; + +static u32 bpmp_channel_status(struct tegra_bpmp *bpmp, unsigned int index) +{ + struct tegra210_bpmp *priv = bpmp->priv; + + return __raw_readl(priv->arb_sema + STA_OFFSET) & CH_MASK(index); +} + +static bool tegra210_bpmp_is_response_ready(struct tegra_bpmp_channel *channel) +{ + unsigned int index = channel->index; + + return bpmp_channel_status(channel->bpmp, index) == MA_ACKD(index); +} + +static bool tegra210_bpmp_is_request_ready(struct tegra_bpmp_channel *channel) +{ + unsigned int index = channel->index; + + return bpmp_channel_status(channel->bpmp, index) == SL_SIGL(index); +} + +static bool +tegra210_bpmp_is_request_channel_free(struct tegra_bpmp_channel *channel) +{ + unsigned int index = channel->index; + + return bpmp_channel_status(channel->bpmp, index) == MA_FREE(index); +} + +static bool +tegra210_bpmp_is_response_channel_free(struct tegra_bpmp_channel *channel) +{ + unsigned int index = channel->index; + + return bpmp_channel_status(channel->bpmp, index) == SL_QUED(index); +} + +static int tegra210_bpmp_post_request(struct tegra_bpmp_channel *channel) +{ + struct tegra210_bpmp *priv = channel->bpmp->priv; + + __raw_writel(CH_MASK(channel->index), priv->arb_sema + CLR_OFFSET); + + return 0; +} + +static int tegra210_bpmp_post_response(struct tegra_bpmp_channel *channel) +{ + struct tegra210_bpmp *priv = channel->bpmp->priv; + + __raw_writel(MA_ACKD(channel->index), priv->arb_sema + SET_OFFSET); + + return 0; +} + +static int tegra210_bpmp_ack_response(struct tegra_bpmp_channel *channel) +{ + struct tegra210_bpmp *priv = channel->bpmp->priv; + + __raw_writel(MA_ACKD(channel->index) ^ MA_FREE(channel->index), + priv->arb_sema + CLR_OFFSET); + + return 0; +} + +static int tegra210_bpmp_ack_request(struct tegra_bpmp_channel *channel) +{ + struct tegra210_bpmp *priv = channel->bpmp->priv; + + __raw_writel(SL_QUED(channel->index), priv->arb_sema + SET_OFFSET); + + return 0; +} + +static int tegra210_bpmp_ring_doorbell(struct tegra_bpmp *bpmp) +{ + struct tegra210_bpmp *priv = bpmp->priv; + struct irq_data *irq_data = priv->tx_irq_data; + + /* + * Tegra Legacy Interrupt Controller (LIC) is used to notify BPMP of + * available messages + */ + if (irq_data->chip->irq_retrigger) + return irq_data->chip->irq_retrigger(irq_data); + + return -EINVAL; +} + +static irqreturn_t rx_irq(int irq, void *data) +{ + struct tegra_bpmp *bpmp = data; + + tegra_bpmp_handle_rx(bpmp); + + return IRQ_HANDLED; +} + +static int tegra210_bpmp_channel_init(struct tegra_bpmp_channel *channel, + struct tegra_bpmp *bpmp, + unsigned int index) +{ + struct tegra210_bpmp *priv = bpmp->priv; + u32 address; + void *p; + + /* Retrieve channel base address from BPMP */ + writel(index << TRIGGER_ID_SHIFT | TRIGGER_CMD_GET, + priv->atomics + TRIGGER_OFFSET); + address = readl(priv->atomics + RESULT_OFFSET(index)); + + p = devm_ioremap(bpmp->dev, address, 0x80); + if (!p) + return -ENOMEM; + + channel->ib = p; + channel->ob = p; + channel->index = index; + init_completion(&channel->completion); + channel->bpmp = bpmp; + + return 0; +} + +static int tegra210_bpmp_init(struct tegra_bpmp *bpmp) +{ + struct platform_device *pdev = to_platform_device(bpmp->dev); + struct tegra210_bpmp *priv; + struct resource *res; + unsigned int i; + int err; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + bpmp->priv = priv; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->atomics = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(priv->atomics)) + return PTR_ERR(priv->atomics); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + priv->arb_sema = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(priv->arb_sema)) + return PTR_ERR(priv->arb_sema); + + err = tegra210_bpmp_channel_init(bpmp->tx_channel, bpmp, + bpmp->soc->channels.cpu_tx.offset); + if (err < 0) + return err; + + err = tegra210_bpmp_channel_init(bpmp->rx_channel, bpmp, + bpmp->soc->channels.cpu_rx.offset); + if (err < 0) + return err; + + for (i = 0; i < bpmp->threaded.count; i++) { + unsigned int index = bpmp->soc->channels.thread.offset + i; + + err = tegra210_bpmp_channel_init(&bpmp->threaded_channels[i], + bpmp, index); + if (err < 0) + return err; + } + + err = platform_get_irq_byname(pdev, "tx"); + if (err < 0) { + dev_err(&pdev->dev, "failed to get TX IRQ: %d\n", err); + return err; + } + + priv->tx_irq_data = irq_get_irq_data(err); + if (!priv->tx_irq_data) { + dev_err(&pdev->dev, "failed to get IRQ data for TX IRQ\n"); + return err; + } + + err = platform_get_irq_byname(pdev, "rx"); + if (err < 0) { + dev_err(&pdev->dev, "failed to get rx IRQ: %d\n", err); + return err; + } + + err = devm_request_irq(&pdev->dev, err, rx_irq, + IRQF_NO_SUSPEND, dev_name(&pdev->dev), bpmp); + if (err < 0) { + dev_err(&pdev->dev, "failed to request IRQ: %d\n", err); + return err; + } + + return 0; +} + +const struct tegra_bpmp_ops tegra210_bpmp_ops = { + .init = tegra210_bpmp_init, + .is_response_ready = tegra210_bpmp_is_response_ready, + .is_request_ready = tegra210_bpmp_is_request_ready, + .ack_response = tegra210_bpmp_ack_response, + .ack_request = tegra210_bpmp_ack_request, + .is_response_channel_free = tegra210_bpmp_is_response_channel_free, + .is_request_channel_free = tegra210_bpmp_is_request_channel_free, + .post_response = tegra210_bpmp_post_response, + .post_request = tegra210_bpmp_post_request, + .ring_doorbell = tegra210_bpmp_ring_doorbell, +}; diff --git a/drivers/firmware/tegra/bpmp.c b/drivers/firmware/tegra/bpmp.c index 2b498deacdbc..8e3f79959d48 100644 --- a/drivers/firmware/tegra/bpmp.c +++ b/drivers/firmware/tegra/bpmp.c @@ -768,17 +768,23 @@ static int tegra_bpmp_probe(struct platform_device *pdev) if (err < 0) goto free_mrq; - err = tegra_bpmp_init_clocks(bpmp); - if (err < 0) - goto free_mrq; + if (of_find_property(pdev->dev.of_node, "#clock-cells", NULL)) { + err = tegra_bpmp_init_clocks(bpmp); + if (err < 0) + goto free_mrq; + } - err = tegra_bpmp_init_resets(bpmp); - if (err < 0) - goto free_mrq; + if (of_find_property(pdev->dev.of_node, "#reset-cells", NULL)) { + err = tegra_bpmp_init_resets(bpmp); + if (err < 0) + goto free_mrq; + } - err = tegra_bpmp_init_powergates(bpmp); - if (err < 0) - goto free_mrq; + if (of_find_property(pdev->dev.of_node, "#power-domain-cells", NULL)) { + err = tegra_bpmp_init_powergates(bpmp); + if (err < 0) + goto free_mrq; + } err = tegra_bpmp_init_debugfs(bpmp); if (err < 0) @@ -827,8 +833,30 @@ static const struct tegra_bpmp_soc tegra186_soc = { .num_resets = 193, }; +static const struct tegra_bpmp_soc tegra210_soc = { + .channels = { + .cpu_tx = { + .offset = 0, + .count = 1, + .timeout = 60 * USEC_PER_SEC, + }, + .thread = { + .offset = 4, + .count = 1, + .timeout = 600 * USEC_PER_SEC, + }, + .cpu_rx = { + .offset = 8, + .count = 1, + .timeout = 0, + }, + }, + .ops = &tegra210_bpmp_ops, +}; + static const struct of_device_id tegra_bpmp_match[] = { { .compatible = "nvidia,tegra186-bpmp", .data = &tegra186_soc }, + { .compatible = "nvidia,tegra210-bpmp", .data = &tegra210_soc }, { } }; diff --git a/include/soc/tegra/bpmp.h b/include/soc/tegra/bpmp.h index bdd1bd107aba..45960aa08f4a 100644 --- a/include/soc/tegra/bpmp.h +++ b/include/soc/tegra/bpmp.h @@ -50,6 +50,7 @@ struct tegra_bpmp_channel { struct tegra_bpmp_mb_data *ob; struct completion completion; struct tegra_ivc *ivc; + unsigned int index; }; typedef void (*tegra_bpmp_mrq_handler_t)(unsigned int mrq, From e835d442cff641a0831b090a47457366d7919324 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 25 Jan 2019 15:46:56 +0100 Subject: [PATCH 038/113] firmware: tegra: bpmp-tegra186: Remove unused includes Many of the include files are not needed, so drop them. Signed-off-by: Thierry Reding --- drivers/firmware/tegra/bpmp-tegra186.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/drivers/firmware/tegra/bpmp-tegra186.c b/drivers/firmware/tegra/bpmp-tegra186.c index 89bb32b97345..ea308751635f 100644 --- a/drivers/firmware/tegra/bpmp-tegra186.c +++ b/drivers/firmware/tegra/bpmp-tegra186.c @@ -3,14 +3,9 @@ * Copyright (c) 2018, NVIDIA CORPORATION. */ -#include #include #include -#include -#include -#include #include -#include #include #include From 589997a157df82fa04fec63ef4d01e73545fd415 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 25 Jan 2019 11:22:52 +0100 Subject: [PATCH 039/113] 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(-) 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 @@ static int tegra_powergate_power_up(struct tegra_powergate *pg, 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 @@ static int tegra_powergate_power_down(struct tegra_powergate *pg) 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 @@ static int tegra_powergate_of_get_clks(struct tegra_powergate *pg, 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 @@ int tegra_io_pad_power_disable(enum tegra_io_pad id) } 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 @@ static void tegra_pmc_init_tsense_reset(struct tegra_pmc *pmc) 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 @@ static int tegra_pmc_probe(struct platform_device *pdev) #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; } From f1d912996fc054f1d7059b23371cd623692724ce Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 25 Jan 2019 11:22:53 +0100 Subject: [PATCH 040/113] 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(-) 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); } } From fa3bc04ef8ccccfe47db1f4030a7a43569956402 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 25 Jan 2019 11:22:54 +0100 Subject: [PATCH 041/113] 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(-) 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, From e247deae1a55089cb04cc25c91faeba083d0c39c Mon Sep 17 00:00:00 2001 From: Mikko Perttunen Date: Fri, 25 Jan 2019 11:22:55 +0100 Subject: [PATCH 042/113] 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(-) 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); /* From 4cab5bf616f6513b2111ce80f634a9a07c037a0a Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Mon, 21 Jan 2019 18:10:42 -0800 Subject: [PATCH 043/113] dt-bindings: reset: imx7: Document usage on i.MX8MQ SoCs The driver now supports i.MX8MQ, so update bindings accordingly. Cc: p.zabel@pengutronix.de Cc: Fabio Estevam Cc: cphealy@gmail.com Cc: l.stach@pengutronix.de Cc: Leonard Crestez Cc: "A.s. Dong" Cc: Richard Zhu Cc: linux-imx@nxp.com Cc: linux-arm-kernel@lists.infradead.org Cc: linux-kernel@vger.kernel.org Reviewed-by: Rob Herring Signed-off-by: Andrey Smirnov Signed-off-by: Philipp Zabel --- .../bindings/reset/fsl,imx7-src.txt | 7 +- include/dt-bindings/reset/imx8mq-reset.h | 64 +++++++++++++++++++ 2 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 include/dt-bindings/reset/imx8mq-reset.h diff --git a/Documentation/devicetree/bindings/reset/fsl,imx7-src.txt b/Documentation/devicetree/bindings/reset/fsl,imx7-src.txt index 1ab1d109318e..2ecf33815d18 100644 --- a/Documentation/devicetree/bindings/reset/fsl,imx7-src.txt +++ b/Documentation/devicetree/bindings/reset/fsl,imx7-src.txt @@ -5,7 +5,9 @@ Please also refer to reset.txt in this directory for common reset controller binding usage. Required properties: -- compatible: Should be "fsl,imx7d-src", "syscon" +- compatible: + - For i.MX7 SoCs should be "fsl,imx7d-src", "syscon" + - For i.MX8MQ SoCs should be "fsl,imx8mq-src", "syscon" - reg: should be register base and length as documented in the datasheet - interrupts: Should contain SRC interrupt @@ -44,4 +46,5 @@ Example: For list of all valid reset indicies see - + for i.MX7 and + for i.MX8MQ diff --git a/include/dt-bindings/reset/imx8mq-reset.h b/include/dt-bindings/reset/imx8mq-reset.h new file mode 100644 index 000000000000..57c592498aa0 --- /dev/null +++ b/include/dt-bindings/reset/imx8mq-reset.h @@ -0,0 +1,64 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2018 Zodiac Inflight Innovations + * + * Author: Andrey Smirnov + */ + +#ifndef DT_BINDING_RESET_IMX8MQ_H +#define DT_BINDING_RESET_IMX8MQ_H + +#define IMX8MQ_RESET_A53_CORE_POR_RESET0 0 +#define IMX8MQ_RESET_A53_CORE_POR_RESET1 1 +#define IMX8MQ_RESET_A53_CORE_POR_RESET2 2 +#define IMX8MQ_RESET_A53_CORE_POR_RESET3 3 +#define IMX8MQ_RESET_A53_CORE_RESET0 4 +#define IMX8MQ_RESET_A53_CORE_RESET1 5 +#define IMX8MQ_RESET_A53_CORE_RESET2 6 +#define IMX8MQ_RESET_A53_CORE_RESET3 7 +#define IMX8MQ_RESET_A53_DBG_RESET0 8 +#define IMX8MQ_RESET_A53_DBG_RESET1 9 +#define IMX8MQ_RESET_A53_DBG_RESET2 10 +#define IMX8MQ_RESET_A53_DBG_RESET3 11 +#define IMX8MQ_RESET_A53_ETM_RESET0 12 +#define IMX8MQ_RESET_A53_ETM_RESET1 13 +#define IMX8MQ_RESET_A53_ETM_RESET2 14 +#define IMX8MQ_RESET_A53_ETM_RESET3 15 +#define IMX8MQ_RESET_A53_SOC_DBG_RESET 16 +#define IMX8MQ_RESET_A53_L2RESET 17 +#define IMX8MQ_RESET_SW_NON_SCLR_M4C_RST 18 +#define IMX8MQ_RESET_OTG1_PHY_RESET 19 +#define IMX8MQ_RESET_OTG2_PHY_RESET 20 +#define IMX8MQ_RESET_MIPI_DSI_RESET_BYTE_N 21 +#define IMX8MQ_RESET_MIPI_DSI_RESET_N 22 +#define IMX8MQ_RESET_MIPI_DIS_DPI_RESET_N 23 +#define IMX8MQ_RESET_MIPI_DIS_ESC_RESET_N 24 +#define IMX8MQ_RESET_MIPI_DIS_PCLK_RESET_N 25 +#define IMX8MQ_RESET_PCIEPHY 26 +#define IMX8MQ_RESET_PCIEPHY_PERST 27 +#define IMX8MQ_RESET_PCIE_CTRL_APPS_EN 28 +#define IMX8MQ_RESET_PCIE_CTRL_APPS_TURNOFF 29 +#define IMX8MQ_RESET_HDMI_PHY_APB_RESET 30 +#define IMX8MQ_RESET_DISP_RESET 31 +#define IMX8MQ_RESET_GPU_RESET 32 +#define IMX8MQ_RESET_VPU_RESET 33 +#define IMX8MQ_RESET_PCIEPHY2 34 +#define IMX8MQ_RESET_PCIEPHY2_PERST 35 +#define IMX8MQ_RESET_PCIE2_CTRL_APPS_EN 36 +#define IMX8MQ_RESET_PCIE2_CTRL_APPS_TURNOFF 37 +#define IMX8MQ_RESET_MIPI_CSI1_CORE_RESET 38 +#define IMX8MQ_RESET_MIPI_CSI1_PHY_REF_RESET 39 +#define IMX8MQ_RESET_MIPI_CSI1_ESC_RESET 40 +#define IMX8MQ_RESET_MIPI_CSI2_CORE_RESET 41 +#define IMX8MQ_RESET_MIPI_CSI2_PHY_REF_RESET 42 +#define IMX8MQ_RESET_MIPI_CSI2_ESC_RESET 43 +#define IMX8MQ_RESET_DDRC1_PRST 44 +#define IMX8MQ_RESET_DDRC1_CORE_RESET 45 +#define IMX8MQ_RESET_DDRC1_PHY_RESET 46 +#define IMX8MQ_RESET_DDRC2_PRST 47 +#define IMX8MQ_RESET_DDRC2_CORE_RESET 48 +#define IMX8MQ_RESET_DDRC2_PHY_RESET 49 + +#define IMX8MQ_RESET_NUM 50 + +#endif From 37c7393962441a968428dd0d0b11dfb50917f4c3 Mon Sep 17 00:00:00 2001 From: Philipp Zabel Date: Thu, 13 Dec 2018 12:26:54 +0100 Subject: [PATCH 044/113] MAINTAINERS: use include/linux/reset for reset controller related headers The include/linux/reset directory currently contains one header with helper functions for Broadcom BCM63xx PMB, which can control reset lines to on-chip peripherals. Even though that driver doesn't use the reset controller framework, the the directory can be shared with other reset controller drivers that do. Acked-by: Florian Fainelli Signed-off-by: Philipp Zabel --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index 4d04cebb4a71..19b64726b762 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12957,6 +12957,7 @@ F: drivers/reset/ F: Documentation/devicetree/bindings/reset/ F: include/dt-bindings/reset/ F: include/linux/reset.h +F: include/linux/reset/ F: include/linux/reset-controller.h RESTARTABLE SEQUENCES SUPPORT From fdce60787f6215607dc7ac910cbaf4416684b589 Mon Sep 17 00:00:00 2001 From: Philipp Zabel Date: Thu, 13 Dec 2018 12:22:32 +0100 Subject: [PATCH 045/113] reset: sunxi: declare sun6i_reset_init in a header file Avoid declaring extern functions in c files. To make sure function definition and usage don't get out of sync, declare sun6i_reset_init in a common header. Suggested-by: Stephen Rothwell Signed-off-by: Philipp Zabel --- arch/arm/mach-sunxi/sunxi.c | 2 +- drivers/reset/reset-sunxi.c | 1 + include/linux/reset/sunxi.h | 7 +++++++ 3 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 include/linux/reset/sunxi.h diff --git a/arch/arm/mach-sunxi/sunxi.c b/arch/arm/mach-sunxi/sunxi.c index 8a7f301839c2..933b6930f024 100644 --- a/arch/arm/mach-sunxi/sunxi.c +++ b/arch/arm/mach-sunxi/sunxi.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -37,7 +38,6 @@ static const char * const sun6i_board_dt_compat[] = { NULL, }; -extern void __init sun6i_reset_init(void); static void __init sun6i_timer_init(void) { of_clk_init(NULL); diff --git a/drivers/reset/reset-sunxi.c b/drivers/reset/reset-sunxi.c index db9a1a75523f..b06d724d8f21 100644 --- a/drivers/reset/reset-sunxi.c +++ b/drivers/reset/reset-sunxi.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include diff --git a/include/linux/reset/sunxi.h b/include/linux/reset/sunxi.h new file mode 100644 index 000000000000..1ad7fffb413e --- /dev/null +++ b/include/linux/reset/sunxi.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __LINUX_RESET_SUNXI_H__ +#define __LINUX_RESET_SUNXI_H__ + +void __init sun6i_reset_init(void); + +#endif /* __LINUX_RESET_SUNXI_H__ */ From cdbeb315ed8dcc142a68054899cedd6e4f1fea3f Mon Sep 17 00:00:00 2001 From: Philipp Zabel Date: Thu, 13 Dec 2018 12:24:36 +0100 Subject: [PATCH 046/113] reset: socfpga: declare socfpga_reset_init in a header file Avoid declaring extern functions in c files. To make sure function definition and usage don't get out of sync, declare socfpga_reset_init in a common header. Suggested-by: Stephen Rothwell Signed-off-by: Philipp Zabel Acked-by: Dinh Nguyen --- arch/arm/mach-socfpga/socfpga.c | 3 +-- drivers/reset/reset-socfpga.c | 2 +- include/linux/reset/socfpga.h | 7 +++++++ 3 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 include/linux/reset/socfpga.h diff --git a/arch/arm/mach-socfpga/socfpga.c b/arch/arm/mach-socfpga/socfpga.c index afd98971d903..816da0eb6616 100644 --- a/arch/arm/mach-socfpga/socfpga.c +++ b/arch/arm/mach-socfpga/socfpga.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -32,8 +33,6 @@ void __iomem *rst_manager_base_addr; void __iomem *sdr_ctl_base_addr; unsigned long socfpga_cpu1start_addr; -extern void __init socfpga_reset_init(void); - static void __init socfpga_sysmgr_init(void) { struct device_node *np; diff --git a/drivers/reset/reset-socfpga.c b/drivers/reset/reset-socfpga.c index 318cfc51c441..96953992c2bb 100644 --- a/drivers/reset/reset-socfpga.c +++ b/drivers/reset/reset-socfpga.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -18,7 +19,6 @@ #include "reset-simple.h" #define SOCFPGA_NR_BANKS 8 -void __init socfpga_reset_init(void); static int a10_reset_init(struct device_node *np) { diff --git a/include/linux/reset/socfpga.h b/include/linux/reset/socfpga.h new file mode 100644 index 000000000000..b11a2047c342 --- /dev/null +++ b/include/linux/reset/socfpga.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __LINUX_RESET_SOCFPGA_H__ +#define __LINUX_RESET_SOCFPGA_H__ + +void __init socfpga_reset_init(void); + +#endif /* __LINUX_RESET_SOCFPGA_H__ */ From 0807caf647dd150cbc3f3efaff7b6a9136aa7e6f Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Tue, 15 Jan 2019 10:44:05 -0800 Subject: [PATCH 047/113] dt-bindings: reset: Add document for Broadcom STB reset controller Add a binding document for the Broadcom STB reset controller, also known as SW_INIT-style reset controller. Signed-off-by: Florian Fainelli Reviewed-by: Rob Herring Signed-off-by: Philipp Zabel --- .../bindings/reset/brcm,brcmstb-reset.txt | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 Documentation/devicetree/bindings/reset/brcm,brcmstb-reset.txt diff --git a/Documentation/devicetree/bindings/reset/brcm,brcmstb-reset.txt b/Documentation/devicetree/bindings/reset/brcm,brcmstb-reset.txt new file mode 100644 index 000000000000..6e5341b4f891 --- /dev/null +++ b/Documentation/devicetree/bindings/reset/brcm,brcmstb-reset.txt @@ -0,0 +1,27 @@ +Broadcom STB SW_INIT-style reset controller +=========================================== + +Broadcom STB SoCs have a SW_INIT-style reset controller with separate +SET/CLEAR/STATUS registers and possibly multiple banks, each of 32 bit +reset lines. + +Please also refer to reset.txt in this directory for common reset +controller binding usage. + +Required properties: +- compatible: should be brcm,brcmstb-reset +- reg: register base and length +- #reset-cells: must be set to 1 + +Example: + + reset: reset-controller@8404318 { + compatible = "brcm,brcmstb-reset"; + reg = <0x8404318 0x30>; + #reset-cells = <1>; + }; + + ðernet_switch { + resets = <&reset>; + reset-names = "switch"; + }; From 77750bc089e4508ff62f036e6e6626cf8d7384cb Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Wed, 23 Jan 2019 14:54:36 -0800 Subject: [PATCH 048/113] reset: Add Broadcom STB SW_INIT reset controller driver Add support for resetting blocks through the Linux reset controller subsystem when reset lines are provided through a SW_INIT-style reset controller on Broadcom STB SoCs. Signed-off-by: Florian Fainelli Signed-off-by: Philipp Zabel --- drivers/reset/Kconfig | 8 +++ drivers/reset/Makefile | 1 + drivers/reset/reset-brcmstb.c | 132 ++++++++++++++++++++++++++++++++++ 3 files changed, 141 insertions(+) create mode 100644 drivers/reset/reset-brcmstb.c diff --git a/drivers/reset/Kconfig b/drivers/reset/Kconfig index 2e01bd833ffd..d9a02b7f90cf 100644 --- a/drivers/reset/Kconfig +++ b/drivers/reset/Kconfig @@ -40,6 +40,14 @@ config RESET_BERLIN help This enables the reset controller driver for Marvell Berlin SoCs. +config RESET_BRCMSTB + tristate "Broadcom STB reset controller" + depends on ARCH_BRCMSTB || COMPILE_TEST + default ARCH_BRCMSTB + help + This enables the reset controller driver for Broadcom STB SoCs using + a SUN_TOP_CTRL_SW_INIT style controller. + config RESET_HSDK bool "Synopsys HSDK Reset Driver" depends on HAS_IOMEM diff --git a/drivers/reset/Makefile b/drivers/reset/Makefile index dc7874df78d9..7395db2cb1dd 100644 --- a/drivers/reset/Makefile +++ b/drivers/reset/Makefile @@ -7,6 +7,7 @@ obj-$(CONFIG_RESET_A10SR) += reset-a10sr.o obj-$(CONFIG_RESET_ATH79) += reset-ath79.o obj-$(CONFIG_RESET_AXS10X) += reset-axs10x.o obj-$(CONFIG_RESET_BERLIN) += reset-berlin.o +obj-$(CONFIG_RESET_BRCMSTB) += reset-brcmstb.o obj-$(CONFIG_RESET_HSDK) += reset-hsdk.o obj-$(CONFIG_RESET_IMX7) += reset-imx7.o obj-$(CONFIG_RESET_LANTIQ) += reset-lantiq.o diff --git a/drivers/reset/reset-brcmstb.c b/drivers/reset/reset-brcmstb.c new file mode 100644 index 000000000000..a608f445dad6 --- /dev/null +++ b/drivers/reset/reset-brcmstb.c @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Broadcom STB generic reset controller for SW_INIT style reset controller + * + * Author: Florian Fainelli + * Copyright (C) 2018 Broadcom + */ +#include +#include +#include +#include +#include +#include +#include +#include + +struct brcmstb_reset { + void __iomem *base; + struct reset_controller_dev rcdev; +}; + +#define SW_INIT_SET 0x00 +#define SW_INIT_CLEAR 0x04 +#define SW_INIT_STATUS 0x08 + +#define SW_INIT_BIT(id) BIT((id) & 0x1f) +#define SW_INIT_BANK(id) ((id) >> 5) + +/* A full bank contains extra registers that we are not utilizing but still + * qualify as a single bank. + */ +#define SW_INIT_BANK_SIZE 0x18 + +static inline +struct brcmstb_reset *to_brcmstb(struct reset_controller_dev *rcdev) +{ + return container_of(rcdev, struct brcmstb_reset, rcdev); +} + +static int brcmstb_reset_assert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + unsigned int off = SW_INIT_BANK(id) * SW_INIT_BANK_SIZE; + struct brcmstb_reset *priv = to_brcmstb(rcdev); + + writel_relaxed(SW_INIT_BIT(id), priv->base + off + SW_INIT_SET); + + return 0; +} + +static int brcmstb_reset_deassert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + unsigned int off = SW_INIT_BANK(id) * SW_INIT_BANK_SIZE; + struct brcmstb_reset *priv = to_brcmstb(rcdev); + + writel_relaxed(SW_INIT_BIT(id), priv->base + off + SW_INIT_CLEAR); + /* Maximum reset delay after de-asserting a line and seeing block + * operation is typically 14us for the worst case, build some slack + * here. + */ + usleep_range(100, 200); + + return 0; +} + +static int brcmstb_reset_status(struct reset_controller_dev *rcdev, + unsigned long id) +{ + unsigned int off = SW_INIT_BANK(id) * SW_INIT_BANK_SIZE; + struct brcmstb_reset *priv = to_brcmstb(rcdev); + + return readl_relaxed(priv->base + off + SW_INIT_STATUS) & + SW_INIT_BIT(id); +} + +static const struct reset_control_ops brcmstb_reset_ops = { + .assert = brcmstb_reset_assert, + .deassert = brcmstb_reset_deassert, + .status = brcmstb_reset_status, +}; + +static int brcmstb_reset_probe(struct platform_device *pdev) +{ + struct device *kdev = &pdev->dev; + struct brcmstb_reset *priv; + struct resource *res; + + priv = devm_kzalloc(kdev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!IS_ALIGNED(res->start, SW_INIT_BANK_SIZE) || + !IS_ALIGNED(resource_size(res), SW_INIT_BANK_SIZE)) { + dev_err(kdev, "incorrect register range\n"); + return -EINVAL; + } + + priv->base = devm_ioremap_resource(kdev, res); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + dev_set_drvdata(kdev, priv); + + priv->rcdev.owner = THIS_MODULE; + priv->rcdev.nr_resets = DIV_ROUND_DOWN_ULL(resource_size(res), + SW_INIT_BANK_SIZE) * 32; + priv->rcdev.ops = &brcmstb_reset_ops; + priv->rcdev.of_node = kdev->of_node; + /* Use defaults: 1 cell and simple xlate function */ + + return devm_reset_controller_register(kdev, &priv->rcdev); +} + +static const struct of_device_id brcmstb_reset_of_match[] = { + { .compatible = "brcm,brcmstb-reset" }, + { /* sentinel */ } +}; + +static struct platform_driver brcmstb_reset_driver = { + .probe = brcmstb_reset_probe, + .driver = { + .name = "brcmstb-reset", + .of_match_table = brcmstb_reset_of_match, + }, +}; +module_platform_driver(brcmstb_reset_driver); + +MODULE_AUTHOR("Broadcom"); +MODULE_DESCRIPTION("Broadcom STB reset controller"); +MODULE_LICENSE("GPL"); From 1059035853ae4d65b138acdb1cd15d963e6aa776 Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Mon, 21 Jan 2019 18:10:41 -0800 Subject: [PATCH 049/113] reset: imx7: Add plubming to support multiple IP variants In order to enable supporting i.MX8MQ with this driver, convert it to expect variant specific bits to be passed via driver data. Cc: p.zabel@pengutronix.de Cc: Fabio Estevam Cc: cphealy@gmail.com Cc: l.stach@pengutronix.de Cc: Leonard Crestez Cc: "A.s. Dong" Cc: Richard Zhu Cc: Rob Herring Cc: devicetree@vger.kernel.org Cc: linux-imx@nxp.com Cc: linux-arm-kernel@lists.infradead.org Cc: linux-kernel@vger.kernel.org Signed-off-by: Andrey Smirnov Signed-off-by: Philipp Zabel --- drivers/reset/reset-imx7.c | 50 +++++++++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/drivers/reset/reset-imx7.c b/drivers/reset/reset-imx7.c index 77911fa8f31d..cb6e3bc4acc1 100644 --- a/drivers/reset/reset-imx7.c +++ b/drivers/reset/reset-imx7.c @@ -17,14 +17,26 @@ #include #include +#include #include #include #include #include +struct imx7_src_signal { + unsigned int offset, bit; +}; + +struct imx7_src_variant { + const struct imx7_src_signal *signals; + unsigned int signals_num; + struct reset_control_ops ops; +}; + struct imx7_src { struct reset_controller_dev rcdev; struct regmap *regmap; + const struct imx7_src_signal *signals; }; enum imx7_src_registers { @@ -39,9 +51,14 @@ enum imx7_src_registers { SRC_DDRC_RCR = 0x1000, }; -struct imx7_src_signal { - unsigned int offset, bit; -}; +static int imx7_reset_update(struct imx7_src *imx7src, + unsigned long id, unsigned int value) +{ + const struct imx7_src_signal *signal = &imx7src->signals[id]; + + return regmap_update_bits(imx7src->regmap, + signal->offset, signal->bit, value); +} static const struct imx7_src_signal imx7_src_signals[IMX7_RESET_NUM] = { [IMX7_RESET_A7_CORE_POR_RESET0] = { SRC_A7RCR0, BIT(0) }, @@ -81,8 +98,8 @@ static int imx7_reset_set(struct reset_controller_dev *rcdev, unsigned long id, bool assert) { struct imx7_src *imx7src = to_imx7_src(rcdev); - const struct imx7_src_signal *signal = &imx7_src_signals[id]; - unsigned int value = assert ? signal->bit : 0; + const unsigned int bit = imx7src->signals[id].bit; + unsigned int value = assert ? bit : 0; switch (id) { case IMX7_RESET_PCIEPHY: @@ -95,12 +112,11 @@ static int imx7_reset_set(struct reset_controller_dev *rcdev, break; case IMX7_RESET_PCIE_CTRL_APPS_EN: - value = (assert) ? 0 : signal->bit; + value = assert ? 0 : bit; break; } - return regmap_update_bits(imx7src->regmap, - signal->offset, signal->bit, value); + return imx7_reset_update(imx7src, id, value); } static int imx7_reset_assert(struct reset_controller_dev *rcdev, @@ -115,9 +131,13 @@ static int imx7_reset_deassert(struct reset_controller_dev *rcdev, return imx7_reset_set(rcdev, id, false); } -static const struct reset_control_ops imx7_reset_ops = { - .assert = imx7_reset_assert, - .deassert = imx7_reset_deassert, +static const struct imx7_src_variant variant_imx7 = { + .signals = imx7_src_signals, + .signals_num = ARRAY_SIZE(imx7_src_signals), + .ops = { + .assert = imx7_reset_assert, + .deassert = imx7_reset_deassert, + }, }; static int imx7_reset_probe(struct platform_device *pdev) @@ -125,11 +145,13 @@ static int imx7_reset_probe(struct platform_device *pdev) struct imx7_src *imx7src; struct device *dev = &pdev->dev; struct regmap_config config = { .name = "src" }; + const struct imx7_src_variant *variant = of_device_get_match_data(dev); imx7src = devm_kzalloc(dev, sizeof(*imx7src), GFP_KERNEL); if (!imx7src) return -ENOMEM; + imx7src->signals = variant->signals; imx7src->regmap = syscon_node_to_regmap(dev->of_node); if (IS_ERR(imx7src->regmap)) { dev_err(dev, "Unable to get imx7-src regmap"); @@ -138,15 +160,15 @@ static int imx7_reset_probe(struct platform_device *pdev) regmap_attach_dev(dev, imx7src->regmap, &config); imx7src->rcdev.owner = THIS_MODULE; - imx7src->rcdev.nr_resets = IMX7_RESET_NUM; - imx7src->rcdev.ops = &imx7_reset_ops; + imx7src->rcdev.nr_resets = variant->signals_num; + imx7src->rcdev.ops = &variant->ops; imx7src->rcdev.of_node = dev->of_node; return devm_reset_controller_register(dev, &imx7src->rcdev); } static const struct of_device_id imx7_reset_dt_ids[] = { - { .compatible = "fsl,imx7d-src", }, + { .compatible = "fsl,imx7d-src", .data = &variant_imx7 }, { /* sentinel */ }, }; From c979dbf59987f4114fdbd491c8ad254343a430fd Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Mon, 21 Jan 2019 18:10:43 -0800 Subject: [PATCH 050/113] reset: imx7: Add support for i.MX8MQ IP block variant Add bits and pieces needed to support IP block variant found on i.MX8MQ SoCs. Cc: p.zabel@pengutronix.de Cc: Fabio Estevam Cc: cphealy@gmail.com Cc: l.stach@pengutronix.de Cc: Leonard Crestez Cc: "A.s. Dong" Cc: Richard Zhu Cc: Rob Herring Cc: devicetree@vger.kernel.org Cc: linux-imx@nxp.com Cc: linux-arm-kernel@lists.infradead.org Cc: linux-kernel@vger.kernel.org Signed-off-by: Andrey Smirnov [p.zabel@pengutronix.de: fixed whitespace alignment] Signed-off-by: Philipp Zabel --- drivers/reset/Kconfig | 4 +- drivers/reset/reset-imx7.c | 122 +++++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+), 2 deletions(-) diff --git a/drivers/reset/Kconfig b/drivers/reset/Kconfig index d9a02b7f90cf..2c8c23db92fb 100644 --- a/drivers/reset/Kconfig +++ b/drivers/reset/Kconfig @@ -56,9 +56,9 @@ config RESET_HSDK This enables the reset controller driver for HSDK board. config RESET_IMX7 - bool "i.MX7 Reset Driver" if COMPILE_TEST + bool "i.MX7/8 Reset Driver" if COMPILE_TEST depends on HAS_IOMEM - default SOC_IMX7D + default SOC_IMX7D || (ARM64 && ARCH_MXC) select MFD_SYSCON help This enables the reset controller driver for i.MX7 SoCs. diff --git a/drivers/reset/reset-imx7.c b/drivers/reset/reset-imx7.c index cb6e3bc4acc1..aed76e33a0a9 100644 --- a/drivers/reset/reset-imx7.c +++ b/drivers/reset/reset-imx7.c @@ -22,6 +22,7 @@ #include #include #include +#include struct imx7_src_signal { unsigned int offset, bit; @@ -140,6 +141,126 @@ static const struct imx7_src_variant variant_imx7 = { }, }; +enum imx8mq_src_registers { + SRC_A53RCR0 = 0x0004, + SRC_HDMI_RCR = 0x0030, + SRC_DISP_RCR = 0x0034, + SRC_GPU_RCR = 0x0040, + SRC_VPU_RCR = 0x0044, + SRC_PCIE2_RCR = 0x0048, + SRC_MIPIPHY1_RCR = 0x004c, + SRC_MIPIPHY2_RCR = 0x0050, + SRC_DDRC2_RCR = 0x1004, +}; + +static const struct imx7_src_signal imx8mq_src_signals[IMX8MQ_RESET_NUM] = { + [IMX8MQ_RESET_A53_CORE_POR_RESET0] = { SRC_A53RCR0, BIT(0) }, + [IMX8MQ_RESET_A53_CORE_POR_RESET1] = { SRC_A53RCR0, BIT(1) }, + [IMX8MQ_RESET_A53_CORE_POR_RESET2] = { SRC_A53RCR0, BIT(2) }, + [IMX8MQ_RESET_A53_CORE_POR_RESET3] = { SRC_A53RCR0, BIT(3) }, + [IMX8MQ_RESET_A53_CORE_RESET0] = { SRC_A53RCR0, BIT(4) }, + [IMX8MQ_RESET_A53_CORE_RESET1] = { SRC_A53RCR0, BIT(5) }, + [IMX8MQ_RESET_A53_CORE_RESET2] = { SRC_A53RCR0, BIT(6) }, + [IMX8MQ_RESET_A53_CORE_RESET3] = { SRC_A53RCR0, BIT(7) }, + [IMX8MQ_RESET_A53_DBG_RESET0] = { SRC_A53RCR0, BIT(8) }, + [IMX8MQ_RESET_A53_DBG_RESET1] = { SRC_A53RCR0, BIT(9) }, + [IMX8MQ_RESET_A53_DBG_RESET2] = { SRC_A53RCR0, BIT(10) }, + [IMX8MQ_RESET_A53_DBG_RESET3] = { SRC_A53RCR0, BIT(11) }, + [IMX8MQ_RESET_A53_ETM_RESET0] = { SRC_A53RCR0, BIT(12) }, + [IMX8MQ_RESET_A53_ETM_RESET1] = { SRC_A53RCR0, BIT(13) }, + [IMX8MQ_RESET_A53_ETM_RESET2] = { SRC_A53RCR0, BIT(14) }, + [IMX8MQ_RESET_A53_ETM_RESET3] = { SRC_A53RCR0, BIT(15) }, + [IMX8MQ_RESET_A53_SOC_DBG_RESET] = { SRC_A53RCR0, BIT(20) }, + [IMX8MQ_RESET_A53_L2RESET] = { SRC_A53RCR0, BIT(21) }, + [IMX8MQ_RESET_SW_NON_SCLR_M4C_RST] = { SRC_M4RCR, BIT(0) }, + [IMX8MQ_RESET_OTG1_PHY_RESET] = { SRC_USBOPHY1_RCR, BIT(0) }, + [IMX8MQ_RESET_OTG2_PHY_RESET] = { SRC_USBOPHY2_RCR, BIT(0) }, + [IMX8MQ_RESET_MIPI_DSI_RESET_BYTE_N] = { SRC_MIPIPHY_RCR, BIT(1) }, + [IMX8MQ_RESET_MIPI_DSI_RESET_N] = { SRC_MIPIPHY_RCR, BIT(2) }, + [IMX8MQ_RESET_MIPI_DIS_DPI_RESET_N] = { SRC_MIPIPHY_RCR, BIT(3) }, + [IMX8MQ_RESET_MIPI_DIS_ESC_RESET_N] = { SRC_MIPIPHY_RCR, BIT(4) }, + [IMX8MQ_RESET_MIPI_DIS_PCLK_RESET_N] = { SRC_MIPIPHY_RCR, BIT(5) }, + [IMX8MQ_RESET_PCIEPHY] = { SRC_PCIEPHY_RCR, + BIT(2) | BIT(1) }, + [IMX8MQ_RESET_PCIEPHY_PERST] = { SRC_PCIEPHY_RCR, BIT(3) }, + [IMX8MQ_RESET_PCIE_CTRL_APPS_EN] = { SRC_PCIEPHY_RCR, BIT(6) }, + [IMX8MQ_RESET_PCIE_CTRL_APPS_TURNOFF] = { SRC_PCIEPHY_RCR, BIT(11) }, + [IMX8MQ_RESET_HDMI_PHY_APB_RESET] = { SRC_HDMI_RCR, BIT(0) }, + [IMX8MQ_RESET_DISP_RESET] = { SRC_DISP_RCR, BIT(0) }, + [IMX8MQ_RESET_GPU_RESET] = { SRC_GPU_RCR, BIT(0) }, + [IMX8MQ_RESET_VPU_RESET] = { SRC_VPU_RCR, BIT(0) }, + [IMX8MQ_RESET_PCIEPHY2] = { SRC_PCIE2_RCR, + BIT(2) | BIT(1) }, + [IMX8MQ_RESET_PCIEPHY2_PERST] = { SRC_PCIE2_RCR, BIT(3) }, + [IMX8MQ_RESET_PCIE2_CTRL_APPS_EN] = { SRC_PCIE2_RCR, BIT(6) }, + [IMX8MQ_RESET_PCIE2_CTRL_APPS_TURNOFF] = { SRC_PCIE2_RCR, BIT(11) }, + [IMX8MQ_RESET_MIPI_CSI1_CORE_RESET] = { SRC_MIPIPHY1_RCR, BIT(0) }, + [IMX8MQ_RESET_MIPI_CSI1_PHY_REF_RESET] = { SRC_MIPIPHY1_RCR, BIT(1) }, + [IMX8MQ_RESET_MIPI_CSI1_ESC_RESET] = { SRC_MIPIPHY1_RCR, BIT(2) }, + [IMX8MQ_RESET_MIPI_CSI2_CORE_RESET] = { SRC_MIPIPHY2_RCR, BIT(0) }, + [IMX8MQ_RESET_MIPI_CSI2_PHY_REF_RESET] = { SRC_MIPIPHY2_RCR, BIT(1) }, + [IMX8MQ_RESET_MIPI_CSI2_ESC_RESET] = { SRC_MIPIPHY2_RCR, BIT(2) }, + [IMX8MQ_RESET_DDRC1_PRST] = { SRC_DDRC_RCR, BIT(0) }, + [IMX8MQ_RESET_DDRC1_CORE_RESET] = { SRC_DDRC_RCR, BIT(1) }, + [IMX8MQ_RESET_DDRC1_PHY_RESET] = { SRC_DDRC_RCR, BIT(2) }, + [IMX8MQ_RESET_DDRC2_PHY_RESET] = { SRC_DDRC2_RCR, BIT(0) }, + [IMX8MQ_RESET_DDRC2_CORE_RESET] = { SRC_DDRC2_RCR, BIT(1) }, + [IMX8MQ_RESET_DDRC2_PRST] = { SRC_DDRC2_RCR, BIT(2) }, +}; + +static int imx8mq_reset_set(struct reset_controller_dev *rcdev, + unsigned long id, bool assert) +{ + struct imx7_src *imx7src = to_imx7_src(rcdev); + const unsigned int bit = imx7src->signals[id].bit; + unsigned int value = assert ? bit : 0; + + switch (id) { + case IMX8MQ_RESET_PCIEPHY: + case IMX8MQ_RESET_PCIEPHY2: /* fallthrough */ + /* + * wait for more than 10us to release phy g_rst and + * btnrst + */ + if (!assert) + udelay(10); + break; + + case IMX8MQ_RESET_PCIE_CTRL_APPS_EN: + case IMX8MQ_RESET_PCIE2_CTRL_APPS_EN: /* fallthrough */ + case IMX8MQ_RESET_MIPI_DIS_PCLK_RESET_N: /* fallthrough */ + case IMX8MQ_RESET_MIPI_DIS_ESC_RESET_N: /* fallthrough */ + case IMX8MQ_RESET_MIPI_DIS_DPI_RESET_N: /* fallthrough */ + case IMX8MQ_RESET_MIPI_DSI_RESET_N: /* fallthrough */ + case IMX8MQ_RESET_MIPI_DSI_RESET_BYTE_N: /* fallthrough */ + value = assert ? 0 : bit; + break; + } + + return imx7_reset_update(imx7src, id, value); +} + +static int imx8mq_reset_assert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + return imx8mq_reset_set(rcdev, id, true); +} + +static int imx8mq_reset_deassert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + return imx8mq_reset_set(rcdev, id, false); +} + +static const struct imx7_src_variant variant_imx8mq = { + .signals = imx8mq_src_signals, + .signals_num = ARRAY_SIZE(imx8mq_src_signals), + .ops = { + .assert = imx8mq_reset_assert, + .deassert = imx8mq_reset_deassert, + }, +}; + static int imx7_reset_probe(struct platform_device *pdev) { struct imx7_src *imx7src; @@ -169,6 +290,7 @@ static int imx7_reset_probe(struct platform_device *pdev) static const struct of_device_id imx7_reset_dt_ids[] = { { .compatible = "fsl,imx7d-src", .data = &variant_imx7 }, + { .compatible = "fsl,imx8mq-src", .data = &variant_imx8mq }, { /* sentinel */ }, }; From bc3843d4d357061d92e7800c7da342e2d068772c Mon Sep 17 00:00:00 2001 From: Nava kishore Manne Date: Fri, 25 Jan 2019 13:16:52 +0530 Subject: [PATCH 051/113] firmware: xilinx: Add reset API's This Patch Adds reset API's to support release, assert and status functionalities by using firmware interface. Signed-off-by: Nava kishore Manne Signed-off-by: Michal Simek --- drivers/firmware/xilinx/zynqmp.c | 40 ++++++++ include/linux/firmware/xlnx-zynqmp.h | 136 +++++++++++++++++++++++++++ 2 files changed, 176 insertions(+) diff --git a/drivers/firmware/xilinx/zynqmp.c b/drivers/firmware/xilinx/zynqmp.c index 9a1c72a9280f..70b50377ae5f 100644 --- a/drivers/firmware/xilinx/zynqmp.c +++ b/drivers/firmware/xilinx/zynqmp.c @@ -469,6 +469,44 @@ static int zynqmp_pm_ioctl(u32 node_id, u32 ioctl_id, u32 arg1, u32 arg2, arg1, arg2, out); } +/** + * zynqmp_pm_reset_assert - Request setting of reset (1 - assert, 0 - release) + * @reset: Reset to be configured + * @assert_flag: Flag stating should reset be asserted (1) or + * released (0) + * + * Return: Returns status, either success or error+reason + */ +static int zynqmp_pm_reset_assert(const enum zynqmp_pm_reset reset, + const enum zynqmp_pm_reset_action assert_flag) +{ + return zynqmp_pm_invoke_fn(PM_RESET_ASSERT, reset, assert_flag, + 0, 0, NULL); +} + +/** + * zynqmp_pm_reset_get_status - Get status of the reset + * @reset: Reset whose status should be returned + * @status: Returned status + * + * Return: Returns status, either success or error+reason + */ +static int zynqmp_pm_reset_get_status(const enum zynqmp_pm_reset reset, + u32 *status) +{ + u32 ret_payload[PAYLOAD_ARG_CNT]; + int ret; + + if (!status) + return -EINVAL; + + ret = zynqmp_pm_invoke_fn(PM_RESET_GET_STATUS, reset, 0, + 0, 0, ret_payload); + *status = ret_payload[1]; + + return ret; +} + static const struct zynqmp_eemi_ops eemi_ops = { .get_api_version = zynqmp_pm_get_api_version, .query_data = zynqmp_pm_query_data, @@ -482,6 +520,8 @@ static const struct zynqmp_eemi_ops eemi_ops = { .clock_setparent = zynqmp_pm_clock_setparent, .clock_getparent = zynqmp_pm_clock_getparent, .ioctl = zynqmp_pm_ioctl, + .reset_assert = zynqmp_pm_reset_assert, + .reset_get_status = zynqmp_pm_reset_get_status, }; /** diff --git a/include/linux/firmware/xlnx-zynqmp.h b/include/linux/firmware/xlnx-zynqmp.h index 3c3c28eff56a..07c587a0b06e 100644 --- a/include/linux/firmware/xlnx-zynqmp.h +++ b/include/linux/firmware/xlnx-zynqmp.h @@ -34,6 +34,8 @@ enum pm_api_id { PM_GET_API_VERSION = 1, + PM_RESET_ASSERT = 17, + PM_RESET_GET_STATUS, PM_IOCTL = 34, PM_QUERY_DATA, PM_CLOCK_ENABLE, @@ -75,6 +77,137 @@ enum pm_query_id { PM_QID_CLOCK_GET_NUM_CLOCKS = 12, }; +enum zynqmp_pm_reset_action { + PM_RESET_ACTION_RELEASE, + PM_RESET_ACTION_ASSERT, + PM_RESET_ACTION_PULSE, +}; + +enum zynqmp_pm_reset { + ZYNQMP_PM_RESET_START = 1000, + ZYNQMP_PM_RESET_PCIE_CFG = ZYNQMP_PM_RESET_START, + ZYNQMP_PM_RESET_PCIE_BRIDGE, + ZYNQMP_PM_RESET_PCIE_CTRL, + ZYNQMP_PM_RESET_DP, + ZYNQMP_PM_RESET_SWDT_CRF, + ZYNQMP_PM_RESET_AFI_FM5, + ZYNQMP_PM_RESET_AFI_FM4, + ZYNQMP_PM_RESET_AFI_FM3, + ZYNQMP_PM_RESET_AFI_FM2, + ZYNQMP_PM_RESET_AFI_FM1, + ZYNQMP_PM_RESET_AFI_FM0, + ZYNQMP_PM_RESET_GDMA, + ZYNQMP_PM_RESET_GPU_PP1, + ZYNQMP_PM_RESET_GPU_PP0, + ZYNQMP_PM_RESET_GPU, + ZYNQMP_PM_RESET_GT, + ZYNQMP_PM_RESET_SATA, + ZYNQMP_PM_RESET_ACPU3_PWRON, + ZYNQMP_PM_RESET_ACPU2_PWRON, + ZYNQMP_PM_RESET_ACPU1_PWRON, + ZYNQMP_PM_RESET_ACPU0_PWRON, + ZYNQMP_PM_RESET_APU_L2, + ZYNQMP_PM_RESET_ACPU3, + ZYNQMP_PM_RESET_ACPU2, + ZYNQMP_PM_RESET_ACPU1, + ZYNQMP_PM_RESET_ACPU0, + ZYNQMP_PM_RESET_DDR, + ZYNQMP_PM_RESET_APM_FPD, + ZYNQMP_PM_RESET_SOFT, + ZYNQMP_PM_RESET_GEM0, + ZYNQMP_PM_RESET_GEM1, + ZYNQMP_PM_RESET_GEM2, + ZYNQMP_PM_RESET_GEM3, + ZYNQMP_PM_RESET_QSPI, + ZYNQMP_PM_RESET_UART0, + ZYNQMP_PM_RESET_UART1, + ZYNQMP_PM_RESET_SPI0, + ZYNQMP_PM_RESET_SPI1, + ZYNQMP_PM_RESET_SDIO0, + ZYNQMP_PM_RESET_SDIO1, + ZYNQMP_PM_RESET_CAN0, + ZYNQMP_PM_RESET_CAN1, + ZYNQMP_PM_RESET_I2C0, + ZYNQMP_PM_RESET_I2C1, + ZYNQMP_PM_RESET_TTC0, + ZYNQMP_PM_RESET_TTC1, + ZYNQMP_PM_RESET_TTC2, + ZYNQMP_PM_RESET_TTC3, + ZYNQMP_PM_RESET_SWDT_CRL, + ZYNQMP_PM_RESET_NAND, + ZYNQMP_PM_RESET_ADMA, + ZYNQMP_PM_RESET_GPIO, + ZYNQMP_PM_RESET_IOU_CC, + ZYNQMP_PM_RESET_TIMESTAMP, + ZYNQMP_PM_RESET_RPU_R50, + ZYNQMP_PM_RESET_RPU_R51, + ZYNQMP_PM_RESET_RPU_AMBA, + ZYNQMP_PM_RESET_OCM, + ZYNQMP_PM_RESET_RPU_PGE, + ZYNQMP_PM_RESET_USB0_CORERESET, + ZYNQMP_PM_RESET_USB1_CORERESET, + ZYNQMP_PM_RESET_USB0_HIBERRESET, + ZYNQMP_PM_RESET_USB1_HIBERRESET, + ZYNQMP_PM_RESET_USB0_APB, + ZYNQMP_PM_RESET_USB1_APB, + ZYNQMP_PM_RESET_IPI, + ZYNQMP_PM_RESET_APM_LPD, + ZYNQMP_PM_RESET_RTC, + ZYNQMP_PM_RESET_SYSMON, + ZYNQMP_PM_RESET_AFI_FM6, + ZYNQMP_PM_RESET_LPD_SWDT, + ZYNQMP_PM_RESET_FPD, + ZYNQMP_PM_RESET_RPU_DBG1, + ZYNQMP_PM_RESET_RPU_DBG0, + ZYNQMP_PM_RESET_DBG_LPD, + ZYNQMP_PM_RESET_DBG_FPD, + ZYNQMP_PM_RESET_APLL, + ZYNQMP_PM_RESET_DPLL, + ZYNQMP_PM_RESET_VPLL, + ZYNQMP_PM_RESET_IOPLL, + ZYNQMP_PM_RESET_RPLL, + ZYNQMP_PM_RESET_GPO3_PL_0, + ZYNQMP_PM_RESET_GPO3_PL_1, + ZYNQMP_PM_RESET_GPO3_PL_2, + ZYNQMP_PM_RESET_GPO3_PL_3, + ZYNQMP_PM_RESET_GPO3_PL_4, + ZYNQMP_PM_RESET_GPO3_PL_5, + ZYNQMP_PM_RESET_GPO3_PL_6, + ZYNQMP_PM_RESET_GPO3_PL_7, + ZYNQMP_PM_RESET_GPO3_PL_8, + ZYNQMP_PM_RESET_GPO3_PL_9, + ZYNQMP_PM_RESET_GPO3_PL_10, + ZYNQMP_PM_RESET_GPO3_PL_11, + ZYNQMP_PM_RESET_GPO3_PL_12, + ZYNQMP_PM_RESET_GPO3_PL_13, + ZYNQMP_PM_RESET_GPO3_PL_14, + ZYNQMP_PM_RESET_GPO3_PL_15, + ZYNQMP_PM_RESET_GPO3_PL_16, + ZYNQMP_PM_RESET_GPO3_PL_17, + ZYNQMP_PM_RESET_GPO3_PL_18, + ZYNQMP_PM_RESET_GPO3_PL_19, + ZYNQMP_PM_RESET_GPO3_PL_20, + ZYNQMP_PM_RESET_GPO3_PL_21, + ZYNQMP_PM_RESET_GPO3_PL_22, + ZYNQMP_PM_RESET_GPO3_PL_23, + ZYNQMP_PM_RESET_GPO3_PL_24, + ZYNQMP_PM_RESET_GPO3_PL_25, + ZYNQMP_PM_RESET_GPO3_PL_26, + ZYNQMP_PM_RESET_GPO3_PL_27, + ZYNQMP_PM_RESET_GPO3_PL_28, + ZYNQMP_PM_RESET_GPO3_PL_29, + ZYNQMP_PM_RESET_GPO3_PL_30, + ZYNQMP_PM_RESET_GPO3_PL_31, + ZYNQMP_PM_RESET_RPU_LS, + ZYNQMP_PM_RESET_PS_ONLY, + ZYNQMP_PM_RESET_PL, + ZYNQMP_PM_RESET_PS_PL0, + ZYNQMP_PM_RESET_PS_PL1, + ZYNQMP_PM_RESET_PS_PL2, + ZYNQMP_PM_RESET_PS_PL3, + ZYNQMP_PM_RESET_END = ZYNQMP_PM_RESET_PS_PL3 +}; + /** * struct zynqmp_pm_query_data - PM query data * @qid: query ID @@ -102,6 +235,9 @@ struct zynqmp_eemi_ops { int (*clock_setparent)(u32 clock_id, u32 parent_id); int (*clock_getparent)(u32 clock_id, u32 *parent_id); int (*ioctl)(u32 node_id, u32 ioctl_id, u32 arg1, u32 arg2, u32 *out); + int (*reset_assert)(const enum zynqmp_pm_reset reset, + const enum zynqmp_pm_reset_action assert_flag); + int (*reset_get_status)(const enum zynqmp_pm_reset reset, u32 *status); }; #if IS_REACHABLE(CONFIG_ARCH_ZYNQMP) From 3f1b66be4aaa5dbe0a16197bfdfc355cf1da7701 Mon Sep 17 00:00:00 2001 From: Nava kishore Manne Date: Fri, 25 Jan 2019 13:16:53 +0530 Subject: [PATCH 052/113] dt-bindings: reset: Add bindings for ZynqMP reset driver Add documentation to describe Xilinx ZynqMP reset driver bindings. Signed-off-by: Nava kishore Manne Signed-off-by: Jolly Shah Reviewed-by: Rob Herring Signed-off-by: Michal Simek --- .../bindings/reset/xlnx,zynqmp-reset.txt | 52 +++++++ .../dt-bindings/reset/xlnx-zynqmp-resets.h | 130 ++++++++++++++++++ 2 files changed, 182 insertions(+) create mode 100644 Documentation/devicetree/bindings/reset/xlnx,zynqmp-reset.txt create mode 100644 include/dt-bindings/reset/xlnx-zynqmp-resets.h diff --git a/Documentation/devicetree/bindings/reset/xlnx,zynqmp-reset.txt b/Documentation/devicetree/bindings/reset/xlnx,zynqmp-reset.txt new file mode 100644 index 000000000000..27a45fe5ecf1 --- /dev/null +++ b/Documentation/devicetree/bindings/reset/xlnx,zynqmp-reset.txt @@ -0,0 +1,52 @@ +-------------------------------------------------------------------------- + = Zynq UltraScale+ MPSoC reset driver binding = +-------------------------------------------------------------------------- +The Zynq UltraScale+ MPSoC has several different resets. + +See Chapter 36 of the Zynq UltraScale+ MPSoC TRM (UG) for more information +about zynqmp resets. + +Please also refer to reset.txt in this directory for common reset +controller binding usage. + +Required Properties: +- compatible: "xlnx,zynqmp-reset" +- #reset-cells: Specifies the number of cells needed to encode reset + line, should be 1 + +------- +Example +------- + +firmware { + zynqmp_firmware: zynqmp-firmware { + compatible = "xlnx,zynqmp-firmware"; + method = "smc"; + + zynqmp_reset: reset-controller { + compatible = "xlnx,zynqmp-reset"; + #reset-cells = <1>; + }; + }; +}; + +Specifying reset lines connected to IP modules +============================================== + +Device nodes that need access to reset lines should +specify them as a reset phandle in their corresponding node as +specified in reset.txt. + +For list of all valid reset indicies see + + +Example: + +serdes: zynqmp_phy@fd400000 { + ... + + resets = <&zynqmp_reset ZYNQMP_RESET_SATA>; + reset-names = "sata_rst"; + + ... +}; diff --git a/include/dt-bindings/reset/xlnx-zynqmp-resets.h b/include/dt-bindings/reset/xlnx-zynqmp-resets.h new file mode 100644 index 000000000000..d44525b9f8db --- /dev/null +++ b/include/dt-bindings/reset/xlnx-zynqmp-resets.h @@ -0,0 +1,130 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2018 Xilinx, Inc. + */ + +#ifndef _DT_BINDINGS_ZYNQMP_RESETS_H +#define _DT_BINDINGS_ZYNQMP_RESETS_H + +#define ZYNQMP_RESET_PCIE_CFG 0 +#define ZYNQMP_RESET_PCIE_BRIDGE 1 +#define ZYNQMP_RESET_PCIE_CTRL 2 +#define ZYNQMP_RESET_DP 3 +#define ZYNQMP_RESET_SWDT_CRF 4 +#define ZYNQMP_RESET_AFI_FM5 5 +#define ZYNQMP_RESET_AFI_FM4 6 +#define ZYNQMP_RESET_AFI_FM3 7 +#define ZYNQMP_RESET_AFI_FM2 8 +#define ZYNQMP_RESET_AFI_FM1 9 +#define ZYNQMP_RESET_AFI_FM0 10 +#define ZYNQMP_RESET_GDMA 11 +#define ZYNQMP_RESET_GPU_PP1 12 +#define ZYNQMP_RESET_GPU_PP0 13 +#define ZYNQMP_RESET_GPU 14 +#define ZYNQMP_RESET_GT 15 +#define ZYNQMP_RESET_SATA 16 +#define ZYNQMP_RESET_ACPU3_PWRON 17 +#define ZYNQMP_RESET_ACPU2_PWRON 18 +#define ZYNQMP_RESET_ACPU1_PWRON 19 +#define ZYNQMP_RESET_ACPU0_PWRON 20 +#define ZYNQMP_RESET_APU_L2 21 +#define ZYNQMP_RESET_ACPU3 22 +#define ZYNQMP_RESET_ACPU2 23 +#define ZYNQMP_RESET_ACPU1 24 +#define ZYNQMP_RESET_ACPU0 25 +#define ZYNQMP_RESET_DDR 26 +#define ZYNQMP_RESET_APM_FPD 27 +#define ZYNQMP_RESET_SOFT 28 +#define ZYNQMP_RESET_GEM0 29 +#define ZYNQMP_RESET_GEM1 30 +#define ZYNQMP_RESET_GEM2 31 +#define ZYNQMP_RESET_GEM3 32 +#define ZYNQMP_RESET_QSPI 33 +#define ZYNQMP_RESET_UART0 34 +#define ZYNQMP_RESET_UART1 35 +#define ZYNQMP_RESET_SPI0 36 +#define ZYNQMP_RESET_SPI1 37 +#define ZYNQMP_RESET_SDIO0 38 +#define ZYNQMP_RESET_SDIO1 39 +#define ZYNQMP_RESET_CAN0 40 +#define ZYNQMP_RESET_CAN1 41 +#define ZYNQMP_RESET_I2C0 42 +#define ZYNQMP_RESET_I2C1 43 +#define ZYNQMP_RESET_TTC0 44 +#define ZYNQMP_RESET_TTC1 45 +#define ZYNQMP_RESET_TTC2 46 +#define ZYNQMP_RESET_TTC3 47 +#define ZYNQMP_RESET_SWDT_CRL 48 +#define ZYNQMP_RESET_NAND 49 +#define ZYNQMP_RESET_ADMA 50 +#define ZYNQMP_RESET_GPIO 51 +#define ZYNQMP_RESET_IOU_CC 52 +#define ZYNQMP_RESET_TIMESTAMP 53 +#define ZYNQMP_RESET_RPU_R50 54 +#define ZYNQMP_RESET_RPU_R51 55 +#define ZYNQMP_RESET_RPU_AMBA 56 +#define ZYNQMP_RESET_OCM 57 +#define ZYNQMP_RESET_RPU_PGE 58 +#define ZYNQMP_RESET_USB0_CORERESET 59 +#define ZYNQMP_RESET_USB1_CORERESET 60 +#define ZYNQMP_RESET_USB0_HIBERRESET 61 +#define ZYNQMP_RESET_USB1_HIBERRESET 62 +#define ZYNQMP_RESET_USB0_APB 63 +#define ZYNQMP_RESET_USB1_APB 64 +#define ZYNQMP_RESET_IPI 65 +#define ZYNQMP_RESET_APM_LPD 66 +#define ZYNQMP_RESET_RTC 67 +#define ZYNQMP_RESET_SYSMON 68 +#define ZYNQMP_RESET_AFI_FM6 69 +#define ZYNQMP_RESET_LPD_SWDT 70 +#define ZYNQMP_RESET_FPD 71 +#define ZYNQMP_RESET_RPU_DBG1 72 +#define ZYNQMP_RESET_RPU_DBG0 73 +#define ZYNQMP_RESET_DBG_LPD 74 +#define ZYNQMP_RESET_DBG_FPD 75 +#define ZYNQMP_RESET_APLL 76 +#define ZYNQMP_RESET_DPLL 77 +#define ZYNQMP_RESET_VPLL 78 +#define ZYNQMP_RESET_IOPLL 79 +#define ZYNQMP_RESET_RPLL 80 +#define ZYNQMP_RESET_GPO3_PL_0 81 +#define ZYNQMP_RESET_GPO3_PL_1 82 +#define ZYNQMP_RESET_GPO3_PL_2 83 +#define ZYNQMP_RESET_GPO3_PL_3 84 +#define ZYNQMP_RESET_GPO3_PL_4 85 +#define ZYNQMP_RESET_GPO3_PL_5 86 +#define ZYNQMP_RESET_GPO3_PL_6 87 +#define ZYNQMP_RESET_GPO3_PL_7 88 +#define ZYNQMP_RESET_GPO3_PL_8 89 +#define ZYNQMP_RESET_GPO3_PL_9 90 +#define ZYNQMP_RESET_GPO3_PL_10 91 +#define ZYNQMP_RESET_GPO3_PL_11 92 +#define ZYNQMP_RESET_GPO3_PL_12 93 +#define ZYNQMP_RESET_GPO3_PL_13 94 +#define ZYNQMP_RESET_GPO3_PL_14 95 +#define ZYNQMP_RESET_GPO3_PL_15 96 +#define ZYNQMP_RESET_GPO3_PL_16 97 +#define ZYNQMP_RESET_GPO3_PL_17 98 +#define ZYNQMP_RESET_GPO3_PL_18 99 +#define ZYNQMP_RESET_GPO3_PL_19 100 +#define ZYNQMP_RESET_GPO3_PL_20 101 +#define ZYNQMP_RESET_GPO3_PL_21 102 +#define ZYNQMP_RESET_GPO3_PL_22 103 +#define ZYNQMP_RESET_GPO3_PL_23 104 +#define ZYNQMP_RESET_GPO3_PL_24 105 +#define ZYNQMP_RESET_GPO3_PL_25 106 +#define ZYNQMP_RESET_GPO3_PL_26 107 +#define ZYNQMP_RESET_GPO3_PL_27 108 +#define ZYNQMP_RESET_GPO3_PL_28 109 +#define ZYNQMP_RESET_GPO3_PL_29 110 +#define ZYNQMP_RESET_GPO3_PL_30 111 +#define ZYNQMP_RESET_GPO3_PL_31 112 +#define ZYNQMP_RESET_RPU_LS 113 +#define ZYNQMP_RESET_PS_ONLY 114 +#define ZYNQMP_RESET_PL 115 +#define ZYNQMP_RESET_PS_PL0 116 +#define ZYNQMP_RESET_PS_PL1 117 +#define ZYNQMP_RESET_PS_PL2 118 +#define ZYNQMP_RESET_PS_PL3 119 + +#endif From 62f0d7dc3bae9f7ce2701d6c8cfd3d93130017af Mon Sep 17 00:00:00 2001 From: Nava kishore Manne Date: Fri, 25 Jan 2019 13:16:54 +0530 Subject: [PATCH 053/113] reset: reset-zynqmp: Adding support for Xilinx zynqmp reset controller. Add a reset controller driver for Xilinx Zynq UltraScale+ MPSoC. The zynqmp reset-controller has the ability to reset lines connected to different blocks and peripheral in the Soc. Signed-off-by: Nava kishore Manne Acked-by: Philipp Zabel Signed-off-by: Michal Simek --- drivers/reset/Makefile | 1 + drivers/reset/reset-zynqmp.c | 114 +++++++++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 drivers/reset/reset-zynqmp.c diff --git a/drivers/reset/Makefile b/drivers/reset/Makefile index d08e8b90046a..380602d9c9b4 100644 --- a/drivers/reset/Makefile +++ b/drivers/reset/Makefile @@ -25,4 +25,5 @@ obj-$(CONFIG_RESET_TI_SYSCON) += reset-ti-syscon.o obj-$(CONFIG_RESET_UNIPHIER) += reset-uniphier.o obj-$(CONFIG_RESET_UNIPHIER_USB3) += reset-uniphier-usb3.o obj-$(CONFIG_RESET_ZYNQ) += reset-zynq.o +obj-$(CONFIG_ARCH_ZYNQMP) += reset-zynqmp.o diff --git a/drivers/reset/reset-zynqmp.c b/drivers/reset/reset-zynqmp.c new file mode 100644 index 000000000000..2ef1f13aa47b --- /dev/null +++ b/drivers/reset/reset-zynqmp.c @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2018 Xilinx, Inc. + * + */ + +#include +#include +#include +#include +#include + +#define ZYNQMP_NR_RESETS (ZYNQMP_PM_RESET_END - ZYNQMP_PM_RESET_START) +#define ZYNQMP_RESET_ID ZYNQMP_PM_RESET_START + +struct zynqmp_reset_data { + struct reset_controller_dev rcdev; + const struct zynqmp_eemi_ops *eemi_ops; +}; + +static inline struct zynqmp_reset_data * +to_zynqmp_reset_data(struct reset_controller_dev *rcdev) +{ + return container_of(rcdev, struct zynqmp_reset_data, rcdev); +} + +static int zynqmp_reset_assert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct zynqmp_reset_data *priv = to_zynqmp_reset_data(rcdev); + + return priv->eemi_ops->reset_assert(ZYNQMP_RESET_ID + id, + PM_RESET_ACTION_ASSERT); +} + +static int zynqmp_reset_deassert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct zynqmp_reset_data *priv = to_zynqmp_reset_data(rcdev); + + return priv->eemi_ops->reset_assert(ZYNQMP_RESET_ID + id, + PM_RESET_ACTION_RELEASE); +} + +static int zynqmp_reset_status(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct zynqmp_reset_data *priv = to_zynqmp_reset_data(rcdev); + int val, err; + + err = priv->eemi_ops->reset_get_status(ZYNQMP_RESET_ID + id, &val); + if (err) + return err; + + return val; +} + +static int zynqmp_reset_reset(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct zynqmp_reset_data *priv = to_zynqmp_reset_data(rcdev); + + return priv->eemi_ops->reset_assert(ZYNQMP_RESET_ID + id, + PM_RESET_ACTION_PULSE); +} + +static struct reset_control_ops zynqmp_reset_ops = { + .reset = zynqmp_reset_reset, + .assert = zynqmp_reset_assert, + .deassert = zynqmp_reset_deassert, + .status = zynqmp_reset_status, +}; + +static int zynqmp_reset_probe(struct platform_device *pdev) +{ + struct zynqmp_reset_data *priv; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + platform_set_drvdata(pdev, priv); + + priv->eemi_ops = zynqmp_pm_get_eemi_ops(); + if (!priv->eemi_ops) + return -ENXIO; + + priv->rcdev.ops = &zynqmp_reset_ops; + priv->rcdev.owner = THIS_MODULE; + priv->rcdev.of_node = pdev->dev.of_node; + priv->rcdev.nr_resets = ZYNQMP_NR_RESETS; + + return devm_reset_controller_register(&pdev->dev, &priv->rcdev); +} + +static const struct of_device_id zynqmp_reset_dt_ids[] = { + { .compatible = "xlnx,zynqmp-reset", }, + { /* sentinel */ }, +}; + +static struct platform_driver zynqmp_reset_driver = { + .probe = zynqmp_reset_probe, + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = zynqmp_reset_dt_ids, + }, +}; + +static int __init zynqmp_reset_init(void) +{ + return platform_driver_register(&zynqmp_reset_driver); +} + +arch_initcall(zynqmp_reset_init); From 5953c887fb5fd8de8b3b716485a0457b93ba4ce2 Mon Sep 17 00:00:00 2001 From: Yangtao Li Date: Wed, 30 Jan 2019 10:21:44 -0800 Subject: [PATCH 054/113] firmware: ti_sci: Change to use DEFINE_SHOW_ATTRIBUTE macro Use DEFINE_SHOW_ATTRIBUTE macro to simplify the code. Reviewed-by: Nishanth Menon Signed-off-by: Yangtao Li Signed-off-by: Santosh Shilimkar --- drivers/firmware/ti_sci.c | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/drivers/firmware/ti_sci.c b/drivers/firmware/ti_sci.c index 69ed1464175c..3fbbb61012c4 100644 --- a/drivers/firmware/ti_sci.c +++ b/drivers/firmware/ti_sci.c @@ -146,25 +146,8 @@ static int ti_sci_debug_show(struct seq_file *s, void *unused) return 0; } -/** - * ti_sci_debug_open() - debug file open - * @inode: inode pointer - * @file: file pointer - * - * Return: result of single_open - */ -static int ti_sci_debug_open(struct inode *inode, struct file *file) -{ - return single_open(file, ti_sci_debug_show, inode->i_private); -} - -/* log file operations */ -static const struct file_operations ti_sci_debug_fops = { - .open = ti_sci_debug_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; +/* Provide the log file operations interface*/ +DEFINE_SHOW_ATTRIBUTE(ti_sci_debug); /** * ti_sci_debugfs_create() - Create log debug file From 2b13ef1f4261688c2dc5d67dbee69ed2fdddf53e Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Wed, 30 Jan 2019 10:21:44 -0800 Subject: [PATCH 055/113] 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(-) 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); From 722f761084bd1e1a26ba096b48ba8c09048413a1 Mon Sep 17 00:00:00 2001 From: Wei Yongjun Date: Wed, 16 Jan 2019 05:44:17 +0000 Subject: [PATCH 056/113] 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(-) 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, }; From 81fc035f07d230c0f687ef09d5ecf2c885dba8ae Mon Sep 17 00:00:00 2001 From: Eric Anholt Date: Wed, 12 Dec 2018 15:51:49 -0800 Subject: [PATCH 057/113] ARM: bcm283x: Extend the WDT DT node out to cover the whole PM block. (v4) It was covering part of the PM block's range, up to the WDT regs. To support the rest of the PM block's functionality, we need the full register range plus the AXI Async Bridge regs for PM sequencing. This doesn't convert any of the consumers over to the new binding yet, since we will need to be careful in coordinating our usage of firmware services that might power domains on and off versus the bcm2835-pm driver's access of those same domains. Signed-off-by: Eric Anholt Acked-by: Stefan Wahren Signed-off-by: Stefan Wahren --- arch/arm/boot/dts/bcm283x.dtsi | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/arch/arm/boot/dts/bcm283x.dtsi b/arch/arm/boot/dts/bcm283x.dtsi index 31b29646b14c..20ed8b1da11b 100644 --- a/arch/arm/boot/dts/bcm283x.dtsi +++ b/arch/arm/boot/dts/bcm283x.dtsi @@ -121,8 +121,17 @@ intc: interrupt-controller@7e00b200 { }; watchdog@7e100000 { - compatible = "brcm,bcm2835-pm-wdt"; - reg = <0x7e100000 0x28>; + compatible = "brcm,bcm2835-pm", "brcm,bcm2835-pm-wdt"; + #power-domain-cells = <1>; + #reset-cells = <1>; + reg = <0x7e100000 0x114>, + <0x7e00a000 0x24>; + clocks = <&clocks BCM2835_CLOCK_V3D>, + <&clocks BCM2835_CLOCK_PERI_IMAGE>, + <&clocks BCM2835_CLOCK_H264>, + <&clocks BCM2835_CLOCK_ISP>; + clock-names = "v3d", "peri_image", "h264", "isp"; + system-power-controller; }; clocks: cprman@7e101000 { From e1dc2b2e1bef7237fd8fc055fe1ec2a6ff001f91 Mon Sep 17 00:00:00 2001 From: Eric Anholt Date: Wed, 12 Dec 2018 15:51:50 -0800 Subject: [PATCH 058/113] ARM: bcm283x: Switch V3D over to using the PM driver instead of firmware. The GRAFX domain only contains V3D, and this driver should be the only accessor of V3D (firmware usage gets disabled when V3D is in the DT), so we can safely make Linux control the GRAFX and GRAFX_V3D power domains. Signed-off-by: Eric Anholt Acked-by: Stefan Wahren Signed-off-by: Stefan Wahren --- arch/arm/boot/dts/bcm2835-rpi.dtsi | 4 ---- arch/arm/boot/dts/bcm283x.dtsi | 4 +++- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/arch/arm/boot/dts/bcm2835-rpi.dtsi b/arch/arm/boot/dts/bcm2835-rpi.dtsi index 29f970f864dc..c6e2b2320abc 100644 --- a/arch/arm/boot/dts/bcm2835-rpi.dtsi +++ b/arch/arm/boot/dts/bcm2835-rpi.dtsi @@ -87,10 +87,6 @@ &usb { power-domains = <&power RPI_POWER_DOMAIN_USB>; }; -&v3d { - power-domains = <&power RPI_POWER_DOMAIN_V3D>; -}; - &hdmi { power-domains = <&power RPI_POWER_DOMAIN_HDMI>; status = "okay"; diff --git a/arch/arm/boot/dts/bcm283x.dtsi b/arch/arm/boot/dts/bcm283x.dtsi index 20ed8b1da11b..9777644c6c2b 100644 --- a/arch/arm/boot/dts/bcm283x.dtsi +++ b/arch/arm/boot/dts/bcm283x.dtsi @@ -3,6 +3,7 @@ #include #include #include +#include /* firmware-provided startup stubs live here, where the secondary CPUs are * spinning. @@ -120,7 +121,7 @@ intc: interrupt-controller@7e00b200 { #interrupt-cells = <2>; }; - watchdog@7e100000 { + pm: watchdog@7e100000 { compatible = "brcm,bcm2835-pm", "brcm,bcm2835-pm-wdt"; #power-domain-cells = <1>; #reset-cells = <1>; @@ -638,6 +639,7 @@ v3d: v3d@7ec00000 { compatible = "brcm,bcm2835-v3d"; reg = <0x7ec00000 0x1000>; interrupts = <1 10>; + power-domains = <&pm BCM2835_POWER_DOMAIN_GRAFX_V3D>; }; vc4: gpu { From 42bf4152d8a79f89f5456dee63a1f364fbce2dd6 Mon Sep 17 00:00:00 2001 From: Sumit Garg Date: Tue, 29 Jan 2019 11:19:36 +0530 Subject: [PATCH 059/113] tee: add supp_nowait flag in tee_context struct This flag indicates that requests in this context should not wait for tee-supplicant daemon to be started if not present and just return with an error code. It is needed for requests which should be non-blocking in nature like ones arising from TEE based kernel drivers or any in kernel api that uses TEE internal client interface. Signed-off-by: Sumit Garg Reviewed-by: Daniel Thompson Signed-off-by: Jens Wiklander --- drivers/tee/optee/supp.c | 10 +++++++++- drivers/tee/tee_core.c | 13 +++++++++++++ include/linux/tee_drv.h | 6 ++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/drivers/tee/optee/supp.c b/drivers/tee/optee/supp.c index 43626e15703a..92f56b8645e3 100644 --- a/drivers/tee/optee/supp.c +++ b/drivers/tee/optee/supp.c @@ -88,10 +88,18 @@ u32 optee_supp_thrd_req(struct tee_context *ctx, u32 func, size_t num_params, { struct optee *optee = tee_get_drvdata(ctx->teedev); struct optee_supp *supp = &optee->supp; - struct optee_supp_req *req = kzalloc(sizeof(*req), GFP_KERNEL); + struct optee_supp_req *req; bool interruptable; u32 ret; + /* + * Return in case there is no supplicant available and + * non-blocking request. + */ + if (!supp->ctx && ctx->supp_nowait) + return TEEC_ERROR_COMMUNICATION; + + req = kzalloc(sizeof(*req), GFP_KERNEL); if (!req) return TEEC_ERROR_OUT_OF_MEMORY; diff --git a/drivers/tee/tee_core.c b/drivers/tee/tee_core.c index 7b2bb4c50058..adf2588282fc 100644 --- a/drivers/tee/tee_core.c +++ b/drivers/tee/tee_core.c @@ -106,6 +106,11 @@ static int tee_open(struct inode *inode, struct file *filp) if (IS_ERR(ctx)) return PTR_ERR(ctx); + /* + * Default user-space behaviour is to wait for tee-supplicant + * if not present for any requests in this context. + */ + ctx->supp_nowait = false; filp->private_data = ctx; return 0; } @@ -982,6 +987,14 @@ tee_client_open_context(struct tee_context *start, } while (IS_ERR(ctx) && PTR_ERR(ctx) != -ENOMEM); put_device(put_dev); + /* + * Default behaviour for in kernel client is to not wait for + * tee-supplicant if not present for any requests in this context. + * Also this flag could be configured again before call to + * tee_client_open_session() if any in kernel client requires + * different behaviour. + */ + ctx->supp_nowait = true; return ctx; } EXPORT_SYMBOL_GPL(tee_client_open_context); diff --git a/include/linux/tee_drv.h b/include/linux/tee_drv.h index 6cfe05893a76..5076502c07d7 100644 --- a/include/linux/tee_drv.h +++ b/include/linux/tee_drv.h @@ -47,6 +47,11 @@ struct tee_shm_pool; * @releasing: flag that indicates if context is being released right now. * It is needed to break circular dependency on context during * shared memory release. + * @supp_nowait: flag that indicates that requests in this context should not + * wait for tee-supplicant daemon to be started if not present + * and just return with an error code. It is needed for requests + * that arises from TEE based kernel drivers that should be + * non-blocking in nature. */ struct tee_context { struct tee_device *teedev; @@ -54,6 +59,7 @@ struct tee_context { void *data; struct kref refcount; bool releasing; + bool supp_nowait; }; struct tee_param_memref { From 0fc1db9d105915021260eb241661b8e96f5c0f1a Mon Sep 17 00:00:00 2001 From: Sumit Garg Date: Tue, 29 Jan 2019 11:19:35 +0530 Subject: [PATCH 060/113] tee: add bus driver framework for TEE based devices Introduce a generic TEE bus driver concept for TEE based kernel drivers which would like to communicate with TEE based devices/services. Also add support in module device table for these new TEE based devices. In this TEE bus concept, devices/services are identified via Universally Unique Identifier (UUID) and drivers register a table of device UUIDs which they can support. So this TEE bus framework registers following apis: - match(): Iterates over the driver UUID table to find a corresponding match for device UUID. If a match is found, then this particular device is probed via corresponding probe api registered by the driver. This process happens whenever a device or a driver is registered with TEE bus. - uevent(): Notifies user-space (udev) whenever a new device is registered on this bus for auto-loading of modularized drivers. Also this framework allows for device enumeration to be specific to corresponding TEE implementation like OP-TEE etc. Signed-off-by: Sumit Garg Reviewed-by: Daniel Thompson Reviewed-by: Bhupesh Sharma Signed-off-by: Jens Wiklander --- drivers/tee/tee_core.c | 54 ++++++++++++++++++++++++++++--- include/linux/mod_devicetable.h | 9 ++++++ include/linux/tee_drv.h | 32 +++++++++++++++++- scripts/mod/devicetable-offsets.c | 3 ++ scripts/mod/file2alias.c | 19 +++++++++++ 5 files changed, 112 insertions(+), 5 deletions(-) diff --git a/drivers/tee/tee_core.c b/drivers/tee/tee_core.c index adf2588282fc..25f3b9cc8908 100644 --- a/drivers/tee/tee_core.c +++ b/drivers/tee/tee_core.c @@ -15,7 +15,6 @@ #define pr_fmt(fmt) "%s: " fmt, __func__ #include -#include #include #include #include @@ -1040,6 +1039,39 @@ int tee_client_invoke_func(struct tee_context *ctx, } EXPORT_SYMBOL_GPL(tee_client_invoke_func); +static int tee_client_device_match(struct device *dev, + struct device_driver *drv) +{ + const struct tee_client_device_id *id_table; + struct tee_client_device *tee_device; + + id_table = to_tee_client_driver(drv)->id_table; + tee_device = to_tee_client_device(dev); + + while (!uuid_is_null(&id_table->uuid)) { + if (uuid_equal(&tee_device->id.uuid, &id_table->uuid)) + return 1; + id_table++; + } + + return 0; +} + +static int tee_client_device_uevent(struct device *dev, + struct kobj_uevent_env *env) +{ + uuid_t *dev_id = &to_tee_client_device(dev)->id.uuid; + + return add_uevent_var(env, "MODALIAS=tee:%pUb", dev_id); +} + +struct bus_type tee_bus_type = { + .name = "tee", + .match = tee_client_device_match, + .uevent = tee_client_device_uevent, +}; +EXPORT_SYMBOL_GPL(tee_bus_type); + static int __init tee_init(void) { int rc; @@ -1053,18 +1085,32 @@ static int __init tee_init(void) rc = alloc_chrdev_region(&tee_devt, 0, TEE_NUM_DEVICES, "tee"); if (rc) { pr_err("failed to allocate char dev region\n"); - class_destroy(tee_class); - tee_class = NULL; + goto out_unreg_class; } + rc = bus_register(&tee_bus_type); + if (rc) { + pr_err("failed to register tee bus\n"); + goto out_unreg_chrdev; + } + + return 0; + +out_unreg_chrdev: + unregister_chrdev_region(tee_devt, TEE_NUM_DEVICES); +out_unreg_class: + class_destroy(tee_class); + tee_class = NULL; + return rc; } static void __exit tee_exit(void) { + bus_unregister(&tee_bus_type); + unregister_chrdev_region(tee_devt, TEE_NUM_DEVICES); class_destroy(tee_class); tee_class = NULL; - unregister_chrdev_region(tee_devt, TEE_NUM_DEVICES); } subsys_initcall(tee_init); diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h index f9bd2f34b99f..14eaeeb46f41 100644 --- a/include/linux/mod_devicetable.h +++ b/include/linux/mod_devicetable.h @@ -779,4 +779,13 @@ struct typec_device_id { kernel_ulong_t driver_data; }; +/** + * struct tee_client_device_id - tee based device identifier + * @uuid: For TEE based client devices we use the device uuid as + * the identifier. + */ +struct tee_client_device_id { + uuid_t uuid; +}; + #endif /* LINUX_MOD_DEVICETABLE_H */ diff --git a/include/linux/tee_drv.h b/include/linux/tee_drv.h index 5076502c07d7..56d7f1b4516d 100644 --- a/include/linux/tee_drv.h +++ b/include/linux/tee_drv.h @@ -15,11 +15,14 @@ #ifndef __TEE_DRV_H #define __TEE_DRV_H -#include +#include #include #include #include +#include #include +#include +#include /* * The file describes the API provided by the generic TEE driver to the @@ -544,4 +547,31 @@ static inline bool tee_param_is_memref(struct tee_param *param) } } +extern struct bus_type tee_bus_type; + +/** + * struct tee_client_device - tee based device + * @id: device identifier + * @dev: device structure + */ +struct tee_client_device { + struct tee_client_device_id id; + struct device dev; +}; + +#define to_tee_client_device(d) container_of(d, struct tee_client_device, dev) + +/** + * struct tee_client_driver - tee client driver + * @id_table: device id table supported by this driver + * @driver: driver structure + */ +struct tee_client_driver { + const struct tee_client_device_id *id_table; + struct device_driver driver; +}; + +#define to_tee_client_driver(d) \ + container_of(d, struct tee_client_driver, driver) + #endif /*__TEE_DRV_H*/ diff --git a/scripts/mod/devicetable-offsets.c b/scripts/mod/devicetable-offsets.c index 293004499b4d..160718383a71 100644 --- a/scripts/mod/devicetable-offsets.c +++ b/scripts/mod/devicetable-offsets.c @@ -225,5 +225,8 @@ int main(void) DEVID_FIELD(typec_device_id, svid); DEVID_FIELD(typec_device_id, mode); + DEVID(tee_client_device_id); + DEVID_FIELD(tee_client_device_id, uuid); + return 0; } diff --git a/scripts/mod/file2alias.c b/scripts/mod/file2alias.c index a37af7d71973..d0e41723627f 100644 --- a/scripts/mod/file2alias.c +++ b/scripts/mod/file2alias.c @@ -37,6 +37,9 @@ typedef unsigned char __u8; typedef struct { __u8 b[16]; } uuid_le; +typedef struct { + __u8 b[16]; +} uuid_t; /* Big exception to the "don't include kernel headers into userspace, which * even potentially has different endianness and word sizes, since @@ -1287,6 +1290,21 @@ static int do_typec_entry(const char *filename, void *symval, char *alias) return 1; } +/* Looks like: tee:uuid */ +static int do_tee_entry(const char *filename, void *symval, char *alias) +{ + DEF_FIELD(symval, tee_client_device_id, uuid); + + sprintf(alias, "tee:%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", + uuid.b[0], uuid.b[1], uuid.b[2], uuid.b[3], uuid.b[4], + uuid.b[5], uuid.b[6], uuid.b[7], uuid.b[8], uuid.b[9], + uuid.b[10], uuid.b[11], uuid.b[12], uuid.b[13], uuid.b[14], + uuid.b[15]); + + add_wildcard(alias); + return 1; +} + /* Does namelen bytes of name exactly match the symbol? */ static bool sym_is(const char *name, unsigned namelen, const char *symbol) { @@ -1357,6 +1375,7 @@ static const struct devtable devtable[] = { {"fslmc", SIZE_fsl_mc_device_id, do_fsl_mc_entry}, {"tbsvc", SIZE_tb_service_id, do_tbsvc_entry}, {"typec", SIZE_typec_device_id, do_typec_entry}, + {"tee", SIZE_tee_client_device_id, do_tee_entry}, }; /* Create MODULE_ALIAS() statements. From c3fa24af92445c419c19df5981ab4e41a3ae3361 Mon Sep 17 00:00:00 2001 From: Sumit Garg Date: Tue, 29 Jan 2019 11:19:37 +0530 Subject: [PATCH 061/113] tee: optee: add TEE bus device enumeration support OP-TEE provides a pseudo TA to enumerate TAs which can act as devices/ services for TEE bus. So implement device enumeration using invoke function: PTA_CMD_GET_DEVICES provided by pseudo TA to fetch array of device UUIDs. Also register these enumerated devices with TEE bus as "optee-clntX" device. Signed-off-by: Sumit Garg Reviewed-by: Daniel Thompson [jw: fix optee_enumerate_devices() with no devices found] Signed-off-by: Jens Wiklander --- drivers/tee/optee/Makefile | 1 + drivers/tee/optee/core.c | 4 + drivers/tee/optee/device.c | 155 ++++++++++++++++++++++++++++++ drivers/tee/optee/optee_private.h | 3 + 4 files changed, 163 insertions(+) create mode 100644 drivers/tee/optee/device.c diff --git a/drivers/tee/optee/Makefile b/drivers/tee/optee/Makefile index 48d262ae2f04..56263ae3b1d7 100644 --- a/drivers/tee/optee/Makefile +++ b/drivers/tee/optee/Makefile @@ -5,3 +5,4 @@ optee-objs += call.o optee-objs += rpc.o optee-objs += supp.o optee-objs += shm_pool.o +optee-objs += device.o diff --git a/drivers/tee/optee/core.c b/drivers/tee/optee/core.c index e5efce3c08e2..ac59c77841a4 100644 --- a/drivers/tee/optee/core.c +++ b/drivers/tee/optee/core.c @@ -634,6 +634,10 @@ static struct optee *optee_probe(struct device_node *np) if (optee->sec_caps & OPTEE_SMC_SEC_CAP_DYNAMIC_SHM) pr_info("dynamic shared memory is enabled\n"); + rc = optee_enumerate_devices(); + if (rc) + goto err; + pr_info("initialized driver\n"); return optee; err: diff --git a/drivers/tee/optee/device.c b/drivers/tee/optee/device.c new file mode 100644 index 000000000000..5e4938bbef2b --- /dev/null +++ b/drivers/tee/optee/device.c @@ -0,0 +1,155 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019 Linaro Ltd. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include "optee_private.h" + +/* + * Get device UUIDs + * + * [out] memref[0] Array of device UUIDs + * + * Return codes: + * TEE_SUCCESS - Invoke command success + * TEE_ERROR_BAD_PARAMETERS - Incorrect input param + * TEE_ERROR_SHORT_BUFFER - Output buffer size less than required + */ +#define PTA_CMD_GET_DEVICES 0x0 + +static int optee_ctx_match(struct tee_ioctl_version_data *ver, const void *data) +{ + if (ver->impl_id == TEE_IMPL_ID_OPTEE) + return 1; + else + return 0; +} + +static int get_devices(struct tee_context *ctx, u32 session, + struct tee_shm *device_shm, u32 *shm_size) +{ + u32 ret = 0; + struct tee_ioctl_invoke_arg inv_arg = {0}; + struct tee_param param[4] = {0}; + + /* Invoke PTA_CMD_GET_DEVICES function */ + inv_arg.func = PTA_CMD_GET_DEVICES; + inv_arg.session = session; + inv_arg.num_params = 4; + + /* Fill invoke cmd params */ + param[0].attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT; + param[0].u.memref.shm = device_shm; + param[0].u.memref.size = *shm_size; + param[0].u.memref.shm_offs = 0; + + ret = tee_client_invoke_func(ctx, &inv_arg, param); + if ((ret < 0) || ((inv_arg.ret != TEEC_SUCCESS) && + (inv_arg.ret != TEEC_ERROR_SHORT_BUFFER))) { + pr_err("PTA_CMD_GET_DEVICES invoke function err: %x\n", + inv_arg.ret); + return -EINVAL; + } + + *shm_size = param[0].u.memref.size; + + return 0; +} + +static int optee_register_device(const uuid_t *device_uuid, u32 device_id) +{ + struct tee_client_device *optee_device = NULL; + int rc; + + optee_device = kzalloc(sizeof(*optee_device), GFP_KERNEL); + if (!optee_device) + return -ENOMEM; + + optee_device->dev.bus = &tee_bus_type; + dev_set_name(&optee_device->dev, "optee-clnt%u", device_id); + uuid_copy(&optee_device->id.uuid, device_uuid); + + rc = device_register(&optee_device->dev); + if (rc) { + pr_err("device registration failed, err: %d\n", rc); + kfree(optee_device); + } + + return rc; +} + +int optee_enumerate_devices(void) +{ + const uuid_t pta_uuid = + UUID_INIT(0x7011a688, 0xddde, 0x4053, + 0xa5, 0xa9, 0x7b, 0x3c, 0x4d, 0xdf, 0x13, 0xb8); + struct tee_ioctl_open_session_arg sess_arg = {0}; + struct tee_shm *device_shm = NULL; + const uuid_t *device_uuid = NULL; + struct tee_context *ctx = NULL; + u32 shm_size = 0, idx, num_devices = 0; + int rc; + + /* Open context with OP-TEE driver */ + ctx = tee_client_open_context(NULL, optee_ctx_match, NULL, NULL); + if (IS_ERR(ctx)) + return -ENODEV; + + /* Open session with device enumeration pseudo TA */ + memcpy(sess_arg.uuid, pta_uuid.b, TEE_IOCTL_UUID_LEN); + sess_arg.clnt_login = TEE_IOCTL_LOGIN_PUBLIC; + sess_arg.num_params = 0; + + rc = tee_client_open_session(ctx, &sess_arg, NULL); + if ((rc < 0) || (sess_arg.ret != TEEC_SUCCESS)) { + /* Device enumeration pseudo TA not found */ + rc = 0; + goto out_ctx; + } + + rc = get_devices(ctx, sess_arg.session, NULL, &shm_size); + if (rc < 0 || !shm_size) + goto out_sess; + + device_shm = tee_shm_alloc(ctx, shm_size, + TEE_SHM_MAPPED | TEE_SHM_DMA_BUF); + if (IS_ERR(device_shm)) { + pr_err("tee_shm_alloc failed\n"); + rc = PTR_ERR(device_shm); + goto out_sess; + } + + rc = get_devices(ctx, sess_arg.session, device_shm, &shm_size); + if (rc < 0) + goto out_shm; + + device_uuid = tee_shm_get_va(device_shm, 0); + if (IS_ERR(device_uuid)) { + pr_err("tee_shm_get_va failed\n"); + rc = PTR_ERR(device_uuid); + goto out_shm; + } + + num_devices = shm_size / sizeof(uuid_t); + + for (idx = 0; idx < num_devices; idx++) { + rc = optee_register_device(&device_uuid[idx], idx); + if (rc) + goto out_shm; + } + +out_shm: + tee_shm_free(device_shm); +out_sess: + tee_client_close_session(ctx, sess_arg.session); +out_ctx: + tee_client_close_context(ctx); + + return rc; +} diff --git a/drivers/tee/optee/optee_private.h b/drivers/tee/optee/optee_private.h index 35e79386c556..a5e84afd5013 100644 --- a/drivers/tee/optee/optee_private.h +++ b/drivers/tee/optee/optee_private.h @@ -28,6 +28,7 @@ #define TEEC_ERROR_BAD_PARAMETERS 0xFFFF0006 #define TEEC_ERROR_COMMUNICATION 0xFFFF000E #define TEEC_ERROR_OUT_OF_MEMORY 0xFFFF000C +#define TEEC_ERROR_SHORT_BUFFER 0xFFFF0010 #define TEEC_ORIGIN_COMMS 0x00000002 @@ -181,6 +182,8 @@ void optee_free_pages_list(void *array, size_t num_entries); void optee_fill_pages_list(u64 *dst, struct page **pages, int num_pages, size_t page_offset); +int optee_enumerate_devices(void); + /* * Small helpers */ From 5fe8b1cc6a03c46b3061e808256d39dcebd0d0f0 Mon Sep 17 00:00:00 2001 From: Sumit Garg Date: Tue, 29 Jan 2019 11:19:38 +0530 Subject: [PATCH 062/113] hwrng: add OP-TEE based rng driver On ARM SoC's with TrustZone enabled, peripherals like entropy sources might not be accessible to normal world (linux in this case) and rather accessible to secure world (OP-TEE in this case) only. So this driver aims to provides a generic interface to OP-TEE based random number generator service. This driver registers on TEE bus to interact with OP-TEE based rng device/service. Signed-off-by: Sumit Garg Reviewed-by: Daniel Thompson Acked-by: Herbert Xu Signed-off-by: Jens Wiklander --- MAINTAINERS | 5 + drivers/char/hw_random/Kconfig | 15 ++ drivers/char/hw_random/Makefile | 1 + drivers/char/hw_random/optee-rng.c | 298 +++++++++++++++++++++++++++++ 4 files changed, 319 insertions(+) create mode 100644 drivers/char/hw_random/optee-rng.c diff --git a/MAINTAINERS b/MAINTAINERS index 51029a425dbe..dcef7e938f2f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11262,6 +11262,11 @@ M: Jens Wiklander S: Maintained F: drivers/tee/optee/ +OP-TEE RANDOM NUMBER GENERATOR (RNG) DRIVER +M: Sumit Garg +S: Maintained +F: drivers/char/hw_random/optee-rng.c + OPA-VNIC DRIVER M: Dennis Dalessandro M: Niranjana Vishwanathapura diff --git a/drivers/char/hw_random/Kconfig b/drivers/char/hw_random/Kconfig index dac895dc01b9..25a7d8ffdb5d 100644 --- a/drivers/char/hw_random/Kconfig +++ b/drivers/char/hw_random/Kconfig @@ -424,6 +424,21 @@ config HW_RANDOM_EXYNOS will be called exynos-trng. If unsure, say Y. + +config HW_RANDOM_OPTEE + tristate "OP-TEE based Random Number Generator support" + depends on OPTEE + default HW_RANDOM + help + This driver provides support for OP-TEE based Random Number + Generator on ARM SoCs where hardware entropy sources are not + accessible to normal world (Linux). + + To compile this driver as a module, choose M here: the module + will be called optee-rng. + + If unsure, say Y. + endif # HW_RANDOM config UML_RANDOM diff --git a/drivers/char/hw_random/Makefile b/drivers/char/hw_random/Makefile index e35ec3ce3a20..7c9ef4a7667f 100644 --- a/drivers/char/hw_random/Makefile +++ b/drivers/char/hw_random/Makefile @@ -38,3 +38,4 @@ obj-$(CONFIG_HW_RANDOM_CAVIUM) += cavium-rng.o cavium-rng-vf.o obj-$(CONFIG_HW_RANDOM_MTK) += mtk-rng.o obj-$(CONFIG_HW_RANDOM_S390) += s390-trng.o obj-$(CONFIG_HW_RANDOM_KEYSTONE) += ks-sa-rng.o +obj-$(CONFIG_HW_RANDOM_OPTEE) += optee-rng.o diff --git a/drivers/char/hw_random/optee-rng.c b/drivers/char/hw_random/optee-rng.c new file mode 100644 index 000000000000..2b9fc8ac5500 --- /dev/null +++ b/drivers/char/hw_random/optee-rng.c @@ -0,0 +1,298 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018-2019 Linaro Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "optee-rng" + +#define TEE_ERROR_HEALTH_TEST_FAIL 0x00000001 + +/* + * TA_CMD_GET_ENTROPY - Get Entropy from RNG + * + * param[0] (inout memref) - Entropy buffer memory reference + * param[1] unused + * param[2] unused + * param[3] unused + * + * Result: + * TEE_SUCCESS - Invoke command success + * TEE_ERROR_BAD_PARAMETERS - Incorrect input param + * TEE_ERROR_NOT_SUPPORTED - Requested entropy size greater than size of pool + * TEE_ERROR_HEALTH_TEST_FAIL - Continuous health testing failed + */ +#define TA_CMD_GET_ENTROPY 0x0 + +/* + * TA_CMD_GET_RNG_INFO - Get RNG information + * + * param[0] (out value) - value.a: RNG data-rate in bytes per second + * value.b: Quality/Entropy per 1024 bit of data + * param[1] unused + * param[2] unused + * param[3] unused + * + * Result: + * TEE_SUCCESS - Invoke command success + * TEE_ERROR_BAD_PARAMETERS - Incorrect input param + */ +#define TA_CMD_GET_RNG_INFO 0x1 + +#define MAX_ENTROPY_REQ_SZ (4 * 1024) + +/** + * struct optee_rng_private - OP-TEE Random Number Generator private data + * @dev: OP-TEE based RNG device. + * @ctx: OP-TEE context handler. + * @session_id: RNG TA session identifier. + * @data_rate: RNG data rate. + * @entropy_shm_pool: Memory pool shared with RNG device. + * @optee_rng: OP-TEE RNG driver structure. + */ +struct optee_rng_private { + struct device *dev; + struct tee_context *ctx; + u32 session_id; + u32 data_rate; + struct tee_shm *entropy_shm_pool; + struct hwrng optee_rng; +}; + +#define to_optee_rng_private(r) \ + container_of(r, struct optee_rng_private, optee_rng) + +static size_t get_optee_rng_data(struct optee_rng_private *pvt_data, + void *buf, size_t req_size) +{ + u32 ret = 0; + u8 *rng_data = NULL; + size_t rng_size = 0; + struct tee_ioctl_invoke_arg inv_arg = {0}; + struct tee_param param[4] = {0}; + + /* Invoke TA_CMD_GET_ENTROPY function of Trusted App */ + inv_arg.func = TA_CMD_GET_ENTROPY; + inv_arg.session = pvt_data->session_id; + inv_arg.num_params = 4; + + /* Fill invoke cmd params */ + param[0].attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT; + param[0].u.memref.shm = pvt_data->entropy_shm_pool; + param[0].u.memref.size = req_size; + param[0].u.memref.shm_offs = 0; + + ret = tee_client_invoke_func(pvt_data->ctx, &inv_arg, param); + if ((ret < 0) || (inv_arg.ret != 0)) { + dev_err(pvt_data->dev, "TA_CMD_GET_ENTROPY invoke err: %x\n", + inv_arg.ret); + return 0; + } + + rng_data = tee_shm_get_va(pvt_data->entropy_shm_pool, 0); + if (IS_ERR(rng_data)) { + dev_err(pvt_data->dev, "tee_shm_get_va failed\n"); + return 0; + } + + rng_size = param[0].u.memref.size; + memcpy(buf, rng_data, rng_size); + + return rng_size; +} + +static int optee_rng_read(struct hwrng *rng, void *buf, size_t max, bool wait) +{ + struct optee_rng_private *pvt_data = to_optee_rng_private(rng); + size_t read = 0, rng_size = 0; + int timeout = 1; + u8 *data = buf; + + if (max > MAX_ENTROPY_REQ_SZ) + max = MAX_ENTROPY_REQ_SZ; + + while (read == 0) { + rng_size = get_optee_rng_data(pvt_data, data, (max - read)); + + data += rng_size; + read += rng_size; + + if (wait) { + if (timeout-- == 0) + return read; + msleep((1000 * (max - read)) / pvt_data->data_rate); + } else { + return read; + } + } + + return read; +} + +static int optee_rng_init(struct hwrng *rng) +{ + struct optee_rng_private *pvt_data = to_optee_rng_private(rng); + struct tee_shm *entropy_shm_pool = NULL; + + entropy_shm_pool = tee_shm_alloc(pvt_data->ctx, MAX_ENTROPY_REQ_SZ, + TEE_SHM_MAPPED | TEE_SHM_DMA_BUF); + if (IS_ERR(entropy_shm_pool)) { + dev_err(pvt_data->dev, "tee_shm_alloc failed\n"); + return PTR_ERR(entropy_shm_pool); + } + + pvt_data->entropy_shm_pool = entropy_shm_pool; + + return 0; +} + +static void optee_rng_cleanup(struct hwrng *rng) +{ + struct optee_rng_private *pvt_data = to_optee_rng_private(rng); + + tee_shm_free(pvt_data->entropy_shm_pool); +} + +static struct optee_rng_private pvt_data = { + .optee_rng = { + .name = DRIVER_NAME, + .init = optee_rng_init, + .cleanup = optee_rng_cleanup, + .read = optee_rng_read, + } +}; + +static int get_optee_rng_info(struct device *dev) +{ + u32 ret = 0; + struct tee_ioctl_invoke_arg inv_arg = {0}; + struct tee_param param[4] = {0}; + + /* Invoke TA_CMD_GET_RNG_INFO function of Trusted App */ + inv_arg.func = TA_CMD_GET_RNG_INFO; + inv_arg.session = pvt_data.session_id; + inv_arg.num_params = 4; + + /* Fill invoke cmd params */ + param[0].attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_OUTPUT; + + ret = tee_client_invoke_func(pvt_data.ctx, &inv_arg, param); + if ((ret < 0) || (inv_arg.ret != 0)) { + dev_err(dev, "TA_CMD_GET_RNG_INFO invoke err: %x\n", + inv_arg.ret); + return -EINVAL; + } + + pvt_data.data_rate = param[0].u.value.a; + pvt_data.optee_rng.quality = param[0].u.value.b; + + return 0; +} + +static int optee_ctx_match(struct tee_ioctl_version_data *ver, const void *data) +{ + if (ver->impl_id == TEE_IMPL_ID_OPTEE) + return 1; + else + return 0; +} + +static int optee_rng_probe(struct device *dev) +{ + struct tee_client_device *rng_device = to_tee_client_device(dev); + int ret = 0, err = -ENODEV; + struct tee_ioctl_open_session_arg sess_arg = {0}; + + /* Open context with TEE driver */ + pvt_data.ctx = tee_client_open_context(NULL, optee_ctx_match, NULL, + NULL); + if (IS_ERR(pvt_data.ctx)) + return -ENODEV; + + /* Open session with hwrng Trusted App */ + memcpy(sess_arg.uuid, rng_device->id.uuid.b, TEE_IOCTL_UUID_LEN); + sess_arg.clnt_login = TEE_IOCTL_LOGIN_PUBLIC; + sess_arg.num_params = 0; + + ret = tee_client_open_session(pvt_data.ctx, &sess_arg, NULL); + if ((ret < 0) || (sess_arg.ret != 0)) { + dev_err(dev, "tee_client_open_session failed, err: %x\n", + sess_arg.ret); + err = -EINVAL; + goto out_ctx; + } + pvt_data.session_id = sess_arg.session; + + err = get_optee_rng_info(dev); + if (err) + goto out_sess; + + err = hwrng_register(&pvt_data.optee_rng); + if (err) { + dev_err(dev, "hwrng registration failed (%d)\n", err); + goto out_sess; + } + + pvt_data.dev = dev; + + return 0; + +out_sess: + tee_client_close_session(pvt_data.ctx, pvt_data.session_id); +out_ctx: + tee_client_close_context(pvt_data.ctx); + + return err; +} + +static int optee_rng_remove(struct device *dev) +{ + hwrng_unregister(&pvt_data.optee_rng); + tee_client_close_session(pvt_data.ctx, pvt_data.session_id); + tee_client_close_context(pvt_data.ctx); + + return 0; +} + +const struct tee_client_device_id optee_rng_id_table[] = { + {UUID_INIT(0xab7a617c, 0xb8e7, 0x4d8f, + 0x83, 0x01, 0xd0, 0x9b, 0x61, 0x03, 0x6b, 0x64)}, + {} +}; + +MODULE_DEVICE_TABLE(tee, optee_rng_id_table); + +static struct tee_client_driver optee_rng_driver = { + .id_table = optee_rng_id_table, + .driver = { + .name = DRIVER_NAME, + .bus = &tee_bus_type, + .probe = optee_rng_probe, + .remove = optee_rng_remove, + }, +}; + +static int __init optee_rng_mod_init(void) +{ + return driver_register(&optee_rng_driver.driver); +} + +static void __exit optee_rng_mod_exit(void) +{ + driver_unregister(&optee_rng_driver.driver); +} + +module_init(optee_rng_mod_init); +module_exit(optee_rng_mod_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Sumit Garg "); +MODULE_DESCRIPTION("OP-TEE based random number generator driver"); From b4aa93bca91318a8cf832b9fd34a830e44660d50 Mon Sep 17 00:00:00 2001 From: Evan Green Date: Wed, 2 Jan 2019 16:02:12 -0800 Subject: [PATCH 063/113] 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(-) 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); From f58b0f9d9dd6d033396561517bb191d904df9e54 Mon Sep 17 00:00:00 2001 From: Evan Green Date: Wed, 2 Jan 2019 16:02:13 -0800 Subject: [PATCH 064/113] 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(-) 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, 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: [PATCH 065/113] 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(-) 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); } From baef1c90aac7e5bf13f0360a3b334825a23d31a1 Mon Sep 17 00:00:00 2001 From: Stephen Boyd Date: Tue, 15 Jan 2019 14:54:47 -0800 Subject: [PATCH 066/113] 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(-) 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; } From 8cd09a3dd3e176c62da67efcd477a44a8d87185e Mon Sep 17 00:00:00 2001 From: Alexey Khoroshilov Date: Sat, 8 Dec 2018 01:57:04 +0300 Subject: [PATCH 067/113] 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(-) 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) From 4e2256d31f0f24107c36dae35a1d84dff0fced30 Mon Sep 17 00:00:00 2001 From: Craig Tatlor Date: Sat, 11 Aug 2018 17:24:17 +0100 Subject: [PATCH 068/113] 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 --- Documentation/devicetree/bindings/soc/qcom/qcom,smd-rpm.txt | 1 + drivers/soc/qcom/smd-rpm.c | 1 + 2 files changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,smd-rpm.txt b/Documentation/devicetree/bindings/soc/qcom/qcom,smd-rpm.txt index ec95705ba692..f3fa313963d5 100644 --- a/Documentation/devicetree/bindings/soc/qcom/qcom,smd-rpm.txt +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,smd-rpm.txt @@ -23,6 +23,7 @@ resources. "qcom,rpm-msm8916" "qcom,rpm-msm8974" "qcom,rpm-msm8998" + "qcom,rpm-sdm660" "qcom,rpm-qcs404" - qcom,smd-channels: 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" }, {} }; From c1959066ac02bd123201352ff5a8c506469a1645 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Mon, 4 Feb 2019 17:10:05 +0300 Subject: [PATCH 069/113] 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(-) 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, From fe6f42cf6eb3183ebd6ab6b0b7dcbee2600c2baa Mon Sep 17 00:00:00 2001 From: Nava kishore Manne Date: Wed, 6 Feb 2019 16:37:19 +0530 Subject: [PATCH 070/113] firmware: xilinx: Add zynqmp_pm_get_chipid() API This patch adds a new API to provide access to the hardware related data like soc revision, IDCODE... etc. Signed-off-by: Nava kishore Manne Signed-off-by: Michal Simek --- drivers/firmware/xilinx/zynqmp.c | 24 ++++++++++++++++++++++++ include/linux/firmware/xlnx-zynqmp.h | 2 ++ 2 files changed, 26 insertions(+) diff --git a/drivers/firmware/xilinx/zynqmp.c b/drivers/firmware/xilinx/zynqmp.c index 70b50377ae5f..16a23bc4c2c3 100644 --- a/drivers/firmware/xilinx/zynqmp.c +++ b/drivers/firmware/xilinx/zynqmp.c @@ -186,6 +186,29 @@ static int zynqmp_pm_get_api_version(u32 *version) return ret; } +/** + * zynqmp_pm_get_chipid - Get silicon ID registers + * @idcode: IDCODE register + * @version: version register + * + * Return: Returns the status of the operation and the idcode and version + * registers in @idcode and @version. + */ +static int zynqmp_pm_get_chipid(u32 *idcode, u32 *version) +{ + u32 ret_payload[PAYLOAD_ARG_CNT]; + int ret; + + if (!idcode || !version) + return -EINVAL; + + ret = zynqmp_pm_invoke_fn(PM_GET_CHIPID, 0, 0, 0, 0, ret_payload); + *idcode = ret_payload[1]; + *version = ret_payload[2]; + + return ret; +} + /** * zynqmp_pm_get_trustzone_version() - Get secure trustzone firmware version * @version: Returned version value @@ -509,6 +532,7 @@ static int zynqmp_pm_reset_get_status(const enum zynqmp_pm_reset reset, static const struct zynqmp_eemi_ops eemi_ops = { .get_api_version = zynqmp_pm_get_api_version, + .get_chipid = zynqmp_pm_get_chipid, .query_data = zynqmp_pm_query_data, .clock_enable = zynqmp_pm_clock_enable, .clock_disable = zynqmp_pm_clock_disable, diff --git a/include/linux/firmware/xlnx-zynqmp.h b/include/linux/firmware/xlnx-zynqmp.h index 07c587a0b06e..5a1f19848100 100644 --- a/include/linux/firmware/xlnx-zynqmp.h +++ b/include/linux/firmware/xlnx-zynqmp.h @@ -36,6 +36,7 @@ enum pm_api_id { PM_GET_API_VERSION = 1, PM_RESET_ASSERT = 17, PM_RESET_GET_STATUS, + PM_GET_CHIPID = 24, PM_IOCTL = 34, PM_QUERY_DATA, PM_CLOCK_ENABLE, @@ -224,6 +225,7 @@ struct zynqmp_pm_query_data { struct zynqmp_eemi_ops { int (*get_api_version)(u32 *version); + int (*get_chipid)(u32 *idcode, u32 *version); int (*query_data)(struct zynqmp_pm_query_data qdata, u32 *out); int (*clock_enable)(u32 clock_id); int (*clock_disable)(u32 clock_id); From 940c2361b56ac82a555ce88d77bf18e80a8f31d0 Mon Sep 17 00:00:00 2001 From: Nava kishore Manne Date: Wed, 6 Feb 2019 16:37:20 +0530 Subject: [PATCH 071/113] dt-bindings: nvmem: Add bindings for ZynqMP nvmem driver Add documentation to describe Xilinx ZynqMP nvmem driver bindings. Signed-off-by: Nava kishore Manne Reviewed-by: Rob Herring Acked-by: Srinivas Kandagatla Signed-off-by: Michal Simek --- .../bindings/nvmem/xlnx,zynqmp-nvmem.txt | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 Documentation/devicetree/bindings/nvmem/xlnx,zynqmp-nvmem.txt diff --git a/Documentation/devicetree/bindings/nvmem/xlnx,zynqmp-nvmem.txt b/Documentation/devicetree/bindings/nvmem/xlnx,zynqmp-nvmem.txt new file mode 100644 index 000000000000..4881561b3a02 --- /dev/null +++ b/Documentation/devicetree/bindings/nvmem/xlnx,zynqmp-nvmem.txt @@ -0,0 +1,46 @@ +-------------------------------------------------------------------------- += Zynq UltraScale+ MPSoC nvmem firmware driver binding = +-------------------------------------------------------------------------- +The nvmem_firmware node provides access to the hardware related data +like soc revision, IDCODE... etc, By using the firmware interface. + +Required properties: +- compatible: should be "xlnx,zynqmp-nvmem-fw" + += Data cells = +Are child nodes of silicon id, bindings of which as described in +bindings/nvmem/nvmem.txt + +------- + Example +------- +firmware { + zynqmp_firmware: zynqmp-firmware { + compatible = "xlnx,zynqmp-firmware"; + method = "smc"; + + nvmem_firmware { + compatible = "xlnx,zynqmp-nvmem-fw"; + #address-cells = <1>; + #size-cells = <1>; + + /* Data cells */ + soc_revision: soc_revision { + reg = <0x0 0x4>; + }; + }; + }; +}; + += Data consumers = +Are device nodes which consume nvmem data cells. + +For example: + pcap { + ... + + nvmem-cells = <&soc_revision>; + nvmem-cell-names = "soc_revision"; + + ... + }; From 4640fa1833fe8579c07a7b20b11f110803082e9f Mon Sep 17 00:00:00 2001 From: Nava kishore Manne Date: Wed, 6 Feb 2019 16:37:21 +0530 Subject: [PATCH 072/113] nvmem: zynqmp: Added zynqmp nvmem firmware driver This patch adds zynqmp nvmem firmware driver to access the SoC revision information from the hardware register. Signed-off-by: Nava kishore Manne Acked-by: Srinivas Kandagatla Signed-off-by: Michal Simek --- drivers/nvmem/Kconfig | 10 +++++ drivers/nvmem/Makefile | 2 + drivers/nvmem/zynqmp_nvmem.c | 86 ++++++++++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+) create mode 100644 drivers/nvmem/zynqmp_nvmem.c diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig index 0a7a470ee859..4ad846ceac7c 100644 --- a/drivers/nvmem/Kconfig +++ b/drivers/nvmem/Kconfig @@ -192,4 +192,14 @@ config SC27XX_EFUSE This driver can also be built as a module. If so, the module will be called nvmem-sc27xx-efuse. +config NVMEM_ZYNQMP + bool "Xilinx ZYNQMP SoC nvmem firmware support" + depends on ARCH_ZYNQMP + help + This is a driver to access hardware related data like + soc revision, IDCODE... etc by using the firmware + interface. + + If sure, say yes. If unsure, say no. + endif diff --git a/drivers/nvmem/Makefile b/drivers/nvmem/Makefile index 4e8c61628f1a..2ece8ffffdda 100644 --- a/drivers/nvmem/Makefile +++ b/drivers/nvmem/Makefile @@ -41,3 +41,5 @@ obj-$(CONFIG_RAVE_SP_EEPROM) += nvmem-rave-sp-eeprom.o nvmem-rave-sp-eeprom-y := rave-sp-eeprom.o obj-$(CONFIG_SC27XX_EFUSE) += nvmem-sc27xx-efuse.o nvmem-sc27xx-efuse-y := sc27xx-efuse.o +obj-$(CONFIG_NVMEM_ZYNQMP) += nvmem_zynqmp_nvmem.o +nvmem_zynqmp_nvmem-y := zynqmp_nvmem.o diff --git a/drivers/nvmem/zynqmp_nvmem.c b/drivers/nvmem/zynqmp_nvmem.c new file mode 100644 index 000000000000..490c8fcaec80 --- /dev/null +++ b/drivers/nvmem/zynqmp_nvmem.c @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2019 Xilinx, Inc. + */ + +#include +#include +#include +#include +#include + +#define SILICON_REVISION_MASK 0xF + +struct zynqmp_nvmem_data { + struct device *dev; + struct nvmem_device *nvmem; +}; + +static int zynqmp_nvmem_read(void *context, unsigned int offset, + void *val, size_t bytes) +{ + int ret; + int idcode, version; + struct zynqmp_nvmem_data *priv = context; + + const struct zynqmp_eemi_ops *eemi_ops = zynqmp_pm_get_eemi_ops(); + + if (!eemi_ops || !eemi_ops->get_chipid) + return -ENXIO; + + ret = eemi_ops->get_chipid(&idcode, &version); + if (ret < 0) + return ret; + + dev_dbg(priv->dev, "Read chipid val %x %x\n", idcode, version); + *(int *)val = version & SILICON_REVISION_MASK; + + return 0; +} + +static struct nvmem_config econfig = { + .name = "zynqmp-nvmem", + .owner = THIS_MODULE, + .word_size = 1, + .size = 1, + .read_only = true, +}; + +static const struct of_device_id zynqmp_nvmem_match[] = { + { .compatible = "xlnx,zynqmp-nvmem-fw", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, zynqmp_nvmem_match); + +static int zynqmp_nvmem_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct zynqmp_nvmem_data *priv; + + priv = devm_kzalloc(dev, sizeof(struct zynqmp_nvmem_data), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = dev; + econfig.dev = dev; + econfig.reg_read = zynqmp_nvmem_read; + econfig.priv = priv; + + priv->nvmem = devm_nvmem_register(dev, &econfig); + + return PTR_ERR_OR_ZERO(priv->nvmem); +} + +static struct platform_driver zynqmp_nvmem_driver = { + .probe = zynqmp_nvmem_probe, + .driver = { + .name = "zynqmp-nvmem", + .of_match_table = zynqmp_nvmem_match, + }, +}; + +module_platform_driver(zynqmp_nvmem_driver); + +MODULE_AUTHOR("Michal Simek , Nava kishore Manne "); +MODULE_DESCRIPTION("ZynqMP NVMEM driver"); +MODULE_LICENSE("GPL"); From b0dcfb78dc6aec8698ab5900dfdf6aeae0830815 Mon Sep 17 00:00:00 2001 From: Peter De Schrijver Date: Fri, 4 Jan 2019 11:06:47 +0800 Subject: [PATCH 073/113] clk: tegra: dfll: registration for multiple SoCs In a future patch, support for the DFLL in Tegra210 will be introduced. This requires support for more than 1 set of CVB and CPU max frequency tables. Signed-off-by: Peter De Schrijver Signed-off-by: Joseph Lo Acked-by: Jon Hunter Acked-by: Stephen Boyd Signed-off-by: Thierry Reding --- drivers/clk/tegra/clk-tegra124-dfll-fcpu.c | 45 ++++++++++++++++------ 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/drivers/clk/tegra/clk-tegra124-dfll-fcpu.c b/drivers/clk/tegra/clk-tegra124-dfll-fcpu.c index 269d3595758b..2bf6a769e5ce 100644 --- a/drivers/clk/tegra/clk-tegra124-dfll-fcpu.c +++ b/drivers/clk/tegra/clk-tegra124-dfll-fcpu.c @@ -1,7 +1,7 @@ /* * Tegra124 DFLL FCPU clock source driver * - * Copyright (C) 2012-2014 NVIDIA Corporation. All rights reserved. + * Copyright (C) 2012-2019 NVIDIA Corporation. All rights reserved. * * Aleksandr Frid * Paul Walmsley @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -28,8 +29,15 @@ #include "clk-dfll.h" #include "cvb.h" +struct dfll_fcpu_data { + const unsigned long *cpu_max_freq_table; + unsigned int cpu_max_freq_table_size; + const struct cvb_table *cpu_cvb_tables; + unsigned int cpu_cvb_tables_size; +}; + /* Maximum CPU frequency, indexed by CPU speedo id */ -static const unsigned long cpu_max_freq_table[] = { +static const unsigned long tegra124_cpu_max_freq_table[] = { [0] = 2014500000UL, [1] = 2320500000UL, [2] = 2116500000UL, @@ -82,16 +90,36 @@ static const struct cvb_table tegra124_cpu_cvb_tables[] = { }, }; +static const struct dfll_fcpu_data tegra124_dfll_fcpu_data = { + .cpu_max_freq_table = tegra124_cpu_max_freq_table, + .cpu_max_freq_table_size = ARRAY_SIZE(tegra124_cpu_max_freq_table), + .cpu_cvb_tables = tegra124_cpu_cvb_tables, + .cpu_cvb_tables_size = ARRAY_SIZE(tegra124_cpu_cvb_tables) +}; + +static const struct of_device_id tegra124_dfll_fcpu_of_match[] = { + { + .compatible = "nvidia,tegra124-dfll", + .data = &tegra124_dfll_fcpu_data, + }, + { }, +}; + static int tegra124_dfll_fcpu_probe(struct platform_device *pdev) { int process_id, speedo_id, speedo_value, err; struct tegra_dfll_soc_data *soc; + const struct dfll_fcpu_data *fcpu_data; + + fcpu_data = of_device_get_match_data(&pdev->dev); + if (!fcpu_data) + return -ENODEV; process_id = tegra_sku_info.cpu_process_id; speedo_id = tegra_sku_info.cpu_speedo_id; speedo_value = tegra_sku_info.cpu_speedo_value; - if (speedo_id >= ARRAY_SIZE(cpu_max_freq_table)) { + if (speedo_id >= fcpu_data->cpu_max_freq_table_size) { dev_err(&pdev->dev, "unknown max CPU freq for speedo_id=%d\n", speedo_id); return -ENODEV; @@ -107,10 +135,10 @@ static int tegra124_dfll_fcpu_probe(struct platform_device *pdev) return -ENODEV; } - soc->max_freq = cpu_max_freq_table[speedo_id]; + soc->max_freq = fcpu_data->cpu_max_freq_table[speedo_id]; - soc->cvb = tegra_cvb_add_opp_table(soc->dev, tegra124_cpu_cvb_tables, - ARRAY_SIZE(tegra124_cpu_cvb_tables), + soc->cvb = tegra_cvb_add_opp_table(soc->dev, fcpu_data->cpu_cvb_tables, + fcpu_data->cpu_cvb_tables_size, process_id, speedo_id, speedo_value, soc->max_freq); if (IS_ERR(soc->cvb)) { @@ -142,11 +170,6 @@ static int tegra124_dfll_fcpu_remove(struct platform_device *pdev) return 0; } -static const struct of_device_id tegra124_dfll_fcpu_of_match[] = { - { .compatible = "nvidia,tegra124-dfll", }, - { }, -}; - static const struct dev_pm_ops tegra124_dfll_pm_ops = { SET_RUNTIME_PM_OPS(tegra_dfll_runtime_suspend, tegra_dfll_runtime_resume, NULL) From b3cf8d0695056a370276c416979277635c3e4299 Mon Sep 17 00:00:00 2001 From: Joseph Lo Date: Fri, 4 Jan 2019 11:06:48 +0800 Subject: [PATCH 074/113] clk: tegra: dfll: CVB calculation alignment with the regulator The CVB table contains calibration data for the CPU DFLL based on process characterization. The regulator step and offset parameters depend on the regulator supplying vdd-cpu, not on the specific Tegra SKU. When using a PWM controlled regulator, the voltage step and offset are determined by the regulator type in use. This is specified in DT. When using an I2C controlled regulator, we can retrieve them from CPU regulator Then pass this information to the CVB table calculation function. Based on the work done of "Peter De Schrijver " and "Alex Frid ". Signed-off-by: Joseph Lo Acked-by: Jon Hunter Acked-by: Stephen Boyd Signed-off-by: Thierry Reding --- drivers/clk/tegra/clk-dfll.h | 6 ++- drivers/clk/tegra/clk-tegra124-dfll-fcpu.c | 49 +++++++++++++++++++--- drivers/clk/tegra/cvb.c | 12 +++--- drivers/clk/tegra/cvb.h | 6 +-- 4 files changed, 59 insertions(+), 14 deletions(-) diff --git a/drivers/clk/tegra/clk-dfll.h b/drivers/clk/tegra/clk-dfll.h index 83352c8078f2..85d0d95223f3 100644 --- a/drivers/clk/tegra/clk-dfll.h +++ b/drivers/clk/tegra/clk-dfll.h @@ -1,6 +1,6 @@ /* * clk-dfll.h - prototypes and macros for the Tegra DFLL clocksource driver - * Copyright (C) 2013 NVIDIA Corporation. All rights reserved. + * Copyright (C) 2013-2019 NVIDIA Corporation. All rights reserved. * * Aleksandr Frid * Paul Walmsley @@ -22,11 +22,14 @@ #include #include +#include "cvb.h" + /** * struct tegra_dfll_soc_data - SoC-specific hooks/integration for the DFLL driver * @dev: struct device * that holds the OPP table for the DFLL * @max_freq: maximum frequency supported on this SoC * @cvb: CPU frequency table for this SoC + * @alignment: parameters of the regulator step and offset * @init_clock_trimmers: callback to initialize clock trimmers * @set_clock_trimmers_high: callback to tune clock trimmers for high voltage * @set_clock_trimmers_low: callback to tune clock trimmers for low voltage @@ -35,6 +38,7 @@ struct tegra_dfll_soc_data { struct device *dev; unsigned long max_freq; const struct cvb_table *cvb; + struct rail_alignment alignment; void (*init_clock_trimmers)(void); void (*set_clock_trimmers_high)(void); diff --git a/drivers/clk/tegra/clk-tegra124-dfll-fcpu.c b/drivers/clk/tegra/clk-tegra124-dfll-fcpu.c index 2bf6a769e5ce..92b83f50a765 100644 --- a/drivers/clk/tegra/clk-tegra124-dfll-fcpu.c +++ b/drivers/clk/tegra/clk-tegra124-dfll-fcpu.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include "clk.h" @@ -50,9 +51,6 @@ static const struct cvb_table tegra124_cpu_cvb_tables[] = { .process_id = -1, .min_millivolts = 900, .max_millivolts = 1260, - .alignment = { - .step_uv = 10000, /* 10mV */ - }, .speedo_scale = 100, .voltage_scale = 1000, .entries = { @@ -105,11 +103,42 @@ static const struct of_device_id tegra124_dfll_fcpu_of_match[] = { { }, }; +static void get_alignment_from_dt(struct device *dev, + struct rail_alignment *align) +{ + if (of_property_read_u32(dev->of_node, + "nvidia,pwm-voltage-step-microvolts", + &align->step_uv)) + align->step_uv = 0; + + if (of_property_read_u32(dev->of_node, + "nvidia,pwm-min-microvolts", + &align->offset_uv)) + align->offset_uv = 0; +} + +static int get_alignment_from_regulator(struct device *dev, + struct rail_alignment *align) +{ + struct regulator *reg = devm_regulator_get(dev, "vdd-cpu"); + + if (IS_ERR(reg)) + return PTR_ERR(reg); + + align->offset_uv = regulator_list_voltage(reg, 0); + align->step_uv = regulator_get_linear_step(reg); + + devm_regulator_put(reg); + + return 0; +} + static int tegra124_dfll_fcpu_probe(struct platform_device *pdev) { int process_id, speedo_id, speedo_value, err; struct tegra_dfll_soc_data *soc; const struct dfll_fcpu_data *fcpu_data; + struct rail_alignment align; fcpu_data = of_device_get_match_data(&pdev->dev); if (!fcpu_data) @@ -135,12 +164,22 @@ static int tegra124_dfll_fcpu_probe(struct platform_device *pdev) return -ENODEV; } + if (of_property_read_bool(pdev->dev.of_node, "nvidia,pwm-to-pmic")) { + get_alignment_from_dt(&pdev->dev, &align); + } else { + err = get_alignment_from_regulator(&pdev->dev, &align); + if (err) + return err; + } + soc->max_freq = fcpu_data->cpu_max_freq_table[speedo_id]; soc->cvb = tegra_cvb_add_opp_table(soc->dev, fcpu_data->cpu_cvb_tables, fcpu_data->cpu_cvb_tables_size, - process_id, speedo_id, speedo_value, - soc->max_freq); + &align, process_id, speedo_id, + speedo_value, soc->max_freq); + soc->alignment = align; + if (IS_ERR(soc->cvb)) { dev_err(&pdev->dev, "couldn't add OPP table: %ld\n", PTR_ERR(soc->cvb)); diff --git a/drivers/clk/tegra/cvb.c b/drivers/clk/tegra/cvb.c index da9e8e7b5ce5..35eeb6adc68e 100644 --- a/drivers/clk/tegra/cvb.c +++ b/drivers/clk/tegra/cvb.c @@ -1,7 +1,7 @@ /* * Utility functions for parsing Tegra CVB voltage tables * - * Copyright (C) 2012-2014 NVIDIA Corporation. All rights reserved. + * Copyright (C) 2012-2019 NVIDIA Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -62,9 +62,9 @@ static int round_voltage(int mv, const struct rail_alignment *align, int up) } static int build_opp_table(struct device *dev, const struct cvb_table *table, + struct rail_alignment *align, int speedo_value, unsigned long max_freq) { - const struct rail_alignment *align = &table->alignment; int i, ret, dfll_mv, min_mv, max_mv; min_mv = round_voltage(table->min_millivolts, align, UP); @@ -109,8 +109,9 @@ static int build_opp_table(struct device *dev, const struct cvb_table *table, */ const struct cvb_table * tegra_cvb_add_opp_table(struct device *dev, const struct cvb_table *tables, - size_t count, int process_id, int speedo_id, - int speedo_value, unsigned long max_freq) + size_t count, struct rail_alignment *align, + int process_id, int speedo_id, int speedo_value, + unsigned long max_freq) { size_t i; int ret; @@ -124,7 +125,8 @@ tegra_cvb_add_opp_table(struct device *dev, const struct cvb_table *tables, if (table->process_id != -1 && table->process_id != process_id) continue; - ret = build_opp_table(dev, table, speedo_value, max_freq); + ret = build_opp_table(dev, table, align, speedo_value, + max_freq); return ret ? ERR_PTR(ret) : table; } diff --git a/drivers/clk/tegra/cvb.h b/drivers/clk/tegra/cvb.h index c1f077993b2a..bcf15a089b93 100644 --- a/drivers/clk/tegra/cvb.h +++ b/drivers/clk/tegra/cvb.h @@ -49,7 +49,6 @@ struct cvb_table { int min_millivolts; int max_millivolts; - struct rail_alignment alignment; int speedo_scale; int voltage_scale; @@ -59,8 +58,9 @@ struct cvb_table { const struct cvb_table * tegra_cvb_add_opp_table(struct device *dev, const struct cvb_table *cvb_tables, - size_t count, int process_id, int speedo_id, - int speedo_value, unsigned long max_freq); + size_t count, struct rail_alignment *align, + int process_id, int speedo_id, int speedo_value, + unsigned long max_freq); void tegra_cvb_remove_opp_table(struct device *dev, const struct cvb_table *table, unsigned long max_freq); From 36541f0499fe02541de8edbcb05e6536104b11d2 Mon Sep 17 00:00:00 2001 From: Joseph Lo Date: Fri, 4 Jan 2019 11:06:49 +0800 Subject: [PATCH 075/113] clk: tegra: dfll: support PWM regulator control The DFLL hardware supports two modes (I2C and PWM) for voltage control when requesting a frequency. In this patch, we introduce PWM mode support. To support that, we re-organize the LUT for unifying the table for both cases of I2C and PWM mode. And generate that based on regulator info. For the PWM-based regulator, we get this info from DT. And do the same as the case of I2C LUT, which can help to map the PMIC voltage ID and voltages that the regulator supported. The other parts are the support code for initializing the DFLL hardware to support PWM mode. Also, the register debugfs file is slightly reworked to only show the i2c registers when I2C mode is in use. Based on the work of Peter De Schrijver . Signed-off-by: Joseph Lo Acked-by: Jon Hunter Acked-by: Stephen Boyd Signed-off-by: Thierry Reding --- drivers/clk/tegra/clk-dfll.c | 444 +++++++++++++++++++++++++++++------ 1 file changed, 377 insertions(+), 67 deletions(-) diff --git a/drivers/clk/tegra/clk-dfll.c b/drivers/clk/tegra/clk-dfll.c index 609e363dabf8..69bbf62a9eab 100644 --- a/drivers/clk/tegra/clk-dfll.c +++ b/drivers/clk/tegra/clk-dfll.c @@ -1,7 +1,7 @@ /* * clk-dfll.c - Tegra DFLL clock source common code * - * Copyright (C) 2012-2014 NVIDIA Corporation. All rights reserved. + * Copyright (C) 2012-2019 NVIDIA Corporation. All rights reserved. * * Aleksandr Frid * Paul Walmsley @@ -47,6 +47,7 @@ #include #include #include +#include #include #include #include @@ -243,6 +244,12 @@ enum dfll_tune_range { DFLL_TUNE_LOW = 1, }; + +enum tegra_dfll_pmu_if { + TEGRA_DFLL_PMU_I2C = 0, + TEGRA_DFLL_PMU_PWM = 1, +}; + /** * struct dfll_rate_req - target DFLL rate request data * @rate: target frequency, after the postscaling @@ -300,10 +307,19 @@ struct tegra_dfll { u32 i2c_reg; u32 i2c_slave_addr; - /* i2c_lut array entries are regulator framework selectors */ - unsigned i2c_lut[MAX_DFLL_VOLTAGES]; - int i2c_lut_size; - u8 lut_min, lut_max, lut_safe; + /* lut array entries are regulator framework selectors or PWM values*/ + unsigned lut[MAX_DFLL_VOLTAGES]; + unsigned long lut_uv[MAX_DFLL_VOLTAGES]; + int lut_size; + u8 lut_bottom, lut_min, lut_max, lut_safe; + + /* PWM interface */ + enum tegra_dfll_pmu_if pmu_if; + unsigned long pwm_rate; + struct pinctrl *pwm_pin; + struct pinctrl_state *pwm_enable_state; + struct pinctrl_state *pwm_disable_state; + u32 reg_init_uV; }; #define clk_hw_to_dfll(_hw) container_of(_hw, struct tegra_dfll, dfll_clk_hw) @@ -489,6 +505,34 @@ static void dfll_set_mode(struct tegra_dfll *td, dfll_wmb(td); } +/* + * DVCO rate control + */ + +static unsigned long get_dvco_rate_below(struct tegra_dfll *td, u8 out_min) +{ + struct dev_pm_opp *opp; + unsigned long rate, prev_rate; + unsigned long uv, min_uv; + + min_uv = td->lut_uv[out_min]; + for (rate = 0, prev_rate = 0; ; rate++) { + opp = dev_pm_opp_find_freq_ceil(td->soc->dev, &rate); + if (IS_ERR(opp)) + break; + + uv = dev_pm_opp_get_voltage(opp); + dev_pm_opp_put(opp); + + if (uv && uv > min_uv) + return prev_rate; + + prev_rate = rate; + } + + return prev_rate; +} + /* * DFLL-to-I2C controller interface */ @@ -518,6 +562,118 @@ static int dfll_i2c_set_output_enabled(struct tegra_dfll *td, bool enable) return 0; } + +/* + * DFLL-to-PWM controller interface + */ + +/** + * dfll_pwm_set_output_enabled - enable/disable PWM voltage requests + * @td: DFLL instance + * @enable: whether to enable or disable the PWM voltage requests + * + * Set the master enable control for PWM control value updates. If disabled, + * then the PWM signal is not driven. Also configure the PWM output pad + * to the appropriate state. + */ +static int dfll_pwm_set_output_enabled(struct tegra_dfll *td, bool enable) +{ + int ret; + u32 val, div; + + if (enable) { + ret = pinctrl_select_state(td->pwm_pin, td->pwm_enable_state); + if (ret < 0) { + dev_err(td->dev, "setting enable state failed\n"); + return -EINVAL; + } + val = dfll_readl(td, DFLL_OUTPUT_CFG); + val &= ~DFLL_OUTPUT_CFG_PWM_DIV_MASK; + div = DIV_ROUND_UP(td->ref_rate, td->pwm_rate); + val |= (div << DFLL_OUTPUT_CFG_PWM_DIV_SHIFT) + & DFLL_OUTPUT_CFG_PWM_DIV_MASK; + dfll_writel(td, val, DFLL_OUTPUT_CFG); + dfll_wmb(td); + + val |= DFLL_OUTPUT_CFG_PWM_ENABLE; + dfll_writel(td, val, DFLL_OUTPUT_CFG); + dfll_wmb(td); + } else { + ret = pinctrl_select_state(td->pwm_pin, td->pwm_disable_state); + if (ret < 0) + dev_warn(td->dev, "setting disable state failed\n"); + + val = dfll_readl(td, DFLL_OUTPUT_CFG); + val &= ~DFLL_OUTPUT_CFG_PWM_ENABLE; + dfll_writel(td, val, DFLL_OUTPUT_CFG); + dfll_wmb(td); + } + + return 0; +} + +/** + * dfll_set_force_output_value - set fixed value for force output + * @td: DFLL instance + * @out_val: value to force output + * + * Set the fixed value for force output, DFLL will output this value when + * force output is enabled. + */ +static u32 dfll_set_force_output_value(struct tegra_dfll *td, u8 out_val) +{ + u32 val = dfll_readl(td, DFLL_OUTPUT_FORCE); + + val = (val & DFLL_OUTPUT_FORCE_ENABLE) | (out_val & OUT_MASK); + dfll_writel(td, val, DFLL_OUTPUT_FORCE); + dfll_wmb(td); + + return dfll_readl(td, DFLL_OUTPUT_FORCE); +} + +/** + * dfll_set_force_output_enabled - enable/disable force output + * @td: DFLL instance + * @enable: whether to enable or disable the force output + * + * Set the enable control for fouce output with fixed value. + */ +static void dfll_set_force_output_enabled(struct tegra_dfll *td, bool enable) +{ + u32 val = dfll_readl(td, DFLL_OUTPUT_FORCE); + + if (enable) + val |= DFLL_OUTPUT_FORCE_ENABLE; + else + val &= ~DFLL_OUTPUT_FORCE_ENABLE; + + dfll_writel(td, val, DFLL_OUTPUT_FORCE); + dfll_wmb(td); +} + +/** + * dfll_force_output - force output a fixed value + * @td: DFLL instance + * @out_sel: value to force output + * + * Set the fixed value for force output, DFLL will output this value. + */ +static int dfll_force_output(struct tegra_dfll *td, unsigned int out_sel) +{ + u32 val; + + if (out_sel > OUT_MASK) + return -EINVAL; + + val = dfll_set_force_output_value(td, out_sel); + if ((td->mode < DFLL_CLOSED_LOOP) && + !(val & DFLL_OUTPUT_FORCE_ENABLE)) { + dfll_set_force_output_enabled(td, true); + } + + return 0; +} + /** * dfll_load_lut - load the voltage lookup table * @td: struct tegra_dfll * @@ -539,7 +695,7 @@ static void dfll_load_i2c_lut(struct tegra_dfll *td) lut_index = i; val = regulator_list_hardware_vsel(td->vdd_reg, - td->i2c_lut[lut_index]); + td->lut[lut_index]); __raw_writel(val, td->lut_base + i * 4); } @@ -594,24 +750,41 @@ static void dfll_init_out_if(struct tegra_dfll *td) { u32 val; - td->lut_min = 0; - td->lut_max = td->i2c_lut_size - 1; - td->lut_safe = td->lut_min + 1; + td->lut_min = td->lut_bottom; + td->lut_max = td->lut_size - 1; + td->lut_safe = td->lut_min + (td->lut_min < td->lut_max ? 1 : 0); + + /* clear DFLL_OUTPUT_CFG before setting new value */ + dfll_writel(td, 0, DFLL_OUTPUT_CFG); + dfll_wmb(td); - dfll_i2c_writel(td, 0, DFLL_OUTPUT_CFG); val = (td->lut_safe << DFLL_OUTPUT_CFG_SAFE_SHIFT) | - (td->lut_max << DFLL_OUTPUT_CFG_MAX_SHIFT) | - (td->lut_min << DFLL_OUTPUT_CFG_MIN_SHIFT); - dfll_i2c_writel(td, val, DFLL_OUTPUT_CFG); - dfll_i2c_wmb(td); + (td->lut_max << DFLL_OUTPUT_CFG_MAX_SHIFT) | + (td->lut_min << DFLL_OUTPUT_CFG_MIN_SHIFT); + dfll_writel(td, val, DFLL_OUTPUT_CFG); + dfll_wmb(td); dfll_writel(td, 0, DFLL_OUTPUT_FORCE); dfll_i2c_writel(td, 0, DFLL_INTR_EN); dfll_i2c_writel(td, DFLL_INTR_MAX_MASK | DFLL_INTR_MIN_MASK, DFLL_INTR_STS); - dfll_load_i2c_lut(td); - dfll_init_i2c_if(td); + if (td->pmu_if == TEGRA_DFLL_PMU_PWM) { + u32 vinit = td->reg_init_uV; + int vstep = td->soc->alignment.step_uv; + unsigned long vmin = td->lut_uv[0]; + + /* set initial voltage */ + if ((vinit >= vmin) && vstep) { + unsigned int vsel; + + vsel = DIV_ROUND_UP((vinit - vmin), vstep); + dfll_force_output(td, vsel); + } + } else { + dfll_load_i2c_lut(td); + dfll_init_i2c_if(td); + } } /* @@ -631,7 +804,8 @@ static void dfll_init_out_if(struct tegra_dfll *td) static int find_lut_index_for_rate(struct tegra_dfll *td, unsigned long rate) { struct dev_pm_opp *opp; - int i, uv; + unsigned long uv; + int i; opp = dev_pm_opp_find_freq_ceil(td->soc->dev, &rate); if (IS_ERR(opp)) @@ -640,8 +814,8 @@ static int find_lut_index_for_rate(struct tegra_dfll *td, unsigned long rate) uv = dev_pm_opp_get_voltage(opp); dev_pm_opp_put(opp); - for (i = 0; i < td->i2c_lut_size; i++) { - if (regulator_list_voltage(td->vdd_reg, td->i2c_lut[i]) == uv) + for (i = td->lut_bottom; i < td->lut_size; i++) { + if (td->lut_uv[i] >= uv) return i; } @@ -863,9 +1037,14 @@ static int dfll_lock(struct tegra_dfll *td) return -EINVAL; } - dfll_i2c_set_output_enabled(td, true); + if (td->pmu_if == TEGRA_DFLL_PMU_PWM) + dfll_pwm_set_output_enabled(td, true); + else + dfll_i2c_set_output_enabled(td, true); + dfll_set_mode(td, DFLL_CLOSED_LOOP); dfll_set_frequency_request(td, req); + dfll_set_force_output_enabled(td, false); return 0; default: @@ -889,7 +1068,10 @@ static int dfll_unlock(struct tegra_dfll *td) case DFLL_CLOSED_LOOP: dfll_set_open_loop_config(td); dfll_set_mode(td, DFLL_OPEN_LOOP); - dfll_i2c_set_output_enabled(td, false); + if (td->pmu_if == TEGRA_DFLL_PMU_PWM) + dfll_pwm_set_output_enabled(td, false); + else + dfll_i2c_set_output_enabled(td, false); return 0; case DFLL_OPEN_LOOP: @@ -1171,15 +1353,17 @@ static int attr_registers_show(struct seq_file *s, void *data) seq_printf(s, "[0x%02x] = 0x%08x\n", offs, dfll_i2c_readl(td, offs)); - seq_puts(s, "\nINTEGRATED I2C CONTROLLER REGISTERS:\n"); - offs = DFLL_I2C_CLK_DIVISOR; - seq_printf(s, "[0x%02x] = 0x%08x\n", offs, - __raw_readl(td->i2c_controller_base + offs)); - - seq_puts(s, "\nLUT:\n"); - for (offs = 0; offs < 4 * MAX_DFLL_VOLTAGES; offs += 4) + if (td->pmu_if == TEGRA_DFLL_PMU_I2C) { + seq_puts(s, "\nINTEGRATED I2C CONTROLLER REGISTERS:\n"); + offs = DFLL_I2C_CLK_DIVISOR; seq_printf(s, "[0x%02x] = 0x%08x\n", offs, - __raw_readl(td->lut_base + offs)); + __raw_readl(td->i2c_controller_base + offs)); + + seq_puts(s, "\nLUT:\n"); + for (offs = 0; offs < 4 * MAX_DFLL_VOLTAGES; offs += 4) + seq_printf(s, "[0x%02x] = 0x%08x\n", offs, + __raw_readl(td->lut_base + offs)); + } return 0; } @@ -1351,6 +1535,9 @@ static int find_vdd_map_entry_exact(struct tegra_dfll *td, int uV) { int i, n_voltages, reg_uV; + if (WARN_ON(td->pmu_if == TEGRA_DFLL_PMU_PWM)) + return -EINVAL; + n_voltages = regulator_count_voltages(td->vdd_reg); for (i = 0; i < n_voltages; i++) { reg_uV = regulator_list_voltage(td->vdd_reg, i); @@ -1373,6 +1560,9 @@ static int find_vdd_map_entry_min(struct tegra_dfll *td, int uV) { int i, n_voltages, reg_uV; + if (WARN_ON(td->pmu_if == TEGRA_DFLL_PMU_PWM)) + return -EINVAL; + n_voltages = regulator_count_voltages(td->vdd_reg); for (i = 0; i < n_voltages; i++) { reg_uV = regulator_list_voltage(td->vdd_reg, i); @@ -1387,9 +1577,61 @@ static int find_vdd_map_entry_min(struct tegra_dfll *td, int uV) return -EINVAL; } +/* + * dfll_build_pwm_lut - build the PWM regulator lookup table + * @td: DFLL instance + * @v_max: Vmax from OPP table + * + * Look-up table in h/w is ignored when PWM is used as DFLL interface to PMIC. + * In this case closed loop output is controlling duty cycle directly. The s/w + * look-up that maps PWM duty cycle to voltage is still built by this function. + */ +static int dfll_build_pwm_lut(struct tegra_dfll *td, unsigned long v_max) +{ + int i; + unsigned long rate, reg_volt; + u8 lut_bottom = MAX_DFLL_VOLTAGES; + int v_min = td->soc->cvb->min_millivolts * 1000; + + for (i = 0; i < MAX_DFLL_VOLTAGES; i++) { + reg_volt = td->lut_uv[i]; + + /* since opp voltage is exact mv */ + reg_volt = (reg_volt / 1000) * 1000; + if (reg_volt > v_max) + break; + + td->lut[i] = i; + if ((lut_bottom == MAX_DFLL_VOLTAGES) && (reg_volt >= v_min)) + lut_bottom = i; + } + + /* determine voltage boundaries */ + td->lut_size = i; + if ((lut_bottom == MAX_DFLL_VOLTAGES) || + (lut_bottom + 1 >= td->lut_size)) { + dev_err(td->dev, "no voltage above DFLL minimum %d mV\n", + td->soc->cvb->min_millivolts); + return -EINVAL; + } + td->lut_bottom = lut_bottom; + + /* determine rate boundaries */ + rate = get_dvco_rate_below(td, td->lut_bottom); + if (!rate) { + dev_err(td->dev, "no opp below DFLL minimum voltage %d mV\n", + td->soc->cvb->min_millivolts); + return -EINVAL; + } + td->dvco_rate_min = rate; + + return 0; +} + /** * dfll_build_i2c_lut - build the I2C voltage register lookup table * @td: DFLL instance + * @v_max: Vmax from OPP table * * The DFLL hardware has 33 bytes of look-up table RAM that must be filled with * PMIC voltage register values that span the entire DFLL operating range. @@ -1397,33 +1639,24 @@ static int find_vdd_map_entry_min(struct tegra_dfll *td, int uV) * the soc-specific platform driver (td->soc->opp_dev) and the PMIC * register-to-voltage mapping queried from the regulator framework. * - * On success, fills in td->i2c_lut and returns 0, or -err on failure. + * On success, fills in td->lut and returns 0, or -err on failure. */ -static int dfll_build_i2c_lut(struct tegra_dfll *td) +static int dfll_build_i2c_lut(struct tegra_dfll *td, unsigned long v_max) { + unsigned long rate, v, v_opp; int ret = -EINVAL; - int j, v, v_max, v_opp; - int selector; - unsigned long rate; - struct dev_pm_opp *opp; - int lut; - - rate = ULONG_MAX; - opp = dev_pm_opp_find_freq_floor(td->soc->dev, &rate); - if (IS_ERR(opp)) { - dev_err(td->dev, "couldn't get vmax opp, empty opp table?\n"); - goto out; - } - v_max = dev_pm_opp_get_voltage(opp); - dev_pm_opp_put(opp); + int j, selector, lut; v = td->soc->cvb->min_millivolts * 1000; lut = find_vdd_map_entry_exact(td, v); if (lut < 0) goto out; - td->i2c_lut[0] = lut; + td->lut[0] = lut; + td->lut_bottom = 0; for (j = 1, rate = 0; ; rate++) { + struct dev_pm_opp *opp; + opp = dev_pm_opp_find_freq_ceil(td->soc->dev, &rate); if (IS_ERR(opp)) break; @@ -1435,39 +1668,64 @@ static int dfll_build_i2c_lut(struct tegra_dfll *td) dev_pm_opp_put(opp); for (;;) { - v += max(1, (v_max - v) / (MAX_DFLL_VOLTAGES - j)); + v += max(1UL, (v_max - v) / (MAX_DFLL_VOLTAGES - j)); if (v >= v_opp) break; selector = find_vdd_map_entry_min(td, v); if (selector < 0) goto out; - if (selector != td->i2c_lut[j - 1]) - td->i2c_lut[j++] = selector; + if (selector != td->lut[j - 1]) + td->lut[j++] = selector; } v = (j == MAX_DFLL_VOLTAGES - 1) ? v_max : v_opp; selector = find_vdd_map_entry_exact(td, v); if (selector < 0) goto out; - if (selector != td->i2c_lut[j - 1]) - td->i2c_lut[j++] = selector; + if (selector != td->lut[j - 1]) + td->lut[j++] = selector; if (v >= v_max) break; } - td->i2c_lut_size = j; + td->lut_size = j; if (!td->dvco_rate_min) dev_err(td->dev, "no opp above DFLL minimum voltage %d mV\n", td->soc->cvb->min_millivolts); - else + else { ret = 0; + for (j = 0; j < td->lut_size; j++) + td->lut_uv[j] = + regulator_list_voltage(td->vdd_reg, + td->lut[j]); + } out: return ret; } +static int dfll_build_lut(struct tegra_dfll *td) +{ + unsigned long rate, v_max; + struct dev_pm_opp *opp; + + rate = ULONG_MAX; + opp = dev_pm_opp_find_freq_floor(td->soc->dev, &rate); + if (IS_ERR(opp)) { + dev_err(td->dev, "couldn't get vmax opp, empty opp table?\n"); + return -EINVAL; + } + v_max = dev_pm_opp_get_voltage(opp); + dev_pm_opp_put(opp); + + if (td->pmu_if == TEGRA_DFLL_PMU_PWM) + return dfll_build_pwm_lut(td, v_max); + else + return dfll_build_i2c_lut(td, v_max); +} + /** * read_dt_param - helper function for reading required parameters from the DT * @td: DFLL instance @@ -1526,12 +1784,57 @@ static int dfll_fetch_i2c_params(struct tegra_dfll *td) } td->i2c_reg = vsel_reg; - ret = dfll_build_i2c_lut(td); - if (ret) { - dev_err(td->dev, "couldn't build I2C LUT\n"); + return 0; +} + +static int dfll_fetch_pwm_params(struct tegra_dfll *td) +{ + int ret, i; + u32 pwm_period; + + if (!td->soc->alignment.step_uv || !td->soc->alignment.offset_uv) { + dev_err(td->dev, + "Missing step or alignment info for PWM regulator"); + return -EINVAL; + } + for (i = 0; i < MAX_DFLL_VOLTAGES; i++) + td->lut_uv[i] = td->soc->alignment.offset_uv + + i * td->soc->alignment.step_uv; + + ret = read_dt_param(td, "nvidia,pwm-tristate-microvolts", + &td->reg_init_uV); + if (!ret) { + dev_err(td->dev, "couldn't get initialized voltage\n"); return ret; } + ret = read_dt_param(td, "nvidia,pwm-period-nanoseconds", &pwm_period); + if (!ret) { + dev_err(td->dev, "couldn't get PWM period\n"); + return ret; + } + td->pwm_rate = (NSEC_PER_SEC / pwm_period) * (MAX_DFLL_VOLTAGES - 1); + + td->pwm_pin = devm_pinctrl_get(td->dev); + if (IS_ERR(td->pwm_pin)) { + dev_err(td->dev, "DT: missing pinctrl device\n"); + return PTR_ERR(td->pwm_pin); + } + + td->pwm_enable_state = pinctrl_lookup_state(td->pwm_pin, + "dvfs_pwm_enable"); + if (IS_ERR(td->pwm_enable_state)) { + dev_err(td->dev, "DT: missing pwm enabled state\n"); + return PTR_ERR(td->pwm_enable_state); + } + + td->pwm_disable_state = pinctrl_lookup_state(td->pwm_pin, + "dvfs_pwm_disable"); + if (IS_ERR(td->pwm_disable_state)) { + dev_err(td->dev, "DT: missing pwm disabled state\n"); + return PTR_ERR(td->pwm_disable_state); + } + return 0; } @@ -1597,16 +1900,6 @@ int tegra_dfll_register(struct platform_device *pdev, td->soc = soc; - td->vdd_reg = devm_regulator_get(td->dev, "vdd-cpu"); - if (IS_ERR(td->vdd_reg)) { - ret = PTR_ERR(td->vdd_reg); - if (ret != -EPROBE_DEFER) - dev_err(td->dev, "couldn't get vdd_cpu regulator: %d\n", - ret); - - return ret; - } - td->dvco_rst = devm_reset_control_get(td->dev, "dvco"); if (IS_ERR(td->dvco_rst)) { dev_err(td->dev, "couldn't get dvco reset\n"); @@ -1619,10 +1912,27 @@ int tegra_dfll_register(struct platform_device *pdev, return ret; } - ret = dfll_fetch_i2c_params(td); + if (of_property_read_bool(td->dev->of_node, "nvidia,pwm-to-pmic")) { + td->pmu_if = TEGRA_DFLL_PMU_PWM; + ret = dfll_fetch_pwm_params(td); + } else { + td->vdd_reg = devm_regulator_get(td->dev, "vdd-cpu"); + if (IS_ERR(td->vdd_reg)) { + dev_err(td->dev, "couldn't get vdd_cpu regulator\n"); + return PTR_ERR(td->vdd_reg); + } + td->pmu_if = TEGRA_DFLL_PMU_I2C; + ret = dfll_fetch_i2c_params(td); + } if (ret) return ret; + ret = dfll_build_lut(td); + if (ret) { + dev_err(td->dev, "couldn't build LUT\n"); + return ret; + } + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!mem) { dev_err(td->dev, "no control register resource\n"); From f7ebf8874c2abb12be786fe73734ba47c87ff123 Mon Sep 17 00:00:00 2001 From: Joseph Lo Date: Fri, 4 Jan 2019 11:06:50 +0800 Subject: [PATCH 076/113] clk: tegra: dfll: round down voltages based on alignment When generating the OPP table, the voltages are round down with the alignment from the regulator. The alignment should be applied for voltages look up as well. Based on the work of Penny Chiu . Signed-off-by: Joseph Lo Acked-by: Jon Hunter Acked-by: Stephen Boyd Signed-off-by: Thierry Reding --- drivers/clk/tegra/clk-dfll.c | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/drivers/clk/tegra/clk-dfll.c b/drivers/clk/tegra/clk-dfll.c index 69bbf62a9eab..0400e5b1d627 100644 --- a/drivers/clk/tegra/clk-dfll.c +++ b/drivers/clk/tegra/clk-dfll.c @@ -804,18 +804,17 @@ static void dfll_init_out_if(struct tegra_dfll *td) static int find_lut_index_for_rate(struct tegra_dfll *td, unsigned long rate) { struct dev_pm_opp *opp; - unsigned long uv; - int i; + int i, align_step; opp = dev_pm_opp_find_freq_ceil(td->soc->dev, &rate); if (IS_ERR(opp)) return PTR_ERR(opp); - uv = dev_pm_opp_get_voltage(opp); + align_step = dev_pm_opp_get_voltage(opp) / td->soc->alignment.step_uv; dev_pm_opp_put(opp); for (i = td->lut_bottom; i < td->lut_size; i++) { - if (td->lut_uv[i] >= uv) + if ((td->lut_uv[i] / td->soc->alignment.step_uv) >= align_step) return i; } @@ -1533,18 +1532,21 @@ static int dfll_init(struct tegra_dfll *td) */ static int find_vdd_map_entry_exact(struct tegra_dfll *td, int uV) { - int i, n_voltages, reg_uV; + int i, n_voltages, reg_uV,reg_volt_id, align_step; if (WARN_ON(td->pmu_if == TEGRA_DFLL_PMU_PWM)) return -EINVAL; + align_step = uV / td->soc->alignment.step_uv; n_voltages = regulator_count_voltages(td->vdd_reg); for (i = 0; i < n_voltages; i++) { reg_uV = regulator_list_voltage(td->vdd_reg, i); if (reg_uV < 0) break; - if (uV == reg_uV) + reg_volt_id = reg_uV / td->soc->alignment.step_uv; + + if (align_step == reg_volt_id) return i; } @@ -1558,18 +1560,21 @@ static int find_vdd_map_entry_exact(struct tegra_dfll *td, int uV) * */ static int find_vdd_map_entry_min(struct tegra_dfll *td, int uV) { - int i, n_voltages, reg_uV; + int i, n_voltages, reg_uV, reg_volt_id, align_step; if (WARN_ON(td->pmu_if == TEGRA_DFLL_PMU_PWM)) return -EINVAL; + align_step = uV / td->soc->alignment.step_uv; n_voltages = regulator_count_voltages(td->vdd_reg); for (i = 0; i < n_voltages; i++) { reg_uV = regulator_list_voltage(td->vdd_reg, i); if (reg_uV < 0) break; - if (uV <= reg_uV) + reg_volt_id = reg_uV / td->soc->alignment.step_uv; + + if (align_step <= reg_volt_id) return i; } From 2b2dbc2f94e55c940e1eed70706f363aa94373b0 Mon Sep 17 00:00:00 2001 From: Joseph Lo Date: Fri, 4 Jan 2019 11:06:51 +0800 Subject: [PATCH 077/113] clk: tegra: dfll: add CVB tables for Tegra210 Add CVB tables with different chip characterization, so that we can generate the customize OPP table that suitable for different chips with different SKUs. The parameter 'tune_high_min_millivolts' is first time introduced in this patch, which didn't use in the DFLL driver for clock and voltage tuning before. It will be used later when DFLL in high voltage range. Signed-off-by: Joseph Lo Acked-by: Jon Hunter Acked-by: Stephen Boyd Signed-off-by: Thierry Reding --- drivers/clk/tegra/clk-tegra124-dfll-fcpu.c | 426 +++++++++++++++++++++ drivers/clk/tegra/cvb.h | 1 + 2 files changed, 427 insertions(+) diff --git a/drivers/clk/tegra/clk-tegra124-dfll-fcpu.c b/drivers/clk/tegra/clk-tegra124-dfll-fcpu.c index 92b83f50a765..49a17916f61b 100644 --- a/drivers/clk/tegra/clk-tegra124-dfll-fcpu.c +++ b/drivers/clk/tegra/clk-tegra124-dfll-fcpu.c @@ -88,6 +88,421 @@ static const struct cvb_table tegra124_cpu_cvb_tables[] = { }, }; +static const unsigned long tegra210_cpu_max_freq_table[] = { + [0] = 1912500000UL, + [1] = 1912500000UL, + [2] = 2218500000UL, + [3] = 1785000000UL, + [4] = 1632000000UL, + [5] = 1912500000UL, + [6] = 2014500000UL, + [7] = 1734000000UL, + [8] = 1683000000UL, + [9] = 1555500000UL, + [10] = 1504500000UL, +}; + +#define CPU_CVB_TABLE \ + .speedo_scale = 100, \ + .voltage_scale = 1000, \ + .entries = { \ + { 204000000UL, { 1007452, -23865, 370 } }, \ + { 306000000UL, { 1052709, -24875, 370 } }, \ + { 408000000UL, { 1099069, -25895, 370 } }, \ + { 510000000UL, { 1146534, -26905, 370 } }, \ + { 612000000UL, { 1195102, -27915, 370 } }, \ + { 714000000UL, { 1244773, -28925, 370 } }, \ + { 816000000UL, { 1295549, -29935, 370 } }, \ + { 918000000UL, { 1347428, -30955, 370 } }, \ + { 1020000000UL, { 1400411, -31965, 370 } }, \ + { 1122000000UL, { 1454497, -32975, 370 } }, \ + { 1224000000UL, { 1509687, -33985, 370 } }, \ + { 1326000000UL, { 1565981, -35005, 370 } }, \ + { 1428000000UL, { 1623379, -36015, 370 } }, \ + { 1530000000UL, { 1681880, -37025, 370 } }, \ + { 1632000000UL, { 1741485, -38035, 370 } }, \ + { 1734000000UL, { 1802194, -39055, 370 } }, \ + { 1836000000UL, { 1864006, -40065, 370 } }, \ + { 1912500000UL, { 1910780, -40815, 370 } }, \ + { 2014500000UL, { 1227000, 0, 0 } }, \ + { 2218500000UL, { 1227000, 0, 0 } }, \ + { 0UL, { 0, 0, 0 } }, \ + } + +#define CPU_CVB_TABLE_XA \ + .speedo_scale = 100, \ + .voltage_scale = 1000, \ + .entries = { \ + { 204000000UL, { 1250024, -39785, 565 } }, \ + { 306000000UL, { 1297556, -41145, 565 } }, \ + { 408000000UL, { 1346718, -42505, 565 } }, \ + { 510000000UL, { 1397511, -43855, 565 } }, \ + { 612000000UL, { 1449933, -45215, 565 } }, \ + { 714000000UL, { 1503986, -46575, 565 } }, \ + { 816000000UL, { 1559669, -47935, 565 } }, \ + { 918000000UL, { 1616982, -49295, 565 } }, \ + { 1020000000UL, { 1675926, -50645, 565 } }, \ + { 1122000000UL, { 1736500, -52005, 565 } }, \ + { 1224000000UL, { 1798704, -53365, 565 } }, \ + { 1326000000UL, { 1862538, -54725, 565 } }, \ + { 1428000000UL, { 1928003, -56085, 565 } }, \ + { 1530000000UL, { 1995097, -57435, 565 } }, \ + { 1606500000UL, { 2046149, -58445, 565 } }, \ + { 1632000000UL, { 2063822, -58795, 565 } }, \ + { 0UL, { 0, 0, 0 } }, \ + } + +#define CPU_CVB_TABLE_EUCM1 \ + .speedo_scale = 100, \ + .voltage_scale = 1000, \ + .entries = { \ + { 204000000UL, { 734429, 0, 0 } }, \ + { 306000000UL, { 768191, 0, 0 } }, \ + { 408000000UL, { 801953, 0, 0 } }, \ + { 510000000UL, { 835715, 0, 0 } }, \ + { 612000000UL, { 869477, 0, 0 } }, \ + { 714000000UL, { 903239, 0, 0 } }, \ + { 816000000UL, { 937001, 0, 0 } }, \ + { 918000000UL, { 970763, 0, 0 } }, \ + { 1020000000UL, { 1004525, 0, 0 } }, \ + { 1122000000UL, { 1038287, 0, 0 } }, \ + { 1224000000UL, { 1072049, 0, 0 } }, \ + { 1326000000UL, { 1105811, 0, 0 } }, \ + { 1428000000UL, { 1130000, 0, 0 } }, \ + { 1555500000UL, { 1130000, 0, 0 } }, \ + { 1632000000UL, { 1170000, 0, 0 } }, \ + { 1734000000UL, { 1227500, 0, 0 } }, \ + { 0UL, { 0, 0, 0 } }, \ + } + +#define CPU_CVB_TABLE_EUCM2 \ + .speedo_scale = 100, \ + .voltage_scale = 1000, \ + .entries = { \ + { 204000000UL, { 742283, 0, 0 } }, \ + { 306000000UL, { 776249, 0, 0 } }, \ + { 408000000UL, { 810215, 0, 0 } }, \ + { 510000000UL, { 844181, 0, 0 } }, \ + { 612000000UL, { 878147, 0, 0 } }, \ + { 714000000UL, { 912113, 0, 0 } }, \ + { 816000000UL, { 946079, 0, 0 } }, \ + { 918000000UL, { 980045, 0, 0 } }, \ + { 1020000000UL, { 1014011, 0, 0 } }, \ + { 1122000000UL, { 1047977, 0, 0 } }, \ + { 1224000000UL, { 1081943, 0, 0 } }, \ + { 1326000000UL, { 1090000, 0, 0 } }, \ + { 1479000000UL, { 1090000, 0, 0 } }, \ + { 1555500000UL, { 1162000, 0, 0 } }, \ + { 1683000000UL, { 1195000, 0, 0 } }, \ + { 0UL, { 0, 0, 0 } }, \ + } + +#define CPU_CVB_TABLE_EUCM2_JOINT_RAIL \ + .speedo_scale = 100, \ + .voltage_scale = 1000, \ + .entries = { \ + { 204000000UL, { 742283, 0, 0 } }, \ + { 306000000UL, { 776249, 0, 0 } }, \ + { 408000000UL, { 810215, 0, 0 } }, \ + { 510000000UL, { 844181, 0, 0 } }, \ + { 612000000UL, { 878147, 0, 0 } }, \ + { 714000000UL, { 912113, 0, 0 } }, \ + { 816000000UL, { 946079, 0, 0 } }, \ + { 918000000UL, { 980045, 0, 0 } }, \ + { 1020000000UL, { 1014011, 0, 0 } }, \ + { 1122000000UL, { 1047977, 0, 0 } }, \ + { 1224000000UL, { 1081943, 0, 0 } }, \ + { 1326000000UL, { 1090000, 0, 0 } }, \ + { 1479000000UL, { 1090000, 0, 0 } }, \ + { 1504500000UL, { 1120000, 0, 0 } }, \ + { 0UL, { 0, 0, 0 } }, \ + } + +#define CPU_CVB_TABLE_ODN \ + .speedo_scale = 100, \ + .voltage_scale = 1000, \ + .entries = { \ + { 204000000UL, { 721094, 0, 0 } }, \ + { 306000000UL, { 754040, 0, 0 } }, \ + { 408000000UL, { 786986, 0, 0 } }, \ + { 510000000UL, { 819932, 0, 0 } }, \ + { 612000000UL, { 852878, 0, 0 } }, \ + { 714000000UL, { 885824, 0, 0 } }, \ + { 816000000UL, { 918770, 0, 0 } }, \ + { 918000000UL, { 915716, 0, 0 } }, \ + { 1020000000UL, { 984662, 0, 0 } }, \ + { 1122000000UL, { 1017608, 0, 0 } }, \ + { 1224000000UL, { 1050554, 0, 0 } }, \ + { 1326000000UL, { 1083500, 0, 0 } }, \ + { 1428000000UL, { 1116446, 0, 0 } }, \ + { 1581000000UL, { 1130000, 0, 0 } }, \ + { 1683000000UL, { 1168000, 0, 0 } }, \ + { 1785000000UL, { 1227500, 0, 0 } }, \ + { 0UL, { 0, 0, 0 } }, \ + } + +struct cvb_table tegra210_cpu_cvb_tables[] = { + { + .speedo_id = 10, + .process_id = 0, + .min_millivolts = 840, + .max_millivolts = 1120, + CPU_CVB_TABLE_EUCM2_JOINT_RAIL, + .cpu_dfll_data = { + .tune0_low = 0xffead0ff, + .tune0_high = 0xffead0ff, + .tune1 = 0x20091d9, + .tune_high_min_millivolts = 864, + } + }, + { + .speedo_id = 10, + .process_id = 1, + .min_millivolts = 840, + .max_millivolts = 1120, + CPU_CVB_TABLE_EUCM2_JOINT_RAIL, + .cpu_dfll_data = { + .tune0_low = 0xffead0ff, + .tune0_high = 0xffead0ff, + .tune1 = 0x20091d9, + .tune_high_min_millivolts = 864, + } + }, + { + .speedo_id = 9, + .process_id = 0, + .min_millivolts = 900, + .max_millivolts = 1162, + CPU_CVB_TABLE_EUCM2, + .cpu_dfll_data = { + .tune0_low = 0xffead0ff, + .tune0_high = 0xffead0ff, + .tune1 = 0x20091d9, + } + }, + { + .speedo_id = 9, + .process_id = 1, + .min_millivolts = 900, + .max_millivolts = 1162, + CPU_CVB_TABLE_EUCM2, + .cpu_dfll_data = { + .tune0_low = 0xffead0ff, + .tune0_high = 0xffead0ff, + .tune1 = 0x20091d9, + } + }, + { + .speedo_id = 8, + .process_id = 0, + .min_millivolts = 900, + .max_millivolts = 1195, + CPU_CVB_TABLE_EUCM2, + .cpu_dfll_data = { + .tune0_low = 0xffead0ff, + .tune0_high = 0xffead0ff, + .tune1 = 0x20091d9, + } + }, + { + .speedo_id = 8, + .process_id = 1, + .min_millivolts = 900, + .max_millivolts = 1195, + CPU_CVB_TABLE_EUCM2, + .cpu_dfll_data = { + .tune0_low = 0xffead0ff, + .tune0_high = 0xffead0ff, + .tune1 = 0x20091d9, + } + }, + { + .speedo_id = 7, + .process_id = 0, + .min_millivolts = 841, + .max_millivolts = 1227, + CPU_CVB_TABLE_EUCM1, + .cpu_dfll_data = { + .tune0_low = 0xffead0ff, + .tune0_high = 0xffead0ff, + .tune1 = 0x20091d9, + .tune_high_min_millivolts = 864, + } + }, + { + .speedo_id = 7, + .process_id = 1, + .min_millivolts = 841, + .max_millivolts = 1227, + CPU_CVB_TABLE_EUCM1, + .cpu_dfll_data = { + .tune0_low = 0xffead0ff, + .tune0_high = 0xffead0ff, + .tune1 = 0x20091d9, + .tune_high_min_millivolts = 864, + } + }, + { + .speedo_id = 6, + .process_id = 0, + .min_millivolts = 870, + .max_millivolts = 1150, + CPU_CVB_TABLE, + .cpu_dfll_data = { + .tune0_low = 0xffead0ff, + .tune1 = 0x20091d9, + } + }, + { + .speedo_id = 6, + .process_id = 1, + .min_millivolts = 870, + .max_millivolts = 1150, + CPU_CVB_TABLE, + .cpu_dfll_data = { + .tune0_low = 0xffead0ff, + .tune1 = 0x25501d0, + } + }, + { + .speedo_id = 5, + .process_id = 0, + .min_millivolts = 818, + .max_millivolts = 1227, + CPU_CVB_TABLE, + .cpu_dfll_data = { + .tune0_low = 0xffead0ff, + .tune0_high = 0xffead0ff, + .tune1 = 0x20091d9, + .tune_high_min_millivolts = 864, + } + }, + { + .speedo_id = 5, + .process_id = 1, + .min_millivolts = 818, + .max_millivolts = 1227, + CPU_CVB_TABLE, + .cpu_dfll_data = { + .tune0_low = 0xffead0ff, + .tune0_high = 0xffead0ff, + .tune1 = 0x25501d0, + .tune_high_min_millivolts = 864, + } + }, + { + .speedo_id = 4, + .process_id = -1, + .min_millivolts = 918, + .max_millivolts = 1113, + CPU_CVB_TABLE_XA, + .cpu_dfll_data = { + .tune0_low = 0xffead0ff, + .tune1 = 0x17711BD, + } + }, + { + .speedo_id = 3, + .process_id = 0, + .min_millivolts = 825, + .max_millivolts = 1227, + CPU_CVB_TABLE_ODN, + .cpu_dfll_data = { + .tune0_low = 0xffead0ff, + .tune0_high = 0xffead0ff, + .tune1 = 0x20091d9, + .tune_high_min_millivolts = 864, + } + }, + { + .speedo_id = 3, + .process_id = 1, + .min_millivolts = 825, + .max_millivolts = 1227, + CPU_CVB_TABLE_ODN, + .cpu_dfll_data = { + .tune0_low = 0xffead0ff, + .tune0_high = 0xffead0ff, + .tune1 = 0x25501d0, + .tune_high_min_millivolts = 864, + } + }, + { + .speedo_id = 2, + .process_id = 0, + .min_millivolts = 870, + .max_millivolts = 1227, + CPU_CVB_TABLE, + .cpu_dfll_data = { + .tune0_low = 0xffead0ff, + .tune1 = 0x20091d9, + } + }, + { + .speedo_id = 2, + .process_id = 1, + .min_millivolts = 870, + .max_millivolts = 1227, + CPU_CVB_TABLE, + .cpu_dfll_data = { + .tune0_low = 0xffead0ff, + .tune1 = 0x25501d0, + } + }, + { + .speedo_id = 1, + .process_id = 0, + .min_millivolts = 837, + .max_millivolts = 1227, + CPU_CVB_TABLE, + .cpu_dfll_data = { + .tune0_low = 0xffead0ff, + .tune0_high = 0xffead0ff, + .tune1 = 0x20091d9, + .tune_high_min_millivolts = 864, + } + }, + { + .speedo_id = 1, + .process_id = 1, + .min_millivolts = 837, + .max_millivolts = 1227, + CPU_CVB_TABLE, + .cpu_dfll_data = { + .tune0_low = 0xffead0ff, + .tune0_high = 0xffead0ff, + .tune1 = 0x25501d0, + .tune_high_min_millivolts = 864, + } + }, + { + .speedo_id = 0, + .process_id = 0, + .min_millivolts = 850, + .max_millivolts = 1170, + CPU_CVB_TABLE, + .cpu_dfll_data = { + .tune0_low = 0xffead0ff, + .tune0_high = 0xffead0ff, + .tune1 = 0x20091d9, + .tune_high_min_millivolts = 864, + } + }, + { + .speedo_id = 0, + .process_id = 1, + .min_millivolts = 850, + .max_millivolts = 1170, + CPU_CVB_TABLE, + .cpu_dfll_data = { + .tune0_low = 0xffead0ff, + .tune0_high = 0xffead0ff, + .tune1 = 0x25501d0, + .tune_high_min_millivolts = 864, + } + }, +}; + static const struct dfll_fcpu_data tegra124_dfll_fcpu_data = { .cpu_max_freq_table = tegra124_cpu_max_freq_table, .cpu_max_freq_table_size = ARRAY_SIZE(tegra124_cpu_max_freq_table), @@ -95,11 +510,22 @@ static const struct dfll_fcpu_data tegra124_dfll_fcpu_data = { .cpu_cvb_tables_size = ARRAY_SIZE(tegra124_cpu_cvb_tables) }; +static const struct dfll_fcpu_data tegra210_dfll_fcpu_data = { + .cpu_max_freq_table = tegra210_cpu_max_freq_table, + .cpu_max_freq_table_size = ARRAY_SIZE(tegra210_cpu_max_freq_table), + .cpu_cvb_tables = tegra210_cpu_cvb_tables, + .cpu_cvb_tables_size = ARRAY_SIZE(tegra210_cpu_cvb_tables), +}; + static const struct of_device_id tegra124_dfll_fcpu_of_match[] = { { .compatible = "nvidia,tegra124-dfll", .data = &tegra124_dfll_fcpu_data, }, + { + .compatible = "nvidia,tegra210-dfll", + .data = &tegra210_dfll_fcpu_data + }, { }, }; diff --git a/drivers/clk/tegra/cvb.h b/drivers/clk/tegra/cvb.h index bcf15a089b93..91a1941c21ef 100644 --- a/drivers/clk/tegra/cvb.h +++ b/drivers/clk/tegra/cvb.h @@ -41,6 +41,7 @@ struct cvb_cpu_dfll_data { u32 tune0_low; u32 tune0_high; u32 tune1; + unsigned int tune_high_min_millivolts; }; struct cvb_table { From 8bf9437a4e7f7c97909c6ace50cdd1177b907853 Mon Sep 17 00:00:00 2001 From: Peter De Schrijver Date: Fri, 4 Jan 2019 11:06:52 +0800 Subject: [PATCH 078/113] clk: tegra: dfll: build clk-dfll.c for Tegra124 and Tegra210 Tegra210 has a DFLL as well and can share the majority of the code with the Tegra124 implementation. So build the same code for both platforms. Signed-off-by: Peter De Schrijver Signed-off-by: Joseph Lo Acked-by: Jon Hunter Acked-by: Stephen Boyd Signed-off-by: Thierry Reding --- drivers/clk/tegra/Kconfig | 5 +++++ drivers/clk/tegra/Makefile | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/clk/tegra/Kconfig b/drivers/clk/tegra/Kconfig index 7ddacae5d0b1..1adcccfa7829 100644 --- a/drivers/clk/tegra/Kconfig +++ b/drivers/clk/tegra/Kconfig @@ -5,3 +5,8 @@ config TEGRA_CLK_EMC config CLK_TEGRA_BPMP def_bool y depends on TEGRA_BPMP + +config TEGRA_CLK_DFLL + depends on ARCH_TEGRA_124_SOC || ARCH_TEGRA_210_SOC + select PM_OPP + def_bool y diff --git a/drivers/clk/tegra/Makefile b/drivers/clk/tegra/Makefile index 6507acc843c7..4812e45c2214 100644 --- a/drivers/clk/tegra/Makefile +++ b/drivers/clk/tegra/Makefile @@ -20,7 +20,7 @@ obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += clk-tegra20.o obj-$(CONFIG_ARCH_TEGRA_3x_SOC) += clk-tegra30.o obj-$(CONFIG_ARCH_TEGRA_114_SOC) += clk-tegra114.o obj-$(CONFIG_ARCH_TEGRA_124_SOC) += clk-tegra124.o -obj-$(CONFIG_ARCH_TEGRA_124_SOC) += clk-tegra124-dfll-fcpu.o +obj-$(CONFIG_TEGRA_CLK_DFLL) += clk-tegra124-dfll-fcpu.o obj-$(CONFIG_ARCH_TEGRA_132_SOC) += clk-tegra124.o obj-y += cvb.o obj-$(CONFIG_ARCH_TEGRA_210_SOC) += clk-tegra210.o From 9f5ed5fe60605cadbfeb929565c02fa55e4016ba Mon Sep 17 00:00:00 2001 From: Joseph Lo Date: Fri, 4 Jan 2019 11:06:53 +0800 Subject: [PATCH 079/113] cpufreq: tegra124: do not handle the CPU rail The Tegra124 cpufreq driver has no information to handle the Vdd-CPU rail. So this driver shouldn't handle for the CPU clock switching from DFLL to other PLL clocks. It was designed to work on DFLL clock only, which handle the frequency/voltage scaling in the background. This patch removes the driver dependency of the CPU rail, as well as not allow it to be built as a module and remove the removal function. So it can keep working on DFLL clock. Cc: Viresh Kumar Cc: linux-pm@vger.kernel.org Signed-off-by: Joseph Lo Acked-by: Jon Hunter Acked-by: Viresh Kumar Signed-off-by: Thierry Reding --- drivers/cpufreq/Kconfig.arm | 4 +-- drivers/cpufreq/tegra124-cpufreq.c | 41 ++---------------------------- 2 files changed, 4 insertions(+), 41 deletions(-) diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm index 688f10227793..1a6778e81f90 100644 --- a/drivers/cpufreq/Kconfig.arm +++ b/drivers/cpufreq/Kconfig.arm @@ -272,8 +272,8 @@ config ARM_TEGRA20_CPUFREQ This adds the CPUFreq driver support for Tegra20 SOCs. config ARM_TEGRA124_CPUFREQ - tristate "Tegra124 CPUFreq support" - depends on ARCH_TEGRA && CPUFREQ_DT && REGULATOR + bool "Tegra124 CPUFreq support" + depends on ARCH_TEGRA && CPUFREQ_DT default y help This adds the CPUFreq driver support for Tegra124 SOCs. diff --git a/drivers/cpufreq/tegra124-cpufreq.c b/drivers/cpufreq/tegra124-cpufreq.c index 43530254201a..a1bfde0a7950 100644 --- a/drivers/cpufreq/tegra124-cpufreq.c +++ b/drivers/cpufreq/tegra124-cpufreq.c @@ -22,11 +22,9 @@ #include #include #include -#include #include struct tegra124_cpufreq_priv { - struct regulator *vdd_cpu_reg; struct clk *cpu_clk; struct clk *pllp_clk; struct clk *pllx_clk; @@ -60,14 +58,6 @@ static int tegra124_cpu_switch_to_dfll(struct tegra124_cpufreq_priv *priv) return ret; } -static void tegra124_cpu_switch_to_pllx(struct tegra124_cpufreq_priv *priv) -{ - clk_set_parent(priv->cpu_clk, priv->pllp_clk); - clk_disable_unprepare(priv->dfll_clk); - regulator_sync_voltage(priv->vdd_cpu_reg); - clk_set_parent(priv->cpu_clk, priv->pllx_clk); -} - static int tegra124_cpufreq_probe(struct platform_device *pdev) { struct tegra124_cpufreq_priv *priv; @@ -88,16 +78,10 @@ static int tegra124_cpufreq_probe(struct platform_device *pdev) if (!np) return -ENODEV; - priv->vdd_cpu_reg = regulator_get(cpu_dev, "vdd-cpu"); - if (IS_ERR(priv->vdd_cpu_reg)) { - ret = PTR_ERR(priv->vdd_cpu_reg); - goto out_put_np; - } - priv->cpu_clk = of_clk_get_by_name(np, "cpu_g"); if (IS_ERR(priv->cpu_clk)) { ret = PTR_ERR(priv->cpu_clk); - goto out_put_vdd_cpu_reg; + goto out_put_np; } priv->dfll_clk = of_clk_get_by_name(np, "dfll"); @@ -129,15 +113,13 @@ static int tegra124_cpufreq_probe(struct platform_device *pdev) platform_device_register_full(&cpufreq_dt_devinfo); if (IS_ERR(priv->cpufreq_dt_pdev)) { ret = PTR_ERR(priv->cpufreq_dt_pdev); - goto out_switch_to_pllx; + goto out_put_pllp_clk; } platform_set_drvdata(pdev, priv); return 0; -out_switch_to_pllx: - tegra124_cpu_switch_to_pllx(priv); out_put_pllp_clk: clk_put(priv->pllp_clk); out_put_pllx_clk: @@ -146,34 +128,15 @@ static int tegra124_cpufreq_probe(struct platform_device *pdev) clk_put(priv->dfll_clk); out_put_cpu_clk: clk_put(priv->cpu_clk); -out_put_vdd_cpu_reg: - regulator_put(priv->vdd_cpu_reg); out_put_np: of_node_put(np); return ret; } -static int tegra124_cpufreq_remove(struct platform_device *pdev) -{ - struct tegra124_cpufreq_priv *priv = platform_get_drvdata(pdev); - - platform_device_unregister(priv->cpufreq_dt_pdev); - tegra124_cpu_switch_to_pllx(priv); - - clk_put(priv->pllp_clk); - clk_put(priv->pllx_clk); - clk_put(priv->dfll_clk); - clk_put(priv->cpu_clk); - regulator_put(priv->vdd_cpu_reg); - - return 0; -} - static struct platform_driver tegra124_cpufreq_platdrv = { .driver.name = "cpufreq-tegra124", .probe = tegra124_cpufreq_probe, - .remove = tegra124_cpufreq_remove, }; static int __init tegra_cpufreq_init(void) From c06697d850fb9013fec9035a43442a7ead5c2236 Mon Sep 17 00:00:00 2001 From: Joseph Lo Date: Fri, 4 Jan 2019 11:06:54 +0800 Subject: [PATCH 080/113] cpufreq: tegra124: extend to support Tegra210 Tegra210 uses the same methodology as Tegra124 for CPUFreq controlling that based on DFLL clock. So extending this driver to support Tegra210. Cc: Viresh Kumar Cc: linux-pm@vger.kernel.org Signed-off-by: Joseph Lo Acked-by: Viresh Kumar Acked-by: Jon Hunter Signed-off-by: Thierry Reding --- drivers/cpufreq/tegra124-cpufreq.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/cpufreq/tegra124-cpufreq.c b/drivers/cpufreq/tegra124-cpufreq.c index a1bfde0a7950..ba3795e13ac6 100644 --- a/drivers/cpufreq/tegra124-cpufreq.c +++ b/drivers/cpufreq/tegra124-cpufreq.c @@ -144,7 +144,8 @@ static int __init tegra_cpufreq_init(void) int ret; struct platform_device *pdev; - if (!of_machine_is_compatible("nvidia,tegra124")) + if (!(of_machine_is_compatible("nvidia,tegra124") || + of_machine_is_compatible("nvidia,tegra210"))) return -ENODEV; /* From 43c36002b86d6027b48f7c68ac32245f73f29f6b Mon Sep 17 00:00:00 2001 From: Joseph Lo Date: Fri, 4 Jan 2019 11:06:55 +0800 Subject: [PATCH 081/113] cpufreq: dt-platdev: add Tegra210 to blacklist Tegra210 uses "tegra124-cpufreq" platform driver to register device data for "cpufreq-dt" driver. So add it in the blacklist for "cpufreq-dt-platdev" driver to drop that. Cc: Viresh Kumar Cc: linux-pm@vger.kernel.org Signed-off-by: Joseph Lo Acked-by: Jon Hunter Acked-by: Viresh Kumar Signed-off-by: Thierry Reding --- drivers/cpufreq/cpufreq-dt-platdev.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/cpufreq/cpufreq-dt-platdev.c b/drivers/cpufreq/cpufreq-dt-platdev.c index b1c5468dca16..47729a22c159 100644 --- a/drivers/cpufreq/cpufreq-dt-platdev.c +++ b/drivers/cpufreq/cpufreq-dt-platdev.c @@ -119,6 +119,7 @@ static const struct of_device_id blacklist[] __initconst = { { .compatible = "mediatek,mt8176", }, { .compatible = "nvidia,tegra124", }, + { .compatible = "nvidia,tegra210", }, { .compatible = "qcom,apq8096", }, { .compatible = "qcom,msm8996", }, From 99e5a8df8b3627239ecef09547931a81618d0851 Mon Sep 17 00:00:00 2001 From: wen yang Date: Tue, 5 Feb 2019 05:07:26 +0000 Subject: [PATCH 082/113] 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(-) 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); From 79d031fcad56e27fc4d614d54cd5962cad282473 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 7 Feb 2019 12:50:05 +0100 Subject: [PATCH 083/113] firmware: tegra: Conditionally support SoC generations Only include support for Tegra210 and Tegra186 in the BPMP driver if support for those SoCs was selected. This fixes a build failure seen on 32-bit ARM allmodconfig builds, but could also happen on 64-bit ARM builds if either Tegra210 or Tegra186 were not selected. Reported-by: Guenter Roeck Reviewed-by: Timo Alho Tested-by: Timo Alho Signed-off-by: Thierry Reding --- drivers/firmware/tegra/bpmp-private.h | 4 ++++ drivers/firmware/tegra/bpmp.c | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/drivers/firmware/tegra/bpmp-private.h b/drivers/firmware/tegra/bpmp-private.h index 07c3d46abb87..cc343f4ebafb 100644 --- a/drivers/firmware/tegra/bpmp-private.h +++ b/drivers/firmware/tegra/bpmp-private.h @@ -23,7 +23,11 @@ struct tegra_bpmp_ops { int (*resume)(struct tegra_bpmp *bpmp); }; +#if IS_ENABLED(CONFIG_ARCH_TEGRA_186_SOC) extern const struct tegra_bpmp_ops tegra186_bpmp_ops; +#endif +#if IS_ENABLED(CONFIG_ARCH_TEGRA_210_SOC) extern const struct tegra_bpmp_ops tegra210_bpmp_ops; +#endif #endif diff --git a/drivers/firmware/tegra/bpmp.c b/drivers/firmware/tegra/bpmp.c index 8e3f79959d48..6498c848c82c 100644 --- a/drivers/firmware/tegra/bpmp.c +++ b/drivers/firmware/tegra/bpmp.c @@ -813,6 +813,7 @@ static int __maybe_unused tegra_bpmp_resume(struct device *dev) static SIMPLE_DEV_PM_OPS(tegra_bpmp_pm_ops, NULL, tegra_bpmp_resume); +#if IS_ENABLED(CONFIG_ARCH_TEGRA_186_SOC) static const struct tegra_bpmp_soc tegra186_soc = { .channels = { .cpu_tx = { @@ -832,7 +833,9 @@ static const struct tegra_bpmp_soc tegra186_soc = { .ops = &tegra186_bpmp_ops, .num_resets = 193, }; +#endif +#if IS_ENABLED(CONFIG_ARCH_TEGRA_210_SOC) static const struct tegra_bpmp_soc tegra210_soc = { .channels = { .cpu_tx = { @@ -853,10 +856,15 @@ static const struct tegra_bpmp_soc tegra210_soc = { }, .ops = &tegra210_bpmp_ops, }; +#endif static const struct of_device_id tegra_bpmp_match[] = { +#if IS_ENABLED(CONFIG_ARCH_TEGRA_186_SOC) { .compatible = "nvidia,tegra186-bpmp", .data = &tegra186_soc }, +#endif +#if IS_ENABLED(CONFIG_ARCH_TEGRA_210_SOC) { .compatible = "nvidia,tegra210-bpmp", .data = &tegra210_soc }, +#endif { } }; From fe45ab552955ee93b492cbf86603eb02b3c77a66 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 7 Feb 2019 12:50:06 +0100 Subject: [PATCH 084/113] firmware/tegra: Enable Tegra186 BPMP support on Tegra194 The BPMP implementation on Tegra194 is mostly compatible with the implementation on Tegra186, so make sure the latter is available when support for Tegra194 is enabled. Suggested-by: Timo Alho Reviewed-by: Timo Alho Tested-by: Timo Alho Signed-off-by: Thierry Reding --- drivers/firmware/tegra/Makefile | 1 + drivers/firmware/tegra/bpmp-private.h | 3 ++- drivers/firmware/tegra/bpmp.c | 6 ++++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/firmware/tegra/Makefile b/drivers/firmware/tegra/Makefile index ba45e58f7647..676b01caff05 100644 --- a/drivers/firmware/tegra/Makefile +++ b/drivers/firmware/tegra/Makefile @@ -1,6 +1,7 @@ tegra-bpmp-y = bpmp.o tegra-bpmp-$(CONFIG_ARCH_TEGRA_210_SOC) += bpmp-tegra210.o tegra-bpmp-$(CONFIG_ARCH_TEGRA_186_SOC) += bpmp-tegra186.o +tegra-bpmp-$(CONFIG_ARCH_TEGRA_194_SOC) += bpmp-tegra186.o tegra-bpmp-$(CONFIG_DEBUG_FS) += bpmp-debugfs.o obj-$(CONFIG_TEGRA_BPMP) += tegra-bpmp.o obj-$(CONFIG_TEGRA_IVC) += ivc.o diff --git a/drivers/firmware/tegra/bpmp-private.h b/drivers/firmware/tegra/bpmp-private.h index cc343f4ebafb..54d560c48398 100644 --- a/drivers/firmware/tegra/bpmp-private.h +++ b/drivers/firmware/tegra/bpmp-private.h @@ -23,7 +23,8 @@ struct tegra_bpmp_ops { int (*resume)(struct tegra_bpmp *bpmp); }; -#if IS_ENABLED(CONFIG_ARCH_TEGRA_186_SOC) +#if IS_ENABLED(CONFIG_ARCH_TEGRA_186_SOC) || \ + IS_ENABLED(CONFIG_ARCH_TEGRA_194_SOC) extern const struct tegra_bpmp_ops tegra186_bpmp_ops; #endif #if IS_ENABLED(CONFIG_ARCH_TEGRA_210_SOC) diff --git a/drivers/firmware/tegra/bpmp.c b/drivers/firmware/tegra/bpmp.c index 6498c848c82c..dd775e8ba5a0 100644 --- a/drivers/firmware/tegra/bpmp.c +++ b/drivers/firmware/tegra/bpmp.c @@ -813,7 +813,8 @@ static int __maybe_unused tegra_bpmp_resume(struct device *dev) static SIMPLE_DEV_PM_OPS(tegra_bpmp_pm_ops, NULL, tegra_bpmp_resume); -#if IS_ENABLED(CONFIG_ARCH_TEGRA_186_SOC) +#if IS_ENABLED(CONFIG_ARCH_TEGRA_186_SOC) || \ + IS_ENABLED(CONFIG_ARCH_TEGRA_194_SOC) static const struct tegra_bpmp_soc tegra186_soc = { .channels = { .cpu_tx = { @@ -859,7 +860,8 @@ static const struct tegra_bpmp_soc tegra210_soc = { #endif static const struct of_device_id tegra_bpmp_match[] = { -#if IS_ENABLED(CONFIG_ARCH_TEGRA_186_SOC) +#if IS_ENABLED(CONFIG_ARCH_TEGRA_186_SOC) || \ + IS_ENABLED(CONFIG_ARCH_TEGRA_194_SOC) { .compatible = "nvidia,tegra186-bpmp", .data = &tegra186_soc }, #endif #if IS_ENABLED(CONFIG_ARCH_TEGRA_210_SOC) From 382f8be04551d60c3e0c4103ce2941c3f335279e Mon Sep 17 00:00:00 2001 From: Neil Armstrong Date: Mon, 4 Feb 2019 10:49:37 +0100 Subject: [PATCH 085/113] 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(-) 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); From c28de6bf856eac0b0d1854d57c7a55b7c65a46e3 Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Fri, 18 Jan 2019 11:34:20 +0100 Subject: [PATCH 086/113] dt-bindings: amlogic: add new compatible devices to clk_measure Add the axg and g12a SoC family compatible to the clock measure bindings Signed-off-by: Jerome Brunet Reviewed-by: Martin Blumenstingl Reviewed-by: Rob Herring Signed-off-by: Kevin Hilman --- Documentation/devicetree/bindings/soc/amlogic/clk-measure.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/soc/amlogic/clk-measure.txt b/Documentation/devicetree/bindings/soc/amlogic/clk-measure.txt index 205a54bcd7c7..6bf6b43f8dd8 100644 --- a/Documentation/devicetree/bindings/soc/amlogic/clk-measure.txt +++ b/Documentation/devicetree/bindings/soc/amlogic/clk-measure.txt @@ -9,6 +9,8 @@ Required properties: "amlogic,meson-gx-clk-measure" for GX SoCs "amlogic,meson8-clk-measure" for Meson8 SoCs "amlogic,meson8b-clk-measure" for Meson8b SoCs + "amlogic,meson-axg-clk-measure" for AXG SoCs + "amlogic,meson-g12a-clk-measure" for G12a SoCs - reg: base address and size of the Clock Measurer register space. Example: From 19e0bde7bf6a18a910e5d633ef606d6ff298c2cb Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Fri, 18 Jan 2019 11:34:21 +0100 Subject: [PATCH 087/113] 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(+) 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 */ } }; From 705c0ee8d4a64b072e324f8daa8767e92560a892 Mon Sep 17 00:00:00 2001 From: John Garry Date: Thu, 3 Jan 2019 19:57:02 +0800 Subject: [PATCH 088/113] bus: hisi_lpc: Don't fail probe for unrecognised child devices Currently for ACPI-based FW we fail the probe for an unrecognised child HID. However, there is FW in the field with LPC child devices having fake HIDs, namely "IPI0002", which was an IPMI device invented to support the initial out-of-tree LPC host driver, different from the final mainline version. To provide compatibility support for these dodgy FWs, just discard the unrecognised HIDs instead of failing the probe altogether. Tested-by: Zengruan Ye Signed-off-by: John Garry Signed-off-by: Wei Xu --- drivers/bus/hisi_lpc.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/bus/hisi_lpc.c b/drivers/bus/hisi_lpc.c index d5f85455fa62..19d7b6ff2f17 100644 --- a/drivers/bus/hisi_lpc.c +++ b/drivers/bus/hisi_lpc.c @@ -522,10 +522,9 @@ static int hisi_lpc_acpi_probe(struct device *hostdev) if (!found) { dev_warn(hostdev, - "could not find cell for child device (%s)\n", + "could not find cell for child device (%s), discarding\n", hid); - ret = -ENODEV; - goto fail; + continue; } pdev = platform_device_alloc(cell->name, PLATFORM_DEVID_AUTO); From dbfc54534dfcaee004d54137d9b34d000f847e41 Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Fri, 1 Feb 2019 13:50:03 +0100 Subject: [PATCH 089/113] dt-bindings: reset: meson: add g12a bindings Add device tree bindings for the reset controller of g12a SoC family. Acked-by: Neil Armstrong Signed-off-by: Jerome Brunet Acked-by: Kevin Hilman Signed-off-by: Philipp Zabel --- .../reset/amlogic,meson-g12a-reset.h | 134 ++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 include/dt-bindings/reset/amlogic,meson-g12a-reset.h diff --git a/include/dt-bindings/reset/amlogic,meson-g12a-reset.h b/include/dt-bindings/reset/amlogic,meson-g12a-reset.h new file mode 100644 index 000000000000..8063e8314eef --- /dev/null +++ b/include/dt-bindings/reset/amlogic,meson-g12a-reset.h @@ -0,0 +1,134 @@ +/* SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause */ +/* + * Copyright (c) 2019 BayLibre, SAS. + * Author: Jerome Brunet + * + */ + +#ifndef _DT_BINDINGS_AMLOGIC_MESON_G12A_RESET_H +#define _DT_BINDINGS_AMLOGIC_MESON_G12A_RESET_H + +/* RESET0 */ +#define RESET_HIU 0 +/* 1 */ +#define RESET_DOS 2 +/* 3-4 */ +#define RESET_VIU 5 +#define RESET_AFIFO 6 +#define RESET_VID_PLL_DIV 7 +/* 8-9 */ +#define RESET_VENC 10 +#define RESET_ASSIST 11 +#define RESET_PCIE_CTRL_A 12 +#define RESET_VCBUS 13 +#define RESET_PCIE_PHY 14 +#define RESET_PCIE_APB 15 +#define RESET_GIC 16 +#define RESET_CAPB3_DECODE 17 +/* 18 */ +#define RESET_HDMITX_CAPB3 19 +#define RESET_DVALIN_CAPB3 20 +#define RESET_DOS_CAPB3 21 +/* 22 */ +#define RESET_CBUS_CAPB3 23 +#define RESET_AHB_CNTL 24 +#define RESET_AHB_DATA 25 +#define RESET_VCBUS_CLK81 26 +/* 27-31 */ +/* RESET1 */ +/* 32 */ +#define RESET_DEMUX 33 +#define RESET_USB 34 +#define RESET_DDR 35 +/* 36 */ +#define RESET_BT656 37 +#define RESET_AHB_SRAM 38 +/* 39 */ +#define RESET_PARSER 40 +/* 41 */ +#define RESET_ISA 42 +#define RESET_ETHERNET 43 +#define RESET_SD_EMMC_A 44 +#define RESET_SD_EMMC_B 45 +#define RESET_SD_EMMC_C 46 +/* 47-60 */ +#define RESET_AUDIO_CODEC 61 +/* 62-63 */ +/* RESET2 */ +/* 64 */ +#define RESET_AUDIO 65 +#define RESET_HDMITX_PHY 66 +/* 67 */ +#define RESET_MIPI_DSI_HOST 68 +#define RESET_ALOCKER 69 +#define RESET_GE2D 70 +#define RESET_PARSER_REG 71 +#define RESET_PARSER_FETCH 72 +#define RESET_CTL 73 +#define RESET_PARSER_TOP 74 +/* 75-77 */ +#define RESET_DVALIN 78 +#define RESET_HDMITX 79 +/* 80-95 */ +/* RESET3 */ +/* 96-95 */ +#define RESET_DEMUX_TOP 105 +#define RESET_DEMUX_DES_PL 106 +#define RESET_DEMUX_S2P_0 107 +#define RESET_DEMUX_S2P_1 108 +#define RESET_DEMUX_0 109 +#define RESET_DEMUX_1 110 +#define RESET_DEMUX_2 111 +/* 112-127 */ +/* RESET4 */ +/* 128-129 */ +#define RESET_MIPI_DSI_PHY 130 +/* 131-132 */ +#define RESET_RDMA 133 +#define RESET_VENCI 134 +#define RESET_VENCP 135 +/* 136 */ +#define RESET_VDAC 137 +/* 138-139 */ +#define RESET_VDI6 140 +#define RESET_VENCL 141 +#define RESET_I2C_M1 142 +#define RESET_I2C_M2 143 +/* 144-159 */ +/* RESET5 */ +/* 160-191 */ +/* RESET6 */ +#define RESET_GEN 192 +#define RESET_SPICC0 193 +#define RESET_SC 194 +#define RESET_SANA_3 195 +#define RESET_I2C_M0 196 +#define RESET_TS_PLL 197 +#define RESET_SPICC1 198 +#define RESET_STREAM 199 +#define RESET_TS_CPU 200 +#define RESET_UART0 201 +#define RESET_UART1_2 202 +#define RESET_ASYNC0 203 +#define RESET_ASYNC1 204 +#define RESET_SPIFC0 205 +#define RESET_I2C_M3 206 +/* 207-223 */ +/* RESET7 */ +#define RESET_USB_DDR_0 224 +#define RESET_USB_DDR_1 225 +#define RESET_USB_DDR_2 226 +#define RESET_USB_DDR_3 227 +#define RESET_TS_GPU 228 +#define RESET_DEVICE_MMC_ARB 229 +#define RESET_DVALIN_DMC_PIPL 230 +#define RESET_VID_LOCK 231 +#define RESET_NIC_DMC_PIPL 232 +#define RESET_DMC_VPU_PIPL 233 +#define RESET_GE2D_DMC_PIPL 234 +#define RESET_HCODEC_DMC_PIPL 235 +#define RESET_WAVE420_DMC_PIPL 236 +#define RESET_HEVCF_DMC_PIPL 237 +/* 238-255 */ + +#endif From d90bf296ae18f26a18e572965fc0047fa1bd37a8 Mon Sep 17 00:00:00 2001 From: Daniel Baluta Date: Wed, 30 Jan 2019 13:30:22 +0000 Subject: [PATCH 090/113] firmware: imx: Add support to start/stop a CPU This is done via RPC call to SCU. Signed-off-by: Daniel Baluta Reviewed-by: Dong Aisheng Signed-off-by: Shawn Guo --- drivers/firmware/imx/misc.c | 38 +++++++++++++++++++++++++++ include/linux/firmware/imx/svc/misc.h | 3 +++ 2 files changed, 41 insertions(+) diff --git a/drivers/firmware/imx/misc.c b/drivers/firmware/imx/misc.c index 97f5424dbac9..4b56a587dacd 100644 --- a/drivers/firmware/imx/misc.c +++ b/drivers/firmware/imx/misc.c @@ -18,6 +18,14 @@ struct imx_sc_msg_req_misc_set_ctrl { u16 resource; } __packed; +struct imx_sc_msg_req_cpu_start { + struct imx_sc_rpc_msg hdr; + u32 address_hi; + u32 address_lo; + u16 resource; + u8 enable; +} __packed; + struct imx_sc_msg_req_misc_get_ctrl { struct imx_sc_rpc_msg hdr; u32 ctrl; @@ -97,3 +105,33 @@ int imx_sc_misc_get_control(struct imx_sc_ipc *ipc, u32 resource, return 0; } EXPORT_SYMBOL(imx_sc_misc_get_control); + +/* + * This function starts/stops a CPU identified by @resource + * + * @param[in] ipc IPC handle + * @param[in] resource resource the control is associated with + * @param[in] enable true for start, false for stop + * @param[in] phys_addr initial instruction address to be executed + * + * @return Returns 0 for success and < 0 for errors. + */ +int imx_sc_pm_cpu_start(struct imx_sc_ipc *ipc, u32 resource, + bool enable, u64 phys_addr) +{ + struct imx_sc_msg_req_cpu_start msg; + struct imx_sc_rpc_msg *hdr = &msg.hdr; + + hdr->ver = IMX_SC_RPC_VERSION; + hdr->svc = IMX_SC_RPC_SVC_PM; + hdr->func = IMX_SC_PM_FUNC_CPU_START; + hdr->size = 4; + + msg.address_hi = phys_addr >> 32; + msg.address_lo = phys_addr; + msg.resource = resource; + msg.enable = enable; + + return imx_scu_call_rpc(ipc, &msg, true); +} +EXPORT_SYMBOL(imx_sc_pm_cpu_start); diff --git a/include/linux/firmware/imx/svc/misc.h b/include/linux/firmware/imx/svc/misc.h index e21c49aba92f..031dd4d3c766 100644 --- a/include/linux/firmware/imx/svc/misc.h +++ b/include/linux/firmware/imx/svc/misc.h @@ -52,4 +52,7 @@ int imx_sc_misc_set_control(struct imx_sc_ipc *ipc, u32 resource, int imx_sc_misc_get_control(struct imx_sc_ipc *ipc, u32 resource, u8 ctrl, u32 *val); +int imx_sc_pm_cpu_start(struct imx_sc_ipc *ipc, u32 resource, + bool enable, u64 phys_addr); + #endif /* _SC_MISC_API_H */ From d4ff6c9efa2e40fe3afc0eac5cf93086c6db962b Mon Sep 17 00:00:00 2001 From: Rajan Vaja Date: Tue, 29 Jan 2019 12:38:19 -0800 Subject: [PATCH 091/113] dt-bindings: soc: Add ZynqMP PM bindings Add documentation to describe Xilinx ZynqMP power management bindings. Signed-off-by: Rajan Vaja Signed-off-by: Jolly Shah Reviewed-by: Rob Herring Signed-off-by: Michal Simek --- .../power/reset/xlnx,zynqmp-power.txt | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 Documentation/devicetree/bindings/power/reset/xlnx,zynqmp-power.txt diff --git a/Documentation/devicetree/bindings/power/reset/xlnx,zynqmp-power.txt b/Documentation/devicetree/bindings/power/reset/xlnx,zynqmp-power.txt new file mode 100644 index 000000000000..d366f1eb623a --- /dev/null +++ b/Documentation/devicetree/bindings/power/reset/xlnx,zynqmp-power.txt @@ -0,0 +1,25 @@ +-------------------------------------------------------------------- +Device Tree Bindings for the Xilinx Zynq MPSoC Power Management +-------------------------------------------------------------------- +The zynqmp-power node describes the power management configurations. +It will control remote suspend/shutdown interfaces. + +Required properties: + - compatible: Must contain: "xlnx,zynqmp-power" + - interrupts: Interrupt specifier + +------- +Example +------- + +firmware { + zynqmp_firmware: zynqmp-firmware { + compatible = "xlnx,zynqmp-firmware"; + method = "smc"; + + zynqmp_power: zynqmp-power { + compatible = "xlnx,zynqmp-power"; + interrupts = <0 35 4>; + }; + }; +}; From e178df31cf41ba7cd63f7830bd02fd918d16592d Mon Sep 17 00:00:00 2001 From: Jolly Shah Date: Tue, 29 Jan 2019 12:38:20 -0800 Subject: [PATCH 092/113] firmware: xilinx: Implement ZynqMP power management APIs Add Xilinx ZynqMP firmware APIs to set suspend mode and inform firmware that master has initialized its own power management. Signed-off-by: Rajan Vaja Signed-off-by: Jolly Shah Signed-off-by: Michal Simek --- drivers/firmware/xilinx/zynqmp.c | 29 ++++++++++++++++++++++++++++ include/linux/firmware/xlnx-zynqmp.h | 20 +++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/drivers/firmware/xilinx/zynqmp.c b/drivers/firmware/xilinx/zynqmp.c index 16a23bc4c2c3..765a2ca1b100 100644 --- a/drivers/firmware/xilinx/zynqmp.c +++ b/drivers/firmware/xilinx/zynqmp.c @@ -530,6 +530,33 @@ static int zynqmp_pm_reset_get_status(const enum zynqmp_pm_reset reset, return ret; } +/** + * zynqmp_pm_init_finalize() - PM call to inform firmware that the caller + * master has initialized its own power management + * + * This API function is to be used for notify the power management controller + * about the completed power management initialization. + * + * Return: Returns status, either success or error+reason + */ +static int zynqmp_pm_init_finalize(void) +{ + return zynqmp_pm_invoke_fn(PM_PM_INIT_FINALIZE, 0, 0, 0, 0, NULL); +} + +/** + * zynqmp_pm_set_suspend_mode() - Set system suspend mode + * @mode: Mode to set for system suspend + * + * This API function is used to set mode of system suspend. + * + * Return: Returns status, either success or error+reason + */ +static int zynqmp_pm_set_suspend_mode(u32 mode) +{ + return zynqmp_pm_invoke_fn(PM_SET_SUSPEND_MODE, mode, 0, 0, 0, NULL); +} + static const struct zynqmp_eemi_ops eemi_ops = { .get_api_version = zynqmp_pm_get_api_version, .get_chipid = zynqmp_pm_get_chipid, @@ -546,6 +573,8 @@ static const struct zynqmp_eemi_ops eemi_ops = { .ioctl = zynqmp_pm_ioctl, .reset_assert = zynqmp_pm_reset_assert, .reset_get_status = zynqmp_pm_reset_get_status, + .init_finalize = zynqmp_pm_init_finalize, + .set_suspend_mode = zynqmp_pm_set_suspend_mode, }; /** diff --git a/include/linux/firmware/xlnx-zynqmp.h b/include/linux/firmware/xlnx-zynqmp.h index 5a1f19848100..56b2108a2148 100644 --- a/include/linux/firmware/xlnx-zynqmp.h +++ b/include/linux/firmware/xlnx-zynqmp.h @@ -28,14 +28,23 @@ /* SMC SIP service Call Function Identifier Prefix */ #define PM_SIP_SVC 0xC2000000 #define PM_GET_TRUSTZONE_VERSION 0xa03 +#define PM_SET_SUSPEND_MODE 0xa02 +#define GET_CALLBACK_DATA 0xa01 /* Number of 32bits values in payload */ #define PAYLOAD_ARG_CNT 4U +/* Number of arguments for a callback */ +#define CB_ARG_CNT 4 + +/* Payload size (consists of callback API ID + arguments) */ +#define CB_PAYLOAD_SIZE (CB_ARG_CNT + 1) + enum pm_api_id { PM_GET_API_VERSION = 1, PM_RESET_ASSERT = 17, PM_RESET_GET_STATUS, + PM_PM_INIT_FINALIZE = 21, PM_GET_CHIPID = 24, PM_IOCTL = 34, PM_QUERY_DATA, @@ -209,6 +218,12 @@ enum zynqmp_pm_reset { ZYNQMP_PM_RESET_END = ZYNQMP_PM_RESET_PS_PL3 }; +enum zynqmp_pm_suspend_reason { + SUSPEND_POWER_REQUEST = 201, + SUSPEND_ALERT, + SUSPEND_SYSTEM_SHUTDOWN, +}; + /** * struct zynqmp_pm_query_data - PM query data * @qid: query ID @@ -240,8 +255,13 @@ struct zynqmp_eemi_ops { int (*reset_assert)(const enum zynqmp_pm_reset reset, const enum zynqmp_pm_reset_action assert_flag); int (*reset_get_status)(const enum zynqmp_pm_reset reset, u32 *status); + int (*init_finalize)(void); + int (*set_suspend_mode)(u32 mode); }; +int zynqmp_pm_invoke_fn(u32 pm_api_id, u32 arg0, u32 arg1, + u32 arg2, u32 arg3, u32 *ret_payload); + #if IS_REACHABLE(CONFIG_ARCH_ZYNQMP) const struct zynqmp_eemi_ops *zynqmp_pm_get_eemi_ops(void); #else From ab272643d723f531b9caa8a4ac5f85fcd26840d2 Mon Sep 17 00:00:00 2001 From: Rajan Vaja Date: Tue, 29 Jan 2019 12:38:21 -0800 Subject: [PATCH 093/113] 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 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); From 8fd27fb4cf76a64d4ea4814b358541477dc7a31e Mon Sep 17 00:00:00 2001 From: Rajan Vaja Date: Fri, 1 Feb 2019 14:08:48 -0800 Subject: [PATCH 094/113] dt-bindings: power: Add ZynqMP power domain bindings Add documentation to describe ZynqMP power domain bindings. Signed-off-by: Rajan Vaja Signed-off-by: Jolly Shah Reviewed-by: Rob Herring Signed-off-by: Michal Simek --- .../bindings/power/xlnx,zynqmp-genpd.txt | 34 ++++++++++++++++ include/dt-bindings/power/xlnx-zynqmp-power.h | 39 +++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 Documentation/devicetree/bindings/power/xlnx,zynqmp-genpd.txt create mode 100644 include/dt-bindings/power/xlnx-zynqmp-power.h diff --git a/Documentation/devicetree/bindings/power/xlnx,zynqmp-genpd.txt b/Documentation/devicetree/bindings/power/xlnx,zynqmp-genpd.txt new file mode 100644 index 000000000000..8d1b8200ebd0 --- /dev/null +++ b/Documentation/devicetree/bindings/power/xlnx,zynqmp-genpd.txt @@ -0,0 +1,34 @@ +----------------------------------------------------------- +Device Tree Bindings for the Xilinx Zynq MPSoC PM domains +----------------------------------------------------------- +The binding for zynqmp-power-controller follow the common +generic PM domain binding[1]. + +[1] Documentation/devicetree/bindings/power/power_domain.txt + +== Zynq MPSoC Generic PM Domain Node == + +Required property: + - Below property should be in zynqmp-firmware node. + - #power-domain-cells: Number of cells in a PM domain specifier. Must be 1. + +Power domain ID indexes are mentioned in +include/dt-bindings/power/xlnx-zynqmp-power.h. + +------- +Example +------- + +firmware { + zynqmp_firmware: zynqmp-firmware { + ... + #power-domain-cells = <1>; + ... + }; +}; + +sata { + ... + power-domains = <&zynqmp_firmware 28>; + ... +}; diff --git a/include/dt-bindings/power/xlnx-zynqmp-power.h b/include/dt-bindings/power/xlnx-zynqmp-power.h new file mode 100644 index 000000000000..0d9a412fd5e0 --- /dev/null +++ b/include/dt-bindings/power/xlnx-zynqmp-power.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2018 Xilinx, Inc. + */ + +#ifndef _DT_BINDINGS_ZYNQMP_POWER_H +#define _DT_BINDINGS_ZYNQMP_POWER_H + +#define PD_USB_0 22 +#define PD_USB_1 23 +#define PD_TTC_0 24 +#define PD_TTC_1 25 +#define PD_TTC_2 26 +#define PD_TTC_3 27 +#define PD_SATA 28 +#define PD_ETH_0 29 +#define PD_ETH_1 30 +#define PD_ETH_2 31 +#define PD_ETH_3 32 +#define PD_UART_0 33 +#define PD_UART_1 34 +#define PD_SPI_0 35 +#define PD_SPI_1 36 +#define PD_I2C_0 37 +#define PD_I2C_1 38 +#define PD_SD_0 39 +#define PD_SD_1 40 +#define PD_DP 41 +#define PD_GDMA 42 +#define PD_ADMA 43 +#define PD_NAND 44 +#define PD_QSPI 45 +#define PD_GPIO 46 +#define PD_CAN_0 47 +#define PD_CAN_1 48 +#define PD_GPU 58 +#define PD_PCIE 59 + +#endif From c1986ac3d483b051fc237aea3e9812fd1bb4d239 Mon Sep 17 00:00:00 2001 From: Rajan Vaja Date: Fri, 1 Feb 2019 14:08:49 -0800 Subject: [PATCH 095/113] firmware: xilinx: Add APIs to control node status/power Add Xilinx ZynqMP firmware APIs to control node status and power. These APIs allows turning on/off power domain and setting capabilities of devices present in power domain. Signed-off-by: Rajan Vaja Signed-off-by: Jolly Shah Signed-off-by: Michal Simek --- drivers/firmware/xilinx/zynqmp.c | 58 ++++++++++++++++++++++++++++ include/linux/firmware/xlnx-zynqmp.h | 26 +++++++++++++ 2 files changed, 84 insertions(+) diff --git a/drivers/firmware/xilinx/zynqmp.c b/drivers/firmware/xilinx/zynqmp.c index 765a2ca1b100..af5cffd3ac43 100644 --- a/drivers/firmware/xilinx/zynqmp.c +++ b/drivers/firmware/xilinx/zynqmp.c @@ -557,6 +557,61 @@ static int zynqmp_pm_set_suspend_mode(u32 mode) return zynqmp_pm_invoke_fn(PM_SET_SUSPEND_MODE, mode, 0, 0, 0, NULL); } +/** + * zynqmp_pm_request_node() - Request a node with specific capabilities + * @node: Node ID of the slave + * @capabilities: Requested capabilities of the slave + * @qos: Quality of service (not supported) + * @ack: Flag to specify whether acknowledge is requested + * + * This function is used by master to request particular node from firmware. + * Every master must request node before using it. + * + * Return: Returns status, either success or error+reason + */ +static int zynqmp_pm_request_node(const u32 node, const u32 capabilities, + const u32 qos, + const enum zynqmp_pm_request_ack ack) +{ + return zynqmp_pm_invoke_fn(PM_REQUEST_NODE, node, capabilities, + qos, ack, NULL); +} + +/** + * zynqmp_pm_release_node() - Release a node + * @node: Node ID of the slave + * + * This function is used by master to inform firmware that master + * has released node. Once released, master must not use that node + * without re-request. + * + * Return: Returns status, either success or error+reason + */ +static int zynqmp_pm_release_node(const u32 node) +{ + return zynqmp_pm_invoke_fn(PM_RELEASE_NODE, node, 0, 0, 0, NULL); +} + +/** + * zynqmp_pm_set_requirement() - PM call to set requirement for PM slaves + * @node: Node ID of the slave + * @capabilities: Requested capabilities of the slave + * @qos: Quality of service (not supported) + * @ack: Flag to specify whether acknowledge is requested + * + * This API function is to be used for slaves a PU already has requested + * to change its capabilities. + * + * Return: Returns status, either success or error+reason + */ +static int zynqmp_pm_set_requirement(const u32 node, const u32 capabilities, + const u32 qos, + const enum zynqmp_pm_request_ack ack) +{ + return zynqmp_pm_invoke_fn(PM_SET_REQUIREMENT, node, capabilities, + qos, ack, NULL); +} + static const struct zynqmp_eemi_ops eemi_ops = { .get_api_version = zynqmp_pm_get_api_version, .get_chipid = zynqmp_pm_get_chipid, @@ -575,6 +630,9 @@ static const struct zynqmp_eemi_ops eemi_ops = { .reset_get_status = zynqmp_pm_reset_get_status, .init_finalize = zynqmp_pm_init_finalize, .set_suspend_mode = zynqmp_pm_set_suspend_mode, + .request_node = zynqmp_pm_request_node, + .release_node = zynqmp_pm_release_node, + .set_requirement = zynqmp_pm_set_requirement, }; /** diff --git a/include/linux/firmware/xlnx-zynqmp.h b/include/linux/firmware/xlnx-zynqmp.h index 56b2108a2148..642dab10f65d 100644 --- a/include/linux/firmware/xlnx-zynqmp.h +++ b/include/linux/firmware/xlnx-zynqmp.h @@ -40,8 +40,19 @@ /* Payload size (consists of callback API ID + arguments) */ #define CB_PAYLOAD_SIZE (CB_ARG_CNT + 1) +#define ZYNQMP_PM_MAX_QOS 100U + +/* Node capabilities */ +#define ZYNQMP_PM_CAPABILITY_ACCESS 0x1U +#define ZYNQMP_PM_CAPABILITY_CONTEXT 0x2U +#define ZYNQMP_PM_CAPABILITY_WAKEUP 0x4U +#define ZYNQMP_PM_CAPABILITY_POWER 0x8U + enum pm_api_id { PM_GET_API_VERSION = 1, + PM_REQUEST_NODE = 13, + PM_RELEASE_NODE, + PM_SET_REQUIREMENT, PM_RESET_ASSERT = 17, PM_RESET_GET_STATUS, PM_PM_INIT_FINALIZE = 21, @@ -224,6 +235,12 @@ enum zynqmp_pm_suspend_reason { SUSPEND_SYSTEM_SHUTDOWN, }; +enum zynqmp_pm_request_ack { + ZYNQMP_PM_REQUEST_ACK_NO = 1, + ZYNQMP_PM_REQUEST_ACK_BLOCKING, + ZYNQMP_PM_REQUEST_ACK_NON_BLOCKING, +}; + /** * struct zynqmp_pm_query_data - PM query data * @qid: query ID @@ -257,6 +274,15 @@ struct zynqmp_eemi_ops { int (*reset_get_status)(const enum zynqmp_pm_reset reset, u32 *status); int (*init_finalize)(void); int (*set_suspend_mode)(u32 mode); + int (*request_node)(const u32 node, + const u32 capabilities, + const u32 qos, + const enum zynqmp_pm_request_ack ack); + int (*release_node)(const u32 node); + int (*set_requirement)(const u32 node, + const u32 capabilities, + const u32 qos, + const enum zynqmp_pm_request_ack ack); }; int zynqmp_pm_invoke_fn(u32 pm_api_id, u32 arg0, u32 arg1, From e23d9c6d0d4912fab12cd2d56070b0a8199772f0 Mon Sep 17 00:00:00 2001 From: Jolly Shah Date: Fri, 1 Feb 2019 14:08:50 -0800 Subject: [PATCH 096/113] 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/firmware/xilinx/Kconfig | 1 + drivers/firmware/xilinx/zynqmp.c | 15 ++ drivers/soc/xilinx/Kconfig | 9 + drivers/soc/xilinx/Makefile | 1 + drivers/soc/xilinx/zynqmp_pm_domains.c | 321 +++++++++++++++++++++++++ 5 files changed, 347 insertions(+) create mode 100644 drivers/soc/xilinx/zynqmp_pm_domains.c diff --git a/drivers/firmware/xilinx/Kconfig b/drivers/firmware/xilinx/Kconfig index 8f44b9cd295a..bd33bbf70daf 100644 --- a/drivers/firmware/xilinx/Kconfig +++ b/drivers/firmware/xilinx/Kconfig @@ -6,6 +6,7 @@ menu "Zynq MPSoC Firmware Drivers" config ZYNQMP_FIRMWARE bool "Enable Xilinx Zynq MPSoC firmware interface" + select MFD_CORE help Firmware interface driver is used by different drivers to communicate with the firmware for diff --git a/drivers/firmware/xilinx/zynqmp.c b/drivers/firmware/xilinx/zynqmp.c index af5cffd3ac43..98f936125643 100644 --- a/drivers/firmware/xilinx/zynqmp.c +++ b/drivers/firmware/xilinx/zynqmp.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -23,6 +24,12 @@ #include #include "zynqmp-debug.h" +static const struct mfd_cell firmware_devs[] = { + { + .name = "zynqmp_power_controller", + }, +}; + /** * zynqmp_pm_ret_code() - Convert PMU-FW error codes to Linux error codes * @ret_status: PMUFW return code @@ -689,11 +696,19 @@ static int zynqmp_firmware_probe(struct platform_device *pdev) zynqmp_pm_api_debugfs_init(); + ret = mfd_add_devices(&pdev->dev, PLATFORM_DEVID_NONE, firmware_devs, + ARRAY_SIZE(firmware_devs), NULL, 0, NULL); + if (ret) { + dev_err(&pdev->dev, "failed to add MFD devices %d\n", ret); + return ret; + } + return of_platform_populate(dev->of_node, NULL, NULL, dev); } static int zynqmp_firmware_remove(struct platform_device *pdev) { + mfd_remove_devices(&pdev->dev); zynqmp_pm_api_debugfs_exit(); return 0; 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"); From 72d1cd033154f50e77cd4feb4e16c227b598632e Mon Sep 17 00:00:00 2001 From: Jordan Crouse Date: Tue, 11 Dec 2018 13:07:45 -0700 Subject: [PATCH 097/113] 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 +++++++++++++++++++++++------- include/linux/soc/qcom/llcc-qcom.h | 6 +++ 3 files changed, 66 insertions(+), 17 deletions(-) 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); diff --git a/include/linux/soc/qcom/llcc-qcom.h b/include/linux/soc/qcom/llcc-qcom.h index 69c285b1c990..eb71a50b8afc 100644 --- a/include/linux/soc/qcom/llcc-qcom.h +++ b/include/linux/soc/qcom/llcc-qcom.h @@ -162,6 +162,12 @@ int llcc_slice_deactivate(struct llcc_slice_desc *desc); */ int qcom_llcc_probe(struct platform_device *pdev, const struct llcc_slice_config *table, u32 sz); + +/** + * qcom_llcc_remove - remove the sct table + * @pdev: Platform device pointer + */ +int qcom_llcc_remove(struct platform_device *pdev); #else static inline struct llcc_slice_desc *llcc_slice_getd(u32 uid) { From ed10a259faa1bbdf77b8c2153b274b70c90cbb35 Mon Sep 17 00:00:00 2001 From: Jordan Crouse Date: Tue, 11 Dec 2018 13:07:46 -0700 Subject: [PATCH 098/113] 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(-) 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; From 32616b2121f75f40f66eefa04cc49052200c4181 Mon Sep 17 00:00:00 2001 From: Andy Gross Date: Fri, 15 Feb 2019 16:30:34 -0600 Subject: [PATCH 099/113] 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(-) 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)) From bb8727096c86c299080c46258492098cc01741b9 Mon Sep 17 00:00:00 2001 From: Wei Yongjun Date: Mon, 18 Feb 2019 07:06:09 +0000 Subject: [PATCH 100/113] clk: tegra: dfll: Make symbol 'tegra210_cpu_cvb_tables' static Fixes the following sparse warning: drivers/clk/tegra/clk-tegra124-dfll-fcpu.c:244:18: warning: symbol 'tegra210_cpu_cvb_tables' was not declared. Should it be static? Fixes: 2b2dbc2f94e5 ("clk: tegra: dfll: add CVB tables for Tegra210") Signed-off-by: Wei Yongjun Acked-by: Thierry Reding Signed-off-by: Arnd Bergmann --- drivers/clk/tegra/clk-tegra124-dfll-fcpu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/clk/tegra/clk-tegra124-dfll-fcpu.c b/drivers/clk/tegra/clk-tegra124-dfll-fcpu.c index 4b6dd2b3767f..e8ec42bf8638 100644 --- a/drivers/clk/tegra/clk-tegra124-dfll-fcpu.c +++ b/drivers/clk/tegra/clk-tegra124-dfll-fcpu.c @@ -241,7 +241,7 @@ static const unsigned long tegra210_cpu_max_freq_table[] = { { 0UL, { 0, 0, 0 } }, \ } -struct cvb_table tegra210_cpu_cvb_tables[] = { +static struct cvb_table tegra210_cpu_cvb_tables[] = { { .speedo_id = 10, .process_id = 0, From bd3bd3b36df725645036748e58a8c35c8d2cbf91 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Tue, 19 Feb 2019 14:05:17 +0000 Subject: [PATCH 101/113] 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(+) 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; } From 50ceca6894adb834126ae644920902473bdc2d33 Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Tue, 19 Feb 2019 02:15:20 -0700 Subject: [PATCH 102/113] tee: optee: Initialize some structs using memset instead of braces Clang warns: drivers/tee/optee/device.c:39:31: warning: suggest braces around initialization of subobject [-Wmissing-braces] struct tee_param param[4] = {0}; ^ {} drivers/tee/optee/device.c:92:48: warning: suggest braces around initialization of subobject [-Wmissing-braces] struct tee_ioctl_open_session_arg sess_arg = {0}; ^ {} 2 warnings generated. One way to fix these warnings is to add additional braces like Clang suggests; however, there has been a bit of push back from some maintainers, who just prefer memset as it is unambiguous, doesn't depend on a particular compiler version, and properly initializes all subobjects [1][2]. Do that here so there are no more warnings. [1]: https://lore.kernel.org/lkml/022e41c0-8465-dc7a-a45c-64187ecd9684@amd.com/ [2]: https://lore.kernel.org/lkml/20181128.215241.702406654469517539.davem@davemloft.net/ Fixes: c3fa24af9244 ("tee: optee: add TEE bus device enumeration support") Link: https://github.com/ClangBuiltLinux/linux/issues/370 Signed-off-by: Nathan Chancellor Reviewed-by: Sumit Garg Signed-off-by: Arnd Bergmann --- drivers/tee/optee/device.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/tee/optee/device.c b/drivers/tee/optee/device.c index 5e4938bbef2b..167839b371f2 100644 --- a/drivers/tee/optee/device.c +++ b/drivers/tee/optee/device.c @@ -35,8 +35,11 @@ static int get_devices(struct tee_context *ctx, u32 session, struct tee_shm *device_shm, u32 *shm_size) { u32 ret = 0; - struct tee_ioctl_invoke_arg inv_arg = {0}; - struct tee_param param[4] = {0}; + struct tee_ioctl_invoke_arg inv_arg; + struct tee_param param[4]; + + memset(&inv_arg, 0, sizeof(inv_arg)); + memset(¶m, 0, sizeof(param)); /* Invoke PTA_CMD_GET_DEVICES function */ inv_arg.func = PTA_CMD_GET_DEVICES; @@ -89,13 +92,15 @@ int optee_enumerate_devices(void) const uuid_t pta_uuid = UUID_INIT(0x7011a688, 0xddde, 0x4053, 0xa5, 0xa9, 0x7b, 0x3c, 0x4d, 0xdf, 0x13, 0xb8); - struct tee_ioctl_open_session_arg sess_arg = {0}; + struct tee_ioctl_open_session_arg sess_arg; struct tee_shm *device_shm = NULL; const uuid_t *device_uuid = NULL; struct tee_context *ctx = NULL; u32 shm_size = 0, idx, num_devices = 0; int rc; + memset(&sess_arg, 0, sizeof(sess_arg)); + /* Open context with OP-TEE driver */ ctx = tee_client_open_context(NULL, optee_ctx_match, NULL, NULL); if (IS_ERR(ctx)) From 56410c0cb43e26a94af094aceab45a5ea38a1a38 Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Mon, 18 Feb 2019 20:22:40 -0700 Subject: [PATCH 103/113] hwrng: optee: Initialize some structs using memset instead of braces Clang warns: drivers/char/hw_random/optee-rng.c:80:31: warning: suggest braces around initialization of subobject [-Wmissing-braces] struct tee_param param[4] = {0}; ^ {} drivers/char/hw_random/optee-rng.c:177:31: warning: suggest braces around initialization of subobject [-Wmissing-braces] struct tee_param param[4] = {0}; ^ {} drivers/char/hw_random/optee-rng.c:212:48: warning: suggest braces around initialization of subobject [-Wmissing-braces] struct tee_ioctl_open_session_arg sess_arg = {0}; ^ {} 3 warnings generated. One way to fix these warnings is to add additional braces like Clang suggests; however, there has been a bit of push back from some maintainers, who just prefer memset as it is unambiguous, doesn't depend on a particular compiler version, and properly initializes all subobjects [1][2]. Do that here so there are no more warnings. [1]: https://lore.kernel.org/lkml/022e41c0-8465-dc7a-a45c-64187ecd9684@amd.com/ [2]: https://lore.kernel.org/lkml/20181128.215241.702406654469517539.davem@davemloft.net/ Fixes: 5fe8b1cc6a03 ("hwrng: add OP-TEE based rng driver") Link: https://github.com/ClangBuiltLinux/linux/issues/369 Signed-off-by: Nathan Chancellor Reviewed-by: Sumit Garg Signed-off-by: Arnd Bergmann --- drivers/char/hw_random/optee-rng.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/drivers/char/hw_random/optee-rng.c b/drivers/char/hw_random/optee-rng.c index 2b9fc8ac5500..46f4bcd28c41 100644 --- a/drivers/char/hw_random/optee-rng.c +++ b/drivers/char/hw_random/optee-rng.c @@ -76,8 +76,11 @@ static size_t get_optee_rng_data(struct optee_rng_private *pvt_data, u32 ret = 0; u8 *rng_data = NULL; size_t rng_size = 0; - struct tee_ioctl_invoke_arg inv_arg = {0}; - struct tee_param param[4] = {0}; + struct tee_ioctl_invoke_arg inv_arg; + struct tee_param param[4]; + + memset(&inv_arg, 0, sizeof(inv_arg)); + memset(¶m, 0, sizeof(param)); /* Invoke TA_CMD_GET_ENTROPY function of Trusted App */ inv_arg.func = TA_CMD_GET_ENTROPY; @@ -173,8 +176,11 @@ static struct optee_rng_private pvt_data = { static int get_optee_rng_info(struct device *dev) { u32 ret = 0; - struct tee_ioctl_invoke_arg inv_arg = {0}; - struct tee_param param[4] = {0}; + struct tee_ioctl_invoke_arg inv_arg; + struct tee_param param[4]; + + memset(&inv_arg, 0, sizeof(inv_arg)); + memset(¶m, 0, sizeof(param)); /* Invoke TA_CMD_GET_RNG_INFO function of Trusted App */ inv_arg.func = TA_CMD_GET_RNG_INFO; @@ -209,7 +215,9 @@ static int optee_rng_probe(struct device *dev) { struct tee_client_device *rng_device = to_tee_client_device(dev); int ret = 0, err = -ENODEV; - struct tee_ioctl_open_session_arg sess_arg = {0}; + struct tee_ioctl_open_session_arg sess_arg; + + memset(&sess_arg, 0, sizeof(sess_arg)); /* Open context with TEE driver */ pvt_data.ctx = tee_client_open_context(NULL, optee_ctx_match, NULL, From bb342f016862a0d5b013c2a9dce0f91412b0be77 Mon Sep 17 00:00:00 2001 From: Sumit Garg Date: Wed, 20 Feb 2019 11:32:27 +0530 Subject: [PATCH 104/113] tee: fix possible error pointer ctx dereferencing Add check for valid ctx pointer and then only dereference ctx to configure supp_nowait flag. Fixes: 42bf4152d8a7 ("tee: add supp_nowait flag in tee_context struct") Reported-by: Dan Carpenter Signed-off-by: Sumit Garg Signed-off-by: Arnd Bergmann --- drivers/tee/tee_core.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/tee/tee_core.c b/drivers/tee/tee_core.c index 25f3b9cc8908..06fbfc044ea3 100644 --- a/drivers/tee/tee_core.c +++ b/drivers/tee/tee_core.c @@ -993,7 +993,9 @@ tee_client_open_context(struct tee_context *start, * tee_client_open_session() if any in kernel client requires * different behaviour. */ - ctx->supp_nowait = true; + if (!IS_ERR(ctx)) + ctx->supp_nowait = true; + return ctx; } EXPORT_SYMBOL_GPL(tee_client_open_context); From 8deed847445a8fb619689988a24c0f8df1369a58 Mon Sep 17 00:00:00 2001 From: YueHaibing Date: Tue, 19 Feb 2019 14:33:02 +0800 Subject: [PATCH 105/113] hwrng: Fix unsigned comparison with less than zero The return from the call to tee_client_invoke_func can be a negative error code however this is being assigned to an unsigned variable 'ret' hence the check is always false. Fix this by making 'ret' an int. Detected by Coccinelle ("Unsigned expression compared with zero: ret < 0") Fixes: 5fe8b1cc6a03 ("hwrng: add OP-TEE based rng driver") Signed-off-by: YueHaibing Reviewed-by: Sumit Garg Signed-off-by: Arnd Bergmann --- drivers/char/hw_random/optee-rng.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/char/hw_random/optee-rng.c b/drivers/char/hw_random/optee-rng.c index 46f4bcd28c41..d3581ce85cb3 100644 --- a/drivers/char/hw_random/optee-rng.c +++ b/drivers/char/hw_random/optee-rng.c @@ -73,7 +73,7 @@ struct optee_rng_private { static size_t get_optee_rng_data(struct optee_rng_private *pvt_data, void *buf, size_t req_size) { - u32 ret = 0; + int ret = 0; u8 *rng_data = NULL; size_t rng_size = 0; struct tee_ioctl_invoke_arg inv_arg; @@ -175,7 +175,7 @@ static struct optee_rng_private pvt_data = { static int get_optee_rng_info(struct device *dev) { - u32 ret = 0; + int ret = 0; struct tee_ioctl_invoke_arg inv_arg; struct tee_param param[4]; From 62ade1bed27c26c2ea9280174ae6d6c7a9e825b2 Mon Sep 17 00:00:00 2001 From: YueHaibing Date: Tue, 19 Feb 2019 15:04:28 +0800 Subject: [PATCH 106/113] tee: optee: Fix unsigned comparison with less than zero The return from the call to tee_client_invoke_func can be a negative error code however this is being assigned to an unsigned variable 'ret' hence the check is always false. Fix this by making 'ret' an int. Detected by Coccinelle ("Unsigned expression compared with zero: ret < 0") Fixes: c3fa24af9244 ("tee: optee: add TEE bus device enumeration support") Signed-off-by: YueHaibing Reviewed-by: Sumit Garg Signed-off-by: Arnd Bergmann --- drivers/tee/optee/device.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/tee/optee/device.c b/drivers/tee/optee/device.c index 167839b371f2..e3a148521ec1 100644 --- a/drivers/tee/optee/device.c +++ b/drivers/tee/optee/device.c @@ -34,7 +34,7 @@ static int optee_ctx_match(struct tee_ioctl_version_data *ver, const void *data) static int get_devices(struct tee_context *ctx, u32 session, struct tee_shm *device_shm, u32 *shm_size) { - u32 ret = 0; + int ret = 0; struct tee_ioctl_invoke_arg inv_arg; struct tee_param param[4]; From 4f640201a8aebcee053712828f5457aee9909cc8 Mon Sep 17 00:00:00 2001 From: Wei Yongjun Date: Wed, 20 Feb 2019 09:34:58 +0000 Subject: [PATCH 107/113] hwrng: make symbol 'optee_rng_id_table' static Fixes the following sparse warning: drivers/char/hw_random/optee-rng.c:265:35: warning: symbol 'optee_rng_id_table' was not declared. Should it be static? Fixes: 5fe8b1cc6a03 ("hwrng: add OP-TEE based rng driver") Signed-off-by: Wei Yongjun Reviewed-by: Sumit Garg Signed-off-by: Arnd Bergmann --- drivers/char/hw_random/optee-rng.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/char/hw_random/optee-rng.c b/drivers/char/hw_random/optee-rng.c index d3581ce85cb3..ddfbabaa5f8f 100644 --- a/drivers/char/hw_random/optee-rng.c +++ b/drivers/char/hw_random/optee-rng.c @@ -270,7 +270,7 @@ static int optee_rng_remove(struct device *dev) return 0; } -const struct tee_client_device_id optee_rng_id_table[] = { +static const struct tee_client_device_id optee_rng_id_table[] = { {UUID_INIT(0xab7a617c, 0xb8e7, 0x4d8f, 0x83, 0x01, 0xd0, 0x9b, 0x61, 0x03, 0x6b, 0x64)}, {} From ae23a0fe58887a1c0518062b49bf8ac30209c26c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Horia=20Geant=C4=83?= Date: Thu, 21 Feb 2019 12:37:31 +0200 Subject: [PATCH 108/113] 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 +-- include/linux/fsl/guts.h | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) 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) { diff --git a/include/linux/fsl/guts.h b/include/linux/fsl/guts.h index 941b11811f85..1fc0edd71c52 100644 --- a/include/linux/fsl/guts.h +++ b/include/linux/fsl/guts.h @@ -135,8 +135,6 @@ struct ccsr_guts { u32 srds2cr1; /* 0x.0f44 - SerDes2 Control Register 0 */ } __attribute__ ((packed)); -u32 fsl_guts_get_svr(void); - /* Alternate function signal multiplex control */ #define MPC85xx_PMUXCR_QE(x) (0x8000 >> (x)) From 390bf02d275cee595d551fc041bb62d71728cbfa Mon Sep 17 00:00:00 2001 From: Ioana Ciornei Date: Sat, 23 Feb 2019 08:48:48 +0000 Subject: [PATCH 109/113] 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(-) 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 */ From 51da14e96e9b5187bd42148e37628e59486e267f Mon Sep 17 00:00:00 2001 From: Ioana Ciornei Date: Sat, 23 Feb 2019 08:48:52 +0000 Subject: [PATCH 110/113] 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(+) 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, From f8b995853444aba9c16c1ccdccdd397527fde96d Mon Sep 17 00:00:00 2001 From: Ioana Ciornei Date: Sat, 23 Feb 2019 08:48:55 +0000 Subject: [PATCH 111/113] dpaa2-eth: configure the cache stashing amount on a queue Configure the amount of 64 bytes of frame, annotation and context data that will be cache stashed for a specific frame queue. Since the frame context is not used, configure that only 64 bytes of frame data and 64 bytes of annotation will be stashed. Signed-off-by: Ioana Ciornei Acked-by: David S. Miller Signed-off-by: Li Yang --- drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.c index c500ea77aaa0..3c03fca83b70 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.c +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.c @@ -2303,9 +2303,14 @@ static int setup_rx_flow(struct dpaa2_eth_priv *priv, queue.destination.type = DPNI_DEST_DPCON; queue.destination.priority = 1; queue.user_context = (u64)(uintptr_t)fq; + queue.flc.stash_control = 1; + queue.flc.value &= 0xFFFFFFFFFFFFFFC0; + /* 01 01 00 - data, annotation, flow context */ + queue.flc.value |= 0x14; err = dpni_set_queue(priv->mc_io, 0, priv->mc_token, DPNI_QUEUE_RX, 0, fq->flowid, - DPNI_QUEUE_OPT_USER_CTX | DPNI_QUEUE_OPT_DEST, + DPNI_QUEUE_OPT_USER_CTX | DPNI_QUEUE_OPT_DEST | + DPNI_QUEUE_OPT_FLC, &queue); if (err) { dev_err(dev, "dpni_set_queue(RX) failed\n"); From 4f062dc1b759299851939524ff755b20542d8fc1 Mon Sep 17 00:00:00 2001 From: Igor Opaniuk Date: Thu, 24 Jan 2019 19:32:31 +0200 Subject: [PATCH 112/113] tee: add cancellation support to client interface Add support of cancellation request to the TEE kernel internal client interface. Can be used by software TPM drivers, that leverage TEE under the hood (for instance TPM2.0 mobile profile), for requesting cancellation of time-consuming operations (RSA key-pair generation etc.). Signed-off-by: Igor Opaniuk Signed-off-by: Jens Wiklander --- drivers/tee/tee_core.c | 9 +++++++++ include/linux/tee_drv.h | 12 ++++++++++++ 2 files changed, 21 insertions(+) diff --git a/drivers/tee/tee_core.c b/drivers/tee/tee_core.c index 25f3b9cc8908..ecffdd8a29b7 100644 --- a/drivers/tee/tee_core.c +++ b/drivers/tee/tee_core.c @@ -1039,6 +1039,15 @@ int tee_client_invoke_func(struct tee_context *ctx, } EXPORT_SYMBOL_GPL(tee_client_invoke_func); +int tee_client_cancel_req(struct tee_context *ctx, + struct tee_ioctl_cancel_arg *arg) +{ + if (!ctx->teedev->desc->ops->cancel_req) + return -EINVAL; + return ctx->teedev->desc->ops->cancel_req(ctx, arg->cancel_id, + arg->session); +} + static int tee_client_device_match(struct device *dev, struct device_driver *drv) { diff --git a/include/linux/tee_drv.h b/include/linux/tee_drv.h index 56d7f1b4516d..4a49f80e7f71 100644 --- a/include/linux/tee_drv.h +++ b/include/linux/tee_drv.h @@ -535,6 +535,18 @@ int tee_client_invoke_func(struct tee_context *ctx, struct tee_ioctl_invoke_arg *arg, struct tee_param *param); +/** + * tee_client_cancel_req() - Request cancellation of the previous open-session + * or invoke-command operations in a Trusted Application + * @ctx: TEE Context + * @arg: Cancellation arguments, see description of + * struct tee_ioctl_cancel_arg + * + * Returns < 0 on error else 0 if the cancellation was successfully requested. + */ +int tee_client_cancel_req(struct tee_context *ctx, + struct tee_ioctl_cancel_arg *arg); + static inline bool tee_param_is_memref(struct tee_param *param) { switch (param->attr & TEE_IOCTL_PARAM_ATTR_TYPE_MASK) { From 32356d309c22a9497e612ed437d66407e15935f3 Mon Sep 17 00:00:00 2001 From: Jerome Forissier Date: Fri, 8 Feb 2019 16:42:06 +0100 Subject: [PATCH 113/113] tee: optee: update optee_msg.h and optee_smc.h to dual license The files optee_msg.h and optee_smc.h (under drivers/tee/optee) contain information originating from the OP-TEE OS project [1] [2], where the licensing terms are BSD 2-Clause. Therefore, apply a dual license to those files. Link: [1] https://github.com/OP-TEE/optee_os/blob/master/core/include/optee_msg.h Link: [2] https://github.com/OP-TEE/optee_os/blob/master/core/arch/arm/include/sm/optee_smc.h Signed-off-by: Jerome Forissier Signed-off-by: Jens Wiklander --- drivers/tee/optee/optee_msg.h | 26 ++------------------------ drivers/tee/optee/optee_smc.h | 26 ++------------------------ 2 files changed, 4 insertions(+), 48 deletions(-) diff --git a/drivers/tee/optee/optee_msg.h b/drivers/tee/optee/optee_msg.h index 30504901be80..795bc19ae17a 100644 --- a/drivers/tee/optee/optee_msg.h +++ b/drivers/tee/optee/optee_msg.h @@ -1,28 +1,6 @@ +/* SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) */ /* - * Copyright (c) 2015-2016, Linaro Limited - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. + * Copyright (c) 2015-2019, Linaro Limited */ #ifndef _OPTEE_MSG_H #define _OPTEE_MSG_H diff --git a/drivers/tee/optee/optee_smc.h b/drivers/tee/optee/optee_smc.h index bbf0cf028c16..c72122d9c997 100644 --- a/drivers/tee/optee/optee_smc.h +++ b/drivers/tee/optee/optee_smc.h @@ -1,28 +1,6 @@ +/* SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) */ /* - * Copyright (c) 2015-2016, Linaro Limited - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. + * Copyright (c) 2015-2019, Linaro Limited */ #ifndef OPTEE_SMC_H #define OPTEE_SMC_H