GNU Linux-libre 6.8.9-gnu
[releases.git] / drivers / hwmon / occ / p8_i2c.c
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
29 struct 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
36 static 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
66 static 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
88 static 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
99 static 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
114 static 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
210 static 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
230 static 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
237 static const struct of_device_id p8_i2c_occ_of_match[] = {
238         { .compatible = "ibm,p8-occ-hwmon" },
239         {}
240 };
241 MODULE_DEVICE_TABLE(of, p8_i2c_occ_of_match);
242
243 static 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
253 module_i2c_driver(p8_i2c_occ_driver);
254
255 MODULE_AUTHOR("Eddie James <eajames@linux.ibm.com>");
256 MODULE_DESCRIPTION("BMC P8 OCC hwmon driver");
257 MODULE_LICENSE("GPL");