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 50adbfc747SReka 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; 57adbfc747SReka 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 62*425d2051SReka Norman static void cros_ec_cec_received_message(struct cros_ec_cec_port *port, 63*425d2051SReka Norman uint8_t *msg, uint8_t len) 64*425d2051SReka Norman { 65*425d2051SReka Norman if (len > CEC_MAX_MSG_SIZE) 66*425d2051SReka Norman len = CEC_MAX_MSG_SIZE; 67*425d2051SReka Norman 68*425d2051SReka Norman port->rx_msg.len = len; 69*425d2051SReka Norman memcpy(port->rx_msg.msg, msg, len); 70*425d2051SReka Norman 71*425d2051SReka Norman cec_received_msg(port->adap, &port->rx_msg); 72*425d2051SReka Norman } 73*425d2051SReka Norman 744be5e864SMauro Carvalho Chehab static void handle_cec_message(struct cros_ec_cec *cros_ec_cec) 754be5e864SMauro Carvalho Chehab { 764be5e864SMauro Carvalho Chehab struct cros_ec_device *cros_ec = cros_ec_cec->cros_ec; 774be5e864SMauro Carvalho Chehab uint8_t *cec_message = cros_ec->event_data.data.cec_message; 784be5e864SMauro Carvalho Chehab unsigned int len = cros_ec->event_size; 79*425d2051SReka Norman struct cros_ec_cec_port *port; 80*425d2051SReka Norman /* 81*425d2051SReka Norman * There are two ways of receiving CEC messages: 82*425d2051SReka Norman * 1. Old EC firmware which only supports one port sends the data in a 83*425d2051SReka Norman * cec_message MKBP event. 84*425d2051SReka Norman * 2. New EC firmware which supports multiple ports uses 85*425d2051SReka Norman * EC_MKBP_CEC_HAVE_DATA to notify that data is ready and 86*425d2051SReka Norman * EC_CMD_CEC_READ_MSG to read it. 87*425d2051SReka Norman * Check that the EC only has one CEC port, and then we can assume the 88*425d2051SReka Norman * message is from port 0. 89*425d2051SReka Norman */ 90*425d2051SReka Norman if (cros_ec_cec->num_ports != 1) { 91*425d2051SReka Norman dev_err(cros_ec->dev, 92*425d2051SReka Norman "received cec_message on device with %d ports\n", 93*425d2051SReka Norman cros_ec_cec->num_ports); 94*425d2051SReka Norman return; 95*425d2051SReka Norman } 96*425d2051SReka Norman port = cros_ec_cec->ports[0]; 974be5e864SMauro Carvalho Chehab 98*425d2051SReka Norman cros_ec_cec_received_message(port, cec_message, len); 99*425d2051SReka Norman } 1004be5e864SMauro Carvalho Chehab 101*425d2051SReka Norman static void cros_ec_cec_read_message(struct cros_ec_cec_port *port) 102*425d2051SReka Norman { 103*425d2051SReka Norman struct cros_ec_device *cros_ec = port->cros_ec_cec->cros_ec; 104*425d2051SReka Norman struct ec_params_cec_read params = { 105*425d2051SReka Norman .port = port->port_num, 106*425d2051SReka Norman }; 107*425d2051SReka Norman struct ec_response_cec_read response; 108*425d2051SReka Norman int ret; 109*425d2051SReka Norman 110*425d2051SReka Norman ret = cros_ec_cmd(cros_ec, 0, EC_CMD_CEC_READ_MSG, ¶ms, 111*425d2051SReka Norman sizeof(params), &response, sizeof(response)); 112*425d2051SReka Norman if (ret < 0) { 113*425d2051SReka Norman dev_err(cros_ec->dev, 114*425d2051SReka Norman "error reading CEC message on EC: %d\n", ret); 115*425d2051SReka Norman return; 116*425d2051SReka Norman } 117*425d2051SReka Norman 118*425d2051SReka Norman cros_ec_cec_received_message(port, response.msg, response.msg_len); 1194be5e864SMauro Carvalho Chehab } 1204be5e864SMauro Carvalho Chehab 1214be5e864SMauro Carvalho Chehab static void handle_cec_event(struct cros_ec_cec *cros_ec_cec) 1224be5e864SMauro Carvalho Chehab { 1234be5e864SMauro Carvalho Chehab struct cros_ec_device *cros_ec = cros_ec_cec->cros_ec; 1241cabf526SReka Norman uint32_t cec_events = cros_ec->event_data.data.cec_events; 1251cabf526SReka Norman uint32_t port_num = EC_MKBP_EVENT_CEC_GET_PORT(cec_events); 1261cabf526SReka Norman uint32_t events = EC_MKBP_EVENT_CEC_GET_EVENTS(cec_events); 1271cabf526SReka Norman struct cros_ec_cec_port *port; 1281cabf526SReka Norman 1291cabf526SReka Norman if (port_num >= cros_ec_cec->num_ports) { 1301cabf526SReka Norman dev_err(cros_ec->dev, 1311cabf526SReka Norman "received CEC event for invalid port %d\n", port_num); 1321cabf526SReka Norman return; 1331cabf526SReka Norman } 1341cabf526SReka Norman port = cros_ec_cec->ports[port_num]; 1354be5e864SMauro Carvalho Chehab 1364be5e864SMauro Carvalho Chehab if (events & EC_MKBP_CEC_SEND_OK) 1374d0e179aSReka Norman cec_transmit_attempt_done(port->adap, CEC_TX_STATUS_OK); 1384be5e864SMauro Carvalho Chehab 1394be5e864SMauro Carvalho Chehab /* FW takes care of all retries, tell core to avoid more retries */ 1404be5e864SMauro Carvalho Chehab if (events & EC_MKBP_CEC_SEND_FAILED) 1414d0e179aSReka Norman cec_transmit_attempt_done(port->adap, 1424be5e864SMauro Carvalho Chehab CEC_TX_STATUS_MAX_RETRIES | 1434be5e864SMauro Carvalho Chehab CEC_TX_STATUS_NACK); 144*425d2051SReka Norman 145*425d2051SReka Norman if (events & EC_MKBP_CEC_HAVE_DATA) 146*425d2051SReka Norman cros_ec_cec_read_message(port); 1474be5e864SMauro Carvalho Chehab } 1484be5e864SMauro Carvalho Chehab 1494be5e864SMauro Carvalho Chehab static int cros_ec_cec_event(struct notifier_block *nb, 1504be5e864SMauro Carvalho Chehab unsigned long queued_during_suspend, 1514be5e864SMauro Carvalho Chehab void *_notify) 1524be5e864SMauro Carvalho Chehab { 1534be5e864SMauro Carvalho Chehab struct cros_ec_cec *cros_ec_cec; 1544be5e864SMauro Carvalho Chehab struct cros_ec_device *cros_ec; 1554be5e864SMauro Carvalho Chehab 1564be5e864SMauro Carvalho Chehab cros_ec_cec = container_of(nb, struct cros_ec_cec, notifier); 1574be5e864SMauro Carvalho Chehab cros_ec = cros_ec_cec->cros_ec; 1584be5e864SMauro Carvalho Chehab 1594be5e864SMauro Carvalho Chehab if (cros_ec->event_data.event_type == EC_MKBP_EVENT_CEC_EVENT) { 1604be5e864SMauro Carvalho Chehab handle_cec_event(cros_ec_cec); 1614be5e864SMauro Carvalho Chehab return NOTIFY_OK; 1624be5e864SMauro Carvalho Chehab } 1634be5e864SMauro Carvalho Chehab 1644be5e864SMauro Carvalho Chehab if (cros_ec->event_data.event_type == EC_MKBP_EVENT_CEC_MESSAGE) { 1654be5e864SMauro Carvalho Chehab handle_cec_message(cros_ec_cec); 1664be5e864SMauro Carvalho Chehab return NOTIFY_OK; 1674be5e864SMauro Carvalho Chehab } 1684be5e864SMauro Carvalho Chehab 1694be5e864SMauro Carvalho Chehab return NOTIFY_DONE; 1704be5e864SMauro Carvalho Chehab } 1714be5e864SMauro Carvalho Chehab 1724be5e864SMauro Carvalho Chehab static int cros_ec_cec_set_log_addr(struct cec_adapter *adap, u8 logical_addr) 1734be5e864SMauro Carvalho Chehab { 1744d0e179aSReka Norman struct cros_ec_cec_port *port = adap->priv; 1754d0e179aSReka Norman struct cros_ec_cec *cros_ec_cec = port->cros_ec_cec; 1764be5e864SMauro Carvalho Chehab struct cros_ec_device *cros_ec = cros_ec_cec->cros_ec; 177afca12e3SReka Norman struct ec_params_cec_set params = { 178afca12e3SReka Norman .cmd = CEC_CMD_LOGICAL_ADDRESS, 179e90bd1feSReka Norman .port = port->port_num, 180afca12e3SReka Norman .val = logical_addr, 181afca12e3SReka Norman }; 1824be5e864SMauro Carvalho Chehab int ret; 1834be5e864SMauro Carvalho Chehab 184afca12e3SReka Norman ret = cros_ec_cmd(cros_ec, 0, EC_CMD_CEC_SET, ¶ms, sizeof(params), 185afca12e3SReka Norman NULL, 0); 1864be5e864SMauro Carvalho Chehab if (ret < 0) { 1874be5e864SMauro Carvalho Chehab dev_err(cros_ec->dev, 1884be5e864SMauro Carvalho Chehab "error setting CEC logical address on EC: %d\n", 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 int cros_ec_cec_transmit(struct cec_adapter *adap, u8 attempts, 1964be5e864SMauro Carvalho Chehab u32 signal_free_time, struct cec_msg *cec_msg) 1974be5e864SMauro Carvalho Chehab { 1984d0e179aSReka Norman struct cros_ec_cec_port *port = adap->priv; 1994d0e179aSReka Norman struct cros_ec_cec *cros_ec_cec = port->cros_ec_cec; 2004be5e864SMauro Carvalho Chehab struct cros_ec_device *cros_ec = cros_ec_cec->cros_ec; 201afca12e3SReka Norman struct ec_params_cec_write params; 202adbfc747SReka Norman struct ec_params_cec_write_v1 params_v1; 2034be5e864SMauro Carvalho Chehab int ret; 2044be5e864SMauro Carvalho Chehab 205adbfc747SReka Norman if (cros_ec_cec->write_cmd_version == 0) { 206afca12e3SReka Norman memcpy(params.msg, cec_msg->msg, cec_msg->len); 207afca12e3SReka Norman ret = cros_ec_cmd(cros_ec, 0, EC_CMD_CEC_WRITE_MSG, ¶ms, 208afca12e3SReka Norman cec_msg->len, NULL, 0); 209adbfc747SReka Norman } else { 210adbfc747SReka Norman params_v1.port = port->port_num; 211adbfc747SReka Norman params_v1.msg_len = cec_msg->len; 212adbfc747SReka Norman memcpy(params_v1.msg, cec_msg->msg, cec_msg->len); 213adbfc747SReka Norman ret = cros_ec_cmd(cros_ec, cros_ec_cec->write_cmd_version, 214adbfc747SReka Norman EC_CMD_CEC_WRITE_MSG, ¶ms_v1, 215adbfc747SReka Norman sizeof(params_v1), NULL, 0); 216adbfc747SReka Norman } 217adbfc747SReka Norman 2184be5e864SMauro Carvalho Chehab if (ret < 0) { 2194be5e864SMauro Carvalho Chehab dev_err(cros_ec->dev, 2204be5e864SMauro Carvalho Chehab "error writing CEC msg on EC: %d\n", ret); 2214be5e864SMauro Carvalho Chehab return ret; 2224be5e864SMauro Carvalho Chehab } 2234be5e864SMauro Carvalho Chehab 2244be5e864SMauro Carvalho Chehab return 0; 2254be5e864SMauro Carvalho Chehab } 2264be5e864SMauro Carvalho Chehab 2274be5e864SMauro Carvalho Chehab static int cros_ec_cec_adap_enable(struct cec_adapter *adap, bool enable) 2284be5e864SMauro Carvalho Chehab { 2294d0e179aSReka Norman struct cros_ec_cec_port *port = adap->priv; 2304d0e179aSReka Norman struct cros_ec_cec *cros_ec_cec = port->cros_ec_cec; 2314be5e864SMauro Carvalho Chehab struct cros_ec_device *cros_ec = cros_ec_cec->cros_ec; 232afca12e3SReka Norman struct ec_params_cec_set params = { 233afca12e3SReka Norman .cmd = CEC_CMD_ENABLE, 234e90bd1feSReka Norman .port = port->port_num, 235afca12e3SReka Norman .val = enable, 236afca12e3SReka Norman }; 2374be5e864SMauro Carvalho Chehab int ret; 2384be5e864SMauro Carvalho Chehab 239afca12e3SReka Norman ret = cros_ec_cmd(cros_ec, 0, EC_CMD_CEC_SET, ¶ms, sizeof(params), 240afca12e3SReka Norman NULL, 0); 2414be5e864SMauro Carvalho Chehab if (ret < 0) { 2424be5e864SMauro Carvalho Chehab dev_err(cros_ec->dev, 2434be5e864SMauro Carvalho Chehab "error %sabling CEC on EC: %d\n", 2444be5e864SMauro Carvalho Chehab (enable ? "en" : "dis"), ret); 2454be5e864SMauro Carvalho Chehab return ret; 2464be5e864SMauro Carvalho Chehab } 2474be5e864SMauro Carvalho Chehab 2484be5e864SMauro Carvalho Chehab return 0; 2494be5e864SMauro Carvalho Chehab } 2504be5e864SMauro Carvalho Chehab 2514be5e864SMauro Carvalho Chehab static const struct cec_adap_ops cros_ec_cec_ops = { 2524be5e864SMauro Carvalho Chehab .adap_enable = cros_ec_cec_adap_enable, 2534be5e864SMauro Carvalho Chehab .adap_log_addr = cros_ec_cec_set_log_addr, 2544be5e864SMauro Carvalho Chehab .adap_transmit = cros_ec_cec_transmit, 2554be5e864SMauro Carvalho Chehab }; 2564be5e864SMauro Carvalho Chehab 2574be5e864SMauro Carvalho Chehab #ifdef CONFIG_PM_SLEEP 2584be5e864SMauro Carvalho Chehab static int cros_ec_cec_suspend(struct device *dev) 2594be5e864SMauro Carvalho Chehab { 2604be5e864SMauro Carvalho Chehab struct platform_device *pdev = to_platform_device(dev); 2614be5e864SMauro Carvalho Chehab struct cros_ec_cec *cros_ec_cec = dev_get_drvdata(&pdev->dev); 2624be5e864SMauro Carvalho Chehab 2634be5e864SMauro Carvalho Chehab if (device_may_wakeup(dev)) 2644be5e864SMauro Carvalho Chehab enable_irq_wake(cros_ec_cec->cros_ec->irq); 2654be5e864SMauro Carvalho Chehab 2664be5e864SMauro Carvalho Chehab return 0; 2674be5e864SMauro Carvalho Chehab } 2684be5e864SMauro Carvalho Chehab 2694be5e864SMauro Carvalho Chehab static int cros_ec_cec_resume(struct device *dev) 2704be5e864SMauro Carvalho Chehab { 2714be5e864SMauro Carvalho Chehab struct platform_device *pdev = to_platform_device(dev); 2724be5e864SMauro Carvalho Chehab struct cros_ec_cec *cros_ec_cec = dev_get_drvdata(&pdev->dev); 2734be5e864SMauro Carvalho Chehab 2744be5e864SMauro Carvalho Chehab if (device_may_wakeup(dev)) 2754be5e864SMauro Carvalho Chehab disable_irq_wake(cros_ec_cec->cros_ec->irq); 2764be5e864SMauro Carvalho Chehab 2774be5e864SMauro Carvalho Chehab return 0; 2784be5e864SMauro Carvalho Chehab } 2794be5e864SMauro Carvalho Chehab #endif 2804be5e864SMauro Carvalho Chehab 2814be5e864SMauro Carvalho Chehab static SIMPLE_DEV_PM_OPS(cros_ec_cec_pm_ops, 2824be5e864SMauro Carvalho Chehab cros_ec_cec_suspend, cros_ec_cec_resume); 2834be5e864SMauro Carvalho Chehab 2844be5e864SMauro Carvalho Chehab #if IS_ENABLED(CONFIG_PCI) && IS_ENABLED(CONFIG_DMI) 2854be5e864SMauro Carvalho Chehab 2864be5e864SMauro Carvalho Chehab /* 2874be5e864SMauro Carvalho Chehab * The Firmware only handles a single CEC interface tied to a single HDMI 2884be5e864SMauro Carvalho Chehab * connector we specify along with the DRM device name handling the HDMI output 2894be5e864SMauro Carvalho Chehab */ 2904be5e864SMauro Carvalho Chehab 2914be5e864SMauro Carvalho Chehab struct cec_dmi_match { 2924be5e864SMauro Carvalho Chehab const char *sys_vendor; 2934be5e864SMauro Carvalho Chehab const char *product_name; 2944be5e864SMauro Carvalho Chehab const char *devname; 2954be5e864SMauro Carvalho Chehab const char *conn; 2964be5e864SMauro Carvalho Chehab }; 2974be5e864SMauro Carvalho Chehab 2984be5e864SMauro Carvalho Chehab static const struct cec_dmi_match cec_dmi_match_table[] = { 2994be5e864SMauro Carvalho Chehab /* Google Fizz */ 3004be5e864SMauro Carvalho Chehab { "Google", "Fizz", "0000:00:02.0", "Port B" }, 30197733180SZhuohao Lee /* Google Brask */ 30297733180SZhuohao Lee { "Google", "Brask", "0000:00:02.0", "Port B" }, 303a1a9b71eSScott Chao /* Google Moli */ 304a1a9b71eSScott Chao { "Google", "Moli", "0000:00:02.0", "Port B" }, 305f5d48ba2SAjye Huang /* Google Kinox */ 306f5d48ba2SAjye Huang { "Google", "Kinox", "0000:00:02.0", "Port B" }, 307594b6bddSRory Liu /* Google Kuldax */ 308594b6bddSRory Liu { "Google", "Kuldax", "0000:00:02.0", "Port B" }, 30946ff24efSZoey Wu /* Google Aurash */ 31046ff24efSZoey Wu { "Google", "Aurash", "0000:00:02.0", "Port B" }, 3116f8cdfdfSKevin Chiu /* Google Gladios */ 3126f8cdfdfSKevin Chiu { "Google", "Gladios", "0000:00:02.0", "Port B" }, 3136f8cdfdfSKevin Chiu /* Google Lisbon */ 3146f8cdfdfSKevin Chiu { "Google", "Lisbon", "0000:00:02.0", "Port B" }, 3154be5e864SMauro Carvalho Chehab }; 3164be5e864SMauro Carvalho Chehab 3174be5e864SMauro Carvalho Chehab static struct device *cros_ec_cec_find_hdmi_dev(struct device *dev, 3184be5e864SMauro Carvalho Chehab const char **conn) 3194be5e864SMauro Carvalho Chehab { 3204be5e864SMauro Carvalho Chehab int i; 3214be5e864SMauro Carvalho Chehab 3224be5e864SMauro Carvalho Chehab for (i = 0 ; i < ARRAY_SIZE(cec_dmi_match_table) ; ++i) { 3234be5e864SMauro Carvalho Chehab const struct cec_dmi_match *m = &cec_dmi_match_table[i]; 3244be5e864SMauro Carvalho Chehab 3254be5e864SMauro Carvalho Chehab if (dmi_match(DMI_SYS_VENDOR, m->sys_vendor) && 3264be5e864SMauro Carvalho Chehab dmi_match(DMI_PRODUCT_NAME, m->product_name)) { 3274be5e864SMauro Carvalho Chehab struct device *d; 3284be5e864SMauro Carvalho Chehab 3294be5e864SMauro Carvalho Chehab /* Find the device, bail out if not yet registered */ 3304be5e864SMauro Carvalho Chehab d = bus_find_device_by_name(&pci_bus_type, NULL, 3314be5e864SMauro Carvalho Chehab m->devname); 3324be5e864SMauro Carvalho Chehab if (!d) 3334be5e864SMauro Carvalho Chehab return ERR_PTR(-EPROBE_DEFER); 3344be5e864SMauro Carvalho Chehab put_device(d); 3354be5e864SMauro Carvalho Chehab *conn = m->conn; 3364be5e864SMauro Carvalho Chehab return d; 3374be5e864SMauro Carvalho Chehab } 3384be5e864SMauro Carvalho Chehab } 3394be5e864SMauro Carvalho Chehab 3404be5e864SMauro Carvalho Chehab /* Hardware support must be added in the cec_dmi_match_table */ 3414be5e864SMauro Carvalho Chehab dev_warn(dev, "CEC notifier not configured for this hardware\n"); 3424be5e864SMauro Carvalho Chehab 3434be5e864SMauro Carvalho Chehab return ERR_PTR(-ENODEV); 3444be5e864SMauro Carvalho Chehab } 3454be5e864SMauro Carvalho Chehab 3464be5e864SMauro Carvalho Chehab #else 3474be5e864SMauro Carvalho Chehab 3484be5e864SMauro Carvalho Chehab static struct device *cros_ec_cec_find_hdmi_dev(struct device *dev, 3494be5e864SMauro Carvalho Chehab const char **conn) 3504be5e864SMauro Carvalho Chehab { 3514be5e864SMauro Carvalho Chehab return ERR_PTR(-ENODEV); 3524be5e864SMauro Carvalho Chehab } 3534be5e864SMauro Carvalho Chehab 3544be5e864SMauro Carvalho Chehab #endif 3554be5e864SMauro Carvalho Chehab 356adbfc747SReka Norman static int cros_ec_cec_get_write_cmd_version(struct cros_ec_cec *cros_ec_cec) 357adbfc747SReka Norman { 358adbfc747SReka Norman struct cros_ec_device *cros_ec = cros_ec_cec->cros_ec; 359adbfc747SReka Norman struct ec_params_get_cmd_versions_v1 params = { 360adbfc747SReka Norman .cmd = EC_CMD_CEC_WRITE_MSG, 361adbfc747SReka Norman }; 362adbfc747SReka Norman struct ec_response_get_cmd_versions response; 363adbfc747SReka Norman int ret; 364adbfc747SReka Norman 365adbfc747SReka Norman ret = cros_ec_cmd(cros_ec, 1, EC_CMD_GET_CMD_VERSIONS, ¶ms, 366adbfc747SReka Norman sizeof(params), &response, sizeof(response)); 367adbfc747SReka Norman if (ret < 0) { 368adbfc747SReka Norman dev_err(cros_ec->dev, 369adbfc747SReka Norman "error getting CEC write command version: %d\n", ret); 370adbfc747SReka Norman return ret; 371adbfc747SReka Norman } 372adbfc747SReka Norman 373adbfc747SReka Norman if (response.version_mask & EC_VER_MASK(1)) { 374adbfc747SReka Norman cros_ec_cec->write_cmd_version = 1; 375adbfc747SReka Norman } else { 376adbfc747SReka Norman if (cros_ec_cec->num_ports != 1) { 377adbfc747SReka Norman dev_err(cros_ec->dev, 378adbfc747SReka Norman "v0 write command only supports 1 port, %d reported\n", 379adbfc747SReka Norman cros_ec_cec->num_ports); 380adbfc747SReka Norman return -EINVAL; 381adbfc747SReka Norman } 382adbfc747SReka Norman cros_ec_cec->write_cmd_version = 0; 383adbfc747SReka Norman } 384adbfc747SReka Norman 385adbfc747SReka Norman return 0; 386adbfc747SReka Norman } 387adbfc747SReka Norman 3884d0e179aSReka Norman static int cros_ec_cec_init_port(struct device *dev, 3894d0e179aSReka Norman struct cros_ec_cec *cros_ec_cec, 3904d0e179aSReka Norman int port_num, struct device *hdmi_dev, 3914d0e179aSReka Norman const char *conn) 3924d0e179aSReka Norman { 3934d0e179aSReka Norman struct cros_ec_cec_port *port; 3944d0e179aSReka Norman int ret; 3954d0e179aSReka Norman 3964d0e179aSReka Norman port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL); 3974d0e179aSReka Norman if (!port) 3984d0e179aSReka Norman return -ENOMEM; 3994d0e179aSReka Norman 4004d0e179aSReka Norman port->cros_ec_cec = cros_ec_cec; 4014d0e179aSReka Norman port->port_num = port_num; 4024d0e179aSReka Norman 4034d0e179aSReka Norman port->adap = cec_allocate_adapter(&cros_ec_cec_ops, port, DRV_NAME, 4044d0e179aSReka Norman CEC_CAP_DEFAULTS | 4054d0e179aSReka Norman CEC_CAP_CONNECTOR_INFO, 1); 4064d0e179aSReka Norman if (IS_ERR(port->adap)) 4074d0e179aSReka Norman return PTR_ERR(port->adap); 4084d0e179aSReka Norman 4094d0e179aSReka Norman port->notify = cec_notifier_cec_adap_register(hdmi_dev, conn, 4104d0e179aSReka Norman port->adap); 4114d0e179aSReka Norman if (!port->notify) { 4124d0e179aSReka Norman ret = -ENOMEM; 4134d0e179aSReka Norman goto out_probe_adapter; 4144d0e179aSReka Norman } 4154d0e179aSReka Norman 4164d0e179aSReka Norman ret = cec_register_adapter(port->adap, dev); 4174d0e179aSReka Norman if (ret < 0) 4184d0e179aSReka Norman goto out_probe_notify; 4194d0e179aSReka Norman 4204d0e179aSReka Norman cros_ec_cec->ports[port_num] = port; 4214d0e179aSReka Norman 4224d0e179aSReka Norman return 0; 4234d0e179aSReka Norman 4244d0e179aSReka Norman out_probe_notify: 4254d0e179aSReka Norman cec_notifier_cec_adap_unregister(port->notify, port->adap); 4264d0e179aSReka Norman out_probe_adapter: 4274d0e179aSReka Norman cec_delete_adapter(port->adap); 4284d0e179aSReka Norman return ret; 4294d0e179aSReka Norman } 4304d0e179aSReka Norman 4314be5e864SMauro Carvalho Chehab static int cros_ec_cec_probe(struct platform_device *pdev) 4324be5e864SMauro Carvalho Chehab { 4334be5e864SMauro Carvalho Chehab struct cros_ec_dev *ec_dev = dev_get_drvdata(pdev->dev.parent); 4344be5e864SMauro Carvalho Chehab struct cros_ec_device *cros_ec = ec_dev->ec_dev; 4354be5e864SMauro Carvalho Chehab struct cros_ec_cec *cros_ec_cec; 4364d0e179aSReka Norman struct cros_ec_cec_port *port; 4374be5e864SMauro Carvalho Chehab struct device *hdmi_dev; 4384be5e864SMauro Carvalho Chehab const char *conn = NULL; 4394be5e864SMauro Carvalho Chehab int ret; 4404be5e864SMauro Carvalho Chehab 4414be5e864SMauro Carvalho Chehab hdmi_dev = cros_ec_cec_find_hdmi_dev(&pdev->dev, &conn); 4424be5e864SMauro Carvalho Chehab if (IS_ERR(hdmi_dev)) 4434be5e864SMauro Carvalho Chehab return PTR_ERR(hdmi_dev); 4444be5e864SMauro Carvalho Chehab 4454be5e864SMauro Carvalho Chehab cros_ec_cec = devm_kzalloc(&pdev->dev, sizeof(*cros_ec_cec), 4464be5e864SMauro Carvalho Chehab GFP_KERNEL); 4474be5e864SMauro Carvalho Chehab if (!cros_ec_cec) 4484be5e864SMauro Carvalho Chehab return -ENOMEM; 4494be5e864SMauro Carvalho Chehab 4504be5e864SMauro Carvalho Chehab platform_set_drvdata(pdev, cros_ec_cec); 4514be5e864SMauro Carvalho Chehab cros_ec_cec->cros_ec = cros_ec; 4524be5e864SMauro Carvalho Chehab 4536f01dfb7SDariusz Marcinkiewicz device_init_wakeup(&pdev->dev, 1); 4544be5e864SMauro Carvalho Chehab 4554d0e179aSReka Norman cros_ec_cec->num_ports = CEC_NUM_PORTS; 4564be5e864SMauro Carvalho Chehab 457adbfc747SReka Norman ret = cros_ec_cec_get_write_cmd_version(cros_ec_cec); 458adbfc747SReka Norman if (ret) 459adbfc747SReka Norman return ret; 460adbfc747SReka Norman 4614d0e179aSReka Norman for (int i = 0; i < cros_ec_cec->num_ports; i++) { 4624d0e179aSReka Norman ret = cros_ec_cec_init_port(&pdev->dev, cros_ec_cec, i, 4634d0e179aSReka Norman hdmi_dev, conn); 4644d0e179aSReka Norman if (ret) 4654d0e179aSReka Norman goto unregister_ports; 4664be5e864SMauro Carvalho Chehab } 4674be5e864SMauro Carvalho Chehab 4684be5e864SMauro Carvalho Chehab /* Get CEC events from the EC. */ 4694be5e864SMauro Carvalho Chehab cros_ec_cec->notifier.notifier_call = cros_ec_cec_event; 4704be5e864SMauro Carvalho Chehab ret = blocking_notifier_chain_register(&cros_ec->event_notifier, 4714be5e864SMauro Carvalho Chehab &cros_ec_cec->notifier); 4724be5e864SMauro Carvalho Chehab if (ret) { 4734be5e864SMauro Carvalho Chehab dev_err(&pdev->dev, "failed to register notifier\n"); 4744d0e179aSReka Norman goto unregister_ports; 4754be5e864SMauro Carvalho Chehab } 4764be5e864SMauro Carvalho Chehab 4774be5e864SMauro Carvalho Chehab return 0; 4784be5e864SMauro Carvalho Chehab 4794d0e179aSReka Norman unregister_ports: 4804d0e179aSReka Norman /* 4814d0e179aSReka Norman * Unregister any adapters which have been registered. We don't add the 4824d0e179aSReka Norman * port to the array until the adapter has been registered successfully, 4834d0e179aSReka Norman * so any non-NULL ports must have been registered. 4844d0e179aSReka Norman */ 4854d0e179aSReka Norman for (int i = 0; i < cros_ec_cec->num_ports; i++) { 4864d0e179aSReka Norman port = cros_ec_cec->ports[i]; 4874d0e179aSReka Norman if (!port) 4884d0e179aSReka Norman break; 4894d0e179aSReka Norman cec_notifier_cec_adap_unregister(port->notify, port->adap); 4904d0e179aSReka Norman cec_unregister_adapter(port->adap); 4914d0e179aSReka Norman } 4924be5e864SMauro Carvalho Chehab return ret; 4934be5e864SMauro Carvalho Chehab } 4944be5e864SMauro Carvalho Chehab 49545848b28SUwe Kleine-König static void cros_ec_cec_remove(struct platform_device *pdev) 4964be5e864SMauro Carvalho Chehab { 4974be5e864SMauro Carvalho Chehab struct cros_ec_cec *cros_ec_cec = platform_get_drvdata(pdev); 4984be5e864SMauro Carvalho Chehab struct device *dev = &pdev->dev; 4994d0e179aSReka Norman struct cros_ec_cec_port *port; 5004be5e864SMauro Carvalho Chehab int ret; 5014be5e864SMauro Carvalho Chehab 5020ff7aee2SUwe Kleine-König /* 5030ff7aee2SUwe Kleine-König * blocking_notifier_chain_unregister() only fails if the notifier isn't 5040ff7aee2SUwe Kleine-König * in the list. We know it was added to it by .probe(), so there should 5050ff7aee2SUwe Kleine-König * be no need for error checking. Be cautious and still check. 5060ff7aee2SUwe Kleine-König */ 5074be5e864SMauro Carvalho Chehab ret = blocking_notifier_chain_unregister( 5084be5e864SMauro Carvalho Chehab &cros_ec_cec->cros_ec->event_notifier, 5094be5e864SMauro Carvalho Chehab &cros_ec_cec->notifier); 5100ff7aee2SUwe Kleine-König if (ret) 5114be5e864SMauro Carvalho Chehab dev_err(dev, "failed to unregister notifier\n"); 5124be5e864SMauro Carvalho Chehab 5134d0e179aSReka Norman for (int i = 0; i < cros_ec_cec->num_ports; i++) { 5144d0e179aSReka Norman port = cros_ec_cec->ports[i]; 5154d0e179aSReka Norman cec_notifier_cec_adap_unregister(port->notify, port->adap); 5164d0e179aSReka Norman cec_unregister_adapter(port->adap); 5174d0e179aSReka Norman } 5184be5e864SMauro Carvalho Chehab } 5194be5e864SMauro Carvalho Chehab 5204be5e864SMauro Carvalho Chehab static struct platform_driver cros_ec_cec_driver = { 5214be5e864SMauro Carvalho Chehab .probe = cros_ec_cec_probe, 52245848b28SUwe Kleine-König .remove_new = cros_ec_cec_remove, 5234be5e864SMauro Carvalho Chehab .driver = { 5244be5e864SMauro Carvalho Chehab .name = DRV_NAME, 5254be5e864SMauro Carvalho Chehab .pm = &cros_ec_cec_pm_ops, 5264be5e864SMauro Carvalho Chehab }, 5274be5e864SMauro Carvalho Chehab }; 5284be5e864SMauro Carvalho Chehab 5294be5e864SMauro Carvalho Chehab module_platform_driver(cros_ec_cec_driver); 5304be5e864SMauro Carvalho Chehab 5314be5e864SMauro Carvalho Chehab MODULE_DESCRIPTION("CEC driver for ChromeOS ECs"); 5324be5e864SMauro Carvalho Chehab MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>"); 5334be5e864SMauro Carvalho Chehab MODULE_LICENSE("GPL"); 5344be5e864SMauro Carvalho Chehab MODULE_ALIAS("platform:" DRV_NAME); 535