Merge tag 'mfd-next-5.20' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/mfd
[linux-2.6-microblaze.git] / drivers / mfd / cros_ec_dev.c
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");