Commit | Line | Data |
---|---|---|
1ccea77e | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
e7c256fb BR |
2 | /* |
3 | * cros_ec_dev - expose the Chrome OS Embedded Controller to user-space | |
4 | * | |
5 | * Copyright (C) 2014 Google, Inc. | |
e7c256fb BR |
6 | */ |
7 | ||
a75f4d1f | 8 | #include <linux/dmi.h> |
4602dce0 | 9 | #include <linux/kconfig.h> |
5668bfdd | 10 | #include <linux/mfd/core.h> |
e7c256fb | 11 | #include <linux/module.h> |
ac316725 | 12 | #include <linux/mod_devicetable.h> |
0545625b | 13 | #include <linux/of_platform.h> |
e7c256fb | 14 | #include <linux/platform_device.h> |
459aedb9 | 15 | #include <linux/platform_data/cros_ec_chardev.h> |
840d9f13 EBS |
16 | #include <linux/platform_data/cros_ec_commands.h> |
17 | #include <linux/platform_data/cros_ec_proto.h> | |
a8411784 | 18 | #include <linux/slab.h> |
e7c256fb | 19 | |
ea01a31b TE |
20 | #define DRV_NAME "cros-ec-dev" |
21 | ||
57b33ff0 GG |
22 | static struct class cros_class = { |
23 | .owner = THIS_MODULE, | |
24 | .name = "chromeos", | |
57b33ff0 GG |
25 | }; |
26 | ||
b027dcf7 | 27 | /** |
5ae3d1bc | 28 | * struct cros_feature_to_name - CrOS feature id to name/short description. |
b027dcf7 EBS |
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 | ||
832a636f | 39 | /** |
5ae3d1bc | 40 | * struct cros_feature_to_cells - CrOS feature id to mfd cells association. |
832a636f EBS |
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 | ||
b027dcf7 EBS |
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_TOUCHPAD, | |
69 | .name = CROS_EC_DEV_TP_NAME, | |
70 | .desc = "Touchpad", | |
71 | }, | |
72 | }; | |
73 | ||
832a636f EBS |
74 | static const struct mfd_cell cros_ec_cec_cells[] = { |
75 | { .name = "cros-ec-cec", }, | |
76 | }; | |
77 | ||
78 | static const struct mfd_cell cros_ec_rtc_cells[] = { | |
79 | { .name = "cros-ec-rtc", }, | |
80 | }; | |
81 | ||
d60ac88a GG |
82 | static const struct mfd_cell cros_ec_sensorhub_cells[] = { |
83 | { .name = "cros-ec-sensorhub", }, | |
84 | }; | |
85 | ||
832a636f EBS |
86 | static const struct mfd_cell cros_usbpd_charger_cells[] = { |
87 | { .name = "cros-usbpd-charger", }, | |
88 | { .name = "cros-usbpd-logger", }, | |
89 | }; | |
90 | ||
4602dce0 PM |
91 | static const struct mfd_cell cros_usbpd_notify_cells[] = { |
92 | { .name = "cros-usbpd-notify", }, | |
93 | }; | |
94 | ||
832a636f EBS |
95 | static const struct cros_feature_to_cells cros_subdevices[] = { |
96 | { | |
97 | .id = EC_FEATURE_CEC, | |
98 | .mfd_cells = cros_ec_cec_cells, | |
99 | .num_cells = ARRAY_SIZE(cros_ec_cec_cells), | |
100 | }, | |
101 | { | |
102 | .id = EC_FEATURE_RTC, | |
103 | .mfd_cells = cros_ec_rtc_cells, | |
104 | .num_cells = ARRAY_SIZE(cros_ec_rtc_cells), | |
105 | }, | |
106 | { | |
107 | .id = EC_FEATURE_USB_PD, | |
108 | .mfd_cells = cros_usbpd_charger_cells, | |
109 | .num_cells = ARRAY_SIZE(cros_usbpd_charger_cells), | |
110 | }, | |
111 | }; | |
112 | ||
113 | static const struct mfd_cell cros_ec_platform_cells[] = { | |
114 | { .name = "cros-ec-chardev", }, | |
115 | { .name = "cros-ec-debugfs", }, | |
832a636f | 116 | { .name = "cros-ec-sysfs", }, |
8a14ded5 | 117 | { .name = "cros-ec-pchg", }, |
832a636f EBS |
118 | }; |
119 | ||
a75f4d1f GG |
120 | static const struct mfd_cell cros_ec_lightbar_cells[] = { |
121 | { .name = "cros-ec-lightbar", } | |
122 | }; | |
123 | ||
832a636f EBS |
124 | static const struct mfd_cell cros_ec_vbc_cells[] = { |
125 | { .name = "cros-ec-vbc", } | |
126 | }; | |
127 | ||
48a2ca0e EBS |
128 | static void cros_ec_class_release(struct device *dev) |
129 | { | |
130 | kfree(to_cros_ec_dev(dev)); | |
131 | } | |
132 | ||
e7c256fb BR |
133 | static int ec_device_probe(struct platform_device *pdev) |
134 | { | |
57b33ff0 | 135 | int retval = -ENOMEM; |
0545625b | 136 | struct device_node *node; |
57b33ff0 GG |
137 | struct device *dev = &pdev->dev; |
138 | struct cros_ec_platform *ec_platform = dev_get_platdata(dev); | |
48a2ca0e | 139 | struct cros_ec_dev *ec = kzalloc(sizeof(*ec), GFP_KERNEL); |
b027dcf7 | 140 | int i; |
57b33ff0 GG |
141 | |
142 | if (!ec) | |
143 | return retval; | |
e7c256fb | 144 | |
57b33ff0 GG |
145 | dev_set_drvdata(dev, ec); |
146 | ec->ec_dev = dev_get_drvdata(dev->parent); | |
147 | ec->dev = dev; | |
148 | ec->cmd_offset = ec_platform->cmd_offset; | |
7ff22787 PM |
149 | ec->features.flags[0] = -1U; /* Not cached yet */ |
150 | ec->features.flags[1] = -1U; /* Not cached yet */ | |
57b33ff0 | 151 | device_initialize(&ec->class_dev); |
e7c256fb | 152 | |
b027dcf7 | 153 | for (i = 0; i < ARRAY_SIZE(cros_mcu_devices); i++) { |
90486af5 | 154 | /* |
b027dcf7 EBS |
155 | * Check whether this is actually a dedicated MCU rather |
156 | * than an standard EC. | |
90486af5 | 157 | */ |
b027dcf7 EBS |
158 | if (cros_ec_check_features(ec, cros_mcu_devices[i].id)) { |
159 | dev_info(dev, "CrOS %s MCU detected\n", | |
160 | cros_mcu_devices[i].desc); | |
161 | /* | |
162 | * Help userspace differentiating ECs from other MCU, | |
163 | * regardless of the probing order. | |
164 | */ | |
165 | ec_platform->ec_name = cros_mcu_devices[i].name; | |
166 | break; | |
167 | } | |
554e937e PHS |
168 | } |
169 | ||
57b33ff0 GG |
170 | /* |
171 | * Add the class device | |
57b33ff0 | 172 | */ |
57b33ff0 GG |
173 | ec->class_dev.class = &cros_class; |
174 | ec->class_dev.parent = dev; | |
48a2ca0e | 175 | ec->class_dev.release = cros_ec_class_release; |
57b33ff0 GG |
176 | |
177 | retval = dev_set_name(&ec->class_dev, "%s", ec_platform->ec_name); | |
178 | if (retval) { | |
179 | dev_err(dev, "dev_set_name failed => %d\n", retval); | |
1c1d152c | 180 | goto failed; |
e7c256fb BR |
181 | } |
182 | ||
459aedb9 EBS |
183 | retval = device_add(&ec->class_dev); |
184 | if (retval) | |
185 | goto failed; | |
186 | ||
c1d1e91a | 187 | /* check whether this EC is a sensor hub. */ |
d60ac88a GG |
188 | if (cros_ec_get_sensor_count(ec) > 0) { |
189 | retval = mfd_add_hotplug_devices(ec->dev, | |
190 | cros_ec_sensorhub_cells, | |
191 | ARRAY_SIZE(cros_ec_sensorhub_cells)); | |
192 | if (retval) | |
193 | dev_err(ec->dev, "failed to add %s subdevice: %d\n", | |
194 | cros_ec_sensorhub_cells->name, retval); | |
195 | } | |
c1d1e91a | 196 | |
832a636f EBS |
197 | /* |
198 | * The following subdevices can be detected by sending the | |
199 | * EC_FEATURE_GET_CMD Embedded Controller device. | |
200 | */ | |
201 | for (i = 0; i < ARRAY_SIZE(cros_subdevices); i++) { | |
202 | if (cros_ec_check_features(ec, cros_subdevices[i].id)) { | |
203 | retval = mfd_add_hotplug_devices(ec->dev, | |
204 | cros_subdevices[i].mfd_cells, | |
205 | cros_subdevices[i].num_cells); | |
206 | if (retval) | |
207 | dev_err(ec->dev, | |
208 | "failed to add %s subdevice: %d\n", | |
209 | cros_subdevices[i].mfd_cells->name, | |
210 | retval); | |
211 | } | |
3144dce7 EBS |
212 | } |
213 | ||
a75f4d1f GG |
214 | /* |
215 | * Lightbar is a special case. Newer devices support autodetection, | |
216 | * but older ones do not. | |
217 | */ | |
218 | if (cros_ec_check_features(ec, EC_FEATURE_LIGHTBAR) || | |
219 | dmi_match(DMI_PRODUCT_NAME, "Link")) { | |
220 | retval = mfd_add_hotplug_devices(ec->dev, | |
221 | cros_ec_lightbar_cells, | |
222 | ARRAY_SIZE(cros_ec_lightbar_cells)); | |
223 | if (retval) | |
224 | dev_warn(ec->dev, "failed to add lightbar: %d\n", | |
225 | retval); | |
226 | } | |
227 | ||
4602dce0 PM |
228 | /* |
229 | * The PD notifier driver cell is separate since it only needs to be | |
230 | * explicitly added on platforms that don't have the PD notifier ACPI | |
231 | * device entry defined. | |
232 | */ | |
f8db89d1 | 233 | if (IS_ENABLED(CONFIG_OF) && ec->ec_dev->dev->of_node) { |
4602dce0 PM |
234 | if (cros_ec_check_features(ec, EC_FEATURE_USB_PD)) { |
235 | retval = mfd_add_hotplug_devices(ec->dev, | |
236 | cros_usbpd_notify_cells, | |
237 | ARRAY_SIZE(cros_usbpd_notify_cells)); | |
238 | if (retval) | |
239 | dev_err(ec->dev, | |
240 | "failed to add PD notify devices: %d\n", | |
241 | retval); | |
242 | } | |
243 | } | |
244 | ||
832a636f EBS |
245 | /* |
246 | * The following subdevices cannot be detected by sending the | |
247 | * EC_FEATURE_GET_CMD to the Embedded Controller device. | |
248 | */ | |
28e6fcc8 EBS |
249 | retval = mfd_add_hotplug_devices(ec->dev, cros_ec_platform_cells, |
250 | ARRAY_SIZE(cros_ec_platform_cells)); | |
ecf8a6cd EBS |
251 | if (retval) |
252 | dev_warn(ec->dev, | |
253 | "failed to add cros-ec platform devices: %d\n", | |
254 | retval); | |
255 | ||
0545625b EBS |
256 | /* Check whether this EC instance has a VBC NVRAM */ |
257 | node = ec->ec_dev->dev->of_node; | |
258 | if (of_property_read_bool(node, "google,has-vbc-nvram")) { | |
28e6fcc8 EBS |
259 | retval = mfd_add_hotplug_devices(ec->dev, cros_ec_vbc_cells, |
260 | ARRAY_SIZE(cros_ec_vbc_cells)); | |
0545625b EBS |
261 | if (retval) |
262 | dev_warn(ec->dev, "failed to add VBC devices: %d\n", | |
263 | retval); | |
264 | } | |
265 | ||
e7c256fb | 266 | return 0; |
57b33ff0 | 267 | |
1c1d152c LG |
268 | failed: |
269 | put_device(&ec->class_dev); | |
57b33ff0 | 270 | return retval; |
e7c256fb BR |
271 | } |
272 | ||
273 | static int ec_device_remove(struct platform_device *pdev) | |
274 | { | |
57b33ff0 | 275 | struct cros_ec_dev *ec = dev_get_drvdata(&pdev->dev); |
e8626459 | 276 | |
18e294dd | 277 | mfd_remove_devices(ec->dev); |
57b33ff0 | 278 | device_unregister(&ec->class_dev); |
e7c256fb BR |
279 | return 0; |
280 | } | |
281 | ||
afbf8ec7 | 282 | static const struct platform_device_id cros_ec_id[] = { |
ea01a31b | 283 | { DRV_NAME, 0 }, |
abeed71b | 284 | { /* sentinel */ } |
afbf8ec7 JMC |
285 | }; |
286 | MODULE_DEVICE_TABLE(platform, cros_ec_id); | |
287 | ||
e7c256fb BR |
288 | static struct platform_driver cros_ec_dev_driver = { |
289 | .driver = { | |
ea01a31b | 290 | .name = DRV_NAME, |
e7c256fb | 291 | }, |
6eb35784 | 292 | .id_table = cros_ec_id, |
e7c256fb BR |
293 | .probe = ec_device_probe, |
294 | .remove = ec_device_remove, | |
295 | }; | |
296 | ||
297 | static int __init cros_ec_dev_init(void) | |
298 | { | |
299 | int ret; | |
e7c256fb | 300 | |
57b33ff0 GG |
301 | ret = class_register(&cros_class); |
302 | if (ret) { | |
e7c256fb | 303 | pr_err(CROS_EC_DEV_NAME ": failed to register device class\n"); |
57b33ff0 | 304 | return ret; |
e7c256fb BR |
305 | } |
306 | ||
e7c256fb BR |
307 | /* Register the driver */ |
308 | ret = platform_driver_register(&cros_ec_dev_driver); | |
309 | if (ret < 0) { | |
310 | pr_warn(CROS_EC_DEV_NAME ": can't register driver: %d\n", ret); | |
311 | goto failed_devreg; | |
312 | } | |
313 | return 0; | |
314 | ||
315 | failed_devreg: | |
57b33ff0 | 316 | class_unregister(&cros_class); |
e7c256fb BR |
317 | return ret; |
318 | } | |
319 | ||
320 | static void __exit cros_ec_dev_exit(void) | |
321 | { | |
322 | platform_driver_unregister(&cros_ec_dev_driver); | |
57b33ff0 | 323 | class_unregister(&cros_class); |
e7c256fb BR |
324 | } |
325 | ||
326 | module_init(cros_ec_dev_init); | |
327 | module_exit(cros_ec_dev_exit); | |
328 | ||
329 | MODULE_AUTHOR("Bill Richardson <wfrichar@chromium.org>"); | |
330 | MODULE_DESCRIPTION("Userspace interface to the Chrome OS Embedded Controller"); | |
331 | MODULE_VERSION("1.0"); | |
332 | MODULE_LICENSE("GPL"); |