u-boot/drivers/firmware/scmi/scmi_agent-uclass.c
Peng Fan 7830ccc77a firmware: scmi: Support probe vendor ID 0x80 and 0x82
Preparing to add i.MX LMM and CPU protocol driver, support probe SCMI
vendor ID 0x80(i.MX SCMI LMM ID) and 0x82(i.MX SCMI CPU ID). And use
Kconfig option to support conditional compilation.

Signed-off-by: Peng Fan <peng.fan@nxp.com>
Reviewed-by: Alice Guo <alice.guo@nxp.com>
2025-10-24 13:47:50 -06:00

509 lines
12 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2020 Linaro Limited.
*/
#define LOG_CATEGORY UCLASS_SCMI_AGENT
#include <dm.h>
#include <errno.h>
#include <scmi_agent.h>
#include <scmi_agent-uclass.h>
#include <scmi_protocols.h>
#include <dm/device_compat.h>
#include <dm/device-internal.h>
#include <linux/compat.h>
/**
* struct error_code - Helper structure for SCMI error code conversion
* @scmi: SCMI error code
* @errno: Related standard error number
*/
struct error_code {
int scmi;
int errno;
};
static const struct error_code scmi_linux_errmap[] = {
{ .scmi = SCMI_NOT_SUPPORTED, .errno = -EOPNOTSUPP, },
{ .scmi = SCMI_INVALID_PARAMETERS, .errno = -EINVAL, },
{ .scmi = SCMI_DENIED, .errno = -EACCES, },
{ .scmi = SCMI_NOT_FOUND, .errno = -ENOENT, },
{ .scmi = SCMI_OUT_OF_RANGE, .errno = -ERANGE, },
{ .scmi = SCMI_BUSY, .errno = -EBUSY, },
{ .scmi = SCMI_COMMS_ERROR, .errno = -ECOMM, },
{ .scmi = SCMI_GENERIC_ERROR, .errno = -EIO, },
{ .scmi = SCMI_HARDWARE_ERROR, .errno = -EREMOTEIO, },
{ .scmi = SCMI_PROTOCOL_ERROR, .errno = -EPROTO, },
{ .scmi = SCMI_IN_USE, .errno = -EADDRINUSE, },
};
/**
* scmi_protocol_is_supported - check availability of protocol
* @dev: SCMI agent device
* @proto_id: Identifier of protocol
*
* check if the protocol, @proto_id, is provided by the SCMI agent,
* @dev.
*
* Return: 0 on success, error code otherwise
*/
static bool scmi_protocol_is_supported(struct udevice *dev,
enum scmi_std_protocol proto_id)
{
struct scmi_agent_priv *priv;
int i;
if (proto_id == SCMI_PROTOCOL_ID_BASE)
return true;
priv = dev_get_uclass_plat(dev);
if (!priv) {
dev_err(dev, "No priv data found\n");
return false;
}
for (i = 0; i < priv->num_protocols; i++)
if (priv->protocols[i] == proto_id)
return true;
return false;
}
struct udevice *scmi_get_protocol(struct udevice *dev,
enum scmi_std_protocol id)
{
struct scmi_agent_priv *priv;
struct udevice *proto;
priv = dev_get_uclass_plat(dev);
if (!priv) {
dev_err(dev, "No priv data found\n");
return NULL;
}
switch (id) {
case SCMI_PROTOCOL_ID_BASE:
proto = priv->base_dev;
break;
#if IS_ENABLED(CONFIG_SCMI_POWER_DOMAIN)
case SCMI_PROTOCOL_ID_POWER_DOMAIN:
proto = priv->pwdom_dev;
break;
#endif
#if IS_ENABLED(CONFIG_CLK_SCMI)
case SCMI_PROTOCOL_ID_CLOCK:
proto = priv->clock_dev;
break;
#endif
#if IS_ENABLED(CONFIG_RESET_SCMI)
case SCMI_PROTOCOL_ID_RESET_DOMAIN:
proto = priv->resetdom_dev;
break;
#endif
#if IS_ENABLED(CONFIG_DM_REGULATOR_SCMI)
case SCMI_PROTOCOL_ID_VOLTAGE_DOMAIN:
proto = priv->voltagedom_dev;
break;
#endif
#if IS_ENABLED(CONFIG_PINCTRL_IMX_SCMI)
case SCMI_PROTOCOL_ID_PINCTRL:
proto = priv->pinctrl_dev;
break;
#endif
#if IS_ENABLED(CONFIG_SCMI_ID_VENDOR_80)
case SCMI_PROTOCOL_ID_VENDOR_80:
proto = priv->vendor_dev_80;
break;
#endif
#if IS_ENABLED(CONFIG_SCMI_ID_VENDOR_82)
case SCMI_PROTOCOL_ID_VENDOR_82:
proto = priv->vendor_dev_82;
break;
#endif
default:
dev_err(dev, "Protocol not supported\n");
proto = NULL;
break;
}
if (proto && device_probe(proto))
dev_err(dev, "Probe failed\n");
return proto;
}
/**
* scmi_add_protocol - add protocol to agent
* @dev: SCMI agent device
* @proto_id: SCMI protocol ID
* @proto: SCMI protocol device
*
* Associate the protocol instance, @proto, to the agent, @dev,
* for later use.
*
* Return: 0 on success, error code on failure
*/
static int scmi_add_protocol(struct udevice *dev,
enum scmi_std_protocol proto_id,
struct udevice *proto)
{
struct scmi_agent_priv *priv;
priv = dev_get_uclass_plat(dev);
if (!priv) {
dev_err(dev, "No priv data found\n");
return -ENODEV;
}
switch (proto_id) {
case SCMI_PROTOCOL_ID_BASE:
priv->base_dev = proto;
break;
#if IS_ENABLED(CONFIG_SCMI_POWER_DOMAIN)
case SCMI_PROTOCOL_ID_POWER_DOMAIN:
priv->pwdom_dev = proto;
break;
#endif
#if IS_ENABLED(CONFIG_CLK_SCMI)
case SCMI_PROTOCOL_ID_CLOCK:
priv->clock_dev = proto;
break;
#endif
#if IS_ENABLED(CONFIG_RESET_SCMI)
case SCMI_PROTOCOL_ID_RESET_DOMAIN:
priv->resetdom_dev = proto;
break;
#endif
#if IS_ENABLED(CONFIG_DM_REGULATOR_SCMI)
case SCMI_PROTOCOL_ID_VOLTAGE_DOMAIN:
priv->voltagedom_dev = proto;
break;
#endif
#if IS_ENABLED(CONFIG_PINCTRL_IMX_SCMI)
case SCMI_PROTOCOL_ID_PINCTRL:
priv->pinctrl_dev = proto;
break;
#endif
#if IS_ENABLED(CONFIG_SCMI_ID_VENDOR_80)
case SCMI_PROTOCOL_ID_VENDOR_80:
priv->vendor_dev_80 = proto;
break;
#endif
#if IS_ENABLED(CONFIG_SCMI_ID_VENDOR_82)
case SCMI_PROTOCOL_ID_VENDOR_82:
priv->vendor_dev_82 = proto;
break;
#endif
default:
dev_err(dev, "Protocol not supported\n");
return -EPROTO;
}
return 0;
}
int scmi_to_linux_errno(s32 scmi_code)
{
int n;
if (!scmi_code)
return 0;
for (n = 0; n < ARRAY_SIZE(scmi_linux_errmap); n++)
if (scmi_code == scmi_linux_errmap[n].scmi)
return scmi_linux_errmap[n].errno;
return -EPROTO;
}
static struct udevice *find_scmi_protocol_device(struct udevice *dev)
{
struct udevice *parent = NULL, *protocol;
for (protocol = dev; protocol; protocol = parent) {
parent = dev_get_parent(protocol);
if (!parent ||
device_get_uclass_id(parent) == UCLASS_SCMI_AGENT)
break;
}
if (!parent) {
dev_err(dev, "Invalid SCMI device, agent not found\n");
return NULL;
}
return protocol;
}
static const struct scmi_agent_ops *transport_dev_ops(struct udevice *dev)
{
return (const struct scmi_agent_ops *)dev->driver->ops;
}
/**
* scmi_of_get_channel() - Get SCMI channel handle
*
* @dev: SCMI agent device
* @channel: Output reference to the SCMI channel upon success
*
* On return, @channel will be set.
* Return 0 on success and a negative errno on failure
*/
static int scmi_of_get_channel(struct udevice *dev, struct udevice *protocol,
struct scmi_channel **channel)
{
const struct scmi_agent_ops *ops;
ops = transport_dev_ops(dev);
if (ops->of_get_channel)
return ops->of_get_channel(dev, protocol, channel);
else
return -EPROTONOSUPPORT;
}
int devm_scmi_of_get_channel(struct udevice *dev)
{
struct udevice *protocol;
struct scmi_agent_proto_priv *priv;
int ret;
protocol = find_scmi_protocol_device(dev);
if (!protocol)
return -ENODEV;
priv = dev_get_parent_priv(protocol);
ret = scmi_of_get_channel(protocol->parent, protocol, &priv->channel);
if (ret == -EPROTONOSUPPORT) {
/* Drivers without a get_channel operator don't need a channel ref */
priv->channel = NULL;
return 0;
}
return ret;
}
/**
* scmi_process_msg() - Send and process an SCMI message
*
* Send a message to an SCMI server.
* Caller sets scmi_msg::out_msg_sz to the output message buffer size.
*
* @dev: SCMI agent device
* @channel: Communication channel for the device
* @msg: Message structure reference
*
* On return, scmi_msg::out_msg_sz stores the response payload size.
* Return: 0 on success and a negative errno on failure
*/
static int scmi_process_msg(struct udevice *dev, struct scmi_channel *channel,
struct scmi_msg *msg)
{
const struct scmi_agent_ops *ops;
ops = transport_dev_ops(dev);
if (ops->process_msg)
return ops->process_msg(dev, channel, msg);
else
return -EPROTONOSUPPORT;
}
int devm_scmi_process_msg(struct udevice *dev, struct scmi_msg *msg)
{
struct udevice *protocol;
struct scmi_agent_proto_priv *priv;
protocol = find_scmi_protocol_device(dev);
if (!protocol)
return -ENODEV;
priv = dev_get_parent_priv(protocol);
return scmi_process_msg(protocol->parent, priv->channel, msg);
}
/**
* scmi_fill_base_info - get base information about SCMI server
* @agent: SCMI agent device
* @dev: SCMI protocol device
*
* By using Base protocol commands, collect the base information
* about SCMI server.
*
* Return: 0 on success, error code on failure
*/
static int scmi_fill_base_info(struct udevice *agent, struct udevice *dev)
{
struct scmi_agent_priv *priv = dev_get_uclass_plat(agent);
int ret;
ret = scmi_base_protocol_version(dev, &priv->version);
if (ret) {
dev_err(dev, "protocol_version() failed (%d)\n", ret);
return ret;
}
/* check for required version */
if (priv->version < SCMI_BASE_PROTOCOL_VERSION) {
dev_err(dev, "base protocol version (%d) lower than expected\n",
priv->version);
return -EPROTO;
}
ret = scmi_base_protocol_attrs(dev, &priv->num_agents,
&priv->num_protocols);
if (ret) {
dev_err(dev, "protocol_attrs() failed (%d)\n", ret);
return ret;
}
ret = scmi_base_discover_vendor(dev, &priv->vendor);
if (ret) {
dev_err(dev, "base_discover_vendor() failed (%d)\n", ret);
return ret;
}
ret = scmi_base_discover_sub_vendor(dev, &priv->sub_vendor);
if (ret) {
if (ret != -EOPNOTSUPP) {
dev_err(dev, "base_discover_sub_vendor() failed (%d)\n",
ret);
return ret;
}
priv->sub_vendor = "NA";
}
ret = scmi_base_discover_impl_version(dev, &priv->impl_version);
if (ret) {
dev_err(dev, "base_discover_impl_version() failed (%d)\n",
ret);
return ret;
}
ret = scmi_base_discover_agent(dev, 0xffffffff,
&priv->agent_id, &priv->agent_name);
if (ret) {
if (ret != -EOPNOTSUPP) {
dev_err(dev,
"base_discover_agent() failed for myself (%d)\n",
ret);
return ret;
}
priv->agent_id = 0xffffffff;
priv->agent_name = "NA";
}
ret = scmi_base_discover_list_protocols(dev, &priv->protocols);
if (ret != priv->num_protocols) {
dev_err(dev, "base_discover_list_protocols() failed (%d)\n",
ret);
return -EPROTO;
}
return 0;
}
static struct driver *scmi_proto_driver_get(unsigned int proto_id)
{
struct scmi_proto_driver *start, *entry;
int n_ents;
start = ll_entry_start(struct scmi_proto_driver, scmi_proto_driver);
n_ents = ll_entry_count(struct scmi_proto_driver, scmi_proto_driver);
for (entry = start; entry != start + n_ents; entry++) {
if (entry->match->proto_id == proto_id)
return entry->driver;
}
return NULL;
}
/*
* SCMI agent devices binds devices of various uclasses depending on
* the FDT description. scmi_bind_protocol() is a generic bind sequence
* called by the uclass at bind stage, that is uclass post_bind.
*/
static int scmi_bind_protocols(struct udevice *dev)
{
int ret = 0;
ofnode node;
const char *name;
struct driver *drv;
struct udevice *agent, *proto;
if (!uclass_get_device(UCLASS_SCMI_AGENT, 1, &agent)) {
/* This is a second SCMI agent */
dev_err(dev, "Cannot have more than one SCMI agent\n");
return -EEXIST;
}
/* initialize the device from device tree */
drv = DM_DRIVER_GET(scmi_base_drv);
name = "scmi-base.0";
ret = device_bind(dev, drv, name, NULL, ofnode_null(), &proto);
if (ret) {
dev_err(dev, "failed to bind base protocol\n");
return ret;
}
ret = scmi_add_protocol(dev, SCMI_PROTOCOL_ID_BASE, proto);
if (ret) {
dev_err(dev, "failed to add protocol: %s, ret: %d\n",
proto->name, ret);
return ret;
}
ret = device_probe(proto);
if (ret) {
dev_err(dev, "failed to probe base protocol\n");
return ret;
}
ret = scmi_fill_base_info(dev, proto);
if (ret) {
dev_err(dev, "failed to get base information\n");
return ret;
}
dev_for_each_subnode(node, dev) {
u32 protocol_id;
if (!ofnode_is_enabled(node))
continue;
if (ofnode_read_u32(node, "reg", &protocol_id))
continue;
drv = NULL;
name = ofnode_get_name(node);
if (!scmi_protocol_is_supported(dev, protocol_id))
continue;
drv = scmi_proto_driver_get(protocol_id);
if (!drv) {
dev_dbg(dev, "Ignore unsupported SCMI protocol %#x\n",
protocol_id);
continue;
}
ret = device_bind(dev, drv, name, NULL, node, &proto);
if (ret) {
dev_err(dev, "failed to bind %s protocol\n", drv->name);
break;
}
ret = scmi_add_protocol(dev, protocol_id, proto);
if (ret) {
dev_err(dev, "failed to add protocol: %s, ret: %d\n",
proto->name, ret);
break;
}
}
return ret;
}
UCLASS_DRIVER(scmi_agent) = {
.id = UCLASS_SCMI_AGENT,
.name = "scmi_agent",
.post_bind = scmi_bind_protocols,
.per_device_plat_auto = sizeof(struct scmi_agent_priv),
.per_child_auto = sizeof(struct scmi_agent_proto_priv),
};