1ec2daf6eSJon Flatley // SPDX-License-Identifier: GPL-2.0-only
2ec2daf6eSJon Flatley /*
3ec2daf6eSJon Flatley  * Copyright 2020 Google LLC
4ec2daf6eSJon Flatley  *
5ec2daf6eSJon Flatley  * This driver serves as the receiver of cros_ec PD host events.
6ec2daf6eSJon Flatley  */
7ec2daf6eSJon Flatley 
8ec2daf6eSJon Flatley #include <linux/acpi.h>
9ec2daf6eSJon Flatley #include <linux/module.h>
10ec2daf6eSJon Flatley #include <linux/platform_data/cros_ec_proto.h>
11ec2daf6eSJon Flatley #include <linux/platform_data/cros_usbpd_notify.h>
12ec2daf6eSJon Flatley #include <linux/platform_device.h>
13ec2daf6eSJon Flatley 
14ec2daf6eSJon Flatley #define DRV_NAME "cros-usbpd-notify"
157e91e1acSPrashant Malani #define DRV_NAME_PLAT_ACPI "cros-usbpd-notify-acpi"
16ec2daf6eSJon Flatley #define ACPI_DRV_NAME "GOOG0003"
17ec2daf6eSJon Flatley 
18ec2daf6eSJon Flatley static BLOCKING_NOTIFIER_HEAD(cros_usbpd_notifier_list);
19ec2daf6eSJon Flatley 
20f5d84a21SPrashant Malani struct cros_usbpd_notify_data {
21f5d84a21SPrashant Malani 	struct device *dev;
22f5d84a21SPrashant Malani 	struct cros_ec_device *ec;
23f5d84a21SPrashant Malani 	struct notifier_block nb;
24f5d84a21SPrashant Malani };
25f5d84a21SPrashant Malani 
26ec2daf6eSJon Flatley /**
27ec2daf6eSJon Flatley  * cros_usbpd_register_notify - Register a notifier callback for PD events.
28ec2daf6eSJon Flatley  * @nb: Notifier block pointer to register
29ec2daf6eSJon Flatley  *
30ec2daf6eSJon Flatley  * On ACPI platforms this corresponds to host events on the ECPD
31ec2daf6eSJon Flatley  * "GOOG0003" ACPI device. On non-ACPI platforms this will filter mkbp events
32ec2daf6eSJon Flatley  * for USB PD events.
33ec2daf6eSJon Flatley  *
34ec2daf6eSJon Flatley  * Return: 0 on success or negative error code.
35ec2daf6eSJon Flatley  */
36ec2daf6eSJon Flatley int cros_usbpd_register_notify(struct notifier_block *nb)
37ec2daf6eSJon Flatley {
38ec2daf6eSJon Flatley 	return blocking_notifier_chain_register(&cros_usbpd_notifier_list,
39ec2daf6eSJon Flatley 						nb);
40ec2daf6eSJon Flatley }
41ec2daf6eSJon Flatley EXPORT_SYMBOL_GPL(cros_usbpd_register_notify);
42ec2daf6eSJon Flatley 
43ec2daf6eSJon Flatley /**
44ec2daf6eSJon Flatley  * cros_usbpd_unregister_notify - Unregister notifier callback for PD events.
45ec2daf6eSJon Flatley  * @nb: Notifier block pointer to unregister
46ec2daf6eSJon Flatley  *
47ec2daf6eSJon Flatley  * Unregister a notifier callback that was previously registered with
48ec2daf6eSJon Flatley  * cros_usbpd_register_notify().
49ec2daf6eSJon Flatley  */
50ec2daf6eSJon Flatley void cros_usbpd_unregister_notify(struct notifier_block *nb)
51ec2daf6eSJon Flatley {
52ec2daf6eSJon Flatley 	blocking_notifier_chain_unregister(&cros_usbpd_notifier_list, nb);
53ec2daf6eSJon Flatley }
54ec2daf6eSJon Flatley EXPORT_SYMBOL_GPL(cros_usbpd_unregister_notify);
55ec2daf6eSJon Flatley 
56ec2daf6eSJon Flatley #ifdef CONFIG_ACPI
57ec2daf6eSJon Flatley 
587e91e1acSPrashant Malani static void cros_usbpd_notify_acpi(acpi_handle device, u32 event, void *data)
59ec2daf6eSJon Flatley {
607e91e1acSPrashant Malani 	blocking_notifier_call_chain(&cros_usbpd_notifier_list, event, NULL);
617e91e1acSPrashant Malani }
627e91e1acSPrashant Malani 
637e91e1acSPrashant Malani static int cros_usbpd_notify_probe_acpi(struct platform_device *pdev)
647e91e1acSPrashant Malani {
657e91e1acSPrashant Malani 	struct cros_usbpd_notify_data *pdnotify;
667e91e1acSPrashant Malani 	struct device *dev = &pdev->dev;
677e91e1acSPrashant Malani 	struct acpi_device *adev;
687e91e1acSPrashant Malani 	struct cros_ec_device *ec_dev;
697e91e1acSPrashant Malani 	acpi_status status;
707e91e1acSPrashant Malani 
717e91e1acSPrashant Malani 	adev = ACPI_COMPANION(dev);
727e91e1acSPrashant Malani 
737e91e1acSPrashant Malani 	pdnotify = devm_kzalloc(dev, sizeof(*pdnotify), GFP_KERNEL);
747e91e1acSPrashant Malani 	if (!pdnotify)
757e91e1acSPrashant Malani 		return -ENOMEM;
767e91e1acSPrashant Malani 
777e91e1acSPrashant Malani 	/* Get the EC device pointer needed to talk to the EC. */
787e91e1acSPrashant Malani 	ec_dev = dev_get_drvdata(dev->parent);
797e91e1acSPrashant Malani 	if (!ec_dev) {
807e91e1acSPrashant Malani 		/*
817e91e1acSPrashant Malani 		 * We continue even for older devices which don't have the
827e91e1acSPrashant Malani 		 * correct device heirarchy, namely, GOOG0003 is a child
837e91e1acSPrashant Malani 		 * of GOOG0004.
847e91e1acSPrashant Malani 		 */
857e91e1acSPrashant Malani 		dev_warn(dev, "Couldn't get Chrome EC device pointer.\n");
867e91e1acSPrashant Malani 	}
877e91e1acSPrashant Malani 
887e91e1acSPrashant Malani 	pdnotify->dev = dev;
897e91e1acSPrashant Malani 	pdnotify->ec = ec_dev;
907e91e1acSPrashant Malani 
917e91e1acSPrashant Malani 	status = acpi_install_notify_handler(adev->handle,
927e91e1acSPrashant Malani 					     ACPI_ALL_NOTIFY,
937e91e1acSPrashant Malani 					     cros_usbpd_notify_acpi,
947e91e1acSPrashant Malani 					     pdnotify);
957e91e1acSPrashant Malani 	if (ACPI_FAILURE(status)) {
967e91e1acSPrashant Malani 		dev_warn(dev, "Failed to register notify handler %08x\n",
977e91e1acSPrashant Malani 			 status);
987e91e1acSPrashant Malani 		return -EINVAL;
997e91e1acSPrashant Malani 	}
1007e91e1acSPrashant Malani 
101ec2daf6eSJon Flatley 	return 0;
102ec2daf6eSJon Flatley }
103ec2daf6eSJon Flatley 
1047e91e1acSPrashant Malani static int cros_usbpd_notify_remove_acpi(struct platform_device *pdev)
105ec2daf6eSJon Flatley {
1067e91e1acSPrashant Malani 	struct device *dev = &pdev->dev;
1077e91e1acSPrashant Malani 	struct acpi_device *adev = ACPI_COMPANION(dev);
1087e91e1acSPrashant Malani 
1097e91e1acSPrashant Malani 	acpi_remove_notify_handler(adev->handle, ACPI_ALL_NOTIFY,
1107e91e1acSPrashant Malani 				   cros_usbpd_notify_acpi);
1117e91e1acSPrashant Malani 
1127e91e1acSPrashant Malani 	return 0;
113ec2daf6eSJon Flatley }
114ec2daf6eSJon Flatley 
115ec2daf6eSJon Flatley static const struct acpi_device_id cros_usbpd_notify_acpi_device_ids[] = {
116ec2daf6eSJon Flatley 	{ ACPI_DRV_NAME, 0 },
117ec2daf6eSJon Flatley 	{ }
118ec2daf6eSJon Flatley };
119ec2daf6eSJon Flatley MODULE_DEVICE_TABLE(acpi, cros_usbpd_notify_acpi_device_ids);
120ec2daf6eSJon Flatley 
1217e91e1acSPrashant Malani static struct platform_driver cros_usbpd_notify_acpi_driver = {
1227e91e1acSPrashant Malani 	.driver = {
1237e91e1acSPrashant Malani 		.name = DRV_NAME_PLAT_ACPI,
1247e91e1acSPrashant Malani 		.acpi_match_table = cros_usbpd_notify_acpi_device_ids,
125ec2daf6eSJon Flatley 	},
1267e91e1acSPrashant Malani 	.probe = cros_usbpd_notify_probe_acpi,
1277e91e1acSPrashant Malani 	.remove = cros_usbpd_notify_remove_acpi,
128ec2daf6eSJon Flatley };
129ec2daf6eSJon Flatley 
130ec2daf6eSJon Flatley #endif /* CONFIG_ACPI */
131ec2daf6eSJon Flatley 
132ec2daf6eSJon Flatley static int cros_usbpd_notify_plat(struct notifier_block *nb,
133ec2daf6eSJon Flatley 				  unsigned long queued_during_suspend,
134ec2daf6eSJon Flatley 				  void *data)
135ec2daf6eSJon Flatley {
136ec2daf6eSJon Flatley 	struct cros_ec_device *ec_dev = (struct cros_ec_device *)data;
137ec2daf6eSJon Flatley 	u32 host_event = cros_ec_get_host_event(ec_dev);
138ec2daf6eSJon Flatley 
139ec2daf6eSJon Flatley 	if (!host_event)
14092e399c0SGwendal Grignou 		return NOTIFY_DONE;
141ec2daf6eSJon Flatley 
142ec2daf6eSJon Flatley 	if (host_event & EC_HOST_EVENT_MASK(EC_HOST_EVENT_PD_MCU)) {
143ec2daf6eSJon Flatley 		blocking_notifier_call_chain(&cros_usbpd_notifier_list,
144ec2daf6eSJon Flatley 					     host_event, NULL);
145ec2daf6eSJon Flatley 		return NOTIFY_OK;
146ec2daf6eSJon Flatley 	}
147ec2daf6eSJon Flatley 	return NOTIFY_DONE;
148ec2daf6eSJon Flatley }
149ec2daf6eSJon Flatley 
150ec2daf6eSJon Flatley static int cros_usbpd_notify_probe_plat(struct platform_device *pdev)
151ec2daf6eSJon Flatley {
152ec2daf6eSJon Flatley 	struct device *dev = &pdev->dev;
153ec2daf6eSJon Flatley 	struct cros_ec_dev *ecdev = dev_get_drvdata(dev->parent);
154f5d84a21SPrashant Malani 	struct cros_usbpd_notify_data *pdnotify;
155ec2daf6eSJon Flatley 	int ret;
156ec2daf6eSJon Flatley 
157f5d84a21SPrashant Malani 	pdnotify = devm_kzalloc(dev, sizeof(*pdnotify), GFP_KERNEL);
158f5d84a21SPrashant Malani 	if (!pdnotify)
159ec2daf6eSJon Flatley 		return -ENOMEM;
160ec2daf6eSJon Flatley 
161f5d84a21SPrashant Malani 	pdnotify->dev = dev;
162f5d84a21SPrashant Malani 	pdnotify->ec = ecdev->ec_dev;
163f5d84a21SPrashant Malani 	pdnotify->nb.notifier_call = cros_usbpd_notify_plat;
164f5d84a21SPrashant Malani 
165f5d84a21SPrashant Malani 	dev_set_drvdata(dev, pdnotify);
166ec2daf6eSJon Flatley 
167ec2daf6eSJon Flatley 	ret = blocking_notifier_chain_register(&ecdev->ec_dev->event_notifier,
168f5d84a21SPrashant Malani 					       &pdnotify->nb);
169ec2daf6eSJon Flatley 	if (ret < 0) {
170ec2daf6eSJon Flatley 		dev_err(dev, "Failed to register notifier\n");
171ec2daf6eSJon Flatley 		return ret;
172ec2daf6eSJon Flatley 	}
173ec2daf6eSJon Flatley 
174ec2daf6eSJon Flatley 	return 0;
175ec2daf6eSJon Flatley }
176ec2daf6eSJon Flatley 
177ec2daf6eSJon Flatley static int cros_usbpd_notify_remove_plat(struct platform_device *pdev)
178ec2daf6eSJon Flatley {
179ec2daf6eSJon Flatley 	struct device *dev = &pdev->dev;
180ec2daf6eSJon Flatley 	struct cros_ec_dev *ecdev = dev_get_drvdata(dev->parent);
181f5d84a21SPrashant Malani 	struct cros_usbpd_notify_data *pdnotify =
182f5d84a21SPrashant Malani 		(struct cros_usbpd_notify_data *)dev_get_drvdata(dev);
183ec2daf6eSJon Flatley 
184f5d84a21SPrashant Malani 	blocking_notifier_chain_unregister(&ecdev->ec_dev->event_notifier,
185f5d84a21SPrashant Malani 					   &pdnotify->nb);
186ec2daf6eSJon Flatley 
187ec2daf6eSJon Flatley 	return 0;
188ec2daf6eSJon Flatley }
189ec2daf6eSJon Flatley 
190ec2daf6eSJon Flatley static struct platform_driver cros_usbpd_notify_plat_driver = {
191ec2daf6eSJon Flatley 	.driver = {
192ec2daf6eSJon Flatley 		.name = DRV_NAME,
193ec2daf6eSJon Flatley 	},
194ec2daf6eSJon Flatley 	.probe = cros_usbpd_notify_probe_plat,
195ec2daf6eSJon Flatley 	.remove = cros_usbpd_notify_remove_plat,
196ec2daf6eSJon Flatley };
197ec2daf6eSJon Flatley 
198ec2daf6eSJon Flatley static int __init cros_usbpd_notify_init(void)
199ec2daf6eSJon Flatley {
200ec2daf6eSJon Flatley 	int ret;
201ec2daf6eSJon Flatley 
202ec2daf6eSJon Flatley 	ret = platform_driver_register(&cros_usbpd_notify_plat_driver);
203ec2daf6eSJon Flatley 	if (ret < 0)
204ec2daf6eSJon Flatley 		return ret;
205ec2daf6eSJon Flatley 
206ec2daf6eSJon Flatley #ifdef CONFIG_ACPI
2077e91e1acSPrashant Malani 	platform_driver_register(&cros_usbpd_notify_acpi_driver);
208ec2daf6eSJon Flatley #endif
209ec2daf6eSJon Flatley 	return 0;
210ec2daf6eSJon Flatley }
211ec2daf6eSJon Flatley 
212ec2daf6eSJon Flatley static void __exit cros_usbpd_notify_exit(void)
213ec2daf6eSJon Flatley {
214ec2daf6eSJon Flatley #ifdef CONFIG_ACPI
2157e91e1acSPrashant Malani 	platform_driver_unregister(&cros_usbpd_notify_acpi_driver);
216ec2daf6eSJon Flatley #endif
217ec2daf6eSJon Flatley 	platform_driver_unregister(&cros_usbpd_notify_plat_driver);
218ec2daf6eSJon Flatley }
219ec2daf6eSJon Flatley 
220ec2daf6eSJon Flatley module_init(cros_usbpd_notify_init);
221ec2daf6eSJon Flatley module_exit(cros_usbpd_notify_exit);
222ec2daf6eSJon Flatley 
223ec2daf6eSJon Flatley MODULE_LICENSE("GPL");
224ec2daf6eSJon Flatley MODULE_DESCRIPTION("ChromeOS power delivery notifier device");
225ec2daf6eSJon Flatley MODULE_AUTHOR("Jon Flatley <jflat@chromium.org>");
226ec2daf6eSJon Flatley MODULE_ALIAS("platform:" DRV_NAME);
227