Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,102 @@
# SPDX-License-Identifier: GPL-2.0-only
menu "ARM System Control and Management Interface Protocol"
config ARM_SCMI_PROTOCOL
tristate "ARM System Control and Management Interface (SCMI) Message Protocol"
depends on ARM || ARM64 || COMPILE_TEST
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.
if ARM_SCMI_PROTOCOL
config ARM_SCMI_NEED_DEBUGFS
bool
help
This declares whether at least one SCMI facility is configured
which needs debugfs support. When selected causess the creation
of a common SCMI debugfs root directory.
config ARM_SCMI_RAW_MODE_SUPPORT
bool "Enable support for SCMI Raw transmission mode"
depends on DEBUG_FS
select ARM_SCMI_NEED_DEBUGFS
help
Enable support for SCMI Raw transmission mode.
If enabled allows the direct injection and snooping of SCMI bare
messages through a dedicated debugfs interface.
It is meant to be used by SCMI compliance/testing suites.
When enabled regular SCMI drivers interactions are inhibited in
order to avoid unexpected interactions with the SCMI Raw message
flow. If unsure say N.
config ARM_SCMI_RAW_MODE_SUPPORT_COEX
bool "Allow SCMI Raw mode coexistence with normal SCMI stack"
depends on ARM_SCMI_RAW_MODE_SUPPORT
help
Allow SCMI Raw transmission mode to coexist with normal SCMI stack.
This will allow regular SCMI drivers to register with the core and
operate normally, thing which could make an SCMI test suite using the
SCMI Raw mode support unreliable. If unsure, say N.
config ARM_SCMI_DEBUG_COUNTERS
bool "Enable SCMI communication debug metrics tracking"
select ARM_SCMI_NEED_DEBUGFS
depends on DEBUG_FS
default n
help
Enables tracking of some key communication metrics for debug
purposes. It may track metrics like how many messages were sent
or received, were there any failures, what kind of failures, ..etc.
Enable this option to create a new debugfs directory which contains
such useful debug counters. This can be helpful for debugging and
SCMI monitoring.
config ARM_SCMI_QUIRKS
bool "Enable SCMI Quirks framework"
depends on JUMP_LABEL || COMPILE_TEST
default y
help
Enables support for SCMI Quirks framework to workaround SCMI platform
firmware bugs on system already deployed in the wild.
The framework allows the definition of platform-specific code quirks
that will be associated and enabled only on the desired platforms
depending on the SCMI firmware advertised versions and/or machine
compatibles.
source "drivers/firmware/arm_scmi/transports/Kconfig"
source "drivers/firmware/arm_scmi/vendors/imx/Kconfig"
endif #ARM_SCMI_PROTOCOL
config ARM_SCMI_POWER_CONTROL
tristate "SCMI system power control driver"
depends on ARM_SCMI_PROTOCOL || (COMPILE_TEST && OF)
help
This enables System Power control logic which binds system shutdown or
reboot actions to SCMI System Power notifications generated by SCP
firmware.
This driver can also be built as a module. If so, the module will be
called scmi_power_control. Note this may be needed early in boot to
catch early shutdown/reboot SCMI requests.
endmenu
@@ -0,0 +1,20 @@
# SPDX-License-Identifier: GPL-2.0-only
scmi-bus-y = bus.o
scmi-core-objs := $(scmi-bus-y)
scmi-driver-y = driver.o notify.o
scmi-driver-$(CONFIG_ARM_SCMI_QUIRKS) += quirks.o
scmi-driver-$(CONFIG_ARM_SCMI_RAW_MODE_SUPPORT) += raw_mode.o
scmi-transport-$(CONFIG_ARM_SCMI_HAVE_SHMEM) = shmem.o
scmi-transport-$(CONFIG_ARM_SCMI_HAVE_MSG) += msg.o
scmi-protocols-y := base.o clock.o perf.o power.o reset.o sensors.o system.o voltage.o powercap.o
scmi-protocols-y += pinctrl.o
scmi-module-objs := $(scmi-driver-y) $(scmi-protocols-y) $(scmi-transport-y)
obj-$(CONFIG_ARM_SCMI_PROTOCOL) += transports/
obj-$(CONFIG_ARM_SCMI_PROTOCOL) += vendors/imx/
obj-$(CONFIG_ARM_SCMI_PROTOCOL) += scmi-core.o
obj-$(CONFIG_ARM_SCMI_PROTOCOL) += scmi-module.o
obj-$(CONFIG_ARM_SCMI_POWER_CONTROL) += scmi_power_control.o
@@ -0,0 +1,436 @@
// SPDX-License-Identifier: GPL-2.0
/*
* System Control and Management Interface (SCMI) Base Protocol
*
* Copyright (C) 2018-2021 ARM Ltd.
*/
#define pr_fmt(fmt) "SCMI Notifications BASE - " fmt
#include <linux/math.h>
#include <linux/module.h>
#include <linux/scmi_protocol.h>
#include "common.h"
#include "notify.h"
/* Updated only after ALL the mandatory features for that version are merged */
#define SCMI_PROTOCOL_SUPPORTED_VERSION 0x20001
#define SCMI_BASE_NUM_SOURCES 1
#define SCMI_BASE_MAX_CMD_ERR_COUNT 1024
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,
BASE_SET_DEVICE_PERMISSIONS = 0x9,
BASE_SET_PROTOCOL_PERMISSIONS = 0xa,
BASE_RESET_AGENT_CONFIGURATION = 0xb,
};
struct scmi_msg_resp_base_attributes {
u8 num_protocols;
u8 num_agents;
__le16 reserved;
};
struct scmi_msg_resp_base_discover_agent {
__le32 agent_id;
u8 name[SCMI_SHORT_NAME_MAX_SIZE];
};
struct scmi_msg_base_error_notify {
__le32 event_control;
#define BASE_TP_NOTIFY_ALL BIT(0)
};
struct scmi_base_error_notify_payld {
__le32 agent_id;
__le32 error_status;
#define IS_FATAL_ERROR(x) ((x) & BIT(31))
#define ERROR_CMD_COUNT(x) FIELD_GET(GENMASK(9, 0), (x))
__le64 msg_reports[SCMI_BASE_MAX_CMD_ERR_COUNT];
};
/**
* scmi_base_attributes_get() - gets the implementation details
* that are associated with the base protocol.
*
* @ph: SCMI protocol handle
*
* Return: 0 on success, else appropriate SCMI error.
*/
static int scmi_base_attributes_get(const struct scmi_protocol_handle *ph)
{
int ret;
struct scmi_xfer *t;
struct scmi_msg_resp_base_attributes *attr_info;
struct scmi_base_info *rev = ph->get_priv(ph);
ret = ph->xops->xfer_get_init(ph, PROTOCOL_ATTRIBUTES,
0, sizeof(*attr_info), &t);
if (ret)
return ret;
ret = ph->xops->do_xfer(ph, t);
if (!ret) {
attr_info = t->rx.buf;
rev->num_protocols = attr_info->num_protocols;
rev->num_agents = attr_info->num_agents;
}
ph->xops->xfer_put(ph, t);
return ret;
}
/**
* scmi_base_vendor_id_get() - gets vendor/subvendor identifier ASCII string.
*
* @ph: SCMI protocol 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_protocol_handle *ph, bool sub_vendor)
{
u8 cmd;
int ret, size;
char *vendor_id;
struct scmi_xfer *t;
struct scmi_base_info *rev = ph->get_priv(ph);
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 = ph->xops->xfer_get_init(ph, cmd, 0, size, &t);
if (ret)
return ret;
ret = ph->xops->do_xfer(ph, t);
if (!ret)
strscpy(vendor_id, t->rx.buf, size);
ph->xops->xfer_put(ph, 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
*
* @ph: SCMI protocol handle
*
* Return: 0 on success, else appropriate SCMI error.
*/
static int
scmi_base_implementation_version_get(const struct scmi_protocol_handle *ph)
{
int ret;
__le32 *impl_ver;
struct scmi_xfer *t;
struct scmi_base_info *rev = ph->get_priv(ph);
ret = ph->xops->xfer_get_init(ph, BASE_DISCOVER_IMPLEMENT_VERSION,
0, sizeof(*impl_ver), &t);
if (ret)
return ret;
ret = ph->xops->do_xfer(ph, t);
if (!ret) {
impl_ver = t->rx.buf;
rev->impl_ver = le32_to_cpu(*impl_ver);
}
ph->xops->xfer_put(ph, t);
return ret;
}
/**
* scmi_base_implementation_list_get() - gets the list of protocols it is
* OSPM is allowed to access
*
* @ph: SCMI protocol 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_protocol_handle *ph,
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 = ph->dev;
struct scmi_base_info *rev = ph->get_priv(ph);
ret = ph->xops->xfer_get_init(ph, BASE_DISCOVER_LIST_PROTOCOLS,
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 {
size_t real_list_sz;
u32 calc_list_sz;
/* Set the number of protocols to be skipped/already read */
*num_skip = cpu_to_le32(tot_num_ret);
ret = ph->xops->do_xfer(ph, t);
if (ret)
break;
loop_num_ret = le32_to_cpu(*num_ret);
if (!loop_num_ret)
break;
if (loop_num_ret > rev->num_protocols - tot_num_ret) {
dev_err(dev,
"No. Returned protocols > Total protocols.\n");
break;
}
if (t->rx.len < (sizeof(u32) * 2)) {
dev_err(dev, "Truncated reply - rx.len:%zd\n",
t->rx.len);
ret = -EPROTO;
break;
}
real_list_sz = t->rx.len - sizeof(u32);
calc_list_sz = round_up(loop_num_ret, sizeof(u32));
if (calc_list_sz != real_list_sz) {
dev_warn(dev,
"Malformed reply - real_sz:%zd calc_sz:%u (loop_num_ret:%d)\n",
real_list_sz, calc_list_sz, loop_num_ret);
/*
* Bail out if the expected list size is bigger than the
* total payload size of the received reply.
*/
if (calc_list_sz > real_list_sz) {
ret = -EPROTO;
break;
}
}
for (loop = 0; loop < loop_num_ret; loop++)
protocols_imp[tot_num_ret + loop] = *(list + loop);
tot_num_ret += loop_num_ret;
ph->xops->reset_rx_to_maxsz(ph, t);
} while (tot_num_ret < rev->num_protocols);
ph->xops->xfer_put(ph, t);
return ret;
}
/**
* scmi_base_discover_agent_get() - discover the name of an agent
*
* @ph: SCMI protocol 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_protocol_handle *ph,
int id, char *name)
{
int ret;
struct scmi_msg_resp_base_discover_agent *agent_info;
struct scmi_xfer *t;
ret = ph->xops->xfer_get_init(ph, BASE_DISCOVER_AGENT,
sizeof(__le32), sizeof(*agent_info), &t);
if (ret)
return ret;
put_unaligned_le32(id, t->tx.buf);
ret = ph->xops->do_xfer(ph, t);
if (!ret) {
agent_info = t->rx.buf;
strscpy(name, agent_info->name, SCMI_SHORT_NAME_MAX_SIZE);
}
ph->xops->xfer_put(ph, t);
return ret;
}
static int scmi_base_error_notify(const struct scmi_protocol_handle *ph,
bool enable)
{
int ret;
u32 evt_cntl = enable ? BASE_TP_NOTIFY_ALL : 0;
struct scmi_xfer *t;
struct scmi_msg_base_error_notify *cfg;
ret = ph->xops->xfer_get_init(ph, BASE_NOTIFY_ERRORS,
sizeof(*cfg), 0, &t);
if (ret)
return ret;
cfg = t->tx.buf;
cfg->event_control = cpu_to_le32(evt_cntl);
ret = ph->xops->do_xfer(ph, t);
ph->xops->xfer_put(ph, t);
return ret;
}
static int scmi_base_set_notify_enabled(const struct scmi_protocol_handle *ph,
u8 evt_id, u32 src_id, bool enable)
{
int ret;
ret = scmi_base_error_notify(ph, enable);
if (ret)
pr_debug("FAIL_ENABLED - evt[%X] ret:%d\n", evt_id, ret);
return ret;
}
static void *scmi_base_fill_custom_report(const struct scmi_protocol_handle *ph,
u8 evt_id, ktime_t timestamp,
const void *payld, size_t payld_sz,
void *report, u32 *src_id)
{
int i;
u32 error_status;
size_t expected_sz;
const struct scmi_base_error_notify_payld *p = payld;
struct scmi_base_error_report *r = report;
/*
* BaseError notification payload is variable in size but
* up to a maximum length determined by the struct ponted by p.
* Instead payld_sz is the effective length of this notification
* payload so cannot be greater of the maximum allowed size as
* pointed by p.
*/
if (evt_id != SCMI_EVENT_BASE_ERROR_EVENT || sizeof(*p) < payld_sz)
return NULL;
expected_sz = offsetof(typeof(*p), msg_reports);
if (payld_sz < expected_sz)
return NULL;
r->timestamp = timestamp;
r->agent_id = le32_to_cpu(p->agent_id);
error_status = le32_to_cpu(p->error_status);
r->fatal = IS_FATAL_ERROR(error_status);
r->cmd_count = ERROR_CMD_COUNT(error_status);
expected_sz += r->cmd_count * sizeof(p->msg_reports[0]);
if (payld_sz < expected_sz)
return NULL;
for (i = 0; i < r->cmd_count; i++)
r->reports[i] = le64_to_cpu(p->msg_reports[i]);
*src_id = 0;
return r;
}
static const struct scmi_event base_events[] = {
{
.id = SCMI_EVENT_BASE_ERROR_EVENT,
.max_payld_sz = sizeof(struct scmi_base_error_notify_payld),
.max_report_sz = sizeof(struct scmi_base_error_report) +
SCMI_BASE_MAX_CMD_ERR_COUNT * sizeof(u64),
},
};
static const struct scmi_event_ops base_event_ops = {
.set_notify_enabled = scmi_base_set_notify_enabled,
.fill_custom_report = scmi_base_fill_custom_report,
};
static const struct scmi_protocol_events base_protocol_events = {
.queue_sz = 4 * SCMI_PROTO_QUEUE_SZ,
.ops = &base_event_ops,
.evts = base_events,
.num_events = ARRAY_SIZE(base_events),
.num_sources = SCMI_BASE_NUM_SOURCES,
};
static int scmi_base_protocol_init(const struct scmi_protocol_handle *ph)
{
int id, ret;
u8 *prot_imp;
char name[SCMI_SHORT_NAME_MAX_SIZE];
struct device *dev = ph->dev;
struct scmi_base_info *rev = scmi_revision_area_get(ph);
rev->major_ver = PROTOCOL_REV_MAJOR(ph->version);
rev->minor_ver = PROTOCOL_REV_MINOR(ph->version);
ph->set_priv(ph, rev);
ret = scmi_base_attributes_get(ph);
if (ret)
return ret;
prot_imp = devm_kcalloc(dev, rev->num_protocols, sizeof(u8),
GFP_KERNEL);
if (!prot_imp)
return -ENOMEM;
scmi_base_vendor_id_get(ph, false);
scmi_base_vendor_id_get(ph, true);
scmi_base_implementation_version_get(ph);
scmi_base_implementation_list_get(ph, prot_imp);
scmi_setup_protocol_implemented(ph, 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(ph, id, name);
dev_dbg(dev, "Agent %d: %s\n", id, name);
}
return 0;
}
static const struct scmi_protocol scmi_base = {
.id = SCMI_PROTOCOL_BASE,
.owner = NULL,
.instance_init = &scmi_base_protocol_init,
.ops = NULL,
.events = &base_protocol_events,
.supported_version = SCMI_PROTOCOL_SUPPORTED_VERSION,
};
DEFINE_SCMI_PROTOCOL_REGISTER_UNREGISTER(base, scmi_base)
+612
View File
@@ -0,0 +1,612 @@
// SPDX-License-Identifier: GPL-2.0
/*
* System Control and Management Interface (SCMI) Message Protocol bus layer
*
* Copyright (C) 2018-2021 ARM Ltd.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/atomic.h>
#include <linux/types.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/device.h>
#include "common.h"
#define SCMI_UEVENT_MODALIAS_FMT "%s:%02x:%s"
BLOCKING_NOTIFIER_HEAD(scmi_requested_devices_nh);
EXPORT_SYMBOL_GPL(scmi_requested_devices_nh);
static DEFINE_IDA(scmi_bus_id);
static DEFINE_IDR(scmi_requested_devices);
/* Protect access to scmi_requested_devices */
static DEFINE_MUTEX(scmi_requested_devices_mtx);
struct scmi_requested_dev {
const struct scmi_device_id *id_table;
struct list_head node;
};
/* Track globally the creation of SCMI SystemPower related devices */
static atomic_t scmi_syspower_registered = ATOMIC_INIT(0);
/**
* scmi_protocol_device_request - Helper to request a device
*
* @id_table: A protocol/name pair descriptor for the device to be created.
*
* This helper let an SCMI driver request specific devices identified by the
* @id_table to be created for each active SCMI instance.
*
* The requested device name MUST NOT be already existent for this protocol;
* at first the freshly requested @id_table is annotated in the IDR table
* @scmi_requested_devices and then the requested device is advertised to any
* registered party via the @scmi_requested_devices_nh notification chain.
*
* Return: 0 on Success
*/
static int scmi_protocol_device_request(const struct scmi_device_id *id_table)
{
int ret = 0;
struct list_head *head, *phead = NULL;
struct scmi_requested_dev *rdev;
pr_debug("Requesting SCMI device (%s) for protocol %x\n",
id_table->name, id_table->protocol_id);
if (IS_ENABLED(CONFIG_ARM_SCMI_RAW_MODE_SUPPORT) &&
!IS_ENABLED(CONFIG_ARM_SCMI_RAW_MODE_SUPPORT_COEX)) {
pr_warn("SCMI Raw mode active. Rejecting '%s'/0x%02X\n",
id_table->name, id_table->protocol_id);
return -EINVAL;
}
/*
* Find the matching protocol rdev list and then search of any
* existent equally named device...fails if any duplicate found.
*/
mutex_lock(&scmi_requested_devices_mtx);
phead = idr_find(&scmi_requested_devices, id_table->protocol_id);
if (phead) {
head = phead;
list_for_each_entry(rdev, head, node) {
if (!strcmp(rdev->id_table->name, id_table->name)) {
pr_err("Ignoring duplicate request [%d] %s\n",
rdev->id_table->protocol_id,
rdev->id_table->name);
ret = -EINVAL;
goto out;
}
}
}
/*
* No duplicate found for requested id_table, so let's create a new
* requested device entry for this new valid request.
*/
rdev = kzalloc_obj(*rdev);
if (!rdev) {
ret = -ENOMEM;
goto out;
}
rdev->id_table = id_table;
/*
* Append the new requested device table descriptor to the head of the
* related protocol list, eventually creating such head if not already
* there.
*/
if (!phead) {
phead = kzalloc_obj(*phead);
if (!phead) {
kfree(rdev);
ret = -ENOMEM;
goto out;
}
INIT_LIST_HEAD(phead);
ret = idr_alloc(&scmi_requested_devices, (void *)phead,
id_table->protocol_id,
id_table->protocol_id + 1, GFP_KERNEL);
if (ret != id_table->protocol_id) {
pr_err("Failed to save SCMI device - ret:%d\n", ret);
kfree(rdev);
kfree(phead);
ret = -EINVAL;
goto out;
}
ret = 0;
}
list_add(&rdev->node, phead);
out:
mutex_unlock(&scmi_requested_devices_mtx);
if (!ret)
blocking_notifier_call_chain(&scmi_requested_devices_nh,
SCMI_BUS_NOTIFY_DEVICE_REQUEST,
(void *)rdev->id_table);
return ret;
}
static int scmi_protocol_table_register(const struct scmi_device_id *id_table)
{
int ret = 0;
const struct scmi_device_id *entry;
for (entry = id_table; entry->name && ret == 0; entry++)
ret = scmi_protocol_device_request(entry);
return ret;
}
/**
* scmi_protocol_device_unrequest - Helper to unrequest a device
*
* @id_table: A protocol/name pair descriptor for the device to be unrequested.
*
* The unrequested device, described by the provided id_table, is at first
* removed from the IDR @scmi_requested_devices and then the removal is
* advertised to any registered party via the @scmi_requested_devices_nh
* notification chain.
*/
static void scmi_protocol_device_unrequest(const struct scmi_device_id *id_table)
{
struct list_head *phead;
pr_debug("Unrequesting SCMI device (%s) for protocol %x\n",
id_table->name, id_table->protocol_id);
mutex_lock(&scmi_requested_devices_mtx);
phead = idr_find(&scmi_requested_devices, id_table->protocol_id);
if (phead) {
struct scmi_requested_dev *victim, *tmp;
list_for_each_entry_safe(victim, tmp, phead, node) {
if (!strcmp(victim->id_table->name, id_table->name)) {
list_del(&victim->node);
mutex_unlock(&scmi_requested_devices_mtx);
blocking_notifier_call_chain(&scmi_requested_devices_nh,
SCMI_BUS_NOTIFY_DEVICE_UNREQUEST,
(void *)victim->id_table);
kfree(victim);
mutex_lock(&scmi_requested_devices_mtx);
break;
}
}
if (list_empty(phead)) {
idr_remove(&scmi_requested_devices,
id_table->protocol_id);
kfree(phead);
}
}
mutex_unlock(&scmi_requested_devices_mtx);
}
static void
scmi_protocol_table_unregister(const struct scmi_device_id *id_table)
{
const struct scmi_device_id *entry;
for (entry = id_table; entry->name; entry++)
scmi_protocol_device_unrequest(entry);
}
static int scmi_dev_match_by_id_table(struct scmi_device *scmi_dev,
const struct scmi_device_id *id_table)
{
if (!id_table || !id_table->name)
return 0;
/* Always skip transport devices from matching */
for (; id_table->protocol_id && id_table->name; id_table++)
if (id_table->protocol_id == scmi_dev->protocol_id &&
strncmp(scmi_dev->name, "__scmi_transport_device", 23) &&
!strcmp(id_table->name, scmi_dev->name))
return 1;
return 0;
}
static int scmi_dev_match_id(struct scmi_device *scmi_dev,
const struct scmi_driver *scmi_drv)
{
return scmi_dev_match_by_id_table(scmi_dev, scmi_drv->id_table);
}
static int scmi_dev_match(struct device *dev, const struct device_driver *drv)
{
const struct scmi_driver *scmi_drv = to_scmi_driver(drv);
struct scmi_device *scmi_dev = to_scmi_dev(dev);
return scmi_dev_match_id(scmi_dev, scmi_drv);
}
static int scmi_match_by_id_table(struct device *dev, const void *data)
{
struct scmi_device *scmi_dev = to_scmi_dev(dev);
const struct scmi_device_id *id_table = data;
return scmi_dev_match_by_id_table(scmi_dev, id_table);
}
static struct scmi_device *scmi_child_dev_find(struct device *parent,
int prot_id, const char *name)
{
struct scmi_device_id id_table[2] = { 0 };
struct device *dev;
id_table[0].protocol_id = prot_id;
id_table[0].name = name;
dev = device_find_child(parent, &id_table, scmi_match_by_id_table);
if (!dev)
return NULL;
/* Drop the refcnt bumped implicitly by device_find_child */
put_device(dev);
return to_scmi_dev(dev);
}
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);
if (!scmi_dev->handle)
return -EPROBE_DEFER;
return scmi_drv->probe(scmi_dev);
}
static void 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);
}
static int scmi_device_uevent(const struct device *dev, struct kobj_uevent_env *env)
{
const struct scmi_device *scmi_dev = to_scmi_dev(dev);
return add_uevent_var(env, "MODALIAS=" SCMI_UEVENT_MODALIAS_FMT,
dev_name(&scmi_dev->dev), scmi_dev->protocol_id,
scmi_dev->name);
}
static ssize_t modalias_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct scmi_device *scmi_dev = to_scmi_dev(dev);
return sysfs_emit(buf, SCMI_UEVENT_MODALIAS_FMT,
dev_name(&scmi_dev->dev), scmi_dev->protocol_id,
scmi_dev->name);
}
static DEVICE_ATTR_RO(modalias);
static ssize_t protocol_id_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct scmi_device *scmi_dev = to_scmi_dev(dev);
return sprintf(buf, "0x%02x\n", scmi_dev->protocol_id);
}
static DEVICE_ATTR_RO(protocol_id);
static ssize_t name_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct scmi_device *scmi_dev = to_scmi_dev(dev);
return sprintf(buf, "%s\n", scmi_dev->name);
}
static DEVICE_ATTR_RO(name);
static struct attribute *scmi_device_attributes_attrs[] = {
&dev_attr_protocol_id.attr,
&dev_attr_name.attr,
&dev_attr_modalias.attr,
NULL,
};
ATTRIBUTE_GROUPS(scmi_device_attributes);
static int scmi_pm_suspend(struct device *dev)
{
const struct device_driver *drv = dev->driver;
if (drv && drv->pm && drv->pm->suspend)
return drv->pm->suspend(dev);
return 0;
}
static int scmi_pm_resume(struct device *dev)
{
const struct device_driver *drv = dev->driver;
if (drv && drv->pm && drv->pm->resume)
return drv->pm->resume(dev);
return 0;
}
static const struct dev_pm_ops scmi_dev_pm_ops = {
.suspend = pm_sleep_ptr(scmi_pm_suspend),
.resume = pm_sleep_ptr(scmi_pm_resume),
};
const struct bus_type scmi_bus_type = {
.name = "scmi_protocol",
.match = scmi_dev_match,
.probe = scmi_dev_probe,
.remove = scmi_dev_remove,
.uevent = scmi_device_uevent,
.dev_groups = scmi_device_attributes_groups,
.pm = &scmi_dev_pm_ops,
};
EXPORT_SYMBOL_GPL(scmi_bus_type);
int scmi_driver_register(struct scmi_driver *driver, struct module *owner,
const char *mod_name)
{
int retval;
if (!driver->probe)
return -EINVAL;
retval = scmi_protocol_table_register(driver->id_table);
if (retval)
return 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);
scmi_protocol_table_unregister(driver->id_table);
}
EXPORT_SYMBOL_GPL(scmi_driver_unregister);
static void scmi_device_release(struct device *dev)
{
struct scmi_device *scmi_dev = to_scmi_dev(dev);
kfree_const(scmi_dev->name);
kfree(scmi_dev);
}
static void __scmi_device_destroy(struct scmi_device *scmi_dev)
{
pr_debug("(%pOF) Destroying SCMI device '%s' for protocol 0x%x (%s)\n",
scmi_dev->dev.parent->of_node,
dev_name(&scmi_dev->dev), scmi_dev->protocol_id,
scmi_dev->name);
if (scmi_dev->protocol_id == SCMI_PROTOCOL_SYSTEM)
atomic_set(&scmi_syspower_registered, 0);
ida_free(&scmi_bus_id, scmi_dev->id);
device_unregister(&scmi_dev->dev);
}
static struct scmi_device *
__scmi_device_create(struct device_node *np, struct device *parent,
int protocol, const char *name)
{
int id, retval;
struct scmi_device *scmi_dev;
/*
* If the same protocol/name device already exist under the same parent
* (i.e. SCMI instance) just return the existent device.
* This avoids any race between the SCMI driver, creating devices for
* each DT defined protocol at probe time, and the concurrent
* registration of SCMI drivers.
*/
scmi_dev = scmi_child_dev_find(parent, protocol, name);
if (scmi_dev)
return scmi_dev;
/*
* Ignore any possible subsequent failures while creating the device
* since we are doomed anyway at that point; not using a mutex which
* spans across this whole function to keep things simple and to avoid
* to serialize all the __scmi_device_create calls across possibly
* different SCMI server instances (parent)
*/
if (protocol == SCMI_PROTOCOL_SYSTEM &&
atomic_cmpxchg(&scmi_syspower_registered, 0, 1)) {
dev_warn(parent,
"SCMI SystemPower protocol device must be unique !\n");
return NULL;
}
scmi_dev = kzalloc_obj(*scmi_dev);
if (!scmi_dev)
return NULL;
scmi_dev->name = kstrdup_const(name ?: "unknown", GFP_KERNEL);
if (!scmi_dev->name) {
kfree(scmi_dev);
return NULL;
}
id = ida_alloc_min(&scmi_bus_id, 1, GFP_KERNEL);
if (id < 0) {
kfree_const(scmi_dev->name);
kfree(scmi_dev);
return NULL;
}
scmi_dev->id = id;
scmi_dev->protocol_id = protocol;
scmi_dev->dev.parent = parent;
device_set_node(&scmi_dev->dev, of_fwnode_handle(np));
scmi_dev->dev.bus = &scmi_bus_type;
scmi_dev->dev.release = scmi_device_release;
dev_set_name(&scmi_dev->dev, "scmi_dev.%d", id);
retval = device_register(&scmi_dev->dev);
if (retval)
goto put_dev;
pr_debug("(%pOF) Created SCMI device '%s' for protocol 0x%x (%s)\n",
parent->of_node, dev_name(&scmi_dev->dev), protocol, name);
return scmi_dev;
put_dev:
put_device(&scmi_dev->dev);
ida_free(&scmi_bus_id, id);
return NULL;
}
static struct scmi_device *
_scmi_device_create(struct device_node *np, struct device *parent,
int protocol, const char *name)
{
struct scmi_device *sdev;
sdev = __scmi_device_create(np, parent, protocol, name);
if (!sdev)
pr_err("(%pOF) Failed to create device for protocol 0x%x (%s)\n",
parent->of_node, protocol, name);
return sdev;
}
/**
* scmi_device_create - A method to create one or more SCMI devices
*
* @np: A reference to the device node to use for the new device(s)
* @parent: The parent device to use identifying a specific SCMI instance
* @protocol: The SCMI protocol to be associated with this device
* @name: The requested-name of the device to be created; this is optional
* and if no @name is provided, all the devices currently known to
* be requested on the SCMI bus for @protocol will be created.
*
* This method can be invoked to create a single well-defined device (like
* a transport device or a device requested by an SCMI driver loaded after
* the core SCMI stack has been probed), or to create all the devices currently
* known to have been requested by the loaded SCMI drivers for a specific
* protocol (typically during SCMI core protocol enumeration at probe time).
*
* Return: The created device (or one of them if @name was NOT provided and
* multiple devices were created) or NULL if no device was created;
* note that NULL indicates an error ONLY in case a specific @name
* was provided: when @name param was not provided, a number of devices
* could have been potentially created for a whole protocol, unless no
* device was found to have been requested for that specific protocol.
*/
struct scmi_device *scmi_device_create(struct device_node *np,
struct device *parent, int protocol,
const char *name)
{
struct list_head *phead;
struct scmi_requested_dev *rdev;
struct scmi_device *scmi_dev = NULL;
if (name)
return _scmi_device_create(np, parent, protocol, name);
mutex_lock(&scmi_requested_devices_mtx);
phead = idr_find(&scmi_requested_devices, protocol);
/* Nothing to do. */
if (!phead) {
mutex_unlock(&scmi_requested_devices_mtx);
return NULL;
}
/* Walk the list of requested devices for protocol and create them */
list_for_each_entry(rdev, phead, node) {
struct scmi_device *sdev;
sdev = _scmi_device_create(np, parent,
rdev->id_table->protocol_id,
rdev->id_table->name);
if (sdev)
scmi_dev = sdev;
}
mutex_unlock(&scmi_requested_devices_mtx);
return scmi_dev;
}
EXPORT_SYMBOL_GPL(scmi_device_create);
void scmi_device_destroy(struct device *parent, int protocol, const char *name)
{
struct scmi_device *scmi_dev;
scmi_dev = scmi_child_dev_find(parent, protocol, name);
if (scmi_dev)
__scmi_device_destroy(scmi_dev);
}
EXPORT_SYMBOL_GPL(scmi_device_destroy);
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);
pr_info("SCMI protocol bus registered\n");
return retval;
}
subsys_initcall(scmi_bus_init);
static void __exit scmi_bus_exit(void)
{
/*
* Destroy all remaining devices: just in case the drivers were
* manually unbound and at first and then the modules unloaded.
*/
scmi_devices_unregister();
bus_unregister(&scmi_bus_type);
ida_destroy(&scmi_bus_id);
}
module_exit(scmi_bus_exit);
MODULE_ALIAS("scmi-core");
MODULE_AUTHOR("Sudeep Holla <sudeep.holla@arm.com>");
MODULE_DESCRIPTION("ARM SCMI protocol bus");
MODULE_LICENSE("GPL");
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,688 @@
/* 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-2024 ARM Ltd.
*/
#ifndef _SCMI_COMMON_H
#define _SCMI_COMMON_H
#include <linux/bitfield.h>
#include <linux/completion.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/hashtable.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/property.h>
#include <linux/refcount.h>
#include <linux/scmi_protocol.h>
#include <linux/spinlock.h>
#include <linux/types.h>
#include <linux/unaligned.h>
#include "protocols.h"
#include "notify.h"
#define SCMI_MAX_CHANNELS 256
#define SCMI_MAX_RESPONSE_TIMEOUT (2 * MSEC_PER_SEC)
#define SCMI_SHMEM_MAX_PAYLOAD_SIZE 104
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 */
};
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)
{
int err_idx = -errno;
if (err_idx >= SCMI_SUCCESS && err_idx < ARRAY_SIZE(scmi_linux_errmap))
return scmi_linux_errmap[err_idx];
return -EIO;
}
#define MSG_ID_MASK GENMASK(7, 0)
#define MSG_XTRACT_ID(hdr) FIELD_GET(MSG_ID_MASK, (hdr))
#define MSG_TYPE_MASK GENMASK(9, 8)
#define MSG_XTRACT_TYPE(hdr) FIELD_GET(MSG_TYPE_MASK, (hdr))
#define MSG_TYPE_COMMAND 0
#define MSG_TYPE_DELAYED_RESP 2
#define MSG_TYPE_NOTIFICATION 3
#define MSG_PROTOCOL_ID_MASK GENMASK(17, 10)
#define MSG_XTRACT_PROT_ID(hdr) FIELD_GET(MSG_PROTOCOL_ID_MASK, (hdr))
#define MSG_TOKEN_ID_MASK GENMASK(27, 18)
#define MSG_XTRACT_TOKEN(hdr) FIELD_GET(MSG_TOKEN_ID_MASK, (hdr))
#define MSG_TOKEN_MAX (MSG_XTRACT_TOKEN(MSG_TOKEN_ID_MASK) + 1)
/*
* Size of @pending_xfers hashtable included in @scmi_xfers_info; ideally, in
* order to minimize space and collisions, this should equal max_msg, i.e. the
* maximum number of in-flight messages on a specific platform, but such value
* is only available at runtime while kernel hashtables are statically sized:
* pick instead as a fixed static size the maximum number of entries that can
* fit the whole table into one 4k page.
*/
#define SCMI_PENDING_XFERS_HT_ORDER_SZ 9
/**
* pack_scmi_header() - packs and returns 32-bit header
*
* @hdr: pointer to header containing all the information on message id,
* protocol id, sequence id and type.
*
* Return: 32-bit packed message header to be sent to the platform.
*/
static inline u32 pack_scmi_header(struct scmi_msg_hdr *hdr)
{
return FIELD_PREP(MSG_ID_MASK, hdr->id) |
FIELD_PREP(MSG_TYPE_MASK, hdr->type) |
FIELD_PREP(MSG_TOKEN_ID_MASK, hdr->seq) |
FIELD_PREP(MSG_PROTOCOL_ID_MASK, hdr->protocol_id);
}
/**
* unpack_scmi_header() - unpacks and records message and protocol id
*
* @msg_hdr: 32-bit packed message header sent from the platform
* @hdr: pointer to header to fetch message and protocol id.
*/
static inline void unpack_scmi_header(u32 msg_hdr, struct scmi_msg_hdr *hdr)
{
hdr->id = MSG_XTRACT_ID(msg_hdr);
hdr->protocol_id = MSG_XTRACT_PROT_ID(msg_hdr);
hdr->type = MSG_XTRACT_TYPE(msg_hdr);
}
/*
* An helper macro to lookup an xfer from the @pending_xfers hashtable
* using the message sequence number token as a key.
*/
#define XFER_FIND(__ht, __k) \
({ \
typeof(__k) k_ = __k; \
struct scmi_xfer *xfer_ = NULL; \
\
hash_for_each_possible((__ht), xfer_, node, k_) \
if (xfer_->hdr.seq == k_) \
break; \
xfer_; \
})
struct scmi_base_info *
scmi_revision_area_get(const struct scmi_protocol_handle *ph);
void scmi_setup_protocol_implemented(const struct scmi_protocol_handle *ph,
u8 *prot_imp);
extern const struct bus_type scmi_bus_type;
#define SCMI_BUS_NOTIFY_DEVICE_REQUEST 0
#define SCMI_BUS_NOTIFY_DEVICE_UNREQUEST 1
extern struct blocking_notifier_head scmi_requested_devices_nh;
struct scmi_device *scmi_device_create(struct device_node *np,
struct device *parent, int protocol,
const char *name);
void scmi_device_destroy(struct device *parent, int protocol, const char *name);
int scmi_protocol_acquire(const struct scmi_handle *handle, u8 protocol_id);
void scmi_protocol_release(const struct scmi_handle *handle, u8 protocol_id);
/* SCMI Transport */
/**
* struct scmi_chan_info - Structure representing a SCMI channel information
*
* @id: An identifier for this channel: this matches the protocol number
* used to initialize this channel
* @dev: Reference to device in the SCMI hierarchy corresponding to this
* channel
* @is_p2a: A flag to identify a channel as P2A (RX)
* @rx_timeout_ms: The configured RX timeout in milliseconds.
* @max_msg_size: Maximum size of message payload.
* @handle: Pointer to SCMI entity handle
* @no_completion_irq: Flag to indicate that this channel has no completion
* interrupt mechanism for synchronous commands.
* This can be dynamically set by transports at run-time
* inside their provided .chan_setup().
* @transport_info: Transport layer related information
*/
struct scmi_chan_info {
int id;
struct device *dev;
bool is_p2a;
unsigned int rx_timeout_ms;
unsigned int max_msg_size;
struct scmi_handle *handle;
bool no_completion_irq;
void *transport_info;
};
/**
* struct scmi_transport_ops - Structure representing a SCMI transport ops
*
* @chan_available: Callback to check if channel is available or not
* @chan_setup: Callback to allocate and setup a channel
* @chan_free: Callback to free a channel
* @get_max_msg: Optional callback to provide max_msg dynamically
* Returns the maximum number of messages for the channel type
* (tx or rx) that can be pending simultaneously in the system
* @send_message: Callback to send a message
* @mark_txdone: Callback to mark tx as done
* @fetch_response: Callback to fetch response
* @fetch_notification: Callback to fetch notification
* @clear_channel: Callback to clear a channel
* @poll_done: Callback to poll transfer status
*/
struct scmi_transport_ops {
bool (*chan_available)(struct device_node *of_node, int idx);
int (*chan_setup)(struct scmi_chan_info *cinfo, struct device *dev,
bool tx);
int (*chan_free)(int id, void *p, void *data);
unsigned int (*get_max_msg)(struct scmi_chan_info *base_cinfo);
int (*send_message)(struct scmi_chan_info *cinfo,
struct scmi_xfer *xfer);
void (*mark_txdone)(struct scmi_chan_info *cinfo, int ret,
struct scmi_xfer *xfer);
void (*fetch_response)(struct scmi_chan_info *cinfo,
struct scmi_xfer *xfer);
void (*fetch_notification)(struct scmi_chan_info *cinfo,
size_t max_len, struct scmi_xfer *xfer);
void (*clear_channel)(struct scmi_chan_info *cinfo);
bool (*poll_done)(struct scmi_chan_info *cinfo, struct scmi_xfer *xfer);
};
/**
* struct scmi_desc - Description of SoC integration
*
* @ops: Pointer to the transport specific ops structure
* @max_rx_timeout_ms: Timeout for communication with SoC (in Milliseconds)
* @max_msg: Maximum number of messages for a channel type (tx or rx) that can
* be pending simultaneously in the system. May be overridden by the
* get_max_msg op.
* @max_msg_size: Maximum size of data payload per message that can be handled.
* @atomic_threshold: Optional system wide DT-configured threshold, expressed
* in microseconds, for atomic operations.
* Only SCMI synchronous commands reported by the platform
* to have an execution latency lesser-equal to the threshold
* should be considered for atomic mode operation: such
* decision is finally left up to the SCMI drivers.
* @no_completion_irq: Flag to indicate that this transport has no completion
* interrupt and has to be polled. This is similar to the
* force_polling below, except this is set via DT property.
* @force_polling: Flag to force this whole transport to use SCMI core polling
* mechanism instead of completion interrupts even if available.
* @sync_cmds_completed_on_ret: Flag to indicate that the transport assures
* synchronous-command messages are atomically
* completed on .send_message: no need to poll
* actively waiting for a response.
* Used by core internally only when polling is
* selected as a waiting for reply method: i.e.
* if a completion irq was found use that anyway.
* @atomic_enabled: Flag to indicate that this transport, which is assured not
* to sleep anywhere on the TX path, can be used in atomic mode
* when requested.
*/
struct scmi_desc {
const struct scmi_transport_ops *ops;
int max_rx_timeout_ms;
int max_msg;
int max_msg_size;
unsigned int atomic_threshold;
bool no_completion_irq;
const bool force_polling;
const bool sync_cmds_completed_on_ret;
const bool atomic_enabled;
};
static inline bool is_polling_required(struct scmi_chan_info *cinfo,
const struct scmi_desc *desc)
{
return cinfo->no_completion_irq || desc->force_polling;
}
static inline bool is_transport_polling_capable(const struct scmi_desc *desc)
{
return desc->ops->poll_done || desc->sync_cmds_completed_on_ret;
}
static inline bool is_polling_enabled(struct scmi_chan_info *cinfo,
const struct scmi_desc *desc)
{
return is_polling_required(cinfo, desc) &&
is_transport_polling_capable(desc);
}
void scmi_xfer_raw_put(const struct scmi_handle *handle,
struct scmi_xfer *xfer);
struct scmi_xfer *scmi_xfer_raw_get(const struct scmi_handle *handle);
struct scmi_chan_info *
scmi_xfer_raw_channel_get(const struct scmi_handle *handle, u8 protocol_id);
int scmi_xfer_raw_inflight_register(const struct scmi_handle *handle,
struct scmi_xfer *xfer);
int scmi_xfer_raw_wait_for_message_response(struct scmi_chan_info *cinfo,
struct scmi_xfer *xfer,
unsigned int timeout_ms);
enum debug_counters {
SENT_OK,
SENT_FAIL,
SENT_FAIL_POLLING_UNSUPPORTED,
SENT_FAIL_CHANNEL_NOT_FOUND,
RESPONSE_OK,
NOTIFICATION_OK,
DELAYED_RESPONSE_OK,
XFERS_RESPONSE_TIMEOUT,
XFERS_RESPONSE_POLLED_TIMEOUT,
RESPONSE_POLLED_OK,
ERR_MSG_UNEXPECTED,
ERR_MSG_INVALID,
ERR_MSG_NOMEM,
ERR_PROTOCOL,
XFERS_INFLIGHT,
SCMI_DEBUG_COUNTERS_LAST
};
/**
* struct scmi_debug_info - Debug common info
* @top_dentry: A reference to the top debugfs dentry
* @name: Name of this SCMI instance
* @type: Type of this SCMI instance
* @is_atomic: Flag to state if the transport of this instance is atomic
* @counters: An array of atomic_c's used for tracking statistics (if enabled)
*/
struct scmi_debug_info {
struct dentry *top_dentry;
const char *name;
const char *type;
bool is_atomic;
atomic_t counters[SCMI_DEBUG_COUNTERS_LAST];
};
static inline void scmi_inc_count(struct scmi_debug_info *dbg, int stat)
{
if (IS_ENABLED(CONFIG_ARM_SCMI_DEBUG_COUNTERS)) {
if (dbg)
atomic_inc(&dbg->counters[stat]);
}
}
static inline void scmi_dec_count(struct scmi_debug_info *dbg, int stat)
{
if (IS_ENABLED(CONFIG_ARM_SCMI_DEBUG_COUNTERS)) {
if (dbg)
atomic_dec(&dbg->counters[stat]);
}
}
enum scmi_bad_msg {
MSG_UNEXPECTED = -1,
MSG_INVALID = -2,
MSG_UNKNOWN = -3,
MSG_NOMEM = -4,
MSG_MBOX_SPURIOUS = -5,
};
/* Used for compactness and signature validation of the function pointers being
* passed.
*/
typedef void (*shmem_copy_toio_t)(void __iomem *to, const void *from,
size_t count);
typedef void (*shmem_copy_fromio_t)(void *to, const void __iomem *from,
size_t count);
/**
* struct scmi_shmem_io_ops - I/O operations to read from/write to
* Shared Memory
*
* @toio: Copy data to the shared memory area
* @fromio: Copy data from the shared memory area
*/
struct scmi_shmem_io_ops {
shmem_copy_fromio_t fromio;
shmem_copy_toio_t toio;
};
/* shmem related declarations */
struct scmi_shared_mem;
/**
* struct scmi_shared_mem_operations - Transport core operations for
* Shared Memory
*
* @tx_prepare: Prepare the @xfer message for transmission on the chosen @shmem
* @read_header: Read header of the message currently hold in @shmem
* @fetch_response: Copy the message response from @shmem into @xfer
* @fetch_notification: Copy the message notification from @shmem into @xfer
* @clear_channel: Clear the @shmem channel busy flag
* @poll_done: Check if poll has completed for @xfer on @shmem
* @channel_free: Check if @shmem channel is marked as free
* @channel_intr_enabled: Check is @shmem channel has requested a completion irq
* @setup_iomap: Setup IO shared memory for channel @cinfo
*/
struct scmi_shared_mem_operations {
void (*tx_prepare)(struct scmi_shared_mem __iomem *shmem,
struct scmi_xfer *xfer,
struct scmi_chan_info *cinfo,
shmem_copy_toio_t toio);
u32 (*read_header)(struct scmi_shared_mem __iomem *shmem);
void (*fetch_response)(struct scmi_shared_mem __iomem *shmem,
struct scmi_xfer *xfer,
shmem_copy_fromio_t fromio);
void (*fetch_notification)(struct scmi_shared_mem __iomem *shmem,
size_t max_len, struct scmi_xfer *xfer,
shmem_copy_fromio_t fromio);
void (*clear_channel)(struct scmi_shared_mem __iomem *shmem);
bool (*poll_done)(struct scmi_shared_mem __iomem *shmem,
struct scmi_xfer *xfer);
bool (*channel_free)(struct scmi_shared_mem __iomem *shmem);
bool (*channel_intr_enabled)(struct scmi_shared_mem __iomem *shmem);
void __iomem *(*setup_iomap)(struct scmi_chan_info *cinfo,
struct device *dev,
bool tx, struct resource *res,
struct scmi_shmem_io_ops **ops);
};
const struct scmi_shared_mem_operations *scmi_shared_mem_operations_get(void);
/* declarations for message passing transports */
struct scmi_msg_payld;
/* Maximum overhead of message w.r.t. struct scmi_desc.max_msg_size */
#define SCMI_MSG_MAX_PROT_OVERHEAD (2 * sizeof(__le32))
/**
* struct scmi_message_operations - Transport core operations for Message
*
* @response_size: Get calculated response size for @xfer
* @command_size: Get calculated command size for @xfer
* @tx_prepare: Prepare the @xfer message for transmission on the provided @msg
* @read_header: Read header of the message currently hold in @msg
* @fetch_response: Copy the message response from @msg into @xfer
* @fetch_notification: Copy the message notification from @msg into @xfer
*/
struct scmi_message_operations {
size_t (*response_size)(struct scmi_xfer *xfer);
size_t (*command_size)(struct scmi_xfer *xfer);
void (*tx_prepare)(struct scmi_msg_payld *msg, struct scmi_xfer *xfer);
u32 (*read_header)(struct scmi_msg_payld *msg);
void (*fetch_response)(struct scmi_msg_payld *msg, size_t len,
struct scmi_xfer *xfer);
void (*fetch_notification)(struct scmi_msg_payld *msg, size_t len,
size_t max_len, struct scmi_xfer *xfer);
};
const struct scmi_message_operations *scmi_message_operations_get(void);
/**
* struct scmi_transport_core_operations - Transpoert core operations
*
* @bad_message_trace: An helper to report a malformed/unexpected message
* @rx_callback: Callback to report received messages
* @shmem: Datagram operations for shared memory based transports
* @msg: Datagram operations for message based transports
*/
struct scmi_transport_core_operations {
void (*bad_message_trace)(struct scmi_chan_info *cinfo,
u32 msg_hdr, enum scmi_bad_msg err);
void (*rx_callback)(struct scmi_chan_info *cinfo, u32 msg_hdr,
void *priv);
const struct scmi_shared_mem_operations *shmem;
const struct scmi_message_operations *msg;
};
/**
* struct scmi_transport_handle - Transport instance handle
* @supplier_get: A helper to retrieve the device descriptor, identifying the
* transport driver serving this SCMI instance, which will be
* used as a supplier for the core SCMI driver: returning an
* error here causes the probe sequence to be interrupted and
* return that same error code, so that each transport can decide
* which policy to implement by choosing an appropriate error.
* @supplier_put: A helper to signal that the specified transport supplier is
* no more being used and it is made available again.
*
* Note that these helpers are needed and provided only by those transports
* whose initialization relies on some other subsystem and whose relations to
* the core SCMI driver is not tracked by firmware descriptions.
*/
struct scmi_transport_handle {
struct device __must_check *(*supplier_get)
(const struct scmi_transport_handle *th);
int (*supplier_put)(const struct scmi_transport_handle *th,
struct device *dev);
};
/**
* struct scmi_transport - A structure representing a configured transport
*
* @supplier: Device representing the transport and acting as a supplier for
* the core SCMI stack
* @desc: Transport descriptor
* @core_ops: A pointer to a pointer used by the core SCMI stack to make the
* core transport operations accessible to the transports.
* @th: An optional pointer to the transport handle
*/
struct scmi_transport {
struct device *supplier;
struct scmi_desc desc;
struct scmi_transport_core_operations **core_ops;
const struct scmi_transport_handle *th;
};
/**
* struct scmi_transport_supplier - Transport descriptor
* @mtx: A mutex to protect @available
* @available: A reference to an initialized transport device, when available.
* This reference is implicitly used to track the status of the
* supplier and it can cycle through the following 3 states:
* 1. NOT_READY - PTR_ERR(-EPROBE_DEFER): no supplier available;
* this is the transport initial state.
* 2. AVAILABLE - <supplier_dev>: a transport supplier has been
* initialized and it is available, ready to use.
* 3. BUSY _ PTR_ERR(-EBUSY): transport supplier is currently in use.
* @th: An embedded transport handle object that embeds the helpers
* implementing the above mentioned logic
*
* Note that this transport driver enforces single instance probing.
*/
struct scmi_transport_supplier {
/* Protect @available */
struct mutex mtx;
struct device *available;
const struct scmi_transport_handle th;
};
#define to_sup(t) container_of(t, struct scmi_transport_supplier, th)
/**
* scmi_transport_supplier_put - A helper to dispose of a supplier
* @th: A reference to the transport handle to use
* @supplier: A reference to the device supplier to manage, cannot be NULL
* or ERR_PTR.
*
* Note that putting a supplier will have different effect based on the
* current state of scmi_transport_supplier.available:
* - NOT_READY/BUSY: @supplier will be set as the new available device: this
* can be used to made available a supplier OR stop using one.
* - AVAILABLE: if the @supplier we are disposing of matches the currently
* available one, roll back to NOT_READY state.
* Any other attempt to override an available supplier with a
* new one is rejected, effectively enforcing one single supplier.
*
* Return: 0 on Success, errno otherwise.
*/
static inline int
scmi_transport_supplier_put(const struct scmi_transport_handle *th,
struct device *supplier)
{
struct scmi_transport_supplier *sup = to_sup(th);
/* Nothing to do when the provided supplier was never real */
if (IS_ERR_OR_NULL(supplier))
return 0;
guard(mutex)(&sup->mtx);
switch (PTR_ERR_OR_ZERO(sup->available)) {
case -EPROBE_DEFER:
case -EBUSY:
sup->available = supplier;
break;
case 0:
/* Putting a supplier when in the AVAILABLE state causes a
* transition back to the NOT_READY state, BUT only if the
* supplier we are disposing of was exactly the device that was
* previously made readily available.
*/
if (supplier != sup->available)
return -EINVAL;
sup->available = ERR_PTR(-EPROBE_DEFER);
break;
default:
return -EINVAL;
}
return 0;
}
/**
* scmi_transport_supplier_get - A helper to get hold of a supplier
* @th: A reference to the transport handle to use
*
* Note that, trying to get a supplier device can return:
* - a ready to use supplier device, (subsequently made unavailable)
* - PTR_ERR(-EPROBE_DEFER): no supplier is available
* - PTR_ERR(-BUSY): supplier was already taken by a previous get
*
* This allows the probe to defer and wait when a possible device can
* be reasonably expected to appear.
*
* Return: a usable supplier device on Success or PTR_ERR on Failure.
*/
static inline struct device *
scmi_transport_supplier_get(const struct scmi_transport_handle *th)
{
struct scmi_transport_supplier *sup = to_sup(th);
struct device *supplier;
guard(mutex)(&sup->mtx);
supplier = sup->available;
if (!IS_ERR(sup->available))
sup->available = ERR_PTR(-EBUSY);
return supplier;
}
#define DEFINE_SCMI_TRANSPORT_SUPPLIER(__supplier) \
struct scmi_transport_supplier __supplier = { \
.mtx = __MUTEX_INITIALIZER(__supplier.mtx), \
.available = INIT_ERR_PTR(-EPROBE_DEFER), \
.th.supplier_get = scmi_transport_supplier_get, \
.th.supplier_put = scmi_transport_supplier_put, \
}
#define DEFINE_SCMI_TRANSPORT_DRIVER(__tag, __drv, __desc, __match, __core_ops)\
static void __tag##_dev_free(void *data) \
{ \
struct platform_device *spdev = data; \
struct scmi_transport *strans; \
\
strans = dev_get_platdata(&spdev->dev); \
if (strans && strans->th) \
strans->th->supplier_put(strans->th, strans->supplier); \
\
platform_device_unregister(spdev); \
} \
\
static int __tag##_probe(struct platform_device *pdev) \
{ \
struct device *dev = &pdev->dev, *supplier; \
struct platform_device *spdev; \
struct scmi_transport strans; \
int ret; \
\
supplier = dev; \
strans.th = device_get_match_data(dev); \
if (strans.th) { \
supplier = strans.th->supplier_get(strans.th); \
if (IS_ERR(supplier)) \
return PTR_ERR(supplier); \
} \
\
spdev = platform_device_alloc("arm-scmi", PLATFORM_DEVID_AUTO); \
if (!spdev) { \
ret = -ENOMEM; \
goto err_mem; \
} \
\
device_set_of_node_from_dev(&spdev->dev, dev); \
\
strans.supplier = supplier; \
memcpy(&strans.desc, &(__desc), sizeof(strans.desc)); \
strans.core_ops = &(__core_ops); \
\
ret = platform_device_add_data(spdev, &strans, sizeof(strans)); \
if (ret) \
goto err; \
\
spdev->dev.parent = dev; \
ret = platform_device_add(spdev); \
if (ret) \
goto err; \
\
return devm_add_action_or_reset(dev, __tag##_dev_free, spdev); \
\
err: \
platform_device_put(spdev); \
err_mem: \
if (strans.th) \
strans.th->supplier_put(strans.th, supplier); \
\
return ret; \
} \
\
static struct platform_driver __drv = { \
.driver = { \
.name = #__tag "_transport", \
.of_match_table = __match, \
}, \
.probe = __tag##_probe, \
}
void scmi_notification_instance_data_set(const struct scmi_handle *handle,
void *priv);
void *scmi_notification_instance_data_get(const struct scmi_handle *handle);
int scmi_inflight_count(const struct scmi_handle *handle);
#endif /* _SCMI_COMMON_H */
File diff suppressed because it is too large Load Diff
+125
View File
@@ -0,0 +1,125 @@
// SPDX-License-Identifier: GPL-2.0
/*
* For transports using message passing.
*
* Derived from shm.c.
*
* Copyright (C) 2019-2024 ARM Ltd.
* Copyright (C) 2020-2021 OpenSynergy GmbH
*/
#include <linux/types.h>
#include "common.h"
/*
* struct scmi_msg_payld - Transport SDU layout
*
* The SCMI specification requires all parameters, message headers, return
* arguments or any protocol data to be expressed in little endian format only.
*/
struct scmi_msg_payld {
__le32 msg_header;
__le32 msg_payload[];
};
/**
* msg_command_size() - Actual size of transport SDU for command.
*
* @xfer: message which core has prepared for sending
*
* Return: transport SDU size.
*/
static size_t msg_command_size(struct scmi_xfer *xfer)
{
return sizeof(struct scmi_msg_payld) + xfer->tx.len;
}
/**
* msg_response_size() - Maximum size of transport SDU for response.
*
* @xfer: message which core has prepared for sending
*
* Return: transport SDU size.
*/
static size_t msg_response_size(struct scmi_xfer *xfer)
{
return sizeof(struct scmi_msg_payld) + sizeof(__le32) + xfer->rx.len;
}
/**
* msg_tx_prepare() - Set up transport SDU for command.
*
* @msg: transport SDU for command
* @xfer: message which is being sent
*/
static void msg_tx_prepare(struct scmi_msg_payld *msg, struct scmi_xfer *xfer)
{
msg->msg_header = cpu_to_le32(pack_scmi_header(&xfer->hdr));
if (xfer->tx.buf)
memcpy(msg->msg_payload, xfer->tx.buf, xfer->tx.len);
}
/**
* msg_read_header() - Read SCMI header from transport SDU.
*
* @msg: transport SDU
*
* Return: SCMI header
*/
static u32 msg_read_header(struct scmi_msg_payld *msg)
{
return le32_to_cpu(msg->msg_header);
}
/**
* msg_fetch_response() - Fetch response SCMI payload from transport SDU.
*
* @msg: transport SDU with response
* @len: transport SDU size
* @xfer: message being responded to
*/
static void msg_fetch_response(struct scmi_msg_payld *msg,
size_t len, struct scmi_xfer *xfer)
{
size_t prefix_len = sizeof(*msg) + sizeof(msg->msg_payload[0]);
xfer->hdr.status = le32_to_cpu(msg->msg_payload[0]);
xfer->rx.len = min_t(size_t, xfer->rx.len,
len >= prefix_len ? len - prefix_len : 0);
/* Take a copy to the rx buffer.. */
memcpy(xfer->rx.buf, &msg->msg_payload[1], xfer->rx.len);
}
/**
* msg_fetch_notification() - Fetch notification payload from transport SDU.
*
* @msg: transport SDU with notification
* @len: transport SDU size
* @max_len: maximum SCMI payload size to fetch
* @xfer: notification message
*/
static void msg_fetch_notification(struct scmi_msg_payld *msg, size_t len,
size_t max_len, struct scmi_xfer *xfer)
{
xfer->rx.len = min_t(size_t, max_len,
len >= sizeof(*msg) ? len - sizeof(*msg) : 0);
/* Take a copy to the rx buffer.. */
memcpy(xfer->rx.buf, msg->msg_payload, xfer->rx.len);
}
static const struct scmi_message_operations scmi_msg_ops = {
.tx_prepare = msg_tx_prepare,
.command_size = msg_command_size,
.response_size = msg_response_size,
.read_header = msg_read_header,
.fetch_response = msg_fetch_response,
.fetch_notification = msg_fetch_notification,
};
const struct scmi_message_operations *scmi_message_operations_get(void)
{
return &scmi_msg_ops;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,94 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* System Control and Management Interface (SCMI) Message Protocol
* notification header file containing some definitions, structures
* and function prototypes related to SCMI Notification handling.
*
* Copyright (C) 2020-2021 ARM Ltd.
*/
#ifndef _SCMI_NOTIFY_H
#define _SCMI_NOTIFY_H
#include <linux/device.h>
#include <linux/ktime.h>
#include <linux/types.h>
#define SCMI_PROTO_QUEUE_SZ 4096
/**
* struct scmi_event - Describes an event to be supported
* @id: Event ID
* @max_payld_sz: Max possible size for the payload of a notification message
* @max_report_sz: Max possible size for the report of a notification message
*
* Each SCMI protocol, during its initialization phase, can describe the events
* it wishes to support in a few struct scmi_event and pass them to the core
* using scmi_register_protocol_events().
*/
struct scmi_event {
u8 id;
size_t max_payld_sz;
size_t max_report_sz;
};
struct scmi_protocol_handle;
/**
* struct scmi_event_ops - Protocol helpers called by the notification core.
* @is_notify_supported: Return 0 if the specified notification for the
* specified resource (src_id) is supported.
* @get_num_sources: Returns the number of possible events' sources for this
* protocol
* @set_notify_enabled: Enable/disable the required evt_id/src_id notifications
* using the proper custom protocol commands.
* Return 0 on Success
* @fill_custom_report: fills a custom event report from the provided
* event message payld identifying the event
* specific src_id.
* Return NULL on failure otherwise @report now fully
* populated
*
* Context: Helpers described in &struct scmi_event_ops are called only in
* process context.
*/
struct scmi_event_ops {
bool (*is_notify_supported)(const struct scmi_protocol_handle *ph,
u8 evt_id, u32 src_id);
int (*get_num_sources)(const struct scmi_protocol_handle *ph);
int (*set_notify_enabled)(const struct scmi_protocol_handle *ph,
u8 evt_id, u32 src_id, bool enabled);
void *(*fill_custom_report)(const struct scmi_protocol_handle *ph,
u8 evt_id, ktime_t timestamp,
const void *payld, size_t payld_sz,
void *report, u32 *src_id);
};
/**
* struct scmi_protocol_events - Per-protocol description of available events
* @queue_sz: Size in bytes of the per-protocol queue to use.
* @ops: Array of protocol-specific events operations.
* @evts: Array of supported protocol's events.
* @num_events: Number of supported protocol's events described in @evts.
* @num_sources: Number of protocol's sources, should be greater than 0; if not
* available at compile time, it will be provided at run-time via
* @get_num_sources.
*/
struct scmi_protocol_events {
size_t queue_sz;
const struct scmi_event_ops *ops;
const struct scmi_event *evts;
unsigned int num_events;
unsigned int num_sources;
};
int scmi_notification_init(struct scmi_handle *handle);
void scmi_notification_exit(struct scmi_handle *handle);
int scmi_register_protocol_events(const struct scmi_handle *handle, u8 proto_id,
const struct scmi_protocol_handle *ph,
const struct scmi_protocol_events *ee);
void scmi_deregister_protocol_events(const struct scmi_handle *handle,
u8 proto_id);
int scmi_notify(const struct scmi_handle *handle, u8 proto_id, u8 evt_id,
const void *buf, size_t len, ktime_t ts);
#endif /* _SCMI_NOTIFY_H */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,899 @@
// SPDX-License-Identifier: GPL-2.0
/*
* System Control and Management Interface (SCMI) Pinctrl Protocol
*
* Copyright (C) 2024 EPAM
* Copyright 2024 NXP
*/
#include <asm/byteorder.h>
#include <linux/bits.h>
#include <linux/bitfield.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/scmi_protocol.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/types.h>
#include "common.h"
#include "protocols.h"
/* Updated only after ALL the mandatory features for that version are merged */
#define SCMI_PROTOCOL_SUPPORTED_VERSION 0x10000
#define GET_GROUPS_NR(x) le32_get_bits((x), GENMASK(31, 16))
#define GET_PINS_NR(x) le32_get_bits((x), GENMASK(15, 0))
#define GET_FUNCTIONS_NR(x) le32_get_bits((x), GENMASK(15, 0))
#define EXT_NAME_FLAG(x) le32_get_bits((x), BIT(31))
#define NUM_ELEMS(x) le32_get_bits((x), GENMASK(15, 0))
#define REMAINING(x) le32_get_bits((x), GENMASK(31, 16))
#define RETURNED(x) le32_get_bits((x), GENMASK(11, 0))
#define CONFIG_FLAG_MASK GENMASK(19, 18)
#define SELECTOR_MASK GENMASK(17, 16)
#define SKIP_CONFIGS_MASK GENMASK(15, 8)
#define CONFIG_TYPE_MASK GENMASK(7, 0)
enum scmi_pinctrl_protocol_cmd {
PINCTRL_ATTRIBUTES = 0x3,
PINCTRL_LIST_ASSOCIATIONS = 0x4,
PINCTRL_SETTINGS_GET = 0x5,
PINCTRL_SETTINGS_CONFIGURE = 0x6,
PINCTRL_REQUEST = 0x7,
PINCTRL_RELEASE = 0x8,
PINCTRL_NAME_GET = 0x9,
PINCTRL_SET_PERMISSIONS = 0xa,
};
struct scmi_msg_settings_conf {
__le32 identifier;
__le32 function_id;
__le32 attributes;
__le32 configs[];
};
struct scmi_msg_settings_get {
__le32 identifier;
__le32 attributes;
};
struct scmi_resp_settings_get {
__le32 function_selected;
__le32 num_configs;
__le32 configs[];
};
struct scmi_msg_pinctrl_protocol_attributes {
__le32 attributes_low;
__le32 attributes_high;
};
struct scmi_msg_pinctrl_attributes {
__le32 identifier;
__le32 flags;
};
struct scmi_resp_pinctrl_attributes {
__le32 attributes;
u8 name[SCMI_SHORT_NAME_MAX_SIZE];
};
struct scmi_msg_pinctrl_list_assoc {
__le32 identifier;
__le32 flags;
__le32 index;
};
struct scmi_resp_pinctrl_list_assoc {
__le32 flags;
__le16 array[];
};
struct scmi_msg_request {
__le32 identifier;
__le32 flags;
};
struct scmi_group_info {
char name[SCMI_MAX_STR_SIZE];
bool present;
u32 *group_pins;
u32 nr_pins;
};
struct scmi_function_info {
char name[SCMI_MAX_STR_SIZE];
bool present;
u32 *groups;
u32 nr_groups;
};
struct scmi_pin_info {
char name[SCMI_MAX_STR_SIZE];
bool present;
};
struct scmi_pinctrl_info {
int nr_groups;
int nr_functions;
int nr_pins;
struct scmi_group_info *groups;
struct scmi_function_info *functions;
struct scmi_pin_info *pins;
};
static int scmi_pinctrl_attributes_get(const struct scmi_protocol_handle *ph,
struct scmi_pinctrl_info *pi)
{
int ret;
struct scmi_xfer *t;
struct scmi_msg_pinctrl_protocol_attributes *attr;
ret = ph->xops->xfer_get_init(ph, PROTOCOL_ATTRIBUTES, 0, sizeof(*attr), &t);
if (ret)
return ret;
attr = t->rx.buf;
ret = ph->xops->do_xfer(ph, t);
if (!ret) {
pi->nr_functions = GET_FUNCTIONS_NR(attr->attributes_high);
pi->nr_groups = GET_GROUPS_NR(attr->attributes_low);
pi->nr_pins = GET_PINS_NR(attr->attributes_low);
if (pi->nr_pins == 0) {
dev_warn(ph->dev, "returned zero pins\n");
ret = -EINVAL;
}
}
ph->xops->xfer_put(ph, t);
return ret;
}
static int scmi_pinctrl_count_get(const struct scmi_protocol_handle *ph,
enum scmi_pinctrl_selector_type type)
{
struct scmi_pinctrl_info *pi = ph->get_priv(ph);
switch (type) {
case PIN_TYPE:
return pi->nr_pins;
case GROUP_TYPE:
return pi->nr_groups;
case FUNCTION_TYPE:
return pi->nr_functions;
default:
return -EINVAL;
}
}
static int scmi_pinctrl_validate_id(const struct scmi_protocol_handle *ph,
u32 selector,
enum scmi_pinctrl_selector_type type)
{
int value;
value = scmi_pinctrl_count_get(ph, type);
if (value < 0)
return value;
if (selector >= value || value == 0)
return -EINVAL;
return 0;
}
static int scmi_pinctrl_attributes(const struct scmi_protocol_handle *ph,
enum scmi_pinctrl_selector_type type,
u32 selector, char *name,
u32 *n_elems)
{
int ret;
struct scmi_xfer *t;
struct scmi_msg_pinctrl_attributes *tx;
struct scmi_resp_pinctrl_attributes *rx;
bool ext_name_flag;
if (!name)
return -EINVAL;
ret = scmi_pinctrl_validate_id(ph, selector, type);
if (ret)
return ret;
ret = ph->xops->xfer_get_init(ph, PINCTRL_ATTRIBUTES, sizeof(*tx),
sizeof(*rx), &t);
if (ret)
return ret;
tx = t->tx.buf;
rx = t->rx.buf;
tx->identifier = cpu_to_le32(selector);
tx->flags = cpu_to_le32(type);
ret = ph->xops->do_xfer(ph, t);
if (!ret) {
if (n_elems)
*n_elems = NUM_ELEMS(rx->attributes);
strscpy(name, rx->name, SCMI_SHORT_NAME_MAX_SIZE);
ext_name_flag = !!EXT_NAME_FLAG(rx->attributes);
}
ph->xops->xfer_put(ph, t);
if (ret)
return ret;
/*
* If supported overwrite short name with the extended one;
* on error just carry on and use already provided short name.
*/
if (ext_name_flag)
ret = ph->hops->extended_name_get(ph, PINCTRL_NAME_GET,
selector, (u32 *)&type, name,
SCMI_MAX_STR_SIZE);
return ret;
}
struct scmi_pinctrl_ipriv {
u32 selector;
enum scmi_pinctrl_selector_type type;
u32 *array;
};
static void iter_pinctrl_assoc_prepare_message(void *message,
u32 desc_index,
const void *priv)
{
struct scmi_msg_pinctrl_list_assoc *msg = message;
const struct scmi_pinctrl_ipriv *p = priv;
msg->identifier = cpu_to_le32(p->selector);
msg->flags = cpu_to_le32(p->type);
msg->index = cpu_to_le32(desc_index);
}
static int iter_pinctrl_assoc_update_state(struct scmi_iterator_state *st,
const void *response, void *priv)
{
const struct scmi_resp_pinctrl_list_assoc *r = response;
st->num_returned = RETURNED(r->flags);
st->num_remaining = REMAINING(r->flags);
return 0;
}
static int
iter_pinctrl_assoc_process_response(const struct scmi_protocol_handle *ph,
const void *response,
struct scmi_iterator_state *st, void *priv)
{
const struct scmi_resp_pinctrl_list_assoc *r = response;
struct scmi_pinctrl_ipriv *p = priv;
p->array[st->desc_index + st->loop_idx] =
le16_to_cpu(r->array[st->loop_idx]);
return 0;
}
static int scmi_pinctrl_list_associations(const struct scmi_protocol_handle *ph,
u32 selector,
enum scmi_pinctrl_selector_type type,
u16 size, u32 *array)
{
int ret;
void *iter;
struct scmi_iterator_ops ops = {
.prepare_message = iter_pinctrl_assoc_prepare_message,
.update_state = iter_pinctrl_assoc_update_state,
.process_response = iter_pinctrl_assoc_process_response,
};
struct scmi_pinctrl_ipriv ipriv = {
.selector = selector,
.type = type,
.array = array,
};
if (!array || !size || type == PIN_TYPE)
return -EINVAL;
ret = scmi_pinctrl_validate_id(ph, selector, type);
if (ret)
return ret;
iter = ph->hops->iter_response_init(ph, &ops, size,
PINCTRL_LIST_ASSOCIATIONS,
sizeof(struct scmi_msg_pinctrl_list_assoc),
&ipriv);
if (IS_ERR(iter))
return PTR_ERR(iter);
return ph->hops->iter_response_run(iter);
}
struct scmi_settings_get_ipriv {
u32 selector;
enum scmi_pinctrl_selector_type type;
bool get_all;
unsigned int *nr_configs;
enum scmi_pinctrl_conf_type *config_types;
u32 *config_values;
};
static void
iter_pinctrl_settings_get_prepare_message(void *message, u32 desc_index,
const void *priv)
{
struct scmi_msg_settings_get *msg = message;
const struct scmi_settings_get_ipriv *p = priv;
u32 attributes;
attributes = FIELD_PREP(SELECTOR_MASK, p->type);
if (p->get_all) {
attributes |= FIELD_PREP(CONFIG_FLAG_MASK, 1) |
FIELD_PREP(SKIP_CONFIGS_MASK, desc_index);
} else {
attributes |= FIELD_PREP(CONFIG_TYPE_MASK, p->config_types[0]);
}
msg->attributes = cpu_to_le32(attributes);
msg->identifier = cpu_to_le32(p->selector);
}
static int
iter_pinctrl_settings_get_update_state(struct scmi_iterator_state *st,
const void *response, void *priv)
{
const struct scmi_resp_settings_get *r = response;
struct scmi_settings_get_ipriv *p = priv;
if (p->get_all) {
st->num_returned = le32_get_bits(r->num_configs, GENMASK(7, 0));
st->num_remaining = le32_get_bits(r->num_configs, GENMASK(31, 24));
} else {
st->num_returned = 1;
st->num_remaining = 0;
}
return 0;
}
static int
iter_pinctrl_settings_get_process_response(const struct scmi_protocol_handle *ph,
const void *response,
struct scmi_iterator_state *st,
void *priv)
{
const struct scmi_resp_settings_get *r = response;
struct scmi_settings_get_ipriv *p = priv;
u32 type = le32_get_bits(r->configs[st->loop_idx * 2], GENMASK(7, 0));
u32 val = le32_to_cpu(r->configs[st->loop_idx * 2 + 1]);
if (p->get_all) {
p->config_types[st->desc_index + st->loop_idx] = type;
} else {
if (p->config_types[0] != type)
return -EINVAL;
}
p->config_values[st->desc_index + st->loop_idx] = val;
++*p->nr_configs;
return 0;
}
static int
scmi_pinctrl_settings_get(const struct scmi_protocol_handle *ph, u32 selector,
enum scmi_pinctrl_selector_type type,
unsigned int *nr_configs,
enum scmi_pinctrl_conf_type *config_types,
u32 *config_values)
{
int ret;
void *iter;
unsigned int max_configs = *nr_configs;
struct scmi_iterator_ops ops = {
.prepare_message = iter_pinctrl_settings_get_prepare_message,
.update_state = iter_pinctrl_settings_get_update_state,
.process_response = iter_pinctrl_settings_get_process_response,
};
struct scmi_settings_get_ipriv ipriv = {
.selector = selector,
.type = type,
.get_all = (max_configs > 1),
.nr_configs = nr_configs,
.config_types = config_types,
.config_values = config_values,
};
if (!config_types || !config_values || type == FUNCTION_TYPE)
return -EINVAL;
ret = scmi_pinctrl_validate_id(ph, selector, type);
if (ret)
return ret;
/* Prepare to count returned configs */
*nr_configs = 0;
iter = ph->hops->iter_response_init(ph, &ops, max_configs,
PINCTRL_SETTINGS_GET,
sizeof(struct scmi_msg_settings_get),
&ipriv);
if (IS_ERR(iter))
return PTR_ERR(iter);
return ph->hops->iter_response_run(iter);
}
static int scmi_pinctrl_settings_get_one(const struct scmi_protocol_handle *ph,
u32 selector,
enum scmi_pinctrl_selector_type type,
enum scmi_pinctrl_conf_type config_type,
u32 *config_value)
{
unsigned int nr_configs = 1;
return scmi_pinctrl_settings_get(ph, selector, type, &nr_configs,
&config_type, config_value);
}
static int scmi_pinctrl_settings_get_all(const struct scmi_protocol_handle *ph,
u32 selector,
enum scmi_pinctrl_selector_type type,
unsigned int *nr_configs,
enum scmi_pinctrl_conf_type *config_types,
u32 *config_values)
{
if (!nr_configs || *nr_configs == 0)
return -EINVAL;
return scmi_pinctrl_settings_get(ph, selector, type, nr_configs,
config_types, config_values);
}
static int
scmi_pinctrl_settings_conf(const struct scmi_protocol_handle *ph,
u32 selector,
enum scmi_pinctrl_selector_type type,
u32 nr_configs,
enum scmi_pinctrl_conf_type *config_type,
u32 *config_value)
{
struct scmi_xfer *t;
struct scmi_msg_settings_conf *tx;
u32 attributes;
int ret, i;
u32 configs_in_chunk, conf_num = 0;
u32 chunk;
int max_msg_size = ph->hops->get_max_msg_size(ph);
if (!config_type || !config_value || type == FUNCTION_TYPE)
return -EINVAL;
ret = scmi_pinctrl_validate_id(ph, selector, type);
if (ret)
return ret;
configs_in_chunk = (max_msg_size - sizeof(*tx)) / (sizeof(__le32) * 2);
while (conf_num < nr_configs) {
chunk = (nr_configs - conf_num > configs_in_chunk) ?
configs_in_chunk : nr_configs - conf_num;
ret = ph->xops->xfer_get_init(ph, PINCTRL_SETTINGS_CONFIGURE,
sizeof(*tx) +
chunk * 2 * sizeof(__le32), 0, &t);
if (ret)
break;
tx = t->tx.buf;
tx->identifier = cpu_to_le32(selector);
tx->function_id = cpu_to_le32(0xFFFFFFFF);
attributes = FIELD_PREP(GENMASK(1, 0), type) |
FIELD_PREP(GENMASK(9, 2), chunk);
tx->attributes = cpu_to_le32(attributes);
for (i = 0; i < chunk; i++) {
tx->configs[i * 2] =
cpu_to_le32(config_type[conf_num + i]);
tx->configs[i * 2 + 1] =
cpu_to_le32(config_value[conf_num + i]);
}
ret = ph->xops->do_xfer(ph, t);
ph->xops->xfer_put(ph, t);
if (ret)
break;
conf_num += chunk;
}
return ret;
}
static int scmi_pinctrl_function_select(const struct scmi_protocol_handle *ph,
u32 group,
enum scmi_pinctrl_selector_type type,
u32 function_id)
{
int ret;
struct scmi_xfer *t;
struct scmi_msg_settings_conf *tx;
u32 attributes;
ret = scmi_pinctrl_validate_id(ph, group, type);
if (ret)
return ret;
ret = ph->xops->xfer_get_init(ph, PINCTRL_SETTINGS_CONFIGURE,
sizeof(*tx), 0, &t);
if (ret)
return ret;
tx = t->tx.buf;
tx->identifier = cpu_to_le32(group);
tx->function_id = cpu_to_le32(function_id);
attributes = FIELD_PREP(GENMASK(1, 0), type) | BIT(10);
tx->attributes = cpu_to_le32(attributes);
ret = ph->xops->do_xfer(ph, t);
ph->xops->xfer_put(ph, t);
return ret;
}
static int scmi_pinctrl_request_free(const struct scmi_protocol_handle *ph,
u32 identifier,
enum scmi_pinctrl_selector_type type,
enum scmi_pinctrl_protocol_cmd cmd)
{
int ret;
struct scmi_xfer *t;
struct scmi_msg_request *tx;
if (type == FUNCTION_TYPE)
return -EINVAL;
if (cmd != PINCTRL_REQUEST && cmd != PINCTRL_RELEASE)
return -EINVAL;
ret = scmi_pinctrl_validate_id(ph, identifier, type);
if (ret)
return ret;
ret = ph->xops->xfer_get_init(ph, cmd, sizeof(*tx), 0, &t);
if (ret)
return ret;
tx = t->tx.buf;
tx->identifier = cpu_to_le32(identifier);
tx->flags = cpu_to_le32(type);
ret = ph->xops->do_xfer(ph, t);
if (ret == -EOPNOTSUPP)
ret = 0;
ph->xops->xfer_put(ph, t);
return ret;
}
static int scmi_pinctrl_pin_request(const struct scmi_protocol_handle *ph,
u32 pin)
{
return scmi_pinctrl_request_free(ph, pin, PIN_TYPE, PINCTRL_REQUEST);
}
static int scmi_pinctrl_pin_free(const struct scmi_protocol_handle *ph, u32 pin)
{
return scmi_pinctrl_request_free(ph, pin, PIN_TYPE, PINCTRL_RELEASE);
}
static int scmi_pinctrl_get_group_info(const struct scmi_protocol_handle *ph,
u32 selector)
{
struct scmi_pinctrl_info *pi = ph->get_priv(ph);
struct scmi_group_info *group;
int ret;
if (selector >= pi->nr_groups)
return -EINVAL;
group = &pi->groups[selector];
if (group->present)
return 0;
ret = scmi_pinctrl_attributes(ph, GROUP_TYPE, selector, group->name,
&group->nr_pins);
if (ret)
return ret;
if (!group->nr_pins) {
dev_err(ph->dev, "Group %d has 0 elements", selector);
return -ENODATA;
}
group->group_pins = kmalloc_array(group->nr_pins,
sizeof(*group->group_pins),
GFP_KERNEL);
if (!group->group_pins)
return -ENOMEM;
ret = scmi_pinctrl_list_associations(ph, selector, GROUP_TYPE,
group->nr_pins, group->group_pins);
if (ret) {
kfree(group->group_pins);
return ret;
}
group->present = true;
return 0;
}
static int scmi_pinctrl_get_group_name(const struct scmi_protocol_handle *ph,
u32 selector, const char **name)
{
struct scmi_pinctrl_info *pi = ph->get_priv(ph);
int ret;
if (!name)
return -EINVAL;
ret = scmi_pinctrl_get_group_info(ph, selector);
if (ret)
return ret;
*name = pi->groups[selector].name;
return 0;
}
static int scmi_pinctrl_group_pins_get(const struct scmi_protocol_handle *ph,
u32 selector, const u32 **pins,
u32 *nr_pins)
{
struct scmi_pinctrl_info *pi = ph->get_priv(ph);
int ret;
if (!pins || !nr_pins)
return -EINVAL;
ret = scmi_pinctrl_get_group_info(ph, selector);
if (ret)
return ret;
*pins = pi->groups[selector].group_pins;
*nr_pins = pi->groups[selector].nr_pins;
return 0;
}
static int scmi_pinctrl_get_function_info(const struct scmi_protocol_handle *ph,
u32 selector)
{
struct scmi_pinctrl_info *pi = ph->get_priv(ph);
struct scmi_function_info *func;
int ret;
if (selector >= pi->nr_functions)
return -EINVAL;
func = &pi->functions[selector];
if (func->present)
return 0;
ret = scmi_pinctrl_attributes(ph, FUNCTION_TYPE, selector, func->name,
&func->nr_groups);
if (ret)
return ret;
if (!func->nr_groups) {
dev_err(ph->dev, "Function %d has 0 elements", selector);
return -ENODATA;
}
func->groups = kmalloc_array(func->nr_groups, sizeof(*func->groups),
GFP_KERNEL);
if (!func->groups)
return -ENOMEM;
ret = scmi_pinctrl_list_associations(ph, selector, FUNCTION_TYPE,
func->nr_groups, func->groups);
if (ret) {
kfree(func->groups);
return ret;
}
func->present = true;
return 0;
}
static int scmi_pinctrl_get_function_name(const struct scmi_protocol_handle *ph,
u32 selector, const char **name)
{
struct scmi_pinctrl_info *pi = ph->get_priv(ph);
int ret;
if (!name)
return -EINVAL;
ret = scmi_pinctrl_get_function_info(ph, selector);
if (ret)
return ret;
*name = pi->functions[selector].name;
return 0;
}
static int
scmi_pinctrl_function_groups_get(const struct scmi_protocol_handle *ph,
u32 selector, u32 *nr_groups,
const u32 **groups)
{
struct scmi_pinctrl_info *pi = ph->get_priv(ph);
int ret;
if (!groups || !nr_groups)
return -EINVAL;
ret = scmi_pinctrl_get_function_info(ph, selector);
if (ret)
return ret;
*groups = pi->functions[selector].groups;
*nr_groups = pi->functions[selector].nr_groups;
return 0;
}
static int scmi_pinctrl_mux_set(const struct scmi_protocol_handle *ph,
u32 selector, u32 group)
{
return scmi_pinctrl_function_select(ph, group, GROUP_TYPE, selector);
}
static int scmi_pinctrl_get_pin_info(const struct scmi_protocol_handle *ph,
u32 selector)
{
struct scmi_pinctrl_info *pi = ph->get_priv(ph);
struct scmi_pin_info *pin;
int ret;
if (selector >= pi->nr_pins)
return -EINVAL;
pin = &pi->pins[selector];
if (pin->present)
return 0;
ret = scmi_pinctrl_attributes(ph, PIN_TYPE, selector, pin->name, NULL);
if (ret)
return ret;
pin->present = true;
return 0;
}
static int scmi_pinctrl_get_pin_name(const struct scmi_protocol_handle *ph,
u32 selector, const char **name)
{
struct scmi_pinctrl_info *pi = ph->get_priv(ph);
int ret;
if (!name)
return -EINVAL;
ret = scmi_pinctrl_get_pin_info(ph, selector);
if (ret)
return ret;
*name = pi->pins[selector].name;
return 0;
}
static int scmi_pinctrl_name_get(const struct scmi_protocol_handle *ph,
u32 selector,
enum scmi_pinctrl_selector_type type,
const char **name)
{
switch (type) {
case PIN_TYPE:
return scmi_pinctrl_get_pin_name(ph, selector, name);
case GROUP_TYPE:
return scmi_pinctrl_get_group_name(ph, selector, name);
case FUNCTION_TYPE:
return scmi_pinctrl_get_function_name(ph, selector, name);
default:
return -EINVAL;
}
}
static const struct scmi_pinctrl_proto_ops pinctrl_proto_ops = {
.count_get = scmi_pinctrl_count_get,
.name_get = scmi_pinctrl_name_get,
.group_pins_get = scmi_pinctrl_group_pins_get,
.function_groups_get = scmi_pinctrl_function_groups_get,
.mux_set = scmi_pinctrl_mux_set,
.settings_get_one = scmi_pinctrl_settings_get_one,
.settings_get_all = scmi_pinctrl_settings_get_all,
.settings_conf = scmi_pinctrl_settings_conf,
.pin_request = scmi_pinctrl_pin_request,
.pin_free = scmi_pinctrl_pin_free,
};
static int scmi_pinctrl_protocol_init(const struct scmi_protocol_handle *ph)
{
int ret;
struct scmi_pinctrl_info *pinfo;
dev_dbg(ph->dev, "Pinctrl Version %d.%d\n",
PROTOCOL_REV_MAJOR(ph->version), PROTOCOL_REV_MINOR(ph->version));
pinfo = devm_kzalloc(ph->dev, sizeof(*pinfo), GFP_KERNEL);
if (!pinfo)
return -ENOMEM;
ret = scmi_pinctrl_attributes_get(ph, pinfo);
if (ret)
return ret;
pinfo->pins = devm_kcalloc(ph->dev, pinfo->nr_pins,
sizeof(*pinfo->pins), GFP_KERNEL);
if (!pinfo->pins)
return -ENOMEM;
pinfo->groups = devm_kcalloc(ph->dev, pinfo->nr_groups,
sizeof(*pinfo->groups), GFP_KERNEL);
if (!pinfo->groups)
return -ENOMEM;
pinfo->functions = devm_kcalloc(ph->dev, pinfo->nr_functions,
sizeof(*pinfo->functions), GFP_KERNEL);
if (!pinfo->functions)
return -ENOMEM;
return ph->set_priv(ph, pinfo);
}
static int scmi_pinctrl_protocol_deinit(const struct scmi_protocol_handle *ph)
{
int i;
struct scmi_pinctrl_info *pi = ph->get_priv(ph);
/* Free groups_pins allocated in scmi_pinctrl_get_group_info */
for (i = 0; i < pi->nr_groups; i++) {
if (pi->groups[i].present) {
kfree(pi->groups[i].group_pins);
pi->groups[i].present = false;
}
}
/* Free groups allocated in scmi_pinctrl_get_function_info */
for (i = 0; i < pi->nr_functions; i++) {
if (pi->functions[i].present) {
kfree(pi->functions[i].groups);
pi->functions[i].present = false;
}
}
return 0;
}
static const struct scmi_protocol scmi_pinctrl = {
.id = SCMI_PROTOCOL_PINCTRL,
.owner = THIS_MODULE,
.instance_init = &scmi_pinctrl_protocol_init,
.instance_deinit = &scmi_pinctrl_protocol_deinit,
.ops = &pinctrl_proto_ops,
.supported_version = SCMI_PROTOCOL_SUPPORTED_VERSION,
};
DEFINE_SCMI_PROTOCOL_REGISTER_UNREGISTER(pinctrl, scmi_pinctrl)
@@ -0,0 +1,366 @@
// SPDX-License-Identifier: GPL-2.0
/*
* System Control and Management Interface (SCMI) Power Protocol
*
* Copyright (C) 2018-2022 ARM Ltd.
*/
#define pr_fmt(fmt) "SCMI Notifications POWER - " fmt
#include <linux/module.h>
#include <linux/scmi_protocol.h>
#include "protocols.h"
#include "notify.h"
/* Updated only after ALL the mandatory features for that version are merged */
#define SCMI_PROTOCOL_SUPPORTED_VERSION 0x30001
enum scmi_power_protocol_cmd {
POWER_DOMAIN_ATTRIBUTES = 0x3,
POWER_STATE_SET = 0x4,
POWER_STATE_GET = 0x5,
POWER_STATE_NOTIFY = 0x6,
POWER_DOMAIN_NAME_GET = 0x8,
};
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))
#define SUPPORTS_EXTENDED_NAMES(x) ((x) & BIT(27))
u8 name[SCMI_SHORT_NAME_MAX_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 scmi_power_state_notify_payld {
__le32 agent_id;
__le32 domain_id;
__le32 power_state;
};
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 {
bool notify_state_change_cmd;
int num_domains;
u64 stats_addr;
u32 stats_size;
struct power_dom_info *dom_info;
};
static int scmi_power_attributes_get(const struct scmi_protocol_handle *ph,
struct scmi_power_info *pi)
{
int ret;
struct scmi_xfer *t;
struct scmi_msg_resp_power_attributes *attr;
ret = ph->xops->xfer_get_init(ph, PROTOCOL_ATTRIBUTES,
0, sizeof(*attr), &t);
if (ret)
return ret;
attr = t->rx.buf;
ret = ph->xops->do_xfer(ph, 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);
}
ph->xops->xfer_put(ph, t);
if (!ret)
if (!ph->hops->protocol_msg_check(ph, POWER_STATE_NOTIFY, NULL))
pi->notify_state_change_cmd = true;
return ret;
}
static int
scmi_power_domain_attributes_get(const struct scmi_protocol_handle *ph,
u32 domain, struct power_dom_info *dom_info,
bool notify_state_change_cmd)
{
int ret;
u32 flags;
struct scmi_xfer *t;
struct scmi_msg_resp_power_domain_attributes *attr;
ret = ph->xops->xfer_get_init(ph, POWER_DOMAIN_ATTRIBUTES,
sizeof(domain), sizeof(*attr), &t);
if (ret)
return ret;
put_unaligned_le32(domain, t->tx.buf);
attr = t->rx.buf;
ret = ph->xops->do_xfer(ph, t);
if (!ret) {
flags = le32_to_cpu(attr->flags);
if (notify_state_change_cmd)
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);
strscpy(dom_info->name, attr->name, SCMI_SHORT_NAME_MAX_SIZE);
}
ph->xops->xfer_put(ph, t);
/*
* If supported overwrite short name with the extended one;
* on error just carry on and use already provided short name.
*/
if (!ret && PROTOCOL_REV_MAJOR(ph->version) >= 0x3 &&
SUPPORTS_EXTENDED_NAMES(flags)) {
ph->hops->extended_name_get(ph, POWER_DOMAIN_NAME_GET,
domain, NULL, dom_info->name,
SCMI_MAX_STR_SIZE);
}
return ret;
}
static int scmi_power_state_set(const struct scmi_protocol_handle *ph,
u32 domain, u32 state)
{
int ret;
struct scmi_xfer *t;
struct scmi_power_set_state *st;
ret = ph->xops->xfer_get_init(ph, POWER_STATE_SET, 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 = ph->xops->do_xfer(ph, t);
ph->xops->xfer_put(ph, t);
return ret;
}
static int scmi_power_state_get(const struct scmi_protocol_handle *ph,
u32 domain, u32 *state)
{
int ret;
struct scmi_xfer *t;
ret = ph->xops->xfer_get_init(ph, POWER_STATE_GET, sizeof(u32), sizeof(u32), &t);
if (ret)
return ret;
put_unaligned_le32(domain, t->tx.buf);
ret = ph->xops->do_xfer(ph, t);
if (!ret)
*state = get_unaligned_le32(t->rx.buf);
ph->xops->xfer_put(ph, t);
return ret;
}
static int scmi_power_num_domains_get(const struct scmi_protocol_handle *ph)
{
struct scmi_power_info *pi = ph->get_priv(ph);
return pi->num_domains;
}
static const char *
scmi_power_name_get(const struct scmi_protocol_handle *ph,
u32 domain)
{
struct scmi_power_info *pi = ph->get_priv(ph);
struct power_dom_info *dom;
if (domain >= pi->num_domains)
return "unknown";
dom = pi->dom_info + domain;
return dom->name;
}
static const struct scmi_power_proto_ops power_proto_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_request_notify(const struct scmi_protocol_handle *ph,
u32 domain, bool enable)
{
int ret;
struct scmi_xfer *t;
struct scmi_power_state_notify *notify;
ret = ph->xops->xfer_get_init(ph, POWER_STATE_NOTIFY,
sizeof(*notify), 0, &t);
if (ret)
return ret;
notify = t->tx.buf;
notify->domain = cpu_to_le32(domain);
notify->notify_enable = enable ? cpu_to_le32(BIT(0)) : 0;
ret = ph->xops->do_xfer(ph, t);
ph->xops->xfer_put(ph, t);
return ret;
}
static bool scmi_power_notify_supported(const struct scmi_protocol_handle *ph,
u8 evt_id, u32 src_id)
{
struct power_dom_info *dom;
struct scmi_power_info *pinfo = ph->get_priv(ph);
if (evt_id != SCMI_EVENT_POWER_STATE_CHANGED ||
src_id >= pinfo->num_domains)
return false;
dom = pinfo->dom_info + src_id;
return dom->state_set_notify;
}
static int scmi_power_set_notify_enabled(const struct scmi_protocol_handle *ph,
u8 evt_id, u32 src_id, bool enable)
{
int ret;
ret = scmi_power_request_notify(ph, src_id, enable);
if (ret)
pr_debug("FAIL_ENABLE - evt[%X] dom[%d] - ret:%d\n",
evt_id, src_id, ret);
return ret;
}
static void *
scmi_power_fill_custom_report(const struct scmi_protocol_handle *ph,
u8 evt_id, ktime_t timestamp,
const void *payld, size_t payld_sz,
void *report, u32 *src_id)
{
const struct scmi_power_state_notify_payld *p = payld;
struct scmi_power_state_changed_report *r = report;
if (evt_id != SCMI_EVENT_POWER_STATE_CHANGED || sizeof(*p) != payld_sz)
return NULL;
r->timestamp = timestamp;
r->agent_id = le32_to_cpu(p->agent_id);
r->domain_id = le32_to_cpu(p->domain_id);
r->power_state = le32_to_cpu(p->power_state);
*src_id = r->domain_id;
return r;
}
static int scmi_power_get_num_sources(const struct scmi_protocol_handle *ph)
{
struct scmi_power_info *pinfo = ph->get_priv(ph);
if (!pinfo)
return -EINVAL;
return pinfo->num_domains;
}
static const struct scmi_event power_events[] = {
{
.id = SCMI_EVENT_POWER_STATE_CHANGED,
.max_payld_sz = sizeof(struct scmi_power_state_notify_payld),
.max_report_sz =
sizeof(struct scmi_power_state_changed_report),
},
};
static const struct scmi_event_ops power_event_ops = {
.is_notify_supported = scmi_power_notify_supported,
.get_num_sources = scmi_power_get_num_sources,
.set_notify_enabled = scmi_power_set_notify_enabled,
.fill_custom_report = scmi_power_fill_custom_report,
};
static const struct scmi_protocol_events power_protocol_events = {
.queue_sz = SCMI_PROTO_QUEUE_SZ,
.ops = &power_event_ops,
.evts = power_events,
.num_events = ARRAY_SIZE(power_events),
};
static int scmi_power_protocol_init(const struct scmi_protocol_handle *ph)
{
int domain, ret;
struct scmi_power_info *pinfo;
dev_dbg(ph->dev, "Power Version %d.%d\n",
PROTOCOL_REV_MAJOR(ph->version), PROTOCOL_REV_MINOR(ph->version));
pinfo = devm_kzalloc(ph->dev, sizeof(*pinfo), GFP_KERNEL);
if (!pinfo)
return -ENOMEM;
ret = scmi_power_attributes_get(ph, pinfo);
if (ret)
return ret;
pinfo->dom_info = devm_kcalloc(ph->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(ph, domain, dom,
pinfo->notify_state_change_cmd);
}
return ph->set_priv(ph, pinfo);
}
static const struct scmi_protocol scmi_power = {
.id = SCMI_PROTOCOL_POWER,
.owner = THIS_MODULE,
.instance_init = &scmi_power_protocol_init,
.ops = &power_proto_ops,
.events = &power_protocol_events,
.supported_version = SCMI_PROTOCOL_SUPPORTED_VERSION,
};
DEFINE_SCMI_PROTOCOL_REGISTER_UNREGISTER(power, scmi_power)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,395 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* System Control and Management Interface (SCMI) Message Protocol
* protocols common header file containing some definitions, structures
* and function prototypes used in all the different SCMI protocols.
*
* Copyright (C) 2022 ARM Ltd.
*/
#ifndef _SCMI_PROTOCOLS_H
#define _SCMI_PROTOCOLS_H
#include <linux/bitfield.h>
#include <linux/completion.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/hashtable.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/refcount.h>
#include <linux/scmi_protocol.h>
#include <linux/spinlock.h>
#include <linux/types.h>
#include <linux/unaligned.h>
#define PROTOCOL_REV_MINOR_MASK GENMASK(15, 0)
#define PROTOCOL_REV_MAJOR_MASK GENMASK(31, 16)
#define PROTOCOL_REV_MAJOR(x) ((u16)(FIELD_GET(PROTOCOL_REV_MAJOR_MASK, (x))))
#define PROTOCOL_REV_MINOR(x) ((u16)(FIELD_GET(PROTOCOL_REV_MINOR_MASK, (x))))
#define SCMI_PROTOCOL_VENDOR_BASE 0x80
#define MSG_SUPPORTS_FASTCHANNEL(x) ((x) & BIT(0))
enum scmi_common_cmd {
PROTOCOL_VERSION = 0x0,
PROTOCOL_ATTRIBUTES = 0x1,
PROTOCOL_MESSAGE_ATTRIBUTES = 0x2,
NEGOTIATE_PROTOCOL_VERSION = 0x10,
};
/**
* struct scmi_msg_resp_prot_version - Response for a message
*
* @minor_version: Minor version of the ABI that firmware supports
* @major_version: Major 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 - Message(Tx/Rx) structure
*
* @buf: Buffer pointer
* @len: Length of data in the Buffer
*/
struct scmi_msg {
void *buf;
size_t len;
};
/**
* struct scmi_msg_hdr - Message(Tx/Rx) header
*
* @id: The identifier of the message being sent
* @protocol_id: The identifier of the protocol used to send @id message
* @type: The SCMI type for this message
* @seq: The token to identify the message. When a message returns, the
* platform returns the whole message header unmodified including the
* token
* @status: Status of the transfer once it's complete
* @poll_completion: Indicate if the transfer needs to be polled for
* completion or interrupt mode is used
*/
struct scmi_msg_hdr {
u8 id;
u8 protocol_id;
u8 type;
u16 seq;
u32 status;
bool poll_completion;
};
/**
* struct scmi_xfer - Structure representing a message flow
*
* @transfer_id: Unique ID for debug & profiling purpose
* @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: command message transmit completion event
* @async_done: pointer to delayed response message received event completion
* @pending: True for xfers added to @pending_xfers hashtable
* @node: An hlist_node reference used to store this xfer, alternatively, on
* the free list @free_xfers or in the @pending_xfers hashtable
* @users: A refcount to track the active users for this xfer.
* This is meant to protect against the possibility that, when a command
* transaction times out concurrently with the reception of a valid
* response message, the xfer could be finally put on the TX path, and
* so vanish, while on the RX path scmi_rx_callback() is still
* processing it: in such a case this refcounting will ensure that, even
* though the timed-out transaction will anyway cause the command
* request to be reported as failed by time-out, the underlying xfer
* cannot be discarded and possibly reused until the last one user on
* the RX path has released it.
* @busy: An atomic flag to ensure exclusive write access to this xfer
* @state: The current state of this transfer, with states transitions deemed
* valid being:
* - SCMI_XFER_SENT_OK -> SCMI_XFER_RESP_OK [ -> SCMI_XFER_DRESP_OK ]
* - SCMI_XFER_SENT_OK -> SCMI_XFER_DRESP_OK
* (Missing synchronous response is assumed OK and ignored)
* @flags: Optional flags associated to this xfer.
* @lock: A spinlock to protect state and busy fields.
* @priv: A pointer for transport private usage.
*/
struct scmi_xfer {
int transfer_id;
struct scmi_msg_hdr hdr;
struct scmi_msg tx;
struct scmi_msg rx;
struct completion done;
struct completion *async_done;
bool pending;
struct hlist_node node;
refcount_t users;
#define SCMI_XFER_FREE 0
#define SCMI_XFER_BUSY 1
atomic_t busy;
#define SCMI_XFER_SENT_OK 0
#define SCMI_XFER_RESP_OK 1
#define SCMI_XFER_DRESP_OK 2
int state;
#define SCMI_XFER_FLAG_IS_RAW BIT(0)
#define SCMI_XFER_IS_RAW(x) ((x)->flags & SCMI_XFER_FLAG_IS_RAW)
#define SCMI_XFER_FLAG_CHAN_SET BIT(1)
#define SCMI_XFER_IS_CHAN_SET(x) \
((x)->flags & SCMI_XFER_FLAG_CHAN_SET)
int flags;
/* A lock to protect state and busy fields */
spinlock_t lock;
void *priv;
};
struct scmi_xfer_ops;
struct scmi_proto_helpers_ops;
/**
* struct scmi_protocol_handle - Reference to an initialized protocol instance
*
* @dev: A reference to the associated SCMI instance device (handle->dev).
* @version: The protocol version currently effectively in use by this
* initialized instance of the protocol as determined at the end of
* any possibly needed negotiations performed by the core.
* @xops: A reference to a struct holding refs to the core xfer operations that
* can be used by the protocol implementation to generate SCMI messages.
* @set_priv: A method to set protocol private data for this instance.
* @get_priv: A method to get protocol private data previously set.
*
* This structure represents a protocol initialized against specific SCMI
* instance and it will be used as follows:
* - as a parameter fed from the core to the protocol initialization code so
* that it can access the core xfer operations to build and generate SCMI
* messages exclusively for the specific underlying protocol instance.
* - as an opaque handle fed by an SCMI driver user when it tries to access
* this protocol through its own protocol operations.
* In this case this handle will be returned as an opaque object together
* with the related protocol operations when the SCMI driver tries to access
* the protocol.
*/
struct scmi_protocol_handle {
struct device *dev;
unsigned int version;
const struct scmi_xfer_ops *xops;
const struct scmi_proto_helpers_ops *hops;
int (*set_priv)(const struct scmi_protocol_handle *ph, void *priv);
void *(*get_priv)(const struct scmi_protocol_handle *ph);
};
/**
* struct scmi_iterator_state - Iterator current state descriptor
* @desc_index: Starting index for the current multi-part request.
* @num_returned: Number of returned items in the last multi-part reply.
* @num_remaining: Number of remaining items in the multi-part message.
* @max_resources: Maximum acceptable number of items, configured by the caller
* depending on the underlying resources that it is querying.
* @loop_idx: The iterator loop index in the current multi-part reply.
* @rx_len: Size in bytes of the currently processed message; it can be used by
* the user of the iterator to verify a reply size.
* @priv: Optional pointer to some additional state-related private data setup
* by the caller during the iterations.
*/
struct scmi_iterator_state {
unsigned int desc_index;
unsigned int num_returned;
unsigned int num_remaining;
unsigned int max_resources;
unsigned int loop_idx;
size_t rx_len;
void *priv;
};
/**
* struct scmi_iterator_ops - Custom iterator operations
* @prepare_message: An operation to provide the custom logic to fill in the
* SCMI command request pointed by @message. @desc_index is
* a reference to the next index to use in the multi-part
* request.
* @update_state: An operation to provide the custom logic to update the
* iterator state from the actual message response.
* @process_response: An operation to provide the custom logic needed to process
* each chunk of the multi-part message.
*/
struct scmi_iterator_ops {
void (*prepare_message)(void *message, unsigned int desc_index,
const void *priv);
int (*update_state)(struct scmi_iterator_state *st,
const void *response, void *priv);
int (*process_response)(const struct scmi_protocol_handle *ph,
const void *response,
struct scmi_iterator_state *st, void *priv);
};
struct scmi_fc_db_info {
int width;
u64 set;
u64 mask;
void __iomem *addr;
};
struct scmi_fc_info {
void __iomem *set_addr;
void __iomem *get_addr;
struct scmi_fc_db_info *set_db;
u32 rate_limit;
};
/**
* struct scmi_proto_helpers_ops - References to common protocol helpers
* @extended_name_get: A common helper function to retrieve extended naming
* for the specified resource using the specified command.
* Result is returned as a NULL terminated string in the
* pre-allocated area pointed to by @name with maximum
* capacity of @len bytes.
* @iter_response_init: A common helper to initialize a generic iterator to
* parse multi-message responses: when run the iterator
* will take care to send the initial command request as
* specified by @msg_id and @tx_size and then to parse the
* multi-part responses using the custom operations
* provided in @ops.
* @iter_response_run: A common helper to trigger the run of a previously
* initialized iterator. Note that unbound iterators are
* automatically cleaned up.
* @iter_response_run_bound: A common helper to trigger the run of a previously
* initialized iterator, but only within the
* specified, optional, @start and @end resource
* indexes. Note that these bound-iterators need
* explicit cleanup via @iter_response_bound_cleanup.
* @iter_response_bound_cleanup: A common helper to finally release the iterator
* for bound iterators.
* @protocol_msg_check: A common helper to check is a specific protocol message
* is supported.
* @fastchannel_init: A common helper used to initialize FC descriptors by
* gathering FC descriptions from the SCMI platform server.
* @fastchannel_db_ring: A common helper to ring a FC doorbell.
* @get_max_msg_size: A common helper to get the maximum message size.
*/
struct scmi_proto_helpers_ops {
int (*extended_name_get)(const struct scmi_protocol_handle *ph,
u8 cmd_id, u32 res_id, u32 *flags, char *name,
size_t len);
void *(*iter_response_init)(const struct scmi_protocol_handle *ph,
struct scmi_iterator_ops *ops,
unsigned int max_resources, u8 msg_id,
size_t tx_size, void *priv);
int (*iter_response_run)(void *iter);
int (*iter_response_run_bound)(void *iter,
unsigned int *start, unsigned int *end);
void (*iter_response_bound_cleanup)(void *iter);
int (*protocol_msg_check)(const struct scmi_protocol_handle *ph,
u32 message_id, u32 *attributes);
void (*fastchannel_init)(const struct scmi_protocol_handle *ph,
u8 describe_id, u32 message_id,
u32 valid_size, u32 domain,
void __iomem **p_addr,
struct scmi_fc_db_info **p_db,
u32 *rate_limit);
void (*fastchannel_db_ring)(struct scmi_fc_db_info *db);
int (*get_max_msg_size)(const struct scmi_protocol_handle *ph);
};
/**
* struct scmi_xfer_ops - References to the core SCMI xfer operations.
* @xfer_get_init: Initialize one struct xfer if any xfer slot is free.
* @reset_rx_to_maxsz: Reset rx size to max transport size.
* @do_xfer: Do the SCMI transfer.
* @do_xfer_with_response: Do the SCMI transfer waiting for a response.
* @xfer_put: Free the xfer slot.
*
* Note that all this operations expect a protocol handle as first parameter;
* they then internally use it to infer the underlying protocol number: this
* way is not possible for a protocol implementation to forge messages for
* another protocol.
*/
struct scmi_xfer_ops {
int (*xfer_get_init)(const struct scmi_protocol_handle *ph, u8 msg_id,
size_t tx_size, size_t rx_size,
struct scmi_xfer **p);
void (*reset_rx_to_maxsz)(const struct scmi_protocol_handle *ph,
struct scmi_xfer *xfer);
int (*do_xfer)(const struct scmi_protocol_handle *ph,
struct scmi_xfer *xfer);
int (*do_xfer_with_response)(const struct scmi_protocol_handle *ph,
struct scmi_xfer *xfer);
void (*xfer_put)(const struct scmi_protocol_handle *ph,
struct scmi_xfer *xfer);
};
typedef int (*scmi_prot_init_ph_fn_t)(const struct scmi_protocol_handle *);
/**
* struct scmi_protocol - Protocol descriptor
* @id: Protocol ID.
* @owner: Module reference if any.
* @instance_init: Mandatory protocol initialization function.
* @instance_deinit: Optional protocol de-initialization function.
* @ops: Optional reference to the operations provided by the protocol and
* exposed in scmi_protocol.h.
* @events: An optional reference to the events supported by this protocol.
* @supported_version: The highest version currently supported for this
* protocol by the agent. Each protocol implementation
* in the agent is supposed to downgrade to match the
* protocol version supported by the platform.
* @vendor_id: A firmware vendor string for vendor protocols matching.
* Ignored when @id identifies a standard protocol, cannot be NULL
* otherwise.
* @sub_vendor_id: A firmware sub_vendor string for vendor protocols matching.
* Ignored if NULL or when @id identifies a standard protocol.
* @impl_ver: A firmware implementation version for vendor protocols matching.
* Ignored if zero or if @id identifies a standard protocol.
*
* Note that vendor protocols matching at load time is performed by attempting
* the closest match first against the tuple (vendor, sub_vendor, impl_ver)
*/
struct scmi_protocol {
const u8 id;
struct module *owner;
const scmi_prot_init_ph_fn_t instance_init;
const scmi_prot_init_ph_fn_t instance_deinit;
const void *ops;
const struct scmi_protocol_events *events;
unsigned int supported_version;
char *vendor_id;
char *sub_vendor_id;
u32 impl_ver;
};
#define DEFINE_SCMI_PROTOCOL_REGISTER_UNREGISTER(name, proto) \
static const struct scmi_protocol *__this_proto = &(proto); \
\
int __init scmi_##name##_register(void) \
{ \
return scmi_protocol_register(__this_proto); \
} \
\
void __exit scmi_##name##_unregister(void) \
{ \
scmi_protocol_unregister(__this_proto); \
}
#define DECLARE_SCMI_REGISTER_UNREGISTER(func) \
int __init scmi_##func##_register(void); \
void __exit scmi_##func##_unregister(void)
DECLARE_SCMI_REGISTER_UNREGISTER(base);
DECLARE_SCMI_REGISTER_UNREGISTER(clock);
DECLARE_SCMI_REGISTER_UNREGISTER(perf);
DECLARE_SCMI_REGISTER_UNREGISTER(pinctrl);
DECLARE_SCMI_REGISTER_UNREGISTER(power);
DECLARE_SCMI_REGISTER_UNREGISTER(reset);
DECLARE_SCMI_REGISTER_UNREGISTER(sensors);
DECLARE_SCMI_REGISTER_UNREGISTER(voltage);
DECLARE_SCMI_REGISTER_UNREGISTER(system);
DECLARE_SCMI_REGISTER_UNREGISTER(powercap);
#endif /* _SCMI_PROTOCOLS_H */
@@ -0,0 +1,324 @@
// SPDX-License-Identifier: GPL-2.0
/*
* System Control and Management Interface (SCMI) Message Protocol Quirks
*
* Copyright (C) 2025 ARM Ltd.
*/
/**
* DOC: Theory of operation
*
* A framework to define SCMI quirks and their activation conditions based on
* existing static_keys kernel facilities.
*
* Quirks are named and their activation conditions defined using the macro
* DEFINE_SCMI_QUIRK() in this file.
*
* After a quirk is defined, a corresponding entry must also be added to the
* global @scmi_quirks_table in this file using __DECLARE_SCMI_QUIRK_ENTRY().
*
* Additionally a corresponding quirk declaration must be added also to the
* quirk.h file using DECLARE_SCMI_QUIRK().
*
* The needed quirk code-snippet itself will be defined local to the SCMI code
* that is meant to fix and will be associated to the previously defined quirk
* and related activation conditions using the macro SCMI_QUIRK().
*
* At runtime, during the SCMI stack probe sequence, once the SCMI Server had
* advertised the running platform Vendor, SubVendor and Implementation Version
* data, all the defined quirks matching the activation conditions will be
* enabled.
*
* Example
*
* quirk.c
* -------
* DEFINE_SCMI_QUIRK(fix_me, "vendor", "subvend", "0x12000-0x30000",
* "someone,plat_A", "another,plat_b", "vend,sku");
*
* static struct scmi_quirk *scmi_quirks_table[] = {
* ...
* __DECLARE_SCMI_QUIRK_ENTRY(fix_me),
* NULL
* };
*
* quirk.h
* -------
* DECLARE_SCMI_QUIRK(fix_me);
*
* <somewhere_in_the_scmi_stack.c>
* ------------------------------
*
* #define QUIRK_CODE_SNIPPET_FIX_ME() \
* ({ \
* if (p->condition) \
* a_ptr->calculated_val = 123; \
* })
*
*
* int some_function_to_fix(int param, struct something *p)
* {
* struct some_strut *a_ptr;
*
* a_ptr = some_load_func(p);
* SCMI_QUIRK(fix_me, QUIRK_CODE_SNIPPET_FIX_ME);
* some_more_func(a_ptr);
* ...
*
* return 0;
* }
*
*/
#include <linux/ctype.h>
#include <linux/cleanup.h>
#include <linux/device.h>
#include <linux/export.h>
#include <linux/hashtable.h>
#include <linux/kstrtox.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <linux/static_key.h>
#include <linux/string.h>
#include <linux/stringhash.h>
#include <linux/types.h>
#include "quirks.h"
#define SCMI_QUIRKS_HT_SZ 4
struct scmi_quirk {
bool enabled;
const char *name;
const char *vendor;
const char *sub_vendor_id;
const char *impl_ver_range;
u32 start_range;
u32 end_range;
struct static_key_false *key;
struct hlist_node hash;
unsigned int hkey;
const char *const compats[];
};
#define __DEFINE_SCMI_QUIRK_ENTRY(_qn, _ven, _sub, _impl, ...) \
static struct scmi_quirk scmi_quirk_entry_ ## _qn = { \
.name = __stringify(quirk_ ## _qn), \
.vendor = _ven, \
.sub_vendor_id = _sub, \
.impl_ver_range = _impl, \
.key = &(scmi_quirk_ ## _qn), \
.compats = { __VA_ARGS__ __VA_OPT__(,) NULL }, \
}
#define __DECLARE_SCMI_QUIRK_ENTRY(_qn) (&(scmi_quirk_entry_ ## _qn))
/*
* Define a quirk by name and provide the matching tokens where:
*
* _qn: A string which will be used to build the quirk and the global
* static_key names.
* _ven : SCMI Vendor ID string match, NULL means any.
* _sub : SCMI SubVendor ID string match, NULL means any.
* _impl : SCMI Implementation Version string match, NULL means any.
* This string can be used to express version ranges which will be
* interpreted as follows:
*
* NULL [0, 0xFFFFFFFF]
* "X" [X, X]
* "X-" [X, 0xFFFFFFFF]
* "-X" [0, X]
* "X-Y" [X, Y]
*
* with X <= Y and <v> in [X, Y] meaning X <= <v> <= Y
*
* ... : An optional variadic macros argument used to provide a comma-separated
* list of compatible strings matches; when no variadic argument is
* provided, ANY compatible will match this quirk.
*
* This implicitly define also a properly named global static-key that
* will be used to dynamically enable the quirk at initialization time.
*
* Note that it is possible to associate multiple quirks to the same
* matching pattern, if your firmware quality is really astounding :P
*
* Example:
*
* Compatibles list NOT provided, so ANY compatible will match:
*
* DEFINE_SCMI_QUIRK(my_new_issue, "Vend", "SVend", "0x12000-0x30000");
*
*
* A few compatibles provided to match against:
*
* DEFINE_SCMI_QUIRK(my_new_issue, "Vend", "SVend", "0x12000-0x30000",
* "xvend,plat_a", "xvend,plat_b", "xvend,sku_name");
*/
#define DEFINE_SCMI_QUIRK(_qn, _ven, _sub, _impl, ...) \
DEFINE_STATIC_KEY_FALSE(scmi_quirk_ ## _qn); \
__DEFINE_SCMI_QUIRK_ENTRY(_qn, _ven, _sub, _impl, ##__VA_ARGS__)
/*
* Same as DEFINE_SCMI_QUIRK but EXPORTED: this is meant to address quirks
* that possibly reside in code that is included in loadable kernel modules
* that needs to be able to access the global static keys at runtime to
* determine if enabled or not. (see SCMI_QUIRK to understand usage)
*/
#define DEFINE_SCMI_QUIRK_EXPORTED(_qn, _ven, _sub, _impl, ...) \
DEFINE_STATIC_KEY_FALSE(scmi_quirk_ ## _qn); \
EXPORT_SYMBOL_GPL(scmi_quirk_ ## _qn); \
__DEFINE_SCMI_QUIRK_ENTRY(_qn, _ven, _sub, _impl, ##__VA_ARGS__)
/* Global Quirks Definitions */
DEFINE_SCMI_QUIRK(clock_rates_triplet_out_of_spec, NULL, NULL, NULL);
DEFINE_SCMI_QUIRK(perf_level_get_fc_force, "Qualcomm", NULL, "0x20000-");
/*
* Quirks Pointers Array
*
* This is filled at compile-time with the list of pointers to all the currently
* defined quirks descriptors.
*/
static struct scmi_quirk *scmi_quirks_table[] = {
__DECLARE_SCMI_QUIRK_ENTRY(clock_rates_triplet_out_of_spec),
__DECLARE_SCMI_QUIRK_ENTRY(perf_level_get_fc_force),
NULL
};
/*
* Quirks HashTable
*
* A run-time populated hashtable containing all the defined quirks descriptors
* hashed by matching pattern.
*/
static DEFINE_READ_MOSTLY_HASHTABLE(scmi_quirks_ht, SCMI_QUIRKS_HT_SZ);
static unsigned int scmi_quirk_signature(const char *vend, const char *sub_vend)
{
char *signature, *p;
unsigned int hash32;
unsigned long hash = 0;
/* vendor_id/sub_vendor_id guaranteed <= SCMI_SHORT_NAME_MAX_SIZE */
signature = kasprintf(GFP_KERNEL, "|%s|%s|", vend ?: "", sub_vend ?: "");
if (!signature)
return 0;
pr_debug("SCMI Quirk Signature >>>%s<<<\n", signature);
p = signature;
while (*p)
hash = partial_name_hash(tolower(*p++), hash);
hash32 = end_name_hash(hash);
kfree(signature);
return hash32;
}
static int scmi_quirk_range_parse(struct scmi_quirk *quirk)
{
const char *last, *first __free(kfree) = NULL;
int ret = 0;
size_t len;
char *sep;
quirk->start_range = 0;
quirk->end_range = 0xFFFFFFFF;
len = quirk->impl_ver_range ? strlen(quirk->impl_ver_range) : 0;
if (!len)
return 0;
first = kmemdup(quirk->impl_ver_range, len + 1, GFP_KERNEL);
if (!first)
return -ENOMEM;
last = first + len - 1;
sep = strchr(first, '-');
if (sep)
*sep = '\0';
if (sep != first) /* X OR X- OR X-y */ {
ret = kstrtouint(first, 0, &quirk->start_range);
if (ret)
return ret;
}
if (!sep)
quirk->end_range = quirk->start_range;
else if (sep != last) /* -X OR x-Y */
ret = kstrtouint(sep + 1, 0, &quirk->end_range);
if (quirk->start_range > quirk->end_range)
return -EINVAL;
return ret;
}
void scmi_quirks_initialize(void)
{
struct scmi_quirk *quirk;
for (unsigned int i = 0; (quirk = scmi_quirks_table[i]); i++) {
int ret;
ret = scmi_quirk_range_parse(quirk);
if (ret) {
pr_err("SCMI skip QUIRK [%s] - BAD RANGE - |%s|\n",
quirk->name, quirk->impl_ver_range);
continue;
}
quirk->hkey = scmi_quirk_signature(quirk->vendor,
quirk->sub_vendor_id);
hash_add(scmi_quirks_ht, &quirk->hash, quirk->hkey);
pr_debug("Registered SCMI QUIRK [%s] -- %p - Key [0x%08X] - %s/%s/[0x%08X-0x%08X]\n",
quirk->name, quirk, quirk->hkey,
quirk->vendor, quirk->sub_vendor_id,
quirk->start_range, quirk->end_range);
}
pr_debug("SCMI Quirks initialized\n");
}
void scmi_quirks_enable(struct device *dev, const char *vend,
const char *subv, const u32 impl)
{
for (int i = 3; i >= 0; i--) {
struct scmi_quirk *quirk;
unsigned int hkey;
hkey = scmi_quirk_signature(i > 1 ? vend : NULL,
i > 2 ? subv : NULL);
/*
* Note that there could be multiple matches so we
* will enable multiple quirk part of a hash collision
* domain...BUT we cannot assume that ALL quirks on the
* same collision domain are a full match.
*/
hash_for_each_possible(scmi_quirks_ht, quirk, hash, hkey) {
if (quirk->enabled || quirk->hkey != hkey ||
impl < quirk->start_range ||
impl > quirk->end_range)
continue;
if (quirk->compats[0] &&
!of_machine_compatible_match(quirk->compats))
continue;
dev_info(dev, "Enabling SCMI Quirk [%s]\n",
quirk->name);
dev_dbg(dev,
"Quirk matched on: %s/%s/%s/[0x%08X-0x%08X]\n",
quirk->compats[0], quirk->vendor,
quirk->sub_vendor_id,
quirk->start_range, quirk->end_range);
static_branch_enable(quirk->key);
quirk->enabled = true;
}
}
}
@@ -0,0 +1,52 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* System Control and Management Interface (SCMI) Message Protocol Quirks
*
* Copyright (C) 2025 ARM Ltd.
*/
#ifndef _SCMI_QUIRKS_H
#define _SCMI_QUIRKS_H
#include <linux/static_key.h>
#include <linux/types.h>
#ifdef CONFIG_ARM_SCMI_QUIRKS
#define DECLARE_SCMI_QUIRK(_qn) \
DECLARE_STATIC_KEY_FALSE(scmi_quirk_ ## _qn)
/*
* A helper to associate the actual code snippet to use as a quirk
* named as _qn.
*/
#define SCMI_QUIRK(_qn, _blk) \
({ \
if (static_branch_unlikely(&(scmi_quirk_ ## _qn))) \
(_blk); \
})
void scmi_quirks_initialize(void);
void scmi_quirks_enable(struct device *dev, const char *vend,
const char *subv, const u32 impl);
#else
#define DECLARE_SCMI_QUIRK(_qn)
/* Force quirks compilation even when SCMI Quirks are disabled */
#define SCMI_QUIRK(_qn, _blk) \
({ \
if (0) \
(_blk); \
})
static inline void scmi_quirks_initialize(void) { }
static inline void scmi_quirks_enable(struct device *dev, const char *vend,
const char *sub_vend, const u32 impl) { }
#endif /* CONFIG_ARM_SCMI_QUIRKS */
/* Quirk delarations */
DECLARE_SCMI_QUIRK(clock_rates_triplet_out_of_spec);
DECLARE_SCMI_QUIRK(perf_level_get_fc_force);
#endif /* _SCMI_QUIRKS_H */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,31 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* System Control and Management Interface (SCMI) Message Protocol
* Raw mode support header.
*
* Copyright (C) 2022 ARM Ltd.
*/
#ifndef _SCMI_RAW_MODE_H
#define _SCMI_RAW_MODE_H
#include "common.h"
enum {
SCMI_RAW_REPLY_QUEUE,
SCMI_RAW_NOTIF_QUEUE,
SCMI_RAW_ERRS_QUEUE,
SCMI_RAW_MAX_QUEUE
};
void *scmi_raw_mode_init(const struct scmi_handle *handle,
struct dentry *top_dentry, int instance_id,
u8 *channels, int num_chans,
const struct scmi_desc *desc, int tx_max_msg);
void scmi_raw_mode_cleanup(void *raw);
void scmi_raw_message_report(void *raw, struct scmi_xfer *xfer,
unsigned int idx, unsigned int chan_id);
void scmi_raw_error_report(void *raw, struct scmi_chan_info *cinfo,
u32 msg_hdr, void *priv);
#endif /* _SCMI_RAW_MODE_H */
@@ -0,0 +1,390 @@
// SPDX-License-Identifier: GPL-2.0
/*
* System Control and Management Interface (SCMI) Reset Protocol
*
* Copyright (C) 2019-2022 ARM Ltd.
*/
#define pr_fmt(fmt) "SCMI Notifications RESET - " fmt
#include <linux/module.h>
#include <linux/scmi_protocol.h>
#include "protocols.h"
#include "notify.h"
/* Updated only after ALL the mandatory features for that version are merged */
#define SCMI_PROTOCOL_SUPPORTED_VERSION 0x30001
enum scmi_reset_protocol_cmd {
RESET_DOMAIN_ATTRIBUTES = 0x3,
RESET = 0x4,
RESET_NOTIFY = 0x5,
RESET_DOMAIN_NAME_GET = 0x6,
};
#define NUM_RESET_DOMAIN_MASK 0xffff
#define RESET_NOTIFY_ENABLE BIT(0)
struct scmi_msg_resp_reset_domain_attributes {
__le32 attributes;
#define SUPPORTS_ASYNC_RESET(x) ((x) & BIT(31))
#define SUPPORTS_NOTIFY_RESET(x) ((x) & BIT(30))
#define SUPPORTS_EXTENDED_NAMES(x) ((x) & BIT(29))
__le32 latency;
u8 name[SCMI_SHORT_NAME_MAX_SIZE];
};
struct scmi_msg_reset_domain_reset {
__le32 domain_id;
__le32 flags;
#define AUTONOMOUS_RESET BIT(0)
#define EXPLICIT_RESET_ASSERT BIT(1)
#define ASYNCHRONOUS_RESET BIT(2)
__le32 reset_state;
#define ARCH_COLD_RESET 0
};
struct scmi_msg_reset_notify {
__le32 id;
__le32 event_control;
#define RESET_TP_NOTIFY_ALL BIT(0)
};
struct scmi_reset_issued_notify_payld {
__le32 agent_id;
__le32 domain_id;
__le32 reset_state;
};
struct reset_dom_info {
bool async_reset;
bool reset_notify;
u32 latency_us;
char name[SCMI_MAX_STR_SIZE];
};
struct scmi_reset_info {
int num_domains;
bool notify_reset_cmd;
struct reset_dom_info *dom_info;
};
static int scmi_reset_attributes_get(const struct scmi_protocol_handle *ph,
struct scmi_reset_info *pi)
{
int ret;
struct scmi_xfer *t;
u32 attr;
ret = ph->xops->xfer_get_init(ph, PROTOCOL_ATTRIBUTES,
0, sizeof(attr), &t);
if (ret)
return ret;
ret = ph->xops->do_xfer(ph, t);
if (!ret) {
attr = get_unaligned_le32(t->rx.buf);
pi->num_domains = attr & NUM_RESET_DOMAIN_MASK;
}
ph->xops->xfer_put(ph, t);
if (!ret)
if (!ph->hops->protocol_msg_check(ph, RESET_NOTIFY, NULL))
pi->notify_reset_cmd = true;
return ret;
}
static struct reset_dom_info *
scmi_reset_domain_lookup(const struct scmi_protocol_handle *ph, u32 domain)
{
struct scmi_reset_info *pi = ph->get_priv(ph);
if (domain >= pi->num_domains)
return ERR_PTR(-EINVAL);
return pi->dom_info + domain;
}
static int
scmi_reset_domain_attributes_get(const struct scmi_protocol_handle *ph,
struct scmi_reset_info *pinfo, u32 domain)
{
int ret;
u32 attributes;
struct scmi_xfer *t;
struct scmi_msg_resp_reset_domain_attributes *attr;
struct reset_dom_info *dom_info = pinfo->dom_info + domain;
ret = ph->xops->xfer_get_init(ph, RESET_DOMAIN_ATTRIBUTES,
sizeof(domain), sizeof(*attr), &t);
if (ret)
return ret;
put_unaligned_le32(domain, t->tx.buf);
attr = t->rx.buf;
ret = ph->xops->do_xfer(ph, t);
if (!ret) {
attributes = le32_to_cpu(attr->attributes);
dom_info->async_reset = SUPPORTS_ASYNC_RESET(attributes);
if (pinfo->notify_reset_cmd)
dom_info->reset_notify =
SUPPORTS_NOTIFY_RESET(attributes);
dom_info->latency_us = le32_to_cpu(attr->latency);
if (dom_info->latency_us == U32_MAX)
dom_info->latency_us = 0;
strscpy(dom_info->name, attr->name, SCMI_SHORT_NAME_MAX_SIZE);
}
ph->xops->xfer_put(ph, t);
/*
* If supported overwrite short name with the extended one;
* on error just carry on and use already provided short name.
*/
if (!ret && PROTOCOL_REV_MAJOR(ph->version) >= 0x3 &&
SUPPORTS_EXTENDED_NAMES(attributes))
ph->hops->extended_name_get(ph, RESET_DOMAIN_NAME_GET, domain,
NULL, dom_info->name,
SCMI_MAX_STR_SIZE);
return ret;
}
static int scmi_reset_num_domains_get(const struct scmi_protocol_handle *ph)
{
struct scmi_reset_info *pi = ph->get_priv(ph);
return pi->num_domains;
}
static const char *
scmi_reset_name_get(const struct scmi_protocol_handle *ph, u32 domain)
{
struct reset_dom_info *dom_info;
dom_info = scmi_reset_domain_lookup(ph, domain);
if (IS_ERR(dom_info))
return "unknown";
return dom_info->name;
}
static int scmi_reset_latency_get(const struct scmi_protocol_handle *ph,
u32 domain)
{
struct reset_dom_info *dom_info;
dom_info = scmi_reset_domain_lookup(ph, domain);
if (IS_ERR(dom_info))
return PTR_ERR(dom_info);
return dom_info->latency_us;
}
static int scmi_domain_reset(const struct scmi_protocol_handle *ph, u32 domain,
u32 flags, u32 state)
{
int ret;
struct scmi_xfer *t;
struct scmi_msg_reset_domain_reset *dom;
struct reset_dom_info *dom_info;
dom_info = scmi_reset_domain_lookup(ph, domain);
if (IS_ERR(dom_info))
return PTR_ERR(dom_info);
if (dom_info->async_reset && flags & AUTONOMOUS_RESET)
flags |= ASYNCHRONOUS_RESET;
ret = ph->xops->xfer_get_init(ph, RESET, sizeof(*dom), 0, &t);
if (ret)
return ret;
dom = t->tx.buf;
dom->domain_id = cpu_to_le32(domain);
dom->flags = cpu_to_le32(flags);
dom->reset_state = cpu_to_le32(state);
if (flags & ASYNCHRONOUS_RESET)
ret = ph->xops->do_xfer_with_response(ph, t);
else
ret = ph->xops->do_xfer(ph, t);
ph->xops->xfer_put(ph, t);
return ret;
}
static int scmi_reset_domain_reset(const struct scmi_protocol_handle *ph,
u32 domain)
{
return scmi_domain_reset(ph, domain, AUTONOMOUS_RESET,
ARCH_COLD_RESET);
}
static int
scmi_reset_domain_assert(const struct scmi_protocol_handle *ph, u32 domain)
{
return scmi_domain_reset(ph, domain, EXPLICIT_RESET_ASSERT,
ARCH_COLD_RESET);
}
static int
scmi_reset_domain_deassert(const struct scmi_protocol_handle *ph, u32 domain)
{
return scmi_domain_reset(ph, domain, 0, ARCH_COLD_RESET);
}
static const struct scmi_reset_proto_ops reset_proto_ops = {
.num_domains_get = scmi_reset_num_domains_get,
.name_get = scmi_reset_name_get,
.latency_get = scmi_reset_latency_get,
.reset = scmi_reset_domain_reset,
.assert = scmi_reset_domain_assert,
.deassert = scmi_reset_domain_deassert,
};
static bool scmi_reset_notify_supported(const struct scmi_protocol_handle *ph,
u8 evt_id, u32 src_id)
{
struct reset_dom_info *dom_info;
if (evt_id != SCMI_EVENT_RESET_ISSUED)
return false;
dom_info = scmi_reset_domain_lookup(ph, src_id);
if (IS_ERR(dom_info))
return false;
return dom_info->reset_notify;
}
static int scmi_reset_notify(const struct scmi_protocol_handle *ph,
u32 domain_id, bool enable)
{
int ret;
u32 evt_cntl = enable ? RESET_TP_NOTIFY_ALL : 0;
struct scmi_xfer *t;
struct scmi_msg_reset_notify *cfg;
ret = ph->xops->xfer_get_init(ph, RESET_NOTIFY, sizeof(*cfg), 0, &t);
if (ret)
return ret;
cfg = t->tx.buf;
cfg->id = cpu_to_le32(domain_id);
cfg->event_control = cpu_to_le32(evt_cntl);
ret = ph->xops->do_xfer(ph, t);
ph->xops->xfer_put(ph, t);
return ret;
}
static int scmi_reset_set_notify_enabled(const struct scmi_protocol_handle *ph,
u8 evt_id, u32 src_id, bool enable)
{
int ret;
ret = scmi_reset_notify(ph, src_id, enable);
if (ret)
pr_debug("FAIL_ENABLED - evt[%X] dom[%d] - ret:%d\n",
evt_id, src_id, ret);
return ret;
}
static void *
scmi_reset_fill_custom_report(const struct scmi_protocol_handle *ph,
u8 evt_id, ktime_t timestamp,
const void *payld, size_t payld_sz,
void *report, u32 *src_id)
{
const struct scmi_reset_issued_notify_payld *p = payld;
struct scmi_reset_issued_report *r = report;
if (evt_id != SCMI_EVENT_RESET_ISSUED || sizeof(*p) != payld_sz)
return NULL;
r->timestamp = timestamp;
r->agent_id = le32_to_cpu(p->agent_id);
r->domain_id = le32_to_cpu(p->domain_id);
r->reset_state = le32_to_cpu(p->reset_state);
*src_id = r->domain_id;
return r;
}
static int scmi_reset_get_num_sources(const struct scmi_protocol_handle *ph)
{
struct scmi_reset_info *pinfo = ph->get_priv(ph);
if (!pinfo)
return -EINVAL;
return pinfo->num_domains;
}
static const struct scmi_event reset_events[] = {
{
.id = SCMI_EVENT_RESET_ISSUED,
.max_payld_sz = sizeof(struct scmi_reset_issued_notify_payld),
.max_report_sz = sizeof(struct scmi_reset_issued_report),
},
};
static const struct scmi_event_ops reset_event_ops = {
.is_notify_supported = scmi_reset_notify_supported,
.get_num_sources = scmi_reset_get_num_sources,
.set_notify_enabled = scmi_reset_set_notify_enabled,
.fill_custom_report = scmi_reset_fill_custom_report,
};
static const struct scmi_protocol_events reset_protocol_events = {
.queue_sz = SCMI_PROTO_QUEUE_SZ,
.ops = &reset_event_ops,
.evts = reset_events,
.num_events = ARRAY_SIZE(reset_events),
};
static int scmi_reset_protocol_init(const struct scmi_protocol_handle *ph)
{
int domain, ret;
struct scmi_reset_info *pinfo;
dev_dbg(ph->dev, "Reset Version %d.%d\n",
PROTOCOL_REV_MAJOR(ph->version), PROTOCOL_REV_MINOR(ph->version));
pinfo = devm_kzalloc(ph->dev, sizeof(*pinfo), GFP_KERNEL);
if (!pinfo)
return -ENOMEM;
ret = scmi_reset_attributes_get(ph, pinfo);
if (ret)
return ret;
pinfo->dom_info = devm_kcalloc(ph->dev, pinfo->num_domains,
sizeof(*pinfo->dom_info), GFP_KERNEL);
if (!pinfo->dom_info)
return -ENOMEM;
for (domain = 0; domain < pinfo->num_domains; domain++)
scmi_reset_domain_attributes_get(ph, pinfo, domain);
return ph->set_priv(ph, pinfo);
}
static const struct scmi_protocol scmi_reset = {
.id = SCMI_PROTOCOL_RESET,
.owner = THIS_MODULE,
.instance_init = &scmi_reset_protocol_init,
.ops = &reset_proto_ops,
.events = &reset_protocol_events,
.supported_version = SCMI_PROTOCOL_SUPPORTED_VERSION,
};
DEFINE_SCMI_PROTOCOL_REGISTER_UNREGISTER(reset, scmi_reset)
@@ -0,0 +1,393 @@
// SPDX-License-Identifier: GPL-2.0
/*
* SCMI Generic SystemPower Control driver.
*
* Copyright (C) 2020-2022 ARM Ltd.
*/
/*
* In order to handle platform originated SCMI SystemPower requests (like
* shutdowns or cold/warm resets) we register an SCMI Notification notifier
* block to react when such SCMI SystemPower events are emitted by platform.
*
* Once such a notification is received we act accordingly to perform the
* required system transition depending on the kind of request.
*
* Graceful requests are routed to userspace through the same API methods
* (orderly_poweroff/reboot()) used by ACPI when handling ACPI Shutdown bus
* events.
*
* Direct forceful requests are not supported since are not meant to be sent
* by the SCMI platform to an OSPM like Linux.
*
* Additionally, graceful request notifications can carry an optional timeout
* field stating the maximum amount of time allowed by the platform for
* completion after which they are converted to forceful ones: the assumption
* here is that even graceful requests can be upper-bound by a maximum final
* timeout strictly enforced by the platform itself which can ultimately cut
* the power off at will anytime; in order to avoid such extreme scenario, we
* track progress of graceful requests through the means of a reboot notifier
* converting timed-out graceful requests to forceful ones, so at least we
* try to perform a clean sync and shutdown/restart before the power is cut.
*
* Given the peculiar nature of SCMI SystemPower protocol, that is being in
* charge of triggering system wide shutdown/reboot events, there should be
* only one SCMI platform actively emitting SystemPower events.
* For this reason the SCMI core takes care to enforce the creation of one
* single unique device associated to the SCMI System Power protocol; no matter
* how many SCMI platforms are defined on the system, only one can be designated
* to support System Power: as a consequence this driver will never be probed
* more than once.
*
* For similar reasons as soon as the first valid SystemPower is received by
* this driver and the shutdown/reboot is started, any further notification
* possibly emitted by the platform will be ignored.
*/
#include <linux/math.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/pm.h>
#include <linux/printk.h>
#include <linux/reboot.h>
#include <linux/scmi_protocol.h>
#include <linux/slab.h>
#include <linux/suspend.h>
#include <linux/time64.h>
#include <linux/timer.h>
#include <linux/types.h>
#include <linux/workqueue.h>
#ifndef MODULE
#include <linux/fs.h>
#endif
enum scmi_syspower_state {
SCMI_SYSPOWER_IDLE,
SCMI_SYSPOWER_IN_PROGRESS,
SCMI_SYSPOWER_REBOOTING
};
/**
* struct scmi_syspower_conf - Common configuration
*
* @dev: A reference device
* @state: Current SystemPower state
* @state_mtx: @state related mutex
* @required_transition: The requested transition as decribed in the received
* SCMI SystemPower notification
* @userspace_nb: The notifier_block registered against the SCMI SystemPower
* notification to start the needed userspace interactions.
* @reboot_nb: A notifier_block optionally used to track reboot progress
* @forceful_work: A worker used to trigger a forceful transition once a
* graceful has timed out.
* @suspend_work: A worker used to trigger system suspend
*/
struct scmi_syspower_conf {
struct device *dev;
enum scmi_syspower_state state;
/* Protect access to state */
struct mutex state_mtx;
enum scmi_system_events required_transition;
struct notifier_block userspace_nb;
struct notifier_block reboot_nb;
struct delayed_work forceful_work;
struct work_struct suspend_work;
};
#define userspace_nb_to_sconf(x) \
container_of(x, struct scmi_syspower_conf, userspace_nb)
#define reboot_nb_to_sconf(x) \
container_of(x, struct scmi_syspower_conf, reboot_nb)
#define dwork_to_sconf(x) \
container_of(x, struct scmi_syspower_conf, forceful_work)
/**
* scmi_reboot_notifier - A reboot notifier to catch an ongoing successful
* system transition
* @nb: Reference to the related notifier block
* @reason: The reason for the ongoing reboot
* @__unused: The cmd being executed on a restart request (unused)
*
* When an ongoing system transition is detected, compatible with the one
* requested by SCMI, cancel the delayed work.
*
* Return: NOTIFY_OK in any case
*/
static int scmi_reboot_notifier(struct notifier_block *nb,
unsigned long reason, void *__unused)
{
struct scmi_syspower_conf *sc = reboot_nb_to_sconf(nb);
mutex_lock(&sc->state_mtx);
switch (reason) {
case SYS_HALT:
case SYS_POWER_OFF:
if (sc->required_transition == SCMI_SYSTEM_SHUTDOWN)
sc->state = SCMI_SYSPOWER_REBOOTING;
break;
case SYS_RESTART:
if (sc->required_transition == SCMI_SYSTEM_COLDRESET ||
sc->required_transition == SCMI_SYSTEM_WARMRESET)
sc->state = SCMI_SYSPOWER_REBOOTING;
break;
default:
break;
}
if (sc->state == SCMI_SYSPOWER_REBOOTING) {
dev_dbg(sc->dev, "Reboot in progress...cancel delayed work.\n");
cancel_delayed_work_sync(&sc->forceful_work);
}
mutex_unlock(&sc->state_mtx);
return NOTIFY_OK;
}
/**
* scmi_request_forceful_transition - Request forceful SystemPower transition
* @sc: A reference to the configuration data
*
* Initiates the required SystemPower transition without involving userspace:
* just trigger the action at the kernel level after issuing an emergency
* sync. (if possible at all)
*/
static inline void
scmi_request_forceful_transition(struct scmi_syspower_conf *sc)
{
dev_dbg(sc->dev, "Serving forceful request:%d\n",
sc->required_transition);
#ifndef MODULE
emergency_sync();
#endif
switch (sc->required_transition) {
case SCMI_SYSTEM_SHUTDOWN:
kernel_power_off();
break;
case SCMI_SYSTEM_COLDRESET:
case SCMI_SYSTEM_WARMRESET:
kernel_restart(NULL);
break;
default:
break;
}
}
static void scmi_forceful_work_func(struct work_struct *work)
{
struct scmi_syspower_conf *sc;
struct delayed_work *dwork;
if (system_state > SYSTEM_RUNNING)
return;
dwork = to_delayed_work(work);
sc = dwork_to_sconf(dwork);
dev_dbg(sc->dev, "Graceful request timed out...forcing !\n");
mutex_lock(&sc->state_mtx);
/* avoid deadlock by unregistering reboot notifier first */
unregister_reboot_notifier(&sc->reboot_nb);
if (sc->state == SCMI_SYSPOWER_IN_PROGRESS)
scmi_request_forceful_transition(sc);
mutex_unlock(&sc->state_mtx);
}
/**
* scmi_request_graceful_transition - Request graceful SystemPower transition
* @sc: A reference to the configuration data
* @timeout_ms: The desired timeout to wait for the shutdown to complete before
* system is forcibly shutdown.
*
* Initiates the required SystemPower transition, requesting userspace
* co-operation: it uses the same orderly_ methods used by ACPI Shutdown event
* processing.
*
* Takes care also to register a reboot notifier and to schedule a delayed work
* in order to detect if userspace actions are taking too long and in such a
* case to trigger a forceful transition.
*/
static void scmi_request_graceful_transition(struct scmi_syspower_conf *sc,
unsigned int timeout_ms)
{
unsigned int adj_timeout_ms = 0;
if (timeout_ms) {
int ret;
sc->reboot_nb.notifier_call = &scmi_reboot_notifier;
ret = register_reboot_notifier(&sc->reboot_nb);
if (!ret) {
/* Wait only up to 75% of the advertised timeout */
adj_timeout_ms = mult_frac(timeout_ms, 3, 4);
INIT_DELAYED_WORK(&sc->forceful_work,
scmi_forceful_work_func);
schedule_delayed_work(&sc->forceful_work,
msecs_to_jiffies(adj_timeout_ms));
} else {
/* Carry on best effort even without a reboot notifier */
dev_warn(sc->dev,
"Cannot register reboot notifier !\n");
}
}
dev_dbg(sc->dev,
"Serving graceful req:%d (timeout_ms:%u adj_timeout_ms:%u)\n",
sc->required_transition, timeout_ms, adj_timeout_ms);
switch (sc->required_transition) {
case SCMI_SYSTEM_SHUTDOWN:
/*
* When triggered early at boot-time the 'orderly' call will
* partially fail due to the lack of userspace itself, but
* the force=true argument will start anyway a successful
* forced shutdown.
*/
orderly_poweroff(true);
break;
case SCMI_SYSTEM_COLDRESET:
case SCMI_SYSTEM_WARMRESET:
orderly_reboot();
break;
case SCMI_SYSTEM_SUSPEND:
schedule_work(&sc->suspend_work);
break;
default:
break;
}
}
/**
* scmi_userspace_notifier - Notifier callback to act on SystemPower
* Notifications
* @nb: Reference to the related notifier block
* @event: The SystemPower notification event id
* @data: The SystemPower event report
*
* This callback is in charge of decoding the received SystemPower report
* and act accordingly triggering a graceful or forceful system transition.
*
* Note that once a valid SCMI SystemPower event starts being served, any
* other following SystemPower notification received from the same SCMI
* instance (handle) will be ignored.
*
* Return: NOTIFY_OK once a valid SystemPower event has been successfully
* processed.
*/
static int scmi_userspace_notifier(struct notifier_block *nb,
unsigned long event, void *data)
{
struct scmi_system_power_state_notifier_report *er = data;
struct scmi_syspower_conf *sc = userspace_nb_to_sconf(nb);
if (er->system_state >= SCMI_SYSTEM_MAX ||
er->system_state == SCMI_SYSTEM_POWERUP) {
dev_err(sc->dev, "Ignoring unsupported system_state: 0x%X\n",
er->system_state);
return NOTIFY_DONE;
}
if (!SCMI_SYSPOWER_IS_REQUEST_GRACEFUL(er->flags)) {
dev_err(sc->dev, "Ignoring forceful notification.\n");
return NOTIFY_DONE;
}
/*
* Bail out if system is already shutting down or an SCMI SystemPower
* requested is already being served.
*/
if (system_state > SYSTEM_RUNNING)
return NOTIFY_DONE;
mutex_lock(&sc->state_mtx);
if (sc->state != SCMI_SYSPOWER_IDLE) {
dev_dbg(sc->dev,
"Transition already in progress...ignore.\n");
mutex_unlock(&sc->state_mtx);
return NOTIFY_DONE;
}
sc->state = SCMI_SYSPOWER_IN_PROGRESS;
mutex_unlock(&sc->state_mtx);
sc->required_transition = er->system_state;
/* Leaving a trace in logs of who triggered the shutdown/reboot. */
dev_info(sc->dev, "Serving shutdown/reboot request: %d\n",
sc->required_transition);
scmi_request_graceful_transition(sc, er->timeout);
return NOTIFY_OK;
}
static void scmi_suspend_work_func(struct work_struct *work)
{
pm_suspend(PM_SUSPEND_MEM);
}
static int scmi_syspower_probe(struct scmi_device *sdev)
{
int ret;
struct scmi_syspower_conf *sc;
struct scmi_handle *handle = sdev->handle;
if (!handle)
return -ENODEV;
ret = handle->devm_protocol_acquire(sdev, SCMI_PROTOCOL_SYSTEM);
if (ret)
return ret;
sc = devm_kzalloc(&sdev->dev, sizeof(*sc), GFP_KERNEL);
if (!sc)
return -ENOMEM;
sc->state = SCMI_SYSPOWER_IDLE;
mutex_init(&sc->state_mtx);
sc->required_transition = SCMI_SYSTEM_MAX;
sc->userspace_nb.notifier_call = &scmi_userspace_notifier;
sc->dev = &sdev->dev;
dev_set_drvdata(&sdev->dev, sc);
INIT_WORK(&sc->suspend_work, scmi_suspend_work_func);
return handle->notify_ops->devm_event_notifier_register(sdev,
SCMI_PROTOCOL_SYSTEM,
SCMI_EVENT_SYSTEM_POWER_STATE_NOTIFIER,
NULL, &sc->userspace_nb);
}
static int scmi_system_power_resume(struct device *dev)
{
struct scmi_syspower_conf *sc = dev_get_drvdata(dev);
sc->state = SCMI_SYSPOWER_IDLE;
return 0;
}
static const struct dev_pm_ops scmi_system_power_pmops = {
SYSTEM_SLEEP_PM_OPS(NULL, scmi_system_power_resume)
};
static const struct scmi_device_id scmi_id_table[] = {
{ SCMI_PROTOCOL_SYSTEM, "syspower" },
{ },
};
MODULE_DEVICE_TABLE(scmi, scmi_id_table);
static struct scmi_driver scmi_system_power_driver = {
.driver = {
.pm = pm_sleep_ptr(&scmi_system_power_pmops),
},
.name = "scmi-system-power",
.probe = scmi_syspower_probe,
.id_table = scmi_id_table,
};
module_scmi_driver(scmi_system_power_driver);
MODULE_AUTHOR("Cristian Marussi <cristian.marussi@arm.com>");
MODULE_DESCRIPTION("ARM SCMI SystemPower Control driver");
MODULE_LICENSE("GPL");
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,266 @@
// SPDX-License-Identifier: GPL-2.0
/*
* For transport using shared mem structure.
*
* Copyright (C) 2019-2024 ARM Ltd.
*/
#include <linux/ktime.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/processor.h>
#include <linux/types.h>
#include <linux/bug.h>
#include "common.h"
#define SCMI_SHMEM_LAYOUT_OVERHEAD 24
/*
* 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[];
};
static inline void shmem_memcpy_fromio32(void *to,
const void __iomem *from,
size_t count)
{
WARN_ON(!IS_ALIGNED((unsigned long)from, 4) ||
!IS_ALIGNED((unsigned long)to, 4) ||
count % 4);
__ioread32_copy(to, from, count / 4);
}
static inline void shmem_memcpy_toio32(void __iomem *to,
const void *from,
size_t count)
{
WARN_ON(!IS_ALIGNED((unsigned long)to, 4) ||
!IS_ALIGNED((unsigned long)from, 4) ||
count % 4);
__iowrite32_copy(to, from, count / 4);
}
static struct scmi_shmem_io_ops shmem_io_ops32 = {
.fromio = shmem_memcpy_fromio32,
.toio = shmem_memcpy_toio32,
};
/* Wrappers are needed for proper memcpy_{from,to}_io expansion by the
* pre-processor.
*/
static inline void shmem_memcpy_fromio(void *to,
const void __iomem *from,
size_t count)
{
memcpy_fromio(to, from, count);
}
static inline void shmem_memcpy_toio(void __iomem *to,
const void *from,
size_t count)
{
memcpy_toio(to, from, count);
}
static struct scmi_shmem_io_ops shmem_io_ops_default = {
.fromio = shmem_memcpy_fromio,
.toio = shmem_memcpy_toio,
};
static void shmem_tx_prepare(struct scmi_shared_mem __iomem *shmem,
struct scmi_xfer *xfer,
struct scmi_chan_info *cinfo,
shmem_copy_toio_t copy_toio)
{
ktime_t stop;
/*
* Ideally channel must be free by now unless OS timeout last
* request and platform continued to process the same, wait
* until it releases the shared memory, otherwise we may endup
* overwriting its response with new message payload or vice-versa.
* Giving up anyway after twice the expected channel timeout so as
* not to bail-out on intermittent issues where the platform is
* occasionally a bit slower to answer.
*
* Note that after a timeout is detected we bail-out and carry on but
* the transport functionality is probably permanently compromised:
* this is just to ease debugging and avoid complete hangs on boot
* due to a misbehaving SCMI firmware.
*/
stop = ktime_add_ms(ktime_get(), 2 * cinfo->rx_timeout_ms);
spin_until_cond((ioread32(&shmem->channel_status) &
SCMI_SHMEM_CHAN_STAT_CHANNEL_FREE) ||
ktime_after(ktime_get(), stop));
if (!(ioread32(&shmem->channel_status) &
SCMI_SHMEM_CHAN_STAT_CHANNEL_FREE)) {
WARN_ON_ONCE(1);
dev_err(cinfo->dev,
"Timeout waiting for a free TX channel !\n");
return;
}
/* Mark channel busy + clear error */
iowrite32(0x0, &shmem->channel_status);
iowrite32(xfer->hdr.poll_completion ? 0 : SCMI_SHMEM_FLAG_INTR_ENABLED,
&shmem->flags);
iowrite32(sizeof(shmem->msg_header) + xfer->tx.len, &shmem->length);
iowrite32(pack_scmi_header(&xfer->hdr), &shmem->msg_header);
if (xfer->tx.buf)
copy_toio(shmem->msg_payload, xfer->tx.buf, xfer->tx.len);
}
static u32 shmem_read_header(struct scmi_shared_mem __iomem *shmem)
{
return ioread32(&shmem->msg_header);
}
static void shmem_fetch_response(struct scmi_shared_mem __iomem *shmem,
struct scmi_xfer *xfer,
shmem_copy_fromio_t copy_fromio)
{
size_t len = ioread32(&shmem->length);
xfer->hdr.status = ioread32(shmem->msg_payload);
/* Skip the length of header and status in shmem area i.e 8 bytes */
xfer->rx.len = min_t(size_t, xfer->rx.len, len > 8 ? len - 8 : 0);
/* Take a copy to the rx buffer.. */
copy_fromio(xfer->rx.buf, shmem->msg_payload + 4, xfer->rx.len);
}
static void shmem_fetch_notification(struct scmi_shared_mem __iomem *shmem,
size_t max_len, struct scmi_xfer *xfer,
shmem_copy_fromio_t copy_fromio)
{
size_t len = ioread32(&shmem->length);
/* Skip only the length of header in shmem area i.e 4 bytes */
xfer->rx.len = min_t(size_t, max_len, len > 4 ? len - 4 : 0);
/* Take a copy to the rx buffer.. */
copy_fromio(xfer->rx.buf, shmem->msg_payload, xfer->rx.len);
}
static void shmem_clear_channel(struct scmi_shared_mem __iomem *shmem)
{
iowrite32(SCMI_SHMEM_CHAN_STAT_CHANNEL_FREE, &shmem->channel_status);
}
static bool shmem_poll_done(struct scmi_shared_mem __iomem *shmem,
struct scmi_xfer *xfer)
{
u16 xfer_id;
xfer_id = MSG_XTRACT_TOKEN(ioread32(&shmem->msg_header));
if (xfer->hdr.seq != xfer_id)
return false;
return ioread32(&shmem->channel_status) &
(SCMI_SHMEM_CHAN_STAT_CHANNEL_ERROR |
SCMI_SHMEM_CHAN_STAT_CHANNEL_FREE);
}
static bool shmem_channel_free(struct scmi_shared_mem __iomem *shmem)
{
return (ioread32(&shmem->channel_status) &
SCMI_SHMEM_CHAN_STAT_CHANNEL_FREE);
}
static bool shmem_channel_intr_enabled(struct scmi_shared_mem __iomem *shmem)
{
return ioread32(&shmem->flags) & SCMI_SHMEM_FLAG_INTR_ENABLED;
}
static void __iomem *shmem_setup_iomap(struct scmi_chan_info *cinfo,
struct device *dev, bool tx,
struct resource *res,
struct scmi_shmem_io_ops **ops)
{
const char *desc = tx ? "Tx" : "Rx";
int ret, idx = tx ? 0 : 1;
struct device *cdev = cinfo->dev;
struct resource lres = {};
resource_size_t size;
void __iomem *addr;
u32 reg_io_width;
struct device_node *shmem __free(device_node) = of_parse_phandle(cdev->of_node,
"shmem", idx);
if (!shmem)
return IOMEM_ERR_PTR(-ENODEV);
if (!of_device_is_compatible(shmem, "arm,scmi-shmem"))
return IOMEM_ERR_PTR(-ENXIO);
/* Use a local on-stack as a working area when not provided */
if (!res)
res = &lres;
ret = of_address_to_resource(shmem, 0, res);
if (ret) {
dev_err(cdev, "failed to get SCMI %s shared memory\n", desc);
return IOMEM_ERR_PTR(ret);
}
size = resource_size(res);
if (cinfo->max_msg_size + SCMI_SHMEM_LAYOUT_OVERHEAD > size) {
dev_err(dev, "misconfigured SCMI shared memory\n");
return IOMEM_ERR_PTR(-ENOSPC);
}
addr = devm_ioremap(dev, res->start, size);
if (!addr) {
dev_err(dev, "failed to ioremap SCMI %s shared memory\n", desc);
return IOMEM_ERR_PTR(-EADDRNOTAVAIL);
}
of_property_read_u32(shmem, "reg-io-width", &reg_io_width);
switch (reg_io_width) {
case 4:
*ops = &shmem_io_ops32;
break;
default:
*ops = &shmem_io_ops_default;
break;
}
return addr;
}
static const struct scmi_shared_mem_operations scmi_shmem_ops = {
.tx_prepare = shmem_tx_prepare,
.read_header = shmem_read_header,
.fetch_response = shmem_fetch_response,
.fetch_notification = shmem_fetch_notification,
.clear_channel = shmem_clear_channel,
.poll_done = shmem_poll_done,
.channel_free = shmem_channel_free,
.channel_intr_enabled = shmem_channel_intr_enabled,
.setup_iomap = shmem_setup_iomap,
};
const struct scmi_shared_mem_operations *scmi_shared_mem_operations_get(void)
{
return &scmi_shmem_ops;
}
@@ -0,0 +1,170 @@
// SPDX-License-Identifier: GPL-2.0
/*
* System Control and Management Interface (SCMI) System Power Protocol
*
* Copyright (C) 2020-2022 ARM Ltd.
*/
#define pr_fmt(fmt) "SCMI Notifications SYSTEM - " fmt
#include <linux/module.h>
#include <linux/scmi_protocol.h>
#include "protocols.h"
#include "notify.h"
/* Updated only after ALL the mandatory features for that version are merged */
#define SCMI_PROTOCOL_SUPPORTED_VERSION 0x20001
#define SCMI_SYSTEM_NUM_SOURCES 1
enum scmi_system_protocol_cmd {
SYSTEM_POWER_STATE_NOTIFY = 0x5,
};
struct scmi_system_power_state_notify {
__le32 notify_enable;
};
struct scmi_system_power_state_notifier_payld {
__le32 agent_id;
__le32 flags;
__le32 system_state;
__le32 timeout;
};
struct scmi_system_info {
bool graceful_timeout_supported;
bool power_state_notify_cmd;
};
static bool scmi_system_notify_supported(const struct scmi_protocol_handle *ph,
u8 evt_id, u32 src_id)
{
struct scmi_system_info *pinfo = ph->get_priv(ph);
if (evt_id != SCMI_EVENT_SYSTEM_POWER_STATE_NOTIFIER)
return false;
return pinfo->power_state_notify_cmd;
}
static int scmi_system_request_notify(const struct scmi_protocol_handle *ph,
bool enable)
{
int ret;
struct scmi_xfer *t;
struct scmi_system_power_state_notify *notify;
ret = ph->xops->xfer_get_init(ph, SYSTEM_POWER_STATE_NOTIFY,
sizeof(*notify), 0, &t);
if (ret)
return ret;
notify = t->tx.buf;
notify->notify_enable = enable ? cpu_to_le32(BIT(0)) : 0;
ret = ph->xops->do_xfer(ph, t);
ph->xops->xfer_put(ph, t);
return ret;
}
static int scmi_system_set_notify_enabled(const struct scmi_protocol_handle *ph,
u8 evt_id, u32 src_id, bool enable)
{
int ret;
ret = scmi_system_request_notify(ph, enable);
if (ret)
pr_debug("FAIL_ENABLE - evt[%X] - ret:%d\n", evt_id, ret);
return ret;
}
static void *
scmi_system_fill_custom_report(const struct scmi_protocol_handle *ph,
u8 evt_id, ktime_t timestamp,
const void *payld, size_t payld_sz,
void *report, u32 *src_id)
{
size_t expected_sz;
const struct scmi_system_power_state_notifier_payld *p = payld;
struct scmi_system_power_state_notifier_report *r = report;
struct scmi_system_info *pinfo = ph->get_priv(ph);
expected_sz = pinfo->graceful_timeout_supported ?
sizeof(*p) : sizeof(*p) - sizeof(__le32);
if (evt_id != SCMI_EVENT_SYSTEM_POWER_STATE_NOTIFIER ||
payld_sz != expected_sz)
return NULL;
r->timestamp = timestamp;
r->agent_id = le32_to_cpu(p->agent_id);
r->flags = le32_to_cpu(p->flags);
r->system_state = le32_to_cpu(p->system_state);
if (pinfo->graceful_timeout_supported &&
r->system_state == SCMI_SYSTEM_SHUTDOWN &&
SCMI_SYSPOWER_IS_REQUEST_GRACEFUL(r->flags))
r->timeout = le32_to_cpu(p->timeout);
else
r->timeout = 0x00;
*src_id = 0;
return r;
}
static const struct scmi_event system_events[] = {
{
.id = SCMI_EVENT_SYSTEM_POWER_STATE_NOTIFIER,
.max_payld_sz =
sizeof(struct scmi_system_power_state_notifier_payld),
.max_report_sz =
sizeof(struct scmi_system_power_state_notifier_report),
},
};
static const struct scmi_event_ops system_event_ops = {
.is_notify_supported = scmi_system_notify_supported,
.set_notify_enabled = scmi_system_set_notify_enabled,
.fill_custom_report = scmi_system_fill_custom_report,
};
static const struct scmi_protocol_events system_protocol_events = {
.queue_sz = SCMI_PROTO_QUEUE_SZ,
.ops = &system_event_ops,
.evts = system_events,
.num_events = ARRAY_SIZE(system_events),
.num_sources = SCMI_SYSTEM_NUM_SOURCES,
};
static int scmi_system_protocol_init(const struct scmi_protocol_handle *ph)
{
struct scmi_system_info *pinfo;
dev_dbg(ph->dev, "System Power Version %d.%d\n",
PROTOCOL_REV_MAJOR(ph->version), PROTOCOL_REV_MINOR(ph->version));
pinfo = devm_kzalloc(ph->dev, sizeof(*pinfo), GFP_KERNEL);
if (!pinfo)
return -ENOMEM;
if (PROTOCOL_REV_MAJOR(ph->version) >= 0x2)
pinfo->graceful_timeout_supported = true;
if (!ph->hops->protocol_msg_check(ph, SYSTEM_POWER_STATE_NOTIFY, NULL))
pinfo->power_state_notify_cmd = true;
return ph->set_priv(ph, pinfo);
}
static const struct scmi_protocol scmi_system = {
.id = SCMI_PROTOCOL_SYSTEM,
.owner = THIS_MODULE,
.instance_init = &scmi_system_protocol_init,
.ops = NULL,
.events = &system_protocol_events,
.supported_version = SCMI_PROTOCOL_SUPPORTED_VERSION,
};
DEFINE_SCMI_PROTOCOL_REGISTER_UNREGISTER(system, scmi_system)
@@ -0,0 +1,123 @@
# SPDX-License-Identifier: GPL-2.0-only
menu "SCMI Transport Drivers"
config ARM_SCMI_HAVE_TRANSPORT
bool
help
This declares whether at least one SCMI transport has been configured.
Used to trigger a build bug when trying to build SCMI without any
configured transport.
config ARM_SCMI_HAVE_SHMEM
bool
help
This declares whether a shared memory based transport for SCMI is
available.
config ARM_SCMI_HAVE_MSG
bool
help
This declares whether a message passing based transport for SCMI is
available.
config ARM_SCMI_TRANSPORT_MAILBOX
tristate "SCMI transport based on Mailbox"
depends on MAILBOX
select ARM_SCMI_HAVE_TRANSPORT
select ARM_SCMI_HAVE_SHMEM
default y
help
Enable mailbox based transport for SCMI.
If you want the ARM SCMI PROTOCOL stack to include support for a
transport based on mailboxes, answer Y.
This driver can also be built as a module. If so, the module
will be called scmi_transport_mailbox.
config ARM_SCMI_TRANSPORT_SMC
tristate "SCMI transport based on SMC"
depends on HAVE_ARM_SMCCC_DISCOVERY
select ARM_SCMI_HAVE_TRANSPORT
select ARM_SCMI_HAVE_SHMEM
default y
help
Enable SMC based transport for SCMI.
If you want the ARM SCMI PROTOCOL stack to include support for a
transport based on SMC, answer Y.
This driver can also be built as a module. If so, the module
will be called scmi_transport_smc.
config ARM_SCMI_TRANSPORT_SMC_ATOMIC_ENABLE
bool "Enable atomic mode support for SCMI SMC transport"
depends on ARM_SCMI_TRANSPORT_SMC
help
Enable support of atomic operation for SCMI SMC based transport.
If you want the SCMI SMC based transport to operate in atomic
mode, avoiding any kind of sleeping behaviour for selected
transactions on the TX path, answer Y.
Enabling atomic mode operations allows any SCMI driver using this
transport to optionally ask for atomic SCMI transactions and operate
in atomic context too, at the price of using a number of busy-waiting
primitives all over instead. If unsure say N.
config ARM_SCMI_TRANSPORT_OPTEE
tristate "SCMI transport based on OP-TEE service"
depends on OPTEE
select ARM_SCMI_HAVE_TRANSPORT
select ARM_SCMI_HAVE_SHMEM
select ARM_SCMI_HAVE_MSG
default y
help
This enables the OP-TEE service based transport for SCMI.
If you want the ARM SCMI PROTOCOL stack to include support for a
transport based on OP-TEE SCMI service, answer Y.
This driver can also be built as a module. If so, the module
will be called scmi_transport_optee.
config ARM_SCMI_TRANSPORT_VIRTIO
tristate "SCMI transport based on VirtIO"
depends on VIRTIO
select ARM_SCMI_HAVE_TRANSPORT
select ARM_SCMI_HAVE_MSG
help
This enables the virtio based transport for SCMI.
If you want the ARM SCMI PROTOCOL stack to include support for a
transport based on VirtIO, answer Y.
This driver can also be built as a module. If so, the module
will be called scmi_transport_virtio.
config ARM_SCMI_TRANSPORT_VIRTIO_VERSION1_COMPLIANCE
bool "SCMI VirtIO transport Version 1 compliance"
depends on ARM_SCMI_TRANSPORT_VIRTIO
default y
help
This enforces strict compliance with VirtIO Version 1 specification.
If you want the ARM SCMI VirtIO transport layer to refuse to work
with Legacy VirtIO backends and instead support only VirtIO Version 1
devices (or above), answer Y.
If you want instead to support also old Legacy VirtIO backends (like
the ones implemented by kvmtool) and let the core Kernel VirtIO layer
take care of the needed conversions, say N.
config ARM_SCMI_TRANSPORT_VIRTIO_ATOMIC_ENABLE
bool "Enable atomic mode for SCMI VirtIO transport"
depends on ARM_SCMI_TRANSPORT_VIRTIO
help
Enable support of atomic operation for SCMI VirtIO based transport.
If you want the SCMI VirtIO based transport to operate in atomic
mode, avoiding any kind of sleeping behaviour for selected
transactions on the TX path, answer Y.
Enabling atomic mode operations allows any SCMI driver using this
transport to optionally ask for atomic SCMI transactions and operate
in atomic context too, at the price of using a number of busy-waiting
primitives all over instead. If unsure say N.
endmenu
@@ -0,0 +1,18 @@
# SPDX-License-Identifier: GPL-2.0-only
# Keep before scmi_transport_mailbox.o to allow precedence
# while matching the compatible.
scmi_transport_smc-objs := smc.o
obj-$(CONFIG_ARM_SCMI_TRANSPORT_SMC) += scmi_transport_smc.o
scmi_transport_mailbox-objs := mailbox.o
obj-$(CONFIG_ARM_SCMI_TRANSPORT_MAILBOX) += scmi_transport_mailbox.o
scmi_transport_optee-objs := optee.o
obj-$(CONFIG_ARM_SCMI_TRANSPORT_OPTEE) += scmi_transport_optee.o
scmi_transport_virtio-objs := virtio.o
obj-$(CONFIG_ARM_SCMI_TRANSPORT_VIRTIO) += scmi_transport_virtio.o
ifeq ($(CONFIG_THUMB2_KERNEL)$(CONFIG_CC_IS_CLANG),yy)
# The use of R7 in the SMCCC conflicts with the compiler's use of R7 as a frame
# pointer in Thumb2 mode, which is forcibly enabled by Clang when profiling
# hooks are inserted via the -pg switch.
CFLAGS_REMOVE_smc.o += $(CC_FLAGS_FTRACE)
endif
@@ -0,0 +1,388 @@
// SPDX-License-Identifier: GPL-2.0
/*
* System Control and Management Interface (SCMI) Message Mailbox Transport
* driver.
*
* Copyright (C) 2019-2024 ARM Ltd.
*/
#include <linux/err.h>
#include <linux/device.h>
#include <linux/mailbox_client.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include "../common.h"
/**
* struct scmi_mailbox - Structure representing a SCMI mailbox transport
*
* @cl: Mailbox Client
* @chan: Transmit/Receive mailbox uni/bi-directional channel
* @chan_receiver: Optional Receiver mailbox unidirectional channel
* @chan_platform_receiver: Optional Platform Receiver mailbox unidirectional channel
* @cinfo: SCMI channel info
* @shmem: Transmit/Receive shared memory area
* @chan_lock: Lock that prevents multiple xfers from being queued
* @io_ops: Transport specific I/O operations
*/
struct scmi_mailbox {
struct mbox_client cl;
struct mbox_chan *chan;
struct mbox_chan *chan_receiver;
struct mbox_chan *chan_platform_receiver;
struct scmi_chan_info *cinfo;
struct scmi_shared_mem __iomem *shmem;
struct mutex chan_lock;
struct scmi_shmem_io_ops *io_ops;
};
#define client_to_scmi_mailbox(c) container_of(c, struct scmi_mailbox, cl)
static struct scmi_transport_core_operations *core;
static void tx_prepare(struct mbox_client *cl, void *m)
{
struct scmi_mailbox *smbox = client_to_scmi_mailbox(cl);
core->shmem->tx_prepare(smbox->shmem, m, smbox->cinfo,
smbox->io_ops->toio);
}
static void rx_callback(struct mbox_client *cl, void *m)
{
struct scmi_mailbox *smbox = client_to_scmi_mailbox(cl);
/*
* An A2P IRQ is NOT valid when received while the platform still has
* the ownership of the channel, because the platform at first releases
* the SMT channel and then sends the completion interrupt.
*
* This addresses a possible race condition in which a spurious IRQ from
* a previous timed-out reply which arrived late could be wrongly
* associated with the next pending transaction.
*/
if (cl->knows_txdone &&
!core->shmem->channel_free(smbox->shmem)) {
dev_warn(smbox->cinfo->dev, "Ignoring spurious A2P IRQ !\n");
core->bad_message_trace(smbox->cinfo,
core->shmem->read_header(smbox->shmem),
MSG_MBOX_SPURIOUS);
return;
}
core->rx_callback(smbox->cinfo,
core->shmem->read_header(smbox->shmem), NULL);
}
static bool mailbox_chan_available(struct device_node *of_node, int idx)
{
int num_mb;
/*
* Just check if bidirrectional channels are involved, and check the
* index accordingly; proper full validation will be made later
* in mailbox_chan_setup().
*/
num_mb = of_count_phandle_with_args(of_node, "mboxes", "#mbox-cells");
if (num_mb == 3 && idx == 1)
idx = 2;
return !of_parse_phandle_with_args(of_node, "mboxes",
"#mbox-cells", idx, NULL);
}
/**
* mailbox_chan_validate - Validate transport configuration and map channels
*
* @cdev: Reference to the underlying transport device carrying the
* of_node descriptor to analyze.
* @a2p_rx_chan: A reference to an optional unidirectional channel to use
* for replies on the a2p channel. Set as zero if not present.
* @p2a_chan: A reference to the optional p2a channel.
* Set as zero if not present.
* @p2a_rx_chan: A reference to the optional p2a completion channel.
* Set as zero if not present.
*
* At first, validate the transport configuration as described in terms of
* 'mboxes' and 'shmem', then determin which mailbox channel indexes are
* appropriate to be use in the current configuration.
*
* Return: 0 on Success or error
*/
static int mailbox_chan_validate(struct device *cdev, int *a2p_rx_chan,
int *p2a_chan, int *p2a_rx_chan)
{
int num_mb, num_sh, ret = 0;
struct device_node *np = cdev->of_node;
num_mb = of_count_phandle_with_args(np, "mboxes", "#mbox-cells");
num_sh = of_count_phandle_with_args(np, "shmem", NULL);
dev_dbg(cdev, "Found %d mboxes and %d shmems !\n", num_mb, num_sh);
/* Bail out if mboxes and shmem descriptors are inconsistent */
if (num_mb <= 0 || num_sh <= 0 || num_sh > 2 || num_mb > 4 ||
(num_mb == 1 && num_sh != 1) || (num_mb == 3 && num_sh != 2) ||
(num_mb == 4 && num_sh != 2)) {
dev_warn(cdev,
"Invalid channel descriptor for '%pOF' - mbs:%d shm:%d\n",
np, num_mb, num_sh);
return -EINVAL;
}
/* Bail out if provided shmem descriptors do not refer distinct areas */
if (num_sh > 1) {
struct device_node *np_tx __free(device_node) =
of_parse_phandle(np, "shmem", 0);
struct device_node *np_rx __free(device_node) =
of_parse_phandle(np, "shmem", 1);
if (!np_tx || !np_rx || np_tx == np_rx) {
dev_warn(cdev, "Invalid shmem descriptor for '%pOF'\n", np);
ret = -EINVAL;
}
}
/* Calculate channels IDs to use depending on mboxes/shmem layout */
if (!ret) {
switch (num_mb) {
case 1:
*a2p_rx_chan = 0;
*p2a_chan = 0;
*p2a_rx_chan = 0;
break;
case 2:
if (num_sh == 2) {
*a2p_rx_chan = 0;
*p2a_chan = 1;
} else {
*a2p_rx_chan = 1;
*p2a_chan = 0;
}
*p2a_rx_chan = 0;
break;
case 3:
*a2p_rx_chan = 1;
*p2a_chan = 2;
*p2a_rx_chan = 0;
break;
case 4:
*a2p_rx_chan = 1;
*p2a_chan = 2;
*p2a_rx_chan = 3;
break;
}
}
return ret;
}
static int mailbox_chan_setup(struct scmi_chan_info *cinfo, struct device *dev,
bool tx)
{
const char *desc = tx ? "Tx" : "Rx";
struct device *cdev = cinfo->dev;
struct scmi_mailbox *smbox;
int ret, a2p_rx_chan, p2a_chan, p2a_rx_chan;
struct mbox_client *cl;
ret = mailbox_chan_validate(cdev, &a2p_rx_chan, &p2a_chan, &p2a_rx_chan);
if (ret)
return ret;
if (!tx && !p2a_chan)
return -ENODEV;
smbox = devm_kzalloc(dev, sizeof(*smbox), GFP_KERNEL);
if (!smbox)
return -ENOMEM;
smbox->shmem = core->shmem->setup_iomap(cinfo, dev, tx, NULL,
&smbox->io_ops);
if (IS_ERR(smbox->shmem))
return PTR_ERR(smbox->shmem);
cl = &smbox->cl;
cl->dev = cdev;
cl->tx_prepare = tx ? tx_prepare : NULL;
cl->rx_callback = rx_callback;
cl->tx_block = false;
cl->knows_txdone = tx;
smbox->chan = mbox_request_channel(cl, tx ? 0 : p2a_chan);
if (IS_ERR(smbox->chan)) {
ret = PTR_ERR(smbox->chan);
if (ret != -EPROBE_DEFER)
dev_err(cdev,
"failed to request SCMI %s mailbox\n", desc);
return ret;
}
/* Additional unidirectional channel for TX if needed */
if (tx && a2p_rx_chan) {
smbox->chan_receiver = mbox_request_channel(cl, a2p_rx_chan);
if (IS_ERR(smbox->chan_receiver)) {
ret = PTR_ERR(smbox->chan_receiver);
if (ret != -EPROBE_DEFER)
dev_err(cdev, "failed to request SCMI Tx Receiver mailbox\n");
return ret;
}
}
if (!tx && p2a_rx_chan) {
smbox->chan_platform_receiver = mbox_request_channel(cl, p2a_rx_chan);
if (IS_ERR(smbox->chan_platform_receiver)) {
ret = PTR_ERR(smbox->chan_platform_receiver);
if (ret != -EPROBE_DEFER)
dev_err(cdev, "failed to request SCMI P2A Receiver mailbox\n");
return ret;
}
}
cinfo->transport_info = smbox;
smbox->cinfo = cinfo;
mutex_init(&smbox->chan_lock);
return 0;
}
static int mailbox_chan_free(int id, void *p, void *data)
{
struct scmi_chan_info *cinfo = p;
struct scmi_mailbox *smbox = cinfo->transport_info;
if (smbox && !IS_ERR(smbox->chan)) {
mbox_free_channel(smbox->chan);
mbox_free_channel(smbox->chan_receiver);
mbox_free_channel(smbox->chan_platform_receiver);
cinfo->transport_info = NULL;
smbox->chan = NULL;
smbox->chan_receiver = NULL;
smbox->chan_platform_receiver = NULL;
smbox->cinfo = NULL;
}
return 0;
}
static int mailbox_send_message(struct scmi_chan_info *cinfo,
struct scmi_xfer *xfer)
{
struct scmi_mailbox *smbox = cinfo->transport_info;
int ret;
/*
* The mailbox layer has its own queue. However the mailbox queue
* confuses the per message SCMI timeouts since the clock starts when
* the message is submitted into the mailbox queue. So when multiple
* messages are queued up the clock starts on all messages instead of
* only the one inflight.
*/
mutex_lock(&smbox->chan_lock);
ret = mbox_send_message(smbox->chan, xfer);
/* mbox_send_message returns non-negative value on success */
if (ret < 0) {
mutex_unlock(&smbox->chan_lock);
return ret;
}
return 0;
}
static void mailbox_mark_txdone(struct scmi_chan_info *cinfo, int ret,
struct scmi_xfer *__unused)
{
struct scmi_mailbox *smbox = cinfo->transport_info;
mbox_client_txdone(smbox->chan, ret);
/* Release channel */
mutex_unlock(&smbox->chan_lock);
}
static void mailbox_fetch_response(struct scmi_chan_info *cinfo,
struct scmi_xfer *xfer)
{
struct scmi_mailbox *smbox = cinfo->transport_info;
core->shmem->fetch_response(smbox->shmem, xfer, smbox->io_ops->fromio);
}
static void mailbox_fetch_notification(struct scmi_chan_info *cinfo,
size_t max_len, struct scmi_xfer *xfer)
{
struct scmi_mailbox *smbox = cinfo->transport_info;
core->shmem->fetch_notification(smbox->shmem, max_len, xfer,
smbox->io_ops->fromio);
}
static void mailbox_clear_channel(struct scmi_chan_info *cinfo)
{
struct scmi_mailbox *smbox = cinfo->transport_info;
struct mbox_chan *intr_chan;
int ret;
core->shmem->clear_channel(smbox->shmem);
if (!core->shmem->channel_intr_enabled(smbox->shmem))
return;
if (smbox->chan_platform_receiver)
intr_chan = smbox->chan_platform_receiver;
else if (smbox->chan)
intr_chan = smbox->chan;
else
return;
ret = mbox_send_message(intr_chan, NULL);
/* mbox_send_message returns non-negative value on success, so reset */
if (ret > 0)
ret = 0;
mbox_client_txdone(intr_chan, ret);
}
static bool
mailbox_poll_done(struct scmi_chan_info *cinfo, struct scmi_xfer *xfer)
{
struct scmi_mailbox *smbox = cinfo->transport_info;
return core->shmem->poll_done(smbox->shmem, xfer);
}
static const struct scmi_transport_ops scmi_mailbox_ops = {
.chan_available = mailbox_chan_available,
.chan_setup = mailbox_chan_setup,
.chan_free = mailbox_chan_free,
.send_message = mailbox_send_message,
.mark_txdone = mailbox_mark_txdone,
.fetch_response = mailbox_fetch_response,
.fetch_notification = mailbox_fetch_notification,
.clear_channel = mailbox_clear_channel,
.poll_done = mailbox_poll_done,
};
static struct scmi_desc scmi_mailbox_desc = {
.ops = &scmi_mailbox_ops,
.max_rx_timeout_ms = 30, /* We may increase this if required */
.max_msg = 20, /* Limited by MBOX_TX_QUEUE_LEN */
.max_msg_size = SCMI_SHMEM_MAX_PAYLOAD_SIZE,
};
static const struct of_device_id scmi_of_match[] = {
{ .compatible = "arm,scmi" },
{ /* Sentinel */ },
};
MODULE_DEVICE_TABLE(of, scmi_of_match);
DEFINE_SCMI_TRANSPORT_DRIVER(scmi_mailbox, scmi_mailbox_driver,
scmi_mailbox_desc, scmi_of_match, core);
module_platform_driver(scmi_mailbox_driver);
MODULE_AUTHOR("Sudeep Holla <sudeep.holla@arm.com>");
MODULE_DESCRIPTION("SCMI Mailbox Transport driver");
MODULE_LICENSE("GPL");
@@ -0,0 +1,649 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2019-2021 Linaro Ltd.
*/
#include <linux/io.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/tee_drv.h>
#include <linux/uuid.h>
#include <uapi/linux/tee.h>
#include "../common.h"
enum scmi_optee_pta_cmd {
/*
* PTA_SCMI_CMD_CAPABILITIES - Get channel capabilities
*
* [out] value[0].a: Capability bit mask (enum pta_scmi_caps)
* [out] value[0].b: Extended capabilities or 0
*/
PTA_SCMI_CMD_CAPABILITIES = 0,
/*
* PTA_SCMI_CMD_PROCESS_SMT_CHANNEL - Process SCMI message in SMT buffer
*
* [in] value[0].a: Channel handle
*
* Shared memory used for SCMI message/response exhange is expected
* already identified and bound to channel handle in both SCMI agent
* and SCMI server (OP-TEE) parts.
* The memory uses SMT header to carry SCMI meta-data (protocol ID and
* protocol message ID).
*/
PTA_SCMI_CMD_PROCESS_SMT_CHANNEL = 1,
/*
* PTA_SCMI_CMD_PROCESS_SMT_CHANNEL_MESSAGE - Process SMT/SCMI message
*
* [in] value[0].a: Channel handle
* [in/out] memref[1]: Message/response buffer (SMT and SCMI payload)
*
* Shared memory used for SCMI message/response is a SMT buffer
* referenced by param[1]. It shall be 128 bytes large to fit response
* payload whatever message playload size.
* The memory uses SMT header to carry SCMI meta-data (protocol ID and
* protocol message ID).
*/
PTA_SCMI_CMD_PROCESS_SMT_CHANNEL_MESSAGE = 2,
/*
* PTA_SCMI_CMD_GET_CHANNEL - Get channel handle
*
* SCMI shm information are 0 if agent expects to use OP-TEE regular SHM
*
* [in] value[0].a: Channel identifier
* [out] value[0].a: Returned channel handle
* [in] value[0].b: Requested capabilities mask (enum pta_scmi_caps)
*/
PTA_SCMI_CMD_GET_CHANNEL = 3,
/*
* PTA_SCMI_CMD_PROCESS_MSG_CHANNEL - Process SCMI message in a MSG
* buffer pointed by memref parameters
*
* [in] value[0].a: Channel handle
* [in] memref[1]: Message buffer (MSG and SCMI payload)
* [out] memref[2]: Response buffer (MSG and SCMI payload)
*
* Shared memories used for SCMI message/response are MSG buffers
* referenced by param[1] and param[2]. MSG transport protocol
* uses a 32bit header to carry SCMI meta-data (protocol ID and
* protocol message ID) followed by the effective SCMI message
* payload.
*/
PTA_SCMI_CMD_PROCESS_MSG_CHANNEL = 4,
};
/*
* OP-TEE SCMI service capabilities bit flags (32bit)
*
* PTA_SCMI_CAPS_SMT_HEADER
* When set, OP-TEE supports command using SMT header protocol (SCMI shmem) in
* shared memory buffers to carry SCMI protocol synchronisation information.
*
* PTA_SCMI_CAPS_MSG_HEADER
* When set, OP-TEE supports command using MSG header protocol in an OP-TEE
* shared memory to carry SCMI protocol synchronisation information and SCMI
* message payload.
*/
#define PTA_SCMI_CAPS_NONE 0
#define PTA_SCMI_CAPS_SMT_HEADER BIT(0)
#define PTA_SCMI_CAPS_MSG_HEADER BIT(1)
#define PTA_SCMI_CAPS_MASK (PTA_SCMI_CAPS_SMT_HEADER | \
PTA_SCMI_CAPS_MSG_HEADER)
/**
* struct scmi_optee_channel - Description of an OP-TEE SCMI channel
*
* @channel_id: OP-TEE channel ID used for this transport
* @tee_session: TEE session identifier
* @caps: OP-TEE SCMI channel capabilities
* @rx_len: Response size
* @mu: Mutex protection on channel access
* @cinfo: SCMI channel information
* @req: union for SCMI interface
* @req.shmem: Virtual base address of the shared memory
* @req.msg: Shared memory protocol handle for SCMI request and
* synchronous response
* @io_ops: Transport specific I/O operations
* @tee_shm: TEE shared memory handle @req or NULL if using IOMEM shmem
* @link: Reference in agent's channel list
*/
struct scmi_optee_channel {
u32 channel_id;
u32 tee_session;
u32 caps;
u32 rx_len;
struct mutex mu;
struct scmi_chan_info *cinfo;
union {
struct scmi_shared_mem __iomem *shmem;
struct scmi_msg_payld *msg;
} req;
struct scmi_shmem_io_ops *io_ops;
struct tee_shm *tee_shm;
struct list_head link;
};
/**
* struct scmi_optee_agent - OP-TEE transport private data
*
* @dev: Device used for communication with TEE
* @tee_ctx: TEE context used for communication
* @caps: Supported channel capabilities
* @mu: Mutex for protection of @channel_list
* @channel_list: List of all created channels for the agent
*/
struct scmi_optee_agent {
struct device *dev;
struct tee_context *tee_ctx;
u32 caps;
struct mutex mu;
struct list_head channel_list;
};
static struct scmi_transport_core_operations *core;
/* There can be only 1 SCMI service in OP-TEE we connect to */
static struct scmi_optee_agent *scmi_optee_private;
static DEFINE_SCMI_TRANSPORT_SUPPLIER(scmi_optee_supplier);
/* Open a session toward SCMI OP-TEE service with REE_KERNEL identity */
static int open_session(struct scmi_optee_agent *agent, u32 *tee_session)
{
struct device *dev = agent->dev;
struct tee_client_device *scmi_pta = to_tee_client_device(dev);
struct tee_ioctl_open_session_arg arg = { };
int ret;
memcpy(arg.uuid, scmi_pta->id.uuid.b, TEE_IOCTL_UUID_LEN);
arg.clnt_login = TEE_IOCTL_LOGIN_REE_KERNEL;
ret = tee_client_open_session(agent->tee_ctx, &arg, NULL);
if (ret < 0 || arg.ret) {
dev_err(dev, "Can't open tee session: %d / %#x\n", ret, arg.ret);
return -EOPNOTSUPP;
}
*tee_session = arg.session;
return 0;
}
static void close_session(struct scmi_optee_agent *agent, u32 tee_session)
{
tee_client_close_session(agent->tee_ctx, tee_session);
}
static int get_capabilities(struct scmi_optee_agent *agent)
{
struct tee_ioctl_invoke_arg arg = { };
struct tee_param param[1] = { };
u32 caps;
u32 tee_session;
int ret;
ret = open_session(agent, &tee_session);
if (ret)
return ret;
arg.func = PTA_SCMI_CMD_CAPABILITIES;
arg.session = tee_session;
arg.num_params = 1;
param[0].attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_OUTPUT;
ret = tee_client_invoke_func(agent->tee_ctx, &arg, param);
close_session(agent, tee_session);
if (ret < 0 || arg.ret) {
dev_err(agent->dev, "Can't get capabilities: %d / %#x\n", ret, arg.ret);
return -EOPNOTSUPP;
}
caps = param[0].u.value.a;
if (!(caps & (PTA_SCMI_CAPS_SMT_HEADER | PTA_SCMI_CAPS_MSG_HEADER))) {
dev_err(agent->dev, "OP-TEE SCMI PTA doesn't support SMT and MSG\n");
return -EOPNOTSUPP;
}
agent->caps = caps;
return 0;
}
static int get_channel(struct scmi_optee_channel *channel)
{
struct device *dev = scmi_optee_private->dev;
struct tee_ioctl_invoke_arg arg = { };
struct tee_param param[1] = { };
unsigned int caps = 0;
int ret;
if (channel->tee_shm)
caps = PTA_SCMI_CAPS_MSG_HEADER;
else
caps = PTA_SCMI_CAPS_SMT_HEADER;
arg.func = PTA_SCMI_CMD_GET_CHANNEL;
arg.session = channel->tee_session;
arg.num_params = 1;
param[0].attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INOUT;
param[0].u.value.a = channel->channel_id;
param[0].u.value.b = caps;
ret = tee_client_invoke_func(scmi_optee_private->tee_ctx, &arg, param);
if (ret || arg.ret) {
dev_err(dev, "Can't get channel with caps %#x: %d / %#x\n", caps, ret, arg.ret);
return -EOPNOTSUPP;
}
/* From now on use channel identifer provided by OP-TEE SCMI service */
channel->channel_id = param[0].u.value.a;
channel->caps = caps;
return 0;
}
static int invoke_process_smt_channel(struct scmi_optee_channel *channel)
{
struct tee_ioctl_invoke_arg arg = {
.func = PTA_SCMI_CMD_PROCESS_SMT_CHANNEL,
.session = channel->tee_session,
.num_params = 1,
};
struct tee_param param[1] = { };
int ret;
param[0].attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT;
param[0].u.value.a = channel->channel_id;
ret = tee_client_invoke_func(scmi_optee_private->tee_ctx, &arg, param);
if (ret < 0 || arg.ret) {
dev_err(scmi_optee_private->dev, "Can't invoke channel %u: %d / %#x\n",
channel->channel_id, ret, arg.ret);
return -EIO;
}
return 0;
}
static int invoke_process_msg_channel(struct scmi_optee_channel *channel, size_t msg_size)
{
struct tee_ioctl_invoke_arg arg = {
.func = PTA_SCMI_CMD_PROCESS_MSG_CHANNEL,
.session = channel->tee_session,
.num_params = 3,
};
struct tee_param param[3] = { };
int ret;
param[0].attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT;
param[0].u.value.a = channel->channel_id;
param[1].attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT;
param[1].u.memref.shm = channel->tee_shm;
param[1].u.memref.size = msg_size;
param[2].attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT;
param[2].u.memref.shm = channel->tee_shm;
param[2].u.memref.size = SCMI_SHMEM_MAX_PAYLOAD_SIZE;
ret = tee_client_invoke_func(scmi_optee_private->tee_ctx, &arg, param);
if (ret < 0 || arg.ret) {
dev_err(scmi_optee_private->dev, "Can't invoke channel %u: %d / %#x\n",
channel->channel_id, ret, arg.ret);
return -EIO;
}
/* Save response size */
channel->rx_len = param[2].u.memref.size;
return 0;
}
static bool scmi_optee_chan_available(struct device_node *of_node, int idx)
{
u32 channel_id;
return !of_property_read_u32_index(of_node, "linaro,optee-channel-id",
idx, &channel_id);
}
static void scmi_optee_clear_channel(struct scmi_chan_info *cinfo)
{
struct scmi_optee_channel *channel = cinfo->transport_info;
if (!channel->tee_shm)
core->shmem->clear_channel(channel->req.shmem);
}
static int setup_dynamic_shmem(struct device *dev, struct scmi_optee_channel *channel)
{
const size_t msg_size = SCMI_SHMEM_MAX_PAYLOAD_SIZE;
void *shbuf;
channel->tee_shm = tee_shm_alloc_kernel_buf(scmi_optee_private->tee_ctx, msg_size);
if (IS_ERR(channel->tee_shm)) {
dev_err(channel->cinfo->dev, "shmem allocation failed\n");
return -ENOMEM;
}
shbuf = tee_shm_get_va(channel->tee_shm, 0);
memset(shbuf, 0, msg_size);
channel->req.msg = shbuf;
channel->rx_len = msg_size;
return 0;
}
static int setup_static_shmem(struct device *dev, struct scmi_chan_info *cinfo,
struct scmi_optee_channel *channel)
{
channel->req.shmem = core->shmem->setup_iomap(cinfo, dev, true, NULL,
&channel->io_ops);
if (IS_ERR(channel->req.shmem))
return PTR_ERR(channel->req.shmem);
return 0;
}
static int setup_shmem(struct device *dev, struct scmi_chan_info *cinfo,
struct scmi_optee_channel *channel)
{
if (of_property_present(cinfo->dev->of_node, "shmem"))
return setup_static_shmem(dev, cinfo, channel);
else
return setup_dynamic_shmem(dev, channel);
}
static int scmi_optee_chan_setup(struct scmi_chan_info *cinfo, struct device *dev, bool tx)
{
struct scmi_optee_channel *channel;
uint32_t channel_id;
int ret;
if (!tx)
return -ENODEV;
channel = devm_kzalloc(dev, sizeof(*channel), GFP_KERNEL);
if (!channel)
return -ENOMEM;
ret = of_property_read_u32_index(cinfo->dev->of_node, "linaro,optee-channel-id",
0, &channel_id);
if (ret)
return ret;
cinfo->transport_info = channel;
channel->cinfo = cinfo;
channel->channel_id = channel_id;
mutex_init(&channel->mu);
ret = setup_shmem(dev, cinfo, channel);
if (ret)
return ret;
ret = open_session(scmi_optee_private, &channel->tee_session);
if (ret)
goto err_free_shm;
ret = tee_client_system_session(scmi_optee_private->tee_ctx, channel->tee_session);
if (ret)
dev_warn(dev, "Could not switch to system session, do best effort\n");
ret = get_channel(channel);
if (ret)
goto err_close_sess;
/* Enable polling */
cinfo->no_completion_irq = true;
mutex_lock(&scmi_optee_private->mu);
list_add(&channel->link, &scmi_optee_private->channel_list);
mutex_unlock(&scmi_optee_private->mu);
return 0;
err_close_sess:
close_session(scmi_optee_private, channel->tee_session);
err_free_shm:
if (channel->tee_shm)
tee_shm_free(channel->tee_shm);
return ret;
}
static int scmi_optee_chan_free(int id, void *p, void *data)
{
struct scmi_chan_info *cinfo = p;
struct scmi_optee_channel *channel = cinfo->transport_info;
/*
* Different protocols might share the same chan info, so a previous
* call might have already freed the structure.
*/
if (!channel)
return 0;
mutex_lock(&scmi_optee_private->mu);
list_del(&channel->link);
mutex_unlock(&scmi_optee_private->mu);
close_session(scmi_optee_private, channel->tee_session);
if (channel->tee_shm) {
tee_shm_free(channel->tee_shm);
channel->tee_shm = NULL;
}
cinfo->transport_info = NULL;
channel->cinfo = NULL;
return 0;
}
static int scmi_optee_send_message(struct scmi_chan_info *cinfo,
struct scmi_xfer *xfer)
{
struct scmi_optee_channel *channel = cinfo->transport_info;
int ret;
mutex_lock(&channel->mu);
if (channel->tee_shm) {
core->msg->tx_prepare(channel->req.msg, xfer);
ret = invoke_process_msg_channel(channel,
core->msg->command_size(xfer));
} else {
core->shmem->tx_prepare(channel->req.shmem, xfer, cinfo,
channel->io_ops->toio);
ret = invoke_process_smt_channel(channel);
}
if (ret)
mutex_unlock(&channel->mu);
return ret;
}
static void scmi_optee_fetch_response(struct scmi_chan_info *cinfo,
struct scmi_xfer *xfer)
{
struct scmi_optee_channel *channel = cinfo->transport_info;
if (channel->tee_shm)
core->msg->fetch_response(channel->req.msg,
channel->rx_len, xfer);
else
core->shmem->fetch_response(channel->req.shmem, xfer,
channel->io_ops->fromio);
}
static void scmi_optee_mark_txdone(struct scmi_chan_info *cinfo, int ret,
struct scmi_xfer *__unused)
{
struct scmi_optee_channel *channel = cinfo->transport_info;
mutex_unlock(&channel->mu);
}
static const struct scmi_transport_ops scmi_optee_ops = {
.chan_available = scmi_optee_chan_available,
.chan_setup = scmi_optee_chan_setup,
.chan_free = scmi_optee_chan_free,
.send_message = scmi_optee_send_message,
.mark_txdone = scmi_optee_mark_txdone,
.fetch_response = scmi_optee_fetch_response,
.clear_channel = scmi_optee_clear_channel,
};
static int scmi_optee_ctx_match(struct tee_ioctl_version_data *ver, const void *data)
{
return ver->impl_id == TEE_IMPL_ID_OPTEE;
}
static struct scmi_desc scmi_optee_desc = {
.ops = &scmi_optee_ops,
.max_rx_timeout_ms = 30,
.max_msg = 20,
.max_msg_size = SCMI_SHMEM_MAX_PAYLOAD_SIZE,
.sync_cmds_completed_on_ret = true,
};
static const struct of_device_id scmi_of_match[] = {
{ .compatible = "linaro,scmi-optee", .data = &scmi_optee_supplier.th},
{ /* Sentinel */ },
};
DEFINE_SCMI_TRANSPORT_DRIVER(scmi_optee, scmi_optee_driver, scmi_optee_desc,
scmi_of_match, core);
static int scmi_optee_service_probe(struct tee_client_device *scmi_pta)
{
struct device *dev = &scmi_pta->dev;
struct scmi_optee_agent *agent;
struct tee_context *tee_ctx;
int ret;
/* Only one SCMI OP-TEE device allowed */
if (scmi_optee_private) {
dev_err(dev, "An SCMI OP-TEE device was already initialized: only one allowed\n");
return -EBUSY;
}
tee_ctx = tee_client_open_context(NULL, scmi_optee_ctx_match, NULL, NULL);
if (IS_ERR(tee_ctx))
return -ENODEV;
agent = devm_kzalloc(dev, sizeof(*agent), GFP_KERNEL);
if (!agent) {
ret = -ENOMEM;
goto err;
}
agent->dev = dev;
agent->tee_ctx = tee_ctx;
INIT_LIST_HEAD(&agent->channel_list);
mutex_init(&agent->mu);
ret = get_capabilities(agent);
if (ret)
goto err;
/* Ensure initialized scmi_optee_private is visible */
smp_mb();
scmi_optee_private = agent;
ret = scmi_transport_supplier_put(&scmi_optee_supplier.th, agent->dev);
if (ret)
goto err_put;
return 0;
err_put:
/* Ensure cleared reference is visible before resources are released */
smp_store_mb(scmi_optee_private, NULL);
err:
tee_client_close_context(tee_ctx);
return ret;
}
static void scmi_optee_service_remove(struct tee_client_device *scmi_pta)
{
struct scmi_optee_agent *agent = scmi_optee_private;
if (!scmi_optee_private)
return;
if (!list_empty(&scmi_optee_private->channel_list))
return;
/* Ensure cleared reference is visible before resources are released */
smp_store_mb(scmi_optee_private, NULL);
scmi_transport_supplier_put(&scmi_optee_supplier.th, agent->dev);
tee_client_close_context(agent->tee_ctx);
}
static const struct tee_client_device_id scmi_optee_service_id[] = {
{
UUID_INIT(0xa8cfe406, 0xd4f5, 0x4a2e,
0x9f, 0x8d, 0xa2, 0x5d, 0xc7, 0x54, 0xc0, 0x99)
},
{ }
};
MODULE_DEVICE_TABLE(tee, scmi_optee_service_id);
static struct tee_client_driver scmi_optee_service_driver = {
.probe = scmi_optee_service_probe,
.remove = scmi_optee_service_remove,
.id_table = scmi_optee_service_id,
.driver = {
.name = "scmi-optee",
},
};
static int __init scmi_transport_optee_init(void)
{
int ret;
ret = tee_client_driver_register(&scmi_optee_service_driver);
if (ret)
return ret;
ret = platform_driver_register(&scmi_optee_driver);
if (ret) {
tee_client_driver_unregister(&scmi_optee_service_driver);
return ret;
}
return ret;
}
module_init(scmi_transport_optee_init);
static void __exit scmi_transport_optee_exit(void)
{
platform_driver_unregister(&scmi_optee_driver);
tee_client_driver_unregister(&scmi_optee_service_driver);
}
module_exit(scmi_transport_optee_exit);
MODULE_AUTHOR("Etienne Carriere <etienne.carriere@foss.st.com>");
MODULE_DESCRIPTION("SCMI OPTEE Transport driver");
MODULE_LICENSE("GPL");
@@ -0,0 +1,313 @@
// SPDX-License-Identifier: GPL-2.0
/*
* System Control and Management Interface (SCMI) Message SMC/HVC
* Transport driver
*
* Copyright 2020 NXP
*/
#include <linux/arm-smccc.h>
#include <linux/atomic.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/limits.h>
#include <linux/platform_device.h>
#include <linux/processor.h>
#include <linux/slab.h>
#include "../common.h"
/*
* The shmem address is split into 4K page and offset.
* This is to make sure the parameters fit in 32bit arguments of the
* smc/hvc call to keep it uniform across smc32/smc64 conventions.
* This however limits the shmem address to 44 bit.
*
* These optional parameters can be used to distinguish among multiple
* scmi instances that are using the same smc-id.
* The page parameter is passed in r1/x1/w1 register and the offset parameter
* is passed in r2/x2/w2 register.
*/
#define SHMEM_SIZE (SZ_4K)
#define SHMEM_SHIFT 12
#define SHMEM_PAGE(x) (_UL((x) >> SHMEM_SHIFT))
#define SHMEM_OFFSET(x) ((x) & (SHMEM_SIZE - 1))
/**
* struct scmi_smc - Structure representing a SCMI smc transport
*
* @irq: An optional IRQ for completion
* @cinfo: SCMI channel info
* @shmem: Transmit/Receive shared memory area
* @io_ops: Transport specific I/O operations
* @shmem_lock: Lock to protect access to Tx/Rx shared memory area.
* Used when NOT operating in atomic mode.
* @inflight: Atomic flag to protect access to Tx/Rx shared memory area.
* Used when operating in atomic mode.
* @func_id: smc/hvc call function id
* @param_page: 4K page number of the shmem channel
* @param_offset: Offset within the 4K page of the shmem channel
* @cap_id: smc/hvc doorbell's capability id to be used on Qualcomm virtual
* platforms
*/
struct scmi_smc {
int irq;
struct scmi_chan_info *cinfo;
struct scmi_shared_mem __iomem *shmem;
struct scmi_shmem_io_ops *io_ops;
/* Protect access to shmem area */
struct mutex shmem_lock;
#define INFLIGHT_NONE MSG_TOKEN_MAX
atomic_t inflight;
unsigned long func_id;
unsigned long param_page;
unsigned long param_offset;
unsigned long cap_id;
};
static struct scmi_transport_core_operations *core;
static irqreturn_t smc_msg_done_isr(int irq, void *data)
{
struct scmi_smc *scmi_info = data;
core->rx_callback(scmi_info->cinfo,
core->shmem->read_header(scmi_info->shmem), NULL);
return IRQ_HANDLED;
}
static bool smc_chan_available(struct device_node *of_node, int idx)
{
struct device_node *np __free(device_node) =
of_parse_phandle(of_node, "shmem", 0);
if (!np)
return false;
return true;
}
static inline void smc_channel_lock_init(struct scmi_smc *scmi_info)
{
if (IS_ENABLED(CONFIG_ARM_SCMI_TRANSPORT_SMC_ATOMIC_ENABLE))
atomic_set(&scmi_info->inflight, INFLIGHT_NONE);
else
mutex_init(&scmi_info->shmem_lock);
}
static bool smc_xfer_inflight(struct scmi_xfer *xfer, atomic_t *inflight)
{
int ret;
ret = atomic_cmpxchg(inflight, INFLIGHT_NONE, xfer->hdr.seq);
return ret == INFLIGHT_NONE;
}
static inline void
smc_channel_lock_acquire(struct scmi_smc *scmi_info,
struct scmi_xfer *xfer __maybe_unused)
{
if (IS_ENABLED(CONFIG_ARM_SCMI_TRANSPORT_SMC_ATOMIC_ENABLE))
spin_until_cond(smc_xfer_inflight(xfer, &scmi_info->inflight));
else
mutex_lock(&scmi_info->shmem_lock);
}
static inline void smc_channel_lock_release(struct scmi_smc *scmi_info)
{
if (IS_ENABLED(CONFIG_ARM_SCMI_TRANSPORT_SMC_ATOMIC_ENABLE))
atomic_set(&scmi_info->inflight, INFLIGHT_NONE);
else
mutex_unlock(&scmi_info->shmem_lock);
}
static int smc_chan_setup(struct scmi_chan_info *cinfo, struct device *dev,
bool tx)
{
struct device *cdev = cinfo->dev;
unsigned long cap_id = ULONG_MAX;
struct scmi_smc *scmi_info;
struct resource res = {};
u32 func_id;
int ret;
if (!tx)
return -ENODEV;
scmi_info = devm_kzalloc(dev, sizeof(*scmi_info), GFP_KERNEL);
if (!scmi_info)
return -ENOMEM;
scmi_info->shmem = core->shmem->setup_iomap(cinfo, dev, tx, &res,
&scmi_info->io_ops);
if (IS_ERR(scmi_info->shmem))
return PTR_ERR(scmi_info->shmem);
ret = of_property_read_u32(dev->of_node, "arm,smc-id", &func_id);
if (ret < 0)
return ret;
if (of_device_is_compatible(dev->of_node, "qcom,scmi-smc")) {
resource_size_t size = resource_size(&res);
void __iomem *ptr = (void __iomem *)scmi_info->shmem + size - 8;
/* The capability-id is kept in last 8 bytes of shmem.
* +-------+ <-- 0
* | shmem |
* +-------+ <-- size - 8
* | capId |
* +-------+ <-- size
*/
memcpy_fromio(&cap_id, ptr, sizeof(cap_id));
}
if (of_device_is_compatible(dev->of_node, "arm,scmi-smc-param")) {
scmi_info->param_page = SHMEM_PAGE(res.start);
scmi_info->param_offset = SHMEM_OFFSET(res.start);
}
/*
* If there is an interrupt named "a2p", then the service and
* completion of a message is signaled by an interrupt rather than by
* the return of the SMC call.
*/
scmi_info->irq = of_irq_get_byname(cdev->of_node, "a2p");
if (scmi_info->irq > 0) {
ret = request_irq(scmi_info->irq, smc_msg_done_isr,
IRQF_NO_SUSPEND, dev_name(dev), scmi_info);
if (ret) {
dev_err(dev, "failed to setup SCMI smc irq\n");
return ret;
}
} else {
cinfo->no_completion_irq = true;
}
scmi_info->func_id = func_id;
scmi_info->cap_id = cap_id;
scmi_info->cinfo = cinfo;
smc_channel_lock_init(scmi_info);
cinfo->transport_info = scmi_info;
return 0;
}
static int smc_chan_free(int id, void *p, void *data)
{
struct scmi_chan_info *cinfo = p;
struct scmi_smc *scmi_info = cinfo->transport_info;
/*
* Different protocols might share the same chan info, so a previous
* smc_chan_free call might have already freed the structure.
*/
if (!scmi_info)
return 0;
/* Ignore any possible further reception on the IRQ path */
if (scmi_info->irq > 0)
free_irq(scmi_info->irq, scmi_info);
cinfo->transport_info = NULL;
scmi_info->cinfo = NULL;
return 0;
}
static int smc_send_message(struct scmi_chan_info *cinfo,
struct scmi_xfer *xfer)
{
struct scmi_smc *scmi_info = cinfo->transport_info;
struct arm_smccc_res res;
/*
* Channel will be released only once response has been
* surely fully retrieved, so after .mark_txdone()
*/
smc_channel_lock_acquire(scmi_info, xfer);
core->shmem->tx_prepare(scmi_info->shmem, xfer, cinfo,
scmi_info->io_ops->toio);
if (scmi_info->cap_id != ULONG_MAX)
arm_smccc_1_1_invoke(scmi_info->func_id, scmi_info->cap_id, 0,
0, 0, 0, 0, 0, &res);
else
arm_smccc_1_1_invoke(scmi_info->func_id, scmi_info->param_page,
scmi_info->param_offset, 0, 0, 0, 0, 0,
&res);
/* Only SMCCC_RET_NOT_SUPPORTED is valid error code */
if (res.a0) {
smc_channel_lock_release(scmi_info);
return -EOPNOTSUPP;
}
return 0;
}
static void smc_fetch_response(struct scmi_chan_info *cinfo,
struct scmi_xfer *xfer)
{
struct scmi_smc *scmi_info = cinfo->transport_info;
core->shmem->fetch_response(scmi_info->shmem, xfer,
scmi_info->io_ops->fromio);
}
static void smc_mark_txdone(struct scmi_chan_info *cinfo, int ret,
struct scmi_xfer *__unused)
{
struct scmi_smc *scmi_info = cinfo->transport_info;
smc_channel_lock_release(scmi_info);
}
static const struct scmi_transport_ops scmi_smc_ops = {
.chan_available = smc_chan_available,
.chan_setup = smc_chan_setup,
.chan_free = smc_chan_free,
.send_message = smc_send_message,
.mark_txdone = smc_mark_txdone,
.fetch_response = smc_fetch_response,
};
static struct scmi_desc scmi_smc_desc = {
.ops = &scmi_smc_ops,
.max_rx_timeout_ms = 30,
.max_msg = 20,
.max_msg_size = SCMI_SHMEM_MAX_PAYLOAD_SIZE,
/*
* Setting .sync_cmds_atomic_replies to true for SMC assumes that,
* once the SMC instruction has completed successfully, the issued
* SCMI command would have been already fully processed by the SCMI
* platform firmware and so any possible response value expected
* for the issued command will be immmediately ready to be fetched
* from the shared memory area.
*/
.sync_cmds_completed_on_ret = true,
.atomic_enabled = IS_ENABLED(CONFIG_ARM_SCMI_TRANSPORT_SMC_ATOMIC_ENABLE),
};
static const struct of_device_id scmi_of_match[] = {
{ .compatible = "arm,scmi-smc" },
{ .compatible = "arm,scmi-smc-param" },
{ .compatible = "qcom,scmi-smc" },
{ /* Sentinel */ },
};
MODULE_DEVICE_TABLE(of, scmi_of_match);
DEFINE_SCMI_TRANSPORT_DRIVER(scmi_smc, scmi_smc_driver, scmi_smc_desc,
scmi_of_match, core);
module_platform_driver(scmi_smc_driver);
MODULE_AUTHOR("Peng Fan <peng.fan@nxp.com>");
MODULE_AUTHOR("Nikunj Kela <quic_nkela@quicinc.com>");
MODULE_DESCRIPTION("SCMI SMC Transport driver");
MODULE_LICENSE("GPL");
@@ -0,0 +1,979 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Virtio Transport driver for Arm System Control and Management Interface
* (SCMI).
*
* Copyright (C) 2020-2022 OpenSynergy.
* Copyright (C) 2021-2026 ARM Ltd.
*/
/**
* DOC: Theory of Operation
*
* The scmi-virtio transport implements a driver for the virtio SCMI device.
*
* There is one Tx channel (virtio cmdq, A2P channel) and at most one Rx
* channel (virtio eventq, P2A channel). Each channel is implemented through a
* virtqueue. Access to each virtqueue is protected by spinlocks.
*/
#include <linux/completion.h>
#include <linux/errno.h>
#include <linux/platform_device.h>
#include <linux/refcount.h>
#include <linux/slab.h>
#include <linux/virtio.h>
#include <linux/virtio_config.h>
#include <uapi/linux/virtio_ids.h>
#include <uapi/linux/virtio_scmi.h>
#include "../common.h"
#define VIRTIO_MAX_RX_TIMEOUT_MS 60000
#define VIRTIO_SCMI_MAX_MSG_SIZE 128 /* Value may be increased. */
#define VIRTIO_SCMI_MAX_PDU_SIZE(ci) \
((ci)->max_msg_size + SCMI_MSG_MAX_PROT_OVERHEAD)
#define DESCRIPTORS_PER_TX_MSG 2
/**
* struct scmi_vio_channel - Transport channel information
*
* @vqueue: Associated virtqueue
* @cinfo: SCMI Tx or Rx channel
* @free_lock: Protects access to the @free_list.
* @free_list: List of unused scmi_vio_msg, maintained for Tx channels only
* @deferred_tx_work: Worker for TX deferred replies processing
* @deferred_tx_wq: Workqueue for TX deferred replies
* @pending_lock: Protects access to the @pending_cmds_list.
* @pending_cmds_list: List of pre-fetched commands queueud for later processing
* @is_rx: Whether channel is an Rx channel
* @max_msg: Maximum number of pending messages for this channel.
* @lock: Protects access to all members except users, free_list and
* pending_cmds_list.
* @shutdown_done: A reference to a completion used when freeing this channel.
* @users: A reference count to currently active users of this channel.
*/
struct scmi_vio_channel {
struct virtqueue *vqueue;
struct scmi_chan_info *cinfo;
/* lock to protect access to the free list. */
spinlock_t free_lock;
struct list_head free_list;
/* lock to protect access to the pending list. */
spinlock_t pending_lock;
struct list_head pending_cmds_list;
struct work_struct deferred_tx_work;
struct workqueue_struct *deferred_tx_wq;
bool is_rx;
unsigned int max_msg;
/*
* Lock to protect access to all members except users, free_list and
* pending_cmds_list
*/
spinlock_t lock;
struct completion *shutdown_done;
refcount_t users;
};
enum poll_states {
VIO_MSG_NOT_POLLED,
VIO_MSG_POLL_TIMEOUT,
VIO_MSG_POLLING,
VIO_MSG_POLL_DONE,
};
/**
* struct scmi_vio_msg - Transport PDU information
*
* @request: SDU used for commands
* @input: SDU used for (delayed) responses and notifications
* @list: List which scmi_vio_msg may be part of
* @rx_len: Input SDU size in bytes, once input has been received
* @max_len: Maximumm allowed SDU size in bytes
* @poll_idx: Last used index registered for polling purposes if this message
* transaction reply was configured for polling.
* @poll_status: Polling state for this message.
* @poll_lock: A lock to protect @poll_status
* @users: A reference count to track this message users and avoid premature
* freeing (and reuse) when polling and IRQ execution paths interleave.
*/
struct scmi_vio_msg {
struct scmi_msg_payld *request;
struct scmi_msg_payld *input;
struct list_head list;
unsigned int rx_len;
unsigned int max_len;
unsigned int poll_idx;
enum poll_states poll_status;
/* Lock to protect access to poll_status */
spinlock_t poll_lock;
refcount_t users;
};
static struct scmi_transport_core_operations *core;
/* Only one SCMI VirtIO device can possibly exist */
static struct virtio_device *scmi_vdev;
static DEFINE_SCMI_TRANSPORT_SUPPLIER(scmi_virtio_supplier);
static void scmi_vio_channel_ready(struct scmi_vio_channel *vioch,
struct scmi_chan_info *cinfo)
{
unsigned long flags;
spin_lock_irqsave(&vioch->lock, flags);
cinfo->transport_info = vioch;
/* Indirectly setting channel not available any more */
vioch->cinfo = cinfo;
spin_unlock_irqrestore(&vioch->lock, flags);
refcount_set(&vioch->users, 1);
}
static inline bool scmi_vio_channel_acquire(struct scmi_vio_channel *vioch)
{
return refcount_inc_not_zero(&vioch->users);
}
static inline void scmi_vio_channel_release(struct scmi_vio_channel *vioch)
{
if (refcount_dec_and_test(&vioch->users)) {
unsigned long flags;
spin_lock_irqsave(&vioch->lock, flags);
if (vioch->shutdown_done) {
vioch->cinfo = NULL;
complete(vioch->shutdown_done);
}
spin_unlock_irqrestore(&vioch->lock, flags);
}
}
static void scmi_vio_channel_cleanup_sync(struct scmi_vio_channel *vioch)
{
unsigned long flags;
DECLARE_COMPLETION_ONSTACK(vioch_shutdown_done);
/*
* Prepare to wait for the last release if not already released
* or in progress.
*/
spin_lock_irqsave(&vioch->lock, flags);
if (!vioch->cinfo || vioch->shutdown_done) {
spin_unlock_irqrestore(&vioch->lock, flags);
return;
}
vioch->shutdown_done = &vioch_shutdown_done;
if (!vioch->is_rx && vioch->deferred_tx_wq)
/* Cannot be kicked anymore after this...*/
vioch->deferred_tx_wq = NULL;
spin_unlock_irqrestore(&vioch->lock, flags);
scmi_vio_channel_release(vioch);
/* Let any possibly concurrent RX path release the channel */
wait_for_completion(vioch->shutdown_done);
}
/* Assumes to be called with vio channel acquired already */
static struct scmi_vio_msg *
scmi_virtio_get_free_msg(struct scmi_vio_channel *vioch)
{
unsigned long flags;
struct scmi_vio_msg *msg;
spin_lock_irqsave(&vioch->free_lock, flags);
if (list_empty(&vioch->free_list)) {
spin_unlock_irqrestore(&vioch->free_lock, flags);
return NULL;
}
msg = list_first_entry(&vioch->free_list, typeof(*msg), list);
list_del_init(&msg->list);
spin_unlock_irqrestore(&vioch->free_lock, flags);
/* Still no users, no need to acquire poll_lock */
msg->poll_status = VIO_MSG_NOT_POLLED;
refcount_set(&msg->users, 1);
return msg;
}
static inline bool scmi_vio_msg_acquire(struct scmi_vio_msg *msg)
{
return refcount_inc_not_zero(&msg->users);
}
/* Assumes to be called with vio channel acquired already */
static inline bool scmi_vio_msg_release(struct scmi_vio_channel *vioch,
struct scmi_vio_msg *msg)
{
bool ret;
ret = refcount_dec_and_test(&msg->users);
if (ret) {
unsigned long flags;
spin_lock_irqsave(&vioch->free_lock, flags);
list_add_tail(&msg->list, &vioch->free_list);
spin_unlock_irqrestore(&vioch->free_lock, flags);
}
return ret;
}
static bool scmi_vio_have_vq_rx(struct virtio_device *vdev)
{
return virtio_has_feature(vdev, VIRTIO_SCMI_F_P2A_CHANNELS);
}
static int scmi_vio_feed_vq_rx(struct scmi_vio_channel *vioch,
struct scmi_vio_msg *msg)
{
struct scatterlist sg_in;
int rc;
unsigned long flags;
struct device *dev = &vioch->vqueue->vdev->dev;
sg_init_one(&sg_in, msg->input, msg->max_len);
spin_lock_irqsave(&vioch->lock, flags);
rc = virtqueue_add_inbuf(vioch->vqueue, &sg_in, 1, msg, GFP_ATOMIC);
if (rc)
dev_err(dev, "failed to add to RX virtqueue (%d)\n", rc);
else
virtqueue_kick(vioch->vqueue);
spin_unlock_irqrestore(&vioch->lock, flags);
return rc;
}
/*
* Assume to be called with channel already acquired or not ready at all;
* vioch->lock MUST NOT have been already acquired.
*/
static void scmi_finalize_message(struct scmi_vio_channel *vioch,
struct scmi_vio_msg *msg)
{
if (vioch->is_rx)
scmi_vio_feed_vq_rx(vioch, msg);
else
scmi_vio_msg_release(vioch, msg);
}
static void scmi_vio_complete_cb(struct virtqueue *vqueue)
{
unsigned long flags;
unsigned int length;
struct scmi_vio_channel *vioch;
struct scmi_vio_msg *msg;
bool cb_enabled = true;
if (WARN_ON_ONCE(!vqueue->vdev->priv))
return;
vioch = &((struct scmi_vio_channel *)vqueue->vdev->priv)[vqueue->index];
for (;;) {
if (!scmi_vio_channel_acquire(vioch))
return;
spin_lock_irqsave(&vioch->lock, flags);
if (cb_enabled) {
virtqueue_disable_cb(vqueue);
cb_enabled = false;
}
msg = virtqueue_get_buf(vqueue, &length);
if (!msg) {
if (virtqueue_enable_cb(vqueue)) {
spin_unlock_irqrestore(&vioch->lock, flags);
scmi_vio_channel_release(vioch);
return;
}
cb_enabled = true;
}
spin_unlock_irqrestore(&vioch->lock, flags);
if (msg) {
msg->rx_len = length;
core->rx_callback(vioch->cinfo,
core->msg->read_header(msg->input),
msg);
scmi_finalize_message(vioch, msg);
}
/*
* Release vio channel between loop iterations to allow
* virtio_chan_free() to eventually fully release it when
* shutting down; in such a case, any outstanding message will
* be ignored since this loop will bail out at the next
* iteration.
*/
scmi_vio_channel_release(vioch);
}
}
static void scmi_vio_deferred_tx_worker(struct work_struct *work)
{
unsigned long flags;
struct scmi_vio_channel *vioch;
struct scmi_vio_msg *msg, *tmp;
vioch = container_of(work, struct scmi_vio_channel, deferred_tx_work);
if (!scmi_vio_channel_acquire(vioch))
return;
/*
* Process pre-fetched messages: these could be non-polled messages or
* late timed-out replies to polled messages dequeued by chance while
* polling for some other messages: this worker is in charge to process
* the valid non-expired messages and anyway finally free all of them.
*/
spin_lock_irqsave(&vioch->pending_lock, flags);
/* Scan the list of possibly pre-fetched messages during polling. */
list_for_each_entry_safe(msg, tmp, &vioch->pending_cmds_list, list) {
list_del(&msg->list);
/*
* Channel is acquired here (cannot vanish) and this message
* is no more processed elsewhere so no poll_lock needed.
*/
if (msg->poll_status == VIO_MSG_NOT_POLLED)
core->rx_callback(vioch->cinfo,
core->msg->read_header(msg->input),
msg);
/* Free the processed message once done */
scmi_vio_msg_release(vioch, msg);
}
spin_unlock_irqrestore(&vioch->pending_lock, flags);
/* Process possibly still pending messages */
scmi_vio_complete_cb(vioch->vqueue);
scmi_vio_channel_release(vioch);
}
static struct virtqueue_info scmi_vio_vqs_info[] = {
{ "tx", scmi_vio_complete_cb },
{ "rx", scmi_vio_complete_cb },
};
static unsigned int virtio_get_max_msg(struct scmi_chan_info *base_cinfo)
{
struct scmi_vio_channel *vioch = base_cinfo->transport_info;
return vioch->max_msg;
}
static bool virtio_chan_available(struct device_node *of_node, int idx)
{
struct scmi_vio_channel *channels, *vioch = NULL;
if (WARN_ON_ONCE(!scmi_vdev))
return false;
channels = (struct scmi_vio_channel *)scmi_vdev->priv;
switch (idx) {
case VIRTIO_SCMI_VQ_TX:
vioch = &channels[VIRTIO_SCMI_VQ_TX];
break;
case VIRTIO_SCMI_VQ_RX:
if (scmi_vio_have_vq_rx(scmi_vdev))
vioch = &channels[VIRTIO_SCMI_VQ_RX];
break;
default:
return false;
}
dev_dbg(&scmi_vdev->dev, "%s Channel %sAVAILABLE on SCMI Virtio device.\n",
idx == VIRTIO_SCMI_VQ_TX ? "TX" : "RX",
(vioch && !vioch->cinfo) ? "" : "NOT ");
return vioch && !vioch->cinfo;
}
static void scmi_destroy_tx_workqueue(void *deferred_tx_wq)
{
destroy_workqueue(deferred_tx_wq);
}
static int virtio_chan_setup(struct scmi_chan_info *cinfo, struct device *dev,
bool tx)
{
struct scmi_vio_channel *vioch;
int index = tx ? VIRTIO_SCMI_VQ_TX : VIRTIO_SCMI_VQ_RX;
int i;
if (!scmi_vdev)
return -EINVAL;
vioch = &((struct scmi_vio_channel *)scmi_vdev->priv)[index];
/* Setup a deferred worker for polling. */
if (tx && !vioch->deferred_tx_wq) {
int ret;
vioch->deferred_tx_wq =
alloc_workqueue(dev_name(&scmi_vdev->dev),
WQ_UNBOUND | WQ_FREEZABLE | WQ_SYSFS,
0);
if (!vioch->deferred_tx_wq)
return -ENOMEM;
ret = devm_add_action_or_reset(dev, scmi_destroy_tx_workqueue,
vioch->deferred_tx_wq);
if (ret)
return ret;
INIT_WORK(&vioch->deferred_tx_work,
scmi_vio_deferred_tx_worker);
}
for (i = 0; i < vioch->max_msg; i++) {
struct scmi_vio_msg *msg;
msg = devm_kzalloc(dev, sizeof(*msg), GFP_KERNEL);
if (!msg)
return -ENOMEM;
msg->max_len = VIRTIO_SCMI_MAX_PDU_SIZE(cinfo);
if (tx) {
msg->request = devm_kzalloc(dev, msg->max_len,
GFP_KERNEL);
if (!msg->request)
return -ENOMEM;
spin_lock_init(&msg->poll_lock);
refcount_set(&msg->users, 1);
}
msg->input = devm_kzalloc(dev, msg->max_len, GFP_KERNEL);
if (!msg->input)
return -ENOMEM;
scmi_finalize_message(vioch, msg);
}
scmi_vio_channel_ready(vioch, cinfo);
dev_dbg(&scmi_vdev->dev, "%s Channel SETUP on SCMI Virtio device.\n",
tx ? "TX" : "RX");
return 0;
}
static int virtio_chan_free(int id, void *p, void *data)
{
struct scmi_chan_info *cinfo = p;
struct scmi_vio_channel *vioch = cinfo->transport_info;
/*
* Break device to inhibit further traffic flowing while shutting down
* the channels: doing it later holding vioch->lock creates unsafe
* locking dependency chains as reported by LOCKDEP.
*/
virtio_break_device(vioch->vqueue->vdev);
scmi_vio_channel_cleanup_sync(vioch);
return 0;
}
static int virtio_send_message(struct scmi_chan_info *cinfo,
struct scmi_xfer *xfer)
{
struct scmi_vio_channel *vioch = cinfo->transport_info;
struct scatterlist sg_out;
struct scatterlist sg_in;
struct scatterlist *sgs[DESCRIPTORS_PER_TX_MSG] = { &sg_out, &sg_in };
unsigned long flags;
int rc;
struct scmi_vio_msg *msg;
if (!scmi_vio_channel_acquire(vioch))
return -EINVAL;
msg = scmi_virtio_get_free_msg(vioch);
if (!msg) {
scmi_vio_channel_release(vioch);
return -EBUSY;
}
core->msg->tx_prepare(msg->request, xfer);
sg_init_one(&sg_out, msg->request, core->msg->command_size(xfer));
sg_init_one(&sg_in, msg->input, core->msg->response_size(xfer));
spin_lock_irqsave(&vioch->lock, flags);
/*
* If polling was requested for this transaction:
* - retrieve last used index (will be used as polling reference)
* - bind the polled message to the xfer via .priv
* - grab an additional msg refcount for the poll-path
*/
if (xfer->hdr.poll_completion) {
msg->poll_idx = virtqueue_enable_cb_prepare(vioch->vqueue);
/* Still no users, no need to acquire poll_lock */
msg->poll_status = VIO_MSG_POLLING;
scmi_vio_msg_acquire(msg);
/* Ensure initialized msg is visibly bound to xfer */
smp_store_mb(xfer->priv, msg);
}
rc = virtqueue_add_sgs(vioch->vqueue, sgs, 1, 1, msg, GFP_ATOMIC);
if (rc)
dev_err(vioch->cinfo->dev,
"failed to add to TX virtqueue (%d)\n", rc);
else
virtqueue_kick(vioch->vqueue);
spin_unlock_irqrestore(&vioch->lock, flags);
if (rc) {
/* Ensure order between xfer->priv clear and vq feeding */
smp_store_mb(xfer->priv, NULL);
if (xfer->hdr.poll_completion)
scmi_vio_msg_release(vioch, msg);
scmi_vio_msg_release(vioch, msg);
}
scmi_vio_channel_release(vioch);
return rc;
}
static void virtio_fetch_response(struct scmi_chan_info *cinfo,
struct scmi_xfer *xfer)
{
struct scmi_vio_msg *msg = xfer->priv;
if (msg)
core->msg->fetch_response(msg->input, msg->rx_len, xfer);
}
static void virtio_fetch_notification(struct scmi_chan_info *cinfo,
size_t max_len, struct scmi_xfer *xfer)
{
struct scmi_vio_msg *msg = xfer->priv;
if (msg)
core->msg->fetch_notification(msg->input, msg->rx_len,
max_len, xfer);
}
/**
* virtio_mark_txdone - Mark transmission done
*
* Free only completed polling transfer messages.
*
* Note that in the SCMI VirtIO transport we never explicitly release still
* outstanding but timed-out messages by forcibly re-adding them to the
* free-list inside the TX code path; we instead let IRQ/RX callbacks, or the
* TX deferred worker, eventually clean up such messages once, finally, a late
* reply is received and discarded (if ever).
*
* This approach was deemed preferable since those pending timed-out buffers are
* still effectively owned by the SCMI platform VirtIO device even after timeout
* expiration: forcibly freeing and reusing them before they had been returned
* explicitly by the SCMI platform could lead to subtle bugs due to message
* corruption.
* An SCMI platform VirtIO device which never returns message buffers is
* anyway broken and it will quickly lead to exhaustion of available messages.
*
* For this same reason, here, we take care to free only the polled messages
* that had been somehow replied (only if not by chance already processed on the
* IRQ path - the initial scmi_vio_msg_release() takes care of this) and also
* any timed-out polled message if that indeed appears to have been at least
* dequeued from the virtqueues (VIO_MSG_POLL_DONE): this is needed since such
* messages won't be freed elsewhere. Any other polled message is marked as
* VIO_MSG_POLL_TIMEOUT.
*
* Possible late replies to timed-out polled messages will be eventually freed
* by RX callbacks if delivered on the IRQ path or by the deferred TX worker if
* dequeued on some other polling path.
*
* @cinfo: SCMI channel info
* @ret: Transmission return code
* @xfer: Transfer descriptor
*/
static void virtio_mark_txdone(struct scmi_chan_info *cinfo, int ret,
struct scmi_xfer *xfer)
{
unsigned long flags;
struct scmi_vio_channel *vioch = cinfo->transport_info;
struct scmi_vio_msg *msg = xfer->priv;
if (!msg || !scmi_vio_channel_acquire(vioch))
return;
/* Ensure msg is unbound from xfer anyway at this point */
smp_store_mb(xfer->priv, NULL);
/* Must be a polled xfer and not already freed on the IRQ path */
if (!xfer->hdr.poll_completion || scmi_vio_msg_release(vioch, msg)) {
scmi_vio_channel_release(vioch);
return;
}
spin_lock_irqsave(&msg->poll_lock, flags);
/* Do not free timedout polled messages only if still inflight */
if (ret != -ETIMEDOUT || msg->poll_status == VIO_MSG_POLL_DONE)
scmi_vio_msg_release(vioch, msg);
else if (msg->poll_status == VIO_MSG_POLLING)
msg->poll_status = VIO_MSG_POLL_TIMEOUT;
spin_unlock_irqrestore(&msg->poll_lock, flags);
scmi_vio_channel_release(vioch);
}
/**
* virtio_poll_done - Provide polling support for VirtIO transport
*
* @cinfo: SCMI channel info
* @xfer: Reference to the transfer being poll for.
*
* VirtIO core provides a polling mechanism based only on last used indexes:
* this means that it is possible to poll the virtqueues waiting for something
* new to arrive from the host side, but the only way to check if the freshly
* arrived buffer was indeed what we were waiting for is to compare the newly
* arrived message descriptor with the one we are polling on.
*
* As a consequence it can happen to dequeue something different from the buffer
* we were poll-waiting for: if that is the case such early fetched buffers are
* then added to a the @pending_cmds_list list for later processing by a
* dedicated deferred worker.
*
* So, basically, once something new is spotted we proceed to de-queue all the
* freshly received used buffers until we found the one we were polling on, or,
* we have 'seemingly' emptied the virtqueue; if some buffers are still pending
* in the vqueue at the end of the polling loop (possible due to inherent races
* in virtqueues handling mechanisms), we similarly kick the deferred worker
* and let it process those, to avoid indefinitely looping in the .poll_done
* busy-waiting helper.
*
* Finally, we delegate to the deferred worker also the final free of any timed
* out reply to a polled message that we should dequeue.
*
* Note that, since we do NOT have per-message suppress notification mechanism,
* the message we are polling for could be alternatively delivered via usual
* IRQs callbacks on another core which happened to have IRQs enabled while we
* are actively polling for it here: in such a case it will be handled as such
* by rx_callback() and the polling loop in the SCMI Core TX path will be
* transparently terminated anyway.
*
* Return: True once polling has successfully completed.
*/
static bool virtio_poll_done(struct scmi_chan_info *cinfo,
struct scmi_xfer *xfer)
{
bool pending, found = false;
unsigned int length, any_prefetched = 0;
unsigned long flags;
struct scmi_vio_msg *next_msg, *msg = xfer->priv;
struct scmi_vio_channel *vioch = cinfo->transport_info;
if (!msg)
return true;
/*
* Processed already by other polling loop on another CPU ?
*
* Note that this message is acquired on the poll path so cannot vanish
* while inside this loop iteration even if concurrently processed on
* the IRQ path.
*
* Avoid to acquire poll_lock since polled_status can be changed
* in a relevant manner only later in this same thread of execution:
* any other possible changes made concurrently by other polling loops
* or by a reply delivered on the IRQ path have no meaningful impact on
* this loop iteration: in other words it is harmless to allow this
* possible race but let has avoid spinlocking with irqs off in this
* initial part of the polling loop.
*/
if (msg->poll_status == VIO_MSG_POLL_DONE)
return true;
if (!scmi_vio_channel_acquire(vioch))
return true;
/* Has cmdq index moved at all ? */
pending = virtqueue_poll(vioch->vqueue, msg->poll_idx);
if (!pending) {
scmi_vio_channel_release(vioch);
return false;
}
spin_lock_irqsave(&vioch->lock, flags);
virtqueue_disable_cb(vioch->vqueue);
/*
* Process all new messages till the polled-for message is found OR
* the vqueue is empty.
*/
while ((next_msg = virtqueue_get_buf(vioch->vqueue, &length))) {
bool next_msg_done = false;
/*
* Mark any dequeued buffer message as VIO_MSG_POLL_DONE so
* that can be properly freed even on timeout in mark_txdone.
*/
spin_lock(&next_msg->poll_lock);
if (next_msg->poll_status == VIO_MSG_POLLING) {
next_msg->poll_status = VIO_MSG_POLL_DONE;
next_msg_done = true;
}
spin_unlock(&next_msg->poll_lock);
next_msg->rx_len = length;
/* Is the message we were polling for ? */
if (next_msg == msg) {
found = true;
break;
} else if (next_msg_done) {
/* Skip the rest if this was another polled msg */
continue;
}
/*
* Enqueue for later processing any non-polled message and any
* timed-out polled one that we happen to have dequeued.
*/
spin_lock(&next_msg->poll_lock);
if (next_msg->poll_status == VIO_MSG_NOT_POLLED ||
next_msg->poll_status == VIO_MSG_POLL_TIMEOUT) {
spin_unlock(&next_msg->poll_lock);
any_prefetched++;
spin_lock(&vioch->pending_lock);
list_add_tail(&next_msg->list,
&vioch->pending_cmds_list);
spin_unlock(&vioch->pending_lock);
} else {
spin_unlock(&next_msg->poll_lock);
}
}
/*
* When the polling loop has successfully terminated if something
* else was queued in the meantime, it will be served by a deferred
* worker OR by the normal IRQ/callback OR by other poll loops.
*
* If we are still looking for the polled reply, the polling index has
* to be updated to the current vqueue last used index.
*/
if (found) {
pending = !virtqueue_enable_cb(vioch->vqueue);
} else {
msg->poll_idx = virtqueue_enable_cb_prepare(vioch->vqueue);
pending = virtqueue_poll(vioch->vqueue, msg->poll_idx);
}
if (vioch->deferred_tx_wq && (any_prefetched || pending))
queue_work(vioch->deferred_tx_wq, &vioch->deferred_tx_work);
spin_unlock_irqrestore(&vioch->lock, flags);
scmi_vio_channel_release(vioch);
return found;
}
static const struct scmi_transport_ops scmi_virtio_ops = {
.chan_available = virtio_chan_available,
.chan_setup = virtio_chan_setup,
.chan_free = virtio_chan_free,
.get_max_msg = virtio_get_max_msg,
.send_message = virtio_send_message,
.fetch_response = virtio_fetch_response,
.fetch_notification = virtio_fetch_notification,
.mark_txdone = virtio_mark_txdone,
.poll_done = virtio_poll_done,
};
static struct scmi_desc scmi_virtio_desc = {
.ops = &scmi_virtio_ops,
/* for non-realtime virtio devices */
.max_rx_timeout_ms = VIRTIO_MAX_RX_TIMEOUT_MS,
.max_msg = 0, /* overridden by virtio_get_max_msg() */
.max_msg_size = VIRTIO_SCMI_MAX_MSG_SIZE,
.atomic_enabled = IS_ENABLED(CONFIG_ARM_SCMI_TRANSPORT_VIRTIO_ATOMIC_ENABLE),
};
static const struct of_device_id scmi_of_match[] = {
{ .compatible = "arm,scmi-virtio", .data = &scmi_virtio_supplier.th},
{ /* Sentinel */ },
};
DEFINE_SCMI_TRANSPORT_DRIVER(scmi_virtio, scmi_virtio_driver, scmi_virtio_desc,
scmi_of_match, core);
static int scmi_vio_probe(struct virtio_device *vdev)
{
struct device *dev = &vdev->dev;
struct scmi_vio_channel *channels;
bool have_vq_rx;
int vq_cnt;
int i;
int ret;
struct virtqueue *vqs[VIRTIO_SCMI_VQ_MAX_CNT];
/* Only one SCMI VirtiO device allowed */
if (scmi_vdev) {
dev_err(dev,
"One SCMI Virtio device was already initialized: only one allowed.\n");
return -EBUSY;
}
have_vq_rx = scmi_vio_have_vq_rx(vdev);
vq_cnt = have_vq_rx ? VIRTIO_SCMI_VQ_MAX_CNT : 1;
channels = devm_kcalloc(dev, vq_cnt, sizeof(*channels), GFP_KERNEL);
if (!channels)
return -ENOMEM;
if (have_vq_rx)
channels[VIRTIO_SCMI_VQ_RX].is_rx = true;
ret = virtio_find_vqs(vdev, vq_cnt, vqs, scmi_vio_vqs_info, NULL);
if (ret) {
dev_err(dev, "Failed to get %d virtqueue(s)\n", vq_cnt);
return ret;
}
for (i = 0; i < vq_cnt; i++) {
unsigned int sz;
spin_lock_init(&channels[i].lock);
spin_lock_init(&channels[i].free_lock);
INIT_LIST_HEAD(&channels[i].free_list);
spin_lock_init(&channels[i].pending_lock);
INIT_LIST_HEAD(&channels[i].pending_cmds_list);
channels[i].vqueue = vqs[i];
sz = virtqueue_get_vring_size(channels[i].vqueue);
/* Tx messages need multiple descriptors. */
if (!channels[i].is_rx)
sz /= DESCRIPTORS_PER_TX_MSG;
if (sz > MSG_TOKEN_MAX) {
dev_info(dev,
"%s virtqueue could hold %d messages. Only %ld allowed to be pending.\n",
channels[i].is_rx ? "rx" : "tx",
sz, MSG_TOKEN_MAX);
sz = MSG_TOKEN_MAX;
}
channels[i].max_msg = sz;
dev_dbg(dev, "VQ%d initialized with max_msg: %d\n", i, sz);
}
vdev->priv = channels;
/* Ensure initialized scmi_vdev is visible */
smp_store_mb(scmi_vdev, vdev);
/* Set device ready */
virtio_device_ready(vdev);
ret = scmi_transport_supplier_put(&scmi_virtio_supplier.th, &vdev->dev);
if (ret) {
virtio_reset_device(vdev);
vdev->priv = NULL;
vdev->config->del_vqs(vdev);
/* Ensure NULLified scmi_vdev is visible */
smp_store_mb(scmi_vdev, NULL);
return ret;
}
dev_dbg(dev, "Probed and initialized SCMI Virtio device.\n");
return 0;
}
static void scmi_vio_remove(struct virtio_device *vdev)
{
/*
* Once we get here, virtio_chan_free() will have already been called by
* the SCMI core for any existing channel and, as a consequence, all the
* virtio channels will have been already marked NOT ready, causing any
* outstanding message on any vqueue to be ignored by complete_cb: now
* we can just stop processing buffers and destroy the vqueues.
*/
virtio_reset_device(vdev);
vdev->config->del_vqs(vdev);
/* Ensure scmi_vdev is visible as NULL */
smp_store_mb(scmi_vdev, NULL);
scmi_transport_supplier_put(&scmi_virtio_supplier.th, &vdev->dev);
}
static int scmi_vio_validate(struct virtio_device *vdev)
{
#ifdef CONFIG_ARM_SCMI_TRANSPORT_VIRTIO_VERSION1_COMPLIANCE
if (!virtio_has_feature(vdev, VIRTIO_F_VERSION_1)) {
dev_err(&vdev->dev,
"device does not comply with spec version 1.x\n");
return -EINVAL;
}
#endif
return 0;
}
static unsigned int features[] = {
VIRTIO_SCMI_F_P2A_CHANNELS,
};
static const struct virtio_device_id id_table[] = {
{ VIRTIO_ID_SCMI, VIRTIO_DEV_ANY_ID },
{ 0 }
};
MODULE_DEVICE_TABLE(virtio, id_table);
static struct virtio_driver virtio_scmi_driver = {
.driver.name = "scmi-virtio",
.feature_table = features,
.feature_table_size = ARRAY_SIZE(features),
.id_table = id_table,
.probe = scmi_vio_probe,
.remove = scmi_vio_remove,
.validate = scmi_vio_validate,
};
static int __init scmi_transport_virtio_init(void)
{
int ret;
ret = register_virtio_driver(&virtio_scmi_driver);
if (ret)
return ret;
ret = platform_driver_register(&scmi_virtio_driver);
if (ret) {
unregister_virtio_driver(&virtio_scmi_driver);
return ret;
}
return ret;
}
module_init(scmi_transport_virtio_init);
static void __exit scmi_transport_virtio_exit(void)
{
platform_driver_unregister(&scmi_virtio_driver);
unregister_virtio_driver(&virtio_scmi_driver);
}
module_exit(scmi_transport_virtio_exit);
MODULE_AUTHOR("Igor Skalkin <igor.skalkin@opensynergy.com>");
MODULE_AUTHOR("Peter Hilber <peter.hilber@opensynergy.com>");
MODULE_AUTHOR("Cristian Marussi <cristian.marussi@arm.com>");
MODULE_DESCRIPTION("SCMI VirtIO Transport driver");
MODULE_LICENSE("GPL");
@@ -0,0 +1,50 @@
# SPDX-License-Identifier: GPL-2.0-only
menu "ARM SCMI NXP i.MX Vendor Protocols"
config IMX_SCMI_BBM_EXT
tristate "i.MX SCMI BBM EXTENSION"
depends on ARM_SCMI_PROTOCOL || (COMPILE_TEST && OF)
default y if ARCH_MXC
help
This enables i.MX System BBM control logic which supports RTC
and BUTTON.
To compile this driver as a module, choose M here: the
module will be called imx-sm-bbm.
config IMX_SCMI_CPU_EXT
tristate "i.MX SCMI CPU EXTENSION"
depends on ARM_SCMI_PROTOCOL || (COMPILE_TEST && OF)
depends on IMX_SCMI_CPU_DRV
default y if ARCH_MXC
help
This enables i.MX System CPU Protocol to manage cpu
start, stop and etc.
To compile this driver as a module, choose M here: the
module will be called imx-sm-cpu.
config IMX_SCMI_LMM_EXT
tristate "i.MX SCMI LMM EXTENSION"
depends on ARM_SCMI_PROTOCOL || (COMPILE_TEST && OF)
depends on IMX_SCMI_LMM_DRV
default y if ARCH_MXC
help
This enables i.MX System Logical Machine Protocol to
manage Logical Machines boot, shutdown and etc.
To compile this driver as a module, choose M here: the
module will be called imx-sm-lmm.
config IMX_SCMI_MISC_EXT
tristate "i.MX SCMI MISC EXTENSION"
depends on ARM_SCMI_PROTOCOL || (COMPILE_TEST && OF)
depends on IMX_SCMI_MISC_DRV
default y if ARCH_MXC
help
This enables i.MX System MISC control logic such as gpio expander
wakeup
To compile this driver as a module, choose M here: the
module will be called imx-sm-misc.
endmenu
@@ -0,0 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_IMX_SCMI_BBM_EXT) += imx-sm-bbm.o
obj-$(CONFIG_IMX_SCMI_CPU_EXT) += imx-sm-cpu.o
obj-$(CONFIG_IMX_SCMI_LMM_EXT) += imx-sm-lmm.o
obj-$(CONFIG_IMX_SCMI_MISC_EXT) += imx-sm-misc.o
@@ -0,0 +1,378 @@
// SPDX-License-Identifier: GPL-2.0
/*
* System Control and Management Interface (SCMI) NXP BBM Protocol
*
* Copyright 2024 NXP
*/
#define pr_fmt(fmt) "SCMI Notifications BBM - " fmt
#include <linux/bits.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/scmi_protocol.h>
#include <linux/scmi_imx_protocol.h>
#include "../../protocols.h"
#include "../../notify.h"
#define SCMI_PROTOCOL_SUPPORTED_VERSION 0x10000
enum scmi_imx_bbm_protocol_cmd {
IMX_BBM_GPR_SET = 0x3,
IMX_BBM_GPR_GET = 0x4,
IMX_BBM_RTC_ATTRIBUTES = 0x5,
IMX_BBM_RTC_TIME_SET = 0x6,
IMX_BBM_RTC_TIME_GET = 0x7,
IMX_BBM_RTC_ALARM_SET = 0x8,
IMX_BBM_BUTTON_GET = 0x9,
IMX_BBM_RTC_NOTIFY = 0xA,
IMX_BBM_BUTTON_NOTIFY = 0xB,
};
#define GET_RTCS_NR(x) le32_get_bits((x), GENMASK(23, 16))
#define GET_GPRS_NR(x) le32_get_bits((x), GENMASK(15, 0))
#define SCMI_IMX_BBM_NOTIFY_RTC_UPDATED BIT(2)
#define SCMI_IMX_BBM_NOTIFY_RTC_ROLLOVER BIT(1)
#define SCMI_IMX_BBM_NOTIFY_RTC_ALARM BIT(0)
#define SCMI_IMX_BBM_RTC_ALARM_ENABLE_FLAG BIT(0)
#define SCMI_IMX_BBM_NOTIFY_RTC_FLAG \
(SCMI_IMX_BBM_NOTIFY_RTC_UPDATED | SCMI_IMX_BBM_NOTIFY_RTC_ROLLOVER | \
SCMI_IMX_BBM_NOTIFY_RTC_ALARM)
#define SCMI_IMX_BBM_EVENT_RTC_MASK GENMASK(31, 24)
struct scmi_imx_bbm_info {
int nr_rtc;
int nr_gpr;
};
struct scmi_msg_imx_bbm_protocol_attributes {
__le32 attributes;
};
struct scmi_imx_bbm_set_time {
__le32 id;
__le32 flags;
__le32 value_low;
__le32 value_high;
};
struct scmi_imx_bbm_get_time {
__le32 id;
__le32 flags;
};
struct scmi_imx_bbm_alarm_time {
__le32 id;
__le32 flags;
__le32 value_low;
__le32 value_high;
};
struct scmi_msg_imx_bbm_rtc_notify {
__le32 rtc_id;
__le32 flags;
};
struct scmi_msg_imx_bbm_button_notify {
__le32 flags;
};
struct scmi_imx_bbm_notify_payld {
__le32 flags;
};
static int scmi_imx_bbm_attributes_get(const struct scmi_protocol_handle *ph,
struct scmi_imx_bbm_info *pi)
{
int ret;
struct scmi_xfer *t;
struct scmi_msg_imx_bbm_protocol_attributes *attr;
ret = ph->xops->xfer_get_init(ph, PROTOCOL_ATTRIBUTES, 0, sizeof(*attr), &t);
if (ret)
return ret;
attr = t->rx.buf;
ret = ph->xops->do_xfer(ph, t);
if (!ret) {
pi->nr_rtc = GET_RTCS_NR(attr->attributes);
pi->nr_gpr = GET_GPRS_NR(attr->attributes);
}
ph->xops->xfer_put(ph, t);
return ret;
}
static int scmi_imx_bbm_notify(const struct scmi_protocol_handle *ph,
u32 src_id, int message_id, bool enable)
{
int ret;
struct scmi_xfer *t;
if (message_id == IMX_BBM_RTC_NOTIFY) {
struct scmi_msg_imx_bbm_rtc_notify *rtc_notify;
ret = ph->xops->xfer_get_init(ph, message_id,
sizeof(*rtc_notify), 0, &t);
if (ret)
return ret;
rtc_notify = t->tx.buf;
rtc_notify->rtc_id = cpu_to_le32(0);
rtc_notify->flags =
cpu_to_le32(enable ? SCMI_IMX_BBM_NOTIFY_RTC_FLAG : 0);
} else if (message_id == IMX_BBM_BUTTON_NOTIFY) {
struct scmi_msg_imx_bbm_button_notify *button_notify;
ret = ph->xops->xfer_get_init(ph, message_id,
sizeof(*button_notify), 0, &t);
if (ret)
return ret;
button_notify = t->tx.buf;
button_notify->flags = cpu_to_le32(enable ? 1 : 0);
} else {
return -EINVAL;
}
ret = ph->xops->do_xfer(ph, t);
ph->xops->xfer_put(ph, t);
return ret;
}
static enum scmi_imx_bbm_protocol_cmd evt_2_cmd[] = {
IMX_BBM_RTC_NOTIFY,
IMX_BBM_BUTTON_NOTIFY
};
static int scmi_imx_bbm_set_notify_enabled(const struct scmi_protocol_handle *ph,
u8 evt_id, u32 src_id, bool enable)
{
int ret, cmd_id;
if (evt_id >= ARRAY_SIZE(evt_2_cmd))
return -EINVAL;
cmd_id = evt_2_cmd[evt_id];
ret = scmi_imx_bbm_notify(ph, src_id, cmd_id, enable);
if (ret)
pr_debug("FAIL_ENABLED - evt[%X] dom[%d] - ret:%d\n",
evt_id, src_id, ret);
return ret;
}
static void *scmi_imx_bbm_fill_custom_report(const struct scmi_protocol_handle *ph,
u8 evt_id, ktime_t timestamp,
const void *payld, size_t payld_sz,
void *report, u32 *src_id)
{
const struct scmi_imx_bbm_notify_payld *p = payld;
struct scmi_imx_bbm_notif_report *r = report;
if (sizeof(*p) != payld_sz)
return NULL;
if (evt_id == SCMI_EVENT_IMX_BBM_RTC) {
r->is_rtc = true;
r->is_button = false;
r->timestamp = timestamp;
r->rtc_id = le32_get_bits(p->flags, SCMI_IMX_BBM_EVENT_RTC_MASK);
r->rtc_evt = le32_get_bits(p->flags, SCMI_IMX_BBM_NOTIFY_RTC_FLAG);
dev_dbg(ph->dev, "RTC: %d evt: %x\n", r->rtc_id, r->rtc_evt);
*src_id = r->rtc_evt;
} else if (evt_id == SCMI_EVENT_IMX_BBM_BUTTON) {
r->is_rtc = false;
r->is_button = true;
r->timestamp = timestamp;
dev_dbg(ph->dev, "BBM Button\n");
*src_id = 0;
} else {
WARN_ON_ONCE(1);
return NULL;
}
return r;
}
static const struct scmi_event scmi_imx_bbm_events[] = {
{
.id = SCMI_EVENT_IMX_BBM_RTC,
.max_payld_sz = sizeof(struct scmi_imx_bbm_notify_payld),
.max_report_sz = sizeof(struct scmi_imx_bbm_notif_report),
},
{
.id = SCMI_EVENT_IMX_BBM_BUTTON,
.max_payld_sz = sizeof(struct scmi_imx_bbm_notify_payld),
.max_report_sz = sizeof(struct scmi_imx_bbm_notif_report),
},
};
static const struct scmi_event_ops scmi_imx_bbm_event_ops = {
.set_notify_enabled = scmi_imx_bbm_set_notify_enabled,
.fill_custom_report = scmi_imx_bbm_fill_custom_report,
};
static const struct scmi_protocol_events scmi_imx_bbm_protocol_events = {
.queue_sz = SCMI_PROTO_QUEUE_SZ,
.ops = &scmi_imx_bbm_event_ops,
.evts = scmi_imx_bbm_events,
.num_events = ARRAY_SIZE(scmi_imx_bbm_events),
.num_sources = 1,
};
static int scmi_imx_bbm_rtc_time_set(const struct scmi_protocol_handle *ph,
u32 rtc_id, u64 sec)
{
struct scmi_imx_bbm_info *pi = ph->get_priv(ph);
struct scmi_imx_bbm_set_time *cfg;
struct scmi_xfer *t;
int ret;
if (rtc_id >= pi->nr_rtc)
return -EINVAL;
ret = ph->xops->xfer_get_init(ph, IMX_BBM_RTC_TIME_SET, sizeof(*cfg), 0, &t);
if (ret)
return ret;
cfg = t->tx.buf;
cfg->id = cpu_to_le32(rtc_id);
cfg->flags = 0;
cfg->value_low = cpu_to_le32(lower_32_bits(sec));
cfg->value_high = cpu_to_le32(upper_32_bits(sec));
ret = ph->xops->do_xfer(ph, t);
ph->xops->xfer_put(ph, t);
return ret;
}
static int scmi_imx_bbm_rtc_time_get(const struct scmi_protocol_handle *ph,
u32 rtc_id, u64 *value)
{
struct scmi_imx_bbm_info *pi = ph->get_priv(ph);
struct scmi_imx_bbm_get_time *cfg;
struct scmi_xfer *t;
int ret;
if (rtc_id >= pi->nr_rtc)
return -EINVAL;
ret = ph->xops->xfer_get_init(ph, IMX_BBM_RTC_TIME_GET, sizeof(*cfg),
sizeof(u64), &t);
if (ret)
return ret;
cfg = t->tx.buf;
cfg->id = cpu_to_le32(rtc_id);
cfg->flags = 0;
ret = ph->xops->do_xfer(ph, t);
if (!ret)
*value = get_unaligned_le64(t->rx.buf);
ph->xops->xfer_put(ph, t);
return ret;
}
static int scmi_imx_bbm_rtc_alarm_set(const struct scmi_protocol_handle *ph,
u32 rtc_id, bool enable, u64 sec)
{
struct scmi_imx_bbm_info *pi = ph->get_priv(ph);
struct scmi_imx_bbm_alarm_time *cfg;
struct scmi_xfer *t;
int ret;
if (rtc_id >= pi->nr_rtc)
return -EINVAL;
ret = ph->xops->xfer_get_init(ph, IMX_BBM_RTC_ALARM_SET, sizeof(*cfg), 0, &t);
if (ret)
return ret;
cfg = t->tx.buf;
cfg->id = cpu_to_le32(rtc_id);
cfg->flags = enable ?
cpu_to_le32(SCMI_IMX_BBM_RTC_ALARM_ENABLE_FLAG) : 0;
cfg->value_low = cpu_to_le32(lower_32_bits(sec));
cfg->value_high = cpu_to_le32(upper_32_bits(sec));
ret = ph->xops->do_xfer(ph, t);
ph->xops->xfer_put(ph, t);
return ret;
}
static int scmi_imx_bbm_button_get(const struct scmi_protocol_handle *ph, u32 *state)
{
struct scmi_xfer *t;
int ret;
ret = ph->xops->xfer_get_init(ph, IMX_BBM_BUTTON_GET, 0, sizeof(u32), &t);
if (ret)
return ret;
ret = ph->xops->do_xfer(ph, t);
if (!ret)
*state = get_unaligned_le32(t->rx.buf);
ph->xops->xfer_put(ph, t);
return ret;
}
static const struct scmi_imx_bbm_proto_ops scmi_imx_bbm_proto_ops = {
.rtc_time_get = scmi_imx_bbm_rtc_time_get,
.rtc_time_set = scmi_imx_bbm_rtc_time_set,
.rtc_alarm_set = scmi_imx_bbm_rtc_alarm_set,
.button_get = scmi_imx_bbm_button_get,
};
static int scmi_imx_bbm_protocol_init(const struct scmi_protocol_handle *ph)
{
int ret;
struct scmi_imx_bbm_info *binfo;
dev_info(ph->dev, "NXP SM BBM Version %d.%d\n",
PROTOCOL_REV_MAJOR(ph->version), PROTOCOL_REV_MINOR(ph->version));
binfo = devm_kzalloc(ph->dev, sizeof(*binfo), GFP_KERNEL);
if (!binfo)
return -ENOMEM;
ret = scmi_imx_bbm_attributes_get(ph, binfo);
if (ret)
return ret;
return ph->set_priv(ph, binfo);
}
static const struct scmi_protocol scmi_imx_bbm = {
.id = SCMI_PROTOCOL_IMX_BBM,
.owner = THIS_MODULE,
.instance_init = &scmi_imx_bbm_protocol_init,
.ops = &scmi_imx_bbm_proto_ops,
.events = &scmi_imx_bbm_protocol_events,
.supported_version = SCMI_PROTOCOL_SUPPORTED_VERSION,
.vendor_id = SCMI_IMX_VENDOR,
.sub_vendor_id = SCMI_IMX_SUBVENDOR,
};
module_scmi_protocol(scmi_imx_bbm);
MODULE_ALIAS("scmi-protocol-" __stringify(SCMI_PROTOCOL_IMX_BBM) "-" SCMI_IMX_VENDOR);
MODULE_DESCRIPTION("i.MX SCMI BBM driver");
MODULE_LICENSE("GPL");
@@ -0,0 +1,271 @@
// SPDX-License-Identifier: GPL-2.0
/*
* System control and Management Interface (SCMI) NXP CPU Protocol
*
* Copyright 2025 NXP
*/
#include <linux/bits.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/scmi_protocol.h>
#include <linux/scmi_imx_protocol.h>
#include "../../protocols.h"
#include "../../notify.h"
#define SCMI_PROTOCOL_SUPPORTED_VERSION 0x10000
enum scmi_imx_cpu_protocol_cmd {
SCMI_IMX_CPU_ATTRIBUTES = 0x3,
SCMI_IMX_CPU_START = 0x4,
SCMI_IMX_CPU_STOP = 0x5,
SCMI_IMX_CPU_RESET_VECTOR_SET = 0x6,
SCMI_IMX_CPU_INFO_GET = 0xC,
};
struct scmi_imx_cpu_info {
u32 nr_cpu;
};
#define SCMI_IMX_CPU_NR_CPU_MASK GENMASK(15, 0)
struct scmi_msg_imx_cpu_protocol_attributes {
__le32 attributes;
};
struct scmi_msg_imx_cpu_attributes_out {
__le32 attributes;
#define CPU_MAX_NAME 16
u8 name[CPU_MAX_NAME];
};
struct scmi_imx_cpu_reset_vector_set_in {
__le32 cpuid;
#define CPU_VEC_FLAGS_RESUME BIT(31)
#define CPU_VEC_FLAGS_START BIT(30)
#define CPU_VEC_FLAGS_BOOT BIT(29)
__le32 flags;
__le32 resetvectorlow;
__le32 resetvectorhigh;
};
struct scmi_imx_cpu_info_get_out {
#define CPU_RUN_MODE_START 0
#define CPU_RUN_MODE_HOLD 1
#define CPU_RUN_MODE_STOP 2
#define CPU_RUN_MODE_SLEEP 3
__le32 runmode;
__le32 sleepmode;
__le32 resetvectorlow;
__le32 resetvectorhigh;
};
static int scmi_imx_cpu_validate_cpuid(const struct scmi_protocol_handle *ph,
u32 cpuid)
{
struct scmi_imx_cpu_info *info = ph->get_priv(ph);
if (cpuid >= info->nr_cpu)
return -EINVAL;
return 0;
}
static int scmi_imx_cpu_start(const struct scmi_protocol_handle *ph,
u32 cpuid, bool start)
{
struct scmi_xfer *t;
u8 msg_id;
int ret;
ret = scmi_imx_cpu_validate_cpuid(ph, cpuid);
if (ret)
return ret;
if (start)
msg_id = SCMI_IMX_CPU_START;
else
msg_id = SCMI_IMX_CPU_STOP;
ret = ph->xops->xfer_get_init(ph, msg_id, sizeof(u32), 0, &t);
if (ret)
return ret;
put_unaligned_le32(cpuid, t->tx.buf);
ret = ph->xops->do_xfer(ph, t);
ph->xops->xfer_put(ph, t);
return ret;
}
static int scmi_imx_cpu_reset_vector_set(const struct scmi_protocol_handle *ph,
u32 cpuid, u64 vector, bool start,
bool boot, bool resume)
{
struct scmi_imx_cpu_reset_vector_set_in *in;
struct scmi_xfer *t;
int ret;
ret = scmi_imx_cpu_validate_cpuid(ph, cpuid);
if (ret)
return ret;
ret = ph->xops->xfer_get_init(ph, SCMI_IMX_CPU_RESET_VECTOR_SET, sizeof(*in),
0, &t);
if (ret)
return ret;
in = t->tx.buf;
in->cpuid = cpu_to_le32(cpuid);
in->flags = cpu_to_le32(0);
if (start)
in->flags |= le32_encode_bits(1, CPU_VEC_FLAGS_START);
if (boot)
in->flags |= le32_encode_bits(1, CPU_VEC_FLAGS_BOOT);
if (resume)
in->flags |= le32_encode_bits(1, CPU_VEC_FLAGS_RESUME);
in->resetvectorlow = cpu_to_le32(lower_32_bits(vector));
in->resetvectorhigh = cpu_to_le32(upper_32_bits(vector));
ret = ph->xops->do_xfer(ph, t);
ph->xops->xfer_put(ph, t);
return ret;
}
static int scmi_imx_cpu_started(const struct scmi_protocol_handle *ph, u32 cpuid,
bool *started)
{
struct scmi_imx_cpu_info_get_out *out;
struct scmi_xfer *t;
u32 mode;
int ret;
if (!started)
return -EINVAL;
*started = false;
ret = scmi_imx_cpu_validate_cpuid(ph, cpuid);
if (ret)
return ret;
ret = ph->xops->xfer_get_init(ph, SCMI_IMX_CPU_INFO_GET, sizeof(u32),
0, &t);
if (ret)
return ret;
put_unaligned_le32(cpuid, t->tx.buf);
ret = ph->xops->do_xfer(ph, t);
if (!ret) {
out = t->rx.buf;
mode = le32_to_cpu(out->runmode);
if (mode == CPU_RUN_MODE_START || mode == CPU_RUN_MODE_SLEEP)
*started = true;
}
ph->xops->xfer_put(ph, t);
return ret;
}
static const struct scmi_imx_cpu_proto_ops scmi_imx_cpu_proto_ops = {
.cpu_reset_vector_set = scmi_imx_cpu_reset_vector_set,
.cpu_start = scmi_imx_cpu_start,
.cpu_started = scmi_imx_cpu_started,
};
static int scmi_imx_cpu_protocol_attributes_get(const struct scmi_protocol_handle *ph,
struct scmi_imx_cpu_info *info)
{
struct scmi_msg_imx_cpu_protocol_attributes *attr;
struct scmi_xfer *t;
int ret;
ret = ph->xops->xfer_get_init(ph, PROTOCOL_ATTRIBUTES, 0,
sizeof(*attr), &t);
if (ret)
return ret;
attr = t->rx.buf;
ret = ph->xops->do_xfer(ph, t);
if (!ret) {
info->nr_cpu = le32_get_bits(attr->attributes, SCMI_IMX_CPU_NR_CPU_MASK);
dev_info(ph->dev, "i.MX SM CPU: %d cpus\n",
info->nr_cpu);
}
ph->xops->xfer_put(ph, t);
return ret;
}
static int scmi_imx_cpu_attributes_get(const struct scmi_protocol_handle *ph,
u32 cpuid)
{
struct scmi_msg_imx_cpu_attributes_out *out;
char name[SCMI_SHORT_NAME_MAX_SIZE] = {'\0'};
struct scmi_xfer *t;
int ret;
ret = ph->xops->xfer_get_init(ph, SCMI_IMX_CPU_ATTRIBUTES, sizeof(u32), 0, &t);
if (ret)
return ret;
put_unaligned_le32(cpuid, t->tx.buf);
ret = ph->xops->do_xfer(ph, t);
if (!ret) {
out = t->rx.buf;
strscpy(name, out->name, SCMI_SHORT_NAME_MAX_SIZE);
dev_info(ph->dev, "i.MX CPU: name: %s\n", name);
} else {
dev_err(ph->dev, "i.MX cpu: Failed to get info of cpu(%u)\n", cpuid);
}
ph->xops->xfer_put(ph, t);
return ret;
}
static int scmi_imx_cpu_protocol_init(const struct scmi_protocol_handle *ph)
{
struct scmi_imx_cpu_info *info;
int ret, i;
dev_info(ph->dev, "NXP SM CPU Protocol Version %d.%d\n",
PROTOCOL_REV_MAJOR(ph->version), PROTOCOL_REV_MINOR(ph->version));
info = devm_kzalloc(ph->dev, sizeof(*info), GFP_KERNEL);
if (!info)
return -ENOMEM;
ret = scmi_imx_cpu_protocol_attributes_get(ph, info);
if (ret)
return ret;
for (i = 0; i < info->nr_cpu; i++) {
ret = scmi_imx_cpu_attributes_get(ph, i);
if (ret)
return ret;
}
return ph->set_priv(ph, info);
}
static const struct scmi_protocol scmi_imx_cpu = {
.id = SCMI_PROTOCOL_IMX_CPU,
.owner = THIS_MODULE,
.instance_init = &scmi_imx_cpu_protocol_init,
.ops = &scmi_imx_cpu_proto_ops,
.supported_version = SCMI_PROTOCOL_SUPPORTED_VERSION,
.vendor_id = SCMI_IMX_VENDOR,
.sub_vendor_id = SCMI_IMX_SUBVENDOR,
};
module_scmi_protocol(scmi_imx_cpu);
MODULE_ALIAS("scmi-protocol-" __stringify(SCMI_PROTOCOL_IMX_CPU) "-" SCMI_IMX_VENDOR);
MODULE_DESCRIPTION("i.MX SCMI CPU driver");
MODULE_LICENSE("GPL");
@@ -0,0 +1,258 @@
// SPDX-License-Identifier: GPL-2.0
/*
* System control and Management Interface (SCMI) NXP LMM Protocol
*
* Copyright 2025 NXP
*/
#include <linux/bits.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/scmi_protocol.h>
#include <linux/scmi_imx_protocol.h>
#include "../../protocols.h"
#include "../../notify.h"
#define SCMI_PROTOCOL_SUPPORTED_VERSION 0x10000
enum scmi_imx_lmm_protocol_cmd {
SCMI_IMX_LMM_ATTRIBUTES = 0x3,
SCMI_IMX_LMM_BOOT = 0x4,
SCMI_IMX_LMM_RESET = 0x5,
SCMI_IMX_LMM_SHUTDOWN = 0x6,
SCMI_IMX_LMM_WAKE = 0x7,
SCMI_IMX_LMM_SUSPEND = 0x8,
SCMI_IMX_LMM_NOTIFY = 0x9,
SCMI_IMX_LMM_RESET_REASON = 0xA,
SCMI_IMX_LMM_POWER_ON = 0xB,
SCMI_IMX_LMM_RESET_VECTOR_SET = 0xC,
};
struct scmi_imx_lmm_priv {
u32 nr_lmm;
};
#define SCMI_IMX_LMM_NR_LM_MASK GENMASK(5, 0)
#define SCMI_IMX_LMM_NR_MAX 16
struct scmi_msg_imx_lmm_protocol_attributes {
__le32 attributes;
};
struct scmi_msg_imx_lmm_attributes_out {
__le32 lmid;
__le32 attributes;
__le32 state;
__le32 errstatus;
u8 name[LMM_MAX_NAME];
};
struct scmi_imx_lmm_reset_vector_set_in {
__le32 lmid;
__le32 cpuid;
__le32 flags; /* reserved for future extension */
__le32 resetvectorlow;
__le32 resetvectorhigh;
};
struct scmi_imx_lmm_shutdown_in {
__le32 lmid;
#define SCMI_IMX_LMM_SHUTDOWN_GRACEFUL BIT(0)
__le32 flags;
};
static int scmi_imx_lmm_validate_lmid(const struct scmi_protocol_handle *ph, u32 lmid)
{
struct scmi_imx_lmm_priv *priv = ph->get_priv(ph);
if (lmid >= priv->nr_lmm)
return -EINVAL;
return 0;
}
static int scmi_imx_lmm_attributes(const struct scmi_protocol_handle *ph,
u32 lmid, struct scmi_imx_lmm_info *info)
{
struct scmi_msg_imx_lmm_attributes_out *out;
struct scmi_xfer *t;
int ret;
ret = ph->xops->xfer_get_init(ph, SCMI_IMX_LMM_ATTRIBUTES, sizeof(u32), 0, &t);
if (ret)
return ret;
put_unaligned_le32(lmid, t->tx.buf);
ret = ph->xops->do_xfer(ph, t);
if (!ret) {
out = t->rx.buf;
info->lmid = le32_to_cpu(out->lmid);
info->state = le32_to_cpu(out->state);
info->errstatus = le32_to_cpu(out->errstatus);
strscpy(info->name, out->name);
dev_dbg(ph->dev, "i.MX LMM: Logical Machine(%d), name: %s\n",
info->lmid, info->name);
} else {
dev_err(ph->dev, "i.MX LMM: Failed to get info of Logical Machine(%u)\n", lmid);
}
ph->xops->xfer_put(ph, t);
return ret;
}
static int
scmi_imx_lmm_power_boot(const struct scmi_protocol_handle *ph, u32 lmid, bool boot)
{
struct scmi_xfer *t;
u8 msg_id;
int ret;
ret = scmi_imx_lmm_validate_lmid(ph, lmid);
if (ret)
return ret;
if (boot)
msg_id = SCMI_IMX_LMM_BOOT;
else
msg_id = SCMI_IMX_LMM_POWER_ON;
ret = ph->xops->xfer_get_init(ph, msg_id, sizeof(u32), 0, &t);
if (ret)
return ret;
put_unaligned_le32(lmid, t->tx.buf);
ret = ph->xops->do_xfer(ph, t);
ph->xops->xfer_put(ph, t);
return ret;
}
static int scmi_imx_lmm_reset_vector_set(const struct scmi_protocol_handle *ph,
u32 lmid, u32 cpuid, u32 flags, u64 vector)
{
struct scmi_imx_lmm_reset_vector_set_in *in;
struct scmi_xfer *t;
int ret;
ret = ph->xops->xfer_get_init(ph, SCMI_IMX_LMM_RESET_VECTOR_SET, sizeof(*in),
0, &t);
if (ret)
return ret;
in = t->tx.buf;
in->lmid = cpu_to_le32(lmid);
in->cpuid = cpu_to_le32(cpuid);
in->flags = cpu_to_le32(0);
in->resetvectorlow = cpu_to_le32(lower_32_bits(vector));
in->resetvectorhigh = cpu_to_le32(upper_32_bits(vector));
ret = ph->xops->do_xfer(ph, t);
ph->xops->xfer_put(ph, t);
return ret;
}
static int scmi_imx_lmm_shutdown(const struct scmi_protocol_handle *ph, u32 lmid,
u32 flags)
{
struct scmi_imx_lmm_shutdown_in *in;
struct scmi_xfer *t;
int ret;
ret = scmi_imx_lmm_validate_lmid(ph, lmid);
if (ret)
return ret;
ret = ph->xops->xfer_get_init(ph, SCMI_IMX_LMM_SHUTDOWN, sizeof(*in),
0, &t);
if (ret)
return ret;
in = t->tx.buf;
in->lmid = cpu_to_le32(lmid);
if (flags & SCMI_IMX_LMM_SHUTDOWN_GRACEFUL)
in->flags = cpu_to_le32(SCMI_IMX_LMM_SHUTDOWN_GRACEFUL);
else
in->flags = cpu_to_le32(0);
ret = ph->xops->do_xfer(ph, t);
ph->xops->xfer_put(ph, t);
return ret;
}
static const struct scmi_imx_lmm_proto_ops scmi_imx_lmm_proto_ops = {
.lmm_power_boot = scmi_imx_lmm_power_boot,
.lmm_info = scmi_imx_lmm_attributes,
.lmm_reset_vector_set = scmi_imx_lmm_reset_vector_set,
.lmm_shutdown = scmi_imx_lmm_shutdown,
};
static int scmi_imx_lmm_protocol_attributes_get(const struct scmi_protocol_handle *ph,
struct scmi_imx_lmm_priv *priv)
{
struct scmi_msg_imx_lmm_protocol_attributes *attr;
struct scmi_xfer *t;
int ret;
ret = ph->xops->xfer_get_init(ph, PROTOCOL_ATTRIBUTES, 0,
sizeof(*attr), &t);
if (ret)
return ret;
attr = t->rx.buf;
ret = ph->xops->do_xfer(ph, t);
if (!ret) {
priv->nr_lmm = le32_get_bits(attr->attributes, SCMI_IMX_LMM_NR_LM_MASK);
if (priv->nr_lmm > SCMI_IMX_LMM_NR_MAX) {
dev_err(ph->dev, "i.MX LMM: %d:Exceed max supported Logical Machines\n",
priv->nr_lmm);
ret = -EINVAL;
} else {
dev_info(ph->dev, "i.MX LMM: %d Logical Machines\n", priv->nr_lmm);
}
}
ph->xops->xfer_put(ph, t);
return ret;
}
static int scmi_imx_lmm_protocol_init(const struct scmi_protocol_handle *ph)
{
struct scmi_imx_lmm_priv *info;
int ret;
dev_info(ph->dev, "NXP SM LMM Version %d.%d\n",
PROTOCOL_REV_MAJOR(ph->version), PROTOCOL_REV_MINOR(ph->version));
info = devm_kzalloc(ph->dev, sizeof(*info), GFP_KERNEL);
if (!info)
return -ENOMEM;
ret = scmi_imx_lmm_protocol_attributes_get(ph, info);
if (ret)
return ret;
return ph->set_priv(ph, info);
}
static const struct scmi_protocol scmi_imx_lmm = {
.id = SCMI_PROTOCOL_IMX_LMM,
.owner = THIS_MODULE,
.instance_init = &scmi_imx_lmm_protocol_init,
.ops = &scmi_imx_lmm_proto_ops,
.supported_version = SCMI_PROTOCOL_SUPPORTED_VERSION,
.vendor_id = SCMI_IMX_VENDOR,
.sub_vendor_id = SCMI_IMX_SUBVENDOR,
};
module_scmi_protocol(scmi_imx_lmm);
MODULE_ALIAS("scmi-protocol-" __stringify(SCMI_PROTOCOL_IMX_LMM) "-" SCMI_IMX_VENDOR);
MODULE_DESCRIPTION("i.MX SCMI LMM driver");
MODULE_LICENSE("GPL");
@@ -0,0 +1,593 @@
// SPDX-License-Identifier: GPL-2.0
/*
* System control and Management Interface (SCMI) NXP MISC Protocol
*
* Copyright 2024 NXP
*/
#define pr_fmt(fmt) "SCMI Notifications MISC - " fmt
#include <linux/bits.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/scmi_protocol.h>
#include <linux/scmi_imx_protocol.h>
#include "../../protocols.h"
#include "../../notify.h"
#define SCMI_PROTOCOL_SUPPORTED_VERSION 0x10000
#define MAX_MISC_CTRL_SOURCES GENMASK(15, 0)
enum scmi_imx_misc_protocol_cmd {
SCMI_IMX_MISC_CTRL_SET = 0x3,
SCMI_IMX_MISC_CTRL_GET = 0x4,
SCMI_IMX_MISC_DISCOVER_BUILD_INFO = 0x6,
SCMI_IMX_MISC_CTRL_NOTIFY = 0x8,
SCMI_IMX_MISC_RESET_REASON_GET = 0xA,
SCMI_IMX_MISC_CFG_INFO_GET = 0xC,
SCMI_IMX_MISC_SYSLOG_GET = 0xD,
SCMI_IMX_MISC_BOARD_INFO = 0xE,
};
struct scmi_imx_misc_info {
u32 nr_dev_ctrl;
u32 nr_brd_ctrl;
u32 nr_reason;
};
struct scmi_msg_imx_misc_protocol_attributes {
__le32 attributes;
};
#define GET_BRD_CTRLS_NR(x) le32_get_bits((x), GENMASK(31, 24))
#define GET_REASONS_NR(x) le32_get_bits((x), GENMASK(23, 16))
#define GET_DEV_CTRLS_NR(x) le32_get_bits((x), GENMASK(15, 0))
#define BRD_CTRL_START_ID BIT(15)
struct scmi_imx_misc_ctrl_set_in {
__le32 id;
__le32 num;
__le32 value[];
};
struct scmi_imx_misc_ctrl_notify_in {
__le32 ctrl_id;
__le32 flags;
};
struct scmi_imx_misc_ctrl_notify_payld {
__le32 ctrl_id;
__le32 flags;
};
struct scmi_imx_misc_ctrl_get_out {
__le32 num;
__le32 val[];
};
struct scmi_imx_misc_buildinfo_out {
__le32 buildnum;
__le32 buildcommit;
#define MISC_MAX_BUILDDATE 16
u8 builddate[MISC_MAX_BUILDDATE];
#define MISC_MAX_BUILDTIME 16
u8 buildtime[MISC_MAX_BUILDTIME];
};
struct scmi_imx_misc_board_info_out {
__le32 attributes;
#define MISC_MAX_BRDNAME 16
u8 brdname[MISC_MAX_BRDNAME];
};
struct scmi_imx_misc_cfg_info_out {
__le32 msel;
#define MISC_MAX_CFGNAME 16
u8 cfgname[MISC_MAX_CFGNAME];
};
struct scmi_imx_misc_reset_reason_in {
#define MISC_REASON_FLAG_SYSTEM BIT(0)
__le32 flags;
};
struct scmi_imx_misc_reset_reason_out {
/* Boot reason flags */
#define MISC_BOOT_FLAG_VLD BIT(31)
#define MISC_BOOT_FLAG_ORG_VLD BIT(28)
#define MISC_BOOT_FLAG_ORIGIN GENMASK(27, 24)
#define MISC_BOOT_FLAG_O_SHIFT 24
#define MISC_BOOT_FLAG_ERR_VLD BIT(23)
#define MISC_BOOT_FLAG_ERR_ID GENMASK(22, 8)
#define MISC_BOOT_FLAG_E_SHIFT 8
#define MISC_BOOT_FLAG_REASON GENMASK(7, 0)
__le32 b_flags;
/* Shutdown reason flags */
#define MISC_SHUTDOWN_FLAG_VLD BIT(31)
#define MISC_SHUTDOWN_FLAG_EXT_LEN GENMASK(30, 29)
#define MISC_SHUTDOWN_FLAG_ORG_VLD BIT(28)
#define MISC_SHUTDOWN_FLAG_ORIGIN GENMASK(27, 24)
#define MISC_SHUTDOWN_FLAG_O_SHIFT 24
#define MISC_SHUTDOWN_FLAG_ERR_VLD BIT(23)
#define MISC_SHUTDOWN_FLAG_ERR_ID GENMASK(22, 8)
#define MISC_SHUTDOWN_FLAG_E_SHIFT 8
#define MISC_SHUTDOWN_FLAG_REASON GENMASK(7, 0)
__le32 s_flags;
/* Array of extended info words */
__le32 extinfo[MISC_EXT_INFO_LEN_MAX];
};
struct scmi_imx_misc_syslog_in {
__le32 flags;
__le32 index;
};
#define REMAINING(x) le32_get_bits((x), GENMASK(31, 20))
#define RETURNED(x) le32_get_bits((x), GENMASK(11, 0))
struct scmi_imx_misc_syslog_out {
__le32 numlogflags;
__le32 syslog[];
};
static int scmi_imx_misc_attributes_get(const struct scmi_protocol_handle *ph,
struct scmi_imx_misc_info *mi)
{
int ret;
struct scmi_xfer *t;
struct scmi_msg_imx_misc_protocol_attributes *attr;
ret = ph->xops->xfer_get_init(ph, PROTOCOL_ATTRIBUTES, 0,
sizeof(*attr), &t);
if (ret)
return ret;
attr = t->rx.buf;
ret = ph->xops->do_xfer(ph, t);
if (!ret) {
mi->nr_dev_ctrl = GET_DEV_CTRLS_NR(attr->attributes);
mi->nr_brd_ctrl = GET_BRD_CTRLS_NR(attr->attributes);
mi->nr_reason = GET_REASONS_NR(attr->attributes);
dev_info(ph->dev, "i.MX MISC NUM DEV CTRL: %d, NUM BRD CTRL: %d,NUM Reason: %d\n",
mi->nr_dev_ctrl, mi->nr_brd_ctrl, mi->nr_reason);
}
ph->xops->xfer_put(ph, t);
return ret;
}
static int scmi_imx_misc_ctrl_validate_id(const struct scmi_protocol_handle *ph,
u32 ctrl_id)
{
struct scmi_imx_misc_info *mi = ph->get_priv(ph);
/*
* [0, BRD_CTRL_START_ID) is for Dev Ctrl which is SOC related
* [BRD_CTRL_START_ID, 0xffff) is for Board Ctrl which is board related
*/
if (ctrl_id < BRD_CTRL_START_ID && ctrl_id > mi->nr_dev_ctrl)
return -EINVAL;
if (ctrl_id >= BRD_CTRL_START_ID + mi->nr_brd_ctrl)
return -EINVAL;
return 0;
}
static int scmi_imx_misc_ctrl_notify(const struct scmi_protocol_handle *ph,
u32 ctrl_id, u32 evt_id, u32 flags)
{
struct scmi_imx_misc_ctrl_notify_in *in;
struct scmi_xfer *t;
int ret;
ret = scmi_imx_misc_ctrl_validate_id(ph, ctrl_id);
if (ret)
return ret;
ret = ph->xops->xfer_get_init(ph, SCMI_IMX_MISC_CTRL_NOTIFY,
sizeof(*in), 0, &t);
if (ret)
return ret;
in = t->tx.buf;
in->ctrl_id = cpu_to_le32(ctrl_id);
in->flags = cpu_to_le32(flags);
ret = ph->xops->do_xfer(ph, t);
ph->xops->xfer_put(ph, t);
return ret;
}
static int
scmi_imx_misc_ctrl_set_notify_enabled(const struct scmi_protocol_handle *ph,
u8 evt_id, u32 src_id, bool enable)
{
int ret;
/* misc_ctrl_req_notify is for enablement */
if (enable)
return 0;
ret = scmi_imx_misc_ctrl_notify(ph, src_id, evt_id, 0);
if (ret)
dev_err(ph->dev, "FAIL_ENABLED - evt[%X] src[%d] - ret:%d\n",
evt_id, src_id, ret);
return ret;
}
static void *
scmi_imx_misc_ctrl_fill_custom_report(const struct scmi_protocol_handle *ph,
u8 evt_id, ktime_t timestamp,
const void *payld, size_t payld_sz,
void *report, u32 *src_id)
{
const struct scmi_imx_misc_ctrl_notify_payld *p = payld;
struct scmi_imx_misc_ctrl_notify_report *r = report;
if (sizeof(*p) != payld_sz)
return NULL;
r->timestamp = timestamp;
r->ctrl_id = le32_to_cpu(p->ctrl_id);
r->flags = le32_to_cpu(p->flags);
if (src_id)
*src_id = r->ctrl_id;
dev_dbg(ph->dev, "%s: ctrl_id: %d flags: %d\n", __func__,
r->ctrl_id, r->flags);
return r;
}
static const struct scmi_event_ops scmi_imx_misc_event_ops = {
.set_notify_enabled = scmi_imx_misc_ctrl_set_notify_enabled,
.fill_custom_report = scmi_imx_misc_ctrl_fill_custom_report,
};
static const struct scmi_event scmi_imx_misc_events[] = {
{
.id = SCMI_EVENT_IMX_MISC_CONTROL,
.max_payld_sz = sizeof(struct scmi_imx_misc_ctrl_notify_payld),
.max_report_sz = sizeof(struct scmi_imx_misc_ctrl_notify_report),
},
};
static struct scmi_protocol_events scmi_imx_misc_protocol_events = {
.queue_sz = SCMI_PROTO_QUEUE_SZ,
.ops = &scmi_imx_misc_event_ops,
.evts = scmi_imx_misc_events,
.num_events = ARRAY_SIZE(scmi_imx_misc_events),
.num_sources = MAX_MISC_CTRL_SOURCES,
};
static int scmi_imx_misc_ctrl_get(const struct scmi_protocol_handle *ph,
u32 ctrl_id, u32 *num, u32 *val)
{
struct scmi_imx_misc_ctrl_get_out *out;
struct scmi_xfer *t;
int ret, i;
int max_msg_size = ph->hops->get_max_msg_size(ph);
int max_num = (max_msg_size - sizeof(*out)) / sizeof(__le32);
ret = scmi_imx_misc_ctrl_validate_id(ph, ctrl_id);
if (ret)
return ret;
ret = ph->xops->xfer_get_init(ph, SCMI_IMX_MISC_CTRL_GET, sizeof(u32),
0, &t);
if (ret)
return ret;
put_unaligned_le32(ctrl_id, t->tx.buf);
ret = ph->xops->do_xfer(ph, t);
if (!ret) {
out = t->rx.buf;
*num = le32_to_cpu(out->num);
if (*num >= max_num ||
*num * sizeof(__le32) > t->rx.len - sizeof(__le32)) {
ph->xops->xfer_put(ph, t);
return -EINVAL;
}
for (i = 0; i < *num; i++)
val[i] = le32_to_cpu(out->val[i]);
}
ph->xops->xfer_put(ph, t);
return ret;
}
static int scmi_imx_misc_ctrl_set(const struct scmi_protocol_handle *ph,
u32 ctrl_id, u32 num, u32 *val)
{
struct scmi_imx_misc_ctrl_set_in *in;
struct scmi_xfer *t;
int ret, i;
int max_msg_size = ph->hops->get_max_msg_size(ph);
int max_num = (max_msg_size - sizeof(*in)) / sizeof(__le32);
ret = scmi_imx_misc_ctrl_validate_id(ph, ctrl_id);
if (ret)
return ret;
if (num > max_num)
return -EINVAL;
ret = ph->xops->xfer_get_init(ph, SCMI_IMX_MISC_CTRL_SET,
sizeof(*in) + num * sizeof(__le32), 0, &t);
if (ret)
return ret;
in = t->tx.buf;
in->id = cpu_to_le32(ctrl_id);
in->num = cpu_to_le32(num);
for (i = 0; i < num; i++)
in->value[i] = cpu_to_le32(val[i]);
ret = ph->xops->do_xfer(ph, t);
ph->xops->xfer_put(ph, t);
return ret;
}
static int scmi_imx_misc_build_info_discover(const struct scmi_protocol_handle *ph)
{
char date[MISC_MAX_BUILDDATE], time[MISC_MAX_BUILDTIME];
struct scmi_imx_misc_buildinfo_out *out;
struct scmi_xfer *t;
int ret;
ret = ph->xops->xfer_get_init(ph, SCMI_IMX_MISC_DISCOVER_BUILD_INFO, 0,
sizeof(*out), &t);
if (ret)
return ret;
ret = ph->xops->do_xfer(ph, t);
if (!ret) {
out = t->rx.buf;
strscpy(date, out->builddate, MISC_MAX_BUILDDATE);
strscpy(time, out->buildtime, MISC_MAX_BUILDTIME);
dev_info(ph->dev, "SM Version\t= Build %u, Commit %08x %s %s\n",
le32_to_cpu(out->buildnum), le32_to_cpu(out->buildcommit),
date, time);
}
ph->xops->xfer_put(ph, t);
return ret;
}
static int scmi_imx_misc_board_info(const struct scmi_protocol_handle *ph)
{
struct scmi_imx_misc_board_info_out *out;
char name[MISC_MAX_BRDNAME];
struct scmi_xfer *t;
int ret;
ret = ph->xops->xfer_get_init(ph, SCMI_IMX_MISC_BOARD_INFO, 0, sizeof(*out), &t);
if (ret)
return ret;
ret = ph->xops->do_xfer(ph, t);
if (!ret) {
out = t->rx.buf;
strscpy(name, out->brdname, MISC_MAX_BRDNAME);
dev_info(ph->dev, "Board\t\t= %s, attr=0x%08x\n",
name, le32_to_cpu(out->attributes));
}
ph->xops->xfer_put(ph, t);
return ret;
}
static int scmi_imx_misc_cfg_info_get(const struct scmi_protocol_handle *ph)
{
struct scmi_imx_misc_cfg_info_out *out;
char name[MISC_MAX_CFGNAME];
struct scmi_xfer *t;
int ret;
ret = ph->xops->xfer_get_init(ph, SCMI_IMX_MISC_CFG_INFO_GET, 0, sizeof(*out), &t);
if (ret)
return ret;
ret = ph->xops->do_xfer(ph, t);
if (!ret) {
out = t->rx.buf;
strscpy(name, out->cfgname, MISC_MAX_CFGNAME);
dev_info(ph->dev, "SM Config\t= %s, mSel = %u\n",
name, le32_to_cpu(out->msel));
}
ph->xops->xfer_put(ph, t);
return ret;
}
struct scmi_imx_misc_syslog_ipriv {
u32 *array;
u16 *size;
};
static void iter_misc_syslog_prepare_message(void *message, u32 desc_index,
const void *priv)
{
struct scmi_imx_misc_syslog_in *msg = message;
msg->flags = cpu_to_le32(0);
msg->index = cpu_to_le32(desc_index);
}
static int iter_misc_syslog_update_state(struct scmi_iterator_state *st,
const void *response, void *priv)
{
const struct scmi_imx_misc_syslog_out *r = response;
struct scmi_imx_misc_syslog_ipriv *p = priv;
st->num_returned = RETURNED(r->numlogflags);
st->num_remaining = REMAINING(r->numlogflags);
*p->size = st->num_returned + st->num_remaining;
return 0;
}
static int
iter_misc_syslog_process_response(const struct scmi_protocol_handle *ph,
const void *response,
struct scmi_iterator_state *st, void *priv)
{
const struct scmi_imx_misc_syslog_out *r = response;
struct scmi_imx_misc_syslog_ipriv *p = priv;
p->array[st->desc_index + st->loop_idx] =
le32_to_cpu(r->syslog[st->loop_idx]);
return 0;
}
static int scmi_imx_misc_syslog_get(const struct scmi_protocol_handle *ph, u16 *size,
void *array)
{
struct scmi_iterator_ops ops = {
.prepare_message = iter_misc_syslog_prepare_message,
.update_state = iter_misc_syslog_update_state,
.process_response = iter_misc_syslog_process_response,
};
struct scmi_imx_misc_syslog_ipriv ipriv = {
.array = array,
.size = size,
};
void *iter;
if (!array || !size || !*size)
return -EINVAL;
iter = ph->hops->iter_response_init(ph, &ops, *size, SCMI_IMX_MISC_SYSLOG_GET,
sizeof(struct scmi_imx_misc_syslog_in),
&ipriv);
if (IS_ERR(iter))
return PTR_ERR(iter);
/* If firmware return NOT SUPPORTED, propagate value to caller */
return ph->hops->iter_response_run(iter);
}
static int scmi_imx_misc_reset_reason(const struct scmi_protocol_handle *ph, bool system,
struct scmi_imx_misc_reset_reason *boot_r,
struct scmi_imx_misc_reset_reason *shut_r,
u32 *extinfo)
{
struct scmi_imx_misc_reset_reason_in *in;
struct scmi_imx_misc_reset_reason_out *out;
struct scmi_xfer *t;
int ret;
ret = ph->xops->xfer_get_init(ph, SCMI_IMX_MISC_RESET_REASON_GET, sizeof(*in),
sizeof(*out), &t);
if (ret)
return ret;
in = t->tx.buf;
if (system)
in->flags = le32_encode_bits(1, MISC_REASON_FLAG_SYSTEM);
else
in->flags = cpu_to_le32(0);
ret = ph->xops->do_xfer(ph, t);
if (!ret) {
out = t->rx.buf;
if (boot_r) {
boot_r->valid = le32_get_bits(out->b_flags, MISC_BOOT_FLAG_VLD);
boot_r->orig_valid = le32_get_bits(out->b_flags, MISC_BOOT_FLAG_ORG_VLD);
boot_r->err_valid = le32_get_bits(out->b_flags, MISC_BOOT_FLAG_ERR_VLD);
boot_r->reason = le32_get_bits(out->b_flags, MISC_BOOT_FLAG_REASON);
boot_r->origin = le32_get_bits(out->b_flags, MISC_BOOT_FLAG_ORIGIN);
boot_r->errid = le32_get_bits(out->b_flags, MISC_BOOT_FLAG_ERR_ID);
}
if (shut_r) {
shut_r->valid = le32_get_bits(out->s_flags, MISC_SHUTDOWN_FLAG_VLD);
shut_r->orig_valid = le32_get_bits(out->s_flags,
MISC_SHUTDOWN_FLAG_ORG_VLD);
shut_r->err_valid = le32_get_bits(out->s_flags,
MISC_SHUTDOWN_FLAG_ERR_VLD);
shut_r->reason = le32_get_bits(out->s_flags, MISC_SHUTDOWN_FLAG_REASON);
shut_r->origin = le32_get_bits(out->s_flags, MISC_SHUTDOWN_FLAG_ORIGIN);
shut_r->errid = le32_get_bits(out->s_flags, MISC_SHUTDOWN_FLAG_ERR_ID);
}
if (extinfo)
memcpy_from_le32(extinfo, out->extinfo, MISC_EXT_INFO_LEN_MAX);
}
ph->xops->xfer_put(ph, t);
return ret;
}
static const struct scmi_imx_misc_proto_ops scmi_imx_misc_proto_ops = {
.misc_ctrl_set = scmi_imx_misc_ctrl_set,
.misc_ctrl_get = scmi_imx_misc_ctrl_get,
.misc_ctrl_req_notify = scmi_imx_misc_ctrl_notify,
.misc_syslog = scmi_imx_misc_syslog_get,
.misc_reset_reason = scmi_imx_misc_reset_reason,
};
static int scmi_imx_misc_protocol_init(const struct scmi_protocol_handle *ph)
{
struct scmi_imx_misc_info *minfo;
int ret;
dev_info(ph->dev, "NXP SM MISC Version %d.%d\n",
PROTOCOL_REV_MAJOR(ph->version), PROTOCOL_REV_MINOR(ph->version));
minfo = devm_kzalloc(ph->dev, sizeof(*minfo), GFP_KERNEL);
if (!minfo)
return -ENOMEM;
ret = scmi_imx_misc_attributes_get(ph, minfo);
if (ret)
return ret;
ret = scmi_imx_misc_build_info_discover(ph);
if (ret && ret != -EOPNOTSUPP)
return ret;
ret = scmi_imx_misc_board_info(ph);
if (ret && ret != -EOPNOTSUPP)
return ret;
ret = scmi_imx_misc_cfg_info_get(ph);
if (ret && ret != -EOPNOTSUPP)
return ret;
return ph->set_priv(ph, minfo);
}
static const struct scmi_protocol scmi_imx_misc = {
.id = SCMI_PROTOCOL_IMX_MISC,
.owner = THIS_MODULE,
.instance_init = &scmi_imx_misc_protocol_init,
.ops = &scmi_imx_misc_proto_ops,
.events = &scmi_imx_misc_protocol_events,
.supported_version = SCMI_PROTOCOL_SUPPORTED_VERSION,
.vendor_id = SCMI_IMX_VENDOR,
.sub_vendor_id = SCMI_IMX_SUBVENDOR,
};
module_scmi_protocol(scmi_imx_misc);
MODULE_ALIAS("scmi-protocol-" __stringify(SCMI_PROTOCOL_IMX_MISC) "-" SCMI_IMX_VENDOR);
MODULE_DESCRIPTION("i.MX SCMI MISC driver");
MODULE_LICENSE("GPL");
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,444 @@
// SPDX-License-Identifier: GPL-2.0
/*
* System Control and Management Interface (SCMI) Voltage Protocol
*
* Copyright (C) 2020-2022 ARM Ltd.
*/
#include <linux/module.h>
#include <linux/scmi_protocol.h>
#include "protocols.h"
/* Updated only after ALL the mandatory features for that version are merged */
#define SCMI_PROTOCOL_SUPPORTED_VERSION 0x20001
#define VOLTAGE_DOMS_NUM_MASK GENMASK(15, 0)
#define REMAINING_LEVELS_MASK GENMASK(31, 16)
#define RETURNED_LEVELS_MASK GENMASK(11, 0)
enum scmi_voltage_protocol_cmd {
VOLTAGE_DOMAIN_ATTRIBUTES = 0x3,
VOLTAGE_DESCRIBE_LEVELS = 0x4,
VOLTAGE_CONFIG_SET = 0x5,
VOLTAGE_CONFIG_GET = 0x6,
VOLTAGE_LEVEL_SET = 0x7,
VOLTAGE_LEVEL_GET = 0x8,
VOLTAGE_DOMAIN_NAME_GET = 0x09,
};
#define NUM_VOLTAGE_DOMAINS(x) ((u16)(FIELD_GET(VOLTAGE_DOMS_NUM_MASK, (x))))
struct scmi_msg_resp_domain_attributes {
__le32 attr;
#define SUPPORTS_ASYNC_LEVEL_SET(x) ((x) & BIT(31))
#define SUPPORTS_EXTENDED_NAMES(x) ((x) & BIT(30))
u8 name[SCMI_SHORT_NAME_MAX_SIZE];
};
struct scmi_msg_cmd_describe_levels {
__le32 domain_id;
__le32 level_index;
};
struct scmi_msg_resp_describe_levels {
__le32 flags;
#define NUM_REMAINING_LEVELS(f) ((u16)(FIELD_GET(REMAINING_LEVELS_MASK, (f))))
#define NUM_RETURNED_LEVELS(f) ((u16)(FIELD_GET(RETURNED_LEVELS_MASK, (f))))
#define SUPPORTS_SEGMENTED_LEVELS(f) ((f) & BIT(12))
__le32 voltage[];
};
struct scmi_msg_cmd_config_set {
__le32 domain_id;
__le32 config;
};
struct scmi_msg_cmd_level_set {
__le32 domain_id;
__le32 flags;
__le32 voltage_level;
};
struct scmi_resp_voltage_level_set_complete {
__le32 domain_id;
__le32 voltage_level;
};
struct voltage_info {
unsigned int num_domains;
struct scmi_voltage_info *domains;
};
static int scmi_protocol_attributes_get(const struct scmi_protocol_handle *ph,
struct voltage_info *vinfo)
{
int ret;
struct scmi_xfer *t;
ret = ph->xops->xfer_get_init(ph, PROTOCOL_ATTRIBUTES, 0,
sizeof(__le32), &t);
if (ret)
return ret;
ret = ph->xops->do_xfer(ph, t);
if (!ret)
vinfo->num_domains =
NUM_VOLTAGE_DOMAINS(get_unaligned_le32(t->rx.buf));
ph->xops->xfer_put(ph, t);
return ret;
}
static int scmi_init_voltage_levels(struct device *dev,
struct scmi_voltage_info *v,
u32 num_returned, u32 num_remaining,
bool segmented)
{
u32 num_levels;
num_levels = num_returned + num_remaining;
/*
* segmented levels entries are represented by a single triplet
* returned all in one go.
*/
if (!num_levels ||
(segmented && (num_remaining || num_returned != 3))) {
dev_err(dev,
"Invalid level descriptor(%d/%d/%d) for voltage dom %d\n",
num_levels, num_returned, num_remaining, v->id);
return -EINVAL;
}
v->levels_uv = devm_kcalloc(dev, num_levels, sizeof(u32), GFP_KERNEL);
if (!v->levels_uv)
return -ENOMEM;
v->num_levels = num_levels;
v->segmented = segmented;
return 0;
}
struct scmi_volt_ipriv {
struct device *dev;
struct scmi_voltage_info *v;
};
static void iter_volt_levels_prepare_message(void *message,
unsigned int desc_index,
const void *priv)
{
struct scmi_msg_cmd_describe_levels *msg = message;
const struct scmi_volt_ipriv *p = priv;
msg->domain_id = cpu_to_le32(p->v->id);
msg->level_index = cpu_to_le32(desc_index);
}
static int iter_volt_levels_update_state(struct scmi_iterator_state *st,
const void *response, void *priv)
{
int ret = 0;
u32 flags;
const struct scmi_msg_resp_describe_levels *r = response;
struct scmi_volt_ipriv *p = priv;
flags = le32_to_cpu(r->flags);
st->num_returned = NUM_RETURNED_LEVELS(flags);
st->num_remaining = NUM_REMAINING_LEVELS(flags);
/* Allocate space for num_levels if not already done */
if (!p->v->num_levels) {
ret = scmi_init_voltage_levels(p->dev, p->v, st->num_returned,
st->num_remaining,
SUPPORTS_SEGMENTED_LEVELS(flags));
if (!ret)
st->max_resources = p->v->num_levels;
}
return ret;
}
static int
iter_volt_levels_process_response(const struct scmi_protocol_handle *ph,
const void *response,
struct scmi_iterator_state *st, void *priv)
{
s32 val;
const struct scmi_msg_resp_describe_levels *r = response;
struct scmi_volt_ipriv *p = priv;
val = (s32)le32_to_cpu(r->voltage[st->loop_idx]);
p->v->levels_uv[st->desc_index + st->loop_idx] = val;
if (val < 0)
p->v->negative_volts_allowed = true;
return 0;
}
static int scmi_voltage_levels_get(const struct scmi_protocol_handle *ph,
struct scmi_voltage_info *v)
{
int ret;
void *iter;
struct scmi_iterator_ops ops = {
.prepare_message = iter_volt_levels_prepare_message,
.update_state = iter_volt_levels_update_state,
.process_response = iter_volt_levels_process_response,
};
struct scmi_volt_ipriv vpriv = {
.dev = ph->dev,
.v = v,
};
iter = ph->hops->iter_response_init(ph, &ops, v->num_levels,
VOLTAGE_DESCRIBE_LEVELS,
sizeof(struct scmi_msg_cmd_describe_levels),
&vpriv);
if (IS_ERR(iter))
return PTR_ERR(iter);
ret = ph->hops->iter_response_run(iter);
if (ret) {
v->num_levels = 0;
devm_kfree(ph->dev, v->levels_uv);
}
return ret;
}
static int scmi_voltage_descriptors_get(const struct scmi_protocol_handle *ph,
struct voltage_info *vinfo)
{
int ret, dom;
struct scmi_xfer *td;
struct scmi_msg_resp_domain_attributes *resp_dom;
ret = ph->xops->xfer_get_init(ph, VOLTAGE_DOMAIN_ATTRIBUTES,
sizeof(__le32), sizeof(*resp_dom), &td);
if (ret)
return ret;
resp_dom = td->rx.buf;
for (dom = 0; dom < vinfo->num_domains; dom++) {
u32 attributes;
struct scmi_voltage_info *v;
/* Retrieve domain attributes at first ... */
put_unaligned_le32(dom, td->tx.buf);
/* Skip domain on comms error */
if (ph->xops->do_xfer(ph, td)) {
ph->xops->reset_rx_to_maxsz(ph, td);
continue;
}
v = vinfo->domains + dom;
v->id = dom;
attributes = le32_to_cpu(resp_dom->attr);
strscpy(v->name, resp_dom->name, SCMI_SHORT_NAME_MAX_SIZE);
/*
* If supported overwrite short name with the extended one;
* on error just carry on and use already provided short name.
*/
if (PROTOCOL_REV_MAJOR(ph->version) >= 0x2) {
if (SUPPORTS_EXTENDED_NAMES(attributes))
ph->hops->extended_name_get(ph,
VOLTAGE_DOMAIN_NAME_GET,
v->id, NULL, v->name,
SCMI_MAX_STR_SIZE);
if (SUPPORTS_ASYNC_LEVEL_SET(attributes))
v->async_level_set = true;
}
/* Skip invalid voltage descriptors */
scmi_voltage_levels_get(ph, v);
}
ph->xops->xfer_put(ph, td);
return ret;
}
static int __scmi_voltage_get_u32(const struct scmi_protocol_handle *ph,
u8 cmd_id, u32 domain_id, u32 *value)
{
int ret;
struct scmi_xfer *t;
struct voltage_info *vinfo = ph->get_priv(ph);
if (domain_id >= vinfo->num_domains)
return -EINVAL;
ret = ph->xops->xfer_get_init(ph, cmd_id, sizeof(__le32), 0, &t);
if (ret)
return ret;
put_unaligned_le32(domain_id, t->tx.buf);
ret = ph->xops->do_xfer(ph, t);
if (!ret)
*value = get_unaligned_le32(t->rx.buf);
ph->xops->xfer_put(ph, t);
return ret;
}
static int scmi_voltage_config_set(const struct scmi_protocol_handle *ph,
u32 domain_id, u32 config)
{
int ret;
struct scmi_xfer *t;
struct voltage_info *vinfo = ph->get_priv(ph);
struct scmi_msg_cmd_config_set *cmd;
if (domain_id >= vinfo->num_domains)
return -EINVAL;
ret = ph->xops->xfer_get_init(ph, VOLTAGE_CONFIG_SET,
sizeof(*cmd), 0, &t);
if (ret)
return ret;
cmd = t->tx.buf;
cmd->domain_id = cpu_to_le32(domain_id);
cmd->config = cpu_to_le32(config & GENMASK(3, 0));
ret = ph->xops->do_xfer(ph, t);
ph->xops->xfer_put(ph, t);
return ret;
}
static int scmi_voltage_config_get(const struct scmi_protocol_handle *ph,
u32 domain_id, u32 *config)
{
return __scmi_voltage_get_u32(ph, VOLTAGE_CONFIG_GET,
domain_id, config);
}
static int scmi_voltage_level_set(const struct scmi_protocol_handle *ph,
u32 domain_id,
enum scmi_voltage_level_mode mode,
s32 volt_uV)
{
int ret;
struct scmi_xfer *t;
struct voltage_info *vinfo = ph->get_priv(ph);
struct scmi_msg_cmd_level_set *cmd;
struct scmi_voltage_info *v;
if (domain_id >= vinfo->num_domains)
return -EINVAL;
ret = ph->xops->xfer_get_init(ph, VOLTAGE_LEVEL_SET,
sizeof(*cmd), 0, &t);
if (ret)
return ret;
v = vinfo->domains + domain_id;
cmd = t->tx.buf;
cmd->domain_id = cpu_to_le32(domain_id);
cmd->voltage_level = cpu_to_le32(volt_uV);
if (!v->async_level_set || mode != SCMI_VOLTAGE_LEVEL_SET_AUTO) {
cmd->flags = cpu_to_le32(0x0);
ret = ph->xops->do_xfer(ph, t);
} else {
cmd->flags = cpu_to_le32(0x1);
ret = ph->xops->do_xfer_with_response(ph, t);
if (!ret) {
struct scmi_resp_voltage_level_set_complete *resp;
resp = t->rx.buf;
if (le32_to_cpu(resp->domain_id) == domain_id)
dev_dbg(ph->dev,
"Voltage domain %d set async to %d\n",
v->id,
le32_to_cpu(resp->voltage_level));
else
ret = -EPROTO;
}
}
ph->xops->xfer_put(ph, t);
return ret;
}
static int scmi_voltage_level_get(const struct scmi_protocol_handle *ph,
u32 domain_id, s32 *volt_uV)
{
return __scmi_voltage_get_u32(ph, VOLTAGE_LEVEL_GET,
domain_id, (u32 *)volt_uV);
}
static const struct scmi_voltage_info * __must_check
scmi_voltage_info_get(const struct scmi_protocol_handle *ph, u32 domain_id)
{
struct voltage_info *vinfo = ph->get_priv(ph);
if (domain_id >= vinfo->num_domains ||
!vinfo->domains[domain_id].num_levels)
return NULL;
return vinfo->domains + domain_id;
}
static int scmi_voltage_domains_num_get(const struct scmi_protocol_handle *ph)
{
struct voltage_info *vinfo = ph->get_priv(ph);
return vinfo->num_domains;
}
static const struct scmi_voltage_proto_ops voltage_proto_ops = {
.num_domains_get = scmi_voltage_domains_num_get,
.info_get = scmi_voltage_info_get,
.config_set = scmi_voltage_config_set,
.config_get = scmi_voltage_config_get,
.level_set = scmi_voltage_level_set,
.level_get = scmi_voltage_level_get,
};
static int scmi_voltage_protocol_init(const struct scmi_protocol_handle *ph)
{
int ret;
struct voltage_info *vinfo;
dev_dbg(ph->dev, "Voltage Version %d.%d\n",
PROTOCOL_REV_MAJOR(ph->version), PROTOCOL_REV_MINOR(ph->version));
vinfo = devm_kzalloc(ph->dev, sizeof(*vinfo), GFP_KERNEL);
if (!vinfo)
return -ENOMEM;
ret = scmi_protocol_attributes_get(ph, vinfo);
if (ret)
return ret;
if (vinfo->num_domains) {
vinfo->domains = devm_kcalloc(ph->dev, vinfo->num_domains,
sizeof(*vinfo->domains),
GFP_KERNEL);
if (!vinfo->domains)
return -ENOMEM;
ret = scmi_voltage_descriptors_get(ph, vinfo);
if (ret)
return ret;
} else {
dev_warn(ph->dev, "No Voltage domains found.\n");
}
return ph->set_priv(ph, vinfo);
}
static const struct scmi_protocol scmi_voltage = {
.id = SCMI_PROTOCOL_VOLTAGE,
.owner = THIS_MODULE,
.instance_init = &scmi_voltage_protocol_init,
.ops = &voltage_proto_ops,
.supported_version = SCMI_PROTOCOL_SUPPORTED_VERSION,
};
DEFINE_SCMI_PROTOCOL_REGISTER_UNREGISTER(voltage, scmi_voltage)