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:
parent
10cf76215c
commit
9a769db9af
16 changed files with 704 additions and 6 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
||||||
179
drivers/gpu/drm/display/drm_hdmi_cec_helper.c
Normal file
179
drivers/gpu/drm/display/drm_hdmi_cec_helper.c
Normal 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);
|
||||||
59
drivers/gpu/drm/display/drm_hdmi_cec_notifier_helper.c
Normal file
59
drivers/gpu/drm/display/drm_hdmi_cec_notifier_helper.c
Normal 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);
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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));
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
49
include/drm/display/drm_hdmi_cec_helper.h
Normal file
49
include/drm/display/drm_hdmi_cec_helper.h
Normal 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
|
||||||
|
|
@ -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 *
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue