/* Copyright (c) 2012-2015, 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 #include "mdss_mdp.h" #include "mdss_mdp_trace.h" #include "mdss_debug.h" #define SMP_MB_SIZE (mdss_res->smp_mb_size) #define SMP_MB_CNT (mdss_res->smp_mb_cnt) #define SMP_MB_ENTRY_SIZE 16 #define MAX_BPP 4 #define PIPE_CLEANUP_TIMEOUT_US 100000 /* following offsets are relative to ctrl register bit offset */ #define CLK_FORCE_ON_OFFSET 0x0 #define CLK_FORCE_OFF_OFFSET 0x1 /* following offsets are relative to status register bit offset */ #define CLK_STATUS_OFFSET 0x0 #define QOS_LUT_NRT_READ 0x0 #define PANIC_LUT_NRT_READ 0x0 #define ROBUST_LUT_NRT_READ 0xFFFF /* Priority 2, no panic */ #define VBLANK_PANIC_DEFAULT_CONFIG 0x200000 static DEFINE_MUTEX(mdss_mdp_sspp_lock); static DEFINE_MUTEX(mdss_mdp_smp_lock); static void mdss_mdp_pipe_free(struct kref *kref); static int mdss_mdp_smp_mmb_set(int client_id, unsigned long *smp); static void mdss_mdp_smp_mmb_free(unsigned long *smp, bool write); static struct mdss_mdp_pipe *mdss_mdp_pipe_search_by_client_id( struct mdss_data_type *mdata, int client_id); static int mdss_mdp_calc_stride(struct mdss_mdp_pipe *pipe, struct mdss_mdp_plane_sizes *ps); static u32 mdss_mdp_calc_per_plane_num_blks(u32 ystride, struct mdss_mdp_pipe *pipe); static int mdss_mdp_pipe_program_pixel_extn(struct mdss_mdp_pipe *pipe); static inline void mdss_mdp_pipe_write(struct mdss_mdp_pipe *pipe, u32 reg, u32 val) { writel_relaxed(val, pipe->base + reg); } static inline u32 mdss_mdp_pipe_read(struct mdss_mdp_pipe *pipe, u32 reg) { return readl_relaxed(pipe->base + reg); } static inline int mdss_calc_fill_level(struct mdss_mdp_format_params *fmt, u32 src_width) { struct mdss_data_type *mdata = mdss_mdp_get_mdata(); u32 fixed_buff_size = mdata->pixel_ram_size; u32 total_fl; if (fmt->fetch_planes == MDSS_MDP_PLANE_PSEUDO_PLANAR) { if (fmt->chroma_sample == MDSS_MDP_CHROMA_420) { /* NV12 */ total_fl = (fixed_buff_size / 2) / ((src_width + 32) * fmt->bpp); } else { /* non NV12 */ total_fl = (fixed_buff_size) / ((src_width + 32) * fmt->bpp); } } else { total_fl = (fixed_buff_size * 2) / ((src_width + 32) * fmt->bpp); } return total_fl; } static inline u32 get_qos_lut_linear(u32 total_fl) { u32 qos_lut; if (total_fl <= 4) qos_lut = 0x1B; else if (total_fl <= 5) qos_lut = 0x5B; else if (total_fl <= 6) qos_lut = 0x15B; else if (total_fl <= 7) qos_lut = 0x55B; else if (total_fl <= 8) qos_lut = 0x155B; else if (total_fl <= 9) qos_lut = 0x555B; else if (total_fl <= 10) qos_lut = 0x1555B; else if (total_fl <= 11) qos_lut = 0x5555B; else if (total_fl <= 12) qos_lut = 0x15555B; else qos_lut = 0x55555B; return qos_lut; } static inline u32 get_qos_lut_macrotile(u32 total_fl) { u32 qos_lut; if (total_fl <= 10) qos_lut = 0x1AAff; else if (total_fl <= 11) qos_lut = 0x5AAFF; else if (total_fl <= 12) qos_lut = 0x15AAFF; else qos_lut = 0x55AAFF; return qos_lut; } int mdss_mdp_pipe_qos_lut(struct mdss_mdp_pipe *pipe) { struct mdss_mdp_ctl *ctl = pipe->mixer_left->ctl; u32 qos_lut; u32 total_fl = 0; if ((ctl->intf_num == MDSS_MDP_NO_INTF) || pipe->mixer_left->rotator_mode) { qos_lut = QOS_LUT_NRT_READ; /* low priority for nrt */ } else { total_fl = mdss_calc_fill_level(pipe->src_fmt, pipe->src.w); if (mdss_mdp_is_linear_format(pipe->src_fmt)) qos_lut = get_qos_lut_linear(total_fl); else qos_lut = get_qos_lut_macrotile(total_fl); } trace_mdp_perf_set_qos_luts(pipe->num, pipe->src_fmt->format, ctl->intf_num, pipe->mixer_left->rotator_mode, total_fl, qos_lut, mdss_mdp_is_linear_format(pipe->src_fmt)); pr_debug("pnum:%d fmt:%d intf:%d rot:%d fl:%d lut:0x%x\n", pipe->num, pipe->src_fmt->format, ctl->intf_num, pipe->mixer_left->rotator_mode, total_fl, qos_lut); mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON); mdss_mdp_pipe_write(pipe, MDSS_MDP_REG_SSPP_CREQ_LUT, qos_lut); mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF); return 0; } bool is_rt_pipe(struct mdss_mdp_pipe *pipe) { return pipe && pipe->mixer_left && pipe->mixer_left->type == MDSS_MDP_MIXER_TYPE_INTF; } static void mdss_mdp_config_pipe_panic_lut(struct mdss_mdp_pipe *pipe) { u32 panic_lut, robust_lut; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); if (!is_rt_pipe(pipe)) { panic_lut = PANIC_LUT_NRT_READ; robust_lut = ROBUST_LUT_NRT_READ; } else if (mdss_mdp_is_linear_format(pipe->src_fmt)) { panic_lut = mdata->default_panic_lut_per_pipe_linear; robust_lut = mdata->default_robust_lut_per_pipe_linear; } else { panic_lut = mdata->default_panic_lut_per_pipe_tile; robust_lut = mdata->default_robust_lut_per_pipe_tile; } mdss_mdp_pipe_write(pipe, MDSS_MDP_REG_SSPP_DANGER_LUT, panic_lut); mdss_mdp_pipe_write(pipe, MDSS_MDP_REG_SSPP_SAFE_LUT, robust_lut); trace_mdp_perf_set_panic_luts(pipe->num, pipe->src_fmt->format, pipe->src_fmt->fetch_mode, panic_lut, robust_lut); pr_debug("pnum:%d fmt:%d mode:%d luts[0x%x, 0x%x]\n", pipe->num, pipe->src_fmt->format, pipe->src_fmt->fetch_mode, panic_lut, robust_lut); } /** * @mdss_mdp_pipe_panic_vblank_signal_ctrl - * @pipe: pointer to a pipe * @enable: TRUE - enables feature FALSE - disables feature * * This function assumes that clocks are enabled, so it is callers * responsibility to enable clocks before calling this function. */ int mdss_mdp_pipe_panic_vblank_signal_ctrl(struct mdss_mdp_pipe *pipe, bool enable) { uint32_t panic_ctrl; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); if (!mdata->has_panic_ctrl) goto end; if (!is_rt_pipe(pipe)) goto end; if (!test_bit(MDSS_QOS_VBLANK_PANIC_CTRL, mdata->mdss_qos_map)) goto end; mutex_lock(&mdata->reg_lock); panic_ctrl = mdss_mdp_pipe_read(pipe, MDSS_MDP_REG_SSPP_QOS_CTRL); panic_ctrl |= VBLANK_PANIC_DEFAULT_CONFIG; if (enable) panic_ctrl |= BIT(16); else panic_ctrl &= ~BIT(16); mdss_mdp_pipe_write(pipe, MDSS_MDP_REG_SSPP_QOS_CTRL, panic_ctrl); mutex_unlock(&mdata->reg_lock); end: return 0; } int mdss_mdp_pipe_panic_signal_ctrl(struct mdss_mdp_pipe *pipe, bool enable) { uint32_t panic_robust_ctrl; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); if (!mdata->has_panic_ctrl) goto end; if (!is_rt_pipe(pipe)) goto end; mutex_lock(&mdata->reg_lock); switch (mdss_mdp_panic_signal_support_mode(mdata)) { case MDSS_MDP_PANIC_COMMON_REG_CFG: mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON); panic_robust_ctrl = readl_relaxed(mdata->mdp_base + MMSS_MDP_PANIC_ROBUST_CTRL); if (enable) panic_robust_ctrl |= BIT(pipe->panic_ctrl_ndx); else panic_robust_ctrl &= ~BIT(pipe->panic_ctrl_ndx); writel_relaxed(panic_robust_ctrl, mdata->mdp_base + MMSS_MDP_PANIC_ROBUST_CTRL); mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF); break; case MDSS_MDP_PANIC_PER_PIPE_CFG: mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON); panic_robust_ctrl = mdss_mdp_pipe_read(pipe, MDSS_MDP_REG_SSPP_QOS_CTRL); if (enable) panic_robust_ctrl |= BIT(0); else panic_robust_ctrl &= ~BIT(0); mdss_mdp_pipe_write(pipe, MDSS_MDP_REG_SSPP_QOS_CTRL, panic_robust_ctrl); mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF); break; } mutex_unlock(&mdata->reg_lock); end: return 0; } void mdss_mdp_bwcpanic_ctrl(struct mdss_data_type *mdata, bool enable) { if (!mdata) return; mutex_lock(&mdata->reg_lock); if (enable) { writel_relaxed(0x0, mdata->mdp_base + MMSS_MDP_PANIC_LUT0); writel_relaxed(0x0, mdata->mdp_base + MMSS_MDP_PANIC_LUT1); writel_relaxed(0x0, mdata->mdp_base + MMSS_MDP_ROBUST_LUT); } else { writel_relaxed(mdata->default_panic_lut0, mdata->mdp_base + MMSS_MDP_PANIC_LUT0); writel_relaxed(mdata->default_panic_lut1, mdata->mdp_base + MMSS_MDP_PANIC_LUT1); writel_relaxed(mdata->default_robust_lut, mdata->mdp_base + MMSS_MDP_ROBUST_LUT); } mutex_unlock(&mdata->reg_lock); } /** * @mdss_mdp_pipe_nrt_vbif_setup - * @mdata: pointer to global driver data. * @pipe: pointer to a pipe * * This function assumes that clocks are enabled, so it is callers * responsibility to enable clocks before calling this function. */ static void mdss_mdp_pipe_nrt_vbif_setup(struct mdss_data_type *mdata, struct mdss_mdp_pipe *pipe) { uint32_t nrt_vbif_client_sel; if (pipe->type != MDSS_MDP_PIPE_TYPE_DMA) return; mutex_lock(&mdata->reg_lock); nrt_vbif_client_sel = readl_relaxed(mdata->mdp_base + MMSS_MDP_RT_NRT_VBIF_CLIENT_SEL); if (mdss_mdp_is_nrt_vbif_client(mdata, pipe)) nrt_vbif_client_sel |= BIT(pipe->num - MDSS_MDP_SSPP_DMA0); else nrt_vbif_client_sel &= ~BIT(pipe->num - MDSS_MDP_SSPP_DMA0); writel_relaxed(nrt_vbif_client_sel, mdata->mdp_base + MMSS_MDP_RT_NRT_VBIF_CLIENT_SEL); mutex_unlock(&mdata->reg_lock); return; } static inline bool is_unused_smp_allowed(void) { struct mdss_data_type *mdata = mdss_mdp_get_mdata(); switch (MDSS_GET_MAJOR_MINOR(mdata->mdp_rev)) { case MDSS_GET_MAJOR_MINOR(MDSS_MDP_HW_REV_103): case MDSS_GET_MAJOR_MINOR(MDSS_MDP_HW_REV_105): case MDSS_GET_MAJOR_MINOR(MDSS_MDP_HW_REV_109): case MDSS_GET_MAJOR_MINOR(MDSS_MDP_HW_REV_110): return true; default: return false; } } static u32 mdss_mdp_smp_mmb_reserve(struct mdss_mdp_pipe_smp_map *smp_map, size_t n, bool force_alloc) { u32 i, mmb; u32 fixed_cnt = bitmap_weight(smp_map->fixed, SMP_MB_CNT); struct mdss_data_type *mdata = mdss_mdp_get_mdata(); if (n <= fixed_cnt) return fixed_cnt; else n -= fixed_cnt; i = bitmap_weight(smp_map->allocated, SMP_MB_CNT); /* * SMP programming is not double buffered. Fail the request, * that calls for change in smp configuration (addition/removal * of smp blocks), so that fallback solution happens. */ if (i != 0 && !force_alloc && (((n < i) && !is_unused_smp_allowed()) || (n > i))) { pr_debug("Can't change mmb config, num_blks: %zu alloc: %d\n", n, i); return 0; } /* * Clear previous SMP reservations and reserve according to the * latest configuration */ mdss_mdp_smp_mmb_free(smp_map->reserved, false); /* Reserve mmb blocks*/ for (; i < n; i++) { if (bitmap_full(mdata->mmb_alloc_map, SMP_MB_CNT)) break; mmb = find_first_zero_bit(mdata->mmb_alloc_map, SMP_MB_CNT); set_bit(mmb, smp_map->reserved); set_bit(mmb, mdata->mmb_alloc_map); } return i + fixed_cnt; } static int mdss_mdp_smp_mmb_set(int client_id, unsigned long *smp) { u32 mmb, off, data, s; int cnt = 0; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); for_each_set_bit(mmb, smp, SMP_MB_CNT) { off = (mmb / 3) * 4; s = (mmb % 3) * 8; data = readl_relaxed(mdata->mdp_base + MDSS_MDP_REG_SMP_ALLOC_W0 + off); data &= ~(0xFF << s); data |= client_id << s; writel_relaxed(data, mdata->mdp_base + MDSS_MDP_REG_SMP_ALLOC_W0 + off); writel_relaxed(data, mdata->mdp_base + MDSS_MDP_REG_SMP_ALLOC_R0 + off); cnt++; } return cnt; } static void mdss_mdp_smp_mmb_amend(unsigned long *smp, unsigned long *extra) { bitmap_or(smp, smp, extra, SMP_MB_CNT); bitmap_zero(extra, SMP_MB_CNT); } static void mdss_mdp_smp_mmb_free(unsigned long *smp, bool write) { struct mdss_data_type *mdata = mdss_mdp_get_mdata(); if (!bitmap_empty(smp, SMP_MB_CNT)) { if (write) mdss_mdp_smp_mmb_set(0, smp); bitmap_andnot(mdata->mmb_alloc_map, mdata->mmb_alloc_map, smp, SMP_MB_CNT); bitmap_zero(smp, SMP_MB_CNT); } } u32 mdss_mdp_smp_calc_num_blocks(struct mdss_mdp_pipe *pipe) { struct mdss_mdp_plane_sizes ps; int rc = 0; int i, num_blks = 0; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); if (mdata->has_pixel_ram) return 0; rc = mdss_mdp_calc_stride(pipe, &ps); if (rc) { pr_err("wrong stride calc\n"); return 0; } for (i = 0; i < ps.num_planes; i++) { num_blks += mdss_mdp_calc_per_plane_num_blks(ps.ystride[i], pipe); pr_debug("SMP for BW %d mmb for pnum=%d plane=%d\n", num_blks, pipe->num, i); } pr_debug("SMP blks %d mb_cnt for pnum=%d\n", num_blks, pipe->num); return num_blks; } /** * @mdss_mdp_smp_get_size - get allocated smp size for a pipe * @pipe: pointer to a pipe * * Function counts number of blocks that are currently allocated for a * pipe, then smp buffer size is number of blocks multiplied by block * size. */ u32 mdss_mdp_smp_get_size(struct mdss_mdp_pipe *pipe) { int i, mb_cnt = 0, smp_size; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); if (mdata->has_pixel_ram) { smp_size = mdata->pixel_ram_size; } else { for (i = 0; i < MAX_PLANES; i++) { mb_cnt += bitmap_weight(pipe->smp_map[i].allocated, SMP_MB_CNT); mb_cnt += bitmap_weight(pipe->smp_map[i].fixed, SMP_MB_CNT); } smp_size = mb_cnt * SMP_MB_SIZE; } pr_debug("SMP size %d for pnum=%d\n", smp_size, pipe->num); return smp_size; } static void mdss_mdp_smp_set_wm_levels(struct mdss_mdp_pipe *pipe, int mb_cnt) { struct mdss_data_type *mdata = mdss_mdp_get_mdata(); u32 useable_space, latency_bytes, val, wm[3]; struct mdss_mdp_mixer *mixer = pipe->mixer_left; useable_space = mb_cnt * SMP_MB_SIZE; /* * For 1.3.x version, when source format is macrotile then useable * space within total allocated SMP space is limited to src_w * * bpp * nlines. Unlike linear format, any extra space left over is * not filled. * * All other versions, in case of linear we calculate the latency * bytes as the bytes to be used for the latency buffer lines, so the * transactions when filling the full SMPs have the lowest priority. */ latency_bytes = mdss_mdp_calc_latency_buf_bytes(pipe->src_fmt->is_yuv, pipe->bwc_mode, mdss_mdp_is_tile_format(pipe->src_fmt), pipe->src.w, pipe->src_fmt->bpp, false, useable_space, mdss_mdp_is_ubwc_format(pipe->src_fmt), mdss_mdp_is_nv12_format(pipe->src_fmt), (pipe->flags & MDP_FLIP_LR)); if ((pipe->flags & MDP_FLIP_LR) && !mdss_mdp_is_tile_format(pipe->src_fmt)) { /* * when doing hflip, one line is reserved to be consumed down * the pipeline. This line will always be marked as full even * if it doesn't have any data. In order to generate proper * priority levels ignore this region while setting up * watermark levels */ u8 bpp = pipe->src_fmt->is_yuv ? 1 : pipe->src_fmt->bpp; latency_bytes -= (pipe->src.w * bpp); } if (IS_MDSS_MAJOR_MINOR_SAME(mdata->mdp_rev, MDSS_MDP_HW_REV_103) && mdss_mdp_is_tile_format(pipe->src_fmt)) { val = latency_bytes / SMP_MB_ENTRY_SIZE; wm[0] = (val * 5) / 8; wm[1] = (val * 6) / 8; wm[2] = (val * 7) / 8; } else if (mixer->rotator_mode || (mixer->ctl->intf_num == MDSS_MDP_NO_INTF)) { /* any non real time pipe */ wm[0] = 0xffff; wm[1] = 0xffff; wm[2] = 0xffff; } else { /* * 1/3 of the latency buffer bytes from the * SMP pool that is being fetched */ val = (latency_bytes / SMP_MB_ENTRY_SIZE) / 3; wm[0] = val; wm[1] = wm[0] + val; wm[2] = wm[1] + val; } trace_mdp_perf_set_wm_levels(pipe->num, useable_space, latency_bytes, wm[0], wm[1], wm[2], mb_cnt, SMP_MB_SIZE); pr_debug("pnum=%d useable_space=%u watermarks %u,%u,%u\n", pipe->num, useable_space, wm[0], wm[1], wm[2]); mdss_mdp_pipe_write(pipe, MDSS_MDP_REG_SSPP_REQPRIO_FIFO_WM_0, wm[0]); mdss_mdp_pipe_write(pipe, MDSS_MDP_REG_SSPP_REQPRIO_FIFO_WM_1, wm[1]); mdss_mdp_pipe_write(pipe, MDSS_MDP_REG_SSPP_REQPRIO_FIFO_WM_2, wm[2]); } static void mdss_mdp_smp_free(struct mdss_mdp_pipe *pipe) { int i; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); if (mdata->has_pixel_ram) return; mutex_lock(&mdss_mdp_smp_lock); for (i = 0; i < MAX_PLANES; i++) { mdss_mdp_smp_mmb_free(pipe->smp_map[i].reserved, false); mdss_mdp_smp_mmb_free(pipe->smp_map[i].allocated, true); } mutex_unlock(&mdss_mdp_smp_lock); } void mdss_mdp_smp_unreserve(struct mdss_mdp_pipe *pipe) { int i; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); if (mdata->has_pixel_ram) return; mutex_lock(&mdss_mdp_smp_lock); for (i = 0; i < MAX_PLANES; i++) mdss_mdp_smp_mmb_free(pipe->smp_map[i].reserved, false); mutex_unlock(&mdss_mdp_smp_lock); } static int mdss_mdp_calc_stride(struct mdss_mdp_pipe *pipe, struct mdss_mdp_plane_sizes *ps) { struct mdss_data_type *mdata = mdss_mdp_get_mdata(); u16 width; int rc = 0; u32 format, seg_w = 0; if (mdata->has_pixel_ram) return 0; width = DECIMATED_DIMENSION(pipe->src.w, pipe->horz_deci); if (pipe->bwc_mode) { rc = mdss_mdp_get_rau_strides(pipe->src.w, pipe->src.h, pipe->src_fmt, ps); if (rc) return rc; /* * Override fetch strides with SMP buffer size for both the * planes. BWC line buffer needs to be divided into 16 * segments and every segment is aligned to format * specific RAU size */ seg_w = DIV_ROUND_UP(pipe->src.w, 16); if (pipe->src_fmt->fetch_planes == MDSS_MDP_PLANE_INTERLEAVED) { ps->ystride[0] = ALIGN(seg_w, 32) * 16 * ps->rau_h[0] * pipe->src_fmt->bpp; ps->ystride[1] = 0; } else { u32 bwc_width = ALIGN(seg_w, 64) * 16; ps->ystride[0] = bwc_width * ps->rau_h[0]; ps->ystride[1] = bwc_width * ps->rau_h[1]; /* * Since chroma for H1V2 is not subsampled it needs * to be accounted for with bpp factor */ if (pipe->src_fmt->chroma_sample == MDSS_MDP_CHROMA_H1V2) ps->ystride[1] *= 2; } pr_debug("BWC SMP strides ystride0=%x ystride1=%x\n", ps->ystride[0], ps->ystride[1]); } else { format = pipe->src_fmt->format; /* * when decimation block is present, all chroma planes * are fetched on a single SMP plane for chroma pixels */ if (mdata->has_decimation) { switch (pipe->src_fmt->chroma_sample) { case MDSS_MDP_CHROMA_H2V1: format = MDP_Y_CRCB_H2V1; break; case MDSS_MDP_CHROMA_420: format = MDP_Y_CBCR_H2V2; break; default: break; } } rc = mdss_mdp_get_plane_sizes(pipe->src_fmt, width, pipe->src.h, ps, 0, 0); if (rc) return rc; if (pipe->mixer_left && (ps->num_planes == 1)) { ps->ystride[0] = MAX_BPP * max(pipe->mixer_left->width, width); } else if (mdata->has_decimation) { /* * To avoid quailty loss, MDP does one less decimation * on chroma components if they are subsampled. * Account for this to have enough SMPs for latency */ switch (pipe->src_fmt->chroma_sample) { case MDSS_MDP_CHROMA_H2V1: case MDSS_MDP_CHROMA_420: ps->ystride[1] <<= 1; break; } } } return rc; } static u32 mdss_mdp_calc_per_plane_num_blks(u32 ystride, struct mdss_mdp_pipe *pipe) { struct mdss_data_type *mdata = mdss_mdp_get_mdata(); u32 num_blks = 0; u32 nlines = 0; if (pipe->mixer_left && (pipe->mixer_left->rotator_mode || (pipe->mixer_left->type == MDSS_MDP_MIXER_TYPE_WRITEBACK))) { if (mdss_mdp_is_tile_format(pipe->src_fmt)) num_blks = 4; else num_blks = 1; } else { if (mdss_mdp_is_tile_format(pipe->src_fmt)) nlines = 8; else nlines = pipe->bwc_mode ? 1 : 2; num_blks = DIV_ROUND_UP(ystride * nlines, SMP_MB_SIZE); if (mdata->mdp_rev == MDSS_MDP_HW_REV_100) num_blks = roundup_pow_of_two(num_blks); if (mdata->smp_mb_per_pipe && (num_blks > mdata->smp_mb_per_pipe) && !(pipe->flags & MDP_FLIP_LR)) num_blks = mdata->smp_mb_per_pipe; } pr_debug("pipenum:%d tile:%d bwc:%d ystride%d pipeblks:%d blks:%d\n", pipe->num, mdss_mdp_is_tile_format(pipe->src_fmt), pipe->bwc_mode, ystride, mdata->smp_mb_per_pipe, num_blks); return num_blks; } int mdss_mdp_smp_reserve(struct mdss_mdp_pipe *pipe) { struct mdss_data_type *mdata = mdss_mdp_get_mdata(); u32 num_blks = 0, reserved = 0; struct mdss_mdp_plane_sizes ps; int i, rc = 0; bool force_alloc = 0; if (mdata->has_pixel_ram) return 0; rc = mdss_mdp_calc_stride(pipe, &ps); if (rc) return rc; force_alloc = pipe->flags & MDP_SMP_FORCE_ALLOC; mutex_lock(&mdss_mdp_smp_lock); if (!is_unused_smp_allowed()) { for (i = (MAX_PLANES - 1); i >= ps.num_planes; i--) { if (bitmap_weight(pipe->smp_map[i].allocated, SMP_MB_CNT)) { pr_debug("unsed mmb for pipe%d plane%d not allowed\n", pipe->num, i); mutex_unlock(&mdss_mdp_smp_lock); return -EAGAIN; } } } for (i = 0; i < ps.num_planes; i++) { num_blks = mdss_mdp_calc_per_plane_num_blks(ps.ystride[i], pipe); pr_debug("reserving %d mmb for pnum=%d plane=%d\n", num_blks, pipe->num, i); reserved = mdss_mdp_smp_mmb_reserve(&pipe->smp_map[i], num_blks, force_alloc); if (reserved < num_blks) break; } if (reserved < num_blks) { pr_debug("insufficient MMB blocks. pnum:%d\n", pipe->num); for (; i >= 0; i--) mdss_mdp_smp_mmb_free(pipe->smp_map[i].reserved, false); rc = -ENOBUFS; } mutex_unlock(&mdss_mdp_smp_lock); return rc; } /* * mdss_mdp_smp_alloc() -- set smp mmb and and wm levels for a staged pipe * @pipe: pointer to a pipe * * Function amends reserved smp mmbs to allocated bitmap and ties respective * mmbs to their pipe fetch_ids. Based on the number of total allocated mmbs * for a staged pipe, it also sets the watermark levels (wm). * * This function will be called on every commit where pipe params might not * have changed. In such cases, we need to ensure that wm levels are not * wiped out. Also in some rare situations hw might have reset and wiped out * smp mmb programming but new smp reservation is not done. In such cases we * need to ensure that for a staged pipes, mmbs are set properly based on * allocated bitmap. */ static int mdss_mdp_smp_alloc(struct mdss_mdp_pipe *pipe) { int i; int cnt = 0; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); if (mdata->has_pixel_ram) return 0; mutex_lock(&mdss_mdp_smp_lock); for (i = 0; i < MAX_PLANES; i++) { cnt += bitmap_weight(pipe->smp_map[i].fixed, SMP_MB_CNT); if (bitmap_empty(pipe->smp_map[i].reserved, SMP_MB_CNT)) { cnt += mdss_mdp_smp_mmb_set(pipe->ftch_id + i, pipe->smp_map[i].allocated); continue; } mdss_mdp_smp_mmb_amend(pipe->smp_map[i].allocated, pipe->smp_map[i].reserved); cnt += mdss_mdp_smp_mmb_set(pipe->ftch_id + i, pipe->smp_map[i].allocated); } mdss_mdp_smp_set_wm_levels(pipe, cnt); mutex_unlock(&mdss_mdp_smp_lock); return 0; } void mdss_mdp_smp_release(struct mdss_mdp_pipe *pipe) { struct mdss_data_type *mdata = mdss_mdp_get_mdata(); if (mdata->has_pixel_ram) return; mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON); mdss_mdp_smp_free(pipe); mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF); } int mdss_mdp_smp_setup(struct mdss_data_type *mdata, u32 cnt, u32 size) { if (!mdata) return -EINVAL; mdata->smp_mb_cnt = cnt; mdata->smp_mb_size = size; return 0; } /** * mdss_mdp_smp_handoff() - Handoff SMP MMBs in use by staged pipes * @mdata: pointer to the global mdss data structure. * * Iterate through the list of all SMP MMBs and check to see if any * of them are assigned to a pipe being marked as being handed-off. * If so, update the corresponding software allocation map to reflect * this. * * This function would typically be called during MDP probe for the case * when certain pipes might be programmed in the bootloader to display * the splash screen. */ int mdss_mdp_smp_handoff(struct mdss_data_type *mdata) { int rc = 0; int i, client_id, prev_id = 0; u32 off, s, data; struct mdss_mdp_pipe *pipe = NULL; if (mdata->has_pixel_ram) return 0; /* * figure out what SMP MMBs are allocated for each of the pipes * that need to be handed off. */ for (i = 0; i < SMP_MB_CNT; i++) { off = (i / 3) * 4; s = (i % 3) * 8; data = readl_relaxed(mdata->mdp_base + MDSS_MDP_REG_SMP_ALLOC_W0 + off); client_id = (data >> s) & 0xFF; if (test_bit(i, mdata->mmb_alloc_map)) { /* * Certain pipes may have a dedicated set of * SMP MMBs statically allocated to them. In * such cases, we do not need to do anything * here. */ pr_debug("smp mmb %d already assigned to pipe %d (client_id %d)\n" , i, pipe ? pipe->num : -1, client_id); continue; } if (client_id) { if (client_id != prev_id) { pipe = mdss_mdp_pipe_search_by_client_id(mdata, client_id); prev_id = client_id; } if (!pipe) { pr_warn("Invalid client id %d for SMP MMB %d\n", client_id, i); continue; } if (!pipe->is_handed_off) { pr_warn("SMP MMB %d assigned to a pipe not marked for handoff (client id %d)\n" , i, client_id); continue; } /* * Assume that the source format only has * one plane */ pr_debug("Assigning smp mmb %d to pipe %d (client_id %d)\n" , i, pipe->num, client_id); set_bit(i, pipe->smp_map[0].allocated); set_bit(i, mdata->mmb_alloc_map); } } return rc; } void mdss_mdp_pipe_unmap(struct mdss_mdp_pipe *pipe) { if (kref_put_mutex(&pipe->kref, mdss_mdp_pipe_free, &mdss_mdp_sspp_lock)) { WARN(1, "Unexpected free pipe during unmap\n"); mutex_unlock(&mdss_mdp_sspp_lock); } } int mdss_mdp_pipe_map(struct mdss_mdp_pipe *pipe) { if (!kref_get_unless_zero(&pipe->kref)) return -EINVAL; return 0; } /** * mdss_mdp_qos_vbif_remapper_setup - Program the VBIF QoS remapper * registers based on real or non real time clients * @mdata: Pointer to the global mdss data structure. * @pipe: Pointer to source pipe struct to get xin id's. * @is_realtime: To determine if pipe's client is real or * non real time. * This function assumes that clocks are on, so it is caller responsibility to * call this function with clocks enabled. */ static void mdss_mdp_qos_vbif_remapper_setup(struct mdss_data_type *mdata, struct mdss_mdp_pipe *pipe, bool is_realtime) { u32 mask, reg_val, i, vbif_qos; bool is_nrt_vbif = mdss_mdp_is_nrt_vbif_client(mdata, pipe); if (mdata->npriority_lvl == 0) return; mutex_lock(&mdata->reg_lock); for (i = 0; i < mdata->npriority_lvl; i++) { reg_val = MDSS_VBIF_READ(mdata, MDSS_VBIF_QOS_REMAP_BASE + i*4, is_nrt_vbif); mask = 0x3 << (pipe->xin_id * 2); reg_val &= ~(mask); vbif_qos = is_realtime ? mdata->vbif_rt_qos[i] : mdata->vbif_nrt_qos[i]; reg_val |= vbif_qos << (pipe->xin_id * 2); MDSS_VBIF_WRITE(mdata, MDSS_VBIF_QOS_REMAP_BASE + i*4, reg_val, is_nrt_vbif); } mutex_unlock(&mdata->reg_lock); } /** * mdss_mdp_fixed_qos_arbiter_setup - Program the RT/NRT registers based on * real or non real time clients * @mdata: Pointer to the global mdss data structure. * @pipe: Pointer to source pipe struct to get xin id's. * @is_realtime: To determine if pipe's client is real or * non real time. * This function assumes that clocks are on, so it is caller responsibility to * call this function with clocks enabled. */ static void mdss_mdp_fixed_qos_arbiter_setup(struct mdss_data_type *mdata, struct mdss_mdp_pipe *pipe, bool is_realtime) { u32 mask, reg_val; bool is_nrt_vbif = mdss_mdp_is_nrt_vbif_client(mdata, pipe); if (!mdata->has_fixed_qos_arbiter_enabled) return; mutex_lock(&mdata->reg_lock); reg_val = MDSS_VBIF_READ(mdata, MDSS_VBIF_FIXED_SORT_EN, is_nrt_vbif); mask = 0x1 << pipe->xin_id; reg_val |= mask; /* Enable the fixed sort for the client */ MDSS_VBIF_WRITE(mdata, MDSS_VBIF_FIXED_SORT_EN, reg_val, is_nrt_vbif); reg_val = MDSS_VBIF_READ(mdata, MDSS_VBIF_FIXED_SORT_SEL0, is_nrt_vbif); mask = 0x1 << (pipe->xin_id * 2); if (is_realtime) { reg_val &= ~mask; pr_debug("Real time traffic on pipe type=%x pnum=%d\n", pipe->type, pipe->num); } else { reg_val |= mask; pr_debug("Non real time traffic on pipe type=%x pnum=%d\n", pipe->type, pipe->num); } /* Set the fixed_sort regs as per RT/NRT client */ MDSS_VBIF_WRITE(mdata, MDSS_VBIF_FIXED_SORT_SEL0, reg_val, is_nrt_vbif); mutex_unlock(&mdata->reg_lock); } static void mdss_mdp_init_pipe_params(struct mdss_mdp_pipe *pipe) { kref_init(&pipe->kref); init_waitqueue_head(&pipe->free_waitq); INIT_LIST_HEAD(&pipe->buf_queue); } static int mdss_mdp_pipe_init_config(struct mdss_mdp_pipe *pipe, struct mdss_mdp_mixer *mixer, bool pipe_share) { int rc = 0; struct mdss_data_type *mdata; if (pipe && pipe->unhalted) { rc = mdss_mdp_pipe_fetch_halt(pipe, false); if (rc) { pr_err("%d failed because pipe is in bad state\n", pipe->num); goto end; } } mdata = mixer->ctl->mdata; if (pipe) { pr_debug("type=%x pnum=%d\n", pipe->type, pipe->num); mdss_mdp_init_pipe_params(pipe); } else if (pipe_share) { /* * when there is no dedicated wfd blk, DMA pipe can be * shared as long as its attached to a writeback mixer */ pipe = mdata->dma_pipes + mixer->num; if (pipe->mixer_left->type != MDSS_MDP_MIXER_TYPE_WRITEBACK) { rc = -EINVAL; goto end; } kref_get(&pipe->kref); pr_debug("pipe sharing for pipe=%d\n", pipe->num); } end: return rc; } static struct mdss_mdp_pipe *mdss_mdp_pipe_init(struct mdss_mdp_mixer *mixer, u32 type, u32 off, struct mdss_mdp_pipe *left_blend_pipe) { struct mdss_mdp_pipe *pipe = NULL; struct mdss_data_type *mdata; struct mdss_mdp_pipe *pipe_pool = NULL; u32 npipes; bool pipe_share = false; u32 i; int rc; if (!mixer || !mixer->ctl || !mixer->ctl->mdata) return NULL; mdata = mixer->ctl->mdata; switch (type) { case MDSS_MDP_PIPE_TYPE_VIG: pipe_pool = mdata->vig_pipes; npipes = mdata->nvig_pipes; break; case MDSS_MDP_PIPE_TYPE_RGB: pipe_pool = mdata->rgb_pipes; npipes = mdata->nrgb_pipes; break; case MDSS_MDP_PIPE_TYPE_DMA: pipe_pool = mdata->dma_pipes; npipes = mdata->ndma_pipes; if ((mdata->wfd_mode == MDSS_MDP_WFD_SHARED) && (mixer->type == MDSS_MDP_MIXER_TYPE_WRITEBACK)) pipe_share = true; break; case MDSS_MDP_PIPE_TYPE_CURSOR: pipe_pool = mdata->cursor_pipes; npipes = mdata->ncursor_pipes; break; default: npipes = 0; pr_err("invalid pipe type %d\n", type); break; } /* allocate lower priority right blend pipe */ if (left_blend_pipe && (left_blend_pipe->type == type) && pipe_pool) { struct mdss_mdp_pipe *pool_head = pipe_pool + off; off += left_blend_pipe->priority - pool_head->priority + 1; if (off >= npipes) { pr_warn("priority limitation. l_pipe:%d. no low priority %d pipe type available.\n", left_blend_pipe->num, type); pipe = ERR_PTR(-EBADSLT); return pipe; } } for (i = off; i < npipes; i++) { pipe = pipe_pool + i; if (atomic_read(&pipe->kref.refcount) == 0) { pipe->mixer_left = mixer; break; } pipe = NULL; } if (pipe && type == MDSS_MDP_PIPE_TYPE_CURSOR) { mdss_mdp_init_pipe_params(pipe); pr_debug("cursor: type=%x pnum=%d\n", pipe->type, pipe->num); goto cursor_done; } rc = mdss_mdp_pipe_init_config(pipe, mixer, pipe_share); if (rc) return ERR_PTR(-EINVAL); cursor_done: if (!pipe) pr_err("no %d type pipes available\n", type); return pipe; } struct mdss_mdp_pipe *mdss_mdp_pipe_alloc_dma(struct mdss_mdp_mixer *mixer) { struct mdss_mdp_pipe *pipe = NULL; struct mdss_data_type *mdata; mutex_lock(&mdss_mdp_sspp_lock); mdata = mixer->ctl->mdata; pipe = mdss_mdp_pipe_init(mixer, MDSS_MDP_PIPE_TYPE_DMA, mixer->num, NULL); if (IS_ERR_OR_NULL(pipe)) { pr_err("DMA pipes not available for mixer=%d\n", mixer->num); pipe = NULL; } else if (pipe != &mdata->dma_pipes[mixer->num]) { pr_err("Requested DMA pnum=%d not available\n", mdata->dma_pipes[mixer->num].num); kref_put(&pipe->kref, mdss_mdp_pipe_free); pipe = NULL; } else { pipe->mixer_left = mixer; } mutex_unlock(&mdss_mdp_sspp_lock); return pipe; } struct mdss_mdp_pipe *mdss_mdp_pipe_alloc(struct mdss_mdp_mixer *mixer, u32 type, struct mdss_mdp_pipe *left_blend_pipe) { struct mdss_mdp_pipe *pipe; mutex_lock(&mdss_mdp_sspp_lock); pipe = mdss_mdp_pipe_init(mixer, type, 0, left_blend_pipe); mutex_unlock(&mdss_mdp_sspp_lock); return pipe; } struct mdss_mdp_pipe *mdss_mdp_pipe_get(struct mdss_data_type *mdata, u32 ndx) { struct mdss_mdp_pipe *pipe = NULL; if (!ndx) return ERR_PTR(-EINVAL); mutex_lock(&mdss_mdp_sspp_lock); pipe = mdss_mdp_pipe_search(mdata, ndx); if (!pipe) { pipe = ERR_PTR(-EINVAL); goto error; } if (mdss_mdp_pipe_map(pipe)) pipe = ERR_PTR(-EACCES); error: mutex_unlock(&mdss_mdp_sspp_lock); return pipe; } struct mdss_mdp_pipe *mdss_mdp_pipe_assign(struct mdss_data_type *mdata, struct mdss_mdp_mixer *mixer, u32 ndx) { struct mdss_mdp_pipe *pipe = NULL; int rc; int retry_count = 0; if (!ndx) return ERR_PTR(-EINVAL); mutex_lock(&mdss_mdp_sspp_lock); pipe = mdss_mdp_pipe_search(mdata, ndx); if (!pipe) { pr_err("pipe search failed\n"); pipe = ERR_PTR(-EINVAL); goto error; } if (atomic_read(&pipe->kref.refcount) != 0) { mutex_unlock(&mdss_mdp_sspp_lock); do { rc = wait_event_interruptible_timeout(pipe->free_waitq, !atomic_read(&pipe->kref.refcount), usecs_to_jiffies(PIPE_CLEANUP_TIMEOUT_US)); if (rc == 0 || retry_count == 5) { pr_err("pipe ndx:%d free wait failed, mfd ndx:%d rc=%d\n", pipe->ndx, pipe->mfd ? pipe->mfd->index : -1, rc); pipe = ERR_PTR(-EBUSY); goto end; } else if (rc == -ERESTARTSYS) { pr_debug("interrupt signal received\n"); retry_count++; continue; } else { break; } } while (true); mutex_lock(&mdss_mdp_sspp_lock); } pipe->mixer_left = mixer; rc = mdss_mdp_pipe_init_config(pipe, mixer, false); if (rc) pipe = ERR_PTR(rc); error: mutex_unlock(&mdss_mdp_sspp_lock); end: return pipe; } static struct mdss_mdp_pipe *mdss_mdp_pipe_search_by_client_id( struct mdss_data_type *mdata, int client_id) { u32 i; for (i = 0; i < mdata->nrgb_pipes; i++) { if (mdata->rgb_pipes[i].ftch_id == client_id) return &mdata->rgb_pipes[i]; } for (i = 0; i < mdata->nvig_pipes; i++) { if (mdata->vig_pipes[i].ftch_id == client_id) return &mdata->vig_pipes[i]; } for (i = 0; i < mdata->ndma_pipes; i++) { if (mdata->dma_pipes[i].ftch_id == client_id) return &mdata->dma_pipes[i]; } for (i = 0; i < mdata->ncursor_pipes; i++) { if (mdata->cursor_pipes[i].ftch_id == client_id) return &mdata->cursor_pipes[i]; } return NULL; } struct mdss_mdp_pipe *mdss_mdp_pipe_search(struct mdss_data_type *mdata, u32 ndx) { u32 i; for (i = 0; i < mdata->nvig_pipes; i++) { if (mdata->vig_pipes[i].ndx == ndx) return &mdata->vig_pipes[i]; } for (i = 0; i < mdata->nrgb_pipes; i++) { if (mdata->rgb_pipes[i].ndx == ndx) return &mdata->rgb_pipes[i]; } for (i = 0; i < mdata->ndma_pipes; i++) { if (mdata->dma_pipes[i].ndx == ndx) return &mdata->dma_pipes[i]; } for (i = 0; i < mdata->ncursor_pipes; i++) { if (mdata->cursor_pipes[i].ndx == ndx) return &mdata->cursor_pipes[i]; } return NULL; } /* * This API checks if pipe is stagged on mixer or not. If * any pipe is stagged on mixer than it will generate the * panic signal. * * Only pipe_free API can call this API. */ static void mdss_mdp_pipe_check_stage(struct mdss_mdp_pipe *pipe, struct mdss_mdp_mixer *mixer) { int index; u8 right_blend_index; if (pipe->mixer_stage == MDSS_MDP_STAGE_UNUSED || !mixer) return; right_blend_index = pipe->is_right_blend && !(pipe->src_split_req && mixer->is_right_mixer); index = (pipe->mixer_stage * MAX_PIPES_PER_STAGE) + right_blend_index; if (index < MAX_PIPES_PER_LM && pipe == mixer->stage_pipe[index]) { pr_err("pipe%d mixer:%d pipe->mixer_stage=%d src_split:%d right blend:%d\n", pipe->num, mixer->num, pipe->mixer_stage, pipe->src_split_req, pipe->is_right_blend); MDSS_XLOG_TOUT_HANDLER("mdp", "dbg_bus", "panic"); } } static void mdss_mdp_pipe_free(struct kref *kref) { struct mdss_mdp_pipe *pipe; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); pipe = container_of(kref, struct mdss_mdp_pipe, kref); pr_debug("ndx=%x pnum=%d\n", pipe->ndx, pipe->num); mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON); mdss_mdp_pipe_panic_vblank_signal_ctrl(pipe, false); mdss_mdp_pipe_panic_signal_ctrl(pipe, false); if (pipe->play_cnt) { mdss_mdp_pipe_fetch_halt(pipe, false); mdss_mdp_pipe_pp_clear(pipe); mdss_mdp_smp_free(pipe); } else { mdss_mdp_smp_unreserve(pipe); } if (mdss_has_quirk(mdata, MDSS_QUIRK_BWCPANIC) && pipe->bwc_mode) { unsigned long pnum_bitmap = BIT(pipe->num); bitmap_andnot(mdata->bwc_enable_map, mdata->bwc_enable_map, &pnum_bitmap, MAX_DRV_SUP_PIPES); pipe->bwc_mode = 0; if (bitmap_empty(mdata->bwc_enable_map, MAX_DRV_SUP_PIPES)) mdss_mdp_bwcpanic_ctrl(mdata, false); } mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF); pipe->flags = 0; pipe->is_right_blend = false; pipe->src_split_req = false; mdss_mdp_pipe_check_stage(pipe, pipe->mixer_left); mdss_mdp_pipe_check_stage(pipe, pipe->mixer_right); pipe->mfd = NULL; pipe->mixer_left = pipe->mixer_right = NULL; pipe->mixer_stage = MDSS_MDP_STAGE_UNUSED; memset(&pipe->scale, 0, sizeof(struct mdp_scale_data)); memset(&pipe->layer, 0, sizeof(struct mdp_input_layer)); } static bool mdss_mdp_check_pipe_in_use(struct mdss_mdp_pipe *pipe) { int i; u32 mixercfg, mixercfg_extn, stage_off_mask, stage_off_extn_mask; u32 stage = BIT(0) | BIT(1) | BIT(2); bool in_use = false; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); struct mdss_mdp_ctl *ctl; struct mdss_mdp_mixer *mixer; stage_off_mask = mdss_mdp_get_mixer_mask(pipe->num, stage); stage_off_extn_mask = mdss_mdp_get_mixer_extn_mask(pipe->num, stage); for (i = 0; i < mdata->nctl; i++) { ctl = mdata->ctl_off + i; if (!ctl || !ctl->ref_cnt) continue; mixer = ctl->mixer_left; if (mixer && mixer->rotator_mode) continue; mixercfg = mdss_mdp_get_mixercfg(mixer, false); mixercfg_extn = mdss_mdp_get_mixercfg(mixer, true); if ((mixercfg & stage_off_mask) || (mixercfg_extn & stage_off_extn_mask)) { pr_err("IN USE: mixer=%d pipe=%d mcfg:0x%x mask:0x%x mcfg_extn:0x%x mask_ext:0x%x\n", mixer->num, pipe->num, mixercfg, stage_off_mask, mixercfg_extn, stage_off_extn_mask); MDSS_XLOG_TOUT_HANDLER("mdp", "vbif", "vbif_nrt", "dbg_bus", "vbif_dbg_bus", "panic"); } mixer = ctl->mixer_right; mixercfg = mdss_mdp_get_mixercfg(mixer, false); mixercfg_extn = mdss_mdp_get_mixercfg(mixer, true); if ((mixercfg & stage_off_mask) || (mixercfg_extn & stage_off_extn_mask)) { pr_err("IN USE: mixer=%d pipe=%d mcfg:0x%x mask:0x%x mcfg_extn:0x%x mask_ext:0x%x\n", mixer->num, pipe->num, mixercfg, stage_off_mask, mixercfg_extn, stage_off_extn_mask); MDSS_XLOG_TOUT_HANDLER("mdp", "vbif", "vbif_nrt", "dbg_bus", "vbif_dbg_bus", "panic"); } } return in_use; } static int mdss_mdp_is_pipe_idle(struct mdss_mdp_pipe *pipe, bool ignore_force_on, bool is_nrt_vbif) { u32 reg_val; u32 vbif_idle_mask, forced_on_mask, clk_status_idle_mask; bool is_idle = false, is_forced_on; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); forced_on_mask = BIT(pipe->clk_ctrl.bit_off + CLK_FORCE_ON_OFFSET); reg_val = readl_relaxed(mdata->mdp_base + pipe->clk_ctrl.reg_off); is_forced_on = (reg_val & forced_on_mask) ? true : false; pr_debug("pipe#:%d clk_ctrl: 0x%x forced_on_mask: 0x%x\n", pipe->num, reg_val, forced_on_mask); /* if forced on then no need to check status */ if (!is_forced_on) { clk_status_idle_mask = BIT(pipe->clk_status.bit_off + CLK_STATUS_OFFSET); reg_val = readl_relaxed(mdata->mdp_base + pipe->clk_status.reg_off); if ((reg_val & clk_status_idle_mask) == 0) is_idle = true; pr_debug("pipe#:%d clk_status:0x%x clk_status_idle_mask:0x%x\n", pipe->num, reg_val, clk_status_idle_mask); } if (!ignore_force_on && (is_forced_on || !is_idle)) goto exit; /* * skip vbif check for cursor pipes as the same xin-id is shared * between cursor0, cursor1 and dsi */ if (pipe->type == MDSS_MDP_PIPE_TYPE_CURSOR) { if (ignore_force_on && is_forced_on) is_idle = true; goto exit; } vbif_idle_mask = BIT(pipe->xin_id + 16); reg_val = MDSS_VBIF_READ(mdata, MMSS_VBIF_XIN_HALT_CTRL1, is_nrt_vbif); if (reg_val & vbif_idle_mask) is_idle = true; pr_debug("pipe#:%d XIN_HALT_CTRL1: 0x%x, vbif_idle_mask: 0x%x\n", pipe->num, reg_val, vbif_idle_mask); exit: return is_idle; } /* * mdss_mdp_pipe_clk_force_off() - check force off mask and reset for the pipe. * @pipe: pointer to the pipe data structure which needs to be checked for clk. * * This function would be called where software reset is available for pipe * clocks. */ void mdss_mdp_pipe_clk_force_off(struct mdss_mdp_pipe *pipe) { u32 reg_val, force_off_mask; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); force_off_mask = BIT(pipe->clk_ctrl.bit_off + CLK_FORCE_OFF_OFFSET); mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON); mutex_lock(&mdata->reg_lock); reg_val = readl_relaxed(mdata->mdp_base + pipe->clk_ctrl.reg_off); if (reg_val & force_off_mask) { reg_val &= ~force_off_mask; writel_relaxed(reg_val, mdata->mdp_base + pipe->clk_ctrl.reg_off); } mutex_unlock(&mdata->reg_lock); mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF); } /** * mdss_mdp_pipe_fetch_halt() - Halt VBIF client corresponding to specified pipe * @pipe: pointer to the pipe data structure which needs to be halted. * * Check if VBIF client corresponding to specified pipe is idle or not. If not * send a halt request for the client in question and wait for it be idle. * * This function would typically be called after pipe is unstaged or before it * is initialized. On success it should be assumed that pipe is in idle state * and would not fetch any more data. This function cannot be called from * interrupt context. */ int mdss_mdp_pipe_fetch_halt(struct mdss_mdp_pipe *pipe, bool is_recovery) { bool is_idle, forced_on = false, in_use = false; int rc = 0; u32 reg_val, idle_mask, clk_val, clk_mask; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); bool sw_reset_avail = mdss_mdp_pipe_is_sw_reset_available(mdata); bool is_nrt_vbif = mdss_mdp_is_nrt_vbif_client(mdata, pipe); u32 sw_reset_off = pipe->sw_reset.reg_off; u32 clk_ctrl_off = pipe->clk_ctrl.reg_off; mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON); is_idle = mdss_mdp_is_pipe_idle(pipe, true, is_nrt_vbif); /* * avoid pipe_in_use check in recovery path as the pipes would not * have been unstaged at this point. */ if (!is_idle && !is_recovery) in_use = mdss_mdp_check_pipe_in_use(pipe); if (!is_idle && !in_use) { pr_err("%pS: pipe%d is not idle. xin_id=%d\n", __builtin_return_address(0), pipe->num, pipe->xin_id); mutex_lock(&mdata->reg_lock); idle_mask = BIT(pipe->xin_id + 16); /* * make sure client clock is not gated while halting by forcing * it ON only if it was not previously forced on */ clk_val = readl_relaxed(mdata->mdp_base + clk_ctrl_off); clk_mask = BIT(pipe->clk_ctrl.bit_off + CLK_FORCE_ON_OFFSET); if (!(clk_val & clk_mask)) { clk_val |= clk_mask; writel_relaxed(clk_val, mdata->mdp_base + clk_ctrl_off); wmb(); /* ensure write is finished before progressing */ forced_on = true; } reg_val = MDSS_VBIF_READ(mdata, MMSS_VBIF_XIN_HALT_CTRL0, is_nrt_vbif); MDSS_VBIF_WRITE(mdata, MMSS_VBIF_XIN_HALT_CTRL0, reg_val | BIT(pipe->xin_id), is_nrt_vbif); if (sw_reset_avail) { reg_val = readl_relaxed(mdata->mdp_base + sw_reset_off); writel_relaxed(reg_val | BIT(pipe->sw_reset.bit_off), mdata->mdp_base + sw_reset_off); wmb(); } mutex_unlock(&mdata->reg_lock); rc = mdss_mdp_wait_for_xin_halt(pipe->xin_id, is_nrt_vbif); mutex_lock(&mdata->reg_lock); reg_val = MDSS_VBIF_READ(mdata, MMSS_VBIF_XIN_HALT_CTRL0, is_nrt_vbif); MDSS_VBIF_WRITE(mdata, MMSS_VBIF_XIN_HALT_CTRL0, reg_val & ~BIT(pipe->xin_id), is_nrt_vbif); clk_val = readl_relaxed(mdata->mdp_base + clk_ctrl_off); if (forced_on) clk_val &= ~clk_mask; if (sw_reset_avail) { reg_val = readl_relaxed(mdata->mdp_base + sw_reset_off); writel_relaxed(reg_val & ~BIT(pipe->sw_reset.bit_off), mdata->mdp_base + sw_reset_off); wmb(); clk_val |= BIT(pipe->clk_ctrl.bit_off + CLK_FORCE_OFF_OFFSET); } writel_relaxed(clk_val, mdata->mdp_base + clk_ctrl_off); mutex_unlock(&mdata->reg_lock); } mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF); return rc; } int mdss_mdp_pipe_destroy(struct mdss_mdp_pipe *pipe) { if (!kref_put_mutex(&pipe->kref, mdss_mdp_pipe_free, &mdss_mdp_sspp_lock)) { pr_err("unable to free pipe %d while still in use\n", pipe->num); return -EBUSY; } wake_up_all(&pipe->free_waitq); mutex_unlock(&mdss_mdp_sspp_lock); return 0; } /** * mdss_mdp_pipe_handoff() - Handoff staged pipes during bootup * @pipe: pointer to the pipe to be handed-off * * Populate the software structures for the pipe based on the current * configuration of the hardware pipe by the reading the appropriate MDP * registers. * * This function would typically be called during MDP probe for the case * when certain pipes might be programmed in the bootloader to display * the splash screen. */ int mdss_mdp_pipe_handoff(struct mdss_mdp_pipe *pipe) { int rc = 0; u32 src_fmt, reg = 0, bpp = 0; /* * todo: for now, only reading pipe src and dest size details * from the registers. This is needed for appropriately * calculating perf metrics for the handed off pipes. * We may need to parse some more details at a later date. */ reg = mdss_mdp_pipe_read(pipe, MDSS_MDP_REG_SSPP_SRC_SIZE); pipe->src.h = reg >> 16; pipe->src.w = reg & 0xFFFF; reg = mdss_mdp_pipe_read(pipe, MDSS_MDP_REG_SSPP_OUT_SIZE); pipe->dst.h = reg >> 16; pipe->dst.w = reg & 0xFFFF; /* Assume that the source format is RGB */ reg = mdss_mdp_pipe_read(pipe, MDSS_MDP_REG_SSPP_SRC_FORMAT); bpp = ((reg >> 9) & 0x3) + 1; switch (bpp) { case 4: src_fmt = MDP_RGBA_8888; break; case 3: src_fmt = MDP_RGB_888; break; case 2: src_fmt = MDP_RGB_565; break; default: pr_err("Invalid bpp=%d found\n", bpp); rc = -EINVAL; goto error; } pipe->src_fmt = mdss_mdp_get_format_params(src_fmt); if (!pipe->src_fmt) { pr_err("%s: failed to retrieve format parameters\n", __func__); rc = -EINVAL; goto error; } pr_debug("Pipe settings: src.h=%d src.w=%d dst.h=%d dst.w=%d bpp=%d\n" , pipe->src.h, pipe->src.w, pipe->dst.h, pipe->dst.w, pipe->src_fmt->bpp); pipe->is_handed_off = true; pipe->play_cnt = 1; mdss_mdp_init_pipe_params(pipe); error: return rc; } void mdss_mdp_pipe_position_update(struct mdss_mdp_pipe *pipe, struct mdss_rect *src, struct mdss_rect *dst) { u32 src_size, src_xy, dst_size, dst_xy; u32 tmp_src_size, tmp_src_xy, reg_data; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); src_size = (src->h << 16) | src->w; src_xy = (src->y << 16) | src->x; dst_size = (dst->h << 16) | dst->w; /* * base layer requirements are different compared to other layers * located at different stages. If source split is enabled and base * layer is used, base layer on the right LM's x offset is relative * to right LM's co-ordinate system unlike other layers which are * relative to left LM's top-left. */ if (pipe->mixer_stage == MDSS_MDP_STAGE_BASE && mdata->has_src_split && dst->x >= left_lm_w_from_mfd(pipe->mfd)) dst->x -= left_lm_w_from_mfd(pipe->mfd); dst_xy = (dst->y << 16) | dst->x; /* * Software overfetch is used when scalar pixel extension is * not enabled */ if (pipe->overfetch_disable && !pipe->scale.enable_pxl_ext) { if (pipe->overfetch_disable & OVERFETCH_DISABLE_LEFT) src_xy &= ~0xFFFF; if (pipe->overfetch_disable & OVERFETCH_DISABLE_TOP) src_xy &= ~(0xFFFF << 16); } if (IS_MDSS_MAJOR_MINOR_SAME(mdata->mdp_rev, MDSS_MDP_HW_REV_103) && pipe->bwc_mode) { /* check source dimensions change */ tmp_src_size = mdss_mdp_pipe_read(pipe, MDSS_MDP_REG_SSPP_SRC_SIZE); tmp_src_xy = mdss_mdp_pipe_read(pipe, MDSS_MDP_REG_SSPP_SRC_XY); if (src_xy != tmp_src_xy || tmp_src_size != src_size) { reg_data = readl_relaxed(mdata->mdp_base + AHB_CLK_OFFSET); reg_data |= BIT(28); writel_relaxed(reg_data, mdata->mdp_base + AHB_CLK_OFFSET); } } mdss_mdp_pipe_write(pipe, MDSS_MDP_REG_SSPP_SRC_SIZE, src_size); mdss_mdp_pipe_write(pipe, MDSS_MDP_REG_SSPP_SRC_XY, src_xy); mdss_mdp_pipe_write(pipe, MDSS_MDP_REG_SSPP_OUT_SIZE, dst_size); mdss_mdp_pipe_write(pipe, MDSS_MDP_REG_SSPP_OUT_XY, dst_xy); MDSS_XLOG(pipe->num, src_size, src_xy, dst_size, dst_xy, pipe->bwc_mode); } static int mdss_mdp_image_setup(struct mdss_mdp_pipe *pipe, struct mdss_mdp_data *data) { u32 img_size, ystride0, ystride1; u32 width, height, decimation; int ret = 0; struct mdss_rect dst, src; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); bool rotation = false; pr_debug("ctl: %d pnum=%d wh=%dx%d src={%d,%d,%d,%d} dst={%d,%d,%d,%d}\n", pipe->mixer_left->ctl->num, pipe->num, pipe->img_width, pipe->img_height, pipe->src.x, pipe->src.y, pipe->src.w, pipe->src.h, pipe->dst.x, pipe->dst.y, pipe->dst.w, pipe->dst.h); width = pipe->img_width; height = pipe->img_height; if (pipe->flags & MDP_SOURCE_ROTATED_90) rotation = true; mdss_mdp_get_plane_sizes(pipe->src_fmt, width, height, &pipe->src_planes, pipe->bwc_mode, rotation); if (data != NULL) { ret = mdss_mdp_data_check(data, &pipe->src_planes, pipe->src_fmt); if (ret) return ret; } if ((pipe->flags & MDP_DEINTERLACE) && !(pipe->flags & MDP_SOURCE_ROTATED_90)) { int i; for (i = 0; i < pipe->src_planes.num_planes; i++) pipe->src_planes.ystride[i] *= 2; width *= 2; height /= 2; } decimation = ((1 << pipe->horz_deci) - 1) << 8; decimation |= ((1 << pipe->vert_deci) - 1); if (decimation) pr_debug("Image decimation h=%d v=%d\n", pipe->horz_deci, pipe->vert_deci); dst = pipe->dst; src = pipe->src; if (!pipe->mixer_left->ctl->is_video_mode && (pipe->mixer_left->type != MDSS_MDP_MIXER_TYPE_WRITEBACK)) { struct mdss_rect roi = pipe->mixer_left->roi; bool is_right_mixer = pipe->mixer_left->is_right_mixer; struct mdss_mdp_ctl *main_ctl; if (pipe->mixer_left->ctl->is_master) main_ctl = pipe->mixer_left->ctl; else main_ctl = mdss_mdp_get_main_ctl(pipe->mixer_left->ctl); if (!main_ctl) { pr_err("Error: couldn't find main_ctl for pipe%d\n", pipe->num); return -EINVAL; } if (pipe->src_split_req && main_ctl->mixer_right->valid_roi) { /* * pipe is staged on both mixers, expand roi to span * both mixers before cropping pipe's dimensions. */ roi.w += main_ctl->mixer_right->roi.w; } else if (mdata->has_src_split && is_right_mixer) { /* * pipe is only on right mixer but since source-split * is enabled, its dst_x is full panel coordinate * aligned where as ROI is mixer coordinate aligned. * Modify dst_x before applying ROI crop. */ dst.x -= left_lm_w_from_mfd(pipe->mfd); } mdss_mdp_crop_rect(&src, &dst, &roi); if (mdata->has_src_split && is_right_mixer) { /* * re-adjust dst_x only if both mixers are active, * meaning right mixer will be working in source * split mode. */ if (mdss_mdp_is_both_lm_valid(main_ctl)) dst.x += main_ctl->mixer_left->roi.w; } if (pipe->flags & MDP_FLIP_LR) { src.x = pipe->src.x + (pipe->src.x + pipe->src.w) - (src.x + src.w); } if (pipe->flags & MDP_FLIP_UD) { src.y = pipe->src.y + (pipe->src.y + pipe->src.h) - (src.y + src.h); } } ystride0 = (pipe->src_planes.ystride[0]) | (pipe->src_planes.ystride[1] << 16); ystride1 = (pipe->src_planes.ystride[2]) | (pipe->src_planes.ystride[3] << 16); /* * Software overfetch is used when scalar pixel extension is * not enabled */ if (pipe->overfetch_disable && !pipe->scale.enable_pxl_ext) { if (pipe->overfetch_disable & OVERFETCH_DISABLE_BOTTOM) { height = pipe->src.h; if (!(pipe->overfetch_disable & OVERFETCH_DISABLE_TOP)) height += pipe->src.y; } if (pipe->overfetch_disable & OVERFETCH_DISABLE_RIGHT) { width = pipe->src.w; if (!(pipe->overfetch_disable & OVERFETCH_DISABLE_LEFT)) width += pipe->src.x; } pr_debug("overfetch w=%d/%d h=%d/%d\n", width, pipe->img_width, height, pipe->img_height); } img_size = (height << 16) | width; /* * in solid fill, there is no src rectangle, but hardware needs to * be programmed same as dst to avoid issues in scaling blocks */ if (data == NULL) { src = (struct mdss_rect) {0, 0, dst.w, dst.h}; decimation = 0; } mdss_mdp_pipe_position_update(pipe, &src, &dst); mdss_mdp_pipe_write(pipe, MDSS_MDP_REG_SSPP_SRC_IMG_SIZE, img_size); mdss_mdp_pipe_write(pipe, MDSS_MDP_REG_SSPP_SRC_YSTRIDE0, ystride0); mdss_mdp_pipe_write(pipe, MDSS_MDP_REG_SSPP_SRC_YSTRIDE1, ystride1); mdss_mdp_pipe_write(pipe, MDSS_MDP_REG_SSPP_DECIMATION_CONFIG, decimation); return 0; } static void mdss_mdp_set_pipe_cdp(struct mdss_mdp_pipe *pipe) { struct mdss_data_type *mdata = mdss_mdp_get_mdata(); u32 cdp_settings = 0x0; bool is_rotator = (pipe->mixer_left && pipe->mixer_left->rotator_mode); /* Disable CDP for rotator pipe in v1 */ if (is_rotator && mdss_has_quirk(mdata, MDSS_QUIRK_ROTCDP)) goto exit; cdp_settings = MDSS_MDP_CDP_ENABLE; if (!mdss_mdp_is_linear_format(pipe->src_fmt)) { /* Enable Amortized for non-linear formats */ cdp_settings |= MDSS_MDP_CDP_ENABLE_UBWCMETA; cdp_settings |= MDSS_MDP_CDP_AMORTIZED; } else { /* 64-transactions for line mode otherwise we keep 32 */ if (!is_rotator) cdp_settings |= MDSS_MDP_CDP_AHEAD_64; } exit: mdss_mdp_pipe_write(pipe, MDSS_MDP_REG_SSPP_CDP_CTRL, cdp_settings); } static int mdss_mdp_format_setup(struct mdss_mdp_pipe *pipe) { struct mdss_mdp_format_params *fmt; u32 chroma_samp, unpack, src_format; u32 secure = 0; u32 opmode; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); fmt = pipe->src_fmt; if (pipe->flags & MDP_SECURE_OVERLAY_SESSION) secure = 0xF; opmode = pipe->bwc_mode; if (pipe->flags & MDP_FLIP_LR) opmode |= MDSS_MDP_OP_FLIP_LR; if (pipe->flags & MDP_FLIP_UD) opmode |= MDSS_MDP_OP_FLIP_UD; pr_debug("pnum=%d format=%d opmode=%x\n", pipe->num, fmt->format, opmode); chroma_samp = fmt->chroma_sample; if (pipe->flags & MDP_SOURCE_ROTATED_90) { if (chroma_samp == MDSS_MDP_CHROMA_H2V1) chroma_samp = MDSS_MDP_CHROMA_H1V2; else if (chroma_samp == MDSS_MDP_CHROMA_H1V2) chroma_samp = MDSS_MDP_CHROMA_H2V1; } src_format = (chroma_samp << 23) | (fmt->fetch_planes << 19) | (fmt->bits[C3_ALPHA] << 6) | (fmt->bits[C2_R_Cr] << 4) | (fmt->bits[C1_B_Cb] << 2) | (fmt->bits[C0_G_Y] << 0); if (mdss_mdp_is_tile_format(fmt)) src_format |= BIT(30); if (pipe->flags & MDP_ROT_90) src_format |= BIT(11); /* ROT90 */ if (fmt->alpha_enable && fmt->fetch_planes != MDSS_MDP_PLANE_INTERLEAVED) src_format |= BIT(8); /* SRCC3_EN */ unpack = (fmt->element[3] << 24) | (fmt->element[2] << 16) | (fmt->element[1] << 8) | (fmt->element[0] << 0); src_format |= ((fmt->unpack_count - 1) << 12) | (fmt->unpack_tight << 17) | (fmt->unpack_align_msb << 18) | ((fmt->bpp - 1) << 9); if (mdss_mdp_is_ubwc_format(fmt)) { opmode |= BIT(0); src_format |= BIT(31); } if (fmt->is_yuv && test_bit(MDSS_CAPS_YUV_CONFIG, mdata->mdss_caps_map)) src_format |= BIT(15); mdss_mdp_pipe_sspp_setup(pipe, &opmode); if (fmt->fetch_mode != MDSS_MDP_FETCH_LINEAR && mdata->highest_bank_bit) { mdss_mdp_pipe_write(pipe, MDSS_MDP_REG_SSPP_FETCH_CONFIG, MDSS_MDP_FETCH_CONFIG_RESET_VALUE | mdata->highest_bank_bit << 18); } if (pipe->scale.enable_pxl_ext) opmode |= (1 << 31); mdss_mdp_pipe_write(pipe, MDSS_MDP_REG_SSPP_SRC_FORMAT, src_format); mdss_mdp_pipe_write(pipe, MDSS_MDP_REG_SSPP_SRC_UNPACK_PATTERN, unpack); mdss_mdp_pipe_write(pipe, MDSS_MDP_REG_SSPP_SRC_OP_MODE, opmode); mdss_mdp_pipe_write(pipe, MDSS_MDP_REG_SSPP_SRC_ADDR_SW_STATUS, secure); /* clear UBWC error */ mdss_mdp_pipe_write(pipe, MDSS_MDP_REG_SSPP_UBWC_ERROR_STATUS, BIT(31)); /* configure CDP */ if (test_bit(MDSS_QOS_CDP, mdata->mdss_qos_map)) mdss_mdp_set_pipe_cdp(pipe); return 0; } int mdss_mdp_pipe_addr_setup(struct mdss_data_type *mdata, struct mdss_mdp_pipe *head, u32 *offsets, u32 *ftch_id, u32 *xin_id, u32 type, u32 num_base, u32 len, u8 priority_base) { u32 i; if (!head || !mdata) { pr_err("unable to setup pipe type=%d: invalid input\n", type); return -EINVAL; } for (i = 0; i < len; i++) { head[i].type = type; head[i].ftch_id = ftch_id[i]; head[i].xin_id = xin_id[i]; head[i].num = i + num_base; head[i].ndx = BIT(i + num_base); head[i].priority = i + priority_base; head[i].base = mdata->mdss_io.base + offsets[i]; pr_info("type:%d ftchid:%d xinid:%d num:%d ndx:0x%x prio:%d\n", head[i].type, head[i].ftch_id, head[i].xin_id, head[i].num, head[i].ndx, head[i].priority); } return 0; } static int mdss_mdp_src_addr_setup(struct mdss_mdp_pipe *pipe, struct mdss_mdp_data *src_data) { struct mdss_data_type *mdata = mdss_mdp_get_mdata(); struct mdss_mdp_data data = *src_data; int ret = 0; pr_debug("pnum=%d\n", pipe->num); ret = mdss_mdp_data_check(&data, &pipe->src_planes, pipe->src_fmt); if (ret) return ret; if (pipe->overfetch_disable && !pipe->scale.enable_pxl_ext) { u32 x = 0, y = 0; if (pipe->overfetch_disable & OVERFETCH_DISABLE_LEFT) x = pipe->src.x; if (pipe->overfetch_disable & OVERFETCH_DISABLE_TOP) y = pipe->src.y; mdss_mdp_data_calc_offset(&data, x, y, &pipe->src_planes, pipe->src_fmt); } /* planar format expects YCbCr, swap chroma planes if YCrCb */ if (mdata->mdp_rev < MDSS_MDP_HW_REV_102 && (pipe->src_fmt->fetch_planes == MDSS_MDP_PLANE_PLANAR) && (pipe->src_fmt->element[0] == C1_B_Cb)) swap(data.p[1].addr, data.p[2].addr); mdss_mdp_pipe_write(pipe, MDSS_MDP_REG_SSPP_SRC0_ADDR, data.p[0].addr); mdss_mdp_pipe_write(pipe, MDSS_MDP_REG_SSPP_SRC1_ADDR, data.p[1].addr); mdss_mdp_pipe_write(pipe, MDSS_MDP_REG_SSPP_SRC2_ADDR, data.p[2].addr); mdss_mdp_pipe_write(pipe, MDSS_MDP_REG_SSPP_SRC3_ADDR, data.p[3].addr); return 0; } static int mdss_mdp_pipe_solidfill_setup(struct mdss_mdp_pipe *pipe) { int ret; u32 secure, format, unpack, opmode = 0; pr_debug("solid fill setup on pnum=%d\n", pipe->num); ret = mdss_mdp_image_setup(pipe, NULL); if (ret) { pr_err("image setup error for pnum=%d\n", pipe->num); return ret; } format = MDSS_MDP_FMT_SOLID_FILL; secure = (pipe->flags & MDP_SECURE_OVERLAY_SESSION ? 0xF : 0x0); /* support ARGB color format only */ unpack = (C3_ALPHA << 24) | (C2_R_Cr << 16) | (C1_B_Cb << 8) | (C0_G_Y << 0); mdss_mdp_pipe_write(pipe, MDSS_MDP_REG_SSPP_SRC_FORMAT, format); mdss_mdp_pipe_write(pipe, MDSS_MDP_REG_SSPP_SRC_CONSTANT_COLOR, pipe->bg_color); mdss_mdp_pipe_write(pipe, MDSS_MDP_REG_SSPP_SRC_UNPACK_PATTERN, unpack); mdss_mdp_pipe_write(pipe, MDSS_MDP_REG_SSPP_SRC_ADDR_SW_STATUS, secure); if (pipe->scale.enable_pxl_ext) opmode |= (1 << 31); mdss_mdp_pipe_write(pipe, MDSS_MDP_REG_SSPP_SRC_OP_MODE, opmode); if (pipe->type != MDSS_MDP_PIPE_TYPE_DMA) { mdss_mdp_pipe_write(pipe, MDSS_MDP_REG_SCALE_CONFIG, 0); if (pipe->type == MDSS_MDP_PIPE_TYPE_VIG) mdss_mdp_pipe_write(pipe, MDSS_MDP_REG_VIG_OP_MODE, 0); } return 0; } static void mdss_mdp_set_ot_limit_pipe(struct mdss_mdp_pipe *pipe) { struct mdss_mdp_set_ot_params ot_params; struct mdss_mdp_ctl *ctl = pipe->mixer_left->ctl; ot_params.xin_id = pipe->xin_id; ot_params.num = pipe->num; ot_params.width = pipe->src.w; ot_params.height = pipe->src.h; ot_params.reg_off_vbif_lim_conf = MMSS_VBIF_RD_LIM_CONF; ot_params.reg_off_mdp_clk_ctrl = pipe->clk_ctrl.reg_off; ot_params.bit_off_mdp_clk_ctrl = pipe->clk_ctrl.bit_off + CLK_FORCE_ON_OFFSET; ot_params.is_rot = pipe->mixer_left->rotator_mode; ot_params.is_wb = ctl->intf_num == MDSS_MDP_NO_INTF; ot_params.is_yuv = pipe->src_fmt->is_yuv; /* rotator read uses nrt vbif */ if (mdss_mdp_is_nrt_vbif_base_defined(ctl->mdata) && pipe->mixer_left->rotator_mode) ot_params.is_vbif_nrt = true; else ot_params.is_vbif_nrt = false; mdss_mdp_set_ot_limit(&ot_params); } int mdss_mdp_pipe_queue_data(struct mdss_mdp_pipe *pipe, struct mdss_mdp_data *src_data) { int ret = 0; struct mdss_mdp_ctl *ctl; u32 params_changed; u32 opmode = 0; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); bool roi_changed = false; if (!pipe) { pr_err("pipe not setup properly for queue\n"); return -ENODEV; } if (!pipe->mixer_left || !pipe->mixer_left->ctl) { if (src_data) pr_err("pipe%d mixer not setup properly\n", pipe->num); return -ENODEV; } if (pipe->src_split_req && !mdata->has_src_split) { pr_err("src split can't be requested on mdp:0x%x\n", mdata->mdp_rev); return -EINVAL; } pr_debug("pnum=%x mixer=%d play_cnt=%u\n", pipe->num, pipe->mixer_left->num, pipe->play_cnt); mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON); ctl = pipe->mixer_left->ctl; roi_changed = pipe->mixer_left->roi_changed; /* * if pipe is staged on 2 mixers then it is possible that only * right mixer roi has changed. */ if (pipe->mixer_right) roi_changed |= pipe->mixer_right->roi_changed; /* * Reprogram the pipe when there is no dedicated wfd blk and * virtual mixer is allocated for the DMA pipe during concurrent * line and block mode operations */ params_changed = (pipe->params_changed) || ((pipe->type == MDSS_MDP_PIPE_TYPE_DMA) && (pipe->mixer_left->type == MDSS_MDP_MIXER_TYPE_WRITEBACK) && (ctl->mdata->mixer_switched)) || roi_changed; if (params_changed) { bool is_realtime = !((ctl->intf_num == MDSS_MDP_NO_INTF) || pipe->mixer_left->rotator_mode); mdss_mdp_pipe_panic_vblank_signal_ctrl(pipe, false); mdss_mdp_pipe_panic_signal_ctrl(pipe, false); mdss_mdp_qos_vbif_remapper_setup(mdata, pipe, is_realtime); mdss_mdp_fixed_qos_arbiter_setup(mdata, pipe, is_realtime); if (mdata->vbif_nrt_io.base) mdss_mdp_pipe_nrt_vbif_setup(mdata, pipe); if (pipe && mdss_mdp_pipe_is_sw_reset_available(mdata)) mdss_mdp_pipe_clk_force_off(pipe); if (pipe->scale.enable_pxl_ext) mdss_mdp_pipe_program_pixel_extn(pipe); } if ((!(pipe->flags & MDP_VPU_PIPE) && (src_data == NULL)) || (pipe->flags & MDP_SOLID_FILL)) { pipe->params_changed = 0; mdss_mdp_pipe_solidfill_setup(pipe); MDSS_XLOG(pipe->num, pipe->mixer_left->num, pipe->play_cnt, 0x111); goto update_nobuf; } MDSS_XLOG(pipe->num, pipe->mixer_left->num, pipe->play_cnt, 0x222); if (params_changed) { pipe->params_changed = 0; ret = mdss_mdp_pipe_pp_setup(pipe, &opmode); if (ret) { pr_err("pipe pp setup error for pnum=%d\n", pipe->num); goto done; } ret = mdss_mdp_image_setup(pipe, src_data); if (ret) { pr_err("image setup error for pnum=%d\n", pipe->num); goto done; } ret = mdss_mdp_format_setup(pipe); if (ret) { pr_err("format %d setup error pnum=%d\n", pipe->src_fmt->format, pipe->num); goto done; } if (pipe->type == MDSS_MDP_PIPE_TYPE_VIG) mdss_mdp_pipe_write(pipe, MDSS_MDP_REG_VIG_OP_MODE, opmode); if (test_bit(MDSS_QOS_PER_PIPE_LUT, mdata->mdss_qos_map)) mdss_mdp_pipe_qos_lut(pipe); if (mdss_mdp_panic_signal_support_mode(mdata) == MDSS_MDP_PANIC_PER_PIPE_CFG) mdss_mdp_config_pipe_panic_lut(pipe); if (pipe->type != MDSS_MDP_PIPE_TYPE_CURSOR) { mdss_mdp_pipe_panic_vblank_signal_ctrl(pipe, true); mdss_mdp_pipe_panic_signal_ctrl(pipe, true); mdss_mdp_set_ot_limit_pipe(pipe); } } if (src_data == NULL) { pr_debug("src_data=%p pipe num=%dx\n", src_data, pipe->num); goto update_nobuf; } if (pipe->type != MDSS_MDP_PIPE_TYPE_CURSOR) mdss_mdp_smp_alloc(pipe); ret = mdss_mdp_src_addr_setup(pipe, src_data); if (ret) { pr_err("addr setup error for pnum=%d\n", pipe->num); goto done; } update_nobuf: if (pipe->src_split_req) { pr_debug("src_split_enabled. pnum:%d\n", pipe->num); mdss_mdp_mixer_pipe_update(pipe, ctl->mixer_left, params_changed); mdss_mdp_mixer_pipe_update(pipe, ctl->mixer_right, params_changed); pipe->mixer_right = ctl->mixer_right; } else { mdss_mdp_mixer_pipe_update(pipe, pipe->mixer_left, params_changed); } pipe->play_cnt++; if (mdss_has_quirk(mdata, MDSS_QUIRK_BWCPANIC)) { unsigned long pnum_bitmap = BIT(pipe->num); if (pipe->bwc_mode) bitmap_or(mdata->bwc_enable_map, mdata->bwc_enable_map, &pnum_bitmap, MAX_DRV_SUP_PIPES); else bitmap_andnot(mdata->bwc_enable_map, mdata->bwc_enable_map, &pnum_bitmap, MAX_DRV_SUP_PIPES); } done: mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF); return ret; } int mdss_mdp_pipe_is_staged(struct mdss_mdp_pipe *pipe) { return (pipe == pipe->mixer_left->stage_pipe[pipe->mixer_stage]); } static inline void __mdss_mdp_pipe_program_pixel_extn_helper( struct mdss_mdp_pipe *pipe, u32 plane, u32 off) { u32 src_h = DECIMATED_DIMENSION(pipe->src.h, pipe->vert_deci); u32 mask = 0xFF; u32 lr_pe, tb_pe, tot_req_pixels; /* * CB CR plane required pxls need to be accounted * for chroma decimation. */ if (plane == 1) src_h >>= pipe->chroma_sample_v; lr_pe = ((pipe->scale.right_ftch[plane] & mask) << 24)| ((pipe->scale.right_rpt[plane] & mask) << 16)| ((pipe->scale.left_ftch[plane] & mask) << 8)| (pipe->scale.left_rpt[plane] & mask); tb_pe = ((pipe->scale.btm_ftch[plane] & mask) << 24)| ((pipe->scale.btm_rpt[plane] & mask) << 16)| ((pipe->scale.top_ftch[plane] & mask) << 8)| (pipe->scale.top_rpt[plane] & mask); writel_relaxed(lr_pe, pipe->base + MDSS_MDP_REG_SSPP_SW_PIX_EXT_C0_LR + off); writel_relaxed(tb_pe, pipe->base + MDSS_MDP_REG_SSPP_SW_PIX_EXT_C0_TB + off); mask = 0xFFFF; tot_req_pixels = (((src_h + pipe->scale.num_ext_pxls_top[plane] + pipe->scale.num_ext_pxls_btm[plane]) & mask) << 16) | ((pipe->scale.roi_w[plane] + pipe->scale.num_ext_pxls_left[plane] + pipe->scale.num_ext_pxls_right[plane]) & mask); writel_relaxed(tot_req_pixels, pipe->base + MDSS_MDP_REG_SSPP_SW_PIX_EXT_C0_REQ_PIXELS + off); MDSS_XLOG(pipe->num, plane, lr_pe, tb_pe, tot_req_pixels); pr_debug("pipe num=%d, plane=%d, LR PE=0x%x, TB PE=0x%x, req_pixels=0x0%x\n", pipe->num, plane, lr_pe, tb_pe, tot_req_pixels); } /** * mdss_mdp_pipe_program_pixel_extn - Program the source pipe's * sw pixel extension * @pipe: Source pipe struct containing pixel extn values * * Function programs the pixel extn values calculated during * scale setup. */ static int mdss_mdp_pipe_program_pixel_extn(struct mdss_mdp_pipe *pipe) { /* Y plane pixel extn */ __mdss_mdp_pipe_program_pixel_extn_helper(pipe, 0, 0); /* CB CR plane pixel extn */ __mdss_mdp_pipe_program_pixel_extn_helper(pipe, 1, 16); /* Alpha plane pixel extn */ __mdss_mdp_pipe_program_pixel_extn_helper(pipe, 3, 32); return 0; } static int __pxl_extn_helper(int residue) { int tmp = 0; if (residue == 0) { return tmp; } else if (residue > 0) { tmp = (uint32_t) residue; tmp >>= PHASE_STEP_SHIFT; return -tmp; } else { tmp = (uint32_t)(-residue); tmp >>= PHASE_STEP_SHIFT; if ((tmp << PHASE_STEP_SHIFT) != (-residue)) tmp++; return tmp; } } /** * mdss_mdp_calc_pxl_extn - Calculate source pipe's sw pixel extension * * @pipe: Source pipe struct containing pixel extn values * * Function calculates the pixel extn values during scale setup. */ void mdss_mdp_pipe_calc_pixel_extn(struct mdss_mdp_pipe *pipe) { int caf, i; uint32_t src_h; bool unity_scale_x = false, upscale_x = false; bool unity_scale_y, upscale_y; if (!(pipe->src_fmt->is_yuv)) unity_scale_x = (pipe->src.w == pipe->dst.w); if (!unity_scale_x) upscale_x = (pipe->src.w <= pipe->dst.w); pr_debug("pipe=%d, src(%d, %d, %d, %d), dest(%d, %d, %d, %d)\n", pipe->num, pipe->src.x, pipe->src.y, pipe->src.w, pipe->src.h, pipe->dst.x, pipe->dst.y, pipe->dst.w, pipe->dst.h); for (i = 0; i < MAX_PLANES; i++) { int64_t left = 0, right = 0, top = 0, bottom = 0; caf = 0; /* * phase step x,y for 0 plane should be calculated before * this */ if (pipe->src_fmt->is_yuv && (i == 1 || i == 2)) { pipe->scale.phase_step_x[i] = pipe->scale.phase_step_x[0] >> pipe->chroma_sample_h; pipe->scale.phase_step_y[i] = pipe->scale.phase_step_y[0] >> pipe->chroma_sample_v; } else if (i > 0) { pipe->scale.phase_step_x[i] = pipe->scale.phase_step_x[0]; pipe->scale.phase_step_y[i] = pipe->scale.phase_step_y[0]; } /* Pixel extension calculations for X direction */ pipe->scale.roi_w[i] = DECIMATED_DIMENSION(pipe->src.w, pipe->horz_deci); if (pipe->src_fmt->is_yuv) pipe->scale.roi_w[i] &= ~0x1; /* CAF filtering on only luma plane */ if (i == 0 && pipe->src_fmt->is_yuv) caf = 1; if (i == 1 || i == 2) pipe->scale.roi_w[i] >>= pipe->chroma_sample_h; pr_debug("roi_w[%d]=%d, caf=%d\n", i, pipe->scale.roi_w[i], caf); if (unity_scale_x) { left = 0; right = 0; } else if (!upscale_x) { left = 0; right = (pipe->dst.w - 1) * pipe->scale.phase_step_x[i]; right -= (pipe->scale.roi_w[i] - 1) * PHASE_STEP_UNIT_SCALE; right += pipe->scale.phase_step_x[i]; right = -(right); } else { left = (1 << PHASE_RESIDUAL); left -= (caf * PHASE_STEP_UNIT_SCALE); right = (1 << PHASE_RESIDUAL); right += (pipe->dst.w - 1) * pipe->scale.phase_step_x[i]; right -= ((pipe->scale.roi_w[i] - 1) * PHASE_STEP_UNIT_SCALE); right += (caf * PHASE_STEP_UNIT_SCALE); right = -(right); } pr_debug("left=%lld, right=%lld\n", left, right); pipe->scale.num_ext_pxls_left[i] = __pxl_extn_helper(left); pipe->scale.num_ext_pxls_right[i] = __pxl_extn_helper(right); /* Pixel extension calculations for Y direction */ unity_scale_y = false; upscale_y = false; src_h = DECIMATED_DIMENSION(pipe->src.h, pipe->vert_deci); /* Subsampling of chroma components is factored */ if (i == 1 || i == 2) src_h >>= pipe->chroma_sample_v; if (!(pipe->src_fmt->is_yuv)) unity_scale_y = (src_h == pipe->dst.h); if (!unity_scale_y) upscale_y = (src_h <= pipe->dst.h); if (unity_scale_y) { top = 0; bottom = 0; } else if (!upscale_y) { top = 0; bottom = (pipe->dst.h - 1) * pipe->scale.phase_step_y[i]; bottom -= (src_h - 1) * PHASE_STEP_UNIT_SCALE; bottom += pipe->scale.phase_step_y[i]; bottom = -(bottom); } else { top = (1 << PHASE_RESIDUAL); top -= (caf * PHASE_STEP_UNIT_SCALE); bottom = (1 << PHASE_RESIDUAL); bottom += (pipe->dst.h - 1) * pipe->scale.phase_step_y[i]; bottom -= (src_h - 1) * PHASE_STEP_UNIT_SCALE; bottom += (caf * PHASE_STEP_UNIT_SCALE); bottom = -(bottom); } pipe->scale.num_ext_pxls_top[i] = __pxl_extn_helper(top); pipe->scale.num_ext_pxls_btm[i] = __pxl_extn_helper(bottom); /* Single pixel rgb scale adjustment */ if ((!(pipe->src_fmt->is_yuv)) && ((pipe->src.h - pipe->dst.h) == 1)) { uint32_t residue = pipe->scale.phase_step_y[i] - PHASE_STEP_UNIT_SCALE; uint32_t result = (pipe->dst.h * residue) + residue; if (result < PHASE_STEP_UNIT_SCALE) pipe->scale.num_ext_pxls_btm[i] -= 1; } if (pipe->scale.num_ext_pxls_left[i] >= 0) pipe->scale.left_rpt[i] = pipe->scale.num_ext_pxls_left[i]; else pipe->scale.left_ftch[i] = pipe->scale.num_ext_pxls_left[i]; if (pipe->scale.num_ext_pxls_right[i] >= 0) pipe->scale.right_rpt[i] = pipe->scale.num_ext_pxls_right[i]; else pipe->scale.right_ftch[i] = pipe->scale.num_ext_pxls_right[i]; if (pipe->scale.num_ext_pxls_top[i] >= 0) pipe->scale.top_rpt[i] = pipe->scale.num_ext_pxls_top[i]; else pipe->scale.top_ftch[i] = pipe->scale.num_ext_pxls_top[i]; if (pipe->scale.num_ext_pxls_btm[i] >= 0) pipe->scale.btm_rpt[i] = pipe->scale.num_ext_pxls_btm[i]; else pipe->scale.btm_ftch[i] = pipe->scale.num_ext_pxls_btm[i]; pr_debug("plane repeat=%d, left=%d, right=%d, top=%d, btm=%d\n", i, pipe->scale.left_rpt[i], pipe->scale.right_rpt[i], pipe->scale.top_rpt[i], pipe->scale.btm_rpt[i]); pr_debug("plane overfetch=%d, left=%d, right=%d, top=%d, btm=%d\n", i, pipe->scale.left_ftch[i], pipe->scale.right_ftch[i], pipe->scale.top_ftch[i], pipe->scale.btm_ftch[i]); } pipe->scale.enable_pxl_ext = 1; }