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>
11*50a0844bSTzung-Bi Shih #include <linux/mod_devicetable.h>
124be5e864SMauro Carvalho Chehab #include <linux/platform_device.h>
134be5e864SMauro Carvalho Chehab #include <linux/dmi.h>
144be5e864SMauro Carvalho Chehab #include <linux/pci.h>
154be5e864SMauro Carvalho Chehab #include <linux/cec.h>
164be5e864SMauro Carvalho Chehab #include <linux/slab.h>
174be5e864SMauro Carvalho Chehab #include <linux/interrupt.h>
184be5e864SMauro Carvalho Chehab #include <linux/platform_data/cros_ec_commands.h>
194be5e864SMauro Carvalho Chehab #include <linux/platform_data/cros_ec_proto.h>
204be5e864SMauro Carvalho Chehab #include <media/cec.h>
214be5e864SMauro Carvalho Chehab #include <media/cec-notifier.h>
224be5e864SMauro Carvalho Chehab
234be5e864SMauro Carvalho Chehab #define DRV_NAME "cros-ec-cec"
244be5e864SMauro Carvalho Chehab
254d0e179aSReka Norman /**
264d0e179aSReka Norman * struct cros_ec_cec_port - Driver data for a single EC CEC port
274d0e179aSReka Norman *
284d0e179aSReka Norman * @port_num: port number
294d0e179aSReka Norman * @adap: CEC adapter
304d0e179aSReka Norman * @notify: CEC notifier pointer
314d0e179aSReka Norman * @rx_msg: storage for a received message
324d0e179aSReka Norman * @cros_ec_cec: pointer to the parent struct
334d0e179aSReka Norman */
344d0e179aSReka Norman struct cros_ec_cec_port {
354d0e179aSReka Norman int port_num;
364d0e179aSReka Norman struct cec_adapter *adap;
374d0e179aSReka Norman struct cec_notifier *notify;
384d0e179aSReka Norman struct cec_msg rx_msg;
394d0e179aSReka Norman struct cros_ec_cec *cros_ec_cec;
404d0e179aSReka Norman };
414d0e179aSReka Norman
424be5e864SMauro Carvalho Chehab /**
434be5e864SMauro Carvalho Chehab * struct cros_ec_cec - Driver data for EC CEC
444be5e864SMauro Carvalho Chehab *
454be5e864SMauro Carvalho Chehab * @cros_ec: Pointer to EC device
464be5e864SMauro Carvalho Chehab * @notifier: Notifier info for responding to EC events
47adbfc747SReka Norman * @write_cmd_version: Highest supported version of EC_CMD_CEC_WRITE_MSG.
484d0e179aSReka Norman * @num_ports: Number of CEC ports
494d0e179aSReka Norman * @ports: Array of ports
504be5e864SMauro Carvalho Chehab */
514be5e864SMauro Carvalho Chehab struct cros_ec_cec {
524be5e864SMauro Carvalho Chehab struct cros_ec_device *cros_ec;
534be5e864SMauro Carvalho Chehab struct notifier_block notifier;
54adbfc747SReka Norman int write_cmd_version;
554d0e179aSReka Norman int num_ports;
564d0e179aSReka Norman struct cros_ec_cec_port *ports[EC_CEC_MAX_PORTS];
574be5e864SMauro Carvalho Chehab };
584be5e864SMauro Carvalho Chehab
cros_ec_cec_received_message(struct cros_ec_cec_port * port,uint8_t * msg,uint8_t len)59425d2051SReka Norman static void cros_ec_cec_received_message(struct cros_ec_cec_port *port,
60425d2051SReka Norman uint8_t *msg, uint8_t len)
61425d2051SReka Norman {
62425d2051SReka Norman if (len > CEC_MAX_MSG_SIZE)
63425d2051SReka Norman len = CEC_MAX_MSG_SIZE;
64425d2051SReka Norman
65425d2051SReka Norman port->rx_msg.len = len;
66425d2051SReka Norman memcpy(port->rx_msg.msg, msg, len);
67425d2051SReka Norman
68425d2051SReka Norman cec_received_msg(port->adap, &port->rx_msg);
69425d2051SReka Norman }
70425d2051SReka Norman
handle_cec_message(struct cros_ec_cec * cros_ec_cec)714be5e864SMauro Carvalho Chehab static void handle_cec_message(struct cros_ec_cec *cros_ec_cec)
724be5e864SMauro Carvalho Chehab {
734be5e864SMauro Carvalho Chehab struct cros_ec_device *cros_ec = cros_ec_cec->cros_ec;
744be5e864SMauro Carvalho Chehab uint8_t *cec_message = cros_ec->event_data.data.cec_message;
754be5e864SMauro Carvalho Chehab unsigned int len = cros_ec->event_size;
76425d2051SReka Norman struct cros_ec_cec_port *port;
77425d2051SReka Norman /*
78425d2051SReka Norman * There are two ways of receiving CEC messages:
79425d2051SReka Norman * 1. Old EC firmware which only supports one port sends the data in a
80425d2051SReka Norman * cec_message MKBP event.
81425d2051SReka Norman * 2. New EC firmware which supports multiple ports uses
82425d2051SReka Norman * EC_MKBP_CEC_HAVE_DATA to notify that data is ready and
83425d2051SReka Norman * EC_CMD_CEC_READ_MSG to read it.
84425d2051SReka Norman * Check that the EC only has one CEC port, and then we can assume the
85425d2051SReka Norman * message is from port 0.
86425d2051SReka Norman */
87425d2051SReka Norman if (cros_ec_cec->num_ports != 1) {
88425d2051SReka Norman dev_err(cros_ec->dev,
89425d2051SReka Norman "received cec_message on device with %d ports\n",
90425d2051SReka Norman cros_ec_cec->num_ports);
91425d2051SReka Norman return;
92425d2051SReka Norman }
93425d2051SReka Norman port = cros_ec_cec->ports[0];
944be5e864SMauro Carvalho Chehab
95425d2051SReka Norman cros_ec_cec_received_message(port, cec_message, len);
96425d2051SReka Norman }
974be5e864SMauro Carvalho Chehab
cros_ec_cec_read_message(struct cros_ec_cec_port * port)98425d2051SReka Norman static void cros_ec_cec_read_message(struct cros_ec_cec_port *port)
99425d2051SReka Norman {
100425d2051SReka Norman struct cros_ec_device *cros_ec = port->cros_ec_cec->cros_ec;
101425d2051SReka Norman struct ec_params_cec_read params = {
102425d2051SReka Norman .port = port->port_num,
103425d2051SReka Norman };
104425d2051SReka Norman struct ec_response_cec_read response;
105425d2051SReka Norman int ret;
106425d2051SReka Norman
107425d2051SReka Norman ret = cros_ec_cmd(cros_ec, 0, EC_CMD_CEC_READ_MSG, ¶ms,
108425d2051SReka Norman sizeof(params), &response, sizeof(response));
109425d2051SReka Norman if (ret < 0) {
110425d2051SReka Norman dev_err(cros_ec->dev,
111425d2051SReka Norman "error reading CEC message on EC: %d\n", ret);
112425d2051SReka Norman return;
113425d2051SReka Norman }
114425d2051SReka Norman
115425d2051SReka Norman cros_ec_cec_received_message(port, response.msg, response.msg_len);
1164be5e864SMauro Carvalho Chehab }
1174be5e864SMauro Carvalho Chehab
handle_cec_event(struct cros_ec_cec * cros_ec_cec)1184be5e864SMauro Carvalho Chehab static void handle_cec_event(struct cros_ec_cec *cros_ec_cec)
1194be5e864SMauro Carvalho Chehab {
1204be5e864SMauro Carvalho Chehab struct cros_ec_device *cros_ec = cros_ec_cec->cros_ec;
1211cabf526SReka Norman uint32_t cec_events = cros_ec->event_data.data.cec_events;
1221cabf526SReka Norman uint32_t port_num = EC_MKBP_EVENT_CEC_GET_PORT(cec_events);
1231cabf526SReka Norman uint32_t events = EC_MKBP_EVENT_CEC_GET_EVENTS(cec_events);
1241cabf526SReka Norman struct cros_ec_cec_port *port;
1251cabf526SReka Norman
1261cabf526SReka Norman if (port_num >= cros_ec_cec->num_ports) {
1271cabf526SReka Norman dev_err(cros_ec->dev,
1281cabf526SReka Norman "received CEC event for invalid port %d\n", port_num);
1291cabf526SReka Norman return;
1301cabf526SReka Norman }
1311cabf526SReka Norman port = cros_ec_cec->ports[port_num];
1324be5e864SMauro Carvalho Chehab
1334be5e864SMauro Carvalho Chehab if (events & EC_MKBP_CEC_SEND_OK)
1344d0e179aSReka Norman cec_transmit_attempt_done(port->adap, CEC_TX_STATUS_OK);
1354be5e864SMauro Carvalho Chehab
1364be5e864SMauro Carvalho Chehab /* FW takes care of all retries, tell core to avoid more retries */
1374be5e864SMauro Carvalho Chehab if (events & EC_MKBP_CEC_SEND_FAILED)
1384d0e179aSReka Norman cec_transmit_attempt_done(port->adap,
1394be5e864SMauro Carvalho Chehab CEC_TX_STATUS_MAX_RETRIES |
1404be5e864SMauro Carvalho Chehab CEC_TX_STATUS_NACK);
141425d2051SReka Norman
142425d2051SReka Norman if (events & EC_MKBP_CEC_HAVE_DATA)
143425d2051SReka Norman cros_ec_cec_read_message(port);
1444be5e864SMauro Carvalho Chehab }
1454be5e864SMauro Carvalho Chehab
cros_ec_cec_event(struct notifier_block * nb,unsigned long queued_during_suspend,void * _notify)1464be5e864SMauro Carvalho Chehab static int cros_ec_cec_event(struct notifier_block *nb,
1474be5e864SMauro Carvalho Chehab unsigned long queued_during_suspend,
1484be5e864SMauro Carvalho Chehab void *_notify)
1494be5e864SMauro Carvalho Chehab {
1504be5e864SMauro Carvalho Chehab struct cros_ec_cec *cros_ec_cec;
1514be5e864SMauro Carvalho Chehab struct cros_ec_device *cros_ec;
1524be5e864SMauro Carvalho Chehab
1534be5e864SMauro Carvalho Chehab cros_ec_cec = container_of(nb, struct cros_ec_cec, notifier);
1544be5e864SMauro Carvalho Chehab cros_ec = cros_ec_cec->cros_ec;
1554be5e864SMauro Carvalho Chehab
1564be5e864SMauro Carvalho Chehab if (cros_ec->event_data.event_type == EC_MKBP_EVENT_CEC_EVENT) {
1574be5e864SMauro Carvalho Chehab handle_cec_event(cros_ec_cec);
1584be5e864SMauro Carvalho Chehab return NOTIFY_OK;
1594be5e864SMauro Carvalho Chehab }
1604be5e864SMauro Carvalho Chehab
1614be5e864SMauro Carvalho Chehab if (cros_ec->event_data.event_type == EC_MKBP_EVENT_CEC_MESSAGE) {
1624be5e864SMauro Carvalho Chehab handle_cec_message(cros_ec_cec);
1634be5e864SMauro Carvalho Chehab return NOTIFY_OK;
1644be5e864SMauro Carvalho Chehab }
1654be5e864SMauro Carvalho Chehab
1664be5e864SMauro Carvalho Chehab return NOTIFY_DONE;
1674be5e864SMauro Carvalho Chehab }
1684be5e864SMauro Carvalho Chehab
cros_ec_cec_set_log_addr(struct cec_adapter * adap,u8 logical_addr)1694be5e864SMauro Carvalho Chehab static int cros_ec_cec_set_log_addr(struct cec_adapter *adap, u8 logical_addr)
1704be5e864SMauro Carvalho Chehab {
1714d0e179aSReka Norman struct cros_ec_cec_port *port = adap->priv;
1724d0e179aSReka Norman struct cros_ec_cec *cros_ec_cec = port->cros_ec_cec;
1734be5e864SMauro Carvalho Chehab struct cros_ec_device *cros_ec = cros_ec_cec->cros_ec;
174afca12e3SReka Norman struct ec_params_cec_set params = {
175afca12e3SReka Norman .cmd = CEC_CMD_LOGICAL_ADDRESS,
176e90bd1feSReka Norman .port = port->port_num,
177afca12e3SReka Norman .val = logical_addr,
178afca12e3SReka Norman };
1794be5e864SMauro Carvalho Chehab int ret;
1804be5e864SMauro Carvalho Chehab
181afca12e3SReka Norman ret = cros_ec_cmd(cros_ec, 0, EC_CMD_CEC_SET, ¶ms, sizeof(params),
182afca12e3SReka Norman NULL, 0);
1834be5e864SMauro Carvalho Chehab if (ret < 0) {
1844be5e864SMauro Carvalho Chehab dev_err(cros_ec->dev,
1854be5e864SMauro Carvalho Chehab "error setting CEC logical address on EC: %d\n", ret);
1864be5e864SMauro Carvalho Chehab return ret;
1874be5e864SMauro Carvalho Chehab }
1884be5e864SMauro Carvalho Chehab
1894be5e864SMauro Carvalho Chehab return 0;
1904be5e864SMauro Carvalho Chehab }
1914be5e864SMauro Carvalho Chehab
cros_ec_cec_transmit(struct cec_adapter * adap,u8 attempts,u32 signal_free_time,struct cec_msg * cec_msg)1924be5e864SMauro Carvalho Chehab static int cros_ec_cec_transmit(struct cec_adapter *adap, u8 attempts,
1934be5e864SMauro Carvalho Chehab u32 signal_free_time, struct cec_msg *cec_msg)
1944be5e864SMauro Carvalho Chehab {
1954d0e179aSReka Norman struct cros_ec_cec_port *port = adap->priv;
1964d0e179aSReka Norman struct cros_ec_cec *cros_ec_cec = port->cros_ec_cec;
1974be5e864SMauro Carvalho Chehab struct cros_ec_device *cros_ec = cros_ec_cec->cros_ec;
198afca12e3SReka Norman struct ec_params_cec_write params;
199adbfc747SReka Norman struct ec_params_cec_write_v1 params_v1;
2004be5e864SMauro Carvalho Chehab int ret;
2014be5e864SMauro Carvalho Chehab
202adbfc747SReka Norman if (cros_ec_cec->write_cmd_version == 0) {
203afca12e3SReka Norman memcpy(params.msg, cec_msg->msg, cec_msg->len);
204afca12e3SReka Norman ret = cros_ec_cmd(cros_ec, 0, EC_CMD_CEC_WRITE_MSG, ¶ms,
205afca12e3SReka Norman cec_msg->len, NULL, 0);
206adbfc747SReka Norman } else {
207adbfc747SReka Norman params_v1.port = port->port_num;
208adbfc747SReka Norman params_v1.msg_len = cec_msg->len;
209adbfc747SReka Norman memcpy(params_v1.msg, cec_msg->msg, cec_msg->len);
210adbfc747SReka Norman ret = cros_ec_cmd(cros_ec, cros_ec_cec->write_cmd_version,
211adbfc747SReka Norman EC_CMD_CEC_WRITE_MSG, ¶ms_v1,
212adbfc747SReka Norman sizeof(params_v1), NULL, 0);
213adbfc747SReka Norman }
214adbfc747SReka Norman
2154be5e864SMauro Carvalho Chehab if (ret < 0) {
2164be5e864SMauro Carvalho Chehab dev_err(cros_ec->dev,
2174be5e864SMauro Carvalho Chehab "error writing CEC msg on EC: %d\n", ret);
2184be5e864SMauro Carvalho Chehab return ret;
2194be5e864SMauro Carvalho Chehab }
2204be5e864SMauro Carvalho Chehab
2214be5e864SMauro Carvalho Chehab return 0;
2224be5e864SMauro Carvalho Chehab }
2234be5e864SMauro Carvalho Chehab
cros_ec_cec_adap_enable(struct cec_adapter * adap,bool enable)2244be5e864SMauro Carvalho Chehab static int cros_ec_cec_adap_enable(struct cec_adapter *adap, bool enable)
2254be5e864SMauro Carvalho Chehab {
2264d0e179aSReka Norman struct cros_ec_cec_port *port = adap->priv;
2274d0e179aSReka Norman struct cros_ec_cec *cros_ec_cec = port->cros_ec_cec;
2284be5e864SMauro Carvalho Chehab struct cros_ec_device *cros_ec = cros_ec_cec->cros_ec;
229afca12e3SReka Norman struct ec_params_cec_set params = {
230afca12e3SReka Norman .cmd = CEC_CMD_ENABLE,
231e90bd1feSReka Norman .port = port->port_num,
232afca12e3SReka Norman .val = enable,
233afca12e3SReka Norman };
2344be5e864SMauro Carvalho Chehab int ret;
2354be5e864SMauro Carvalho Chehab
236afca12e3SReka Norman ret = cros_ec_cmd(cros_ec, 0, EC_CMD_CEC_SET, ¶ms, sizeof(params),
237afca12e3SReka Norman NULL, 0);
2384be5e864SMauro Carvalho Chehab if (ret < 0) {
2394be5e864SMauro Carvalho Chehab dev_err(cros_ec->dev,
2404be5e864SMauro Carvalho Chehab "error %sabling CEC on EC: %d\n",
2414be5e864SMauro Carvalho Chehab (enable ? "en" : "dis"), ret);
2424be5e864SMauro Carvalho Chehab return ret;
2434be5e864SMauro Carvalho Chehab }
2444be5e864SMauro Carvalho Chehab
2454be5e864SMauro Carvalho Chehab return 0;
2464be5e864SMauro Carvalho Chehab }
2474be5e864SMauro Carvalho Chehab
2484be5e864SMauro Carvalho Chehab static const struct cec_adap_ops cros_ec_cec_ops = {
2494be5e864SMauro Carvalho Chehab .adap_enable = cros_ec_cec_adap_enable,
2504be5e864SMauro Carvalho Chehab .adap_log_addr = cros_ec_cec_set_log_addr,
2514be5e864SMauro Carvalho Chehab .adap_transmit = cros_ec_cec_transmit,
2524be5e864SMauro Carvalho Chehab };
2534be5e864SMauro Carvalho Chehab
2544be5e864SMauro Carvalho Chehab #ifdef CONFIG_PM_SLEEP
cros_ec_cec_suspend(struct device * dev)2554be5e864SMauro Carvalho Chehab static int cros_ec_cec_suspend(struct device *dev)
2564be5e864SMauro Carvalho Chehab {
2574be5e864SMauro Carvalho Chehab struct platform_device *pdev = to_platform_device(dev);
2584be5e864SMauro Carvalho Chehab struct cros_ec_cec *cros_ec_cec = dev_get_drvdata(&pdev->dev);
2594be5e864SMauro Carvalho Chehab
2604be5e864SMauro Carvalho Chehab if (device_may_wakeup(dev))
2614be5e864SMauro Carvalho Chehab enable_irq_wake(cros_ec_cec->cros_ec->irq);
2624be5e864SMauro Carvalho Chehab
2634be5e864SMauro Carvalho Chehab return 0;
2644be5e864SMauro Carvalho Chehab }
2654be5e864SMauro Carvalho Chehab
cros_ec_cec_resume(struct device * dev)2664be5e864SMauro Carvalho Chehab static int cros_ec_cec_resume(struct device *dev)
2674be5e864SMauro Carvalho Chehab {
2684be5e864SMauro Carvalho Chehab struct platform_device *pdev = to_platform_device(dev);
2694be5e864SMauro Carvalho Chehab struct cros_ec_cec *cros_ec_cec = dev_get_drvdata(&pdev->dev);
2704be5e864SMauro Carvalho Chehab
2714be5e864SMauro Carvalho Chehab if (device_may_wakeup(dev))
2724be5e864SMauro Carvalho Chehab disable_irq_wake(cros_ec_cec->cros_ec->irq);
2734be5e864SMauro Carvalho Chehab
2744be5e864SMauro Carvalho Chehab return 0;
2754be5e864SMauro Carvalho Chehab }
2764be5e864SMauro Carvalho Chehab #endif
2774be5e864SMauro Carvalho Chehab
2784be5e864SMauro Carvalho Chehab static SIMPLE_DEV_PM_OPS(cros_ec_cec_pm_ops,
2794be5e864SMauro Carvalho Chehab cros_ec_cec_suspend, cros_ec_cec_resume);
2804be5e864SMauro Carvalho Chehab
2814be5e864SMauro Carvalho Chehab #if IS_ENABLED(CONFIG_PCI) && IS_ENABLED(CONFIG_DMI)
2824be5e864SMauro Carvalho Chehab
2834be5e864SMauro Carvalho Chehab /*
284e7885b9cSReka Norman * Specify the DRM device name handling the HDMI output and the HDMI connector
285e7885b9cSReka Norman * corresponding to each CEC port. The order of connectors must match the order
286e7885b9cSReka Norman * in the EC (first connector is EC port 0, ...), and the number of connectors
287e7885b9cSReka Norman * must match the number of ports in the EC (which can be queried using the
288e7885b9cSReka Norman * EC_CMD_CEC_PORT_COUNT host command).
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;
295e7885b9cSReka Norman const char *const *conns;
2964be5e864SMauro Carvalho Chehab };
2974be5e864SMauro Carvalho Chehab
298678e8d80SKen Lin static const char *const port_b_conns[] = { "Port B", NULL };
299678e8d80SKen Lin static const char *const port_db_conns[] = { "Port D", "Port B", NULL };
300678e8d80SKen Lin static const char *const port_ba_conns[] = { "Port B", "Port A", NULL };
301678e8d80SKen Lin static const char *const port_d_conns[] = { "Port D", NULL };
302e7885b9cSReka Norman
3034be5e864SMauro Carvalho Chehab static const struct cec_dmi_match cec_dmi_match_table[] = {
3044be5e864SMauro Carvalho Chehab /* Google Fizz */
305678e8d80SKen Lin { "Google", "Fizz", "0000:00:02.0", port_b_conns },
30697733180SZhuohao Lee /* Google Brask */
307678e8d80SKen Lin { "Google", "Brask", "0000:00:02.0", port_b_conns },
308a1a9b71eSScott Chao /* Google Moli */
309678e8d80SKen Lin { "Google", "Moli", "0000:00:02.0", port_b_conns },
310f5d48ba2SAjye Huang /* Google Kinox */
311678e8d80SKen Lin { "Google", "Kinox", "0000:00:02.0", port_b_conns },
312594b6bddSRory Liu /* Google Kuldax */
313678e8d80SKen Lin { "Google", "Kuldax", "0000:00:02.0", port_b_conns },
31446ff24efSZoey Wu /* Google Aurash */
315678e8d80SKen Lin { "Google", "Aurash", "0000:00:02.0", port_b_conns },
3166f8cdfdfSKevin Chiu /* Google Gladios */
317678e8d80SKen Lin { "Google", "Gladios", "0000:00:02.0", port_b_conns },
3186f8cdfdfSKevin Chiu /* Google Lisbon */
319678e8d80SKen Lin { "Google", "Lisbon", "0000:00:02.0", port_b_conns },
3208d3e6030SReka Norman /* Google Dibbi */
321678e8d80SKen Lin { "Google", "Dibbi", "0000:00:02.0", port_db_conns },
3225bc2de5fSStefan Adolfsson /* Google Constitution */
323678e8d80SKen Lin { "Google", "Constitution", "0000:00:02.0", port_ba_conns },
324beeefd75Srasheed.hsueh /* Google Boxy */
325678e8d80SKen Lin { "Google", "Boxy", "0000:00:02.0", port_d_conns },
326cd5c11d5SKen Lin /* Google Taranza */
327cd5c11d5SKen Lin { "Google", "Taranza", "0000:00:02.0", port_db_conns },
328ebc733e5SKen Lin /* Google Dexi */
329ebc733e5SKen Lin { "Google", "Dexi", "0000:00:02.0", port_db_conns },
33076f623d2SKells Ping /* Google Dita */
33176f623d2SKells Ping { "Google", "Dita", "0000:00:02.0", port_db_conns },
3324be5e864SMauro Carvalho Chehab };
3334be5e864SMauro Carvalho Chehab
cros_ec_cec_find_hdmi_dev(struct device * dev,const char * const ** conns)3344be5e864SMauro Carvalho Chehab static struct device *cros_ec_cec_find_hdmi_dev(struct device *dev,
335e7885b9cSReka Norman const char * const **conns)
3364be5e864SMauro Carvalho Chehab {
3374be5e864SMauro Carvalho Chehab int i;
3384be5e864SMauro Carvalho Chehab
3394be5e864SMauro Carvalho Chehab for (i = 0 ; i < ARRAY_SIZE(cec_dmi_match_table) ; ++i) {
3404be5e864SMauro Carvalho Chehab const struct cec_dmi_match *m = &cec_dmi_match_table[i];
3414be5e864SMauro Carvalho Chehab
3424be5e864SMauro Carvalho Chehab if (dmi_match(DMI_SYS_VENDOR, m->sys_vendor) &&
3434be5e864SMauro Carvalho Chehab dmi_match(DMI_PRODUCT_NAME, m->product_name)) {
3444be5e864SMauro Carvalho Chehab struct device *d;
3454be5e864SMauro Carvalho Chehab
3464be5e864SMauro Carvalho Chehab /* Find the device, bail out if not yet registered */
3474be5e864SMauro Carvalho Chehab d = bus_find_device_by_name(&pci_bus_type, NULL,
3484be5e864SMauro Carvalho Chehab m->devname);
3494be5e864SMauro Carvalho Chehab if (!d)
3504be5e864SMauro Carvalho Chehab return ERR_PTR(-EPROBE_DEFER);
3514be5e864SMauro Carvalho Chehab put_device(d);
352e7885b9cSReka Norman *conns = m->conns;
3534be5e864SMauro Carvalho Chehab return d;
3544be5e864SMauro Carvalho Chehab }
3554be5e864SMauro Carvalho Chehab }
3564be5e864SMauro Carvalho Chehab
3574be5e864SMauro Carvalho Chehab /* Hardware support must be added in the cec_dmi_match_table */
3584be5e864SMauro Carvalho Chehab dev_warn(dev, "CEC notifier not configured for this hardware\n");
3594be5e864SMauro Carvalho Chehab
3604be5e864SMauro Carvalho Chehab return ERR_PTR(-ENODEV);
3614be5e864SMauro Carvalho Chehab }
3624be5e864SMauro Carvalho Chehab
3634be5e864SMauro Carvalho Chehab #else
3644be5e864SMauro Carvalho Chehab
cros_ec_cec_find_hdmi_dev(struct device * dev,const char * const ** conns)3654be5e864SMauro Carvalho Chehab static struct device *cros_ec_cec_find_hdmi_dev(struct device *dev,
366e7885b9cSReka Norman const char * const **conns)
3674be5e864SMauro Carvalho Chehab {
3684be5e864SMauro Carvalho Chehab return ERR_PTR(-ENODEV);
3694be5e864SMauro Carvalho Chehab }
3704be5e864SMauro Carvalho Chehab
3714be5e864SMauro Carvalho Chehab #endif
3724be5e864SMauro Carvalho Chehab
cros_ec_cec_get_num_ports(struct cros_ec_cec * cros_ec_cec)3735d227f02SReka Norman static int cros_ec_cec_get_num_ports(struct cros_ec_cec *cros_ec_cec)
3745d227f02SReka Norman {
3755d227f02SReka Norman struct ec_response_cec_port_count response;
3765d227f02SReka Norman int ret;
3775d227f02SReka Norman
3785d227f02SReka Norman ret = cros_ec_cmd(cros_ec_cec->cros_ec, 0, EC_CMD_CEC_PORT_COUNT, NULL,
3795d227f02SReka Norman 0, &response, sizeof(response));
3805d227f02SReka Norman if (ret < 0) {
3815d227f02SReka Norman /*
3825d227f02SReka Norman * Old EC firmware only supports one port and does not support
3835d227f02SReka Norman * the port count command, so fall back to assuming one port.
3845d227f02SReka Norman */
3855d227f02SReka Norman cros_ec_cec->num_ports = 1;
3865d227f02SReka Norman return 0;
3875d227f02SReka Norman }
3885d227f02SReka Norman
3895d227f02SReka Norman if (response.port_count == 0) {
3905d227f02SReka Norman dev_err(cros_ec_cec->cros_ec->dev,
3915d227f02SReka Norman "EC reports 0 CEC ports\n");
3925d227f02SReka Norman return -ENODEV;
3935d227f02SReka Norman }
3945d227f02SReka Norman
3955d227f02SReka Norman if (response.port_count > EC_CEC_MAX_PORTS) {
3965d227f02SReka Norman dev_err(cros_ec_cec->cros_ec->dev,
3975d227f02SReka Norman "EC reports too many ports: %d\n", response.port_count);
3985d227f02SReka Norman return -EINVAL;
3995d227f02SReka Norman }
4005d227f02SReka Norman
4015d227f02SReka Norman cros_ec_cec->num_ports = response.port_count;
4025d227f02SReka Norman return 0;
4035d227f02SReka Norman }
4045d227f02SReka Norman
cros_ec_cec_get_write_cmd_version(struct cros_ec_cec * cros_ec_cec)405adbfc747SReka Norman static int cros_ec_cec_get_write_cmd_version(struct cros_ec_cec *cros_ec_cec)
406adbfc747SReka Norman {
407adbfc747SReka Norman struct cros_ec_device *cros_ec = cros_ec_cec->cros_ec;
408adbfc747SReka Norman struct ec_params_get_cmd_versions_v1 params = {
409adbfc747SReka Norman .cmd = EC_CMD_CEC_WRITE_MSG,
410adbfc747SReka Norman };
411adbfc747SReka Norman struct ec_response_get_cmd_versions response;
412adbfc747SReka Norman int ret;
413adbfc747SReka Norman
414adbfc747SReka Norman ret = cros_ec_cmd(cros_ec, 1, EC_CMD_GET_CMD_VERSIONS, ¶ms,
415adbfc747SReka Norman sizeof(params), &response, sizeof(response));
416adbfc747SReka Norman if (ret < 0) {
417adbfc747SReka Norman dev_err(cros_ec->dev,
418adbfc747SReka Norman "error getting CEC write command version: %d\n", ret);
419adbfc747SReka Norman return ret;
420adbfc747SReka Norman }
421adbfc747SReka Norman
422adbfc747SReka Norman if (response.version_mask & EC_VER_MASK(1)) {
423adbfc747SReka Norman cros_ec_cec->write_cmd_version = 1;
424adbfc747SReka Norman } else {
425adbfc747SReka Norman if (cros_ec_cec->num_ports != 1) {
426adbfc747SReka Norman dev_err(cros_ec->dev,
427adbfc747SReka Norman "v0 write command only supports 1 port, %d reported\n",
428adbfc747SReka Norman cros_ec_cec->num_ports);
429adbfc747SReka Norman return -EINVAL;
430adbfc747SReka Norman }
431adbfc747SReka Norman cros_ec_cec->write_cmd_version = 0;
432adbfc747SReka Norman }
433adbfc747SReka Norman
434adbfc747SReka Norman return 0;
435adbfc747SReka Norman }
436adbfc747SReka Norman
cros_ec_cec_init_port(struct device * dev,struct cros_ec_cec * cros_ec_cec,int port_num,struct device * hdmi_dev,const char * const * conns)4374d0e179aSReka Norman static int cros_ec_cec_init_port(struct device *dev,
4384d0e179aSReka Norman struct cros_ec_cec *cros_ec_cec,
4394d0e179aSReka Norman int port_num, struct device *hdmi_dev,
440e7885b9cSReka Norman const char * const *conns)
4414d0e179aSReka Norman {
4424d0e179aSReka Norman struct cros_ec_cec_port *port;
4434d0e179aSReka Norman int ret;
4444d0e179aSReka Norman
4454d0e179aSReka Norman port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL);
4464d0e179aSReka Norman if (!port)
4474d0e179aSReka Norman return -ENOMEM;
4484d0e179aSReka Norman
4494d0e179aSReka Norman port->cros_ec_cec = cros_ec_cec;
4504d0e179aSReka Norman port->port_num = port_num;
4514d0e179aSReka Norman
4524d0e179aSReka Norman port->adap = cec_allocate_adapter(&cros_ec_cec_ops, port, DRV_NAME,
4534d0e179aSReka Norman CEC_CAP_DEFAULTS |
4544d0e179aSReka Norman CEC_CAP_CONNECTOR_INFO, 1);
4554d0e179aSReka Norman if (IS_ERR(port->adap))
4564d0e179aSReka Norman return PTR_ERR(port->adap);
4574d0e179aSReka Norman
458e7885b9cSReka Norman if (!conns[port_num]) {
459e7885b9cSReka Norman dev_err(dev, "no conn for port %d\n", port_num);
460e7885b9cSReka Norman ret = -ENODEV;
461e7885b9cSReka Norman goto out_probe_adapter;
462e7885b9cSReka Norman }
463e7885b9cSReka Norman
464e7885b9cSReka Norman port->notify = cec_notifier_cec_adap_register(hdmi_dev, conns[port_num],
4654d0e179aSReka Norman port->adap);
4664d0e179aSReka Norman if (!port->notify) {
4674d0e179aSReka Norman ret = -ENOMEM;
4684d0e179aSReka Norman goto out_probe_adapter;
4694d0e179aSReka Norman }
4704d0e179aSReka Norman
4714d0e179aSReka Norman ret = cec_register_adapter(port->adap, dev);
4724d0e179aSReka Norman if (ret < 0)
4734d0e179aSReka Norman goto out_probe_notify;
4744d0e179aSReka Norman
4754d0e179aSReka Norman cros_ec_cec->ports[port_num] = port;
4764d0e179aSReka Norman
4774d0e179aSReka Norman return 0;
4784d0e179aSReka Norman
4794d0e179aSReka Norman out_probe_notify:
4804d0e179aSReka Norman cec_notifier_cec_adap_unregister(port->notify, port->adap);
4814d0e179aSReka Norman out_probe_adapter:
4824d0e179aSReka Norman cec_delete_adapter(port->adap);
4834d0e179aSReka Norman return ret;
4844d0e179aSReka Norman }
4854d0e179aSReka Norman
cros_ec_cec_probe(struct platform_device * pdev)4864be5e864SMauro Carvalho Chehab static int cros_ec_cec_probe(struct platform_device *pdev)
4874be5e864SMauro Carvalho Chehab {
4884be5e864SMauro Carvalho Chehab struct cros_ec_dev *ec_dev = dev_get_drvdata(pdev->dev.parent);
4894be5e864SMauro Carvalho Chehab struct cros_ec_device *cros_ec = ec_dev->ec_dev;
4904be5e864SMauro Carvalho Chehab struct cros_ec_cec *cros_ec_cec;
4914d0e179aSReka Norman struct cros_ec_cec_port *port;
4924be5e864SMauro Carvalho Chehab struct device *hdmi_dev;
493e7885b9cSReka Norman const char * const *conns = NULL;
4944be5e864SMauro Carvalho Chehab int ret;
4954be5e864SMauro Carvalho Chehab
496e7885b9cSReka Norman hdmi_dev = cros_ec_cec_find_hdmi_dev(&pdev->dev, &conns);
4974be5e864SMauro Carvalho Chehab if (IS_ERR(hdmi_dev))
4984be5e864SMauro Carvalho Chehab return PTR_ERR(hdmi_dev);
4994be5e864SMauro Carvalho Chehab
5004be5e864SMauro Carvalho Chehab cros_ec_cec = devm_kzalloc(&pdev->dev, sizeof(*cros_ec_cec),
5014be5e864SMauro Carvalho Chehab GFP_KERNEL);
5024be5e864SMauro Carvalho Chehab if (!cros_ec_cec)
5034be5e864SMauro Carvalho Chehab return -ENOMEM;
5044be5e864SMauro Carvalho Chehab
5054be5e864SMauro Carvalho Chehab platform_set_drvdata(pdev, cros_ec_cec);
5064be5e864SMauro Carvalho Chehab cros_ec_cec->cros_ec = cros_ec;
5074be5e864SMauro Carvalho Chehab
5086f01dfb7SDariusz Marcinkiewicz device_init_wakeup(&pdev->dev, 1);
5094be5e864SMauro Carvalho Chehab
5105d227f02SReka Norman ret = cros_ec_cec_get_num_ports(cros_ec_cec);
5115d227f02SReka Norman if (ret)
5125d227f02SReka Norman return ret;
5134be5e864SMauro Carvalho Chehab
514adbfc747SReka Norman ret = cros_ec_cec_get_write_cmd_version(cros_ec_cec);
515adbfc747SReka Norman if (ret)
516adbfc747SReka Norman return ret;
517adbfc747SReka Norman
5184d0e179aSReka Norman for (int i = 0; i < cros_ec_cec->num_ports; i++) {
5194d0e179aSReka Norman ret = cros_ec_cec_init_port(&pdev->dev, cros_ec_cec, i,
520e7885b9cSReka Norman hdmi_dev, conns);
5214d0e179aSReka Norman if (ret)
5224d0e179aSReka Norman goto unregister_ports;
5234be5e864SMauro Carvalho Chehab }
5244be5e864SMauro Carvalho Chehab
5254be5e864SMauro Carvalho Chehab /* Get CEC events from the EC. */
5264be5e864SMauro Carvalho Chehab cros_ec_cec->notifier.notifier_call = cros_ec_cec_event;
5274be5e864SMauro Carvalho Chehab ret = blocking_notifier_chain_register(&cros_ec->event_notifier,
5284be5e864SMauro Carvalho Chehab &cros_ec_cec->notifier);
5294be5e864SMauro Carvalho Chehab if (ret) {
5304be5e864SMauro Carvalho Chehab dev_err(&pdev->dev, "failed to register notifier\n");
5314d0e179aSReka Norman goto unregister_ports;
5324be5e864SMauro Carvalho Chehab }
5334be5e864SMauro Carvalho Chehab
5344be5e864SMauro Carvalho Chehab return 0;
5354be5e864SMauro Carvalho Chehab
5364d0e179aSReka Norman unregister_ports:
5374d0e179aSReka Norman /*
5384d0e179aSReka Norman * Unregister any adapters which have been registered. We don't add the
5394d0e179aSReka Norman * port to the array until the adapter has been registered successfully,
5404d0e179aSReka Norman * so any non-NULL ports must have been registered.
5414d0e179aSReka Norman */
5424d0e179aSReka Norman for (int i = 0; i < cros_ec_cec->num_ports; i++) {
5434d0e179aSReka Norman port = cros_ec_cec->ports[i];
5444d0e179aSReka Norman if (!port)
5454d0e179aSReka Norman break;
5464d0e179aSReka Norman cec_notifier_cec_adap_unregister(port->notify, port->adap);
5474d0e179aSReka Norman cec_unregister_adapter(port->adap);
5484d0e179aSReka Norman }
5494be5e864SMauro Carvalho Chehab return ret;
5504be5e864SMauro Carvalho Chehab }
5514be5e864SMauro Carvalho Chehab
cros_ec_cec_remove(struct platform_device * pdev)55245848b28SUwe Kleine-König static void cros_ec_cec_remove(struct platform_device *pdev)
5534be5e864SMauro Carvalho Chehab {
5544be5e864SMauro Carvalho Chehab struct cros_ec_cec *cros_ec_cec = platform_get_drvdata(pdev);
5554be5e864SMauro Carvalho Chehab struct device *dev = &pdev->dev;
5564d0e179aSReka Norman struct cros_ec_cec_port *port;
5574be5e864SMauro Carvalho Chehab int ret;
5584be5e864SMauro Carvalho Chehab
5590ff7aee2SUwe Kleine-König /*
5600ff7aee2SUwe Kleine-König * blocking_notifier_chain_unregister() only fails if the notifier isn't
5610ff7aee2SUwe Kleine-König * in the list. We know it was added to it by .probe(), so there should
5620ff7aee2SUwe Kleine-König * be no need for error checking. Be cautious and still check.
5630ff7aee2SUwe Kleine-König */
5644be5e864SMauro Carvalho Chehab ret = blocking_notifier_chain_unregister(
5654be5e864SMauro Carvalho Chehab &cros_ec_cec->cros_ec->event_notifier,
5664be5e864SMauro Carvalho Chehab &cros_ec_cec->notifier);
5670ff7aee2SUwe Kleine-König if (ret)
5684be5e864SMauro Carvalho Chehab dev_err(dev, "failed to unregister notifier\n");
5694be5e864SMauro Carvalho Chehab
5704d0e179aSReka Norman for (int i = 0; i < cros_ec_cec->num_ports; i++) {
5714d0e179aSReka Norman port = cros_ec_cec->ports[i];
5724d0e179aSReka Norman cec_notifier_cec_adap_unregister(port->notify, port->adap);
5734d0e179aSReka Norman cec_unregister_adapter(port->adap);
5744d0e179aSReka Norman }
5754be5e864SMauro Carvalho Chehab }
5764be5e864SMauro Carvalho Chehab
577*50a0844bSTzung-Bi Shih static const struct platform_device_id cros_ec_cec_id[] = {
578*50a0844bSTzung-Bi Shih { DRV_NAME, 0 },
579*50a0844bSTzung-Bi Shih {}
580*50a0844bSTzung-Bi Shih };
581*50a0844bSTzung-Bi Shih MODULE_DEVICE_TABLE(platform, cros_ec_cec_id);
582*50a0844bSTzung-Bi Shih
5834be5e864SMauro Carvalho Chehab static struct platform_driver cros_ec_cec_driver = {
5844be5e864SMauro Carvalho Chehab .probe = cros_ec_cec_probe,
58545848b28SUwe Kleine-König .remove_new = cros_ec_cec_remove,
5864be5e864SMauro Carvalho Chehab .driver = {
5874be5e864SMauro Carvalho Chehab .name = DRV_NAME,
5884be5e864SMauro Carvalho Chehab .pm = &cros_ec_cec_pm_ops,
5894be5e864SMauro Carvalho Chehab },
590*50a0844bSTzung-Bi Shih .id_table = cros_ec_cec_id,
5914be5e864SMauro Carvalho Chehab };
5924be5e864SMauro Carvalho Chehab
5934be5e864SMauro Carvalho Chehab module_platform_driver(cros_ec_cec_driver);
5944be5e864SMauro Carvalho Chehab
5954be5e864SMauro Carvalho Chehab MODULE_DESCRIPTION("CEC driver for ChromeOS ECs");
5964be5e864SMauro Carvalho Chehab MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
5974be5e864SMauro Carvalho Chehab MODULE_LICENSE("GPL");
598