diff options
author | Dave Airlie <airlied@redhat.com> | 2012-05-22 10:39:57 +0100 |
---|---|---|
committer | Dave Airlie <airlied@redhat.com> | 2012-05-22 10:39:57 +0100 |
commit | 345f3b9035691d2d6e97398039b99fa484653cc4 (patch) | |
tree | 5918971695857c8d424a8a7e05ed642604d132ae | |
parent | ebe0f2442cc4c5493a85c875d2d8a44ffecc1175 (diff) | |
parent | d7f1642c90ab5eb2d7c48af0581c993094f97e1a (diff) |
Merge branch 'exynos-drm-next' of git://git.infradead.org/users/kmpark/linux-samsung into drm-core-next
* 'exynos-drm-next' of git://git.infradead.org/users/kmpark/linux-samsung:
drm/exynos: add G2D driver
drm/exynos: added vp scaling feature for hdmi
drm/exynos: added source size to overlay structure
drm/exynos: add additional display mode for hdmi
drm/exynos: enable dvi mode for dvi monitor
drm/exynos: fixed wrong pageflip finish event for interlace mode
drm/exynos: add PM functions for hdmi and mixer
drm/exynos: add dpms for hdmi
drm/exynos: use threaded irq for hdmi hotplug
drm/exynos: use platform_get_irq_byname for hdmi
drm/exynos: cleanup for hdmi platform data
drm/exynos: added a feature to get gem buffer information.
drm/exynos: added drm prime feature.
drm/exynos: added cache attribute support for gem.
vgaarb: Provide dummy default device functions
20 files changed, 2066 insertions, 439 deletions
diff --git a/drivers/gpu/drm/exynos/Kconfig b/drivers/gpu/drm/exynos/Kconfig index 3343ac437fe..7f5096763b7 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 @@ -27,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 9e0bff8badf..eb651ca8e2a 100644 --- a/drivers/gpu/drm/exynos/Makefile +++ b/drivers/gpu/drm/exynos/Makefile @@ -8,10 +8,12 @@ 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 \ 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_buf.c b/drivers/gpu/drm/exynos/exynos_drm_buf.c index de8d2090bce..b3cb0a69fbf 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_crtc.c b/drivers/gpu/drm/exynos/exynos_drm_crtc.c index 3486ffed0bf..4afb625128d 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 25f72a62cb8..16b8e2195a0 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_dmabuf.c b/drivers/gpu/drm/exynos/exynos_drm_dmabuf.c new file mode 100644 index 00000000000..274909271c3 --- /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 <inki.dae@samsung.com> + * + * 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 <linux/dma-buf.h> + +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 <inki.dae@samsung.com>"); +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 00000000000..662a8f98ccd --- /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 <inki.dae@samsung.com> + * + * 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 67a67b0839a..420953197d0 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_drv.c +++ b/drivers/gpu/drm/exynos/exynos_drm_drv.c @@ -39,6 +39,8 @@ #include "exynos_drm_gem.h" #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" @@ -147,8 +149,17 @@ 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); } @@ -170,6 +181,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); @@ -207,10 +219,18 @@ 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, 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 = { @@ -225,7 +245,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 +261,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, @@ -307,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; @@ -314,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); @@ -341,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 1d814175cd4..c82c90c443e 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; @@ -205,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. */ @@ -287,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 00000000000..d2d88f22a03 --- /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 <jy0922.shim@samsung.com> + * + * 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 <linux/kernel.h> +#include <linux/module.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/slab.h> +#include <linux/workqueue.h> + +#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_fil |