From 5db578e167cfeedbd086df45cc2328b5bb9cf3fd Mon Sep 17 00:00:00 2001 From: Matthew Garrett Date: Thu, 3 May 2012 09:29:01 +0100 Subject: vgaarb: Provide dummy default device functions Add some stub functions for the case where CONFIG_VGA_ARB is disabled. Signed-off-by: Matthew Garrett Signed-off-by: Dave Airlie diff --git a/include/linux/vgaarb.h b/include/linux/vgaarb.h index 367ab18..0ee42d9 100644 --- a/include/linux/vgaarb.h +++ b/include/linux/vgaarb.h @@ -183,8 +183,13 @@ extern void vga_put(struct pci_dev *pdev, unsigned int rsrc); */ #ifndef __ARCH_HAS_VGA_DEFAULT_DEVICE +#ifdef CONFIG_VGA_ARB extern struct pci_dev *vga_default_device(void); extern void vga_set_default_device(struct pci_dev *pdev); +#else +static inline struct pci_dev *vga_default_device(void) { return NULL; }; +static inline void vga_set_default_device(struct pci_dev *pdev) { }; +#endif #endif /** -- cgit v0.10.2 From c01d73faad2f775036f0b37c753254479b79cbe6 Mon Sep 17 00:00:00 2001 From: Inki Dae Date: Mon, 23 Apr 2012 19:26:34 +0900 Subject: drm/exynos: added cache attribute support for gem. with this patch, user application can set cache attribute(such as cachable, writecombime or non-cachable) of the memory region allocated by gem framework. Signed-off-by: Inki Dae Signed-off-by: Kyungmin Park diff --git a/drivers/gpu/drm/exynos/exynos_drm_gem.c b/drivers/gpu/drm/exynos/exynos_drm_gem.c index 1dffa83..e8ab3be 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_gem.c +++ b/drivers/gpu/drm/exynos/exynos_drm_gem.c @@ -66,6 +66,22 @@ static int check_gem_flags(unsigned int flags) return 0; } +static void update_vm_cache_attr(struct exynos_drm_gem_obj *obj, + struct vm_area_struct *vma) +{ + DRM_DEBUG_KMS("flags = 0x%x\n", obj->flags); + + /* non-cachable as default. */ + if (obj->flags & EXYNOS_BO_CACHABLE) + vma->vm_page_prot = vm_get_page_prot(vma->vm_flags); + else if (obj->flags & EXYNOS_BO_WC) + vma->vm_page_prot = + pgprot_writecombine(vm_get_page_prot(vma->vm_flags)); + else + vma->vm_page_prot = + pgprot_noncached(vm_get_page_prot(vma->vm_flags)); +} + static unsigned long roundup_gem_size(unsigned long size, unsigned int flags) { if (!IS_NONCONTIG_BUFFER(flags)) { @@ -262,24 +278,24 @@ static int exynos_drm_gem_handle_create(struct drm_gem_object *obj, void exynos_drm_gem_destroy(struct exynos_drm_gem_obj *exynos_gem_obj) { struct drm_gem_object *obj; + struct exynos_drm_gem_buf *buf; DRM_DEBUG_KMS("%s\n", __FILE__); - if (!exynos_gem_obj) - return; - obj = &exynos_gem_obj->base; + buf = exynos_gem_obj->buffer; DRM_DEBUG_KMS("handle count = %d\n", atomic_read(&obj->handle_count)); - if ((exynos_gem_obj->flags & EXYNOS_BO_NONCONTIG) && - exynos_gem_obj->buffer->pages) + if (!buf->pages) + return; + + if (exynos_gem_obj->flags & EXYNOS_BO_NONCONTIG) exynos_drm_gem_put_pages(obj); else - exynos_drm_free_buf(obj->dev, exynos_gem_obj->flags, - exynos_gem_obj->buffer); + exynos_drm_free_buf(obj->dev, exynos_gem_obj->flags, buf); - exynos_drm_fini_buf(obj->dev, exynos_gem_obj->buffer); + exynos_drm_fini_buf(obj->dev, buf); exynos_gem_obj->buffer = NULL; if (obj->map_list.map) @@ -493,8 +509,7 @@ static int exynos_drm_gem_mmap_buffer(struct file *filp, vma->vm_flags |= (VM_IO | VM_RESERVED); - /* in case of direct mapping, always having non-cachable attribute */ - vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + update_vm_cache_attr(exynos_gem_obj, vma); vm_size = usize = vma->vm_end - vma->vm_start; @@ -724,6 +739,8 @@ int exynos_drm_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf) int exynos_drm_gem_mmap(struct file *filp, struct vm_area_struct *vma) { + struct exynos_drm_gem_obj *exynos_gem_obj; + struct drm_gem_object *obj; int ret; DRM_DEBUG_KMS("%s\n", __FILE__); @@ -735,8 +752,20 @@ int exynos_drm_gem_mmap(struct file *filp, struct vm_area_struct *vma) return ret; } + obj = vma->vm_private_data; + exynos_gem_obj = to_exynos_gem_obj(obj); + + ret = check_gem_flags(exynos_gem_obj->flags); + if (ret) { + drm_gem_vm_close(vma); + drm_gem_free_mmap_offset(obj); + return ret; + } + vma->vm_flags &= ~VM_PFNMAP; vma->vm_flags |= VM_MIXEDMAP; + update_vm_cache_attr(exynos_gem_obj, vma); + return ret; } diff --git a/include/drm/exynos_drm.h b/include/drm/exynos_drm.h index e478de4..2d6eb06 100644 --- a/include/drm/exynos_drm.h +++ b/include/drm/exynos_drm.h @@ -95,9 +95,18 @@ struct drm_exynos_plane_set_zpos { /* memory type definitions. */ enum e_drm_exynos_gem_mem_type { + /* Physically Continuous memory and used as default. */ + EXYNOS_BO_CONTIG = 0 << 0, /* Physically Non-Continuous memory. */ EXYNOS_BO_NONCONTIG = 1 << 0, - EXYNOS_BO_MASK = EXYNOS_BO_NONCONTIG + /* non-cachable mapping and used as default. */ + EXYNOS_BO_NONCACHABLE = 0 << 1, + /* cachable mapping. */ + EXYNOS_BO_CACHABLE = 1 << 1, + /* write-combine mapping. */ + EXYNOS_BO_WC = 1 << 2, + EXYNOS_BO_MASK = EXYNOS_BO_NONCONTIG | EXYNOS_BO_CACHABLE | + EXYNOS_BO_WC }; #define DRM_EXYNOS_GEM_CREATE 0x00 -- cgit v0.10.2 From b2df26c10b16afe2eed904f2b85a4d2047531d30 Mon Sep 17 00:00:00 2001 From: Inki Dae Date: Mon, 23 Apr 2012 21:01:28 +0900 Subject: drm/exynos: added drm prime feature. this patch adds exynos specific codes for DRM Prime feature. with this patch, user application can get file descriptor from gem handle through DRM_IOCTL_PRIME_HANDLE_TO_FD ioctl command(export) and also gem handle from file descriptor through DRM_IOCTL_PRIME_FD_TO_HANLDE(import) ioctl command. Signed-off-by: Inki Dae Signed-off-by: Kyungmin Park diff --git a/drivers/gpu/drm/exynos/Kconfig b/drivers/gpu/drm/exynos/Kconfig index 3343ac4..135b618 100644 --- a/drivers/gpu/drm/exynos/Kconfig +++ b/drivers/gpu/drm/exynos/Kconfig @@ -10,6 +10,12 @@ config DRM_EXYNOS Choose this option if you have a Samsung SoC EXYNOS chipset. If M is selected the module will be called exynosdrm. +config DRM_EXYNOS_DMABUF + bool "EXYNOS DRM DMABUF" + depends on DRM_EXYNOS + help + Choose this option if you want to use DMABUF feature for DRM. + config DRM_EXYNOS_FIMD bool "Exynos DRM FIMD" depends on DRM_EXYNOS && !FB_S3C diff --git a/drivers/gpu/drm/exynos/Makefile b/drivers/gpu/drm/exynos/Makefile index 9e0bff8..353e1b7 100644 --- a/drivers/gpu/drm/exynos/Makefile +++ b/drivers/gpu/drm/exynos/Makefile @@ -8,6 +8,7 @@ exynosdrm-y := exynos_drm_drv.o exynos_drm_encoder.o exynos_drm_connector.o \ exynos_drm_buf.o exynos_drm_gem.o exynos_drm_core.o \ exynos_drm_plane.o +exynosdrm-$(CONFIG_DRM_EXYNOS_DMABUF) += exynos_drm_dmabuf.o exynosdrm-$(CONFIG_DRM_EXYNOS_FIMD) += exynos_drm_fimd.o exynosdrm-$(CONFIG_DRM_EXYNOS_HDMI) += exynos_hdmi.o exynos_mixer.o \ exynos_ddc.o exynos_hdmiphy.o \ diff --git a/drivers/gpu/drm/exynos/exynos_drm_buf.c b/drivers/gpu/drm/exynos/exynos_drm_buf.c index de8d209..b3cb0a6 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_buf.c +++ b/drivers/gpu/drm/exynos/exynos_drm_buf.c @@ -35,7 +35,7 @@ static int lowlevel_buffer_allocate(struct drm_device *dev, unsigned int flags, struct exynos_drm_gem_buf *buf) { dma_addr_t start_addr; - unsigned int npages, page_size, i = 0; + unsigned int npages, i = 0; struct scatterlist *sgl; int ret = 0; @@ -53,13 +53,13 @@ static int lowlevel_buffer_allocate(struct drm_device *dev, if (buf->size >= SZ_1M) { npages = buf->size >> SECTION_SHIFT; - page_size = SECTION_SIZE; + buf->page_size = SECTION_SIZE; } else if (buf->size >= SZ_64K) { npages = buf->size >> 16; - page_size = SZ_64K; + buf->page_size = SZ_64K; } else { npages = buf->size >> PAGE_SHIFT; - page_size = PAGE_SIZE; + buf->page_size = PAGE_SIZE; } buf->sgt = kzalloc(sizeof(struct sg_table), GFP_KERNEL); @@ -96,9 +96,9 @@ static int lowlevel_buffer_allocate(struct drm_device *dev, while (i < npages) { buf->pages[i] = phys_to_page(start_addr); - sg_set_page(sgl, buf->pages[i], page_size, 0); + sg_set_page(sgl, buf->pages[i], buf->page_size, 0); sg_dma_address(sgl) = start_addr; - start_addr += page_size; + start_addr += buf->page_size; sgl = sg_next(sgl); i++; } diff --git a/drivers/gpu/drm/exynos/exynos_drm_dmabuf.c b/drivers/gpu/drm/exynos/exynos_drm_dmabuf.c new file mode 100644 index 0000000..2749092 --- /dev/null +++ b/drivers/gpu/drm/exynos/exynos_drm_dmabuf.c @@ -0,0 +1,272 @@ +/* exynos_drm_dmabuf.c + * + * Copyright (c) 2012 Samsung Electronics Co., Ltd. + * Author: Inki Dae + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "drmP.h" +#include "drm.h" +#include "exynos_drm_drv.h" +#include "exynos_drm_gem.h" + +#include + +static struct sg_table *exynos_pages_to_sg(struct page **pages, int nr_pages, + unsigned int page_size) +{ + struct sg_table *sgt = NULL; + struct scatterlist *sgl; + int i, ret; + + sgt = kzalloc(sizeof(*sgt), GFP_KERNEL); + if (!sgt) + goto out; + + ret = sg_alloc_table(sgt, nr_pages, GFP_KERNEL); + if (ret) + goto err_free_sgt; + + if (page_size < PAGE_SIZE) + page_size = PAGE_SIZE; + + for_each_sg(sgt->sgl, sgl, nr_pages, i) + sg_set_page(sgl, pages[i], page_size, 0); + + return sgt; + +err_free_sgt: + kfree(sgt); + sgt = NULL; +out: + return NULL; +} + +static struct sg_table * + exynos_gem_map_dma_buf(struct dma_buf_attachment *attach, + enum dma_data_direction dir) +{ + struct exynos_drm_gem_obj *gem_obj = attach->dmabuf->priv; + struct drm_device *dev = gem_obj->base.dev; + struct exynos_drm_gem_buf *buf; + struct sg_table *sgt = NULL; + unsigned int npages; + int nents; + + DRM_DEBUG_PRIME("%s\n", __FILE__); + + mutex_lock(&dev->struct_mutex); + + buf = gem_obj->buffer; + + /* there should always be pages allocated. */ + if (!buf->pages) { + DRM_ERROR("pages is null.\n"); + goto err_unlock; + } + + npages = buf->size / buf->page_size; + + sgt = exynos_pages_to_sg(buf->pages, npages, buf->page_size); + nents = dma_map_sg(attach->dev, sgt->sgl, sgt->nents, dir); + + DRM_DEBUG_PRIME("npages = %d buffer size = 0x%lx page_size = 0x%lx\n", + npages, buf->size, buf->page_size); + +err_unlock: + mutex_unlock(&dev->struct_mutex); + return sgt; +} + +static void exynos_gem_unmap_dma_buf(struct dma_buf_attachment *attach, + struct sg_table *sgt, + enum dma_data_direction dir) +{ + dma_unmap_sg(attach->dev, sgt->sgl, sgt->nents, dir); + sg_free_table(sgt); + kfree(sgt); + sgt = NULL; +} + +static void exynos_dmabuf_release(struct dma_buf *dmabuf) +{ + struct exynos_drm_gem_obj *exynos_gem_obj = dmabuf->priv; + + DRM_DEBUG_PRIME("%s\n", __FILE__); + + /* + * exynos_dmabuf_release() call means that file object's + * f_count is 0 and it calls drm_gem_object_handle_unreference() + * to drop the references that these values had been increased + * at drm_prime_handle_to_fd() + */ + if (exynos_gem_obj->base.export_dma_buf == dmabuf) { + exynos_gem_obj->base.export_dma_buf = NULL; + + /* + * drop this gem object refcount to release allocated buffer + * and resources. + */ + drm_gem_object_unreference_unlocked(&exynos_gem_obj->base); + } +} + +static void *exynos_gem_dmabuf_kmap_atomic(struct dma_buf *dma_buf, + unsigned long page_num) +{ + /* TODO */ + + return NULL; +} + +static void exynos_gem_dmabuf_kunmap_atomic(struct dma_buf *dma_buf, + unsigned long page_num, + void *addr) +{ + /* TODO */ +} + +static void *exynos_gem_dmabuf_kmap(struct dma_buf *dma_buf, + unsigned long page_num) +{ + /* TODO */ + + return NULL; +} + +static void exynos_gem_dmabuf_kunmap(struct dma_buf *dma_buf, + unsigned long page_num, void *addr) +{ + /* TODO */ +} + +static struct dma_buf_ops exynos_dmabuf_ops = { + .map_dma_buf = exynos_gem_map_dma_buf, + .unmap_dma_buf = exynos_gem_unmap_dma_buf, + .kmap = exynos_gem_dmabuf_kmap, + .kmap_atomic = exynos_gem_dmabuf_kmap_atomic, + .kunmap = exynos_gem_dmabuf_kunmap, + .kunmap_atomic = exynos_gem_dmabuf_kunmap_atomic, + .release = exynos_dmabuf_release, +}; + +struct dma_buf *exynos_dmabuf_prime_export(struct drm_device *drm_dev, + struct drm_gem_object *obj, int flags) +{ + struct exynos_drm_gem_obj *exynos_gem_obj = to_exynos_gem_obj(obj); + + return dma_buf_export(exynos_gem_obj, &exynos_dmabuf_ops, + exynos_gem_obj->base.size, 0600); +} + +struct drm_gem_object *exynos_dmabuf_prime_import(struct drm_device *drm_dev, + struct dma_buf *dma_buf) +{ + struct dma_buf_attachment *attach; + struct sg_table *sgt; + struct scatterlist *sgl; + struct exynos_drm_gem_obj *exynos_gem_obj; + struct exynos_drm_gem_buf *buffer; + struct page *page; + int ret, i = 0; + + DRM_DEBUG_PRIME("%s\n", __FILE__); + + /* is this one of own objects? */ + if (dma_buf->ops == &exynos_dmabuf_ops) { + struct drm_gem_object *obj; + + exynos_gem_obj = dma_buf->priv; + obj = &exynos_gem_obj->base; + + /* is it from our device? */ + if (obj->dev == drm_dev) { + drm_gem_object_reference(obj); + return obj; + } + } + + attach = dma_buf_attach(dma_buf, drm_dev->dev); + if (IS_ERR(attach)) + return ERR_PTR(-EINVAL); + + + sgt = dma_buf_map_attachment(attach, DMA_BIDIRECTIONAL); + if (IS_ERR(sgt)) { + ret = PTR_ERR(sgt); + goto err_buf_detach; + } + + buffer = kzalloc(sizeof(*buffer), GFP_KERNEL); + if (!buffer) { + DRM_ERROR("failed to allocate exynos_drm_gem_buf.\n"); + ret = -ENOMEM; + goto err_unmap_attach; + } + + buffer->pages = kzalloc(sizeof(*page) * sgt->nents, GFP_KERNEL); + if (!buffer->pages) { + DRM_ERROR("failed to allocate pages.\n"); + ret = -ENOMEM; + goto err_free_buffer; + } + + exynos_gem_obj = exynos_drm_gem_init(drm_dev, dma_buf->size); + if (!exynos_gem_obj) { + ret = -ENOMEM; + goto err_free_pages; + } + + sgl = sgt->sgl; + buffer->dma_addr = sg_dma_address(sgl); + + while (i < sgt->nents) { + buffer->pages[i] = sg_page(sgl); + buffer->size += sg_dma_len(sgl); + sgl = sg_next(sgl); + i++; + } + + exynos_gem_obj->buffer = buffer; + buffer->sgt = sgt; + exynos_gem_obj->base.import_attach = attach; + + DRM_DEBUG_PRIME("dma_addr = 0x%x, size = 0x%lx\n", buffer->dma_addr, + buffer->size); + + return &exynos_gem_obj->base; + +err_free_pages: + kfree(buffer->pages); + buffer->pages = NULL; +err_free_buffer: + kfree(buffer); + buffer = NULL; +err_unmap_attach: + dma_buf_unmap_attachment(attach, sgt, DMA_BIDIRECTIONAL); +err_buf_detach: + dma_buf_detach(dma_buf, attach); + return ERR_PTR(ret); +} + +MODULE_AUTHOR("Inki Dae "); +MODULE_DESCRIPTION("Samsung SoC DRM DMABUF Module"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/exynos/exynos_drm_dmabuf.h b/drivers/gpu/drm/exynos/exynos_drm_dmabuf.h new file mode 100644 index 0000000..662a8f9 --- /dev/null +++ b/drivers/gpu/drm/exynos/exynos_drm_dmabuf.h @@ -0,0 +1,39 @@ +/* exynos_drm_dmabuf.h + * + * Copyright (c) 2012 Samsung Electronics Co., Ltd. + * Author: Inki Dae + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef _EXYNOS_DRM_DMABUF_H_ +#define _EXYNOS_DRM_DMABUF_H_ + +#ifdef CONFIG_DRM_EXYNOS_DMABUF +struct dma_buf *exynos_dmabuf_prime_export(struct drm_device *drm_dev, + struct drm_gem_object *obj, int flags); + +struct drm_gem_object *exynos_dmabuf_prime_import(struct drm_device *drm_dev, + struct dma_buf *dma_buf); +#else +#define exynos_dmabuf_prime_export NULL +#define exynos_dmabuf_prime_import NULL +#endif +#endif diff --git a/drivers/gpu/drm/exynos/exynos_drm_drv.c b/drivers/gpu/drm/exynos/exynos_drm_drv.c index a6819b5..f58a487 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_drv.c +++ b/drivers/gpu/drm/exynos/exynos_drm_drv.c @@ -39,6 +39,7 @@ #include "exynos_drm_gem.h" #include "exynos_drm_plane.h" #include "exynos_drm_vidi.h" +#include "exynos_drm_dmabuf.h" #define DRIVER_NAME "exynos" #define DRIVER_DESC "Samsung SoC DRM" @@ -149,6 +150,8 @@ static int exynos_drm_open(struct drm_device *dev, struct drm_file *file) { DRM_DEBUG_DRIVER("%s\n", __FILE__); + drm_prime_init_file_private(&file->prime); + return exynos_drm_subdrv_open(dev, file); } @@ -170,6 +173,7 @@ static void exynos_drm_preclose(struct drm_device *dev, e->base.destroy(&e->base); } } + drm_prime_destroy_file_private(&file->prime); spin_unlock_irqrestore(&dev->event_lock, flags); exynos_drm_subdrv_close(dev, file); @@ -225,7 +229,7 @@ static const struct file_operations exynos_drm_driver_fops = { static struct drm_driver exynos_drm_driver = { .driver_features = DRIVER_HAVE_IRQ | DRIVER_BUS_PLATFORM | - DRIVER_MODESET | DRIVER_GEM, + DRIVER_MODESET | DRIVER_GEM | DRIVER_PRIME, .load = exynos_drm_load, .unload = exynos_drm_unload, .open = exynos_drm_open, @@ -241,6 +245,10 @@ static struct drm_driver exynos_drm_driver = { .dumb_create = exynos_drm_gem_dumb_create, .dumb_map_offset = exynos_drm_gem_dumb_map_offset, .dumb_destroy = exynos_drm_gem_dumb_destroy, + .prime_handle_to_fd = drm_gem_prime_handle_to_fd, + .prime_fd_to_handle = drm_gem_prime_fd_to_handle, + .gem_prime_export = exynos_dmabuf_prime_export, + .gem_prime_import = exynos_dmabuf_prime_import, .ioctls = exynos_ioctls, .fops = &exynos_drm_driver_fops, .name = DRIVER_NAME, diff --git a/drivers/gpu/drm/exynos/exynos_drm_gem.c b/drivers/gpu/drm/exynos/exynos_drm_gem.c index e8ab3be..31bb85f 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_gem.c +++ b/drivers/gpu/drm/exynos/exynos_drm_gem.c @@ -96,7 +96,7 @@ out: return roundup(size, PAGE_SIZE); } -static struct page **exynos_gem_get_pages(struct drm_gem_object *obj, +struct page **exynos_gem_get_pages(struct drm_gem_object *obj, gfp_t gfpmask) { struct inode *inode; @@ -196,6 +196,7 @@ static int exynos_drm_gem_get_pages(struct drm_gem_object *obj) } npages = obj->size >> PAGE_SHIFT; + buf->page_size = PAGE_SIZE; buf->sgt = kzalloc(sizeof(struct sg_table), GFP_KERNEL); if (!buf->sgt) { @@ -308,7 +309,7 @@ void exynos_drm_gem_destroy(struct exynos_drm_gem_obj *exynos_gem_obj) exynos_gem_obj = NULL; } -static struct exynos_drm_gem_obj *exynos_drm_gem_init(struct drm_device *dev, +struct exynos_drm_gem_obj *exynos_drm_gem_init(struct drm_device *dev, unsigned long size) { struct exynos_drm_gem_obj *exynos_gem_obj; @@ -612,8 +613,17 @@ int exynos_drm_gem_init_object(struct drm_gem_object *obj) void exynos_drm_gem_free_object(struct drm_gem_object *obj) { + struct exynos_drm_gem_obj *exynos_gem_obj; + struct exynos_drm_gem_buf *buf; + DRM_DEBUG_KMS("%s\n", __FILE__); + exynos_gem_obj = to_exynos_gem_obj(obj); + buf = exynos_gem_obj->buffer; + + if (obj->import_attach) + drm_prime_gem_destroy(obj, buf->sgt); + exynos_drm_gem_destroy(to_exynos_gem_obj(obj)); } diff --git a/drivers/gpu/drm/exynos/exynos_drm_gem.h b/drivers/gpu/drm/exynos/exynos_drm_gem.h index 4ed8420..efc8252 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_gem.h +++ b/drivers/gpu/drm/exynos/exynos_drm_gem.h @@ -40,6 +40,7 @@ * device address with IOMMU. * @sgt: sg table to transfer page data. * @pages: contain all pages to allocated memory region. + * @page_size: could be 4K, 64K or 1MB. * @size: size of allocated memory region. */ struct exynos_drm_gem_buf { @@ -47,6 +48,7 @@ struct exynos_drm_gem_buf { dma_addr_t dma_addr; struct sg_table *sgt; struct page **pages; + unsigned long page_size; unsigned long size; }; @@ -74,9 +76,15 @@ struct exynos_drm_gem_obj { unsigned int flags; }; +struct page **exynos_gem_get_pages(struct drm_gem_object *obj, gfp_t gfpmask); + /* destroy a buffer with gem object */ void exynos_drm_gem_destroy(struct exynos_drm_gem_obj *exynos_gem_obj); +/* create a private gem object and initialize it. */ +struct exynos_drm_gem_obj *exynos_drm_gem_init(struct drm_device *dev, + unsigned long size); + /* create a new buffer with gem object */ struct exynos_drm_gem_obj *exynos_drm_gem_create(struct drm_device *dev, unsigned int flags, -- cgit v0.10.2 From 40cd7e0c02f37af4b44fc00531873832e45587e9 Mon Sep 17 00:00:00 2001 From: Inki Dae Date: Fri, 4 May 2012 15:51:17 +0900 Subject: drm/exynos: added a feature to get gem buffer information. this patch adds a feature to get a gem buffer information and user application can get the gem buffer information simply in runtime through gem handle. Signed-off-by: Inki Dae Signed-off-by: Kyungmin Park diff --git a/drivers/gpu/drm/exynos/exynos_drm_drv.c b/drivers/gpu/drm/exynos/exynos_drm_drv.c index f58a487..b7a2869 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_drv.c +++ b/drivers/gpu/drm/exynos/exynos_drm_drv.c @@ -211,6 +211,8 @@ static struct drm_ioctl_desc exynos_ioctls[] = { DRM_AUTH), DRM_IOCTL_DEF_DRV(EXYNOS_GEM_MMAP, exynos_drm_gem_mmap_ioctl, DRM_UNLOCKED | DRM_AUTH), + DRM_IOCTL_DEF_DRV(EXYNOS_GEM_GET, + exynos_drm_gem_get_ioctl, DRM_UNLOCKED), DRM_IOCTL_DEF_DRV(EXYNOS_PLANE_SET_ZPOS, exynos_plane_set_zpos_ioctl, DRM_UNLOCKED | DRM_AUTH), DRM_IOCTL_DEF_DRV(EXYNOS_VIDI_CONNECTION, diff --git a/drivers/gpu/drm/exynos/exynos_drm_gem.c b/drivers/gpu/drm/exynos/exynos_drm_gem.c index 31bb85f..fc91293 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_gem.c +++ b/drivers/gpu/drm/exynos/exynos_drm_gem.c @@ -604,6 +604,32 @@ int exynos_drm_gem_mmap_ioctl(struct drm_device *dev, void *data, return 0; } +int exynos_drm_gem_get_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ struct exynos_drm_gem_obj *exynos_gem_obj; + struct drm_exynos_gem_info *args = data; + struct drm_gem_object *obj; + + mutex_lock(&dev->struct_mutex); + + obj = drm_gem_object_lookup(dev, file_priv, args->handle); + if (!obj) { + DRM_ERROR("failed to lookup gem object.\n"); + mutex_unlock(&dev->struct_mutex); + return -EINVAL; + } + + exynos_gem_obj = to_exynos_gem_obj(obj); + + args->flags = exynos_gem_obj->flags; + args->size = exynos_gem_obj->size; + + drm_gem_object_unreference(obj); + mutex_unlock(&dev->struct_mutex); + + return 0; +} + int exynos_drm_gem_init_object(struct drm_gem_object *obj) { DRM_DEBUG_KMS("%s\n", __FILE__); diff --git a/drivers/gpu/drm/exynos/exynos_drm_gem.h b/drivers/gpu/drm/exynos/exynos_drm_gem.h index efc8252..14d038b 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_gem.h +++ b/drivers/gpu/drm/exynos/exynos_drm_gem.h @@ -127,6 +127,10 @@ int exynos_drm_gem_map_offset_ioctl(struct drm_device *dev, void *data, int exynos_drm_gem_mmap_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv); +/* get buffer information to memory region allocated by gem. */ +int exynos_drm_gem_get_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv); + /* initialize gem object. */ int exynos_drm_gem_init_object(struct drm_gem_object *obj); diff --git a/include/drm/exynos_drm.h b/include/drm/exynos_drm.h index 2d6eb06..70c0f7e 100644 --- a/include/drm/exynos_drm.h +++ b/include/drm/exynos_drm.h @@ -75,6 +75,21 @@ struct drm_exynos_gem_mmap { }; /** + * A structure to gem information. + * + * @handle: a handle to gem object created. + * @flags: flag value including memory type and cache attribute and + * this value would be set by driver. + * @size: size to memory region allocated by gem and this size would + * be set by driver. + */ +struct drm_exynos_gem_info { + unsigned int handle; + unsigned int flags; + uint64_t size; +}; + +/** * A structure for user connection request of virtual display. * * @connection: indicate whether doing connetion or not by user. @@ -113,6 +128,7 @@ enum e_drm_exynos_gem_mem_type { #define DRM_EXYNOS_GEM_MAP_OFFSET 0x01 #define DRM_EXYNOS_GEM_MMAP 0x02 /* Reserved 0x03 ~ 0x05 for exynos specific gem ioctl */ +#define DRM_EXYNOS_GEM_GET 0x04 #define DRM_EXYNOS_PLANE_SET_ZPOS 0x06 #define DRM_EXYNOS_VIDI_CONNECTION 0x07 @@ -125,6 +141,9 @@ enum e_drm_exynos_gem_mem_type { #define DRM_IOCTL_EXYNOS_GEM_MMAP DRM_IOWR(DRM_COMMAND_BASE + \ DRM_EXYNOS_GEM_MMAP, struct drm_exynos_gem_mmap) +#define DRM_IOCTL_EXYNOS_GEM_GET DRM_IOWR(DRM_COMMAND_BASE + \ + DRM_EXYNOS_GEM_GET, struct drm_exynos_gem_info) + #define DRM_IOCTL_EXYNOS_PLANE_SET_ZPOS DRM_IOWR(DRM_COMMAND_BASE + \ DRM_EXYNOS_PLANE_SET_ZPOS, struct drm_exynos_plane_set_zpos) -- cgit v0.10.2 From 7ecd34e82ce8d9da0cd99541eac4eaf9dd7eb27e Mon Sep 17 00:00:00 2001 From: Joonyoung Shim Date: Mon, 23 Apr 2012 19:35:47 +0900 Subject: drm/exynos: cleanup for hdmi platform data The exynos_drm_hdmi_pdata struct have owned unnessary members. Remove them and add a function pointer to configure hdmi hotplug detection pin. Signed-off-by: Joonyoung Shim Signed-off-by: Kyungmin Park Signed-off-by: Inki Dae diff --git a/drivers/gpu/drm/exynos/exynos_hdmi.c b/drivers/gpu/drm/exynos/exynos_hdmi.c index b003538..7365cc6 100644 --- a/drivers/gpu/drm/exynos/exynos_hdmi.c +++ b/drivers/gpu/drm/exynos/exynos_hdmi.c @@ -57,12 +57,9 @@ struct hdmi_resources { struct hdmi_context { struct device *dev; struct drm_device *drm_dev; - struct fb_videomode *default_timing; - unsigned int is_v13:1; - unsigned int default_win; - unsigned int default_bpp; bool hpd_handle; bool enabled; + bool is_v13; struct resource *regs_res; void __iomem *regs; @@ -78,6 +75,9 @@ struct hdmi_context { struct hdmi_resources res; void *parent_ctx; + + void (*cfg_hpd)(bool external); + int (*get_hpd)(void); }; /* HDMI Version 1.3 */ @@ -2243,9 +2243,8 @@ static int __devinit hdmi_probe(struct platform_device *pdev) platform_set_drvdata(pdev, drm_hdmi_ctx); hdata->is_v13 = pdata->is_v13; - hdata->default_win = pdata->default_win; - hdata->default_timing = &pdata->timing; - hdata->default_bpp = pdata->bpp; + hdata->cfg_hpd = pdata->cfg_hpd; + hdata->get_hpd = pdata->get_hpd; hdata->dev = dev; ret = hdmi_resources_init(hdata); diff --git a/include/drm/exynos_drm.h b/include/drm/exynos_drm.h index 70c0f7e..44f79cf 100644 --- a/include/drm/exynos_drm.h +++ b/include/drm/exynos_drm.h @@ -197,16 +197,14 @@ struct exynos_drm_common_hdmi_pd { /** * Platform Specific Structure for DRM based HDMI core. * - * @timing: default video mode for initializing - * @default_win: default window layer number to be used for UI. - * @bpp: default bit per pixel. * @is_v13: set if hdmi version 13 is. + * @cfg_hpd: function pointer to configure hdmi hotplug detection pin + * @get_hpd: function pointer to get value of hdmi hotplug detection pin */ struct exynos_drm_hdmi_pdata { - struct fb_videomode timing; - unsigned int default_win; - unsigned int bpp; - unsigned int is_v13:1; + bool is_v13; + void (*cfg_hpd)(bool external); + int (*get_hpd)(void); }; #endif /* __KERNEL__ */ -- cgit v0.10.2 From ce80a2d1890063ff2d3b5beb6edac85a2f6c86c4 Mon Sep 17 00:00:00 2001 From: Joonyoung Shim Date: Mon, 23 Apr 2012 19:35:48 +0900 Subject: drm/exynos: use platform_get_irq_byname for hdmi The exynos hdmi supports two hdmi interrupts - external and internal, so use platform_get_irq_byname to distinguish their resources. Signed-off-by: Joonyoung Shim Signed-off-by: Kyungmin Park Signed-off-by: Inki Dae diff --git a/drivers/gpu/drm/exynos/exynos_hdmi.c b/drivers/gpu/drm/exynos/exynos_hdmi.c index 7365cc6..e6a5e00 100644 --- a/drivers/gpu/drm/exynos/exynos_hdmi.c +++ b/drivers/gpu/drm/exynos/exynos_hdmi.c @@ -2293,10 +2293,10 @@ static int __devinit hdmi_probe(struct platform_device *pdev) hdata->hdmiphy_port = hdmi_hdmiphy; - res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); - if (res == NULL) { - DRM_ERROR("get interrupt resource failed.\n"); - ret = -ENXIO; + hdata->irq = platform_get_irq_byname(pdev, "internal_irq"); + if (hdata->irq < 0) { + DRM_ERROR("failed to get platform irq\n"); + ret = hdata->irq; goto err_hdmiphy; } @@ -2311,13 +2311,12 @@ static int __devinit hdmi_probe(struct platform_device *pdev) INIT_WORK(&hdata->hotplug_work, hdmi_hotplug_func); /* register hpd interrupt */ - ret = request_irq(res->start, hdmi_irq_handler, 0, "drm_hdmi", + ret = request_irq(hdata->irq, hdmi_irq_handler, 0, "drm_hdmi", drm_hdmi_ctx); if (ret) { DRM_ERROR("request interrupt failed.\n"); goto err_workqueue; } - hdata->irq = res->start; /* register specific callbacks to common hdmi. */ exynos_hdmi_ops_register(&hdmi_ops); -- cgit v0.10.2 From 66265a2e8e867a9c5ce63e5f883cf751b35b66d5 Mon Sep 17 00:00:00 2001 From: Joonyoung Shim Date: Mon, 23 Apr 2012 19:35:49 +0900 Subject: drm/exynos: use threaded irq for hdmi hotplug We can use irq thread instead of workqueue Signed-off-by: Joonyoung Shim Signed-off-by: Kyungmin Park Signed-off-by: Inki Dae diff --git a/drivers/gpu/drm/exynos/exynos_hdmi.c b/drivers/gpu/drm/exynos/exynos_hdmi.c index e6a5e00..5b04af1 100644 --- a/drivers/gpu/drm/exynos/exynos_hdmi.c +++ b/drivers/gpu/drm/exynos/exynos_hdmi.c @@ -64,8 +64,6 @@ struct hdmi_context { struct resource *regs_res; void __iomem *regs; unsigned int irq; - struct workqueue_struct *wq; - struct work_struct hotplug_work; struct i2c_client *ddc_port; struct i2c_client *hdmiphy_port; @@ -2003,20 +2001,7 @@ static struct exynos_hdmi_ops hdmi_ops = { .disable = hdmi_disable, }; -/* - * Handle hotplug events outside the interrupt handler proper. - */ -static void hdmi_hotplug_func(struct work_struct *work) -{ - struct hdmi_context *hdata = - container_of(work, struct hdmi_context, hotplug_work); - struct exynos_drm_hdmi_context *ctx = - (struct exynos_drm_hdmi_context *)hdata->parent_ctx; - - drm_helper_hpd_irq_event(ctx->drm_dev); -} - -static irqreturn_t hdmi_irq_handler(int irq, void *arg) +static irqreturn_t hdmi_irq_thread(int irq, void *arg) { struct exynos_drm_hdmi_context *ctx = arg; struct hdmi_context *hdata = ctx->ctx; @@ -2036,7 +2021,7 @@ static irqreturn_t hdmi_irq_handler(int irq, void *arg) } if (ctx->drm_dev && hdata->hpd_handle) - queue_work(hdata->wq, &hdata->hotplug_work); + drm_helper_hpd_irq_event(ctx->drm_dev); return IRQ_HANDLED; } @@ -2300,22 +2285,12 @@ static int __devinit hdmi_probe(struct platform_device *pdev) goto err_hdmiphy; } - /* create workqueue and hotplug work */ - hdata->wq = alloc_workqueue("exynos-drm-hdmi", - WQ_UNBOUND | WQ_NON_REENTRANT, 1); - if (hdata->wq == NULL) { - DRM_ERROR("Failed to create workqueue.\n"); - ret = -ENOMEM; - goto err_hdmiphy; - } - INIT_WORK(&hdata->hotplug_work, hdmi_hotplug_func); - /* register hpd interrupt */ - ret = request_irq(hdata->irq, hdmi_irq_handler, 0, "drm_hdmi", - drm_hdmi_ctx); + ret = request_threaded_irq(hdata->irq, NULL, hdmi_irq_thread, + IRQF_ONESHOT, "drm_hdmi", drm_hdmi_ctx); if (ret) { DRM_ERROR("request interrupt failed.\n"); - goto err_workqueue; + goto err_hdmiphy; } /* register specific callbacks to common hdmi. */ @@ -2325,8 +2300,6 @@ static int __devinit hdmi_probe(struct platform_device *pdev) return 0; -err_workqueue: - destroy_workqueue(hdata->wq); err_hdmiphy: i2c_del_driver(&hdmiphy_driver); err_ddc: @@ -2356,9 +2329,6 @@ static int __devexit hdmi_remove(struct platform_device *pdev) disable_irq(hdata->irq); free_irq(hdata->irq, hdata); - cancel_work_sync(&hdata->hotplug_work); - destroy_workqueue(hdata->wq); - hdmi_resources_cleanup(hdata); iounmap(hdata->regs); -- cgit v0.10.2 From cf8fc4f10e2c5c5b6b60d6e79d61cc58afa5967d Mon Sep 17 00:00:00 2001 From: Joonyoung Shim Date: Mon, 23 Apr 2012 19:35:50 +0900 Subject: drm/exynos: add dpms for hdmi The power and clocks turns on always in exynos hdmi and mixer driver, but we should turn off the power and clocks of exynos hdmi and mixer when the hdmi cable unplugged or when hdmi unused. There are two interrupt to detect hotplug of hdmi cable - internal interrupt and external interrupt. The internal interrupt can use only when hdmi is dpms on so if hdmi is dpms off, we should use external interrupt to detect hotplug of hdmi cable. If hdmi is dpms on, we cannot external interrupt because the gpio pin for external interrupt is used to hdmi HPD pin for internal interrupt. Signed-off-by: Joonyoung Shim Signed-off-by: Kyungmin Park Signed-off-by: Inki Dae diff --git a/drivers/gpu/drm/exynos/exynos_drm_hdmi.c b/drivers/gpu/drm/exynos/exynos_drm_hdmi.c index 3424463..5d9d2c2 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_hdmi.c +++ b/drivers/gpu/drm/exynos/exynos_drm_hdmi.c @@ -37,6 +37,8 @@ struct drm_hdmi_context { struct exynos_drm_subdrv subdrv; struct exynos_drm_hdmi_context *hdmi_ctx; struct exynos_drm_hdmi_context *mixer_ctx; + + bool enabled[MIXER_WIN_NR]; }; void exynos_hdmi_ops_register(struct exynos_hdmi_ops *ops) @@ -189,23 +191,34 @@ static void drm_hdmi_dpms(struct device *subdrv_dev, int mode) DRM_DEBUG_KMS("%s\n", __FILE__); - switch (mode) { - case DRM_MODE_DPMS_ON: - break; - case DRM_MODE_DPMS_STANDBY: - case DRM_MODE_DPMS_SUSPEND: - case DRM_MODE_DPMS_OFF: - if (hdmi_ops && hdmi_ops->disable) - hdmi_ops->disable(ctx->hdmi_ctx->ctx); - break; - default: - DRM_DEBUG_KMS("unkown dps mode: %d\n", mode); - break; + if (mixer_ops && mixer_ops->dpms) + mixer_ops->dpms(ctx->mixer_ctx->ctx, mode); + + if (hdmi_ops && hdmi_ops->dpms) + hdmi_ops->dpms(ctx->hdmi_ctx->ctx, mode); +} + +static void drm_hdmi_apply(struct device *subdrv_dev) +{ + struct drm_hdmi_context *ctx = to_context(subdrv_dev); + int i; + + DRM_DEBUG_KMS("%s\n", __FILE__); + + for (i = 0; i < MIXER_WIN_NR; i++) { + if (!ctx->enabled[i]) + continue; + if (mixer_ops && mixer_ops->win_commit) + mixer_ops->win_commit(ctx->mixer_ctx->ctx, i); } + + if (hdmi_ops && hdmi_ops->commit) + hdmi_ops->commit(ctx->hdmi_ctx->ctx); } static struct exynos_drm_manager_ops drm_hdmi_manager_ops = { .dpms = drm_hdmi_dpms, + .apply = drm_hdmi_apply, .enable_vblank = drm_hdmi_enable_vblank, .disable_vblank = drm_hdmi_disable_vblank, .mode_fixup = drm_hdmi_mode_fixup, @@ -228,21 +241,37 @@ static void drm_mixer_mode_set(struct device *subdrv_dev, static void drm_mixer_commit(struct device *subdrv_dev, int zpos) { struct drm_hdmi_context *ctx = to_context(subdrv_dev); + int win = (zpos == DEFAULT_ZPOS) ? MIXER_DEFAULT_WIN : zpos; DRM_DEBUG_KMS("%s\n", __FILE__); + if (win < 0 || win > MIXER_WIN_NR) { + DRM_ERROR("mixer window[%d] is wrong\n", win); + return; + } + if (mixer_ops && mixer_ops->win_commit) - mixer_ops->win_commit(ctx->mixer_ctx->ctx, zpos); + mixer_ops->win_commit(ctx->mixer_ctx->ctx, win); + + ctx->enabled[win] = true; } static void drm_mixer_disable(struct device *subdrv_dev, int zpos) { struct drm_hdmi_context *ctx = to_context(subdrv_dev); + int win = (zpos == DEFAULT_ZPOS) ? MIXER_DEFAULT_WIN : zpos; DRM_DEBUG_KMS("%s\n", __FILE__); + if (win < 0 || win > MIXER_WIN_NR) { + DRM_ERROR("mixer window[%d] is wrong\n", win); + return; + } + if (mixer_ops && mixer_ops->win_disable) - mixer_ops->win_disable(ctx->mixer_ctx->ctx, zpos); + mixer_ops->win_disable(ctx->mixer_ctx->ctx, win); + + ctx->enabled[win] = false; } static struct exynos_drm_overlay_ops drm_hdmi_overlay_ops = { @@ -335,25 +364,6 @@ static int __devinit exynos_drm_hdmi_probe(struct platform_device *pdev) return 0; } -static int hdmi_runtime_suspend(struct device *dev) -{ - DRM_DEBUG_KMS("%s\n", __FILE__); - - return 0; -} - -static int hdmi_runtime_resume(struct device *dev) -{ - DRM_DEBUG_KMS("%s\n", __FILE__); - - return 0; -} - -static const struct dev_pm_ops hdmi_pm_ops = { - .runtime_suspend = hdmi_runtime_suspend, - .runtime_resume = hdmi_runtime_resume, -}; - static int __devexit exynos_drm_hdmi_remove(struct platform_device *pdev) { struct drm_hdmi_context *ctx = platform_get_drvdata(pdev); @@ -372,6 +382,5 @@ struct platform_driver exynos_drm_common_hdmi_driver = { .driver = { .name = "exynos-drm-hdmi", .owner = THIS_MODULE, - .pm = &hdmi_pm_ops, }, }; diff --git a/drivers/gpu/drm/exynos/exynos_drm_hdmi.h b/drivers/gpu/drm/exynos/exynos_drm_hdmi.h index f3ae192..bd81269 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_hdmi.h +++ b/drivers/gpu/drm/exynos/exynos_drm_hdmi.h @@ -26,6 +26,9 @@ #ifndef _EXYNOS_DRM_HDMI_H_ #define _EXYNOS_DRM_HDMI_H_ +#define MIXER_WIN_NR 3 +#define MIXER_DEFAULT_WIN 0 + /* * exynos hdmi common context structure. * @@ -54,13 +57,14 @@ struct exynos_hdmi_ops { void (*get_max_resol)(void *ctx, unsigned int *width, unsigned int *height); void (*commit)(void *ctx); - void (*disable)(void *ctx); + void (*dpms)(void *ctx, int mode); }; struct exynos_mixer_ops { /* manager */ int (*enable_vblank)(void *ctx, int pipe); void (*disable_vblank)(void *ctx); + void (*dpms)(void *ctx, int mode); /* overlay */ void (*win_mode_set)(void *ctx, struct exynos_drm_overlay *overlay); diff --git a/drivers/gpu/drm/exynos/exynos_hdmi.c b/drivers/gpu/drm/exynos/exynos_hdmi.c index 5b04af1..9212d7d 100644 --- a/drivers/gpu/drm/exynos/exynos_hdmi.c +++ b/drivers/gpu/drm/exynos/exynos_hdmi.c @@ -57,13 +57,15 @@ struct hdmi_resources { struct hdmi_context { struct device *dev; struct drm_device *drm_dev; - bool hpd_handle; - bool enabled; + bool hpd; + bool powered; bool is_v13; + struct mutex hdmi_mutex; struct resource *regs_res; void __iomem *regs; - unsigned int irq; + unsigned int external_irq; + unsigned int internal_irq; struct i2c_client *ddc_port; struct i2c_client *hdmiphy_port; @@ -1192,12 +1194,8 @@ static int hdmi_conf_index(struct hdmi_context *hdata, static bool hdmi_is_connected(void *ctx) { struct hdmi_context *hdata = ctx; - u32 val = hdmi_reg_read(hdata, HDMI_HPD_STATUS); - if (val) - return true; - - return false; + return hdata->hpd; } static int hdmi_get_edid(void *ctx, struct drm_connector *connector, @@ -1287,28 +1285,6 @@ static int hdmi_check_timing(void *ctx, void *timing) return hdmi_v14_check_timing(check_timing); } -static int hdmi_display_power_on(void *ctx, int mode) -{ - DRM_DEBUG_KMS("[%d] %s\n", __LINE__, __func__); - - switch (mode) { - case DRM_MODE_DPMS_ON: - DRM_DEBUG_KMS("hdmi [on]\n"); - break; - case DRM_MODE_DPMS_STANDBY: - break; - case DRM_MODE_DPMS_SUSPEND: - break; - case DRM_MODE_DPMS_OFF: - DRM_DEBUG_KMS("hdmi [off]\n"); - break; - default: - break; - } - - return 0; -} - static void hdmi_set_acr(u32 freq, u8 *acr) { u32 n, cts; @@ -1476,9 +1452,6 @@ static void hdmi_conf_reset(struct hdmi_context *hdata) { u32 reg; - /* disable hpd handle for drm */ - hdata->hpd_handle = false; - if (hdata->is_v13) reg = HDMI_V13_CORE_RSTOUT; else @@ -1489,16 +1462,10 @@ static void hdmi_conf_reset(struct hdmi_context *hdata) mdelay(10); hdmi_reg_writemask(hdata, reg, ~0, HDMI_CORE_SW_RSTOUT); mdelay(10); - - /* enable hpd handle for drm */ - hdata->hpd_handle = true; } static void hdmi_conf_init(struct hdmi_context *hdata) { - /* disable hpd handle for drm */ - hdata->hpd_handle = false; - /* enable HPD interrupts */ hdmi_reg_writemask(hdata, HDMI_INTC_CON, 0, HDMI_INTC_EN_GLOBAL | HDMI_INTC_EN_HPD_PLUG | HDMI_INTC_EN_HPD_UNPLUG); @@ -1533,9 +1500,6 @@ static void hdmi_conf_init(struct hdmi_context *hdata) hdmi_reg_writeb(hdata, HDMI_AVI_BYTE(1), 2 << 5); hdmi_reg_writemask(hdata, HDMI_CON_1, 2, 3 << 5); } - - /* enable hpd handle for drm */ - hdata->hpd_handle = true; } static void hdmi_v13_timing_apply(struct hdmi_context *hdata) @@ -1888,8 +1852,11 @@ static void hdmi_conf_apply(struct hdmi_context *hdata) hdmiphy_conf_reset(hdata); hdmiphy_conf_apply(hdata); + mutex_lock(&hdata->hdmi_mutex); hdmi_conf_reset(hdata); hdmi_conf_init(hdata); + mutex_unlock(&hdata->hdmi_mutex); + hdmi_audio_init(hdata); /* setting core registers */ @@ -1969,20 +1936,86 @@ static void hdmi_commit(void *ctx) DRM_DEBUG_KMS("[%d] %s\n", __LINE__, __func__); hdmi_conf_apply(hdata); +} + +static void hdmi_poweron(struct hdmi_context *hdata) +{ + struct hdmi_resources *res = &hdata->res; + + DRM_DEBUG_KMS("[%d] %s\n", __LINE__, __func__); + + mutex_lock(&hdata->hdmi_mutex); + if (hdata->powered) { + mutex_unlock(&hdata->hdmi_mutex); + return; + } - hdata->enabled = true; + hdata->powered = true; + + if (hdata->cfg_hpd) + hdata->cfg_hpd(true); + mutex_unlock(&hdata->hdmi_mutex); + + pm_runtime_get_sync(hdata->dev); + + regulator_bulk_enable(res->regul_count, res->regul_bulk); + clk_enable(res->hdmiphy); + clk_enable(res->hdmi); + clk_enable(res->sclk_hdmi); +} + +static void hdmi_poweroff(struct hdmi_context *hdata) +{ + struct hdmi_resources *res = &hdata->res; + + DRM_DEBUG_KMS("[%d] %s\n", __LINE__, __func__); + + mutex_lock(&hdata->hdmi_mutex); + if (!hdata->powered) + goto out; + mutex_unlock(&hdata->hdmi_mutex); + + /* + * The TV power domain needs any condition of hdmiphy to turn off and + * its reset state seems to meet the condition. + */ + hdmiphy_conf_reset(hdata); + + clk_disable(res->sclk_hdmi); + clk_disable(res->hdmi); + clk_disable(res->hdmiphy); + regulator_bulk_disable(res->regul_count, res->regul_bulk); + + pm_runtime_put_sync(hdata->dev); + + mutex_lock(&hdata->hdmi_mutex); + if (hdata->cfg_hpd) + hdata->cfg_hpd(false); + + hdata->powered = false; + +out: + mutex_unlock(&hdata->hdmi_mutex); } -static void hdmi_disable(void *ctx) +static void hdmi_dpms(void *ctx, int mode) { struct hdmi_context *hdata = ctx; DRM_DEBUG_KMS("[%d] %s\n", __LINE__, __func__); - if (hdata->enabled) { - hdmi_audio_control(hdata, false); - hdmiphy_conf_reset(hdata); - hdmi_conf_reset(hdata); + switch (mode) { + case DRM_MODE_DPMS_ON: + hdmi_poweron(hdata); + break; + case DRM_MODE_DPMS_STANDBY: + case DRM_MODE_DPMS_SUSPEND: + case DRM_MODE_DPMS_OFF: + hdmi_poweroff(hdata); + break; + default: + DRM_DEBUG_KMS("unknown dpms mode: %d\n", mode); + break; } } @@ -1991,17 +2024,35 @@ static struct exynos_hdmi_ops hdmi_ops = { .is_connected = hdmi_is_connected, .get_edid = hdmi_get_edid, .check_timing = hdmi_check_timing, - .power_on = hdmi_display_power_on, /* manager */ .mode_fixup = hdmi_mode_fixup, .mode_set = hdmi_mode_set, .get_max_resol = hdmi_get_max_resol, .commit = hdmi_commit, - .disable = hdmi_disable, + .dpms = hdmi_dpms, }; -static irqreturn_t hdmi_irq_thread(int irq, void *arg) +static irqreturn_t hdmi_external_irq_thread(int irq, void *arg) +{ + struct exynos_drm_hdmi_context *ctx = arg; + struct hdmi_context *hdata = ctx->ctx; + + if (!hdata->get_hpd) + goto out; + + mutex_lock(&hdata->hdmi_mutex); + hdata->hpd = hdata->get_hpd(); + mutex_unlock(&hdata->hdmi_mutex); + + if (ctx->drm_dev) + drm_helper_hpd_irq_event(ctx->drm_dev); + +out: + return IRQ_HANDLED; +} + +static irqreturn_t hdmi_internal_irq_thread(int irq, void *arg) { struct exynos_drm_hdmi_context *ctx = arg; struct hdmi_context *hdata = ctx->ctx; @@ -2010,19 +2061,28 @@ static irqreturn_t hdmi_irq_thread(int irq, void *arg) intc_flag = hdmi_reg_read(hdata, HDMI_INTC_FLAG); /* clearing flags for HPD plug/unplug */ if (intc_flag & HDMI_INTC_FLAG_HPD_UNPLUG) { - DRM_DEBUG_KMS("unplugged, handling:%d\n", hdata->hpd_handle); + DRM_DEBUG_KMS("unplugged\n"); hdmi_reg_writemask(hdata, HDMI_INTC_FLAG, ~0, HDMI_INTC_FLAG_HPD_UNPLUG); } if (intc_flag & HDMI_INTC_FLAG_HPD_PLUG) { - DRM_DEBUG_KMS("plugged, handling:%d\n", hdata->hpd_handle); + DRM_DEBUG_KMS("plugged\n"); hdmi_reg_writemask(hdata, HDMI_INTC_FLAG, ~0, HDMI_INTC_FLAG_HPD_PLUG); } - if (ctx->drm_dev && hdata->hpd_handle) + mutex_lock(&hdata->hdmi_mutex); + hdata->hpd = hdmi_reg_read(hdata, HDMI_HPD_STATUS); + if (hdata->powered && hdata->hpd) { + mutex_unlock(&hdata->hdmi_mutex); + goto out; + } + mutex_unlock(&hdata->hdmi_mutex); + + if (ctx->drm_dev) drm_helper_hpd_irq_event(ctx->drm_dev); +out: return IRQ_HANDLED; } @@ -2116,68 +2176,6 @@ static int hdmi_resources_cleanup(struct hdmi_context *hdata) return 0; } -static void hdmi_resource_poweron(struct hdmi_context *hdata) -{ - struct hdmi_resources *res = &hdata->res; - - DRM_DEBUG_KMS("[%d] %s\n", __LINE__, __func__); - - /* turn HDMI power on */ - regulator_bulk_enable(res->regul_count, res->regul_bulk); - /* power-on hdmi physical interface */ - clk_enable(res->hdmiphy); - /* turn clocks on */ - clk_enable(res->hdmi); - clk_enable(res->sclk_hdmi); - - hdmiphy_conf_reset(hdata); - hdmi_conf_reset(hdata); - hdmi_conf_init(hdata); - hdmi_audio_init(hdata); -} - -static void hdmi_resource_poweroff(struct hdmi_context *hdata) -{ - struct hdmi_resources *res = &hdata->res; - - DRM_DEBUG_KMS("[%d] %s\n", __LINE__, __func__); - - /* turn clocks off */ - clk_disable(res->sclk_hdmi); - clk_disable(res->hdmi); - /* power-off hdmiphy */ - clk_disable(res->hdmiphy); - /* turn HDMI power off */ - regulator_bulk_disable(res->regul_count, res->regul_bulk); -} - -static int hdmi_runtime_suspend(struct device *dev) -{ - struct exynos_drm_hdmi_context *ctx = get_hdmi_context(dev); - - DRM_DEBUG_KMS("%s\n", __func__); - - hdmi_resource_poweroff(ctx->ctx); - - return 0; -} - -static int hdmi_runtime_resume(struct device *dev) -{ - struct exynos_drm_hdmi_context *ctx = get_hdmi_context(dev); - - DRM_DEBUG_KMS("%s\n", __func__); - - hdmi_resource_poweron(ctx->ctx); - - return 0; -} - -static const struct dev_pm_ops hdmi_pm_ops = { - .runtime_suspend = hdmi_runtime_suspend, - .runtime_resume = hdmi_runtime_resume, -}; - static struct i2c_client *hdmi_ddc, *hdmi_hdmiphy; void hdmi_attach_ddc_client(struct i2c_client *ddc) @@ -2222,6 +2220,8 @@ static int __devinit hdmi_probe(struct platform_device *pdev) return -ENOMEM; } + mutex_init(&hdata->hdmi_mutex); + drm_hdmi_ctx->ctx = (void *)hdata; hdata->parent_ctx = (void *)drm_hdmi_ctx; @@ -2278,28 +2278,49 @@ static int __devinit hdmi_probe(struct platform_device *pdev) hdata->hdmiphy_port = hdmi_hdmiphy; - hdata->irq = platform_get_irq_byname(pdev, "internal_irq"); - if (hdata->irq < 0) { + hdata->external_irq = platform_get_irq_byname(pdev, "external_irq"); + if (hdata->external_irq < 0) { DRM_ERROR("failed to get platform irq\n"); - ret = hdata->irq; + ret = hdata->external_irq; goto err_hdmiphy; } - /* register hpd interrupt */ - ret = request_threaded_irq(hdata->irq, NULL, hdmi_irq_thread, - IRQF_ONESHOT, "drm_hdmi", drm_hdmi_ctx); + hdata->internal_irq = platform_get_irq_byname(pdev, "internal_irq"); + if (hdata->internal_irq < 0) { + DRM_ERROR("failed to get platform internal irq\n"); + ret = hdata->internal_irq; + goto err_hdmiphy; + } + + ret = request_threaded_irq(hdata->external_irq, NULL, + hdmi_external_irq_thread, IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "hdmi_external", drm_hdmi_ctx); if (ret) { - DRM_ERROR("request interrupt failed.\n"); + DRM_ERROR("failed to register hdmi internal interrupt\n"); goto err_hdmiphy; } + if (hdata->cfg_hpd) + hdata->cfg_hpd(false); + + ret = request_threaded_irq(hdata->internal_irq, NULL, + hdmi_internal_irq_thread, IRQF_ONESHOT, + "hdmi_internal", drm_hdmi_ctx); + if (ret) { + DRM_ERROR("failed to register hdmi internal interrupt\n"); + goto err_free_irq; + } + /* register specific callbacks to common hdmi. */ exynos_hdmi_ops_register(&hdmi_ops); - hdmi_resource_poweron(hdata); + pm_runtime_enable(dev); return 0; +err_free_irq: + free_irq(hdata->external_irq, drm_hdmi_ctx); err_hdmiphy: i2c_del_driver(&hdmiphy_driver); err_ddc: @@ -2319,15 +2340,15 @@ err_data: static int __devexit hdmi_remove(struct platform_device *pdev) { + struct device *dev = &pdev->dev; struct exynos_drm_hdmi_context *ctx = platform_get_drvdata(pdev); struct hdmi_context *hdata = ctx->ctx; DRM_DEBUG_KMS("[%d] %s\n", __LINE__, __func__); - hdmi_resource_poweroff(hdata); + pm_runtime_disable(dev); - disable_irq(hdata->irq); - free_irq(hdata->irq, hdata); + free_irq(hdata->internal_irq, hdata); hdmi_resources_cleanup(hdata); @@ -2352,6 +2373,5 @@ struct platform_driver hdmi_driver = { .driver = { .name = "exynos4-hdmi", .owner = THIS_MODULE, - .pm = &hdmi_pm_ops, }, }; diff --git a/drivers/gpu/drm/exynos/exynos_mixer.c b/drivers/gpu/drm/exynos/exynos_mixer.c index e15438c..a29a9a8 100644 --- a/drivers/gpu/drm/exynos/exynos_mixer.c +++ b/drivers/gpu/drm/exynos/exynos_mixer.c @@ -37,9 +37,6 @@ #include "exynos_drm_drv.h" #include "exynos_drm_hdmi.h" -#define MIXER_WIN_NR 3 -#define MIXER_DEFAULT_WIN 0 - #define get_mixer_context(dev) platform_get_drvdata(to_platform_device(dev)) struct hdmi_win_data { @@ -63,7 +60,6 @@ struct hdmi_win_data { }; struct mixer_resources { - struct device *dev; int irq; void __iomem *mixer_regs; void __iomem *vp_regs; @@ -76,10 +72,13 @@ struct mixer_resources { }; struct mixer_context { - unsigned int irq; + struct device *dev; int pipe; bool interlace; + bool powered; + u32 int_en; + struct mutex mixer_mutex; struct mixer_resources mixer_res; struct hdmi_win_data win_data[MIXER_WIN_NR]; }; @@ -591,6 +590,116 @@ static void vp_win_reset(struct mixer_context *ctx) WARN(tries == 0, "failed to reset Video Processor\n"); } +static void mixer_win_reset(struct mixer_context *ctx) +{ + struct mixer_resources *res = &ctx->mixer_res; + unsigned long flags; + u32 val; /* value stored to register */ + + spin_lock_irqsave(&res->reg_slock, flags); + mixer_vsync_set_update(ctx, false); + + mixer_reg_writemask(res, MXR_CFG, MXR_CFG_DST_HDMI, MXR_CFG_DST_MASK); + + /* set output in RGB888 mode */ + mixer_reg_writemask(res, MXR_CFG, MXR_CFG_OUT_RGB888, MXR_CFG_OUT_MASK); + + /* 16 beat burst in DMA */ + mixer_reg_writemask(res, MXR_STATUS, MXR_STATUS_16_BURST, + MXR_STATUS_BURST_MASK); + + /* setting default layer priority: layer1 > layer0 > video + * because typical usage scenario would be + * layer1 - OSD + * layer0 - framebuffer + * video - video overlay + */ + val = MXR_LAYER_CFG_GRP1_VAL(3); + val |= MXR_LAYER_CFG_GRP0_VAL(2); + val |= MXR_LAYER_CFG_VP_VAL(1); + mixer_reg_write(res, MXR_LAYER_CFG, val); + + /* setting background color */ + mixer_reg_write(res, MXR_BG_COLOR0, 0x008080); + mixer_reg_write(res, MXR_BG_COLOR1, 0x008080); + mixer_reg_write(res, MXR_BG_COLOR2, 0x008080); + + /* setting graphical layers */ + + val = MXR_GRP_CFG_COLOR_KEY_DISABLE; /* no blank key */ + val |= MXR_GRP_CFG_WIN_BLEND_EN; + val |= MXR_GRP_CFG_ALPHA_VAL(0xff); /* non-transparent alpha */ + + /* the same configuration for both layers */ + mixer_reg_write(res, MXR_GRAPHIC_CFG(0), val); + + val |= MXR_GRP_CFG_BLEND_PRE_MUL; + val |= MXR_GRP_CFG_PIXEL_BLEND_EN; + mixer_reg_write(res, MXR_GRAPHIC_CFG(1), val); + + /* configuration of Video Processor Registers */ + vp_win_reset(ctx); + vp_default_filter(res); + + /* disable all layers */ + mixer_reg_writemask(res, MXR_CFG, 0, MXR_CFG_GRP0_ENABLE); + mixer_reg_writemask(res, MXR_CFG, 0, MXR_CFG_GRP1_ENABLE); + mixer_reg_writemask(res, MXR_CFG, 0, MXR_CFG_VP_ENABLE); + + mixer_vsync_set_update(ctx, true); + spin_unlock_irqrestore(&res->reg_slock, flags); +} + +static void mixer_poweron(struct mixer_context *ctx) +{ + struct mixer_resources *res = &ctx->mixer_res; + + DRM_DEBUG_KMS("[%d] %s\n", __LINE__, __func__); + + mutex_lock(&ctx->mixer_mutex); + if (ctx->powered) { + mutex_unlock(&ctx->mixer_mutex); + return; + } + ctx->powered = true; + mutex_unlock(&ctx->mixer_mutex); + + pm_runtime_get_sync(ctx->dev); + + clk_enable(res->mixer); + clk_enable(res->vp); + clk_enable(res->sclk_mixer); + + mixer_reg_write(res, MXR_INT_EN, ctx->int_en); + mixer_win_reset(ctx); +} + +static void mixer_poweroff(struct mixer_context *ctx) +{ + struct mixer_resources *res = &ctx->mixer_res; + + DRM_DEBUG_KMS("[%d] %s\n", __LINE__, __func__); + + mutex_lock(&ctx->mixer_mutex); + if (!ctx->powered) + goto out; + mutex_unlock(&ctx->mixer_mutex); + + ctx->int_en = mixer_reg_read(res, MXR_INT_EN); + + clk_disable(res->mixer); + clk_disable(res->vp); + clk_disable(res->sclk_mixer); + + pm_runtime_put_sync(ctx->dev); + + mutex_lock(&ctx->mixer_mutex); + ctx->powered = false; + +out: + mutex_unlock(&ctx->mixer_mutex); +} + static int mixer_enable_vblank(void *ctx, int pipe) { struct mixer_context *mixer_ctx = ctx; @@ -618,6 +727,27 @@ static void mixer_disable_vblank(void *ctx) mixer_reg_writemask(res, MXR_INT_EN, 0, MXR_INT_EN_VSYNC); } +static void mixer_dpms(void *ctx, int mode) +{ + struct mixer_context *mixer_ctx = ctx; + + DRM_DEBUG_KMS("[%d] %s\n", __LINE__, __func__); + + switch (mode) { + case DRM_MODE_DPMS_ON: + mixer_poweron(mixer_ctx); + break; + case DRM_MODE_DPMS_STANDBY: + case DRM_MODE_DPMS_SUSPEND: + case DRM_MODE_DPMS_OFF: + mixer_poweroff(mixer_ctx); + break; + default: + DRM_DEBUG_KMS("unknown dpms mode: %d\n", mode); + break; + } +} + static void mixer_win_mode_set(void *ctx, struct exynos_drm_overlay *overlay) { @@ -643,7 +773,7 @@ static void mixer_win_mode_set(void *ctx, win = MIXER_DEFAULT_WIN; if (win < 0 || win > MIXER_WIN_NR) { - DRM_ERROR("overlay plane[%d] is wrong\n", win); + DRM_ERROR("mixer window[%d] is wrong\n", win); return; } @@ -672,44 +802,26 @@ static void mixer_win_mode_set(void *ctx, win_data->scan_flags = overlay->scan_flag; } -static void mixer_win_commit(void *ctx, int zpos) +static void mixer_win_commit(void *ctx, int win) { struct mixer_context *mixer_ctx = ctx; - int win = zpos; DRM_DEBUG_KMS("[%d] %s, win: %d\n", __LINE__, __func__, win); - if (win == DEFAULT_ZPOS) - win = MIXER_DEFAULT_WIN; - - if (win < 0 || win > MIXER_WIN_NR) { - DRM_ERROR("overlay plane[%d] is wrong\n", win); - return; - } - if (win > 1) vp_video_buffer(mixer_ctx, win); else mixer_graph_buffer(mixer_ctx, win); } -static void mixer_win_disable(void *ctx, int zpos) +static void mixer_win_disable(void *ctx, int win) { struct mixer_context *mixer_ctx = ctx; struct mixer_resources *res = &mixer_ctx->mixer_res; unsigned long flags; - int win = zpos; DRM_DEBUG_KMS("[%d] %s, win: %d\n", __LINE__, __func__, win); - if (win == DEFAULT_ZPOS) - win = MIXER_DEFAULT_WIN; - - if (win < 0 || win > MIXER_WIN_NR) { - DRM_ERROR("overlay plane[%d] is wrong\n", win); - return; - } - spin_lock_irqsave(&res->reg_slock, flags); mixer_vsync_set_update(mixer_ctx, false); @@ -723,6 +835,7 @@ static struct exynos_mixer_ops mixer_ops = { /* manager */ .enable_vblank = mixer_enable_vblank, .disable_vblank = mixer_disable_vblank, + .dpms = mixer_dpms, /* overlay */ .win_mode_set = mixer_win_mode_set, @@ -811,117 +924,6 @@ out: return IRQ_HANDLED; } -static void mixer_win_reset(struct mixer_context *ctx) -{ - struct mixer_resources *res = &ctx->mixer_res; - unsigned long flags; - u32 val; /* value stored to register */ - - spin_lock_irqsave(&res->reg_slock, flags); - mixer_vsync_set_update(ctx, false); - - mixer_reg_writemask(res, MXR_CFG, MXR_CFG_DST_HDMI, MXR_CFG_DST_MASK); - - /* set output in RGB888 mode */ - mixer_reg_writemask(res, MXR_CFG, MXR_CFG_OUT_RGB888, MXR_CFG_OUT_MASK); - - /* 16 beat burst in DMA */ - mixer_reg_writemask(res, MXR_STATUS, MXR_STATUS_16_BURST, - MXR_STATUS_BURST_MASK); - - /* setting default layer priority: layer1 > layer0 > video - * because typical usage scenario would be - * layer1 - OSD - * layer0 - framebuffer - * video - video overlay - */ - val = MXR_LAYER_CFG_GRP1_VAL(3); - val |= MXR_LAYER_CFG_GRP0_VAL(2); - val |= MXR_LAYER_CFG_VP_VAL(1); - mixer_reg_write(res, MXR_LAYER_CFG, val); - - /* setting background color */ - mixer_reg_write(res, MXR_BG_COLOR0, 0x008080); - mixer_reg_write(res, MXR_BG_COLOR1, 0x008080); - mixer_reg_write(res, MXR_BG_COLOR2, 0x008080); - - /* setting graphical layers */ - - val = MXR_GRP_CFG_COLOR_KEY_DISABLE; /* no blank key */ - val |= MXR_GRP_CFG_WIN_BLEND_EN; - val |= MXR_GRP_CFG_ALPHA_VAL(0xff); /* non-transparent alpha */ - - /* the same configuration for both layers */ - mixer_reg_write(res, MXR_GRAPHIC_CFG(0), val); - - val |= MXR_GRP_CFG_BLEND_PRE_MUL; - val |= MXR_GRP_CFG_PIXEL_BLEND_EN; - mixer_reg_write(res, MXR_GRAPHIC_CFG(1), val); - - /* configuration of Video Processor Registers */ - vp_win_reset(ctx); - vp_default_filter(res); - - /* disable all layers */ - mixer_reg_writemask(res, MXR_CFG, 0, MXR_CFG_GRP0_ENABLE); - mixer_reg_writemask(res, MXR_CFG, 0, MXR_CFG_GRP1_ENABLE); - mixer_reg_writemask(res, MXR_CFG, 0, MXR_CFG_VP_ENABLE); - - mixer_vsync_set_update(ctx, true); - spin_unlock_irqrestore(&res->reg_slock, flags); -} - -static void mixer_resource_poweron(struct mixer_context *ctx) -{ - struct mixer_resources *res = &ctx->mixer_res; - - DRM_DEBUG_KMS("[%d] %s\n", __LINE__, __func__); - - clk_enable(res->mixer); - clk_enable(res->vp); - clk_enable(res->sclk_mixer); - - mixer_win_reset(ctx); -} - -static void mixer_resource_poweroff(struct mixer_context *ctx) -{ - struct mixer_resources *res = &ctx->mixer_res; - - DRM_DEBUG_KMS("[%d] %s\n", __LINE__, __func__); - - clk_disable(res->mixer); - clk_disable(res->vp); - clk_disable(res->sclk_mixer); -} - -static int mixer_runtime_resume(struct device *dev) -{ - struct exynos_drm_hdmi_context *ctx = get_mixer_context(dev); - - DRM_DEBUG_KMS("resume - start\n"); - - mixer_resource_poweron(ctx->ctx); - - return 0; -} - -static int mixer_runtime_suspend(struct device *dev) -{ - struct exynos_drm_hdmi_context *ctx = get_mixer_context(dev); - - DRM_DEBUG_KMS("suspend - start\n"); - - mixer_resource_poweroff(ctx->ctx); - - return 0; -} - -static const struct dev_pm_ops mixer_pm_ops = { - .runtime_suspend = mixer_runtime_suspend, - .runtime_resume = mixer_runtime_resume, -}; - static int __devinit mixer_resources_init(struct exynos_drm_hdmi_context *ctx, struct platform_device *pdev) { @@ -931,7 +933,6 @@ static int __devinit mixer_resources_init(struct exynos_drm_hdmi_context *ctx, struct resource *res; int ret; - mixer_res->dev = dev; spin_lock_init(&mixer_res->reg_slock); mixer_res->mixer = clk_get(dev, "mixer"); @@ -1027,7 +1028,6 @@ fail: clk_put(mixer_res->vp); if (!IS_ERR_OR_NULL(mixer_res->mixer)) clk_put(mixer_res->mixer); - mixer_res->dev = NULL; return ret; } @@ -1035,7 +1035,6 @@ static void mixer_resources_cleanup(struct mixer_context *ctx) { struct mixer_resources *res = &ctx->mixer_res; - disable_irq(res->irq); free_irq(res->irq, ctx); iounmap(res->vp_regs); @@ -1064,6 +1063,9 @@ static int __devinit mixer_probe(struct platform_device *pdev) return -ENOMEM; } + mutex_init(&ctx->mixer_mutex); + + ctx->dev = &pdev->dev; drm_hdmi_ctx->ctx = (void *)ctx; platform_set_drvdata(pdev, drm_hdmi_ctx); @@ -1076,7 +1078,7 @@ static int __devinit mixer_probe(struct platform_device *pdev) /* register specific callback point to common hdmi. */ exynos_mixer_ops_register(&mixer_ops); - mixer_resource_poweron(ctx); + pm_runtime_enable(dev); return 0; @@ -1095,7 +1097,8 @@ static int mixer_remove(struct platform_device *pdev) dev_info(dev, "remove successful\n"); - mixer_resource_poweroff(ctx); + pm_runtime_disable(&pdev->dev); + mixer_resources_cleanup(ctx); return 0; @@ -1105,7 +1108,6 @@ struct platform_driver mixer_driver = { .driver = { .name = "s5p-mixer", .owner = THIS_MODULE, - .pm = &mixer_pm_ops, }, .probe = mixer_probe, .remove = __devexit_p(mixer_remove), -- cgit v0.10.2 From ab27af85e3154380e65a293b893f79ea0416afdf Mon Sep 17 00:00:00 2001 From: Joonyoung Shim Date: Mon, 23 Apr 2012 19:35:51 +0900 Subject: drm/exynos: add PM functions for hdmi and mixer This patch supports the PM for hdmi and mixer. Turn off hdmi and mixer when suspended, and when resume, will turn on them by hdmi hotplug detection if hdmi is attached. Signed-off-by: Joonyoung Shim Signed-off-by: Kyungmin Park Signed-off-by: Inki Dae diff --git a/drivers/gpu/drm/exynos/exynos_hdmi.c b/drivers/gpu/drm/exynos/exynos_hdmi.c index 9212d7d..ad53c48 100644 --- a/drivers/gpu/drm/exynos/exynos_hdmi.c +++ b/drivers/gpu/drm/exynos/exynos_hdmi.c @@ -2367,11 +2367,43 @@ static int __devexit hdmi_remove(struct platform_device *pdev) return 0; } +#ifdef CONFIG_PM_SLEEP +static int hdmi_suspend(struct device *dev) +{ + struct exynos_drm_hdmi_context *ctx = get_hdmi_context(dev); + struct hdmi_context *hdata = ctx->ctx; + + disable_irq(hdata->internal_irq); + disable_irq(hdata->external_irq); + + hdata->hpd = false; + if (ctx->drm_dev) + drm_helper_hpd_irq_event(ctx->drm_dev); + + hdmi_poweroff(hdata); + + return 0; +} + +static int hdmi_resume(struct device *dev) +{ + struct exynos_drm_hdmi_context *ctx = get_hdmi_context(dev); + struct hdmi_context *hdata = ctx->ctx; + + enable_irq(hdata->external_irq); + enable_irq(hdata->internal_irq); + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(hdmi_pm_ops, hdmi_suspend, hdmi_resume); + struct platform_driver hdmi_driver = { .probe = hdmi_probe, .remove = __devexit_p(hdmi_remove), .driver = { .name = "exynos4-hdmi", .owner = THIS_MODULE, + .pm = &hdmi_pm_ops, }, }; diff --git a/drivers/gpu/drm/exynos/exynos_mixer.c b/drivers/gpu/drm/exynos/exynos_mixer.c index a29a9a8..2f6727a 100644 --- a/drivers/gpu/drm/exynos/exynos_mixer.c +++ b/drivers/gpu/drm/exynos/exynos_mixer.c @@ -1104,10 +1104,25 @@ static int mixer_remove(struct platform_device *pdev) return 0; } +#ifdef CONFIG_PM_SLEEP +static int mixer_suspend(struct device *dev) +{ + struct exynos_drm_hdmi_context *drm_hdmi_ctx = get_mixer_context(dev); + struct mixer_context *ctx = drm_hdmi_ctx->ctx; + + mixer_poweroff(ctx); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(mixer_pm_ops, mixer_suspend, NULL); + struct platform_driver mixer_driver = { .driver = { .name = "s5p-mixer", .owner = THIS_MODULE, + .pm = &mixer_pm_ops, }, .probe = mixer_probe, .remove = __devexit_p(mixer_remove), -- cgit v0.10.2 From 8379e4823d04e2552e1395c2410fc16733e28c6e Mon Sep 17 00:00:00 2001 From: Seung-Woo Kim Date: Mon, 23 Apr 2012 20:30:13 +0900 Subject: drm/exynos: fixed wrong pageflip finish event for interlace mode Pageflip finish event for interlace mode has bug on checking top field vsync because of comparing between dma address converted by start coordinates and non-converted dma address. Signed-off-by: Seung-Woo Kim Signed-off-by: Kyungmin Park Signed-off-by: Inki Dae diff --git a/drivers/gpu/drm/exynos/exynos_mixer.c b/drivers/gpu/drm/exynos/exynos_mixer.c index 2f6727a..3bc01a6 100644 --- a/drivers/gpu/drm/exynos/exynos_mixer.c +++ b/drivers/gpu/drm/exynos/exynos_mixer.c @@ -886,7 +886,7 @@ static irqreturn_t mixer_irq_handler(int irq, void *arg) struct exynos_drm_hdmi_context *drm_hdmi_ctx = arg; struct mixer_context *ctx = drm_hdmi_ctx->ctx; struct mixer_resources *res = &ctx->mixer_res; - u32 val, val_base; + u32 val, base, shadow; spin_lock(&res->reg_slock); @@ -897,12 +897,14 @@ static irqreturn_t mixer_irq_handler(int irq, void *arg) if (val & MXR_INT_STATUS_VSYNC) { /* interlace scan need to check shadow register */ if (ctx->interlace) { - val_base = mixer_reg_read(res, MXR_GRAPHIC_BASE_S(0)); - if (ctx->win_data[0].dma_addr != val_base) + base = mixer_reg_read(res, MXR_GRAPHIC_BASE(0)); + shadow = mixer_reg_read(res, MXR_GRAPHIC_BASE_S(0)); + if (base != shadow) goto out; - val_base = mixer_reg_read(res, MXR_GRAPHIC_BASE_S(1)); - if (ctx->win_data[1].dma_addr != val_base) + base = mixer_reg_read(res, MXR_GRAPHIC_BASE(1)); + shadow = mixer_reg_read(res, MXR_GRAPHIC_BASE_S(1)); + if (base != shadow) goto out; } -- cgit v0.10.2 From 872d20d66c0b7de0787675dce8569a61e4d9bc00 Mon Sep 17 00:00:00 2001 From: Seung-Woo Kim Date: Tue, 24 Apr 2012 17:39:15 +0900 Subject: drm/exynos: enable dvi mode for dvi monitor Hdmi monitor and dvi monitor can be distinguished with edid. This patch enables dvi mode if dvi monitor is connected and does not enable audio feature for dvi mode because dvi has no audio feature. Signed-off-by: Seung-Woo Kim Signed-off-by: Kyungmin Park Signed-off-by: Inki Dae diff --git a/drivers/gpu/drm/exynos/exynos_hdmi.c b/drivers/gpu/drm/exynos/exynos_hdmi.c index ad53c48..5ca0a9f 100644 --- a/drivers/gpu/drm/exynos/exynos_hdmi.c +++ b/drivers/gpu/drm/exynos/exynos_hdmi.c @@ -60,6 +60,7 @@ struct hdmi_context { bool hpd; bool powered; bool is_v13; + bool dvi_mode; struct mutex hdmi_mutex; struct resource *regs_res; @@ -1211,10 +1212,12 @@ static int hdmi_get_edid(void *ctx, struct drm_connector *connector, raw_edid = drm_get_edid(connector, hdata->ddc_port->adapter); if (raw_edid) { + hdata->dvi_mode = !drm_detect_hdmi_monitor(raw_edid); memcpy(edid, raw_edid, min((1 + raw_edid->extensions) * EDID_LENGTH, len)); - DRM_DEBUG_KMS("width[%d] x height[%d]\n", - raw_edid->width_cm, raw_edid->height_cm); + DRM_DEBUG_KMS("%s : width[%d] x height[%d]\n", + (hdata->dvi_mode ? "dvi monitor" : "hdmi monitor"), + raw_edid->width_cm, raw_edid->height_cm); } else { return -ENODEV; } @@ -1437,10 +1440,7 @@ static void hdmi_audio_init(struct hdmi_context *hdata) static void hdmi_audio_control(struct hdmi_context *hdata, bool onoff) { - u32 mod; - - mod = hdmi_reg_read(hdata, HDMI_MODE_SEL); - if (mod & HDMI_DVI_MODE_EN) + if (hdata->dvi_mode) return; hdmi_reg_writeb(hdata, HDMI_AUI_CON, onoff ? 2 : 0); @@ -1479,6 +1479,14 @@ static void hdmi_conf_init(struct hdmi_context *hdata) /* disable bluescreen */ hdmi_reg_writemask(hdata, HDMI_CON_0, 0, HDMI_BLUE_SCR_EN); + if (hdata->dvi_mode) { + /* choose DVI mode */ + hdmi_reg_writemask(hdata, HDMI_MODE_SEL, + HDMI_MODE_DVI_EN, HDMI_MODE_MASK); + hdmi_reg_writeb(hdata, HDMI_CON_2, + HDMI_VID_PREAMBLE_DIS | HDMI_GUARD_BAND_DIS); + } + if (hdata->is_v13) { /* choose bluescreen (fecal) color */ hdmi_reg_writeb(hdata, HDMI_V13_BLUE_SCREEN_0, 0x12); diff --git a/drivers/gpu/drm/exynos/regs-hdmi.h b/drivers/gpu/drm/exynos/regs-hdmi.h index 3c04bea..9cc7c5e 100644 --- a/drivers/gpu/drm/exynos/regs-hdmi.h +++ b/drivers/gpu/drm/exynos/regs-hdmi.h @@ -138,14 +138,16 @@ #define HDMI_ASP_MASK (1 << 2) #define HDMI_EN (1 << 0) +/* HDMI_CON_2 */ +#define HDMI_VID_PREAMBLE_DIS (1 << 5) +#define HDMI_GUARD_BAND_DIS (1 << 1) + /* HDMI_PHY_STATUS */ #define HDMI_PHY_STATUS_READY (1 << 0) /* HDMI_MODE_SEL */ #define HDMI_MODE_HDMI_EN (1 << 1) #define HDMI_MODE_DVI_EN (1 << 0) -#define HDMI_DVI_MODE_EN (1) -#define HDMI_DVI_MODE_DIS (0) #define HDMI_MODE_MASK (3 << 0) /* HDMI_TG_CMD */ -- cgit v0.10.2 From e540adf3421b1c79c80e4224caa4555284bd856b Mon Sep 17 00:00:00 2001 From: Seung-Woo Kim Date: Tue, 24 Apr 2012 17:55:06 +0900 Subject: drm/exynos: add additional display mode for hdmi 1080p@30Hz mode is added to hdmi display mode. Signed-off-by: Seung-Woo Kim Signed-off-by: Kyungmin Park Signed-off-by: Inki Dae diff --git a/drivers/gpu/drm/exynos/exynos_hdmi.c b/drivers/gpu/drm/exynos/exynos_hdmi.c index 5ca0a9f..a137e9e 100644 --- a/drivers/gpu/drm/exynos/exynos_hdmi.c +++ b/drivers/gpu/drm/exynos/exynos_hdmi.c @@ -362,6 +362,13 @@ static const u8 hdmiphy_conf27_027[32] = { 0x54, 0xe3, 0x24, 0x00, 0x00, 0x00, 0x01, 0x00, }; +static const u8 hdmiphy_conf74_176[32] = { + 0x01, 0xd1, 0x1f, 0x10, 0x40, 0x5b, 0xef, 0x08, + 0x81, 0xa0, 0xb9, 0xd8, 0x45, 0xa0, 0xac, 0x80, + 0x5a, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86, + 0x54, 0xa6, 0x24, 0x01, 0x00, 0x00, 0x01, 0x00, +}; + static const u8 hdmiphy_conf74_25[32] = { 0x01, 0xd1, 0x1f, 0x10, 0x40, 0x40, 0xf8, 0x08, 0x81, 0xa0, 0xba, 0xd8, 0x45, 0xa0, 0xac, 0x80, @@ -751,6 +758,63 @@ static const struct hdmi_preset_conf hdmi_conf_1080i60 = { }, }; +static const struct hdmi_preset_conf hdmi_conf_1080p30 = { + .core = { + .h_blank = {0x18, 0x01}, + .v2_blank = {0x65, 0x04}, + .v1_blank = {0x2d, 0x00}, + .v_line = {0x65, 0x04}, + .h_line = {0x98, 0x08}, + .hsync_pol = {0x00}, + .vsync_pol = {0x00}, + .int_pro_mode = {0x00}, + .v_blank_f0 = {0xff, 0xff}, + .v_blank_f1 = {0xff, 0xff}, + .h_sync_start = {0x56, 0x00}, + .h_sync_end = {0x82, 0x00}, + .v_sync_line_bef_2 = {0x09, 0x00}, + .v_sync_line_bef_1 = {0x04, 0x00}, + .v_sync_line_aft_2 = {0xff, 0xff}, + .v_sync_line_aft_1 = {0xff, 0xff}, + .v_sync_line_aft_pxl_2 = {0xff, 0xff}, + .v_sync_line_aft_pxl_1 = {0xff, 0xff}, + .v_blank_f2 = {0xff, 0xff}, + .v_blank_f3 = {0xff, 0xff}, + .v_blank_f4 = {0xff, 0xff}, + .v_blank_f5 = {0xff, 0xff}, + .v_sync_line_aft_3 = {0xff, 0xff}, + .v_sync_line_aft_4 = {0xff, 0xff}, + .v_sync_line_aft_5 = {0xff, 0xff}, + .v_sync_line_aft_6 = {0xff, 0xff}, + .v_sync_line_aft_pxl_3 = {0xff, 0xff}, + .v_sync_line_aft_pxl_4 = {0xff, 0xff}, + .v_sync_line_aft_pxl_5 = {0xff, 0xff}, + .v_sync_line_aft_pxl_6 = {0xff, 0xff}, + .vact_space_1 = {0xff, 0xff}, + .vact_space_2 = {0xff, 0xff}, + .vact_space_3 = {0xff, 0xff}, + .vact_space_4 = {0xff, 0xff}, + .vact_space_5 = {0xff, 0xff}, + .vact_space_6 = {0xff, 0xff}, + /* other don't care */ + }, + .tg = { + 0x00, /* cmd */ + 0x98, 0x08, /* h_fsz */ + 0x18, 0x01, 0x80, 0x07, /* hact */ + 0x65, 0x04, /* v_fsz */ + 0x01, 0x00, 0x33, 0x02, /* vsync */ + 0x2d, 0x00, 0x38, 0x04, /* vact */ + 0x33, 0x02, /* field_chg */ + 0x48, 0x02, /* vact_st2 */ + 0x00, 0x00, /* vact_st3 */ + 0x00, 0x00, /* vact_st4 */ + 0x01, 0x00, 0x01, 0x00, /* vsync top/bot */ + 0x01, 0x00, 0x33, 0x02, /* field top/bot */ + 0x00, /* 3d FP */ + }, +}; + static const struct hdmi_preset_conf hdmi_conf_1080p50 = { .core = { .h_blank = {0xd0, 0x02}, @@ -865,6 +929,7 @@ static const struct hdmi_conf hdmi_confs[] = { { 1280, 720, 60, false, hdmiphy_conf74_25, &hdmi_conf_720p60 }, { 1920, 1080, 50, true, hdmiphy_conf74_25, &hdmi_conf_1080i50 }, { 1920, 1080, 60, true, hdmiphy_conf74_25, &hdmi_conf_1080i60 }, + { 1920, 1080, 30, false, hdmiphy_conf74_176, &hdmi_conf_1080p30 }, { 1920, 1080, 50, false, hdmiphy_conf148_5, &hdmi_conf_1080p50 }, { 1920, 1080, 60, false, hdmiphy_conf148_5, &hdmi_conf_1080p60 }, }; -- cgit v0.10.2 From 0d8071ee11734f92c33c1cc8926314c4e650622c Mon Sep 17 00:00:00 2001 From: Seung-Woo Kim Date: Tue, 24 Apr 2012 18:43:10 +0900 Subject: drm/exynos: added source size to overlay structure Set plane has source size but exynos overlay structure did not consider it. This patch adds source size to overlay structure. For set crtc, source size is set from crtc size. Signed-off-by: Seung-Woo Kim Signed-off-by: Kyungmin Park Signed-off-by: Inki Dae diff --git a/drivers/gpu/drm/exynos/exynos_drm_crtc.c b/drivers/gpu/drm/exynos/exynos_drm_crtc.c index 3486ffe..4afb625 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_crtc.c +++ b/drivers/gpu/drm/exynos/exynos_drm_crtc.c @@ -105,6 +105,8 @@ int exynos_drm_overlay_update(struct exynos_drm_overlay *overlay, overlay->fb_y = pos->fb_y; overlay->fb_width = fb->width; overlay->fb_height = fb->height; + overlay->src_width = pos->src_w; + overlay->src_height = pos->src_h; overlay->bpp = fb->bits_per_pixel; overlay->pitch = fb->pitches[0]; overlay->pixel_format = fb->pixel_format; @@ -153,6 +155,8 @@ static int exynos_drm_crtc_update(struct drm_crtc *crtc) pos.crtc_y = 0; pos.crtc_w = fb->width - crtc->x; pos.crtc_h = fb->height - crtc->y; + pos.src_w = pos.crtc_w; + pos.src_h = pos.crtc_h; return exynos_drm_overlay_update(overlay, crtc->fb, mode, &pos); } diff --git a/drivers/gpu/drm/exynos/exynos_drm_crtc.h b/drivers/gpu/drm/exynos/exynos_drm_crtc.h index 25f72a6..16b8e21 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_crtc.h +++ b/drivers/gpu/drm/exynos/exynos_drm_crtc.h @@ -42,6 +42,8 @@ void exynos_drm_crtc_disable_vblank(struct drm_device *dev, int crtc); * - the unit is screen coordinates. * @fb_y: offset y on a framebuffer to be displayed * - the unit is screen coordinates. + * @src_w: width of source area to be displayed from a framebuffer. + * @src_h: height of source area to be displayed from a framebuffer. * @crtc_x: offset x on hardware screen. * @crtc_y: offset y on hardware screen. * @crtc_w: width of hardware screen. @@ -50,6 +52,8 @@ void exynos_drm_crtc_disable_vblank(struct drm_device *dev, int crtc); struct exynos_drm_crtc_pos { unsigned int fb_x; unsigned int fb_y; + unsigned int src_w; + unsigned int src_h; unsigned int crtc_x; unsigned int crtc_y; unsigned int crtc_w; diff --git a/drivers/gpu/drm/exynos/exynos_drm_drv.h b/drivers/gpu/drm/exynos/exynos_drm_drv.h index 1d81417..5f5b362 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_drv.h +++ b/drivers/gpu/drm/exynos/exynos_drm_drv.h @@ -77,6 +77,8 @@ struct exynos_drm_overlay_ops { * - the unit is screen coordinates. * @fb_width: width of a framebuffer. * @fb_height: height of a framebuffer. + * @src_width: width of a partial image to be displayed from framebuffer. + * @src_height: height of a partial image to be displayed from framebuffer. * @crtc_x: offset x on hardware screen. * @crtc_y: offset y on hardware screen. * @crtc_width: window width to be displayed (hardware screen). @@ -108,6 +110,8 @@ struct exynos_drm_overlay { unsigned int fb_y; unsigned int fb_width; unsigned int fb_height; + unsigned int src_width; + unsigned int src_height; unsigned int crtc_x; unsigned int crtc_y; unsigned int crtc_width; diff --git a/drivers/gpu/drm/exynos/exynos_drm_plane.c b/drivers/gpu/drm/exynos/exynos_drm_plane.c index f92fe4c..c4c6525 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_plane.c +++ b/drivers/gpu/drm/exynos/exynos_drm_plane.c @@ -41,8 +41,6 @@ exynos_update_plane(struct drm_plane *plane, struct drm_crtc *crtc, container_of(plane, struct exynos_plane, base); struct exynos_drm_overlay *overlay = &exynos_plane->overlay; struct exynos_drm_crtc_pos pos; - unsigned int x = src_x >> 16; - unsigned int y = src_y >> 16; int ret; DRM_DEBUG_KMS("[%d] %s\n", __LINE__, __func__); @@ -53,10 +51,12 @@ exynos_update_plane(struct drm_plane *plane, struct drm_crtc *crtc, pos.crtc_w = crtc_w; pos.crtc_h = crtc_h; - pos.fb_x = x; - pos.fb_y = y; + /* considering 16.16 fixed point of source values */ + pos.fb_x = src_x >> 16; + pos.fb_y = src_y >> 16; + pos.src_w = src_w >> 16; + pos.src_h = src_h >> 16; - /* TODO: scale feature */ ret = exynos_drm_overlay_update(overlay, fb, &crtc->mode, &pos); if (ret < 0) return ret; -- cgit v0.10.2 From 8dcb96b628a0749184bbcc5e6c94463f89405c58 Mon Sep 17 00:00:00 2001 From: Seung-Woo Kim Date: Tue, 24 Apr 2012 18:52:22 +0900 Subject: drm/exynos: added vp scaling feature for hdmi This patch adds vp scaling feature for exynos hdmi. Scaling ratio between source and destination is used for width and height. Also meaningless variables to set registers are cleaned. Signed-off-by: Seung-Woo Kim Signed-off-by: Kyungmin Park Signed-off-by: Inki Dae diff --git a/drivers/gpu/drm/exynos/exynos_mixer.c b/drivers/gpu/drm/exynos/exynos_mixer.c index 3bc01a6..68ef010 100644 --- a/drivers/gpu/drm/exynos/exynos_mixer.c +++ b/drivers/gpu/drm/exynos/exynos_mixer.c @@ -54,6 +54,8 @@ struct hdmi_win_data { unsigned int fb_y; unsigned int fb_width; unsigned int fb_height; + unsigned int src_width; + unsigned int src_height; unsigned int mode_width; unsigned int mode_height; unsigned int scan_flags; @@ -351,10 +353,7 @@ static void vp_video_buffer(struct mixer_context *ctx, int win) struct mixer_resources *res = &ctx->mixer_res; unsigned long flags; struct hdmi_win_data *win_data; - unsigned int full_width, full_height, width, height; unsigned int x_ratio, y_ratio; - unsigned int src_x_offset, src_y_offset, dst_x_offset, dst_y_offset; - unsigned int mode_width, mode_height; unsigned int buf_num; dma_addr_t luma_addr[2], chroma_addr[2]; bool tiled_mode = false; @@ -381,21 +380,9 @@ static void vp_video_buffer(struct mixer_context *ctx, int win) return; } - full_width = win_data->fb_width; - full_height = win_data->fb_height; - width = win_data->crtc_width; - height = win_data->crtc_height; - mode_width = win_data->mode_width; - mode_height = win_data->mode_height; - /* scaling feature: (src << 16) / dst */ - x_ratio = (width << 16) / width; - y_ratio = (height << 16) / height; - - src_x_offset = win_data->fb_x; - src_y_offset = win_data->fb_y; - dst_x_offset = win_data->crtc_x; - dst_y_offset = win_data->crtc_y; + x_ratio = (win_data->src_width << 16) / win_data->crtc_width; + y_ratio = (win_data->src_height << 16) / win_data->crtc_height; if (buf_num == 2) { luma_addr[0] = win_data->dma_addr; @@ -403,7 +390,7 @@ static void vp_video_buffer(struct mixer_context *ctx, int win) } else { luma_addr[0] = win_data->dma_addr; chroma_addr[0] = win_data->dma_addr - + (full_width * full_height); + + (win_data->fb_width * win_data->fb_height); } if (win_data->scan_flags & DRM_MODE_FLAG_INTERLACE) { @@ -412,8 +399,8 @@ static void vp_video_buffer(struct mixer_context *ctx, int win) luma_addr[1] = luma_addr[0] + 0x40; chroma_addr[1] = chroma_addr[0] + 0x40; } else { - luma_addr[1] = luma_addr[0] + full_width; - chroma_addr[1] = chroma_addr[0] + full_width; + luma_addr[1] = luma_addr[0] + win_data->fb_width; + chroma_addr[1] = chroma_addr[0] + win_data->fb_width; } } else { ctx->interlace = false; @@ -434,26 +421,26 @@ static void vp_video_buffer(struct mixer_context *ctx, int win) vp_reg_writemask(res, VP_MODE, val, VP_MODE_FMT_MASK); /* setting size of input image */ - vp_reg_write(res, VP_IMG_SIZE_Y, VP_IMG_HSIZE(full_width) | - VP_IMG_VSIZE(full_height)); + vp_reg_write(res, VP_IMG_SIZE_Y, VP_IMG_HSIZE(win_data->fb_width) | + VP_IMG_VSIZE(win_data->fb_height)); /* chroma height has to reduced by 2 to avoid chroma distorions */ - vp_reg_write(res, VP_IMG_SIZE_C, VP_IMG_HSIZE(full_width) | - VP_IMG_VSIZE(full_height / 2)); + vp_reg_write(res, VP_IMG_SIZE_C, VP_IMG_HSIZE(win_data->fb_width) | + VP_IMG_VSIZE(win_data->fb_height / 2)); - vp_reg_write(res, VP_SRC_WIDTH, width); - vp_reg_write(res, VP_SRC_HEIGHT, height); + vp_reg_write(res, VP_SRC_WIDTH, win_data->src_width); + vp_reg_write(res, VP_SRC_HEIGHT, win_data->src_height); vp_reg_write(res, VP_SRC_H_POSITION, - VP_SRC_H_POSITION_VAL(src_x_offset)); - vp_reg_write(res, VP_SRC_V_POSITION, src_y_offset); + VP_SRC_H_POSITION_VAL(win_data->fb_x)); + vp_reg_write(res, VP_SRC_V_POSITION, win_data->fb_y); - vp_reg_write(res, VP_DST_WIDTH, width); - vp_reg_write(res, VP_DST_H_POSITION, dst_x_offset); + vp_reg_write(res, VP_DST_WIDTH, win_data->crtc_width); + vp_reg_write(res, VP_DST_H_POSITION, win_data->crtc_x); if (ctx->interlace) { - vp_reg_write(res, VP_DST_HEIGHT, height / 2); - vp_reg_write(res, VP_DST_V_POSITION, dst_y_offset / 2); + vp_reg_write(res, VP_DST_HEIGHT, win_data->crtc_height / 2); + vp_reg_write(res, VP_DST_V_POSITION, win_data->crtc_y / 2); } else { - vp_reg_write(res, VP_DST_HEIGHT, height); - vp_reg_write(res, VP_DST_V_POSITION, dst_y_offset); + vp_reg_write(res, VP_DST_HEIGHT, win_data->crtc_height); + vp_reg_write(res, VP_DST_V_POSITION, win_data->crtc_y); } vp_reg_write(res, VP_H_RATIO, x_ratio); @@ -467,8 +454,8 @@ static void vp_video_buffer(struct mixer_context *ctx, int win) vp_reg_write(res, VP_TOP_C_PTR, chroma_addr[0]); vp_reg_write(res, VP_BOT_C_PTR, chroma_addr[1]); - mixer_cfg_scan(ctx, mode_height); - mixer_cfg_rgb_fmt(ctx, mode_height); + mixer_cfg_scan(ctx, win_data->mode_height); + mixer_cfg_rgb_fmt(ctx, win_data->mode_height); mixer_cfg_layer(ctx, win, true); mixer_run(ctx); @@ -483,10 +470,8 @@ static void mixer_graph_buffer(struct mixer_context *ctx, int win) struct mixer_resources *res = &ctx->mixer_res; unsigned long flags; struct hdmi_win_data *win_data; - unsigned int full_width, width, height; unsigned int x_ratio, y_ratio; unsigned int src_x_offset, src_y_offset, dst_x_offset, dst_y_offset; - unsigned int mode_width, mode_height; dma_addr_t dma_addr; unsigned int fmt; u32 val; @@ -509,26 +494,17 @@ static void mixer_graph_buffer(struct mixer_context *ctx, int win) fmt = ARGB8888; } - dma_addr = win_data->dma_addr; - full_width = win_data->fb_width; - width = win_data->crtc_width; - height = win_data->crtc_height; - mode_width = win_data->mode_width; - mode_height = win_data->mode_height; - /* 2x scaling feature */ x_ratio = 0; y_ratio = 0; - src_x_offset = win_data->fb_x; - src_y_offset = win_data->fb_y; dst_x_offset = win_data->crtc_x; dst_y_offset = win_data->crtc_y; /* converting dma address base and source offset */ - dma_addr = dma_addr - + (src_x_offset * win_data->bpp >> 3) - + (src_y_offset * full_width * win_data->bpp >> 3); + dma_addr = win_data->dma_addr + + (win_data->fb_x * win_data->bpp >> 3) + + (win_data->fb_y * win_data->fb_width * win_data->bpp >> 3); src_x_offset = 0; src_y_offset = 0; @@ -545,10 +521,10 @@ static void mixer_graph_buffer(struct mixer_context *ctx, int win) MXR_GRP_CFG_FORMAT_VAL(fmt), MXR_GRP_CFG_FORMAT_MASK); /* setup geometry */ - mixer_reg_write(res, MXR_GRAPHIC_SPAN(win), full_width); + mixer_reg_write(res, MXR_GRAPHIC_SPAN(win), win_data->fb_width); - val = MXR_GRP_WH_WIDTH(width); - val |= MXR_GRP_WH_HEIGHT(height); + val = MXR_GRP_WH_WIDTH(win_data->crtc_width); + val |= MXR_GRP_WH_HEIGHT(win_data->crtc_height); val |= MXR_GRP_WH_H_SCALE(x_ratio); val |= MXR_GRP_WH_V_SCALE(y_ratio); mixer_reg_write(res, MXR_GRAPHIC_WH(win), val); @@ -566,8 +542,8 @@ static void mixer_graph_buffer(struct mixer_context *ctx, int win) /* set buffer address to mixer */ mixer_reg_write(res, MXR_GRAPHIC_BASE(win), dma_addr); - mixer_cfg_scan(ctx, mode_height); - mixer_cfg_rgb_fmt(ctx, mode_height); + mixer_cfg_scan(ctx, win_data->mode_height); + mixer_cfg_rgb_fmt(ctx, win_data->mode_height); mixer_cfg_layer(ctx, win, true); mixer_run(ctx); @@ -795,6 +771,8 @@ static void mixer_win_mode_set(void *ctx, win_data->fb_y = overlay->fb_y; win_data->fb_width = overlay->fb_width; win_data->fb_height = overlay->fb_height; + win_data->src_width = overlay->src_width; + win_data->src_height = overlay->src_height; win_data->mode_width = overlay->mode_width; win_data->mode_height = overlay->mode_height; -- cgit v0.10.2 From d7f1642c90ab5eb2d7c48af0581c993094f97e1a Mon Sep 17 00:00:00 2001 From: Joonyoung Shim Date: Thu, 17 May 2012 20:06:32 +0900 Subject: drm/exynos: add G2D driver Changelog v3: - use __u64 instead of pointer in ioctl struct. The G2D is a 2D graphic accelerator that supports Bit Block Transfer. This G2D driver is exynos drm specific and supports only G2D(version 4.1) of later Exynos series from Exynos4X12 because supporting DMA. The G2D is performed by two tasks simply. 1. Configures the rendering parameters, such as foreground color and coordinates data by setting the drawing context registers. 2. Start the rendering process by setting thre relevant command registers accordingly. The G2D version 4.1 supports DMA mode as host interface. User can make command list to reduce HOST(ARM) loads. The contents of The command list is setted to relevant registers of G2D by DMA. The command list is composed Header and command sets and Tail. - Header: The number of command set(4Bytes) - Command set: Register offset(4Bytes) + Register data(4Bytes) - Tail: Pointer of base address of the other command list(4Bytes) By Tail field, the G2D can process many command lists without halt at one go. The G2D has following the rendering pipeline. --> Primitive Drawing --> Rotation --> Clipping --> Bilinear Sampling --> Color Key --> ROP --> Mask Operation --> Alpha Blending --> Dithering --> FrameBuffer And supports various operations from the rendering pipeline. - copy - fast solid color fill - window clipping - rotation - flip - 4 operand raster operation(ROP4) - masking operation - alpha blending - color key - dithering - etc User should make the command list to data and registers needed by operation to use. The Exynos G2D driver only manages the command lists received from user. Some registers needs memory base address(physical address) of image. User doesn't know its physical address, so fills the gem handle of that memory than address to command sets, then G2D driver converts it to memory base address. We adds three ioctls and one event for Exynos G2D. - ioctls DRM_EXYNOS_G2D_GET_VER: get the G2D hardware version DRM_EXYNOS_G2D_SET_CMDLIST: set the command list from user to driver DRM_EXYNOS_G2D_EXEC: execute the command lists setted to driver - event DRM_EXYNOS_G2D_EVENT: event to give notification completion of the command list to user Signed-off-by: Joonyoung Shim Signed-off-by: Inki Dae Signed-off-by: Kyungmin Park diff --git a/drivers/gpu/drm/exynos/Kconfig b/drivers/gpu/drm/exynos/Kconfig index 135b618..7f50967 100644 --- a/drivers/gpu/drm/exynos/Kconfig +++ b/drivers/gpu/drm/exynos/Kconfig @@ -33,3 +33,9 @@ config DRM_EXYNOS_VIDI depends on DRM_EXYNOS help Choose this option if you want to use Exynos VIDI for DRM. + +config DRM_EXYNOS_G2D + bool "Exynos DRM G2D" + depends on DRM_EXYNOS + help + Choose this option if you want to use Exynos G2D for DRM. diff --git a/drivers/gpu/drm/exynos/Makefile b/drivers/gpu/drm/exynos/Makefile index 353e1b7..eb651ca 100644 --- a/drivers/gpu/drm/exynos/Makefile +++ b/drivers/gpu/drm/exynos/Makefile @@ -14,5 +14,6 @@ exynosdrm-$(CONFIG_DRM_EXYNOS_HDMI) += exynos_hdmi.o exynos_mixer.o \ exynos_ddc.o exynos_hdmiphy.o \ exynos_drm_hdmi.o exynosdrm-$(CONFIG_DRM_EXYNOS_VIDI) += exynos_drm_vidi.o +exynosdrm-$(CONFIG_DRM_EXYNOS_G2D) += exynos_drm_g2d.o obj-$(CONFIG_DRM_EXYNOS) += exynosdrm.o diff --git a/drivers/gpu/drm/exynos/exynos_drm_drv.c b/drivers/gpu/drm/exynos/exynos_drm_drv.c index b7a2869..9d3204c 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_drv.c +++ b/drivers/gpu/drm/exynos/exynos_drm_drv.c @@ -40,6 +40,7 @@ #include "exynos_drm_plane.h" #include "exynos_drm_vidi.h" #include "exynos_drm_dmabuf.h" +#include "exynos_drm_g2d.h" #define DRIVER_NAME "exynos" #define DRIVER_DESC "Samsung SoC DRM" @@ -148,9 +149,16 @@ static int exynos_drm_unload(struct drm_device *dev) static int exynos_drm_open(struct drm_device *dev, struct drm_file *file) { + struct drm_exynos_file_private *file_priv; + DRM_DEBUG_DRIVER("%s\n", __FILE__); + file_priv = kzalloc(sizeof(*file_priv), GFP_KERNEL); + if (!file_priv) + return -ENOMEM; + drm_prime_init_file_private(&file->prime); + file->driver_priv = file_priv; return exynos_drm_subdrv_open(dev, file); } @@ -217,6 +225,12 @@ static struct drm_ioctl_desc exynos_ioctls[] = { DRM_UNLOCKED | DRM_AUTH), DRM_IOCTL_DEF_DRV(EXYNOS_VIDI_CONNECTION, vidi_connection_ioctl, DRM_UNLOCKED | DRM_AUTH), + DRM_IOCTL_DEF_DRV(EXYNOS_G2D_GET_VER, + exynos_g2d_get_ver_ioctl, DRM_UNLOCKED | DRM_AUTH), + DRM_IOCTL_DEF_DRV(EXYNOS_G2D_SET_CMDLIST, + exynos_g2d_set_cmdlist_ioctl, DRM_UNLOCKED | DRM_AUTH), + DRM_IOCTL_DEF_DRV(EXYNOS_G2D_EXEC, + exynos_g2d_exec_ioctl, DRM_UNLOCKED | DRM_AUTH), }; static const struct file_operations exynos_drm_driver_fops = { @@ -317,6 +331,12 @@ static int __init exynos_drm_init(void) goto out_vidi; #endif +#ifdef CONFIG_DRM_EXYNOS_G2D + ret = platform_driver_register(&g2d_driver); + if (ret < 0) + goto out_g2d; +#endif + ret = platform_driver_register(&exynos_drm_platform_driver); if (ret < 0) goto out; @@ -324,6 +344,11 @@ static int __init exynos_drm_init(void) return 0; out: +#ifdef CONFIG_DRM_EXYNOS_G2D + platform_driver_unregister(&g2d_driver); +out_g2d: +#endif + #ifdef CONFIG_DRM_EXYNOS_VIDI out_vidi: platform_driver_unregister(&vidi_driver); @@ -351,6 +376,10 @@ static void __exit exynos_drm_exit(void) platform_driver_unregister(&exynos_drm_platform_driver); +#ifdef CONFIG_DRM_EXYNOS_G2D + platform_driver_unregister(&g2d_driver); +#endif + #ifdef CONFIG_DRM_EXYNOS_HDMI platform_driver_unregister(&exynos_drm_common_hdmi_driver); platform_driver_unregister(&mixer_driver); diff --git a/drivers/gpu/drm/exynos/exynos_drm_drv.h b/drivers/gpu/drm/exynos/exynos_drm_drv.h index 5f5b362..c82c90c 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_drv.h +++ b/drivers/gpu/drm/exynos/exynos_drm_drv.h @@ -209,6 +209,18 @@ struct exynos_drm_manager { struct exynos_drm_display_ops *display_ops; }; +struct exynos_drm_g2d_private { + struct device *dev; + struct list_head inuse_cmdlist; + struct list_head event_list; + struct list_head gem_list; + unsigned int gem_nr; +}; + +struct drm_exynos_file_private { + struct exynos_drm_g2d_private *g2d_priv; +}; + /* * Exynos drm private structure. */ @@ -291,4 +303,5 @@ extern struct platform_driver hdmi_driver; extern struct platform_driver mixer_driver; extern struct platform_driver exynos_drm_common_hdmi_driver; extern struct platform_driver vidi_driver; +extern struct platform_driver g2d_driver; #endif diff --git a/drivers/gpu/drm/exynos/exynos_drm_g2d.c b/drivers/gpu/drm/exynos/exynos_drm_g2d.c new file mode 100644 index 0000000..d2d88f2 --- /dev/null +++ b/drivers/gpu/drm/exynos/exynos_drm_g2d.c @@ -0,0 +1,937 @@ +/* + * Copyright (C) 2012 Samsung Electronics Co.Ltd + * Authors: Joonyoung Shim + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundationr + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "drmP.h" +#include "exynos_drm.h" +#include "exynos_drm_drv.h" +#include "exynos_drm_gem.h" + +#define G2D_HW_MAJOR_VER 4 +#define G2D_HW_MINOR_VER 1 + +/* vaild register range set from user: 0x0104 ~ 0x0880 */ +#define G2D_VALID_START 0x0104 +#define G2D_VALID_END 0x0880 + +/* general registers */ +#define G2D_SOFT_RESET 0x0000 +#define G2D_INTEN 0x0004 +#define G2D_INTC_PEND 0x000C +#define G2D_DMA_SFR_BASE_ADDR 0x0080 +#define G2D_DMA_COMMAND 0x0084 +#define G2D_DMA_STATUS 0x008C +#define G2D_DMA_HOLD_CMD 0x0090 + +/* command registers */ +#define G2D_BITBLT_START 0x0100 + +/* registers for base address */ +#define G2D_SRC_BASE_ADDR 0x0304 +#define G2D_SRC_PLANE2_BASE_ADDR 0x0318 +#define G2D_DST_BASE_ADDR 0x0404 +#define G2D_DST_PLANE2_BASE_ADDR 0x0418 +#define G2D_PAT_BASE_ADDR 0x0500 +#define G2D_MSK_BASE_ADDR 0x0520 + +/* G2D_SOFT_RESET */ +#define G2D_SFRCLEAR (1 << 1) +#define G2D_R (1 << 0) + +/* G2D_INTEN */ +#define G2D_INTEN_ACF (1 << 3) +#define G2D_INTEN_UCF (1 << 2) +#define G2D_INTEN_GCF (1 << 1) +#define G2D_INTEN_SCF (1 << 0) + +/* G2D_INTC_PEND */ +#define G2D_INTP_ACMD_FIN (1 << 3) +#define G2D_INTP_UCMD_FIN (1 << 2) +#define G2D_INTP_GCMD_FIN (1 << 1) +#define G2D_INTP_SCMD_FIN (1 << 0) + +/* G2D_DMA_COMMAND */ +#define G2D_DMA_HALT (1 << 2) +#define G2D_DMA_CONTINUE (1 << 1) +#define G2D_DMA_START (1 << 0) + +/* G2D_DMA_STATUS */ +#define G2D_DMA_LIST_DONE_COUNT (0xFF << 17) +#define G2D_DMA_BITBLT_DONE_COUNT (0xFFFF << 1) +#define G2D_DMA_DONE (1 << 0) +#define G2D_DMA_LIST_DONE_COUNT_OFFSET 17 + +/* G2D_DMA_HOLD_CMD */ +#define G2D_USET_HOLD (1 << 2) +#define G2D_LIST_HOLD (1 << 1) +#define G2D_BITBLT_HOLD (1 << 0) + +/* G2D_BITBLT_START */ +#define G2D_START_CASESEL (1 << 2) +#define G2D_START_NHOLT (1 << 1) +#define G2D_START_BITBLT (1 << 0) + +#define G2D_CMDLIST_SIZE (PAGE_SIZE / 4) +#define G2D_CMDLIST_NUM 64 +#define G2D_CMDLIST_POOL_SIZE (G2D_CMDLIST_SIZE * G2D_CMDLIST_NUM) +#define G2D_CMDLIST_DATA_NUM (G2D_CMDLIST_SIZE / sizeof(u32) - 2) + +/* cmdlist data structure */ +struct g2d_cmdlist { + u32 head; + u32 data[G2D_CMDLIST_DATA_NUM]; + u32 last; /* last data offset */ +}; + +struct drm_exynos_pending_g2d_event { + struct drm_pending_event base; + struct drm_exynos_g2d_event event; +}; + +struct g2d_gem_node { + struct list_head list; + unsigned int handle; +}; + +struct g2d_cmdlist_node { + struct list_head list; + struct g2d_cmdlist *cmdlist; + unsigned int gem_nr; + dma_addr_t dma_addr; + + struct drm_exynos_pending_g2d_event *event; +}; + +struct g2d_runqueue_node { + struct list_head list; + struct list_head run_cmdlist; + struct list_head event_list; + struct completion complete; + int async; +}; + +struct g2d_data { + struct device *dev; + struct clk *gate_clk; + struct resource *regs_res; + void __iomem *regs; + int irq; + struct workqueue_struct *g2d_workq; + struct work_struct runqueue_work; + struct exynos_drm_subdrv subdrv; + bool suspended; + + /* cmdlist */ + struct g2d_cmdlist_node *cmdlist_node; + struct list_head free_cmdlist; + struct mutex cmdlist_mutex; + dma_addr_t cmdlist_pool; + void *cmdlist_pool_virt; + + /* runqueue*/ + struct g2d_runqueue_node *runqueue_node; + struct list_head runqueue; + struct mutex runqueue_mutex; + struct kmem_cache *runqueue_slab; +}; + +static int g2d_init_cmdlist(struct g2d_data *g2d) +{ + struct device *dev = g2d->dev; + struct g2d_cmdlist_node *node = g2d->cmdlist_node; + int nr; + int ret; + + g2d->cmdlist_pool_virt = dma_alloc_coherent(dev, G2D_CMDLIST_POOL_SIZE, + &g2d->cmdlist_pool, GFP_KERNEL); + if (!g2d->cmdlist_pool_virt) { + dev_err(dev, "failed to allocate dma memory\n"); + return -ENOMEM; + } + + node = kcalloc(G2D_CMDLIST_NUM, G2D_CMDLIST_NUM * sizeof(*node), + GFP_KERNEL); + if (!node) { + dev_err(dev, "failed to allocate memory\n"); + ret = -ENOMEM; + goto err; + } + + for (nr = 0; nr < G2D_CMDLIST_NUM; nr++) { + node[nr].cmdlist = + g2d->cmdlist_pool_virt + nr * G2D_CMDLIST_SIZE; + node[nr].dma_addr = + g2d->cmdlist_pool + nr * G2D_CMDLIST_SIZE; + + list_add_tail(&node[nr].list, &g2d->free_cmdlist); + } + + return 0; + +err: + dma_free_coherent(dev, G2D_CMDLIST_POOL_SIZE, g2d->cmdlist_pool_virt, + g2d->cmdlist_pool); + return ret; +} + +static void g2d_fini_cmdlist(struct g2d_data *g2d) +{ + struct device *dev = g2d->dev; + + kfree(g2d->cmdlist_node); + dma_free_coherent(dev, G2D_CMDLIST_POOL_SIZE, g2d->cmdlist_pool_virt, + g2d->cmdlist_pool); +} + +static struct g2d_cmdlist_node *g2d_get_cmdlist(struct g2d_data *g2d) +{ + struct device *dev = g2d->dev; + struct g2d_cmdlist_node *node; + + mutex_lock(&g2d->cmdlist_mutex); + if (list_empty(&g2d->free_cmdlist)) { + dev_err(dev, "there is no free cmdlist\n"); + mutex_unlock(&g2d->cmdlist_mutex); + return NULL; + } + + node = list_first_entry(&g2d->free_cmdlist, struct g2d_cmdlist_node, + list); + list_del_init(&node->list); + mutex_unlock(&g2d->cmdlist_mutex); + + return node; +} + +static void g2d_put_cmdlist(struct g2d_data *g2d, struct g2d_cmdlist_node *node) +{ + mutex_lock(&g2d->cmdlist_mutex); + list_move_tail(&node->list, &g2d->free_cmdlist); + mutex_unlock(&g2d->cmdlist_mutex); +} + +static void g2d_add_cmdlist_to_inuse(struct exynos_drm_g2d_private *g2d_priv, + struct g2d_cmdlist_node *node) +{ + struct g2d_cmdlist_node *lnode; + + if (list_empty(&g2d_priv->inuse_cmdlist)) + goto add_to_list; + + /* this links to base address of new cmdlist */ + lnode = list_entry(g2d_priv->inuse_cmdlist.prev, + struct g2d_cmdlist_node, list); + lnode->cmdlist->data[lnode->cmdlist->last] = node->dma_addr; + +add_to_list: + list_add_tail(&node->list, &g2d_priv->inuse_cmdlist); + + if (node->event) + list_add_tail(&node->event->base.link, &g2d_priv->event_list); +} + +static int g2d_get_cmdlist_gem(struct drm_device *drm_dev, + struct drm_file *file, + struct g2d_cmdlist_node *node) +{ + struct drm_exynos_file_private *file_priv = file->driver_priv; + struct exynos_drm_g2d_private *g2d_priv = file_priv->g2d_priv; + struct g2d_cmdlist *cmdlist = node->cmdlist; + dma_addr_t *addr; + int offset; + int i; + + for (i = 0; i < node->gem_nr; i++) { + struct g2d_gem_node *gem_node; + + gem_node = kzalloc(sizeof(*gem_node), GFP_KERNEL); + if (!gem_node) { + dev_err(g2d_priv->dev, "failed to allocate gem node\n"); + return -ENOMEM; + } + + offset = cmdlist->last - (i * 2 + 1); + gem_node->handle = cmdlist->data[offset]; + + addr = exynos_drm_gem_get_dma_addr(drm_dev, gem_node->handle, + file); + if (IS_ERR(addr)) { + node->gem_nr = i; + kfree(gem_node); + return PTR_ERR(addr); + } + + cmdlist->data[offset] = *addr; + list_add_tail(&gem_node->list, &g2d_priv->gem_list); + g2d_priv->gem_nr++; + } + + return 0; +} + +static void g2d_put_cmdlist_gem(struct drm_device *drm_dev, + struct drm_file *file, + unsigned int nr) +{ + struct drm_exynos_file_private *file_priv = file->driver_priv; + struct exynos_drm_g2d_private *g2d_priv = file_priv->g2d_priv; + struct g2d_gem_node *node, *n; + + list_for_each_entry_safe_reverse(node, n, &g2d_priv->gem_list, list) { + if (!nr) + break; + + exynos_drm_gem_put_dma_addr(drm_dev, node->handle, file); + list_del_init(&node->list); + kfree(node); + nr--; + } +} + +static void g2d_dma_start(struct g2d_data *g2d, + struct g2d_runqueue_node *runqueue_node) +{ + struct g2d_cmdlist_node *node = + list_first_entry(&runqueue_node->run_cmdlist, + struct g2d_cmdlist_node, list); + + pm_runtime_get_sync(g2d->dev); + clk_enable(g2d->gate_clk); + + /* interrupt enable */ + writel_relaxed(G2D_INTEN_ACF | G2D_INTEN_UCF | G2D_INTEN_GCF, + g2d->regs + G2D_INTEN); + + writel_relaxed(node->dma_addr, g2d->regs + G2D_DMA_SFR_BASE_ADDR); + writel_relaxed(G2D_DMA_START, g2d->regs + G2D_DMA_COMMAND); +} + +static struct g2d_runqueue_node *g2d_get_runqueue_node(struct g2d_data *g2d) +{ + struct g2d_runqueue_node *runqueue_node; + + if (list_empty(&g2d->runqueue)) + return NULL; + + runqueue_node = list_first_entry(&g2d->runqueue, + struct g2d_runqueue_node, list); + list_del_init(&runqueue_node->list); + return runqueue_node; +} + +static void g2d_free_runqueue_node(struct g2d_data *g2d, + struct g2d_runqueue_node *runqueue_node) +{ + if (!runqueue_node) + return; + + mutex_lock(&g2d->cmdlist_mutex); + list_splice_tail_init(&runqueue_node->run_cmdlist, &g2d->free_cmdlist); + mutex_unlock(&g2d->cmdlist_mutex); + + kmem_cache_free(g2d->runqueue_slab, runqueue_node); +} + +static void g2d_exec_runqueue(struct g2d_data *g2d) +{ + g2d->runqueue_node = g2d_get_runqueue_node(g2d); + if (g2d->runqueue_node) + g2d_dma_start(g2d, g2d->runqueue_node); +} + +static void g2d_runqueue_worker(struct work_struct *work) +{ + struct g2d_data *g2d = container_of(work, struct g2d_data, + runqueue_work); + + + mutex_lock(&g2d->runqueue_mutex); + clk_disable(g2d->gate_clk); + pm_runtime_put_sync(g2d->dev); + + complete(&g2d->runqueue_node->complete); + if (g2d->runqueue_node->async) + g2d_free_runqueue_node(g2d, g2d->runqueue_node); + + if (g2d->suspended) + g2d->runqueue_node = NULL; + else + g2d_exec_runqueue(g2d); + mutex_unlock(&g2d->runqueue_mutex); +} + +static void g2d_finish_event(struct g2d_data *g2d, u32 cmdlist_no) +{ + struct drm_device *drm_dev = g2d->subdrv.drm_dev; + struct g2d_runqueue_node *runqueue_node = g2d->runqueue_node; + struct drm_exynos_pending_g2d_event *e; + struct timeval now; + unsigned long flags; + + if (list_empty(&runqueue_node->event_list)) + return; + + e = list_first_entry(&runqueue_node->event_list, + struct drm_exynos_pending_g2d_event, base.link); + + do_gettimeofday(&now); + e->event.tv_sec = now.tv_sec; + e->event.tv_usec = now.tv_usec; + e->event.cmdlist_no = cmdlist_no; + + spin_lock_irqsave(&drm_dev->event_lock, flags); + list_move_tail(&e->base.link, &e->base.file_priv->event_list); + wake_up_interruptible(&e->base.file_priv->event_wait); + spin_unlock_irqrestore(&drm_dev->event_lock, flags); +} + +static irqreturn_t g2d_irq_handler(int irq, void *dev_id) +{ + struct g2d_data *g2d = dev_id; + u32 pending; + + pending = readl_relaxed(g2d->regs + G2D_INTC_PEND); + if (pending) + writel_relaxed(pending, g2d->regs + G2D_INTC_PEND); + + if (pending & G2D_INTP_GCMD_FIN) { + u32 cmdlist_no = readl_relaxed(g2d->regs + G2D_DMA_STATUS); + + cmdlist_no = (cmdlist_no & G2D_DMA_LIST_DONE_COUNT) >> + G2D_DMA_LIST_DONE_COUNT_OFFSET; + + g2d_finish_event(g2d, cmdlist_no); + + writel_relaxed(0, g2d->regs + G2D_DMA_HOLD_CMD); + if (!(pending & G2D_INTP_ACMD_FIN)) { + writel_relaxed(G2D_DMA_CONTINUE, + g2d->regs + G2D_DMA_COMMAND); + } + } + + if (pending & G2D_INTP_ACMD_FIN) + queue_work(g2d->g2d_workq, &g2d->runqueue_work); + + return IRQ_HANDLED; +} + +static int g2d_check_reg_offset(struct device *dev, struct g2d_cmdlist *cmdlist, + int nr, bool for_addr) +{ + int reg_offset; + int index; + int i; + + for (i = 0; i < nr; i++) { + index = cmdlist->last - 2 * (i + 1); + reg_offset = cmdlist->data[index] & ~0xfffff000; + + if (reg_offset < G2D_VALID_START || reg_offset > G2D_VALID_END) + goto err; + if (reg_offset % 4) + goto err; + + switch (reg_offset) { + case G2D_SRC_BASE_ADDR: + case G2D_SRC_PLANE2_BASE_ADDR: + case G2D_DST_BASE_ADDR: + case G2D_DST_PLANE2_BASE_ADDR: + case G2D_PAT_BASE_ADDR: + case G2D_MSK_BASE_ADDR: + if (!for_addr) + goto err; + break; + default: + if (for_addr) + goto err; + break; + } + } + + return 0; + +err: + dev_err(dev, "Bad register offset: 0x%x\n", cmdlist->data[index]); + return -EINVAL; +} + +/* ioctl functions */ +int exynos_g2d_get_ver_ioctl(struct drm_device *drm_dev, void *data, + struct drm_file *file) +{ + struct drm_exynos_g2d_get_ver *ver = data; + + ver->major = G2D_HW_MAJOR_VER; + ver->minor = G2D_HW_MINOR_VER; + + return 0; +} +EXPORT_SYMBOL_GPL(exynos_g2d_get_ver_ioctl); + +int exynos_g2d_set_cmdlist_ioctl(struct drm_device *drm_dev, void *data, + struct drm_file *file) +{ + struct drm_exynos_file_private *file_priv = file->driver_priv; + struct exynos_drm_g2d_private *g2d_priv = file_priv->g2d_priv; + struct device *dev = g2d_priv->dev; + struct g2d_data *g2d; + struct drm_exynos_g2d_set_cmdlist *req = data; + struct drm_exynos_g2d_cmd *cmd; + struct drm_exynos_pending_g2d_event *e; + struct g2d_cmdlist_node *node; + struct g2d_cmdlist *cmdlist; + unsigned long flags; + int size; + int ret; + + if (!dev) + return -ENODEV; + + g2d = dev_get_drvdata(dev); + if (!g2d) + return -EFAULT; + + node = g2d_get_cmdlist(g2d); + if (!node) + return -ENOMEM; + + node->event = NULL; + + if (req->event_type != G2D_EVENT_NOT) { + spin_lock_irqsave(&drm_dev->event_lock, flags); + if (file->event_space < sizeof(e->event)) { + spin_unlock_irqrestore(&drm_dev->event_lock, flags); + ret = -ENOMEM; + goto err; + } + file->event_space -= sizeof(e->event); + spin_unlock_irqrestore(&drm_dev->event_lock, flags); + + e = kzalloc(sizeof(*node->event), GFP_KERNEL); + if (!e) { + dev_err(dev, "failed to allocate event\n"); + + spin_lock_irqsave(&drm_dev->event_lock, flags); + file->event_space += sizeof(e->event); + spin_unlock_irqrestore(&drm_dev->event_lock, flags); + + ret = -ENOMEM; + goto err; + } + + e->event.base.type = DRM_EXYNOS_G2D_EVENT; + e->event.base.length = sizeof(e->event); + e->event.user_data = req->user_data; + e->base.event = &e->event.base; + e->base.file_priv = file; + e->base.destroy = (void (*) (struct drm_pending_event *)) kfree; + + node->event = e; + } + + cmdlist = node->cmdlist; + + cmdlist->last = 0; + + /* + * If don't clear SFR registers, the cmdlist is affected by register + * values of previous cmdlist. G2D hw executes SFR clear command and + * a next command at the same time then the next command is ignored and + * is executed rightly from next next command, so needs a dummy command + * to next command of SFR clear command. + */ + cmdlist->data[cmdlist->last++] = G2D_SOFT_RESET; + cmdlist->data[cmdlist->last++] = G2D_SFRCLEAR; + cmdlist->data[cmdlist->last++] = G2D_SRC_BASE_ADDR; + cmdlist->data[cmdlist->last++] = 0; + + if (node->event) { + cmdlist->data[cmdlist->last++] = G2D_DMA_HOLD_CMD; + cmdlist->data[cmdlist->last++] = G2D_LIST_HOLD; + } + + /* Check size of cmdlist: last 2 is about G2D_BITBLT_START */ + size = cmdlist->last + req->cmd_nr * 2 + req->cmd_gem_nr * 2 + 2; + if (size > G2D_CMDLIST_DATA_NUM) { + dev_err(dev, "cmdlist size is too big\n"); + ret = -EINVAL; + goto err_free_event; + } + + cmd = (struct drm_exynos_g2d_cmd *)(uint32_t)req->cmd; + + if (copy_from_user(cmdlist->data + cmdlist->last, + (void __user *)cmd, + sizeof(*cmd) * req->cmd_nr)) { + ret = -EFAULT; + goto err_free_event; + } + cmdlist->last += req->cmd_nr * 2; + + ret = g2d_check_reg_offset(dev, cmdlist, req->cmd_nr, false); + if (ret < 0) + goto err_free_event; + + node->gem_nr = req->cmd_gem_nr; + if (req->cmd_gem_nr) { + struct drm_exynos_g2d_cmd *cmd_gem; + + cmd_gem = (struct drm_exynos_g2d_cmd *)(uint32_t)req->cmd_gem; + + if (copy_from_user(cmdlist->data + cmdlist->last, + (void __user *)cmd_gem, + sizeof(*cmd_gem) * req->cmd_gem_nr)) { + ret = -EFAULT; + goto err_free_event; + } + cmdlist->last += req->cmd_gem_nr * 2; + + ret = g2d_check_reg_offset(dev, cmdlist, req->cmd_gem_nr, true); + if (ret < 0) + goto err_free_event; + + ret = g2d_get_cmdlist_gem(drm_dev, file, node); + if (ret < 0) + goto err_unmap; + } + + cmdlist->data[cmdlist->last++] = G2D_BITBLT_START; + cmdlist->data[cmdlist->last++] = G2D_START_BITBLT; + + /* head */ + cmdlist->head = cmdlist->last / 2; + + /* tail */ + cmdlist->data[cmdlist->last] = 0; + + g2d_add_cmdlist_to_inuse(g2d_priv, node); + + return 0; + +err_unmap: + g2d_put_cmdlist_gem(drm_dev, file, node->gem_nr); +err_free_event: + if (node->event) { + spin_lock_irqsave(&drm_dev->event_lock, flags); + file->event_space += sizeof(e->event); + spin_unlock_irqrestore(&drm_dev->event_lock, flags); + kfree(node->event); + } +err: + g2d_put_cmdlist(g2d, node); + return ret; +} +EXPORT_SYMBOL_GPL(exynos_g2d_set_cmdlist_ioctl); + +int exynos_g2d_exec_ioctl(struct drm_device *drm_dev, void *data, + struct drm_file *file) +{ + struct drm_exynos_file_private *file_priv = file->driver_priv; + struct exynos_drm_g2d_private *g2d_priv = file_priv->g2d_priv; + struct device *dev = g2d_priv->dev; + struct g2d_data *g2d; + struct drm_exynos_g2d_exec *req = data; + struct g2d_runqueue_node *runqueue_node; + struct list_head *run_cmdlist; + struct list_head *event_list; + + if (!dev) + return -ENODEV; + + g2d = dev_get_drvdata(dev); + if (!g2d) + return -EFAULT; + + runqueue_node = kmem_cache_alloc(g2d->runqueue_slab, GFP_KERNEL); + if (!runqueue_node) { + dev_err(dev, "failed to allocate memory\n"); + return -ENOMEM; + } + run_cmdlist = &runqueue_node->run_cmdlist; + event_list = &runqueue_node->event_list; + INIT_LIST_HEAD(run_cmdlist); + INIT_LIST_HEAD(event_list); + init_completion(&runqueue_node->complete); + runqueue_node->async = req->async; + + list_splice_init(&g2d_priv->inuse_cmdlist, run_cmdlist); + list_splice_init(&g2d_priv->event_list, event_list); + + if (list_empty(run_cmdlist)) { + dev_err(dev, "there is no inuse cmdlist\n"); + kmem_cache_free(g2d->runqueue_slab, runqueue_node); + return -EPERM; + } + + mutex_lock(&g2d->runqueue_mutex); + list_add_tail(&runqueue_node->list, &g2d->runqueue); + if (!g2d->runqueue_node) + g2d_exec_runqueue(g2d); + mutex_unlock(&g2d->runqueue_mutex); + + if (runqueue_node->async) + goto out; + + wait_for_completion(&runqueue_node->complete); + g2d_free_runqueue_node(g2d, runqueue_node); + +out: + return 0; +} +EXPORT_SYMBOL_GPL(exynos_g2d_exec_ioctl); + +static int g2d_open(struct drm_device *drm_dev, struct device *dev, + struct drm_file *file) +{ + struct drm_exynos_file_private *file_priv = file->driver_priv; + struct exynos_drm_g2d_private *g2d_priv; + + g2d_priv = kzalloc(sizeof(*g2d_priv), GFP_KERNEL); + if (!g2d_priv) { + dev_err(dev, "failed to allocate g2d private data\n"); + return -ENOMEM; + } + + g2d_priv->dev = dev; + file_priv->g2d_priv = g2d_priv; + + INIT_LIST_HEAD(&g2d_priv->inuse_cmdlist); + INIT_LIST_HEAD(&g2d_priv->event_list); + INIT_LIST_HEAD(&g2d_priv->gem_list); + + return 0; +} + +static void g2d_close(struct drm_device *drm_dev, struct device *dev, + struct drm_file *file) +{ + struct drm_exynos_file_private *file_priv = file->driver_priv; + struct exynos_drm_g2d_private *g2d_priv = file_priv->g2d_priv; + struct g2d_data *g2d; + struct g2d_cmdlist_node *node, *n; + + if (!dev) + return; + + g2d = dev_get_drvdata(dev); + if (!g2d) + return; + + mutex_lock(&g2d->cmdlist_mutex); + list_for_each_entry_safe(node, n, &g2d_priv->inuse_cmdlist, list) + list_move_tail(&node->list, &g2d->free_cmdlist); + mutex_unlock(&g2d->cmdlist_mutex); + + g2d_put_cmdlist_gem(drm_dev, file, g2d_priv->gem_nr); + + kfree(file_priv->g2d_priv); +} + +static int __devinit g2d_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res; + struct g2d_data *g2d; + struct exynos_drm_subdrv *subdrv; + int ret; + + g2d = kzalloc(sizeof(*g2d), GFP_KERNEL); + if (!g2d) { + dev_err(dev, "failed to allocate driver data\n"); + return -ENOMEM; + } + + g2d->runqueue_slab = kmem_cache_create("g2d_runqueue_slab", + sizeof(struct g2d_runqueue_node), 0, 0, NULL); + if (!g2d->runqueue_slab) { + ret = -ENOMEM; + goto err_free_mem; + } + + g2d->dev = dev; + + g2d->g2d_workq = create_singlethread_workqueue("g2d"); + if (!g2d->g2d_workq) { + dev_err(dev, "failed to create workqueue\n"); + ret = -EINVAL; + goto err_destroy_slab; + } + + INIT_WORK(&g2d->runqueue_work, g2d_runqueue_worker); + INIT_LIST_HEAD(&g2d->free_cmdlist); + INIT_LIST_HEAD(&g2d->runqueue); + + mutex_init(&g2d->cmdlist_mutex); + mutex_init(&g2d->runqueue_mutex); + + ret = g2d_init_cmdlist(g2d); + if (ret < 0) + goto err_destroy_workqueue; + + g2d->gate_clk = clk_get(dev, "fimg2d"); + if (IS_ERR(g2d->gate_clk)) { + dev_err(dev, "failed to get gate clock\n"); + ret = PTR_ERR(g2d->gate_clk); + goto err_fini_cmdlist; + } + + pm_runtime_enable(dev); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(dev, "failed to get I/O memory\n"); + ret = -ENOENT; + goto err_put_clk; + } + + g2d->regs_res = request_mem_region(res->start, resource_size(res), + dev_name(dev)); + if (!g2d->regs_res) { + dev_err(dev, "failed to request I/O memory\n"); + ret = -ENOENT; + goto err_put_clk; + } + + g2d->regs = ioremap(res->start, resource_size(res)); + if (!g2d->regs) { + dev_err(dev, "failed to remap I/O memory\n"); + ret = -ENXIO; + goto err_release_res; + } + + g2d->irq = platform_get_irq(pdev, 0); + if (g2d->irq < 0) { + dev_err(dev, "failed to get irq\n"); + ret = g2d->irq; + goto err_unmap_base; + } + + ret = request_irq(g2d->irq, g2d_irq_handler, 0, "drm_g2d", g2d); + if (ret < 0) { + dev_err(dev, "irq request failed\n"); + goto err_unmap_base; + } + + platform_set_drvdata(pdev, g2d); + + subdrv = &g2d->subdrv; + subdrv->dev = dev; + subdrv->open = g2d_open; + subdrv->close = g2d_close; + + ret = exynos_drm_subdrv_register(subdrv); + if (ret < 0) { + dev_err(dev, "failed to register drm g2d device\n"); + goto err_free_irq; + } + + dev_info(dev, "The exynos g2d(ver %d.%d) successfully probed\n", + G2D_HW_MAJOR_VER, G2D_HW_MINOR_VER); + + return 0; + +err_free_irq: + free_irq(g2d->irq, g2d); +err_unmap_base: + iounmap(g2d->regs); +err_release_res: + release_resource(g2d->regs_res); + kfree(g2d->regs_res); +err_put_clk: + pm_runtime_disable(dev); + clk_put(g2d->gate_clk); +err_fini_cmdlist: + g2d_fini_cmdlist(g2d); +err_destroy_workqueue: + destroy_workqueue(g2d->g2d_workq); +err_destroy_slab: + kmem_cache_destroy(g2d->runqueue_slab); +err_free_mem: + kfree(g2d); + return ret; +} + +static int __devexit g2d_remove(struct platform_device *pdev) +{ + struct g2d_data *g2d = platform_get_drvdata(pdev); + + cancel_work_sync(&g2d->runqueue_work); + exynos_drm_subdrv_unregister(&g2d->subdrv); + free_irq(g2d->irq, g2d); + + while (g2d->runqueue_node) { + g2d_free_runqueue_node(g2d, g2d->runqueue_node); + g2d->runqueue_node = g2d_get_runqueue_node(g2d); + } + + iounmap(g2d->regs); + release_resource(g2d->regs_res); + kfree(g2d->regs_res); + + pm_runtime_disable(&pdev->dev); + clk_put(g2d->gate_clk); + + g2d_fini_cmdlist(g2d); + destroy_workqueue(g2d->g2d_workq); + kmem_cache_destroy(g2d->runqueue_slab); + kfree(g2d); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int g2d_suspend(struct device *dev) +{ + struct g2d_data *g2d = dev_get_drvdata(dev); + + mutex_lock(&g2d->runqueue_mutex); + g2d->suspended = true; + mutex_unlock(&g2d->runqueue_mutex); + + while (g2d->runqueue_node) + /* FIXME: good range? */ + usleep_range(500, 1000); + + flush_work_sync(&g2d->runqueue_work); + + return 0; +} + +static int g2d_resume(struct device *dev) +{ + struct g2d_data *g2d = dev_get_drvdata(dev); + + g2d->suspended = false; + g2d_exec_runqueue(g2d); + + return 0; +} +#endif + +SIMPLE_DEV_PM_OPS(g2d_pm_ops, g2d_suspend, g2d_resume); + +struct platform_driver g2d_driver = { + .probe = g2d_probe, + .remove = __devexit_p(g2d_remove), + .driver = { + .name = "s5p-g2d", + .owner = THIS_MODULE, + .pm = &g2d_pm_ops, + }, +}; diff --git a/drivers/gpu/drm/exynos/exynos_drm_g2d.h b/drivers/gpu/drm/exynos/exynos_drm_g2d.h new file mode 100644 index 0000000..1a9c7ca --- /dev/null +++ b/drivers/gpu/drm/exynos/exynos_drm_g2d.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2012 Samsung Electronics Co.Ltd + * Authors: Joonyoung Shim + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundationr + */ + +#ifdef CONFIG_DRM_EXYNOS_G2D +extern int exynos_g2d_get_ver_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv); +extern int exynos_g2d_set_cmdlist_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv); +extern int exynos_g2d_exec_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv); +#else +static inline int exynos_g2d_get_ver_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + return -ENODEV; +} + +static inline int exynos_g2d_set_cmdlist_ioctl(struct drm_device *dev, + void *data, + struct drm_file *file_priv) +{ + return -ENODEV; +} + +static inline int exynos_g2d_exec_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + return -ENODEV; +} +#endif diff --git a/include/drm/exynos_drm.h b/include/drm/exynos_drm.h index 44f79cf..b6d7ce9 100644 --- a/include/drm/exynos_drm.h +++ b/include/drm/exynos_drm.h @@ -29,6 +29,8 @@ #ifndef _EXYNOS_DRM_H_ #define _EXYNOS_DRM_H_ +#include "drm.h" + /** * User-desired buffer creation information structure. * @@ -124,6 +126,37 @@ enum e_drm_exynos_gem_mem_type { EXYNOS_BO_WC }; +struct drm_exynos_g2d_get_ver { + __u32 major; + __u32 minor; +}; + +struct drm_exynos_g2d_cmd { + __u32 offset; + __u32 data; +}; + +enum drm_exynos_g2d_event_type { + G2D_EVENT_NOT, + G2D_EVENT_NONSTOP, + G2D_EVENT_STOP, /* not yet */ +}; + +struct drm_exynos_g2d_set_cmdlist { + __u64 cmd; + __u64 cmd_gem; + __u32 cmd_nr; + __u32 cmd_gem_nr; + + /* for g2d event */ + __u64 event_type; + __u64 user_data; +}; + +struct drm_exynos_g2d_exec { + __u64 async; +}; + #define DRM_EXYNOS_GEM_CREATE 0x00 #define DRM_EXYNOS_GEM_MAP_OFFSET 0x01 #define DRM_EXYNOS_GEM_MMAP 0x02 @@ -132,6 +165,11 @@ enum e_drm_exynos_gem_mem_type { #define DRM_EXYNOS_PLANE_SET_ZPOS 0x06 #define DRM_EXYNOS_VIDI_CONNECTION 0x07 +/* G2D */ +#define DRM_EXYNOS_G2D_GET_VER 0x20 +#define DRM_EXYNOS_G2D_SET_CMDLIST 0x21 +#define DRM_EXYNOS_G2D_EXEC 0x22 + #define DRM_IOCTL_EXYNOS_GEM_CREATE DRM_IOWR(DRM_COMMAND_BASE + \ DRM_EXYNOS_GEM_CREATE, struct drm_exynos_gem_create) @@ -150,6 +188,25 @@ enum e_drm_exynos_gem_mem_type { #define DRM_IOCTL_EXYNOS_VIDI_CONNECTION DRM_IOWR(DRM_COMMAND_BASE + \ DRM_EXYNOS_VIDI_CONNECTION, struct drm_exynos_vidi_connection) +#define DRM_IOCTL_EXYNOS_G2D_GET_VER DRM_IOWR(DRM_COMMAND_BASE + \ + DRM_EXYNOS_G2D_GET_VER, struct drm_exynos_g2d_get_ver) +#define DRM_IOCTL_EXYNOS_G2D_SET_CMDLIST DRM_IOWR(DRM_COMMAND_BASE + \ + DRM_EXYNOS_G2D_SET_CMDLIST, struct drm_exynos_g2d_set_cmdlist) +#define DRM_IOCTL_EXYNOS_G2D_EXEC DRM_IOWR(DRM_COMMAND_BASE + \ + DRM_EXYNOS_G2D_EXEC, struct drm_exynos_g2d_exec) + +/* EXYNOS specific events */ +#define DRM_EXYNOS_G2D_EVENT 0x80000000 + +struct drm_exynos_g2d_event { + struct drm_event base; + __u64 user_data; + __u32 tv_sec; + __u32 tv_usec; + __u32 cmdlist_no; + __u32 reserved; +}; + #ifdef __KERNEL__ /** -- cgit v0.10.2