blob: 8017a691cfe01edacf0df9541f7a5c9905a66508 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* PM functions for ASR SoC
* Copyright (C) 2019 ASR Micro Limited
*/
#include <asm/io.h>
#include <soc/asr/pm.h>
#include <linux/irq.h>
#include <asm/compiler.h>
#include <linux/arm-smccc.h>
#include <linux/kernel.h>
#include <linux/pm_qos.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/mod_devicetable.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/debugfs.h>
#include <linux/regmap.h>
#include <linux/mfd/syscon.h>
#include <linux/asr_tee_sip.h>
#include <linux/irqchip/mmp.h>
#include <linux/irqchip/arm-gic.h>
#ifdef CONFIG_SMP
#include <asm/smp.h>
#endif
#include <soc/asr/wakeup_defines.h>
#include <soc/asr/asrdcstat.h>
#include <soc/asr/regs-apmu.h>
#include <trace/events/pxa.h>
static unsigned int edgew_rer[4];
static void __iomem *mfpr_base;
enum {
GPIO_ADD,
GPIO_REMOVE,
};
typedef void (*edge_handler)(int, void *);
/*
* struct edge_wakeup_desc - edge wakeup source descriptor, used for drivers
* whose pin is used to wakeup system.
* @list: list control of the descriptors
* @gpio: the gpio number of the wakeup source
* @dev: the device that gpio attaches to
* @data: optional, any kind private data passed to the handler
* @handler: optional, the handler for the certain wakeup source detected
*/
struct edge_wakeup_desc {
struct list_head list;
int gpio;
struct device *dev;
void *data;
edge_handler handler;
};
struct edge_wakeup {
struct list_head list;
spinlock_t lock;
int num;
void __iomem *base;
};
static struct edge_wakeup *info;
int mfp_edge_wakeup_cb(unsigned long val, int gpio)
{
int error = 0;
switch (val) {
case GPIO_ADD:
error = asr_gpio_edge_detect_add(gpio);
break;
case GPIO_REMOVE:
error = asr_gpio_edge_detect_remove(gpio);;
break;
default:
panic("Wrong LC command for GPIO edge wakeup!");
}
if (error)
pr_warn("GPIO %d %s failed!\n", gpio, (val == GPIO_ADD) ? "add" : "remove");
return error;
}
int asr_set_wake(struct irq_data *data, unsigned int on)
{
int irq = (int)data->hwirq;
struct irq_desc *desc = irq_to_desc(data->irq);
if (!desc) {
pr_err("irq_desc is NULL\n");
return -EINVAL;
}
if (on) {
if (desc->action)
desc->action->flags |= IRQF_NO_SUSPEND;
} else {
if (desc->action)
desc->action->flags &= ~IRQF_NO_SUSPEND;
}
asr_irq_wake_set(irq, on);
return 0;
}
int extern_set_rtc_wkup_disabled(bool flag)
{
asr_irq_wake_set((128 + 6), flag);
pr_info("rtc_no_wakeup set to: %d\n", flag);
return 0;
}
int extern_set_gpio_wkup_disabled(bool disable, int gpio_num)
{
if (gpio_num < 0 || gpio_num > 127) {
pr_err("%s error gpio num: %d!\n", __func__, gpio_num);
return -ENODEV;
}
if (disable)
asr_gpio_edge_detect_disable(gpio_num);
else
asr_gpio_edge_detect_enable(gpio_num);
return 0;
}
static void __iomem * get_gpio_vaddr(int gpio)
{
#if defined(CONFIG_CPU_ASR18XX)
if (gpio <= 54)
return mfpr_base + 0xDC + (gpio) * 4;
#elif defined(CONFIG_CPU_ASR1901)
if (gpio <= 25)
return mfpr_base + 0x4 + ((gpio) << 2);
else if ((gpio >= 26) && (gpio <= 91))
return mfpr_base + 0x20C + ((gpio-26) << 2);
#endif
panic("GPIO number %d doesn't exist!\n", gpio);
}
static struct pm_wakeup_status asr_wkup_sts;
void asr_clear_wakeup_event_idx(void)
{
asr_wkup_sts.main_wakeup_idx = asr_wkup_sts.gpio_wakeup_idx = asr_wkup_sts.irq_wakeup_idx = 0;
}
int asr_get_main_wakeup_count(void)
{
return asr_wkup_sts.main_wakeup_idx;
}
u32 asr_get_main_wakeup_event(int idx)
{
if (idx < asr_wkup_sts.main_wakeup_idx) {
return asr_wkup_sts.sys_main_wakeup_id[idx];
} else {
pr_err("%s: error main wakeup idx %d\n", __func__, idx);
return 0;
}
}
int asr_get_gpio_wakeup_count(void)
{
return asr_wkup_sts.gpio_wakeup_idx;
}
u32 asr_get_gpio_wakeup_event(int idx)
{
if (idx < asr_wkup_sts.gpio_wakeup_idx) {
return asr_wkup_sts.sys_gpio_wakeup_id[idx];
} else {
pr_err("%s: error gpio wakeup idx %d\n", __func__, idx);
return 0;
}
}
int asr_get_irq_wakeup_count(void)
{
return asr_wkup_sts.irq_wakeup_idx;
}
u32 asr_get_irq_wakeup_event(int idx)
{
if (idx < asr_wkup_sts.irq_wakeup_idx) {
return asr_wkup_sts.sys_irq_wakeup_id[idx];
} else {
pr_err("%s: error irq wakeup idx %d\n", __func__, idx);
return 0;
}
}
/* MFPR bits */
#define EDGE_CLEAR (1 << 6)
#define EDGE_FALL_EN (1 << 5)
#define EDGE_RISE_EN (1 << 4)
static inline irqreturn_t edge_wakeup_handler(int irq, void *dev_id)
{
struct edge_wakeup_desc *e;
unsigned int i;
void __iomem *addr;
unsigned int val;
for (i = 0; i < (info->num / 32); i++) {
edgew_rer[i] = readl_relaxed(info->base + i * 4);
}
list_for_each_entry(e, &info->list, list) {
if (test_and_clear_bit(e->gpio, (unsigned long *)edgew_rer)) {
if (e->handler)
e->handler(e->gpio, e->data);
addr = get_gpio_vaddr(e->gpio);
val = readl_relaxed(addr);
val |= EDGE_CLEAR;
val &= ~(EDGE_FALL_EN | EDGE_RISE_EN);
writel_relaxed(val, addr);
}
}
return IRQ_HANDLED;
}
/*
* mmp_request/remove_edge_wakeup is called by common device driver.
*
* Drivers use it to set one or several pins as wakeup sources in deep low
* power modes.
*/
int request_mfp_edge_wakeup(int gpio, edge_handler handler, void *data, struct device *dev)
{
struct edge_wakeup_desc *desc, *e;
unsigned long flags;
if (dev == NULL) {
pr_err("error: edge wakeup: unknown device!\n");
return -EINVAL;
}
if (gpio < 0 || gpio > info->num) {
pr_err("error: edge wakeup: add invalid gpio num %d!\n", gpio);
return -EINVAL;
}
desc = kzalloc(sizeof(struct edge_wakeup_desc), GFP_KERNEL);
if (!desc)
return -ENOMEM;
desc->gpio = gpio;
desc->dev = dev;
desc->data = data;
desc->handler = handler;
spin_lock_irqsave(&info->lock, flags);
list_for_each_entry(e, &info->list, list) {
if (e->gpio == gpio) {
dev_err(dev, "Adding exist gpio%d to edge wakeup!\n", desc->gpio);
spin_unlock_irqrestore(&info->lock, flags);
kfree(desc);
return -EEXIST;
}
}
list_add(&desc->list, &info->list);
spin_unlock_irqrestore(&info->lock, flags);
return mfp_edge_wakeup_cb(GPIO_ADD, gpio);
}
int remove_mfp_edge_wakeup(int gpio)
{
struct edge_wakeup_desc *desc, *tmp;
unsigned long flags;
if (gpio < 0 || gpio > info->num) {
pr_err("error: edge wakeup: remove invalid gpio num %d!\n", gpio);
return -EINVAL;
}
spin_lock_irqsave(&info->lock, flags);
list_for_each_entry_safe(desc, tmp, &info->list, list) {
if (desc->gpio == gpio) {
list_del(&desc->list);
kfree(desc);
break;
}
}
spin_unlock_irqrestore(&info->lock, flags);
return mfp_edge_wakeup_cb(GPIO_REMOVE, gpio);
}
static int edge_wakeup_mfp_probe(struct platform_device *pdev)
{
struct resource *res;
unsigned int irq;
info = devm_kzalloc(&pdev->dev, sizeof(struct edge_wakeup), GFP_KERNEL);
if (!info)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (!res) {
panic("failed to get irq resource\n");
}
irq = res->start;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
panic("failed to get mem resource\n");
}
info->base = devm_ioremap_resource(&pdev->dev, res);
info->num = resource_size(res) * 8;
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
if (!res)
panic("failed to get mem resource\n");
mfpr_base = devm_ioremap(&pdev->dev, res->start, resource_size(res));
if (!mfpr_base)
panic("failed to map registers\n");
spin_lock_init(&info->lock);
INIT_LIST_HEAD(&info->list);
platform_set_drvdata(pdev, info);
if (request_irq(irq, edge_wakeup_handler, IRQF_NO_SUSPEND, "edge irq", NULL))
panic("failed to enable edge wakeup irq!\n");
return 0;
}
#ifdef CONFIG_CPU_ASR1901
void asr1901_gic_raise_softirq(const struct cpumask *mask, unsigned int irq)
{
unsigned int val = 0;
int targ_cpu;
/* We shouldn't access any registers when AXI time out occurred */
if (keep_silent)
return;
/*
* Set the wakeup bits to make sure the core(s) can respond to
* the IPI interrupt.
* If the target core(s) is alive, this operation is ignored by
* the APMU. After the core wakes up, these corresponding bits
* are clearly automatically by PMU hardware.
*/
preempt_disable();
for_each_cpu(targ_cpu, mask) {
BUG_ON(targ_cpu >= CONFIG_NR_CPUS);
val |= APMU_WAKEUP_CORE(targ_cpu);
}
__raw_writel(val, APMU_COREn_WAKEUP_CTL(smp_processor_id()));
preempt_enable();
}
#endif
static void find_deepest_state(int *index)
{
*index = min(*index, pm_qos_request(PM_QOS_CPUIDLE_BLOCK) - 1);
}
#ifdef CONFIG_ASR_CLK_DCSTAT
static int idle_stat[] = {LPM_C1, LPM_C2, LPM_D1P, LPM_D1};
void asr_cpuidle_enter_dcstat(int index)
{
int lpm_mode;
int state = index;
find_deepest_state(&state);
lpm_mode = idle_stat[state];
if ( (lpm_mode >= LPM_C2) && (lpm_mode != LPM_D2_UDR) ) {
cpu_dcstat_event(cpu_dcstat_clk, 0, CPU_M2_OR_DEEPER_ENTER,
lpm_mode);
#ifdef CONFIG_ASR_DVFS
vol_dcstat_event(lpm_mode);
vol_ledstatus_event(lpm_mode);
#endif
}
cpu_dcstat_event(cpu_dcstat_clk, 0, CPU_IDLE_ENTER, lpm_mode);
trace_pxa_cpu_idle(LPM_ENTRY(lpm_mode), 0, 0);
}
void asr_cpuidle_exit_dcstat(void)
{
cpu_dcstat_event(cpu_dcstat_clk, 0, CPU_IDLE_EXIT, MAX_LPM_INDEX);
#ifdef CONFIG_ASR_DVFS
vol_dcstat_event(MAX_LPM_INDEX);
vol_ledstatus_event(MAX_LPM_INDEX);
#endif
trace_pxa_cpu_idle(LPM_EXIT(0), 0, 0);
}
#endif
static int __init asr_pm_init(void)
{
#ifndef CONFIG_CPU_ASR18XX
gic_arch_extn.irq_set_wake = asr_set_wake;
#else
icu_irq_chip.irq_set_wake = asr_set_wake;
#endif
asr_wake_status_init((u64)virt_to_phys(&asr_wkup_sts));
return 0;
}
arch_initcall(asr_pm_init);
static struct of_device_id edge_wakeup_mfp_dt_ids[] = {
{ .compatible = "asr,edge-wakeup", },
{}
};
static struct platform_driver edge_wakeup_driver = {
.probe = edge_wakeup_mfp_probe,
.driver = {
.name = "asr-edge-wakeup",
.of_match_table = of_match_ptr(edge_wakeup_mfp_dt_ids),
},
};
static int __init edge_wakeup_driver_init(void)
{
return platform_driver_register(&edge_wakeup_driver);
}
subsys_initcall(edge_wakeup_driver_init);