From 56dd8d87e5034bf18fbc6d7c248f28f6eb484a9b Mon Sep 17 00:00:00 2001 From: Jorge Ramirez-Ortiz Date: Thu, 15 Nov 2018 10:32:03 +0100 Subject: video: dw_hdmi: add support for color conversion Some IPs like the meson VPU can only feed a particular pixel format to dw_hdmi. As of now, the driver is hardcoded to use RGB888 as input. This commit enables different pixel format inputs, with the appropriate CSC configuration. Signed-off-by: Jorge Ramire-Ortiz Signed-off-by: Maxime Jourdan Signed-off-by: Neil Armstrong Reviewed-by: Anatolij Gustschin --- drivers/video/dw_hdmi.c | 253 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 252 insertions(+), 1 deletion(-) (limited to 'drivers/video') diff --git a/drivers/video/dw_hdmi.c b/drivers/video/dw_hdmi.c index e03f24fa98..463436edf3 100644 --- a/drivers/video/dw_hdmi.c +++ b/drivers/video/dw_hdmi.c @@ -8,6 +8,7 @@ #include #include #include +#include #include "dw_hdmi.h" struct tmds_n_cts { @@ -52,6 +53,24 @@ static const struct tmds_n_cts n_cts_table[] = { } }; +static const u16 csc_coeff_default[3][4] = { + { 0x2000, 0x0000, 0x0000, 0x0000 }, + { 0x0000, 0x2000, 0x0000, 0x0000 }, + { 0x0000, 0x0000, 0x2000, 0x0000 } +}; + +static const u16 csc_coeff_rgb_in_eitu601[3][4] = { + { 0x2591, 0x1322, 0x074b, 0x0000 }, + { 0x6535, 0x2000, 0x7acc, 0x0200 }, + { 0x6acd, 0x7534, 0x2000, 0x0200 } +}; + +static const u16 csc_coeff_rgb_out_eitu601[3][4] = { + { 0x2000, 0x6926, 0x74fd, 0x010e }, + { 0x2000, 0x2cdd, 0x0000, 0x7e9a }, + { 0x2000, 0x0000, 0x38b4, 0x7e3b } +}; + static void dw_hdmi_write(struct dw_hdmi *hdmi, u8 val, int offset) { switch (hdmi->reg_io_width) { @@ -162,9 +181,52 @@ static void hdmi_audio_set_samplerate(struct dw_hdmi *hdmi, u32 pixel_clk) */ static void hdmi_video_sample(struct dw_hdmi *hdmi) { - u32 color_format = 0x01; + u32 color_format; uint val; + switch (hdmi->hdmi_data.enc_in_bus_format) { + case MEDIA_BUS_FMT_RGB888_1X24: + color_format = 0x01; + break; + case MEDIA_BUS_FMT_RGB101010_1X30: + color_format = 0x03; + break; + case MEDIA_BUS_FMT_RGB121212_1X36: + color_format = 0x05; + break; + case MEDIA_BUS_FMT_RGB161616_1X48: + color_format = 0x07; + break; + case MEDIA_BUS_FMT_YUV8_1X24: + case MEDIA_BUS_FMT_UYYVYY8_0_5X24: + color_format = 0x09; + break; + case MEDIA_BUS_FMT_YUV10_1X30: + case MEDIA_BUS_FMT_UYYVYY10_0_5X30: + color_format = 0x0B; + break; + case MEDIA_BUS_FMT_YUV12_1X36: + case MEDIA_BUS_FMT_UYYVYY12_0_5X36: + color_format = 0x0D; + break; + case MEDIA_BUS_FMT_YUV16_1X48: + case MEDIA_BUS_FMT_UYYVYY16_0_5X48: + color_format = 0x0F; + break; + case MEDIA_BUS_FMT_UYVY8_1X16: + color_format = 0x16; + break; + case MEDIA_BUS_FMT_UYVY10_1X20: + color_format = 0x14; + break; + case MEDIA_BUS_FMT_UYVY12_1X24: + color_format = 0x12; + break; + default: + color_format = 0x01; + break; + } + val = HDMI_TX_INVID0_INTERNAL_DE_GENERATOR_DISABLE | ((color_format << HDMI_TX_INVID0_VIDEO_MAPPING_OFFSET) & HDMI_TX_INVID0_VIDEO_MAPPING_MASK); @@ -457,6 +519,180 @@ static void hdmi_av_composer(struct dw_hdmi *hdmi, hdmi_write(hdmi, edid->vsync_len.typ, HDMI_FC_VSYNCINWIDTH); } +static bool hdmi_bus_fmt_is_rgb(unsigned int bus_format) +{ + switch (bus_format) { + case MEDIA_BUS_FMT_RGB888_1X24: + case MEDIA_BUS_FMT_RGB101010_1X30: + case MEDIA_BUS_FMT_RGB121212_1X36: + case MEDIA_BUS_FMT_RGB161616_1X48: + return true; + + default: + return false; + } +} + +static bool hdmi_bus_fmt_is_yuv444(unsigned int bus_format) +{ + switch (bus_format) { + case MEDIA_BUS_FMT_YUV8_1X24: + case MEDIA_BUS_FMT_YUV10_1X30: + case MEDIA_BUS_FMT_YUV12_1X36: + case MEDIA_BUS_FMT_YUV16_1X48: + return true; + + default: + return false; + } +} + +static bool hdmi_bus_fmt_is_yuv422(unsigned int bus_format) +{ + switch (bus_format) { + case MEDIA_BUS_FMT_UYVY8_1X16: + case MEDIA_BUS_FMT_UYVY10_1X20: + case MEDIA_BUS_FMT_UYVY12_1X24: + return true; + + default: + return false; + } +} + +static int is_color_space_interpolation(struct dw_hdmi *hdmi) +{ + if (!hdmi_bus_fmt_is_yuv422(hdmi->hdmi_data.enc_in_bus_format)) + return 0; + + if (hdmi_bus_fmt_is_rgb(hdmi->hdmi_data.enc_out_bus_format) || + hdmi_bus_fmt_is_yuv444(hdmi->hdmi_data.enc_out_bus_format)) + return 1; + + return 0; +} + +static int is_color_space_decimation(struct dw_hdmi *hdmi) +{ + if (!hdmi_bus_fmt_is_yuv422(hdmi->hdmi_data.enc_out_bus_format)) + return 0; + + if (hdmi_bus_fmt_is_rgb(hdmi->hdmi_data.enc_in_bus_format) || + hdmi_bus_fmt_is_yuv444(hdmi->hdmi_data.enc_in_bus_format)) + return 1; + + return 0; +} + +static int hdmi_bus_fmt_color_depth(unsigned int bus_format) +{ + switch (bus_format) { + case MEDIA_BUS_FMT_RGB888_1X24: + case MEDIA_BUS_FMT_YUV8_1X24: + case MEDIA_BUS_FMT_UYVY8_1X16: + case MEDIA_BUS_FMT_UYYVYY8_0_5X24: + return 8; + + case MEDIA_BUS_FMT_RGB101010_1X30: + case MEDIA_BUS_FMT_YUV10_1X30: + case MEDIA_BUS_FMT_UYVY10_1X20: + case MEDIA_BUS_FMT_UYYVYY10_0_5X30: + return 10; + + case MEDIA_BUS_FMT_RGB121212_1X36: + case MEDIA_BUS_FMT_YUV12_1X36: + case MEDIA_BUS_FMT_UYVY12_1X24: + case MEDIA_BUS_FMT_UYYVYY12_0_5X36: + return 12; + + case MEDIA_BUS_FMT_RGB161616_1X48: + case MEDIA_BUS_FMT_YUV16_1X48: + case MEDIA_BUS_FMT_UYYVYY16_0_5X48: + return 16; + + default: + return 0; + } +} + +static int is_color_space_conversion(struct dw_hdmi *hdmi) +{ + return hdmi->hdmi_data.enc_in_bus_format != + hdmi->hdmi_data.enc_out_bus_format; +} + +static void dw_hdmi_update_csc_coeffs(struct dw_hdmi *hdmi) +{ + const u16 (*csc_coeff)[3][4] = &csc_coeff_default; + unsigned int i; + u32 csc_scale = 1; + + if (is_color_space_conversion(hdmi)) { + if (hdmi_bus_fmt_is_rgb(hdmi->hdmi_data.enc_out_bus_format)) { + csc_coeff = &csc_coeff_rgb_out_eitu601; + } else if (hdmi_bus_fmt_is_rgb( + hdmi->hdmi_data.enc_in_bus_format)) { + csc_coeff = &csc_coeff_rgb_in_eitu601; + csc_scale = 0; + } + } + + /* The CSC registers are sequential, alternating MSB then LSB */ + for (i = 0; i < ARRAY_SIZE(csc_coeff_default[0]); i++) { + u16 coeff_a = (*csc_coeff)[0][i]; + u16 coeff_b = (*csc_coeff)[1][i]; + u16 coeff_c = (*csc_coeff)[2][i]; + + hdmi_write(hdmi, coeff_a & 0xff, HDMI_CSC_COEF_A1_LSB + i * 2); + hdmi_write(hdmi, coeff_a >> 8, HDMI_CSC_COEF_A1_MSB + i * 2); + hdmi_write(hdmi, coeff_b & 0xff, HDMI_CSC_COEF_B1_LSB + i * 2); + hdmi_write(hdmi, coeff_b >> 8, HDMI_CSC_COEF_B1_MSB + i * 2); + hdmi_write(hdmi, coeff_c & 0xff, HDMI_CSC_COEF_C1_LSB + i * 2); + hdmi_write(hdmi, coeff_c >> 8, HDMI_CSC_COEF_C1_MSB + i * 2); + } + + hdmi_mod(hdmi, HDMI_CSC_SCALE, HDMI_CSC_SCALE_CSCSCALE_MASK, csc_scale); +} + +static void hdmi_video_csc(struct dw_hdmi *hdmi) +{ + int color_depth = 0; + int interpolation = HDMI_CSC_CFG_INTMODE_DISABLE; + int decimation = 0; + + /* YCC422 interpolation to 444 mode */ + if (is_color_space_interpolation(hdmi)) + interpolation = HDMI_CSC_CFG_INTMODE_CHROMA_INT_FORMULA1; + else if (is_color_space_decimation(hdmi)) + decimation = HDMI_CSC_CFG_DECMODE_CHROMA_INT_FORMULA3; + + switch (hdmi_bus_fmt_color_depth(hdmi->hdmi_data.enc_out_bus_format)) { + case 8: + color_depth = HDMI_CSC_SCALE_CSC_COLORDE_PTH_24BPP; + break; + case 10: + color_depth = HDMI_CSC_SCALE_CSC_COLORDE_PTH_30BPP; + break; + case 12: + color_depth = HDMI_CSC_SCALE_CSC_COLORDE_PTH_36BPP; + break; + case 16: + color_depth = HDMI_CSC_SCALE_CSC_COLORDE_PTH_48BPP; + break; + + default: + return; + } + + /* Configure the CSC registers */ + hdmi_write(hdmi, interpolation | decimation, HDMI_CSC_CFG); + + hdmi_mod(hdmi, HDMI_CSC_SCALE, HDMI_CSC_SCALE_CSC_COLORDE_PTH_MASK, + color_depth); + + dw_hdmi_update_csc_coeffs(hdmi); +} + /* hdmi initialization step b.4 */ static void hdmi_enable_video_path(struct dw_hdmi *hdmi, bool audio) { @@ -483,6 +719,20 @@ static void hdmi_enable_video_path(struct dw_hdmi *hdmi, bool audio) clkdis &= ~HDMI_MC_CLKDIS_TMDSCLK_DISABLE; hdmi_write(hdmi, clkdis, HDMI_MC_CLKDIS); + /* Enable csc path */ + if (is_color_space_conversion(hdmi)) { + clkdis &= ~HDMI_MC_CLKDIS_CSCCLK_DISABLE; + hdmi_write(hdmi, clkdis, HDMI_MC_CLKDIS); + } + + /* Enable color space conversion if needed */ + if (is_color_space_conversion(hdmi)) + hdmi_write(hdmi, HDMI_MC_FLOWCTRL_FEED_THROUGH_OFF_CSC_IN_PATH, + HDMI_MC_FLOWCTRL); + else + hdmi_write(hdmi, HDMI_MC_FLOWCTRL_FEED_THROUGH_OFF_CSC_BYPASS, + HDMI_MC_FLOWCTRL); + if (audio) { clkdis &= ~HDMI_MC_CLKDIS_AUDCLK_DISABLE; hdmi_write(hdmi, clkdis, HDMI_MC_CLKDIS); @@ -736,6 +986,7 @@ int dw_hdmi_enable(struct dw_hdmi *hdmi, const struct display_timing *edid) } hdmi_video_packetize(hdmi); + hdmi_video_csc(hdmi); hdmi_video_sample(hdmi); hdmi_clear_overflow(hdmi); -- cgit v1.2.3