Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

mfd: Add Congatec Board Controller driver

Add core MFD driver for the Board Controller found on some Congatec SMARC
module. This Board Controller provides functions like watchdog, GPIO, and
I2C busses.

This commit adds support only for the conga-SA7 module.

Signed-off-by: Thomas Richard <thomas.richard@bootlin.com>
Link: https://lore.kernel.org/r/20241001-congatec-board-controller-v3-1-39ceceed5c47@bootlin.com
Signed-off-by: Lee Jones <lee@kernel.org>

authored by

Thomas Richard and committed by
Lee Jones
6f1067cf 9852d85e

+468
+12
drivers/mfd/Kconfig
··· 236 236 components like regulators or the PEK (Power Enable Key) under the 237 237 corresponding menus. 238 238 239 + config MFD_CGBC 240 + tristate "Congatec Board Controller" 241 + select MFD_CORE 242 + depends on X86 243 + help 244 + This is the core driver of the Board Controller found on some Congatec 245 + SMARC modules. The Board Controller provides functions like watchdog, 246 + I2C busses, and GPIO controller. 247 + 248 + To compile this driver as a module, choose M here: the module will be 249 + called cgbc-core. 250 + 239 251 config MFD_CROS_EC_DEV 240 252 tristate "ChromeOS Embedded Controller multifunction device" 241 253 select MFD_CORE
+1
drivers/mfd/Makefile
··· 13 13 obj-$(CONFIG_ARCH_BCM2835) += bcm2835-pm.o 14 14 obj-$(CONFIG_MFD_BCM590XX) += bcm590xx.o 15 15 obj-$(CONFIG_MFD_BD9571MWV) += bd9571mwv.o 16 + obj-$(CONFIG_MFD_CGBC) += cgbc-core.o 16 17 obj-$(CONFIG_MFD_CROS_EC_DEV) += cros_ec_dev.o 17 18 obj-$(CONFIG_MFD_CS42L43) += cs42l43.o 18 19 obj-$(CONFIG_MFD_CS42L43_I2C) += cs42l43-i2c.o
+411
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 <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 + 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 + return ret; 325 + 326 + return mfd_add_devices(cgbc->dev, -1, cgbc_devs, ARRAY_SIZE(cgbc_devs), NULL, 0, NULL); 327 + } 328 + 329 + static int cgbc_probe(struct platform_device *pdev) 330 + { 331 + struct device *dev = &pdev->dev; 332 + struct cgbc_device_data *cgbc; 333 + int ret; 334 + 335 + cgbc = devm_kzalloc(dev, sizeof(*cgbc), GFP_KERNEL); 336 + if (!cgbc) 337 + return -ENOMEM; 338 + 339 + cgbc->dev = dev; 340 + 341 + ret = cgbc_map(cgbc); 342 + if (ret) 343 + return ret; 344 + 345 + mutex_init(&cgbc->lock); 346 + 347 + platform_set_drvdata(pdev, cgbc); 348 + 349 + return cgbc_init_device(cgbc); 350 + } 351 + 352 + static void cgbc_remove(struct platform_device *pdev) 353 + { 354 + struct cgbc_device_data *cgbc = platform_get_drvdata(pdev); 355 + 356 + cgbc_session_release(cgbc); 357 + 358 + mfd_remove_devices(&pdev->dev); 359 + } 360 + 361 + static struct platform_driver cgbc_driver = { 362 + .driver = { 363 + .name = "cgbc", 364 + .dev_groups = cgbc_groups, 365 + }, 366 + .probe = cgbc_probe, 367 + .remove_new = cgbc_remove, 368 + }; 369 + 370 + static const struct dmi_system_id cgbc_dmi_table[] __initconst = { 371 + { 372 + .ident = "SA7", 373 + .matches = { 374 + DMI_MATCH(DMI_BOARD_VENDOR, "congatec"), 375 + DMI_MATCH(DMI_BOARD_NAME, "conga-SA7"), 376 + }, 377 + }, 378 + {} 379 + }; 380 + MODULE_DEVICE_TABLE(dmi, cgbc_dmi_table); 381 + 382 + static int __init cgbc_init(void) 383 + { 384 + const struct dmi_system_id *id; 385 + int ret = -ENODEV; 386 + 387 + id = dmi_first_match(cgbc_dmi_table); 388 + if (IS_ERR_OR_NULL(id)) 389 + return ret; 390 + 391 + cgbc_pdev = platform_device_register_simple("cgbc", PLATFORM_DEVID_NONE, cgbc_resources, 392 + ARRAY_SIZE(cgbc_resources)); 393 + if (IS_ERR(cgbc_pdev)) 394 + return PTR_ERR(cgbc_pdev); 395 + 396 + return platform_driver_register(&cgbc_driver); 397 + } 398 + 399 + static void __exit cgbc_exit(void) 400 + { 401 + platform_device_unregister(cgbc_pdev); 402 + platform_driver_unregister(&cgbc_driver); 403 + } 404 + 405 + module_init(cgbc_init); 406 + module_exit(cgbc_exit); 407 + 408 + MODULE_DESCRIPTION("Congatec Board Controller Core Driver"); 409 + MODULE_AUTHOR("Thomas Richard <thomas.richard@bootlin.com>"); 410 + MODULE_LICENSE("GPL"); 411 + MODULE_ALIAS("platform:cgbc-core");
+44
include/linux/mfd/cgbc.h
··· 1 + /* SPDX-License-Identifier: GPL-2.0-or-later */ 2 + /* 3 + * Congatec Board Controller driver definitions 4 + * 5 + * Copyright (C) 2024 Bootlin 6 + * Author: Thomas Richard <thomas.richard@bootlin.com> 7 + */ 8 + 9 + #ifndef _LINUX_MFD_CGBC_H_ 10 + 11 + /** 12 + * struct cgbc_version - Board Controller device version structure 13 + * @feature: Board Controller feature number 14 + * @major: Board Controller major revision 15 + * @minor: Board Controller minor revision 16 + */ 17 + struct cgbc_version { 18 + unsigned char feature; 19 + unsigned char major; 20 + unsigned char minor; 21 + }; 22 + 23 + /** 24 + * struct cgbc_device_data - Internal representation of the Board Controller device 25 + * @io_session: Pointer to the session IO memory 26 + * @io_cmd: Pointer to the command IO memory 27 + * @session: Session id returned by the Board Controller 28 + * @dev: Pointer to kernel device structure 29 + * @cgbc_version: Board Controller version structure 30 + * @mutex: Board Controller mutex 31 + */ 32 + struct cgbc_device_data { 33 + void __iomem *io_session; 34 + void __iomem *io_cmd; 35 + u8 session; 36 + struct device *dev; 37 + struct cgbc_version version; 38 + struct mutex lock; 39 + }; 40 + 41 + int cgbc_command(struct cgbc_device_data *cgbc, void *cmd, unsigned int cmd_size, 42 + void *data, unsigned int data_size, u8 *status); 43 + 44 + #endif /*_LINUX_MFD_CGBC_H_*/