mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-27 06:04:23 +08:00
Arm SCMI updates for v6.10
1. Basic support for SCMI v3.2 pincontrol protocol SCMI v3.2 introduces pincontrol protocol which is intended for controlling pins and their configuration. The pin control protocol provides commands to: - List the pins, groups of pins, available functions, and their association with each other. - Set the parameter configuration and multiplexing of the pins or groups of pins - Optionally request exclusive access to a pin or group of pins. - Optionally configure the permissions of an agent to access a pin or group of pins. These changes adds basic support for the same in the SCMI core layer and an implementation of the generic scmi-pinctrl driver with associated DT bindings. 2. Framework support for multiple vendors custom protocols With the fixed space for vendor protocols, the possibility of having multiple vendors implementing distinct SCMI vendor protocols with the same overlapping protocol number is very high and with the need to support them all in a single kernel image or a module is also high. In order to implement the same we assume: - vendor protocols has to be tagged at build time with a vendor_id - vendor protocols could also optionally be tagged at build time with sub_vendor_id and implementation version At the initialisation all the built vendor protocols are registered with the SCMI core using a key derived from the above tags 3. Logging and tracing improvements This includes using dev_err_probe() to bail out from probe, adding message dump traces for bad and unexpected replies and widening of the tag buffer in trace_scmi_dump_msg to allow diverse tag names 4. Miscellaneous updates or improvements This includes adding the accessor function get_max_msg_size() used in pinctl protocol, updation of dt-bindings examples for protocol@13 to promote new bindings and simplification of scmi_devm_notifier_unregister -----BEGIN PGP SIGNATURE----- iQIzBAABCAAdFiEEunHlEgbzHrJD3ZPhAEG6vDF+4pgFAmYrfzYACgkQAEG6vDF+ 4phXdw//ZmvCV76D3XGJsXnc2Ivv/uaOZKRhzI+IKn0U529hI2YscA9BfhiLZOOI 68KqWiScMTGH5Zn1oDC5wpIEj0i3S/2ZdXZXWSN35+ccsz6y2hbf2q2ciMl1Vrf0 co8kxiDABI+RBeGpiSVQ2Yp6TEnxkiNDNAfON+A+xxbHhhLxGLcCCO33xbJ9Ui7e QpW2xan7AiSANRTUdvM21h00Uy1O/kAkcZJC2oLtCjUr3lzczrZkBCIor0jBE4VB SmM9vvcjWdfLVdfgxeFEAFvxN0fI9RT8ALRyfWbnPcCb37GUUMwS+J/Dz+P3yUa9 6vGz4CCdXpC1lxcM2z5cZsAqwk3WS8LU840lJyb2+gGxLoSPwr1z0NWNCqZQppkp 5LDaFh2v5b34epMHRkNUHTytMBHy5p9c81TOSYH7S2bEsQTOi9qjviH0RqSjyR5A s9+dlxfUiZfoE71UYQLO2E0knu3AwIRQ6oEkhbmLFedOVegkoTeYkaAfdyEAO+SH vqLEX88wwMU6B+bjDnN7lYTuSXbZVKu66TZk6oqMVnv2BQ29pmOR3AFQdFNpLA8W lRygSMEbADzNuJla5Az+VH2tWf1hOmpSsMIOAJbZ6/drv0yFnbBwgtGkAmkqEr68 IQxwJTVQmPlgqpsCATzV29qgSEosHDJo9LZw9/DtgTx5qGL/to0= =WRU/ -----END PGP SIGNATURE----- gpgsig -----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEEiK/NIGsWEZVxh/FrYKtH/8kJUicFAmYwB/YACgkQYKtH/8kJ UicgvxAAh8OV+YcHzBTHdsHDc7iQkb9tmvZg6Cw4sYP8AJ3zZ2NjRYJXLYxxzE6T l+W8DIeV3ZQow+B/x6Xexdz9P/NKnxlTPO1/Z7hGpC2aDiBkseP3WliIJgBH0+iF m6p3VOSVKcfM8vds0d5l35ITHL0eVJzb7bN6PKUR75I2cDJ2UDbWV6OadNNyt4tX eEkCNnmuTx1bhpRXLpAXpEBb4FdbFNc5cSJHw9nbdCyQ8vFtHkaH82L9MCjy4kaW fSRwkNzqwWOA2tS3tkDvsczF4360RKoL95Y92lrQVTlnMjGLaUsCbs8FDKwHNZKb 6hBoiYQERmhGvb76+34kbMM50MyWohjzZcbCL6o3cU195KWJCN1a1cUbq8J+H7k1 MeK0XuwYUu6HdApwI6AfHl9vCvyJB9/XsvWz1eEocbmP8M2aPeXtl8y3vImZ6rkX aj/a9HuSKpAncqsdvpz4FoC0Q16gMZYtW6257kHcPX3QHvRlzJk7fHuqqIAQUsSf prOfYGBCvBuPT3S+bgG7L74Ddt4JLGHYjDxdNP4DR9xLR62ek2eQWc+cVqooatoA ZNckHV6Bibegtb+p/5RWgFem1XAOePM5gC1i5gvqy3oqsAQDrAd9Mx6s97AfrPzN dU9WTMohCjebdyqxYDGb31xzi5VY/L3/UJp3X9moJqbajXX+ty4= =Yce+ -----END PGP SIGNATURE----- Merge tag 'scmi-updates-6.10' of git://git.kernel.org/pub/scm/linux/kernel/git/sudeep.holla/linux into soc/drivers Arm SCMI updates for v6.10 1. Basic support for SCMI v3.2 pincontrol protocol SCMI v3.2 introduces pincontrol protocol which is intended for controlling pins and their configuration. The pin control protocol provides commands to: - List the pins, groups of pins, available functions, and their association with each other. - Set the parameter configuration and multiplexing of the pins or groups of pins - Optionally request exclusive access to a pin or group of pins. - Optionally configure the permissions of an agent to access a pin or group of pins. These changes adds basic support for the same in the SCMI core layer and an implementation of the generic scmi-pinctrl driver with associated DT bindings. 2. Framework support for multiple vendors custom protocols With the fixed space for vendor protocols, the possibility of having multiple vendors implementing distinct SCMI vendor protocols with the same overlapping protocol number is very high and with the need to support them all in a single kernel image or a module is also high. In order to implement the same we assume: - vendor protocols has to be tagged at build time with a vendor_id - vendor protocols could also optionally be tagged at build time with sub_vendor_id and implementation version At the initialisation all the built vendor protocols are registered with the SCMI core using a key derived from the above tags 3. Logging and tracing improvements This includes using dev_err_probe() to bail out from probe, adding message dump traces for bad and unexpected replies and widening of the tag buffer in trace_scmi_dump_msg to allow diverse tag names 4. Miscellaneous updates or improvements This includes adding the accessor function get_max_msg_size() used in pinctl protocol, updation of dt-bindings examples for protocol@13 to promote new bindings and simplification of scmi_devm_notifier_unregister * tag 'scmi-updates-6.10' of git://git.kernel.org/pub/scm/linux/kernel/git/sudeep.holla/linux: pinctrl: Implementation of the generic scmi-pinctrl driver firmware: arm_scmi: Add basic support for SCMI v3.2 pincontrol protocol dt-bindings: firmware: Support SCMI pinctrl protocol firmware: arm_scmi: Introduce get_max_msg_size() helper/accessor firmware: arm_scmi: Add support for multiple vendors custom protocols dt-bindings: firmware: arm,scmi: Update examples for protocol@13 firmware: arm_scmi: Avoid non-constant printk format strings firmware: arm_scmi: Use dev_err_probe to bail out firmware: arm_scmi: Simplify scmi_devm_notifier_unregister firmware: arm_scmi: Add message dump traces for bad and unexpected replies firmware: arm_scmi: Add helper to trace bad messages include: trace: Widen the tag buffer in trace_scmi_dump_msg firmware: arm_scmi: Log the perf domain names in the error paths Link: https://lore.kernel.org/r/20240426105031.1526987-1-sudeep.holla@arm.com Signed-off-by: Arnd Bergmann <arnd@arndb.de>
This commit is contained in:
commit
21cbc1058a
@ -247,6 +247,37 @@ properties:
|
||||
reg:
|
||||
const: 0x18
|
||||
|
||||
protocol@19:
|
||||
type: object
|
||||
allOf:
|
||||
- $ref: '#/$defs/protocol-node'
|
||||
- $ref: /schemas/pinctrl/pinctrl.yaml
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
properties:
|
||||
reg:
|
||||
const: 0x19
|
||||
|
||||
patternProperties:
|
||||
'-pins$':
|
||||
type: object
|
||||
allOf:
|
||||
- $ref: /schemas/pinctrl/pincfg-node.yaml#
|
||||
- $ref: /schemas/pinctrl/pinmux-node.yaml#
|
||||
unevaluatedProperties: false
|
||||
|
||||
description:
|
||||
A pin multiplexing sub-node describes how to configure a
|
||||
set of pins in some desired function.
|
||||
A single sub-node may define several pin configurations.
|
||||
This sub-node is using the default pinctrl bindings to configure
|
||||
pin multiplexing and using SCMI protocol to apply a specified
|
||||
configuration.
|
||||
|
||||
required:
|
||||
- reg
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
$defs:
|
||||
@ -355,7 +386,7 @@ examples:
|
||||
|
||||
scmi_dvfs: protocol@13 {
|
||||
reg = <0x13>;
|
||||
#clock-cells = <1>;
|
||||
#power-domain-cells = <1>;
|
||||
|
||||
mboxes = <&mhuB 1 0>,
|
||||
<&mhuB 1 1>;
|
||||
@ -401,6 +432,25 @@ examples:
|
||||
scmi_powercap: protocol@18 {
|
||||
reg = <0x18>;
|
||||
};
|
||||
|
||||
scmi_pinctrl: protocol@19 {
|
||||
reg = <0x19>;
|
||||
|
||||
i2c2-pins {
|
||||
groups = "g_i2c2_a", "g_i2c2_b";
|
||||
function = "f_i2c2";
|
||||
};
|
||||
|
||||
mdio-pins {
|
||||
groups = "g_avb_mdio";
|
||||
drive-strength = <24>;
|
||||
};
|
||||
|
||||
keys_pins: keys-pins {
|
||||
pins = "gpio_5_17", "gpio_5_20", "gpio_5_22", "gpio_2_1";
|
||||
bias-pull-up;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@ -468,7 +518,7 @@ examples:
|
||||
reg = <0x13>;
|
||||
linaro,optee-channel-id = <1>;
|
||||
shmem = <&cpu_optee_lpri0>;
|
||||
#clock-cells = <1>;
|
||||
#power-domain-cells = <1>;
|
||||
};
|
||||
|
||||
scmi_clk0: protocol@14 {
|
||||
|
@ -21522,6 +21522,7 @@ F: drivers/cpufreq/sc[mp]i-cpufreq.c
|
||||
F: drivers/firmware/arm_scmi/
|
||||
F: drivers/firmware/arm_scpi.c
|
||||
F: drivers/hwmon/scmi-hwmon.c
|
||||
F: drivers/pinctrl/pinctrl-scmi.c
|
||||
F: drivers/pmdomain/arm/
|
||||
F: drivers/powercap/arm_scmi_powercap.c
|
||||
F: drivers/regulator/scmi-regulator.c
|
||||
|
@ -10,7 +10,8 @@ scmi-transport-$(CONFIG_ARM_SCMI_TRANSPORT_SMC) += smc.o
|
||||
scmi-transport-$(CONFIG_ARM_SCMI_HAVE_MSG) += msg.o
|
||||
scmi-transport-$(CONFIG_ARM_SCMI_TRANSPORT_VIRTIO) += virtio.o
|
||||
scmi-transport-$(CONFIG_ARM_SCMI_TRANSPORT_OPTEE) += optee.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 := 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) += scmi-core.o
|
||||
|
@ -301,6 +301,17 @@ extern const struct scmi_desc scmi_optee_desc;
|
||||
|
||||
void scmi_rx_callback(struct scmi_chan_info *cinfo, u32 msg_hdr, void *priv);
|
||||
|
||||
enum scmi_bad_msg {
|
||||
MSG_UNEXPECTED = -1,
|
||||
MSG_INVALID = -2,
|
||||
MSG_UNKNOWN = -3,
|
||||
MSG_NOMEM = -4,
|
||||
MSG_MBOX_SPURIOUS = -5,
|
||||
};
|
||||
|
||||
void scmi_bad_message_trace(struct scmi_chan_info *cinfo, u32 msg_hdr,
|
||||
enum scmi_bad_msg err);
|
||||
|
||||
/* shmem related declarations */
|
||||
struct scmi_shared_mem;
|
||||
|
||||
|
@ -33,6 +33,7 @@
|
||||
#include <linux/processor.h>
|
||||
#include <linux/refcount.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/xarray.h>
|
||||
|
||||
#include "common.h"
|
||||
#include "notify.h"
|
||||
@ -44,8 +45,7 @@
|
||||
|
||||
static DEFINE_IDA(scmi_id);
|
||||
|
||||
static DEFINE_IDR(scmi_protocols);
|
||||
static DEFINE_SPINLOCK(protocol_lock);
|
||||
static DEFINE_XARRAY(scmi_protocols);
|
||||
|
||||
/* List of all SCMI devices active in system */
|
||||
static LIST_HEAD(scmi_list);
|
||||
@ -194,11 +194,94 @@ struct scmi_info {
|
||||
#define bus_nb_to_scmi_info(nb) container_of(nb, struct scmi_info, bus_nb)
|
||||
#define req_nb_to_scmi_info(nb) container_of(nb, struct scmi_info, dev_req_nb)
|
||||
|
||||
static const struct scmi_protocol *scmi_protocol_get(int protocol_id)
|
||||
static unsigned long
|
||||
scmi_vendor_protocol_signature(unsigned int protocol_id, char *vendor_id,
|
||||
char *sub_vendor_id, u32 impl_ver)
|
||||
{
|
||||
const struct scmi_protocol *proto;
|
||||
char *signature, *p;
|
||||
unsigned long hash = 0;
|
||||
|
||||
proto = idr_find(&scmi_protocols, protocol_id);
|
||||
/* vendor_id/sub_vendor_id guaranteed <= SCMI_SHORT_NAME_MAX_SIZE */
|
||||
signature = kasprintf(GFP_KERNEL, "%02X|%s|%s|0x%08X", protocol_id,
|
||||
vendor_id ?: "", sub_vendor_id ?: "", impl_ver);
|
||||
if (!signature)
|
||||
return 0;
|
||||
|
||||
p = signature;
|
||||
while (*p)
|
||||
hash = partial_name_hash(tolower(*p++), hash);
|
||||
hash = end_name_hash(hash);
|
||||
|
||||
kfree(signature);
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
static unsigned long
|
||||
scmi_protocol_key_calculate(int protocol_id, char *vendor_id,
|
||||
char *sub_vendor_id, u32 impl_ver)
|
||||
{
|
||||
if (protocol_id < SCMI_PROTOCOL_VENDOR_BASE)
|
||||
return protocol_id;
|
||||
else
|
||||
return scmi_vendor_protocol_signature(protocol_id, vendor_id,
|
||||
sub_vendor_id, impl_ver);
|
||||
}
|
||||
|
||||
static const struct scmi_protocol *
|
||||
__scmi_vendor_protocol_lookup(int protocol_id, char *vendor_id,
|
||||
char *sub_vendor_id, u32 impl_ver)
|
||||
{
|
||||
unsigned long key;
|
||||
struct scmi_protocol *proto = NULL;
|
||||
|
||||
key = scmi_protocol_key_calculate(protocol_id, vendor_id,
|
||||
sub_vendor_id, impl_ver);
|
||||
if (key)
|
||||
proto = xa_load(&scmi_protocols, key);
|
||||
|
||||
return proto;
|
||||
}
|
||||
|
||||
static const struct scmi_protocol *
|
||||
scmi_vendor_protocol_lookup(int protocol_id, char *vendor_id,
|
||||
char *sub_vendor_id, u32 impl_ver)
|
||||
{
|
||||
const struct scmi_protocol *proto = NULL;
|
||||
|
||||
/* Searching for closest match ...*/
|
||||
proto = __scmi_vendor_protocol_lookup(protocol_id, vendor_id,
|
||||
sub_vendor_id, impl_ver);
|
||||
if (proto)
|
||||
return proto;
|
||||
|
||||
/* Any match just on vendor/sub_vendor ? */
|
||||
if (impl_ver) {
|
||||
proto = __scmi_vendor_protocol_lookup(protocol_id, vendor_id,
|
||||
sub_vendor_id, 0);
|
||||
if (proto)
|
||||
return proto;
|
||||
}
|
||||
|
||||
/* Any match just on the vendor ? */
|
||||
if (sub_vendor_id)
|
||||
proto = __scmi_vendor_protocol_lookup(protocol_id, vendor_id,
|
||||
NULL, 0);
|
||||
return proto;
|
||||
}
|
||||
|
||||
static const struct scmi_protocol *
|
||||
scmi_protocol_get(int protocol_id, struct scmi_revision_info *version)
|
||||
{
|
||||
const struct scmi_protocol *proto = NULL;
|
||||
|
||||
if (protocol_id < SCMI_PROTOCOL_VENDOR_BASE)
|
||||
proto = xa_load(&scmi_protocols, protocol_id);
|
||||
else
|
||||
proto = scmi_vendor_protocol_lookup(protocol_id,
|
||||
version->vendor_id,
|
||||
version->sub_vendor_id,
|
||||
version->impl_ver);
|
||||
if (!proto || !try_module_get(proto->owner)) {
|
||||
pr_warn("SCMI Protocol 0x%x not found!\n", protocol_id);
|
||||
return NULL;
|
||||
@ -206,21 +289,46 @@ static const struct scmi_protocol *scmi_protocol_get(int protocol_id)
|
||||
|
||||
pr_debug("Found SCMI Protocol 0x%x\n", protocol_id);
|
||||
|
||||
if (protocol_id >= SCMI_PROTOCOL_VENDOR_BASE)
|
||||
pr_info("Loaded SCMI Vendor Protocol 0x%x - %s %s %X\n",
|
||||
protocol_id, proto->vendor_id ?: "",
|
||||
proto->sub_vendor_id ?: "", proto->impl_ver);
|
||||
|
||||
return proto;
|
||||
}
|
||||
|
||||
static void scmi_protocol_put(int protocol_id)
|
||||
static void scmi_protocol_put(const struct scmi_protocol *proto)
|
||||
{
|
||||
const struct scmi_protocol *proto;
|
||||
|
||||
proto = idr_find(&scmi_protocols, protocol_id);
|
||||
if (proto)
|
||||
module_put(proto->owner);
|
||||
}
|
||||
|
||||
static int scmi_vendor_protocol_check(const struct scmi_protocol *proto)
|
||||
{
|
||||
if (!proto->vendor_id) {
|
||||
pr_err("missing vendor_id for protocol 0x%x\n", proto->id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (strlen(proto->vendor_id) >= SCMI_SHORT_NAME_MAX_SIZE) {
|
||||
pr_err("malformed vendor_id for protocol 0x%x\n", proto->id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (proto->sub_vendor_id &&
|
||||
strlen(proto->sub_vendor_id) >= SCMI_SHORT_NAME_MAX_SIZE) {
|
||||
pr_err("malformed sub_vendor_id for protocol 0x%x\n",
|
||||
proto->id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int scmi_protocol_register(const struct scmi_protocol *proto)
|
||||
{
|
||||
int ret;
|
||||
unsigned long key;
|
||||
|
||||
if (!proto) {
|
||||
pr_err("invalid protocol\n");
|
||||
@ -232,12 +340,23 @@ int scmi_protocol_register(const struct scmi_protocol *proto)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
spin_lock(&protocol_lock);
|
||||
ret = idr_alloc(&scmi_protocols, (void *)proto,
|
||||
proto->id, proto->id + 1, GFP_ATOMIC);
|
||||
spin_unlock(&protocol_lock);
|
||||
if (ret != proto->id) {
|
||||
pr_err("unable to allocate SCMI idr slot for 0x%x - err %d\n",
|
||||
if (proto->id >= SCMI_PROTOCOL_VENDOR_BASE &&
|
||||
scmi_vendor_protocol_check(proto))
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* Calculate a protocol key to register this protocol with the core;
|
||||
* key value 0 is considered invalid.
|
||||
*/
|
||||
key = scmi_protocol_key_calculate(proto->id, proto->vendor_id,
|
||||
proto->sub_vendor_id,
|
||||
proto->impl_ver);
|
||||
if (!key)
|
||||
return -EINVAL;
|
||||
|
||||
ret = xa_insert(&scmi_protocols, key, (void *)proto, GFP_KERNEL);
|
||||
if (ret) {
|
||||
pr_err("unable to allocate SCMI protocol slot for 0x%x - err %d\n",
|
||||
proto->id, ret);
|
||||
return ret;
|
||||
}
|
||||
@ -250,9 +369,15 @@ EXPORT_SYMBOL_GPL(scmi_protocol_register);
|
||||
|
||||
void scmi_protocol_unregister(const struct scmi_protocol *proto)
|
||||
{
|
||||
spin_lock(&protocol_lock);
|
||||
idr_remove(&scmi_protocols, proto->id);
|
||||
spin_unlock(&protocol_lock);
|
||||
unsigned long key;
|
||||
|
||||
key = scmi_protocol_key_calculate(proto->id, proto->vendor_id,
|
||||
proto->sub_vendor_id,
|
||||
proto->impl_ver);
|
||||
if (!key)
|
||||
return;
|
||||
|
||||
xa_erase(&scmi_protocols, key);
|
||||
|
||||
pr_debug("Unregistered SCMI Protocol 0x%x\n", proto->id);
|
||||
}
|
||||
@ -696,6 +821,45 @@ scmi_xfer_lookup_unlocked(struct scmi_xfers_info *minfo, u16 xfer_id)
|
||||
return xfer ?: ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* scmi_bad_message_trace - A helper to trace weird messages
|
||||
*
|
||||
* @cinfo: A reference to the channel descriptor on which the message was
|
||||
* received
|
||||
* @msg_hdr: Message header to track
|
||||
* @err: A specific error code used as a status value in traces.
|
||||
*
|
||||
* This helper can be used to trace any kind of weird, incomplete, unexpected,
|
||||
* timed-out message that arrives and as such, can be traced only referring to
|
||||
* the header content, since the payload is missing/unreliable.
|
||||
*/
|
||||
void scmi_bad_message_trace(struct scmi_chan_info *cinfo, u32 msg_hdr,
|
||||
enum scmi_bad_msg err)
|
||||
{
|
||||
char *tag;
|
||||
struct scmi_info *info = handle_to_scmi_info(cinfo->handle);
|
||||
|
||||
switch (MSG_XTRACT_TYPE(msg_hdr)) {
|
||||
case MSG_TYPE_COMMAND:
|
||||
tag = "!RESP";
|
||||
break;
|
||||
case MSG_TYPE_DELAYED_RESP:
|
||||
tag = "!DLYD";
|
||||
break;
|
||||
case MSG_TYPE_NOTIFICATION:
|
||||
tag = "!NOTI";
|
||||
break;
|
||||
default:
|
||||
tag = "!UNKN";
|
||||
break;
|
||||
}
|
||||
|
||||
trace_scmi_msg_dump(info->id, cinfo->id,
|
||||
MSG_XTRACT_PROT_ID(msg_hdr),
|
||||
MSG_XTRACT_ID(msg_hdr), tag,
|
||||
MSG_XTRACT_TOKEN(msg_hdr), err, NULL, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* scmi_msg_response_validate - Validate message type against state of related
|
||||
* xfer
|
||||
@ -822,6 +986,9 @@ scmi_xfer_command_acquire(struct scmi_chan_info *cinfo, u32 msg_hdr)
|
||||
"Message for %d type %d is not expected!\n",
|
||||
xfer_id, msg_type);
|
||||
spin_unlock_irqrestore(&minfo->xfer_lock, flags);
|
||||
|
||||
scmi_bad_message_trace(cinfo, msg_hdr, MSG_UNEXPECTED);
|
||||
|
||||
return xfer;
|
||||
}
|
||||
refcount_inc(&xfer->users);
|
||||
@ -846,6 +1013,9 @@ scmi_xfer_command_acquire(struct scmi_chan_info *cinfo, u32 msg_hdr)
|
||||
dev_err(cinfo->dev,
|
||||
"Invalid message type:%d for %d - HDR:0x%X state:%d\n",
|
||||
msg_type, xfer_id, msg_hdr, xfer->state);
|
||||
|
||||
scmi_bad_message_trace(cinfo, msg_hdr, MSG_INVALID);
|
||||
|
||||
/* On error the refcount incremented above has to be dropped */
|
||||
__scmi_xfer_put(minfo, xfer);
|
||||
xfer = ERR_PTR(-EINVAL);
|
||||
@ -882,6 +1052,9 @@ static void scmi_handle_notification(struct scmi_chan_info *cinfo,
|
||||
if (IS_ERR(xfer)) {
|
||||
dev_err(dev, "failed to get free message slot (%ld)\n",
|
||||
PTR_ERR(xfer));
|
||||
|
||||
scmi_bad_message_trace(cinfo, msg_hdr, MSG_NOMEM);
|
||||
|
||||
scmi_clear_channel(info, cinfo);
|
||||
return;
|
||||
}
|
||||
@ -1001,6 +1174,7 @@ void scmi_rx_callback(struct scmi_chan_info *cinfo, u32 msg_hdr, void *priv)
|
||||
break;
|
||||
default:
|
||||
WARN_ONCE(1, "received unknown msg_type:%d\n", msg_type);
|
||||
scmi_bad_message_trace(cinfo, msg_hdr, MSG_UNKNOWN);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -1488,6 +1662,20 @@ out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* scmi_common_get_max_msg_size - Get maximum message size
|
||||
* @ph: A protocol handle reference.
|
||||
*
|
||||
* Return: Maximum message size for the current protocol.
|
||||
*/
|
||||
static int scmi_common_get_max_msg_size(const struct scmi_protocol_handle *ph)
|
||||
{
|
||||
const struct scmi_protocol_instance *pi = ph_to_pi(ph);
|
||||
struct scmi_info *info = handle_to_scmi_info(pi->handle);
|
||||
|
||||
return info->desc->max_msg_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* struct scmi_iterator - Iterator descriptor
|
||||
* @msg: A reference to the message TX buffer; filled by @prepare_message with
|
||||
@ -1799,6 +1987,7 @@ static int scmi_protocol_msg_check(const struct scmi_protocol_handle *ph,
|
||||
|
||||
static const struct scmi_proto_helpers_ops helpers_ops = {
|
||||
.extended_name_get = scmi_common_extended_name_get,
|
||||
.get_max_msg_size = scmi_common_get_max_msg_size,
|
||||
.iter_response_init = scmi_iterator_init,
|
||||
.iter_response_run = scmi_iterator_run,
|
||||
.protocol_msg_check = scmi_protocol_msg_check,
|
||||
@ -1891,7 +2080,7 @@ scmi_alloc_init_protocol_instance(struct scmi_info *info,
|
||||
/* Protocol specific devres group */
|
||||
gid = devres_open_group(handle->dev, NULL, GFP_KERNEL);
|
||||
if (!gid) {
|
||||
scmi_protocol_put(proto->id);
|
||||
scmi_protocol_put(proto);
|
||||
goto out;
|
||||
}
|
||||
|
||||
@ -1955,7 +2144,7 @@ scmi_alloc_init_protocol_instance(struct scmi_info *info,
|
||||
|
||||
clean:
|
||||
/* Take care to put the protocol module's owner before releasing all */
|
||||
scmi_protocol_put(proto->id);
|
||||
scmi_protocol_put(proto);
|
||||
devres_release_group(handle->dev, gid);
|
||||
out:
|
||||
return ERR_PTR(ret);
|
||||
@ -1989,7 +2178,7 @@ scmi_get_protocol_instance(const struct scmi_handle *handle, u8 protocol_id)
|
||||
const struct scmi_protocol *proto;
|
||||
|
||||
/* Fails if protocol not registered on bus */
|
||||
proto = scmi_protocol_get(protocol_id);
|
||||
proto = scmi_protocol_get(protocol_id, &info->version);
|
||||
if (proto)
|
||||
pi = scmi_alloc_init_protocol_instance(info, proto);
|
||||
else
|
||||
@ -2044,7 +2233,7 @@ void scmi_protocol_release(const struct scmi_handle *handle, u8 protocol_id)
|
||||
|
||||
idr_remove(&info->protocols, protocol_id);
|
||||
|
||||
scmi_protocol_put(protocol_id);
|
||||
scmi_protocol_put(pi->proto);
|
||||
|
||||
devres_release_group(handle->dev, gid);
|
||||
dev_dbg(handle->dev, "De-Initialized protocol: 0x%X\n",
|
||||
@ -2491,6 +2680,10 @@ scmi_txrx_setup(struct scmi_info *info, struct device_node *of_node,
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
if (ret)
|
||||
dev_err(info->dev,
|
||||
"failed to setup channel for protocol:0x%X\n", prot_id);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -2760,6 +2953,7 @@ static int scmi_debugfs_raw_mode_setup(struct scmi_info *info)
|
||||
static int scmi_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
char *err_str = "probe failure\n";
|
||||
struct scmi_handle *handle;
|
||||
const struct scmi_desc *desc;
|
||||
struct scmi_info *info;
|
||||
@ -2810,27 +3004,37 @@ static int scmi_probe(struct platform_device *pdev)
|
||||
|
||||
if (desc->ops->link_supplier) {
|
||||
ret = desc->ops->link_supplier(dev);
|
||||
if (ret)
|
||||
if (ret) {
|
||||
err_str = "transport not ready\n";
|
||||
goto clear_ida;
|
||||
}
|
||||
}
|
||||
|
||||
/* Setup all channels described in the DT at first */
|
||||
ret = scmi_channels_setup(info);
|
||||
if (ret)
|
||||
if (ret) {
|
||||
err_str = "failed to setup channels\n";
|
||||
goto clear_ida;
|
||||
}
|
||||
|
||||
ret = bus_register_notifier(&scmi_bus_type, &info->bus_nb);
|
||||
if (ret)
|
||||
if (ret) {
|
||||
err_str = "failed to register bus notifier\n";
|
||||
goto clear_txrx_setup;
|
||||
}
|
||||
|
||||
ret = blocking_notifier_chain_register(&scmi_requested_devices_nh,
|
||||
&info->dev_req_nb);
|
||||
if (ret)
|
||||
if (ret) {
|
||||
err_str = "failed to register device notifier\n";
|
||||
goto clear_bus_notifier;
|
||||
}
|
||||
|
||||
ret = scmi_xfer_info_init(info);
|
||||
if (ret)
|
||||
if (ret) {
|
||||
err_str = "failed to init xfers pool\n";
|
||||
goto clear_dev_req_notifier;
|
||||
}
|
||||
|
||||
if (scmi_top_dentry) {
|
||||
info->dbg = scmi_debugfs_common_setup(info);
|
||||
@ -2867,9 +3071,11 @@ static int scmi_probe(struct platform_device *pdev)
|
||||
*/
|
||||
ret = scmi_protocol_acquire(handle, SCMI_PROTOCOL_BASE);
|
||||
if (ret) {
|
||||
dev_err(dev, "unable to communicate with SCMI\n");
|
||||
if (coex)
|
||||
err_str = "unable to communicate with SCMI\n";
|
||||
if (coex) {
|
||||
dev_err(dev, "%s", err_str);
|
||||
return 0;
|
||||
}
|
||||
goto notification_exit;
|
||||
}
|
||||
|
||||
@ -2923,7 +3129,8 @@ clear_txrx_setup:
|
||||
scmi_cleanup_txrx_channels(info);
|
||||
clear_ida:
|
||||
ida_free(&scmi_id, info->id);
|
||||
return ret;
|
||||
|
||||
return dev_err_probe(dev, ret, "%s", err_str);
|
||||
}
|
||||
|
||||
static void scmi_remove(struct platform_device *pdev)
|
||||
@ -3127,6 +3334,7 @@ static int __init scmi_driver_init(void)
|
||||
scmi_voltage_register();
|
||||
scmi_system_register();
|
||||
scmi_powercap_register();
|
||||
scmi_pinctrl_register();
|
||||
|
||||
return platform_driver_register(&scmi_driver);
|
||||
}
|
||||
@ -3144,6 +3352,7 @@ static void __exit scmi_driver_exit(void)
|
||||
scmi_voltage_unregister();
|
||||
scmi_system_unregister();
|
||||
scmi_powercap_unregister();
|
||||
scmi_pinctrl_unregister();
|
||||
|
||||
scmi_transports_exit();
|
||||
|
||||
|
@ -56,6 +56,9 @@ static void rx_callback(struct mbox_client *cl, void *m)
|
||||
*/
|
||||
if (cl->knows_txdone && !shmem_channel_free(smbox->shmem)) {
|
||||
dev_warn(smbox->cinfo->dev, "Ignoring spurious A2P IRQ !\n");
|
||||
scmi_bad_message_trace(smbox->cinfo,
|
||||
shmem_read_header(smbox->shmem),
|
||||
MSG_MBOX_SPURIOUS);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1513,17 +1513,12 @@ static int scmi_devm_notifier_register(struct scmi_device *sdev,
|
||||
static int scmi_devm_notifier_match(struct device *dev, void *res, void *data)
|
||||
{
|
||||
struct scmi_notifier_devres *dres = res;
|
||||
struct scmi_notifier_devres *xres = data;
|
||||
struct notifier_block *nb = data;
|
||||
|
||||
if (WARN_ON(!dres || !xres))
|
||||
if (WARN_ON(!dres || !nb))
|
||||
return 0;
|
||||
|
||||
return dres->proto_id == xres->proto_id &&
|
||||
dres->evt_id == xres->evt_id &&
|
||||
dres->nb == xres->nb &&
|
||||
((!dres->src_id && !xres->src_id) ||
|
||||
(dres->src_id && xres->src_id &&
|
||||
dres->__src_id == xres->__src_id));
|
||||
return dres->nb == nb;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1531,10 +1526,6 @@ static int scmi_devm_notifier_match(struct device *dev, void *res, void *data)
|
||||
* notifier_block for an event
|
||||
* @sdev: A reference to an scmi_device whose embedded struct device is to
|
||||
* be used for devres accounting.
|
||||
* @proto_id: Protocol ID
|
||||
* @evt_id: Event ID
|
||||
* @src_id: Source ID, when NULL register for events coming form ALL possible
|
||||
* sources
|
||||
* @nb: A standard notifier block to register for the specified event
|
||||
*
|
||||
* Generic devres managed helper to explicitly un-register a notifier_block
|
||||
@ -1544,25 +1535,12 @@ static int scmi_devm_notifier_match(struct device *dev, void *res, void *data)
|
||||
* Return: 0 on Success
|
||||
*/
|
||||
static int scmi_devm_notifier_unregister(struct scmi_device *sdev,
|
||||
u8 proto_id, u8 evt_id,
|
||||
const u32 *src_id,
|
||||
struct notifier_block *nb)
|
||||
{
|
||||
int ret;
|
||||
struct scmi_notifier_devres dres;
|
||||
|
||||
dres.handle = sdev->handle;
|
||||
dres.proto_id = proto_id;
|
||||
dres.evt_id = evt_id;
|
||||
if (src_id) {
|
||||
dres.__src_id = *src_id;
|
||||
dres.src_id = &dres.__src_id;
|
||||
} else {
|
||||
dres.src_id = NULL;
|
||||
}
|
||||
|
||||
ret = devres_release(&sdev->dev, scmi_devm_release_notifier,
|
||||
scmi_devm_notifier_match, &dres);
|
||||
scmi_devm_notifier_match, nb);
|
||||
|
||||
WARN_ON(ret);
|
||||
|
||||
|
@ -387,8 +387,8 @@ process_response_opp(struct device *dev, struct perf_dom_info *dom,
|
||||
|
||||
ret = xa_insert(&dom->opps_by_lvl, opp->perf, opp, GFP_KERNEL);
|
||||
if (ret)
|
||||
dev_warn(dev, "Failed to add opps_by_lvl at %d - ret:%d\n",
|
||||
opp->perf, ret);
|
||||
dev_warn(dev, "Failed to add opps_by_lvl at %d for %s - ret:%d\n",
|
||||
opp->perf, dom->info.name, ret);
|
||||
}
|
||||
|
||||
static inline void
|
||||
@ -405,8 +405,8 @@ process_response_opp_v4(struct device *dev, struct perf_dom_info *dom,
|
||||
|
||||
ret = xa_insert(&dom->opps_by_lvl, opp->perf, opp, GFP_KERNEL);
|
||||
if (ret)
|
||||
dev_warn(dev, "Failed to add opps_by_lvl at %d - ret:%d\n",
|
||||
opp->perf, ret);
|
||||
dev_warn(dev, "Failed to add opps_by_lvl at %d for %s - ret:%d\n",
|
||||
opp->perf, dom->info.name, ret);
|
||||
|
||||
/* Note that PERF v4 reports always five 32-bit words */
|
||||
opp->indicative_freq = le32_to_cpu(r->opp[loop_idx].indicative_freq);
|
||||
@ -417,8 +417,8 @@ process_response_opp_v4(struct device *dev, struct perf_dom_info *dom,
|
||||
GFP_KERNEL);
|
||||
if (ret)
|
||||
dev_warn(dev,
|
||||
"Failed to add opps_by_idx at %d - ret:%d\n",
|
||||
opp->level_index, ret);
|
||||
"Failed to add opps_by_idx at %d for %s - ret:%d\n",
|
||||
opp->level_index, dom->info.name, ret);
|
||||
|
||||
hash_add(dom->opps_by_freq, &opp->hash, opp->indicative_freq);
|
||||
}
|
||||
@ -879,7 +879,8 @@ static int scmi_dvfs_device_opps_add(const struct scmi_protocol_handle *ph,
|
||||
|
||||
ret = dev_pm_opp_add_dynamic(dev, &data);
|
||||
if (ret) {
|
||||
dev_warn(dev, "failed to add opp %luHz\n", freq);
|
||||
dev_warn(dev, "[%d][%s]: Failed to add OPP[%d] %lu\n",
|
||||
domain, dom->info.name, idx, freq);
|
||||
dev_pm_opp_remove_all_dynamic(dev);
|
||||
return ret;
|
||||
}
|
||||
|
916
drivers/firmware/arm_scmi/pinctrl.c
Normal file
916
drivers/firmware/arm_scmi/pinctrl.c
Normal file
@ -0,0 +1,916 @@
|
||||
// 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 {
|
||||
u32 version;
|
||||
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);
|
||||
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_group_info *group)
|
||||
{
|
||||
int ret;
|
||||
|
||||
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);
|
||||
|
||||
if (!name)
|
||||
return -EINVAL;
|
||||
|
||||
if (selector >= pi->nr_groups || pi->nr_groups == 0)
|
||||
return -EINVAL;
|
||||
|
||||
if (!pi->groups[selector].present) {
|
||||
int ret;
|
||||
|
||||
ret = scmi_pinctrl_get_group_info(ph, selector,
|
||||
&pi->groups[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);
|
||||
|
||||
if (!pins || !nr_pins)
|
||||
return -EINVAL;
|
||||
|
||||
if (selector >= pi->nr_groups || pi->nr_groups == 0)
|
||||
return -EINVAL;
|
||||
|
||||
if (!pi->groups[selector].present) {
|
||||
int ret;
|
||||
|
||||
ret = scmi_pinctrl_get_group_info(ph, selector,
|
||||
&pi->groups[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_function_info *func)
|
||||
{
|
||||
int ret;
|
||||
|
||||
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);
|
||||
|
||||
if (!name)
|
||||
return -EINVAL;
|
||||
|
||||
if (selector >= pi->nr_functions || pi->nr_functions == 0)
|
||||
return -EINVAL;
|
||||
|
||||
if (!pi->functions[selector].present) {
|
||||
int ret;
|
||||
|
||||
ret = scmi_pinctrl_get_function_info(ph, selector,
|
||||
&pi->functions[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);
|
||||
|
||||
if (!groups || !nr_groups)
|
||||
return -EINVAL;
|
||||
|
||||
if (selector >= pi->nr_functions || pi->nr_functions == 0)
|
||||
return -EINVAL;
|
||||
|
||||
if (!pi->functions[selector].present) {
|
||||
int ret;
|
||||
|
||||
ret = scmi_pinctrl_get_function_info(ph, selector,
|
||||
&pi->functions[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_pin_info *pin)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!pin)
|
||||
return -EINVAL;
|
||||
|
||||
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);
|
||||
|
||||
if (!name)
|
||||
return -EINVAL;
|
||||
|
||||
if (selector >= pi->nr_pins)
|
||||
return -EINVAL;
|
||||
|
||||
if (!pi->pins[selector].present) {
|
||||
int ret;
|
||||
|
||||
ret = scmi_pinctrl_get_pin_info(ph, selector, &pi->pins[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;
|
||||
u32 version;
|
||||
struct scmi_pinctrl_info *pinfo;
|
||||
|
||||
ret = ph->xops->version_get(ph, &version);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
dev_dbg(ph->dev, "Pinctrl Version %d.%d\n",
|
||||
PROTOCOL_REV_MAJOR(version), PROTOCOL_REV_MINOR(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;
|
||||
|
||||
pinfo->version = version;
|
||||
|
||||
return ph->set_priv(ph, pinfo, version);
|
||||
}
|
||||
|
||||
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)
|
@ -29,6 +29,8 @@
|
||||
#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
|
||||
|
||||
enum scmi_common_cmd {
|
||||
PROTOCOL_VERSION = 0x0,
|
||||
PROTOCOL_ATTRIBUTES = 0x1,
|
||||
@ -258,6 +260,7 @@ struct scmi_fc_info {
|
||||
* @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,
|
||||
@ -277,6 +280,7 @@ struct scmi_proto_helpers_ops {
|
||||
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);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -323,6 +327,16 @@ typedef int (*scmi_prot_init_ph_fn_t)(const struct scmi_protocol_handle *);
|
||||
* 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;
|
||||
@ -332,6 +346,9 @@ struct scmi_protocol {
|
||||
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) \
|
||||
@ -353,6 +370,7 @@ void __exit scmi_##name##_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);
|
||||
|
@ -450,6 +450,17 @@ config PINCTRL_ROCKCHIP
|
||||
help
|
||||
This support pinctrl and GPIO driver for Rockchip SoCs.
|
||||
|
||||
config PINCTRL_SCMI
|
||||
tristate "Pinctrl driver using SCMI protocol interface"
|
||||
depends on ARM_SCMI_PROTOCOL || COMPILE_TEST
|
||||
select PINMUX
|
||||
select GENERIC_PINCONF
|
||||
help
|
||||
This driver provides support for pinctrl which is controlled
|
||||
by firmware that implements the SCMI interface.
|
||||
It uses SCMI Message Protocol to interact with the
|
||||
firmware providing all the pinctrl controls.
|
||||
|
||||
config PINCTRL_SINGLE
|
||||
tristate "One-register-per-pin type device tree based pinctrl driver"
|
||||
depends on OF
|
||||
|
@ -45,6 +45,7 @@ obj-$(CONFIG_PINCTRL_PIC32) += pinctrl-pic32.o
|
||||
obj-$(CONFIG_PINCTRL_PISTACHIO) += pinctrl-pistachio.o
|
||||
obj-$(CONFIG_PINCTRL_RK805) += pinctrl-rk805.o
|
||||
obj-$(CONFIG_PINCTRL_ROCKCHIP) += pinctrl-rockchip.o
|
||||
obj-$(CONFIG_PINCTRL_SCMI) += pinctrl-scmi.o
|
||||
obj-$(CONFIG_PINCTRL_SINGLE) += pinctrl-single.o
|
||||
obj-$(CONFIG_PINCTRL_ST) += pinctrl-st.o
|
||||
obj-$(CONFIG_PINCTRL_STMFX) += pinctrl-stmfx.o
|
||||
|
571
drivers/pinctrl/pinctrl-scmi.c
Normal file
571
drivers/pinctrl/pinctrl-scmi.c
Normal file
@ -0,0 +1,571 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* System Control and Power Interface (SCMI) Protocol based pinctrl driver
|
||||
*
|
||||
* Copyright (C) 2024 EPAM
|
||||
* Copyright 2024 NXP
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/scmi_protocol.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include <linux/pinctrl/machine.h>
|
||||
#include <linux/pinctrl/pinconf.h>
|
||||
#include <linux/pinctrl/pinconf-generic.h>
|
||||
#include <linux/pinctrl/pinctrl.h>
|
||||
#include <linux/pinctrl/pinmux.h>
|
||||
|
||||
#include "pinctrl-utils.h"
|
||||
#include "core.h"
|
||||
#include "pinconf.h"
|
||||
|
||||
#define DRV_NAME "scmi-pinctrl"
|
||||
|
||||
/* Define num configs, if not large than 4 use stack, else use kcalloc() */
|
||||
#define SCMI_NUM_CONFIGS 4
|
||||
|
||||
static const struct scmi_pinctrl_proto_ops *pinctrl_ops;
|
||||
|
||||
struct scmi_pinctrl {
|
||||
struct device *dev;
|
||||
struct scmi_protocol_handle *ph;
|
||||
struct pinctrl_dev *pctldev;
|
||||
struct pinctrl_desc pctl_desc;
|
||||
struct pinfunction *functions;
|
||||
unsigned int nr_functions;
|
||||
struct pinctrl_pin_desc *pins;
|
||||
unsigned int nr_pins;
|
||||
};
|
||||
|
||||
static int pinctrl_scmi_get_groups_count(struct pinctrl_dev *pctldev)
|
||||
{
|
||||
struct scmi_pinctrl *pmx = pinctrl_dev_get_drvdata(pctldev);
|
||||
|
||||
return pinctrl_ops->count_get(pmx->ph, GROUP_TYPE);
|
||||
}
|
||||
|
||||
static const char *pinctrl_scmi_get_group_name(struct pinctrl_dev *pctldev,
|
||||
unsigned int selector)
|
||||
{
|
||||
int ret;
|
||||
const char *name;
|
||||
struct scmi_pinctrl *pmx = pinctrl_dev_get_drvdata(pctldev);
|
||||
|
||||
ret = pinctrl_ops->name_get(pmx->ph, selector, GROUP_TYPE, &name);
|
||||
if (ret) {
|
||||
dev_err(pmx->dev, "get name failed with err %d", ret);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
static int pinctrl_scmi_get_group_pins(struct pinctrl_dev *pctldev,
|
||||
unsigned int selector,
|
||||
const unsigned int **pins,
|
||||
unsigned int *num_pins)
|
||||
{
|
||||
struct scmi_pinctrl *pmx = pinctrl_dev_get_drvdata(pctldev);
|
||||
|
||||
return pinctrl_ops->group_pins_get(pmx->ph, selector, pins, num_pins);
|
||||
}
|
||||
|
||||
static const struct pinctrl_ops pinctrl_scmi_pinctrl_ops = {
|
||||
.get_groups_count = pinctrl_scmi_get_groups_count,
|
||||
.get_group_name = pinctrl_scmi_get_group_name,
|
||||
.get_group_pins = pinctrl_scmi_get_group_pins,
|
||||
#ifdef CONFIG_OF
|
||||
.dt_node_to_map = pinconf_generic_dt_node_to_map_all,
|
||||
.dt_free_map = pinconf_generic_dt_free_map,
|
||||
#endif
|
||||
};
|
||||
|
||||
static int pinctrl_scmi_get_functions_count(struct pinctrl_dev *pctldev)
|
||||
{
|
||||
struct scmi_pinctrl *pmx = pinctrl_dev_get_drvdata(pctldev);
|
||||
|
||||
return pinctrl_ops->count_get(pmx->ph, FUNCTION_TYPE);
|
||||
}
|
||||
|
||||
static const char *pinctrl_scmi_get_function_name(struct pinctrl_dev *pctldev,
|
||||
unsigned int selector)
|
||||
{
|
||||
int ret;
|
||||
const char *name;
|
||||
struct scmi_pinctrl *pmx = pinctrl_dev_get_drvdata(pctldev);
|
||||
|
||||
ret = pinctrl_ops->name_get(pmx->ph, selector, FUNCTION_TYPE, &name);
|
||||
if (ret) {
|
||||
dev_err(pmx->dev, "get name failed with err %d", ret);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
static int pinctrl_scmi_get_function_groups(struct pinctrl_dev *pctldev,
|
||||
unsigned int selector,
|
||||
const char * const **p_groups,
|
||||
unsigned int * const p_num_groups)
|
||||
{
|
||||
struct pinfunction *func;
|
||||
const unsigned int *group_ids;
|
||||
unsigned int num_groups;
|
||||
const char **groups;
|
||||
int ret, i;
|
||||
struct scmi_pinctrl *pmx = pinctrl_dev_get_drvdata(pctldev);
|
||||
|
||||
if (!p_groups || !p_num_groups)
|
||||
return -EINVAL;
|
||||
|
||||
if (selector >= pmx->nr_functions)
|
||||
return -EINVAL;
|
||||
|
||||
func = &pmx->functions[selector];
|
||||
if (func->ngroups)
|
||||
goto done;
|
||||
|
||||
ret = pinctrl_ops->function_groups_get(pmx->ph, selector, &num_groups,
|
||||
&group_ids);
|
||||
if (ret) {
|
||||
dev_err(pmx->dev, "Unable to get function groups, err %d", ret);
|
||||
return ret;
|
||||
}
|
||||
if (!num_groups)
|
||||
return -EINVAL;
|
||||
|
||||
groups = kcalloc(num_groups, sizeof(*groups), GFP_KERNEL);
|
||||
if (!groups)
|
||||
return -ENOMEM;
|
||||
|
||||
for (i = 0; i < num_groups; i++) {
|
||||
groups[i] = pinctrl_scmi_get_group_name(pctldev, group_ids[i]);
|
||||
if (!groups[i]) {
|
||||
ret = -EINVAL;
|
||||
goto err_free;
|
||||
}
|
||||
}
|
||||
|
||||
func->ngroups = num_groups;
|
||||
func->groups = groups;
|
||||
done:
|
||||
*p_groups = func->groups;
|
||||
*p_num_groups = func->ngroups;
|
||||
|
||||
return 0;
|
||||
|
||||
err_free:
|
||||
kfree(groups);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int pinctrl_scmi_func_set_mux(struct pinctrl_dev *pctldev,
|
||||
unsigned int selector, unsigned int group)
|
||||
{
|
||||
struct scmi_pinctrl *pmx = pinctrl_dev_get_drvdata(pctldev);
|
||||
|
||||
return pinctrl_ops->mux_set(pmx->ph, selector, group);
|
||||
}
|
||||
|
||||
static int pinctrl_scmi_request(struct pinctrl_dev *pctldev,
|
||||
unsigned int offset)
|
||||
{
|
||||
struct scmi_pinctrl *pmx = pinctrl_dev_get_drvdata(pctldev);
|
||||
|
||||
return pinctrl_ops->pin_request(pmx->ph, offset);
|
||||
}
|
||||
|
||||
static int pinctrl_scmi_free(struct pinctrl_dev *pctldev, unsigned int offset)
|
||||
{
|
||||
struct scmi_pinctrl *pmx = pinctrl_dev_get_drvdata(pctldev);
|
||||
|
||||
return pinctrl_ops->pin_free(pmx->ph, offset);
|
||||
}
|
||||
|
||||
static const struct pinmux_ops pinctrl_scmi_pinmux_ops = {
|
||||
.request = pinctrl_scmi_request,
|
||||
.free = pinctrl_scmi_free,
|
||||
.get_functions_count = pinctrl_scmi_get_functions_count,
|
||||
.get_function_name = pinctrl_scmi_get_function_name,
|
||||
.get_function_groups = pinctrl_scmi_get_function_groups,
|
||||
.set_mux = pinctrl_scmi_func_set_mux,
|
||||
};
|
||||
|
||||
static int pinctrl_scmi_map_pinconf_type(enum pin_config_param param,
|
||||
enum scmi_pinctrl_conf_type *type)
|
||||
{
|
||||
u32 arg = param;
|
||||
|
||||
switch (arg) {
|
||||
case PIN_CONFIG_BIAS_BUS_HOLD:
|
||||
*type = SCMI_PIN_BIAS_BUS_HOLD;
|
||||
break;
|
||||
case PIN_CONFIG_BIAS_DISABLE:
|
||||
*type = SCMI_PIN_BIAS_DISABLE;
|
||||
break;
|
||||
case PIN_CONFIG_BIAS_HIGH_IMPEDANCE:
|
||||
*type = SCMI_PIN_BIAS_HIGH_IMPEDANCE;
|
||||
break;
|
||||
case PIN_CONFIG_BIAS_PULL_DOWN:
|
||||
*type = SCMI_PIN_BIAS_PULL_DOWN;
|
||||
break;
|
||||
case PIN_CONFIG_BIAS_PULL_PIN_DEFAULT:
|
||||
*type = SCMI_PIN_BIAS_PULL_DEFAULT;
|
||||
break;
|
||||
case PIN_CONFIG_BIAS_PULL_UP:
|
||||
*type = SCMI_PIN_BIAS_PULL_UP;
|
||||
break;
|
||||
case PIN_CONFIG_DRIVE_OPEN_DRAIN:
|
||||
*type = SCMI_PIN_DRIVE_OPEN_DRAIN;
|
||||
break;
|
||||
case PIN_CONFIG_DRIVE_OPEN_SOURCE:
|
||||
*type = SCMI_PIN_DRIVE_OPEN_SOURCE;
|
||||
break;
|
||||
case PIN_CONFIG_DRIVE_PUSH_PULL:
|
||||
*type = SCMI_PIN_DRIVE_PUSH_PULL;
|
||||
break;
|
||||
case PIN_CONFIG_DRIVE_STRENGTH:
|
||||
*type = SCMI_PIN_DRIVE_STRENGTH;
|
||||
break;
|
||||
case PIN_CONFIG_DRIVE_STRENGTH_UA:
|
||||
*type = SCMI_PIN_DRIVE_STRENGTH;
|
||||
break;
|
||||
case PIN_CONFIG_INPUT_DEBOUNCE:
|
||||
*type = SCMI_PIN_INPUT_DEBOUNCE;
|
||||
break;
|
||||
case PIN_CONFIG_INPUT_ENABLE:
|
||||
*type = SCMI_PIN_INPUT_MODE;
|
||||
break;
|
||||
case PIN_CONFIG_INPUT_SCHMITT:
|
||||
*type = SCMI_PIN_INPUT_SCHMITT;
|
||||
break;
|
||||
case PIN_CONFIG_INPUT_SCHMITT_ENABLE:
|
||||
*type = SCMI_PIN_INPUT_MODE;
|
||||
break;
|
||||
case PIN_CONFIG_MODE_LOW_POWER:
|
||||
*type = SCMI_PIN_LOW_POWER_MODE;
|
||||
break;
|
||||
case PIN_CONFIG_OUTPUT:
|
||||
*type = SCMI_PIN_OUTPUT_VALUE;
|
||||
break;
|
||||
case PIN_CONFIG_OUTPUT_ENABLE:
|
||||
*type = SCMI_PIN_OUTPUT_MODE;
|
||||
break;
|
||||
case PIN_CONFIG_OUTPUT_IMPEDANCE_OHMS:
|
||||
*type = SCMI_PIN_OUTPUT_VALUE;
|
||||
break;
|
||||
case PIN_CONFIG_POWER_SOURCE:
|
||||
*type = SCMI_PIN_POWER_SOURCE;
|
||||
break;
|
||||
case PIN_CONFIG_SLEW_RATE:
|
||||
*type = SCMI_PIN_SLEW_RATE;
|
||||
break;
|
||||
case SCMI_PIN_OEM_START ... SCMI_PIN_OEM_END:
|
||||
*type = arg;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pinctrl_scmi_pinconf_get(struct pinctrl_dev *pctldev,
|
||||
unsigned int pin, unsigned long *config)
|
||||
{
|
||||
int ret;
|
||||
struct scmi_pinctrl *pmx = pinctrl_dev_get_drvdata(pctldev);
|
||||
enum pin_config_param config_type;
|
||||
enum scmi_pinctrl_conf_type type;
|
||||
u32 config_value;
|
||||
|
||||
if (!config)
|
||||
return -EINVAL;
|
||||
|
||||
config_type = pinconf_to_config_param(*config);
|
||||
|
||||
ret = pinctrl_scmi_map_pinconf_type(config_type, &type);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = pinctrl_ops->settings_get_one(pmx->ph, pin, PIN_TYPE, type,
|
||||
&config_value);
|
||||
/* Convert SCMI error code to PINCTRL expected error code */
|
||||
if (ret == -EOPNOTSUPP)
|
||||
return -ENOTSUPP;
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*config = pinconf_to_config_packed(config_type, config_value);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
pinctrl_scmi_alloc_configs(struct pinctrl_dev *pctldev, u32 num_configs,
|
||||
u32 **p_config_value,
|
||||
enum scmi_pinctrl_conf_type **p_config_type)
|
||||
{
|
||||
if (num_configs <= SCMI_NUM_CONFIGS)
|
||||
return 0;
|
||||
|
||||
*p_config_value = kcalloc(num_configs, sizeof(**p_config_value), GFP_KERNEL);
|
||||
if (!*p_config_value)
|
||||
return -ENOMEM;
|
||||
|
||||
*p_config_type = kcalloc(num_configs, sizeof(**p_config_type), GFP_KERNEL);
|
||||
if (!*p_config_type) {
|
||||
kfree(*p_config_value);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
pinctrl_scmi_free_configs(struct pinctrl_dev *pctldev, u32 num_configs,
|
||||
u32 **p_config_value,
|
||||
enum scmi_pinctrl_conf_type **p_config_type)
|
||||
{
|
||||
if (num_configs <= SCMI_NUM_CONFIGS)
|
||||
return;
|
||||
|
||||
kfree(*p_config_value);
|
||||
kfree(*p_config_type);
|
||||
}
|
||||
|
||||
static int pinctrl_scmi_pinconf_set(struct pinctrl_dev *pctldev,
|
||||
unsigned int pin,
|
||||
unsigned long *configs,
|
||||
unsigned int num_configs)
|
||||
{
|
||||
int i, ret;
|
||||
struct scmi_pinctrl *pmx = pinctrl_dev_get_drvdata(pctldev);
|
||||
enum scmi_pinctrl_conf_type config_type[SCMI_NUM_CONFIGS];
|
||||
u32 config_value[SCMI_NUM_CONFIGS];
|
||||
enum scmi_pinctrl_conf_type *p_config_type = config_type;
|
||||
u32 *p_config_value = config_value;
|
||||
enum pin_config_param param;
|
||||
|
||||
if (!configs || !num_configs)
|
||||
return -EINVAL;
|
||||
|
||||
ret = pinctrl_scmi_alloc_configs(pctldev, num_configs, &p_config_type,
|
||||
&p_config_value);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
for (i = 0; i < num_configs; i++) {
|
||||
param = pinconf_to_config_param(configs[i]);
|
||||
ret = pinctrl_scmi_map_pinconf_type(param, &p_config_type[i]);
|
||||
if (ret) {
|
||||
dev_err(pmx->dev, "Error map pinconf_type %d\n", ret);
|
||||
goto free_config;
|
||||
}
|
||||
p_config_value[i] = pinconf_to_config_argument(configs[i]);
|
||||
}
|
||||
|
||||
ret = pinctrl_ops->settings_conf(pmx->ph, pin, PIN_TYPE, num_configs,
|
||||
p_config_type, p_config_value);
|
||||
if (ret)
|
||||
dev_err(pmx->dev, "Error parsing config %d\n", ret);
|
||||
|
||||
free_config:
|
||||
pinctrl_scmi_free_configs(pctldev, num_configs, &p_config_type,
|
||||
&p_config_value);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int pinctrl_scmi_pinconf_group_set(struct pinctrl_dev *pctldev,
|
||||
unsigned int group,
|
||||
unsigned long *configs,
|
||||
unsigned int num_configs)
|
||||
{
|
||||
int i, ret;
|
||||
struct scmi_pinctrl *pmx = pinctrl_dev_get_drvdata(pctldev);
|
||||
enum scmi_pinctrl_conf_type config_type[SCMI_NUM_CONFIGS];
|
||||
u32 config_value[SCMI_NUM_CONFIGS];
|
||||
enum scmi_pinctrl_conf_type *p_config_type = config_type;
|
||||
u32 *p_config_value = config_value;
|
||||
enum pin_config_param param;
|
||||
|
||||
if (!configs || !num_configs)
|
||||
return -EINVAL;
|
||||
|
||||
ret = pinctrl_scmi_alloc_configs(pctldev, num_configs, &p_config_type,
|
||||
&p_config_value);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
for (i = 0; i < num_configs; i++) {
|
||||
param = pinconf_to_config_param(configs[i]);
|
||||
ret = pinctrl_scmi_map_pinconf_type(param, &p_config_type[i]);
|
||||
if (ret) {
|
||||
dev_err(pmx->dev, "Error map pinconf_type %d\n", ret);
|
||||
goto free_config;
|
||||
}
|
||||
|
||||
p_config_value[i] = pinconf_to_config_argument(configs[i]);
|
||||
}
|
||||
|
||||
ret = pinctrl_ops->settings_conf(pmx->ph, group, GROUP_TYPE,
|
||||
num_configs, p_config_type,
|
||||
p_config_value);
|
||||
if (ret)
|
||||
dev_err(pmx->dev, "Error parsing config %d", ret);
|
||||
|
||||
free_config:
|
||||
pinctrl_scmi_free_configs(pctldev, num_configs, &p_config_type,
|
||||
&p_config_value);
|
||||
return ret;
|
||||
};
|
||||
|
||||
static int pinctrl_scmi_pinconf_group_get(struct pinctrl_dev *pctldev,
|
||||
unsigned int group,
|
||||
unsigned long *config)
|
||||
{
|
||||
int ret;
|
||||
struct scmi_pinctrl *pmx = pinctrl_dev_get_drvdata(pctldev);
|
||||
enum pin_config_param config_type;
|
||||
enum scmi_pinctrl_conf_type type;
|
||||
u32 config_value;
|
||||
|
||||
if (!config)
|
||||
return -EINVAL;
|
||||
|
||||
config_type = pinconf_to_config_param(*config);
|
||||
ret = pinctrl_scmi_map_pinconf_type(config_type, &type);
|
||||
if (ret) {
|
||||
dev_err(pmx->dev, "Error map pinconf_type %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = pinctrl_ops->settings_get_one(pmx->ph, group, GROUP_TYPE, type,
|
||||
&config_value);
|
||||
/* Convert SCMI error code to PINCTRL expected error code */
|
||||
if (ret == -EOPNOTSUPP)
|
||||
return -ENOTSUPP;
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*config = pinconf_to_config_packed(config_type, config_value);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct pinconf_ops pinctrl_scmi_pinconf_ops = {
|
||||
.is_generic = true,
|
||||
.pin_config_get = pinctrl_scmi_pinconf_get,
|
||||
.pin_config_set = pinctrl_scmi_pinconf_set,
|
||||
.pin_config_group_set = pinctrl_scmi_pinconf_group_set,
|
||||
.pin_config_group_get = pinctrl_scmi_pinconf_group_get,
|
||||
.pin_config_config_dbg_show = pinconf_generic_dump_config,
|
||||
};
|
||||
|
||||
static int pinctrl_scmi_get_pins(struct scmi_pinctrl *pmx,
|
||||
struct pinctrl_desc *desc)
|
||||
{
|
||||
struct pinctrl_pin_desc *pins;
|
||||
unsigned int npins;
|
||||
int ret, i;
|
||||
|
||||
npins = pinctrl_ops->count_get(pmx->ph, PIN_TYPE);
|
||||
/*
|
||||
* npins will never be zero, the scmi pinctrl driver has bailed out
|
||||
* if npins is zero.
|
||||
*/
|
||||
pins = devm_kmalloc_array(pmx->dev, npins, sizeof(*pins), GFP_KERNEL);
|
||||
if (!pins)
|
||||
return -ENOMEM;
|
||||
|
||||
for (i = 0; i < npins; i++) {
|
||||
pins[i].number = i;
|
||||
/*
|
||||
* The memory for name is handled by the scmi firmware driver,
|
||||
* no need free here
|
||||
*/
|
||||
ret = pinctrl_ops->name_get(pmx->ph, i, PIN_TYPE, &pins[i].name);
|
||||
if (ret)
|
||||
return dev_err_probe(pmx->dev, ret,
|
||||
"Can't get name for pin %d", i);
|
||||
}
|
||||
|
||||
desc->npins = npins;
|
||||
desc->pins = pins;
|
||||
dev_dbg(pmx->dev, "got pins %u", npins);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int scmi_pinctrl_probe(struct scmi_device *sdev)
|
||||
{
|
||||
int ret;
|
||||
struct device *dev = &sdev->dev;
|
||||
struct scmi_pinctrl *pmx;
|
||||
const struct scmi_handle *handle;
|
||||
struct scmi_protocol_handle *ph;
|
||||
|
||||
if (!sdev->handle)
|
||||
return -EINVAL;
|
||||
|
||||
handle = sdev->handle;
|
||||
|
||||
pinctrl_ops = handle->devm_protocol_get(sdev, SCMI_PROTOCOL_PINCTRL, &ph);
|
||||
if (IS_ERR(pinctrl_ops))
|
||||
return PTR_ERR(pinctrl_ops);
|
||||
|
||||
pmx = devm_kzalloc(dev, sizeof(*pmx), GFP_KERNEL);
|
||||
if (!pmx)
|
||||
return -ENOMEM;
|
||||
|
||||
pmx->ph = ph;
|
||||
|
||||
pmx->dev = dev;
|
||||
pmx->pctl_desc.name = DRV_NAME;
|
||||
pmx->pctl_desc.owner = THIS_MODULE;
|
||||
pmx->pctl_desc.pctlops = &pinctrl_scmi_pinctrl_ops;
|
||||
pmx->pctl_desc.pmxops = &pinctrl_scmi_pinmux_ops;
|
||||
pmx->pctl_desc.confops = &pinctrl_scmi_pinconf_ops;
|
||||
|
||||
ret = pinctrl_scmi_get_pins(pmx, &pmx->pctl_desc);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = devm_pinctrl_register_and_init(dev, &pmx->pctl_desc, pmx,
|
||||
&pmx->pctldev);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "Failed to register pinctrl\n");
|
||||
|
||||
pmx->nr_functions = pinctrl_scmi_get_functions_count(pmx->pctldev);
|
||||
pmx->functions = devm_kcalloc(dev, pmx->nr_functions,
|
||||
sizeof(*pmx->functions), GFP_KERNEL);
|
||||
if (!pmx->functions)
|
||||
return -ENOMEM;
|
||||
|
||||
return pinctrl_enable(pmx->pctldev);
|
||||
}
|
||||
|
||||
static const struct scmi_device_id scmi_id_table[] = {
|
||||
{ SCMI_PROTOCOL_PINCTRL, "pinctrl" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(scmi, scmi_id_table);
|
||||
|
||||
static struct scmi_driver scmi_pinctrl_driver = {
|
||||
.name = DRV_NAME,
|
||||
.probe = scmi_pinctrl_probe,
|
||||
.id_table = scmi_id_table,
|
||||
};
|
||||
module_scmi_driver(scmi_pinctrl_driver);
|
||||
|
||||
MODULE_AUTHOR("Oleksii Moisieiev <oleksii_moisieiev@epam.com>");
|
||||
MODULE_AUTHOR("Peng Fan <peng.fan@nxp.com>");
|
||||
MODULE_DESCRIPTION("ARM SCMI pin controller driver");
|
||||
MODULE_LICENSE("GPL");
|
@ -737,6 +737,89 @@ struct scmi_powercap_proto_ops {
|
||||
u32 *power_thresh_high);
|
||||
};
|
||||
|
||||
enum scmi_pinctrl_selector_type {
|
||||
PIN_TYPE = 0,
|
||||
GROUP_TYPE,
|
||||
FUNCTION_TYPE,
|
||||
};
|
||||
|
||||
enum scmi_pinctrl_conf_type {
|
||||
SCMI_PIN_DEFAULT = 0,
|
||||
SCMI_PIN_BIAS_BUS_HOLD = 1,
|
||||
SCMI_PIN_BIAS_DISABLE = 2,
|
||||
SCMI_PIN_BIAS_HIGH_IMPEDANCE = 3,
|
||||
SCMI_PIN_BIAS_PULL_UP = 4,
|
||||
SCMI_PIN_BIAS_PULL_DEFAULT = 5,
|
||||
SCMI_PIN_BIAS_PULL_DOWN = 6,
|
||||
SCMI_PIN_DRIVE_OPEN_DRAIN = 7,
|
||||
SCMI_PIN_DRIVE_OPEN_SOURCE = 8,
|
||||
SCMI_PIN_DRIVE_PUSH_PULL = 9,
|
||||
SCMI_PIN_DRIVE_STRENGTH = 10,
|
||||
SCMI_PIN_INPUT_DEBOUNCE = 11,
|
||||
SCMI_PIN_INPUT_MODE = 12,
|
||||
SCMI_PIN_PULL_MODE = 13,
|
||||
SCMI_PIN_INPUT_VALUE = 14,
|
||||
SCMI_PIN_INPUT_SCHMITT = 15,
|
||||
SCMI_PIN_LOW_POWER_MODE = 16,
|
||||
SCMI_PIN_OUTPUT_MODE = 17,
|
||||
SCMI_PIN_OUTPUT_VALUE = 18,
|
||||
SCMI_PIN_POWER_SOURCE = 19,
|
||||
SCMI_PIN_SLEW_RATE = 20,
|
||||
SCMI_PIN_OEM_START = 192,
|
||||
SCMI_PIN_OEM_END = 255,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct scmi_pinctrl_proto_ops - represents the various operations provided
|
||||
* by SCMI Pinctrl Protocol
|
||||
*
|
||||
* @count_get: returns count of the registered elements in given type
|
||||
* @name_get: returns name by index of given type
|
||||
* @group_pins_get: returns the set of pins, assigned to the specified group
|
||||
* @function_groups_get: returns the set of groups, assigned to the specified
|
||||
* function
|
||||
* @mux_set: set muxing function for groups of pins
|
||||
* @settings_get_one: returns one configuration parameter for pin or group
|
||||
* specified by config_type
|
||||
* @settings_get_all: returns all configuration parameters for pin or group
|
||||
* @settings_conf: sets the configuration parameter for pin or group
|
||||
* @pin_request: aquire pin before selecting mux setting
|
||||
* @pin_free: frees pin, acquired by request_pin call
|
||||
*/
|
||||
struct scmi_pinctrl_proto_ops {
|
||||
int (*count_get)(const struct scmi_protocol_handle *ph,
|
||||
enum scmi_pinctrl_selector_type type);
|
||||
int (*name_get)(const struct scmi_protocol_handle *ph, u32 selector,
|
||||
enum scmi_pinctrl_selector_type type,
|
||||
const char **name);
|
||||
int (*group_pins_get)(const struct scmi_protocol_handle *ph,
|
||||
u32 selector, const unsigned int **pins,
|
||||
unsigned int *nr_pins);
|
||||
int (*function_groups_get)(const struct scmi_protocol_handle *ph,
|
||||
u32 selector, unsigned int *nr_groups,
|
||||
const unsigned int **groups);
|
||||
int (*mux_set)(const struct scmi_protocol_handle *ph, u32 selector,
|
||||
u32 group);
|
||||
int (*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);
|
||||
int (*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);
|
||||
int (*settings_conf)(const struct scmi_protocol_handle *ph,
|
||||
u32 selector, enum scmi_pinctrl_selector_type type,
|
||||
unsigned int nr_configs,
|
||||
enum scmi_pinctrl_conf_type *config_type,
|
||||
u32 *config_value);
|
||||
int (*pin_request)(const struct scmi_protocol_handle *ph, u32 pin);
|
||||
int (*pin_free)(const struct scmi_protocol_handle *ph, u32 pin);
|
||||
};
|
||||
|
||||
/**
|
||||
* struct scmi_notify_ops - represents notifications' operations provided by
|
||||
* SCMI core
|
||||
@ -783,8 +866,6 @@ struct scmi_notify_ops {
|
||||
const u32 *src_id,
|
||||
struct notifier_block *nb);
|
||||
int (*devm_event_notifier_unregister)(struct scmi_device *sdev,
|
||||
u8 proto_id, u8 evt_id,
|
||||
const u32 *src_id,
|
||||
struct notifier_block *nb);
|
||||
int (*event_notifier_register)(const struct scmi_handle *handle,
|
||||
u8 proto_id, u8 evt_id,
|
||||
@ -844,6 +925,7 @@ enum scmi_std_protocol {
|
||||
SCMI_PROTOCOL_RESET = 0x16,
|
||||
SCMI_PROTOCOL_VOLTAGE = 0x17,
|
||||
SCMI_PROTOCOL_POWERCAP = 0x18,
|
||||
SCMI_PROTOCOL_PINCTRL = 0x19,
|
||||
};
|
||||
|
||||
enum scmi_system_events {
|
||||
|
@ -7,6 +7,8 @@
|
||||
|
||||
#include <linux/tracepoint.h>
|
||||
|
||||
#define TRACE_SCMI_MAX_TAG_LEN 6
|
||||
|
||||
TRACE_EVENT(scmi_fc_call,
|
||||
TP_PROTO(u8 protocol_id, u8 msg_id, u32 res_id, u32 val1, u32 val2),
|
||||
TP_ARGS(protocol_id, msg_id, res_id, val1, val2),
|
||||
@ -150,7 +152,7 @@ TRACE_EVENT(scmi_msg_dump,
|
||||
__field(u8, channel_id)
|
||||
__field(u8, protocol_id)
|
||||
__field(u8, msg_id)
|
||||
__array(char, tag, 5)
|
||||
__array(char, tag, TRACE_SCMI_MAX_TAG_LEN)
|
||||
__field(u16, seq)
|
||||
__field(int, status)
|
||||
__field(size_t, len)
|
||||
@ -162,7 +164,7 @@ TRACE_EVENT(scmi_msg_dump,
|
||||
__entry->channel_id = channel_id;
|
||||
__entry->protocol_id = protocol_id;
|
||||
__entry->msg_id = msg_id;
|
||||
strscpy(__entry->tag, tag, 5);
|
||||
strscpy(__entry->tag, tag, TRACE_SCMI_MAX_TAG_LEN);
|
||||
__entry->seq = seq;
|
||||
__entry->status = status;
|
||||
__entry->len = len;
|
||||
|
Loading…
Reference in New Issue
Block a user