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(adap: client->adapter, msgs, num: 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, buf: (const char *)buf, count: 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, data: 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, data1: 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, data: 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, data1: 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, data1: 0);
147 if (rc)
148 return rc;
149
150 rc = p8_i2c_occ_getscom(client, OCB_DATA3, data: (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(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(p: &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, data: ((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(dev: &client->dev, size: 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(dev: &client->dev, data: 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(dev: &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 .class = I2C_CLASS_HWMON,
245 .driver = {
246 .name = "occ-hwmon",
247 .of_match_table = p8_i2c_occ_of_match,
248 },
249 .probe = p8_i2c_occ_probe,
250 .remove = p8_i2c_occ_remove,
251};
252
253module_i2c_driver(p8_i2c_occ_driver);
254
255MODULE_AUTHOR("Eddie James <eajames@linux.ibm.com>");
256MODULE_DESCRIPTION("BMC P8 OCC hwmon driver");
257MODULE_LICENSE("GPL");
258

source code of linux/drivers/hwmon/occ/p8_i2c.c