// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright 2022 NXP
 */

#include <common.h>
#include <dm.h>
#include <dm/device_compat.h>
#include <thermal.h>
#include <scmi_agent.h>
#include <scmi_protocols.h>
#include <asm/types.h>
#include <dm/device-internal.h>
#include <dm/device.h>

struct scmi_thermal_priv {
	s16 num_sensors;
	s16 thermal_id;
	struct scmi_sensor_descrition_get_p2a *desc_buf;
	size_t desc_buf_size;
	struct scmi_channel *channel;
};

static int scmi_thermal_get_temp(struct udevice *dev, int *temp)
{
	struct scmi_thermal_priv *priv = dev_get_priv(dev);
	struct scmi_sensor_reading_get_a2p in = { priv->thermal_id };
	struct scmi_sensor_reading_get_p2a out = { 0 };
	struct scmi_msg msg = {
		.protocol_id = SCMI_PROTOCOL_ID_SENSOR,
		.message_id = SCMI_SENSOR_READING_GET,
		.in_msg = (u8 *)&in,
		.in_msg_sz = sizeof(in),
		.out_msg = (u8 *)&out,
		.out_msg_sz = sizeof(out),
	};
	int ret;

	ret = devm_scmi_process_msg(dev, priv->channel, &msg);
	if (ret)
		return ret;

	ret = scmi_to_linux_errno(out.status);
	if (ret < 0)
		return ret;

	*temp = out.val.value_low;

	return ret;
}

static int scmi_sensor_attributes_get(struct udevice *dev)
{
	struct scmi_protocol_attributes_p2a_sensor out = { 0 };
	struct scmi_msg msg = {
		.protocol_id = SCMI_PROTOCOL_ID_SENSOR,
		.message_id = SCMI_PROTOCOL_ATTRIBUTES,
		.in_msg = NULL,
		.in_msg_sz = 0,
		.out_msg = (u8 *)&out,
		.out_msg_sz = sizeof(out),
	};
	int ret;
	struct scmi_thermal_priv *priv = dev_get_priv(dev);

	ret = devm_scmi_process_msg(dev, priv->channel, &msg);
	if (ret)
		return ret;

	ret = scmi_to_linux_errno(out.status);
	if (ret < 0)
		return ret;

	priv->num_sensors = out.num_sensors;

	dev_dbg(dev,"num_sensors %d\n", priv->num_sensors);

	return 0;
}

static int scmi_sensor_description_get(struct udevice *dev, u32 start_ind,
	struct scmi_sensor_descrition_get_p2a *desc_buf, size_t desc_buf_size)
{
	struct scmi_sensor_description_get_a2p in = { start_ind };
	struct scmi_msg msg = {
		.protocol_id = SCMI_PROTOCOL_ID_SENSOR,
		.message_id = SCMI_SENSOR_DESCRIPTION_GET,
		.in_msg = (u8 *)&in,
		.in_msg_sz = sizeof(in),
		.out_msg = (u8 *)desc_buf,
		.out_msg_sz = desc_buf_size,
	};
	int ret;
	struct scmi_thermal_priv *priv = dev_get_priv(dev);

	ret = devm_scmi_process_msg(dev, priv->channel, &msg);
	if (ret)
		return ret;

	ret = scmi_to_linux_errno(desc_buf->status);
	if (ret < 0)
		return ret;

	return ret;
}

static const struct dm_thermal_ops scmi_thermal_ops = {
	.get_temp	= scmi_thermal_get_temp,
};

static int scmi_sensor_config_get(struct udevice *dev)
{
	struct scmi_thermal_priv *priv = dev_get_priv(dev);
	struct scmi_sensor_config_get_a2p in = {
		.sensor_id = priv->thermal_id,
	};
	struct scmi_sensor_config_get_p2a out = { 0 };
	struct scmi_msg msg = SCMI_MSG_IN(SCMI_PROTOCOL_ID_SENSOR,
					  SCMI_SENSOR_CONFIG_GET, in, out);
	int ret;

	ret = devm_scmi_process_msg(dev, priv->channel, &msg);
	if (ret)
		return ret;

	ret = scmi_to_linux_errno(out.status);
	if (ret < 0)
		return ret;

	return out.sensor_config;
}

