257 lines · c
1// SPDX-License-Identifier: GPL-2.0+2// Copyright IBM Corp 20193 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 <linux/unaligned.h>12 13#include "common.h"14 15#define OCC_TIMEOUT_MS 100016#define OCC_CMD_IN_PRG_WAIT_MS 5017 18/* OCB (on-chip control bridge - interface to OCC) registers */19#define OCB_DATA1 0x6B03520#define OCB_ADDR 0x6B07021#define OCB_DATA3 0x6B07522 23/* OCC SRAM address space */24#define OCC_SRAM_ADDR_CMD 0xFFFF600025#define OCC_SRAM_ADDR_RESP 0xFFFF700026 27#define OCC_DATA_ATTN 0x2001000028 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 void 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 237static const struct of_device_id p8_i2c_occ_of_match[] = {238 { .compatible = "ibm,p8-occ-hwmon" },239 {}240};241MODULE_DEVICE_TABLE(of, p8_i2c_occ_of_match);242 243static struct i2c_driver p8_i2c_occ_driver = {244 .driver = {245 .name = "occ-hwmon",246 .of_match_table = p8_i2c_occ_of_match,247 },248 .probe = p8_i2c_occ_probe,249 .remove = p8_i2c_occ_remove,250};251 252module_i2c_driver(p8_i2c_occ_driver);253 254MODULE_AUTHOR("Eddie James <eajames@linux.ibm.com>");255MODULE_DESCRIPTION("BMC P8 OCC hwmon driver");256MODULE_LICENSE("GPL");257