/* * Power Management suspend/resume routines for HiSilicon platform * * Copyright © 2011-2013 HiSilicon Technologies Co., Ltd. * http://www.hisilicon.com * Copyright © 2012-2013 Linaro Ltd. * http://www.linaro.org * * Author: Guodong Xu * * 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. * */ #define CONFIG_HSK3_RTC_WAKEUP #define CONFIG_HSK3_CONSOLE_WORKAROUND #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core.h" #include "hipm.h" #ifdef CONFIG_HSK3_RTC_WAKEUP #include #include #endif #ifdef CONFIG_HSK3_CONSOLE_WORKAROUND #include #endif /* resources in power management */ struct iomap_resource { struct resource *res; void __iomem *virt_addr; }; struct pm_resources { struct iomap_resource pmu_spi; struct iomap_resource secram; struct iomap_resource pctrl; struct iomap_resource a9_per; #ifdef CONFIG_HSK3_CONSOLE_WORKAROUND struct iomap_resource uart0; struct iomap_resource io; #endif }; struct pm_resources hs_pm_resources; void __iomem *timer0_base_addr; void __iomem *hs_secram_va_base; void __iomem *hs_a9per_va_base; void __iomem *hs_pctrl_va_base; void __iomem *hs_pmuspi_va_base; #ifdef CONFIG_HSK3_CONSOLE_WORKAROUND static void __iomem *hs_uart0_va_base; static void __iomem *hs_io_va_base; /* re-init debug uart */ static void debuguart_reinit(void) { void __iomem *usctrl_base = hs_sctrl_base; void __iomem *uuart_base = hs_uart0_va_base; void __iomem *io_base = hs_io_va_base; unsigned int uregv = 0; /* Config necessary IOMG configuration */ writel(0, (io_base + 0xF4)); /* config necessary IOCG configuration */ writel(0, (io_base + 0xA08)); writel(0, (io_base + 0xA0C)); /* disable clk */ uregv = 0x10000; writel(uregv, (usctrl_base + 0x44)); /* select 26MHz clock */ uregv = (1 << 23); writel(uregv, (usctrl_base + 0x100)); /* enable clk */ uregv = 0x10000; writel(uregv, (usctrl_base + 0x40)); /* disable recieve and send */ uregv = 0x0; writel(uregv, (uuart_base + 0x30)); /* enable FIFO */ uregv = 0x70; writel(uregv, (uuart_base + 0x2c)); /* set baudrate */ uregv = 0xE; writel(uregv, (uuart_base + 0x24)); uregv = 0x7; writel(uregv, (uuart_base + 0x28)); /* clear buffer */ uregv = readl(uuart_base); /* enable FIFO */ uregv = 0x70; writel(uregv, (uuart_base + 0x2C)); /* set FIFO depth */ uregv = 0x10A; writel(uregv, (uuart_base + 0x34)); uregv = 0x50; writel(uregv, (uuart_base + 0x38)); /* enable uart trans */ uregv = 0xF01; writel(uregv, (uuart_base + 0x30)); } #endif /* CONFIG_HSK3_CONSOLE_WORKAROUND */ /* power-off GPIO pin handle */ static int pmu_power_off_handle; /* system power off func */ static void hisik3_power_off(void) { int ret; ret = gpio_request(pmu_power_off_handle, 0); if (ret != 0) pr_emerg("request PMU_POWER_OFF gpio error:%d\n", ret); /* clear HRST_REG */ writel(0, PMU_HRST_REG); while (1) { pr_emerg("system power off now\n"); gpio_direction_output(pmu_power_off_handle, 0); gpio_set_value(pmu_power_off_handle, 0); msleep(1000); } gpio_free(pmu_power_off_handle); } static int wrapper_hs_godpsleep(void) { hilpm_cpu_godpsleep(); #ifdef CONFIG_HSK3_CONSOLE_WORKAROUND /* debuguart needs to be reinit when "no_console_suspend" is set. */ if (!console_suspend_enabled) debuguart_reinit(); #endif return 0; } static int hisik3_pm_enter(suspend_state_t state) { void __iomem *addr; unsigned long flage = 0; pr_notice("[PM] Enter sleep, in %s()\n", __func__); switch (state) { case PM_SUSPEND_STANDBY: case PM_SUSPEND_MEM: break; default: return -EINVAL; } /* store master cpu resume addree into SCTRL */ addr = (MASTER_SR_BACK_PHY_ADDR - REG_BASE_SCTRL) + hs_sctrl_base; writel(hisi_v2p(master_cpu_resume), addr); /* copy securam code */ fncpy(hs_secram_va_base + DPSLEEP_CODE_ADDR_OFFSET, &hs_finish_suspend, DPSLEEP_CODE_LEN); local_irq_save(flage); #ifdef CONFIG_CACHE_L2X0 outer_flush_all(); outer_disable(); #endif /* config pmu low power */ pmulowpower(1); /* Entering deep sleep */ wrapper_hs_godpsleep(); /*PMU regs restore*/ pmulowpower(0); #ifdef CONFIG_CACHE_L2X0 outer_resume(); #endif flush_cache_all(); local_irq_restore(flage); pr_notice("[PM] Restore OK\n"); return 0; } static const struct platform_suspend_ops hisik3_pm_ops = { .enter = hisik3_pm_enter, .valid = suspend_valid_only_mem, }; static int hs_pm_get_resources(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct device_node *np = dev->of_node; struct resource *res; int ret = 0; /* get the base address of pmu_spi */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { ret = -ENOMEM; dev_err(dev, "platform_get_resource 0 err, ret=%d\n", ret); goto error_res_1; } hs_pm_resources.pmu_spi.res = res; hs_pm_resources.pmu_spi.virt_addr = ioremap(res->start, resource_size(res)); if (!hs_pm_resources.pmu_spi.virt_addr) { ret = -ENOMEM; dev_err(dev, "cannot ioremap pmu_spi register memory\n"); goto error_res_1; } hs_pmuspi_va_base = hs_pm_resources.pmu_spi.virt_addr; /* get the base address of secram */ res = platform_get_resource(pdev, IORESOURCE_MEM, 1); if (!res) { ret = -ENOMEM; dev_err(dev, "platform_get_resource 1 err, ret=%d\n", ret); goto error_res_2; } hs_pm_resources.secram.res = res; hs_pm_resources.secram.virt_addr = ioremap(res->start, resource_size(res)); if (!hs_pm_resources.secram.virt_addr) { ret = -ENOMEM; dev_err(dev, "cannot ioremap secram register memory\n"); goto error_res_2; } hs_secram_va_base = hs_pm_resources.secram.virt_addr; /* get the base address of pctrl */ res = platform_get_resource(pdev, IORESOURCE_MEM, 2); if (!res) { ret = -ENOMEM; dev_err(dev, "platform_get_resource 2 err, ret=%d\n", ret); goto error_res_3; } hs_pm_resources.pctrl.res = res; hs_pm_resources.pctrl.virt_addr = ioremap(res->start, resource_size(res)); if (!hs_pm_resources.pctrl.virt_addr) { ret = -ENOMEM; dev_err(dev, "cannot ioremap pctrl register memory\n"); goto error_res_3; } hs_pctrl_va_base = hs_pm_resources.pctrl.virt_addr; /* get the base address of a9_per */ res = platform_get_resource(pdev, IORESOURCE_MEM, 3); if (!res) { ret = -ENOMEM; dev_err(dev, "platform_get_resource 3 err, ret=%d\n", ret); goto error_res_4; } hs_pm_resources.a9_per.res = res; hs_pm_resources.a9_per.virt_addr = ioremap(res->start, resource_size(res)); if (!hs_pm_resources.a9_per.virt_addr) { ret = -ENOMEM; dev_err(dev, "cannot ioremap a9_per register memory\n"); goto error_res_4; } hs_a9per_va_base = hs_pm_resources.a9_per.virt_addr; #ifdef CONFIG_HSK3_CONSOLE_WORKAROUND /* get the base address of uart0 */ res = platform_get_resource(pdev, IORESOURCE_MEM, 4); if (!res) { ret = -ENOMEM; dev_err(dev, "platform_get_resource 4 err, ret=%d\n", ret); goto error_res_5; } hs_pm_resources.uart0.res = res; hs_pm_resources.uart0.virt_addr = ioremap(res->start, resource_size(res)); if (!hs_pm_resources.uart0.virt_addr) { ret = -ENOMEM; dev_err(dev, "cannot ioremap uart0 register memory\n"); goto error_res_5; } hs_uart0_va_base = hs_pm_resources.uart0.virt_addr; /* get the base address of io */ res = platform_get_resource(pdev, IORESOURCE_MEM, 5); if (!res) { ret = -ENOMEM; dev_err(dev, "platform_get_resource 5 err, ret=%d\n", ret); goto error_res_6; } hs_pm_resources.io.res = res; hs_pm_resources.io.virt_addr = ioremap(res->start, resource_size(res)); if (!hs_pm_resources.io.virt_addr) { ret = -ENOMEM; dev_err(dev, "cannot ioremap io register memory\n"); goto error_res_6; } hs_io_va_base = hs_pm_resources.io.virt_addr; #endif /* CONFIG_HSK3_CONSOLE_WORKAROUND */ /* get pmu_power_off GPIO */ pmu_power_off_handle = of_get_named_gpio(np, "pmu-power-hold-gpios", 0); if (!gpio_is_valid(pmu_power_off_handle)) { dev_err(dev, "Fail to get pmu_power_off gpio, err=%d\n", pmu_power_off_handle); ret = pmu_power_off_handle; goto error_pmu_gpio; } goto get_res_end; error_pmu_gpio: #ifdef CONFIG_HSK3_CONSOLE_WORKAROUND iounmap(hs_pm_resources.io.virt_addr); error_res_6: iounmap(hs_pm_resources.uart0.virt_addr); error_res_5: #endif iounmap(hs_pm_resources.a9_per.virt_addr); error_res_4: iounmap(hs_pm_resources.pctrl.virt_addr); error_res_3: iounmap(hs_pm_resources.secram.virt_addr); error_res_2: iounmap(hs_pm_resources.pmu_spi.virt_addr); error_res_1: get_res_end: return ret; } static int hs_pm_free_resources(struct platform_device *pdev) { #ifdef CONFIG_HSK3_CONSOLE_WORKAROUND iounmap(hs_pm_resources.io.virt_addr); iounmap(hs_pm_resources.uart0.virt_addr); #endif /* CONFIG_HSK3_CONSOLE_WORKAROUND */ iounmap(hs_pm_resources.a9_per.virt_addr); iounmap(hs_pm_resources.pctrl.virt_addr); iounmap(hs_pm_resources.secram.virt_addr); iounmap(hs_pm_resources.pmu_spi.virt_addr); return 0; } #ifdef CONFIG_HSK3_RTC_WAKEUP static int hs_set_wake(struct irq_data *data, unsigned int state) { return 0; } #endif static int hs_pm_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; void __iomem *addr; int ret = 0; /* alloc memmory for suspend */ hi_cpu_godpsleep_ddrbase = (unsigned long)kzalloc( (SR_PROTECT_CTX_BUFF_SIZE), GFP_DMA|GFP_KERNEL); if (!hi_cpu_godpsleep_ddrbase) { dev_err(dev, "kzalloc err for hi_cpu_godpsleep_ddrbase!\n"); return -ENOMEM; } hi_cpu_godpsleep_phybase = hisi_v2p(hi_cpu_godpsleep_ddrbase); /* get resources, parse from device tree */ ret = hs_pm_get_resources(pdev); if (ret) { dev_err(dev, "hs_pm_get_resources err, ret=%d\n", ret); goto error_res; } #ifdef CONFIG_CPU_IDLE /* hilpm_cpu_godpsleep() use disable_mmu() and enable_mmu() * which running in securam. * we copy the two functions during init phase */ hilpm_cp_cpuidle_code(); #endif addr = hs_sctrl_base + 0x10; writel(SCXTALCTRL_CFG_VAL_200MS, addr); suspend_set_ops(&hisik3_pm_ops); /* power off function */ pm_power_off = hisik3_power_off; #ifdef CONFIG_HSK3_RTC_WAKEUP /* set wakeup funciton */ gic_arch_extn.irq_set_wake = hs_set_wake; #endif goto probe_end; error_res: kfree((void *)hi_cpu_godpsleep_ddrbase); probe_end: return ret; } static int hs_pm_remove(struct platform_device *pdev) { hs_pm_free_resources(pdev); kfree((void *)hi_cpu_godpsleep_ddrbase); return 0; } static struct of_device_id of_hs_pm_match_tbl[] = { { .compatible = "hisilicon,hs-power-management", }, { /* end */ } }; static struct platform_driver hs_pm_driver = { .driver = { .name = "hs_pm", .owner = THIS_MODULE, .of_match_table = of_hs_pm_match_tbl, }, .probe = hs_pm_probe, .remove = hs_pm_remove, }; static int __init hs_pm_init(void) { return platform_driver_register(&hs_pm_driver); } module_init(hs_pm_init); static void __exit hs_pm_exit(void) { platform_driver_unregister(&hs_pm_driver); } module_exit(hs_pm_exit); MODULE_AUTHOR("Guodong Xu "); MODULE_DESCRIPTION("HiSilicon power management driver"); MODULE_LICENSE("GPL v2");