xref: /linux/drivers/hwmon/occ/p8_i2c.c (revision 1bbb2809040a1f9c7c53c9f06c21aa83275ed27b)
1e2f05d60SEddie James // SPDX-License-Identifier: GPL-2.0+
2e2f05d60SEddie James // Copyright IBM Corp 2019
35b5513b8SEddie James 
45b5513b8SEddie James #include <linux/device.h>
55b5513b8SEddie James #include <linux/errno.h>
688be37c0SEddie James #include <linux/fsi-occ.h>
75b5513b8SEddie James #include <linux/i2c.h>
888be37c0SEddie James #include <linux/jiffies.h>
95b5513b8SEddie James #include <linux/module.h>
1088be37c0SEddie James #include <linux/sched.h>
1188be37c0SEddie James #include <asm/unaligned.h>
125b5513b8SEddie James 
135b5513b8SEddie James #include "common.h"
145b5513b8SEddie James 
1588be37c0SEddie James #define OCC_TIMEOUT_MS			1000
1688be37c0SEddie James #define OCC_CMD_IN_PRG_WAIT_MS		50
1788be37c0SEddie James 
1888be37c0SEddie James /* OCB (on-chip control bridge - interface to OCC) registers */
1988be37c0SEddie James #define OCB_DATA1			0x6B035
2088be37c0SEddie James #define OCB_ADDR			0x6B070
2188be37c0SEddie James #define OCB_DATA3			0x6B075
2288be37c0SEddie James 
2388be37c0SEddie James /* OCC SRAM address space */
2488be37c0SEddie James #define OCC_SRAM_ADDR_CMD		0xFFFF6000
2588be37c0SEddie James #define OCC_SRAM_ADDR_RESP		0xFFFF7000
2688be37c0SEddie James 
2788be37c0SEddie James #define OCC_DATA_ATTN			0x20010000
2888be37c0SEddie James 
295b5513b8SEddie James struct p8_i2c_occ {
305b5513b8SEddie James 	struct occ occ;
315b5513b8SEddie James 	struct i2c_client *client;
325b5513b8SEddie James };
335b5513b8SEddie James 
345b5513b8SEddie James #define to_p8_i2c_occ(x)	container_of((x), struct p8_i2c_occ, occ)
355b5513b8SEddie James 
3688be37c0SEddie James static int p8_i2c_occ_getscom(struct i2c_client *client, u32 address, u8 *data)
3788be37c0SEddie James {
3888be37c0SEddie James 	ssize_t rc;
3988be37c0SEddie James 	__be64 buf;
4088be37c0SEddie James 	struct i2c_msg msgs[2];
4188be37c0SEddie James 
4288be37c0SEddie James 	/* p8 i2c slave requires shift */
4388be37c0SEddie James 	address <<= 1;
4488be37c0SEddie James 
4588be37c0SEddie James 	msgs[0].addr = client->addr;
4688be37c0SEddie James 	msgs[0].flags = client->flags & I2C_M_TEN;
4788be37c0SEddie James 	msgs[0].len = sizeof(u32);
4888be37c0SEddie James 	/* address is a scom address; bus-endian */
4988be37c0SEddie James 	msgs[0].buf = (char *)&address;
5088be37c0SEddie James 
5188be37c0SEddie James 	/* data from OCC is big-endian */
5288be37c0SEddie James 	msgs[1].addr = client->addr;
5388be37c0SEddie James 	msgs[1].flags = (client->flags & I2C_M_TEN) | I2C_M_RD;
5488be37c0SEddie James 	msgs[1].len = sizeof(u64);
5588be37c0SEddie James 	msgs[1].buf = (char *)&buf;
5688be37c0SEddie James 
5788be37c0SEddie James 	rc = i2c_transfer(client->adapter, msgs, 2);
5888be37c0SEddie James 	if (rc < 0)
5988be37c0SEddie James 		return rc;
6088be37c0SEddie James 
6188be37c0SEddie James 	*(u64 *)data = be64_to_cpu(buf);
6288be37c0SEddie James 
6388be37c0SEddie James 	return 0;
6488be37c0SEddie James }
6588be37c0SEddie James 
6688be37c0SEddie James static int p8_i2c_occ_putscom(struct i2c_client *client, u32 address, u8 *data)
6788be37c0SEddie James {
6888be37c0SEddie James 	u32 buf[3];
6988be37c0SEddie James 	ssize_t rc;
7088be37c0SEddie James 
7188be37c0SEddie James 	/* p8 i2c slave requires shift */
7288be37c0SEddie James 	address <<= 1;
7388be37c0SEddie James 
7488be37c0SEddie James 	/* address is bus-endian; data passed through from user as-is */
7588be37c0SEddie James 	buf[0] = address;
7688be37c0SEddie James 	memcpy(&buf[1], &data[4], sizeof(u32));
7788be37c0SEddie James 	memcpy(&buf[2], data, sizeof(u32));
7888be37c0SEddie James 
7988be37c0SEddie James 	rc = i2c_master_send(client, (const char *)buf, sizeof(buf));
8088be37c0SEddie James 	if (rc < 0)
8188be37c0SEddie James 		return rc;
8288be37c0SEddie James 	else if (rc != sizeof(buf))
8388be37c0SEddie James 		return -EIO;
8488be37c0SEddie James 
8588be37c0SEddie James 	return 0;
8688be37c0SEddie James }
8788be37c0SEddie James 
8888be37c0SEddie James static int p8_i2c_occ_putscom_u32(struct i2c_client *client, u32 address,
8988be37c0SEddie James 				  u32 data0, u32 data1)
9088be37c0SEddie James {
9188be37c0SEddie James 	u8 buf[8];
9288be37c0SEddie James 
9388be37c0SEddie James 	memcpy(buf, &data0, 4);
9488be37c0SEddie James 	memcpy(buf + 4, &data1, 4);
9588be37c0SEddie James 
9688be37c0SEddie James 	return p8_i2c_occ_putscom(client, address, buf);
9788be37c0SEddie James }
9888be37c0SEddie James 
9988be37c0SEddie James static int p8_i2c_occ_putscom_be(struct i2c_client *client, u32 address,
100908dbf02SEddie James 				 u8 *data, size_t len)
10188be37c0SEddie James {
102908dbf02SEddie James 	__be32 data0 = 0, data1 = 0;
10388be37c0SEddie James 
104908dbf02SEddie James 	memcpy(&data0, data, min_t(size_t, len, 4));
105908dbf02SEddie James 	if (len > 4) {
106908dbf02SEddie James 		len -= 4;
107908dbf02SEddie James 		memcpy(&data1, data + 4, min_t(size_t, len, 4));
108908dbf02SEddie James 	}
10988be37c0SEddie James 
11088be37c0SEddie James 	return p8_i2c_occ_putscom_u32(client, address, be32_to_cpu(data0),
11188be37c0SEddie James 				      be32_to_cpu(data1));
11288be37c0SEddie James }
11388be37c0SEddie James 
114*1bbb2809SEddie James static int p8_i2c_occ_send_cmd(struct occ *occ, u8 *cmd, size_t len,
115*1bbb2809SEddie James 			       void *resp, size_t resp_len)
1165b5513b8SEddie James {
11788be37c0SEddie James 	int i, rc;
11888be37c0SEddie James 	unsigned long start;
11988be37c0SEddie James 	u16 data_length;
12088be37c0SEddie James 	const unsigned long timeout = msecs_to_jiffies(OCC_TIMEOUT_MS);
12188be37c0SEddie James 	const long wait_time = msecs_to_jiffies(OCC_CMD_IN_PRG_WAIT_MS);
12288be37c0SEddie James 	struct p8_i2c_occ *ctx = to_p8_i2c_occ(occ);
12388be37c0SEddie James 	struct i2c_client *client = ctx->client;
124*1bbb2809SEddie James 	struct occ_response *or = (struct occ_response *)resp;
12588be37c0SEddie James 
12688be37c0SEddie James 	start = jiffies;
12788be37c0SEddie James 
12888be37c0SEddie James 	/* set sram address for command */
12988be37c0SEddie James 	rc = p8_i2c_occ_putscom_u32(client, OCB_ADDR, OCC_SRAM_ADDR_CMD, 0);
13088be37c0SEddie James 	if (rc)
13188be37c0SEddie James 		return rc;
13288be37c0SEddie James 
13388be37c0SEddie James 	/* write command (expected to already be BE), we need bus-endian... */
134908dbf02SEddie James 	rc = p8_i2c_occ_putscom_be(client, OCB_DATA3, cmd, len);
13588be37c0SEddie James 	if (rc)
13688be37c0SEddie James 		return rc;
13788be37c0SEddie James 
13888be37c0SEddie James 	/* trigger OCC attention */
13988be37c0SEddie James 	rc = p8_i2c_occ_putscom_u32(client, OCB_DATA1, OCC_DATA_ATTN, 0);
14088be37c0SEddie James 	if (rc)
14188be37c0SEddie James 		return rc;
14288be37c0SEddie James 
14388be37c0SEddie James 	do {
14488be37c0SEddie James 		/* set sram address for response */
14588be37c0SEddie James 		rc = p8_i2c_occ_putscom_u32(client, OCB_ADDR,
14688be37c0SEddie James 					    OCC_SRAM_ADDR_RESP, 0);
14788be37c0SEddie James 		if (rc)
14888be37c0SEddie James 			return rc;
14988be37c0SEddie James 
15088be37c0SEddie James 		rc = p8_i2c_occ_getscom(client, OCB_DATA3, (u8 *)resp);
15188be37c0SEddie James 		if (rc)
15288be37c0SEddie James 			return rc;
15388be37c0SEddie James 
15488be37c0SEddie James 		/* wait for OCC */
155*1bbb2809SEddie James 		if (or->return_status == OCC_RESP_CMD_IN_PRG) {
15688be37c0SEddie James 			rc = -EALREADY;
15788be37c0SEddie James 
15888be37c0SEddie James 			if (time_after(jiffies, start + timeout))
15988be37c0SEddie James 				break;
16088be37c0SEddie James 
16188be37c0SEddie James 			set_current_state(TASK_INTERRUPTIBLE);
16288be37c0SEddie James 			schedule_timeout(wait_time);
16388be37c0SEddie James 		}
16488be37c0SEddie James 	} while (rc);
16588be37c0SEddie James 
16688be37c0SEddie James 	/* check the OCC response */
167*1bbb2809SEddie James 	switch (or->return_status) {
16888be37c0SEddie James 	case OCC_RESP_CMD_IN_PRG:
16988be37c0SEddie James 		rc = -ETIMEDOUT;
17088be37c0SEddie James 		break;
17188be37c0SEddie James 	case OCC_RESP_SUCCESS:
17288be37c0SEddie James 		rc = 0;
17388be37c0SEddie James 		break;
17488be37c0SEddie James 	case OCC_RESP_CMD_INVAL:
17588be37c0SEddie James 	case OCC_RESP_CMD_LEN_INVAL:
17688be37c0SEddie James 	case OCC_RESP_DATA_INVAL:
17788be37c0SEddie James 	case OCC_RESP_CHKSUM_ERR:
17888be37c0SEddie James 		rc = -EINVAL;
17988be37c0SEddie James 		break;
18088be37c0SEddie James 	case OCC_RESP_INT_ERR:
18188be37c0SEddie James 	case OCC_RESP_BAD_STATE:
18288be37c0SEddie James 	case OCC_RESP_CRIT_EXCEPT:
18388be37c0SEddie James 	case OCC_RESP_CRIT_INIT:
18488be37c0SEddie James 	case OCC_RESP_CRIT_WATCHDOG:
18588be37c0SEddie James 	case OCC_RESP_CRIT_OCB:
18688be37c0SEddie James 	case OCC_RESP_CRIT_HW:
18788be37c0SEddie James 		rc = -EREMOTEIO;
18888be37c0SEddie James 		break;
18988be37c0SEddie James 	default:
19088be37c0SEddie James 		rc = -EPROTO;
19188be37c0SEddie James 	}
19288be37c0SEddie James 
19388be37c0SEddie James 	if (rc < 0)
19488be37c0SEddie James 		return rc;
19588be37c0SEddie James 
196*1bbb2809SEddie James 	data_length = get_unaligned_be16(&or->data_length);
197*1bbb2809SEddie James 	if ((data_length + 7) > resp_len)
19888be37c0SEddie James 		return -EMSGSIZE;
19988be37c0SEddie James 
20088be37c0SEddie James 	/* fetch the rest of the response data */
20188be37c0SEddie James 	for (i = 8; i < data_length + 7; i += 8) {
20288be37c0SEddie James 		rc = p8_i2c_occ_getscom(client, OCB_DATA3, ((u8 *)resp) + i);
20388be37c0SEddie James 		if (rc)
20488be37c0SEddie James 			return rc;
20588be37c0SEddie James 	}
20688be37c0SEddie James 
20788be37c0SEddie James 	return 0;
2085b5513b8SEddie James }
2095b5513b8SEddie James 
21067487038SStephen Kitt static int p8_i2c_occ_probe(struct i2c_client *client)
2115b5513b8SEddie James {
2125b5513b8SEddie James 	struct occ *occ;
2135b5513b8SEddie James 	struct p8_i2c_occ *ctx = devm_kzalloc(&client->dev, sizeof(*ctx),
2145b5513b8SEddie James 					      GFP_KERNEL);
2155b5513b8SEddie James 	if (!ctx)
2165b5513b8SEddie James 		return -ENOMEM;
2175b5513b8SEddie James 
2185b5513b8SEddie James 	ctx->client = client;
2195b5513b8SEddie James 	occ = &ctx->occ;
2205b5513b8SEddie James 	occ->bus_dev = &client->dev;
2215b5513b8SEddie James 	dev_set_drvdata(&client->dev, occ);
2225b5513b8SEddie James 
223c10e753dSEddie James 	occ->powr_sample_time_us = 250;
2245b5513b8SEddie James 	occ->poll_cmd_data = 0x10;		/* P8 OCC poll data */
2255b5513b8SEddie James 	occ->send_cmd = p8_i2c_occ_send_cmd;
2265b5513b8SEddie James 
227849b0156SEddie James 	return occ_setup(occ);
2285b5513b8SEddie James }
2295b5513b8SEddie James 
230df04ced6SEddie James static int p8_i2c_occ_remove(struct i2c_client *client)
231df04ced6SEddie James {
232df04ced6SEddie James 	struct occ *occ = dev_get_drvdata(&client->dev);
233df04ced6SEddie James 
234df04ced6SEddie James 	occ_shutdown(occ);
235df04ced6SEddie James 
236df04ced6SEddie James 	return 0;
237df04ced6SEddie James }
238df04ced6SEddie James 
2395b5513b8SEddie James static const struct of_device_id p8_i2c_occ_of_match[] = {
2405b5513b8SEddie James 	{ .compatible = "ibm,p8-occ-hwmon" },
2415b5513b8SEddie James 	{}
2425b5513b8SEddie James };
2435b5513b8SEddie James MODULE_DEVICE_TABLE(of, p8_i2c_occ_of_match);
2445b5513b8SEddie James 
2455b5513b8SEddie James static struct i2c_driver p8_i2c_occ_driver = {
2465b5513b8SEddie James 	.class = I2C_CLASS_HWMON,
2475b5513b8SEddie James 	.driver = {
2485b5513b8SEddie James 		.name = "occ-hwmon",
2495b5513b8SEddie James 		.of_match_table = p8_i2c_occ_of_match,
2505b5513b8SEddie James 	},
25167487038SStephen Kitt 	.probe_new = p8_i2c_occ_probe,
252df04ced6SEddie James 	.remove = p8_i2c_occ_remove,
2535b5513b8SEddie James };
2545b5513b8SEddie James 
2555b5513b8SEddie James module_i2c_driver(p8_i2c_occ_driver);
2565b5513b8SEddie James 
2575b5513b8SEddie James MODULE_AUTHOR("Eddie James <eajames@linux.ibm.com>");
2585b5513b8SEddie James MODULE_DESCRIPTION("BMC P8 OCC hwmon driver");
2595b5513b8SEddie James MODULE_LICENSE("GPL");
260