From 47e07abc265a007f8a12c0c0a2dcb8fc88875880 Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Fri, 8 Jun 2012 16:42:20 +0100 Subject: configs: vexpress: Basic board config fragment Signed-off-by: Jon Medhurst --- linaro/configs/vexpress.conf | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 linaro/configs/vexpress.conf diff --git a/linaro/configs/vexpress.conf b/linaro/configs/vexpress.conf new file mode 100644 index 00000000000..4aca6be57e3 --- /dev/null +++ b/linaro/configs/vexpress.conf @@ -0,0 +1,34 @@ +CONFIG_ARCH_VEXPRESS=y +CONFIG_ARCH_VEXPRESS_CA9X4=y +CONFIG_ARM_ARCH_TIMER=y +CONFIG_CMDLINE="console=ttyAMA0,38400n8 root=/dev/mmcblk0p2 rootwait mmci.fmax=4000000" +CONFIG_VFP=y +CONFIG_NEON=y +CONFIG_SCSI=y +CONFIG_BLK_DEV_SD=y +CONFIG_SMSC911X=y +CONFIG_INPUT_EVDEV=y +CONFIG_SERIO_AMBAKMI=y +CONFIG_SERIAL_AMBA_PL011=y +CONFIG_SERIAL_AMBA_PL011_CONSOLE=y +CONFIG_FB=y +CONFIG_FB_ARMCLCD=y +CONFIG_FB_ARMHDLCD=y +CONFIG_LOGO=y +# CONFIG_LOGO_LINUX_MONO is not set +# CONFIG_LOGO_LINUX_VGA16 is not set +CONFIG_SOUND=y +CONFIG_SND=y +CONFIG_SND_ARMAACI=y +CONFIG_USB=y +CONFIG_USB_ISP1760_HCD=y +CONFIG_USB_STORAGE=y +CONFIG_MMC=y +CONFIG_MMC_ARMMMCI=y +CONFIG_RTC_CLASS=y +CONFIG_RTC_DRV_PL031=y +CONFIG_NFS_FS=y +CONFIG_NFS_V3=y +CONFIG_NFS_V3_ACL=y +CONFIG_NFS_V4=y +CONFIG_ROOT_NFS=y -- cgit v1.2.3 From 7242eb38ab00093db5232c5fb16a3d30f91eac5c Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Thu, 26 Jul 2012 15:49:50 +0100 Subject: configs: vexpress: Enable CONFIG_SMC91X This is the ethernet device RTSM models and we want normal vexpress kernels also working on RTSM. Signed-off-by: Jon Medhurst --- linaro/configs/vexpress.conf | 1 + 1 file changed, 1 insertion(+) diff --git a/linaro/configs/vexpress.conf b/linaro/configs/vexpress.conf index 4aca6be57e3..6a06e8b3806 100644 --- a/linaro/configs/vexpress.conf +++ b/linaro/configs/vexpress.conf @@ -7,6 +7,7 @@ CONFIG_NEON=y CONFIG_SCSI=y CONFIG_BLK_DEV_SD=y CONFIG_SMSC911X=y +CONFIG_SMC91X=y CONFIG_INPUT_EVDEV=y CONFIG_SERIO_AMBAKMI=y CONFIG_SERIAL_AMBA_PL011=y -- cgit v1.2.3 From b1c39e26a4249aeb5a9b7f0c2821c685bf5bca23 Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Tue, 2 Oct 2012 11:53:06 +0100 Subject: configs: vexpress: Changes needed for Linux 3.8 Signed-off-by: Jon Medhurst --- linaro/configs/vexpress.conf | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/linaro/configs/vexpress.conf b/linaro/configs/vexpress.conf index 6a06e8b3806..fb960d96557 100644 --- a/linaro/configs/vexpress.conf +++ b/linaro/configs/vexpress.conf @@ -33,3 +33,9 @@ CONFIG_NFS_V3=y CONFIG_NFS_V3_ACL=y CONFIG_NFS_V4=y CONFIG_ROOT_NFS=y +CONFIG_VEXPRESS_CONFIG=y +CONFIG_SENSORS_VEXPRESS=y +CONFIG_REGULATOR=y +CONFIG_REGULATOR_VEXPRESS=y +CONFIG_NEW_LEDS=y +CONFIG_LEDS_CLASS=y -- cgit v1.2.3 From 2d98cd5fd09214dae7789994fe53bd5e2b9bb1db Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Tue, 5 Mar 2013 11:54:20 +0800 Subject: configs: vexpress: Changes needed for Linux 3.9 Signed-off-by: Jon Medhurst --- linaro/configs/vexpress.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linaro/configs/vexpress.conf b/linaro/configs/vexpress.conf index fb960d96557..8c1c9099114 100644 --- a/linaro/configs/vexpress.conf +++ b/linaro/configs/vexpress.conf @@ -1,6 +1,6 @@ CONFIG_ARCH_VEXPRESS=y CONFIG_ARCH_VEXPRESS_CA9X4=y -CONFIG_ARM_ARCH_TIMER=y +CONFIG_HAVE_ARM_ARCH_TIMER=y CONFIG_CMDLINE="console=ttyAMA0,38400n8 root=/dev/mmcblk0p2 rootwait mmci.fmax=4000000" CONFIG_VFP=y CONFIG_NEON=y -- cgit v1.2.3 From c338b0c753463763b2c07cc8b3de8458c259e852 Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Wed, 30 Jan 2013 18:44:20 +0000 Subject: configs: vexpress: Enable LEDs To provide access to LEDs enable CONFIG_LEDS_CLASS=y CONFIG_LEDS_GPIO=y (LED's are available from user side under under /sys/class/leds) To use the motherboard LEDs to show CPU activity... CONFIG_LEDS_TRIGGERS=y CONFIG_LEDS_TRIGGER_HEARTBEAT=y CONFIG_LEDS_TRIGGER_CPU=y Signed-off-by: Jon Medhurst --- linaro/configs/vexpress.conf | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/linaro/configs/vexpress.conf b/linaro/configs/vexpress.conf index 8c1c9099114..6576492b153 100644 --- a/linaro/configs/vexpress.conf +++ b/linaro/configs/vexpress.conf @@ -39,3 +39,7 @@ CONFIG_REGULATOR=y CONFIG_REGULATOR_VEXPRESS=y CONFIG_NEW_LEDS=y CONFIG_LEDS_CLASS=y +CONFIG_LEDS_GPIO=y +CONFIG_LEDS_TRIGGERS=y +CONFIG_LEDS_TRIGGER_HEARTBEAT=y +CONFIG_LEDS_TRIGGER_CPU=y -- cgit v1.2.3 From ff4091af32617a95fb41809909ff04cc19557976 Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Thu, 5 Jul 2012 11:20:51 +0100 Subject: configs: vexpress: Extra options needed for TC2 Signed-off-by: Jon Medhurst --- linaro/configs/vexpress.conf | 3 +++ 1 file changed, 3 insertions(+) diff --git a/linaro/configs/vexpress.conf b/linaro/configs/vexpress.conf index 6576492b153..a75400acf3c 100644 --- a/linaro/configs/vexpress.conf +++ b/linaro/configs/vexpress.conf @@ -1,6 +1,9 @@ CONFIG_ARCH_VEXPRESS=y CONFIG_ARCH_VEXPRESS_CA9X4=y CONFIG_HAVE_ARM_ARCH_TIMER=y +CONFIG_NR_CPUS=8 +CONFIG_HIGHMEM=y +CONFIG_HIGHPTE=y CONFIG_CMDLINE="console=ttyAMA0,38400n8 root=/dev/mmcblk0p2 rootwait mmci.fmax=4000000" CONFIG_VFP=y CONFIG_NEON=y -- cgit v1.2.3 From d66c5880580ea179a736b407824ddb069ba862fa Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Thu, 26 Jul 2012 15:01:40 +0100 Subject: configs: vexpress: Enable TC2 power management options Note, for the sake of a faster boot and better performance, we default to maximum CPU clock frequency (CONFIG_CPU_FREQ_DEFAULT_GOV_PERFORMANCE) and will rely on user-side scripts on Android and Ubuntu to change this as appropriate for big.LITTLE power saving. E.g. changing LITTLE cores to 'ondemand'. Signed-off-by: Jon Medhurst --- linaro/configs/vexpress.conf | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/linaro/configs/vexpress.conf b/linaro/configs/vexpress.conf index a75400acf3c..7266d0cee9e 100644 --- a/linaro/configs/vexpress.conf +++ b/linaro/configs/vexpress.conf @@ -1,5 +1,15 @@ CONFIG_ARCH_VEXPRESS=y CONFIG_ARCH_VEXPRESS_CA9X4=y +CONFIG_BIG_LITTLE=y +CONFIG_ARCH_VEXPRESS_TC2=y +CONFIG_ARCH_VEXPRESS_DCSCB=y +CONFIG_ARM_VEXPRESS_BL_CPUFREQ=y +CONFIG_PM_OPP=y +CONFIG_CPU_FREQ=y +CONFIG_CPU_FREQ_GOV_ONDEMAND=y +CONFIG_CPU_FREQ_GOV_PERFORMANCE=y +CONFIG_CPU_FREQ_GOV_INTERACTIVE=y +CONFIG_CPU_FREQ_DEFAULT_GOV_PERFORMANCE=y CONFIG_HAVE_ARM_ARCH_TIMER=y CONFIG_NR_CPUS=8 CONFIG_HIGHMEM=y -- cgit v1.2.3 From 5103a93f136b37bd719220f13dc8bb33faadf7f8 Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Wed, 13 Mar 2013 10:56:22 +0000 Subject: configs: vexpress-tuning: Add fragment for better performance Use ARM rather than Thumb2 and disable debug and trace features which aren't essential. (Some trace features are still needed to avoid breaking Gator). Signed-off-by: Jon Medhurst --- linaro/configs/vexpress-tuning.conf | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 linaro/configs/vexpress-tuning.conf diff --git a/linaro/configs/vexpress-tuning.conf b/linaro/configs/vexpress-tuning.conf new file mode 100644 index 00000000000..7bdf2732c92 --- /dev/null +++ b/linaro/configs/vexpress-tuning.conf @@ -0,0 +1,8 @@ +# CONFIG_THUMB2_KERNEL is not set +# CONFIG_PROVE_LOCKING is not set +# CONFIG_OPROFILE is not set +# CONFIG_SCHEDSTATS is not set +# CONFIG_TIMER_STATS is not set +# CONFIG_SCHED_DEBUG is not set +# CONFIG_FTRACE is not set +# CONFIG_FUNCTION_TRACER is not set -- cgit v1.2.3 From 5b3a8f7d3851eae3b7829d15f4c3ac71bbcb2dbc Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Fri, 15 Mar 2013 13:08:32 +0000 Subject: configs: vexpress-tuning: Re-enable tracing and profiling options For now, lets play safe and not disable any tracing and profiling options in case these are expected to be present for benchmarking purposes. Signed-off-by: Jon Medhurst --- linaro/configs/vexpress-tuning.conf | 6 ------ 1 file changed, 6 deletions(-) diff --git a/linaro/configs/vexpress-tuning.conf b/linaro/configs/vexpress-tuning.conf index 7bdf2732c92..917ba6c09cb 100644 --- a/linaro/configs/vexpress-tuning.conf +++ b/linaro/configs/vexpress-tuning.conf @@ -1,8 +1,2 @@ # CONFIG_THUMB2_KERNEL is not set # CONFIG_PROVE_LOCKING is not set -# CONFIG_OPROFILE is not set -# CONFIG_SCHEDSTATS is not set -# CONFIG_TIMER_STATS is not set -# CONFIG_SCHED_DEBUG is not set -# CONFIG_FTRACE is not set -# CONFIG_FUNCTION_TRACER is not set -- cgit v1.2.3 From 7c394d27f87a7e788c32bd39bb0732b9e8eb0cb0 Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Tue, 19 Mar 2013 20:14:31 +0000 Subject: configs: vexpress-tuning: Re-enable CONFIG_THUMB2_KERNEL Performace and power don't appear to be improved by building for ARM, in fact, there's a suggestion it uses slightly more power. Signed-off-by: Jon Medhurst --- linaro/configs/vexpress-tuning.conf | 1 - 1 file changed, 1 deletion(-) diff --git a/linaro/configs/vexpress-tuning.conf b/linaro/configs/vexpress-tuning.conf index 917ba6c09cb..adea6cc66de 100644 --- a/linaro/configs/vexpress-tuning.conf +++ b/linaro/configs/vexpress-tuning.conf @@ -1,2 +1 @@ -# CONFIG_THUMB2_KERNEL is not set # CONFIG_PROVE_LOCKING is not set -- cgit v1.2.3 From d9f00d19278b262ecac9f5d28aedec941d938cf0 Mon Sep 17 00:00:00 2001 From: Liviu Dudau Date: Wed, 13 Mar 2013 15:42:07 +0000 Subject: ARM: VExpress: PSCI: Compile PSCI code by default Compile support for PSCI by default and let the command line decide whether support for SMC calls gets enabled or not. Signed-off-by: Liviu Dudau --- linaro/configs/vexpress.conf | 1 + 1 file changed, 1 insertion(+) diff --git a/linaro/configs/vexpress.conf b/linaro/configs/vexpress.conf index 7266d0cee9e..94c36e016ad 100644 --- a/linaro/configs/vexpress.conf +++ b/linaro/configs/vexpress.conf @@ -10,6 +10,7 @@ CONFIG_CPU_FREQ_GOV_ONDEMAND=y CONFIG_CPU_FREQ_GOV_PERFORMANCE=y CONFIG_CPU_FREQ_GOV_INTERACTIVE=y CONFIG_CPU_FREQ_DEFAULT_GOV_PERFORMANCE=y +CONFIG_ARM_PSCI=y CONFIG_HAVE_ARM_ARCH_TIMER=y CONFIG_NR_CPUS=8 CONFIG_HIGHMEM=y -- cgit v1.2.3 From d0d4f95d6f3f25c47675675b02c020f1422e44d7 Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Wed, 15 Aug 2012 17:28:28 +0100 Subject: ARM: dts: Add device trees for RTSM Signed-off-by: Jon Medhurst --- arch/arm/boot/dts/rtsm_ve-cortex_a15x1.dts | 119 ++++++++++ arch/arm/boot/dts/rtsm_ve-cortex_a15x2.dts | 125 +++++++++++ arch/arm/boot/dts/rtsm_ve-cortex_a15x4.dts | 137 ++++++++++++ arch/arm/boot/dts/rtsm_ve-cortex_a9x2.dts | 131 +++++++++++ arch/arm/boot/dts/rtsm_ve-cortex_a9x4.dts | 143 ++++++++++++ arch/arm/boot/dts/rtsm_ve-motherboard.dtsi | 135 ++++++++++++ arch/arm/boot/dts/rtsm_ve-v2p-ca15x1-ca7x1.dts | 182 ++++++++++++++++ arch/arm/boot/dts/rtsm_ve-v2p-ca15x4-ca7x4.dts | 290 +++++++++++++++++++++++++ 8 files changed, 1262 insertions(+) create mode 100644 arch/arm/boot/dts/rtsm_ve-cortex_a15x1.dts create mode 100644 arch/arm/boot/dts/rtsm_ve-cortex_a15x2.dts create mode 100644 arch/arm/boot/dts/rtsm_ve-cortex_a15x4.dts create mode 100644 arch/arm/boot/dts/rtsm_ve-cortex_a9x2.dts create mode 100644 arch/arm/boot/dts/rtsm_ve-cortex_a9x4.dts create mode 100644 arch/arm/boot/dts/rtsm_ve-motherboard.dtsi create mode 100644 arch/arm/boot/dts/rtsm_ve-v2p-ca15x1-ca7x1.dts create mode 100644 arch/arm/boot/dts/rtsm_ve-v2p-ca15x4-ca7x4.dts diff --git a/arch/arm/boot/dts/rtsm_ve-cortex_a15x1.dts b/arch/arm/boot/dts/rtsm_ve-cortex_a15x1.dts new file mode 100644 index 00000000000..44511dff9d9 --- /dev/null +++ b/arch/arm/boot/dts/rtsm_ve-cortex_a15x1.dts @@ -0,0 +1,119 @@ +/* + * ARM Ltd. Fast Models + * + * Versatile Express (VE) system model + * ARMCortexA15x1CT + * + * RTSM_VE_Cortex_A15x1.lisa + */ + +/dts-v1/; + +/ { + model = "RTSM_VE_CortexA15x1"; + compatible = "arm,rtsm_ve,cortex_a15x1", "arm,vexpress"; + interrupt-parent = <&gic>; + #address-cells = <2>; + #size-cells = <2>; + + chosen { }; + + aliases { + serial0 = &v2m_serial0; + serial1 = &v2m_serial1; + serial2 = &v2m_serial2; + serial3 = &v2m_serial3; + }; + + cpus { + #address-cells = <1>; + #size-cells = <0>; + + cpu@0 { + device_type = "cpu"; + compatible = "arm,cortex-a15"; + reg = <0>; + }; + }; + + memory@80000000 { + device_type = "memory"; + reg = <0 0x80000000 0 0x80000000>; + }; + + gic: interrupt-controller@2c001000 { + compatible = "arm,cortex-a15-gic", "arm,cortex-a9-gic"; + #interrupt-cells = <3>; + #address-cells = <0>; + interrupt-controller; + reg = <0 0x2c001000 0 0x1000>, + <0 0x2c002000 0 0x1000>, + <0 0x2c004000 0 0x2000>, + <0 0x2c006000 0 0x2000>; + interrupts = <1 9 0xf04>; + }; + + timer { + compatible = "arm,armv7-timer"; + interrupts = <1 13 0xf08>, + <1 14 0xf08>, + <1 11 0xf08>, + <1 10 0xf08>; + }; + + motherboard { + ranges = <0 0 0 0x08000000 0x04000000>, + <1 0 0 0x14000000 0x04000000>, + <2 0 0 0x18000000 0x04000000>, + <3 0 0 0x1c000000 0x04000000>, + <4 0 0 0x0c000000 0x04000000>, + <5 0 0 0x10000000 0x04000000>; + + interrupt-map-mask = <0 0 63>; + interrupt-map = <0 0 0 &gic 0 0 4>, + <0 0 1 &gic 0 1 4>, + <0 0 2 &gic 0 2 4>, + <0 0 3 &gic 0 3 4>, + <0 0 4 &gic 0 4 4>, + <0 0 5 &gic 0 5 4>, + <0 0 6 &gic 0 6 4>, + <0 0 7 &gic 0 7 4>, + <0 0 8 &gic 0 8 4>, + <0 0 9 &gic 0 9 4>, + <0 0 10 &gic 0 10 4>, + <0 0 11 &gic 0 11 4>, + <0 0 12 &gic 0 12 4>, + <0 0 13 &gic 0 13 4>, + <0 0 14 &gic 0 14 4>, + <0 0 15 &gic 0 15 4>, + <0 0 16 &gic 0 16 4>, + <0 0 17 &gic 0 17 4>, + <0 0 18 &gic 0 18 4>, + <0 0 19 &gic 0 19 4>, + <0 0 20 &gic 0 20 4>, + <0 0 21 &gic 0 21 4>, + <0 0 22 &gic 0 22 4>, + <0 0 23 &gic 0 23 4>, + <0 0 24 &gic 0 24 4>, + <0 0 25 &gic 0 25 4>, + <0 0 26 &gic 0 26 4>, + <0 0 27 &gic 0 27 4>, + <0 0 28 &gic 0 28 4>, + <0 0 29 &gic 0 29 4>, + <0 0 30 &gic 0 30 4>, + <0 0 31 &gic 0 31 4>, + <0 0 32 &gic 0 32 4>, + <0 0 33 &gic 0 33 4>, + <0 0 34 &gic 0 34 4>, + <0 0 35 &gic 0 35 4>, + <0 0 36 &gic 0 36 4>, + <0 0 37 &gic 0 37 4>, + <0 0 38 &gic 0 38 4>, + <0 0 39 &gic 0 39 4>, + <0 0 40 &gic 0 40 4>, + <0 0 41 &gic 0 41 4>, + <0 0 42 &gic 0 42 4>; + }; +}; + +/include/ "rtsm_ve-motherboard.dtsi" diff --git a/arch/arm/boot/dts/rtsm_ve-cortex_a15x2.dts b/arch/arm/boot/dts/rtsm_ve-cortex_a15x2.dts new file mode 100644 index 00000000000..2a512f80abc --- /dev/null +++ b/arch/arm/boot/dts/rtsm_ve-cortex_a15x2.dts @@ -0,0 +1,125 @@ +/* + * ARM Ltd. Fast Models + * + * Versatile Express (VE) system model + * ARMCortexA15x2CT + * + * RTSM_VE_Cortex_A15x2.lisa + */ + +/dts-v1/; + +/ { + model = "RTSM_VE_CortexA15x2"; + compatible = "arm,rtsm_ve,cortex_a15x2", "arm,vexpress"; + interrupt-parent = <&gic>; + #address-cells = <2>; + #size-cells = <2>; + + chosen { }; + + aliases { + serial0 = &v2m_serial0; + serial1 = &v2m_serial1; + serial2 = &v2m_serial2; + serial3 = &v2m_serial3; + }; + + cpus { + #address-cells = <1>; + #size-cells = <0>; + + cpu@0 { + device_type = "cpu"; + compatible = "arm,cortex-a15"; + reg = <0>; + }; + + cpu@1 { + device_type = "cpu"; + compatible = "arm,cortex-a15"; + reg = <1>; + }; + }; + + memory@80000000 { + device_type = "memory"; + reg = <0 0x80000000 0 0x80000000>; + }; + + gic: interrupt-controller@2c001000 { + compatible = "arm,cortex-a15-gic", "arm,cortex-a9-gic"; + #interrupt-cells = <3>; + #address-cells = <0>; + interrupt-controller; + reg = <0 0x2c001000 0 0x1000>, + <0 0x2c002000 0 0x1000>, + <0 0x2c004000 0 0x2000>, + <0 0x2c006000 0 0x2000>; + interrupts = <1 9 0xf04>; + }; + + timer { + compatible = "arm,armv7-timer"; + interrupts = <1 13 0xf08>, + <1 14 0xf08>, + <1 11 0xf08>, + <1 10 0xf08>; + }; + + motherboard { + ranges = <0 0 0 0x08000000 0x04000000>, + <1 0 0 0x14000000 0x04000000>, + <2 0 0 0x18000000 0x04000000>, + <3 0 0 0x1c000000 0x04000000>, + <4 0 0 0x0c000000 0x04000000>, + <5 0 0 0x10000000 0x04000000>; + + interrupt-map-mask = <0 0 63>; + interrupt-map = <0 0 0 &gic 0 0 4>, + <0 0 1 &gic 0 1 4>, + <0 0 2 &gic 0 2 4>, + <0 0 3 &gic 0 3 4>, + <0 0 4 &gic 0 4 4>, + <0 0 5 &gic 0 5 4>, + <0 0 6 &gic 0 6 4>, + <0 0 7 &gic 0 7 4>, + <0 0 8 &gic 0 8 4>, + <0 0 9 &gic 0 9 4>, + <0 0 10 &gic 0 10 4>, + <0 0 11 &gic 0 11 4>, + <0 0 12 &gic 0 12 4>, + <0 0 13 &gic 0 13 4>, + <0 0 14 &gic 0 14 4>, + <0 0 15 &gic 0 15 4>, + <0 0 16 &gic 0 16 4>, + <0 0 17 &gic 0 17 4>, + <0 0 18 &gic 0 18 4>, + <0 0 19 &gic 0 19 4>, + <0 0 20 &gic 0 20 4>, + <0 0 21 &gic 0 21 4>, + <0 0 22 &gic 0 22 4>, + <0 0 23 &gic 0 23 4>, + <0 0 24 &gic 0 24 4>, + <0 0 25 &gic 0 25 4>, + <0 0 26 &gic 0 26 4>, + <0 0 27 &gic 0 27 4>, + <0 0 28 &gic 0 28 4>, + <0 0 29 &gic 0 29 4>, + <0 0 30 &gic 0 30 4>, + <0 0 31 &gic 0 31 4>, + <0 0 32 &gic 0 32 4>, + <0 0 33 &gic 0 33 4>, + <0 0 34 &gic 0 34 4>, + <0 0 35 &gic 0 35 4>, + <0 0 36 &gic 0 36 4>, + <0 0 37 &gic 0 37 4>, + <0 0 38 &gic 0 38 4>, + <0 0 39 &gic 0 39 4>, + <0 0 40 &gic 0 40 4>, + <0 0 41 &gic 0 41 4>, + <0 0 42 &gic 0 42 4>; + }; +}; + +/include/ "rtsm_ve-motherboard.dtsi" diff --git a/arch/arm/boot/dts/rtsm_ve-cortex_a15x4.dts b/arch/arm/boot/dts/rtsm_ve-cortex_a15x4.dts new file mode 100644 index 00000000000..f3d5eb7dbb9 --- /dev/null +++ b/arch/arm/boot/dts/rtsm_ve-cortex_a15x4.dts @@ -0,0 +1,137 @@ +/* + * ARM Ltd. Fast Models + * + * Versatile Express (VE) system model + * ARMCortexA15x4CT + * + * RTSM_VE_Cortex_A15x4.lisa + */ + +/dts-v1/; + +/ { + model = "RTSM_VE_CortexA15x4"; + compatible = "arm,rtsm_ve,cortex_a15x4", "arm,vexpress"; + interrupt-parent = <&gic>; + #address-cells = <2>; + #size-cells = <2>; + + chosen { }; + + aliases { + serial0 = &v2m_serial0; + serial1 = &v2m_serial1; + serial2 = &v2m_serial2; + serial3 = &v2m_serial3; + }; + + cpus { + #address-cells = <1>; + #size-cells = <0>; + + cpu@0 { + device_type = "cpu"; + compatible = "arm,cortex-a15"; + reg = <0>; + }; + + cpu@1 { + device_type = "cpu"; + compatible = "arm,cortex-a15"; + reg = <1>; + }; + + cpu@2 { + device_type = "cpu"; + compatible = "arm,cortex-a15"; + reg = <2>; + }; + + cpu@3 { + device_type = "cpu"; + compatible = "arm,cortex-a15"; + reg = <3>; + }; + }; + + memory@80000000 { + device_type = "memory"; + reg = <0 0x80000000 0 0x80000000>; + }; + + gic: interrupt-controller@2c001000 { + compatible = "arm,cortex-a15-gic", "arm,cortex-a9-gic"; + #interrupt-cells = <3>; + #address-cells = <0>; + interrupt-controller; + reg = <0 0x2c001000 0 0x1000>, + <0 0x2c002000 0 0x1000>, + <0 0x2c004000 0 0x2000>, + <0 0x2c006000 0 0x2000>; + interrupts = <1 9 0xf04>; + }; + + timer { + compatible = "arm,armv7-timer"; + interrupts = <1 13 0xf08>, + <1 14 0xf08>, + <1 11 0xf08>, + <1 10 0xf08>; + }; + + motherboard { + ranges = <0 0 0 0x08000000 0x04000000>, + <1 0 0 0x14000000 0x04000000>, + <2 0 0 0x18000000 0x04000000>, + <3 0 0 0x1c000000 0x04000000>, + <4 0 0 0x0c000000 0x04000000>, + <5 0 0 0x10000000 0x04000000>; + + interrupt-map-mask = <0 0 63>; + interrupt-map = <0 0 0 &gic 0 0 4>, + <0 0 1 &gic 0 1 4>, + <0 0 2 &gic 0 2 4>, + <0 0 3 &gic 0 3 4>, + <0 0 4 &gic 0 4 4>, + <0 0 5 &gic 0 5 4>, + <0 0 6 &gic 0 6 4>, + <0 0 7 &gic 0 7 4>, + <0 0 8 &gic 0 8 4>, + <0 0 9 &gic 0 9 4>, + <0 0 10 &gic 0 10 4>, + <0 0 11 &gic 0 11 4>, + <0 0 12 &gic 0 12 4>, + <0 0 13 &gic 0 13 4>, + <0 0 14 &gic 0 14 4>, + <0 0 15 &gic 0 15 4>, + <0 0 16 &gic 0 16 4>, + <0 0 17 &gic 0 17 4>, + <0 0 18 &gic 0 18 4>, + <0 0 19 &gic 0 19 4>, + <0 0 20 &gic 0 20 4>, + <0 0 21 &gic 0 21 4>, + <0 0 22 &gic 0 22 4>, + <0 0 23 &gic 0 23 4>, + <0 0 24 &gic 0 24 4>, + <0 0 25 &gic 0 25 4>, + <0 0 26 &gic 0 26 4>, + <0 0 27 &gic 0 27 4>, + <0 0 28 &gic 0 28 4>, + <0 0 29 &gic 0 29 4>, + <0 0 30 &gic 0 30 4>, + <0 0 31 &gic 0 31 4>, + <0 0 32 &gic 0 32 4>, + <0 0 33 &gic 0 33 4>, + <0 0 34 &gic 0 34 4>, + <0 0 35 &gic 0 35 4>, + <0 0 36 &gic 0 36 4>, + <0 0 37 &gic 0 37 4>, + <0 0 38 &gic 0 38 4>, + <0 0 39 &gic 0 39 4>, + <0 0 40 &gic 0 40 4>, + <0 0 41 &gic 0 41 4>, + <0 0 42 &gic 0 42 4>; + }; +}; + +/include/ "rtsm_ve-motherboard.dtsi" diff --git a/arch/arm/boot/dts/rtsm_ve-cortex_a9x2.dts b/arch/arm/boot/dts/rtsm_ve-cortex_a9x2.dts new file mode 100644 index 00000000000..e4379059b32 --- /dev/null +++ b/arch/arm/boot/dts/rtsm_ve-cortex_a9x2.dts @@ -0,0 +1,131 @@ +/* + * ARM Ltd. Fast Models + * + * Versatile Express (VE) system model + * ARMCortexA9MPx2CT + * + * RTSM_VE_Cortex_A9x2.lisa + */ + +/dts-v1/; + +/ { + model = "RTSM_VE_CortexA9x2"; + compatible = "arm,rtsm_ve,cortex_a9x2", "arm,vexpress"; + interrupt-parent = <&gic>; + #address-cells = <1>; + #size-cells = <1>; + + chosen { }; + + aliases { + serial0 = &v2m_serial0; + serial1 = &v2m_serial1; + serial2 = &v2m_serial2; + serial3 = &v2m_serial3; + }; + + cpus { + #address-cells = <1>; + #size-cells = <0>; + + cpu@0 { + device_type = "cpu"; + compatible = "arm,cortex-a9"; + reg = <0>; + }; + + cpu@1 { + device_type = "cpu"; + compatible = "arm,cortex-a9"; + reg = <1>; + }; + }; + + memory@80000000 { + device_type = "memory"; + reg = <0x80000000 0x80000000>; + }; + + scu@2c000000 { + compatible = "arm,cortex-a9-scu"; + reg = <0x2c000000 0x58>; + }; + + timer@2c000600 { + compatible = "arm,cortex-a9-twd-timer"; + reg = <0x2c000600 0x20>; + interrupts = <1 13 0xf04>; + }; + + watchdog@2c000620 { + compatible = "arm,cortex-a9-twd-wdt"; + reg = <0x2c000620 0x20>; + interrupts = <1 14 0xf04>; + }; + + gic: interrupt-controller@2c001000 { + compatible = "arm,cortex-a9-gic"; + #interrupt-cells = <3>; + #address-cells = <0>; + interrupt-controller; + reg = <0x2c001000 0x1000>, + <0x2c000100 0x100>; + }; + + motherboard { + ranges = <0 0 0x08000000 0x04000000>, + <1 0 0x14000000 0x04000000>, + <2 0 0x18000000 0x04000000>, + <3 0 0x1c000000 0x04000000>, + <4 0 0x0c000000 0x04000000>, + <5 0 0x10000000 0x04000000>; + + interrupt-map-mask = <0 0 63>; + interrupt-map = <0 0 0 &gic 0 0 4>, + <0 0 1 &gic 0 1 4>, + <0 0 2 &gic 0 2 4>, + <0 0 3 &gic 0 3 4>, + <0 0 4 &gic 0 4 4>, + <0 0 5 &gic 0 5 4>, + <0 0 6 &gic 0 6 4>, + <0 0 7 &gic 0 7 4>, + <0 0 8 &gic 0 8 4>, + <0 0 9 &gic 0 9 4>, + <0 0 10 &gic 0 10 4>, + <0 0 11 &gic 0 11 4>, + <0 0 12 &gic 0 12 4>, + <0 0 13 &gic 0 13 4>, + <0 0 14 &gic 0 14 4>, + <0 0 15 &gic 0 15 4>, + <0 0 16 &gic 0 16 4>, + <0 0 17 &gic 0 17 4>, + <0 0 18 &gic 0 18 4>, + <0 0 19 &gic 0 19 4>, + <0 0 20 &gic 0 20 4>, + <0 0 21 &gic 0 21 4>, + <0 0 22 &gic 0 22 4>, + <0 0 23 &gic 0 23 4>, + <0 0 24 &gic 0 24 4>, + <0 0 25 &gic 0 25 4>, + <0 0 26 &gic 0 26 4>, + <0 0 27 &gic 0 27 4>, + <0 0 28 &gic 0 28 4>, + <0 0 29 &gic 0 29 4>, + <0 0 30 &gic 0 30 4>, + <0 0 31 &gic 0 31 4>, + <0 0 32 &gic 0 32 4>, + <0 0 33 &gic 0 33 4>, + <0 0 34 &gic 0 34 4>, + <0 0 35 &gic 0 35 4>, + <0 0 36 &gic 0 36 4>, + <0 0 37 &gic 0 37 4>, + <0 0 38 &gic 0 38 4>, + <0 0 39 &gic 0 39 4>, + <0 0 40 &gic 0 40 4>, + <0 0 41 &gic 0 41 4>, + <0 0 42 &gic 0 42 4>; + }; +}; + +/include/ "rtsm_ve-motherboard.dtsi" diff --git a/arch/arm/boot/dts/rtsm_ve-cortex_a9x4.dts b/arch/arm/boot/dts/rtsm_ve-cortex_a9x4.dts new file mode 100644 index 00000000000..62fc613d250 --- /dev/null +++ b/arch/arm/boot/dts/rtsm_ve-cortex_a9x4.dts @@ -0,0 +1,143 @@ +/* + * ARM Ltd. Fast Models + * + * Versatile Express (VE) system model + * ARMCortexA9MPx4CT + * + * RTSM_VE_Cortex_A9x4.lisa + */ + +/dts-v1/; + +/ { + model = "RTSM_VE_CortexA9x4"; + compatible = "arm,rtsm_ve,cortex_a9x4", "arm,vexpress"; + interrupt-parent = <&gic>; + #address-cells = <1>; + #size-cells = <1>; + + chosen { }; + + aliases { + serial0 = &v2m_serial0; + serial1 = &v2m_serial1; + serial2 = &v2m_serial2; + serial3 = &v2m_serial3; + }; + + cpus { + #address-cells = <1>; + #size-cells = <0>; + + cpu@0 { + device_type = "cpu"; + compatible = "arm,cortex-a9"; + reg = <0>; + }; + + cpu@1 { + device_type = "cpu"; + compatible = "arm,cortex-a9"; + reg = <1>; + }; + + cpu@2 { + device_type = "cpu"; + compatible = "arm,cortex-a9"; + reg = <2>; + }; + + cpu@3 { + device_type = "cpu"; + compatible = "arm,cortex-a9"; + reg = <3>; + }; + }; + + memory@80000000 { + device_type = "memory"; + reg = <0x80000000 0x80000000>; + }; + + scu@2c000000 { + compatible = "arm,cortex-a9-scu"; + reg = <0x2c000000 0x58>; + }; + + timer@2c000600 { + compatible = "arm,cortex-a9-twd-timer"; + reg = <0x2c000600 0x20>; + interrupts = <1 13 0xf04>; + }; + + watchdog@2c000620 { + compatible = "arm,cortex-a9-twd-wdt"; + reg = <0x2c000620 0x20>; + interrupts = <1 14 0xf04>; + }; + + gic: interrupt-controller@2c001000 { + compatible = "arm,cortex-a9-gic"; + #interrupt-cells = <3>; + #address-cells = <0>; + interrupt-controller; + reg = <0x2c001000 0x1000>, + <0x2c000100 0x100>; + }; + + motherboard { + ranges = <0 0 0x08000000 0x04000000>, + <1 0 0x14000000 0x04000000>, + <2 0 0x18000000 0x04000000>, + <3 0 0x1c000000 0x04000000>, + <4 0 0x0c000000 0x04000000>, + <5 0 0x10000000 0x04000000>; + + interrupt-map-mask = <0 0 63>; + interrupt-map = <0 0 0 &gic 0 0 4>, + <0 0 1 &gic 0 1 4>, + <0 0 2 &gic 0 2 4>, + <0 0 3 &gic 0 3 4>, + <0 0 4 &gic 0 4 4>, + <0 0 5 &gic 0 5 4>, + <0 0 6 &gic 0 6 4>, + <0 0 7 &gic 0 7 4>, + <0 0 8 &gic 0 8 4>, + <0 0 9 &gic 0 9 4>, + <0 0 10 &gic 0 10 4>, + <0 0 11 &gic 0 11 4>, + <0 0 12 &gic 0 12 4>, + <0 0 13 &gic 0 13 4>, + <0 0 14 &gic 0 14 4>, + <0 0 15 &gic 0 15 4>, + <0 0 16 &gic 0 16 4>, + <0 0 17 &gic 0 17 4>, + <0 0 18 &gic 0 18 4>, + <0 0 19 &gic 0 19 4>, + <0 0 20 &gic 0 20 4>, + <0 0 21 &gic 0 21 4>, + <0 0 22 &gic 0 22 4>, + <0 0 23 &gic 0 23 4>, + <0 0 24 &gic 0 24 4>, + <0 0 25 &gic 0 25 4>, + <0 0 26 &gic 0 26 4>, + <0 0 27 &gic 0 27 4>, + <0 0 28 &gic 0 28 4>, + <0 0 29 &gic 0 29 4>, + <0 0 30 &gic 0 30 4>, + <0 0 31 &gic 0 31 4>, + <0 0 32 &gic 0 32 4>, + <0 0 33 &gic 0 33 4>, + <0 0 34 &gic 0 34 4>, + <0 0 35 &gic 0 35 4>, + <0 0 36 &gic 0 36 4>, + <0 0 37 &gic 0 37 4>, + <0 0 38 &gic 0 38 4>, + <0 0 39 &gic 0 39 4>, + <0 0 40 &gic 0 40 4>, + <0 0 41 &gic 0 41 4>, + <0 0 42 &gic 0 42 4>; + }; +}; + +/include/ "rtsm_ve-motherboard.dtsi" diff --git a/arch/arm/boot/dts/rtsm_ve-motherboard.dtsi b/arch/arm/boot/dts/rtsm_ve-motherboard.dtsi new file mode 100644 index 00000000000..302b8c5560f --- /dev/null +++ b/arch/arm/boot/dts/rtsm_ve-motherboard.dtsi @@ -0,0 +1,135 @@ +/* + * ARM Ltd. Fast Models + * + * Versatile Express (VE) system model + * Motherboard component + * + * VEMotherBoard.lisa + */ + +/ { + aliases { + arm,v2m_timer = &v2m_timer01; + }; + + motherboard { + compatible = "simple-bus"; + arm,v2m-memory-map = "rs1"; + #address-cells = <2>; /* SMB chipselect number and offset */ + #size-cells = <1>; + #interrupt-cells = <1>; + + flash@0,00000000 { + compatible = "arm,vexpress-flash", "cfi-flash"; + reg = <0 0x00000000 0x04000000>, + <4 0x00000000 0x04000000>; + bank-width = <4>; + }; + + vram@2,00000000 { + compatible = "arm,vexpress-vram"; + reg = <2 0x00000000 0x00800000>; + }; + + ethernet@2,02000000 { + compatible = "smsc,lan91c111"; + reg = <2 0x02000000 0x10000>; + interrupts = <15>; + }; + + iofpga@3,00000000 { + compatible = "arm,amba-bus", "simple-bus"; + #address-cells = <1>; + #size-cells = <1>; + ranges = <0 3 0 0x200000>; + + sysreg@010000 { + compatible = "arm,vexpress-sysreg"; + reg = <0x010000 0x1000>; + }; + + sysctl@020000 { + compatible = "arm,sp810", "arm,primecell"; + reg = <0x020000 0x1000>; + }; + + aaci@040000 { + compatible = "arm,pl041", "arm,primecell"; + reg = <0x040000 0x1000>; + interrupts = <11>; + }; + + mmci@050000 { + compatible = "arm,pl180", "arm,primecell"; + reg = <0x050000 0x1000>; + interrupts = <9 10>; + }; + + kmi@060000 { + compatible = "arm,pl050", "arm,primecell"; + reg = <0x060000 0x1000>; + interrupts = <12>; + }; + + kmi@070000 { + compatible = "arm,pl050", "arm,primecell"; + reg = <0x070000 0x1000>; + interrupts = <13>; + }; + + v2m_serial0: uart@090000 { + compatible = "arm,pl011", "arm,primecell"; + reg = <0x090000 0x1000>; + interrupts = <5>; + }; + + v2m_serial1: uart@0a0000 { + compatible = "arm,pl011", "arm,primecell"; + reg = <0x0a0000 0x1000>; + interrupts = <6>; + }; + + v2m_serial2: uart@0b0000 { + compatible = "arm,pl011", "arm,primecell"; + reg = <0x0b0000 0x1000>; + interrupts = <7>; + }; + + v2m_serial3: uart@0c0000 { + compatible = "arm,pl011", "arm,primecell"; + reg = <0x0c0000 0x1000>; + interrupts = <8>; + }; + + wdt@0f0000 { + compatible = "arm,sp805", "arm,primecell"; + reg = <0x0f0000 0x1000>; + interrupts = <0>; + }; + + v2m_timer01: timer@110000 { + compatible = "arm,sp804", "arm,primecell"; + reg = <0x110000 0x1000>; + interrupts = <2>; + }; + + v2m_timer23: timer@120000 { + compatible = "arm,sp804", "arm,primecell"; + reg = <0x120000 0x1000>; + interrupts = <3>; + }; + + rtc@170000 { + compatible = "arm,pl031", "arm,primecell"; + reg = <0x170000 0x1000>; + interrupts = <4>; + }; + + clcd@1f0000 { + compatible = "arm,pl111", "arm,primecell"; + reg = <0x1f0000 0x1000>; + interrupts = <14>; + }; + }; + }; +}; diff --git a/arch/arm/boot/dts/rtsm_ve-v2p-ca15x1-ca7x1.dts b/arch/arm/boot/dts/rtsm_ve-v2p-ca15x1-ca7x1.dts new file mode 100644 index 00000000000..5d4a858240d --- /dev/null +++ b/arch/arm/boot/dts/rtsm_ve-v2p-ca15x1-ca7x1.dts @@ -0,0 +1,182 @@ +/* + * ARM Ltd. Fast Models + * + * Versatile Express (VE) system model + * ARMCortexA15x4CT + * ARMCortexA7x4CT + * RTSM_VE_Cortex_A15x1_A7x1.lisa + */ + +/dts-v1/; + +/memreserve/ 0xff000000 0x01000000; + +/ { + model = "RTSM_VE_CortexA15x1-A7x1"; + compatible = "arm,rtsm_ve,cortex_a15x1_a7x1", "arm,vexpress"; + interrupt-parent = <&gic>; + #address-cells = <2>; + #size-cells = <2>; + + chosen { }; + + aliases { + serial0 = &v2m_serial0; + serial1 = &v2m_serial1; + serial2 = &v2m_serial2; + serial3 = &v2m_serial3; + }; + + clusters { + #address-cells = <1>; + #size-cells = <0>; + + cluster0: cluster@0 { + reg = <0>; +// freqs = <500000000 600000000 700000000 800000000 900000000 1000000000 1100000000 1200000000>; + cores { + #address-cells = <1>; + #size-cells = <0>; + + core0: core@0 { + reg = <0>; + }; + + }; + }; + + cluster1: cluster@1 { + reg = <1>; +// freqs = <350000000 400000000 500000000 600000000 700000000 800000000 900000000 1000000000>; + cores { + #address-cells = <1>; + #size-cells = <0>; + + core1: core@0 { + reg = <0>; + }; + + }; + }; + }; + + cpus { + #address-cells = <1>; + #size-cells = <0>; + + cpu0: cpu@0 { + device_type = "cpu"; + compatible = "arm,cortex-a15"; + reg = <0>; + cluster = <&cluster0>; + core = <&core0>; +// clock-frequency = <1000000000>; + }; + + cpu1: cpu@1 { + device_type = "cpu"; + compatible = "arm,cortex-a7"; + reg = <0x100>; + cluster = <&cluster1>; + core = <&core1>; +// clock-frequency = <800000000>; + }; + }; + + memory@80000000 { + device_type = "memory"; + reg = <0 0x80000000 0 0x80000000>; + }; + + cci@2c090000 { + compatible = "arm,cci"; + reg = <0 0x2c090000 0 0x8000>; + }; + + gic: interrupt-controller@2c001000 { + compatible = "arm,cortex-a15-gic", "arm,cortex-a9-gic"; + #interrupt-cells = <3>; + #address-cells = <0>; + interrupt-controller; + reg = <0 0x2c001000 0 0x1000>, + <0 0x2c002000 0 0x1000>, + <0 0x2c004000 0 0x2000>, + <0 0x2c006000 0 0x2000>; + interrupts = <1 9 0xf04>; + + gic-cpuif@0 { + compatible = "arm,gic-cpuif"; + cpuif-id = <0>; + cpu = <&cpu0>; + }; + gic-cpuif@1 { + compatible = "arm,gic-cpuif"; + cpuif-id = <1>; + cpu = <&cpu1>; + }; + }; + + timer { + compatible = "arm,armv7-timer"; + interrupts = <1 13 0xf08>, + <1 14 0xf08>, + <1 11 0xf08>, + <1 10 0xf08>; + }; + + motherboard { + ranges = <0 0 0 0x08000000 0x04000000>, + <1 0 0 0x14000000 0x04000000>, + <2 0 0 0x18000000 0x04000000>, + <3 0 0 0x1c000000 0x04000000>, + <4 0 0 0x0c000000 0x04000000>, + <5 0 0 0x10000000 0x04000000>; + + interrupt-map-mask = <0 0 63>; + interrupt-map = <0 0 0 &gic 0 0 4>, + <0 0 1 &gic 0 1 4>, + <0 0 2 &gic 0 2 4>, + <0 0 3 &gic 0 3 4>, + <0 0 4 &gic 0 4 4>, + <0 0 5 &gic 0 5 4>, + <0 0 6 &gic 0 6 4>, + <0 0 7 &gic 0 7 4>, + <0 0 8 &gic 0 8 4>, + <0 0 9 &gic 0 9 4>, + <0 0 10 &gic 0 10 4>, + <0 0 11 &gic 0 11 4>, + <0 0 12 &gic 0 12 4>, + <0 0 13 &gic 0 13 4>, + <0 0 14 &gic 0 14 4>, + <0 0 15 &gic 0 15 4>, + <0 0 16 &gic 0 16 4>, + <0 0 17 &gic 0 17 4>, + <0 0 18 &gic 0 18 4>, + <0 0 19 &gic 0 19 4>, + <0 0 20 &gic 0 20 4>, + <0 0 21 &gic 0 21 4>, + <0 0 22 &gic 0 22 4>, + <0 0 23 &gic 0 23 4>, + <0 0 24 &gic 0 24 4>, + <0 0 25 &gic 0 25 4>, + <0 0 26 &gic 0 26 4>, + <0 0 27 &gic 0 27 4>, + <0 0 28 &gic 0 28 4>, + <0 0 29 &gic 0 29 4>, + <0 0 30 &gic 0 30 4>, + <0 0 31 &gic 0 31 4>, + <0 0 32 &gic 0 32 4>, + <0 0 33 &gic 0 33 4>, + <0 0 34 &gic 0 34 4>, + <0 0 35 &gic 0 35 4>, + <0 0 36 &gic 0 36 4>, + <0 0 37 &gic 0 37 4>, + <0 0 38 &gic 0 38 4>, + <0 0 39 &gic 0 39 4>, + <0 0 40 &gic 0 40 4>, + <0 0 41 &gic 0 41 4>, + <0 0 42 &gic 0 42 4>; + }; +}; + +/include/ "rtsm_ve-motherboard.dtsi" \ No newline at end of file diff --git a/arch/arm/boot/dts/rtsm_ve-v2p-ca15x4-ca7x4.dts b/arch/arm/boot/dts/rtsm_ve-v2p-ca15x4-ca7x4.dts new file mode 100644 index 00000000000..277bed5c716 --- /dev/null +++ b/arch/arm/boot/dts/rtsm_ve-v2p-ca15x4-ca7x4.dts @@ -0,0 +1,290 @@ +/* + * ARM Ltd. Fast Models + * + * Versatile Express (VE) system model + * ARMCortexA15x4CT + * ARMCortexA7x4CT + * RTSM_VE_Cortex_A15x4_A7x4.lisa + */ + +/dts-v1/; + +/memreserve/ 0xff000000 0x01000000; + +/ { + model = "RTSM_VE_CortexA15x4-A7x4"; + compatible = "arm,rtsm_ve,cortex_a15x4_a7x4", "arm,vexpress"; + interrupt-parent = <&gic>; + #address-cells = <2>; + #size-cells = <2>; + + chosen { }; + + aliases { + serial0 = &v2m_serial0; + serial1 = &v2m_serial1; + serial2 = &v2m_serial2; + serial3 = &v2m_serial3; + }; + + clusters { + #address-cells = <1>; + #size-cells = <0>; + + cluster0: cluster@0 { + reg = <0>; +// freqs = <500000000 600000000 700000000 800000000 900000000 1000000000 1100000000 1200000000>; + cores { + #address-cells = <1>; + #size-cells = <0>; + + core0: core@0 { + reg = <0>; + }; + + core1: core@1 { + reg = <1>; + }; + + core2: core@2 { + reg = <2>; + }; + + core3: core@3 { + reg = <3>; + }; + + }; + }; + + cluster1: cluster@1 { + reg = <1>; +// freqs = <350000000 400000000 500000000 600000000 700000000 800000000 900000000 1000000000>; + cores { + #address-cells = <1>; + #size-cells = <0>; + + core4: core@0 { + reg = <0>; + }; + + core5: core@1 { + reg = <1>; + }; + + core6: core@2 { + reg = <2>; + }; + + core7: core@3 { + reg = <3>; + }; + + }; + }; + }; + + cpus { + #address-cells = <1>; + #size-cells = <0>; + + cpu0: cpu@0 { + device_type = "cpu"; + compatible = "arm,cortex-a15"; + reg = <0>; + cluster = <&cluster0>; + core = <&core0>; +// clock-frequency = <1000000000>; + }; + + cpu1: cpu@1 { + device_type = "cpu"; + compatible = "arm,cortex-a15"; + reg = <1>; + cluster = <&cluster0>; + core = <&core1>; +// clock-frequency = <1000000000>; + }; + + cpu2: cpu@2 { + device_type = "cpu"; + compatible = "arm,cortex-a15"; + reg = <2>; + cluster = <&cluster0>; + core = <&core2>; +// clock-frequency = <1000000000>; + }; + + cpu3: cpu@3 { + device_type = "cpu"; + compatible = "arm,cortex-a15"; + reg = <3>; + cluster = <&cluster0>; + core = <&core3>; +// clock-frequency = <1000000000>; + }; + + cpu4: cpu@4 { + device_type = "cpu"; + compatible = "arm,cortex-a7"; + reg = <0x100>; + cluster = <&cluster1>; + core = <&core4>; +// clock-frequency = <800000000>; + }; + + cpu5: cpu@5 { + device_type = "cpu"; + compatible = "arm,cortex-a7"; + reg = <0x101>; + cluster = <&cluster1>; + core = <&core5>; +// clock-frequency = <800000000>; + }; + + cpu6: cpu@6 { + device_type = "cpu"; + compatible = "arm,cortex-a7"; + reg = <0x102>; + cluster = <&cluster1>; + core = <&core6>; +// clock-frequency = <800000000>; + }; + + cpu7: cpu@7 { + device_type = "cpu"; + compatible = "arm,cortex-a7"; + reg = <0x103>; + cluster = <&cluster1>; + core = <&core7>; +// clock-frequency = <800000000>; + }; + }; + + memory@80000000 { + device_type = "memory"; + reg = <0 0x80000000 0 0x80000000>; + }; + + cci@2c090000 { + compatible = "arm,cci"; + reg = <0 0x2c090000 0 0x8000>; + }; + + gic: interrupt-controller@2c001000 { + compatible = "arm,cortex-a15-gic", "arm,cortex-a9-gic"; + #interrupt-cells = <3>; + #address-cells = <0>; + interrupt-controller; + reg = <0 0x2c001000 0 0x1000>, + <0 0x2c002000 0 0x1000>, + <0 0x2c004000 0 0x2000>, + <0 0x2c006000 0 0x2000>; + interrupts = <1 9 0xf04>; + + gic-cpuif@0 { + compatible = "arm,gic-cpuif"; + cpuif-id = <0>; + cpu = <&cpu0>; + }; + gic-cpuif@1 { + compatible = "arm,gic-cpuif"; + cpuif-id = <1>; + cpu = <&cpu1>; + }; + gic-cpuif@2 { + compatible = "arm,gic-cpuif"; + cpuif-id = <2>; + cpu = <&cpu2>; + }; + gic-cpuif@3 { + compatible = "arm,gic-cpuif"; + cpuif-id = <3>; + cpu = <&cpu3>; + }; + gic-cpuif@4 { + compatible = "arm,gic-cpuif"; + cpuif-id = <4>; + cpu = <&cpu4>; + }; + gic-cpuif@5 { + compatible = "arm,gic-cpuif"; + cpuif-id = <5>; + cpu = <&cpu5>; + }; + gic-cpuif@6 { + compatible = "arm,gic-cpuif"; + cpuif-id = <6>; + cpu = <&cpu6>; + }; + gic-cpuif@7 { + compatible = "arm,gic-cpuif"; + cpuif-id = <7>; + cpu = <&cpu7>; + }; + }; + + timer { + compatible = "arm,armv7-timer"; + interrupts = <1 13 0xf08>, + <1 14 0xf08>, + <1 11 0xf08>, + <1 10 0xf08>; + }; + + motherboard { + ranges = <0 0 0 0x08000000 0x04000000>, + <1 0 0 0x14000000 0x04000000>, + <2 0 0 0x18000000 0x04000000>, + <3 0 0 0x1c000000 0x04000000>, + <4 0 0 0x0c000000 0x04000000>, + <5 0 0 0x10000000 0x04000000>; + + interrupt-map-mask = <0 0 63>; + interrupt-map = <0 0 0 &gic 0 0 4>, + <0 0 1 &gic 0 1 4>, + <0 0 2 &gic 0 2 4>, + <0 0 3 &gic 0 3 4>, + <0 0 4 &gic 0 4 4>, + <0 0 5 &gic 0 5 4>, + <0 0 6 &gic 0 6 4>, + <0 0 7 &gic 0 7 4>, + <0 0 8 &gic 0 8 4>, + <0 0 9 &gic 0 9 4>, + <0 0 10 &gic 0 10 4>, + <0 0 11 &gic 0 11 4>, + <0 0 12 &gic 0 12 4>, + <0 0 13 &gic 0 13 4>, + <0 0 14 &gic 0 14 4>, + <0 0 15 &gic 0 15 4>, + <0 0 16 &gic 0 16 4>, + <0 0 17 &gic 0 17 4>, + <0 0 18 &gic 0 18 4>, + <0 0 19 &gic 0 19 4>, + <0 0 20 &gic 0 20 4>, + <0 0 21 &gic 0 21 4>, + <0 0 22 &gic 0 22 4>, + <0 0 23 &gic 0 23 4>, + <0 0 24 &gic 0 24 4>, + <0 0 25 &gic 0 25 4>, + <0 0 26 &gic 0 26 4>, + <0 0 27 &gic 0 27 4>, + <0 0 28 &gic 0 28 4>, + <0 0 29 &gic 0 29 4>, + <0 0 30 &gic 0 30 4>, + <0 0 31 &gic 0 31 4>, + <0 0 32 &gic 0 32 4>, + <0 0 33 &gic 0 33 4>, + <0 0 34 &gic 0 34 4>, + <0 0 35 &gic 0 35 4>, + <0 0 36 &gic 0 36 4>, + <0 0 37 &gic 0 37 4>, + <0 0 38 &gic 0 38 4>, + <0 0 39 &gic 0 39 4>, + <0 0 40 &gic 0 40 4>, + <0 0 41 &gic 0 41 4>, + <0 0 42 &gic 0 42 4>; + }; +}; + +/include/ "rtsm_ve-motherboard.dtsi" \ No newline at end of file -- cgit v1.2.3 From ecc1f5b6c9ea90f23410553980d672dd766508c2 Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Fri, 28 Sep 2012 13:22:23 +0100 Subject: ARM: dts: Add RTSM dtb's to Makefile.boot So they are built by 'make dtbs' and so will appear in Linaro's Ubuntu kernel package. Signed-off-by: Jon Medhurst --- arch/arm/boot/dts/Makefile | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/arch/arm/boot/dts/Makefile b/arch/arm/boot/dts/Makefile index f0895c581a8..00baf9f5766 100644 --- a/arch/arm/boot/dts/Makefile +++ b/arch/arm/boot/dts/Makefile @@ -202,7 +202,14 @@ dtb-$(CONFIG_ARCH_VERSATILE) += versatile-ab.dtb \ dtb-$(CONFIG_ARCH_VEXPRESS) += vexpress-v2p-ca5s.dtb \ vexpress-v2p-ca9.dtb \ vexpress-v2p-ca15-tc1.dtb \ - vexpress-v2p-ca15_a7.dtb + vexpress-v2p-ca15_a7.dtb \ + rtsm_ve-cortex_a9x2.dtb \ + rtsm_ve-cortex_a9x4.dtb \ + rtsm_ve-cortex_a15x1.dtb \ + rtsm_ve-cortex_a15x2.dtb \ + rtsm_ve-cortex_a15x4.dtb \ + rtsm_ve-v2p-ca15x1-ca7x1.dtb \ + rtsm_ve-v2p-ca15x4-ca7x4.dtb dtb-$(CONFIG_ARCH_VIRT) += xenvm-4.2.dtb dtb-$(CONFIG_ARCH_VT8500) += vt8500-bv07.dtb \ wm8505-ref.dtb \ -- cgit v1.2.3 From cec71182328f37b7bc3740e982f01548b816511e Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Wed, 30 Jan 2013 12:02:46 +0000 Subject: ARM: dts: Changes to get RTSM working with Linux 3.8 Signed-off-by: Jon Medhurst --- arch/arm/boot/dts/rtsm_ve-cortex_a15x1.dts | 44 ++++++++++- arch/arm/boot/dts/rtsm_ve-cortex_a15x2.dts | 44 ++++++++++- arch/arm/boot/dts/rtsm_ve-cortex_a15x4.dts | 44 ++++++++++- arch/arm/boot/dts/rtsm_ve-cortex_a9x2.dts | 44 ++++++++++- arch/arm/boot/dts/rtsm_ve-cortex_a9x4.dts | 44 ++++++++++- arch/arm/boot/dts/rtsm_ve-motherboard.dtsi | 104 ++++++++++++++++++++++--- arch/arm/boot/dts/rtsm_ve-v2p-ca15x1-ca7x1.dts | 44 ++++++++++- arch/arm/boot/dts/rtsm_ve-v2p-ca15x4-ca7x4.dts | 44 ++++++++++- 8 files changed, 389 insertions(+), 23 deletions(-) diff --git a/arch/arm/boot/dts/rtsm_ve-cortex_a15x1.dts b/arch/arm/boot/dts/rtsm_ve-cortex_a15x1.dts index 44511dff9d9..c9eee916aa7 100644 --- a/arch/arm/boot/dts/rtsm_ve-cortex_a15x1.dts +++ b/arch/arm/boot/dts/rtsm_ve-cortex_a15x1.dts @@ -11,6 +11,7 @@ / { model = "RTSM_VE_CortexA15x1"; + arm,vexpress,site = <0xf>; compatible = "arm,rtsm_ve,cortex_a15x1", "arm,vexpress"; interrupt-parent = <&gic>; #address-cells = <2>; @@ -61,7 +62,43 @@ <1 10 0xf08>; }; - motherboard { + dcc { + compatible = "arm,vexpress,config-bus"; + arm,vexpress,config-bridge = <&v2m_sysreg>; + + osc@0 { + /* ACLK clock to the AXI master port on the test chip */ + compatible = "arm,vexpress-osc"; + arm,vexpress-sysreg,func = <1 0>; + freq-range = <30000000 50000000>; + #clock-cells = <0>; + clock-output-names = "extsaxiclk"; + }; + + oscclk1: osc@1 { + /* Reference clock for the CLCD */ + compatible = "arm,vexpress-osc"; + arm,vexpress-sysreg,func = <1 1>; + freq-range = <10000000 80000000>; + #clock-cells = <0>; + clock-output-names = "clcdclk"; + }; + + smbclk: oscclk2: osc@2 { + /* Reference clock for the test chip internal PLLs */ + compatible = "arm,vexpress-osc"; + arm,vexpress-sysreg,func = <1 2>; + freq-range = <33000000 100000000>; + #clock-cells = <0>; + clock-output-names = "tcrefclk"; + }; + }; + + smb { + compatible = "simple-bus"; + + #address-cells = <2>; + #size-cells = <1>; ranges = <0 0 0 0x08000000 0x04000000>, <1 0 0 0x14000000 0x04000000>, <2 0 0 0x18000000 0x04000000>, @@ -69,6 +106,7 @@ <4 0 0 0x0c000000 0x04000000>, <5 0 0 0x10000000 0x04000000>; + #interrupt-cells = <1>; interrupt-map-mask = <0 0 63>; interrupt-map = <0 0 0 &gic 0 0 4>, <0 0 1 &gic 0 1 4>, @@ -113,7 +151,9 @@ <0 0 40 &gic 0 40 4>, <0 0 41 &gic 0 41 4>, <0 0 42 &gic 0 42 4>; + + /include/ "rtsm_ve-motherboard.dtsi" }; }; -/include/ "rtsm_ve-motherboard.dtsi" +/include/ "clcd-panels.dtsi" diff --git a/arch/arm/boot/dts/rtsm_ve-cortex_a15x2.dts b/arch/arm/boot/dts/rtsm_ve-cortex_a15x2.dts index 2a512f80abc..853a166e3c3 100644 --- a/arch/arm/boot/dts/rtsm_ve-cortex_a15x2.dts +++ b/arch/arm/boot/dts/rtsm_ve-cortex_a15x2.dts @@ -11,6 +11,7 @@ / { model = "RTSM_VE_CortexA15x2"; + arm,vexpress,site = <0xf>; compatible = "arm,rtsm_ve,cortex_a15x2", "arm,vexpress"; interrupt-parent = <&gic>; #address-cells = <2>; @@ -67,7 +68,43 @@ <1 10 0xf08>; }; - motherboard { + dcc { + compatible = "arm,vexpress,config-bus"; + arm,vexpress,config-bridge = <&v2m_sysreg>; + + osc@0 { + /* ACLK clock to the AXI master port on the test chip */ + compatible = "arm,vexpress-osc"; + arm,vexpress-sysreg,func = <1 0>; + freq-range = <30000000 50000000>; + #clock-cells = <0>; + clock-output-names = "extsaxiclk"; + }; + + oscclk1: osc@1 { + /* Reference clock for the CLCD */ + compatible = "arm,vexpress-osc"; + arm,vexpress-sysreg,func = <1 1>; + freq-range = <10000000 80000000>; + #clock-cells = <0>; + clock-output-names = "clcdclk"; + }; + + smbclk: oscclk2: osc@2 { + /* Reference clock for the test chip internal PLLs */ + compatible = "arm,vexpress-osc"; + arm,vexpress-sysreg,func = <1 2>; + freq-range = <33000000 100000000>; + #clock-cells = <0>; + clock-output-names = "tcrefclk"; + }; + }; + + smb { + compatible = "simple-bus"; + + #address-cells = <2>; + #size-cells = <1>; ranges = <0 0 0 0x08000000 0x04000000>, <1 0 0 0x14000000 0x04000000>, <2 0 0 0x18000000 0x04000000>, @@ -75,6 +112,7 @@ <4 0 0 0x0c000000 0x04000000>, <5 0 0 0x10000000 0x04000000>; + #interrupt-cells = <1>; interrupt-map-mask = <0 0 63>; interrupt-map = <0 0 0 &gic 0 0 4>, <0 0 1 &gic 0 1 4>, @@ -119,7 +157,9 @@ <0 0 40 &gic 0 40 4>, <0 0 41 &gic 0 41 4>, <0 0 42 &gic 0 42 4>; + + /include/ "rtsm_ve-motherboard.dtsi" }; }; -/include/ "rtsm_ve-motherboard.dtsi" +/include/ "clcd-panels.dtsi" diff --git a/arch/arm/boot/dts/rtsm_ve-cortex_a15x4.dts b/arch/arm/boot/dts/rtsm_ve-cortex_a15x4.dts index f3d5eb7dbb9..c1947a3a5c8 100644 --- a/arch/arm/boot/dts/rtsm_ve-cortex_a15x4.dts +++ b/arch/arm/boot/dts/rtsm_ve-cortex_a15x4.dts @@ -11,6 +11,7 @@ / { model = "RTSM_VE_CortexA15x4"; + arm,vexpress,site = <0xf>; compatible = "arm,rtsm_ve,cortex_a15x4", "arm,vexpress"; interrupt-parent = <&gic>; #address-cells = <2>; @@ -79,7 +80,43 @@ <1 10 0xf08>; }; - motherboard { + dcc { + compatible = "arm,vexpress,config-bus"; + arm,vexpress,config-bridge = <&v2m_sysreg>; + + osc@0 { + /* ACLK clock to the AXI master port on the test chip */ + compatible = "arm,vexpress-osc"; + arm,vexpress-sysreg,func = <1 0>; + freq-range = <30000000 50000000>; + #clock-cells = <0>; + clock-output-names = "extsaxiclk"; + }; + + oscclk1: osc@1 { + /* Reference clock for the CLCD */ + compatible = "arm,vexpress-osc"; + arm,vexpress-sysreg,func = <1 1>; + freq-range = <10000000 80000000>; + #clock-cells = <0>; + clock-output-names = "clcdclk"; + }; + + smbclk: oscclk2: osc@2 { + /* Reference clock for the test chip internal PLLs */ + compatible = "arm,vexpress-osc"; + arm,vexpress-sysreg,func = <1 2>; + freq-range = <33000000 100000000>; + #clock-cells = <0>; + clock-output-names = "tcrefclk"; + }; + }; + + smb { + compatible = "simple-bus"; + + #address-cells = <2>; + #size-cells = <1>; ranges = <0 0 0 0x08000000 0x04000000>, <1 0 0 0x14000000 0x04000000>, <2 0 0 0x18000000 0x04000000>, @@ -87,6 +124,7 @@ <4 0 0 0x0c000000 0x04000000>, <5 0 0 0x10000000 0x04000000>; + #interrupt-cells = <1>; interrupt-map-mask = <0 0 63>; interrupt-map = <0 0 0 &gic 0 0 4>, <0 0 1 &gic 0 1 4>, @@ -131,7 +169,9 @@ <0 0 40 &gic 0 40 4>, <0 0 41 &gic 0 41 4>, <0 0 42 &gic 0 42 4>; + + /include/ "rtsm_ve-motherboard.dtsi" }; }; -/include/ "rtsm_ve-motherboard.dtsi" +/include/ "clcd-panels.dtsi" diff --git a/arch/arm/boot/dts/rtsm_ve-cortex_a9x2.dts b/arch/arm/boot/dts/rtsm_ve-cortex_a9x2.dts index e4379059b32..fca6b2f7967 100644 --- a/arch/arm/boot/dts/rtsm_ve-cortex_a9x2.dts +++ b/arch/arm/boot/dts/rtsm_ve-cortex_a9x2.dts @@ -11,6 +11,7 @@ / { model = "RTSM_VE_CortexA9x2"; + arm,vexpress,site = <0xf>; compatible = "arm,rtsm_ve,cortex_a9x2", "arm,vexpress"; interrupt-parent = <&gic>; #address-cells = <1>; @@ -73,7 +74,43 @@ <0x2c000100 0x100>; }; - motherboard { + dcc { + compatible = "arm,vexpress,config-bus"; + arm,vexpress,config-bridge = <&v2m_sysreg>; + + osc@0 { + /* ACLK clock to the AXI master port on the test chip */ + compatible = "arm,vexpress-osc"; + arm,vexpress-sysreg,func = <1 0>; + freq-range = <30000000 50000000>; + #clock-cells = <0>; + clock-output-names = "extsaxiclk"; + }; + + oscclk1: osc@1 { + /* Reference clock for the CLCD */ + compatible = "arm,vexpress-osc"; + arm,vexpress-sysreg,func = <1 1>; + freq-range = <10000000 80000000>; + #clock-cells = <0>; + clock-output-names = "clcdclk"; + }; + + smbclk: oscclk2: osc@2 { + /* Reference clock for the test chip internal PLLs */ + compatible = "arm,vexpress-osc"; + arm,vexpress-sysreg,func = <1 2>; + freq-range = <33000000 100000000>; + #clock-cells = <0>; + clock-output-names = "tcrefclk"; + }; + }; + + smb { + compatible = "simple-bus"; + + #address-cells = <2>; + #size-cells = <1>; ranges = <0 0 0x08000000 0x04000000>, <1 0 0x14000000 0x04000000>, <2 0 0x18000000 0x04000000>, @@ -81,6 +118,7 @@ <4 0 0x0c000000 0x04000000>, <5 0 0x10000000 0x04000000>; + #interrupt-cells = <1>; interrupt-map-mask = <0 0 63>; interrupt-map = <0 0 0 &gic 0 0 4>, <0 0 1 &gic 0 1 4>, @@ -125,7 +163,9 @@ <0 0 40 &gic 0 40 4>, <0 0 41 &gic 0 41 4>, <0 0 42 &gic 0 42 4>; + + /include/ "rtsm_ve-motherboard.dtsi" }; }; -/include/ "rtsm_ve-motherboard.dtsi" +/include/ "clcd-panels.dtsi" diff --git a/arch/arm/boot/dts/rtsm_ve-cortex_a9x4.dts b/arch/arm/boot/dts/rtsm_ve-cortex_a9x4.dts index 62fc613d250..fd8a6ed97a0 100644 --- a/arch/arm/boot/dts/rtsm_ve-cortex_a9x4.dts +++ b/arch/arm/boot/dts/rtsm_ve-cortex_a9x4.dts @@ -11,6 +11,7 @@ / { model = "RTSM_VE_CortexA9x4"; + arm,vexpress,site = <0xf>; compatible = "arm,rtsm_ve,cortex_a9x4", "arm,vexpress"; interrupt-parent = <&gic>; #address-cells = <1>; @@ -85,7 +86,43 @@ <0x2c000100 0x100>; }; - motherboard { + dcc { + compatible = "arm,vexpress,config-bus"; + arm,vexpress,config-bridge = <&v2m_sysreg>; + + osc@0 { + /* ACLK clock to the AXI master port on the test chip */ + compatible = "arm,vexpress-osc"; + arm,vexpress-sysreg,func = <1 0>; + freq-range = <30000000 50000000>; + #clock-cells = <0>; + clock-output-names = "extsaxiclk"; + }; + + oscclk1: osc@1 { + /* Reference clock for the CLCD */ + compatible = "arm,vexpress-osc"; + arm,vexpress-sysreg,func = <1 1>; + freq-range = <10000000 80000000>; + #clock-cells = <0>; + clock-output-names = "clcdclk"; + }; + + smbclk: oscclk2: osc@2 { + /* Reference clock for the test chip internal PLLs */ + compatible = "arm,vexpress-osc"; + arm,vexpress-sysreg,func = <1 2>; + freq-range = <33000000 100000000>; + #clock-cells = <0>; + clock-output-names = "tcrefclk"; + }; + }; + + smb { + compatible = "simple-bus"; + + #address-cells = <2>; + #size-cells = <1>; ranges = <0 0 0x08000000 0x04000000>, <1 0 0x14000000 0x04000000>, <2 0 0x18000000 0x04000000>, @@ -93,6 +130,7 @@ <4 0 0x0c000000 0x04000000>, <5 0 0x10000000 0x04000000>; + #interrupt-cells = <1>; interrupt-map-mask = <0 0 63>; interrupt-map = <0 0 0 &gic 0 0 4>, <0 0 1 &gic 0 1 4>, @@ -137,7 +175,9 @@ <0 0 40 &gic 0 40 4>, <0 0 41 &gic 0 41 4>, <0 0 42 &gic 0 42 4>; + + /include/ "rtsm_ve-motherboard.dtsi" }; }; -/include/ "rtsm_ve-motherboard.dtsi" +/include/ "clcd-panels.dtsi" diff --git a/arch/arm/boot/dts/rtsm_ve-motherboard.dtsi b/arch/arm/boot/dts/rtsm_ve-motherboard.dtsi index 302b8c5560f..73b6fa94f80 100644 --- a/arch/arm/boot/dts/rtsm_ve-motherboard.dtsi +++ b/arch/arm/boot/dts/rtsm_ve-motherboard.dtsi @@ -7,17 +7,15 @@ * VEMotherBoard.lisa */ -/ { - aliases { - arm,v2m_timer = &v2m_timer01; - }; - motherboard { - compatible = "simple-bus"; + compatible = "arm,vexpress,v2m-p1", "simple-bus"; + arm,hbi = <0x190>; + arm,vexpress,site = <0>; arm,v2m-memory-map = "rs1"; #address-cells = <2>; /* SMB chipselect number and offset */ #size-cells = <1>; #interrupt-cells = <1>; + ranges; flash@0,00000000 { compatible = "arm,vexpress-flash", "cfi-flash"; @@ -43,93 +41,181 @@ #size-cells = <1>; ranges = <0 3 0 0x200000>; - sysreg@010000 { + v2m_sysreg: sysreg@010000 { compatible = "arm,vexpress-sysreg"; reg = <0x010000 0x1000>; + gpio-controller; + #gpio-cells = <2>; }; - sysctl@020000 { + v2m_sysctl: sysctl@020000 { compatible = "arm,sp810", "arm,primecell"; reg = <0x020000 0x1000>; + clocks = <&v2m_refclk32khz>, <&v2m_refclk1mhz>, <&smbclk>; + clock-names = "refclk", "timclk", "apb_pclk"; + #clock-cells = <1>; + clock-output-names = "timerclken0", "timerclken1", "timerclken2", "timerclken3"; }; aaci@040000 { compatible = "arm,pl041", "arm,primecell"; reg = <0x040000 0x1000>; interrupts = <11>; + clocks = <&smbclk>; + clock-names = "apb_pclk"; }; mmci@050000 { compatible = "arm,pl180", "arm,primecell"; reg = <0x050000 0x1000>; interrupts = <9 10>; + cd-gpios = <&v2m_sysreg 0 0>; + wp-gpios = <&v2m_sysreg 1 0>; + max-frequency = <12000000>; + vmmc-supply = <&v2m_fixed_3v3>; + clocks = <&v2m_clk24mhz>, <&smbclk>; + clock-names = "mclk", "apb_pclk"; }; kmi@060000 { compatible = "arm,pl050", "arm,primecell"; reg = <0x060000 0x1000>; interrupts = <12>; + clocks = <&v2m_clk24mhz>, <&smbclk>; + clock-names = "KMIREFCLK", "apb_pclk"; }; kmi@070000 { compatible = "arm,pl050", "arm,primecell"; reg = <0x070000 0x1000>; interrupts = <13>; + clocks = <&v2m_clk24mhz>, <&smbclk>; + clock-names = "KMIREFCLK", "apb_pclk"; }; v2m_serial0: uart@090000 { compatible = "arm,pl011", "arm,primecell"; reg = <0x090000 0x1000>; interrupts = <5>; + clocks = <&v2m_clk24mhz>, <&smbclk>; + clock-names = "uartclk", "apb_pclk"; }; v2m_serial1: uart@0a0000 { compatible = "arm,pl011", "arm,primecell"; reg = <0x0a0000 0x1000>; interrupts = <6>; + clocks = <&v2m_clk24mhz>, <&smbclk>; + clock-names = "uartclk", "apb_pclk"; }; v2m_serial2: uart@0b0000 { compatible = "arm,pl011", "arm,primecell"; reg = <0x0b0000 0x1000>; interrupts = <7>; + clocks = <&v2m_clk24mhz>, <&smbclk>; + clock-names = "uartclk", "apb_pclk"; }; v2m_serial3: uart@0c0000 { compatible = "arm,pl011", "arm,primecell"; reg = <0x0c0000 0x1000>; interrupts = <8>; + clocks = <&v2m_clk24mhz>, <&smbclk>; + clock-names = "uartclk", "apb_pclk"; }; wdt@0f0000 { compatible = "arm,sp805", "arm,primecell"; reg = <0x0f0000 0x1000>; interrupts = <0>; + clocks = <&v2m_refclk32khz>, <&smbclk>; + clock-names = "wdogclk", "apb_pclk"; }; v2m_timer01: timer@110000 { compatible = "arm,sp804", "arm,primecell"; reg = <0x110000 0x1000>; interrupts = <2>; + clocks = <&v2m_sysctl 0>, <&v2m_sysctl 1>, <&smbclk>; + clock-names = "timclken1", "timclken2", "apb_pclk"; }; v2m_timer23: timer@120000 { compatible = "arm,sp804", "arm,primecell"; reg = <0x120000 0x1000>; interrupts = <3>; + clocks = <&v2m_sysctl 2>, <&v2m_sysctl 3>, <&smbclk>; + clock-names = "timclken1", "timclken2", "apb_pclk"; }; rtc@170000 { compatible = "arm,pl031", "arm,primecell"; reg = <0x170000 0x1000>; interrupts = <4>; + clocks = <&smbclk>; + clock-names = "apb_pclk"; }; clcd@1f0000 { compatible = "arm,pl111", "arm,primecell"; reg = <0x1f0000 0x1000>; interrupts = <14>; + clocks = <&v2m_oscclk1>, <&smbclk>; + clock-names = "v2m:oscclk1", "apb_pclk"; + }; + }; + + v2m_fixed_3v3: fixedregulator@0 { + compatible = "regulator-fixed"; + regulator-name = "3V3"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; + }; + + v2m_clk24mhz: clk24mhz { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <24000000>; + clock-output-names = "v2m:clk24mhz"; + }; + + v2m_refclk1mhz: refclk1mhz { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <1000000>; + clock-output-names = "v2m:refclk1mhz"; + }; + + v2m_refclk32khz: refclk32khz { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <32768>; + clock-output-names = "v2m:refclk32khz"; + }; + + mcc { + compatible = "simple-bus"; + arm,vexpress,config-bridge = <&v2m_sysreg>; + + v2m_oscclk1: osc@1 { + /* CLCD clock */ + compatible = "arm,vexpress-osc"; + arm,vexpress-sysreg,func = <1 1>; + freq-range = <23750000 63500000>; + #clock-cells = <0>; + clock-output-names = "v2m:oscclk1"; + }; + + muxfpga@0 { + compatible = "arm,vexpress-muxfpga"; + arm,vexpress-sysreg,func = <7 0>; + }; + + shutdown@0 { + compatible = "arm,vexpress-shutdown"; + arm,vexpress-sysreg,func = <8 0>; }; }; }; -}; diff --git a/arch/arm/boot/dts/rtsm_ve-v2p-ca15x1-ca7x1.dts b/arch/arm/boot/dts/rtsm_ve-v2p-ca15x1-ca7x1.dts index 5d4a858240d..cbec3de46d1 100644 --- a/arch/arm/boot/dts/rtsm_ve-v2p-ca15x1-ca7x1.dts +++ b/arch/arm/boot/dts/rtsm_ve-v2p-ca15x1-ca7x1.dts @@ -13,6 +13,7 @@ / { model = "RTSM_VE_CortexA15x1-A7x1"; + arm,vexpress,site = <0xf>; compatible = "arm,rtsm_ve,cortex_a15x1_a7x1", "arm,vexpress"; interrupt-parent = <&gic>; #address-cells = <2>; @@ -124,7 +125,43 @@ <1 10 0xf08>; }; - motherboard { + dcc { + compatible = "arm,vexpress,config-bus"; + arm,vexpress,config-bridge = <&v2m_sysreg>; + + osc@0 { + /* ACLK clock to the AXI master port on the test chip */ + compatible = "arm,vexpress-osc"; + arm,vexpress-sysreg,func = <1 0>; + freq-range = <30000000 50000000>; + #clock-cells = <0>; + clock-output-names = "extsaxiclk"; + }; + + oscclk1: osc@1 { + /* Reference clock for the CLCD */ + compatible = "arm,vexpress-osc"; + arm,vexpress-sysreg,func = <1 1>; + freq-range = <10000000 80000000>; + #clock-cells = <0>; + clock-output-names = "clcdclk"; + }; + + smbclk: oscclk2: osc@2 { + /* Reference clock for the test chip internal PLLs */ + compatible = "arm,vexpress-osc"; + arm,vexpress-sysreg,func = <1 2>; + freq-range = <33000000 100000000>; + #clock-cells = <0>; + clock-output-names = "tcrefclk"; + }; + }; + + smb { + compatible = "simple-bus"; + + #address-cells = <2>; + #size-cells = <1>; ranges = <0 0 0 0x08000000 0x04000000>, <1 0 0 0x14000000 0x04000000>, <2 0 0 0x18000000 0x04000000>, @@ -132,6 +169,7 @@ <4 0 0 0x0c000000 0x04000000>, <5 0 0 0x10000000 0x04000000>; + #interrupt-cells = <1>; interrupt-map-mask = <0 0 63>; interrupt-map = <0 0 0 &gic 0 0 4>, <0 0 1 &gic 0 1 4>, @@ -176,7 +214,9 @@ <0 0 40 &gic 0 40 4>, <0 0 41 &gic 0 41 4>, <0 0 42 &gic 0 42 4>; + + /include/ "rtsm_ve-motherboard.dtsi" }; }; -/include/ "rtsm_ve-motherboard.dtsi" \ No newline at end of file +/include/ "clcd-panels.dtsi" diff --git a/arch/arm/boot/dts/rtsm_ve-v2p-ca15x4-ca7x4.dts b/arch/arm/boot/dts/rtsm_ve-v2p-ca15x4-ca7x4.dts index 277bed5c716..1b7de69a4b5 100644 --- a/arch/arm/boot/dts/rtsm_ve-v2p-ca15x4-ca7x4.dts +++ b/arch/arm/boot/dts/rtsm_ve-v2p-ca15x4-ca7x4.dts @@ -13,6 +13,7 @@ / { model = "RTSM_VE_CortexA15x4-A7x4"; + arm,vexpress,site = <0xf>; compatible = "arm,rtsm_ve,cortex_a15x4_a7x4", "arm,vexpress"; interrupt-parent = <&gic>; #address-cells = <2>; @@ -232,7 +233,43 @@ <1 10 0xf08>; }; - motherboard { + dcc { + compatible = "arm,vexpress,config-bus"; + arm,vexpress,config-bridge = <&v2m_sysreg>; + + osc@0 { + /* ACLK clock to the AXI master port on the test chip */ + compatible = "arm,vexpress-osc"; + arm,vexpress-sysreg,func = <1 0>; + freq-range = <30000000 50000000>; + #clock-cells = <0>; + clock-output-names = "extsaxiclk"; + }; + + oscclk1: osc@1 { + /* Reference clock for the CLCD */ + compatible = "arm,vexpress-osc"; + arm,vexpress-sysreg,func = <1 1>; + freq-range = <10000000 80000000>; + #clock-cells = <0>; + clock-output-names = "clcdclk"; + }; + + smbclk: oscclk2: osc@2 { + /* Reference clock for the test chip internal PLLs */ + compatible = "arm,vexpress-osc"; + arm,vexpress-sysreg,func = <1 2>; + freq-range = <33000000 100000000>; + #clock-cells = <0>; + clock-output-names = "tcrefclk"; + }; + }; + + smb { + compatible = "simple-bus"; + + #address-cells = <2>; + #size-cells = <1>; ranges = <0 0 0 0x08000000 0x04000000>, <1 0 0 0x14000000 0x04000000>, <2 0 0 0x18000000 0x04000000>, @@ -240,6 +277,7 @@ <4 0 0 0x0c000000 0x04000000>, <5 0 0 0x10000000 0x04000000>; + #interrupt-cells = <1>; interrupt-map-mask = <0 0 63>; interrupt-map = <0 0 0 &gic 0 0 4>, <0 0 1 &gic 0 1 4>, @@ -284,7 +322,9 @@ <0 0 40 &gic 0 40 4>, <0 0 41 &gic 0 41 4>, <0 0 42 &gic 0 42 4>; + + /include/ "rtsm_ve-motherboard.dtsi" }; }; -/include/ "rtsm_ve-motherboard.dtsi" \ No newline at end of file +/include/ "clcd-panels.dtsi" -- cgit v1.2.3 From 2791e6ee27a35b076fc683574767c8a4fdfcc333 Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Wed, 30 Jan 2013 12:04:47 +0000 Subject: ARM: dts: Update RTSM dtbs for CLCD changes Signed-off-by: Jon Medhurst Conflicts: arch/arm/boot/dts/rtsm_ve-motherboard.dtsi --- arch/arm/boot/dts/rtsm_ve-motherboard.dtsi | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/arm/boot/dts/rtsm_ve-motherboard.dtsi b/arch/arm/boot/dts/rtsm_ve-motherboard.dtsi index 73b6fa94f80..6d125662612 100644 --- a/arch/arm/boot/dts/rtsm_ve-motherboard.dtsi +++ b/arch/arm/boot/dts/rtsm_ve-motherboard.dtsi @@ -163,6 +163,9 @@ interrupts = <14>; clocks = <&v2m_oscclk1>, <&smbclk>; clock-names = "v2m:oscclk1", "apb_pclk"; + mode = "VGA"; + use_dma = <0>; + framebuffer = <0x18000000 0x00180000>; }; }; -- cgit v1.2.3 From 76546a4560df30615570efb80bd5ee04791692fb Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Thu, 29 Nov 2012 16:37:51 -0500 Subject: ARM: RTSM: add DCSCB address definition to ca15x*-ca7x* .dts files --- arch/arm/boot/dts/rtsm_ve-v2p-ca15x1-ca7x1.dts | 5 +++++ arch/arm/boot/dts/rtsm_ve-v2p-ca15x4-ca7x4.dts | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/arch/arm/boot/dts/rtsm_ve-v2p-ca15x1-ca7x1.dts b/arch/arm/boot/dts/rtsm_ve-v2p-ca15x1-ca7x1.dts index cbec3de46d1..9f5683fe48b 100644 --- a/arch/arm/boot/dts/rtsm_ve-v2p-ca15x1-ca7x1.dts +++ b/arch/arm/boot/dts/rtsm_ve-v2p-ca15x1-ca7x1.dts @@ -94,6 +94,11 @@ reg = <0 0x2c090000 0 0x8000>; }; + dcscb@60000000 { + compatible = "arm,dcscb"; + reg = <0 0x60000000 0 0x1000>; + }; + gic: interrupt-controller@2c001000 { compatible = "arm,cortex-a15-gic", "arm,cortex-a9-gic"; #interrupt-cells = <3>; diff --git a/arch/arm/boot/dts/rtsm_ve-v2p-ca15x4-ca7x4.dts b/arch/arm/boot/dts/rtsm_ve-v2p-ca15x4-ca7x4.dts index 1b7de69a4b5..ba02441f070 100644 --- a/arch/arm/boot/dts/rtsm_ve-v2p-ca15x4-ca7x4.dts +++ b/arch/arm/boot/dts/rtsm_ve-v2p-ca15x4-ca7x4.dts @@ -172,6 +172,11 @@ reg = <0 0x2c090000 0 0x8000>; }; + dcscb@60000000 { + compatible = "arm,dcscb"; + reg = <0 0x60000000 0 0x1000>; + }; + gic: interrupt-controller@2c001000 { compatible = "arm,cortex-a15-gic", "arm,cortex-a9-gic"; #interrupt-cells = <3>; -- cgit v1.2.3 From e63a47a858f5404b5d7f1dc73dab8c78115e9c04 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Wed, 20 Mar 2013 00:33:24 -0400 Subject: ARM: rtsm_ve-v2p-ca15x?-ca7x?.dts: update dcscb compatible string ... to match the version that was approved during upstream review. Signed-off-by: Nicolas Pitre --- arch/arm/boot/dts/rtsm_ve-v2p-ca15x1-ca7x1.dts | 2 +- arch/arm/boot/dts/rtsm_ve-v2p-ca15x4-ca7x4.dts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm/boot/dts/rtsm_ve-v2p-ca15x1-ca7x1.dts b/arch/arm/boot/dts/rtsm_ve-v2p-ca15x1-ca7x1.dts index 9f5683fe48b..55d4f5ce019 100644 --- a/arch/arm/boot/dts/rtsm_ve-v2p-ca15x1-ca7x1.dts +++ b/arch/arm/boot/dts/rtsm_ve-v2p-ca15x1-ca7x1.dts @@ -95,7 +95,7 @@ }; dcscb@60000000 { - compatible = "arm,dcscb"; + compatible = "arm,rtsm,dcscb"; reg = <0 0x60000000 0 0x1000>; }; diff --git a/arch/arm/boot/dts/rtsm_ve-v2p-ca15x4-ca7x4.dts b/arch/arm/boot/dts/rtsm_ve-v2p-ca15x4-ca7x4.dts index ba02441f070..a2d4441568a 100644 --- a/arch/arm/boot/dts/rtsm_ve-v2p-ca15x4-ca7x4.dts +++ b/arch/arm/boot/dts/rtsm_ve-v2p-ca15x4-ca7x4.dts @@ -173,7 +173,7 @@ }; dcscb@60000000 { - compatible = "arm,dcscb"; + compatible = "arm,rtsm,dcscb"; reg = <0 0x60000000 0 0x1000>; }; -- cgit v1.2.3 From d2cab175b788011e535f4d5184560b61012e51e1 Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Tue, 14 May 2013 15:22:02 +0100 Subject: rtsm: fixup for new CCI driver We include the compatible string "arm,cci" because the semihosting bootwrapper searches for this to initialise the CCI before boot. The boot-wrapper will need updating for the new CCI device-tree bindings when they are stable, then we can remove this old compatible string from the RTSM device-trees. Signed-off-by: Jon Medhurst --- arch/arm/boot/dts/rtsm_ve-v2p-ca15x1-ca7x1.dts | 21 ++++++++++++++++++-- arch/arm/boot/dts/rtsm_ve-v2p-ca15x4-ca7x4.dts | 27 ++++++++++++++++++++++++-- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/arch/arm/boot/dts/rtsm_ve-v2p-ca15x1-ca7x1.dts b/arch/arm/boot/dts/rtsm_ve-v2p-ca15x1-ca7x1.dts index 55d4f5ce019..fe8cf5dc857 100644 --- a/arch/arm/boot/dts/rtsm_ve-v2p-ca15x1-ca7x1.dts +++ b/arch/arm/boot/dts/rtsm_ve-v2p-ca15x1-ca7x1.dts @@ -72,6 +72,7 @@ cluster = <&cluster0>; core = <&core0>; // clock-frequency = <1000000000>; + cci-control-port = <&cci_control1>; }; cpu1: cpu@1 { @@ -81,6 +82,7 @@ cluster = <&cluster1>; core = <&core1>; // clock-frequency = <800000000>; + cci-control-port = <&cci_control2>; }; }; @@ -90,8 +92,23 @@ }; cci@2c090000 { - compatible = "arm,cci"; - reg = <0 0x2c090000 0 0x8000>; + compatible = "arm,cci-400", "arm,cci"; + #address-cells = <1>; + #size-cells = <1>; + reg = <0 0x2c090000 0 0x1000>; + ranges = <0x0 0x0 0x2c090000 0x10000>; + + cci_control1: slave-if@4000 { + compatible = "arm,cci-400-ctrl-if"; + interface-type = "ace"; + reg = <0x4000 0x1000>; + }; + + cci_control2: slave-if@5000 { + compatible = "arm,cci-400-ctrl-if"; + interface-type = "ace"; + reg = <0x5000 0x1000>; + }; }; dcscb@60000000 { diff --git a/arch/arm/boot/dts/rtsm_ve-v2p-ca15x4-ca7x4.dts b/arch/arm/boot/dts/rtsm_ve-v2p-ca15x4-ca7x4.dts index a2d4441568a..f715285131d 100644 --- a/arch/arm/boot/dts/rtsm_ve-v2p-ca15x4-ca7x4.dts +++ b/arch/arm/boot/dts/rtsm_ve-v2p-ca15x4-ca7x4.dts @@ -96,6 +96,7 @@ cluster = <&cluster0>; core = <&core0>; // clock-frequency = <1000000000>; + cci-control-port = <&cci_control1>; }; cpu1: cpu@1 { @@ -105,6 +106,7 @@ cluster = <&cluster0>; core = <&core1>; // clock-frequency = <1000000000>; + cci-control-port = <&cci_control1>; }; cpu2: cpu@2 { @@ -114,6 +116,7 @@ cluster = <&cluster0>; core = <&core2>; // clock-frequency = <1000000000>; + cci-control-port = <&cci_control1>; }; cpu3: cpu@3 { @@ -123,6 +126,7 @@ cluster = <&cluster0>; core = <&core3>; // clock-frequency = <1000000000>; + cci-control-port = <&cci_control1>; }; cpu4: cpu@4 { @@ -132,6 +136,7 @@ cluster = <&cluster1>; core = <&core4>; // clock-frequency = <800000000>; + cci-control-port = <&cci_control2>; }; cpu5: cpu@5 { @@ -141,6 +146,7 @@ cluster = <&cluster1>; core = <&core5>; // clock-frequency = <800000000>; + cci-control-port = <&cci_control2>; }; cpu6: cpu@6 { @@ -150,6 +156,7 @@ cluster = <&cluster1>; core = <&core6>; // clock-frequency = <800000000>; + cci-control-port = <&cci_control2>; }; cpu7: cpu@7 { @@ -159,6 +166,7 @@ cluster = <&cluster1>; core = <&core7>; // clock-frequency = <800000000>; + cci-control-port = <&cci_control2>; }; }; @@ -168,8 +176,23 @@ }; cci@2c090000 { - compatible = "arm,cci"; - reg = <0 0x2c090000 0 0x8000>; + compatible = "arm,cci-400", "arm,cci"; + #address-cells = <1>; + #size-cells = <1>; + reg = <0 0x2c090000 0 0x1000>; + ranges = <0x0 0x0 0x2c090000 0x10000>; + + cci_control1: slave-if@4000 { + compatible = "arm,cci-400-ctrl-if"; + interface-type = "ace"; + reg = <0x4000 0x1000>; + }; + + cci_control2: slave-if@5000 { + compatible = "arm,cci-400-ctrl-if"; + interface-type = "ace"; + reg = <0x5000 0x1000>; + }; }; dcscb@60000000 { -- cgit v1.2.3 From d73a32d90f3e75e3936796783728536907e874b4 Mon Sep 17 00:00:00 2001 From: Pawel Moll Date: Mon, 17 Sep 2012 18:09:28 +0100 Subject: video: Versatile Express DVI output driver Versatile Express' DVI video output can be connected to one the three sources - motherboard's CLCD controller or a video signal generated by one of the daughterboards. This driver configures the muxer FPGA so the output displays content of one of the framebuffers in the system (0 by default, can be changed by user writing to the "fb" sysfs attribute of the muxfpga device). It will also set up the display formatter mode and keep it up to date with mode changes requested by the user (eg. with fbset tool). Signed-off-by: Pawel Moll --- drivers/video/Makefile | 3 + drivers/video/vexpress-dvi.c | 220 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 223 insertions(+) create mode 100644 drivers/video/vexpress-dvi.c diff --git a/drivers/video/Makefile b/drivers/video/Makefile index e8bae8dd480..5084e8c4da9 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -177,3 +177,6 @@ obj-$(CONFIG_VIDEOMODE_HELPERS) += display_timing.o videomode.o ifeq ($(CONFIG_OF),y) obj-$(CONFIG_VIDEOMODE_HELPERS) += of_display_timing.o of_videomode.o endif + +# platform specific output drivers +obj-$(CONFIG_VEXPRESS_CONFIG) += vexpress-dvi.o diff --git a/drivers/video/vexpress-dvi.c b/drivers/video/vexpress-dvi.c new file mode 100644 index 00000000000..f08753450ee --- /dev/null +++ b/drivers/video/vexpress-dvi.c @@ -0,0 +1,220 @@ +/* + * 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 in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Copyright (C) 2012 ARM Limited + */ + +#define pr_fmt(fmt) "vexpress-dvi: " fmt + +#include +#include +#include +#include + + +static struct vexpress_config_func *vexpress_dvimode_func; + +static struct { + u32 xres, yres, mode; +} vexpress_dvi_dvimodes[] = { + { 640, 480, 0 }, /* VGA */ + { 800, 600, 1 }, /* SVGA */ + { 1024, 768, 2 }, /* XGA */ + { 1280, 1024, 3 }, /* SXGA */ + { 1600, 1200, 4 }, /* UXGA */ + { 1920, 1080, 5 }, /* HD1080 */ +}; + +static void vexpress_dvi_mode_set(struct fb_info *info, u32 xres, u32 yres) +{ + int err = -ENOENT; + int i; + + if (!vexpress_dvimode_func) + return; + + for (i = 0; i < ARRAY_SIZE(vexpress_dvi_dvimodes); i++) { + if (vexpress_dvi_dvimodes[i].xres == xres && + vexpress_dvi_dvimodes[i].yres == yres) { + pr_debug("mode: %ux%u = %d\n", xres, yres, + vexpress_dvi_dvimodes[i].mode); + err = vexpress_config_write(vexpress_dvimode_func, 0, + vexpress_dvi_dvimodes[i].mode); + break; + } + } + + if (err) + pr_warn("Failed to set %ux%u mode! (%d)\n", xres, yres, err); +} + + +static struct vexpress_config_func *vexpress_muxfpga_func; +static int vexpress_dvi_fb = -1; + +static int vexpress_dvi_mux_set(struct fb_info *info) +{ + int err; + u32 site = vexpress_get_site_by_dev(info->device); + + if (!vexpress_muxfpga_func) + return -ENXIO; + + err = vexpress_config_write(vexpress_muxfpga_func, 0, site); + if (!err) { + pr_debug("Selected MUXFPGA input %d (fb%d)\n", site, + info->node); + vexpress_dvi_fb = info->node; + vexpress_dvi_mode_set(info, info->var.xres, + info->var.yres); + } else { + pr_warn("Failed to select MUXFPGA input %d (fb%d)! (%d)\n", + site, info->node, err); + } + + return err; +} + +static int vexpress_dvi_fb_select(int fb) +{ + int err; + struct fb_info *info; + + /* fb0 is the default */ + if (fb < 0) + fb = 0; + + info = registered_fb[fb]; + if (!info || !lock_fb_info(info)) + return -ENODEV; + + err = vexpress_dvi_mux_set(info); + + unlock_fb_info(info); + + return err; +} + +static ssize_t vexpress_dvi_fb_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", vexpress_dvi_fb); +} + +static ssize_t vexpress_dvi_fb_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + long value; + int err = kstrtol(buf, 0, &value); + + if (!err) + err = vexpress_dvi_fb_select(value); + + return err ? err : count; +} + +DEVICE_ATTR(fb, S_IRUGO | S_IWUSR, vexpress_dvi_fb_show, + vexpress_dvi_fb_store); + + +static int vexpress_dvi_fb_event_notify(struct notifier_block *self, + unsigned long action, void *data) +{ + struct fb_event *event = data; + struct fb_info *info = event->info; + struct fb_videomode *mode = event->data; + + switch (action) { + case FB_EVENT_FB_REGISTERED: + if (vexpress_dvi_fb < 0) + vexpress_dvi_mux_set(info); + break; + case FB_EVENT_MODE_CHANGE: + case FB_EVENT_MODE_CHANGE_ALL: + if (info->node == vexpress_dvi_fb) + vexpress_dvi_mode_set(info, mode->xres, mode->yres); + break; + } + + return NOTIFY_OK; +} + +static struct notifier_block vexpress_dvi_fb_notifier = { + .notifier_call = vexpress_dvi_fb_event_notify, +}; +static bool vexpress_dvi_fb_notifier_registered; + + +enum vexpress_dvi_func { FUNC_MUXFPGA, FUNC_DVIMODE }; + +static struct of_device_id vexpress_dvi_of_match[] = { + { + .compatible = "arm,vexpress-muxfpga", + .data = (void *)FUNC_MUXFPGA, + }, { + .compatible = "arm,vexpress-dvimode", + .data = (void *)FUNC_DVIMODE, + }, + {} +}; + +static int vexpress_dvi_probe(struct platform_device *pdev) +{ + enum vexpress_dvi_func func; + const struct of_device_id *match = + of_match_device(vexpress_dvi_of_match, &pdev->dev); + + if (match) + func = (enum vexpress_dvi_func)match->data; + else + func = pdev->id_entry->driver_data; + + switch (func) { + case FUNC_MUXFPGA: + vexpress_muxfpga_func = + vexpress_config_func_get_by_dev(&pdev->dev); + device_create_file(&pdev->dev, &dev_attr_fb); + break; + case FUNC_DVIMODE: + vexpress_dvimode_func = + vexpress_config_func_get_by_dev(&pdev->dev); + break; + } + + if (!vexpress_dvi_fb_notifier_registered) { + fb_register_client(&vexpress_dvi_fb_notifier); + vexpress_dvi_fb_notifier_registered = true; + } + + vexpress_dvi_fb_select(vexpress_dvi_fb); + + return 0; +} + +static const struct platform_device_id vexpress_dvi_id_table[] = { + { .name = "vexpress-muxfpga", .driver_data = FUNC_MUXFPGA, }, + { .name = "vexpress-dvimode", .driver_data = FUNC_DVIMODE, }, + {} +}; + +static struct platform_driver vexpress_dvi_driver = { + .probe = vexpress_dvi_probe, + .driver = { + .name = "vexpress-dvi", + .of_match_table = vexpress_dvi_of_match, + }, + .id_table = vexpress_dvi_id_table, +}; + +static int __init vexpress_dvi_init(void) +{ + return platform_driver_register(&vexpress_dvi_driver); +} +device_initcall(vexpress_dvi_init); -- cgit v1.2.3 From c3e43a142a26fa86463426b085e2fb89a452fdb2 Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Tue, 15 Jan 2013 17:45:19 +0000 Subject: video: Make vexpress DVI driver depend on CONFIG_FB The driver uses symbols from fbmem.c so if CONFIG_FB is not selected we get build errors like: drivers/built-in.o: In function `vexpress_dvi_fb_select': :(.text+0x1b6c): undefined reference to `lock_fb_info' :(.text+0x1ba0): undefined reference to `registered_fb' Note, this is only a partial solution because we still have a problem if CONFIG_FB is selected as a module. Signed-off-by: Jon Medhurst --- drivers/video/Kconfig | 5 +++++ drivers/video/Makefile | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 2e937bdace6..aa9429fd4d4 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -39,6 +39,11 @@ config VIDEOMODE_HELPERS config HDMI bool +config VEXPRESS_DVI_CONTROL + bool "Versatile Express DVI control" + depends on FB && VEXPRESS_CONFIG + default y + menuconfig FB tristate "Support for frame buffer devices" ---help--- diff --git a/drivers/video/Makefile b/drivers/video/Makefile index 5084e8c4da9..6b5d5f2b430 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -179,4 +179,4 @@ obj-$(CONFIG_VIDEOMODE_HELPERS) += of_display_timing.o of_videomode.o endif # platform specific output drivers -obj-$(CONFIG_VEXPRESS_CONFIG) += vexpress-dvi.o +obj-$(CONFIG_VEXPRESS_DVI_CONTROL) += vexpress-dvi.o -- cgit v1.2.3 From 5f97d66c4a98e658892b0a0d8dcb99332bfa2b54 Mon Sep 17 00:00:00 2001 From: Pawel Moll Date: Mon, 10 Jun 2013 16:05:06 +0100 Subject: clk: vexpress: Use full node name to identify individual clocks Previously all the clocks were reported as "osc". Now it will be something like "/dcc/osc@0". Signed-off-by: Pawel Moll --- drivers/clk/versatile/clk-vexpress-osc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/clk/versatile/clk-vexpress-osc.c b/drivers/clk/versatile/clk-vexpress-osc.c index 256c8be74df..2dc8b41a339 100644 --- a/drivers/clk/versatile/clk-vexpress-osc.c +++ b/drivers/clk/versatile/clk-vexpress-osc.c @@ -107,7 +107,7 @@ void __init vexpress_osc_of_setup(struct device_node *node) osc->func = vexpress_config_func_get_by_node(node); if (!osc->func) { pr_err("Failed to obtain config func for node '%s'!\n", - node->name); + node->full_name); goto error; } @@ -119,7 +119,7 @@ void __init vexpress_osc_of_setup(struct device_node *node) of_property_read_string(node, "clock-output-names", &init.name); if (!init.name) - init.name = node->name; + init.name = node->full_name; init.ops = &vexpress_osc_ops; init.flags = CLK_IS_ROOT; -- cgit v1.2.3 From 503552ea012f92de0186efa8b03e7b6208776f98 Mon Sep 17 00:00:00 2001 From: Pawel Moll Date: Mon, 10 Jun 2013 16:05:07 +0100 Subject: clk: vexpress: Make the clock drivers directly available for arm64 The new arm64 architecture has no idea of platform or machine, so it doesn't have to define ARCH_VEXPRESS configuration option at all. To allow user to select the drivers at all, make it depend on ARM64 as well. Signed-off-by: Pawel Moll Acked-by: Catalin Marinas --- drivers/clk/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index 0357ac44638..d0d9b212475 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -42,7 +42,7 @@ config COMMON_CLK_WM831X config COMMON_CLK_VERSATILE bool "Clock driver for ARM Reference designs" - depends on ARCH_INTEGRATOR || ARCH_REALVIEW || ARCH_VEXPRESS + depends on ARCH_INTEGRATOR || ARCH_REALVIEW || ARCH_VEXPRESS || ARM64 ---help--- Supports clocking on ARM Reference designs: - Integrator/AP and Integrator/CP -- cgit v1.2.3 From 981794e2f24b5c57558d89a52e1129eadbed46d8 Mon Sep 17 00:00:00 2001 From: Pawel Moll Date: Mon, 10 Jun 2013 16:12:01 +0100 Subject: power/reset: Make the vexpress driver optional on arm and arm64 The driver can be used on either arm or arm64 platforms, but the latter doesn't have any platform-specific configuration options, so it must be possible to manually enable the driver. Signed-off-by: Pawel Moll Acked-by: Catalin Marinas --- drivers/power/reset/Kconfig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig index 349e9ae8090..ee039dcead0 100644 --- a/drivers/power/reset/Kconfig +++ b/drivers/power/reset/Kconfig @@ -32,7 +32,8 @@ config POWER_RESET_RESTART user presses a key. u-boot then boots into Linux. config POWER_RESET_VEXPRESS - bool + bool "ARM Versatile Express power-off and reset driver" + depends on ARM || ARM64 depends on POWER_RESET help Power off and reset support for the ARM Ltd. Versatile -- cgit v1.2.3 From abe8e2fe9ee8b3cdef3d753706513eb7f340e28d Mon Sep 17 00:00:00 2001 From: Pawel Moll Date: Tue, 11 Jun 2013 11:56:02 +0100 Subject: mfd: vexpress: Make the driver optional for arm and arm64 The driver can be used on either arm or arm64 platforms, but the latter doesn't have any platform-specific configuration options, so it must be possible to manually enable the driver. As the gpiolib is optional for arm64 arch, the gpio/led code must be compiled conditionally. Signed-off-by: Pawel Moll Acked-by: Catalin Marinas --- drivers/mfd/Kconfig | 3 ++- drivers/mfd/vexpress-sysreg.c | 10 ++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index d54e985748b..6959b8dc0cb 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -1144,7 +1144,8 @@ config MCP_UCB1200_TS endmenu config VEXPRESS_CONFIG - bool + bool "ARM Versatile Express platform infrastructure" + depends on ARM || ARM64 help Platform configuration infrastructure for the ARM Ltd. Versatile Express. diff --git a/drivers/mfd/vexpress-sysreg.c b/drivers/mfd/vexpress-sysreg.c index 96a020b1dcd..981bef4b7eb 100644 --- a/drivers/mfd/vexpress-sysreg.c +++ b/drivers/mfd/vexpress-sysreg.c @@ -351,6 +351,8 @@ void __init vexpress_sysreg_of_early_init(void) } +#ifdef CONFIG_GPIOLIB + #define VEXPRESS_SYSREG_GPIO(_name, _reg, _value) \ [VEXPRESS_GPIO_##_name] = { \ .reg = _reg, \ @@ -445,6 +447,8 @@ struct gpio_led_platform_data vexpress_sysreg_leds_pdata = { .leds = vexpress_sysreg_leds, }; +#endif + static ssize_t vexpress_sysreg_sys_id_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -480,6 +484,9 @@ static int vexpress_sysreg_probe(struct platform_device *pdev) setup_timer(&vexpress_sysreg_config_timer, vexpress_sysreg_config_complete, 0); + vexpress_sysreg_dev = &pdev->dev; + +#ifdef CONFIG_GPIOLIB vexpress_sysreg_gpio_chip.dev = &pdev->dev; err = gpiochip_add(&vexpress_sysreg_gpio_chip); if (err) { @@ -490,11 +497,10 @@ static int vexpress_sysreg_probe(struct platform_device *pdev) return err; } - vexpress_sysreg_dev = &pdev->dev; - platform_device_register_data(vexpress_sysreg_dev, "leds-gpio", PLATFORM_DEVID_AUTO, &vexpress_sysreg_leds_pdata, sizeof(vexpress_sysreg_leds_pdata)); +#endif device_create_file(vexpress_sysreg_dev, &dev_attr_sys_id); -- cgit v1.2.3 From caf003fa1d4386755d34f12bb23f264da1496d3b Mon Sep 17 00:00:00 2001 From: Liviu Dudau Date: Wed, 18 Jan 2012 16:52:04 +0000 Subject: ARM HDLCD: Add support for ARM High Definition LCD. The ARM HDLCD device is now found in various new Versatile Express coretiles. Signed-off-by: Liviu Dudau --- drivers/video/Kconfig | 15 + drivers/video/Makefile | 1 + drivers/video/arm-hdlcd.c | 739 ++++++++++++++++++++++++++++++++++++++++++++++ include/linux/arm-hdlcd.h | 100 +++++++ 4 files changed, 855 insertions(+) create mode 100644 drivers/video/arm-hdlcd.c create mode 100644 include/linux/arm-hdlcd.h diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 2e937bdace6..89f8c68c40c 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -326,6 +326,21 @@ config FB_ARMCLCD here and read . The module will be called amba-clcd. +config FB_ARMHDLCD + tristate "ARM High Definition LCD support" + depends on FB && ARM + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + This framebuffer device driver is for the ARM High Definition + Colour LCD controller. + + If you want to compile this as a module (=code which can be + inserted into and removed from the running kernel), say M + here and read . The module + will be called arm-hdlcd. + config FB_ACORN bool "Acorn VIDC support" depends on (FB = y) && ARM && ARCH_ACORN diff --git a/drivers/video/Makefile b/drivers/video/Makefile index e8bae8dd480..82f09484b0d 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -99,6 +99,7 @@ obj-$(CONFIG_FB_ATMEL) += atmel_lcdfb.o obj-$(CONFIG_FB_PVR2) += pvr2fb.o obj-$(CONFIG_FB_VOODOO1) += sstfb.o obj-$(CONFIG_FB_ARMCLCD) += amba-clcd.o +obj-$(CONFIG_FB_ARMHDLCD) += arm-hdlcd.o obj-$(CONFIG_FB_GOLDFISH) += goldfishfb.o obj-$(CONFIG_FB_68328) += 68328fb.o obj-$(CONFIG_FB_GBE) += gbefb.o diff --git a/drivers/video/arm-hdlcd.c b/drivers/video/arm-hdlcd.c new file mode 100644 index 00000000000..3e103a5c9b5 --- /dev/null +++ b/drivers/video/arm-hdlcd.c @@ -0,0 +1,739 @@ +/* + * drivers/video/arm-hdlcd.c + * + * Copyright (C) 2011 ARM Limited + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive + * for more details. + * + * ARM HDLCD Controller + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "edid.h" + +#ifdef CONFIG_SERIAL_AMBA_PCU_UART +/* set the DVI output mode using the firmware */ +int set_dvi_mode(u8 *msgbuf); +int get_edid(u8 *msgbuf); +#endif + +#define to_hdlcd_device(info) container_of(info, struct hdlcd_device, fb) + +static struct of_device_id hdlcd_of_matches[] = { + { .compatible = "arm,hdlcd" }, + {}, +}; + +/* Framebuffer size. */ +static unsigned long framebuffer_size; + +static char *fb_mode = "1680x1050-32@60\0\0\0\0\0"; + +static struct fb_var_screeninfo cached_var_screeninfo; + +static struct fb_videomode hdlcd_default_mode = { + .refresh = 60, + .xres = 1680, + .yres = 1050, + .pixclock = 8403, + .left_margin = 80, + .right_margin = 48, + .upper_margin = 21, + .lower_margin = 3, + .hsync_len = 32, + .vsync_len = 6, + .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + .vmode = FB_VMODE_NONINTERLACED +}; + + +static inline void hdlcd_enable(struct hdlcd_device *hdlcd) +{ + dev_dbg(hdlcd->dev, "HDLCD: output enabled\n"); + writel(1, hdlcd->base + HDLCD_REG_COMMAND); +} + +static inline void hdlcd_disable(struct hdlcd_device *hdlcd) +{ + dev_dbg(hdlcd->dev, "HDLCD: output disabled\n"); + writel(0, hdlcd->base + HDLCD_REG_COMMAND); +} + +static int hdlcd_set_bitfields(struct hdlcd_device *hdlcd, + struct fb_var_screeninfo *var) +{ + int ret = 0; + + memset(&var->transp, 0, sizeof(var->transp)); + var->red.msb_right = 0; + var->green.msb_right = 0; + var->blue.msb_right = 0; + var->blue.offset = 0; + + switch (var->bits_per_pixel) { + case 8: + /* pseudocolor */ + var->red.length = 8; + var->green.length = 8; + var->blue.length = 8; + break; + case 16: + /* 565 format */ + var->red.length = 5; + var->green.length = 6; + var->blue.length = 5; + break; + case 32: + var->transp.length = 8; + case 24: + var->red.length = 8; + var->green.length = 8; + var->blue.length = 8; + break; + default: + ret = -EINVAL; + break; + } + + if (!ret) { + var->green.offset = var->blue.length; + var->red.offset = var->green.offset + var->green.length; + if (var->bits_per_pixel == 32) + var->transp.offset = var->red.offset + var->red.length; + } + + return ret; +} + +static int hdlcd_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct hdlcd_device *hdlcd = to_hdlcd_device(info); + int bytes_per_pixel = var->bits_per_pixel / 8; + + var->yres_virtual = 2 * var->yres; + + if ((var->xres_virtual * bytes_per_pixel * var->yres_virtual) > hdlcd->fb.fix.smem_len) + return -ENOMEM; + + if (var->xres > HDLCD_MAX_XRES || var->yres > HDLCD_MAX_YRES) + return -EINVAL; + + /* make sure the bitfields are set appropriately */ + return hdlcd_set_bitfields(hdlcd, var); +} + +#ifdef CONFIG_SERIAL_AMBA_PCU_UART +static int hdlcd_set_output_mode(int xres, int yres) +{ + /* firmware uses some stupid protocol: 5 bytes (only byte 1 used) + to send 3 bits of information (value between 0 - 5) */ + u8 msgbuffer[5]; + + memset(msgbuffer, 0, sizeof(msgbuffer)); + /* default resolution: 640 x 480 */ + if (xres == 800 && yres <= 600) + msgbuffer[0] = 1; /* SVGA: 800 * 600 */ + else if (xres == 1024 && yres <= 768) + msgbuffer[0] = 2; /* XGA: 1024 * 768 */ + else if (xres == 1280 && yres <= 1024) + msgbuffer[0] = 3; /* SXGA: 1280 * 1024 */ + else if (xres == 1600 && yres <= 1200) + msgbuffer[0] = 4; /* UXGA: 1600 * 1200 */ + else if (xres == 1920 && yres <= 1200) + msgbuffer[0] = 5; /* WUXGA: 1920 * 1200 */ + + return set_dvi_mode(msgbuffer); +} +#else +inline int hdlcd_set_output_mode(int xres, int yres) +{ + return 0; +} +#endif + +#define WRITE_HDLCD_REG(reg, value) writel((value), hdlcd->base + (reg)) +#define READ_HDLCD_REG(reg) readl(hdlcd->base + (reg)) + +static int hdlcd_set_par(struct fb_info *info) +{ + struct hdlcd_device *hdlcd = to_hdlcd_device(info); + int bytes_per_pixel = hdlcd->fb.var.bits_per_pixel / 8; + int polarities; + + if (!memcmp(&info->var, &cached_var_screeninfo, sizeof(struct fb_var_screeninfo))) + return 0; + + hdlcd->fb.fix.line_length = hdlcd->fb.var.xres * bytes_per_pixel; + + if (hdlcd->fb.var.bits_per_pixel >= 16) + hdlcd->fb.fix.visual = FB_VISUAL_TRUECOLOR; + else + hdlcd->fb.fix.visual = FB_VISUAL_PSEUDOCOLOR; + + memcpy(&cached_var_screeninfo, &info->var, sizeof(struct fb_var_screeninfo)); + + polarities = HDLCD_POLARITY_DATAEN | +#ifndef CONFIG_ARCH_TUSCAN + HDLCD_POLARITY_PIXELCLK | +#endif + HDLCD_POLARITY_DATA; + polarities |= (hdlcd->fb.var.sync & FB_SYNC_HOR_HIGH_ACT) ? HDLCD_POLARITY_HSYNC : 0; + polarities |= (hdlcd->fb.var.sync & FB_SYNC_VERT_HIGH_ACT) ? HDLCD_POLARITY_VSYNC : 0; + + hdlcd_disable(hdlcd); + + WRITE_HDLCD_REG(HDLCD_REG_FB_LINE_LENGTH, hdlcd->fb.var.xres * bytes_per_pixel); + WRITE_HDLCD_REG(HDLCD_REG_FB_LINE_PITCH, hdlcd->fb.var.xres * bytes_per_pixel); + WRITE_HDLCD_REG(HDLCD_REG_FB_LINE_COUNT, hdlcd->fb.var.yres - 1); + WRITE_HDLCD_REG(HDLCD_REG_V_SYNC, hdlcd->fb.var.vsync_len - 1); + WRITE_HDLCD_REG(HDLCD_REG_V_BACK_PORCH, hdlcd->fb.var.upper_margin - 1); + WRITE_HDLCD_REG(HDLCD_REG_V_DATA, hdlcd->fb.var.yres - 1); + WRITE_HDLCD_REG(HDLCD_REG_V_FRONT_PORCH, hdlcd->fb.var.lower_margin - 1); + WRITE_HDLCD_REG(HDLCD_REG_H_SYNC, hdlcd->fb.var.hsync_len - 1); + WRITE_HDLCD_REG(HDLCD_REG_H_BACK_PORCH, hdlcd->fb.var.left_margin - 1); + WRITE_HDLCD_REG(HDLCD_REG_H_DATA, hdlcd->fb.var.xres - 1); + WRITE_HDLCD_REG(HDLCD_REG_H_FRONT_PORCH, hdlcd->fb.var.right_margin - 1); + WRITE_HDLCD_REG(HDLCD_REG_POLARITIES, polarities); + WRITE_HDLCD_REG(HDLCD_REG_PIXEL_FORMAT, (bytes_per_pixel - 1) << 3); + WRITE_HDLCD_REG(HDLCD_REG_RED_SELECT, ((hdlcd->fb.var.red.length & 0xf) << 8) | hdlcd->fb.var.red.offset); + WRITE_HDLCD_REG(HDLCD_REG_GREEN_SELECT, ((hdlcd->fb.var.green.length & 0xf) << 8) | hdlcd->fb.var.green.offset); + WRITE_HDLCD_REG(HDLCD_REG_BLUE_SELECT, ((hdlcd->fb.var.blue.length & 0xf) << 8) | hdlcd->fb.var.blue.offset); + + hdlcd_set_output_mode(hdlcd->fb.var.xres, hdlcd->fb.var.yres); + + clk_set_rate(hdlcd->clk, (1000000000 / hdlcd->fb.var.pixclock) * 1000); + clk_enable(hdlcd->clk); + + hdlcd_enable(hdlcd); + + return 0; +} + +static int hdlcd_setcolreg(unsigned int regno, unsigned int red, unsigned int green, + unsigned int blue, unsigned int transp, struct fb_info *info) +{ + if (regno < 16) { + u32 *pal = info->pseudo_palette; + + pal[regno] = ((red >> 8) << info->var.red.offset) | + ((green >> 8) << info->var.green.offset) | + ((blue >> 8) << info->var.blue.offset); + } + + return 0; +} + +static irqreturn_t hdlcd_irq(int irq, void *data) +{ + struct hdlcd_device *hdlcd = data; + unsigned long irq_mask, irq_status; + + irq_mask = READ_HDLCD_REG(HDLCD_REG_INT_MASK); + irq_status = READ_HDLCD_REG(HDLCD_REG_INT_STATUS); + + /* acknowledge interrupt(s) */ + WRITE_HDLCD_REG(HDLCD_REG_INT_CLEAR, irq_status); + + if (irq_status & HDLCD_INTERRUPT_VSYNC) { + /* disable future VSYNC interrupts */ + WRITE_HDLCD_REG(HDLCD_REG_INT_MASK, irq_mask & ~HDLCD_INTERRUPT_VSYNC); + + complete(&hdlcd->vsync_completion); + } + + return IRQ_HANDLED; +} + +static int hdlcd_wait_for_vsync(struct fb_info *info) +{ + struct hdlcd_device *hdlcd = to_hdlcd_device(info); + unsigned long irq_mask; + int err; + + /* enable VSYNC interrupt */ + irq_mask = READ_HDLCD_REG(HDLCD_REG_INT_MASK); + WRITE_HDLCD_REG(HDLCD_REG_INT_MASK, irq_mask | HDLCD_INTERRUPT_VSYNC); + + err = wait_for_completion_interruptible_timeout(&hdlcd->vsync_completion, + msecs_to_jiffies(100)); + + if (!err) + return -ETIMEDOUT; + + return 0; +} + +static int hdlcd_blank(int blank_mode, struct fb_info *info) +{ + struct hdlcd_device *hdlcd = to_hdlcd_device(info); + + switch (blank_mode) { + case FB_BLANK_POWERDOWN: + clk_disable(hdlcd->clk); + case FB_BLANK_NORMAL: + hdlcd_disable(hdlcd); + break; + case FB_BLANK_UNBLANK: + clk_enable(hdlcd->clk); + hdlcd_enable(hdlcd); + break; + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + default: + return 1; + } + + return 0; +} + +static void hdlcd_mmap_open(struct vm_area_struct *vma) +{ +} + +static void hdlcd_mmap_close(struct vm_area_struct *vma) +{ +} + +static struct vm_operations_struct hdlcd_mmap_ops = { + .open = hdlcd_mmap_open, + .close = hdlcd_mmap_close, +}; + +static int hdlcd_mmap(struct fb_info *info, struct vm_area_struct *vma) +{ + struct hdlcd_device *hdlcd = to_hdlcd_device(info); + unsigned long off; + unsigned long start; + unsigned long len = hdlcd->fb.fix.smem_len; + + if (vma->vm_end - vma->vm_start == 0) + return 0; + if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT)) + return -EINVAL; + + off = vma->vm_pgoff << PAGE_SHIFT; + if ((off >= len) || (vma->vm_end - vma->vm_start + off) > len) + return -EINVAL; + + start = hdlcd->fb.fix.smem_start; + off += start; + + vma->vm_pgoff = off >> PAGE_SHIFT; + vma->vm_flags |= VM_IO; + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + vma->vm_ops = &hdlcd_mmap_ops; + if (io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT, + vma->vm_end - vma->vm_start, + vma->vm_page_prot)) + return -EAGAIN; + + return 0; +} + +static int hdlcd_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct hdlcd_device *hdlcd = to_hdlcd_device(info); + + hdlcd->fb.var.yoffset = var->yoffset; + WRITE_HDLCD_REG(HDLCD_REG_FB_BASE, hdlcd->fb.fix.smem_start + + (var->yoffset * hdlcd->fb.fix.line_length)); + + hdlcd_wait_for_vsync(info); + + return 0; +} + +static int hdlcd_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg) +{ + int err; + + switch (cmd) { + case FBIO_WAITFORVSYNC: + err = hdlcd_wait_for_vsync(info); + break; + default: + err = -ENOIOCTLCMD; + break; + } + + return err; +} + +static struct fb_ops hdlcd_ops = { + .owner = THIS_MODULE, + .fb_check_var = hdlcd_check_var, + .fb_set_par = hdlcd_set_par, + .fb_setcolreg = hdlcd_setcolreg, + .fb_blank = hdlcd_blank, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_mmap = hdlcd_mmap, + .fb_pan_display = hdlcd_pan_display, + .fb_ioctl = hdlcd_ioctl, + .fb_compat_ioctl = hdlcd_ioctl +}; + +static int hdlcd_setup(struct hdlcd_device *hdlcd) +{ + u32 version; + int err = -EFAULT; + + hdlcd->clk = clk_get(hdlcd->dev, NULL); + if (IS_ERR(hdlcd->clk)) { + dev_err(hdlcd->dev, "HDLCD: unable to find clock data\n"); + return PTR_ERR(hdlcd->clk); + } + + hdlcd->base = ioremap_nocache(hdlcd->fb.fix.mmio_start, hdlcd->fb.fix.mmio_len); + if (!hdlcd->base) { + dev_err(hdlcd->dev, "HDLCD: unable to map registers\n"); + goto remap_err; + } + + hdlcd->fb.pseudo_palette = kmalloc(sizeof(u32) * 16, GFP_KERNEL); + if (!hdlcd->fb.pseudo_palette) { + dev_err(hdlcd->dev, "HDLCD: unable to allocate pseudo_palette memory\n"); + err = -ENOMEM; + goto kmalloc_err; + } + + version = readl(hdlcd->base + HDLCD_REG_VERSION); + if ((version & HDLCD_PRODUCT_MASK) != HDLCD_PRODUCT_ID) { + dev_err(hdlcd->dev, "HDLCD: unknown product id: 0x%x\n", version); + err = -EINVAL; + goto kmalloc_err; + } + dev_info(hdlcd->dev, "HDLCD: found ARM HDLCD version r%dp%d\n", + (version & HDLCD_VERSION_MAJOR_MASK) >> 8, + version & HDLCD_VERSION_MINOR_MASK); + + strcpy(hdlcd->fb.fix.id, "hdlcd"); + hdlcd->fb.fbops = &hdlcd_ops; + hdlcd->fb.flags = FBINFO_FLAG_DEFAULT/* | FBINFO_VIRTFB*/; + + hdlcd->fb.fix.type = FB_TYPE_PACKED_PIXELS; + hdlcd->fb.fix.type_aux = 0; + hdlcd->fb.fix.xpanstep = 0; + hdlcd->fb.fix.ypanstep = 1; + hdlcd->fb.fix.ywrapstep = 0; + hdlcd->fb.fix.accel = FB_ACCEL_NONE; + + hdlcd->fb.var.nonstd = 0; + hdlcd->fb.var.activate = FB_ACTIVATE_NOW; + hdlcd->fb.var.height = -1; + hdlcd->fb.var.width = -1; + hdlcd->fb.var.accel_flags = 0; + + init_completion(&hdlcd->vsync_completion); + + if (hdlcd->edid) { + /* build modedb from EDID */ + fb_edid_to_monspecs(hdlcd->edid, &hdlcd->fb.monspecs); + fb_videomode_to_modelist(hdlcd->fb.monspecs.modedb, + hdlcd->fb.monspecs.modedb_len, + &hdlcd->fb.modelist); + fb_find_mode(&hdlcd->fb.var, &hdlcd->fb, fb_mode, + hdlcd->fb.monspecs.modedb, + hdlcd->fb.monspecs.modedb_len, + &hdlcd_default_mode, 32); + } else { + hdlcd->fb.monspecs.hfmin = 0; + hdlcd->fb.monspecs.hfmax = 100000; + hdlcd->fb.monspecs.vfmin = 0; + hdlcd->fb.monspecs.vfmax = 400; + hdlcd->fb.monspecs.dclkmin = 1000000; + hdlcd->fb.monspecs.dclkmax = 100000000; + fb_find_mode(&hdlcd->fb.var, &hdlcd->fb, fb_mode, NULL, 0, &hdlcd_default_mode, 32); + } + + dev_info(hdlcd->dev, "using %dx%d-%d@%d mode\n", hdlcd->fb.var.xres, + hdlcd->fb.var.yres, hdlcd->fb.var.bits_per_pixel, + hdlcd->fb.mode ? hdlcd->fb.mode->refresh : 60); + hdlcd->fb.var.xres_virtual = hdlcd->fb.var.xres; + hdlcd->fb.var.yres_virtual = hdlcd->fb.var.yres * 2; + + /* initialise and set the palette */ + if (fb_alloc_cmap(&hdlcd->fb.cmap, NR_PALETTE, 0)) { + dev_err(hdlcd->dev, "failed to allocate cmap memory\n"); + err = -ENOMEM; + goto setup_err; + } + fb_set_cmap(&hdlcd->fb.cmap, &hdlcd->fb); + + /* Allow max number of outstanding requests with the largest beat burst */ + WRITE_HDLCD_REG(HDLCD_REG_BUS_OPTIONS, HDLCD_BUS_MAX_OUTSTAND | HDLCD_BUS_BURST_16); + /* Set the framebuffer base to start of allocated memory */ + WRITE_HDLCD_REG(HDLCD_REG_FB_BASE, hdlcd->fb.fix.smem_start); + /* Ensure interrupts are disabled */ + WRITE_HDLCD_REG(HDLCD_REG_INT_MASK, 0); + + if (!register_framebuffer(&hdlcd->fb)) { + fb_set_var(&hdlcd->fb, &hdlcd->fb.var); + clk_enable(hdlcd->clk); + return 0; + } + + dev_err(hdlcd->dev, "HDLCD: cannot register framebuffer\n"); + + fb_dealloc_cmap(&hdlcd->fb.cmap); +setup_err: + iounmap(hdlcd->base); +kmalloc_err: + kfree(hdlcd->fb.pseudo_palette); +remap_err: + clk_put(hdlcd->clk); + return err; +} + +static inline unsigned char atohex(u8 data) +{ + if (!isxdigit(data)) + return 0; + /* truncate the upper nibble and add 9 to non-digit values */ + return (data > 0x39) ? ((data & 0xf) + 9) : (data & 0xf); +} + +/* EDID data is passed from devicetree in a literal string that can contain spaces and + the hexadecimal dump of the data */ +static int parse_edid_data(struct hdlcd_device *hdlcd, const u8 *edid_data, int data_len) +{ + int i, j; + + if (!edid_data) + return -EINVAL; + + hdlcd->edid = kzalloc(EDID_LENGTH, GFP_KERNEL); + if (!hdlcd->edid) + return -ENOMEM; + + for (i = 0, j = 0; i < data_len; i++) { + if (isspace(edid_data[i])) + continue; + hdlcd->edid[j++] = atohex(edid_data[i]); + if (j >= EDID_LENGTH) + break; + } + + if (j < EDID_LENGTH) { + kfree(hdlcd->edid); + hdlcd->edid = NULL; + return -EINVAL; + } + + return 0; +} + +static int hdlcd_probe(struct platform_device *pdev) +{ + int err = 0, i; + struct hdlcd_device *hdlcd; + struct resource *mem; +#ifdef CONFIG_OF + struct device_node *of_node; +#endif + + memset(&cached_var_screeninfo, 0, sizeof(struct fb_var_screeninfo)); + + dev_dbg(&pdev->dev, "HDLCD: probing\n"); + + hdlcd = kzalloc(sizeof(*hdlcd), GFP_KERNEL); + if (!hdlcd) + return -ENOMEM; + +#ifdef CONFIG_OF + of_node = pdev->dev.of_node; + if (of_node) { + int len; + const u8 *edid; + const void *prop = of_get_property(of_node, "mode", &len); + if (prop) + strncpy(fb_mode, prop, len); + prop = of_get_property(of_node, "framebuffer", &len); + if (prop) { + hdlcd->fb.fix.smem_start = of_read_ulong((const __be32 *) prop, 1); + framebuffer_size = of_read_ulong((const __be32 *)prop, 1); + if (framebuffer_size > HDLCD_MAX_FRAMEBUFFER_SIZE) + framebuffer_size = HDLCD_MAX_FRAMEBUFFER_SIZE; + dev_dbg(&pdev->dev, "HDLCD: phys_addr = 0x%lx, size = 0x%lx\n", + hdlcd->fb.fix.smem_start, framebuffer_size); + } + edid = of_get_property(of_node, "edid", &len); + if (edid) { + err = parse_edid_data(hdlcd, edid, len); +#ifdef CONFIG_SERIAL_AMBA_PCU_UART + } else { + /* ask the firmware to fetch the EDID */ + dev_dbg(&pdev->dev, "HDLCD: Requesting EDID data\n"); + hdlcd->edid = kzalloc(EDID_LENGTH, GFP_KERNEL); + if (!hdlcd->edid) + return -ENOMEM; + err = get_edid(hdlcd->edid); +#endif /* CONFIG_SERIAL_AMBA_PCU_UART */ + } + if (err) + dev_info(&pdev->dev, "HDLCD: Failed to parse EDID data\n"); + } +#endif /* CONFIG_OF */ + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) { + dev_err(&pdev->dev, "HDLCD: cannot get platform resources\n"); + err = -EINVAL; + goto resource_err; + } + + i = platform_get_irq(pdev, 0); + if (i < 0) { + dev_err(&pdev->dev, "HDLCD: no irq defined for vsync\n"); + err = -ENOENT; + goto resource_err; + } else { + err = request_irq(i, hdlcd_irq, 0, dev_name(&pdev->dev), hdlcd); + if (err) { + dev_err(&pdev->dev, "HDLCD: unable to request irq\n"); + goto resource_err; + } + hdlcd->irq = i; + } + + if (!request_mem_region(mem->start, resource_size(mem), dev_name(&pdev->dev))) { + err = -ENXIO; + goto request_err; + } + + if (!hdlcd->fb.fix.smem_start) { + dev_err(&pdev->dev, "platform did not allocate frame buffer memory\n"); + err = -ENOMEM; + goto memalloc_err; + } + hdlcd->fb.screen_base = ioremap_wc(hdlcd->fb.fix.smem_start, framebuffer_size); + if (!hdlcd->fb.screen_base) { + dev_err(&pdev->dev, "unable to ioremap framebuffer\n"); + err = -ENOMEM; + goto probe_err; + } + + hdlcd->fb.screen_size = framebuffer_size; + hdlcd->fb.fix.smem_len = framebuffer_size; + hdlcd->fb.fix.mmio_start = mem->start; + hdlcd->fb.fix.mmio_len = resource_size(mem); + + /* Clear the framebuffer */ + memset(hdlcd->fb.screen_base, 0, framebuffer_size); + + hdlcd->dev = &pdev->dev; + + dev_dbg(&pdev->dev, "HDLCD: framebuffer virt base %p, phys base 0x%lX\n", + hdlcd->fb.screen_base, (unsigned long)hdlcd->fb.fix.smem_start); + + err = hdlcd_setup(hdlcd); + + if (err) + goto probe_err; + + platform_set_drvdata(pdev, hdlcd); + return 0; + +probe_err: + iounmap(hdlcd->fb.screen_base); + memblock_free(hdlcd->fb.fix.smem_start, hdlcd->fb.fix.smem_start); + +memalloc_err: + release_mem_region(mem->start, resource_size(mem)); + +request_err: + free_irq(hdlcd->irq, hdlcd); + +resource_err: + kfree(hdlcd); + + return err; +} + +static int hdlcd_remove(struct platform_device *pdev) +{ + struct hdlcd_device *hdlcd = platform_get_drvdata(pdev); + + clk_disable(hdlcd->clk); + clk_put(hdlcd->clk); + + /* unmap memory */ + iounmap(hdlcd->fb.screen_base); + iounmap(hdlcd->base); + + /* deallocate fb memory */ + fb_dealloc_cmap(&hdlcd->fb.cmap); + kfree(hdlcd->fb.pseudo_palette); + memblock_free(hdlcd->fb.fix.smem_start, hdlcd->fb.fix.smem_start); + release_mem_region(hdlcd->fb.fix.mmio_start, hdlcd->fb.fix.mmio_len); + + free_irq(hdlcd->irq, NULL); + kfree(hdlcd); + + return 0; +} + +#ifdef CONFIG_PM +static int hdlcd_suspend(struct platform_device *pdev, pm_message_t state) +{ + /* not implemented yet */ + return 0; +} + +static int hdlcd_resume(struct platform_device *pdev) +{ + /* not implemented yet */ + return 0; +} +#else +#define hdlcd_suspend NULL +#define hdlcd_resume NULL +#endif + +static struct platform_driver hdlcd_driver = { + .probe = hdlcd_probe, + .remove = hdlcd_remove, + .suspend = hdlcd_suspend, + .resume = hdlcd_resume, + .driver = { + .name = "hdlcd", + .owner = THIS_MODULE, + .of_match_table = hdlcd_of_matches, + }, +}; + +static int __init hdlcd_init(void) +{ + return platform_driver_register(&hdlcd_driver); +} + +void __exit hdlcd_exit(void) +{ + platform_driver_unregister(&hdlcd_driver); +} + +module_init(hdlcd_init); +module_exit(hdlcd_exit); + +MODULE_AUTHOR("Liviu Dudau"); +MODULE_DESCRIPTION("ARM HDLCD core driver"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/arm-hdlcd.h b/include/linux/arm-hdlcd.h new file mode 100644 index 00000000000..085fbec1fec --- /dev/null +++ b/include/linux/arm-hdlcd.h @@ -0,0 +1,100 @@ +/* + * include/linux/arm-hdlcd.h + * + * Copyright (C) 2011 ARM Limited + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive + * for more details. + * + * ARM HDLCD Controller register definition + */ + +#include +#include + +/* register offsets */ +#define HDLCD_REG_VERSION 0x0000 /* ro */ +#define HDLCD_REG_INT_RAWSTAT 0x0010 /* rw */ +#define HDLCD_REG_INT_CLEAR 0x0014 /* wo */ +#define HDLCD_REG_INT_MASK 0x0018 /* rw */ +#define HDLCD_REG_INT_STATUS 0x001c /* ro */ +#define HDLCD_REG_USER_OUT 0x0020 /* rw */ +#define HDLCD_REG_FB_BASE 0x0100 /* rw */ +#define HDLCD_REG_FB_LINE_LENGTH 0x0104 /* rw */ +#define HDLCD_REG_FB_LINE_COUNT 0x0108 /* rw */ +#define HDLCD_REG_FB_LINE_PITCH 0x010c /* rw */ +#define HDLCD_REG_BUS_OPTIONS 0x0110 /* rw */ +#define HDLCD_REG_V_SYNC 0x0200 /* rw */ +#define HDLCD_REG_V_BACK_PORCH 0x0204 /* rw */ +#define HDLCD_REG_V_DATA 0x0208 /* rw */ +#define HDLCD_REG_V_FRONT_PORCH 0x020c /* rw */ +#define HDLCD_REG_H_SYNC 0x0210 /* rw */ +#define HDLCD_REG_H_BACK_PORCH 0x0214 /* rw */ +#define HDLCD_REG_H_DATA 0x0218 /* rw */ +#define HDLCD_REG_H_FRONT_PORCH 0x021c /* rw */ +#define HDLCD_REG_POLARITIES 0x0220 /* rw */ +#define HDLCD_REG_COMMAND 0x0230 /* rw */ +#define HDLCD_REG_PIXEL_FORMAT 0x0240 /* rw */ +#define HDLCD_REG_BLUE_SELECT 0x0244 /* rw */ +#define HDLCD_REG_GREEN_SELECT 0x0248 /* rw */ +#define HDLCD_REG_RED_SELECT 0x024c /* rw */ + +/* version */ +#define HDLCD_PRODUCT_ID 0x1CDC0000 +#define HDLCD_PRODUCT_MASK 0xFFFF0000 +#define HDLCD_VERSION_MAJOR_MASK 0x0000FF00 +#define HDLCD_VERSION_MINOR_MASK 0x000000FF + +/* interrupts */ +#define HDLCD_INTERRUPT_DMA_END (1 << 0) +#define HDLCD_INTERRUPT_BUS_ERROR (1 << 1) +#define HDLCD_INTERRUPT_VSYNC (1 << 2) +#define HDLCD_INTERRUPT_UNDERRUN (1 << 3) + +/* polarity */ +#define HDLCD_POLARITY_VSYNC (1 << 0) +#define HDLCD_POLARITY_HSYNC (1 << 1) +#define HDLCD_POLARITY_DATAEN (1 << 2) +#define HDLCD_POLARITY_DATA (1 << 3) +#define HDLCD_POLARITY_PIXELCLK (1 << 4) + +/* commands */ +#define HDLCD_COMMAND_DISABLE (0 << 0) +#define HDLCD_COMMAND_ENABLE (1 << 0) + +/* pixel format */ +#define HDLCD_PIXEL_FMT_LITTLE_ENDIAN (0 << 31) +#define HDLCD_PIXEL_FMT_BIG_ENDIAN (1 << 31) +#define HDLCD_BYTES_PER_PIXEL_MASK (3 << 3) + +/* bus options */ +#define HDLCD_BUS_BURST_MASK 0x01f +#define HDLCD_BUS_MAX_OUTSTAND 0xf00 +#define HDLCD_BUS_BURST_NONE (0 << 0) +#define HDLCD_BUS_BURST_1 (1 << 0) +#define HDLCD_BUS_BURST_2 (1 << 1) +#define HDLCD_BUS_BURST_4 (1 << 2) +#define HDLCD_BUS_BURST_8 (1 << 3) +#define HDLCD_BUS_BURST_16 (1 << 4) + +/* Max resolution supported is 4096x4096, 8 bit per color component, + 8 bit alpha, but we are going to choose the usual hardware default + (2048x2048, 32 bpp) and enable double buffering */ +#define HDLCD_MAX_XRES 2048 +#define HDLCD_MAX_YRES 2048 +#define HDLCD_MAX_FRAMEBUFFER_SIZE (HDLCD_MAX_XRES * HDLCD_MAX_YRES << 2) + +#define HDLCD_MEM_BASE (CONFIG_PAGE_OFFSET - 0x1000000) + +#define NR_PALETTE 256 + +struct hdlcd_device { + struct fb_info fb; + struct device *dev; + struct clk *clk; + void __iomem *base; + int irq; + struct completion vsync_completion; + unsigned char *edid; +}; -- cgit v1.2.3 From 7c7d3b31d5e22bed91202c022932e4bde0cf1822 Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Wed, 13 Jun 2012 09:45:43 +0100 Subject: ARM HDLCD: Add missing clk_{un}prepare calls Signed-off-by: Jon Medhurst --- drivers/video/arm-hdlcd.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/video/arm-hdlcd.c b/drivers/video/arm-hdlcd.c index 3e103a5c9b5..221004379c7 100644 --- a/drivers/video/arm-hdlcd.c +++ b/drivers/video/arm-hdlcd.c @@ -219,6 +219,7 @@ static int hdlcd_set_par(struct fb_info *info) hdlcd_set_output_mode(hdlcd->fb.var.xres, hdlcd->fb.var.yres); + clk_prepare(hdlcd->clk); clk_set_rate(hdlcd->clk, (1000000000 / hdlcd->fb.var.pixclock) * 1000); clk_enable(hdlcd->clk); @@ -674,6 +675,7 @@ static int hdlcd_remove(struct platform_device *pdev) struct hdlcd_device *hdlcd = platform_get_drvdata(pdev); clk_disable(hdlcd->clk); + clk_unprepare(hdlcd->clk); clk_put(hdlcd->clk); /* unmap memory */ -- cgit v1.2.3 From 51f1911828c30b0ce27825452cef4db5a00edb07 Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Fri, 13 Jul 2012 13:32:48 +0100 Subject: ARM HDLCD: Fixup driver to resemble ARM's latest codebase. Signed-off-by: Jon Medhurst --- drivers/video/arm-hdlcd.c | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/drivers/video/arm-hdlcd.c b/drivers/video/arm-hdlcd.c index 221004379c7..948d41c1413 100644 --- a/drivers/video/arm-hdlcd.c +++ b/drivers/video/arm-hdlcd.c @@ -29,10 +29,12 @@ #include "edid.h" -#ifdef CONFIG_SERIAL_AMBA_PCU_UART /* set the DVI output mode using the firmware */ -int set_dvi_mode(u8 *msgbuf); +extern int set_dvi_mode(int mode); + +#ifdef CONFIG_SERIAL_AMBA_PCU_UART int get_edid(u8 *msgbuf); +#else #endif #define to_hdlcd_device(info) container_of(info, struct hdlcd_device, fb) @@ -140,34 +142,29 @@ static int hdlcd_check_var(struct fb_var_screeninfo *var, struct fb_info *info) return hdlcd_set_bitfields(hdlcd, var); } -#ifdef CONFIG_SERIAL_AMBA_PCU_UART static int hdlcd_set_output_mode(int xres, int yres) { - /* firmware uses some stupid protocol: 5 bytes (only byte 1 used) + /* some firmware uses some stupid protocol: 5 bytes (only byte 1 used) to send 3 bits of information (value between 0 - 5) */ u8 msgbuffer[5]; memset(msgbuffer, 0, sizeof(msgbuffer)); - /* default resolution: 640 x 480 */ - if (xres == 800 && yres <= 600) + + if (xres < 800 && yres < 600) + msgbuffer[0] = 0; /* default resolution: 640 x 480 */ + else if (xres < 1024 && yres < 768) msgbuffer[0] = 1; /* SVGA: 800 * 600 */ - else if (xres == 1024 && yres <= 768) + else if (xres < 1280 && yres < 1024) msgbuffer[0] = 2; /* XGA: 1024 * 768 */ - else if (xres == 1280 && yres <= 1024) + else if (xres < 1600 && yres < 1200) msgbuffer[0] = 3; /* SXGA: 1280 * 1024 */ - else if (xres == 1600 && yres <= 1200) + else if (xres < 1920 && yres <= 1200) msgbuffer[0] = 4; /* UXGA: 1600 * 1200 */ - else if (xres == 1920 && yres <= 1200) + else msgbuffer[0] = 5; /* WUXGA: 1920 * 1200 */ - return set_dvi_mode(msgbuffer); -} -#else -inline int hdlcd_set_output_mode(int xres, int yres) -{ - return 0; + return set_dvi_mode(msgbuffer[0]); } -#endif #define WRITE_HDLCD_REG(reg, value) writel((value), hdlcd->base + (reg)) #define READ_HDLCD_REG(reg) readl(hdlcd->base + (reg)) @@ -565,13 +562,16 @@ static int hdlcd_probe(struct platform_device *pdev) if (of_node) { int len; const u8 *edid; - const void *prop = of_get_property(of_node, "mode", &len); + const __be32 *prop = of_get_property(of_node, "mode", &len); if (prop) - strncpy(fb_mode, prop, len); + strncpy(fb_mode, (char *)prop, len); prop = of_get_property(of_node, "framebuffer", &len); if (prop) { - hdlcd->fb.fix.smem_start = of_read_ulong((const __be32 *) prop, 1); - framebuffer_size = of_read_ulong((const __be32 *)prop, 1); + hdlcd->fb.fix.smem_start = of_read_ulong(prop, + of_n_addr_cells(of_node)); + prop += of_n_addr_cells(of_node); + framebuffer_size = of_read_ulong(prop, + of_n_size_cells(of_node)); if (framebuffer_size > HDLCD_MAX_FRAMEBUFFER_SIZE) framebuffer_size = HDLCD_MAX_FRAMEBUFFER_SIZE; dev_dbg(&pdev->dev, "HDLCD: phys_addr = 0x%lx, size = 0x%lx\n", -- cgit v1.2.3 From f03c663576a50642f1e9f142627a86de61a6574e Mon Sep 17 00:00:00 2001 From: Dietmar Eggemann Date: Thu, 21 Jun 2012 17:16:33 +0100 Subject: ARM HDLCD: Get it working under Android. Add a shortcut when set_par is called to only change the yoffset. Android is doing that instead of calling pan_display to flip the framebuffer. Signed-off-by: Chris Redpath --- drivers/video/arm-hdlcd.c | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/drivers/video/arm-hdlcd.c b/drivers/video/arm-hdlcd.c index 948d41c1413..526d771db99 100644 --- a/drivers/video/arm-hdlcd.c +++ b/drivers/video/arm-hdlcd.c @@ -166,6 +166,11 @@ static int hdlcd_set_output_mode(int xres, int yres) return set_dvi_mode(msgbuffer[0]); } + +/* prototype */ +static int hdlcd_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info); + #define WRITE_HDLCD_REG(reg, value) writel((value), hdlcd->base + (reg)) #define READ_HDLCD_REG(reg) readl(hdlcd->base + (reg)) @@ -174,9 +179,22 @@ static int hdlcd_set_par(struct fb_info *info) struct hdlcd_device *hdlcd = to_hdlcd_device(info); int bytes_per_pixel = hdlcd->fb.var.bits_per_pixel / 8; int polarities; - - if (!memcmp(&info->var, &cached_var_screeninfo, sizeof(struct fb_var_screeninfo))) + int old_yoffset; + + /* check for shortcuts */ + old_yoffset = cached_var_screeninfo.yoffset; + cached_var_screeninfo.yoffset = info->var.yoffset; + if (!memcmp(&info->var, &cached_var_screeninfo, + sizeof(struct fb_var_screeninfo))) { + if(old_yoffset != info->var.yoffset) { + /* we only changed yoffset */ + hdlcd_pan_display(&info->var, info); + memcpy(&cached_var_screeninfo, &info->var, + sizeof(struct fb_var_screeninfo)); + } + /* or no change */ return 0; + } hdlcd->fb.fix.line_length = hdlcd->fb.var.xres * bytes_per_pixel; -- cgit v1.2.3 From 585617333b9f883182dd285b50710deab04d4e00 Mon Sep 17 00:00:00 2001 From: Chris Redpath Date: Fri, 29 Jun 2012 13:10:35 +0100 Subject: ARM HDLCD: Change default byte ordering to match Android assumption Change color byte location in 32-bit word from argb to abgr to match the assumption made in Android when 32-bit color displays are used. Signed-off-by: Chris Redpath --- drivers/video/arm-hdlcd.c | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/drivers/video/arm-hdlcd.c b/drivers/video/arm-hdlcd.c index 526d771db99..9b5b21ab136 100644 --- a/drivers/video/arm-hdlcd.c +++ b/drivers/video/arm-hdlcd.c @@ -116,10 +116,25 @@ static int hdlcd_set_bitfields(struct hdlcd_device *hdlcd, } if (!ret) { - var->green.offset = var->blue.length; - var->red.offset = var->green.offset + var->green.length; - if (var->bits_per_pixel == 32) - var->transp.offset = var->red.offset + var->red.length; + if(var->bits_per_pixel != 32) + { + var->green.offset = var->blue.length; + var->red.offset = var->green.offset + var->green.length; + } + else + { + /* Previously, the byte ordering for 32-bit color was + * (msb)(lsb) + * but this does not match what android expects and + * the colors are odd. Instead, use + * + * Since we tell fb what we are doing, console + * , X and directfb access should work fine. + */ + var->green.offset = var->red.length; + var->blue.offset = var->green.offset + var->green.length; + var->transp.offset = var->blue.offset + var->blue.length; + } } return ret; -- cgit v1.2.3 From 7195cb14440127f4bcda912dd51cfc2760f9f25e Mon Sep 17 00:00:00 2001 From: Chris Redpath Date: Fri, 29 Jun 2012 16:07:08 +0100 Subject: ARM HDLCD: Add useful functions to HDLCD driver for system integration During TC2 integration a bad config option resulted in HDLCD memory reads not being serviced often enough. This lead to unsightly screen blanking. These options allow the developer to count the number of underruns and to control the color used by HDLCD when an underrun prevents accessing pixel data. The combination of these two options allow easy diagnosis of HDLCD underrun conditions. Signed-off-by: Chris Redpath --- drivers/video/arm-hdlcd.c | 90 +++++++++++++++++++++++++++++++++++++++++++++-- include/linux/arm-hdlcd.h | 14 ++++++++ 2 files changed, 101 insertions(+), 3 deletions(-) diff --git a/drivers/video/arm-hdlcd.c b/drivers/video/arm-hdlcd.c index 9b5b21ab136..8d5409db6b1 100644 --- a/drivers/video/arm-hdlcd.c +++ b/drivers/video/arm-hdlcd.c @@ -26,6 +26,10 @@ #include #include #include +#ifdef HDLCD_COUNT_BUFFERUNDERRUNS +#include +#include +#endif #include "edid.h" @@ -47,6 +51,63 @@ static struct of_device_id hdlcd_of_matches[] = { /* Framebuffer size. */ static unsigned long framebuffer_size; +#ifdef HDLCD_COUNT_BUFFERUNDERRUNS +static unsigned long buffer_underrun_events; +static DEFINE_SPINLOCK(hdlcd_underrun_lock); + +static void hdlcd_underrun_set(unsigned long val) +{ + spin_lock(&hdlcd_underrun_lock); + buffer_underrun_events = val; + spin_unlock(&hdlcd_underrun_lock); +} + +static unsigned long hdlcd_underrun_get(void) +{ + unsigned long val; + spin_lock(&hdlcd_underrun_lock); + val = buffer_underrun_events; + spin_unlock(&hdlcd_underrun_lock); + return val; +} + +#ifdef CONFIG_PROC_FS +static int hdlcd_underrun_show(struct seq_file *m, void *v) +{ + unsigned char underrun_string[32]; + snprintf(underrun_string, 32, "%lu\n", hdlcd_underrun_get()); + seq_puts(m, underrun_string); + return 0; +} + +static int proc_hdlcd_underrun_open(struct inode *inode, struct file *file) +{ + return single_open(file, hdlcd_underrun_show, NULL); +} + +static const struct file_operations proc_hdlcd_underrun_operations = { + .open = proc_hdlcd_underrun_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int hdlcd_underrun_init(void) +{ + hdlcd_underrun_set(0); + proc_create("hdlcd_underrun", 0, NULL, &proc_hdlcd_underrun_operations); + return 0; +} +static void hdlcd_underrun_close(void) +{ + remove_proc_entry("hdlcd_underrun", NULL); +} +#else +static int hdlcd_underrun_init(void) { return 0; } +static void hdlcd_underrun_close(void) { } +#endif +#endif + static char *fb_mode = "1680x1050-32@60\0\0\0\0\0"; static struct fb_var_screeninfo cached_var_screeninfo; @@ -66,7 +127,6 @@ static struct fb_videomode hdlcd_default_mode = { .vmode = FB_VMODE_NONINTERLACED }; - static inline void hdlcd_enable(struct hdlcd_device *hdlcd) { dev_dbg(hdlcd->dev, "HDLCD: output enabled\n"); @@ -243,7 +303,12 @@ static int hdlcd_set_par(struct fb_info *info) WRITE_HDLCD_REG(HDLCD_REG_H_FRONT_PORCH, hdlcd->fb.var.right_margin - 1); WRITE_HDLCD_REG(HDLCD_REG_POLARITIES, polarities); WRITE_HDLCD_REG(HDLCD_REG_PIXEL_FORMAT, (bytes_per_pixel - 1) << 3); +#ifdef HDLCD_RED_DEFAULT_COLOUR + WRITE_HDLCD_REG(HDLCD_REG_RED_SELECT, (0x00ff0000 | (hdlcd->fb.var.red.length & 0xf) << 8) \ + | hdlcd->fb.var.red.offset); +#else WRITE_HDLCD_REG(HDLCD_REG_RED_SELECT, ((hdlcd->fb.var.red.length & 0xf) << 8) | hdlcd->fb.var.red.offset); +#endif WRITE_HDLCD_REG(HDLCD_REG_GREEN_SELECT, ((hdlcd->fb.var.green.length & 0xf) << 8) | hdlcd->fb.var.green.offset); WRITE_HDLCD_REG(HDLCD_REG_BLUE_SELECT, ((hdlcd->fb.var.blue.length & 0xf) << 8) | hdlcd->fb.var.blue.offset); @@ -282,7 +347,12 @@ static irqreturn_t hdlcd_irq(int irq, void *data) /* acknowledge interrupt(s) */ WRITE_HDLCD_REG(HDLCD_REG_INT_CLEAR, irq_status); - +#ifdef HDLCD_COUNT_BUFFERUNDERRUNS + if (irq_status & HDLCD_INTERRUPT_UNDERRUN) { + /* increment the count */ + hdlcd_underrun_set(hdlcd_underrun_get() + 1); + } +#endif if (irq_status & HDLCD_INTERRUPT_VSYNC) { /* disable future VSYNC interrupts */ WRITE_HDLCD_REG(HDLCD_REG_INT_MASK, irq_mask & ~HDLCD_INTERRUPT_VSYNC); @@ -514,9 +584,13 @@ static int hdlcd_setup(struct hdlcd_device *hdlcd) WRITE_HDLCD_REG(HDLCD_REG_BUS_OPTIONS, HDLCD_BUS_MAX_OUTSTAND | HDLCD_BUS_BURST_16); /* Set the framebuffer base to start of allocated memory */ WRITE_HDLCD_REG(HDLCD_REG_FB_BASE, hdlcd->fb.fix.smem_start); +#ifdef HDLCD_COUNT_BUFFERUNDERRUNS + /* turn on underrun interrupt for counting */ + WRITE_HDLCD_REG(HDLCD_REG_INT_MASK, HDLCD_INTERRUPT_UNDERRUN); +#else /* Ensure interrupts are disabled */ WRITE_HDLCD_REG(HDLCD_REG_INT_MASK, 0); - +#endif if (!register_framebuffer(&hdlcd->fb)) { fb_set_var(&hdlcd->fb, &hdlcd->fb.var); clk_enable(hdlcd->clk); @@ -758,11 +832,21 @@ static struct platform_driver hdlcd_driver = { static int __init hdlcd_init(void) { +#ifdef HDLCD_COUNT_BUFFERUNDERRUNS + int err = platform_driver_register(&hdlcd_driver); + if (!err) + hdlcd_underrun_init(); + return err; +#else return platform_driver_register(&hdlcd_driver); +#endif } void __exit hdlcd_exit(void) { +#ifdef HDLCD_COUNT_BUFFERUNDERRUNS + hdlcd_underrun_close(); +#endif platform_driver_unregister(&hdlcd_driver); } diff --git a/include/linux/arm-hdlcd.h b/include/linux/arm-hdlcd.h index 085fbec1fec..f3f4887ac4f 100644 --- a/include/linux/arm-hdlcd.h +++ b/include/linux/arm-hdlcd.h @@ -89,6 +89,20 @@ #define NR_PALETTE 256 +/* OEMs using HDLCD may wish to enable these settings if + * display disruption is apparent and you suspect HDLCD + * access to RAM may be starved. + */ +/* Turn HDLCD default color red instead of black so + * that it's easy to see pixel clock data underruns + * (compared to other visual disruption) + */ +//#define HDLCD_RED_DEFAULT_COLOUR +/* Add a counter in the IRQ handler to count buffer underruns + * and /proc/hdlcd_underrun to read the counter + */ +//#define HDLCD_COUNT_BUFFERUNDERRUNS + struct hdlcd_device { struct fb_info fb; struct device *dev; -- cgit v1.2.3 From 5705f5f9c6d47d59af73145b5febd1b1d5a56775 Mon Sep 17 00:00:00 2001 From: Chris Redpath Date: Fri, 29 Jun 2012 16:07:46 +0100 Subject: ARM HDLCD: Review comments from Liviu - extraneous memcpy Signed-off-by: Chris Redpath Reviewed-By: Liviu Dudau --- drivers/video/arm-hdlcd.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/video/arm-hdlcd.c b/drivers/video/arm-hdlcd.c index 8d5409db6b1..f16bb882e0c 100644 --- a/drivers/video/arm-hdlcd.c +++ b/drivers/video/arm-hdlcd.c @@ -262,10 +262,10 @@ static int hdlcd_set_par(struct fb_info *info) if (!memcmp(&info->var, &cached_var_screeninfo, sizeof(struct fb_var_screeninfo))) { if(old_yoffset != info->var.yoffset) { - /* we only changed yoffset */ + /* we only changed yoffset, and we already + * already recorded it a couple lines up + */ hdlcd_pan_display(&info->var, info); - memcpy(&cached_var_screeninfo, &info->var, - sizeof(struct fb_var_screeninfo)); } /* or no change */ return 0; -- cgit v1.2.3 From 9f14266f6f6108f0ff67be65da8866f3b757beec Mon Sep 17 00:00:00 2001 From: Chris Redpath Date: Thu, 12 Jul 2012 12:33:13 +0100 Subject: ARM HDLCD: Add developer option to remove double-height framebuffers This option can be used with Android to push the graphics subsystem into a different composition strategy which is more effective when used on hardware where the framebuffer memory is not cacheable. Signed-off-by: Chris Redpath --- drivers/video/arm-hdlcd.c | 8 ++++++++ include/linux/arm-hdlcd.h | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/drivers/video/arm-hdlcd.c b/drivers/video/arm-hdlcd.c index f16bb882e0c..9600c70209b 100644 --- a/drivers/video/arm-hdlcd.c +++ b/drivers/video/arm-hdlcd.c @@ -205,7 +205,11 @@ static int hdlcd_check_var(struct fb_var_screeninfo *var, struct fb_info *info) struct hdlcd_device *hdlcd = to_hdlcd_device(info); int bytes_per_pixel = var->bits_per_pixel / 8; +#ifdef HDLCD_NO_VIRTUAL_SCREEN + var->yres_virtual = var->yres; +#else var->yres_virtual = 2 * var->yres; +#endif if ((var->xres_virtual * bytes_per_pixel * var->yres_virtual) > hdlcd->fb.fix.smem_len) return -ENOMEM; @@ -570,7 +574,11 @@ static int hdlcd_setup(struct hdlcd_device *hdlcd) hdlcd->fb.var.yres, hdlcd->fb.var.bits_per_pixel, hdlcd->fb.mode ? hdlcd->fb.mode->refresh : 60); hdlcd->fb.var.xres_virtual = hdlcd->fb.var.xres; +#ifdef HDLCD_NO_VIRTUAL_SCREEN + hdlcd->fb.var.yres_virtual = hdlcd->fb.var.yres; +#else hdlcd->fb.var.yres_virtual = hdlcd->fb.var.yres * 2; +#endif /* initialise and set the palette */ if (fb_alloc_cmap(&hdlcd->fb.cmap, NR_PALETTE, 0)) { diff --git a/include/linux/arm-hdlcd.h b/include/linux/arm-hdlcd.h index f3f4887ac4f..124995aa34f 100644 --- a/include/linux/arm-hdlcd.h +++ b/include/linux/arm-hdlcd.h @@ -102,6 +102,10 @@ * and /proc/hdlcd_underrun to read the counter */ //#define HDLCD_COUNT_BUFFERUNDERRUNS +/* Restrict height to 1x screen size + * + */ +//#define HDLCD_NO_VIRTUAL_SCREEN struct hdlcd_device { struct fb_info fb; -- cgit v1.2.3 From e361c22545d9879a7f20f45aaea8b96366092cc4 Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Fri, 13 Jul 2012 14:25:50 +0100 Subject: ARM HDLCD: Enable HDLCD_NO_VIRTUAL_SCREEN on Android This is a hack which prevents annoying screen flicker in the Android UI. Signed-off-by: Jon Medhurst --- include/linux/arm-hdlcd.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/linux/arm-hdlcd.h b/include/linux/arm-hdlcd.h index 124995aa34f..939f3a81d56 100644 --- a/include/linux/arm-hdlcd.h +++ b/include/linux/arm-hdlcd.h @@ -107,6 +107,10 @@ */ //#define HDLCD_NO_VIRTUAL_SCREEN +#ifdef CONFIG_ANDROID +#define HDLCD_NO_VIRTUAL_SCREEN +#endif + struct hdlcd_device { struct fb_info fb; struct device *dev; -- cgit v1.2.3 From ac1b17efc7bae092fd46aeb144ddbf28add50877 Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Thu, 4 Oct 2012 12:38:06 +0100 Subject: ARM HDLCD: Changes to get HDLCD working with 've-updates' Signed-off-by: Jon Medhurst --- drivers/video/arm-hdlcd.c | 32 ++------------------------------ 1 file changed, 2 insertions(+), 30 deletions(-) diff --git a/drivers/video/arm-hdlcd.c b/drivers/video/arm-hdlcd.c index 9600c70209b..bd5fe68684a 100644 --- a/drivers/video/arm-hdlcd.c +++ b/drivers/video/arm-hdlcd.c @@ -33,9 +33,6 @@ #include "edid.h" -/* set the DVI output mode using the firmware */ -extern int set_dvi_mode(int mode); - #ifdef CONFIG_SERIAL_AMBA_PCU_UART int get_edid(u8 *msgbuf); #else @@ -221,31 +218,6 @@ static int hdlcd_check_var(struct fb_var_screeninfo *var, struct fb_info *info) return hdlcd_set_bitfields(hdlcd, var); } -static int hdlcd_set_output_mode(int xres, int yres) -{ - /* some firmware uses some stupid protocol: 5 bytes (only byte 1 used) - to send 3 bits of information (value between 0 - 5) */ - u8 msgbuffer[5]; - - memset(msgbuffer, 0, sizeof(msgbuffer)); - - if (xres < 800 && yres < 600) - msgbuffer[0] = 0; /* default resolution: 640 x 480 */ - else if (xres < 1024 && yres < 768) - msgbuffer[0] = 1; /* SVGA: 800 * 600 */ - else if (xres < 1280 && yres < 1024) - msgbuffer[0] = 2; /* XGA: 1024 * 768 */ - else if (xres < 1600 && yres < 1200) - msgbuffer[0] = 3; /* SXGA: 1280 * 1024 */ - else if (xres < 1920 && yres <= 1200) - msgbuffer[0] = 4; /* UXGA: 1600 * 1200 */ - else - msgbuffer[0] = 5; /* WUXGA: 1920 * 1200 */ - - return set_dvi_mode(msgbuffer[0]); -} - - /* prototype */ static int hdlcd_pan_display(struct fb_var_screeninfo *var, struct fb_info *info); @@ -316,8 +288,6 @@ static int hdlcd_set_par(struct fb_info *info) WRITE_HDLCD_REG(HDLCD_REG_GREEN_SELECT, ((hdlcd->fb.var.green.length & 0xf) << 8) | hdlcd->fb.var.green.offset); WRITE_HDLCD_REG(HDLCD_REG_BLUE_SELECT, ((hdlcd->fb.var.blue.length & 0xf) << 8) | hdlcd->fb.var.blue.offset); - hdlcd_set_output_mode(hdlcd->fb.var.xres, hdlcd->fb.var.yres); - clk_prepare(hdlcd->clk); clk_set_rate(hdlcd->clk, (1000000000 / hdlcd->fb.var.pixclock) * 1000); clk_enable(hdlcd->clk); @@ -502,6 +472,8 @@ static int hdlcd_setup(struct hdlcd_device *hdlcd) u32 version; int err = -EFAULT; + hdlcd->fb.device = hdlcd->dev; + hdlcd->clk = clk_get(hdlcd->dev, NULL); if (IS_ERR(hdlcd->clk)) { dev_err(hdlcd->dev, "HDLCD: unable to find clock data\n"); -- cgit v1.2.3 From e9a113ad8f8d113afd80028561f07426b229a31f Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Tue, 5 Mar 2013 09:24:57 +0800 Subject: ARM HDLCD: Fix compilation on Linux 3.9 Signed-off-by: Jon Medhurst --- drivers/video/arm-hdlcd.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/video/arm-hdlcd.c b/drivers/video/arm-hdlcd.c index bd5fe68684a..f9c4e7490c0 100644 --- a/drivers/video/arm-hdlcd.c +++ b/drivers/video/arm-hdlcd.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include -- cgit v1.2.3 From 6b614fae0ee3b62aea5345b80ec8ada919973a90 Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Wed, 13 Jun 2012 10:01:28 +0100 Subject: ARM: vexpress: Add support for HDLCD This is a temporary solution to get everything running. Signed-off-by: Jon Medhurst --- arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dts | 4 ++++ arch/arm/boot/dts/vexpress-v2p-ca5s.dts | 4 ++++ arch/arm/mach-vexpress/v2m.c | 28 ++++++++++++++++++++++++++++ 3 files changed, 36 insertions(+) diff --git a/arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dts b/arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dts index 9420053acc1..cc6a8c0cfe3 100644 --- a/arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dts +++ b/arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dts @@ -9,6 +9,8 @@ /dts-v1/; +/memreserve/ 0xbf000000 0x01000000; + / { model = "V2P-CA15"; arm,hbi = <0x237>; @@ -57,6 +59,8 @@ interrupts = <0 85 4>; clocks = <&oscclk5>; clock-names = "pxlclk"; + mode = "1024x768-16@60"; + framebuffer = <0 0xff000000 0 0x01000000>; }; memory-controller@2b0a0000 { diff --git a/arch/arm/boot/dts/vexpress-v2p-ca5s.dts b/arch/arm/boot/dts/vexpress-v2p-ca5s.dts index c544a550459..cf633ed6a1b 100644 --- a/arch/arm/boot/dts/vexpress-v2p-ca5s.dts +++ b/arch/arm/boot/dts/vexpress-v2p-ca5s.dts @@ -9,6 +9,8 @@ /dts-v1/; +/memreserve/ 0xbf000000 0x01000000; + / { model = "V2P-CA5s"; arm,hbi = <0x225>; @@ -59,6 +61,8 @@ interrupts = <0 85 4>; clocks = <&oscclk3>; clock-names = "pxlclk"; + mode = "640x480-16@60"; + framebuffer = <0xbf000000 0x01000000>; }; memory-controller@2a150000 { diff --git a/arch/arm/mach-vexpress/v2m.c b/arch/arm/mach-vexpress/v2m.c index 8802030df98..c227959e5e4 100644 --- a/arch/arm/mach-vexpress/v2m.c +++ b/arch/arm/mach-vexpress/v2m.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -373,6 +374,31 @@ MACHINE_START(VEXPRESS, "ARM-Versatile Express") .init_machine = v2m_init, MACHINE_END +static void __init v2m_dt_hdlcd_init(void) +{ + struct device_node *node; + int len, na, ns; + const __be32 *prop; + phys_addr_t fb_base, fb_size; + + node = of_find_compatible_node(NULL, NULL, "arm,hdlcd"); + if (!node) + return; + + na = of_n_addr_cells(node); + ns = of_n_size_cells(node); + + prop = of_get_property(node, "framebuffer", &len); + if (WARN_ON(!prop || len < (na + ns) * sizeof(*prop))) + return; + + fb_base = of_read_number(prop, na); + fb_size = of_read_number(prop + na, ns); + + if (WARN_ON(memblock_remove(fb_base, fb_size))) + return; +}; + static struct map_desc v2m_rs1_io_desc __initdata = { .virtual = V2M_PERIPH, .pfn = __phys_to_pfn(0x1c000000), @@ -423,6 +449,8 @@ void __init v2m_dt_init_early(void) pr_warning("vexpress: DT HBI (%x) is not matching " "hardware (%x)!\n", dt_hbi, hbi); } + + v2m_dt_hdlcd_init(); } static void __init v2m_dt_timer_init(void) -- cgit v1.2.3 From 0e2f1857008ef538fe170a914427aad552f61ada Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Tue, 26 Mar 2013 13:06:11 +0000 Subject: ARM: vexpress: Update TC2 device tree for HDLCD hack Signed-off-by: Jon Medhurst --- arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts b/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts index d2803be4e1a..6dfa9fa29a0 100644 --- a/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts +++ b/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts @@ -9,6 +9,8 @@ /dts-v1/; +/memreserve/ 0xff000000 0x01000000; + / { model = "V2P-CA15_CA7"; arm,hbi = <0x249>; @@ -81,6 +83,8 @@ compatible = "arm,hdlcd"; reg = <0 0x2b000000 0 0x1000>; interrupts = <0 85 4>; + mode = "1024x768-16@60"; + framebuffer = <0 0xff000000 0 0x01000000>; clocks = <&oscclk5>; clock-names = "pxlclk"; }; -- cgit v1.2.3 From 83bce1182d983be01f3247f974654cdac5690436 Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Fri, 14 Jun 2013 18:30:08 +0100 Subject: ARM HDLCD: Fix clock initialisation sequence This reworks HDLCD initialisation to mirror how CLCD does this, in particular to prepare the clock immediately after it has been got which ensures that we don't try and enable clocks before they were prepared, e.g. in the former clk_enable after register_framebuffer(). The reason this issue wasn't noticed before is that we have been setting CONFIG_FRAMEBUFFER_CONSOLE and this caused register_framebuffer() to trigger the creation of a console which calls hdlcd_set_par(), which in turn was preparing and enabling the clock. Signed-off-by: Jon Medhurst --- drivers/video/arm-hdlcd.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/video/arm-hdlcd.c b/drivers/video/arm-hdlcd.c index f9c4e7490c0..cfd631e3dc5 100644 --- a/drivers/video/arm-hdlcd.c +++ b/drivers/video/arm-hdlcd.c @@ -289,7 +289,6 @@ static int hdlcd_set_par(struct fb_info *info) WRITE_HDLCD_REG(HDLCD_REG_GREEN_SELECT, ((hdlcd->fb.var.green.length & 0xf) << 8) | hdlcd->fb.var.green.offset); WRITE_HDLCD_REG(HDLCD_REG_BLUE_SELECT, ((hdlcd->fb.var.blue.length & 0xf) << 8) | hdlcd->fb.var.blue.offset); - clk_prepare(hdlcd->clk); clk_set_rate(hdlcd->clk, (1000000000 / hdlcd->fb.var.pixclock) * 1000); clk_enable(hdlcd->clk); @@ -481,6 +480,10 @@ static int hdlcd_setup(struct hdlcd_device *hdlcd) return PTR_ERR(hdlcd->clk); } + err = clk_prepare(hdlcd->clk); + if (err) + goto clk_prepare_err; + hdlcd->base = ioremap_nocache(hdlcd->fb.fix.mmio_start, hdlcd->fb.fix.mmio_len); if (!hdlcd->base) { dev_err(hdlcd->dev, "HDLCD: unable to map registers\n"); @@ -572,9 +575,9 @@ static int hdlcd_setup(struct hdlcd_device *hdlcd) /* Ensure interrupts are disabled */ WRITE_HDLCD_REG(HDLCD_REG_INT_MASK, 0); #endif + fb_set_var(&hdlcd->fb, &hdlcd->fb.var); + if (!register_framebuffer(&hdlcd->fb)) { - fb_set_var(&hdlcd->fb, &hdlcd->fb.var); - clk_enable(hdlcd->clk); return 0; } @@ -586,6 +589,8 @@ setup_err: kmalloc_err: kfree(hdlcd->fb.pseudo_palette); remap_err: + clk_unprepare(hdlcd->clk); +clk_prepare_err: clk_put(hdlcd->clk); return err; } -- cgit v1.2.3 From 778a02466db200a8a3f2875ff64f991b70440107 Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Thu, 28 Mar 2013 15:57:56 +0000 Subject: amba-clcd: Add Device Tree support to amba-clcd driver Add support to parse the display configuration from device tree. If the board does not provide platform specific functions in the struct clcd_board contained with the amba device info, then defaults are provided by the driver. The device tree configuration can either ask for a DMA setup or provide a framebuffer address to be remapped into the driver. Signed-off-by: Ryan Harkin --- drivers/video/amba-clcd.c | 250 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 250 insertions(+) diff --git a/drivers/video/amba-clcd.c b/drivers/video/amba-clcd.c index 0a2cce7285b..3cacf26f3e5 100644 --- a/drivers/video/amba-clcd.c +++ b/drivers/video/amba-clcd.c @@ -16,7 +16,10 @@ #include #include #include +#include +#include #include +#include #include #include #include @@ -391,6 +394,19 @@ static int clcdfb_blank(int blank_mode, struct fb_info *info) } return 0; } +int clcdfb_mmap_dma(struct clcd_fb *fb, struct vm_area_struct *vma) +{ + return dma_mmap_writecombine(&fb->dev->dev, vma, + fb->fb.screen_base, + fb->fb.fix.smem_start, + fb->fb.fix.smem_len); +} + +void clcdfb_remove_dma(struct clcd_fb *fb) +{ + dma_free_writecombine(&fb->dev->dev, fb->fb.fix.smem_len, + fb->fb.screen_base, fb->fb.fix.smem_start); +} static int clcdfb_mmap(struct fb_info *info, struct vm_area_struct *vma) @@ -542,12 +558,246 @@ static int clcdfb_register(struct clcd_fb *fb) return ret; } +struct string_lookup { + const char *string; + const u32 val; +}; + +static struct string_lookup vmode_lookups[] = { + { "FB_VMODE_NONINTERLACED", FB_VMODE_NONINTERLACED}, + { "FB_VMODE_INTERLACED", FB_VMODE_INTERLACED}, + { "FB_VMODE_DOUBLE", FB_VMODE_DOUBLE}, + { "FB_VMODE_ODD_FLD_FIRST", FB_VMODE_ODD_FLD_FIRST}, + { NULL, 0 }, +}; + +static struct string_lookup tim2_lookups[] = { + { "TIM2_CLKSEL", TIM2_CLKSEL}, + { "TIM2_IVS", TIM2_IVS}, + { "TIM2_IHS", TIM2_IHS}, + { "TIM2_IPC", TIM2_IPC}, + { "TIM2_IOE", TIM2_IOE}, + { "TIM2_BCD", TIM2_BCD}, + { NULL, 0}, +}; +static struct string_lookup cntl_lookups[] = { + {"CNTL_LCDEN", CNTL_LCDEN}, + {"CNTL_LCDBPP1", CNTL_LCDBPP1}, + {"CNTL_LCDBPP2", CNTL_LCDBPP2}, + {"CNTL_LCDBPP4", CNTL_LCDBPP4}, + {"CNTL_LCDBPP8", CNTL_LCDBPP8}, + {"CNTL_LCDBPP16", CNTL_LCDBPP16}, + {"CNTL_LCDBPP16_565", CNTL_LCDBPP16_565}, + {"CNTL_LCDBPP16_444", CNTL_LCDBPP16_444}, + {"CNTL_LCDBPP24", CNTL_LCDBPP24}, + {"CNTL_LCDBW", CNTL_LCDBW}, + {"CNTL_LCDTFT", CNTL_LCDTFT}, + {"CNTL_LCDMONO8", CNTL_LCDMONO8}, + {"CNTL_LCDDUAL", CNTL_LCDDUAL}, + {"CNTL_BGR", CNTL_BGR}, + {"CNTL_BEBO", CNTL_BEBO}, + {"CNTL_BEPO", CNTL_BEPO}, + {"CNTL_LCDPWR", CNTL_LCDPWR}, + {"CNTL_LCDVCOMP(1)", CNTL_LCDVCOMP(1)}, + {"CNTL_LCDVCOMP(2)", CNTL_LCDVCOMP(2)}, + {"CNTL_LCDVCOMP(3)", CNTL_LCDVCOMP(3)}, + {"CNTL_LCDVCOMP(4)", CNTL_LCDVCOMP(4)}, + {"CNTL_LCDVCOMP(5)", CNTL_LCDVCOMP(5)}, + {"CNTL_LCDVCOMP(6)", CNTL_LCDVCOMP(6)}, + {"CNTL_LCDVCOMP(7)", CNTL_LCDVCOMP(7)}, + {"CNTL_LDMAFIFOTIME", CNTL_LDMAFIFOTIME}, + {"CNTL_WATERMARK", CNTL_WATERMARK}, + { NULL, 0}, +}; +static struct string_lookup caps_lookups[] = { + {"CLCD_CAP_RGB444", CLCD_CAP_RGB444}, + {"CLCD_CAP_RGB5551", CLCD_CAP_RGB5551}, + {"CLCD_CAP_RGB565", CLCD_CAP_RGB565}, + {"CLCD_CAP_RGB888", CLCD_CAP_RGB888}, + {"CLCD_CAP_BGR444", CLCD_CAP_BGR444}, + {"CLCD_CAP_BGR5551", CLCD_CAP_BGR5551}, + {"CLCD_CAP_BGR565", CLCD_CAP_BGR565}, + {"CLCD_CAP_BGR888", CLCD_CAP_BGR888}, + {"CLCD_CAP_444", CLCD_CAP_444}, + {"CLCD_CAP_5551", CLCD_CAP_5551}, + {"CLCD_CAP_565", CLCD_CAP_565}, + {"CLCD_CAP_888", CLCD_CAP_888}, + {"CLCD_CAP_RGB", CLCD_CAP_RGB}, + {"CLCD_CAP_BGR", CLCD_CAP_BGR}, + {"CLCD_CAP_ALL", CLCD_CAP_ALL}, + { NULL, 0}, +}; + +u32 parse_setting(struct string_lookup *lookup, const char *name) +{ + int i = 0; + while (lookup[i].string != NULL) { + if (strcmp(lookup[i].string, name) == 0) + return lookup[i].val; + ++i; + } + return -EINVAL; +} + +u32 get_string_lookup(struct device_node *node, const char *name, + struct string_lookup *lookup) +{ + const char *string; + int count, i, ret = 0; + + count = of_property_count_strings(node, name); + if (count >= 0) + for (i = 0; i < count; i++) + if (of_property_read_string_index(node, name, i, + &string) == 0) + ret |= parse_setting(lookup, string); + return ret; +} + +int get_val(struct device_node *node, const char *string) +{ + u32 ret = 0; + + if (of_property_read_u32(node, string, &ret)) + ret = -1; + return ret; +} + +struct clcd_panel *getPanel(struct device_node *node) +{ + static struct clcd_panel panel; + + panel.mode.refresh = get_val(node, "refresh"); + panel.mode.xres = get_val(node, "xres"); + panel.mode.yres = get_val(node, "yres"); + panel.mode.pixclock = get_val(node, "pixclock"); + panel.mode.left_margin = get_val(node, "left_margin"); + panel.mode.right_margin = get_val(node, "right_margin"); + panel.mode.upper_margin = get_val(node, "upper_margin"); + panel.mode.lower_margin = get_val(node, "lower_margin"); + panel.mode.hsync_len = get_val(node, "hsync_len"); + panel.mode.vsync_len = get_val(node, "vsync_len"); + panel.mode.sync = get_val(node, "sync"); + panel.bpp = get_val(node, "bpp"); + panel.width = (signed short) get_val(node, "width"); + panel.height = (signed short) get_val(node, "height"); + + panel.mode.vmode = get_string_lookup(node, "vmode", vmode_lookups); + panel.tim2 = get_string_lookup(node, "tim2", tim2_lookups); + panel.cntl = get_string_lookup(node, "cntl", cntl_lookups); + panel.caps = get_string_lookup(node, "caps", caps_lookups); + + return &panel; +} + +struct clcd_panel *clcdfb_get_panel(const char *name) +{ + struct device_node *node = NULL; + const char *mode; + struct clcd_panel *panel = NULL; + + do { + node = of_find_compatible_node(node, NULL, "panel"); + if (node) + if (of_property_read_string(node, "mode", &mode) == 0) + if (strcmp(mode, name) == 0) { + panel = getPanel(node); + panel->mode.name = name; + } + } while (node != NULL); + + return panel; +} + +#ifdef CONFIG_OF +static int clcdfb_dt_init(struct clcd_fb *fb) +{ + int err = 0; + struct device_node *node; + const char *mode; + dma_addr_t dma; + u32 use_dma; + const __be32 *prop; + int len, na, ns; + phys_addr_t fb_base, fb_size; + + node = fb->dev->dev.of_node; + if (!node) + return -ENODEV; + + na = of_n_addr_cells(node); + ns = of_n_size_cells(node); + + if (WARN_ON(of_property_read_string(node, "mode", &mode))) + return -ENODEV; + + fb->panel = clcdfb_get_panel(mode); + if (!fb->panel) + return -EINVAL; + fb->fb.fix.smem_len = fb->panel->mode.xres * fb->panel->mode.yres * 2; + + if (of_property_read_u32(node, "use_dma", &use_dma)) + use_dma = 0; + if (use_dma) { + fb->fb.screen_base = dma_alloc_writecombine(&fb->dev->dev, + fb->fb.fix.smem_len, &dma, GFP_KERNEL); + if (!fb->fb.screen_base) { + pr_err("CLCD: unable to map framebuffer\n"); + err = -ENOMEM; + } else + fb->fb.fix.smem_start = dma; + } else { + prop = of_get_property(node, "framebuffer", &len); + if (WARN_ON(!prop || len < (na + ns) * sizeof(*prop))) + return -EINVAL; + fb_base = of_read_number(prop, na); + fb_size = of_read_number(prop + na, ns); + + fb->fb.fix.smem_start = fb_base; + fb->fb.screen_base = ioremap_wc(fb->fb.fix.smem_start, fb_size); + } + return err; +} +#endif /* CONFIG_OF */ + static int clcdfb_probe(struct amba_device *dev, const struct amba_id *id) { struct clcd_board *board = dev->dev.platform_data; struct clcd_fb *fb; int ret; +#ifdef CONFIG_OF + if (dev->dev.of_node) { + const __be32 *prop; + int len, na, ns; + phys_addr_t reg_base; + + na = of_n_addr_cells(dev->dev.of_node); + ns = of_n_size_cells(dev->dev.of_node); + + prop = of_get_property(dev->dev.of_node, "reg", &len); + if (WARN_ON(!prop || len < (na + ns) * sizeof(*prop))) + return -EINVAL; + reg_base = of_read_number(prop, na); + + if (dev->res.start != reg_base) + return -EINVAL; + + if (!board) { + board = kzalloc(sizeof(struct clcd_board), GFP_KERNEL); + if (!board) + return -EINVAL; + board->name = "Device Tree CLCD PL111"; + board->caps = CLCD_CAP_5551 | CLCD_CAP_565; + board->check = clcdfb_check; + board->decode = clcdfb_decode; + board->setup = clcdfb_dt_init; + board->mmap = clcdfb_mmap_dma; + board->remove = clcdfb_remove_dma; + } + } +#endif /* CONFIG_OF */ + if (!board) return -EINVAL; -- cgit v1.2.3 From 95c8041c5018e75c58968f0ca2251482fe6b22d6 Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Wed, 10 Oct 2012 13:27:25 +0100 Subject: amba-clcd: Remove check for 'reg' value in clcdfb_probe This check was attempting to ensure only one clcd device in the device-tree was probed, however the check fails in the valid case where the device is a child of another device and the 'reg' value is a offset from the start of that other device, not an absolute address. This occurs on vexpress with the motherboard clcd being a child of iofga. For now, we will just have to rely on there only being one display device specified in device-tree. Signed-off-by: Jon Medhurst --- drivers/video/amba-clcd.c | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/drivers/video/amba-clcd.c b/drivers/video/amba-clcd.c index 3cacf26f3e5..53af3d18a0c 100644 --- a/drivers/video/amba-clcd.c +++ b/drivers/video/amba-clcd.c @@ -768,21 +768,6 @@ static int clcdfb_probe(struct amba_device *dev, const struct amba_id *id) #ifdef CONFIG_OF if (dev->dev.of_node) { - const __be32 *prop; - int len, na, ns; - phys_addr_t reg_base; - - na = of_n_addr_cells(dev->dev.of_node); - ns = of_n_size_cells(dev->dev.of_node); - - prop = of_get_property(dev->dev.of_node, "reg", &len); - if (WARN_ON(!prop || len < (na + ns) * sizeof(*prop))) - return -EINVAL; - reg_base = of_read_number(prop, na); - - if (dev->res.start != reg_base) - return -EINVAL; - if (!board) { board = kzalloc(sizeof(struct clcd_board), GFP_KERNEL); if (!board) -- cgit v1.2.3 From 8df3102afa56f6e0b8fdc6df4af0ac4d8436f9a6 Mon Sep 17 00:00:00 2001 From: Catalin Marinas Date: Fri, 4 Jan 2013 12:56:49 +0000 Subject: amba-clcd: Only use dma_alloc_writecombine() if the arch supports it This patch hides the dma_(alloc|free)_writecombine() calls behind macros to allow the amba-clcd.c to be used on architectures that do not provide this DMA API. With this patch, the *_writecombine() API is only used on ARM (AArch32). Signed-off-by: Catalin Marinas --- drivers/video/amba-clcd.c | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/drivers/video/amba-clcd.c b/drivers/video/amba-clcd.c index 53af3d18a0c..76b1e6bc5ee 100644 --- a/drivers/video/amba-clcd.c +++ b/drivers/video/amba-clcd.c @@ -33,6 +33,16 @@ #define to_clcd(info) container_of(info, struct clcd_fb, fb) +#ifdef CONFIG_ARM +#define clcdfb_dma_alloc dma_alloc_writecombine +#define clcdfb_dma_free dma_free_writecombine +#define clcdfb_dma_mmap dma_mmap_writecombine +#else +#define clcdfb_dma_alloc dma_alloc_coherent +#define clcdfb_dma_free dma_free_coherent +#define clcdfb_dma_mmap dma_mmap_coherent +#endif + /* This is limited to 16 characters when displayed by X startup */ static const char *clcd_name = "CLCD FB"; @@ -396,16 +406,19 @@ static int clcdfb_blank(int blank_mode, struct fb_info *info) } int clcdfb_mmap_dma(struct clcd_fb *fb, struct vm_area_struct *vma) { - return dma_mmap_writecombine(&fb->dev->dev, vma, - fb->fb.screen_base, - fb->fb.fix.smem_start, - fb->fb.fix.smem_len); + return clcdfb_dma_mmap(&fb->dev->dev, vma, fb->fb.screen_base, + fb->fb.fix.smem_start, fb->fb.fix.smem_len); } void clcdfb_remove_dma(struct clcd_fb *fb) { - dma_free_writecombine(&fb->dev->dev, fb->fb.fix.smem_len, - fb->fb.screen_base, fb->fb.fix.smem_start); + struct device_node *node = fb->dev->dev.of_node; + u32 use_dma = 0; + + of_property_read_u32(node, "use_dma", &use_dma); + if (use_dma) + clcdfb_dma_free(&fb->dev->dev, fb->fb.fix.smem_len, + fb->fb.screen_base, fb->fb.fix.smem_start); } static int clcdfb_mmap(struct fb_info *info, @@ -739,7 +752,7 @@ static int clcdfb_dt_init(struct clcd_fb *fb) if (of_property_read_u32(node, "use_dma", &use_dma)) use_dma = 0; if (use_dma) { - fb->fb.screen_base = dma_alloc_writecombine(&fb->dev->dev, + fb->fb.screen_base = clcdfb_dma_alloc(&fb->dev->dev, fb->fb.fix.smem_len, &dma, GFP_KERNEL); if (!fb->fb.screen_base) { pr_err("CLCD: unable to map framebuffer\n"); -- cgit v1.2.3 From 50ad0eb192d28ffaaac1a8c0ec97aa07bf9be645 Mon Sep 17 00:00:00 2001 From: Will Deacon Date: Mon, 28 Jan 2013 12:06:58 +0000 Subject: amba-clcd: separate ioremap framebuffer from DMA implementation The amba-clcd device can be configured to use either DMA or, when this feature is not available, an ioremapped frambuffer in static ram. In the case of the latter, we must take care not to pass ioremapped addresses to dma_common_mmap, since this expects only addresses from dma_mmap_coherent, which reside in the kernel linear mapping. This patch reworks the fb initialisation code so that either DMA or IO implementations of the mmap/remove functions are chosen as appropriate. Signed-off-by: Will Deacon Signed-off-by: Catalin Marinas --- drivers/video/amba-clcd.c | 84 +++++++++++++++++++++++++++++++---------------- 1 file changed, 56 insertions(+), 28 deletions(-) diff --git a/drivers/video/amba-clcd.c b/drivers/video/amba-clcd.c index 76b1e6bc5ee..94a1998338d 100644 --- a/drivers/video/amba-clcd.c +++ b/drivers/video/amba-clcd.c @@ -404,21 +404,43 @@ static int clcdfb_blank(int blank_mode, struct fb_info *info) } return 0; } + int clcdfb_mmap_dma(struct clcd_fb *fb, struct vm_area_struct *vma) { - return clcdfb_dma_mmap(&fb->dev->dev, vma, fb->fb.screen_base, - fb->fb.fix.smem_start, fb->fb.fix.smem_len); + return clcdfb_dma_mmap(&fb->dev->dev, vma, + fb->fb.screen_base, + fb->fb.fix.smem_start, + fb->fb.fix.smem_len); +} + +int clcdfb_mmap_io(struct clcd_fb *fb, struct vm_area_struct *vma) +{ + unsigned long user_count, count, pfn, off; + + user_count = (vma->vm_end - vma->vm_start) >> PAGE_SHIFT; + count = PAGE_ALIGN(fb->fb.fix.smem_len) >> PAGE_SHIFT; + pfn = fb->fb.fix.smem_start >> PAGE_SHIFT; + off = vma->vm_pgoff; + + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + if (off < count && user_count <= (count - off)) + return remap_pfn_range(vma, vma->vm_start, pfn + off, + user_count << PAGE_SHIFT, + vma->vm_page_prot); + + return -ENXIO; } void clcdfb_remove_dma(struct clcd_fb *fb) { - struct device_node *node = fb->dev->dev.of_node; - u32 use_dma = 0; + clcdfb_dma_free(&fb->dev->dev, fb->fb.fix.smem_len, + fb->fb.screen_base, fb->fb.fix.smem_start); +} - of_property_read_u32(node, "use_dma", &use_dma); - if (use_dma) - clcdfb_dma_free(&fb->dev->dev, fb->fb.fix.smem_len, - fb->fb.screen_base, fb->fb.fix.smem_start); +void clcdfb_remove_io(struct clcd_fb *fb) +{ + iounmap(fb->fb.screen_base); } static int clcdfb_mmap(struct fb_info *info, @@ -749,26 +771,40 @@ static int clcdfb_dt_init(struct clcd_fb *fb) return -EINVAL; fb->fb.fix.smem_len = fb->panel->mode.xres * fb->panel->mode.yres * 2; + fb->board->name = "Device Tree CLCD PL111"; + fb->board->caps = CLCD_CAP_5551 | CLCD_CAP_565; + fb->board->check = clcdfb_check; + fb->board->decode = clcdfb_decode; + if (of_property_read_u32(node, "use_dma", &use_dma)) use_dma = 0; + if (use_dma) { fb->fb.screen_base = clcdfb_dma_alloc(&fb->dev->dev, - fb->fb.fix.smem_len, &dma, GFP_KERNEL); + fb->fb.fix.smem_len, + &dma, GFP_KERNEL); if (!fb->fb.screen_base) { pr_err("CLCD: unable to map framebuffer\n"); - err = -ENOMEM; - } else - fb->fb.fix.smem_start = dma; + return -ENOMEM; + } + + fb->fb.fix.smem_start = dma; + fb->board->mmap = clcdfb_mmap_dma; + fb->board->remove = clcdfb_remove_dma; } else { prop = of_get_property(node, "framebuffer", &len); if (WARN_ON(!prop || len < (na + ns) * sizeof(*prop))) return -EINVAL; + fb_base = of_read_number(prop, na); fb_size = of_read_number(prop + na, ns); - fb->fb.fix.smem_start = fb_base; - fb->fb.screen_base = ioremap_wc(fb->fb.fix.smem_start, fb_size); + fb->fb.fix.smem_start = fb_base; + fb->fb.screen_base = ioremap_wc(fb_base, fb_size); + fb->board->mmap = clcdfb_mmap_io; + fb->board->remove = clcdfb_remove_io; } + return err; } #endif /* CONFIG_OF */ @@ -779,25 +815,17 @@ static int clcdfb_probe(struct amba_device *dev, const struct amba_id *id) struct clcd_fb *fb; int ret; + if (!board) { #ifdef CONFIG_OF - if (dev->dev.of_node) { - if (!board) { + if (dev->dev.of_node) { board = kzalloc(sizeof(struct clcd_board), GFP_KERNEL); if (!board) - return -EINVAL; - board->name = "Device Tree CLCD PL111"; - board->caps = CLCD_CAP_5551 | CLCD_CAP_565; - board->check = clcdfb_check; - board->decode = clcdfb_decode; + return -ENOMEM; board->setup = clcdfb_dt_init; - board->mmap = clcdfb_mmap_dma; - board->remove = clcdfb_remove_dma; - } + } else +#endif + return -EINVAL; } -#endif /* CONFIG_OF */ - - if (!board) - return -EINVAL; ret = amba_request_regions(dev, NULL); if (ret) { -- cgit v1.2.3 From c0ac923b5e9addc55f56fc777e1137c6d0a59c3c Mon Sep 17 00:00:00 2001 From: Ryan Harkin Date: Thu, 26 Jul 2012 19:05:10 +0100 Subject: ARM: vexpress: configure CLCD driver device tree support for A9 CoreTile Configuration for the amba-clcd PL111 driver is added to the A9 CoreTile's DTS file. Configuration of the motherboard CLCD driver is disabled in the DTSI files to prevent duplicate CLCD drivers being registered. A generic set of CLCD panel descriptions has been split into its own DTSI file. Currently, only XVGA and VGA monitors are described. Signed-off-by: Ryan Harkin Signed-off-by: Jon Medhurst --- arch/arm/boot/dts/clcd-panels.dtsi | 52 +++++++++++++++++++++++++++++++++ arch/arm/boot/dts/vexpress-v2m-rs1.dtsi | 1 + arch/arm/boot/dts/vexpress-v2m.dtsi | 1 + arch/arm/boot/dts/vexpress-v2p-ca9.dts | 4 +++ 4 files changed, 58 insertions(+) create mode 100644 arch/arm/boot/dts/clcd-panels.dtsi diff --git a/arch/arm/boot/dts/clcd-panels.dtsi b/arch/arm/boot/dts/clcd-panels.dtsi new file mode 100644 index 00000000000..0b0ff6ead4b --- /dev/null +++ b/arch/arm/boot/dts/clcd-panels.dtsi @@ -0,0 +1,52 @@ +/* + * ARM Ltd. Versatile Express + * + */ + +/ { + panels { + panel@0 { + compatible = "panel"; + mode = "VGA"; + refresh = <60>; + xres = <640>; + yres = <480>; + pixclock = <39721>; + left_margin = <40>; + right_margin = <24>; + upper_margin = <32>; + lower_margin = <11>; + hsync_len = <96>; + vsync_len = <2>; + sync = <0>; + vmode = "FB_VMODE_NONINTERLACED"; + + tim2 = "TIM2_BCD", "TIM2_IPC"; + cntl = "CNTL_LCDTFT", "CNTL_BGR", "CNTL_LCDVCOMP(1)"; + caps = "CLCD_CAP_5551", "CLCD_CAP_565", "CLCD_CAP_888"; + bpp = <16>; + }; + + panel@1 { + compatible = "panel"; + mode = "XVGA"; + refresh = <60>; + xres = <1024>; + yres = <768>; + pixclock = <15748>; + left_margin = <152>; + right_margin = <48>; + upper_margin = <23>; + lower_margin = <3>; + hsync_len = <104>; + vsync_len = <4>; + sync = <0>; + vmode = "FB_VMODE_NONINTERLACED"; + + tim2 = "TIM2_BCD", "TIM2_IPC"; + cntl = "CNTL_LCDTFT", "CNTL_BGR", "CNTL_LCDVCOMP(1)"; + caps = "CLCD_CAP_5551", "CLCD_CAP_565", "CLCD_CAP_888"; + bpp = <16>; + }; + }; +}; diff --git a/arch/arm/boot/dts/vexpress-v2m-rs1.dtsi b/arch/arm/boot/dts/vexpress-v2m-rs1.dtsi index ac870fb3fa0..9584232ee6b 100644 --- a/arch/arm/boot/dts/vexpress-v2m-rs1.dtsi +++ b/arch/arm/boot/dts/vexpress-v2m-rs1.dtsi @@ -228,6 +228,7 @@ }; clcd@1f0000 { + status = "disabled"; compatible = "arm,pl111", "arm,primecell"; reg = <0x1f0000 0x1000>; interrupts = <14>; diff --git a/arch/arm/boot/dts/vexpress-v2m.dtsi b/arch/arm/boot/dts/vexpress-v2m.dtsi index f1420368355..6593398c11a 100644 --- a/arch/arm/boot/dts/vexpress-v2m.dtsi +++ b/arch/arm/boot/dts/vexpress-v2m.dtsi @@ -227,6 +227,7 @@ }; clcd@1f000 { + status = "disabled"; compatible = "arm,pl111", "arm,primecell"; reg = <0x1f000 0x1000>; interrupts = <14>; diff --git a/arch/arm/boot/dts/vexpress-v2p-ca9.dts b/arch/arm/boot/dts/vexpress-v2p-ca9.dts index 62d9b225dcc..f83706bd3f9 100644 --- a/arch/arm/boot/dts/vexpress-v2p-ca9.dts +++ b/arch/arm/boot/dts/vexpress-v2p-ca9.dts @@ -9,6 +9,8 @@ /dts-v1/; +/include/ "clcd-panels.dtsi" + / { model = "V2P-CA9"; arm,hbi = <0x191>; @@ -73,6 +75,8 @@ interrupts = <0 44 4>; clocks = <&oscclk1>, <&oscclk2>; clock-names = "clcdclk", "apb_pclk"; + mode = "XVGA"; + use_dma = <1>; }; memory-controller@100e0000 { -- cgit v1.2.3 From 85705038621c022cffc4e36b3b60720d5dd421ec Mon Sep 17 00:00:00 2001 From: Catalin Marinas Date: Thu, 23 Feb 2012 17:16:41 +0000 Subject: ARM: LPAE: Invalidate the TLB for module addresses during translation fault During the free_pgtables() call all user and modules/pkmap entries are removed. If a translation fault for the modules/pkmap area occurs before we switched away from the current pgd, do_translation_fault() would copy the init_mm pud into the user pud. There is a small window between pud_clear() and pmd_free_tlb() in free_pmd_range() where the pud entry was cleared but the TLB has not been invalidated yet and the CPU may have cached the original (valid) pud entry in the TLB. A scenario like below would get stuck in continuous prefetch abort: 1. Current process exiting. The modules pmd entries not populated 2. exit_mmap() -> ... -> pmd_free_tlb() 3. pud_clear() for the 1GB pud containing user stack and modules (no TLB invalidation yet) 4. Interrupt -> module interrupt routine 5. Level 2 (pmd) translation fault occurs when executing the module interrupt routine. The CPU previously cached (TLB) the old valid pud value for the modules area, so we don't get a level 1 translation fault 6. do_translation fault() copies the pud_k into the pud 7. Linux returns to the faulty instruction. Goes back to 5 At point 7, since the CPU still has the old pud value, it goes back to point 5 and never gets out of this loop. With this patch, the stale pud TLB entry is invalidated after point 6 and the subsequent prefetch abort doesn't occur. Reported-by: Tony Thompson Signed-off-by: Catalin Marinas --- arch/arm/mm/fault.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/arch/arm/mm/fault.c b/arch/arm/mm/fault.c index 5dbf13f954f..e207aa5f846 100644 --- a/arch/arm/mm/fault.c +++ b/arch/arm/mm/fault.c @@ -446,8 +446,16 @@ do_translation_fault(unsigned long addr, unsigned int fsr, if (pud_none(*pud_k)) goto bad_area; - if (!pud_present(*pud)) + if (!pud_present(*pud)) { set_pud(pud, *pud_k); + /* + * There is a small window during free_pgtables() where the + * user *pud entry is 0 but the TLB has not been invalidated + * and we get a level 2 (pmd) translation fault caused by the + * intermediate TLB caching of the old level 1 (pud) entry. + */ + flush_tlb_kernel_page(addr); + } pmd = pmd_offset(pud, addr); pmd_k = pmd_offset(pud_k, addr); @@ -470,8 +478,9 @@ do_translation_fault(unsigned long addr, unsigned int fsr, #endif if (pmd_none(pmd_k[index])) goto bad_area; + if (!pmd_present(pmd[index])) + copy_pmd(pmd, pmd_k); - copy_pmd(pmd, pmd_k); return 0; bad_area: -- cgit v1.2.3 From f8b1ed7161bfa027a96775129e9dc34f7853d480 Mon Sep 17 00:00:00 2001 From: Catalin Marinas Date: Thu, 9 May 2013 08:48:54 +0000 Subject: net: smc91x: Do not cast pointer to int The patch removes a compiler warning when casting the ioaddr pointer to (unsigned int) in the smc_probe() function of the smc91x.c driver. The casting is now done to (unsigned long). Signed-off-by: Catalin Marinas --- drivers/net/ethernet/smsc/smc91x.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/smsc/smc91x.c b/drivers/net/ethernet/smsc/smc91x.c index dfbf978315d..bdd703c6bf1 100644 --- a/drivers/net/ethernet/smsc/smc91x.c +++ b/drivers/net/ethernet/smsc/smc91x.c @@ -1896,7 +1896,7 @@ static int smc_probe(struct net_device *dev, void __iomem *ioaddr, SMC_SELECT_BANK(lp, 1); val = SMC_GET_BASE(lp); val = ((val & 0x1F00) >> 3) << SMC_IO_SHIFT; - if (((unsigned int)ioaddr & (0x3e0 << SMC_IO_SHIFT)) != val) { + if (((unsigned long)ioaddr & (0x3e0 << SMC_IO_SHIFT)) != val) { printk("%s: IOADDR %p doesn't match configuration (%x).\n", CARDNAME, ioaddr, val); } -- cgit v1.2.3 From f0e32abb746fd4926fe0c3dab0e70f3d1505a588 Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Thu, 6 Jun 2013 14:36:40 +0100 Subject: ARM: vexpress: Select CONFIG_ARM_ERRATA_643719 The CA9X4 CoreTile suffers from this errata and as a consequence has reboot and shutdown crashes since the cpu hotplug changes introduced in Linux 3.10. (Commit bca7a5a04933 "ARM: cpu hotplug: remove majority of cache flushing from platforms") Signed-off-by: Jon Medhurst --- arch/arm/mach-vexpress/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm/mach-vexpress/Kconfig b/arch/arm/mach-vexpress/Kconfig index 5907e10c37f..fe637e90cdb 100644 --- a/arch/arm/mach-vexpress/Kconfig +++ b/arch/arm/mach-vexpress/Kconfig @@ -56,5 +56,6 @@ config ARCH_VEXPRESS_CORTEX_A5_A9_ERRATA config ARCH_VEXPRESS_CA9X4 bool "Versatile Express Cortex-A9x4 tile" + select ARM_ERRATA_643719 endmenu -- cgit v1.2.3 From 49428cc5c3413d8a955923e55c36515e02426d8a Mon Sep 17 00:00:00 2001 From: "Mathieu J. Poirier" Date: Tue, 11 Jun 2013 17:07:50 -0600 Subject: timer: Avoid masking EVENTEN bit in CNTKCTL register Will Deacon has a better solution and his patch should be coming in soon. In the mean time please consider for inclusion. This is an in-between solution that prevents the EVENTEN bit in the CNTKCTL register from being mask, resulting in events between clusters being lost. Bug: LP1188778 Signed-off-by: Mathieu Poirier Signed-off-by: Sudeep KarkadaNagesha --- arch/arm/include/asm/arch_timer.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm/include/asm/arch_timer.h b/arch/arm/include/asm/arch_timer.h index 7c1bfc0aea0..4928cdacedf 100644 --- a/arch/arm/include/asm/arch_timer.h +++ b/arch/arm/include/asm/arch_timer.h @@ -105,7 +105,7 @@ static inline void __cpuinit arch_counter_set_user_access(void) asm volatile("mrc p15, 0, %0, c14, c1, 0" : "=r" (cntkctl)); /* disable user access to everything */ - cntkctl &= ~((3 << 8) | (7 << 0)); + cntkctl &= ~((3 << 8) | (3 << 0)); asm volatile("mcr p15, 0, %0, c14, c1, 0" : : "r" (cntkctl)); } -- cgit v1.2.3 From bba0859a996589e9673717864d104a7603687941 Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Mon, 10 Jun 2013 16:07:24 +0100 Subject: arm: versatile: don't mark pen as __INIT When booting fewer cores than are physically present on a versatile platform (e.g. when passing maxcpus=N on the command line), some secondary cores may remain in the holding pen, which is marked __INIT. Late in the boot process, the memory comprising the holding pen will be released to the kernel for more general use, and may be overwritten with arbitrary data, which can cause the held secondaries to start behaving unpredictably. This can lead to all manner of odd behaviour from the kernel. Instead don't mark the section as __INIT. This means we can't reuse the pen memory, but we won't get secondaries corrupting the rest of the kernel. Signed-off-by: Mark Rutland Acked-by: Pawel Moll Cc: Lorenzo Pieralisi --- arch/arm/plat-versatile/headsmp.S | 2 -- 1 file changed, 2 deletions(-) diff --git a/arch/arm/plat-versatile/headsmp.S b/arch/arm/plat-versatile/headsmp.S index b178d44e9ea..2677bc3762d 100644 --- a/arch/arm/plat-versatile/headsmp.S +++ b/arch/arm/plat-versatile/headsmp.S @@ -11,8 +11,6 @@ #include #include - __INIT - /* * Realview/Versatile Express specific entry point for secondary CPUs. * This provides a "holding pen" into which all secondary cores are held -- cgit v1.2.3 From 25a62f4a64e2be4d57ba465b82cbb87b00c09bd0 Mon Sep 17 00:00:00 2001 From: Liviu Dudau Date: Fri, 29 Jun 2012 17:50:14 +0100 Subject: ARM: vexpress: Add proper DT support for the dual cluster V2P-CA15_CA7 CoreTile Signed-off-by: Liviu Dudau Signed-off-by: Jon Medhurst --- arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts | 80 ++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts b/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts index d2803be4e1a..af457d15255 100644 --- a/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts +++ b/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts @@ -29,6 +29,48 @@ i2c1 = &v2m_i2c_pcie; }; + clusters { + #address-cells = <1>; + #size-cells = <0>; + + cluster0: cluster@0 { + reg = <0>; + cores { + #address-cells = <1>; + #size-cells = <0>; + + core0: core@0 { + reg = <0>; + }; + + core1: core@1 { + reg = <1>; + }; + + }; + }; + + cluster1: cluster@1 { + reg = <1>; + cores { + #address-cells = <1>; + #size-cells = <0>; + + core2: core@0 { + reg = <0>; + }; + + core3: core@1 { + reg = <1>; + }; + + core4: core@2 { + reg = <2>; + }; + }; + }; + }; + cpus { #address-cells = <1>; #size-cells = <0>; @@ -37,30 +79,40 @@ device_type = "cpu"; compatible = "arm,cortex-a15"; reg = <0>; + cluster = <&cluster0>; + core = <&core0>; }; cpu1: cpu@1 { device_type = "cpu"; compatible = "arm,cortex-a15"; reg = <1>; + cluster = <&cluster0>; + core = <&core1>; }; cpu2: cpu@2 { device_type = "cpu"; compatible = "arm,cortex-a7"; reg = <0x100>; + cluster = <&cluster1>; + core = <&core2>; }; cpu3: cpu@3 { device_type = "cpu"; compatible = "arm,cortex-a7"; reg = <0x101>; + cluster = <&cluster1>; + core = <&core3>; }; cpu4: cpu@4 { device_type = "cpu"; compatible = "arm,cortex-a7"; reg = <0x102>; + cluster = <&cluster1>; + core = <&core4>; }; }; @@ -102,6 +154,34 @@ <0 0x2c004000 0 0x2000>, <0 0x2c006000 0 0x2000>; interrupts = <1 9 0xf04>; + + gic-cpuif@0 { + compatible = "arm,gic-cpuif"; + cpuif-id = <0>; + cpu = <&cpu0>; + }; + gic-cpuif@1 { + compatible = "arm,gic-cpuif"; + cpuif-id = <1>; + cpu = <&cpu1>; + }; + gic-cpuif@2 { + compatible = "arm,gic-cpuif"; + cpuif-id = <2>; + cpu = <&cpu2>; + }; + + gic-cpuif@3 { + compatible = "arm,gic-cpuif"; + cpuif-id = <3>; + cpu = <&cpu3>; + }; + + gic-cpuif@4 { + compatible = "arm,gic-cpuif"; + cpuif-id = <4>; + cpu = <&cpu4>; + }; }; memory-controller@7ffd0000 { -- cgit v1.2.3 From fa945c17bfc3068c0ca2e66c9dc47497a357d327 Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Tue, 26 Mar 2013 13:04:51 +0000 Subject: ARM: vexpress: Update TC2 memory to 2GB All TC2 boards 'in the wild' will have 2GB of memory, so lets make it all available. Signed-off-by: Jon Medhurst --- arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts b/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts index af457d15255..2e720e97659 100644 --- a/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts +++ b/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts @@ -118,7 +118,7 @@ memory@80000000 { device_type = "memory"; - reg = <0 0x80000000 0 0x40000000>; + reg = <0 0x80000000 0 0x80000000>; }; wdt@2a490000 { -- cgit v1.2.3 From 8f15973ef8c3c3c71bfb0f69670827e0d1882042 Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Tue, 26 Mar 2013 13:06:53 +0000 Subject: ARM: vexpress: Add CPU clock-frequencies to TC2 device-tree Signed-off-by: Jon Medhurst --- arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts b/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts index 2e720e97659..9e93597dd53 100644 --- a/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts +++ b/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts @@ -81,6 +81,7 @@ reg = <0>; cluster = <&cluster0>; core = <&core0>; + clock-frequency = <1000000000>; }; cpu1: cpu@1 { @@ -89,6 +90,7 @@ reg = <1>; cluster = <&cluster0>; core = <&core1>; + clock-frequency = <1000000000>; }; cpu2: cpu@2 { @@ -97,6 +99,7 @@ reg = <0x100>; cluster = <&cluster1>; core = <&core2>; + clock-frequency = <800000000>; }; cpu3: cpu@3 { @@ -105,6 +108,7 @@ reg = <0x101>; cluster = <&cluster1>; core = <&core3>; + clock-frequency = <800000000>; }; cpu4: cpu@4 { @@ -113,6 +117,7 @@ reg = <0x102>; cluster = <&cluster1>; core = <&core4>; + clock-frequency = <800000000>; }; }; -- cgit v1.2.3 From 40bb83f11c5881d8455866458522816397dd5836 Mon Sep 17 00:00:00 2001 From: Sudeep KarkadaNagesha Date: Mon, 16 Jul 2012 12:29:43 +0000 Subject: ARM: vexpress: update TC2 dts to support multiple PMUs This patch adds support for both A15 and A7 PMUs on vexpress TC2 Signed-off-by: Sudeep KarkadaNagesha --- arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts b/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts index 9e93597dd53..446708869c7 100644 --- a/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts +++ b/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts @@ -218,12 +218,21 @@ <1 10 0xf08>; }; - pmu { + pmu_a15 { compatible = "arm,cortex-a15-pmu"; + cluster = <&cluster0>; interrupts = <0 68 4>, <0 69 4>; }; + pmu_a7 { + compatible = "arm,cortex-a7-pmu"; + cluster = <&cluster1>; + interrupts = <0 128 4>, + <0 129 4>, + <0 130 4>; + }; + oscclk6a: oscclk6a { /* Reference 24MHz clock */ compatible = "fixed-clock"; -- cgit v1.2.3 From 17b821ed0e31bfb55037b124f5c89aad1521ca1d Mon Sep 17 00:00:00 2001 From: Vincent Guittot Date: Fri, 17 May 2013 11:20:19 +0200 Subject: ARM: bL boot on A7 cluster Ensure that A7 cluster will be mapped on CPU0-2 Suggested-by: Chris Redpath Signed-off-by: Vincent Guittot --- arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts | 37 +++++++++++++++--------------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts b/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts index 446708869c7..f6b4e8471cb 100644 --- a/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts +++ b/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts @@ -75,24 +75,6 @@ #address-cells = <1>; #size-cells = <0>; - cpu0: cpu@0 { - device_type = "cpu"; - compatible = "arm,cortex-a15"; - reg = <0>; - cluster = <&cluster0>; - core = <&core0>; - clock-frequency = <1000000000>; - }; - - cpu1: cpu@1 { - device_type = "cpu"; - compatible = "arm,cortex-a15"; - reg = <1>; - cluster = <&cluster0>; - core = <&core1>; - clock-frequency = <1000000000>; - }; - cpu2: cpu@2 { device_type = "cpu"; compatible = "arm,cortex-a7"; @@ -119,6 +101,25 @@ core = <&core4>; clock-frequency = <800000000>; }; + + cpu0: cpu@0 { + device_type = "cpu"; + compatible = "arm,cortex-a15"; + reg = <0>; + cluster = <&cluster0>; + core = <&core0>; + clock-frequency = <1000000000>; + }; + + cpu1: cpu@1 { + device_type = "cpu"; + compatible = "arm,cortex-a15"; + reg = <1>; + cluster = <&cluster0>; + core = <&core1>; + clock-frequency = <1000000000>; + }; + }; memory@80000000 { -- cgit v1.2.3 From 23f83a32303d54cd6c6e45930d1a2ae98ddb9df0 Mon Sep 17 00:00:00 2001 From: Stefano Stabellini Date: Tue, 21 May 2013 14:24:11 +0000 Subject: arm: introduce psci_smp_ops Rename virt_smp_ops to psci_smp_ops and move them to arch/arm/kernel/psci_smp.c. Remove mach-virt/platsmp.c, now unused. Compile psci_smp if CONFIG_ARM_PSCI and CONFIG_SMP. Add a cpu_die smp_op based on psci_ops.cpu_off. Initialize PSCI before setting smp_ops in setup_arch. If PSCI is available on the platform, prefer psci_smp_ops over the platform smp_ops. Signed-off-by: Stefano Stabellini Acked-by: Will Deacon CC: arnd@arndb.de CC: marc.zyngier@arm.com CC: linux@arm.linux.org.uk CC: nico@linaro.org CC: rob.herring@calxeda.com --- arch/arm/include/asm/psci.h | 9 +++++ arch/arm/kernel/Makefile | 5 ++- arch/arm/kernel/psci.c | 7 ++-- arch/arm/kernel/psci_smp.c | 84 ++++++++++++++++++++++++++++++++++++++++++++ arch/arm/kernel/setup.c | 7 +++- arch/arm/mach-virt/Makefile | 1 - arch/arm/mach-virt/platsmp.c | 50 -------------------------- arch/arm/mach-virt/virt.c | 3 -- 8 files changed, 106 insertions(+), 60 deletions(-) create mode 100644 arch/arm/kernel/psci_smp.c delete mode 100644 arch/arm/mach-virt/platsmp.c diff --git a/arch/arm/include/asm/psci.h b/arch/arm/include/asm/psci.h index ce0dbe7c162..c4ae171850f 100644 --- a/arch/arm/include/asm/psci.h +++ b/arch/arm/include/asm/psci.h @@ -32,5 +32,14 @@ struct psci_operations { }; extern struct psci_operations psci_ops; +extern struct smp_operations psci_smp_ops; + +#ifdef CONFIG_ARM_PSCI +void psci_init(void); +bool psci_smp_available(void); +#else +static inline void psci_init(void) { } +static inline bool psci_smp_available(void) { return false; } +#endif #endif /* __ASM_ARM_PSCI_H */ diff --git a/arch/arm/kernel/Makefile b/arch/arm/kernel/Makefile index 5f3338eacad..dd9d90ab65d 100644 --- a/arch/arm/kernel/Makefile +++ b/arch/arm/kernel/Makefile @@ -82,6 +82,9 @@ obj-$(CONFIG_DEBUG_LL) += debug.o obj-$(CONFIG_EARLY_PRINTK) += early_printk.o obj-$(CONFIG_ARM_VIRT_EXT) += hyp-stub.o -obj-$(CONFIG_ARM_PSCI) += psci.o +ifeq ($(CONFIG_ARM_PSCI),y) +obj-y += psci.o +obj-$(CONFIG_SMP) += psci_smp.o +endif extra-y := $(head-y) vmlinux.lds diff --git a/arch/arm/kernel/psci.c b/arch/arm/kernel/psci.c index 36531643cc2..46931880093 100644 --- a/arch/arm/kernel/psci.c +++ b/arch/arm/kernel/psci.c @@ -158,7 +158,7 @@ static const struct of_device_id psci_of_match[] __initconst = { {}, }; -static int __init psci_init(void) +void __init psci_init(void) { struct device_node *np; const char *method; @@ -166,7 +166,7 @@ static int __init psci_init(void) np = of_find_matching_node(NULL, psci_of_match); if (!np) - return 0; + return; pr_info("probing function IDs from device-tree\n"); @@ -206,6 +206,5 @@ static int __init psci_init(void) out_put_node: of_node_put(np); - return 0; + return; } -early_initcall(psci_init); diff --git a/arch/arm/kernel/psci_smp.c b/arch/arm/kernel/psci_smp.c new file mode 100644 index 00000000000..23a11424c56 --- /dev/null +++ b/arch/arm/kernel/psci_smp.c @@ -0,0 +1,84 @@ +/* + * 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 in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Copyright (C) 2012 ARM Limited + * + * Author: Will Deacon + */ + +#include +#include +#include +#include + +#include +#include + +/* + * psci_smp assumes that the following is true about PSCI: + * + * cpu_suspend Suspend the execution on a CPU + * @state we don't currently describe affinity levels, so just pass 0. + * @entry_point the first instruction to be executed on return + * returns 0 success, < 0 on failure + * + * cpu_off Power down a CPU + * @state we don't currently describe affinity levels, so just pass 0. + * no return on successful call + * + * cpu_on Power up a CPU + * @cpuid cpuid of target CPU, as from MPIDR + * @entry_point the first instruction to be executed on return + * returns 0 success, < 0 on failure + * + * migrate Migrate the context to a different CPU + * @cpuid cpuid of target CPU, as from MPIDR + * returns 0 success, < 0 on failure + * + */ + +extern void secondary_startup(void); + +static int __cpuinit psci_boot_secondary(unsigned int cpu, + struct task_struct *idle) +{ + if (psci_ops.cpu_on) + return psci_ops.cpu_on(cpu_logical_map(cpu), + __pa(secondary_startup)); + return -ENODEV; +} + +#ifdef CONFIG_HOTPLUG_CPU +void __ref psci_cpu_die(unsigned int cpu) +{ + const struct psci_power_state ps = { + .type = PSCI_POWER_STATE_TYPE_POWER_DOWN, + }; + + if (psci_ops.cpu_off) + psci_ops.cpu_off(ps); + + /* We should never return */ + panic("psci: cpu %d failed to shutdown\n", cpu); +} +#else +#define psci_cpu_die NULL +#endif + +bool __init psci_smp_available(void) +{ + /* is cpu_on available at least? */ + return (psci_ops.cpu_on != NULL); +} + +struct smp_operations __initdata psci_smp_ops = { + .smp_boot_secondary = psci_boot_secondary, + .cpu_die = psci_cpu_die, +}; diff --git a/arch/arm/kernel/setup.c b/arch/arm/kernel/setup.c index b4b1d397592..860952438bd 100644 --- a/arch/arm/kernel/setup.c +++ b/arch/arm/kernel/setup.c @@ -37,6 +37,7 @@ #include #include #include +#include #include #include #include @@ -796,9 +797,13 @@ void __init setup_arch(char **cmdline_p) unflatten_device_tree(); arm_dt_init_cpu_maps(); + psci_init(); #ifdef CONFIG_SMP if (is_smp()) { - smp_set_ops(mdesc->smp); + if (psci_smp_available()) + smp_set_ops(&psci_smp_ops); + else if (mdesc->smp) + smp_set_ops(mdesc->smp); smp_init_cpus(); } #endif diff --git a/arch/arm/mach-virt/Makefile b/arch/arm/mach-virt/Makefile index 042afc1f8c4..7ddbfa60227 100644 --- a/arch/arm/mach-virt/Makefile +++ b/arch/arm/mach-virt/Makefile @@ -3,4 +3,3 @@ # obj-y := virt.o -obj-$(CONFIG_SMP) += platsmp.o diff --git a/arch/arm/mach-virt/platsmp.c b/arch/arm/mach-virt/platsmp.c deleted file mode 100644 index f4143f5bfa5..00000000000 --- a/arch/arm/mach-virt/platsmp.c +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Dummy Virtual Machine - does what it says on the tin. - * - * Copyright (C) 2012 ARM Ltd - * Author: Will Deacon - * - * 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 in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include -#include -#include - -#include -#include - -extern void secondary_startup(void); - -static void __init virt_smp_init_cpus(void) -{ -} - -static void __init virt_smp_prepare_cpus(unsigned int max_cpus) -{ -} - -static int __cpuinit virt_boot_secondary(unsigned int cpu, - struct task_struct *idle) -{ - if (psci_ops.cpu_on) - return psci_ops.cpu_on(cpu_logical_map(cpu), - __pa(secondary_startup)); - return -ENODEV; -} - -struct smp_operations __initdata virt_smp_ops = { - .smp_init_cpus = virt_smp_init_cpus, - .smp_prepare_cpus = virt_smp_prepare_cpus, - .smp_boot_secondary = virt_boot_secondary, -}; diff --git a/arch/arm/mach-virt/virt.c b/arch/arm/mach-virt/virt.c index 061f283f579..a67d2dd5bb6 100644 --- a/arch/arm/mach-virt/virt.c +++ b/arch/arm/mach-virt/virt.c @@ -36,11 +36,8 @@ static const char *virt_dt_match[] = { NULL }; -extern struct smp_operations virt_smp_ops; - DT_MACHINE_START(VIRT, "Dummy Virtual Machine") .init_irq = irqchip_init, .init_machine = virt_init, - .smp = smp_ops(virt_smp_ops), .dt_compat = virt_dt_match, MACHINE_END -- cgit v1.2.3 From ae42c72e2110f3cb3018562eb3980f13bcf819c4 Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Tue, 21 May 2013 13:40:51 +0000 Subject: ARM: Enable selection of SMP operations at boot time Add a new 'smp_init' hook to machine_desc so platforms can specify a function to be used to setup smp ops instead of having a statically defined value. The hook must return true when smp_ops are initialized. If false the static mdesc->smp_ops will be used by default. Add the definition of "bool" by including the linux/types.h file to asm/mach/arch.h and make it self-contained. Signed-off-by: Jon Medhurst Signed-off-by: Nicolas Pitre Signed-off-by: Stefano Stabellini Signed-off-by: Nicolas Ferre Reviewed-by: Santosh Shilimkar --- arch/arm/include/asm/mach/arch.h | 5 +++++ arch/arm/kernel/setup.c | 10 ++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/arch/arm/include/asm/mach/arch.h b/arch/arm/include/asm/mach/arch.h index 308ad7d6f98..75bf07910b8 100644 --- a/arch/arm/include/asm/mach/arch.h +++ b/arch/arm/include/asm/mach/arch.h @@ -8,6 +8,8 @@ * published by the Free Software Foundation. */ +#include + #ifndef __ASSEMBLY__ struct tag; @@ -16,8 +18,10 @@ struct pt_regs; struct smp_operations; #ifdef CONFIG_SMP #define smp_ops(ops) (&(ops)) +#define smp_init_ops(ops) (&(ops)) #else #define smp_ops(ops) (struct smp_operations *)NULL +#define smp_init_ops(ops) (bool (*)(void))NULL #endif struct machine_desc { @@ -41,6 +45,7 @@ struct machine_desc { unsigned char reserve_lp2 :1; /* never has lp2 */ char restart_mode; /* default restart mode */ struct smp_operations *smp; /* SMP operations */ + bool (*smp_init)(void); void (*fixup)(struct tag *, char **, struct meminfo *); void (*reserve)(void);/* reserve mem blocks */ diff --git a/arch/arm/kernel/setup.c b/arch/arm/kernel/setup.c index 860952438bd..ad84971b807 100644 --- a/arch/arm/kernel/setup.c +++ b/arch/arm/kernel/setup.c @@ -800,10 +800,12 @@ void __init setup_arch(char **cmdline_p) psci_init(); #ifdef CONFIG_SMP if (is_smp()) { - if (psci_smp_available()) - smp_set_ops(&psci_smp_ops); - else if (mdesc->smp) - smp_set_ops(mdesc->smp); + if (!mdesc->smp_init || !mdesc->smp_init()) { + if (psci_smp_available()) + smp_set_ops(&psci_smp_ops); + else if (mdesc->smp) + smp_set_ops(mdesc->smp); + } smp_init_cpus(); } #endif -- cgit v1.2.3 From 33aa5d68875775d02f835d98850c92b6a070bdf9 Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Wed, 30 Jan 2013 09:12:55 +0000 Subject: ARM: vexpress: Select multi-cluster SMP operation if required Signed-off-by: Jon Medhurst Signed-off-by: Nicolas Pitre Reviewed-by: Santosh Shilimkar Acked-by: Pawel Moll --- arch/arm/mach-vexpress/core.h | 2 ++ arch/arm/mach-vexpress/platsmp.c | 20 ++++++++++++++++++++ arch/arm/mach-vexpress/v2m.c | 1 + 3 files changed, 23 insertions(+) diff --git a/arch/arm/mach-vexpress/core.h b/arch/arm/mach-vexpress/core.h index f134cd4a85f..bde4374ab6d 100644 --- a/arch/arm/mach-vexpress/core.h +++ b/arch/arm/mach-vexpress/core.h @@ -6,6 +6,8 @@ void vexpress_dt_smp_map_io(void); +bool vexpress_smp_init_ops(void); + extern struct smp_operations vexpress_smp_ops; extern void vexpress_cpu_die(unsigned int cpu); diff --git a/arch/arm/mach-vexpress/platsmp.c b/arch/arm/mach-vexpress/platsmp.c index dc1ace55d55..993c9ae5dc5 100644 --- a/arch/arm/mach-vexpress/platsmp.c +++ b/arch/arm/mach-vexpress/platsmp.c @@ -12,9 +12,11 @@ #include #include #include +#include #include #include +#include #include #include @@ -203,3 +205,21 @@ struct smp_operations __initdata vexpress_smp_ops = { .cpu_die = vexpress_cpu_die, #endif }; + +bool __init vexpress_smp_init_ops(void) +{ +#ifdef CONFIG_MCPM + /* + * The best way to detect a multi-cluster configuration at the moment + * is to look for the presence of a CCI in the system. + * Override the default vexpress_smp_ops if so. + */ + struct device_node *node; + node = of_find_compatible_node(NULL, NULL, "arm,cci-400"); + if (node && of_device_is_available(node)) { + mcpm_smp_set_ops(); + return true; + } +#endif + return false; +} diff --git a/arch/arm/mach-vexpress/v2m.c b/arch/arm/mach-vexpress/v2m.c index 8802030df98..b0eccf7e06e 100644 --- a/arch/arm/mach-vexpress/v2m.c +++ b/arch/arm/mach-vexpress/v2m.c @@ -456,6 +456,7 @@ static const char * const v2m_dt_match[] __initconst = { DT_MACHINE_START(VEXPRESS_DT, "ARM-Versatile Express") .dt_compat = v2m_dt_match, .smp = smp_ops(vexpress_smp_ops), + .smp_init = smp_init_ops(vexpress_smp_init_ops), .map_io = v2m_dt_map_io, .init_early = v2m_dt_init_early, .init_irq = irqchip_init, -- cgit v1.2.3 From 524f7be297e743ad275c088e14ea0d582c7b0115 Mon Sep 17 00:00:00 2001 From: Dave Martin Date: Wed, 20 Feb 2013 17:34:20 +0000 Subject: ARM: mcpm: Make all mcpm functions notrace The functions in mcpm_entry.c are mostly intended for use during scary cache and coherency disabling sequences, or do other things which confuse trace ... like powering a CPU down and not returning. Similarly for the backend code. For simplicity, this patch just makes whole files notrace. There should be more than enough traceable points on the paths to these functions, but we can be more fine-grained later if there is a need for it. Signed-off-by: Dave Martin --- arch/arm/common/Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm/common/Makefile b/arch/arm/common/Makefile index 48434cbe3e8..f27d6a7af57 100644 --- a/arch/arm/common/Makefile +++ b/arch/arm/common/Makefile @@ -16,3 +16,4 @@ obj-$(CONFIG_ARM_TIMER_SP804) += timer-sp.o obj-$(CONFIG_MCPM) += mcpm_head.o mcpm_entry.o mcpm_platsmp.o vlock.o AFLAGS_mcpm_head.o := -march=armv7-a AFLAGS_vlock.o := -march=armv7-a +CFLAGS_REMOVE_mcpm_entry.o = -pg -- cgit v1.2.3 From e472e4373b92dbfa98fc5f9ec81cd32c924a8346 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Tue, 19 Mar 2013 16:07:17 -0400 Subject: ARM: introduce a standalone CONFIG_BIG_LITTLE option The BIG_LITTLE config option is independent from the MCPM one. Semantically, they cover different things. MCPM can be used on non b.L systems for example. Signed-of-by: Nicolas Pitre Signed-off-by: Jon Medhurst --- arch/arm/Kconfig | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 136f263ed47..c24b364bb8c 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -1521,6 +1521,13 @@ config MCPM for (multi-)cluster based systems, such as big.LITTLE based systems. +config BIG_LITTLE + bool "big.LITTLE support (Experimental)" + depends on CPU_V7 && SMP + select MCPM + help + This option enables support for the big.LITTLE architecture. + choice prompt "Memory split" default VMSPLIT_3G -- cgit v1.2.3 From e53b923187b1b07a709ca6e8d76a213b2d91f6e8 Mon Sep 17 00:00:00 2001 From: Lorenzo Pieralisi Date: Fri, 13 Jul 2012 15:55:52 +0100 Subject: drivers: bus: add ARM CCI support On ARM multi-cluster systems coherency between cores running on different clusters is managed by the cache-coherent interconnect (CCI). It allows broadcasting of TLB invalidates and memory barriers and it guarantees cache coherency at system level through snooping of slave interfaces connected to it. This patch enables the basic infrastructure required in Linux to handle and programme the CCI component. Non-local variables used by the CCI management functions called by power down function calls 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 power down functions execute. Driver code ensures that relevant data structures are flushed from inner and outer caches after the driver probe is completed. CCI slave port resources are linked to set of CPUs through bus masters phandle properties that link the interface resources to masters node in the device tree. Documentation describing the CCI DT bindings is provided with the patch. Signed-off-by: Lorenzo Pieralisi Signed-off-by: Nicolas Pitre --- Documentation/devicetree/bindings/arm/cci.txt | 172 +++++++++++ drivers/bus/Kconfig | 7 + drivers/bus/Makefile | 2 + drivers/bus/arm-cci.c | 426 ++++++++++++++++++++++++++ include/linux/arm-cci.h | 61 ++++ 5 files changed, 668 insertions(+) create mode 100644 Documentation/devicetree/bindings/arm/cci.txt create mode 100644 drivers/bus/arm-cci.c create mode 100644 include/linux/arm-cci.h diff --git a/Documentation/devicetree/bindings/arm/cci.txt b/Documentation/devicetree/bindings/arm/cci.txt new file mode 100644 index 00000000000..92d36e2aa87 --- /dev/null +++ b/Documentation/devicetree/bindings/arm/cci.txt @@ -0,0 +1,172 @@ +======================================================= +ARM CCI cache coherent interconnect binding description +======================================================= + +ARM multi-cluster systems maintain intra-cluster coherency through a +cache coherent interconnect (CCI) that is capable of monitoring bus +transactions and manage coherency, TLB invalidations and memory barriers. + +It allows snooping and distributed virtual memory message broadcast across +clusters, through memory mapped interface, with a global control register +space and multiple sets of interface control registers, one per slave +interface. + +Bindings for the CCI node follow the ePAPR standard, available from: + +www.power.org/documentation/epapr-version-1-1/ + +with the addition of the bindings described in this document which are +specific to ARM. + +* CCI interconnect node + + Description: Describes a CCI cache coherent Interconnect component + + Node name must be "cci". + Node's parent must be the root node /, and the address space visible + through the CCI interconnect is the same as the one seen from the + root node (ie from CPUs perspective as per DT standard). + Every CCI node has to define the following properties: + + - compatible + Usage: required + Value type: + Definition: must be set to + "arm,cci-400" + + - reg + Usage: required + Value type: + Definition: A standard property. Specifies base physical + address of CCI control registers common to all + interfaces. + + - ranges: + Usage: required + Value type: + Definition: A standard property. Follow rules in the ePAPR for + hierarchical bus addressing. CCI interfaces + addresses refer to the parent node addressing + scheme to declare their register bases. + + CCI interconnect node can define the following child nodes: + + - CCI control interface nodes + + Node name must be "slave-if". + Parent node must be CCI interconnect node. + + A CCI control interface node must contain the following + properties: + + - compatible + Usage: required + Value type: + Definition: must be set to + "arm,cci-400-ctrl-if" + + - interface-type: + Usage: required + Value type: + Definition: must be set to one of {"ace", "ace-lite"} + depending on the interface type the node + represents. + + - reg: + Usage: required + Value type: + Definition: the base address and size of the + corresponding interface programming + registers. + +* CCI interconnect bus masters + + Description: masters in the device tree connected to a CCI port + (inclusive of CPUs and their cpu nodes). + + A CCI interconnect bus master node must contain the following + properties: + + - cci-control-port: + Usage: required + Value type: + Definition: a phandle containing the CCI control interface node + the master is connected to. + +Example: + + cpus { + #size-cells = <0>; + #address-cells = <1>; + + CPU0: cpu@0 { + device_type = "cpu"; + compatible = "arm,cortex-a15"; + cci-control-port = <&cci_control1>; + reg = <0x0>; + }; + + CPU1: cpu@1 { + device_type = "cpu"; + compatible = "arm,cortex-a15"; + cci-control-port = <&cci_control1>; + reg = <0x1>; + }; + + CPU2: cpu@100 { + device_type = "cpu"; + compatible = "arm,cortex-a7"; + cci-control-port = <&cci_control2>; + reg = <0x100>; + }; + + CPU3: cpu@101 { + device_type = "cpu"; + compatible = "arm,cortex-a7"; + cci-control-port = <&cci_control2>; + reg = <0x101>; + }; + + }; + + dma0: dma@3000000 { + compatible = "arm,pl330", "arm,primecell"; + cci-control-port = <&cci_control0>; + reg = <0x0 0x3000000 0x0 0x1000>; + interrupts = <10>; + #dma-cells = <1>; + #dma-channels = <8>; + #dma-requests = <32>; + }; + + cci@2c090000 { + compatible = "arm,cci-400"; + #address-cells = <1>; + #size-cells = <1>; + reg = <0x0 0x2c090000 0 0x1000>; + ranges = <0x0 0x0 0x2c090000 0x6000>; + + cci_control0: slave-if@1000 { + compatible = "arm,cci-400-ctrl-if"; + interface-type = "ace-lite"; + reg = <0x1000 0x1000>; + }; + + cci_control1: slave-if@4000 { + compatible = "arm,cci-400-ctrl-if"; + interface-type = "ace"; + reg = <0x4000 0x1000>; + }; + + cci_control2: slave-if@5000 { + compatible = "arm,cci-400-ctrl-if"; + interface-type = "ace"; + reg = <0x5000 0x1000>; + }; + }; + +This CCI node corresponds to a CCI component whose control registers sits +at address 0x000000002c090000. +CCI slave interface @0x000000002c091000 is connected to dma controller dma0. +CCI slave interface @0x000000002c094000 is connected to CPUs {CPU0, CPU1}; +CCI slave interface @0x000000002c095000 is connected to CPUs {CPU2, CPU3}; diff --git a/drivers/bus/Kconfig b/drivers/bus/Kconfig index b05ecab915c..5286e2d333b 100644 --- a/drivers/bus/Kconfig +++ b/drivers/bus/Kconfig @@ -26,4 +26,11 @@ config OMAP_INTERCONNECT help Driver to enable OMAP interconnect error handling driver. + +config ARM_CCI + bool "ARM CCI driver support" + depends on ARM + help + Driver supporting the CCI cache coherent interconnect for ARM + platforms. endmenu diff --git a/drivers/bus/Makefile b/drivers/bus/Makefile index 3c7b53c1209..670cea44380 100644 --- a/drivers/bus/Makefile +++ b/drivers/bus/Makefile @@ -7,3 +7,5 @@ obj-$(CONFIG_OMAP_OCP2SCP) += omap-ocp2scp.o # Interconnect bus driver for OMAP SoCs. obj-$(CONFIG_OMAP_INTERCONNECT) += omap_l3_smx.o omap_l3_noc.o +# CCI cache coherent interconnect for ARM platforms +obj-$(CONFIG_ARM_CCI) += arm-cci.o diff --git a/drivers/bus/arm-cci.c b/drivers/bus/arm-cci.c new file mode 100644 index 00000000000..ea81fa4a28d --- /dev/null +++ b/drivers/bus/arm-cci.c @@ -0,0 +1,426 @@ +/* + * CCI cache coherent interconnect driver + * + * Copyright (C) 2013 ARM Ltd. + * Author: 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 + +#define CCI_PORT_CTRL 0x0 +#define CCI_CTRL_STATUS 0xc + +#define CCI_ENABLE_SNOOP_REQ 0x1 +#define CCI_ENABLE_DVM_REQ 0x2 +#define CCI_ENABLE_REQ (CCI_ENABLE_SNOOP_REQ | CCI_ENABLE_DVM_REQ) + +struct cci_nb_ports { + unsigned int nb_ace; + unsigned int nb_ace_lite; +}; + +enum cci_ace_port_type { + ACE_INVALID_PORT = 0x0, + ACE_PORT, + ACE_LITE_PORT, +}; + +struct cci_ace_port { + void __iomem *base; + enum cci_ace_port_type type; + struct device_node *dn; +}; + +static struct cci_ace_port *ports; +static unsigned int nb_cci_ports; + +static void __iomem *cci_ctrl_base; + +struct cpu_port { + u64 mpidr; + u32 port; +}; +/* + * Use the port MSB as valid flag, shift can be made dynamic + * by computing number of bits required for port indexes. + * Code disabling CCI cpu ports runs with D-cache invalidated + * and SCTLR bit clear so data accesses must be kept to a minimum + * to improve performance; for now shift is left static to + * avoid one more data access while disabling the CCI port. + */ +#define PORT_VALID_SHIFT 31 +#define PORT_VALID (0x1 << PORT_VALID_SHIFT) + +static inline void init_cpu_port(struct cpu_port *port, u32 index, u64 mpidr) +{ + port->port = PORT_VALID | index; + port->mpidr = mpidr; +} + +static inline bool cpu_port_is_valid(struct cpu_port *port) +{ + return !!(port->port & PORT_VALID); +} + +static inline bool cpu_port_match(struct cpu_port *port, u64 mpidr) +{ + return port->mpidr == (mpidr & MPIDR_HWID_BITMASK); +} + +static struct cpu_port cpu_port[NR_CPUS]; + +/** + * __cci_ace_get_port - Function to retrieve the port index connected to + * a cpu or device. + * + * @dn: device node of the device to look-up + * @type: port type + * + * Return value: + * - CCI port index if success + * - -ENODEV if failure + */ +static int __cci_ace_get_port(struct device_node *dn, int type) +{ + int i; + bool ace_match; + struct device_node *cci_portn; + + cci_portn = of_parse_phandle(dn, "cci-control-port", 0); + for (i = 0; i < nb_cci_ports; i++) { + ace_match = ports[i].type == type; + if (ace_match && cci_portn == ports[i].dn) + return i; + } + return -ENODEV; +} + +int cci_ace_get_port(struct device_node *dn) +{ + return __cci_ace_get_port(dn, ACE_LITE_PORT); +} +EXPORT_SYMBOL_GPL(cci_ace_get_port); + +static void __init cci_ace_init_ports(void) +{ + int port, ac, cpu; + u64 hwid; + const u32 *cell; + struct device_node *cpun, *cpus; + + cpus = of_find_node_by_path("/cpus"); + if (WARN(!cpus, "Missing cpus node, bailing out\n")) + return; + + if (WARN_ON(of_property_read_u32(cpus, "#address-cells", &ac))) + ac = of_n_addr_cells(cpus); + + /* + * Port index look-up speeds up the function disabling ports by CPU, + * since the logical to port index mapping is done once and does + * not change after system boot. + * The stashed index array is initialized for all possible CPUs + * at probe time. + */ + for_each_child_of_node(cpus, cpun) { + if (of_node_cmp(cpun->type, "cpu")) + continue; + cell = of_get_property(cpun, "reg", NULL); + if (WARN(!cell, "%s: missing reg property\n", cpun->full_name)) + continue; + + hwid = of_read_number(cell, ac); + cpu = get_logical_index(hwid & MPIDR_HWID_BITMASK); + + if (cpu < 0 || !cpu_possible(cpu)) + continue; + port = __cci_ace_get_port(cpun, ACE_PORT); + if (port < 0) + continue; + + init_cpu_port(&cpu_port[cpu], port, cpu_logical_map(cpu)); + } + + for_each_possible_cpu(cpu) { + WARN(!cpu_port_is_valid(&cpu_port[cpu]), + "CPU %u does not have an associated CCI port\n", + cpu); + } +} +/* + * Functions to enable/disable a CCI interconnect slave port + * + * They are called by low-level power management code to disable slave + * interfaces snoops and DVM broadcast. + * Since they may execute with cache data allocation disabled and + * after the caches have been cleaned and invalidated the functions provide + * no explicit locking since they may run with D-cache disabled, so normal + * cacheable kernel locks based on ldrex/strex may not work. + * Locking has to be provided by BSP implementations to ensure proper + * operations. + */ + +/** + * cci_port_control() - function to control a CCI port + * + * @port: index of the port to setup + * @enable: if true enables the port, if false disables it + */ +static void notrace cci_port_control(unsigned int port, bool enable) +{ + void __iomem *base = ports[port].base; + + writel_relaxed(enable ? CCI_ENABLE_REQ : 0, base + CCI_PORT_CTRL); + /* + * This function is called from power down procedures + * and must not execute any instruction that might + * cause the processor to be put in a quiescent state + * (eg wfi). Hence, cpu_relax() can not be added to this + * read loop to optimize power, since it might hide possibly + * disruptive operations. + */ + while (readl_relaxed(cci_ctrl_base + CCI_CTRL_STATUS) & 0x1) + ; +} + +/** + * cci_disable_port_by_cpu() - function to disable a CCI port by CPU + * reference + * + * @mpidr: mpidr of the CPU whose CCI port should be disabled + * + * Disabling a CCI port for a CPU implies disabling the CCI port + * controlling that CPU cluster. Code disabling CPU CCI ports + * must make sure that the CPU running the code is the last active CPU + * in the cluster ie all other CPUs are quiescent in a low power state. + * + * Return: + * 0 on success + * -ENODEV on port look-up failure + */ +int notrace cci_disable_port_by_cpu(u64 mpidr) +{ + int cpu; + bool is_valid; + for (cpu = 0; cpu < nr_cpu_ids; cpu++) { + is_valid = cpu_port_is_valid(&cpu_port[cpu]); + if (is_valid && cpu_port_match(&cpu_port[cpu], mpidr)) { + cci_port_control(cpu_port[cpu].port, false); + return 0; + } + } + return -ENODEV; +} +EXPORT_SYMBOL_GPL(cci_disable_port_by_cpu); + +/** + * __cci_control_port_by_device() - function to control a CCI port by device + * reference + * + * @dn: device node pointer of the device whose CCI port should be + * controlled + * @enable: if true enables the port, if false disables it + * + * Return: + * 0 on success + * -ENODEV on port look-up failure + */ +int notrace __cci_control_port_by_device(struct device_node *dn, bool enable) +{ + int port; + + if (!dn) + return -ENODEV; + + port = __cci_ace_get_port(dn, ACE_LITE_PORT); + if (WARN_ONCE(port < 0, "node %s ACE lite port look-up failure\n", + dn->full_name)) + return -ENODEV; + cci_port_control(port, enable); + return 0; +} +EXPORT_SYMBOL_GPL(__cci_control_port_by_device); + +/** + * __cci_control_port_by_index() - function to control a CCI port by port index + * + * @port: port index previously retrieved with cci_ace_get_port() + * @enable: if true enables the port, if false disables it + * + * Return: + * 0 on success + * -ENODEV on port index out of range + * -EPERM if operation carried out on an ACE PORT + */ +int notrace __cci_control_port_by_index(u32 port, bool enable) +{ + if (port >= nb_cci_ports || ports[port].type == ACE_INVALID_PORT) + return -ENODEV; + /* + * CCI control for ports connected to CPUS is extremely fragile + * and must be made to go through a specific and controlled + * interface (ie cci_disable_port_by_cpu(); control by general purpose + * indexing is therefore disabled for ACE ports. + */ + if (ports[port].type == ACE_PORT) + return -EPERM; + + cci_port_control(port, enable); + return 0; +} +EXPORT_SYMBOL_GPL(__cci_control_port_by_index); + +static const struct cci_nb_ports cci400_ports = { + .nb_ace = 2, + .nb_ace_lite = 3 +}; + +static const struct of_device_id arm_cci_matches[] = { + {.compatible = "arm,cci-400", .data = &cci400_ports }, + {}, +}; + +static const struct of_device_id arm_cci_ctrl_if_matches[] = { + {.compatible = "arm,cci-400-ctrl-if", }, + {}, +}; + +static int __init cci_probe(void) +{ + struct cci_nb_ports const *cci_config; + int ret, i, nb_ace = 0, nb_ace_lite = 0; + struct device_node *np, *cp; + const char *match_str; + bool is_ace; + + np = of_find_matching_node(NULL, arm_cci_matches); + if (!np) + return -ENODEV; + + cci_config = of_match_node(arm_cci_matches, np)->data; + if (!cci_config) + return -ENODEV; + + nb_cci_ports = cci_config->nb_ace + cci_config->nb_ace_lite; + + ports = kcalloc(sizeof(*ports), nb_cci_ports, GFP_KERNEL); + if (!ports) + return -ENOMEM; + + cci_ctrl_base = of_iomap(np, 0); + + if (!cci_ctrl_base) { + WARN(1, "unable to ioremap CCI ctrl\n"); + ret = -ENXIO; + goto memalloc_err; + } + + for_each_child_of_node(np, cp) { + if (!of_match_node(arm_cci_ctrl_if_matches, cp)) + continue; + + i = nb_ace + nb_ace_lite; + + if (i >= nb_cci_ports) + break; + + if (of_property_read_string(cp, "interface-type", + &match_str)) { + WARN(1, "node %s missing interface-type property\n", + cp->full_name); + continue; + } + is_ace = strcmp(match_str, "ace") == 0; + if (!is_ace && strcmp(match_str, "ace-lite")) { + WARN(1, "node %s containing invalid interface-type property, skipping it\n", + cp->full_name); + continue; + } + + ports[i].base = of_iomap(cp, 0); + + if (!ports[i].base) { + WARN(1, "unable to ioremap CCI port %d\n", i); + continue; + } + + if (is_ace) { + if (WARN_ON(nb_ace >= cci_config->nb_ace)) + continue; + ports[i].type = ACE_PORT; + ++nb_ace; + } else { + if (WARN_ON(nb_ace_lite >= cci_config->nb_ace_lite)) + continue; + ports[i].type = ACE_LITE_PORT; + ++nb_ace_lite; + } + ports[i].dn = cp; + } + + /* initialize a stashed array of ACE ports to speed-up look-up */ + cci_ace_init_ports(); + + /* + * Multi-cluster systems may need this data when non-coherent, during + * cluster power-up/power-down. Make sure it reaches main memory. + */ + sync_cache_w(&cci_ctrl_base); + sync_cache_w(&ports); + sync_cache_w(&cpu_port); + __sync_cache_range_w(ports, sizeof(*ports) * nb_cci_ports); + pr_info("ARM CCI driver probed\n"); + return 0; + +memalloc_err: + + kfree(ports); + return ret; +} + +static int cci_init_status = -EAGAIN; +static DEFINE_MUTEX(cci_probing); + +static int __init cci_init(void) +{ + if (cci_init_status != -EAGAIN) + return cci_init_status; + + mutex_lock(&cci_probing); + if (cci_init_status == -EAGAIN) + cci_init_status = cci_probe(); + mutex_unlock(&cci_probing); + return cci_init_status; +} + +/* + * To sort out early init calls ordering a helper function is provided to + * check if the CCI driver has beed initialized. Function check if the driver + * has been initialized, if not it calls the init function that probes + * the driver and updates the return value. + */ +bool __init cci_probed(void) +{ + return cci_init() == 0; +} +EXPORT_SYMBOL_GPL(cci_probed); + +early_initcall(cci_init); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("ARM CCI support"); diff --git a/include/linux/arm-cci.h b/include/linux/arm-cci.h new file mode 100644 index 00000000000..79d6edf446d --- /dev/null +++ b/include/linux/arm-cci.h @@ -0,0 +1,61 @@ +/* + * CCI cache coherent interconnect support + * + * Copyright (C) 2013 ARM Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __LINUX_ARM_CCI_H +#define __LINUX_ARM_CCI_H + +#include +#include + +struct device_node; + +#ifdef CONFIG_ARM_CCI +extern bool cci_probed(void); +extern int cci_ace_get_port(struct device_node *dn); +extern int cci_disable_port_by_cpu(u64 mpidr); +extern int __cci_control_port_by_device(struct device_node *dn, bool enable); +extern int __cci_control_port_by_index(u32 port, bool enable); +#else +static inline bool cci_probed(void) { return false; } +static inline int cci_ace_get_port(struct device_node *dn) +{ + return -ENODEV; +} +static inline int cci_disable_port_by_cpu(u64 mpidr) { return -ENODEV; } +static inline int __cci_control_port_by_device(struct device_node *dn, + bool enable) +{ + return -ENODEV; +} +static inline int __cci_control_port_by_index(u32 port, bool enable) +{ + return -ENODEV; +} +#endif +#define cci_disable_port_by_device(dev) \ + __cci_control_port_by_device(dev, false) +#define cci_enable_port_by_device(dev) \ + __cci_control_port_by_device(dev, true) +#define cci_disable_port_by_index(dev) \ + __cci_control_port_by_index(dev, false) +#define cci_enable_port_by_index(dev) \ + __cci_control_port_by_index(dev, true) + +#endif -- cgit v1.2.3 From ab28e08f102695a7f81cc5618e1f78fbbfba92a0 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Tue, 21 May 2013 23:34:41 -0400 Subject: drivers/bus: arm-cci: function to enable CCI ports from early boot code This provides cci_enable_port_for_self(). This is the counterpart to cci_disable_port_by_cpu(self). This is meant to be called from the MCPM machine specific power_up_setup callback code when the appropriate affinity level needs to be initialized. The code therefore has to be position independent as the MMU is still off and it cannot rely on any stack space. Signed-off-by: Nicolas Pitre Reviewed-by: Dave Martin --- drivers/bus/arm-cci.c | 119 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 113 insertions(+), 6 deletions(-) diff --git a/drivers/bus/arm-cci.c b/drivers/bus/arm-cci.c index ea81fa4a28d..380319ebf72 100644 --- a/drivers/bus/arm-cci.c +++ b/drivers/bus/arm-cci.c @@ -43,6 +43,7 @@ enum cci_ace_port_type { struct cci_ace_port { void __iomem *base; + unsigned long phys; enum cci_ace_port_type type; struct device_node *dn; }; @@ -51,11 +52,13 @@ static struct cci_ace_port *ports; static unsigned int nb_cci_ports; static void __iomem *cci_ctrl_base; +static unsigned long cci_ctrl_phys; struct cpu_port { u64 mpidr; u32 port; }; + /* * Use the port MSB as valid flag, shift can be made dynamic * by computing number of bits required for port indexes. @@ -229,6 +232,102 @@ int notrace cci_disable_port_by_cpu(u64 mpidr) } EXPORT_SYMBOL_GPL(cci_disable_port_by_cpu); +/** + * cci_enable_port_for_self() - enable a CCI port for calling CPU + * + * Enabling a CCI port for the calling CPU implies enabling the CCI + * port controlling that CPU's cluster. Caller must make sure that the + * CPU running the code is the first active CPU in the cluster and all + * other CPUs are quiescent in a low power state or waiting for this CPU + * to complete the CCI initialization. + * + * Because this is called when the MMU is still off and with no stack, + * the code must be position independent and ideally rely on callee + * clobbered registers only. To achieve this we must code this function + * entirely in assembler. + * + * On success this returns with the proper CCI port enabled. In case of + * any failure this never returns as the inability to enable the CCI is + * fatal and there is no possible recovery at this stage. + */ +asmlinkage void __naked cci_enable_port_for_self(void) +{ + asm volatile ("\n" + +" mrc p15, 0, r0, c0, c0, 5 @ get MPIDR value \n" +" and r0, r0, #"__stringify(MPIDR_HWID_BITMASK)" \n" +" adr r1, 5f \n" +" ldr r2, [r1] \n" +" add r1, r1, r2 @ &cpu_port \n" +" add ip, r1, %[sizeof_cpu_port] \n" + + /* Loop over the cpu_port array looking for a matching MPIDR */ +"1: ldr r2, [r1, %[offsetof_cpu_port_mpidr_lsb]] \n" +" cmp r2, r0 @ compare MPIDR \n" +" bne 2f \n" + + /* Found a match, now test port validity */ +" ldr r3, [r1, %[offsetof_cpu_port_port]] \n" +" tst r3, #"__stringify(PORT_VALID)" \n" +" bne 3f \n" + + /* no match, loop with the next cpu_port entry */ +"2: add r1, r1, %[sizeof_struct_cpu_port] \n" +" cmp r1, ip @ done? \n" +" blo 1b \n" + + /* CCI port not found -- cheaply try to stall this CPU */ +"cci_port_not_found: \n" +" wfi \n" +" wfe \n" +" b cci_port_not_found \n" + + /* Use matched port index to look up the corresponding ports entry */ +"3: bic r3, r3, #"__stringify(PORT_VALID)" \n" +" adr r0, 6f \n" +" ldmia r0, {r1, r2} \n" +" sub r1, r1, r0 @ virt - phys \n" +" ldr r0, [r0, r2] @ *(&ports) \n" +" mov r2, %[sizeof_struct_ace_port] \n" +" mla r0, r2, r3, r0 @ &ports[index] \n" +" sub r0, r0, r1 @ virt_to_phys() \n" + + /* Enable the CCI port */ +" ldr r0, [r0, %[offsetof_port_phys]] \n" +" mov r3, #"__stringify(CCI_ENABLE_REQ)" \n" +" str r3, [r0, #"__stringify(CCI_PORT_CTRL)"] \n" + + /* poll the status reg for completion */ +" adr r1, 7f \n" +" ldr r0, [r1] \n" +" ldr r0, [r0, r1] @ cci_ctrl_base \n" +"4: ldr r1, [r0, #"__stringify(CCI_CTRL_STATUS)"] \n" +" tst r1, #1 \n" +" bne 4b \n" + +" mov r0, #0 \n" +" bx lr \n" + +" .align 2 \n" +"5: .word cpu_port - . \n" +"6: .word . \n" +" .word ports - 6b \n" +"7: .word cci_ctrl_phys - . \n" + : : + [sizeof_cpu_port] "i" (sizeof(cpu_port)), +#ifndef __ARMEB__ + [offsetof_cpu_port_mpidr_lsb] "i" (offsetof(struct cpu_port, mpidr)), +#else + [offsetof_cpu_port_mpidr_lsb] "i" (offsetof(struct cpu_port, mpidr)+4), +#endif + [offsetof_cpu_port_port] "i" (offsetof(struct cpu_port, port)), + [sizeof_struct_cpu_port] "i" (sizeof(struct cpu_port)), + [sizeof_struct_ace_port] "i" (sizeof(struct cci_ace_port)), + [offsetof_port_phys] "i" (offsetof(struct cci_ace_port, phys)) ); + + unreachable(); +} + /** * __cci_control_port_by_device() - function to control a CCI port by device * reference @@ -306,6 +405,7 @@ static int __init cci_probe(void) struct cci_nb_ports const *cci_config; int ret, i, nb_ace = 0, nb_ace_lite = 0; struct device_node *np, *cp; + struct resource res; const char *match_str; bool is_ace; @@ -323,9 +423,12 @@ static int __init cci_probe(void) if (!ports) return -ENOMEM; - cci_ctrl_base = of_iomap(np, 0); - - if (!cci_ctrl_base) { + ret = of_address_to_resource(np, 0, &res); + if (!ret) { + cci_ctrl_base = ioremap(res.start, resource_size(&res)); + cci_ctrl_phys = res.start; + } + if (ret || !cci_ctrl_base) { WARN(1, "unable to ioremap CCI ctrl\n"); ret = -ENXIO; goto memalloc_err; @@ -353,9 +456,12 @@ static int __init cci_probe(void) continue; } - ports[i].base = of_iomap(cp, 0); - - if (!ports[i].base) { + ret = of_address_to_resource(cp, 0, &res); + if (!ret) { + ports[i].base = ioremap(res.start, resource_size(&res)); + ports[i].phys = res.start; + } + if (ret || !ports[i].base) { WARN(1, "unable to ioremap CCI port %d\n", i); continue; } @@ -382,6 +488,7 @@ static int __init cci_probe(void) * cluster power-up/power-down. Make sure it reaches main memory. */ sync_cache_w(&cci_ctrl_base); + sync_cache_w(&cci_ctrl_phys); sync_cache_w(&ports); sync_cache_w(&cpu_port); __sync_cache_range_w(ports, sizeof(*ports) * nb_cci_ports); -- cgit v1.2.3 From 1d36a70525f2876794e1b3e4bc8b82c7d43682b4 Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Fri, 10 May 2013 14:37:37 +0100 Subject: ARM: vexpress: Add CCI nodes to TC2 device-tree Signed-off-by: Jon Medhurst --- arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts b/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts index d2803be4e1a..12bd4ea07ea 100644 --- a/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts +++ b/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts @@ -37,30 +37,35 @@ device_type = "cpu"; compatible = "arm,cortex-a15"; reg = <0>; + cci-control-port = <&cci_control1>; }; cpu1: cpu@1 { device_type = "cpu"; compatible = "arm,cortex-a15"; reg = <1>; + cci-control-port = <&cci_control1>; }; cpu2: cpu@2 { device_type = "cpu"; compatible = "arm,cortex-a7"; reg = <0x100>; + cci-control-port = <&cci_control2>; }; cpu3: cpu@3 { device_type = "cpu"; compatible = "arm,cortex-a7"; reg = <0x101>; + cci-control-port = <&cci_control2>; }; cpu4: cpu@4 { device_type = "cpu"; compatible = "arm,cortex-a7"; reg = <0x102>; + cci-control-port = <&cci_control2>; }; }; @@ -104,6 +109,26 @@ interrupts = <1 9 0xf04>; }; + cci@2c090000 { + compatible = "arm,cci-400"; + #address-cells = <1>; + #size-cells = <1>; + reg = <0 0x2c090000 0 0x1000>; + ranges = <0x0 0x0 0x2c090000 0x10000>; + + cci_control1: slave-if@4000 { + compatible = "arm,cci-400-ctrl-if"; + interface-type = "ace"; + reg = <0x4000 0x1000>; + }; + + cci_control2: slave-if@5000 { + compatible = "arm,cci-400-ctrl-if"; + interface-type = "ace"; + reg = <0x5000 0x1000>; + }; + }; + memory-controller@7ffd0000 { compatible = "arm,pl354", "arm,primecell"; reg = <0 0x7ffd0000 0 0x1000>; -- cgit v1.2.3 From 0b9bd1cc8d48a4dcb61aa01a4a80d690f99f24a1 Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Fri, 10 May 2013 14:47:27 +0100 Subject: drivers: ARM CCI: Add a platform driver stub for the PMU This is a hack to enable the old CCI PMU patches to be used with the new CCI driver. The CCI PMU is (mis)represented by a separate node in TC2 device-tree. Signed-off-by: Jon Medhurst --- arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts | 10 ++++++ drivers/bus/arm-cci.c | 51 ++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts b/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts index 12bd4ea07ea..9d802fc71fc 100644 --- a/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts +++ b/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts @@ -129,6 +129,16 @@ }; }; + cci-pmu@2c099000 { + compatible = "arm,cci-400-pmu"; + reg = <0 0x2c099000 0 0x6000>; + interrupts = <0 101 4>, + <0 102 4>, + <0 103 4>, + <0 104 4>, + <0 105 4>; + }; + memory-controller@7ffd0000 { compatible = "arm,pl354", "arm,primecell"; reg = <0 0x7ffd0000 0 0x1000>; diff --git a/drivers/bus/arm-cci.c b/drivers/bus/arm-cci.c index 380319ebf72..a621d3d8b90 100644 --- a/drivers/bus/arm-cci.c +++ b/drivers/bus/arm-cci.c @@ -17,12 +17,15 @@ #include #include #include +#include #include #include #include #include +#define DRIVER_NAME "CCI" + #define CCI_PORT_CTRL 0x0 #define CCI_CTRL_STATUS 0xc @@ -54,6 +57,53 @@ static unsigned int nb_cci_ports; static void __iomem *cci_ctrl_base; static unsigned long cci_ctrl_phys; +#ifdef CONFIG_HW_PERF_EVENTS + +static void __iomem *cci_pmu_base; + +static int cci_pmu_probe(struct platform_device *pdev) +{ + struct resource *res; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + cci_pmu_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(cci_pmu_base)) + return PTR_ERR(cci_pmu_base); + + return 0; +} + +static const struct of_device_id arm_cci_pmu_matches[] = { + {.compatible = "arm,cci-400-pmu"}, + {}, +}; + +static struct platform_driver cci_pmu_platform_driver = { + .driver = { + .name = DRIVER_NAME, + .of_match_table = arm_cci_pmu_matches, + }, + .probe = cci_pmu_probe, +}; + +static int __init cci_pmu_init(void) +{ + if (platform_driver_register(&cci_pmu_platform_driver)) + WARN(1, "unable to register CCI platform driver\n"); + return 0; +} + +#else + +static int __init cci_pmu_init(void) +{ + return 0; +} + +static void cci_pmu_destroy(void) { } + +#endif /* CONFIG_HW_PERF_EVENTS */ + struct cpu_port { u64 mpidr; u32 port; @@ -529,5 +579,6 @@ bool __init cci_probed(void) EXPORT_SYMBOL_GPL(cci_probed); early_initcall(cci_init); +core_initcall(cci_pmu_init); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("ARM CCI support"); -- cgit v1.2.3 From 4311d5a4b599317bc321b1d8b9c0d1f4e49e7df5 Mon Sep 17 00:00:00 2001 From: Punit Agrawal Date: Thu, 20 Sep 2012 16:39:25 +0100 Subject: Support CCI PMU in perf CCI400 has a set of counters that can be used to profile different transations at CCI master and slave interfaces. These counters can observe different kinds of transations passing through the CCI and provide a system-level view of activity. This patch adds support for CCI PMU by extending the existing CCI driver. Signed-off-by: Punit Agrawal Signed-off-by: Jon Medhurst --- drivers/bus/arm-cci.c | 365 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 364 insertions(+), 1 deletion(-) diff --git a/drivers/bus/arm-cci.c b/drivers/bus/arm-cci.c index a621d3d8b90..ed284ab0041 100644 --- a/drivers/bus/arm-cci.c +++ b/drivers/bus/arm-cci.c @@ -22,6 +22,8 @@ #include #include +#include +#include #include #define DRIVER_NAME "CCI" @@ -61,6 +63,362 @@ static unsigned long cci_ctrl_phys; static void __iomem *cci_pmu_base; +#define CCI400_PMCR 0x0100 + +#define CCI400_PMU_CYCLE_CNTR_BASE 0x0000 +#define CCI400_PMU_CNTR_BASE(idx) (CCI400_PMU_CYCLE_CNTR_BASE + (idx) * 0x1000) + +#define CCI400_PMCR_CEN 0x00000001 +#define CCI400_PMCR_RST 0x00000002 +#define CCI400_PMCR_CCR 0x00000004 +#define CCI400_PMCR_CCD 0x00000008 +#define CCI400_PMCR_EX 0x00000010 +#define CCI400_PMCR_DP 0x00000020 +#define CCI400_PMCR_NCNT_MASK 0x0000F800 +#define CCI400_PMCR_NCNT_SHIFT 11 + +#define CCI400_PMU_EVT_SEL 0x000 +#define CCI400_PMU_CNTR 0x004 +#define CCI400_PMU_CNTR_CTRL 0x008 +#define CCI400_PMU_OVERFLOW 0x00C + +#define CCI400_PMU_OVERFLOW_FLAG 1 + +enum cci400_perf_events { + CCI400_PMU_CYCLES = 0xFF +}; + +#define CCI400_PMU_EVENT_MASK 0xff +#define CCI400_PMU_EVENT_SOURCE(event) ((event >> 5) & 0x7) +#define CCI400_PMU_EVENT_CODE(event) (event & 0x1f) + +#define CCI400_PMU_EVENT_SOURCE_S0 0 +#define CCI400_PMU_EVENT_SOURCE_S4 4 +#define CCI400_PMU_EVENT_SOURCE_M0 5 +#define CCI400_PMU_EVENT_SOURCE_M2 7 + +#define CCI400_PMU_EVENT_SLAVE_MIN 0x0 +#define CCI400_PMU_EVENT_SLAVE_MAX 0x13 + +#define CCI400_PMU_EVENT_MASTER_MIN 0x14 +#define CCI400_PMU_EVENT_MASTER_MAX 0x1A + +#define CCI400_PMU_MAX_HW_EVENTS 5 /* CCI PMU has 4 counters + 1 cycle counter */ + +#define CCI400_PMU_CYCLE_COUNTER_IDX 0 +#define CCI400_PMU_COUNTER0_IDX 1 +#define CCI400_PMU_COUNTER_LAST(cci_pmu) (CCI400_PMU_CYCLE_COUNTER_IDX + cci_pmu->num_events - 1) + + +static struct perf_event *events[CCI400_PMU_MAX_HW_EVENTS]; +static unsigned long used_mask[BITS_TO_LONGS(CCI400_PMU_MAX_HW_EVENTS)]; +static struct pmu_hw_events cci_hw_events = { + .events = events, + .used_mask = used_mask, +}; + +static int cci_pmu_validate_hw_event(u8 hw_event) +{ + u8 ev_source = CCI400_PMU_EVENT_SOURCE(hw_event); + u8 ev_code = CCI400_PMU_EVENT_CODE(hw_event); + + if (ev_source <= CCI400_PMU_EVENT_SOURCE_S4 && + ev_code <= CCI400_PMU_EVENT_SLAVE_MAX) + return hw_event; + else if (CCI400_PMU_EVENT_SOURCE_M0 <= ev_source && + ev_source <= CCI400_PMU_EVENT_SOURCE_M2 && + CCI400_PMU_EVENT_MASTER_MIN <= ev_code && + ev_code <= CCI400_PMU_EVENT_MASTER_MAX) + return hw_event; + + return -EINVAL; +} + +static inline int cci_pmu_counter_is_valid(struct arm_pmu *cci_pmu, int idx) +{ + return CCI400_PMU_CYCLE_COUNTER_IDX <= idx && + idx <= CCI400_PMU_COUNTER_LAST(cci_pmu); +} + +static inline u32 cci_pmu_read_register(int idx, unsigned int offset) +{ + return readl_relaxed(cci_pmu_base + CCI400_PMU_CNTR_BASE(idx) + offset); +} + +static inline void cci_pmu_write_register(u32 value, int idx, unsigned int offset) +{ + return writel_relaxed(value, cci_pmu_base + CCI400_PMU_CNTR_BASE(idx) + offset); +} + +static inline void cci_pmu_disable_counter(int idx) +{ + cci_pmu_write_register(0, idx, CCI400_PMU_CNTR_CTRL); +} + +static inline void cci_pmu_enable_counter(int idx) +{ + cci_pmu_write_register(1, idx, CCI400_PMU_CNTR_CTRL); +} + +static inline void cci_pmu_select_event(int idx, unsigned long event) +{ + event &= CCI400_PMU_EVENT_MASK; + cci_pmu_write_register(event, idx, CCI400_PMU_EVT_SEL); +} + +static u32 cci_pmu_get_max_counters(void) +{ + u32 n_cnts = (readl_relaxed(cci_ctrl_base + CCI400_PMCR) & + CCI400_PMCR_NCNT_MASK) >> CCI400_PMCR_NCNT_SHIFT; + + /* add 1 for cycle counter */ + return n_cnts + 1; +} + +static struct pmu_hw_events *cci_pmu_get_hw_events(void) +{ + return &cci_hw_events; +} + +static int cci_pmu_get_event_idx(struct pmu_hw_events *hw, struct perf_event *event) +{ + struct arm_pmu *cci_pmu = to_arm_pmu(event->pmu); + struct hw_perf_event *hw_event = &event->hw; + unsigned long cci_event = hw_event->config_base & CCI400_PMU_EVENT_MASK; + int idx; + + if (cci_event == CCI400_PMU_CYCLES) { + if (test_and_set_bit(CCI400_PMU_CYCLE_COUNTER_IDX, hw->used_mask)) + return -EAGAIN; + + return CCI400_PMU_CYCLE_COUNTER_IDX; + } + + for (idx = CCI400_PMU_COUNTER0_IDX; idx <= CCI400_PMU_COUNTER_LAST(cci_pmu); ++idx) { + if (!test_and_set_bit(idx, hw->used_mask)) + return idx; + } + + /* No counters available */ + return -EAGAIN; +} + +static int cci_pmu_map_event(struct perf_event *event) +{ + int mapping; + u8 config = event->attr.config & CCI400_PMU_EVENT_MASK; + + if (event->attr.type < PERF_TYPE_MAX) + return -ENOENT; + + /* 0xff is used to represent CCI Cycles */ + if (config == 0xff) + mapping = config; + else + mapping = cci_pmu_validate_hw_event(config); + + return mapping; +} + +static int cci_pmu_request_irq(struct arm_pmu *cci_pmu, irq_handler_t handler) +{ + int irq, err, i = 0; + struct platform_device *pmu_device = cci_pmu->plat_device; + + if (unlikely(!pmu_device)) + return -ENODEV; + + /* CCI exports 6 interrupts - 1 nERRORIRQ + 5 nEVNTCNTOVERFLOW (PMU) + nERRORIRQ will be handled by secure firmware on TC2. So we + assume that all CCI interrupts listed in the linux device + tree are PMU interrupts. + + The following code should then be able to handle different routing + of the CCI PMU interrupts. + */ + while ((irq = platform_get_irq(pmu_device, i)) > 0) { + err = request_irq(irq, handler, 0, "arm-cci-pmu", cci_pmu); + if (err) { + dev_err(&pmu_device->dev, "unable to request IRQ%d for ARM CCI PMU counters\n", + irq); + return err; + } + i++; + } + + return 0; +} + +static irqreturn_t cci_pmu_handle_irq(int irq_num, void *dev) +{ + struct arm_pmu *cci_pmu = (struct arm_pmu *)dev; + struct pmu_hw_events *events = cci_pmu->get_hw_events(); + struct perf_sample_data data; + struct pt_regs *regs; + int idx; + + regs = get_irq_regs(); + + /* Iterate over counters and update the corresponding perf events. + This should work regardless of whether we have per-counter overflow + interrupt or a combined overflow interrupt. */ + for (idx = CCI400_PMU_CYCLE_COUNTER_IDX; idx <= CCI400_PMU_COUNTER_LAST(cci_pmu); idx++) { + struct perf_event *event = events->events[idx]; + struct hw_perf_event *hw_counter; + + if (!event) + continue; + + hw_counter = &event->hw; + + /* Did this counter overflow? */ + if (!(cci_pmu_read_register(idx, CCI400_PMU_OVERFLOW) & CCI400_PMU_OVERFLOW_FLAG)) + continue; + cci_pmu_write_register(CCI400_PMU_OVERFLOW_FLAG, idx, CCI400_PMU_OVERFLOW); + + armpmu_event_update(event); + perf_sample_data_init(&data, 0, hw_counter->last_period); + if (!armpmu_event_set_period(event)) + continue; + + if (perf_event_overflow(event, &data, regs)) + cci_pmu->disable(event); + } + + irq_work_run(); + return IRQ_HANDLED; +} + +static void cci_pmu_free_irq(struct arm_pmu *cci_pmu) +{ + int irq, i = 0; + struct platform_device *pmu_device = cci_pmu->plat_device; + + while ((irq = platform_get_irq(pmu_device, i)) > 0) { + free_irq(irq, cci_pmu); + i++; + } +} + +static void cci_pmu_enable_event(struct perf_event *event) +{ + unsigned long flags; + struct arm_pmu *cci_pmu = to_arm_pmu(event->pmu); + struct pmu_hw_events *events = cci_pmu->get_hw_events(); + struct hw_perf_event *hw_counter = &event->hw; + int idx = hw_counter->idx; + + if (unlikely(!cci_pmu_counter_is_valid(cci_pmu, idx))) { + dev_err(&cci_pmu->plat_device->dev, "Invalid CCI PMU counter %d\n", idx); + return; + } + + raw_spin_lock_irqsave(&events->pmu_lock, flags); + + /* Configure the event to count, unless you are counting cycles */ + if (idx != CCI400_PMU_CYCLE_COUNTER_IDX) + cci_pmu_select_event(idx, hw_counter->config_base); + + cci_pmu_enable_counter(idx); + + raw_spin_unlock_irqrestore(&events->pmu_lock, flags); +} + +static void cci_pmu_disable_event(struct perf_event *event) +{ + unsigned long flags; + struct arm_pmu *cci_pmu = to_arm_pmu(event->pmu); + struct pmu_hw_events *events = cci_pmu->get_hw_events(); + struct hw_perf_event *hw_counter = &event->hw; + int idx = hw_counter->idx; + + if (unlikely(!cci_pmu_counter_is_valid(cci_pmu, idx))) { + dev_err(&cci_pmu->plat_device->dev, "Invalid CCI PMU counter %d\n", idx); + return; + } + + raw_spin_lock_irqsave(&events->pmu_lock, flags); + + cci_pmu_disable_counter(idx); + + raw_spin_unlock_irqrestore(&events->pmu_lock, flags); +} + +static void cci_pmu_start(struct arm_pmu *cci_pmu) +{ + u32 val; + unsigned long flags; + struct pmu_hw_events *events = cci_pmu->get_hw_events(); + + raw_spin_lock_irqsave(&events->pmu_lock, flags); + + /* Enable all the PMU counters. */ + val = readl(cci_ctrl_base + CCI400_PMCR) | CCI400_PMCR_CEN; + writel(val, cci_ctrl_base + CCI400_PMCR); + + raw_spin_unlock_irqrestore(&events->pmu_lock, flags); +} + +static void cci_pmu_stop(struct arm_pmu *cci_pmu) +{ + u32 val; + unsigned long flags; + struct pmu_hw_events *events = cci_pmu->get_hw_events(); + + raw_spin_lock_irqsave(&events->pmu_lock, flags); + + /* Disable all the PMU counters. */ + val = readl(cci_ctrl_base + CCI400_PMCR) & ~CCI400_PMCR_CEN; + writel(val, cci_ctrl_base + CCI400_PMCR); + + raw_spin_unlock_irqrestore(&events->pmu_lock, flags); +} + +static u32 cci_pmu_read_counter(struct perf_event *event) +{ + struct arm_pmu *cci_pmu = to_arm_pmu(event->pmu); + struct hw_perf_event *hw_counter = &event->hw; + int idx = hw_counter->idx; + u32 value; + + if (unlikely(!cci_pmu_counter_is_valid(cci_pmu, idx))) { + dev_err(&cci_pmu->plat_device->dev, "Invalid CCI PMU counter %d\n", idx); + return 0; + } + value = cci_pmu_read_register(idx, CCI400_PMU_CNTR); + + return value; +} + +static void cci_pmu_write_counter(struct perf_event *event, u32 value) +{ + struct arm_pmu *cci_pmu = to_arm_pmu(event->pmu); + struct hw_perf_event *hw_counter = &event->hw; + int idx = hw_counter->idx; + + if (unlikely(!cci_pmu_counter_is_valid(cci_pmu, idx))) + dev_err(&cci_pmu->plat_device->dev, "Invalid CCI PMU counter %d\n", idx); + else + cci_pmu_write_register(value, idx, CCI400_PMU_CNTR); +} + +static struct arm_pmu cci_pmu = { + .name = DRIVER_NAME, + .max_period = (1LLU << 32) - 1, + .get_hw_events = cci_pmu_get_hw_events, + .get_event_idx = cci_pmu_get_event_idx, + .map_event = cci_pmu_map_event, + .request_irq = cci_pmu_request_irq, + .handle_irq = cci_pmu_handle_irq, + .free_irq = cci_pmu_free_irq, + .enable = cci_pmu_enable_event, + .disable = cci_pmu_disable_event, + .start = cci_pmu_start, + .stop = cci_pmu_stop, + .read_counter = cci_pmu_read_counter, + .write_counter = cci_pmu_write_counter, +}; + static int cci_pmu_probe(struct platform_device *pdev) { struct resource *res; @@ -70,7 +428,12 @@ static int cci_pmu_probe(struct platform_device *pdev) if (IS_ERR(cci_pmu_base)) return PTR_ERR(cci_pmu_base); - return 0; + cci_pmu.plat_device = pdev; + cci_pmu.num_events = cci_pmu_get_max_counters(); + raw_spin_lock_init(&cci_hw_events.pmu_lock); + cpumask_setall(&cci_pmu.valid_cpus); + + return armpmu_register(&cci_pmu, -1); } static const struct of_device_id arm_cci_pmu_matches[] = { -- cgit v1.2.3 From 854f623384031939e66b1b838e7c332a9da2aac5 Mon Sep 17 00:00:00 2001 From: Pawel Moll Date: Thu, 6 Jun 2013 10:59:22 +0100 Subject: drivers: mfd: refactor the vexpress config bridge API The introduction of Serial Power Controller (SPC) requires the vexpress config interface to change slightly since the SPC memory mapped interface can be used as configuration bus but also for operating points programming and retrieval. The helper that allocates the bridge functions requires an additional parameter allowing to request component specific functions that need not be initialized through device tree bindings but just using simple look-up and statically defined constants. This patch introduces the necessary changes to the vexpress config layer to cater for the new vexpress bridge interface requirements. Cc: Samuel Ortiz Cc: Achin Gupta Cc: Sudeep KarkadaNagesha Cc: Pawel Moll Cc: Nicolas Pitre Cc: Amit Kucheria Cc: Jon Medhurst Signed-off-by: Pawel Moll Acked-by: Nicolas Pitre --- drivers/mfd/vexpress-config.c | 61 +++++++++++++++++++++++++++---------------- drivers/mfd/vexpress-sysreg.c | 2 +- include/linux/vexpress.h | 16 ++++++++---- 3 files changed, 51 insertions(+), 28 deletions(-) diff --git a/drivers/mfd/vexpress-config.c b/drivers/mfd/vexpress-config.c index 84ce6b9daa3..1af2b0e0182 100644 --- a/drivers/mfd/vexpress-config.c +++ b/drivers/mfd/vexpress-config.c @@ -86,29 +86,13 @@ void vexpress_config_bridge_unregister(struct vexpress_config_bridge *bridge) } EXPORT_SYMBOL(vexpress_config_bridge_unregister); - -struct vexpress_config_func { - struct vexpress_config_bridge *bridge; - void *func; -}; - -struct vexpress_config_func *__vexpress_config_func_get(struct device *dev, - struct device_node *node) +static struct vexpress_config_bridge * + vexpress_config_bridge_find(struct device_node *node) { - struct device_node *bridge_node; - struct vexpress_config_func *func; int i; + struct vexpress_config_bridge *res = NULL; + struct device_node *bridge_node = of_node_get(node); - if (WARN_ON(dev && node && dev->of_node != node)) - return NULL; - if (dev && !node) - node = dev->of_node; - - func = kzalloc(sizeof(*func), GFP_KERNEL); - if (!func) - return NULL; - - bridge_node = of_node_get(node); while (bridge_node) { const __be32 *prop = of_get_property(bridge_node, "arm,vexpress,config-bridge", NULL); @@ -129,13 +113,46 @@ struct vexpress_config_func *__vexpress_config_func_get(struct device *dev, if (test_bit(i, vexpress_config_bridges_map) && bridge->node == bridge_node) { - func->bridge = bridge; - func->func = bridge->info->func_get(dev, node); + res = bridge; break; } } mutex_unlock(&vexpress_config_bridges_mutex); + return res; +} + + +struct vexpress_config_func { + struct vexpress_config_bridge *bridge; + void *func; +}; + +struct vexpress_config_func *__vexpress_config_func_get( + struct vexpress_config_bridge *bridge, + struct device *dev, + struct device_node *node, + const char *id) +{ + struct vexpress_config_func *func; + + if (WARN_ON(dev && node && dev->of_node != node)) + return NULL; + if (dev && !node) + node = dev->of_node; + + if (!bridge) + bridge = vexpress_config_bridge_find(node); + if (!bridge) + return NULL; + + func = kzalloc(sizeof(*func), GFP_KERNEL); + if (!func) + return NULL; + + func->bridge = bridge; + func->func = bridge->info->func_get(dev, node, id); + if (!func->func) { of_node_put(node); kfree(func); diff --git a/drivers/mfd/vexpress-sysreg.c b/drivers/mfd/vexpress-sysreg.c index 96a020b1dcd..d2599aa26f9 100644 --- a/drivers/mfd/vexpress-sysreg.c +++ b/drivers/mfd/vexpress-sysreg.c @@ -165,7 +165,7 @@ static u32 *vexpress_sysreg_config_data; static int vexpress_sysreg_config_tries; static void *vexpress_sysreg_config_func_get(struct device *dev, - struct device_node *node) + struct device_node *node, const char *id) { struct vexpress_sysreg_config_func *config_func; u32 site; diff --git a/include/linux/vexpress.h b/include/linux/vexpress.h index ea7168a6808..89152673a47 100644 --- a/include/linux/vexpress.h +++ b/include/linux/vexpress.h @@ -68,7 +68,8 @@ */ struct vexpress_config_bridge_info { const char *name; - void *(*func_get)(struct device *dev, struct device_node *node); + void *(*func_get)(struct device *dev, struct device_node *node, + const char *id); void (*func_put)(void *func); int (*func_exec)(void *func, int offset, bool write, u32 *data); }; @@ -87,12 +88,17 @@ void vexpress_config_complete(struct vexpress_config_bridge *bridge, struct vexpress_config_func; -struct vexpress_config_func *__vexpress_config_func_get(struct device *dev, - struct device_node *node); +struct vexpress_config_func *__vexpress_config_func_get( + struct vexpress_config_bridge *bridge, + struct device *dev, + struct device_node *node, + const char *id); +#define vexpress_config_func_get(bridge, id) \ + __vexpress_config_func_get(bridge, NULL, NULL, id) #define vexpress_config_func_get_by_dev(dev) \ - __vexpress_config_func_get(dev, NULL) + __vexpress_config_func_get(NULL, dev, NULL, NULL) #define vexpress_config_func_get_by_node(node) \ - __vexpress_config_func_get(NULL, node) + __vexpress_config_func_get(NULL, NULL, node, NULL) void vexpress_config_func_put(struct vexpress_config_func *func); /* Both may sleep! */ -- cgit v1.2.3 From af17603909e9e54771741e30bfacf2a718d901ca Mon Sep 17 00:00:00 2001 From: Lorenzo Pieralisi Date: Thu, 6 Jun 2013 10:59:23 +0100 Subject: drivers: mfd: vexpress: add Serial Power Controller (SPC) support The TC2 versatile express 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 Serial Power Controller (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. The driver also provides a bridge interface through the vexpress config infrastructure. Operations allowing to read/write operating points are made to go via the same interface as configuration transactions so that all requests to M3 are serialized. Device tree bindings documentation for the SPC component is provided with the patchset. Cc: Samuel Ortiz Cc: Pawel Moll Cc: Nicolas Pitre Cc: Amit Kucheria Cc: Jon Medhurst Signed-off-by: Achin Gupta Signed-off-by: Lorenzo Pieralisi Signed-off-by: Sudeep KarkadaNagesha Reviewed-by: Nicolas Pitre --- .../devicetree/bindings/mfd/vexpress-spc.txt | 35 ++ drivers/mfd/Kconfig | 7 + drivers/mfd/Makefile | 1 + drivers/mfd/vexpress-spc.c | 633 +++++++++++++++++++++ include/linux/vexpress.h | 43 ++ 5 files changed, 719 insertions(+) create mode 100644 Documentation/devicetree/bindings/mfd/vexpress-spc.txt create mode 100644 drivers/mfd/vexpress-spc.c diff --git a/Documentation/devicetree/bindings/mfd/vexpress-spc.txt b/Documentation/devicetree/bindings/mfd/vexpress-spc.txt new file mode 100644 index 00000000000..1d71dc2ff15 --- /dev/null +++ b/Documentation/devicetree/bindings/mfd/vexpress-spc.txt @@ -0,0 +1,35 @@ +* ARM Versatile Express Serial Power Controller device tree bindings + +Latest ARM development boards implement a power management interface (serial +power controller - SPC) that is capable of managing power/voltage and +operating point transitions, through memory mapped registers interface. + +On testchips like TC2 it also provides a configuration interface that can +be used to read/write values which cannot be read/written through simple +memory mapped reads/writes. + +- spc node + + - compatible: + Usage: required + Value type: + Definition: must be + "arm,vexpress-spc,v2p-ca15_a7","arm,vexpress-spc" + - reg: + Usage: required + Value type: + Definition: A standard property that specifies the base address + and the size of the SPC address space + - interrupts: + Usage: required + Value type: + Definition: SPC interrupt configuration. A standard property + that follows ePAPR interrupts specifications + +Example: + +spc: spc@7fff0000 { + compatible = "arm,vexpress-spc,v2p-ca15_a7","arm,vexpress-spc"; + reg = <0 0x7FFF0000 0 0x1000>; + interrupts = <0 95 4>; +}; diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index d54e985748b..391eda139d6 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -1148,3 +1148,10 @@ config VEXPRESS_CONFIG help Platform configuration infrastructure for the ARM Ltd. Versatile Express. + +config VEXPRESS_SPC + bool "Versatile Express SPC driver support" + depends on ARM + depends on VEXPRESS_CONFIG + help + Serial Power Controller driver for ARM Ltd. test chips. diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 718e94a2a9a..3a0120315aa 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -153,5 +153,6 @@ obj-$(CONFIG_MFD_SEC_CORE) += sec-core.o sec-irq.o obj-$(CONFIG_MFD_SYSCON) += syscon.o obj-$(CONFIG_MFD_LM3533) += lm3533-core.o lm3533-ctrlbank.o obj-$(CONFIG_VEXPRESS_CONFIG) += vexpress-config.o vexpress-sysreg.o +obj-$(CONFIG_VEXPRESS_SPC) += vexpress-spc.o obj-$(CONFIG_MFD_RETU) += retu-mfd.o obj-$(CONFIG_MFD_AS3711) += as3711.o diff --git a/drivers/mfd/vexpress-spc.c b/drivers/mfd/vexpress-spc.c new file mode 100644 index 00000000000..0c6718abf1b --- /dev/null +++ b/drivers/mfd/vexpress-spc.c @@ -0,0 +1,633 @@ +/* + * Versatile Express Serial Power Controller (SPC) support + * + * Copyright (C) 2013 ARM Ltd. + * + * Authors: 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 + +#include + +#define SCC_CFGREG19 0x120 +#define SCC_CFGREG20 0x124 +#define A15_CONF 0x400 +#define A7_CONF 0x500 +#define SYS_INFO 0x700 +#define PERF_LVL_A15 0xB00 +#define PERF_REQ_A15 0xB04 +#define PERF_LVL_A7 0xB08 +#define PERF_REQ_A7 0xB0c +#define SYS_CFGCTRL 0xB10 +#define SYS_CFGCTRL_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 A7_PWRDNACK 0xB54 +#define A15_BX_ADDR0 0xB68 +#define SYS_CFG_WDATA 0xB70 +#define SYS_CFG_RDATA 0xB74 +#define A7_BX_ADDR0 0xB78 + +#define GBL_WAKEUP_INT_MSK (0x3 << 10) + +#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 OPP_FUNCTION 6 +#define OPP_BASE_DEVICE 0x300 +#define OPP_A15_OFFSET 0x4 +#define OPP_A7_OFFSET 0xc + +#define SYS_CFGCTRL_START (1 << 31) +#define SYS_CFGCTRL_WRITE (1 << 30) +#define SYS_CFGCTRL_FUNC(n) (((n) & 0x3f) << 20) +#define SYS_CFGCTRL_DEVICE(n) (((n) & 0xfff) << 0) + +#define MAX_OPPS 8 +#define MAX_CLUSTERS 2 + +enum { + A15_OPP_TYPE = 0, + A7_OPP_TYPE = 1, + SYS_CFGCTRL_TYPE = 2, + INVALID_TYPE +}; + +#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 vexpress_spc_drvdata { + void __iomem *baseaddr; + u32 a15_clusid; + int irq; + u32 cur_req_type; + u32 freqs[MAX_CLUSTERS][MAX_OPPS]; + int freqs_cnt[MAX_CLUSTERS]; +}; + +enum spc_func_type { + CONFIG_FUNC = 0, + PERF_FUNC = 1, +}; + +struct vexpress_spc_func { + enum spc_func_type type; + u32 function; + u32 device; +}; + +static struct vexpress_spc_drvdata *info; +static u32 *vexpress_spc_config_data; +static struct vexpress_config_bridge *vexpress_spc_config_bridge; +static struct vexpress_config_func *opp_func, *perf_func; + +static int vexpress_spc_load_result = -EAGAIN; + +static bool vexpress_spc_initialized(void) +{ + return vexpress_spc_load_result == 0; +} + +/** + * vexpress_spc_write_resume_reg() - set the jump address used for warm boot + * + * @cluster: mpidr[15:8] bitfield describing cluster affinity level + * @cpu: mpidr[7:0] bitfield describing cpu affinity level + * @addr: physical resume address + */ +void vexpress_spc_write_resume_reg(u32 cluster, u32 cpu, u32 addr) +{ + void __iomem *baseaddr; + + if (WARN_ON_ONCE(cluster >= MAX_CLUSTERS)) + return; + + if (cluster != info->a15_clusid) + baseaddr = info->baseaddr + A7_BX_ADDR0 + (cpu << 2); + else + baseaddr = info->baseaddr + A15_BX_ADDR0 + (cpu << 2); + + writel_relaxed(addr, baseaddr); +} + +/** + * vexpress_spc_get_nb_cpus() - get number of cpus in a cluster + * + * @cluster: mpidr[15:8] bitfield describing cluster affinity level + * + * Return: number of cpus in the cluster + * -EINVAL if cluster number invalid + */ +int vexpress_spc_get_nb_cpus(u32 cluster) +{ + u32 val; + + if (WARN_ON_ONCE(cluster >= MAX_CLUSTERS)) + return -EINVAL; + + val = readl_relaxed(info->baseaddr + SYS_INFO); + val = (cluster != info->a15_clusid) ? (val >> 20) : (val >> 16); + return val & 0xf; +} +EXPORT_SYMBOL_GPL(vexpress_spc_get_nb_cpus); + +/** + * vexpress_spc_get_performance - get current performance level of cluster + * @cluster: mpidr[15:8] bitfield describing cluster affinity level + * @freq: pointer to the performance level to be assigned + * + * Return: 0 on success + * < 0 on read error + */ +int vexpress_spc_get_performance(u32 cluster, u32 *freq) +{ + u32 perf_cfg_reg; + int perf, ret; + + if (!vexpress_spc_initialized() || (cluster >= MAX_CLUSTERS)) + return -EINVAL; + + perf_cfg_reg = cluster != info->a15_clusid ? PERF_LVL_A7 : PERF_LVL_A15; + ret = vexpress_config_read(perf_func, perf_cfg_reg, &perf); + + if (!ret) + *freq = info->freqs[cluster][perf]; + + return ret; +} +EXPORT_SYMBOL_GPL(vexpress_spc_get_performance); + +/** + * vexpress_spc_get_perf_index - get performance level corresponding to + * a frequency + * @cluster: mpidr[15:8] bitfield describing cluster affinity level + * @freq: frequency to be looked-up + * + * Return: perf level index on success + * -EINVAL on error + */ +static int vexpress_spc_find_perf_index(u32 cluster, u32 freq) +{ + int idx; + + for (idx = 0; idx < info->freqs_cnt[cluster]; idx++) + if (info->freqs[cluster][idx] == freq) + break; + return (idx == info->freqs_cnt[cluster]) ? -EINVAL : idx; +} + +/** + * vexpress_spc_set_performance - set current performance level of cluster + * + * @cluster: mpidr[15:8] bitfield describing cluster affinity level + * @freq: performance level to be programmed + * + * Returns: 0 on success + * < 0 on write error + */ +int vexpress_spc_set_performance(u32 cluster, u32 freq) +{ + int ret, perf, offset; + + if (!vexpress_spc_initialized() || (cluster >= MAX_CLUSTERS)) + return -EINVAL; + + offset = (cluster != info->a15_clusid) ? PERF_LVL_A7 : PERF_LVL_A15; + + perf = vexpress_spc_find_perf_index(cluster, freq); + + if (perf < 0 || perf >= MAX_OPPS) + return -EINVAL; + + ret = vexpress_config_write(perf_func, offset, perf); + + return ret; +} +EXPORT_SYMBOL_GPL(vexpress_spc_set_performance); + +static void vexpress_spc_set_wake_intr(u32 mask) +{ + writel_relaxed(mask & VEXPRESS_SPC_WAKE_INTR_MASK, + info->baseaddr + WAKE_INT_MASK); +} + +static inline void reg_bitmask(u32 *reg, u32 mask, bool set) +{ + if (set) + *reg |= mask; + else + *reg &= ~mask; +} + +/** + * vexpress_spc_set_global_wakeup_intr() + * + * Function to set/clear global wakeup IRQs. Not protected by locking since + * it might be used in code paths where normal cacheable locks are not + * working. Locking must be provided by the caller to ensure atomicity. + * + * @set: if true, global wake-up IRQs are set, if false they are cleared + */ +void vexpress_spc_set_global_wakeup_intr(bool set) +{ + u32 wake_int_mask_reg = 0; + + wake_int_mask_reg = readl_relaxed(info->baseaddr + WAKE_INT_MASK); + reg_bitmask(&wake_int_mask_reg, GBL_WAKEUP_INT_MSK, set); + vexpress_spc_set_wake_intr(wake_int_mask_reg); +} + +/** + * vexpress_spc_set_cpu_wakeup_irq() + * + * Function to set/clear per-CPU wake-up IRQs. Not protected by locking since + * it might be used in code paths where normal cacheable locks are not + * working. Locking must be provided by the caller to ensure atomicity. + * + * @cpu: mpidr[7:0] bitfield describing cpu affinity level + * @cluster: mpidr[15:8] bitfield describing cluster affinity level + * @set: if true, wake-up IRQs are set, if false they are cleared + */ +void vexpress_spc_set_cpu_wakeup_irq(u32 cpu, u32 cluster, bool set) +{ + u32 mask = 0; + u32 wake_int_mask_reg = 0; + + mask = 1 << cpu; + if (info->a15_clusid != cluster) + mask <<= 4; + + wake_int_mask_reg = readl_relaxed(info->baseaddr + WAKE_INT_MASK); + reg_bitmask(&wake_int_mask_reg, mask, set); + vexpress_spc_set_wake_intr(wake_int_mask_reg); +} + +/** + * vexpress_spc_powerdown_enable() + * + * Function to enable/disable cluster powerdown. Not protected by locking + * since it might be used in code paths where normal cacheable locks are not + * working. Locking must be provided by the caller to ensure atomicity. + * + * @cluster: mpidr[15:8] bitfield describing cluster affinity level + * @enable: if true enables powerdown, if false disables it + */ +void vexpress_spc_powerdown_enable(u32 cluster, bool enable) +{ + u32 pwdrn_reg = 0; + + if (cluster >= MAX_CLUSTERS) + return; + pwdrn_reg = cluster != info->a15_clusid ? A7_PWRDN_EN : A15_PWRDN_EN; + writel_relaxed(enable, info->baseaddr + pwdrn_reg); +} + +irqreturn_t vexpress_spc_irq_handler(int irq, void *data) +{ + int ret; + u32 status = readl_relaxed(info->baseaddr + PWC_STATUS); + + if (!(status & RESPONSE_MASK(info->cur_req_type))) + return IRQ_NONE; + + if ((status == STAT_COMPLETE(SYS_CFGCTRL_TYPE)) + && vexpress_spc_config_data) { + *vexpress_spc_config_data = + readl_relaxed(info->baseaddr + SYS_CFG_RDATA); + vexpress_spc_config_data = NULL; + } + + ret = STAT_COMPLETE(info->cur_req_type) ? 0 : -EIO; + info->cur_req_type = INVALID_TYPE; + vexpress_config_complete(vexpress_spc_config_bridge, ret); + return IRQ_HANDLED; +} + +/** + * 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; + u32 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)); +} + +/** + * vexpress_spc_populate_opps() - initialize opp tables from microcontroller + * + * @cluster: mpidr[15:8] bitfield describing cluster affinity level + * + * Return: 0 on success + * < 0 on error + */ +static int vexpress_spc_populate_opps(u32 cluster) +{ + u32 data = 0, ret, i, offset; + int mult_fact = __get_mult_factor(); + + if (WARN_ON_ONCE(cluster >= MAX_CLUSTERS)) + return -EINVAL; + + offset = cluster != info->a15_clusid ? OPP_A7_OFFSET : OPP_A15_OFFSET; + for (i = 0; i < MAX_OPPS; i++) { + ret = vexpress_config_read(opp_func, i + offset, &data); + if (!ret) + info->freqs[cluster][i] = (data & 0xFFFFF) * mult_fact; + else + break; + } + + info->freqs_cnt[cluster] = i; + return ret; +} + +/** + * vexpress_spc_get_freq_table() - Retrieve a pointer to the frequency + * table for a given cluster + * + * @cluster: mpidr[15:8] bitfield describing cluster affinity level + * @fptr: pointer to be initialized + * Return: operating points count on success + * -EINVAL on pointer error + */ +int vexpress_spc_get_freq_table(u32 cluster, u32 **fptr) +{ + if (WARN_ON_ONCE(!fptr || cluster >= MAX_CLUSTERS)) + return -EINVAL; + *fptr = info->freqs[cluster]; + return info->freqs_cnt[cluster]; +} +EXPORT_SYMBOL_GPL(vexpress_spc_get_freq_table); + +static void *vexpress_spc_func_get(struct device *dev, + struct device_node *node, const char *id) +{ + struct vexpress_spc_func *spc_func; + u32 func_device[2]; + int err = 0; + + spc_func = kzalloc(sizeof(*spc_func), GFP_KERNEL); + if (!spc_func) + return NULL; + + if (strcmp(id, "opp") == 0) { + spc_func->type = CONFIG_FUNC; + spc_func->function = OPP_FUNCTION; + spc_func->device = OPP_BASE_DEVICE; + } else if (strcmp(id, "perf") == 0) { + spc_func->type = PERF_FUNC; + } else if (node) { + of_node_get(node); + err = of_property_read_u32_array(node, + "arm,vexpress-sysreg,func", func_device, + ARRAY_SIZE(func_device)); + of_node_put(node); + spc_func->type = CONFIG_FUNC; + spc_func->function = func_device[0]; + spc_func->device = func_device[1]; + } + + if (WARN_ON(err)) { + kfree(spc_func); + return NULL; + } + + pr_debug("func 0x%p = 0x%x, %d %d\n", spc_func, + spc_func->function, + spc_func->device, + spc_func->type); + + return spc_func; +} + +static void vexpress_spc_func_put(void *func) +{ + kfree(func); +} + +static int vexpress_spc_func_exec(void *func, int offset, bool write, + u32 *data) +{ + struct vexpress_spc_func *spc_func = func; + u32 command; + + if (!data) + return -EINVAL; + /* + * Setting and retrieval of operating points is not part of + * DCC config interface. It was made to go through the same + * code path so that requests to the M3 can be serialized + * properly with config reads/writes through the common + * vexpress config interface + */ + switch (spc_func->type) { + case PERF_FUNC: + if (write) { + info->cur_req_type = (offset == PERF_LVL_A15) ? + A15_OPP_TYPE : A7_OPP_TYPE; + writel_relaxed(*data, info->baseaddr + offset); + return VEXPRESS_CONFIG_STATUS_WAIT; + } else { + *data = readl_relaxed(info->baseaddr + offset); + return VEXPRESS_CONFIG_STATUS_DONE; + } + case CONFIG_FUNC: + info->cur_req_type = SYS_CFGCTRL_TYPE; + + command = SYS_CFGCTRL_START; + command |= write ? SYS_CFGCTRL_WRITE : 0; + command |= SYS_CFGCTRL_FUNC(spc_func->function); + command |= SYS_CFGCTRL_DEVICE(spc_func->device + offset); + + pr_debug("command %x\n", command); + + if (!write) + vexpress_spc_config_data = data; + else + writel_relaxed(*data, info->baseaddr + SYS_CFG_WDATA); + writel_relaxed(command, info->baseaddr + SYS_CFGCTRL); + + return VEXPRESS_CONFIG_STATUS_WAIT; + default: + return -EINVAL; + } +} + +struct vexpress_config_bridge_info vexpress_spc_config_bridge_info = { + .name = "vexpress-spc", + .func_get = vexpress_spc_func_get, + .func_put = vexpress_spc_func_put, + .func_exec = vexpress_spc_func_exec, +}; + +static const struct of_device_id vexpress_spc_ids[] __initconst = { + { .compatible = "arm,vexpress-spc,v2p-ca15_a7" }, + { .compatible = "arm,vexpress-spc" }, + {}, +}; + +static int __init vexpress_spc_init(void) +{ + int ret; + struct device_node *node = of_find_matching_node(NULL, + vexpress_spc_ids); + + if (!node) + return -ENODEV; + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) { + pr_err("%s: unable to allocate mem\n", __func__); + return -ENOMEM; + } + info->cur_req_type = INVALID_TYPE; + + info->baseaddr = of_iomap(node, 0); + if (WARN_ON(!info->baseaddr)) { + ret = -ENXIO; + goto mem_free; + } + + info->irq = irq_of_parse_and_map(node, 0); + + if (WARN_ON(!info->irq)) { + ret = -ENXIO; + goto unmap; + } + + 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) { + pr_err("IRQ %d request failed\n", info->irq); + ret = -ENODEV; + goto unmap; + } + + info->a15_clusid = readl_relaxed(info->baseaddr + A15_CONF) & 0xf; + + vexpress_spc_config_bridge = vexpress_config_bridge_register( + node, &vexpress_spc_config_bridge_info); + + if (WARN_ON(!vexpress_spc_config_bridge)) { + ret = -ENODEV; + goto unmap; + } + + opp_func = vexpress_config_func_get(vexpress_spc_config_bridge, "opp"); + perf_func = + vexpress_config_func_get(vexpress_spc_config_bridge, "perf"); + + if (!opp_func || !perf_func) { + ret = -ENODEV; + goto unmap; + } + + if (vexpress_spc_populate_opps(0) || vexpress_spc_populate_opps(1)) { + if (info->irq) + free_irq(info->irq, info); + pr_err("failed to build OPP table\n"); + ret = -ENODEV; + goto unmap; + } + /* + * Multi-cluster systems may need this data when non-coherent, during + * cluster power-up/power-down. Make sure it reaches main memory: + */ + sync_cache_w(info); + sync_cache_w(&info); + pr_info("vexpress-spc loaded at %p\n", info->baseaddr); + return 0; + +unmap: + iounmap(info->baseaddr); + +mem_free: + kfree(info); + return ret; +} + +static bool __init __vexpress_spc_check_loaded(void); +/* + * Pointer spc_check_loaded is swapped after init hence it is safe + * to initialize it to a function in the __init section + */ +static bool (*spc_check_loaded)(void) __refdata = &__vexpress_spc_check_loaded; + +static bool __init __vexpress_spc_check_loaded(void) +{ + if (vexpress_spc_load_result == -EAGAIN) + vexpress_spc_load_result = vexpress_spc_init(); + spc_check_loaded = &vexpress_spc_initialized; + return vexpress_spc_initialized(); +} + +/* + * Function exported to manage early_initcall ordering. + * SPC code is needed very early in the boot process + * to bring CPUs out of reset and initialize power + * management back-end. After boot swap pointers to + * make the functionality check available to loadable + * modules, when early boot init functions have been + * already freed from kernel address space. + */ +bool vexpress_spc_check_loaded(void) +{ + return spc_check_loaded(); +} +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"); diff --git a/include/linux/vexpress.h b/include/linux/vexpress.h index 89152673a47..0d11c230907 100644 --- a/include/linux/vexpress.h +++ b/include/linux/vexpress.h @@ -129,4 +129,47 @@ 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_VEXPRESS_SPC +extern bool vexpress_spc_check_loaded(void); +extern void vexpress_spc_set_cpu_wakeup_irq(u32 cpu, u32 cluster, bool set); +extern void vexpress_spc_set_global_wakeup_intr(bool set); +extern int vexpress_spc_get_freq_table(u32 cluster, u32 **fptr); +extern int vexpress_spc_get_performance(u32 cluster, u32 *freq); +extern int vexpress_spc_set_performance(u32 cluster, u32 freq); +extern void vexpress_spc_write_resume_reg(u32 cluster, u32 cpu, u32 addr); +extern int vexpress_spc_get_nb_cpus(u32 cluster); +extern void vexpress_spc_powerdown_enable(u32 cluster, bool enable); +#else +static inline bool vexpress_spc_check_loaded(void) { return false; } +static inline void vexpress_spc_set_cpu_wakeup_irq(u32 cpu, u32 cluster, + bool set) { } +static inline void vexpress_spc_set_global_wakeup_intr(bool set) { } +static inline int vexpress_spc_get_freq_table(u32 cluster, u32 **fptr) +{ + return -ENODEV; +} +static inline int vexpress_spc_get_performance(u32 cluster, u32 *freq) +{ + return -ENODEV; +} +static inline int vexpress_spc_set_performance(u32 cluster, u32 freq) +{ + return -ENODEV; +} +static inline void vexpress_spc_write_resume_reg(u32 cluster, + u32 cpu, u32 addr) { } +static inline int vexpress_spc_get_nb_cpus(u32 cluster) { return -ENODEV; } +static inline void vexpress_spc_powerdown_enable(u32 cluster, bool enable) { } +#endif + #endif -- cgit v1.2.3 From 9ff6b9a38bf6e90f9c6aee94c747c656bea39756 Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Fri, 31 May 2013 11:20:41 +0100 Subject: ARM: vexpress: Add SPC node to TC2 device-tree Signed-off-by: Jon Medhurst --- 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 d2803be4e1a..a1481e41585 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,vexpress-spc,v2p-ca15_a7","arm,vexpress-spc"; + reg = <0 0x7fff0000 0 0x1000>; + interrupts = <0 95 4>; + }; + timer { compatible = "arm,armv7-timer"; interrupts = <1 13 0xf08>, -- cgit v1.2.3 From 88d295ae948710b6eec2de332ad16d61d2e6a9cf Mon Sep 17 00:00:00 2001 From: Achin Gupta Date: Sun, 16 Dec 2012 21:46:10 +0000 Subject: ARM: psci: add constants to specify affinity levels This patch defines constants to allow callers of the psci 'suspend' & 'off' calls specify supported affinity levels. Signed-off-by: Achin Gupta Signed-off-by: Liviu Dudau --- arch/arm/include/asm/psci.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/arch/arm/include/asm/psci.h b/arch/arm/include/asm/psci.h index ce0dbe7c162..78ce38ff19f 100644 --- a/arch/arm/include/asm/psci.h +++ b/arch/arm/include/asm/psci.h @@ -16,6 +16,10 @@ #define PSCI_POWER_STATE_TYPE_STANDBY 0 #define PSCI_POWER_STATE_TYPE_POWER_DOWN 1 +#define PSCI_POWER_STATE_AFFINITY_LEVEL0 0 +#define PSCI_POWER_STATE_AFFINITY_LEVEL1 1 +#define PSCI_POWER_STATE_AFFINITY_LEVEL2 2 +#define PSCI_POWER_STATE_AFFINITY_LEVEL3 3 struct psci_power_state { u16 id; -- cgit v1.2.3 From 4e57b6c98d568bb411e793cfd85930202bac2649 Mon Sep 17 00:00:00 2001 From: Achin Gupta Date: Sun, 10 Mar 2013 21:31:29 +0000 Subject: ARM: psci: convert psci '-EALREADYON' error code to linux '-EAGAIN' This patch adds a possible error code of the cpu_on psci api. It indicates that the cpu specified in the cpu_on call is up and running (e.g. the firmware still has not seen the preceding cpu_off call). Signed-off-by: Achin Gupta Signed-off-by: Liviu Dudau --- arch/arm/kernel/psci.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/arm/kernel/psci.c b/arch/arm/kernel/psci.c index 36531643cc2..5211f18cd2b 100644 --- a/arch/arm/kernel/psci.c +++ b/arch/arm/kernel/psci.c @@ -42,6 +42,7 @@ static u32 psci_function_id[PSCI_FN_MAX]; #define PSCI_RET_EOPNOTSUPP -1 #define PSCI_RET_EINVAL -2 #define PSCI_RET_EPERM -3 +#define PSCI_RET_EALREADYON -4 static int psci_to_linux_errno(int errno) { @@ -54,6 +55,8 @@ static int psci_to_linux_errno(int errno) return -EINVAL; case PSCI_RET_EPERM: return -EPERM; + case PSCI_RET_EALREADYON: + return -EAGAIN; }; return -EINVAL; -- cgit v1.2.3 From 25366fe14faa30941b28f3d724438d6ec9a7cf4d Mon Sep 17 00:00:00 2001 From: Achin Gupta Date: Sun, 16 Dec 2012 23:11:32 +0000 Subject: ARM: psci: add probe function to discover presence of a psci implementation This patch adds a probe function to check if the secure firmware has an implementation of the Power State Coordination Interface. 'bL_platform_power_ops' will be implemented by: a. a native backend when Linux runs in secure world b. a psci backend which relies on the secure firmware to implement the power ops presence of b. will be indicated by the psci device node in the device tree. The device node is expected to be populated by the secure firmware if it supports psci. If the native backend detects a psci node then it bails out allowing the psci backend to be registered. Also a dummy 'psci_probe' function is added for the case when psci support is not included. This prevents the build from breaking for tc2 and the rtsm platforms. Signed-off-by: Achin Gupta Signed-off-by: Liviu Dudau --- arch/arm/include/asm/psci.h | 8 ++++++++ arch/arm/kernel/psci.c | 13 +++++++++++++ 2 files changed, 21 insertions(+) diff --git a/arch/arm/include/asm/psci.h b/arch/arm/include/asm/psci.h index 78ce38ff19f..a079cbee427 100644 --- a/arch/arm/include/asm/psci.h +++ b/arch/arm/include/asm/psci.h @@ -37,4 +37,12 @@ struct psci_operations { extern struct psci_operations psci_ops; +#ifdef CONFIG_ARM_PSCI +extern int __init psci_probe(void); +#else +static inline int psci_probe(void) +{ + return -ENODEV; +} +#endif #endif /* __ASM_ARM_PSCI_H */ diff --git a/arch/arm/kernel/psci.c b/arch/arm/kernel/psci.c index 5211f18cd2b..03c10f65add 100644 --- a/arch/arm/kernel/psci.c +++ b/arch/arm/kernel/psci.c @@ -212,3 +212,16 @@ out_put_node: return 0; } early_initcall(psci_init); + +int __init psci_probe(void) +{ + struct device_node *np; + int ret = -ENODEV; + + np = of_find_matching_node(NULL, psci_of_match); + if (np) + ret = 0; + + of_node_put(np); + return ret; +} -- cgit v1.2.3 From b5fdc36b35130308bd62a18b4682b51708a7f378 Mon Sep 17 00:00:00 2001 From: Achin Gupta Date: Sun, 10 Mar 2013 22:04:29 +0000 Subject: ARM: psci: add cmdline option to enable use of psci This patch adds the 'psci' kernel command line option. Secure firmware cannot yet add a psci device node in the dt to indicate whether it supports psci or not. So in the current dt, the psci device node is present by default. The probe function will always indicate that the secure firmware implements psci irrespective of the address space linux runs in as the same device tree will be used in either case. Hence a kernel cmdline option is required to choose either the native or psci power api backend depending upon the address space linux is running in. Specifying 'psci=enable' in the cmdline will allow Linux running in the non-secure address space to use the same dt but use the psci backend instead of the native backend. It effectively overrides the presence of the native implementation by ensuring registration of the psci backend. Linux running in the secure address space will use the native backend for power management when 'psci=disable' in the cmdline (also the default value i.e. psci backend is disabled by default) or the psci node in the dt is absent. Signed-off-by: Achin Gupta Signed-off-by: Liviu Dudau --- arch/arm/kernel/psci.c | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/arch/arm/kernel/psci.c b/arch/arm/kernel/psci.c index 03c10f65add..1180801468d 100644 --- a/arch/arm/kernel/psci.c +++ b/arch/arm/kernel/psci.c @@ -17,6 +17,7 @@ #include #include +#include #include #include @@ -26,6 +27,11 @@ struct psci_operations psci_ops; +/* Type of psci support. Currently can only be enabled or disabled */ +#define PSCI_SUP_DISABLED 0 +#define PSCI_SUP_ENABLED 1 + +static unsigned int psci; static int (*invoke_psci_fn)(u32, u32, u32, u32); enum psci_function { @@ -167,6 +173,9 @@ static int __init psci_init(void) const char *method; u32 id; + if (psci == PSCI_SUP_DISABLED) + return 0; + np = of_find_matching_node(NULL, psci_of_match); if (!np) return 0; @@ -218,10 +227,27 @@ int __init psci_probe(void) struct device_node *np; int ret = -ENODEV; - np = of_find_matching_node(NULL, psci_of_match); - if (np) - ret = 0; + if (psci == PSCI_SUP_ENABLED) { + np = of_find_matching_node(NULL, psci_of_match); + if (np) + ret = 0; + } of_node_put(np); return ret; } + +static int __init early_psci(char *val) +{ + int ret = 0; + + if (strcmp(val, "enable") == 0) + psci = PSCI_SUP_ENABLED; + else if (strcmp(val, "disable") == 0) + psci = PSCI_SUP_DISABLED; + else + ret = -EINVAL; + + return ret; +} +early_param("psci", early_psci); -- cgit v1.2.3 From 983b26177be95457be52e0a001893b7de4e0d4f3 Mon Sep 17 00:00:00 2001 From: Rob Herring Date: Wed, 16 Jan 2013 17:57:33 -0600 Subject: ARM: introduce common set_auxcr/get_auxcr functions Move the private set_auxcr/get_auxcr functions from drivers/cpuidle/cpuidle-calxeda.c so they can be used across platforms. Signed-off-by: Rob Herring Cc: Russell King Signed-off-by: Nicolas Pitre Acked-by: Tony Lindgren Acked-by: Santosh Shilimkar Reviewed-by: Will Deacon --- arch/arm/include/asm/cp15.h | 14 ++++++++++++++ drivers/cpuidle/cpuidle-calxeda.c | 14 -------------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/arch/arm/include/asm/cp15.h b/arch/arm/include/asm/cp15.h index 1f3262e99d8..cedd3721318 100644 --- a/arch/arm/include/asm/cp15.h +++ b/arch/arm/include/asm/cp15.h @@ -61,6 +61,20 @@ static inline void set_cr(unsigned int val) isb(); } +static inline unsigned int get_auxcr(void) +{ + unsigned int val; + asm("mrc p15, 0, %0, c1, c0, 1 @ get AUXCR" : "=r" (val)); + return val; +} + +static inline void set_auxcr(unsigned int val) +{ + asm volatile("mcr p15, 0, %0, c1, c0, 1 @ set AUXCR" + : : "r" (val)); + isb(); +} + #ifndef CONFIG_SMP extern void adjust_cr(unsigned long mask, unsigned long set); #endif diff --git a/drivers/cpuidle/cpuidle-calxeda.c b/drivers/cpuidle/cpuidle-calxeda.c index 223379169cb..0e6e408c0a6 100644 --- a/drivers/cpuidle/cpuidle-calxeda.c +++ b/drivers/cpuidle/cpuidle-calxeda.c @@ -37,20 +37,6 @@ extern void highbank_set_cpu_jump(int cpu, void *jump_addr); extern void *scu_base_addr; -static inline unsigned int get_auxcr(void) -{ - unsigned int val; - asm("mrc p15, 0, %0, c1, c0, 1 @ get AUXCR" : "=r" (val) : : "cc"); - return val; -} - -static inline void set_auxcr(unsigned int val) -{ - asm volatile("mcr p15, 0, %0, c1, c0, 1 @ set AUXCR" - : : "r" (val) : "cc"); - isb(); -} - static noinline void calxeda_idle_restore(void) { set_cr(get_cr() | CR_C); -- cgit v1.2.3 From bd2bf7cdad1fac1918a7fa9e576ebe1a0c7605ca Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Wed, 2 May 2012 20:56:52 -0400 Subject: ARM: vexpress: introduce DCSCB support This adds basic CPU and cluster reset controls on RTSM for the A15x4-A7x4 model configuration using the Dual Cluster System Configuration Block (DCSCB). The cache coherency interconnect (CCI) is not handled yet. Signed-off-by: Nicolas Pitre Reviewed-by: Santosh Shilimkar Acked-by: Pawel Moll --- .../devicetree/bindings/arm/rtsm-dcscb.txt | 19 +++ arch/arm/mach-vexpress/Kconfig | 8 + arch/arm/mach-vexpress/Makefile | 1 + arch/arm/mach-vexpress/dcscb.c | 164 +++++++++++++++++++++ 4 files changed, 192 insertions(+) create mode 100644 Documentation/devicetree/bindings/arm/rtsm-dcscb.txt create mode 100644 arch/arm/mach-vexpress/dcscb.c diff --git a/Documentation/devicetree/bindings/arm/rtsm-dcscb.txt b/Documentation/devicetree/bindings/arm/rtsm-dcscb.txt new file mode 100644 index 00000000000..3b8fbf3c00c --- /dev/null +++ b/Documentation/devicetree/bindings/arm/rtsm-dcscb.txt @@ -0,0 +1,19 @@ +ARM Dual Cluster System Configuration Block +------------------------------------------- + +The Dual Cluster System Configuration Block (DCSCB) provides basic +functionality for controlling clocks, resets and configuration pins in +the Dual Cluster System implemented by the Real-Time System Model (RTSM). + +Required properties: + +- compatible : should be "arm,rtsm,dcscb" + +- reg : physical base address and the size of the registers window + +Example: + + dcscb@60000000 { + compatible = "arm,rtsm,dcscb"; + reg = <0x60000000 0x1000>; + }; diff --git a/arch/arm/mach-vexpress/Kconfig b/arch/arm/mach-vexpress/Kconfig index 5907e10c37f..2f46385c281 100644 --- a/arch/arm/mach-vexpress/Kconfig +++ b/arch/arm/mach-vexpress/Kconfig @@ -57,4 +57,12 @@ config ARCH_VEXPRESS_CORTEX_A5_A9_ERRATA config ARCH_VEXPRESS_CA9X4 bool "Versatile Express Cortex-A9x4 tile" +config ARCH_VEXPRESS_DCSCB + bool "Dual Cluster System Control Block (DCSCB) support" + depends on MCPM + help + Support for the Dual Cluster System Configuration Block (DCSCB). + This is needed to provide CPU and cluster power management + on RTSM implementing big.LITTLE. + endmenu diff --git a/arch/arm/mach-vexpress/Makefile b/arch/arm/mach-vexpress/Makefile index 42703e8b4d3..518519f57a5 100644 --- a/arch/arm/mach-vexpress/Makefile +++ b/arch/arm/mach-vexpress/Makefile @@ -6,5 +6,6 @@ ccflags-$(CONFIG_ARCH_MULTIPLATFORM) := -I$(srctree)/$(src)/include \ obj-y := v2m.o obj-$(CONFIG_ARCH_VEXPRESS_CA9X4) += ct-ca9x4.o +obj-$(CONFIG_ARCH_VEXPRESS_DCSCB) += dcscb.o obj-$(CONFIG_SMP) += platsmp.o obj-$(CONFIG_HOTPLUG_CPU) += hotplug.o diff --git a/arch/arm/mach-vexpress/dcscb.c b/arch/arm/mach-vexpress/dcscb.c new file mode 100644 index 00000000000..2ca4bbce530 --- /dev/null +++ b/arch/arm/mach-vexpress/dcscb.c @@ -0,0 +1,164 @@ +/* + * arch/arm/mach-vexpress/dcscb.c - Dual Cluster System Configuration Block + * + * Created by: Nicolas Pitre, May 2012 + * Copyright: (C) 2012-2013 Linaro Limited + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + + +#define RST_HOLD0 0x0 +#define RST_HOLD1 0x4 +#define SYS_SWRESET 0x8 +#define RST_STAT0 0xc +#define RST_STAT1 0x10 +#define EAG_CFG_R 0x20 +#define EAG_CFG_W 0x24 +#define KFC_CFG_R 0x28 +#define KFC_CFG_W 0x2c +#define DCS_CFG_R 0x30 + +/* + * We can't use regular spinlocks. In the switcher case, it is possible + * for an outbound CPU to call power_down() while its inbound counterpart + * is already live using the same logical CPU number which trips lockdep + * debugging. + */ +static arch_spinlock_t dcscb_lock = __ARCH_SPIN_LOCK_UNLOCKED; + +static void __iomem *dcscb_base; + +static int dcscb_power_up(unsigned int cpu, unsigned int cluster) +{ + unsigned int rst_hold, cpumask = (1 << cpu); + + pr_debug("%s: cpu %u cluster %u\n", __func__, cpu, cluster); + if (cpu >= 4 || cluster >= 2) + return -EINVAL; + + /* + * Since this is called with IRQs enabled, and no arch_spin_lock_irq + * variant exists, we need to disable IRQs manually here. + */ + local_irq_disable(); + arch_spin_lock(&dcscb_lock); + + rst_hold = readl_relaxed(dcscb_base + RST_HOLD0 + cluster * 4); + if (rst_hold & (1 << 8)) { + /* remove cluster reset and add individual CPU's reset */ + rst_hold &= ~(1 << 8); + rst_hold |= 0xf; + } + rst_hold &= ~(cpumask | (cpumask << 4)); + writel_relaxed(rst_hold, dcscb_base + RST_HOLD0 + cluster * 4); + + arch_spin_unlock(&dcscb_lock); + local_irq_enable(); + + return 0; +} + +static void dcscb_power_down(void) +{ + unsigned int mpidr, cpu, cluster, rst_hold, cpumask, last_man; + + mpidr = read_cpuid_mpidr(); + cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0); + cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1); + cpumask = (1 << cpu); + + pr_debug("%s: cpu %u cluster %u\n", __func__, cpu, cluster); + BUG_ON(cpu >= 4 || cluster >= 2); + + arch_spin_lock(&dcscb_lock); + rst_hold = readl_relaxed(dcscb_base + RST_HOLD0 + cluster * 4); + rst_hold |= cpumask; + if (((rst_hold | (rst_hold >> 4)) & 0xf) == 0xf) + rst_hold |= (1 << 8); + writel_relaxed(rst_hold, dcscb_base + RST_HOLD0 + cluster * 4); + arch_spin_unlock(&dcscb_lock); + last_man = (rst_hold & (1 << 8)); + + /* + * Now let's clean our L1 cache and shut ourself down. + * If we're the last CPU in this cluster then clean L2 too. + */ + + /* + * A15/A7 can hit in the cache with SCTLR.C=0, so we don't need + * a preliminary flush here for those CPUs. At least, that's + * the theory -- without the extra flush, Linux explodes on + * RTSM (to be investigated).. + */ + flush_cache_louis(); + set_cr(get_cr() & ~CR_C); + + if (!last_man) { + flush_cache_louis(); + } else { + flush_cache_all(); + outer_flush_all(); + } + + /* Disable local coherency by clearing the ACTLR "SMP" bit: */ + set_auxcr(get_auxcr() & ~(1 << 6)); + + /* Now we are prepared for power-down, do it: */ + dsb(); + wfi(); + + /* Not dead at this point? Let our caller cope. */ +} + +static const struct mcpm_platform_ops dcscb_power_ops = { + .power_up = dcscb_power_up, + .power_down = dcscb_power_down, +}; + +static int __init dcscb_init(void) +{ + struct device_node *node; + int ret; + + node = of_find_compatible_node(NULL, NULL, "arm,rtsm,dcscb"); + if (!node) + return -ENODEV; + dcscb_base = of_iomap(node, 0); + if (!dcscb_base) + return -EADDRNOTAVAIL; + + ret = mcpm_platform_register(&dcscb_power_ops); + if (ret) { + iounmap(dcscb_base); + return ret; + } + + pr_info("VExpress DCSCB support installed\n"); + + /* + * Future entries into the kernel can now go + * through the cluster entry vectors. + */ + vexpress_flags_set(virt_to_phys(mcpm_entry_point)); + + return 0; +} + +early_initcall(dcscb_init); -- cgit v1.2.3 From 29722e1e61a723f2c6e35e33fdcbdf9555db5a02 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Mon, 16 Jul 2012 22:07:10 -0400 Subject: ARM: vexpress/dcscb: add CPU use counts to the power up/down API implementation It is possible for a CPU to be told to power up before it managed to power itself down. Solve this race with a usage count to deal with this possibility as mandated by the MCPM API definition. Signed-off-by: nicolas Pitre Reviewed-by: Santosh Shilimkar Acked-by: Pawel Moll --- arch/arm/mach-vexpress/dcscb.c | 74 +++++++++++++++++++++++++++++++++--------- 1 file changed, 59 insertions(+), 15 deletions(-) diff --git a/arch/arm/mach-vexpress/dcscb.c b/arch/arm/mach-vexpress/dcscb.c index 2ca4bbce530..65dee781808 100644 --- a/arch/arm/mach-vexpress/dcscb.c +++ b/arch/arm/mach-vexpress/dcscb.c @@ -44,6 +44,7 @@ static arch_spinlock_t dcscb_lock = __ARCH_SPIN_LOCK_UNLOCKED; static void __iomem *dcscb_base; +static int dcscb_use_count[4][2]; static int dcscb_power_up(unsigned int cpu, unsigned int cluster) { @@ -60,14 +61,27 @@ static int dcscb_power_up(unsigned int cpu, unsigned int cluster) local_irq_disable(); arch_spin_lock(&dcscb_lock); - rst_hold = readl_relaxed(dcscb_base + RST_HOLD0 + cluster * 4); - if (rst_hold & (1 << 8)) { - /* remove cluster reset and add individual CPU's reset */ - rst_hold &= ~(1 << 8); - rst_hold |= 0xf; + dcscb_use_count[cpu][cluster]++; + if (dcscb_use_count[cpu][cluster] == 1) { + rst_hold = readl_relaxed(dcscb_base + RST_HOLD0 + cluster * 4); + if (rst_hold & (1 << 8)) { + /* remove cluster reset and add individual CPU's reset */ + rst_hold &= ~(1 << 8); + rst_hold |= 0xf; + } + rst_hold &= ~(cpumask | (cpumask << 4)); + writel_relaxed(rst_hold, dcscb_base + RST_HOLD0 + cluster * 4); + } else if (dcscb_use_count[cpu][cluster] != 2) { + /* + * The only possible values are: + * 0 = CPU down + * 1 = CPU (still) up + * 2 = CPU requested to be up before it had a chance + * to actually make itself down. + * Any other value is a bug. + */ + BUG(); } - rst_hold &= ~(cpumask | (cpumask << 4)); - writel_relaxed(rst_hold, dcscb_base + RST_HOLD0 + cluster * 4); arch_spin_unlock(&dcscb_lock); local_irq_enable(); @@ -77,7 +91,8 @@ static int dcscb_power_up(unsigned int cpu, unsigned int cluster) static void dcscb_power_down(void) { - unsigned int mpidr, cpu, cluster, rst_hold, cpumask, last_man; + unsigned int mpidr, cpu, cluster, rst_hold, cpumask; + bool last_man = false, skip_wfi = false; mpidr = read_cpuid_mpidr(); cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0); @@ -88,13 +103,26 @@ static void dcscb_power_down(void) BUG_ON(cpu >= 4 || cluster >= 2); arch_spin_lock(&dcscb_lock); - rst_hold = readl_relaxed(dcscb_base + RST_HOLD0 + cluster * 4); - rst_hold |= cpumask; - if (((rst_hold | (rst_hold >> 4)) & 0xf) == 0xf) - rst_hold |= (1 << 8); - writel_relaxed(rst_hold, dcscb_base + RST_HOLD0 + cluster * 4); + dcscb_use_count[cpu][cluster]--; + if (dcscb_use_count[cpu][cluster] == 0) { + rst_hold = readl_relaxed(dcscb_base + RST_HOLD0 + cluster * 4); + rst_hold |= cpumask; + if (((rst_hold | (rst_hold >> 4)) & 0xf) == 0xf) { + rst_hold |= (1 << 8); + last_man = true; + } + writel_relaxed(rst_hold, dcscb_base + RST_HOLD0 + cluster * 4); + } else if (dcscb_use_count[cpu][cluster] == 1) { + /* + * A power_up request went ahead of us. + * Even if we do not want to shut this CPU down, + * the caller expects a certain state as if the WFI + * was aborted. So let's continue with cache cleaning. + */ + skip_wfi = true; + } else + BUG(); arch_spin_unlock(&dcscb_lock); - last_man = (rst_hold & (1 << 8)); /* * Now let's clean our L1 cache and shut ourself down. @@ -122,7 +150,8 @@ static void dcscb_power_down(void) /* Now we are prepared for power-down, do it: */ dsb(); - wfi(); + if (!skip_wfi) + wfi(); /* Not dead at this point? Let our caller cope. */ } @@ -132,6 +161,19 @@ static const struct mcpm_platform_ops dcscb_power_ops = { .power_down = dcscb_power_down, }; +static void __init dcscb_usage_count_init(void) +{ + unsigned int mpidr, cpu, cluster; + + mpidr = read_cpuid_mpidr(); + cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0); + cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1); + + pr_debug("%s: cpu %u cluster %u\n", __func__, cpu, cluster); + BUG_ON(cpu >= 4 || cluster >= 2); + dcscb_use_count[cpu][cluster] = 1; +} + static int __init dcscb_init(void) { struct device_node *node; @@ -144,6 +186,8 @@ static int __init dcscb_init(void) if (!dcscb_base) return -EADDRNOTAVAIL; + dcscb_usage_count_init(); + ret = mcpm_platform_register(&dcscb_power_ops); if (ret) { iounmap(dcscb_base); -- cgit v1.2.3 From c621f114fd5fc19dfa54d3c03d504768b4a47de2 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Wed, 18 Jul 2012 16:41:16 -0400 Subject: ARM: vexpress/dcscb: do not hardcode number of CPUs per cluster If 4 CPUs are assumed, the A15x1-A7x1 model configuration would never shut down the initial cluster as the 0xf reset bit mask will never be observed. Let's construct this mask based on the provided information in the DCSCB config register for the number of CPUs per cluster. Signed-off-by: Nicolas Pitre Reviewed-by: Santosh Shilimkar Acked-by: Pawel Moll --- arch/arm/mach-vexpress/dcscb.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/arch/arm/mach-vexpress/dcscb.c b/arch/arm/mach-vexpress/dcscb.c index 65dee781808..44aa7b040e8 100644 --- a/arch/arm/mach-vexpress/dcscb.c +++ b/arch/arm/mach-vexpress/dcscb.c @@ -45,10 +45,12 @@ static arch_spinlock_t dcscb_lock = __ARCH_SPIN_LOCK_UNLOCKED; static void __iomem *dcscb_base; static int dcscb_use_count[4][2]; +static int dcscb_allcpus_mask[2]; static int dcscb_power_up(unsigned int cpu, unsigned int cluster) { unsigned int rst_hold, cpumask = (1 << cpu); + unsigned int all_mask = dcscb_allcpus_mask[cluster]; pr_debug("%s: cpu %u cluster %u\n", __func__, cpu, cluster); if (cpu >= 4 || cluster >= 2) @@ -67,7 +69,7 @@ static int dcscb_power_up(unsigned int cpu, unsigned int cluster) if (rst_hold & (1 << 8)) { /* remove cluster reset and add individual CPU's reset */ rst_hold &= ~(1 << 8); - rst_hold |= 0xf; + rst_hold |= all_mask; } rst_hold &= ~(cpumask | (cpumask << 4)); writel_relaxed(rst_hold, dcscb_base + RST_HOLD0 + cluster * 4); @@ -91,13 +93,14 @@ static int dcscb_power_up(unsigned int cpu, unsigned int cluster) static void dcscb_power_down(void) { - unsigned int mpidr, cpu, cluster, rst_hold, cpumask; + unsigned int mpidr, cpu, cluster, rst_hold, cpumask, all_mask; bool last_man = false, skip_wfi = false; mpidr = read_cpuid_mpidr(); cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0); cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1); cpumask = (1 << cpu); + all_mask = dcscb_allcpus_mask[cluster]; pr_debug("%s: cpu %u cluster %u\n", __func__, cpu, cluster); BUG_ON(cpu >= 4 || cluster >= 2); @@ -107,7 +110,7 @@ static void dcscb_power_down(void) if (dcscb_use_count[cpu][cluster] == 0) { rst_hold = readl_relaxed(dcscb_base + RST_HOLD0 + cluster * 4); rst_hold |= cpumask; - if (((rst_hold | (rst_hold >> 4)) & 0xf) == 0xf) { + if (((rst_hold | (rst_hold >> 4)) & all_mask) == all_mask) { rst_hold |= (1 << 8); last_man = true; } @@ -177,6 +180,7 @@ static void __init dcscb_usage_count_init(void) static int __init dcscb_init(void) { struct device_node *node; + unsigned int cfg; int ret; node = of_find_compatible_node(NULL, NULL, "arm,rtsm,dcscb"); @@ -185,7 +189,9 @@ static int __init dcscb_init(void) dcscb_base = of_iomap(node, 0); if (!dcscb_base) return -EADDRNOTAVAIL; - + cfg = readl_relaxed(dcscb_base + DCS_CFG_R); + dcscb_allcpus_mask[0] = (1 << (((cfg >> 16) >> (0 << 2)) & 0xf)) - 1; + dcscb_allcpus_mask[1] = (1 << (((cfg >> 16) >> (1 << 2)) & 0xf)) - 1; dcscb_usage_count_init(); ret = mcpm_platform_register(&dcscb_power_ops); -- cgit v1.2.3 From 3d2f3686f7cc6c8f8d458c4c21653960841ef4ee Mon Sep 17 00:00:00 2001 From: Dave Martin Date: Tue, 17 Jul 2012 14:25:44 +0100 Subject: ARM: vexpress/dcscb: handle platform coherency exit/setup and CCI Add the required code to properly handle race free platform coherency exit to the DCSCB power down method. The power_up_setup callback is used to enable the CCI interface for the cluster being brought up. This must be done in assembly before the kernel environment is entered. Thanks to Achin Gupta and Nicolas Pitre for their help and contributions. Signed-off-by: Dave Martin Signed-off-by: Nicolas Pitre Reviewed-by: Santosh Shilimkar Acked-by: Pawel Moll --- arch/arm/mach-vexpress/Kconfig | 1 + arch/arm/mach-vexpress/Makefile | 2 +- arch/arm/mach-vexpress/dcscb.c | 77 +++++++++++++++++++++++++++--------- arch/arm/mach-vexpress/dcscb_setup.S | 38 ++++++++++++++++++ 4 files changed, 98 insertions(+), 20 deletions(-) create mode 100644 arch/arm/mach-vexpress/dcscb_setup.S diff --git a/arch/arm/mach-vexpress/Kconfig b/arch/arm/mach-vexpress/Kconfig index 2f46385c281..b8bbabec631 100644 --- a/arch/arm/mach-vexpress/Kconfig +++ b/arch/arm/mach-vexpress/Kconfig @@ -60,6 +60,7 @@ config ARCH_VEXPRESS_CA9X4 config ARCH_VEXPRESS_DCSCB bool "Dual Cluster System Control Block (DCSCB) support" depends on MCPM + select ARM_CCI help Support for the Dual Cluster System Configuration Block (DCSCB). This is needed to provide CPU and cluster power management diff --git a/arch/arm/mach-vexpress/Makefile b/arch/arm/mach-vexpress/Makefile index 518519f57a5..48ba89a8149 100644 --- a/arch/arm/mach-vexpress/Makefile +++ b/arch/arm/mach-vexpress/Makefile @@ -6,6 +6,6 @@ ccflags-$(CONFIG_ARCH_MULTIPLATFORM) := -I$(srctree)/$(src)/include \ obj-y := v2m.o obj-$(CONFIG_ARCH_VEXPRESS_CA9X4) += ct-ca9x4.o -obj-$(CONFIG_ARCH_VEXPRESS_DCSCB) += dcscb.o +obj-$(CONFIG_ARCH_VEXPRESS_DCSCB) += dcscb.o dcscb_setup.o obj-$(CONFIG_SMP) += platsmp.o obj-$(CONFIG_HOTPLUG_CPU) += hotplug.o diff --git a/arch/arm/mach-vexpress/dcscb.c b/arch/arm/mach-vexpress/dcscb.c index 44aa7b040e8..16d57a8a9d5 100644 --- a/arch/arm/mach-vexpress/dcscb.c +++ b/arch/arm/mach-vexpress/dcscb.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -105,7 +106,10 @@ static void dcscb_power_down(void) pr_debug("%s: cpu %u cluster %u\n", __func__, cpu, cluster); BUG_ON(cpu >= 4 || cluster >= 2); + __mcpm_cpu_going_down(cpu, cluster); + arch_spin_lock(&dcscb_lock); + BUG_ON(__mcpm_cluster_state(cluster) != CLUSTER_UP); dcscb_use_count[cpu][cluster]--; if (dcscb_use_count[cpu][cluster] == 0) { rst_hold = readl_relaxed(dcscb_base + RST_HOLD0 + cluster * 4); @@ -125,31 +129,59 @@ static void dcscb_power_down(void) skip_wfi = true; } else BUG(); - arch_spin_unlock(&dcscb_lock); - - /* - * Now let's clean our L1 cache and shut ourself down. - * If we're the last CPU in this cluster then clean L2 too. - */ - /* - * A15/A7 can hit in the cache with SCTLR.C=0, so we don't need - * a preliminary flush here for those CPUs. At least, that's - * the theory -- without the extra flush, Linux explodes on - * RTSM (to be investigated).. - */ - flush_cache_louis(); - set_cr(get_cr() & ~CR_C); + if (last_man && __mcpm_outbound_enter_critical(cpu, cluster)) { + arch_spin_unlock(&dcscb_lock); - if (!last_man) { - flush_cache_louis(); - } else { + /* + * Flush all cache levels for this cluster. + * + * A15/A7 can hit in the cache with SCTLR.C=0, so we don't need + * a preliminary flush here for those CPUs. At least, that's + * the theory -- without the extra flush, Linux explodes on + * RTSM (to be investigated). + */ + flush_cache_all(); + set_cr(get_cr() & ~CR_C); flush_cache_all(); + + /* + * This is a harmless no-op. On platforms with a real + * outer cache this might either be needed or not, + * depending on where the outer cache sits. + */ outer_flush_all(); + + /* Disable local coherency by clearing the ACTLR "SMP" bit: */ + set_auxcr(get_auxcr() & ~(1 << 6)); + + /* + * Disable cluster-level coherency by masking + * incoming snoops and DVM messages: + */ + cci_disable_port_by_cpu(mpidr); + + __mcpm_outbound_leave_critical(cluster, CLUSTER_DOWN); + } else { + arch_spin_unlock(&dcscb_lock); + + /* + * Flush the local CPU cache. + * + * A15/A7 can hit in the cache with SCTLR.C=0, so we don't need + * a preliminary flush here for those CPUs. At least, that's + * the theory -- without the extra flush, Linux explodes on + * RTSM (to be investigated). + */ + flush_cache_louis(); + set_cr(get_cr() & ~CR_C); + flush_cache_louis(); + + /* Disable local coherency by clearing the ACTLR "SMP" bit: */ + set_auxcr(get_auxcr() & ~(1 << 6)); } - /* Disable local coherency by clearing the ACTLR "SMP" bit: */ - set_auxcr(get_auxcr() & ~(1 << 6)); + __mcpm_cpu_down(cpu, cluster); /* Now we are prepared for power-down, do it: */ dsb(); @@ -177,12 +209,17 @@ static void __init dcscb_usage_count_init(void) dcscb_use_count[cpu][cluster] = 1; } +extern void dcscb_power_up_setup(unsigned int affinity_level); + static int __init dcscb_init(void) { struct device_node *node; unsigned int cfg; int ret; + if (!cci_probed()) + return -ENODEV; + node = of_find_compatible_node(NULL, NULL, "arm,rtsm,dcscb"); if (!node) return -ENODEV; @@ -195,6 +232,8 @@ static int __init dcscb_init(void) dcscb_usage_count_init(); ret = mcpm_platform_register(&dcscb_power_ops); + if (!ret) + ret = mcpm_sync_init(dcscb_power_up_setup); if (ret) { iounmap(dcscb_base); return ret; diff --git a/arch/arm/mach-vexpress/dcscb_setup.S b/arch/arm/mach-vexpress/dcscb_setup.S new file mode 100644 index 00000000000..4bb7fbe0f62 --- /dev/null +++ b/arch/arm/mach-vexpress/dcscb_setup.S @@ -0,0 +1,38 @@ +/* + * arch/arm/include/asm/dcscb_setup.S + * + * Created by: Dave Martin, 2012-06-22 + * Copyright: (C) 2012-2013 Linaro Limited + * + * 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. + */ + +#include + + +ENTRY(dcscb_power_up_setup) + + cmp r0, #0 @ check affinity level + beq 2f + +/* + * Enable cluster-level coherency, in preparation for turning on the MMU. + * The ACTLR SMP bit does not need to be set here, because cpu_resume() + * already restores that. + * + * A15/A7 may not require explicit L2 invalidation on reset, dependent + * on hardware integration decisions. + * For now, this code assumes that L2 is either already invalidated, + * or invalidation is not required. + */ + + b cci_enable_port_for_self + +2: @ Implementation-specific local CPU setup operations should go here, + @ if any. In this case, there is nothing to do. + + bx lr + +ENDPROC(dcscb_power_up_setup) -- cgit v1.2.3 From 659eaba2ac2b87a46f5d6415230a30ae2e961bda Mon Sep 17 00:00:00 2001 From: Achin Gupta Date: Mon, 17 Dec 2012 00:15:00 +0000 Subject: ARM: vexpress: allow native pm ops backends to probe for psci suppport This patch allows the vexpress 'rtsm' native backend to probe the dt for presence of the psci backend. If present then the native implementation of the 'bL_platform_power_ops' is not used. Signed-off-by: Achin Gupta Signed-off-by: Liviu Dudau Signed-off-by: Jon Medhurst --- arch/arm/mach-vexpress/dcscb.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/arch/arm/mach-vexpress/dcscb.c b/arch/arm/mach-vexpress/dcscb.c index 16d57a8a9d5..948aec0890f 100644 --- a/arch/arm/mach-vexpress/dcscb.c +++ b/arch/arm/mach-vexpress/dcscb.c @@ -23,6 +23,7 @@ #include #include #include +#include #define RST_HOLD0 0x0 @@ -217,6 +218,12 @@ static int __init dcscb_init(void) unsigned int cfg; int ret; + ret = psci_probe(); + if (!ret) { + pr_debug("psci found. Aborting native init\n"); + return -ENODEV; + } + if (!cci_probed()) return -ENODEV; -- cgit v1.2.3 From 6821d4bd29c8d13206ea30b5dbc07d446b1d5018 Mon Sep 17 00:00:00 2001 From: Dave Martin Date: Wed, 20 Feb 2013 17:34:20 +0000 Subject: ARM: mcpm: Make all mcpm functions notrace The functions in mcpm_entry.c are mostly intended for use during scary cache and coherency disabling sequences, or do other things which confuse trace ... like powering a CPU down and not returning. Similarly for the backend code. For simplicity, this patch just makes whole files notrace. There should be more than enough traceable points on the paths to these functions, but we can be more fine-grained later if there is a need for it. Signed-off-by: Dave Martin --- arch/arm/mach-vexpress/Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm/mach-vexpress/Makefile b/arch/arm/mach-vexpress/Makefile index 48ba89a8149..c298f7c0799 100644 --- a/arch/arm/mach-vexpress/Makefile +++ b/arch/arm/mach-vexpress/Makefile @@ -7,5 +7,6 @@ ccflags-$(CONFIG_ARCH_MULTIPLATFORM) := -I$(srctree)/$(src)/include \ obj-y := v2m.o obj-$(CONFIG_ARCH_VEXPRESS_CA9X4) += ct-ca9x4.o obj-$(CONFIG_ARCH_VEXPRESS_DCSCB) += dcscb.o dcscb_setup.o +CFLAGS_REMOVE_dcscb.o = -pg obj-$(CONFIG_SMP) += platsmp.o obj-$(CONFIG_HOTPLUG_CPU) += hotplug.o -- cgit v1.2.3 From 5276597a467415e7d56034ba587925385f78662c Mon Sep 17 00:00:00 2001 From: Lorenzo Pieralisi Date: Fri, 4 May 2012 17:20:44 +0100 Subject: ARM: kernel: fix MPIDR cpu_{suspend}/{resume} usage The current version of cpu_{suspend}/{resume} relies on the 8 LSBs of the MPIDR register to index the context pointer saved and restored on CPU shutdown. This approach breaks as soon as platforms with populated MPIDR affinity levels 1 and 2 are deployed, since the MPIDR cannot be considered a linear index anymore. There are multiple solutions to this problem, each with pros and cons. This patch changes cpu_{suspend}/{resume} so that the CPU logical id is used to retrieve an index into the context pointers array. Performance is impacted on both save and restore paths. On save path the CPU logical id has to be retrieved from thread_info; since caches are on, the performance hit should be neglectable. In the resume code path the MMU is off and so are the caches. The patch adds a trivial for loop that polls the cpu_logical_map array scanning the present MPIDRs and retrieves the actual CPU logical index. Since everything runs out of strongly ordered memory the perfomance hit in the resume code path must be measured and thought over; it worsens as the number of CPUs increases since it is a linear search (but can be improved). On the up side, the logical index approach is by far the easiest solution in terms of coding and make dynamic changes to the cpu mapping trivial at run-time. Any change to the cpu_logical_map (ie in-kernel switcher) at run time must be cleaned from the caches since this data has to be retrieved with the MMU off, when caches are not searched. Tested on TC2 and fast models. --- arch/arm/kernel/sleep.S | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/arch/arm/kernel/sleep.S b/arch/arm/kernel/sleep.S index 987dcf33415..c8952daf410 100644 --- a/arch/arm/kernel/sleep.S +++ b/arch/arm/kernel/sleep.S @@ -4,6 +4,7 @@ #include #include #include +#include "entry-header.S" .text /* @@ -30,9 +31,8 @@ ENTRY(__cpu_suspend) mov r2, r5 @ virtual SP ldr r3, =sleep_save_sp #ifdef CONFIG_SMP - ALT_SMP(mrc p15, 0, lr, c0, c0, 5) - ALT_UP(mov lr, #0) - and lr, lr, #15 + get_thread_info r5 + ldr lr, [r5, #TI_CPU] @ cpu logical index add r3, r3, lr, lsl #2 #endif bl __cpu_suspend_save @@ -82,10 +82,13 @@ ENDPROC(cpu_resume_after_mmu) .align ENTRY(cpu_resume) #ifdef CONFIG_SMP + mov r1, #0 @ fall-back logical index for UP + ALT_SMP(mrc p15, 0, r0, c0, c0, 5) + ALT_UP_B(1f) + bfc r0, #24, #8 + bl cpu_logical_index @ return logical index in r1 +1: adr r0, sleep_save_sp - ALT_SMP(mrc p15, 0, r1, c0, c0, 5) - ALT_UP(mov r1, #0) - and r1, r1, #15 ldr r0, [r0, r1, lsl #2] @ stack phys addr #else ldr r0, sleep_save_sp @ stack phys addr @@ -102,3 +105,20 @@ sleep_save_sp: .rept CONFIG_NR_CPUS .long 0 @ preserve stack phys ptr here .endr + +#ifdef CONFIG_SMP +cpu_logical_index: + adr r3, cpu_map_ptr + ldr r2, [r3] + add r3, r3, r2 @ virt_to_phys(__cpu_logical_map) + mov r1, #0 +1: + ldr r2, [r3, r1, lsl #2] + cmp r2, r0 + moveq pc, lr + add r1, r1, #1 + b 1b + +cpu_map_ptr: + .long __cpu_logical_map - . +#endif -- cgit v1.2.3 From 59e1c2d0f95afdd94a05aeca24d70da87d8a500d Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Wed, 29 Aug 2012 09:16:44 +0100 Subject: ARM: kernel: Fix compilation of sleep.S on ARMv6 The patch "ARM: kernel: fix MPIDR cpu_{suspend}/{resume} usage" uses the BFC assembler instruction but this isn't available on ARMv6 CPUs, which breaks compilation when building kernels which support both SMP and ARMv6, e.g. omap2plus_defconifg. Fix this by using a BIC instruction instead. Signed-off-by: Jon Medhurst --- arch/arm/kernel/sleep.S | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm/kernel/sleep.S b/arch/arm/kernel/sleep.S index c8952daf410..b5c1e636ed8 100644 --- a/arch/arm/kernel/sleep.S +++ b/arch/arm/kernel/sleep.S @@ -85,7 +85,7 @@ ENTRY(cpu_resume) mov r1, #0 @ fall-back logical index for UP ALT_SMP(mrc p15, 0, r0, c0, c0, 5) ALT_UP_B(1f) - bfc r0, #24, #8 + bic r0, #0xff000000 bl cpu_logical_index @ return logical index in r1 1: adr r0, sleep_save_sp -- cgit v1.2.3 From 88d444dfb9e5776c0234e37eeea5bf1a37f8a71f Mon Sep 17 00:00:00 2001 From: Lorenzo Pieralisi Date: Fri, 13 Jul 2012 12:06:26 +0100 Subject: ARM: vexpress: add TC2 CPU idle PM TC2 test-chip integrates power management circuitry and firmware that allows to remove voltage from both (A7 and A15) clusters when they are idle or more generically when the system is forced into shutdown mode. All CPUs in a cluster share the same voltage source so they cannot be shutdown independently. In order to take advantage of TC2 power management capabilities this patch implements a multi-cluster aware CPU idle implementation. It is based on coupled C-state concept provided by this code: http://lists.infradead.org/pipermail/linux-arm-kernel/2012-April/097084.html CPUs that are part of the same cluster are coupled using the mask provided by the MPIDR at boot. Once all CPUs hit the coupled barrier the primary CPU in the cluster (the one with MPIDR[7:0] == 0) waits for secondaries to clean their L1 and enter wfi. Then it cleans all cache levels, exits cluster coherency and starts the procedure to shutdown the respective cluster. All wake-up IRQ sources are enabled by default. Deep shutdown states for clusters are not enabled by default. To enabled them: A15 cluster echo 0 > /sys/kernel/debug/idle_debug/enable_idle A7 cluster echo 1 > /sys/kernel/debug/idle_debug/enable_idle Tested thoroughly using lookbusy to modulate system load and trigger idle states entry/exit. --- arch/arm/mach-vexpress/Kconfig | 14 ++ arch/arm/mach-vexpress/Makefile | 1 + arch/arm/mach-vexpress/cpuidle-tc2.c | 288 +++++++++++++++++++++++++++++++++++ arch/arm/mach-vexpress/hotplug-asm.S | 28 ++++ arch/arm/mach-vexpress/tc2-sleep.S | 76 +++++++++ 5 files changed, 407 insertions(+) create mode 100644 arch/arm/mach-vexpress/cpuidle-tc2.c create mode 100644 arch/arm/mach-vexpress/hotplug-asm.S create mode 100644 arch/arm/mach-vexpress/tc2-sleep.S diff --git a/arch/arm/mach-vexpress/Kconfig b/arch/arm/mach-vexpress/Kconfig index 5907e10c37f..6733f471931 100644 --- a/arch/arm/mach-vexpress/Kconfig +++ b/arch/arm/mach-vexpress/Kconfig @@ -54,6 +54,20 @@ config ARCH_VEXPRESS_CORTEX_A5_A9_ERRATA build a working kernel, you must also enable relevant core tile support or Flattened Device Tree based support options. +config ARCH_VEXPRESS_TC2_PM + bool "Power Management Support for TC2 test-chip (EXPERIMENTAL)" + depends on CPU_IDLE && PM + select ARM_CPU_SUSPEND + select ARCH_NEEDS_CPU_IDLE_COUPLED + select ARM_SPC + select ARM_CCI + help + Provides code that enables CPU idle power management on the + TC2 testchip. It enables the CPU idle driver so that the kernel + can enter cluster power down states provided by the power + controller. Code is built on top of coupled C-state idle code + since all CPUs need to be idle to enter cluster shutdown. + config ARCH_VEXPRESS_CA9X4 bool "Versatile Express Cortex-A9x4 tile" diff --git a/arch/arm/mach-vexpress/Makefile b/arch/arm/mach-vexpress/Makefile index 42703e8b4d3..1f57882b46c 100644 --- a/arch/arm/mach-vexpress/Makefile +++ b/arch/arm/mach-vexpress/Makefile @@ -8,3 +8,4 @@ obj-y := v2m.o obj-$(CONFIG_ARCH_VEXPRESS_CA9X4) += ct-ca9x4.o obj-$(CONFIG_SMP) += platsmp.o obj-$(CONFIG_HOTPLUG_CPU) += hotplug.o +obj-$(CONFIG_ARCH_VEXPRESS_TC2_PM) += cpuidle-tc2.o hotplug-asm.o tc2-sleep.o diff --git a/arch/arm/mach-vexpress/cpuidle-tc2.c b/arch/arm/mach-vexpress/cpuidle-tc2.c new file mode 100644 index 00000000000..de66243a7ef --- /dev/null +++ b/arch/arm/mach-vexpress/cpuidle-tc2.c @@ -0,0 +1,288 @@ +/* + * TC2 CPU idle driver. + * + * Copyright (C) 2012 ARM Ltd. + * Author: 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +static int tc2_cpuidle_simple_enter(struct cpuidle_device *dev, + struct cpuidle_driver *drv, int index) +{ + ktime_t time_start, time_end; + s64 diff; + + time_start = ktime_get(); + + cpu_do_idle(); + + time_end = ktime_get(); + + local_irq_enable(); + + diff = ktime_to_us(ktime_sub(time_end, time_start)); + if (diff > INT_MAX) + diff = INT_MAX; + + dev->last_residency = (int) diff; + + return index; +} + +static int tc2_enter_coupled(struct cpuidle_device *dev, + struct cpuidle_driver *drv, int idx); + +static struct cpuidle_state tc2_cpuidle_set[] __initdata = { + [0] = { + .enter = tc2_cpuidle_simple_enter, + .exit_latency = 1, + .target_residency = 1, + .power_usage = UINT_MAX, + .flags = CPUIDLE_FLAG_TIME_VALID, + .name = "WFI", + .desc = "ARM WFI", + }, + [1] = { + .enter = tc2_enter_coupled, + .exit_latency = 300, + .target_residency = 1000, + .flags = CPUIDLE_FLAG_TIME_VALID | + CPUIDLE_FLAG_COUPLED, + .name = "C1", + .desc = "ARM power down", + }, +}; + +struct cpuidle_driver tc2_idle_driver = { + .name = "tc2_idle", + .owner = THIS_MODULE, + .safe_state_index = 0 +}; + +static DEFINE_PER_CPU(struct cpuidle_device, tc2_idle_dev); + +#define NR_CLUSTERS 2 +static cpumask_t cluster_mask = CPU_MASK_NONE; + +extern void disable_clean_inv_dcache(int); +static atomic_t abort_barrier[NR_CLUSTERS]; + +extern void tc2_cpu_resume(void); +extern void disable_snoops(void); + +static int notrace tc2_coupled_finisher(unsigned long arg) +{ + unsigned int mpidr = read_cpuid_mpidr(); + unsigned int cpu = smp_processor_id(); + unsigned int cluster = (mpidr >> 8) & 0xf; + unsigned int weight = cpumask_weight(topology_core_cpumask(cpu)); + u8 wfi_weight = 0; + + cpuidle_coupled_parallel_barrier((struct cpuidle_device *)arg, + &abort_barrier[cluster]); + if (mpidr & 0xf) { + disable_clean_inv_dcache(0); + wfi(); + /* not reached */ + } + + while (wfi_weight != (weight - 1)) { + wfi_weight = vexpress_spc_wfi_cpustat(cluster); + wfi_weight = hweight8(wfi_weight); + } + + vexpress_spc_powerdown_enable(cluster, 1); + disable_clean_inv_dcache(1); + disable_cci(cluster); + disable_snoops(); + return 1; +} + +/* + * tc2_enter_coupled - Programs CPU to enter the specified state + * @dev: cpuidle device + * @drv: The target state to be programmed + * @idx: state index + * + * Called from the CPUidle framework to program the device to the + * specified target state selected by the governor. + */ +static int tc2_enter_coupled(struct cpuidle_device *dev, + struct cpuidle_driver *drv, int idx) +{ + struct timespec ts_preidle, ts_postidle, ts_idle; + int ret; + int cluster = (read_cpuid_mpidr() >> 8) & 0xf; + /* Used to keep track of the total time in idle */ + getnstimeofday(&ts_preidle); + + if (!cpu_isset(cluster, cluster_mask)) { + cpuidle_coupled_parallel_barrier(dev, + &abort_barrier[cluster]); + goto shallow_out; + } + + BUG_ON(!irqs_disabled()); + + cpu_pm_enter(); + + clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, &dev->cpu); + + ret = cpu_suspend((unsigned long) dev, tc2_coupled_finisher); + + if (ret) + BUG(); + + clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &dev->cpu); + + cpu_pm_exit(); + +shallow_out: + getnstimeofday(&ts_postidle); + ts_idle = timespec_sub(ts_postidle, ts_preidle); + + dev->last_residency = ts_idle.tv_nsec / NSEC_PER_USEC + + ts_idle.tv_sec * USEC_PER_SEC; + return idx; +} + +static int idle_mask_show(struct seq_file *f, void *p) +{ + char buf[256]; + bitmap_scnlistprintf(buf, 256, cpumask_bits(&cluster_mask), + NR_CLUSTERS); + + seq_printf(f, "%s\n", buf); + + return 0; +} + +static int idle_mask_open(struct inode *inode, struct file *file) +{ + return single_open(file, idle_mask_show, inode->i_private); +} + +static const struct file_operations cpuidle_fops = { + .open = idle_mask_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int idle_debug_set(void *data, u64 val) +{ + if (val >= (unsigned)NR_CLUSTERS && val != 0xff) { + pr_warning("Wrong parameter passed\n"); + return -EINVAL; + } + cpuidle_pause_and_lock(); + if (val == 0xff) + cpumask_clear(&cluster_mask); + else + cpumask_set_cpu(val, &cluster_mask); + + cpuidle_resume_and_unlock(); + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(idle_debug_fops, NULL, idle_debug_set, "%llu\n"); + +/* + * tc2_idle_init + * + * Registers the TC2 specific cpuidle driver with the cpuidle + * framework with the valid set of states. + */ +int __init tc2_idle_init(void) +{ + struct cpuidle_device *dev; + int i, cpu_id; + struct dentry *idle_debug, *file_debug; + struct cpuidle_driver *drv = &tc2_idle_driver; + + drv->state_count = (sizeof(tc2_cpuidle_set) / + sizeof(struct cpuidle_state)); + + for (i = 0; i < drv->state_count; i++) { + memcpy(&drv->states[i], &tc2_cpuidle_set[i], + sizeof(struct cpuidle_state)); + } + + cpuidle_register_driver(drv); + + for_each_cpu(cpu_id, cpu_online_mask) { + pr_err("CPUidle for CPU%d registered\n", cpu_id); + dev = &per_cpu(tc2_idle_dev, cpu_id); + dev->cpu = cpu_id; + dev->safe_state_index = 0; + + cpumask_copy(&dev->coupled_cpus, + topology_core_cpumask(cpu_id)); + dev->state_count = drv->state_count; + + if (cpuidle_register_device(dev)) { + printk(KERN_ERR "%s: Cpuidle register device failed\n", + __func__); + return -EIO; + } + } + + idle_debug = debugfs_create_dir("idle_debug", NULL); + + if (IS_ERR_OR_NULL(idle_debug)) { + printk(KERN_INFO "Error in creating idle debugfs directory\n"); + return 0; + } + + file_debug = debugfs_create_file("enable_idle", S_IRUGO | S_IWGRP, + idle_debug, NULL, &idle_debug_fops); + + if (IS_ERR_OR_NULL(file_debug)) { + printk(KERN_INFO "Error in creating enable_idle file\n"); + return 0; + } + + file_debug = debugfs_create_file("enable_mask", S_IRUGO | S_IWGRP, + idle_debug, NULL, &cpuidle_fops); + + if (IS_ERR_OR_NULL(file_debug)) + printk(KERN_INFO "Error in creating enable_mask file\n"); + + /* enable all wake-up IRQs by default */ + vexpress_spc_set_wake_intr(0x7ff); + vexpress_flags_set(virt_to_phys(tc2_cpu_resume)); + + /* + * Enable idle by default for all possible clusters. + * This must be done after all other setup to prevent the + * possibility of clusters being powered down before they + * are fully configured. + */ + for (i = 0; i < NR_CLUSTERS; i++) + cpumask_set_cpu(i, &cluster_mask); + + return 0; +} + +late_initcall(tc2_idle_init); diff --git a/arch/arm/mach-vexpress/hotplug-asm.S b/arch/arm/mach-vexpress/hotplug-asm.S new file mode 100644 index 00000000000..f63472edcc4 --- /dev/null +++ b/arch/arm/mach-vexpress/hotplug-asm.S @@ -0,0 +1,28 @@ +#include +#include + + .text +ENTRY(disable_clean_inv_dcache) + ARM( stmfd sp!, {r4-r5, r7, r9-r11, lr} ) + THUMB( stmfd sp!, {r4-r7, r9-r11, lr} ) + + mrc p15, 0, r3, c1, c0, 0 + bic r3, #4 @ clear C bit + mcr p15, 0, r3, c1, c0, 0 + dsb + isb + mov r12, r0 + cmp r12, #0 + bleq v7_flush_dcache_louis + cmp r12, #0 + blne v7_flush_dcache_all + clrex + mrc p15, 0, r3, c1, c0, 1 + bic r3, #0x40 @ clear SMP bit + mcr p15, 0, r3, c1, c0, 1 + isb + dsb + ARM( ldmfd sp!, {r4-r5, r7, r9-r11, lr} ) + THUMB( ldmfd sp!, {r4-r7, r9-r11, lr} ) + mov pc, lr +ENDPROC(disable_clean_inv_dcache) diff --git a/arch/arm/mach-vexpress/tc2-sleep.S b/arch/arm/mach-vexpress/tc2-sleep.S new file mode 100644 index 00000000000..9bf8348fcc8 --- /dev/null +++ b/arch/arm/mach-vexpress/tc2-sleep.S @@ -0,0 +1,76 @@ +#include + +#define SPC_PHYS_BASE 0x7FFF0000 +#define A15_CONF 0x400 + +ENTRY(tc2_cpu_resume) + mrc p15, 0, r0, c0, c0, 5 + ands r0, r0, #0xff00 + ldr r1, =SPC_PHYS_BASE + mov r2, #A15_CONF + add r1, r1, r2 + ldr r1, [r1] + and r1, r1, #0x7 + cmp r1, r0, lsr #8 + adr r0, value + addne r0, r0, #16 + ldmia r0, {r1, r2, r3, r4} @ CCI address, SCC snoop control & val + mvn r3, r3 @ undo actions done at shutdown + ldr r0, [r2] + and r5, r0, r3 + str r5, [r2] + mov r0, #3 @ enable CCI for the cluster + str r0, [r1] + adr r1, cci_ctrl + ldr r1, [r1] +loop: + ldr r0, [r1] + ands r0, r0, #1 + bne loop + mov r0, #0 @ disable power down enable + str r0, [r4] + b cpu_resume +ENDPROC(tc2_cpu_resume) + +ENTRY(disable_snoops) + mrc p15, 0, r0, c0, c0, 5 + ands r0, r0, #0xff00 + ldr r1, scc_ptr + ldr r1, [r1] + mov r2, #A15_CONF + add r1, r1, r2 + ldr r1, [r1] + and r1, r1, #0x7 + cmp r1, r0, lsr #8 + adr r0, vvalue + addne r0, r0, #8 + ldmia r0, {r2, r3} @ CCI address, SCC snoop control & val + ldr r1, scc_ptr + ldr r1, [r1] + add r2, r1, r2 + ldr r0, [r2] + orr r0, r0, r3 + dsb + isb + str r0, [r2] + wfi +ENDPROC(disable_snoops) + +cci_ctrl: + .long 0x2c09000c +value: + .long 0x2c094000 + .long 0x7fff0404 + .long 0x180 + .long 0x7fff0b30 + .long 0x2c095000 + .long 0x7fff0504 + .long 0x2000 + .long 0x7fff0b34 +vvalue: + .long 0x404 + .long 0x180 + .long 0x504 + .long 0x2000 +scc_ptr: + .long vscc -- cgit v1.2.3 From e7fd319fd055450dce5ff4f07fa2012f103e9413 Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Wed, 25 Jul 2012 15:13:46 +0100 Subject: ARM: vexpress: Make cpuidle check for presence of SPC driver The cpuidle code requires SPC hardware, so check for its presence before initialising. This enables the cpuidle code to safely exist in kernels run on hardware without SPC support. Signed-off-by: Jon Medhurst --- arch/arm/mach-vexpress/cpuidle-tc2.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/arch/arm/mach-vexpress/cpuidle-tc2.c b/arch/arm/mach-vexpress/cpuidle-tc2.c index de66243a7ef..3b73d4a5dc5 100644 --- a/arch/arm/mach-vexpress/cpuidle-tc2.c +++ b/arch/arm/mach-vexpress/cpuidle-tc2.c @@ -221,6 +221,11 @@ int __init tc2_idle_init(void) struct dentry *idle_debug, *file_debug; struct cpuidle_driver *drv = &tc2_idle_driver; + if (!vexpress_spc_check_loaded()) { + pr_info("TC2 CPUidle not registered because no SPC found\n"); + return -ENODEV; + } + drv->state_count = (sizeof(tc2_cpuidle_set) / sizeof(struct cpuidle_state)); -- cgit v1.2.3 From f8c889662f07604470441725078ec3f92b7977d2 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Wed, 13 Jun 2012 09:19:05 -0400 Subject: ARM: b.L: assume aliasing I-cache To deal with the I-cache discrepancy between Cortex-A15 and Cortex-A7, let's assume aliasing I-cache in both cases. Note: this might need to be refined i.e. detect a big.LITTLE system somehow by probing all CPUs not only the boot one. Signed-off-by: Nicolas Pitre --- arch/arm/kernel/setup.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/arch/arm/kernel/setup.c b/arch/arm/kernel/setup.c index b4b1d397592..b79d8f74e5b 100644 --- a/arch/arm/kernel/setup.c +++ b/arch/arm/kernel/setup.c @@ -261,6 +261,19 @@ static int cpu_has_aliasing_icache(unsigned int arch) int aliasing_icache; unsigned int id_reg, num_sets, line_size; +#ifdef CONFIG_BIG_LITTLE + /* + * We expect a combination of Cortex-A15 and Cortex-A7 cores. + * A7 = VIPT aliasing I-cache + * A15 = PIPT (non-aliasing) I-cache + * To cater for this discrepancy, let's assume aliasing I-cache + * all the time. This means unneeded extra work on the A15 but + * only ptrace is affected which is not performance critical. + */ + if ((read_cpuid_id() & 0xff0ffff0) == 0x410fc0f0) + return 1; +#endif + /* PIPT caches never alias. */ if (icache_is_pipt()) return 0; -- cgit v1.2.3 From 81543a9102e6b4bcc143271dcef798160c32fae3 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Fri, 19 Oct 2012 20:48:50 -0400 Subject: ARM: TC2: basic PM support Signed-off-by: Nicolas Pitre --- arch/arm/mach-vexpress/Kconfig | 8 ++ arch/arm/mach-vexpress/Makefile | 1 + arch/arm/mach-vexpress/tc2_pm.c | 234 ++++++++++++++++++++++++++++++++++ arch/arm/mach-vexpress/tc2_pm_setup.S | 88 +++++++++++++ 4 files changed, 331 insertions(+) create mode 100644 arch/arm/mach-vexpress/tc2_pm.c create mode 100644 arch/arm/mach-vexpress/tc2_pm_setup.S diff --git a/arch/arm/mach-vexpress/Kconfig b/arch/arm/mach-vexpress/Kconfig index 6733f471931..6e432b2c90c 100644 --- a/arch/arm/mach-vexpress/Kconfig +++ b/arch/arm/mach-vexpress/Kconfig @@ -71,4 +71,12 @@ config ARCH_VEXPRESS_TC2_PM config ARCH_VEXPRESS_CA9X4 bool "Versatile Express Cortex-A9x4 tile" +config ARCH_VEXPRESS_TC2 + bool "TC2 cluster management" + depends on MCPM + select ARM_SPC + select ARM_CCI + help + Support for CPU and cluster power management on TC2. + endmenu diff --git a/arch/arm/mach-vexpress/Makefile b/arch/arm/mach-vexpress/Makefile index 1f57882b46c..9650cc4eec1 100644 --- a/arch/arm/mach-vexpress/Makefile +++ b/arch/arm/mach-vexpress/Makefile @@ -6,6 +6,7 @@ ccflags-$(CONFIG_ARCH_MULTIPLATFORM) := -I$(srctree)/$(src)/include \ obj-y := v2m.o obj-$(CONFIG_ARCH_VEXPRESS_CA9X4) += ct-ca9x4.o +obj-$(CONFIG_ARCH_VEXPRESS_TC2) += tc2_pm.o tc2_pm_setup.o obj-$(CONFIG_SMP) += platsmp.o obj-$(CONFIG_HOTPLUG_CPU) += hotplug.o obj-$(CONFIG_ARCH_VEXPRESS_TC2_PM) += cpuidle-tc2.o hotplug-asm.o tc2-sleep.o diff --git a/arch/arm/mach-vexpress/tc2_pm.c b/arch/arm/mach-vexpress/tc2_pm.c new file mode 100644 index 00000000000..4be2d73cb38 --- /dev/null +++ b/arch/arm/mach-vexpress/tc2_pm.c @@ -0,0 +1,234 @@ +/* + * arch/arm/mach-vexpress/tc2_pm.c - TC2 power management support + * + * Created by: Nicolas Pitre, October 2012 + * Copyright: (C) 2012 Linaro Limited + * + * Some portions of this file were originally written by Achin Gupta + * Copyright: (C) 2012 ARM Limited + * + * 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. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include + +/* + * We can't use regular spinlocks. In the switcher case, it is possible + * for an outbound CPU to call power_down() after its inbound counterpart + * is already live using the same logical CPU number which trips lockdep + * debugging. + */ +static arch_spinlock_t tc2_pm_lock = __ARCH_SPIN_LOCK_UNLOCKED; + +static int tc2_pm_use_count[3][2]; + +static int tc2_pm_power_up(unsigned int cpu, unsigned int cluster) +{ + pr_debug("%s: cpu %u cluster %u\n", __func__, cpu, cluster); + if (cluster >= 2 || cpu >= vexpress_spc_get_nb_cpus(cluster)) + return -EINVAL; + + /* + * Since this is called with IRQs enabled, and no arch_spin_lock_irq + * variant exists, we need to disable IRQs manually here. + */ + local_irq_disable(); + arch_spin_lock(&tc2_pm_lock); + + if (!tc2_pm_use_count[0][cluster] && + !tc2_pm_use_count[1][cluster] && + !tc2_pm_use_count[2][cluster]) + vexpress_spc_powerdown_enable(cluster, 0); + + tc2_pm_use_count[cpu][cluster]++; + if (tc2_pm_use_count[cpu][cluster] == 1) { + vexpress_spc_write_bxaddr_reg(cluster, cpu, + virt_to_phys(mcpm_entry_point)); + vexpress_spc_set_cpu_wakeup_irq(cpu, cluster, 1); + } else if (tc2_pm_use_count[cpu][cluster] != 2) { + /* + * The only possible values are: + * 0 = CPU down + * 1 = CPU (still) up + * 2 = CPU requested to be up before it had a chance + * to actually make itself down. + * Any other value is a bug. + */ + BUG(); + } + + arch_spin_unlock(&tc2_pm_lock); + local_irq_enable(); + + return 0; +} + +static void tc2_pm_power_down(void) +{ + unsigned int mpidr, cpu, cluster; + bool last_man = false, skip_wfi = false; + + mpidr = read_cpuid_mpidr(); + cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0); + cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1); + + pr_debug("%s: cpu %u cluster %u\n", __func__, cpu, cluster); + BUG_ON(cluster >= 2 || cpu >= vexpress_spc_get_nb_cpus(cluster)); + + __mcpm_cpu_going_down(cpu, cluster); + + arch_spin_lock(&tc2_pm_lock); + BUG_ON(__mcpm_cluster_state(cluster) != CLUSTER_UP); + tc2_pm_use_count[cpu][cluster]--; + if (tc2_pm_use_count[cpu][cluster] == 0) { + vexpress_spc_set_cpu_wakeup_irq(cpu, cluster, 1); + if (!tc2_pm_use_count[0][cluster] && + !tc2_pm_use_count[1][cluster] && + !tc2_pm_use_count[2][cluster]) { + vexpress_spc_powerdown_enable(cluster, 1); + vexpress_spc_set_global_wakeup_intr(1); + last_man = true; + } + } else if (tc2_pm_use_count[cpu][cluster] == 1) { + /* + * A power_up request went ahead of us. + * Even if we do not want to shut this CPU down, + * the caller expects a certain state as if the WFI + * was aborted. So let's continue with cache cleaning. + */ + skip_wfi = true; + } else + BUG(); + + if (last_man && __mcpm_outbound_enter_critical(cpu, cluster)) { + arch_spin_unlock(&tc2_pm_lock); + + set_cr(get_cr() & ~CR_C); + flush_cache_all(); + asm volatile ("clrex"); + set_auxcr(get_auxcr() & ~(1 << 6)); + + disable_cci(cluster); + + /* + * Ensure that both C & I bits are disabled in the SCTLR + * before disabling ACE snoops. This ensures that no + * coherency traffic will originate from this cpu after + * ACE snoops are turned off. + */ + cpu_proc_fin(); + + __mcpm_outbound_leave_critical(cluster, CLUSTER_DOWN); + } else { + /* + * If last man then undo any setup done previously. + */ + if (last_man) { + vexpress_spc_powerdown_enable(cluster, 0); + vexpress_spc_set_global_wakeup_intr(0); + } + + arch_spin_unlock(&tc2_pm_lock); + + set_cr(get_cr() & ~CR_C); + flush_cache_louis(); + asm volatile ("clrex"); + set_auxcr(get_auxcr() & ~(1 << 6)); + } + + __mcpm_cpu_down(cpu, cluster); + + /* Now we are prepared for power-down, do it: */ + if (!skip_wfi) + wfi(); + + /* Not dead at this point? Let our caller cope. */ +} + +static void tc2_pm_powered_up(void) +{ + unsigned int mpidr, cpu, cluster; + unsigned long flags; + + mpidr = read_cpuid_mpidr(); + cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0); + cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1); + + pr_debug("%s: cpu %u cluster %u\n", __func__, cpu, cluster); + BUG_ON(cluster >= 2 || cpu >= vexpress_spc_get_nb_cpus(cluster)); + + local_irq_save(flags); + arch_spin_lock(&tc2_pm_lock); + + if (!tc2_pm_use_count[0][cluster] && + !tc2_pm_use_count[1][cluster] && + !tc2_pm_use_count[2][cluster]) { + vexpress_spc_powerdown_enable(cluster, 0); + vexpress_spc_set_global_wakeup_intr(0); + } + + if (!tc2_pm_use_count[cpu][cluster]) + tc2_pm_use_count[cpu][cluster] = 1; + + vexpress_spc_set_cpu_wakeup_irq(cpu, cluster, 0); + vexpress_spc_write_bxaddr_reg(cluster, cpu, 0); + + arch_spin_unlock(&tc2_pm_lock); + local_irq_restore(flags); +} + +static const struct mcpm_platform_ops tc2_pm_power_ops = { + .power_up = tc2_pm_power_up, + .power_down = tc2_pm_power_down, + .powered_up = tc2_pm_powered_up, +}; + +static void __init tc2_pm_usage_count_init(void) +{ + unsigned int mpidr, cpu, cluster; + + mpidr = read_cpuid_mpidr(); + cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0); + cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1); + + pr_debug("%s: cpu %u cluster %u\n", __func__, cpu, cluster); + BUG_ON(cpu >= 3 || cluster >= 2); + tc2_pm_use_count[cpu][cluster] = 1; +} + +extern void tc2_pm_power_up_setup(unsigned int affinity_level); + +static int __init tc2_pm_init(void) +{ + int ret; + + if (!vexpress_spc_check_loaded()) + return -ENODEV; + + tc2_pm_usage_count_init(); + + ret = mcpm_platform_register(&tc2_pm_power_ops); + if (!ret) + ret = mcpm_sync_init(tc2_pm_power_up_setup); + if (!ret) + pr_info("TC2 power management initialized\n"); + return ret; +} + +early_initcall(tc2_pm_init); diff --git a/arch/arm/mach-vexpress/tc2_pm_setup.S b/arch/arm/mach-vexpress/tc2_pm_setup.S new file mode 100644 index 00000000000..046890e5ff8 --- /dev/null +++ b/arch/arm/mach-vexpress/tc2_pm_setup.S @@ -0,0 +1,88 @@ +/* + * arch/arm/include/asm/tc2_pm_setup.S + * + * Created by: Nicolas Pitre, October 2012 + ( (based on dcscb_setup.S by Dave Martin) + * Copyright: (C) 2012 Linaro Limited + * + * 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. + */ + + +#include +#include + + +#define SPC_PHYS_BASE 0x7FFF0000 + +#define SNOOP_CTL_A15 0x404 +#define SNOOP_CTL_A7 0x504 + +#define A15_SNOOP_MASK (0x3 << 7) +#define A7_SNOOP_MASK (0x1 << 13) + +#define A15_BX_ADDR0 0xB68 + + +#define CCI_PHYS_BASE 0x2c090000 + +#define SLAVE_SNOOPCTL_OFFSET 0 +#define SNOOPCTL_SNOOP_ENABLE (1 << 0) +#define SNOOPCTL_DVM_ENABLE (1 << 1) + +#define CCI_STATUS_OFFSET 0xc +#define STATUS_CHANGE_PENDING (1 << 0) + +#define CCI_SLAVE_OFFSET(n) (0x1000 + 0x1000 * (n)) +#define CCI_SLAVE_A15 3 +#define CCI_SLAVE_A7 4 +#define CCI_A15_OFFSET CCI_SLAVE_OFFSET(CCI_SLAVE_A15) +#define CCI_A7_OFFSET CCI_SLAVE_OFFSET(CCI_SLAVE_A7) + + +/* + * Enable cluster-level coherency, in preparation for turning on the MMU. + * The ACTLR SMP bit does not need to be set here, because cpu_resume() + * already restores that. + */ + +ENTRY(tc2_pm_power_up_setup) + + cmp r0, #0 + beq 2f + + @ Enable CCI snoops + mrc p15, 0, r0, c0, c0, 5 @ MPIDR + ubfx r0, r0, #8, #4 @ cluster + ldr r3, =CCI_PHYS_BASE + CCI_A15_OFFSET + cmp r0, #0 @ A15 cluster? + addne r3, r3, #CCI_A7_OFFSET - CCI_A15_OFFSET + + @ r3 now points to the correct CCI slave register block + ldr r0, [r3, #SLAVE_SNOOPCTL_OFFSET] + orr r0, r0, #SNOOPCTL_SNOOP_ENABLE | SNOOPCTL_DVM_ENABLE + str r0, [r3, #SLAVE_SNOOPCTL_OFFSET] @ enable CCI snoops + + @ Wait for snoop control change to complete: + ldr r3, =CCI_PHYS_BASE +1: ldr r0, [r3, #CCI_STATUS_OFFSET] + tst r0, #STATUS_CHANGE_PENDING + bne 1b + + bx lr + +2: @ Clear the BX addr register + ldr r3, =SPC_PHYS_BASE + A15_BX_ADDR0 + mrc p15, 0, r0, c0, c0, 5 @ MPIDR + ubfx r1, r0, #8, #4 @ cluster + ubfx r0, r0, #0, #4 @ cpu + add r3, r3, r1, lsl #4 + mov r1, #0 + str r1, [r3, r0, lsl #2] + dsb + + bx lr + +ENDPROC(tc2_pm_power_up_setup) -- cgit v1.2.3 From 15438a8f30a2cdc73fbc03b4fa4f28925ad31564 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Tue, 19 Mar 2013 23:59:04 -0400 Subject: gic: introduce gic_cpu_if_down() This should be queued right before 'Revert "ARM: common: add GIC bybass disable on GIC CPU IF save function"'. Signed-off-by: Nicolas Pitre --- drivers/irqchip/irq-gic.c | 6 ++++++ include/linux/irqchip/arm-gic.h | 2 ++ 2 files changed, 8 insertions(+) diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index 19ceaa60e0f..fe44d3e2c70 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -453,6 +453,12 @@ static void __cpuinit gic_cpu_init(struct gic_chip_data *gic) writel_relaxed(1, base + GIC_CPU_CTRL); } +void gic_cpu_if_down(void) +{ + void __iomem *cpu_base = gic_data_cpu_base(&gic_data[0]); + writel_relaxed(0, cpu_base + GIC_CPU_CTRL); +} + #ifdef CONFIG_CPU_PM /* * Saves the GIC distributor registers during suspend or idle. Must be called diff --git a/include/linux/irqchip/arm-gic.h b/include/linux/irqchip/arm-gic.h index 3e203eb23cc..b5696108c06 100644 --- a/include/linux/irqchip/arm-gic.h +++ b/include/linux/irqchip/arm-gic.h @@ -67,6 +67,8 @@ void gic_init_bases(unsigned int, int, void __iomem *, void __iomem *, u32 offset, struct device_node *); void gic_cascade_irq(unsigned int gic_nr, unsigned int irq); +void gic_cpu_if_down(void); + static inline void gic_init(unsigned int nr, int start, void __iomem *dist , void __iomem *cpu) { -- cgit v1.2.3 From 0b94e1be54079c872d8081c4de6ca667dbc4b488 Mon Sep 17 00:00:00 2001 From: Lorenzo Pieralisi Date: Tue, 5 Feb 2013 11:09:16 +0000 Subject: ARM: TC2: disable GIC CPU IF on power down On TC2 testchip the GIC CPU IF must be disabled before powering down a core since a pending IRQ might cause wfi completion and the processor would exit wfi state while power controller is taking action to reset or power up the CPU upon IRQ reception. This patch adds code that disables the GIC CPU IF in TC2 specific power API methods. Signed-off-by: Lorenzo Pieralisi --- arch/arm/mach-vexpress/tc2_pm.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/arm/mach-vexpress/tc2_pm.c b/arch/arm/mach-vexpress/tc2_pm.c index 4be2d73cb38..8be975e4b31 100644 --- a/arch/arm/mach-vexpress/tc2_pm.c +++ b/arch/arm/mach-vexpress/tc2_pm.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -116,6 +117,8 @@ static void tc2_pm_power_down(void) } else BUG(); + gic_cpu_if_down(); + if (last_man && __mcpm_outbound_enter_critical(cpu, cluster)) { arch_spin_unlock(&tc2_pm_lock); -- cgit v1.2.3 From 3ce60848e5c3d8fe8b7479cfc5736a5e6370f253 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Mon, 10 Dec 2012 00:22:06 -0500 Subject: ARM: vexpress/tc2: implement PM suspend method This is simplistic for the moment as the expected residency is used to prevent the shutting down of L2 and the cluster if the residency for the last man is lower than 5 ms. To make this right, the residency end time for each CPU would need to be recorded and taken into account. On a suspend, the firmware mailbox address has to be set prior entering low power mode. Signed-off-by: Nicolas Pitre --- arch/arm/mach-vexpress/Kconfig | 25 +++---- arch/arm/mach-vexpress/Makefile | 2 +- arch/arm/mach-vexpress/cpuidle-tc2.c | 141 +++++------------------------------ arch/arm/mach-vexpress/hotplug-asm.S | 28 ------- arch/arm/mach-vexpress/tc2-sleep.S | 76 ------------------- arch/arm/mach-vexpress/tc2_pm.c | 23 +++++- 6 files changed, 50 insertions(+), 245 deletions(-) delete mode 100644 arch/arm/mach-vexpress/hotplug-asm.S delete mode 100644 arch/arm/mach-vexpress/tc2-sleep.S diff --git a/arch/arm/mach-vexpress/Kconfig b/arch/arm/mach-vexpress/Kconfig index 6e432b2c90c..ceb7ef03d0f 100644 --- a/arch/arm/mach-vexpress/Kconfig +++ b/arch/arm/mach-vexpress/Kconfig @@ -54,20 +54,6 @@ config ARCH_VEXPRESS_CORTEX_A5_A9_ERRATA build a working kernel, you must also enable relevant core tile support or Flattened Device Tree based support options. -config ARCH_VEXPRESS_TC2_PM - bool "Power Management Support for TC2 test-chip (EXPERIMENTAL)" - depends on CPU_IDLE && PM - select ARM_CPU_SUSPEND - select ARCH_NEEDS_CPU_IDLE_COUPLED - select ARM_SPC - select ARM_CCI - help - Provides code that enables CPU idle power management on the - TC2 testchip. It enables the CPU idle driver so that the kernel - can enter cluster power down states provided by the power - controller. Code is built on top of coupled C-state idle code - since all CPUs need to be idle to enter cluster shutdown. - config ARCH_VEXPRESS_CA9X4 bool "Versatile Express Cortex-A9x4 tile" @@ -79,4 +65,15 @@ config ARCH_VEXPRESS_TC2 help Support for CPU and cluster power management on TC2. +config VEXPRESS_TC2_CPUIDLE + bool "cpuidle support for TC2 test-chip (EXPERIMENTAL)" + depends on CPU_IDLE && PM && ARCH_VEXPRESS_TC2 + select ARM_CPU_SUSPEND + select ARM_SPC + help + Provides code that enables CPU idle power management on the + TC2 testchip. It enables the CPU idle driver so that the kernel + can enter cluster power down states provided by the power + controller. + endmenu diff --git a/arch/arm/mach-vexpress/Makefile b/arch/arm/mach-vexpress/Makefile index 9650cc4eec1..4745fd36e91 100644 --- a/arch/arm/mach-vexpress/Makefile +++ b/arch/arm/mach-vexpress/Makefile @@ -9,4 +9,4 @@ obj-$(CONFIG_ARCH_VEXPRESS_CA9X4) += ct-ca9x4.o obj-$(CONFIG_ARCH_VEXPRESS_TC2) += tc2_pm.o tc2_pm_setup.o obj-$(CONFIG_SMP) += platsmp.o obj-$(CONFIG_HOTPLUG_CPU) += hotplug.o -obj-$(CONFIG_ARCH_VEXPRESS_TC2_PM) += cpuidle-tc2.o hotplug-asm.o tc2-sleep.o +obj-$(CONFIG_VEXPRESS_TC2_CPUIDLE) += cpuidle-tc2.o diff --git a/arch/arm/mach-vexpress/cpuidle-tc2.c b/arch/arm/mach-vexpress/cpuidle-tc2.c index 3b73d4a5dc5..0a983dd531b 100644 --- a/arch/arm/mach-vexpress/cpuidle-tc2.c +++ b/arch/arm/mach-vexpress/cpuidle-tc2.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -51,7 +52,7 @@ static int tc2_cpuidle_simple_enter(struct cpuidle_device *dev, return index; } -static int tc2_enter_coupled(struct cpuidle_device *dev, +static int tc2_enter_powerdown(struct cpuidle_device *dev, struct cpuidle_driver *drv, int idx); static struct cpuidle_state tc2_cpuidle_set[] __initdata = { @@ -65,11 +66,10 @@ static struct cpuidle_state tc2_cpuidle_set[] __initdata = { .desc = "ARM WFI", }, [1] = { - .enter = tc2_enter_coupled, + .enter = tc2_enter_powerdown, .exit_latency = 300, .target_residency = 1000, - .flags = CPUIDLE_FLAG_TIME_VALID | - CPUIDLE_FLAG_COUPLED, + .flags = CPUIDLE_FLAG_TIME_VALID, .name = "C1", .desc = "ARM power down", }, @@ -83,45 +83,21 @@ struct cpuidle_driver tc2_idle_driver = { static DEFINE_PER_CPU(struct cpuidle_device, tc2_idle_dev); -#define NR_CLUSTERS 2 -static cpumask_t cluster_mask = CPU_MASK_NONE; - -extern void disable_clean_inv_dcache(int); -static atomic_t abort_barrier[NR_CLUSTERS]; - -extern void tc2_cpu_resume(void); -extern void disable_snoops(void); - -static int notrace tc2_coupled_finisher(unsigned long arg) +static int notrace tc2_powerdown_finisher(unsigned long arg) { unsigned int mpidr = read_cpuid_mpidr(); - unsigned int cpu = smp_processor_id(); unsigned int cluster = (mpidr >> 8) & 0xf; - unsigned int weight = cpumask_weight(topology_core_cpumask(cpu)); - u8 wfi_weight = 0; - - cpuidle_coupled_parallel_barrier((struct cpuidle_device *)arg, - &abort_barrier[cluster]); - if (mpidr & 0xf) { - disable_clean_inv_dcache(0); - wfi(); - /* not reached */ - } - - while (wfi_weight != (weight - 1)) { - wfi_weight = vexpress_spc_wfi_cpustat(cluster); - wfi_weight = hweight8(wfi_weight); - } + unsigned int cpu = mpidr & 0xf; - vexpress_spc_powerdown_enable(cluster, 1); - disable_clean_inv_dcache(1); - disable_cci(cluster); - disable_snoops(); + bL_set_entry_vector(cpu, cluster, cpu_resume); + vexpress_spc_write_bxaddr_reg(cluster, cpu, + virt_to_phys(bL_entry_point)); + bL_cpu_power_down(); return 1; } /* - * tc2_enter_coupled - Programs CPU to enter the specified state + * tc2_enter_powerdown - Programs CPU to enter the specified state * @dev: cpuidle device * @drv: The target state to be programmed * @idx: state index @@ -129,38 +105,33 @@ static int notrace tc2_coupled_finisher(unsigned long arg) * Called from the CPUidle framework to program the device to the * specified target state selected by the governor. */ -static int tc2_enter_coupled(struct cpuidle_device *dev, +static int tc2_enter_powerdown(struct cpuidle_device *dev, struct cpuidle_driver *drv, int idx) { struct timespec ts_preidle, ts_postidle, ts_idle; int ret; - int cluster = (read_cpuid_mpidr() >> 8) & 0xf; + /* Used to keep track of the total time in idle */ getnstimeofday(&ts_preidle); - if (!cpu_isset(cluster, cluster_mask)) { - cpuidle_coupled_parallel_barrier(dev, - &abort_barrier[cluster]); - goto shallow_out; - } - BUG_ON(!irqs_disabled()); cpu_pm_enter(); clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, &dev->cpu); - ret = cpu_suspend((unsigned long) dev, tc2_coupled_finisher); - + ret = cpu_suspend((unsigned long) dev, tc2_powerdown_finisher); if (ret) BUG(); + bL_cpu_powered_up(); + clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &dev->cpu); cpu_pm_exit(); -shallow_out: getnstimeofday(&ts_postidle); + local_irq_enable(); ts_idle = timespec_sub(ts_postidle, ts_preidle); dev->last_residency = ts_idle.tv_nsec / NSEC_PER_USEC + @@ -168,46 +139,6 @@ shallow_out: return idx; } -static int idle_mask_show(struct seq_file *f, void *p) -{ - char buf[256]; - bitmap_scnlistprintf(buf, 256, cpumask_bits(&cluster_mask), - NR_CLUSTERS); - - seq_printf(f, "%s\n", buf); - - return 0; -} - -static int idle_mask_open(struct inode *inode, struct file *file) -{ - return single_open(file, idle_mask_show, inode->i_private); -} - -static const struct file_operations cpuidle_fops = { - .open = idle_mask_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; - -static int idle_debug_set(void *data, u64 val) -{ - if (val >= (unsigned)NR_CLUSTERS && val != 0xff) { - pr_warning("Wrong parameter passed\n"); - return -EINVAL; - } - cpuidle_pause_and_lock(); - if (val == 0xff) - cpumask_clear(&cluster_mask); - else - cpumask_set_cpu(val, &cluster_mask); - - cpuidle_resume_and_unlock(); - return 0; -} -DEFINE_SIMPLE_ATTRIBUTE(idle_debug_fops, NULL, idle_debug_set, "%llu\n"); - /* * tc2_idle_init * @@ -218,7 +149,6 @@ int __init tc2_idle_init(void) { struct cpuidle_device *dev; int i, cpu_id; - struct dentry *idle_debug, *file_debug; struct cpuidle_driver *drv = &tc2_idle_driver; if (!vexpress_spc_check_loaded()) { @@ -240,10 +170,7 @@ int __init tc2_idle_init(void) pr_err("CPUidle for CPU%d registered\n", cpu_id); dev = &per_cpu(tc2_idle_dev, cpu_id); dev->cpu = cpu_id; - dev->safe_state_index = 0; - cpumask_copy(&dev->coupled_cpus, - topology_core_cpumask(cpu_id)); dev->state_count = drv->state_count; if (cpuidle_register_device(dev)) { @@ -253,40 +180,6 @@ int __init tc2_idle_init(void) } } - idle_debug = debugfs_create_dir("idle_debug", NULL); - - if (IS_ERR_OR_NULL(idle_debug)) { - printk(KERN_INFO "Error in creating idle debugfs directory\n"); - return 0; - } - - file_debug = debugfs_create_file("enable_idle", S_IRUGO | S_IWGRP, - idle_debug, NULL, &idle_debug_fops); - - if (IS_ERR_OR_NULL(file_debug)) { - printk(KERN_INFO "Error in creating enable_idle file\n"); - return 0; - } - - file_debug = debugfs_create_file("enable_mask", S_IRUGO | S_IWGRP, - idle_debug, NULL, &cpuidle_fops); - - if (IS_ERR_OR_NULL(file_debug)) - printk(KERN_INFO "Error in creating enable_mask file\n"); - - /* enable all wake-up IRQs by default */ - vexpress_spc_set_wake_intr(0x7ff); - vexpress_flags_set(virt_to_phys(tc2_cpu_resume)); - - /* - * Enable idle by default for all possible clusters. - * This must be done after all other setup to prevent the - * possibility of clusters being powered down before they - * are fully configured. - */ - for (i = 0; i < NR_CLUSTERS; i++) - cpumask_set_cpu(i, &cluster_mask); - return 0; } diff --git a/arch/arm/mach-vexpress/hotplug-asm.S b/arch/arm/mach-vexpress/hotplug-asm.S deleted file mode 100644 index f63472edcc4..00000000000 --- a/arch/arm/mach-vexpress/hotplug-asm.S +++ /dev/null @@ -1,28 +0,0 @@ -#include -#include - - .text -ENTRY(disable_clean_inv_dcache) - ARM( stmfd sp!, {r4-r5, r7, r9-r11, lr} ) - THUMB( stmfd sp!, {r4-r7, r9-r11, lr} ) - - mrc p15, 0, r3, c1, c0, 0 - bic r3, #4 @ clear C bit - mcr p15, 0, r3, c1, c0, 0 - dsb - isb - mov r12, r0 - cmp r12, #0 - bleq v7_flush_dcache_louis - cmp r12, #0 - blne v7_flush_dcache_all - clrex - mrc p15, 0, r3, c1, c0, 1 - bic r3, #0x40 @ clear SMP bit - mcr p15, 0, r3, c1, c0, 1 - isb - dsb - ARM( ldmfd sp!, {r4-r5, r7, r9-r11, lr} ) - THUMB( ldmfd sp!, {r4-r7, r9-r11, lr} ) - mov pc, lr -ENDPROC(disable_clean_inv_dcache) diff --git a/arch/arm/mach-vexpress/tc2-sleep.S b/arch/arm/mach-vexpress/tc2-sleep.S deleted file mode 100644 index 9bf8348fcc8..00000000000 --- a/arch/arm/mach-vexpress/tc2-sleep.S +++ /dev/null @@ -1,76 +0,0 @@ -#include - -#define SPC_PHYS_BASE 0x7FFF0000 -#define A15_CONF 0x400 - -ENTRY(tc2_cpu_resume) - mrc p15, 0, r0, c0, c0, 5 - ands r0, r0, #0xff00 - ldr r1, =SPC_PHYS_BASE - mov r2, #A15_CONF - add r1, r1, r2 - ldr r1, [r1] - and r1, r1, #0x7 - cmp r1, r0, lsr #8 - adr r0, value - addne r0, r0, #16 - ldmia r0, {r1, r2, r3, r4} @ CCI address, SCC snoop control & val - mvn r3, r3 @ undo actions done at shutdown - ldr r0, [r2] - and r5, r0, r3 - str r5, [r2] - mov r0, #3 @ enable CCI for the cluster - str r0, [r1] - adr r1, cci_ctrl - ldr r1, [r1] -loop: - ldr r0, [r1] - ands r0, r0, #1 - bne loop - mov r0, #0 @ disable power down enable - str r0, [r4] - b cpu_resume -ENDPROC(tc2_cpu_resume) - -ENTRY(disable_snoops) - mrc p15, 0, r0, c0, c0, 5 - ands r0, r0, #0xff00 - ldr r1, scc_ptr - ldr r1, [r1] - mov r2, #A15_CONF - add r1, r1, r2 - ldr r1, [r1] - and r1, r1, #0x7 - cmp r1, r0, lsr #8 - adr r0, vvalue - addne r0, r0, #8 - ldmia r0, {r2, r3} @ CCI address, SCC snoop control & val - ldr r1, scc_ptr - ldr r1, [r1] - add r2, r1, r2 - ldr r0, [r2] - orr r0, r0, r3 - dsb - isb - str r0, [r2] - wfi -ENDPROC(disable_snoops) - -cci_ctrl: - .long 0x2c09000c -value: - .long 0x2c094000 - .long 0x7fff0404 - .long 0x180 - .long 0x7fff0b30 - .long 0x2c095000 - .long 0x7fff0504 - .long 0x2000 - .long 0x7fff0b34 -vvalue: - .long 0x404 - .long 0x180 - .long 0x504 - .long 0x2000 -scc_ptr: - .long vscc diff --git a/arch/arm/mach-vexpress/tc2_pm.c b/arch/arm/mach-vexpress/tc2_pm.c index 8be975e4b31..a5e18df801d 100644 --- a/arch/arm/mach-vexpress/tc2_pm.c +++ b/arch/arm/mach-vexpress/tc2_pm.c @@ -80,7 +80,7 @@ static int tc2_pm_power_up(unsigned int cpu, unsigned int cluster) return 0; } -static void tc2_pm_power_down(void) +static void tc2_pm_down(u64 residency) { unsigned int mpidr, cpu, cluster; bool last_man = false, skip_wfi = false; @@ -101,7 +101,8 @@ static void tc2_pm_power_down(void) vexpress_spc_set_cpu_wakeup_irq(cpu, cluster, 1); if (!tc2_pm_use_count[0][cluster] && !tc2_pm_use_count[1][cluster] && - !tc2_pm_use_count[2][cluster]) { + !tc2_pm_use_count[2][cluster] && + (!residency || residency > 5000)) { vexpress_spc_powerdown_enable(cluster, 1); vexpress_spc_set_global_wakeup_intr(1); last_man = true; @@ -164,6 +165,23 @@ static void tc2_pm_power_down(void) /* Not dead at this point? Let our caller cope. */ } +static void tc2_pm_power_down(void) +{ + tc2_pm_down(0); +} + +static void tc2_pm_suspend(u64 residency) +{ + unsigned int mpidr, cpu, cluster; + + mpidr = read_cpuid_mpidr(); + cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0); + cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1); + vexpress_spc_write_bxaddr_reg(cluster, cpu, + virt_to_phys(mcpm_entry_point)); + tc2_pm_down(residency); +} + static void tc2_pm_powered_up(void) { unsigned int mpidr, cpu, cluster; @@ -199,6 +217,7 @@ static void tc2_pm_powered_up(void) static const struct mcpm_platform_ops tc2_pm_power_ops = { .power_up = tc2_pm_power_up, .power_down = tc2_pm_power_down, + .suspend = tc2_pm_suspend, .powered_up = tc2_pm_powered_up, }; -- cgit v1.2.3 From 625b10c3d3d06603012895f2c0416998b791fe5a Mon Sep 17 00:00:00 2001 From: Dave Martin Date: Wed, 20 Feb 2013 17:34:20 +0000 Subject: ARM: mcpm: Make all mcpm functions notrace The functions in mcpm_entry.c are mostly intended for use during scary cache and coherency disabling sequences, or do other things which confuse trace ... like powering a CPU down and not returning. Similarly for the backend code. For simplicity, this patch just makes whole files notrace. There should be more than enough traceable points on the paths to these functions, but we can be more fine-grained later if there is a need for it. Signed-off-by: Dave Martin --- arch/arm/mach-vexpress/Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm/mach-vexpress/Makefile b/arch/arm/mach-vexpress/Makefile index 4745fd36e91..caa32245519 100644 --- a/arch/arm/mach-vexpress/Makefile +++ b/arch/arm/mach-vexpress/Makefile @@ -7,6 +7,7 @@ ccflags-$(CONFIG_ARCH_MULTIPLATFORM) := -I$(srctree)/$(src)/include \ obj-y := v2m.o obj-$(CONFIG_ARCH_VEXPRESS_CA9X4) += ct-ca9x4.o obj-$(CONFIG_ARCH_VEXPRESS_TC2) += tc2_pm.o tc2_pm_setup.o +CFLAGS_REMOVE_tc2_pm.o = -pg obj-$(CONFIG_SMP) += platsmp.o obj-$(CONFIG_HOTPLUG_CPU) += hotplug.o obj-$(CONFIG_VEXPRESS_TC2_CPUIDLE) += cpuidle-tc2.o -- cgit v1.2.3 From 0251cea2c008e018aea480a275b05dda36bef55f Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Mon, 10 Dec 2012 00:36:26 -0500 Subject: ARM: vexpress/tc2: clean up the cpuidle driver Use the bL_cpu_suspend method instead of bL_cpu_power_down. This allows for the driver to become usable on non SPC based platform such as RTSM if vexpress_spc_check_loaded() is removed. Signed-off-by: Nicolas Pitre --- arch/arm/mach-vexpress/cpuidle-tc2.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/arch/arm/mach-vexpress/cpuidle-tc2.c b/arch/arm/mach-vexpress/cpuidle-tc2.c index 0a983dd531b..1f1efc4936f 100644 --- a/arch/arm/mach-vexpress/cpuidle-tc2.c +++ b/arch/arm/mach-vexpress/cpuidle-tc2.c @@ -90,9 +90,7 @@ static int notrace tc2_powerdown_finisher(unsigned long arg) unsigned int cpu = mpidr & 0xf; bL_set_entry_vector(cpu, cluster, cpu_resume); - vexpress_spc_write_bxaddr_reg(cluster, cpu, - virt_to_phys(bL_entry_point)); - bL_cpu_power_down(); + bL_cpu_suspend(0); /* 0 should be replaced with better value here */ return 1; } -- cgit v1.2.3 From 13a7b5d65608f7a8051487eb5cd5a343ed9da896 Mon Sep 17 00:00:00 2001 From: mark hambleton Date: Fri, 18 Jan 2013 14:16:19 +0000 Subject: Use dts compatible node to init cpuidle-tc2 Change the init code for cpuidle-tc2 to check for a compatible node in the devicetree of "arm,generic" in preparation for moving it to driver/cpuidle. Rename functions / variable from tc2_ to bl_. Signed-off-by: mark hambleton --- arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts | 2 +- arch/arm/mach-vexpress/Kconfig | 11 -- arch/arm/mach-vexpress/Makefile | 1 - arch/arm/mach-vexpress/cpuidle-tc2.c | 184 ----------------------------- drivers/cpuidle/Makefile | 2 +- drivers/cpuidle/arm_big_little.c | 183 ++++++++++++++++++++++++++++ 6 files changed, 185 insertions(+), 198 deletions(-) delete mode 100644 arch/arm/mach-vexpress/cpuidle-tc2.c create mode 100644 drivers/cpuidle/arm_big_little.c diff --git a/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts b/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts index d2803be4e1a..1abef13f9e9 100644 --- a/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts +++ b/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts @@ -13,7 +13,7 @@ model = "V2P-CA15_CA7"; arm,hbi = <0x249>; arm,vexpress,site = <0xf>; - compatible = "arm,vexpress,v2p-ca15_a7", "arm,vexpress"; + compatible = "arm,vexpress,v2p-ca15_a7", "arm,vexpress", "arm,generic"; interrupt-parent = <&gic>; #address-cells = <2>; #size-cells = <2>; diff --git a/arch/arm/mach-vexpress/Kconfig b/arch/arm/mach-vexpress/Kconfig index ceb7ef03d0f..1ce8798e7ee 100644 --- a/arch/arm/mach-vexpress/Kconfig +++ b/arch/arm/mach-vexpress/Kconfig @@ -65,15 +65,4 @@ config ARCH_VEXPRESS_TC2 help Support for CPU and cluster power management on TC2. -config VEXPRESS_TC2_CPUIDLE - bool "cpuidle support for TC2 test-chip (EXPERIMENTAL)" - depends on CPU_IDLE && PM && ARCH_VEXPRESS_TC2 - select ARM_CPU_SUSPEND - select ARM_SPC - help - Provides code that enables CPU idle power management on the - TC2 testchip. It enables the CPU idle driver so that the kernel - can enter cluster power down states provided by the power - controller. - endmenu diff --git a/arch/arm/mach-vexpress/Makefile b/arch/arm/mach-vexpress/Makefile index caa32245519..fd9bcfd7431 100644 --- a/arch/arm/mach-vexpress/Makefile +++ b/arch/arm/mach-vexpress/Makefile @@ -10,4 +10,3 @@ obj-$(CONFIG_ARCH_VEXPRESS_TC2) += tc2_pm.o tc2_pm_setup.o CFLAGS_REMOVE_tc2_pm.o = -pg obj-$(CONFIG_SMP) += platsmp.o obj-$(CONFIG_HOTPLUG_CPU) += hotplug.o -obj-$(CONFIG_VEXPRESS_TC2_CPUIDLE) += cpuidle-tc2.o diff --git a/arch/arm/mach-vexpress/cpuidle-tc2.c b/arch/arm/mach-vexpress/cpuidle-tc2.c deleted file mode 100644 index 1f1efc4936f..00000000000 --- a/arch/arm/mach-vexpress/cpuidle-tc2.c +++ /dev/null @@ -1,184 +0,0 @@ -/* - * TC2 CPU idle driver. - * - * Copyright (C) 2012 ARM Ltd. - * Author: 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. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -static int tc2_cpuidle_simple_enter(struct cpuidle_device *dev, - struct cpuidle_driver *drv, int index) -{ - ktime_t time_start, time_end; - s64 diff; - - time_start = ktime_get(); - - cpu_do_idle(); - - time_end = ktime_get(); - - local_irq_enable(); - - diff = ktime_to_us(ktime_sub(time_end, time_start)); - if (diff > INT_MAX) - diff = INT_MAX; - - dev->last_residency = (int) diff; - - return index; -} - -static int tc2_enter_powerdown(struct cpuidle_device *dev, - struct cpuidle_driver *drv, int idx); - -static struct cpuidle_state tc2_cpuidle_set[] __initdata = { - [0] = { - .enter = tc2_cpuidle_simple_enter, - .exit_latency = 1, - .target_residency = 1, - .power_usage = UINT_MAX, - .flags = CPUIDLE_FLAG_TIME_VALID, - .name = "WFI", - .desc = "ARM WFI", - }, - [1] = { - .enter = tc2_enter_powerdown, - .exit_latency = 300, - .target_residency = 1000, - .flags = CPUIDLE_FLAG_TIME_VALID, - .name = "C1", - .desc = "ARM power down", - }, -}; - -struct cpuidle_driver tc2_idle_driver = { - .name = "tc2_idle", - .owner = THIS_MODULE, - .safe_state_index = 0 -}; - -static DEFINE_PER_CPU(struct cpuidle_device, tc2_idle_dev); - -static int notrace tc2_powerdown_finisher(unsigned long arg) -{ - unsigned int mpidr = read_cpuid_mpidr(); - unsigned int cluster = (mpidr >> 8) & 0xf; - unsigned int cpu = mpidr & 0xf; - - bL_set_entry_vector(cpu, cluster, cpu_resume); - bL_cpu_suspend(0); /* 0 should be replaced with better value here */ - return 1; -} - -/* - * tc2_enter_powerdown - Programs CPU to enter the specified state - * @dev: cpuidle device - * @drv: The target state to be programmed - * @idx: state index - * - * Called from the CPUidle framework to program the device to the - * specified target state selected by the governor. - */ -static int tc2_enter_powerdown(struct cpuidle_device *dev, - struct cpuidle_driver *drv, int idx) -{ - struct timespec ts_preidle, ts_postidle, ts_idle; - int ret; - - /* Used to keep track of the total time in idle */ - getnstimeofday(&ts_preidle); - - BUG_ON(!irqs_disabled()); - - cpu_pm_enter(); - - clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, &dev->cpu); - - ret = cpu_suspend((unsigned long) dev, tc2_powerdown_finisher); - if (ret) - BUG(); - - bL_cpu_powered_up(); - - clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &dev->cpu); - - cpu_pm_exit(); - - getnstimeofday(&ts_postidle); - local_irq_enable(); - ts_idle = timespec_sub(ts_postidle, ts_preidle); - - dev->last_residency = ts_idle.tv_nsec / NSEC_PER_USEC + - ts_idle.tv_sec * USEC_PER_SEC; - return idx; -} - -/* - * tc2_idle_init - * - * Registers the TC2 specific cpuidle driver with the cpuidle - * framework with the valid set of states. - */ -int __init tc2_idle_init(void) -{ - struct cpuidle_device *dev; - int i, cpu_id; - struct cpuidle_driver *drv = &tc2_idle_driver; - - if (!vexpress_spc_check_loaded()) { - pr_info("TC2 CPUidle not registered because no SPC found\n"); - return -ENODEV; - } - - drv->state_count = (sizeof(tc2_cpuidle_set) / - sizeof(struct cpuidle_state)); - - for (i = 0; i < drv->state_count; i++) { - memcpy(&drv->states[i], &tc2_cpuidle_set[i], - sizeof(struct cpuidle_state)); - } - - cpuidle_register_driver(drv); - - for_each_cpu(cpu_id, cpu_online_mask) { - pr_err("CPUidle for CPU%d registered\n", cpu_id); - dev = &per_cpu(tc2_idle_dev, cpu_id); - dev->cpu = cpu_id; - - dev->state_count = drv->state_count; - - if (cpuidle_register_device(dev)) { - printk(KERN_ERR "%s: Cpuidle register device failed\n", - __func__); - return -EIO; - } - } - - return 0; -} - -late_initcall(tc2_idle_init); diff --git a/drivers/cpuidle/Makefile b/drivers/cpuidle/Makefile index 0d8bd55e776..7d8256a5ea9 100644 --- a/drivers/cpuidle/Makefile +++ b/drivers/cpuidle/Makefile @@ -4,6 +4,6 @@ obj-y += cpuidle.o driver.o governor.o sysfs.o governors/ obj-$(CONFIG_ARCH_NEEDS_CPU_IDLE_COUPLED) += coupled.o - +obj-$(CONFIG_BIG_LITTLE) += arm_big_little.o obj-$(CONFIG_CPU_IDLE_CALXEDA) += cpuidle-calxeda.o obj-$(CONFIG_ARCH_KIRKWOOD) += cpuidle-kirkwood.o diff --git a/drivers/cpuidle/arm_big_little.c b/drivers/cpuidle/arm_big_little.c new file mode 100644 index 00000000000..b97ebe018b6 --- /dev/null +++ b/drivers/cpuidle/arm_big_little.c @@ -0,0 +1,183 @@ +/* + * big.LITTLE CPU idle driver. + * + * Copyright (C) 2012 ARM Ltd. + * Author: 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int bl_cpuidle_simple_enter(struct cpuidle_device *dev, + struct cpuidle_driver *drv, int index) +{ + ktime_t time_start, time_end; + s64 diff; + + time_start = ktime_get(); + + cpu_do_idle(); + + time_end = ktime_get(); + + local_irq_enable(); + + diff = ktime_to_us(ktime_sub(time_end, time_start)); + if (diff > INT_MAX) + diff = INT_MAX; + + dev->last_residency = (int) diff; + + return index; +} + +static int bl_enter_powerdown(struct cpuidle_device *dev, + struct cpuidle_driver *drv, int idx); + +static struct cpuidle_state bl_cpuidle_set[] __initdata = { + [0] = { + .enter = bl_cpuidle_simple_enter, + .exit_latency = 1, + .target_residency = 1, + .power_usage = UINT_MAX, + .flags = CPUIDLE_FLAG_TIME_VALID, + .name = "WFI", + .desc = "ARM WFI", + }, + [1] = { + .enter = bl_enter_powerdown, + .exit_latency = 300, + .target_residency = 1000, + .flags = CPUIDLE_FLAG_TIME_VALID, + .name = "C1", + .desc = "ARM power down", + }, +}; + +struct cpuidle_driver bl_idle_driver = { + .name = "bl_idle", + .owner = THIS_MODULE, + .safe_state_index = 0 +}; + +static DEFINE_PER_CPU(struct cpuidle_device, bl_idle_dev); + +static int notrace bl_powerdown_finisher(unsigned long arg) +{ + unsigned int mpidr = read_cpuid_mpidr(); + unsigned int cluster = (mpidr >> 8) & 0xf; + unsigned int cpu = mpidr & 0xf; + + bL_set_entry_vector(cpu, cluster, cpu_resume); + bL_cpu_suspend(0); /* 0 should be replaced with better value here */ + return 1; +} + +/* + * bl_enter_powerdown - Programs CPU to enter the specified state + * @dev: cpuidle device + * @drv: The target state to be programmed + * @idx: state index + * + * Called from the CPUidle framework to program the device to the + * specified target state selected by the governor. + */ +static int bl_enter_powerdown(struct cpuidle_device *dev, + struct cpuidle_driver *drv, int idx) +{ + struct timespec ts_preidle, ts_postidle, ts_idle; + int ret; + + /* Used to keep track of the total time in idle */ + getnstimeofday(&ts_preidle); + + BUG_ON(!irqs_disabled()); + + cpu_pm_enter(); + + clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, &dev->cpu); + + ret = cpu_suspend((unsigned long) dev, bl_powerdown_finisher); + if (ret) + BUG(); + + bL_cpu_powered_up(); + + clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &dev->cpu); + + cpu_pm_exit(); + + getnstimeofday(&ts_postidle); + local_irq_enable(); + ts_idle = timespec_sub(ts_postidle, ts_preidle); + + dev->last_residency = ts_idle.tv_nsec / NSEC_PER_USEC + + ts_idle.tv_sec * USEC_PER_SEC; + return idx; +} + +/* + * bl_idle_init + * + * Registers the bl specific cpuidle driver with the cpuidle + * framework with the valid set of states. + */ +int __init bl_idle_init(void) +{ + struct cpuidle_device *dev; + int i, cpu_id; + struct cpuidle_driver *drv = &bl_idle_driver; + + if (!of_find_compatible_node(NULL, NULL, "arm,generic")) { + pr_info("%s: No compatible node found\n", __func__); + return -ENODEV; + } + + drv->state_count = (sizeof(bl_cpuidle_set) / + sizeof(struct cpuidle_state)); + + for (i = 0; i < drv->state_count; i++) { + memcpy(&drv->states[i], &bl_cpuidle_set[i], + sizeof(struct cpuidle_state)); + } + + cpuidle_register_driver(drv); + + for_each_cpu(cpu_id, cpu_online_mask) { + pr_err("CPUidle for CPU%d registered\n", cpu_id); + dev = &per_cpu(bl_idle_dev, cpu_id); + dev->cpu = cpu_id; + + dev->state_count = drv->state_count; + + if (cpuidle_register_device(dev)) { + printk(KERN_ERR "%s: Cpuidle register device failed\n", + __func__); + return -EIO; + } + } + + return 0; +} + +late_initcall(bl_idle_init); -- cgit v1.2.3 From 62373580b24749b621529f0dcc8e64719e189fa9 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Tue, 19 Mar 2013 15:59:24 -0400 Subject: cpuidle: arm_big_little: fixup for MCPM The low-level layer is now called "mcpm". --- drivers/cpuidle/arm_big_little.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/cpuidle/arm_big_little.c b/drivers/cpuidle/arm_big_little.c index b97ebe018b6..a430800d4a7 100644 --- a/drivers/cpuidle/arm_big_little.c +++ b/drivers/cpuidle/arm_big_little.c @@ -20,7 +20,7 @@ #include #include #include -#include +#include #include #include #include @@ -88,8 +88,8 @@ static int notrace bl_powerdown_finisher(unsigned long arg) unsigned int cluster = (mpidr >> 8) & 0xf; unsigned int cpu = mpidr & 0xf; - bL_set_entry_vector(cpu, cluster, cpu_resume); - bL_cpu_suspend(0); /* 0 should be replaced with better value here */ + mcpm_set_entry_vector(cpu, cluster, cpu_resume); + mcpm_cpu_suspend(0); /* 0 should be replaced with better value here */ return 1; } @@ -121,7 +121,7 @@ static int bl_enter_powerdown(struct cpuidle_device *dev, if (ret) BUG(); - bL_cpu_powered_up(); + mcpm_cpu_powered_up(); clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &dev->cpu); -- cgit v1.2.3 From aa55d8151df4cc277cd453d9e8dbfabdbced761f Mon Sep 17 00:00:00 2001 From: Lorenzo Pieralisi Date: Thu, 14 Mar 2013 14:07:20 +0000 Subject: ARM: TC2: reset CPUs spuriously woken up on cluster power up On TC2, all CPUs in a cluster are woken up when an IRQ event triggers for a CPU in a cluster in shutdown state. This patch puts spuriously woken CPUs back in reset by checking the pending IRQ status in the SPC wake-up interrupt status register; if the CPU has no pending IRQ routed to it, the core reexecutes wfi and it is put in reset by FW straight away. Tested-by: Viresh Kumar Signed-off-by: Lorenzo Pieralisi --- arch/arm/mach-vexpress/tc2_pm.c | 4 +++- arch/arm/mach-vexpress/tc2_pm_setup.S | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/arch/arm/mach-vexpress/tc2_pm.c b/arch/arm/mach-vexpress/tc2_pm.c index a5e18df801d..54757301ca5 100644 --- a/arch/arm/mach-vexpress/tc2_pm.c +++ b/arch/arm/mach-vexpress/tc2_pm.c @@ -172,13 +172,15 @@ static void tc2_pm_power_down(void) static void tc2_pm_suspend(u64 residency) { + extern void tc2_resume(void); unsigned int mpidr, cpu, cluster; mpidr = read_cpuid_mpidr(); cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0); cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1); vexpress_spc_write_bxaddr_reg(cluster, cpu, - virt_to_phys(mcpm_entry_point)); + virt_to_phys(tc2_resume)); + tc2_pm_down(residency); } diff --git a/arch/arm/mach-vexpress/tc2_pm_setup.S b/arch/arm/mach-vexpress/tc2_pm_setup.S index 046890e5ff8..4728f83731a 100644 --- a/arch/arm/mach-vexpress/tc2_pm_setup.S +++ b/arch/arm/mach-vexpress/tc2_pm_setup.S @@ -16,6 +16,7 @@ #define SPC_PHYS_BASE 0x7FFF0000 +#define SPC_WAKE_INT_STAT 0xb2c #define SNOOP_CTL_A15 0x404 #define SNOOP_CTL_A7 0x504 @@ -42,6 +43,19 @@ #define CCI_A7_OFFSET CCI_SLAVE_OFFSET(CCI_SLAVE_A7) +ENTRY(tc2_resume) + mrc p15, 0, r0, c0, c0, 5 + ubfx r1, r0, #0, #4 @ r1 = cpu + ubfx r2, r0, #8, #4 @ r2 = cluster + add r1, r1, r2, lsl #2 @ r1 = index of CPU in WAKE_INT_STAT + ldr r3, =SPC_PHYS_BASE + SPC_WAKE_INT_STAT + ldr r3, [r3] + lsr r3, r1 + tst r3, #1 + wfieq @ if no pending IRQ reenters wfi + b mcpm_entry_point +ENDPROC(tc2_resume) + /* * Enable cluster-level coherency, in preparation for turning on the MMU. * The ACTLR SMP bit does not need to be set here, because cpu_resume() -- cgit v1.2.3 From a7dda41efb86708ed48b99657e4e3c747d2e676b Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Tue, 14 May 2013 10:05:05 +0100 Subject: cpuidle: arm_big_little: Initialise earlier by using device_initcall Using late_initcall is too late for IKS. Requested-by: Nicolas Pitre Signed-off-by: Jon Medhurst --- drivers/cpuidle/arm_big_little.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/cpuidle/arm_big_little.c b/drivers/cpuidle/arm_big_little.c index a430800d4a7..e5378896a8c 100644 --- a/drivers/cpuidle/arm_big_little.c +++ b/drivers/cpuidle/arm_big_little.c @@ -180,4 +180,4 @@ int __init bl_idle_init(void) return 0; } -late_initcall(bl_idle_init); +device_initcall(bl_idle_init); -- cgit v1.2.3 From d955295af4b96aa4a2b17a4602046f08cb3f3349 Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Thu, 9 May 2013 16:50:56 +0100 Subject: tc2_pm: fixup for new CCI driver Signed-off-by: Jon Medhurst --- arch/arm/mach-vexpress/tc2_pm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm/mach-vexpress/tc2_pm.c b/arch/arm/mach-vexpress/tc2_pm.c index 54757301ca5..d2f84272ae8 100644 --- a/arch/arm/mach-vexpress/tc2_pm.c +++ b/arch/arm/mach-vexpress/tc2_pm.c @@ -128,7 +128,7 @@ static void tc2_pm_down(u64 residency) asm volatile ("clrex"); set_auxcr(get_auxcr() & ~(1 << 6)); - disable_cci(cluster); + cci_disable_port_by_cpu(mpidr); /* * Ensure that both C & I bits are disabled in the SCTLR -- cgit v1.2.3 From 5ecac7d2203156ab963db3b5c34712395f83562b Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Wed, 22 May 2013 17:04:06 -0400 Subject: ARM: vexpress: use generic CCI code to turn on CCI ports on TC2 Signed-off-by: Nicolas Pitre --- arch/arm/mach-vexpress/tc2_pm_setup.S | 36 +---------------------------------- 1 file changed, 1 insertion(+), 35 deletions(-) diff --git a/arch/arm/mach-vexpress/tc2_pm_setup.S b/arch/arm/mach-vexpress/tc2_pm_setup.S index 4728f83731a..a18dafeeb0e 100644 --- a/arch/arm/mach-vexpress/tc2_pm_setup.S +++ b/arch/arm/mach-vexpress/tc2_pm_setup.S @@ -27,22 +27,6 @@ #define A15_BX_ADDR0 0xB68 -#define CCI_PHYS_BASE 0x2c090000 - -#define SLAVE_SNOOPCTL_OFFSET 0 -#define SNOOPCTL_SNOOP_ENABLE (1 << 0) -#define SNOOPCTL_DVM_ENABLE (1 << 1) - -#define CCI_STATUS_OFFSET 0xc -#define STATUS_CHANGE_PENDING (1 << 0) - -#define CCI_SLAVE_OFFSET(n) (0x1000 + 0x1000 * (n)) -#define CCI_SLAVE_A15 3 -#define CCI_SLAVE_A7 4 -#define CCI_A15_OFFSET CCI_SLAVE_OFFSET(CCI_SLAVE_A15) -#define CCI_A7_OFFSET CCI_SLAVE_OFFSET(CCI_SLAVE_A7) - - ENTRY(tc2_resume) mrc p15, 0, r0, c0, c0, 5 ubfx r1, r0, #0, #4 @ r1 = cpu @@ -67,25 +51,7 @@ ENTRY(tc2_pm_power_up_setup) cmp r0, #0 beq 2f - @ Enable CCI snoops - mrc p15, 0, r0, c0, c0, 5 @ MPIDR - ubfx r0, r0, #8, #4 @ cluster - ldr r3, =CCI_PHYS_BASE + CCI_A15_OFFSET - cmp r0, #0 @ A15 cluster? - addne r3, r3, #CCI_A7_OFFSET - CCI_A15_OFFSET - - @ r3 now points to the correct CCI slave register block - ldr r0, [r3, #SLAVE_SNOOPCTL_OFFSET] - orr r0, r0, #SNOOPCTL_SNOOP_ENABLE | SNOOPCTL_DVM_ENABLE - str r0, [r3, #SLAVE_SNOOPCTL_OFFSET] @ enable CCI snoops - - @ Wait for snoop control change to complete: - ldr r3, =CCI_PHYS_BASE -1: ldr r0, [r3, #CCI_STATUS_OFFSET] - tst r0, #STATUS_CHANGE_PENDING - bne 1b - - bx lr + b cci_enable_port_for_self 2: @ Clear the BX addr register ldr r3, =SPC_PHYS_BASE + A15_BX_ADDR0 -- cgit v1.2.3 From dd94c6f646873c2f16b70e1cc82451fe1c2a83c6 Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Fri, 31 May 2013 13:43:05 +0100 Subject: tc2_pm: Fixup for new SPC driver Signed-off-by: Jon Medhurst --- arch/arm/mach-vexpress/Kconfig | 2 +- arch/arm/mach-vexpress/tc2_pm.c | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/arch/arm/mach-vexpress/Kconfig b/arch/arm/mach-vexpress/Kconfig index 1ce8798e7ee..d8b86861a7d 100644 --- a/arch/arm/mach-vexpress/Kconfig +++ b/arch/arm/mach-vexpress/Kconfig @@ -60,7 +60,7 @@ config ARCH_VEXPRESS_CA9X4 config ARCH_VEXPRESS_TC2 bool "TC2 cluster management" depends on MCPM - select ARM_SPC + select VEXPRESS_SPC select ARM_CCI help Support for CPU and cluster power management on TC2. diff --git a/arch/arm/mach-vexpress/tc2_pm.c b/arch/arm/mach-vexpress/tc2_pm.c index d2f84272ae8..aa866e5f9ea 100644 --- a/arch/arm/mach-vexpress/tc2_pm.c +++ b/arch/arm/mach-vexpress/tc2_pm.c @@ -59,7 +59,7 @@ static int tc2_pm_power_up(unsigned int cpu, unsigned int cluster) tc2_pm_use_count[cpu][cluster]++; if (tc2_pm_use_count[cpu][cluster] == 1) { - vexpress_spc_write_bxaddr_reg(cluster, cpu, + vexpress_spc_write_resume_reg(cluster, cpu, virt_to_phys(mcpm_entry_point)); vexpress_spc_set_cpu_wakeup_irq(cpu, cluster, 1); } else if (tc2_pm_use_count[cpu][cluster] != 2) { @@ -178,7 +178,7 @@ static void tc2_pm_suspend(u64 residency) mpidr = read_cpuid_mpidr(); cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0); cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1); - vexpress_spc_write_bxaddr_reg(cluster, cpu, + vexpress_spc_write_resume_reg(cluster, cpu, virt_to_phys(tc2_resume)); tc2_pm_down(residency); @@ -210,7 +210,7 @@ static void tc2_pm_powered_up(void) tc2_pm_use_count[cpu][cluster] = 1; vexpress_spc_set_cpu_wakeup_irq(cpu, cluster, 0); - vexpress_spc_write_bxaddr_reg(cluster, cpu, 0); + vexpress_spc_write_resume_reg(cluster, cpu, 0); arch_spin_unlock(&tc2_pm_lock); local_irq_restore(flags); -- cgit v1.2.3 From 922b78c7094e1db6fcb3a0e39c91c5a4cdde8957 Mon Sep 17 00:00:00 2001 From: Achin Gupta Date: Sun, 10 Mar 2013 22:36:51 +0000 Subject: ARM: TC2: replace hard coded cluster and cpu values with constants This patch adds constants in a tc2 specific header file to prevent use of hard coded values for specifying the number of cpus and clusters. Signed-off-by: Achin Gupta Signed-off-by: Liviu Dudau --- arch/arm/mach-vexpress/include/mach/tc2.h | 10 ++++++++++ arch/arm/mach-vexpress/tc2_pm.c | 16 +++++++++++----- 2 files changed, 21 insertions(+), 5 deletions(-) create mode 100644 arch/arm/mach-vexpress/include/mach/tc2.h diff --git a/arch/arm/mach-vexpress/include/mach/tc2.h b/arch/arm/mach-vexpress/include/mach/tc2.h new file mode 100644 index 00000000000..d3b5a2225a0 --- /dev/null +++ b/arch/arm/mach-vexpress/include/mach/tc2.h @@ -0,0 +1,10 @@ +#ifndef __MACH_TC2_H +#define __MACH_TC2_H + +/* + * cpu and cluster limits + */ +#define TC2_MAX_CPUS 3 +#define TC2_MAX_CLUSTERS 2 + +#endif diff --git a/arch/arm/mach-vexpress/tc2_pm.c b/arch/arm/mach-vexpress/tc2_pm.c index aa866e5f9ea..fddea7c3a2a 100644 --- a/arch/arm/mach-vexpress/tc2_pm.c +++ b/arch/arm/mach-vexpress/tc2_pm.c @@ -25,6 +25,7 @@ #include #include +#include #include #include @@ -37,12 +38,13 @@ */ static arch_spinlock_t tc2_pm_lock = __ARCH_SPIN_LOCK_UNLOCKED; -static int tc2_pm_use_count[3][2]; +static int tc2_pm_use_count[TC2_MAX_CPUS][TC2_MAX_CLUSTERS]; static int tc2_pm_power_up(unsigned int cpu, unsigned int cluster) { pr_debug("%s: cpu %u cluster %u\n", __func__, cpu, cluster); - if (cluster >= 2 || cpu >= vexpress_spc_get_nb_cpus(cluster)) + if (cluster >= TC2_MAX_CLUSTERS || + cpu >= vexpress_spc_get_nb_cpus(cluster)) return -EINVAL; /* @@ -90,7 +92,8 @@ static void tc2_pm_down(u64 residency) cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1); pr_debug("%s: cpu %u cluster %u\n", __func__, cpu, cluster); - BUG_ON(cluster >= 2 || cpu >= vexpress_spc_get_nb_cpus(cluster)); + BUG_ON(cluster >= TC2_MAX_CLUSTERS || + cpu >= vexpress_spc_get_nb_cpus(cluster)); __mcpm_cpu_going_down(cpu, cluster); @@ -194,7 +197,8 @@ static void tc2_pm_powered_up(void) cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1); pr_debug("%s: cpu %u cluster %u\n", __func__, cpu, cluster); - BUG_ON(cluster >= 2 || cpu >= vexpress_spc_get_nb_cpus(cluster)); + BUG_ON(cluster >= TC2_MAX_CLUSTERS || + cpu >= vexpress_spc_get_nb_cpus(cluster)); local_irq_save(flags); arch_spin_lock(&tc2_pm_lock); @@ -232,7 +236,9 @@ static void __init tc2_pm_usage_count_init(void) cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1); pr_debug("%s: cpu %u cluster %u\n", __func__, cpu, cluster); - BUG_ON(cpu >= 3 || cluster >= 2); + BUG_ON(cluster >= TC2_MAX_CLUSTERS || + cpu >= vexpress_spc_get_nb_cpus(cluster)); + tc2_pm_use_count[cpu][cluster] = 1; } -- cgit v1.2.3 From 58068c45b899873906a6dccc94715a3e72387d42 Mon Sep 17 00:00:00 2001 From: Achin Gupta Date: Mon, 17 Dec 2012 00:15:00 +0000 Subject: ARM: vexpress: allow native pm ops backends to probe for psci suppport This patch allows the vexpress 'tc2' native backend to probe the dt for presence of the psci backend. If present then the native implementation of the 'bL_platform_power_ops' is not used. Signed-off-by: Achin Gupta Signed-off-by: Liviu Dudau --- arch/arm/mach-vexpress/tc2_pm.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/arch/arm/mach-vexpress/tc2_pm.c b/arch/arm/mach-vexpress/tc2_pm.c index fddea7c3a2a..9c742edb7ae 100644 --- a/arch/arm/mach-vexpress/tc2_pm.c +++ b/arch/arm/mach-vexpress/tc2_pm.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -248,6 +249,12 @@ static int __init tc2_pm_init(void) { int ret; + ret = psci_probe(); + if (!ret) { + pr_debug("psci found. Aborting native init\n"); + return -ENODEV; + } + if (!vexpress_spc_check_loaded()) return -ENODEV; -- cgit v1.2.3 From dfd6f0c31471ceb6438f054922aadba39c2888ff Mon Sep 17 00:00:00 2001 From: Achin Gupta Date: Tue, 26 Feb 2013 15:33:25 +0000 Subject: ARM: vexpress: add shim layer for psci backend on TC2 This patch introduces a shim layer for the TC2 platform which converts 'bL_platform_power_ops' routines to their psci counterparts. The psci counterparts are implemented by the secure firmware. The shim layer is used only when Linux is running in non-secure world and the secure firmware implements psci. It also introduces the use of a reference count to allow a power up call to go ahead of a power down call. Signed-off-by: Achin Gupta Signed-off-by: Liviu Dudau --- arch/arm/mach-vexpress/Makefile | 4 + arch/arm/mach-vexpress/tc2_pm_psci.c | 179 +++++++++++++++++++++++++++++++++++ 2 files changed, 183 insertions(+) create mode 100644 arch/arm/mach-vexpress/tc2_pm_psci.c diff --git a/arch/arm/mach-vexpress/Makefile b/arch/arm/mach-vexpress/Makefile index fd9bcfd7431..59cdec9cbd7 100644 --- a/arch/arm/mach-vexpress/Makefile +++ b/arch/arm/mach-vexpress/Makefile @@ -8,5 +8,9 @@ obj-y := v2m.o obj-$(CONFIG_ARCH_VEXPRESS_CA9X4) += ct-ca9x4.o obj-$(CONFIG_ARCH_VEXPRESS_TC2) += tc2_pm.o tc2_pm_setup.o CFLAGS_REMOVE_tc2_pm.o = -pg +ifeq ($(CONFIG_ARCH_VEXPRESS_TC2),y) +obj-$(CONFIG_ARM_PSCI) += tc2_pm_psci.o +CFLAGS_REMOVE_tc2_pm_psci.o = -pg +endif obj-$(CONFIG_SMP) += platsmp.o obj-$(CONFIG_HOTPLUG_CPU) += hotplug.o diff --git a/arch/arm/mach-vexpress/tc2_pm_psci.c b/arch/arm/mach-vexpress/tc2_pm_psci.c new file mode 100644 index 00000000000..c9715b8137e --- /dev/null +++ b/arch/arm/mach-vexpress/tc2_pm_psci.c @@ -0,0 +1,179 @@ +/* + * arch/arm/mach-vexpress/tc2_pm_psci.c - TC2 PSCI support + * + * Created by: Achin Gupta, December 2012 + * Copyright: (C) 2012 ARM Limited + * + * Some portions of this file were originally written by Nicolas Pitre + * Copyright: (C) 2012 Linaro Limited + * + * 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. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +/* + * Platform specific state id understood by the firmware and used to + * program the power controller + */ +#define PSCI_POWER_STATE_ID 0 + +static atomic_t tc2_pm_use_count[TC2_MAX_CPUS][TC2_MAX_CLUSTERS]; + +static int tc2_pm_psci_power_up(unsigned int cpu, unsigned int cluster) +{ + unsigned int mpidr = (cluster << 8) | cpu; + int ret = 0; + + BUG_ON(!psci_ops.cpu_on); + + switch (atomic_inc_return(&tc2_pm_use_count[cpu][cluster])) { + case 1: + /* + * This is a request to power up a cpu that linux thinks has + * been powered down. Retries are needed if the firmware has + * seen the power down request as yet. + */ + do + ret = psci_ops.cpu_on(mpidr, + virt_to_phys(bL_entry_point)); + while (ret == -EAGAIN); + + return ret; + case 2: + /* This power up request has overtaken a power down request */ + return ret; + default: + /* Any other value is a bug */ + BUG(); + } +} + +static void tc2_pm_psci_power_down(void) +{ + struct psci_power_state power_state; + unsigned int mpidr, cpu, cluster; + + mpidr = read_cpuid_mpidr(); + cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0); + cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1); + + BUG_ON(!psci_ops.cpu_off); + + switch (atomic_dec_return(&tc2_pm_use_count[cpu][cluster])) { + case 1: + /* + * Overtaken by a power up. Flush caches, exit coherency, + * return & fake a reset + */ + asm volatile ( + "mrc p15, 0, ip, c1, c0, 0 \n\t" + "bic ip, ip, #(1 << 2) @ clear C bit \n\t" + "mcr p15, 0, ip, c1, c0, 0 \n\t" + "dsb \n\t" + "isb" + : : : "ip" ); + + flush_cache_louis(); + + asm volatile ( + "clrex \n\t" + "mrc p15, 0, ip, c1, c0, 1 \n\t" + "bic ip, ip, #(1 << 6) @ clear SMP bit \n\t" + "mcr p15, 0, ip, c1, c0, 1 \n\t" + "isb \n\t" + "dsb" + : : : "ip" ); + + return; + case 0: + /* A normal request to possibly power down the cluster */ + power_state.id = PSCI_POWER_STATE_ID; + power_state.type = PSCI_POWER_STATE_TYPE_POWER_DOWN; + power_state.affinity_level = PSCI_POWER_STATE_AFFINITY_LEVEL1; + + psci_ops.cpu_off(power_state); + + /* On success this function never returns */ + default: + /* Any other value is a bug */ + BUG(); + } +} + +static void tc2_pm_psci_suspend(u64 unused) +{ + struct psci_power_state power_state; + + BUG_ON(!psci_ops.cpu_suspend); + + /* On TC2 always attempt to power down the cluster */ + power_state.id = PSCI_POWER_STATE_ID; + power_state.type = PSCI_POWER_STATE_TYPE_POWER_DOWN; + power_state.affinity_level = PSCI_POWER_STATE_AFFINITY_LEVEL1; + + psci_ops.cpu_suspend(power_state, virt_to_phys(bL_entry_point)); + + /* On success this function never returns */ + BUG(); +} + +static const struct bL_platform_power_ops tc2_pm_power_ops = { + .power_up = tc2_pm_psci_power_up, + .power_down = tc2_pm_psci_power_down, + .suspend = tc2_pm_psci_suspend, +}; + +static void __init tc2_pm_usage_count_init(void) +{ + unsigned int mpidr, cpu, cluster; + + mpidr = read_cpuid_mpidr(); + cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0); + cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1); + + pr_debug("%s: cpu %u cluster %u\n", __func__, cpu, cluster); + BUG_ON(cluster >= TC2_MAX_CLUSTERS || + cpu >= vexpress_spc_get_nb_cpus(cluster)); + + atomic_set(&tc2_pm_use_count[cpu][cluster], 1); +} + +static int __init tc2_pm_psci_init(void) +{ + int ret; + + ret = psci_probe(); + if (ret) { + pr_debug("psci not found. Aborting psci init\n"); + return -ENODEV; + } + + tc2_pm_usage_count_init(); + + ret = bL_platform_power_register(&tc2_pm_power_ops); + if (!ret) + ret = bL_cluster_sync_init(NULL); + if (!ret) + pr_info("TC2 power management initialized\n"); + return ret; +} + +early_initcall(tc2_pm_psci_init); -- cgit v1.2.3 From 4997791921e168d5bd8dd707deb360f6d32f5d7e Mon Sep 17 00:00:00 2001 From: Achin Gupta Date: Sun, 16 Dec 2012 22:41:02 +0000 Subject: ARM: vexpress: add psci support in TC2 device tree This patch adds a psci device node to allow the ospm subsystems on the TC2 to work with a psci backend implemented in the secure firmware. The function offsets start from 1 instead of 0 as thats whats the current secure firmware implements. Signed-off-by: Achin Gupta Signed-off-by: Liviu Dudau --- arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts b/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts index 1abef13f9e9..cd621a42b0d 100644 --- a/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts +++ b/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts @@ -147,6 +147,15 @@ clock-output-names = "oscclk6a"; }; + psci { + compatible = "arm,psci"; + method = "smc"; + cpu_suspend = <0x80100001>; + cpu_off = <0x80100002>; + cpu_on = <0x80100003>; + migrate = <0x80100004>; + }; + dcc { compatible = "arm,vexpress,config-bus"; arm,vexpress,config-bridge = <&v2m_sysreg>; -- cgit v1.2.3 From e64a97aa17f483b616b9adc93e74daee1eabf4a0 Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Thu, 21 Mar 2013 11:15:56 +0000 Subject: ARM: vexpress: Fixup tc2_pm_psci.c for mcpm APIs Signed-off-by: Jon Medhurst --- arch/arm/mach-vexpress/tc2_pm_psci.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/arch/arm/mach-vexpress/tc2_pm_psci.c b/arch/arm/mach-vexpress/tc2_pm_psci.c index c9715b8137e..04d83d2965f 100644 --- a/arch/arm/mach-vexpress/tc2_pm_psci.c +++ b/arch/arm/mach-vexpress/tc2_pm_psci.c @@ -17,7 +17,7 @@ #include #include -#include +#include #include #include #include @@ -53,7 +53,7 @@ static int tc2_pm_psci_power_up(unsigned int cpu, unsigned int cluster) */ do ret = psci_ops.cpu_on(mpidr, - virt_to_phys(bL_entry_point)); + virt_to_phys(mcpm_entry_point)); while (ret == -EAGAIN); return ret; @@ -129,13 +129,13 @@ static void tc2_pm_psci_suspend(u64 unused) power_state.type = PSCI_POWER_STATE_TYPE_POWER_DOWN; power_state.affinity_level = PSCI_POWER_STATE_AFFINITY_LEVEL1; - psci_ops.cpu_suspend(power_state, virt_to_phys(bL_entry_point)); + psci_ops.cpu_suspend(power_state, virt_to_phys(mcpm_entry_point)); /* On success this function never returns */ BUG(); } -static const struct bL_platform_power_ops tc2_pm_power_ops = { +static const struct mcpm_platform_ops tc2_pm_power_ops = { .power_up = tc2_pm_psci_power_up, .power_down = tc2_pm_psci_power_down, .suspend = tc2_pm_psci_suspend, @@ -168,9 +168,9 @@ static int __init tc2_pm_psci_init(void) tc2_pm_usage_count_init(); - ret = bL_platform_power_register(&tc2_pm_power_ops); + ret = mcpm_platform_register(&tc2_pm_power_ops); if (!ret) - ret = bL_cluster_sync_init(NULL); + ret = mcpm_sync_init(NULL); if (!ret) pr_info("TC2 power management initialized\n"); return ret; -- cgit v1.2.3 From b62c31f5262bdba8171287cfe92df7bdeed1af31 Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Thu, 21 Mar 2013 12:00:09 +0000 Subject: ARM: vexpress: Get tc2_pm_psci.c to use common CP15 accessor functions Signed-off-by: Jon Medhurst --- arch/arm/mach-vexpress/tc2_pm_psci.c | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/arch/arm/mach-vexpress/tc2_pm_psci.c b/arch/arm/mach-vexpress/tc2_pm_psci.c index 04d83d2965f..5a5e4f56849 100644 --- a/arch/arm/mach-vexpress/tc2_pm_psci.c +++ b/arch/arm/mach-vexpress/tc2_pm_psci.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -83,24 +84,12 @@ static void tc2_pm_psci_power_down(void) * Overtaken by a power up. Flush caches, exit coherency, * return & fake a reset */ - asm volatile ( - "mrc p15, 0, ip, c1, c0, 0 \n\t" - "bic ip, ip, #(1 << 2) @ clear C bit \n\t" - "mcr p15, 0, ip, c1, c0, 0 \n\t" - "dsb \n\t" - "isb" - : : : "ip" ); + set_cr(get_cr() & ~CR_C); flush_cache_louis(); - asm volatile ( - "clrex \n\t" - "mrc p15, 0, ip, c1, c0, 1 \n\t" - "bic ip, ip, #(1 << 6) @ clear SMP bit \n\t" - "mcr p15, 0, ip, c1, c0, 1 \n\t" - "isb \n\t" - "dsb" - : : : "ip" ); + asm volatile ("clrex"); + set_auxcr(get_auxcr() & ~(1 << 6)); return; case 0: -- cgit v1.2.3 From 521caf641e76b48016e25b66e37799a9333ede39 Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Wed, 19 Jun 2013 10:17:00 +0100 Subject: ARM: vexpress: Ensure SPC driver is loaded before using it in PSCI init Otherwise we get a nullptr dereference calling vexpress_spc_get_nb_cpus. Signed-off-by: Jon Medhurst --- arch/arm/mach-vexpress/tc2_pm_psci.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/arch/arm/mach-vexpress/tc2_pm_psci.c b/arch/arm/mach-vexpress/tc2_pm_psci.c index 5a5e4f56849..c2fdc22e4c0 100644 --- a/arch/arm/mach-vexpress/tc2_pm_psci.c +++ b/arch/arm/mach-vexpress/tc2_pm_psci.c @@ -155,6 +155,11 @@ static int __init tc2_pm_psci_init(void) return -ENODEV; } + if (!vexpress_spc_check_loaded()) { + pr_debug("spc not found. Aborting psci init\n"); + return -ENODEV; + } + tc2_pm_usage_count_init(); ret = mcpm_platform_register(&tc2_pm_power_ops); -- cgit v1.2.3 From dd30bf6dec22f4f3400b702de7feae496ade5093 Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Mon, 29 Apr 2013 14:07:37 +0530 Subject: cpufreq: ARM big LITTLE: Add depends on BIG_LITTLE Signed-off-by: Viresh Kumar --- drivers/cpufreq/Kconfig.arm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm index 6e57543fe0b..9c6a69112e8 100644 --- a/drivers/cpufreq/Kconfig.arm +++ b/drivers/cpufreq/Kconfig.arm @@ -4,7 +4,7 @@ config ARM_BIG_LITTLE_CPUFREQ tristate "Generic ARM big LITTLE CPUfreq driver" - depends on ARM_CPU_TOPOLOGY && PM_OPP && HAVE_CLK + depends on ARM_CPU_TOPOLOGY && PM_OPP && HAVE_CLK && BIG_LITTLE help This enables the Generic CPUfreq driver for ARM big.LITTLE platforms. -- cgit v1.2.3 From 34620c33bdfda20d1b7952468449bff05c70dec2 Mon Sep 17 00:00:00 2001 From: Sudeep KarkadaNagesha Date: Fri, 13 Jul 2012 17:05:30 +0100 Subject: ARM: Select ARCH_HAS_CPUFREQ for ARCH_VEXPRESS This patch enables ARCH_HAS_CPUFREQ for Versatile Express platforms in order to support CPU frequency scaling. Signed-off-by: Sudeep KarkadaNagesha --- arch/arm/mach-vexpress/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm/mach-vexpress/Kconfig b/arch/arm/mach-vexpress/Kconfig index 5907e10c37f..01c18dea1c2 100644 --- a/arch/arm/mach-vexpress/Kconfig +++ b/arch/arm/mach-vexpress/Kconfig @@ -1,5 +1,6 @@ config ARCH_VEXPRESS bool "ARM Ltd. Versatile Express family" if ARCH_MULTI_V7 + select ARCH_HAS_CPUFREQ select ARCH_REQUIRE_GPIOLIB select ARM_AMBA select ARM_GIC -- cgit v1.2.3 From 9c218a698947120c0b0463468685f34bff0171e7 Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Mon, 29 Apr 2013 15:36:46 +0530 Subject: Vexpress: Enable OPP library for cpufreq Signed-off-by: Viresh Kumar --- arch/arm/mach-vexpress/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm/mach-vexpress/Kconfig b/arch/arm/mach-vexpress/Kconfig index 01c18dea1c2..f6ba1716e40 100644 --- a/arch/arm/mach-vexpress/Kconfig +++ b/arch/arm/mach-vexpress/Kconfig @@ -1,6 +1,7 @@ config ARCH_VEXPRESS bool "ARM Ltd. Versatile Express family" if ARCH_MULTI_V7 select ARCH_HAS_CPUFREQ + select ARCH_HAS_OPP select ARCH_REQUIRE_GPIOLIB select ARM_AMBA select ARM_GIC -- cgit v1.2.3 From 07ae43edc3b2e692356671f9444832fc291ae093 Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Sat, 24 Nov 2012 00:51:07 +0530 Subject: clk: Vexpress-spc: Add clock driver This patch adds spc clock controller. In Vexpress cpu cluster clock is controlled via spc controller and so it must be present in clk framework. vexpress_clk_[of_]register_spc() registers cluster clocks with and without DT. These are added as root clocks without any parents and their names are "cluster[0|1|..]". Now, platform must add clocks of all the cpus below these clusters. cpufreq driver would get cpu clock and will do clk_get[set]_rate() on cpu clock, which will then pass it to cluster clocks. And finally spc will get programmed. This patch doesn't add non-DT clocks for clusters and cpus as i don't see a user of that for now. Signed-off-by: Viresh Kumar --- drivers/clk/versatile/Makefile | 2 +- drivers/clk/versatile/clk-vexpress-spc.c | 131 +++++++++++++++++++++++++++++++ include/linux/vexpress.h | 3 + 3 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 drivers/clk/versatile/clk-vexpress-spc.c diff --git a/drivers/clk/versatile/Makefile b/drivers/clk/versatile/Makefile index c16ca787170..6e76bf87ca8 100644 --- a/drivers/clk/versatile/Makefile +++ b/drivers/clk/versatile/Makefile @@ -4,4 +4,4 @@ obj-$(CONFIG_ARCH_INTEGRATOR) += clk-integrator.o obj-$(CONFIG_INTEGRATOR_IMPD1) += clk-impd1.o obj-$(CONFIG_ARCH_REALVIEW) += clk-realview.o obj-$(CONFIG_ARCH_VEXPRESS) += clk-vexpress.o clk-sp810.o -obj-$(CONFIG_VEXPRESS_CONFIG) += clk-vexpress-osc.o +obj-$(CONFIG_VEXPRESS_CONFIG) += clk-vexpress-osc.o clk-vexpress-spc.o diff --git a/drivers/clk/versatile/clk-vexpress-spc.c b/drivers/clk/versatile/clk-vexpress-spc.c new file mode 100644 index 00000000000..b701ae41475 --- /dev/null +++ b/drivers/clk/versatile/clk-vexpress-spc.c @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2012 ARM Limited + * Copyright (C) 2012 Linaro + * + * Author: Viresh Kumar + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +/* SPC clock programming interface for Vexpress cpus */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include + +struct clk_spc { + struct clk_hw hw; + spinlock_t *lock; + int cluster; +}; + +#define to_clk_spc(spc) container_of(spc, struct clk_spc, hw) + +static unsigned long spc_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct clk_spc *spc = to_clk_spc(hw); + u32 freq; + + if (vexpress_spc_get_performance(spc->cluster, &freq)) { + return -EIO; + pr_err("%s: Failed", __func__); + } + + return freq * 1000; +} + +static long spc_round_rate(struct clk_hw *hw, unsigned long drate, + unsigned long *parent_rate) +{ + return drate; +} + +static int spc_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct clk_spc *spc = to_clk_spc(hw); + + return vexpress_spc_set_performance(spc->cluster, rate / 1000); +} + +static struct clk_ops clk_spc_ops = { + .recalc_rate = spc_recalc_rate, + .round_rate = spc_round_rate, + .set_rate = spc_set_rate, +}; + +struct clk *vexpress_clk_register_spc(const char *name, int cluster_id) +{ + struct clk_init_data init; + struct clk_spc *spc; + struct clk *clk; + + if (!name) { + pr_err("Invalid name passed"); + return ERR_PTR(-EINVAL); + } + + spc = kzalloc(sizeof(*spc), GFP_KERNEL); + if (!spc) { + pr_err("could not allocate spc clk\n"); + return ERR_PTR(-ENOMEM); + } + + spc->hw.init = &init; + spc->cluster = cluster_id; + + init.name = name; + init.ops = &clk_spc_ops; + init.flags = CLK_IS_ROOT | CLK_GET_RATE_NOCACHE; + init.num_parents = 0; + + clk = clk_register(NULL, &spc->hw); + if (!IS_ERR_OR_NULL(clk)) + return clk; + + pr_err("clk register failed\n"); + kfree(spc); + + return NULL; +} + +#if defined(CONFIG_OF) +void __init vexpress_clk_of_register_spc(void) +{ + char name[14] = "cpu-cluster."; + struct device_node *node = NULL; + struct clk *clk; + const u32 *val; + int cluster_id = 0, len; + + if (!of_find_compatible_node(NULL, NULL, "arm,spc")) { + pr_debug("%s: No SPC found, Exiting!!\n", __func__); + return; + } + + while ((node = of_find_node_by_name(node, "cluster"))) { + val = of_get_property(node, "reg", &len); + if (val && len == 4) + cluster_id = be32_to_cpup(val); + + name[12] = cluster_id + '0'; + clk = vexpress_clk_register_spc(name, cluster_id); + if (IS_ERR(clk)) + return; + + pr_debug("Registered clock '%s'\n", name); + clk_register_clkdev(clk, NULL, name); + } +} +CLK_OF_DECLARE(spc, "arm,spc", vexpress_clk_of_register_spc); +#endif diff --git a/include/linux/vexpress.h b/include/linux/vexpress.h index ea7168a6808..6e7980de37f 100644 --- a/include/linux/vexpress.h +++ b/include/linux/vexpress.h @@ -120,6 +120,9 @@ void vexpress_sysreg_of_early_init(void); struct clk *vexpress_osc_setup(struct device *dev); void vexpress_osc_of_setup(struct device_node *node); +struct clk *vexpress_clk_register_spc(const char *name, int cluster_id); +void vexpress_clk_of_register_spc(void); + void vexpress_clk_init(void __iomem *sp810_base); void vexpress_clk_of_init(void); -- cgit v1.2.3 From 4566d7de202da83bc464b32dac2fd81cf1fbac92 Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Fri, 31 May 2013 15:10:41 +0100 Subject: clk: Vexpress-spc: Fixup for new SPC driver Signed-off-by: Jon Medhurst --- drivers/clk/versatile/clk-vexpress-spc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/clk/versatile/clk-vexpress-spc.c b/drivers/clk/versatile/clk-vexpress-spc.c index b701ae41475..37125bf48cb 100644 --- a/drivers/clk/versatile/clk-vexpress-spc.c +++ b/drivers/clk/versatile/clk-vexpress-spc.c @@ -108,7 +108,7 @@ void __init vexpress_clk_of_register_spc(void) const u32 *val; int cluster_id = 0, len; - if (!of_find_compatible_node(NULL, NULL, "arm,spc")) { + if (!of_find_compatible_node(NULL, NULL, "arm,vexpress-spc")) { pr_debug("%s: No SPC found, Exiting!!\n", __func__); return; } @@ -127,5 +127,5 @@ void __init vexpress_clk_of_register_spc(void) clk_register_clkdev(clk, NULL, name); } } -CLK_OF_DECLARE(spc, "arm,spc", vexpress_clk_of_register_spc); +CLK_OF_DECLARE(spc, "arm,vexpress-spc", vexpress_clk_of_register_spc); #endif -- cgit v1.2.3 From 51939511b8f12aacd134b0301f4ff2b1f579ea48 Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Mon, 29 Apr 2013 14:49:50 +0530 Subject: cpufreq: ARM big LITTLE: Add Vexpress glue driver Vexpress depends on motherboard firmware + spc for getting opp table. This patch adds Vexpress glue driver for ARM big LITTLE parent driver. Signed-off-by: Viresh Kumar --- drivers/cpufreq/Kconfig.arm | 8 ++++ drivers/cpufreq/Makefile | 1 + drivers/cpufreq/vexpress_big_little.c | 85 +++++++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+) create mode 100644 drivers/cpufreq/vexpress_big_little.c diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm index 9c6a69112e8..203e8959bde 100644 --- a/drivers/cpufreq/Kconfig.arm +++ b/drivers/cpufreq/Kconfig.arm @@ -15,6 +15,14 @@ config ARM_DT_BL_CPUFREQ This enables probing via DT for Generic CPUfreq driver for ARM big.LITTLE platform. This gets frequency tables from DT. +config ARM_VEXPRESS_BL_CPUFREQ + tristate "ARM Vexpress big LITTLE CPUfreq driver" + select ARM_BIG_LITTLE_CPUFREQ + depends on ARM_SPC + help + This enables the CPUfreq driver for ARM Vexpress big.LITTLE platform. + If in doubt, say N. + config ARM_EXYNOS_CPUFREQ bool "SAMSUNG EXYNOS SoCs" depends on ARCH_EXYNOS diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile index 315b9231feb..1db9b4929cf 100644 --- a/drivers/cpufreq/Makefile +++ b/drivers/cpufreq/Makefile @@ -48,6 +48,7 @@ obj-$(CONFIG_X86_AMD_FREQ_SENSITIVITY) += amd_freq_sensitivity.o obj-$(CONFIG_ARM_BIG_LITTLE_CPUFREQ) += arm_big_little.o # big LITTLE per platform glues. Keep DT_BL_CPUFREQ as the last entry in all big # LITTLE drivers, so that it is probed last. +obj-$(CONFIG_ARM_VEXPRESS_BL_CPUFREQ) += vexpress_big_little.o obj-$(CONFIG_ARM_DT_BL_CPUFREQ) += arm_big_little_dt.o obj-$(CONFIG_ARCH_DAVINCI_DA850) += davinci-cpufreq.o diff --git a/drivers/cpufreq/vexpress_big_little.c b/drivers/cpufreq/vexpress_big_little.c new file mode 100644 index 00000000000..aac24620704 --- /dev/null +++ b/drivers/cpufreq/vexpress_big_little.c @@ -0,0 +1,85 @@ +/* + * Vexpress big.LITTLE CPUFreq Interface driver + * + * It provides necessary ops to arm_big_little cpufreq driver and gets + * Frequency information from Device Tree. Freq table in DT must be in KHz. + * + * Copyright (C) 2013 Linaro. + * Viresh Kumar + * + * 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. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include "arm_big_little.h" + +static int vexpress_init_opp_table(struct device *cpu_dev) +{ + int i = -1, count, cluster = cpu_to_cluster(cpu_dev->id); + unsigned int *table = vexpress_spc_get_freq_table(cluster, &count); + int ret; + + if (!table || !count) { + pr_err("SPC controller returned invalid freq table"); + return -EINVAL; + } + + while (++i < count) { + /* FIXME: Voltage value */ + ret = opp_add(cpu_dev, table[i] * 1000, 900000); + if (ret) { + dev_warn(cpu_dev, "%s: Failed to add OPP %d, err: %d\n", + __func__, table[i] * 1000, ret); + return ret; + } + } + + return 0; +} + +static int vexpress_get_transition_latency(struct device *cpu_dev) +{ + /* 1 ms */ + return 1000000; +} + +static struct cpufreq_arm_bL_ops vexpress_bL_ops = { + .name = "vexpress-bL", + .get_transition_latency = vexpress_get_transition_latency, + .init_opp_table = vexpress_init_opp_table, +}; + +static int vexpress_bL_init(void) +{ + if (!vexpress_spc_check_loaded()) { + pr_info("%s: No SPC found\n", __func__); + return -ENOENT; + } + + return bL_cpufreq_register(&vexpress_bL_ops); +} +module_init(vexpress_bL_init); + +static void vexpress_bL_exit(void) +{ + return bL_cpufreq_unregister(&vexpress_bL_ops); +} +module_exit(vexpress_bL_exit); + +MODULE_AUTHOR("Viresh Kumar "); +MODULE_DESCRIPTION("ARM Vexpress big LITTLE cpufreq driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 9a5fed19b029d3c0ba22444d69f4422459b870be Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Fri, 31 May 2013 15:12:16 +0100 Subject: cpufreq: ARM big LITTLE: Fixup for new SPC driver Signed-off-by: Jon Medhurst --- drivers/cpufreq/Kconfig.arm | 2 +- drivers/cpufreq/vexpress_big_little.c | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm index 203e8959bde..8327444b76c 100644 --- a/drivers/cpufreq/Kconfig.arm +++ b/drivers/cpufreq/Kconfig.arm @@ -18,7 +18,7 @@ config ARM_DT_BL_CPUFREQ config ARM_VEXPRESS_BL_CPUFREQ tristate "ARM Vexpress big LITTLE CPUfreq driver" select ARM_BIG_LITTLE_CPUFREQ - depends on ARM_SPC + depends on VEXPRESS_SPC help This enables the CPUfreq driver for ARM Vexpress big.LITTLE platform. If in doubt, say N. diff --git a/drivers/cpufreq/vexpress_big_little.c b/drivers/cpufreq/vexpress_big_little.c index aac24620704..1abb883c051 100644 --- a/drivers/cpufreq/vexpress_big_little.c +++ b/drivers/cpufreq/vexpress_big_little.c @@ -30,9 +30,10 @@ static int vexpress_init_opp_table(struct device *cpu_dev) { int i = -1, count, cluster = cpu_to_cluster(cpu_dev->id); - unsigned int *table = vexpress_spc_get_freq_table(cluster, &count); + u32 *table; int ret; + count = vexpress_spc_get_freq_table(cluster, &table); if (!table || !count) { pr_err("SPC controller returned invalid freq table"); return -EINVAL; -- cgit v1.2.3