While turning power domain to on/off, some clocks need to be enabled in the Exynos7 SOC. This patch adds the framework for enabling those clocks before on/off and restoring it back after the operation. Also these list of clocks may be different for on/off operation so not using the generic pm domain suspend/resume interface. Signed-off-by: Amit Daniel Kachhap <amit.daniel@xxxxxxxxxxx> --- .../bindings/arm/exynos/power_domain.txt | 12 ++ drivers/soc/samsung/pm_domains.c | 138 +++++++++++++++++++- 2 files changed, 146 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/arm/exynos/power_domain.txt b/Documentation/devicetree/bindings/arm/exynos/power_domain.txt index 8d913b9..c48769e 100644 --- a/Documentation/devicetree/bindings/arm/exynos/power_domain.txt +++ b/Documentation/devicetree/bindings/arm/exynos/power_domain.txt @@ -24,6 +24,14 @@ Optional Properties: - pclkN, clkN: Pairs of parent of input clock and input clock to the devices in this power domain. Maximum of 10 sets (N = 0 to 9) are supported. +- pd-on-en-clocks: List of clock handles. These clocks are required to enabled + before turning on a power domain. +- pd-on-en-clock-names: clocks can be specified as, + - clkN: N can vary between 0-30. +- pd-off-en-clocks: List of clock handles. These clocks are required to enabled + before turning off a power domain. +- pd-off-en-clock-names: clocks can be specified as, + - clkN: N can vary between 0-30. - parents: phandle of parent power domains. Node of a device using power domains must have a samsung,power-domain property @@ -43,6 +51,10 @@ Example: pd-parent-clocks = <&clock CLK_FIN_PLL>, <&clock CLK_MOUT_SW_ACLK333>, <&clock CLK_MOUT_USER_ACLK333>; pd-parent-clock-names = "tclk0", "pclk0", "clk0"; + pd-on-en-clocks = <&clock CLK_IP1>, <&clock CLK_IP2>, + pd-on-en-clock-names = "clk0", "clk1"; + pd-off-en-clocks = <&clock CLK_IP1>, <&clock CLK_IP2>, + pd-off-en-clock-names = "clk0", "clk1"; #power-domain-cells = <0>; }; diff --git a/drivers/soc/samsung/pm_domains.c b/drivers/soc/samsung/pm_domains.c index 96196f8..5407eb7 100644 --- a/drivers/soc/samsung/pm_domains.c +++ b/drivers/soc/samsung/pm_domains.c @@ -18,6 +18,7 @@ #include <linux/slab.h> #include <linux/pm_domain.h> #include <linux/clk.h> +#include <linux/clk-provider.h> #include <linux/delay.h> #include <linux/of_address.h> #include <linux/of_platform.h> @@ -29,6 +30,12 @@ static struct exynos_pmu_pd_ops *pd_ops; +struct clk_enable_list { + struct clk **clks; + unsigned int count; + unsigned int en_status; +}; + struct clk_parent_list { struct clk **clks; struct clk **parent_clks; @@ -44,6 +51,8 @@ struct exynos_pm_domain { char const *name; bool is_off; struct generic_pm_domain pd; + struct clk_enable_list *clk_pd_on; + struct clk_enable_list *clk_pd_off; struct clk_parent_list *clk_parent; }; @@ -72,6 +81,55 @@ static struct clk *exynos_pd_clk_get(struct device_node *np, return clk; } +static int pd_init_enable_clocks(struct platform_device *pdev, + struct device_node *np, bool on, struct exynos_pm_domain *pd) +{ + struct clk_enable_list *list; + char propname[32], clk_name[8]; + int count, i; + struct clk *clk = ERR_PTR(-ENOENT); + + list = devm_kzalloc(&pdev->dev, sizeof(*list), GFP_KERNEL); + if (!list) + return -ENOMEM; + + if (on) { + pd->clk_pd_on = list; + snprintf(propname, sizeof(propname), "pd-on-en-clock-names"); + } else { + pd->clk_pd_off = list; + snprintf(propname, sizeof(propname), "pd-off-en-clock-names"); + } + + count = of_property_count_strings(np, propname); + if (!count || count > MAX_CLK_PER_DOMAIN) + return -EINVAL; + + list->count = count; + list->clks = devm_kzalloc(&pdev->dev, sizeof(*list->clks) * count, + GFP_KERNEL); + if (!list->clks) + return -ENOMEM; + + if (on) + snprintf(propname, sizeof(propname), "pd-on-en"); + else + snprintf(propname, sizeof(propname), "pd-off-en"); + + for (i = 0; i < count; i++) { + snprintf(clk_name, sizeof(clk_name), "clk%d", i); + clk = exynos_pd_clk_get(np, propname, clk_name); + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "%s clock not found\n", clk_name); + return -EINVAL; + } + list->clks[i] = clk; + } + dev_info(&pdev->dev, "pd %s enable clocks initialised\n", + on ? "on" : "off"); + return 0; +} + static int pd_init_parent_clocks(struct platform_device *pdev, struct device_node *np, struct exynos_pm_domain *pd) { @@ -140,25 +198,67 @@ static int pd_init_parent_clocks(struct platform_device *pdev, return 0; } +static void exynos_pd_poweron_prepare(struct exynos_pm_domain *pd) +{ + struct clk_enable_list *en_list; + int i; + + en_list = pd->clk_pd_on; + if (!en_list) + return; + + /* Enable the necessary clocks not enabled and update the status */ + for (i = 0; i < en_list->count; i++) { + if (__clk_is_enabled(en_list->clks[i])) + continue; + clk_enable(en_list->clks[i]); + en_list->en_status |= (1 << i); + } +} + static void exynos_pd_post_poweron(struct exynos_pm_domain *pd) { struct clk_parent_list *p_list; + struct clk_enable_list *en_list; int i; p_list = pd->clk_parent; - if (!p_list) + if (p_list) { + /* Set the parents clocks correctly */ + for (i = 0; i < p_list->count; i++) + clk_set_parent(p_list->clks[i], p_list->parent_clks[i]); + } + + en_list = pd->clk_pd_on; + if (!en_list) return; - /* Set the parents clocks correctly */ - for (i = 0; i < p_list->count; i++) - clk_set_parent(p_list->clks[i], p_list->parent_clks[i]); + /* Disable the clocks enabled during power off preperation */ + for (i = 0; i < en_list->count; i++) { + if (!(en_list->en_status & (1 << i))) + continue; + clk_disable(en_list->clks[i]); + en_list->en_status &= ~(1 << i); + } } static void exynos_pd_poweroff_prepare(struct exynos_pm_domain *pd) { struct clk_parent_list *p_list; + struct clk_enable_list *en_list; int i; + en_list = pd->clk_pd_off; + if (en_list) { + /* Enable the clocks not enabled and update the status */ + for (i = 0; i < en_list->count; i++) { + if (__clk_is_enabled(en_list->clks[i])) + continue; + clk_enable(en_list->clks[i]); + en_list->en_status |= (1 << i); + } + } + p_list = pd->clk_parent; if (!p_list) return; @@ -168,12 +268,31 @@ static void exynos_pd_poweroff_prepare(struct exynos_pm_domain *pd) clk_set_parent(p_list->clks[i], p_list->trans_clks[i]); } +static void exynos_pd_post_poweroff(struct exynos_pm_domain *pd) +{ + struct clk_enable_list *en_list; + int i; + + en_list = pd->clk_pd_off; + if (!en_list) + return; + + /* Disable the clocks enabled during power off preperation */ + for (i = 0; i < en_list->count; i++) { + if (!(en_list->en_status & (1 << i))) + continue; + clk_disable(en_list->clks[i]); + en_list->en_status &= ~(1 << i); + } +} + static int exynos_pd_power_on(struct generic_pm_domain *domain) { int ret = 0; struct exynos_pm_domain *pd; pd = container_of(domain, struct exynos_pm_domain, pd); + exynos_pd_poweron_prepare(pd); ret = pd_ops->pd_on(domain->name, pd->base); if (ret) return ret; @@ -189,6 +308,9 @@ static int exynos_pd_power_off(struct generic_pm_domain *domain) pd = container_of(domain, struct exynos_pm_domain, pd); exynos_pd_poweroff_prepare(pd); ret = pd_ops->pd_off(domain->name, pd->base); + if (ret) + return ret; + exynos_pd_post_poweroff(pd); return ret; } @@ -232,6 +354,14 @@ static int exynos_power_domain_probe(struct platform_device *pdev) pd->pd.power_off = exynos_pd_power_off; pd->pd.power_on = exynos_pd_power_on; + if (of_find_property(np, "pd-on-en-clocks", NULL)) + if (pd_init_enable_clocks(pdev, np, true, pd)) + return -EINVAL; + + if (of_find_property(np, "pd-off-en-clocks", NULL)) + if (pd_init_enable_clocks(pdev, np, false, pd)) + return -EINVAL; + if (of_find_property(np, "pd-parent-clocks", NULL)) if (pd_init_parent_clocks(pdev, np, pd)) return -EINVAL; -- 1.7.9.5 -- To unsubscribe from this list: send the line "unsubscribe linux-samsung-soc" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html