blob: 69506d4bced5e81289c8914117afe955ceba075b [file] [log] [blame]
/* Copyright (c) 2012-2014, 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 <linux/dma-mapping.h>
#include <linux/errno.h>
#include <linux/file.h>
#include <linux/msm_ion.h>
#include <linux/iommu.h>
#include <linux/msm_kgsl.h>
#include <linux/spinlock.h>
#include <linux/types.h>
#include <media/msm_media_info.h>
#include <mach/iommu_domains.h>
#include "mdss_fb.h"
#include "mdss_mdp.h"
#include "mdss_mdp_formats.h"
#include "mdss_debug.h"
enum {
MDP_INTR_VSYNC_INTF_0,
MDP_INTR_VSYNC_INTF_1,
MDP_INTR_VSYNC_INTF_2,
MDP_INTR_VSYNC_INTF_3,
MDP_INTR_UNDERRUN_INTF_0,
MDP_INTR_UNDERRUN_INTF_1,
MDP_INTR_UNDERRUN_INTF_2,
MDP_INTR_UNDERRUN_INTF_3,
MDP_INTR_PING_PONG_0,
MDP_INTR_PING_PONG_1,
MDP_INTR_PING_PONG_2,
MDP_INTR_PING_PONG_3,
MDP_INTR_PING_PONG_0_RD_PTR,
MDP_INTR_PING_PONG_1_RD_PTR,
MDP_INTR_PING_PONG_2_RD_PTR,
MDP_INTR_PING_PONG_3_RD_PTR,
MDP_INTR_WB_0,
MDP_INTR_WB_1,
MDP_INTR_WB_2,
MDP_INTR_MAX,
};
struct intr_callback {
void (*func)(void *);
void *arg;
};
struct intr_callback mdp_intr_cb[MDP_INTR_MAX];
static DEFINE_SPINLOCK(mdss_mdp_intr_lock);
static int mdss_mdp_intr2index(u32 intr_type, u32 intf_num)
{
int index = -1;
switch (intr_type) {
case MDSS_MDP_IRQ_INTF_UNDER_RUN:
index = MDP_INTR_UNDERRUN_INTF_0 + (intf_num - MDSS_MDP_INTF0);
break;
case MDSS_MDP_IRQ_INTF_VSYNC:
index = MDP_INTR_VSYNC_INTF_0 + (intf_num - MDSS_MDP_INTF0);
break;
case MDSS_MDP_IRQ_PING_PONG_COMP:
index = MDP_INTR_PING_PONG_0 + intf_num;
break;
case MDSS_MDP_IRQ_PING_PONG_RD_PTR:
index = MDP_INTR_PING_PONG_0_RD_PTR + intf_num;
break;
case MDSS_MDP_IRQ_WB_ROT_COMP:
index = MDP_INTR_WB_0 + intf_num;
break;
case MDSS_MDP_IRQ_WB_WFD:
index = MDP_INTR_WB_2 + intf_num;
break;
}
return index;
}
int mdss_mdp_set_intr_callback(u32 intr_type, u32 intf_num,
void (*fnc_ptr)(void *), void *arg)
{
unsigned long flags;
int index;
index = mdss_mdp_intr2index(intr_type, intf_num);
if (index < 0) {
pr_warn("invalid intr type=%u intf_num=%u\n",
intr_type, intf_num);
return -EINVAL;
}
spin_lock_irqsave(&mdss_mdp_intr_lock, flags);
WARN(mdp_intr_cb[index].func && fnc_ptr,
"replacing current intr callback for ndx=%d\n", index);
mdp_intr_cb[index].func = fnc_ptr;
mdp_intr_cb[index].arg = arg;
spin_unlock_irqrestore(&mdss_mdp_intr_lock, flags);
return 0;
}
static inline void mdss_mdp_intr_done(int index)
{
void (*fnc)(void *);
void *arg;
spin_lock(&mdss_mdp_intr_lock);
fnc = mdp_intr_cb[index].func;
arg = mdp_intr_cb[index].arg;
spin_unlock(&mdss_mdp_intr_lock);
if (fnc)
fnc(arg);
}
irqreturn_t mdss_mdp_isr(int irq, void *ptr)
{
struct mdss_data_type *mdata = ptr;
u32 isr, mask, hist_isr, hist_mask;
isr = MDSS_MDP_REG_READ(MDSS_MDP_REG_INTR_STATUS);
if (isr == 0)
goto mdp_isr_done;
mask = MDSS_MDP_REG_READ(MDSS_MDP_REG_INTR_EN);
MDSS_MDP_REG_WRITE(MDSS_MDP_REG_INTR_CLEAR, isr);
pr_debug("%s: isr=%x mask=%x\n", __func__, isr, mask);
isr &= mask;
if (isr == 0)
goto mdp_isr_done;
if (isr & MDSS_MDP_INTR_INTF_0_UNDERRUN)
mdss_mdp_intr_done(MDP_INTR_UNDERRUN_INTF_0);
if (isr & MDSS_MDP_INTR_INTF_1_UNDERRUN)
mdss_mdp_intr_done(MDP_INTR_UNDERRUN_INTF_1);
if (isr & MDSS_MDP_INTR_INTF_2_UNDERRUN)
mdss_mdp_intr_done(MDP_INTR_UNDERRUN_INTF_2);
if (isr & MDSS_MDP_INTR_INTF_3_UNDERRUN)
mdss_mdp_intr_done(MDP_INTR_UNDERRUN_INTF_3);
if (isr & MDSS_MDP_INTR_PING_PONG_0_DONE)
mdss_mdp_intr_done(MDP_INTR_PING_PONG_0);
if (isr & MDSS_MDP_INTR_PING_PONG_1_DONE)
mdss_mdp_intr_done(MDP_INTR_PING_PONG_1);
if (isr & MDSS_MDP_INTR_PING_PONG_2_DONE)
mdss_mdp_intr_done(MDP_INTR_PING_PONG_2);
if (isr & MDSS_MDP_INTR_PING_PONG_3_DONE)
mdss_mdp_intr_done(MDP_INTR_PING_PONG_3);
if (isr & MDSS_MDP_INTR_PING_PONG_0_RD_PTR)
mdss_mdp_intr_done(MDP_INTR_PING_PONG_0_RD_PTR);
if (isr & MDSS_MDP_INTR_PING_PONG_1_RD_PTR)
mdss_mdp_intr_done(MDP_INTR_PING_PONG_1_RD_PTR);
if (isr & MDSS_MDP_INTR_PING_PONG_2_RD_PTR)
mdss_mdp_intr_done(MDP_INTR_PING_PONG_2_RD_PTR);
if (isr & MDSS_MDP_INTR_PING_PONG_3_RD_PTR)
mdss_mdp_intr_done(MDP_INTR_PING_PONG_3_RD_PTR);
if (isr & MDSS_MDP_INTR_INTF_0_VSYNC) {
mdss_mdp_intr_done(MDP_INTR_VSYNC_INTF_0);
mdss_misr_crc_collect(mdata, DISPLAY_MISR_EDP);
}
if (isr & MDSS_MDP_INTR_INTF_1_VSYNC) {
mdss_mdp_intr_done(MDP_INTR_VSYNC_INTF_1);
mdss_misr_crc_collect(mdata, DISPLAY_MISR_DSI0);
}
if (isr & MDSS_MDP_INTR_INTF_2_VSYNC) {
mdss_mdp_intr_done(MDP_INTR_VSYNC_INTF_2);
mdss_misr_crc_collect(mdata, DISPLAY_MISR_DSI1);
}
if (isr & MDSS_MDP_INTR_INTF_3_VSYNC) {
mdss_mdp_intr_done(MDP_INTR_VSYNC_INTF_3);
mdss_misr_crc_collect(mdata, DISPLAY_MISR_HDMI);
}
if (isr & MDSS_MDP_INTR_WB_0_DONE) {
mdss_mdp_intr_done(MDP_INTR_WB_0);
mdss_misr_crc_collect(mdata, DISPLAY_MISR_MDP);
}
if (isr & MDSS_MDP_INTR_WB_1_DONE) {
mdss_mdp_intr_done(MDP_INTR_WB_1);
mdss_misr_crc_collect(mdata, DISPLAY_MISR_MDP);
}
if (isr & MDSS_MDP_INTR_WB_2_DONE) {
mdss_mdp_intr_done(MDP_INTR_WB_2);
mdss_misr_crc_collect(mdata, DISPLAY_MISR_MDP);
}
mdp_isr_done:
hist_isr = MDSS_MDP_REG_READ(MDSS_MDP_REG_HIST_INTR_STATUS);
if (hist_isr == 0)
goto hist_isr_done;
hist_mask = MDSS_MDP_REG_READ(MDSS_MDP_REG_HIST_INTR_EN);
MDSS_MDP_REG_WRITE(MDSS_MDP_REG_HIST_INTR_CLEAR, hist_isr);
hist_isr &= hist_mask;
if (hist_isr == 0)
goto hist_isr_done;
mdss_mdp_hist_intr_done(hist_isr);
hist_isr_done:
return IRQ_HANDLED;
}
struct mdss_mdp_format_params *mdss_mdp_get_format_params(u32 format)
{
if (format < MDP_IMGTYPE_LIMIT) {
struct mdss_mdp_format_params *fmt = NULL;
int i;
for (i = 0; i < ARRAY_SIZE(mdss_mdp_format_map); i++) {
fmt = &mdss_mdp_format_map[i];
if (format == fmt->format)
return fmt;
}
}
return NULL;
}
void mdss_mdp_intersect_rect(struct mdss_mdp_img_rect *res_rect,
const struct mdss_mdp_img_rect *dst_rect,
const struct mdss_mdp_img_rect *sci_rect)
{
int l = max(dst_rect->x, sci_rect->x);
int t = max(dst_rect->y, sci_rect->y);
int r = min((dst_rect->x + dst_rect->w), (sci_rect->x + sci_rect->w));
int b = min((dst_rect->y + dst_rect->h), (sci_rect->y + sci_rect->h));
if (r < l || b < t)
*res_rect = (struct mdss_mdp_img_rect){0, 0, 0, 0};
else
*res_rect = (struct mdss_mdp_img_rect){l, t, (r-l), (b-t)};
}
void mdss_mdp_crop_rect(struct mdss_mdp_img_rect *src_rect,
struct mdss_mdp_img_rect *dst_rect,
const struct mdss_mdp_img_rect *sci_rect)
{
struct mdss_mdp_img_rect res;
mdss_mdp_intersect_rect(&res, dst_rect, sci_rect);
if (res.w && res.h) {
if ((res.w != dst_rect->w) || (res.h != dst_rect->h)) {
src_rect->x = src_rect->x + (res.x - dst_rect->x);
src_rect->y = src_rect->y + (res.y - dst_rect->y);
src_rect->w = res.w;
src_rect->h = res.h;
}
*dst_rect = (struct mdss_mdp_img_rect)
{(res.x - sci_rect->x), (res.y - sci_rect->y),
res.w, res.h};
}
}
int mdss_mdp_get_rau_strides(u32 w, u32 h,
struct mdss_mdp_format_params *fmt,
struct mdss_mdp_plane_sizes *ps)
{
if (fmt->is_yuv) {
ps->rau_cnt = DIV_ROUND_UP(w, 64);
ps->ystride[0] = 64 * 4;
ps->rau_h[0] = 4;
ps->rau_h[1] = 2;
if (fmt->chroma_sample == MDSS_MDP_CHROMA_H1V2)
ps->ystride[1] = 64 * 2;
else if (fmt->chroma_sample == MDSS_MDP_CHROMA_H2V1) {
ps->ystride[1] = 32 * 4;
ps->rau_h[1] = 4;
} else
ps->ystride[1] = 32 * 2;
/* account for both chroma components */
ps->ystride[1] <<= 1;
} else if (fmt->fetch_planes == MDSS_MDP_PLANE_INTERLEAVED) {
ps->rau_cnt = DIV_ROUND_UP(w, 32);
ps->ystride[0] = 32 * 4 * fmt->bpp;
ps->ystride[1] = 0;
ps->rau_h[0] = 4;
ps->rau_h[1] = 0;
} else {
pr_err("Invalid format=%d\n", fmt->format);
return -EINVAL;
}
ps->ystride[0] *= ps->rau_cnt;
ps->ystride[1] *= ps->rau_cnt;
ps->num_planes = 2;
pr_debug("BWC rau_cnt=%d strides={%d,%d} heights={%d,%d}\n",
ps->rau_cnt, ps->ystride[0], ps->ystride[1],
ps->rau_h[0], ps->rau_h[1]);
return 0;
}
int mdss_mdp_get_plane_sizes(u32 format, u32 w, u32 h,
struct mdss_mdp_plane_sizes *ps, u32 bwc_mode)
{
struct mdss_mdp_format_params *fmt;
int i, rc;
u32 bpp;
if (ps == NULL)
return -EINVAL;
if ((w > MAX_IMG_WIDTH) || (h > MAX_IMG_HEIGHT))
return -ERANGE;
fmt = mdss_mdp_get_format_params(format);
if (!fmt)
return -EINVAL;
bpp = fmt->bpp;
memset(ps, 0, sizeof(struct mdss_mdp_plane_sizes));
if (bwc_mode) {
u32 height, meta_size;
rc = mdss_mdp_get_rau_strides(w, h, fmt, ps);
if (rc)
return rc;
height = DIV_ROUND_UP(h, ps->rau_h[0]);
meta_size = DIV_ROUND_UP(ps->rau_cnt, 8);
ps->ystride[1] += meta_size;
ps->ystride[0] += ps->ystride[1] + meta_size;
ps->plane_size[0] = ps->ystride[0] * height;
ps->ystride[1] = 2;
ps->plane_size[1] = 2 * ps->rau_cnt * height;
pr_debug("BWC data stride=%d size=%d meta size=%d\n",
ps->ystride[0], ps->plane_size[0], ps->plane_size[1]);
} else {
if (fmt->fetch_planes == MDSS_MDP_PLANE_INTERLEAVED) {
ps->num_planes = 1;
ps->plane_size[0] = w * h * bpp;
ps->ystride[0] = w * bpp;
} else if (format == MDP_Y_CBCR_H2V2_VENUS) {
int cf = COLOR_FMT_NV12;
ps->num_planes = 2;
ps->ystride[0] = VENUS_Y_STRIDE(cf, w);
ps->ystride[1] = VENUS_UV_STRIDE(cf, w);
ps->plane_size[0] = VENUS_Y_SCANLINES(cf, h) *
ps->ystride[0];
ps->plane_size[1] = VENUS_UV_SCANLINES(cf, h) *
ps->ystride[1];
} else {
u8 hmap[] = { 1, 2, 1, 2 };
u8 vmap[] = { 1, 1, 2, 2 };
u8 horiz, vert, stride_align, height_align;
horiz = hmap[fmt->chroma_sample];
vert = vmap[fmt->chroma_sample];
switch (format) {
case MDP_Y_CR_CB_GH2V2:
stride_align = 16;
height_align = 1;
break;
default:
stride_align = 1;
height_align = 1;
break;
}
ps->ystride[0] = ALIGN(w, stride_align);
ps->ystride[1] = ALIGN(w / horiz, stride_align);
ps->plane_size[0] = ps->ystride[0] *
ALIGN(h, height_align);
ps->plane_size[1] = ps->ystride[1] * (h / vert);
if (fmt->fetch_planes == MDSS_MDP_PLANE_PSEUDO_PLANAR) {
ps->num_planes = 2;
ps->plane_size[1] *= 2;
ps->ystride[1] *= 2;
} else { /* planar */
ps->num_planes = 3;
ps->plane_size[2] = ps->plane_size[1];
ps->ystride[2] = ps->ystride[1];
}
}
}
for (i = 0; i < ps->num_planes; i++)
ps->total_size += ps->plane_size[i];
return 0;
}
int mdss_mdp_data_check(struct mdss_mdp_data *data,
struct mdss_mdp_plane_sizes *ps)
{
struct mdss_mdp_img_data *prev, *curr;
int i;
if (!ps)
return 0;
if (!data || data->num_planes == 0)
return -ENOMEM;
pr_debug("srcp0=%x len=%u frame_size=%u\n", data->p[0].addr,
data->p[0].len, ps->total_size);
for (i = 0; i < ps->num_planes; i++) {
curr = &data->p[i];
if (i >= data->num_planes) {
u32 psize = ps->plane_size[i-1];
prev = &data->p[i-1];
if (prev->len > psize) {
curr->len = prev->len - psize;
prev->len = psize;
}
curr->addr = prev->addr + psize;
}
if (curr->len < ps->plane_size[i]) {
pr_err("insufficient mem=%u p=%d len=%u\n",
curr->len, i, ps->plane_size[i]);
return -ENOMEM;
}
pr_debug("plane[%d] addr=%x len=%u\n", i,
curr->addr, curr->len);
}
data->num_planes = ps->num_planes;
return 0;
}
void mdss_mdp_data_calc_offset(struct mdss_mdp_data *data, u16 x, u16 y,
struct mdss_mdp_plane_sizes *ps, struct mdss_mdp_format_params *fmt)
{
if ((x == 0) && (y == 0))
return;
data->p[0].addr += y * ps->ystride[0];
if (data->num_planes == 1) {
data->p[0].addr += x * fmt->bpp;
} else {
u8 hmap[] = { 1, 2, 1, 2 };
u8 vmap[] = { 1, 1, 2, 2 };
u16 xoff = x / hmap[fmt->chroma_sample];
u16 yoff = y / vmap[fmt->chroma_sample];
data->p[0].addr += x;
data->p[1].addr += xoff + (yoff * ps->ystride[1]);
if (data->num_planes == 2) /* pseudo planar */
data->p[1].addr += xoff;
else /* planar */
data->p[2].addr += xoff + (yoff * ps->ystride[2]);
}
}
int mdss_mdp_put_img(struct mdss_mdp_img_data *data)
{
struct ion_client *iclient = mdss_get_ionclient();
if (data->flags & MDP_MEMORY_ID_TYPE_FB) {
pr_debug("fb mem buf=0x%x\n", data->addr);
fput_light(data->srcp_file, data->p_need);
data->srcp_file = NULL;
} else if (data->srcp_file) {
pr_debug("pmem buf=0x%x\n", data->addr);
data->srcp_file = NULL;
} else if (!IS_ERR_OR_NULL(data->srcp_ihdl)) {
pr_debug("ion hdl=%p buf=0x%x\n", data->srcp_ihdl, data->addr);
if (is_mdss_iommu_attached()) {
int domain;
if (data->flags & MDP_SECURE_OVERLAY_SESSION)
domain = MDSS_IOMMU_DOMAIN_SECURE;
else
domain = MDSS_IOMMU_DOMAIN_UNSECURE;
ion_unmap_iommu(iclient, data->srcp_ihdl,
mdss_get_iommu_domain(domain), 0);
if (domain == MDSS_IOMMU_DOMAIN_SECURE) {
msm_ion_unsecure_buffer(iclient,
data->srcp_ihdl);
}
}
ion_free(iclient, data->srcp_ihdl);
data->srcp_ihdl = NULL;
} else {
return -ENOMEM;
}
return 0;
}
int mdss_mdp_get_img(struct msmfb_data *img, struct mdss_mdp_img_data *data)
{
struct file *file;
int ret = -EINVAL;
int fb_num;
unsigned long *start, *len;
struct ion_client *iclient = mdss_get_ionclient();
start = (unsigned long *) &data->addr;
len = (unsigned long *) &data->len;
data->flags |= img->flags;
data->p_need = 0;
if (img->flags & MDP_BLIT_SRC_GEM) {
data->srcp_file = NULL;
ret = kgsl_gem_obj_addr(img->memory_id, (int) img->priv,
start, len);
} else if (img->flags & MDP_MEMORY_ID_TYPE_FB) {
file = fget_light(img->memory_id, &data->p_need);
if (file == NULL) {
pr_err("invalid framebuffer file (%d)\n",
img->memory_id);
return -EINVAL;
}
data->srcp_file = file;
if (MAJOR(file->f_dentry->d_inode->i_rdev) == FB_MAJOR) {
fb_num = MINOR(file->f_dentry->d_inode->i_rdev);
ret = mdss_fb_get_phys_info(start, len, fb_num);
if (ret)
pr_err("mdss_fb_get_phys_info() failed\n");
} else {
pr_err("invalid FB_MAJOR\n");
ret = -1;
}
} else if (iclient) {
data->srcp_ihdl = ion_import_dma_buf(iclient, img->memory_id);
if (IS_ERR_OR_NULL(data->srcp_ihdl)) {
pr_err("error on ion_import_fd\n");
ret = PTR_ERR(data->srcp_ihdl);
data->srcp_ihdl = NULL;
return ret;
}
if (is_mdss_iommu_attached()) {
int domain;
if (data->flags & MDP_SECURE_OVERLAY_SESSION) {
domain = MDSS_IOMMU_DOMAIN_SECURE;
ret = msm_ion_secure_buffer(iclient,
data->srcp_ihdl, 0x2, 0);
if (IS_ERR_VALUE(ret)) {
ion_free(iclient, data->srcp_ihdl);
pr_err("failed to secure handle (%d)\n",
ret);
return ret;
}
} else {
domain = MDSS_IOMMU_DOMAIN_UNSECURE;
}
ret = ion_map_iommu(iclient, data->srcp_ihdl,
mdss_get_iommu_domain(domain),
0, SZ_4K, 0, start, len, 0, 0);
if (ret && (domain == MDSS_IOMMU_DOMAIN_SECURE))
msm_ion_unsecure_buffer(iclient,
data->srcp_ihdl);
} else {
ret = ion_phys(iclient, data->srcp_ihdl, start,
(size_t *) len);
}
if (IS_ERR_VALUE(ret)) {
ion_free(iclient, data->srcp_ihdl);
pr_err("failed to map ion handle (%d)\n", ret);
return ret;
}
}
if (!*start) {
pr_err("start address is zero!\n");
mdss_mdp_put_img(data);
return -ENOMEM;
}
if (!ret && (img->offset < data->len)) {
data->addr += img->offset;
data->len -= img->offset;
pr_debug("mem=%d ihdl=%p buf=0x%x len=0x%x\n", img->memory_id,
data->srcp_ihdl, data->addr, data->len);
} else {
mdss_mdp_put_img(data);
return ret ? : -EOVERFLOW;
}
return ret;
}
int mdss_mdp_calc_phase_step(u32 src, u32 dst, u32 *out_phase)
{
u32 unit, residue, result;
if (src == 0 || dst == 0)
return -EINVAL;
unit = 1 << PHASE_STEP_SHIFT;
*out_phase = mult_frac(unit, src, dst);
/* check if overflow is possible */
if (src > dst) {
residue = *out_phase - unit;
result = (residue * dst) + residue;
while (result > (unit + (unit >> 1)))
result -= unit;
if ((result > residue) && (result < unit))
return -EOVERFLOW;
}
return 0;
}