/* * Copyright (c) 2014, Fuzhou Rockchip Electronics Co., Ltd * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "rockchip_drm_drv.h" #include "rockchip_drm_vop.h" #define RK3228_GRF_SOC_CON2 0x0408 #define RK3228_DDC_MASK_EN ((3 << 13) | (3 << (13 + 16))) #define RK3228_GRF_SOC_CON6 0x0418 #define RK3228_IO_3V_DOMAIN ((7 << 4) | (7 << (4 + 16))) #define RK3288_GRF_SOC_CON6 0x025C #define RK3288_HDMI_LCDC_SEL BIT(4) #define RK3288_GRF_SOC_CON16 0x03a8 #define RK3288_HDMI_LCDC0_YUV420 BIT(2) #define RK3288_HDMI_LCDC1_YUV420 BIT(3) #define RK3328_GRF_SOC_CON2 0x0408 #define RK3328_HDMI_SDAIN_MSK BIT(11) #define RK3328_HDMI_SCLIN_MSK BIT(10) #define RK3328_HDMI_HPD_IOE BIT(2) #define RK3328_GRF_SOC_CON3 0x040c /* need to be unset if hdmi or i2c should control voltage */ #define RK3328_HDMI_SDA5V_GRF BIT(15) #define RK3328_HDMI_SCL5V_GRF BIT(14) #define RK3328_HDMI_HPD5V_GRF BIT(13) #define RK3328_HDMI_CEC5V_GRF BIT(12) #define RK3328_GRF_SOC_CON4 0x0410 #define RK3328_HDMI_HPD_SARADC BIT(13) #define RK3328_HDMI_CEC_5V BIT(11) #define RK3328_HDMI_SDA_5V BIT(10) #define RK3328_HDMI_SCL_5V BIT(9) #define RK3328_HDMI_HPD_5V BIT(8) #define RK3399_GRF_SOC_CON20 0x6250 #define RK3399_HDMI_LCDC_SEL BIT(6) #define RK3528_VO_GRF_HDMI_MASK 0x60014 #define RK3528_HDMI_SNKDET_SEL BIT(6) #define RK3528_HDMI_SNKDET BIT(5) #define RK3528_HDMI_CECIN_MSK BIT(2) #define RK3528_HDMI_SDAIN_MSK BIT(1) #define RK3528_HDMI_SCLIN_MSK BIT(0) #define RK3528PMU_GRF_SOC_CON6 0x70018 #define RK3528_HDMI_SDA5V_GRF BIT(6) #define RK3528_HDMI_SCL5V_GRF BIT(5) #define RK3528_HDMI_CEC5V_GRF BIT(4) #define RK3528_HDMI_HPD5V_GRF BIT(3) #define RK3528_GPIO_SWPORT_DR_L 0x0000 #define RK3528_GPIO0_A2_DR BIT(2) #define RK3568_GRF_VO_CON1 0x0364 #define RK3568_HDMI_SDAIN_MSK BIT(15) #define RK3568_HDMI_SCLIN_MSK BIT(14) #define HIWORD_UPDATE(val, mask) (val | (mask) << 16) #define RK_HDMI_COLORIMETRY_BT2020 (HDMI_COLORIMETRY_EXTENDED + \ HDMI_EXTENDED_COLORIMETRY_BT2020) /** * struct rockchip_hdmi_chip_data - splite the grf setting of kind of chips * @lcdsel_grf_reg: grf register offset of lcdc select * @ddc_en_reg: grf register offset of hdmi ddc enable * @lcdsel_big: reg value of selecting vop big for HDMI * @lcdsel_lit: reg value of selecting vop little for HDMI */ struct rockchip_hdmi_chip_data { int lcdsel_grf_reg; int ddc_en_reg; u32 lcdsel_big; u32 lcdsel_lit; }; /* HDMI output pixel format */ enum drm_hdmi_output_type { DRM_HDMI_OUTPUT_DEFAULT_RGB, /* default RGB */ DRM_HDMI_OUTPUT_YCBCR444, /* YCBCR 444 */ DRM_HDMI_OUTPUT_YCBCR422, /* YCBCR 422 */ DRM_HDMI_OUTPUT_YCBCR420, /* YCBCR 420 */ DRM_HDMI_OUTPUT_YCBCR_HQ, /* Highest subsampled YUV */ DRM_HDMI_OUTPUT_YCBCR_LQ, /* Lowest subsampled YUV */ DRM_HDMI_OUTPUT_INVALID, /* Guess what ? */ }; enum dw_hdmi_rockchip_color_depth { ROCKCHIP_HDMI_DEPTH_8, ROCKCHIP_HDMI_DEPTH_10, ROCKCHIP_HDMI_DEPTH_12, ROCKCHIP_HDMI_DEPTH_16, ROCKCHIP_HDMI_DEPTH_420_10, ROCKCHIP_HDMI_DEPTH_420_12, ROCKCHIP_HDMI_DEPTH_420_16 }; struct rockchip_hdmi { struct device *dev; struct regmap *regmap; void __iomem *gpio_base; struct drm_encoder encoder; const struct rockchip_hdmi_chip_data *chip_data; struct clk *phyref_clk; struct clk *grf_clk; struct clk *hclk_vio; struct clk *hclk_vop; struct clk *dclk_vop; struct dw_hdmi *hdmi; struct phy *phy; int max_tmdsclk; bool unsupported_yuv_input; bool unsupported_deep_color; bool skip_check_420_mode; bool mode_changed; bool hpd_wake_en; u8 force_output; u8 id; unsigned long bus_format; unsigned long output_bus_format; unsigned long enc_out_encoding; unsigned long prev_bus_format; int color_changed; struct drm_property *color_depth_property; struct drm_property *hdmi_output_property; struct drm_property *colordepth_capacity; struct drm_property *outputmode_capacity; struct drm_property *colorimetry_property; struct drm_property *quant_range; struct drm_property *hdr_panel_metadata_property; struct drm_property *output_hdmi_dvi; struct drm_property *output_type_capacity; struct drm_property_blob *hdr_panel_blob_ptr; unsigned int colordepth; unsigned int colorimetry; unsigned int hdmi_quant_range; unsigned int phy_bus_width; enum drm_hdmi_output_type hdmi_output; struct rockchip_drm_sub_dev sub_dev; struct gpio_desc *hpd_gpiod; struct pinctrl *p; struct pinctrl_state *idle_state; struct pinctrl_state *default_state; int hpd_irq; }; #define to_rockchip_hdmi(x) container_of(x, struct rockchip_hdmi, x) /* * There are some rates that would be ranged for better clock jitter at * Chrome OS tree, like 25.175Mhz would range to 25.170732Mhz. But due * to the clock is aglined to KHz in struct drm_display_mode, this would * bring some inaccurate error if we still run the compute_n math, so * let's just code an const table for it until we can actually get the * right clock rate. */ static const struct dw_hdmi_audio_tmds_n rockchip_werid_tmds_n_table[] = { /* 25176471 for 25.175 MHz = 428000000 / 17. */ { .tmds = 25177000, .n_32k = 4352, .n_44k1 = 14994, .n_48k = 6528, }, /* 57290323 for 57.284 MHz */ { .tmds = 57291000, .n_32k = 3968, .n_44k1 = 4557, .n_48k = 5952, }, /* 74437500 for 74.44 MHz = 297750000 / 4 */ { .tmds = 74438000, .n_32k = 8192, .n_44k1 = 18816, .n_48k = 4096, }, /* 118666667 for 118.68 MHz */ { .tmds = 118667000, .n_32k = 4224, .n_44k1 = 5292, .n_48k = 6336, }, /* 121714286 for 121.75 MHz */ { .tmds = 121715000, .n_32k = 4480, .n_44k1 = 6174, .n_48k = 6272, }, /* 136800000 for 136.75 MHz */ { .tmds = 136800000, .n_32k = 4096, .n_44k1 = 5684, .n_48k = 6144, }, /* End of table */ { .tmds = 0, .n_32k = 0, .n_44k1 = 0, .n_48k = 0, }, }; static const struct dw_hdmi_mpll_config rockchip_mpll_cfg[] = { { 30666000, { { 0x00b3, 0x0000 }, { 0x2153, 0x0000 }, { 0x40f3, 0x0000 }, }, }, { 36800000, { { 0x00b3, 0x0000 }, { 0x2153, 0x0000 }, { 0x40a2, 0x0001 }, }, }, { 46000000, { { 0x00b3, 0x0000 }, { 0x2142, 0x0001 }, { 0x40a2, 0x0001 }, }, }, { 61333000, { { 0x0072, 0x0001 }, { 0x2142, 0x0001 }, { 0x40a2, 0x0001 }, }, }, { 73600000, { { 0x0072, 0x0001 }, { 0x2142, 0x0001 }, { 0x4061, 0x0002 }, }, }, { 92000000, { { 0x0072, 0x0001 }, { 0x2145, 0x0002 }, { 0x4061, 0x0002 }, }, }, { 122666000, { { 0x0051, 0x0002 }, { 0x2145, 0x0002 }, { 0x4061, 0x0002 }, }, }, { 147200000, { { 0x0051, 0x0002 }, { 0x2145, 0x0002 }, { 0x4064, 0x0003 }, }, }, { 184000000, { { 0x0051, 0x0002 }, { 0x214c, 0x0003 }, { 0x4064, 0x0003 }, }, }, { 226666000, { { 0x0040, 0x0003 }, { 0x214c, 0x0003 }, { 0x4064, 0x0003 }, }, }, { 272000000, { { 0x0040, 0x0003 }, { 0x214c, 0x0003 }, { 0x5a64, 0x0003 }, }, }, { 340000000, { { 0x0040, 0x0003 }, { 0x3b4c, 0x0003 }, { 0x5a64, 0x0003 }, }, }, { 600000000, { { 0x1a40, 0x0003 }, { 0x3b4c, 0x0003 }, { 0x5a64, 0x0003 }, }, }, { ~0UL, { { 0x0000, 0x0000 }, { 0x0000, 0x0000 }, { 0x0000, 0x0000 }, }, } }; static const struct dw_hdmi_mpll_config rockchip_mpll_cfg_rk356x[] = { { 30666000, { { 0x00b3, 0x0000 }, { 0x2153, 0x0000 }, { 0x40f3, 0x0000 }, }, }, { 36800000, { { 0x00b3, 0x0000 }, { 0x2153, 0x0000 }, { 0x40a2, 0x0001 }, }, }, { 46000000, { { 0x00b3, 0x0000 }, { 0x2142, 0x0001 }, { 0x40a2, 0x0001 }, }, }, { 61333000, { { 0x0072, 0x0001 }, { 0x2142, 0x0001 }, { 0x40a2, 0x0001 }, }, }, { 73600000, { { 0x0072, 0x0001 }, { 0x2142, 0x0001 }, { 0x4061, 0x0002 }, }, }, { 92000000, { { 0x0072, 0x0001 }, { 0x2145, 0x0002 }, { 0x4061, 0x0002 }, }, }, { 122666000, { { 0x0051, 0x0002 }, { 0x2145, 0x0002 }, { 0x4061, 0x0002 }, }, }, { 147200000, { { 0x0051, 0x0002 }, { 0x2145, 0x0002 }, { 0x4064, 0x0003 }, }, }, { 184000000, { { 0x0051, 0x0002 }, { 0x214c, 0x0003 }, { 0x4064, 0x0003 }, }, }, { 226666000, { { 0x0040, 0x0003 }, { 0x214c, 0x0003 }, { 0x4064, 0x0003 }, }, }, { 272000000, { { 0x0040, 0x0003 }, { 0x214c, 0x0003 }, { 0x5a64, 0x0003 }, }, }, { 340000000, { { 0x0040, 0x0002 }, { 0x3b4c, 0x0003 }, { 0x5a64, 0x0003 }, }, }, { 600000000, { { 0x1a40, 0x0003 }, { 0x3b4c, 0x0003 }, { 0x5a64, 0x0003 }, }, }, { ~0UL, { { 0x0000, 0x0000 }, { 0x0000, 0x0000 }, { 0x0000, 0x0000 }, }, } }; static const struct dw_hdmi_mpll_config rockchip_mpll_cfg_420[] = { { 30666000, { { 0x00b7, 0x0000 }, { 0x2157, 0x0000 }, { 0x40f7, 0x0000 }, }, }, { 92000000, { { 0x00b7, 0x0000 }, { 0x2143, 0x0001 }, { 0x40a3, 0x0001 }, }, }, { 184000000, { { 0x0073, 0x0001 }, { 0x2146, 0x0002 }, { 0x4062, 0x0002 }, }, }, { 340000000, { { 0x0052, 0x0003 }, { 0x214d, 0x0003 }, { 0x4065, 0x0003 }, }, }, { 600000000, { { 0x0041, 0x0003 }, { 0x3b4d, 0x0003 }, { 0x5a65, 0x0003 }, }, }, { ~0UL, { { 0x0000, 0x0000 }, { 0x0000, 0x0000 }, { 0x0000, 0x0000 }, }, } }; static const struct dw_hdmi_mpll_config rockchip_rk3288w_mpll_cfg_420[] = { { 30666000, { { 0x00b7, 0x0000 }, { 0x2157, 0x0000 }, { 0x40f7, 0x0000 }, }, }, { 92000000, { { 0x00b7, 0x0000 }, { 0x2143, 0x0001 }, { 0x40a3, 0x0001 }, }, }, { 184000000, { { 0x0073, 0x0001 }, { 0x2146, 0x0002 }, { 0x4062, 0x0002 }, }, }, { 340000000, { { 0x0052, 0x0003 }, { 0x214d, 0x0003 }, { 0x4065, 0x0003 }, }, }, { 600000000, { { 0x0040, 0x0003 }, { 0x3b4c, 0x0003 }, { 0x5a65, 0x0003 }, }, }, { ~0UL, { { 0x0000, 0x0000 }, { 0x0000, 0x0000 }, { 0x0000, 0x0000 }, }, } }; static const struct dw_hdmi_curr_ctrl rockchip_cur_ctr[] = { /* pixelclk bpp8 bpp10 bpp12 */ { 600000000, { 0x0000, 0x0000, 0x0000 }, }, { ~0UL, { 0x0000, 0x0000, 0x0000}, } }; static const struct dw_hdmi_curr_ctrl rockchip_cur_ctr_rk356x[] = { /* pixelclk bpp8 bpp10 bpp12 */ { 272000000, { 0x0000, 0x0000, 0x0000 }, }, { 340000000, { 0x0001, 0x0000, 0x0000 }, }, { 600000000, { 0x0000, 0x0000, 0x0000 }, }, { ~0UL, { 0x0000, 0x0000, 0x0000}, } }; static struct dw_hdmi_phy_config rockchip_phy_config[] = { /*pixelclk symbol term vlev*/ { 74250000, 0x8009, 0x0004, 0x0272}, { 165000000, 0x802b, 0x0004, 0x0209}, { 297000000, 0x8039, 0x0005, 0x028d}, { 594000000, 0x8039, 0x0000, 0x019d}, { ~0UL, 0x0000, 0x0000, 0x0000}, { ~0UL, 0x0000, 0x0000, 0x0000}, }; 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 rockchip_hdmi_update_phy_table(struct rockchip_hdmi *hdmi, u32 *config, int phy_table_size) { int i; if (phy_table_size > ARRAY_SIZE(rockchip_phy_config)) { dev_err(hdmi->dev, "phy table array number is out of range\n"); return -E2BIG; } for (i = 0; i < phy_table_size; i++) { if (config[i * 4] != 0) rockchip_phy_config[i].mpixelclock = (u64)config[i * 4]; else rockchip_phy_config[i].mpixelclock = ~0UL; rockchip_phy_config[i].sym_ctr = (u16)config[i * 4 + 1]; rockchip_phy_config[i].term = (u16)config[i * 4 + 2]; rockchip_phy_config[i].vlev_ctr = (u16)config[i * 4 + 3]; } return 0; } static irqreturn_t rockchip_hdmi_hpd_irq_handler(int irq, void *arg) { u32 val; struct rockchip_hdmi *hdmi = arg; val = gpiod_get_value(hdmi->hpd_gpiod); if (val) { val = HIWORD_UPDATE(RK3528_HDMI_SNKDET, RK3528_HDMI_SNKDET); if (hdmi->hdmi && hdmi->hpd_wake_en && hdmi->hpd_gpiod) dw_hdmi_set_hpd_wake(hdmi->hdmi); } else { val = HIWORD_UPDATE(0, RK3528_HDMI_SNKDET); } regmap_write(hdmi->regmap, RK3528_VO_GRF_HDMI_MASK, val); return IRQ_HANDLED; } static void dw_hdmi_rk3528_gpio_hpd_init(struct rockchip_hdmi *hdmi) { u32 val; if (hdmi->hpd_gpiod) { /* gpio0_a2's input enable is controlled by gpio output data bit */ val = HIWORD_UPDATE(RK3528_GPIO0_A2_DR, RK3528_GPIO0_A2_DR); writel(val, hdmi->gpio_base + RK3528_GPIO_SWPORT_DR_L); val = HIWORD_UPDATE(RK3528_HDMI_SNKDET_SEL | RK3528_HDMI_SDAIN_MSK | RK3528_HDMI_SCLIN_MSK, RK3528_HDMI_SNKDET_SEL | RK3528_HDMI_SDAIN_MSK | RK3528_HDMI_SCLIN_MSK); } else { val = HIWORD_UPDATE(RK3528_HDMI_SDAIN_MSK | RK3528_HDMI_SCLIN_MSK, RK3528_HDMI_SDAIN_MSK | RK3528_HDMI_SCLIN_MSK); } regmap_write(hdmi->regmap, RK3528_VO_GRF_HDMI_MASK, val); val = gpiod_get_value(hdmi->hpd_gpiod); if (val) { val = HIWORD_UPDATE(RK3528_HDMI_SNKDET, RK3528_HDMI_SNKDET); if (hdmi->hdmi && hdmi->hpd_wake_en && hdmi->hpd_gpiod) dw_hdmi_set_hpd_wake(hdmi->hdmi); } else { val = HIWORD_UPDATE(0, RK3528_HDMI_SNKDET); } regmap_write(hdmi->regmap, RK3528_VO_GRF_HDMI_MASK, val); } static int rockchip_hdmi_parse_dt(struct rockchip_hdmi *hdmi) { int ret, val, phy_table_size; u32 *phy_config; struct device_node *np = hdmi->dev->of_node; hdmi->regmap = syscon_regmap_lookup_by_phandle(np, "rockchip,grf"); if (IS_ERR(hdmi->regmap)) { DRM_DEV_ERROR(hdmi->dev, "Unable to get rockchip,grf\n"); return PTR_ERR(hdmi->regmap); } hdmi->phyref_clk = devm_clk_get(hdmi->dev, "vpll"); if (PTR_ERR(hdmi->phyref_clk) == -ENOENT) hdmi->phyref_clk = devm_clk_get(hdmi->dev, "ref"); if (PTR_ERR(hdmi->phyref_clk) == -ENOENT) { hdmi->phyref_clk = NULL; } else if (PTR_ERR(hdmi->phyref_clk) == -EPROBE_DEFER) { return -EPROBE_DEFER; } else if (IS_ERR(hdmi->phyref_clk)) { DRM_DEV_ERROR(hdmi->dev, "failed to get grf clock\n"); return PTR_ERR(hdmi->phyref_clk); } hdmi->grf_clk = devm_clk_get(hdmi->dev, "grf"); if (PTR_ERR(hdmi->grf_clk) == -ENOENT) { hdmi->grf_clk = NULL; } else if (PTR_ERR(hdmi->grf_clk) == -EPROBE_DEFER) { return -EPROBE_DEFER; } else if (IS_ERR(hdmi->grf_clk)) { DRM_DEV_ERROR(hdmi->dev, "failed to get grf clock\n"); return PTR_ERR(hdmi->grf_clk); } hdmi->hclk_vio = devm_clk_get(hdmi->dev, "hclk_vio"); if (PTR_ERR(hdmi->hclk_vio) == -ENOENT) { hdmi->hclk_vio = NULL; } else if (PTR_ERR(hdmi->hclk_vio) == -EPROBE_DEFER) { return -EPROBE_DEFER; } else if (IS_ERR(hdmi->hclk_vio)) { dev_err(hdmi->dev, "failed to get hclk_vio clock\n"); return PTR_ERR(hdmi->hclk_vio); } hdmi->hclk_vop = devm_clk_get(hdmi->dev, "hclk"); if (PTR_ERR(hdmi->hclk_vop) == -ENOENT) { hdmi->hclk_vop = NULL; } else if (PTR_ERR(hdmi->hclk_vop) == -EPROBE_DEFER) { return -EPROBE_DEFER; } else if (IS_ERR(hdmi->hclk_vop)) { dev_err(hdmi->dev, "failed to get hclk_vop clock\n"); return PTR_ERR(hdmi->hclk_vop); } hdmi->dclk_vop = devm_clk_get_optional(hdmi->dev, "dclk_vop"); if (IS_ERR(hdmi->dclk_vop)) { dev_err(hdmi->dev, "failed to get dclk_vop\n"); return PTR_ERR(hdmi->dclk_vop); } ret = of_property_read_u32(np, "max-tmdsclk", &hdmi->max_tmdsclk); if (ret != -EINVAL && ret < 0) { DRM_DEV_ERROR(hdmi->dev, "incorrect max tmdsclk\n"); return ret; } else if (ret == -EINVAL) { DRM_DEV_DEBUG(hdmi->dev, "max tmdsclk is not set, set to 594M\n"); hdmi->max_tmdsclk = 594000; } hdmi->unsupported_yuv_input = of_property_read_bool(np, "unsupported-yuv-input"); hdmi->unsupported_deep_color = of_property_read_bool(np, "unsupported-deep-color"); hdmi->skip_check_420_mode = of_property_read_bool(np, "skip-check-420-mode"); if (of_get_property(np, "rockchip,phy-table", &val)) { phy_config = kmalloc(val, GFP_KERNEL); if (!phy_config) { /* use default table when kmalloc failed. */ dev_err(hdmi->dev, "kmalloc phy table failed\n"); return -ENOMEM; } phy_table_size = val / 16; of_property_read_u32_array(np, "rockchip,phy-table", phy_config, val / sizeof(u32)); ret = rockchip_hdmi_update_phy_table(hdmi, phy_config, phy_table_size); if (ret) { kfree(phy_config); return ret; } kfree(phy_config); } else { dev_dbg(hdmi->dev, "use default hdmi phy table\n"); } hdmi->hpd_gpiod = devm_gpiod_get_optional(hdmi->dev, "hpd", GPIOD_IN); if (IS_ERR(hdmi->hpd_gpiod)) { dev_err(hdmi->dev, "error getting HDP GPIO: %ld\n", PTR_ERR(hdmi->hpd_gpiod)); return PTR_ERR(hdmi->hpd_gpiod); } if (hdmi->hpd_gpiod) { struct resource *res; struct platform_device *pdev = to_platform_device(hdmi->dev); /* gpio interrupt reflects hpd status */ hdmi->hpd_irq = gpiod_to_irq(hdmi->hpd_gpiod); if (hdmi->hpd_irq < 0) return -EINVAL; res = platform_get_resource(pdev, IORESOURCE_MEM, 1); if (!res) { DRM_DEV_ERROR(hdmi->dev, "failed to get gpio regs\n"); return -EINVAL; } hdmi->gpio_base = devm_ioremap(hdmi->dev, res->start, resource_size(res)); if (IS_ERR(hdmi->gpio_base)) { DRM_DEV_ERROR(hdmi->dev, "Unable to get gpio ioregmap\n"); return PTR_ERR(hdmi->gpio_base); } dw_hdmi_rk3528_gpio_hpd_init(hdmi); ret = devm_request_threaded_irq(hdmi->dev, hdmi->hpd_irq, NULL, rockchip_hdmi_hpd_irq_handler, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, "hdmi-hpd", hdmi); if (ret) { dev_err(hdmi->dev, "failed to request hpd IRQ: %d\n", ret); return ret; } hdmi->hpd_wake_en = device_property_read_bool(hdmi->dev, "hpd-wake-up"); if (hdmi->hpd_wake_en) enable_irq_wake(hdmi->hpd_irq); } hdmi->p = devm_pinctrl_get(hdmi->dev); if (IS_ERR(hdmi->p)) { dev_err(hdmi->dev, "could not get pinctrl\n"); return PTR_ERR(hdmi->p); } hdmi->idle_state = pinctrl_lookup_state(hdmi->p, "idle"); if (IS_ERR(hdmi->idle_state)) { dev_dbg(hdmi->dev, "idle state is not defined\n"); return 0; } hdmi->default_state = pinctrl_lookup_state(hdmi->p, "default"); if (IS_ERR(hdmi->default_state)) { dev_err(hdmi->dev, "could not find default state\n"); return PTR_ERR(hdmi->default_state); } return 0; } static enum drm_mode_status dw_hdmi_rockchip_mode_valid(struct drm_connector *connector, const struct drm_display_mode *mode) { struct drm_encoder *encoder = connector->encoder; enum drm_mode_status status = MODE_OK; struct drm_device *dev = connector->dev; struct rockchip_drm_private *priv = dev->dev_private; struct drm_crtc *crtc; struct rockchip_hdmi *hdmi; /* * Pixel clocks we support are always < 2GHz and so fit in an * int. We should make sure source rate does too so we don't get * overflow when we multiply by 1000. */ if (mode->clock > INT_MAX / 1000) return MODE_BAD; if (!encoder) { const struct drm_connector_helper_funcs *funcs; funcs = connector->helper_private; if (funcs->atomic_best_encoder) encoder = funcs->atomic_best_encoder(connector, connector->state); else encoder = funcs->best_encoder(connector); } if (!encoder || !encoder->possible_crtcs) return MODE_BAD; hdmi = to_rockchip_hdmi(encoder); /* * If sink max TMDS clock < 340MHz, we should check the mode pixel * clock > 340MHz is YCbCr420 or not and whether the platform supports * YCbCr420. */ if (!hdmi->skip_check_420_mode) { if (mode->clock > 340000 && connector->display_info.max_tmds_clock < 340000 && (!drm_mode_is_420(&connector->display_info, mode) || !connector->ycbcr_420_allowed)) return MODE_BAD; if (hdmi->max_tmdsclk <= 340000 && mode->clock > 340000 && !drm_mode_is_420(&connector->display_info, mode)) return MODE_BAD; }; if (hdmi->phy) phy_set_bus_width(hdmi->phy, 8); /* * ensure all drm display mode can work, if someone want support more * resolutions, please limit the possible_crtc, only connect to * needed crtc. */ drm_for_each_crtc(crtc, connector->dev) { int pipe = drm_crtc_index(crtc); const struct rockchip_crtc_funcs *funcs = priv->crtc_funcs[pipe]; if (!(encoder->possible_crtcs & drm_crtc_mask(crtc))) continue; if (!funcs || !funcs->mode_valid) continue; status = funcs->mode_valid(crtc, mode, DRM_MODE_CONNECTOR_HDMIA); if (status != MODE_OK) return status; } return status; } static const struct drm_encoder_funcs dw_hdmi_rockchip_encoder_funcs = { .destroy = drm_encoder_cleanup, }; static void dw_hdmi_rockchip_encoder_disable(struct drm_encoder *encoder) { struct rockchip_hdmi *hdmi = to_rockchip_hdmi(encoder); struct drm_crtc *crtc = encoder->crtc; struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc->state); if (WARN_ON(!crtc || !crtc->state)) return; if (!hdmi->mode_changed) s->output_if &= ~VOP_OUTPUT_IF_HDMI0; /* * when plug out hdmi it will be switch cvbs and then phy bus width * must be set as 8 */ if (hdmi->phy) phy_set_bus_width(hdmi->phy, 8); } static void dw_hdmi_rockchip_encoder_enable(struct drm_encoder *encoder) { struct rockchip_hdmi *hdmi = to_rockchip_hdmi(encoder); struct drm_crtc *crtc = encoder->crtc; u32 val; int mux; int ret; if (WARN_ON(!crtc || !crtc->state)) return; if (hdmi->phy) phy_set_bus_width(hdmi->phy, hdmi->phy_bus_width); clk_set_rate(hdmi->phyref_clk, crtc->state->adjusted_mode.crtc_clock * 1000); if (hdmi->chip_data->lcdsel_grf_reg < 0) return; mux = drm_of_encoder_active_endpoint_id(hdmi->dev->of_node, encoder); if (mux) val = hdmi->chip_data->lcdsel_lit; else val = hdmi->chip_data->lcdsel_big; ret = clk_prepare_enable(hdmi->grf_clk); if (ret < 0) { DRM_DEV_ERROR(hdmi->dev, "failed to enable grfclk %d\n", ret); return; } ret = regmap_write(hdmi->regmap, hdmi->chip_data->lcdsel_grf_reg, val); if (ret != 0) DRM_DEV_ERROR(hdmi->dev, "Could not write to GRF: %d\n", ret); if (hdmi->chip_data->lcdsel_grf_reg == RK3288_GRF_SOC_CON6) { struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc->state); u32 mode_mask = mux ? RK3288_HDMI_LCDC1_YUV420 : RK3288_HDMI_LCDC0_YUV420; if (s->output_mode == ROCKCHIP_OUT_MODE_YUV420) val = HIWORD_UPDATE(mode_mask, mode_mask); else val = HIWORD_UPDATE(0, mode_mask); regmap_write(hdmi->regmap, RK3288_GRF_SOC_CON16, val); } clk_disable_unprepare(hdmi->grf_clk); DRM_DEV_DEBUG(hdmi->dev, "vop %s output to hdmi\n", ret ? "LIT" : "BIG"); } static void dw_hdmi_rockchip_select_output(struct drm_connector_state *conn_state, struct drm_crtc_state *crtc_state, struct rockchip_hdmi *hdmi, unsigned int *color_format, unsigned int *output_mode, unsigned long *bus_format, unsigned int *bus_width, unsigned long *enc_out_encoding, unsigned int *eotf) { struct drm_display_info *info = &conn_state->connector->display_info; struct drm_display_mode *mode = &crtc_state->mode; struct hdr_output_metadata *hdr_metadata; u32 vic = drm_match_cea_mode(mode); unsigned long tmdsclock, pixclock = mode->crtc_clock; unsigned int color_depth; bool support_dc = false; bool sink_is_hdmi = dw_hdmi_get_output_whether_hdmi(hdmi->hdmi); bool yuv422_out = false; int max_tmds_clock = info->max_tmds_clock; int output_eotf; *color_format = DRM_HDMI_OUTPUT_DEFAULT_RGB; switch (hdmi->hdmi_output) { case DRM_HDMI_OUTPUT_YCBCR_HQ: if (info->color_formats & DRM_COLOR_FORMAT_YCRCB444) *color_format = DRM_HDMI_OUTPUT_YCBCR444; else if (info->color_formats & DRM_COLOR_FORMAT_YCRCB422) *color_format = DRM_HDMI_OUTPUT_YCBCR422; else if (conn_state->connector->ycbcr_420_allowed && drm_mode_is_420(info, mode) && pixclock >= 594000) *color_format = DRM_HDMI_OUTPUT_YCBCR420; break; case DRM_HDMI_OUTPUT_YCBCR_LQ: if (conn_state->connector->ycbcr_420_allowed && drm_mode_is_420(info, mode) && pixclock >= 594000) *color_format = DRM_HDMI_OUTPUT_YCBCR420; else if (info->color_formats & DRM_COLOR_FORMAT_YCRCB422) *color_format = DRM_HDMI_OUTPUT_YCBCR422; else if (info->color_formats & DRM_COLOR_FORMAT_YCRCB444) *color_format = DRM_HDMI_OUTPUT_YCBCR444; break; case DRM_HDMI_OUTPUT_YCBCR420: if (conn_state->connector->ycbcr_420_allowed && drm_mode_is_420(info, mode) && pixclock >= 594000) *color_format = DRM_HDMI_OUTPUT_YCBCR420; break; case DRM_HDMI_OUTPUT_YCBCR422: if (info->color_formats & DRM_COLOR_FORMAT_YCRCB422) *color_format = DRM_HDMI_OUTPUT_YCBCR422; break; case DRM_HDMI_OUTPUT_YCBCR444: if (info->color_formats & DRM_COLOR_FORMAT_YCRCB444) *color_format = DRM_HDMI_OUTPUT_YCBCR444; break; case DRM_HDMI_OUTPUT_DEFAULT_RGB: default: break; } if (*color_format == DRM_HDMI_OUTPUT_DEFAULT_RGB && info->edid_hdmi_dc_modes & DRM_EDID_HDMI_DC_30) support_dc = true; if (*color_format == DRM_HDMI_OUTPUT_YCBCR444 && info->edid_hdmi_dc_modes & (DRM_EDID_HDMI_DC_Y444 | DRM_EDID_HDMI_DC_30)) support_dc = true; if (*color_format == DRM_HDMI_OUTPUT_YCBCR422) support_dc = true; if (*color_format == DRM_HDMI_OUTPUT_YCBCR420 && info->hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_30) support_dc = true; if (hdmi->colordepth > 8 && support_dc) color_depth = 10; else color_depth = 8; if (!sink_is_hdmi) { *color_format = DRM_HDMI_OUTPUT_DEFAULT_RGB; color_depth = 8; } *eotf = TRADITIONAL_GAMMA_SDR; if (conn_state->hdr_output_metadata) { hdr_metadata = (struct hdr_output_metadata *) conn_state->hdr_output_metadata->data; output_eotf = hdr_metadata->hdmi_metadata_type1.eotf; if (output_eotf > TRADITIONAL_GAMMA_HDR && output_eotf < FUTURE_EOTF) *eotf = output_eotf; } /* bt2020 sdr/hdr output */ if (hdmi->colorimetry == RK_HDMI_COLORIMETRY_BT2020 && info->hdmi.colorimetry & (BIT(6) | BIT(7))) { *enc_out_encoding = V4L2_YCBCR_ENC_BT2020; yuv422_out = true; /* bt709 hdr output */ } else if (hdmi->colorimetry != RK_HDMI_COLORIMETRY_BT2020 && (conn_state->connector->hdr_sink_metadata.hdmi_type1.eotf & BIT(*eotf) && *eotf > TRADITIONAL_GAMMA_HDR)) { *enc_out_encoding = V4L2_YCBCR_ENC_709; yuv422_out = true; } else if ((vic == 6) || (vic == 7) || (vic == 21) || (vic == 22) || (vic == 2) || (vic == 3) || (vic == 17) || (vic == 18)) { *enc_out_encoding = V4L2_YCBCR_ENC_601; } else { *enc_out_encoding = V4L2_YCBCR_ENC_709; } if ((yuv422_out || hdmi->hdmi_output == DRM_HDMI_OUTPUT_YCBCR_HQ) && color_depth == 10 && hdmi_bus_fmt_color_depth(hdmi->prev_bus_format) == 8) { /* We prefer use YCbCr422 to send hdr 10bit */ if (info->color_formats & DRM_COLOR_FORMAT_YCRCB422) *color_format = DRM_HDMI_OUTPUT_YCBCR422; } if (mode->flags & DRM_MODE_FLAG_DBLCLK) pixclock *= 2; if ((mode->flags & DRM_MODE_FLAG_3D_MASK) == DRM_MODE_FLAG_3D_FRAME_PACKING) pixclock *= 2; if (*color_format == DRM_HDMI_OUTPUT_YCBCR422 || color_depth == 8) tmdsclock = pixclock; else tmdsclock = pixclock * (color_depth) / 8; if (*color_format == DRM_HDMI_OUTPUT_YCBCR420) tmdsclock /= 2; /* XXX: max_tmds_clock of some sink is 0, we think it is 340MHz. */ if (!max_tmds_clock) max_tmds_clock = 340000; max_tmds_clock = min(max_tmds_clock, hdmi->max_tmdsclk); if (tmdsclock > max_tmds_clock) { if (max_tmds_clock >= 594000) { color_depth = 8; } else if (max_tmds_clock > 340000) { if (drm_mode_is_420(info, mode) || tmdsclock >= 594000) *color_format = DRM_HDMI_OUTPUT_YCBCR420; } else { color_depth = 8; if (drm_mode_is_420(info, mode) || tmdsclock >= 594000) *color_format = DRM_HDMI_OUTPUT_YCBCR420; } } if (*color_format == DRM_HDMI_OUTPUT_YCBCR420) { *output_mode = ROCKCHIP_OUT_MODE_YUV420; if (color_depth > 8) *bus_format = MEDIA_BUS_FMT_UYYVYY10_0_5X30; else *bus_format = MEDIA_BUS_FMT_UYYVYY8_0_5X24; *bus_width = color_depth / 2; hdmi->output_bus_format = *bus_format; } else { *output_mode = ROCKCHIP_OUT_MODE_AAAA; if (color_depth > 8) { if (*color_format != DRM_HDMI_OUTPUT_DEFAULT_RGB) hdmi->output_bus_format = MEDIA_BUS_FMT_YUV10_1X30; else hdmi->output_bus_format = MEDIA_BUS_FMT_RGB101010_1X30; if (!hdmi->unsupported_yuv_input) *bus_format = hdmi->output_bus_format; else *bus_format = MEDIA_BUS_FMT_RGB101010_1X30; } else { if (*color_format != DRM_HDMI_OUTPUT_DEFAULT_RGB) hdmi->output_bus_format = MEDIA_BUS_FMT_YUV8_1X24; else hdmi->output_bus_format = MEDIA_BUS_FMT_RGB888_1X24; if (!hdmi->unsupported_yuv_input) *bus_format = hdmi->output_bus_format; else *bus_format = MEDIA_BUS_FMT_RGB888_1X24; } if (*color_format == DRM_HDMI_OUTPUT_YCBCR422) { *bus_width = 8; if (color_depth == 12) hdmi->output_bus_format = MEDIA_BUS_FMT_UYVY12_1X24; else if (color_depth == 10) hdmi->output_bus_format = MEDIA_BUS_FMT_UYVY10_1X20; else hdmi->output_bus_format = MEDIA_BUS_FMT_UYVY8_1X16; } else { *bus_width = color_depth; } } hdmi->bus_format = *bus_format; } static bool dw_hdmi_rockchip_check_color(struct drm_connector_state *conn_state, struct rockchip_hdmi *hdmi) { struct drm_crtc_state *crtc_state = conn_state->crtc->state; unsigned int colorformat; unsigned long bus_format; unsigned long output_bus_format = hdmi->output_bus_format; unsigned long enc_out_encoding = hdmi->enc_out_encoding; unsigned int eotf, bus_width; unsigned int output_mode; dw_hdmi_rockchip_select_output(conn_state, crtc_state, hdmi, &colorformat, &output_mode, &bus_format, &bus_width, &hdmi->enc_out_encoding, &eotf); if (output_bus_format != hdmi->output_bus_format || enc_out_encoding != hdmi->enc_out_encoding) return true; else return false; } static int dw_hdmi_rockchip_encoder_atomic_check(struct drm_encoder *encoder, struct drm_crtc_state *crtc_state, struct drm_connector_state *conn_state) { struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc_state); struct rockchip_hdmi *hdmi = to_rockchip_hdmi(encoder); unsigned int colorformat, bus_width; unsigned int output_mode; unsigned long bus_format; dw_hdmi_rockchip_select_output(conn_state, crtc_state, hdmi, &colorformat, &output_mode, &bus_format, &bus_width, &hdmi->enc_out_encoding, &s->eotf); hdmi->phy_bus_width = bus_width; if (hdmi->phy) phy_set_bus_width(hdmi->phy, bus_width); s->output_type = DRM_MODE_CONNECTOR_HDMIA; s->tv_state = &conn_state->tv; s->output_if |= VOP_OUTPUT_IF_HDMI0; s->output_mode = output_mode; s->bus_format = bus_format; hdmi->bus_format = s->bus_format; hdmi->mode_changed = crtc_state->mode_changed; if (hdmi->enc_out_encoding == V4L2_YCBCR_ENC_BT2020) s->color_space = V4L2_COLORSPACE_BT2020; else if (colorformat == DRM_HDMI_OUTPUT_DEFAULT_RGB) s->color_space = V4L2_COLORSPACE_DEFAULT; else if (hdmi->enc_out_encoding == V4L2_YCBCR_ENC_709) s->color_space = V4L2_COLORSPACE_REC709; else s->color_space = V4L2_COLORSPACE_SMPTE170M; return 0; } static void dw_hdmi_rockchip_encoder_mode_set(struct drm_encoder *encoder, struct drm_display_mode *mode, struct drm_display_mode *adj) { struct rockchip_hdmi *hdmi = to_rockchip_hdmi(encoder); clk_set_rate(hdmi->phyref_clk, adj->crtc_clock * 1000); } static unsigned long dw_hdmi_rockchip_get_input_bus_format(void *data) { struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data; return hdmi->bus_format; } static unsigned long dw_hdmi_rockchip_get_output_bus_format(void *data) { struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data; return hdmi->output_bus_format; } static unsigned long dw_hdmi_rockchip_get_enc_in_encoding(void *data) { struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data; return hdmi->enc_out_encoding; } static unsigned long dw_hdmi_rockchip_get_enc_out_encoding(void *data) { struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data; return hdmi->enc_out_encoding; } static unsigned long dw_hdmi_rockchip_get_quant_range(void *data) { struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data; return hdmi->hdmi_quant_range; } static struct drm_property * dw_hdmi_rockchip_get_hdr_property(void *data) { struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data; return hdmi->hdr_panel_metadata_property; } static struct drm_property_blob * dw_hdmi_rockchip_get_hdr_blob(void *data) { struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data; return hdmi->hdr_panel_blob_ptr; } static void dw_hdmi_rockchip_update_color_format(struct drm_connector_state *conn_state, void *data) { struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data; dw_hdmi_rockchip_check_color(conn_state, hdmi); } static bool dw_hdmi_rockchip_get_color_changed(void *data) { struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data; bool ret = false; if (hdmi->color_changed) ret = true; hdmi->color_changed = 0; return ret; } static bool dw_hdmi_rockchip_check_hdr_color_change(struct drm_connector_state *conn_state, void *data) { struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data; if (!conn_state || !data) return false; if (dw_hdmi_rockchip_check_color(conn_state, hdmi)) return true; return false; } static void dw_hdmi_rockchip_set_prev_bus_format(void *data, unsigned long bus_format) { struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data; hdmi->prev_bus_format = bus_format; } static void dw_hdmi_rockchip_set_ddc_io(void *data, bool enable) { struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data; if (!hdmi->p || !hdmi->idle_state || !hdmi->default_state) return; if (!enable) { if (pinctrl_select_state(hdmi->p, hdmi->idle_state)) dev_err(hdmi->dev, "could not select idle state\n"); } else { if (pinctrl_select_state(hdmi->p, hdmi->default_state)) dev_err(hdmi->dev, "could not select default state\n"); } } static int dw_hdmi_rockchip_dclk_set(void *data, bool enable, int vp_id) { struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data; int ret = 0; if (!hdmi->dclk_vop) return 0; if (enable) { ret = clk_prepare_enable(hdmi->dclk_vop); if (ret < 0) dev_err(hdmi->dev, "failed to enable dclk_vop\n"); } else { clk_disable_unprepare(hdmi->dclk_vop); } return ret; } static const struct drm_prop_enum_list color_depth_enum_list[] = { { 0, "Automatic" }, /* Prefer highest color depth */ { 8, "24bit" }, { 10, "30bit" }, }; static const struct drm_prop_enum_list drm_hdmi_output_enum_list[] = { { DRM_HDMI_OUTPUT_DEFAULT_RGB, "output_rgb" }, { DRM_HDMI_OUTPUT_YCBCR444, "output_ycbcr444" }, { DRM_HDMI_OUTPUT_YCBCR422, "output_ycbcr422" }, { DRM_HDMI_OUTPUT_YCBCR420, "output_ycbcr420" }, { DRM_HDMI_OUTPUT_YCBCR_HQ, "output_ycbcr_high_subsampling" }, { DRM_HDMI_OUTPUT_YCBCR_LQ, "output_ycbcr_low_subsampling" }, { DRM_HDMI_OUTPUT_INVALID, "invalid_output" }, }; static const struct drm_prop_enum_list quant_range_enum_list[] = { { HDMI_QUANTIZATION_RANGE_DEFAULT, "default" }, { HDMI_QUANTIZATION_RANGE_LIMITED, "limit" }, { HDMI_QUANTIZATION_RANGE_FULL, "full" }, }; static const struct drm_prop_enum_list colorimetry_enum_list[] = { { HDMI_COLORIMETRY_NONE, "None" }, { RK_HDMI_COLORIMETRY_BT2020, "ITU_2020" }, }; static const struct drm_prop_enum_list output_hdmi_dvi_enum_list[] = { { 0, "auto" }, { 1, "force_hdmi" }, { 2, "force_dvi" }, }; static const struct drm_prop_enum_list output_type_cap_list[] = { { 0, "DVI" }, { 1, "HDMI" }, }; static void dw_hdmi_rockchip_attach_properties(struct drm_connector *connector, unsigned int color, int version, void *data) { struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data; struct drm_property *prop; struct rockchip_drm_private *private = connector->dev->dev_private; switch (color) { case MEDIA_BUS_FMT_RGB101010_1X30: hdmi->hdmi_output = DRM_HDMI_OUTPUT_DEFAULT_RGB; hdmi->colordepth = 10; break; case MEDIA_BUS_FMT_YUV8_1X24: hdmi->hdmi_output = DRM_HDMI_OUTPUT_YCBCR444; hdmi->colordepth = 8; break; case MEDIA_BUS_FMT_YUV10_1X30: hdmi->hdmi_output = DRM_HDMI_OUTPUT_YCBCR444; hdmi->colordepth = 10; break; case MEDIA_BUS_FMT_UYVY10_1X20: hdmi->hdmi_output = DRM_HDMI_OUTPUT_YCBCR422; hdmi->colordepth = 10; break; case MEDIA_BUS_FMT_UYVY8_1X16: hdmi->hdmi_output = DRM_HDMI_OUTPUT_YCBCR422; hdmi->colordepth = 8; break; case MEDIA_BUS_FMT_UYYVYY8_0_5X24: hdmi->hdmi_output = DRM_HDMI_OUTPUT_YCBCR420; hdmi->colordepth = 8; break; case MEDIA_BUS_FMT_UYYVYY10_0_5X30: hdmi->hdmi_output = DRM_HDMI_OUTPUT_YCBCR420; hdmi->colordepth = 10; break; default: hdmi->hdmi_output = DRM_HDMI_OUTPUT_DEFAULT_RGB; hdmi->colordepth = 8; } hdmi->bus_format = color; hdmi->prev_bus_format = color; if (hdmi->hdmi_output == DRM_HDMI_OUTPUT_YCBCR422) { if (hdmi->colordepth == 12) hdmi->output_bus_format = MEDIA_BUS_FMT_UYVY12_1X24; else if (hdmi->colordepth == 10) hdmi->output_bus_format = MEDIA_BUS_FMT_UYVY10_1X20; else hdmi->output_bus_format = MEDIA_BUS_FMT_UYVY8_1X16; } else { hdmi->output_bus_format = hdmi->bus_format; } /* RK3368 does not support deep color mode */ if (!hdmi->color_depth_property && !hdmi->unsupported_deep_color) { prop = drm_property_create_enum(connector->dev, 0, "hdmi_output_depth", color_depth_enum_list, ARRAY_SIZE(color_depth_enum_list)); if (prop) { hdmi->color_depth_property = prop; drm_object_attach_property(&connector->base, prop, 0); } } prop = drm_property_create_enum(connector->dev, 0, "hdmi_output_format", drm_hdmi_output_enum_list, ARRAY_SIZE(drm_hdmi_output_enum_list)); if (prop) { hdmi->hdmi_output_property = prop; drm_object_attach_property(&connector->base, prop, 0); } prop = drm_property_create_enum(connector->dev, 0, "hdmi_output_colorimetry", colorimetry_enum_list, ARRAY_SIZE(colorimetry_enum_list)); if (prop) { hdmi->colorimetry_property = prop; drm_object_attach_property(&connector->base, prop, 0); } prop = drm_property_create_range(connector->dev, 0, "hdmi_color_depth_capacity", 0, 0xff); if (prop) { hdmi->colordepth_capacity = prop; drm_object_attach_property(&connector->base, prop, 0); } prop = drm_property_create_range(connector->dev, 0, "hdmi_output_mode_capacity", 0, 0xf); if (prop) { hdmi->outputmode_capacity = prop; drm_object_attach_property(&connector->base, prop, 0); } prop = drm_property_create_enum(connector->dev, 0, "hdmi_quant_range", quant_range_enum_list, ARRAY_SIZE(quant_range_enum_list)); if (prop) { hdmi->quant_range = prop; drm_object_attach_property(&connector->base, prop, 0); } prop = drm_property_create(connector->dev, DRM_MODE_PROP_BLOB | DRM_MODE_PROP_IMMUTABLE, "HDR_PANEL_METADATA", 0); if (prop) { hdmi->hdr_panel_metadata_property = prop; drm_object_attach_property(&connector->base, prop, 0); } prop = drm_property_create_enum(connector->dev, 0, "output_hdmi_dvi", output_hdmi_dvi_enum_list, ARRAY_SIZE(output_hdmi_dvi_enum_list)); if (prop) { hdmi->output_hdmi_dvi = prop; drm_object_attach_property(&connector->base, prop, 0); } prop = drm_property_create_enum(connector->dev, 0, "output_type_capacity", output_type_cap_list, ARRAY_SIZE(output_type_cap_list)); if (prop) { hdmi->output_type_capacity = prop; drm_object_attach_property(&connector->base, prop, 0); } prop = connector->dev->mode_config.hdr_output_metadata_property; if (version >= 0x211a) drm_object_attach_property(&connector->base, prop, 0); drm_object_attach_property(&connector->base, private->connector_id_prop, 0); } static void dw_hdmi_rockchip_destroy_properties(struct drm_connector *connector, void *data) { struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data; if (hdmi->color_depth_property) { drm_property_destroy(connector->dev, hdmi->color_depth_property); hdmi->color_depth_property = NULL; } if (hdmi->hdmi_output_property) { drm_property_destroy(connector->dev, hdmi->hdmi_output_property); hdmi->hdmi_output_property = NULL; } if (hdmi->colordepth_capacity) { drm_property_destroy(connector->dev, hdmi->colordepth_capacity); hdmi->colordepth_capacity = NULL; } if (hdmi->outputmode_capacity) { drm_property_destroy(connector->dev, hdmi->outputmode_capacity); hdmi->outputmode_capacity = NULL; } if (hdmi->quant_range) { drm_property_destroy(connector->dev, hdmi->quant_range); hdmi->quant_range = NULL; } if (hdmi->colorimetry_property) { drm_property_destroy(connector->dev, hdmi->colorimetry_property); hdmi->colordepth_capacity = NULL; } if (hdmi->hdr_panel_metadata_property) { drm_property_destroy(connector->dev, hdmi->hdr_panel_metadata_property); hdmi->hdr_panel_metadata_property = NULL; } if (hdmi->output_hdmi_dvi) { drm_property_destroy(connector->dev, hdmi->output_hdmi_dvi); hdmi->output_hdmi_dvi = NULL; } if (hdmi->output_type_capacity) { drm_property_destroy(connector->dev, hdmi->output_type_capacity); hdmi->output_type_capacity = NULL; } } static int dw_hdmi_rockchip_set_property(struct drm_connector *connector, struct drm_connector_state *state, struct drm_property *property, u64 val, void *data) { struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data; struct drm_mode_config *config = &connector->dev->mode_config; if (property == hdmi->color_depth_property) { hdmi->colordepth = val; /* If hdmi is disconnected, state->crtc is null */ if (!state->crtc) return 0; if (dw_hdmi_rockchip_check_color(state, hdmi)) hdmi->color_changed++; return 0; } else if (property == hdmi->hdmi_output_property) { hdmi->hdmi_output = val; if (!state->crtc) return 0; if (dw_hdmi_rockchip_check_color(state, hdmi)) hdmi->color_changed++; return 0; } else if (property == hdmi->quant_range) { u64 quant_range = hdmi->hdmi_quant_range; hdmi->hdmi_quant_range = val; if (quant_range != hdmi->hdmi_quant_range) dw_hdmi_set_quant_range(hdmi->hdmi); return 0; } else if (property == config->hdr_output_metadata_property) { return 0; } else if (property == hdmi->colorimetry_property) { hdmi->colorimetry = val; return 0; } else if (property == hdmi->output_hdmi_dvi) { if (hdmi->force_output != val) hdmi->color_changed++; hdmi->force_output = val; dw_hdmi_set_output_type(hdmi->hdmi, val); return 0; } else if (property == hdmi->colordepth_capacity) { return 0; } else if (property == hdmi->outputmode_capacity) { return 0; } else if (property == hdmi->output_type_capacity) { return 0; } DRM_ERROR("failed to set rockchip hdmi connector property %s\n", property->name); return -EINVAL; } static int dw_hdmi_rockchip_get_property(struct drm_connector *connector, const struct drm_connector_state *state, struct drm_property *property, u64 *val, void *data) { struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data; struct drm_display_info *info = &connector->display_info; struct drm_mode_config *config = &connector->dev->mode_config; struct rockchip_drm_private *private = connector->dev->dev_private; if (property == hdmi->color_depth_property) { *val = hdmi->colordepth; return 0; } else if (property == hdmi->hdmi_output_property) { *val = hdmi->hdmi_output; return 0; } else if (property == hdmi->colordepth_capacity) { *val = BIT(ROCKCHIP_HDMI_DEPTH_8); /* RK3368 only support 8bit */ if (hdmi->unsupported_deep_color) return 0; if (info->edid_hdmi_dc_modes & DRM_EDID_HDMI_DC_30) *val |= BIT(ROCKCHIP_HDMI_DEPTH_10); if (info->edid_hdmi_dc_modes & DRM_EDID_HDMI_DC_36) *val |= BIT(ROCKCHIP_HDMI_DEPTH_12); if (info->edid_hdmi_dc_modes & DRM_EDID_HDMI_DC_48) *val |= BIT(ROCKCHIP_HDMI_DEPTH_16); if (info->hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_30) *val |= BIT(ROCKCHIP_HDMI_DEPTH_420_10); if (info->hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_36) *val |= BIT(ROCKCHIP_HDMI_DEPTH_420_12); if (info->hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_48) *val |= BIT(ROCKCHIP_HDMI_DEPTH_420_16); return 0; } else if (property == hdmi->outputmode_capacity) { *val = BIT(DRM_HDMI_OUTPUT_DEFAULT_RGB); if (info->color_formats & DRM_COLOR_FORMAT_YCRCB444) *val |= BIT(DRM_HDMI_OUTPUT_YCBCR444); if (info->color_formats & DRM_COLOR_FORMAT_YCRCB422) *val |= BIT(DRM_HDMI_OUTPUT_YCBCR422); if (connector->ycbcr_420_allowed && info->color_formats & DRM_COLOR_FORMAT_YCRCB420) *val |= BIT(DRM_HDMI_OUTPUT_YCBCR420); return 0; } else if (property == hdmi->quant_range) { *val = hdmi->hdmi_quant_range; return 0; } else if (property == config->hdr_output_metadata_property) { *val = state->hdr_output_metadata ? state->hdr_output_metadata->base.id : 0; return 0; } else if (property == hdmi->colorimetry_property) { *val = hdmi->colorimetry; return 0; } else if (property == private->connector_id_prop) { *val = hdmi->id; return 0; } else if (property == hdmi->output_hdmi_dvi) { *val = hdmi->force_output; return 0; } else if (property == hdmi->output_type_capacity) { *val = dw_hdmi_get_output_type_cap(hdmi->hdmi); return 0; } DRM_ERROR("failed to get rockchip hdmi connector property %s\n", property->name); return -EINVAL; } static const struct dw_hdmi_property_ops dw_hdmi_rockchip_property_ops = { .attach_properties = dw_hdmi_rockchip_attach_properties, .destroy_properties = dw_hdmi_rockchip_destroy_properties, .set_property = dw_hdmi_rockchip_set_property, .get_property = dw_hdmi_rockchip_get_property, }; static const struct drm_encoder_helper_funcs dw_hdmi_rockchip_encoder_helper_funcs = { .enable = dw_hdmi_rockchip_encoder_enable, .disable = dw_hdmi_rockchip_encoder_disable, .atomic_check = dw_hdmi_rockchip_encoder_atomic_check, .mode_set = dw_hdmi_rockchip_encoder_mode_set, }; static void dw_hdmi_rockchip_genphy_disable(struct dw_hdmi *dw_hdmi, void *data) { struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data; while (hdmi->phy->power_count > 0) phy_power_off(hdmi->phy); } static int dw_hdmi_rockchip_genphy_init(struct dw_hdmi *dw_hdmi, void *data, struct drm_display_mode *mode) { struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data; dw_hdmi_rockchip_genphy_disable(dw_hdmi, data); dw_hdmi_set_high_tmds_clock_ratio(dw_hdmi); return phy_power_on(hdmi->phy); } static void dw_hdmi_rk3228_setup_hpd(struct dw_hdmi *dw_hdmi, void *data) { struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data; dw_hdmi_phy_setup_hpd(dw_hdmi, data); regmap_write(hdmi->regmap, RK3228_GRF_SOC_CON2, RK3228_DDC_MASK_EN); regmap_write(hdmi->regmap, RK3228_GRF_SOC_CON6, RK3228_IO_3V_DOMAIN); } static enum drm_connector_status dw_hdmi_rk3328_read_hpd(struct dw_hdmi *dw_hdmi, void *data) { struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data; enum drm_connector_status status; status = dw_hdmi_phy_read_hpd(dw_hdmi, data); if (status == connector_status_connected) regmap_write(hdmi->regmap, RK3328_GRF_SOC_CON4, HIWORD_UPDATE(RK3328_HDMI_SDA_5V | RK3328_HDMI_SCL_5V, RK3328_HDMI_SDA_5V | RK3328_HDMI_SCL_5V)); else regmap_write(hdmi->regmap, RK3328_GRF_SOC_CON4, HIWORD_UPDATE(0, RK3328_HDMI_SDA_5V | RK3328_HDMI_SCL_5V)); return status; } static void dw_hdmi_rk3328_setup_hpd(struct dw_hdmi *dw_hdmi, void *data) { struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data; dw_hdmi_phy_setup_hpd(dw_hdmi, data); /* Enable and map pins to 3V grf-controlled io-voltage */ regmap_write(hdmi->regmap, RK3328_GRF_SOC_CON4, HIWORD_UPDATE(0, RK3328_HDMI_HPD_SARADC | RK3328_HDMI_CEC_5V | RK3328_HDMI_SDA_5V | RK3328_HDMI_SCL_5V | RK3328_HDMI_HPD_5V)); regmap_write(hdmi->regmap, RK3328_GRF_SOC_CON3, HIWORD_UPDATE(0, RK3328_HDMI_SDA5V_GRF | RK3328_HDMI_SCL5V_GRF | RK3328_HDMI_HPD5V_GRF | RK3328_HDMI_CEC5V_GRF)); regmap_write(hdmi->regmap, RK3328_GRF_SOC_CON2, HIWORD_UPDATE(RK3328_HDMI_SDAIN_MSK | RK3328_HDMI_SCLIN_MSK, RK3328_HDMI_SDAIN_MSK | RK3328_HDMI_SCLIN_MSK | RK3328_HDMI_HPD_IOE)); } static const struct dw_hdmi_phy_ops rk3228_hdmi_phy_ops = { .init = dw_hdmi_rockchip_genphy_init, .disable = dw_hdmi_rockchip_genphy_disable, .read_hpd = dw_hdmi_phy_read_hpd, .update_hpd = dw_hdmi_phy_update_hpd, .setup_hpd = dw_hdmi_rk3228_setup_hpd, }; static struct rockchip_hdmi_chip_data rk3228_chip_data = { .lcdsel_grf_reg = -1, }; static const struct dw_hdmi_plat_data rk3228_hdmi_drv_data = { .mode_valid = dw_hdmi_rockchip_mode_valid, .phy_config = rockchip_phy_config, .phy_data = &rk3228_chip_data, .phy_ops = &rk3228_hdmi_phy_ops, .phy_name = "inno_dw_hdmi_phy", .phy_force_vendor = true, .ycbcr_420_allowed = true, }; static struct rockchip_hdmi_chip_data rk3288_chip_data = { .lcdsel_grf_reg = RK3288_GRF_SOC_CON6, .lcdsel_big = HIWORD_UPDATE(0, RK3288_HDMI_LCDC_SEL), .lcdsel_lit = HIWORD_UPDATE(RK3288_HDMI_LCDC_SEL, RK3288_HDMI_LCDC_SEL), }; static const struct dw_hdmi_plat_data rk3288_hdmi_drv_data = { .mode_valid = dw_hdmi_rockchip_mode_valid, .mpll_cfg = rockchip_mpll_cfg, .mpll_cfg_420 = rockchip_rk3288w_mpll_cfg_420, .cur_ctr = rockchip_cur_ctr, .phy_config = rockchip_phy_config, .phy_data = &rk3288_chip_data, .tmds_n_table = rockchip_werid_tmds_n_table, .ycbcr_420_allowed = true, }; static const struct dw_hdmi_phy_ops rk3328_hdmi_phy_ops = { .init = dw_hdmi_rockchip_genphy_init, .disable = dw_hdmi_rockchip_genphy_disable, .read_hpd = dw_hdmi_rk3328_read_hpd, .update_hpd = dw_hdmi_phy_update_hpd, .setup_hpd = dw_hdmi_rk3328_setup_hpd, }; static enum drm_connector_status dw_hdmi_rk3528_read_hpd(struct dw_hdmi *dw_hdmi, void *data) { return dw_hdmi_phy_read_hpd(dw_hdmi, data); } static const struct dw_hdmi_phy_ops rk3528_hdmi_phy_ops = { .init = dw_hdmi_rockchip_genphy_init, .disable = dw_hdmi_rockchip_genphy_disable, .read_hpd = dw_hdmi_rk3528_read_hpd, .update_hpd = dw_hdmi_phy_update_hpd, .setup_hpd = dw_hdmi_phy_setup_hpd, }; static struct rockchip_hdmi_chip_data rk3328_chip_data = { .lcdsel_grf_reg = -1, }; static const struct dw_hdmi_plat_data rk3328_hdmi_drv_data = { .mode_valid = dw_hdmi_rockchip_mode_valid, .mpll_cfg = rockchip_mpll_cfg, .cur_ctr = rockchip_cur_ctr, .phy_config = rockchip_phy_config, .phy_data = &rk3328_chip_data, .phy_ops = &rk3328_hdmi_phy_ops, .phy_name = "inno_dw_hdmi_phy2", .phy_force_vendor = true, .ycbcr_420_allowed = true, }; static struct rockchip_hdmi_chip_data rk3368_chip_data = { .lcdsel_grf_reg = -1, }; static const struct dw_hdmi_plat_data rk3368_hdmi_drv_data = { .mode_valid = dw_hdmi_rockchip_mode_valid, .mpll_cfg = rockchip_mpll_cfg, .mpll_cfg_420 = rockchip_mpll_cfg_420, .cur_ctr = rockchip_cur_ctr, .phy_config = rockchip_phy_config, .phy_data = &rk3368_chip_data, .ycbcr_420_allowed = true, }; static struct rockchip_hdmi_chip_data rk3399_chip_data = { .lcdsel_grf_reg = RK3399_GRF_SOC_CON20, .lcdsel_big = HIWORD_UPDATE(0, RK3399_HDMI_LCDC_SEL), .lcdsel_lit = HIWORD_UPDATE(RK3399_HDMI_LCDC_SEL, RK3399_HDMI_LCDC_SEL), }; static const struct dw_hdmi_plat_data rk3399_hdmi_drv_data = { .mode_valid = dw_hdmi_rockchip_mode_valid, .mpll_cfg = rockchip_mpll_cfg, .mpll_cfg_420 = rockchip_mpll_cfg_420, .cur_ctr = rockchip_cur_ctr, .phy_config = rockchip_phy_config, .phy_data = &rk3399_chip_data, .ycbcr_420_allowed = true, }; static struct rockchip_hdmi_chip_data rk3528_chip_data = { .lcdsel_grf_reg = -1, }; static const struct dw_hdmi_plat_data rk3528_hdmi_drv_data = { .mode_valid = dw_hdmi_rockchip_mode_valid, .mpll_cfg = rockchip_mpll_cfg, .cur_ctr = rockchip_cur_ctr, .phy_config = rockchip_phy_config, .phy_data = &rk3528_chip_data, .phy_ops = &rk3528_hdmi_phy_ops, .phy_name = "inno_dw_hdmi_phy2", .phy_force_vendor = true, .ycbcr_420_allowed = true, }; static struct rockchip_hdmi_chip_data rk3568_chip_data = { .lcdsel_grf_reg = -1, .ddc_en_reg = RK3568_GRF_VO_CON1, }; static const struct dw_hdmi_plat_data rk3568_hdmi_drv_data = { .mode_valid = dw_hdmi_rockchip_mode_valid, .mpll_cfg = rockchip_mpll_cfg_rk356x, .mpll_cfg_420 = rockchip_mpll_cfg_420, .cur_ctr = rockchip_cur_ctr_rk356x, .phy_config = rockchip_phy_config, .phy_data = &rk3568_chip_data, .ycbcr_420_allowed = true, }; static const struct of_device_id dw_hdmi_rockchip_dt_ids[] = { { .compatible = "rockchip,rk3228-dw-hdmi", .data = &rk3228_hdmi_drv_data }, { .compatible = "rockchip,rk3288-dw-hdmi", .data = &rk3288_hdmi_drv_data }, { .compatible = "rockchip,rk3328-dw-hdmi", .data = &rk3328_hdmi_drv_data }, { .compatible = "rockchip,rk3368-dw-hdmi", .data = &rk3368_hdmi_drv_data }, { .compatible = "rockchip,rk3399-dw-hdmi", .data = &rk3399_hdmi_drv_data }, { .compatible = "rockchip,rk3528-dw-hdmi", .data = &rk3528_hdmi_drv_data }, { .compatible = "rockchip,rk3568-dw-hdmi", .data = &rk3568_hdmi_drv_data }, {}, }; MODULE_DEVICE_TABLE(of, dw_hdmi_rockchip_dt_ids); static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master, void *data) { struct platform_device *pdev = to_platform_device(dev); struct dw_hdmi_plat_data *plat_data; const struct of_device_id *match; struct drm_device *drm = data; struct drm_encoder *encoder; struct rockchip_hdmi *hdmi; int ret, id; if (!pdev->dev.of_node) return -ENODEV; hdmi = devm_kzalloc(&pdev->dev, sizeof(*hdmi), GFP_KERNEL); if (!hdmi) return -ENOMEM; match = of_match_node(dw_hdmi_rockchip_dt_ids, pdev->dev.of_node); plat_data = devm_kmemdup(&pdev->dev, match->data, sizeof(*plat_data), GFP_KERNEL); if (!plat_data) return -ENOMEM; id = of_alias_get_id(dev->of_node, "hdmi"); if (id < 0) id = 0; hdmi->id = id; hdmi->dev = &pdev->dev; hdmi->chip_data = plat_data->phy_data; plat_data->phy_data = hdmi; plat_data->get_input_bus_format = dw_hdmi_rockchip_get_input_bus_format; plat_data->get_output_bus_format = dw_hdmi_rockchip_get_output_bus_format; plat_data->get_enc_in_encoding = dw_hdmi_rockchip_get_enc_in_encoding; plat_data->get_enc_out_encoding = dw_hdmi_rockchip_get_enc_out_encoding; plat_data->get_quant_range = dw_hdmi_rockchip_get_quant_range; plat_data->get_hdr_property = dw_hdmi_rockchip_get_hdr_property; plat_data->get_hdr_blob = dw_hdmi_rockchip_get_hdr_blob; plat_data->get_color_changed = dw_hdmi_rockchip_get_color_changed; plat_data->update_color_format = dw_hdmi_rockchip_update_color_format; plat_data->check_hdr_color_change = dw_hdmi_rockchip_check_hdr_color_change; plat_data->set_prev_bus_format = dw_hdmi_rockchip_set_prev_bus_format; plat_data->set_ddc_io = dw_hdmi_rockchip_set_ddc_io; plat_data->dclk_set = dw_hdmi_rockchip_dclk_set; plat_data->property_ops = &dw_hdmi_rockchip_property_ops; encoder = &hdmi->encoder; encoder->possible_crtcs = drm_of_find_possible_crtcs(drm, dev->of_node); /* * If we failed to find the CRTC(s) which this encoder is * supposed to be connected to, it's because the CRTC has * not been registered yet. Defer probing, and hope that * the required CRTC is added later. */ if (encoder->possible_crtcs == 0) return -EPROBE_DEFER; ret = rockchip_hdmi_parse_dt(hdmi); if (ret) { DRM_DEV_ERROR(hdmi->dev, "Unable to parse OF data\n"); return ret; } if (hdmi->chip_data->ddc_en_reg == RK3568_GRF_VO_CON1) { regmap_write(hdmi->regmap, RK3568_GRF_VO_CON1, HIWORD_UPDATE(RK3568_HDMI_SDAIN_MSK | RK3568_HDMI_SCLIN_MSK, RK3568_HDMI_SDAIN_MSK | RK3568_HDMI_SCLIN_MSK)); } ret = clk_prepare_enable(hdmi->phyref_clk); if (ret) { DRM_DEV_ERROR(hdmi->dev, "Failed to enable HDMI vpll: %d\n", ret); return ret; } ret = clk_prepare_enable(hdmi->hclk_vio); if (ret) { dev_err(hdmi->dev, "Failed to enable HDMI hclk_vio: %d\n", ret); return ret; } ret = clk_prepare_enable(hdmi->hclk_vop); if (ret) { dev_err(hdmi->dev, "Failed to enable HDMI hclk_vop: %d\n", ret); return ret; } hdmi->phy = devm_phy_optional_get(dev, "hdmi"); if (IS_ERR(hdmi->phy)) { hdmi->phy = devm_phy_optional_get(dev, "hdmi_phy"); if (IS_ERR(hdmi->phy)) { ret = PTR_ERR(hdmi->phy); if (ret != -EPROBE_DEFER) DRM_DEV_ERROR(hdmi->dev, "failed to get phy\n"); return ret; } } drm_encoder_helper_add(encoder, &dw_hdmi_rockchip_encoder_helper_funcs); drm_encoder_init(drm, encoder, &dw_hdmi_rockchip_encoder_funcs, DRM_MODE_ENCODER_TMDS, NULL); platform_set_drvdata(pdev, hdmi); hdmi->hdmi = dw_hdmi_bind(pdev, encoder, plat_data); /* * If dw_hdmi_bind() fails we'll never call dw_hdmi_unbind(), * which would have called the encoder cleanup. Do it manually. */ if (IS_ERR(hdmi->hdmi)) { ret = PTR_ERR(hdmi->hdmi); drm_encoder_cleanup(encoder); clk_disable_unprepare(hdmi->phyref_clk); clk_disable_unprepare(hdmi->hclk_vop); } if (plat_data->connector) { hdmi->sub_dev.connector = plat_data->connector; hdmi->sub_dev.of_node = dev->of_node; rockchip_drm_register_sub_dev(&hdmi->sub_dev); } return ret; } static void dw_hdmi_rockchip_unbind(struct device *dev, struct device *master, void *data) { struct rockchip_hdmi *hdmi = dev_get_drvdata(dev); if (hdmi->sub_dev.connector) rockchip_drm_unregister_sub_dev(&hdmi->sub_dev); dw_hdmi_unbind(hdmi->hdmi); clk_disable_unprepare(hdmi->phyref_clk); clk_disable_unprepare(hdmi->hclk_vop); } static const struct component_ops dw_hdmi_rockchip_ops = { .bind = dw_hdmi_rockchip_bind, .unbind = dw_hdmi_rockchip_unbind, }; static int dw_hdmi_rockchip_probe(struct platform_device *pdev) { pm_runtime_enable(&pdev->dev); pm_runtime_get_sync(&pdev->dev); return component_add(&pdev->dev, &dw_hdmi_rockchip_ops); } static void dw_hdmi_rockchip_shutdown(struct platform_device *pdev) { struct rockchip_hdmi *hdmi = platform_get_drvdata(pdev); if (!hdmi) return; if (hdmi->hpd_gpiod) { disable_irq(hdmi->hpd_irq); if (hdmi->hpd_wake_en) disable_irq_wake(hdmi->hpd_irq); } dw_hdmi_suspend(&pdev->dev, hdmi->hdmi); pm_runtime_put_sync(&pdev->dev); } static int dw_hdmi_rockchip_remove(struct platform_device *pdev) { component_del(&pdev->dev, &dw_hdmi_rockchip_ops); pm_runtime_disable(&pdev->dev); return 0; } static int dw_hdmi_rockchip_suspend(struct device *dev) { struct rockchip_hdmi *hdmi = dev_get_drvdata(dev); if (hdmi->hpd_gpiod) disable_irq(hdmi->hpd_irq); dw_hdmi_suspend(dev, hdmi->hdmi); pm_runtime_put_sync(dev); return 0; } static int dw_hdmi_rockchip_resume(struct device *dev) { struct rockchip_hdmi *hdmi = dev_get_drvdata(dev); if (hdmi->hpd_gpiod) { dw_hdmi_rk3528_gpio_hpd_init(hdmi); enable_irq(hdmi->hpd_irq); } pm_runtime_get_sync(dev); dw_hdmi_resume(dev, hdmi->hdmi); return 0; } static const struct dev_pm_ops dw_hdmi_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(dw_hdmi_rockchip_suspend, dw_hdmi_rockchip_resume) }; struct platform_driver dw_hdmi_rockchip_pltfm_driver = { .probe = dw_hdmi_rockchip_probe, .remove = dw_hdmi_rockchip_remove, .shutdown = dw_hdmi_rockchip_shutdown, .driver = { .name = "dwhdmi-rockchip", .of_match_table = dw_hdmi_rockchip_dt_ids, .pm = &dw_hdmi_pm_ops, }, };