static int scmi_sensor_config_set(struct udevice *dev, u32 config)
{
	struct scmi_thermal_priv *priv = dev_get_priv(dev);
	struct scmi_sensor_config_set_a2p in = {
		.id = priv->thermal_id,
		.sensor_config = config,
	};
	struct scmi_sensor_config_set_p2a out = { 0 };
	struct scmi_msg msg = SCMI_MSG_IN(SCMI_PROTOCOL_ID_SENSOR,
					  SCMI_SENSOR_CONFIG_SET, in, out);
	int ret;

	ret = devm_scmi_process_msg(dev, priv->channel, &msg);
	if (ret)
		return ret;

	ret = scmi_to_linux_errno(*(u32 *)out.buf);
	if (ret < 0)
		return ret;

	return 0;
}

static int scmi_thermal_probe(struct udevice *dev)
{
	int ret;
	u16 num_returned, num_remaining, cnt;
	u32 index = 0;
	bool find = false;
	struct scmi_sensor_desc *desc;
	struct scmi_thermal_priv *priv = dev_get_priv(dev);

	ret = devm_scmi_of_get_channel(dev, &priv->channel);
	if (ret)
		return ret;

	ret = scmi_sensor_attributes_get(dev);
	if (ret){
		dev_err(dev, "scmi_sensor_attributes_get failure %d\n", ret);
		return ret;
	}

	priv->desc_buf_size = sizeof(struct scmi_sensor_descrition_get_p2a) +
		(priv->num_sensors - 1) * sizeof(struct scmi_sensor_desc);
	priv->desc_buf = (struct scmi_sensor_descrition_get_p2a *)calloc(1, priv->desc_buf_size);
	if (!priv->desc_buf) {
		dev_err(dev, "allocate desc_buffer failure\n");
		return -ENOMEM;
	}

	do {
		ret = scmi_sensor_description_get(dev, index, priv->desc_buf, priv->desc_buf_size);
		if (ret){
			dev_err(dev, "scmi_sensor_description_get failure %d\n", ret);
			return ret;
		}

		num_returned = priv->desc_buf->num_sensor_flags & 0xffff;
		num_remaining = (priv->desc_buf->num_sensor_flags >> 16) & 0xffff;

		if (index + num_returned > priv->num_sensors) {
			dev_err(dev, "Num of sensors can't exceed %d",
				priv->num_sensors);
			return -EINVAL;
		}

		for (cnt = 0; cnt < num_returned; cnt++) {
			desc = &priv->desc_buf->desc[cnt];
			if ((desc->attr_high & 0xff) == 0x2) {
				priv->thermal_id = desc->id; /* Only get one thermal sensor */
				dev_dbg(dev, "thermal id %u\n", priv->thermal_id);
				find = true;
				break;
			}
		}

		index += num_returned;
	} while (num_returned && num_remaining && !find);

	if (!find) {
		dev_err(dev, "Can't find thermal sensor device\n");
		return -ENODEV;
	}

	u32 sensor_config = scmi_sensor_config_get(dev);
	if ((sensor_config & SCMI_SENS_CFG_ENABLED_MASK) == SCMI_SENS_CFG_DISABLE)
		scmi_sensor_config_set(dev, sensor_config | SCMI_SENS_CFG_ENABLE);

	return 0;
}

U_BOOT_DRIVER(scmi_thermal) = {
	.name = "scmi_thermal",
	.id = UCLASS_THERMAL,
	.ops = &scmi_thermal_ops,
	.probe = scmi_thermal_probe,
	.flags = DM_FLAG_PRE_RELOC,
	.priv_auto	= sizeof(struct scmi_thermal_priv),
};
