blob: 742928e71a9fc2402e33679a6327c79a70fa7320 [file] [log] [blame]
b.liue9582032025-04-17 19:18:16 +08001From ed9e7a6b3346b186a7bd22d9b62072e55ff7b9c5 Mon Sep 17 00:00:00 2001
2From: Sandor Yu <Sandor.yu@nxp.com>
3Date: Mon, 2 Sep 2019 14:57:05 +0800
4Subject: [PATCH] drm: bridge: cadence: Add CEC driver for cdns mhdp hdmi
5
6Add cec driver for cdns mhdp hdmi.
7
8Signed-off-by: Sandor Yu <Sandor.yu@nxp.com>
9---
10 drivers/gpu/drm/bridge/cadence/Kconfig | 3 +
11 drivers/gpu/drm/bridge/cadence/Makefile | 1 +
12 drivers/gpu/drm/bridge/cadence/cdns-dp-core.c | 0
13 drivers/gpu/drm/bridge/cadence/cdns-hdmi-core.c | 9 +
14 drivers/gpu/drm/bridge/cadence/cdns-mhdp-cec.c | 347 ++++++++++++++++++++++
15 drivers/gpu/drm/bridge/cadence/cdns-mhdp-common.c | 0
16 drivers/gpu/drm/bridge/cadence/cdns-mhdp-hdmi.c | 0
17 include/drm/bridge/cdns-mhdp-common.h | 24 +-
18 8 files changed, 382 insertions(+), 2 deletions(-)
19 mode change 100755 => 100644 drivers/gpu/drm/bridge/cadence/cdns-dp-core.c
20 mode change 100755 => 100644 drivers/gpu/drm/bridge/cadence/cdns-hdmi-core.c
21 create mode 100644 drivers/gpu/drm/bridge/cadence/cdns-mhdp-cec.c
22 mode change 100755 => 100644 drivers/gpu/drm/bridge/cadence/cdns-mhdp-common.c
23 mode change 100755 => 100644 drivers/gpu/drm/bridge/cadence/cdns-mhdp-hdmi.c
24
25--- a/drivers/gpu/drm/bridge/cadence/Kconfig
26+++ b/drivers/gpu/drm/bridge/cadence/Kconfig
27@@ -14,3 +14,6 @@ config DRM_CDNS_DP
28
29 config DRM_CDNS_AUDIO
30 tristate "Cadence MHDP Audio driver"
31+
32+config DRM_CDNS_HDMI_CEC
33+ tristate "Cadence MHDP HDMI CEC driver"
34--- a/drivers/gpu/drm/bridge/cadence/Makefile
35+++ b/drivers/gpu/drm/bridge/cadence/Makefile
36@@ -2,3 +2,4 @@ obj-$(CONFIG_DRM_CDNS_MHDP) += cdns-mhdp
37 obj-$(CONFIG_DRM_CDNS_HDMI) += cdns-hdmi-core.o
38 obj-$(CONFIG_DRM_CDNS_DP) += cdns-dp-core.o
39 obj-$(CONFIG_DRM_CDNS_AUDIO) += cdns-mhdp-audio.o
40+obj-$(CONFIG_DRM_CDNS_HDMI_CEC) += cdns-mhdp-cec.o
41--- a/drivers/gpu/drm/bridge/cadence/cdns-hdmi-core.c
42+++ b/drivers/gpu/drm/bridge/cadence/cdns-hdmi-core.c
43@@ -515,6 +515,11 @@ __cdns_hdmi_probe(struct platform_device
44 /* register audio driver */
45 cdns_mhdp_register_audio_driver(dev);
46
47+ /* register cec driver */
48+#ifdef CONFIG_DRM_CDNS_HDMI_CEC
49+ cdns_mhdp_register_cec_driver(dev);
50+#endif
51+
52 return hdmi;
53
54 err_out:
55@@ -524,6 +529,10 @@ err_out:
56
57 static void __cdns_hdmi_remove(struct cdns_mhdp_device *mhdp)
58 {
59+ /* unregister cec driver */
60+#ifdef CONFIG_DRM_CDNS_HDMI_CEC
61+ cdns_mhdp_unregister_cec_driver(mhdp->dev);
62+#endif
63 cdns_mhdp_unregister_audio_driver(mhdp->dev);
64 }
65
66--- /dev/null
67+++ b/drivers/gpu/drm/bridge/cadence/cdns-mhdp-cec.c
68@@ -0,0 +1,347 @@
69+/*
70+ * Copyright 2019 NXP
71+ *
72+ * This program is free software; you can redistribute it and/or
73+ * modify it under the terms of the GNU General Public License
74+ * as published by the Free Software Foundation; either version 2
75+ * of the License, or (at your option) any later version.
76+ *
77+ * This program is distributed in the hope that it will be useful,
78+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
79+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
80+ * GNU General Public License for more details.
81+ */
82+#include <linux/module.h>
83+#include <linux/workqueue.h>
84+#include <linux/kthread.h>
85+#include <linux/freezer.h>
86+#include <drm/bridge/cdns-mhdp-common.h>
87+
88+#define CEC_NAME "cdns-mhdp-cec"
89+
90+#define REG_ADDR_OFF 4
91+#define MAX_LA_IDX 4
92+#define MAX_LA_VAL 15
93+
94+/* regsiter define */
95+#define TX_MSG_HEADER 0x33800
96+#define TX_MSG_LENGTH 0x33840
97+#define TX_MSG_CMD 0x33844
98+#define RX_MSG_CMD 0x33850
99+#define RX_CLEAR_BUF 0x33854
100+#define LOGICAL_ADDRESS_LA0 0x33858
101+
102+#define CLK_DIV_MSB 0x3386c
103+#define CLK_DIV_LSB 0x33870
104+#define RX_MSG_DATA1 0x33900
105+#define RX_MSG_LENGTH 0x33940
106+#define RX_MSG_STATUS 0x33944
107+#define NUM_OF_MSG_RX_BUF 0x33948
108+#define TX_MSG_STATUS 0x3394c
109+#define DB_L_TIMER 0x33980
110+
111+/**
112+ * CEC Transceiver operation.
113+ */
114+enum {
115+ CEC_TX_STOP,
116+ CEC_TX_TRANSMIT,
117+ CEC_TX_ABORT,
118+ CEC_TX_ABORT_AND_TRANSMIT
119+};
120+
121+/**
122+ * CEC Transceiver status.
123+ */
124+enum {
125+ CEC_STS_IDLE,
126+ CEC_STS_BUSY,
127+ CEC_STS_SUCCESS,
128+ CEC_STS_ERROR
129+};
130+
131+/**
132+ * CEC Receiver operation.
133+ */
134+enum {
135+ CEC_RX_STOP,
136+ CEC_RX_READ,
137+ CEC_RX_DISABLE,
138+ CEC_RX_ABORT_AND_CLR_FIFO
139+};
140+/**
141+ * Maximum number of Messages in the RX Buffers.
142+ */
143+#define CEC_MAX_RX_MSGS 2
144+
145+static u32 mhdp_cec_read(struct cdns_mhdp_cec *cec, u32 offset)
146+{
147+ struct cdns_mhdp_device *mhdp =
148+ container_of(cec, struct cdns_mhdp_device, hdmi.cec);
149+ return cdns_mhdp_bus_read(mhdp, offset);
150+}
151+
152+static void mhdp_cec_write(struct cdns_mhdp_cec *cec, u32 offset, u32 val)
153+{
154+ struct cdns_mhdp_device *mhdp =
155+ container_of(cec, struct cdns_mhdp_device, hdmi.cec);
156+ cdns_mhdp_bus_write(val, mhdp, offset);
157+}
158+
159+static void mhdp_cec_clear_rx_buffer(struct cdns_mhdp_cec *cec)
160+{
161+ mhdp_cec_write(cec, RX_CLEAR_BUF, 1);
162+ mhdp_cec_write(cec, RX_CLEAR_BUF, 0);
163+}
164+
165+static void mhdp_cec_set_divider(struct cdns_mhdp_cec *cec)
166+{
167+ struct cdns_mhdp_device *mhdp =
168+ container_of(cec, struct cdns_mhdp_device, hdmi.cec);
169+ u32 clk_div;
170+
171+ /* Set clock divider */
172+ clk_div = cdns_mhdp_get_fw_clk(mhdp) * 10;
173+
174+ mhdp_cec_write(cec, CLK_DIV_MSB,
175+ (clk_div >> 8) & 0xFF);
176+ mhdp_cec_write(cec, CLK_DIV_LSB, clk_div & 0xFF);
177+}
178+
179+static u32 mhdp_cec_read_message(struct cdns_mhdp_cec *cec)
180+{
181+ struct cec_msg *msg = &cec->msg;
182+ int len;
183+ int i;
184+
185+ mhdp_cec_write(cec, RX_MSG_CMD, CEC_RX_READ);
186+
187+ len = mhdp_cec_read(cec, RX_MSG_LENGTH);
188+ msg->len = len + 1;
189+ dev_dbg(cec->dev, "RX MSG len =%d\n", len);
190+
191+ /* Read RX MSG bytes */
192+ for (i = 0; i < msg->len; ++i) {
193+ msg->msg[i] = (u8) mhdp_cec_read(cec, RX_MSG_DATA1 + (i * REG_ADDR_OFF));
194+ dev_dbg(cec->dev, "RX MSG[%d]=0x%x\n", i, msg->msg[i]);
195+ }
196+
197+ mhdp_cec_write(cec, RX_MSG_CMD, CEC_RX_STOP);
198+
199+ return true;
200+}
201+
202+static u32 mhdp_cec_write_message(struct cdns_mhdp_cec *cec, struct cec_msg *msg)
203+{
204+ u8 i;
205+
206+ mhdp_cec_write(cec, TX_MSG_CMD, CEC_TX_STOP);
207+
208+ if (msg->len > CEC_MAX_MSG_SIZE) {
209+ dev_err(cec->dev, "Invalid MSG size!\n");
210+ return -EINVAL;
211+ }
212+
213+ for (i = 0; i < msg->len; ++i)
214+ printk("msg[%d]=0x%x\n",i, msg->msg[i]);
215+
216+ /* Write Message to register */
217+ for (i = 0; i < msg->len; ++i) {
218+ mhdp_cec_write(cec, TX_MSG_HEADER + (i * REG_ADDR_OFF),
219+ msg->msg[i]);
220+ }
221+ /* Write Message Length (payload + opcode) */
222+ mhdp_cec_write(cec, TX_MSG_LENGTH, msg->len - 1);
223+
224+ mhdp_cec_write(cec, TX_MSG_CMD, CEC_TX_TRANSMIT);
225+
226+ return true;
227+}
228+
229+//static void cec_abort_tx_transfer(struct cdns_mhdp_cec *cec)
230+//{
231+// cec_write(cec, TX_MSG_CMD, CEC_TX_ABORT);
232+// cec_write(cec, TX_MSG_CMD, CEC_TX_STOP);
233+//}
234+
235+static int mhdp_cec_set_logical_addr(struct cdns_mhdp_cec *cec, u32 la)
236+{
237+ u8 i;
238+ u8 la_reg;
239+
240+ if (la >= MAX_LA_VAL) {
241+ dev_err(cec->dev, "Error logical Addr\n");
242+ return -EINVAL;
243+ }
244+
245+ for (i = 0; i < MAX_LA_IDX; ++i) {
246+ la_reg =
247+ mhdp_cec_read(cec, LOGICAL_ADDRESS_LA0 + (i * REG_ADDR_OFF));
248+
249+ if (la_reg & 0x10)
250+ continue;
251+
252+ if ((la_reg & 0xF) == la) {
253+ dev_warn(cec->dev, "Warning. LA already in use.\n");
254+ return true;
255+ }
256+
257+ la = (la & 0xF) | (1 << 4);
258+
259+ mhdp_cec_write(cec, LOGICAL_ADDRESS_LA0 + (i * REG_ADDR_OFF), la);
260+ return true;
261+ }
262+
263+ dev_warn(cec->dev, "All LA in use\n");
264+
265+ return false;
266+}
267+
268+static int mhdp_cec_poll_worker(void *_cec)
269+{
270+ struct cdns_mhdp_cec *cec = (struct cdns_mhdp_cec *)_cec;
271+ int num_rx_msgs, i;
272+ int sts;
273+
274+ set_freezable();
275+
276+ for (;;) {
277+ if (kthread_freezable_should_stop(NULL))
278+ break;
279+
280+ /* Check TX State */
281+ sts = mhdp_cec_read(cec, TX_MSG_STATUS);
282+ switch (sts) {
283+ case CEC_STS_SUCCESS:
284+ cec_transmit_done(cec->adap, CEC_TX_STATUS_OK, 0, 0, 0,
285+ 0);
286+ mhdp_cec_write(cec, TX_MSG_CMD, CEC_TX_STOP);
287+ break;
288+ case CEC_STS_ERROR:
289+ mhdp_cec_write(cec, TX_MSG_CMD, CEC_TX_STOP);
290+ cec_transmit_done(cec->adap,
291+ CEC_TX_STATUS_MAX_RETRIES |
292+ CEC_TX_STATUS_NACK, 0, 1, 0, 0);
293+ break;
294+ case CEC_STS_BUSY:
295+ default:
296+ break;
297+ }
298+
299+ /* Check RX State */
300+ sts = mhdp_cec_read(cec, RX_MSG_STATUS);
301+ num_rx_msgs = mhdp_cec_read(cec, NUM_OF_MSG_RX_BUF);
302+ switch (sts) {
303+ case CEC_STS_SUCCESS:
304+ if (num_rx_msgs == 0xf)
305+ num_rx_msgs = CEC_MAX_RX_MSGS;
306+
307+ if (num_rx_msgs > CEC_MAX_RX_MSGS) {
308+ dev_err(cec->dev, "Error rx msg num %d\n",
309+ num_rx_msgs);
310+ mhdp_cec_clear_rx_buffer(cec);
311+ break;
312+ }
313+
314+ /* Rx FIFO Depth 2 RX MSG */
315+ for (i = 0; i < num_rx_msgs; i++) {
316+ mhdp_cec_read_message(cec);
317+ cec->msg.rx_status = CEC_RX_STATUS_OK;
318+ cec_received_msg(cec->adap, &cec->msg);
319+ }
320+ break;
321+ default:
322+ break;
323+ }
324+
325+ if (!kthread_should_stop())
326+ schedule_timeout_idle(20);
327+ }
328+
329+ return 0;
330+}
331+
332+static int mhdp_cec_adap_enable(struct cec_adapter *adap, bool enable)
333+{
334+ struct cdns_mhdp_cec *cec = adap->priv;
335+
336+ if (enable) {
337+ mhdp_cec_write(cec, DB_L_TIMER, 0x10);
338+ mhdp_cec_set_divider(cec);
339+ } else
340+ mhdp_cec_set_divider(cec);
341+
342+ return 0;
343+}
344+
345+static int mhdp_cec_adap_log_addr(struct cec_adapter *adap, u8 addr)
346+{
347+ struct cdns_mhdp_cec *cec = adap->priv;
348+
349+ return mhdp_cec_set_logical_addr(cec, addr);
350+}
351+
352+static int mhdp_cec_adap_transmit(struct cec_adapter *adap, u8 attempts,
353+ u32 signal_free_time, struct cec_msg *msg)
354+{
355+ struct cdns_mhdp_cec *cec = adap->priv;
356+
357+ mhdp_cec_write_message(cec, msg);
358+
359+ return 0;
360+}
361+
362+static const struct cec_adap_ops cdns_mhdp_cec_adap_ops = {
363+ .adap_enable = mhdp_cec_adap_enable,
364+ .adap_log_addr = mhdp_cec_adap_log_addr,
365+ .adap_transmit = mhdp_cec_adap_transmit,
366+};
367+
368+int cdns_mhdp_register_cec_driver(struct device *dev)
369+{
370+ struct cdns_mhdp_device *mhdp = dev_get_drvdata(dev);
371+ struct cdns_mhdp_cec *cec = &mhdp->hdmi.cec;
372+ int ret;
373+
374+ cec->adap = cec_allocate_adapter(&cdns_mhdp_cec_adap_ops, cec,
375+ CEC_NAME,
376+ CEC_CAP_PHYS_ADDR | CEC_CAP_LOG_ADDRS |
377+ CEC_CAP_TRANSMIT | CEC_CAP_PASSTHROUGH
378+ | CEC_CAP_RC, 1);
379+ ret = PTR_ERR_OR_ZERO(cec->adap);
380+ if (ret)
381+ return ret;
382+ ret = cec_register_adapter(cec->adap, dev);
383+ if (ret) {
384+ cec_delete_adapter(cec->adap);
385+ return ret;
386+ }
387+
388+ cec->dev = dev;
389+
390+ cec->cec_worker = kthread_create(mhdp_cec_poll_worker, cec, "cdns-mhdp-cec");
391+ if (IS_ERR(cec->cec_worker))
392+ dev_err(cec->dev, "failed create hdp cec thread\n");
393+
394+ wake_up_process(cec->cec_worker);
395+
396+ dev_dbg(dev, "CEC successfuly probed\n");
397+ return 0;
398+}
399+
400+int cdns_mhdp_unregister_cec_driver(struct device *dev)
401+{
402+ struct cdns_mhdp_device *mhdp = dev_get_drvdata(dev);
403+ struct cdns_mhdp_cec *cec = &mhdp->hdmi.cec;
404+
405+ if (cec->cec_worker) {
406+ kthread_stop(cec->cec_worker);
407+ cec->cec_worker = NULL;
408+ }
409+ cec_unregister_adapter(cec->adap);
410+ return 0;
411+}
412+
413+MODULE_AUTHOR("Sandor.Yu@NXP.com");
414+MODULE_LICENSE("GPL");
415+MODULE_DESCRIPTION("NXP CDNS MHDP CEC driver");
416--- a/include/drm/bridge/cdns-mhdp-common.h
417+++ b/include/drm/bridge/cdns-mhdp-common.h
418@@ -21,7 +21,7 @@
419 #include <drm/drm_connector.h>
420 #include <drm/drm_dp_helper.h>
421 #include <drm/drm_dp_mst_helper.h>
422-
423+#include <media/cec.h>
424 #include <linux/bitops.h>
425
426 #define ADDR_IMEM 0x10000
427@@ -605,6 +605,17 @@ struct cdns_mhdp_connector {
428 struct cdns_mhdp_bridge *bridge;
429 };
430
431+#ifdef CONFIG_DRM_CDNS_HDMI_CEC
432+struct cdns_mhdp_cec {
433+ struct cec_adapter *adap;
434+ struct device *dev;
435+ struct mutex lock;
436+
437+ struct cec_msg msg;
438+ struct task_struct *cec_worker;
439+};
440+#endif
441+
442 struct cdns_mhdp_device {
443 void __iomem *regs;
444
445@@ -633,7 +644,7 @@ struct cdns_mhdp_device {
446 bool plugged;
447
448 union {
449- struct cdn_dp_data {
450+ struct _dp_data {
451 struct drm_dp_link link;
452 struct drm_dp_aux aux;
453 struct cdns_mhdp_host host;
454@@ -645,6 +656,9 @@ struct cdns_mhdp_device {
455 u32 num_lanes;
456 } dp;
457 struct _hdmi_data {
458+#ifdef CONFIG_DRM_CDNS_HDMI_CEC
459+ struct cdns_mhdp_cec cec;
460+#endif
461 u32 char_rate;
462 u32 hdmi_type;
463 } hdmi;
464@@ -713,4 +727,10 @@ int cdns_hdmi_disable_gcp(struct cdns_mh
465 int cdns_hdmi_enable_gcp(struct cdns_mhdp_device *mhdp);
466
467 bool cdns_mhdp_check_alive(struct cdns_mhdp_device *mhdp);
468+/* CEC */
469+#ifdef CONFIG_DRM_CDNS_HDMI_CEC
470+int cdns_mhdp_register_cec_driver(struct device *dev);
471+int cdns_mhdp_unregister_cec_driver(struct device *dev);
472+#endif
473+
474 #endif /* CDNS_MHDP_COMMON_H_ */