From 3bdf6a624cd1225fa8fe2e6366ca6b63c8882c72 Mon Sep 17 00:00:00 2001 From: Inki Dae Date: Tue, 2 Dec 2014 22:05:01 +0900 Subject: drm/exynos: add LPD: Low Power Display mechanism support The purpose of this mechanism is to transfer the only framebuffer region updated by X server to Display panel to save power consumed by Display relevant DMA devices. Change-Id: I2589c617d817833011e761c9c8835e287ff2fb7c Signed-off-by: Inki Dae --- drivers/gpu/drm/exynos/exynos_drm_crtc.c | 34 ++++++++++++ drivers/gpu/drm/exynos/exynos_drm_crtc.h | 10 ++++ drivers/gpu/drm/exynos/exynos_drm_drv.h | 7 +++ drivers/gpu/drm/exynos/exynos_drm_dsi.c | 13 ++++- drivers/gpu/drm/exynos/exynos_drm_encoder.c | 11 ++++ drivers/gpu/drm/exynos/exynos_drm_encoder.h | 4 ++ drivers/gpu/drm/exynos/exynos_drm_fb.c | 64 +++++++++++++++++++++- drivers/gpu/drm/exynos/exynos_drm_fb.h | 10 ++++ drivers/gpu/drm/exynos/exynos_drm_fimd.c | 85 +++++++++++++++++++++++++++++ drivers/gpu/drm/exynos/exynos_drm_plane.c | 23 ++++++++ drivers/gpu/drm/panel/panel-s6e63j0x03.c | 38 +++++++++++++ include/drm/drm_panel.h | 14 +++++ 12 files changed, 311 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/exynos/exynos_drm_crtc.c b/drivers/gpu/drm/exynos/exynos_drm_crtc.c index 7e52a170d1f..d35f7782e36 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_crtc.c +++ b/drivers/gpu/drm/exynos/exynos_drm_crtc.c @@ -594,3 +594,37 @@ int exynos_drm_crtc_te_handler(struct drm_crtc *crtc) return ret; } + +void exynos_drm_crtc_adjust_partial_region(struct drm_crtc *crtc, + struct exynos_drm_partial_pos *pos) +{ + struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc); + struct exynos_drm_manager *mgr = exynos_crtc->manager; + + if (mgr && mgr->ops->adjust_partial_region) + mgr->ops->adjust_partial_region(mgr, &pos->x, &pos->y, + &pos->w, &pos->h); +} + +void exynos_drm_crtc_change_resolution(struct drm_device *drm_dev, + unsigned int pipe, unsigned int x, + unsigned int y, unsigned int w, + unsigned int h) +{ + struct exynos_drm_private *private = drm_dev->dev_private; + struct exynos_drm_crtc *exynos_crtc = + to_exynos_crtc(private->crtc[pipe]); + struct exynos_drm_manager *manager = exynos_crtc->manager; + struct drm_encoder *encoder; + + /* TODO. mutex_lock */ + + list_for_each_entry(encoder, &drm_dev->mode_config.encoder_list, head) { + if (encoder->crtc == &exynos_crtc->drm_crtc) { + exynos_drm_encoder_change_resolution(encoder, x, y, + w, h); + break; + } + } +} + diff --git a/drivers/gpu/drm/exynos/exynos_drm_crtc.h b/drivers/gpu/drm/exynos/exynos_drm_crtc.h index eb78c16e52c..ee8e5c98ffe 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_crtc.h +++ b/drivers/gpu/drm/exynos/exynos_drm_crtc.h @@ -39,4 +39,14 @@ void exynos_drm_crtc_plane_disable(struct drm_crtc *crtc, int zpos); */ int exynos_drm_crtc_te_handler(struct drm_crtc *crtc); +struct exynos_drm_partial_pos; + +void exynos_drm_crtc_adjust_partial_region(struct drm_crtc *crtc, + struct exynos_drm_partial_pos *pos); + +void exynos_drm_crtc_change_resolution(struct drm_device *drm_dev, + unsigned int pipe, unsigned int x, + unsigned int y, unsigned int w, + unsigned int h); + #endif diff --git a/drivers/gpu/drm/exynos/exynos_drm_drv.h b/drivers/gpu/drm/exynos/exynos_drm_drv.h index d93adc9a058..03d770ef384 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_drv.h +++ b/drivers/gpu/drm/exynos/exynos_drm_drv.h @@ -146,6 +146,7 @@ struct exynos_drm_overlay { * @check_mode: check if mode is valid or not. * @dpms: display device on or off. * @commit: apply changes to hw + * @set_partial_region: change region region to a given position. */ struct exynos_drm_display; struct exynos_drm_display_ops { @@ -164,6 +165,9 @@ struct exynos_drm_display_ops { struct drm_display_mode *mode); void (*dpms)(struct exynos_drm_display *display, int mode); void (*commit)(struct exynos_drm_display *display); + void (*change_resolution)(struct exynos_drm_display *display, + unsigned int x, unsigned int y, + unsigned int w, unsigned int h); }; /* @@ -226,6 +230,9 @@ struct exynos_drm_manager_ops { void (*win_enable)(struct exynos_drm_manager *mgr, int zpos); void (*win_disable)(struct exynos_drm_manager *mgr, int zpos); int (*te_handler)(struct exynos_drm_manager *mgr); + void (*adjust_partial_region)(struct exynos_drm_manager *mgr, + unsigned int *x, unsigned int *y, + unsigned int *w, unsigned int *h); }; /* diff --git a/drivers/gpu/drm/exynos/exynos_drm_dsi.c b/drivers/gpu/drm/exynos/exynos_drm_dsi.c index ea3d7b78757..19982eebadf 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_dsi.c +++ b/drivers/gpu/drm/exynos/exynos_drm_dsi.c @@ -1302,6 +1302,16 @@ static void exynos_dsi_dpms(struct exynos_drm_display *display, int mode) } } + +static void exynos_dsi_change_resolution(struct exynos_drm_display *display, + unsigned int x, unsigned int y, + unsigned int w, unsigned int h) +{ + struct exynos_dsi *dsi = display->ctx; + + drm_panel_change_resolution(dsi->panel, x, y, w, h); +} + static enum drm_connector_status exynos_dsi_detect(struct drm_connector *connector, bool force) { @@ -1412,7 +1422,8 @@ static void exynos_dsi_mode_set(struct exynos_drm_display *display, static struct exynos_drm_display_ops exynos_dsi_display_ops = { .create_connector = exynos_dsi_create_connector, .mode_set = exynos_dsi_mode_set, - .dpms = exynos_dsi_dpms + .dpms = exynos_dsi_dpms, + .change_resolution = exynos_dsi_change_resolution, }; static struct exynos_drm_display exynos_dsi_display = { diff --git a/drivers/gpu/drm/exynos/exynos_drm_encoder.c b/drivers/gpu/drm/exynos/exynos_drm_encoder.c index 835c0f1e88a..fad528e4dc1 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_encoder.c +++ b/drivers/gpu/drm/exynos/exynos_drm_encoder.c @@ -195,3 +195,14 @@ struct exynos_drm_display *exynos_drm_get_display(struct drm_encoder *encoder) { return to_exynos_encoder(encoder)->display; } + +void exynos_drm_encoder_change_resolution(struct drm_encoder *encoder, + unsigned int x, unsigned int y, + unsigned int w, unsigned int h) +{ + struct exynos_drm_display *display = + to_exynos_encoder(encoder)->display; + + if (display && display->ops->change_resolution) + display->ops->change_resolution(display, x, y, w, h); +} diff --git a/drivers/gpu/drm/exynos/exynos_drm_encoder.h b/drivers/gpu/drm/exynos/exynos_drm_encoder.h index b7a1620a7e7..0755cb3719a 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_encoder.h +++ b/drivers/gpu/drm/exynos/exynos_drm_encoder.h @@ -22,4 +22,8 @@ struct drm_encoder *exynos_drm_encoder_create(struct drm_device *dev, unsigned long possible_crtcs); struct exynos_drm_display *exynos_drm_get_display(struct drm_encoder *encoder); +void exynos_drm_encoder_change_resolution(struct drm_encoder *encoder, + unsigned int x, unsigned int y, + unsigned int w, unsigned int h); + #endif diff --git a/drivers/gpu/drm/exynos/exynos_drm_fb.c b/drivers/gpu/drm/exynos/exynos_drm_fb.c index bef99bb1755..899e9c47035 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_fb.c +++ b/drivers/gpu/drm/exynos/exynos_drm_fb.c @@ -89,12 +89,74 @@ static int exynos_drm_fb_create_handle(struct drm_framebuffer *fb, &exynos_fb->exynos_gem_obj[0]->base, handle); } +static void exynos_drm_fb_adjust_clips(struct exynos_drm_partial_pos *pos, + struct drm_clip_rect *clips, unsigned int num_clips) +{ + unsigned int index; + unsigned int min_sx = 0; + unsigned int min_sy = 0; + unsigned int max_bx = 0; + unsigned int max_by = 0; + + for (index = 0; index < num_clips; index++) { + min_sx = min_t(unsigned int, clips[index].x1, min_sx); + if (min_sx == 0 && index == 0) + min_sx = clips[index].x1; + + min_sy = min_t(unsigned int, clips[index].y1, min_sy); + if (min_sy == 0 && index == 0) + min_sy = clips[index].y1; + + max_bx = max_t(unsigned int, clips[index].x2, max_bx); + max_by = max_t(unsigned int, clips[index].y2, max_by); + + DRM_DEBUG_KMS("[%d] x1(%d) y1(%d) x2(%d) y2(%d)\n", + index, clips[index].x1, clips[index].y1, + clips[index].x2, clips[index].y2); + } + + pos->x = min_sx; + pos->y = min_sy; + pos->w = max_bx - min_sx; + pos->h = max_by - min_sy; + + DRM_DEBUG_KMS("partial region: x1(%d) y1(%d) x2(%d) y2(%d)\n", + pos->x, pos->y, pos->w, pos->h); +} + static int exynos_drm_fb_dirty(struct drm_framebuffer *fb, struct drm_file *file_priv, unsigned flags, unsigned color, struct drm_clip_rect *clips, unsigned num_clips) { - /* TODO */ + struct exynos_drm_fb *exynos_fb = to_exynos_fb(fb); + + if (!num_clips || !clips) + return -EINVAL; + + /* + * Get maximum region to all clip regions + * if one more clip regions exsit. + */ + if (num_clips > 1) { + DRM_DEBUG_KMS("use only one clip region.\n"); + + exynos_drm_fb_adjust_clips(&exynos_fb->part_pos, clips, + num_clips); + goto out; + } + + atomic_set(&exynos_fb->partial_mode, 1); + + exynos_fb->part_pos.x = clips[0].x1; + exynos_fb->part_pos.y = clips[0].y1; + exynos_fb->part_pos.w = clips[0].x2 - clips[0].x1; + exynos_fb->part_pos.h = clips[0].y2 - clips[0].y1; + +out: + DRM_INFO("%s: x1 = %d, y1 = %d, w = %d, h = %d\n", + __func__, exynos_fb->part_pos.x, exynos_fb->part_pos.y, + exynos_fb->part_pos.w, exynos_fb->part_pos.h); return 0; } diff --git a/drivers/gpu/drm/exynos/exynos_drm_fb.h b/drivers/gpu/drm/exynos/exynos_drm_fb.h index 3163e41c8e0..085f03209ff 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_fb.h +++ b/drivers/gpu/drm/exynos/exynos_drm_fb.h @@ -16,17 +16,27 @@ #define to_exynos_fb(x) container_of(x, struct exynos_drm_fb, fb) +struct exynos_drm_partial_pos { + unsigned int x; + unsigned int y; + unsigned int w; + unsigned int h; +}; + /* * exynos specific framebuffer structure. * * @fb: drm framebuffer obejct. * @buf_cnt: a buffer count to drm framebuffer. * @exynos_gem_obj: array of exynos specific gem object containing a gem object. + * @part_pos: a position to partial update region. */ struct exynos_drm_fb { struct drm_framebuffer fb; unsigned int buf_cnt; struct exynos_drm_gem_obj *exynos_gem_obj[MAX_FB_BUFFER]; + struct exynos_drm_partial_pos part_pos; + atomic_t partial_mode; }; struct drm_framebuffer * diff --git a/drivers/gpu/drm/exynos/exynos_drm_fimd.c b/drivers/gpu/drm/exynos/exynos_drm_fimd.c index 8b3c7a43447..26f66dd4a36 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_fimd.c +++ b/drivers/gpu/drm/exynos/exynos_drm_fimd.c @@ -41,6 +41,8 @@ #define FIMD_DEFAULT_FRAMERATE 60 +#define WIDTH_LIMIT 16 + /* position control register for hardware window 0, 2 ~ 4.*/ #define VIDOSD_A(win) (VIDOSD_BASE + 0x00 + (win) * 16) #define VIDOSD_B(win) (VIDOSD_BASE + 0x04 + (win) * 16) @@ -173,6 +175,10 @@ struct fimd_context { struct exynos_drm_panel_info panel; struct fimd_driver_data *driver_data; struct notifier_block nb_ctrl; + unsigned int current_x; + unsigned int current_y; + unsigned int current_w; + unsigned int current_h; }; static const struct of_device_id fimd_driver_dt_match[] = { @@ -980,6 +986,76 @@ static void fimd_trigger(struct device *dev) writel(reg, timing_base + TRIGCON); } +static void fimd_adjust_partial_region(struct exynos_drm_manager *mgr, + unsigned int *x, unsigned int *y, unsigned int *w, + unsigned int *h) +{ + struct fimd_context *ctx = mgr->ctx; + unsigned int old_x; + unsigned int old_w; + + old_x = *x; + old_w = *w; + + /* + * if pos->x is bigger than 0, adjusts pos->x and pos->w roughly. + * + * ######################################### <- image + * | | | + * | before(x) before(w) + * after(x) after(w) + */ + if (*x > 0) { + *x = ALIGN(*x, WIDTH_LIMIT) - WIDTH_LIMIT; + *w += WIDTH_LIMIT; + } + + /* + * pos->w should be WIDTH_LIMIT if pos->w is 0, and also pos->x should + * be adjusted properly if pos->x is bigger than WIDTH_LIMIT. + */ + if (*w == 0) { + if (*x > WIDTH_LIMIT) + *x -= WIDTH_LIMIT; + *w = WIDTH_LIMIT; + } else + *w = ALIGN(*w, WIDTH_LIMIT); + + /* + * pos->x + pos->w should be smaller than horizontal size of display. + * If not so, page fault exception will be occurred. + */ + if (*x + *w > ctx->mode.hdisplay) + *w = ctx->mode.hdisplay - *x; + + ctx->current_x = *x; + ctx->current_y = *y; + ctx->current_w = *w; + ctx->current_h = *h; + + DRM_DEBUG_KMS("Adjusted x = %d -> %d, and w = %d -> %d\n", + old_x, *x, old_w, *w); +} + +static void fimd_change_resolution(struct exynos_drm_manager *mgr) +{ + struct fimd_context *ctx = mgr->ctx; + struct fimd_driver_data *driver_data = ctx->driver_data; + unsigned long val; + + /* setup horizontal and vertical display size. */ + val = VIDTCON2_LINEVAL(ctx->current_h - 1) | + VIDTCON2_HOZVAL(ctx->current_w - 1) | + VIDTCON2_LINEVAL_E(ctx->current_h - 1) | + VIDTCON2_HOZVAL_E(ctx->current_w - 1); + writel(val, ctx->regs + driver_data->timing_base + VIDTCON2); + + ctx->current_x = 0; + ctx->current_y = 0; + ctx->current_h = 0; + ctx->current_w = 0; +} + static int fimd_te_handler(struct exynos_drm_manager *mgr) { struct fimd_context *ctx = mgr->ctx; @@ -999,6 +1075,14 @@ static int fimd_te_handler(struct exynos_drm_manager *mgr) atomic_set(&ctx->win_updated, 0); spin_unlock_irqrestore(&ctx->win_updated_lock, flags); + if (ctx->current_w && ctx->current_h) { + exynos_drm_crtc_change_resolution(ctx->drm_dev, + mgr->pipe, ctx->current_x, + ctx->current_y, ctx->current_w, + ctx->current_h); + fimd_change_resolution(mgr); + } + fimd_trigger(ctx->dev); spin_lock_irqsave(&ctx->win_updated_lock, flags); @@ -1026,6 +1110,7 @@ static struct exynos_drm_manager_ops fimd_manager_ops = { .win_commit = fimd_win_commit, .win_disable = fimd_win_disable, .te_handler = fimd_te_handler, + .adjust_partial_region = fimd_adjust_partial_region, }; static struct exynos_drm_manager fimd_manager = { diff --git a/drivers/gpu/drm/exynos/exynos_drm_plane.c b/drivers/gpu/drm/exynos/exynos_drm_plane.c index 8371cbd7631..38610a5ab41 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_plane.c +++ b/drivers/gpu/drm/exynos/exynos_drm_plane.c @@ -77,6 +77,7 @@ int exynos_plane_mode_set(struct drm_plane *plane, struct drm_crtc *crtc, { struct exynos_plane *exynos_plane = to_exynos_plane(plane); struct exynos_drm_overlay *overlay = &exynos_plane->overlay; + struct exynos_drm_fb *exynos_fb = to_exynos_fb(fb); unsigned int actual_w; unsigned int actual_h; int nr; @@ -112,6 +113,28 @@ int exynos_plane_mode_set(struct drm_plane *plane, struct drm_crtc *crtc, crtc_y = 0; } + /* Change overlay values to partial region. */ + if (atomic_read(&exynos_fb->partial_mode)) { + struct exynos_drm_partial_pos *pos; + + exynos_drm_crtc_adjust_partial_region(crtc, + &exynos_fb->part_pos); + + pos = &exynos_fb->part_pos; + crtc_x = 0; + crtc_y = 0; + crtc_w = pos->w; + crtc_h = pos->h; + src_x = pos->x; + src_y = pos->y; + src_w = pos->w; + src_h = pos->h; + actual_w = pos->w; + actual_h = pos->h; + + atomic_set(&exynos_fb->partial_mode, 0); + } + /* set drm framebuffer data. */ overlay->fb_x = src_x; overlay->fb_y = src_y; diff --git a/drivers/gpu/drm/panel/panel-s6e63j0x03.c b/drivers/gpu/drm/panel/panel-s6e63j0x03.c index de7bdfa6615..3eb33cf8061 100644 --- a/drivers/gpu/drm/panel/panel-s6e63j0x03.c +++ b/drivers/gpu/drm/panel/panel-s6e63j0x03.c @@ -31,6 +31,9 @@ #define MCS_MTP_SET3 0xd4 #define MCS_MTP_KEY 0xf1 +#define LDI_CASET_REG 0x2A +#define LDI_PASET_REG 0x2B + #define MIN_BRIGHTNESS 0 #define MAX_BRIGHTNESS 100 #define DEFAULT_BRIGHTNESS 80 @@ -548,10 +551,45 @@ static int s6e63j0x03_get_modes(struct drm_panel *panel) return 1; } +static int s6e63j0x03_change_resolution(struct drm_panel *panel, + unsigned int x, unsigned int y, + unsigned int w, unsigned int h) +{ + struct s6e63j0x03 *ctx = panel_to_s6e63j0x03(panel); + char buf[5]; + + if (ctx->power > FB_BLANK_UNBLANK) + return -EPERM; + + if (y >= ctx->vm.vactive || h > ctx->vm.vactive) + return -EINVAL; + + x += 20; + + buf[0] = LDI_CASET_REG; + buf[1] = (x & 0xff00) >> 8; + buf[2] = x & 0x00ff; + buf[3] = ((x + w - 1) & 0xff00) >> 8; + buf[4] = (x + w - 1) & 0x00ff; + + s6e63j0x03_dcs_write(ctx, buf, ARRAY_SIZE(buf)); + + buf[0] = LDI_PASET_REG; + buf[1] = (y & 0xff00) >> 8; + buf[2] = y & 0x00ff; + buf[3] = ((y + h - 1) & 0xff00) >> 8; + buf[4] = (y + h - 1) & 0x00ff; + + s6e63j0x03_dcs_write(ctx, buf, ARRAY_SIZE(buf)); + + return 0; +} + static const struct drm_panel_funcs s6e63j0x03_drm_funcs = { .disable = s6e63j0x03_disable, .enable = s6e63j0x03_enable, .get_modes = s6e63j0x03_get_modes, + .change_resolution = s6e63j0x03_change_resolution, }; static int s6e63j0x03_parse_dt(struct s6e63j0x03 *ctx) diff --git a/include/drm/drm_panel.h b/include/drm/drm_panel.h index 29e3daf1b25..cd42e80905c 100644 --- a/include/drm/drm_panel.h +++ b/include/drm/drm_panel.h @@ -38,6 +38,7 @@ struct drm_panel; * @enable: enable panel (turn on back light, etc.) * @get_modes: add modes to the connector that the panel is attached to and * return the number of modes added + * @change_resolution: change panel resolution to a given position. * * The .prepare() function is typically called before the display controller * starts to transmit video data. Panel drivers can use this to turn the panel @@ -68,6 +69,9 @@ struct drm_panel_funcs { int (*prepare)(struct drm_panel *panel); int (*enable)(struct drm_panel *panel); int (*get_modes)(struct drm_panel *panel); + int (*change_resolution)(struct drm_panel *panel, unsigned int x, + unsigned int y, unsigned int w, + unsigned int h); }; struct drm_panel { @@ -112,6 +116,16 @@ static inline int drm_panel_enable(struct drm_panel *panel) return panel ? -ENOSYS : -EINVAL; } + +static inline void drm_panel_change_resolution(struct drm_panel *panel, + unsigned int x, unsigned int y, + unsigned int w, unsigned int h) +{ + + if (panel && panel->funcs && panel->funcs->change_resolution) + panel->funcs->change_resolution(panel, x, y, w, h); +} + void drm_panel_init(struct drm_panel *panel); int drm_panel_add(struct drm_panel *panel); -- cgit v1.2.3