14be5e864SMauro Carvalho Chehab // SPDX-License-Identifier: GPL-2.0+ 24be5e864SMauro Carvalho Chehab /* 34be5e864SMauro Carvalho Chehab * CEC driver for ChromeOS Embedded Controller 44be5e864SMauro Carvalho Chehab * 54be5e864SMauro Carvalho Chehab * Copyright (c) 2018 BayLibre, SAS 64be5e864SMauro Carvalho Chehab * Author: Neil Armstrong <narmstrong@baylibre.com> 74be5e864SMauro Carvalho Chehab */ 84be5e864SMauro Carvalho Chehab 94be5e864SMauro Carvalho Chehab #include <linux/kernel.h> 104be5e864SMauro Carvalho Chehab #include <linux/module.h> 114be5e864SMauro Carvalho Chehab #include <linux/platform_device.h> 124be5e864SMauro Carvalho Chehab #include <linux/dmi.h> 134be5e864SMauro Carvalho Chehab #include <linux/pci.h> 144be5e864SMauro Carvalho Chehab #include <linux/cec.h> 154be5e864SMauro Carvalho Chehab #include <linux/slab.h> 164be5e864SMauro Carvalho Chehab #include <linux/interrupt.h> 174be5e864SMauro Carvalho Chehab #include <linux/platform_data/cros_ec_commands.h> 184be5e864SMauro Carvalho Chehab #include <linux/platform_data/cros_ec_proto.h> 194be5e864SMauro Carvalho Chehab #include <media/cec.h> 204be5e864SMauro Carvalho Chehab #include <media/cec-notifier.h> 214be5e864SMauro Carvalho Chehab 224be5e864SMauro Carvalho Chehab #define DRV_NAME "cros-ec-cec" 234be5e864SMauro Carvalho Chehab 244d0e179aSReka Norman /* Only one port is supported for now */ 254d0e179aSReka Norman #define CEC_NUM_PORTS 1 264d0e179aSReka Norman #define CEC_PORT 0 274d0e179aSReka Norman 284d0e179aSReka Norman /** 294d0e179aSReka Norman * struct cros_ec_cec_port - Driver data for a single EC CEC port 304d0e179aSReka Norman * 314d0e179aSReka Norman * @port_num: port number 324d0e179aSReka Norman * @adap: CEC adapter 334d0e179aSReka Norman * @notify: CEC notifier pointer 344d0e179aSReka Norman * @rx_msg: storage for a received message 354d0e179aSReka Norman * @cros_ec_cec: pointer to the parent struct 364d0e179aSReka Norman */ 374d0e179aSReka Norman struct cros_ec_cec_port { 384d0e179aSReka Norman int port_num; 394d0e179aSReka Norman struct cec_adapter *adap; 404d0e179aSReka Norman struct cec_notifier *notify; 414d0e179aSReka Norman struct cec_msg rx_msg; 424d0e179aSReka Norman struct cros_ec_cec *cros_ec_cec; 434d0e179aSReka Norman }; 444d0e179aSReka Norman 454be5e864SMauro Carvalho Chehab /** 464be5e864SMauro Carvalho Chehab * struct cros_ec_cec - Driver data for EC CEC 474be5e864SMauro Carvalho Chehab * 484be5e864SMauro Carvalho Chehab * @cros_ec: Pointer to EC device 494be5e864SMauro Carvalho Chehab * @notifier: Notifier info for responding to EC events 50*adbfc747SReka Norman * @write_cmd_version: Highest supported version of EC_CMD_CEC_WRITE_MSG. 514d0e179aSReka Norman * @num_ports: Number of CEC ports 524d0e179aSReka Norman * @ports: Array of ports 534be5e864SMauro Carvalho Chehab */ 544be5e864SMauro Carvalho Chehab struct cros_ec_cec { 554be5e864SMauro Carvalho Chehab struct cros_ec_device *cros_ec; 564be5e864SMauro Carvalho Chehab struct notifier_block notifier; 57*adbfc747SReka Norman int write_cmd_version; 584d0e179aSReka Norman int num_ports; 594d0e179aSReka Norman struct cros_ec_cec_port *ports[EC_CEC_MAX_PORTS]; 604be5e864SMauro Carvalho Chehab }; 614be5e864SMauro Carvalho Chehab 624be5e864SMauro Carvalho Chehab static void handle_cec_message(struct cros_ec_cec *cros_ec_cec) 634be5e864SMauro Carvalho Chehab { 644be5e864SMauro Carvalho Chehab struct cros_ec_device *cros_ec = cros_ec_cec->cros_ec; 654be5e864SMauro Carvalho Chehab uint8_t *cec_message = cros_ec->event_data.data.cec_message; 664be5e864SMauro Carvalho Chehab unsigned int len = cros_ec->event_size; 674d0e179aSReka Norman struct cros_ec_cec_port *port = cros_ec_cec->ports[CEC_PORT]; 684be5e864SMauro Carvalho Chehab 692dc73b48SHans Verkuil if (len > CEC_MAX_MSG_SIZE) 702dc73b48SHans Verkuil len = CEC_MAX_MSG_SIZE; 714d0e179aSReka Norman port->rx_msg.len = len; 724d0e179aSReka Norman memcpy(port->rx_msg.msg, cec_message, len); 734be5e864SMauro Carvalho Chehab 744d0e179aSReka Norman cec_received_msg(port->adap, &port->rx_msg); 754be5e864SMauro Carvalho Chehab } 764be5e864SMauro Carvalho Chehab 774be5e864SMauro Carvalho Chehab static void handle_cec_event(struct cros_ec_cec *cros_ec_cec) 784be5e864SMauro Carvalho Chehab { 794be5e864SMauro Carvalho Chehab struct cros_ec_device *cros_ec = cros_ec_cec->cros_ec; 804be5e864SMauro Carvalho Chehab uint32_t events = cros_ec->event_data.data.cec_events; 814d0e179aSReka Norman struct cros_ec_cec_port *port = cros_ec_cec->ports[CEC_PORT]; 824be5e864SMauro Carvalho Chehab 834be5e864SMauro Carvalho Chehab if (events & EC_MKBP_CEC_SEND_OK) 844d0e179aSReka Norman cec_transmit_attempt_done(port->adap, CEC_TX_STATUS_OK); 854be5e864SMauro Carvalho Chehab 864be5e864SMauro Carvalho Chehab /* FW takes care of all retries, tell core to avoid more retries */ 874be5e864SMauro Carvalho Chehab if (events & EC_MKBP_CEC_SEND_FAILED) 884d0e179aSReka Norman cec_transmit_attempt_done(port->adap, 894be5e864SMauro Carvalho Chehab CEC_TX_STATUS_MAX_RETRIES | 904be5e864SMauro Carvalho Chehab CEC_TX_STATUS_NACK); 914be5e864SMauro Carvalho Chehab } 924be5e864SMauro Carvalho Chehab 934be5e864SMauro Carvalho Chehab static int cros_ec_cec_event(struct notifier_block *nb, 944be5e864SMauro Carvalho Chehab unsigned long queued_during_suspend, 954be5e864SMauro Carvalho Chehab void *_notify) 964be5e864SMauro Carvalho Chehab { 974be5e864SMauro Carvalho Chehab struct cros_ec_cec *cros_ec_cec; 984be5e864SMauro Carvalho Chehab struct cros_ec_device *cros_ec; 994be5e864SMauro Carvalho Chehab 1004be5e864SMauro Carvalho Chehab cros_ec_cec = container_of(nb, struct cros_ec_cec, notifier); 1014be5e864SMauro Carvalho Chehab cros_ec = cros_ec_cec->cros_ec; 1024be5e864SMauro Carvalho Chehab 1034be5e864SMauro Carvalho Chehab if (cros_ec->event_data.event_type == EC_MKBP_EVENT_CEC_EVENT) { 1044be5e864SMauro Carvalho Chehab handle_cec_event(cros_ec_cec); 1054be5e864SMauro Carvalho Chehab return NOTIFY_OK; 1064be5e864SMauro Carvalho Chehab } 1074be5e864SMauro Carvalho Chehab 1084be5e864SMauro Carvalho Chehab if (cros_ec->event_data.event_type == EC_MKBP_EVENT_CEC_MESSAGE) { 1094be5e864SMauro Carvalho Chehab handle_cec_message(cros_ec_cec); 1104be5e864SMauro Carvalho Chehab return NOTIFY_OK; 1114be5e864SMauro Carvalho Chehab } 1124be5e864SMauro Carvalho Chehab 1134be5e864SMauro Carvalho Chehab return NOTIFY_DONE; 1144be5e864SMauro Carvalho Chehab } 1154be5e864SMauro Carvalho Chehab 1164be5e864SMauro Carvalho Chehab static int cros_ec_cec_set_log_addr(struct cec_adapter *adap, u8 logical_addr) 1174be5e864SMauro Carvalho Chehab { 1184d0e179aSReka Norman struct cros_ec_cec_port *port = adap->priv; 1194d0e179aSReka Norman struct cros_ec_cec *cros_ec_cec = port->cros_ec_cec; 1204be5e864SMauro Carvalho Chehab struct cros_ec_device *cros_ec = cros_ec_cec->cros_ec; 121afca12e3SReka Norman struct ec_params_cec_set params = { 122afca12e3SReka Norman .cmd = CEC_CMD_LOGICAL_ADDRESS, 123e90bd1feSReka Norman .port = port->port_num, 124afca12e3SReka Norman .val = logical_addr, 125afca12e3SReka Norman }; 1264be5e864SMauro Carvalho Chehab int ret; 1274be5e864SMauro Carvalho Chehab 128afca12e3SReka Norman ret = cros_ec_cmd(cros_ec, 0, EC_CMD_CEC_SET, ¶ms, sizeof(params), 129afca12e3SReka Norman NULL, 0); 1304be5e864SMauro Carvalho Chehab if (ret < 0) { 1314be5e864SMauro Carvalho Chehab dev_err(cros_ec->dev, 1324be5e864SMauro Carvalho Chehab "error setting CEC logical address on EC: %d\n", ret); 1334be5e864SMauro Carvalho Chehab return ret; 1344be5e864SMauro Carvalho Chehab } 1354be5e864SMauro Carvalho Chehab 1364be5e864SMauro Carvalho Chehab return 0; 1374be5e864SMauro Carvalho Chehab } 1384be5e864SMauro Carvalho Chehab 1394be5e864SMauro Carvalho Chehab static int cros_ec_cec_transmit(struct cec_adapter *adap, u8 attempts, 1404be5e864SMauro Carvalho Chehab u32 signal_free_time, struct cec_msg *cec_msg) 1414be5e864SMauro Carvalho Chehab { 1424d0e179aSReka Norman struct cros_ec_cec_port *port = adap->priv; 1434d0e179aSReka Norman struct cros_ec_cec *cros_ec_cec = port->cros_ec_cec; 1444be5e864SMauro Carvalho Chehab struct cros_ec_device *cros_ec = cros_ec_cec->cros_ec; 145afca12e3SReka Norman struct ec_params_cec_write params; 146*adbfc747SReka Norman struct ec_params_cec_write_v1 params_v1; 1474be5e864SMauro Carvalho Chehab int ret; 1484be5e864SMauro Carvalho Chehab 149*adbfc747SReka Norman if (cros_ec_cec->write_cmd_version == 0) { 150afca12e3SReka Norman memcpy(params.msg, cec_msg->msg, cec_msg->len); 151afca12e3SReka Norman ret = cros_ec_cmd(cros_ec, 0, EC_CMD_CEC_WRITE_MSG, ¶ms, 152afca12e3SReka Norman cec_msg->len, NULL, 0); 153*adbfc747SReka Norman } else { 154*adbfc747SReka Norman params_v1.port = port->port_num; 155*adbfc747SReka Norman params_v1.msg_len = cec_msg->len; 156*adbfc747SReka Norman memcpy(params_v1.msg, cec_msg->msg, cec_msg->len); 157*adbfc747SReka Norman ret = cros_ec_cmd(cros_ec, cros_ec_cec->write_cmd_version, 158*adbfc747SReka Norman EC_CMD_CEC_WRITE_MSG, ¶ms_v1, 159*adbfc747SReka Norman sizeof(params_v1), NULL, 0); 160*adbfc747SReka Norman } 161*adbfc747SReka Norman 1624be5e864SMauro Carvalho Chehab if (ret < 0) { 1634be5e864SMauro Carvalho Chehab dev_err(cros_ec->dev, 1644be5e864SMauro Carvalho Chehab "error writing CEC msg on EC: %d\n", ret); 1654be5e864SMauro Carvalho Chehab return ret; 1664be5e864SMauro Carvalho Chehab } 1674be5e864SMauro Carvalho Chehab 1684be5e864SMauro Carvalho Chehab return 0; 1694be5e864SMauro Carvalho Chehab } 1704be5e864SMauro Carvalho Chehab 1714be5e864SMauro Carvalho Chehab static int cros_ec_cec_adap_enable(struct cec_adapter *adap, bool enable) 1724be5e864SMauro Carvalho Chehab { 1734d0e179aSReka Norman struct cros_ec_cec_port *port = adap->priv; 1744d0e179aSReka Norman struct cros_ec_cec *cros_ec_cec = port->cros_ec_cec; 1754be5e864SMauro Carvalho Chehab struct cros_ec_device *cros_ec = cros_ec_cec->cros_ec; 176afca12e3SReka Norman struct ec_params_cec_set params = { 177afca12e3SReka Norman .cmd = CEC_CMD_ENABLE, 178e90bd1feSReka Norman .port = port->port_num, 179afca12e3SReka Norman .val = enable, 180afca12e3SReka Norman }; 1814be5e864SMauro Carvalho Chehab int ret; 1824be5e864SMauro Carvalho Chehab 183afca12e3SReka Norman ret = cros_ec_cmd(cros_ec, 0, EC_CMD_CEC_SET, ¶ms, sizeof(params), 184afca12e3SReka Norman NULL, 0); 1854be5e864SMauro Carvalho Chehab if (ret < 0) { 1864be5e864SMauro Carvalho Chehab dev_err(cros_ec->dev, 1874be5e864SMauro Carvalho Chehab "error %sabling CEC on EC: %d\n", 1884be5e864SMauro Carvalho Chehab (enable ? "en" : "dis"), ret); 1894be5e864SMauro Carvalho Chehab return ret; 1904be5e864SMauro Carvalho Chehab } 1914be5e864SMauro Carvalho Chehab 1924be5e864SMauro Carvalho Chehab return 0; 1934be5e864SMauro Carvalho Chehab } 1944be5e864SMauro Carvalho Chehab 1954be5e864SMauro Carvalho Chehab static const struct cec_adap_ops cros_ec_cec_ops = { 1964be5e864SMauro Carvalho Chehab .adap_enable = cros_ec_cec_adap_enable, 1974be5e864SMauro Carvalho Chehab .adap_log_addr = cros_ec_cec_set_log_addr, 1984be5e864SMauro Carvalho Chehab .adap_transmit = cros_ec_cec_transmit, 1994be5e864SMauro Carvalho Chehab }; 2004be5e864SMauro Carvalho Chehab 2014be5e864SMauro Carvalho Chehab #ifdef CONFIG_PM_SLEEP 2024be5e864SMauro Carvalho Chehab static int cros_ec_cec_suspend(struct device *dev) 2034be5e864SMauro Carvalho Chehab { 2044be5e864SMauro Carvalho Chehab struct platform_device *pdev = to_platform_device(dev); 2054be5e864SMauro Carvalho Chehab struct cros_ec_cec *cros_ec_cec = dev_get_drvdata(&pdev->dev); 2064be5e864SMauro Carvalho Chehab 2074be5e864SMauro Carvalho Chehab if (device_may_wakeup(dev)) 2084be5e864SMauro Carvalho Chehab enable_irq_wake(cros_ec_cec->cros_ec->irq); 2094be5e864SMauro Carvalho Chehab 2104be5e864SMauro Carvalho Chehab return 0; 2114be5e864SMauro Carvalho Chehab } 2124be5e864SMauro Carvalho Chehab 2134be5e864SMauro Carvalho Chehab static int cros_ec_cec_resume(struct device *dev) 2144be5e864SMauro Carvalho Chehab { 2154be5e864SMauro Carvalho Chehab struct platform_device *pdev = to_platform_device(dev); 2164be5e864SMauro Carvalho Chehab struct cros_ec_cec *cros_ec_cec = dev_get_drvdata(&pdev->dev); 2174be5e864SMauro Carvalho Chehab 2184be5e864SMauro Carvalho Chehab if (device_may_wakeup(dev)) 2194be5e864SMauro Carvalho Chehab disable_irq_wake(cros_ec_cec->cros_ec->irq); 2204be5e864SMauro Carvalho Chehab 2214be5e864SMauro Carvalho Chehab return 0; 2224be5e864SMauro Carvalho Chehab } 2234be5e864SMauro Carvalho Chehab #endif 2244be5e864SMauro Carvalho Chehab 2254be5e864SMauro Carvalho Chehab static SIMPLE_DEV_PM_OPS(cros_ec_cec_pm_ops, 2264be5e864SMauro Carvalho Chehab cros_ec_cec_suspend, cros_ec_cec_resume); 2274be5e864SMauro Carvalho Chehab 2284be5e864SMauro Carvalho Chehab #if IS_ENABLED(CONFIG_PCI) && IS_ENABLED(CONFIG_DMI) 2294be5e864SMauro Carvalho Chehab 2304be5e864SMauro Carvalho Chehab /* 2314be5e864SMauro Carvalho Chehab * The Firmware only handles a single CEC interface tied to a single HDMI 2324be5e864SMauro Carvalho Chehab * connector we specify along with the DRM device name handling the HDMI output 2334be5e864SMauro Carvalho Chehab */ 2344be5e864SMauro Carvalho Chehab 2354be5e864SMauro Carvalho Chehab struct cec_dmi_match { 2364be5e864SMauro Carvalho Chehab const char *sys_vendor; 2374be5e864SMauro Carvalho Chehab const char *product_name; 2384be5e864SMauro Carvalho Chehab const char *devname; 2394be5e864SMauro Carvalho Chehab const char *conn; 2404be5e864SMauro Carvalho Chehab }; 2414be5e864SMauro Carvalho Chehab 2424be5e864SMauro Carvalho Chehab static const struct cec_dmi_match cec_dmi_match_table[] = { 2434be5e864SMauro Carvalho Chehab /* Google Fizz */ 2444be5e864SMauro Carvalho Chehab { "Google", "Fizz", "0000:00:02.0", "Port B" }, 24597733180SZhuohao Lee /* Google Brask */ 24697733180SZhuohao Lee { "Google", "Brask", "0000:00:02.0", "Port B" }, 247a1a9b71eSScott Chao /* Google Moli */ 248a1a9b71eSScott Chao { "Google", "Moli", "0000:00:02.0", "Port B" }, 249f5d48ba2SAjye Huang /* Google Kinox */ 250f5d48ba2SAjye Huang { "Google", "Kinox", "0000:00:02.0", "Port B" }, 251594b6bddSRory Liu /* Google Kuldax */ 252594b6bddSRory Liu { "Google", "Kuldax", "0000:00:02.0", "Port B" }, 25346ff24efSZoey Wu /* Google Aurash */ 25446ff24efSZoey Wu { "Google", "Aurash", "0000:00:02.0", "Port B" }, 2556f8cdfdfSKevin Chiu /* Google Gladios */ 2566f8cdfdfSKevin Chiu { "Google", "Gladios", "0000:00:02.0", "Port B" }, 2576f8cdfdfSKevin Chiu /* Google Lisbon */ 2586f8cdfdfSKevin Chiu { "Google", "Lisbon", "0000:00:02.0", "Port B" }, 2594be5e864SMauro Carvalho Chehab }; 2604be5e864SMauro Carvalho Chehab 2614be5e864SMauro Carvalho Chehab static struct device *cros_ec_cec_find_hdmi_dev(struct device *dev, 2624be5e864SMauro Carvalho Chehab const char **conn) 2634be5e864SMauro Carvalho Chehab { 2644be5e864SMauro Carvalho Chehab int i; 2654be5e864SMauro Carvalho Chehab 2664be5e864SMauro Carvalho Chehab for (i = 0 ; i < ARRAY_SIZE(cec_dmi_match_table) ; ++i) { 2674be5e864SMauro Carvalho Chehab const struct cec_dmi_match *m = &cec_dmi_match_table[i]; 2684be5e864SMauro Carvalho Chehab 2694be5e864SMauro Carvalho Chehab if (dmi_match(DMI_SYS_VENDOR, m->sys_vendor) && 2704be5e864SMauro Carvalho Chehab dmi_match(DMI_PRODUCT_NAME, m->product_name)) { 2714be5e864SMauro Carvalho Chehab struct device *d; 2724be5e864SMauro Carvalho Chehab 2734be5e864SMauro Carvalho Chehab /* Find the device, bail out if not yet registered */ 2744be5e864SMauro Carvalho Chehab d = bus_find_device_by_name(&pci_bus_type, NULL, 2754be5e864SMauro Carvalho Chehab m->devname); 2764be5e864SMauro Carvalho Chehab if (!d) 2774be5e864SMauro Carvalho Chehab return ERR_PTR(-EPROBE_DEFER); 2784be5e864SMauro Carvalho Chehab put_device(d); 2794be5e864SMauro Carvalho Chehab *conn = m->conn; 2804be5e864SMauro Carvalho Chehab return d; 2814be5e864SMauro Carvalho Chehab } 2824be5e864SMauro Carvalho Chehab } 2834be5e864SMauro Carvalho Chehab 2844be5e864SMauro Carvalho Chehab /* Hardware support must be added in the cec_dmi_match_table */ 2854be5e864SMauro Carvalho Chehab dev_warn(dev, "CEC notifier not configured for this hardware\n"); 2864be5e864SMauro Carvalho Chehab 2874be5e864SMauro Carvalho Chehab return ERR_PTR(-ENODEV); 2884be5e864SMauro Carvalho Chehab } 2894be5e864SMauro Carvalho Chehab 2904be5e864SMauro Carvalho Chehab #else 2914be5e864SMauro Carvalho Chehab 2924be5e864SMauro Carvalho Chehab static struct device *cros_ec_cec_find_hdmi_dev(struct device *dev, 2934be5e864SMauro Carvalho Chehab const char **conn) 2944be5e864SMauro Carvalho Chehab { 2954be5e864SMauro Carvalho Chehab return ERR_PTR(-ENODEV); 2964be5e864SMauro Carvalho Chehab } 2974be5e864SMauro Carvalho Chehab 2984be5e864SMauro Carvalho Chehab #endif 2994be5e864SMauro Carvalho Chehab 300*adbfc747SReka Norman static int cros_ec_cec_get_write_cmd_version(struct cros_ec_cec *cros_ec_cec) 301*adbfc747SReka Norman { 302*adbfc747SReka Norman struct cros_ec_device *cros_ec = cros_ec_cec->cros_ec; 303*adbfc747SReka Norman struct ec_params_get_cmd_versions_v1 params = { 304*adbfc747SReka Norman .cmd = EC_CMD_CEC_WRITE_MSG, 305*adbfc747SReka Norman }; 306*adbfc747SReka Norman struct ec_response_get_cmd_versions response; 307*adbfc747SReka Norman int ret; 308*adbfc747SReka Norman 309*adbfc747SReka Norman ret = cros_ec_cmd(cros_ec, 1, EC_CMD_GET_CMD_VERSIONS, ¶ms, 310*adbfc747SReka Norman sizeof(params), &response, sizeof(response)); 311*adbfc747SReka Norman if (ret < 0) { 312*adbfc747SReka Norman dev_err(cros_ec->dev, 313*adbfc747SReka Norman "error getting CEC write command version: %d\n", ret); 314*adbfc747SReka Norman return ret; 315*adbfc747SReka Norman } 316*adbfc747SReka Norman 317*adbfc747SReka Norman if (response.version_mask & EC_VER_MASK(1)) { 318*adbfc747SReka Norman cros_ec_cec->write_cmd_version = 1; 319*adbfc747SReka Norman } else { 320*adbfc747SReka Norman if (cros_ec_cec->num_ports != 1) { 321*adbfc747SReka Norman dev_err(cros_ec->dev, 322*adbfc747SReka Norman "v0 write command only supports 1 port, %d reported\n", 323*adbfc747SReka Norman cros_ec_cec->num_ports); 324*adbfc747SReka Norman return -EINVAL; 325*adbfc747SReka Norman } 326*adbfc747SReka Norman cros_ec_cec->write_cmd_version = 0; 327*adbfc747SReka Norman } 328*adbfc747SReka Norman 329*adbfc747SReka Norman return 0; 330*adbfc747SReka Norman } 331*adbfc747SReka Norman 3324d0e179aSReka Norman static int cros_ec_cec_init_port(struct device *dev, 3334d0e179aSReka Norman struct cros_ec_cec *cros_ec_cec, 3344d0e179aSReka Norman int port_num, struct device *hdmi_dev, 3354d0e179aSReka Norman const char *conn) 3364d0e179aSReka Norman { 3374d0e179aSReka Norman struct cros_ec_cec_port *port; 3384d0e179aSReka Norman int ret; 3394d0e179aSReka Norman 3404d0e179aSReka Norman port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL); 3414d0e179aSReka Norman if (!port) 3424d0e179aSReka Norman return -ENOMEM; 3434d0e179aSReka Norman 3444d0e179aSReka Norman port->cros_ec_cec = cros_ec_cec; 3454d0e179aSReka Norman port->port_num = port_num; 3464d0e179aSReka Norman 3474d0e179aSReka Norman port->adap = cec_allocate_adapter(&cros_ec_cec_ops, port, DRV_NAME, 3484d0e179aSReka Norman CEC_CAP_DEFAULTS | 3494d0e179aSReka Norman CEC_CAP_CONNECTOR_INFO, 1); 3504d0e179aSReka Norman if (IS_ERR(port->adap)) 3514d0e179aSReka Norman return PTR_ERR(port->adap); 3524d0e179aSReka Norman 3534d0e179aSReka Norman port->notify = cec_notifier_cec_adap_register(hdmi_dev, conn, 3544d0e179aSReka Norman port->adap); 3554d0e179aSReka Norman if (!port->notify) { 3564d0e179aSReka Norman ret = -ENOMEM; 3574d0e179aSReka Norman goto out_probe_adapter; 3584d0e179aSReka Norman } 3594d0e179aSReka Norman 3604d0e179aSReka Norman ret = cec_register_adapter(port->adap, dev); 3614d0e179aSReka Norman if (ret < 0) 3624d0e179aSReka Norman goto out_probe_notify; 3634d0e179aSReka Norman 3644d0e179aSReka Norman cros_ec_cec->ports[port_num] = port; 3654d0e179aSReka Norman 3664d0e179aSReka Norman return 0; 3674d0e179aSReka Norman 3684d0e179aSReka Norman out_probe_notify: 3694d0e179aSReka Norman cec_notifier_cec_adap_unregister(port->notify, port->adap); 3704d0e179aSReka Norman out_probe_adapter: 3714d0e179aSReka Norman cec_delete_adapter(port->adap); 3724d0e179aSReka Norman return ret; 3734d0e179aSReka Norman } 3744d0e179aSReka Norman 3754be5e864SMauro Carvalho Chehab static int cros_ec_cec_probe(struct platform_device *pdev) 3764be5e864SMauro Carvalho Chehab { 3774be5e864SMauro Carvalho Chehab struct cros_ec_dev *ec_dev = dev_get_drvdata(pdev->dev.parent); 3784be5e864SMauro Carvalho Chehab struct cros_ec_device *cros_ec = ec_dev->ec_dev; 3794be5e864SMauro Carvalho Chehab struct cros_ec_cec *cros_ec_cec; 3804d0e179aSReka Norman struct cros_ec_cec_port *port; 3814be5e864SMauro Carvalho Chehab struct device *hdmi_dev; 3824be5e864SMauro Carvalho Chehab const char *conn = NULL; 3834be5e864SMauro Carvalho Chehab int ret; 3844be5e864SMauro Carvalho Chehab 3854be5e864SMauro Carvalho Chehab hdmi_dev = cros_ec_cec_find_hdmi_dev(&pdev->dev, &conn); 3864be5e864SMauro Carvalho Chehab if (IS_ERR(hdmi_dev)) 3874be5e864SMauro Carvalho Chehab return PTR_ERR(hdmi_dev); 3884be5e864SMauro Carvalho Chehab 3894be5e864SMauro Carvalho Chehab cros_ec_cec = devm_kzalloc(&pdev->dev, sizeof(*cros_ec_cec), 3904be5e864SMauro Carvalho Chehab GFP_KERNEL); 3914be5e864SMauro Carvalho Chehab if (!cros_ec_cec) 3924be5e864SMauro Carvalho Chehab return -ENOMEM; 3934be5e864SMauro Carvalho Chehab 3944be5e864SMauro Carvalho Chehab platform_set_drvdata(pdev, cros_ec_cec); 3954be5e864SMauro Carvalho Chehab cros_ec_cec->cros_ec = cros_ec; 3964be5e864SMauro Carvalho Chehab 3976f01dfb7SDariusz Marcinkiewicz device_init_wakeup(&pdev->dev, 1); 3984be5e864SMauro Carvalho Chehab 3994d0e179aSReka Norman cros_ec_cec->num_ports = CEC_NUM_PORTS; 4004be5e864SMauro Carvalho Chehab 401*adbfc747SReka Norman ret = cros_ec_cec_get_write_cmd_version(cros_ec_cec); 402*adbfc747SReka Norman if (ret) 403*adbfc747SReka Norman return ret; 404*adbfc747SReka Norman 4054d0e179aSReka Norman for (int i = 0; i < cros_ec_cec->num_ports; i++) { 4064d0e179aSReka Norman ret = cros_ec_cec_init_port(&pdev->dev, cros_ec_cec, i, 4074d0e179aSReka Norman hdmi_dev, conn); 4084d0e179aSReka Norman if (ret) 4094d0e179aSReka Norman goto unregister_ports; 4104be5e864SMauro Carvalho Chehab } 4114be5e864SMauro Carvalho Chehab 4124be5e864SMauro Carvalho Chehab /* Get CEC events from the EC. */ 4134be5e864SMauro Carvalho Chehab cros_ec_cec->notifier.notifier_call = cros_ec_cec_event; 4144be5e864SMauro Carvalho Chehab ret = blocking_notifier_chain_register(&cros_ec->event_notifier, 4154be5e864SMauro Carvalho Chehab &cros_ec_cec->notifier); 4164be5e864SMauro Carvalho Chehab if (ret) { 4174be5e864SMauro Carvalho Chehab dev_err(&pdev->dev, "failed to register notifier\n"); 4184d0e179aSReka Norman goto unregister_ports; 4194be5e864SMauro Carvalho Chehab } 4204be5e864SMauro Carvalho Chehab 4214be5e864SMauro Carvalho Chehab return 0; 4224be5e864SMauro Carvalho Chehab 4234d0e179aSReka Norman unregister_ports: 4244d0e179aSReka Norman /* 4254d0e179aSReka Norman * Unregister any adapters which have been registered. We don't add the 4264d0e179aSReka Norman * port to the array until the adapter has been registered successfully, 4274d0e179aSReka Norman * so any non-NULL ports must have been registered. 4284d0e179aSReka Norman */ 4294d0e179aSReka Norman for (int i = 0; i < cros_ec_cec->num_ports; i++) { 4304d0e179aSReka Norman port = cros_ec_cec->ports[i]; 4314d0e179aSReka Norman if (!port) 4324d0e179aSReka Norman break; 4334d0e179aSReka Norman cec_notifier_cec_adap_unregister(port->notify, port->adap); 4344d0e179aSReka Norman cec_unregister_adapter(port->adap); 4354d0e179aSReka Norman } 4364be5e864SMauro Carvalho Chehab return ret; 4374be5e864SMauro Carvalho Chehab } 4384be5e864SMauro Carvalho Chehab 43945848b28SUwe Kleine-König static void cros_ec_cec_remove(struct platform_device *pdev) 4404be5e864SMauro Carvalho Chehab { 4414be5e864SMauro Carvalho Chehab struct cros_ec_cec *cros_ec_cec = platform_get_drvdata(pdev); 4424be5e864SMauro Carvalho Chehab struct device *dev = &pdev->dev; 4434d0e179aSReka Norman struct cros_ec_cec_port *port; 4444be5e864SMauro Carvalho Chehab int ret; 4454be5e864SMauro Carvalho Chehab 4460ff7aee2SUwe Kleine-König /* 4470ff7aee2SUwe Kleine-König * blocking_notifier_chain_unregister() only fails if the notifier isn't 4480ff7aee2SUwe Kleine-König * in the list. We know it was added to it by .probe(), so there should 4490ff7aee2SUwe Kleine-König * be no need for error checking. Be cautious and still check. 4500ff7aee2SUwe Kleine-König */ 4514be5e864SMauro Carvalho Chehab ret = blocking_notifier_chain_unregister( 4524be5e864SMauro Carvalho Chehab &cros_ec_cec->cros_ec->event_notifier, 4534be5e864SMauro Carvalho Chehab &cros_ec_cec->notifier); 4540ff7aee2SUwe Kleine-König if (ret) 4554be5e864SMauro Carvalho Chehab dev_err(dev, "failed to unregister notifier\n"); 4564be5e864SMauro Carvalho Chehab 4574d0e179aSReka Norman for (int i = 0; i < cros_ec_cec->num_ports; i++) { 4584d0e179aSReka Norman port = cros_ec_cec->ports[i]; 4594d0e179aSReka Norman cec_notifier_cec_adap_unregister(port->notify, port->adap); 4604d0e179aSReka Norman cec_unregister_adapter(port->adap); 4614d0e179aSReka Norman } 4624be5e864SMauro Carvalho Chehab } 4634be5e864SMauro Carvalho Chehab 4644be5e864SMauro Carvalho Chehab static struct platform_driver cros_ec_cec_driver = { 4654be5e864SMauro Carvalho Chehab .probe = cros_ec_cec_probe, 46645848b28SUwe Kleine-König .remove_new = cros_ec_cec_remove, 4674be5e864SMauro Carvalho Chehab .driver = { 4684be5e864SMauro Carvalho Chehab .name = DRV_NAME, 4694be5e864SMauro Carvalho Chehab .pm = &cros_ec_cec_pm_ops, 4704be5e864SMauro Carvalho Chehab }, 4714be5e864SMauro Carvalho Chehab }; 4724be5e864SMauro Carvalho Chehab 4734be5e864SMauro Carvalho Chehab module_platform_driver(cros_ec_cec_driver); 4744be5e864SMauro Carvalho Chehab 4754be5e864SMauro Carvalho Chehab MODULE_DESCRIPTION("CEC driver for ChromeOS ECs"); 4764be5e864SMauro Carvalho Chehab MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>"); 4774be5e864SMauro Carvalho Chehab MODULE_LICENSE("GPL"); 4784be5e864SMauro Carvalho Chehab MODULE_ALIAS("platform:" DRV_NAME); 479