1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Congatec Board Controller core driver.
4 *
5 * The x86 Congatec modules have an embedded micro controller named Board
6 * Controller. This Board Controller has a Watchdog timer, some GPIOs, and two
7 * I2C busses.
8 *
9 * Copyright (C) 2024 Bootlin
10 *
11 * Author: Thomas Richard <thomas.richard@bootlin.com>
12 */
13
14 #include <linux/dmi.h>
15 #include <linux/iopoll.h>
16 #include <linux/mfd/cgbc.h>
17 #include <linux/mfd/core.h>
18 #include <linux/module.h>
19 #include <linux/platform_device.h>
20 #include <linux/sysfs.h>
21
22 #define CGBC_IO_SESSION_BASE 0x0E20
23 #define CGBC_IO_SESSION_END 0x0E30
24 #define CGBC_IO_CMD_BASE 0x0E00
25 #define CGBC_IO_CMD_END 0x0E10
26
27 #define CGBC_MASK_STATUS (BIT(6) | BIT(7))
28 #define CGBC_MASK_DATA_COUNT 0x1F
29 #define CGBC_MASK_ERROR_CODE 0x1F
30
31 #define CGBC_STATUS_DATA_READY 0x00
32 #define CGBC_STATUS_CMD_READY BIT(6)
33 #define CGBC_STATUS_ERROR (BIT(6) | BIT(7))
34
35 #define CGBC_SESSION_CMD 0x00
36 #define CGBC_SESSION_CMD_IDLE 0x00
37 #define CGBC_SESSION_CMD_REQUEST 0x01
38 #define CGBC_SESSION_DATA 0x01
39 #define CGBC_SESSION_STATUS 0x02
40 #define CGBC_SESSION_STATUS_FREE 0x03
41 #define CGBC_SESSION_ACCESS 0x04
42 #define CGBC_SESSION_ACCESS_GAINED 0x00
43
44 #define CGBC_SESSION_VALID_MIN 0x02
45 #define CGBC_SESSION_VALID_MAX 0xFE
46
47 #define CGBC_CMD_STROBE 0x00
48 #define CGBC_CMD_INDEX 0x02
49 #define CGBC_CMD_INDEX_CBM_MAN8 0x00
50 #define CGBC_CMD_INDEX_CBM_AUTO32 0x03
51 #define CGBC_CMD_DATA 0x04
52 #define CGBC_CMD_ACCESS 0x0C
53
54 #define CGBC_CMD_GET_FW_REV 0x21
55
56 static struct platform_device *cgbc_pdev;
57
58 /* Wait the Board Controller is ready to receive some session commands */
cgbc_wait_device(struct cgbc_device_data * cgbc)59 static int cgbc_wait_device(struct cgbc_device_data *cgbc)
60 {
61 u16 status;
62 int ret;
63
64 ret = readx_poll_timeout(ioread16, cgbc->io_session + CGBC_SESSION_STATUS, status,
65 status == CGBC_SESSION_STATUS_FREE, 0, 500000);
66
67 if (ret || ioread32(cgbc->io_session + CGBC_SESSION_ACCESS))
68 ret = -ENODEV;
69
70 return ret;
71 }
72
cgbc_session_command(struct cgbc_device_data * cgbc,u8 cmd)73 static int cgbc_session_command(struct cgbc_device_data *cgbc, u8 cmd)
74 {
75 int ret;
76 u8 val;
77
78 ret = readx_poll_timeout(ioread8, cgbc->io_session + CGBC_SESSION_CMD, val,
79 val == CGBC_SESSION_CMD_IDLE, 0, 100000);
80 if (ret)
81 return ret;
82
83 iowrite8(cmd, cgbc->io_session + CGBC_SESSION_CMD);
84
85 ret = readx_poll_timeout(ioread8, cgbc->io_session + CGBC_SESSION_CMD, val,
86 val == CGBC_SESSION_CMD_IDLE, 0, 100000);
87 if (ret)
88 return ret;
89
90 ret = (int)ioread8(cgbc->io_session + CGBC_SESSION_DATA);
91
92 iowrite8(CGBC_SESSION_STATUS_FREE, cgbc->io_session + CGBC_SESSION_STATUS);
93
94 return ret;
95 }
96
cgbc_session_request(struct cgbc_device_data * cgbc)97 static int cgbc_session_request(struct cgbc_device_data *cgbc)
98 {
99 int ret;
100
101 ret = cgbc_wait_device(cgbc);
102
103 if (ret)
104 return dev_err_probe(cgbc->dev, ret, "device not found or not ready\n");
105
106 cgbc->session = cgbc_session_command(cgbc, CGBC_SESSION_CMD_REQUEST);
107
108 /* The Board Controller sent us a wrong session handle, we cannot communicate with it */
109 if (cgbc->session < CGBC_SESSION_VALID_MIN || cgbc->session > CGBC_SESSION_VALID_MAX)
110 return dev_err_probe(cgbc->dev, -ECONNREFUSED,
111 "failed to get a valid session handle\n");
112
113 return 0;
114 }
115
cgbc_session_release(struct cgbc_device_data * cgbc)116 static void cgbc_session_release(struct cgbc_device_data *cgbc)
117 {
118 if (cgbc_session_command(cgbc, cgbc->session) != cgbc->session)
119 dev_warn(cgbc->dev, "failed to release session\n");
120 }
121
cgbc_command_lock(struct cgbc_device_data * cgbc)122 static bool cgbc_command_lock(struct cgbc_device_data *cgbc)
123 {
124 iowrite8(cgbc->session, cgbc->io_cmd + CGBC_CMD_ACCESS);
125
126 return ioread8(cgbc->io_cmd + CGBC_CMD_ACCESS) == cgbc->session;
127 }
128
cgbc_command_unlock(struct cgbc_device_data * cgbc)129 static void cgbc_command_unlock(struct cgbc_device_data *cgbc)
130 {
131 iowrite8(cgbc->session, cgbc->io_cmd + CGBC_CMD_ACCESS);
132 }
133
cgbc_command(struct cgbc_device_data * cgbc,void * cmd,unsigned int cmd_size,void * data,unsigned int data_size,u8 * status)134 int cgbc_command(struct cgbc_device_data *cgbc, void *cmd, unsigned int cmd_size, void *data,
135 unsigned int data_size, u8 *status)
136 {
137 u8 checksum = 0, data_checksum = 0, istatus = 0, val;
138 u8 *_data = (u8 *)data;
139 u8 *_cmd = (u8 *)cmd;
140 int mode_change = -1;
141 bool lock;
142 int ret, i;
143
144 mutex_lock(&cgbc->lock);
145
146 /* Request access */
147 ret = readx_poll_timeout(cgbc_command_lock, cgbc, lock, lock, 0, 100000);
148 if (ret)
149 goto out;
150
151 /* Wait board controller is ready */
152 ret = readx_poll_timeout(ioread8, cgbc->io_cmd + CGBC_CMD_STROBE, val,
153 val == CGBC_CMD_STROBE, 0, 100000);
154 if (ret)
155 goto release;
156
157 /* Write command packet */
158 if (cmd_size <= 2) {
159 iowrite8(CGBC_CMD_INDEX_CBM_MAN8, cgbc->io_cmd + CGBC_CMD_INDEX);
160 } else {
161 iowrite8(CGBC_CMD_INDEX_CBM_AUTO32, cgbc->io_cmd + CGBC_CMD_INDEX);
162 if ((cmd_size % 4) != 0x03)
163 mode_change = (cmd_size & 0xFFFC) - 1;
164 }
165
166 for (i = 0; i < cmd_size; i++) {
167 iowrite8(_cmd[i], cgbc->io_cmd + CGBC_CMD_DATA + (i % 4));
168 checksum ^= _cmd[i];
169 if (mode_change == i)
170 iowrite8((i + 1) | CGBC_CMD_INDEX_CBM_MAN8, cgbc->io_cmd + CGBC_CMD_INDEX);
171 }
172
173 /* Append checksum byte */
174 iowrite8(checksum, cgbc->io_cmd + CGBC_CMD_DATA + (i % 4));
175
176 /* Perform command strobe */
177 iowrite8(cgbc->session, cgbc->io_cmd + CGBC_CMD_STROBE);
178
179 /* Rewind cmd buffer index */
180 iowrite8(CGBC_CMD_INDEX_CBM_AUTO32, cgbc->io_cmd + CGBC_CMD_INDEX);
181
182 /* Wait command completion */
183 ret = read_poll_timeout(ioread8, val, val == CGBC_CMD_STROBE, 0, 100000, false,
184 cgbc->io_cmd + CGBC_CMD_STROBE);
185 if (ret)
186 goto release;
187
188 istatus = ioread8(cgbc->io_cmd + CGBC_CMD_DATA);
189 checksum = istatus;
190
191 /* Check command status */
192 switch (istatus & CGBC_MASK_STATUS) {
193 case CGBC_STATUS_DATA_READY:
194 if (istatus > data_size)
195 istatus = data_size;
196 for (i = 0; i < istatus; i++) {
197 _data[i] = ioread8(cgbc->io_cmd + CGBC_CMD_DATA + ((i + 1) % 4));
198 checksum ^= _data[i];
199 }
200 data_checksum = ioread8(cgbc->io_cmd + CGBC_CMD_DATA + ((i + 1) % 4));
201 istatus &= CGBC_MASK_DATA_COUNT;
202 break;
203 case CGBC_STATUS_ERROR:
204 case CGBC_STATUS_CMD_READY:
205 data_checksum = ioread8(cgbc->io_cmd + CGBC_CMD_DATA + 1);
206 if ((istatus & CGBC_MASK_STATUS) == CGBC_STATUS_ERROR)
207 ret = -EIO;
208 istatus = istatus & CGBC_MASK_ERROR_CODE;
209 break;
210 default:
211 data_checksum = ioread8(cgbc->io_cmd + CGBC_CMD_DATA + 1);
212 istatus &= CGBC_MASK_ERROR_CODE;
213 ret = -EIO;
214 break;
215 }
216
217 /* Checksum verification */
218 if (ret == 0 && data_checksum != checksum)
219 ret = -EIO;
220
221 release:
222 cgbc_command_unlock(cgbc);
223
224 out:
225 mutex_unlock(&cgbc->lock);
226
227 if (status)
228 *status = istatus;
229
230 return ret;
231 }
232 EXPORT_SYMBOL_GPL(cgbc_command);
233
234 static struct mfd_cell cgbc_devs[] = {
235 { .name = "cgbc-wdt" },
236 { .name = "cgbc-gpio" },
237 { .name = "cgbc-i2c", .id = 1 },
238 { .name = "cgbc-i2c", .id = 2 },
239 { .name = "cgbc-hwmon" },
240 };
241
cgbc_map(struct cgbc_device_data * cgbc)242 static int cgbc_map(struct cgbc_device_data *cgbc)
243 {
244 struct device *dev = cgbc->dev;
245 struct platform_device *pdev = to_platform_device(dev);
246 struct resource *ioport;
247
248 ioport = platform_get_resource(pdev, IORESOURCE_IO, 0);
249 if (!ioport)
250 return -EINVAL;
251
252 cgbc->io_session = devm_ioport_map(dev, ioport->start, resource_size(ioport));
253 if (!cgbc->io_session)
254 return -ENOMEM;
255
256 ioport = platform_get_resource(pdev, IORESOURCE_IO, 1);
257 if (!ioport)
258 return -EINVAL;
259
260 cgbc->io_cmd = devm_ioport_map(dev, ioport->start, resource_size(ioport));
261 if (!cgbc->io_cmd)
262 return -ENOMEM;
263
264 return 0;
265 }
266
267 static const struct resource cgbc_resources[] = {
268 {
269 .start = CGBC_IO_SESSION_BASE,
270 .end = CGBC_IO_SESSION_END,
271 .flags = IORESOURCE_IO,
272 },
273 {
274 .start = CGBC_IO_CMD_BASE,
275 .end = CGBC_IO_CMD_END,
276 .flags = IORESOURCE_IO,
277 },
278 };
279
cgbc_version_show(struct device * dev,struct device_attribute * attr,char * buf)280 static ssize_t cgbc_version_show(struct device *dev,
281 struct device_attribute *attr, char *buf)
282 {
283 struct cgbc_device_data *cgbc = dev_get_drvdata(dev);
284
285 return sysfs_emit(buf, "CGBCP%c%c%c\n", cgbc->version.feature, cgbc->version.major,
286 cgbc->version.minor);
287 }
288
289 static DEVICE_ATTR_RO(cgbc_version);
290
291 static struct attribute *cgbc_attrs[] = {
292 &dev_attr_cgbc_version.attr,
293 NULL
294 };
295
296 ATTRIBUTE_GROUPS(cgbc);
297
cgbc_get_version(struct cgbc_device_data * cgbc)298 static int cgbc_get_version(struct cgbc_device_data *cgbc)
299 {
300 u8 cmd = CGBC_CMD_GET_FW_REV;
301 u8 data[4];
302 int ret;
303
304 ret = cgbc_command(cgbc, &cmd, 1, &data, sizeof(data), NULL);
305 if (ret)
306 return ret;
307
308 cgbc->version.feature = data[0];
309 cgbc->version.major = data[1];
310 cgbc->version.minor = data[2];
311
312 return 0;
313 }
314
cgbc_init_device(struct cgbc_device_data * cgbc)315 static int cgbc_init_device(struct cgbc_device_data *cgbc)
316 {
317 int ret;
318
319 ret = cgbc_session_request(cgbc);
320 if (ret)
321 return ret;
322
323 ret = cgbc_get_version(cgbc);
324 if (ret)
325 goto release_session;
326
327 ret = mfd_add_devices(cgbc->dev, -1, cgbc_devs, ARRAY_SIZE(cgbc_devs),
328 NULL, 0, NULL);
329 if (ret)
330 goto release_session;
331
332 return 0;
333
334 release_session:
335 cgbc_session_release(cgbc);
336 return ret;
337 }
338
cgbc_probe(struct platform_device * pdev)339 static int cgbc_probe(struct platform_device *pdev)
340 {
341 struct device *dev = &pdev->dev;
342 struct cgbc_device_data *cgbc;
343 int ret;
344
345 cgbc = devm_kzalloc(dev, sizeof(*cgbc), GFP_KERNEL);
346 if (!cgbc)
347 return -ENOMEM;
348
349 cgbc->dev = dev;
350
351 ret = cgbc_map(cgbc);
352 if (ret)
353 return ret;
354
355 mutex_init(&cgbc->lock);
356
357 platform_set_drvdata(pdev, cgbc);
358
359 return cgbc_init_device(cgbc);
360 }
361
cgbc_remove(struct platform_device * pdev)362 static void cgbc_remove(struct platform_device *pdev)
363 {
364 struct cgbc_device_data *cgbc = platform_get_drvdata(pdev);
365
366 cgbc_session_release(cgbc);
367
368 mfd_remove_devices(&pdev->dev);
369 }
370
371 static struct platform_driver cgbc_driver = {
372 .driver = {
373 .name = "cgbc",
374 .dev_groups = cgbc_groups,
375 },
376 .probe = cgbc_probe,
377 .remove = cgbc_remove,
378 };
379
380 static const struct dmi_system_id cgbc_dmi_table[] __initconst = {
381 {
382 .ident = "SA7",
383 .matches = {
384 DMI_MATCH(DMI_BOARD_VENDOR, "congatec"),
385 DMI_MATCH(DMI_BOARD_NAME, "conga-SA7"),
386 },
387 },
388 {
389 .ident = "SA8",
390 .matches = {
391 DMI_MATCH(DMI_BOARD_VENDOR, "congatec"),
392 DMI_MATCH(DMI_BOARD_NAME, "conga-SA8"),
393 },
394 },
395 {}
396 };
397 MODULE_DEVICE_TABLE(dmi, cgbc_dmi_table);
398
cgbc_init(void)399 static int __init cgbc_init(void)
400 {
401 const struct dmi_system_id *id;
402 int ret = -ENODEV;
403
404 id = dmi_first_match(cgbc_dmi_table);
405 if (IS_ERR_OR_NULL(id))
406 return ret;
407
408 cgbc_pdev = platform_device_register_simple("cgbc", PLATFORM_DEVID_NONE, cgbc_resources,
409 ARRAY_SIZE(cgbc_resources));
410 if (IS_ERR(cgbc_pdev))
411 return PTR_ERR(cgbc_pdev);
412
413 return platform_driver_register(&cgbc_driver);
414 }
415
cgbc_exit(void)416 static void __exit cgbc_exit(void)
417 {
418 platform_device_unregister(cgbc_pdev);
419 platform_driver_unregister(&cgbc_driver);
420 }
421
422 module_init(cgbc_init);
423 module_exit(cgbc_exit);
424
425 MODULE_DESCRIPTION("Congatec Board Controller Core Driver");
426 MODULE_AUTHOR("Thomas Richard <thomas.richard@bootlin.com>");
427 MODULE_LICENSE("GPL");
428 MODULE_ALIAS("platform:cgbc-core");
429