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