File Index
Files and directories
Sign in
FILE

cgbc-core.c

File preview with syntax highlighting.

linux-6.19.14/drivers/mfd/cgbc-core.c 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 <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
 56static struct platform_device *cgbc_pdev;
 57
 58/* Wait the Board Controller is ready to receive some session commands */
 59static 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
 73static 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
 97static 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
116static 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
122static 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
129static void cgbc_command_unlock(struct cgbc_device_data *cgbc)
130{
131	iowrite8(cgbc->session, cgbc->io_cmd + CGBC_CMD_ACCESS);
132}
133
134int 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
221release:
222	cgbc_command_unlock(cgbc);
223
224out:
225	mutex_unlock(&cgbc->lock);
226
227	if (status)
228		*status = istatus;
229
230	return ret;
231}
232EXPORT_SYMBOL_GPL(cgbc_command);
233
234static 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
242static 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
267static 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
280static 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
289static DEVICE_ATTR_RO(cgbc_version);
290
291static struct attribute *cgbc_attrs[] = {
292	&dev_attr_cgbc_version.attr,
293	NULL
294};
295
296ATTRIBUTE_GROUPS(cgbc);
297
298static 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
315static 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
334release_session:
335	cgbc_session_release(cgbc);
336	return ret;
337}
338
339static 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
362static 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
371static 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
380static 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};
397MODULE_DEVICE_TABLE(dmi, cgbc_dmi_table);
398
399static 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
416static void __exit cgbc_exit(void)
417{
418	platform_device_unregister(cgbc_pdev);
419	platform_driver_unregister(&cgbc_driver);
420}
421
422module_init(cgbc_init);
423module_exit(cgbc_exit);
424
425MODULE_DESCRIPTION("Congatec Board Controller Core Driver");
426MODULE_AUTHOR("Thomas Richard <thomas.richard@bootlin.com>");
427MODULE_LICENSE("GPL");
428MODULE_ALIAS("platform:cgbc-core");
~/ Read-only file preview.