/* Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ #define pr_fmt(fmt) "%s: " fmt, __func__ #include #include #include #include "mdss_fb.h" #include "mdss_mdp.h" /* wait for at least 2 vsyncs for lowest refresh rate (24hz) */ #define VSYNC_TIMEOUT_US 100000 #define MDP_INTR_MASK_INTF_VSYNC(intf_num) \ (1 << (2 * (intf_num - MDSS_MDP_INTF0) + MDSS_MDP_IRQ_INTF_VSYNC)) /* intf timing settings */ struct intf_timing_params { u32 width; u32 height; u32 xres; u32 yres; u32 h_back_porch; u32 h_front_porch; u32 v_back_porch; u32 v_front_porch; u32 hsync_pulse_width; u32 vsync_pulse_width; u32 border_clr; u32 underflow_clr; u32 hsync_skew; }; struct mdss_mdp_video_ctx { u32 intf_num; char __iomem *base; u32 intf_type; u8 ref_cnt; u8 timegen_en; bool polling_en; u32 poll_cnt; struct completion vsync_comp; int wait_pending; atomic_t vsync_ref; spinlock_t vsync_lock; struct list_head vsync_handlers; }; static inline void mdp_video_write(struct mdss_mdp_video_ctx *ctx, u32 reg, u32 val) { writel_relaxed(val, ctx->base + reg); } static inline u32 mdp_video_read(struct mdss_mdp_video_ctx *ctx, u32 reg) { return readl_relaxed(ctx->base + reg); } static inline u32 mdss_mdp_video_line_count(struct mdss_mdp_ctl *ctl) { struct mdss_mdp_video_ctx *ctx = ctl->priv_data; u32 line_cnt = 0; mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON, false); line_cnt = mdp_video_read(ctx, MDSS_MDP_REG_INTF_LINE_COUNT); mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false); return line_cnt; } int mdss_mdp_video_addr_setup(struct mdss_data_type *mdata, u32 *offsets, u32 count) { struct mdss_mdp_video_ctx *head; u32 i; head = devm_kzalloc(&mdata->pdev->dev, sizeof(struct mdss_mdp_video_ctx) * count, GFP_KERNEL); if (!head) return -ENOMEM; for (i = 0; i < count; i++) { head[i].base = mdata->mdp_base + offsets[i]; pr_debug("adding Video Intf #%d offset=0x%x virt=%p\n", i, offsets[i], head[i].base); head[i].ref_cnt = 0; head[i].intf_num = i + MDSS_MDP_INTF0; INIT_LIST_HEAD(&head[i].vsync_handlers); } mdata->video_intf = head; mdata->nintf = count; return 0; } static int mdss_mdp_video_timegen_setup(struct mdss_mdp_video_ctx *ctx, struct intf_timing_params *p) { u32 hsync_period, vsync_period; u32 hsync_start_x, hsync_end_x, display_v_start, display_v_end; u32 active_h_start, active_h_end, active_v_start, active_v_end; u32 den_polarity, hsync_polarity, vsync_polarity; u32 display_hctl, active_hctl, hsync_ctl, polarity_ctl; hsync_period = p->hsync_pulse_width + p->h_back_porch + p->width + p->h_front_porch; vsync_period = p->vsync_pulse_width + p->v_back_porch + p->height + p->v_front_porch; display_v_start = ((p->vsync_pulse_width + p->v_back_porch) * hsync_period) + p->hsync_skew; display_v_end = ((vsync_period - p->v_front_porch) * hsync_period) + p->hsync_skew - 1; if (ctx->intf_type == MDSS_INTF_EDP) { display_v_start += p->hsync_pulse_width + p->h_back_porch; display_v_end -= p->h_front_porch; } hsync_start_x = p->h_back_porch + p->hsync_pulse_width; hsync_end_x = hsync_period - p->h_front_porch - 1; if (p->width != p->xres) { active_h_start = hsync_start_x; active_h_end = active_h_start + p->xres - 1; } else { active_h_start = 0; active_h_end = 0; } if (p->height != p->yres) { active_v_start = display_v_start; active_v_end = active_v_start + (p->yres * hsync_period) - 1; } else { active_v_start = 0; active_v_end = 0; } if (active_h_end) { active_hctl = (active_h_end << 16) | active_h_start; active_hctl |= BIT(31); /* ACTIVE_H_ENABLE */ } else { active_hctl = 0; } if (active_v_end) active_v_start |= BIT(31); /* ACTIVE_V_ENABLE */ hsync_ctl = (hsync_period << 16) | p->hsync_pulse_width; display_hctl = (hsync_end_x << 16) | hsync_start_x; den_polarity = 0; if (MDSS_INTF_HDMI == ctx->intf_type) { hsync_polarity = p->yres >= 720 ? 0 : 1; vsync_polarity = p->yres >= 720 ? 0 : 1; } else { hsync_polarity = 0; vsync_polarity = 0; } polarity_ctl = (den_polarity << 2) | /* DEN Polarity */ (vsync_polarity << 1) | /* VSYNC Polarity */ (hsync_polarity << 0); /* HSYNC Polarity */ mdp_video_write(ctx, MDSS_MDP_REG_INTF_HSYNC_CTL, hsync_ctl); mdp_video_write(ctx, MDSS_MDP_REG_INTF_VSYNC_PERIOD_F0, vsync_period * hsync_period); mdp_video_write(ctx, MDSS_MDP_REG_INTF_VSYNC_PULSE_WIDTH_F0, p->vsync_pulse_width * hsync_period); mdp_video_write(ctx, MDSS_MDP_REG_INTF_DISPLAY_HCTL, display_hctl); mdp_video_write(ctx, MDSS_MDP_REG_INTF_DISPLAY_V_START_F0, display_v_start); mdp_video_write(ctx, MDSS_MDP_REG_INTF_DISPLAY_V_END_F0, display_v_end); mdp_video_write(ctx, MDSS_MDP_REG_INTF_ACTIVE_HCTL, active_hctl); mdp_video_write(ctx, MDSS_MDP_REG_INTF_ACTIVE_V_START_F0, active_v_start); mdp_video_write(ctx, MDSS_MDP_REG_INTF_ACTIVE_V_END_F0, active_v_end); mdp_video_write(ctx, MDSS_MDP_REG_INTF_BORDER_COLOR, p->border_clr); mdp_video_write(ctx, MDSS_MDP_REG_INTF_UNDERFLOW_COLOR, p->underflow_clr); mdp_video_write(ctx, MDSS_MDP_REG_INTF_HSYNC_SKEW, p->hsync_skew); mdp_video_write(ctx, MDSS_MDP_REG_INTF_POLARITY_CTL, polarity_ctl); mdp_video_write(ctx, MDSS_MDP_REG_INTF_FRAME_LINE_COUNT_EN, 0x3); return 0; } static inline void video_vsync_irq_enable(struct mdss_mdp_ctl *ctl, bool clear) { struct mdss_mdp_video_ctx *ctx = ctl->priv_data; if (atomic_inc_return(&ctx->vsync_ref) == 1) mdss_mdp_irq_enable(MDSS_MDP_IRQ_INTF_VSYNC, ctl->intf_num); else if (clear) mdss_mdp_irq_clear(ctl->mdata, MDSS_MDP_IRQ_INTF_VSYNC, ctl->intf_num); } static inline void video_vsync_irq_disable(struct mdss_mdp_ctl *ctl) { struct mdss_mdp_video_ctx *ctx = ctl->priv_data; if (atomic_dec_return(&ctx->vsync_ref) == 0) mdss_mdp_irq_disable(MDSS_MDP_IRQ_INTF_VSYNC, ctl->intf_num); } static int mdss_mdp_video_add_vsync_handler(struct mdss_mdp_ctl *ctl, struct mdss_mdp_vsync_handler *handle) { struct mdss_mdp_video_ctx *ctx; unsigned long flags; int ret = 0; bool irq_en = false; if (!handle || !(handle->vsync_handler)) { ret = -EINVAL; goto exit; } ctx = (struct mdss_mdp_video_ctx *) ctl->priv_data; if (!ctx) { pr_err("invalid ctx for ctl=%d\n", ctl->num); ret = -ENODEV; goto exit; } spin_lock_irqsave(&ctx->vsync_lock, flags); if (!handle->enabled) { handle->enabled = true; list_add(&handle->list, &ctx->vsync_handlers); irq_en = true; } spin_unlock_irqrestore(&ctx->vsync_lock, flags); if (irq_en) video_vsync_irq_enable(ctl, false); exit: return ret; } static int mdss_mdp_video_remove_vsync_handler(struct mdss_mdp_ctl *ctl, struct mdss_mdp_vsync_handler *handle) { struct mdss_mdp_video_ctx *ctx; unsigned long flags; bool irq_dis = false; ctx = (struct mdss_mdp_video_ctx *) ctl->priv_data; if (!ctx) { pr_err("invalid ctx for ctl=%d\n", ctl->num); return -ENODEV; } spin_lock_irqsave(&ctx->vsync_lock, flags); if (handle->enabled) { handle->enabled = false; list_del_init(&handle->list); irq_dis = true; } spin_unlock_irqrestore(&ctx->vsync_lock, flags); if (irq_dis) video_vsync_irq_disable(ctl); return 0; } static int mdss_mdp_video_stop(struct mdss_mdp_ctl *ctl) { struct mdss_mdp_video_ctx *ctx; struct mdss_mdp_vsync_handler *tmp, *handle; int rc; pr_debug("stop ctl=%d\n", ctl->num); ctx = (struct mdss_mdp_video_ctx *) ctl->priv_data; if (!ctx) { pr_err("invalid ctx for ctl=%d\n", ctl->num); return -ENODEV; } if (ctx->timegen_en) { rc = mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_BLANK, NULL); if (rc == -EBUSY) { pr_debug("intf #%d busy don't turn off\n", ctl->intf_num); return rc; } WARN(rc, "intf %d blank error (%d)\n", ctl->intf_num, rc); mdp_video_write(ctx, MDSS_MDP_REG_INTF_TIMING_ENGINE_EN, 0); mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false); ctx->timegen_en = false; rc = mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_PANEL_OFF, NULL); WARN(rc, "intf %d timegen off error (%d)\n", ctl->intf_num, rc); mdss_mdp_irq_disable(MDSS_MDP_IRQ_INTF_UNDER_RUN, ctl->intf_num); } list_for_each_entry_safe(handle, tmp, &ctx->vsync_handlers, list) mdss_mdp_video_remove_vsync_handler(ctl, handle); mdss_mdp_set_intr_callback(MDSS_MDP_IRQ_INTF_VSYNC, ctl->intf_num, NULL, NULL); mdss_mdp_set_intr_callback(MDSS_MDP_IRQ_INTF_UNDER_RUN, ctl->intf_num, NULL, NULL); ctx->ref_cnt--; ctl->priv_data = NULL; return 0; } static void mdss_mdp_video_vsync_intr_done(void *arg) { struct mdss_mdp_ctl *ctl = arg; struct mdss_mdp_video_ctx *ctx = ctl->priv_data; struct mdss_mdp_vsync_handler *tmp; ktime_t vsync_time; if (!ctx) { pr_err("invalid ctx\n"); return; } vsync_time = ktime_get(); ctl->vsync_cnt++; pr_debug("intr ctl=%d vsync cnt=%u vsync_time=%d\n", ctl->num, ctl->vsync_cnt, (int)ktime_to_ms(vsync_time)); ctx->polling_en = false; complete_all(&ctx->vsync_comp); spin_lock(&ctx->vsync_lock); list_for_each_entry(tmp, &ctx->vsync_handlers, list) { tmp->vsync_handler(ctl, vsync_time); } spin_unlock(&ctx->vsync_lock); } static int mdss_mdp_video_pollwait(struct mdss_mdp_ctl *ctl) { struct mdss_mdp_video_ctx *ctx = ctl->priv_data; u32 mask, status; int rc; mask = MDP_INTR_MASK_INTF_VSYNC(ctl->intf_num); mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON, false); rc = readl_poll_timeout(ctl->mdata->mdp_base + MDSS_MDP_REG_INTR_STATUS, status, (status & mask) || try_wait_for_completion(&ctx->vsync_comp), 1000, VSYNC_TIMEOUT_US); mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false); if (rc == 0) { pr_debug("vsync poll successful! rc=%d status=0x%x\n", rc, status); ctx->poll_cnt++; if (status) { struct mdss_mdp_vsync_handler *tmp; unsigned long flags; ktime_t vsync_time = ktime_get(); spin_lock_irqsave(&ctx->vsync_lock, flags); list_for_each_entry(tmp, &ctx->vsync_handlers, list) tmp->vsync_handler(ctl, vsync_time); spin_unlock_irqrestore(&ctx->vsync_lock, flags); } } else { pr_warn("vsync poll timed out! rc=%d status=0x%x mask=0x%x\n", rc, status, mask); } return rc; } static int mdss_mdp_video_wait4comp(struct mdss_mdp_ctl *ctl, void *arg) { struct mdss_mdp_video_ctx *ctx; int rc; ctx = (struct mdss_mdp_video_ctx *) ctl->priv_data; if (!ctx) { pr_err("invalid ctx\n"); return -ENODEV; } WARN(!ctx->wait_pending, "waiting without commit! ctl=%d", ctl->num); if (ctx->polling_en) { rc = mdss_mdp_video_pollwait(ctl); } else { rc = wait_for_completion_interruptible_timeout(&ctx->vsync_comp, usecs_to_jiffies(VSYNC_TIMEOUT_US)); if (rc < 0) { pr_warn("vsync wait interrupted ctl=%d\n", ctl->num); } else if (rc == 0) { pr_warn("vsync wait timeout %d, fallback to poll mode\n", ctl->num); ctx->polling_en++; rc = mdss_mdp_video_pollwait(ctl); } else { rc = 0; } } if (ctx->wait_pending) { ctx->wait_pending = 0; video_vsync_irq_disable(ctl); } return rc; } static void mdss_mdp_video_underrun_intr_done(void *arg) { struct mdss_mdp_ctl *ctl = arg; if (unlikely(!ctl)) return; ctl->underrun_cnt++; pr_debug("display underrun detected for ctl=%d count=%d\n", ctl->num, ctl->underrun_cnt); } static int mdss_mdp_video_display(struct mdss_mdp_ctl *ctl, void *arg) { struct mdss_mdp_video_ctx *ctx; int rc; pr_debug("kickoff ctl=%d\n", ctl->num); ctx = (struct mdss_mdp_video_ctx *) ctl->priv_data; if (!ctx) { pr_err("invalid ctx\n"); return -ENODEV; } if (!ctx->wait_pending) { ctx->wait_pending++; video_vsync_irq_enable(ctl, true); INIT_COMPLETION(ctx->vsync_comp); } else { WARN(1, "commit without wait! ctl=%d", ctl->num); } if (!ctx->timegen_en) { rc = mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_UNBLANK, NULL); if (rc) { pr_warn("intf #%d unblank error (%d)\n", ctl->intf_num, rc); video_vsync_irq_disable(ctl); ctx->wait_pending = 0; return rc; } pr_debug("enabling timing gen for intf=%d\n", ctl->intf_num); mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON, false); mdss_mdp_irq_enable(MDSS_MDP_IRQ_INTF_UNDER_RUN, ctl->intf_num); mdp_video_write(ctx, MDSS_MDP_REG_INTF_TIMING_ENGINE_EN, 1); wmb(); rc = wait_for_completion_interruptible_timeout(&ctx->vsync_comp, usecs_to_jiffies(VSYNC_TIMEOUT_US)); WARN(rc <= 0, "timeout (%d) enabling timegen on ctl=%d\n", rc, ctl->num); ctx->timegen_en = true; rc = mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_PANEL_ON, NULL); WARN(rc, "intf %d panel on error (%d)\n", ctl->intf_num, rc); } return 0; } int mdss_mdp_video_copy_splash_screen(struct mdss_panel_data *pdata) { void *virt = NULL; unsigned long bl_fb_addr = 0; unsigned long *bl_fb_addr_va; unsigned long pipe_addr, pipe_src_size; u32 height, width, rgb_size, bpp; size_t size; static struct ion_handle *ihdl; struct ion_client *iclient = mdss_get_ionclient(); static ion_phys_addr_t phys; pipe_addr = MDSS_MDP_REG_SSPP_OFFSET(3) + MDSS_MDP_REG_SSPP_SRC0_ADDR; pipe_src_size = MDSS_MDP_REG_SSPP_OFFSET(3) + MDSS_MDP_REG_SSPP_SRC_SIZE; bpp = 3; rgb_size = MDSS_MDP_REG_READ(pipe_src_size); bl_fb_addr = MDSS_MDP_REG_READ(pipe_addr); height = (rgb_size >> 16) & 0xffff; width = rgb_size & 0xffff; size = PAGE_ALIGN(height * width * bpp); pr_debug("%s:%d splash_height=%d splash_width=%d Buffer size=%d\n", __func__, __LINE__, height, width, size); ihdl = ion_alloc(iclient, size, SZ_1M, ION_HEAP(ION_QSECOM_HEAP_ID), 0); if (IS_ERR_OR_NULL(ihdl)) { pr_err("unable to alloc fbmem from ion (%p)\n", ihdl); return -ENOMEM; } pdata->panel_info.splash_ihdl = ihdl; virt = ion_map_kernel(iclient, ihdl); ion_phys(iclient, ihdl, &phys, &size); pr_debug("%s %d Allocating %u bytes at 0x%lx (%pa phys)\n", __func__, __LINE__, size, (unsigned long int)virt, &phys); bl_fb_addr_va = (unsigned long *)ioremap(bl_fb_addr, size); memcpy(virt, bl_fb_addr_va, size); MDSS_MDP_REG_WRITE(pipe_addr, phys); MDSS_MDP_REG_WRITE(MDSS_MDP_REG_CTL_FLUSH + MDSS_MDP_REG_CTL_OFFSET(0), 0x48); return 0; } int mdss_mdp_video_reconfigure_splash_done(struct mdss_mdp_ctl *ctl) { struct ion_client *iclient = mdss_get_ionclient(); struct mdss_panel_data *pdata; int ret = 0, off; int mdss_mdp_rev = MDSS_MDP_REG_READ(MDSS_MDP_REG_HW_VERSION); int mdss_v2_intf_off = 0; off = 0; pdata = ctl->panel_data; pdata->panel_info.cont_splash_enabled = 0; ret = mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_CONT_SPLASH_BEGIN, NULL); if (ret) { pr_err("%s: Failed to handle 'CONT_SPLASH_BEGIN' event\n", __func__); return ret; } mdss_mdp_ctl_write(ctl, 0, MDSS_MDP_LM_BORDER_COLOR); off = MDSS_MDP_REG_INTF_OFFSET(ctl->intf_num); if (mdss_mdp_rev == MDSS_MDP_HW_REV_102) mdss_v2_intf_off = 0xEC00; MDSS_MDP_REG_WRITE(off + MDSS_MDP_REG_INTF_TIMING_ENGINE_EN - mdss_v2_intf_off, 0); /* wait for 1 VSYNC for the pipe to be unstaged */ msleep(20); ion_free(iclient, pdata->panel_info.splash_ihdl); ret = mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_CONT_SPLASH_FINISH, NULL); mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false); return ret; } int mdss_mdp_video_start(struct mdss_mdp_ctl *ctl) { struct mdss_data_type *mdata; struct mdss_panel_info *pinfo; struct mdss_mdp_video_ctx *ctx; struct mdss_mdp_mixer *mixer; struct intf_timing_params itp = {0}; u32 dst_bpp; int i; mdata = ctl->mdata; pinfo = &ctl->panel_data->panel_info; mixer = mdss_mdp_mixer_get(ctl, MDSS_MDP_MIXER_MUX_LEFT); if (!mixer) { pr_err("mixer not setup correctly\n"); return -ENODEV; } i = ctl->intf_num - MDSS_MDP_INTF0; if (i < mdata->nintf) { ctx = ((struct mdss_mdp_video_ctx *) mdata->video_intf) + i; if (ctx->ref_cnt) { pr_err("Intf %d already in use\n", ctl->intf_num); return -EBUSY; } pr_debug("video Intf #%d base=%p", ctx->intf_num, ctx->base); ctx->ref_cnt++; } else { pr_err("Invalid intf number: %d\n", ctl->intf_num); return -EINVAL; } pr_debug("start ctl=%u\n", ctl->num); ctl->priv_data = ctx; ctx->intf_type = ctl->intf_type; init_completion(&ctx->vsync_comp); spin_lock_init(&ctx->vsync_lock); atomic_set(&ctx->vsync_ref, 0); mdss_mdp_set_intr_callback(MDSS_MDP_IRQ_INTF_VSYNC, ctl->intf_num, mdss_mdp_video_vsync_intr_done, ctl); mdss_mdp_set_intr_callback(MDSS_MDP_IRQ_INTF_UNDER_RUN, ctl->intf_num, mdss_mdp_video_underrun_intr_done, ctl); dst_bpp = pinfo->fbc.enabled ? (pinfo->fbc.target_bpp) : (pinfo->bpp); itp.width = mult_frac((pinfo->xres + pinfo->lcdc.xres_pad), dst_bpp, pinfo->bpp); itp.height = pinfo->yres + pinfo->lcdc.yres_pad; itp.border_clr = pinfo->lcdc.border_clr; itp.underflow_clr = pinfo->lcdc.underflow_clr; itp.hsync_skew = pinfo->lcdc.hsync_skew; itp.xres = mult_frac(pinfo->xres, dst_bpp, pinfo->bpp); itp.yres = pinfo->yres; itp.h_back_porch = mult_frac(pinfo->lcdc.h_back_porch, dst_bpp, pinfo->bpp); itp.h_front_porch = mult_frac(pinfo->lcdc.h_front_porch, dst_bpp, pinfo->bpp); itp.v_back_porch = mult_frac(pinfo->lcdc.v_back_porch, dst_bpp, pinfo->bpp); itp.v_front_porch = mult_frac(pinfo->lcdc.v_front_porch, dst_bpp, pinfo->bpp); itp.hsync_pulse_width = mult_frac(pinfo->lcdc.h_pulse_width, dst_bpp, pinfo->bpp); itp.vsync_pulse_width = pinfo->lcdc.v_pulse_width; if (mdss_mdp_video_timegen_setup(ctx, &itp)) { pr_err("unable to get timing parameters\n"); return -EINVAL; } mdp_video_write(ctx, MDSS_MDP_REG_INTF_PANEL_FORMAT, ctl->dst_format); ctl->stop_fnc = mdss_mdp_video_stop; ctl->display_fnc = mdss_mdp_video_display; ctl->wait_fnc = mdss_mdp_video_wait4comp; ctl->read_line_cnt_fnc = mdss_mdp_video_line_count; ctl->add_vsync_handler = mdss_mdp_video_add_vsync_handler; ctl->remove_vsync_handler = mdss_mdp_video_remove_vsync_handler; return 0; }