mirror of
https://github.com/LibreELEC/LibreELEC.tv
synced 2025-09-24 19:46:01 +07:00
354 lines
10 KiB
Diff
354 lines
10 KiB
Diff
From 2821e9a052529c84f737797f364d8d9971234119 Mon Sep 17 00:00:00 2001
|
|
From: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
|
|
Date: Wed, 3 Sep 2025 21:50:59 +0300
|
|
Subject: [PATCH 052/113] FROMLIST(v4): drm/bridge: dw-hdmi-qp: Add CEC support
|
|
|
|
Add support for the CEC interface of the Synopsys DesignWare HDMI QP TX
|
|
controller.
|
|
|
|
This is based on the downstream implementation, but rewritten on top of
|
|
the CEC helpers added recently to the DRM HDMI connector framework.
|
|
|
|
Also note struct dw_hdmi_qp_plat_data has been extended to include the
|
|
CEC IRQ number to be provided by the platform driver.
|
|
|
|
Co-developed-by: Algea Cao <algea.cao@rock-chips.com>
|
|
Signed-off-by: Algea Cao <algea.cao@rock-chips.com>
|
|
Co-developed-by: Derek Foreman <derek.foreman@collabora.com>
|
|
Signed-off-by: Derek Foreman <derek.foreman@collabora.com>
|
|
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
|
|
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
|
|
---
|
|
drivers/gpu/drm/bridge/synopsys/Kconfig | 8 +
|
|
drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c | 212 +++++++++++++++++++
|
|
drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.h | 14 ++
|
|
include/drm/bridge/dw_hdmi_qp.h | 1 +
|
|
4 files changed, 235 insertions(+)
|
|
|
|
diff --git a/drivers/gpu/drm/bridge/synopsys/Kconfig b/drivers/gpu/drm/bridge/synopsys/Kconfig
|
|
index f3ab2f985f8c..99878f051067 100644
|
|
--- a/drivers/gpu/drm/bridge/synopsys/Kconfig
|
|
+++ b/drivers/gpu/drm/bridge/synopsys/Kconfig
|
|
@@ -54,6 +54,14 @@ config DRM_DW_HDMI_QP
|
|
select DRM_KMS_HELPER
|
|
select REGMAP_MMIO
|
|
|
|
+config DRM_DW_HDMI_QP_CEC
|
|
+ bool "Synopsis Designware QP CEC interface"
|
|
+ depends on DRM_DW_HDMI_QP
|
|
+ select DRM_DISPLAY_HDMI_CEC_HELPER
|
|
+ help
|
|
+ Support the CEC interface which is part of the Synopsys
|
|
+ Designware HDMI QP block.
|
|
+
|
|
config DRM_DW_MIPI_DSI
|
|
tristate
|
|
select DRM_KMS_HELPER
|
|
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c
|
|
index 8f5059edb582..1ab30dfaf988 100644
|
|
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c
|
|
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c
|
|
@@ -18,6 +18,7 @@
|
|
|
|
#include <drm/bridge/dw_hdmi_qp.h>
|
|
#include <drm/display/drm_hdmi_helper.h>
|
|
+#include <drm/display/drm_hdmi_cec_helper.h>
|
|
#include <drm/display/drm_hdmi_state_helper.h>
|
|
#include <drm/drm_atomic.h>
|
|
#include <drm/drm_atomic_helper.h>
|
|
@@ -26,6 +27,8 @@
|
|
#include <drm/drm_edid.h>
|
|
#include <drm/drm_modes.h>
|
|
|
|
+#include <media/cec.h>
|
|
+
|
|
#include <sound/hdmi-codec.h>
|
|
|
|
#include "dw-hdmi-qp.h"
|
|
@@ -131,12 +134,28 @@ struct dw_hdmi_qp_i2c {
|
|
bool is_segment;
|
|
};
|
|
|
|
+#ifdef CONFIG_DRM_DW_HDMI_QP_CEC
|
|
+struct dw_hdmi_qp_cec {
|
|
+ struct drm_connector *connector;
|
|
+ int irq;
|
|
+ u32 addresses;
|
|
+ struct cec_msg rx_msg;
|
|
+ u8 tx_status;
|
|
+ bool tx_done;
|
|
+ bool rx_done;
|
|
+};
|
|
+#endif
|
|
+
|
|
struct dw_hdmi_qp {
|
|
struct drm_bridge bridge;
|
|
|
|
struct device *dev;
|
|
struct dw_hdmi_qp_i2c *i2c;
|
|
|
|
+#ifdef CONFIG_DRM_DW_HDMI_QP_CEC
|
|
+ struct dw_hdmi_qp_cec *cec;
|
|
+#endif
|
|
+
|
|
struct {
|
|
const struct dw_hdmi_qp_phy_ops *ops;
|
|
void *data;
|
|
@@ -964,6 +983,179 @@ static int dw_hdmi_qp_bridge_write_infoframe(struct drm_bridge *bridge,
|
|
}
|
|
}
|
|
|
|
+#ifdef CONFIG_DRM_DW_HDMI_QP_CEC
|
|
+static irqreturn_t dw_hdmi_qp_cec_hardirq(int irq, void *dev_id)
|
|
+{
|
|
+ struct dw_hdmi_qp *hdmi = dev_id;
|
|
+ struct dw_hdmi_qp_cec *cec = hdmi->cec;
|
|
+ irqreturn_t ret = IRQ_HANDLED;
|
|
+ u32 stat;
|
|
+
|
|
+ stat = dw_hdmi_qp_read(hdmi, CEC_INT_STATUS);
|
|
+ if (stat == 0)
|
|
+ return IRQ_NONE;
|
|
+
|
|
+ dw_hdmi_qp_write(hdmi, stat, CEC_INT_CLEAR);
|
|
+
|
|
+ if (stat & CEC_STAT_LINE_ERR) {
|
|
+ cec->tx_status = CEC_TX_STATUS_ERROR;
|
|
+ cec->tx_done = true;
|
|
+ ret = IRQ_WAKE_THREAD;
|
|
+ } else if (stat & CEC_STAT_DONE) {
|
|
+ cec->tx_status = CEC_TX_STATUS_OK;
|
|
+ cec->tx_done = true;
|
|
+ ret = IRQ_WAKE_THREAD;
|
|
+ } else if (stat & CEC_STAT_NACK) {
|
|
+ cec->tx_status = CEC_TX_STATUS_NACK;
|
|
+ cec->tx_done = true;
|
|
+ ret = IRQ_WAKE_THREAD;
|
|
+ }
|
|
+
|
|
+ if (stat & CEC_STAT_EOM) {
|
|
+ unsigned int len, i, val;
|
|
+
|
|
+ val = dw_hdmi_qp_read(hdmi, CEC_RX_COUNT_STATUS);
|
|
+ len = (val & 0xf) + 1;
|
|
+
|
|
+ if (len > sizeof(cec->rx_msg.msg))
|
|
+ len = sizeof(cec->rx_msg.msg);
|
|
+
|
|
+ for (i = 0; i < 4; i++) {
|
|
+ val = dw_hdmi_qp_read(hdmi, CEC_RX_DATA3_0 + i * 4);
|
|
+ cec->rx_msg.msg[i * 4] = val & 0xff;
|
|
+ cec->rx_msg.msg[i * 4 + 1] = (val >> 8) & 0xff;
|
|
+ cec->rx_msg.msg[i * 4 + 2] = (val >> 16) & 0xff;
|
|
+ cec->rx_msg.msg[i * 4 + 3] = (val >> 24) & 0xff;
|
|
+ }
|
|
+
|
|
+ dw_hdmi_qp_write(hdmi, 1, CEC_LOCK_CONTROL);
|
|
+
|
|
+ cec->rx_msg.len = len;
|
|
+ cec->rx_done = true;
|
|
+
|
|
+ ret = IRQ_WAKE_THREAD;
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static irqreturn_t dw_hdmi_qp_cec_thread(int irq, void *dev_id)
|
|
+{
|
|
+ struct dw_hdmi_qp *hdmi = dev_id;
|
|
+ struct dw_hdmi_qp_cec *cec = hdmi->cec;
|
|
+
|
|
+ if (cec->tx_done) {
|
|
+ cec->tx_done = false;
|
|
+ drm_connector_hdmi_cec_transmit_attempt_done(cec->connector,
|
|
+ cec->tx_status);
|
|
+ }
|
|
+
|
|
+ if (cec->rx_done) {
|
|
+ cec->rx_done = false;
|
|
+ drm_connector_hdmi_cec_received_msg(cec->connector, &cec->rx_msg);
|
|
+ }
|
|
+
|
|
+ return IRQ_HANDLED;
|
|
+}
|
|
+
|
|
+static int dw_hdmi_qp_cec_init(struct drm_bridge *bridge,
|
|
+ struct drm_connector *connector)
|
|
+{
|
|
+ struct dw_hdmi_qp *hdmi = dw_hdmi_qp_from_bridge(bridge);
|
|
+ struct dw_hdmi_qp_cec *cec = hdmi->cec;
|
|
+
|
|
+ cec->connector = connector;
|
|
+
|
|
+ dw_hdmi_qp_write(hdmi, 0, CEC_TX_COUNT);
|
|
+ dw_hdmi_qp_write(hdmi, ~0, CEC_INT_CLEAR);
|
|
+ dw_hdmi_qp_write(hdmi, 0, CEC_INT_MASK_N);
|
|
+
|
|
+ return devm_request_threaded_irq(hdmi->dev, cec->irq,
|
|
+ dw_hdmi_qp_cec_hardirq,
|
|
+ dw_hdmi_qp_cec_thread, IRQF_SHARED,
|
|
+ dev_name(hdmi->dev), hdmi);
|
|
+}
|
|
+
|
|
+static int dw_hdmi_qp_cec_log_addr(struct drm_bridge *bridge, u8 logical_addr)
|
|
+{
|
|
+ struct dw_hdmi_qp *hdmi = dw_hdmi_qp_from_bridge(bridge);
|
|
+ struct dw_hdmi_qp_cec *cec = hdmi->cec;
|
|
+
|
|
+ if (logical_addr == CEC_LOG_ADDR_INVALID)
|
|
+ cec->addresses = 0;
|
|
+ else
|
|
+ cec->addresses |= BIT(logical_addr) | CEC_ADDR_BROADCAST;
|
|
+
|
|
+ dw_hdmi_qp_write(hdmi, cec->addresses, CEC_ADDR);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int dw_hdmi_qp_cec_enable(struct drm_bridge *bridge, bool enable)
|
|
+{
|
|
+ struct dw_hdmi_qp *hdmi = dw_hdmi_qp_from_bridge(bridge);
|
|
+ unsigned int irqs;
|
|
+ u32 swdisable;
|
|
+
|
|
+ if (!enable) {
|
|
+ dw_hdmi_qp_write(hdmi, 0, CEC_INT_MASK_N);
|
|
+ dw_hdmi_qp_write(hdmi, ~0, CEC_INT_CLEAR);
|
|
+
|
|
+ swdisable = dw_hdmi_qp_read(hdmi, GLOBAL_SWDISABLE);
|
|
+ swdisable = swdisable | CEC_SWDISABLE;
|
|
+ dw_hdmi_qp_write(hdmi, swdisable, GLOBAL_SWDISABLE);
|
|
+ } else {
|
|
+ swdisable = dw_hdmi_qp_read(hdmi, GLOBAL_SWDISABLE);
|
|
+ swdisable = swdisable & ~CEC_SWDISABLE;
|
|
+ dw_hdmi_qp_write(hdmi, swdisable, GLOBAL_SWDISABLE);
|
|
+
|
|
+ dw_hdmi_qp_write(hdmi, ~0, CEC_INT_CLEAR);
|
|
+ dw_hdmi_qp_write(hdmi, 1, CEC_LOCK_CONTROL);
|
|
+
|
|
+ dw_hdmi_qp_cec_log_addr(bridge, CEC_LOG_ADDR_INVALID);
|
|
+
|
|
+ irqs = CEC_STAT_LINE_ERR | CEC_STAT_NACK | CEC_STAT_EOM |
|
|
+ CEC_STAT_DONE;
|
|
+ dw_hdmi_qp_write(hdmi, ~0, CEC_INT_CLEAR);
|
|
+ dw_hdmi_qp_write(hdmi, irqs, CEC_INT_MASK_N);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int dw_hdmi_qp_cec_transmit(struct drm_bridge *bridge, u8 attempts,
|
|
+ u32 signal_free_time, struct cec_msg *msg)
|
|
+{
|
|
+ struct dw_hdmi_qp *hdmi = dw_hdmi_qp_from_bridge(bridge);
|
|
+ unsigned int i;
|
|
+ u32 val;
|
|
+
|
|
+ for (i = 0; i < msg->len; i++) {
|
|
+ if (!(i % 4))
|
|
+ val = msg->msg[i];
|
|
+ if ((i % 4) == 1)
|
|
+ val |= msg->msg[i] << 8;
|
|
+ if ((i % 4) == 2)
|
|
+ val |= msg->msg[i] << 16;
|
|
+ if ((i % 4) == 3)
|
|
+ val |= msg->msg[i] << 24;
|
|
+
|
|
+ if (i == (msg->len - 1) || (i % 4) == 3)
|
|
+ dw_hdmi_qp_write(hdmi, val, CEC_TX_DATA3_0 + (i / 4) * 4);
|
|
+ }
|
|
+
|
|
+ dw_hdmi_qp_write(hdmi, msg->len - 1, CEC_TX_COUNT);
|
|
+ dw_hdmi_qp_write(hdmi, CEC_CTRL_START, CEC_TX_CONTROL);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+#else
|
|
+#define dw_hdmi_qp_cec_init NULL
|
|
+#define dw_hdmi_qp_cec_enable NULL
|
|
+#define dw_hdmi_qp_cec_log_addr NULL
|
|
+#define dw_hdmi_qp_cec_transmit NULL
|
|
+#endif /* CONFIG_DRM_DW_HDMI_QP_CEC */
|
|
+
|
|
static const struct drm_bridge_funcs dw_hdmi_qp_bridge_funcs = {
|
|
.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
|
|
.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
|
|
@@ -978,6 +1170,10 @@ static const struct drm_bridge_funcs dw_hdmi_qp_bridge_funcs = {
|
|
.hdmi_audio_startup = dw_hdmi_qp_audio_enable,
|
|
.hdmi_audio_shutdown = dw_hdmi_qp_audio_disable,
|
|
.hdmi_audio_prepare = dw_hdmi_qp_audio_prepare,
|
|
+ .hdmi_cec_init = dw_hdmi_qp_cec_init,
|
|
+ .hdmi_cec_enable = dw_hdmi_qp_cec_enable,
|
|
+ .hdmi_cec_log_addr = dw_hdmi_qp_cec_log_addr,
|
|
+ .hdmi_cec_transmit = dw_hdmi_qp_cec_transmit,
|
|
};
|
|
|
|
static irqreturn_t dw_hdmi_qp_main_hardirq(int irq, void *dev_id)
|
|
@@ -1092,6 +1288,22 @@ struct dw_hdmi_qp *dw_hdmi_qp_bind(struct platform_device *pdev,
|
|
hdmi->bridge.hdmi_audio_dev = dev;
|
|
hdmi->bridge.hdmi_audio_dai_port = 1;
|
|
|
|
+#ifdef CONFIG_DRM_DW_HDMI_QP_CEC
|
|
+ if (plat_data->cec_irq) {
|
|
+ hdmi->bridge.ops |= DRM_BRIDGE_OP_HDMI_CEC_ADAPTER;
|
|
+ hdmi->bridge.hdmi_cec_dev = dev;
|
|
+ hdmi->bridge.hdmi_cec_adapter_name = dev_name(dev);
|
|
+
|
|
+ hdmi->cec = devm_kzalloc(hdmi->dev, sizeof(*hdmi->cec), GFP_KERNEL);
|
|
+ if (!hdmi->cec)
|
|
+ return ERR_PTR(-ENOMEM);
|
|
+
|
|
+ hdmi->cec->irq = plat_data->cec_irq;
|
|
+ } else {
|
|
+ dev_warn(dev, "Disabled CEC support due to missing IRQ\n");
|
|
+ }
|
|
+#endif
|
|
+
|
|
ret = devm_drm_bridge_add(dev, &hdmi->bridge);
|
|
if (ret)
|
|
return ERR_PTR(ret);
|
|
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.h b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.h
|
|
index 72987e6c4689..91a15f82e32a 100644
|
|
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.h
|
|
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.h
|
|
@@ -488,9 +488,23 @@
|
|
#define AUDPKT_VBIT_OVR0 0xf24
|
|
/* CEC Registers */
|
|
#define CEC_TX_CONTROL 0x1000
|
|
+#define CEC_CTRL_CLEAR BIT(0)
|
|
+#define CEC_CTRL_START BIT(0)
|
|
#define CEC_STATUS 0x1004
|
|
+#define CEC_STAT_DONE BIT(0)
|
|
+#define CEC_STAT_NACK BIT(1)
|
|
+#define CEC_STAT_ARBLOST BIT(2)
|
|
+#define CEC_STAT_LINE_ERR BIT(3)
|
|
+#define CEC_STAT_RETRANS_FAIL BIT(4)
|
|
+#define CEC_STAT_DISCARD BIT(5)
|
|
+#define CEC_STAT_TX_BUSY BIT(8)
|
|
+#define CEC_STAT_RX_BUSY BIT(9)
|
|
+#define CEC_STAT_DRIVE_ERR BIT(10)
|
|
+#define CEC_STAT_EOM BIT(11)
|
|
+#define CEC_STAT_NOTIFY_ERR BIT(12)
|
|
#define CEC_CONFIG 0x1008
|
|
#define CEC_ADDR 0x100c
|
|
+#define CEC_ADDR_BROADCAST BIT(15)
|
|
#define CEC_TX_COUNT 0x1020
|
|
#define CEC_TX_DATA3_0 0x1024
|
|
#define CEC_TX_DATA7_4 0x1028
|
|
diff --git a/include/drm/bridge/dw_hdmi_qp.h b/include/drm/bridge/dw_hdmi_qp.h
|
|
index e9be6d507ad9..b4a9b739734e 100644
|
|
--- a/include/drm/bridge/dw_hdmi_qp.h
|
|
+++ b/include/drm/bridge/dw_hdmi_qp.h
|
|
@@ -23,6 +23,7 @@ struct dw_hdmi_qp_plat_data {
|
|
const struct dw_hdmi_qp_phy_ops *phy_ops;
|
|
void *phy_data;
|
|
int main_irq;
|
|
+ int cec_irq;
|
|
};
|
|
|
|
struct dw_hdmi_qp *dw_hdmi_qp_bind(struct platform_device *pdev,
|
|
--
|
|
2.34.1
|
|
|