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
p8_i2c_occ_getscom(struct i2c_client * client,u32 address,u8 * data)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
p8_i2c_occ_putscom(struct i2c_client * client,u32 address,u8 * data)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
p8_i2c_occ_putscom_u32(struct i2c_client * client,u32 address,u32 data0,u32 data1)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
p8_i2c_occ_putscom_be(struct i2c_client * client,u32 address,u8 * data,size_t len)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
p8_i2c_occ_send_cmd(struct occ * occ,u8 * cmd,size_t len,void * resp,size_t resp_len)1141bbb2809SEddie James static int p8_i2c_occ_send_cmd(struct occ *occ, u8 *cmd, size_t len,
1151bbb2809SEddie 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;
1241bbb2809SEddie 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 */
1551bbb2809SEddie 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 */
1671bbb2809SEddie 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
1961bbb2809SEddie James data_length = get_unaligned_be16(&or->data_length);
1971bbb2809SEddie 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
p8_i2c_occ_probe(struct i2c_client * client)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
p8_i2c_occ_remove(struct i2c_client * client)230ed5c2f5fSUwe Kleine-König static void 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
2375b5513b8SEddie James static const struct of_device_id p8_i2c_occ_of_match[] = {
2385b5513b8SEddie James { .compatible = "ibm,p8-occ-hwmon" },
2395b5513b8SEddie James {}
2405b5513b8SEddie James };
2415b5513b8SEddie James MODULE_DEVICE_TABLE(of, p8_i2c_occ_of_match);
2425b5513b8SEddie James
2435b5513b8SEddie James static struct i2c_driver p8_i2c_occ_driver = {
2445b5513b8SEddie James .class = I2C_CLASS_HWMON,
2455b5513b8SEddie James .driver = {
2465b5513b8SEddie James .name = "occ-hwmon",
2475b5513b8SEddie James .of_match_table = p8_i2c_occ_of_match,
2485b5513b8SEddie James },
249*1975d167SUwe Kleine-König .probe = p8_i2c_occ_probe,
250df04ced6SEddie James .remove = p8_i2c_occ_remove,
2515b5513b8SEddie James };
2525b5513b8SEddie James
2535b5513b8SEddie James module_i2c_driver(p8_i2c_occ_driver);
2545b5513b8SEddie James
2555b5513b8SEddie James MODULE_AUTHOR("Eddie James <eajames@linux.ibm.com>");
2565b5513b8SEddie James MODULE_DESCRIPTION("BMC P8 OCC hwmon driver");
2575b5513b8SEddie James MODULE_LICENSE("GPL");
258