1
0
Fork 0

Add HDMI CEC support for the DesignWare HDMI QP controller

Backport the generic DRM HDMI CEC helpers (drm_hdmi_cec_helper and
drm_hdmi_cec_notifier_helper) along with the drm_connector CEC state,
drm_bridge CEC callbacks, and drm_bridge_connector glue that the
adapter registration path requires. Propagate the HDMI physical
address to the CEC adapter from the HDMI state helper hotplug path.

Implement the CEC adapter in dw-hdmi-qp: register definitions, IRQ
handling, transmit and receive paths, logical address programming,
and enable/disable. Pass the named "cec" IRQ and the HDMI reference
clock rate down from dw_hdmi_qp-rockchip so the timer base is
initialised from the actual reference clock.

Enable CONFIG_DRM_DW_HDMI_QP_CEC in tart_defconfig.
This commit is contained in:
Brendan Szymanski 2026-08-09 22:18:44 -04:00
parent 10cf76215c
commit 9a769db9af
16 changed files with 704 additions and 6 deletions

View file

@ -6012,6 +6012,7 @@ CONFIG_VIDEO_NOMODESET=y
CONFIG_DRM=y CONFIG_DRM=y
#CONFIG_DRM_MIPI_DBI=m #CONFIG_DRM_MIPI_DBI=m
CONFIG_DRM_DW_HDMI_QP=y CONFIG_DRM_DW_HDMI_QP=y
CONFIG_DRM_DW_HDMI_QP_CEC=y
CONFIG_DRM_DW_DP=y CONFIG_DRM_DW_DP=y
CONFIG_DRM_MIPI_DSI=y CONFIG_DRM_MIPI_DSI=y
CONFIG_DRM_EDID=y CONFIG_DRM_EDID=y

View file

@ -61,6 +61,14 @@ config DRM_DW_HDMI_QP
select DRM_KMS_HELPER select DRM_KMS_HELPER
select REGMAP_MMIO select REGMAP_MMIO
config DRM_DW_HDMI_QP_CEC
bool "Synopsys 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 config DRM_DW_MIPI_DSI
tristate tristate
select DRM_KMS_HELPER select DRM_KMS_HELPER

View file

