18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * CEC driver for ChromeOS Embedded Controller 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (c) 2018 BayLibre, SAS 68c2ecf20Sopenharmony_ci * Author: Neil Armstrong <narmstrong@baylibre.com> 78c2ecf20Sopenharmony_ci */ 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#include <linux/kernel.h> 108c2ecf20Sopenharmony_ci#include <linux/module.h> 118c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 128c2ecf20Sopenharmony_ci#include <linux/dmi.h> 138c2ecf20Sopenharmony_ci#include <linux/pci.h> 148c2ecf20Sopenharmony_ci#include <linux/cec.h> 158c2ecf20Sopenharmony_ci#include <linux/slab.h> 168c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 178c2ecf20Sopenharmony_ci#include <linux/platform_data/cros_ec_commands.h> 188c2ecf20Sopenharmony_ci#include <linux/platform_data/cros_ec_proto.h> 198c2ecf20Sopenharmony_ci#include <media/cec.h> 208c2ecf20Sopenharmony_ci#include <media/cec-notifier.h> 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_ci#define DRV_NAME "cros-ec-cec" 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ci/** 258c2ecf20Sopenharmony_ci * struct cros_ec_cec - Driver data for EC CEC 268c2ecf20Sopenharmony_ci * 278c2ecf20Sopenharmony_ci * @cros_ec: Pointer to EC device 288c2ecf20Sopenharmony_ci * @notifier: Notifier info for responding to EC events 298c2ecf20Sopenharmony_ci * @adap: CEC adapter 308c2ecf20Sopenharmony_ci * @notify: CEC notifier pointer 318c2ecf20Sopenharmony_ci * @rx_msg: storage for a received message 328c2ecf20Sopenharmony_ci */ 338c2ecf20Sopenharmony_cistruct cros_ec_cec { 348c2ecf20Sopenharmony_ci struct cros_ec_device *cros_ec; 358c2ecf20Sopenharmony_ci struct notifier_block notifier; 368c2ecf20Sopenharmony_ci struct cec_adapter *adap; 378c2ecf20Sopenharmony_ci struct cec_notifier *notify; 388c2ecf20Sopenharmony_ci struct cec_msg rx_msg; 398c2ecf20Sopenharmony_ci}; 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_cistatic void handle_cec_message(struct cros_ec_cec *cros_ec_cec) 428c2ecf20Sopenharmony_ci{ 438c2ecf20Sopenharmony_ci struct cros_ec_device *cros_ec = cros_ec_cec->cros_ec; 448c2ecf20Sopenharmony_ci uint8_t *cec_message = cros_ec->event_data.data.cec_message; 458c2ecf20Sopenharmony_ci unsigned int len = cros_ec->event_size; 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_ci if (len > CEC_MAX_MSG_SIZE) 488c2ecf20Sopenharmony_ci len = CEC_MAX_MSG_SIZE; 498c2ecf20Sopenharmony_ci cros_ec_cec->rx_msg.len = len; 508c2ecf20Sopenharmony_ci memcpy(cros_ec_cec->rx_msg.msg, cec_message, len); 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_ci cec_received_msg(cros_ec_cec->adap, &cros_ec_cec->rx_msg); 538c2ecf20Sopenharmony_ci} 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_cistatic void handle_cec_event(struct cros_ec_cec *cros_ec_cec) 568c2ecf20Sopenharmony_ci{ 578c2ecf20Sopenharmony_ci struct cros_ec_device *cros_ec = cros_ec_cec->cros_ec; 588c2ecf20Sopenharmony_ci uint32_t events = cros_ec->event_data.data.cec_events; 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_ci if (events & EC_MKBP_CEC_SEND_OK) 618c2ecf20Sopenharmony_ci cec_transmit_attempt_done(cros_ec_cec->adap, 628c2ecf20Sopenharmony_ci CEC_TX_STATUS_OK); 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci /* FW takes care of all retries, tell core to avoid more retries */ 658c2ecf20Sopenharmony_ci if (events & EC_MKBP_CEC_SEND_FAILED) 668c2ecf20Sopenharmony_ci cec_transmit_attempt_done(cros_ec_cec->adap, 678c2ecf20Sopenharmony_ci CEC_TX_STATUS_MAX_RETRIES | 688c2ecf20Sopenharmony_ci CEC_TX_STATUS_NACK); 698c2ecf20Sopenharmony_ci} 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_cistatic int cros_ec_cec_event(struct notifier_block *nb, 728c2ecf20Sopenharmony_ci unsigned long queued_during_suspend, 738c2ecf20Sopenharmony_ci void *_notify) 748c2ecf20Sopenharmony_ci{ 758c2ecf20Sopenharmony_ci struct cros_ec_cec *cros_ec_cec; 768c2ecf20Sopenharmony_ci struct cros_ec_device *cros_ec; 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_ci cros_ec_cec = container_of(nb, struct cros_ec_cec, notifier); 798c2ecf20Sopenharmony_ci cros_ec = cros_ec_cec->cros_ec; 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_ci if (cros_ec->event_data.event_type == EC_MKBP_EVENT_CEC_EVENT) { 828c2ecf20Sopenharmony_ci handle_cec_event(cros_ec_cec); 838c2ecf20Sopenharmony_ci return NOTIFY_OK; 848c2ecf20Sopenharmony_ci } 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_ci if (cros_ec->event_data.event_type == EC_MKBP_EVENT_CEC_MESSAGE) { 878c2ecf20Sopenharmony_ci handle_cec_message(cros_ec_cec); 888c2ecf20Sopenharmony_ci return NOTIFY_OK; 898c2ecf20Sopenharmony_ci } 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_ci return NOTIFY_DONE; 928c2ecf20Sopenharmony_ci} 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_cistatic int cros_ec_cec_set_log_addr(struct cec_adapter *adap, u8 logical_addr) 958c2ecf20Sopenharmony_ci{ 968c2ecf20Sopenharmony_ci struct cros_ec_cec *cros_ec_cec = adap->priv; 978c2ecf20Sopenharmony_ci struct cros_ec_device *cros_ec = cros_ec_cec->cros_ec; 988c2ecf20Sopenharmony_ci struct { 998c2ecf20Sopenharmony_ci struct cros_ec_command msg; 1008c2ecf20Sopenharmony_ci struct ec_params_cec_set data; 1018c2ecf20Sopenharmony_ci } __packed msg = {}; 1028c2ecf20Sopenharmony_ci int ret; 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci msg.msg.command = EC_CMD_CEC_SET; 1058c2ecf20Sopenharmony_ci msg.msg.outsize = sizeof(msg.data); 1068c2ecf20Sopenharmony_ci msg.data.cmd = CEC_CMD_LOGICAL_ADDRESS; 1078c2ecf20Sopenharmony_ci msg.data.val = logical_addr; 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_ci ret = cros_ec_cmd_xfer_status(cros_ec, &msg.msg); 1108c2ecf20Sopenharmony_ci if (ret < 0) { 1118c2ecf20Sopenharmony_ci dev_err(cros_ec->dev, 1128c2ecf20Sopenharmony_ci "error setting CEC logical address on EC: %d\n", ret); 1138c2ecf20Sopenharmony_ci return ret; 1148c2ecf20Sopenharmony_ci } 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_ci return 0; 1178c2ecf20Sopenharmony_ci} 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_cistatic int cros_ec_cec_transmit(struct cec_adapter *adap, u8 attempts, 1208c2ecf20Sopenharmony_ci u32 signal_free_time, struct cec_msg *cec_msg) 1218c2ecf20Sopenharmony_ci{ 1228c2ecf20Sopenharmony_ci struct cros_ec_cec *cros_ec_cec = adap->priv; 1238c2ecf20Sopenharmony_ci struct cros_ec_device *cros_ec = cros_ec_cec->cros_ec; 1248c2ecf20Sopenharmony_ci struct { 1258c2ecf20Sopenharmony_ci struct cros_ec_command msg; 1268c2ecf20Sopenharmony_ci struct ec_params_cec_write data; 1278c2ecf20Sopenharmony_ci } __packed msg = {}; 1288c2ecf20Sopenharmony_ci int ret; 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci msg.msg.command = EC_CMD_CEC_WRITE_MSG; 1318c2ecf20Sopenharmony_ci msg.msg.outsize = cec_msg->len; 1328c2ecf20Sopenharmony_ci memcpy(msg.data.msg, cec_msg->msg, cec_msg->len); 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_ci ret = cros_ec_cmd_xfer_status(cros_ec, &msg.msg); 1358c2ecf20Sopenharmony_ci if (ret < 0) { 1368c2ecf20Sopenharmony_ci dev_err(cros_ec->dev, 1378c2ecf20Sopenharmony_ci "error writing CEC msg on EC: %d\n", ret); 1388c2ecf20Sopenharmony_ci return ret; 1398c2ecf20Sopenharmony_ci } 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci return 0; 1428c2ecf20Sopenharmony_ci} 1438c2ecf20Sopenharmony_ci 1448c2ecf20Sopenharmony_cistatic int cros_ec_cec_adap_enable(struct cec_adapter *adap, bool enable) 1458c2ecf20Sopenharmony_ci{ 1468c2ecf20Sopenharmony_ci struct cros_ec_cec *cros_ec_cec = adap->priv; 1478c2ecf20Sopenharmony_ci struct cros_ec_device *cros_ec = cros_ec_cec->cros_ec; 1488c2ecf20Sopenharmony_ci struct { 1498c2ecf20Sopenharmony_ci struct cros_ec_command msg; 1508c2ecf20Sopenharmony_ci struct ec_params_cec_set data; 1518c2ecf20Sopenharmony_ci } __packed msg = {}; 1528c2ecf20Sopenharmony_ci int ret; 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci msg.msg.command = EC_CMD_CEC_SET; 1558c2ecf20Sopenharmony_ci msg.msg.outsize = sizeof(msg.data); 1568c2ecf20Sopenharmony_ci msg.data.cmd = CEC_CMD_ENABLE; 1578c2ecf20Sopenharmony_ci msg.data.val = enable; 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci ret = cros_ec_cmd_xfer_status(cros_ec, &msg.msg); 1608c2ecf20Sopenharmony_ci if (ret < 0) { 1618c2ecf20Sopenharmony_ci dev_err(cros_ec->dev, 1628c2ecf20Sopenharmony_ci "error %sabling CEC on EC: %d\n", 1638c2ecf20Sopenharmony_ci (enable ? "en" : "dis"), ret); 1648c2ecf20Sopenharmony_ci return ret; 1658c2ecf20Sopenharmony_ci } 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_ci return 0; 1688c2ecf20Sopenharmony_ci} 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_cistatic const struct cec_adap_ops cros_ec_cec_ops = { 1718c2ecf20Sopenharmony_ci .adap_enable = cros_ec_cec_adap_enable, 1728c2ecf20Sopenharmony_ci .adap_log_addr = cros_ec_cec_set_log_addr, 1738c2ecf20Sopenharmony_ci .adap_transmit = cros_ec_cec_transmit, 1748c2ecf20Sopenharmony_ci}; 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci#ifdef CONFIG_PM_SLEEP 1778c2ecf20Sopenharmony_cistatic int cros_ec_cec_suspend(struct device *dev) 1788c2ecf20Sopenharmony_ci{ 1798c2ecf20Sopenharmony_ci struct platform_device *pdev = to_platform_device(dev); 1808c2ecf20Sopenharmony_ci struct cros_ec_cec *cros_ec_cec = dev_get_drvdata(&pdev->dev); 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ci if (device_may_wakeup(dev)) 1838c2ecf20Sopenharmony_ci enable_irq_wake(cros_ec_cec->cros_ec->irq); 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_ci return 0; 1868c2ecf20Sopenharmony_ci} 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_cistatic int cros_ec_cec_resume(struct device *dev) 1898c2ecf20Sopenharmony_ci{ 1908c2ecf20Sopenharmony_ci struct platform_device *pdev = to_platform_device(dev); 1918c2ecf20Sopenharmony_ci struct cros_ec_cec *cros_ec_cec = dev_get_drvdata(&pdev->dev); 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_ci if (device_may_wakeup(dev)) 1948c2ecf20Sopenharmony_ci disable_irq_wake(cros_ec_cec->cros_ec->irq); 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_ci return 0; 1978c2ecf20Sopenharmony_ci} 1988c2ecf20Sopenharmony_ci#endif 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_cistatic SIMPLE_DEV_PM_OPS(cros_ec_cec_pm_ops, 2018c2ecf20Sopenharmony_ci cros_ec_cec_suspend, cros_ec_cec_resume); 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_ci#if IS_ENABLED(CONFIG_PCI) && IS_ENABLED(CONFIG_DMI) 2048c2ecf20Sopenharmony_ci 2058c2ecf20Sopenharmony_ci/* 2068c2ecf20Sopenharmony_ci * The Firmware only handles a single CEC interface tied to a single HDMI 2078c2ecf20Sopenharmony_ci * connector we specify along with the DRM device name handling the HDMI output 2088c2ecf20Sopenharmony_ci */ 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_cistruct cec_dmi_match { 2118c2ecf20Sopenharmony_ci const char *sys_vendor; 2128c2ecf20Sopenharmony_ci const char *product_name; 2138c2ecf20Sopenharmony_ci const char *devname; 2148c2ecf20Sopenharmony_ci const char *conn; 2158c2ecf20Sopenharmony_ci}; 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_cistatic const struct cec_dmi_match cec_dmi_match_table[] = { 2188c2ecf20Sopenharmony_ci /* Google Fizz */ 2198c2ecf20Sopenharmony_ci { "Google", "Fizz", "0000:00:02.0", "Port B" }, 2208c2ecf20Sopenharmony_ci}; 2218c2ecf20Sopenharmony_ci 2228c2ecf20Sopenharmony_cistatic struct device *cros_ec_cec_find_hdmi_dev(struct device *dev, 2238c2ecf20Sopenharmony_ci const char **conn) 2248c2ecf20Sopenharmony_ci{ 2258c2ecf20Sopenharmony_ci int i; 2268c2ecf20Sopenharmony_ci 2278c2ecf20Sopenharmony_ci for (i = 0 ; i < ARRAY_SIZE(cec_dmi_match_table) ; ++i) { 2288c2ecf20Sopenharmony_ci const struct cec_dmi_match *m = &cec_dmi_match_table[i]; 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ci if (dmi_match(DMI_SYS_VENDOR, m->sys_vendor) && 2318c2ecf20Sopenharmony_ci dmi_match(DMI_PRODUCT_NAME, m->product_name)) { 2328c2ecf20Sopenharmony_ci struct device *d; 2338c2ecf20Sopenharmony_ci 2348c2ecf20Sopenharmony_ci /* Find the device, bail out if not yet registered */ 2358c2ecf20Sopenharmony_ci d = bus_find_device_by_name(&pci_bus_type, NULL, 2368c2ecf20Sopenharmony_ci m->devname); 2378c2ecf20Sopenharmony_ci if (!d) 2388c2ecf20Sopenharmony_ci return ERR_PTR(-EPROBE_DEFER); 2398c2ecf20Sopenharmony_ci put_device(d); 2408c2ecf20Sopenharmony_ci *conn = m->conn; 2418c2ecf20Sopenharmony_ci return d; 2428c2ecf20Sopenharmony_ci } 2438c2ecf20Sopenharmony_ci } 2448c2ecf20Sopenharmony_ci 2458c2ecf20Sopenharmony_ci /* Hardware support must be added in the cec_dmi_match_table */ 2468c2ecf20Sopenharmony_ci dev_warn(dev, "CEC notifier not configured for this hardware\n"); 2478c2ecf20Sopenharmony_ci 2488c2ecf20Sopenharmony_ci return ERR_PTR(-ENODEV); 2498c2ecf20Sopenharmony_ci} 2508c2ecf20Sopenharmony_ci 2518c2ecf20Sopenharmony_ci#else 2528c2ecf20Sopenharmony_ci 2538c2ecf20Sopenharmony_cistatic struct device *cros_ec_cec_find_hdmi_dev(struct device *dev, 2548c2ecf20Sopenharmony_ci const char **conn) 2558c2ecf20Sopenharmony_ci{ 2568c2ecf20Sopenharmony_ci return ERR_PTR(-ENODEV); 2578c2ecf20Sopenharmony_ci} 2588c2ecf20Sopenharmony_ci 2598c2ecf20Sopenharmony_ci#endif 2608c2ecf20Sopenharmony_ci 2618c2ecf20Sopenharmony_cistatic int cros_ec_cec_probe(struct platform_device *pdev) 2628c2ecf20Sopenharmony_ci{ 2638c2ecf20Sopenharmony_ci struct cros_ec_dev *ec_dev = dev_get_drvdata(pdev->dev.parent); 2648c2ecf20Sopenharmony_ci struct cros_ec_device *cros_ec = ec_dev->ec_dev; 2658c2ecf20Sopenharmony_ci struct cros_ec_cec *cros_ec_cec; 2668c2ecf20Sopenharmony_ci struct device *hdmi_dev; 2678c2ecf20Sopenharmony_ci const char *conn = NULL; 2688c2ecf20Sopenharmony_ci int ret; 2698c2ecf20Sopenharmony_ci 2708c2ecf20Sopenharmony_ci hdmi_dev = cros_ec_cec_find_hdmi_dev(&pdev->dev, &conn); 2718c2ecf20Sopenharmony_ci if (IS_ERR(hdmi_dev)) 2728c2ecf20Sopenharmony_ci return PTR_ERR(hdmi_dev); 2738c2ecf20Sopenharmony_ci 2748c2ecf20Sopenharmony_ci cros_ec_cec = devm_kzalloc(&pdev->dev, sizeof(*cros_ec_cec), 2758c2ecf20Sopenharmony_ci GFP_KERNEL); 2768c2ecf20Sopenharmony_ci if (!cros_ec_cec) 2778c2ecf20Sopenharmony_ci return -ENOMEM; 2788c2ecf20Sopenharmony_ci 2798c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, cros_ec_cec); 2808c2ecf20Sopenharmony_ci cros_ec_cec->cros_ec = cros_ec; 2818c2ecf20Sopenharmony_ci 2828c2ecf20Sopenharmony_ci device_init_wakeup(&pdev->dev, 1); 2838c2ecf20Sopenharmony_ci 2848c2ecf20Sopenharmony_ci cros_ec_cec->adap = cec_allocate_adapter(&cros_ec_cec_ops, cros_ec_cec, 2858c2ecf20Sopenharmony_ci DRV_NAME, 2868c2ecf20Sopenharmony_ci CEC_CAP_DEFAULTS | 2878c2ecf20Sopenharmony_ci CEC_CAP_CONNECTOR_INFO, 1); 2888c2ecf20Sopenharmony_ci if (IS_ERR(cros_ec_cec->adap)) 2898c2ecf20Sopenharmony_ci return PTR_ERR(cros_ec_cec->adap); 2908c2ecf20Sopenharmony_ci 2918c2ecf20Sopenharmony_ci cros_ec_cec->notify = cec_notifier_cec_adap_register(hdmi_dev, conn, 2928c2ecf20Sopenharmony_ci cros_ec_cec->adap); 2938c2ecf20Sopenharmony_ci if (!cros_ec_cec->notify) { 2948c2ecf20Sopenharmony_ci ret = -ENOMEM; 2958c2ecf20Sopenharmony_ci goto out_probe_adapter; 2968c2ecf20Sopenharmony_ci } 2978c2ecf20Sopenharmony_ci 2988c2ecf20Sopenharmony_ci /* Get CEC events from the EC. */ 2998c2ecf20Sopenharmony_ci cros_ec_cec->notifier.notifier_call = cros_ec_cec_event; 3008c2ecf20Sopenharmony_ci ret = blocking_notifier_chain_register(&cros_ec->event_notifier, 3018c2ecf20Sopenharmony_ci &cros_ec_cec->notifier); 3028c2ecf20Sopenharmony_ci if (ret) { 3038c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed to register notifier\n"); 3048c2ecf20Sopenharmony_ci goto out_probe_notify; 3058c2ecf20Sopenharmony_ci } 3068c2ecf20Sopenharmony_ci 3078c2ecf20Sopenharmony_ci ret = cec_register_adapter(cros_ec_cec->adap, &pdev->dev); 3088c2ecf20Sopenharmony_ci if (ret < 0) 3098c2ecf20Sopenharmony_ci goto out_probe_notify; 3108c2ecf20Sopenharmony_ci 3118c2ecf20Sopenharmony_ci return 0; 3128c2ecf20Sopenharmony_ci 3138c2ecf20Sopenharmony_ciout_probe_notify: 3148c2ecf20Sopenharmony_ci cec_notifier_cec_adap_unregister(cros_ec_cec->notify, 3158c2ecf20Sopenharmony_ci cros_ec_cec->adap); 3168c2ecf20Sopenharmony_ciout_probe_adapter: 3178c2ecf20Sopenharmony_ci cec_delete_adapter(cros_ec_cec->adap); 3188c2ecf20Sopenharmony_ci return ret; 3198c2ecf20Sopenharmony_ci} 3208c2ecf20Sopenharmony_ci 3218c2ecf20Sopenharmony_cistatic int cros_ec_cec_remove(struct platform_device *pdev) 3228c2ecf20Sopenharmony_ci{ 3238c2ecf20Sopenharmony_ci struct cros_ec_cec *cros_ec_cec = platform_get_drvdata(pdev); 3248c2ecf20Sopenharmony_ci struct device *dev = &pdev->dev; 3258c2ecf20Sopenharmony_ci int ret; 3268c2ecf20Sopenharmony_ci 3278c2ecf20Sopenharmony_ci ret = blocking_notifier_chain_unregister( 3288c2ecf20Sopenharmony_ci &cros_ec_cec->cros_ec->event_notifier, 3298c2ecf20Sopenharmony_ci &cros_ec_cec->notifier); 3308c2ecf20Sopenharmony_ci 3318c2ecf20Sopenharmony_ci if (ret) { 3328c2ecf20Sopenharmony_ci dev_err(dev, "failed to unregister notifier\n"); 3338c2ecf20Sopenharmony_ci return ret; 3348c2ecf20Sopenharmony_ci } 3358c2ecf20Sopenharmony_ci 3368c2ecf20Sopenharmony_ci cec_notifier_cec_adap_unregister(cros_ec_cec->notify, 3378c2ecf20Sopenharmony_ci cros_ec_cec->adap); 3388c2ecf20Sopenharmony_ci cec_unregister_adapter(cros_ec_cec->adap); 3398c2ecf20Sopenharmony_ci 3408c2ecf20Sopenharmony_ci return 0; 3418c2ecf20Sopenharmony_ci} 3428c2ecf20Sopenharmony_ci 3438c2ecf20Sopenharmony_cistatic struct platform_driver cros_ec_cec_driver = { 3448c2ecf20Sopenharmony_ci .probe = cros_ec_cec_probe, 3458c2ecf20Sopenharmony_ci .remove = cros_ec_cec_remove, 3468c2ecf20Sopenharmony_ci .driver = { 3478c2ecf20Sopenharmony_ci .name = DRV_NAME, 3488c2ecf20Sopenharmony_ci .pm = &cros_ec_cec_pm_ops, 3498c2ecf20Sopenharmony_ci }, 3508c2ecf20Sopenharmony_ci}; 3518c2ecf20Sopenharmony_ci 3528c2ecf20Sopenharmony_cimodule_platform_driver(cros_ec_cec_driver); 3538c2ecf20Sopenharmony_ci 3548c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("CEC driver for ChromeOS ECs"); 3558c2ecf20Sopenharmony_ciMODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>"); 3568c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 3578c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:" DRV_NAME); 358