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 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 <thomas.richard@bootlin.com>"); 419 MODULE_LICENSE("GPL"); 420 MODULE_ALIAS("platform:cgbc-core"); 421