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