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