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