blob: 4d722907fb1f989408c12ad6fb1b5369da92cfc2 [file] [log] [blame]
/*
* 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 <guodong.xu@linaro.org>
*
* 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 <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/pm.h>
#include <linux/suspend.h>
#include <linux/cpu.h>
#include <linux/notifier.h>
#include <linux/reboot.h>
#include <asm/memory.h>
#include <linux/delay.h>
#include <linux/suspend.h>
#include <asm/mach/time.h>
#include <linux/slab.h>
#include <asm/io.h>
#include <linux/clk.h>
#include <asm/cacheflush.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/cpu_pm.h>
#include <asm/fncpy.h>
#include "core.h"
#include "hipm.h"
#ifdef CONFIG_HSK3_RTC_WAKEUP
#include <linux/irqchip/arm-gic.h>
#include <linux/irq.h>
#endif
#ifdef CONFIG_HSK3_CONSOLE_WORKAROUND
#include <linux/console.h>
#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;
}
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 <guodong.xu@linaro.org>");
MODULE_DESCRIPTION("HiSilicon power management driver");
MODULE_LICENSE("GPL v2");