diff --git a/drivers/amlogic/pm/Makefile b/drivers/amlogic/pm/Makefile index 49582c280dc0..87b8709853fb 100644 --- a/drivers/amlogic/pm/Makefile +++ b/drivers/amlogic/pm/Makefile @@ -1,3 +1,4 @@ obj-$(CONFIG_AMLOGIC_LEGACY_EARLY_SUSPEND) += lgcy_early_suspend.o obj-$(CONFIG_AMLOGIC_GX_SUSPEND) += gx_pm.o +obj-$(CONFIG_AMLOGIC_GX_SUSPEND) += vad_power.o obj-$(CONFIG_AMLOGIC_M8B_SUSPEND) += m8b_pm.o diff --git a/drivers/amlogic/pm/gx_pm.c b/drivers/amlogic/pm/gx_pm.c index 951a3dc4b7ed..0ea41c52470f 100644 --- a/drivers/amlogic/pm/gx_pm.c +++ b/drivers/amlogic/pm/gx_pm.c @@ -42,6 +42,7 @@ #include #include <../kernel/power/power.h> #include +#include "vad_power.h" typedef unsigned long (psci_fn)(unsigned long, unsigned long, unsigned long, unsigned long); @@ -218,6 +219,8 @@ static int __init meson_pm_probe(struct platform_device *pdev) { struct device_node *cpu_node; struct device_node *state_node; + struct pm_data *p_data; + struct device *dev = &pdev->dev; int count = 0, ret; u32 ver = psci_get_version(); u32 paddr = 0; @@ -246,6 +249,14 @@ static int __init meson_pm_probe(struct platform_device *pdev) suspend_set_ops(&meson_gx_ops); } + p_data = devm_kzalloc(&pdev->dev, sizeof(struct pm_data), GFP_KERNEL); + if (!p_data) + return -ENOMEM; + p_data->dev = dev; + dev_set_drvdata(dev, p_data); + + vad_wakeup_power_init(pdev, p_data); + ret = of_property_read_u32(pdev->dev.of_node, "debug_reg", &paddr); if (!ret) { @@ -280,6 +291,23 @@ uniomap: return -ENXIO; } +int pm_suspend_noirq(struct device *dev) +{ + vad_wakeup_power_suspend(dev); + return 0; +} + +int pm_resume_noirq(struct device *dev) +{ + vad_wakeup_power_resume(dev); + return 0; +} + +static const struct dev_pm_ops meson_pm_noirq_ops = { + .suspend_noirq = pm_suspend_noirq, + .resume_noirq = pm_resume_noirq, +}; + static int meson_pm_remove(struct platform_device *pdev) { return 0; @@ -296,6 +324,7 @@ static struct platform_driver meson_pm_driver = { .name = "pm-meson", .owner = THIS_MODULE, .of_match_table = amlogic_pm_dt_match, + .pm = &meson_pm_noirq_ops, }, .probe = meson_pm_probe, .remove = meson_pm_remove, diff --git a/drivers/amlogic/pm/vad_power.c b/drivers/amlogic/pm/vad_power.c new file mode 100644 index 000000000000..77cadc925912 --- /dev/null +++ b/drivers/amlogic/pm/vad_power.c @@ -0,0 +1,217 @@ +/* + * drivers/amlogic/pm/vad_power.c + * + * Copyright (C) 2017 Amlogic, Inc. 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 as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../../gpio/gpiolib.h" +#include "vad_power.h" + +#define IO3V3_EN "vddio3v3_en" + +static int fixed_pll_cnt; + +int vad_wakeup_power_init(struct platform_device *pdev, struct pm_data *p_data) +{ + int ret; + const char *value; + struct gpio_desc *desc; + u32 paddr = 0; + + ret = of_property_read_string(pdev->dev.of_node, "vddio3v3_en", &value); + if (ret) { + pr_info("no vddio3v3_en pin"); + p_data->vddio3v3_en = 0; + } else { + desc = of_get_named_gpiod_flags(pdev->dev.of_node, + "vddio3v3_en", 0, NULL); + p_data->vddio3v3_en = desc_to_gpio(desc); + } + if (p_data->vddio3v3_en > 0) + gpio_request(p_data->vddio3v3_en, IO3V3_EN); + + ret = of_property_read_u32(pdev->dev.of_node, + "vad_wakeup_disable", &paddr); + if (!ret) { + p_data->vad_wakeup_disable = paddr; + pr_info("vad_wakeup_disable: 0x%x\n", paddr); + } else { + p_data->vad_wakeup_disable = 1; + } + + ret = of_property_read_u32(pdev->dev.of_node, + "dmc_asr", &paddr); + if (!ret) { + pr_info("dmc_asr: 0x%x\n", paddr); + p_data->dmc_asr = ioremap(paddr, 0x4); + } else { + p_data->dmc_asr = 0; + } + + ret = of_property_read_u32(pdev->dev.of_node, + "cpu_reg", &paddr); + if (!ret) { + pr_info("cpu_reg: 0x%x\n", paddr); + p_data->cpu_reg = ioremap(paddr, 0x4); + } else { + p_data->cpu_reg = 0; + } + + p_data->switch_clk81 = devm_clk_get(&pdev->dev, "switch_clk81"); + if (IS_ERR(p_data->switch_clk81)) { + dev_err(&pdev->dev, + "Can't get switch_clk81\n"); + return PTR_ERR(p_data->switch_clk81); + } + p_data->clk81 = devm_clk_get(&pdev->dev, "clk81"); + if (IS_ERR(p_data->clk81)) { + dev_err(&pdev->dev, + "Can't get clk81\n"); + return PTR_ERR(p_data->clk81); + } + p_data->xtal = devm_clk_get(&pdev->dev, "xtal"); + if (IS_ERR(p_data->xtal)) { + dev_err(&pdev->dev, + "Can't get xtal\n"); + return PTR_ERR(p_data->xtal); + } + p_data->fixed_pll = devm_clk_get(&pdev->dev, "fixed_pll"); + if (IS_ERR(p_data->fixed_pll)) { + dev_err(&pdev->dev, + "Can't get fixed_pll\n"); + return PTR_ERR(p_data->fixed_pll); + } + + return 0; +} + +void cpu_clk_switch_to_gp1(unsigned int flag, void __iomem *paddr) +{ + u32 control; + u32 dyn_pre_mux = 0; + u32 dyn_post_mux = 1; + u32 dyn_div = 1; + + control = readl(paddr); + /*check cpu busy or not*/ + do { + control = readl(paddr); + } while (control & (1 << 28)); + + if (!flag) { + dyn_pre_mux = 3; + dyn_post_mux = 1; + dyn_div = 1; + /*cpu clk sel channel a*/ + if (control & (1 << 10)) { + control = (control & ~((1 << 10) | (0x3f << 4) + | (1 << 2) | (0x3 << 0))) + | ((0 << 10) + | (dyn_div << 4) + | (dyn_post_mux << 2) + | (dyn_pre_mux << 0)); + } else { + /*cpu clk sel channel b*/ + control = (control & ~((1 << 10) | (0x3f << 20) + | (1 << 18) | (0x3 << 16))) + | ((1 << 10) + | (dyn_div << 20) + | (dyn_post_mux << 18) + | (dyn_pre_mux << 16)); + } + } else { + if (control & (1 << 10)) + control = control & ~(1 << 10); + else + control = control | (1 << 10); + } + writel(control, paddr); +} + +int vad_wakeup_power_suspend(struct device *dev) +{ + struct pm_data *p_data = dev_get_drvdata(dev); + int i; + + if (!is_pm_freeze_mode() || p_data->vad_wakeup_disable) + return 0; + + clk_set_parent(p_data->switch_clk81, p_data->xtal); + pr_info("switch clk81 to 24M.\n"); + + /*cpu clk switch to gp1*/ + cpu_clk_switch_to_gp1(0, p_data->cpu_reg); + pr_info("cpu clk switch to gp1.\n"); + + fixed_pll_cnt = __clk_get_enable_count(p_data->fixed_pll); + if (fixed_pll_cnt > 1) + dev_warn(dev, + "Now fixed pll enable count = %d\n", + fixed_pll_cnt); + + for (i = 0; i < fixed_pll_cnt; i++) + clk_disable_unprepare(p_data->fixed_pll); + + gpio_direction_output(p_data->vddio3v3_en, 0); + pr_info("power off vddio_3v3.\n"); + + if (p_data->dmc_asr) { + writel(0x3fe00, p_data->dmc_asr); + pr_info("enable dmc asr\n"); + } + + return 0; +} + +int vad_wakeup_power_resume(struct device *dev) +{ + struct pm_data *p_data = dev_get_drvdata(dev); + int i; + + if (!is_pm_freeze_mode() || p_data->vad_wakeup_disable) + return 0; + + gpio_direction_output(p_data->vddio3v3_en, 1); + pr_info("power on vddio_3v3.\n"); + + if (p_data->dmc_asr) { + writel(0x0, p_data->dmc_asr); + pr_info("disable dmc asr\n"); + } + + /* enable fixed pll */ + for (i = 0; i < fixed_pll_cnt; i++) { + if (clk_prepare_enable(p_data->fixed_pll)) + dev_err(dev, "failed to enable fixed pll\n"); + } + + /*restore cpu clk*/ + cpu_clk_switch_to_gp1(1, p_data->cpu_reg); + pr_info("cpu clk restore.\n"); + clk_set_parent(p_data->switch_clk81, p_data->clk81); + pr_info("switch clk81 to 166M.\n"); + + return 0; +} diff --git a/drivers/amlogic/pm/vad_power.h b/drivers/amlogic/pm/vad_power.h new file mode 100644 index 000000000000..ed651c251da7 --- /dev/null +++ b/drivers/amlogic/pm/vad_power.h @@ -0,0 +1,33 @@ +/* + * drivers/amlogic/pm/vad_power.h + * + * Copyright (C) 2017 Amlogic, Inc. 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 as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + +struct pm_data { + struct device *dev; + int vddio3v3_en; + bool vad_wakeup_disable; + void __iomem *dmc_asr; + void __iomem *cpu_reg; + struct clk *switch_clk81; + struct clk *clk81; + struct clk *fixed_pll; + struct clk *xtal; +}; + +int vad_wakeup_power_init(struct platform_device *pdev, struct pm_data *p_data); +int vad_wakeup_power_suspend(struct device *dev); +int vad_wakeup_power_resume(struct device *dev); +