1 // SPDX-License-Identifier: GPL-2.0+ 2 3 /* 4 * Copyright (C) 2022 Pengutronix, Lucas Stach <kernel@pengutronix.de> 5 */ 6 7 #include <linux/clk.h> 8 #include <linux/component.h> 9 #include <linux/mod_devicetable.h> 10 #include <linux/module.h> 11 #include <linux/platform_device.h> 12 #include <drm/bridge/dw_hdmi.h> 13 #include <drm/drm_modes.h> 14 #include <drm/drm_of.h> 15 16 struct imx8mp_hdmi { 17 struct dw_hdmi_plat_data plat_data; 18 struct dw_hdmi *dw_hdmi; 19 struct clk *pixclk; 20 }; 21 22 static enum drm_mode_status 23 imx8mp_hdmi_mode_valid(struct dw_hdmi *dw_hdmi, void *data, 24 const struct drm_display_info *info, 25 const struct drm_display_mode *mode) 26 { 27 struct imx8mp_hdmi *hdmi = (struct imx8mp_hdmi *)data; 28 long round_rate; 29 30 if (mode->clock < 13500) 31 return MODE_CLOCK_LOW; 32 33 if (mode->clock > 297000) 34 return MODE_CLOCK_HIGH; 35 36 round_rate = clk_round_rate(hdmi->pixclk, mode->clock * 1000); 37 /* imx8mp's pixel clock generator (fsl-samsung-hdmi) cannot generate 38 * all possible frequencies, so allow some tolerance to support more 39 * modes. 40 * Allow 0.5% difference allowed in various standards (VESA, CEA861) 41 * 0.5% = 5/1000 tolerance (mode->clock is 1/1000) 42 */ 43 if (abs(round_rate - mode->clock * 1000) > mode->clock * 5) 44 return MODE_CLOCK_RANGE; 45 46 /* We don't support double-clocked and Interlaced modes */ 47 if ((mode->flags & DRM_MODE_FLAG_DBLCLK) || 48 (mode->flags & DRM_MODE_FLAG_INTERLACE)) 49 return MODE_BAD; 50 51 return MODE_OK; 52 } 53 54 static int imx8mp_hdmi_phy_init(struct dw_hdmi *dw_hdmi, void *data, 55 const struct drm_display_info *display, 56 const struct drm_display_mode *mode) 57 { 58 return 0; 59 } 60 61 static void imx8mp_hdmi_phy_disable(struct dw_hdmi *dw_hdmi, void *data) 62 { 63 } 64 65 static void im8mp_hdmi_phy_setup_hpd(struct dw_hdmi *hdmi, void *data) 66 { 67 /* 68 * Just release PHY core from reset, all other power management is done 69 * by the PHY driver. 70 */ 71 dw_hdmi_phy_gen1_reset(hdmi); 72 73 dw_hdmi_phy_setup_hpd(hdmi, data); 74 } 75 76 static const struct dw_hdmi_phy_ops imx8mp_hdmi_phy_ops = { 77 .init = imx8mp_hdmi_phy_init, 78 .disable = imx8mp_hdmi_phy_disable, 79 .setup_hpd = im8mp_hdmi_phy_setup_hpd, 80 .read_hpd = dw_hdmi_phy_read_hpd, 81 .update_hpd = dw_hdmi_phy_update_hpd, 82 }; 83 84 static int imx8mp_dw_hdmi_bind(struct device *dev) 85 { 86 struct platform_device *pdev = to_platform_device(dev); 87 struct imx8mp_hdmi *hdmi = dev_get_drvdata(dev); 88 int ret; 89 90 ret = component_bind_all(dev, &hdmi->plat_data); 91 if (ret) 92 return dev_err_probe(dev, ret, "component_bind_all failed!\n"); 93 94 hdmi->dw_hdmi = dw_hdmi_probe(pdev, &hdmi->plat_data); 95 if (IS_ERR(hdmi->dw_hdmi)) { 96 component_unbind_all(dev, &hdmi->plat_data); 97 return PTR_ERR(hdmi->dw_hdmi); 98 } 99 100 return 0; 101 } 102 103 static void imx8mp_dw_hdmi_unbind(struct device *dev) 104 { 105 struct imx8mp_hdmi *hdmi = dev_get_drvdata(dev); 106 107 dw_hdmi_remove(hdmi->dw_hdmi); 108 109 component_unbind_all(dev, &hdmi->plat_data); 110 } 111 112 static const struct component_master_ops imx8mp_dw_hdmi_ops = { 113 .bind = imx8mp_dw_hdmi_bind, 114 .unbind = imx8mp_dw_hdmi_unbind, 115 }; 116 117 static int imx8mp_dw_hdmi_probe(struct platform_device *pdev) 118 { 119 struct device *dev = &pdev->dev; 120 struct dw_hdmi_plat_data *plat_data; 121 struct component_match *match = NULL; 122 struct device_node *remote; 123 struct imx8mp_hdmi *hdmi; 124 125 hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL); 126 if (!hdmi) 127 return -ENOMEM; 128 129 plat_data = &hdmi->plat_data; 130 131 hdmi->pixclk = devm_clk_get(dev, "pix"); 132 if (IS_ERR(hdmi->pixclk)) 133 return dev_err_probe(dev, PTR_ERR(hdmi->pixclk), 134 "Unable to get pixel clock\n"); 135 136 plat_data->mode_valid = imx8mp_hdmi_mode_valid; 137 plat_data->phy_ops = &imx8mp_hdmi_phy_ops; 138 plat_data->phy_name = "SAMSUNG HDMI TX PHY"; 139 plat_data->priv_data = hdmi; 140 plat_data->phy_force_vendor = true; 141 142 platform_set_drvdata(pdev, hdmi); 143 144 /* port@2 is for hdmi_pai device */ 145 remote = of_graph_get_remote_node(pdev->dev.of_node, 2, 0); 146 if (!remote) { 147 hdmi->dw_hdmi = dw_hdmi_probe(pdev, plat_data); 148 if (IS_ERR(hdmi->dw_hdmi)) 149 return PTR_ERR(hdmi->dw_hdmi); 150 } else { 151 drm_of_component_match_add(dev, &match, component_compare_of, remote); 152 153 of_node_put(remote); 154 155 return component_master_add_with_match(dev, &imx8mp_dw_hdmi_ops, match); 156 } 157 158 return 0; 159 } 160 161 static void imx8mp_dw_hdmi_remove(struct platform_device *pdev) 162 { 163 struct imx8mp_hdmi *hdmi = platform_get_drvdata(pdev); 164 struct device_node *remote; 165 166 remote = of_graph_get_remote_node(pdev->dev.of_node, 2, 0); 167 if (remote) { 168 of_node_put(remote); 169 170 component_master_del(&pdev->dev, &imx8mp_dw_hdmi_ops); 171 } else { 172 dw_hdmi_remove(hdmi->dw_hdmi); 173 } 174 } 175 176 static int imx8mp_dw_hdmi_pm_suspend(struct device *dev) 177 { 178 return 0; 179 } 180 181 static int imx8mp_dw_hdmi_pm_resume(struct device *dev) 182 { 183 struct imx8mp_hdmi *hdmi = dev_get_drvdata(dev); 184 185 dw_hdmi_resume(hdmi->dw_hdmi); 186 187 return 0; 188 } 189 190 static const struct dev_pm_ops imx8mp_dw_hdmi_pm_ops = { 191 SYSTEM_SLEEP_PM_OPS(imx8mp_dw_hdmi_pm_suspend, imx8mp_dw_hdmi_pm_resume) 192 }; 193 194 static const struct of_device_id imx8mp_dw_hdmi_of_table[] = { 195 { .compatible = "fsl,imx8mp-hdmi-tx" }, 196 { /* Sentinel */ } 197 }; 198 MODULE_DEVICE_TABLE(of, imx8mp_dw_hdmi_of_table); 199 200 static struct platform_driver imx8mp_dw_hdmi_platform_driver = { 201 .probe = imx8mp_dw_hdmi_probe, 202 .remove = imx8mp_dw_hdmi_remove, 203 .driver = { 204 .name = "imx8mp-dw-hdmi-tx", 205 .of_match_table = imx8mp_dw_hdmi_of_table, 206 .pm = pm_ptr(&imx8mp_dw_hdmi_pm_ops), 207 }, 208 }; 209 210 module_platform_driver(imx8mp_dw_hdmi_platform_driver); 211 212 MODULE_DESCRIPTION("i.MX8MP HDMI encoder driver"); 213 MODULE_LICENSE("GPL"); 214