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 56a8821408SPrashant Malani /** 57a8821408SPrashant Malani * cros_ec_pd_command - Send a command to the EC. 58a8821408SPrashant Malani * 59a8821408SPrashant Malani * @ec_dev: EC device 60a8821408SPrashant Malani * @command: EC command 61a8821408SPrashant Malani * @outdata: EC command output data 62a8821408SPrashant Malani * @outsize: Size of outdata 63a8821408SPrashant Malani * @indata: EC command input data 64a8821408SPrashant Malani * @insize: Size of indata 65a8821408SPrashant Malani * 66a8821408SPrashant Malani * Return: >= 0 on success, negative error number on failure. 67a8821408SPrashant Malani */ 68a8821408SPrashant Malani static int cros_ec_pd_command(struct cros_ec_device *ec_dev, 69a8821408SPrashant Malani int command, 70a8821408SPrashant Malani uint8_t *outdata, 71a8821408SPrashant Malani int outsize, 72a8821408SPrashant Malani uint8_t *indata, 73a8821408SPrashant Malani int insize) 74a8821408SPrashant Malani { 75a8821408SPrashant Malani struct cros_ec_command *msg; 76a8821408SPrashant Malani int ret; 77a8821408SPrashant Malani 78a8821408SPrashant Malani msg = kzalloc(sizeof(*msg) + max(insize, outsize), GFP_KERNEL); 79a8821408SPrashant Malani if (!msg) 80a8821408SPrashant Malani return -ENOMEM; 81a8821408SPrashant Malani 82a8821408SPrashant Malani msg->command = command; 83a8821408SPrashant Malani msg->outsize = outsize; 84a8821408SPrashant Malani msg->insize = insize; 85a8821408SPrashant Malani 86a8821408SPrashant Malani if (outsize) 87a8821408SPrashant Malani memcpy(msg->data, outdata, outsize); 88a8821408SPrashant Malani 89a8821408SPrashant Malani ret = cros_ec_cmd_xfer_status(ec_dev, msg); 90a8821408SPrashant Malani if (ret < 0) 91a8821408SPrashant Malani goto error; 92a8821408SPrashant Malani 93a8821408SPrashant Malani if (insize) 94a8821408SPrashant Malani memcpy(indata, msg->data, insize); 95a8821408SPrashant Malani error: 96a8821408SPrashant Malani kfree(msg); 97a8821408SPrashant Malani return ret; 98a8821408SPrashant Malani } 99a8821408SPrashant Malani 100a8821408SPrashant Malani static void cros_usbpd_get_event_and_notify(struct device *dev, 101a8821408SPrashant Malani struct cros_ec_device *ec_dev) 102a8821408SPrashant Malani { 103a8821408SPrashant Malani struct ec_response_host_event_status host_event_status; 104a8821408SPrashant Malani u32 event = 0; 105a8821408SPrashant Malani int ret; 106a8821408SPrashant Malani 107a8821408SPrashant Malani /* 108a8821408SPrashant Malani * We still send a 0 event out to older devices which don't 109a8821408SPrashant Malani * have the updated device heirarchy. 110a8821408SPrashant Malani */ 111a8821408SPrashant Malani if (!ec_dev) { 112a8821408SPrashant Malani dev_dbg(dev, 113a8821408SPrashant Malani "EC device inaccessible; sending 0 event status.\n"); 114a8821408SPrashant Malani goto send_notify; 115a8821408SPrashant Malani } 116a8821408SPrashant Malani 117a8821408SPrashant Malani /* Check for PD host events on EC. */ 118a8821408SPrashant Malani ret = cros_ec_pd_command(ec_dev, EC_CMD_PD_HOST_EVENT_STATUS, 119a8821408SPrashant Malani NULL, 0, 120a8821408SPrashant Malani (uint8_t *)&host_event_status, 121a8821408SPrashant Malani sizeof(host_event_status)); 122a8821408SPrashant Malani if (ret < 0) { 123a8821408SPrashant Malani dev_warn(dev, "Can't get host event status (err: %d)\n", ret); 124a8821408SPrashant Malani goto send_notify; 125a8821408SPrashant Malani } 126a8821408SPrashant Malani 127a8821408SPrashant Malani event = host_event_status.status; 128a8821408SPrashant Malani 129a8821408SPrashant Malani send_notify: 130a8821408SPrashant Malani blocking_notifier_call_chain(&cros_usbpd_notifier_list, event, NULL); 131a8821408SPrashant Malani } 132a8821408SPrashant Malani 133ec2daf6eSJon Flatley #ifdef CONFIG_ACPI 134ec2daf6eSJon Flatley 1357e91e1acSPrashant Malani static void cros_usbpd_notify_acpi(acpi_handle device, u32 event, void *data) 136ec2daf6eSJon Flatley { 137a8821408SPrashant Malani struct cros_usbpd_notify_data *pdnotify = data; 138a8821408SPrashant Malani 139a8821408SPrashant Malani cros_usbpd_get_event_and_notify(pdnotify->dev, pdnotify->ec); 1407e91e1acSPrashant Malani } 1417e91e1acSPrashant Malani 1427e91e1acSPrashant Malani static int cros_usbpd_notify_probe_acpi(struct platform_device *pdev) 1437e91e1acSPrashant Malani { 1447e91e1acSPrashant Malani struct cros_usbpd_notify_data *pdnotify; 1457e91e1acSPrashant Malani struct device *dev = &pdev->dev; 1467e91e1acSPrashant Malani struct acpi_device *adev; 1477e91e1acSPrashant Malani struct cros_ec_device *ec_dev; 1487e91e1acSPrashant Malani acpi_status status; 1497e91e1acSPrashant Malani 1507e91e1acSPrashant Malani adev = ACPI_COMPANION(dev); 1517e91e1acSPrashant Malani 1527e91e1acSPrashant Malani pdnotify = devm_kzalloc(dev, sizeof(*pdnotify), GFP_KERNEL); 1537e91e1acSPrashant Malani if (!pdnotify) 1547e91e1acSPrashant Malani return -ENOMEM; 1557e91e1acSPrashant Malani 1567e91e1acSPrashant Malani /* Get the EC device pointer needed to talk to the EC. */ 1577e91e1acSPrashant Malani ec_dev = dev_get_drvdata(dev->parent); 1587e91e1acSPrashant Malani if (!ec_dev) { 1597e91e1acSPrashant Malani /* 1607e91e1acSPrashant Malani * We continue even for older devices which don't have the 1617e91e1acSPrashant Malani * correct device heirarchy, namely, GOOG0003 is a child 1627e91e1acSPrashant Malani * of GOOG0004. 1637e91e1acSPrashant Malani */ 1647e91e1acSPrashant Malani dev_warn(dev, "Couldn't get Chrome EC device pointer.\n"); 1657e91e1acSPrashant Malani } 1667e91e1acSPrashant Malani 1677e91e1acSPrashant Malani pdnotify->dev = dev; 1687e91e1acSPrashant Malani pdnotify->ec = ec_dev; 1697e91e1acSPrashant Malani 1707e91e1acSPrashant Malani status = acpi_install_notify_handler(adev->handle, 1717e91e1acSPrashant Malani ACPI_ALL_NOTIFY, 1727e91e1acSPrashant Malani cros_usbpd_notify_acpi, 1737e91e1acSPrashant Malani pdnotify); 1747e91e1acSPrashant Malani if (ACPI_FAILURE(status)) { 1757e91e1acSPrashant Malani dev_warn(dev, "Failed to register notify handler %08x\n", 1767e91e1acSPrashant Malani status); 1777e91e1acSPrashant Malani return -EINVAL; 1787e91e1acSPrashant Malani } 1797e91e1acSPrashant Malani 180ec2daf6eSJon Flatley return 0; 181ec2daf6eSJon Flatley } 182ec2daf6eSJon Flatley 1837e91e1acSPrashant Malani static int cros_usbpd_notify_remove_acpi(struct platform_device *pdev) 184ec2daf6eSJon Flatley { 1857e91e1acSPrashant Malani struct device *dev = &pdev->dev; 1867e91e1acSPrashant Malani struct acpi_device *adev = ACPI_COMPANION(dev); 1877e91e1acSPrashant Malani 1887e91e1acSPrashant Malani acpi_remove_notify_handler(adev->handle, ACPI_ALL_NOTIFY, 1897e91e1acSPrashant Malani cros_usbpd_notify_acpi); 1907e91e1acSPrashant Malani 1917e91e1acSPrashant Malani return 0; 192ec2daf6eSJon Flatley } 193ec2daf6eSJon Flatley 194ec2daf6eSJon Flatley static const struct acpi_device_id cros_usbpd_notify_acpi_device_ids[] = { 195ec2daf6eSJon Flatley { ACPI_DRV_NAME, 0 }, 196ec2daf6eSJon Flatley { } 197ec2daf6eSJon Flatley }; 198ec2daf6eSJon Flatley MODULE_DEVICE_TABLE(acpi, cros_usbpd_notify_acpi_device_ids); 199ec2daf6eSJon Flatley 2007e91e1acSPrashant Malani static struct platform_driver cros_usbpd_notify_acpi_driver = { 2017e91e1acSPrashant Malani .driver = { 2027e91e1acSPrashant Malani .name = DRV_NAME_PLAT_ACPI, 2037e91e1acSPrashant Malani .acpi_match_table = cros_usbpd_notify_acpi_device_ids, 204ec2daf6eSJon Flatley }, 2057e91e1acSPrashant Malani .probe = cros_usbpd_notify_probe_acpi, 2067e91e1acSPrashant Malani .remove = cros_usbpd_notify_remove_acpi, 207ec2daf6eSJon Flatley }; 208ec2daf6eSJon Flatley 209ec2daf6eSJon Flatley #endif /* CONFIG_ACPI */ 210ec2daf6eSJon Flatley 211ec2daf6eSJon Flatley static int cros_usbpd_notify_plat(struct notifier_block *nb, 212ec2daf6eSJon Flatley unsigned long queued_during_suspend, 213ec2daf6eSJon Flatley void *data) 214ec2daf6eSJon Flatley { 215a8821408SPrashant Malani struct cros_usbpd_notify_data *pdnotify = container_of(nb, 216a8821408SPrashant Malani struct cros_usbpd_notify_data, nb); 217ec2daf6eSJon Flatley struct cros_ec_device *ec_dev = (struct cros_ec_device *)data; 218ec2daf6eSJon Flatley u32 host_event = cros_ec_get_host_event(ec_dev); 219ec2daf6eSJon Flatley 220ec2daf6eSJon Flatley if (!host_event) 22192e399c0SGwendal Grignou return NOTIFY_DONE; 222ec2daf6eSJon Flatley 223ec2daf6eSJon Flatley if (host_event & EC_HOST_EVENT_MASK(EC_HOST_EVENT_PD_MCU)) { 224a8821408SPrashant Malani cros_usbpd_get_event_and_notify(pdnotify->dev, ec_dev); 225ec2daf6eSJon Flatley return NOTIFY_OK; 226ec2daf6eSJon Flatley } 227ec2daf6eSJon Flatley return NOTIFY_DONE; 228ec2daf6eSJon Flatley } 229ec2daf6eSJon Flatley 230ec2daf6eSJon Flatley static int cros_usbpd_notify_probe_plat(struct platform_device *pdev) 231ec2daf6eSJon Flatley { 232ec2daf6eSJon Flatley struct device *dev = &pdev->dev; 233ec2daf6eSJon Flatley struct cros_ec_dev *ecdev = dev_get_drvdata(dev->parent); 234f5d84a21SPrashant Malani struct cros_usbpd_notify_data *pdnotify; 235ec2daf6eSJon Flatley int ret; 236ec2daf6eSJon Flatley 237f5d84a21SPrashant Malani pdnotify = devm_kzalloc(dev, sizeof(*pdnotify), GFP_KERNEL); 238f5d84a21SPrashant Malani if (!pdnotify) 239ec2daf6eSJon Flatley return -ENOMEM; 240ec2daf6eSJon Flatley 241f5d84a21SPrashant Malani pdnotify->dev = dev; 242f5d84a21SPrashant Malani pdnotify->ec = ecdev->ec_dev; 243f5d84a21SPrashant Malani pdnotify->nb.notifier_call = cros_usbpd_notify_plat; 244f5d84a21SPrashant Malani 245f5d84a21SPrashant Malani dev_set_drvdata(dev, pdnotify); 246ec2daf6eSJon Flatley 247ec2daf6eSJon Flatley ret = blocking_notifier_chain_register(&ecdev->ec_dev->event_notifier, 248f5d84a21SPrashant Malani &pdnotify->nb); 249ec2daf6eSJon Flatley if (ret < 0) { 250ec2daf6eSJon Flatley dev_err(dev, "Failed to register notifier\n"); 251ec2daf6eSJon Flatley return ret; 252ec2daf6eSJon Flatley } 253ec2daf6eSJon Flatley 254ec2daf6eSJon Flatley return 0; 255ec2daf6eSJon Flatley } 256ec2daf6eSJon Flatley 257ec2daf6eSJon Flatley static int cros_usbpd_notify_remove_plat(struct platform_device *pdev) 258ec2daf6eSJon Flatley { 259ec2daf6eSJon Flatley struct device *dev = &pdev->dev; 260ec2daf6eSJon Flatley struct cros_ec_dev *ecdev = dev_get_drvdata(dev->parent); 261f5d84a21SPrashant Malani struct cros_usbpd_notify_data *pdnotify = 262f5d84a21SPrashant Malani (struct cros_usbpd_notify_data *)dev_get_drvdata(dev); 263ec2daf6eSJon Flatley 264f5d84a21SPrashant Malani blocking_notifier_chain_unregister(&ecdev->ec_dev->event_notifier, 265f5d84a21SPrashant Malani &pdnotify->nb); 266ec2daf6eSJon Flatley 267ec2daf6eSJon Flatley return 0; 268ec2daf6eSJon Flatley } 269ec2daf6eSJon Flatley 270ec2daf6eSJon Flatley static struct platform_driver cros_usbpd_notify_plat_driver = { 271ec2daf6eSJon Flatley .driver = { 272ec2daf6eSJon Flatley .name = DRV_NAME, 273ec2daf6eSJon Flatley }, 274ec2daf6eSJon Flatley .probe = cros_usbpd_notify_probe_plat, 275ec2daf6eSJon Flatley .remove = cros_usbpd_notify_remove_plat, 276ec2daf6eSJon Flatley }; 277ec2daf6eSJon Flatley 278ec2daf6eSJon Flatley static int __init cros_usbpd_notify_init(void) 279ec2daf6eSJon Flatley { 280ec2daf6eSJon Flatley int ret; 281ec2daf6eSJon Flatley 282ec2daf6eSJon Flatley ret = platform_driver_register(&cros_usbpd_notify_plat_driver); 283ec2daf6eSJon Flatley if (ret < 0) 284ec2daf6eSJon Flatley return ret; 285ec2daf6eSJon Flatley 286ec2daf6eSJon Flatley #ifdef CONFIG_ACPI 2877e91e1acSPrashant Malani platform_driver_register(&cros_usbpd_notify_acpi_driver); 288ec2daf6eSJon Flatley #endif 289ec2daf6eSJon Flatley return 0; 290ec2daf6eSJon Flatley } 291ec2daf6eSJon Flatley 292ec2daf6eSJon Flatley static void __exit cros_usbpd_notify_exit(void) 293ec2daf6eSJon Flatley { 294ec2daf6eSJon Flatley #ifdef CONFIG_ACPI 2957e91e1acSPrashant Malani platform_driver_unregister(&cros_usbpd_notify_acpi_driver); 296ec2daf6eSJon Flatley #endif 297ec2daf6eSJon Flatley platform_driver_unregister(&cros_usbpd_notify_plat_driver); 298ec2daf6eSJon Flatley } 299ec2daf6eSJon Flatley 300ec2daf6eSJon Flatley module_init(cros_usbpd_notify_init); 301ec2daf6eSJon Flatley module_exit(cros_usbpd_notify_exit); 302ec2daf6eSJon Flatley 303ec2daf6eSJon Flatley MODULE_LICENSE("GPL"); 304ec2daf6eSJon Flatley MODULE_DESCRIPTION("ChromeOS power delivery notifier device"); 305ec2daf6eSJon Flatley MODULE_AUTHOR("Jon Flatley <jflat@chromium.org>"); 306ec2daf6eSJon Flatley MODULE_ALIAS("platform:" DRV_NAME); 307