xref: /linux/drivers/platform/chrome/cros_usbpd_notify.c (revision f4cdf7ca9a1fdcca413157df19753f388a5a224e)
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  */
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  */
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 
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 
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 
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 
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 
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 
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 
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 
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 
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