11ccea77eSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
25e011558SThierry Escande /*
35e011558SThierry Escande * cros_ec_dev - expose the Chrome OS Embedded Controller to user-space
45e011558SThierry Escande *
55e011558SThierry Escande * Copyright (C) 2014 Google, Inc.
65e011558SThierry Escande */
75e011558SThierry Escande
8a75f4d1fSGwendal Grignou #include <linux/dmi.h>
94602dce0SPrashant Malani #include <linux/kconfig.h>
105e011558SThierry Escande #include <linux/mfd/core.h>
115e011558SThierry Escande #include <linux/module.h>
12ac316725SRandy Dunlap #include <linux/mod_devicetable.h>
13*dc0c386eSRob Herring #include <linux/of.h>
145e011558SThierry Escande #include <linux/platform_device.h>
15459aedb9SEnric Balletbo i Serra #include <linux/platform_data/cros_ec_chardev.h>
16840d9f13SEnric Balletbo i Serra #include <linux/platform_data/cros_ec_commands.h>
17840d9f13SEnric Balletbo i Serra #include <linux/platform_data/cros_ec_proto.h>
185e011558SThierry Escande #include <linux/slab.h>
195e011558SThierry Escande
205e011558SThierry Escande #define DRV_NAME "cros-ec-dev"
215e011558SThierry Escande
225e011558SThierry Escande static struct class cros_class = {
235e011558SThierry Escande .name = "chromeos",
245e011558SThierry Escande };
255e011558SThierry Escande
26b027dcf7SEnric Balletbo i Serra /**
275ae3d1bcSLee Jones * struct cros_feature_to_name - CrOS feature id to name/short description.
28b027dcf7SEnric Balletbo i Serra * @id: The feature identifier.
29b027dcf7SEnric Balletbo i Serra * @name: Device name associated with the feature id.
30b027dcf7SEnric Balletbo i Serra * @desc: Short name that will be displayed.
31b027dcf7SEnric Balletbo i Serra */
32b027dcf7SEnric Balletbo i Serra struct cros_feature_to_name {
33b027dcf7SEnric Balletbo i Serra unsigned int id;
34b027dcf7SEnric Balletbo i Serra const char *name;
35b027dcf7SEnric Balletbo i Serra const char *desc;
36b027dcf7SEnric Balletbo i Serra };
37b027dcf7SEnric Balletbo i Serra
38832a636fSEnric Balletbo i Serra /**
395ae3d1bcSLee Jones * struct cros_feature_to_cells - CrOS feature id to mfd cells association.
40832a636fSEnric Balletbo i Serra * @id: The feature identifier.
41832a636fSEnric Balletbo i Serra * @mfd_cells: Pointer to the array of mfd cells that needs to be added.
42832a636fSEnric Balletbo i Serra * @num_cells: Number of mfd cells into the array.
43832a636fSEnric Balletbo i Serra */
44832a636fSEnric Balletbo i Serra struct cros_feature_to_cells {
45832a636fSEnric Balletbo i Serra unsigned int id;
46832a636fSEnric Balletbo i Serra const struct mfd_cell *mfd_cells;
47832a636fSEnric Balletbo i Serra unsigned int num_cells;
48832a636fSEnric Balletbo i Serra };
49832a636fSEnric Balletbo i Serra
50b027dcf7SEnric Balletbo i Serra static const struct cros_feature_to_name cros_mcu_devices[] = {
51b027dcf7SEnric Balletbo i Serra {
52b027dcf7SEnric Balletbo i Serra .id = EC_FEATURE_FINGERPRINT,
53b027dcf7SEnric Balletbo i Serra .name = CROS_EC_DEV_FP_NAME,
54b027dcf7SEnric Balletbo i Serra .desc = "Fingerprint",
55b027dcf7SEnric Balletbo i Serra },
56b027dcf7SEnric Balletbo i Serra {
57b027dcf7SEnric Balletbo i Serra .id = EC_FEATURE_ISH,
58b027dcf7SEnric Balletbo i Serra .name = CROS_EC_DEV_ISH_NAME,
59b027dcf7SEnric Balletbo i Serra .desc = "Integrated Sensor Hub",
60b027dcf7SEnric Balletbo i Serra },
61b027dcf7SEnric Balletbo i Serra {
62b027dcf7SEnric Balletbo i Serra .id = EC_FEATURE_SCP,
63b027dcf7SEnric Balletbo i Serra .name = CROS_EC_DEV_SCP_NAME,
64b027dcf7SEnric Balletbo i Serra .desc = "System Control Processor",
65b027dcf7SEnric Balletbo i Serra },
66b027dcf7SEnric Balletbo i Serra {
67b027dcf7SEnric Balletbo i Serra .id = EC_FEATURE_TOUCHPAD,
68b027dcf7SEnric Balletbo i Serra .name = CROS_EC_DEV_TP_NAME,
69b027dcf7SEnric Balletbo i Serra .desc = "Touchpad",
70b027dcf7SEnric Balletbo i Serra },
71b027dcf7SEnric Balletbo i Serra };
72b027dcf7SEnric Balletbo i Serra
73832a636fSEnric Balletbo i Serra static const struct mfd_cell cros_ec_cec_cells[] = {
74832a636fSEnric Balletbo i Serra { .name = "cros-ec-cec", },
75832a636fSEnric Balletbo i Serra };
76832a636fSEnric Balletbo i Serra
77832a636fSEnric Balletbo i Serra static const struct mfd_cell cros_ec_rtc_cells[] = {
78832a636fSEnric Balletbo i Serra { .name = "cros-ec-rtc", },
79832a636fSEnric Balletbo i Serra };
80832a636fSEnric Balletbo i Serra
81d60ac88aSGwendal Grignou static const struct mfd_cell cros_ec_sensorhub_cells[] = {
82d60ac88aSGwendal Grignou { .name = "cros-ec-sensorhub", },
83d60ac88aSGwendal Grignou };
84d60ac88aSGwendal Grignou
85832a636fSEnric Balletbo i Serra static const struct mfd_cell cros_usbpd_charger_cells[] = {
86832a636fSEnric Balletbo i Serra { .name = "cros-usbpd-charger", },
87832a636fSEnric Balletbo i Serra { .name = "cros-usbpd-logger", },
88832a636fSEnric Balletbo i Serra };
89832a636fSEnric Balletbo i Serra
904602dce0SPrashant Malani static const struct mfd_cell cros_usbpd_notify_cells[] = {
914602dce0SPrashant Malani { .name = "cros-usbpd-notify", },
924602dce0SPrashant Malani };
934602dce0SPrashant Malani
94832a636fSEnric Balletbo i Serra static const struct cros_feature_to_cells cros_subdevices[] = {
95832a636fSEnric Balletbo i Serra {
96832a636fSEnric Balletbo i Serra .id = EC_FEATURE_CEC,
97832a636fSEnric Balletbo i Serra .mfd_cells = cros_ec_cec_cells,
98832a636fSEnric Balletbo i Serra .num_cells = ARRAY_SIZE(cros_ec_cec_cells),
99832a636fSEnric Balletbo i Serra },
100832a636fSEnric Balletbo i Serra {
101832a636fSEnric Balletbo i Serra .id = EC_FEATURE_RTC,
102832a636fSEnric Balletbo i Serra .mfd_cells = cros_ec_rtc_cells,
103832a636fSEnric Balletbo i Serra .num_cells = ARRAY_SIZE(cros_ec_rtc_cells),
104832a636fSEnric Balletbo i Serra },
105832a636fSEnric Balletbo i Serra {
106832a636fSEnric Balletbo i Serra .id = EC_FEATURE_USB_PD,
107832a636fSEnric Balletbo i Serra .mfd_cells = cros_usbpd_charger_cells,
108832a636fSEnric Balletbo i Serra .num_cells = ARRAY_SIZE(cros_usbpd_charger_cells),
109832a636fSEnric Balletbo i Serra },
110832a636fSEnric Balletbo i Serra };
111832a636fSEnric Balletbo i Serra
112832a636fSEnric Balletbo i Serra static const struct mfd_cell cros_ec_platform_cells[] = {
113832a636fSEnric Balletbo i Serra { .name = "cros-ec-chardev", },
114832a636fSEnric Balletbo i Serra { .name = "cros-ec-debugfs", },
115832a636fSEnric Balletbo i Serra { .name = "cros-ec-sysfs", },
116ff23a46eSStephen Boyd };
117ff23a46eSStephen Boyd
118ff23a46eSStephen Boyd static const struct mfd_cell cros_ec_pchg_cells[] = {
1198a14ded5SDaisuke Nojiri { .name = "cros-ec-pchg", },
120832a636fSEnric Balletbo i Serra };
121832a636fSEnric Balletbo i Serra
122a75f4d1fSGwendal Grignou static const struct mfd_cell cros_ec_lightbar_cells[] = {
123a75f4d1fSGwendal Grignou { .name = "cros-ec-lightbar", }
124a75f4d1fSGwendal Grignou };
125a75f4d1fSGwendal Grignou
126832a636fSEnric Balletbo i Serra static const struct mfd_cell cros_ec_vbc_cells[] = {
127832a636fSEnric Balletbo i Serra { .name = "cros-ec-vbc", }
128832a636fSEnric Balletbo i Serra };
129832a636fSEnric Balletbo i Serra
cros_ec_class_release(struct device * dev)13048a2ca0eSEnric Balletbo i Serra static void cros_ec_class_release(struct device *dev)
13148a2ca0eSEnric Balletbo i Serra {
13248a2ca0eSEnric Balletbo i Serra kfree(to_cros_ec_dev(dev));
13348a2ca0eSEnric Balletbo i Serra }
13448a2ca0eSEnric Balletbo i Serra
ec_device_probe(struct platform_device * pdev)1355e011558SThierry Escande static int ec_device_probe(struct platform_device *pdev)
1365e011558SThierry Escande {
1375e011558SThierry Escande int retval = -ENOMEM;
1380545625bSEnric Balletbo i Serra struct device_node *node;
1395e011558SThierry Escande struct device *dev = &pdev->dev;
1405e011558SThierry Escande struct cros_ec_platform *ec_platform = dev_get_platdata(dev);
14148a2ca0eSEnric Balletbo i Serra struct cros_ec_dev *ec = kzalloc(sizeof(*ec), GFP_KERNEL);
142ff23a46eSStephen Boyd struct ec_response_pchg_count pchg_count;
143b027dcf7SEnric Balletbo i Serra int i;
1445e011558SThierry Escande
1455e011558SThierry Escande if (!ec)
1465e011558SThierry Escande return retval;
1475e011558SThierry Escande
1485e011558SThierry Escande dev_set_drvdata(dev, ec);
1495e011558SThierry Escande ec->ec_dev = dev_get_drvdata(dev->parent);
1505e011558SThierry Escande ec->dev = dev;
1515e011558SThierry Escande ec->cmd_offset = ec_platform->cmd_offset;
1527ff22787SPrashant Malani ec->features.flags[0] = -1U; /* Not cached yet */
1537ff22787SPrashant Malani ec->features.flags[1] = -1U; /* Not cached yet */
1545e011558SThierry Escande device_initialize(&ec->class_dev);
1555e011558SThierry Escande
156b027dcf7SEnric Balletbo i Serra for (i = 0; i < ARRAY_SIZE(cros_mcu_devices); i++) {
15790486af5SEnric Balletbo i Serra /*
158b027dcf7SEnric Balletbo i Serra * Check whether this is actually a dedicated MCU rather
159b027dcf7SEnric Balletbo i Serra * than an standard EC.
160b027dcf7SEnric Balletbo i Serra */
161b027dcf7SEnric Balletbo i Serra if (cros_ec_check_features(ec, cros_mcu_devices[i].id)) {
162b027dcf7SEnric Balletbo i Serra dev_info(dev, "CrOS %s MCU detected\n",
163b027dcf7SEnric Balletbo i Serra cros_mcu_devices[i].desc);
164b027dcf7SEnric Balletbo i Serra /*
165b027dcf7SEnric Balletbo i Serra * Help userspace differentiating ECs from other MCU,
16690486af5SEnric Balletbo i Serra * regardless of the probing order.
16790486af5SEnric Balletbo i Serra */
168b027dcf7SEnric Balletbo i Serra ec_platform->ec_name = cros_mcu_devices[i].name;
169b027dcf7SEnric Balletbo i Serra break;
17090486af5SEnric Balletbo i Serra }
171554e937eSPi-Hsun Shih }
172554e937eSPi-Hsun Shih
173d4cee950SRushikesh S Kadam /*
1745e011558SThierry Escande * Add the class device
1755e011558SThierry Escande */
1765e011558SThierry Escande ec->class_dev.class = &cros_class;
1775e011558SThierry Escande ec->class_dev.parent = dev;
17848a2ca0eSEnric Balletbo i Serra ec->class_dev.release = cros_ec_class_release;
1795e011558SThierry Escande
1805e011558SThierry Escande retval = dev_set_name(&ec->class_dev, "%s", ec_platform->ec_name);
1815e011558SThierry Escande if (retval) {
1825e011558SThierry Escande dev_err(dev, "dev_set_name failed => %d\n", retval);
1835e011558SThierry Escande goto failed;
1845e011558SThierry Escande }
1855e011558SThierry Escande
186459aedb9SEnric Balletbo i Serra retval = device_add(&ec->class_dev);
187459aedb9SEnric Balletbo i Serra if (retval)
188459aedb9SEnric Balletbo i Serra goto failed;
189459aedb9SEnric Balletbo i Serra
190c1d1e91aSGwendal Grignou /* check whether this EC is a sensor hub. */
191d60ac88aSGwendal Grignou if (cros_ec_get_sensor_count(ec) > 0) {
192d60ac88aSGwendal Grignou retval = mfd_add_hotplug_devices(ec->dev,
193d60ac88aSGwendal Grignou cros_ec_sensorhub_cells,
194d60ac88aSGwendal Grignou ARRAY_SIZE(cros_ec_sensorhub_cells));
195d60ac88aSGwendal Grignou if (retval)
196d60ac88aSGwendal Grignou dev_err(ec->dev, "failed to add %s subdevice: %d\n",
197d60ac88aSGwendal Grignou cros_ec_sensorhub_cells->name, retval);
198d60ac88aSGwendal Grignou }
199c1d1e91aSGwendal Grignou
200832a636fSEnric Balletbo i Serra /*
201832a636fSEnric Balletbo i Serra * The following subdevices can be detected by sending the
202832a636fSEnric Balletbo i Serra * EC_FEATURE_GET_CMD Embedded Controller device.
203832a636fSEnric Balletbo i Serra */
204832a636fSEnric Balletbo i Serra for (i = 0; i < ARRAY_SIZE(cros_subdevices); i++) {
205832a636fSEnric Balletbo i Serra if (cros_ec_check_features(ec, cros_subdevices[i].id)) {
206832a636fSEnric Balletbo i Serra retval = mfd_add_hotplug_devices(ec->dev,
207832a636fSEnric Balletbo i Serra cros_subdevices[i].mfd_cells,
208832a636fSEnric Balletbo i Serra cros_subdevices[i].num_cells);
20903a5755cSNeil Armstrong if (retval)
21003a5755cSNeil Armstrong dev_err(ec->dev,
211832a636fSEnric Balletbo i Serra "failed to add %s subdevice: %d\n",
212832a636fSEnric Balletbo i Serra cros_subdevices[i].mfd_cells->name,
21303a5755cSNeil Armstrong retval);
21403a5755cSNeil Armstrong }
21595a4d07fSEnric Balletbo i Serra }
21695a4d07fSEnric Balletbo i Serra
217832a636fSEnric Balletbo i Serra /*
218a75f4d1fSGwendal Grignou * Lightbar is a special case. Newer devices support autodetection,
219a75f4d1fSGwendal Grignou * but older ones do not.
220a75f4d1fSGwendal Grignou */
221a75f4d1fSGwendal Grignou if (cros_ec_check_features(ec, EC_FEATURE_LIGHTBAR) ||
222a75f4d1fSGwendal Grignou dmi_match(DMI_PRODUCT_NAME, "Link")) {
223a75f4d1fSGwendal Grignou retval = mfd_add_hotplug_devices(ec->dev,
224a75f4d1fSGwendal Grignou cros_ec_lightbar_cells,
225a75f4d1fSGwendal Grignou ARRAY_SIZE(cros_ec_lightbar_cells));
226a75f4d1fSGwendal Grignou if (retval)
227a75f4d1fSGwendal Grignou dev_warn(ec->dev, "failed to add lightbar: %d\n",
228a75f4d1fSGwendal Grignou retval);
229a75f4d1fSGwendal Grignou }
230a75f4d1fSGwendal Grignou
231a75f4d1fSGwendal Grignou /*
2324602dce0SPrashant Malani * The PD notifier driver cell is separate since it only needs to be
2334602dce0SPrashant Malani * explicitly added on platforms that don't have the PD notifier ACPI
2344602dce0SPrashant Malani * device entry defined.
2354602dce0SPrashant Malani */
236f8db89d1SPrashant Malani if (IS_ENABLED(CONFIG_OF) && ec->ec_dev->dev->of_node) {
2374602dce0SPrashant Malani if (cros_ec_check_features(ec, EC_FEATURE_USB_PD)) {
2384602dce0SPrashant Malani retval = mfd_add_hotplug_devices(ec->dev,
2394602dce0SPrashant Malani cros_usbpd_notify_cells,
2404602dce0SPrashant Malani ARRAY_SIZE(cros_usbpd_notify_cells));
2414602dce0SPrashant Malani if (retval)
2424602dce0SPrashant Malani dev_err(ec->dev,
2434602dce0SPrashant Malani "failed to add PD notify devices: %d\n",
2444602dce0SPrashant Malani retval);
2454602dce0SPrashant Malani }
2464602dce0SPrashant Malani }
2474602dce0SPrashant Malani
2484602dce0SPrashant Malani /*
249ff23a46eSStephen Boyd * The PCHG device cannot be detected by sending EC_FEATURE_GET_CMD, but
250ff23a46eSStephen Boyd * it can be detected by querying the number of peripheral chargers.
251ff23a46eSStephen Boyd */
252b1d288d9SPrashant Malani retval = cros_ec_cmd(ec->ec_dev, 0, EC_CMD_PCHG_COUNT, NULL, 0,
253ff23a46eSStephen Boyd &pchg_count, sizeof(pchg_count));
254ff23a46eSStephen Boyd if (retval >= 0 && pchg_count.port_count) {
255ff23a46eSStephen Boyd retval = mfd_add_hotplug_devices(ec->dev,
256ff23a46eSStephen Boyd cros_ec_pchg_cells,
257ff23a46eSStephen Boyd ARRAY_SIZE(cros_ec_pchg_cells));
258ff23a46eSStephen Boyd if (retval)
259ff23a46eSStephen Boyd dev_warn(ec->dev, "failed to add pchg: %d\n",
260ff23a46eSStephen Boyd retval);
261ff23a46eSStephen Boyd }
262ff23a46eSStephen Boyd
263ff23a46eSStephen Boyd /*
264832a636fSEnric Balletbo i Serra * The following subdevices cannot be detected by sending the
265832a636fSEnric Balletbo i Serra * EC_FEATURE_GET_CMD to the Embedded Controller device.
266832a636fSEnric Balletbo i Serra */
26728e6fcc8SEnric Balletbo i Serra retval = mfd_add_hotplug_devices(ec->dev, cros_ec_platform_cells,
26828e6fcc8SEnric Balletbo i Serra ARRAY_SIZE(cros_ec_platform_cells));
269ecf8a6cdSEnric Balletbo i Serra if (retval)
270ecf8a6cdSEnric Balletbo i Serra dev_warn(ec->dev,
271ecf8a6cdSEnric Balletbo i Serra "failed to add cros-ec platform devices: %d\n",
272ecf8a6cdSEnric Balletbo i Serra retval);
273ecf8a6cdSEnric Balletbo i Serra
2740545625bSEnric Balletbo i Serra /* Check whether this EC instance has a VBC NVRAM */
2750545625bSEnric Balletbo i Serra node = ec->ec_dev->dev->of_node;
2760545625bSEnric Balletbo i Serra if (of_property_read_bool(node, "google,has-vbc-nvram")) {
27728e6fcc8SEnric Balletbo i Serra retval = mfd_add_hotplug_devices(ec->dev, cros_ec_vbc_cells,
27828e6fcc8SEnric Balletbo i Serra ARRAY_SIZE(cros_ec_vbc_cells));
2790545625bSEnric Balletbo i Serra if (retval)
2800545625bSEnric Balletbo i Serra dev_warn(ec->dev, "failed to add VBC devices: %d\n",
2810545625bSEnric Balletbo i Serra retval);
2820545625bSEnric Balletbo i Serra }
2830545625bSEnric Balletbo i Serra
2845e011558SThierry Escande return 0;
2855e011558SThierry Escande
2865e011558SThierry Escande failed:
2875e011558SThierry Escande put_device(&ec->class_dev);
2885e011558SThierry Escande return retval;
2895e011558SThierry Escande }
2905e011558SThierry Escande
ec_device_remove(struct platform_device * pdev)2915e011558SThierry Escande static int ec_device_remove(struct platform_device *pdev)
2925e011558SThierry Escande {
2935e011558SThierry Escande struct cros_ec_dev *ec = dev_get_drvdata(&pdev->dev);
2945e011558SThierry Escande
29518e294ddSEnric Balletbo i Serra mfd_remove_devices(ec->dev);
2965e011558SThierry Escande device_unregister(&ec->class_dev);
2975e011558SThierry Escande return 0;
2985e011558SThierry Escande }
2995e011558SThierry Escande
3005e011558SThierry Escande static const struct platform_device_id cros_ec_id[] = {
3015e011558SThierry Escande { DRV_NAME, 0 },
302abeed71bSWei-Ning Huang { /* sentinel */ }
3035e011558SThierry Escande };
3045e011558SThierry Escande MODULE_DEVICE_TABLE(platform, cros_ec_id);
3055e011558SThierry Escande
3065e011558SThierry Escande static struct platform_driver cros_ec_dev_driver = {
3075e011558SThierry Escande .driver = {
3085e011558SThierry Escande .name = DRV_NAME,
3095e011558SThierry Escande },
3106eb35784SNathan Chancellor .id_table = cros_ec_id,
3115e011558SThierry Escande .probe = ec_device_probe,
3125e011558SThierry Escande .remove = ec_device_remove,
3135e011558SThierry Escande };
3145e011558SThierry Escande
cros_ec_dev_init(void)3155e011558SThierry Escande static int __init cros_ec_dev_init(void)
3165e011558SThierry Escande {
3175e011558SThierry Escande int ret;
3185e011558SThierry Escande
3195e011558SThierry Escande ret = class_register(&cros_class);
3205e011558SThierry Escande if (ret) {
3215e011558SThierry Escande pr_err(CROS_EC_DEV_NAME ": failed to register device class\n");
3225e011558SThierry Escande return ret;
3235e011558SThierry Escande }
3245e011558SThierry Escande
3255e011558SThierry Escande /* Register the driver */
3265e011558SThierry Escande ret = platform_driver_register(&cros_ec_dev_driver);
3275e011558SThierry Escande if (ret < 0) {
3285e011558SThierry Escande pr_warn(CROS_EC_DEV_NAME ": can't register driver: %d\n", ret);
3295e011558SThierry Escande goto failed_devreg;
3305e011558SThierry Escande }
3315e011558SThierry Escande return 0;
3325e011558SThierry Escande
3335e011558SThierry Escande failed_devreg:
3345e011558SThierry Escande class_unregister(&cros_class);
3355e011558SThierry Escande return ret;
3365e011558SThierry Escande }
3375e011558SThierry Escande
cros_ec_dev_exit(void)3385e011558SThierry Escande static void __exit cros_ec_dev_exit(void)
3395e011558SThierry Escande {
3405e011558SThierry Escande platform_driver_unregister(&cros_ec_dev_driver);
3415e011558SThierry Escande class_unregister(&cros_class);
3425e011558SThierry Escande }
3435e011558SThierry Escande
3445e011558SThierry Escande module_init(cros_ec_dev_init);
3455e011558SThierry Escande module_exit(cros_ec_dev_exit);
3465e011558SThierry Escande
3475e011558SThierry Escande MODULE_AUTHOR("Bill Richardson <wfrichar@chromium.org>");
3485e011558SThierry Escande MODULE_DESCRIPTION("Userspace interface to the Chrome OS Embedded Controller");
3495e011558SThierry Escande MODULE_VERSION("1.0");
3505e011558SThierry Escande MODULE_LICENSE("GPL");
351