Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

drm/bridge: dw-hdmi-qp: Add CEC support

Add support for the CEC interface of the Synopsys DesignWare HDMI QP TX
controller.

This is based on the downstream implementation, but rewritten on top of
the CEC helpers added recently to the DRM HDMI connector framework.

Also note struct dw_hdmi_qp_plat_data has been extended to include the
CEC IRQ number to be provided by the platform driver.

Co-developed-by: Algea Cao <algea.cao@rock-chips.com>
Signed-off-by: Algea Cao <algea.cao@rock-chips.com>
Co-developed-by: Derek Foreman <derek.foreman@collabora.com>
Signed-off-by: Derek Foreman <derek.foreman@collabora.com>
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
Signed-off-by: Heiko Stuebner <heiko@sntech.de>
Link: https://lore.kernel.org/r/20250903-rk3588-hdmi-cec-v4-1-fa25163c4b08@collabora.com

authored by

Cristian Ciocaltea and committed by
Heiko Stuebner
e4a2d54a e485883c

+235
+8
drivers/gpu/drm/bridge/synopsys/Kconfig
··· 61 61 select DRM_KMS_HELPER 62 62 select REGMAP_MMIO 63 63 64 + config DRM_DW_HDMI_QP_CEC 65 + bool "Synopsis Designware QP CEC interface" 66 + depends on DRM_DW_HDMI_QP 67 + select DRM_DISPLAY_HDMI_CEC_HELPER 68 + help 69 + Support the CEC interface which is part of the Synopsys 70 + Designware HDMI QP block. 71 + 64 72 config DRM_DW_MIPI_DSI 65 73 tristate 66 74 select DRM_KMS_HELPER
+212
drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c
··· 18 18 19 19 #include <drm/bridge/dw_hdmi_qp.h> 20 20 #include <drm/display/drm_hdmi_helper.h> 21 + #include <drm/display/drm_hdmi_cec_helper.h> 21 22 #include <drm/display/drm_hdmi_state_helper.h> 22 23 #include <drm/drm_atomic.h> 23 24 #include <drm/drm_atomic_helper.h> ··· 26 25 #include <drm/drm_connector.h> 27 26 #include <drm/drm_edid.h> 28 27 #include <drm/drm_modes.h> 28 + 29 + #include <media/cec.h> 29 30 30 31 #include <sound/hdmi-codec.h> 31 32 ··· 134 131 bool is_segment; 135 132 }; 136 133 134 + #ifdef CONFIG_DRM_DW_HDMI_QP_CEC 135 + struct dw_hdmi_qp_cec { 136 + struct drm_connector *connector; 137 + int irq; 138 + u32 addresses; 139 + struct cec_msg rx_msg; 140 + u8 tx_status; 141 + bool tx_done; 142 + bool rx_done; 143 + }; 144 + #endif 145 + 137 146 struct dw_hdmi_qp { 138 147 struct drm_bridge bridge; 139 148 140 149 struct device *dev; 141 150 struct dw_hdmi_qp_i2c *i2c; 151 + 152 + #ifdef CONFIG_DRM_DW_HDMI_QP_CEC 153 + struct dw_hdmi_qp_cec *cec; 154 + #endif 142 155 143 156 struct { 144 157 const struct dw_hdmi_qp_phy_ops *ops; ··· 984 965 } 985 966 } 986 967 968 + #ifdef CONFIG_DRM_DW_HDMI_QP_CEC 969 + static irqreturn_t dw_hdmi_qp_cec_hardirq(int irq, void *dev_id) 970 + { 971 + struct dw_hdmi_qp *hdmi = dev_id; 972 + struct dw_hdmi_qp_cec *cec = hdmi->cec; 973 + irqreturn_t ret = IRQ_HANDLED; 974 + u32 stat; 975 + 976 + stat = dw_hdmi_qp_read(hdmi, CEC_INT_STATUS); 977 + if (stat == 0) 978 + return IRQ_NONE; 979 + 980 + dw_hdmi_qp_write(hdmi, stat, CEC_INT_CLEAR); 981 + 982 + if (stat & CEC_STAT_LINE_ERR) { 983 + cec->tx_status = CEC_TX_STATUS_ERROR; 984 + cec->tx_done = true; 985 + ret = IRQ_WAKE_THREAD; 986 + } else if (stat & CEC_STAT_DONE) { 987 + cec->tx_status = CEC_TX_STATUS_OK; 988 + cec->tx_done = true; 989 + ret = IRQ_WAKE_THREAD; 990 + } else if (stat & CEC_STAT_NACK) { 991 + cec->tx_status = CEC_TX_STATUS_NACK; 992 + cec->tx_done = true; 993 + ret = IRQ_WAKE_THREAD; 994 + } 995 + 996 + if (stat & CEC_STAT_EOM) { 997 + unsigned int len, i, val; 998 + 999 + val = dw_hdmi_qp_read(hdmi, CEC_RX_COUNT_STATUS); 1000 + len = (val & 0xf) + 1; 1001 + 1002 + if (len > sizeof(cec->rx_msg.msg)) 1003 + len = sizeof(cec->rx_msg.msg); 1004 + 1005 + for (i = 0; i < 4; i++) { 1006 + val = dw_hdmi_qp_read(hdmi, CEC_RX_DATA3_0 + i * 4); 1007 + cec->rx_msg.msg[i * 4] = val & 0xff; 1008 + cec->rx_msg.msg[i * 4 + 1] = (val >> 8) & 0xff; 1009 + cec->rx_msg.msg[i * 4 + 2] = (val >> 16) & 0xff; 1010 + cec->rx_msg.msg[i * 4 + 3] = (val >> 24) & 0xff; 1011 + } 1012 + 1013 + dw_hdmi_qp_write(hdmi, 1, CEC_LOCK_CONTROL); 1014 + 1015 + cec->rx_msg.len = len; 1016 + cec->rx_done = true; 1017 + 1018 + ret = IRQ_WAKE_THREAD; 1019 + } 1020 + 1021 + return ret; 1022 + } 1023 + 1024 + static irqreturn_t dw_hdmi_qp_cec_thread(int irq, void *dev_id) 1025 + { 1026 + struct dw_hdmi_qp *hdmi = dev_id; 1027 + struct dw_hdmi_qp_cec *cec = hdmi->cec; 1028 + 1029 + if (cec->tx_done) { 1030 + cec->tx_done = false; 1031 + drm_connector_hdmi_cec_transmit_attempt_done(cec->connector, 1032 + cec->tx_status); 1033 + } 1034 + 1035 + if (cec->rx_done) { 1036 + cec->rx_done = false; 1037 + drm_connector_hdmi_cec_received_msg(cec->connector, &cec->rx_msg); 1038 + } 1039 + 1040 + return IRQ_HANDLED; 1041 + } 1042 + 1043 + static int dw_hdmi_qp_cec_init(struct drm_bridge *bridge, 1044 + struct drm_connector *connector) 1045 + { 1046 + struct dw_hdmi_qp *hdmi = dw_hdmi_qp_from_bridge(bridge); 1047 + struct dw_hdmi_qp_cec *cec = hdmi->cec; 1048 + 1049 + cec->connector = connector; 1050 + 1051 + dw_hdmi_qp_write(hdmi, 0, CEC_TX_COUNT); 1052 + dw_hdmi_qp_write(hdmi, ~0, CEC_INT_CLEAR); 1053 + dw_hdmi_qp_write(hdmi, 0, CEC_INT_MASK_N); 1054 + 1055 + return devm_request_threaded_irq(hdmi->dev, cec->irq, 1056 + dw_hdmi_qp_cec_hardirq, 1057 + dw_hdmi_qp_cec_thread, IRQF_SHARED, 1058 + dev_name(hdmi->dev), hdmi); 1059 + } 1060 + 1061 + static int dw_hdmi_qp_cec_log_addr(struct drm_bridge *bridge, u8 logical_addr) 1062 + { 1063 + struct dw_hdmi_qp *hdmi = dw_hdmi_qp_from_bridge(bridge); 1064 + struct dw_hdmi_qp_cec *cec = hdmi->cec; 1065 + 1066 + if (logical_addr == CEC_LOG_ADDR_INVALID) 1067 + cec->addresses = 0; 1068 + else 1069 + cec->addresses |= BIT(logical_addr) | CEC_ADDR_BROADCAST; 1070 + 1071 + dw_hdmi_qp_write(hdmi, cec->addresses, CEC_ADDR); 1072 + 1073 + return 0; 1074 + } 1075 + 1076 + static int dw_hdmi_qp_cec_enable(struct drm_bridge *bridge, bool enable) 1077 + { 1078 + struct dw_hdmi_qp *hdmi = dw_hdmi_qp_from_bridge(bridge); 1079 + unsigned int irqs; 1080 + u32 swdisable; 1081 + 1082 + if (!enable) { 1083 + dw_hdmi_qp_write(hdmi, 0, CEC_INT_MASK_N); 1084 + dw_hdmi_qp_write(hdmi, ~0, CEC_INT_CLEAR); 1085 + 1086 + swdisable = dw_hdmi_qp_read(hdmi, GLOBAL_SWDISABLE); 1087 + swdisable = swdisable | CEC_SWDISABLE; 1088 + dw_hdmi_qp_write(hdmi, swdisable, GLOBAL_SWDISABLE); 1089 + } else { 1090 + swdisable = dw_hdmi_qp_read(hdmi, GLOBAL_SWDISABLE); 1091 + swdisable = swdisable & ~CEC_SWDISABLE; 1092 + dw_hdmi_qp_write(hdmi, swdisable, GLOBAL_SWDISABLE); 1093 + 1094 + dw_hdmi_qp_write(hdmi, ~0, CEC_INT_CLEAR); 1095 + dw_hdmi_qp_write(hdmi, 1, CEC_LOCK_CONTROL); 1096 + 1097 + dw_hdmi_qp_cec_log_addr(bridge, CEC_LOG_ADDR_INVALID); 1098 + 1099 + irqs = CEC_STAT_LINE_ERR | CEC_STAT_NACK | CEC_STAT_EOM | 1100 + CEC_STAT_DONE; 1101 + dw_hdmi_qp_write(hdmi, ~0, CEC_INT_CLEAR); 1102 + dw_hdmi_qp_write(hdmi, irqs, CEC_INT_MASK_N); 1103 + } 1104 + 1105 + return 0; 1106 + } 1107 + 1108 + static int dw_hdmi_qp_cec_transmit(struct drm_bridge *bridge, u8 attempts, 1109 + u32 signal_free_time, struct cec_msg *msg) 1110 + { 1111 + struct dw_hdmi_qp *hdmi = dw_hdmi_qp_from_bridge(bridge); 1112 + unsigned int i; 1113 + u32 val; 1114 + 1115 + for (i = 0; i < msg->len; i++) { 1116 + if (!(i % 4)) 1117 + val = msg->msg[i]; 1118 + if ((i % 4) == 1) 1119 + val |= msg->msg[i] << 8; 1120 + if ((i % 4) == 2) 1121 + val |= msg->msg[i] << 16; 1122 + if ((i % 4) == 3) 1123 + val |= msg->msg[i] << 24; 1124 + 1125 + if (i == (msg->len - 1) || (i % 4) == 3) 1126 + dw_hdmi_qp_write(hdmi, val, CEC_TX_DATA3_0 + (i / 4) * 4); 1127 + } 1128 + 1129 + dw_hdmi_qp_write(hdmi, msg->len - 1, CEC_TX_COUNT); 1130 + dw_hdmi_qp_write(hdmi, CEC_CTRL_START, CEC_TX_CONTROL); 1131 + 1132 + return 0; 1133 + } 1134 + #else 1135 + #define dw_hdmi_qp_cec_init NULL 1136 + #define dw_hdmi_qp_cec_enable NULL 1137 + #define dw_hdmi_qp_cec_log_addr NULL 1138 + #define dw_hdmi_qp_cec_transmit NULL 1139 + #endif /* CONFIG_DRM_DW_HDMI_QP_CEC */ 1140 + 987 1141 static const struct drm_bridge_funcs dw_hdmi_qp_bridge_funcs = { 988 1142 .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, 989 1143 .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, ··· 1171 979 .hdmi_audio_startup = dw_hdmi_qp_audio_enable, 1172 980 .hdmi_audio_shutdown = dw_hdmi_qp_audio_disable, 1173 981 .hdmi_audio_prepare = dw_hdmi_qp_audio_prepare, 982 + .hdmi_cec_init = dw_hdmi_qp_cec_init, 983 + .hdmi_cec_enable = dw_hdmi_qp_cec_enable, 984 + .hdmi_cec_log_addr = dw_hdmi_qp_cec_log_addr, 985 + .hdmi_cec_transmit = dw_hdmi_qp_cec_transmit, 1174 986 }; 1175 987 1176 988 static irqreturn_t dw_hdmi_qp_main_hardirq(int irq, void *dev_id) ··· 1288 1092 hdmi->bridge.hdmi_audio_max_i2s_playback_channels = 8; 1289 1093 hdmi->bridge.hdmi_audio_dev = dev; 1290 1094 hdmi->bridge.hdmi_audio_dai_port = 1; 1095 + 1096 + #ifdef CONFIG_DRM_DW_HDMI_QP_CEC 1097 + if (plat_data->cec_irq) { 1098 + hdmi->bridge.ops |= DRM_BRIDGE_OP_HDMI_CEC_ADAPTER; 1099 + hdmi->bridge.hdmi_cec_dev = dev; 1100 + hdmi->bridge.hdmi_cec_adapter_name = dev_name(dev); 1101 + 1102 + hdmi->cec = devm_kzalloc(hdmi->dev, sizeof(*hdmi->cec), GFP_KERNEL); 1103 + if (!hdmi->cec) 1104 + return ERR_PTR(-ENOMEM); 1105 + 1106 + hdmi->cec->irq = plat_data->cec_irq; 1107 + } else { 1108 + dev_warn(dev, "Disabled CEC support due to missing IRQ\n"); 1109 + } 1110 + #endif 1291 1111 1292 1112 ret = devm_drm_bridge_add(dev, &hdmi->bridge); 1293 1113 if (ret)
+14
drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.h
··· 488 488 #define AUDPKT_VBIT_OVR0 0xf24 489 489 /* CEC Registers */ 490 490 #define CEC_TX_CONTROL 0x1000 491 + #define CEC_CTRL_CLEAR BIT(0) 492 + #define CEC_CTRL_START BIT(0) 491 493 #define CEC_STATUS 0x1004 494 + #define CEC_STAT_DONE BIT(0) 495 + #define CEC_STAT_NACK BIT(1) 496 + #define CEC_STAT_ARBLOST BIT(2) 497 + #define CEC_STAT_LINE_ERR BIT(3) 498 + #define CEC_STAT_RETRANS_FAIL BIT(4) 499 + #define CEC_STAT_DISCARD BIT(5) 500 + #define CEC_STAT_TX_BUSY BIT(8) 501 + #define CEC_STAT_RX_BUSY BIT(9) 502 + #define CEC_STAT_DRIVE_ERR BIT(10) 503 + #define CEC_STAT_EOM BIT(11) 504 + #define CEC_STAT_NOTIFY_ERR BIT(12) 492 505 #define CEC_CONFIG 0x1008 493 506 #define CEC_ADDR 0x100c 507 + #define CEC_ADDR_BROADCAST BIT(15) 494 508 #define CEC_TX_COUNT 0x1020 495 509 #define CEC_TX_DATA3_0 0x1024 496 510 #define CEC_TX_DATA7_4 0x1028
+1
include/drm/bridge/dw_hdmi_qp.h
··· 23 23 const struct dw_hdmi_qp_phy_ops *phy_ops; 24 24 void *phy_data; 25 25 int main_irq; 26 + int cec_irq; 26 27 }; 27 28 28 29 struct dw_hdmi_qp *dw_hdmi_qp_bind(struct platform_device *pdev,