1 // SPDX-License-Identifier: GPL-2.0-or-later
3 * Congatec Board Controller core driver.
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
9 * Copyright (C) 2024 Bootlin
11 * Author: Thomas Richard <thomas.richard@bootlin.com>
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>
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
27 #define CGBC_MASK_STATUS (BIT(6) | BIT(7))
28 #define CGBC_MASK_DATA_COUNT 0x1F
29 #define CGBC_MASK_ERROR_CODE 0x1F
31 #define CGBC_STATUS_DATA_READY 0x00
32 #define CGBC_STATUS_CMD_READY BIT(6)
33 #define CGBC_STATUS_ERROR (BIT(6) | BIT(7))
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
44 #define CGBC_SESSION_VALID_MIN 0x02
45 #define CGBC_SESSION_VALID_MAX 0xFE
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
54 #define CGBC_CMD_GET_FW_REV 0x21
56 static struct platform_device
*cgbc_pdev
;
58 /* Wait the Board Controller is ready to receive some session commands */
59 static int cgbc_wait_device(struct cgbc_device_data
*cgbc
)
64 ret
= readx_poll_timeout(ioread16
, cgbc
->io_session
+ CGBC_SESSION_STATUS
, status
,
65 status
== CGBC_SESSION_STATUS_FREE
, 0, 500000);
67 if (ret
|| ioread32(cgbc
->io_session
+ CGBC_SESSION_ACCESS
))
73 static int cgbc_session_command(struct cgbc_device_data
*cgbc
, u8 cmd
)
78 ret
= readx_poll_timeout(ioread8
, cgbc
->io_session
+ CGBC_SESSION_CMD
, val
,
79 val
== CGBC_SESSION_CMD_IDLE
, 0, 100000);
83 iowrite8(cmd
, cgbc
->io_session
+ CGBC_SESSION_CMD
);
85 ret
= readx_poll_timeout(ioread8
, cgbc
->io_session
+ CGBC_SESSION_CMD
, val
,
86 val
== CGBC_SESSION_CMD_IDLE
, 0, 100000);
90 ret
= (int)ioread8(cgbc
->io_session
+ CGBC_SESSION_DATA
);
92 iowrite8(CGBC_SESSION_STATUS_FREE
, cgbc
->io_session
+ CGBC_SESSION_STATUS
);
97 static int cgbc_session_request(struct cgbc_device_data
*cgbc
)
101 ret
= cgbc_wait_device(cgbc
);
104 return dev_err_probe(cgbc
->dev
, ret
, "device not found or not ready\n");
106 cgbc
->session
= cgbc_session_command(cgbc
, CGBC_SESSION_CMD_REQUEST
);
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");
116 static void cgbc_session_release(struct cgbc_device_data
*cgbc
)
118 if (cgbc_session_command(cgbc
, cgbc
->session
) != cgbc
->session
)
119 dev_warn(cgbc
->dev
, "failed to release session\n");
122 static bool cgbc_command_lock(struct cgbc_device_data
*cgbc
)
124 iowrite8(cgbc
->session
, cgbc
->io_cmd
+ CGBC_CMD_ACCESS
);
126 return ioread8(cgbc
->io_cmd
+ CGBC_CMD_ACCESS
) == cgbc
->session
;
129 static void cgbc_command_unlock(struct cgbc_device_data
*cgbc
)
131 iowrite8(cgbc
->session
, cgbc
->io_cmd
+ CGBC_CMD_ACCESS
);
134 int cgbc_command(struct cgbc_device_data
*cgbc
, void *cmd
, unsigned int cmd_size
, void *data
,
135 unsigned int data_size
, u8
*status
)
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;
144 mutex_lock(&cgbc
->lock
);
147 ret
= readx_poll_timeout(cgbc_command_lock
, cgbc
, lock
, lock
, 0, 100000);
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);
157 /* Write command packet */
159 iowrite8(CGBC_CMD_INDEX_CBM_MAN8
, cgbc
->io_cmd
+ CGBC_CMD_INDEX
);
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;
166 for (i
= 0; i
< cmd_size
; i
++) {
167 iowrite8(_cmd
[i
], cgbc
->io_cmd
+ CGBC_CMD_DATA
+ (i
% 4));
169 if (mode_change
== i
)
170 iowrite8((i
+ 1) | CGBC_CMD_INDEX_CBM_MAN8
, cgbc
->io_cmd
+ CGBC_CMD_INDEX
);
173 /* Append checksum byte */
174 iowrite8(checksum
, cgbc
->io_cmd
+ CGBC_CMD_DATA
+ (i
% 4));
176 /* Perform command strobe */
177 iowrite8(cgbc
->session
, cgbc
->io_cmd
+ CGBC_CMD_STROBE
);
179 /* Rewind cmd buffer index */
180 iowrite8(CGBC_CMD_INDEX_CBM_AUTO32
, cgbc
->io_cmd
+ CGBC_CMD_INDEX
);
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
);
188 istatus
= ioread8(cgbc
->io_cmd
+ CGBC_CMD_DATA
);
191 /* Check command status */
192 switch (istatus
& CGBC_MASK_STATUS
) {
193 case CGBC_STATUS_DATA_READY
:
194 if (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
];
200 data_checksum
= ioread8(cgbc
->io_cmd
+ CGBC_CMD_DATA
+ ((i
+ 1) % 4));
201 istatus
&= CGBC_MASK_DATA_COUNT
;
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
)
208 istatus
= istatus
& CGBC_MASK_ERROR_CODE
;
211 data_checksum
= ioread8(cgbc
->io_cmd
+ CGBC_CMD_DATA
+ 1);
212 istatus
&= CGBC_MASK_ERROR_CODE
;
217 /* Checksum verification */
218 if (ret
== 0 && data_checksum
!= checksum
)
222 cgbc_command_unlock(cgbc
);
225 mutex_unlock(&cgbc
->lock
);
232 EXPORT_SYMBOL_GPL(cgbc_command
);
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 },
241 static int cgbc_map(struct cgbc_device_data
*cgbc
)
243 struct device
*dev
= cgbc
->dev
;
244 struct platform_device
*pdev
= to_platform_device(dev
);
245 struct resource
*ioport
;
247 ioport
= platform_get_resource(pdev
, IORESOURCE_IO
, 0);
251 cgbc
->io_session
= devm_ioport_map(dev
, ioport
->start
, resource_size(ioport
));
252 if (!cgbc
->io_session
)
255 ioport
= platform_get_resource(pdev
, IORESOURCE_IO
, 1);
259 cgbc
->io_cmd
= devm_ioport_map(dev
, ioport
->start
, resource_size(ioport
));
266 static const struct resource cgbc_resources
[] = {
268 .start
= CGBC_IO_SESSION_BASE
,
269 .end
= CGBC_IO_SESSION_END
,
270 .flags
= IORESOURCE_IO
,
273 .start
= CGBC_IO_CMD_BASE
,
274 .end
= CGBC_IO_CMD_END
,
275 .flags
= IORESOURCE_IO
,
279 static ssize_t
cgbc_version_show(struct device
*dev
,
280 struct device_attribute
*attr
, char *buf
)
282 struct cgbc_device_data
*cgbc
= dev_get_drvdata(dev
);
284 return sysfs_emit(buf
, "CGBCP%c%c%c\n", cgbc
->version
.feature
, cgbc
->version
.major
,
285 cgbc
->version
.minor
);
288 static DEVICE_ATTR_RO(cgbc_version
);
290 static struct attribute
*cgbc_attrs
[] = {
291 &dev_attr_cgbc_version
.attr
,
295 ATTRIBUTE_GROUPS(cgbc
);
297 static int cgbc_get_version(struct cgbc_device_data
*cgbc
)
299 u8 cmd
= CGBC_CMD_GET_FW_REV
;
303 ret
= cgbc_command(cgbc
, &cmd
, 1, &data
, sizeof(data
), NULL
);
307 cgbc
->version
.feature
= data
[0];
308 cgbc
->version
.major
= data
[1];
309 cgbc
->version
.minor
= data
[2];
314 static int cgbc_init_device(struct cgbc_device_data
*cgbc
)
318 ret
= cgbc_session_request(cgbc
);
322 ret
= cgbc_get_version(cgbc
);
324 goto release_session
;
326 ret
= mfd_add_devices(cgbc
->dev
, -1, cgbc_devs
, ARRAY_SIZE(cgbc_devs
),
329 goto release_session
;
334 cgbc_session_release(cgbc
);
338 static int cgbc_probe(struct platform_device
*pdev
)
340 struct device
*dev
= &pdev
->dev
;
341 struct cgbc_device_data
*cgbc
;
344 cgbc
= devm_kzalloc(dev
, sizeof(*cgbc
), GFP_KERNEL
);
350 ret
= cgbc_map(cgbc
);
354 mutex_init(&cgbc
->lock
);
356 platform_set_drvdata(pdev
, cgbc
);
358 return cgbc_init_device(cgbc
);
361 static void cgbc_remove(struct platform_device
*pdev
)
363 struct cgbc_device_data
*cgbc
= platform_get_drvdata(pdev
);
365 cgbc_session_release(cgbc
);
367 mfd_remove_devices(&pdev
->dev
);
370 static struct platform_driver cgbc_driver
= {
373 .dev_groups
= cgbc_groups
,
376 .remove
= cgbc_remove
,
379 static const struct dmi_system_id cgbc_dmi_table
[] __initconst
= {
383 DMI_MATCH(DMI_BOARD_VENDOR
, "congatec"),
384 DMI_MATCH(DMI_BOARD_NAME
, "conga-SA7"),
389 MODULE_DEVICE_TABLE(dmi
, cgbc_dmi_table
);
391 static int __init
cgbc_init(void)
393 const struct dmi_system_id
*id
;
396 id
= dmi_first_match(cgbc_dmi_table
);
397 if (IS_ERR_OR_NULL(id
))
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
);
405 return platform_driver_register(&cgbc_driver
);
408 static void __exit
cgbc_exit(void)
410 platform_device_unregister(cgbc_pdev
);
411 platform_driver_unregister(&cgbc_driver
);
414 module_init(cgbc_init
);
415 module_exit(cgbc_exit
);
417 MODULE_DESCRIPTION("Congatec Board Controller Core Driver");
418 MODULE_AUTHOR("Thomas Richard <thomas.richard@bootlin.com>");
419 MODULE_LICENSE("GPL");
420 MODULE_ALIAS("platform:cgbc-core");