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