diff options
author | Thierry Reding <treding@nvidia.com> | 2014-12-19 12:17:32 +0100 |
---|---|---|
committer | Thierry Reding <treding@nvidia.com> | 2014-12-19 12:17:32 +0100 |
commit | ea133975e62d597bb53e14095eec59725e3d1345 (patch) | |
tree | 61f00891390c3be2b457342e3a291d97b72f8107 | |
parent | fa3dca4aefcfec97be61688457d3439ab7a7dd16 (diff) | |
parent | 268d23c16a4565ba183421e163cc3e98658280eb (diff) |
Merge branch 'staging/powergate' into staging/master
-rw-r--r-- | arch/arm/boot/dts/tegra124.dtsi | 11 | ||||
-rw-r--r-- | arch/arm/mach-tegra/Kconfig | 1 | ||||
-rw-r--r-- | drivers/soc/tegra/pmc.c | 234 | ||||
-rw-r--r-- | include/dt-bindings/power/tegra-powergate.h | 30 |
4 files changed, 275 insertions, 1 deletions
diff --git a/arch/arm/boot/dts/tegra124.dtsi b/arch/arm/boot/dts/tegra124.dtsi index 3e2195163d9..0aa83575a0d 100644 --- a/arch/arm/boot/dts/tegra124.dtsi +++ b/arch/arm/boot/dts/tegra124.dtsi @@ -4,6 +4,7 @@ #include <dt-bindings/pinctrl/pinctrl-tegra.h> #include <dt-bindings/pinctrl/pinctrl-tegra-xusb.h> #include <dt-bindings/interrupt-controller/arm-gic.h> +#include <dt-bindings/power/tegra-powergate.h> #include <dt-bindings/thermal/tegra124-soctherm.h> #include "skeleton.dtsi" @@ -48,6 +49,7 @@ <&tegra_car 72>, <&tegra_car 74>; reset-names = "pex", "afi", "pcie_x"; + power-domains = <&pmc TEGRA_POWERGATE_PCIE>; status = "disabled"; phys = <&padctl TEGRA_XUSB_PADCTL_PCIE>; @@ -98,6 +100,7 @@ compatible = "nvidia,tegra124-dc"; reg = <0x0 0x54200000 0x0 0x00040000>; interrupts = <GIC_SPI 73 IRQ_TYPE_LEVEL_HIGH>; + power-domains = <&pmc TEGRA_POWERGATE_DIS>; clocks = <&tegra_car TEGRA124_CLK_DISP1>, <&tegra_car TEGRA124_CLK_PLL_P>; clock-names = "dc", "parent"; @@ -113,6 +116,7 @@ compatible = "nvidia,tegra124-dc"; reg = <0x0 0x54240000 0x0 0x00040000>; interrupts = <GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>; + power-domains = <&pmc TEGRA_POWERGATE_DISB>; clocks = <&tegra_car TEGRA124_CLK_DISP2>, <&tegra_car TEGRA124_CLK_PLL_P>; clock-names = "dc", "parent"; @@ -147,6 +151,7 @@ clock-names = "sor", "parent", "dp", "safe"; resets = <&tegra_car 182>; reset-names = "sor"; + power-domains = <&pmc TEGRA_POWERGATE_SOR>; status = "disabled"; }; @@ -542,11 +547,13 @@ clocks = <&tegra_car TEGRA124_CLK_RTC>; }; - pmc@0,7000e400 { + pmc: pmc@0,7000e400 { compatible = "nvidia,tegra124-pmc"; reg = <0x0 0x7000e400 0x0 0x400>; clocks = <&tegra_car TEGRA124_CLK_PCLK>, <&clk32k_in>; clock-names = "pclk", "clk32k_in"; + + #power-domain-cells = <1>; }; fuse@0,7000f800 { @@ -588,6 +595,8 @@ <&tegra_car 129>; reset-names = "sata", "sata-oob", "sata-cold"; + power-domains = <&pmc TEGRA_POWERGATE_SATA>; + phys = <&padctl TEGRA_XUSB_PADCTL_SATA>; phy-names = "sata-phy"; diff --git a/arch/arm/mach-tegra/Kconfig b/arch/arm/mach-tegra/Kconfig index 9a515824598..f87684e600c 100644 --- a/arch/arm/mach-tegra/Kconfig +++ b/arch/arm/mach-tegra/Kconfig @@ -9,6 +9,7 @@ menuconfig ARCH_TEGRA select HAVE_ARM_SCU if SMP select HAVE_ARM_TWD if SMP select PINCTRL + select PM_GENERIC_DOMAINS if PM select ARCH_HAS_RESET_CONTROLLER select RESET_CONTROLLER select SOC_BUS diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c index a2c0ceb95f8..d36f8dc26d4 100644 --- a/drivers/soc/tegra/pmc.c +++ b/drivers/soc/tegra/pmc.c @@ -17,6 +17,8 @@ * */ +#define DEBUG + #include <linux/kernel.h> #include <linux/clk.h> #include <linux/clk/tegra.h> @@ -29,8 +31,10 @@ #include <linux/of.h> #include <linux/of_address.h> #include <linux/platform_device.h> +#include <linux/pm_domain.h> #include <linux/reboot.h> #include <linux/reset.h> +#include <linux/sched.h> #include <linux/seq_file.h> #include <linux/spinlock.h> @@ -83,6 +87,18 @@ #define GPU_RG_CNTRL 0x2d4 +struct tegra_powergate { + struct generic_pm_domain base; + struct tegra_pmc *pmc; + unsigned int id; +}; + +static inline struct tegra_powergate * +to_powergate(struct generic_pm_domain *domain) +{ + return container_of(domain, struct tegra_powergate, base); +} + struct tegra_pmc_soc { unsigned int num_powergates; const char *const *powergates; @@ -92,8 +108,10 @@ struct tegra_pmc_soc { /** * struct tegra_pmc - NVIDIA Tegra PMC + * @dev: pointer to parent device * @base: pointer to I/O remapped register region * @clk: pointer to pclk clock + * @soc: SoC-specific data * @rate: currently configured rate of pclk * @suspend_mode: lowest suspend mode available * @cpu_good_time: CPU power good time (in microseconds) @@ -107,9 +125,12 @@ struct tegra_pmc_soc { * @cpu_pwr_good_en: CPU power good signal is enabled * @lp0_vec_phys: physical base address of the LP0 warm boot code * @lp0_vec_size: size of the LP0 warm boot code + * @powergates: list of power gates * @powergates_lock: mutex for power gate register access + * @nb: bus notifier for generic power domains */ struct tegra_pmc { + struct device *dev; void __iomem *base; struct clk *clk; @@ -130,7 +151,9 @@ struct tegra_pmc { u32 lp0_vec_phys; u32 lp0_vec_size; + struct tegra_powergate *powergates; struct mutex powergates_lock; + struct notifier_block nb; }; static struct tegra_pmc *pmc = &(struct tegra_pmc) { @@ -353,6 +376,8 @@ int tegra_pmc_cpu_remove_clamping(int cpuid) if (id < 0) return id; + usleep_range(10, 20); + return tegra_powergate_remove_clamping(id); } #endif /* CONFIG_SMP */ @@ -387,6 +412,130 @@ void tegra_pmc_restart(enum reboot_mode mode, const char *cmd) tegra_pmc_writel(value, 0); } +static bool tegra_pmc_powergate_is_powered(struct tegra_powergate *powergate) +{ + u32 status = tegra_pmc_readl(PWRGATE_STATUS); + + return (status & BIT(powergate->id)) != 0; +} + +static int tegra_pmc_powergate_set(struct tegra_powergate *powergate, + bool new_state) +{ + u32 status, mask = new_state ? BIT(powergate->id) : 0; + bool state = false; + + mutex_lock(&pmc->powergates_lock); + + /* check the current state of the partition */ + status = tegra_pmc_readl(PWRGATE_STATUS); + if (status & BIT(powergate->id)) + state = true; + + /* nothing to do */ + if (new_state == state) { + mutex_unlock(&pmc->powergates_lock); + return 0; + } + + /* toggle partition state and wait for state change to finish */ + tegra_pmc_writel(PWRGATE_TOGGLE_START | powergate->id, PWRGATE_TOGGLE); + + while (1) { + status = tegra_pmc_readl(PWRGATE_STATUS); + if ((status & BIT(powergate->id)) == mask) + break; + + usleep_range(10, 20); + } + + mutex_unlock(&pmc->powergates_lock); + + return 0; +} + +static int +tegra_pmc_powergate_remove_clamping(struct tegra_powergate *powergate) +{ + u32 mask; + + /* + * The Tegra124 GPU has a separate register (with different semantics) + * to remove clamps. + */ + if (tegra_get_chip_id() == TEGRA124) { + if (powergate->id == TEGRA_POWERGATE_3D) { + tegra_pmc_writel(0, GPU_RG_CNTRL); + return 0; + } + } + + /* + * Tegra 2 has a bug where PCIE and VDE clamping masks are + * swapped relatively to the partition ids + */ + if (powergate->id == TEGRA_POWERGATE_VDEC) + mask = (1 << TEGRA_POWERGATE_PCIE); + else if (powergate->id == TEGRA_POWERGATE_PCIE) + mask = (1 << TEGRA_POWERGATE_VDEC); + else + mask = (1 << powergate->id); + + tegra_pmc_writel(mask, REMOVE_CLAMPING); + + return 0; +} + +static int tegra_pmc_powergate_power_on(struct generic_pm_domain *domain) +{ + struct tegra_powergate *powergate = to_powergate(domain); + struct tegra_pmc *pmc = powergate->pmc; + int err = 0; + + dev_dbg(pmc->dev, "> %s(domain=%p)\n", __func__, domain); + dev_dbg(pmc->dev, " name: %s\n", domain->name); + + err = tegra_pmc_powergate_set(powergate, true); + if (err < 0) + goto out; + + err = tegra_pmc_powergate_remove_clamping(powergate); + +out: + dev_dbg(pmc->dev, "< %s() = %d\n", __func__, err); + return err; +} + +static int tegra_pmc_powergate_power_off(struct generic_pm_domain *domain) +{ + struct tegra_powergate *powergate = to_powergate(domain); + struct tegra_pmc *pmc = powergate->pmc; + int err = 0; + + dev_dbg(pmc->dev, "> %s(domain=%p)\n", __func__, domain); + dev_dbg(pmc->dev, " name: %s\n", domain->name); + + /* never turn off this partition */ + switch (powergate->id) { + case TEGRA_POWERGATE_CPU: + case TEGRA_POWERGATE_CPU1: + case TEGRA_POWERGATE_CPU2: + case TEGRA_POWERGATE_CPU3: + case TEGRA_POWERGATE_CPU0: + case TEGRA_POWERGATE_C0NC: + case TEGRA_POWERGATE_IRAM: + dev_dbg(pmc->dev, "not disabling always-on partition %s\n", + domain->name); + goto out; + } + + err = tegra_pmc_powergate_set(powergate, false); + +out: + dev_dbg(pmc->dev, "< %s() = %d\n", __func__, err); + return err; +} + static int powergate_show(struct seq_file *s, void *data) { unsigned int i; @@ -429,6 +578,81 @@ static int tegra_powergate_debugfs_init(void) return 0; } +static struct generic_pm_domain * +tegra_powergate_of_xlate(struct of_phandle_args *args, void *data) +{ + struct tegra_pmc *pmc = data; + unsigned int i; + + dev_dbg(pmc->dev, "> %s(args=%p, data=%p)\n", __func__, args, data); + + for (i = 0; i < pmc->soc->num_powergates; i++) { + struct tegra_powergate *powergate = &pmc->powergates[i]; + + if (!powergate->base.name) + continue; + + if (powergate->id == args->args[0]) { + dev_dbg(pmc->dev, "< %s() = %p\n", __func__, powergate); + return &powergate->base; + } + } + + dev_dbg(pmc->dev, "< %s() = -ENOENT\n", __func__); + return ERR_PTR(-ENOENT); +} + +static int tegra_powergate_init(struct tegra_pmc *pmc) +{ + struct device_node *np = pmc->dev->of_node; + struct tegra_powergate *powergate; + unsigned int i; + int err = 0; + + dev_dbg(pmc->dev, "> %s(pmc=%p)\n", __func__, pmc); + + pmc->powergates = devm_kcalloc(pmc->dev, pmc->soc->num_powergates, + sizeof(*powergate), GFP_KERNEL); + if (!pmc->powergates) { + err = -ENOMEM; + goto out; + } + + for (i = 0; i < pmc->soc->num_powergates; i++) { + bool off; + + /* the powergate table may have gaps */ + if (!pmc->soc->powergates[i]) + continue; + + powergate = &pmc->powergates[i]; + + powergate->base.name = kstrdup(pmc->soc->powergates[i], + GFP_KERNEL); + powergate->base.power_off = tegra_pmc_powergate_power_off; + powergate->base.power_on = tegra_pmc_powergate_power_on; + + powergate->pmc = pmc; + powergate->id = i; + + off = !tegra_pmc_powergate_is_powered(powergate); + pm_genpd_init(&powergate->base, NULL, off); + } + + err = __of_genpd_add_provider(np, tegra_powergate_of_xlate, pmc); + if (err < 0) + return err; + +#if 0 + pmc->nb.notifier_call = tegra_powergate_notifier_call; + bus_register_notifier(&platform_bus_type, &pmc->nb); +#endif + +out: + dev_dbg(pmc->dev, "< %s() = %d\n", __func__, err); + return err; +} + static int tegra_io_rail_prepare(int id, unsigned long *request, unsigned long *status, unsigned int *bit) { @@ -709,6 +933,8 @@ static int tegra_pmc_probe(struct platform_device *pdev) struct resource *res; int err; + dev_dbg(&pdev->dev, "> %s(pdev=%p)\n", __func__, pdev); + err = tegra_pmc_parse_dt(pmc, pdev->dev.of_node); if (err < 0) return err; @@ -728,14 +954,22 @@ static int tegra_pmc_probe(struct platform_device *pdev) return err; } + pmc->dev = &pdev->dev; tegra_pmc_init(pmc); + if (0 && IS_ENABLED(CONFIG_PM_GENERIC_DOMAINS)) { + err = tegra_powergate_init(pmc); + if (err < 0) + return err; + } + if (IS_ENABLED(CONFIG_DEBUG_FS)) { err = tegra_powergate_debugfs_init(); if (err < 0) return err; } + dev_dbg(&pdev->dev, "< %s()\n", __func__); return 0; } diff --git a/include/dt-bindings/power/tegra-powergate.h b/include/dt-bindings/power/tegra-powergate.h new file mode 100644 index 00000000000..b8265167c20 --- /dev/null +++ b/include/dt-bindings/power/tegra-powergate.h @@ -0,0 +1,30 @@ +#ifndef _DT_BINDINGS_POWER_TEGRA_POWERGATE_H +#define _DT_BINDINGS_POWER_TEGRA_POWERGATE_H + +#define TEGRA_POWERGATE_CPU 0 +#define TEGRA_POWERGATE_3D 1 +#define TEGRA_POWERGATE_VENC 2 +#define TEGRA_POWERGATE_PCIE 3 +#define TEGRA_POWERGATE_VDEC 4 +#define TEGRA_POWERGATE_L2 5 +#define TEGRA_POWERGATE_MPE 6 +#define TEGRA_POWERGATE_HEG 7 +#define TEGRA_POWERGATE_SATA 8 +#define TEGRA_POWERGATE_CPU1 9 +#define TEGRA_POWERGATE_CPU2 10 +#define TEGRA_POWERGATE_CPU3 11 +#define TEGRA_POWERGATE_CELP 12 +#define TEGRA_POWERGATE_3D1 13 +#define TEGRA_POWERGATE_CPU0 14 +#define TEGRA_POWERGATE_C0NC 15 +#define TEGRA_POWERGATE_C1NC 16 +#define TEGRA_POWERGATE_SOR 17 +#define TEGRA_POWERGATE_DIS 18 +#define TEGRA_POWERGATE_DISB 19 +#define TEGRA_POWERGATE_XUSBA 20 +#define TEGRA_POWERGATE_XUSBB 21 +#define TEGRA_POWERGATE_XUSBC 22 +#define TEGRA_POWERGATE_VIC 23 +#define TEGRA_POWERGATE_IRAM 24 + +#endif |