From 9823c7f5861b4101aaf068ebdb062ed4c6899bb6 Mon Sep 17 00:00:00 2001 From: Lorenzo Pieralisi Date: Fri, 13 Jul 2012 15:30:06 +0100 Subject: drivers: vexpress: add drivers infrastructure This patch provides the new kernel infrastructure needed by versatile express boards. Drivers are moved to drivers/misc/vexpress directory, where Kconfig and Makefile are created. A coalesced include file is created to cater for all versatile express required function declarations. --- drivers/misc/Kconfig | 1 + drivers/misc/Makefile | 1 + drivers/misc/vexpress/Kconfig | 0 drivers/misc/vexpress/Makefile | 0 4 files changed, 2 insertions(+) create mode 100644 drivers/misc/vexpress/Kconfig create mode 100644 drivers/misc/vexpress/Makefile diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index e83fdfe0c8c..9e201ca28fb 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -519,4 +519,5 @@ source "drivers/misc/carma/Kconfig" source "drivers/misc/altera-stapl/Kconfig" source "drivers/misc/mei/Kconfig" source "drivers/misc/vmw_vmci/Kconfig" +source "drivers/misc/vexpress/Kconfig" endmenu diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 35a1463c72d..2ff81f2b462 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -52,3 +52,4 @@ obj-$(CONFIG_INTEL_MEI) += mei/ obj-$(CONFIG_MAX8997_MUIC) += max8997-muic.o obj-$(CONFIG_VMWARE_VMCI) += vmw_vmci/ obj-$(CONFIG_LATTICE_ECP3_CONFIG) += lattice-ecp3-config.o +obj-$(CONFIG_ARCH_VEXPRESS) += vexpress/ diff --git a/drivers/misc/vexpress/Kconfig b/drivers/misc/vexpress/Kconfig new file mode 100644 index 00000000000..e69de29bb2d diff --git a/drivers/misc/vexpress/Makefile b/drivers/misc/vexpress/Makefile new file mode 100644 index 00000000000..e69de29bb2d -- cgit v1.2.3 From 7fb3e6503a5a7f79c703db69d928d502ce28bd5b Mon Sep 17 00:00:00 2001 From: Lorenzo Pieralisi Date: Fri, 13 Jul 2012 15:45:58 +0100 Subject: drivers: misc: vexpress: add SPC support The TC2 core tile integrates a logic block that provides the interface between the dual cluster test-chip and the M3 microcontroller that carries out power management. The logic block, called SPC, contains several memory mapped registers to control among other things low-power states, operating points and reset control. This patch provides a driver that enables run-time control of features implemented by the SPC control logic. Signed-off-by: Achin Gupta Signed-off-by: Lorenzo Pieralisi Signed-off-by: Sudeep KarkadaNagesha --- drivers/misc/vexpress/Kconfig | 2 + drivers/misc/vexpress/Makefile | 1 + drivers/misc/vexpress/arm-spc.c | 371 ++++++++++++++++++++++++++++++++++++++++ include/linux/vexpress.h | 41 +++++ 4 files changed, 415 insertions(+) create mode 100644 drivers/misc/vexpress/arm-spc.c diff --git a/drivers/misc/vexpress/Kconfig b/drivers/misc/vexpress/Kconfig index e69de29bb2d..8fb52f640bf 100644 --- a/drivers/misc/vexpress/Kconfig +++ b/drivers/misc/vexpress/Kconfig @@ -0,0 +1,2 @@ +config ARM_SPC + bool "ARM SPC driver support" diff --git a/drivers/misc/vexpress/Makefile b/drivers/misc/vexpress/Makefile index e69de29bb2d..95b58166d0a 100644 --- a/drivers/misc/vexpress/Makefile +++ b/drivers/misc/vexpress/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_ARM_SPC) += arm-spc.o 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 + * Achin Gupta + * Lorenzo Pieralisi + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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"); diff --git a/include/linux/vexpress.h b/include/linux/vexpress.h index 75818744ab5..df276d25fcf 100644 --- a/include/linux/vexpress.h +++ b/include/linux/vexpress.h @@ -126,4 +126,45 @@ void vexpress_osc_of_setup(struct device_node *node); void vexpress_clk_init(void __iomem *sp810_base); void vexpress_clk_of_init(void); +/* SPC */ + +#define VEXPRESS_SPC_WAKE_INTR_IRQ(cluster, cpu) \ + (1 << (4 * (cluster) + (cpu))) +#define VEXPRESS_SPC_WAKE_INTR_FIQ(cluster, cpu) \ + (1 << (7 * (cluster) + (cpu))) +#define VEXPRESS_SPC_WAKE_INTR_SWDOG (1 << 10) +#define VEXPRESS_SPC_WAKE_INTR_GTIMER (1 << 11) +#define VEXPRESS_SPC_WAKE_INTR_MASK 0xFFF + +#ifdef CONFIG_ARM_SPC +extern int vexpress_spc_get_performance(int cluster, int *perf); +extern int vexpress_spc_set_performance(int cluster, int perf); +extern void vexpress_spc_set_wake_intr(u32 mask); +extern u32 vexpress_spc_get_wake_intr(int raw); +extern void vexpress_spc_powerdown_enable(int cluster, int enable); +extern void vexpress_spc_adb400_pd_enable(int cluster, int enable); +extern void vexpress_spc_wfi_cpureset(int cluster, int cpu, int enable); +extern int vexpress_spc_wfi_cpustat(int cluster); +extern void vexpress_spc_wfi_cluster_reset(int cluster, int enable); +extern void vexpress_scc_ctl_snoops(int cluster, int enable); +#else +static inline int vexpress_spc_get_performance(int cluster, int *perf) +{ + return -EINVAL; +} +static inline int vexpress_spc_set_performance(int cluster, int perf) +{ + return -EINVAL; +} +static inline void vexpress_spc_set_wake_intr(u32 mask) { } +static inline u32 vexpress_spc_get_wake_intr(int raw) { return 0; } +static inline void vexpress_spc_powerdown_enable(int cluster, int enable) { } +static inline void vexpress_spc_adb400_pd_enable(int cluster, int enable) { } +static inline void vexpress_spc_wfi_cpureset(int cluster, int cpu, int enable) +{ } +static inline int vexpress_spc_wfi_cpustat(int cluster) { return 0; } +static inline void vexpress_spc_wfi_cluster_reset(int cluster, int enable) { } +static inline void vexpress_scc_ctl_snoops(int cluster, int enable) { } +#endif + #endif -- cgit v1.2.3 From 0cb6b75d8bd32750432a9c2ac544321b3fa2d894 Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Wed, 25 Jul 2012 15:01:40 +0100 Subject: drivers: arm-spc: Add function for testing if SPC driver is loaded To enable drivers which use SPC functions to safely exist in kernels which are run on hardware without SPC hardware, they need a method of detecting its presence. The new function vexpress_spc_check_loaded() provides this. Signed-off-by: Jon Medhurst --- drivers/misc/vexpress/arm-spc.c | 8 ++++++++ include/linux/vexpress.h | 5 +++++ 2 files changed, 13 insertions(+) diff --git a/drivers/misc/vexpress/arm-spc.c b/drivers/misc/vexpress/arm-spc.c index 23cee9df576..6453d5b0ac5 100644 --- a/drivers/misc/vexpress/arm-spc.c +++ b/drivers/misc/vexpress/arm-spc.c @@ -281,6 +281,13 @@ int vexpress_spc_wfi_cpustat(int cluster) } EXPORT_SYMBOL_GPL(vexpress_spc_wfi_cpustat); +static bool vexpress_spc_loaded; + +bool vexpress_spc_check_loaded(void) +{ + return vexpress_spc_loaded; +} +EXPORT_SYMBOL_GPL(vexpress_spc_check_loaded); static int __devinit vexpress_spc_driver_probe(struct platform_device *pdev) { @@ -317,6 +324,7 @@ static int __devinit vexpress_spc_driver_probe(struct platform_device *pdev) platform_set_drvdata(pdev, info); pr_info("vexpress_spc loaded at %p\n", info->baseaddr); + vexpress_spc_loaded = true; return ret; ioremap_err: diff --git a/include/linux/vexpress.h b/include/linux/vexpress.h index df276d25fcf..e75f0a3c511 100644 --- a/include/linux/vexpress.h +++ b/include/linux/vexpress.h @@ -146,6 +146,7 @@ extern void vexpress_spc_adb400_pd_enable(int cluster, int enable); extern void vexpress_spc_wfi_cpureset(int cluster, int cpu, int enable); extern int vexpress_spc_wfi_cpustat(int cluster); extern void vexpress_spc_wfi_cluster_reset(int cluster, int enable); +extern bool vexpress_spc_check_loaded(void); extern void vexpress_scc_ctl_snoops(int cluster, int enable); #else static inline int vexpress_spc_get_performance(int cluster, int *perf) @@ -164,6 +165,10 @@ static inline void vexpress_spc_wfi_cpureset(int cluster, int cpu, int enable) { } static inline int vexpress_spc_wfi_cpustat(int cluster) { return 0; } static inline void vexpress_spc_wfi_cluster_reset(int cluster, int enable) { } +static inline bool vexpress_spc_check_loaded(void) +{ + return false; +} static inline void vexpress_scc_ctl_snoops(int cluster, int enable) { } #endif -- cgit v1.2.3 From 9ed7d1a299474c62124c5478f70f86c525914cf1 Mon Sep 17 00:00:00 2001 From: Dave Martin Date: Mon, 19 Nov 2012 12:01:20 +0000 Subject: ARM: TC2: ensure powerdown-time data is flushed from cache Non-local variables used by the CCI and SPC management functions called by tc2_pm_power_down() after disabling the cache must be flushed out to main memory in advance, otherwise incoherency of those values may occur if they are sitting in the cache of some other CPU when tc2_pm_power_down() executes. This patch adds the appropriate flushing to the CCI and SPC drivers to ensure that the relevant data is available in RAM ahead of time. Because this creates a dependency on arch-specific cacheflushing functions, this patch also makes ARM_CCI and ARM_SPC depend on ARM (pending a proper tidyup of those drivers). Signed-off-by: Dave Martin --- drivers/misc/vexpress/Kconfig | 1 + drivers/misc/vexpress/arm-spc.c | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/drivers/misc/vexpress/Kconfig b/drivers/misc/vexpress/Kconfig index 8fb52f640bf..3e2676ae6ee 100644 --- a/drivers/misc/vexpress/Kconfig +++ b/drivers/misc/vexpress/Kconfig @@ -1,2 +1,3 @@ config ARM_SPC bool "ARM SPC driver support" + depends on ARM diff --git a/drivers/misc/vexpress/arm-spc.c b/drivers/misc/vexpress/arm-spc.c index 6453d5b0ac5..6e2e4820c3a 100644 --- a/drivers/misc/vexpress/arm-spc.c +++ b/drivers/misc/vexpress/arm-spc.c @@ -25,6 +25,10 @@ #include #include +#include +#include +#include + #define SNOOP_CTL_A15 0x404 #define SNOOP_CTL_A7 0x504 #define PERF_LVL_A15 0xB00 @@ -323,6 +327,13 @@ static int __devinit vexpress_spc_driver_probe(struct platform_device *pdev) spin_lock_init(&info->lock); platform_set_drvdata(pdev, info); + /* + * Multi-cluster systems may need this data when non-coherent, during + * cluster power-up/power-down. Make sure it reaches main memory: + */ + __cpuc_flush_dcache_area(info, sizeof *info); + outer_clean_range(virt_to_phys(info), virt_to_phys(info + 1)); + pr_info("vexpress_spc loaded at %p\n", info->baseaddr); vexpress_spc_loaded = true; return ret; -- cgit v1.2.3 From 3fb50a7f5e44d850aa6c46b5755bc2b9767184d7 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Fri, 19 Oct 2012 21:15:17 -0400 Subject: ARM: bulk changes to the spc driver Picked wholesale in the IKS tree from ARM. --- drivers/misc/vexpress/arm-spc.c | 292 ++++++++++++++++++++++++++++++++++++---- include/linux/vexpress.h | 66 ++++++++- 2 files changed, 331 insertions(+), 27 deletions(-) diff --git a/drivers/misc/vexpress/arm-spc.c b/drivers/misc/vexpress/arm-spc.c index 6e2e4820c3a..19d681cd594 100644 --- a/drivers/misc/vexpress/arm-spc.c +++ b/drivers/misc/vexpress/arm-spc.c @@ -19,10 +19,11 @@ #include #include #include +#include #include #include #include -#include +#include #include #include @@ -58,13 +59,27 @@ #define A7_RESET_STAT 0xB64 #define A15_CONF 0x400 #define A7_CONF 0x500 +#define SYS_INFO 0x700 +#define SCC_CFGREG6 0x018 + +#define A15_STANDBYWFIL2_MSK (1 << 2) +#define A7_STANDBYWFIL2_MSK (1 << 6) +#define GBL_WAKEUP_INT_MSK (0x3 << 10) + +#define A15_PART_NO 0xF +#define A7_PART_NO 0x7 + +#define A15_BX_ADDR0 0xB68 +#define A7_BX_ADDR0 0xB78 #define DRIVER_NAME "SPC" -#define TIME_OUT 100 +#define TIME_OUT_US 3000 struct vexpress_spc_drvdata { void __iomem *baseaddr; - spinlock_t lock; + struct semaphore lock; + int irq; + struct completion done; }; static struct vexpress_spc_drvdata *info; @@ -72,23 +87,150 @@ static struct vexpress_spc_drvdata *info; /* SCC virtual address */ u32 vscc; -static inline int read_wait_to(void __iomem *reg, int status, int timeout) +u32 vexpress_spc_get_clusterid(int cpu_part_no) { - while (timeout-- && readl(reg) == status) { - cpu_relax(); - udelay(2); + switch (cpu_part_no & 0xf) { + case A15_PART_NO: + return readl_relaxed(info->baseaddr + A15_CONF) & 0xf; + case A7_PART_NO: + return readl_relaxed(info->baseaddr + A7_CONF) & 0xf; + default: + BUG(); } - if (!timeout) - return -EAGAIN; +} + +EXPORT_SYMBOL_GPL(vexpress_spc_get_clusterid); + +void vexpress_spc_write_bxaddr_reg(int cluster, int cpu, u32 val) +{ + u32 a15_clusid; + + if (IS_ERR_OR_NULL(info)) + return; + + a15_clusid = readl_relaxed(info->baseaddr + A15_CONF) & 0xf; + + if (cluster != a15_clusid) + writel_relaxed(val, info->baseaddr + A7_BX_ADDR0 + (cpu << 2)); else - return 0; + writel_relaxed(val, info->baseaddr + A15_BX_ADDR0 + (cpu << 2)); + + dsb(); + return; +} + +EXPORT_SYMBOL_GPL(vexpress_spc_write_bxaddr_reg); + +int vexpress_spc_get_nb_cpus(int cluster) +{ + u32 a15_clusid; + u32 val; + + if (IS_ERR_OR_NULL(info)) + return -ENXIO; + + a15_clusid = readl_relaxed(info->baseaddr + A15_CONF) & 0xf; + val = readl_relaxed(info->baseaddr + SYS_INFO); + val = (cluster != a15_clusid) ? (val >> 20) : (val >> 16); + + return (val & 0xf); +} + +EXPORT_SYMBOL_GPL(vexpress_spc_get_nb_cpus); + +int vexpress_spc_standbywfil2_status(int cluster) +{ + u32 standbywfi_stat; + u32 a15_clusid; + + if (IS_ERR_OR_NULL(info)) + BUG(); + + a15_clusid = readl_relaxed(info->baseaddr + A15_CONF) & 0xf; + standbywfi_stat = readl_relaxed(info->baseaddr + STANDBYWFI_STAT); + + if (cluster != a15_clusid) + return standbywfi_stat & A7_STANDBYWFIL2_MSK; + else + return standbywfi_stat & A15_STANDBYWFIL2_MSK; +} + +EXPORT_SYMBOL_GPL(vexpress_spc_standbywfil2_status); + +int vexpress_spc_standbywfi_status(int cluster, int cpu) +{ + u32 a15_clusid; + u32 standbywfi_stat; + + if (IS_ERR_OR_NULL(info)) + BUG(); + + a15_clusid = readl_relaxed(info->baseaddr + A15_CONF) & 0xf; + standbywfi_stat = readl_relaxed(info->baseaddr + STANDBYWFI_STAT); + + if (cluster != a15_clusid) + return standbywfi_stat & ((1 << cpu) << 3); + else + return standbywfi_stat & (1 << cpu); +} + +EXPORT_SYMBOL_GPL(vexpress_spc_standbywfi_status); + +u32 vexpress_spc_read_rststat_reg(int cluster) +{ + u32 a15_clusid = 0; + + if (IS_ERR_OR_NULL(info)) + BUG(); + + a15_clusid = readl_relaxed(info->baseaddr + A15_CONF) & 0xf; + + if (cluster != a15_clusid) + return readl_relaxed(info->baseaddr + A7_RESET_STAT); + else + return readl_relaxed(info->baseaddr + A15_RESET_STAT); +} + +EXPORT_SYMBOL_GPL(vexpress_spc_read_rststat_reg); + +u32 vexpress_spc_read_rsthold_reg(int cluster) +{ + u32 a15_clusid = 0; + + if (IS_ERR_OR_NULL(info)) + BUG(); + + a15_clusid = readl_relaxed(info->baseaddr + A15_CONF) & 0xf; + + if (cluster != a15_clusid) + return readl_relaxed(info->baseaddr + A7_RESET_HOLD); + else + return readl_relaxed(info->baseaddr + A15_RESET_HOLD); } +EXPORT_SYMBOL_GPL(vexpress_spc_read_rsthold_reg); + +void vexpress_spc_write_rsthold_reg(int cluster, u32 value) +{ + u32 a15_clusid = 0; + + if (IS_ERR_OR_NULL(info)) + BUG(); + + a15_clusid = readl_relaxed(info->baseaddr + A15_CONF) & 0xf; + + if (cluster != a15_clusid) + writel_relaxed(value, info->baseaddr + A7_RESET_HOLD); + else + writel_relaxed(value, info->baseaddr + A15_RESET_HOLD); +} + +EXPORT_SYMBOL_GPL(vexpress_spc_write_rsthold_reg); + 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; @@ -96,11 +238,12 @@ int vexpress_spc_get_performance(int cluster, int *perf) 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); + if (down_timeout(&info->lock, usecs_to_jiffies(TIME_OUT_US))) + return -ETIME; *perf = readl(info->baseaddr + perf_cfg_reg); - spin_unlock(&info->lock); + up(&info->lock); - return ret; + return 0; } EXPORT_SYMBOL_GPL(vexpress_spc_get_performance); @@ -115,6 +258,8 @@ int vexpress_spc_set_performance(int cluster, int perf) if (IS_ERR_OR_NULL(info)) return -ENXIO; + init_completion(&info->done); + 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; @@ -122,16 +267,65 @@ int vexpress_spc_set_performance(int cluster, int perf) if (perf < 0 || perf > 7) return -EINVAL; - spin_lock(&info->lock); + if (down_timeout(&info->lock, usecs_to_jiffies(TIME_OUT_US))) + return -ETIME; 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); + + if (!wait_for_completion_interruptible_timeout(&info->done, + usecs_to_jiffies(TIME_OUT_US))) { + ret = -ETIMEDOUT; + } + + up(&info->lock); return ret; } EXPORT_SYMBOL_GPL(vexpress_spc_set_performance); +int vexpress_spc_set_global_wakeup_intr(u32 set) +{ + u32 wake_int_mask_reg = 0; + + if (IS_ERR_OR_NULL(info)) + return -ENXIO; + + wake_int_mask_reg = readl(info->baseaddr + WAKE_INT_MASK); + if (set) + wake_int_mask_reg |= GBL_WAKEUP_INT_MSK; + else + wake_int_mask_reg &= ~GBL_WAKEUP_INT_MSK; + + vexpress_spc_set_wake_intr(wake_int_mask_reg); + + return 0; +} +EXPORT_SYMBOL_GPL(vexpress_spc_set_global_wakeup_intr); + +int vexpress_spc_set_cpu_wakeup_irq(u32 cpu, u32 cluster, u32 set) +{ + u32 a15_clusid = 0, mask = 0; + u32 wake_int_mask_reg = 0; + + if (IS_ERR_OR_NULL(info)) + return -ENXIO; + + a15_clusid = readl_relaxed(info->baseaddr + A15_CONF) & 0xf; + mask = 1 << cpu; + if (a15_clusid != cluster) + mask <<= 4; + + wake_int_mask_reg = readl(info->baseaddr + WAKE_INT_MASK); + if (set) + wake_int_mask_reg |= mask; + else + wake_int_mask_reg &= ~mask; + + vexpress_spc_set_wake_intr(wake_int_mask_reg); + + return 0; +} +EXPORT_SYMBOL_GPL(vexpress_spc_set_cpu_wakeup_irq); + void vexpress_spc_set_wake_intr(u32 mask) { if (!IS_ERR_OR_NULL(info)) @@ -178,9 +372,7 @@ void vexpress_spc_adb400_pd_enable(int cluster, int enable) 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); @@ -205,11 +397,30 @@ void vexpress_scc_ctl_snoops(int cluster, int enable) val &= or; } else { val |= or; + dsb(); + isb(); } + writel_relaxed(val, info->baseaddr + snoop_reg); } EXPORT_SYMBOL_GPL(vexpress_scc_ctl_snoops); +u32 vexpress_scc_read_rststat(int cluster) +{ + u32 a15_clusid = 0; + + if (IS_ERR_OR_NULL(info)) + BUG(); + + a15_clusid = readl_relaxed(info->baseaddr + A15_CONF) & 0xf; + + if (cluster != a15_clusid) + return (readl_relaxed(info->baseaddr + SCC_CFGREG6) >> 16) & 0x7; + else + return (readl_relaxed(info->baseaddr + SCC_CFGREG6) >> 2) & 0x3; +} +EXPORT_SYMBOL_GPL(vexpress_scc_read_rststat); + void vexpress_spc_wfi_cpureset(int cluster, int cpu, int enable) { u32 rsthold_reg, prst_shift; @@ -256,14 +467,12 @@ void vexpress_spc_wfi_cluster_reset(int cluster, int enable) 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); @@ -293,9 +502,20 @@ bool vexpress_spc_check_loaded(void) } EXPORT_SYMBOL_GPL(vexpress_spc_check_loaded); +irqreturn_t vexpress_spc_irq_handler(int irq, void *data) +{ + struct vexpress_spc_drvdata *drv_data = data; + + readl_relaxed(drv_data->baseaddr + PWC_STATUS); + + complete(&drv_data->done); + + return IRQ_HANDLED; +} + static int __devinit vexpress_spc_driver_probe(struct platform_device *pdev) { - struct resource *res; + struct resource *res, *irq_res; int ret = 0; info = kzalloc(sizeof(*info), GFP_KERNEL); @@ -324,7 +544,28 @@ static int __devinit vexpress_spc_driver_probe(struct platform_device *pdev) goto ioremap_err; } vscc = (u32) info->baseaddr; - spin_lock_init(&info->lock); + sema_init(&info->lock, 1); + + irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!irq_res) { + dev_err(&pdev->dev, "No interrupt resource\n"); + ret = -EINVAL; + goto irq_err; + } + info->irq = irq_res->start; + + init_completion(&info->done); + + readl_relaxed(info->baseaddr + PWC_STATUS); + + ret = request_irq(info->irq, vexpress_spc_irq_handler, + IRQF_DISABLED | IRQF_TRIGGER_HIGH | IRQF_ONESHOT, "arm-spc", info); + if (ret) { + dev_err(&pdev->dev, "IRQ %d request failed \n", info->irq); + ret = -ENODEV; + goto irq_err; + } + platform_set_drvdata(pdev, info); /* @@ -338,6 +579,8 @@ static int __devinit vexpress_spc_driver_probe(struct platform_device *pdev) vexpress_spc_loaded = true; return ret; +irq_err: + iounmap(info->baseaddr); ioremap_err: release_region(res->start, resource_size(res)); mem_free: @@ -352,6 +595,7 @@ static int __devexit vexpress_spc_driver_remove(struct platform_device *pdev) struct resource *res = pdev->resource; info = platform_get_drvdata(pdev); + free_irq(info->irq, info); iounmap(info->baseaddr); release_region(res->start, resource_size(res)); kfree(info); diff --git a/include/linux/vexpress.h b/include/linux/vexpress.h index e75f0a3c511..977535236db 100644 --- a/include/linux/vexpress.h +++ b/include/linux/vexpress.h @@ -137,18 +137,78 @@ void vexpress_clk_of_init(void); #define VEXPRESS_SPC_WAKE_INTR_MASK 0xFFF #ifdef CONFIG_ARM_SPC + +extern u32 vexpress_spc_get_clusterid(int cpu_part_no); +extern u32 vexpress_spc_read_rsthold_reg(int cluster); +extern u32 vexpress_spc_read_rststat_reg(int cluster); +extern u32 vexpress_scc_read_rststat(int cluster); +extern u32 vexpress_spc_get_wake_intr(int raw); +extern int vexpress_spc_standbywfi_status(int cluster, int cpu); +extern int vexpress_spc_standbywfil2_status(int cluster); +extern int vexpress_spc_set_cpu_wakeup_irq(u32 cpu, u32 cluster, u32 set); +extern int vexpress_spc_set_global_wakeup_intr(u32 set); extern int vexpress_spc_get_performance(int cluster, int *perf); extern int vexpress_spc_set_performance(int cluster, int perf); +extern int vexpress_spc_wfi_cpustat(int cluster); extern void vexpress_spc_set_wake_intr(u32 mask); -extern u32 vexpress_spc_get_wake_intr(int raw); +extern void vexpress_spc_write_bxaddr_reg(int cluster, int cpu, u32 val); +extern int vexpress_spc_get_nb_cpus(int cluster); +extern void vexpress_spc_write_rsthold_reg(int cluster, u32 value); extern void vexpress_spc_powerdown_enable(int cluster, int enable); extern void vexpress_spc_adb400_pd_enable(int cluster, int enable); extern void vexpress_spc_wfi_cpureset(int cluster, int cpu, int enable); -extern int vexpress_spc_wfi_cpustat(int cluster); extern void vexpress_spc_wfi_cluster_reset(int cluster, int enable); -extern bool vexpress_spc_check_loaded(void); extern void vexpress_scc_ctl_snoops(int cluster, int enable); +extern bool vexpress_spc_check_loaded(void); #else +static inline int vexpress_spc_set_cpu_wakeup_irq(u32 cpu, u32 cluster, u32 set) +{ + return 0; +} + +static inline int vexpress_spc_set_global_wakeup_intr(u32 set) +{ + return 0; +} + +static inline int vexpress_spc_standbywfi_status(int cluster, int cpu) +{ + return 0; +} + +static inline int vexpress_spc_standbywfil2_status(int cluster) +{ + return 0; +} + +static inline u32 vexpress_spc_get_clusterid(int cpu_part_no) +{ + return 0; +} + +static inline u32 vexpress_spc_read_rsthold_reg(int cluster) +{ + return 0; +} + +static inline u32 vexpress_spc_read_rststat_reg(int cluster) +{ + return 0; +} + +static inline void vexpress_spc_write_bxaddr_reg(int cluster, int cpu, u32 val) +{ +} + +static inline void vexpress_spc_write_rsthold_reg(int cluster, u32 value) +{ +} + +static inline u32 vexpress_scc_read_rststat(int cluster) +{ + return 0; +} + static inline int vexpress_spc_get_performance(int cluster, int *perf) { return -EINVAL; -- cgit v1.2.3 From 29333e85cdf92caaf99e0285e308e1629773d7d4 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Sun, 28 Oct 2012 16:35:36 -0400 Subject: ARM: spc: initialize completion block after locking the semaphore Otherwise any concurrent call could reset the completion block while it is being waited for by a previous call. Signed-off-by: Nicolas Pitre --- drivers/misc/vexpress/arm-spc.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/misc/vexpress/arm-spc.c b/drivers/misc/vexpress/arm-spc.c index 19d681cd594..31c10126ba0 100644 --- a/drivers/misc/vexpress/arm-spc.c +++ b/drivers/misc/vexpress/arm-spc.c @@ -258,8 +258,6 @@ int vexpress_spc_set_performance(int cluster, int perf) if (IS_ERR_OR_NULL(info)) return -ENXIO; - init_completion(&info->done); - 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; @@ -269,6 +267,9 @@ int vexpress_spc_set_performance(int cluster, int perf) if (down_timeout(&info->lock, usecs_to_jiffies(TIME_OUT_US))) return -ETIME; + + init_completion(&info->done); + writel(perf, info->baseaddr + perf_cfg_reg); if (!wait_for_completion_interruptible_timeout(&info->done, -- cgit v1.2.3 From 7db0862cfc0c6304042540377b2aa6a0fac17e77 Mon Sep 17 00:00:00 2001 From: Achin Gupta Date: Fri, 9 Nov 2012 09:16:38 +0000 Subject: drivers: misc: vexpress: Check writes to SPC registers have taken effect This patch ensures that the SPC registers written by the power api are read back to ensure that the changes have reflected. This is done to workaround any delays caused by the slow nature of the SPC interface e.g. the wfi signal being asserted to the SPC before the changes to the SPC registers have taken effect. --- drivers/misc/vexpress/arm-spc.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/drivers/misc/vexpress/arm-spc.c b/drivers/misc/vexpress/arm-spc.c index 31c10126ba0..965a41a87bf 100644 --- a/drivers/misc/vexpress/arm-spc.c +++ b/drivers/misc/vexpress/arm-spc.c @@ -104,6 +104,7 @@ EXPORT_SYMBOL_GPL(vexpress_spc_get_clusterid); void vexpress_spc_write_bxaddr_reg(int cluster, int cpu, u32 val) { u32 a15_clusid; + void __iomem *baseaddr; if (IS_ERR_OR_NULL(info)) return; @@ -111,11 +112,14 @@ void vexpress_spc_write_bxaddr_reg(int cluster, int cpu, u32 val) a15_clusid = readl_relaxed(info->baseaddr + A15_CONF) & 0xf; if (cluster != a15_clusid) - writel_relaxed(val, info->baseaddr + A7_BX_ADDR0 + (cpu << 2)); + baseaddr = info->baseaddr + A7_BX_ADDR0 + (cpu << 2); else - writel_relaxed(val, info->baseaddr + A15_BX_ADDR0 + (cpu << 2)); + baseaddr = info->baseaddr + A15_BX_ADDR0 + (cpu << 2); + writel_relaxed(val, baseaddr); dsb(); + while (val != readl_relaxed(baseaddr)); + return; } @@ -329,9 +333,14 @@ EXPORT_SYMBOL_GPL(vexpress_spc_set_cpu_wakeup_irq); void vexpress_spc_set_wake_intr(u32 mask) { - if (!IS_ERR_OR_NULL(info)) + if (!IS_ERR_OR_NULL(info)) { writel(mask & VEXPRESS_SPC_WAKE_INTR_MASK, - info->baseaddr + WAKE_INT_MASK); + info->baseaddr + WAKE_INT_MASK); + dsb(); + while ((mask & VEXPRESS_SPC_WAKE_INTR_MASK) != + readl(info->baseaddr + WAKE_INT_MASK)); + } + return; } EXPORT_SYMBOL_GPL(vexpress_spc_set_wake_intr); @@ -356,6 +365,8 @@ void vexpress_spc_powerdown_enable(int cluster, int enable) 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); + dsb(); + while (readl(info->baseaddr + pwdrn_reg) != !!enable); } return; } -- cgit v1.2.3 From 1e12456d6a9d57662f7cbdaa0d6397ff93cc39c1 Mon Sep 17 00:00:00 2001 From: Sudeep KarkadaNagesha Date: Fri, 9 Nov 2012 09:16:39 +0000 Subject: drivers: misc: vexpress: Enable use of SPC driver prior to secondary bringup This patch initializes the SPC driver before smp initialization is done. The driver is required to be able to use the power api to bring up secondary cpus. Hence its availability is mandatory at this stage. --- drivers/misc/vexpress/arm-spc.c | 111 +++++++++------------------------------- 1 file changed, 25 insertions(+), 86 deletions(-) diff --git a/drivers/misc/vexpress/arm-spc.c b/drivers/misc/vexpress/arm-spc.c index 965a41a87bf..2bc1be3d21b 100644 --- a/drivers/misc/vexpress/arm-spc.c +++ b/drivers/misc/vexpress/arm-spc.c @@ -21,7 +21,8 @@ #include #include #include -#include +#include +#include #include #include #include @@ -525,61 +526,47 @@ irqreturn_t vexpress_spc_irq_handler(int irq, void *data) return IRQ_HANDLED; } -static int __devinit vexpress_spc_driver_probe(struct platform_device *pdev) +static int __init vexpress_spc_early_init(void) { - struct resource *res, *irq_res; - int ret = 0; + struct device_node *node = of_find_compatible_node(NULL, NULL, + "arm,spc"); info = kzalloc(sizeof(*info), GFP_KERNEL); if (!info) { - dev_err(&pdev->dev, "unable to allocate mem\n"); + pr_err("%s: unable to allocate mem\n", __func__); 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 (node) + info->baseaddr = of_iomap(node, 0); - 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; + if (WARN_ON(!info->baseaddr)) { + kfree(info); + return -EIO; } - info->baseaddr = ioremap(res->start, resource_size(res)); - if (!info->baseaddr) { - ret = -ENXIO; - goto ioremap_err; - } vscc = (u32) info->baseaddr; sema_init(&info->lock, 1); - irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); - if (!irq_res) { - dev_err(&pdev->dev, "No interrupt resource\n"); - ret = -EINVAL; - goto irq_err; - } - info->irq = irq_res->start; + info->irq = irq_of_parse_and_map(node, 0); - init_completion(&info->done); + if (info->irq) { + int ret; + + init_completion(&info->done); - readl_relaxed(info->baseaddr + PWC_STATUS); + readl_relaxed(info->baseaddr + PWC_STATUS); - ret = request_irq(info->irq, vexpress_spc_irq_handler, + ret = request_irq(info->irq, vexpress_spc_irq_handler, IRQF_DISABLED | IRQF_TRIGGER_HIGH | IRQF_ONESHOT, "arm-spc", info); - if (ret) { - dev_err(&pdev->dev, "IRQ %d request failed \n", info->irq); - ret = -ENODEV; - goto irq_err; + if (ret) { + pr_err("IRQ %d request failed \n", info->irq); + iounmap(info->baseaddr); + kfree(info); + return -ENODEV; + } } - platform_set_drvdata(pdev, info); - /* * Multi-cluster systems may need this data when non-coherent, during * cluster power-up/power-down. Make sure it reaches main memory: @@ -589,58 +576,10 @@ static int __devinit vexpress_spc_driver_probe(struct platform_device *pdev) pr_info("vexpress_spc loaded at %p\n", info->baseaddr); vexpress_spc_loaded = true; - return ret; - -irq_err: - iounmap(info->baseaddr); -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); - free_irq(info->irq, info); - 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); +early_initcall(vexpress_spc_early_init); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Serial Power Controller (SPC) support"); -- cgit v1.2.3 From b05df044469bcd93b133f5144d7dda811e917cd6 Mon Sep 17 00:00:00 2001 From: Sudeep KarkadaNagesha Date: Fri, 30 Nov 2012 18:45:43 +0000 Subject: misc: vexpress/spc: organize the offsets in proper order The SCC/SPC offsets defined in spc driver are scattered a bit. Just reordering them for better readability. Signed-off-by: Sudeep KarkadaNagesha --- drivers/misc/vexpress/arm-spc.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/drivers/misc/vexpress/arm-spc.c b/drivers/misc/vexpress/arm-spc.c index 2bc1be3d21b..d9bebe7f1fb 100644 --- a/drivers/misc/vexpress/arm-spc.c +++ b/drivers/misc/vexpress/arm-spc.c @@ -31,8 +31,12 @@ #include #include +#define SCC_CFGREG6 0x018 +#define A15_CONF 0x400 #define SNOOP_CTL_A15 0x404 +#define A7_CONF 0x500 #define SNOOP_CTL_A7 0x504 +#define SYS_INFO 0x700 #define PERF_LVL_A15 0xB00 #define PERF_REQ_A15 0xB04 #define PERF_LVL_A7 0xB08 @@ -58,10 +62,8 @@ #define A7_RESET_HOLD 0xB5c #define A15_RESET_STAT 0xB60 #define A7_RESET_STAT 0xB64 -#define A15_CONF 0x400 -#define A7_CONF 0x500 -#define SYS_INFO 0x700 -#define SCC_CFGREG6 0x018 +#define A15_BX_ADDR0 0xB68 +#define A7_BX_ADDR0 0xB78 #define A15_STANDBYWFIL2_MSK (1 << 2) #define A7_STANDBYWFIL2_MSK (1 << 6) @@ -70,9 +72,6 @@ #define A15_PART_NO 0xF #define A7_PART_NO 0x7 -#define A15_BX_ADDR0 0xB68 -#define A7_BX_ADDR0 0xB78 - #define DRIVER_NAME "SPC" #define TIME_OUT_US 3000 -- cgit v1.2.3 From 2e294c2481d3e5245f93078b5fa2aa85bfaf89a2 Mon Sep 17 00:00:00 2001 From: Sudeep KarkadaNagesha Date: Fri, 30 Nov 2012 18:45:44 +0000 Subject: misc: vexpress/spc: avoid reading A15_CONF everytime to get A15 cluster id In order to compare the given cluster id with A15, the A15_CONF is read everytime. Instead read the A15 cluster id once during initialisation and store the same for all future references. Signed-off-by: Sudeep KarkadaNagesha --- drivers/misc/vexpress/arm-spc.c | 85 +++++++++++------------------------------ 1 file changed, 23 insertions(+), 62 deletions(-) diff --git a/drivers/misc/vexpress/arm-spc.c b/drivers/misc/vexpress/arm-spc.c index d9bebe7f1fb..bc2ed8d9433 100644 --- a/drivers/misc/vexpress/arm-spc.c +++ b/drivers/misc/vexpress/arm-spc.c @@ -77,8 +77,9 @@ struct vexpress_spc_drvdata { void __iomem *baseaddr; - struct semaphore lock; + uint32_t a15_clusid; int irq; + struct semaphore lock; struct completion done; }; @@ -103,15 +104,12 @@ EXPORT_SYMBOL_GPL(vexpress_spc_get_clusterid); void vexpress_spc_write_bxaddr_reg(int cluster, int cpu, u32 val) { - u32 a15_clusid; void __iomem *baseaddr; if (IS_ERR_OR_NULL(info)) return; - a15_clusid = readl_relaxed(info->baseaddr + A15_CONF) & 0xf; - - if (cluster != a15_clusid) + if (cluster != info->a15_clusid) baseaddr = info->baseaddr + A7_BX_ADDR0 + (cpu << 2); else baseaddr = info->baseaddr + A15_BX_ADDR0 + (cpu << 2); @@ -127,15 +125,13 @@ EXPORT_SYMBOL_GPL(vexpress_spc_write_bxaddr_reg); int vexpress_spc_get_nb_cpus(int cluster) { - u32 a15_clusid; u32 val; if (IS_ERR_OR_NULL(info)) return -ENXIO; - a15_clusid = readl_relaxed(info->baseaddr + A15_CONF) & 0xf; val = readl_relaxed(info->baseaddr + SYS_INFO); - val = (cluster != a15_clusid) ? (val >> 20) : (val >> 16); + val = (cluster != info->a15_clusid) ? (val >> 20) : (val >> 16); return (val & 0xf); } @@ -145,15 +141,13 @@ EXPORT_SYMBOL_GPL(vexpress_spc_get_nb_cpus); int vexpress_spc_standbywfil2_status(int cluster) { u32 standbywfi_stat; - u32 a15_clusid; if (IS_ERR_OR_NULL(info)) BUG(); - a15_clusid = readl_relaxed(info->baseaddr + A15_CONF) & 0xf; standbywfi_stat = readl_relaxed(info->baseaddr + STANDBYWFI_STAT); - if (cluster != a15_clusid) + if (cluster != info->a15_clusid) return standbywfi_stat & A7_STANDBYWFIL2_MSK; else return standbywfi_stat & A15_STANDBYWFIL2_MSK; @@ -163,16 +157,14 @@ EXPORT_SYMBOL_GPL(vexpress_spc_standbywfil2_status); int vexpress_spc_standbywfi_status(int cluster, int cpu) { - u32 a15_clusid; u32 standbywfi_stat; if (IS_ERR_OR_NULL(info)) BUG(); - a15_clusid = readl_relaxed(info->baseaddr + A15_CONF) & 0xf; standbywfi_stat = readl_relaxed(info->baseaddr + STANDBYWFI_STAT); - if (cluster != a15_clusid) + if (cluster != info->a15_clusid) return standbywfi_stat & ((1 << cpu) << 3); else return standbywfi_stat & (1 << cpu); @@ -182,14 +174,11 @@ EXPORT_SYMBOL_GPL(vexpress_spc_standbywfi_status); u32 vexpress_spc_read_rststat_reg(int cluster) { - u32 a15_clusid = 0; if (IS_ERR_OR_NULL(info)) BUG(); - a15_clusid = readl_relaxed(info->baseaddr + A15_CONF) & 0xf; - - if (cluster != a15_clusid) + if (cluster != info->a15_clusid) return readl_relaxed(info->baseaddr + A7_RESET_STAT); else return readl_relaxed(info->baseaddr + A15_RESET_STAT); @@ -199,14 +188,11 @@ EXPORT_SYMBOL_GPL(vexpress_spc_read_rststat_reg); u32 vexpress_spc_read_rsthold_reg(int cluster) { - u32 a15_clusid = 0; if (IS_ERR_OR_NULL(info)) BUG(); - a15_clusid = readl_relaxed(info->baseaddr + A15_CONF) & 0xf; - - if (cluster != a15_clusid) + if (cluster != info->a15_clusid) return readl_relaxed(info->baseaddr + A7_RESET_HOLD); else return readl_relaxed(info->baseaddr + A15_RESET_HOLD); @@ -216,14 +202,11 @@ EXPORT_SYMBOL_GPL(vexpress_spc_read_rsthold_reg); void vexpress_spc_write_rsthold_reg(int cluster, u32 value) { - u32 a15_clusid = 0; if (IS_ERR_OR_NULL(info)) BUG(); - a15_clusid = readl_relaxed(info->baseaddr + A15_CONF) & 0xf; - - if (cluster != a15_clusid) + if (cluster != info->a15_clusid) writel_relaxed(value, info->baseaddr + A7_RESET_HOLD); else writel_relaxed(value, info->baseaddr + A15_RESET_HOLD); @@ -234,13 +217,11 @@ EXPORT_SYMBOL_GPL(vexpress_spc_write_rsthold_reg); int vexpress_spc_get_performance(int cluster, int *perf) { u32 perf_cfg_reg = 0; - u32 a15_clusid = 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_cfg_reg = cluster != info->a15_clusid ? PERF_LVL_A7 : PERF_LVL_A15; if (down_timeout(&info->lock, usecs_to_jiffies(TIME_OUT_US))) return -ETIME; @@ -256,15 +237,13 @@ 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; + perf_cfg_reg = cluster != info->a15_clusid ? PERF_LVL_A7 : PERF_LVL_A15; + perf_stat_reg = cluster != info->a15_clusid ? PERF_REQ_A7 : PERF_REQ_A15; if (perf < 0 || perf > 7) return -EINVAL; @@ -308,15 +287,14 @@ EXPORT_SYMBOL_GPL(vexpress_spc_set_global_wakeup_intr); int vexpress_spc_set_cpu_wakeup_irq(u32 cpu, u32 cluster, u32 set) { - u32 a15_clusid = 0, mask = 0; + u32 mask = 0; u32 wake_int_mask_reg = 0; if (IS_ERR_OR_NULL(info)) return -ENXIO; - a15_clusid = readl_relaxed(info->baseaddr + A15_CONF) & 0xf; mask = 1 << cpu; - if (a15_clusid != cluster) + if (info->a15_clusid != cluster) mask <<= 4; wake_int_mask_reg = readl(info->baseaddr + WAKE_INT_MASK); @@ -359,11 +337,9 @@ 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; + pwdrn_reg = cluster != info->a15_clusid ? A7_PWRDN_EN : A15_PWRDN_EN; writel(!!enable, info->baseaddr + pwdrn_reg); dsb(); while (readl(info->baseaddr + pwdrn_reg) != !!enable); @@ -375,14 +351,12 @@ 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; + pwdrn_reg = cluster != info->a15_clusid ? A7_PWRDNREQ : A15_PWRDNREQ; writel(val, info->baseaddr + pwdrn_reg); return; @@ -393,15 +367,13 @@ 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; + snoop_reg = cluster != info->a15_clusid ? SNOOP_CTL_A7 : SNOOP_CTL_A15; + or = cluster != info->a15_clusid ? 0x2000 : 0x180; val = readl_relaxed(info->baseaddr + snoop_reg); if (enable) { @@ -419,14 +391,10 @@ EXPORT_SYMBOL_GPL(vexpress_scc_ctl_snoops); u32 vexpress_scc_read_rststat(int cluster) { - u32 a15_clusid = 0; - if (IS_ERR_OR_NULL(info)) BUG(); - a15_clusid = readl_relaxed(info->baseaddr + A15_CONF) & 0xf; - - if (cluster != a15_clusid) + if (cluster != info->a15_clusid) return (readl_relaxed(info->baseaddr + SCC_CFGREG6) >> 16) & 0x7; else return (readl_relaxed(info->baseaddr + SCC_CFGREG6) >> 2) & 0x3; @@ -437,14 +405,11 @@ 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) { + if (cluster != info->a15_clusid) { rsthold_reg = A7_RESET_HOLD; prst_shift = 3; } else { @@ -465,14 +430,11 @@ 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) { + if (cluster != info->a15_clusid) { rsthold_reg = A7_RESET_HOLD; shift = 6; } else { @@ -493,16 +455,14 @@ 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); + return cluster != info->a15_clusid ? ((val & 0x38) >> 3) : (val & 0x3); } EXPORT_SYMBOL_GPL(vexpress_spc_wfi_cpustat); @@ -566,6 +526,7 @@ static int __init vexpress_spc_early_init(void) } } + info->a15_clusid = readl_relaxed(info->baseaddr + A15_CONF) & 0xf; /* * Multi-cluster systems may need this data when non-coherent, during * cluster power-up/power-down. Make sure it reaches main memory: -- cgit v1.2.3 From 229371a94b0fa362960e7865230fb13723638c49 Mon Sep 17 00:00:00 2001 From: Sudeep KarkadaNagesha Date: Fri, 30 Nov 2012 18:45:45 +0000 Subject: misc: vexpress/spc: add support to populate frequencies from firmware This patch adds support to read the SPC virtual registers that store the OPPs through SYS_CFG interface. This helps in avoiding to rely on DT for frequency to performanace index as expected by firmware. It can also be used to get other information from the firware. Signed-off-by: Sudeep KarkadaNagesha --- drivers/misc/vexpress/arm-spc.c | 95 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/drivers/misc/vexpress/arm-spc.c b/drivers/misc/vexpress/arm-spc.c index bc2ed8d9433..124a8d9af97 100644 --- a/drivers/misc/vexpress/arm-spc.c +++ b/drivers/misc/vexpress/arm-spc.c @@ -32,6 +32,8 @@ #include #define SCC_CFGREG6 0x018 +#define SCC_CFGREG19 0x120 +#define SCC_CFGREG20 0x124 #define A15_CONF 0x400 #define SNOOP_CTL_A15 0x404 #define A7_CONF 0x500 @@ -63,24 +65,45 @@ #define A15_RESET_STAT 0xB60 #define A7_RESET_STAT 0xB64 #define A15_BX_ADDR0 0xB68 +#define SYS_CFG_WDATA 0xB70 +#define SYS_CFG_RDATA 0xB74 #define A7_BX_ADDR0 0xB78 +#define SPC_CONTROL 0xC00 +#define SPC_LATENCY 0xC04 +#define A15_PERFVAL_BASE 0xC10 +#define A7_PERFVAL_BASE 0xC30 #define A15_STANDBYWFIL2_MSK (1 << 2) #define A7_STANDBYWFIL2_MSK (1 << 6) #define GBL_WAKEUP_INT_MSK (0x3 << 10) +#define SYS_CFG_START (1 << 31) +#define SYS_CFG_SCC (6 << 20) +#define SYS_CFG_STAT (14 << 20) + +#define CLKF_SHIFT 16 +#define CLKF_MASK 0x1FFF +#define CLKR_SHIFT 0 +#define CLKR_MASK 0x3F +#define CLKOD_SHIFT 8 +#define CLKOD_MASK 0xF + #define A15_PART_NO 0xF #define A7_PART_NO 0x7 #define DRIVER_NAME "SPC" #define TIME_OUT_US 3000 +#define MAX_OPPS 8 +#define MAX_CLUSTERS 2 + struct vexpress_spc_drvdata { void __iomem *baseaddr; uint32_t a15_clusid; int irq; struct semaphore lock; struct completion done; + uint32_t freqs[MAX_CLUSTERS][MAX_OPPS]; }; static struct vexpress_spc_drvdata *info; @@ -485,6 +508,68 @@ irqreturn_t vexpress_spc_irq_handler(int irq, void *data) return IRQ_HANDLED; } +static int read_sys_cfg(int func, int offset, uint32_t *data) +{ + int ret; + + if (down_timeout(&info->lock, usecs_to_jiffies(TIME_OUT_US))) + return -ETIME; + + init_completion(&info->done); + + /* Set the control value */ + writel(SYS_CFG_START | func | offset >> 2, info->baseaddr + COMMS); + + if (!wait_for_completion_interruptible_timeout(&info->done, + usecs_to_jiffies(TIME_OUT_US))) + ret = -ETIMEDOUT; + else + *data = readl(info->baseaddr + SYS_CFG_RDATA); + + up(&info->lock); + + return ret; +} + +/* + * Based on the firmware documentation, this is always fixed to 20 + * All the 4 OSC: A15 PLL0/1, A7 PLL0/1 must be programmed same + * values for both control and value registers. + * This function uses A15 PLL 0 registers to compute multiple factor + * F out = F in * (CLKF + 1) / ((CLKOD + 1) * (CLKR + 1)) + */ +static inline int __get_mult_factor(void) +{ + int i_div, o_div, f_div; + uint32_t tmp; + + tmp = readl(info->baseaddr + SCC_CFGREG19); + f_div = (tmp >> CLKF_SHIFT) & CLKF_MASK; + + tmp = readl(info->baseaddr + SCC_CFGREG20); + o_div = (tmp >> CLKOD_SHIFT) & CLKOD_MASK; + i_div = (tmp >> CLKR_SHIFT) & CLKR_MASK; + + return (f_div + 1) / ((o_div + 1) * (i_div + 1)); +} + +static int vexpress_spc_populate_opps(uint32_t cluster) +{ + uint32_t data = 0, off, ret, j; + int mult_fact = __get_mult_factor(); + + off = cluster != info->a15_clusid ? A7_PERFVAL_BASE : A15_PERFVAL_BASE; + for (j = 0; j < MAX_OPPS; j++, off += 4) { + ret = read_sys_cfg(SYS_CFG_SCC, off, &data); + if (!ret) + info->freqs[cluster][j] = (data & 0xFFFFF) * mult_fact; + else + break; + } + + return ret; +} + static int __init vexpress_spc_early_init(void) { struct device_node *node = of_find_compatible_node(NULL, NULL, @@ -527,6 +612,16 @@ static int __init vexpress_spc_early_init(void) } info->a15_clusid = readl_relaxed(info->baseaddr + A15_CONF) & 0xf; + + if (vexpress_spc_populate_opps(0) || vexpress_spc_populate_opps(1)) { + if (info->irq) + free_irq(info->irq, info); + iounmap(info->baseaddr); + kfree(info); + pr_err("failed to build OPP table\n"); + return -ENODEV; + } + /* * Multi-cluster systems may need this data when non-coherent, during * cluster power-up/power-down. Make sure it reaches main memory: -- cgit v1.2.3 From 3eb9d1401200e998fb9af53d4afe4f3959a2b19b Mon Sep 17 00:00:00 2001 From: Sudeep KarkadaNagesha Date: Fri, 30 Nov 2012 18:45:46 +0000 Subject: misc: vexpress/spc: update get/set_perf to use frequency as input This patch changes the input parameter of spc_get/spc_set_perf routines to frequency instead of performance index as expected by the firmware. The SPC driver now has the list of frequencies supported by the firmware, so it can map the requested frequency to the right performance index. Signed-off-by: Sudeep KarkadaNagesha --- drivers/misc/vexpress/arm-spc.c | 26 +++++++++++++++++++++----- include/linux/vexpress.h | 12 ++++++------ 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/drivers/misc/vexpress/arm-spc.c b/drivers/misc/vexpress/arm-spc.c index 124a8d9af97..073a3fc465f 100644 --- a/drivers/misc/vexpress/arm-spc.c +++ b/drivers/misc/vexpress/arm-spc.c @@ -237,9 +237,10 @@ void vexpress_spc_write_rsthold_reg(int cluster, u32 value) EXPORT_SYMBOL_GPL(vexpress_spc_write_rsthold_reg); -int vexpress_spc_get_performance(int cluster, int *perf) +int vexpress_spc_get_performance(int cluster, u32 *freq) { u32 perf_cfg_reg = 0; + int perf; if (IS_ERR_OR_NULL(info)) return -ENXIO; @@ -248,7 +249,11 @@ int vexpress_spc_get_performance(int cluster, int *perf) if (down_timeout(&info->lock, usecs_to_jiffies(TIME_OUT_US))) return -ETIME; - *perf = readl(info->baseaddr + perf_cfg_reg); + + perf = readl(info->baseaddr + perf_cfg_reg); + + *freq = info->freqs[cluster][perf]; + up(&info->lock); return 0; @@ -256,19 +261,30 @@ int vexpress_spc_get_performance(int cluster, int *perf) } EXPORT_SYMBOL_GPL(vexpress_spc_get_performance); -int vexpress_spc_set_performance(int cluster, int perf) +static int vexpress_spc_find_perf_index(int cluster, u32 freq) +{ + int idx; + /* Hash function would be ideal, based on hashtable in v3.8?? */ + for (idx = 0; idx < MAX_OPPS; idx++) + if (info->freqs[cluster][idx] == freq) + break; + return idx; +} + +int vexpress_spc_set_performance(int cluster, u32 freq) { u32 perf_cfg_reg = 0; u32 perf_stat_reg = 0; - int ret = 0; + int ret = 0, perf; if (IS_ERR_OR_NULL(info)) return -ENXIO; perf_cfg_reg = cluster != info->a15_clusid ? PERF_LVL_A7 : PERF_LVL_A15; perf_stat_reg = cluster != info->a15_clusid ? PERF_REQ_A7 : PERF_REQ_A15; + perf = vexpress_spc_find_perf_index(cluster, freq); - if (perf < 0 || perf > 7) + if (perf >= MAX_OPPS) return -EINVAL; if (down_timeout(&info->lock, usecs_to_jiffies(TIME_OUT_US))) diff --git a/include/linux/vexpress.h b/include/linux/vexpress.h index 977535236db..8d2443f97ce 100644 --- a/include/linux/vexpress.h +++ b/include/linux/vexpress.h @@ -147,8 +147,8 @@ extern int vexpress_spc_standbywfi_status(int cluster, int cpu); extern int vexpress_spc_standbywfil2_status(int cluster); extern int vexpress_spc_set_cpu_wakeup_irq(u32 cpu, u32 cluster, u32 set); extern int vexpress_spc_set_global_wakeup_intr(u32 set); -extern int vexpress_spc_get_performance(int cluster, int *perf); -extern int vexpress_spc_set_performance(int cluster, int perf); +extern int vexpress_spc_get_performance(int cluster, u32 *freq); +extern int vexpress_spc_set_performance(int cluster, u32 freq); extern int vexpress_spc_wfi_cpustat(int cluster); extern void vexpress_spc_set_wake_intr(u32 mask); extern void vexpress_spc_write_bxaddr_reg(int cluster, int cpu, u32 val); @@ -209,13 +209,13 @@ static inline u32 vexpress_scc_read_rststat(int cluster) return 0; } -static inline int vexpress_spc_get_performance(int cluster, int *perf) +static inline int vexpress_spc_get_performance(int cluster, u32 *freq) { - return -EINVAL; + return -ENOSYS; } -static inline int vexpress_spc_set_performance(int cluster, int perf) +static inline int vexpress_spc_set_performance(int cluster, u32 freq) { - return -EINVAL; + return -ENOSYS; } static inline void vexpress_spc_set_wake_intr(u32 mask) { } static inline u32 vexpress_spc_get_wake_intr(int raw) { return 0; } -- cgit v1.2.3 From 115a0591a2dbc54ed825ca93bc57c490477eac77 Mon Sep 17 00:00:00 2001 From: Sudeep KarkadaNagesha Date: Fri, 30 Nov 2012 18:45:48 +0000 Subject: misc: vexpress/spc: update error handling code based on firmware response The latest firmware has the following protocol for PWC_STATUS update to communicate status/faults: CA15 DVFS request: 0x0001 complete, 0x0002 error CA7 DVFS request: 0x0010 complete, 0x0020 error SPC_SYS_CFG request: 0x0100 complete, 0x0200 error This patch updates the driver to handle the error conditions based on the above protocol. Signed-off-by: Sudeep KarkadaNagesha --- drivers/misc/vexpress/arm-spc.c | 67 ++++++++++++++++++++++++++++++----------- 1 file changed, 50 insertions(+), 17 deletions(-) diff --git a/drivers/misc/vexpress/arm-spc.c b/drivers/misc/vexpress/arm-spc.c index 073a3fc465f..85583d58674 100644 --- a/drivers/misc/vexpress/arm-spc.c +++ b/drivers/misc/vexpress/arm-spc.c @@ -101,6 +101,14 @@ struct vexpress_spc_drvdata { void __iomem *baseaddr; uint32_t a15_clusid; int irq; + uint32_t cur_rsp_mask; + uint32_t cur_rsp_stat; +#define A15_OPP 0 +#define A7_OPP 1 +#define COMMS_OPP 2 +#define STAT_COMPLETE(type) ((1 << 0) << (type << 2)) +#define STAT_ERR(type) ((1 << 1) << (type << 2)) +#define RESPONSE_MASK(type) (STAT_COMPLETE(type) | STAT_ERR(type)) struct semaphore lock; struct completion done; uint32_t freqs[MAX_CLUSTERS][MAX_OPPS]; @@ -271,17 +279,36 @@ static int vexpress_spc_find_perf_index(int cluster, u32 freq) return idx; } +static int vexpress_spc_waitforcompletion(int req_type) +{ + int ret; + + if (!wait_for_completion_interruptible_timeout(&info->done, + usecs_to_jiffies(TIME_OUT_US))) + ret = -ETIMEDOUT; + else + ret = info->cur_rsp_stat & STAT_COMPLETE(req_type) ? 0 : -EIO; + return ret; +} + int vexpress_spc_set_performance(int cluster, u32 freq) { - u32 perf_cfg_reg = 0; - u32 perf_stat_reg = 0; - int ret = 0, perf; + u32 perf_cfg_reg, perf_stat_reg; + int ret, perf, req_type; if (IS_ERR_OR_NULL(info)) return -ENXIO; - perf_cfg_reg = cluster != info->a15_clusid ? PERF_LVL_A7 : PERF_LVL_A15; - perf_stat_reg = cluster != info->a15_clusid ? PERF_REQ_A7 : PERF_REQ_A15; + if (cluster != info->a15_clusid) { + req_type = A7_OPP; + perf_cfg_reg = PERF_LVL_A7; + perf_stat_reg = PERF_REQ_A7; + } else { + req_type = A15_OPP; + perf_cfg_reg = PERF_LVL_A15; + perf_stat_reg = PERF_REQ_A15; + } + perf = vexpress_spc_find_perf_index(cluster, freq); if (perf >= MAX_OPPS) @@ -292,16 +319,17 @@ int vexpress_spc_set_performance(int cluster, u32 freq) init_completion(&info->done); + info->cur_rsp_mask = RESPONSE_MASK(req_type); + writel(perf, info->baseaddr + perf_cfg_reg); - if (!wait_for_completion_interruptible_timeout(&info->done, - usecs_to_jiffies(TIME_OUT_US))) { - ret = -ETIMEDOUT; - } + ret = vexpress_spc_waitforcompletion(req_type); + + info->cur_rsp_mask = 0; up(&info->lock); - return ret; + return ret; } EXPORT_SYMBOL_GPL(vexpress_spc_set_performance); @@ -516,10 +544,12 @@ EXPORT_SYMBOL_GPL(vexpress_spc_check_loaded); irqreturn_t vexpress_spc_irq_handler(int irq, void *data) { struct vexpress_spc_drvdata *drv_data = data; + uint32_t status = readl_relaxed(drv_data->baseaddr + PWC_STATUS); - readl_relaxed(drv_data->baseaddr + PWC_STATUS); - - complete(&drv_data->done); + if (info->cur_rsp_mask & status) { + info->cur_rsp_stat = status; + complete(&drv_data->done); + } return IRQ_HANDLED; } @@ -533,15 +563,18 @@ static int read_sys_cfg(int func, int offset, uint32_t *data) init_completion(&info->done); + info->cur_rsp_mask = RESPONSE_MASK(COMMS_OPP); + /* Set the control value */ writel(SYS_CFG_START | func | offset >> 2, info->baseaddr + COMMS); - if (!wait_for_completion_interruptible_timeout(&info->done, - usecs_to_jiffies(TIME_OUT_US))) - ret = -ETIMEDOUT; - else + ret = vexpress_spc_waitforcompletion(COMMS_OPP); + + if (!ret) *data = readl(info->baseaddr + SYS_CFG_RDATA); + info->cur_rsp_mask = 0; + up(&info->lock); return ret; -- cgit v1.2.3 From f7f504c31c18cf4ac1bf35cdb946553c31f3af20 Mon Sep 17 00:00:00 2001 From: Sudeep KarkadaNagesha Date: Fri, 30 Nov 2012 18:45:49 +0000 Subject: misc: vexpress/spc: change timeout to jiffies instead of microseconds Both SPC OPP and COMMS request takes ~3-5ms at max with CPU wakeup enabled. The OPP change takes ~900us and COMMS ~200us if wakeup are disabled. Since all the operations are serialised in the firmware and CA7 wakeup takes ~2ms max, this could affect other SPC request when they occur at the same time. Further an SPC request could start just before jiffie is about to be updated. In order to handle any of these cases, this patch changes timeout value to 20ms. Signed-off-by: Sudeep KarkadaNagesha --- drivers/misc/vexpress/arm-spc.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/misc/vexpress/arm-spc.c b/drivers/misc/vexpress/arm-spc.c index 85583d58674..82ca8efcd17 100644 --- a/drivers/misc/vexpress/arm-spc.c +++ b/drivers/misc/vexpress/arm-spc.c @@ -92,7 +92,12 @@ #define A7_PART_NO 0x7 #define DRIVER_NAME "SPC" -#define TIME_OUT_US 3000 +/* + * Even though the SPC takes max 3-5 ms to complete any OPP/COMMS + * operation, the operation could start just before jiffie is about + * to be incremented. So setting timeout value of 20ms = 2jiffies@100Hz + */ +#define TIME_OUT_US 20000 #define MAX_OPPS 8 #define MAX_CLUSTERS 2 -- cgit v1.2.3 From f6863ac8241d0a31fb70faaa09fbb5e3394304ef Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Thu, 29 Nov 2012 22:13:08 -0500 Subject: misc: vexpress/spc: attempt to init the spc driver from vexpress_spc_check_loaded() Even if it is initialized via early_INITCALL(), there are other pieces of code that also have to be initialized with early_initcall() and the relative call ordering may not always be satisfied. So try to initialize the SPC code when vexpress_spc_check_loaded() is called if that has not been done yet. Signed-off-by: Nicolas Pitre --- drivers/misc/vexpress/arm-spc.c | 40 ++++++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/drivers/misc/vexpress/arm-spc.c b/drivers/misc/vexpress/arm-spc.c index 82ca8efcd17..0432e3d9506 100644 --- a/drivers/misc/vexpress/arm-spc.c +++ b/drivers/misc/vexpress/arm-spc.c @@ -538,14 +538,6 @@ int vexpress_spc_wfi_cpustat(int cluster) } EXPORT_SYMBOL_GPL(vexpress_spc_wfi_cpustat); -static bool vexpress_spc_loaded; - -bool vexpress_spc_check_loaded(void) -{ - return vexpress_spc_loaded; -} -EXPORT_SYMBOL_GPL(vexpress_spc_check_loaded); - irqreturn_t vexpress_spc_irq_handler(int irq, void *data) { struct vexpress_spc_drvdata *drv_data = data; @@ -624,10 +616,12 @@ static int vexpress_spc_populate_opps(uint32_t cluster) return ret; } -static int __init vexpress_spc_early_init(void) +static int vexpress_spc_init(void) { struct device_node *node = of_find_compatible_node(NULL, NULL, "arm,spc"); + if (!node) + return -ENODEV; info = kzalloc(sizeof(*info), GFP_KERNEL); if (!info) { @@ -635,9 +629,7 @@ static int __init vexpress_spc_early_init(void) return -ENOMEM; } - if (node) - info->baseaddr = of_iomap(node, 0); - + info->baseaddr = of_iomap(node, 0); if (WARN_ON(!info->baseaddr)) { kfree(info); return -EIO; @@ -684,11 +676,31 @@ static int __init vexpress_spc_early_init(void) outer_clean_range(virt_to_phys(info), virt_to_phys(info + 1)); pr_info("vexpress_spc loaded at %p\n", info->baseaddr); - vexpress_spc_loaded = true; - return 0; } +static int vexpress_spc_load_result = -EAGAIN; +static DEFINE_MUTEX(vexpress_spc_loading); + +bool vexpress_spc_check_loaded(void) +{ + if (vexpress_spc_load_result != -EAGAIN) + return (vexpress_spc_load_result == 0); + + mutex_lock(&vexpress_spc_loading); + if (vexpress_spc_load_result == -EAGAIN) + vexpress_spc_load_result = vexpress_spc_init(); + mutex_unlock(&vexpress_spc_loading); + return (vexpress_spc_load_result == 0); +} +EXPORT_SYMBOL_GPL(vexpress_spc_check_loaded); + +static int __init vexpress_spc_early_init(void) +{ + vexpress_spc_check_loaded(); + return vexpress_spc_load_result; +} + early_initcall(vexpress_spc_early_init); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Serial Power Controller (SPC) support"); -- cgit v1.2.3 From b16af23be497bb00d4233677e3affe76caed6a7c Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Wed, 5 Dec 2012 20:48:55 -0500 Subject: ARM: vexpress/spc: more cache flush Not only the pointed data, but the pointer as well has to be flushed out of the cache. Signed-off-by: Nicolas Pitre --- drivers/misc/vexpress/arm-spc.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/misc/vexpress/arm-spc.c b/drivers/misc/vexpress/arm-spc.c index 0432e3d9506..5708bb35a10 100644 --- a/drivers/misc/vexpress/arm-spc.c +++ b/drivers/misc/vexpress/arm-spc.c @@ -673,7 +673,9 @@ static int vexpress_spc_init(void) * cluster power-up/power-down. Make sure it reaches main memory: */ __cpuc_flush_dcache_area(info, sizeof *info); + __cpuc_flush_dcache_area(&info, sizeof info); outer_clean_range(virt_to_phys(info), virt_to_phys(info + 1)); + outer_clean_range(virt_to_phys(&info), virt_to_phys(&info + 1)); pr_info("vexpress_spc loaded at %p\n", info->baseaddr); return 0; -- cgit v1.2.3 From 23f4428dbdcac2fb064a1c4fdbb084fbaa584d0e Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Sat, 1 Dec 2012 16:18:07 +0530 Subject: misc: vexpress/spc: add support to get frequency table from spc SPC controller driver has already populated frequency table from firmware and not its time to share it with other frameworks like: cpufreq. This patch provides interface to get freq table from spc driver. Signed-off-by: Viresh Kumar --- drivers/misc/vexpress/arm-spc.c | 12 +++++++++++- include/linux/vexpress.h | 7 +++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/drivers/misc/vexpress/arm-spc.c b/drivers/misc/vexpress/arm-spc.c index 5708bb35a10..913dd087282 100644 --- a/drivers/misc/vexpress/arm-spc.c +++ b/drivers/misc/vexpress/arm-spc.c @@ -117,6 +117,7 @@ struct vexpress_spc_drvdata { struct semaphore lock; struct completion done; uint32_t freqs[MAX_CLUSTERS][MAX_OPPS]; + int freqs_cnt[MAX_CLUSTERS]; }; static struct vexpress_spc_drvdata *info; @@ -278,7 +279,7 @@ static int vexpress_spc_find_perf_index(int cluster, u32 freq) { int idx; /* Hash function would be ideal, based on hashtable in v3.8?? */ - for (idx = 0; idx < MAX_OPPS; idx++) + for (idx = 0; idx < info->freqs_cnt[cluster]; idx++) if (info->freqs[cluster][idx] == freq) break; return idx; @@ -613,9 +614,18 @@ static int vexpress_spc_populate_opps(uint32_t cluster) break; } + info->freqs_cnt[cluster] = j; return ret; } +unsigned int *vexpress_spc_get_freq_table(uint32_t cluster, int *count) +{ + + *count = info->freqs_cnt[cluster]; + return info->freqs[cluster]; +} +EXPORT_SYMBOL_GPL(vexpress_spc_get_freq_table); + static int vexpress_spc_init(void) { struct device_node *node = of_find_compatible_node(NULL, NULL, diff --git a/include/linux/vexpress.h b/include/linux/vexpress.h index 8d2443f97ce..63e71e28801 100644 --- a/include/linux/vexpress.h +++ b/include/linux/vexpress.h @@ -15,6 +15,7 @@ #define _LINUX_VEXPRESS_H #include +#include #define VEXPRESS_SITE_MB 0 #define VEXPRESS_SITE_DB1 1 @@ -147,6 +148,7 @@ extern int vexpress_spc_standbywfi_status(int cluster, int cpu); extern int vexpress_spc_standbywfil2_status(int cluster); extern int vexpress_spc_set_cpu_wakeup_irq(u32 cpu, u32 cluster, u32 set); extern int vexpress_spc_set_global_wakeup_intr(u32 set); +extern unsigned int *vexpress_spc_get_freq_table(uint32_t cluster, int *count); extern int vexpress_spc_get_performance(int cluster, u32 *freq); extern int vexpress_spc_set_performance(int cluster, u32 freq); extern int vexpress_spc_wfi_cpustat(int cluster); @@ -209,6 +211,11 @@ static inline u32 vexpress_scc_read_rststat(int cluster) return 0; } +static inline unsigned int *vexpress_spc_get_freq_table(uint32_t cluster, int *count) +{ + return ERR_PTR(-ENOSYS); +} + static inline int vexpress_spc_get_performance(int cluster, u32 *freq) { return -ENOSYS; -- cgit v1.2.3 From 838c40d8f8108361c5cb17045526d0c51d4e5ddc Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Tue, 26 Mar 2013 13:09:23 +0000 Subject: ARM: vexpress: Add SPC node to TC2 device-tree --- arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts b/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts index dfe371ec274..edf3738da42 100644 --- a/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts +++ b/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts @@ -125,6 +125,12 @@ clock-names = "apb_pclk"; }; + spc@7fff0000 { + compatible = "arm,spc"; + reg = <0 0x7fff0000 0 0x1000>; + interrupts = <0 95 4>; + }; + timer { compatible = "arm,armv7-timer"; interrupts = <1 13 0xf08>, -- cgit v1.2.3