ASR_BASE
Change-Id: Icf3719cc0afe3eeb3edc7fa80a2eb5199ca9dda1
diff --git a/marvell/linux/drivers/soc/asr/pm.c b/marvell/linux/drivers/soc/asr/pm.c
new file mode 100644
index 0000000..8017a69
--- /dev/null
+++ b/marvell/linux/drivers/soc/asr/pm.c
@@ -0,0 +1,442 @@
+// 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);