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