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