2 * Copyright (C) ROCKCHIP, Inc.
3 * Author:yzq<yzq@rock-chips.com>
4 * This software is licensed under the terms of the GNU General Public
5 * License version 2, as published by the Free Software Foundation, and
6 * may be copied, distributed, and modified under those terms.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
15 #include <drm/drm_crtc_helper.h>
17 #include <drm/rockchip_drm.h>
18 #include "rockchip_drm_drv.h"
19 #include "rockchip_drm_encoder.h"
21 #define to_rockchip_connector(x) container_of(x, struct rockchip_drm_connector,\
24 struct rockchip_drm_connector {
25 struct drm_connector drm_connector;
27 struct rockchip_drm_manager *manager;
31 /* convert rockchip_video_timings to drm_display_mode */
33 convert_to_display_mode(struct drm_display_mode *mode,
34 struct rockchip_drm_panel_info *panel)
36 struct fb_videomode *timing = &panel->timing;
37 DRM_DEBUG_KMS("%s\n", __FILE__);
39 mode->clock = timing->pixclock / 1000;
40 mode->vrefresh = timing->refresh;
42 mode->hdisplay = timing->xres;
43 mode->hsync_start = mode->hdisplay + timing->right_margin;
44 mode->hsync_end = mode->hsync_start + timing->hsync_len;
45 mode->htotal = mode->hsync_end + timing->left_margin;
47 mode->vdisplay = timing->yres;
48 mode->vsync_start = mode->vdisplay + timing->lower_margin;
49 mode->vsync_end = mode->vsync_start + timing->vsync_len;
50 mode->vtotal = mode->vsync_end + timing->upper_margin;
51 mode->width_mm = panel->width_mm;
52 mode->height_mm = panel->height_mm;
54 if (timing->vmode & FB_VMODE_INTERLACED)
55 mode->flags |= DRM_MODE_FLAG_INTERLACE;
57 if (timing->vmode & FB_VMODE_DOUBLE)
58 mode->flags |= DRM_MODE_FLAG_DBLSCAN;
61 convert_fbmode_to_display_mode(struct drm_display_mode *mode,
62 struct fb_videomode *timing)
64 DRM_DEBUG_KMS("%s\n", __FILE__);
66 mode->clock = timing->pixclock / 1000;
67 mode->vrefresh = timing->refresh;
69 mode->hdisplay = timing->xres;
70 mode->hsync_start = mode->hdisplay + timing->right_margin;
71 mode->hsync_end = mode->hsync_start + timing->hsync_len;
72 mode->htotal = mode->hsync_end + timing->left_margin;
74 mode->vdisplay = timing->yres;
75 mode->vsync_start = mode->vdisplay + timing->lower_margin;
76 mode->vsync_end = mode->vsync_start + timing->vsync_len;
77 mode->vtotal = mode->vsync_end + timing->upper_margin;
79 if (timing->vmode & FB_VMODE_INTERLACED)
80 mode->flags |= DRM_MODE_FLAG_INTERLACE;
82 if (timing->vmode & FB_VMODE_DOUBLE)
83 mode->flags |= DRM_MODE_FLAG_DBLSCAN;
85 /* convert drm_display_mode to rockchip_video_timings */
87 convert_to_video_timing(struct fb_videomode *timing,
88 struct drm_display_mode *mode)
90 DRM_DEBUG_KMS("%s\n", __FILE__);
92 memset(timing, 0, sizeof(*timing));
94 timing->pixclock = mode->clock * 1000;
95 timing->refresh = drm_mode_vrefresh(mode);
97 timing->xres = mode->hdisplay;
98 timing->right_margin = mode->hsync_start - mode->hdisplay;
99 timing->hsync_len = mode->hsync_end - mode->hsync_start;
100 timing->left_margin = mode->htotal - mode->hsync_end;
102 timing->yres = mode->vdisplay;
103 timing->lower_margin = mode->vsync_start - mode->vdisplay;
104 timing->vsync_len = mode->vsync_end - mode->vsync_start;
105 timing->upper_margin = mode->vtotal - mode->vsync_end;
107 if (mode->flags & DRM_MODE_FLAG_INTERLACE)
108 timing->vmode = FB_VMODE_INTERLACED;
110 timing->vmode = FB_VMODE_NONINTERLACED;
112 if (mode->flags & DRM_MODE_FLAG_DBLSCAN)
113 timing->vmode |= FB_VMODE_DOUBLE;
116 static int rockchip_drm_connector_get_modes(struct drm_connector *connector)
118 struct rockchip_drm_connector *rockchip_connector =
119 to_rockchip_connector(connector);
120 struct rockchip_drm_manager *manager = rockchip_connector->manager;
121 struct rockchip_drm_display_ops *display_ops = manager->display_ops;
122 struct edid *edid = NULL;
123 unsigned int count = 0;
126 DRM_DEBUG_KMS("%s\n", __FILE__);
129 DRM_DEBUG_KMS("display_ops is null.\n");
134 * if get_edid() exists then get_edid() callback of hdmi side
135 * is called to get edid data through i2c interface else
136 * get timing from the FIMD driver(display controller).
138 * P.S. in case of lcd panel, count is always 1 if success
139 * because lcd panel has only one mode.
141 if (display_ops->get_edid) {
142 edid = display_ops->get_edid(manager->dev, connector);
143 if (IS_ERR_OR_NULL(edid)) {
146 DRM_ERROR("Panel operation get_edid failed %d\n", ret);
150 count = drm_add_edid_modes(connector, edid);
152 DRM_ERROR("Add edid modes failed %d\n", count);
156 drm_mode_connector_update_edid_property(connector, edid);
157 } else if(display_ops->get_modelist){
158 struct list_head *pos,*head;
159 struct fb_modelist *modelist;
160 struct fb_videomode *mode;
161 struct drm_display_mode *disp_mode = NULL;
163 head = display_ops->get_modelist(manager->dev);
165 list_for_each(pos,head){
166 modelist = list_entry(pos, struct fb_modelist, list);
167 mode = &modelist->mode;
168 disp_mode = drm_mode_create(connector->dev);
170 DRM_ERROR("failed to create a new display mode.\n");
173 convert_fbmode_to_display_mode(disp_mode, mode);
175 if(mode->xres == 1280 && mode->yres == 720 && mode->refresh == 60)
176 disp_mode->type |= DRM_MODE_TYPE_PREFERRED;
178 drm_mode_set_name(disp_mode);
179 // snprintf(disp_mode->name, DRM_DISPLAY_MODE_LEN, "%dx%d%s-%d",
180 // disp_mode->hdisplay, disp_mode->vdisplay,
181 // !!(disp_mode->flags & DRM_MODE_FLAG_INTERLACE)? "i" : "p",disp_mode->vrefresh);
182 drm_mode_probed_add(connector, disp_mode);
186 struct rockchip_drm_panel_info *panel;
187 struct drm_display_mode *mode = drm_mode_create(connector->dev);
189 DRM_ERROR("failed to create a new display mode.\n");
193 if (display_ops->get_panel)
194 panel = display_ops->get_panel(manager->dev);
196 drm_mode_destroy(connector->dev, mode);
200 convert_to_display_mode(mode, panel);
201 connector->display_info.width_mm = mode->width_mm;
202 connector->display_info.height_mm = mode->height_mm;
204 mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
205 drm_mode_set_name(mode);
206 drm_mode_probed_add(connector, mode);
216 static int rockchip_drm_connector_mode_valid(struct drm_connector *connector,
217 struct drm_display_mode *mode)
219 struct rockchip_drm_connector *rockchip_connector =
220 to_rockchip_connector(connector);
221 struct rockchip_drm_manager *manager = rockchip_connector->manager;
222 struct rockchip_drm_display_ops *display_ops = manager->display_ops;
223 struct fb_videomode timing;
226 DRM_DEBUG_KMS("%s\n", __FILE__);
228 convert_to_video_timing(&timing, mode);
230 if (display_ops && display_ops->check_timing)
231 if (!display_ops->check_timing(manager->dev, (void *)&timing))
237 struct drm_encoder *rockchip_drm_best_encoder(struct drm_connector *connector)
239 struct drm_device *dev = connector->dev;
240 struct rockchip_drm_connector *rockchip_connector =
241 to_rockchip_connector(connector);
242 struct drm_mode_object *obj;
243 struct drm_encoder *encoder;
245 DRM_DEBUG_KMS("%s\n", __FILE__);
247 obj = drm_mode_object_find(dev, rockchip_connector->encoder_id,
248 DRM_MODE_OBJECT_ENCODER);
250 DRM_DEBUG_KMS("Unknown ENCODER ID %d\n",
251 rockchip_connector->encoder_id);
255 encoder = obj_to_encoder(obj);
260 static struct drm_connector_helper_funcs rockchip_connector_helper_funcs = {
261 .get_modes = rockchip_drm_connector_get_modes,
262 .mode_valid = rockchip_drm_connector_mode_valid,
263 .best_encoder = rockchip_drm_best_encoder,
266 void rockchip_drm_display_power(struct drm_connector *connector, int mode)
268 struct drm_encoder *encoder = rockchip_drm_best_encoder(connector);
269 struct rockchip_drm_connector *rockchip_connector;
270 struct rockchip_drm_manager *manager = rockchip_drm_get_manager(encoder);
271 struct rockchip_drm_display_ops *display_ops = manager->display_ops;
273 rockchip_connector = to_rockchip_connector(connector);
275 if (rockchip_connector->dpms == mode) {
276 DRM_DEBUG_KMS("desired dpms mode is same as previous one.\n");
280 if (display_ops && display_ops->power_on)
281 display_ops->power_on(manager->dev, mode);
283 rockchip_connector->dpms = mode;
286 static void rockchip_drm_connector_dpms(struct drm_connector *connector,
289 DRM_DEBUG_KMS("%s\n", __FILE__);
292 * in case that drm_crtc_helper_set_mode() is called,
293 * encoder/crtc->funcs->dpms() will be just returned
294 * because they already were DRM_MODE_DPMS_ON so only
295 * rockchip_drm_display_power() will be called.
297 drm_helper_connector_dpms(connector, mode);
299 rockchip_drm_display_power(connector, mode);
303 static int rockchip_drm_connector_fill_modes(struct drm_connector *connector,
304 unsigned int max_width, unsigned int max_height)
306 struct rockchip_drm_connector *rockchip_connector =
307 to_rockchip_connector(connector);
308 struct rockchip_drm_manager *manager = rockchip_connector->manager;
309 struct rockchip_drm_manager_ops *ops = manager->ops;
310 unsigned int width, height;
316 * if specific driver want to find desired_mode using maxmum
317 * resolution then get max width and height from that driver.
319 if (ops && ops->get_max_resol)
320 ops->get_max_resol(manager->dev, &width, &height);
322 return drm_helper_probe_single_connector_modes(connector, width,
326 /* get detection status of display device. */
327 static enum drm_connector_status
328 rockchip_drm_connector_detect(struct drm_connector *connector, bool force)
330 struct rockchip_drm_connector *rockchip_connector =
331 to_rockchip_connector(connector);
332 struct rockchip_drm_manager *manager = rockchip_connector->manager;
333 struct rockchip_drm_display_ops *display_ops =
334 manager->display_ops;
335 enum drm_connector_status status = connector_status_disconnected;
337 DRM_DEBUG_KMS("%s\n", __FILE__);
339 if (display_ops && display_ops->is_connected) {
340 if (display_ops->is_connected(manager->dev))
341 status = connector_status_connected;
343 status = connector_status_disconnected;
349 static void rockchip_drm_connector_destroy(struct drm_connector *connector)
351 struct rockchip_drm_connector *rockchip_connector =
352 to_rockchip_connector(connector);
354 DRM_DEBUG_KMS("%s\n", __FILE__);
356 drm_sysfs_connector_remove(connector);
357 drm_connector_cleanup(connector);
358 kfree(rockchip_connector);
361 static struct drm_connector_funcs rockchip_connector_funcs = {
362 .dpms = rockchip_drm_connector_dpms,
363 .fill_modes = rockchip_drm_connector_fill_modes,
364 .detect = rockchip_drm_connector_detect,
365 .destroy = rockchip_drm_connector_destroy,
368 struct drm_connector *rockchip_drm_connector_create(struct drm_device *dev,
369 struct drm_encoder *encoder)
371 struct rockchip_drm_connector *rockchip_connector;
372 struct rockchip_drm_manager *manager = rockchip_drm_get_manager(encoder);
373 struct drm_connector *connector;
377 DRM_DEBUG_KMS("%s\n", __FILE__);
379 rockchip_connector = kzalloc(sizeof(*rockchip_connector), GFP_KERNEL);
380 if (!rockchip_connector) {
381 DRM_ERROR("failed to allocate connector\n");
385 connector = &rockchip_connector->drm_connector;
387 switch (manager->display_ops->type) {
388 case ROCKCHIP_DISPLAY_TYPE_HDMI:
389 type = DRM_MODE_CONNECTOR_HDMIA;
390 connector->interlace_allowed = true;
391 connector->polled = DRM_CONNECTOR_POLL_HPD;
393 case ROCKCHIP_DISPLAY_TYPE_VIDI:
394 type = DRM_MODE_CONNECTOR_VIRTUAL;
395 connector->polled = DRM_CONNECTOR_POLL_HPD;
397 case ROCKCHIP_DISPLAY_TYPE_LCD:
398 type = DRM_MODE_CONNECTOR_LVDS;
401 type = DRM_MODE_CONNECTOR_Unknown;
405 drm_connector_init(dev, connector, &rockchip_connector_funcs, type);
406 drm_connector_helper_add(connector, &rockchip_connector_helper_funcs);
408 err = drm_sysfs_connector_add(connector);
412 rockchip_connector->encoder_id = encoder->base.id;
413 rockchip_connector->manager = manager;
414 rockchip_connector->dpms = DRM_MODE_DPMS_OFF;
415 connector->dpms = DRM_MODE_DPMS_OFF;
416 connector->encoder = encoder;
418 err = drm_mode_connector_attach_encoder(connector, encoder);
420 DRM_ERROR("failed to attach a connector to a encoder\n");
424 DRM_DEBUG_KMS("connector has been created\n");
429 drm_sysfs_connector_remove(connector);
431 drm_connector_cleanup(connector);
432 kfree(rockchip_connector);