xref: /linux/drivers/media/cec/platform/cros-ec/cros-ec-cec.c (revision 50a0844bf8c4d38be540e423672ef9408d029252)
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 
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 
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 
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, &params,
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 
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 
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 
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, &params, 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 
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, &params,
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, &params_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 
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, &params, 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
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 
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 
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 
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 
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 
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, &params,
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 
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 
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 
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