From 233e144415048ee39fce4a2888a5e9056bb23f21 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 2 May 2014 13:38:31 -0700 Subject: arm64: topology: Tell the scheduler about the relative power of cores In heterogeneous systems like big.LITTLE systems the scheduler will be able to make better use of the available cores if we provide power numbers to it indicating their relative performance. Do this by parsing the CPU nodes in the DT. This code currently has no effect as no information on the relative performance of the cores is provided. Signed-off-by: Mark Brown Signed-off-by: Jon Medhurst --- arch/arm64/kernel/topology.c | 153 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) diff --git a/arch/arm64/kernel/topology.c b/arch/arm64/kernel/topology.c index b6ee26b0939a..7a6eabec3c06 100644 --- a/arch/arm64/kernel/topology.c +++ b/arch/arm64/kernel/topology.c @@ -19,10 +19,34 @@ #include #include #include +#include #include #include +/* + * 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; @@ -161,6 +185,38 @@ 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[] = { + { 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; @@ -200,6 +256,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_POWER_SHIFT+1); + else + middle_capacity = ((max_capacity / 3) + >> (SCHED_POWER_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 */ @@ -269,6 +410,7 @@ void store_cpu_topology(unsigned int cpuid) topology_populated: update_siblings_masks(cpuid); + update_cpu_power(cpuid); } static void __init reset_cpu_topology(void) @@ -289,6 +431,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_POWER_SCALE); +} + void __init init_cpu_topology(void) { reset_cpu_topology(); @@ -299,4 +449,7 @@ void __init init_cpu_topology(void) */ if (parse_dt_topology()) reset_cpu_topology(); + + reset_cpu_power(); + parse_dt_cpu_power(); } -- cgit v1.2.3 From 753f654290ec838199d4220a8ac9ee2ae3e8acfb Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 2 May 2014 13:38:32 -0700 Subject: arm64: topology: Provide relative power numbers for cores Provide performance numbers to the scheduler to help it fill the cores in the system on big.LITTLE systems. With the current scheduler this may perform poorly for applications that try to do OpenMP style work over all cores but should help for more common workloads. The current 32 bit ARM implementation provides a similar estimate so this helps ensure that work to improve big.LITTLE systems on ARMv7 systems performs similarly on ARMv8 systems. The power numbers are the same as for ARMv7 since it seems that the expected differential between the big and little cores is very similar on both ARMv7 and ARMv8. In both ARMv7 and ARMv8 cases the numbers were based on the published DMIPS numbers. These numbers are just an initial and basic approximation for use with the current scheduler, it is likely that both experience with silicon and ongoing work on improving the scheduler will lead to further tuning or will tune automatically at runtime and so make the specific choice of numbers here less critical. Signed-off-by: Mark Brown Signed-off-by: Jon Medhurst --- arch/arm64/kernel/topology.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm64/kernel/topology.c b/arch/arm64/kernel/topology.c index 7a6eabec3c06..7924ecb6faf7 100644 --- a/arch/arm64/kernel/topology.c +++ b/arch/arm64/kernel/topology.c @@ -201,6 +201,8 @@ struct cpu_efficiency { * 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, }, }; -- cgit v1.2.3 From ea18c057a3853cdff3603445a3cfe5c2d30ff951 Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Tue, 15 Apr 2014 15:14:27 +0100 Subject: mailbox: add support for ARM Message Handling Unit(MHU) controller This patch adds support for ARM Message Handling Unit(MHU) controller that provides control logic and interrupt generation to support inter-processor communication between the Application Processor and the System Control Processor(SCP). This support is built on the existing common mailbox framework for client/protocol drivers and controller drivers of Inter Processor Communication(IPC). SCP controls most of the power managament on the Application Processors. Signed-off-by: Sudeep Holla Signed-off-by: Jon Medhurst --- drivers/mailbox/Kconfig | 14 ++ drivers/mailbox/Makefile | 2 + drivers/mailbox/arm_mhu.c | 336 ++++++++++++++++++++++++++++++++++++++++++++++ drivers/mailbox/arm_mhu.h | 31 +++++ 4 files changed, 383 insertions(+) create mode 100644 drivers/mailbox/arm_mhu.c create mode 100644 drivers/mailbox/arm_mhu.h diff --git a/drivers/mailbox/Kconfig b/drivers/mailbox/Kconfig index 9fd9c6717e0c..cbe038319816 100644 --- a/drivers/mailbox/Kconfig +++ b/drivers/mailbox/Kconfig @@ -6,6 +6,20 @@ 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 PL320_MBOX bool "ARM PL320 Mailbox" depends on ARM_AMBA diff --git a/drivers/mailbox/Makefile b/drivers/mailbox/Makefile index 94ed7cefb14d..ff640e5c957b 100644 --- a/drivers/mailbox/Makefile +++ b/drivers/mailbox/Makefile @@ -2,6 +2,8 @@ obj-$(CONFIG_MAILBOX) += mailbox.o +obj-$(CONFIG_ARM_MHU_MBOX) += arm_mhu.o + obj-$(CONFIG_PL320_MBOX) += pl320-ipc.o obj-$(CONFIG_OMAP2PLUS_MBOX) += omap-mailbox.o diff --git a/drivers/mailbox/arm_mhu.c b/drivers/mailbox/arm_mhu.c new file mode 100644 index 000000000000..841b0cb1b710 --- /dev/null +++ b/drivers/mailbox/arm_mhu.c @@ -0,0 +1,336 @@ +/* + * 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 + * + * 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 . + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "arm_mhu.h" + +#define DRIVER_NAME CONTROLLER_NAME"_drv" + +/* + * +--------------------+-------+---------------+ + * | 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 mbox_link link; + 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 inline struct mhu_chan *to_mhu_chan(struct mbox_link *lnk) +{ + if (!lnk) + return NULL; + + return container_of(lnk, struct mhu_chan, link); +} + +static irqreturn_t mbox_handler(int irq, void *p) +{ + struct mbox_link *link = (struct mbox_link *)p; + struct mhu_chan *chan = to_mhu_chan(link); + 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; + writel(~0, mbox_base + RX_CLEAR(idx)); + mbox_link_received_data(link, data); + } + + return IRQ_HANDLED; +} + +static int mhu_send_data(struct mbox_link *link, void *msg) +{ + struct mhu_chan *chan = to_mhu_chan(link); + 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_link *link, void *ignored) +{ + struct mhu_chan *chan = to_mhu_chan(link); + int err, mbox_irq = chan->rx_irq; + + err = request_threaded_irq(mbox_irq, NULL, mbox_handler, IRQF_ONESHOT, + link->link_name, link); + if (err) + return err; + + chan->data = NULL; + return 0; +} + +static void mhu_shutdown(struct mbox_link *link) +{ + struct mhu_chan *chan = to_mhu_chan(link); + + chan->data = NULL; + free_irq(chan->rx_irq, link); +} + +static bool mhu_last_tx_done(struct mbox_link *link) +{ + struct mhu_chan *chan = to_mhu_chan(link); + 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_link_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_link **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 + 1), GFP_KERNEL); + if (!l) { + dev_err(dev, "failed to allocate memory\n"); + return -ENOMEM; + } + + ctlr->mbox_con.links = l; + ctlr->mbox_con.txdone_poll = true; + ctlr->mbox_con.txpoll_period = 10; + ctlr->mbox_con.ops = &mhu_ops; + snprintf(ctlr->mbox_con.controller_name, 16, CONTROLLER_NAME); + 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] = &chan->link; + snprintf(l[idx]->link_name, 16, channel_names[idx]); + } + l[idx] = NULL; + + if (mbox_controller_register(&ctlr->mbox_con)) { + dev_err(dev, "failed to register mailbox controller\n"); + return -ENOMEM; + } + _dev_info(dev, "registered mailbox controller %s\n", + ctlr->mbox_con.controller_name); + 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); + _dev_info(dev, "unregistered mailbox controller %s\n", + ctlr->mbox_con.controller_name); + devm_kfree(dev, ctlr->mbox_con.links); + + 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 "); +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..3b5343375c43 --- /dev/null +++ b/drivers/mailbox/arm_mhu.h @@ -0,0 +1,31 @@ +/* + * 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 . + */ +#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; +}; -- cgit v1.2.3 From 27340e6ffe363e5dde474154f783db89ae7b79d8 Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Tue, 15 Apr 2014 16:09:42 +0100 Subject: mailbox: add support for System Control and Power Interface(SCPI) protocol This patch add supports for System Control and Power Interface (SCPI) Message Protocol used between the Application Cores(AP) and the System Control Processor(SCP). The MHU peripheral provides a mechanism for inter-processor communication between SCP's M3 processor and AP. SCP 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. This protocol library provides interface for all the client drivers using SCPI to make use of the features offered by the SCP. Signed-off-by: Sudeep Holla Signed-off-by: Jon Medhurst --- drivers/mailbox/Kconfig | 13 ++ drivers/mailbox/Makefile | 1 + drivers/mailbox/scpi_protocol.c | 354 ++++++++++++++++++++++++++++++++++++++++ include/linux/scpi_protocol.h | 30 ++++ 4 files changed, 398 insertions(+) create mode 100644 drivers/mailbox/scpi_protocol.c create mode 100644 include/linux/scpi_protocol.h diff --git a/drivers/mailbox/Kconfig b/drivers/mailbox/Kconfig index cbe038319816..8e5e5b1becec 100644 --- a/drivers/mailbox/Kconfig +++ b/drivers/mailbox/Kconfig @@ -20,6 +20,19 @@ config ARM_MHU_MBOX 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 ff640e5c957b..e31c84b6bc40 100644 --- a/drivers/mailbox/Makefile +++ b/drivers/mailbox/Makefile @@ -3,6 +3,7 @@ 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 diff --git a/drivers/mailbox/scpi_protocol.c b/drivers/mailbox/scpi_protocol.c new file mode 100644 index 000000000000..7a1ff2dd2687 --- /dev/null +++ b/drivers/mailbox/scpi_protocol.c @@ -0,0 +1,354 @@ +/* + * 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#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.rx_callback = scpi_rx_callback; + cl.tx_done = NULL; + cl.tx_block = true; + cl.tx_tout = 50; /* 50 msec */ + cl.link_data = NULL; + cl.knows_txdone = false; + cl.chan_name = high_priority ? + CONTROLLER_NAME":"CHANNEL_HIGH_PRIORITY : + CONTROLLER_NAME":"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)) + return -EIO; + + if (!wait_for_completion_timeout(&scpi_buf->complete, + msecs_to_jiffies(50))) + status = SCPI_ERR_TIMEOUT; + else + status = *(u32 *)(data->rx_buf); /* read first word */ + + 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 { + 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 { + 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 { + 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 { + 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 { + 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/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 . + */ +#include + +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); -- cgit v1.2.3 From 1eb3b5540c0773d87e14b1c1f3c488893666128f Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Fri, 27 Jun 2014 16:54:07 +0100 Subject: mailbox: get mhu driver working with new (v7) mailbox framework Signed-off-by: Jon Medhurst --- drivers/mailbox/arm_mhu.c | 65 ++++++++++++++++------------------------- drivers/mailbox/arm_mhu.h | 2 ++ drivers/mailbox/scpi_protocol.c | 6 ++-- 3 files changed, 30 insertions(+), 43 deletions(-) diff --git a/drivers/mailbox/arm_mhu.c b/drivers/mailbox/arm_mhu.c index 841b0cb1b710..6256caae9133 100644 --- a/drivers/mailbox/arm_mhu.c +++ b/drivers/mailbox/arm_mhu.c @@ -46,7 +46,9 @@ #include "arm_mhu.h" -#define DRIVER_NAME CONTROLLER_NAME"_drv" +struct device* the_scpi_device; + +#define DRIVER_NAME "arm_mhu" /* * +--------------------+-------+---------------+ @@ -98,7 +100,6 @@ struct mhu_chan { int index; int rx_irq; - struct mbox_link link; struct mhu_ctlr *ctlr; struct mhu_data_buf *data; }; @@ -111,18 +112,10 @@ struct mhu_ctlr { struct mhu_chan channels[CHANNEL_MAX]; }; -static inline struct mhu_chan *to_mhu_chan(struct mbox_link *lnk) -{ - if (!lnk) - return NULL; - - return container_of(lnk, struct mhu_chan, link); -} - static irqreturn_t mbox_handler(int irq, void *p) { - struct mbox_link *link = (struct mbox_link *)p; - struct mhu_chan *chan = to_mhu_chan(link); + 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; @@ -138,15 +131,15 @@ static irqreturn_t mbox_handler(int irq, void *p) data->rx_size); chan->data = NULL; writel(~0, mbox_base + RX_CLEAR(idx)); - mbox_link_received_data(link, data); + mbox_chan_received_data(link, data); } return IRQ_HANDLED; } -static int mhu_send_data(struct mbox_link *link, void *msg) +static int mhu_send_data(struct mbox_chan *link, void *msg) { - struct mhu_chan *chan = to_mhu_chan(link); + 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; @@ -164,31 +157,27 @@ static int mhu_send_data(struct mbox_link *link, void *msg) return 0; } -static int mhu_startup(struct mbox_link *link, void *ignored) +static int mhu_startup(struct mbox_chan *link) { - struct mhu_chan *chan = to_mhu_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, - link->link_name, link); - if (err) - return err; - - chan->data = NULL; - return 0; + DRIVER_NAME, link); + return err; } -static void mhu_shutdown(struct mbox_link *link) +static void mhu_shutdown(struct mbox_chan *link) { - struct mhu_chan *chan = to_mhu_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_link *link) +static bool mhu_last_tx_done(struct mbox_chan *link) { - struct mhu_chan *chan = to_mhu_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; @@ -196,7 +185,7 @@ static bool mhu_last_tx_done(struct mbox_link *link) return !readl(mbox_base + TX_STATUS(idx)); } -static struct mbox_link_ops mhu_ops = { +static struct mbox_chan_ops mhu_ops = { .send_data = mhu_send_data, .startup = mhu_startup, .shutdown = mhu_shutdown, @@ -208,7 +197,7 @@ static int mhu_probe(struct platform_device *pdev) struct mhu_ctlr *ctlr; struct mhu_chan *chan; struct device *dev = &pdev->dev; - struct mbox_link **l; + struct mbox_chan *l; struct resource *res; int idx; static const char * const channel_names[] = { @@ -249,17 +238,17 @@ static int mhu_probe(struct platform_device *pdev) ctlr->dev = dev; platform_set_drvdata(pdev, ctlr); - l = devm_kzalloc(dev, sizeof(*l) * (CHANNEL_MAX + 1), GFP_KERNEL); + 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.links = l; + ctlr->mbox_con.chans = l; + ctlr->mbox_con.num_chans = CHANNEL_MAX; ctlr->mbox_con.txdone_poll = true; ctlr->mbox_con.txpoll_period = 10; ctlr->mbox_con.ops = &mhu_ops; - snprintf(ctlr->mbox_con.controller_name, 16, CONTROLLER_NAME); ctlr->mbox_con.dev = dev; for (idx = 0; idx < CHANNEL_MAX; idx++) { @@ -272,17 +261,15 @@ static int mhu_probe(struct platform_device *pdev) channel_names[idx]); return -ENXIO; } - l[idx] = &chan->link; - snprintf(l[idx]->link_name, 16, channel_names[idx]); + l[idx].con_priv = chan; } - l[idx] = NULL; if (mbox_controller_register(&ctlr->mbox_con)) { dev_err(dev, "failed to register mailbox controller\n"); return -ENOMEM; } - _dev_info(dev, "registered mailbox controller %s\n", - ctlr->mbox_con.controller_name); + + the_scpi_device = dev; return 0; } @@ -292,9 +279,7 @@ static int mhu_remove(struct platform_device *pdev) struct device *dev = &pdev->dev; mbox_controller_unregister(&ctlr->mbox_con); - _dev_info(dev, "unregistered mailbox controller %s\n", - ctlr->mbox_con.controller_name); - devm_kfree(dev, ctlr->mbox_con.links); + devm_kfree(dev, ctlr->mbox_con.chans); devm_iounmap(dev, ctlr->payload_base); devm_iounmap(dev, ctlr->mbox_base); diff --git a/drivers/mailbox/arm_mhu.h b/drivers/mailbox/arm_mhu.h index 3b5343375c43..f78c1fa369c8 100644 --- a/drivers/mailbox/arm_mhu.h +++ b/drivers/mailbox/arm_mhu.h @@ -29,3 +29,5 @@ struct mhu_data_buf { void *rx_buf; void *cl_data; }; + +extern struct device* the_scpi_device; diff --git a/drivers/mailbox/scpi_protocol.c b/drivers/mailbox/scpi_protocol.c index 7a1ff2dd2687..c8a824a60a43 100644 --- a/drivers/mailbox/scpi_protocol.c +++ b/drivers/mailbox/scpi_protocol.c @@ -165,15 +165,15 @@ static int send_scpi_cmd(struct scpi_data_buf *scpi_buf, bool high_priority) 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 = true; cl.tx_tout = 50; /* 50 msec */ - cl.link_data = NULL; cl.knows_txdone = false; cl.chan_name = high_priority ? - CONTROLLER_NAME":"CHANNEL_HIGH_PRIORITY : - CONTROLLER_NAME":"CHANNEL_LOW_PRIORITY; + CHANNEL_HIGH_PRIORITY : + CHANNEL_LOW_PRIORITY; chan = mbox_request_channel(&cl); if (IS_ERR(chan)) -- cgit v1.2.3 From 067e601b0bf956b0a0076b4d8e8ad2ca41abb0c1 Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Thu, 24 Apr 2014 16:58:11 +0100 Subject: clk: add support for clocks provided by system control processor On some ARM based systems, a separate Cortex-M based System Control Processor(SCP) provides the overall power, clock, reset and system control. System Control and Power Interface(SCPI) Message Protocol is defined for the communication between the Application Cores(AP) and the SCP. This patch adds support for the clocks provided by SCP using SCPI protocol. Signed-off-by: Sudeep Holla Signed-off-by: Jon Medhurst --- Documentation/devicetree/bindings/clock/scpi.txt | 34 +++ drivers/clk/Kconfig | 10 + drivers/clk/Makefile | 1 + drivers/clk/clk-scpi.c | 309 +++++++++++++++++++++++ 4 files changed, 354 insertions(+) create mode 100644 Documentation/devicetree/bindings/clock/scpi.txt create mode 100644 drivers/clk/clk-scpi.c 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/drivers/clk/Kconfig b/drivers/clk/Kconfig index 455fd17d938e..0c8192076aa5 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_MAX_GEN bool diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index d5fba5bc6e1b..589b7732e5b6 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -33,6 +33,7 @@ obj-$(CONFIG_COMMON_CLK_PALMAS) += clk-palmas.o obj-$(CONFIG_CLK_PPC_CORENET) += clk-ppc-corenet.o obj-$(CONFIG_COMMON_CLK_RK808) += clk-rk808.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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +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 "); +MODULE_DESCRIPTION("ARM SCPI clock driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From c8ddd99eeabeb7b08e4734763b803dab9496d001 Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Thu, 8 May 2014 17:47:48 +0100 Subject: cpufreq: arm_big_little: add SPCI interface driver On some ARM based systems, a separate Cortex-M based System Control Processor(SCP) provides the overall power, clock, reset and system control including CPU DVFS. SCPI Message Protocol is used to communicate with the SCPI. This patch adds a interface driver for adding OPPs and registering the arm_big_little cpufreq driver for such systems. Signed-off-by: Sudeep Holla Signed-off-by: Jon Medhurst --- drivers/cpufreq/Kconfig.arm | 9 ++++ drivers/cpufreq/Makefile | 1 + drivers/cpufreq/scpi-cpufreq.c | 99 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+) create mode 100644 drivers/cpufreq/scpi-cpufreq.c diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm index 83a75dc84761..d504c0cf86bc 100644 --- a/drivers/cpufreq/Kconfig.arm +++ b/drivers/cpufreq/Kconfig.arm @@ -24,6 +24,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 40c53dc1937e..f2964a6cf3fc 100644 --- a/drivers/cpufreq/Makefile +++ b/drivers/cpufreq/Makefile @@ -77,6 +77,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/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 + * + * 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 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"); -- cgit v1.2.3 From 9475aa7a52a164c1ec763efc6e5105e07e720a6e Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Wed, 28 May 2014 15:15:49 +0100 Subject: [HACK] cpufreq: arm_big_little: Fall back to getting clock from cpu device The driver in LSK assumes a hard-coded name for cluster clock, as used by vexpress TC2. Modify this to allow also clocks to be obtained from the cpu device; as Juno requires and as seems more like the correct way. Signed-off-by: Jon Medhurst --- drivers/cpufreq/arm_big_little.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/cpufreq/arm_big_little.c b/drivers/cpufreq/arm_big_little.c index a46c223c2506..91bf1c829358 100644 --- a/drivers/cpufreq/arm_big_little.c +++ b/drivers/cpufreq/arm_big_little.c @@ -341,7 +341,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], -- cgit v1.2.3 From 3fe7d8da47f04d1a716f0bbb0a866c912d66c0ce Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 9 May 2014 17:24:17 +0100 Subject: arm64: Add big.LITTLE switcher stub The big.LITTLE cpufreq driver is useful on arm64 big.LITTLE systems even without IKS support since it implements support for clusters with shared clocks (a common big.LITTLE configuration). In order to allow it to be built provide the non-IKS stubs for arm64, enabling cpufreq with all the cores available. It may make sense to make an asm-generic version of these stubs instead but given that there's only likely to be these two architectures using the code and asm-generic stubs also need per architecture updates it's probably more trouble than it's worth. Signed-off-by: Mark Brown --- arch/arm64/include/asm/bL_switcher.h | 54 ++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 arch/arm64/include/asm/bL_switcher.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 +#include + +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 -- cgit v1.2.3 From dfb9e5c2e892923628229e463c045183ccdd65ec Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 9 May 2014 17:40:31 +0100 Subject: cpufreq: Enable big.LITTLE cpufreq driver on arm64 There are arm64 big.LITTLE systems so enable the big.LITTLE cpufreq driver. While IKS is not available for these systems the driver is still useful since it manages clusters with shared frequencies which is the common case for these systems. Long term combining the cpufreq-cpu0 and big.LITTLE drivers may be a more sensible option but that is substantially more complex especially in the case of IKS. Signed-off-by: Mark Brown Acked-by: Viresh Kumar Signed-off-by: Rafael J. Wysocki (cherry picked from commit 4920ab84979d8cd2eb7e3c4fefcc924efabf1cb2) Signed-off-by: Mark Brown Signed-off-by: Jon Medhurst --- drivers/cpufreq/Kconfig.arm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm index d504c0cf86bc..037795c23b0e 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. -- cgit v1.2.3 From 8b116c282da8505fa387580436b2c6d6741a4730 Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Mon, 23 Jun 2014 11:39:09 +0100 Subject: arm64: topology: Use new name for SCHED_POWER_SHIFT Commit ca8ce3d0b1 (sched: Final power vs. capacity cleanups) renamed SCHED_POWER_SHIFT to SCHED_CAPACITY_SHIFT so we need to use that name. Signed-off-by: Jon Medhurst --- arch/arm64/kernel/topology.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/arch/arm64/kernel/topology.c b/arch/arm64/kernel/topology.c index 7924ecb6faf7..4af25776eb95 100644 --- a/arch/arm64/kernel/topology.c +++ b/arch/arm64/kernel/topology.c @@ -321,10 +321,10 @@ static void __init parse_dt_cpu_power(void) return; else if (4 * max_capacity < (3 * (max_capacity + min_capacity))) middle_capacity = (min_capacity + max_capacity) - >> (SCHED_POWER_SHIFT+1); + >> (SCHED_CAPACITY_SHIFT+1); else middle_capacity = ((max_capacity / 3) - >> (SCHED_POWER_SHIFT-1)) + 1; + >> (SCHED_CAPACITY_SHIFT-1)) + 1; } /* @@ -438,7 +438,7 @@ static void __init reset_cpu_power(void) unsigned int cpu; for_each_possible_cpu(cpu) - set_power_scale(cpu, SCHED_POWER_SCALE); + set_power_scale(cpu, SCHED_CAPACITY_SCALE); } void __init init_cpu_topology(void) -- cgit v1.2.3 From 1587fee6408fd5f0069bf305809914ca2119521e Mon Sep 17 00:00:00 2001 From: Liviu Dudau Date: Tue, 22 Jul 2014 18:34:54 +0100 Subject: mailbox: Pack SCPI structures used for messages. The System Control Processor expects data sent in the messages to be contiguos. When using unpacked structures to describe the data being transmitted we increase the general size of the message which leads to SCP rejecting our request. Signed-off-by: Liviu Dudau Signed-off-by: Jon Medhurst --- drivers/mailbox/scpi_protocol.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/mailbox/scpi_protocol.c b/drivers/mailbox/scpi_protocol.c index c8a824a60a43..4e442667ed7f 100644 --- a/drivers/mailbox/scpi_protocol.c +++ b/drivers/mailbox/scpi_protocol.c @@ -228,7 +228,7 @@ unsigned long scpi_clk_get_val(u16 clk_id) { struct scpi_data_buf sdata; struct mhu_data_buf mdata; - struct { + struct __packed { u32 status; u32 clk_rate; } buf; @@ -247,7 +247,7 @@ int scpi_clk_set_val(u16 clk_id, unsigned long rate) struct scpi_data_buf sdata; struct mhu_data_buf mdata; int stat; - struct { + struct __packed { u32 clk_rate; u16 clk_id; } buf; @@ -265,7 +265,7 @@ struct scpi_opp *scpi_dvfs_get_opps(u8 domain) { struct scpi_data_buf sdata; struct mhu_data_buf mdata; - struct { + struct __packed { u32 status; u32 header; u32 freqs[MAX_DVFS_OPPS]; @@ -312,7 +312,7 @@ int scpi_dvfs_get_idx(u8 domain) { struct scpi_data_buf sdata; struct mhu_data_buf mdata; - struct { + struct __packed { u32 status; u8 dvfs_idx; } buf; @@ -335,7 +335,7 @@ int scpi_dvfs_set_idx(u8 domain, u8 idx) { struct scpi_data_buf sdata; struct mhu_data_buf mdata; - struct { + struct __packed { u8 dvfs_domain; u8 dvfs_idx; } buf; -- cgit v1.2.3 From 3f8d89c22d69fcacc0f5eb2a1abbc00b6f1dc176 Mon Sep 17 00:00:00 2001 From: Liviu Dudau Date: Thu, 24 Jul 2014 11:14:21 +0100 Subject: mailbox: scpi: Free the mailbox channel when we fail to queue a message. When sending an SCPI command we aquire a channel and queue the message in the mailbox. If the queuing failed we were not releasing the channel hence preventing everyone else from using it. Signed-off-by: Punit Agrawal Signed-off-by: Liviu Dudau Signed-off-by: Jon Medhurst --- drivers/mailbox/scpi_protocol.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/mailbox/scpi_protocol.c b/drivers/mailbox/scpi_protocol.c index 4e442667ed7f..195f86c6fd58 100644 --- a/drivers/mailbox/scpi_protocol.c +++ b/drivers/mailbox/scpi_protocol.c @@ -180,8 +180,10 @@ static int send_scpi_cmd(struct scpi_data_buf *scpi_buf, bool high_priority) return PTR_ERR(chan); init_completion(&scpi_buf->complete); - if (mbox_send_message(chan, (void *)data)) - return -EIO; + if (mbox_send_message(chan, (void *)data) < 0) { + status = SCPI_ERR_TIMEOUT; + goto free_channel; + } if (!wait_for_completion_timeout(&scpi_buf->complete, msecs_to_jiffies(50))) @@ -189,6 +191,7 @@ static int send_scpi_cmd(struct scpi_data_buf *scpi_buf, bool high_priority) else status = *(u32 *)(data->rx_buf); /* read first word */ +free_channel: mbox_free_channel(chan); return scpi_to_linux_errno(status); -- cgit v1.2.3 From cd9218d67560c08f9e89e461aecb34e595d9d885 Mon Sep 17 00:00:00 2001 From: Liviu Dudau Date: Thu, 24 Jul 2014 12:21:17 +0100 Subject: mailbox: mhu: Acknowledge the interrupt only after data is pushed According to the mailbox documentation the controller should ACK the RX only after it has finished pushing the data up the link. Signed-off-by: Punit Agrawal Signed-off-by: Liviu Dudau Signed-off-by: Jon Medhurst --- drivers/mailbox/arm_mhu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mailbox/arm_mhu.c b/drivers/mailbox/arm_mhu.c index 6256caae9133..5029af71780d 100644 --- a/drivers/mailbox/arm_mhu.c +++ b/drivers/mailbox/arm_mhu.c @@ -130,8 +130,8 @@ static irqreturn_t mbox_handler(int irq, void *p) memcpy(data->rx_buf, payload + RX_PAYLOAD(idx), data->rx_size); chan->data = NULL; - writel(~0, mbox_base + RX_CLEAR(idx)); mbox_chan_received_data(link, data); + writel(~0, mbox_base + RX_CLEAR(idx)); } return IRQ_HANDLED; -- cgit v1.2.3 From 4ad63320755196017b29386ddb02e301cb670e85 Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Tue, 29 Jul 2014 14:02:26 +0100 Subject: mailbox: Remove all message timeouts and block until they complete Neither the mailbox framework nor the scpi_protocol code correctly handle timeouts if a message is subsequently completed by the SCP, in that case they end up accessing no-longer live stack based objects. Even if the code was reworked to fix those issues, we are still left with problems with the scpi protocol because a delayed message response may look like a reply to a later message. To hopefully avoid all these problems this patch removes all timeouts and forces things block until each message completes. Signed-off-by: Jon Medhurst On branch test Untracked files: 000000_scpi_protocol.c nothing added to commit but untracked files present (use "git add" to track) Signed-off-by: Jon Medhurst --- drivers/mailbox/arm_mhu.c | 3 +-- drivers/mailbox/scpi_protocol.c | 10 +++------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/drivers/mailbox/arm_mhu.c b/drivers/mailbox/arm_mhu.c index 5029af71780d..c6842784e410 100644 --- a/drivers/mailbox/arm_mhu.c +++ b/drivers/mailbox/arm_mhu.c @@ -246,8 +246,7 @@ static int mhu_probe(struct platform_device *pdev) ctlr->mbox_con.chans = l; ctlr->mbox_con.num_chans = CHANNEL_MAX; - ctlr->mbox_con.txdone_poll = true; - ctlr->mbox_con.txpoll_period = 10; + ctlr->mbox_con.txdone_irq = true; ctlr->mbox_con.ops = &mhu_ops; ctlr->mbox_con.dev = dev; diff --git a/drivers/mailbox/scpi_protocol.c b/drivers/mailbox/scpi_protocol.c index 195f86c6fd58..edcf47ea06ab 100644 --- a/drivers/mailbox/scpi_protocol.c +++ b/drivers/mailbox/scpi_protocol.c @@ -168,8 +168,7 @@ static int send_scpi_cmd(struct scpi_data_buf *scpi_buf, bool high_priority) cl.dev = the_scpi_device; cl.rx_callback = scpi_rx_callback; cl.tx_done = NULL; - cl.tx_block = true; - cl.tx_tout = 50; /* 50 msec */ + cl.tx_block = false; cl.knows_txdone = false; cl.chan_name = high_priority ? CHANNEL_HIGH_PRIORITY : @@ -185,11 +184,8 @@ static int send_scpi_cmd(struct scpi_data_buf *scpi_buf, bool high_priority) goto free_channel; } - if (!wait_for_completion_timeout(&scpi_buf->complete, - msecs_to_jiffies(50))) - status = SCPI_ERR_TIMEOUT; - else - status = *(u32 *)(data->rx_buf); /* read first word */ + wait_for_completion(&scpi_buf->complete); + status = *(u32 *)(data->rx_buf); /* read first word */ free_channel: mbox_free_channel(chan); -- cgit v1.2.3 From cc3bc06d756bf8146be6565f9ce36e2ceaf506b7 Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Fri, 22 Aug 2014 14:59:45 +0100 Subject: mailbox: mhu: Replace use of devm_request_and_ioremap() This deprecated API is removed in Linux 3.17 Signed-off-by: Jon Medhurst --- drivers/mailbox/arm_mhu.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/mailbox/arm_mhu.c b/drivers/mailbox/arm_mhu.c index c6842784e410..28fb4f01b413 100644 --- a/drivers/mailbox/arm_mhu.c +++ b/drivers/mailbox/arm_mhu.c @@ -217,10 +217,10 @@ static int mhu_probe(struct platform_device *pdev) return -ENXIO; } - ctlr->mbox_base = devm_request_and_ioremap(dev, res); - if (!ctlr->mbox_base) { + ctlr->mbox_base = devm_ioremap_resource(dev, res); + if (IS_ERR(ctlr->mbox_base)) { dev_err(dev, "failed to request or ioremap mailbox control\n"); - return -EADDRNOTAVAIL; + return PTR_ERR(ctlr->mbox_base); } res = platform_get_resource(pdev, IORESOURCE_MEM, 1); @@ -229,10 +229,10 @@ static int mhu_probe(struct platform_device *pdev) return -ENXIO; } - ctlr->payload_base = devm_request_and_ioremap(dev, res); - if (!ctlr->payload_base) { + ctlr->payload_base = devm_ioremap_resource(dev, res); + if (IS_ERR(ctlr->payload_base)) { dev_err(dev, "failed to request or ioremap mailbox payload\n"); - return -EADDRNOTAVAIL; + return PTR_ERR(ctlr->payload_base); } ctlr->dev = dev; -- cgit v1.2.3 From baf8617b8b21ef8f9b81c7093958b818cc5673b4 Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Fri, 22 Aug 2014 13:07:50 +0100 Subject: mailbox: mhu: Update for new version of mailbox patches Signed-off-by: Jon Medhurst --- drivers/mailbox/scpi_protocol.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/mailbox/scpi_protocol.c b/drivers/mailbox/scpi_protocol.c index edcf47ea06ab..49b500cd87ef 100644 --- a/drivers/mailbox/scpi_protocol.c +++ b/drivers/mailbox/scpi_protocol.c @@ -170,11 +170,8 @@ static int send_scpi_cmd(struct scpi_data_buf *scpi_buf, bool high_priority) 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); + chan = mbox_request_channel(&cl, high_priority); if (IS_ERR(chan)) return PTR_ERR(chan); -- cgit v1.2.3 From afd2f0492af1b958296968f59b515b1dba3f7661 Mon Sep 17 00:00:00 2001 From: Filipe Rinaldi Date: Fri, 20 Jun 2014 15:25:38 +0100 Subject: scpi: Add voltage on the DVFS Info command Newer versions of SCP added voltage as one of the parameters in the DVFS Info command. This patch reads the voltage which can be used by CPUFreq and Devfreq. Signed-off-by: Filipe Rinaldi Signed-off-by: Jon Medhurst --- drivers/clk/clk-scpi.c | 26 +++++++++++++------------- drivers/cpufreq/scpi-cpufreq.c | 21 +++++++++++---------- drivers/mailbox/scpi_protocol.c | 28 ++++++++++++++-------------- include/linux/scpi_protocol.h | 9 +++++++-- 4 files changed, 45 insertions(+), 39 deletions(-) diff --git a/drivers/clk/clk-scpi.c b/drivers/clk/clk-scpi.c index 2d707663542f..e4874bfeaae5 100644 --- a/drivers/clk/clk-scpi.c +++ b/drivers/clk/clk-scpi.c @@ -72,11 +72,11 @@ static struct clk_ops scpi_clk_ops = { 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; + struct scpi_opp_entry *opp = clk->opps->opp; u32 fmin = 0, fmax = ~0, ftmp; - for (idx = 0; idx < max_opp; idx++, freqs++) { - ftmp = *freqs; + for (idx = 0; idx < max_opp; idx++, opp++) { + ftmp = opp->freq_hz; if (ftmp >= (u32)rate) { if (ftmp <= fmax) fmax = ftmp; @@ -96,12 +96,12 @@ static unsigned long scpi_dvfs_recalc_rate(struct clk_hw *hw, { struct scpi_clk *clk = to_scpi_clk(hw); int idx = scpi_dvfs_get_idx(clk->id); - u32 *freqs = clk->opps->freqs; + struct scpi_opp_entry *opp = clk->opps->opp; if (idx < 0) return 0; else - return *(freqs + idx); + return opp[idx].freq_hz; } static long scpi_dvfs_round_rate(struct clk_hw *hw, unsigned long rate, @@ -114,10 +114,10 @@ static long scpi_dvfs_round_rate(struct clk_hw *hw, unsigned long 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; + struct scpi_opp_entry *opp = clk->opps->opp; - for (idx = 0; idx < max_opp; idx++, freqs++) - if (*freqs == (u32)rate) + for (idx = 0; idx < max_opp; idx++, opp++) + if (opp->freq_hz == (u32)rate) break; return (idx == max_opp) ? -EINVAL : idx; } @@ -145,7 +145,7 @@ scpi_dvfs_ops_init(struct device *dev, struct device_node *np, struct scpi_clk *sclk) { struct clk_init_data init; - struct scpi_opp *opp; + struct scpi_opp *opps; init.name = sclk->name; init.flags = CLK_IS_ROOT; @@ -153,11 +153,11 @@ scpi_dvfs_ops_init(struct device *dev, struct device_node *np, init.ops = &scpi_dvfs_ops; sclk->hw.init = &init; - opp = scpi_dvfs_get_opps(sclk->id); - if (IS_ERR(opp)) - return (struct clk *)opp; + opps = scpi_dvfs_get_opps(sclk->id); + if (IS_ERR(opps)) + return (struct clk *)opps; - sclk->opps = opp; + sclk->opps = opps; return devm_clk_register(dev, &sclk->hw); } diff --git a/drivers/cpufreq/scpi-cpufreq.c b/drivers/cpufreq/scpi-cpufreq.c index 60725199b9aa..65482c6e9c03 100644 --- a/drivers/cpufreq/scpi-cpufreq.c +++ b/drivers/cpufreq/scpi-cpufreq.c @@ -30,20 +30,21 @@ static int scpi_init_opp_table(struct device *cpu_dev) { u8 domain = topology_physical_package_id(cpu_dev->id); - struct scpi_opp *opp; + struct scpi_opp *opps; int idx, ret = 0, max_opp; - u32 *freqs; + struct scpi_opp_entry *opp; - opp = scpi_dvfs_get_opps(domain); - if (IS_ERR(opp)) - return PTR_ERR(opp); + opps = scpi_dvfs_get_opps(domain); + if (IS_ERR(opps)) + return PTR_ERR(opps); - 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 */); + opp = opps->opp; + max_opp = opps->count; + for (idx = 0; idx < max_opp; idx++, opp++) { + ret = dev_pm_opp_add(cpu_dev, opp->freq_hz, opp->volt_mv*1000); if (ret) { - dev_warn(cpu_dev, "failed to add opp %u\n", *freqs); + dev_warn(cpu_dev, "failed to add opp %uHz %umV\n", + opp->freq_hz, opp->volt_mv); return ret; } } diff --git a/drivers/mailbox/scpi_protocol.c b/drivers/mailbox/scpi_protocol.c index 49b500cd87ef..d3fcc8144ced 100644 --- a/drivers/mailbox/scpi_protocol.c +++ b/drivers/mailbox/scpi_protocol.c @@ -264,10 +264,10 @@ struct scpi_opp *scpi_dvfs_get_opps(u8 domain) struct __packed { u32 status; u32 header; - u32 freqs[MAX_DVFS_OPPS]; + struct scpi_opp_entry opp[MAX_DVFS_OPPS]; } buf; - struct scpi_opp *opp; - size_t freqs_sz; + struct scpi_opp *opps; + size_t opps_sz; int count, ret; if (domain >= MAX_DVFS_DOMAINS) @@ -282,25 +282,25 @@ struct scpi_opp *scpi_dvfs_get_opps(u8 domain) if (ret) return ERR_PTR(ret); - opp = kmalloc(sizeof(*opp), GFP_KERNEL); - if (!opp) + opps = kmalloc(sizeof(*opps), GFP_KERNEL); + if (!opps) return ERR_PTR(-ENOMEM); count = DVFS_OPP_COUNT(buf.header); - freqs_sz = count * sizeof(*(opp->freqs)); + opps_sz = count * sizeof(*(opps->opp)); - opp->count = count; - opp->latency = DVFS_LATENCY(buf.header); - opp->freqs = kmalloc(freqs_sz, GFP_KERNEL); - if (!opp->freqs) { - kfree(opp); + opps->count = count; + opps->latency = DVFS_LATENCY(buf.header); + opps->opp = kmalloc(opps_sz, GFP_KERNEL); + if (!opps->opp) { + kfree(opps); return ERR_PTR(-ENOMEM); } - memcpy(opp->freqs, &buf.freqs[0], freqs_sz); - scpi_opps[domain] = opp; + memcpy(opps->opp, &buf.opp[0], opps_sz); + scpi_opps[domain] = opps; - return opp; + return opps; } EXPORT_SYMBOL_GPL(scpi_dvfs_get_opps); diff --git a/include/linux/scpi_protocol.h b/include/linux/scpi_protocol.h index 66e5eb3710ab..0fdc969d8b9e 100644 --- a/include/linux/scpi_protocol.h +++ b/include/linux/scpi_protocol.h @@ -17,11 +17,16 @@ */ #include +struct scpi_opp_entry { + u32 freq_hz; + u32 volt_mv; +} __packed; + struct scpi_opp { - u32 *freqs; + struct scpi_opp_entry *opp; u32 latency; /* in usecs */ int count; -}; +} __packed; unsigned long scpi_clk_get_val(u16 clk_id); int scpi_clk_set_val(u16 clk_id, unsigned long rate); -- cgit v1.2.3 From aea3317e3e5deef3d948335a1e8d72cfcb942deb Mon Sep 17 00:00:00 2001 From: Filipe Rinaldi Date: Fri, 20 Jun 2014 11:38:35 +0100 Subject: scpi: Increase the maximum number of DVFS OPPs Signed-off-by: Filipe Rinaldi Signed-off-by: Jon Medhurst --- drivers/mailbox/scpi_protocol.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mailbox/scpi_protocol.c b/drivers/mailbox/scpi_protocol.c index d3fcc8144ced..16daf3c16d75 100644 --- a/drivers/mailbox/scpi_protocol.c +++ b/drivers/mailbox/scpi_protocol.c @@ -39,7 +39,7 @@ (((txsz) & CMD_DATA_SIZE_MASK) << CMD_DATA_SIZE_SHIFT)) #define MAX_DVFS_DOMAINS 3 -#define MAX_DVFS_OPPS 4 +#define MAX_DVFS_OPPS 8 #define DVFS_LATENCY(hdr) ((hdr) >> 16) #define DVFS_OPP_COUNT(hdr) (((hdr) >> 8) & 0xff) -- cgit v1.2.3