summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJon Medhurst <tixy@linaro.org>2014-08-12 10:16:41 +0100
committerJon Medhurst <tixy@linaro.org>2014-08-12 10:16:41 +0100
commitd43c5441a78dcb78e87a7b897cce5ef92e98bdb6 (patch)
treee8fe5c7877a66230db6faa19c6f396a8dd819c16
parent67d8124a96e68f0321f1eee7dc1a7c70ede8cef7 (diff)
parentd18ccdd3a0bf0c8a8c51d449606e8aa19910c67e (diff)
Merge branch 'tracking-armlt-juno-cpufreq' into integration-linaro-vexpresstracking-integration-linaro-vexpress-ll-20140818.0tracking-integration-linaro-vexpress-ll-20140814.0
-rw-r--r--Documentation/devicetree/bindings/clock/scpi.txt34
-rw-r--r--Documentation/devicetree/bindings/mailbox/mailbox.txt33
-rw-r--r--MAINTAINERS8
-rw-r--r--arch/arm/mach-highbank/highbank.c2
-rw-r--r--arch/arm64/include/asm/bL_switcher.h54
-rw-r--r--arch/arm64/include/asm/cputype.h5
-rw-r--r--arch/arm64/kernel/topology.c201
-rw-r--r--drivers/clk/Kconfig10
-rw-r--r--drivers/clk/Makefile1
-rw-r--r--drivers/clk/clk-scpi.c309
-rw-r--r--drivers/cpufreq/Kconfig.arm12
-rw-r--r--drivers/cpufreq/Makefile1
-rw-r--r--drivers/cpufreq/arm_big_little.c4
-rw-r--r--drivers/cpufreq/highbank-cpufreq.c2
-rw-r--r--drivers/cpufreq/scpi-cpufreq.c99
-rw-r--r--drivers/mailbox/Kconfig27
-rw-r--r--drivers/mailbox/Makefile7
-rw-r--r--drivers/mailbox/arm_mhu.c320
-rw-r--r--drivers/mailbox/arm_mhu.h33
-rw-r--r--drivers/mailbox/mailbox.c488
-rw-r--r--drivers/mailbox/pl320-ipc.c2
-rw-r--r--drivers/mailbox/scpi_protocol.c353
-rw-r--r--include/linux/mailbox_client.h45
-rw-r--r--include/linux/mailbox_controller.h121
-rw-r--r--include/linux/pl320-ipc.h (renamed from include/linux/mailbox.h)0
-rw-r--r--include/linux/scpi_protocol.h30
26 files changed, 2192 insertions, 9 deletions
diff --git a/Documentation/devicetree/bindings/clock/scpi.txt b/Documentation/devicetree/bindings/clock/scpi.txt
new file mode 100644
index 000000000000..b2b7035018f4
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/scpi.txt
@@ -0,0 +1,34 @@
+Device Tree Clock bindings for the clocks based on
+System Control and Power Interface (SCPI) Message Protocol
+
+This binding uses the common clock binding[1].
+
+Required properties:
+- compatible : shall be one of the following:
+ "arm,scpi-clks" - for the container node with all the clocks
+ based on the SCPI protocol
+ "arm,scpi-clk-indexed" - all the clocks that are variable and index
+ based. These clocks don't provide the full range between the
+ limits but only discrete points within the range. The firmware
+ provides the mapping for each such operating frequency and the
+ index associated with it.
+ "arm,scpi-clk-range" - all the clocks that are variable and provide
+ full range within the specified range
+
+Required properties for all clocks(all from common clock binding):
+- #clock-cells : ; shall be set to 0 or 1 depending on whether it has single
+ or multiple clock outputs.
+- clock-output-names : shall be the corresponding names of the outputs.
+- clock-indices: The identifyng number for the clocks in the node as expected
+ by the firmware. It can be non linear and hence provide the mapping
+ of identifiers into the clock-output-names array.
+- frequency-range: The allowed range of clock frequency supported specified
+ in the form of minimum and maximum limits(two u32 fields)
+ This is required only if compatible is "arm,scpi-clk-range"
+
+Clock consumers should specify the desired clocks they use with a
+"clocks" phandle cell. Consumers should also provide an additional ID
+in their clock property. This ID refers to the specific clock in the clock
+provider list.
+
+[1] Documentation/devicetree/bindings/clock/clock-bindings.txt
diff --git a/Documentation/devicetree/bindings/mailbox/mailbox.txt b/Documentation/devicetree/bindings/mailbox/mailbox.txt
new file mode 100644
index 000000000000..3f009555f392
--- /dev/null
+++ b/Documentation/devicetree/bindings/mailbox/mailbox.txt
@@ -0,0 +1,33 @@
+* Generic Mailbox Controller and client driver bindings
+
+Generic binding to provide a way for Mailbox controller drivers to
+assign appropriate mailbox channel to client drivers.
+
+* Mailbox Controller
+
+Required property:
+- #mbox-cells: Must be at least 1. Number of cells in a mailbox
+ specifier.
+
+Example:
+ mailbox: mailbox {
+ ...
+ #mbox-cells = <1>;
+ };
+
+
+* Mailbox Client
+
+Required property:
+- mbox: List of phandle and mailbox channel specifier.
+
+- mbox-names: List of identifier strings for each mailbox channel
+ required by the client.
+
+Example:
+ pwr_cntrl: power {
+ ...
+ mbox-names = "pwr-ctrl", "rpc";
+ mbox = <&mailbox 0
+ &mailbox 1>;
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index c2066f4c3286..1bcc1ccd0feb 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5672,6 +5672,14 @@ S: Maintained
F: drivers/net/macvlan.c
F: include/linux/if_macvlan.h
+MAILBOX API
+M: Jassi Brar <jassisinghbrar@gmail.com>
+L: linux-kernel@vger.kernel.org
+S: Maintained
+F: drivers/mailbox/
+F: include/linux/mailbox_client.h
+F: include/linux/mailbox_controller.h
+
MAN-PAGES: MANUAL PAGES FOR LINUX -- Sections 2, 3, 4, 5, and 7
M: Michael Kerrisk <mtk.manpages@gmail.com>
W: http://www.kernel.org/doc/man-pages
diff --git a/arch/arm/mach-highbank/highbank.c b/arch/arm/mach-highbank/highbank.c
index 8c35ae4ff176..07a09570175d 100644
--- a/arch/arm/mach-highbank/highbank.c
+++ b/arch/arm/mach-highbank/highbank.c
@@ -20,7 +20,7 @@
#include <linux/input.h>
#include <linux/io.h>
#include <linux/irqchip.h>
-#include <linux/mailbox.h>
+#include <linux/pl320-ipc.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
diff --git a/arch/arm64/include/asm/bL_switcher.h b/arch/arm64/include/asm/bL_switcher.h
new file mode 100644
index 000000000000..2bee500b7f54
--- /dev/null
+++ b/arch/arm64/include/asm/bL_switcher.h
@@ -0,0 +1,54 @@
+/*
+ * Based on the stubs for the ARM implementation which is:
+ *
+ * Created by: Nicolas Pitre, April 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.
+ */
+
+#ifndef ASM_BL_SWITCHER_H
+#define ASM_BL_SWITCHER_H
+
+#include <linux/notifier.h>
+#include <linux/types.h>
+
+typedef void (*bL_switch_completion_handler)(void *cookie);
+
+static inline int bL_switch_request(unsigned int cpu,
+ unsigned int new_cluster_id)
+{
+ return -ENOTSUPP;
+}
+
+/*
+ * Register here to be notified about runtime enabling/disabling of
+ * the switcher.
+ *
+ * The notifier chain is called with the switcher activation lock held:
+ * the switcher will not be enabled or disabled during callbacks.
+ * Callbacks must not call bL_switcher_{get,put}_enabled().
+ */
+#define BL_NOTIFY_PRE_ENABLE 0
+#define BL_NOTIFY_POST_ENABLE 1
+#define BL_NOTIFY_PRE_DISABLE 2
+#define BL_NOTIFY_POST_DISABLE 3
+
+static inline int bL_switcher_register_notifier(struct notifier_block *nb)
+{
+ return 0;
+}
+
+static inline int bL_switcher_unregister_notifier(struct notifier_block *nb)
+{
+ return 0;
+}
+
+static inline bool bL_switcher_get_enabled(void) { return false; }
+static inline void bL_switcher_put_enabled(void) { }
+static inline int bL_switcher_trace_trigger(void) { return 0; }
+static inline int bL_switcher_get_logical_index(u32 mpidr) { return -EUNATCH; }
+
+#endif
diff --git a/arch/arm64/include/asm/cputype.h b/arch/arm64/include/asm/cputype.h
index 27f54a7cc81b..54872696a17a 100644
--- a/arch/arm64/include/asm/cputype.h
+++ b/arch/arm64/include/asm/cputype.h
@@ -18,6 +18,8 @@
#define INVALID_HWID ULONG_MAX
+#define MPIDR_UP_BITMASK (0x1 << 30)
+#define MPIDR_MT_BITMASK (0x1 << 24)
#define MPIDR_HWID_BITMASK 0xff00ffffff
#define MPIDR_LEVEL_BITS_SHIFT 3
@@ -30,6 +32,9 @@
#define MPIDR_AFFINITY_LEVEL(mpidr, level) \
((mpidr >> MPIDR_LEVEL_SHIFT(level)) & MPIDR_LEVEL_MASK)
+#define MPIDR_AFF_MASK(level) \
+ ((u64)MPIDR_LEVEL_MASK << MPIDR_LEVEL_SHIFT(level))
+
#define read_cpuid(reg) ({ \
u64 __val; \
asm("mrs %0, " #reg : "=r" (__val)); \
diff --git a/arch/arm64/kernel/topology.c b/arch/arm64/kernel/topology.c
index 43514f905916..0bdeb6c1fcf4 100644
--- a/arch/arm64/kernel/topology.c
+++ b/arch/arm64/kernel/topology.c
@@ -19,9 +19,35 @@
#include <linux/nodemask.h>
#include <linux/of.h>
#include <linux/sched.h>
+#include <linux/slab.h>
+#include <asm/cputype.h>
+#include <asm/smp_plat.h>
#include <asm/topology.h>
+/*
+ * cpu power table
+ * This per cpu data structure describes the relative capacity of each core.
+ * On a heteregenous system, cores don't have the same computation capacity
+ * and we reflect that difference in the cpu_power field so the scheduler can
+ * take this difference into account during load balance. A per cpu structure
+ * is preferred because each CPU updates its own cpu_power field during the
+ * load balance except for idle cores. One idle core is selected to run the
+ * rebalance_domains for all idle cores and the cpu_power can be updated
+ * during this sequence.
+ */
+static DEFINE_PER_CPU(unsigned long, cpu_scale);
+
+unsigned long arch_scale_freq_power(struct sched_domain *sd, int cpu)
+{
+ return per_cpu(cpu_scale, cpu);
+}
+
+static void set_power_scale(unsigned int cpu, unsigned long power)
+{
+ per_cpu(cpu_scale, cpu) = power;
+}
+
static int __init get_cpu_for_node(struct device_node *node)
{
struct device_node *cpu_node;
@@ -160,6 +186,40 @@ static int __init parse_cluster(struct device_node *cluster, int depth)
return 0;
}
+struct cpu_efficiency {
+ const char *compatible;
+ unsigned long efficiency;
+};
+
+/*
+ * Table of relative efficiency of each processors
+ * The efficiency value must fit in 20bit and the final
+ * cpu_scale value must be in the range
+ * 0 < cpu_scale < 3*SCHED_POWER_SCALE/2
+ * in order to return at most 1 when DIV_ROUND_CLOSEST
+ * is used to compute the capacity of a CPU.
+ * Processors that are not defined in the table,
+ * use the default SCHED_POWER_SCALE value for cpu_scale.
+ */
+static const struct cpu_efficiency table_efficiency[] = {
+ { "arm,cortex-a57", 3891 },
+ { "arm,cortex-a53", 2048 },
+ { NULL, },
+};
+
+static unsigned long *__cpu_capacity;
+#define cpu_capacity(cpu) __cpu_capacity[cpu]
+
+static unsigned long middle_capacity = 1;
+
+/*
+ * Iterate all CPUs' descriptor in DT and compute the efficiency
+ * (as per table_efficiency). Also calculate a middle efficiency
+ * as close as possible to (max{eff_i} - min{eff_i}) / 2
+ * This is later used to scale the cpu_power field such that an
+ * 'average' CPU is of middle power. Also see the comments near
+ * table_efficiency[] and update_cpu_power().
+ */
static int __init parse_dt_topology(void)
{
struct device_node *cn, *map;
@@ -203,6 +263,91 @@ out:
return ret;
}
+static void __init parse_dt_cpu_power(void)
+{
+ const struct cpu_efficiency *cpu_eff;
+ struct device_node *cn;
+ unsigned long min_capacity = ULONG_MAX;
+ unsigned long max_capacity = 0;
+ unsigned long capacity = 0;
+ int cpu;
+
+ __cpu_capacity = kcalloc(nr_cpu_ids, sizeof(*__cpu_capacity),
+ GFP_NOWAIT);
+
+ for_each_possible_cpu(cpu) {
+ const u32 *rate;
+ int len;
+
+ /* Too early to use cpu->of_node */
+ cn = of_get_cpu_node(cpu, NULL);
+ if (!cn) {
+ pr_err("Missing device node for CPU %d\n", cpu);
+ continue;
+ }
+
+ for (cpu_eff = table_efficiency; cpu_eff->compatible; cpu_eff++)
+ if (of_device_is_compatible(cn, cpu_eff->compatible))
+ break;
+
+ if (cpu_eff->compatible == NULL) {
+ pr_warn("%s: Unknown CPU type\n", cn->full_name);
+ continue;
+ }
+
+ rate = of_get_property(cn, "clock-frequency", &len);
+ if (!rate || len != 4) {
+ pr_err("%s: Missing clock-frequency property\n",
+ cn->full_name);
+ continue;
+ }
+
+ capacity = ((be32_to_cpup(rate)) >> 20) * cpu_eff->efficiency;
+
+ /* Save min capacity of the system */
+ if (capacity < min_capacity)
+ min_capacity = capacity;
+
+ /* Save max capacity of the system */
+ if (capacity > max_capacity)
+ max_capacity = capacity;
+
+ cpu_capacity(cpu) = capacity;
+ }
+
+ /* If min and max capacities are equal we bypass the update of the
+ * cpu_scale because all CPUs have the same capacity. Otherwise, we
+ * compute a middle_capacity factor that will ensure that the capacity
+ * of an 'average' CPU of the system will be as close as possible to
+ * SCHED_POWER_SCALE, which is the default value, but with the
+ * constraint explained near table_efficiency[].
+ */
+ if (min_capacity == max_capacity)
+ return;
+ else if (4 * max_capacity < (3 * (max_capacity + min_capacity)))
+ middle_capacity = (min_capacity + max_capacity)
+ >> (SCHED_CAPACITY_SHIFT+1);
+ else
+ middle_capacity = ((max_capacity / 3)
+ >> (SCHED_CAPACITY_SHIFT-1)) + 1;
+}
+
+/*
+ * Look for a customed capacity of a CPU in the cpu_topo_data table during the
+ * boot. The update of all CPUs is in O(n^2) for heteregeneous system but the
+ * function returns directly for SMP system.
+ */
+static void update_cpu_power(unsigned int cpu)
+{
+ if (!cpu_capacity(cpu))
+ return;
+
+ set_power_scale(cpu, cpu_capacity(cpu) / middle_capacity);
+
+ pr_info("CPU%u: update cpu_power %lu\n",
+ cpu, arch_scale_freq_power(NULL, cpu));
+}
+
/*
* cpu topology table
*/
@@ -220,10 +365,8 @@ static void update_siblings_masks(unsigned int cpuid)
int cpu;
if (cpuid_topo->cluster_id == -1) {
- /*
- * DT does not contain topology information for this cpu.
- */
- pr_debug("CPU%u: No topology information configured\n", cpuid);
+ /* No topology information for this cpu ?! */
+ pr_err("CPU%u: No topology information configured\n", cpuid);
return;
}
@@ -249,7 +392,46 @@ static void update_siblings_masks(unsigned int cpuid)
void store_cpu_topology(unsigned int cpuid)
{
+ struct cpu_topology *cpuid_topo = &cpu_topology[cpuid];
+ u64 mpidr;
+
+ if (cpuid_topo->cluster_id != -1)
+ goto topology_populated;
+
+ mpidr = read_cpuid_mpidr();
+
+ /* Create cpu topology mapping based on MPIDR. */
+ if (mpidr & MPIDR_UP_BITMASK) {
+ /* Uniprocessor system */
+ cpuid_topo->thread_id = -1;
+ cpuid_topo->core_id = MPIDR_AFFINITY_LEVEL(mpidr, 0);
+ cpuid_topo->cluster_id = 0;
+ } else if (mpidr & MPIDR_MT_BITMASK) {
+ /* Multiprocessor system : Multi-threads per core */
+ cpuid_topo->thread_id = MPIDR_AFFINITY_LEVEL(mpidr, 0);
+ cpuid_topo->core_id = MPIDR_AFFINITY_LEVEL(mpidr, 1);
+ cpuid_topo->cluster_id =
+ ((mpidr & MPIDR_AFF_MASK(2)) >> mpidr_hash.shift_aff[2] |
+ (mpidr & MPIDR_AFF_MASK(3)) >> mpidr_hash.shift_aff[3])
+ >> mpidr_hash.shift_aff[1] >> mpidr_hash.shift_aff[0];
+ } else {
+ /* Multiprocessor system : Single-thread per core */
+ cpuid_topo->thread_id = -1;
+ cpuid_topo->core_id = MPIDR_AFFINITY_LEVEL(mpidr, 0);
+ cpuid_topo->cluster_id =
+ ((mpidr & MPIDR_AFF_MASK(1)) >> mpidr_hash.shift_aff[1] |
+ (mpidr & MPIDR_AFF_MASK(2)) >> mpidr_hash.shift_aff[2] |
+ (mpidr & MPIDR_AFF_MASK(3)) >> mpidr_hash.shift_aff[3])
+ >> mpidr_hash.shift_aff[0];
+ }
+
+ pr_debug("CPU%u: cluster %d core %d thread %d mpidr %llx\n",
+ cpuid, cpuid_topo->cluster_id, cpuid_topo->core_id,
+ cpuid_topo->thread_id, mpidr);
+
+topology_populated:
update_siblings_masks(cpuid);
+ update_cpu_power(cpuid);
}
static void __init reset_cpu_topology(void)
@@ -270,6 +452,14 @@ static void __init reset_cpu_topology(void)
}
}
+static void __init reset_cpu_power(void)
+{
+ unsigned int cpu;
+
+ for_each_possible_cpu(cpu)
+ set_power_scale(cpu, SCHED_CAPACITY_SCALE);
+}
+
void __init init_cpu_topology(void)
{
reset_cpu_topology();
@@ -280,4 +470,7 @@ void __init init_cpu_topology(void)
*/
if (parse_dt_topology())
reset_cpu_topology();
+
+ reset_cpu_power();
+ parse_dt_cpu_power();
}
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index 9f9c5ae5359b..b09ec50451b8 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -32,6 +32,16 @@ config COMMON_CLK_WM831X
source "drivers/clk/versatile/Kconfig"
+config COMMON_CLK_SCPI
+ bool "Clock driver controlled via SCPI interface"
+ depends on ARM_SCPI_PROTOCOL
+ ---help---
+ This driver provides support for clocks that are controlled
+ by firmware that implements the SCPI interface.
+
+ This driver uses SCPI Message Protocol to interact with the
+ firware providing all the clock controls.
+
config COMMON_CLK_MAX77686
tristate "Clock driver for Maxim 77686 MFD"
depends on MFD_MAX77686
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 567f10259029..520fb98ce2b5 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -24,6 +24,7 @@ obj-$(CONFIG_ARCH_NOMADIK) += clk-nomadik.o
obj-$(CONFIG_ARCH_NSPIRE) += clk-nspire.o
obj-$(CONFIG_CLK_PPC_CORENET) += clk-ppc-corenet.o
obj-$(CONFIG_COMMON_CLK_S2MPS11) += clk-s2mps11.o
+obj-$(CONFIG_COMMON_CLK_SCPI) += clk-scpi.o
obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o
obj-$(CONFIG_COMMON_CLK_SI570) += clk-si570.o
obj-$(CONFIG_CLK_TWL6040) += clk-twl6040.o
diff --git a/drivers/clk/clk-scpi.c b/drivers/clk/clk-scpi.c
new file mode 100644
index 000000000000..2d707663542f
--- /dev/null
+++ b/drivers/clk/clk-scpi.c
@@ -0,0 +1,309 @@
+/*
+ * System Control and Power Interface (SCPI) Protocol based clock driver
+ *
+ * Copyright (C) 2014 ARM Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/clkdev.h>
+#include <linux/clk-provider.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/of.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/scpi_protocol.h>
+
+struct scpi_clk {
+ u32 id;
+ const char *name;
+ struct clk_hw hw;
+ struct scpi_opp *opps;
+ unsigned long rate_min;
+ unsigned long rate_max;
+};
+
+#define to_scpi_clk(clk) container_of(clk, struct scpi_clk, hw)
+
+static unsigned long scpi_clk_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct scpi_clk *clk = to_scpi_clk(hw);
+ return scpi_clk_get_val(clk->id);
+}
+
+static long scpi_clk_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ struct scpi_clk *clk = to_scpi_clk(hw);
+ if (clk->rate_min && rate < clk->rate_min)
+ rate = clk->rate_min;
+ if (clk->rate_max && rate > clk->rate_max)
+ rate = clk->rate_max;
+
+ return rate;
+}
+
+static int scpi_clk_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct scpi_clk *clk = to_scpi_clk(hw);
+ return scpi_clk_set_val(clk->id, rate);
+}
+
+static struct clk_ops scpi_clk_ops = {
+ .recalc_rate = scpi_clk_recalc_rate,
+ .round_rate = scpi_clk_round_rate,
+ .set_rate = scpi_clk_set_rate,
+};
+
+/* find closest match to given frequency in OPP table */
+static int __scpi_dvfs_round_rate(struct scpi_clk *clk, unsigned long rate)
+{
+ int idx, max_opp = clk->opps->count;
+ u32 *freqs = clk->opps->freqs;
+ u32 fmin = 0, fmax = ~0, ftmp;
+
+ for (idx = 0; idx < max_opp; idx++, freqs++) {
+ ftmp = *freqs;
+ if (ftmp >= (u32)rate) {
+ if (ftmp <= fmax)
+ fmax = ftmp;
+ } else {
+ if (ftmp >= fmin)
+ fmin = ftmp;
+ }
+ }
+ if (fmax != ~0)
+ return fmax;
+ else
+ return fmin;
+}
+
+static unsigned long scpi_dvfs_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct scpi_clk *clk = to_scpi_clk(hw);
+ int idx = scpi_dvfs_get_idx(clk->id);
+ u32 *freqs = clk->opps->freqs;
+
+ if (idx < 0)
+ return 0;
+ else
+ return *(freqs + idx);
+}
+
+static long scpi_dvfs_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ struct scpi_clk *clk = to_scpi_clk(hw);
+ return __scpi_dvfs_round_rate(clk, rate);
+}
+
+static int __scpi_find_dvfs_index(struct scpi_clk *clk, unsigned long rate)
+{
+ int idx, max_opp = clk->opps->count;
+ u32 *freqs = clk->opps->freqs;
+
+ for (idx = 0; idx < max_opp; idx++, freqs++)
+ if (*freqs == (u32)rate)
+ break;
+ return (idx == max_opp) ? -EINVAL : idx;
+}
+
+static int scpi_dvfs_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct scpi_clk *clk = to_scpi_clk(hw);
+ int ret = __scpi_find_dvfs_index(clk, rate);
+
+ if (ret < 0)
+ return ret;
+ else
+ return scpi_dvfs_set_idx(clk->id, (u8)ret);
+}
+
+static struct clk_ops scpi_dvfs_ops = {
+ .recalc_rate = scpi_dvfs_recalc_rate,
+ .round_rate = scpi_dvfs_round_rate,
+ .set_rate = scpi_dvfs_set_rate,
+};
+
+static struct clk *
+scpi_dvfs_ops_init(struct device *dev, struct device_node *np,
+ struct scpi_clk *sclk)
+{
+ struct clk_init_data init;
+ struct scpi_opp *opp;
+
+ init.name = sclk->name;
+ init.flags = CLK_IS_ROOT;
+ init.num_parents = 0;
+ init.ops = &scpi_dvfs_ops;
+ sclk->hw.init = &init;
+
+ opp = scpi_dvfs_get_opps(sclk->id);
+ if (IS_ERR(opp))
+ return (struct clk *)opp;
+
+ sclk->opps = opp;
+
+ return devm_clk_register(dev, &sclk->hw);
+}
+
+static struct clk *
+scpi_clk_ops_init(struct device *dev, struct device_node *np,
+ struct scpi_clk *sclk)
+{
+ struct clk_init_data init;
+ u32 range[2];
+ int ret;
+
+ init.name = sclk->name;
+ init.flags = CLK_IS_ROOT;
+ init.num_parents = 0;
+ init.ops = &scpi_clk_ops;
+ sclk->hw.init = &init;
+
+ ret = of_property_read_u32_array(np, "frequency-range", range,
+ ARRAY_SIZE(range));
+ if (ret)
+ return ERR_PTR(ret);
+ sclk->rate_min = range[0];
+ sclk->rate_max = range[1];
+
+ return devm_clk_register(dev, &sclk->hw);
+}
+
+static int scpi_clk_setup(struct device *dev, struct device_node *np,
+ const void *data)
+{
+ struct clk *(*setup_ops)(struct device *, struct device_node *,
+ struct scpi_clk *) = data;
+ struct clk_onecell_data *clk_data;
+ struct clk **clks;
+ size_t count;
+ int idx;
+
+ count = of_property_count_strings(np, "clock-output-names");
+ if (count < 0) {
+ dev_err(dev, "%s: invalid clock output count\n", np->name);
+ return -EINVAL;
+ }
+
+ clk_data = devm_kmalloc(dev, sizeof(*clk_data), GFP_KERNEL);
+ if (!clk_data) {
+ dev_err(dev, "failed to allocate clock provider data\n");
+ return -ENOMEM;
+ }
+
+ clks = devm_kmalloc(dev, count * sizeof(*clks), GFP_KERNEL);
+ if (!clks) {
+ dev_err(dev, "failed to allocate clock providers\n");
+ return -ENOMEM;
+ }
+
+ for (idx = 0; idx < count; idx++) {
+ struct scpi_clk *sclk;
+ u32 val;
+
+ sclk = devm_kzalloc(dev, sizeof(*sclk), GFP_KERNEL);
+ if (!sclk) {
+ dev_err(dev, "failed to allocate scpi clocks\n");
+ return -ENOMEM;
+ }
+
+ if (of_property_read_string_index(np, "clock-output-names",
+ idx, &sclk->name)) {
+ dev_err(dev, "invalid clock name @ %s\n", np->name);
+ return -EINVAL;
+ }
+
+ if (of_property_read_u32_index(np, "clock-indices",
+ idx, &val)) {
+ dev_err(dev, "invalid clock index @ %s\n", np->name);
+ return -EINVAL;
+ }
+
+ sclk->id = val;
+
+ clks[idx] = setup_ops(dev, np, sclk);
+ if (IS_ERR(clks[idx])) {
+ dev_err(dev, "failed to register clock '%s'\n",
+ sclk->name);
+ return PTR_ERR(clks[idx]);
+ }
+
+ dev_dbg(dev, "Registered clock '%s'\n", sclk->name);
+ }
+
+ clk_data->clks = clks;
+ clk_data->clk_num = count;
+ of_clk_add_provider(np, of_clk_src_onecell_get, clk_data);
+
+ return 0;
+}
+
+static const struct of_device_id clk_match[] = {
+ { .compatible = "arm,scpi-clk-indexed", .data = scpi_dvfs_ops_init, },
+ { .compatible = "arm,scpi-clk-range", .data = &scpi_clk_ops_init, },
+ {}
+};
+
+static int scpi_clk_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node, *child;
+ const struct of_device_id *match;
+ int ret;
+
+ for_each_child_of_node(np, child) {
+ match = of_match_node(clk_match, child);
+ if (!match)
+ continue;
+ ret = scpi_clk_setup(dev, child, match->data);
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+
+static struct of_device_id scpi_clk_ids[] = {
+ { .compatible = "arm,scpi-clks", },
+ {}
+};
+
+static struct platform_driver scpi_clk_driver = {
+ .driver = {
+ .name = "scpi_clocks",
+ .of_match_table = scpi_clk_ids,
+ },
+ .probe = scpi_clk_probe,
+};
+
+static int __init scpi_clk_init(void)
+{
+ return platform_driver_register(&scpi_clk_driver);
+}
+postcore_initcall(scpi_clk_init);
+
+static void __exit scpi_clk_exit(void)
+{
+ platform_driver_unregister(&scpi_clk_driver);
+}
+module_exit(scpi_clk_exit);
+
+MODULE_AUTHOR("Sudeep Holla <sudeep.holla@arm.com>");
+MODULE_DESCRIPTION("ARM SCPI clock driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm
index 7364a538e056..daf4e516f415 100644
--- a/drivers/cpufreq/Kconfig.arm
+++ b/drivers/cpufreq/Kconfig.arm
@@ -5,7 +5,8 @@
# big LITTLE core layer and glue drivers
config ARM_BIG_LITTLE_CPUFREQ
tristate "Generic ARM big LITTLE CPUfreq driver"
- depends on ARM && BIG_LITTLE && ARM_CPU_TOPOLOGY && HAVE_CLK
+ depends on ARM_CPU_TOPOLOGY || (ARM64 && SMP)
+ depends on HAVE_CLK
select PM_OPP
help
This enables the Generic CPUfreq driver for ARM big.LITTLE platforms.
@@ -24,6 +25,15 @@ config ARM_VEXPRESS_SPC_CPUFREQ
This add the CPUfreq driver support for Versatile Express
big.LITTLE platforms using SPC for power management.
+config ARM_SPCI_CPUFREQ
+ tristate "SPCI based CPUfreq driver"
+ depends on ARM_BIG_LITTLE_CPUFREQ && ARM_SCPI_PROTOCOL
+ help
+ This add the CPUfreq driver support for ARM big.LITTLE platforms
+ using SCPI interface for CPU power management.
+
+ This driver works only if firmware the supporting CPU DVFS adhere
+ to SCPI protocol.
config ARM_EXYNOS_CPUFREQ
bool
diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile
index fdd12e88a5e6..38bd66fedfc9 100644
--- a/drivers/cpufreq/Makefile
+++ b/drivers/cpufreq/Makefile
@@ -78,6 +78,7 @@ obj-$(CONFIG_ARM_SA1110_CPUFREQ) += sa1110-cpufreq.o
obj-$(CONFIG_ARM_SPEAR_CPUFREQ) += spear-cpufreq.o
obj-$(CONFIG_ARM_TEGRA_CPUFREQ) += tegra-cpufreq.o
obj-$(CONFIG_ARM_VEXPRESS_SPC_CPUFREQ) += vexpress-spc-cpufreq.o
+obj-$(CONFIG_ARM_SPCI_CPUFREQ) += scpi-cpufreq.o
##################################################################################
# PowerPC platform drivers
diff --git a/drivers/cpufreq/arm_big_little.c b/drivers/cpufreq/arm_big_little.c
index 1f4d4e315057..a155ad5dc769 100644
--- a/drivers/cpufreq/arm_big_little.c
+++ b/drivers/cpufreq/arm_big_little.c
@@ -340,7 +340,9 @@ static int _get_cluster_clk_and_freq_table(struct device *cpu_dev)
}
name[12] = cluster + '0';
- clk[cluster] = clk_get(cpu_dev, name);
+ clk[cluster] = clk_get_sys(name, NULL);
+ if (IS_ERR(clk[cluster]))
+ clk[cluster] = clk_get(cpu_dev, NULL);
if (!IS_ERR(clk[cluster])) {
dev_dbg(cpu_dev, "%s: clk: %p & freq table: %p, cluster: %d\n",
__func__, clk[cluster], freq_table[cluster],
diff --git a/drivers/cpufreq/highbank-cpufreq.c b/drivers/cpufreq/highbank-cpufreq.c
index bf8902a0866d..b464f29d8d54 100644
--- a/drivers/cpufreq/highbank-cpufreq.c
+++ b/drivers/cpufreq/highbank-cpufreq.c
@@ -19,7 +19,7 @@
#include <linux/cpu.h>
#include <linux/err.h>
#include <linux/of.h>
-#include <linux/mailbox.h>
+#include <linux/pl320-ipc.h>
#include <linux/platform_device.h>
#define HB_CPUFREQ_CHANGE_NOTE 0x80000001
diff --git a/drivers/cpufreq/scpi-cpufreq.c b/drivers/cpufreq/scpi-cpufreq.c
new file mode 100644
index 000000000000..60725199b9aa
--- /dev/null
+++ b/drivers/cpufreq/scpi-cpufreq.c
@@ -0,0 +1,99 @@
+/*
+ * SCPI CPUFreq Interface driver
+ *
+ * It provides necessary ops to arm_big_little cpufreq driver.
+ *
+ * Copyright (C) 2014 ARM Ltd.
+ * Sudeep Holla <sudeep.holla@arm.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/cpufreq.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pm_opp.h>
+#include <linux/scpi_protocol.h>
+#include <linux/types.h>
+
+#include "arm_big_little.h"
+
+static int scpi_init_opp_table(struct device *cpu_dev)
+{
+ u8 domain = topology_physical_package_id(cpu_dev->id);
+ struct scpi_opp *opp;
+ int idx, ret = 0, max_opp;
+ u32 *freqs;
+
+ opp = scpi_dvfs_get_opps(domain);
+ if (IS_ERR(opp))
+ return PTR_ERR(opp);
+
+ freqs = opp->freqs;
+ max_opp = opp->count;
+ for (idx = 0; idx < max_opp; idx++, freqs++) {
+ ret = dev_pm_opp_add(cpu_dev, *freqs, 900000000 /* TODO */);
+ if (ret) {
+ dev_warn(cpu_dev, "failed to add opp %u\n", *freqs);
+ return ret;
+ }
+ }
+ return ret;
+}
+
+static int scpi_get_transition_latency(struct device *cpu_dev)
+{
+ u8 domain = topology_physical_package_id(cpu_dev->id);
+ struct scpi_opp *opp;
+
+ opp = scpi_dvfs_get_opps(domain);
+ if (IS_ERR(opp))
+ return PTR_ERR(opp);
+
+ return opp->latency * 1000; /* SCPI returns in uS */
+}
+
+static struct cpufreq_arm_bL_ops scpi_cpufreq_ops = {
+ .name = "scpi",
+ .get_transition_latency = scpi_get_transition_latency,
+ .init_opp_table = scpi_init_opp_table,
+};
+
+static int scpi_cpufreq_probe(struct platform_device *pdev)
+{
+ return bL_cpufreq_register(&scpi_cpufreq_ops);
+}
+
+static int scpi_cpufreq_remove(struct platform_device *pdev)
+{
+ bL_cpufreq_unregister(&scpi_cpufreq_ops);
+ return 0;
+}
+
+static struct of_device_id scpi_cpufreq_of_match[] = {
+ { .compatible = "arm,scpi-cpufreq" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, scpi_cpufreq_of_match);
+
+static struct platform_driver scpi_cpufreq_platdrv = {
+ .driver = {
+ .name = "scpi-cpufreq",
+ .owner = THIS_MODULE,
+ .of_match_table = scpi_cpufreq_of_match,
+ },
+ .probe = scpi_cpufreq_probe,
+ .remove = scpi_cpufreq_remove,
+};
+module_platform_driver(scpi_cpufreq_platdrv);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/mailbox/Kconfig b/drivers/mailbox/Kconfig
index c8b5c13bcd05..c146c9e797cf 100644
--- a/drivers/mailbox/Kconfig
+++ b/drivers/mailbox/Kconfig
@@ -6,6 +6,33 @@ menuconfig MAILBOX
signals. Say Y if your platform supports hardware mailboxes.
if MAILBOX
+config ARM_MHU_MBOX
+ bool "ARM Message Handling Unit (MHU) Mailbox"
+ help
+ This driver provides support for inter-processor communication
+ between System Control Processor (SCP) with Cortex-M3 processor
+ and Application Processors (AP) on some ARM based systems with
+ MHU peripheral.
+
+ SCP controls most of the power managament on the Application
+ Processors. It offers control and management of: the core/cluster
+ power states, various power domain DVFS including the core/cluster,
+ certain system clocks configuration, thermal sensors and many
+ others.
+
+config ARM_SCPI_PROTOCOL
+ bool "ARM System Control and Power Interface (SCPI) Message Protocol"
+ select ARM_MHU_MBOX
+ help
+ System Control and Power Interface (SCPI) Message Protocol is
+ defined for the purpose of communication between the Application
+ Cores(AP) and the System Control Processor(SCP). The MHU peripheral
+ provides a mechanism for inter-processor communication between SCP
+ and AP.
+
+ This protocol library provides interface for all the client drivers
+ making use of the features offered by the SCP.
+
config PL320_MBOX
bool "ARM PL320 Mailbox"
depends on ARM_AMBA
diff --git a/drivers/mailbox/Makefile b/drivers/mailbox/Makefile
index e0facb34084a..ba98d45d0e9f 100644
--- a/drivers/mailbox/Makefile
+++ b/drivers/mailbox/Makefile
@@ -1,3 +1,10 @@
+# Generic MAILBOX API
+
+obj-$(CONFIG_MAILBOX) += mailbox.o
+
+obj-$(CONFIG_ARM_MHU_MBOX) += arm_mhu.o
+obj-$(CONFIG_ARM_SCPI_PROTOCOL) += scpi_protocol.o
+
obj-$(CONFIG_PL320_MBOX) += pl320-ipc.o
obj-$(CONFIG_OMAP_MBOX) += omap-mailbox.o
diff --git a/drivers/mailbox/arm_mhu.c b/drivers/mailbox/arm_mhu.c
new file mode 100644
index 000000000000..c6842784e410
--- /dev/null
+++ b/drivers/mailbox/arm_mhu.c
@@ -0,0 +1,320 @@
+/*
+ * Driver for the Message Handling Unit (MHU) which is the peripheral in
+ * the Compute SubSystem (CSS) providing a mechanism for inter-processor
+ * communication between System Control Processor (SCP) with Cortex-M3
+ * processor and Application Processors (AP).
+ *
+ * The MHU peripheral provides a mechanism to assert interrupt signals to
+ * facilitate inter-processor message passing between the SCP and the AP.
+ * The message payload can be deposited into main memory or on-chip memories.
+ * The MHU supports three bi-directional channels - low priority, high
+ * priority and secure(can't be used in non-secure execution modes)
+ *
+ * Copyright (C) 2014 ARM Ltd.
+ *
+ * Author: Sudeep Holla <sudeep.holla@arm.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 <http://www.gnu.org/licenses/>.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/export.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/mailbox_controller.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/notifier.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+
+#include "arm_mhu.h"
+
+struct device* the_scpi_device;
+
+#define DRIVER_NAME "arm_mhu"
+
+/*
+ * +--------------------+-------+---------------+
+ * | Hardware Register | Offset| Driver View |
+ * +--------------------+-------+---------------+
+ * | SCP_INTR_L_STAT | 0x000 | RX_STATUS(L) |
+ * | SCP_INTR_L_SET | 0x008 | RX_SET(L) |
+ * | SCP_INTR_L_CLEAR | 0x010 | RX_CLEAR(L) |
+ * +--------------------+-------+---------------+
+ * | SCP_INTR_H_STAT | 0x020 | RX_STATUS(H) |
+ * | SCP_INTR_H_SET | 0x028 | RX_SET(H) |
+ * | SCP_INTR_H_CLEAR | 0x030 | RX_CLEAR(H) |
+ * +--------------------+-------+---------------+
+ * | CPU_INTR_L_STAT | 0x100 | TX_STATUS(L) |
+ * | CPU_INTR_L_SET | 0x108 | TX_SET(L) |
+ * | CPU_INTR_L_CLEAR | 0x110 | TX_CLEAR(L) |
+ * +--------------------+-------+---------------+
+ * | CPU_INTR_H_STAT | 0x120 | TX_STATUS(H) |
+ * | CPU_INTR_H_SET | 0x128 | TX_SET(H) |
+ * | CPU_INTR_H_CLEAR | 0x130 | TX_CLEAR(H) |
+ * +--------------------+-------+---------------+
+*/
+#define RX_OFFSET(chan) ((idx) * 0x20)
+#define RX_STATUS(chan) RX_OFFSET(chan)
+#define RX_SET(chan) (RX_OFFSET(chan) + 0x8)
+#define RX_CLEAR(chan) (RX_OFFSET(chan) + 0x10)
+
+#define TX_OFFSET(chan) (0x100 + (idx) * 0x20)
+#define TX_STATUS(chan) TX_OFFSET(chan)
+#define TX_SET(chan) (TX_OFFSET(chan) + 0x8)
+#define TX_CLEAR(chan) (TX_OFFSET(chan) + 0x10)
+
+/*
+ * +---------------+-------+----------------+
+ * | Payload | Offset| Driver View |
+ * +---------------+-------+----------------+
+ * | SCP->AP Low | 0x000 | RX_PAYLOAD(L) |
+ * | SCP->AP High | 0x400 | RX_PAYLOAD(H) |
+ * +---------------+-------+----------------+
+ * | AP->SCP Low | 0x200 | TX_PAYLOAD(H) |
+ * | AP->SCP High | 0x600 | TX_PAYLOAD(H) |
+ * +---------------+-------+----------------+
+*/
+#define PAYLOAD_MAX_SIZE 0x200
+#define PAYLOAD_OFFSET 0x400
+#define RX_PAYLOAD(chan) ((chan) * PAYLOAD_OFFSET)
+#define TX_PAYLOAD(chan) ((chan) * PAYLOAD_OFFSET + PAYLOAD_MAX_SIZE)
+
+struct mhu_chan {
+ int index;
+ int rx_irq;
+ struct mhu_ctlr *ctlr;
+ struct mhu_data_buf *data;
+};
+
+struct mhu_ctlr {
+ struct device *dev;
+ void __iomem *mbox_base;
+ void __iomem *payload_base;
+ struct mbox_controller mbox_con;
+ struct mhu_chan channels[CHANNEL_MAX];
+};
+
+static irqreturn_t mbox_handler(int irq, void *p)
+{
+ struct mbox_chan *link = (struct mbox_chan *)p;
+ struct mhu_chan *chan = link->con_priv;
+ struct mhu_ctlr *ctlr = chan->ctlr;
+ void __iomem *mbox_base = ctlr->mbox_base;
+ void __iomem *payload = ctlr->payload_base;
+ int idx = chan->index;
+ u32 status = readl(mbox_base + RX_STATUS(idx));
+
+ if (status && irq == chan->rx_irq) {
+ struct mhu_data_buf *data = chan->data;
+ if (!data)
+ return IRQ_NONE; /* spurious */
+ if (data->rx_buf)
+ memcpy(data->rx_buf, payload + RX_PAYLOAD(idx),
+ data->rx_size);
+ chan->data = NULL;
+ mbox_chan_received_data(link, data);
+ writel(~0, mbox_base + RX_CLEAR(idx));
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int mhu_send_data(struct mbox_chan *link, void *msg)
+{
+ struct mhu_chan *chan = link->con_priv;
+ struct mhu_ctlr *ctlr = chan->ctlr;
+ void __iomem *mbox_base = ctlr->mbox_base;
+ void __iomem *payload = ctlr->payload_base;
+ struct mhu_data_buf *data = (struct mhu_data_buf *)msg;
+ int idx = chan->index;
+
+ if (!data)
+ return -EINVAL;
+
+ chan->data = data;
+ if (data->tx_buf)
+ memcpy(payload + TX_PAYLOAD(idx), data->tx_buf, data->tx_size);
+ writel(data->cmd, mbox_base + TX_SET(idx));
+
+ return 0;
+}
+
+static int mhu_startup(struct mbox_chan *link)
+{
+ struct mhu_chan *chan = link->con_priv;
+ int err, mbox_irq = chan->rx_irq;
+
+ err = request_threaded_irq(mbox_irq, NULL, mbox_handler, IRQF_ONESHOT,
+ DRIVER_NAME, link);
+ return err;
+}
+
+static void mhu_shutdown(struct mbox_chan *link)
+{
+ struct mhu_chan *chan = link->con_priv;
+
+ chan->data = NULL;
+ free_irq(chan->rx_irq, link);
+}
+
+static bool mhu_last_tx_done(struct mbox_chan *link)
+{
+ struct mhu_chan *chan = link->con_priv;
+ struct mhu_ctlr *ctlr = chan->ctlr;
+ void __iomem *mbox_base = ctlr->mbox_base;
+ int idx = chan->index;
+
+ return !readl(mbox_base + TX_STATUS(idx));
+}
+
+static struct mbox_chan_ops mhu_ops = {
+ .send_data = mhu_send_data,
+ .startup = mhu_startup,
+ .shutdown = mhu_shutdown,
+ .last_tx_done = mhu_last_tx_done,
+};
+
+static int mhu_probe(struct platform_device *pdev)
+{
+ struct mhu_ctlr *ctlr;
+ struct mhu_chan *chan;
+ struct device *dev = &pdev->dev;
+ struct mbox_chan *l;
+ struct resource *res;
+ int idx;
+ static const char * const channel_names[] = {
+ CHANNEL_LOW_PRIORITY,
+ CHANNEL_HIGH_PRIORITY
+ };
+
+ ctlr = devm_kzalloc(dev, sizeof(*ctlr), GFP_KERNEL);
+ if (!ctlr) {
+ dev_err(dev, "failed to allocate memory\n");
+ return -ENOMEM;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(dev, "failed to get mailbox memory resource\n");
+ return -ENXIO;
+ }
+
+ ctlr->mbox_base = devm_request_and_ioremap(dev, res);
+ if (!ctlr->mbox_base) {
+ dev_err(dev, "failed to request or ioremap mailbox control\n");
+ return -EADDRNOTAVAIL;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ if (!res) {
+ dev_err(dev, "failed to get payload memory resource\n");
+ return -ENXIO;
+ }
+
+ ctlr->payload_base = devm_request_and_ioremap(dev, res);
+ if (!ctlr->payload_base) {
+ dev_err(dev, "failed to request or ioremap mailbox payload\n");
+ return -EADDRNOTAVAIL;
+ }
+
+ ctlr->dev = dev;
+ platform_set_drvdata(pdev, ctlr);
+
+ l = devm_kzalloc(dev, sizeof(*l) * CHANNEL_MAX, GFP_KERNEL);
+ if (!l) {
+ dev_err(dev, "failed to allocate memory\n");
+ return -ENOMEM;
+ }
+
+ ctlr->mbox_con.chans = l;
+ ctlr->mbox_con.num_chans = CHANNEL_MAX;
+ ctlr->mbox_con.txdone_irq = true;
+ ctlr->mbox_con.ops = &mhu_ops;
+ ctlr->mbox_con.dev = dev;
+
+ for (idx = 0; idx < CHANNEL_MAX; idx++) {
+ chan = &ctlr->channels[idx];
+ chan->index = idx;
+ chan->ctlr = ctlr;
+ chan->rx_irq = platform_get_irq(pdev, idx);
+ if (chan->rx_irq < 0) {
+ dev_err(dev, "failed to get interrupt for %s\n",
+ channel_names[idx]);
+ return -ENXIO;
+ }
+ l[idx].con_priv = chan;
+ }
+
+ if (mbox_controller_register(&ctlr->mbox_con)) {
+ dev_err(dev, "failed to register mailbox controller\n");
+ return -ENOMEM;
+ }
+
+ the_scpi_device = dev;
+ return 0;
+}
+
+static int mhu_remove(struct platform_device *pdev)
+{
+ struct mhu_ctlr *ctlr = platform_get_drvdata(pdev);
+ struct device *dev = &pdev->dev;
+
+ mbox_controller_unregister(&ctlr->mbox_con);
+ devm_kfree(dev, ctlr->mbox_con.chans);
+
+ devm_iounmap(dev, ctlr->payload_base);
+ devm_iounmap(dev, ctlr->mbox_base);
+
+ platform_set_drvdata(pdev, NULL);
+ devm_kfree(dev, ctlr);
+ return 0;
+}
+
+static struct of_device_id mhu_of_match[] = {
+ { .compatible = "arm,mhu" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, mhu_of_match);
+
+static struct platform_driver mhu_driver = {
+ .probe = mhu_probe,
+ .remove = mhu_remove,
+ .driver = {
+ .name = DRIVER_NAME,
+ .of_match_table = mhu_of_match,
+ },
+};
+
+static int __init mhu_init(void)
+{
+ return platform_driver_register(&mhu_driver);
+}
+core_initcall(mhu_init);
+
+static void __exit mhu_exit(void)
+{
+ platform_driver_unregister(&mhu_driver);
+}
+module_exit(mhu_exit);
+
+MODULE_AUTHOR("Sudeep Holla <sudeep.holla@arm.com>");
+MODULE_DESCRIPTION("ARM MHU mailbox driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mailbox/arm_mhu.h b/drivers/mailbox/arm_mhu.h
new file mode 100644
index 000000000000..f78c1fa369c8
--- /dev/null
+++ b/drivers/mailbox/arm_mhu.h
@@ -0,0 +1,33 @@
+/*
+ * ARM Message Handling Unit (MHU) driver header
+ *
+ * Copyright (C) 2014 ARM Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 <http://www.gnu.org/licenses/>.
+ */
+#define CONTROLLER_NAME "mhu_ctlr"
+
+#define CHANNEL_MAX 2
+#define CHANNEL_LOW_PRIORITY "cpu_to_scp_low"
+#define CHANNEL_HIGH_PRIORITY "cpu_to_scp_high"
+
+struct mhu_data_buf {
+ u32 cmd;
+ int tx_size;
+ void *tx_buf;
+ int rx_size;
+ void *rx_buf;
+ void *cl_data;
+};
+
+extern struct device* the_scpi_device;
diff --git a/drivers/mailbox/mailbox.c b/drivers/mailbox/mailbox.c
new file mode 100644
index 000000000000..ae4abd9f7513
--- /dev/null
+++ b/drivers/mailbox/mailbox.c
@@ -0,0 +1,488 @@
+/*
+ * Mailbox: Common code for Mailbox controllers and users
+ *
+ * Copyright (C) 2014 Linaro Ltd.
+ * Author: Jassi Brar <jassisinghbrar@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+#include <linux/mutex.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/mailbox_client.h>
+#include <linux/mailbox_controller.h>
+
+#define TXDONE_BY_IRQ (1 << 0) /* controller has remote RTR irq */
+#define TXDONE_BY_POLL (1 << 1) /* controller can read status of last TX */
+#define TXDONE_BY_ACK (1 << 2) /* S/W ACK recevied by Client ticks the TX */
+
+static LIST_HEAD(mbox_cons);
+static DEFINE_MUTEX(con_mutex);
+
+static int _add_to_rbuf(struct mbox_chan *chan, void *mssg)
+{
+ int idx;
+ unsigned long flags;
+
+ spin_lock_irqsave(&chan->lock, flags);
+
+ /* See if there is any space left */
+ if (chan->msg_count == MBOX_TX_QUEUE_LEN) {
+ spin_unlock_irqrestore(&chan->lock, flags);
+ return -ENOMEM;
+ }
+
+ idx = chan->msg_free;
+ chan->msg_data[idx] = mssg;
+ chan->msg_count++;
+
+ if (idx == MBOX_TX_QUEUE_LEN - 1)
+ chan->msg_free = 0;
+ else
+ chan->msg_free++;
+
+ spin_unlock_irqrestore(&chan->lock, flags);
+
+ return idx;
+}
+
+static void _msg_submit(struct mbox_chan *chan)
+{
+ unsigned count, idx;
+ unsigned long flags;
+ void *data;
+ int err;
+
+ spin_lock_irqsave(&chan->lock, flags);
+
+ if (!chan->msg_count || chan->active_req) {
+ spin_unlock_irqrestore(&chan->lock, flags);
+ return;
+ }
+
+ count = chan->msg_count;
+ idx = chan->msg_free;
+ if (idx >= count)
+ idx -= count;
+ else
+ idx += MBOX_TX_QUEUE_LEN - count;
+
+ data = chan->msg_data[idx];
+
+ /* Try to submit a message to the MBOX controller */
+ err = chan->mbox->ops->send_data(chan, data);
+ if (!err) {
+ chan->active_req = data;
+ chan->msg_count--;
+ }
+
+ spin_unlock_irqrestore(&chan->lock, flags);
+}
+
+static void tx_tick(struct mbox_chan *chan, int r)
+{
+ unsigned long flags;
+ void *mssg;
+
+ spin_lock_irqsave(&chan->lock, flags);
+ mssg = chan->active_req;
+ chan->active_req = NULL;
+ spin_unlock_irqrestore(&chan->lock, flags);
+
+ /* Submit next message */
+ _msg_submit(chan);
+
+ /* Notify the client */
+ if (chan->cl->tx_block)
+ complete(&chan->tx_complete);
+ else if (mssg && chan->cl->tx_done)
+ chan->cl->tx_done(chan->cl, mssg, r);
+}
+
+static void poll_txdone(unsigned long data)
+{
+ struct mbox_controller *mbox = (struct mbox_controller *)data;
+ bool txdone, resched = false;
+ int i;
+
+ for (i = 0; i < mbox->num_chans; i++) {
+ struct mbox_chan *chan = &mbox->chans[i];
+
+ if (chan->active_req && chan->cl) {
+ resched = true;
+ txdone = chan->mbox->ops->last_tx_done(chan);
+ if (txdone)
+ tx_tick(chan, 0);
+ }
+ }
+
+ if (resched)
+ mod_timer(&mbox->poll,
+ jiffies + msecs_to_jiffies(mbox->period));
+}
+
+/**
+ * mbox_chan_received_data - A way for controller driver to push data
+ * received from remote to the upper layer.
+ * @chan: Pointer to the mailbox channel on which RX happened.
+ * @data: Client specific message typecasted as void *
+ *
+ * After startup and before shutdown any data received on the chan
+ * is passed on to the API via atomic mbox_chan_received_data().
+ * The controller should ACK the RX only after this call returns.
+ */
+void mbox_chan_received_data(struct mbox_chan *chan, void *mssg)
+{
+ /* No buffering the received data */
+ if (chan->cl->rx_callback)
+ chan->cl->rx_callback(chan->cl, mssg);
+}
+EXPORT_SYMBOL_GPL(mbox_chan_received_data);
+
+/**
+ * mbox_chan_txdone - A way for controller driver to notify the
+ * framework that the last TX has completed.
+ * @chan: Pointer to the mailbox chan on which TX happened.
+ * @r: Status of last TX - OK or ERROR
+ *
+ * The controller that has IRQ for TX ACK calls this atomic API
+ * to tick the TX state machine. It works only if txdone_irq
+ * is set by the controller.
+ */
+void mbox_chan_txdone(struct mbox_chan *chan, int r)
+{
+ if (unlikely(!(chan->txdone_method & TXDONE_BY_IRQ))) {
+ pr_err("Controller can't run the TX ticker\n");
+ return;
+ }
+
+ tx_tick(chan, r);
+}
+EXPORT_SYMBOL_GPL(mbox_chan_txdone);
+
+/**
+ * mbox_client_txdone - The way for a client to run the TX state machine.
+ * @chan: Mailbox channel assigned to this client.
+ * @r: Success status of last transmission.
+ *
+ * The client/protocol had received some 'ACK' packet and it notifies
+ * the API that the last packet was sent successfully. This only works
+ * if the controller can't sense TX-Done.
+ */
+void mbox_client_txdone(struct mbox_chan *chan, int r)
+{
+ if (unlikely(!(chan->txdone_method & TXDONE_BY_ACK))) {
+ pr_err("Client can't run the TX ticker\n");
+ return;
+ }
+
+ tx_tick(chan, r);
+}
+EXPORT_SYMBOL_GPL(mbox_client_txdone);
+
+/**
+ * mbox_client_peek_data - A way for client driver to pull data
+ * received from remote by the controller.
+ * @chan: Mailbox channel assigned to this client.
+ *
+ * A poke to controller driver for any received data.
+ * The data is actually passed onto client via the
+ * mbox_chan_received_data()
+ * The call can be made from atomic context, so the controller's
+ * implementation of peek_data() must not sleep.
+ *
+ * Return: True, if controller has, and is going to push after this,
+ * some data.
+ * False, if controller doesn't have any data to be read.
+ */
+bool mbox_client_peek_data(struct mbox_chan *chan)
+{
+ if (chan->mbox->ops->peek_data)
+ return chan->mbox->ops->peek_data(chan);
+
+ return false;
+}
+EXPORT_SYMBOL_GPL(mbox_client_peek_data);
+
+/**
+ * mbox_send_message - For client to submit a message to be
+ * sent to the remote.
+ * @chan: Mailbox channel assigned to this client.
+ * @mssg: Client specific message typecasted.
+ *
+ * For client to submit data to the controller destined for a remote
+ * processor. If the client had set 'tx_block', the call will return
+ * either when the remote receives the data or when 'tx_tout' millisecs
+ * run out.
+ * In non-blocking mode, the requests are buffered by the API and a
+ * non-negative token is returned for each queued request. If the request
+ * is not queued, a negative token is returned. Upon failure or successful
+ * TX, the API calls 'tx_done' from atomic context, from which the client
+ * could submit yet another request.
+ * In blocking mode, 'tx_done' is not called, effectively making the
+ * queue length 1.
+ * The pointer to message should be preserved until it is sent
+ * over the chan, i.e, tx_done() is made.
+ * This function could be called from atomic context as it simply
+ * queues the data and returns a token against the request.
+ *
+ * Return: Non-negative integer for successful submission (non-blocking mode)
+ * or transmission over chan (blocking mode).
+ * Negative value denotes failure.
+ */
+int mbox_send_message(struct mbox_chan *chan, void *mssg)
+{
+ int t;
+
+ if (!chan || !chan->cl)
+ return -EINVAL;
+
+ t = _add_to_rbuf(chan, mssg);
+ if (t < 0) {
+ pr_err("Try increasing MBOX_TX_QUEUE_LEN\n");
+ return t;
+ }
+
+ _msg_submit(chan);
+
+ reinit_completion(&chan->tx_complete);
+
+ if (chan->txdone_method == TXDONE_BY_POLL)
+ poll_txdone((unsigned long)chan->mbox);
+
+ if (chan->cl->tx_block && chan->active_req) {
+ unsigned long wait;
+ int ret;
+
+ if (!chan->cl->tx_tout) /* wait for ever */
+ wait = msecs_to_jiffies(3600000);
+ else
+ wait = msecs_to_jiffies(chan->cl->tx_tout);
+
+ ret = wait_for_completion_timeout(&chan->tx_complete, wait);
+ if (ret == 0) {
+ t = -EIO;
+ tx_tick(chan, -EIO);
+ }
+ }
+
+ return t;
+}
+EXPORT_SYMBOL_GPL(mbox_send_message);
+
+/**
+ * mbox_request_channel - Request a mailbox channel.
+ * @cl: Identity of the client requesting the channel.
+ *
+ * The Client specifies its requirements and capabilities while asking for
+ * a mailbox channel. It can't be called from atomic context.
+ * The channel is exclusively allocated and can't be used by another
+ * client before the owner calls mbox_free_channel.
+ * After assignment, any packet received on this channel will be
+ * handed over to the client via the 'rx_callback'.
+ * The framework holds reference to the client, so the mbox_client
+ * structure shouldn't be modified until the mbox_free_channel returns.
+ *
+ * Return: Pointer to the channel assigned to the client if successful.
+ * ERR_PTR for request failure.
+ */
+struct mbox_chan *mbox_request_channel(const struct mbox_client *cl)
+{
+ struct device *dev = cl->dev;
+ struct mbox_controller *mbox;
+ struct of_phandle_args spec;
+ struct mbox_chan *chan;
+ unsigned long flags;
+ int count, i, ret;
+
+ if (!dev || !dev->of_node) {
+ pr_err("%s: No owner device node\n", __func__);
+ return ERR_PTR(-ENODEV);
+ }
+
+ count = of_property_count_strings(dev->of_node, "mbox-names");
+ if (count < 0) {
+ pr_err("%s: mbox-names property of node '%s' missing\n",
+ __func__, dev->of_node->full_name);
+ return ERR_PTR(-ENODEV);
+ }
+
+ mutex_lock(&con_mutex);
+
+ ret = -ENODEV;
+ for (i = 0; i < count; i++) {
+ const char *s;
+
+ if (of_property_read_string_index(dev->of_node,
+ "mbox-names", i, &s))
+ continue;
+
+ if (strcmp(cl->chan_name, s))
+ continue;
+
+ if (of_parse_phandle_with_args(dev->of_node,
+ "mbox", "#mbox-cells", i, &spec))
+ continue;
+
+ chan = NULL;
+ list_for_each_entry(mbox, &mbox_cons, node)
+ if (mbox->dev->of_node == spec.np) {
+ chan = mbox->of_xlate(mbox, &spec);
+ break;
+ }
+
+ of_node_put(spec.np);
+
+ if (!chan)
+ continue;
+
+ ret = -EBUSY;
+ if (!chan->cl && try_module_get(mbox->dev->driver->owner))
+ break;
+ }
+
+ if (i == count) {
+ mutex_unlock(&con_mutex);
+ return ERR_PTR(ret);
+ }
+
+ spin_lock_irqsave(&chan->lock, flags);
+ chan->msg_free = 0;
+ chan->msg_count = 0;
+ chan->active_req = NULL;
+ chan->cl = cl;
+ init_completion(&chan->tx_complete);
+
+ if (chan->txdone_method == TXDONE_BY_POLL
+ && cl->knows_txdone)
+ chan->txdone_method |= TXDONE_BY_ACK;
+ spin_unlock_irqrestore(&chan->lock, flags);
+
+ ret = chan->mbox->ops->startup(chan);
+ if (ret) {
+ pr_err("Unable to startup the chan (%d)\n", ret);
+ mbox_free_channel(chan);
+ chan = ERR_PTR(ret);
+ }
+
+ mutex_unlock(&con_mutex);
+ return chan;
+}
+EXPORT_SYMBOL_GPL(mbox_request_channel);
+
+/**
+ * mbox_free_channel - The client relinquishes control of a mailbox
+ * channel by this call.
+ * @chan: The mailbox channel to be freed.
+ */
+void mbox_free_channel(struct mbox_chan *chan)
+{
+ unsigned long flags;
+
+ if (!chan || !chan->cl)
+ return;
+
+ chan->mbox->ops->shutdown(chan);
+
+ /* The queued TX requests are simply aborted, no callbacks are made */
+ spin_lock_irqsave(&chan->lock, flags);
+ chan->cl = NULL;
+ chan->active_req = NULL;
+ if (chan->txdone_method == (TXDONE_BY_POLL | TXDONE_BY_ACK))
+ chan->txdone_method = TXDONE_BY_POLL;
+
+ module_put(chan->mbox->dev->driver->owner);
+ spin_unlock_irqrestore(&chan->lock, flags);
+}
+EXPORT_SYMBOL_GPL(mbox_free_channel);
+
+static struct mbox_chan *
+of_mbox_index_xlate(struct mbox_controller *mbox,
+ const struct of_phandle_args *sp)
+{
+ int ind = sp->args[0];
+
+ if (ind >= mbox->num_chans)
+ return NULL;
+
+ return &mbox->chans[ind];
+}
+
+/**
+ * mbox_controller_register - Register the mailbox controller
+ * @mbox: Pointer to the mailbox controller.
+ *
+ * The controller driver registers its communication chans
+ */
+int mbox_controller_register(struct mbox_controller *mbox)
+{
+ int i, txdone;
+
+ /* Sanity check */
+ if (!mbox || !mbox->dev || !mbox->ops || !mbox->num_chans)
+ return -EINVAL;
+
+ if (mbox->txdone_irq)
+ txdone = TXDONE_BY_IRQ;
+ else if (mbox->txdone_poll)
+ txdone = TXDONE_BY_POLL;
+ else /* It has to be ACK then */
+ txdone = TXDONE_BY_ACK;
+
+ if (txdone == TXDONE_BY_POLL) {
+ mbox->poll.function = &poll_txdone;
+ mbox->poll.data = (unsigned long)mbox;
+ init_timer(&mbox->poll);
+ }
+
+ for (i = 0; i < mbox->num_chans; i++) {
+ struct mbox_chan *chan = &mbox->chans[i];
+ chan->cl = NULL;
+ chan->mbox = mbox;
+ chan->txdone_method = txdone;
+ spin_lock_init(&chan->lock);
+ }
+
+ if (!mbox->of_xlate)
+ mbox->of_xlate = of_mbox_index_xlate;
+
+ mutex_lock(&con_mutex);
+ list_add_tail(&mbox->node, &mbox_cons);
+ mutex_unlock(&con_mutex);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(mbox_controller_register);
+
+/**
+ * mbox_controller_unregister - UnRegister the mailbox controller
+ * @mbox: Pointer to the mailbox controller.
+ */
+void mbox_controller_unregister(struct mbox_controller *mbox)
+{
+ int i;
+
+ if (!mbox)
+ return;
+
+ mutex_lock(&con_mutex);
+
+ list_del(&mbox->node);
+
+ for (i = 0; i < mbox->num_chans; i++)
+ mbox_free_channel(&mbox->chans[i]);
+
+ if (mbox->txdone_poll)
+ del_timer_sync(&mbox->poll);
+
+ mutex_unlock(&con_mutex);
+}
+EXPORT_SYMBOL_GPL(mbox_controller_unregister);
diff --git a/drivers/mailbox/pl320-ipc.c b/drivers/mailbox/pl320-ipc.c
index d873cbae2fbb..f3755e0aa935 100644
--- a/drivers/mailbox/pl320-ipc.c
+++ b/drivers/mailbox/pl320-ipc.c
@@ -26,7 +26,7 @@
#include <linux/device.h>
#include <linux/amba/bus.h>
-#include <linux/mailbox.h>
+#include <linux/pl320-ipc.h>
#define IPCMxSOURCE(m) ((m) * 0x40)
#define IPCMxDSET(m) (((m) * 0x40) + 0x004)
diff --git a/drivers/mailbox/scpi_protocol.c b/drivers/mailbox/scpi_protocol.c
new file mode 100644
index 000000000000..edcf47ea06ab
--- /dev/null
+++ b/drivers/mailbox/scpi_protocol.c
@@ -0,0 +1,353 @@
+/*
+ * System Control and Power Interface (SCPI) Message Protocol driver
+ *
+ * Copyright (C) 2014 ARM Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/err.h>
+#include <linux/export.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/printk.h>
+#include <linux/mailbox_client.h>
+#include <linux/scpi_protocol.h>
+#include <linux/slab.h>
+
+#include "arm_mhu.h"
+
+#define CMD_ID_SHIFT 0
+#define CMD_ID_MASK 0xff
+#define CMD_SENDER_ID_SHIFT 8
+#define CMD_SENDER_ID_MASK 0xff
+#define CMD_DATA_SIZE_SHIFT 20
+#define CMD_DATA_SIZE_MASK 0x1ff
+#define PACK_SCPI_CMD(cmd, sender, txsz) \
+ ((((cmd) & CMD_ID_MASK) << CMD_ID_SHIFT) | \
+ (((sender) & CMD_SENDER_ID_MASK) << CMD_SENDER_ID_SHIFT) | \
+ (((txsz) & CMD_DATA_SIZE_MASK) << CMD_DATA_SIZE_SHIFT))
+
+#define MAX_DVFS_DOMAINS 3
+#define MAX_DVFS_OPPS 4
+#define DVFS_LATENCY(hdr) ((hdr) >> 16)
+#define DVFS_OPP_COUNT(hdr) (((hdr) >> 8) & 0xff)
+
+enum scpi_error_codes {
+ SCPI_SUCCESS = 0, /* Success */
+ SCPI_ERR_PARAM = 1, /* Invalid parameter(s) */
+ SCPI_ERR_ALIGN = 2, /* Invalid alignment */
+ SCPI_ERR_SIZE = 3, /* Invalid size */
+ SCPI_ERR_HANDLER = 4, /* Invalid handler/callback */
+ SCPI_ERR_ACCESS = 5, /* Invalid access/permission denied */
+ SCPI_ERR_RANGE = 6, /* Value out of range */
+ SCPI_ERR_TIMEOUT = 7, /* Timeout has occurred */
+ SCPI_ERR_NOMEM = 8, /* Invalid memory area or pointer */
+ SCPI_ERR_PWRSTATE = 9, /* Invalid power state */
+ SCPI_ERR_SUPPORT = 10, /* Not supported or disabled */
+ SCPI_ERR_DEVICE = 11, /* Device error */
+ SCPI_ERR_MAX
+};
+
+enum scpi_client_id {
+ SCPI_CL_NONE,
+ SCPI_CL_CLOCKS,
+ SCPI_CL_DVFS,
+ SCPI_CL_POWER,
+ SCPI_MAX,
+};
+
+enum scpi_std_cmd {
+ SCPI_CMD_INVALID = 0x00,
+ SCPI_CMD_SCPI_READY = 0x01,
+ SCPI_CMD_SCPI_CAPABILITIES = 0x02,
+ SCPI_CMD_EVENT = 0x03,
+ SCPI_CMD_SET_CSS_PWR_STATE = 0x04,
+ SCPI_CMD_GET_CSS_PWR_STATE = 0x05,
+ SCPI_CMD_CFG_PWR_STATE_STAT = 0x06,
+ SCPI_CMD_GET_PWR_STATE_STAT = 0x07,
+ SCPI_CMD_SYS_PWR_STATE = 0x08,
+ SCPI_CMD_L2_READY = 0x09,
+ SCPI_CMD_SET_AP_TIMER = 0x0a,
+ SCPI_CMD_CANCEL_AP_TIME = 0x0b,
+ SCPI_CMD_DVFS_CAPABILITIES = 0x0c,
+ SCPI_CMD_GET_DVFS_INFO = 0x0d,
+ SCPI_CMD_SET_DVFS = 0x0e,
+ SCPI_CMD_GET_DVFS = 0x0f,
+ SCPI_CMD_GET_DVFS_STAT = 0x10,
+ SCPI_CMD_SET_RTC = 0x11,
+ SCPI_CMD_GET_RTC = 0x12,
+ SCPI_CMD_CLOCK_CAPABILITIES = 0x13,
+ SCPI_CMD_SET_CLOCK_INDEX = 0x14,
+ SCPI_CMD_SET_CLOCK_VALUE = 0x15,
+ SCPI_CMD_GET_CLOCK_VALUE = 0x16,
+ SCPI_CMD_PSU_CAPABILITIES = 0x17,
+ SCPI_CMD_SET_PSU = 0x18,
+ SCPI_CMD_GET_PSU = 0x19,
+ SCPI_CMD_SENSOR_CAPABILITIES = 0x1a,
+ SCPI_CMD_SENSOR_INFO = 0x1b,
+ SCPI_CMD_SENSOR_VALUE = 0x1c,
+ SCPI_CMD_SENSOR_CFG_PERIODIC = 0x1d,
+ SCPI_CMD_SENSOR_CFG_BOUNDS = 0x1e,
+ SCPI_CMD_SENSOR_ASYNC_VALUE = 0x1f,
+ SCPI_CMD_COUNT
+};
+
+struct scpi_data_buf {
+ int client_id;
+ struct mhu_data_buf *data;
+ struct completion complete;
+};
+
+static int high_priority_cmds[] = {
+ SCPI_CMD_GET_CSS_PWR_STATE,
+ SCPI_CMD_CFG_PWR_STATE_STAT,
+ SCPI_CMD_GET_PWR_STATE_STAT,
+ SCPI_CMD_SET_DVFS,
+ SCPI_CMD_GET_DVFS,
+ SCPI_CMD_SET_RTC,
+ SCPI_CMD_GET_RTC,
+ SCPI_CMD_SET_CLOCK_INDEX,
+ SCPI_CMD_SET_CLOCK_VALUE,
+ SCPI_CMD_GET_CLOCK_VALUE,
+ SCPI_CMD_SET_PSU,
+ SCPI_CMD_GET_PSU,
+ SCPI_CMD_SENSOR_VALUE,
+ SCPI_CMD_SENSOR_CFG_PERIODIC,
+ SCPI_CMD_SENSOR_CFG_BOUNDS,
+};
+
+static struct scpi_opp *scpi_opps[MAX_DVFS_DOMAINS];
+
+static int scpi_linux_errmap[SCPI_ERR_MAX] = {
+ 0, -EINVAL, -ENOEXEC, -EMSGSIZE,
+ -EINVAL, -EACCES, -ERANGE, -ETIMEDOUT,
+ -ENOMEM, -EINVAL, -EOPNOTSUPP, -EIO,
+};
+
+static inline int scpi_to_linux_errno(int errno)
+{
+ if (errno >= SCPI_SUCCESS && errno < SCPI_ERR_MAX)
+ return scpi_linux_errmap[errno];
+ return -EIO;
+}
+
+static bool high_priority_chan_supported(int cmd)
+{
+ int idx;
+ for (idx = 0; idx < ARRAY_SIZE(high_priority_cmds); idx++)
+ if (cmd == high_priority_cmds[idx])
+ return true;
+ return false;
+}
+
+static void scpi_rx_callback(struct mbox_client *cl, void *msg)
+{
+ struct mhu_data_buf *data = (struct mhu_data_buf *)msg;
+ struct scpi_data_buf *scpi_buf = data->cl_data;
+ complete(&scpi_buf->complete);
+}
+
+static int send_scpi_cmd(struct scpi_data_buf *scpi_buf, bool high_priority)
+{
+ struct mbox_chan *chan;
+ struct mbox_client cl;
+ struct mhu_data_buf *data = scpi_buf->data;
+ u32 status;
+
+ cl.dev = the_scpi_device;
+ cl.rx_callback = scpi_rx_callback;
+ cl.tx_done = NULL;
+ cl.tx_block = false;
+ cl.knows_txdone = false;
+ cl.chan_name = high_priority ?
+ CHANNEL_HIGH_PRIORITY :
+ CHANNEL_LOW_PRIORITY;
+
+ chan = mbox_request_channel(&cl);
+ if (IS_ERR(chan))
+ return PTR_ERR(chan);
+
+ init_completion(&scpi_buf->complete);
+ if (mbox_send_message(chan, (void *)data) < 0) {
+ status = SCPI_ERR_TIMEOUT;
+ goto free_channel;
+ }
+
+ wait_for_completion(&scpi_buf->complete);
+ status = *(u32 *)(data->rx_buf); /* read first word */
+
+free_channel:
+ mbox_free_channel(chan);
+
+ return scpi_to_linux_errno(status);
+}
+
+#define SCPI_SETUP_DBUF(scpi_buf, mhu_buf, _client_id,\
+ _cmd, _tx_buf, _rx_buf) \
+do { \
+ struct mhu_data_buf *pdata = &mhu_buf; \
+ pdata->cmd = _cmd; \
+ pdata->tx_buf = &_tx_buf; \
+ pdata->tx_size = sizeof(_tx_buf); \
+ pdata->rx_buf = &_rx_buf; \
+ pdata->rx_size = sizeof(_rx_buf); \
+ scpi_buf.client_id = _client_id; \
+ scpi_buf.data = pdata; \
+} while (0)
+
+static int scpi_execute_cmd(struct scpi_data_buf *scpi_buf)
+{
+ struct mhu_data_buf *data;
+ bool high_priority;
+
+ if (!scpi_buf || !scpi_buf->data)
+ return -EINVAL;
+
+ data = scpi_buf->data;
+ high_priority = high_priority_chan_supported(data->cmd);
+ data->cmd = PACK_SCPI_CMD(data->cmd, scpi_buf->client_id,
+ data->tx_size);
+ data->cl_data = scpi_buf;
+
+ return send_scpi_cmd(scpi_buf, high_priority);
+}
+
+unsigned long scpi_clk_get_val(u16 clk_id)
+{
+ struct scpi_data_buf sdata;
+ struct mhu_data_buf mdata;
+ struct __packed {
+ u32 status;
+ u32 clk_rate;
+ } buf;
+
+ SCPI_SETUP_DBUF(sdata, mdata, SCPI_CL_CLOCKS,
+ SCPI_CMD_GET_CLOCK_VALUE, clk_id, buf);
+ if (scpi_execute_cmd(&sdata))
+ return 0;
+
+ return buf.clk_rate;
+}
+EXPORT_SYMBOL_GPL(scpi_clk_get_val);
+
+int scpi_clk_set_val(u16 clk_id, unsigned long rate)
+{
+ struct scpi_data_buf sdata;
+ struct mhu_data_buf mdata;
+ int stat;
+ struct __packed {
+ u32 clk_rate;
+ u16 clk_id;
+ } buf;
+
+ buf.clk_rate = (u32)rate;
+ buf.clk_id = clk_id;
+
+ SCPI_SETUP_DBUF(sdata, mdata, SCPI_CL_CLOCKS,
+ SCPI_CMD_SET_CLOCK_VALUE, buf, stat);
+ return scpi_execute_cmd(&sdata);
+}
+EXPORT_SYMBOL_GPL(scpi_clk_set_val);
+
+struct scpi_opp *scpi_dvfs_get_opps(u8 domain)
+{
+ struct scpi_data_buf sdata;
+ struct mhu_data_buf mdata;
+ struct __packed {
+ u32 status;
+ u32 header;
+ u32 freqs[MAX_DVFS_OPPS];
+ } buf;
+ struct scpi_opp *opp;
+ size_t freqs_sz;
+ int count, ret;
+
+ if (domain >= MAX_DVFS_DOMAINS)
+ return ERR_PTR(-EINVAL);
+
+ if (scpi_opps[domain]) /* data already populated */
+ return scpi_opps[domain];
+
+ SCPI_SETUP_DBUF(sdata, mdata, SCPI_CL_DVFS,
+ SCPI_CMD_GET_DVFS_INFO, domain, buf);
+ ret = scpi_execute_cmd(&sdata);
+ if (ret)
+ return ERR_PTR(ret);
+
+ opp = kmalloc(sizeof(*opp), GFP_KERNEL);
+ if (!opp)
+ return ERR_PTR(-ENOMEM);
+
+ count = DVFS_OPP_COUNT(buf.header);
+ freqs_sz = count * sizeof(*(opp->freqs));
+
+ opp->count = count;
+ opp->latency = DVFS_LATENCY(buf.header);
+ opp->freqs = kmalloc(freqs_sz, GFP_KERNEL);
+ if (!opp->freqs) {
+ kfree(opp);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ memcpy(opp->freqs, &buf.freqs[0], freqs_sz);
+ scpi_opps[domain] = opp;
+
+ return opp;
+}
+EXPORT_SYMBOL_GPL(scpi_dvfs_get_opps);
+
+int scpi_dvfs_get_idx(u8 domain)
+{
+ struct scpi_data_buf sdata;
+ struct mhu_data_buf mdata;
+ struct __packed {
+ u32 status;
+ u8 dvfs_idx;
+ } buf;
+ int ret;
+
+ if (domain >= MAX_DVFS_DOMAINS)
+ return -EINVAL;
+
+ SCPI_SETUP_DBUF(sdata, mdata, SCPI_CL_DVFS,
+ SCPI_CMD_GET_DVFS, domain, buf);
+ ret = scpi_execute_cmd(&sdata);
+
+ if (!ret)
+ ret = buf.dvfs_idx;
+ return ret;
+}
+EXPORT_SYMBOL_GPL(scpi_dvfs_get_idx);
+
+int scpi_dvfs_set_idx(u8 domain, u8 idx)
+{
+ struct scpi_data_buf sdata;
+ struct mhu_data_buf mdata;
+ struct __packed {
+ u8 dvfs_domain;
+ u8 dvfs_idx;
+ } buf;
+ int stat;
+
+ buf.dvfs_idx = idx;
+ buf.dvfs_domain = domain;
+
+ if (domain >= MAX_DVFS_DOMAINS)
+ return -EINVAL;
+
+ SCPI_SETUP_DBUF(sdata, mdata, SCPI_CL_DVFS,
+ SCPI_CMD_SET_DVFS, buf, stat);
+ return scpi_execute_cmd(&sdata);
+}
+EXPORT_SYMBOL_GPL(scpi_dvfs_set_idx);
diff --git a/include/linux/mailbox_client.h b/include/linux/mailbox_client.h
new file mode 100644
index 000000000000..53eb0784261b
--- /dev/null
+++ b/include/linux/mailbox_client.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2014 Linaro Ltd.
+ * Author: Jassi Brar <jassisinghbrar@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __MAILBOX_CLIENT_H
+#define __MAILBOX_CLIENT_H
+
+#include <linux/of.h>
+
+struct mbox_chan;
+
+/**
+ * struct mbox_client - User of a mailbox
+ * @dev: The client device
+ * @chan_name: The "controller:channel" this client wants
+ * @rx_callback: Atomic callback to provide client the data received
+ * @tx_done: Atomic callback to tell client of data transmission
+ * @tx_block: If the mbox_send_message should block until data is
+ * transmitted.
+ * @tx_tout: Max block period in ms before TX is assumed failure
+ * @knows_txdone: if the client could run the TX state machine. Usually
+ * if the client receives some ACK packet for transmission.
+ * Unused if the controller already has TX_Done/RTR IRQ.
+ */
+struct mbox_client {
+ struct device *dev;
+ const char *chan_name;
+ void (*rx_callback)(struct mbox_client *cl, void *mssg);
+ void (*tx_done)(struct mbox_client *cl, void *mssg, int r);
+ bool tx_block;
+ unsigned long tx_tout;
+ bool knows_txdone;
+};
+
+struct mbox_chan *mbox_request_channel(const struct mbox_client *cl);
+int mbox_send_message(struct mbox_chan *chan, void *mssg);
+void mbox_client_txdone(struct mbox_chan *chan, int r);
+void mbox_free_channel(struct mbox_chan *chan);
+
+#endif /* __MAILBOX_CLIENT_H */
diff --git a/include/linux/mailbox_controller.h b/include/linux/mailbox_controller.h
new file mode 100644
index 000000000000..5d1915b9af60
--- /dev/null
+++ b/include/linux/mailbox_controller.h
@@ -0,0 +1,121 @@
+/*
+ * 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.
+ */
+
+#ifndef __MAILBOX_CONTROLLER_H
+#define __MAILBOX_CONTROLLER_H
+
+#include <linux/of.h>
+
+struct mbox_chan;
+
+/**
+ * struct mbox_chan_ops - s/w representation of a communication chan
+ * @send_data: The API asks the MBOX controller driver, in atomic
+ * context try to transmit a message on the bus. Returns 0 if
+ * data is accepted for transmission, -EBUSY while rejecting
+ * if the remote hasn't yet read the last data sent. Actual
+ * transmission of data is reported by the controller via
+ * mbox_chan_txdone (if it has some TX ACK irq). It must not
+ * block.
+ * @startup: Called when a client requests the chan. The controller
+ * could ask clients for additional parameters of communication
+ * to be provided via client's chan_data. This call may
+ * block. After this call the Controller must forward any
+ * data received on the chan by calling mbox_chan_received_data.
+ * @shutdown: Called when a client relinquishes control of a chan.
+ * This call may block too. The controller must not forwared
+ * any received data anymore.
+ * @last_tx_done: If the controller sets 'txdone_poll', the API calls
+ * this to poll status of last TX. The controller must
+ * give priority to IRQ method over polling and never
+ * set both txdone_poll and txdone_irq. Only in polling
+ * mode 'send_data' is expected to return -EBUSY.
+ * Used only if txdone_poll:=true && txdone_irq:=false
+ * @peek_data: Atomic check for any received data. Return true if controller
+ * has some data to push to the client. False otherwise.
+ */
+struct mbox_chan_ops {
+ int (*send_data)(struct mbox_chan *chan, void *data);
+ int (*startup)(struct mbox_chan *chan);
+ void (*shutdown)(struct mbox_chan *chan);
+ bool (*last_tx_done)(struct mbox_chan *chan);
+ bool (*peek_data)(struct mbox_chan *chan);
+};
+
+/**
+ * struct mbox_controller - Controller of a class of communication chans
+ * @dev: Device backing this controller
+ * @controller_name: Literal name of the controller.
+ * @ops: Operators that work on each communication chan
+ * @chans: Null terminated array of chans.
+ * @txdone_irq: Indicates if the controller can report to API when
+ * the last transmitted data was read by the remote.
+ * Eg, if it has some TX ACK irq.
+ * @txdone_poll: If the controller can read but not report the TX
+ * done. Ex, some register shows the TX status but
+ * no interrupt rises. Ignored if 'txdone_irq' is set.
+ * @txpoll_period: If 'txdone_poll' is in effect, the API polls for
+ * last TX's status after these many millisecs
+ */
+struct mbox_controller {
+ struct device *dev;
+ struct mbox_chan_ops *ops;
+ struct mbox_chan *chans;
+ int num_chans;
+ bool txdone_irq;
+ bool txdone_poll;
+ unsigned txpoll_period;
+ struct mbox_chan *(*of_xlate)(struct mbox_controller *mbox,
+ const struct of_phandle_args *sp);
+ /*
+ * If the controller supports only TXDONE_BY_POLL,
+ * this timer polls all the links for txdone.
+ */
+ struct timer_list poll;
+ unsigned period;
+ /* Hook to add to the global controller list */
+ struct list_head node;
+};
+
+/*
+ * The length of circular buffer for queuing messages from a client.
+ * 'msg_count' tracks the number of buffered messages while 'msg_free'
+ * is the index where the next message would be buffered.
+ * We shouldn't need it too big because every transferr is interrupt
+ * triggered and if we have lots of data to transfer, the interrupt
+ * latencies are going to be the bottleneck, not the buffer length.
+ * Besides, mbox_send_message could be called from atomic context and
+ * the client could also queue another message from the notifier 'tx_done'
+ * of the last transfer done.
+ * REVIST: If too many platforms see the "Try increasing MBOX_TX_QUEUE_LEN"
+ * print, it needs to be taken from config option or somesuch.
+ */
+#define MBOX_TX_QUEUE_LEN 20
+
+struct mbox_chan {
+ struct mbox_controller *mbox; /* Parent Controller */
+ unsigned txdone_method;
+
+ /* client */
+ struct mbox_client *cl;
+ struct completion tx_complete;
+
+ void *active_req;
+ unsigned msg_count, msg_free;
+ void *msg_data[MBOX_TX_QUEUE_LEN];
+ /* Access to the channel */
+ spinlock_t lock;
+
+ /* Private data for controller */
+ void *con_priv;
+};
+
+int mbox_controller_register(struct mbox_controller *mbox);
+void mbox_chan_received_data(struct mbox_chan *chan, void *data);
+void mbox_chan_txdone(struct mbox_chan *chan, int r);
+void mbox_controller_unregister(struct mbox_controller *mbox);
+
+#endif /* __MAILBOX_CONTROLLER_H */
diff --git a/include/linux/mailbox.h b/include/linux/pl320-ipc.h
index 5161f63ec1c8..5161f63ec1c8 100644
--- a/include/linux/mailbox.h
+++ b/include/linux/pl320-ipc.h
diff --git a/include/linux/scpi_protocol.h b/include/linux/scpi_protocol.h
new file mode 100644
index 000000000000..66e5eb3710ab
--- /dev/null
+++ b/include/linux/scpi_protocol.h
@@ -0,0 +1,30 @@
+/*
+ * SCPI Message Protocol driver header
+ *
+ * Copyright (C) 2014 ARM Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 <http://www.gnu.org/licenses/>.
+ */
+#include <linux/types.h>
+
+struct scpi_opp {
+ u32 *freqs;
+ u32 latency; /* in usecs */
+ int count;
+};
+
+unsigned long scpi_clk_get_val(u16 clk_id);
+int scpi_clk_set_val(u16 clk_id, unsigned long rate);
+int scpi_dvfs_get_idx(u8 domain);
+int scpi_dvfs_set_idx(u8 domain, u8 idx);
+struct scpi_opp *scpi_dvfs_get_opps(u8 domain);