diff options
| -rw-r--r-- | Documentation/devicetree/bindings/arm/exynos/power_domain.txt | 20 | ||||
| -rw-r--r-- | arch/arm/mach-exynos/pm_domains.c | 61 |
2 files changed, 80 insertions, 1 deletions
diff --git a/Documentation/devicetree/bindings/arm/exynos/power_domain.txt b/Documentation/devicetree/bindings/arm/exynos/power_domain.txt index 5216b419016a..8b4f7b7fe88b 100644 --- a/Documentation/devicetree/bindings/arm/exynos/power_domain.txt +++ b/Documentation/devicetree/bindings/arm/exynos/power_domain.txt | |||
| @@ -9,6 +9,18 @@ Required Properties: | |||
| 9 | - reg: physical base address of the controller and length of memory mapped | 9 | - reg: physical base address of the controller and length of memory mapped |
| 10 | region. | 10 | region. |
| 11 | 11 | ||
| 12 | Optional Properties: | ||
| 13 | - clocks: List of clock handles. The parent clocks of the input clocks to the | ||
| 14 | devices in this power domain are set to oscclk before power gating | ||
| 15 | and restored back after powering on a domain. This is required for | ||
| 16 | all domains which are powered on and off and not required for unused | ||
| 17 | domains. | ||
| 18 | - clock-names: The following clocks can be specified: | ||
| 19 | - oscclk: Oscillator clock. | ||
| 20 | - pclkN, clkN: Pairs of parent of input clock and input clock to the | ||
| 21 | devices in this power domain. Maximum of 4 pairs (N = 0 to 3) | ||
| 22 | are supported currently. | ||
| 23 | |||
| 12 | Node of a device using power domains must have a samsung,power-domain property | 24 | Node of a device using power domains must have a samsung,power-domain property |
| 13 | defined with a phandle to respective power domain. | 25 | defined with a phandle to respective power domain. |
| 14 | 26 | ||
| @@ -19,6 +31,14 @@ Example: | |||
| 19 | reg = <0x10023C00 0x10>; | 31 | reg = <0x10023C00 0x10>; |
| 20 | }; | 32 | }; |
| 21 | 33 | ||
| 34 | mfc_pd: power-domain@10044060 { | ||
| 35 | compatible = "samsung,exynos4210-pd"; | ||
| 36 | reg = <0x10044060 0x20>; | ||
| 37 | clocks = <&clock CLK_FIN_PLL>, <&clock CLK_MOUT_SW_ACLK333>, | ||
| 38 | <&clock CLK_MOUT_USER_ACLK333>; | ||
| 39 | clock-names = "oscclk", "pclk0", "clk0"; | ||
| 40 | }; | ||
| 41 | |||
| 22 | Example of the node using power domain: | 42 | Example of the node using power domain: |
| 23 | 43 | ||
| 24 | node { | 44 | node { |
diff --git a/arch/arm/mach-exynos/pm_domains.c b/arch/arm/mach-exynos/pm_domains.c index fe6570ebbdde..797cb134bfff 100644 --- a/arch/arm/mach-exynos/pm_domains.c +++ b/arch/arm/mach-exynos/pm_domains.c | |||
| @@ -17,6 +17,7 @@ | |||
| 17 | #include <linux/err.h> | 17 | #include <linux/err.h> |
| 18 | #include <linux/slab.h> | 18 | #include <linux/slab.h> |
| 19 | #include <linux/pm_domain.h> | 19 | #include <linux/pm_domain.h> |
| 20 | #include <linux/clk.h> | ||
| 20 | #include <linux/delay.h> | 21 | #include <linux/delay.h> |
| 21 | #include <linux/of_address.h> | 22 | #include <linux/of_address.h> |
| 22 | #include <linux/of_platform.h> | 23 | #include <linux/of_platform.h> |
| @@ -24,6 +25,8 @@ | |||
| 24 | 25 | ||
| 25 | #include "regs-pmu.h" | 26 | #include "regs-pmu.h" |
| 26 | 27 | ||
| 28 | #define MAX_CLK_PER_DOMAIN 4 | ||
| 29 | |||
| 27 | /* | 30 | /* |
| 28 | * Exynos specific wrapper around the generic power domain | 31 | * Exynos specific wrapper around the generic power domain |
| 29 | */ | 32 | */ |
| @@ -32,6 +35,9 @@ struct exynos_pm_domain { | |||
| 32 | char const *name; | 35 | char const *name; |
| 33 | bool is_off; | 36 | bool is_off; |
| 34 | struct generic_pm_domain pd; | 37 | struct generic_pm_domain pd; |
| 38 | struct clk *oscclk; | ||
| 39 | struct clk *clk[MAX_CLK_PER_DOMAIN]; | ||
| 40 | struct clk *pclk[MAX_CLK_PER_DOMAIN]; | ||
| 35 | }; | 41 | }; |
| 36 | 42 | ||
| 37 | static int exynos_pd_power(struct generic_pm_domain *domain, bool power_on) | 43 | static int exynos_pd_power(struct generic_pm_domain *domain, bool power_on) |
| @@ -44,6 +50,19 @@ static int exynos_pd_power(struct generic_pm_domain *domain, bool power_on) | |||
| 44 | pd = container_of(domain, struct exynos_pm_domain, pd); | 50 | pd = container_of(domain, struct exynos_pm_domain, pd); |
| 45 | base = pd->base; | 51 | base = pd->base; |
| 46 | 52 | ||
| 53 | /* Set oscclk before powering off a domain*/ | ||
| 54 | if (!power_on) { | ||
| 55 | int i; | ||
| 56 | |||
| 57 | for (i = 0; i < MAX_CLK_PER_DOMAIN; i++) { | ||
| 58 | if (IS_ERR(pd->clk[i])) | ||
| 59 | break; | ||
| 60 | if (clk_set_parent(pd->clk[i], pd->oscclk)) | ||
| 61 | pr_err("%s: error setting oscclk as parent to clock %d\n", | ||
| 62 | pd->name, i); | ||
| 63 | } | ||
| 64 | } | ||
| 65 | |||
| 47 | pwr = power_on ? S5P_INT_LOCAL_PWR_EN : 0; | 66 | pwr = power_on ? S5P_INT_LOCAL_PWR_EN : 0; |
| 48 | __raw_writel(pwr, base); | 67 | __raw_writel(pwr, base); |
| 49 | 68 | ||
| @@ -60,6 +79,20 @@ static int exynos_pd_power(struct generic_pm_domain *domain, bool power_on) | |||
| 60 | cpu_relax(); | 79 | cpu_relax(); |
| 61 | usleep_range(80, 100); | 80 | usleep_range(80, 100); |
| 62 | } | 81 | } |
| 82 | |||
| 83 | /* Restore clocks after powering on a domain*/ | ||
| 84 | if (power_on) { | ||
| 85 | int i; | ||
| 86 | |||
| 87 | for (i = 0; i < MAX_CLK_PER_DOMAIN; i++) { | ||
| 88 | if (IS_ERR(pd->clk[i])) | ||
| 89 | break; | ||
| 90 | if (clk_set_parent(pd->clk[i], pd->pclk[i])) | ||
| 91 | pr_err("%s: error setting parent to clock%d\n", | ||
| 92 | pd->name, i); | ||
| 93 | } | ||
| 94 | } | ||
| 95 | |||
| 63 | return 0; | 96 | return 0; |
| 64 | } | 97 | } |
| 65 | 98 | ||
| @@ -152,9 +185,11 @@ static __init int exynos4_pm_init_power_domain(void) | |||
| 152 | 185 | ||
| 153 | for_each_compatible_node(np, NULL, "samsung,exynos4210-pd") { | 186 | for_each_compatible_node(np, NULL, "samsung,exynos4210-pd") { |
| 154 | struct exynos_pm_domain *pd; | 187 | struct exynos_pm_domain *pd; |
| 155 | int on; | 188 | int on, i; |
| 189 | struct device *dev; | ||
| 156 | 190 | ||
| 157 | pdev = of_find_device_by_node(np); | 191 | pdev = of_find_device_by_node(np); |
| 192 | dev = &pdev->dev; | ||
| 158 | 193 | ||
| 159 | pd = kzalloc(sizeof(*pd), GFP_KERNEL); | 194 | pd = kzalloc(sizeof(*pd), GFP_KERNEL); |
| 160 | if (!pd) { | 195 | if (!pd) { |
| @@ -170,6 +205,30 @@ static __init int exynos4_pm_init_power_domain(void) | |||
| 170 | pd->pd.power_on = exynos_pd_power_on; | 205 | pd->pd.power_on = exynos_pd_power_on; |
| 171 | pd->pd.of_node = np; | 206 | pd->pd.of_node = np; |
| 172 | 207 | ||
| 208 | pd->oscclk = clk_get(dev, "oscclk"); | ||
| 209 | if (IS_ERR(pd->oscclk)) | ||
| 210 | goto no_clk; | ||
| 211 | |||
| 212 | for (i = 0; i < MAX_CLK_PER_DOMAIN; i++) { | ||
| 213 | char clk_name[8]; | ||
| 214 | |||
| 215 | snprintf(clk_name, sizeof(clk_name), "clk%d", i); | ||
| 216 | pd->clk[i] = clk_get(dev, clk_name); | ||
| 217 | if (IS_ERR(pd->clk[i])) | ||
| 218 | break; | ||
| 219 | snprintf(clk_name, sizeof(clk_name), "pclk%d", i); | ||
| 220 | pd->pclk[i] = clk_get(dev, clk_name); | ||
| 221 | if (IS_ERR(pd->pclk[i])) { | ||
| 222 | clk_put(pd->clk[i]); | ||
| 223 | pd->clk[i] = ERR_PTR(-EINVAL); | ||
| 224 | break; | ||
| 225 | } | ||
| 226 | } | ||
| 227 | |||
| 228 | if (IS_ERR(pd->clk[0])) | ||
| 229 | clk_put(pd->oscclk); | ||
| 230 | |||
| 231 | no_clk: | ||
| 173 | platform_set_drvdata(pdev, pd); | 232 | platform_set_drvdata(pdev, pd); |
| 174 | 233 | ||
| 175 | on = __raw_readl(pd->base + 0x4) & S5P_INT_LOCAL_PWR_EN; | 234 | on = __raw_readl(pd->base + 0x4) & S5P_INT_LOCAL_PWR_EN; |
