]> Git Repo - J-linux.git/blob - drivers/mfd/cgbc-core.c
Merge tag 'vfs-6.13-rc7.fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs
[J-linux.git] / drivers / mfd / cgbc-core.c
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 <[email protected]>
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 */
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
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
97 static int cgbc_session_request(struct cgbc_device_data *cgbc)
98 {
99         unsigned 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
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
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
129 static void cgbc_command_unlock(struct cgbc_device_data *cgbc)
130 {
131         iowrite8(cgbc->session, cgbc->io_cmd + CGBC_CMD_ACCESS);
132 }
133
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 };
240
241 static int cgbc_map(struct cgbc_device_data *cgbc)
242 {
243         struct device *dev = cgbc->dev;
244         struct platform_device *pdev = to_platform_device(dev);
245         struct resource *ioport;
246
247         ioport = platform_get_resource(pdev, IORESOURCE_IO, 0);
248         if (!ioport)
249                 return -EINVAL;
250
251         cgbc->io_session = devm_ioport_map(dev, ioport->start, resource_size(ioport));
252         if (!cgbc->io_session)
253                 return -ENOMEM;
254
255         ioport = platform_get_resource(pdev, IORESOURCE_IO, 1);
256         if (!ioport)
257                 return -EINVAL;
258
259         cgbc->io_cmd = devm_ioport_map(dev, ioport->start, resource_size(ioport));
260         if (!cgbc->io_cmd)
261                 return -ENOMEM;
262
263         return 0;
264 }
265
266 static const struct resource cgbc_resources[] = {
267         {
268                 .start  = CGBC_IO_SESSION_BASE,
269                 .end    = CGBC_IO_SESSION_END,
270                 .flags  = IORESOURCE_IO,
271         },
272         {
273                 .start  = CGBC_IO_CMD_BASE,
274                 .end    = CGBC_IO_CMD_END,
275                 .flags  = IORESOURCE_IO,
276         },
277 };
278
279 static ssize_t cgbc_version_show(struct device *dev,
280                                  struct device_attribute *attr, char *buf)
281 {
282         struct cgbc_device_data *cgbc = dev_get_drvdata(dev);
283
284         return sysfs_emit(buf, "CGBCP%c%c%c\n", cgbc->version.feature, cgbc->version.major,
285                           cgbc->version.minor);
286 }
287
288 static DEVICE_ATTR_RO(cgbc_version);
289
290 static struct attribute *cgbc_attrs[] = {
291         &dev_attr_cgbc_version.attr,
292         NULL
293 };
294
295 ATTRIBUTE_GROUPS(cgbc);
296
297 static int cgbc_get_version(struct cgbc_device_data *cgbc)
298 {
299         u8 cmd = CGBC_CMD_GET_FW_REV;
300         u8 data[4];
301         int ret;
302
303         ret = cgbc_command(cgbc, &cmd, 1, &data, sizeof(data), NULL);
304         if (ret)
305                 return ret;
306
307         cgbc->version.feature = data[0];
308         cgbc->version.major = data[1];
309         cgbc->version.minor = data[2];
310
311         return 0;
312 }
313
314 static int cgbc_init_device(struct cgbc_device_data *cgbc)
315 {
316         int ret;
317
318         ret = cgbc_session_request(cgbc);
319         if (ret)
320                 return ret;
321
322         ret = cgbc_get_version(cgbc);
323         if (ret)
324                 goto release_session;
325
326         ret = mfd_add_devices(cgbc->dev, -1, cgbc_devs, ARRAY_SIZE(cgbc_devs),
327                               NULL, 0, NULL);
328         if (ret)
329                 goto release_session;
330
331         return 0;
332
333 release_session:
334         cgbc_session_release(cgbc);
335         return ret;
336 }
337
338 static int cgbc_probe(struct platform_device *pdev)
339 {
340         struct device *dev = &pdev->dev;
341         struct cgbc_device_data *cgbc;
342         int ret;
343
344         cgbc = devm_kzalloc(dev, sizeof(*cgbc), GFP_KERNEL);
345         if (!cgbc)
346                 return -ENOMEM;
347
348         cgbc->dev = dev;
349
350         ret = cgbc_map(cgbc);
351         if (ret)
352                 return ret;
353
354         mutex_init(&cgbc->lock);
355
356         platform_set_drvdata(pdev, cgbc);
357
358         return cgbc_init_device(cgbc);
359 }
360
361 static void cgbc_remove(struct platform_device *pdev)
362 {
363         struct cgbc_device_data *cgbc = platform_get_drvdata(pdev);
364
365         cgbc_session_release(cgbc);
366
367         mfd_remove_devices(&pdev->dev);
368 }
369
370 static struct platform_driver cgbc_driver = {
371         .driver         = {
372                 .name           = "cgbc",
373                 .dev_groups     = cgbc_groups,
374         },
375         .probe          = cgbc_probe,
376         .remove         = cgbc_remove,
377 };
378
379 static const struct dmi_system_id cgbc_dmi_table[] __initconst = {
380         {
381                 .ident = "SA7",
382                 .matches = {
383                         DMI_MATCH(DMI_BOARD_VENDOR, "congatec"),
384                         DMI_MATCH(DMI_BOARD_NAME, "conga-SA7"),
385                 },
386         },
387         {}
388 };
389 MODULE_DEVICE_TABLE(dmi, cgbc_dmi_table);
390
391 static int __init cgbc_init(void)
392 {
393         const struct dmi_system_id *id;
394         int ret = -ENODEV;
395
396         id = dmi_first_match(cgbc_dmi_table);
397         if (IS_ERR_OR_NULL(id))
398                 return ret;
399
400         cgbc_pdev = platform_device_register_simple("cgbc", PLATFORM_DEVID_NONE, cgbc_resources,
401                                                     ARRAY_SIZE(cgbc_resources));
402         if (IS_ERR(cgbc_pdev))
403                 return PTR_ERR(cgbc_pdev);
404
405         return platform_driver_register(&cgbc_driver);
406 }
407
408 static void __exit cgbc_exit(void)
409 {
410         platform_device_unregister(cgbc_pdev);
411         platform_driver_unregister(&cgbc_driver);
412 }
413
414 module_init(cgbc_init);
415 module_exit(cgbc_exit);
416
417 MODULE_DESCRIPTION("Congatec Board Controller Core Driver");
418 MODULE_AUTHOR("Thomas Richard <[email protected]>");
419 MODULE_LICENSE("GPL");
420 MODULE_ALIAS("platform:cgbc-core");
This page took 0.049683 seconds and 4 git commands to generate.