953 lines
22 KiB
C
953 lines
22 KiB
C
/* 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 <linux/errno.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/major.h>
|
|
#include <linux/module.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/iommu.h>
|
|
#include <linux/switch.h>
|
|
|
|
#include <linux/qcom_iommu.h>
|
|
#include <linux/msm_iommu_domains.h>
|
|
|
|
#include "mdss_mdp.h"
|
|
#include "mdss_fb.h"
|
|
#include "mdss_wb.h"
|
|
#include "mdss_smmu.h"
|
|
|
|
enum mdss_mdp_wb_state {
|
|
WB_OPEN,
|
|
WB_START,
|
|
WB_STOPING,
|
|
WB_STOP
|
|
};
|
|
|
|
struct mdss_mdp_wb {
|
|
u32 fb_ndx;
|
|
struct mutex lock;
|
|
struct list_head busy_queue;
|
|
struct list_head free_queue;
|
|
struct list_head register_queue;
|
|
wait_queue_head_t wait_q;
|
|
u32 state;
|
|
int is_secure;
|
|
struct mdss_mdp_pipe *secure_pipe;
|
|
};
|
|
|
|
enum mdss_mdp_wb_node_state {
|
|
REGISTERED,
|
|
IN_FREE_QUEUE,
|
|
IN_BUSY_QUEUE,
|
|
WITH_CLIENT,
|
|
WB_BUFFER_READY,
|
|
};
|
|
|
|
struct mdss_mdp_wb_data {
|
|
struct list_head registered_entry;
|
|
struct list_head active_entry;
|
|
struct msmfb_data buf_info;
|
|
struct mdss_mdp_data buf_data;
|
|
int state;
|
|
bool user_alloc;
|
|
};
|
|
|
|
static DEFINE_MUTEX(mdss_mdp_wb_buf_lock);
|
|
static struct mdss_mdp_wb mdss_mdp_wb_info;
|
|
|
|
static void mdss_mdp_wb_free_node(struct mdss_mdp_wb_data *node);
|
|
|
|
#ifdef DEBUG_WRITEBACK
|
|
/* for debugging: writeback output buffer to allocated memory */
|
|
static inline
|
|
struct mdss_mdp_data *mdss_mdp_wb_debug_buffer(struct msm_fb_data_type *mfd)
|
|
{
|
|
static struct ion_handle *ihdl;
|
|
static void *videomemory;
|
|
static ion_phys_addr_t mdss_wb_mem;
|
|
static struct mdss_mdp_data mdss_wb_buffer = { .num_planes = 1, };
|
|
int rc;
|
|
|
|
if (IS_ERR_OR_NULL(ihdl)) {
|
|
struct fb_info *fbi;
|
|
size_t img_size;
|
|
struct ion_client *iclient = mdss_get_ionclient();
|
|
struct mdss_mdp_img_data *img = mdss_wb_buffer.p;
|
|
|
|
fbi = mfd->fbi;
|
|
img_size = fbi->var.xres * fbi->var.yres *
|
|
fbi->var.bits_per_pixel / 8;
|
|
|
|
|
|
ihdl = ion_alloc(iclient, img_size, SZ_4K,
|
|
ION_HEAP(ION_SF_HEAP_ID), 0);
|
|
if (IS_ERR_OR_NULL(ihdl)) {
|
|
pr_err("unable to alloc fbmem from ion (%p)\n", ihdl);
|
|
return NULL;
|
|
}
|
|
|
|
videomemory = ion_map_kernel(iclient, ihdl);
|
|
ion_phys(iclient, ihdl, &mdss_wb_mem, &img_size);
|
|
|
|
if (is_mdss_iommu_attached()) {
|
|
int domain = MDSS_IOMMU_DOMAIN_UNSECURE;
|
|
rc = ion_map_iommu(iclient, ihdl,
|
|
mdss_smmu_get_domain_id(domain),
|
|
0, SZ_4K, 0,
|
|
&img->addr,
|
|
(unsigned long *) &img->len,
|
|
0, 0);
|
|
} else {
|
|
if (MDSS_LPAE_CHECK(mdss_wb_mem)) {
|
|
pr_err("Can't use phys mem %pa>4Gb w/o IOMMU\n",
|
|
&mdss_wb_mem);
|
|
ion_free(iclient, ihdl);
|
|
return NULL;
|
|
}
|
|
|
|
img->addr = mdss_wb_mem;
|
|
img->len = img_size;
|
|
}
|
|
|
|
pr_debug("ihdl=%p virt=%p phys=0x%pa iova=0x%pa size=%u\n",
|
|
ihdl, videomemory, &mdss_wb_mem, &img->addr, img_size);
|
|
}
|
|
return &mdss_wb_buffer;
|
|
}
|
|
#else
|
|
static inline
|
|
struct mdss_mdp_data *mdss_mdp_wb_debug_buffer(struct msm_fb_data_type *mfd)
|
|
{
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* mdss_mdp_get_secure() - Queries the secure status of a writeback session
|
|
* @mfd: Frame buffer device structure
|
|
* @enabled: Pointer to convey if session is secure
|
|
*
|
|
* This api enables an entity (userspace process, driver module, etc.) to
|
|
* query the secure status of a writeback session. The secure status is
|
|
* then supplied via a pointer.
|
|
*/
|
|
int mdss_mdp_wb_get_secure(struct msm_fb_data_type *mfd, uint8_t *enabled)
|
|
{
|
|
struct mdss_mdp_wb *wb = mfd_to_wb(mfd);
|
|
if (!wb)
|
|
return -EINVAL;
|
|
*enabled = wb->is_secure;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* mdss_mdp_set_secure() - Updates the secure status of a writeback session
|
|
* @mfd: Frame buffer device structure
|
|
* @enable: New secure status (1: secure, 0: non-secure)
|
|
*
|
|
* This api enables an entity to modify the secure status of a writeback
|
|
* session. If enable is 1, we allocate a secure pipe so that MDP is
|
|
* allowed to write back into the secure buffer. If enable is 0, we
|
|
* deallocate the secure pipe (if it was allocated previously).
|
|
*/
|
|
int mdss_mdp_wb_set_secure(struct msm_fb_data_type *mfd, int enable)
|
|
{
|
|
struct mdss_mdp_wb *wb = mfd_to_wb(mfd);
|
|
struct mdss_mdp_ctl *ctl = mfd_to_ctl(mfd);
|
|
struct mdss_mdp_pipe *pipe;
|
|
struct mdss_mdp_mixer *mixer;
|
|
|
|
pr_debug("setting secure=%d\n", enable);
|
|
if ((enable != 1) && (enable != 0)) {
|
|
pr_err("Invalid enable value = %d\n", enable);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!ctl || !ctl->mdata) {
|
|
pr_err("%s : ctl is NULL", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!wb) {
|
|
pr_err("unable to start, writeback is not initialized\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
ctl->is_secure = enable;
|
|
wb->is_secure = enable;
|
|
|
|
/* newer revisions don't require secure src pipe for secure session */
|
|
if (ctl->mdata->mdp_rev > MDSS_MDP_HW_REV_100)
|
|
return 0;
|
|
|
|
pipe = wb->secure_pipe;
|
|
|
|
if (!enable) {
|
|
if (pipe) {
|
|
/* unset pipe */
|
|
mdss_mdp_mixer_pipe_unstage(pipe, pipe->mixer_left);
|
|
mdss_mdp_pipe_destroy(pipe);
|
|
wb->secure_pipe = NULL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
mixer = mdss_mdp_mixer_get(ctl, MDSS_MDP_MIXER_MUX_DEFAULT);
|
|
if (!mixer) {
|
|
pr_err("Unable to find mixer for wb\n");
|
|
return -ENOENT;
|
|
}
|
|
|
|
if (!pipe) {
|
|
pipe = mdss_mdp_pipe_alloc(mixer, MDSS_MDP_PIPE_TYPE_RGB,
|
|
NULL);
|
|
if (IS_ERR_OR_NULL(pipe))
|
|
pipe = mdss_mdp_pipe_alloc(mixer,
|
|
MDSS_MDP_PIPE_TYPE_VIG, NULL);
|
|
if (IS_ERR_OR_NULL(pipe)) {
|
|
pr_err("Unable to get pipe to set secure session\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
pipe->src_fmt = mdss_mdp_get_format_params(MDP_RGBA_8888);
|
|
|
|
pipe->mfd = mfd;
|
|
pipe->mixer_stage = MDSS_MDP_STAGE_BASE;
|
|
wb->secure_pipe = pipe;
|
|
}
|
|
|
|
pipe->img_height = mixer->height;
|
|
pipe->img_width = mixer->width;
|
|
pipe->src.x = 0;
|
|
pipe->src.y = 0;
|
|
pipe->src.w = pipe->img_width;
|
|
pipe->src.h = pipe->img_height;
|
|
pipe->dst = pipe->src;
|
|
|
|
pipe->flags = (enable ? MDP_SECURE_OVERLAY_SESSION : 0);
|
|
pipe->params_changed++;
|
|
|
|
pr_debug("setting secure pipe=%d flags=%x\n", pipe->num, pipe->flags);
|
|
|
|
return mdss_mdp_pipe_queue_data(pipe, NULL);
|
|
}
|
|
|
|
static int mdss_mdp_wb_init(struct msm_fb_data_type *mfd)
|
|
{
|
|
struct mdss_overlay_private *mdp5_data = mfd_to_mdp5_data(mfd);
|
|
struct mdss_mdp_wb *wb = mfd_to_wb(mfd);
|
|
int rc = 0;
|
|
|
|
mutex_lock(&mdss_mdp_wb_buf_lock);
|
|
if (wb == NULL) {
|
|
wb = &mdss_mdp_wb_info;
|
|
wb->fb_ndx = mfd->index;
|
|
mdp5_data->wb = wb;
|
|
} else if (mfd->index != wb->fb_ndx) {
|
|
pr_err("only one writeback intf supported at a time\n");
|
|
rc = -EMLINK;
|
|
goto error;
|
|
} else {
|
|
pr_debug("writeback already initialized\n");
|
|
}
|
|
|
|
pr_debug("init writeback on fb%d\n", wb->fb_ndx);
|
|
|
|
mutex_init(&wb->lock);
|
|
INIT_LIST_HEAD(&wb->free_queue);
|
|
INIT_LIST_HEAD(&wb->busy_queue);
|
|
INIT_LIST_HEAD(&wb->register_queue);
|
|
wb->state = WB_OPEN;
|
|
init_waitqueue_head(&wb->wait_q);
|
|
|
|
mdp5_data->wb = wb;
|
|
error:
|
|
mutex_unlock(&mdss_mdp_wb_buf_lock);
|
|
return rc;
|
|
}
|
|
|
|
static int mdss_mdp_wb_terminate(struct msm_fb_data_type *mfd)
|
|
{
|
|
struct mdss_overlay_private *mdp5_data = mfd_to_mdp5_data(mfd);
|
|
struct mdss_mdp_wb *wb = mfd_to_wb(mfd);
|
|
|
|
if (!wb) {
|
|
pr_err("unable to terminate, writeback is not initialized\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
pr_debug("terminate writeback\n");
|
|
|
|
mutex_lock(&mdss_mdp_wb_buf_lock);
|
|
mutex_lock(&wb->lock);
|
|
if (!list_empty(&wb->register_queue)) {
|
|
struct mdss_mdp_wb_data *node, *temp;
|
|
list_for_each_entry_safe(node, temp, &wb->register_queue,
|
|
registered_entry) {
|
|
mdss_mdp_wb_free_node(node);
|
|
list_del(&node->registered_entry);
|
|
kfree(node);
|
|
}
|
|
}
|
|
|
|
wb->is_secure = false;
|
|
if (wb->secure_pipe)
|
|
mdss_mdp_pipe_destroy(wb->secure_pipe);
|
|
mutex_unlock(&wb->lock);
|
|
if (mdp5_data->ctl)
|
|
mdp5_data->ctl->is_secure = false;
|
|
mdp5_data->wb = NULL;
|
|
mutex_unlock(&mdss_mdp_wb_buf_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mdss_mdp_wb_start(struct msm_fb_data_type *mfd)
|
|
{
|
|
struct mdss_mdp_wb *wb = mfd_to_wb(mfd);
|
|
|
|
if (!wb) {
|
|
pr_err("unable to start, writeback is not initialized\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
mutex_lock(&wb->lock);
|
|
wb->state = WB_START;
|
|
mutex_unlock(&wb->lock);
|
|
wake_up(&wb->wait_q);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mdss_mdp_wb_stop(struct msm_fb_data_type *mfd)
|
|
{
|
|
struct mdss_mdp_wb *wb = mfd_to_wb(mfd);
|
|
|
|
if (!wb) {
|
|
pr_err("unable to stop, writeback is not initialized\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
mutex_lock(&wb->lock);
|
|
wb->state = WB_STOPING;
|
|
mutex_unlock(&wb->lock);
|
|
wake_up(&wb->wait_q);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mdss_mdp_wb_register_node(struct mdss_mdp_wb *wb,
|
|
struct mdss_mdp_wb_data *node)
|
|
{
|
|
if (!node) {
|
|
pr_err("Invalid wb node\n");
|
|
return -EINVAL;
|
|
}
|
|
node->state = REGISTERED;
|
|
list_add_tail(&node->registered_entry, &wb->register_queue);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct mdss_mdp_wb_data *get_local_node(struct mdss_mdp_wb *wb,
|
|
struct msmfb_data *data) {
|
|
struct mdss_mdp_wb_data *node;
|
|
struct mdss_mdp_img_data *buf;
|
|
int ret;
|
|
|
|
if (!data->iova)
|
|
return NULL;
|
|
|
|
if (!list_empty(&wb->register_queue)) {
|
|
list_for_each_entry(node, &wb->register_queue, registered_entry)
|
|
if (node->buf_info.iova == data->iova) {
|
|
pr_debug("found node iova=%pa addr=%pa\n",
|
|
&data->iova, &node->buf_data.p[0].addr);
|
|
return node;
|
|
}
|
|
}
|
|
|
|
node = kzalloc(sizeof(struct mdss_mdp_wb_data), GFP_KERNEL);
|
|
if (node == NULL) {
|
|
pr_err("out of memory\n");
|
|
return NULL;
|
|
}
|
|
|
|
node->buf_data.num_planes = 1;
|
|
node->buf_info = *data;
|
|
buf = &node->buf_data.p[0];
|
|
buf->addr = (u32) (data->iova + data->offset);
|
|
buf->len = UINT_MAX; /* trusted source */
|
|
if (wb->is_secure)
|
|
buf->flags |= MDP_SECURE_OVERLAY_SESSION;
|
|
ret = mdss_mdp_wb_register_node(wb, node);
|
|
if (IS_ERR_VALUE(ret)) {
|
|
pr_err("error registering wb node\n");
|
|
kfree(node);
|
|
return NULL;
|
|
}
|
|
|
|
pr_debug("register node iova=0x%pa addr=0x%pa\n", &data->iova,
|
|
&buf->addr);
|
|
|
|
return node;
|
|
}
|
|
|
|
static struct mdss_mdp_wb_data *get_user_node(struct msm_fb_data_type *mfd,
|
|
struct msmfb_data *data)
|
|
{
|
|
struct mdss_mdp_ctl *ctl = mfd_to_ctl(mfd);
|
|
struct mdp_layer_buffer buffer;
|
|
struct mdss_mdp_wb *wb = mfd_to_wb(mfd);
|
|
struct mdss_mdp_wb_data *node;
|
|
struct mdss_mdp_img_data *buf;
|
|
struct mdss_mdp_mixer *mixer;
|
|
u32 flags = 0;
|
|
int ret;
|
|
|
|
if (!list_empty(&wb->register_queue)) {
|
|
struct ion_client *iclient = mdss_get_ionclient();
|
|
struct ion_handle *ihdl;
|
|
|
|
if (IS_ERR_OR_NULL(iclient)) {
|
|
pr_err("unable to get mdss ion client\n");
|
|
return NULL;
|
|
}
|
|
ihdl = ion_import_dma_buf(iclient, data->memory_id);
|
|
if (IS_ERR_OR_NULL(ihdl)) {
|
|
pr_err("unable to import fd %d\n", data->memory_id);
|
|
return NULL;
|
|
}
|
|
/* only interested in ptr address, so we can free handle */
|
|
ion_free(iclient, ihdl);
|
|
|
|
list_for_each_entry(node, &wb->register_queue, registered_entry)
|
|
if ((node->buf_data.p[0].srcp_ihdl == ihdl) &&
|
|
(node->buf_info.offset == data->offset)) {
|
|
pr_debug("found fd=%d hdl=%p off=%x addr=%pa\n",
|
|
data->memory_id, ihdl,
|
|
data->offset,
|
|
&node->buf_data.p[0].addr);
|
|
return node;
|
|
}
|
|
}
|
|
|
|
node = kzalloc(sizeof(struct mdss_mdp_wb_data), GFP_KERNEL);
|
|
if (node == NULL) {
|
|
pr_err("out of memory\n");
|
|
return NULL;
|
|
}
|
|
|
|
node->user_alloc = true;
|
|
if (wb->is_secure)
|
|
flags |= MDP_SECURE_OVERLAY_SESSION;
|
|
|
|
mixer = mdss_mdp_mixer_get(ctl, MDSS_MDP_MIXER_MUX_DEFAULT);
|
|
if (!mixer) {
|
|
pr_err("Null mixer\n");
|
|
goto register_fail;
|
|
}
|
|
buffer.width = mixer->width;
|
|
buffer.height = mixer->height;
|
|
buffer.format = ctl->dst_format;
|
|
ret = mdss_mdp_data_get_and_validate_size(&node->buf_data,
|
|
data, 1, flags, &mfd->pdev->dev, true, DMA_FROM_DEVICE,
|
|
&buffer);
|
|
if (IS_ERR_VALUE(ret)) {
|
|
pr_err("error getting buffer info\n");
|
|
goto register_fail;
|
|
}
|
|
|
|
ret = mdss_iommu_ctrl(1);
|
|
if (IS_ERR_VALUE(ret)) {
|
|
pr_err("IOMMU attach failed\n");
|
|
goto fail_freebuf;
|
|
}
|
|
|
|
ret = mdss_mdp_data_map(&node->buf_data, true, DMA_FROM_DEVICE);
|
|
if (IS_ERR_VALUE(ret)) {
|
|
pr_err("error mapping buffer\n");
|
|
mdss_iommu_ctrl(0);
|
|
goto fail_freebuf;
|
|
}
|
|
|
|
mdss_iommu_ctrl(0);
|
|
|
|
memcpy(&node->buf_info, data, sizeof(*data));
|
|
|
|
ret = mdss_mdp_wb_register_node(wb, node);
|
|
if (IS_ERR_VALUE(ret)) {
|
|
pr_err("error registering wb node\n");
|
|
goto fail_freebuf;
|
|
}
|
|
|
|
buf = &node->buf_data.p[0];
|
|
pr_debug("register node mem_id=%d offset=%u addr=0x%pa len=%lu\n",
|
|
data->memory_id, data->offset, &buf->addr, buf->len);
|
|
|
|
return node;
|
|
fail_freebuf:
|
|
mdss_mdp_data_free(&node->buf_data, true, DMA_FROM_DEVICE);
|
|
register_fail:
|
|
kfree(node);
|
|
return NULL;
|
|
}
|
|
|
|
static void mdss_mdp_wb_free_node(struct mdss_mdp_wb_data *node)
|
|
{
|
|
struct mdss_mdp_img_data *buf;
|
|
|
|
if (node->user_alloc) {
|
|
buf = &node->buf_data.p[0];
|
|
|
|
pr_debug("free user mem_id=%d ihdl=%p, offset=%u addr=0x%pa\n",
|
|
node->buf_info.memory_id,
|
|
buf->srcp_ihdl,
|
|
node->buf_info.offset,
|
|
&buf->addr);
|
|
|
|
mdss_mdp_data_free(&node->buf_data, true, DMA_FROM_DEVICE);
|
|
node->user_alloc = false;
|
|
}
|
|
}
|
|
|
|
static int mdss_mdp_wb_queue(struct msm_fb_data_type *mfd,
|
|
struct msmfb_data *data, int local)
|
|
{
|
|
struct mdss_mdp_wb *wb = mfd_to_wb(mfd);
|
|
struct mdss_mdp_wb_data *node = NULL;
|
|
int ret = 0;
|
|
|
|
if (!wb) {
|
|
pr_err("unable to queue, writeback is not initialized\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
pr_debug("fb%d queue\n", wb->fb_ndx);
|
|
|
|
mutex_lock(&wb->lock);
|
|
if (local)
|
|
node = get_local_node(wb, data);
|
|
if (node == NULL)
|
|
node = get_user_node(mfd, data);
|
|
|
|
if (!node) {
|
|
pr_err("memory not registered\n");
|
|
ret = -ENOENT;
|
|
} else {
|
|
struct mdss_mdp_img_data *buf = &node->buf_data.p[0];
|
|
|
|
switch (node->state) {
|
|
case IN_FREE_QUEUE:
|
|
pr_err("node 0x%pa was already queueued before\n",
|
|
&buf->addr);
|
|
ret = -EINVAL;
|
|
break;
|
|
case IN_BUSY_QUEUE:
|
|
pr_err("node 0x%pa still in busy state\n", &buf->addr);
|
|
ret = -EBUSY;
|
|
break;
|
|
case WB_BUFFER_READY:
|
|
pr_debug("node 0x%pa re-queueded without dequeue\n",
|
|
&buf->addr);
|
|
list_del(&node->active_entry);
|
|
case WITH_CLIENT:
|
|
case REGISTERED:
|
|
list_add_tail(&node->active_entry, &wb->free_queue);
|
|
node->state = IN_FREE_QUEUE;
|
|
break;
|
|
default:
|
|
pr_err("Invalid node 0x%pa state %d\n",
|
|
&buf->addr, node->state);
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
}
|
|
mutex_unlock(&wb->lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int is_buffer_ready(struct mdss_mdp_wb *wb)
|
|
{
|
|
int rc;
|
|
mutex_lock(&wb->lock);
|
|
rc = !list_empty(&wb->busy_queue) || (wb->state == WB_STOPING);
|
|
mutex_unlock(&wb->lock);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int mdss_mdp_wb_dequeue(struct msm_fb_data_type *mfd,
|
|
struct msmfb_data *data)
|
|
{
|
|
struct mdss_mdp_wb *wb = mfd_to_wb(mfd);
|
|
struct mdss_mdp_wb_data *node = NULL;
|
|
struct mdss_mdp_ctl *ctl = mfd_to_ctl(mfd);
|
|
int ret;
|
|
|
|
if (!wb) {
|
|
pr_err("unable to dequeue, writeback is not initialized\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (!ctl) {
|
|
pr_err("unable to dequeue, ctl is not initialized\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
ret = wait_event_interruptible(wb->wait_q, is_buffer_ready(wb));
|
|
if (ret) {
|
|
pr_err("failed to get dequeued buffer\n");
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
mutex_lock(&wb->lock);
|
|
if (wb->state == WB_STOPING) {
|
|
pr_debug("wfd stopped\n");
|
|
mdss_mdp_display_wait4comp(ctl);
|
|
wb->state = WB_STOP;
|
|
ret = -ENOBUFS;
|
|
} else if (!list_empty(&wb->busy_queue)) {
|
|
struct mdss_mdp_img_data *buf;
|
|
node = list_first_entry(&wb->busy_queue,
|
|
struct mdss_mdp_wb_data,
|
|
active_entry);
|
|
list_del(&node->active_entry);
|
|
node->state = WITH_CLIENT;
|
|
memcpy(data, &node->buf_info, sizeof(*data));
|
|
|
|
buf = &node->buf_data.p[0];
|
|
pr_debug("found node addr=%pa len=%lu\n", &buf->addr, buf->len);
|
|
} else {
|
|
pr_debug("node is NULL, wait for next\n");
|
|
ret = -ENOBUFS;
|
|
}
|
|
mutex_unlock(&wb->lock);
|
|
return ret;
|
|
}
|
|
|
|
int mdss_mdp_wb_kickoff(struct msm_fb_data_type *mfd,
|
|
struct mdss_mdp_commit_cb *commit_cb)
|
|
{
|
|
struct mdss_mdp_wb *wb = mfd_to_wb(mfd);
|
|
struct mdss_mdp_ctl *ctl = mfd_to_ctl(mfd);
|
|
struct mdss_mdp_wb_data *node = NULL;
|
|
int ret = 0;
|
|
struct mdss_mdp_writeback_arg wb_args;
|
|
|
|
if (!ctl) {
|
|
pr_err("no ctl attached to fb=%d devicet\n", mfd->index);
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (!mdss_mdp_ctl_is_power_on(ctl))
|
|
return 0;
|
|
|
|
memset(&wb_args, 0, sizeof(wb_args));
|
|
|
|
mutex_lock(&mdss_mdp_wb_buf_lock);
|
|
if (wb) {
|
|
mutex_lock(&wb->lock);
|
|
/* in case of reinit of control path need to reset secure */
|
|
if (ctl->play_cnt == 0)
|
|
mdss_mdp_wb_set_secure(ctl->mfd, wb->is_secure);
|
|
if (!list_empty(&wb->free_queue) && wb->state != WB_STOPING &&
|
|
wb->state != WB_STOP) {
|
|
node = list_first_entry(&wb->free_queue,
|
|
struct mdss_mdp_wb_data,
|
|
active_entry);
|
|
list_del(&node->active_entry);
|
|
node->state = IN_BUSY_QUEUE;
|
|
wb_args.data = &node->buf_data;
|
|
} else {
|
|
pr_debug("unable to get buf wb state=%d\n", wb->state);
|
|
}
|
|
mutex_unlock(&wb->lock);
|
|
}
|
|
|
|
if (wb_args.data == NULL)
|
|
wb_args.data = mdss_mdp_wb_debug_buffer(ctl->mfd);
|
|
|
|
if (wb_args.data == NULL) {
|
|
pr_err("unable to get writeback buf ctl=%d\n", ctl->num);
|
|
/* drop buffer but don't return error */
|
|
ret = 0;
|
|
mdss_mdp_ctl_notify(ctl, MDP_NOTIFY_FRAME_DONE);
|
|
goto kickoff_fail;
|
|
}
|
|
|
|
ret = mdss_mdp_writeback_display_commit(ctl, &wb_args);
|
|
if (ret) {
|
|
pr_err("error on commit ctl=%d\n", ctl->num);
|
|
goto kickoff_fail;
|
|
}
|
|
|
|
if (commit_cb)
|
|
commit_cb->commit_cb_fnc(
|
|
MDP_COMMIT_STAGE_SETUP_DONE,
|
|
commit_cb->data);
|
|
|
|
mdss_mdp_display_wait4comp(ctl);
|
|
|
|
if (commit_cb)
|
|
commit_cb->commit_cb_fnc(MDP_COMMIT_STAGE_READY_FOR_KICKOFF,
|
|
commit_cb->data);
|
|
|
|
if (wb && node) {
|
|
mutex_lock(&wb->lock);
|
|
list_add_tail(&node->active_entry, &wb->busy_queue);
|
|
node->state = WB_BUFFER_READY;
|
|
mutex_unlock(&wb->lock);
|
|
wake_up(&wb->wait_q);
|
|
}
|
|
|
|
kickoff_fail:
|
|
mutex_unlock(&mdss_mdp_wb_buf_lock);
|
|
return ret;
|
|
}
|
|
|
|
int mdss_mdp_wb_set_mirr_hint(struct msm_fb_data_type *mfd, int hint)
|
|
{
|
|
struct mdss_panel_data *pdata = NULL;
|
|
struct mdss_wb_ctrl *wb_ctrl = NULL;
|
|
|
|
if (!mfd) {
|
|
pr_err("No panel data!\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
pdata = mfd->pdev->dev.platform_data;
|
|
wb_ctrl = container_of(pdata, struct mdss_wb_ctrl, pdata);
|
|
|
|
switch (hint) {
|
|
case MDP_WRITEBACK_MIRROR_ON:
|
|
case MDP_WRITEBACK_MIRROR_PAUSE:
|
|
case MDP_WRITEBACK_MIRROR_RESUME:
|
|
case MDP_WRITEBACK_MIRROR_OFF:
|
|
pr_info("wfd state switched to %d\n", hint);
|
|
switch_set_state(&wb_ctrl->sdev, hint);
|
|
return 0;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
int mdss_mdp_wb_get_format(struct msm_fb_data_type *mfd,
|
|
struct mdp_mixer_cfg *mixer_cfg)
|
|
{
|
|
struct mdss_mdp_ctl *ctl = mfd_to_ctl(mfd);
|
|
|
|
if (!ctl) {
|
|
pr_err("No panel data!\n");
|
|
return -EINVAL;
|
|
} else {
|
|
mixer_cfg->writeback_format = ctl->dst_format;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int mdss_mdp_wb_set_format(struct msm_fb_data_type *mfd, u32 dst_format)
|
|
{
|
|
struct mdss_mdp_ctl *ctl = mfd_to_ctl(mfd);
|
|
|
|
if (!ctl) {
|
|
pr_err("No panel data!\n");
|
|
return -EINVAL;
|
|
} else if (dst_format >= MDP_IMGTYPE_LIMIT2) {
|
|
pr_err("Invalid dst format=%u\n", dst_format);
|
|
return -EINVAL;
|
|
} else {
|
|
ctl->dst_format = dst_format;
|
|
}
|
|
|
|
pr_debug("wfd format %d\n", ctl->dst_format);
|
|
return 0;
|
|
}
|
|
|
|
int mdss_mdp_wb_ioctl_handler(struct msm_fb_data_type *mfd, u32 cmd,
|
|
void *arg)
|
|
{
|
|
struct msmfb_data data;
|
|
int ret = -ENOSYS, hint = 0;
|
|
|
|
switch (cmd) {
|
|
case MSMFB_WRITEBACK_INIT:
|
|
ret = mdss_mdp_wb_init(mfd);
|
|
break;
|
|
case MSMFB_WRITEBACK_START:
|
|
ret = mdss_mdp_wb_start(mfd);
|
|
break;
|
|
case MSMFB_WRITEBACK_STOP:
|
|
ret = mdss_mdp_wb_stop(mfd);
|
|
break;
|
|
case MSMFB_WRITEBACK_QUEUE_BUFFER:
|
|
if (!copy_from_user(&data, arg, sizeof(data))) {
|
|
ret = mdss_mdp_wb_queue(mfd, &data, false);
|
|
ret = copy_to_user(arg, &data, sizeof(data));
|
|
} else {
|
|
pr_err("wb queue buf failed on copy_from_user\n");
|
|
ret = -EFAULT;
|
|
}
|
|
break;
|
|
case MSMFB_WRITEBACK_DEQUEUE_BUFFER:
|
|
if (!copy_from_user(&data, arg, sizeof(data))) {
|
|
ret = mdss_mdp_wb_dequeue(mfd, &data);
|
|
ret = copy_to_user(arg, &data, sizeof(data));
|
|
} else {
|
|
pr_err("wb dequeue buf failed on copy_from_user\n");
|
|
ret = -EFAULT;
|
|
}
|
|
break;
|
|
case MSMFB_WRITEBACK_TERMINATE:
|
|
ret = mdss_mdp_wb_terminate(mfd);
|
|
break;
|
|
case MSMFB_WRITEBACK_SET_MIRRORING_HINT:
|
|
if (!copy_from_user(&hint, arg, sizeof(hint))) {
|
|
ret = mdss_mdp_wb_set_mirr_hint(mfd, hint);
|
|
} else {
|
|
pr_err("set mirroring hint failed on copy_from_user\n");
|
|
ret = -EFAULT;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int msm_fb_writeback_start(struct fb_info *info)
|
|
{
|
|
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *) info->par;
|
|
|
|
if (!mfd)
|
|
return -ENODEV;
|
|
|
|
return mdss_mdp_wb_start(mfd);
|
|
}
|
|
EXPORT_SYMBOL(msm_fb_writeback_start);
|
|
|
|
int msm_fb_writeback_queue_buffer(struct fb_info *info,
|
|
struct msmfb_data *data)
|
|
{
|
|
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *) info->par;
|
|
|
|
if (!mfd)
|
|
return -ENODEV;
|
|
|
|
return mdss_mdp_wb_queue(mfd, data, true);
|
|
}
|
|
EXPORT_SYMBOL(msm_fb_writeback_queue_buffer);
|
|
|
|
int msm_fb_writeback_dequeue_buffer(struct fb_info *info,
|
|
struct msmfb_data *data)
|
|
{
|
|
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *) info->par;
|
|
|
|
if (!mfd)
|
|
return -ENODEV;
|
|
|
|
return mdss_mdp_wb_dequeue(mfd, data);
|
|
}
|
|
EXPORT_SYMBOL(msm_fb_writeback_dequeue_buffer);
|
|
|
|
int msm_fb_writeback_stop(struct fb_info *info)
|
|
{
|
|
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *) info->par;
|
|
|
|
if (!mfd)
|
|
return -ENODEV;
|
|
|
|
return mdss_mdp_wb_stop(mfd);
|
|
}
|
|
EXPORT_SYMBOL(msm_fb_writeback_stop);
|
|
|
|
int msm_fb_writeback_init(struct fb_info *info)
|
|
{
|
|
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *) info->par;
|
|
|
|
if (!mfd)
|
|
return -ENODEV;
|
|
|
|
return mdss_mdp_wb_init(mfd);
|
|
}
|
|
EXPORT_SYMBOL(msm_fb_writeback_init);
|
|
|
|
int msm_fb_writeback_terminate(struct fb_info *info)
|
|
{
|
|
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *) info->par;
|
|
|
|
if (!mfd)
|
|
return -ENODEV;
|
|
|
|
return mdss_mdp_wb_terminate(mfd);
|
|
}
|
|
EXPORT_SYMBOL(msm_fb_writeback_terminate);
|
|
|
|
int msm_fb_get_iommu_domain(struct fb_info *info, int domain)
|
|
{
|
|
int mdss_domain;
|
|
switch (domain) {
|
|
case MDP_IOMMU_DOMAIN_CP:
|
|
mdss_domain = MDSS_IOMMU_DOMAIN_SECURE;
|
|
break;
|
|
case MDP_IOMMU_DOMAIN_NS:
|
|
mdss_domain = MDSS_IOMMU_DOMAIN_UNSECURE;
|
|
break;
|
|
default:
|
|
pr_err("Invalid mdp iommu domain (%d)\n", domain);
|
|
return -EINVAL;
|
|
}
|
|
return mdss_smmu_get_domain_id(mdss_domain);
|
|
}
|
|
EXPORT_SYMBOL(msm_fb_get_iommu_domain);
|
|
|
|
int msm_fb_writeback_set_secure(struct fb_info *info, int enable)
|
|
{
|
|
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *) info->par;
|
|
|
|
if (!mfd)
|
|
return -ENODEV;
|
|
|
|
return mdss_mdp_wb_set_secure(mfd, enable);
|
|
}
|
|
EXPORT_SYMBOL(msm_fb_writeback_set_secure);
|
|
|
|
/**
|
|
* msm_fb_writeback_iommu_ref() - Add/Remove vote on MDSS IOMMU being attached.
|
|
* @enable - true adds vote on MDSS IOMMU, false removes the vote.
|
|
*
|
|
* Call to vote on MDSS IOMMU being enabled. To ensure buffers are properly
|
|
* mapped to IOMMU context bank.
|
|
*/
|
|
int msm_fb_writeback_iommu_ref(struct fb_info *info, int enable)
|
|
{
|
|
int ret;
|
|
|
|
if (enable) {
|
|
ret = mdss_iommu_ctrl(1);
|
|
if (IS_ERR_VALUE(ret)) {
|
|
pr_err("IOMMU attach failed\n");
|
|
return ret;
|
|
}
|
|
} else {
|
|
mdss_iommu_ctrl(0);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(msm_fb_writeback_iommu_ref);
|