From bcb41a53b0b075600cb821302e7177ca5ab62efd Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Tue, 9 Jan 2018 19:29:56 +0100 Subject: soc: samsung: Add SPDX license identifiers to headers Replace GPL license statements with SPDX GPL-2.0 license identifiers. Signed-off-by: Krzysztof Kozlowski --- include/linux/soc/samsung/exynos-pmu.h | 5 +---- include/linux/soc/samsung/exynos-regs-pmu.h | 6 +----- 2 files changed, 2 insertions(+), 9 deletions(-) (limited to 'include') diff --git a/include/linux/soc/samsung/exynos-pmu.h b/include/linux/soc/samsung/exynos-pmu.h index e57eb4b6cc5a..fc0b445bb36b 100644 --- a/include/linux/soc/samsung/exynos-pmu.h +++ b/include/linux/soc/samsung/exynos-pmu.h @@ -1,12 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0 */ /* * Copyright (c) 2014 Samsung Electronics Co., Ltd. * http://www.samsung.com * * Header for EXYNOS PMU Driver support - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. */ #ifndef __LINUX_SOC_EXYNOS_PMU_H diff --git a/include/linux/soc/samsung/exynos-regs-pmu.h b/include/linux/soc/samsung/exynos-regs-pmu.h index bebdde5dccd6..66dcb9ec273a 100644 --- a/include/linux/soc/samsung/exynos-regs-pmu.h +++ b/include/linux/soc/samsung/exynos-regs-pmu.h @@ -1,14 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0 */ /* * Copyright (c) 2010-2015 Samsung Electronics Co., Ltd. * http://www.samsung.com * * EXYNOS - Power management unit definition * - * 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. - * - * * Notice: * This is not a list of all Exynos Power Management Unit SFRs. * There are too many of them, not mentioning subtle differences -- cgit v1.2.3 From aa4f886f3893f88146e8e02fd1e9c5c9e43cbcc1 Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Tue, 28 Mar 2017 11:36:07 +0100 Subject: firmware: arm_scmi: add basic driver infrastructure for SCMI 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 Cc: Greg Kroah-Hartman Signed-off-by: Sudeep Holla --- MAINTAINERS | 3 +- drivers/firmware/Kconfig | 21 ++ drivers/firmware/Makefile | 1 + drivers/firmware/arm_scmi/Makefile | 2 + drivers/firmware/arm_scmi/common.h | 66 ++++ drivers/firmware/arm_scmi/driver.c | 678 +++++++++++++++++++++++++++++++++++++ include/linux/scmi_protocol.h | 16 + 7 files changed, 786 insertions(+), 1 deletion(-) 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 (limited to 'include') diff --git a/MAINTAINERS b/MAINTAINERS index 5c8c55ba22a3..7cede6e7dfed 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13387,7 +13387,8 @@ F: Documentation/devicetree/bindings/arm/arm,sc[mp]i.txt F: drivers/clk/clk-scpi.c F: drivers/cpufreq/scpi-cpufreq.c F: drivers/firmware/arm_scpi.c -F: include/linux/scpi_protocol.h +F: drivers/firmware/arm_scmi/ +F: include/linux/sc[mp]i_protocol.h SYSTEM RESET/SHUTDOWN DRIVERS M: Sebastian Reichel diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig index b7c748248e53..704961e0473a 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 + bool "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 b248238ddc6a..e18a041cfc53 100644 --- a/drivers/firmware/Makefile +++ b/drivers/firmware/Makefile @@ -25,6 +25,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..b2a24ba2b636 --- /dev/null +++ b/drivers/firmware/arm_scmi/Makefile @@ -0,0 +1,2 @@ +obj-y = scmi-driver.o +scmi-driver-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..d57eb1862f68 --- /dev/null +++ b/drivers/firmware/arm_scmi/common.h @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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) 2018 ARM Ltd. + */ + +#include +#include +#include + +/** + * 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 { + void *con_priv; + 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); +int scmi_handle_put(const struct scmi_handle *handle); +struct scmi_handle *scmi_handle_get(struct device *dev); diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c new file mode 100644 index 000000000000..7824cce54373 --- /dev/null +++ b/drivers/firmware/arm_scmi/driver.c @@ -0,0 +1,678 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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) 2018 ARM Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + * + * @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 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 + * @tx_payload: Transmit 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; + void __iomem *tx_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) + +/* + * SCMI specification requires all parameters, message headers, return + * arguments or any protocol data to be expressed in little endian + * format only. + */ +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 const 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 __iomem *mem) +{ + xfer->hdr.status = ioread32(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, ioread32(&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 __iomem *mem = info->tx_payload; + + xfer_id = MSG_XTRACT_TOKEN(ioread32(&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 __iomem *mem = info->tx_payload; + + /* Mark channel busy + clear error */ + iowrite32(0x0, &mem->channel_status); + iowrite32(t->hdr.poll_completion ? 0 : SCMI_SHMEM_FLAG_INTR_ENABLED, + &mem->flags); + iowrite32(sizeof(mem->msg_header) + t->tx.len, &mem->length); + iowrite32(pack_scmi_header(&t->hdr), &mem->msg_header); + 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; + 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; + + /* 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); +} + +/** + * 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: %pS)\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, NULL on error + */ +struct scmi_handle *scmi_handle_get(struct device *dev) +{ + struct list_head *p; + struct scmi_info *info; + struct scmi_handle *handle = NULL; + + 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); + + return handle; +} + +/** + * 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 null was passed, it returns -EINVAL; + */ +int scmi_handle_put(const struct scmi_handle *handle) +{ + struct scmi_info *info; + + 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; +} + +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); + + 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; + } + + return 0; +} + +static int scmi_remove(struct platform_device *pdev) +{ + int ret = 0; + struct scmi_info *info = platform_get_drvdata(pdev); + + 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; + } + + /* Transmit channel is first entry i.e. index 0 */ + info->tx_chan = mbox_request_channel(cl, 0); + 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 = scmi_of_match, + }, + .probe = scmi_probe, + .remove = scmi_remove, +}; + +module_platform_driver(scmi_driver); + +MODULE_ALIAS("platform: arm-scmi"); +MODULE_AUTHOR("Sudeep Holla "); +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..1f0e89b270c6 --- /dev/null +++ b/include/linux/scmi_protocol.h @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * SCMI Message Protocol driver header + * + * Copyright (C) 2018 ARM Ltd. + */ +#include + +/** + * struct scmi_handle - Handle returned to ARM SCMI clients for usage. + * + * @dev: pointer to the SCMI device + */ +struct scmi_handle { + struct device *dev; +}; -- cgit v1.2.3 From b6f20ff8bd94ad34032804a60bab5ee56752007e Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Tue, 6 Jun 2017 11:16:15 +0100 Subject: firmware: arm_scmi: add common infrastructure and support for base protocol 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 Cc: Greg Kroah-Hartman Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scmi/Makefile | 3 +- drivers/firmware/arm_scmi/base.c | 253 +++++++++++++++++++++++++++++++++++++ drivers/firmware/arm_scmi/common.h | 37 ++++++ drivers/firmware/arm_scmi/driver.c | 53 ++++++++ include/linux/scmi_protocol.h | 37 ++++++ 5 files changed, 382 insertions(+), 1 deletion(-) create mode 100644 drivers/firmware/arm_scmi/base.c (limited to 'include') diff --git a/drivers/firmware/arm_scmi/Makefile b/drivers/firmware/arm_scmi/Makefile index b2a24ba2b636..5d9c7ef35f0f 100644 --- a/drivers/firmware/arm_scmi/Makefile +++ b/drivers/firmware/arm_scmi/Makefile @@ -1,2 +1,3 @@ -obj-y = scmi-driver.o +obj-y = scmi-driver.o scmi-protocols.o scmi-driver-y = driver.o +scmi-protocols-y = base.o diff --git a/drivers/firmware/arm_scmi/base.c b/drivers/firmware/arm_scmi/base.c new file mode 100644 index 000000000000..0d3806c0d432 --- /dev/null +++ b/drivers/firmware/arm_scmi/base.c @@ -0,0 +1,253 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * System Control and Management Interface (SCMI) Base Protocol + * + * Copyright (C) 2018 ARM Ltd. + */ + +#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; + __le32 *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; +} + +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_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 d57eb1862f68..0fc9f5ae8684 100644 --- a/drivers/firmware/arm_scmi/common.h +++ b/drivers/firmware/arm_scmi/common.h @@ -8,9 +8,41 @@ */ #include +#include +#include +#include #include #include +#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_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 * @@ -64,3 +96,8 @@ 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_handle_put(const struct scmi_handle *handle); struct scmi_handle *scmi_handle_get(struct device *dev); +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 7824cce54373..49875cd68365 100644 --- a/drivers/firmware/arm_scmi/driver.c +++ b/drivers/firmware/arm_scmi/driver.c @@ -94,21 +94,27 @@ 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 * @tx_payload: Transmit mailbox channel payload area * @minfo: Message info + * @protocols_imp: list of protocols implemented, currently maximum of + * MAX_PROTOCOLS_IMP elements allocated by the base protocol * @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; void __iomem *tx_payload; struct scmi_xfers_info minfo; + u8 *protocols_imp; struct list_head node; int users; }; @@ -421,6 +427,45 @@ int scmi_one_xfer_init(const struct scmi_handle *handle, u8 msg_id, u8 prot_id, return 0; } +/** + * 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; +} + /** * scmi_handle_get() - Get the SCMI handle for a device * @@ -649,11 +694,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 1f0e89b270c6..08fcc1dd0276 100644 --- a/include/linux/scmi_protocol.h +++ b/include/linux/scmi_protocol.h @@ -6,11 +6,48 @@ */ #include +#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; +}; + +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, }; -- cgit v1.2.3 From 933c504424a2bc784fdb4cd5c318049d55da20e0 Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Mon, 30 Oct 2017 18:33:30 +0000 Subject: firmware: arm_scmi: add scmi protocol bus to enumerate protocol devices The SCMI specification encompasses various protocols. However, not every protocol has to be present on a given platform/implementation as not every protocol is relevant for it. Furthermore, the platform chooses which protocols it exposes to a given agent. The only protocol that must be implemented is the base protocol. The base protocol is used by an agent to discover which protocols are available to it. In order to enumerate the discovered implemented protocols, this patch adds support for a separate scmi protocol bus. It also adds mechanism to register support for different protocols. Cc: Arnd Bergmann Cc: Greg Kroah-Hartman Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scmi/Makefile | 3 +- drivers/firmware/arm_scmi/bus.c | 221 +++++++++++++++++++++++++++++++++++++ drivers/firmware/arm_scmi/common.h | 1 + include/linux/scmi_protocol.h | 64 +++++++++++ 4 files changed, 288 insertions(+), 1 deletion(-) create mode 100644 drivers/firmware/arm_scmi/bus.c (limited to 'include') diff --git a/drivers/firmware/arm_scmi/Makefile b/drivers/firmware/arm_scmi/Makefile index 5d9c7ef35f0f..5f4ec2613db6 100644 --- a/drivers/firmware/arm_scmi/Makefile +++ b/drivers/firmware/arm_scmi/Makefile @@ -1,3 +1,4 @@ -obj-y = scmi-driver.o scmi-protocols.o +obj-y = scmi-bus.o scmi-driver.o scmi-protocols.o +scmi-bus-y = bus.o scmi-driver-y = driver.o scmi-protocols-y = base.o diff --git a/drivers/firmware/arm_scmi/bus.c b/drivers/firmware/arm_scmi/bus.c new file mode 100644 index 000000000000..f2760a596c28 --- /dev/null +++ b/drivers/firmware/arm_scmi/bus.c @@ -0,0 +1,221 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * System Control and Management Interface (SCMI) Message Protocol bus layer + * + * Copyright (C) 2018 ARM Ltd. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include + +#include "common.h" + +static DEFINE_IDA(scmi_bus_id); +static DEFINE_IDR(scmi_protocols); +static DEFINE_SPINLOCK(protocol_lock); + +static const struct scmi_device_id * +scmi_dev_match_id(struct scmi_device *scmi_dev, struct scmi_driver *scmi_drv) +{ + const struct scmi_device_id *id = scmi_drv->id_table; + + if (!id) + return NULL; + + for (; id->protocol_id; id++) + if (id->protocol_id == scmi_dev->protocol_id) + return id; + + return NULL; +} + +static int scmi_dev_match(struct device *dev, struct device_driver *drv) +{ + struct scmi_driver *scmi_drv = to_scmi_driver(drv); + struct scmi_device *scmi_dev = to_scmi_dev(dev); + const struct scmi_device_id *id; + + id = scmi_dev_match_id(scmi_dev, scmi_drv); + if (id) + return 1; + + return 0; +} + +static int scmi_protocol_init(int protocol_id, struct scmi_handle *handle) +{ + scmi_prot_init_fn_t fn = idr_find(&scmi_protocols, protocol_id); + + if (unlikely(!fn)) + return -EINVAL; + return fn(handle); +} + +static int scmi_dev_probe(struct device *dev) +{ + struct scmi_driver *scmi_drv = to_scmi_driver(dev->driver); + struct scmi_device *scmi_dev = to_scmi_dev(dev); + const struct scmi_device_id *id; + int ret; + + id = scmi_dev_match_id(scmi_dev, scmi_drv); + if (!id) + return -ENODEV; + + if (!scmi_dev->handle) + return -EPROBE_DEFER; + + ret = scmi_protocol_init(scmi_dev->protocol_id, scmi_dev->handle); + if (ret) + return ret; + + return scmi_drv->probe(scmi_dev); +} + +static int scmi_dev_remove(struct device *dev) +{ + struct scmi_driver *scmi_drv = to_scmi_driver(dev->driver); + struct scmi_device *scmi_dev = to_scmi_dev(dev); + + if (scmi_drv->remove) + scmi_drv->remove(scmi_dev); + + return 0; +} + +static struct bus_type scmi_bus_type = { + .name = "scmi_protocol", + .match = scmi_dev_match, + .probe = scmi_dev_probe, + .remove = scmi_dev_remove, +}; + +int scmi_driver_register(struct scmi_driver *driver, struct module *owner, + const char *mod_name) +{ + int retval; + + driver->driver.bus = &scmi_bus_type; + driver->driver.name = driver->name; + driver->driver.owner = owner; + driver->driver.mod_name = mod_name; + + retval = driver_register(&driver->driver); + if (!retval) + pr_debug("registered new scmi driver %s\n", driver->name); + + return retval; +} +EXPORT_SYMBOL_GPL(scmi_driver_register); + +void scmi_driver_unregister(struct scmi_driver *driver) +{ + driver_unregister(&driver->driver); +} +EXPORT_SYMBOL_GPL(scmi_driver_unregister); + +struct scmi_device * +scmi_device_create(struct device_node *np, struct device *parent, int protocol) +{ + int id, retval; + struct scmi_device *scmi_dev; + + id = ida_simple_get(&scmi_bus_id, 1, 0, GFP_KERNEL); + if (id < 0) + return NULL; + + scmi_dev = kzalloc(sizeof(*scmi_dev), GFP_KERNEL); + if (!scmi_dev) + goto no_mem; + + scmi_dev->id = id; + scmi_dev->protocol_id = protocol; + scmi_dev->dev.parent = parent; + scmi_dev->dev.of_node = np; + scmi_dev->dev.bus = &scmi_bus_type; + dev_set_name(&scmi_dev->dev, "scmi_dev.%d", id); + + retval = device_register(&scmi_dev->dev); + if (!retval) + return scmi_dev; + + put_device(&scmi_dev->dev); + kfree(scmi_dev); +no_mem: + ida_simple_remove(&scmi_bus_id, id); + return NULL; +} + +void scmi_device_destroy(struct scmi_device *scmi_dev) +{ + scmi_handle_put(scmi_dev->handle); + device_unregister(&scmi_dev->dev); + ida_simple_remove(&scmi_bus_id, scmi_dev->id); + kfree(scmi_dev); +} + +void scmi_set_handle(struct scmi_device *scmi_dev) +{ + scmi_dev->handle = scmi_handle_get(&scmi_dev->dev); +} + +int scmi_protocol_register(int protocol_id, scmi_prot_init_fn_t fn) +{ + int ret; + + spin_lock(&protocol_lock); + ret = idr_alloc(&scmi_protocols, fn, protocol_id, protocol_id + 1, + GFP_ATOMIC); + if (ret != protocol_id) + pr_err("unable to allocate SCMI idr slot, err %d\n", ret); + spin_unlock(&protocol_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(scmi_protocol_register); + +void scmi_protocol_unregister(int protocol_id) +{ + spin_lock(&protocol_lock); + idr_remove(&scmi_protocols, protocol_id); + spin_unlock(&protocol_lock); +} +EXPORT_SYMBOL_GPL(scmi_protocol_unregister); + +static int __scmi_devices_unregister(struct device *dev, void *data) +{ + struct scmi_device *scmi_dev = to_scmi_dev(dev); + + scmi_device_destroy(scmi_dev); + return 0; +} + +static void scmi_devices_unregister(void) +{ + bus_for_each_dev(&scmi_bus_type, NULL, NULL, __scmi_devices_unregister); +} + +static int __init scmi_bus_init(void) +{ + int retval; + + retval = bus_register(&scmi_bus_type); + if (retval) + pr_err("scmi protocol bus register failed (%d)\n", retval); + + return retval; +} +subsys_initcall(scmi_bus_init); + +static void __exit scmi_bus_exit(void) +{ + scmi_devices_unregister(); + bus_unregister(&scmi_bus_type); + ida_destroy(&scmi_bus_id); +} +module_exit(scmi_bus_exit); diff --git a/drivers/firmware/arm_scmi/common.h b/drivers/firmware/arm_scmi/common.h index 0fc9f5ae8684..95053ed5ccb9 100644 --- a/drivers/firmware/arm_scmi/common.h +++ b/drivers/firmware/arm_scmi/common.h @@ -96,6 +96,7 @@ 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_handle_put(const struct scmi_handle *handle); struct scmi_handle *scmi_handle_get(struct device *dev); +void scmi_set_handle(struct scmi_device *scmi_dev); 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); diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h index 08fcc1dd0276..464086b9d8c5 100644 --- a/include/linux/scmi_protocol.h +++ b/include/linux/scmi_protocol.h @@ -4,6 +4,7 @@ * * Copyright (C) 2018 ARM Ltd. */ +#include #include #define SCMI_MAX_STR_SIZE 16 @@ -51,3 +52,66 @@ enum scmi_std_protocol { SCMI_PROTOCOL_CLOCK = 0x14, SCMI_PROTOCOL_SENSOR = 0x15, }; + +struct scmi_device { + u32 id; + u8 protocol_id; + struct device dev; + struct scmi_handle *handle; +}; + +#define to_scmi_dev(d) container_of(d, struct scmi_device, dev) + +struct scmi_device * +scmi_device_create(struct device_node *np, struct device *parent, int protocol); +void scmi_device_destroy(struct scmi_device *scmi_dev); + +struct scmi_device_id { + u8 protocol_id; +}; + +struct scmi_driver { + const char *name; + int (*probe)(struct scmi_device *sdev); + void (*remove)(struct scmi_device *sdev); + const struct scmi_device_id *id_table; + + struct device_driver driver; +}; + +#define to_scmi_driver(d) container_of(d, struct scmi_driver, driver) + +#ifdef CONFIG_ARM_SCMI_PROTOCOL +int scmi_driver_register(struct scmi_driver *driver, + struct module *owner, const char *mod_name); +void scmi_driver_unregister(struct scmi_driver *driver); +#else +static inline int +scmi_driver_register(struct scmi_driver *driver, struct module *owner, + const char *mod_name) +{ + return -EINVAL; +} + +static inline void scmi_driver_unregister(struct scmi_driver *driver) {} +#endif /* CONFIG_ARM_SCMI_PROTOCOL */ + +#define scmi_register(driver) \ + scmi_driver_register(driver, THIS_MODULE, KBUILD_MODNAME) +#define scmi_unregister(driver) \ + scmi_driver_unregister(driver) + +/** + * module_scmi_driver() - Helper macro for registering a scmi driver + * @__scmi_driver: scmi_driver structure + * + * Helper macro for scmi drivers to set up proper module init / exit + * functions. Replaces module_init() and module_exit() and keeps people from + * printing pointless things to the kernel log when their driver is loaded. + */ +#define module_scmi_driver(__scmi_driver) \ + module_driver(__scmi_driver, scmi_register, scmi_unregister) + +typedef int (*scmi_prot_init_fn_t)(struct scmi_handle *); +int scmi_protocol_register(int protocol_id, scmi_prot_init_fn_t fn); +void scmi_protocol_unregister(int protocol_id); -- cgit v1.2.3 From a9e3fbfaa0ff885aacafe6f33e72448a2993d072 Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Tue, 6 Jun 2017 11:22:51 +0100 Subject: firmware: arm_scmi: add initial support for performance protocol 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 Cc: Greg Kroah-Hartman Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scmi/Makefile | 2 +- drivers/firmware/arm_scmi/common.h | 1 + drivers/firmware/arm_scmi/perf.c | 478 +++++++++++++++++++++++++++++++++++++ include/linux/scmi_protocol.h | 42 ++++ 4 files changed, 522 insertions(+), 1 deletion(-) create mode 100644 drivers/firmware/arm_scmi/perf.c (limited to 'include') diff --git a/drivers/firmware/arm_scmi/Makefile b/drivers/firmware/arm_scmi/Makefile index 5f4ec2613db6..687cbbfb3af6 100644 --- a/drivers/firmware/arm_scmi/Makefile +++ b/drivers/firmware/arm_scmi/Makefile @@ -1,4 +1,4 @@ obj-y = scmi-bus.o scmi-driver.o scmi-protocols.o scmi-bus-y = bus.o scmi-driver-y = driver.o -scmi-protocols-y = base.o +scmi-protocols-y = base.o perf.o diff --git a/drivers/firmware/arm_scmi/common.h b/drivers/firmware/arm_scmi/common.h index 95053ed5ccb9..0c30234f9098 100644 --- a/drivers/firmware/arm_scmi/common.h +++ b/drivers/firmware/arm_scmi/common.h @@ -19,6 +19,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_common_cmd { PROTOCOL_VERSION = 0x0, diff --git a/drivers/firmware/arm_scmi/perf.c b/drivers/firmware/arm_scmi/perf.c new file mode 100644 index 000000000000..9c56ea503890 --- /dev/null +++ b/drivers/firmware/arm_scmi/perf.c @@ -0,0 +1,478 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * System Control and Management Interface (SCMI) Performance Protocol + * + * Copyright (C) 2018 ARM Ltd. + */ + +#include +#include +#include +#include + +#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 int scmi_perf_attributes_get(const struct scmi_handle *handle, + struct scmi_perf_info *pi) +{ + 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); + + pi->num_domains = le16_to_cpu(attr->num_domains); + pi->power_scale_mw = POWER_SCALE_IN_MILLIWATT(flags); + pi->stats_addr = le32_to_cpu(attr->stats_addr_low) | + (u64)le32_to_cpu(attr->stats_addr_high) << 32; + pi->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; +} + +/* 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(const struct scmi_handle *handle, + struct device *dev) +{ + int idx, ret, domain; + unsigned long freq; + struct scmi_opp *opp; + struct perf_dom_info *dom; + struct scmi_perf_info *pi = handle->perf_priv; + + domain = scmi_dev_domain_id(dev); + if (domain < 0) + return domain; + + dom = pi->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, 0); + if (ret) { + dev_warn(dev, "failed to add opp %luHz\n", freq); + + 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(const struct scmi_handle *handle, + struct device *dev) +{ + struct perf_dom_info *dom; + struct scmi_perf_info *pi = handle->perf_priv; + int domain = scmi_dev_domain_id(dev); + + if (domain < 0) + return domain; + + dom = pi->dom_info + domain; + if (!dom) + return -EIO; + + /* uS to nS */ + return dom->opp[dom->opp_count - 1].trans_latency_us * 1000; +} + +static int scmi_dvfs_freq_set(const struct scmi_handle *handle, u32 domain, + unsigned long freq) +{ + struct scmi_perf_info *pi = handle->perf_priv; + struct perf_dom_info *dom = pi->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 scmi_perf_info *pi = handle->perf_priv; + struct perf_dom_info *dom = pi->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, + .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, +}; + +static int scmi_perf_protocol_init(struct scmi_handle *handle) +{ + int domain; + u32 version; + struct scmi_perf_info *pinfo; + + 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)); + + pinfo = devm_kzalloc(handle->dev, sizeof(*pinfo), GFP_KERNEL); + if (!pinfo) + return -ENOMEM; + + scmi_perf_attributes_get(handle, pinfo); + + pinfo->dom_info = devm_kcalloc(handle->dev, pinfo->num_domains, + sizeof(*pinfo->dom_info), GFP_KERNEL); + if (!pinfo->dom_info) + return -ENOMEM; + + for (domain = 0; domain < pinfo->num_domains; domain++) { + struct perf_dom_info *dom = pinfo->dom_info + domain; + + scmi_perf_domain_attributes_get(handle, domain, dom); + scmi_perf_describe_levels_get(handle, domain, dom); + } + + handle->perf_ops = &perf_ops; + handle->perf_priv = pinfo; + + return 0; +} + +static int __init scmi_perf_init(void) +{ + return scmi_protocol_register(SCMI_PROTOCOL_PERF, + &scmi_perf_protocol_init); +} +subsys_initcall(scmi_perf_init); diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h index 464086b9d8c5..57d4b1c099e5 100644 --- a/include/linux/scmi_protocol.h +++ b/include/linux/scmi_protocol.h @@ -33,15 +33,57 @@ 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 + * @device_domain_id: gets the scmi domain id for a given device + * @get_transition_latency: gets the DVFS transition latency for a given device + * @add_opps_to_device: adds all the OPPs for a given device + * @freq_set: sets the frequency for a given device using sustained frequency + * to sustained performance level mapping + * @freq_get: gets the frequency for a given device using sustained frequency + * to sustained performance level mapping + */ +struct scmi_perf_ops { + int (*limits_set)(const struct scmi_handle *handle, u32 domain, + u32 max_perf, u32 min_perf); + int (*limits_get)(const struct scmi_handle *handle, u32 domain, + u32 *max_perf, u32 *min_perf); + int (*level_set)(const struct scmi_handle *handle, u32 domain, + u32 level); + int (*level_get)(const struct scmi_handle *handle, u32 domain, + u32 *level); + int (*device_domain_id)(struct device *dev); + int (*get_transition_latency)(const struct scmi_handle *handle, + struct device *dev); + int (*add_opps_to_device)(const struct scmi_handle *handle, + struct device *dev); + int (*freq_set)(const struct scmi_handle *handle, u32 domain, + unsigned long rate); + int (*freq_get)(const struct scmi_handle *handle, u32 domain, + unsigned long *rate); +}; + /** * 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; + /* for protocol internal use */ + void *perf_priv; }; enum scmi_std_protocol { -- cgit v1.2.3 From 5f6c6430e904d21bfe5d0076b1ff3e8b9ed94ba0 Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Tue, 6 Jun 2017 11:27:57 +0100 Subject: firmware: arm_scmi: add initial support for clock protocol 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 Cc: Greg Kroah-Hartman Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scmi/Makefile | 2 +- drivers/firmware/arm_scmi/clock.c | 342 +++++++++++++++++++++++++++++++++++++ include/linux/scmi_protocol.h | 44 +++++ 3 files changed, 387 insertions(+), 1 deletion(-) create mode 100644 drivers/firmware/arm_scmi/clock.c (limited to 'include') diff --git a/drivers/firmware/arm_scmi/Makefile b/drivers/firmware/arm_scmi/Makefile index 687cbbfb3af6..2130ee9ac825 100644 --- a/drivers/firmware/arm_scmi/Makefile +++ b/drivers/firmware/arm_scmi/Makefile @@ -1,4 +1,4 @@ obj-y = scmi-bus.o scmi-driver.o scmi-protocols.o scmi-bus-y = bus.o scmi-driver-y = driver.o -scmi-protocols-y = base.o perf.o +scmi-protocols-y = base.o clock.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..e8ffad33a0ff --- /dev/null +++ b/drivers/firmware/arm_scmi/clock.c @@ -0,0 +1,342 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * System Control and Management Interface (SCMI) Clock Protocol + * + * Copyright (C) 2018 ARM Ltd. + */ + +#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 int scmi_clock_protocol_attributes_get(const struct scmi_handle *handle, + struct clock_info *ci) +{ + 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) { + ci->num_clocks = le16_to_cpu(attr->num_clocks); + ci->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) +{ + struct clock_info *ci = handle->clk_priv; + + return ci->num_clocks; +} + +static const struct scmi_clock_info * +scmi_clock_info_get(const struct scmi_handle *handle, u32 clk_id) +{ + struct clock_info *ci = handle->clk_priv; + struct scmi_clock_info *clk = ci->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, +}; + +static int scmi_clock_protocol_init(struct scmi_handle *handle) +{ + u32 version; + int clkid, ret; + struct clock_info *cinfo; + + 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)); + + cinfo = devm_kzalloc(handle->dev, sizeof(*cinfo), GFP_KERNEL); + if (!cinfo) + return -ENOMEM; + + scmi_clock_protocol_attributes_get(handle, cinfo); + + cinfo->clk = devm_kcalloc(handle->dev, cinfo->num_clocks, + sizeof(*cinfo->clk), GFP_KERNEL); + if (!cinfo->clk) + return -ENOMEM; + + for (clkid = 0; clkid < cinfo->num_clocks; clkid++) { + struct scmi_clock_info *clk = cinfo->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; + handle->clk_priv = cinfo; + + return 0; +} + +static int __init scmi_clock_init(void) +{ + return scmi_protocol_register(SCMI_PROTOCOL_CLOCK, + &scmi_clock_protocol_init); +} +subsys_initcall(scmi_clock_init); diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h index 57d4b1c099e5..5a3092f05011 100644 --- a/include/linux/scmi_protocol.h +++ b/include/linux/scmi_protocol.h @@ -8,6 +8,7 @@ #include #define SCMI_MAX_STR_SIZE 16 +#define SCMI_MAX_NUM_RATES 16 /** * struct scmi_revision_info - version information structure @@ -33,8 +34,48 @@ 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 *handle); + + const struct scmi_clock_info *(*info_get) + (const struct scmi_handle *handle, u32 clk_id); + int (*rate_get)(const struct scmi_handle *handle, u32 clk_id, + u64 *rate); + int (*rate_set)(const struct scmi_handle *handle, u32 clk_id, + u32 config, u64 rate); + int (*enable)(const struct scmi_handle *handle, u32 clk_id); + int (*disable)(const struct scmi_handle *handle, u32 clk_id); +}; + /** * struct scmi_perf_ops - represents the various operations provided * by SCMI Performance Protocol @@ -77,13 +118,16 @@ 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; /* for protocol internal use */ void *perf_priv; + void *clk_priv; }; enum scmi_std_protocol { -- cgit v1.2.3 From 76a6550990e296a7acbb4d33201c9740be912a8c Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Tue, 6 Jun 2017 11:32:24 +0100 Subject: firmware: arm_scmi: add initial support for power protocol 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 Cc: Greg Kroah-Hartman Signed-off-by: Sudeep Holla -- 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 | 221 +++++++++++++++++++++++++++++++++++++ include/linux/scmi_protocol.h | 28 +++++ 3 files changed, 250 insertions(+), 1 deletion(-) create mode 100644 drivers/firmware/arm_scmi/power.c (limited to 'include') diff --git a/drivers/firmware/arm_scmi/Makefile b/drivers/firmware/arm_scmi/Makefile index 2130ee9ac825..420c761ced94 100644 --- a/drivers/firmware/arm_scmi/Makefile +++ b/drivers/firmware/arm_scmi/Makefile @@ -1,4 +1,4 @@ obj-y = scmi-bus.o scmi-driver.o scmi-protocols.o scmi-bus-y = bus.o scmi-driver-y = driver.o -scmi-protocols-y = base.o clock.o perf.o +scmi-protocols-y = base.o clock.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..087c2876cdf2 --- /dev/null +++ b/drivers/firmware/arm_scmi/power.c @@ -0,0 +1,221 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * System Control and Management Interface (SCMI) Power Protocol + * + * Copyright (C) 2018 ARM Ltd. + */ + +#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 int scmi_power_attributes_get(const struct scmi_handle *handle, + struct scmi_power_info *pi) +{ + 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) { + pi->num_domains = le16_to_cpu(attr->num_domains); + pi->stats_addr = le32_to_cpu(attr->stats_addr_low) | + (u64)le32_to_cpu(attr->stats_addr_high) << 32; + pi->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_num_domains_get(const struct scmi_handle *handle) +{ + struct scmi_power_info *pi = handle->power_priv; + + return pi->num_domains; +} + +static char *scmi_power_name_get(const struct scmi_handle *handle, u32 domain) +{ + struct scmi_power_info *pi = handle->power_priv; + struct power_dom_info *dom = pi->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, +}; + +static int scmi_power_protocol_init(struct scmi_handle *handle) +{ + int domain; + u32 version; + struct scmi_power_info *pinfo; + + 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)); + + pinfo = devm_kzalloc(handle->dev, sizeof(*pinfo), GFP_KERNEL); + if (!pinfo) + return -ENOMEM; + + scmi_power_attributes_get(handle, pinfo); + + pinfo->dom_info = devm_kcalloc(handle->dev, pinfo->num_domains, + sizeof(*pinfo->dom_info), GFP_KERNEL); + if (!pinfo->dom_info) + return -ENOMEM; + + for (domain = 0; domain < pinfo->num_domains; domain++) { + struct power_dom_info *dom = pinfo->dom_info + domain; + + scmi_power_domain_attributes_get(handle, domain, dom); + } + + handle->power_ops = &power_ops; + handle->power_priv = pinfo; + + return 0; +} + +static int __init scmi_power_init(void) +{ + return scmi_protocol_register(SCMI_PROTOCOL_POWER, + &scmi_power_protocol_init); +} +subsys_initcall(scmi_power_init); diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h index 5a3092f05011..8cd0348787bc 100644 --- a/include/linux/scmi_protocol.h +++ b/include/linux/scmi_protocol.h @@ -112,11 +112,37 @@ struct scmi_perf_ops { unsigned long *rate); }; +/** + * 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 + */ +struct scmi_power_ops { + int (*num_domains_get)(const struct scmi_handle *handle); + char *(*name_get)(const struct scmi_handle *handle, u32 domain); +#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 *handle, u32 domain, + u32 state); + int (*state_get)(const struct scmi_handle *handle, u32 domain, + u32 *state); +}; + /** * 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 */ @@ -125,9 +151,11 @@ struct scmi_handle { struct scmi_revision_info *version; struct scmi_perf_ops *perf_ops; struct scmi_clk_ops *clk_ops; + struct scmi_power_ops *power_ops; /* for protocol internal use */ void *perf_priv; void *clk_priv; + void *power_priv; }; enum scmi_std_protocol { -- cgit v1.2.3 From 5179c523c1eae4b80fbafe9656bc24a375217cac Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Tue, 6 Jun 2017 11:38:10 +0100 Subject: firmware: arm_scmi: add initial support for sensor protocol 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 Cc: Greg Kroah-Hartman Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scmi/Makefile | 2 +- drivers/firmware/arm_scmi/sensors.c | 291 ++++++++++++++++++++++++++++++++++++ include/linux/scmi_protocol.h | 46 ++++++ 3 files changed, 338 insertions(+), 1 deletion(-) create mode 100644 drivers/firmware/arm_scmi/sensors.c (limited to 'include') diff --git a/drivers/firmware/arm_scmi/Makefile b/drivers/firmware/arm_scmi/Makefile index 420c761ced94..3236890905b9 100644 --- a/drivers/firmware/arm_scmi/Makefile +++ b/drivers/firmware/arm_scmi/Makefile @@ -1,4 +1,4 @@ obj-y = scmi-bus.o scmi-driver.o scmi-protocols.o scmi-bus-y = bus.o scmi-driver-y = driver.o -scmi-protocols-y = base.o clock.o perf.o power.o +scmi-protocols-y = base.o clock.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..bbb469fea0ed --- /dev/null +++ b/drivers/firmware/arm_scmi/sensors.c @@ -0,0 +1,291 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * System Control and Management Interface (SCMI) Sensor Protocol + * + * Copyright (C) 2018 ARM Ltd. + */ + +#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 int scmi_sensor_attributes_get(const struct scmi_handle *handle, + struct sensors_info *si) +{ + 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) { + si->num_sensors = le16_to_cpu(attr->num_sensors); + si->max_requests = attr->max_requests; + si->reg_addr = le32_to_cpu(attr->reg_addr_low) | + (u64)le32_to_cpu(attr->reg_addr_high) << 32; + si->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, + struct sensors_info *si) +{ + 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 > si->num_sensors) { + dev_err(handle->dev, "No. of sensors can't exceed %d", + si->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 = &si->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) +{ + struct sensors_info *si = handle->sensor_priv; + + return si->sensors + sensor_id; +} + +static int scmi_sensor_count_get(const struct scmi_handle *handle) +{ + struct sensors_info *si = handle->sensor_priv; + + return si->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, +}; + +static int scmi_sensors_protocol_init(struct scmi_handle *handle) +{ + u32 version; + struct sensors_info *sinfo; + + 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)); + + sinfo = devm_kzalloc(handle->dev, sizeof(*sinfo), GFP_KERNEL); + if (!sinfo) + return -ENOMEM; + + scmi_sensor_attributes_get(handle, sinfo); + + sinfo->sensors = devm_kcalloc(handle->dev, sinfo->num_sensors, + sizeof(*sinfo->sensors), GFP_KERNEL); + if (!sinfo->sensors) + return -ENOMEM; + + scmi_sensor_description_get(handle, sinfo); + + handle->sensor_ops = &sensor_ops; + handle->sensor_priv = sinfo; + + return 0; +} + +static int __init scmi_sensors_init(void) +{ + return scmi_protocol_register(SCMI_PROTOCOL_SENSOR, + &scmi_sensors_protocol_init); +} +subsys_initcall(scmi_sensors_init); diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h index 8cd0348787bc..5d63da9435ba 100644 --- a/include/linux/scmi_protocol.h +++ b/include/linux/scmi_protocol.h @@ -137,6 +137,49 @@ struct scmi_power_ops { u32 *state); }; +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 { + NONE = 0x0, + 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 *handle); + + const struct scmi_sensor_info *(*info_get) + (const struct scmi_handle *handle, u32 sensor_id); + int (*configuration_set)(const struct scmi_handle *handle, + u32 sensor_id); + int (*trip_point_set)(const struct scmi_handle *handle, u32 sensor_id, + u8 trip_id, u64 trip_value); + int (*reading_get)(const struct scmi_handle *handle, u32 sensor_id, + bool async, u64 *value); +}; + /** * struct scmi_handle - Handle returned to ARM SCMI clients for usage. * @@ -145,6 +188,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; @@ -152,10 +196,12 @@ struct scmi_handle { struct scmi_perf_ops *perf_ops; struct scmi_clk_ops *clk_ops; struct scmi_power_ops *power_ops; + struct scmi_sensor_ops *sensor_ops; /* for protocol internal use */ void *perf_priv; void *clk_priv; void *power_priv; + void *sensor_priv; }; enum scmi_std_protocol { -- cgit v1.2.3 From 5c4ba3cc85296398855d621bf90b78866ea80444 Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Fri, 21 Jul 2017 11:42:24 +0100 Subject: firmware: arm_scmi: add option for polling based performance domain operations 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 Cc: Greg Kroah-Hartman Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scmi/perf.c | 19 +++++++++++-------- include/linux/scmi_protocol.h | 8 ++++---- 2 files changed, 15 insertions(+), 12 deletions(-) (limited to 'include') diff --git a/drivers/firmware/arm_scmi/perf.c b/drivers/firmware/arm_scmi/perf.c index 9c56ea503890..987c64d19801 100644 --- a/drivers/firmware/arm_scmi/perf.c +++ b/drivers/firmware/arm_scmi/perf.c @@ -292,8 +292,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; @@ -304,6 +304,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); @@ -314,8 +315,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; @@ -325,6 +326,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); @@ -400,23 +402,24 @@ static int scmi_dvfs_get_transition_latency(const struct scmi_handle *handle, } static int scmi_dvfs_freq_set(const struct scmi_handle *handle, u32 domain, - unsigned long freq) + unsigned long freq, bool poll) { struct scmi_perf_info *pi = handle->perf_priv; struct perf_dom_info *dom = pi->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 scmi_perf_info *pi = handle->perf_priv; struct perf_dom_info *dom = pi->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 5d63da9435ba..b458c87b866c 100644 --- a/include/linux/scmi_protocol.h +++ b/include/linux/scmi_protocol.h @@ -98,18 +98,18 @@ struct scmi_perf_ops { int (*limits_get)(const struct scmi_handle *handle, u32 domain, u32 *max_perf, u32 *min_perf); int (*level_set)(const struct scmi_handle *handle, u32 domain, - u32 level); + u32 level, bool poll); int (*level_get)(const struct scmi_handle *handle, u32 domain, - u32 *level); + u32 *level, bool poll); int (*device_domain_id)(struct device *dev); int (*get_transition_latency)(const struct scmi_handle *handle, struct device *dev); int (*add_opps_to_device)(const struct scmi_handle *handle, struct device *dev); int (*freq_set)(const struct scmi_handle *handle, u32 domain, - unsigned long rate); + unsigned long rate, bool poll); int (*freq_get)(const struct scmi_handle *handle, u32 domain, - unsigned long *rate); + unsigned long *rate, bool poll); }; /** -- cgit v1.2.3 From d57538004b2e57be6a5d8583b65d1b049245abf7 Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Wed, 27 Sep 2017 16:20:50 +0100 Subject: hwmon: (core) Add hwmon_max to hwmon_sensor_types enumeration It's useful to know the maximum types of sensor supported by hwmon framework. It can be used to allocate some data structures when sorting the monitors based on their type. This will be used by scmi hwmon support. Cc: linux-hwmon@vger.kernel.org Acked-by: Guenter Roeck Signed-off-by: Sudeep Holla --- include/linux/hwmon.h | 1 + 1 file changed, 1 insertion(+) (limited to 'include') diff --git a/include/linux/hwmon.h b/include/linux/hwmon.h index ceb751987c40..e5fd2707b6df 100644 --- a/include/linux/hwmon.h +++ b/include/linux/hwmon.h @@ -29,6 +29,7 @@ enum hwmon_sensor_types { hwmon_humidity, hwmon_fan, hwmon_pwm, + hwmon_max, }; enum hwmon_chip_attributes { -- cgit v1.2.3 From 1abb081e41a718d73183b0e1b76bfff66e92f7e1 Mon Sep 17 00:00:00 2001 From: Mikko Perttunen Date: Tue, 20 Feb 2018 13:58:06 +0200 Subject: firmware: tegra: Simplify channel management The Tegra194 BPMP only implements 5 channels (4 to BPMP, 1 to CCPLEX), and they are not placed contiguously in memory. The current channel management in the BPMP driver does not support this. Simplify and refactor the channel management such that only one atomic transmit channel and one receive channel are supported, and channels are not required to be placed contiguously in memory. The same configuration also works on T186 so we end up with less code. Signed-off-by: Mikko Perttunen Signed-off-by: Thierry Reding --- drivers/firmware/tegra/bpmp.c | 142 +++++++++++++++++++----------------------- include/soc/tegra/bpmp.h | 4 +- 2 files changed, 66 insertions(+), 80 deletions(-) (limited to 'include') diff --git a/drivers/firmware/tegra/bpmp.c b/drivers/firmware/tegra/bpmp.c index a7f461f2e650..81bc2dce8626 100644 --- a/drivers/firmware/tegra/bpmp.c +++ b/drivers/firmware/tegra/bpmp.c @@ -70,57 +70,20 @@ void tegra_bpmp_put(struct tegra_bpmp *bpmp) } EXPORT_SYMBOL_GPL(tegra_bpmp_put); -static int tegra_bpmp_channel_get_index(struct tegra_bpmp_channel *channel) -{ - return channel - channel->bpmp->channels; -} - static int tegra_bpmp_channel_get_thread_index(struct tegra_bpmp_channel *channel) { struct tegra_bpmp *bpmp = channel->bpmp; - unsigned int offset, count; + unsigned int count; int index; - offset = bpmp->soc->channels.thread.offset; count = bpmp->soc->channels.thread.count; - index = tegra_bpmp_channel_get_index(channel); - if (index < 0) - return index; - - if (index < offset || index >= offset + count) + index = channel - channel->bpmp->threaded_channels; + if (index < 0 || index >= count) return -EINVAL; - return index - offset; -} - -static struct tegra_bpmp_channel * -tegra_bpmp_channel_get_thread(struct tegra_bpmp *bpmp, unsigned int index) -{ - unsigned int offset = bpmp->soc->channels.thread.offset; - unsigned int count = bpmp->soc->channels.thread.count; - - if (index >= count) - return NULL; - - return &bpmp->channels[offset + index]; -} - -static struct tegra_bpmp_channel * -tegra_bpmp_channel_get_tx(struct tegra_bpmp *bpmp) -{ - unsigned int offset = bpmp->soc->channels.cpu_tx.offset; - - return &bpmp->channels[offset + smp_processor_id()]; -} - -static struct tegra_bpmp_channel * -tegra_bpmp_channel_get_rx(struct tegra_bpmp *bpmp) -{ - unsigned int offset = bpmp->soc->channels.cpu_rx.offset; - - return &bpmp->channels[offset]; + return index; } static bool tegra_bpmp_message_valid(const struct tegra_bpmp_message *msg) @@ -271,11 +234,7 @@ tegra_bpmp_write_threaded(struct tegra_bpmp *bpmp, unsigned int mrq, goto unlock; } - channel = tegra_bpmp_channel_get_thread(bpmp, index); - if (!channel) { - err = -EINVAL; - goto unlock; - } + channel = &bpmp->threaded_channels[index]; if (!tegra_bpmp_master_free(channel)) { err = -EBUSY; @@ -328,12 +287,18 @@ int tegra_bpmp_transfer_atomic(struct tegra_bpmp *bpmp, if (!tegra_bpmp_message_valid(msg)) return -EINVAL; - channel = tegra_bpmp_channel_get_tx(bpmp); + channel = bpmp->tx_channel; + + spin_lock(&bpmp->atomic_tx_lock); err = tegra_bpmp_channel_write(channel, msg->mrq, MSG_ACK, msg->tx.data, msg->tx.size); - if (err < 0) + if (err < 0) { + spin_unlock(&bpmp->atomic_tx_lock); return err; + } + + spin_unlock(&bpmp->atomic_tx_lock); err = mbox_send_message(bpmp->mbox.channel, NULL); if (err < 0) @@ -607,7 +572,7 @@ static void tegra_bpmp_handle_rx(struct mbox_client *client, void *data) unsigned int i, count; unsigned long *busy; - channel = tegra_bpmp_channel_get_rx(bpmp); + channel = bpmp->rx_channel; count = bpmp->soc->channels.thread.count; busy = bpmp->threaded.busy; @@ -619,9 +584,7 @@ static void tegra_bpmp_handle_rx(struct mbox_client *client, void *data) for_each_set_bit(i, busy, count) { struct tegra_bpmp_channel *channel; - channel = tegra_bpmp_channel_get_thread(bpmp, i); - if (!channel) - continue; + channel = &bpmp->threaded_channels[i]; if (tegra_bpmp_master_acked(channel)) { tegra_bpmp_channel_signal(channel); @@ -698,7 +661,6 @@ static void tegra_bpmp_channel_cleanup(struct tegra_bpmp_channel *channel) static int tegra_bpmp_probe(struct platform_device *pdev) { - struct tegra_bpmp_channel *channel; struct tegra_bpmp *bpmp; unsigned int i; char tag[32]; @@ -758,24 +720,45 @@ static int tegra_bpmp_probe(struct platform_device *pdev) goto free_rx; } - bpmp->num_channels = bpmp->soc->channels.cpu_tx.count + - bpmp->soc->channels.thread.count + - bpmp->soc->channels.cpu_rx.count; + spin_lock_init(&bpmp->atomic_tx_lock); + bpmp->tx_channel = devm_kzalloc(&pdev->dev, sizeof(*bpmp->tx_channel), + GFP_KERNEL); + if (!bpmp->tx_channel) { + err = -ENOMEM; + goto free_rx; + } - bpmp->channels = devm_kcalloc(&pdev->dev, bpmp->num_channels, - sizeof(*channel), GFP_KERNEL); - if (!bpmp->channels) { + bpmp->rx_channel = devm_kzalloc(&pdev->dev, sizeof(*bpmp->rx_channel), + GFP_KERNEL); + if (!bpmp->rx_channel) { err = -ENOMEM; goto free_rx; } - /* message channel initialization */ - for (i = 0; i < bpmp->num_channels; i++) { - struct tegra_bpmp_channel *channel = &bpmp->channels[i]; + bpmp->threaded_channels = devm_kcalloc(&pdev->dev, bpmp->threaded.count, + sizeof(*bpmp->threaded_channels), + GFP_KERNEL); + if (!bpmp->threaded_channels) { + err = -ENOMEM; + goto free_rx; + } - err = tegra_bpmp_channel_init(channel, bpmp, i); + err = tegra_bpmp_channel_init(bpmp->tx_channel, bpmp, + bpmp->soc->channels.cpu_tx.offset); + if (err < 0) + goto free_rx; + + err = tegra_bpmp_channel_init(bpmp->rx_channel, bpmp, + bpmp->soc->channels.cpu_rx.offset); + if (err < 0) + goto cleanup_tx_channel; + + for (i = 0; i < bpmp->threaded.count; i++) { + err = tegra_bpmp_channel_init( + &bpmp->threaded_channels[i], bpmp, + bpmp->soc->channels.thread.offset + i); if (err < 0) - goto cleanup_channels; + goto cleanup_threaded_channels; } /* mbox registration */ @@ -788,15 +771,14 @@ static int tegra_bpmp_probe(struct platform_device *pdev) if (IS_ERR(bpmp->mbox.channel)) { err = PTR_ERR(bpmp->mbox.channel); dev_err(&pdev->dev, "failed to get HSP mailbox: %d\n", err); - goto cleanup_channels; + goto cleanup_threaded_channels; } /* reset message channels */ - for (i = 0; i < bpmp->num_channels; i++) { - struct tegra_bpmp_channel *channel = &bpmp->channels[i]; - - tegra_bpmp_channel_reset(channel); - } + tegra_bpmp_channel_reset(bpmp->tx_channel); + tegra_bpmp_channel_reset(bpmp->rx_channel); + for (i = 0; i < bpmp->threaded.count; i++) + tegra_bpmp_channel_reset(&bpmp->threaded_channels[i]); err = tegra_bpmp_request_mrq(bpmp, MRQ_PING, tegra_bpmp_mrq_handle_ping, bpmp); @@ -845,9 +827,15 @@ free_mrq: tegra_bpmp_free_mrq(bpmp, MRQ_PING, bpmp); free_mbox: mbox_free_channel(bpmp->mbox.channel); -cleanup_channels: - while (i--) - tegra_bpmp_channel_cleanup(&bpmp->channels[i]); +cleanup_threaded_channels: + for (i = 0; i < bpmp->threaded.count; i++) { + if (bpmp->threaded_channels[i].bpmp) + tegra_bpmp_channel_cleanup(&bpmp->threaded_channels[i]); + } + + tegra_bpmp_channel_cleanup(bpmp->rx_channel); +cleanup_tx_channel: + tegra_bpmp_channel_cleanup(bpmp->tx_channel); free_rx: gen_pool_free(bpmp->rx.pool, (unsigned long)bpmp->rx.virt, 4096); free_tx: @@ -858,18 +846,16 @@ free_tx: static const struct tegra_bpmp_soc tegra186_soc = { .channels = { .cpu_tx = { - .offset = 0, - .count = 6, + .offset = 3, .timeout = 60 * USEC_PER_SEC, }, .thread = { - .offset = 6, - .count = 7, + .offset = 0, + .count = 3, .timeout = 600 * USEC_PER_SEC, }, .cpu_rx = { .offset = 13, - .count = 1, .timeout = 0, }, }, diff --git a/include/soc/tegra/bpmp.h b/include/soc/tegra/bpmp.h index aeae4466dd25..e69e4c4d80ae 100644 --- a/include/soc/tegra/bpmp.h +++ b/include/soc/tegra/bpmp.h @@ -75,8 +75,8 @@ struct tegra_bpmp { struct mbox_chan *channel; } mbox; - struct tegra_bpmp_channel *channels; - unsigned int num_channels; + spinlock_t atomic_tx_lock; + struct tegra_bpmp_channel *tx_channel, *rx_channel, *threaded_channels; struct { unsigned long *allocated; -- cgit v1.2.3 From 0c1a2c17f60fc120cd6a6033ffdb5c336078a0a3 Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Wed, 7 Feb 2018 18:22:48 +0800 Subject: dt-bindings: soc: add header files required for MT7623A SCPSYS dt-binding Add relevant header files required for dt-bindings of SCPSYS power domain control for subsystems found on MT7623A SoC. Signed-off-by: Sean Wang Cc: Rob Herring Reviewed-by: Rob Herring Reviewed-by: Ulf Hansson [mb: clean-up commit message] Signed-off-by: Matthias Brugger --- include/dt-bindings/power/mt7623a-power.h | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 include/dt-bindings/power/mt7623a-power.h (limited to 'include') diff --git a/include/dt-bindings/power/mt7623a-power.h b/include/dt-bindings/power/mt7623a-power.h new file mode 100644 index 000000000000..2544822aa76b --- /dev/null +++ b/include/dt-bindings/power/mt7623a-power.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _DT_BINDINGS_POWER_MT7623A_POWER_H +#define _DT_BINDINGS_POWER_MT7623A_POWER_H + +#define MT7623A_POWER_DOMAIN_CONN 0 +#define MT7623A_POWER_DOMAIN_ETH 1 +#define MT7623A_POWER_DOMAIN_HIF 2 +#define MT7623A_POWER_DOMAIN_IFR_MSC 3 + +#endif /* _DT_BINDINGS_POWER_MT7623A_POWER_H */ -- cgit v1.2.3 From c59c9c85e36aa09cfd901cc15a0d8d3772c18195 Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Wed, 7 Feb 2018 18:22:49 +0800 Subject: soc: mediatek: avoid hardcoded value with bus_prot_mask use a meaningful definition for bus_prot_mask instead of just hardcoded for it. Signed-off-by: Sean Wang Reviewed-by: Ulf Hansson Signed-off-by: Matthias Brugger --- drivers/soc/mediatek/mtk-scpsys.c | 5 +++-- include/linux/soc/mediatek/infracfg.h | 4 ++++ 2 files changed, 7 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/drivers/soc/mediatek/mtk-scpsys.c b/drivers/soc/mediatek/mtk-scpsys.c index 435ce5ec648a..5346f33dd70b 100644 --- a/drivers/soc/mediatek/mtk-scpsys.c +++ b/drivers/soc/mediatek/mtk-scpsys.c @@ -518,7 +518,8 @@ static const struct scp_domain_data scp_domain_data_mt2701[] = { .name = "conn", .sta_mask = PWR_STATUS_CONN, .ctl_offs = SPM_CONN_PWR_CON, - .bus_prot_mask = 0x0104, + .bus_prot_mask = MT2701_TOP_AXI_PROT_EN_CONN_M | + MT2701_TOP_AXI_PROT_EN_CONN_S, .clk_id = {CLK_NONE}, .active_wakeup = true, }, @@ -528,7 +529,7 @@ static const struct scp_domain_data scp_domain_data_mt2701[] = { .ctl_offs = SPM_DIS_PWR_CON, .sram_pdn_bits = GENMASK(11, 8), .clk_id = {CLK_MM}, - .bus_prot_mask = 0x0002, + .bus_prot_mask = MT2701_TOP_AXI_PROT_EN_MM_M0, .active_wakeup = true, }, [MT2701_POWER_DOMAIN_MFG] = { diff --git a/include/linux/soc/mediatek/infracfg.h b/include/linux/soc/mediatek/infracfg.h index b0a507d356ef..fd25f0148566 100644 --- a/include/linux/soc/mediatek/infracfg.h +++ b/include/linux/soc/mediatek/infracfg.h @@ -21,6 +21,10 @@ #define MT8173_TOP_AXI_PROT_EN_MFG_M1 BIT(22) #define MT8173_TOP_AXI_PROT_EN_MFG_SNOOP_OUT BIT(23) +#define MT2701_TOP_AXI_PROT_EN_MM_M0 BIT(1) +#define MT2701_TOP_AXI_PROT_EN_CONN_M BIT(2) +#define MT2701_TOP_AXI_PROT_EN_CONN_S BIT(8) + #define MT7622_TOP_AXI_PROT_EN_ETHSYS (BIT(3) | BIT(17)) #define MT7622_TOP_AXI_PROT_EN_HIF0 (BIT(24) | BIT(25)) #define MT7622_TOP_AXI_PROT_EN_HIF1 (BIT(26) | BIT(27) | \ -- cgit v1.2.3 From 1390515aed5e5eea8d6c2c5c08ef6d04ba4a4a50 Mon Sep 17 00:00:00 2001 From: "weiyi.lu@mediatek.com" Date: Mon, 12 Mar 2018 15:03:38 +0800 Subject: dt-bindings: soc: update MT2712 power dt-bindings Add new power domains(MFG_SC1/MFG_SC2/MFG_SC3) for MT2712 according to ECO design change. Signed-off-by: Weiyi Lu Reviewed-by: Rob Herring Signed-off-by: Matthias Brugger --- include/dt-bindings/power/mt2712-power.h | 3 +++ 1 file changed, 3 insertions(+) (limited to 'include') diff --git a/include/dt-bindings/power/mt2712-power.h b/include/dt-bindings/power/mt2712-power.h index 92b46d772fae..2c147817efc2 100644 --- a/include/dt-bindings/power/mt2712-power.h +++ b/include/dt-bindings/power/mt2712-power.h @@ -22,5 +22,8 @@ #define MT2712_POWER_DOMAIN_USB 5 #define MT2712_POWER_DOMAIN_USB2 6 #define MT2712_POWER_DOMAIN_MFG 7 +#define MT2712_POWER_DOMAIN_MFG_SC1 8 +#define MT2712_POWER_DOMAIN_MFG_SC2 9 +#define MT2712_POWER_DOMAIN_MFG_SC3 10 #endif /* _DT_BINDINGS_POWER_MT2712_POWER_H */ -- cgit v1.2.3 From 6691dffab0ab6301bb7b489b1dcf9f5efdef202f Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 28 Feb 2018 14:08:57 +0100 Subject: reset: add support for non-DT systems The reset framework only supports device-tree. There are some platforms however, which need to use it even in legacy, board-file based mode. An example of such architecture is the DaVinci family of SoCs which supports both device tree and legacy boot modes and we don't want to introduce any regressions. We're currently working on converting the platform from its hand-crafted clock API to using the common clock framework. Part of the overhaul will be representing the chip's power sleep controller's reset lines using the reset framework. This changeset extends the core reset code with a new reset lookup entry structure. It contains data allowing the reset core to associate reset lines with devices by comparing the dev_id and con_id strings. It also provides a function allowing drivers to register lookup entries with the framework. The new lookup function is only called as a fallback in case the of_node field is NULL and doesn't change anything for current users. Tested with a dummy reset driver with several lookup entries. An example lookup table registration from a driver can be found below: static struct reset_control_lookup foobar_reset_lookup[] = { RESET_LOOKUP("foo.0", "foo", 15), RESET_LOOKUP("bar.0", NULL, 5), }; foobar_probe() { ... reset_controller_add_lookup(&rcdev, foobar_reset_lookup, ARRAY_SIZE(foobar_reset_lookup)); ... } Cc: Sekhar Nori Cc: Kevin Hilman Cc: David Lechner Signed-off-by: Bartosz Golaszewski Signed-off-by: Philipp Zabel --- drivers/reset/core.c | 72 +++++++++++++++++++++++++++++++++++++++- include/linux/reset-controller.h | 28 ++++++++++++++++ 2 files changed, 99 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/drivers/reset/core.c b/drivers/reset/core.c index da4292e9de97..06fa4907afc4 100644 --- a/drivers/reset/core.c +++ b/drivers/reset/core.c @@ -23,6 +23,9 @@ static DEFINE_MUTEX(reset_list_mutex); static LIST_HEAD(reset_controller_list); +static DEFINE_MUTEX(reset_lookup_mutex); +static LIST_HEAD(reset_lookup_list); + /** * struct reset_control - a reset control * @rcdev: a pointer to the reset controller device @@ -148,6 +151,36 @@ int devm_reset_controller_register(struct device *dev, } EXPORT_SYMBOL_GPL(devm_reset_controller_register); +/** + * reset_controller_add_lookup - register a set of lookup entries + * @rcdev: initialized reset controller device owning the reset line + * @lookup: array of reset lookup entries + * @num_entries: number of entries in the lookup array + */ +void reset_controller_add_lookup(struct reset_controller_dev *rcdev, + struct reset_control_lookup *lookup, + unsigned int num_entries) +{ + struct reset_control_lookup *entry; + unsigned int i; + + mutex_lock(&reset_lookup_mutex); + for (i = 0; i < num_entries; i++) { + entry = &lookup[i]; + + if (!entry->dev_id) { + pr_warn("%s(): reset lookup entry has no dev_id, skipping\n", + __func__); + continue; + } + + entry->rcdev = rcdev; + list_add_tail(&entry->list, &reset_lookup_list); + } + mutex_unlock(&reset_lookup_mutex); +} +EXPORT_SYMBOL_GPL(reset_controller_add_lookup); + static inline struct reset_control_array * rstc_to_array(struct reset_control *rstc) { return container_of(rstc, struct reset_control_array, base); @@ -493,6 +526,43 @@ struct reset_control *__of_reset_control_get(struct device_node *node, } EXPORT_SYMBOL_GPL(__of_reset_control_get); +static struct reset_control * +__reset_control_get_from_lookup(struct device *dev, const char *con_id, + bool shared, bool optional) +{ + const struct reset_control_lookup *lookup; + const char *dev_id = dev_name(dev); + struct reset_control *rstc = NULL; + + if (!dev) + return ERR_PTR(-EINVAL); + + mutex_lock(&reset_lookup_mutex); + + list_for_each_entry(lookup, &reset_lookup_list, list) { + if (strcmp(lookup->dev_id, dev_id)) + continue; + + if ((!con_id && !lookup->con_id) || + ((con_id && lookup->con_id) && + !strcmp(con_id, lookup->con_id))) { + mutex_lock(&reset_list_mutex); + rstc = __reset_control_get_internal(lookup->rcdev, + lookup->index, + shared); + mutex_unlock(&reset_list_mutex); + break; + } + } + + mutex_unlock(&reset_lookup_mutex); + + if (!rstc) + return optional ? NULL : ERR_PTR(-ENOENT); + + return rstc; +} + struct reset_control *__reset_control_get(struct device *dev, const char *id, int index, bool shared, bool optional) { @@ -500,7 +570,7 @@ struct reset_control *__reset_control_get(struct device *dev, const char *id, return __of_reset_control_get(dev->of_node, id, index, shared, optional); - return optional ? NULL : ERR_PTR(-EINVAL); + return __reset_control_get_from_lookup(dev, id, shared, optional); } EXPORT_SYMBOL_GPL(__reset_control_get); diff --git a/include/linux/reset-controller.h b/include/linux/reset-controller.h index adb88f8cefbc..25698f6c1fae 100644 --- a/include/linux/reset-controller.h +++ b/include/linux/reset-controller.h @@ -26,6 +26,30 @@ struct module; struct device_node; struct of_phandle_args; +/** + * struct reset_control_lookup - represents a single lookup entry + * + * @list: internal list of all reset lookup entries + * @rcdev: reset controller device controlling this reset line + * @index: ID of the reset controller in the reset controller device + * @dev_id: name of the device associated with this reset line + * @con_id name of the reset line (can be NULL) + */ +struct reset_control_lookup { + struct list_head list; + struct reset_controller_dev *rcdev; + unsigned int index; + const char *dev_id; + const char *con_id; +}; + +#define RESET_LOOKUP(_dev_id, _con_id, _index) \ + { \ + .dev_id = _dev_id, \ + .con_id = _con_id, \ + .index = _index, \ + } + /** * struct reset_controller_dev - reset controller entity that might * provide multiple reset controls @@ -58,4 +82,8 @@ struct device; int devm_reset_controller_register(struct device *dev, struct reset_controller_dev *rcdev); +void reset_controller_add_lookup(struct reset_controller_dev *rcdev, + struct reset_control_lookup *lookup, + unsigned int num_entries); + #endif -- cgit v1.2.3 From e2749bb998701e21cdb8b34486b82fc1c051ab41 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Fri, 23 Mar 2018 14:04:48 +0100 Subject: reset: modify the way reset lookup works for board files Commit 7af1bb19f1d7 ("reset: add support for non-DT systems") introduced reset control lookup mechanism for boards that still use board files. The routine used to register lookup entries takes the corresponding reset_controlled_dev structure as argument. It's been determined however that for the first user of this new interface - davinci psc driver - it will be easier to register the lookup entries using the reset controller device name. This patch changes the way lookup entries are added. Signed-off-by: Bartosz Golaszewski [p.zabel@pengutronix.de: added missing ERR_PTR] Signed-off-by: Philipp Zabel --- drivers/reset/core.c | 38 +++++++++++++++++++++++++++++++------- include/linux/reset-controller.h | 14 ++++++++------ 2 files changed, 39 insertions(+), 13 deletions(-) (limited to 'include') diff --git a/drivers/reset/core.c b/drivers/reset/core.c index 06fa4907afc4..6488292e129c 100644 --- a/drivers/reset/core.c +++ b/drivers/reset/core.c @@ -153,12 +153,10 @@ EXPORT_SYMBOL_GPL(devm_reset_controller_register); /** * reset_controller_add_lookup - register a set of lookup entries - * @rcdev: initialized reset controller device owning the reset line * @lookup: array of reset lookup entries * @num_entries: number of entries in the lookup array */ -void reset_controller_add_lookup(struct reset_controller_dev *rcdev, - struct reset_control_lookup *lookup, +void reset_controller_add_lookup(struct reset_control_lookup *lookup, unsigned int num_entries) { struct reset_control_lookup *entry; @@ -168,13 +166,12 @@ void reset_controller_add_lookup(struct reset_controller_dev *rcdev, for (i = 0; i < num_entries; i++) { entry = &lookup[i]; - if (!entry->dev_id) { - pr_warn("%s(): reset lookup entry has no dev_id, skipping\n", + if (!entry->dev_id || !entry->provider) { + pr_warn("%s(): reset lookup entry badly specified, skipping\n", __func__); continue; } - entry->rcdev = rcdev; list_add_tail(&entry->list, &reset_lookup_list); } mutex_unlock(&reset_lookup_mutex); @@ -526,11 +523,30 @@ struct reset_control *__of_reset_control_get(struct device_node *node, } EXPORT_SYMBOL_GPL(__of_reset_control_get); +static struct reset_controller_dev * +__reset_controller_by_name(const char *name) +{ + struct reset_controller_dev *rcdev; + + lockdep_assert_held(&reset_list_mutex); + + list_for_each_entry(rcdev, &reset_controller_list, list) { + if (!rcdev->dev) + continue; + + if (!strcmp(name, dev_name(rcdev->dev))) + return rcdev; + } + + return NULL; +} + static struct reset_control * __reset_control_get_from_lookup(struct device *dev, const char *con_id, bool shared, bool optional) { const struct reset_control_lookup *lookup; + struct reset_controller_dev *rcdev; const char *dev_id = dev_name(dev); struct reset_control *rstc = NULL; @@ -547,7 +563,15 @@ __reset_control_get_from_lookup(struct device *dev, const char *con_id, ((con_id && lookup->con_id) && !strcmp(con_id, lookup->con_id))) { mutex_lock(&reset_list_mutex); - rstc = __reset_control_get_internal(lookup->rcdev, + rcdev = __reset_controller_by_name(lookup->provider); + if (!rcdev) { + mutex_unlock(&reset_list_mutex); + mutex_unlock(&reset_lookup_mutex); + /* Reset provider may not be ready yet. */ + return ERR_PTR(-EPROBE_DEFER); + } + + rstc = __reset_control_get_internal(rcdev, lookup->index, shared); mutex_unlock(&reset_list_mutex); diff --git a/include/linux/reset-controller.h b/include/linux/reset-controller.h index 25698f6c1fae..9326d671b6e6 100644 --- a/include/linux/reset-controller.h +++ b/include/linux/reset-controller.h @@ -30,24 +30,25 @@ struct of_phandle_args; * struct reset_control_lookup - represents a single lookup entry * * @list: internal list of all reset lookup entries - * @rcdev: reset controller device controlling this reset line + * @provider: name of the reset controller device controlling this reset line * @index: ID of the reset controller in the reset controller device * @dev_id: name of the device associated with this reset line * @con_id name of the reset line (can be NULL) */ struct reset_control_lookup { struct list_head list; - struct reset_controller_dev *rcdev; + const char *provider; unsigned int index; const char *dev_id; const char *con_id; }; -#define RESET_LOOKUP(_dev_id, _con_id, _index) \ +#define RESET_LOOKUP(_provider, _index, _dev_id, _con_id) \ { \ + .provider = _provider, \ + .index = _index, \ .dev_id = _dev_id, \ .con_id = _con_id, \ - .index = _index, \ } /** @@ -57,6 +58,7 @@ struct reset_control_lookup { * @owner: kernel module of the reset controller driver * @list: internal list of reset controller devices * @reset_control_head: head of internal list of requested reset controls + * @dev: corresponding driver model device struct * @of_node: corresponding device tree node as phandle target * @of_reset_n_cells: number of cells in reset line specifiers * @of_xlate: translation function to translate from specifier as found in the @@ -68,6 +70,7 @@ struct reset_controller_dev { struct module *owner; struct list_head list; struct list_head reset_control_head; + struct device *dev; struct device_node *of_node; int of_reset_n_cells; int (*of_xlate)(struct reset_controller_dev *rcdev, @@ -82,8 +85,7 @@ struct device; int devm_reset_controller_register(struct device *dev, struct reset_controller_dev *rcdev); -void reset_controller_add_lookup(struct reset_controller_dev *rcdev, - struct reset_control_lookup *lookup, +void reset_controller_add_lookup(struct reset_control_lookup *lookup, unsigned int num_entries); #endif -- cgit v1.2.3 From dae5af9762c8f03233b68401ecc4fab4befae11c Mon Sep 17 00:00:00 2001 From: Gabriel Fernandez Date: Mon, 19 Mar 2018 08:25:50 +0100 Subject: dt-bindings: reset: add STM32MP1 resets This patch adds the reset binding entry for STM32MP1 Signed-off-by: Gabriel Fernandez Reviewed-by: Rob Herring Signed-off-by: Philipp Zabel --- .../devicetree/bindings/reset/st,stm32mp1-rcc.txt | 6 ++ include/dt-bindings/reset/stm32mp1-resets.h | 108 +++++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 Documentation/devicetree/bindings/reset/st,stm32mp1-rcc.txt create mode 100644 include/dt-bindings/reset/stm32mp1-resets.h (limited to 'include') diff --git a/Documentation/devicetree/bindings/reset/st,stm32mp1-rcc.txt b/Documentation/devicetree/bindings/reset/st,stm32mp1-rcc.txt new file mode 100644 index 000000000000..b4edaf7c7ff3 --- /dev/null +++ b/Documentation/devicetree/bindings/reset/st,stm32mp1-rcc.txt @@ -0,0 +1,6 @@ +STMicroelectronics STM32MP1 Peripheral Reset Controller +======================================================= + +The RCC IP is both a reset and a clock controller. + +Please see Documentation/devicetree/bindings/clock/st,stm32mp1-rcc.txt diff --git a/include/dt-bindings/reset/stm32mp1-resets.h b/include/dt-bindings/reset/stm32mp1-resets.h new file mode 100644 index 000000000000..f0c3aaef67a0 --- /dev/null +++ b/include/dt-bindings/reset/stm32mp1-resets.h @@ -0,0 +1,108 @@ +/* SPDX-License-Identifier: GPL-2.0 or BSD-3-Clause */ +/* + * Copyright (C) STMicroelectronics 2018 - All Rights Reserved + * Author: Gabriel Fernandez for STMicroelectronics. + */ + +#ifndef _DT_BINDINGS_STM32MP1_RESET_H_ +#define _DT_BINDINGS_STM32MP1_RESET_H_ + +#define LTDC_R 3072 +#define DSI_R 3076 +#define DDRPERFM_R 3080 +#define USBPHY_R 3088 +#define SPI6_R 3136 +#define I2C4_R 3138 +#define I2C6_R 3139 +#define USART1_R 3140 +#define STGEN_R 3156 +#define GPIOZ_R 3200 +#define CRYP1_R 3204 +#define HASH1_R 3205 +#define RNG1_R 3206 +#define AXIM_R 3216 +#define GPU_R 3269 +#define ETHMAC_R 3274 +#define FMC_R 3276 +#define QSPI_R 3278 +#define SDMMC1_R 3280 +#define SDMMC2_R 3281 +#define CRC1_R 3284 +#define USBH_R 3288 +#define MDMA_R 3328 +#define MCU_R 8225 +#define TIM2_R 19456 +#define TIM3_R 19457 +#define TIM4_R 19458 +#define TIM5_R 19459 +#define TIM6_R 19460 +#define TIM7_R 19461 +#define TIM12_R 16462 +#define TIM13_R 16463 +#define TIM14_R 16464 +#define LPTIM1_R 19465 +#define SPI2_R 19467 +#define SPI3_R 19468 +#define USART2_R 19470 +#define USART3_R 19471 +#define UART4_R 19472 +#define UART5_R 19473 +#define UART7_R 19474 +#define UART8_R 19475 +#define I2C1_R 19477 +#define I2C2_R 19478 +#define I2C3_R 19479 +#define I2C5_R 19480 +#define SPDIF_R 19482 +#define CEC_R 19483 +#define DAC12_R 19485 +#define MDIO_R 19847 +#define TIM1_R 19520 +#define TIM8_R 19521 +#define TIM15_R 19522 +#define TIM16_R 19523 +#define TIM17_R 19524 +#define SPI1_R 19528 +#define SPI4_R 19529 +#define SPI5_R 19530 +#define USART6_R 19533 +#define SAI1_R 19536 +#define SAI2_R 19537 +#define SAI3_R 19538 +#define DFSDM_R 19540 +#define FDCAN_R 19544 +#define LPTIM2_R 19584 +#define LPTIM3_R 19585 +#define LPTIM4_R 19586 +#define LPTIM5_R 19587 +#define SAI4_R 19592 +#define SYSCFG_R 19595 +#define VREF_R 19597 +#define TMPSENS_R 19600 +#define PMBCTRL_R 19601 +#define DMA1_R 19648 +#define DMA2_R 19649 +#define DMAMUX_R 19650 +#define ADC12_R 19653 +#define USBO_R 19656 +#define SDMMC3_R 19664 +#define CAMITF_R 19712 +#define CRYP2_R 19716 +#define HASH2_R 19717 +#define RNG2_R 19718 +#define CRC2_R 19719 +#define HSEM_R 19723 +#define MBOX_R 19724 +#define GPIOA_R 19776 +#define GPIOB_R 19777 +#define GPIOC_R 19778 +#define GPIOD_R 19779 +#define GPIOE_R 19780 +#define GPIOF_R 19781 +#define GPIOG_R 19782 +#define GPIOH_R 19783 +#define GPIOI_R 19784 +#define GPIOJ_R 19785 +#define GPIOK_R 19786 + +#endif /* _DT_BINDINGS_STM32MP1_RESET_H_ */ -- cgit v1.2.3