Hi all,
Let me begin admitting that we are introducing yet another protocol to
achieve same things as many existing protocols like ARM SCPI, TI SCI,
QCOM RPM, Nvidia Tegra BPMP, and so on.
All I can say is that this new ARM System Control and Management
Interface(SCMI) is more flexible and easily extensible than any of the
existing ones. Many vendors were involved in the making of this formal
specification and is now officially published[1].
There is a strong trend in the industry to provide micro-controllers in
systems to abstract various power, or other system management tasks.
These controllers usually have similar interfaces, both in terms of the
functions that are provided by them, and in terms of how requests are
communicated to them.
This specification is to standardise and avoid (any further)
fragmentation in the design of such interface by various vendors.
This patch set is intended to get feedback on the design and structure
of the code. This is not complete and not fully tested due to
non-availability of firmware with full feature set at this time.
It currently doesn't support notification, asynchronous/delayed response,
perf/power statistics region and sensor register region to name a few.
I have borrowed some of the ideas of message allocation/management from
TI SCI.
I had previously posted RFC[2].
Changes:
v1->v2:
- Additional support for polling based DVFS and per protocol
channels
- Dependent drivers(clock, hwmon, cpufreq and power domains)
- Various other review comments and issued found during testing
addressed
- Explicit binding for method dropped as even SMC based method
are adviertised as mailbox
RFC->v1:
- Add generic mailbox binding for shared memory(Rob H)
- Dropped compatibles per protocol(Suggested by Matt S)
- Dropped lot of unnecessary pointer casting(Arnd B)
- Dropped packing of structures(Arnd B)
- Few other changes/additions based initial testing with firmware
providing SCMI interface to OSPM
--
Regards,
Sudeep
[1] http://infocenter.arm.com/help/topic/com.arm.doc.den0056a/index.html
[2] https://marc.info/?l=linux-kernel&m=149685193627620&w=2
[3] https://marc.info/?l=linux-arm-kernel&m=149849482623492&w=2
Sudeep Holla (18):
dt-bindings: mailbox: add support for mailbox client shared memory
dt-bindings: arm: add support for ARM System Control and Management
Interface(SCMI) protocol
firmware: arm_scmi: add basic driver infrastructure for SCMI
firmware: arm_scmi: add common infrastructure and support for base
protocol
firmware: arm_scmi: add initial support for performance protocol
firmware: arm_scmi: add initial support for clock protocol
firmware: arm_scmi: add initial support for power protocol
firmware: arm_scmi: add initial support for sensor protocol
firmware: arm_scmi: probe and initialise all the supported protocols
firmware: arm_scmi: add support for polling based SCMI transfers
firmware: arm_scmi: add option for polling based performance domain
operations
firmware: arm_scmi: refactor in preparation to support per-protocol
channels
firmware: arm_scmi: add per-protocol channels support using idr
objects
firmware: arm_scmi: add device power domain support using genpd
clk: add support for clocks provided by SCMI
hwmon: add support for sensors exported via ARM SCMI
cpufreq: add support for CPU DVFS based on SCMI message protocol
cpufreq: scmi: add support for fast frequency switching
Documentation/devicetree/bindings/arm/arm,scmi.txt | 174 ++++
.../devicetree/bindings/mailbox/mailbox.txt | 28 +
drivers/clk/Kconfig | 10 +
drivers/clk/Makefile | 1 +
drivers/clk/clk-scmi.c | 216 +++++
drivers/cpufreq/Kconfig.arm | 11 +
drivers/cpufreq/Makefile | 1 +
drivers/cpufreq/scmi-cpufreq.c | 284 ++++++
drivers/firmware/Kconfig | 34 +
drivers/firmware/Makefile | 1 +
drivers/firmware/arm_scmi/Makefile | 3 +
drivers/firmware/arm_scmi/base.c | 293 ++++++
drivers/firmware/arm_scmi/clock.c | 339 +++++++
drivers/firmware/arm_scmi/common.h | 126 +++
drivers/firmware/arm_scmi/driver.c | 992 +++++++++++++++++++++
drivers/firmware/arm_scmi/perf.c | 514 +++++++++++
drivers/firmware/arm_scmi/power.c | 242 +++++
drivers/firmware/arm_scmi/scmi_pm_domain.c | 134 +++
drivers/firmware/arm_scmi/sensors.c | 287 ++++++
drivers/hwmon/Kconfig | 12 +
drivers/hwmon/Makefile | 1 +
drivers/hwmon/scmi-hwmon.c | 261 ++++++
include/linux/scmi_protocol.h | 215 +++++
23 files changed, 4179 insertions(+)
create mode 100644 Documentation/devicetree/bindings/arm/arm,scmi.txt
create mode 100644 drivers/clk/clk-scmi.c
create mode 100644 drivers/cpufreq/scmi-cpufreq.c
create mode 100644 drivers/firmware/arm_scmi/Makefile
create mode 100644 drivers/firmware/arm_scmi/base.c
create mode 100644 drivers/firmware/arm_scmi/clock.c
create mode 100644 drivers/firmware/arm_scmi/common.h
create mode 100644 drivers/firmware/arm_scmi/driver.c
create mode 100644 drivers/firmware/arm_scmi/perf.c
create mode 100644 drivers/firmware/arm_scmi/power.c
create mode 100644 drivers/firmware/arm_scmi/scmi_pm_domain.c
create mode 100644 drivers/firmware/arm_scmi/sensors.c
create mode 100644 drivers/hwmon/scmi-hwmon.c
create mode 100644 include/linux/scmi_protocol.h
--
2.7.4
Many users of the mailbox controllers depend on the shared memory
between the two end points to exchange the main data while using simple
doorbell mechanism to alert the end points of the presence of a message.
This patch defines device tree bindings to represent such shared memory
in a generic way.
Cc: Rob Herring <[email protected]>
Cc: Mark Rutland <[email protected]>
Acked-by: Rob Herring <[email protected]>
Signed-off-by: Sudeep Holla <[email protected]>
---
.../devicetree/bindings/mailbox/mailbox.txt | 28 ++++++++++++++++++++++
1 file changed, 28 insertions(+)
diff --git a/Documentation/devicetree/bindings/mailbox/mailbox.txt b/Documentation/devicetree/bindings/mailbox/mailbox.txt
index be05b9746c69..af8ecee2ac68 100644
--- a/Documentation/devicetree/bindings/mailbox/mailbox.txt
+++ b/Documentation/devicetree/bindings/mailbox/mailbox.txt
@@ -23,6 +23,11 @@ assign appropriate mailbox channel to client drivers.
Optional property:
- mbox-names: List of identifier strings for each mailbox channel.
+- shmem : List of phandle pointing to the shared memory(SHM) area between the
+ users of these mailboxes for IPC, one for each mailbox. This shared
+ memory can be part of any memory reserved for the purpose of this
+ communication between the mailbox client and the remote.
+
Example:
pwr_cntrl: power {
@@ -30,3 +35,26 @@ assign appropriate mailbox channel to client drivers.
mbox-names = "pwr-ctrl", "rpc";
mboxes = <&mailbox 0 &mailbox 1>;
};
+
+Example with shared memory(shmem):
+
+ sram: sram@50000000 {
+ compatible = "mmio-sram";
+ reg = <0x50000000 0x10000>;
+
+ #address-cells = <1>;
+ #size-cells = <1>;
+ ranges = <0 0x50000000 0x10000>;
+
+ cl_shmem: shmem@0 {
+ compatible = "client-shmem";
+ reg = <0x0 0x200>;
+ };
+ };
+
+ client@2e000000 {
+ ...
+ mboxes = <&mailbox 0>;
+ shmem = <&cl_shmem>;
+ ..
+ };
--
2.7.4
The SCMI is intended to allow OSPM to manage various functions that are
provided by the hardware platform it is running on, including power and
performance functions. SCMI provides two levels of abstraction, protocols
and transports. Protocols define individual groups of system control and
management messages. A protocol specification describes the messages
that it supports. Transports describe the method by which protocol
messages are communicated between agents and the platform.
This patch adds basic infrastructure to manage the message allocation,
initialisation, packing/unpacking and shared memory management.
Cc: Arnd Bergmann <[email protected]>
Signed-off-by: Sudeep Holla <[email protected]>
---
drivers/firmware/Kconfig | 21 +
drivers/firmware/Makefile | 1 +
drivers/firmware/arm_scmi/Makefile | 2 +
drivers/firmware/arm_scmi/common.h | 74 ++++
drivers/firmware/arm_scmi/driver.c | 774 +++++++++++++++++++++++++++++++++++++
include/linux/scmi_protocol.h | 48 +++
6 files changed, 920 insertions(+)
create mode 100644 drivers/firmware/arm_scmi/Makefile
create mode 100644 drivers/firmware/arm_scmi/common.h
create mode 100644 drivers/firmware/arm_scmi/driver.c
create mode 100644 include/linux/scmi_protocol.h
diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig
index 6e4ed5a9c6fd..c3d1a12763ce 100644
--- a/drivers/firmware/Kconfig
+++ b/drivers/firmware/Kconfig
@@ -19,6 +19,27 @@ config ARM_PSCI_CHECKER
on and off through hotplug, so for now torture tests and PSCI checker
are mutually exclusive.
+config ARM_SCMI_PROTOCOL
+ tristate "ARM System Control and Management Interface (SCMI) Message Protocol"
+ depends on ARM || ARM64 || COMPILE_TEST
+ depends on MAILBOX
+ help
+ ARM System Control and Management Interface (SCMI) protocol is a
+ set of operating system-independent software interfaces that are
+ used in system management. SCMI is extensible and currently provides
+ interfaces for: Discovery and self-description of the interfaces
+ it supports, Power domain management which is the ability to place
+ a given device or domain into the various power-saving states that
+ it supports, Performance management which is the ability to control
+ the performance of a domain that is composed of compute engines
+ such as application processors and other accelerators, Clock
+ management which is the ability to set and inquire rates on platform
+ managed clocks and Sensor management which is the ability to read
+ sensor data, and be notified of sensor value.
+
+ This protocol library provides interface for all the client drivers
+ making use of the features offered by the SCMI.
+
config ARM_SCPI_PROTOCOL
tristate "ARM System Control and Power Interface (SCPI) Message Protocol"
depends on ARM || ARM64 || COMPILE_TEST
diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile
index a37f12e8d137..91d3ff62c653 100644
--- a/drivers/firmware/Makefile
+++ b/drivers/firmware/Makefile
@@ -23,6 +23,7 @@ obj-$(CONFIG_QCOM_SCM_32) += qcom_scm-32.o
CFLAGS_qcom_scm-32.o :=$(call as-instr,.arch armv7-a\n.arch_extension sec,-DREQUIRES_SEC=1) -march=armv7-a
obj-$(CONFIG_TI_SCI_PROTOCOL) += ti_sci.o
+obj-$(CONFIG_ARM_SCMI_PROTOCOL) += arm_scmi/
obj-y += broadcom/
obj-y += meson/
obj-$(CONFIG_GOOGLE_FIRMWARE) += google/
diff --git a/drivers/firmware/arm_scmi/Makefile b/drivers/firmware/arm_scmi/Makefile
new file mode 100644
index 000000000000..58e94c95e523
--- /dev/null
+++ b/drivers/firmware/arm_scmi/Makefile
@@ -0,0 +1,2 @@
+obj-$(CONFIG_ARM_SCMI_PROTOCOL) = arm_scmi.o
+arm_scmi-y = driver.o
diff --git a/drivers/firmware/arm_scmi/common.h b/drivers/firmware/arm_scmi/common.h
new file mode 100644
index 000000000000..a6829afc17e3
--- /dev/null
+++ b/drivers/firmware/arm_scmi/common.h
@@ -0,0 +1,74 @@
+/*
+ * System Control and Management Interface (SCMI) Message Protocol
+ * driver common header file containing some definitions, structures
+ * and function prototypes used in all the different SCMI protocols.
+ *
+ * Copyright (C) 2017 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/completion.h>
+#include <linux/scmi_protocol.h>
+#include <linux/types.h>
+
+/**
+ * struct scmi_msg_hdr - Message(Tx/Rx) header
+ *
+ * @id: The identifier of the command being sent
+ * @protocol_id: The identifier of the protocol used to send @id command
+ * @seq: The token to identify the message. when a message/command returns,
+ * the platform returns the whole message header unmodified including
+ * the token.
+ */
+struct scmi_msg_hdr {
+ u8 id;
+ u8 protocol_id;
+ u16 seq;
+ u32 status;
+ bool poll_completion;
+};
+
+/**
+ * struct scmi_msg - Message(Tx/Rx) structure
+ *
+ * @buf: Buffer pointer
+ * @len: Length of data in the Buffer
+ */
+struct scmi_msg {
+ void *buf;
+ size_t len;
+};
+
+/**
+ * struct scmi_xfer - Structure representing a message flow
+ *
+ * @hdr: Transmit message header
+ * @tx: Transmit message
+ * @rx: Receive message, the buffer should be pre-allocated to store
+ * message. If request-ACK protocol is used, we can reuse the same
+ * buffer for the rx path as we use for the tx path.
+ * @done: completion event
+ */
+
+struct scmi_xfer {
+ struct scmi_msg_hdr hdr;
+ struct scmi_msg tx;
+ struct scmi_msg rx;
+ struct completion done;
+};
+
+void scmi_one_xfer_put(const struct scmi_handle *h, struct scmi_xfer *xfer);
+int scmi_do_xfer(const struct scmi_handle *h, struct scmi_xfer *xfer);
+int scmi_one_xfer_init(const struct scmi_handle *h, u8 msg_id, u8 prot_id,
+ size_t tx_size, size_t rx_size, struct scmi_xfer **p);
diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c
new file mode 100644
index 000000000000..139d6980f270
--- /dev/null
+++ b/drivers/firmware/arm_scmi/driver.c
@@ -0,0 +1,774 @@
+/*
+ * System Control and Management Interface (SCMI) Message Protocol driver
+ *
+ * SCMI Message Protocol is used between the System Control Processor(SCP)
+ * and the Application Processors(AP). The Message Handling Unit(MHU)
+ * provides a mechanism for inter-processor communication between SCP's
+ * Cortex M3 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.
+ *
+ * Copyright (C) 2017 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/bitmap.h>
+#include <linux/export.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/mailbox_client.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/semaphore.h>
+#include <linux/slab.h>
+
+#include "common.h"
+
+#define MSG_ID_SHIFT 0
+#define MSG_ID_MASK 0xff
+#define MSG_TYPE_SHIFT 8
+#define MSG_TYPE_MASK 0x3
+#define MSG_PROTOCOL_ID_SHIFT 10
+#define MSG_PROTOCOL_ID_MASK 0xff
+#define MSG_TOKEN_ID_SHIFT 18
+#define MSG_TOKEN_ID_MASK 0x3ff
+#define MSG_XTRACT_TOKEN(header) \
+ (((header) >> MSG_TOKEN_ID_SHIFT) & MSG_TOKEN_ID_MASK)
+
+enum scmi_error_codes {
+ SCMI_SUCCESS = 0, /* Success */
+ SCMI_ERR_SUPPORT = -1, /* Not supported */
+ SCMI_ERR_PARAMS = -2, /* Invalid Parameters */
+ SCMI_ERR_ACCESS = -3, /* Invalid access/permission denied */
+ SCMI_ERR_ENTRY = -4, /* Not found */
+ SCMI_ERR_RANGE = -5, /* Value out of range */
+ SCMI_ERR_BUSY = -6, /* Device busy */
+ SCMI_ERR_COMMS = -7, /* Communication Error */
+ SCMI_ERR_GENERIC = -8, /* Generic Error */
+ SCMI_ERR_HARDWARE = -9, /* Hardware Error */
+ SCMI_ERR_PROTOCOL = -10,/* Protocol Error */
+ SCMI_ERR_MAX
+};
+
+/* List of all SCMI devices active in system */
+static LIST_HEAD(scmi_list);
+/* Protection for the entire list */
+static DEFINE_MUTEX(scmi_list_mutex);
+
+/**
+ * struct scmi_xfers_info - Structure to manage transfer information
+ *
+ * @sem_xfer_count: Counting Semaphore for managing max simultaneous
+ * Messages.
+ * @xfer_block: Preallocated Message array
+ * @xfer_alloc_table: Bitmap table for allocated messages.
+ * Index of this bitmap table is also used for message
+ * sequence identifier.
+ * @xfer_lock: Protection for message allocation
+ */
+struct scmi_xfers_info {
+ struct semaphore sem_xfer_count;
+ struct scmi_xfer *xfer_block;
+ unsigned long *xfer_alloc_table;
+ /* protect transfer allocation */
+ spinlock_t xfer_lock;
+};
+
+/**
+ * struct scmi_desc - Description of SoC integration
+ *
+ * @max_rx_timeout_ms: Timeout for communication with SoC (in Milliseconds)
+ * @max_msg: Maximum number of messages that can be pending
+ * simultaneously in the system
+ * @max_msg_size: Maximum size of data per message that can be handled.
+ */
+struct scmi_desc {
+ int max_rx_timeout_ms;
+ int max_msg;
+ int max_msg_size;
+};
+
+/**
+ * struct scmi_info - Structure representing a SCMI instance
+ *
+ * @dev: Device pointer
+ * @desc: SoC description for this instance
+ * @handle: Instance of SCMI handle to send to clients
+ * @cl: Mailbox Client
+ * @tx_chan: Transmit mailbox channel
+ * @rx_chan: Receive mailbox channel
+ * @tx_payload: Transmit mailbox channel payload area
+ * @rx_payload: Receive mailbox channel payload area
+ * @minfo: Message info
+ * @node: list head
+ * @users: Number of users of this instance
+ */
+struct scmi_info {
+ struct device *dev;
+ const struct scmi_desc *desc;
+ struct scmi_handle handle;
+ struct mbox_client cl;
+ struct mbox_chan *tx_chan;
+ struct mbox_chan *rx_chan;
+ void __iomem *tx_payload;
+ void __iomem *rx_payload;
+ struct scmi_xfers_info minfo;
+ struct list_head node;
+ int users;
+};
+
+#define client_to_scmi_info(c) container_of(c, struct scmi_info, cl)
+#define handle_to_scmi_info(h) container_of(h, struct scmi_info, handle)
+
+/*
+ * The SCP firmware providing SCM interface to OSPM and other agents must
+ * execute only in little-endian mode as per SCMI specification, so any buffers
+ * shared through SCMI should have their contents converted to little-endian
+ */
+struct scmi_shared_mem {
+ __le32 reserved;
+ __le32 channel_status;
+#define SCMI_SHMEM_CHAN_STAT_CHANNEL_ERROR BIT(1)
+#define SCMI_SHMEM_CHAN_STAT_CHANNEL_FREE BIT(0)
+ __le32 reserved1[2];
+ __le32 flags;
+#define SCMI_SHMEM_FLAG_INTR_ENABLED BIT(0)
+ __le32 length;
+ __le32 msg_header;
+ u8 msg_payload[0];
+};
+
+static int scmi_linux_errmap[] = {
+ /* better than switch case as long as return value is continuous */
+ 0, /* SCMI_SUCCESS */
+ -EOPNOTSUPP, /* SCMI_ERR_SUPPORT */
+ -EINVAL, /* SCMI_ERR_PARAM */
+ -EACCES, /* SCMI_ERR_ACCESS */
+ -ENOENT, /* SCMI_ERR_ENTRY */
+ -ERANGE, /* SCMI_ERR_RANGE */
+ -EBUSY, /* SCMI_ERR_BUSY */
+ -ECOMM, /* SCMI_ERR_COMMS */
+ -EIO, /* SCMI_ERR_GENERIC */
+ -EREMOTEIO, /* SCMI_ERR_HARDWARE */
+ -EPROTO, /* SCMI_ERR_PROTOCOL */
+};
+
+static inline int scmi_to_linux_errno(int errno)
+{
+ if (errno < SCMI_SUCCESS && errno > SCMI_ERR_MAX)
+ return scmi_linux_errmap[-errno];
+ return -EIO;
+}
+
+/**
+ * scmi_dump_header_dbg() - Helper to dump a message header.
+ *
+ * @dev: Device pointer corresponding to the SCMI entity
+ * @hdr: pointer to header.
+ */
+static inline void scmi_dump_header_dbg(struct device *dev,
+ struct scmi_msg_hdr *hdr)
+{
+ dev_dbg(dev, "Command ID: %x Sequence ID: %x Protocol: %x\n",
+ hdr->id, hdr->seq, hdr->protocol_id);
+}
+
+static void scmi_fetch_response(struct scmi_xfer *xfer,
+ struct scmi_shared_mem *mem)
+{
+ xfer->hdr.status = le32_to_cpu(*(__le32 *)mem->msg_payload);
+ /* Skip the length of header and statues in payload area i.e 8 bytes*/
+ xfer->rx.len = min_t(size_t, xfer->rx.len, mem->length - 8);
+
+ /* Take a copy to the rx buffer.. */
+ memcpy_fromio(xfer->rx.buf, mem->msg_payload + 4, xfer->rx.len);
+}
+
+/**
+ * scmi_rx_callback() - mailbox client callback for receive messages
+ *
+ * @cl: client pointer
+ * @m: mailbox message
+ *
+ * Processes one received message to appropriate transfer information and
+ * signals completion of the transfer.
+ *
+ * NOTE: This function will be invoked in IRQ context, hence should be
+ * as optimal as possible.
+ */
+static void scmi_rx_callback(struct mbox_client *cl, void *m)
+{
+ u16 xfer_id;
+ struct scmi_xfer *xfer;
+ struct scmi_info *info = client_to_scmi_info(cl);
+ struct scmi_xfers_info *minfo = &info->minfo;
+ struct device *dev = info->dev;
+ struct scmi_shared_mem *mem = info->tx_payload;
+
+ xfer_id = MSG_XTRACT_TOKEN(mem->msg_header);
+
+ /*
+ * Are we even expecting this?
+ */
+ if (!test_bit(xfer_id, minfo->xfer_alloc_table)) {
+ dev_err(dev, "message for %d is not expected!\n", xfer_id);
+ return;
+ }
+
+ xfer = &minfo->xfer_block[xfer_id];
+
+ scmi_dump_header_dbg(dev, &xfer->hdr);
+ /* Is the message of valid length? */
+ if (xfer->rx.len > info->desc->max_msg_size) {
+ dev_err(dev, "unable to handle %zu xfer(max %d)\n",
+ xfer->rx.len, info->desc->max_msg_size);
+ return;
+ }
+
+ scmi_fetch_response(xfer, mem);
+ complete(&xfer->done);
+}
+
+/**
+ * pack_scmi_header() - packs and returns 32-bit header
+ *
+ * @hdr: pointer to header containing all the information on message id,
+ * protocol id and sequence id.
+ */
+static inline u32 pack_scmi_header(struct scmi_msg_hdr *hdr)
+{
+ return ((hdr->id & MSG_ID_MASK) << MSG_ID_SHIFT) |
+ ((hdr->seq & MSG_TOKEN_ID_MASK) << MSG_TOKEN_ID_SHIFT) |
+ ((hdr->protocol_id & MSG_PROTOCOL_ID_MASK) << MSG_PROTOCOL_ID_SHIFT);
+}
+
+/**
+ * scmi_tx_prepare() - mailbox client callback to prepare for the transfer
+ *
+ * @cl: client pointer
+ * @m: mailbox message
+ *
+ * This function prepares the shared memory which contains the header and the
+ * payload.
+ */
+static void scmi_tx_prepare(struct mbox_client *cl, void *m)
+{
+ struct scmi_xfer *t = m;
+ struct scmi_info *info = client_to_scmi_info(cl);
+ struct scmi_shared_mem *mem = info->tx_payload;
+
+ mem->channel_status = 0x0; /* Mark channel busy + clear error */
+ mem->flags = t->hdr.poll_completion ? 0 : SCMI_SHMEM_FLAG_INTR_ENABLED;
+ mem->length = sizeof(mem->msg_header) + t->tx.len;
+ mem->msg_header = cpu_to_le32(pack_scmi_header(&t->hdr));
+ if (t->tx.buf)
+ memcpy_toio(mem->msg_payload, t->tx.buf, t->tx.len);
+}
+
+/**
+ * scmi_one_xfer_get() - Allocate one message
+ *
+ * @handle: SCMI entity handle
+ *
+ * Helper function which is used by various command functions that are
+ * exposed to clients of this driver for allocating a message traffic event.
+ *
+ * This function can sleep depending on pending requests already in the system
+ * for the SCMI entity. Further, this also holds a spinlock to maintain
+ * integrity of internal data structures.
+ *
+ * Return: 0 if all went fine, else corresponding error.
+ */
+static struct scmi_xfer *scmi_one_xfer_get(const struct scmi_handle *handle)
+{
+ u16 xfer_id;
+ int ret, timeout;
+ struct scmi_xfer *xfer;
+ unsigned long flags, bit_pos;
+ struct scmi_info *info = handle_to_scmi_info(handle);
+ struct scmi_xfers_info *minfo = &info->minfo;
+
+ /*
+ * Ensure we have only controlled number of pending messages.
+ * Ideally, we might just have to wait a single message, be
+ * conservative and wait 5 times that..
+ */
+ timeout = msecs_to_jiffies(info->desc->max_rx_timeout_ms) * 5;
+ ret = down_timeout(&minfo->sem_xfer_count, timeout);
+ if (ret < 0)
+ return ERR_PTR(ret);
+
+ /* Keep the locked section as small as possible */
+ spin_lock_irqsave(&minfo->xfer_lock, flags);
+ bit_pos = find_first_zero_bit(minfo->xfer_alloc_table,
+ info->desc->max_msg);
+ if (bit_pos == info->desc->max_msg) {
+ spin_unlock_irqrestore(&minfo->xfer_lock, flags);
+ return ERR_PTR(-ENOMEM);
+ }
+ set_bit(bit_pos, minfo->xfer_alloc_table);
+ spin_unlock_irqrestore(&minfo->xfer_lock, flags);
+
+ xfer_id = bit_pos;
+
+ xfer = &minfo->xfer_block[xfer_id];
+ xfer->hdr.seq = xfer_id;
+ reinit_completion(&xfer->done);
+
+ return xfer;
+}
+
+/**
+ * scmi_one_xfer_put() - Release a message
+ *
+ * @minfo: transfer info pointer
+ * @xfer: message that was reserved by scmi_one_xfer_get
+ *
+ * This holds a spinlock to maintain integrity of internal data structures.
+ */
+void scmi_one_xfer_put(const struct scmi_handle *handle, struct scmi_xfer *xfer)
+{
+ unsigned long flags;
+ struct scmi_info *info = handle_to_scmi_info(handle);
+ struct scmi_xfers_info *minfo = &info->minfo;
+
+ /*
+ * Keep the locked section as small as possible
+ * NOTE: we might escape with smp_mb and no lock here..
+ * but just be conservative and symmetric.
+ */
+ spin_lock_irqsave(&minfo->xfer_lock, flags);
+ clear_bit(xfer->hdr.seq, minfo->xfer_alloc_table);
+ spin_unlock_irqrestore(&minfo->xfer_lock, flags);
+
+ /* Increment the count for the next user to get through */
+ up(&minfo->sem_xfer_count);
+}
+
+/**
+ * scmi_do_xfer() - Do one transfer
+ *
+ * @info: Pointer to SCMI entity information
+ * @xfer: Transfer to initiate and wait for response
+ *
+ * Return: -ETIMEDOUT in case of no response, if transmit error,
+ * return corresponding error, else if all goes well,
+ * return 0.
+ */
+int scmi_do_xfer(const struct scmi_handle *handle, struct scmi_xfer *xfer)
+{
+ int ret;
+ int timeout;
+ struct scmi_info *info = handle_to_scmi_info(handle);
+ struct device *dev = info->dev;
+
+ ret = mbox_send_message(info->tx_chan, xfer);
+ if (ret < 0) {
+ dev_dbg(dev, "mbox send fail %d\n", ret);
+ return ret;
+ }
+
+ /* mbox_send_message returns non-negative value on success, so reset */
+ ret = 0;
+
+ /* And we wait for the response. */
+ timeout = msecs_to_jiffies(info->desc->max_rx_timeout_ms);
+ if (!wait_for_completion_timeout(&xfer->done, timeout)) {
+ dev_err(dev, "mbox timed out in resp(caller: %pF)\n",
+ (void *)_RET_IP_);
+ ret = -ETIMEDOUT;
+ } else if (xfer->hdr.status) {
+ ret = scmi_to_linux_errno(xfer->hdr.status);
+ }
+ /*
+ * NOTE: we might prefer not to need the mailbox ticker to manage the
+ * transfer queueing since the protocol layer queues things by itself.
+ * Unfortunately, we have to kick the mailbox framework after we have
+ * received our message.
+ */
+ mbox_client_txdone(info->tx_chan, ret);
+
+ return ret;
+}
+
+/**
+ * scmi_one_xfer_init() - Allocate and initialise one message
+ *
+ * @handle: SCMI entity handle
+ * @msg_id: Message identifier
+ * @msg_prot_id: Protocol identifier for the message
+ * @tx_size: transmit message size
+ * @rx_size: receive message size
+ * @p: pointer to the allocated and initialised message
+ *
+ * This function allocates the message using @scmi_one_xfer_get and
+ * initialise the header.
+ *
+ * Return: 0 if all went fine with @p pointing to message, else
+ * corresponding error.
+ */
+int scmi_one_xfer_init(const struct scmi_handle *handle, u8 msg_id, u8 prot_id,
+ size_t tx_size, size_t rx_size, struct scmi_xfer **p)
+{
+ int ret;
+ struct scmi_xfer *xfer;
+ struct scmi_info *info = handle_to_scmi_info(handle);
+ struct device *dev = info->dev;
+
+ /* Ensure we have sane transfer sizes */
+ if (rx_size > info->desc->max_msg_size ||
+ tx_size > info->desc->max_msg_size)
+ return -ERANGE;
+
+ xfer = scmi_one_xfer_get(handle);
+ if (IS_ERR(xfer)) {
+ ret = PTR_ERR(xfer);
+ dev_err(dev, "failed to get free message slot(%d)\n", ret);
+ return ret;
+ }
+
+ xfer->tx.len = tx_size;
+ xfer->rx.len = rx_size ? : info->desc->max_msg_size;
+ xfer->hdr.id = msg_id;
+ xfer->hdr.protocol_id = prot_id;
+ xfer->hdr.poll_completion = false;
+
+ *p = xfer;
+ return 0;
+}
+
+/**
+ * scmi_handle_get() - Get the SCMI handle for a device
+ *
+ * @dev: pointer to device for which we want SCMI handle
+ *
+ * NOTE: The function does not track individual clients of the framework
+ * and is expected to be maintained by caller of SCMI protocol library.
+ * scmi_handle_put must be balanced with successful scmi_handle_get
+ *
+ * Return: pointer to handle if successful, else:
+ * -EPROBE_DEFER if the instance is not ready
+ * -ENODEV if the required node handler is missing
+ * -EINVAL if invalid conditions are encountered.
+ */
+const struct scmi_handle *scmi_handle_get(struct device *dev)
+{
+ struct list_head *p;
+ struct scmi_info *info;
+ struct scmi_handle *handle = NULL;
+
+ if (!dev || !dev->parent) {
+ pr_err("missing device or parent pointer\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ mutex_lock(&scmi_list_mutex);
+ list_for_each(p, &scmi_list) {
+ info = list_entry(p, struct scmi_info, node);
+ if (dev->parent == info->dev) {
+ handle = &info->handle;
+ info->users++;
+ break;
+ }
+ }
+ mutex_unlock(&scmi_list_mutex);
+
+ if (!handle)
+ return ERR_PTR(-EPROBE_DEFER);
+
+ return handle;
+}
+EXPORT_SYMBOL_GPL(scmi_handle_get);
+
+/**
+ * scmi_handle_put() - Release the handle acquired by scmi_handle_get
+ *
+ * @handle: handle acquired by scmi_handle_get
+ *
+ * NOTE: The function does not track individual clients of the framework
+ * and is expected to be maintained by caller of SCMI protocol library.
+ * scmi_handle_put must be balanced with successful scmi_handle_get
+ *
+ * Return: 0 is successfully released
+ * if an error pointer was passed, it returns the error value back,
+ * if null was passed, it returns -EINVAL;
+ */
+int scmi_handle_put(const struct scmi_handle *handle)
+{
+ struct scmi_info *info;
+
+ if (IS_ERR(handle))
+ return PTR_ERR(handle);
+ if (!handle)
+ return -EINVAL;
+
+ info = handle_to_scmi_info(handle);
+ mutex_lock(&scmi_list_mutex);
+ if (!WARN_ON(!info->users))
+ info->users--;
+ mutex_unlock(&scmi_list_mutex);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(scmi_handle_put);
+
+static void devm_scmi_release(struct device *dev, void *res)
+{
+ const struct scmi_handle **ptr = res;
+ const struct scmi_handle *handle = *ptr;
+ int ret;
+
+ ret = scmi_handle_put(handle);
+ if (ret)
+ dev_err(dev, "failed to put handle %d\n", ret);
+}
+
+/**
+ * devm_scmi_handle_get() - Managed get handle
+ * @dev: device for which we want SCMI handle for.
+ *
+ * NOTE: This releases the handle once the device resources are
+ * no longer needed. MUST NOT BE released with scmi_handle_put.
+ * The function does not track individual clients of the framework
+ * and is expected to be maintained by caller of SCMI protocol library.
+ *
+ * Return: 0 if all went fine, else corresponding error.
+ */
+const struct scmi_handle *devm_scmi_handle_get(struct device *dev)
+{
+ const struct scmi_handle **ptr;
+ const struct scmi_handle *handle;
+
+ ptr = devres_alloc(devm_scmi_release, sizeof(*ptr), GFP_KERNEL);
+ if (!ptr)
+ return ERR_PTR(-ENOMEM);
+ handle = scmi_handle_get(dev);
+
+ if (!IS_ERR(handle)) {
+ *ptr = handle;
+ devres_add(dev, ptr);
+ } else {
+ devres_free(ptr);
+ }
+
+ return handle;
+}
+EXPORT_SYMBOL_GPL(devm_scmi_handle_get);
+
+static const struct scmi_desc scmi_generic_desc = {
+ .max_rx_timeout_ms = 30, /* we may increase this if required */
+ .max_msg = 20, /* Limited by MBOX_TX_QUEUE_LEN */
+ .max_msg_size = 128,
+};
+
+/* Each compatible listed below must have descriptor associated with it */
+static const struct of_device_id scmi_of_match[] = {
+ { .compatible = "arm,scmi", .data = &scmi_generic_desc },
+ { /* Sentinel */ },
+};
+
+MODULE_DEVICE_TABLE(of, scmi_of_match);
+
+static int scmi_xfer_info_init(struct scmi_info *sinfo)
+{
+ int i;
+ struct scmi_xfer *xfer;
+ struct device *dev = sinfo->dev;
+ const struct scmi_desc *desc = sinfo->desc;
+ struct scmi_xfers_info *info = &sinfo->minfo;
+
+ /* Pre-allocated messages, no more than what hdr.seq can support */
+ if (WARN_ON(desc->max_msg >= (MSG_TOKEN_ID_MASK + 1))) {
+ dev_err(dev, "Maximum message of %d exceeds supported %d\n",
+ desc->max_msg, MSG_TOKEN_ID_MASK + 1);
+ return -EINVAL;
+ }
+
+ info->xfer_block = devm_kcalloc(dev, desc->max_msg,
+ sizeof(*info->xfer_block), GFP_KERNEL);
+ if (!info->xfer_block)
+ return -ENOMEM;
+
+ info->xfer_alloc_table = devm_kcalloc(dev, BITS_TO_LONGS(desc->max_msg),
+ sizeof(long), GFP_KERNEL);
+ if (!info->xfer_alloc_table)
+ return -ENOMEM;
+
+ bitmap_zero(info->xfer_alloc_table, desc->max_msg);
+
+ /* Pre-initialize the buffer pointer to pre-allocated buffers */
+ for (i = 0, xfer = info->xfer_block; i < desc->max_msg; i++, xfer++) {
+ xfer->rx.buf = devm_kcalloc(dev, sizeof(u8), desc->max_msg_size,
+ GFP_KERNEL);
+ if (!xfer->rx.buf)
+ return -ENOMEM;
+
+ xfer->tx.buf = xfer->rx.buf;
+ init_completion(&xfer->done);
+ }
+
+ spin_lock_init(&info->xfer_lock);
+
+ sema_init(&info->sem_xfer_count, desc->max_msg);
+
+ return 0;
+}
+
+static int scmi_mailbox_check(struct device_node *np)
+{
+ struct of_phandle_args arg;
+
+ return of_parse_phandle_with_args(np, "mboxes", "#mbox-cells", 0, &arg);
+}
+
+static int scmi_mbox_free_channel(struct scmi_info *info)
+{
+ if (!IS_ERR_OR_NULL(info->tx_chan)) {
+ mbox_free_channel(info->tx_chan);
+ info->tx_chan = NULL;
+ }
+ if (!IS_ERR_OR_NULL(info->rx_chan)) {
+ mbox_free_channel(info->rx_chan);
+ info->rx_chan = NULL;
+ }
+
+ return 0;
+}
+
+static int scmi_remove(struct platform_device *pdev)
+{
+ int ret = 0;
+ struct scmi_info *info = platform_get_drvdata(pdev);
+
+ of_platform_depopulate(&pdev->dev);
+
+ mutex_lock(&scmi_list_mutex);
+ if (info->users)
+ ret = -EBUSY;
+ else
+ list_del(&info->node);
+ mutex_unlock(&scmi_list_mutex);
+
+ if (!ret)
+ /* Safe to free channels since no more users */
+ return scmi_mbox_free_channel(info);
+
+ return ret;
+}
+
+static inline int scmi_mbox_chan_setup(struct scmi_info *info)
+{
+ int ret;
+ struct resource res;
+ resource_size_t size;
+ struct device *dev = info->dev;
+ struct device_node *shmem, *np = dev->of_node;
+ struct mbox_client *cl;
+
+ cl = &info->cl;
+ cl->dev = dev;
+ cl->rx_callback = scmi_rx_callback;
+ cl->tx_prepare = scmi_tx_prepare;
+ cl->tx_block = false;
+ cl->knows_txdone = true;
+
+ shmem = of_parse_phandle(np, "shmem", 0);
+ ret = of_address_to_resource(shmem, 0, &res);
+ of_node_put(shmem);
+ if (ret) {
+ dev_err(dev, "failed to get SCMI Tx payload mem resource\n");
+ return ret;
+ }
+
+ size = resource_size(&res);
+ info->tx_payload = devm_ioremap(dev, res.start, size);
+ if (!info->tx_payload) {
+ dev_err(dev, "failed to ioremap SCMI Tx payload\n");
+ return -EADDRNOTAVAIL;
+ }
+
+ info->tx_chan = mbox_request_channel_byname(cl, "tx");
+ if (IS_ERR(info->tx_chan)) {
+ ret = PTR_ERR(info->tx_chan);
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "failed to request SCMI Tx mailbox\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int scmi_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct scmi_handle *handle;
+ const struct scmi_desc *desc;
+ struct scmi_info *info;
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+
+ /* Only mailbox method supported, check for the presence of one */
+ if (scmi_mailbox_check(np)) {
+ dev_err(dev, "no mailbox found in %pOF\n", np);
+ return -EINVAL;
+ }
+
+ desc = of_match_device(scmi_of_match, dev)->data;
+
+ info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ info->dev = dev;
+ info->desc = desc;
+ INIT_LIST_HEAD(&info->node);
+
+ ret = scmi_xfer_info_init(info);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, info);
+
+ handle = &info->handle;
+ handle->dev = info->dev;
+
+ ret = scmi_mbox_chan_setup(info);
+ if (ret)
+ return ret;
+
+ mutex_lock(&scmi_list_mutex);
+ list_add_tail(&info->node, &scmi_list);
+ mutex_unlock(&scmi_list_mutex);
+
+ return 0;
+}
+
+static struct platform_driver scmi_driver = {
+ .driver = {
+ .name = "arm-scmi",
+ .of_match_table = of_match_ptr(scmi_of_match),
+ },
+ .probe = scmi_probe,
+ .remove = scmi_remove,
+};
+
+module_platform_driver(scmi_driver);
+
+MODULE_ALIAS("platform: arm-scmi");
+MODULE_AUTHOR("Sudeep Holla <[email protected]>");
+MODULE_DESCRIPTION("ARM SCMI protocol driver");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
new file mode 100644
index 000000000000..fbe80fa1ec9f
--- /dev/null
+++ b/include/linux/scmi_protocol.h
@@ -0,0 +1,48 @@
+/*
+ * SCMI Message Protocol driver header
+ *
+ * Copyright (C) 2017 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 scmi_handle - Handle returned to ARM SCMI clients for usage.
+ *
+ * @dev: pointer to the SCMI device
+ */
+struct scmi_handle {
+ struct device *dev;
+};
+
+#if IS_REACHABLE(CONFIG_ARM_SCMI_PROTOCOL)
+int scmi_handle_put(const struct scmi_handle *handle);
+const struct scmi_handle *scmi_handle_get(struct device *dev);
+const struct scmi_handle *devm_scmi_handle_get(struct device *dev);
+#else
+static inline int scmi_handle_put(const struct scmi_handle *handle)
+{
+ return 0;
+}
+
+static inline const struct scmi_handle *scmi_handle_get(struct device *dev)
+{
+ return NULL;
+}
+
+static inline const struct scmi_handle *devm_scmi_handle_get(struct device *dev)
+{
+ return NULL;
+}
+#endif /* CONFIG_ARM_SCMI_PROTOCOL */
--
2.7.4
The performance protocol is intended for the performance management of
group(s) of device(s) that run in the same performance domain. It
includes even the CPUs. A performance domain is defined by a set of
devices that always have to run at the same performance level.
For example, a set of CPUs that share a voltage domain, and have a
common frequency control, is said to be in the same performance domain.
The commands in this protocol provide functionality to describe the
protocol version, describe various attribute flags, set and get the
performance level of a domain. It also supports discovery of the list
of performance levels supported by a performance domain, and the
properties of each performance level.
This patch adds basic support for the performance protocol.
Cc: Arnd Bergmann <[email protected]>
Signed-off-by: Sudeep Holla <[email protected]>
---
drivers/firmware/arm_scmi/Makefile | 2 +-
drivers/firmware/arm_scmi/common.h | 1 +
drivers/firmware/arm_scmi/perf.c | 511 +++++++++++++++++++++++++++++++++++++
include/linux/scmi_protocol.h | 31 +++
4 files changed, 544 insertions(+), 1 deletion(-)
create mode 100644 drivers/firmware/arm_scmi/perf.c
diff --git a/drivers/firmware/arm_scmi/Makefile b/drivers/firmware/arm_scmi/Makefile
index 21d01d1d6b9c..159de726ee45 100644
--- a/drivers/firmware/arm_scmi/Makefile
+++ b/drivers/firmware/arm_scmi/Makefile
@@ -1,2 +1,2 @@
obj-$(CONFIG_ARM_SCMI_PROTOCOL) = arm_scmi.o
-arm_scmi-y = base.o driver.o
+arm_scmi-y = base.o driver.o perf.o
diff --git a/drivers/firmware/arm_scmi/common.h b/drivers/firmware/arm_scmi/common.h
index e3fe5d9acc82..7473dfcad4ee 100644
--- a/drivers/firmware/arm_scmi/common.h
+++ b/drivers/firmware/arm_scmi/common.h
@@ -30,6 +30,7 @@
#define PROTOCOL_REV_MAJOR(x) ((x) >> PROTOCOL_REV_MINOR_BITS)
#define PROTOCOL_REV_MINOR(x) ((x) & PROTOCOL_REV_MINOR_MASK)
#define MAX_PROTOCOLS_IMP 16
+#define MAX_OPPS 16
enum scmi_std_protocol {
SCMI_PROTOCOL_BASE = 0x10,
diff --git a/drivers/firmware/arm_scmi/perf.c b/drivers/firmware/arm_scmi/perf.c
new file mode 100644
index 000000000000..13d84d829201
--- /dev/null
+++ b/drivers/firmware/arm_scmi/perf.c
@@ -0,0 +1,511 @@
+/*
+ * System Control and Management Interface (SCMI) Performance Protocol
+ *
+ * Copyright (C) 2017 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/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_opp.h>
+#include <linux/sort.h>
+
+#include "common.h"
+
+enum scmi_performance_protocol_cmd {
+ PERF_DOMAIN_ATTRIBUTES = 0x3,
+ PERF_DESCRIBE_LEVELS = 0x4,
+ PERF_LIMITS_SET = 0x5,
+ PERF_LIMITS_GET = 0x6,
+ PERF_LEVEL_SET = 0x7,
+ PERF_LEVEL_GET = 0x8,
+ PERF_NOTIFY_LIMITS = 0x9,
+ PERF_NOTIFY_LEVEL = 0xa,
+};
+
+struct scmi_opp {
+ u32 perf;
+ u32 power;
+ u32 trans_latency_us;
+};
+
+struct scmi_msg_resp_perf_attributes {
+ __le16 num_domains;
+ __le16 flags;
+#define POWER_SCALE_IN_MILLIWATT(x) ((x) & BIT(0))
+ __le32 stats_addr_low;
+ __le32 stats_addr_high;
+ __le32 stats_size;
+};
+
+struct scmi_msg_resp_perf_domain_attributes {
+ __le32 flags;
+#define SUPPORTS_SET_LIMITS(x) ((x) & BIT(31))
+#define SUPPORTS_SET_PERF_LVL(x) ((x) & BIT(30))
+#define SUPPORTS_PERF_LIMIT_NOTIFY(x) ((x) & BIT(29))
+#define SUPPORTS_PERF_LEVEL_NOTIFY(x) ((x) & BIT(28))
+ __le32 rate_limit_us;
+ __le32 sustained_freq_khz;
+ __le32 sustained_perf_level;
+ u8 name[SCMI_MAX_STR_SIZE];
+};
+
+struct scmi_msg_perf_describe_levels {
+ __le32 domain;
+ __le32 level_index;
+};
+
+struct scmi_perf_set_limits {
+ __le32 domain;
+ __le32 max_level;
+ __le32 min_level;
+};
+
+struct scmi_perf_get_limits {
+ __le32 max_level;
+ __le32 min_level;
+};
+
+struct scmi_perf_set_level {
+ __le32 domain;
+ __le32 level;
+};
+
+struct scmi_perf_notify_level_or_limits {
+ __le32 domain;
+ __le32 notify_enable;
+};
+
+struct scmi_msg_resp_perf_describe_levels {
+ __le16 num_returned;
+ __le16 num_remaining;
+ struct {
+ __le32 perf_val;
+ __le32 power;
+ __le16 transition_latency_us;
+ __le16 reserved;
+ } opp[0];
+};
+
+struct perf_dom_info {
+ bool set_limits;
+ bool set_perf;
+ bool perf_limit_notify;
+ bool perf_level_notify;
+ u32 opp_count;
+ u32 sustained_freq_khz;
+ u32 sustained_perf_level;
+ u32 mult_factor;
+ char name[SCMI_MAX_STR_SIZE];
+ struct scmi_opp opp[MAX_OPPS];
+};
+
+struct scmi_perf_info {
+ int num_domains;
+ bool power_scale_mw;
+ u64 stats_addr;
+ u32 stats_size;
+ struct perf_dom_info *dom_info;
+};
+
+static struct scmi_perf_info perf_info;
+
+static int scmi_perf_attributes_get(const struct scmi_handle *handle,
+ struct scmi_perf_info *perf_info)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_msg_resp_perf_attributes *attr;
+
+ ret = scmi_one_xfer_init(handle, PROTOCOL_ATTRIBUTES,
+ SCMI_PROTOCOL_PERF, 0, sizeof(*attr), &t);
+ if (ret)
+ return ret;
+
+ attr = t->rx.buf;
+
+ ret = scmi_do_xfer(handle, t);
+ if (!ret) {
+ u16 flags = le16_to_cpu(attr->flags);
+
+ perf_info->num_domains = le16_to_cpu(attr->num_domains);
+ perf_info->power_scale_mw = POWER_SCALE_IN_MILLIWATT(flags);
+ perf_info->stats_addr = le32_to_cpu(attr->stats_addr_low) |
+ (u64)le32_to_cpu(attr->stats_addr_high) << 32;
+ perf_info->stats_size = le32_to_cpu(attr->stats_size);
+ }
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int
+scmi_perf_domain_attributes_get(const struct scmi_handle *handle, u32 domain,
+ struct perf_dom_info *dom_info)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_msg_resp_perf_domain_attributes *attr;
+
+ ret = scmi_one_xfer_init(handle, PERF_DOMAIN_ATTRIBUTES,
+ SCMI_PROTOCOL_PERF, sizeof(domain),
+ sizeof(*attr), &t);
+ if (ret)
+ return ret;
+
+ *(__le32 *)t->tx.buf = cpu_to_le32(domain);
+ attr = t->rx.buf;
+
+ ret = scmi_do_xfer(handle, t);
+ if (!ret) {
+ u32 flags = le32_to_cpu(attr->flags);
+
+ dom_info->set_limits = SUPPORTS_SET_LIMITS(flags);
+ dom_info->set_perf = SUPPORTS_SET_PERF_LVL(flags);
+ dom_info->perf_limit_notify = SUPPORTS_PERF_LIMIT_NOTIFY(flags);
+ dom_info->perf_level_notify = SUPPORTS_PERF_LEVEL_NOTIFY(flags);
+ dom_info->sustained_freq_khz =
+ le32_to_cpu(attr->sustained_freq_khz);
+ dom_info->sustained_perf_level =
+ le32_to_cpu(attr->sustained_perf_level);
+ dom_info->mult_factor = (dom_info->sustained_freq_khz * 1000) /
+ dom_info->sustained_perf_level;
+ memcpy(dom_info->name, attr->name, SCMI_MAX_STR_SIZE);
+ }
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int opp_cmp_func(const void *opp1, const void *opp2)
+{
+ const struct scmi_opp *t1 = opp1, *t2 = opp2;
+
+ return t1->perf - t2->perf;
+}
+
+static int
+scmi_perf_describe_levels_get(const struct scmi_handle *handle, u32 domain,
+ struct perf_dom_info *perf_dom)
+{
+ int ret, cnt;
+ u32 tot_opp_cnt = 0;
+ u16 num_returned, num_remaining;
+ struct scmi_xfer *t;
+ struct scmi_opp *opp;
+ struct scmi_msg_perf_describe_levels *dom_info;
+ struct scmi_msg_resp_perf_describe_levels *level_info;
+
+ ret = scmi_one_xfer_init(handle, PERF_DESCRIBE_LEVELS,
+ SCMI_PROTOCOL_PERF, sizeof(*dom_info), 0, &t);
+ if (ret)
+ return ret;
+
+ dom_info = t->tx.buf;
+ level_info = t->rx.buf;
+
+ do {
+ dom_info->domain = cpu_to_le32(domain);
+ /* Set the number of OPPs to be skipped/already read */
+ dom_info->level_index = cpu_to_le32(tot_opp_cnt);
+
+ ret = scmi_do_xfer(handle, t);
+ if (ret)
+ break;
+
+ num_returned = le16_to_cpu(level_info->num_returned);
+ num_remaining = le16_to_cpu(level_info->num_remaining);
+ if (tot_opp_cnt + num_returned > MAX_OPPS) {
+ dev_err(handle->dev, "No. of OPPs exceeded MAX_OPPS");
+ break;
+ }
+
+ opp = &perf_dom->opp[tot_opp_cnt];
+ for (cnt = 0; cnt < num_returned; cnt++, opp++) {
+ opp->perf = le32_to_cpu(level_info->opp[cnt].perf_val);
+ opp->power = le32_to_cpu(level_info->opp[cnt].power);
+ opp->trans_latency_us = le16_to_cpu(
+ level_info->opp[cnt].transition_latency_us);
+
+ dev_dbg(handle->dev, "Level %d Power %d Latency %dus\n",
+ opp->perf, opp->power, opp->trans_latency_us);
+ }
+
+ tot_opp_cnt += num_returned;
+ /*
+ * check for both returned and remaining to avoid infinite
+ * loop due to buggy firmware
+ */
+ } while (num_returned && num_remaining);
+
+ perf_dom->opp_count = tot_opp_cnt;
+ scmi_one_xfer_put(handle, t);
+
+ sort(perf_dom->opp, tot_opp_cnt, sizeof(*opp), opp_cmp_func, NULL);
+ return ret;
+}
+
+static int scmi_perf_limits_set(const struct scmi_handle *handle, u32 domain,
+ u32 max_perf, u32 min_perf)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_perf_set_limits *limits;
+
+ ret = scmi_one_xfer_init(handle, PERF_LIMITS_SET, SCMI_PROTOCOL_PERF,
+ sizeof(*limits), 0, &t);
+ if (ret)
+ return ret;
+
+ limits = t->tx.buf;
+ limits->domain = cpu_to_le32(domain);
+ limits->max_level = cpu_to_le32(max_perf);
+ limits->min_level = cpu_to_le32(min_perf);
+
+ ret = scmi_do_xfer(handle, t);
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int scmi_perf_limits_get(const struct scmi_handle *handle, u32 domain,
+ u32 *max_perf, u32 *min_perf)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_perf_get_limits *limits;
+
+ ret = scmi_one_xfer_init(handle, PERF_LIMITS_GET, SCMI_PROTOCOL_PERF,
+ sizeof(__le32), 0, &t);
+ if (ret)
+ return ret;
+
+ *(__le32 *)t->tx.buf = cpu_to_le32(domain);
+
+ ret = scmi_do_xfer(handle, t);
+ if (!ret) {
+ limits = t->rx.buf;
+
+ *max_perf = le32_to_cpu(limits->max_level);
+ *min_perf = le32_to_cpu(limits->min_level);
+ }
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int
+scmi_perf_level_set(const struct scmi_handle *handle, u32 domain, u32 level)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_perf_set_level *lvl;
+
+ ret = scmi_one_xfer_init(handle, PERF_LEVEL_SET, SCMI_PROTOCOL_PERF,
+ sizeof(*lvl), 0, &t);
+ if (ret)
+ return ret;
+
+ lvl = t->tx.buf;
+ lvl->domain = cpu_to_le32(domain);
+ lvl->level = cpu_to_le32(level);
+
+ ret = scmi_do_xfer(handle, t);
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int
+scmi_perf_level_get(const struct scmi_handle *handle, u32 domain, u32 *level)
+{
+ int ret;
+ struct scmi_xfer *t;
+
+ ret = scmi_one_xfer_init(handle, PERF_LEVEL_GET, SCMI_PROTOCOL_PERF,
+ sizeof(u32), sizeof(u32), &t);
+ if (ret)
+ return ret;
+
+ *(__le32 *)t->tx.buf = cpu_to_le32(domain);
+
+ ret = scmi_do_xfer(handle, t);
+ if (!ret)
+ *level = le32_to_cpu(*(__le32 *)t->rx.buf);
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int __scmi_perf_notify_enable(const struct scmi_handle *handle, u32 cmd,
+ u32 domain, bool enable)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_perf_notify_level_or_limits *notify;
+
+ ret = scmi_one_xfer_init(handle, cmd, SCMI_PROTOCOL_PERF,
+ sizeof(*notify), 0, &t);
+ if (ret)
+ return ret;
+
+ notify = t->tx.buf;
+ notify->domain = cpu_to_le32(domain);
+ notify->notify_enable = cpu_to_le32(enable & BIT(0));
+
+ ret = scmi_do_xfer(handle, t);
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int scmi_perf_limits_notify_enable(const struct scmi_handle *handle,
+ u32 domain, bool enable)
+{
+ return __scmi_perf_notify_enable(handle, PERF_NOTIFY_LIMITS,
+ domain, enable);
+}
+
+static int scmi_perf_level_notify_enable(const struct scmi_handle *handle,
+ u32 domain, bool enable)
+{
+ return __scmi_perf_notify_enable(handle, PERF_NOTIFY_LEVEL,
+ domain, enable);
+}
+
+/* Device specific ops */
+static int scmi_dev_domain_id(struct device *dev)
+{
+ struct of_phandle_args clkspec;
+
+ if (of_parse_phandle_with_args(dev->of_node, "clocks", "#clock-cells",
+ 0, &clkspec))
+ return -EINVAL;
+
+ return clkspec.args[0];
+}
+
+static int scmi_dvfs_add_opps_to_device(struct device *dev)
+{
+ int idx, ret, domain;
+ unsigned long freq;
+ struct scmi_opp *opp;
+ struct perf_dom_info *dom;
+
+ domain = scmi_dev_domain_id(dev);
+ if (domain < 0)
+ return domain;
+
+ dom = perf_info.dom_info + domain;
+ if (!dom)
+ return -EIO;
+
+ for (opp = dom->opp, idx = 0; idx < dom->opp_count; idx++, opp++) {
+ freq = opp->perf * dom->mult_factor;
+
+ ret = dev_pm_opp_add(dev, freq, opp->power);
+ if (ret) {
+ dev_warn(dev, "failed to add opp %luHz %umV\n",
+ freq, opp->power);
+
+ while (idx-- > 0) {
+ freq = (--opp)->perf * dom->mult_factor;
+ dev_pm_opp_remove(dev, freq);
+ }
+ return ret;
+ }
+ }
+ return 0;
+}
+
+static int scmi_dvfs_get_transition_latency(struct device *dev)
+{
+ struct perf_dom_info *dom;
+ int domain = scmi_dev_domain_id(dev);
+
+ if (domain < 0)
+ return domain;
+
+ dom = perf_info.dom_info + domain;
+ if (!dom)
+ return -EIO;
+
+ return dom->opp[dom->opp_count - 1].trans_latency_us;
+}
+
+static int scmi_dvfs_freq_set(const struct scmi_handle *handle, u32 domain,
+ unsigned long freq)
+{
+ struct perf_dom_info *dom = perf_info.dom_info + domain;
+
+ return scmi_perf_level_set(handle, domain, freq / dom->mult_factor);
+}
+
+static int scmi_dvfs_freq_get(const struct scmi_handle *handle, u32 domain,
+ unsigned long *freq)
+{
+ int ret;
+ u32 level;
+ struct perf_dom_info *dom = perf_info.dom_info + domain;
+
+ ret = scmi_perf_level_get(handle, domain, &level);
+ if (!ret)
+ *freq = level * dom->mult_factor;
+
+ return ret;
+}
+
+static struct scmi_perf_ops perf_ops = {
+ .limits_set = scmi_perf_limits_set,
+ .limits_get = scmi_perf_limits_get,
+ .level_set = scmi_perf_level_set,
+ .level_get = scmi_perf_level_get,
+ .limits_notify_enable = scmi_perf_limits_notify_enable,
+ .level_notify_enable = scmi_perf_level_notify_enable,
+ .device_domain_id = scmi_dev_domain_id,
+ .get_transition_latency = scmi_dvfs_get_transition_latency,
+ .add_opps_to_device = scmi_dvfs_add_opps_to_device,
+ .freq_set = scmi_dvfs_freq_set,
+ .freq_get = scmi_dvfs_freq_get,
+};
+
+int scmi_perf_protocol_init(struct scmi_handle *handle)
+{
+ int domain;
+ u32 version;
+
+ scmi_version_get(handle, SCMI_PROTOCOL_PERF, &version);
+
+ dev_dbg(handle->dev, "Performance Version %d.%d\n",
+ PROTOCOL_REV_MAJOR(version), PROTOCOL_REV_MINOR(version));
+
+ scmi_perf_attributes_get(handle, &perf_info);
+
+ perf_info.dom_info = devm_kcalloc(handle->dev, perf_info.num_domains,
+ sizeof(struct perf_dom_info),
+ GFP_KERNEL);
+ if (!perf_info.dom_info)
+ return -ENOMEM;
+
+ for (domain = 0; domain < perf_info.num_domains; domain++) {
+ struct perf_dom_info *dom = perf_info.dom_info + domain;
+
+ scmi_perf_domain_attributes_get(handle, domain, dom);
+ scmi_perf_describe_levels_get(handle, domain, dom);
+ }
+
+ handle->perf_ops = &perf_ops;
+
+ return 0;
+}
diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
index 3ef2d48f03c2..c9f97e69444a 100644
--- a/include/linux/scmi_protocol.h
+++ b/include/linux/scmi_protocol.h
@@ -43,15 +43,46 @@ struct scmi_revision_info {
char sub_vendor_id[SCMI_MAX_STR_SIZE];
};
+struct scmi_handle;
+
+/**
+ * struct scmi_perf_ops - represents the various operations provided
+ * by SCMI Performance Protocol
+ *
+ * @limits_set: sets limits on the performance level of a domain
+ * @limits_get: gets limits on the performance level of a domain
+ * @level_set: sets the performance level of a domain
+ * @level_get: gets the performance level of a domain
+ * @limits_notify_enable: requests notifications from the platform for changes
+ * in the allowed maximum and minimum performance levels
+ * @level_notify_enable: requests notifications from the platform when the
+ * performance level for a domain changes in value
+ */
+struct scmi_perf_ops {
+ int (*limits_set)(const struct scmi_handle *, u32, u32, u32);
+ int (*limits_get)(const struct scmi_handle *, u32, u32 *, u32 *);
+ int (*level_set)(const struct scmi_handle *, u32, u32);
+ int (*level_get)(const struct scmi_handle *, u32, u32 *);
+ int (*limits_notify_enable)(const struct scmi_handle *, u32, bool);
+ int (*level_notify_enable)(const struct scmi_handle *, u32, bool);
+ int (*device_domain_id)(struct device *);
+ int (*get_transition_latency)(struct device *);
+ int (*add_opps_to_device)(struct device *);
+ int (*freq_set)(const struct scmi_handle *, u32, unsigned long);
+ int (*freq_get)(const struct scmi_handle *, u32, unsigned long *);
+};
+
/**
* struct scmi_handle - Handle returned to ARM SCMI clients for usage.
*
* @dev: pointer to the SCMI device
* @version: pointer to the structure containing SCMI version information
+ * @perf_ops: pointer to set of performance protocol operations
*/
struct scmi_handle {
struct device *dev;
struct scmi_revision_info *version;
+ struct scmi_perf_ops *perf_ops;
};
#if IS_REACHABLE(CONFIG_ARM_SCMI_PROTOCOL)
--
2.7.4
Now that we have basic support for all the protocols in the
specification, let's probe them individually and initialise them.
Cc: Arnd Bergmann <[email protected]>
Signed-off-by: Sudeep Holla <[email protected]>
---
drivers/firmware/arm_scmi/common.h | 5 +++
drivers/firmware/arm_scmi/driver.c | 80 +++++++++++++++++++++++++++++++++++++-
2 files changed, 84 insertions(+), 1 deletion(-)
diff --git a/drivers/firmware/arm_scmi/common.h b/drivers/firmware/arm_scmi/common.h
index 7473dfcad4ee..d7c73a8d260b 100644
--- a/drivers/firmware/arm_scmi/common.h
+++ b/drivers/firmware/arm_scmi/common.h
@@ -118,4 +118,9 @@ int scmi_version_get(const struct scmi_handle *h, u8 protocol, u32 *version);
void scmi_setup_protocol_implemented(const struct scmi_handle *handle,
u8 *prot_imp);
+typedef int (*scmi_init_fn_t)(struct scmi_handle *);
int scmi_base_protocol_init(struct scmi_handle *h);
+int scmi_perf_protocol_init(struct scmi_handle *h);
+int scmi_sensors_protocol_init(struct scmi_handle *h);
+int scmi_power_protocol_init(struct scmi_handle *h);
+int scmi_clock_protocol_init(struct scmi_handle *h);
diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c
index 601d0d7210d9..6f31761043e2 100644
--- a/drivers/firmware/arm_scmi/driver.c
+++ b/drivers/firmware/arm_scmi/driver.c
@@ -157,6 +157,12 @@ struct scmi_shared_mem {
u8 msg_payload[0];
};
+struct scmi_protocol_match {
+ u8 protocol_id;
+ scmi_init_fn_t fn;
+ char name[32];
+};
+
static int scmi_linux_errmap[] = {
/* better than switch case as long as return value is continuous */
0, /* SCMI_SUCCESS */
@@ -687,6 +693,41 @@ static int scmi_xfer_info_init(struct scmi_info *sinfo)
return 0;
}
+static const struct scmi_protocol_match scmi_protocols[] = {
+ {
+ .protocol_id = SCMI_PROTOCOL_PERF,
+ .fn = scmi_perf_protocol_init,
+ .name = "scmi-cpufreq",
+ }, {
+ .protocol_id = SCMI_PROTOCOL_CLOCK,
+ .fn = scmi_clock_protocol_init,
+ .name = "scmi-clocks",
+ }, {
+ .protocol_id = SCMI_PROTOCOL_POWER,
+ .fn = scmi_power_protocol_init,
+ .name = "scmi-power-domain",
+ }, {
+ .protocol_id = SCMI_PROTOCOL_SENSOR,
+ .fn = scmi_sensors_protocol_init,
+ .name = "scmi-hwmon",
+ },
+ {}
+};
+
+static const struct scmi_protocol_match *scmi_protocol_match_get(u8 protocol_id)
+{
+ int i;
+ const struct scmi_protocol_match *match = NULL, *loop = scmi_protocols;
+
+ for (i = 0; i < ARRAY_SIZE(scmi_protocols); i++, loop++)
+ if (loop->protocol_id == protocol_id) {
+ match = loop;
+ break;
+ }
+
+ return match;
+}
+
static int scmi_mailbox_check(struct device_node *np)
{
struct of_phandle_args arg;
@@ -778,7 +819,7 @@ static int scmi_probe(struct platform_device *pdev)
const struct scmi_desc *desc;
struct scmi_info *info;
struct device *dev = &pdev->dev;
- struct device_node *np = dev->of_node;
+ struct device_node *child, *np = dev->of_node;
/* Only mailbox method supported, check for the presence of one */
if (scmi_mailbox_check(np)) {
@@ -817,6 +858,43 @@ static int scmi_probe(struct platform_device *pdev)
return ret;
}
+ for_each_available_child_of_node(np, child) {
+ int init_ret;
+ u32 prot_id;
+ const struct scmi_protocol_match *match;
+
+ if (of_property_read_u32(child, "reg", &prot_id))
+ continue;
+
+ prot_id &= MSG_PROTOCOL_ID_MASK;
+
+ if (!scmi_is_protocol_implemented(handle, prot_id)) {
+ dev_err(dev, "SCMI protocol %d not implemented\n",
+ prot_id);
+ continue;
+ }
+
+ match = scmi_protocol_match_get(prot_id);
+ if (match) {
+ struct platform_device *cdev;
+
+ cdev = of_platform_device_create(child, match->name,
+ dev);
+ if (!cdev) {
+ dev_err(dev, "failed to create %s device\n",
+ match->name);
+ continue;
+ }
+
+ init_ret = match->fn(handle);
+ if (init_ret) {
+ dev_err(dev, "SCMI protocol %d init error %d\n",
+ prot_id, init_ret);
+ of_platform_device_destroy(&cdev->dev, NULL);
+ }
+ }
+ }
+
mutex_lock(&scmi_list_mutex);
list_add_tail(&info->node, &scmi_list);
mutex_unlock(&scmi_list_mutex);
--
2.7.4
In order to implement fast CPU DVFS switching, we need to perform all
DVFS operations atomically. Since SCMI transfer already provide option
to choose between pooling vs interrupt driven(default), we can opt for
polling based transfers for set,get performance domain operations.
This patch adds option to choose between polling vs interrupt driven
SCMI transfers for set,get performance level operations.
Cc: Arnd Bergmann <[email protected]>
Signed-off-by: Sudeep Holla <[email protected]>
---
drivers/firmware/arm_scmi/perf.c | 19 +++++++++++--------
include/linux/scmi_protocol.h | 8 ++++----
2 files changed, 15 insertions(+), 12 deletions(-)
diff --git a/drivers/firmware/arm_scmi/perf.c b/drivers/firmware/arm_scmi/perf.c
index 13d84d829201..334e8ec321ed 100644
--- a/drivers/firmware/arm_scmi/perf.c
+++ b/drivers/firmware/arm_scmi/perf.c
@@ -305,8 +305,8 @@ static int scmi_perf_limits_get(const struct scmi_handle *handle, u32 domain,
return ret;
}
-static int
-scmi_perf_level_set(const struct scmi_handle *handle, u32 domain, u32 level)
+static int scmi_perf_level_set(const struct scmi_handle *handle, u32 domain,
+ u32 level, bool poll)
{
int ret;
struct scmi_xfer *t;
@@ -317,6 +317,7 @@ scmi_perf_level_set(const struct scmi_handle *handle, u32 domain, u32 level)
if (ret)
return ret;
+ t->hdr.poll_completion = poll;
lvl = t->tx.buf;
lvl->domain = cpu_to_le32(domain);
lvl->level = cpu_to_le32(level);
@@ -327,8 +328,8 @@ scmi_perf_level_set(const struct scmi_handle *handle, u32 domain, u32 level)
return ret;
}
-static int
-scmi_perf_level_get(const struct scmi_handle *handle, u32 domain, u32 *level)
+static int scmi_perf_level_get(const struct scmi_handle *handle, u32 domain,
+ u32 *level, bool poll)
{
int ret;
struct scmi_xfer *t;
@@ -338,6 +339,7 @@ scmi_perf_level_get(const struct scmi_handle *handle, u32 domain, u32 *level)
if (ret)
return ret;
+ t->hdr.poll_completion = poll;
*(__le32 *)t->tx.buf = cpu_to_le32(domain);
ret = scmi_do_xfer(handle, t);
@@ -445,21 +447,22 @@ static int scmi_dvfs_get_transition_latency(struct device *dev)
}
static int scmi_dvfs_freq_set(const struct scmi_handle *handle, u32 domain,
- unsigned long freq)
+ unsigned long freq, bool poll)
{
struct perf_dom_info *dom = perf_info.dom_info + domain;
- return scmi_perf_level_set(handle, domain, freq / dom->mult_factor);
+ return scmi_perf_level_set(handle, domain, freq / dom->mult_factor,
+ poll);
}
static int scmi_dvfs_freq_get(const struct scmi_handle *handle, u32 domain,
- unsigned long *freq)
+ unsigned long *freq, bool poll)
{
int ret;
u32 level;
struct perf_dom_info *dom = perf_info.dom_info + domain;
- ret = scmi_perf_level_get(handle, domain, &level);
+ ret = scmi_perf_level_get(handle, domain, &level, poll);
if (!ret)
*freq = level * dom->mult_factor;
diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
index d2a34cc9df8b..cf48fc3e2243 100644
--- a/include/linux/scmi_protocol.h
+++ b/include/linux/scmi_protocol.h
@@ -99,15 +99,15 @@ struct scmi_clk_ops {
struct scmi_perf_ops {
int (*limits_set)(const struct scmi_handle *, u32, u32, u32);
int (*limits_get)(const struct scmi_handle *, u32, u32 *, u32 *);
- int (*level_set)(const struct scmi_handle *, u32, u32);
- int (*level_get)(const struct scmi_handle *, u32, u32 *);
+ int (*level_set)(const struct scmi_handle *, u32, u32, bool);
+ int (*level_get)(const struct scmi_handle *, u32, u32 *, bool);
int (*limits_notify_enable)(const struct scmi_handle *, u32, bool);
int (*level_notify_enable)(const struct scmi_handle *, u32, bool);
int (*device_domain_id)(struct device *);
int (*get_transition_latency)(struct device *);
int (*add_opps_to_device)(struct device *);
- int (*freq_set)(const struct scmi_handle *, u32, unsigned long);
- int (*freq_get)(const struct scmi_handle *, u32, unsigned long *);
+ int (*freq_set)(const struct scmi_handle *, u32, unsigned long, bool);
+ int (*freq_get)(const struct scmi_handle *, u32, unsigned long *, bool);
};
/**
--
2.7.4
In order to support per-protocol channels if available, we need to
factor out all the mailbox channel information(Tx/Rx payload and
channel handle) out of the main SCMI instance information structure.
This patch refactors the existing channel information into a separate
chan_info structure.
Cc: Arnd Bergmann <[email protected]>
Signed-off-by: Sudeep Holla <[email protected]>
---
drivers/firmware/arm_scmi/driver.c | 93 +++++++++++++++++++++++---------------
1 file changed, 57 insertions(+), 36 deletions(-)
diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c
index 8257315cf3eb..3eaf619858bb 100644
--- a/drivers/firmware/arm_scmi/driver.c
+++ b/drivers/firmware/arm_scmi/driver.c
@@ -104,6 +104,25 @@ struct scmi_desc {
};
/**
+ * struct scmi_chan_info - Structure representing a SCMI channel informfation
+ *
+ * @cl: Mailbox Client
+ * @tx_chan: Transmit mailbox channel
+ * @rx_chan: Receive mailbox channel
+ * @tx_payload: Transmit mailbox channel payload area
+ * @rx_payload: Receive mailbox channel payload area
+ * @info: Reference to SCMI instance corresponding to this channel
+ */
+struct scmi_chan_info {
+ struct mbox_client cl;
+ struct mbox_chan *tx_chan;
+ struct mbox_chan *rx_chan;
+ void __iomem *tx_payload;
+ void __iomem *rx_payload;
+ struct scmi_info *info;
+};
+
+/**
* struct scmi_info - Structure representing a SCMI instance
*
* @dev: Device pointer
@@ -111,12 +130,8 @@ struct scmi_desc {
* @handle: Instance of SCMI handle to send to clients
* @version: SCMI revision information containing protocol version,
* implementation version and (sub-)vendor identification.
- * @cl: Mailbox Client
- * @tx_chan: Transmit mailbox channel
- * @rx_chan: Receive mailbox channel
- * @tx_payload: Transmit mailbox channel payload area
- * @rx_payload: Receive mailbox channel payload area
* @minfo: Message info
+ * @cinfo: Reference to SCMI channel information
* @protocols_imp: list of protocols implemented
* @node: list head
* @users: Number of users of this instance
@@ -126,18 +141,14 @@ struct scmi_info {
const struct scmi_desc *desc;
struct scmi_revision_info version;
struct scmi_handle handle;
- struct mbox_client cl;
- struct mbox_chan *tx_chan;
- struct mbox_chan *rx_chan;
- void __iomem *tx_payload;
- void __iomem *rx_payload;
struct scmi_xfers_info minfo;
+ struct scmi_chan_info *cinfo;
u8 *protocols_imp;
struct list_head node;
int users;
};
-#define client_to_scmi_info(c) container_of(c, struct scmi_info, cl)
+#define client_to_scmi_chan_info(c) container_of(c, struct scmi_chan_info, cl)
#define handle_to_scmi_info(h) container_of(h, struct scmi_info, handle)
/*
@@ -226,10 +237,11 @@ static void scmi_rx_callback(struct mbox_client *cl, void *m)
{
u16 xfer_id;
struct scmi_xfer *xfer;
- struct scmi_info *info = client_to_scmi_info(cl);
+ struct scmi_chan_info *cinfo = client_to_scmi_chan_info(cl);
+ struct scmi_info *info = cinfo->info;
struct scmi_xfers_info *minfo = &info->minfo;
struct device *dev = info->dev;
- struct scmi_shared_mem *mem = info->tx_payload;
+ struct scmi_shared_mem *mem = cinfo->tx_payload;
xfer_id = MSG_XTRACT_TOKEN(mem->msg_header);
@@ -280,8 +292,8 @@ static inline u32 pack_scmi_header(struct scmi_msg_hdr *hdr)
static void scmi_tx_prepare(struct mbox_client *cl, void *m)
{
struct scmi_xfer *t = m;
- struct scmi_info *info = client_to_scmi_info(cl);
- struct scmi_shared_mem *mem = info->tx_payload;
+ struct scmi_chan_info *cinfo = client_to_scmi_chan_info(cl);
+ struct scmi_shared_mem *mem = cinfo->tx_payload;
mem->channel_status = 0x0; /* Mark channel busy + clear error */
mem->flags = t->hdr.poll_completion ? 0 : SCMI_SHMEM_FLAG_INTR_ENABLED;
@@ -372,9 +384,9 @@ void scmi_one_xfer_put(const struct scmi_handle *handle, struct scmi_xfer *xfer)
}
static bool
-scmi_xfer_poll_done(const struct scmi_info *info, struct scmi_xfer *xfer)
+scmi_xfer_poll_done(const struct scmi_chan_info *cinfo, struct scmi_xfer *xfer)
{
- struct scmi_shared_mem *mem = info->tx_payload;
+ struct scmi_shared_mem *mem = cinfo->tx_payload;
u16 xfer_id = MSG_XTRACT_TOKEN(mem->msg_header);
if (xfer->hdr.seq != xfer_id)
@@ -400,8 +412,9 @@ int scmi_do_xfer(const struct scmi_handle *handle, struct scmi_xfer *xfer)
int timeout;
struct scmi_info *info = handle_to_scmi_info(handle);
struct device *dev = info->dev;
+ struct scmi_chan_info *cinfo = info->cinfo;
- ret = mbox_send_message(info->tx_chan, xfer);
+ ret = mbox_send_message(cinfo->tx_chan, xfer);
if (ret < 0) {
dev_dbg(dev, "mbox send fail %d\n", ret);
return ret;
@@ -412,10 +425,10 @@ int scmi_do_xfer(const struct scmi_handle *handle, struct scmi_xfer *xfer)
if (xfer->hdr.poll_completion) {
timeout = info->desc->max_rx_timeout_ms * 100;
- while (!scmi_xfer_poll_done(info, xfer) && timeout--)
+ while (!scmi_xfer_poll_done(cinfo, xfer) && timeout--)
udelay(10);
if (timeout)
- scmi_fetch_response(xfer, info->tx_payload);
+ scmi_fetch_response(xfer, cinfo->tx_payload);
else
ret = -ETIMEDOUT;
} else {
@@ -435,7 +448,7 @@ int scmi_do_xfer(const struct scmi_handle *handle, struct scmi_xfer *xfer)
* Unfortunately, we have to kick the mailbox framework after we have
* received our message.
*/
- mbox_client_txdone(info->tx_chan, ret);
+ mbox_client_txdone(cinfo->tx_chan, ret);
return ret;
}
@@ -759,15 +772,15 @@ static int scmi_mailbox_check(struct device_node *np)
return of_parse_phandle_with_args(np, "mboxes", "#mbox-cells", 0, &arg);
}
-static int scmi_mbox_free_channel(struct scmi_info *info)
+static int scmi_mbox_free_channel(struct scmi_chan_info *cinfo)
{
- if (!IS_ERR_OR_NULL(info->tx_chan)) {
- mbox_free_channel(info->tx_chan);
- info->tx_chan = NULL;
+ if (!IS_ERR_OR_NULL(cinfo->tx_chan)) {
+ mbox_free_channel(cinfo->tx_chan);
+ cinfo->tx_chan = NULL;
}
- if (!IS_ERR_OR_NULL(info->rx_chan)) {
- mbox_free_channel(info->rx_chan);
- info->rx_chan = NULL;
+ if (!IS_ERR_OR_NULL(cinfo->rx_chan)) {
+ mbox_free_channel(cinfo->rx_chan);
+ cinfo->rx_chan = NULL;
}
return 0;
@@ -789,7 +802,7 @@ static int scmi_remove(struct platform_device *pdev)
if (!ret)
/* Safe to free channels since no more users */
- return scmi_mbox_free_channel(info);
+ return scmi_mbox_free_channel(info->cinfo);
return ret;
}
@@ -801,9 +814,17 @@ static inline int scmi_mbox_chan_setup(struct scmi_info *info)
resource_size_t size;
struct device *dev = info->dev;
struct device_node *shmem, *np = dev->of_node;
+ struct scmi_chan_info *cinfo;
struct mbox_client *cl;
- cl = &info->cl;
+ cinfo = devm_kzalloc(info->dev, sizeof(*cinfo), GFP_KERNEL);
+ if (!cinfo)
+ return -ENOMEM;
+
+ info->cinfo = cinfo;
+ cinfo->info = info;
+
+ cl = &cinfo->cl;
cl->dev = dev;
cl->rx_callback = scmi_rx_callback;
cl->tx_prepare = scmi_tx_prepare;
@@ -819,15 +840,15 @@ static inline int scmi_mbox_chan_setup(struct scmi_info *info)
}
size = resource_size(&res);
- info->tx_payload = devm_ioremap(dev, res.start, size);
- if (!info->tx_payload) {
+ cinfo->tx_payload = devm_ioremap(info->dev, res.start, size);
+ if (!cinfo->tx_payload) {
dev_err(dev, "failed to ioremap SCMI Tx payload\n");
return -EADDRNOTAVAIL;
}
- info->tx_chan = mbox_request_channel_byname(cl, "tx");
- if (IS_ERR(info->tx_chan)) {
- ret = PTR_ERR(info->tx_chan);
+ cinfo->tx_chan = mbox_request_channel_byname(cl, "tx");
+ if (IS_ERR(cinfo->tx_chan)) {
+ ret = PTR_ERR(cinfo->tx_chan);
if (ret != -EPROBE_DEFER)
dev_err(dev, "failed to request SCMI Tx mailbox\n");
return ret;
@@ -878,7 +899,7 @@ static int scmi_probe(struct platform_device *pdev)
ret = scmi_base_protocol_init(handle);
if (ret) {
dev_err(dev, "unable to communicate with SCMI(%d)\n", ret);
- scmi_mbox_free_channel(info);
+ scmi_mbox_free_channel(info->cinfo);
return ret;
}
--
2.7.4
This patch hooks up the support for device power domain provided by
SCMI using the Linux generic power domain infrastructure.
Cc: Kevin Hilman <[email protected]>
Cc: Ulf Hansson <[email protected]>
Signed-off-by: Sudeep Holla <[email protected]>
---
drivers/firmware/Kconfig | 13 +++
drivers/firmware/arm_scmi/Makefile | 1 +
drivers/firmware/arm_scmi/scmi_pm_domain.c | 134 +++++++++++++++++++++++++++++
3 files changed, 148 insertions(+)
create mode 100644 drivers/firmware/arm_scmi/scmi_pm_domain.c
diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig
index c3d1a12763ce..a4462bc661c8 100644
--- a/drivers/firmware/Kconfig
+++ b/drivers/firmware/Kconfig
@@ -40,6 +40,19 @@ config ARM_SCMI_PROTOCOL
This protocol library provides interface for all the client drivers
making use of the features offered by the SCMI.
+config ARM_SCMI_POWER_DOMAIN
+ tristate "SCMI power domain driver"
+ depends on ARM_SCMI_PROTOCOL || (COMPILE_TEST && OF)
+ default y
+ select PM_GENERIC_DOMAINS if PM
+ help
+ This enables support for the SCMI power domains which can be
+ enabled or disabled via the SCP firmware
+
+ This driver can also be built as a module. If so, the module
+ will be called scmi_pm_domain. Note this may needed early in boot
+ before rootfs may be available.
+
config ARM_SCPI_PROTOCOL
tristate "ARM System Control and Power Interface (SCPI) Message Protocol"
depends on ARM || ARM64 || COMPILE_TEST
diff --git a/drivers/firmware/arm_scmi/Makefile b/drivers/firmware/arm_scmi/Makefile
index f9dee5ad0aa0..93e399d8f0ce 100644
--- a/drivers/firmware/arm_scmi/Makefile
+++ b/drivers/firmware/arm_scmi/Makefile
@@ -1,2 +1,3 @@
obj-$(CONFIG_ARM_SCMI_PROTOCOL) = arm_scmi.o
arm_scmi-y = base.o clock.o driver.o perf.o power.o sensors.o
+obj-$(CONFIG_ARM_SCMI_POWER_DOMAIN) += scmi_pm_domain.o
diff --git a/drivers/firmware/arm_scmi/scmi_pm_domain.c b/drivers/firmware/arm_scmi/scmi_pm_domain.c
new file mode 100644
index 000000000000..e53aa9d0af6e
--- /dev/null
+++ b/drivers/firmware/arm_scmi/scmi_pm_domain.c
@@ -0,0 +1,134 @@
+/*
+ * SCMI Generic power domain support.
+ *
+ * Copyright (C) 2017 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/io.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/pm_domain.h>
+#include <linux/scmi_protocol.h>
+
+struct scmi_pm_domain {
+ struct generic_pm_domain genpd;
+ const struct scmi_handle *handle;
+ const char *name;
+ u32 domain;
+};
+
+#define to_scmi_pd(gpd) container_of(gpd, struct scmi_pm_domain, genpd)
+
+static int scmi_pd_power(struct generic_pm_domain *domain, bool power_on)
+{
+ int ret;
+ u32 state, ret_state;
+ struct scmi_pm_domain *pd = to_scmi_pd(domain);
+ const struct scmi_power_ops *ops = pd->handle->power_ops;
+
+ if (power_on)
+ state = SCMI_POWER_STATE_GENERIC_ON;
+ else
+ state = SCMI_POWER_STATE_GENERIC_OFF;
+
+ ret = ops->state_set(pd->handle, pd->domain, state);
+ if (!ret)
+ ret = ops->state_get(pd->handle, pd->domain, &ret_state);
+ if (!ret && state != ret_state)
+ return -EIO;
+
+ return ret;
+}
+
+static int scmi_pd_power_on(struct generic_pm_domain *domain)
+{
+ return scmi_pd_power(domain, true);
+}
+
+static int scmi_pd_power_off(struct generic_pm_domain *domain)
+{
+ return scmi_pd_power(domain, false);
+}
+
+static int scmi_pm_domain_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct scmi_pm_domain *scmi_pd;
+ struct genpd_onecell_data *scmi_pd_data;
+ struct generic_pm_domain **domains;
+ int num_domains, i;
+ const struct scmi_handle *handle = devm_scmi_handle_get(dev);
+
+ if (IS_ERR_OR_NULL(handle) || !handle->power_ops)
+ return -EPROBE_DEFER;
+
+ num_domains = handle->power_ops->num_domains_get(handle);
+ if (num_domains < 0) {
+ dev_err(dev, "number of domains not found\n");
+ return num_domains;
+ }
+
+ scmi_pd = devm_kcalloc(dev, num_domains, sizeof(*scmi_pd), GFP_KERNEL);
+ if (!scmi_pd)
+ return -ENOMEM;
+
+ scmi_pd_data = devm_kzalloc(dev, sizeof(*scmi_pd_data), GFP_KERNEL);
+ if (!scmi_pd_data)
+ return -ENOMEM;
+
+ domains = devm_kcalloc(dev, num_domains, sizeof(*domains), GFP_KERNEL);
+ if (!domains)
+ return -ENOMEM;
+
+ for (i = 0; i < num_domains; i++, scmi_pd++) {
+ domains[i] = &scmi_pd->genpd;
+
+ scmi_pd->domain = i;
+ scmi_pd->handle = handle;
+ scmi_pd->name = handle->power_ops->name_get(handle, i);
+ scmi_pd->genpd.name = scmi_pd->name;
+ scmi_pd->genpd.power_off = scmi_pd_power_off;
+ scmi_pd->genpd.power_on = scmi_pd_power_on;
+
+ /*
+ * Treat all power domains as off at boot.
+ *
+ * The SCP firmware itself may have switched on some domains,
+ * but for reference counting purpose, keep it this way.
+ */
+ pm_genpd_init(&scmi_pd->genpd, NULL, true);
+ }
+
+ scmi_pd_data->domains = domains;
+ scmi_pd_data->num_domains = num_domains;
+
+ of_genpd_add_provider_onecell(np, scmi_pd_data);
+
+ return 0;
+}
+
+static struct platform_driver scmi_power_domain_driver = {
+ .driver = {
+ .name = "scmi-power-domain",
+ },
+ .probe = scmi_pm_domain_probe,
+};
+module_platform_driver(scmi_power_domain_driver);
+
+MODULE_AUTHOR("Sudeep Holla <[email protected]>");
+MODULE_DESCRIPTION("ARM SCMI power domain driver");
+MODULE_LICENSE("GPL v2");
--
2.7.4
Create a driver to add support for SoC sensors exported by the System
Control Processor (SCP) via the System Control and Management Interface
(SCMI). The supported sensor types is one of voltage, temperature,
current, and power.
The sensor labels and values provided by the SCP are exported via the
hwmon sysfs interface.
Cc: Guenter Roeck <[email protected]>
Cc: [email protected]
Signed-off-by: Sudeep Holla <[email protected]>
---
drivers/hwmon/Kconfig | 12 +++
drivers/hwmon/Makefile | 1 +
drivers/hwmon/scmi-hwmon.c | 261 +++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 274 insertions(+)
create mode 100644 drivers/hwmon/scmi-hwmon.c
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 5ef2814345ef..2bb63af8d674 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -321,6 +321,18 @@ config SENSORS_APPLESMC
Say Y here if you have an applicable laptop and want to experience
the awesome power of applesmc.
+config SENSORS_ARM_SCMI
+ tristate "ARM SCMI Sensors"
+ depends on ARM_SCMI_PROTOCOL
+ depends on THERMAL || !THERMAL_OF
+ help
+ This driver provides support for temperature, voltage, current
+ and power sensors available on SCMI based platforms. The actual
+ number and type of sensors exported depend on the platform.
+
+ This driver can also be built as a module. If so, the module
+ will be called scmi-hwmon.
+
config SENSORS_ARM_SCPI
tristate "ARM SCPI Sensors"
depends on ARM_SCPI_PROTOCOL
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index d4641a9f16c1..02c3783c319f 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -44,6 +44,7 @@ obj-$(CONFIG_SENSORS_ADT7462) += adt7462.o
obj-$(CONFIG_SENSORS_ADT7470) += adt7470.o
obj-$(CONFIG_SENSORS_ADT7475) += adt7475.o
obj-$(CONFIG_SENSORS_APPLESMC) += applesmc.o
+obj-$(CONFIG_SENSORS_ARM_SCMI) += scmi-hwmon.o
obj-$(CONFIG_SENSORS_ARM_SCPI) += scpi-hwmon.o
obj-$(CONFIG_SENSORS_ASC7621) += asc7621.o
obj-$(CONFIG_SENSORS_ASPEED) += aspeed-pwm-tacho.o
diff --git a/drivers/hwmon/scmi-hwmon.c b/drivers/hwmon/scmi-hwmon.c
new file mode 100644
index 000000000000..f38c9708d99c
--- /dev/null
+++ b/drivers/hwmon/scmi-hwmon.c
@@ -0,0 +1,261 @@
+/*
+ * System Control and Management Interface(SCMI) based hwmon sensor driver
+ *
+ * Copyright (C) 2017 ARM Ltd.
+ * Punit Agrawal <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/hwmon.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/scmi_protocol.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/thermal.h>
+
+struct sensor_data {
+ const struct scmi_sensor_info *info;
+ struct device_attribute dev_attr_input;
+ struct device_attribute dev_attr_label;
+ char input[20];
+ char label[20];
+};
+
+struct scmi_thermal_zone {
+ int sensor_id;
+ struct scmi_sensors *scmi_sensors;
+};
+
+struct scmi_sensors {
+ const struct scmi_handle *handle;
+ struct sensor_data *data;
+ struct list_head thermal_zones;
+ struct attribute **attrs;
+ struct attribute_group group;
+ const struct attribute_group *groups[2];
+};
+
+static int scmi_read_temp(void *dev, int *temp)
+{
+ struct scmi_thermal_zone *zone = dev;
+ struct scmi_sensors *scmi_sensors = zone->scmi_sensors;
+ const struct scmi_handle *handle = scmi_sensors->handle;
+ struct sensor_data *sensor = &scmi_sensors->data[zone->sensor_id];
+ u64 value;
+ int ret;
+
+ ret = handle->sensor_ops->reading_get(handle, sensor->info->id,
+ false, &value);
+ if (ret)
+ return ret;
+
+ *temp = value;
+ return 0;
+}
+
+/* hwmon callback functions */
+static ssize_t
+scmi_show_sensor(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct scmi_sensors *scmi_sensors = dev_get_drvdata(dev);
+ const struct scmi_handle *handle = scmi_sensors->handle;
+ struct sensor_data *sensor;
+ u64 value;
+ int ret;
+
+ sensor = container_of(attr, struct sensor_data, dev_attr_input);
+
+ ret = handle->sensor_ops->reading_get(handle, sensor->info->id,
+ false, &value);
+ if (ret)
+ return ret;
+
+ return sprintf(buf, "%llu\n", value);
+}
+
+static ssize_t
+scmi_show_label(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct sensor_data *sensor;
+
+ sensor = container_of(attr, struct sensor_data, dev_attr_label);
+
+ return sprintf(buf, "%s\n", sensor->info->name);
+}
+
+static struct thermal_zone_of_device_ops scmi_sensor_ops = {
+ .get_temp = scmi_read_temp,
+};
+
+static int scmi_hwmon_probe(struct platform_device *pdev)
+{
+ int idx;
+ u16 nr_sensors, i;
+ int num_temp = 0, num_volt = 0, num_current = 0, num_power = 0;
+ int num_energy = 0;
+ struct device *hwdev, *dev = &pdev->dev;
+ struct scmi_sensors *scmi_sensors;
+ const struct scmi_handle *handle = devm_scmi_handle_get(dev);
+
+ if (IS_ERR_OR_NULL(handle) || !handle->sensor_ops)
+ return -EPROBE_DEFER;
+
+ nr_sensors = handle->sensor_ops->count_get(handle);
+ if (!nr_sensors)
+ return -EIO;
+
+ scmi_sensors = devm_kzalloc(dev, sizeof(*scmi_sensors), GFP_KERNEL);
+ if (!scmi_sensors)
+ return -ENOMEM;
+
+ scmi_sensors->data = devm_kcalloc(dev, nr_sensors,
+ sizeof(*scmi_sensors->data),
+ GFP_KERNEL);
+ if (!scmi_sensors->data)
+ return -ENOMEM;
+
+ scmi_sensors->attrs = devm_kcalloc(dev, (nr_sensors * 2) + 1,
+ sizeof(*scmi_sensors->attrs),
+ GFP_KERNEL);
+ if (!scmi_sensors->attrs)
+ return -ENOMEM;
+
+ scmi_sensors->handle = handle;
+
+ for (i = 0, idx = 0; i < nr_sensors; i++) {
+ struct sensor_data *sensor = &scmi_sensors->data[idx];
+
+ sensor->info = handle->sensor_ops->info_get(handle, i);
+ if (!sensor->info)
+ return PTR_ERR(sensor->info);
+
+ switch (sensor->info->type) {
+ case TEMPERATURE_C:
+ snprintf(sensor->input, sizeof(sensor->input),
+ "temp%d_input", num_temp + 1);
+ snprintf(sensor->label, sizeof(sensor->input),
+ "temp%d_label", num_temp + 1);
+ num_temp++;
+ break;
+ case VOLTAGE:
+ snprintf(sensor->input, sizeof(sensor->input),
+ "in%d_input", num_volt);
+ snprintf(sensor->label, sizeof(sensor->input),
+ "in%d_label", num_volt);
+ num_volt++;
+ break;
+ case CURRENT:
+ snprintf(sensor->input, sizeof(sensor->input),
+ "curr%d_input", num_current + 1);
+ snprintf(sensor->label, sizeof(sensor->input),
+ "curr%d_label", num_current + 1);
+ num_current++;
+ break;
+ case POWER:
+ snprintf(sensor->input, sizeof(sensor->input),
+ "power%d_input", num_power + 1);
+ snprintf(sensor->label, sizeof(sensor->input),
+ "power%d_label", num_power + 1);
+ num_power++;
+ break;
+ case ENERGY:
+ snprintf(sensor->input, sizeof(sensor->input),
+ "energy%d_input", num_energy + 1);
+ snprintf(sensor->label, sizeof(sensor->input),
+ "energy%d_label", num_energy + 1);
+ num_energy++;
+ break;
+ default:
+ continue;
+ }
+
+ sensor->dev_attr_input.attr.mode = S_IRUGO;
+ sensor->dev_attr_input.show = scmi_show_sensor;
+ sensor->dev_attr_input.attr.name = sensor->input;
+
+ sensor->dev_attr_label.attr.mode = S_IRUGO;
+ sensor->dev_attr_label.show = scmi_show_label;
+ sensor->dev_attr_label.attr.name = sensor->label;
+
+ scmi_sensors->attrs[idx << 1] = &sensor->dev_attr_input.attr;
+ scmi_sensors->attrs[(idx << 1) + 1] =
+ &sensor->dev_attr_label.attr;
+
+ sysfs_attr_init(scmi_sensors->attrs[idx << 1]);
+ sysfs_attr_init(scmi_sensors->attrs[(idx << 1) + 1]);
+ idx++;
+ }
+
+ scmi_sensors->group.attrs = scmi_sensors->attrs;
+ scmi_sensors->groups[0] = &scmi_sensors->group;
+
+ platform_set_drvdata(pdev, scmi_sensors);
+
+ hwdev = devm_hwmon_device_register_with_groups(dev, "scmi_sensors",
+ scmi_sensors,
+ scmi_sensors->groups);
+
+ if (IS_ERR(hwdev))
+ return PTR_ERR(hwdev);
+
+ /*
+ * Register the temperature sensors with the thermal framework
+ * to allow their usage in setting up the thermal zones from
+ * device tree.
+ *
+ * NOTE: Not all temperature sensors maybe used for thermal
+ * control
+ */
+ INIT_LIST_HEAD(&scmi_sensors->thermal_zones);
+ for (i = 0; i < nr_sensors; i++) {
+ struct sensor_data *sensor = &scmi_sensors->data[i];
+ const struct scmi_sensor_info *info = sensor->info;
+ struct thermal_zone_device *z;
+ struct scmi_thermal_zone *zone;
+
+ if (info->type != TEMPERATURE_C)
+ continue;
+
+ zone = devm_kzalloc(dev, sizeof(*zone), GFP_KERNEL);
+ if (!zone)
+ return -ENOMEM;
+
+ zone->sensor_id = i;
+ zone->scmi_sensors = scmi_sensors;
+ z = devm_thermal_zone_of_sensor_register(dev, info->id, zone,
+ &scmi_sensor_ops);
+ /*
+ * The call to thermal_zone_of_sensor_register returns
+ * an error for sensors that are not associated with
+ * any thermal zones or if the thermal subsystem is
+ * not configured.
+ */
+ if (IS_ERR(z)) {
+ devm_kfree(dev, zone);
+ continue;
+ }
+ }
+
+ return 0;
+}
+
+static struct platform_driver scmi_hwmon_platdrv = {
+ .driver = {
+ .name = "scmi-hwmon",
+ },
+ .probe = scmi_hwmon_probe,
+};
+module_platform_driver(scmi_hwmon_platdrv);
+
+MODULE_AUTHOR("Punit Agrawal <[email protected]>");
+MODULE_DESCRIPTION("ARM SCMI HWMON interface driver");
+MODULE_LICENSE("GPL v2");
--
2.7.4
The cpufreq core provides option for drivers to implement fast_switch
callback which is invoked for frequency switching from interrupt context.
This patch adds support for fast_switch callback in SCMI cpufreq driver
by making use of polling based SCMI transfer. It also sets the flag
fast_switch_possible.
Cc: "Rafael J. Wysocki" <[email protected]>
Cc: Viresh Kumar <[email protected]>
Cc: [email protected]
Signed-off-by: Sudeep Holla <[email protected]>
---
drivers/cpufreq/scmi-cpufreq.c | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/drivers/cpufreq/scmi-cpufreq.c b/drivers/cpufreq/scmi-cpufreq.c
index 034359cafea5..cb1084cb1ef1 100644
--- a/drivers/cpufreq/scmi-cpufreq.c
+++ b/drivers/cpufreq/scmi-cpufreq.c
@@ -61,6 +61,19 @@ scmi_cpufreq_set_target(struct cpufreq_policy *policy, unsigned int index)
return perf_ops->freq_set(priv->handle, priv->domain_id, freq, false);
}
+static unsigned int scmi_cpufreq_fast_switch(struct cpufreq_policy *policy,
+ unsigned int target_freq)
+{
+ struct scmi_data *priv = policy->driver_data;
+ struct scmi_perf_ops *perf_ops = priv->handle->perf_ops;
+
+ if (!perf_ops->freq_set(priv->handle, priv->domain_id,
+ target_freq * 1000, true))
+ return target_freq;
+
+ return CPUFREQ_ENTRY_INVALID;
+}
+
static int
scmi_get_sharing_cpus(struct device *cpu_dev, struct cpumask *cpumask)
{
@@ -164,6 +177,7 @@ static int scmi_cpufreq_init(struct cpufreq_policy *policy)
policy->cpuinfo.transition_latency = latency;
+ policy->fast_switch_possible = true;
return 0;
out_free_cpufreq_table:
@@ -180,6 +194,7 @@ static int scmi_cpufreq_exit(struct cpufreq_policy *policy)
{
struct scmi_data *priv = policy->driver_data;
+ policy->fast_switch_possible = false;
cpufreq_cooling_unregister(priv->cdev);
dev_pm_opp_free_cpufreq_table(priv->cpu_dev, &policy->freq_table);
dev_pm_opp_cpumask_remove_table(policy->related_cpus);
@@ -228,6 +243,7 @@ static struct cpufreq_driver scmi_cpufreq_driver = {
.init = scmi_cpufreq_init,
.exit = scmi_cpufreq_exit,
.ready = scmi_cpufreq_ready,
+ .fast_switch = scmi_cpufreq_fast_switch,
};
static int scmi_cpufreq_probe(struct platform_device *pdev)
--
2.7.4
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. SCMI Message Protocol is used to
communicate with the SCP.
This patch adds a cpufreq driver for such systems using SCMI interface
to drive CPU DVFS.
Cc: "Rafael J. Wysocki" <[email protected]>
Cc: Viresh Kumar <[email protected]>
Cc: [email protected]
Signed-off-by: Sudeep Holla <[email protected]>
---
drivers/cpufreq/Kconfig.arm | 11 ++
drivers/cpufreq/Makefile | 1 +
drivers/cpufreq/scmi-cpufreq.c | 268 +++++++++++++++++++++++++++++++++++++++++
3 files changed, 280 insertions(+)
create mode 100644 drivers/cpufreq/scmi-cpufreq.c
diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm
index 2011fec2d6ad..c34633855bc7 100644
--- a/drivers/cpufreq/Kconfig.arm
+++ b/drivers/cpufreq/Kconfig.arm
@@ -215,6 +215,17 @@ config ARM_SA1100_CPUFREQ
config ARM_SA1110_CPUFREQ
bool
+config ARM_SCMI_CPUFREQ
+ tristate "SCMI based CPUfreq driver"
+ depends on ARM_SCMI_PROTOCOL || COMPILE_TEST
+ select PM_OPP
+ help
+ This adds the CPUfreq driver support for ARM platforms using SCMI
+ protocol for CPU power management.
+
+ This driver uses SCMI Message Protocol driver to interact with the
+ firmware providing the CPU DVFS functionality.
+
config ARM_SCPI_CPUFREQ
tristate "SCPI based CPUfreq driver"
depends on ARM_BIG_LITTLE_CPUFREQ && ARM_SCPI_PROTOCOL && COMMON_CLK_SCPI
diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile
index ab3a42cd29ef..4810b45568d3 100644
--- a/drivers/cpufreq/Makefile
+++ b/drivers/cpufreq/Makefile
@@ -72,6 +72,7 @@ obj-$(CONFIG_ARM_S3C64XX_CPUFREQ) += s3c64xx-cpufreq.o
obj-$(CONFIG_ARM_S5PV210_CPUFREQ) += s5pv210-cpufreq.o
obj-$(CONFIG_ARM_SA1100_CPUFREQ) += sa1100-cpufreq.o
obj-$(CONFIG_ARM_SA1110_CPUFREQ) += sa1110-cpufreq.o
+obj-$(CONFIG_ARM_SCMI_CPUFREQ) += scmi-cpufreq.o
obj-$(CONFIG_ARM_SCPI_CPUFREQ) += scpi-cpufreq.o
obj-$(CONFIG_ARM_SPEAR_CPUFREQ) += spear-cpufreq.o
obj-$(CONFIG_ARM_STI_CPUFREQ) += sti-cpufreq.o
diff --git a/drivers/cpufreq/scmi-cpufreq.c b/drivers/cpufreq/scmi-cpufreq.c
new file mode 100644
index 000000000000..034359cafea5
--- /dev/null
+++ b/drivers/cpufreq/scmi-cpufreq.c
@@ -0,0 +1,268 @@
+/*
+ * System Control and Power Interface (SCMI) based CPUFreq Interface driver
+ *
+ * Copyright (C) 2017 ARM Ltd.
+ * Sudeep Holla <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/cpu.h>
+#include <linux/cpufreq.h>
+#include <linux/cpumask.h>
+#include <linux/cpu_cooling.h>
+#include <linux/export.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pm_opp.h>
+#include <linux/slab.h>
+#include <linux/scmi_protocol.h>
+#include <linux/types.h>
+
+struct scmi_data {
+ int domain_id;
+ struct device *cpu_dev;
+ struct thermal_cooling_device *cdev;
+ const struct scmi_handle *handle;
+};
+
+static const struct scmi_handle *handle;
+
+unsigned int scmi_cpufreq_get_rate(unsigned int cpu)
+{
+ int ret;
+ unsigned long rate;
+ struct cpufreq_policy *policy = cpufreq_cpu_get_raw(cpu);
+ struct scmi_data *priv = policy->driver_data;
+ struct scmi_perf_ops *perf_ops = priv->handle->perf_ops;
+
+ ret = perf_ops->freq_get(priv->handle, priv->domain_id, &rate, false);
+ if (ret)
+ return CPUFREQ_ENTRY_INVALID;
+ return rate / 1000;
+}
+
+static int
+scmi_cpufreq_set_target(struct cpufreq_policy *policy, unsigned int index)
+{
+ struct scmi_data *priv = policy->driver_data;
+ struct scmi_perf_ops *perf_ops = priv->handle->perf_ops;
+ u64 freq = policy->freq_table[index].frequency * 1000;
+
+ return perf_ops->freq_set(priv->handle, priv->domain_id, freq, false);
+}
+
+static int
+scmi_get_sharing_cpus(struct device *cpu_dev, struct cpumask *cpumask)
+{
+ int cpu, domain, ret = 0;
+ struct device *tcpu_dev;
+
+ domain = handle->perf_ops->device_domain_id(cpu_dev);
+ if (domain < 0)
+ return domain;
+
+ cpumask_set_cpu(cpu_dev->id, cpumask);
+
+ for_each_possible_cpu(cpu) {
+ if (cpu == cpu_dev->id)
+ continue;
+
+ tcpu_dev = get_cpu_device(cpu);
+ if (!tcpu_dev)
+ continue;
+
+ ret = handle->perf_ops->device_domain_id(tcpu_dev);
+ if (ret == domain)
+ cpumask_set_cpu(cpu, cpumask);
+ }
+
+ return 0;
+}
+
+static int scmi_cpufreq_init(struct cpufreq_policy *policy)
+{
+ int ret;
+ unsigned int latency;
+ struct device *cpu_dev;
+ struct scmi_data *priv;
+ struct cpufreq_frequency_table *freq_table;
+
+ cpu_dev = get_cpu_device(policy->cpu);
+ if (!cpu_dev) {
+ pr_err("failed to get cpu%d device\n", policy->cpu);
+ return -ENODEV;
+ }
+
+ ret = handle->perf_ops->add_opps_to_device(cpu_dev);
+ if (ret) {
+ dev_warn(cpu_dev, "failed to add opps to the device\n");
+ return ret;
+ }
+
+ ret = scmi_get_sharing_cpus(cpu_dev, policy->cpus);
+ if (ret) {
+ dev_warn(cpu_dev, "failed to get sharing cpumask\n");
+ return ret;
+ }
+
+ ret = dev_pm_opp_set_sharing_cpus(cpu_dev, policy->cpus);
+ if (ret) {
+ dev_err(cpu_dev, "%s: failed to mark OPPs as shared: %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ /*
+ * But we need OPP table to function so if it is not there let's
+ * give platform code chance to provide it for us.
+ */
+ ret = dev_pm_opp_get_opp_count(cpu_dev);
+ if (ret <= 0) {
+ dev_dbg(cpu_dev, "OPP table is not ready, deferring probe\n");
+ ret = -EPROBE_DEFER;
+ goto out_free_opp;
+ }
+
+ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+ if (!priv) {
+ ret = -ENOMEM;
+ goto out_free_opp;
+ }
+
+ ret = dev_pm_opp_init_cpufreq_table(cpu_dev, &freq_table);
+ if (ret) {
+ dev_err(cpu_dev, "failed to init cpufreq table: %d\n", ret);
+ goto out_free_priv;
+ }
+
+ priv->handle = handle;
+ priv->cpu_dev = cpu_dev;
+ priv->domain_id = handle->perf_ops->device_domain_id(cpu_dev);
+
+ policy->driver_data = priv;
+
+ ret = cpufreq_table_validate_and_show(policy, freq_table);
+ if (ret) {
+ dev_err(cpu_dev, "%s: invalid frequency table: %d\n", __func__,
+ ret);
+ goto out_free_cpufreq_table;
+ }
+
+ latency = handle->perf_ops->get_transition_latency(cpu_dev);
+ if (!latency)
+ latency = CPUFREQ_ETERNAL;
+
+ policy->cpuinfo.transition_latency = latency;
+
+ return 0;
+
+out_free_cpufreq_table:
+ dev_pm_opp_free_cpufreq_table(cpu_dev, &freq_table);
+out_free_priv:
+ kfree(priv);
+out_free_opp:
+ dev_pm_opp_cpumask_remove_table(policy->cpus);
+
+ return ret;
+}
+
+static int scmi_cpufreq_exit(struct cpufreq_policy *policy)
+{
+ struct scmi_data *priv = policy->driver_data;
+
+ cpufreq_cooling_unregister(priv->cdev);
+ dev_pm_opp_free_cpufreq_table(priv->cpu_dev, &policy->freq_table);
+ dev_pm_opp_cpumask_remove_table(policy->related_cpus);
+ kfree(priv);
+
+ return 0;
+}
+
+static void scmi_cpufreq_ready(struct cpufreq_policy *policy)
+{
+ struct scmi_data *priv = policy->driver_data;
+ struct device_node *np = of_node_get(priv->cpu_dev->of_node);
+
+ if (WARN_ON(!np))
+ return;
+
+ if (of_find_property(np, "#cooling-cells", NULL)) {
+ u32 pcoeff = 0;
+
+ of_property_read_u32(np, "dynamic-power-coefficient",
+ &pcoeff);
+
+ priv->cdev = of_cpufreq_power_cooling_register(np, policy,
+ pcoeff, NULL);
+ if (IS_ERR(priv->cdev)) {
+ dev_err(priv->cpu_dev,
+ "running cpufreq without cooling device: %ld\n",
+ PTR_ERR(priv->cdev));
+
+ priv->cdev = NULL;
+ }
+ }
+
+ of_node_put(np);
+}
+
+static struct cpufreq_driver scmi_cpufreq_driver = {
+ .name = "scmi",
+ .flags = CPUFREQ_STICKY |
+ CPUFREQ_HAVE_GOVERNOR_PER_POLICY |
+ CPUFREQ_NEED_INITIAL_FREQ_CHECK,
+ .verify = cpufreq_generic_frequency_table_verify,
+ .attr = cpufreq_generic_attr,
+ .target_index = scmi_cpufreq_set_target,
+ .get = scmi_cpufreq_get_rate,
+ .init = scmi_cpufreq_init,
+ .exit = scmi_cpufreq_exit,
+ .ready = scmi_cpufreq_ready,
+};
+
+static int scmi_cpufreq_probe(struct platform_device *pdev)
+{
+ int ret;
+
+ handle = devm_scmi_handle_get(&pdev->dev);
+
+ if (IS_ERR_OR_NULL(handle) || !handle->perf_ops)
+ return -EPROBE_DEFER;
+
+ ret = cpufreq_register_driver(&scmi_cpufreq_driver);
+ if (ret) {
+ dev_err(&pdev->dev, "%s: registering cpufreq failed, err: %d\n",
+ __func__, ret);
+ }
+
+ return ret;
+}
+
+static int scmi_cpufreq_remove(struct platform_device *pdev)
+{
+ cpufreq_unregister_driver(&scmi_cpufreq_driver);
+ return 0;
+}
+
+static struct platform_driver scmi_cpufreq_platdrv = {
+ .driver = {
+ .name = "scmi-cpufreq",
+ },
+ .probe = scmi_cpufreq_probe,
+ .remove = scmi_cpufreq_remove,
+};
+module_platform_driver(scmi_cpufreq_platdrv);
+
+MODULE_AUTHOR("Sudeep Holla <[email protected]>");
+MODULE_DESCRIPTION("ARM SCMI CPUFreq interface driver");
+MODULE_LICENSE("GPL v2");
--
2.7.4
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 Management Interface(SCMI) 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 SCMI
protocol.
Cc: Michael Turquette <[email protected]>
Cc: Stephen Boyd <[email protected]>
Cc: [email protected]
Signed-off-by: Sudeep Holla <[email protected]>
---
drivers/clk/Kconfig | 10 +++
drivers/clk/Makefile | 1 +
drivers/clk/clk-scmi.c | 216 +++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 227 insertions(+)
create mode 100644 drivers/clk/clk-scmi.c
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index 68ca2d9fcd73..68b442b2c742 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -55,6 +55,16 @@ config COMMON_CLK_HI655X
multi-function device has one fixed-rate oscillator, clocked
at 32KHz.
+config COMMON_CLK_SCMI
+ tristate "Clock driver controlled via SCMI interface"
+ depends on ARM_SCMI_PROTOCOL || COMPILE_TEST
+ ---help---
+ This driver provides support for clocks that are controlled
+ by firmware that implements the SCMI interface.
+
+ This driver uses SCMI Message Protocol to interact with the
+ firmware providing all the clock controls.
+
config COMMON_CLK_SCPI
tristate "Clock driver controlled via SCPI interface"
depends on ARM_SCPI_PROTOCOL || COMPILE_TEST
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index cd376b3fb47a..74a8ae081923 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -39,6 +39,7 @@ obj-$(CONFIG_CLK_QORIQ) += clk-qoriq.o
obj-$(CONFIG_COMMON_CLK_RK808) += clk-rk808.o
obj-$(CONFIG_COMMON_CLK_HI655X) += clk-hi655x.o
obj-$(CONFIG_COMMON_CLK_S2MPS11) += clk-s2mps11.o
+obj-$(CONFIG_COMMON_CLK_SCMI) += clk-scmi.o
obj-$(CONFIG_COMMON_CLK_SCPI) += clk-scpi.o
obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o
obj-$(CONFIG_COMMON_CLK_SI514) += clk-si514.o
diff --git a/drivers/clk/clk-scmi.c b/drivers/clk/clk-scmi.c
new file mode 100644
index 000000000000..37f98a6439a0
--- /dev/null
+++ b/drivers/clk/clk-scmi.c
@@ -0,0 +1,216 @@
+/*
+ * System Control and Power Interface (SCMI) Protocol based clock driver
+ *
+ * Copyright (C) 2017 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/clk-provider.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/of.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/scmi_protocol.h>
+
+struct scmi_clk {
+ u32 id;
+ struct clk_hw hw;
+ const struct scmi_clock_info *info;
+ const struct scmi_handle *handle;
+};
+
+#define to_scmi_clk(clk) container_of(clk, struct scmi_clk, hw)
+
+static unsigned long scmi_clk_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ int ret;
+ u64 rate;
+ struct scmi_clk *clk = to_scmi_clk(hw);
+
+ ret = clk->handle->clk_ops->rate_get(clk->handle, clk->id, &rate);
+ if (ret)
+ return 0;
+ return rate;
+}
+
+static long scmi_clk_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ u64 fmin, fmax, ftmp;
+ struct scmi_clk *clk = to_scmi_clk(hw);
+
+ /*
+ * We can't figure out what rate it will be, so just return the
+ * rate back to the caller. scmi_clk_recalc_rate() will be called
+ * after the rate is set and we'll know what rate the clock is
+ * running at then.
+ */
+ if (clk->info->rate_discrete)
+ return rate;
+
+ fmin = clk->info->range.min_rate;
+ fmax = clk->info->range.max_rate;
+ for (ftmp = fmin; ftmp <= fmax; ftmp += clk->info->range.step_size) {
+ if (ftmp >= rate) {
+ if (ftmp <= fmax)
+ fmax = ftmp;
+ break;
+ } else if (ftmp >= fmin) {
+ fmin = ftmp;
+ }
+ }
+ return fmax != clk->info->range.max_rate ? fmax : fmin;
+}
+
+static int scmi_clk_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct scmi_clk *clk = to_scmi_clk(hw);
+
+ return clk->handle->clk_ops->rate_set(clk->handle, clk->id, 0, rate);
+}
+
+static int scmi_clk_enable(struct clk_hw *hw)
+{
+ struct scmi_clk *clk = to_scmi_clk(hw);
+
+ return clk->handle->clk_ops->enable(clk->handle, clk->id);
+}
+
+static void scmi_clk_disable(struct clk_hw *hw)
+{
+ struct scmi_clk *clk = to_scmi_clk(hw);
+
+ clk->handle->clk_ops->disable(clk->handle, clk->id);
+}
+
+static const struct clk_ops scmi_clk_ops = {
+ .recalc_rate = scmi_clk_recalc_rate,
+ .round_rate = scmi_clk_round_rate,
+ .set_rate = scmi_clk_set_rate,
+ /*
+ * We can't provide enable/disable callback as we can't perform the same
+ * in atomic context. Since the clock framework provides standard API
+ * clk_prepare_enable that helps cases using clk_enable in non-atomic
+ * context, it should be fine providing prepare/unprepare.
+ */
+ .prepare = scmi_clk_enable,
+ .unprepare = scmi_clk_disable,
+};
+
+static int scmi_clk_ops_init(struct device *dev, struct scmi_clk *sclk)
+{
+ int ret;
+ struct clk_init_data init;
+
+ init.flags = CLK_GET_RATE_NOCACHE;
+ init.num_parents = 0;
+ init.ops = &scmi_clk_ops;
+ init.name = sclk->info->name;
+ sclk->hw.init = &init;
+
+ ret = devm_clk_hw_register(dev, &sclk->hw);
+ if (!ret)
+ clk_hw_set_rate_range(&sclk->hw, sclk->info->range.min_rate,
+ sclk->info->range.max_rate);
+ return ret;
+}
+
+static int scmi_clk_add(struct device *dev, struct device_node *np,
+ const struct scmi_handle *handle)
+{
+ int idx, count, err;
+ struct clk_hw **hws;
+ struct clk_hw_onecell_data *clk_data;
+
+ count = handle->clk_ops->count_get(handle);
+ if (count < 0) {
+ dev_err(dev, "%s: invalid clock output count\n", np->name);
+ return -EINVAL;
+ }
+
+ clk_data = devm_kzalloc(dev, sizeof(*clk_data) +
+ sizeof(*clk_data->hws) * count, GFP_KERNEL);
+ if (!clk_data)
+ return -ENOMEM;
+
+ clk_data->num = count;
+ hws = clk_data->hws;
+
+ for (idx = 0; idx < count; idx++) {
+ struct scmi_clk *sclk;
+
+ sclk = devm_kzalloc(dev, sizeof(*sclk), GFP_KERNEL);
+ if (!sclk)
+ return -ENOMEM;
+
+ sclk->info = handle->clk_ops->info_get(handle, idx);
+ if (!sclk->info) {
+ dev_dbg(dev, "invalid clock info for idx %d\n", idx);
+ continue;
+ }
+
+ sclk->id = idx;
+ sclk->handle = handle;
+
+ err = scmi_clk_ops_init(dev, sclk);
+ if (err) {
+ dev_err(dev, "failed to register clock %d\n", idx);
+ devm_kfree(dev, sclk);
+ hws[idx] = NULL;
+ } else {
+ dev_dbg(dev, "Registered clock:%s\n", sclk->info->name);
+ hws[idx] = &sclk->hw;
+ }
+ }
+
+ return of_clk_add_hw_provider(np, of_clk_hw_onecell_get, clk_data);
+}
+
+static int scmi_clocks_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+
+ of_clk_del_provider(np);
+ return 0;
+}
+
+static int scmi_clocks_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ const struct scmi_handle *handle = devm_scmi_handle_get(dev);
+
+ if (IS_ERR_OR_NULL(handle) || !handle->clk_ops)
+ return -EPROBE_DEFER;
+
+ return scmi_clk_add(dev, np, handle);
+}
+
+static struct platform_driver scmi_clocks_driver = {
+ .driver = {
+ .name = "scmi-clocks",
+ },
+ .probe = scmi_clocks_probe,
+ .remove = scmi_clocks_remove,
+};
+module_platform_driver(scmi_clocks_driver);
+
+MODULE_AUTHOR("Sudeep Holla <[email protected]>");
+MODULE_DESCRIPTION("ARM SCMI clock driver");
+MODULE_LICENSE("GPL v2");
--
2.7.4
In order to maintain the channel information per protocol, we need
some sort of list or hashtable to hold all this information. IDR
provides sparse array mapping of small integer ID numbers onto arbitrary
pointers. In this case the arbitrary pointers can be pointers to the
channel information.
This patch adds support for per-protocol channels using those idr
objects.
Cc: Arnd Bergmann <[email protected]>
Signed-off-by: Sudeep Holla <[email protected]>
---
drivers/firmware/arm_scmi/driver.c | 50 +++++++++++++++++++++++++++++---------
1 file changed, 39 insertions(+), 11 deletions(-)
diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c
index 3eaf619858bb..b59cbdde0237 100644
--- a/drivers/firmware/arm_scmi/driver.c
+++ b/drivers/firmware/arm_scmi/driver.c
@@ -112,6 +112,8 @@ struct scmi_desc {
* @tx_payload: Transmit mailbox channel payload area
* @rx_payload: Receive mailbox channel payload area
* @info: Reference to SCMI instance corresponding to this channel
+ * @dev: Reference to device in the SCMI hierarchy corresponding to this
+ * channel
*/
struct scmi_chan_info {
struct mbox_client cl;
@@ -120,6 +122,7 @@ struct scmi_chan_info {
void __iomem *tx_payload;
void __iomem *rx_payload;
struct scmi_info *info;
+ struct device *dev;
};
/**
@@ -131,7 +134,7 @@ struct scmi_chan_info {
* @version: SCMI revision information containing protocol version,
* implementation version and (sub-)vendor identification.
* @minfo: Message info
- * @cinfo: Reference to SCMI channel information
+ * @idr: IDR object to map protocol id to channel info pointer
* @protocols_imp: list of protocols implemented
* @node: list head
* @users: Number of users of this instance
@@ -142,7 +145,7 @@ struct scmi_info {
struct scmi_revision_info version;
struct scmi_handle handle;
struct scmi_xfers_info minfo;
- struct scmi_chan_info *cinfo;
+ struct idr idr;
u8 *protocols_imp;
struct list_head node;
int users;
@@ -412,7 +415,11 @@ int scmi_do_xfer(const struct scmi_handle *handle, struct scmi_xfer *xfer)
int timeout;
struct scmi_info *info = handle_to_scmi_info(handle);
struct device *dev = info->dev;
- struct scmi_chan_info *cinfo = info->cinfo;
+ struct scmi_chan_info *cinfo;
+
+ cinfo = idr_find(&info->idr, xfer->hdr.protocol_id);
+ if (unlikely(!cinfo))
+ return -EINVAL;
ret = mbox_send_message(cinfo->tx_chan, xfer);
if (ret < 0) {
@@ -772,8 +779,11 @@ static int scmi_mailbox_check(struct device_node *np)
return of_parse_phandle_with_args(np, "mboxes", "#mbox-cells", 0, &arg);
}
-static int scmi_mbox_free_channel(struct scmi_chan_info *cinfo)
+static int scmi_mbox_free_channel(int id, void *p, void *data)
{
+ struct scmi_chan_info *cinfo = p;
+ struct idr *idr = data;
+
if (!IS_ERR_OR_NULL(cinfo->tx_chan)) {
mbox_free_channel(cinfo->tx_chan);
cinfo->tx_chan = NULL;
@@ -783,6 +793,8 @@ static int scmi_mbox_free_channel(struct scmi_chan_info *cinfo)
cinfo->rx_chan = NULL;
}
+ idr_remove(idr, id);
+
return 0;
}
@@ -790,6 +802,7 @@ static int scmi_remove(struct platform_device *pdev)
{
int ret = 0;
struct scmi_info *info = platform_get_drvdata(pdev);
+ struct idr *idr = &info->idr;
of_platform_depopulate(&pdev->dev);
@@ -800,28 +813,34 @@ static int scmi_remove(struct platform_device *pdev)
list_del(&info->node);
mutex_unlock(&scmi_list_mutex);
- if (!ret)
+ if (!ret) {
/* Safe to free channels since no more users */
- return scmi_mbox_free_channel(info->cinfo);
+ ret = idr_for_each(idr, scmi_mbox_free_channel, idr);
+ idr_destroy(&info->idr);
+ }
return ret;
}
-static inline int scmi_mbox_chan_setup(struct scmi_info *info)
+static inline int
+scmi_mbox_chan_setup(struct scmi_info *info, struct device *dev, int prot_id)
{
int ret;
struct resource res;
resource_size_t size;
- struct device *dev = info->dev;
struct device_node *shmem, *np = dev->of_node;
struct scmi_chan_info *cinfo;
struct mbox_client *cl;
+ if (scmi_mailbox_check(np)) {
+ cinfo = idr_find(&info->idr, SCMI_PROTOCOL_BASE);
+ goto idr_alloc;
+ }
+
cinfo = devm_kzalloc(info->dev, sizeof(*cinfo), GFP_KERNEL);
if (!cinfo)
return -ENOMEM;
- info->cinfo = cinfo;
cinfo->info = info;
cl = &cinfo->cl;
@@ -854,6 +873,13 @@ static inline int scmi_mbox_chan_setup(struct scmi_info *info)
return ret;
}
+idr_alloc:
+ ret = idr_alloc(&info->idr, cinfo, prot_id, prot_id + 1, GFP_KERNEL);
+ if (ret != prot_id) {
+ dev_err(dev, "unable to allocate SCMI idr slot err %d\n", ret);
+ return ret;
+ }
+
return 0;
}
@@ -887,19 +913,19 @@ static int scmi_probe(struct platform_device *pdev)
return ret;
platform_set_drvdata(pdev, info);
+ idr_init(&info->idr);
handle = &info->handle;
handle->dev = info->dev;
handle->version = &info->version;
- ret = scmi_mbox_chan_setup(info);
+ ret = scmi_mbox_chan_setup(info, dev, SCMI_PROTOCOL_BASE);
if (ret)
return ret;
ret = scmi_base_protocol_init(handle);
if (ret) {
dev_err(dev, "unable to communicate with SCMI(%d)\n", ret);
- scmi_mbox_free_channel(info->cinfo);
return ret;
}
@@ -931,6 +957,8 @@ static int scmi_probe(struct platform_device *pdev)
continue;
}
+ scmi_mbox_chan_setup(info, &cdev->dev, prot_id);
+
init_ret = match->fn(handle);
if (init_ret) {
dev_err(dev, "SCMI protocol %d init error %d\n",
--
2.7.4
It would be useful to have options to perform some SCMI transfers
atomically by polling for the completion flag instead of interrupt
driven. The SCMI specification has option to disable the interrupt and
poll for the completion flag in the shared memory.
This patch adds support for polling based SCMI transfers using that
option. This might be used for uninterrupted/atomic DVFS operations
from the scheduler context.
Cc: Arnd Bergmann <[email protected]>
Signed-off-by: Sudeep Holla <[email protected]>
---
drivers/firmware/arm_scmi/driver.c | 40 ++++++++++++++++++++++++++++++--------
1 file changed, 32 insertions(+), 8 deletions(-)
diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c
index 6f31761043e2..8257315cf3eb 100644
--- a/drivers/firmware/arm_scmi/driver.c
+++ b/drivers/firmware/arm_scmi/driver.c
@@ -26,6 +26,7 @@
*/
#include <linux/bitmap.h>
+#include <linux/delay.h>
#include <linux/export.h>
#include <linux/io.h>
#include <linux/kernel.h>
@@ -370,6 +371,19 @@ void scmi_one_xfer_put(const struct scmi_handle *handle, struct scmi_xfer *xfer)
up(&minfo->sem_xfer_count);
}
+static bool
+scmi_xfer_poll_done(const struct scmi_info *info, struct scmi_xfer *xfer)
+{
+ struct scmi_shared_mem *mem = info->tx_payload;
+ u16 xfer_id = MSG_XTRACT_TOKEN(mem->msg_header);
+
+ if (xfer->hdr.seq != xfer_id)
+ return false;
+
+ return mem->channel_status & (SCMI_SHMEM_CHAN_STAT_CHANNEL_ERROR |
+ SCMI_SHMEM_CHAN_STAT_CHANNEL_FREE);
+}
+
/**
* scmi_do_xfer() - Do one transfer
*
@@ -396,14 +410,24 @@ int scmi_do_xfer(const struct scmi_handle *handle, struct scmi_xfer *xfer)
/* mbox_send_message returns non-negative value on success, so reset */
ret = 0;
- /* And we wait for the response. */
- timeout = msecs_to_jiffies(info->desc->max_rx_timeout_ms);
- if (!wait_for_completion_timeout(&xfer->done, timeout)) {
- dev_err(dev, "mbox timed out in resp(caller: %pF)\n",
- (void *)_RET_IP_);
- ret = -ETIMEDOUT;
- } else if (xfer->hdr.status) {
- ret = scmi_to_linux_errno(xfer->hdr.status);
+ if (xfer->hdr.poll_completion) {
+ timeout = info->desc->max_rx_timeout_ms * 100;
+ while (!scmi_xfer_poll_done(info, xfer) && timeout--)
+ udelay(10);
+ if (timeout)
+ scmi_fetch_response(xfer, info->tx_payload);
+ else
+ ret = -ETIMEDOUT;
+ } else {
+ /* And we wait for the response. */
+ timeout = msecs_to_jiffies(info->desc->max_rx_timeout_ms);
+ if (!wait_for_completion_timeout(&xfer->done, timeout)) {
+ dev_err(dev, "mbox timed out in resp(caller: %pF)\n",
+ (void *)_RET_IP_);
+ ret = -ETIMEDOUT;
+ } else if (xfer->hdr.status) {
+ ret = scmi_to_linux_errno(xfer->hdr.status);
+ }
}
/*
* NOTE: we might prefer not to need the mailbox ticker to manage the
--
2.7.4
The sensor protocol provides functions to manage platform sensors, and
provides the commands to describe the protocol version and the various
attribute flags. It also provides commands to discover various sensors
implemented and managed by the platform, read any sensor synchronously
or asynchronously as allowed by the platform, program sensor attributes
and/or configurations, if applicable.
This patch adds support for most of the above features.
Cc: Arnd Bergmann <[email protected]>
Signed-off-by: Sudeep Holla <[email protected]>
---
drivers/firmware/arm_scmi/Makefile | 2 +-
drivers/firmware/arm_scmi/sensors.c | 287 ++++++++++++++++++++++++++++++++++++
include/linux/scmi_protocol.h | 40 +++++
3 files changed, 328 insertions(+), 1 deletion(-)
create mode 100644 drivers/firmware/arm_scmi/sensors.c
diff --git a/drivers/firmware/arm_scmi/Makefile b/drivers/firmware/arm_scmi/Makefile
index 52ecc08556a2..f9dee5ad0aa0 100644
--- a/drivers/firmware/arm_scmi/Makefile
+++ b/drivers/firmware/arm_scmi/Makefile
@@ -1,2 +1,2 @@
obj-$(CONFIG_ARM_SCMI_PROTOCOL) = arm_scmi.o
-arm_scmi-y = base.o clock.o driver.o perf.o power.o
+arm_scmi-y = base.o clock.o driver.o perf.o power.o sensors.o
diff --git a/drivers/firmware/arm_scmi/sensors.c b/drivers/firmware/arm_scmi/sensors.c
new file mode 100644
index 000000000000..43400558f7e1
--- /dev/null
+++ b/drivers/firmware/arm_scmi/sensors.c
@@ -0,0 +1,287 @@
+/*
+ * System Control and Management Interface (SCMI) Sensor Protocol
+ *
+ * Copyright (C) 2017 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 "common.h"
+
+enum scmi_sensor_protocol_cmd {
+ SENSOR_DESCRIPTION_GET = 0x3,
+ SENSOR_CONFIG_SET = 0x4,
+ SENSOR_TRIP_POINT_SET = 0x5,
+ SENSOR_READING_GET = 0x6,
+};
+
+struct scmi_msg_resp_sensor_attributes {
+ __le16 num_sensors;
+ u8 max_requests;
+ u8 reserved;
+ __le32 reg_addr_low;
+ __le32 reg_addr_high;
+ __le32 reg_size;
+};
+
+struct scmi_msg_resp_sensor_description {
+ __le16 num_returned;
+ __le16 num_remaining;
+ struct {
+ __le32 id;
+ __le32 attributes_low;
+#define SUPPORTS_ASYNC_READ(x) ((x) & BIT(31))
+#define NUM_TRIP_POINTS(x) (((x) >> 4) & 0xff)
+ __le32 attributes_high;
+#define SENSOR_TYPE(x) ((x) & 0xff)
+#define SENSOR_SCALE(x) (((x) >> 11) & 0x3f)
+#define SENSOR_UPDATE_SCALE(x) (((x) >> 22) & 0x1f)
+#define SENSOR_UPDATE_BASE(x) (((x) >> 27) & 0x1f)
+ u8 name[SCMI_MAX_STR_SIZE];
+ } desc[0];
+};
+
+struct scmi_msg_set_sensor_config {
+ __le32 id;
+ __le32 event_control;
+};
+
+struct scmi_msg_set_sensor_trip_point {
+ __le32 id;
+ __le32 event_control;
+#define SENSOR_TP_EVENT_MASK (0x3)
+#define SENSOR_TP_DISABLED 0x0
+#define SENSOR_TP_POSITIVE 0x1
+#define SENSOR_TP_NEGATIVE 0x2
+#define SENSOR_TP_BOTH 0x3
+#define SENSOR_TP_ID(x) (((x) & 0xff) << 4)
+ __le32 value_low;
+ __le32 value_high;
+};
+
+struct scmi_msg_sensor_reading_get {
+ __le32 id;
+ __le32 flags;
+#define SENSOR_READ_ASYNC BIT(0)
+};
+
+struct sensors_info {
+ int num_sensors;
+ int max_requests;
+ u64 reg_addr;
+ u32 reg_size;
+ struct scmi_sensor_info *sensors;
+};
+
+static struct sensors_info sensor_info;
+
+static int scmi_sensor_attributes_get(const struct scmi_handle *handle,
+ struct sensors_info *sensor_info)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_msg_resp_sensor_attributes *attr;
+
+ ret = scmi_one_xfer_init(handle, PROTOCOL_ATTRIBUTES,
+ SCMI_PROTOCOL_SENSOR, 0, sizeof(*attr), &t);
+ if (ret)
+ return ret;
+
+ attr = t->rx.buf;
+
+ ret = scmi_do_xfer(handle, t);
+ if (!ret) {
+ sensor_info->num_sensors = le16_to_cpu(attr->num_sensors);
+ sensor_info->max_requests = le16_to_cpu(attr->max_requests);
+ sensor_info->reg_addr = le32_to_cpu(attr->reg_addr_low) |
+ (u64)le32_to_cpu(attr->reg_addr_high) << 32;
+ sensor_info->reg_size = le32_to_cpu(attr->reg_size);
+ }
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int scmi_sensor_description_get(const struct scmi_handle *handle)
+{
+ int ret, cnt;
+ u32 desc_index = 0;
+ u16 num_returned, num_remaining;
+ struct scmi_xfer *t;
+ struct scmi_msg_resp_sensor_description *buf;
+
+ ret = scmi_one_xfer_init(handle, SENSOR_DESCRIPTION_GET,
+ SCMI_PROTOCOL_SENSOR, sizeof(__le32), 0, &t);
+ if (ret)
+ return ret;
+
+ buf = t->rx.buf;
+
+ do {
+ /* Set the number of sensors to be skipped/already read */
+ *(__le32 *)t->tx.buf = cpu_to_le32(desc_index);
+
+ ret = scmi_do_xfer(handle, t);
+ if (ret)
+ break;
+
+ num_returned = le16_to_cpu(buf->num_returned);
+ num_remaining = le16_to_cpu(buf->num_remaining);
+
+ if (desc_index + num_returned > sensor_info.num_sensors) {
+ dev_err(handle->dev, "No. of sensors can't exceed %d",
+ sensor_info.num_sensors);
+ break;
+ }
+
+ for (cnt = 0; cnt < num_returned; cnt++) {
+ u32 attrh;
+ struct scmi_sensor_info *s;
+
+ attrh = le32_to_cpu(buf->desc[cnt].attributes_high);
+ s = &sensor_info.sensors[desc_index + cnt];
+ s->id = le32_to_cpu(buf->desc[cnt].id);
+ s->type = SENSOR_TYPE(attrh);
+ memcpy(s->name, buf->desc[cnt].name, SCMI_MAX_STR_SIZE);
+ }
+
+ desc_index += num_returned;
+ /*
+ * check for both returned and remaining to avoid infinite
+ * loop due to buggy firmware
+ */
+ } while (num_returned && num_remaining);
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int
+scmi_sensor_configuration_set(const struct scmi_handle *handle, u32 sensor_id)
+{
+ int ret;
+ u32 evt_cntl = BIT(0);
+ struct scmi_xfer *t;
+ struct scmi_msg_set_sensor_config *cfg;
+
+ ret = scmi_one_xfer_init(handle, SENSOR_CONFIG_SET,
+ SCMI_PROTOCOL_SENSOR, sizeof(*cfg), 0, &t);
+ if (ret)
+ return ret;
+
+ cfg = t->tx.buf;
+ cfg->id = cpu_to_le32(sensor_id);
+ cfg->event_control = cpu_to_le32(evt_cntl);
+
+ ret = scmi_do_xfer(handle, t);
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int scmi_sensor_trip_point_set(const struct scmi_handle *handle,
+ u32 sensor_id, u8 trip_id, u64 trip_value)
+{
+ int ret;
+ u32 evt_cntl = SENSOR_TP_BOTH;
+ struct scmi_xfer *t;
+ struct scmi_msg_set_sensor_trip_point *trip;
+
+ ret = scmi_one_xfer_init(handle, SENSOR_TRIP_POINT_SET,
+ SCMI_PROTOCOL_SENSOR, sizeof(*trip), 0, &t);
+ if (ret)
+ return ret;
+
+ trip = t->tx.buf;
+ trip->id = cpu_to_le32(sensor_id);
+ trip->event_control = cpu_to_le32(evt_cntl | SENSOR_TP_ID(trip_id));
+ trip->value_low = cpu_to_le32(trip_value & 0xffffffff);
+ trip->value_high = cpu_to_le32(trip_value >> 32);
+
+ ret = scmi_do_xfer(handle, t);
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int scmi_sensor_reading_get(const struct scmi_handle *handle,
+ u32 sensor_id, bool async, u64 *value)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_msg_sensor_reading_get *sensor;
+
+ ret = scmi_one_xfer_init(handle, SENSOR_READING_GET,
+ SCMI_PROTOCOL_SENSOR, sizeof(*sensor),
+ sizeof(u64), &t);
+ if (ret)
+ return ret;
+
+ sensor = t->tx.buf;
+ sensor->id = cpu_to_le32(sensor_id);
+ sensor->flags = cpu_to_le32(async ? SENSOR_READ_ASYNC : 0);
+
+ ret = scmi_do_xfer(handle, t);
+ if (!ret) {
+ __le32 *pval = t->rx.buf;
+
+ *value = le32_to_cpu(*pval);
+ *value |= (u64)le32_to_cpu(*(pval + 1)) << 32;
+ }
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static const struct scmi_sensor_info *
+scmi_sensor_info_get(const struct scmi_handle *handle, u32 sensor_id)
+{
+ return sensor_info.sensors + sensor_id;
+}
+
+static int scmi_sensor_count_get(const struct scmi_handle *handle)
+{
+ return sensor_info.num_sensors;
+}
+
+static struct scmi_sensor_ops sensor_ops = {
+ .count_get = scmi_sensor_count_get,
+ .info_get = scmi_sensor_info_get,
+ .configuration_set = scmi_sensor_configuration_set,
+ .trip_point_set = scmi_sensor_trip_point_set,
+ .reading_get = scmi_sensor_reading_get,
+};
+
+int scmi_sensors_protocol_init(struct scmi_handle *handle)
+{
+ u32 version;
+
+ scmi_version_get(handle, SCMI_PROTOCOL_SENSOR, &version);
+
+ dev_dbg(handle->dev, "Sensor Version %d.%d\n",
+ PROTOCOL_REV_MAJOR(version), PROTOCOL_REV_MINOR(version));
+
+ scmi_sensor_attributes_get(handle, &sensor_info);
+
+ sensor_info.sensors = devm_kcalloc(handle->dev, sensor_info.num_sensors,
+ sizeof(struct scmi_sensor_info),
+ GFP_KERNEL);
+ if (!sensor_info.sensors)
+ return -ENOMEM;
+
+ scmi_sensor_description_get(handle);
+
+ handle->sensor_ops = &sensor_ops;
+
+ return 0;
+}
diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
index e5a80511f88c..d2a34cc9df8b 100644
--- a/include/linux/scmi_protocol.h
+++ b/include/linux/scmi_protocol.h
@@ -136,6 +136,44 @@ struct scmi_power_ops {
int (*state_notify_enable)(const struct scmi_handle *, u32, bool);
};
+struct scmi_sensor_info {
+ u32 id;
+ u8 type;
+ char name[SCMI_MAX_STR_SIZE];
+};
+
+/*
+ * Partial list from Distributed Management Task Force (DMTF) specification:
+ * DSP0249 (Platform Level Data Model specification)
+ */
+enum scmi_sensor_class {
+ TEMPERATURE_C = 0x2,
+ VOLTAGE = 0x5,
+ CURRENT = 0x6,
+ POWER = 0x7,
+ ENERGY = 0x8,
+};
+
+/**
+ * struct scmi_sensor_ops - represents the various operations provided
+ * by SCMI Sensor Protocol
+ *
+ * @count_get: get the count of sensors provided by SCMI
+ * @info_get: get the information of the specified sensor
+ * @configuration_set: control notifications on cross-over events for
+ * the trip-points
+ * @trip_point_set: selects and configures a trip-point of interest
+ * @reading_get: gets the current value of the sensor
+ */
+struct scmi_sensor_ops {
+ int (*count_get)(const struct scmi_handle *);
+ const struct scmi_sensor_info *(*info_get)(const struct scmi_handle *,
+ u32);
+ int (*configuration_set)(const struct scmi_handle *, u32);
+ int (*trip_point_set)(const struct scmi_handle *, u32, u8, u64);
+ int (*reading_get)(const struct scmi_handle *, u32, bool, u64 *);
+};
+
/**
* struct scmi_handle - Handle returned to ARM SCMI clients for usage.
*
@@ -144,6 +182,7 @@ struct scmi_power_ops {
* @power_ops: pointer to set of power protocol operations
* @perf_ops: pointer to set of performance protocol operations
* @clk_ops: pointer to set of clock protocol operations
+ * @sensor_ops: pointer to set of sensor protocol operations
*/
struct scmi_handle {
struct device *dev;
@@ -151,6 +190,7 @@ struct scmi_handle {
struct scmi_power_ops *power_ops;
struct scmi_perf_ops *perf_ops;
struct scmi_clk_ops *clk_ops;
+ struct scmi_sensor_ops *sensor_ops;
};
#if IS_REACHABLE(CONFIG_ARM_SCMI_PROTOCOL)
--
2.7.4
The power protocol is intended for management of power states of various
power domains. The power domain management protocol provides commands to
describe the protocol version, discover the implementation specific
attributes, set and get the power state of a domain.
This patch adds support for the above mention features of the protocol.
Cc: Arnd Bergmann <[email protected]>
Signed-off-by: Sudeep Holla <[email protected]>
--
drivers/firmware/arm_scmi/Makefile | 2 +-
drivers/firmware/arm_scmi/power.c | 242 +++++++++++++++++++++++++++++++++++++
include/linux/scmi_protocol.h | 28 +++++
3 files changed, 271 insertions(+), 1 deletion(-)
create mode 100644 drivers/firmware/arm_scmi/power.c
---
drivers/firmware/arm_scmi/Makefile | 2 +-
drivers/firmware/arm_scmi/power.c | 242 +++++++++++++++++++++++++++++++++++++
include/linux/scmi_protocol.h | 28 +++++
3 files changed, 271 insertions(+), 1 deletion(-)
create mode 100644 drivers/firmware/arm_scmi/power.c
diff --git a/drivers/firmware/arm_scmi/Makefile b/drivers/firmware/arm_scmi/Makefile
index 6836b1f38f7f..52ecc08556a2 100644
--- a/drivers/firmware/arm_scmi/Makefile
+++ b/drivers/firmware/arm_scmi/Makefile
@@ -1,2 +1,2 @@
obj-$(CONFIG_ARM_SCMI_PROTOCOL) = arm_scmi.o
-arm_scmi-y = base.o clock.o driver.o perf.o
+arm_scmi-y = base.o clock.o driver.o perf.o power.o
diff --git a/drivers/firmware/arm_scmi/power.c b/drivers/firmware/arm_scmi/power.c
new file mode 100644
index 000000000000..34c45f101abb
--- /dev/null
+++ b/drivers/firmware/arm_scmi/power.c
@@ -0,0 +1,242 @@
+/*
+ * System Control and Management Interface (SCMI) Power Protocol
+ *
+ * Copyright (C) 2017 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 "common.h"
+
+enum scmi_power_protocol_cmd {
+ POWER_DOMAIN_ATTRIBUTES = 0x3,
+ POWER_STATE_SET = 0x4,
+ POWER_STATE_GET = 0x5,
+ POWER_STATE_NOTIFY = 0x6,
+};
+
+struct scmi_msg_resp_power_attributes {
+ __le16 num_domains;
+ __le16 reserved;
+ __le32 stats_addr_low;
+ __le32 stats_addr_high;
+ __le32 stats_size;
+};
+
+struct scmi_msg_resp_power_domain_attributes {
+ __le32 flags;
+#define SUPPORTS_STATE_SET_NOTIFY(x) ((x) & BIT(31))
+#define SUPPORTS_STATE_SET_ASYNC(x) ((x) & BIT(30))
+#define SUPPORTS_STATE_SET_SYNC(x) ((x) & BIT(29))
+ u8 name[SCMI_MAX_STR_SIZE];
+};
+
+struct scmi_power_set_state {
+ __le32 flags;
+#define STATE_SET_ASYNC BIT(0)
+ __le32 domain;
+ __le32 state;
+};
+
+struct scmi_power_state_notify {
+ __le32 domain;
+ __le32 notify_enable;
+};
+
+struct power_dom_info {
+ bool state_set_sync;
+ bool state_set_async;
+ bool state_set_notify;
+ char name[SCMI_MAX_STR_SIZE];
+};
+
+struct scmi_power_info {
+ int num_domains;
+ u64 stats_addr;
+ u32 stats_size;
+ struct power_dom_info *dom_info;
+};
+
+static struct scmi_power_info power_info;
+
+static int scmi_power_attributes_get(const struct scmi_handle *handle,
+ struct scmi_power_info *power_info)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_msg_resp_power_attributes *attr;
+
+ ret = scmi_one_xfer_init(handle, PROTOCOL_ATTRIBUTES,
+ SCMI_PROTOCOL_POWER, 0, sizeof(*attr), &t);
+ if (ret)
+ return ret;
+
+ attr = t->rx.buf;
+
+ ret = scmi_do_xfer(handle, t);
+ if (!ret) {
+ power_info->num_domains = le16_to_cpu(attr->num_domains);
+ power_info->stats_addr = le32_to_cpu(attr->stats_addr_low) |
+ (u64)le32_to_cpu(attr->stats_addr_high) << 32;
+ power_info->stats_size = le32_to_cpu(attr->stats_size);
+ }
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int
+scmi_power_domain_attributes_get(const struct scmi_handle *handle, u32 domain,
+ struct power_dom_info *dom_info)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_msg_resp_power_domain_attributes *attr;
+
+ ret = scmi_one_xfer_init(handle, POWER_DOMAIN_ATTRIBUTES,
+ SCMI_PROTOCOL_POWER, sizeof(domain),
+ sizeof(*attr), &t);
+ if (ret)
+ return ret;
+
+ *(__le32 *)t->tx.buf = cpu_to_le32(domain);
+ attr = t->rx.buf;
+
+ ret = scmi_do_xfer(handle, t);
+ if (!ret) {
+ u32 flags = le32_to_cpu(attr->flags);
+
+ dom_info->state_set_notify = SUPPORTS_STATE_SET_NOTIFY(flags);
+ dom_info->state_set_async = SUPPORTS_STATE_SET_ASYNC(flags);
+ dom_info->state_set_sync = SUPPORTS_STATE_SET_SYNC(flags);
+ memcpy(dom_info->name, attr->name, SCMI_MAX_STR_SIZE);
+ }
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int
+scmi_power_state_set(const struct scmi_handle *handle, u32 domain, u32 state)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_power_set_state *st;
+
+ ret = scmi_one_xfer_init(handle, POWER_STATE_SET, SCMI_PROTOCOL_POWER,
+ sizeof(*st), 0, &t);
+ if (ret)
+ return ret;
+
+ st = t->tx.buf;
+ st->flags = cpu_to_le32(0);
+ st->domain = cpu_to_le32(domain);
+ st->state = cpu_to_le32(state);
+
+ ret = scmi_do_xfer(handle, t);
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int
+scmi_power_state_get(const struct scmi_handle *handle, u32 domain, u32 *state)
+{
+ int ret;
+ struct scmi_xfer *t;
+
+ ret = scmi_one_xfer_init(handle, POWER_STATE_GET, SCMI_PROTOCOL_POWER,
+ sizeof(u32), sizeof(u32), &t);
+ if (ret)
+ return ret;
+
+ *(__le32 *)t->tx.buf = cpu_to_le32(domain);
+
+ ret = scmi_do_xfer(handle, t);
+ if (!ret)
+ *state = le32_to_cpu(*(__le32 *)t->rx.buf);
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int scmi_power_state_notify_enable(const struct scmi_handle *handle,
+ u32 domain, bool enable)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_power_state_notify *notify;
+
+ ret = scmi_one_xfer_init(handle, POWER_STATE_NOTIFY,
+ SCMI_PROTOCOL_POWER, sizeof(*notify), 0, &t);
+ if (ret)
+ return ret;
+
+ notify = t->tx.buf;
+ notify->domain = cpu_to_le32(domain);
+ notify->notify_enable = cpu_to_le32(enable & BIT(0));
+
+ ret = scmi_do_xfer(handle, t);
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int scmi_power_num_domains_get(const struct scmi_handle *handle)
+{
+ return power_info.num_domains;
+}
+
+static char *scmi_power_name_get(const struct scmi_handle *handle, u32 domain)
+{
+ struct power_dom_info *dom = power_info.dom_info + domain;
+
+ return dom->name;
+}
+
+static struct scmi_power_ops power_ops = {
+ .num_domains_get = scmi_power_num_domains_get,
+ .name_get = scmi_power_name_get,
+ .state_set = scmi_power_state_set,
+ .state_get = scmi_power_state_get,
+ .state_notify_enable = scmi_power_state_notify_enable,
+};
+
+int scmi_power_protocol_init(struct scmi_handle *handle)
+{
+ u32 version;
+ int domain;
+
+ scmi_version_get(handle, SCMI_PROTOCOL_POWER, &version);
+
+ dev_dbg(handle->dev, "Power Version %d.%d\n",
+ PROTOCOL_REV_MAJOR(version), PROTOCOL_REV_MINOR(version));
+
+ scmi_power_attributes_get(handle, &power_info);
+
+ power_info.dom_info = devm_kcalloc(handle->dev, power_info.num_domains,
+ sizeof(struct power_dom_info),
+ GFP_KERNEL);
+ if (!power_info.dom_info)
+ return -ENOMEM;
+
+ for (domain = 0; domain < power_info.num_domains; domain++) {
+ struct power_dom_info *dom = power_info.dom_info + domain;
+
+ scmi_power_domain_attributes_get(handle, domain, dom);
+ }
+
+ handle->power_ops = &power_ops;
+
+ return 0;
+}
diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
index c15f37c86025..e5a80511f88c 100644
--- a/include/linux/scmi_protocol.h
+++ b/include/linux/scmi_protocol.h
@@ -111,16 +111,44 @@ struct scmi_perf_ops {
};
/**
+ * struct scmi_power_ops - represents the various operations provided
+ * by SCMI Power Protocol
+ *
+ * @num_domains_get: get the count of power domains provided by SCMI
+ * @name_get: gets the name of a power domain
+ * @state_set: sets the power state of a power domain
+ * @state_get: gets the power state of a power domain
+ * @state_notify_enable: request notifications from the platform for
+ * state changes in a specific power domain
+ */
+struct scmi_power_ops {
+ int (*num_domains_get)(const struct scmi_handle *);
+ char *(*name_get)(const struct scmi_handle *, u32);
+#define SCMI_POWER_STATE_TYPE_SHIFT 30
+#define SCMI_POWER_STATE_ID_MASK (BIT(28) - 1)
+#define SCMI_POWER_STATE_PARAM(type, id) \
+ ((((type) & BIT(0)) << SCMI_POWER_STATE_TYPE_SHIFT) | \
+ ((id) & SCMI_POWER_STATE_ID_MASK))
+#define SCMI_POWER_STATE_GENERIC_ON SCMI_POWER_STATE_PARAM(0, 0)
+#define SCMI_POWER_STATE_GENERIC_OFF SCMI_POWER_STATE_PARAM(1, 0)
+ int (*state_set)(const struct scmi_handle *, u32, u32);
+ int (*state_get)(const struct scmi_handle *, u32, u32 *);
+ int (*state_notify_enable)(const struct scmi_handle *, u32, bool);
+};
+
+/**
* struct scmi_handle - Handle returned to ARM SCMI clients for usage.
*
* @dev: pointer to the SCMI device
* @version: pointer to the structure containing SCMI version information
+ * @power_ops: pointer to set of power protocol operations
* @perf_ops: pointer to set of performance protocol operations
* @clk_ops: pointer to set of clock protocol operations
*/
struct scmi_handle {
struct device *dev;
struct scmi_revision_info *version;
+ struct scmi_power_ops *power_ops;
struct scmi_perf_ops *perf_ops;
struct scmi_clk_ops *clk_ops;
};
--
2.7.4
The clock protocol is intended for management of clocks. It is used to
enable or disable clocks, and to set and get the clock rates. This
protocol provides commands to describe the protocol version, discover
various implementation specific attributes, describe a clock, enable
and disable a clock and get/set the rate of the clock synchronously or
asynchronously.
This patch adds initial support for the clock protocol.
Cc: Arnd Bergmann <[email protected]>
Signed-off-by: Sudeep Holla <[email protected]>
---
drivers/firmware/arm_scmi/Makefile | 2 +-
drivers/firmware/arm_scmi/clock.c | 339 +++++++++++++++++++++++++++++++++++++
include/linux/scmi_protocol.h | 40 +++++
3 files changed, 380 insertions(+), 1 deletion(-)
create mode 100644 drivers/firmware/arm_scmi/clock.c
diff --git a/drivers/firmware/arm_scmi/Makefile b/drivers/firmware/arm_scmi/Makefile
index 159de726ee45..6836b1f38f7f 100644
--- a/drivers/firmware/arm_scmi/Makefile
+++ b/drivers/firmware/arm_scmi/Makefile
@@ -1,2 +1,2 @@
obj-$(CONFIG_ARM_SCMI_PROTOCOL) = arm_scmi.o
-arm_scmi-y = base.o driver.o perf.o
+arm_scmi-y = base.o clock.o driver.o perf.o
diff --git a/drivers/firmware/arm_scmi/clock.c b/drivers/firmware/arm_scmi/clock.c
new file mode 100644
index 000000000000..87d0befab07d
--- /dev/null
+++ b/drivers/firmware/arm_scmi/clock.c
@@ -0,0 +1,339 @@
+/*
+ * System Control and Management Interface (SCMI) Clock Protocol
+ *
+ * Copyright (C) 2017 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 "common.h"
+
+enum scmi_clock_protocol_cmd {
+ CLOCK_ATTRIBUTES = 0x3,
+ CLOCK_DESCRIBE_RATES = 0x4,
+ CLOCK_RATE_SET = 0x5,
+ CLOCK_RATE_GET = 0x6,
+ CLOCK_CONFIG_SET = 0x7,
+};
+
+struct scmi_msg_resp_clock_protocol_attributes {
+ __le16 num_clocks;
+ u8 max_async_req;
+ u8 reserved;
+};
+
+struct scmi_msg_resp_clock_attributes {
+ __le32 attributes;
+#define CLOCK_ENABLE BIT(0)
+ u8 name[SCMI_MAX_STR_SIZE];
+};
+
+struct scmi_clock_set_config {
+ __le32 id;
+ __le32 attributes;
+};
+
+struct scmi_msg_clock_describe_rates {
+ __le32 id;
+ __le32 rate_index;
+};
+
+struct scmi_msg_resp_clock_describe_rates {
+ __le32 num_rates_flags;
+#define NUM_RETURNED(x) ((x) & 0xfff)
+#define RATE_DISCRETE(x) !((x) & BIT(12))
+#define NUM_REMAINING(x) ((x) >> 16)
+ struct {
+ __le32 value_low;
+ __le32 value_high;
+ } rate[0];
+#define RATE_TO_U64(X) \
+({ \
+ typeof(X) x = (X); \
+ le32_to_cpu((x).value_low) | (u64)le32_to_cpu((x).value_high) << 32; \
+})
+};
+
+struct scmi_clock_set_rate {
+ __le32 flags;
+#define CLOCK_SET_ASYNC BIT(0)
+#define CLOCK_SET_DELAYED BIT(1)
+#define CLOCK_SET_ROUND_UP BIT(2)
+#define CLOCK_SET_ROUND_AUTO BIT(3)
+ __le32 id;
+ __le32 value_low;
+ __le32 value_high;
+};
+
+struct clock_info {
+ int num_clocks;
+ int max_async_req;
+ struct scmi_clock_info *clk;
+};
+
+static struct clock_info clocks;
+
+static int scmi_clock_protocol_attributes_get(const struct scmi_handle *handle,
+ struct clock_info *clocks)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_msg_resp_clock_protocol_attributes *attr;
+
+ ret = scmi_one_xfer_init(handle, PROTOCOL_ATTRIBUTES,
+ SCMI_PROTOCOL_CLOCK, 0, sizeof(*attr), &t);
+ if (ret)
+ return ret;
+
+ attr = t->rx.buf;
+
+ ret = scmi_do_xfer(handle, t);
+ if (!ret) {
+ clocks->num_clocks = le16_to_cpu(attr->num_clocks);
+ clocks->max_async_req = attr->max_async_req;
+ }
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int scmi_clock_attributes_get(const struct scmi_handle *handle,
+ u32 clk_id, struct scmi_clock_info *clk)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_msg_resp_clock_attributes *attr;
+
+ ret = scmi_one_xfer_init(handle, CLOCK_ATTRIBUTES, SCMI_PROTOCOL_CLOCK,
+ sizeof(clk_id), sizeof(*attr), &t);
+ if (ret)
+ return ret;
+
+ *(__le32 *)t->tx.buf = cpu_to_le32(clk_id);
+ attr = t->rx.buf;
+
+ ret = scmi_do_xfer(handle, t);
+ if (!ret)
+ memcpy(clk->name, attr->name, SCMI_MAX_STR_SIZE);
+ else
+ clk->name[0] = '\0';
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int
+scmi_clock_describe_rates_get(const struct scmi_handle *handle, u32 clk_id,
+ struct scmi_clock_info *clk)
+{
+ u64 *rate;
+ int ret, cnt;
+ bool rate_discrete;
+ u32 tot_rate_cnt = 0, rates_flag;
+ u16 num_returned, num_remaining;
+ struct scmi_xfer *t;
+ struct scmi_msg_clock_describe_rates *clk_desc;
+ struct scmi_msg_resp_clock_describe_rates *rlist;
+
+ ret = scmi_one_xfer_init(handle, CLOCK_DESCRIBE_RATES,
+ SCMI_PROTOCOL_CLOCK, sizeof(*clk_desc), 0, &t);
+ if (ret)
+ return ret;
+
+ clk_desc = t->tx.buf;
+ rlist = t->rx.buf;
+
+ do {
+ clk_desc->id = cpu_to_le32(clk_id);
+ /* Set the number of rates to be skipped/already read */
+ clk_desc->rate_index = cpu_to_le32(tot_rate_cnt);
+
+ ret = scmi_do_xfer(handle, t);
+ if (ret)
+ break;
+
+ rates_flag = le32_to_cpu(rlist->num_rates_flags);
+ num_remaining = NUM_REMAINING(rates_flag);
+ rate_discrete = RATE_DISCRETE(rates_flag);
+ num_returned = NUM_RETURNED(rates_flag);
+
+ if (tot_rate_cnt + num_returned > SCMI_MAX_NUM_RATES) {
+ dev_err(handle->dev, "No. of rates > MAX_NUM_RATES");
+ break;
+ }
+
+ if (!rate_discrete) {
+ clk->range.min_rate = RATE_TO_U64(rlist->rate[0]);
+ clk->range.max_rate = RATE_TO_U64(rlist->rate[1]);
+ clk->range.step_size = RATE_TO_U64(rlist->rate[2]);
+ dev_dbg(handle->dev, "Min %llu Max %llu Step %llu Hz\n",
+ clk->range.min_rate, clk->range.max_rate,
+ clk->range.step_size);
+ break;
+ }
+
+ rate = &clk->list.rates[tot_rate_cnt];
+ for (cnt = 0; cnt < num_returned; cnt++, rate++) {
+ *rate = RATE_TO_U64(rlist->rate[cnt]);
+ dev_dbg(handle->dev, "Rate %llu Hz\n", *rate);
+ }
+
+ tot_rate_cnt += num_returned;
+ /*
+ * check for both returned and remaining to avoid infinite
+ * loop due to buggy firmware
+ */
+ } while (num_returned && num_remaining);
+
+ if (rate_discrete)
+ clk->list.num_rates = tot_rate_cnt;
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int
+scmi_clock_rate_get(const struct scmi_handle *handle, u32 clk_id, u64 *value)
+{
+ int ret;
+ struct scmi_xfer *t;
+
+ ret = scmi_one_xfer_init(handle, CLOCK_RATE_GET, SCMI_PROTOCOL_CLOCK,
+ sizeof(__le32), sizeof(u64), &t);
+ if (ret)
+ return ret;
+
+ *(__le32 *)t->tx.buf = cpu_to_le32(clk_id);
+
+ ret = scmi_do_xfer(handle, t);
+ if (!ret) {
+ __le32 *pval = t->rx.buf;
+
+ *value = le32_to_cpu(*pval);
+ *value |= (u64)le32_to_cpu(*(pval + 1)) << 32;
+ }
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int scmi_clock_rate_set(const struct scmi_handle *handle, u32 clk_id,
+ u32 config, u64 rate)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_clock_set_rate *cfg;
+
+ ret = scmi_one_xfer_init(handle, CLOCK_RATE_SET, SCMI_PROTOCOL_CLOCK,
+ sizeof(*cfg), 0, &t);
+ if (ret)
+ return ret;
+
+ cfg = t->tx.buf;
+ cfg->flags = cpu_to_le32(config);
+ cfg->id = cpu_to_le32(clk_id);
+ cfg->value_low = cpu_to_le32(rate & 0xffffffff);
+ cfg->value_high = cpu_to_le32(rate >> 32);
+
+ ret = scmi_do_xfer(handle, t);
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int
+scmi_clock_config_set(const struct scmi_handle *handle, u32 clk_id, u32 config)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_clock_set_config *cfg;
+
+ ret = scmi_one_xfer_init(handle, CLOCK_CONFIG_SET, SCMI_PROTOCOL_CLOCK,
+ sizeof(*cfg), 0, &t);
+ if (ret)
+ return ret;
+
+ cfg = t->tx.buf;
+ cfg->id = cpu_to_le32(clk_id);
+ cfg->attributes = cpu_to_le32(config);
+
+ ret = scmi_do_xfer(handle, t);
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int scmi_clock_enable(const struct scmi_handle *handle, u32 clk_id)
+{
+ return scmi_clock_config_set(handle, clk_id, CLOCK_ENABLE);
+}
+
+static int scmi_clock_disable(const struct scmi_handle *handle, u32 clk_id)
+{
+ return scmi_clock_config_set(handle, clk_id, 0);
+}
+
+static int scmi_clock_count_get(const struct scmi_handle *handle)
+{
+ return clocks.num_clocks;
+}
+
+static const struct scmi_clock_info *
+scmi_clock_info_get(const struct scmi_handle *handle, u32 clk_id)
+{
+ struct scmi_clock_info *clk = clocks.clk + clk_id;
+
+ if (!clk->name || !clk->name[0])
+ return NULL;
+
+ return clk;
+}
+
+static struct scmi_clk_ops clk_ops = {
+ .count_get = scmi_clock_count_get,
+ .info_get = scmi_clock_info_get,
+ .rate_get = scmi_clock_rate_get,
+ .rate_set = scmi_clock_rate_set,
+ .enable = scmi_clock_enable,
+ .disable = scmi_clock_disable,
+};
+
+int scmi_clock_protocol_init(struct scmi_handle *handle)
+{
+ int clkid, ret;
+ u32 version;
+
+ scmi_version_get(handle, SCMI_PROTOCOL_CLOCK, &version);
+
+ dev_dbg(handle->dev, "Clock Version %d.%d\n",
+ PROTOCOL_REV_MAJOR(version), PROTOCOL_REV_MINOR(version));
+
+ scmi_clock_protocol_attributes_get(handle, &clocks);
+
+ clocks.clk = devm_kcalloc(handle->dev, clocks.num_clocks,
+ sizeof(struct scmi_clock_info), GFP_KERNEL);
+ if (!clocks.clk)
+ return -ENOMEM;
+
+ for (clkid = 0; clkid < clocks.num_clocks; clkid++) {
+ struct scmi_clock_info *clk = clocks.clk + clkid;
+
+ ret = scmi_clock_attributes_get(handle, clkid, clk);
+ if (!ret)
+ scmi_clock_describe_rates_get(handle, clkid, clk);
+ }
+
+ handle->clk_ops = &clk_ops;
+
+ return 0;
+}
diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
index c9f97e69444a..c15f37c86025 100644
--- a/include/linux/scmi_protocol.h
+++ b/include/linux/scmi_protocol.h
@@ -18,6 +18,7 @@
#include <linux/types.h>
#define SCMI_MAX_STR_SIZE 16
+#define SCMI_MAX_NUM_RATES 16
/**
* struct scmi_revision_info - version information structure
@@ -43,9 +44,46 @@ struct scmi_revision_info {
char sub_vendor_id[SCMI_MAX_STR_SIZE];
};
+struct scmi_clock_info {
+ char name[SCMI_MAX_STR_SIZE];
+ bool rate_discrete;
+ union {
+ struct {
+ int num_rates;
+ u64 rates[SCMI_MAX_NUM_RATES];
+ } list;
+ struct {
+ u64 min_rate;
+ u64 max_rate;
+ u64 step_size;
+ } range;
+ };
+};
+
struct scmi_handle;
/**
+ * struct scmi_clk_ops - represents the various operations provided
+ * by SCMI Clock Protocol
+ *
+ * @count_get: get the count of clocks provided by SCMI
+ * @info_get: get the information of the specified clock
+ * @rate_get: request the current clock rate of a clock
+ * @rate_set: set the clock rate of a clock
+ * @enable: enables the specified clock
+ * @disable: disables the specified clock
+ */
+struct scmi_clk_ops {
+ int (*count_get)(const struct scmi_handle *);
+ const struct scmi_clock_info *(*info_get)(const struct scmi_handle *,
+ u32);
+ int (*rate_get)(const struct scmi_handle *, u32, u64*);
+ int (*rate_set)(const struct scmi_handle *, u32, u32, u64);
+ int (*enable)(const struct scmi_handle *, u32);
+ int (*disable)(const struct scmi_handle *, u32);
+};
+
+/**
* struct scmi_perf_ops - represents the various operations provided
* by SCMI Performance Protocol
*
@@ -78,11 +116,13 @@ struct scmi_perf_ops {
* @dev: pointer to the SCMI device
* @version: pointer to the structure containing SCMI version information
* @perf_ops: pointer to set of performance protocol operations
+ * @clk_ops: pointer to set of clock protocol operations
*/
struct scmi_handle {
struct device *dev;
struct scmi_revision_info *version;
struct scmi_perf_ops *perf_ops;
+ struct scmi_clk_ops *clk_ops;
};
#if IS_REACHABLE(CONFIG_ARM_SCMI_PROTOCOL)
--
2.7.4
The base protocol describes the properties of the implementation and
provide generic error management. The base protocol provides commands
to describe protocol version, discover implementation specific
attributes and vendor/sub-vendor identification, list of protocols
implemented and the various agents are in the system including OSPM
and the platform. It also supports registering for notifications of
platform errors.
This protocol is mandatory. This patch adds support for the same along
with some basic infrastructure to add support for other protocols.
Cc: Arnd Bergmann <[email protected]>
Signed-off-by: Sudeep Holla <[email protected]>
---
drivers/firmware/arm_scmi/Makefile | 2 +-
drivers/firmware/arm_scmi/base.c | 293 +++++++++++++++++++++++++++++++++++++
drivers/firmware/arm_scmi/common.h | 46 ++++++
drivers/firmware/arm_scmi/driver.c | 67 +++++++++
include/linux/scmi_protocol.h | 28 ++++
5 files changed, 435 insertions(+), 1 deletion(-)
create mode 100644 drivers/firmware/arm_scmi/base.c
diff --git a/drivers/firmware/arm_scmi/Makefile b/drivers/firmware/arm_scmi/Makefile
index 58e94c95e523..21d01d1d6b9c 100644
--- a/drivers/firmware/arm_scmi/Makefile
+++ b/drivers/firmware/arm_scmi/Makefile
@@ -1,2 +1,2 @@
obj-$(CONFIG_ARM_SCMI_PROTOCOL) = arm_scmi.o
-arm_scmi-y = driver.o
+arm_scmi-y = base.o driver.o
diff --git a/drivers/firmware/arm_scmi/base.c b/drivers/firmware/arm_scmi/base.c
new file mode 100644
index 000000000000..9bfffbe95c21
--- /dev/null
+++ b/drivers/firmware/arm_scmi/base.c
@@ -0,0 +1,293 @@
+/*
+ * System Control and Management Interface (SCMI) Base Protocol
+ *
+ * Copyright (C) 2017 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 "common.h"
+
+enum scmi_base_protocol_cmd {
+ BASE_DISCOVER_VENDOR = 0x3,
+ BASE_DISCOVER_SUB_VENDOR = 0x4,
+ BASE_DISCOVER_IMPLEMENT_VERSION = 0x5,
+ BASE_DISCOVER_LIST_PROTOCOLS = 0x6,
+ BASE_DISCOVER_AGENT = 0x7,
+ BASE_NOTIFY_ERRORS = 0x8,
+};
+
+struct scmi_msg_resp_base_attributes {
+ u8 num_protocols;
+ u8 num_agents;
+ __le16 reserved;
+};
+
+/**
+ * scmi_base_attributes_get() - gets the implementation details
+ * that are associated with the base protocol.
+ *
+ * @handle - SCMI entity handle
+ *
+ * Return: 0 on success, else appropriate SCMI error.
+ */
+static int scmi_base_attributes_get(const struct scmi_handle *handle)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_msg_resp_base_attributes *attr_info;
+ struct scmi_revision_info *rev = handle->version;
+
+ ret = scmi_one_xfer_init(handle, PROTOCOL_ATTRIBUTES,
+ SCMI_PROTOCOL_BASE, 0, sizeof(*attr_info), &t);
+ if (ret)
+ return ret;
+
+ ret = scmi_do_xfer(handle, t);
+ if (!ret) {
+ attr_info = t->rx.buf;
+ rev->num_protocols = attr_info->num_protocols;
+ rev->num_agents = attr_info->num_agents;
+ }
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+/**
+ * scmi_base_vendor_id_get() - gets vendor/subvendor identifier ASCII string.
+ *
+ * @handle - SCMI entity handle
+ * @sub_vendor - specify true if sub-vendor ID is needed
+ *
+ * Return: 0 on success, else appropriate SCMI error.
+ */
+static int
+scmi_base_vendor_id_get(const struct scmi_handle *handle, bool sub_vendor)
+{
+ u8 cmd;
+ int ret, size;
+ char *vendor_id;
+ struct scmi_xfer *t;
+ struct scmi_revision_info *rev = handle->version;
+
+ if (sub_vendor) {
+ cmd = BASE_DISCOVER_SUB_VENDOR;
+ vendor_id = rev->sub_vendor_id;
+ size = ARRAY_SIZE(rev->sub_vendor_id);
+ } else {
+ cmd = BASE_DISCOVER_VENDOR;
+ vendor_id = rev->vendor_id;
+ size = ARRAY_SIZE(rev->vendor_id);
+ }
+
+ ret = scmi_one_xfer_init(handle, cmd, SCMI_PROTOCOL_BASE, 0, size, &t);
+ if (ret)
+ return ret;
+
+ ret = scmi_do_xfer(handle, t);
+ if (!ret)
+ memcpy(vendor_id, t->rx.buf, size);
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+/**
+ * scmi_base_implementation_version_get() - gets a vendor-specific
+ * implementation 32-bit version. The format of the version number is
+ * vendor-specific
+ *
+ * @handle - SCMI entity handle
+ *
+ * Return: 0 on success, else appropriate SCMI error.
+ */
+static int
+scmi_base_implementation_version_get(const struct scmi_handle *handle)
+{
+ int ret;
+ u32 *impl_ver;
+ struct scmi_xfer *t;
+ struct scmi_revision_info *rev = handle->version;
+
+ ret = scmi_one_xfer_init(handle, BASE_DISCOVER_IMPLEMENT_VERSION,
+ SCMI_PROTOCOL_BASE, 0, sizeof(*impl_ver), &t);
+ if (ret)
+ return ret;
+
+ ret = scmi_do_xfer(handle, t);
+ if (!ret) {
+ impl_ver = t->rx.buf;
+ rev->impl_ver = le32_to_cpu(*impl_ver);
+ }
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+/**
+ * scmi_base_implementation_list_get() - gets the list of protocols it is
+ * OSPM is allowed to access
+ *
+ * @handle - SCMI entity handle
+ * @protocols_imp - pointer to hold the list of protocol identifiers
+ *
+ * Return: 0 on success, else appropriate SCMI error.
+ */
+static int scmi_base_implementation_list_get(const struct scmi_handle *handle,
+ u8 *protocols_imp)
+{
+ u8 *list;
+ int ret, loop;
+ struct scmi_xfer *t;
+ __le32 *num_skip, *num_ret;
+ u32 tot_num_ret = 0, loop_num_ret;
+ struct device *dev = handle->dev;
+
+ ret = scmi_one_xfer_init(handle, BASE_DISCOVER_LIST_PROTOCOLS,
+ SCMI_PROTOCOL_BASE, sizeof(*num_skip), 0, &t);
+ if (ret)
+ return ret;
+
+ num_skip = t->tx.buf;
+ num_ret = t->rx.buf;
+ list = t->rx.buf + sizeof(*num_ret);
+
+ do {
+ /* Set the number of protocols to be skipped/already read */
+ *num_skip = cpu_to_le32(tot_num_ret);
+
+ ret = scmi_do_xfer(handle, t);
+ if (ret)
+ break;
+
+ loop_num_ret = le32_to_cpu(*num_ret);
+ if (tot_num_ret + loop_num_ret > MAX_PROTOCOLS_IMP) {
+ dev_err(dev, "No. of Protocol > MAX_PROTOCOLS_IMP");
+ break;
+ }
+
+ for (loop = 0; loop < loop_num_ret; loop++)
+ protocols_imp[tot_num_ret + loop] = *(list + loop);
+
+ tot_num_ret += loop_num_ret;
+ } while (loop_num_ret);
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+/**
+ * scmi_base_discover_agent_get() - discover the name of an agent
+ *
+ * @handle - SCMI entity handle
+ * @id - Agent identifier
+ * @name - Agent identifier ASCII string
+ *
+ * An agent id of 0 is reserved to identify the platform itself.
+ * Generally operating system is represented as "OSPM"
+ *
+ * Return: 0 on success, else appropriate SCMI error.
+ */
+static int scmi_base_discover_agent_get(const struct scmi_handle *handle,
+ int id, char *name)
+{
+ int ret;
+ struct scmi_xfer *t;
+
+ ret = scmi_one_xfer_init(handle, BASE_DISCOVER_AGENT,
+ SCMI_PROTOCOL_BASE, sizeof(__le32),
+ SCMI_MAX_STR_SIZE, &t);
+ if (ret)
+ return ret;
+
+ *(__le32 *)t->tx.buf = cpu_to_le32(id);
+
+ ret = scmi_do_xfer(handle, t);
+ if (!ret)
+ memcpy(name, t->rx.buf, SCMI_MAX_STR_SIZE);
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+/**
+ * scmi_base_error_notifications_enable() - register/unregister for
+ * notifications of errors in the platform
+ *
+ * @handle - SCMI entity handle
+ * @enable - Enable/Disable the notification
+ *
+ * Return: 0 on success, else appropriate SCMI error.
+ */
+static int
+scmi_base_error_notifications_enable(const struct scmi_handle *handle, bool en)
+{
+ int ret;
+ struct scmi_xfer *t;
+
+ ret = scmi_one_xfer_init(handle, BASE_NOTIFY_ERRORS, SCMI_PROTOCOL_BASE,
+ sizeof(__le32), 0, &t);
+ if (ret)
+ return ret;
+
+ *(__le32 *)t->tx.buf = cpu_to_le32(en & BIT(0));
+
+ ret = scmi_do_xfer(handle, t);
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+int scmi_base_protocol_init(struct scmi_handle *h)
+{
+ int id, ret;
+ u8 *prot_imp;
+ u32 version;
+ char name[SCMI_MAX_STR_SIZE];
+ const struct scmi_handle *handle = h;
+ struct device *dev = handle->dev;
+ struct scmi_revision_info *rev = handle->version;
+
+ ret = scmi_version_get(handle, SCMI_PROTOCOL_BASE, &version);
+ if (ret)
+ return ret;
+
+ prot_imp = devm_kcalloc(dev, MAX_PROTOCOLS_IMP, sizeof(u8), GFP_KERNEL);
+ if (!prot_imp)
+ return -ENOMEM;
+
+ rev->major_ver = PROTOCOL_REV_MAJOR(version),
+ rev->minor_ver = PROTOCOL_REV_MINOR(version);
+
+ scmi_base_attributes_get(handle);
+ scmi_base_vendor_id_get(handle, false);
+ scmi_base_vendor_id_get(handle, true);
+ scmi_base_implementation_version_get(handle);
+ scmi_base_implementation_list_get(handle, prot_imp);
+ scmi_base_error_notifications_enable(handle, true);
+ scmi_setup_protocol_implemented(handle, prot_imp);
+
+ dev_info(dev, "SCMI Protocol v%d.%d '%s:%s' Firmware version 0x%x\n",
+ rev->major_ver, rev->minor_ver, rev->vendor_id,
+ rev->sub_vendor_id, rev->impl_ver);
+ dev_dbg(dev, "Found %d protocol(s) %d agent(s)\n", rev->num_protocols,
+ rev->num_agents);
+
+ for (id = 0; id < rev->num_agents; id++) {
+ scmi_base_discover_agent_get(handle, id, name);
+ dev_dbg(dev, "Agent %d: %s\n", id, name);
+ }
+
+ return 0;
+}
diff --git a/drivers/firmware/arm_scmi/common.h b/drivers/firmware/arm_scmi/common.h
index a6829afc17e3..e3fe5d9acc82 100644
--- a/drivers/firmware/arm_scmi/common.h
+++ b/drivers/firmware/arm_scmi/common.h
@@ -19,9 +19,50 @@
*/
#include <linux/completion.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
#include <linux/scmi_protocol.h>
#include <linux/types.h>
+#define PROTOCOL_REV_MINOR_BITS 16
+#define PROTOCOL_REV_MINOR_MASK ((1U << PROTOCOL_REV_MINOR_BITS) - 1)
+#define PROTOCOL_REV_MAJOR(x) ((x) >> PROTOCOL_REV_MINOR_BITS)
+#define PROTOCOL_REV_MINOR(x) ((x) & PROTOCOL_REV_MINOR_MASK)
+#define MAX_PROTOCOLS_IMP 16
+
+enum scmi_std_protocol {
+ SCMI_PROTOCOL_BASE = 0x10,
+ SCMI_PROTOCOL_POWER = 0x11,
+ SCMI_PROTOCOL_SYSTEM = 0x12,
+ SCMI_PROTOCOL_PERF = 0x13,
+ SCMI_PROTOCOL_CLOCK = 0x14,
+ SCMI_PROTOCOL_SENSOR = 0x15,
+};
+
+enum scmi_common_cmd {
+ PROTOCOL_VERSION = 0x0,
+ PROTOCOL_ATTRIBUTES = 0x1,
+ PROTOCOL_MESSAGE_ATTRIBUTES = 0x2,
+};
+
+/**
+ * struct scmi_msg_resp_prot_version - Response for a message
+ *
+ * @major_version: Major version of the ABI that firmware supports
+ * @minor_version: Minor version of the ABI that firmware supports
+ *
+ * In general, ABI version changes follow the rule that minor version increments
+ * are backward compatible. Major revision changes in ABI may not be
+ * backward compatible.
+ *
+ * Response to a generic message with message type SCMI_MSG_VERSION
+ */
+struct scmi_msg_resp_prot_version {
+ __le16 minor_version;
+ __le16 major_version;
+};
+
/**
* struct scmi_msg_hdr - Message(Tx/Rx) header
*
@@ -72,3 +113,8 @@ void scmi_one_xfer_put(const struct scmi_handle *h, struct scmi_xfer *xfer);
int scmi_do_xfer(const struct scmi_handle *h, struct scmi_xfer *xfer);
int scmi_one_xfer_init(const struct scmi_handle *h, u8 msg_id, u8 prot_id,
size_t tx_size, size_t rx_size, struct scmi_xfer **p);
+int scmi_version_get(const struct scmi_handle *h, u8 protocol, u32 *version);
+void scmi_setup_protocol_implemented(const struct scmi_handle *handle,
+ u8 *prot_imp);
+
+int scmi_base_protocol_init(struct scmi_handle *h);
diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c
index 139d6980f270..601d0d7210d9 100644
--- a/drivers/firmware/arm_scmi/driver.c
+++ b/drivers/firmware/arm_scmi/driver.c
@@ -108,18 +108,22 @@ struct scmi_desc {
* @dev: Device pointer
* @desc: SoC description for this instance
* @handle: Instance of SCMI handle to send to clients
+ * @version: SCMI revision information containing protocol version,
+ * implementation version and (sub-)vendor identification.
* @cl: Mailbox Client
* @tx_chan: Transmit mailbox channel
* @rx_chan: Receive mailbox channel
* @tx_payload: Transmit mailbox channel payload area
* @rx_payload: Receive mailbox channel payload area
* @minfo: Message info
+ * @protocols_imp: list of protocols implemented
* @node: list head
* @users: Number of users of this instance
*/
struct scmi_info {
struct device *dev;
const struct scmi_desc *desc;
+ struct scmi_revision_info version;
struct scmi_handle handle;
struct mbox_client cl;
struct mbox_chan *tx_chan;
@@ -127,6 +131,7 @@ struct scmi_info {
void __iomem *tx_payload;
void __iomem *rx_payload;
struct scmi_xfers_info minfo;
+ u8 *protocols_imp;
struct list_head node;
int users;
};
@@ -452,6 +457,60 @@ int scmi_one_xfer_init(const struct scmi_handle *handle, u8 msg_id, u8 prot_id,
}
/**
+ * scmi_version_get() - command to get the revision of the SCMI entity
+ *
+ * @handle: Handle to SCMI entity information
+ *
+ * Updates the SCMI information in the internal data structure.
+ *
+ * Return: 0 if all went fine, else return appropriate error.
+ */
+int scmi_version_get(const struct scmi_handle *handle, u8 protocol,
+ u32 *version)
+{
+ int ret;
+ __le32 *rev_info;
+ struct scmi_xfer *t;
+
+ ret = scmi_one_xfer_init(handle, PROTOCOL_VERSION, protocol, 0,
+ sizeof(*version), &t);
+ if (ret)
+ return ret;
+
+ ret = scmi_do_xfer(handle, t);
+ if (!ret) {
+ rev_info = t->rx.buf;
+ *version = le32_to_cpu(*rev_info);
+ }
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+void scmi_setup_protocol_implemented(const struct scmi_handle *handle,
+ u8 *prot_imp)
+{
+ struct scmi_info *info = handle_to_scmi_info(handle);
+
+ info->protocols_imp = prot_imp;
+}
+
+static bool
+scmi_is_protocol_implemented(const struct scmi_handle *handle, u8 prot_id)
+{
+ int i;
+ struct scmi_info *info = handle_to_scmi_info(handle);
+
+ if (!info->protocols_imp)
+ return false;
+
+ for (i = 0; i < MAX_PROTOCOLS_IMP; i++)
+ if (info->protocols_imp[i] == prot_id)
+ return true;
+ return false;
+}
+
+/**
* scmi_handle_get() - Get the SCMI handle for a device
*
* @dev: pointer to device for which we want SCMI handle
@@ -745,11 +804,19 @@ static int scmi_probe(struct platform_device *pdev)
handle = &info->handle;
handle->dev = info->dev;
+ handle->version = &info->version;
ret = scmi_mbox_chan_setup(info);
if (ret)
return ret;
+ ret = scmi_base_protocol_init(handle);
+ if (ret) {
+ dev_err(dev, "unable to communicate with SCMI(%d)\n", ret);
+ scmi_mbox_free_channel(info);
+ return ret;
+ }
+
mutex_lock(&scmi_list_mutex);
list_add_tail(&info->node, &scmi_list);
mutex_unlock(&scmi_list_mutex);
diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
index fbe80fa1ec9f..3ef2d48f03c2 100644
--- a/include/linux/scmi_protocol.h
+++ b/include/linux/scmi_protocol.h
@@ -17,13 +17,41 @@
*/
#include <linux/types.h>
+#define SCMI_MAX_STR_SIZE 16
+
+/**
+ * struct scmi_revision_info - version information structure
+ *
+ * @major_ver: Major ABI version. Change here implies risk of backward
+ * compatibility break.
+ * @minor_ver: Minor ABI version. Change here implies new feature addition,
+ * or compatible change in ABI.
+ * @num_protocols: Number of protocols that are implemented, excluding the
+ * base protocol.
+ * @num_agents: Number of agents in the system.
+ * @impl_ver: A vendor-specific implementation version.
+ * @vendor_id: A vendor identifier(Null terminated ASCII string)
+ * @sub_vendor_id: A sub-vendor identifier(Null terminated ASCII string)
+ */
+struct scmi_revision_info {
+ u16 major_ver;
+ u16 minor_ver;
+ u8 num_protocols;
+ u8 num_agents;
+ u32 impl_ver;
+ char vendor_id[SCMI_MAX_STR_SIZE];
+ char sub_vendor_id[SCMI_MAX_STR_SIZE];
+};
+
/**
* struct scmi_handle - Handle returned to ARM SCMI clients for usage.
*
* @dev: pointer to the SCMI device
+ * @version: pointer to the structure containing SCMI version information
*/
struct scmi_handle {
struct device *dev;
+ struct scmi_revision_info *version;
};
#if IS_REACHABLE(CONFIG_ARM_SCMI_PROTOCOL)
--
2.7.4
This patch adds devicetree binding for System Control and Management
Interface (SCMI) 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.
SCMI protocol is developed as better replacement to the existing SCPI
which is not flexible and easily extensible.
Cc: Rob Herring <[email protected]>
Cc: Mark Rutland <[email protected]>
Signed-off-by: Sudeep Holla <[email protected]>
---
Documentation/devicetree/bindings/arm/arm,scmi.txt | 174 +++++++++++++++++++++
1 file changed, 174 insertions(+)
create mode 100644 Documentation/devicetree/bindings/arm/arm,scmi.txt
diff --git a/Documentation/devicetree/bindings/arm/arm,scmi.txt b/Documentation/devicetree/bindings/arm/arm,scmi.txt
new file mode 100644
index 000000000000..33c16be58e72
--- /dev/null
+++ b/Documentation/devicetree/bindings/arm/arm,scmi.txt
@@ -0,0 +1,174 @@
+System Control and Management Interface (SCMI) Message Protocol
+----------------------------------------------------------
+
+The SCMI is intended to allow agents such as OSPM to manage various functions
+that are provided by the hardware platform it is running on, including power
+and performance functions.
+
+This binding is intended to define the interface the firmware implementing
+the SCMI as described in ARM document number ARM DUI 0922B ("ARM System Control
+and Management Interface Platform Design Document")[0] provide for OSPM in
+the device tree.
+
+Required properties:
+
+- compatible : shall be "arm,scmi"
+- mboxes: List of phandle and mailbox channel specifiers. It should contain
+ exactly one or two mailboxes, one for transmitting messages("tx")
+ and another optional for receiving the notifications("rx") if
+ supported.
+- mbox-names: shall be "tx" or "rx"
+- shmem : List of phandle pointing to the shared memory(SHM) area as per
+ generic mailbox client binding.
+
+See Documentation/devicetree/bindings/mailbox/mailbox.txt for more details
+about the generic mailbox controller and client driver bindings.
+
+The mailbox is the only permitted method of calling the SCMI firmware.
+Mailbox doorbell is used as a mechanism to alert the presence of a
+messages and/or notification.
+
+Each protocol supported shall have a sub-node with corresponding compatible
+as described in the following sections. If the platform supports dedicated
+communication channel for a particular protocol, the 3 properties namely:
+mboxes, mbox-names and shmem shall be present in the sub-node corresponding
+to that protocol.
+
+Clock/Performance bindings for the clocks/OPPs based on SCMI Message Protocol
+------------------------------------------------------------
+
+This binding uses the common clock binding[1].
+
+Required properties:
+- #clock-cells : Should be 1. Contains the Clock ID value used by SCMI commands.
+
+Power domain bindings for the power domains based on SCMI Message Protocol
+------------------------------------------------------------
+
+This binding uses the generic power domain binding[4].
+
+PM domain providers
+===================
+
+Required properties:
+ - #power-domain-cells : Should be 1. Contains the device or the power
+ domain ID value used by SCMI commands.
+
+PM domain consumers
+===================
+
+Required properties:
+ - power-domains : A phandle and PM domain specifier as defined by bindings of
+ the power controller specified by phandle.
+
+Sensor bindings for the sensors based on SCMI Message Protocol
+--------------------------------------------------------------
+SCMI provides an API to access the various sensors on the SoC.
+
+Required properties:
+- #thermal-sensor-cells: should be set to 1. This property follows the
+ thermal device tree bindings[2].
+
+ Valid cell values are raw identifiers (Sensor ID)
+ as used by the firmware. Refer to platform details
+ for your implementation for the IDs to use.
+
+SRAM and Shared Memory for SCMI
+-------------------------------
+
+A small area of SRAM is reserved for SCMI communication between application
+processors and SCP.
+
+The properties should follow the generic mmio-sram description found in [3]
+
+Each sub-node represents the reserved area for SCMI.
+
+Required sub-node properties:
+- reg : The base offset and size of the reserved area with the SRAM
+- compatible : should be "arm,scmi-shmem" for Non-secure SRAM based
+ shared memory
+
+[0] http://infocenter.arm.com/help/topic/com.arm.doc.den0056a/index.html
+[1] Documentation/devicetree/bindings/clock/clock-bindings.txt
+[2] Documentation/devicetree/bindings/thermal/thermal.txt
+[3] Documentation/devicetree/bindings/sram/sram.txt
+[4] Documentation/devicetree/bindings/power/power_domain.txt
+
+Example:
+
+sram: sram@50000000 {
+ compatible = "mmio-sram";
+ reg = <0x0 0x50000000 0x0 0x10000>;
+
+ #address-cells = <1>;
+ #size-cells = <1>;
+ ranges = <0 0x0 0x50000000 0x10000>;
+
+ cpu_scp_lpri: scp-shmem@0 {
+ compatible = "arm,scmi-shmem";
+ reg = <0x0 0x200>;
+ };
+
+ cpu_scp_hpri: scp-shmem@200 {
+ compatible = "arm,scmi-shmem";
+ reg = <0x200 0x200>;
+ };
+};
+
+mailbox: mailbox0@40000000 {
+ ....
+ #mbox-cells = <1>;
+};
+
+scmi_protocol: scmi@2e000000 {
+ compatible = "arm,scmi";
+ method = "mailbox-doorbell";
+ mboxes = <&mailbox 0 &mailbox 1>;
+ shmem = <&cpu_scp_lpri &cpu_scp_hpri>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ scmi_devpd: protocol@11 {
+ reg = <0x11>;
+ #power-domain-cells = <1>;
+ };
+
+ scmi_dvfs: protocol@13 {
+ reg = <0x13>;
+ #clock-cells = <1>;
+ };
+
+ scmi_clk: protocol@14 {
+ reg = <0x14>;
+ #clock-cells = <1>;
+ };
+
+ scmi_sensors0: protocol@15 {
+ reg = <0x15>;
+ #thermal-sensor-cells = <1>;
+ };
+};
+
+cpu@0 {
+ ...
+ reg = <0 0>;
+ clocks = <&scmi_dvfs 0>;
+};
+
+hdlcd@7ff60000 {
+ ...
+ reg = <0 0x7ff60000 0 0x1000>;
+ clocks = <&scmi_clk 4>;
+ power-domains = <&scmi_devpd 1>;
+};
+
+thermal-zones {
+ soc_thermal {
+ polling-delay-passive = <100>;
+ polling-delay = <1000>;
+
+ /* sensor ID */
+ thermal-sensors = <&scmi_sensors0 3>;
+ ...
+ };
+};
--
2.7.4
On Fri, Aug 04, 2017 at 03:31:42PM +0100, Sudeep Holla wrote:
> Create a driver to add support for SoC sensors exported by the System
> Control Processor (SCP) via the System Control and Management Interface
> (SCMI). The supported sensor types is one of voltage, temperature,
> current, and power.
>
> The sensor labels and values provided by the SCP are exported via the
> hwmon sysfs interface.
>
> Cc: Guenter Roeck <[email protected]>
> Cc: [email protected]
> Signed-off-by: Sudeep Holla <[email protected]>
> ---
> drivers/hwmon/Kconfig | 12 +++
> drivers/hwmon/Makefile | 1 +
> drivers/hwmon/scmi-hwmon.c | 261 +++++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 274 insertions(+)
> create mode 100644 drivers/hwmon/scmi-hwmon.c
>
> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> index 5ef2814345ef..2bb63af8d674 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -321,6 +321,18 @@ config SENSORS_APPLESMC
> Say Y here if you have an applicable laptop and want to experience
> the awesome power of applesmc.
>
> +config SENSORS_ARM_SCMI
> + tristate "ARM SCMI Sensors"
> + depends on ARM_SCMI_PROTOCOL
> + depends on THERMAL || !THERMAL_OF
> + help
> + This driver provides support for temperature, voltage, current
> + and power sensors available on SCMI based platforms. The actual
> + number and type of sensors exported depend on the platform.
> +
> + This driver can also be built as a module. If so, the module
> + will be called scmi-hwmon.
> +
> config SENSORS_ARM_SCPI
> tristate "ARM SCPI Sensors"
> depends on ARM_SCPI_PROTOCOL
> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> index d4641a9f16c1..02c3783c319f 100644
> --- a/drivers/hwmon/Makefile
> +++ b/drivers/hwmon/Makefile
> @@ -44,6 +44,7 @@ obj-$(CONFIG_SENSORS_ADT7462) += adt7462.o
> obj-$(CONFIG_SENSORS_ADT7470) += adt7470.o
> obj-$(CONFIG_SENSORS_ADT7475) += adt7475.o
> obj-$(CONFIG_SENSORS_APPLESMC) += applesmc.o
> +obj-$(CONFIG_SENSORS_ARM_SCMI) += scmi-hwmon.o
> obj-$(CONFIG_SENSORS_ARM_SCPI) += scpi-hwmon.o
> obj-$(CONFIG_SENSORS_ASC7621) += asc7621.o
> obj-$(CONFIG_SENSORS_ASPEED) += aspeed-pwm-tacho.o
> diff --git a/drivers/hwmon/scmi-hwmon.c b/drivers/hwmon/scmi-hwmon.c
> new file mode 100644
> index 000000000000..f38c9708d99c
> --- /dev/null
> +++ b/drivers/hwmon/scmi-hwmon.c
> @@ -0,0 +1,261 @@
> +/*
> + * System Control and Management Interface(SCMI) based hwmon sensor driver
> + *
> + * Copyright (C) 2017 ARM Ltd.
> + * Punit Agrawal <[email protected]>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + * This program is distributed "as is" WITHOUT ANY WARRANTY of any
> + * kind, whether express or implied; without even the implied warranty
> + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + */
> +
> +#include <linux/hwmon.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/scmi_protocol.h>
> +#include <linux/slab.h>
> +#include <linux/sysfs.h>
> +#include <linux/thermal.h>
> +
> +struct sensor_data {
> + const struct scmi_sensor_info *info;
> + struct device_attribute dev_attr_input;
> + struct device_attribute dev_attr_label;
> + char input[20];
> + char label[20];
> +};
> +
> +struct scmi_thermal_zone {
> + int sensor_id;
> + struct scmi_sensors *scmi_sensors;
> +};
> +
> +struct scmi_sensors {
> + const struct scmi_handle *handle;
> + struct sensor_data *data;
> + struct list_head thermal_zones;
> + struct attribute **attrs;
> + struct attribute_group group;
> + const struct attribute_group *groups[2];
> +};
> +
> +static int scmi_read_temp(void *dev, int *temp)
> +{
> + struct scmi_thermal_zone *zone = dev;
> + struct scmi_sensors *scmi_sensors = zone->scmi_sensors;
> + const struct scmi_handle *handle = scmi_sensors->handle;
> + struct sensor_data *sensor = &scmi_sensors->data[zone->sensor_id];
> + u64 value;
> + int ret;
> +
> + ret = handle->sensor_ops->reading_get(handle, sensor->info->id,
> + false, &value);
> + if (ret)
> + return ret;
> +
> + *temp = value;
> + return 0;
> +}
> +
> +/* hwmon callback functions */
> +static ssize_t
> +scmi_show_sensor(struct device *dev, struct device_attribute *attr, char *buf)
> +{
> + struct scmi_sensors *scmi_sensors = dev_get_drvdata(dev);
> + const struct scmi_handle *handle = scmi_sensors->handle;
> + struct sensor_data *sensor;
> + u64 value;
> + int ret;
> +
> + sensor = container_of(attr, struct sensor_data, dev_attr_input);
> +
> + ret = handle->sensor_ops->reading_get(handle, sensor->info->id,
> + false, &value);
> + if (ret)
> + return ret;
> +
> + return sprintf(buf, "%llu\n", value);
> +}
> +
> +static ssize_t
> +scmi_show_label(struct device *dev, struct device_attribute *attr, char *buf)
> +{
> + struct sensor_data *sensor;
> +
> + sensor = container_of(attr, struct sensor_data, dev_attr_label);
> +
> + return sprintf(buf, "%s\n", sensor->info->name);
> +}
> +
> +static struct thermal_zone_of_device_ops scmi_sensor_ops = {
> + .get_temp = scmi_read_temp,
> +};
> +
> +static int scmi_hwmon_probe(struct platform_device *pdev)
> +{
> + int idx;
> + u16 nr_sensors, i;
> + int num_temp = 0, num_volt = 0, num_current = 0, num_power = 0;
> + int num_energy = 0;
> + struct device *hwdev, *dev = &pdev->dev;
> + struct scmi_sensors *scmi_sensors;
> + const struct scmi_handle *handle = devm_scmi_handle_get(dev);
> +
> + if (IS_ERR_OR_NULL(handle) || !handle->sensor_ops)
> + return -EPROBE_DEFER;
> +
> + nr_sensors = handle->sensor_ops->count_get(handle);
> + if (!nr_sensors)
> + return -EIO;
> +
> + scmi_sensors = devm_kzalloc(dev, sizeof(*scmi_sensors), GFP_KERNEL);
> + if (!scmi_sensors)
> + return -ENOMEM;
> +
> + scmi_sensors->data = devm_kcalloc(dev, nr_sensors,
> + sizeof(*scmi_sensors->data),
> + GFP_KERNEL);
> + if (!scmi_sensors->data)
> + return -ENOMEM;
> +
> + scmi_sensors->attrs = devm_kcalloc(dev, (nr_sensors * 2) + 1,
> + sizeof(*scmi_sensors->attrs),
> + GFP_KERNEL);
> + if (!scmi_sensors->attrs)
> + return -ENOMEM;
> +
> + scmi_sensors->handle = handle;
> +
> + for (i = 0, idx = 0; i < nr_sensors; i++) {
> + struct sensor_data *sensor = &scmi_sensors->data[idx];
> +
> + sensor->info = handle->sensor_ops->info_get(handle, i);
> + if (!sensor->info)
> + return PTR_ERR(sensor->info);
> +
> + switch (sensor->info->type) {
> + case TEMPERATURE_C:
> + snprintf(sensor->input, sizeof(sensor->input),
> + "temp%d_input", num_temp + 1);
> + snprintf(sensor->label, sizeof(sensor->input),
> + "temp%d_label", num_temp + 1);
> + num_temp++;
> + break;
> + case VOLTAGE:
> + snprintf(sensor->input, sizeof(sensor->input),
> + "in%d_input", num_volt);
> + snprintf(sensor->label, sizeof(sensor->input),
> + "in%d_label", num_volt);
> + num_volt++;
> + break;
> + case CURRENT:
> + snprintf(sensor->input, sizeof(sensor->input),
> + "curr%d_input", num_current + 1);
> + snprintf(sensor->label, sizeof(sensor->input),
> + "curr%d_label", num_current + 1);
> + num_current++;
> + break;
> + case POWER:
> + snprintf(sensor->input, sizeof(sensor->input),
> + "power%d_input", num_power + 1);
> + snprintf(sensor->label, sizeof(sensor->input),
> + "power%d_label", num_power + 1);
> + num_power++;
> + break;
> + case ENERGY:
> + snprintf(sensor->input, sizeof(sensor->input),
> + "energy%d_input", num_energy + 1);
> + snprintf(sensor->label, sizeof(sensor->input),
> + "energy%d_label", num_energy + 1);
> + num_energy++;
> + break;
> + default:
> + continue;
> + }
> +
> + sensor->dev_attr_input.attr.mode = S_IRUGO;
> + sensor->dev_attr_input.show = scmi_show_sensor;
> + sensor->dev_attr_input.attr.name = sensor->input;
> +
> + sensor->dev_attr_label.attr.mode = S_IRUGO;
> + sensor->dev_attr_label.show = scmi_show_label;
> + sensor->dev_attr_label.attr.name = sensor->label;
> +
> + scmi_sensors->attrs[idx << 1] = &sensor->dev_attr_input.attr;
> + scmi_sensors->attrs[(idx << 1) + 1] =
> + &sensor->dev_attr_label.attr;
> +
> + sysfs_attr_init(scmi_sensors->attrs[idx << 1]);
> + sysfs_attr_init(scmi_sensors->attrs[(idx << 1) + 1]);
> + idx++;
> + }
> +
> + scmi_sensors->group.attrs = scmi_sensors->attrs;
> + scmi_sensors->groups[0] = &scmi_sensors->group;
> +
> + platform_set_drvdata(pdev, scmi_sensors);
> +
> + hwdev = devm_hwmon_device_register_with_groups(dev, "scmi_sensors",
> + scmi_sensors,
> + scmi_sensors->groups);
Can you rework this to use devm_hwmon_device_register_with_info(),
and if possible let it handle the thermal registration ?
Thanks,
Guenter
> +
> + if (IS_ERR(hwdev))
> + return PTR_ERR(hwdev);
> +
> + /*
> + * Register the temperature sensors with the thermal framework
> + * to allow their usage in setting up the thermal zones from
> + * device tree.
> + *
> + * NOTE: Not all temperature sensors maybe used for thermal
> + * control
> + */
> + INIT_LIST_HEAD(&scmi_sensors->thermal_zones);
> + for (i = 0; i < nr_sensors; i++) {
> + struct sensor_data *sensor = &scmi_sensors->data[i];
> + const struct scmi_sensor_info *info = sensor->info;
> + struct thermal_zone_device *z;
> + struct scmi_thermal_zone *zone;
> +
> + if (info->type != TEMPERATURE_C)
> + continue;
> +
> + zone = devm_kzalloc(dev, sizeof(*zone), GFP_KERNEL);
> + if (!zone)
> + return -ENOMEM;
> +
> + zone->sensor_id = i;
> + zone->scmi_sensors = scmi_sensors;
> + z = devm_thermal_zone_of_sensor_register(dev, info->id, zone,
> + &scmi_sensor_ops);
> + /*
> + * The call to thermal_zone_of_sensor_register returns
> + * an error for sensors that are not associated with
> + * any thermal zones or if the thermal subsystem is
> + * not configured.
> + */
> + if (IS_ERR(z)) {
> + devm_kfree(dev, zone);
> + continue;
> + }
> + }
> +
> + return 0;
> +}
> +
> +static struct platform_driver scmi_hwmon_platdrv = {
> + .driver = {
> + .name = "scmi-hwmon",
> + },
> + .probe = scmi_hwmon_probe,
> +};
> +module_platform_driver(scmi_hwmon_platdrv);
> +
> +MODULE_AUTHOR("Punit Agrawal <[email protected]>");
> +MODULE_DESCRIPTION("ARM SCMI HWMON interface driver");
> +MODULE_LICENSE("GPL v2");
> --
> 2.7.4
>
On 04/08/17 20:32, Guenter Roeck wrote:
> On Fri, Aug 04, 2017 at 03:31:42PM +0100, Sudeep Holla wrote:
[...]
>> + platform_set_drvdata(pdev, scmi_sensors);
>> +
>> + hwdev = devm_hwmon_device_register_with_groups(dev, "scmi_sensors",
>> + scmi_sensors,
>> + scmi_sensors->groups);
>
> Can you rework this to use devm_hwmon_device_register_with_info(),
> and if possible let it handle the thermal registration ?
>
Thanks for the pointers. I will check on the possibility and use it if
possible.
--
Regards,
Sudeep
On Fri, Aug 4, 2017 at 8:01 PM, Sudeep Holla <[email protected]> wrote:
....
> +int scmi_do_xfer(const struct scmi_handle *handle, struct scmi_xfer *xfer)
> +{
> + int ret;
> + int timeout;
> + struct scmi_info *info = handle_to_scmi_info(handle);
> + struct device *dev = info->dev;
> +
> + ret = mbox_send_message(info->tx_chan, xfer);
>
NAK
This is still not fixed. For the reasons mentioned many times like
here... https://lkml.org/lkml/2017/7/7/465
On 08/08/17 03:46, Jassi Brar wrote:
> On Fri, Aug 4, 2017 at 8:01 PM, Sudeep Holla <[email protected]> wrote:
>
> ....
>> +int scmi_do_xfer(const struct scmi_handle *handle, struct scmi_xfer *xfer)
>> +{
>> + int ret;
>> + int timeout;
>> + struct scmi_info *info = handle_to_scmi_info(handle);
>> + struct device *dev = info->dev;
>> +
>> + ret = mbox_send_message(info->tx_chan, xfer);
>>
> NAK
>
> This is still not fixed. For the reasons mentioned many times like
> here... https://lkml.org/lkml/2017/7/7/465
>
Since SCPI and SCMI are based on doorbell designs like ACPI PCC, they
can't send any data as all the data are part of shared memory.
What data needs to be sent from SCPI/SCMI as part of mbox_send_message
in your opinion ? I can't think of any generic way to form this data.
It's not possible to generalize that and SCPI/SCMI's transport is
specifically designed in that way to avoid any kind of dependency
on the mailbox hardware so that the protocol can be generic exactly like
ACPI PCC.
I understand your concern on how existing mailbox controllers work with
it. They can't as they stand today as they expect some specific data
based on the hardware or the protocol they support on it.
One option to deal with this is to have a generic DT based doorbell kind
of controller driver but I am not sure if we can define register level
bindings like ACPI PCC.
Or we teach them to ignore the data if it's not present. I don't like
the idea of adding shim layer for each of the controller as each one
expect different format and more over SCPI/SCMI doesn't even require it
to support SCMI on those mailbox controllers. That may end up with 2x
drivers(one actual driver and and another shim layer for it)
--
Regards,
Sudeep
On Tue, Aug 8, 2017 at 2:59 PM, Sudeep Holla <[email protected]> wrote:
>
>
> On 08/08/17 03:46, Jassi Brar wrote:
>> On Fri, Aug 4, 2017 at 8:01 PM, Sudeep Holla <[email protected]> wrote:
>>
>> ....
>>> +int scmi_do_xfer(const struct scmi_handle *handle, struct scmi_xfer *xfer)
>>> +{
>>> + int ret;
>>> + int timeout;
>>> + struct scmi_info *info = handle_to_scmi_info(handle);
>>> + struct device *dev = info->dev;
>>> +
>>> + ret = mbox_send_message(info->tx_chan, xfer);
>>>
>> NAK
>>
>> This is still not fixed. For the reasons mentioned many times like
>> here... https://lkml.org/lkml/2017/7/7/465
>>
>
> Since SCPI and SCMI are based on doorbell designs like ACPI PCC, they
> can't send any data as all the data are part of shared memory.
>
> What data needs to be sent from SCPI/SCMI as part of mbox_send_message
> in your opinion ? I can't think of any generic way to form this data.
>
https://lkml.org/lkml/2017/7/7/290
On 04-08-17, 15:31, Sudeep Holla wrote:
I don't think its the Microsoft exchange server which screwed up tabs and
spaces, but you.
> diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm
> index 2011fec2d6ad..c34633855bc7 100644
> --- a/drivers/cpufreq/Kconfig.arm
> +++ b/drivers/cpufreq/Kconfig.arm
> @@ -215,6 +215,17 @@ config ARM_SA1100_CPUFREQ
> config ARM_SA1110_CPUFREQ
> bool
>
> +config ARM_SCMI_CPUFREQ
> + tristate "SCMI based CPUfreq driver"
You have used spaces here instead of tab and at multiple other places, can you
please fix them all ?
> + depends on ARM_SCMI_PROTOCOL || COMPILE_TEST
> + select PM_OPP
> + help
> + This adds the CPUfreq driver support for ARM platforms using SCMI
> + protocol for CPU power management.
> +
> + This driver uses SCMI Message Protocol driver to interact with the
> + firmware providing the CPU DVFS functionality.
> +
> config ARM_SCPI_CPUFREQ
> tristate "SCPI based CPUfreq driver"
> depends on ARM_BIG_LITTLE_CPUFREQ && ARM_SCPI_PROTOCOL && COMMON_CLK_SCPI
> diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile
> index ab3a42cd29ef..4810b45568d3 100644
> --- a/drivers/cpufreq/Makefile
> +++ b/drivers/cpufreq/Makefile
> @@ -72,6 +72,7 @@ obj-$(CONFIG_ARM_S3C64XX_CPUFREQ) += s3c64xx-cpufreq.o
> obj-$(CONFIG_ARM_S5PV210_CPUFREQ) += s5pv210-cpufreq.o
> obj-$(CONFIG_ARM_SA1100_CPUFREQ) += sa1100-cpufreq.o
> obj-$(CONFIG_ARM_SA1110_CPUFREQ) += sa1110-cpufreq.o
> +obj-$(CONFIG_ARM_SCMI_CPUFREQ) += scmi-cpufreq.o
> obj-$(CONFIG_ARM_SCPI_CPUFREQ) += scpi-cpufreq.o
> obj-$(CONFIG_ARM_SPEAR_CPUFREQ) += spear-cpufreq.o
> obj-$(CONFIG_ARM_STI_CPUFREQ) += sti-cpufreq.o
> diff --git a/drivers/cpufreq/scmi-cpufreq.c b/drivers/cpufreq/scmi-cpufreq.c
> new file mode 100644
> index 000000000000..034359cafea5
> --- /dev/null
> +++ b/drivers/cpufreq/scmi-cpufreq.c
> @@ -0,0 +1,268 @@
> +/*
> + * System Control and Power Interface (SCMI) based CPUFreq Interface driver
> + *
> + * Copyright (C) 2017 ARM Ltd.
> + * Sudeep Holla <[email protected]>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + * This program is distributed "as is" WITHOUT ANY WARRANTY of any
> + * kind, whether express or implied; without even the implied warranty
> + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + */
> +
> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> +
> +#include <linux/cpu.h>
> +#include <linux/cpufreq.h>
> +#include <linux/cpumask.h>
> +#include <linux/cpu_cooling.h>
> +#include <linux/export.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_opp.h>
> +#include <linux/slab.h>
> +#include <linux/scmi_protocol.h>
> +#include <linux/types.h>
> +
> +struct scmi_data {
> + int domain_id;
> + struct device *cpu_dev;
> + struct thermal_cooling_device *cdev;
> + const struct scmi_handle *handle;
This stores the same handle pointer which is stored in the global variable
below. Right? Why keep a local variable here at all ?
> +};
> +
> +static const struct scmi_handle *handle;
> +
> +unsigned int scmi_cpufreq_get_rate(unsigned int cpu)
> +{
> + int ret;
> + unsigned long rate;
> + struct cpufreq_policy *policy = cpufreq_cpu_get_raw(cpu);
> + struct scmi_data *priv = policy->driver_data;
> + struct scmi_perf_ops *perf_ops = priv->handle->perf_ops;
Normally people prefer to keep these definitions in decreasing order of their
lengths. i.e. ret and rate would be defined in the last line. Though I would
leave it to you to decide.
> +
> + ret = perf_ops->freq_get(priv->handle, priv->domain_id, &rate, false);
> + if (ret)
> + return CPUFREQ_ENTRY_INVALID;
This is something special which is used only when we are returning indexes and
I am not sure if this will have benefit here. I will rather return 0 here.
That's what other drivers are doing.
> + return rate / 1000;
> +}
> +
> +static int
> +scmi_cpufreq_set_target(struct cpufreq_policy *policy, unsigned int index)
> +{
> + struct scmi_data *priv = policy->driver_data;
> + struct scmi_perf_ops *perf_ops = priv->handle->perf_ops;
> + u64 freq = policy->freq_table[index].frequency * 1000;
> +
> + return perf_ops->freq_set(priv->handle, priv->domain_id, freq, false);
> +}
I suppose any CPU can change the frequency of any other CPU here, right? You
must set policy->dvfs_possible_from_any_cpu = true, from ->init() then.
> +static int
> +scmi_get_sharing_cpus(struct device *cpu_dev, struct cpumask *cpumask)
> +{
> + int cpu, domain, ret = 0;
You don't need to initialize ret here and I would rather name it tdomain or
something else. ret is a lot used to store error/success values, which isn't
your case.
> + struct device *tcpu_dev;
> +
> + domain = handle->perf_ops->device_domain_id(cpu_dev);
> + if (domain < 0)
> + return domain;
> +
> + cpumask_set_cpu(cpu_dev->id, cpumask);
The mask already have this set from the core, you don't need to do it again.
> + for_each_possible_cpu(cpu) {
> + if (cpu == cpu_dev->id)
> + continue;
> +
> + tcpu_dev = get_cpu_device(cpu);
> + if (!tcpu_dev)
> + continue;
> +
> + ret = handle->perf_ops->device_domain_id(tcpu_dev);
> + if (ret == domain)
> + cpumask_set_cpu(cpu, cpumask);
> + }
> +
> + return 0;
> +}
> +
> +static int scmi_cpufreq_init(struct cpufreq_policy *policy)
> +{
> + int ret;
> + unsigned int latency;
> + struct device *cpu_dev;
> + struct scmi_data *priv;
> + struct cpufreq_frequency_table *freq_table;
> +
> + cpu_dev = get_cpu_device(policy->cpu);
> + if (!cpu_dev) {
> + pr_err("failed to get cpu%d device\n", policy->cpu);
> + return -ENODEV;
> + }
> +
> + ret = handle->perf_ops->add_opps_to_device(cpu_dev);
> + if (ret) {
> + dev_warn(cpu_dev, "failed to add opps to the device\n");
> + return ret;
> + }
> +
> + ret = scmi_get_sharing_cpus(cpu_dev, policy->cpus);
> + if (ret) {
> + dev_warn(cpu_dev, "failed to get sharing cpumask\n");
> + return ret;
> + }
> +
> + ret = dev_pm_opp_set_sharing_cpus(cpu_dev, policy->cpus);
> + if (ret) {
> + dev_err(cpu_dev, "%s: failed to mark OPPs as shared: %d\n",
> + __func__, ret);
> + return ret;
> + }
> +
> + /*
> + * But we need OPP table to function so if it is not there let's
> + * give platform code chance to provide it for us.
> + */
How are we getting the OPPs? DT or non DT ?
> + ret = dev_pm_opp_get_opp_count(cpu_dev);
> + if (ret <= 0) {
> + dev_dbg(cpu_dev, "OPP table is not ready, deferring probe\n");
> + ret = -EPROBE_DEFER;
> + goto out_free_opp;
> + }
> +
> + priv = kzalloc(sizeof(*priv), GFP_KERNEL);
> + if (!priv) {
> + ret = -ENOMEM;
> + goto out_free_opp;
> + }
> +
> + ret = dev_pm_opp_init_cpufreq_table(cpu_dev, &freq_table);
> + if (ret) {
> + dev_err(cpu_dev, "failed to init cpufreq table: %d\n", ret);
> + goto out_free_priv;
> + }
> +
> + priv->handle = handle;
> + priv->cpu_dev = cpu_dev;
> + priv->domain_id = handle->perf_ops->device_domain_id(cpu_dev);
> +
> + policy->driver_data = priv;
> +
> + ret = cpufreq_table_validate_and_show(policy, freq_table);
> + if (ret) {
> + dev_err(cpu_dev, "%s: invalid frequency table: %d\n", __func__,
> + ret);
> + goto out_free_cpufreq_table;
> + }
> +
> + latency = handle->perf_ops->get_transition_latency(cpu_dev);
> + if (!latency)
> + latency = CPUFREQ_ETERNAL;
> +
> + policy->cpuinfo.transition_latency = latency;
> +
> + return 0;
> +
> +out_free_cpufreq_table:
> + dev_pm_opp_free_cpufreq_table(cpu_dev, &freq_table);
> +out_free_priv:
> + kfree(priv);
> +out_free_opp:
> + dev_pm_opp_cpumask_remove_table(policy->cpus);
> +
> + return ret;
> +}
> +
> +static int scmi_cpufreq_exit(struct cpufreq_policy *policy)
> +{
> + struct scmi_data *priv = policy->driver_data;
> +
> + cpufreq_cooling_unregister(priv->cdev);
> + dev_pm_opp_free_cpufreq_table(priv->cpu_dev, &policy->freq_table);
> + dev_pm_opp_cpumask_remove_table(policy->related_cpus);
> + kfree(priv);
I would rather swap the above two lines to keep the same order as in probe.
Though nothing would fail with the current code as well.
> +
> + return 0;
> +}
> +
> +static void scmi_cpufreq_ready(struct cpufreq_policy *policy)
> +{
> + struct scmi_data *priv = policy->driver_data;
> + struct device_node *np = of_node_get(priv->cpu_dev->of_node);
> +
> + if (WARN_ON(!np))
> + return;
> +
> + if (of_find_property(np, "#cooling-cells", NULL)) {
> + u32 pcoeff = 0;
> +
> + of_property_read_u32(np, "dynamic-power-coefficient",
> + &pcoeff);
> +
> + priv->cdev = of_cpufreq_power_cooling_register(np, policy,
> + pcoeff, NULL);
> + if (IS_ERR(priv->cdev)) {
> + dev_err(priv->cpu_dev,
> + "running cpufreq without cooling device: %ld\n",
> + PTR_ERR(priv->cdev));
> +
> + priv->cdev = NULL;
> + }
> + }
> +
> + of_node_put(np);
> +}
> +
> +static struct cpufreq_driver scmi_cpufreq_driver = {
> + .name = "scmi",
> + .flags = CPUFREQ_STICKY |
> + CPUFREQ_HAVE_GOVERNOR_PER_POLICY |
> + CPUFREQ_NEED_INITIAL_FREQ_CHECK,
> + .verify = cpufreq_generic_frequency_table_verify,
> + .attr = cpufreq_generic_attr,
> + .target_index = scmi_cpufreq_set_target,
> + .get = scmi_cpufreq_get_rate,
> + .init = scmi_cpufreq_init,
> + .exit = scmi_cpufreq_exit,
> + .ready = scmi_cpufreq_ready,
> +};
Above block has lots of space/tab issues. Can you please use tabs before "="
instead?
> +static int scmi_cpufreq_probe(struct platform_device *pdev)
> +{
> + int ret;
> +
> + handle = devm_scmi_handle_get(&pdev->dev);
What code is creating this pdev ?
> +
> + if (IS_ERR_OR_NULL(handle) || !handle->perf_ops)
> + return -EPROBE_DEFER;
> +
> + ret = cpufreq_register_driver(&scmi_cpufreq_driver);
> + if (ret) {
> + dev_err(&pdev->dev, "%s: registering cpufreq failed, err: %d\n",
> + __func__, ret);
> + }
> +
> + return ret;
> +}
> +
> +static int scmi_cpufreq_remove(struct platform_device *pdev)
> +{
> + cpufreq_unregister_driver(&scmi_cpufreq_driver);
> + return 0;
> +}
> +
> +static struct platform_driver scmi_cpufreq_platdrv = {
> + .driver = {
> + .name = "scmi-cpufreq",
> + },
> + .probe = scmi_cpufreq_probe,
> + .remove = scmi_cpufreq_remove,
> +};
> +module_platform_driver(scmi_cpufreq_platdrv);
> +
> +MODULE_AUTHOR("Sudeep Holla <[email protected]>");
> +MODULE_DESCRIPTION("ARM SCMI CPUFreq interface driver");
> +MODULE_LICENSE("GPL v2");
--
viresh
On 04-08-17, 15:31, Sudeep Holla wrote:
> The cpufreq core provides option for drivers to implement fast_switch
> callback which is invoked for frequency switching from interrupt context.
>
> This patch adds support for fast_switch callback in SCMI cpufreq driver
> by making use of polling based SCMI transfer. It also sets the flag
> fast_switch_possible.
>
> Cc: "Rafael J. Wysocki" <[email protected]>
> Cc: Viresh Kumar <[email protected]>
> Cc: [email protected]
> Signed-off-by: Sudeep Holla <[email protected]>
> ---
> drivers/cpufreq/scmi-cpufreq.c | 16 ++++++++++++++++
> 1 file changed, 16 insertions(+)
>
> diff --git a/drivers/cpufreq/scmi-cpufreq.c b/drivers/cpufreq/scmi-cpufreq.c
> index 034359cafea5..cb1084cb1ef1 100644
> --- a/drivers/cpufreq/scmi-cpufreq.c
> +++ b/drivers/cpufreq/scmi-cpufreq.c
> @@ -61,6 +61,19 @@ scmi_cpufreq_set_target(struct cpufreq_policy *policy, unsigned int index)
> return perf_ops->freq_set(priv->handle, priv->domain_id, freq, false);
> }
>
> +static unsigned int scmi_cpufreq_fast_switch(struct cpufreq_policy *policy,
> + unsigned int target_freq)
> +{
> + struct scmi_data *priv = policy->driver_data;
> + struct scmi_perf_ops *perf_ops = priv->handle->perf_ops;
> +
> + if (!perf_ops->freq_set(priv->handle, priv->domain_id,
> + target_freq * 1000, true))
> + return target_freq;
> +
> + return CPUFREQ_ENTRY_INVALID;
> +}
This is very much similar to the target routine, perhaps we should write another
local routine which is used by both target and fast switch.
Do we guarantee that the frequency is changed by the time this routine returns?
Or we just send a SCMI request and return back ?
If we just send the request and don't wait for freq to get changed, what
protects another scmi_cpufreq_fast_switch() to get called before the first one
is finished? And what will happen on that call ?
> static int
> scmi_get_sharing_cpus(struct device *cpu_dev, struct cpumask *cpumask)
> {
> @@ -164,6 +177,7 @@ static int scmi_cpufreq_init(struct cpufreq_policy *policy)
>
> policy->cpuinfo.transition_latency = latency;
>
> + policy->fast_switch_possible = true;
> return 0;
>
> out_free_cpufreq_table:
> @@ -180,6 +194,7 @@ static int scmi_cpufreq_exit(struct cpufreq_policy *policy)
> {
> struct scmi_data *priv = policy->driver_data;
>
> + policy->fast_switch_possible = false;
> cpufreq_cooling_unregister(priv->cdev);
> dev_pm_opp_free_cpufreq_table(priv->cpu_dev, &policy->freq_table);
> dev_pm_opp_cpumask_remove_table(policy->related_cpus);
> @@ -228,6 +243,7 @@ static struct cpufreq_driver scmi_cpufreq_driver = {
> .init = scmi_cpufreq_init,
> .exit = scmi_cpufreq_exit,
> .ready = scmi_cpufreq_ready,
> + .fast_switch = scmi_cpufreq_fast_switch,
Maybe add it right after target_index ?
--
viresh
On 09/08/17 05:18, Viresh Kumar wrote:
> On 04-08-17, 15:31, Sudeep Holla wrote:
>
> I don't think its the Microsoft exchange server which screwed up tabs and
> spaces, but you.
>
Indeed, copy paste to blame ;)
>> diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm
>> index 2011fec2d6ad..c34633855bc7 100644
>> --- a/drivers/cpufreq/Kconfig.arm
>> +++ b/drivers/cpufreq/Kconfig.arm
>> @@ -215,6 +215,17 @@ config ARM_SA1100_CPUFREQ
>> config ARM_SA1110_CPUFREQ
>> bool
>>
>> +config ARM_SCMI_CPUFREQ
>> + tristate "SCMI based CPUfreq driver"
>
> You have used spaces here instead of tab and at multiple other places, can you
> please fix them all ?
>
Done locally.
>> + depends on ARM_SCMI_PROTOCOL || COMPILE_TEST
>> + select PM_OPP
>> + help
>> + This adds the CPUfreq driver support for ARM platforms using SCMI
>> + protocol for CPU power management.
>> +
>> + This driver uses SCMI Message Protocol driver to interact with the
>> + firmware providing the CPU DVFS functionality.
>> +
>> config ARM_SCPI_CPUFREQ
>> tristate "SCPI based CPUfreq driver"
>> depends on ARM_BIG_LITTLE_CPUFREQ && ARM_SCPI_PROTOCOL && COMMON_CLK_SCPI
>> diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile
>> index ab3a42cd29ef..4810b45568d3 100644
>> --- a/drivers/cpufreq/Makefile
>> +++ b/drivers/cpufreq/Makefile
>> @@ -72,6 +72,7 @@ obj-$(CONFIG_ARM_S3C64XX_CPUFREQ) += s3c64xx-cpufreq.o
>> obj-$(CONFIG_ARM_S5PV210_CPUFREQ) += s5pv210-cpufreq.o
>> obj-$(CONFIG_ARM_SA1100_CPUFREQ) += sa1100-cpufreq.o
>> obj-$(CONFIG_ARM_SA1110_CPUFREQ) += sa1110-cpufreq.o
>> +obj-$(CONFIG_ARM_SCMI_CPUFREQ) += scmi-cpufreq.o
>> obj-$(CONFIG_ARM_SCPI_CPUFREQ) += scpi-cpufreq.o
>> obj-$(CONFIG_ARM_SPEAR_CPUFREQ) += spear-cpufreq.o
>> obj-$(CONFIG_ARM_STI_CPUFREQ) += sti-cpufreq.o
>> diff --git a/drivers/cpufreq/scmi-cpufreq.c b/drivers/cpufreq/scmi-cpufreq.c
>> new file mode 100644
>> index 000000000000..034359cafea5
>> --- /dev/null
>> +++ b/drivers/cpufreq/scmi-cpufreq.c
>> @@ -0,0 +1,268 @@
>> +/*
>> + * System Control and Power Interface (SCMI) based CPUFreq Interface driver
>> + *
>> + * Copyright (C) 2017 ARM Ltd.
>> + * Sudeep Holla <[email protected]>
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License version 2 as
>> + * published by the Free Software Foundation.
>> + *
>> + * This program is distributed "as is" WITHOUT ANY WARRANTY of any
>> + * kind, whether express or implied; without even the implied warranty
>> + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
>> + * GNU General Public License for more details.
>> + */
>> +
>> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
>> +
>> +#include <linux/cpu.h>
>> +#include <linux/cpufreq.h>
>> +#include <linux/cpumask.h>
>> +#include <linux/cpu_cooling.h>
>> +#include <linux/export.h>
>> +#include <linux/module.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/pm_opp.h>
>> +#include <linux/slab.h>
>> +#include <linux/scmi_protocol.h>
>> +#include <linux/types.h>
>> +
>> +struct scmi_data {
>> + int domain_id;
>> + struct device *cpu_dev;
>> + struct thermal_cooling_device *cdev;
>> + const struct scmi_handle *handle;
>
> This stores the same handle pointer which is stored in the global variable
> below. Right? Why keep a local variable here at all ?
>
Yes, you are right. Initially, started with just private pointers and
then added global. I was thinking of calling devm_scmi_handle_get per
policy to reflect the refcount correctly and drop global variable. Let
me know what you think.
>> +};
>> +
>> +static const struct scmi_handle *handle;
>> +
>> +unsigned int scmi_cpufreq_get_rate(unsigned int cpu)
>> +{
>> + int ret;
>> + unsigned long rate;
>> + struct cpufreq_policy *policy = cpufreq_cpu_get_raw(cpu);
>> + struct scmi_data *priv = policy->driver_data;
>> + struct scmi_perf_ops *perf_ops = priv->handle->perf_ops;
>
> Normally people prefer to keep these definitions in decreasing order of their
> lengths. i.e. ret and rate would be defined in the last line. Though I would
> leave it to you to decide.
>
I too prefer that, will fix that.
>> +
>> + ret = perf_ops->freq_get(priv->handle, priv->domain_id, &rate, false);
>> + if (ret)
>> + return CPUFREQ_ENTRY_INVALID;
>
> This is something special which is used only when we are returning indexes and
> I am not sure if this will have benefit here. I will rather return 0 here.
> That's what other drivers are doing.
>
Indeed had 0 initially but changed as per Juri's suggestion. But is 0
treated as failure and still running at current OPP ? and not 0KHz I assume.
>> + return rate / 1000;
>> +}
>> +
>> +static int
>> +scmi_cpufreq_set_target(struct cpufreq_policy *policy, unsigned int index)
>> +{
>> + struct scmi_data *priv = policy->driver_data;
>> + struct scmi_perf_ops *perf_ops = priv->handle->perf_ops;
>> + u64 freq = policy->freq_table[index].frequency * 1000;
>> +
>> + return perf_ops->freq_set(priv->handle, priv->domain_id, freq, false);
>> +}
>
> I suppose any CPU can change the frequency of any other CPU here, right? You
> must set policy->dvfs_possible_from_any_cpu = true, from ->init() then.
>
OK, I missed to see something like that exists, will do.
>> +static int
>> +scmi_get_sharing_cpus(struct device *cpu_dev, struct cpumask *cpumask)
>> +{
>> + int cpu, domain, ret = 0;
>
> You don't need to initialize ret here and I would rather name it tdomain or
> something else. ret is a lot used to store error/success values, which isn't
> your case.
>
Agreed.
>> + struct device *tcpu_dev;
>> +
>> + domain = handle->perf_ops->device_domain_id(cpu_dev);
>> + if (domain < 0)
>> + return domain;
>> +
>> + cpumask_set_cpu(cpu_dev->id, cpumask);
>
> The mask already have this set from the core, you don't need to do it again.
>
Cool, wasn't aware of that.
>> + for_each_possible_cpu(cpu) {
>> + if (cpu == cpu_dev->id)
>> + continue;
>> +
>> + tcpu_dev = get_cpu_device(cpu);
>> + if (!tcpu_dev)
>> + continue;
>> +
>> + ret = handle->perf_ops->device_domain_id(tcpu_dev);
>> + if (ret == domain)
>> + cpumask_set_cpu(cpu, cpumask);
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +static int scmi_cpufreq_init(struct cpufreq_policy *policy)
>> +{
>> + int ret;
>> + unsigned int latency;
>> + struct device *cpu_dev;
>> + struct scmi_data *priv;
>> + struct cpufreq_frequency_table *freq_table;
>> +
>> + cpu_dev = get_cpu_device(policy->cpu);
>> + if (!cpu_dev) {
>> + pr_err("failed to get cpu%d device\n", policy->cpu);
>> + return -ENODEV;
>> + }
>> +
>> + ret = handle->perf_ops->add_opps_to_device(cpu_dev);
>> + if (ret) {
>> + dev_warn(cpu_dev, "failed to add opps to the device\n");
>> + return ret;
>> + }
>> +
>> + ret = scmi_get_sharing_cpus(cpu_dev, policy->cpus);
>> + if (ret) {
>> + dev_warn(cpu_dev, "failed to get sharing cpumask\n");
>> + return ret;
>> + }
>> +
>> + ret = dev_pm_opp_set_sharing_cpus(cpu_dev, policy->cpus);
>> + if (ret) {
>> + dev_err(cpu_dev, "%s: failed to mark OPPs as shared: %d\n",
>> + __func__, ret);
>> + return ret;
>> + }
>> +
>> + /*
>> + * But we need OPP table to function so if it is not there let's
>> + * give platform code chance to provide it for us.
>> + */
>
> How are we getting the OPPs? DT or non DT ?
>
Non DT :), from the firmware.
>> + ret = dev_pm_opp_get_opp_count(cpu_dev);
>> + if (ret <= 0) {
>> + dev_dbg(cpu_dev, "OPP table is not ready, deferring probe\n");
>> + ret = -EPROBE_DEFER;
>> + goto out_free_opp;
>> + }
>> +
>> + priv = kzalloc(sizeof(*priv), GFP_KERNEL);
>> + if (!priv) {
>> + ret = -ENOMEM;
>> + goto out_free_opp;
>> + }
>> +
>> + ret = dev_pm_opp_init_cpufreq_table(cpu_dev, &freq_table);
>> + if (ret) {
>> + dev_err(cpu_dev, "failed to init cpufreq table: %d\n", ret);
>> + goto out_free_priv;
>> + }
>> +
>> + priv->handle = handle;
>> + priv->cpu_dev = cpu_dev;
>> + priv->domain_id = handle->perf_ops->device_domain_id(cpu_dev);
>> +
>> + policy->driver_data = priv;
>> +
>> + ret = cpufreq_table_validate_and_show(policy, freq_table);
>> + if (ret) {
>> + dev_err(cpu_dev, "%s: invalid frequency table: %d\n", __func__,
>> + ret);
>> + goto out_free_cpufreq_table;
>> + }
>> +
>> + latency = handle->perf_ops->get_transition_latency(cpu_dev);
>> + if (!latency)
>> + latency = CPUFREQ_ETERNAL;
>> +
>> + policy->cpuinfo.transition_latency = latency;
>> +
>> + return 0;
>> +
>> +out_free_cpufreq_table:
>> + dev_pm_opp_free_cpufreq_table(cpu_dev, &freq_table);
>> +out_free_priv:
>> + kfree(priv);
>> +out_free_opp:
>> + dev_pm_opp_cpumask_remove_table(policy->cpus);
>> +
>> + return ret;
>> +}
>> +
>> +static int scmi_cpufreq_exit(struct cpufreq_policy *policy)
>> +{
>> + struct scmi_data *priv = policy->driver_data;
>> +
>> + cpufreq_cooling_unregister(priv->cdev);
>> + dev_pm_opp_free_cpufreq_table(priv->cpu_dev, &policy->freq_table);
>> + dev_pm_opp_cpumask_remove_table(policy->related_cpus);
>> + kfree(priv);
>
> I would rather swap the above two lines to keep the same order as in probe.
> Though nothing would fail with the current code as well.
>
Sure.
>> +
>> + return 0;
>> +}
>> +
>> +static void scmi_cpufreq_ready(struct cpufreq_policy *policy)
>> +{
>> + struct scmi_data *priv = policy->driver_data;
>> + struct device_node *np = of_node_get(priv->cpu_dev->of_node);
>> +
>> + if (WARN_ON(!np))
>> + return;
>> +
>> + if (of_find_property(np, "#cooling-cells", NULL)) {
>> + u32 pcoeff = 0;
>> +
>> + of_property_read_u32(np, "dynamic-power-coefficient",
>> + &pcoeff);
>> +
>> + priv->cdev = of_cpufreq_power_cooling_register(np, policy,
>> + pcoeff, NULL);
>> + if (IS_ERR(priv->cdev)) {
>> + dev_err(priv->cpu_dev,
>> + "running cpufreq without cooling device: %ld\n",
>> + PTR_ERR(priv->cdev));
>> +
>> + priv->cdev = NULL;
>> + }
>> + }
>> +
>> + of_node_put(np);
>> +}
>> +
>> +static struct cpufreq_driver scmi_cpufreq_driver = {
>> + .name = "scmi",
>> + .flags = CPUFREQ_STICKY |
>> + CPUFREQ_HAVE_GOVERNOR_PER_POLICY |
>> + CPUFREQ_NEED_INITIAL_FREQ_CHECK,
>> + .verify = cpufreq_generic_frequency_table_verify,
>> + .attr = cpufreq_generic_attr,
>> + .target_index = scmi_cpufreq_set_target,
>> + .get = scmi_cpufreq_get_rate,
>> + .init = scmi_cpufreq_init,
>> + .exit = scmi_cpufreq_exit,
>> + .ready = scmi_cpufreq_ready,
>> +};
>
> Above block has lots of space/tab issues. Can you please use tabs before "="
> instead?
>
OK, again copy pasted from some other driver ;)
>> +static int scmi_cpufreq_probe(struct platform_device *pdev)
>> +{
>> + int ret;
>> +
>> + handle = devm_scmi_handle_get(&pdev->dev);
>
> What code is creating this pdev ?
>
SCMI driver, once it finds the performance protocol is available
and setup/initialized.
Thanks for the review.
--
Regards,
Sudeep
On 09-08-17, 10:59, Sudeep Holla wrote:
> On 09/08/17 05:18, Viresh Kumar wrote:
> > This stores the same handle pointer which is stored in the global variable
> > below. Right? Why keep a local variable here at all ?
>
> Yes, you are right. Initially, started with just private pointers and
> then added global. I was thinking of calling devm_scmi_handle_get per
> policy to reflect the refcount correctly and drop global variable. Let
> me know what you think.
A refcount of 1 should be fine as well, i.e. For the cpufreq driver. Why would
SCMI care if we manage multiple policies here ? Unless it makes something within
SCMI core better.
> > This is something special which is used only when we are returning indexes and
> > I am not sure if this will have benefit here. I will rather return 0 here.
> > That's what other drivers are doing.
>
> Indeed had 0 initially but changed as per Juri's suggestion.
Maybe he suggested doing that in the fast switch routine ? As that's the normal
protocol there. Though I have sent a patch today to propose using 0 there as
well (you cc'd).
> But is 0
> treated as failure and still running at current OPP ?
You have used that in the ->get() routine. So the OPP isn't changing, but we are
just trying to fetch it. cpufreq core doesn't do a lot with the value returned
from here, but at one place we break early if 0 is returned. And so all drivers
are returning that.
> and not 0KHz I assume.
Yeah, 0 KHz is dead CPU really :)
> > I suppose any CPU can change the frequency of any other CPU here, right? You
> > must set policy->dvfs_possible_from_any_cpu = true, from ->init() then.
> >
>
> OK, I missed to see something like that exists, will do.
Fairly recent stuff, present in pm/linux-next only.
> >> + /*
> >> + * But we need OPP table to function so if it is not there let's
> >> + * give platform code chance to provide it for us.
> >> + */
> >
> > How are we getting the OPPs? DT or non DT ?
> >
>
> Non DT :), from the firmware.
I would improve the above comment in that case to clearly say that OPPs are
added by the platform, lets wait for it.
--
viresh
On 09/08/17 05:28, Viresh Kumar wrote:
> On 04-08-17, 15:31, Sudeep Holla wrote:
>> The cpufreq core provides option for drivers to implement fast_switch
>> callback which is invoked for frequency switching from interrupt context.
>>
>> This patch adds support for fast_switch callback in SCMI cpufreq driver
>> by making use of polling based SCMI transfer. It also sets the flag
>> fast_switch_possible.
>>
>> Cc: "Rafael J. Wysocki" <[email protected]>
>> Cc: Viresh Kumar <[email protected]>
>> Cc: [email protected]
>> Signed-off-by: Sudeep Holla <[email protected]>
>> ---
>> drivers/cpufreq/scmi-cpufreq.c | 16 ++++++++++++++++
>> 1 file changed, 16 insertions(+)
>>
>> diff --git a/drivers/cpufreq/scmi-cpufreq.c b/drivers/cpufreq/scmi-cpufreq.c
>> index 034359cafea5..cb1084cb1ef1 100644
>> --- a/drivers/cpufreq/scmi-cpufreq.c
>> +++ b/drivers/cpufreq/scmi-cpufreq.c
>> @@ -61,6 +61,19 @@ scmi_cpufreq_set_target(struct cpufreq_policy *policy, unsigned int index)
>> return perf_ops->freq_set(priv->handle, priv->domain_id, freq, false);
>> }
>>
>> +static unsigned int scmi_cpufreq_fast_switch(struct cpufreq_policy *policy,
>> + unsigned int target_freq)
>> +{
>> + struct scmi_data *priv = policy->driver_data;
>> + struct scmi_perf_ops *perf_ops = priv->handle->perf_ops;
>> +
>> + if (!perf_ops->freq_set(priv->handle, priv->domain_id,
>> + target_freq * 1000, true))
>> + return target_freq;
>> +
>> + return CPUFREQ_ENTRY_INVALID;
>> +}
>
> This is very much similar to the target routine, perhaps we should write another
> local routine which is used by both target and fast switch.
>
Just one difference, fast switch uses polling based mailbox while
target_index uses interrupt based. I thought initially to reuse, but
it's comes done to just perf_ops->freq_set, so dropped that idea.
> Do we guarantee that the frequency is changed by the time this routine returns?
No, firmware may return acknowledging the request not it's completion.
> Or we just send a SCMI request and return back ?
>
Yes, exactly.
> If we just send the request and don't wait for freq to get changed, what
> protects another scmi_cpufreq_fast_switch() to get called before the first one
> is finished? And what will happen on that call ?
Firmware needs to serialize or override based on the timing of the two
consecutive requests.
>
>> static int
>> scmi_get_sharing_cpus(struct device *cpu_dev, struct cpumask *cpumask)
>> {
>> @@ -164,6 +177,7 @@ static int scmi_cpufreq_init(struct cpufreq_policy *policy)
>>
>> policy->cpuinfo.transition_latency = latency;
>>
>> + policy->fast_switch_possible = true;
>> return 0;
>>
>> out_free_cpufreq_table:
>> @@ -180,6 +194,7 @@ static int scmi_cpufreq_exit(struct cpufreq_policy *policy)
>> {
>> struct scmi_data *priv = policy->driver_data;
>>
>> + policy->fast_switch_possible = false;
>> cpufreq_cooling_unregister(priv->cdev);
>> dev_pm_opp_free_cpufreq_table(priv->cpu_dev, &policy->freq_table);
>> dev_pm_opp_cpumask_remove_table(policy->related_cpus);
>> @@ -228,6 +243,7 @@ static struct cpufreq_driver scmi_cpufreq_driver = {
>> .init = scmi_cpufreq_init,
>> .exit = scmi_cpufreq_exit,
>> .ready = scmi_cpufreq_ready,
>> + .fast_switch = scmi_cpufreq_fast_switch,
>
> Maybe add it right after target_index ?
>
Done
--
Regards,
Sudeep
On 09-08-17, 11:09, Sudeep Holla wrote:
> Firmware needs to serialize or override based on the timing of the two
> consecutive requests.
Maybe add a comment over that routine and detail its working a bit? Like its not
synchronous, etc. I expect that you would also add a callback with SCMI, so that
the cpufreq driver is notified when frequency is changed ?
--
viresh
On 09/08/17 11:06, Viresh Kumar wrote:
> On 09-08-17, 10:59, Sudeep Holla wrote:
>> On 09/08/17 05:18, Viresh Kumar wrote:
>
>>> This stores the same handle pointer which is stored in the global variable
>>> below. Right? Why keep a local variable here at all ?
>>
>> Yes, you are right. Initially, started with just private pointers and
>> then added global. I was thinking of calling devm_scmi_handle_get per
>> policy to reflect the refcount correctly and drop global variable. Let
>> me know what you think.
>
> A refcount of 1 should be fine as well, i.e. For the cpufreq driver. Why would
> SCMI care if we manage multiple policies here ? Unless it makes something within
> SCMI core better.
>
Not really, just we can get rid of global pointer which may be need in
system with multiple scmi instances, but that's long way to go.
>>> This is something special which is used only when we are returning indexes and
>>> I am not sure if this will have benefit here. I will rather return 0 here.
>>> That's what other drivers are doing.
>>
>> Indeed had 0 initially but changed as per Juri's suggestion.
>
> Maybe he suggested doing that in the fast switch routine ? As that's the normal
> protocol there. Though I have sent a patch today to propose using 0 there as
> well (you cc'd).
>
Yes, saw that. I have changed both to 0 for now. I will watch that
thread and update if necessary before next posting.
>> But is 0
>> treated as failure and still running at current OPP ?
>
> You have used that in the ->get() routine. So the OPP isn't changing, but we are
> just trying to fetch it. cpufreq core doesn't do a lot with the value returned
> from here, but at one place we break early if 0 is returned. And so all drivers
> are returning that.
>
Agreed, I assumed _INVALID is new thing and changed at both target_indes
and fast_switch.
>> and not 0KHz I assume.
>
> Yeah, 0 KHz is dead CPU really :)
>
:)
>>> I suppose any CPU can change the frequency of any other CPU here, right? You
>>> must set policy->dvfs_possible_from_any_cpu = true, from ->init() then.
>>>
>>
>> OK, I missed to see something like that exists, will do.
>
> Fairly recent stuff, present in pm/linux-next only.
>
Oh OK.
>>>> + /*
>>>> + * But we need OPP table to function so if it is not there let's
>>>> + * give platform code chance to provide it for us.
>>>> + */
>>>
>>> How are we getting the OPPs? DT or non DT ?
>>>
>>
>> Non DT :), from the firmware.
>
> I would improve the above comment in that case to clearly say that OPPs are
> added by the platform, lets wait for it.
>
Done
--
Regards,
Sudeep
On 09/08/17 11:13, Viresh Kumar wrote:
> On 09-08-17, 11:09, Sudeep Holla wrote:
>> Firmware needs to serialize or override based on the timing of the two
>> consecutive requests.
>
> Maybe add a comment over that routine and detail its working a bit? Like its not
> synchronous, etc. I expect that you would also add a callback with SCMI, so that
> the cpufreq driver is notified when frequency is changed ?
>
OK, will do. Yes, I don't have firmware implementing notifications yet,
so waiting until I can test it.
--
Regards,
Sudeep
On Fri, Aug 04, 2017 at 03:31:28PM +0100, Sudeep Holla wrote:
> This patch adds devicetree binding for System Control and Management
> Interface (SCMI) 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.
>
> SCMI protocol is developed as better replacement to the existing SCPI
> which is not flexible and easily extensible.
>
> Cc: Rob Herring <[email protected]>
> Cc: Mark Rutland <[email protected]>
> Signed-off-by: Sudeep Holla <[email protected]>
> ---
> Documentation/devicetree/bindings/arm/arm,scmi.txt | 174 +++++++++++++++++++++
> 1 file changed, 174 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/arm/arm,scmi.txt
>
> diff --git a/Documentation/devicetree/bindings/arm/arm,scmi.txt b/Documentation/devicetree/bindings/arm/arm,scmi.txt
> new file mode 100644
> index 000000000000..33c16be58e72
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/arm/arm,scmi.txt
> @@ -0,0 +1,174 @@
> +System Control and Management Interface (SCMI) Message Protocol
> +----------------------------------------------------------
> +
> +The SCMI is intended to allow agents such as OSPM to manage various functions
> +that are provided by the hardware platform it is running on, including power
> +and performance functions.
> +
> +This binding is intended to define the interface the firmware implementing
> +the SCMI as described in ARM document number ARM DUI 0922B ("ARM System Control
> +and Management Interface Platform Design Document")[0] provide for OSPM in
> +the device tree.
> +
> +Required properties:
Please define this is a subnode of /firmware node.
> +
> +- compatible : shall be "arm,scmi"
> +- mboxes: List of phandle and mailbox channel specifiers. It should contain
> + exactly one or two mailboxes, one for transmitting messages("tx")
> + and another optional for receiving the notifications("rx") if
> + supported.
> +- mbox-names: shall be "tx" or "rx"
> +- shmem : List of phandle pointing to the shared memory(SHM) area as per
> + generic mailbox client binding.
> +
> +See Documentation/devicetree/bindings/mailbox/mailbox.txt for more details
> +about the generic mailbox controller and client driver bindings.
> +
> +The mailbox is the only permitted method of calling the SCMI firmware.
> +Mailbox doorbell is used as a mechanism to alert the presence of a
> +messages and/or notification.
> +
> +Each protocol supported shall have a sub-node with corresponding compatible
> +as described in the following sections. If the platform supports dedicated
> +communication channel for a particular protocol, the 3 properties namely:
> +mboxes, mbox-names and shmem shall be present in the sub-node corresponding
> +to that protocol.
> +
> +Clock/Performance bindings for the clocks/OPPs based on SCMI Message Protocol
> +------------------------------------------------------------
> +
> +This binding uses the common clock binding[1].
> +
> +Required properties:
> +- #clock-cells : Should be 1. Contains the Clock ID value used by SCMI commands.
> +
> +Power domain bindings for the power domains based on SCMI Message Protocol
> +------------------------------------------------------------
> +
> +This binding uses the generic power domain binding[4].
> +
> +PM domain providers
> +===================
> +
> +Required properties:
> + - #power-domain-cells : Should be 1. Contains the device or the power
> + domain ID value used by SCMI commands.
> +
> +PM domain consumers
> +===================
How consumers work is already defined elsewhere.
> +
> +Required properties:
> + - power-domains : A phandle and PM domain specifier as defined by bindings of
> + the power controller specified by phandle.
> +
> +Sensor bindings for the sensors based on SCMI Message Protocol
> +--------------------------------------------------------------
> +SCMI provides an API to access the various sensors on the SoC.
> +
> +Required properties:
> +- #thermal-sensor-cells: should be set to 1. This property follows the
> + thermal device tree bindings[2].
> +
> + Valid cell values are raw identifiers (Sensor ID)
> + as used by the firmware. Refer to platform details
> + for your implementation for the IDs to use.
> +
> +SRAM and Shared Memory for SCMI
> +-------------------------------
> +
> +A small area of SRAM is reserved for SCMI communication between application
> +processors and SCP.
> +
> +The properties should follow the generic mmio-sram description found in [3]
> +
> +Each sub-node represents the reserved area for SCMI.
> +
> +Required sub-node properties:
> +- reg : The base offset and size of the reserved area with the SRAM
> +- compatible : should be "arm,scmi-shmem" for Non-secure SRAM based
> + shared memory
> +
> +[0] http://infocenter.arm.com/help/topic/com.arm.doc.den0056a/index.html
> +[1] Documentation/devicetree/bindings/clock/clock-bindings.txt
> +[2] Documentation/devicetree/bindings/thermal/thermal.txt
> +[3] Documentation/devicetree/bindings/sram/sram.txt
> +[4] Documentation/devicetree/bindings/power/power_domain.txt
> +
> +Example:
> +
> +sram: sram@50000000 {
> + compatible = "mmio-sram";
> + reg = <0x0 0x50000000 0x0 0x10000>;
> +
> + #address-cells = <1>;
> + #size-cells = <1>;
> + ranges = <0 0x0 0x50000000 0x10000>;
> +
> + cpu_scp_lpri: scp-shmem@0 {
> + compatible = "arm,scmi-shmem";
> + reg = <0x0 0x200>;
> + };
> +
> + cpu_scp_hpri: scp-shmem@200 {
> + compatible = "arm,scmi-shmem";
> + reg = <0x200 0x200>;
> + };
> +};
> +
> +mailbox: mailbox0@40000000 {
> + ....
> + #mbox-cells = <1>;
> +};
> +
> +scmi_protocol: scmi@2e000000 {
The unit address is not valid.
> + compatible = "arm,scmi";
> + method = "mailbox-doorbell";
Is this not implied by the mboxes property?
> + mboxes = <&mailbox 0 &mailbox 1>;
> + shmem = <&cpu_scp_lpri &cpu_scp_hpri>;
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + scmi_devpd: protocol@11 {
> + reg = <0x11>;
> + #power-domain-cells = <1>;
> + };
> +
> + scmi_dvfs: protocol@13 {
> + reg = <0x13>;
> + #clock-cells = <1>;
> + };
> +
> + scmi_clk: protocol@14 {
> + reg = <0x14>;
> + #clock-cells = <1>;
> + };
> +
> + scmi_sensors0: protocol@15 {
> + reg = <0x15>;
> + #thermal-sensor-cells = <1>;
> + };
> +};
> +
> +cpu@0 {
> + ...
> + reg = <0 0>;
> + clocks = <&scmi_dvfs 0>;
> +};
> +
> +hdlcd@7ff60000 {
> + ...
> + reg = <0 0x7ff60000 0 0x1000>;
> + clocks = <&scmi_clk 4>;
> + power-domains = <&scmi_devpd 1>;
> +};
> +
> +thermal-zones {
> + soc_thermal {
> + polling-delay-passive = <100>;
> + polling-delay = <1000>;
> +
> + /* sensor ID */
> + thermal-sensors = <&scmi_sensors0 3>;
> + ...
> + };
> +};
> --
> 2.7.4
>
On 10/08/17 20:28, Rob Herring wrote:
> On Fri, Aug 04, 2017 at 03:31:28PM +0100, Sudeep Holla wrote:
>> This patch adds devicetree binding for System Control and Management
>> Interface (SCMI) 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.
>>
>> SCMI protocol is developed as better replacement to the existing SCPI
>> which is not flexible and easily extensible.
>>
>> Cc: Rob Herring <[email protected]>
>> Cc: Mark Rutland <[email protected]>
>> Signed-off-by: Sudeep Holla <[email protected]>
>> ---
>> Documentation/devicetree/bindings/arm/arm,scmi.txt | 174 +++++++++++++++++++++
>> 1 file changed, 174 insertions(+)
>> create mode 100644 Documentation/devicetree/bindings/arm/arm,scmi.txt
>>
>> diff --git a/Documentation/devicetree/bindings/arm/arm,scmi.txt b/Documentation/devicetree/bindings/arm/arm,scmi.txt
>> new file mode 100644
>> index 000000000000..33c16be58e72
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/arm/arm,scmi.txt
>> @@ -0,0 +1,174 @@
>> +System Control and Management Interface (SCMI) Message Protocol
>> +----------------------------------------------------------
>> +
>> +The SCMI is intended to allow agents such as OSPM to manage various functions
>> +that are provided by the hardware platform it is running on, including power
>> +and performance functions.
>> +
>> +This binding is intended to define the interface the firmware implementing
>> +the SCMI as described in ARM document number ARM DUI 0922B ("ARM System Control
>> +and Management Interface Platform Design Document")[0] provide for OSPM in
>> +the device tree.
>> +
>> +Required properties:
>
> Please define this is a subnode of /firmware node.
>
Thanks for pointing that out, I wasn't aware of that.
Ideally, should we move PSCI and SCPI also under that ?
Also should we contain all firmware bindings under
Documentation/devicetree/bindings/arm/firmware/ ?
[..]
>> +PM domain consumers
>> +===================
>
> How consumers work is already defined elsewhere.
>
Agreed, will drop it.
[..]
>> +
>> +scmi_protocol: scmi@2e000000 {
>
> The unit address is not valid.
>
Ah, copy paste, will drop. Need to fix in scpi bindings too.
>> + compatible = "arm,scmi";
>> + method = "mailbox-doorbell";
>
> Is this not implied by the mboxes property?
>
Indeed, remnants from v1. I removed in the definition but left in the
example.
--
Regards,
Sudeep
Hi Guenter,
On 07/08/17 13:25, Sudeep Holla wrote:
>
>
> On 04/08/17 20:32, Guenter Roeck wrote:
>> On Fri, Aug 04, 2017 at 03:31:42PM +0100, Sudeep Holla wrote:
>
> [...]
>
>>> + platform_set_drvdata(pdev, scmi_sensors);
>>> +
>>> + hwdev = devm_hwmon_device_register_with_groups(dev, "scmi_sensors",
>>> + scmi_sensors,
>>> + scmi_sensors->groups);
>>
>> Can you rework this to use devm_hwmon_device_register_with_info(),
>> and if possible let it handle the thermal registration ?
>>
>
> Thanks for the pointers. I will check on the possibility and use it if
> possible.
>
I had a look at devm_hwmon_device_register_with_info. It mostly deals
with constant structures where all the attributes are known at the boot
time. In case of SCMI, the firmware presents all the sensors and all the
information(type, scale, name/label, ...etc). Unless I create those
structures dynamically and typecast as const during probe, I can't get
it working. Are you OK with that ? If so, I can try making those changes.
--
Regards,
Sudeep
On Mon, Aug 14, 2017 at 04:09:18PM +0100, Sudeep Holla wrote:
> Hi Guenter,
>
> On 07/08/17 13:25, Sudeep Holla wrote:
> >
> >
> > On 04/08/17 20:32, Guenter Roeck wrote:
> >> On Fri, Aug 04, 2017 at 03:31:42PM +0100, Sudeep Holla wrote:
> >
> > [...]
> >
> >>> + platform_set_drvdata(pdev, scmi_sensors);
> >>> +
> >>> + hwdev = devm_hwmon_device_register_with_groups(dev, "scmi_sensors",
> >>> + scmi_sensors,
> >>> + scmi_sensors->groups);
> >>
> >> Can you rework this to use devm_hwmon_device_register_with_info(),
> >> and if possible let it handle the thermal registration ?
> >>
> >
> > Thanks for the pointers. I will check on the possibility and use it if
> > possible.
> >
>
> I had a look at devm_hwmon_device_register_with_info. It mostly deals
> with constant structures where all the attributes are known at the boot
> time. In case of SCMI, the firmware presents all the sensors and all the
> information(type, scale, name/label, ...etc). Unless I create those
> structures dynamically and typecast as const during probe, I can't get
> it working. Are you OK with that ? If so, I can try making those changes.
>
Yes, please.
What operations exactly fail, though ? The idea was that the structures
are not modified by the infrastructure code, not that they must be constant
in the driver. Maybe some of the structure definitions are simply wrong.
Thanks,
Guenter
On 08/04, Sudeep Holla wrote:
> diff --git a/drivers/clk/clk-scmi.c b/drivers/clk/clk-scmi.c
> new file mode 100644
> index 000000000000..37f98a6439a0
> --- /dev/null
> +++ b/drivers/clk/clk-scmi.c
> @@ -0,0 +1,216 @@
> +/*
> + * System Control and Power Interface (SCMI) Protocol based clock driver
> + *
> + * Copyright (C) 2017 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/clk-provider.h>
> +#include <linux/device.h>
> +#include <linux/err.h>
> +#include <linux/of.h>
> +#include <linux/module.h>
> +#include <linux/of_platform.h>
Is this include used?
> +#include <linux/platform_device.h>
> +#include <linux/scmi_protocol.h>
> +
> +struct scmi_clk {
> + u32 id;
> + struct clk_hw hw;
> + const struct scmi_clock_info *info;
> + const struct scmi_handle *handle;
> +};
> +
> +#define to_scmi_clk(clk) container_of(clk, struct scmi_clk, hw)
> +
> +static unsigned long scmi_clk_recalc_rate(struct clk_hw *hw,
> + unsigned long parent_rate)
> +{
> + int ret;
> + u64 rate;
> + struct scmi_clk *clk = to_scmi_clk(hw);
> +
> + ret = clk->handle->clk_ops->rate_get(clk->handle, clk->id, &rate);
> + if (ret)
> + return 0;
> + return rate;
> +}
> +
> +static long scmi_clk_round_rate(struct clk_hw *hw, unsigned long rate,
> + unsigned long *parent_rate)
> +{
> + u64 fmin, fmax, ftmp;
> + struct scmi_clk *clk = to_scmi_clk(hw);
> +
> + /*
> + * We can't figure out what rate it will be, so just return the
> + * rate back to the caller. scmi_clk_recalc_rate() will be called
> + * after the rate is set and we'll know what rate the clock is
> + * running at then.
> + */
> + if (clk->info->rate_discrete)
> + return rate;
> +
> + fmin = clk->info->range.min_rate;
> + fmax = clk->info->range.max_rate;
> + for (ftmp = fmin; ftmp <= fmax; ftmp += clk->info->range.step_size) {
> + if (ftmp >= rate) {
> + if (ftmp <= fmax)
> + fmax = ftmp;
> + break;
> + } else if (ftmp >= fmin) {
> + fmin = ftmp;
> + }
> + }
Are the max_rate and min_rate potentially unaligned with the
step_size? Do any of these things change at runtime? It seems
like we could do some simple math instead of this for loop unless
I missed something.
> + return fmax != clk->info->range.max_rate ? fmax : fmin;
> +}
> +
> +static int scmi_clk_set_rate(struct clk_hw *hw, unsigned long rate,
> + unsigned long parent_rate)
> +{
> + struct scmi_clk *clk = to_scmi_clk(hw);
> +
> + return clk->handle->clk_ops->rate_set(clk->handle, clk->id, 0, rate);
> +}
> +
> +static int scmi_clk_enable(struct clk_hw *hw)
> +{
> + struct scmi_clk *clk = to_scmi_clk(hw);
> +
> + return clk->handle->clk_ops->enable(clk->handle, clk->id);
> +}
> +
> +static void scmi_clk_disable(struct clk_hw *hw)
> +{
> + struct scmi_clk *clk = to_scmi_clk(hw);
> +
> + clk->handle->clk_ops->disable(clk->handle, clk->id);
> +}
> +
> +static const struct clk_ops scmi_clk_ops = {
> + .recalc_rate = scmi_clk_recalc_rate,
> + .round_rate = scmi_clk_round_rate,
> + .set_rate = scmi_clk_set_rate,
> + /*
> + * We can't provide enable/disable callback as we can't perform the same
> + * in atomic context. Since the clock framework provides standard API
> + * clk_prepare_enable that helps cases using clk_enable in non-atomic
> + * context, it should be fine providing prepare/unprepare.
> + */
> + .prepare = scmi_clk_enable,
> + .unprepare = scmi_clk_disable,
> +};
> +
> +static int scmi_clk_ops_init(struct device *dev, struct scmi_clk *sclk)
> +{
> + int ret;
> + struct clk_init_data init;
Best to do = { } here in case we add something else to the init
structure in future.
> +
> + init.flags = CLK_GET_RATE_NOCACHE;
> + init.num_parents = 0;
> + init.ops = &scmi_clk_ops;
> + init.name = sclk->info->name;
> + sclk->hw.init = &init;
> +
> + ret = devm_clk_hw_register(dev, &sclk->hw);
> + if (!ret)
> + clk_hw_set_rate_range(&sclk->hw, sclk->info->range.min_rate,
> + sclk->info->range.max_rate);
> + return ret;
> +}
> +
> +static int scmi_clk_add(struct device *dev, struct device_node *np,
> + const struct scmi_handle *handle)
> +{
> + int idx, count, err;
> + struct clk_hw **hws;
> + struct clk_hw_onecell_data *clk_data;
> +
> + count = handle->clk_ops->count_get(handle);
> + if (count < 0) {
> + dev_err(dev, "%s: invalid clock output count\n", np->name);
> + return -EINVAL;
> + }
> +
> + clk_data = devm_kzalloc(dev, sizeof(*clk_data) +
> + sizeof(*clk_data->hws) * count, GFP_KERNEL);
> + if (!clk_data)
> + return -ENOMEM;
> +
> + clk_data->num = count;
> + hws = clk_data->hws;
> +
> + for (idx = 0; idx < count; idx++) {
> + struct scmi_clk *sclk;
> +
> + sclk = devm_kzalloc(dev, sizeof(*sclk), GFP_KERNEL);
> + if (!sclk)
> + return -ENOMEM;
> +
> + sclk->info = handle->clk_ops->info_get(handle, idx);
> + if (!sclk->info) {
> + dev_dbg(dev, "invalid clock info for idx %d\n", idx);
> + continue;
> + }
> +
> + sclk->id = idx;
> + sclk->handle = handle;
> +
> + err = scmi_clk_ops_init(dev, sclk);
> + if (err) {
> + dev_err(dev, "failed to register clock %d\n", idx);
> + devm_kfree(dev, sclk);
> + hws[idx] = NULL;
> + } else {
> + dev_dbg(dev, "Registered clock:%s\n", sclk->info->name);
> + hws[idx] = &sclk->hw;
> + }
> + }
> +
> + return of_clk_add_hw_provider(np, of_clk_hw_onecell_get, clk_data);
> +}
> +
> +static int scmi_clocks_remove(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct device_node *np = dev->of_node;
> +
> + of_clk_del_provider(np);
> + return 0;
> +}
> +
> +static int scmi_clocks_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct device_node *np = dev->of_node;
> + const struct scmi_handle *handle = devm_scmi_handle_get(dev);
> +
> + if (IS_ERR_OR_NULL(handle) || !handle->clk_ops)
> + return -EPROBE_DEFER;
> +
> + return scmi_clk_add(dev, np, handle);
Why the function? We support more than just platform devices?
Just fold scmi_clk_add() into this function instead please.
> +}
> +
> +static struct platform_driver scmi_clocks_driver = {
> + .driver = {
> + .name = "scmi-clocks",
> + },
> + .probe = scmi_clocks_probe,
> + .remove = scmi_clocks_remove,
> +};
> +module_platform_driver(scmi_clocks_driver);
> +
> +MODULE_AUTHOR("Sudeep Holla <[email protected]>");
> +MODULE_DESCRIPTION("ARM SCMI clock driver");
> +MODULE_LICENSE("GPL v2");
> --
> 2.7.4
>
--
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum,
a Linux Foundation Collaborative Project
On 01/09/17 01:19, Stephen Boyd wrote:
> On 08/04, Sudeep Holla wrote:
>> diff --git a/drivers/clk/clk-scmi.c b/drivers/clk/clk-scmi.c
>> new file mode 100644
>> index 000000000000..37f98a6439a0
>> --- /dev/null
>> +++ b/drivers/clk/clk-scmi.c
[...]
>> +static int scmi_clocks_probe(struct platform_device *pdev)
>> +{
>> + struct device *dev = &pdev->dev;
>> + struct device_node *np = dev->of_node;
>> + const struct scmi_handle *handle = devm_scmi_handle_get(dev);
>> +
>> + if (IS_ERR_OR_NULL(handle) || !handle->clk_ops)
>> + return -EPROBE_DEFER;
>> +
>> + return scmi_clk_add(dev, np, handle);
>
> Why the function? We support more than just platform devices?
> Just fold scmi_clk_add() into this function instead please.
>
All valid points. Fixed locally now, will post as part of next posting
of the series.
--
Regards,
Sudeep
Hi Sudeep,
I am not sure what the patch does is correct when having a big endian
kernel dealing with scmi_shared_mem. Unless there is a reason not to
have SCMI with big endian kernel, please see remarks below.
On 04/08/17 15:31, Sudeep Holla wrote:
> The SCMI is intended to allow OSPM to manage various functions that are
> provided by the hardware platform it is running on, including power and
> performance functions. SCMI provides two levels of abstraction, protocols
> and transports. Protocols define individual groups of system control and
> management messages. A protocol specification describes the messages
> that it supports. Transports describe the method by which protocol
> messages are communicated between agents and the platform.
>
> This patch adds basic infrastructure to manage the message allocation,
> initialisation, packing/unpacking and shared memory management.
>
> Cc: Arnd Bergmann <[email protected]>
> Signed-off-by: Sudeep Holla <[email protected]>
> ---
> drivers/firmware/Kconfig | 21 +
> drivers/firmware/Makefile | 1 +
> drivers/firmware/arm_scmi/Makefile | 2 +
> drivers/firmware/arm_scmi/common.h | 74 ++++
> drivers/firmware/arm_scmi/driver.c | 774 +++++++++++++++++++++++++++++++++++++
> include/linux/scmi_protocol.h | 48 +++
> 6 files changed, 920 insertions(+)
> create mode 100644 drivers/firmware/arm_scmi/Makefile
> create mode 100644 drivers/firmware/arm_scmi/common.h
> create mode 100644 drivers/firmware/arm_scmi/driver.c
> create mode 100644 include/linux/scmi_protocol.h
>
> diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig
> index 6e4ed5a9c6fd..c3d1a12763ce 100644
> --- a/drivers/firmware/Kconfig
> +++ b/drivers/firmware/Kconfig
> @@ -19,6 +19,27 @@ config ARM_PSCI_CHECKER
> on and off through hotplug, so for now torture tests and PSCI checker
> are mutually exclusive.
>
> +config ARM_SCMI_PROTOCOL
> + tristate "ARM System Control and Management Interface (SCMI) Message Protocol"
> + depends on ARM || ARM64 || COMPILE_TEST
> + depends on MAILBOX
> + help
> + ARM System Control and Management Interface (SCMI) protocol is a
> + set of operating system-independent software interfaces that are
> + used in system management. SCMI is extensible and currently provides
> + interfaces for: Discovery and self-description of the interfaces
> + it supports, Power domain management which is the ability to place
> + a given device or domain into the various power-saving states that
> + it supports, Performance management which is the ability to control
> + the performance of a domain that is composed of compute engines
> + such as application processors and other accelerators, Clock
> + management which is the ability to set and inquire rates on platform
> + managed clocks and Sensor management which is the ability to read
> + sensor data, and be notified of sensor value.
> +
> + This protocol library provides interface for all the client drivers
> + making use of the features offered by the SCMI.
> +
> config ARM_SCPI_PROTOCOL
> tristate "ARM System Control and Power Interface (SCPI) Message Protocol"
> depends on ARM || ARM64 || COMPILE_TEST
> diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile
> index a37f12e8d137..91d3ff62c653 100644
> --- a/drivers/firmware/Makefile
> +++ b/drivers/firmware/Makefile
> @@ -23,6 +23,7 @@ obj-$(CONFIG_QCOM_SCM_32) += qcom_scm-32.o
> CFLAGS_qcom_scm-32.o :=$(call as-instr,.arch armv7-a\n.arch_extension sec,-DREQUIRES_SEC=1) -march=armv7-a
> obj-$(CONFIG_TI_SCI_PROTOCOL) += ti_sci.o
>
> +obj-$(CONFIG_ARM_SCMI_PROTOCOL) += arm_scmi/
> obj-y += broadcom/
> obj-y += meson/
> obj-$(CONFIG_GOOGLE_FIRMWARE) += google/
> diff --git a/drivers/firmware/arm_scmi/Makefile b/drivers/firmware/arm_scmi/Makefile
> new file mode 100644
> index 000000000000..58e94c95e523
> --- /dev/null
> +++ b/drivers/firmware/arm_scmi/Makefile
> @@ -0,0 +1,2 @@
> +obj-$(CONFIG_ARM_SCMI_PROTOCOL) = arm_scmi.o
> +arm_scmi-y = driver.o
> diff --git a/drivers/firmware/arm_scmi/common.h b/drivers/firmware/arm_scmi/common.h
> new file mode 100644
> index 000000000000..a6829afc17e3
> --- /dev/null
> +++ b/drivers/firmware/arm_scmi/common.h
> @@ -0,0 +1,74 @@
> +/*
> + * System Control and Management Interface (SCMI) Message Protocol
> + * driver common header file containing some definitions, structures
> + * and function prototypes used in all the different SCMI protocols.
> + *
> + * Copyright (C) 2017 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/completion.h>
> +#include <linux/scmi_protocol.h>
> +#include <linux/types.h>
> +
> +/**
> + * struct scmi_msg_hdr - Message(Tx/Rx) header
> + *
> + * @id: The identifier of the command being sent
> + * @protocol_id: The identifier of the protocol used to send @id command
> + * @seq: The token to identify the message. when a message/command returns,
> + * the platform returns the whole message header unmodified including
> + * the token.
> + */
> +struct scmi_msg_hdr {
> + u8 id;
> + u8 protocol_id;
> + u16 seq;
> + u32 status;
> + bool poll_completion;
> +};
> +
> +/**
> + * struct scmi_msg - Message(Tx/Rx) structure
> + *
> + * @buf: Buffer pointer
> + * @len: Length of data in the Buffer
> + */
> +struct scmi_msg {
> + void *buf;
> + size_t len;
> +};
> +
> +/**
> + * struct scmi_xfer - Structure representing a message flow
> + *
> + * @hdr: Transmit message header
> + * @tx: Transmit message
> + * @rx: Receive message, the buffer should be pre-allocated to store
> + * message. If request-ACK protocol is used, we can reuse the same
> + * buffer for the rx path as we use for the tx path.
> + * @done: completion event
> + */
> +
> +struct scmi_xfer {
> + struct scmi_msg_hdr hdr;
> + struct scmi_msg tx;
> + struct scmi_msg rx;
> + struct completion done;
> +};
> +
> +void scmi_one_xfer_put(const struct scmi_handle *h, struct scmi_xfer *xfer);
> +int scmi_do_xfer(const struct scmi_handle *h, struct scmi_xfer *xfer);
> +int scmi_one_xfer_init(const struct scmi_handle *h, u8 msg_id, u8 prot_id,
> + size_t tx_size, size_t rx_size, struct scmi_xfer **p);
> diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c
> new file mode 100644
> index 000000000000..139d6980f270
> --- /dev/null
> +++ b/drivers/firmware/arm_scmi/driver.c
> @@ -0,0 +1,774 @@
> +/*
> + * System Control and Management Interface (SCMI) Message Protocol driver
> + *
> + * SCMI Message Protocol is used between the System Control Processor(SCP)
> + * and the Application Processors(AP). The Message Handling Unit(MHU)
> + * provides a mechanism for inter-processor communication between SCP's
> + * Cortex M3 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.
> + *
> + * Copyright (C) 2017 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/bitmap.h>
> +#include <linux/export.h>
> +#include <linux/io.h>
> +#include <linux/kernel.h>
> +#include <linux/mailbox_client.h>
> +#include <linux/module.h>
> +#include <linux/of_address.h>
> +#include <linux/of_device.h>
> +#include <linux/semaphore.h>
> +#include <linux/slab.h>
> +
> +#include "common.h"
> +
> +#define MSG_ID_SHIFT 0
> +#define MSG_ID_MASK 0xff
> +#define MSG_TYPE_SHIFT 8
> +#define MSG_TYPE_MASK 0x3
> +#define MSG_PROTOCOL_ID_SHIFT 10
> +#define MSG_PROTOCOL_ID_MASK 0xff
> +#define MSG_TOKEN_ID_SHIFT 18
> +#define MSG_TOKEN_ID_MASK 0x3ff
> +#define MSG_XTRACT_TOKEN(header) \
> + (((header) >> MSG_TOKEN_ID_SHIFT) & MSG_TOKEN_ID_MASK)
> +
> +enum scmi_error_codes {
> + SCMI_SUCCESS = 0, /* Success */
> + SCMI_ERR_SUPPORT = -1, /* Not supported */
> + SCMI_ERR_PARAMS = -2, /* Invalid Parameters */
> + SCMI_ERR_ACCESS = -3, /* Invalid access/permission denied */
> + SCMI_ERR_ENTRY = -4, /* Not found */
> + SCMI_ERR_RANGE = -5, /* Value out of range */
> + SCMI_ERR_BUSY = -6, /* Device busy */
> + SCMI_ERR_COMMS = -7, /* Communication Error */
> + SCMI_ERR_GENERIC = -8, /* Generic Error */
> + SCMI_ERR_HARDWARE = -9, /* Hardware Error */
> + SCMI_ERR_PROTOCOL = -10,/* Protocol Error */
> + SCMI_ERR_MAX
> +};
> +
> +/* List of all SCMI devices active in system */
> +static LIST_HEAD(scmi_list);
> +/* Protection for the entire list */
> +static DEFINE_MUTEX(scmi_list_mutex);
> +
> +/**
> + * struct scmi_xfers_info - Structure to manage transfer information
> + *
> + * @sem_xfer_count: Counting Semaphore for managing max simultaneous
> + * Messages.
> + * @xfer_block: Preallocated Message array
> + * @xfer_alloc_table: Bitmap table for allocated messages.
> + * Index of this bitmap table is also used for message
> + * sequence identifier.
> + * @xfer_lock: Protection for message allocation
> + */
> +struct scmi_xfers_info {
> + struct semaphore sem_xfer_count;
> + struct scmi_xfer *xfer_block;
> + unsigned long *xfer_alloc_table;
> + /* protect transfer allocation */
> + spinlock_t xfer_lock;
> +};
> +
> +/**
> + * struct scmi_desc - Description of SoC integration
> + *
> + * @max_rx_timeout_ms: Timeout for communication with SoC (in Milliseconds)
> + * @max_msg: Maximum number of messages that can be pending
> + * simultaneously in the system
> + * @max_msg_size: Maximum size of data per message that can be handled.
> + */
> +struct scmi_desc {
> + int max_rx_timeout_ms;
> + int max_msg;
> + int max_msg_size;
> +};
> +
> +/**
> + * struct scmi_info - Structure representing a SCMI instance
> + *
> + * @dev: Device pointer
> + * @desc: SoC description for this instance
> + * @handle: Instance of SCMI handle to send to clients
> + * @cl: Mailbox Client
> + * @tx_chan: Transmit mailbox channel
> + * @rx_chan: Receive mailbox channel
> + * @tx_payload: Transmit mailbox channel payload area
> + * @rx_payload: Receive mailbox channel payload area
> + * @minfo: Message info
> + * @node: list head
> + * @users: Number of users of this instance
> + */
> +struct scmi_info {
> + struct device *dev;
> + const struct scmi_desc *desc;
> + struct scmi_handle handle;
> + struct mbox_client cl;
> + struct mbox_chan *tx_chan;
> + struct mbox_chan *rx_chan;
> + void __iomem *tx_payload;
> + void __iomem *rx_payload;
> + struct scmi_xfers_info minfo;
> + struct list_head node;
> + int users;
> +};
> +
> +#define client_to_scmi_info(c) container_of(c, struct scmi_info, cl)
> +#define handle_to_scmi_info(h) container_of(h, struct scmi_info, handle)
> +
> +/*
> + * The SCP firmware providing SCM interface to OSPM and other agents must
> + * execute only in little-endian mode as per SCMI specification, so any buffers
> + * shared through SCMI should have their contents converted to little-endian
> + */
> +struct scmi_shared_mem {
> + __le32 reserved;
> + __le32 channel_status;
> +#define SCMI_SHMEM_CHAN_STAT_CHANNEL_ERROR BIT(1)
> +#define SCMI_SHMEM_CHAN_STAT_CHANNEL_FREE BIT(0)
> + __le32 reserved1[2];
> + __le32 flags;
> +#define SCMI_SHMEM_FLAG_INTR_ENABLED BIT(0)
> + __le32 length;
> + __le32 msg_header;
> + u8 msg_payload[0];
> +};
> +
> +static int scmi_linux_errmap[] = {
> + /* better than switch case as long as return value is continuous */
> + 0, /* SCMI_SUCCESS */
> + -EOPNOTSUPP, /* SCMI_ERR_SUPPORT */
> + -EINVAL, /* SCMI_ERR_PARAM */
> + -EACCES, /* SCMI_ERR_ACCESS */
> + -ENOENT, /* SCMI_ERR_ENTRY */
> + -ERANGE, /* SCMI_ERR_RANGE */
> + -EBUSY, /* SCMI_ERR_BUSY */
> + -ECOMM, /* SCMI_ERR_COMMS */
> + -EIO, /* SCMI_ERR_GENERIC */
> + -EREMOTEIO, /* SCMI_ERR_HARDWARE */
> + -EPROTO, /* SCMI_ERR_PROTOCOL */
> +};
> +
> +static inline int scmi_to_linux_errno(int errno)
> +{
> + if (errno < SCMI_SUCCESS && errno > SCMI_ERR_MAX)
> + return scmi_linux_errmap[-errno];
> + return -EIO;
> +}
> +
> +/**
> + * scmi_dump_header_dbg() - Helper to dump a message header.
> + *
> + * @dev: Device pointer corresponding to the SCMI entity
> + * @hdr: pointer to header.
> + */
> +static inline void scmi_dump_header_dbg(struct device *dev,
> + struct scmi_msg_hdr *hdr)
> +{
> + dev_dbg(dev, "Command ID: %x Sequence ID: %x Protocol: %x\n",
> + hdr->id, hdr->seq, hdr->protocol_id);
> +}
> +
> +static void scmi_fetch_response(struct scmi_xfer *xfer,
> + struct scmi_shared_mem *mem)
> +{
> + xfer->hdr.status = le32_to_cpu(*(__le32 *)mem->msg_payload);
> + /* Skip the length of header and statues in payload area i.e 8 bytes*/
> + xfer->rx.len = min_t(size_t, xfer->rx.len, mem->length - 8);
> +
> + /* Take a copy to the rx buffer.. */
> + memcpy_fromio(xfer->rx.buf, mem->msg_payload + 4, xfer->rx.len);
> +}
> +
> +/**
> + * scmi_rx_callback() - mailbox client callback for receive messages
> + *
> + * @cl: client pointer
> + * @m: mailbox message
> + *
> + * Processes one received message to appropriate transfer information and
> + * signals completion of the transfer.
> + *
> + * NOTE: This function will be invoked in IRQ context, hence should be
> + * as optimal as possible.
> + */
> +static void scmi_rx_callback(struct mbox_client *cl, void *m)
> +{
> + u16 xfer_id;
> + struct scmi_xfer *xfer;
> + struct scmi_info *info = client_to_scmi_info(cl);
> + struct scmi_xfers_info *minfo = &info->minfo;
> + struct device *dev = info->dev;
> + struct scmi_shared_mem *mem = info->tx_payload;
> +
> + xfer_id = MSG_XTRACT_TOKEN(mem->msg_header);
> +
Don't we need to convert msg_header to cpu endian?
> + /*
> + * Are we even expecting this?
> + */
> + if (!test_bit(xfer_id, minfo->xfer_alloc_table)) {
> + dev_err(dev, "message for %d is not expected!\n", xfer_id);
> + return;
> + }
> +
> + xfer = &minfo->xfer_block[xfer_id];
> +
> + scmi_dump_header_dbg(dev, &xfer->hdr);
> + /* Is the message of valid length? */
> + if (xfer->rx.len > info->desc->max_msg_size) {
> + dev_err(dev, "unable to handle %zu xfer(max %d)\n",
> + xfer->rx.len, info->desc->max_msg_size);
> + return;
> + }
> +
> + scmi_fetch_response(xfer, mem);
> + complete(&xfer->done);
> +}
> +
> +/**
> + * pack_scmi_header() - packs and returns 32-bit header
> + *
> + * @hdr: pointer to header containing all the information on message id,
> + * protocol id and sequence id.
> + */
> +static inline u32 pack_scmi_header(struct scmi_msg_hdr *hdr)
> +{
> + return ((hdr->id & MSG_ID_MASK) << MSG_ID_SHIFT) |
> + ((hdr->seq & MSG_TOKEN_ID_MASK) << MSG_TOKEN_ID_SHIFT) |
> + ((hdr->protocol_id & MSG_PROTOCOL_ID_MASK) << MSG_PROTOCOL_ID_SHIFT);
> +}
> +
> +/**
> + * scmi_tx_prepare() - mailbox client callback to prepare for the transfer
> + *
> + * @cl: client pointer
> + * @m: mailbox message
> + *
> + * This function prepares the shared memory which contains the header and the
> + * payload.
> + */
> +static void scmi_tx_prepare(struct mbox_client *cl, void *m)
> +{
> + struct scmi_xfer *t = m;
> + struct scmi_info *info = client_to_scmi_info(cl);
> + struct scmi_shared_mem *mem = info->tx_payload;
> +
> + mem->channel_status = 0x0; /* Mark channel busy + clear error */
> + mem->flags = t->hdr.poll_completion ? 0 : SCMI_SHMEM_FLAG_INTR_ENABLED;
> + mem->length = sizeof(mem->msg_header) + t->tx.len;
> + mem->msg_header = cpu_to_le32(pack_scmi_header(&t->hdr));
> + if (t->tx.buf)
> + memcpy_toio(mem->msg_payload, t->tx.buf, t->tx.len);
> +}
I thought every member of scmi_shared_mem should be in le, not just the
header.
If that is the case, why do mem->length and mem->flags not get converted
to le?
If it is not the case, should they be of type __le32?
(same remark applies in scmi_fetch_response for mem->length)
> +
> +/**
> + * scmi_one_xfer_get() - Allocate one message
> + *
> + * @handle: SCMI entity handle
> + *
> + * Helper function which is used by various command functions that are
> + * exposed to clients of this driver for allocating a message traffic event.
> + *
> + * This function can sleep depending on pending requests already in the system
> + * for the SCMI entity. Further, this also holds a spinlock to maintain
> + * integrity of internal data structures.
> + *
> + * Return: 0 if all went fine, else corresponding error.
> + */
> +static struct scmi_xfer *scmi_one_xfer_get(const struct scmi_handle *handle)
> +{
> + u16 xfer_id;
> + int ret, timeout;
> + struct scmi_xfer *xfer;
> + unsigned long flags, bit_pos;
> + struct scmi_info *info = handle_to_scmi_info(handle);
> + struct scmi_xfers_info *minfo = &info->minfo;
> +
> + /*
> + * Ensure we have only controlled number of pending messages.
> + * Ideally, we might just have to wait a single message, be
> + * conservative and wait 5 times that..
> + */
> + timeout = msecs_to_jiffies(info->desc->max_rx_timeout_ms) * 5;
> + ret = down_timeout(&minfo->sem_xfer_count, timeout);
> + if (ret < 0)
> + return ERR_PTR(ret);
> +
> + /* Keep the locked section as small as possible */
> + spin_lock_irqsave(&minfo->xfer_lock, flags);
> + bit_pos = find_first_zero_bit(minfo->xfer_alloc_table,
> + info->desc->max_msg);
> + if (bit_pos == info->desc->max_msg) {
> + spin_unlock_irqrestore(&minfo->xfer_lock, flags);
> + return ERR_PTR(-ENOMEM);
> + }
> + set_bit(bit_pos, minfo->xfer_alloc_table);
> + spin_unlock_irqrestore(&minfo->xfer_lock, flags);
> +
Is it needed to disable IRQs here? Since the callback called in IRQ
context only reads the xfer_alloc_table without modification nor taking
locks, can't we just do spin_lock/spin_unlock?
> + xfer_id = bit_pos;
> +
> + xfer = &minfo->xfer_block[xfer_id];
> + xfer->hdr.seq = xfer_id;
> + reinit_completion(&xfer->done);
> +
> + return xfer;
> +}
> +
> +/**
> + * scmi_one_xfer_put() - Release a message
> + *
> + * @minfo: transfer info pointer
> + * @xfer: message that was reserved by scmi_one_xfer_get
> + *
> + * This holds a spinlock to maintain integrity of internal data structures.
> + */
> +void scmi_one_xfer_put(const struct scmi_handle *handle, struct scmi_xfer *xfer)
> +{
> + unsigned long flags;
> + struct scmi_info *info = handle_to_scmi_info(handle);
> + struct scmi_xfers_info *minfo = &info->minfo;
> +
> + /*
> + * Keep the locked section as small as possible
> + * NOTE: we might escape with smp_mb and no lock here..
> + * but just be conservative and symmetric.
> + */
> + spin_lock_irqsave(&minfo->xfer_lock, flags);
> + clear_bit(xfer->hdr.seq, minfo->xfer_alloc_table);
> + spin_unlock_irqrestore(&minfo->xfer_lock, flags);
> +
Same question as before.
Cheer,
--
Julien Thierry
Hi Julien,
Thanks for reviewing this.
On 05/09/17 11:03, Julien Thierry wrote:
> Hi Sudeep,
>
> I am not sure what the patch does is correct when having a big endian
> kernel dealing with scmi_shared_mem. Unless there is a reason not to
> have SCMI with big endian kernel, please see remarks below.
>
No the intention at least is to have it working even with big endian
kernel. I found couple of issues when testing after this was posted.
They are already fixed in [1]
[...]
>> +/**
>> + * scmi_rx_callback() - mailbox client callback for receive messages
>> + *
>> + * @cl: client pointer
>> + * @m: mailbox message
>> + *
>> + * Processes one received message to appropriate transfer information
>> and
>> + * signals completion of the transfer.
>> + *
>> + * NOTE: This function will be invoked in IRQ context, hence should be
>> + * as optimal as possible.
>> + */
>> +static void scmi_rx_callback(struct mbox_client *cl, void *m)
>> +{
>> + u16 xfer_id;
>> + struct scmi_xfer *xfer;
>> + struct scmi_info *info = client_to_scmi_info(cl);
>> + struct scmi_xfers_info *minfo = &info->minfo;
>> + struct device *dev = info->dev;
>> + struct scmi_shared_mem *mem = info->tx_payload;
>> +
>> + xfer_id = MSG_XTRACT_TOKEN(mem->msg_header);
>> +
>
> Don't we need to convert msg_header to cpu endian?
>
Indeed, already fixed as mentioned above.
>> + /*
>> + * Are we even expecting this?
>> + */
>> + if (!test_bit(xfer_id, minfo->xfer_alloc_table)) {
>> + dev_err(dev, "message for %d is not expected!\n", xfer_id);
>> + return;
>> + }
>> +
>> + xfer = &minfo->xfer_block[xfer_id];
>> +
>> + scmi_dump_header_dbg(dev, &xfer->hdr);
>> + /* Is the message of valid length? */
>> + if (xfer->rx.len > info->desc->max_msg_size) {
>> + dev_err(dev, "unable to handle %zu xfer(max %d)\n",
>> + xfer->rx.len, info->desc->max_msg_size);
>> + return;
>> + }
>> +
>> + scmi_fetch_response(xfer, mem);
>> + complete(&xfer->done);
>> +}
>> +
>> +/**
>> + * pack_scmi_header() - packs and returns 32-bit header
>> + *
>> + * @hdr: pointer to header containing all the information on message id,
>> + * protocol id and sequence id.
>> + */
>> +static inline u32 pack_scmi_header(struct scmi_msg_hdr *hdr)
>> +{
>> + return ((hdr->id & MSG_ID_MASK) << MSG_ID_SHIFT) |
>> + ((hdr->seq & MSG_TOKEN_ID_MASK) << MSG_TOKEN_ID_SHIFT) |
>> + ((hdr->protocol_id & MSG_PROTOCOL_ID_MASK) <<
>> MSG_PROTOCOL_ID_SHIFT);
>> +}
>> +
>> +/**
>> + * scmi_tx_prepare() - mailbox client callback to prepare for the
>> transfer
>> + *
>> + * @cl: client pointer
>> + * @m: mailbox message
>> + *
>> + * This function prepares the shared memory which contains the header
>> and the
>> + * payload.
>> + */
>> +static void scmi_tx_prepare(struct mbox_client *cl, void *m)
>> +{
>> + struct scmi_xfer *t = m;
>> + struct scmi_info *info = client_to_scmi_info(cl);
>> + struct scmi_shared_mem *mem = info->tx_payload;
>> +
>> + mem->channel_status = 0x0; /* Mark channel busy + clear error */
>> + mem->flags = t->hdr.poll_completion ? 0 :
>> SCMI_SHMEM_FLAG_INTR_ENABLED;
>> + mem->length = sizeof(mem->msg_header) + t->tx.len;
>> + mem->msg_header = cpu_to_le32(pack_scmi_header(&t->hdr));
>> + if (t->tx.buf)
>> + memcpy_toio(mem->msg_payload, t->tx.buf, t->tx.len);
>> +}
>
> I thought every member of scmi_shared_mem should be in le, not just the
> header.
> If that is the case, why do mem->length and mem->flags not get converted
> to le?
> If it is not the case, should they be of type __le32?
>
> (same remark applies in scmi_fetch_response for mem->length)
>
Agreed and already fixed, sorry for that. I am planning to repost after
4.14-rc1. and hence waiting with fixes.
>> +
>> +/**
>> + * scmi_one_xfer_get() - Allocate one message
>> + *
>> + * @handle: SCMI entity handle
>> + *
>> + * Helper function which is used by various command functions that are
>> + * exposed to clients of this driver for allocating a message traffic
>> event.
>> + *
>> + * This function can sleep depending on pending requests already in
>> the system
>> + * for the SCMI entity. Further, this also holds a spinlock to maintain
>> + * integrity of internal data structures.
>> + *
>> + * Return: 0 if all went fine, else corresponding error.
>> + */
>> +static struct scmi_xfer *scmi_one_xfer_get(const struct scmi_handle
>> *handle)
>> +{
>> + u16 xfer_id;
>> + int ret, timeout;
>> + struct scmi_xfer *xfer;
>> + unsigned long flags, bit_pos;
>> + struct scmi_info *info = handle_to_scmi_info(handle);
>> + struct scmi_xfers_info *minfo = &info->minfo;
>> +
>> + /*
>> + * Ensure we have only controlled number of pending messages.
>> + * Ideally, we might just have to wait a single message, be
>> + * conservative and wait 5 times that..
>> + */
>> + timeout = msecs_to_jiffies(info->desc->max_rx_timeout_ms) * 5;
>> + ret = down_timeout(&minfo->sem_xfer_count, timeout);
>> + if (ret < 0)
>> + return ERR_PTR(ret);
>> +
>> + /* Keep the locked section as small as possible */
>> + spin_lock_irqsave(&minfo->xfer_lock, flags);
>> + bit_pos = find_first_zero_bit(minfo->xfer_alloc_table,
>> + info->desc->max_msg);
>> + if (bit_pos == info->desc->max_msg) {
>> + spin_unlock_irqrestore(&minfo->xfer_lock, flags);
>> + return ERR_PTR(-ENOMEM);
>> + }
>> + set_bit(bit_pos, minfo->xfer_alloc_table);
>> + spin_unlock_irqrestore(&minfo->xfer_lock, flags);
>> +
>
> Is it needed to disable IRQs here? Since the callback called in IRQ
> context only reads the xfer_alloc_table without modification nor taking
> locks, can't we just do spin_lock/spin_unlock?
>
Yes we can for now. I started adding notification support where we may
need to allocate(i.e. assign from the pre-allocated buffer) in IRQ
context. I left it as is though I removed notifications as I haven't
tested it.
--
Regards,
Sudeep
[1] https://git.kernel.org/sudeep.holla/linux/h/for-list/arm_scmi
Hi Sudeep,
On 04/08/17 15:31, Sudeep Holla wrote:
> The base protocol describes the properties of the implementation and
> provide generic error management. The base protocol provides commands
> to describe protocol version, discover implementation specific
> attributes and vendor/sub-vendor identification, list of protocols
> implemented and the various agents are in the system including OSPM
> and the platform. It also supports registering for notifications of
> platform errors.
>
> This protocol is mandatory. This patch adds support for the same along
> with some basic infrastructure to add support for other protocols.
>
> Cc: Arnd Bergmann <[email protected]>
> Signed-off-by: Sudeep Holla <[email protected]>
> ---
> drivers/firmware/arm_scmi/Makefile | 2 +-
> drivers/firmware/arm_scmi/base.c | 293 +++++++++++++++++++++++++++++++++++++
> drivers/firmware/arm_scmi/common.h | 46 ++++++
> drivers/firmware/arm_scmi/driver.c | 67 +++++++++
> include/linux/scmi_protocol.h | 28 ++++
> 5 files changed, 435 insertions(+), 1 deletion(-)
> create mode 100644 drivers/firmware/arm_scmi/base.c
>
> diff --git a/drivers/firmware/arm_scmi/Makefile b/drivers/firmware/arm_scmi/Makefile
> index 58e94c95e523..21d01d1d6b9c 100644
> --- a/drivers/firmware/arm_scmi/Makefile
> +++ b/drivers/firmware/arm_scmi/Makefile
> @@ -1,2 +1,2 @@
> obj-$(CONFIG_ARM_SCMI_PROTOCOL) = arm_scmi.o
> -arm_scmi-y = driver.o
> +arm_scmi-y = base.o driver.o
> diff --git a/drivers/firmware/arm_scmi/base.c b/drivers/firmware/arm_scmi/base.c
> new file mode 100644
> index 000000000000..9bfffbe95c21
> --- /dev/null
> +++ b/drivers/firmware/arm_scmi/base.c
> @@ -0,0 +1,293 @@
> +/*
> + * System Control and Management Interface (SCMI) Base Protocol
> + *
> + * Copyright (C) 2017 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 "common.h"
> +
> +enum scmi_base_protocol_cmd {
> + BASE_DISCOVER_VENDOR = 0x3,
> + BASE_DISCOVER_SUB_VENDOR = 0x4,
> + BASE_DISCOVER_IMPLEMENT_VERSION = 0x5,
> + BASE_DISCOVER_LIST_PROTOCOLS = 0x6,
> + BASE_DISCOVER_AGENT = 0x7,
> + BASE_NOTIFY_ERRORS = 0x8,
> +};
> +
> +struct scmi_msg_resp_base_attributes {
> + u8 num_protocols;
> + u8 num_agents;
> + __le16 reserved;
> +};
> +
> +/**
> + * scmi_base_attributes_get() - gets the implementation details
> + * that are associated with the base protocol.
> + *
> + * @handle - SCMI entity handle
> + *
> + * Return: 0 on success, else appropriate SCMI error.
> + */
> +static int scmi_base_attributes_get(const struct scmi_handle *handle)
> +{
> + int ret;
> + struct scmi_xfer *t;
> + struct scmi_msg_resp_base_attributes *attr_info;
> + struct scmi_revision_info *rev = handle->version;
> +
> + ret = scmi_one_xfer_init(handle, PROTOCOL_ATTRIBUTES,
> + SCMI_PROTOCOL_BASE, 0, sizeof(*attr_info), &t);
> + if (ret)
> + return ret;
> +
> + ret = scmi_do_xfer(handle, t);
> + if (!ret) {
> + attr_info = t->rx.buf;
> + rev->num_protocols = attr_info->num_protocols;
> + rev->num_agents = attr_info->num_agents;
> + }
> +
> + scmi_one_xfer_put(handle, t);
> + return ret;
> +}
> +
> +/**
> + * scmi_base_vendor_id_get() - gets vendor/subvendor identifier ASCII string.
> + *
> + * @handle - SCMI entity handle
> + * @sub_vendor - specify true if sub-vendor ID is needed
> + *
> + * Return: 0 on success, else appropriate SCMI error.
> + */
> +static int
> +scmi_base_vendor_id_get(const struct scmi_handle *handle, bool sub_vendor)
> +{
> + u8 cmd;
> + int ret, size;
> + char *vendor_id;
> + struct scmi_xfer *t;
> + struct scmi_revision_info *rev = handle->version;
> +
> + if (sub_vendor) {
> + cmd = BASE_DISCOVER_SUB_VENDOR;
> + vendor_id = rev->sub_vendor_id;
> + size = ARRAY_SIZE(rev->sub_vendor_id);
> + } else {
> + cmd = BASE_DISCOVER_VENDOR;
> + vendor_id = rev->vendor_id;
> + size = ARRAY_SIZE(rev->vendor_id);
> + }
> +
> + ret = scmi_one_xfer_init(handle, cmd, SCMI_PROTOCOL_BASE, 0, size, &t);
> + if (ret)
> + return ret;
> +
> + ret = scmi_do_xfer(handle, t);
> + if (!ret)
> + memcpy(vendor_id, t->rx.buf, size);
> +
> + scmi_one_xfer_put(handle, t);
> + return ret;
> +}
> +
> +/**
> + * scmi_base_implementation_version_get() - gets a vendor-specific
> + * implementation 32-bit version. The format of the version number is
> + * vendor-specific
> + *
> + * @handle - SCMI entity handle
> + *
> + * Return: 0 on success, else appropriate SCMI error.
> + */
> +static int
> +scmi_base_implementation_version_get(const struct scmi_handle *handle)
> +{
> + int ret;
> + u32 *impl_ver;
> + struct scmi_xfer *t;
> + struct scmi_revision_info *rev = handle->version;
> +
> + ret = scmi_one_xfer_init(handle, BASE_DISCOVER_IMPLEMENT_VERSION,
> + SCMI_PROTOCOL_BASE, 0, sizeof(*impl_ver), &t);
> + if (ret)
> + return ret;
> +
> + ret = scmi_do_xfer(handle, t);
> + if (!ret) {
> + impl_ver = t->rx.buf;
> + rev->impl_ver = le32_to_cpu(*impl_ver);
> + }
> +
> + scmi_one_xfer_put(handle, t);
> + return ret;
> +}
> +
> +/**
> + * scmi_base_implementation_list_get() - gets the list of protocols it is
> + * OSPM is allowed to access
> + *
> + * @handle - SCMI entity handle
> + * @protocols_imp - pointer to hold the list of protocol identifiers
> + *
> + * Return: 0 on success, else appropriate SCMI error.
> + */
> +static int scmi_base_implementation_list_get(const struct scmi_handle *handle,
> + u8 *protocols_imp)
> +{
> + u8 *list;
> + int ret, loop;
> + struct scmi_xfer *t;
> + __le32 *num_skip, *num_ret;
> + u32 tot_num_ret = 0, loop_num_ret;
> + struct device *dev = handle->dev;
> +
> + ret = scmi_one_xfer_init(handle, BASE_DISCOVER_LIST_PROTOCOLS,
> + SCMI_PROTOCOL_BASE, sizeof(*num_skip), 0, &t);
> + if (ret)
> + return ret;
> +
> + num_skip = t->tx.buf;
> + num_ret = t->rx.buf;
> + list = t->rx.buf + sizeof(*num_ret);
> +
> + do {
> + /* Set the number of protocols to be skipped/already read */
> + *num_skip = cpu_to_le32(tot_num_ret);
> +
> + ret = scmi_do_xfer(handle, t);
> + if (ret)
> + break;
> +
> + loop_num_ret = le32_to_cpu(*num_ret);
> + if (tot_num_ret + loop_num_ret > MAX_PROTOCOLS_IMP) {
> + dev_err(dev, "No. of Protocol > MAX_PROTOCOLS_IMP");
> + break;
> + }
> +
> + for (loop = 0; loop < loop_num_ret; loop++)
> + protocols_imp[tot_num_ret + loop] = *(list + loop);
> +
> + tot_num_ret += loop_num_ret;
> + } while (loop_num_ret);
> +
> + scmi_one_xfer_put(handle, t);
> + return ret;
> +}
> +
> +/**
> + * scmi_base_discover_agent_get() - discover the name of an agent
> + *
> + * @handle - SCMI entity handle
> + * @id - Agent identifier
> + * @name - Agent identifier ASCII string
> + *
> + * An agent id of 0 is reserved to identify the platform itself.
> + * Generally operating system is represented as "OSPM"
> + *
> + * Return: 0 on success, else appropriate SCMI error.
> + */
> +static int scmi_base_discover_agent_get(const struct scmi_handle *handle,
> + int id, char *name)
> +{
> + int ret;
> + struct scmi_xfer *t;
> +
> + ret = scmi_one_xfer_init(handle, BASE_DISCOVER_AGENT,
> + SCMI_PROTOCOL_BASE, sizeof(__le32),
> + SCMI_MAX_STR_SIZE, &t);
> + if (ret)
> + return ret;
> +
> + *(__le32 *)t->tx.buf = cpu_to_le32(id);
> +
> + ret = scmi_do_xfer(handle, t);
> + if (!ret)
> + memcpy(name, t->rx.buf, SCMI_MAX_STR_SIZE);
> +
> + scmi_one_xfer_put(handle, t);
> + return ret;
> +}
> +
> +/**
> + * scmi_base_error_notifications_enable() - register/unregister for
> + * notifications of errors in the platform
> + *
> + * @handle - SCMI entity handle
> + * @enable - Enable/Disable the notification
> + *
> + * Return: 0 on success, else appropriate SCMI error.
> + */
> +static int
> +scmi_base_error_notifications_enable(const struct scmi_handle *handle, bool en)
> +{
> + int ret;
> + struct scmi_xfer *t;
> +
> + ret = scmi_one_xfer_init(handle, BASE_NOTIFY_ERRORS, SCMI_PROTOCOL_BASE,
> + sizeof(__le32), 0, &t);
> + if (ret)
> + return ret;
> +
> + *(__le32 *)t->tx.buf = cpu_to_le32(en & BIT(0));
> +
> + ret = scmi_do_xfer(handle, t);
> +
> + scmi_one_xfer_put(handle, t);
> + return ret;
> +}
> +
> +int scmi_base_protocol_init(struct scmi_handle *h)
> +{
> + int id, ret;
> + u8 *prot_imp;
> + u32 version;
> + char name[SCMI_MAX_STR_SIZE];
> + const struct scmi_handle *handle = h;
> + struct device *dev = handle->dev;
> + struct scmi_revision_info *rev = handle->version;
> +
> + ret = scmi_version_get(handle, SCMI_PROTOCOL_BASE, &version);
> + if (ret)
> + return ret;
> +
> + prot_imp = devm_kcalloc(dev, MAX_PROTOCOLS_IMP, sizeof(u8), GFP_KERNEL);
> + if (!prot_imp)
> + return -ENOMEM;
> +
> + rev->major_ver = PROTOCOL_REV_MAJOR(version),
> + rev->minor_ver = PROTOCOL_REV_MINOR(version);
> +
> + scmi_base_attributes_get(handle);
> + scmi_base_vendor_id_get(handle, false);
> + scmi_base_vendor_id_get(handle, true);
> + scmi_base_implementation_version_get(handle);
> + scmi_base_implementation_list_get(handle, prot_imp);
> + scmi_base_error_notifications_enable(handle, true);
> + scmi_setup_protocol_implemented(handle, prot_imp);
> +
> + dev_info(dev, "SCMI Protocol v%d.%d '%s:%s' Firmware version 0x%x\n",
> + rev->major_ver, rev->minor_ver, rev->vendor_id,
> + rev->sub_vendor_id, rev->impl_ver);
> + dev_dbg(dev, "Found %d protocol(s) %d agent(s)\n", rev->num_protocols,
> + rev->num_agents);
> +
> + for (id = 0; id < rev->num_agents; id++) {
> + scmi_base_discover_agent_get(handle, id, name);
> + dev_dbg(dev, "Agent %d: %s\n", id, name);
> + }
> +
> + return 0;
> +}
> diff --git a/drivers/firmware/arm_scmi/common.h b/drivers/firmware/arm_scmi/common.h
> index a6829afc17e3..e3fe5d9acc82 100644
> --- a/drivers/firmware/arm_scmi/common.h
> +++ b/drivers/firmware/arm_scmi/common.h
> @@ -19,9 +19,50 @@
> */
>
> #include <linux/completion.h>
> +#include <linux/device.h>
> +#include <linux/errno.h>
> +#include <linux/kernel.h>
> #include <linux/scmi_protocol.h>
> #include <linux/types.h>
>
> +#define PROTOCOL_REV_MINOR_BITS 16
> +#define PROTOCOL_REV_MINOR_MASK ((1U << PROTOCOL_REV_MINOR_BITS) - 1)
> +#define PROTOCOL_REV_MAJOR(x) ((x) >> PROTOCOL_REV_MINOR_BITS)
> +#define PROTOCOL_REV_MINOR(x) ((x) & PROTOCOL_REV_MINOR_MASK)
> +#define MAX_PROTOCOLS_IMP 16
> +
> +enum scmi_std_protocol {
> + SCMI_PROTOCOL_BASE = 0x10,
> + SCMI_PROTOCOL_POWER = 0x11,
> + SCMI_PROTOCOL_SYSTEM = 0x12,
> + SCMI_PROTOCOL_PERF = 0x13,
> + SCMI_PROTOCOL_CLOCK = 0x14,
> + SCMI_PROTOCOL_SENSOR = 0x15,
> +};
> +
> +enum scmi_common_cmd {
> + PROTOCOL_VERSION = 0x0,
> + PROTOCOL_ATTRIBUTES = 0x1,
> + PROTOCOL_MESSAGE_ATTRIBUTES = 0x2,
> +};
> +
> +/**
> + * struct scmi_msg_resp_prot_version - Response for a message
> + *
> + * @major_version: Major version of the ABI that firmware supports
> + * @minor_version: Minor version of the ABI that firmware supports
> + *
> + * In general, ABI version changes follow the rule that minor version increments
> + * are backward compatible. Major revision changes in ABI may not be
> + * backward compatible.
> + *
> + * Response to a generic message with message type SCMI_MSG_VERSION
> + */
> +struct scmi_msg_resp_prot_version {
> + __le16 minor_version;
> + __le16 major_version;
> +};
> +
> /**
> * struct scmi_msg_hdr - Message(Tx/Rx) header
> *
> @@ -72,3 +113,8 @@ void scmi_one_xfer_put(const struct scmi_handle *h, struct scmi_xfer *xfer);
> int scmi_do_xfer(const struct scmi_handle *h, struct scmi_xfer *xfer);
> int scmi_one_xfer_init(const struct scmi_handle *h, u8 msg_id, u8 prot_id,
> size_t tx_size, size_t rx_size, struct scmi_xfer **p);
> +int scmi_version_get(const struct scmi_handle *h, u8 protocol, u32 *version);
> +void scmi_setup_protocol_implemented(const struct scmi_handle *handle,
> + u8 *prot_imp);
> +
> +int scmi_base_protocol_init(struct scmi_handle *h);
> diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c
> index 139d6980f270..601d0d7210d9 100644
> --- a/drivers/firmware/arm_scmi/driver.c
> +++ b/drivers/firmware/arm_scmi/driver.c
> @@ -108,18 +108,22 @@ struct scmi_desc {
> * @dev: Device pointer
> * @desc: SoC description for this instance
> * @handle: Instance of SCMI handle to send to clients
> + * @version: SCMI revision information containing protocol version,
> + * implementation version and (sub-)vendor identification.
> * @cl: Mailbox Client
> * @tx_chan: Transmit mailbox channel
> * @rx_chan: Receive mailbox channel
> * @tx_payload: Transmit mailbox channel payload area
> * @rx_payload: Receive mailbox channel payload area
> * @minfo: Message info
> + * @protocols_imp: list of protocols implemented
> * @node: list head
> * @users: Number of users of this instance
> */
> struct scmi_info {
> struct device *dev;
> const struct scmi_desc *desc;
> + struct scmi_revision_info version;
> struct scmi_handle handle;
> struct mbox_client cl;
> struct mbox_chan *tx_chan;
> @@ -127,6 +131,7 @@ struct scmi_info {
> void __iomem *tx_payload;
> void __iomem *rx_payload;
> struct scmi_xfers_info minfo;
> + u8 *protocols_imp;
Both the base protocol and driver part rely on this being of size
MAX_PROTOCOLS_IMP (if existing).
Could this be a "u8 protocols_imp[MAX_PROTOCOLS_IMP]" instead? Or at
least add a comment making it clearer at the scmi_info definition that
this array will have MAX_PROTOCOLS_IMP elements in all scmi_info instances?
Other than that, patch seems fine.
Cheers,
--
Julien Thierry
On 05/09/17 14:39, Julien Thierry wrote:
[...]
>> diff --git a/drivers/firmware/arm_scmi/driver.c
>> b/drivers/firmware/arm_scmi/driver.c
>> index 139d6980f270..601d0d7210d9 100644
>> --- a/drivers/firmware/arm_scmi/driver.c
>> +++ b/drivers/firmware/arm_scmi/driver.c
>> @@ -108,18 +108,22 @@ struct scmi_desc {
>> * @dev: Device pointer
>> * @desc: SoC description for this instance
>> * @handle: Instance of SCMI handle to send to clients
>> + * @version: SCMI revision information containing protocol version,
>> + * implementation version and (sub-)vendor identification.
>> * @cl: Mailbox Client
>> * @tx_chan: Transmit mailbox channel
>> * @rx_chan: Receive mailbox channel
>> * @tx_payload: Transmit mailbox channel payload area
>> * @rx_payload: Receive mailbox channel payload area
>> * @minfo: Message info
>> + * @protocols_imp: list of protocols implemented
>> * @node: list head
>> * @users: Number of users of this instance
>> */
>> struct scmi_info {
>> struct device *dev;
>> const struct scmi_desc *desc;
>> + struct scmi_revision_info version;
>> struct scmi_handle handle;
>> struct mbox_client cl;
>> struct mbox_chan *tx_chan;
>> @@ -127,6 +131,7 @@ struct scmi_info {
>> void __iomem *tx_payload;
>> void __iomem *rx_payload;
>> struct scmi_xfers_info minfo;
>> + u8 *protocols_imp;
>
> Both the base protocol and driver part rely on this being of size
> MAX_PROTOCOLS_IMP (if existing).
>
> Could this be a "u8 protocols_imp[MAX_PROTOCOLS_IMP]" instead?
Sounds better, will update accordingly.
--
Regards,
Sudeep
Hi Sudeep,
On 04/08/17 15:31, Sudeep Holla wrote:
> The performance protocol is intended for the performance management of
> group(s) of device(s) that run in the same performance domain. It
> includes even the CPUs. A performance domain is defined by a set of
> devices that always have to run at the same performance level.
> For example, a set of CPUs that share a voltage domain, and have a
> common frequency control, is said to be in the same performance domain.
>
> The commands in this protocol provide functionality to describe the
> protocol version, describe various attribute flags, set and get the
> performance level of a domain. It also supports discovery of the list
> of performance levels supported by a performance domain, and the
> properties of each performance level.
>
> This patch adds basic support for the performance protocol.
>
> Cc: Arnd Bergmann <[email protected]>
> Signed-off-by: Sudeep Holla <[email protected]>
> ---
> drivers/firmware/arm_scmi/Makefile | 2 +-
> drivers/firmware/arm_scmi/common.h | 1 +
> drivers/firmware/arm_scmi/perf.c | 511 +++++++++++++++++++++++++++++++++++++
> include/linux/scmi_protocol.h | 31 +++
> 4 files changed, 544 insertions(+), 1 deletion(-)
> create mode 100644 drivers/firmware/arm_scmi/perf.c
>
> diff --git a/drivers/firmware/arm_scmi/Makefile b/drivers/firmware/arm_scmi/Makefile
> index 21d01d1d6b9c..159de726ee45 100644
> --- a/drivers/firmware/arm_scmi/Makefile
> +++ b/drivers/firmware/arm_scmi/Makefile
> @@ -1,2 +1,2 @@
> obj-$(CONFIG_ARM_SCMI_PROTOCOL) = arm_scmi.o
> -arm_scmi-y = base.o driver.o
> +arm_scmi-y = base.o driver.o perf.o
> diff --git a/drivers/firmware/arm_scmi/common.h b/drivers/firmware/arm_scmi/common.h
> index e3fe5d9acc82..7473dfcad4ee 100644
> --- a/drivers/firmware/arm_scmi/common.h
> +++ b/drivers/firmware/arm_scmi/common.h
> @@ -30,6 +30,7 @@
> #define PROTOCOL_REV_MAJOR(x) ((x) >> PROTOCOL_REV_MINOR_BITS)
> #define PROTOCOL_REV_MINOR(x) ((x) & PROTOCOL_REV_MINOR_MASK)
> #define MAX_PROTOCOLS_IMP 16
> +#define MAX_OPPS 16
>
> enum scmi_std_protocol {
> SCMI_PROTOCOL_BASE = 0x10,
> diff --git a/drivers/firmware/arm_scmi/perf.c b/drivers/firmware/arm_scmi/perf.c
> new file mode 100644
> index 000000000000..13d84d829201
> --- /dev/null
> +++ b/drivers/firmware/arm_scmi/perf.c
> @@ -0,0 +1,511 @@
> +/*
> + * System Control and Management Interface (SCMI) Performance Protocol
> + *
> + * Copyright (C) 2017 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/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_opp.h>
> +#include <linux/sort.h>
> +
> +#include "common.h"
> +
> +enum scmi_performance_protocol_cmd {
> + PERF_DOMAIN_ATTRIBUTES = 0x3,
> + PERF_DESCRIBE_LEVELS = 0x4,
> + PERF_LIMITS_SET = 0x5,
> + PERF_LIMITS_GET = 0x6,
> + PERF_LEVEL_SET = 0x7,
> + PERF_LEVEL_GET = 0x8,
> + PERF_NOTIFY_LIMITS = 0x9,
> + PERF_NOTIFY_LEVEL = 0xa,
> +};
> +
> +struct scmi_opp {
> + u32 perf;
> + u32 power;
> + u32 trans_latency_us;
> +};
> +
> +struct scmi_msg_resp_perf_attributes {
> + __le16 num_domains;
> + __le16 flags;
> +#define POWER_SCALE_IN_MILLIWATT(x) ((x) & BIT(0))
> + __le32 stats_addr_low;
> + __le32 stats_addr_high;
> + __le32 stats_size;
> +};
> +
> +struct scmi_msg_resp_perf_domain_attributes {
> + __le32 flags;
> +#define SUPPORTS_SET_LIMITS(x) ((x) & BIT(31))
> +#define SUPPORTS_SET_PERF_LVL(x) ((x) & BIT(30))
> +#define SUPPORTS_PERF_LIMIT_NOTIFY(x) ((x) & BIT(29))
> +#define SUPPORTS_PERF_LEVEL_NOTIFY(x) ((x) & BIT(28))
> + __le32 rate_limit_us;
> + __le32 sustained_freq_khz;
> + __le32 sustained_perf_level;
> + u8 name[SCMI_MAX_STR_SIZE];
> +};
> +
> +struct scmi_msg_perf_describe_levels {
> + __le32 domain;
> + __le32 level_index;
> +};
> +
> +struct scmi_perf_set_limits {
> + __le32 domain;
> + __le32 max_level;
> + __le32 min_level;
> +};
> +
> +struct scmi_perf_get_limits {
> + __le32 max_level;
> + __le32 min_level;
> +};
> +
> +struct scmi_perf_set_level {
> + __le32 domain;
> + __le32 level;
> +};
> +
> +struct scmi_perf_notify_level_or_limits {
> + __le32 domain;
> + __le32 notify_enable;
> +};
> +
> +struct scmi_msg_resp_perf_describe_levels {
> + __le16 num_returned;
> + __le16 num_remaining;
> + struct {
> + __le32 perf_val;
> + __le32 power;
> + __le16 transition_latency_us;
> + __le16 reserved;
> + } opp[0];
> +};
> +
> +struct perf_dom_info {
> + bool set_limits;
> + bool set_perf;
> + bool perf_limit_notify;
> + bool perf_level_notify;
> + u32 opp_count;
> + u32 sustained_freq_khz;
> + u32 sustained_perf_level;
> + u32 mult_factor;
> + char name[SCMI_MAX_STR_SIZE];
> + struct scmi_opp opp[MAX_OPPS];
> +};
> +
> +struct scmi_perf_info {
> + int num_domains;
> + bool power_scale_mw;
> + u64 stats_addr;
> + u32 stats_size;
> + struct perf_dom_info *dom_info;
> +};
> +
> +static struct scmi_perf_info perf_info;
> +
> +static int scmi_perf_attributes_get(const struct scmi_handle *handle,
> + struct scmi_perf_info *perf_info)
> +{
> + int ret;
> + struct scmi_xfer *t;
> + struct scmi_msg_resp_perf_attributes *attr;
> +
> + ret = scmi_one_xfer_init(handle, PROTOCOL_ATTRIBUTES,
> + SCMI_PROTOCOL_PERF, 0, sizeof(*attr), &t);
> + if (ret)
> + return ret;
> +
> + attr = t->rx.buf;
> +
> + ret = scmi_do_xfer(handle, t);
> + if (!ret) {
> + u16 flags = le16_to_cpu(attr->flags);
> +
> + perf_info->num_domains = le16_to_cpu(attr->num_domains);
> + perf_info->power_scale_mw = POWER_SCALE_IN_MILLIWATT(flags);
> + perf_info->stats_addr = le32_to_cpu(attr->stats_addr_low) |
> + (u64)le32_to_cpu(attr->stats_addr_high) << 32;
This seems odd, shouldn't it be the following?
le64_to_cpu(attr->stats_addr_low | (__le64)attr->stats_addr_high << 32)
Cheers,
--
Julien Thierry
On 05/09/17 16:04, Julien Thierry wrote:
> Hi Sudeep,
>
> On 04/08/17 15:31, Sudeep Holla wrote:
>> The performance protocol is intended for the performance management of
>> group(s) of device(s) that run in the same performance domain. It
>> includes even the CPUs. A performance domain is defined by a set of
>> devices that always have to run at the same performance level.
>> For example, a set of CPUs that share a voltage domain, and have a
>> common frequency control, is said to be in the same performance domain.
>>
>> The commands in this protocol provide functionality to describe the
>> protocol version, describe various attribute flags, set and get the
>> performance level of a domain. It also supports discovery of the list
>> of performance levels supported by a performance domain, and the
>> properties of each performance level.
>>
>> This patch adds basic support for the performance protocol.
>>
>> Cc: Arnd Bergmann <[email protected]>
>> Signed-off-by: Sudeep Holla <[email protected]>
>> ---
>> drivers/firmware/arm_scmi/Makefile | 2 +-
>> drivers/firmware/arm_scmi/common.h | 1 +
>> drivers/firmware/arm_scmi/perf.c | 511
>> +++++++++++++++++++++++++++++++++++++
>> include/linux/scmi_protocol.h | 31 +++
>> 4 files changed, 544 insertions(+), 1 deletion(-)
>> create mode 100644 drivers/firmware/arm_scmi/perf.c
>>
>> diff --git a/drivers/firmware/arm_scmi/Makefile
>> b/drivers/firmware/arm_scmi/Makefile
>> index 21d01d1d6b9c..159de726ee45 100644
>> --- a/drivers/firmware/arm_scmi/Makefile
>> +++ b/drivers/firmware/arm_scmi/Makefile
>> @@ -1,2 +1,2 @@
>> obj-$(CONFIG_ARM_SCMI_PROTOCOL) = arm_scmi.o
>> -arm_scmi-y = base.o driver.o
>> +arm_scmi-y = base.o driver.o perf.o
>> diff --git a/drivers/firmware/arm_scmi/common.h
>> b/drivers/firmware/arm_scmi/common.h
>> index e3fe5d9acc82..7473dfcad4ee 100644
>> --- a/drivers/firmware/arm_scmi/common.h
>> +++ b/drivers/firmware/arm_scmi/common.h
>> @@ -30,6 +30,7 @@
>> #define PROTOCOL_REV_MAJOR(x) ((x) >> PROTOCOL_REV_MINOR_BITS)
>> #define PROTOCOL_REV_MINOR(x) ((x) & PROTOCOL_REV_MINOR_MASK)
>> #define MAX_PROTOCOLS_IMP 16
>> +#define MAX_OPPS 16
>> enum scmi_std_protocol {
>> SCMI_PROTOCOL_BASE = 0x10,
>> diff --git a/drivers/firmware/arm_scmi/perf.c
>> b/drivers/firmware/arm_scmi/perf.c
>> new file mode 100644
>> index 000000000000..13d84d829201
>> --- /dev/null
>> +++ b/drivers/firmware/arm_scmi/perf.c
>> @@ -0,0 +1,511 @@
>> +/*
>> + * System Control and Management Interface (SCMI) Performance Protocol
>> + *
>> + * Copyright (C) 2017 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/of.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/pm_opp.h>
>> +#include <linux/sort.h>
>> +
>> +#include "common.h"
>> +
>> +enum scmi_performance_protocol_cmd {
>> + PERF_DOMAIN_ATTRIBUTES = 0x3,
>> + PERF_DESCRIBE_LEVELS = 0x4,
>> + PERF_LIMITS_SET = 0x5,
>> + PERF_LIMITS_GET = 0x6,
>> + PERF_LEVEL_SET = 0x7,
>> + PERF_LEVEL_GET = 0x8,
>> + PERF_NOTIFY_LIMITS = 0x9,
>> + PERF_NOTIFY_LEVEL = 0xa,
>> +};
>> +
>> +struct scmi_opp {
>> + u32 perf;
>> + u32 power;
>> + u32 trans_latency_us;
>> +};
>> +
>> +struct scmi_msg_resp_perf_attributes {
>> + __le16 num_domains;
>> + __le16 flags;
>> +#define POWER_SCALE_IN_MILLIWATT(x) ((x) & BIT(0))
>> + __le32 stats_addr_low;
>> + __le32 stats_addr_high;
>> + __le32 stats_size;
>> +};
>> +
>> +struct scmi_msg_resp_perf_domain_attributes {
>> + __le32 flags;
>> +#define SUPPORTS_SET_LIMITS(x) ((x) & BIT(31))
>> +#define SUPPORTS_SET_PERF_LVL(x) ((x) & BIT(30))
>> +#define SUPPORTS_PERF_LIMIT_NOTIFY(x) ((x) & BIT(29))
>> +#define SUPPORTS_PERF_LEVEL_NOTIFY(x) ((x) & BIT(28))
>> + __le32 rate_limit_us;
>> + __le32 sustained_freq_khz;
>> + __le32 sustained_perf_level;
>> + u8 name[SCMI_MAX_STR_SIZE];
>> +};
>> +
>> +struct scmi_msg_perf_describe_levels {
>> + __le32 domain;
>> + __le32 level_index;
>> +};
>> +
>> +struct scmi_perf_set_limits {
>> + __le32 domain;
>> + __le32 max_level;
>> + __le32 min_level;
>> +};
>> +
>> +struct scmi_perf_get_limits {
>> + __le32 max_level;
>> + __le32 min_level;
>> +};
>> +
>> +struct scmi_perf_set_level {
>> + __le32 domain;
>> + __le32 level;
>> +};
>> +
>> +struct scmi_perf_notify_level_or_limits {
>> + __le32 domain;
>> + __le32 notify_enable;
>> +};
>> +
>> +struct scmi_msg_resp_perf_describe_levels {
>> + __le16 num_returned;
>> + __le16 num_remaining;
>> + struct {
>> + __le32 perf_val;
>> + __le32 power;
>> + __le16 transition_latency_us;
>> + __le16 reserved;
>> + } opp[0];
>> +};
>> +
>> +struct perf_dom_info {
>> + bool set_limits;
>> + bool set_perf;
>> + bool perf_limit_notify;
>> + bool perf_level_notify;
>> + u32 opp_count;
>> + u32 sustained_freq_khz;
>> + u32 sustained_perf_level;
>> + u32 mult_factor;
>> + char name[SCMI_MAX_STR_SIZE];
>> + struct scmi_opp opp[MAX_OPPS];
>> +};
>> +
>> +struct scmi_perf_info {
>> + int num_domains;
>> + bool power_scale_mw;
>> + u64 stats_addr;
>> + u32 stats_size;
>> + struct perf_dom_info *dom_info;
>> +};
>> +
>> +static struct scmi_perf_info perf_info;
>> +
>> +static int scmi_perf_attributes_get(const struct scmi_handle *handle,
>> + struct scmi_perf_info *perf_info)
>> +{
>> + int ret;
>> + struct scmi_xfer *t;
>> + struct scmi_msg_resp_perf_attributes *attr;
>> +
>> + ret = scmi_one_xfer_init(handle, PROTOCOL_ATTRIBUTES,
>> + SCMI_PROTOCOL_PERF, 0, sizeof(*attr), &t);
>> + if (ret)
>> + return ret;
>> +
>> + attr = t->rx.buf;
>> +
>> + ret = scmi_do_xfer(handle, t);
>> + if (!ret) {
>> + u16 flags = le16_to_cpu(attr->flags);
>> +
>> + perf_info->num_domains = le16_to_cpu(attr->num_domains);
>> + perf_info->power_scale_mw = POWER_SCALE_IN_MILLIWATT(flags);
>> + perf_info->stats_addr = le32_to_cpu(attr->stats_addr_low) |
>> + (u64)le32_to_cpu(attr->stats_addr_high) << 32;
>
> This seems odd, shouldn't it be the following?
> le64_to_cpu(attr->stats_addr_low | (__le64)attr->stats_addr_high << 32)
>
After further reflexion, I think you are right. If I understood the
specification, the address seems to be split into upper and lower 32bits
and each one is stored as a uint32, which fits what you are doing to
obtain the address.
You can ignore my previous comment.
--
Julien Thierry
Hi Julien,
On 05/09/17 16:56, Julien Thierry wrote:
>
>
> On 05/09/17 16:04, Julien Thierry wrote:
[...]
>>
>> This seems odd, shouldn't it be the following?
>> le64_to_cpu(attr->stats_addr_low | (__le64)attr->stats_addr_high << 32)
>>
>
> After further reflexion, I think you are right. If I understood the
> specification, the address seems to be split into upper and lower 32bits
> and each one is stored as a uint32, which fits what you are doing to
> obtain the address.
>
> You can ignore my previous comment.
>
Sorry I am checking this bit late, you have figured out yourself
already. All the 64 bit values need to be dealt similarly as SCMI
specification restricts so. It's basically done so as the firmware
running is mostly 32-bit little endian system.
--
Regards,
Sudeep
On 04/08/17 15:31, Sudeep Holla wrote:
> Now that we have basic support for all the protocols in the
> specification, let's probe them individually and initialise them.
>
> Cc: Arnd Bergmann <[email protected]>
> Signed-off-by: Sudeep Holla <[email protected]>
> ---
> drivers/firmware/arm_scmi/common.h | 5 +++
> drivers/firmware/arm_scmi/driver.c | 80 +++++++++++++++++++++++++++++++++++++-
> 2 files changed, 84 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/firmware/arm_scmi/common.h b/drivers/firmware/arm_scmi/common.h
> index 7473dfcad4ee..d7c73a8d260b 100644
> --- a/drivers/firmware/arm_scmi/common.h
> +++ b/drivers/firmware/arm_scmi/common.h
> @@ -118,4 +118,9 @@ int scmi_version_get(const struct scmi_handle *h, u8 protocol, u32 *version);
> void scmi_setup_protocol_implemented(const struct scmi_handle *handle,
> u8 *prot_imp);
>
> +typedef int (*scmi_init_fn_t)(struct scmi_handle *);
> int scmi_base_protocol_init(struct scmi_handle *h);
> +int scmi_perf_protocol_init(struct scmi_handle *h);
> +int scmi_sensors_protocol_init(struct scmi_handle *h);
> +int scmi_power_protocol_init(struct scmi_handle *h);
> +int scmi_clock_protocol_init(struct scmi_handle *h);
> diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c
> index 601d0d7210d9..6f31761043e2 100644
> --- a/drivers/firmware/arm_scmi/driver.c
> +++ b/drivers/firmware/arm_scmi/driver.c
> @@ -157,6 +157,12 @@ struct scmi_shared_mem {
> u8 msg_payload[0];
> };
>
> +struct scmi_protocol_match {
> + u8 protocol_id;
> + scmi_init_fn_t fn;
Could we call this "init" or "prot_init"?
> + char name[32];
> +};
> +
> static int scmi_linux_errmap[] = {
> /* better than switch case as long as return value is continuous */
> 0, /* SCMI_SUCCESS */
> @@ -687,6 +693,41 @@ static int scmi_xfer_info_init(struct scmi_info *sinfo)
> return 0;
> }
>
> +static const struct scmi_protocol_match scmi_protocols[] = {
> + {
> + .protocol_id = SCMI_PROTOCOL_PERF,
> + .fn = scmi_perf_protocol_init,
> + .name = "scmi-cpufreq",
> + }, {
> + .protocol_id = SCMI_PROTOCOL_CLOCK,
> + .fn = scmi_clock_protocol_init,
> + .name = "scmi-clocks",
> + }, {
> + .protocol_id = SCMI_PROTOCOL_POWER,
> + .fn = scmi_power_protocol_init,
> + .name = "scmi-power-domain",
> + }, {
> + .protocol_id = SCMI_PROTOCOL_SENSOR,
> + .fn = scmi_sensors_protocol_init,
> + .name = "scmi-hwmon",
> + },
> + {}
> +};
> +
> +static const struct scmi_protocol_match *scmi_protocol_match_get(u8 protocol_id)
> +{
> + int i;
> + const struct scmi_protocol_match *match = NULL, *loop = scmi_protocols;
> +
> + for (i = 0; i < ARRAY_SIZE(scmi_protocols); i++, loop++)
> + if (loop->protocol_id == protocol_id) {
> + match = loop;
> + break;
> + }
> +
> + return match;
> +}
> +
The "match" variable is not needed. We can just return "loop" in the if
branch and return NULL at the end of the function. Unless we are
following some coding standard that advises against that?
> static int scmi_mailbox_check(struct device_node *np)
> {
> struct of_phandle_args arg;
> @@ -778,7 +819,7 @@ static int scmi_probe(struct platform_device *pdev)
> const struct scmi_desc *desc;
> struct scmi_info *info;
> struct device *dev = &pdev->dev;
> - struct device_node *np = dev->of_node;
> + struct device_node *child, *np = dev->of_node;
>
> /* Only mailbox method supported, check for the presence of one */
> if (scmi_mailbox_check(np)) {
> @@ -817,6 +858,43 @@ static int scmi_probe(struct platform_device *pdev)
> return ret;
> }
>
> + for_each_available_child_of_node(np, child) {
> + int init_ret;
> + u32 prot_id;
> + const struct scmi_protocol_match *match;
> +
> + if (of_property_read_u32(child, "reg", &prot_id))
> + continue;
> +
> + prot_id &= MSG_PROTOCOL_ID_MASK;
> +
> + if (!scmi_is_protocol_implemented(handle, prot_id)) {
> + dev_err(dev, "SCMI protocol %d not implemented\n",
> + prot_id);
> + continue;
> + }
> +
> + match = scmi_protocol_match_get(prot_id);
> + if (match) {
> + struct platform_device *cdev;
> +
> + cdev = of_platform_device_create(child, match->name,
> + dev);
> + if (!cdev) {
> + dev_err(dev, "failed to create %s device\n",
> + match->name);
> + continue;
> + }
> +
> + init_ret = match->fn(handle);
> + if (init_ret) {
> + dev_err(dev, "SCMI protocol %d init error %d\n",
> + prot_id, init_ret);
> + of_platform_device_destroy(&cdev->dev, NULL);
> + }
> + }
> + }
> +
For the code in the "if (match)" branch, would it be better to have an
inline function "scmi_create_protocol_device" or something with a name
that fits?
Also I was wondering, what is the difference between the protocol being
implemented and finding the matching structure? Feels like there is a
bit of redundancy. If the protocol is implemented but doesn't have a
match structure does it just mean it doesn't need any specific
initialization?
Thanks,
--
Julien Thierry
On 06/09/17 10:41, Julien Thierry wrote:
>
>
> On 04/08/17 15:31, Sudeep Holla wrote:
>> Now that we have basic support for all the protocols in the
>> specification, let's probe them individually and initialise them.
>>
>> Cc: Arnd Bergmann <[email protected]>
>> Signed-off-by: Sudeep Holla <[email protected]>
>> ---
>> drivers/firmware/arm_scmi/common.h | 5 +++
>> drivers/firmware/arm_scmi/driver.c | 80
>> +++++++++++++++++++++++++++++++++++++-
>> 2 files changed, 84 insertions(+), 1 deletion(-)
>>
>> diff --git a/drivers/firmware/arm_scmi/common.h
>> b/drivers/firmware/arm_scmi/common.h
>> index 7473dfcad4ee..d7c73a8d260b 100644
>> --- a/drivers/firmware/arm_scmi/common.h
>> +++ b/drivers/firmware/arm_scmi/common.h
>> @@ -118,4 +118,9 @@ int scmi_version_get(const struct scmi_handle *h,
>> u8 protocol, u32 *version);
>> void scmi_setup_protocol_implemented(const struct scmi_handle *handle,
>> u8 *prot_imp);
>> +typedef int (*scmi_init_fn_t)(struct scmi_handle *);
>> int scmi_base_protocol_init(struct scmi_handle *h);
>> +int scmi_perf_protocol_init(struct scmi_handle *h);
>> +int scmi_sensors_protocol_init(struct scmi_handle *h);
>> +int scmi_power_protocol_init(struct scmi_handle *h);
>> +int scmi_clock_protocol_init(struct scmi_handle *h);
>> diff --git a/drivers/firmware/arm_scmi/driver.c
>> b/drivers/firmware/arm_scmi/driver.c
>> index 601d0d7210d9..6f31761043e2 100644
>> --- a/drivers/firmware/arm_scmi/driver.c
>> +++ b/drivers/firmware/arm_scmi/driver.c
>> @@ -157,6 +157,12 @@ struct scmi_shared_mem {
>> u8 msg_payload[0];
>> };
>> +struct scmi_protocol_match {
>> + u8 protocol_id;
>> + scmi_init_fn_t fn;
>
> Could we call this "init" or "prot_init"?
>
Done
>> + char name[32];
>> +};
>> +
>> static int scmi_linux_errmap[] = {
>> /* better than switch case as long as return value is continuous */
>> 0, /* SCMI_SUCCESS */
>> @@ -687,6 +693,41 @@ static int scmi_xfer_info_init(struct scmi_info
>> *sinfo)
>> return 0;
>> }
>> +static const struct scmi_protocol_match scmi_protocols[] = {
>> + {
>> + .protocol_id = SCMI_PROTOCOL_PERF,
>> + .fn = scmi_perf_protocol_init,
>> + .name = "scmi-cpufreq",
>> + }, {
>> + .protocol_id = SCMI_PROTOCOL_CLOCK,
>> + .fn = scmi_clock_protocol_init,
>> + .name = "scmi-clocks",
>> + }, {
>> + .protocol_id = SCMI_PROTOCOL_POWER,
>> + .fn = scmi_power_protocol_init,
>> + .name = "scmi-power-domain",
>> + }, {
>> + .protocol_id = SCMI_PROTOCOL_SENSOR,
>> + .fn = scmi_sensors_protocol_init,
>> + .name = "scmi-hwmon",
>> + },
>> + {}
>> +};
>> +
>> +static const struct scmi_protocol_match *scmi_protocol_match_get(u8
>> protocol_id)
>> +{
>> + int i;
>> + const struct scmi_protocol_match *match = NULL, *loop =
>> scmi_protocols;
>> +
>> + for (i = 0; i < ARRAY_SIZE(scmi_protocols); i++, loop++)
>> + if (loop->protocol_id == protocol_id) {
>> + match = loop;
>> + break;
>> + }
>> +
>> + return match;
>> +}
>> +
>
> The "match" variable is not needed. We can just return "loop" in the if
> branch and return NULL at the end of the function. Unless we are
> following some coding standard that advises against that?
>
Agreed and fixed locally.
>> static int scmi_mailbox_check(struct device_node *np)
>> {
>> struct of_phandle_args arg;
>> @@ -778,7 +819,7 @@ static int scmi_probe(struct platform_device *pdev)
>> const struct scmi_desc *desc;
>> struct scmi_info *info;
>> struct device *dev = &pdev->dev;
>> - struct device_node *np = dev->of_node;
>> + struct device_node *child, *np = dev->of_node;
>> /* Only mailbox method supported, check for the presence of
>> one */
>> if (scmi_mailbox_check(np)) {
>> @@ -817,6 +858,43 @@ static int scmi_probe(struct platform_device *pdev)
>> return ret;
>> }
>> + for_each_available_child_of_node(np, child) {
>> + int init_ret;
>> + u32 prot_id;
>> + const struct scmi_protocol_match *match;
>> +
>> + if (of_property_read_u32(child, "reg", &prot_id))
>> + continue;
>> +
>> + prot_id &= MSG_PROTOCOL_ID_MASK;
>> +
>> + if (!scmi_is_protocol_implemented(handle, prot_id)) {
>> + dev_err(dev, "SCMI protocol %d not implemented\n",
>> + prot_id);
>> + continue;
>> + }
>> +
>> + match = scmi_protocol_match_get(prot_id);
>> + if (match) {
>> + struct platform_device *cdev;
>> +
>> + cdev = of_platform_device_create(child, match->name,
>> + dev);
>> + if (!cdev) {
>> + dev_err(dev, "failed to create %s device\n",
>> + match->name);
>> + continue;
>> + }
>> +
>> + init_ret = match->fn(handle);
>> + if (init_ret) {
>> + dev_err(dev, "SCMI protocol %d init error %d\n",
>> + prot_id, init_ret);
>> + of_platform_device_destroy(&cdev->dev, NULL);
>> + }
>> + }
>> + }
>> +
>
> For the code in the "if (match)" branch, would it be better to have an
> inline function "scmi_create_protocol_device" or something with a name
> that fits?
>
Done
> Also I was wondering, what is the difference between the protocol being
> implemented and finding the matching structure? Feels like there is a
> bit of redundancy. If the protocol is implemented but doesn't have a
> match structure does it just mean it doesn't need any specific
> initialization?
>
Yes, that's the idea. One reason to have both check is backward
compatibility. Suppose new protocols are implemented in the f/w but DT
doesn't have them or know how to use it or there are no users of that
feature, then skip initializing it. Similarly if DT is updated but not
the firmware.
--
Regards,
Sudeep
On 06/09/17 14:31, Sudeep Holla wrote:
>
>
> On 06/09/17 10:41, Julien Thierry wrote:
>>
>>
>> On 04/08/17 15:31, Sudeep Holla wrote:
>>> Now that we have basic support for all the protocols in the
>>> specification, let's probe them individually and initialise them.
>>>
>>> Cc: Arnd Bergmann <[email protected]>
>>> Signed-off-by: Sudeep Holla <[email protected]>
>>> ---
>>> drivers/firmware/arm_scmi/common.h | 5 +++
>>> drivers/firmware/arm_scmi/driver.c | 80
>>> +++++++++++++++++++++++++++++++++++++-
>>> 2 files changed, 84 insertions(+), 1 deletion(-)
>>>
>>> diff --git a/drivers/firmware/arm_scmi/common.h
>>> b/drivers/firmware/arm_scmi/common.h
>>> index 7473dfcad4ee..d7c73a8d260b 100644
>>> --- a/drivers/firmware/arm_scmi/common.h
>>> +++ b/drivers/firmware/arm_scmi/common.h
>>> @@ -118,4 +118,9 @@ int scmi_version_get(const struct scmi_handle *h,
>>> u8 protocol, u32 *version);
>>> void scmi_setup_protocol_implemented(const struct scmi_handle *handle,
>>> u8 *prot_imp);
>>> +typedef int (*scmi_init_fn_t)(struct scmi_handle *);
>>> int scmi_base_protocol_init(struct scmi_handle *h);
>>> +int scmi_perf_protocol_init(struct scmi_handle *h);
>>> +int scmi_sensors_protocol_init(struct scmi_handle *h);
>>> +int scmi_power_protocol_init(struct scmi_handle *h);
>>> +int scmi_clock_protocol_init(struct scmi_handle *h);
>>> diff --git a/drivers/firmware/arm_scmi/driver.c
>>> b/drivers/firmware/arm_scmi/driver.c
>>> index 601d0d7210d9..6f31761043e2 100644
>>> --- a/drivers/firmware/arm_scmi/driver.c
>>> +++ b/drivers/firmware/arm_scmi/driver.c
>>> @@ -157,6 +157,12 @@ struct scmi_shared_mem {
>>> u8 msg_payload[0];
>>> };
>>> +struct scmi_protocol_match {
>>> + u8 protocol_id;
>>> + scmi_init_fn_t fn;
>>
>> Could we call this "init" or "prot_init"?
>>
>
> Done
>
>>> + char name[32];
>>> +};
>>> +
>>> static int scmi_linux_errmap[] = {
>>> /* better than switch case as long as return value is continuous */
>>> 0, /* SCMI_SUCCESS */
>>> @@ -687,6 +693,41 @@ static int scmi_xfer_info_init(struct scmi_info
>>> *sinfo)
>>> return 0;
>>> }
>>> +static const struct scmi_protocol_match scmi_protocols[] = {
>>> + {
>>> + .protocol_id = SCMI_PROTOCOL_PERF,
>>> + .fn = scmi_perf_protocol_init,
>>> + .name = "scmi-cpufreq",
>>> + }, {
>>> + .protocol_id = SCMI_PROTOCOL_CLOCK,
>>> + .fn = scmi_clock_protocol_init,
>>> + .name = "scmi-clocks",
>>> + }, {
>>> + .protocol_id = SCMI_PROTOCOL_POWER,
>>> + .fn = scmi_power_protocol_init,
>>> + .name = "scmi-power-domain",
>>> + }, {
>>> + .protocol_id = SCMI_PROTOCOL_SENSOR,
>>> + .fn = scmi_sensors_protocol_init,
>>> + .name = "scmi-hwmon",
>>> + },
>>> + {}
>>> +};
>>> +
>>> +static const struct scmi_protocol_match *scmi_protocol_match_get(u8
>>> protocol_id)
>>> +{
>>> + int i;
>>> + const struct scmi_protocol_match *match = NULL, *loop =
>>> scmi_protocols;
>>> +
>>> + for (i = 0; i < ARRAY_SIZE(scmi_protocols); i++, loop++)
>>> + if (loop->protocol_id == protocol_id) {
>>> + match = loop;
>>> + break;
>>> + }
>>> +
>>> + return match;
>>> +}
>>> +
>>
>> The "match" variable is not needed. We can just return "loop" in the if
>> branch and return NULL at the end of the function. Unless we are
>> following some coding standard that advises against that?
>>
>
> Agreed and fixed locally.
>
>>> static int scmi_mailbox_check(struct device_node *np)
>>> {
>>> struct of_phandle_args arg;
>>> @@ -778,7 +819,7 @@ static int scmi_probe(struct platform_device *pdev)
>>> const struct scmi_desc *desc;
>>> struct scmi_info *info;
>>> struct device *dev = &pdev->dev;
>>> - struct device_node *np = dev->of_node;
>>> + struct device_node *child, *np = dev->of_node;
>>> /* Only mailbox method supported, check for the presence of
>>> one */
>>> if (scmi_mailbox_check(np)) {
>>> @@ -817,6 +858,43 @@ static int scmi_probe(struct platform_device *pdev)
>>> return ret;
>>> }
>>> + for_each_available_child_of_node(np, child) {
>>> + int init_ret;
>>> + u32 prot_id;
>>> + const struct scmi_protocol_match *match;
>>> +
>>> + if (of_property_read_u32(child, "reg", &prot_id))
>>> + continue;
>>> +
>>> + prot_id &= MSG_PROTOCOL_ID_MASK;
>>> +
>>> + if (!scmi_is_protocol_implemented(handle, prot_id)) {
>>> + dev_err(dev, "SCMI protocol %d not implemented\n",
>>> + prot_id);
>>> + continue;
>>> + }
>>> +
>>> + match = scmi_protocol_match_get(prot_id);
>>> + if (match) {
>>> + struct platform_device *cdev;
>>> +
>>> + cdev = of_platform_device_create(child, match->name,
>>> + dev);
>>> + if (!cdev) {
>>> + dev_err(dev, "failed to create %s device\n",
>>> + match->name);
>>> + continue;
>>> + }
>>> +
>>> + init_ret = match->fn(handle);
>>> + if (init_ret) {
>>> + dev_err(dev, "SCMI protocol %d init error %d\n",
>>> + prot_id, init_ret);
>>> + of_platform_device_destroy(&cdev->dev, NULL);
>>> + }
>>> + }
>>> + }
>>> +
>>
>> For the code in the "if (match)" branch, would it be better to have an
>> inline function "scmi_create_protocol_device" or something with a name
>> that fits?
>>
>
> Done
>
>> Also I was wondering, what is the difference between the protocol being
>> implemented and finding the matching structure? Feels like there is a
>> bit of redundancy. If the protocol is implemented but doesn't have a
>> match structure does it just mean it doesn't need any specific
>> initialization?
>>
>
> Yes, that's the idea. One reason to have both check is backward
> compatibility. Suppose new protocols are implemented in the f/w but DT
> doesn't have them or know how to use it or there are no users of that
> feature, then skip initializing it. Similarly if DT is updated but not
> the firmware.
>
Oh I see, makes sense. Thanks for explaining.
Cheers,
--
Julien Thierry