1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * Copyright (c) 2018 Jernej Skrabec <jernej.skrabec@siol.net> 4 */ 5 6 #include <linux/component.h> 7 #include <linux/module.h> 8 #include <linux/platform_device.h> 9 10 #include <drm/drm_of.h> 11 #include <drm/drmP.h> 12 #include <drm/drm_crtc_helper.h> 13 14 #include "sun8i_dw_hdmi.h" 15 16 static void sun8i_dw_hdmi_encoder_mode_set(struct drm_encoder *encoder, 17 struct drm_display_mode *mode, 18 struct drm_display_mode *adj_mode) 19 { 20 struct sun8i_dw_hdmi *hdmi = encoder_to_sun8i_dw_hdmi(encoder); 21 22 clk_set_rate(hdmi->clk_tmds, mode->crtc_clock * 1000); 23 } 24 25 static const struct drm_encoder_helper_funcs 26 sun8i_dw_hdmi_encoder_helper_funcs = { 27 .mode_set = sun8i_dw_hdmi_encoder_mode_set, 28 }; 29 30 static const struct drm_encoder_funcs sun8i_dw_hdmi_encoder_funcs = { 31 .destroy = drm_encoder_cleanup, 32 }; 33 34 static enum drm_mode_status 35 sun8i_dw_hdmi_mode_valid(struct drm_connector *connector, 36 const struct drm_display_mode *mode) 37 { 38 if (mode->clock > 297000) 39 return MODE_CLOCK_HIGH; 40 41 return MODE_OK; 42 } 43 44 static int sun8i_dw_hdmi_bind(struct device *dev, struct device *master, 45 void *data) 46 { 47 struct platform_device *pdev = to_platform_device(dev); 48 struct dw_hdmi_plat_data *plat_data; 49 struct drm_device *drm = data; 50 struct device_node *phy_node; 51 struct drm_encoder *encoder; 52 struct sun8i_dw_hdmi *hdmi; 53 int ret; 54 55 if (!pdev->dev.of_node) 56 return -ENODEV; 57 58 hdmi = devm_kzalloc(&pdev->dev, sizeof(*hdmi), GFP_KERNEL); 59 if (!hdmi) 60 return -ENOMEM; 61 62 plat_data = &hdmi->plat_data; 63 hdmi->dev = &pdev->dev; 64 encoder = &hdmi->encoder; 65 66 encoder->possible_crtcs = drm_of_find_possible_crtcs(drm, dev->of_node); 67 /* 68 * If we failed to find the CRTC(s) which this encoder is 69 * supposed to be connected to, it's because the CRTC has 70 * not been registered yet. Defer probing, and hope that 71 * the required CRTC is added later. 72 */ 73 if (encoder->possible_crtcs == 0) 74 return -EPROBE_DEFER; 75 76 hdmi->rst_ctrl = devm_reset_control_get(dev, "ctrl"); 77 if (IS_ERR(hdmi->rst_ctrl)) { 78 dev_err(dev, "Could not get ctrl reset control\n"); 79 return PTR_ERR(hdmi->rst_ctrl); 80 } 81 82 hdmi->clk_tmds = devm_clk_get(dev, "tmds"); 83 if (IS_ERR(hdmi->clk_tmds)) { 84 dev_err(dev, "Couldn't get the tmds clock\n"); 85 return PTR_ERR(hdmi->clk_tmds); 86 } 87 88 ret = reset_control_deassert(hdmi->rst_ctrl); 89 if (ret) { 90 dev_err(dev, "Could not deassert ctrl reset control\n"); 91 return ret; 92 } 93 94 ret = clk_prepare_enable(hdmi->clk_tmds); 95 if (ret) { 96 dev_err(dev, "Could not enable tmds clock\n"); 97 goto err_assert_ctrl_reset; 98 } 99 100 phy_node = of_parse_phandle(dev->of_node, "phys", 0); 101 if (!phy_node) { 102 dev_err(dev, "Can't found PHY phandle\n"); 103 goto err_disable_clk_tmds; 104 } 105 106 ret = sun8i_hdmi_phy_probe(hdmi, phy_node); 107 of_node_put(phy_node); 108 if (ret) { 109 dev_err(dev, "Couldn't get the HDMI PHY\n"); 110 goto err_disable_clk_tmds; 111 } 112 113 drm_encoder_helper_add(encoder, &sun8i_dw_hdmi_encoder_helper_funcs); 114 drm_encoder_init(drm, encoder, &sun8i_dw_hdmi_encoder_funcs, 115 DRM_MODE_ENCODER_TMDS, NULL); 116 117 sun8i_hdmi_phy_init(hdmi->phy); 118 119 plat_data->mode_valid = &sun8i_dw_hdmi_mode_valid; 120 plat_data->phy_ops = sun8i_hdmi_phy_get_ops(); 121 plat_data->phy_name = "sun8i_dw_hdmi_phy"; 122 plat_data->phy_data = hdmi->phy; 123 124 platform_set_drvdata(pdev, hdmi); 125 126 hdmi->hdmi = dw_hdmi_bind(pdev, encoder, plat_data); 127 128 /* 129 * If dw_hdmi_bind() fails we'll never call dw_hdmi_unbind(), 130 * which would have called the encoder cleanup. Do it manually. 131 */ 132 if (IS_ERR(hdmi->hdmi)) { 133 ret = PTR_ERR(hdmi->hdmi); 134 goto cleanup_encoder; 135 } 136 137 return 0; 138 139 cleanup_encoder: 140 drm_encoder_cleanup(encoder); 141 sun8i_hdmi_phy_remove(hdmi); 142 err_disable_clk_tmds: 143 clk_disable_unprepare(hdmi->clk_tmds); 144 err_assert_ctrl_reset: 145 reset_control_assert(hdmi->rst_ctrl); 146 147 return ret; 148 } 149 150 static void sun8i_dw_hdmi_unbind(struct device *dev, struct device *master, 151 void *data) 152 { 153 struct sun8i_dw_hdmi *hdmi = dev_get_drvdata(dev); 154 155 dw_hdmi_unbind(hdmi->hdmi); 156 sun8i_hdmi_phy_remove(hdmi); 157 clk_disable_unprepare(hdmi->clk_tmds); 158 reset_control_assert(hdmi->rst_ctrl); 159 } 160 161 static const struct component_ops sun8i_dw_hdmi_ops = { 162 .bind = sun8i_dw_hdmi_bind, 163 .unbind = sun8i_dw_hdmi_unbind, 164 }; 165 166 static int sun8i_dw_hdmi_probe(struct platform_device *pdev) 167 { 168 return component_add(&pdev->dev, &sun8i_dw_hdmi_ops); 169 } 170 171 static int sun8i_dw_hdmi_remove(struct platform_device *pdev) 172 { 173 component_del(&pdev->dev, &sun8i_dw_hdmi_ops); 174 175 return 0; 176 } 177 178 static const struct of_device_id sun8i_dw_hdmi_dt_ids[] = { 179 { .compatible = "allwinner,sun8i-a83t-dw-hdmi" }, 180 { /* sentinel */ }, 181 }; 182 MODULE_DEVICE_TABLE(of, sun8i_dw_hdmi_dt_ids); 183 184 struct platform_driver sun8i_dw_hdmi_pltfm_driver = { 185 .probe = sun8i_dw_hdmi_probe, 186 .remove = sun8i_dw_hdmi_remove, 187 .driver = { 188 .name = "sun8i-dw-hdmi", 189 .of_match_table = sun8i_dw_hdmi_dt_ids, 190 }, 191 }; 192 module_platform_driver(sun8i_dw_hdmi_pltfm_driver); 193 194 MODULE_AUTHOR("Jernej Skrabec <jernej.skrabec@siol.net>"); 195 MODULE_DESCRIPTION("Allwinner DW HDMI bridge"); 196 MODULE_LICENSE("GPL"); 197