@ -18,6 +18,7 @@
#include <drm/bridge/dw_hdmi_qp.h> #include <drm/bridge/dw_hdmi_qp.h>
#include <drm/display/drm_hdmi_helper.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/display/drm_hdmi_state_helper.h>
#include <drm/display/drm_scdc_helper.h> #include <drm/display/drm_scdc_helper.h>
#include <drm/drm_atomic.h> #include <drm/drm_atomic.h>
@ -28,6 +29,7 @@
#include <drm/drm_modes.h> #include <drm/drm_modes.h>
#include <sound/hdmi-codec.h> #include <sound/hdmi-codec.h>
#include <media/cec.h>
#include "dw-hdmi-qp.h" #include "dw-hdmi-qp.h"
@ -136,6 +138,18 @@ struct dw_hdmi_qp_i2c {
bool is_segment; 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 dw_hdmi_qp {
struct drm_bridge bridge; struct drm_bridge bridge;
@ -147,11 +161,16 @@ struct dw_hdmi_qp {
void *data; void *data;
} phy; } phy;
#ifdef CONFIG_DRM_DW_HDMI_QP_CEC
struct dw_hdmi_qp_cec *cec;
#endif
struct drm_connector *curr_conn; struct drm_connector *curr_conn;
struct delayed_work scramb_work; struct delayed_work scramb_work;
bool scramb_enabled; bool scramb_enabled;
struct regmap *regm; struct regmap *regm;
unsigned long ref_clk_rate;
unsigned long tmds_char_rate; unsigned long tmds_char_rate;
}; };
@ -1130,6 +1149,171 @@ 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)
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_connector *connector,
struct drm_bridge *bridge)
{
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);
dw_hdmi_qp_write(hdmi, swdisable | CEC_SWDISABLE,
GLOBAL_SWDISABLE);
} else {
swdisable = dw_hdmi_qp_read(hdmi, GLOBAL_SWDISABLE);
dw_hdmi_qp_write(hdmi, swdisable & ~CEC_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 = 0;
for (i = 0; i < msg->len; i++) {
if (!(i % 4))
val = msg->msg[i];
else if ((i % 4) == 1)
val |= msg->msg[i] << 8;
else if ((i % 4) == 2)
val |= msg->msg[i] << 16;
else
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
static const struct drm_bridge_funcs dw_hdmi_qp_bridge_funcs = { static const struct drm_bridge_funcs dw_hdmi_qp_bridge_funcs = {
.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
@ -1145,6 +1329,10 @@ static const struct drm_bridge_funcs dw_hdmi_qp_bridge_funcs = {
.hdmi_audio_startup = dw_hdmi_qp_audio_enable, .hdmi_audio_startup = dw_hdmi_qp_audio_enable,
.hdmi_audio_shutdown = dw_hdmi_qp_audio_disable, .hdmi_audio_shutdown = dw_hdmi_qp_audio_disable,
.hdmi_audio_prepare = dw_hdmi_qp_audio_prepare, .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) static irqreturn_t dw_hdmi_qp_main_hardirq(int irq, void *dev_id)
@ -1180,7 +1368,7 @@ static void dw_hdmi_qp_init_hw(struct dw_hdmi_qp *hdmi)
{ {
dw_hdmi_qp_write(hdmi, 0, MAINUNIT_0_INT_MASK_N); dw_hdmi_qp_write(hdmi, 0, MAINUNIT_0_INT_MASK_N);
dw_hdmi_qp_write(hdmi, 0, MAINUNIT_1_INT_MASK_N); dw_hdmi_qp_write(hdmi, 0, MAINUNIT_1_INT_MASK_N);
dw_hdmi_qp_write(hdmi, 428571429, TIMER_BASE_CONFIG0); dw_hdmi_qp_write(hdmi, hdmi->ref_clk_rate, TIMER_BASE_CONFIG0);
/* Software reset */ /* Software reset */
dw_hdmi_qp_write(hdmi, 0x01, I2CM_CONTROL0); dw_hdmi_qp_write(hdmi, 0x01, I2CM_CONTROL0);
@ -1232,6 +1420,10 @@ struct dw_hdmi_qp *dw_hdmi_qp_bind(struct platform_device *pdev,
hdmi->phy.ops = plat_data->phy_ops; hdmi->phy.ops = plat_data->phy_ops;
hdmi->phy.data = plat_data->phy_data; hdmi->phy.data = plat_data->phy_data;
if (plat_data->ref_clk_rate)
hdmi->ref_clk_rate = plat_data->ref_clk_rate;
else
hdmi->ref_clk_rate = 428571429;
dw_hdmi_qp_init_hw(hdmi); dw_hdmi_qp_init_hw(hdmi);
@ -1260,6 +1452,18 @@ struct dw_hdmi_qp *dw_hdmi_qp_bind(struct platform_device *pdev,
hdmi->bridge.hdmi_audio_dev = dev; hdmi->bridge.hdmi_audio_dev = dev;
hdmi->bridge.hdmi_audio_dai_port = 1; 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(dev, sizeof(*hdmi->cec), GFP_KERNEL);
if (!hdmi->cec)
return ERR_PTR(-ENOMEM);
hdmi->cec->irq = plat_data->cec_irq;
}
#endif
ret = devm_drm_bridge_add(dev, &hdmi->bridge); ret = devm_drm_bridge_add(dev, &hdmi->bridge);
if (ret) if (ret)
return ERR_PTR(ret); return ERR_PTR(ret);

View file

@ -488,9 +488,23 @@
#define AUDPKT_VBIT_OVR0 0xf24 #define AUDPKT_VBIT_OVR0 0xf24
/* CEC Registers */ /* CEC Registers */
#define CEC_TX_CONTROL 0x1000 #define CEC_TX_CONTROL 0x1000
#define CEC_CTRL_CLEAR BIT(0)
#define CEC_CTRL_START BIT(0)
#define CEC_STATUS 0x1004 #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_CONFIG 0x1008
#define CEC_ADDR 0x100c #define CEC_ADDR 0x100c
#define CEC_ADDR_BROADCAST BIT(15)
#define CEC_TX_COUNT 0x1020 #define CEC_TX_COUNT 0x1020
#define CEC_TX_DATA3_0 0x1024 #define CEC_TX_DATA3_0 0x1024
#define CEC_TX_DATA7_4 0x1028 #define CEC_TX_DATA7_4 0x1028

View file

@ -8,9 +8,11 @@ config DRM_DISPLAY_DP_AUX_BUS
config DRM_DISPLAY_HELPER config DRM_DISPLAY_HELPER
tristate tristate
depends on DRM depends on DRM
select CEC_CORE if DRM_DISPLAY_DP_AUX_CEC || DRM_DISPLAY_HDMI_CEC_HELPER || CEC_NOTIFIER
help help
DRM helpers for display adapters. DRM helpers for display adapters.
if DRM_DISPLAY_HELPER if DRM_DISPLAY_HELPER
config DRM_BRIDGE_CONNECTOR config DRM_BRIDGE_CONNECTOR
@ -82,6 +84,16 @@ config DRM_DISPLAY_HDMI_AUDIO_HELPER
DRM display helpers for HDMI Audio functionality (generic HDMI Codec DRM display helpers for HDMI Audio functionality (generic HDMI Codec
implementation). implementation).
config DRM_DISPLAY_HDMI_CEC_HELPER
bool
help
DRM display helpers for HDMI CEC implementation.
config DRM_DISPLAY_HDMI_CEC_NOTIFIER_HELPER
def_bool CEC_NOTIFIER
help
DRM display helpers for HDMI CEC notifiers implementation.
config DRM_DISPLAY_HDMI_HELPER config DRM_DISPLAY_HDMI_HELPER
bool bool
help help

View file

@ -16,6 +16,10 @@ drm_display_helper-$(CONFIG_DRM_DISPLAY_DSC_HELPER) += \
drm_display_helper-$(CONFIG_DRM_DISPLAY_HDCP_HELPER) += drm_hdcp_helper.o drm_display_helper-$(CONFIG_DRM_DISPLAY_HDCP_HELPER) += drm_hdcp_helper.o
drm_display_helper-$(CONFIG_DRM_DISPLAY_HDMI_AUDIO_HELPER) += \ drm_display_helper-$(CONFIG_DRM_DISPLAY_HDMI_AUDIO_HELPER) += \
drm_hdmi_audio_helper.o drm_hdmi_audio_helper.o
drm_display_helper-$(CONFIG_DRM_DISPLAY_HDMI_CEC_HELPER) += \
drm_hdmi_cec_helper.o
drm_display_helper-$(CONFIG_DRM_DISPLAY_HDMI_CEC_NOTIFIER_HELPER) += \
drm_hdmi_cec_notifier_helper.o
drm_display_helper-$(CONFIG_DRM_DISPLAY_HDMI_HELPER) += \ drm_display_helper-$(CONFIG_DRM_DISPLAY_HDMI_HELPER) += \
drm_hdmi_helper.o \ drm_hdmi_helper.o \
drm_scdc_helper.o drm_scdc_helper.o

View file

@ -20,6 +20,7 @@
#include <drm/drm_print.h> #include <drm/drm_print.h>
#include <drm/drm_probe_helper.h> #include <drm/drm_probe_helper.h>
#include <drm/display/drm_hdmi_audio_helper.h> #include <drm/display/drm_hdmi_audio_helper.h>
#include <drm/display/drm_hdmi_cec_helper.h>
#include <drm/display/drm_hdmi_helper.h> #include <drm/display/drm_hdmi_helper.h>
#include <drm/display/drm_hdmi_state_helper.h> #include <drm/display/drm_hdmi_state_helper.h>
@ -98,6 +99,7 @@ struct drm_bridge_connector {
* HDMI connector infrastructure, if any (see &DRM_BRIDGE_OP_HDMI). * HDMI connector infrastructure, if any (see &DRM_BRIDGE_OP_HDMI).
*/ */
struct drm_bridge *bridge_hdmi; struct drm_bridge *bridge_hdmi;
struct drm_bridge *bridge_hdmi_cec;
}; };
#define to_drm_bridge_connector(x) \ #define to_drm_bridge_connector(x) \
@ -534,6 +536,61 @@ static const struct drm_connector_hdmi_audio_funcs drm_bridge_connector_hdmi_aud
.shutdown = drm_bridge_connector_audio_shutdown, .shutdown = drm_bridge_connector_audio_shutdown,
.mute_stream = drm_bridge_connector_audio_mute_stream, .mute_stream = drm_bridge_connector_audio_mute_stream,
}; };
static int drm_bridge_connector_hdmi_cec_enable(struct drm_connector *connector,
bool enable)
{
struct drm_bridge_connector *bridge_connector =
to_drm_bridge_connector(connector);
struct drm_bridge *bridge = bridge_connector->bridge_hdmi_cec;
return bridge->funcs->hdmi_cec_enable(bridge, enable);
}
static int drm_bridge_connector_hdmi_cec_log_addr(
struct drm_connector *connector, u8 logical_addr)
{
struct drm_bridge_connector *bridge_connector =
to_drm_bridge_connector(connector);
struct drm_bridge *bridge = bridge_connector->bridge_hdmi_cec;
return bridge->funcs->hdmi_cec_log_addr(bridge, logical_addr);
}
static int drm_bridge_connector_hdmi_cec_transmit(
struct drm_connector *connector, u8 attempts,
u32 signal_free_time, struct cec_msg *msg)
{
struct drm_bridge_connector *bridge_connector =
to_drm_bridge_connector(connector);
struct drm_bridge *bridge = bridge_connector->bridge_hdmi_cec;
return bridge->funcs->hdmi_cec_transmit(bridge, attempts,
signal_free_time, msg);
}
static int drm_bridge_connector_hdmi_cec_init(struct drm_connector *connector)
{
struct drm_bridge_connector *bridge_connector =
to_drm_bridge_connector(connector);
struct drm_bridge *bridge = bridge_connector->bridge_hdmi_cec;
if (!bridge->funcs->hdmi_cec_init)
return 0;
return bridge->funcs->hdmi_cec_init(connector, bridge);
}
static const struct drm_connector_hdmi_cec_funcs
drm_bridge_connector_hdmi_cec_funcs = {
.init = drm_bridge_connector_hdmi_cec_init,
.enable = drm_bridge_connector_hdmi_cec_enable,
.log_addr = drm_bridge_connector_hdmi_cec_log_addr,
.transmit = drm_bridge_connector_hdmi_cec_transmit,
};
/* ----------------------------------------------------------------------------
* Bridge Connector Initialisation
*/
/* ----------------------------------------------------------------------------- /* -----------------------------------------------------------------------------
* Bridge Connector Initialisation * Bridge Connector Initialisation
@ -614,6 +671,20 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
if (bridge->max_bpc) if (bridge->max_bpc)
max_bpc = bridge->max_bpc; max_bpc = bridge->max_bpc;
} }
if (bridge->ops & DRM_BRIDGE_OP_HDMI_CEC_NOTIFIER) {
if (bridge_connector->bridge_hdmi_cec)
return ERR_PTR(-EBUSY);
bridge_connector->bridge_hdmi_cec = bridge;
}
if (bridge->ops & DRM_BRIDGE_OP_HDMI_CEC_ADAPTER) {
if (bridge_connector->bridge_hdmi_cec)
return ERR_PTR(-EBUSY);
if (!bridge->funcs->hdmi_cec_enable ||
!bridge->funcs->hdmi_cec_log_addr ||
!bridge->funcs->hdmi_cec_transmit)
return ERR_PTR(-EINVAL);
bridge_connector->bridge_hdmi_cec = bridge;
}
if (!drm_bridge_get_next_bridge(bridge)) if (!drm_bridge_get_next_bridge(bridge))
connector_type = bridge->type; connector_type = bridge->type;
@ -673,6 +744,19 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
if (ret) if (ret)
return ERR_PTR(ret); return ERR_PTR(ret);
} }
if (bridge_connector->bridge_hdmi_cec) {
bridge = bridge_connector->bridge_hdmi_cec;
if (bridge->ops & DRM_BRIDGE_OP_HDMI_CEC_ADAPTER)
ret = drm_connector_hdmi_cec_register(connector,
&drm_bridge_connector_hdmi_cec_funcs,
bridge->hdmi_cec_adapter_name, 0,
bridge->hdmi_cec_dev);
else
ret = drm_connector_hdmi_cec_notifier_register(connector, NULL,
bridge->hdmi_cec_dev);
if (ret)
return ERR_PTR(ret);
}
drm_connector_helper_add(connector, &drm_bridge_connector_helper_funcs); drm_connector_helper_add(connector, &drm_bridge_connector_helper_funcs);

View file

@ -0,0 +1,179 @@
// SPDX-License-Identifier: MIT
/*
* Copyright (c) 2024 Linaro Ltd
*/
#include <drm/drm_bridge.h>
#include <drm/drm_connector.h>
#include <drm/display/drm_hdmi_cec_helper.h>
#include <linux/mutex.h>
#include <media/cec.h>
struct drm_connector_hdmi_cec_data {
struct cec_adapter *adapter;
const struct drm_connector_hdmi_cec_funcs *funcs;
};
static int drm_connector_hdmi_cec_adap_enable(struct cec_adapter *adap,
bool enable)
{
struct drm_connector *connector = cec_get_drvdata(adap);
struct drm_connector_hdmi_cec_data *data = connector->cec.data;
return data->funcs->enable(connector, enable);
}
static int drm_connector_hdmi_cec_adap_log_addr(struct cec_adapter *adap,
u8 logical_addr)
{
struct drm_connector *connector = cec_get_drvdata(adap);
struct drm_connector_hdmi_cec_data *data = connector->cec.data;
return data->funcs->log_addr(connector, logical_addr);
}
static int drm_connector_hdmi_cec_adap_transmit(struct cec_adapter *adap,
u8 attempts,
u32 signal_free_time,
struct cec_msg *msg)
{
struct drm_connector *connector = cec_get_drvdata(adap);
struct drm_connector_hdmi_cec_data *data = connector->cec.data;
return data->funcs->transmit(connector, attempts, signal_free_time, msg);
}
static const struct cec_adap_ops drm_connector_hdmi_cec_adap_ops = {
.adap_enable = drm_connector_hdmi_cec_adap_enable,
.adap_log_addr = drm_connector_hdmi_cec_adap_log_addr,
.adap_transmit = drm_connector_hdmi_cec_adap_transmit,
};
static void drm_connector_hdmi_cec_adapter_phys_addr_invalidate(
struct drm_connector *connector)
{
struct drm_connector_hdmi_cec_data *data = connector->cec.data;
cec_phys_addr_invalidate(data->adapter);
}
static void drm_connector_hdmi_cec_adapter_phys_addr_set(
struct drm_connector *connector, u16 addr)
{
struct drm_connector_hdmi_cec_data *data = connector->cec.data;
cec_s_phys_addr(data->adapter, addr, false);
}
static void drm_connector_hdmi_cec_adapter_unregister(
struct drm_connector *connector)
{
struct drm_connector_hdmi_cec_data *data = connector->cec.data;
cec_delete_adapter(data->adapter);
if (data->funcs->uninit)
data->funcs->uninit(connector);
kfree(data);
connector->cec.data = NULL;
}
static const struct drm_connector_cec_funcs
drm_connector_hdmi_cec_adapter_funcs = {
.phys_addr_invalidate = drm_connector_hdmi_cec_adapter_phys_addr_invalidate,
.phys_addr_set = drm_connector_hdmi_cec_adapter_phys_addr_set,
.unregister = drm_connector_hdmi_cec_adapter_unregister,
};
int drm_connector_hdmi_cec_register(
struct drm_connector *connector,
const struct drm_connector_hdmi_cec_funcs *funcs,
const char *name, u8 available_las, struct device *dev)
{
struct drm_connector_hdmi_cec_data *data;
struct cec_connector_info conn_info;
struct cec_adapter *cec_adap;
int ret;
if (!funcs->init || !funcs->enable || !funcs->log_addr ||
!funcs->transmit)
return -EINVAL;
data = kzalloc(sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->funcs = funcs;
cec_adap = cec_allocate_adapter(&drm_connector_hdmi_cec_adap_ops,
connector, name,
CEC_CAP_DEFAULTS | CEC_CAP_CONNECTOR_INFO,
available_las ? : CEC_MAX_LOG_ADDRS);
ret = PTR_ERR_OR_ZERO(cec_adap);
if (ret < 0)
goto err_free;
cec_fill_conn_info_from_drm(&conn_info, connector);
cec_s_conn_info(cec_adap, &conn_info);
data->adapter = cec_adap;
mutex_lock(&connector->cec.mutex);
connector->cec.data = data;
connector->cec.funcs = &drm_connector_hdmi_cec_adapter_funcs;
ret = funcs->init(connector);
if (ret < 0)
goto err_delete_adapter;
ret = cec_register_adapter(cec_adap, dev);
if (ret < 0)
goto err_delete_adapter;
mutex_unlock(&connector->cec.mutex);
return 0;
err_delete_adapter:
cec_delete_adapter(cec_adap);
connector->cec.data = NULL;
mutex_unlock(&connector->cec.mutex);
err_free:
kfree(data);
return ret;
}
EXPORT_SYMBOL(drm_connector_hdmi_cec_register);
void drm_connector_hdmi_cec_received_msg(struct drm_connector *connector,
struct cec_msg *msg)
{
struct drm_connector_hdmi_cec_data *data = connector->cec.data;
cec_received_msg(data->adapter, msg);
}
EXPORT_SYMBOL(drm_connector_hdmi_cec_received_msg);
void drm_connector_hdmi_cec_transmit_attempt_done(
struct drm_connector *connector, u8 status)
{
struct drm_connector_hdmi_cec_data *data = connector->cec.data;
cec_transmit_attempt_done(data->adapter, status);
}
EXPORT_SYMBOL(drm_connector_hdmi_cec_transmit_attempt_done);
void drm_connector_hdmi_cec_transmit_done(struct drm_connector *connector,
u8 status, u8 arb_lost_cnt,
u8 nack_cnt, u8 low_drive_cnt,
u8 error_cnt)
{
struct drm_connector_hdmi_cec_data *data = connector->cec.data;
cec_transmit_done(data->adapter, status, arb_lost_cnt, nack_cnt,
low_drive_cnt, error_cnt);
}
EXPORT_SYMBOL(drm_connector_hdmi_cec_transmit_done);

View file

@ -0,0 +1,59 @@
// SPDX-License-Identifier: MIT
/*
* Copyright (c) 2024 Linaro Ltd
*/
#include <drm/drm_bridge.h>
#include <drm/drm_connector.h>
#include <drm/display/drm_hdmi_cec_helper.h>
#include <linux/mutex.h>
#include <media/cec-notifier.h>
static void drm_connector_hdmi_cec_notifier_phys_addr_invalidate(
struct drm_connector *connector)
{
cec_notifier_phys_addr_invalidate(connector->cec.data);
}
static void drm_connector_hdmi_cec_notifier_phys_addr_set(
struct drm_connector *connector, u16 addr)
{
cec_notifier_set_phys_addr(connector->cec.data, addr);
}
static void drm_connector_hdmi_cec_notifier_unregister(
struct drm_connector *connector)
{
cec_notifier_conn_unregister(connector->cec.data);
connector->cec.data = NULL;
}
static const struct drm_connector_cec_funcs drm_connector_cec_notifier_funcs = {
.phys_addr_invalidate = drm_connector_hdmi_cec_notifier_phys_addr_invalidate,
.phys_addr_set = drm_connector_hdmi_cec_notifier_phys_addr_set,
.unregister = drm_connector_hdmi_cec_notifier_unregister,
};
int drm_connector_hdmi_cec_notifier_register(struct drm_connector *connector,
const char *port_name,
struct device *dev)
{
struct cec_connector_info conn_info;
struct cec_notifier *notifier;
cec_fill_conn_info_from_drm(&conn_info, connector);
notifier = cec_notifier_conn_register(dev, port_name, &conn_info);
if (!notifier)
return -ENOMEM;
mutex_lock(&connector->cec.mutex);
connector->cec.data = notifier;
connector->cec.funcs = &drm_connector_cec_notifier_funcs;
mutex_unlock(&connector->cec.mutex);
return 0;
}
EXPORT_SYMBOL(drm_connector_hdmi_cec_notifier_register);

View file

@ -6,6 +6,7 @@
#include <drm/drm_print.h> #include <drm/drm_print.h>
#include <drm/display/drm_hdmi_audio_helper.h> #include <drm/display/drm_hdmi_audio_helper.h>
#include <drm/display/drm_hdmi_cec_helper.h>
#include <drm/display/drm_hdmi_helper.h> #include <drm/display/drm_hdmi_helper.h>
#include <drm/display/drm_hdmi_state_helper.h> #include <drm/display/drm_hdmi_state_helper.h>
@ -789,9 +790,10 @@ drm_atomic_helper_connector_hdmi_update(struct drm_connector *connector,
const struct drm_edid *drm_edid; const struct drm_edid *drm_edid;
if (status == connector_status_disconnected) { if (status == connector_status_disconnected) {
// TODO: also handle CEC and scramber, HDMI sink disconnected. // TODO: also handle scramber, HDMI sink disconnected.
drm_connector_hdmi_audio_plugged_notify(connector, false); drm_connector_hdmi_audio_plugged_notify(connector, false);
drm_edid_connector_update(connector, NULL); drm_edid_connector_update(connector, NULL);
drm_connector_cec_phys_addr_invalidate(connector);
return; return;
} }
@ -805,8 +807,9 @@ drm_atomic_helper_connector_hdmi_update(struct drm_connector *connector,
drm_edid_free(drm_edid); drm_edid_free(drm_edid);
if (status == connector_status_connected) { if (status == connector_status_connected) {
// TODO: also handle CEC and scramber, HDMI sink is now connected. // TODO: also handle scramber, HDMI sink is now connected.
drm_connector_hdmi_audio_plugged_notify(connector, true); drm_connector_hdmi_audio_plugged_notify(connector, true);
drm_connector_cec_phys_addr_set(connector);
} }
} }

View file

@ -279,6 +279,7 @@ static int drm_connector_init_only(struct drm_device *dev,
INIT_LIST_HEAD(&connector->probed_modes); INIT_LIST_HEAD(&connector->probed_modes);
INIT_LIST_HEAD(&connector->modes); INIT_LIST_HEAD(&connector->modes);
mutex_init(&connector->mutex); mutex_init(&connector->mutex);
mutex_init(&connector->cec.mutex);
mutex_init(&connector->eld_mutex); mutex_init(&connector->eld_mutex);
mutex_init(&connector->edid_override_mutex); mutex_init(&connector->edid_override_mutex);
mutex_init(&connector->hdmi.infoframes.lock); mutex_init(&connector->hdmi.infoframes.lock);
@ -701,6 +702,34 @@ static void drm_mode_remove(struct drm_connector *connector,
drm_mode_destroy(connector->dev, mode); drm_mode_destroy(connector->dev, mode);
} }
void drm_connector_cec_phys_addr_invalidate(struct drm_connector *connector)
{
mutex_lock(&connector->cec.mutex);
if (connector->cec.funcs &&
connector->cec.funcs->phys_addr_invalidate)
connector->cec.funcs->phys_addr_invalidate(connector);
mutex_unlock(&connector->cec.mutex);
}
EXPORT_SYMBOL(drm_connector_cec_phys_addr_invalidate);
void drm_connector_cec_phys_addr_set(struct drm_connector *connector)
{
u16 addr;
mutex_lock(&connector->cec.mutex);
addr = connector->display_info.source_physical_address;
if (connector->cec.funcs && connector->cec.funcs->phys_addr_set)
connector->cec.funcs->phys_addr_set(connector, addr);
mutex_unlock(&connector->cec.mutex);
}
EXPORT_SYMBOL(drm_connector_cec_phys_addr_set);
static void drm_connector_cec_unregister(struct drm_connector *connector)
{
if (connector->cec.funcs && connector->cec.funcs->unregister)
connector->cec.funcs->unregister(connector);
}
/** /**
* drm_connector_cleanup - cleans up an initialised connector * drm_connector_cleanup - cleans up an initialised connector
* @connector: connector to cleanup * @connector: connector to cleanup
@ -720,6 +749,7 @@ void drm_connector_cleanup(struct drm_connector *connector)
drm_connector_unregister(connector); drm_connector_unregister(connector);
platform_device_unregister(connector->hdmi_audio.codec_pdev); platform_device_unregister(connector->hdmi_audio.codec_pdev);
drm_connector_cec_unregister(connector);
if (connector->privacy_screen) { if (connector->privacy_screen) {
drm_privacy_screen_put(connector->privacy_screen); drm_privacy_screen_put(connector->privacy_screen);
@ -759,6 +789,7 @@ void drm_connector_cleanup(struct drm_connector *connector)
mutex_destroy(&connector->hdmi_audio.lock); mutex_destroy(&connector->hdmi_audio.lock);
mutex_destroy(&connector->hdmi.infoframes.lock); mutex_destroy(&connector->hdmi.infoframes.lock);
mutex_destroy(&connector->cec.mutex);
mutex_destroy(&connector->mutex); mutex_destroy(&connector->mutex);
memset(connector, 0, sizeof(*connector)); memset(connector, 0, sizeof(*connector));

View file

@ -436,15 +436,15 @@ static int dw_hdmi_qp_rockchip_bind(struct device *dev, struct device *master,
{ {
struct platform_device *pdev = to_platform_device(dev); struct platform_device *pdev = to_platform_device(dev);
const struct rockchip_hdmi_qp_cfg *cfg; const struct rockchip_hdmi_qp_cfg *cfg;
struct dw_hdmi_qp_plat_data plat_data; struct dw_hdmi_qp_plat_data plat_data = {};
struct drm_device *drm = data; struct drm_device *drm = data;
//struct drm_connector *connector;
struct drm_encoder *encoder; struct drm_encoder *encoder;
struct rockchip_hdmi_qp *hdmi; struct rockchip_hdmi_qp *hdmi;
struct resource *res; struct resource *res;
struct clk_bulk_data *clks; struct clk_bulk_data *clks;
//int ret, irq, i; struct clk *ref_clk;
int ret, hpd_irq, i; int ret, hpd_irq, i;
int cec_irq;
if (!pdev->dev.of_node) if (!pdev->dev.of_node)
return -ENODEV; return -ENODEV;
@ -519,6 +519,11 @@ static int dw_hdmi_qp_rockchip_bind(struct device *dev, struct device *master,
dev_err(hdmi->dev, "Failed to get clocks: %d\n", ret); dev_err(hdmi->dev, "Failed to get clocks: %d\n", ret);
return ret; return ret;
} }
ref_clk = clk_get(hdmi->dev, "ref");
if (IS_ERR(ref_clk))
return PTR_ERR(ref_clk);
plat_data.ref_clk_rate = clk_get_rate(ref_clk);
clk_put(ref_clk);
hdmi->enable_gpio = devm_gpiod_get_optional(hdmi->dev, "enable", hdmi->enable_gpio = devm_gpiod_get_optional(hdmi->dev, "enable",
GPIOD_OUT_HIGH); GPIOD_OUT_HIGH);
@ -538,6 +543,10 @@ static int dw_hdmi_qp_rockchip_bind(struct device *dev, struct device *master,
cfg->ctrl_ops->io_init(hdmi); cfg->ctrl_ops->io_init(hdmi);
cec_irq = platform_get_irq_byname(pdev, "cec");
if (cec_irq < 0)
return cec_irq;
plat_data.cec_irq = cec_irq;
INIT_DELAYED_WORK(&hdmi->hpd_work, dw_hdmi_qp_rk3588_hpd_work); INIT_DELAYED_WORK(&hdmi->hpd_work, dw_hdmi_qp_rk3588_hpd_work);
plat_data.main_irq = platform_get_irq_byname(pdev, "main"); plat_data.main_irq = platform_get_irq_byname(pdev, "main");

View file

@ -23,6 +23,8 @@ struct dw_hdmi_qp_plat_data {
const struct dw_hdmi_qp_phy_ops *phy_ops; const struct dw_hdmi_qp_phy_ops *phy_ops;
void *phy_data; void *phy_data;
int main_irq; int main_irq;
int cec_irq;
unsigned long ref_clk_rate;
}; };
struct dw_hdmi_qp *dw_hdmi_qp_bind(struct platform_device *pdev, struct dw_hdmi_qp *dw_hdmi_qp_bind(struct platform_device *pdev,

View file

@ -0,0 +1,49 @@
/* SPDX-License-Identifier: MIT */
#ifndef DRM_DISPLAY_HDMI_CEC_HELPER
#define DRM_DISPLAY_HDMI_CEC_HELPER
#include <linux/types.h>
struct drm_connector;
struct cec_msg;
struct device;
struct drm_connector_hdmi_cec_funcs {
int (*init)(struct drm_connector *connector);
void (*uninit)(struct drm_connector *connector);
int (*enable)(struct drm_connector *connector, bool enable);
int (*log_addr)(struct drm_connector *connector, u8 logical_addr);
int (*transmit)(struct drm_connector *connector, u8 attempts,
u32 signal_free_time, struct cec_msg *msg);
};
int drm_connector_hdmi_cec_register(
struct drm_connector *connector,
const struct drm_connector_hdmi_cec_funcs *funcs,
const char *name, u8 available_las, struct device *dev);
void drm_connector_hdmi_cec_received_msg(struct drm_connector *connector,
struct cec_msg *msg);
void drm_connector_hdmi_cec_transmit_done(struct drm_connector *connector,
u8 status, u8 arb_lost_cnt,
u8 nack_cnt, u8 low_drive_cnt,
u8 error_cnt);
void drm_connector_hdmi_cec_transmit_attempt_done(
struct drm_connector *connector, u8 status);
#if IS_ENABLED(CONFIG_DRM_DISPLAY_HDMI_CEC_NOTIFIER_HELPER)
int drm_connector_hdmi_cec_notifier_register(struct drm_connector *connector,
const char *port_name,
struct device *dev);
#else
static inline int drm_connector_hdmi_cec_notifier_register(
struct drm_connector *connector,
const char *port_name,
struct device *dev)
{
return 0;
}
#endif
#endif

View file

@ -41,6 +41,7 @@ struct drm_display_info;
struct drm_minor; struct drm_minor;
struct drm_panel; struct drm_panel;
struct edid; struct edid;
struct cec_msg;
struct hdmi_codec_daifmt; struct hdmi_codec_daifmt;
struct hdmi_codec_params; struct hdmi_codec_params;
struct i2c_adapter; struct i2c_adapter;
@ -758,6 +759,19 @@ struct drm_bridge_funcs {
struct drm_bridge *bridge, struct drm_bridge *bridge,
bool enable, int direction); bool enable, int direction);
/**
* @hdmi_cec_init: initialize the bridge CEC hardware for a connector.
*/
int (*hdmi_cec_init)(struct drm_connector *connector,
struct drm_bridge *bridge);
/** @hdmi_cec_enable: enable or disable CEC. */
int (*hdmi_cec_enable)(struct drm_bridge *bridge, bool enable);
/** @hdmi_cec_log_addr: program a CEC logical address. */
int (*hdmi_cec_log_addr)(struct drm_bridge *bridge, u8 logical_addr);
/** @hdmi_cec_transmit: start a CEC message transmission. */
int (*hdmi_cec_transmit)(struct drm_bridge *bridge, u8 attempts,
u32 signal_free_time, struct cec_msg *msg);
/** /**
* @debugfs_init: * @debugfs_init:
* *
@ -843,6 +857,8 @@ enum drm_bridge_ops {
* drivers. * drivers.
*/ */
DRM_BRIDGE_OP_HDMI = BIT(4), DRM_BRIDGE_OP_HDMI = BIT(4),
DRM_BRIDGE_OP_HDMI_CEC_NOTIFIER = BIT(5),
DRM_BRIDGE_OP_HDMI_CEC_ADAPTER = BIT(6),
}; };
/** /**
@ -962,6 +978,11 @@ struct drm_bridge {
* @hdmi_audio_dai_port: sound DAI port, -1 if it is not enabled * @hdmi_audio_dai_port: sound DAI port, -1 if it is not enabled
*/ */
int hdmi_audio_dai_port; int hdmi_audio_dai_port;
/** @hdmi_cec_dev: device to use as the CEC adapter parent. */
struct device *hdmi_cec_dev;
/** @hdmi_cec_adapter_name: name of the CEC adapter. */
const char *hdmi_cec_adapter_name;
}; };
static inline struct drm_bridge * static inline struct drm_bridge *

View file

@ -1191,6 +1191,15 @@ struct drm_connector_hdmi_audio_funcs {
bool enable, int direction); bool enable, int direction);
}; };
void drm_connector_cec_phys_addr_invalidate(struct drm_connector *connector);
void drm_connector_cec_phys_addr_set(struct drm_connector *connector);
struct drm_connector_cec_funcs {
void (*phys_addr_invalidate)(struct drm_connector *connector);
void (*phys_addr_set)(struct drm_connector *connector, u16 addr);
void (*unregister)(struct drm_connector *connector);
};
/** /**
* struct drm_connector_hdmi_funcs - drm_hdmi_connector control functions * struct drm_connector_hdmi_funcs - drm_hdmi_connector control functions
*/ */
@ -1832,6 +1841,12 @@ struct drm_connector_hdmi {
} infoframes; } infoframes;
}; };
struct drm_connector_cec {
struct mutex mutex;
const struct drm_connector_cec_funcs *funcs;
void *data;
};
/** /**
* struct drm_connector - central DRM connector control structure * struct drm_connector - central DRM connector control structure
* *
@ -2253,6 +2268,9 @@ struct drm_connector {
* @hdmi_audio: HDMI codec properties and non-DRM state. * @hdmi_audio: HDMI codec properties and non-DRM state.
*/ */
struct drm_connector_hdmi_audio hdmi_audio; struct drm_connector_hdmi_audio hdmi_audio;
/** @cec: CEC-related data. */
struct drm_connector_cec cec;
}; };
#define obj_to_connector(x) container_of(x, struct drm_connector, base) #define obj_to_connector(x) container_of(x, struct drm_connector, base)