diff options
Diffstat (limited to 'drivers/misc/dispdev/dispdev.c')
-rw-r--r-- | drivers/misc/dispdev/dispdev.c | 522 |
1 files changed, 522 insertions, 0 deletions
diff --git a/drivers/misc/dispdev/dispdev.c b/drivers/misc/dispdev/dispdev.c new file mode 100644 index 00000000000..969bb2a1bc1 --- /dev/null +++ b/drivers/misc/dispdev/dispdev.c @@ -0,0 +1,522 @@ +/* + * 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/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/dispdev.h> +#include <linux/hwmem.h> +#include <video/mcde_dss.h> + +#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 dispdev_config config; + bool overlay; + struct dispdev_buffer buffers[MAX_BUFFERS]; + wait_queue_head_t waitq_dq; +}; + +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_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 = cfg->width; + info->dirty.h = cfg->height; +} + +static int dispdev_set_config(struct dispdev *dd, struct dispdev_config *cfg) +{ + int ret; + struct mcde_overlay_info info; + + if (memcmp(&dd->config, cfg, sizeof(struct dispdev_config)) == 0) + return 0; + + /* Allow change of z,x and y */ + + get_ovly_info(cfg, &info, dd->overlay); + ret = mcde_dss_apply_overlay(dd->ovly, &info); + if (ret) + return ret; + + dd->config = *cfg; + + return 0; +} + +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; +} + +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 (buf->state == BUF_ACTIVATED) { + struct mcde_overlay_info info; + /* TODO Wait for frame done */ + get_ovly_info(&dd->config, &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; + + return 0; +} + +static int dispdev_queue_buffer(struct dispdev *dd, u32 buf_idx) +{ + 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; + + if (buf_idx >= ARRAY_SIZE(dd->buffers) || + dd->buffers[buf_idx].state != BUF_DEQUEUED) + return -EINVAL; + + alloc = dd->buffers[buf_idx].alloc; + get_ovly_info(&dd->config, &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) { + /* TODO Wait for frame done */ + dd->buffers[i].state = BUF_FREE; + wake_up(&dd->waitq_dq); + } + + info.paddr = mem_chunk.paddr; + mcde_dss_apply_overlay(dd->ovly, &info); + mcde_dss_update_overlay(dd->ovly, false); + + 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; + + wait_event(dd->waitq_dq, (i = find_buf(dd, BUF_FREE)) >= 0); + } + hwmem_unpin(dd->buffers[i].alloc); + dd->buffers[i].state = BUF_DEQUEUED; + + return i; +} + +int dispdev_ioctl(struct inode *inode, 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: + ret = dispdev_queue_buffer(dd, (u32)arg); + 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, + .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.z = 0; + 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) +{ + int ret = 0; + struct dispdev *dd; + struct mcde_video_mode vmode; + struct mcde_overlay_info info; + + static int counter; + char *name = "dispdev0"; + + dd = kzalloc(sizeof(struct dispdev), GFP_KERNEL); + if (!dd) + return -ENOMEM; + + sprintf(name, "%s%d", DISPDEV_DEFAULT_DEVICE_PREFIX, counter++); + init_dispdev(dd, ddev, name, overlay); + + get_ovly_info(&dd->config, &info, 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); + } + + 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"); + |