162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * sbrmi.c - hwmon driver for a SB-RMI mailbox
462306a36Sopenharmony_ci *           compliant AMD SoC device.
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * Copyright (C) 2020-2021 Advanced Micro Devices, Inc.
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <linux/delay.h>
1062306a36Sopenharmony_ci#include <linux/err.h>
1162306a36Sopenharmony_ci#include <linux/hwmon.h>
1262306a36Sopenharmony_ci#include <linux/i2c.h>
1362306a36Sopenharmony_ci#include <linux/init.h>
1462306a36Sopenharmony_ci#include <linux/module.h>
1562306a36Sopenharmony_ci#include <linux/mutex.h>
1662306a36Sopenharmony_ci#include <linux/of.h>
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci/* Do not allow setting negative power limit */
1962306a36Sopenharmony_ci#define SBRMI_PWR_MIN	0
2062306a36Sopenharmony_ci/* Mask for Status Register bit[1] */
2162306a36Sopenharmony_ci#define SW_ALERT_MASK	0x2
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci/* Software Interrupt for triggering */
2462306a36Sopenharmony_ci#define START_CMD	0x80
2562306a36Sopenharmony_ci#define TRIGGER_MAILBOX	0x01
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci/*
2862306a36Sopenharmony_ci * SB-RMI supports soft mailbox service request to MP1 (power management
2962306a36Sopenharmony_ci * firmware) through SBRMI inbound/outbound message registers.
3062306a36Sopenharmony_ci * SB-RMI message IDs
3162306a36Sopenharmony_ci */
3262306a36Sopenharmony_cienum sbrmi_msg_id {
3362306a36Sopenharmony_ci	SBRMI_READ_PKG_PWR_CONSUMPTION = 0x1,
3462306a36Sopenharmony_ci	SBRMI_WRITE_PKG_PWR_LIMIT,
3562306a36Sopenharmony_ci	SBRMI_READ_PKG_PWR_LIMIT,
3662306a36Sopenharmony_ci	SBRMI_READ_PKG_MAX_PWR_LIMIT,
3762306a36Sopenharmony_ci};
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci/* SB-RMI registers */
4062306a36Sopenharmony_cienum sbrmi_reg {
4162306a36Sopenharmony_ci	SBRMI_CTRL		= 0x01,
4262306a36Sopenharmony_ci	SBRMI_STATUS,
4362306a36Sopenharmony_ci	SBRMI_OUTBNDMSG0	= 0x30,
4462306a36Sopenharmony_ci	SBRMI_OUTBNDMSG1,
4562306a36Sopenharmony_ci	SBRMI_OUTBNDMSG2,
4662306a36Sopenharmony_ci	SBRMI_OUTBNDMSG3,
4762306a36Sopenharmony_ci	SBRMI_OUTBNDMSG4,
4862306a36Sopenharmony_ci	SBRMI_OUTBNDMSG5,
4962306a36Sopenharmony_ci	SBRMI_OUTBNDMSG6,
5062306a36Sopenharmony_ci	SBRMI_OUTBNDMSG7,
5162306a36Sopenharmony_ci	SBRMI_INBNDMSG0,
5262306a36Sopenharmony_ci	SBRMI_INBNDMSG1,
5362306a36Sopenharmony_ci	SBRMI_INBNDMSG2,
5462306a36Sopenharmony_ci	SBRMI_INBNDMSG3,
5562306a36Sopenharmony_ci	SBRMI_INBNDMSG4,
5662306a36Sopenharmony_ci	SBRMI_INBNDMSG5,
5762306a36Sopenharmony_ci	SBRMI_INBNDMSG6,
5862306a36Sopenharmony_ci	SBRMI_INBNDMSG7,
5962306a36Sopenharmony_ci	SBRMI_SW_INTERRUPT,
6062306a36Sopenharmony_ci};
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci/* Each client has this additional data */
6362306a36Sopenharmony_cistruct sbrmi_data {
6462306a36Sopenharmony_ci	struct i2c_client *client;
6562306a36Sopenharmony_ci	struct mutex lock;
6662306a36Sopenharmony_ci	u32 pwr_limit_max;
6762306a36Sopenharmony_ci};
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_cistruct sbrmi_mailbox_msg {
7062306a36Sopenharmony_ci	u8 cmd;
7162306a36Sopenharmony_ci	bool read;
7262306a36Sopenharmony_ci	u32 data_in;
7362306a36Sopenharmony_ci	u32 data_out;
7462306a36Sopenharmony_ci};
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_cistatic int sbrmi_enable_alert(struct i2c_client *client)
7762306a36Sopenharmony_ci{
7862306a36Sopenharmony_ci	int ctrl;
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	/*
8162306a36Sopenharmony_ci	 * Enable the SB-RMI Software alert status
8262306a36Sopenharmony_ci	 * by writing 0 to bit 4 of Control register(0x1)
8362306a36Sopenharmony_ci	 */
8462306a36Sopenharmony_ci	ctrl = i2c_smbus_read_byte_data(client, SBRMI_CTRL);
8562306a36Sopenharmony_ci	if (ctrl < 0)
8662306a36Sopenharmony_ci		return ctrl;
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	if (ctrl & 0x10) {
8962306a36Sopenharmony_ci		ctrl &= ~0x10;
9062306a36Sopenharmony_ci		return i2c_smbus_write_byte_data(client,
9162306a36Sopenharmony_ci						 SBRMI_CTRL, ctrl);
9262306a36Sopenharmony_ci	}
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci	return 0;
9562306a36Sopenharmony_ci}
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_cistatic int rmi_mailbox_xfer(struct sbrmi_data *data,
9862306a36Sopenharmony_ci			    struct sbrmi_mailbox_msg *msg)
9962306a36Sopenharmony_ci{
10062306a36Sopenharmony_ci	int i, ret, retry = 10;
10162306a36Sopenharmony_ci	int sw_status;
10262306a36Sopenharmony_ci	u8 byte;
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	mutex_lock(&data->lock);
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci	/* Indicate firmware a command is to be serviced */
10762306a36Sopenharmony_ci	ret = i2c_smbus_write_byte_data(data->client,
10862306a36Sopenharmony_ci					SBRMI_INBNDMSG7, START_CMD);
10962306a36Sopenharmony_ci	if (ret < 0)
11062306a36Sopenharmony_ci		goto exit_unlock;
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	/* Write the command to SBRMI::InBndMsg_inst0 */
11362306a36Sopenharmony_ci	ret = i2c_smbus_write_byte_data(data->client,
11462306a36Sopenharmony_ci					SBRMI_INBNDMSG0, msg->cmd);
11562306a36Sopenharmony_ci	if (ret < 0)
11662306a36Sopenharmony_ci		goto exit_unlock;
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	/*
11962306a36Sopenharmony_ci	 * For both read and write the initiator (BMC) writes
12062306a36Sopenharmony_ci	 * Command Data In[31:0] to SBRMI::InBndMsg_inst[4:1]
12162306a36Sopenharmony_ci	 * SBRMI_x3C(MSB):SBRMI_x39(LSB)
12262306a36Sopenharmony_ci	 */
12362306a36Sopenharmony_ci	for (i = 0; i < 4; i++) {
12462306a36Sopenharmony_ci		byte = (msg->data_in >> i * 8) & 0xff;
12562306a36Sopenharmony_ci		ret = i2c_smbus_write_byte_data(data->client,
12662306a36Sopenharmony_ci						SBRMI_INBNDMSG1 + i, byte);
12762306a36Sopenharmony_ci		if (ret < 0)
12862306a36Sopenharmony_ci			goto exit_unlock;
12962306a36Sopenharmony_ci	}
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci	/*
13262306a36Sopenharmony_ci	 * Write 0x01 to SBRMI::SoftwareInterrupt to notify firmware to
13362306a36Sopenharmony_ci	 * perform the requested read or write command
13462306a36Sopenharmony_ci	 */
13562306a36Sopenharmony_ci	ret = i2c_smbus_write_byte_data(data->client,
13662306a36Sopenharmony_ci					SBRMI_SW_INTERRUPT, TRIGGER_MAILBOX);
13762306a36Sopenharmony_ci	if (ret < 0)
13862306a36Sopenharmony_ci		goto exit_unlock;
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	/*
14162306a36Sopenharmony_ci	 * Firmware will write SBRMI::Status[SwAlertSts]=1 to generate
14262306a36Sopenharmony_ci	 * an ALERT (if enabled) to initiator (BMC) to indicate completion
14362306a36Sopenharmony_ci	 * of the requested command
14462306a36Sopenharmony_ci	 */
14562306a36Sopenharmony_ci	do {
14662306a36Sopenharmony_ci		sw_status = i2c_smbus_read_byte_data(data->client,
14762306a36Sopenharmony_ci						     SBRMI_STATUS);
14862306a36Sopenharmony_ci		if (sw_status < 0) {
14962306a36Sopenharmony_ci			ret = sw_status;
15062306a36Sopenharmony_ci			goto exit_unlock;
15162306a36Sopenharmony_ci		}
15262306a36Sopenharmony_ci		if (sw_status & SW_ALERT_MASK)
15362306a36Sopenharmony_ci			break;
15462306a36Sopenharmony_ci		usleep_range(50, 100);
15562306a36Sopenharmony_ci	} while (retry--);
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci	if (retry < 0) {
15862306a36Sopenharmony_ci		dev_err(&data->client->dev,
15962306a36Sopenharmony_ci			"Firmware fail to indicate command completion\n");
16062306a36Sopenharmony_ci		ret = -EIO;
16162306a36Sopenharmony_ci		goto exit_unlock;
16262306a36Sopenharmony_ci	}
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	/*
16562306a36Sopenharmony_ci	 * For a read operation, the initiator (BMC) reads the firmware
16662306a36Sopenharmony_ci	 * response Command Data Out[31:0] from SBRMI::OutBndMsg_inst[4:1]
16762306a36Sopenharmony_ci	 * {SBRMI_x34(MSB):SBRMI_x31(LSB)}.
16862306a36Sopenharmony_ci	 */
16962306a36Sopenharmony_ci	if (msg->read) {
17062306a36Sopenharmony_ci		for (i = 0; i < 4; i++) {
17162306a36Sopenharmony_ci			ret = i2c_smbus_read_byte_data(data->client,
17262306a36Sopenharmony_ci						       SBRMI_OUTBNDMSG1 + i);
17362306a36Sopenharmony_ci			if (ret < 0)
17462306a36Sopenharmony_ci				goto exit_unlock;
17562306a36Sopenharmony_ci			msg->data_out |= ret << i * 8;
17662306a36Sopenharmony_ci		}
17762306a36Sopenharmony_ci	}
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci	/*
18062306a36Sopenharmony_ci	 * BMC must write 1'b1 to SBRMI::Status[SwAlertSts] to clear the
18162306a36Sopenharmony_ci	 * ALERT to initiator
18262306a36Sopenharmony_ci	 */
18362306a36Sopenharmony_ci	ret = i2c_smbus_write_byte_data(data->client, SBRMI_STATUS,
18462306a36Sopenharmony_ci					sw_status | SW_ALERT_MASK);
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ciexit_unlock:
18762306a36Sopenharmony_ci	mutex_unlock(&data->lock);
18862306a36Sopenharmony_ci	return ret;
18962306a36Sopenharmony_ci}
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_cistatic int sbrmi_read(struct device *dev, enum hwmon_sensor_types type,
19262306a36Sopenharmony_ci		      u32 attr, int channel, long *val)
19362306a36Sopenharmony_ci{
19462306a36Sopenharmony_ci	struct sbrmi_data *data = dev_get_drvdata(dev);
19562306a36Sopenharmony_ci	struct sbrmi_mailbox_msg msg = { 0 };
19662306a36Sopenharmony_ci	int ret;
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_ci	if (type != hwmon_power)
19962306a36Sopenharmony_ci		return -EINVAL;
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_ci	msg.read = true;
20262306a36Sopenharmony_ci	switch (attr) {
20362306a36Sopenharmony_ci	case hwmon_power_input:
20462306a36Sopenharmony_ci		msg.cmd = SBRMI_READ_PKG_PWR_CONSUMPTION;
20562306a36Sopenharmony_ci		ret = rmi_mailbox_xfer(data, &msg);
20662306a36Sopenharmony_ci		break;
20762306a36Sopenharmony_ci	case hwmon_power_cap:
20862306a36Sopenharmony_ci		msg.cmd = SBRMI_READ_PKG_PWR_LIMIT;
20962306a36Sopenharmony_ci		ret = rmi_mailbox_xfer(data, &msg);
21062306a36Sopenharmony_ci		break;
21162306a36Sopenharmony_ci	case hwmon_power_cap_max:
21262306a36Sopenharmony_ci		msg.data_out = data->pwr_limit_max;
21362306a36Sopenharmony_ci		ret = 0;
21462306a36Sopenharmony_ci		break;
21562306a36Sopenharmony_ci	default:
21662306a36Sopenharmony_ci		return -EINVAL;
21762306a36Sopenharmony_ci	}
21862306a36Sopenharmony_ci	if (ret < 0)
21962306a36Sopenharmony_ci		return ret;
22062306a36Sopenharmony_ci	/* hwmon power attributes are in microWatt */
22162306a36Sopenharmony_ci	*val = (long)msg.data_out * 1000;
22262306a36Sopenharmony_ci	return ret;
22362306a36Sopenharmony_ci}
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_cistatic int sbrmi_write(struct device *dev, enum hwmon_sensor_types type,
22662306a36Sopenharmony_ci		       u32 attr, int channel, long val)
22762306a36Sopenharmony_ci{
22862306a36Sopenharmony_ci	struct sbrmi_data *data = dev_get_drvdata(dev);
22962306a36Sopenharmony_ci	struct sbrmi_mailbox_msg msg = { 0 };
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_ci	if (type != hwmon_power && attr != hwmon_power_cap)
23262306a36Sopenharmony_ci		return -EINVAL;
23362306a36Sopenharmony_ci	/*
23462306a36Sopenharmony_ci	 * hwmon power attributes are in microWatt
23562306a36Sopenharmony_ci	 * mailbox read/write is in mWatt
23662306a36Sopenharmony_ci	 */
23762306a36Sopenharmony_ci	val /= 1000;
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_ci	val = clamp_val(val, SBRMI_PWR_MIN, data->pwr_limit_max);
24062306a36Sopenharmony_ci
24162306a36Sopenharmony_ci	msg.cmd = SBRMI_WRITE_PKG_PWR_LIMIT;
24262306a36Sopenharmony_ci	msg.data_in = val;
24362306a36Sopenharmony_ci	msg.read = false;
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci	return rmi_mailbox_xfer(data, &msg);
24662306a36Sopenharmony_ci}
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_cistatic umode_t sbrmi_is_visible(const void *data,
24962306a36Sopenharmony_ci				enum hwmon_sensor_types type,
25062306a36Sopenharmony_ci				u32 attr, int channel)
25162306a36Sopenharmony_ci{
25262306a36Sopenharmony_ci	switch (type) {
25362306a36Sopenharmony_ci	case hwmon_power:
25462306a36Sopenharmony_ci		switch (attr) {
25562306a36Sopenharmony_ci		case hwmon_power_input:
25662306a36Sopenharmony_ci		case hwmon_power_cap_max:
25762306a36Sopenharmony_ci			return 0444;
25862306a36Sopenharmony_ci		case hwmon_power_cap:
25962306a36Sopenharmony_ci			return 0644;
26062306a36Sopenharmony_ci		}
26162306a36Sopenharmony_ci		break;
26262306a36Sopenharmony_ci	default:
26362306a36Sopenharmony_ci		break;
26462306a36Sopenharmony_ci	}
26562306a36Sopenharmony_ci	return 0;
26662306a36Sopenharmony_ci}
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_cistatic const struct hwmon_channel_info * const sbrmi_info[] = {
26962306a36Sopenharmony_ci	HWMON_CHANNEL_INFO(power,
27062306a36Sopenharmony_ci			   HWMON_P_INPUT | HWMON_P_CAP | HWMON_P_CAP_MAX),
27162306a36Sopenharmony_ci	NULL
27262306a36Sopenharmony_ci};
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_cistatic const struct hwmon_ops sbrmi_hwmon_ops = {
27562306a36Sopenharmony_ci	.is_visible = sbrmi_is_visible,
27662306a36Sopenharmony_ci	.read = sbrmi_read,
27762306a36Sopenharmony_ci	.write = sbrmi_write,
27862306a36Sopenharmony_ci};
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_cistatic const struct hwmon_chip_info sbrmi_chip_info = {
28162306a36Sopenharmony_ci	.ops = &sbrmi_hwmon_ops,
28262306a36Sopenharmony_ci	.info = sbrmi_info,
28362306a36Sopenharmony_ci};
28462306a36Sopenharmony_ci
28562306a36Sopenharmony_cistatic int sbrmi_get_max_pwr_limit(struct sbrmi_data *data)
28662306a36Sopenharmony_ci{
28762306a36Sopenharmony_ci	struct sbrmi_mailbox_msg msg = { 0 };
28862306a36Sopenharmony_ci	int ret;
28962306a36Sopenharmony_ci
29062306a36Sopenharmony_ci	msg.cmd = SBRMI_READ_PKG_MAX_PWR_LIMIT;
29162306a36Sopenharmony_ci	msg.read = true;
29262306a36Sopenharmony_ci	ret = rmi_mailbox_xfer(data, &msg);
29362306a36Sopenharmony_ci	if (ret < 0)
29462306a36Sopenharmony_ci		return ret;
29562306a36Sopenharmony_ci	data->pwr_limit_max = msg.data_out;
29662306a36Sopenharmony_ci
29762306a36Sopenharmony_ci	return ret;
29862306a36Sopenharmony_ci}
29962306a36Sopenharmony_ci
30062306a36Sopenharmony_cistatic int sbrmi_probe(struct i2c_client *client)
30162306a36Sopenharmony_ci{
30262306a36Sopenharmony_ci	struct device *dev = &client->dev;
30362306a36Sopenharmony_ci	struct device *hwmon_dev;
30462306a36Sopenharmony_ci	struct sbrmi_data *data;
30562306a36Sopenharmony_ci	int ret;
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_ci	data = devm_kzalloc(dev, sizeof(struct sbrmi_data), GFP_KERNEL);
30862306a36Sopenharmony_ci	if (!data)
30962306a36Sopenharmony_ci		return -ENOMEM;
31062306a36Sopenharmony_ci
31162306a36Sopenharmony_ci	data->client = client;
31262306a36Sopenharmony_ci	mutex_init(&data->lock);
31362306a36Sopenharmony_ci
31462306a36Sopenharmony_ci	/* Enable alert for SB-RMI sequence */
31562306a36Sopenharmony_ci	ret = sbrmi_enable_alert(client);
31662306a36Sopenharmony_ci	if (ret < 0)
31762306a36Sopenharmony_ci		return ret;
31862306a36Sopenharmony_ci
31962306a36Sopenharmony_ci	/* Cache maximum power limit */
32062306a36Sopenharmony_ci	ret = sbrmi_get_max_pwr_limit(data);
32162306a36Sopenharmony_ci	if (ret < 0)
32262306a36Sopenharmony_ci		return ret;
32362306a36Sopenharmony_ci
32462306a36Sopenharmony_ci	hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data,
32562306a36Sopenharmony_ci							 &sbrmi_chip_info, NULL);
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_ci	return PTR_ERR_OR_ZERO(hwmon_dev);
32862306a36Sopenharmony_ci}
32962306a36Sopenharmony_ci
33062306a36Sopenharmony_cistatic const struct i2c_device_id sbrmi_id[] = {
33162306a36Sopenharmony_ci	{"sbrmi", 0},
33262306a36Sopenharmony_ci	{}
33362306a36Sopenharmony_ci};
33462306a36Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, sbrmi_id);
33562306a36Sopenharmony_ci
33662306a36Sopenharmony_cistatic const struct of_device_id __maybe_unused sbrmi_of_match[] = {
33762306a36Sopenharmony_ci	{
33862306a36Sopenharmony_ci		.compatible = "amd,sbrmi",
33962306a36Sopenharmony_ci	},
34062306a36Sopenharmony_ci	{ },
34162306a36Sopenharmony_ci};
34262306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, sbrmi_of_match);
34362306a36Sopenharmony_ci
34462306a36Sopenharmony_cistatic struct i2c_driver sbrmi_driver = {
34562306a36Sopenharmony_ci	.class = I2C_CLASS_HWMON,
34662306a36Sopenharmony_ci	.driver = {
34762306a36Sopenharmony_ci		.name = "sbrmi",
34862306a36Sopenharmony_ci		.of_match_table = of_match_ptr(sbrmi_of_match),
34962306a36Sopenharmony_ci	},
35062306a36Sopenharmony_ci	.probe = sbrmi_probe,
35162306a36Sopenharmony_ci	.id_table = sbrmi_id,
35262306a36Sopenharmony_ci};
35362306a36Sopenharmony_ci
35462306a36Sopenharmony_cimodule_i2c_driver(sbrmi_driver);
35562306a36Sopenharmony_ci
35662306a36Sopenharmony_ciMODULE_AUTHOR("Akshay Gupta <akshay.gupta@amd.com>");
35762306a36Sopenharmony_ciMODULE_DESCRIPTION("Hwmon driver for AMD SB-RMI emulated sensor");
35862306a36Sopenharmony_ciMODULE_LICENSE("GPL");
359