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
;
71 static void dw_hdmi_write(struct dw_hdmi_cec
*cec
, u8 val
, int offset
)
73 cec
->ops
->write(cec
->hdmi
, val
, offset
);
76 static u8
dw_hdmi_read(struct dw_hdmi_cec
*cec
, int offset
)
78 return cec
->ops
->read(cec
->hdmi
, offset
);
81 static int dw_hdmi_cec_log_addr(struct cec_adapter
*adap
, u8 logical_addr
)
83 struct dw_hdmi_cec
*cec
= cec_get_drvdata(adap
);
85 if (logical_addr
== CEC_LOG_ADDR_INVALID
)
88 cec
->addresses
|= BIT(logical_addr
) | BIT(15);
90 dw_hdmi_write(cec
, cec
->addresses
& 255, HDMI_CEC_ADDR_L
);
91 dw_hdmi_write(cec
, cec
->addresses
>> 8, HDMI_CEC_ADDR_H
);
96 static int dw_hdmi_cec_transmit(struct cec_adapter
*adap
, u8 attempts
,
97 u32 signal_free_time
, struct cec_msg
*msg
)
99 struct dw_hdmi_cec
*cec
= cec_get_drvdata(adap
);
100 unsigned int i
, ctrl
;
102 switch (signal_free_time
) {
103 case CEC_SIGNAL_FREE_TIME_RETRY
:
104 ctrl
= CEC_CTRL_RETRY
;
106 case CEC_SIGNAL_FREE_TIME_NEW_INITIATOR
:
108 ctrl
= CEC_CTRL_NORMAL
;
110 case CEC_SIGNAL_FREE_TIME_NEXT_XFER
:
111 ctrl
= CEC_CTRL_IMMED
;
115 for (i
= 0; i
< msg
->len
; i
++)
116 dw_hdmi_write(cec
, msg
->msg
[i
], HDMI_CEC_TX_DATA0
+ i
);
118 dw_hdmi_write(cec
, msg
->len
, HDMI_CEC_TX_CNT
);
119 dw_hdmi_write(cec
, ctrl
| CEC_CTRL_START
, HDMI_CEC_CTRL
);
124 static irqreturn_t
dw_hdmi_cec_hardirq(int irq
, void *data
)
126 struct cec_adapter
*adap
= data
;
127 struct dw_hdmi_cec
*cec
= cec_get_drvdata(adap
);
128 unsigned int stat
= dw_hdmi_read(cec
, HDMI_IH_CEC_STAT0
);
129 irqreturn_t ret
= IRQ_HANDLED
;
134 dw_hdmi_write(cec
, stat
, HDMI_IH_CEC_STAT0
);
136 if (stat
& CEC_STAT_ERROR_INIT
) {
137 cec
->tx_status
= CEC_TX_STATUS_ERROR
;
139 ret
= IRQ_WAKE_THREAD
;
140 } else if (stat
& CEC_STAT_DONE
) {
141 cec
->tx_status
= CEC_TX_STATUS_OK
;
143 ret
= IRQ_WAKE_THREAD
;
144 } else if (stat
& CEC_STAT_NACK
) {
145 cec
->tx_status
= CEC_TX_STATUS_NACK
;
147 ret
= IRQ_WAKE_THREAD
;
148 } else if (stat
& CEC_STAT_ARBLOST
) {
149 cec
->tx_status
= CEC_TX_STATUS_ARB_LOST
;
151 ret
= IRQ_WAKE_THREAD
;
154 if (stat
& CEC_STAT_EOM
) {
157 len
= dw_hdmi_read(cec
, HDMI_CEC_RX_CNT
);
158 if (len
> sizeof(cec
->rx_msg
.msg
))
159 len
= sizeof(cec
->rx_msg
.msg
);
161 for (i
= 0; i
< len
; i
++)
163 dw_hdmi_read(cec
, HDMI_CEC_RX_DATA0
+ i
);
165 dw_hdmi_write(cec
, 0, HDMI_CEC_LOCK
);
167 cec
->rx_msg
.len
= len
;
171 ret
= IRQ_WAKE_THREAD
;
177 static irqreturn_t
dw_hdmi_cec_thread(int irq
, void *data
)
179 struct cec_adapter
*adap
= data
;
180 struct dw_hdmi_cec
*cec
= cec_get_drvdata(adap
);
183 cec
->tx_done
= false;
184 cec_transmit_attempt_done(adap
, cec
->tx_status
);
187 cec
->rx_done
= false;
189 cec_received_msg(adap
, &cec
->rx_msg
);
194 static int dw_hdmi_cec_enable(struct cec_adapter
*adap
, bool enable
)
196 struct dw_hdmi_cec
*cec
= cec_get_drvdata(adap
);
199 dw_hdmi_write(cec
, ~0, HDMI_CEC_MASK
);
200 dw_hdmi_write(cec
, ~0, HDMI_IH_MUTE_CEC_STAT0
);
201 dw_hdmi_write(cec
, 0, HDMI_CEC_POLARITY
);
203 cec
->ops
->disable(cec
->hdmi
);
207 dw_hdmi_write(cec
, 0, HDMI_CEC_CTRL
);
208 dw_hdmi_write(cec
, ~0, HDMI_IH_CEC_STAT0
);
209 dw_hdmi_write(cec
, 0, HDMI_CEC_LOCK
);
211 dw_hdmi_cec_log_addr(cec
->adap
, CEC_LOG_ADDR_INVALID
);
213 cec
->ops
->enable(cec
->hdmi
);
215 irqs
= CEC_STAT_ERROR_INIT
| CEC_STAT_NACK
| CEC_STAT_EOM
|
216 CEC_STAT_ARBLOST
| CEC_STAT_DONE
;
217 dw_hdmi_write(cec
, irqs
, HDMI_CEC_POLARITY
);
218 dw_hdmi_write(cec
, ~irqs
, HDMI_CEC_MASK
);
219 dw_hdmi_write(cec
, ~irqs
, HDMI_IH_MUTE_CEC_STAT0
);
224 static const struct cec_adap_ops dw_hdmi_cec_ops
= {
225 .adap_enable
= dw_hdmi_cec_enable
,
226 .adap_log_addr
= dw_hdmi_cec_log_addr
,
227 .adap_transmit
= dw_hdmi_cec_transmit
,
230 static void dw_hdmi_cec_del(void *data
)
232 struct dw_hdmi_cec
*cec
= data
;
234 cec_delete_adapter(cec
->adap
);
237 static int dw_hdmi_cec_probe(struct platform_device
*pdev
)
239 struct dw_hdmi_cec_data
*data
= dev_get_platdata(&pdev
->dev
);
240 struct dw_hdmi_cec
*cec
;
247 * Our device is just a convenience - we want to link to the real
248 * hardware device here, so that userspace can see the association
249 * between the HDMI hardware and its associated CEC chardev.
251 cec
= devm_kzalloc(&pdev
->dev
, sizeof(*cec
), GFP_KERNEL
);
255 cec
->irq
= data
->irq
;
256 cec
->ops
= data
->ops
;
257 cec
->hdmi
= data
->hdmi
;
259 platform_set_drvdata(pdev
, cec
);
261 dw_hdmi_write(cec
, 0, HDMI_CEC_TX_CNT
);
262 dw_hdmi_write(cec
, ~0, HDMI_CEC_MASK
);
263 dw_hdmi_write(cec
, ~0, HDMI_IH_MUTE_CEC_STAT0
);
264 dw_hdmi_write(cec
, 0, HDMI_CEC_POLARITY
);
266 cec
->adap
= cec_allocate_adapter(&dw_hdmi_cec_ops
, cec
, "dw_hdmi",
268 CEC_CAP_CONNECTOR_INFO
,
270 if (IS_ERR(cec
->adap
))
271 return PTR_ERR(cec
->adap
);
273 /* override the module pointer */
274 cec
->adap
->owner
= THIS_MODULE
;
276 ret
= devm_add_action_or_reset(&pdev
->dev
, dw_hdmi_cec_del
, cec
);
280 ret
= devm_request_threaded_irq(&pdev
->dev
, cec
->irq
,
282 dw_hdmi_cec_thread
, IRQF_SHARED
,
283 "dw-hdmi-cec", cec
->adap
);
287 cec
->notify
= cec_notifier_cec_adap_register(pdev
->dev
.parent
,
292 ret
= cec_register_adapter(cec
->adap
, pdev
->dev
.parent
);
294 cec_notifier_cec_adap_unregister(cec
->notify
, cec
->adap
);
299 * CEC documentation says we must not call cec_delete_adapter
300 * after a successful call to cec_register_adapter().
302 devm_remove_action(&pdev
->dev
, dw_hdmi_cec_del
, cec
);
307 static void dw_hdmi_cec_remove(struct platform_device
*pdev
)
309 struct dw_hdmi_cec
*cec
= platform_get_drvdata(pdev
);
311 cec_notifier_cec_adap_unregister(cec
->notify
, cec
->adap
);
312 cec_unregister_adapter(cec
->adap
);
315 static int dw_hdmi_cec_resume(struct device
*dev
)
317 struct dw_hdmi_cec
*cec
= dev_get_drvdata(dev
);
319 /* Restore logical address */
320 dw_hdmi_write(cec
, cec
->addresses
& 255, HDMI_CEC_ADDR_L
);
321 dw_hdmi_write(cec
, cec
->addresses
>> 8, HDMI_CEC_ADDR_H
);
323 /* Restore interrupt status/mask registers */
324 dw_hdmi_write(cec
, cec
->regs_polarity
, HDMI_CEC_POLARITY
);
325 dw_hdmi_write(cec
, cec
->regs_mask
, HDMI_CEC_MASK
);
326 dw_hdmi_write(cec
, cec
->regs_mute_stat0
, HDMI_IH_MUTE_CEC_STAT0
);
331 static int dw_hdmi_cec_suspend(struct device
*dev
)
333 struct dw_hdmi_cec
*cec
= dev_get_drvdata(dev
);
335 /* store interrupt status/mask registers */
336 cec
->regs_polarity
= dw_hdmi_read(cec
, HDMI_CEC_POLARITY
);
337 cec
->regs_mask
= dw_hdmi_read(cec
, HDMI_CEC_MASK
);
338 cec
->regs_mute_stat0
= dw_hdmi_read(cec
, HDMI_IH_MUTE_CEC_STAT0
);
343 static const struct dev_pm_ops dw_hdmi_cec_pm
= {
344 SYSTEM_SLEEP_PM_OPS(dw_hdmi_cec_suspend
, dw_hdmi_cec_resume
)
347 static struct platform_driver dw_hdmi_cec_driver
= {
348 .probe
= dw_hdmi_cec_probe
,
349 .remove
= dw_hdmi_cec_remove
,
351 .name
= "dw-hdmi-cec",
352 .pm
= pm_ptr(&dw_hdmi_cec_pm
),
355 module_platform_driver(dw_hdmi_cec_driver
);
357 MODULE_AUTHOR("Russell King <rmk+kernel@armlinux.org.uk>");
358 MODULE_DESCRIPTION("Synopsys Designware HDMI CEC driver for i.MX");
359 MODULE_LICENSE("GPL");
360 MODULE_ALIAS(PLATFORM_MODULE_PREFIX
"dw-hdmi-cec");