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);