summaryrefslogtreecommitdiff
path: root/drivers/misc/vexpress/arm-spc.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/misc/vexpress/arm-spc.c')
-rw-r--r--drivers/misc/vexpress/arm-spc.c371
1 files changed, 371 insertions, 0 deletions
diff --git a/drivers/misc/vexpress/arm-spc.c b/drivers/misc/vexpress/arm-spc.c
new file mode 100644
index 00000000000..23cee9df576
--- /dev/null
+++ b/drivers/misc/vexpress/arm-spc.c
@@ -0,0 +1,371 @@
+/*
+ * Serial Power Controller (SPC) support
+ *
+ * Copyright (C) 2012 ARM Ltd.
+ * Author(s): Sudeep KarkadaNagesha <sudeep.karkadanagesha@arm.com>
+ * Achin Gupta <achin.gupta@arm.com>
+ * Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/vexpress.h>
+
+#define SNOOP_CTL_A15 0x404
+#define SNOOP_CTL_A7 0x504
+#define PERF_LVL_A15 0xB00
+#define PERF_REQ_A15 0xB04
+#define PERF_LVL_A7 0xB08
+#define PERF_REQ_A7 0xB0c
+#define COMMS 0xB10
+#define COMMS_REQ 0xB14
+#define PWC_STATUS 0xB18
+#define PWC_FLAG 0xB1c
+#define WAKE_INT_MASK 0xB24
+#define WAKE_INT_RAW 0xB28
+#define WAKE_INT_STAT 0xB2c
+#define A15_PWRDN_EN 0xB30
+#define A7_PWRDN_EN 0xB34
+#define A15_A7_ISOLATE 0xB38
+#define STANDBYWFI_STAT 0xB3c
+#define A15_CACTIVE 0xB40
+#define A15_PWRDNREQ 0xB44
+#define A15_PWRDNACK 0xB48
+#define A7_CACTIVE 0xB4c
+#define A7_PWRDNREQ 0xB50
+#define A7_PWRDNACK 0xB54
+#define A15_RESET_HOLD 0xB58
+#define A7_RESET_HOLD 0xB5c
+#define A15_RESET_STAT 0xB60
+#define A7_RESET_STAT 0xB64
+#define A15_CONF 0x400
+#define A7_CONF 0x500
+
+#define DRIVER_NAME "SPC"
+#define TIME_OUT 100
+
+struct vexpress_spc_drvdata {
+ void __iomem *baseaddr;
+ spinlock_t lock;
+};
+
+static struct vexpress_spc_drvdata *info;
+
+/* SCC virtual address */
+u32 vscc;
+
+static inline int read_wait_to(void __iomem *reg, int status, int timeout)
+{
+ while (timeout-- && readl(reg) == status) {
+ cpu_relax();
+ udelay(2);
+ }
+ if (!timeout)
+ return -EAGAIN;
+ else
+ return 0;
+}
+
+int vexpress_spc_get_performance(int cluster, int *perf)
+{
+ u32 perf_cfg_reg = 0;
+ u32 a15_clusid = 0;
+ int ret = 0;
+
+ if (IS_ERR_OR_NULL(info))
+ return -ENXIO;
+
+ a15_clusid = readl_relaxed(info->baseaddr + A15_CONF) & 0xf;
+ perf_cfg_reg = cluster != a15_clusid ? PERF_LVL_A7 : PERF_LVL_A15;
+
+ spin_lock(&info->lock);
+ *perf = readl(info->baseaddr + perf_cfg_reg);
+ spin_unlock(&info->lock);
+
+ return ret;
+
+}
+EXPORT_SYMBOL_GPL(vexpress_spc_get_performance);
+
+int vexpress_spc_set_performance(int cluster, int perf)
+{
+ u32 perf_cfg_reg = 0;
+ u32 perf_stat_reg = 0;
+ u32 a15_clusid = 0;
+ int ret = 0;
+
+ if (IS_ERR_OR_NULL(info))
+ return -ENXIO;
+
+ a15_clusid = readl_relaxed(info->baseaddr + A15_CONF) & 0xf;
+ perf_cfg_reg = cluster != a15_clusid ? PERF_LVL_A7 : PERF_LVL_A15;
+ perf_stat_reg = cluster != a15_clusid ? PERF_REQ_A7 : PERF_REQ_A15;
+
+ if (perf < 0 || perf > 7)
+ return -EINVAL;
+
+ spin_lock(&info->lock);
+ writel(perf, info->baseaddr + perf_cfg_reg);
+ if (read_wait_to(info->baseaddr + perf_stat_reg, 1, TIME_OUT))
+ ret = -EAGAIN;
+ spin_unlock(&info->lock);
+ return ret;
+
+}
+EXPORT_SYMBOL_GPL(vexpress_spc_set_performance);
+
+void vexpress_spc_set_wake_intr(u32 mask)
+{
+ if (!IS_ERR_OR_NULL(info))
+ writel(mask & VEXPRESS_SPC_WAKE_INTR_MASK,
+ info->baseaddr + WAKE_INT_MASK);
+ return;
+}
+EXPORT_SYMBOL_GPL(vexpress_spc_set_wake_intr);
+
+u32 vexpress_spc_get_wake_intr(int raw)
+{
+ u32 wake_intr_reg = raw ? WAKE_INT_RAW : WAKE_INT_STAT;
+
+ if (!IS_ERR_OR_NULL(info))
+ return readl(info->baseaddr + wake_intr_reg);
+ else
+ return 0;
+}
+EXPORT_SYMBOL_GPL(vexpress_spc_get_wake_intr);
+
+void vexpress_spc_powerdown_enable(int cluster, int enable)
+{
+ u32 pwdrn_reg = 0;
+ u32 a15_clusid = 0;
+
+ if (!IS_ERR_OR_NULL(info)) {
+ a15_clusid = readl_relaxed(info->baseaddr + A15_CONF) & 0xf;
+ pwdrn_reg = cluster != a15_clusid ? A7_PWRDN_EN : A15_PWRDN_EN;
+ writel(!!enable, info->baseaddr + pwdrn_reg);
+ }
+ return;
+}
+EXPORT_SYMBOL_GPL(vexpress_spc_powerdown_enable);
+
+void vexpress_spc_adb400_pd_enable(int cluster, int enable)
+{
+ u32 pwdrn_reg = 0;
+ u32 a15_clusid = 0;
+ u32 val = enable ? 0xF : 0x0; /* all adb bridges ?? */
+
+ if (IS_ERR_OR_NULL(info))
+ return;
+
+ a15_clusid = readl_relaxed(info->baseaddr + A15_CONF) & 0xf;
+ pwdrn_reg = cluster != a15_clusid ? A7_PWRDNREQ : A15_PWRDNREQ;
+
+ spin_lock(&info->lock);
+ writel(val, info->baseaddr + pwdrn_reg);
+ spin_unlock(&info->lock);
+ return;
+}
+EXPORT_SYMBOL_GPL(vexpress_spc_adb400_pd_enable);
+
+void vexpress_scc_ctl_snoops(int cluster, int enable)
+{
+ u32 val;
+ u32 snoop_reg = 0;
+ u32 a15_clusid = 0;
+ u32 or = 0;
+
+ if (IS_ERR_OR_NULL(info))
+ return;
+
+ a15_clusid = readl_relaxed(info->baseaddr + A15_CONF) & 0xf;
+ snoop_reg = cluster != a15_clusid ? SNOOP_CTL_A7 : SNOOP_CTL_A15;
+ or = cluster != a15_clusid ? 0x2000 : 0x180;
+
+ val = readl_relaxed(info->baseaddr + snoop_reg);
+ if (enable) {
+ or = ~or;
+ val &= or;
+ } else {
+ val |= or;
+ }
+ writel_relaxed(val, info->baseaddr + snoop_reg);
+}
+EXPORT_SYMBOL_GPL(vexpress_scc_ctl_snoops);
+
+void vexpress_spc_wfi_cpureset(int cluster, int cpu, int enable)
+{
+ u32 rsthold_reg, prst_shift;
+ u32 val;
+ u32 a15_clusid = 0;
+
+ if (IS_ERR_OR_NULL(info))
+ return;
+
+ a15_clusid = readl_relaxed(info->baseaddr + A15_CONF) & 0xf;
+
+ if (cluster != a15_clusid) {
+ rsthold_reg = A7_RESET_HOLD;
+ prst_shift = 3;
+ } else {
+ rsthold_reg = A15_RESET_HOLD;
+ prst_shift = 2;
+ }
+ val = readl_relaxed(info->baseaddr + rsthold_reg);
+ if (enable)
+ val |= (1 << cpu);
+ else
+ val &= ~(1 << cpu);
+ writel_relaxed(val, info->baseaddr + rsthold_reg);
+ return;
+}
+EXPORT_SYMBOL_GPL(vexpress_spc_wfi_cpureset);
+
+void vexpress_spc_wfi_cluster_reset(int cluster, int enable)
+{
+ u32 rsthold_reg, shift;
+ u32 val;
+ u32 a15_clusid = 0;
+
+ if (IS_ERR_OR_NULL(info))
+ return;
+
+ a15_clusid = readl_relaxed(info->baseaddr + A15_CONF) & 0xf;
+
+ if (cluster != a15_clusid) {
+ rsthold_reg = A7_RESET_HOLD;
+ shift = 6;
+ } else {
+ rsthold_reg = A15_RESET_HOLD;
+ shift = 4;
+ }
+ spin_lock(&info->lock);
+ val = readl(info->baseaddr + rsthold_reg);
+ if (enable)
+ val |= 1 << shift;
+ else
+ val &= ~(1 << shift);
+ writel(val, info->baseaddr + rsthold_reg);
+ spin_unlock(&info->lock);
+ return;
+}
+EXPORT_SYMBOL_GPL(vexpress_spc_wfi_cluster_reset);
+
+int vexpress_spc_wfi_cpustat(int cluster)
+{
+ u32 rststat_reg;
+ u32 val;
+ u32 a15_clusid = 0;
+
+ if (IS_ERR_OR_NULL(info))
+ return 0;
+
+ a15_clusid = readl_relaxed(info->baseaddr + A15_CONF) & 0xf;
+ rststat_reg = STANDBYWFI_STAT;
+
+ val = readl_relaxed(info->baseaddr + rststat_reg);
+ return cluster != a15_clusid ? ((val & 0x38) >> 3) : (val & 0x3);
+}
+EXPORT_SYMBOL_GPL(vexpress_spc_wfi_cpustat);
+
+
+static int __devinit vexpress_spc_driver_probe(struct platform_device *pdev)
+{
+ struct resource *res;
+ int ret = 0;
+
+ info = kzalloc(sizeof(*info), GFP_KERNEL);
+ if (!info) {
+ dev_err(&pdev->dev, "unable to allocate mem\n");
+ return -ENOMEM;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "No memory resource\n");
+ ret = -EINVAL;
+ goto mem_free;
+ }
+
+ if (!request_mem_region(res->start, resource_size(res),
+ dev_name(&pdev->dev))) {
+ dev_err(&pdev->dev, "address 0x%x in use\n", (u32) res->start);
+ ret = -EBUSY;
+ goto mem_free;
+ }
+
+ info->baseaddr = ioremap(res->start, resource_size(res));
+ if (!info->baseaddr) {
+ ret = -ENXIO;
+ goto ioremap_err;
+ }
+ vscc = (u32) info->baseaddr;
+ spin_lock_init(&info->lock);
+ platform_set_drvdata(pdev, info);
+
+ pr_info("vexpress_spc loaded at %p\n", info->baseaddr);
+ return ret;
+
+ioremap_err:
+ release_region(res->start, resource_size(res));
+mem_free:
+ kfree(info);
+
+ return ret;
+}
+
+static int __devexit vexpress_spc_driver_remove(struct platform_device *pdev)
+{
+ struct vexpress_spc_drvdata *info;
+ struct resource *res = pdev->resource;
+
+ info = platform_get_drvdata(pdev);
+ iounmap(info->baseaddr);
+ release_region(res->start, resource_size(res));
+ kfree(info);
+
+ return 0;
+}
+
+static const struct of_device_id arm_vexpress_spc_matches[] = {
+ {.compatible = "arm,spc"},
+ {},
+};
+
+static struct platform_driver vexpress_spc_platform_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = DRIVER_NAME,
+ .of_match_table = arm_vexpress_spc_matches,
+ },
+ .probe = vexpress_spc_driver_probe,
+ .remove = vexpress_spc_driver_remove,
+};
+
+static int __init vexpress_spc_init(void)
+{
+ return platform_driver_register(&vexpress_spc_platform_driver);
+}
+
+static void __exit vexpress_spc_exit(void)
+{
+ platform_driver_unregister(&vexpress_spc_platform_driver);
+}
+
+arch_initcall(vexpress_spc_init);
+module_exit(vexpress_spc_exit);
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Serial Power Controller (SPC) support");