1 // SPDX-License-Identifier: GPL-2.0+
3 * CEC driver for ChromeOS Embedded Controller
5 * Copyright (c) 2018 BayLibre, SAS
6 * Author: Neil Armstrong <narmstrong@baylibre.com>
9 #include <linux/kernel.h>
10 #include <linux/module.h>
11 #include <linux/platform_device.h>
12 #include <linux/dmi.h>
13 #include <linux/pci.h>
14 #include <linux/cec.h>
15 #include <linux/slab.h>
16 #include <linux/interrupt.h>
17 #include <linux/mfd/cros_ec.h>
18 #include <linux/platform_data/cros_ec_commands.h>
19 #include <linux/platform_data/cros_ec_proto.h>
20 #include <media/cec.h>
21 #include <media/cec-notifier.h>
23 #define DRV_NAME "cros-ec-cec"
26 * struct cros_ec_cec - Driver data for EC CEC
28 * @cros_ec: Pointer to EC device
29 * @notifier: Notifier info for responding to EC events
31 * @notify: CEC notifier pointer
32 * @rx_msg: storage for a received message
35 struct cros_ec_device
*cros_ec
;
36 struct notifier_block notifier
;
37 struct cec_adapter
*adap
;
38 struct cec_notifier
*notify
;
39 struct cec_msg rx_msg
;
42 static void handle_cec_message(struct cros_ec_cec
*cros_ec_cec
)
44 struct cros_ec_device
*cros_ec
= cros_ec_cec
->cros_ec
;
45 uint8_t *cec_message
= cros_ec
->event_data
.data
.cec_message
;
46 unsigned int len
= cros_ec
->event_size
;
48 cros_ec_cec
->rx_msg
.len
= len
;
49 memcpy(cros_ec_cec
->rx_msg
.msg
, cec_message
, len
);
51 cec_received_msg(cros_ec_cec
->adap
, &cros_ec_cec
->rx_msg
);
54 static void handle_cec_event(struct cros_ec_cec
*cros_ec_cec
)
56 struct cros_ec_device
*cros_ec
= cros_ec_cec
->cros_ec
;
57 uint32_t events
= cros_ec
->event_data
.data
.cec_events
;
59 if (events
& EC_MKBP_CEC_SEND_OK
)
60 cec_transmit_attempt_done(cros_ec_cec
->adap
,
63 /* FW takes care of all retries, tell core to avoid more retries */
64 if (events
& EC_MKBP_CEC_SEND_FAILED
)
65 cec_transmit_attempt_done(cros_ec_cec
->adap
,
66 CEC_TX_STATUS_MAX_RETRIES
|
70 static int cros_ec_cec_event(struct notifier_block
*nb
,
71 unsigned long queued_during_suspend
,
74 struct cros_ec_cec
*cros_ec_cec
;
75 struct cros_ec_device
*cros_ec
;
77 cros_ec_cec
= container_of(nb
, struct cros_ec_cec
, notifier
);
78 cros_ec
= cros_ec_cec
->cros_ec
;
80 if (cros_ec
->event_data
.event_type
== EC_MKBP_EVENT_CEC_EVENT
) {
81 handle_cec_event(cros_ec_cec
);
85 if (cros_ec
->event_data
.event_type
== EC_MKBP_EVENT_CEC_MESSAGE
) {
86 handle_cec_message(cros_ec_cec
);
93 static int cros_ec_cec_set_log_addr(struct cec_adapter
*adap
, u8 logical_addr
)
95 struct cros_ec_cec
*cros_ec_cec
= adap
->priv
;
96 struct cros_ec_device
*cros_ec
= cros_ec_cec
->cros_ec
;
98 struct cros_ec_command msg
;
99 struct ec_params_cec_set data
;
103 msg
.msg
.command
= EC_CMD_CEC_SET
;
104 msg
.msg
.outsize
= sizeof(msg
.data
);
105 msg
.data
.cmd
= CEC_CMD_LOGICAL_ADDRESS
;
106 msg
.data
.val
= logical_addr
;
108 ret
= cros_ec_cmd_xfer_status(cros_ec
, &msg
.msg
);
110 dev_err(cros_ec
->dev
,
111 "error setting CEC logical address on EC: %d\n", ret
);
118 static int cros_ec_cec_transmit(struct cec_adapter
*adap
, u8 attempts
,
119 u32 signal_free_time
, struct cec_msg
*cec_msg
)
121 struct cros_ec_cec
*cros_ec_cec
= adap
->priv
;
122 struct cros_ec_device
*cros_ec
= cros_ec_cec
->cros_ec
;
124 struct cros_ec_command msg
;
125 struct ec_params_cec_write data
;
129 msg
.msg
.command
= EC_CMD_CEC_WRITE_MSG
;
130 msg
.msg
.outsize
= cec_msg
->len
;
131 memcpy(msg
.data
.msg
, cec_msg
->msg
, cec_msg
->len
);
133 ret
= cros_ec_cmd_xfer_status(cros_ec
, &msg
.msg
);
135 dev_err(cros_ec
->dev
,
136 "error writing CEC msg on EC: %d\n", ret
);
143 static int cros_ec_cec_adap_enable(struct cec_adapter
*adap
, bool enable
)
145 struct cros_ec_cec
*cros_ec_cec
= adap
->priv
;
146 struct cros_ec_device
*cros_ec
= cros_ec_cec
->cros_ec
;
148 struct cros_ec_command msg
;
149 struct ec_params_cec_set data
;
153 msg
.msg
.command
= EC_CMD_CEC_SET
;
154 msg
.msg
.outsize
= sizeof(msg
.data
);
155 msg
.data
.cmd
= CEC_CMD_ENABLE
;
156 msg
.data
.val
= enable
;
158 ret
= cros_ec_cmd_xfer_status(cros_ec
, &msg
.msg
);
160 dev_err(cros_ec
->dev
,
161 "error %sabling CEC on EC: %d\n",
162 (enable
? "en" : "dis"), ret
);
169 static const struct cec_adap_ops cros_ec_cec_ops
= {
170 .adap_enable
= cros_ec_cec_adap_enable
,
171 .adap_log_addr
= cros_ec_cec_set_log_addr
,
172 .adap_transmit
= cros_ec_cec_transmit
,
175 #ifdef CONFIG_PM_SLEEP
176 static int cros_ec_cec_suspend(struct device
*dev
)
178 struct platform_device
*pdev
= to_platform_device(dev
);
179 struct cros_ec_cec
*cros_ec_cec
= dev_get_drvdata(&pdev
->dev
);
181 if (device_may_wakeup(dev
))
182 enable_irq_wake(cros_ec_cec
->cros_ec
->irq
);
187 static int cros_ec_cec_resume(struct device
*dev
)
189 struct platform_device
*pdev
= to_platform_device(dev
);
190 struct cros_ec_cec
*cros_ec_cec
= dev_get_drvdata(&pdev
->dev
);
192 if (device_may_wakeup(dev
))
193 disable_irq_wake(cros_ec_cec
->cros_ec
->irq
);
199 static SIMPLE_DEV_PM_OPS(cros_ec_cec_pm_ops
,
200 cros_ec_cec_suspend
, cros_ec_cec_resume
);
202 #if IS_ENABLED(CONFIG_PCI) && IS_ENABLED(CONFIG_DMI)
205 * The Firmware only handles a single CEC interface tied to a single HDMI
206 * connector we specify along with the DRM device name handling the HDMI output
209 struct cec_dmi_match
{
210 const char *sys_vendor
;
211 const char *product_name
;
216 static const struct cec_dmi_match cec_dmi_match_table
[] = {
218 { "Google", "Fizz", "0000:00:02.0", "Port B" },
221 static struct device
*cros_ec_cec_find_hdmi_dev(struct device
*dev
,
226 for (i
= 0 ; i
< ARRAY_SIZE(cec_dmi_match_table
) ; ++i
) {
227 const struct cec_dmi_match
*m
= &cec_dmi_match_table
[i
];
229 if (dmi_match(DMI_SYS_VENDOR
, m
->sys_vendor
) &&
230 dmi_match(DMI_PRODUCT_NAME
, m
->product_name
)) {
233 /* Find the device, bail out if not yet registered */
234 d
= bus_find_device_by_name(&pci_bus_type
, NULL
,
237 return ERR_PTR(-EPROBE_DEFER
);
244 /* Hardware support must be added in the cec_dmi_match_table */
245 dev_warn(dev
, "CEC notifier not configured for this hardware\n");
247 return ERR_PTR(-ENODEV
);
252 static struct device
*cros_ec_cec_find_hdmi_dev(struct device
*dev
,
255 return ERR_PTR(-ENODEV
);
260 static int cros_ec_cec_probe(struct platform_device
*pdev
)
262 struct cros_ec_dev
*ec_dev
= dev_get_drvdata(pdev
->dev
.parent
);
263 struct cros_ec_device
*cros_ec
= ec_dev
->ec_dev
;
264 struct cros_ec_cec
*cros_ec_cec
;
265 struct device
*hdmi_dev
;
266 const char *conn
= NULL
;
269 hdmi_dev
= cros_ec_cec_find_hdmi_dev(&pdev
->dev
, &conn
);
270 if (IS_ERR(hdmi_dev
))
271 return PTR_ERR(hdmi_dev
);
273 cros_ec_cec
= devm_kzalloc(&pdev
->dev
, sizeof(*cros_ec_cec
),
278 platform_set_drvdata(pdev
, cros_ec_cec
);
279 cros_ec_cec
->cros_ec
= cros_ec
;
281 ret
= device_init_wakeup(&pdev
->dev
, 1);
283 dev_err(&pdev
->dev
, "failed to initialize wakeup\n");
287 cros_ec_cec
->adap
= cec_allocate_adapter(&cros_ec_cec_ops
, cros_ec_cec
,
290 CEC_CAP_CONNECTOR_INFO
, 1);
291 if (IS_ERR(cros_ec_cec
->adap
))
292 return PTR_ERR(cros_ec_cec
->adap
);
294 cros_ec_cec
->notify
= cec_notifier_cec_adap_register(hdmi_dev
, conn
,
296 if (!cros_ec_cec
->notify
) {
298 goto out_probe_adapter
;
301 /* Get CEC events from the EC. */
302 cros_ec_cec
->notifier
.notifier_call
= cros_ec_cec_event
;
303 ret
= blocking_notifier_chain_register(&cros_ec
->event_notifier
,
304 &cros_ec_cec
->notifier
);
306 dev_err(&pdev
->dev
, "failed to register notifier\n");
307 goto out_probe_notify
;
310 ret
= cec_register_adapter(cros_ec_cec
->adap
, &pdev
->dev
);
312 goto out_probe_notify
;
317 cec_notifier_cec_adap_unregister(cros_ec_cec
->notify
,
320 cec_delete_adapter(cros_ec_cec
->adap
);
324 static int cros_ec_cec_remove(struct platform_device
*pdev
)
326 struct cros_ec_cec
*cros_ec_cec
= platform_get_drvdata(pdev
);
327 struct device
*dev
= &pdev
->dev
;
330 ret
= blocking_notifier_chain_unregister(
331 &cros_ec_cec
->cros_ec
->event_notifier
,
332 &cros_ec_cec
->notifier
);
335 dev_err(dev
, "failed to unregister notifier\n");
339 cec_notifier_cec_adap_unregister(cros_ec_cec
->notify
,
341 cec_unregister_adapter(cros_ec_cec
->adap
);
346 static struct platform_driver cros_ec_cec_driver
= {
347 .probe
= cros_ec_cec_probe
,
348 .remove
= cros_ec_cec_remove
,
351 .pm
= &cros_ec_cec_pm_ops
,
355 module_platform_driver(cros_ec_cec_driver
);
357 MODULE_DESCRIPTION("CEC driver for ChromeOS ECs");
358 MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
359 MODULE_LICENSE("GPL");
360 MODULE_ALIAS("platform:" DRV_NAME
);