// SPDX-License-Identifier: GPL-2.0 /* * System control and Management Interface (SCMI) NXP LMM Protocol * * Copyright 2025 NXP */ #include #include #include #include #include #include #include #include "../../protocols.h" #include "../../notify.h" #define SCMI_PROTOCOL_SUPPORTED_VERSION 0x10000 enum scmi_imx_lmm_protocol_cmd { SCMI_IMX_LMM_ATTRIBUTES = 0x3, SCMI_IMX_LMM_BOOT = 0x4, SCMI_IMX_LMM_RESET = 0x5, SCMI_IMX_LMM_SHUTDOWN = 0x6, SCMI_IMX_LMM_WAKE = 0x7, SCMI_IMX_LMM_SUSPEND = 0x8, SCMI_IMX_LMM_NOTIFY = 0x9, SCMI_IMX_LMM_RESET_REASON = 0xA, SCMI_IMX_LMM_POWER_ON = 0xB, SCMI_IMX_LMM_RESET_VECTOR_SET = 0xC, }; struct scmi_imx_lmm_priv { u32 nr_lmm; }; #define SCMI_IMX_LMM_NR_LM_MASK GENMASK(5, 0) #define SCMI_IMX_LMM_NR_MAX 16 struct scmi_msg_imx_lmm_protocol_attributes { __le32 attributes; }; struct scmi_msg_imx_lmm_attributes_out { __le32 lmid; __le32 attributes; __le32 state; __le32 errstatus; u8 name[LMM_MAX_NAME]; }; struct scmi_imx_lmm_reset_vector_set_in { __le32 lmid; __le32 cpuid; __le32 flags; /* reserved for future extension */ __le32 resetvectorlow; __le32 resetvectorhigh; }; struct scmi_imx_lmm_shutdown_in { __le32 lmid; #define SCMI_IMX_LMM_SHUTDOWN_GRACEFUL BIT(0) __le32 flags; }; static int scmi_imx_lmm_validate_lmid(const struct scmi_protocol_handle *ph, u32 lmid) { struct scmi_imx_lmm_priv *priv = ph->get_priv(ph); if (lmid >= priv->nr_lmm) return -EINVAL; return 0; } static int scmi_imx_lmm_attributes(const struct scmi_protocol_handle *ph, u32 lmid, struct scmi_imx_lmm_info *info) { struct scmi_msg_imx_lmm_attributes_out *out; struct scmi_xfer *t; int ret; ret = ph->xops->xfer_get_init(ph, SCMI_IMX_LMM_ATTRIBUTES, sizeof(u32), 0, &t); if (ret) return ret; put_unaligned_le32(lmid, t->tx.buf); ret = ph->xops->do_xfer(ph, t); if (!ret) { out = t->rx.buf; info->lmid = le32_to_cpu(out->lmid); info->state = le32_to_cpu(out->state); info->errstatus = le32_to_cpu(out->errstatus); strscpy(info->name, out->name); dev_dbg(ph->dev, "i.MX LMM: Logical Machine(%d), name: %s\n", info->lmid, info->name); } else { dev_err(ph->dev, "i.MX LMM: Failed to get info of Logical Machine(%u)\n", lmid); } ph->xops->xfer_put(ph, t); return ret; } static int scmi_imx_lmm_power_boot(const struct scmi_protocol_handle *ph, u32 lmid, bool boot) { struct scmi_xfer *t; u8 msg_id; int ret; ret = scmi_imx_lmm_validate_lmid(ph, lmid); if (ret) return ret; if (boot) msg_id = SCMI_IMX_LMM_BOOT; else msg_id = SCMI_IMX_LMM_POWER_ON; ret = ph->xops->xfer_get_init(ph, msg_id, sizeof(u32), 0, &t); if (ret) return ret; put_unaligned_le32(lmid, t->tx.buf); ret = ph->xops->do_xfer(ph, t); ph->xops->xfer_put(ph, t); return ret; } static int scmi_imx_lmm_reset_vector_set(const struct scmi_protocol_handle *ph, u32 lmid, u32 cpuid, u32 flags, u64 vector) { struct scmi_imx_lmm_reset_vector_set_in *in; struct scmi_xfer *t; int ret; ret = ph->xops->xfer_get_init(ph, SCMI_IMX_LMM_RESET_VECTOR_SET, sizeof(*in), 0, &t); if (ret) return ret; in = t->tx.buf; in->lmid = cpu_to_le32(lmid); in->cpuid = cpu_to_le32(cpuid); in->flags = cpu_to_le32(0); in->resetvectorlow = cpu_to_le32(lower_32_bits(vector)); in->resetvectorhigh = cpu_to_le32(upper_32_bits(vector)); ret = ph->xops->do_xfer(ph, t); ph->xops->xfer_put(ph, t); return ret; } static int scmi_imx_lmm_shutdown(const struct scmi_protocol_handle *ph, u32 lmid, u32 flags) { struct scmi_imx_lmm_shutdown_in *in; struct scmi_xfer *t; int ret; ret = scmi_imx_lmm_validate_lmid(ph, lmid); if (ret) return ret; ret = ph->xops->xfer_get_init(ph, SCMI_IMX_LMM_SHUTDOWN, sizeof(*in), 0, &t); if (ret) return ret; in = t->tx.buf; in->lmid = cpu_to_le32(lmid); if (flags & SCMI_IMX_LMM_SHUTDOWN_GRACEFUL) in->flags = cpu_to_le32(SCMI_IMX_LMM_SHUTDOWN_GRACEFUL); else in->flags = cpu_to_le32(0); ret = ph->xops->do_xfer(ph, t); ph->xops->xfer_put(ph, t); return ret; } static const struct scmi_imx_lmm_proto_ops scmi_imx_lmm_proto_ops = { .lmm_power_boot = scmi_imx_lmm_power_boot, .lmm_info = scmi_imx_lmm_attributes, .lmm_reset_vector_set = scmi_imx_lmm_reset_vector_set, .lmm_shutdown = scmi_imx_lmm_shutdown, }; static int scmi_imx_lmm_protocol_attributes_get(const struct scmi_protocol_handle *ph, struct scmi_imx_lmm_priv *priv) { struct scmi_msg_imx_lmm_protocol_attributes *attr; struct scmi_xfer *t; int ret; ret = ph->xops->xfer_get_init(ph, PROTOCOL_ATTRIBUTES, 0, sizeof(*attr), &t); if (ret) return ret; attr = t->rx.buf; ret = ph->xops->do_xfer(ph, t); if (!ret) { priv->nr_lmm = le32_get_bits(attr->attributes, SCMI_IMX_LMM_NR_LM_MASK); if (priv->nr_lmm > SCMI_IMX_LMM_NR_MAX) { dev_err(ph->dev, "i.MX LMM: %d:Exceed max supported Logical Machines\n", priv->nr_lmm); ret = -EINVAL; } else { dev_info(ph->dev, "i.MX LMM: %d Logical Machines\n", priv->nr_lmm); } } ph->xops->xfer_put(ph, t); return ret; } static int scmi_imx_lmm_protocol_init(const struct scmi_protocol_handle *ph) { struct scmi_imx_lmm_priv *info; u32 version; int ret; ret = ph->xops->version_get(ph, &version); if (ret) return ret; dev_info(ph->dev, "NXP SM LMM Version %d.%d\n", PROTOCOL_REV_MAJOR(version), PROTOCOL_REV_MINOR(version)); info = devm_kzalloc(ph->dev, sizeof(*info), GFP_KERNEL); if (!info) return -ENOMEM; ret = scmi_imx_lmm_protocol_attributes_get(ph, info); if (ret) return ret; return ph->set_priv(ph, info, version); } static const struct scmi_protocol scmi_imx_lmm = { .id = SCMI_PROTOCOL_IMX_LMM, .owner = THIS_MODULE, .instance_init = &scmi_imx_lmm_protocol_init, .ops = &scmi_imx_lmm_proto_ops, .supported_version = SCMI_PROTOCOL_SUPPORTED_VERSION, .vendor_id = SCMI_IMX_VENDOR, .sub_vendor_id = SCMI_IMX_SUBVENDOR, }; module_scmi_protocol(scmi_imx_lmm); MODULE_ALIAS("scmi-protocol-" __stringify(SCMI_PROTOCOL_IMX_LMM) "-" SCMI_IMX_VENDOR); MODULE_DESCRIPTION("i.MX SCMI LMM driver"); MODULE_LICENSE("GPL");