From: Yakir Yang Date: Sun, 20 Mar 2016 09:43:41 +0000 (+0800) Subject: drm/rockchip: add RGA driver support X-Git-Tag: firefly_0821_release~2776 X-Git-Url: http://plrg.eecs.uci.edu/git/?p=firefly-linux-kernel-4.4.55.git;a=commitdiff_plain;h=3e2bb4ee5b135ab77042496013ad1886bf8dc552 drm/rockchip: add RGA driver support Rockchip RGA is a separate 2D raster graphic acceleration unit. It accelerates 2D graphics operations, such as point/line drawing, image scaling, rotation, BitBLT, alpha blending and image blur/sharpness. Change-Id: I9be8d683ea04802affb973b8b1ada646afe411d7 Signed-off-by: Yakir Yang --- diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig index 731f5c36380e..455ad8421907 100644 --- a/drivers/gpu/drm/rockchip/Kconfig +++ b/drivers/gpu/drm/rockchip/Kconfig @@ -16,6 +16,15 @@ config DRM_ROCKCHIP 2D or 3D acceleration; acceleration is performed by other IP found on the SoC. +config ROCKCHIP_DRM_RGA + tristate "Rockchip RGA support" + depends on DRM_ROCKCHIP + help + Choose this option to enable support for Rockchip RGA. + Rockchip RGA is a kind of hardware 2D accelerator, and it support + solid roration, scaling, color format transform, say Y to enable its + driver + config ROCKCHIP_DW_HDMI tristate "Rockchip specific extensions for Synopsys DW HDMI" depends on DRM_ROCKCHIP diff --git a/drivers/gpu/drm/rockchip/Makefile b/drivers/gpu/drm/rockchip/Makefile index 921fabf8f67a..34a96b67d087 100644 --- a/drivers/gpu/drm/rockchip/Makefile +++ b/drivers/gpu/drm/rockchip/Makefile @@ -6,6 +6,7 @@ rockchipdrm-y := rockchip_drm_drv.o rockchip_drm_fb.o \ rockchip_drm_gem.o rockchip_drm_vop.o rockchipdrm-$(CONFIG_DRM_FBDEV_EMULATION) += rockchip_drm_fbdev.o +obj-$(CONFIG_ROCKCHIP_DRM_RGA) += rockchip_drm_rga.o obj-$(CONFIG_ROCKCHIP_DW_HDMI) += dw_hdmi-rockchip.o obj-$(CONFIG_ROCKCHIP_DW_MIPI_DSI) += dw-mipi-dsi.o obj-$(CONFIG_ROCKCHIP_ANALOGIX_DP) += analogix_dp-rockchip.o diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c index fb5a0f19d1b2..73745ef7722b 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -27,12 +28,11 @@ #include #include -#include - #include "rockchip_drm_drv.h" #include "rockchip_drm_fb.h" #include "rockchip_drm_fbdev.h" #include "rockchip_drm_gem.h" +#include "rockchip_drm_rga.h" #define DRIVER_NAME "rockchip" #define DRIVER_DESC "RockChip Soc DRM" @@ -426,6 +426,13 @@ static const struct drm_ioctl_desc rockchip_ioctls[] = { DRM_IOCTL_DEF_DRV(ROCKCHIP_GEM_CPU_RELEASE, rockchip_gem_cpu_release_ioctl, DRM_UNLOCKED | DRM_AUTH), + DRM_IOCTL_DEF_DRV(ROCKCHIP_RGA_GET_VER, rockchip_rga_get_ver_ioctl, + DRM_AUTH | DRM_RENDER_ALLOW), + DRM_IOCTL_DEF_DRV(ROCKCHIP_RGA_SET_CMDLIST, + rockchip_rga_set_cmdlist_ioctl, + DRM_AUTH | DRM_RENDER_ALLOW), + DRM_IOCTL_DEF_DRV(ROCKCHIP_RGA_EXEC, rockchip_rga_exec_ioctl, + DRM_AUTH | DRM_RENDER_ALLOW), }; static const struct file_operations rockchip_drm_driver_fops = { diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h index b1dd679d04c5..841c5b8c3ac0 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h @@ -68,6 +68,7 @@ struct rockchip_atomic_commit { */ struct rockchip_drm_file_private { struct list_head gem_cpu_acquire_list; + struct rockchip_drm_rga_private *rga_priv; }; /* diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_rga.c b/drivers/gpu/drm/rockchip/rockchip_drm_rga.c new file mode 100644 index 000000000000..7df0fd38f248 --- /dev/null +++ b/drivers/gpu/drm/rockchip/rockchip_drm_rga.c @@ -0,0 +1,963 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "rockchip_drm_drv.h" +#include "rockchip_drm_rga.h" + +#define RGA_MODE_BASE_REG 0x0100 +#define RGA_MODE_MAX_REG 0x017C + +#define RGA_SYS_CTRL 0x0000 +#define RGA_CMD_CTRL 0x0004 +#define RGA_CMD_BASE 0x0008 +#define RGA_INT 0x0010 +#define RGA_MMU_CTRL0 0x0014 +#define RGA_VERSION_INFO 0x0028 + +#define RGA_SRC_Y_RGB_BASE_ADDR 0x0108 +#define RGA_SRC_CB_BASE_ADDR 0x010C +#define RGA_SRC_CR_BASE_ADDR 0x0110 +#define RGA_SRC1_RGB_BASE_ADDR 0x0114 +#define RGA_DST_Y_RGB_BASE_ADDR 0x013C +#define RGA_DST_CB_BASE_ADDR 0x0140 +#define RGA_DST_CR_BASE_ADDR 0x014C +#define RGA_MMU_CTRL1 0x016C +#define RGA_MMU_SRC_BASE 0x0170 +#define RGA_MMU_SRC1_BASE 0x0174 +#define RGA_MMU_DST_BASE 0x0178 + +static void rga_dma_flush_range(void *ptr, int size) +{ +#ifdef CONFIG_ARM + dmac_flush_range(ptr, ptr + size); + outer_flush_range(virt_to_phys(ptr), virt_to_phys(ptr + size)); +#elif CONFIG_ARM64 + __dma_flush_range(ptr, ptr + size); +#endif +} + +static inline void rga_write(struct rockchip_rga *rga, u32 reg, u32 value) +{ + writel(value, rga->regs + reg); +} + +static inline u32 rga_read(struct rockchip_rga *rga, u32 reg) +{ + return readl(rga->regs + reg); +} + +static inline void rga_mod(struct rockchip_rga *rga, u32 reg, u32 val, u32 mask) +{ + u32 temp = rga_read(rga, reg) & ~(mask); + + temp |= val & mask; + rga_write(rga, reg, temp); +} + +static int rga_enable_clocks(struct rockchip_rga *rga) +{ + int ret; + + ret = clk_prepare_enable(rga->sclk); + if (ret) { + dev_err(rga->dev, "Cannot enable rga sclk: %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(rga->aclk); + if (ret) { + dev_err(rga->dev, "Cannot enable rga aclk: %d\n", ret); + goto err_disable_sclk; + } + + ret = clk_prepare_enable(rga->hclk); + if (ret) { + dev_err(rga->dev, "Cannot enable rga hclk: %d\n", ret); + goto err_disable_aclk; + } + + return 0; + +err_disable_sclk: + clk_disable_unprepare(rga->sclk); +err_disable_aclk: + clk_disable_unprepare(rga->aclk); + + return ret; +} + +static void rga_disable_clocks(struct rockchip_rga *rga) +{ + clk_disable_unprepare(rga->sclk); + clk_disable_unprepare(rga->hclk); + clk_disable_unprepare(rga->aclk); +} + +static void rga_init_cmdlist(struct rockchip_rga *rga) +{ + struct rga_cmdlist_node *node; + int nr; + + node = rga->cmdlist_node; + + for (nr = 0; nr < ARRAY_SIZE(rga->cmdlist_node); nr++) + list_add_tail(&node[nr].list, &rga->free_cmdlist); +} + +static int rga_alloc_dma_buf_for_cmdlist(struct rga_runqueue_node *runqueue) +{ + struct list_head *run_cmdlist = &runqueue->run_cmdlist; + struct device *dev = runqueue->dev; + struct dma_attrs cmdlist_dma_attrs; + struct rga_cmdlist_node *node; + void *cmdlist_pool_virt; + dma_addr_t cmdlist_pool; + int cmdlist_cnt = 0; + int count = 0; + + list_for_each_entry(node, run_cmdlist, list) + cmdlist_cnt++; + + init_dma_attrs(&cmdlist_dma_attrs); + dma_set_attr(DMA_ATTR_WRITE_COMBINE, &runqueue->cmdlist_dma_attrs); + + cmdlist_pool_virt = dma_alloc_attrs(dev, cmdlist_cnt * RGA_CMDLIST_SIZE, + &cmdlist_pool, GFP_KERNEL, + &cmdlist_dma_attrs); + if (!cmdlist_pool_virt) { + dev_err(dev, "failed to allocate cmdlist dma memory\n"); + return -ENOMEM; + } + + /* + * Fill in the RGA operation registers from cmdlist command buffer, + * and also filled in the MMU TLB base information. + */ + list_for_each_entry(node, run_cmdlist, list) { + struct rga_cmdlist *cmdlist = &node->cmdlist; + unsigned int mmu_ctrl = 0; + unsigned int reg; + u32 *dest; + int i; + + dest = cmdlist_pool_virt + RGA_CMDLIST_SIZE * 4 * count++; + + for (i = 0; i < cmdlist->last / 2; i++) { + reg = (node->cmdlist.data[2 * i] - RGA_MODE_BASE_REG); + if (reg > RGA_MODE_BASE_REG) + continue; + dest[reg >> 2] = cmdlist->data[2 * i + 1]; + } + + if (cmdlist->src_mmu_pages) { + reg = RGA_MMU_SRC_BASE - RGA_MODE_BASE_REG; + dest[reg >> 2] = virt_to_phys(cmdlist->src_mmu_pages) >> 4; + mmu_ctrl |= 0x7; + } + + if (cmdlist->dst_mmu_pages) { + reg = RGA_MMU_DST_BASE - RGA_MODE_BASE_REG; + dest[reg >> 2] = virt_to_phys(cmdlist->dst_mmu_pages) >> 4; + mmu_ctrl |= 0x7 << 8; + } + + if (cmdlist->src1_mmu_pages) { + reg = RGA_MMU_SRC1_BASE - RGA_MODE_BASE_REG; + dest[reg >> 2] = virt_to_phys(cmdlist->src1_mmu_pages) >> 4; + mmu_ctrl |= 0x7 << 4; + } + + reg = RGA_MMU_CTRL1 - RGA_MODE_BASE_REG; + dest[reg >> 2] = mmu_ctrl; + } + + rga_dma_flush_range(cmdlist_pool_virt, cmdlist_cnt * RGA_CMDLIST_SIZE); + + runqueue->cmdlist_dma_attrs = cmdlist_dma_attrs; + runqueue->cmdlist_pool_virt = cmdlist_pool_virt; + runqueue->cmdlist_pool = cmdlist_pool; + runqueue->cmdlist_cnt = cmdlist_cnt; + + return 0; +} + +static int rga_check_reg_offset(struct device *dev, + struct rga_cmdlist_node *node) +{ + struct rga_cmdlist *cmdlist = &node->cmdlist; + int index; + int reg; + int i; + + for (i = 0; i < cmdlist->last / 2; i++) { + index = cmdlist->last - 2 * (i + 1); + reg = cmdlist->data[index]; + + switch (reg) { + case RGA_BUF_TYPE_GEMFD | RGA_DST_Y_RGB_BASE_ADDR: + case RGA_BUF_TYPE_GEMFD | RGA_SRC_Y_RGB_BASE_ADDR: + break; + + case RGA_BUF_TYPE_USERPTR | RGA_DST_Y_RGB_BASE_ADDR: + case RGA_BUF_TYPE_USERPTR | RGA_SRC_Y_RGB_BASE_ADDR: + goto err; + + default: + if (reg < RGA_MODE_BASE_REG || reg > RGA_MODE_MAX_REG) + goto err; + + if (reg % 4) + goto err; + } + } + + return 0; + +err: + dev_err(dev, "Bad register offset: 0x%x\n", cmdlist->data[index]); + return -EINVAL; +} + +static struct dma_buf_attachment * +rga_gem_buf_to_pages(struct rockchip_rga *rga, void **mmu_pages, int fd) +{ + struct dma_buf_attachment *attach; + struct dma_buf *dmabuf; + struct sg_table *sgt; + struct scatterlist *sgl; + unsigned int mapped_size = 0; + unsigned int address; + unsigned int len; + unsigned int i, p; + unsigned int *pages; + int ret; + + dmabuf = dma_buf_get(fd); + if (IS_ERR(dmabuf)) { + dev_err(rga->dev, "Failed to get dma_buf with fd %d\n", fd); + return ERR_PTR(-EINVAL); + } + + attach = dma_buf_attach(dmabuf, rga->dev); + if (IS_ERR(attach)) { + dev_err(rga->dev, "Failed to attach dma_buf\n"); + ret = PTR_ERR(attach); + goto failed_attach; + } + + sgt = dma_buf_map_attachment(attach, DMA_BIDIRECTIONAL); + if (IS_ERR(sgt)) { + dev_err(rga->dev, "Failed to map dma_buf attachment\n"); + ret = PTR_ERR(sgt); + goto failed_detach; + } + + /* + * Alloc (2^3 * 4K) = 32K byte for storing pages, those space could + * cover 32K * 4K = 128M ram address. + */ + pages = (unsigned int *)__get_free_pages(GFP_KERNEL | __GFP_ZERO, 3); + + for_each_sg(sgt->sgl, sgl, sgt->nents, i) { + len = sg_dma_len(sgl) >> PAGE_SHIFT; + address = sg_phys(sgl); + + for (p = 0; p < len; p++) { + dma_addr_t phys = address + (p << PAGE_SHIFT); + void *virt = phys_to_virt(phys); + + rga_dma_flush_range(virt, 4 * 1024); + pages[mapped_size + p] = phys; + } + + mapped_size += len; + } + + rga_dma_flush_range(pages, 32 * 1024); + + *mmu_pages = pages; + + dma_buf_unmap_attachment(attach, sgt, DMA_BIDIRECTIONAL); + + return attach; + +failed_detach: + dma_buf_detach(dmabuf, attach); +failed_attach: + dma_buf_put(dmabuf); + + return ERR_PTR(ret); +} + +static int rga_map_cmdlist_gem(struct rockchip_rga *rga, + struct rga_cmdlist_node *node, + struct drm_device *drm_dev, + struct drm_file *file) +{ + struct rga_cmdlist *cmdlist = &node->cmdlist; + struct dma_buf_attachment *attach; + void *mmu_pages; + int fd; + int i; + + for (i = 0; i < cmdlist->last / 2; i++) { + int index = cmdlist->last - 2 * (i + 1); + + switch (cmdlist->data[index]) { + case RGA_SRC_Y_RGB_BASE_ADDR | RGA_BUF_TYPE_GEMFD: + fd = cmdlist->data[index + 1]; + attach = rga_gem_buf_to_pages(rga, &mmu_pages, fd); + + cmdlist->src_attach = attach; + cmdlist->src_mmu_pages = mmu_pages; + break; + + case RGA_DST_Y_RGB_BASE_ADDR | RGA_BUF_TYPE_GEMFD: + fd = cmdlist->data[index + 1]; + attach = rga_gem_buf_to_pages(rga, &mmu_pages, fd); + + cmdlist->dst_attach = attach; + cmdlist->dst_mmu_pages = mmu_pages; + break; + } + } + + return 0; +} + +static void rga_unmap_cmdlist_gem(struct rockchip_rga *rga, + struct rga_cmdlist_node *node) +{ + struct dma_buf_attachment *attach; + struct dma_buf *dma_buf; + + attach = node->cmdlist.src_attach; + if (attach) { + dma_buf = attach->dmabuf; + dma_buf_detach(dma_buf, attach); + dma_buf_put(dma_buf); + } + node->cmdlist.src_attach = NULL; + + attach = node->cmdlist.dst_attach; + if (attach) { + dma_buf = attach->dmabuf; + dma_buf_detach(dma_buf, attach); + dma_buf_put(dma_buf); + } + node->cmdlist.dst_attach = NULL; + + if (node->cmdlist.src_mmu_pages) + free_pages((unsigned long)node->cmdlist.src_mmu_pages, 3); + node->cmdlist.src_mmu_pages = NULL; + + if (node->cmdlist.src1_mmu_pages) + free_pages((unsigned long)node->cmdlist.src1_mmu_pages, 3); + node->cmdlist.src1_mmu_pages = NULL; + + if (node->cmdlist.dst_mmu_pages) + free_pages((unsigned long)node->cmdlist.dst_mmu_pages, 3); + node->cmdlist.dst_mmu_pages = NULL; +} + +static void rga_cmd_start(struct rockchip_rga *rga, + struct rga_runqueue_node *runqueue) +{ + int ret; + + ret = pm_runtime_get_sync(rga->dev); + if (ret < 0) + return; + + rga_write(rga, RGA_SYS_CTRL, 0x00); + + rga_write(rga, RGA_CMD_BASE, runqueue->cmdlist_pool); + + rga_write(rga, RGA_SYS_CTRL, 0x22); + + rga_write(rga, RGA_INT, 0x600); + + rga_write(rga, RGA_CMD_CTRL, ((runqueue->cmdlist_cnt - 1) << 3) | 0x1); +} + +static void rga_free_runqueue_node(struct rockchip_rga *rga, + struct rga_runqueue_node *runqueue) +{ + struct rga_cmdlist_node *node; + + if (!runqueue) + return; + + if (runqueue->cmdlist_pool_virt && runqueue->cmdlist_pool) + dma_free_attrs(rga->dev, runqueue->cmdlist_cnt * RGA_CMDLIST_SIZE, + runqueue->cmdlist_pool_virt, + runqueue->cmdlist_pool, + &runqueue->cmdlist_dma_attrs); + + mutex_lock(&rga->cmdlist_mutex); + /* + * commands in run_cmdlist have been completed so unmap all gem + * objects in each command node so that they are unreferenced. + */ + list_for_each_entry(node, &runqueue->run_cmdlist, list) + rga_unmap_cmdlist_gem(rga, node); + list_splice_tail_init(&runqueue->run_cmdlist, &rga->free_cmdlist); + mutex_unlock(&rga->cmdlist_mutex); + + kmem_cache_free(rga->runqueue_slab, runqueue); +} + +static struct rga_runqueue_node *rga_get_runqueue(struct rockchip_rga *rga) +{ + struct rga_runqueue_node *runqueue; + + if (list_empty(&rga->runqueue_list)) + return NULL; + + runqueue = list_first_entry(&rga->runqueue_list, + struct rga_runqueue_node, list); + list_del_init(&runqueue->list); + + return runqueue; +} + +static void rga_exec_runqueue(struct rockchip_rga *rga) +{ + rga->runqueue_node = rga_get_runqueue(rga); + if (rga->runqueue_node) + rga_cmd_start(rga, rga->runqueue_node); +} + +static struct rga_cmdlist_node *rga_get_cmdlist(struct rockchip_rga *rga) +{ + struct rga_cmdlist_node *node; + struct device *dev = rga->dev; + + mutex_lock(&rga->cmdlist_mutex); + if (list_empty(&rga->free_cmdlist)) { + dev_err(dev, "there is no free cmdlist\n"); + mutex_unlock(&rga->cmdlist_mutex); + return NULL; + } + + node = list_first_entry(&rga->free_cmdlist, + struct rga_cmdlist_node, list); + list_del_init(&node->list); + mutex_unlock(&rga->cmdlist_mutex); + + return node; +} + +static void rga_add_cmdlist_to_inuse(struct rockchip_drm_rga_private *rga_priv, + struct rga_cmdlist_node *node) +{ + struct rga_cmdlist_node *lnode; + + if (list_empty(&rga_priv->inuse_cmdlist)) + goto add_to_list; + + /* this links to base address of new cmdlist */ + lnode = list_entry(rga_priv->inuse_cmdlist.prev, + struct rga_cmdlist_node, list); + +add_to_list: + list_add_tail(&node->list, &rga_priv->inuse_cmdlist); +} + +/* + * IOCRL functions for userspace to get RGA version. + */ +int rockchip_rga_get_ver_ioctl(struct drm_device *drm_dev, void *data, + struct drm_file *file) +{ + struct rockchip_drm_file_private *file_priv = file->driver_priv; + struct rockchip_drm_rga_private *rga_priv = file_priv->rga_priv; + struct drm_rockchip_rga_get_ver *ver = data; + struct rockchip_rga *rga; + struct device *dev; + + if (!rga_priv) + return -ENODEV; + + dev = rga_priv->dev; + if (!dev) + return -ENODEV; + + rga = dev_get_drvdata(dev); + if (!rga) + return -EFAULT; + + ver->major = rga->version.major; + ver->minor = rga->version.minor; + + return 0; +} + +/* + * IOCRL functions for userspace to send an RGA request. + */ +int rockchip_rga_set_cmdlist_ioctl(struct drm_device *drm_dev, void *data, + struct drm_file *file) +{ + struct rockchip_drm_file_private *file_priv = file->driver_priv; + struct rockchip_drm_rga_private *rga_priv = file_priv->rga_priv; + struct drm_rockchip_rga_set_cmdlist *req = data; + struct rga_cmdlist_node *node; + struct rga_cmdlist *cmdlist; + struct rockchip_rga *rga; + int ret; + + if (!rga_priv) + return -ENODEV; + + if (!rga_priv->dev) + return -ENODEV; + + rga = dev_get_drvdata(rga_priv->dev); + if (!rga) + return -EFAULT; + + node = rga_get_cmdlist(rga); + if (!node) + return -ENOMEM; + + cmdlist = &node->cmdlist; + cmdlist->last = 0; + + if (req->cmd_nr > RGA_CMDLIST_SIZE || req->cmd_buf_nr > RGA_CMDBUF_SIZE) { + dev_err(rga->dev, "cmdlist size is too big\n"); + return -EINVAL; + } + + /* + * Copy the command / buffer registers setting from userspace, each + * command have two integer, one for register offset, another for + * register value. + */ + if (copy_from_user(cmdlist->data, compat_ptr((compat_uptr_t)req->cmd), + sizeof(struct drm_rockchip_rga_cmd) * req->cmd_nr)) + return -EFAULT; + cmdlist->last += req->cmd_nr * 2; + + if (copy_from_user(&cmdlist->data[cmdlist->last], + compat_ptr((compat_uptr_t)req->cmd_buf), + sizeof(struct drm_rockchip_rga_cmd) * req->cmd_buf_nr)) + return -EFAULT; + cmdlist->last += req->cmd_buf_nr * 2; + + /* + * Check the userspace command registers, and mapping the framebuffer, + * create the RGA mmu pages or get the framebuffer dma address. + */ + ret = rga_check_reg_offset(rga->dev, node); + if (ret < 0) + return ret; + + ret = rga_map_cmdlist_gem(rga, node, drm_dev, file); + if (ret < 0) + return ret; + + rga_add_cmdlist_to_inuse(rga_priv, node); + + return 0; +} + +/* + * IOCRL functions for userspace to start RGA transform. + */ +int rockchip_rga_exec_ioctl(struct drm_device *drm_dev, void *data, + struct drm_file *file) +{ + struct rockchip_drm_file_private *file_priv = file->driver_priv; + struct rockchip_drm_rga_private *rga_priv = file_priv->rga_priv; + struct rga_runqueue_node *runqueue; + struct rockchip_rga *rga; + struct device *dev; + int ret; + + if (!rga_priv) + return -ENODEV; + + dev = rga_priv->dev; + if (!dev) + return -ENODEV; + + rga = dev_get_drvdata(dev); + if (!rga) + return -EFAULT; + + runqueue = kmem_cache_alloc(rga->runqueue_slab, GFP_KERNEL); + if (!runqueue) { + dev_err(rga->dev, "failed to allocate memory\n"); + return -ENOMEM; + } + + runqueue->dev = rga->dev; + + init_completion(&runqueue->complete); + + INIT_LIST_HEAD(&runqueue->run_cmdlist); + + list_splice_init(&rga_priv->inuse_cmdlist, &runqueue->run_cmdlist); + + if (list_empty(&runqueue->run_cmdlist)) { + dev_err(rga->dev, "there is no inuse cmdlist\n"); + kmem_cache_free(rga->runqueue_slab, runqueue); + return -EPERM; + } + + ret = rga_alloc_dma_buf_for_cmdlist(runqueue); + if (ret < 0) { + dev_err(rga->dev, "cmdlist init failed\n"); + return ret; + } + + mutex_lock(&rga->runqueue_mutex); + runqueue->pid = current->pid; + runqueue->file = file; + list_add_tail(&runqueue->list, &rga->runqueue_list); + if (!rga->runqueue_node) + rga_exec_runqueue(rga); + mutex_unlock(&rga->runqueue_mutex); + + wait_for_completion(&runqueue->complete); + rga_free_runqueue_node(rga, runqueue); + + return 0; +} + +static int rockchip_rga_open(struct drm_device *drm_dev, struct device *dev, + struct drm_file *file) +{ + struct rockchip_drm_file_private *file_priv = file->driver_priv; + struct rockchip_drm_rga_private *rga_priv; + + rga_priv = kzalloc(sizeof(*rga_priv), GFP_KERNEL); + if (!rga_priv) + return -ENOMEM; + + rga_priv->dev = dev; + file_priv->rga_priv = rga_priv; + + INIT_LIST_HEAD(&rga_priv->inuse_cmdlist); + + return 0; +} + +static void rockchip_rga_close(struct drm_device *drm_dev, struct device *dev, + struct drm_file *file) +{ + struct rockchip_drm_file_private *file_priv = file->driver_priv; + struct rockchip_drm_rga_private *rga_priv = file_priv->rga_priv; + struct rga_cmdlist_node *node, *n; + struct rockchip_rga *rga; + + if (!dev) + return; + + rga = dev_get_drvdata(dev); + if (!rga) + return; + + mutex_lock(&rga->cmdlist_mutex); + list_for_each_entry_safe(node, n, &rga_priv->inuse_cmdlist, list) { + /* + * unmap all gem objects not completed. + * + * P.S. if current process was terminated forcely then + * there may be some commands in inuse_cmdlist so unmap + * them. + */ + rga_unmap_cmdlist_gem(rga, node); + list_move_tail(&node->list, &rga->free_cmdlist); + } + mutex_unlock(&rga->cmdlist_mutex); + + kfree(file_priv->rga_priv); +} + +static void rga_runqueue_worker(struct work_struct *work) +{ + struct rockchip_rga *rga = container_of(work, struct rockchip_rga, + runqueue_work); + + mutex_lock(&rga->runqueue_mutex); + pm_runtime_put_sync(rga->dev); + + complete(&rga->runqueue_node->complete); + + if (rga->suspended) + rga->runqueue_node = NULL; + else + rga_exec_runqueue(rga); + + mutex_unlock(&rga->runqueue_mutex); +} + +static irqreturn_t rga_irq_handler(int irq, void *dev_id) +{ + struct rockchip_rga *rga = dev_id; + int intr; + + intr = rga_read(rga, RGA_INT) & 0xf; + + rga_mod(rga, RGA_INT, intr << 4, 0xf << 4); + + if (intr & 0x04) + queue_work(rga->rga_workq, &rga->runqueue_work); + + return IRQ_HANDLED; +} + +static int rga_parse_dt(struct rockchip_rga *rga) +{ + struct reset_control *core_rst, *axi_rst, *ahb_rst; + + core_rst = devm_reset_control_get(rga->dev, "core"); + if (IS_ERR(core_rst)) { + dev_err(rga->dev, "failed to get core reset controller\n"); + return PTR_ERR(core_rst); + } + + axi_rst = devm_reset_control_get(rga->dev, "axi"); + if (IS_ERR(axi_rst)) { + dev_err(rga->dev, "failed to get axi reset controller\n"); + return PTR_ERR(axi_rst); + } + + ahb_rst = devm_reset_control_get(rga->dev, "ahb"); + if (IS_ERR(ahb_rst)) { + dev_err(rga->dev, "failed to get ahb reset controller\n"); + return PTR_ERR(ahb_rst); + } + + reset_control_assert(core_rst); + udelay(1); + reset_control_deassert(core_rst); + + reset_control_assert(axi_rst); + udelay(1); + reset_control_deassert(axi_rst); + + reset_control_assert(ahb_rst); + udelay(1); + reset_control_deassert(ahb_rst); + + rga->sclk = devm_clk_get(rga->dev, "sclk"); + if (IS_ERR(rga->sclk)) { + dev_err(rga->dev, "failed to get sclk clock\n"); + return PTR_ERR(rga->sclk); + } + + rga->aclk = devm_clk_get(rga->dev, "aclk"); + if (IS_ERR(rga->aclk)) { + dev_err(rga->dev, "failed to get aclk clock\n"); + return PTR_ERR(rga->aclk); + } + + rga->hclk = devm_clk_get(rga->dev, "hclk"); + if (IS_ERR(rga->hclk)) { + dev_err(rga->dev, "failed to get hclk clock\n"); + return PTR_ERR(rga->hclk); + } + + return rga_enable_clocks(rga); +} + +static const struct of_device_id rockchip_rga_dt_ids[] = { + { .compatible = "rockchip,rk3288-rga", }, + { .compatible = "rockchip,rk3228-rga", }, + { .compatible = "rockchip,rk3399-rga", }, + {}, +}; +MODULE_DEVICE_TABLE(of, rockchip_rga_dt_ids); + +static int rga_probe(struct platform_device *pdev) +{ + struct drm_rockchip_subdrv *subdrv; + struct rockchip_rga *rga; + struct resource *iores; + int irq; + int ret; + + if (!pdev->dev.of_node) + return -ENODEV; + + rga = devm_kzalloc(&pdev->dev, sizeof(*rga), GFP_KERNEL); + if (!rga) + return -ENOMEM; + + rga->dev = &pdev->dev; + + rga->runqueue_slab = kmem_cache_create("rga_runqueue_slab", + sizeof(struct rga_runqueue_node), + 0, 0, NULL); + if (!rga->runqueue_slab) + return -ENOMEM; + + rga->rga_workq = create_singlethread_workqueue("rga"); + if (!rga->rga_workq) { + dev_err(rga->dev, "failed to create workqueue\n"); + goto err_destroy_slab; + } + + INIT_WORK(&rga->runqueue_work, rga_runqueue_worker); + INIT_LIST_HEAD(&rga->runqueue_list); + mutex_init(&rga->runqueue_mutex); + + INIT_LIST_HEAD(&rga->free_cmdlist); + mutex_init(&rga->cmdlist_mutex); + + rga_init_cmdlist(rga); + + ret = rga_parse_dt(rga); + if (ret) { + dev_err(rga->dev, "Unable to parse OF data\n"); + goto err_destroy_workqueue; + } + + pm_runtime_enable(rga->dev); + + iores = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + rga->regs = devm_ioremap_resource(rga->dev, iores); + if (IS_ERR(rga->regs)) { + ret = PTR_ERR(rga->regs); + goto err_put_clk; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(rga->dev, "failed to get irq\n"); + ret = irq; + goto err_put_clk; + } + + ret = devm_request_irq(rga->dev, irq, rga_irq_handler, 0, + dev_name(rga->dev), rga); + if (ret < 0) { + dev_err(rga->dev, "failed to request irq\n"); + goto err_put_clk; + } + + platform_set_drvdata(pdev, rga); + + rga->version.major = (rga_read(rga, RGA_VERSION_INFO) >> 24) & 0xFF; + rga->version.minor = (rga_read(rga, RGA_VERSION_INFO) >> 20) & 0x0F; + + subdrv = &rga->subdrv; + subdrv->dev = rga->dev; + subdrv->open = rockchip_rga_open; + subdrv->close = rockchip_rga_close; + + rockchip_drm_register_subdrv(subdrv); + + return 0; + +err_put_clk: + pm_runtime_disable(rga->dev); +err_destroy_workqueue: + destroy_workqueue(rga->rga_workq); +err_destroy_slab: + kmem_cache_destroy(rga->runqueue_slab); + + return ret; +} + +static int rga_remove(struct platform_device *pdev) +{ + struct rockchip_rga *rga = platform_get_drvdata(pdev); + + cancel_work_sync(&rga->runqueue_work); + + while (rga->runqueue_node) { + rga_free_runqueue_node(rga, rga->runqueue_node); + rga->runqueue_node = rga_get_runqueue(rga); + } + + rockchip_drm_unregister_subdrv(&rga->subdrv); + + pm_runtime_disable(rga->dev); + + return 0; +} + +static int rga_suspend(struct device *dev) +{ + struct rockchip_rga *rga = dev_get_drvdata(dev); + + mutex_lock(&rga->runqueue_mutex); + rga->suspended = true; + mutex_unlock(&rga->runqueue_mutex); + + flush_work(&rga->runqueue_work); + + return 0; +} + +static int rga_resume(struct device *dev) +{ + struct rockchip_rga *rga = dev_get_drvdata(dev); + + rga->suspended = false; + rga_exec_runqueue(rga); + + return 0; +} + +#ifdef CONFIG_PM +static int rga_runtime_suspend(struct device *dev) +{ + struct rockchip_rga *rga = dev_get_drvdata(dev); + + rga_disable_clocks(rga); + + return 0; +} + +static int rga_runtime_resume(struct device *dev) +{ + struct rockchip_rga *rga = dev_get_drvdata(dev); + + return rga_enable_clocks(rga); +} +#endif + +static const struct dev_pm_ops rga_pm = { + SET_SYSTEM_SLEEP_PM_OPS(rga_suspend, rga_resume) + SET_RUNTIME_PM_OPS(rga_runtime_suspend, + rga_runtime_resume, NULL) +}; + +static struct platform_driver rga_pltfm_driver = { + .probe = rga_probe, + .remove = rga_remove, + .driver = { + .name = "rockchip-rga", + .pm = &rga_pm, + .of_match_table = rockchip_rga_dt_ids, + }, +}; + +module_platform_driver(rga_pltfm_driver); + +MODULE_AUTHOR("Yakir Yang "); +MODULE_DESCRIPTION("Rockchip RGA Driver Extension"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:rockchip-rga"); diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_rga.h b/drivers/gpu/drm/rockchip/rockchip_drm_rga.h new file mode 100644 index 000000000000..2dbd10d1f85d --- /dev/null +++ b/drivers/gpu/drm/rockchip/rockchip_drm_rga.h @@ -0,0 +1,108 @@ +#ifndef __ROCKCHIP_DRM_RGA__ +#define __ROCKCHIP_DRM_RGA__ + +#define RGA_CMDBUF_SIZE 14 +#define RGA_CMDLIST_SIZE 0x20 +#define RGA_CMDLIST_NUM 64 + +/* cmdlist data structure */ +struct rga_cmdlist { + u32 head; + u32 data[RGA_CMDLIST_SIZE * 2]; + u32 last; /* last data offset */ + void *src_mmu_pages; + void *dst_mmu_pages; + void *src1_mmu_pages; + struct dma_buf_attachment *src_attach; + struct dma_buf_attachment *dst_attach; +}; + +struct rga_cmdlist_node { + struct list_head list; + struct rga_cmdlist cmdlist; +}; + +struct rga_runqueue_node { + struct list_head list; + + struct device *dev; + pid_t pid; + struct drm_file *file; + struct completion complete; + + struct list_head run_cmdlist; + + int cmdlist_cnt; + void *cmdlist_pool_virt; + dma_addr_t cmdlist_pool; + struct dma_attrs cmdlist_dma_attrs; +}; + +struct rockchip_rga_version { + u32 major; + u32 minor; +}; + +struct rockchip_rga { + struct drm_device *drm_dev; + struct device *dev; + struct regmap *grf; + void __iomem *regs; + struct clk *sclk; + struct clk *aclk; + struct clk *hclk; + + bool suspended; + struct rockchip_rga_version version; + struct drm_rockchip_subdrv subdrv; + struct workqueue_struct *rga_workq; + struct work_struct runqueue_work; + + /* rga command list pool */ + struct rga_cmdlist_node cmdlist_node[RGA_CMDLIST_NUM]; + struct mutex cmdlist_mutex; + + struct list_head free_cmdlist; + + /* rga runqueue */ + struct rga_runqueue_node *runqueue_node; + struct list_head runqueue_list; + struct mutex runqueue_mutex; + struct kmem_cache *runqueue_slab; +}; + +struct rockchip_drm_rga_private { + struct device *dev; + struct list_head inuse_cmdlist; + struct list_head userptr_list; +}; + +#ifdef CONFIG_ROCKCHIP_DRM_RGA +int rockchip_rga_get_ver_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv); +int rockchip_rga_set_cmdlist_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv); +int rockchip_rga_exec_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv); +#else +static inline int rockchip_rga_get_ver_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + return -ENODEV; +} + +static inline int rockchip_rga_set_cmdlist_ioctl(struct drm_device *dev, + void *data, + struct drm_file *file_priv) +{ + return -ENODEV; +} + +static inline int rockchip_rga_exec_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + return -ENODEV; +} +#endif + +#endif /* __ROCKCHIP_DRM_RGA__ */ diff --git a/include/uapi/drm/rockchip_drm.h b/include/uapi/drm/rockchip_drm.h index b8f367d9114d..1dae92e6d0d2 100644 --- a/include/uapi/drm/rockchip_drm.h +++ b/include/uapi/drm/rockchip_drm.h @@ -71,11 +71,42 @@ struct drm_rockchip_gem_cpu_release { uint32_t handle; }; +struct drm_rockchip_rga_get_ver { + __u32 major; + __u32 minor; +}; + +struct drm_rockchip_rga_cmd { + __u32 offset; + __u32 data; +}; + +enum drm_rockchip_rga_buf_type { + RGA_BUF_TYPE_USERPTR = 1 << 31, + RGA_BUF_TYPE_GEMFD = 1 << 30, +}; + +struct drm_rockchip_rga_set_cmdlist { + __u64 cmd; + __u64 cmd_buf; + __u32 cmd_nr; + __u32 cmd_buf_nr; + __u64 user_data; +}; + +struct drm_rockchip_rga_exec { + __u64 async; +}; + #define DRM_ROCKCHIP_GEM_CREATE 0x00 #define DRM_ROCKCHIP_GEM_MAP_OFFSET 0x01 #define DRM_ROCKCHIP_GEM_CPU_ACQUIRE 0x02 #define DRM_ROCKCHIP_GEM_CPU_RELEASE 0x03 +#define DRM_ROCKCHIP_RGA_GET_VER 0x20 +#define DRM_ROCKCHIP_RGA_SET_CMDLIST 0x21 +#define DRM_ROCKCHIP_RGA_EXEC 0x22 + #define DRM_IOCTL_ROCKCHIP_GEM_CREATE DRM_IOWR(DRM_COMMAND_BASE + \ DRM_ROCKCHIP_GEM_CREATE, struct drm_rockchip_gem_create) @@ -88,4 +119,13 @@ struct drm_rockchip_gem_cpu_release { #define DRM_IOCTL_ROCKCHIP_GEM_CPU_RELEASE DRM_IOWR(DRM_COMMAND_BASE + \ DRM_ROCKCHIP_GEM_CPU_RELEASE, struct drm_rockchip_gem_cpu_release) +#define DRM_IOCTL_ROCKCHIP_RGA_GET_VER DRM_IOWR(DRM_COMMAND_BASE + \ + DRM_ROCKCHIP_RGA_GET_VER, struct drm_rockchip_rga_get_ver) + +#define DRM_IOCTL_ROCKCHIP_RGA_SET_CMDLIST DRM_IOWR(DRM_COMMAND_BASE + \ + DRM_ROCKCHIP_RGA_SET_CMDLIST, struct drm_rockchip_rga_set_cmdlist) + +#define DRM_IOCTL_ROCKCHIP_RGA_EXEC DRM_IOWR(DRM_COMMAND_BASE + \ + DRM_ROCKCHIP_RGA_EXEC, struct drm_rockchip_rga_exec) + #endif /* _UAPI_ROCKCHIP_DRM_H */