summaryrefslogtreecommitdiff
path: root/drivers/misc
diff options
context:
space:
mode:
authorPhilippe Langlais <philippe.langlais@stericsson.com>2012-06-04 19:45:30 +0800
committerPhilippe Langlais <philippe.langlais@stericsson.com>2012-06-04 19:45:30 +0800
commit1cd04b2aba9f3b3468adf0789030240e032d8d3e (patch)
tree2e010c15b347b7acda9bc7a2ab4abca9d58043eb /drivers/misc
parentcff72c10aa27e3a74eb5d5b7ceb7808d8c917d15 (diff)
parentb7086e0a1dec82bf7e8e96e17738151f0aee82fc (diff)
Merge topic branch 'video' into integration-linux-ux500
Signed-off-by: Philippe Langlais <philippe.langlais@stericsson.com>
Diffstat (limited to 'drivers/misc')
-rw-r--r--drivers/misc/Kconfig34
-rw-r--r--drivers/misc/Makefile3
-rw-r--r--drivers/misc/clonedev/Makefile5
-rw-r--r--drivers/misc/clonedev/clonedev.c312
-rw-r--r--drivers/misc/compdev/Makefile6
-rw-r--r--drivers/misc/compdev/compdev.c1381
-rw-r--r--drivers/misc/dispdev/Makefile1
-rw-r--r--drivers/misc/dispdev/dispdev.c659
8 files changed, 2401 insertions, 0 deletions
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 1a9ae1647b0..f92560ef750 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -461,6 +461,40 @@ config BMP085
To compile this driver as a module, choose M here: the
module will be called bmp085.
+config DISPDEV
+ bool "Display overlay device"
+ depends on FB_MCDE
+ default n
+ help
+ This driver provides a way to use a second overlay for a display (in
+ addition to the framebuffer). The device allows for registration of
+ userspace buffers to be used with the overlay.
+
+config COMPDEV
+ bool "Display composition device"
+ depends on FB_MCDE && HWMEM
+ default n
+ help
+ This driver provides a way to use several overlays for a display.
+ This driver replaces the use of the framebuffer The device allows
+ for posting userspace buffers to be used with the overlays.
+
+config CLONEDEV
+ bool "Display cloning device"
+ depends on FB_MCDE && HWMEM && COMPDEV
+ default n
+ help
+ This driver provides a way to clone content between two compdev
+ devices.
+
+config CLONEDEV_DEBUG
+ bool "Display cloning device debug"
+ depends on CLONEDEV
+ default n
+ help
+ This driver provides a way to clone content between two compdev
+ devices.
+
config PCH_PHUB
tristate "Intel EG20T PCH/LAPIS Semicon IOH(ML7213/ML7223/ML7831) PHUB"
depends on PCI
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 5317121efcd..2741a01610f 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -48,6 +48,9 @@ obj-y += lis3lv02d/
obj-y += carma/
obj-$(CONFIG_STM_TRACE) += stm.o
obj-$(CONFIG_HWMEM) += hwmem/
+obj-$(CONFIG_DISPDEV) += dispdev/
+obj-$(CONFIG_COMPDEV) += compdev/
+obj-$(CONFIG_CLONEDEV) += clonedev/
obj-$(CONFIG_USB_SWITCH_FSA9480) += fsa9480.o
obj-$(CONFIG_ALTERA_STAPL) +=altera-stapl/
obj-$(CONFIG_MAX8997_MUIC) += max8997-muic.o
diff --git a/drivers/misc/clonedev/Makefile b/drivers/misc/clonedev/Makefile
new file mode 100644
index 00000000000..f84859dd3ee
--- /dev/null
+++ b/drivers/misc/clonedev/Makefile
@@ -0,0 +1,5 @@
+obj-$(CONFIG_CLONEDEV) += clonedev.o
+
+ifdef CONFIG_CLONEDEV_DEBUG
+EXTRA_CFLAGS += -DDEBUG
+endif
diff --git a/drivers/misc/clonedev/clonedev.c b/drivers/misc/clonedev/clonedev.c
new file mode 100644
index 00000000000..d3b770fd324
--- /dev/null
+++ b/drivers/misc/clonedev/clonedev.c
@@ -0,0 +1,312 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2011
+ *
+ * Device for display cloning on external output.
+ *
+ * Author: Per-Daniel Olsson <per-daniel.olsson@stericsson.com>
+ * for ST-Ericsson.
+ *
+ * License terms: GNU General Public License (GPL), version 2.
+ */
+
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/miscdevice.h>
+#include <linux/uaccess.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/ioctl.h>
+
+#include <linux/clonedev.h>
+
+#include <linux/compdev.h>
+#include <linux/mm.h>
+#include <video/mcde.h>
+
+static LIST_HEAD(dev_list);
+static DEFINE_MUTEX(dev_list_lock);
+
+struct clonedev {
+ struct mutex lock;
+ struct miscdevice mdev;
+ struct list_head list;
+ bool open;
+ struct compdev *src_compdev;
+ struct compdev *dst_compdev;
+ bool overlay_case;
+ struct compdev_size dst_size;
+ struct compdev_scene_info s_info;
+};
+
+static void best_fit(struct compdev_rect *src_rect,
+ struct compdev_size *dst_size,
+ struct compdev_img *img)
+{
+ /* aspect ratio in 26.6 fixed point */
+ int aspect = 1;
+ int dst_w;
+ int dst_h;
+
+ if (img->rotation == COMPDEV_ROT_90_CCW ||
+ img->rotation == COMPDEV_ROT_270_CCW)
+ aspect = (src_rect->height << 6) / src_rect->width;
+ else
+ aspect = (src_rect->width << 6) / src_rect->height;
+
+ dst_w = aspect * dst_size->height >> 6;
+ dst_h = dst_size->height;
+ img->dst_rect.y = 0;
+
+ if (dst_w > dst_size->width) {
+ /*
+ * Destination rectangle too wide.
+ * Clamp to image width. Keep aspect ratio.
+ */
+ dst_h = (dst_size->width << 6) / aspect;
+ dst_w = dst_size->width;
+ }
+
+ /* center the image */
+ if (dst_w < dst_size->width) {
+ int offset = (dst_size->width - dst_w) / 2;
+ img->dst_rect.x = offset;
+ }
+
+ if (dst_h < dst_size->height) {
+ int offset = (dst_size->height - dst_h) / 2;
+ img->dst_rect.y = offset;
+ }
+
+ img->dst_rect.width = dst_w;
+ img->dst_rect.height = dst_h;
+}
+
+static int clonedev_open(struct inode *inode, struct file *file)
+{
+ struct clonedev *cd = NULL;
+
+ mutex_lock(&dev_list_lock);
+ list_for_each_entry(cd, &dev_list, list)
+ if (cd->mdev.minor == iminor(inode))
+ break;
+
+ if (&cd->list == &dev_list) {
+ mutex_unlock(&dev_list_lock);
+ return -ENODEV;
+ }
+
+ if (cd->open) {
+ mutex_unlock(&dev_list_lock);
+ return -EBUSY;
+ }
+
+ cd->open = true;
+
+ mutex_unlock(&dev_list_lock);
+
+ file->private_data = cd;
+
+ return 0;
+}
+
+static int clonedev_release(struct inode *inode, struct file *file)
+{
+ struct clonedev *cd = NULL;
+
+ mutex_lock(&dev_list_lock);
+ list_for_each_entry(cd, &dev_list, list)
+ if (cd->mdev.minor == iminor(inode))
+ break;
+ mutex_unlock(&dev_list_lock);
+
+ if (&cd->list == &dev_list)
+ return -ENODEV;
+
+ cd->open = false;
+ return 0;
+}
+
+static long clonedev_ioctl(struct file *file,
+ unsigned int cmd,
+ unsigned long arg)
+{
+ int ret;
+ struct clonedev *cd = (struct clonedev *)file->private_data;
+
+ mutex_lock(&cd->lock);
+
+ switch (cmd) {
+ case CLONEDEV_SET_MODE_IOC:
+ /* TODO: Get the user data */
+
+ break;
+
+ default:
+ ret = -ENOSYS;
+ }
+
+ mutex_unlock(&cd->lock);
+
+ return ret;
+}
+
+static const struct file_operations clonedev_fops = {
+ .open = clonedev_open,
+ .release = clonedev_release,
+ .unlocked_ioctl = clonedev_ioctl,
+};
+
+static void init_clonedev(struct clonedev *cd, const char *name)
+{
+ mutex_init(&cd->lock);
+ INIT_LIST_HEAD(&cd->list);
+
+ cd->mdev.minor = MISC_DYNAMIC_MINOR;
+ cd->mdev.name = name;
+ cd->mdev.fops = &clonedev_fops;
+}
+
+static void clonedev_post_buffer_callback(void *data,
+ struct compdev_img *cb_img)
+{
+ struct clonedev *cd = (struct clonedev *)data;
+
+ mutex_lock(&cd->lock);
+
+ if (!cd->overlay_case || (cd->overlay_case &&
+ (cb_img->flags & COMPDEV_OVERLAY_FLAG))) {
+ struct compdev_img img;
+
+ img = *cb_img;
+
+ if (img.flags & COMPDEV_BYPASS_FLAG)
+ img.flags &= ~COMPDEV_BYPASS_FLAG;
+
+ if (cd->overlay_case)
+ img.rotation = cd->s_info.ovly_rotation;
+ else
+ img.rotation = cd->s_info.fb_rotation;
+
+ best_fit(&img.src_rect, &cd->dst_size, &img);
+
+ compdev_post_buffer(cd->dst_compdev, &img);
+ }
+ mutex_unlock(&cd->lock);
+}
+
+static void clonedev_post_scene_info_callback(void *data,
+ struct compdev_scene_info *s_info)
+{
+ struct clonedev *cd = (struct clonedev *)data;
+
+ mutex_lock(&cd->lock);
+ if (s_info->img_count > 1)
+ cd->overlay_case = true;
+ else
+ cd->overlay_case = false;
+
+ cd->s_info = *s_info;
+ cd->s_info.img_count = 1;
+ compdev_post_scene_info(cd->dst_compdev, &cd->s_info);
+ mutex_unlock(&cd->lock);
+}
+
+int clonedev_create(void)
+{
+ int ret;
+ struct clonedev *cd;
+
+ static int counter;
+ char name[10];
+
+ cd = kzalloc(sizeof(struct clonedev), GFP_KERNEL);
+ if (!cd)
+ return -ENOMEM;
+
+ snprintf(name, sizeof(name), "%s%d", CLONEDEV_DEFAULT_DEVICE_PREFIX,
+ counter++);
+ init_clonedev(cd, name);
+
+ ret = misc_register(&cd->mdev);
+ if (ret)
+ goto fail_register_misc;
+ mutex_lock(&dev_list_lock);
+ list_add_tail(&cd->list, &dev_list);
+ mutex_unlock(&dev_list_lock);
+
+ mutex_lock(&cd->lock);
+
+ compdev_get(0, &cd->src_compdev);
+ compdev_get(1, &cd->dst_compdev);
+ compdev_get_size(cd->dst_compdev, &cd->dst_size);
+
+ compdev_register_listener_callbacks(cd->src_compdev, (void *)cd,
+ &clonedev_post_buffer_callback,
+ &clonedev_post_scene_info_callback);
+
+ mutex_unlock(&cd->lock);
+ goto out;
+
+fail_register_misc:
+ kfree(cd);
+out:
+ return ret;
+}
+
+void clonedev_destroy(void)
+{
+ struct clonedev *cd;
+ struct clonedev *tmp;
+
+ mutex_lock(&dev_list_lock);
+ list_for_each_entry_safe(cd, tmp, &dev_list, list) {
+ compdev_put(cd->src_compdev);
+ compdev_put(cd->dst_compdev);
+ compdev_deregister_callbacks(cd->src_compdev);
+ list_del(&cd->list);
+ misc_deregister(&cd->mdev);
+ kfree(cd);
+ break;
+ }
+ mutex_unlock(&dev_list_lock);
+}
+
+static void clonedev_destroy_all(void)
+{
+ struct clonedev *cd;
+ struct clonedev *tmp;
+
+ mutex_lock(&dev_list_lock);
+ list_for_each_entry_safe(cd, tmp, &dev_list, list) {
+ list_del(&cd->list);
+ misc_deregister(&cd->mdev);
+ kfree(cd);
+ }
+ mutex_unlock(&dev_list_lock);
+
+ mutex_destroy(&dev_list_lock);
+}
+
+static int __init clonedev_init(void)
+{
+ pr_info("%s\n", __func__);
+
+ mutex_init(&dev_list_lock);
+
+ return 0;
+}
+module_init(clonedev_init);
+
+static void __exit clonedev_exit(void)
+{
+ clonedev_destroy_all();
+ pr_info("%s\n", __func__);
+}
+module_exit(clonedev_exit);
+
+MODULE_AUTHOR("Per-Daniel Olsson <per-daniel.olsson@stericsson.com>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Device for display cloning on external output");
+
diff --git a/drivers/misc/compdev/Makefile b/drivers/misc/compdev/Makefile
new file mode 100644
index 00000000000..b8385848712
--- /dev/null
+++ b/drivers/misc/compdev/Makefile
@@ -0,0 +1,6 @@
+obj-$(CONFIG_COMPDEV) += compdev.o
+
+ifdef CONFIG_COMPDEV_DEBUG
+EXTRA_CFLAGS += -DDEBUG
+endif
+
diff --git a/drivers/misc/compdev/compdev.c b/drivers/misc/compdev/compdev.c
new file mode 100644
index 00000000000..d929a02c565
--- /dev/null
+++ b/drivers/misc/compdev/compdev.c
@@ -0,0 +1,1381 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2011
+ *
+ * Display overlay compositer device driver
+ *
+ * Author: Anders Bauer <anders.bauer@stericsson.com>
+ * for ST-Ericsson.
+ *
+ * Modified: Per-Daniel Olsson <per-daniel.olsson@stericsson.com>
+ * for ST-Ericsson.
+ *
+ * License terms: GNU General Public License (GPL), version 2.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/miscdevice.h>
+#include <linux/uaccess.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/ioctl.h>
+#include <linux/sched.h>
+
+#include <linux/compdev.h>
+#include <linux/hwmem.h>
+#include <linux/mm.h>
+#include <video/mcde_dss.h>
+#include <video/b2r2_blt.h>
+#include <linux/workqueue.h>
+#include <linux/completion.h>
+
+#define BUFFER_CACHE_DEPTH 2
+#define NUM_COMPDEV_BUFS 2
+
+static LIST_HEAD(dev_list);
+static DEFINE_MUTEX(dev_list_lock);
+static int dev_counter;
+
+struct compdev_buffer {
+ struct hwmem_alloc *alloc;
+ enum compdev_ptr_type type;
+ u32 size;
+ u32 paddr; /* if pinned */
+};
+
+struct compdev_img_internal {
+ struct compdev_img img;
+ u32 ref_count;
+};
+
+struct compdev_blt_work {
+ struct work_struct work;
+ struct compdev_img *src_img;
+ struct compdev_img_internal *dst_img;
+ int blt_handle;
+ bool mcde_rotation;
+ struct device *dev;
+};
+
+struct compdev_post_callback_work {
+ struct work_struct work;
+ struct compdev_img *img;
+ post_buffer_callback pb_cb;
+ void *cb_data;
+ struct device *dev;
+};
+
+struct buffer_cache_context {
+ struct compdev_img_internal
+ *img[BUFFER_CACHE_DEPTH];
+ u8 index;
+ u8 unused_counter;
+ struct device *dev;
+};
+
+struct dss_context {
+ struct device *dev;
+ struct mcde_display_device *ddev;
+ struct mcde_overlay *ovly[NUM_COMPDEV_BUFS];
+ struct compdev_buffer ovly_buffer[NUM_COMPDEV_BUFS];
+ struct compdev_size phy_size;
+ enum mcde_display_rotation display_rotation;
+ enum compdev_rotation current_buffer_rotation;
+ int blt_handle;
+ u8 temp_img_count;
+ struct compdev_img_internal *temp_img[NUM_COMPDEV_BUFS];
+ struct buffer_cache_context cache_ctx;
+};
+
+struct compdev {
+ struct mutex lock;
+ struct miscdevice mdev;
+ struct device *dev;
+ struct list_head list;
+ struct dss_context dss_ctx;
+ u16 ref_count;
+ struct workqueue_struct *worker_thread;
+ int dev_index;
+ post_buffer_callback pb_cb;
+ post_scene_info_callback si_cb;
+ struct compdev_scene_info s_info;
+ u8 sync_count;
+ u8 image_count;
+ struct compdev_img *images[NUM_COMPDEV_BUFS];
+ struct completion fence;
+ void *cb_data;
+ bool mcde_rotation;
+};
+
+static struct compdev *compdevs[MAX_NBR_OF_COMPDEVS];
+
+static int compdev_post_buffers_dss(struct dss_context *dss_ctx,
+ struct compdev_img *img1, struct compdev_img *img2);
+
+
+static int compdev_open(struct inode *inode, struct file *file)
+{
+ struct compdev *cd = NULL;
+
+ mutex_lock(&dev_list_lock);
+ list_for_each_entry(cd, &dev_list, list)
+ if (cd->mdev.minor == iminor(inode))
+ break;
+
+ if (&cd->list == &dev_list) {
+ mutex_unlock(&dev_list_lock);
+ return -ENODEV;
+ }
+ mutex_unlock(&dev_list_lock);
+ file->private_data = cd;
+ return 0;
+}
+
+static int disable_overlay(struct mcde_overlay *ovly)
+{
+ struct mcde_overlay_info info;
+
+ mcde_dss_get_overlay_info(ovly, &info);
+ if (info.paddr != 0) {
+ /* Set the pointer to zero to disable the overlay */
+ info.paddr = 0;
+ mcde_dss_apply_overlay(ovly, &info);
+ }
+ return 0;
+}
+
+static int compdev_release(struct inode *inode, struct file *file)
+{
+ struct compdev *cd = NULL;
+ int i;
+
+ mutex_lock(&dev_list_lock);
+ list_for_each_entry(cd, &dev_list, list)
+ if (cd->mdev.minor == iminor(inode))
+ break;
+ mutex_unlock(&dev_list_lock);
+
+ if (&cd->list == &dev_list)
+ return -ENODEV;
+
+ for (i = 0; i < NUM_COMPDEV_BUFS; i++) {
+ disable_overlay(cd->dss_ctx.ovly[i]);
+ if (cd->dss_ctx.ovly_buffer[i].paddr &&
+ cd->dss_ctx.ovly_buffer[i].type ==
+ COMPDEV_PTR_HWMEM_BUF_NAME_OFFSET)
+ hwmem_unpin(cd->dss_ctx.ovly_buffer[i].alloc);
+
+ cd->dss_ctx.ovly_buffer[i].alloc = NULL;
+ cd->dss_ctx.ovly_buffer[i].size = 0;
+ cd->dss_ctx.ovly_buffer[i].paddr = 0;
+ }
+
+ return 0;
+}
+
+static enum mcde_ovly_pix_fmt get_ovly_fmt(enum compdev_fmt fmt)
+{
+ switch (fmt) {
+ default:
+ case COMPDEV_FMT_RGB565:
+ return MCDE_OVLYPIXFMT_RGB565;
+ case COMPDEV_FMT_RGB888:
+ return MCDE_OVLYPIXFMT_RGB888;
+ case COMPDEV_FMT_RGBA8888:
+ return MCDE_OVLYPIXFMT_RGBA8888;
+ case COMPDEV_FMT_RGBX8888:
+ return MCDE_OVLYPIXFMT_RGBX8888;
+ case COMPDEV_FMT_YUV422:
+ return MCDE_OVLYPIXFMT_YCbCr422;
+ }
+}
+
+static int compdev_setup_ovly(struct compdev_img *img,
+ struct compdev_buffer *buffer,
+ struct mcde_overlay *ovly,
+ int z_order,
+ struct dss_context *dss_ctx)
+{
+ int ret = 0;
+ enum hwmem_mem_type memtype;
+ enum hwmem_access access;
+ struct hwmem_mem_chunk mem_chunk;
+ size_t mem_chunk_length = 1;
+ struct hwmem_region rgn = { .offset = 0, .count = 1, .start = 0 };
+ struct mcde_overlay_info info;
+
+ if (img->buf.type == COMPDEV_PTR_HWMEM_BUF_NAME_OFFSET) {
+ buffer->type = COMPDEV_PTR_HWMEM_BUF_NAME_OFFSET;
+ buffer->alloc = hwmem_resolve_by_name(img->buf.hwmem_buf_name);
+ if (IS_ERR(buffer->alloc)) {
+ ret = PTR_ERR(buffer->alloc);
+ dev_warn(dss_ctx->dev,
+ "HWMEM resolve failed, %d\n", ret);
+ goto resolve_failed;
+ }
+
+ hwmem_get_info(buffer->alloc, &buffer->size, &memtype,
+ &access);
+
+ if (!(access & HWMEM_ACCESS_READ) ||
+ memtype != HWMEM_MEM_CONTIGUOUS_SYS) {
+ ret = -EACCES;
+ dev_warn(dss_ctx->dev,
+ "Invalid_mem overlay, %d\n", ret);
+ goto invalid_mem;
+ }
+ ret = hwmem_pin(buffer->alloc, &mem_chunk, &mem_chunk_length);
+ if (ret) {
+ dev_warn(dss_ctx->dev,
+ "Pin failed, %d\n", ret);
+ goto pin_failed;
+ }
+
+ rgn.size = rgn.end = buffer->size;
+ ret = hwmem_set_domain(buffer->alloc, HWMEM_ACCESS_READ,
+ HWMEM_DOMAIN_SYNC, &rgn);
+ if (ret)
+ dev_warn(dss_ctx->dev,
+ "Set domain failed, %d\n", ret);
+
+ buffer->paddr = mem_chunk.paddr;
+ } else if (img->buf.type == COMPDEV_PTR_PHYSICAL) {
+ buffer->type = COMPDEV_PTR_PHYSICAL;
+ buffer->alloc = NULL;
+ buffer->size = img->buf.len;
+ buffer->paddr = img->buf.offset;
+ }
+
+ info.stride = img->pitch;
+ info.fmt = get_ovly_fmt(img->fmt);
+ info.src_x = 0;
+ info.src_y = 0;
+ info.dst_x = img->dst_rect.x;
+ info.dst_y = img->dst_rect.y;
+ info.dst_z = z_order;
+ info.w = img->dst_rect.width;
+ info.h = img->dst_rect.height;
+ info.dirty.x = 0;
+ info.dirty.y = 0;
+ info.dirty.w = img->dst_rect.width;
+ info.dirty.h = img->dst_rect.height;
+ info.paddr = buffer->paddr;
+
+ mcde_dss_apply_overlay(ovly, &info);
+ return ret;
+
+pin_failed:
+invalid_mem:
+ buffer->alloc = NULL;
+ buffer->size = 0;
+ buffer->paddr = 0;
+
+resolve_failed:
+ return ret;
+}
+
+static int compdev_update_rotation(struct dss_context *dss_ctx,
+ enum compdev_rotation rotation)
+{
+ /* Set video mode */
+ struct mcde_video_mode vmode;
+ int ret = 0;
+
+ memset(&vmode, 0, sizeof(struct mcde_video_mode));
+ mcde_dss_get_video_mode(dss_ctx->ddev, &vmode);
+ if ((dss_ctx->display_rotation + rotation) % 180) {
+ vmode.xres = dss_ctx->phy_size.height;
+ vmode.yres = dss_ctx->phy_size.width;
+ } else {
+ vmode.xres = dss_ctx->phy_size.width;
+ vmode.yres = dss_ctx->phy_size.height;
+ }
+
+ /* Set rotation */
+ ret = mcde_dss_set_rotation(dss_ctx->ddev,
+ (dss_ctx->display_rotation + rotation) % 360);
+ if (ret != 0)
+ goto exit;
+
+ ret = mcde_dss_set_video_mode(dss_ctx->ddev, &vmode);
+ if (ret != 0)
+ goto exit;
+
+
+ /* Apply */
+ ret = mcde_dss_apply_channel(dss_ctx->ddev);
+exit:
+ return ret;
+}
+
+static int release_prev_frame(struct dss_context *dss_ctx)
+{
+ int ret = 0;
+ int i;
+
+ /* Handle unpin of previous buffers */
+ for (i = 0; i < NUM_COMPDEV_BUFS; i++) {
+ if (dss_ctx->ovly_buffer[i].type ==
+ COMPDEV_PTR_HWMEM_BUF_NAME_OFFSET &&
+ dss_ctx->ovly_buffer[i].paddr != 0) {
+ hwmem_unpin(dss_ctx->ovly_buffer[i].alloc);
+ hwmem_release(dss_ctx->ovly_buffer[i].alloc);
+ }
+ dss_ctx->ovly_buffer[i].alloc = NULL;
+ dss_ctx->ovly_buffer[i].size = 0;
+ dss_ctx->ovly_buffer[i].paddr = 0;
+ }
+ return ret;
+
+}
+
+static enum b2r2_blt_fmt compdev_to_blt_format(enum compdev_fmt fmt)
+{
+ switch (fmt) {
+ case COMPDEV_FMT_RGBA8888:
+ return B2R2_BLT_FMT_32_BIT_ABGR8888;
+ case COMPDEV_FMT_RGB888:
+ return B2R2_BLT_FMT_24_BIT_RGB888;
+ case COMPDEV_FMT_RGB565:
+ return B2R2_BLT_FMT_16_BIT_RGB565;
+ case COMPDEV_FMT_YUV422:
+ return B2R2_BLT_FMT_CB_Y_CR_Y;
+ case COMPDEV_FMT_YCBCR42XMBN:
+ return B2R2_BLT_FMT_YUV420_PACKED_SEMIPLANAR_MB_STE;
+ case COMPDEV_FMT_YUV420_SP:
+ return B2R2_BLT_FMT_YUV420_PACKED_SEMI_PLANAR;
+ case COMPDEV_FMT_YVU420_SP:
+ return B2R2_BLT_FMT_YVU420_PACKED_SEMI_PLANAR;
+ case COMPDEV_FMT_YUV420_P:
+ return B2R2_BLT_FMT_YUV420_PACKED_PLANAR;
+ default:
+ return B2R2_BLT_FMT_UNUSED;
+ }
+}
+
+static enum b2r2_blt_transform to_blt_transform
+ (enum compdev_rotation compdev_rot)
+{
+ switch (compdev_rot) {
+ case COMPDEV_ROT_0:
+ return B2R2_BLT_TRANSFORM_NONE;
+ case COMPDEV_ROT_90_CCW:
+ return B2R2_BLT_TRANSFORM_CCW_ROT_90;
+ case COMPDEV_ROT_180:
+ return B2R2_BLT_TRANSFORM_CCW_ROT_180;
+ case COMPDEV_ROT_270_CCW:
+ return B2R2_BLT_TRANSFORM_CCW_ROT_90;
+ default:
+ return B2R2_BLT_TRANSFORM_NONE;
+ }
+}
+
+static u32 get_stride(u32 width, enum compdev_fmt fmt)
+{
+ u32 stride = 0;
+ switch (fmt) {
+ case COMPDEV_FMT_RGB565:
+ stride = width * 2;
+ break;
+ case COMPDEV_FMT_RGB888:
+ stride = width * 3;
+ break;
+ case COMPDEV_FMT_RGBX8888:
+ stride = width * 4;
+ break;
+ case COMPDEV_FMT_RGBA8888:
+ stride = width * 4;
+ break;
+ case COMPDEV_FMT_YUV422:
+ stride = width * 2;
+ break;
+ case COMPDEV_FMT_YCBCR42XMBN:
+ case COMPDEV_FMT_YUV420_SP:
+ case COMPDEV_FMT_YVU420_SP:
+ case COMPDEV_FMT_YUV420_P:
+ stride = width;
+ break;
+ }
+
+ /* The display controller requires 8 byte aligned strides */
+ if (stride % 8)
+ stride += 8 - (stride % 8);
+
+ return stride;
+}
+
+static int alloc_comp_internal_img(enum compdev_fmt fmt,
+ u16 width, u16 height, struct compdev_img_internal **img_pp)
+{
+ struct hwmem_alloc *alloc;
+ int name;
+ u32 size;
+ u32 stride;
+ struct compdev_img_internal *img;
+
+ stride = get_stride(width, fmt);
+ size = stride * height;
+ size = PAGE_ALIGN(size);
+
+ img = kzalloc(sizeof(struct compdev_img_internal), GFP_KERNEL);
+
+ if (!img)
+ return -ENOMEM;
+
+ alloc = hwmem_alloc(size, HWMEM_ALLOC_HINT_WRITE_COMBINE |
+ HWMEM_ALLOC_HINT_UNCACHED,
+ (HWMEM_ACCESS_READ | HWMEM_ACCESS_WRITE |
+ HWMEM_ACCESS_IMPORT),
+ HWMEM_MEM_CONTIGUOUS_SYS);
+
+ if (IS_ERR(alloc)) {
+ kfree(img);
+ img = NULL;
+ return PTR_ERR(alloc);
+ }
+
+ name = hwmem_get_name(alloc);
+ if (name < 0) {
+ kfree(img);
+ img = NULL;
+ hwmem_release(alloc);
+ return name;
+ }
+
+ img->img.height = height;
+ img->img.width = width;
+ img->img.fmt = fmt;
+ img->img.pitch = stride;
+ img->img.buf.hwmem_buf_name = name;
+ img->img.buf.type = COMPDEV_PTR_HWMEM_BUF_NAME_OFFSET;
+ img->img.buf.offset = 0;
+ img->img.buf.len = size;
+
+ img->ref_count = 1;
+
+ *img_pp = img;
+
+ return 0;
+}
+
+static void free_comp_img_buf(struct compdev_img_internal *img,
+ struct device *dev)
+{
+ dev_dbg(dev, "%s\n", __func__);
+
+ if (img != NULL && img->ref_count) {
+ img->ref_count--;
+ if (img->ref_count == 0) {
+ struct hwmem_alloc *alloc;
+ if (img->img.buf.hwmem_buf_name > 0) {
+ alloc = hwmem_resolve_by_name(
+ img->img.buf.hwmem_buf_name);
+ if (IS_ERR(alloc)) {
+ dev_err(dev, "%s: Error getting Alloc "
+ "from HWMEM\n", __func__);
+ return;
+ }
+ /* Double release needed */
+ hwmem_release(alloc);
+ hwmem_release(alloc);
+ }
+ kfree(img);
+ }
+ }
+}
+
+struct compdev_img_internal *compdev_buffer_cache_get_image(
+ struct buffer_cache_context *cache_ctx, enum compdev_fmt fmt,
+ u16 width, u16 height)
+{
+ int i;
+ struct compdev_img_internal *img = NULL;
+
+ dev_dbg(cache_ctx->dev, "%s\n", __func__);
+
+ /* First check for a cache hit */
+ if (cache_ctx->unused_counter > 0) {
+ u8 active_index = cache_ctx->index;
+ struct compdev_img_internal *temp =
+ cache_ctx->img[active_index];
+ if (temp != NULL && temp->img.fmt == fmt &&
+ temp->img.width == width &&
+ temp->img.height == height) {
+ img = temp;
+ cache_ctx->unused_counter = 0;
+ }
+ }
+ /* Check if there was a cache hit */
+ if (img == NULL) {
+ /* Create new buffers and release old */
+ for (i = 0; i < BUFFER_CACHE_DEPTH; i++) {
+ if (cache_ctx->img[i]) {
+ free_comp_img_buf(cache_ctx->img[i],
+ cache_ctx->dev);
+ cache_ctx->img[i] = NULL;
+ }
+ cache_ctx->index = 0;
+ if (alloc_comp_internal_img(fmt, width, height,
+ &cache_ctx->img[i]))
+ dev_err(cache_ctx->dev,
+ "%s: Allocation error\n",
+ __func__);
+ }
+ img = cache_ctx->img[0];
+ }
+
+ if (img != NULL) {
+ img->ref_count++;
+ cache_ctx->unused_counter = 0;
+ cache_ctx->index++;
+ if (cache_ctx->index >= BUFFER_CACHE_DEPTH)
+ cache_ctx->index = 0;
+ }
+
+ return img;
+}
+
+static void compdev_buffer_cache_mark_frame
+ (struct buffer_cache_context *cache_ctx)
+{
+ if (cache_ctx->unused_counter < 2)
+ cache_ctx->unused_counter++;
+ if (cache_ctx->unused_counter == 2) {
+ int i;
+ for (i = 0; i < BUFFER_CACHE_DEPTH; i++) {
+ if (cache_ctx->img[i]) {
+ free_comp_img_buf(cache_ctx->img[i],
+ cache_ctx->dev);
+ cache_ctx->img[i] = NULL;
+ }
+ }
+ }
+}
+
+static bool check_hw_format(enum compdev_fmt fmt)
+{
+ if (fmt == COMPDEV_FMT_RGB565 ||
+ fmt == COMPDEV_FMT_RGB888 ||
+ fmt == COMPDEV_FMT_RGBA8888 ||
+ fmt == COMPDEV_FMT_RGBX8888 ||
+ fmt == COMPDEV_FMT_YUV422)
+ return true;
+ else
+ return false;
+}
+
+static enum compdev_fmt find_compatible_fmt(enum compdev_fmt fmt, bool rotation)
+{
+ if (!rotation) {
+ switch (fmt) {
+ case COMPDEV_FMT_RGB565:
+ case COMPDEV_FMT_RGB888:
+ case COMPDEV_FMT_RGBA8888:
+ case COMPDEV_FMT_RGBX8888:
+ return fmt;
+ case COMPDEV_FMT_YUV422:
+ case COMPDEV_FMT_YCBCR42XMBN:
+ case COMPDEV_FMT_YUV420_SP:
+ case COMPDEV_FMT_YVU420_SP:
+ case COMPDEV_FMT_YUV420_P:
+ return COMPDEV_FMT_YUV422;
+ default:
+ return COMPDEV_FMT_RGBA8888;
+ }
+ } else {
+ switch (fmt) {
+ case COMPDEV_FMT_RGB565:
+ case COMPDEV_FMT_RGB888:
+ case COMPDEV_FMT_RGBA8888:
+ case COMPDEV_FMT_RGBX8888:
+ return fmt;
+ case COMPDEV_FMT_YUV422:
+ case COMPDEV_FMT_YCBCR42XMBN:
+ case COMPDEV_FMT_YUV420_SP:
+ case COMPDEV_FMT_YVU420_SP:
+ case COMPDEV_FMT_YUV420_P:
+ return COMPDEV_FMT_RGB888;
+ default:
+ return COMPDEV_FMT_RGBA8888;
+ }
+ }
+}
+
+static void compdev_callback_worker_function(struct work_struct *work)
+{
+ struct compdev_post_callback_work *cb_work =
+ (struct compdev_post_callback_work *)work;
+
+ if (cb_work->pb_cb != NULL)
+ cb_work->pb_cb(cb_work->cb_data, cb_work->img);
+}
+static void compdev_blt_worker_function(struct work_struct *work)
+{
+ struct compdev_blt_work *blt_work = (struct compdev_blt_work *)work;
+ struct compdev_img *src_img;
+ struct compdev_img *dst_img;
+ struct b2r2_blt_req req;
+ int req_id;
+
+ dev_dbg(blt_work->dev, "%s\n", __func__);
+
+ src_img = blt_work->src_img;
+ dst_img = &blt_work->dst_img->img;
+
+ memset(&req, 0, sizeof(req));
+ req.size = sizeof(req);
+
+ if (src_img->buf.type == COMPDEV_PTR_PHYSICAL) {
+ req.src_img.buf.type = B2R2_BLT_PTR_PHYSICAL;
+ req.src_img.buf.fd = src_img->buf.fd;
+ } else {
+ struct hwmem_alloc *alloc;
+
+ req.src_img.buf.type = B2R2_BLT_PTR_HWMEM_BUF_NAME_OFFSET;
+ req.src_img.buf.hwmem_buf_name = src_img->buf.hwmem_buf_name;
+
+ alloc = hwmem_resolve_by_name(src_img->buf.hwmem_buf_name);
+ if (IS_ERR(alloc)) {
+ dev_warn(blt_work->dev,
+ "HWMEM resolve failed\n");
+ }
+ hwmem_set_access(alloc,
+ HWMEM_ACCESS_READ | HWMEM_ACCESS_IMPORT,
+ task_tgid_nr(current));
+ hwmem_release(alloc);
+ }
+ req.src_img.pitch = src_img->pitch;
+ req.src_img.buf.offset = src_img->buf.offset;
+ req.src_img.buf.len = src_img->buf.len;
+ req.src_img.fmt = compdev_to_blt_format(src_img->fmt);
+ req.src_img.width = src_img->width;
+ req.src_img.height = src_img->height;
+
+ req.src_rect.x = src_img->src_rect.x;
+ req.src_rect.y = src_img->src_rect.y;
+ req.src_rect.width = src_img->src_rect.width;
+ req.src_rect.height = src_img->src_rect.height;
+
+ if (dst_img->buf.type == COMPDEV_PTR_PHYSICAL) {
+ req.dst_img.buf.type = B2R2_BLT_PTR_PHYSICAL;
+ req.dst_img.buf.fd = dst_img->buf.fd;
+ } else {
+ req.dst_img.buf.type = B2R2_BLT_PTR_HWMEM_BUF_NAME_OFFSET;
+ req.dst_img.buf.hwmem_buf_name = dst_img->buf.hwmem_buf_name;
+ }
+ req.dst_img.pitch = dst_img->pitch;
+ req.dst_img.buf.offset = dst_img->buf.offset;
+ req.dst_img.buf.len = dst_img->buf.len;
+ req.dst_img.fmt = compdev_to_blt_format(dst_img->fmt);
+ req.dst_img.width = dst_img->width;
+ req.dst_img.height = dst_img->height;
+
+ if (blt_work->mcde_rotation)
+ req.transform = B2R2_BLT_TRANSFORM_NONE;
+ else
+ req.transform = to_blt_transform(src_img->rotation);
+ req.dst_rect.x = 0;
+ req.dst_rect.y = 0;
+ req.dst_rect.width = src_img->dst_rect.width;
+ req.dst_rect.height = src_img->dst_rect.height;
+
+ req.global_alpha = 0xff;
+ req.flags = B2R2_BLT_FLAG_DITHER;
+
+ req_id = b2r2_blt_request(blt_work->blt_handle, &req);
+
+ if (b2r2_blt_synch(blt_work->blt_handle, req_id) < 0) {
+ dev_err(blt_work->dev,
+ "%s: Could not perform b2r2_blt_synch",
+ __func__);
+ }
+
+ dst_img->src_rect.x = 0;
+ dst_img->src_rect.x = 0;
+ dst_img->src_rect.width = dst_img->width;
+ dst_img->src_rect.height = dst_img->height;
+
+ dst_img->dst_rect.x = src_img->dst_rect.x;
+ dst_img->dst_rect.y = src_img->dst_rect.y;
+ dst_img->dst_rect.width = src_img->dst_rect.width;
+ dst_img->dst_rect.height = src_img->dst_rect.height;
+
+ dst_img->rotation = src_img->rotation;
+}
+
+static int compdev_post_buffer_locked(struct compdev *cd,
+ struct compdev_img *src_img)
+{
+ int ret = 0;
+ int i;
+ bool transform_needed = false;
+ struct compdev_img *resulting_img;
+ struct compdev_blt_work blt_work;
+ struct compdev_post_callback_work cb_work;
+ bool callback_work = false;
+ bool bypass_case = false;
+
+ dev_dbg(cd->dev, "%s\n", __func__);
+
+ /* Free potential temp buffers */
+ for (i = 0; i < cd->dss_ctx.temp_img_count; i++)
+ free_comp_img_buf(cd->dss_ctx.temp_img[i], cd->dev);
+ cd->dss_ctx.temp_img_count = 0;
+
+ /* Check for bypass images */
+ if (src_img->flags & COMPDEV_BYPASS_FLAG)
+ bypass_case = true;
+
+ /* Handle callback */
+ if (cd->pb_cb != NULL) {
+ callback_work = true;
+ INIT_WORK((struct work_struct *)&cb_work,
+ compdev_callback_worker_function);
+ cb_work.img = src_img;
+ cb_work.pb_cb = cd->pb_cb;
+ cb_work.cb_data = cd->cb_data;
+ cb_work.dev = cd->dev;
+ queue_work(cd->worker_thread, (struct work_struct *)&cb_work);
+ }
+
+ if (!bypass_case) {
+ /* Determine if transform is needed */
+ /* First check scaling */
+ if ((src_img->rotation == COMPDEV_ROT_0 ||
+ src_img->rotation == COMPDEV_ROT_180) &&
+ (src_img->src_rect.width != src_img->dst_rect.width ||
+ src_img->src_rect.height != src_img->dst_rect.height))
+ transform_needed = true;
+ else if ((src_img->rotation == COMPDEV_ROT_90_CCW ||
+ src_img->rotation == COMPDEV_ROT_270_CCW) &&
+ (src_img->src_rect.width != src_img->dst_rect.height ||
+ src_img->src_rect.height != src_img->dst_rect.width))
+ transform_needed = true;
+
+ if (!transform_needed && check_hw_format(src_img->fmt) == false)
+ transform_needed = true;
+
+ if (transform_needed) {
+ u16 width = 0;
+ u16 height = 0;
+ enum compdev_fmt fmt;
+
+ INIT_WORK((struct work_struct *)&blt_work,
+ compdev_blt_worker_function);
+
+ if (cd->dss_ctx.blt_handle == 0) {
+ dev_dbg(cd->dev, "%s: B2R2 opened\n", __func__);
+ cd->dss_ctx.blt_handle = b2r2_blt_open();
+ if (cd->dss_ctx.blt_handle < 0) {
+ dev_warn(cd->dev,
+ "%s(%d): Failed to "
+ "open b2r2 device\n",
+ __func__, __LINE__);
+ }
+ }
+ blt_work.blt_handle = cd->dss_ctx.blt_handle;
+ blt_work.src_img = src_img;
+ blt_work.mcde_rotation = cd->mcde_rotation;
+
+ width = src_img->dst_rect.width;
+ height = src_img->dst_rect.height;
+
+ fmt = find_compatible_fmt(src_img->fmt,
+ (!cd->mcde_rotation) &&
+ (src_img->rotation != COMPDEV_ROT_0));
+
+ blt_work.dst_img = compdev_buffer_cache_get_image
+ (&cd->dss_ctx.cache_ctx,
+ fmt, width, height);
+
+ blt_work.dst_img->img.flags = src_img->flags;
+ blt_work.dev = cd->dev;
+
+ queue_work(cd->worker_thread,
+ (struct work_struct *)&blt_work);
+ flush_work_sync((struct work_struct *)&blt_work);
+
+ resulting_img = &blt_work.dst_img->img;
+
+ cd->dss_ctx.temp_img[cd->dss_ctx.temp_img_count] =
+ blt_work.dst_img;
+ cd->dss_ctx.temp_img_count++;
+
+ } else {
+ resulting_img = src_img;
+ }
+
+ if (!cd->mcde_rotation)
+ resulting_img->rotation = COMPDEV_ROT_0;
+
+ cd->images[cd->image_count] = resulting_img;
+ cd->image_count++;
+
+ /* make sure that a potential callback has returned */
+ if (callback_work)
+ flush_work_sync((struct work_struct *)&cb_work);
+
+ if (cd->sync_count > 1) {
+ cd->sync_count--;
+ mutex_unlock(&cd->lock);
+ /* Wait for fence */
+ wait_for_completion(&cd->fence);
+ mutex_lock(&cd->lock);
+ } else {
+ struct compdev_img *img1 = NULL;
+ struct compdev_img *img2 = NULL;
+
+ if (cd->sync_count)
+ cd->sync_count--;
+
+ img1 = cd->images[0];
+ if (cd->image_count)
+ img2 = cd->images[1];
+
+ /* Do the refresh */
+ compdev_post_buffers_dss(&cd->dss_ctx, img1, img2);
+ compdev_buffer_cache_mark_frame
+ (&cd->dss_ctx.cache_ctx);
+
+ if (cd->s_info.img_count > 1) {
+ /* Releasing fence */
+ complete(&cd->fence);
+ }
+
+ cd->sync_count = 0;
+ cd->image_count = 0;
+ cd->images[0] = NULL;
+ cd->images[1] = NULL;
+ }
+ } else {
+ /* make sure that a potential callback has returned */
+ if (callback_work)
+ flush_work_sync((struct work_struct *)&cb_work);
+ }
+
+ return ret;
+}
+
+static int compdev_post_buffers_dss(struct dss_context *dss_ctx,
+ struct compdev_img *img1, struct compdev_img *img2)
+{
+ int ret = 0;
+ int i = 0;
+
+ struct compdev_img *fb_img = NULL;
+ struct compdev_img *ovly_img = NULL;
+
+ /* Unpin the previous frame */
+ release_prev_frame(dss_ctx);
+
+ /* Set channel rotation */
+ if (img1 != NULL &&
+ (dss_ctx->current_buffer_rotation != img1->rotation)) {
+ if (compdev_update_rotation(dss_ctx, img1->rotation) != 0)
+ dev_warn(dss_ctx->dev,
+ "Failed to update MCDE rotation "
+ "(img1->rotation = %d), %d\n",
+ img1->rotation, ret);
+ else
+ dss_ctx->current_buffer_rotation = img1->rotation;
+ }
+
+ if ((img1 != NULL) && (img1->flags & COMPDEV_OVERLAY_FLAG))
+ ovly_img = img1;
+ else if (img1 != NULL)
+ fb_img = img1;
+
+
+ if ((img2 != NULL) && (img2->flags & COMPDEV_OVERLAY_FLAG))
+ ovly_img = img2;
+ else if (img2 != NULL)
+ fb_img = img2;
+
+ /* Handle buffers */
+ if (fb_img != NULL) {
+ ret = compdev_setup_ovly(fb_img,
+ &dss_ctx->ovly_buffer[i], dss_ctx->ovly[0], 1, dss_ctx);
+ if (ret)
+ dev_warn(dss_ctx->dev,
+ "Failed to setup overlay[%d], %d\n", 0, ret);
+ i++;
+ } else {
+ disable_overlay(dss_ctx->ovly[0]);
+ }
+
+
+ if (ovly_img != NULL) {
+ ret = compdev_setup_ovly(ovly_img,
+ &dss_ctx->ovly_buffer[i], dss_ctx->ovly[1], 0, dss_ctx);
+ if (ret)
+ dev_warn(dss_ctx->dev,
+ "Failed to setup overlay[%d], %d\n", 1, ret);
+ } else {
+ disable_overlay(dss_ctx->ovly[1]);
+ }
+
+ /* Do the display update */
+ mcde_dss_update_overlay(dss_ctx->ovly[0], true);
+
+ return ret;
+}
+
+static int compdev_post_scene_info_locked(struct compdev *cd,
+ struct compdev_scene_info *s_info)
+{
+ int ret = 0;
+
+ dev_dbg(cd->dev, "%s\n", __func__);
+
+ cd->s_info = *s_info;
+ cd->sync_count = cd->s_info.img_count;
+
+ /* always complete the fence in case someone is hanging incorrectly. */
+ complete(&cd->fence);
+ init_completion(&cd->fence);
+
+ /* Handle callback */
+ if (cd->si_cb != NULL) {
+ mutex_unlock(&cd->lock);
+ cd->si_cb(cd->cb_data, s_info);
+ mutex_lock(&cd->lock);
+ }
+ return ret;
+}
+
+
+static int compdev_get_size_locked(struct dss_context *dss_ctx,
+ struct compdev_size *size)
+{
+ int ret = 0;
+ if ((dss_ctx->display_rotation) % 180) {
+ size->height = dss_ctx->phy_size.width;
+ size->width = dss_ctx->phy_size.height;
+ } else {
+ size->height = dss_ctx->phy_size.height;
+ size->width = dss_ctx->phy_size.width;
+ }
+
+ return ret;
+}
+
+static int compdev_get_listener_state_locked(struct compdev *cd,
+ enum compdev_listener_state *state)
+{
+ int ret = 0;
+
+ *state = COMPDEV_LISTENER_OFF;
+ if (cd->pb_cb != NULL)
+ *state = COMPDEV_LISTENER_ON;
+ return ret;
+}
+
+static long compdev_ioctl(struct file *file,
+ unsigned int cmd,
+ unsigned long arg)
+{
+ int ret;
+ struct compdev *cd = (struct compdev *)file->private_data;
+ struct compdev_img img;
+ struct compdev_scene_info s_info;
+
+ mutex_lock(&cd->lock);
+
+ switch (cmd) {
+ case COMPDEV_GET_SIZE_IOC:
+ {
+ struct compdev_size tmp;
+ compdev_get_size_locked(&cd->dss_ctx, &tmp);
+ ret = copy_to_user((void __user *)arg, &tmp,
+ sizeof(tmp));
+ if (ret)
+ ret = -EFAULT;
+ }
+ break;
+ case COMPDEV_GET_LISTENER_STATE_IOC:
+ {
+ enum compdev_listener_state state;
+ compdev_get_listener_state_locked(cd, &state);
+ ret = copy_to_user((void __user *)arg, &state,
+ sizeof(state));
+ if (ret)
+ ret = -EFAULT;
+ }
+ break;
+ case COMPDEV_POST_BUFFER_IOC:
+ memset(&img, 0, sizeof(img));
+ /* Get the user data */
+ if (copy_from_user(&img, (void *)arg, sizeof(img))) {
+ dev_warn(cd->dev,
+ "%s: copy_from_user failed\n",
+ __func__);
+ mutex_unlock(&cd->lock);
+ return -EFAULT;
+ }
+ ret = compdev_post_buffer_locked(cd, &img);
+
+ break;
+ case COMPDEV_POST_SCENE_INFO_IOC:
+ memset(&s_info, 0, sizeof(s_info));
+ /* Get the user data */
+ if (copy_from_user(&s_info, (void *)arg, sizeof(s_info))) {
+ dev_warn(cd->dev,
+ "%s: copy_from_user failed\n",
+ __func__);
+ mutex_unlock(&cd->lock);
+ return -EFAULT;
+ }
+ ret = compdev_post_scene_info_locked(cd, &s_info);
+
+ break;
+
+ default:
+ ret = -ENOSYS;
+ }
+
+ mutex_unlock(&cd->lock);
+
+ return ret;
+}
+
+static const struct file_operations compdev_fops = {
+ .open = compdev_open,
+ .release = compdev_release,
+ .unlocked_ioctl = compdev_ioctl,
+};
+
+static void init_compdev(struct compdev *cd, const char *name)
+{
+ mutex_init(&cd->lock);
+ INIT_LIST_HEAD(&cd->list);
+ init_completion(&cd->fence);
+
+ cd->mdev.minor = MISC_DYNAMIC_MINOR;
+ cd->mdev.name = name;
+ cd->mdev.fops = &compdev_fops;
+ cd->dev = cd->mdev.this_device;
+}
+
+static void init_dss_context(struct dss_context *dss_ctx,
+ struct mcde_display_device *ddev, struct compdev *cd)
+{
+ dss_ctx->ddev = ddev;
+ dss_ctx->dev = cd->dev;
+ memset(&dss_ctx->cache_ctx, 0, sizeof(struct buffer_cache_context));
+ dss_ctx->cache_ctx.dev = dss_ctx->dev;
+}
+
+int compdev_create(struct mcde_display_device *ddev,
+ struct mcde_overlay *parent_ovly, bool mcde_rotation)
+{
+ int ret = 0;
+ int i;
+ struct compdev *cd;
+ struct mcde_video_mode vmode;
+ struct mcde_overlay_info info;
+
+ char name[10];
+
+ if (dev_counter == 0) {
+ for (i = 0; i < MAX_NBR_OF_COMPDEVS; i++)
+ compdevs[i] = NULL;
+ }
+
+ if (dev_counter > MAX_NBR_OF_COMPDEVS)
+ return -ENOMEM;
+
+ cd = kzalloc(sizeof(struct compdev), GFP_KERNEL);
+ if (!cd)
+ return -ENOMEM;
+
+ compdevs[dev_counter] = cd;
+ cd->dev_index = dev_counter;
+
+ snprintf(name, sizeof(name), "%s%d", COMPDEV_DEFAULT_DEVICE_PREFIX,
+ dev_counter++);
+ init_compdev(cd, name);
+
+ init_dss_context(&cd->dss_ctx, ddev, cd);
+
+ mcde_dss_get_video_mode(ddev, &vmode);
+
+ cd->worker_thread = create_workqueue(name);
+ if (!cd->worker_thread) {
+ ret = -ENOMEM;
+ goto fail_workqueue;
+ }
+
+ cd->dss_ctx.ovly[0] = parent_ovly;
+ if (!cd->dss_ctx.ovly[0]) {
+ ret = -ENOMEM;
+ goto fail_create_ovly;
+ }
+
+ for (i = 1; i < NUM_COMPDEV_BUFS; i++) {
+ cd->dss_ctx.ovly[i] = mcde_dss_create_overlay(ddev, &info);
+ if (!cd->dss_ctx.ovly[i]) {
+ ret = -ENOMEM;
+ goto fail_create_ovly;
+ }
+ if (mcde_dss_enable_overlay(cd->dss_ctx.ovly[i]))
+ goto fail_create_ovly;
+ if (disable_overlay(cd->dss_ctx.ovly[i]))
+ goto fail_create_ovly;
+ }
+
+ mcde_dss_get_native_resolution(ddev, &cd->dss_ctx.phy_size.width,
+ &cd->dss_ctx.phy_size.height);
+ cd->dss_ctx.display_rotation = mcde_dss_get_rotation(ddev);
+ cd->dss_ctx.current_buffer_rotation = 0;
+
+ cd->mcde_rotation = mcde_rotation;
+
+ ret = misc_register(&cd->mdev);
+ if (ret)
+ goto fail_register_misc;
+ mutex_lock(&dev_list_lock);
+ list_add_tail(&cd->list, &dev_list);
+ mutex_unlock(&dev_list_lock);
+
+ goto out;
+
+fail_register_misc:
+fail_create_ovly:
+ for (i = 0; i < NUM_COMPDEV_BUFS; i++) {
+ if (cd->dss_ctx.ovly[i])
+ mcde_dss_destroy_overlay(cd->dss_ctx.ovly[i]);
+ }
+fail_workqueue:
+ kfree(cd);
+out:
+ return ret;
+}
+
+
+int compdev_get(int dev_idx, struct compdev **cd_pp)
+{
+ struct compdev *cd;
+ cd = NULL;
+
+ if (dev_idx >= MAX_NBR_OF_COMPDEVS)
+ return -ENOMEM;
+
+ cd = compdevs[dev_idx];
+ if (cd != NULL) {
+ mutex_lock(&cd->lock);
+ cd->ref_count++;
+ mutex_unlock(&cd->lock);
+ *cd_pp = cd;
+ return 0;
+ } else {
+ return -ENOMEM;
+ }
+}
+EXPORT_SYMBOL(compdev_get);
+
+int compdev_put(struct compdev *cd)
+{
+ int ret = 0;
+ if (cd == NULL)
+ return -ENOMEM;
+
+ mutex_lock(&cd->lock);
+ cd->ref_count--;
+ if (cd->ref_count < 0)
+ dev_warn(cd->dev,
+ "%s: Incorrect ref count\n", __func__);
+ mutex_unlock(&cd->lock);
+ return ret;
+}
+EXPORT_SYMBOL(compdev_put);
+
+int compdev_get_size(struct compdev *cd, struct compdev_size *size)
+{
+ int ret = 0;
+ if (cd == NULL)
+ return -ENOMEM;
+
+ mutex_lock(&cd->lock);
+
+ ret = compdev_get_size_locked(&cd->dss_ctx, size);
+
+ mutex_unlock(&cd->lock);
+ return ret;
+}
+EXPORT_SYMBOL(compdev_get_size);
+
+int compdev_get_listener_state(struct compdev *cd,
+ enum compdev_listener_state *listener_state)
+{
+ int ret = 0;
+ if (cd == NULL)
+ return -ENOMEM;
+
+ mutex_lock(&cd->lock);
+
+ ret = compdev_get_listener_state_locked(cd, listener_state);
+
+ mutex_unlock(&cd->lock);
+ return ret;
+}
+EXPORT_SYMBOL(compdev_get_listener_state);
+
+
+int compdev_post_buffer(struct compdev *cd, struct compdev_img *img)
+{
+ int ret = 0;
+ if (cd == NULL)
+ return -ENOMEM;
+
+ mutex_lock(&cd->lock);
+
+ ret = compdev_post_buffer_locked(cd, img);
+
+ mutex_unlock(&cd->lock);
+ return ret;
+}
+EXPORT_SYMBOL(compdev_post_buffer);
+
+int compdev_post_scene_info(struct compdev *cd,
+ struct compdev_scene_info *s_info)
+{
+ int ret = 0;
+ if (cd == NULL)
+ return -ENOMEM;
+
+ mutex_lock(&cd->lock);
+
+ ret = compdev_post_scene_info_locked(cd, s_info);
+
+ mutex_unlock(&cd->lock);
+ return ret;
+}
+EXPORT_SYMBOL(compdev_post_scene_info);
+
+int compdev_register_listener_callbacks(struct compdev *cd, void *data,
+ post_buffer_callback pb_cb, post_scene_info_callback si_cb)
+{
+ int ret = 0;
+ if (cd == NULL)
+ return -ENOMEM;
+ mutex_lock(&cd->lock);
+ cd->cb_data = data;
+ cd->pb_cb = pb_cb;
+ cd->si_cb = si_cb;
+ mutex_unlock(&cd->lock);
+ return ret;
+}
+EXPORT_SYMBOL(compdev_register_listener_callbacks);
+
+int compdev_deregister_callbacks(struct compdev *cd)
+{
+ int ret = 0;
+ if (cd == NULL)
+ return -ENOMEM;
+ mutex_lock(&cd->lock);
+ cd->cb_data = NULL;
+ cd->pb_cb = NULL;
+ cd->si_cb = NULL;
+ mutex_unlock(&cd->lock);
+ return ret;
+}
+EXPORT_SYMBOL(compdev_deregister_callbacks);
+
+void compdev_destroy(struct mcde_display_device *ddev)
+{
+ struct compdev *cd;
+ struct compdev *tmp;
+ int i;
+
+ mutex_lock(&dev_list_lock);
+ list_for_each_entry_safe(cd, tmp, &dev_list, list) {
+ if (cd->dss_ctx.ddev == ddev) {
+ list_del(&cd->list);
+ misc_deregister(&cd->mdev);
+ for (i = 1; i < NUM_COMPDEV_BUFS; i++)
+ mcde_dss_destroy_overlay(cd->dss_ctx.ovly[i]);
+ b2r2_blt_close(cd->dss_ctx.blt_handle);
+
+ release_prev_frame(&cd->dss_ctx);
+
+ /* Free potential temp buffers */
+ for (i = 0; i < cd->dss_ctx.temp_img_count; i++)
+ free_comp_img_buf(cd->dss_ctx.temp_img[i],
+ cd->dev);
+
+ for (i = 0; i < BUFFER_CACHE_DEPTH; i++) {
+ if (cd->dss_ctx.cache_ctx.img[i]) {
+ free_comp_img_buf
+ (cd->dss_ctx.cache_ctx.img[i],
+ cd->dev);
+ cd->dss_ctx.cache_ctx.img[i] = NULL;
+ }
+ }
+
+ destroy_workqueue(cd->worker_thread);
+ kfree(cd);
+ break;
+ }
+ }
+ dev_counter--;
+ mutex_unlock(&dev_list_lock);
+}
+
+static void compdev_destroy_all(void)
+{
+ struct compdev *cd;
+ struct compdev *tmp;
+ int i;
+
+ mutex_lock(&dev_list_lock);
+ list_for_each_entry_safe(cd, tmp, &dev_list, list) {
+ list_del(&cd->list);
+ misc_deregister(&cd->mdev);
+ for (i = 0; i < NUM_COMPDEV_BUFS; i++)
+ mcde_dss_destroy_overlay(cd->dss_ctx.ovly[i]);
+
+ release_prev_frame(&cd->dss_ctx);
+ /* Free potential temp buffers */
+ for (i = 0; i < cd->dss_ctx.temp_img_count; i++)
+ free_comp_img_buf(cd->dss_ctx.temp_img[i], cd->dev);
+
+ for (i = 0; i < BUFFER_CACHE_DEPTH; i++) {
+ if (cd->dss_ctx.cache_ctx.img[i]) {
+ free_comp_img_buf
+ (cd->dss_ctx.cache_ctx.img[i],
+ cd->dev);
+ cd->dss_ctx.cache_ctx.img[i] = NULL;
+ }
+ }
+
+ kfree(cd);
+ }
+ mutex_unlock(&dev_list_lock);
+
+ mutex_destroy(&dev_list_lock);
+}
+
+static int __init compdev_init(void)
+{
+ pr_info("%s\n", __func__);
+
+ mutex_init(&dev_list_lock);
+
+ return 0;
+}
+module_init(compdev_init);
+
+static void __exit compdev_exit(void)
+{
+ compdev_destroy_all();
+ pr_info("%s\n", __func__);
+}
+module_exit(compdev_exit);
+
+MODULE_AUTHOR("Anders Bauer <anders.bauer@stericsson.com>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Display overlay device driver");
+
diff --git a/drivers/misc/dispdev/Makefile b/drivers/misc/dispdev/Makefile
new file mode 100644
index 00000000000..11dc7611d26
--- /dev/null
+++ b/drivers/misc/dispdev/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_DISPDEV) += dispdev.o
diff --git a/drivers/misc/dispdev/dispdev.c b/drivers/misc/dispdev/dispdev.c
new file mode 100644
index 00000000000..5413a252d35
--- /dev/null
+++ b/drivers/misc/dispdev/dispdev.c
@@ -0,0 +1,659 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2011
+ *
+ * Display output device driver
+ *
+ * Author: Marcus Lorentzon <marcus.xm.lorentzon@stericsson.com>
+ * for ST-Ericsson.
+ *
+ * License terms: GNU General Public License (GPL), version 2.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/idr.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/miscdevice.h>
+#include <linux/uaccess.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/wait.h>
+#include <linux/sched.h>
+#include <linux/ioctl.h>
+
+#include <linux/dispdev.h>
+#include <linux/hwmem.h>
+#include <video/mcde_dss.h>
+
+#define DENSITY_CHECK (16)
+#define MAX_BUFFERS 4
+
+static LIST_HEAD(dev_list);
+static DEFINE_MUTEX(dev_list_lock);
+
+enum buffer_state {
+ BUF_UNUSED = 0,
+ BUF_QUEUED,
+ BUF_ACTIVATED,
+/*TODO:waitfordone BUF_DEACTIVATED,*/
+ BUF_FREE,
+ BUF_DEQUEUED,
+};
+
+struct dispdev_buffer {
+ struct hwmem_alloc *alloc;
+ u32 size;
+ enum buffer_state state;
+ u32 paddr; /* if pinned */
+};
+
+struct dispdev {
+ bool open;
+ struct mutex lock;
+ struct miscdevice mdev;
+ struct list_head list;
+ struct mcde_display_device *ddev;
+ struct mcde_overlay *ovly;
+ struct mcde_overlay *parent_ovly;
+ struct dispdev_config config;
+ bool overlay;
+ struct dispdev_buffer buffers[MAX_BUFFERS];
+ wait_queue_head_t waitq_dq;
+ /*
+ * For the rotation use case
+ * buffers_need_update is used to ensure that a set_config that
+ * changes width or height is followed by a unregister_buffer.
+ */
+ bool buffers_need_update;
+ /*
+ * For the overlay startup use case.
+ * first_update is used to handle the first update after a set_config.
+ * In this case a queue_buffer will arrive after set_config and not a
+ * unregister_buffer as in the rotation use case.
+ */
+ bool first_update;
+ char name[sizeof(DISPDEV_DEFAULT_DEVICE_PREFIX) + 3];
+};
+
+static int find_buf(struct dispdev *dd, enum buffer_state state)
+{
+ int i;
+ for (i = 0; i < MAX_BUFFERS; i++)
+ if (dd->buffers[i].state == state)
+ return i;
+ return -1;
+}
+
+int dispdev_open(struct inode *inode, struct file *file)
+{
+ int ret;
+ struct dispdev *dd = NULL;
+
+ mutex_lock(&dev_list_lock);
+ list_for_each_entry(dd, &dev_list, list)
+ if (dd->mdev.minor == iminor(inode))
+ break;
+
+ if (&dd->list == &dev_list) {
+ mutex_unlock(&dev_list_lock);
+ return -ENODEV;
+ }
+
+ if (dd->open) {
+ mutex_unlock(&dev_list_lock);
+ return -EBUSY;
+ }
+
+ dd->open = true;
+
+ mutex_unlock(&dev_list_lock);
+
+ ret = mcde_dss_enable_overlay(dd->ovly);
+ if (ret)
+ return ret;
+
+ file->private_data = dd;
+
+ return 0;
+}
+
+int dispdev_release(struct inode *inode, struct file *file)
+{
+ int i;
+ struct dispdev *dd = NULL;
+
+ mutex_lock(&dev_list_lock);
+ list_for_each_entry(dd, &dev_list, list)
+ if (dd->mdev.minor == iminor(inode))
+ break;
+ mutex_unlock(&dev_list_lock);
+
+ if (&dd->list == &dev_list)
+ return -ENODEV;
+
+ /* TODO: Make sure it waits for completion */
+ mcde_dss_disable_overlay(dd->ovly);
+ for (i = 0; i < MAX_BUFFERS; i++) {
+ if (dd->buffers[i].paddr)
+ hwmem_unpin(dd->buffers[i].alloc);
+ if (dd->buffers[i].alloc)
+ hwmem_release(dd->buffers[i].alloc);
+ dd->buffers[i].alloc = NULL;
+ dd->buffers[i].state = BUF_UNUSED;
+ dd->buffers[i].size = 0;
+ dd->buffers[i].paddr = 0;
+ }
+ dd->open = false;
+ wake_up(&dd->waitq_dq);
+ return 0;
+}
+
+static enum mcde_ovly_pix_fmt get_ovly_fmt(enum dispdev_fmt fmt)
+{
+ switch (fmt) {
+ default:
+ case DISPDEV_FMT_RGB565:
+ return MCDE_OVLYPIXFMT_RGB565;
+ case DISPDEV_FMT_RGB888:
+ return MCDE_OVLYPIXFMT_RGB888;
+ case DISPDEV_FMT_RGBA8888:
+ return MCDE_OVLYPIXFMT_RGBA8888;
+ case DISPDEV_FMT_RGBX8888:
+ return MCDE_OVLYPIXFMT_RGBX8888;
+ case DISPDEV_FMT_YUV422:
+ return MCDE_OVLYPIXFMT_YCbCr422;
+ }
+}
+
+static void get_ovly_info(struct dispdev_config *cfg,
+ struct mcde_video_mode *vmode,
+ struct mcde_overlay_info *info, bool overlay)
+{
+ info->paddr = 0;
+ info->stride = cfg->stride;
+ info->fmt = get_ovly_fmt(cfg->format);
+ info->src_x = 0;
+ info->src_y = 0;
+ info->dst_x = cfg->x;
+ info->dst_y = cfg->y;
+ info->dst_z = cfg->z;
+ info->w = cfg->width;
+ info->h = cfg->height;
+ info->dirty.x = 0;
+ info->dirty.y = 0;
+ info->dirty.w = vmode->xres;
+ info->dirty.h = vmode->yres;
+}
+
+static int dispdev_set_config(struct dispdev *dd, struct dispdev_config *cfg)
+{
+ int ret = 0;
+ if (memcmp(&dd->config, cfg, sizeof(struct dispdev_config)) == 0)
+ return 0;
+
+ /*
+ * Only update MCDE if format, stride, width and height
+ * is the same. Otherwise just store the new config and update
+ * MCDE in the next queue buffer. This because the buffer that is
+ * active can be have the wrong format, width ...
+ */
+ if (cfg->format == dd->config.format &&
+ cfg->stride == dd->config.stride &&
+ cfg->width == dd->config.width &&
+ cfg->height == dd->config.height) {
+
+ int buf_index;
+ if (!dd->buffers_need_update) {
+ buf_index = find_buf(dd, BUF_ACTIVATED);
+ if (buf_index >= 0) {
+ struct mcde_overlay_info info;
+ struct dispdev_buffer *buf;
+ struct mcde_video_mode vmode;
+
+ buf = &dd->buffers[buf_index];
+ mcde_dss_get_video_mode(dd->ddev, &vmode);
+ get_ovly_info(cfg, &vmode, &info, dd->overlay);
+ info.paddr = buf->paddr;
+ ret = mcde_dss_apply_overlay(dd->ovly, &info);
+ if (!ret)
+ mcde_dss_update_overlay(dd->ovly,
+ false);
+ }
+ }
+ } else {
+ dd->buffers_need_update = true;
+ }
+
+ dd->config = *cfg;
+
+ return ret;
+}
+
+static int dispdev_register_buffer(struct dispdev *dd, s32 hwmem_name)
+{
+ int ret;
+ struct dispdev_buffer *buf;
+ enum hwmem_mem_type memtype;
+ enum hwmem_access access;
+
+ ret = find_buf(dd, BUF_UNUSED);
+ if (ret < 0)
+ return -ENOMEM;
+ buf = &dd->buffers[ret];
+ buf->alloc = hwmem_resolve_by_name(hwmem_name);
+ if (IS_ERR(buf->alloc)) {
+ ret = PTR_ERR(buf->alloc);
+ goto resolve_failed;
+ }
+
+ hwmem_get_info(buf->alloc, &buf->size, &memtype, &access);
+
+ if (!(access & HWMEM_ACCESS_READ) ||
+ memtype != HWMEM_MEM_CONTIGUOUS_SYS) {
+ ret = -EACCES;
+ goto invalid_mem;
+ }
+
+ buf->state = BUF_FREE;
+ goto out;
+invalid_mem:
+ hwmem_release(buf->alloc);
+resolve_failed:
+out:
+ return ret;
+}
+
+static int dispdev_unregister_buffer(struct dispdev *dd, u32 buf_idx)
+{
+ struct dispdev_buffer *buf = &dd->buffers[buf_idx];
+
+ if (buf_idx >= ARRAY_SIZE(dd->buffers))
+ return -EINVAL;
+
+ if (buf->state == BUF_UNUSED)
+ return -EINVAL;
+
+ if (dd->buffers_need_update)
+ dd->buffers_need_update = false;
+
+ if (buf->state == BUF_ACTIVATED) {
+ /* Disable the overlay */
+ struct mcde_overlay_info info;
+ struct mcde_video_mode vmode;
+ /* TODO Wait for frame done */
+ mcde_dss_get_video_mode(dd->ddev, &vmode);
+ get_ovly_info(&dd->config, &vmode, &info, dd->overlay);
+ mcde_dss_apply_overlay(dd->ovly, &info);
+ mcde_dss_update_overlay(dd->ovly, false);
+ hwmem_unpin(dd->buffers[buf_idx].alloc);
+ }
+
+ hwmem_release(buf->alloc);
+ buf->state = BUF_UNUSED;
+ buf->alloc = NULL;
+ buf->size = 0;
+ buf->paddr = 0;
+ dd->first_update = false;
+
+ return 0;
+}
+
+
+/**
+ * @brief Check if the buffer is transparent or black (ARGB = X000)
+ * Note: Only for ARGB32.
+ * Worst case: a ~full transparent buffer
+ * Results: ~2200us @800Mhz for a WVGA screen, with DENSITY_CHECK=8
+ * ~520us @800Mhz for a WVGA screen, with DENSITY_CHECK=16
+ *
+ * @param w witdh
+ * @param h height
+ * @param addr buffer addr
+ *
+ * @return 1 if the buffer is transparent, else 0
+ */
+static int is_transparent(int w, int h, u32 *addr)
+{
+ int i, j;
+ u32 *c, *next_line;
+ u32 sum;
+
+ next_line = addr;
+ sum = 0;
+
+ /* TODO Optimize me */
+ for (j = 0; j < h; j += DENSITY_CHECK) {
+ c = next_line;
+ for (i = 0; i < w; i += DENSITY_CHECK) {
+ sum += ((*c) & 0x00FFFFFF);
+ c += DENSITY_CHECK;
+ }
+ if (sum)
+ return 0; /* Not "transparent" */
+ next_line += (w * DENSITY_CHECK);
+ }
+
+ return 1; /* "Transparent" */
+}
+
+static int dispdev_queue_buffer(struct dispdev *dd,
+ struct dispdev_buffer_info *buffer)
+{
+ int ret, i;
+ struct mcde_overlay_info info;
+ struct hwmem_mem_chunk mem_chunk;
+ size_t mem_chunk_length = 1;
+ struct hwmem_region rgn = { .offset = 0, .count = 1, .start = 0 };
+ struct hwmem_alloc *alloc;
+ struct mcde_video_mode vmode;
+ u32 buf_idx = buffer->buf_idx;
+
+ if (buf_idx >= ARRAY_SIZE(dd->buffers) ||
+ dd->buffers[buf_idx].state != BUF_DEQUEUED)
+ return -EINVAL;
+
+ alloc = dd->buffers[buf_idx].alloc;
+ mcde_dss_get_video_mode(dd->ddev, &vmode);
+ get_ovly_info(&dd->config, &vmode, &info, dd->overlay);
+ ret = hwmem_pin(alloc, &mem_chunk, &mem_chunk_length);
+ if (ret) {
+ dev_warn(dd->mdev.this_device, "Pin failed, %d\n", ret);
+ return -EINVAL;
+ }
+
+ rgn.size = rgn.end = dd->buffers[buf_idx].size;
+ ret = hwmem_set_domain(alloc, HWMEM_ACCESS_READ,
+ HWMEM_DOMAIN_SYNC, &rgn);
+ if (ret)
+ dev_warn(dd->mdev.this_device, "Set domain failed, %d\n", ret);
+
+ i = find_buf(dd, BUF_ACTIVATED);
+ if (i >= 0) {
+ dd->buffers[i].state = BUF_FREE;
+ wake_up(&dd->waitq_dq);
+ }
+
+ if (!dd->first_update) {
+ dd->first_update = true;
+ dd->buffers_need_update = false;
+ }
+
+ dd->buffers[buf_idx].paddr = mem_chunk.paddr;
+
+ if (buffer->display_update && !dd->buffers_need_update &&
+ dd->config.width == buffer->buf_cfg.width &&
+ dd->config.height == buffer->buf_cfg.height &&
+ dd->config.format == buffer->buf_cfg.format &&
+ dd->config.stride == buffer->buf_cfg.stride) {
+ info.paddr = mem_chunk.paddr;
+ mcde_dss_apply_overlay(dd->ovly, &info);
+ mcde_dss_update_overlay(dd->ovly, false);
+ } else if (buffer->display_update) {
+ dd->buffers_need_update = true;
+ }
+
+ /* Disable the MCDE FB overlay */
+ if ((dd->parent_ovly->state != NULL) &&
+ (dd->ddev->check_transparency)) {
+ dd->ddev->check_transparency--;
+ mcde_dss_get_overlay_info(dd->parent_ovly, &info);
+ if (dd->ddev->check_transparency == 0) {
+ if (is_transparent(info.w, info.h, info.vaddr)) {
+ mcde_dss_disable_overlay(dd->parent_ovly);
+ printk(KERN_INFO "%s Disable overlay\n",
+ __func__);
+ }
+ }
+ }
+
+ dd->buffers[buf_idx].state = BUF_ACTIVATED;
+
+ return 0;
+}
+
+static int dispdev_dequeue_buffer(struct dispdev *dd)
+{
+ int i;
+
+ i = find_buf(dd, BUF_FREE);
+ if (i < 0) {
+ if (find_buf(dd, BUF_ACTIVATED) < 0)
+ return -EINVAL;
+ mutex_unlock(&dd->lock);
+ wait_event(dd->waitq_dq, (i = find_buf(dd, BUF_FREE)) >= 0);
+ mutex_lock(&dd->lock);
+ }
+ hwmem_unpin(dd->buffers[i].alloc);
+ dd->buffers[i].state = BUF_DEQUEUED;
+ dd->buffers[i].paddr = 0;
+
+ return i;
+}
+
+long dispdev_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ int ret;
+ struct dispdev *dd = (struct dispdev *)file->private_data;
+
+ mutex_lock(&dd->lock);
+
+ switch (cmd) {
+ case DISPDEV_SET_CONFIG_IOC:
+ {
+ struct dispdev_config cfg;
+ if (copy_from_user(&cfg, (void __user *)arg,
+ sizeof(cfg)))
+ ret = -EFAULT;
+ else
+ ret = dispdev_set_config(dd, &cfg);
+ }
+ break;
+ case DISPDEV_GET_CONFIG_IOC:
+ ret = copy_to_user((void __user *)arg, &dd->config,
+ sizeof(dd->config));
+ if (ret)
+ ret = -EFAULT;
+ break;
+ case DISPDEV_REGISTER_BUFFER_IOC:
+ ret = dispdev_register_buffer(dd, (s32)arg);
+ break;
+ case DISPDEV_UNREGISTER_BUFFER_IOC:
+ ret = dispdev_unregister_buffer(dd, (u32)arg);
+ break;
+ case DISPDEV_QUEUE_BUFFER_IOC:
+ {
+ struct dispdev_buffer_info buffer;
+ if (copy_from_user(&buffer, (void __user *)arg,
+ sizeof(buffer)))
+ ret = -EFAULT;
+ else
+ ret = dispdev_queue_buffer(dd, &buffer);
+ break;
+ }
+ case DISPDEV_DEQUEUE_BUFFER_IOC:
+ ret = dispdev_dequeue_buffer(dd);
+ break;
+ default:
+ ret = -ENOSYS;
+ }
+
+ mutex_unlock(&dd->lock);
+
+ return ret;
+}
+
+static const struct file_operations dispdev_fops = {
+ .open = dispdev_open,
+ .release = dispdev_release,
+ .unlocked_ioctl = dispdev_ioctl,
+};
+
+static void init_dispdev(struct dispdev *dd, struct mcde_display_device *ddev,
+ const char *name, bool overlay)
+{
+ u16 w, h;
+ int rotation;
+
+ mutex_init(&dd->lock);
+ INIT_LIST_HEAD(&dd->list);
+ dd->ddev = ddev;
+ dd->overlay = overlay;
+ mcde_dss_get_native_resolution(ddev, &w, &h);
+ rotation = mcde_dss_get_rotation(ddev);
+
+ if ((rotation == MCDE_DISPLAY_ROT_90_CCW) ||
+ (rotation == MCDE_DISPLAY_ROT_90_CW)) {
+ dd->config.width = h;
+ dd->config.height = w;
+ } else {
+ dd->config.width = w;
+ dd->config.height = h;
+ }
+ dd->config.format = DISPDEV_FMT_RGB565;
+ dd->config.stride = sizeof(u16) * w;
+ dd->config.x = 0;
+ dd->config.y = 0;
+ dd->config.z = 0;
+ dd->buffers_need_update = false;
+ dd->first_update = false;
+ init_waitqueue_head(&dd->waitq_dq);
+ dd->mdev.minor = MISC_DYNAMIC_MINOR;
+ dd->mdev.name = name;
+ dd->mdev.fops = &dispdev_fops;
+ pr_info("%s: name=%s w=%d, h=%d, fmt=%d, stride=%d\n", __func__, name,
+ dd->config.width, dd->config.height, dd->config.format,
+ dd->config.stride);
+}
+
+int dispdev_create(struct mcde_display_device *ddev, bool overlay,
+ struct mcde_overlay *parent_ovly)
+{
+ int ret = 0;
+ struct dispdev *dd;
+ struct mcde_video_mode vmode;
+ struct mcde_overlay_info info = {0};
+
+ static int counter;
+
+ dd = kzalloc(sizeof(struct dispdev), GFP_KERNEL);
+ if (!dd)
+ return -ENOMEM;
+
+ snprintf(dd->name, sizeof(dd->name), "%s%d",
+ DISPDEV_DEFAULT_DEVICE_PREFIX, counter++);
+ init_dispdev(dd, ddev, dd->name, overlay);
+
+ if (!overlay) {
+ ret = mcde_dss_enable_display(ddev);
+ if (ret)
+ goto fail_enable_display;
+ mcde_dss_get_video_mode(ddev, &vmode);
+ mcde_dss_try_video_mode(ddev, &vmode);
+ ret = mcde_dss_set_video_mode(ddev, &vmode);
+ if (ret)
+ goto fail_set_video_mode;
+ mcde_dss_set_pixel_format(ddev, info.fmt);
+ mcde_dss_apply_channel(ddev);
+ } else
+ mcde_dss_get_video_mode(ddev, &vmode);
+ get_ovly_info(&dd->config, &vmode, &info, overlay);
+
+ /* Save the MCDE FB overlay */
+ dd->parent_ovly = parent_ovly;
+
+ dd->ovly = mcde_dss_create_overlay(ddev, &info);
+ if (!dd->ovly) {
+ ret = -ENOMEM;
+ goto fail_create_ovly;
+ }
+
+ ret = misc_register(&dd->mdev);
+ if (ret)
+ goto fail_register_misc;
+ mutex_lock(&dev_list_lock);
+ list_add_tail(&dd->list, &dev_list);
+ mutex_unlock(&dev_list_lock);
+
+ goto out;
+
+fail_register_misc:
+ mcde_dss_destroy_overlay(dd->ovly);
+fail_create_ovly:
+ if (!overlay)
+ mcde_dss_disable_display(ddev);
+fail_set_video_mode:
+fail_enable_display:
+ kfree(dd);
+out:
+ return ret;
+}
+
+void dispdev_destroy(struct mcde_display_device *ddev)
+{
+ struct dispdev *dd;
+ struct dispdev *tmp;
+
+ mutex_lock(&dev_list_lock);
+ list_for_each_entry_safe(dd, tmp, &dev_list, list) {
+ if (dd->ddev == ddev) {
+ list_del(&dd->list);
+ misc_deregister(&dd->mdev);
+ mcde_dss_destroy_overlay(dd->ovly);
+ /*
+ * TODO: Uncomment when DSS has reference
+ * counting of enable/disable
+ */
+ /* mcde_dss_disable_display(dd->ddev); */
+ kfree(dd);
+ break;
+ }
+ }
+ mutex_unlock(&dev_list_lock);
+}
+
+static void dispdev_destroy_all(void)
+{
+ struct dispdev *dd;
+ struct dispdev *tmp;
+
+ mutex_lock(&dev_list_lock);
+ list_for_each_entry_safe(dd, tmp, &dev_list, list) {
+ list_del(&dd->list);
+ misc_deregister(&dd->mdev);
+ mcde_dss_destroy_overlay(dd->ovly);
+ /*
+ * TODO: Uncomment when DSS has reference
+ * counting of enable/disable
+ */
+ /* mcde_dss_disable_display(dd->ddev); */
+ kfree(dd);
+ }
+ mutex_unlock(&dev_list_lock);
+
+ mutex_destroy(&dev_list_lock);
+}
+
+static int __init dispdev_init(void)
+{
+ pr_info("%s\n", __func__);
+
+ mutex_init(&dev_list_lock);
+
+ return 0;
+}
+module_init(dispdev_init);
+
+static void __exit dispdev_exit(void)
+{
+ dispdev_destroy_all();
+ pr_info("%s\n", __func__);
+}
+module_exit(dispdev_exit);
+
+MODULE_AUTHOR("Marcus Lorentzon <marcus.xm.lorentzon@stericsson.com>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Display output device driver");
+