p8_i2c.c (5850B)
1// SPDX-License-Identifier: GPL-2.0+ 2// Copyright IBM Corp 2019 3 4#include <linux/device.h> 5#include <linux/errno.h> 6#include <linux/fsi-occ.h> 7#include <linux/i2c.h> 8#include <linux/jiffies.h> 9#include <linux/module.h> 10#include <linux/sched.h> 11#include <asm/unaligned.h> 12 13#include "common.h" 14 15#define OCC_TIMEOUT_MS 1000 16#define OCC_CMD_IN_PRG_WAIT_MS 50 17 18/* OCB (on-chip control bridge - interface to OCC) registers */ 19#define OCB_DATA1 0x6B035 20#define OCB_ADDR 0x6B070 21#define OCB_DATA3 0x6B075 22 23/* OCC SRAM address space */ 24#define OCC_SRAM_ADDR_CMD 0xFFFF6000 25#define OCC_SRAM_ADDR_RESP 0xFFFF7000 26 27#define OCC_DATA_ATTN 0x20010000 28 29struct p8_i2c_occ { 30 struct occ occ; 31 struct i2c_client *client; 32}; 33 34#define to_p8_i2c_occ(x) container_of((x), struct p8_i2c_occ, occ) 35 36static int p8_i2c_occ_getscom(struct i2c_client *client, u32 address, u8 *data) 37{ 38 ssize_t rc; 39 __be64 buf; 40 struct i2c_msg msgs[2]; 41 42 /* p8 i2c slave requires shift */ 43 address <<= 1; 44 45 msgs[0].addr = client->addr; 46 msgs[0].flags = client->flags & I2C_M_TEN; 47 msgs[0].len = sizeof(u32); 48 /* address is a scom address; bus-endian */ 49 msgs[0].buf = (char *)&address; 50 51 /* data from OCC is big-endian */ 52 msgs[1].addr = client->addr; 53 msgs[1].flags = (client->flags & I2C_M_TEN) | I2C_M_RD; 54 msgs[1].len = sizeof(u64); 55 msgs[1].buf = (char *)&buf; 56 57 rc = i2c_transfer(client->adapter, msgs, 2); 58 if (rc < 0) 59 return rc; 60 61 *(u64 *)data = be64_to_cpu(buf); 62 63 return 0; 64} 65 66static int p8_i2c_occ_putscom(struct i2c_client *client, u32 address, u8 *data) 67{ 68 u32 buf[3]; 69 ssize_t rc; 70 71 /* p8 i2c slave requires shift */ 72 address <<= 1; 73 74 /* address is bus-endian; data passed through from user as-is */ 75 buf[0] = address; 76 memcpy(&buf[1], &data[4], sizeof(u32)); 77 memcpy(&buf[2], data, sizeof(u32)); 78 79 rc = i2c_master_send(client, (const char *)buf, sizeof(buf)); 80 if (rc < 0) 81 return rc; 82 else if (rc != sizeof(buf)) 83 return -EIO; 84 85 return 0; 86} 87 88static int p8_i2c_occ_putscom_u32(struct i2c_client *client, u32 address, 89 u32 data0, u32 data1) 90{ 91 u8 buf[8]; 92 93 memcpy(buf, &data0, 4); 94 memcpy(buf + 4, &data1, 4); 95 96 return p8_i2c_occ_putscom(client, address, buf); 97} 98 99static int p8_i2c_occ_putscom_be(struct i2c_client *client, u32 address, 100 u8 *data, size_t len) 101{ 102 __be32 data0 = 0, data1 = 0; 103 104 memcpy(&data0, data, min_t(size_t, len, 4)); 105 if (len > 4) { 106 len -= 4; 107 memcpy(&data1, data + 4, min_t(size_t, len, 4)); 108 } 109 110 return p8_i2c_occ_putscom_u32(client, address, be32_to_cpu(data0), 111 be32_to_cpu(data1)); 112} 113 114static int p8_i2c_occ_send_cmd(struct occ *occ, u8 *cmd, size_t len, 115 void *resp, size_t resp_len) 116{ 117 int i, rc; 118 unsigned long start; 119 u16 data_length; 120 const unsigned long timeout = msecs_to_jiffies(OCC_TIMEOUT_MS); 121 const long wait_time = msecs_to_jiffies(OCC_CMD_IN_PRG_WAIT_MS); 122 struct p8_i2c_occ *ctx = to_p8_i2c_occ(occ); 123 struct i2c_client *client = ctx->client; 124 struct occ_response *or = (struct occ_response *)resp; 125 126 start = jiffies; 127 128 /* set sram address for command */ 129 rc = p8_i2c_occ_putscom_u32(client, OCB_ADDR, OCC_SRAM_ADDR_CMD, 0); 130 if (rc) 131 return rc; 132 133 /* write command (expected to already be BE), we need bus-endian... */ 134 rc = p8_i2c_occ_putscom_be(client, OCB_DATA3, cmd, len); 135 if (rc) 136 return rc; 137 138 /* trigger OCC attention */ 139 rc = p8_i2c_occ_putscom_u32(client, OCB_DATA1, OCC_DATA_ATTN, 0); 140 if (rc) 141 return rc; 142 143 do { 144 /* set sram address for response */ 145 rc = p8_i2c_occ_putscom_u32(client, OCB_ADDR, 146 OCC_SRAM_ADDR_RESP, 0); 147 if (rc) 148 return rc; 149 150 rc = p8_i2c_occ_getscom(client, OCB_DATA3, (u8 *)resp); 151 if (rc) 152 return rc; 153 154 /* wait for OCC */ 155 if (or->return_status == OCC_RESP_CMD_IN_PRG) { 156 rc = -EALREADY; 157 158 if (time_after(jiffies, start + timeout)) 159 break; 160 161 set_current_state(TASK_INTERRUPTIBLE); 162 schedule_timeout(wait_time); 163 } 164 } while (rc); 165 166 /* check the OCC response */ 167 switch (or->return_status) { 168 case OCC_RESP_CMD_IN_PRG: 169 rc = -ETIMEDOUT; 170 break; 171 case OCC_RESP_SUCCESS: 172 rc = 0; 173 break; 174 case OCC_RESP_CMD_INVAL: 175 case OCC_RESP_CMD_LEN_INVAL: 176 case OCC_RESP_DATA_INVAL: 177 case OCC_RESP_CHKSUM_ERR: 178 rc = -EINVAL; 179 break; 180 case OCC_RESP_INT_ERR: 181 case OCC_RESP_BAD_STATE: 182 case OCC_RESP_CRIT_EXCEPT: 183 case OCC_RESP_CRIT_INIT: 184 case OCC_RESP_CRIT_WATCHDOG: 185 case OCC_RESP_CRIT_OCB: 186 case OCC_RESP_CRIT_HW: 187 rc = -EREMOTEIO; 188 break; 189 default: 190 rc = -EPROTO; 191 } 192 193 if (rc < 0) 194 return rc; 195 196 data_length = get_unaligned_be16(&or->data_length); 197 if ((data_length + 7) > resp_len) 198 return -EMSGSIZE; 199 200 /* fetch the rest of the response data */ 201 for (i = 8; i < data_length + 7; i += 8) { 202 rc = p8_i2c_occ_getscom(client, OCB_DATA3, ((u8 *)resp) + i); 203 if (rc) 204 return rc; 205 } 206 207 return 0; 208} 209 210static int p8_i2c_occ_probe(struct i2c_client *client) 211{ 212 struct occ *occ; 213 struct p8_i2c_occ *ctx = devm_kzalloc(&client->dev, sizeof(*ctx), 214 GFP_KERNEL); 215 if (!ctx) 216 return -ENOMEM; 217 218 ctx->client = client; 219 occ = &ctx->occ; 220 occ->bus_dev = &client->dev; 221 dev_set_drvdata(&client->dev, occ); 222 223 occ->powr_sample_time_us = 250; 224 occ->poll_cmd_data = 0x10; /* P8 OCC poll data */ 225 occ->send_cmd = p8_i2c_occ_send_cmd; 226 227 return occ_setup(occ); 228} 229 230static int p8_i2c_occ_remove(struct i2c_client *client) 231{ 232 struct occ *occ = dev_get_drvdata(&client->dev); 233 234 occ_shutdown(occ); 235 236 return 0; 237} 238 239static const struct of_device_id p8_i2c_occ_of_match[] = { 240 { .compatible = "ibm,p8-occ-hwmon" }, 241 {} 242}; 243MODULE_DEVICE_TABLE(of, p8_i2c_occ_of_match); 244 245static struct i2c_driver p8_i2c_occ_driver = { 246 .class = I2C_CLASS_HWMON, 247 .driver = { 248 .name = "occ-hwmon", 249 .of_match_table = p8_i2c_occ_of_match, 250 }, 251 .probe_new = p8_i2c_occ_probe, 252 .remove = p8_i2c_occ_remove, 253}; 254 255module_i2c_driver(p8_i2c_occ_driver); 256 257MODULE_AUTHOR("Eddie James <eajames@linux.ibm.com>"); 258MODULE_DESCRIPTION("BMC P8 OCC hwmon driver"); 259MODULE_LICENSE("GPL");