1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * cros_ec_dev - expose the Chrome OS Embedded Controller to user-space 4 * 5 * Copyright (C) 2014 Google, Inc. 6 */ 7 8 #include <linux/dmi.h> 9 #include <linux/kconfig.h> 10 #include <linux/mfd/core.h> 11 #include <linux/module.h> 12 #include <linux/mod_devicetable.h> 13 #include <linux/of.h> 14 #include <linux/platform_device.h> 15 #include <linux/platform_data/cros_ec_chardev.h> 16 #include <linux/platform_data/cros_ec_commands.h> 17 #include <linux/platform_data/cros_ec_proto.h> 18 #include <linux/slab.h> 19 20 #define DRV_NAME "cros-ec-dev" 21 22 static struct class cros_class = { 23 .name = "chromeos", 24 }; 25 26 /** 27 * struct cros_feature_to_name - CrOS feature id to name/short description. 28 * @id: The feature identifier. 29 * @name: Device name associated with the feature id. 30 * @desc: Short name that will be displayed. 31 */ 32 struct cros_feature_to_name { 33 unsigned int id; 34 const char *name; 35 const char *desc; 36 }; 37 38 /** 39 * struct cros_feature_to_cells - CrOS feature id to mfd cells association. 40 * @id: The feature identifier. 41 * @mfd_cells: Pointer to the array of mfd cells that needs to be added. 42 * @num_cells: Number of mfd cells into the array. 43 */ 44 struct cros_feature_to_cells { 45 unsigned int id; 46 const struct mfd_cell *mfd_cells; 47 unsigned int num_cells; 48 }; 49 50 static const struct cros_feature_to_name cros_mcu_devices[] = { 51 { 52 .id = EC_FEATURE_FINGERPRINT, 53 .name = CROS_EC_DEV_FP_NAME, 54 .desc = "Fingerprint", 55 }, 56 { 57 .id = EC_FEATURE_ISH, 58 .name = CROS_EC_DEV_ISH_NAME, 59 .desc = "Integrated Sensor Hub", 60 }, 61 { 62 .id = EC_FEATURE_SCP, 63 .name = CROS_EC_DEV_SCP_NAME, 64 .desc = "System Control Processor", 65 }, 66 { 67 .id = EC_FEATURE_TOUCHPAD, 68 .name = CROS_EC_DEV_TP_NAME, 69 .desc = "Touchpad", 70 }, 71 }; 72 73 static const struct mfd_cell cros_ec_cec_cells[] = { 74 { .name = "cros-ec-cec", }, 75 }; 76 77 static const struct mfd_cell cros_ec_gpio_cells[] = { 78 { .name = "cros-ec-gpio", }, 79 }; 80 81 static const struct mfd_cell cros_ec_rtc_cells[] = { 82 { .name = "cros-ec-rtc", }, 83 }; 84 85 static const struct mfd_cell cros_ec_sensorhub_cells[] = { 86 { .name = "cros-ec-sensorhub", }, 87 }; 88 89 static const struct mfd_cell cros_usbpd_charger_cells[] = { 90 { .name = "cros-usbpd-charger", }, 91 { .name = "cros-usbpd-logger", }, 92 }; 93 94 static const struct mfd_cell cros_usbpd_notify_cells[] = { 95 { .name = "cros-usbpd-notify", }, 96 }; 97 98 static const struct mfd_cell cros_ec_wdt_cells[] = { 99 { .name = "cros-ec-wdt", } 100 }; 101 102 static const struct mfd_cell cros_ec_led_cells[] = { 103 { .name = "cros-ec-led", }, 104 }; 105 106 static const struct mfd_cell cros_ec_keyboard_leds_cells[] = { 107 { .name = "cros-keyboard-leds", }, 108 }; 109 110 static const struct cros_feature_to_cells cros_subdevices[] = { 111 { 112 .id = EC_FEATURE_CEC, 113 .mfd_cells = cros_ec_cec_cells, 114 .num_cells = ARRAY_SIZE(cros_ec_cec_cells), 115 }, 116 { 117 .id = EC_FEATURE_GPIO, 118 .mfd_cells = cros_ec_gpio_cells, 119 .num_cells = ARRAY_SIZE(cros_ec_gpio_cells), 120 }, 121 { 122 .id = EC_FEATURE_RTC, 123 .mfd_cells = cros_ec_rtc_cells, 124 .num_cells = ARRAY_SIZE(cros_ec_rtc_cells), 125 }, 126 { 127 .id = EC_FEATURE_USB_PD, 128 .mfd_cells = cros_usbpd_charger_cells, 129 .num_cells = ARRAY_SIZE(cros_usbpd_charger_cells), 130 }, 131 { 132 .id = EC_FEATURE_HANG_DETECT, 133 .mfd_cells = cros_ec_wdt_cells, 134 .num_cells = ARRAY_SIZE(cros_ec_wdt_cells), 135 }, 136 { 137 .id = EC_FEATURE_LED, 138 .mfd_cells = cros_ec_led_cells, 139 .num_cells = ARRAY_SIZE(cros_ec_led_cells), 140 }, 141 { 142 .id = EC_FEATURE_PWM_KEYB, 143 .mfd_cells = cros_ec_keyboard_leds_cells, 144 .num_cells = ARRAY_SIZE(cros_ec_keyboard_leds_cells), 145 }, 146 }; 147 148 static const struct mfd_cell cros_ec_platform_cells[] = { 149 { .name = "cros-ec-chardev", }, 150 { .name = "cros-ec-debugfs", }, 151 { .name = "cros-ec-sysfs", }, 152 }; 153 154 static const struct mfd_cell cros_ec_pchg_cells[] = { 155 { .name = "cros-ec-pchg", }, 156 }; 157 158 static const struct mfd_cell cros_ec_lightbar_cells[] = { 159 { .name = "cros-ec-lightbar", } 160 }; 161 162 static const struct mfd_cell cros_ec_vbc_cells[] = { 163 { .name = "cros-ec-vbc", } 164 }; 165 166 static void cros_ec_class_release(struct device *dev) 167 { 168 kfree(to_cros_ec_dev(dev)); 169 } 170 171 static int ec_device_probe(struct platform_device *pdev) 172 { 173 int retval = -ENOMEM; 174 struct device_node *node; 175 struct device *dev = &pdev->dev; 176 struct cros_ec_platform *ec_platform = dev_get_platdata(dev); 177 struct cros_ec_dev *ec = kzalloc(sizeof(*ec), GFP_KERNEL); 178 struct ec_response_pchg_count pchg_count; 179 int i; 180 181 if (!ec) 182 return retval; 183 184 dev_set_drvdata(dev, ec); 185 ec->ec_dev = dev_get_drvdata(dev->parent); 186 ec->dev = dev; 187 ec->cmd_offset = ec_platform->cmd_offset; 188 ec->features.flags[0] = -1U; /* Not cached yet */ 189 ec->features.flags[1] = -1U; /* Not cached yet */ 190 device_initialize(&ec->class_dev); 191 192 for (i = 0; i < ARRAY_SIZE(cros_mcu_devices); i++) { 193 /* 194 * Check whether this is actually a dedicated MCU rather 195 * than an standard EC. 196 */ 197 if (cros_ec_check_features(ec, cros_mcu_devices[i].id)) { 198 dev_info(dev, "CrOS %s MCU detected\n", 199 cros_mcu_devices[i].desc); 200 /* 201 * Help userspace differentiating ECs from other MCU, 202 * regardless of the probing order. 203 */ 204 ec_platform->ec_name = cros_mcu_devices[i].name; 205 break; 206 } 207 } 208 209 /* 210 * Add the class device 211 */ 212 ec->class_dev.class = &cros_class; 213 ec->class_dev.parent = dev; 214 ec->class_dev.release = cros_ec_class_release; 215 216 retval = dev_set_name(&ec->class_dev, "%s", ec_platform->ec_name); 217 if (retval) { 218 dev_err(dev, "dev_set_name failed => %d\n", retval); 219 goto failed; 220 } 221 222 retval = device_add(&ec->class_dev); 223 if (retval) 224 goto failed; 225 226 /* check whether this EC is a sensor hub. */ 227 if (cros_ec_get_sensor_count(ec) > 0) { 228 retval = mfd_add_hotplug_devices(ec->dev, 229 cros_ec_sensorhub_cells, 230 ARRAY_SIZE(cros_ec_sensorhub_cells)); 231 if (retval) 232 dev_err(ec->dev, "failed to add %s subdevice: %d\n", 233 cros_ec_sensorhub_cells->name, retval); 234 } 235 236 /* 237 * The following subdevices can be detected by sending the 238 * EC_FEATURE_GET_CMD Embedded Controller device. 239 */ 240 for (i = 0; i < ARRAY_SIZE(cros_subdevices); i++) { 241 if (cros_ec_check_features(ec, cros_subdevices[i].id)) { 242 retval = mfd_add_hotplug_devices(ec->dev, 243 cros_subdevices[i].mfd_cells, 244 cros_subdevices[i].num_cells); 245 if (retval) 246 dev_err(ec->dev, 247 "failed to add %s subdevice: %d\n", 248 cros_subdevices[i].mfd_cells->name, 249 retval); 250 } 251 } 252 253 /* 254 * Lightbar is a special case. Newer devices support autodetection, 255 * but older ones do not. 256 */ 257 if (cros_ec_check_features(ec, EC_FEATURE_LIGHTBAR) || 258 dmi_match(DMI_PRODUCT_NAME, "Link")) { 259 retval = mfd_add_hotplug_devices(ec->dev, 260 cros_ec_lightbar_cells, 261 ARRAY_SIZE(cros_ec_lightbar_cells)); 262 if (retval) 263 dev_warn(ec->dev, "failed to add lightbar: %d\n", 264 retval); 265 } 266 267 /* 268 * The PD notifier driver cell is separate since it only needs to be 269 * explicitly added on platforms that don't have the PD notifier ACPI 270 * device entry defined. 271 */ 272 if (IS_ENABLED(CONFIG_OF) && ec->ec_dev->dev->of_node) { 273 if (cros_ec_check_features(ec, EC_FEATURE_USB_PD)) { 274 retval = mfd_add_hotplug_devices(ec->dev, 275 cros_usbpd_notify_cells, 276 ARRAY_SIZE(cros_usbpd_notify_cells)); 277 if (retval) 278 dev_err(ec->dev, 279 "failed to add PD notify devices: %d\n", 280 retval); 281 } 282 } 283 284 /* 285 * The PCHG device cannot be detected by sending EC_FEATURE_GET_CMD, but 286 * it can be detected by querying the number of peripheral chargers. 287 */ 288 retval = cros_ec_cmd(ec->ec_dev, 0, EC_CMD_PCHG_COUNT, NULL, 0, 289 &pchg_count, sizeof(pchg_count)); 290 if (retval >= 0 && pchg_count.port_count) { 291 retval = mfd_add_hotplug_devices(ec->dev, 292 cros_ec_pchg_cells, 293 ARRAY_SIZE(cros_ec_pchg_cells)); 294 if (retval) 295 dev_warn(ec->dev, "failed to add pchg: %d\n", 296 retval); 297 } 298 299 /* 300 * The following subdevices cannot be detected by sending the 301 * EC_FEATURE_GET_CMD to the Embedded Controller device. 302 */ 303 retval = mfd_add_hotplug_devices(ec->dev, cros_ec_platform_cells, 304 ARRAY_SIZE(cros_ec_platform_cells)); 305 if (retval) 306 dev_warn(ec->dev, 307 "failed to add cros-ec platform devices: %d\n", 308 retval); 309 310 /* Check whether this EC instance has a VBC NVRAM */ 311 node = ec->ec_dev->dev->of_node; 312 if (of_property_read_bool(node, "google,has-vbc-nvram")) { 313 retval = mfd_add_hotplug_devices(ec->dev, cros_ec_vbc_cells, 314 ARRAY_SIZE(cros_ec_vbc_cells)); 315 if (retval) 316 dev_warn(ec->dev, "failed to add VBC devices: %d\n", 317 retval); 318 } 319 320 return 0; 321 322 failed: 323 put_device(&ec->class_dev); 324 return retval; 325 } 326 327 static void ec_device_remove(struct platform_device *pdev) 328 { 329 struct cros_ec_dev *ec = dev_get_drvdata(&pdev->dev); 330 331 mfd_remove_devices(ec->dev); 332 device_unregister(&ec->class_dev); 333 } 334 335 static const struct platform_device_id cros_ec_id[] = { 336 { DRV_NAME, 0 }, 337 { /* sentinel */ } 338 }; 339 MODULE_DEVICE_TABLE(platform, cros_ec_id); 340 341 static struct platform_driver cros_ec_dev_driver = { 342 .driver = { 343 .name = DRV_NAME, 344 }, 345 .id_table = cros_ec_id, 346 .probe = ec_device_probe, 347 .remove_new = ec_device_remove, 348 }; 349 350 static int __init cros_ec_dev_init(void) 351 { 352 int ret; 353 354 ret = class_register(&cros_class); 355 if (ret) { 356 pr_err(CROS_EC_DEV_NAME ": failed to register device class\n"); 357 return ret; 358 } 359 360 /* Register the driver */ 361 ret = platform_driver_register(&cros_ec_dev_driver); 362 if (ret < 0) { 363 pr_warn(CROS_EC_DEV_NAME ": can't register driver: %d\n", ret); 364 goto failed_devreg; 365 } 366 return 0; 367 368 failed_devreg: 369 class_unregister(&cros_class); 370 return ret; 371 } 372 373 static void __exit cros_ec_dev_exit(void) 374 { 375 platform_driver_unregister(&cros_ec_dev_driver); 376 class_unregister(&cros_class); 377 } 378 379 module_init(cros_ec_dev_init); 380 module_exit(cros_ec_dev_exit); 381 382 MODULE_AUTHOR("Bill Richardson <[email protected]>"); 383 MODULE_DESCRIPTION("Userspace interface to the Chrome OS Embedded Controller"); 384 MODULE_VERSION("1.0"); 385 MODULE_LICENSE("GPL"); 386