blob: 9c62ea2d0dbfe5e57b6bf2d15b7352bd8b7ef2c4 [file] [log] [blame]
/* Copyright (c) 2012, 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/kernel.h>
#include <linux/major.h>
#include <linux/module.h>
#include <linux/uaccess.h>
#include <mach/iommu_domains.h>
#include "mdss_fb.h"
#include "mdss_mdp.h"
#include "mdss_mdp_rotator.h"
#define VSYNC_PERIOD 16
#define CHECK_BOUNDS(offset, size, max_size) \
(((size) > (max_size)) || ((offset) > ((max_size) - (size))))
static atomic_t ov_active_panels = ATOMIC_INIT(0);
static int mdss_mdp_overlay_get(struct msm_fb_data_type *mfd,
struct mdp_overlay *req)
{
struct mdss_mdp_pipe *pipe;
pipe = mdss_mdp_pipe_get_locked(req->id);
if (pipe == NULL) {
pr_err("invalid pipe ndx=%x\n", req->id);
return -ENODEV;
}
*req = pipe->req_data;
mdss_mdp_pipe_unlock(pipe);
return 0;
}
static int mdss_mdp_overlay_req_check(struct msm_fb_data_type *mfd,
struct mdp_overlay *req,
struct mdss_mdp_format_params *fmt)
{
u32 xres, yres;
xres = mfd->fbi->var.xres;
yres = mfd->fbi->var.yres;
if (req->z_order >= MDSS_MDP_MAX_STAGE) {
pr_err("zorder %d out of range\n", req->z_order);
return -ERANGE;
}
if (req->src.width > MAX_IMG_WIDTH ||
req->src.height > MAX_IMG_HEIGHT ||
req->src_rect.w == 0 || req->src_rect.h == 0 ||
CHECK_BOUNDS(req->src_rect.x, req->src_rect.w, req->src.width) ||
CHECK_BOUNDS(req->src_rect.y, req->src_rect.h, req->src.height)) {
pr_err("invalid source image img wh=%dx%d rect=%d,%d,%d,%d\n",
req->src.width, req->src.height,
req->src_rect.x, req->src_rect.y,
req->src_rect.w, req->src_rect.h);
return -EOVERFLOW;
}
if (req->dst_rect.w < MIN_DST_W || req->dst_rect.h < MIN_DST_H ||
req->dst_rect.w > MAX_DST_W || req->dst_rect.h > MAX_DST_H) {
pr_err("invalid destination resolution (%dx%d)",
req->dst_rect.w, req->dst_rect.h);
return -EOVERFLOW;
}
if (req->flags & MDSS_MDP_ROT_ONLY) {
/* dst res should match src res in rotation only mode*/
req->dst_rect.w = req->src_rect.w;
req->dst_rect.h = req->src_rect.h;
} else {
u32 dst_w, dst_h;
if ((CHECK_BOUNDS(req->dst_rect.x, req->dst_rect.w, xres) ||
CHECK_BOUNDS(req->dst_rect.y, req->dst_rect.h, yres))) {
pr_err("invalid destination rect=%d,%d,%d,%d\n",
req->dst_rect.x, req->dst_rect.y,
req->dst_rect.w, req->dst_rect.h);
return -EOVERFLOW;
}
if (req->flags & MDP_ROT_90) {
dst_h = req->dst_rect.w;
dst_w = req->dst_rect.h;
} else {
dst_w = req->dst_rect.w;
dst_h = req->dst_rect.h;
}
if ((req->src_rect.w * MAX_UPSCALE_RATIO) < dst_w) {
pr_err("too much upscaling Width %d->%d\n",
req->src_rect.w, req->dst_rect.w);
return -EINVAL;
}
if ((req->src_rect.h * MAX_UPSCALE_RATIO) < dst_h) {
pr_err("too much upscaling. Height %d->%d\n",
req->src_rect.h, req->dst_rect.h);
return -EINVAL;
}
if (req->src_rect.w > (dst_w * MAX_DOWNSCALE_RATIO)) {
pr_err("too much downscaling. Width %d->%d\n",
req->src_rect.w, req->dst_rect.w);
return -EINVAL;
}
if (req->src_rect.h > (dst_h * MAX_DOWNSCALE_RATIO)) {
pr_err("too much downscaling. Height %d->%d\n",
req->src_rect.h, req->dst_rect.h);
return -EINVAL;
}
if ((fmt->chroma_sample == MDSS_MDP_CHROMA_420 ||
fmt->chroma_sample == MDSS_MDP_CHROMA_H2V1) &&
((req->src_rect.w * (MAX_UPSCALE_RATIO / 2)) < dst_w)) {
pr_err("too much YUV upscaling Width %d->%d\n",
req->src_rect.w, req->dst_rect.w);
return -EINVAL;
}
if ((fmt->chroma_sample == MDSS_MDP_CHROMA_420 ||
fmt->chroma_sample == MDSS_MDP_CHROMA_H1V2) &&
(req->src_rect.h * (MAX_UPSCALE_RATIO / 2)) < dst_h) {
pr_err("too much YUV upscaling Height %d->%d\n",
req->src_rect.h, req->dst_rect.h);
return -EINVAL;
}
}
if (fmt->is_yuv) {
if ((req->src_rect.x & 0x1) || (req->src_rect.y & 0x1) ||
(req->src_rect.w & 0x1) || (req->src_rect.h & 0x1)) {
pr_err("invalid odd src resolution or coordinates\n");
return -EINVAL;
}
if ((req->dst_rect.w & 0x1) || (req->dst_rect.h & 0x1)) {
pr_err("invalid odd dst resolution\n");
return -EINVAL;
}
}
return 0;
}
static int mdss_mdp_overlay_rotator_setup(struct msm_fb_data_type *mfd,
struct mdp_overlay *req)
{
struct mdss_mdp_rotator_session *rot;
struct mdss_mdp_format_params *fmt;
int ret = 0;
pr_debug("rot ctl=%u req id=%x\n", mfd->ctl->num, req->id);
fmt = mdss_mdp_get_format_params(req->src.format);
if (!fmt) {
pr_err("invalid rot format %d\n", req->src.format);
return -EINVAL;
}
ret = mdss_mdp_overlay_req_check(mfd, req, fmt);
if (ret)
return ret;
if (req->id == MSMFB_NEW_REQUEST) {
rot = mdss_mdp_rotator_session_alloc();
if (!rot) {
pr_err("unable to allocate rotator session\n");
return -ENOMEM;
}
} else if (req->id & MDSS_MDP_ROT_SESSION_MASK) {
rot = mdss_mdp_rotator_session_get(req->id);
if (!rot) {
pr_err("rotator session=%x not found\n", req->id);
return -ENODEV;
}
} else {
pr_err("invalid rotator session id=%x\n", req->id);
return -EINVAL;
}
/* keep only flags of interest to rotator */
rot->flags = req->flags & (MDP_ROT_90 | MDP_FLIP_LR | MDP_FLIP_UD |
MDP_SECURE_OVERLAY_SESSION);
rot->format = fmt->format;
rot->img_width = req->src.width;
rot->img_height = req->src.height;
rot->src_rect.x = req->src_rect.x;
rot->src_rect.y = req->src_rect.y;
rot->src_rect.w = req->src_rect.w;
rot->src_rect.h = req->src_rect.h;
if (req->flags & MDP_DEINTERLACE) {
rot->flags |= MDP_DEINTERLACE;
rot->src_rect.h /= 2;
}
rot->params_changed++;
req->id = rot->session_id;
return ret;
}
static int mdss_mdp_overlay_pipe_setup(struct msm_fb_data_type *mfd,
struct mdp_overlay *req,
struct mdss_mdp_pipe **ppipe)
{
struct mdss_mdp_format_params *fmt;
struct mdss_mdp_pipe *pipe;
struct mdss_mdp_mixer *mixer = NULL;
u32 pipe_type, mixer_mux;
int ret;
if (mfd == NULL || mfd->ctl == NULL)
return -ENODEV;
if (req->flags & MDSS_MDP_RIGHT_MIXER)
mixer_mux = MDSS_MDP_MIXER_MUX_RIGHT;
else
mixer_mux = MDSS_MDP_MIXER_MUX_LEFT;
pr_debug("pipe ctl=%u req id=%x mux=%d\n", mfd->ctl->num, req->id,
mixer_mux);
if (req->flags & MDP_ROT_90) {
pr_err("unsupported inline rotation\n");
return -ENOTSUPP;
}
fmt = mdss_mdp_get_format_params(req->src.format);
if (!fmt) {
pr_err("invalid pipe format %d\n", req->src.format);
return -EINVAL;
}
ret = mdss_mdp_overlay_req_check(mfd, req, fmt);
if (ret)
return ret;
pipe = mdss_mdp_mixer_stage_pipe(mfd->ctl, mixer_mux, req->z_order);
if (pipe && pipe->ndx != req->id) {
pr_err("stage %d taken by pnum=%d\n", req->z_order, pipe->num);
return -EBUSY;
}
if (req->id == MSMFB_NEW_REQUEST) {
mixer = mdss_mdp_mixer_get(mfd->ctl, mixer_mux);
if (!mixer) {
pr_err("unable to get mixer\n");
return -ENODEV;
}
if (fmt->is_yuv || (req->flags & MDP_OV_PIPE_SHARE))
pipe_type = MDSS_MDP_PIPE_TYPE_VIG;
else
pipe_type = MDSS_MDP_PIPE_TYPE_RGB;
pipe = mdss_mdp_pipe_alloc_locked(pipe_type);
/* VIG pipes can also support RGB format */
if (!pipe && pipe_type == MDSS_MDP_PIPE_TYPE_RGB) {
pipe_type = MDSS_MDP_PIPE_TYPE_VIG;
pipe = mdss_mdp_pipe_alloc_locked(pipe_type);
}
if (pipe == NULL) {
pr_err("error allocating pipe\n");
return -ENOMEM;
}
mutex_lock(&mfd->lock);
list_add(&pipe->used_list, &mfd->pipes_used);
mutex_unlock(&mfd->lock);
pipe->mixer = mixer;
pipe->mfd = mfd;
} else {
pipe = mdss_mdp_pipe_get_locked(req->id);
if (pipe == NULL) {
pr_err("invalid pipe ndx=%x\n", req->id);
return -ENODEV;
}
}
pipe->flags = req->flags;
pipe->img_width = req->src.width & 0x3fff;
pipe->img_height = req->src.height & 0x3fff;
pipe->src.x = req->src_rect.x;
pipe->src.y = req->src_rect.y;
pipe->src.w = req->src_rect.w;
pipe->src.h = req->src_rect.h;
pipe->dst.x = req->dst_rect.x;
pipe->dst.y = req->dst_rect.y;
pipe->dst.w = req->dst_rect.w;
pipe->dst.h = req->dst_rect.h;
pipe->src_fmt = fmt;
pipe->mixer_stage = req->z_order;
pipe->is_fg = req->is_fg;
pipe->alpha = req->alpha;
pipe->transp = req->transp_mask;
pipe->overfetch_disable = fmt->is_yuv;
pipe->req_data = *req;
if (pipe->flags & MDP_OVERLAY_PP_CFG_EN) {
if (pipe->num <= MDSS_MDP_SSPP_VIG2)
memcpy(&pipe->pp_cfg, &req->overlay_pp_cfg,
sizeof(struct mdp_overlay_pp_params));
else
pr_debug("%s: RGB Pipes don't support CSC/QSEED\n",
__func__);
}
if (pipe->flags & MDP_DEINTERLACE) {
if (pipe->flags & MDP_SOURCE_ROTATED_90) {
pipe->src.w /= 2;
pipe->img_width /= 2;
} else {
pipe->src.h /= 2;
}
}
pipe->params_changed++;
req->id = pipe->ndx;
*ppipe = pipe;
mdss_mdp_pipe_unlock(pipe);
return ret;
}
static int mdss_mdp_overlay_set(struct msm_fb_data_type *mfd,
struct mdp_overlay *req)
{
int ret;
ret = mutex_lock_interruptible(&mfd->ov_lock);
if (ret)
return ret;
if (!mfd->panel_power_on) {
mutex_unlock(&mfd->ov_lock);
return -EPERM;
}
if (req->flags & MDSS_MDP_ROT_ONLY) {
ret = mdss_mdp_overlay_rotator_setup(mfd, req);
} else {
struct mdss_mdp_pipe *pipe;
/* userspace zorder start with stage 0 */
req->z_order += MDSS_MDP_STAGE_0;
ret = mdss_mdp_overlay_pipe_setup(mfd, req, &pipe);
req->z_order -= MDSS_MDP_STAGE_0;
}
mutex_unlock(&mfd->ov_lock);
return ret;
}
static inline int mdss_mdp_overlay_get_buf(struct msm_fb_data_type *mfd,
struct mdss_mdp_data *data,
struct msmfb_data *planes,
int num_planes,
u32 flags)
{
int i;
memset(data, 0, sizeof(*data));
for (i = 0; i < num_planes; i++) {
data->p[i].flags = flags;
mdss_mdp_get_img(&planes[i], &data->p[i]);
if (data->p[0].len == 0)
break;
}
if (i != num_planes) {
for (; i >= 0; i--)
mdss_mdp_put_img(&data->p[i]);
return -ENOMEM;
}
data->num_planes = num_planes;
return 0;
}
static inline int mdss_mdp_overlay_free_buf(struct mdss_mdp_data *data)
{
int i;
for (i = 0; i < data->num_planes && data->p[i].len; i++)
mdss_mdp_put_img(&data->p[i]);
data->num_planes = 0;
return 0;
}
static int mdss_mdp_overlay_cleanup(struct msm_fb_data_type *mfd)
{
struct mdss_mdp_pipe *pipe, *tmp;
LIST_HEAD(destroy_pipes);
int i;
mutex_lock(&mfd->ov_lock);
mutex_lock(&mfd->lock);
list_for_each_entry_safe(pipe, tmp, &mfd->pipes_cleanup, cleanup_list) {
list_move(&pipe->cleanup_list, &destroy_pipes);
for (i = 0; i < ARRAY_SIZE(pipe->buffers); i++)
mdss_mdp_overlay_free_buf(&pipe->buffers[i]);
}
if (!list_empty(&mfd->pipes_used)) {
struct mdss_mdp_data *data;
int buf_ndx;
list_for_each_entry(pipe, &mfd->pipes_used, used_list) {
buf_ndx = (pipe->play_cnt - 1) & 1; /* prev buffer */
data = &pipe->buffers[buf_ndx];
if (data->num_planes) {
pr_debug("free buffer ndx=%d pnum=%d\n",
buf_ndx, pipe->num);
mdss_mdp_overlay_free_buf(data);
}
}
}
mutex_unlock(&mfd->lock);
list_for_each_entry_safe(pipe, tmp, &destroy_pipes, cleanup_list)
mdss_mdp_pipe_destroy(pipe);
mutex_unlock(&mfd->ov_lock);
return 0;
}
static int mdss_mdp_overlay_kickoff(struct mdss_mdp_ctl *ctl)
{
struct msm_fb_data_type *mfd = ctl->mfd;
int ret;
if (mfd->kickoff_fnc)
ret = mfd->kickoff_fnc(ctl);
else
ret = mdss_mdp_display_commit(ctl, NULL);
if (IS_ERR_VALUE(ret))
return ret;
complete(&mfd->update.comp);
mutex_lock(&mfd->no_update.lock);
if (mfd->no_update.timer.function)
del_timer(&(mfd->no_update.timer));
mfd->no_update.timer.expires = jiffies + (2 * HZ);
add_timer(&mfd->no_update.timer);
mutex_unlock(&mfd->no_update.lock);
ret = mdss_mdp_overlay_cleanup(mfd);
return ret;
}
static int mdss_mdp_overlay_release(struct msm_fb_data_type *mfd, int ndx)
{
struct mdss_mdp_pipe *pipe;
u32 pipe_ndx, unset_ndx = 0;
int i;
for (i = 0; unset_ndx != ndx && i < MDSS_MDP_MAX_SSPP; i++) {
pipe_ndx = BIT(i);
if (pipe_ndx & ndx) {
unset_ndx |= pipe_ndx;
pipe = mdss_mdp_pipe_get_locked(pipe_ndx);
if (!pipe) {
pr_warn("unknown pipe ndx=%x\n", pipe_ndx);
continue;
}
mutex_lock(&mfd->lock);
list_del(&pipe->used_list);
list_add(&pipe->cleanup_list, &mfd->pipes_cleanup);
mutex_unlock(&mfd->lock);
mdss_mdp_mixer_pipe_unstage(pipe);
}
}
return 0;
}
static int mdss_mdp_overlay_unset(struct msm_fb_data_type *mfd, int ndx)
{
int ret = 0;
if (!mfd || !mfd->ctl)
return -ENODEV;
ret = mutex_lock_interruptible(&mfd->ov_lock);
if (ret)
return ret;
if (!mfd->panel_power_on) {
mutex_unlock(&mfd->ov_lock);
return -EPERM;
}
pr_debug("unset ndx=%x\n", ndx);
if (ndx & MDSS_MDP_ROT_SESSION_MASK)
ret = mdss_mdp_rotator_release(ndx);
else
ret = mdss_mdp_overlay_release(mfd, ndx);
mutex_unlock(&mfd->ov_lock);
return ret;
}
static int mdss_mdp_overlay_release_all(struct msm_fb_data_type *mfd)
{
struct mdss_mdp_pipe *pipe;
u32 unset_ndx = 0;
int cnt = 0;
mutex_lock(&mfd->ov_lock);
mutex_lock(&mfd->lock);
list_for_each_entry(pipe, &mfd->pipes_used, used_list) {
unset_ndx |= pipe->ndx;
cnt++;
}
mutex_unlock(&mfd->lock);
if (unset_ndx) {
pr_debug("%d pipes need cleanup (%x)\n", cnt, unset_ndx);
mdss_mdp_overlay_release(mfd, unset_ndx);
}
mutex_unlock(&mfd->ov_lock);
if (cnt)
mdss_mdp_overlay_kickoff(mfd->ctl);
return 0;
}
static int mdss_mdp_overlay_play_wait(struct msm_fb_data_type *mfd,
struct msmfb_overlay_data *req)
{
int ret;
if (!mfd || !mfd->ctl)
return -ENODEV;
ret = mdss_mdp_overlay_kickoff(mfd->ctl);
if (!ret)
pr_err("error displaying\n");
return ret;
}
static int mdss_mdp_overlay_rotate(struct msm_fb_data_type *mfd,
struct msmfb_overlay_data *req)
{
struct mdss_mdp_rotator_session *rot;
struct mdss_mdp_data src_data, dst_data;
int ret;
u32 flgs;
rot = mdss_mdp_rotator_session_get(req->id);
if (!rot) {
pr_err("invalid session id=%x\n", req->id);
return -ENOENT;
}
flgs = rot->flags & MDP_SECURE_OVERLAY_SESSION;
ret = mdss_mdp_overlay_get_buf(mfd, &src_data, &req->data, 1, flgs);
if (ret) {
pr_err("src_data pmem error\n");
goto rotate_done;
}
ret = mdss_mdp_overlay_get_buf(mfd, &dst_data, &req->dst_data, 1, flgs);
if (ret) {
pr_err("dst_data pmem error\n");
goto rotate_done;
}
ret = mdss_mdp_rotator_queue(rot, &src_data, &dst_data);
if (ret) {
pr_err("rotator queue error session id=%x\n", req->id);
goto rotate_done;
}
rotate_done:
mdss_mdp_overlay_free_buf(&dst_data);
mdss_mdp_overlay_free_buf(&src_data);
return 0;
}
static int mdss_mdp_overlay_queue(struct msm_fb_data_type *mfd,
struct msmfb_overlay_data *req)
{
struct mdss_mdp_ctl *ctl;
struct mdss_mdp_pipe *pipe;
struct mdss_mdp_data *src_data;
int ret, buf_ndx;
u32 flags;
pipe = mdss_mdp_pipe_get_locked(req->id);
if (pipe == NULL) {
pr_err("pipe ndx=%x doesn't exist\n", req->id);
return -ENODEV;
}
pr_debug("ov queue pnum=%d\n", pipe->num);
flags = (pipe->flags & MDP_SECURE_OVERLAY_SESSION);
buf_ndx = (pipe->play_cnt + 1) & 1; /* next buffer */
src_data = &pipe->buffers[buf_ndx];
mdss_mdp_overlay_free_buf(src_data);
ret = mdss_mdp_overlay_get_buf(mfd, src_data, &req->data, 1, flags);
if (IS_ERR_VALUE(ret)) {
pr_err("src_data pmem error\n");
} else {
ret = mdss_mdp_pipe_queue_data(pipe, src_data);
if (IS_ERR_VALUE(ret))
mdss_mdp_overlay_free_buf(src_data);
}
ctl = pipe->mixer->ctl;
mdss_mdp_pipe_unlock(pipe);
return ret;
}
static int mdss_mdp_overlay_play(struct msm_fb_data_type *mfd,
struct msmfb_overlay_data *req)
{
int ret = 0;
pr_debug("play req id=%x\n", req->id);
ret = mutex_lock_interruptible(&mfd->ov_lock);
if (ret)
return ret;
if (!mfd->panel_power_on) {
mutex_unlock(&mfd->ov_lock);
return -EPERM;
}
if (req->id & MDSS_MDP_ROT_SESSION_MASK) {
ret = mdss_mdp_overlay_rotate(mfd, req);
} else {
ret = mdss_mdp_overlay_queue(mfd, req);
if ((ret == 0) && (mfd->panel_info.type == WRITEBACK_PANEL)) {
mutex_unlock(&mfd->ov_lock);
ret = mdss_mdp_overlay_kickoff(mfd->ctl);
return ret;
}
}
mutex_unlock(&mfd->ov_lock);
return ret;
}
static int mdss_mdp_overlay_get_fb_pipe(struct msm_fb_data_type *mfd,
struct mdss_mdp_pipe **ppipe,
int mixer_mux)
{
struct mdss_mdp_pipe *pipe;
pipe = mdss_mdp_mixer_stage_pipe(mfd->ctl, mixer_mux,
MDSS_MDP_STAGE_BASE);
if (pipe == NULL) {
struct mdp_overlay req;
struct fb_info *fbi = mfd->fbi;
struct mdss_mdp_mixer *mixer;
int ret, bpp;
mixer = mdss_mdp_mixer_get(mfd->ctl, MDSS_MDP_MIXER_MUX_LEFT);
if (!mixer) {
pr_err("unable to retrieve mixer\n");
return -ENODEV;
}
memset(&req, 0, sizeof(req));
bpp = fbi->var.bits_per_pixel / 8;
req.id = MSMFB_NEW_REQUEST;
req.src.format = mfd->fb_imgType;
req.src.height = fbi->var.yres;
req.src.width = fbi->fix.line_length / bpp;
if (mixer_mux == MDSS_MDP_MIXER_MUX_RIGHT) {
if (req.src.width <= mixer->width) {
pr_warn("right fb pipe not needed\n");
return -EINVAL;
}
req.flags |= MDSS_MDP_RIGHT_MIXER;
req.src_rect.x = mixer->width;
req.src_rect.w = fbi->var.xres - mixer->width;
} else {
req.src_rect.x = 0;
req.src_rect.w = MIN(fbi->var.xres, mixer->width);
}
req.src_rect.y = 0;
req.src_rect.h = req.src.height;
req.dst_rect.x = 0;
req.dst_rect.y = 0;
req.dst_rect.w = req.src_rect.w;
req.dst_rect.h = req.src_rect.h;
req.z_order = MDSS_MDP_STAGE_BASE;
pr_debug("allocating base pipe mux=%d\n", mixer_mux);
ret = mdss_mdp_overlay_pipe_setup(mfd, &req, &pipe);
if (ret)
return ret;
pr_debug("ctl=%d pnum=%d\n", mfd->ctl->num, pipe->num);
}
*ppipe = pipe;
return 0;
}
static void mdss_mdp_overlay_pan_display(struct msm_fb_data_type *mfd)
{
struct mdss_mdp_data data;
struct mdss_mdp_pipe *pipe;
struct fb_info *fbi;
u32 offset;
int bpp, ret;
if (!mfd || !mfd->ctl)
return;
fbi = mfd->fbi;
if (fbi->fix.smem_len == 0) {
mdss_mdp_overlay_kickoff(mfd->ctl);
return;
}
if (mutex_lock_interruptible(&mfd->ov_lock))
return;
if (!mfd->panel_power_on) {
mutex_unlock(&mfd->ov_lock);
return;
}
memset(&data, 0, sizeof(data));
bpp = fbi->var.bits_per_pixel / 8;
offset = fbi->var.xoffset * bpp +
fbi->var.yoffset * fbi->fix.line_length;
if (offset > fbi->fix.smem_len) {
pr_err("invalid fb offset=%u total length=%u\n",
offset, fbi->fix.smem_len);
return;
}
if (is_mdss_iommu_attached())
data.p[0].addr = mfd->iova;
else
data.p[0].addr = fbi->fix.smem_start;
data.p[0].addr += offset;
data.p[0].len = fbi->fix.smem_len - offset;
data.num_planes = 1;
ret = mdss_mdp_overlay_get_fb_pipe(mfd, &pipe, MDSS_MDP_MIXER_MUX_LEFT);
if (ret) {
pr_err("unable to allocate base pipe\n");
return;
}
mdss_mdp_pipe_lock(pipe);
ret = mdss_mdp_pipe_queue_data(pipe, &data);
mdss_mdp_pipe_unlock(pipe);
if (ret) {
pr_err("unable to queue data\n");
return;
}
if (fbi->var.xres > MAX_MIXER_WIDTH) {
ret = mdss_mdp_overlay_get_fb_pipe(mfd, &pipe,
MDSS_MDP_MIXER_MUX_RIGHT);
if (ret) {
pr_err("unable to allocate right base pipe\n");
return;
}
mdss_mdp_pipe_lock(pipe);
ret = mdss_mdp_pipe_queue_data(pipe, &data);
mdss_mdp_pipe_unlock(pipe);
if (ret) {
pr_err("unable to queue right data\n");
return;
}
}
mutex_unlock(&mfd->ov_lock);
if (fbi->var.activate & FB_ACTIVATE_VBL)
mdss_mdp_overlay_kickoff(mfd->ctl);
}
/* function is called in irq context should have minimum processing */
static void mdss_mdp_overlay_handle_vsync(struct mdss_mdp_ctl *ctl, ktime_t t)
{
struct msm_fb_data_type *mfd = ctl->mfd;
if (!mfd) {
pr_warn("Invalid handle for vsync\n");
return;
}
pr_debug("vsync on fb%d play_cnt=%d\n", mfd->index, ctl->play_cnt);
spin_lock(&mfd->vsync_lock);
mfd->vsync_time = t;
complete(&mfd->vsync_comp);
spin_unlock(&mfd->vsync_lock);
}
int mdss_mdp_overlay_vsync_ctrl(struct msm_fb_data_type *mfd, int en)
{
struct mdss_mdp_ctl *ctl = mfd->ctl;
unsigned long flags;
int rc;
if (!ctl)
return -ENODEV;
if (!ctl->set_vsync_handler)
return -ENOTSUPP;
if (!ctl->power_on) {
pr_debug("fb%d vsync pending first update en=%d\n",
mfd->index, en);
mfd->vsync_pending = en;
return 0;
}
pr_debug("fb%d vsync en=%d\n", mfd->index, en);
spin_lock_irqsave(&mfd->vsync_lock, flags);
INIT_COMPLETION(mfd->vsync_comp);
if (en && ctl->play_cnt == 0) {
mfd->vsync_time = ktime_get();
complete(&mfd->vsync_comp);
}
spin_unlock_irqrestore(&mfd->vsync_lock, flags);
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON, false);
if (en)
rc = ctl->set_vsync_handler(ctl, mdss_mdp_overlay_handle_vsync);
else
rc = ctl->set_vsync_handler(ctl, NULL);
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false);
return rc;
}
static ssize_t mdss_mdp_vsync_show_event(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct fb_info *fbi = dev_get_drvdata(dev);
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)fbi->par;
unsigned long flags;
u64 vsync_ticks;
int ret;
if (!mfd->ctl || !mfd->ctl->power_on)
return 0;
ret = wait_for_completion_interruptible_timeout(&mfd->vsync_comp,
msecs_to_jiffies(VSYNC_PERIOD * 5));
if (ret <= 0) {
pr_warn("vsync wait on fb%d interrupted (%d)\n",
mfd->index, ret);
return -EBUSY;
}
spin_lock_irqsave(&mfd->vsync_lock, flags);
vsync_ticks = ktime_to_ns(mfd->vsync_time);
spin_unlock_irqrestore(&mfd->vsync_lock, flags);
pr_debug("fb%d vsync=%llu", mfd->index, vsync_ticks);
ret = snprintf(buf, PAGE_SIZE, "VSYNC=%llu", vsync_ticks);
return ret;
}
static DEVICE_ATTR(vsync_event, S_IRUGO, mdss_mdp_vsync_show_event, NULL);
static struct attribute *vsync_fs_attrs[] = {
&dev_attr_vsync_event.attr,
NULL,
};
static struct attribute_group vsync_fs_attr_group = {
.attrs = vsync_fs_attrs,
};
static int mdss_mdp_hw_cursor_update(struct msm_fb_data_type *mfd,
struct fb_cursor *cursor)
{
struct mdss_mdp_mixer *mixer;
struct fb_image *img = &cursor->image;
u32 blendcfg;
int off, ret = 0;
if (!mfd->cursor_buf && (cursor->set & FB_CUR_SETIMAGE)) {
mfd->cursor_buf = dma_alloc_coherent(NULL, MDSS_MDP_CURSOR_SIZE,
(dma_addr_t *) &mfd->cursor_buf_phys,
GFP_KERNEL);
if (!mfd->cursor_buf) {
pr_err("can't allocate cursor buffer\n");
return -ENOMEM;
}
ret = msm_iommu_map_contig_buffer(mfd->cursor_buf_phys,
mdss_get_iommu_domain(MDSS_IOMMU_DOMAIN_UNSECURE),
0, MDSS_MDP_CURSOR_SIZE, SZ_4K, 0,
&(mfd->cursor_buf_iova));
if (IS_ERR_VALUE(ret)) {
dma_free_coherent(NULL, MDSS_MDP_CURSOR_SIZE,
mfd->cursor_buf,
(dma_addr_t) mfd->cursor_buf_phys);
pr_err("unable to map cursor buffer to iommu(%d)\n",
ret);
return -ENOMEM;
}
}
mixer = mdss_mdp_mixer_get(mfd->ctl, MDSS_MDP_MIXER_MUX_DEFAULT);
off = MDSS_MDP_REG_LM_OFFSET(mixer->num);
if ((img->width > MDSS_MDP_CURSOR_WIDTH) ||
(img->height > MDSS_MDP_CURSOR_HEIGHT) ||
(img->depth != 32))
return -EINVAL;
pr_debug("mixer=%d enable=%x set=%x\n", mixer->num, cursor->enable,
cursor->set);
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON, false);
blendcfg = MDSS_MDP_REG_READ(off + MDSS_MDP_REG_LM_CURSOR_BLEND_CONFIG);
if (cursor->set & FB_CUR_SETPOS)
MDSS_MDP_REG_WRITE(off + MDSS_MDP_REG_LM_CURSOR_START_XY,
(img->dy << 16) | img->dx);
if (cursor->set & FB_CUR_SETIMAGE) {
int calpha_en, transp_en, alpha, size, cursor_addr;
ret = copy_from_user(mfd->cursor_buf, img->data,
img->width * img->height * 4);
if (ret)
return ret;
if (is_mdss_iommu_attached())
cursor_addr = mfd->cursor_buf_iova;
else
cursor_addr = mfd->cursor_buf_phys;
if (img->bg_color == 0xffffffff)
transp_en = 0;
else
transp_en = 1;
alpha = (img->fg_color & 0xff000000) >> 24;
if (alpha)
calpha_en = 0x0; /* xrgb */
else
calpha_en = 0x2; /* argb */
size = (img->height << 16) | img->width;
MDSS_MDP_REG_WRITE(off + MDSS_MDP_REG_LM_CURSOR_IMG_SIZE, size);
MDSS_MDP_REG_WRITE(off + MDSS_MDP_REG_LM_CURSOR_SIZE, size);
MDSS_MDP_REG_WRITE(off + MDSS_MDP_REG_LM_CURSOR_STRIDE,
img->width * 4);
MDSS_MDP_REG_WRITE(off + MDSS_MDP_REG_LM_CURSOR_BASE_ADDR,
cursor_addr);
wmb();
blendcfg &= ~0x1;
blendcfg |= (transp_en << 3) | (calpha_en << 1);
MDSS_MDP_REG_WRITE(off + MDSS_MDP_REG_LM_CURSOR_BLEND_CONFIG,
blendcfg);
if (calpha_en)
MDSS_MDP_REG_WRITE(off +
MDSS_MDP_REG_LM_CURSOR_BLEND_PARAM,
alpha);
if (transp_en) {
MDSS_MDP_REG_WRITE(off +
MDSS_MDP_REG_LM_CURSOR_BLEND_TRANSP_LOW0,
((img->bg_color & 0xff00) << 8) |
(img->bg_color & 0xff));
MDSS_MDP_REG_WRITE(off +
MDSS_MDP_REG_LM_CURSOR_BLEND_TRANSP_LOW1,
((img->bg_color & 0xff0000) >> 16));
MDSS_MDP_REG_WRITE(off +
MDSS_MDP_REG_LM_CURSOR_BLEND_TRANSP_HIGH0,
((img->bg_color & 0xff00) << 8) |
(img->bg_color & 0xff));
MDSS_MDP_REG_WRITE(off +
MDSS_MDP_REG_LM_CURSOR_BLEND_TRANSP_HIGH1,
((img->bg_color & 0xff0000) >> 16));
}
}
if (!cursor->enable != !(blendcfg & 0x1)) {
if (cursor->enable) {
pr_debug("enable hw cursor on mixer=%d\n", mixer->num);
blendcfg |= 0x1;
} else {
pr_debug("disable hw cursor on mixer=%d\n", mixer->num);
blendcfg &= ~0x1;
}
MDSS_MDP_REG_WRITE(off + MDSS_MDP_REG_LM_CURSOR_BLEND_CONFIG,
blendcfg);
mixer->cursor_enabled = cursor->enable;
mixer->params_changed++;
}
mixer->ctl->flush_bits |= BIT(6) << mixer->num;
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false);
return 0;
}
static int mdss_mdp_overlay_ioctl_handler(struct msm_fb_data_type *mfd,
u32 cmd, void __user *argp)
{
struct mdp_overlay req;
int val, ret = -ENOSYS;
switch (cmd) {
case MSMFB_OVERLAY_GET:
ret = copy_from_user(&req, argp, sizeof(req));
if (!ret) {
ret = mdss_mdp_overlay_get(mfd, &req);
if (!IS_ERR_VALUE(ret))
ret = copy_to_user(argp, &req, sizeof(req));
}
if (ret) {
pr_debug("OVERLAY_GET failed (%d)\n", ret);
ret = -EFAULT;
}
break;
case MSMFB_OVERLAY_SET:
ret = copy_from_user(&req, argp, sizeof(req));
if (!ret) {
ret = mdss_mdp_overlay_set(mfd, &req);
if (!IS_ERR_VALUE(ret))
ret = copy_to_user(argp, &req, sizeof(req));
}
if (ret) {
pr_debug("OVERLAY_SET failed (%d)\n", ret);
ret = -EFAULT;
}
break;
case MSMFB_OVERLAY_UNSET:
if (!IS_ERR_VALUE(copy_from_user(&val, argp, sizeof(val))))
ret = mdss_mdp_overlay_unset(mfd, val);
break;
case MSMFB_OVERLAY_PLAY_ENABLE:
if (!copy_from_user(&val, argp, sizeof(val))) {
mfd->overlay_play_enable = val;
} else {
pr_err("OVERLAY_PLAY_ENABLE failed (%d)\n", ret);
ret = -EFAULT;
}
break;
case MSMFB_OVERLAY_PLAY:
if (mfd->overlay_play_enable) {
struct msmfb_overlay_data data;
ret = copy_from_user(&data, argp, sizeof(data));
if (!ret) {
ret = mdss_mdp_overlay_play(mfd, &data);
if (!IS_ERR_VALUE(ret))
mdss_fb_update_backlight(mfd);
}
if (ret) {
pr_debug("OVERLAY_PLAY failed (%d)\n", ret);
ret = -EFAULT;
}
} else {
ret = 0;
}
break;
case MSMFB_OVERLAY_PLAY_WAIT:
if (mfd->overlay_play_enable) {
struct msmfb_overlay_data data;
ret = copy_from_user(&data, argp, sizeof(data));
if (!ret)
ret = mdss_mdp_overlay_play_wait(mfd, &data);
if (ret) {
pr_err("OVERLAY_PLAY_WAIT failed (%d)\n", ret);
ret = -EFAULT;
}
} else {
ret = 0;
}
break;
case MSMFB_VSYNC_CTRL:
case MSMFB_OVERLAY_VSYNC_CTRL:
if (!copy_from_user(&val, argp, sizeof(val))) {
ret = mdss_mdp_overlay_vsync_ctrl(mfd, val);
} else {
pr_err("MSMFB_OVERLAY_VSYNC_CTRL failed (%d)\n", ret);
ret = -EFAULT;
}
break;
case MSMFB_OVERLAY_COMMIT:
ret = mdss_mdp_overlay_kickoff(mfd->ctl);
break;
default:
if (mfd->panel_info.type == WRITEBACK_PANEL)
ret = mdss_mdp_wb_ioctl_handler(mfd, cmd, argp);
break;
}
return ret;
}
static int mdss_mdp_overlay_on(struct msm_fb_data_type *mfd)
{
int rc;
rc = mdss_mdp_ctl_on(mfd);
if (rc == 0)
atomic_inc(&ov_active_panels);
return rc;
}
static int mdss_mdp_overlay_off(struct msm_fb_data_type *mfd)
{
int rc;
mdss_mdp_overlay_release_all(mfd);
rc = mdss_mdp_ctl_off(mfd);
if (rc == 0) {
if (atomic_dec_return(&ov_active_panels) == 0)
mdss_mdp_rotator_release_all();
}
return rc;
}
int mdss_mdp_overlay_init(struct msm_fb_data_type *mfd)
{
struct device *dev = mfd->fbi->dev;
int rc;
mfd->on_fnc = mdss_mdp_overlay_on;
mfd->off_fnc = mdss_mdp_overlay_off;
mfd->hw_refresh = true;
mfd->do_histogram = NULL;
mfd->overlay_play_enable = true;
mfd->cursor_update = mdss_mdp_hw_cursor_update;
mfd->dma_fnc = mdss_mdp_overlay_pan_display;
mfd->ioctl_handler = mdss_mdp_overlay_ioctl_handler;
if (mfd->panel_info.type == WRITEBACK_PANEL)
mfd->kickoff_fnc = mdss_mdp_wb_kickoff;
INIT_LIST_HEAD(&mfd->pipes_used);
INIT_LIST_HEAD(&mfd->pipes_cleanup);
init_completion(&mfd->vsync_comp);
spin_lock_init(&mfd->vsync_lock);
mutex_init(&mfd->ov_lock);
rc = sysfs_create_group(&dev->kobj, &vsync_fs_attr_group);
if (rc) {
pr_err("vsync sysfs group creation failed, ret=%d\n", rc);
return rc;
}
kobject_uevent(&dev->kobj, KOBJ_ADD);
pr_debug("vsync kobject_uevent(KOBJ_ADD)\n");
return rc;
}