1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Core driver for Wilco Embedded Controller
4  *
5  * Copyright 2018 Google LLC
6  *
7  * This is the entry point for the drivers that control the Wilco EC.
8  */
9 
10 #include <linux/acpi.h>
11 #include <linux/device.h>
12 #include <linux/ioport.h>
13 #include <linux/mod_devicetable.h>
14 #include <linux/module.h>
15 #include <linux/platform_data/wilco-ec.h>
16 #include <linux/platform_device.h>
17 
18 #include "../cros_ec_lpc_mec.h"
19 
20 #define DRV_NAME "wilco-ec"
21 
wilco_get_resource(struct platform_device * pdev,int index)22 static struct resource *wilco_get_resource(struct platform_device *pdev,
23 					   int index)
24 {
25 	struct device *dev = &pdev->dev;
26 	struct resource *res;
27 
28 	res = platform_get_resource(pdev, IORESOURCE_IO, index);
29 	if (!res) {
30 		dev_dbg(dev, "Couldn't find IO resource %d\n", index);
31 		return res;
32 	}
33 
34 	return devm_request_region(dev, res->start, resource_size(res),
35 				   dev_name(dev));
36 }
37 
wilco_ec_probe(struct platform_device * pdev)38 static int wilco_ec_probe(struct platform_device *pdev)
39 {
40 	struct device *dev = &pdev->dev;
41 	struct wilco_ec_device *ec;
42 	int ret;
43 
44 	ec = devm_kzalloc(dev, sizeof(*ec), GFP_KERNEL);
45 	if (!ec)
46 		return -ENOMEM;
47 
48 	platform_set_drvdata(pdev, ec);
49 	ec->dev = dev;
50 	mutex_init(&ec->mailbox_lock);
51 
52 	ec->data_size = sizeof(struct wilco_ec_response) + EC_MAILBOX_DATA_SIZE;
53 	ec->data_buffer = devm_kzalloc(dev, ec->data_size, GFP_KERNEL);
54 	if (!ec->data_buffer)
55 		return -ENOMEM;
56 
57 	/* Prepare access to IO regions provided by ACPI */
58 	ec->io_data = wilco_get_resource(pdev, 0);	/* Host Data */
59 	ec->io_command = wilco_get_resource(pdev, 1);	/* Host Command */
60 	ec->io_packet = wilco_get_resource(pdev, 2);	/* MEC EMI */
61 	if (!ec->io_data || !ec->io_command || !ec->io_packet)
62 		return -ENODEV;
63 
64 	/* Initialize cros_ec register interface for communication */
65 	cros_ec_lpc_mec_init(ec->io_packet->start,
66 			     ec->io_packet->start + EC_MAILBOX_DATA_SIZE);
67 
68 	/*
69 	 * Register a child device that will be found by the debugfs driver.
70 	 * Ignore failure.
71 	 */
72 	ec->debugfs_pdev = platform_device_register_data(dev,
73 							 "wilco-ec-debugfs",
74 							 PLATFORM_DEVID_AUTO,
75 							 NULL, 0);
76 
77 	/* Register a child device that will be found by the RTC driver. */
78 	ec->rtc_pdev = platform_device_register_data(dev, "rtc-wilco-ec",
79 						     PLATFORM_DEVID_AUTO,
80 						     NULL, 0);
81 	if (IS_ERR(ec->rtc_pdev)) {
82 		dev_err(dev, "Failed to create RTC platform device\n");
83 		ret = PTR_ERR(ec->rtc_pdev);
84 		goto unregister_debugfs;
85 	}
86 
87 	/* Set up the keyboard backlight LEDs. */
88 	ret = wilco_keyboard_leds_init(ec);
89 	if (ret < 0) {
90 		dev_err(dev,
91 			"Failed to initialize keyboard LEDs: %d\n",
92 			ret);
93 		goto unregister_rtc;
94 	}
95 
96 	ret = wilco_ec_add_sysfs(ec);
97 	if (ret < 0) {
98 		dev_err(dev, "Failed to create sysfs entries: %d\n", ret);
99 		goto unregister_rtc;
100 	}
101 
102 	/* Register child device to be found by charger config driver. */
103 	ec->charger_pdev = platform_device_register_data(dev, "wilco-charger",
104 							 PLATFORM_DEVID_AUTO,
105 							 NULL, 0);
106 	if (IS_ERR(ec->charger_pdev)) {
107 		dev_err(dev, "Failed to create charger platform device\n");
108 		ret = PTR_ERR(ec->charger_pdev);
109 		goto remove_sysfs;
110 	}
111 
112 	/* Register child device that will be found by the telemetry driver. */
113 	ec->telem_pdev = platform_device_register_data(dev, "wilco_telem",
114 						       PLATFORM_DEVID_AUTO,
115 						       ec, sizeof(*ec));
116 	if (IS_ERR(ec->telem_pdev)) {
117 		dev_err(dev, "Failed to create telemetry platform device\n");
118 		ret = PTR_ERR(ec->telem_pdev);
119 		goto unregister_charge_config;
120 	}
121 
122 	return 0;
123 
124 unregister_charge_config:
125 	platform_device_unregister(ec->charger_pdev);
126 remove_sysfs:
127 	wilco_ec_remove_sysfs(ec);
128 unregister_rtc:
129 	platform_device_unregister(ec->rtc_pdev);
130 unregister_debugfs:
131 	if (ec->debugfs_pdev)
132 		platform_device_unregister(ec->debugfs_pdev);
133 	return ret;
134 }
135 
wilco_ec_remove(struct platform_device * pdev)136 static void wilco_ec_remove(struct platform_device *pdev)
137 {
138 	struct wilco_ec_device *ec = platform_get_drvdata(pdev);
139 
140 	platform_device_unregister(ec->telem_pdev);
141 	platform_device_unregister(ec->charger_pdev);
142 	wilco_ec_remove_sysfs(ec);
143 	platform_device_unregister(ec->rtc_pdev);
144 	if (ec->debugfs_pdev)
145 		platform_device_unregister(ec->debugfs_pdev);
146 }
147 
148 static const struct acpi_device_id wilco_ec_acpi_device_ids[] = {
149 	{ "GOOG000C", 0 },
150 	{ }
151 };
152 MODULE_DEVICE_TABLE(acpi, wilco_ec_acpi_device_ids);
153 
154 static const struct platform_device_id wilco_ec_id[] = {
155 	{ DRV_NAME, 0 },
156 	{}
157 };
158 MODULE_DEVICE_TABLE(platform, wilco_ec_id);
159 
160 static struct platform_driver wilco_ec_driver = {
161 	.driver = {
162 		.name = DRV_NAME,
163 		.acpi_match_table = wilco_ec_acpi_device_ids,
164 	},
165 	.probe = wilco_ec_probe,
166 	.remove_new = wilco_ec_remove,
167 	.id_table = wilco_ec_id,
168 };
169 
170 module_platform_driver(wilco_ec_driver);
171 
172 MODULE_AUTHOR("Nick Crews <ncrews@chromium.org>");
173 MODULE_AUTHOR("Duncan Laurie <dlaurie@chromium.org>");
174 MODULE_LICENSE("GPL v2");
175 MODULE_DESCRIPTION("ChromeOS Wilco Embedded Controller driver");
176