1 // SPDX-License-Identifier: GPL-2.0 2 /* Copyright (C) 2011-2013 Freescale Semiconductor, Inc. 3 * 4 * derived from imx-hdmi.c(renamed to bridge/dw_hdmi.c now) 5 */ 6 7 #include <linux/component.h> 8 #include <linux/mfd/syscon.h> 9 #include <linux/mfd/syscon/imx6q-iomuxc-gpr.h> 10 #include <linux/module.h> 11 #include <linux/platform_device.h> 12 #include <linux/regmap.h> 13 14 #include <video/imx-ipu-v3.h> 15 16 #include <drm/bridge/dw_hdmi.h> 17 #include <drm/drm_atomic_helper.h> 18 #include <drm/drm_bridge.h> 19 #include <drm/drm_edid.h> 20 #include <drm/drm_encoder.h> 21 #include <drm/drm_managed.h> 22 #include <drm/drm_of.h> 23 #include <drm/drm_simple_kms_helper.h> 24 25 #include "imx-drm.h" 26 27 struct imx_hdmi; 28 29 struct imx_hdmi_encoder { 30 struct drm_encoder encoder; 31 struct imx_hdmi *hdmi; 32 }; 33 34 struct imx_hdmi { 35 struct device *dev; 36 struct drm_bridge *bridge; 37 struct dw_hdmi *hdmi; 38 struct regmap *regmap; 39 }; 40 41 static inline struct imx_hdmi *enc_to_imx_hdmi(struct drm_encoder *e) 42 { 43 return container_of(e, struct imx_hdmi_encoder, encoder)->hdmi; 44 } 45 46 static const struct dw_hdmi_mpll_config imx_mpll_cfg[] = { 47 { 48 45250000, { 49 { 0x01e0, 0x0000 }, 50 { 0x21e1, 0x0000 }, 51 { 0x41e2, 0x0000 } 52 }, 53 }, { 54 92500000, { 55 { 0x0140, 0x0005 }, 56 { 0x2141, 0x0005 }, 57 { 0x4142, 0x0005 }, 58 }, 59 }, { 60 148500000, { 61 { 0x00a0, 0x000a }, 62 { 0x20a1, 0x000a }, 63 { 0x40a2, 0x000a }, 64 }, 65 }, { 66 216000000, { 67 { 0x00a0, 0x000a }, 68 { 0x2001, 0x000f }, 69 { 0x4002, 0x000f }, 70 }, 71 }, { 72 ~0UL, { 73 { 0x0000, 0x0000 }, 74 { 0x0000, 0x0000 }, 75 { 0x0000, 0x0000 }, 76 }, 77 } 78 }; 79 80 static const struct dw_hdmi_curr_ctrl imx_cur_ctr[] = { 81 /* pixelclk bpp8 bpp10 bpp12 */ 82 { 83 54000000, { 0x091c, 0x091c, 0x06dc }, 84 }, { 85 58400000, { 0x091c, 0x06dc, 0x06dc }, 86 }, { 87 72000000, { 0x06dc, 0x06dc, 0x091c }, 88 }, { 89 74250000, { 0x06dc, 0x0b5c, 0x091c }, 90 }, { 91 118800000, { 0x091c, 0x091c, 0x06dc }, 92 }, { 93 216000000, { 0x06dc, 0x0b5c, 0x091c }, 94 }, { 95 ~0UL, { 0x0000, 0x0000, 0x0000 }, 96 }, 97 }; 98 99 /* 100 * Resistance term 133Ohm Cfg 101 * PREEMP config 0.00 102 * TX/CK level 10 103 */ 104 static const struct dw_hdmi_phy_config imx_phy_config[] = { 105 /*pixelclk symbol term vlev */ 106 { 216000000, 0x800d, 0x0005, 0x01ad}, 107 { ~0UL, 0x0000, 0x0000, 0x0000} 108 }; 109 110 static void dw_hdmi_imx_encoder_enable(struct drm_encoder *encoder) 111 { 112 struct imx_hdmi *hdmi = enc_to_imx_hdmi(encoder); 113 int mux = drm_of_encoder_active_port_id(hdmi->dev->of_node, encoder); 114 115 regmap_update_bits(hdmi->regmap, IOMUXC_GPR3, 116 IMX6Q_GPR3_HDMI_MUX_CTL_MASK, 117 mux << IMX6Q_GPR3_HDMI_MUX_CTL_SHIFT); 118 } 119 120 static int dw_hdmi_imx_atomic_check(struct drm_encoder *encoder, 121 struct drm_crtc_state *crtc_state, 122 struct drm_connector_state *conn_state) 123 { 124 struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(crtc_state); 125 126 imx_crtc_state->bus_format = MEDIA_BUS_FMT_RGB888_1X24; 127 imx_crtc_state->di_hsync_pin = 2; 128 imx_crtc_state->di_vsync_pin = 3; 129 130 return 0; 131 } 132 133 static const struct drm_encoder_helper_funcs dw_hdmi_imx_encoder_helper_funcs = { 134 .enable = dw_hdmi_imx_encoder_enable, 135 .atomic_check = dw_hdmi_imx_atomic_check, 136 }; 137 138 static enum drm_mode_status 139 imx6q_hdmi_mode_valid(struct dw_hdmi *hdmi, void *data, 140 const struct drm_display_info *info, 141 const struct drm_display_mode *mode) 142 { 143 if (mode->clock < 13500) 144 return MODE_CLOCK_LOW; 145 /* FIXME: Hardware is capable of 266MHz, but setup data is missing. */ 146 if (mode->clock > 216000) 147 return MODE_CLOCK_HIGH; 148 149 return MODE_OK; 150 } 151 152 static enum drm_mode_status 153 imx6dl_hdmi_mode_valid(struct dw_hdmi *hdmi, void *data, 154 const struct drm_display_info *info, 155 const struct drm_display_mode *mode) 156 { 157 if (mode->clock < 13500) 158 return MODE_CLOCK_LOW; 159 /* FIXME: Hardware is capable of 270MHz, but setup data is missing. */ 160 if (mode->clock > 216000) 161 return MODE_CLOCK_HIGH; 162 163 return MODE_OK; 164 } 165 166 static struct dw_hdmi_plat_data imx6q_hdmi_drv_data = { 167 .mpll_cfg = imx_mpll_cfg, 168 .cur_ctr = imx_cur_ctr, 169 .phy_config = imx_phy_config, 170 .mode_valid = imx6q_hdmi_mode_valid, 171 }; 172 173 static struct dw_hdmi_plat_data imx6dl_hdmi_drv_data = { 174 .mpll_cfg = imx_mpll_cfg, 175 .cur_ctr = imx_cur_ctr, 176 .phy_config = imx_phy_config, 177 .mode_valid = imx6dl_hdmi_mode_valid, 178 }; 179 180 static const struct of_device_id dw_hdmi_imx_dt_ids[] = { 181 { .compatible = "fsl,imx6q-hdmi", 182 .data = &imx6q_hdmi_drv_data 183 }, { 184 .compatible = "fsl,imx6dl-hdmi", 185 .data = &imx6dl_hdmi_drv_data 186 }, 187 {}, 188 }; 189 MODULE_DEVICE_TABLE(of, dw_hdmi_imx_dt_ids); 190 191 static int dw_hdmi_imx_bind(struct device *dev, struct device *master, 192 void *data) 193 { 194 struct drm_device *drm = data; 195 struct imx_hdmi_encoder *hdmi_encoder; 196 struct drm_encoder *encoder; 197 int ret; 198 199 hdmi_encoder = drmm_simple_encoder_alloc(drm, struct imx_hdmi_encoder, 200 encoder, DRM_MODE_ENCODER_TMDS); 201 if (IS_ERR(hdmi_encoder)) 202 return PTR_ERR(hdmi_encoder); 203 204 hdmi_encoder->hdmi = dev_get_drvdata(dev); 205 encoder = &hdmi_encoder->encoder; 206 207 ret = imx_drm_encoder_parse_of(drm, encoder, dev->of_node); 208 if (ret) 209 return ret; 210 211 drm_encoder_helper_add(encoder, &dw_hdmi_imx_encoder_helper_funcs); 212 213 return drm_bridge_attach(encoder, hdmi_encoder->hdmi->bridge, NULL, 0); 214 } 215 216 static const struct component_ops dw_hdmi_imx_ops = { 217 .bind = dw_hdmi_imx_bind, 218 }; 219 220 static int dw_hdmi_imx_probe(struct platform_device *pdev) 221 { 222 struct device_node *np = pdev->dev.of_node; 223 const struct of_device_id *match = of_match_node(dw_hdmi_imx_dt_ids, np); 224 struct imx_hdmi *hdmi; 225 int ret; 226 227 hdmi = devm_kzalloc(&pdev->dev, sizeof(*hdmi), GFP_KERNEL); 228 if (!hdmi) 229 return -ENOMEM; 230 231 platform_set_drvdata(pdev, hdmi); 232 hdmi->dev = &pdev->dev; 233 234 hdmi->regmap = syscon_regmap_lookup_by_phandle(np, "gpr"); 235 if (IS_ERR(hdmi->regmap)) { 236 dev_err(hdmi->dev, "Unable to get gpr\n"); 237 return PTR_ERR(hdmi->regmap); 238 } 239 240 hdmi->hdmi = dw_hdmi_probe(pdev, match->data); 241 if (IS_ERR(hdmi->hdmi)) 242 return PTR_ERR(hdmi->hdmi); 243 244 hdmi->bridge = of_drm_find_bridge(np); 245 if (!hdmi->bridge) { 246 dev_err(hdmi->dev, "Unable to find bridge\n"); 247 dw_hdmi_remove(hdmi->hdmi); 248 return -ENODEV; 249 } 250 251 ret = component_add(&pdev->dev, &dw_hdmi_imx_ops); 252 if (ret) 253 dw_hdmi_remove(hdmi->hdmi); 254 255 return ret; 256 } 257 258 static void dw_hdmi_imx_remove(struct platform_device *pdev) 259 { 260 struct imx_hdmi *hdmi = platform_get_drvdata(pdev); 261 262 component_del(&pdev->dev, &dw_hdmi_imx_ops); 263 dw_hdmi_remove(hdmi->hdmi); 264 } 265 266 static struct platform_driver dw_hdmi_imx_platform_driver = { 267 .probe = dw_hdmi_imx_probe, 268 .remove_new = dw_hdmi_imx_remove, 269 .driver = { 270 .name = "dwhdmi-imx", 271 .of_match_table = dw_hdmi_imx_dt_ids, 272 }, 273 }; 274 275 module_platform_driver(dw_hdmi_imx_platform_driver); 276 277 MODULE_AUTHOR("Andy Yan <andy.yan@rock-chips.com>"); 278 MODULE_AUTHOR("Yakir Yang <ykk@rock-chips.com>"); 279 MODULE_DESCRIPTION("IMX6 Specific DW-HDMI Driver Extension"); 280 MODULE_LICENSE("GPL"); 281 MODULE_ALIAS("platform:dwhdmi-imx"); 282