xref: /openbmc/linux/drivers/mfd/cros_ec_dev.c (revision dc0c386e)
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