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