1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0+
2*4882a593Smuzhiyun /*
3*4882a593Smuzhiyun * Copyright (C) Rockchip Electronics Co.Ltd
4*4882a593Smuzhiyun * Author:
5*4882a593Smuzhiyun * Algea Cao <algea.cao@rock-chips.com>
6*4882a593Smuzhiyun */
7*4882a593Smuzhiyun #include <linux/interrupt.h>
8*4882a593Smuzhiyun #include <linux/io.h>
9*4882a593Smuzhiyun #include <linux/module.h>
10*4882a593Smuzhiyun #include <linux/platform_device.h>
11*4882a593Smuzhiyun #include <linux/sched.h>
12*4882a593Smuzhiyun #include <linux/slab.h>
13*4882a593Smuzhiyun
14*4882a593Smuzhiyun #include <drm/drm_edid.h>
15*4882a593Smuzhiyun #include <drm/bridge/dw_hdmi.h>
16*4882a593Smuzhiyun
17*4882a593Smuzhiyun #include <media/cec.h>
18*4882a593Smuzhiyun #include <media/cec-notifier.h>
19*4882a593Smuzhiyun
20*4882a593Smuzhiyun #include "dw-hdmi-qp-cec.h"
21*4882a593Smuzhiyun
22*4882a593Smuzhiyun enum {
23*4882a593Smuzhiyun CEC_TX_CONTROL = 0x1000,
24*4882a593Smuzhiyun CEC_CTRL_CLEAR = BIT(0),
25*4882a593Smuzhiyun CEC_CTRL_START = BIT(0),
26*4882a593Smuzhiyun
27*4882a593Smuzhiyun CEC_STAT_DONE = BIT(0),
28*4882a593Smuzhiyun CEC_STAT_NACK = BIT(1),
29*4882a593Smuzhiyun CEC_STAT_ARBLOST = BIT(2),
30*4882a593Smuzhiyun CEC_STAT_LINE_ERR = BIT(3),
31*4882a593Smuzhiyun CEC_STAT_RETRANS_FAIL = BIT(4),
32*4882a593Smuzhiyun CEC_STAT_DISCARD = BIT(5),
33*4882a593Smuzhiyun CEC_STAT_TX_BUSY = BIT(8),
34*4882a593Smuzhiyun CEC_STAT_RX_BUSY = BIT(9),
35*4882a593Smuzhiyun CEC_STAT_DRIVE_ERR = BIT(10),
36*4882a593Smuzhiyun CEC_STAT_EOM = BIT(11),
37*4882a593Smuzhiyun CEC_STAT_NOTIFY_ERR = BIT(12),
38*4882a593Smuzhiyun
39*4882a593Smuzhiyun CEC_CONFIG = 0x1008,
40*4882a593Smuzhiyun CEC_ADDR = 0x100c,
41*4882a593Smuzhiyun CEC_TX_CNT = 0x1020,
42*4882a593Smuzhiyun CEC_RX_CNT = 0x1040,
43*4882a593Smuzhiyun CEC_TX_DATA3_0 = 0x1024,
44*4882a593Smuzhiyun CEC_RX_DATA3_0 = 0x1044,
45*4882a593Smuzhiyun CEC_LOCK_CONTROL = 0x1054,
46*4882a593Smuzhiyun
47*4882a593Smuzhiyun CEC_INT_STATUS = 0x4000,
48*4882a593Smuzhiyun CEC_INT_MASK_N = 0x4004,
49*4882a593Smuzhiyun CEC_INT_CLEAR = 0x4008,
50*4882a593Smuzhiyun };
51*4882a593Smuzhiyun
52*4882a593Smuzhiyun struct dw_hdmi_qp_cec {
53*4882a593Smuzhiyun struct dw_hdmi_qp *hdmi;
54*4882a593Smuzhiyun const struct dw_hdmi_qp_cec_ops *ops;
55*4882a593Smuzhiyun u32 addresses;
56*4882a593Smuzhiyun struct cec_adapter *adap;
57*4882a593Smuzhiyun struct cec_msg rx_msg;
58*4882a593Smuzhiyun unsigned int tx_status;
59*4882a593Smuzhiyun bool tx_done;
60*4882a593Smuzhiyun bool rx_done;
61*4882a593Smuzhiyun struct cec_notifier *notify;
62*4882a593Smuzhiyun int irq;
63*4882a593Smuzhiyun };
64*4882a593Smuzhiyun
dw_hdmi_qp_write(struct dw_hdmi_qp_cec * cec,u32 val,int offset)65*4882a593Smuzhiyun static void dw_hdmi_qp_write(struct dw_hdmi_qp_cec *cec, u32 val, int offset)
66*4882a593Smuzhiyun {
67*4882a593Smuzhiyun cec->ops->write(cec->hdmi, val, offset);
68*4882a593Smuzhiyun }
69*4882a593Smuzhiyun
dw_hdmi_qp_read(struct dw_hdmi_qp_cec * cec,int offset)70*4882a593Smuzhiyun static u32 dw_hdmi_qp_read(struct dw_hdmi_qp_cec *cec, int offset)
71*4882a593Smuzhiyun {
72*4882a593Smuzhiyun return cec->ops->read(cec->hdmi, offset);
73*4882a593Smuzhiyun }
74*4882a593Smuzhiyun
dw_hdmi_qp_cec_log_addr(struct cec_adapter * adap,u8 logical_addr)75*4882a593Smuzhiyun static int dw_hdmi_qp_cec_log_addr(struct cec_adapter *adap, u8 logical_addr)
76*4882a593Smuzhiyun {
77*4882a593Smuzhiyun struct dw_hdmi_qp_cec *cec = cec_get_drvdata(adap);
78*4882a593Smuzhiyun
79*4882a593Smuzhiyun if (logical_addr == CEC_LOG_ADDR_INVALID)
80*4882a593Smuzhiyun cec->addresses = 0;
81*4882a593Smuzhiyun else
82*4882a593Smuzhiyun cec->addresses |= BIT(logical_addr) | BIT(15);
83*4882a593Smuzhiyun
84*4882a593Smuzhiyun dw_hdmi_qp_write(cec, cec->addresses, CEC_ADDR);
85*4882a593Smuzhiyun
86*4882a593Smuzhiyun return 0;
87*4882a593Smuzhiyun }
88*4882a593Smuzhiyun
dw_hdmi_qp_cec_transmit(struct cec_adapter * adap,u8 attempts,u32 signal_free_time,struct cec_msg * msg)89*4882a593Smuzhiyun static int dw_hdmi_qp_cec_transmit(struct cec_adapter *adap, u8 attempts,
90*4882a593Smuzhiyun u32 signal_free_time, struct cec_msg *msg)
91*4882a593Smuzhiyun {
92*4882a593Smuzhiyun struct dw_hdmi_qp_cec *cec = cec_get_drvdata(adap);
93*4882a593Smuzhiyun unsigned int i;
94*4882a593Smuzhiyun u32 val;
95*4882a593Smuzhiyun
96*4882a593Smuzhiyun for (i = 0; i < msg->len; i++) {
97*4882a593Smuzhiyun if (!(i % 4))
98*4882a593Smuzhiyun val = msg->msg[i];
99*4882a593Smuzhiyun if ((i % 4) == 1)
100*4882a593Smuzhiyun val |= msg->msg[i] << 8;
101*4882a593Smuzhiyun if ((i % 4) == 2)
102*4882a593Smuzhiyun val |= msg->msg[i] << 16;
103*4882a593Smuzhiyun if ((i % 4) == 3)
104*4882a593Smuzhiyun val |= msg->msg[i] << 24;
105*4882a593Smuzhiyun
106*4882a593Smuzhiyun if (i == (msg->len - 1) || (i % 4) == 3)
107*4882a593Smuzhiyun dw_hdmi_qp_write(cec, val, CEC_TX_DATA3_0 + (i / 4) * 4);
108*4882a593Smuzhiyun }
109*4882a593Smuzhiyun
110*4882a593Smuzhiyun dw_hdmi_qp_write(cec, msg->len - 1, CEC_TX_CNT);
111*4882a593Smuzhiyun dw_hdmi_qp_write(cec, CEC_CTRL_START, CEC_TX_CONTROL);
112*4882a593Smuzhiyun
113*4882a593Smuzhiyun return 0;
114*4882a593Smuzhiyun }
115*4882a593Smuzhiyun
dw_hdmi_qp_cec_hardirq(int irq,void * data)116*4882a593Smuzhiyun static irqreturn_t dw_hdmi_qp_cec_hardirq(int irq, void *data)
117*4882a593Smuzhiyun {
118*4882a593Smuzhiyun struct cec_adapter *adap = data;
119*4882a593Smuzhiyun struct dw_hdmi_qp_cec *cec = cec_get_drvdata(adap);
120*4882a593Smuzhiyun u32 stat = dw_hdmi_qp_read(cec, CEC_INT_STATUS);
121*4882a593Smuzhiyun irqreturn_t ret = IRQ_HANDLED;
122*4882a593Smuzhiyun
123*4882a593Smuzhiyun if (stat == 0)
124*4882a593Smuzhiyun return IRQ_NONE;
125*4882a593Smuzhiyun
126*4882a593Smuzhiyun dw_hdmi_qp_write(cec, stat, CEC_INT_CLEAR);
127*4882a593Smuzhiyun
128*4882a593Smuzhiyun if (stat & CEC_STAT_LINE_ERR) {
129*4882a593Smuzhiyun cec->tx_status = CEC_TX_STATUS_ERROR;
130*4882a593Smuzhiyun cec->tx_done = true;
131*4882a593Smuzhiyun ret = IRQ_WAKE_THREAD;
132*4882a593Smuzhiyun } else if (stat & CEC_STAT_DONE) {
133*4882a593Smuzhiyun cec->tx_status = CEC_TX_STATUS_OK;
134*4882a593Smuzhiyun cec->tx_done = true;
135*4882a593Smuzhiyun ret = IRQ_WAKE_THREAD;
136*4882a593Smuzhiyun } else if (stat & CEC_STAT_NACK) {
137*4882a593Smuzhiyun cec->tx_status = CEC_TX_STATUS_NACK;
138*4882a593Smuzhiyun cec->tx_done = true;
139*4882a593Smuzhiyun ret = IRQ_WAKE_THREAD;
140*4882a593Smuzhiyun }
141*4882a593Smuzhiyun
142*4882a593Smuzhiyun if (stat & CEC_STAT_EOM) {
143*4882a593Smuzhiyun unsigned int len, i, val;
144*4882a593Smuzhiyun
145*4882a593Smuzhiyun val = dw_hdmi_qp_read(cec, CEC_RX_CNT);
146*4882a593Smuzhiyun len = (val & 0xf) + 1;
147*4882a593Smuzhiyun
148*4882a593Smuzhiyun if (len > sizeof(cec->rx_msg.msg))
149*4882a593Smuzhiyun len = sizeof(cec->rx_msg.msg);
150*4882a593Smuzhiyun
151*4882a593Smuzhiyun for (i = 0; i < 4; i++) {
152*4882a593Smuzhiyun val = dw_hdmi_qp_read(cec, CEC_RX_DATA3_0 + i * 4);
153*4882a593Smuzhiyun cec->rx_msg.msg[i * 4] = val & 0xff;
154*4882a593Smuzhiyun cec->rx_msg.msg[i * 4 + 1] = (val >> 8) & 0xff;
155*4882a593Smuzhiyun cec->rx_msg.msg[i * 4 + 2] = (val >> 16) & 0xff;
156*4882a593Smuzhiyun cec->rx_msg.msg[i * 4 + 3] = (val >> 24) & 0xff;
157*4882a593Smuzhiyun }
158*4882a593Smuzhiyun
159*4882a593Smuzhiyun dw_hdmi_qp_write(cec, 1, CEC_LOCK_CONTROL);
160*4882a593Smuzhiyun
161*4882a593Smuzhiyun cec->rx_msg.len = len;
162*4882a593Smuzhiyun cec->rx_done = true;
163*4882a593Smuzhiyun
164*4882a593Smuzhiyun ret = IRQ_WAKE_THREAD;
165*4882a593Smuzhiyun }
166*4882a593Smuzhiyun
167*4882a593Smuzhiyun return ret;
168*4882a593Smuzhiyun }
169*4882a593Smuzhiyun
dw_hdmi_qp_cec_thread(int irq,void * data)170*4882a593Smuzhiyun static irqreturn_t dw_hdmi_qp_cec_thread(int irq, void *data)
171*4882a593Smuzhiyun {
172*4882a593Smuzhiyun struct cec_adapter *adap = data;
173*4882a593Smuzhiyun struct dw_hdmi_qp_cec *cec = cec_get_drvdata(adap);
174*4882a593Smuzhiyun
175*4882a593Smuzhiyun if (cec->tx_done) {
176*4882a593Smuzhiyun cec->tx_done = false;
177*4882a593Smuzhiyun cec_transmit_attempt_done(adap, cec->tx_status);
178*4882a593Smuzhiyun }
179*4882a593Smuzhiyun if (cec->rx_done) {
180*4882a593Smuzhiyun cec->rx_done = false;
181*4882a593Smuzhiyun cec_received_msg(adap, &cec->rx_msg);
182*4882a593Smuzhiyun }
183*4882a593Smuzhiyun return IRQ_HANDLED;
184*4882a593Smuzhiyun }
185*4882a593Smuzhiyun
dw_hdmi_qp_cec_enable(struct cec_adapter * adap,bool enable)186*4882a593Smuzhiyun static int dw_hdmi_qp_cec_enable(struct cec_adapter *adap, bool enable)
187*4882a593Smuzhiyun {
188*4882a593Smuzhiyun struct dw_hdmi_qp_cec *cec = cec_get_drvdata(adap);
189*4882a593Smuzhiyun
190*4882a593Smuzhiyun if (!enable) {
191*4882a593Smuzhiyun dw_hdmi_qp_write(cec, 0, CEC_INT_MASK_N);
192*4882a593Smuzhiyun dw_hdmi_qp_write(cec, ~0, CEC_INT_CLEAR);
193*4882a593Smuzhiyun cec->ops->disable(cec->hdmi);
194*4882a593Smuzhiyun } else {
195*4882a593Smuzhiyun unsigned int irqs;
196*4882a593Smuzhiyun
197*4882a593Smuzhiyun cec->ops->enable(cec->hdmi);
198*4882a593Smuzhiyun
199*4882a593Smuzhiyun dw_hdmi_qp_write(cec, ~0, CEC_INT_CLEAR);
200*4882a593Smuzhiyun dw_hdmi_qp_write(cec, 1, CEC_LOCK_CONTROL);
201*4882a593Smuzhiyun
202*4882a593Smuzhiyun dw_hdmi_qp_cec_log_addr(cec->adap, CEC_LOG_ADDR_INVALID);
203*4882a593Smuzhiyun
204*4882a593Smuzhiyun irqs = CEC_STAT_LINE_ERR | CEC_STAT_NACK | CEC_STAT_EOM |
205*4882a593Smuzhiyun CEC_STAT_DONE;
206*4882a593Smuzhiyun dw_hdmi_qp_write(cec, ~0, CEC_INT_CLEAR);
207*4882a593Smuzhiyun dw_hdmi_qp_write(cec, irqs, CEC_INT_MASK_N);
208*4882a593Smuzhiyun }
209*4882a593Smuzhiyun return 0;
210*4882a593Smuzhiyun }
211*4882a593Smuzhiyun
212*4882a593Smuzhiyun static const struct cec_adap_ops dw_hdmi_qp_cec_ops = {
213*4882a593Smuzhiyun .adap_enable = dw_hdmi_qp_cec_enable,
214*4882a593Smuzhiyun .adap_log_addr = dw_hdmi_qp_cec_log_addr,
215*4882a593Smuzhiyun .adap_transmit = dw_hdmi_qp_cec_transmit,
216*4882a593Smuzhiyun };
217*4882a593Smuzhiyun
dw_hdmi_qp_cec_del(void * data)218*4882a593Smuzhiyun static void dw_hdmi_qp_cec_del(void *data)
219*4882a593Smuzhiyun {
220*4882a593Smuzhiyun struct dw_hdmi_qp_cec *cec = data;
221*4882a593Smuzhiyun
222*4882a593Smuzhiyun cec_delete_adapter(cec->adap);
223*4882a593Smuzhiyun }
224*4882a593Smuzhiyun
dw_hdmi_qp_cec_probe(struct platform_device * pdev)225*4882a593Smuzhiyun static int dw_hdmi_qp_cec_probe(struct platform_device *pdev)
226*4882a593Smuzhiyun {
227*4882a593Smuzhiyun struct dw_hdmi_qp_cec_data *data = dev_get_platdata(&pdev->dev);
228*4882a593Smuzhiyun struct dw_hdmi_qp_cec *cec;
229*4882a593Smuzhiyun int ret;
230*4882a593Smuzhiyun
231*4882a593Smuzhiyun if (!data) {
232*4882a593Smuzhiyun dev_err(&pdev->dev, "can't get data\n");
233*4882a593Smuzhiyun return -ENXIO;
234*4882a593Smuzhiyun }
235*4882a593Smuzhiyun
236*4882a593Smuzhiyun /*
237*4882a593Smuzhiyun * Our device is just a convenience - we want to link to the real
238*4882a593Smuzhiyun * hardware device here, so that userspace can see the association
239*4882a593Smuzhiyun * between the HDMI hardware and its associated CEC chardev.
240*4882a593Smuzhiyun */
241*4882a593Smuzhiyun cec = devm_kzalloc(&pdev->dev, sizeof(*cec), GFP_KERNEL);
242*4882a593Smuzhiyun if (!cec)
243*4882a593Smuzhiyun return -ENOMEM;
244*4882a593Smuzhiyun
245*4882a593Smuzhiyun cec->ops = data->ops;
246*4882a593Smuzhiyun cec->hdmi = data->hdmi;
247*4882a593Smuzhiyun cec->irq = data->irq;
248*4882a593Smuzhiyun
249*4882a593Smuzhiyun platform_set_drvdata(pdev, cec);
250*4882a593Smuzhiyun
251*4882a593Smuzhiyun dw_hdmi_qp_write(cec, 0, CEC_TX_CNT);
252*4882a593Smuzhiyun dw_hdmi_qp_write(cec, ~0, CEC_INT_CLEAR);
253*4882a593Smuzhiyun dw_hdmi_qp_write(cec, 0, CEC_INT_MASK_N);
254*4882a593Smuzhiyun
255*4882a593Smuzhiyun cec->adap = cec_allocate_adapter(&dw_hdmi_qp_cec_ops, cec, "dw_hdmi_qp",
256*4882a593Smuzhiyun CEC_CAP_LOG_ADDRS | CEC_CAP_TRANSMIT |
257*4882a593Smuzhiyun CEC_CAP_RC | CEC_CAP_PASSTHROUGH,
258*4882a593Smuzhiyun CEC_MAX_LOG_ADDRS);
259*4882a593Smuzhiyun if (IS_ERR(cec->adap)) {
260*4882a593Smuzhiyun dev_err(&pdev->dev, "cec allocate adapter failed\n");
261*4882a593Smuzhiyun return PTR_ERR(cec->adap);
262*4882a593Smuzhiyun }
263*4882a593Smuzhiyun
264*4882a593Smuzhiyun dw_hdmi_qp_set_cec_adap(cec->hdmi, cec->adap);
265*4882a593Smuzhiyun
266*4882a593Smuzhiyun /* override the module pointer */
267*4882a593Smuzhiyun cec->adap->owner = THIS_MODULE;
268*4882a593Smuzhiyun
269*4882a593Smuzhiyun ret = devm_add_action(&pdev->dev, dw_hdmi_qp_cec_del, cec);
270*4882a593Smuzhiyun if (ret) {
271*4882a593Smuzhiyun dev_err(&pdev->dev, "cec add action failed\n");
272*4882a593Smuzhiyun cec_delete_adapter(cec->adap);
273*4882a593Smuzhiyun return ret;
274*4882a593Smuzhiyun }
275*4882a593Smuzhiyun
276*4882a593Smuzhiyun if (cec->irq < 0) {
277*4882a593Smuzhiyun ret = cec->irq;
278*4882a593Smuzhiyun dev_err(&pdev->dev, "cec get irq failed\n");
279*4882a593Smuzhiyun return ret;
280*4882a593Smuzhiyun }
281*4882a593Smuzhiyun
282*4882a593Smuzhiyun ret = devm_request_threaded_irq(&pdev->dev, cec->irq,
283*4882a593Smuzhiyun dw_hdmi_qp_cec_hardirq,
284*4882a593Smuzhiyun dw_hdmi_qp_cec_thread, IRQF_SHARED,
285*4882a593Smuzhiyun "dw-hdmi-qp-cec", cec->adap);
286*4882a593Smuzhiyun if (ret < 0) {
287*4882a593Smuzhiyun dev_err(&pdev->dev, "cec request irq thread failed\n");
288*4882a593Smuzhiyun return ret;
289*4882a593Smuzhiyun }
290*4882a593Smuzhiyun
291*4882a593Smuzhiyun cec->notify = cec_notifier_cec_adap_register(pdev->dev.parent,
292*4882a593Smuzhiyun NULL, cec->adap);
293*4882a593Smuzhiyun if (!cec->notify) {
294*4882a593Smuzhiyun dev_err(&pdev->dev, "cec notifier adap register failed\n");
295*4882a593Smuzhiyun return -ENOMEM;
296*4882a593Smuzhiyun }
297*4882a593Smuzhiyun
298*4882a593Smuzhiyun ret = cec_register_adapter(cec->adap, pdev->dev.parent);
299*4882a593Smuzhiyun if (ret < 0) {
300*4882a593Smuzhiyun dev_err(&pdev->dev, "cec adap register failed\n");
301*4882a593Smuzhiyun cec_notifier_cec_adap_unregister(cec->notify, cec->adap);
302*4882a593Smuzhiyun return ret;
303*4882a593Smuzhiyun }
304*4882a593Smuzhiyun
305*4882a593Smuzhiyun /*
306*4882a593Smuzhiyun * CEC documentation says we must not call cec_delete_adapter
307*4882a593Smuzhiyun * after a successful call to cec_register_adapter().
308*4882a593Smuzhiyun */
309*4882a593Smuzhiyun devm_remove_action(&pdev->dev, dw_hdmi_qp_cec_del, cec);
310*4882a593Smuzhiyun
311*4882a593Smuzhiyun return 0;
312*4882a593Smuzhiyun }
313*4882a593Smuzhiyun
dw_hdmi_qp_cec_remove(struct platform_device * pdev)314*4882a593Smuzhiyun static int dw_hdmi_qp_cec_remove(struct platform_device *pdev)
315*4882a593Smuzhiyun {
316*4882a593Smuzhiyun struct dw_hdmi_qp_cec *cec = platform_get_drvdata(pdev);
317*4882a593Smuzhiyun
318*4882a593Smuzhiyun cec_notifier_cec_adap_unregister(cec->notify, cec->adap);
319*4882a593Smuzhiyun cec_unregister_adapter(cec->adap);
320*4882a593Smuzhiyun
321*4882a593Smuzhiyun return 0;
322*4882a593Smuzhiyun }
323*4882a593Smuzhiyun
324*4882a593Smuzhiyun static struct platform_driver dw_hdmi_qp_cec_driver = {
325*4882a593Smuzhiyun .probe = dw_hdmi_qp_cec_probe,
326*4882a593Smuzhiyun .remove = dw_hdmi_qp_cec_remove,
327*4882a593Smuzhiyun .driver = {
328*4882a593Smuzhiyun .name = "dw-hdmi-qp-cec",
329*4882a593Smuzhiyun },
330*4882a593Smuzhiyun };
331*4882a593Smuzhiyun module_platform_driver(dw_hdmi_qp_cec_driver);
332*4882a593Smuzhiyun
333*4882a593Smuzhiyun MODULE_AUTHOR("Algea Cao <algea.cao@rock-chips.com>");
334*4882a593Smuzhiyun MODULE_DESCRIPTION("Synopsys Designware HDMI QP CEC driver");
335*4882a593Smuzhiyun MODULE_LICENSE("GPL");
336*4882a593Smuzhiyun MODULE_ALIAS(PLATFORM_MODULE_PREFIX "dw-hdmi-qp-cec");
337