1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3 * Copyright 2020 Google LLC
4 *
5 * This driver serves as the receiver of cros_ec PD host events.
6 */
7
8 #include <linux/acpi.h>
9 #include <linux/fwnode.h>
10 #include <linux/module.h>
11 #include <linux/platform_data/cros_ec_proto.h>
12 #include <linux/platform_data/cros_usbpd_notify.h>
13 #include <linux/platform_device.h>
14
15 #define DRV_NAME "cros-usbpd-notify"
16 #define DRV_NAME_PLAT_ACPI "cros-usbpd-notify-acpi"
17 #define ACPI_DRV_NAME "GOOG0003"
18 #define CREC_DRV_NAME "GOOG0004"
19
20 static BLOCKING_NOTIFIER_HEAD(cros_usbpd_notifier_list);
21
22 struct cros_usbpd_notify_data {
23 struct device *dev;
24 struct cros_ec_device *ec;
25 struct notifier_block nb;
26 };
27
28 /**
29 * cros_usbpd_register_notify - Register a notifier callback for PD events.
30 * @nb: Notifier block pointer to register
31 *
32 * On ACPI platforms this corresponds to host events on the ECPD
33 * "GOOG0003" ACPI device. On non-ACPI platforms this will filter mkbp events
34 * for USB PD events.
35 *
36 * Return: 0 on success or negative error code.
37 */
cros_usbpd_register_notify(struct notifier_block * nb)38 int cros_usbpd_register_notify(struct notifier_block *nb)
39 {
40 return blocking_notifier_chain_register(&cros_usbpd_notifier_list,
41 nb);
42 }
43 EXPORT_SYMBOL_GPL(cros_usbpd_register_notify);
44
45 /**
46 * cros_usbpd_unregister_notify - Unregister notifier callback for PD events.
47 * @nb: Notifier block pointer to unregister
48 *
49 * Unregister a notifier callback that was previously registered with
50 * cros_usbpd_register_notify().
51 */
cros_usbpd_unregister_notify(struct notifier_block * nb)52 void cros_usbpd_unregister_notify(struct notifier_block *nb)
53 {
54 blocking_notifier_chain_unregister(&cros_usbpd_notifier_list, nb);
55 }
56 EXPORT_SYMBOL_GPL(cros_usbpd_unregister_notify);
57
cros_usbpd_get_event_and_notify(struct device * dev,struct cros_ec_device * ec_dev)58 static void cros_usbpd_get_event_and_notify(struct device *dev,
59 struct cros_ec_device *ec_dev)
60 {
61 struct ec_response_host_event_status host_event_status;
62 u32 event = 0;
63 int ret;
64
65 /*
66 * We still send a 0 event out to older devices which don't
67 * have the updated device heirarchy.
68 */
69 if (!ec_dev) {
70 dev_dbg(dev,
71 "EC device inaccessible; sending 0 event status.\n");
72 goto send_notify;
73 }
74
75 /* Check for PD host events on EC. */
76 ret = cros_ec_cmd(ec_dev, 0, EC_CMD_PD_HOST_EVENT_STATUS,
77 NULL, 0, &host_event_status, sizeof(host_event_status));
78 if (ret < 0) {
79 dev_warn(dev, "Can't get host event status (err: %d)\n", ret);
80 goto send_notify;
81 }
82
83 event = host_event_status.status;
84
85 send_notify:
86 blocking_notifier_call_chain(&cros_usbpd_notifier_list, event, NULL);
87 }
88
89 #ifdef CONFIG_ACPI
90
cros_usbpd_notify_acpi(acpi_handle device,u32 event,void * data)91 static void cros_usbpd_notify_acpi(acpi_handle device, u32 event, void *data)
92 {
93 struct cros_usbpd_notify_data *pdnotify = data;
94
95 cros_usbpd_get_event_and_notify(pdnotify->dev, pdnotify->ec);
96 }
97
cros_usbpd_notify_probe_acpi(struct platform_device * pdev)98 static int cros_usbpd_notify_probe_acpi(struct platform_device *pdev)
99 {
100 struct cros_usbpd_notify_data *pdnotify;
101 struct device *dev = &pdev->dev;
102 struct acpi_device *adev, *parent_adev;
103 struct cros_ec_device *ec_dev;
104 struct fwnode_handle *parent_fwnode;
105 acpi_status status;
106
107 adev = ACPI_COMPANION(dev);
108
109 pdnotify = devm_kzalloc(dev, sizeof(*pdnotify), GFP_KERNEL);
110 if (!pdnotify)
111 return -ENOMEM;
112
113 /* Get the EC device pointer needed to talk to the EC. */
114 ec_dev = dev_get_drvdata(dev->parent);
115 if (!ec_dev) {
116 /*
117 * We continue even for older devices which don't have the
118 * correct device heirarchy, namely, GOOG0003 is a child
119 * of GOOG0004. If GOOG0003 is a child of GOOG0004 and we
120 * can't get a pointer to the Chrome EC device, defer the
121 * probe function.
122 */
123 parent_fwnode = fwnode_get_parent(dev->fwnode);
124 if (parent_fwnode) {
125 parent_adev = to_acpi_device_node(parent_fwnode);
126 if (parent_adev &&
127 acpi_dev_hid_match(parent_adev, CREC_DRV_NAME)) {
128 return -EPROBE_DEFER;
129 }
130 }
131 dev_warn(dev, "Couldn't get Chrome EC device pointer.\n");
132 }
133
134 pdnotify->dev = dev;
135 pdnotify->ec = ec_dev;
136
137 status = acpi_install_notify_handler(adev->handle,
138 ACPI_ALL_NOTIFY,
139 cros_usbpd_notify_acpi,
140 pdnotify);
141 if (ACPI_FAILURE(status)) {
142 dev_warn(dev, "Failed to register notify handler %08x\n",
143 status);
144 return -EINVAL;
145 }
146
147 return 0;
148 }
149
cros_usbpd_notify_remove_acpi(struct platform_device * pdev)150 static void cros_usbpd_notify_remove_acpi(struct platform_device *pdev)
151 {
152 struct device *dev = &pdev->dev;
153 struct acpi_device *adev = ACPI_COMPANION(dev);
154
155 acpi_remove_notify_handler(adev->handle, ACPI_ALL_NOTIFY,
156 cros_usbpd_notify_acpi);
157 }
158
159 static const struct acpi_device_id cros_usbpd_notify_acpi_device_ids[] = {
160 { ACPI_DRV_NAME, 0 },
161 { }
162 };
163 MODULE_DEVICE_TABLE(acpi, cros_usbpd_notify_acpi_device_ids);
164
165 static struct platform_driver cros_usbpd_notify_acpi_driver = {
166 .driver = {
167 .name = DRV_NAME_PLAT_ACPI,
168 .acpi_match_table = cros_usbpd_notify_acpi_device_ids,
169 },
170 .probe = cros_usbpd_notify_probe_acpi,
171 .remove = cros_usbpd_notify_remove_acpi,
172 };
173
174 #endif /* CONFIG_ACPI */
175
cros_usbpd_notify_plat(struct notifier_block * nb,unsigned long queued_during_suspend,void * data)176 static int cros_usbpd_notify_plat(struct notifier_block *nb,
177 unsigned long queued_during_suspend,
178 void *data)
179 {
180 struct cros_usbpd_notify_data *pdnotify = container_of(nb,
181 struct cros_usbpd_notify_data, nb);
182 struct cros_ec_device *ec_dev = (struct cros_ec_device *)data;
183 u32 host_event = cros_ec_get_host_event(ec_dev);
184
185 if (!host_event)
186 return NOTIFY_DONE;
187
188 if (host_event & (EC_HOST_EVENT_MASK(EC_HOST_EVENT_PD_MCU) |
189 EC_HOST_EVENT_MASK(EC_HOST_EVENT_USB_MUX))) {
190 cros_usbpd_get_event_and_notify(pdnotify->dev, ec_dev);
191 return NOTIFY_OK;
192 }
193 return NOTIFY_DONE;
194 }
195
cros_usbpd_notify_probe_plat(struct platform_device * pdev)196 static int cros_usbpd_notify_probe_plat(struct platform_device *pdev)
197 {
198 struct device *dev = &pdev->dev;
199 struct cros_ec_dev *ecdev = dev_get_drvdata(dev->parent);
200 struct cros_usbpd_notify_data *pdnotify;
201 int ret;
202
203 pdnotify = devm_kzalloc(dev, sizeof(*pdnotify), GFP_KERNEL);
204 if (!pdnotify)
205 return -ENOMEM;
206
207 pdnotify->dev = dev;
208 pdnotify->ec = ecdev->ec_dev;
209 pdnotify->nb.notifier_call = cros_usbpd_notify_plat;
210
211 dev_set_drvdata(dev, pdnotify);
212
213 ret = blocking_notifier_chain_register(&ecdev->ec_dev->event_notifier,
214 &pdnotify->nb);
215 if (ret < 0) {
216 dev_err(dev, "Failed to register notifier\n");
217 return ret;
218 }
219
220 return 0;
221 }
222
cros_usbpd_notify_remove_plat(struct platform_device * pdev)223 static void cros_usbpd_notify_remove_plat(struct platform_device *pdev)
224 {
225 struct device *dev = &pdev->dev;
226 struct cros_ec_dev *ecdev = dev_get_drvdata(dev->parent);
227 struct cros_usbpd_notify_data *pdnotify =
228 (struct cros_usbpd_notify_data *)dev_get_drvdata(dev);
229
230 blocking_notifier_chain_unregister(&ecdev->ec_dev->event_notifier,
231 &pdnotify->nb);
232 }
233
234 static const struct platform_device_id cros_usbpd_notify_id[] = {
235 { .name = DRV_NAME },
236 { }
237 };
238 MODULE_DEVICE_TABLE(platform, cros_usbpd_notify_id);
239
240 static struct platform_driver cros_usbpd_notify_plat_driver = {
241 .driver = {
242 .name = DRV_NAME,
243 },
244 .probe = cros_usbpd_notify_probe_plat,
245 .remove = cros_usbpd_notify_remove_plat,
246 .id_table = cros_usbpd_notify_id,
247 };
248
cros_usbpd_notify_init(void)249 static int __init cros_usbpd_notify_init(void)
250 {
251 int ret;
252
253 ret = platform_driver_register(&cros_usbpd_notify_plat_driver);
254 if (ret < 0)
255 return ret;
256
257 #ifdef CONFIG_ACPI
258 ret = platform_driver_register(&cros_usbpd_notify_acpi_driver);
259 if (ret) {
260 platform_driver_unregister(&cros_usbpd_notify_plat_driver);
261 return ret;
262 }
263 #endif
264 return 0;
265 }
266
cros_usbpd_notify_exit(void)267 static void __exit cros_usbpd_notify_exit(void)
268 {
269 #ifdef CONFIG_ACPI
270 platform_driver_unregister(&cros_usbpd_notify_acpi_driver);
271 #endif
272 platform_driver_unregister(&cros_usbpd_notify_plat_driver);
273 }
274
275 module_init(cros_usbpd_notify_init);
276 module_exit(cros_usbpd_notify_exit);
277
278 MODULE_LICENSE("GPL");
279 MODULE_DESCRIPTION("ChromeOS power delivery notifier device");
280 MODULE_AUTHOR("Jon Flatley <jflat@chromium.org>");
281