2 * Designware HDMI CEC driver
4 * Copyright (C) 2015-2017 Russell King.
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 2 as
8 * published by the Free Software Foundation.
10 #include <linux/interrupt.h>
12 #include <linux/module.h>
13 #include <linux/platform_device.h>
14 #include <linux/sched.h>
15 #include <linux/slab.h>
17 #include <drm/drm_edid.h>
19 #include <media/cec.h>
20 #include <media/cec-notifier.h>
22 #include "dw-hdmi-cec.h"
25 HDMI_IH_CEC_STAT0
= 0x0106,
26 HDMI_IH_MUTE_CEC_STAT0
= 0x0186,
28 HDMI_CEC_CTRL
= 0x7d00,
29 CEC_CTRL_START
= BIT(0),
30 CEC_CTRL_FRAME_TYP
= 3 << 1,
31 CEC_CTRL_RETRY
= 0 << 1,
32 CEC_CTRL_NORMAL
= 1 << 1,
33 CEC_CTRL_IMMED
= 2 << 1,
35 HDMI_CEC_STAT
= 0x7d01,
36 CEC_STAT_DONE
= BIT(0),
37 CEC_STAT_EOM
= BIT(1),
38 CEC_STAT_NACK
= BIT(2),
39 CEC_STAT_ARBLOST
= BIT(3),
40 CEC_STAT_ERROR_INIT
= BIT(4),
41 CEC_STAT_ERROR_FOLL
= BIT(5),
42 CEC_STAT_WAKEUP
= BIT(6),
44 HDMI_CEC_MASK
= 0x7d02,
45 HDMI_CEC_POLARITY
= 0x7d03,
46 HDMI_CEC_INT
= 0x7d04,
47 HDMI_CEC_ADDR_L
= 0x7d05,
48 HDMI_CEC_ADDR_H
= 0x7d06,
49 HDMI_CEC_TX_CNT
= 0x7d07,
50 HDMI_CEC_RX_CNT
= 0x7d08,
51 HDMI_CEC_TX_DATA0
= 0x7d10,
52 HDMI_CEC_RX_DATA0
= 0x7d20,
53 HDMI_CEC_LOCK
= 0x7d30,
54 HDMI_CEC_WKUPCTRL
= 0x7d31,
59 const struct dw_hdmi_cec_ops
*ops
;
61 struct cec_adapter
*adap
;
62 struct cec_msg rx_msg
;
63 unsigned int tx_status
;
66 struct cec_notifier
*notify
;
70 static void dw_hdmi_write(struct dw_hdmi_cec
*cec
, u8 val
, int offset
)
72 cec
->ops
->write(cec
->hdmi
, val
, offset
);
75 static u8
dw_hdmi_read(struct dw_hdmi_cec
*cec
, int offset
)
77 return cec
->ops
->read(cec
->hdmi
, offset
);
80 static int dw_hdmi_cec_log_addr(struct cec_adapter
*adap
, u8 logical_addr
)
82 struct dw_hdmi_cec
*cec
= cec_get_drvdata(adap
);
84 if (logical_addr
== CEC_LOG_ADDR_INVALID
)
87 cec
->addresses
|= BIT(logical_addr
) | BIT(15);
89 dw_hdmi_write(cec
, cec
->addresses
& 255, HDMI_CEC_ADDR_L
);
90 dw_hdmi_write(cec
, cec
->addresses
>> 8, HDMI_CEC_ADDR_H
);
95 static int dw_hdmi_cec_transmit(struct cec_adapter
*adap
, u8 attempts
,
96 u32 signal_free_time
, struct cec_msg
*msg
)
98 struct dw_hdmi_cec
*cec
= cec_get_drvdata(adap
);
101 switch (signal_free_time
) {
102 case CEC_SIGNAL_FREE_TIME_RETRY
:
103 ctrl
= CEC_CTRL_RETRY
;
105 case CEC_SIGNAL_FREE_TIME_NEW_INITIATOR
:
107 ctrl
= CEC_CTRL_NORMAL
;
109 case CEC_SIGNAL_FREE_TIME_NEXT_XFER
:
110 ctrl
= CEC_CTRL_IMMED
;
114 for (i
= 0; i
< msg
->len
; i
++)
115 dw_hdmi_write(cec
, msg
->msg
[i
], HDMI_CEC_TX_DATA0
+ i
);
117 dw_hdmi_write(cec
, msg
->len
, HDMI_CEC_TX_CNT
);
118 dw_hdmi_write(cec
, ctrl
| CEC_CTRL_START
, HDMI_CEC_CTRL
);
123 static irqreturn_t
dw_hdmi_cec_hardirq(int irq
, void *data
)
125 struct cec_adapter
*adap
= data
;
126 struct dw_hdmi_cec
*cec
= cec_get_drvdata(adap
);
127 unsigned int stat
= dw_hdmi_read(cec
, HDMI_IH_CEC_STAT0
);
128 irqreturn_t ret
= IRQ_HANDLED
;
133 dw_hdmi_write(cec
, stat
, HDMI_IH_CEC_STAT0
);
135 if (stat
& CEC_STAT_ERROR_INIT
) {
136 cec
->tx_status
= CEC_TX_STATUS_ERROR
;
138 ret
= IRQ_WAKE_THREAD
;
139 } else if (stat
& CEC_STAT_DONE
) {
140 cec
->tx_status
= CEC_TX_STATUS_OK
;
142 ret
= IRQ_WAKE_THREAD
;
143 } else if (stat
& CEC_STAT_NACK
) {
144 cec
->tx_status
= CEC_TX_STATUS_NACK
;
146 ret
= IRQ_WAKE_THREAD
;
149 if (stat
& CEC_STAT_EOM
) {
152 len
= dw_hdmi_read(cec
, HDMI_CEC_RX_CNT
);
153 if (len
> sizeof(cec
->rx_msg
.msg
))
154 len
= sizeof(cec
->rx_msg
.msg
);
156 for (i
= 0; i
< len
; i
++)
158 dw_hdmi_read(cec
, HDMI_CEC_RX_DATA0
+ i
);
160 dw_hdmi_write(cec
, 0, HDMI_CEC_LOCK
);
162 cec
->rx_msg
.len
= len
;
166 ret
= IRQ_WAKE_THREAD
;
172 static irqreturn_t
dw_hdmi_cec_thread(int irq
, void *data
)
174 struct cec_adapter
*adap
= data
;
175 struct dw_hdmi_cec
*cec
= cec_get_drvdata(adap
);
178 cec
->tx_done
= false;
179 cec_transmit_attempt_done(adap
, cec
->tx_status
);
182 cec
->rx_done
= false;
184 cec_received_msg(adap
, &cec
->rx_msg
);
189 static int dw_hdmi_cec_enable(struct cec_adapter
*adap
, bool enable
)
191 struct dw_hdmi_cec
*cec
= cec_get_drvdata(adap
);
194 dw_hdmi_write(cec
, ~0, HDMI_CEC_MASK
);
195 dw_hdmi_write(cec
, ~0, HDMI_IH_MUTE_CEC_STAT0
);
196 dw_hdmi_write(cec
, 0, HDMI_CEC_POLARITY
);
198 cec
->ops
->disable(cec
->hdmi
);
202 dw_hdmi_write(cec
, 0, HDMI_CEC_CTRL
);
203 dw_hdmi_write(cec
, ~0, HDMI_IH_CEC_STAT0
);
204 dw_hdmi_write(cec
, 0, HDMI_CEC_LOCK
);
206 dw_hdmi_cec_log_addr(cec
->adap
, CEC_LOG_ADDR_INVALID
);
208 cec
->ops
->enable(cec
->hdmi
);
210 irqs
= CEC_STAT_ERROR_INIT
| CEC_STAT_NACK
| CEC_STAT_EOM
|
212 dw_hdmi_write(cec
, irqs
, HDMI_CEC_POLARITY
);
213 dw_hdmi_write(cec
, ~irqs
, HDMI_CEC_MASK
);
214 dw_hdmi_write(cec
, ~irqs
, HDMI_IH_MUTE_CEC_STAT0
);
219 static const struct cec_adap_ops dw_hdmi_cec_ops
= {
220 .adap_enable
= dw_hdmi_cec_enable
,
221 .adap_log_addr
= dw_hdmi_cec_log_addr
,
222 .adap_transmit
= dw_hdmi_cec_transmit
,
225 static void dw_hdmi_cec_del(void *data
)
227 struct dw_hdmi_cec
*cec
= data
;
229 cec_delete_adapter(cec
->adap
);
232 static int dw_hdmi_cec_probe(struct platform_device
*pdev
)
234 struct dw_hdmi_cec_data
*data
= dev_get_platdata(&pdev
->dev
);
235 struct dw_hdmi_cec
*cec
;
242 * Our device is just a convenience - we want to link to the real
243 * hardware device here, so that userspace can see the association
244 * between the HDMI hardware and its associated CEC chardev.
246 cec
= devm_kzalloc(&pdev
->dev
, sizeof(*cec
), GFP_KERNEL
);
250 cec
->irq
= data
->irq
;
251 cec
->ops
= data
->ops
;
252 cec
->hdmi
= data
->hdmi
;
254 platform_set_drvdata(pdev
, cec
);
256 dw_hdmi_write(cec
, 0, HDMI_CEC_TX_CNT
);
257 dw_hdmi_write(cec
, ~0, HDMI_CEC_MASK
);
258 dw_hdmi_write(cec
, ~0, HDMI_IH_MUTE_CEC_STAT0
);
259 dw_hdmi_write(cec
, 0, HDMI_CEC_POLARITY
);
261 cec
->adap
= cec_allocate_adapter(&dw_hdmi_cec_ops
, cec
, "dw_hdmi",
262 CEC_CAP_LOG_ADDRS
| CEC_CAP_TRANSMIT
|
263 CEC_CAP_RC
| CEC_CAP_PASSTHROUGH
,
265 if (IS_ERR(cec
->adap
))
266 return PTR_ERR(cec
->adap
);
268 /* override the module pointer */
269 cec
->adap
->owner
= THIS_MODULE
;
271 ret
= devm_add_action(&pdev
->dev
, dw_hdmi_cec_del
, cec
);
273 cec_delete_adapter(cec
->adap
);
277 ret
= devm_request_threaded_irq(&pdev
->dev
, cec
->irq
,
279 dw_hdmi_cec_thread
, IRQF_SHARED
,
280 "dw-hdmi-cec", cec
->adap
);
284 cec
->notify
= cec_notifier_get(pdev
->dev
.parent
);
288 ret
= cec_register_adapter(cec
->adap
, pdev
->dev
.parent
);
290 cec_notifier_put(cec
->notify
);
295 * CEC documentation says we must not call cec_delete_adapter
296 * after a successful call to cec_register_adapter().
298 devm_remove_action(&pdev
->dev
, dw_hdmi_cec_del
, cec
);
300 cec_register_cec_notifier(cec
->adap
, cec
->notify
);
305 static int dw_hdmi_cec_remove(struct platform_device
*pdev
)
307 struct dw_hdmi_cec
*cec
= platform_get_drvdata(pdev
);
309 cec_unregister_adapter(cec
->adap
);
310 cec_notifier_put(cec
->notify
);
315 static struct platform_driver dw_hdmi_cec_driver
= {
316 .probe
= dw_hdmi_cec_probe
,
317 .remove
= dw_hdmi_cec_remove
,
319 .name
= "dw-hdmi-cec",
322 module_platform_driver(dw_hdmi_cec_driver
);
324 MODULE_AUTHOR("Russell King <rmk+kernel@armlinux.org.uk>");
325 MODULE_DESCRIPTION("Synopsys Designware HDMI CEC driver for i.MX");
326 MODULE_LICENSE("GPL");
327 MODULE_ALIAS(PLATFORM_MODULE_PREFIX
"dw-hdmi-cec");