mailbox: add support for System Control and Power Interface(SCPI) protocol
authorSudeep Holla <sudeep.holla@arm.com>
Tue, 15 Apr 2014 15:09:42 +0000 (16:09 +0100)
committerSudeep Holla <sudeep.holla@arm.com>
Mon, 2 Jun 2014 17:20:21 +0000 (18:20 +0100)
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 <sudeep.holla@arm.com>
drivers/mailbox/Kconfig
drivers/mailbox/Makefile
drivers/mailbox/scpi_protocol.c [new file with mode: 0644]
include/linux/scpi_protocol.h [new file with mode: 0644]

index b772e2a..c146c9e 100644 (file)
@@ -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
index 99264d9..7d06435 100644 (file)
@@ -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.o
 
 obj-$(CONFIG_OMAP_MBOX)                += omap-mailbox.o
diff --git a/drivers/mailbox/scpi_protocol.c b/drivers/mailbox/scpi_protocol.c
new file mode 100644 (file)
index 0000000..7a1ff2d
--- /dev/null
@@ -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 <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.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 (file)
index 0000000..66e5eb3
--- /dev/null
@@ -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);