diff --git a/arch/arm64/configs/tart_defconfig b/arch/arm64/configs/tart_defconfig index 0f8250eea..9262c8302 100644 --- a/arch/arm64/configs/tart_defconfig +++ b/arch/arm64/configs/tart_defconfig @@ -6012,6 +6012,7 @@ CONFIG_VIDEO_NOMODESET=y CONFIG_DRM=y #CONFIG_DRM_MIPI_DBI=m CONFIG_DRM_DW_HDMI_QP=y +CONFIG_DRM_DW_HDMI_QP_CEC=y CONFIG_DRM_DW_DP=y CONFIG_DRM_MIPI_DSI=y CONFIG_DRM_EDID=y diff --git a/drivers/gpu/drm/bridge/synopsys/Kconfig b/drivers/gpu/drm/bridge/synopsys/Kconfig index 895906697..e12264b8d 100644 --- a/drivers/gpu/drm/bridge/synopsys/Kconfig +++ b/drivers/gpu/drm/bridge/synopsys/Kconfig @@ -61,6 +61,14 @@ config DRM_DW_HDMI_QP select DRM_KMS_HELPER 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 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 b81c23e0a..3a147e263 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 #include +#include #include #include #include @@ -28,6 +29,7 @@ #include #include +#include #include "dw-hdmi-qp.h" @@ -136,6 +138,18 @@ 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; @@ -147,11 +161,16 @@ struct dw_hdmi_qp { void *data; } phy; +#ifdef CONFIG_DRM_DW_HDMI_QP_CEC + struct dw_hdmi_qp_cec *cec; +#endif + struct drm_connector *curr_conn; struct delayed_work scramb_work; bool scramb_enabled; struct regmap *regm; + unsigned long ref_clk_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 = { .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_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_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) @@ -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_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 */ 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.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); @@ -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_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); 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 72987e6c4..3008fdba9 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/drivers/gpu/drm/display/Kconfig b/drivers/gpu/drm/display/Kconfig index 8d22b7627..3ea9cdf1d 100644 --- a/drivers/gpu/drm/display/Kconfig +++ b/drivers/gpu/drm/display/Kconfig @@ -8,9 +8,11 @@ config DRM_DISPLAY_DP_AUX_BUS config DRM_DISPLAY_HELPER tristate depends on DRM + select CEC_CORE if DRM_DISPLAY_DP_AUX_CEC || DRM_DISPLAY_HDMI_CEC_HELPER || CEC_NOTIFIER help DRM helpers for display adapters. + if DRM_DISPLAY_HELPER config DRM_BRIDGE_CONNECTOR @@ -82,6 +84,16 @@ config DRM_DISPLAY_HDMI_AUDIO_HELPER DRM display helpers for HDMI Audio functionality (generic HDMI Codec 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 bool help diff --git a/drivers/gpu/drm/display/Makefile b/drivers/gpu/drm/display/Makefile index b17879b95..0ff4a1ad0 100644 --- a/drivers/gpu/drm/display/Makefile +++ b/drivers/gpu/drm/display/Makefile @@ -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_HDMI_AUDIO_HELPER) += \ 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_hdmi_helper.o \ drm_scdc_helper.o diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c index 74b83a0f0..0aeabbc52 100644 --- a/drivers/gpu/drm/display/drm_bridge_connector.c +++ b/drivers/gpu/drm/display/drm_bridge_connector.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -98,6 +99,7 @@ struct drm_bridge_connector { * HDMI connector infrastructure, if any (see &DRM_BRIDGE_OP_HDMI). */ struct drm_bridge *bridge_hdmi; + struct drm_bridge *bridge_hdmi_cec; }; #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, .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 @@ -614,6 +671,20 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm, if (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)) connector_type = bridge->type; @@ -673,6 +744,19 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm, if (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); diff --git a/drivers/gpu/drm/display/drm_hdmi_cec_helper.c b/drivers/gpu/drm/display/drm_hdmi_cec_helper.c new file mode 100644 index 000000000..4887a3bb4 --- /dev/null +++ b/drivers/gpu/drm/display/drm_hdmi_cec_helper.c @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (c) 2024 Linaro Ltd + */ + +#include +#include +#include + +#include + +#include + +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); diff --git a/drivers/gpu/drm/display/drm_hdmi_cec_notifier_helper.c b/drivers/gpu/drm/display/drm_hdmi_cec_notifier_helper.c new file mode 100644 index 000000000..8a918dc67 --- /dev/null +++ b/drivers/gpu/drm/display/drm_hdmi_cec_notifier_helper.c @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (c) 2024 Linaro Ltd + */ + +#include +#include +#include + +#include + +#include + +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); diff --git a/drivers/gpu/drm/display/drm_hdmi_state_helper.c b/drivers/gpu/drm/display/drm_hdmi_state_helper.c index c205f37da..039e654dd 100644 --- a/drivers/gpu/drm/display/drm_hdmi_state_helper.c +++ b/drivers/gpu/drm/display/drm_hdmi_state_helper.c @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -789,9 +790,10 @@ drm_atomic_helper_connector_hdmi_update(struct drm_connector *connector, const struct drm_edid *drm_edid; 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_edid_connector_update(connector, NULL); + drm_connector_cec_phys_addr_invalidate(connector); return; } @@ -805,8 +807,9 @@ drm_atomic_helper_connector_hdmi_update(struct drm_connector *connector, drm_edid_free(drm_edid); 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_cec_phys_addr_set(connector); } } diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c index 48b08c961..9d7116b41 100644 --- a/drivers/gpu/drm/drm_connector.c +++ b/drivers/gpu/drm/drm_connector.c @@ -279,6 +279,7 @@ static int drm_connector_init_only(struct drm_device *dev, INIT_LIST_HEAD(&connector->probed_modes); INIT_LIST_HEAD(&connector->modes); mutex_init(&connector->mutex); + mutex_init(&connector->cec.mutex); mutex_init(&connector->eld_mutex); mutex_init(&connector->edid_override_mutex); 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); } +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 * @connector: connector to cleanup @@ -720,6 +749,7 @@ void drm_connector_cleanup(struct drm_connector *connector) drm_connector_unregister(connector); platform_device_unregister(connector->hdmi_audio.codec_pdev); + drm_connector_cec_unregister(connector); if (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.infoframes.lock); + mutex_destroy(&connector->cec.mutex); mutex_destroy(&connector->mutex); memset(connector, 0, sizeof(*connector)); diff --git a/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c index 9559b22ff..be6ddfa61 100644 --- a/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c +++ b/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c @@ -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); 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_connector *connector; struct drm_encoder *encoder; struct rockchip_hdmi_qp *hdmi; struct resource *res; struct clk_bulk_data *clks; - //int ret, irq, i; + struct clk *ref_clk; int ret, hpd_irq, i; + int cec_irq; if (!pdev->dev.of_node) 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); 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", 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); + 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); plat_data.main_irq = platform_get_irq_byname(pdev, "main"); diff --git a/include/drm/bridge/dw_hdmi_qp.h b/include/drm/bridge/dw_hdmi_qp.h index e9be6d507..76ecf3130 100644 --- a/include/drm/bridge/dw_hdmi_qp.h +++ b/include/drm/bridge/dw_hdmi_qp.h @@ -23,6 +23,8 @@ struct dw_hdmi_qp_plat_data { const struct dw_hdmi_qp_phy_ops *phy_ops; void *phy_data; int main_irq; + int cec_irq; + unsigned long ref_clk_rate; }; struct dw_hdmi_qp *dw_hdmi_qp_bind(struct platform_device *pdev, diff --git a/include/drm/display/drm_hdmi_cec_helper.h b/include/drm/display/drm_hdmi_cec_helper.h new file mode 100644 index 000000000..696f3e6e4 --- /dev/null +++ b/include/drm/display/drm_hdmi_cec_helper.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: MIT */ + +#ifndef DRM_DISPLAY_HDMI_CEC_HELPER +#define DRM_DISPLAY_HDMI_CEC_HELPER + +#include + +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 diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h index d2daf443a..039aa0b13 100644 --- a/include/drm/drm_bridge.h +++ b/include/drm/drm_bridge.h @@ -41,6 +41,7 @@ struct drm_display_info; struct drm_minor; struct drm_panel; struct edid; +struct cec_msg; struct hdmi_codec_daifmt; struct hdmi_codec_params; struct i2c_adapter; @@ -758,6 +759,19 @@ struct drm_bridge_funcs { struct drm_bridge *bridge, 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: * @@ -843,6 +857,8 @@ enum drm_bridge_ops { * drivers. */ 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 */ 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 * diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h index f13d59737..3d5d4861e 100644 --- a/include/drm/drm_connector.h +++ b/include/drm/drm_connector.h @@ -1191,6 +1191,15 @@ struct drm_connector_hdmi_audio_funcs { 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 */ @@ -1832,6 +1841,12 @@ struct drm_connector_hdmi { } 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 * @@ -2253,6 +2268,9 @@ struct drm_connector { * @hdmi_audio: HDMI codec properties and non-DRM state. */ 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)