Contributors: 1
Author Tokens Token Proportion Commits Commit Proportion
Peng Fan 1238 100.00% 3 100.00%
Total 1238 3


// SPDX-License-Identifier: GPL-2.0
/*
 * System control and Management Interface (SCMI) NXP CPU Protocol
 *
 * Copyright 2025 NXP
 */

#include <linux/bits.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/scmi_protocol.h>
#include <linux/scmi_imx_protocol.h>

#include "../../protocols.h"
#include "../../notify.h"

#define SCMI_PROTOCOL_SUPPORTED_VERSION		0x10000

enum scmi_imx_cpu_protocol_cmd {
	SCMI_IMX_CPU_ATTRIBUTES	= 0x3,
	SCMI_IMX_CPU_START = 0x4,
	SCMI_IMX_CPU_STOP = 0x5,
	SCMI_IMX_CPU_RESET_VECTOR_SET = 0x6,
	SCMI_IMX_CPU_INFO_GET = 0xC,
};

struct scmi_imx_cpu_info {
	u32 nr_cpu;
};

#define SCMI_IMX_CPU_NR_CPU_MASK	GENMASK(15, 0)
struct scmi_msg_imx_cpu_protocol_attributes {
	__le32 attributes;
};

struct scmi_msg_imx_cpu_attributes_out {
	__le32 attributes;
#define	CPU_MAX_NAME	16
	u8 name[CPU_MAX_NAME];
};

struct scmi_imx_cpu_reset_vector_set_in {
	__le32 cpuid;
#define	CPU_VEC_FLAGS_RESUME	BIT(31)
#define	CPU_VEC_FLAGS_START	BIT(30)
#define	CPU_VEC_FLAGS_BOOT	BIT(29)
	__le32 flags;
	__le32 resetvectorlow;
	__le32 resetvectorhigh;
};

struct scmi_imx_cpu_info_get_out {
#define	CPU_RUN_MODE_START	0
#define	CPU_RUN_MODE_HOLD	1
#define	CPU_RUN_MODE_STOP	2
#define	CPU_RUN_MODE_SLEEP	3
	__le32 runmode;
	__le32 sleepmode;
	__le32 resetvectorlow;
	__le32 resetvectorhigh;
};

static int scmi_imx_cpu_validate_cpuid(const struct scmi_protocol_handle *ph,
				       u32 cpuid)
{
	struct scmi_imx_cpu_info *info = ph->get_priv(ph);

	if (cpuid >= info->nr_cpu)
		return -EINVAL;

	return 0;
}

static int scmi_imx_cpu_start(const struct scmi_protocol_handle *ph,
			      u32 cpuid, bool start)
{
	struct scmi_xfer *t;
	u8 msg_id;
	int ret;

	ret = scmi_imx_cpu_validate_cpuid(ph, cpuid);
	if (ret)
		return ret;

	if (start)
		msg_id = SCMI_IMX_CPU_START;
	else
		msg_id = SCMI_IMX_CPU_STOP;

	ret = ph->xops->xfer_get_init(ph, msg_id, sizeof(u32), 0, &t);
	if (ret)
		return ret;

	put_unaligned_le32(cpuid, t->tx.buf);
	ret = ph->xops->do_xfer(ph, t);

	ph->xops->xfer_put(ph, t);

	return ret;
}

static int scmi_imx_cpu_reset_vector_set(const struct scmi_protocol_handle *ph,
					 u32 cpuid, u64 vector, bool start,
					 bool boot, bool resume)
{
	struct scmi_imx_cpu_reset_vector_set_in *in;
	struct scmi_xfer *t;
	int ret;

	ret = scmi_imx_cpu_validate_cpuid(ph, cpuid);
	if (ret)
		return ret;

	ret = ph->xops->xfer_get_init(ph, SCMI_IMX_CPU_RESET_VECTOR_SET, sizeof(*in),
				      0, &t);
	if (ret)
		return ret;

	in = t->tx.buf;
	in->cpuid = cpu_to_le32(cpuid);
	in->flags = cpu_to_le32(0);
	if (start)
		in->flags |= le32_encode_bits(1, CPU_VEC_FLAGS_START);
	if (boot)
		in->flags |= le32_encode_bits(1, CPU_VEC_FLAGS_BOOT);
	if (resume)
		in->flags |= le32_encode_bits(1, CPU_VEC_FLAGS_RESUME);
	in->resetvectorlow = cpu_to_le32(lower_32_bits(vector));
	in->resetvectorhigh = cpu_to_le32(upper_32_bits(vector));
	ret = ph->xops->do_xfer(ph, t);

	ph->xops->xfer_put(ph, t);

	return ret;
}

static int scmi_imx_cpu_started(const struct scmi_protocol_handle *ph, u32 cpuid,
				bool *started)
{
	struct scmi_imx_cpu_info_get_out *out;
	struct scmi_xfer *t;
	u32 mode;
	int ret;

	if (!started)
		return -EINVAL;

	*started = false;
	ret = scmi_imx_cpu_validate_cpuid(ph, cpuid);
	if (ret)
		return ret;

	ret = ph->xops->xfer_get_init(ph, SCMI_IMX_CPU_INFO_GET, sizeof(u32),
				      0, &t);
	if (ret)
		return ret;

	put_unaligned_le32(cpuid, t->tx.buf);
	ret = ph->xops->do_xfer(ph, t);
	if (!ret) {
		out = t->rx.buf;
		mode = le32_to_cpu(out->runmode);
		if (mode == CPU_RUN_MODE_START || mode == CPU_RUN_MODE_SLEEP)
			*started = true;
	}

	ph->xops->xfer_put(ph, t);

	return ret;
}

static const struct scmi_imx_cpu_proto_ops scmi_imx_cpu_proto_ops = {
	.cpu_reset_vector_set = scmi_imx_cpu_reset_vector_set,
	.cpu_start = scmi_imx_cpu_start,
	.cpu_started = scmi_imx_cpu_started,
};

static int scmi_imx_cpu_protocol_attributes_get(const struct scmi_protocol_handle *ph,
						struct scmi_imx_cpu_info *info)
{
	struct scmi_msg_imx_cpu_protocol_attributes *attr;
	struct scmi_xfer *t;
	int ret;

	ret = ph->xops->xfer_get_init(ph, PROTOCOL_ATTRIBUTES, 0,
				      sizeof(*attr), &t);
	if (ret)
		return ret;

	attr = t->rx.buf;

	ret = ph->xops->do_xfer(ph, t);
	if (!ret) {
		info->nr_cpu = le32_get_bits(attr->attributes, SCMI_IMX_CPU_NR_CPU_MASK);
		dev_info(ph->dev, "i.MX SM CPU: %d cpus\n",
			 info->nr_cpu);
	}

	ph->xops->xfer_put(ph, t);

	return ret;
}

static int scmi_imx_cpu_attributes_get(const struct scmi_protocol_handle *ph,
				       u32 cpuid)
{
	struct scmi_msg_imx_cpu_attributes_out *out;
	char name[SCMI_SHORT_NAME_MAX_SIZE] = {'\0'};
	struct scmi_xfer *t;
	int ret;

	ret = ph->xops->xfer_get_init(ph, SCMI_IMX_CPU_ATTRIBUTES, sizeof(u32), 0, &t);
	if (ret)
		return ret;

	put_unaligned_le32(cpuid, t->tx.buf);
	ret = ph->xops->do_xfer(ph, t);
	if (!ret) {
		out = t->rx.buf;
		strscpy(name, out->name, SCMI_SHORT_NAME_MAX_SIZE);
		dev_info(ph->dev, "i.MX CPU: name: %s\n", name);
	} else {
		dev_err(ph->dev, "i.MX cpu: Failed to get info of cpu(%u)\n", cpuid);
	}

	ph->xops->xfer_put(ph, t);

	return ret;
}

static int scmi_imx_cpu_protocol_init(const struct scmi_protocol_handle *ph)
{
	struct scmi_imx_cpu_info *info;
	u32 version;
	int ret, i;

	ret = ph->xops->version_get(ph, &version);
	if (ret)
		return ret;

	dev_info(ph->dev, "NXP SM CPU Protocol 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_cpu_protocol_attributes_get(ph, info);
	if (ret)
		return ret;

	for (i = 0; i < info->nr_cpu; i++) {
		ret = scmi_imx_cpu_attributes_get(ph, i);
		if (ret)
			return ret;
	}

	return ph->set_priv(ph, info, version);
}

static const struct scmi_protocol scmi_imx_cpu = {
	.id = SCMI_PROTOCOL_IMX_CPU,
	.owner = THIS_MODULE,
	.instance_init = &scmi_imx_cpu_protocol_init,
	.ops = &scmi_imx_cpu_proto_ops,
	.supported_version = SCMI_PROTOCOL_SUPPORTED_VERSION,
	.vendor_id = SCMI_IMX_VENDOR,
	.sub_vendor_id = SCMI_IMX_SUBVENDOR,
};
module_scmi_protocol(scmi_imx_cpu);

MODULE_ALIAS("scmi-protocol-" __stringify(SCMI_PROTOCOL_IMX_CPU) "-" SCMI_IMX_VENDOR);
MODULE_DESCRIPTION("i.MX SCMI CPU driver");
MODULE_LICENSE("GPL");