1 /*
2 * Copyright (C) 2019 Allwinnertech Co.Ltd
3 * Authors: zhengwanyu
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version.
9 *
10 */
11 #include <drm/drm_fb_helper.h>
12 #include <drm/drm_plane_helper.h>
13 #include <drm/drm_crtc_helper.h>
14 #include <drm/drm_atomic_helper.h>
15 #include <drm/drm_sysfs.h>
16 #include <drm/drm_probe_helper.h>
17 #include <drm/drm_print.h>
18 #include <video/sunxi_display2.h>
19
20 #include "drm_internal.h"
21 #include "sunxi_drm_drv.h"
22 #include "sunxi_drm_encoder.h"
23 #include "sunxi_drm_connector.h"
24
25 #include "sunxi_device/sunxi_lcd.h"
26
27 #if defined(CONFIG_AW_DRM_BACKLIGHT)
28 #include "sunxi_device/sunxi_backlight.h"
29 #endif
30
31 #include "de/include.h"
32
33 /*
34 * transform lcd_type defined in sunxi_lcd to drm connector type
35 */
sunxi_connector_lcd_type_trans(int sunxi_lcd_type)36 static int sunxi_connector_lcd_type_trans(int sunxi_lcd_type)
37 {
38 switch (sunxi_lcd_type) {
39 case LCD_IF_HV:
40 return DRM_MODE_CONNECTOR_Unknown;
41 case LCD_IF_CPU:
42 return DRM_MODE_CONNECTOR_Unknown;
43 case LCD_IF_LVDS:
44 return DRM_MODE_CONNECTOR_LVDS;
45 case LCD_IF_DSI:
46 return DRM_MODE_CONNECTOR_DSI;
47 case LCD_IF_EDP:
48 return DRM_MODE_CONNECTOR_eDP;
49 }
50
51 DRM_ERROR("wrong sunxi_lcd_type:%d\n", sunxi_lcd_type);
52 return -1;
53 }
54
55
sunxi_connector_lcd_convert_panel_to_display_mode(struct drm_display_mode * mode,struct disp_panel_para * panel)56 static void sunxi_connector_lcd_convert_panel_to_display_mode(
57 struct drm_display_mode *mode,
58 struct disp_panel_para *panel)
59 {
60 mode->clock = panel->lcd_dclk_freq * 1000;
61
62 mode->hdisplay = panel->lcd_x;
63 mode->hsync_start = panel->lcd_ht - panel->lcd_hbp;
64 mode->hsync_end = panel->lcd_ht - panel->lcd_hbp + panel->lcd_hspw;
65 mode->htotal = panel->lcd_ht;
66
67 mode->vdisplay = panel->lcd_y;
68 mode->vsync_start = panel->lcd_vt - panel->lcd_vbp;
69 mode->vsync_end = panel->lcd_vt - panel->lcd_vbp + panel->lcd_vspw;
70 mode->vtotal = panel->lcd_vt;
71 mode->vscan = 0;
72 mode->flags = 0;
73 mode->width_mm = panel->lcd_width;
74 mode->height_mm = panel->lcd_height;
75 /* mode->vrefresh = mode->clock * 1000 / mode->vtotal / mode->htotal;
76 DRM_DEBUG_KMS("Modeline %d:%d %d %d %d %d %d %d %d %d %d 0x%x 0x%x\n",
77 mode->base.id, mode->vrefresh, mode->clock,
78 mode->hdisplay, mode->hsync_start,
79 mode->hsync_end, mode->htotal,
80 mode->vdisplay, mode->vsync_start,
81 mode->vsync_end, mode->vtotal, mode->type, mode->flags); */
82 DRM_DEBUG_KMS("Modeline:%d %d %d %d %d %d %d %d %d 0x%x 0x%x\n",
83 mode->clock,
84 mode->hdisplay, mode->hsync_start,
85 mode->hsync_end, mode->htotal,
86 mode->vdisplay, mode->vsync_start,
87 mode->vsync_end, mode->vtotal, mode->type, mode->flags);
88 DRM_DEBUG_KMS("panel: clk[%d] [x %d, ht %d, hbp %d, hspw %d]\n",
89 panel->lcd_dclk_freq * 1000,
90 panel->lcd_x, panel->lcd_ht,
91 panel->lcd_hbp, panel->lcd_hspw);
92 DRM_DEBUG_KMS("[y%d, vt%d, bp %d, pw %d] %dx%d\n",
93 panel->lcd_y, panel->lcd_vt,
94 panel->lcd_vbp, panel->lcd_vspw, panel->lcd_width,
95 panel->lcd_height);
96 }
97
sunxi_connector_lcd_detect(struct drm_connector * connector,bool force)98 static enum drm_connector_status sunxi_connector_lcd_detect(
99 struct drm_connector *connector,
100 bool force)
101 {
102 return connector_status_connected;
103 }
104
sunxi_connector_lcd_get_modes(struct drm_connector * connector)105 static int sunxi_connector_lcd_get_modes(struct drm_connector *connector)
106 {
107 struct drm_display_mode *mode;
108 struct sunxi_drm_connector *sunxi_con = to_sunxi_connector(connector);
109 struct disp_panel_para *panel_para = NULL;
110 struct sunxi_lcd_funcs *lcd_funcs
111 = (struct sunxi_lcd_funcs *)sunxi_con->hw_funcs;
112
113
114 mode = drm_mode_create(connector->dev);
115 if (!mode) {
116 DRM_ERROR("failed to create a new display mode.\n");
117 return 0;
118 }
119
120 panel_para = lcd_funcs->get_panel_para(sunxi_con->type_id);
121 if (!panel_para) {
122 DRM_ERROR("get lcd%d panel para failed\n",
123 sunxi_con->type_id);
124 return -1;
125 }
126
127 sunxi_connector_lcd_convert_panel_to_display_mode(mode, panel_para);
128 connector->display_info.width_mm = mode->width_mm;
129 connector->display_info.height_mm = mode->height_mm;
130
131 mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
132 drm_mode_set_name(mode);
133 drm_mode_probed_add(connector, mode);
134
135 return 1;
136 }
137
sunxi_connector_lcd_mode_valid(struct drm_connector * connector,struct drm_display_mode * mode)138 static int sunxi_connector_lcd_mode_valid(struct drm_connector *connector,
139 struct drm_display_mode *mode)
140 {
141 return MODE_OK;
142 }
143
144
145 /**
146 * @name :sunxi_connector_register
147 * @brief :register sysfs backlight
148 * @param[IN] :connector:pointer of drm_connector
149 * @return :0 if success, -1 else
150 */
sunxi_connector_lcd_register(struct drm_connector * connector)151 int sunxi_connector_lcd_register(struct drm_connector *connector)
152 {
153 int ret = -1;
154 #if defined(CONFIG_AW_DRM_BACKLIGHT)
155 struct sunxi_drm_connector *sconn = to_sunxi_connector(connector);
156
157 if (!sconn) {
158 DRM_ERROR("Null sunxi connector pointer!\n");
159 goto OUT;
160 }
161
162 if (sconn->type != DISP_OUTPUT_TYPE_LCD) {
163 DRM_INFO("[WARN]connector:%d is NOT lcd!\n", connector->index);
164 ret = 0;
165 goto OUT;
166 }
167
168 ret = sunxi_backlight_device_register(connector->kdev, sconn->type_id);
169 if (ret < 0)
170 DRM_ERROR("sunxi_backlight_device_register for lcd:%d failed\n",
171 sconn->type_id);
172 #else
173 DRM_INFO("[WARN]: NOT support backlight for connector:%d\n", connector->index);
174 ret = 0;
175 #endif
176
177 OUT:
178 return ret;
179 }
180
181
sunxi_connector_lcd_unregister(struct drm_connector * connector)182 void sunxi_connector_lcd_unregister(struct drm_connector *connector)
183 {
184 #if defined(CONFIG_AW_DRM_BACKLIGHT)
185 struct sunxi_drm_connector *sconn = to_sunxi_connector(connector);
186
187 if (!sconn) {
188 DRM_ERROR("Null sunxi_drm_connector pointer!\n");
189 return;
190 }
191 if (sconn->type != DISP_OUTPUT_TYPE_LCD)
192 return;
193
194 sunxi_backlight_device_unregister(sconn->type_id);
195 #endif
196
197 }
198
199 static const struct drm_connector_funcs sunxi_connector_lcd_funcs = {
200 .detect = sunxi_connector_lcd_detect,
201 .fill_modes = drm_helper_probe_single_connector_modes,
202 .destroy = drm_connector_cleanup,
203 .late_register = sunxi_connector_lcd_register,
204 .early_unregister = sunxi_connector_lcd_unregister,
205 .reset = drm_atomic_helper_connector_reset,
206 .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
207 .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
208 };
209
210 static const struct drm_connector_helper_funcs sunxi_connector_helper_lcd_funcs = {
211 .get_modes = sunxi_connector_lcd_get_modes,
212 .mode_valid = sunxi_connector_lcd_mode_valid,
213 .best_encoder = sunxi_connector_best_encoder,
214 };
215
sunxi_connector_lcd_get_prefered_resolution(struct sunxi_drm_connector * sunxi_conn,unsigned int * w,unsigned int * h,unsigned int * vfresh)216 static void sunxi_connector_lcd_get_prefered_resolution(
217 struct sunxi_drm_connector *sunxi_conn, /*unsigned int *tvmode,*/
218 unsigned int *w, unsigned int *h, unsigned int *vfresh)
219 {
220 struct disp_panel_para *panel_para = NULL;
221 struct sunxi_lcd_funcs *lcd_funcs
222 = (struct sunxi_lcd_funcs *)sunxi_conn->hw_funcs;
223
224 panel_para = lcd_funcs->get_panel_para(sunxi_conn->type_id);
225 if (!panel_para) {
226 DRM_ERROR("get lcd%d panel para failed\n",
227 sunxi_conn->type_id);
228 return;
229 }
230
231 *w = panel_para->lcd_x;
232 *h = panel_para->lcd_y;
233 *vfresh = panel_para->lcd_dclk_freq * 1000 * 1000
234 / (panel_para->lcd_vt * panel_para->lcd_ht);
235 DRM_INFO("w:%u h:%u vfresh:%u\n", *w, *h, *vfresh);
236 }
237
sunxi_connector_lcd_is_use_irq(struct sunxi_drm_connector * sconn)238 bool sunxi_connector_lcd_is_use_irq(struct sunxi_drm_connector *sconn)
239 {
240 struct sunxi_lcd_funcs *lcd_funcs
241 = (struct sunxi_lcd_funcs *)sconn->hw_funcs;
242
243 return lcd_funcs->is_use_irq(sconn->type_id);
244 }
245
sunxi_connector_lcd_enable(struct sunxi_drm_connector * sconn,struct drm_display_mode * mode)246 static int sunxi_connector_lcd_enable(struct sunxi_drm_connector *sconn,
247 struct drm_display_mode *mode)
248 {
249 struct sunxi_lcd_funcs *lcd_funcs
250 = (struct sunxi_lcd_funcs *)sconn->hw_funcs;
251
252 return lcd_funcs->enable(sconn->type_id);
253 }
254
255 static int
sunxi_connector_lcd_sw_enable(struct sunxi_drm_connector * sconn,struct drm_display_mode * mode)256 sunxi_connector_lcd_sw_enable(struct sunxi_drm_connector *sconn,
257 struct drm_display_mode *mode)
258 {
259 struct sunxi_lcd_funcs *lcd_funcs
260 = (struct sunxi_lcd_funcs *)sconn->hw_funcs;
261
262 lcd_funcs->sw_enable(sconn->type_id);
263
264 return 0;
265 }
266
sunxi_connector_lcd_disable(struct sunxi_drm_connector * sconn)267 static void sunxi_connector_lcd_disable(struct sunxi_drm_connector *sconn)
268 {
269 struct sunxi_lcd_funcs *lcd_funcs
270 = (struct sunxi_lcd_funcs *)sconn->hw_funcs;
271
272 lcd_funcs->disable(sconn->type_id);
273 }
274
275 struct sunxi_drm_connector *
sunxi_drm_connector_lcd_create(struct drm_device * dev,int conn_id,int lcd_id)276 sunxi_drm_connector_lcd_create(struct drm_device *dev, int conn_id, int lcd_id)
277 {
278 struct drm_encoder *enc;
279 struct sunxi_drm_encoder *sunxi_enc;
280 struct sunxi_drm_connector *sunxi_conn;
281 int lcd_type = 0;
282 int drm_con_type;
283 int ret;
284 struct sunxi_lcd_funcs *lcd_funcs;
285
286 sunxi_conn = kzalloc(sizeof(struct sunxi_drm_connector), GFP_KERNEL);
287 if (!sunxi_conn) {
288 DRM_ERROR("can NOT allocate memory for sunxi_connector\n");
289 goto lcd_conn_err;
290 }
291
292 sunxi_conn->con_id = conn_id;
293 sunxi_conn->type_id = lcd_id;
294 sunxi_conn->type = DISP_OUTPUT_TYPE_LCD;
295
296 lcd_funcs = sunxi_lcd_get_hw_funcs(lcd_id);
297 if (!lcd_funcs) {
298 DRM_ERROR("lcd:%d has NO funcs\n", lcd_id);
299 goto lcd_conn_err;
300 }
301 sunxi_conn->hw_funcs = lcd_funcs;
302 lcd_type = lcd_funcs->get_type(lcd_id);
303 drm_con_type
304 = sunxi_connector_lcd_type_trans(lcd_type);
305 if (drm_con_type < 0) {
306 DRM_ERROR("get drm connector type failed\n");
307 goto lcd_conn_err;
308 }
309
310 sunxi_conn->connector.connector_type = drm_con_type;
311 sunxi_conn->connector.interlace_allowed = false;
312 sunxi_conn->connector.polled = 0;
313
314 /*search for all of the encoders that can be attached to this connector*/
315 list_for_each_entry(enc,
316 &dev->mode_config.encoder_list, head) {
317 sunxi_enc = to_sunxi_encoder(enc);
318
319 /* set an encoder working for a certain kind of connector,
320 * NOTE: if you want to use the callback functions(hw_funcs) of a encoder,
321 * you must call sunxi_tcon_attach_connector_type() to set a tcon to a kind of
322 * connector.
323 */
324 sunxi_enc->hw_funcs =
325 sunxi_tcon_attach_connector_type(sunxi_enc->encoder_id,
326 DISP_OUTPUT_TYPE_LCD);
327
328 if (sunxi_enc->conn_is_supported(
329 sunxi_enc, sunxi_conn)) {
330 ret = drm_connector_attach_encoder(
331 &sunxi_conn->connector, enc);
332 if (ret) {
333 DRM_ERROR("failed to attach a"
334 "connector to a encoder\n");
335 goto lcd_conn_err;
336 }
337 }
338
339 sunxi_tcon_unattach_connector_type(sunxi_enc->encoder_id);
340 sunxi_enc->hw_funcs = NULL;
341 }
342
343 sunxi_conn->connector.dpms = DRM_MODE_DPMS_OFF;
344
345 ret = drm_connector_init(dev, &sunxi_conn->connector,
346 &sunxi_connector_lcd_funcs,
347 drm_con_type);
348 if (ret < 0) {
349 DRM_ERROR("drm_connector_init failed\n");
350 goto lcd_conn_err;
351 }
352
353 drm_connector_helper_add(&sunxi_conn->connector,
354 &sunxi_connector_helper_lcd_funcs);
355
356 drm_atomic_helper_connector_reset(&sunxi_conn->connector);
357
358 sunxi_conn->use_irq = lcd_funcs->is_use_irq(lcd_id);
359 sunxi_conn->irq_no = lcd_funcs->get_irq_no(lcd_id);
360 sunxi_conn->get_init_resolution
361 = sunxi_connector_lcd_get_prefered_resolution;
362 sunxi_conn->enable = sunxi_connector_lcd_enable;
363 sunxi_conn->sw_enable = sunxi_connector_lcd_sw_enable;
364 sunxi_conn->disable = sunxi_connector_lcd_disable;
365 return sunxi_conn;
366
367 lcd_conn_err:
368 drm_connector_cleanup(&sunxi_conn->connector);
369 kfree(sunxi_conn);
370 return NULL;
371 }
372