diff options
author | Philippe Langlais <philippe.langlais@stericsson.com> | 2012-06-04 19:45:30 +0800 |
---|---|---|
committer | Philippe Langlais <philippe.langlais@stericsson.com> | 2012-06-04 19:45:30 +0800 |
commit | 1cd04b2aba9f3b3468adf0789030240e032d8d3e (patch) | |
tree | 2e010c15b347b7acda9bc7a2ab4abca9d58043eb /drivers/misc | |
parent | cff72c10aa27e3a74eb5d5b7ceb7808d8c917d15 (diff) | |
parent | b7086e0a1dec82bf7e8e96e17738151f0aee82fc (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/Kconfig | 34 | ||||
-rw-r--r-- | drivers/misc/Makefile | 3 | ||||
-rw-r--r-- | drivers/misc/clonedev/Makefile | 5 | ||||
-rw-r--r-- | drivers/misc/clonedev/clonedev.c | 312 | ||||
-rw-r--r-- | drivers/misc/compdev/Makefile | 6 | ||||
-rw-r--r-- | drivers/misc/compdev/compdev.c | 1381 | ||||
-rw-r--r-- | drivers/misc/dispdev/Makefile | 1 | ||||
-rw-r--r-- | drivers/misc/dispdev/dispdev.c | 659 |
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"); + |