1 // SPDX-License-Identifier: GPL-2.0-only
3 * Designware HDMI CEC driver
5 * Copyright (C) 2015-2017 Russell King.
7 #include <linux/interrupt.h>
9 #include <linux/module.h>
10 #include <linux/platform_device.h>
11 #include <linux/sched.h>
12 #include <linux/slab.h>
14 #include <drm/drm_edid.h>
16 #include <media/cec.h>
17 #include <media/cec-notifier.h>
19 #include "dw-hdmi-cec.h"
22 HDMI_IH_CEC_STAT0
= 0x0106,
23 HDMI_IH_MUTE_CEC_STAT0
= 0x0186,
25 HDMI_CEC_CTRL
= 0x7d00,
26 CEC_CTRL_START
= BIT(0),
27 CEC_CTRL_FRAME_TYP
= 3 << 1,
28 CEC_CTRL_RETRY
= 0 << 1,
29 CEC_CTRL_NORMAL
= 1 << 1,
30 CEC_CTRL_IMMED
= 2 << 1,
32 HDMI_CEC_STAT
= 0x7d01,
33 CEC_STAT_DONE
= BIT(0),
34 CEC_STAT_EOM
= BIT(1),
35 CEC_STAT_NACK
= BIT(2),
36 CEC_STAT_ARBLOST
= BIT(3),
37 CEC_STAT_ERROR_INIT
= BIT(4),
38 CEC_STAT_ERROR_FOLL
= BIT(5),
39 CEC_STAT_WAKEUP
= BIT(6),
41 HDMI_CEC_MASK
= 0x7d02,
42 HDMI_CEC_POLARITY
= 0x7d03,
43 HDMI_CEC_INT
= 0x7d04,
44 HDMI_CEC_ADDR_L
= 0x7d05,
45 HDMI_CEC_ADDR_H
= 0x7d06,
46 HDMI_CEC_TX_CNT
= 0x7d07,
47 HDMI_CEC_RX_CNT
= 0x7d08,
48 HDMI_CEC_TX_DATA0
= 0x7d10,
49 HDMI_CEC_RX_DATA0
= 0x7d20,
50 HDMI_CEC_LOCK
= 0x7d30,
51 HDMI_CEC_WKUPCTRL
= 0x7d31,
56 const struct dw_hdmi_cec_ops
*ops
;
58 struct cec_adapter
*adap
;
59 struct cec_msg rx_msg
;
60 unsigned int tx_status
;
63 struct cec_notifier
*notify
;
67 static void dw_hdmi_write(struct dw_hdmi_cec
*cec
, u8 val
, int offset
)
69 cec
->ops
->write(cec
->hdmi
, val
, offset
);
72 static u8
dw_hdmi_read(struct dw_hdmi_cec
*cec
, int offset
)
74 return cec
->ops
->read(cec
->hdmi
, offset
);
77 static int dw_hdmi_cec_log_addr(struct cec_adapter
*adap
, u8 logical_addr
)
79 struct dw_hdmi_cec
*cec
= cec_get_drvdata(adap
);
81 if (logical_addr
== CEC_LOG_ADDR_INVALID
)
84 cec
->addresses
|= BIT(logical_addr
) | BIT(15);
86 dw_hdmi_write(cec
, cec
->addresses
& 255, HDMI_CEC_ADDR_L
);
87 dw_hdmi_write(cec
, cec
->addresses
>> 8, HDMI_CEC_ADDR_H
);
92 static int dw_hdmi_cec_transmit(struct cec_adapter
*adap
, u8 attempts
,
93 u32 signal_free_time
, struct cec_msg
*msg
)
95 struct dw_hdmi_cec
*cec
= cec_get_drvdata(adap
);
98 switch (signal_free_time
) {
99 case CEC_SIGNAL_FREE_TIME_RETRY
:
100 ctrl
= CEC_CTRL_RETRY
;
102 case CEC_SIGNAL_FREE_TIME_NEW_INITIATOR
:
104 ctrl
= CEC_CTRL_NORMAL
;
106 case CEC_SIGNAL_FREE_TIME_NEXT_XFER
:
107 ctrl
= CEC_CTRL_IMMED
;
111 for (i
= 0; i
< msg
->len
; i
++)
112 dw_hdmi_write(cec
, msg
->msg
[i
], HDMI_CEC_TX_DATA0
+ i
);
114 dw_hdmi_write(cec
, msg
->len
, HDMI_CEC_TX_CNT
);
115 dw_hdmi_write(cec
, ctrl
| CEC_CTRL_START
, HDMI_CEC_CTRL
);
120 static irqreturn_t
dw_hdmi_cec_hardirq(int irq
, void *data
)
122 struct cec_adapter
*adap
= data
;
123 struct dw_hdmi_cec
*cec
= cec_get_drvdata(adap
);
124 unsigned int stat
= dw_hdmi_read(cec
, HDMI_IH_CEC_STAT0
);
125 irqreturn_t ret
= IRQ_HANDLED
;
130 dw_hdmi_write(cec
, stat
, HDMI_IH_CEC_STAT0
);
132 if (stat
& CEC_STAT_ERROR_INIT
) {
133 cec
->tx_status
= CEC_TX_STATUS_ERROR
;
135 ret
= IRQ_WAKE_THREAD
;
136 } else if (stat
& CEC_STAT_DONE
) {
137 cec
->tx_status
= CEC_TX_STATUS_OK
;
139 ret
= IRQ_WAKE_THREAD
;
140 } else if (stat
& CEC_STAT_NACK
) {
141 cec
->tx_status
= CEC_TX_STATUS_NACK
;
143 ret
= IRQ_WAKE_THREAD
;
146 if (stat
& CEC_STAT_EOM
) {
149 len
= dw_hdmi_read(cec
, HDMI_CEC_RX_CNT
);
150 if (len
> sizeof(cec
->rx_msg
.msg
))
151 len
= sizeof(cec
->rx_msg
.msg
);
153 for (i
= 0; i
< len
; i
++)
155 dw_hdmi_read(cec
, HDMI_CEC_RX_DATA0
+ i
);
157 dw_hdmi_write(cec
, 0, HDMI_CEC_LOCK
);
159 cec
->rx_msg
.len
= len
;
163 ret
= IRQ_WAKE_THREAD
;
169 static irqreturn_t
dw_hdmi_cec_thread(int irq
, void *data
)
171 struct cec_adapter
*adap
= data
;
172 struct dw_hdmi_cec
*cec
= cec_get_drvdata(adap
);
175 cec
->tx_done
= false;
176 cec_transmit_attempt_done(adap
, cec
->tx_status
);
179 cec
->rx_done
= false;
181 cec_received_msg(adap
, &cec
->rx_msg
);
186 static int dw_hdmi_cec_enable(struct cec_adapter
*adap
, bool enable
)
188 struct dw_hdmi_cec
*cec
= cec_get_drvdata(adap
);
191 dw_hdmi_write(cec
, ~0, HDMI_CEC_MASK
);
192 dw_hdmi_write(cec
, ~0, HDMI_IH_MUTE_CEC_STAT0
);
193 dw_hdmi_write(cec
, 0, HDMI_CEC_POLARITY
);
195 cec
->ops
->disable(cec
->hdmi
);
199 dw_hdmi_write(cec
, 0, HDMI_CEC_CTRL
);
200 dw_hdmi_write(cec
, ~0, HDMI_IH_CEC_STAT0
);
201 dw_hdmi_write(cec
, 0, HDMI_CEC_LOCK
);
203 dw_hdmi_cec_log_addr(cec
->adap
, CEC_LOG_ADDR_INVALID
);
205 cec
->ops
->enable(cec
->hdmi
);
207 irqs
= CEC_STAT_ERROR_INIT
| CEC_STAT_NACK
| CEC_STAT_EOM
|
209 dw_hdmi_write(cec
, irqs
, HDMI_CEC_POLARITY
);
210 dw_hdmi_write(cec
, ~irqs
, HDMI_CEC_MASK
);
211 dw_hdmi_write(cec
, ~irqs
, HDMI_IH_MUTE_CEC_STAT0
);
216 static const struct cec_adap_ops dw_hdmi_cec_ops
= {
217 .adap_enable
= dw_hdmi_cec_enable
,
218 .adap_log_addr
= dw_hdmi_cec_log_addr
,
219 .adap_transmit
= dw_hdmi_cec_transmit
,
222 static void dw_hdmi_cec_del(void *data
)
224 struct dw_hdmi_cec
*cec
= data
;
226 cec_delete_adapter(cec
->adap
);
229 static int dw_hdmi_cec_probe(struct platform_device
*pdev
)
231 struct dw_hdmi_cec_data
*data
= dev_get_platdata(&pdev
->dev
);
232 struct dw_hdmi_cec
*cec
;
239 * Our device is just a convenience - we want to link to the real
240 * hardware device here, so that userspace can see the association
241 * between the HDMI hardware and its associated CEC chardev.
243 cec
= devm_kzalloc(&pdev
->dev
, sizeof(*cec
), GFP_KERNEL
);
247 cec
->irq
= data
->irq
;
248 cec
->ops
= data
->ops
;
249 cec
->hdmi
= data
->hdmi
;
251 platform_set_drvdata(pdev
, cec
);
253 dw_hdmi_write(cec
, 0, HDMI_CEC_TX_CNT
);
254 dw_hdmi_write(cec
, ~0, HDMI_CEC_MASK
);
255 dw_hdmi_write(cec
, ~0, HDMI_IH_MUTE_CEC_STAT0
);
256 dw_hdmi_write(cec
, 0, HDMI_CEC_POLARITY
);
258 cec
->adap
= cec_allocate_adapter(&dw_hdmi_cec_ops
, cec
, "dw_hdmi",
260 CEC_CAP_CONNECTOR_INFO
,
262 if (IS_ERR(cec
->adap
))
263 return PTR_ERR(cec
->adap
);
265 /* override the module pointer */
266 cec
->adap
->owner
= THIS_MODULE
;
268 ret
= devm_add_action(&pdev
->dev
, dw_hdmi_cec_del
, cec
);
270 cec_delete_adapter(cec
->adap
);
274 ret
= devm_request_threaded_irq(&pdev
->dev
, cec
->irq
,
276 dw_hdmi_cec_thread
, IRQF_SHARED
,
277 "dw-hdmi-cec", cec
->adap
);
281 cec
->notify
= cec_notifier_cec_adap_register(pdev
->dev
.parent
,
286 ret
= cec_register_adapter(cec
->adap
, pdev
->dev
.parent
);
288 cec_notifier_cec_adap_unregister(cec
->notify
, cec
->adap
);
293 * CEC documentation says we must not call cec_delete_adapter
294 * after a successful call to cec_register_adapter().
296 devm_remove_action(&pdev
->dev
, dw_hdmi_cec_del
, cec
);
301 static int dw_hdmi_cec_remove(struct platform_device
*pdev
)
303 struct dw_hdmi_cec
*cec
= platform_get_drvdata(pdev
);
305 cec_notifier_cec_adap_unregister(cec
->notify
, cec
->adap
);
306 cec_unregister_adapter(cec
->adap
);
311 static struct platform_driver dw_hdmi_cec_driver
= {
312 .probe
= dw_hdmi_cec_probe
,
313 .remove
= dw_hdmi_cec_remove
,
315 .name
= "dw-hdmi-cec",
318 module_platform_driver(dw_hdmi_cec_driver
);
320 MODULE_AUTHOR("Russell King <rmk+kernel@armlinux.org.uk>");
321 MODULE_DESCRIPTION("Synopsys Designware HDMI CEC driver for i.MX");
322 MODULE_LICENSE("GPL");
323 MODULE_ALIAS(PLATFORM_MODULE_PREFIX
"dw-hdmi-cec");