1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * Copyright (C) 2017 Free Electrons 4 * Maxime Ripard <maxime.ripard@free-electrons.com> 5 */ 6 7 #include <linux/clk.h> 8 9 #include <drm/drmP.h> 10 #include <drm/drm_atomic_helper.h> 11 #include <drm/drm_crtc_helper.h> 12 #include <drm/drm_of.h> 13 #include <drm/drm_panel.h> 14 15 #include "sun4i_crtc.h" 16 #include "sun4i_tcon.h" 17 #include "sun4i_lvds.h" 18 19 struct sun4i_lvds { 20 struct drm_connector connector; 21 struct drm_encoder encoder; 22 23 struct sun4i_tcon *tcon; 24 }; 25 26 static inline struct sun4i_lvds * 27 drm_connector_to_sun4i_lvds(struct drm_connector *connector) 28 { 29 return container_of(connector, struct sun4i_lvds, 30 connector); 31 } 32 33 static inline struct sun4i_lvds * 34 drm_encoder_to_sun4i_lvds(struct drm_encoder *encoder) 35 { 36 return container_of(encoder, struct sun4i_lvds, 37 encoder); 38 } 39 40 static int sun4i_lvds_get_modes(struct drm_connector *connector) 41 { 42 struct sun4i_lvds *lvds = 43 drm_connector_to_sun4i_lvds(connector); 44 struct sun4i_tcon *tcon = lvds->tcon; 45 46 return drm_panel_get_modes(tcon->panel); 47 } 48 49 static struct drm_connector_helper_funcs sun4i_lvds_con_helper_funcs = { 50 .get_modes = sun4i_lvds_get_modes, 51 }; 52 53 static void 54 sun4i_lvds_connector_destroy(struct drm_connector *connector) 55 { 56 struct sun4i_lvds *lvds = drm_connector_to_sun4i_lvds(connector); 57 struct sun4i_tcon *tcon = lvds->tcon; 58 59 drm_panel_detach(tcon->panel); 60 drm_connector_cleanup(connector); 61 } 62 63 static const struct drm_connector_funcs sun4i_lvds_con_funcs = { 64 .fill_modes = drm_helper_probe_single_connector_modes, 65 .destroy = sun4i_lvds_connector_destroy, 66 .reset = drm_atomic_helper_connector_reset, 67 .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, 68 .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, 69 }; 70 71 static void sun4i_lvds_encoder_enable(struct drm_encoder *encoder) 72 { 73 struct sun4i_lvds *lvds = drm_encoder_to_sun4i_lvds(encoder); 74 struct sun4i_tcon *tcon = lvds->tcon; 75 76 DRM_DEBUG_DRIVER("Enabling LVDS output\n"); 77 78 if (!IS_ERR(tcon->panel)) { 79 drm_panel_prepare(tcon->panel); 80 drm_panel_enable(tcon->panel); 81 } 82 } 83 84 static void sun4i_lvds_encoder_disable(struct drm_encoder *encoder) 85 { 86 struct sun4i_lvds *lvds = drm_encoder_to_sun4i_lvds(encoder); 87 struct sun4i_tcon *tcon = lvds->tcon; 88 89 DRM_DEBUG_DRIVER("Disabling LVDS output\n"); 90 91 if (!IS_ERR(tcon->panel)) { 92 drm_panel_disable(tcon->panel); 93 drm_panel_unprepare(tcon->panel); 94 } 95 } 96 97 static enum drm_mode_status sun4i_lvds_encoder_mode_valid(struct drm_encoder *crtc, 98 const struct drm_display_mode *mode) 99 { 100 struct sun4i_lvds *lvds = drm_encoder_to_sun4i_lvds(crtc); 101 struct sun4i_tcon *tcon = lvds->tcon; 102 u32 hsync = mode->hsync_end - mode->hsync_start; 103 u32 vsync = mode->vsync_end - mode->vsync_start; 104 unsigned long rate = mode->clock * 1000; 105 long rounded_rate; 106 107 DRM_DEBUG_DRIVER("Validating modes...\n"); 108 109 if (hsync < 1) 110 return MODE_HSYNC_NARROW; 111 112 if (hsync > 0x3ff) 113 return MODE_HSYNC_WIDE; 114 115 if ((mode->hdisplay < 1) || (mode->htotal < 1)) 116 return MODE_H_ILLEGAL; 117 118 if ((mode->hdisplay > 0x7ff) || (mode->htotal > 0xfff)) 119 return MODE_BAD_HVALUE; 120 121 DRM_DEBUG_DRIVER("Horizontal parameters OK\n"); 122 123 if (vsync < 1) 124 return MODE_VSYNC_NARROW; 125 126 if (vsync > 0x3ff) 127 return MODE_VSYNC_WIDE; 128 129 if ((mode->vdisplay < 1) || (mode->vtotal < 1)) 130 return MODE_V_ILLEGAL; 131 132 if ((mode->vdisplay > 0x7ff) || (mode->vtotal > 0xfff)) 133 return MODE_BAD_VVALUE; 134 135 DRM_DEBUG_DRIVER("Vertical parameters OK\n"); 136 137 tcon->dclk_min_div = 7; 138 tcon->dclk_max_div = 7; 139 rounded_rate = clk_round_rate(tcon->dclk, rate); 140 if (rounded_rate < rate) 141 return MODE_CLOCK_LOW; 142 143 if (rounded_rate > rate) 144 return MODE_CLOCK_HIGH; 145 146 DRM_DEBUG_DRIVER("Clock rate OK\n"); 147 148 return MODE_OK; 149 } 150 151 static const struct drm_encoder_helper_funcs sun4i_lvds_enc_helper_funcs = { 152 .disable = sun4i_lvds_encoder_disable, 153 .enable = sun4i_lvds_encoder_enable, 154 .mode_valid = sun4i_lvds_encoder_mode_valid, 155 }; 156 157 static const struct drm_encoder_funcs sun4i_lvds_enc_funcs = { 158 .destroy = drm_encoder_cleanup, 159 }; 160 161 int sun4i_lvds_init(struct drm_device *drm, struct sun4i_tcon *tcon) 162 { 163 struct drm_encoder *encoder; 164 struct drm_bridge *bridge; 165 struct sun4i_lvds *lvds; 166 int ret; 167 168 lvds = devm_kzalloc(drm->dev, sizeof(*lvds), GFP_KERNEL); 169 if (!lvds) 170 return -ENOMEM; 171 lvds->tcon = tcon; 172 encoder = &lvds->encoder; 173 174 ret = drm_of_find_panel_or_bridge(tcon->dev->of_node, 1, 0, 175 &tcon->panel, &bridge); 176 if (ret) { 177 dev_info(drm->dev, "No panel or bridge found... LVDS output disabled\n"); 178 return 0; 179 } 180 181 drm_encoder_helper_add(&lvds->encoder, 182 &sun4i_lvds_enc_helper_funcs); 183 ret = drm_encoder_init(drm, 184 &lvds->encoder, 185 &sun4i_lvds_enc_funcs, 186 DRM_MODE_ENCODER_LVDS, 187 NULL); 188 if (ret) { 189 dev_err(drm->dev, "Couldn't initialise the lvds encoder\n"); 190 goto err_out; 191 } 192 193 /* The LVDS encoder can only work with the TCON channel 0 */ 194 lvds->encoder.possible_crtcs = BIT(drm_crtc_index(&tcon->crtc->crtc)); 195 196 if (tcon->panel) { 197 drm_connector_helper_add(&lvds->connector, 198 &sun4i_lvds_con_helper_funcs); 199 ret = drm_connector_init(drm, &lvds->connector, 200 &sun4i_lvds_con_funcs, 201 DRM_MODE_CONNECTOR_LVDS); 202 if (ret) { 203 dev_err(drm->dev, "Couldn't initialise the lvds connector\n"); 204 goto err_cleanup_connector; 205 } 206 207 drm_mode_connector_attach_encoder(&lvds->connector, 208 &lvds->encoder); 209 210 ret = drm_panel_attach(tcon->panel, &lvds->connector); 211 if (ret) { 212 dev_err(drm->dev, "Couldn't attach our panel\n"); 213 goto err_cleanup_connector; 214 } 215 } 216 217 if (bridge) { 218 ret = drm_bridge_attach(encoder, bridge, NULL); 219 if (ret) { 220 dev_err(drm->dev, "Couldn't attach our bridge\n"); 221 goto err_cleanup_connector; 222 } 223 } 224 225 return 0; 226 227 err_cleanup_connector: 228 drm_encoder_cleanup(&lvds->encoder); 229 err_out: 230 return ret; 231 } 232 EXPORT_SYMBOL(sun4i_lvds_init); 233