18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * pxa3xx-gcu.c - Linux kernel module for PXA3xx graphics controllers 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * This driver needs a DirectFB counterpart in user space, communication 68c2ecf20Sopenharmony_ci * is handled via mmap()ed memory areas and an ioctl. 78c2ecf20Sopenharmony_ci * 88c2ecf20Sopenharmony_ci * Copyright (c) 2009 Daniel Mack <daniel@caiaq.de> 98c2ecf20Sopenharmony_ci * Copyright (c) 2009 Janine Kropp <nin@directfb.org> 108c2ecf20Sopenharmony_ci * Copyright (c) 2009 Denis Oliver Kropp <dok@directfb.org> 118c2ecf20Sopenharmony_ci */ 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_ci/* 148c2ecf20Sopenharmony_ci * WARNING: This controller is attached to System Bus 2 of the PXA which 158c2ecf20Sopenharmony_ci * needs its arbiter to be enabled explicitly (CKENB & 1<<9). 168c2ecf20Sopenharmony_ci * There is currently no way to do this from Linux, so you need to teach 178c2ecf20Sopenharmony_ci * your bootloader for now. 188c2ecf20Sopenharmony_ci */ 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_ci#include <linux/module.h> 218c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 228c2ecf20Sopenharmony_ci#include <linux/dma-mapping.h> 238c2ecf20Sopenharmony_ci#include <linux/miscdevice.h> 248c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 258c2ecf20Sopenharmony_ci#include <linux/spinlock.h> 268c2ecf20Sopenharmony_ci#include <linux/uaccess.h> 278c2ecf20Sopenharmony_ci#include <linux/ioctl.h> 288c2ecf20Sopenharmony_ci#include <linux/delay.h> 298c2ecf20Sopenharmony_ci#include <linux/sched.h> 308c2ecf20Sopenharmony_ci#include <linux/slab.h> 318c2ecf20Sopenharmony_ci#include <linux/clk.h> 328c2ecf20Sopenharmony_ci#include <linux/fs.h> 338c2ecf20Sopenharmony_ci#include <linux/io.h> 348c2ecf20Sopenharmony_ci#include <linux/of.h> 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_ci#include "pxa3xx-gcu.h" 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci#define DRV_NAME "pxa3xx-gcu" 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_ci#define REG_GCCR 0x00 418c2ecf20Sopenharmony_ci#define GCCR_SYNC_CLR (1 << 9) 428c2ecf20Sopenharmony_ci#define GCCR_BP_RST (1 << 8) 438c2ecf20Sopenharmony_ci#define GCCR_ABORT (1 << 6) 448c2ecf20Sopenharmony_ci#define GCCR_STOP (1 << 4) 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_ci#define REG_GCISCR 0x04 478c2ecf20Sopenharmony_ci#define REG_GCIECR 0x08 488c2ecf20Sopenharmony_ci#define REG_GCRBBR 0x20 498c2ecf20Sopenharmony_ci#define REG_GCRBLR 0x24 508c2ecf20Sopenharmony_ci#define REG_GCRBHR 0x28 518c2ecf20Sopenharmony_ci#define REG_GCRBTR 0x2C 528c2ecf20Sopenharmony_ci#define REG_GCRBEXHR 0x30 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_ci#define IE_EOB (1 << 0) 558c2ecf20Sopenharmony_ci#define IE_EEOB (1 << 5) 568c2ecf20Sopenharmony_ci#define IE_ALL 0xff 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_ci#define SHARED_SIZE PAGE_ALIGN(sizeof(struct pxa3xx_gcu_shared)) 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_ci/* #define PXA3XX_GCU_DEBUG */ 618c2ecf20Sopenharmony_ci/* #define PXA3XX_GCU_DEBUG_TIMER */ 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_ci#ifdef PXA3XX_GCU_DEBUG 648c2ecf20Sopenharmony_ci#define QDUMP(msg) \ 658c2ecf20Sopenharmony_ci do { \ 668c2ecf20Sopenharmony_ci QPRINT(priv, KERN_DEBUG, msg); \ 678c2ecf20Sopenharmony_ci } while (0) 688c2ecf20Sopenharmony_ci#else 698c2ecf20Sopenharmony_ci#define QDUMP(msg) do {} while (0) 708c2ecf20Sopenharmony_ci#endif 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci#define QERROR(msg) \ 738c2ecf20Sopenharmony_ci do { \ 748c2ecf20Sopenharmony_ci QPRINT(priv, KERN_ERR, msg); \ 758c2ecf20Sopenharmony_ci } while (0) 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_cistruct pxa3xx_gcu_batch { 788c2ecf20Sopenharmony_ci struct pxa3xx_gcu_batch *next; 798c2ecf20Sopenharmony_ci u32 *ptr; 808c2ecf20Sopenharmony_ci dma_addr_t phys; 818c2ecf20Sopenharmony_ci unsigned long length; 828c2ecf20Sopenharmony_ci}; 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_cistruct pxa3xx_gcu_priv { 858c2ecf20Sopenharmony_ci struct device *dev; 868c2ecf20Sopenharmony_ci void __iomem *mmio_base; 878c2ecf20Sopenharmony_ci struct clk *clk; 888c2ecf20Sopenharmony_ci struct pxa3xx_gcu_shared *shared; 898c2ecf20Sopenharmony_ci dma_addr_t shared_phys; 908c2ecf20Sopenharmony_ci struct resource *resource_mem; 918c2ecf20Sopenharmony_ci struct miscdevice misc_dev; 928c2ecf20Sopenharmony_ci wait_queue_head_t wait_idle; 938c2ecf20Sopenharmony_ci wait_queue_head_t wait_free; 948c2ecf20Sopenharmony_ci spinlock_t spinlock; 958c2ecf20Sopenharmony_ci struct timespec64 base_time; 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_ci struct pxa3xx_gcu_batch *free; 988c2ecf20Sopenharmony_ci struct pxa3xx_gcu_batch *ready; 998c2ecf20Sopenharmony_ci struct pxa3xx_gcu_batch *ready_last; 1008c2ecf20Sopenharmony_ci struct pxa3xx_gcu_batch *running; 1018c2ecf20Sopenharmony_ci}; 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_cistatic inline unsigned long 1048c2ecf20Sopenharmony_cigc_readl(struct pxa3xx_gcu_priv *priv, unsigned int off) 1058c2ecf20Sopenharmony_ci{ 1068c2ecf20Sopenharmony_ci return __raw_readl(priv->mmio_base + off); 1078c2ecf20Sopenharmony_ci} 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_cistatic inline void 1108c2ecf20Sopenharmony_cigc_writel(struct pxa3xx_gcu_priv *priv, unsigned int off, unsigned long val) 1118c2ecf20Sopenharmony_ci{ 1128c2ecf20Sopenharmony_ci __raw_writel(val, priv->mmio_base + off); 1138c2ecf20Sopenharmony_ci} 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci#define QPRINT(priv, level, msg) \ 1168c2ecf20Sopenharmony_ci do { \ 1178c2ecf20Sopenharmony_ci struct timespec64 ts; \ 1188c2ecf20Sopenharmony_ci struct pxa3xx_gcu_shared *shared = priv->shared; \ 1198c2ecf20Sopenharmony_ci u32 base = gc_readl(priv, REG_GCRBBR); \ 1208c2ecf20Sopenharmony_ci \ 1218c2ecf20Sopenharmony_ci ktime_get_ts64(&ts); \ 1228c2ecf20Sopenharmony_ci ts = timespec64_sub(ts, priv->base_time); \ 1238c2ecf20Sopenharmony_ci \ 1248c2ecf20Sopenharmony_ci printk(level "%lld.%03ld.%03ld - %-17s: %-21s (%s, " \ 1258c2ecf20Sopenharmony_ci "STATUS " \ 1268c2ecf20Sopenharmony_ci "0x%02lx, B 0x%08lx [%ld], E %5ld, H %5ld, " \ 1278c2ecf20Sopenharmony_ci "T %5ld)\n", \ 1288c2ecf20Sopenharmony_ci (s64)(ts.tv_sec), \ 1298c2ecf20Sopenharmony_ci ts.tv_nsec / NSEC_PER_MSEC, \ 1308c2ecf20Sopenharmony_ci (ts.tv_nsec % NSEC_PER_MSEC) / USEC_PER_MSEC, \ 1318c2ecf20Sopenharmony_ci __func__, msg, \ 1328c2ecf20Sopenharmony_ci shared->hw_running ? "running" : " idle", \ 1338c2ecf20Sopenharmony_ci gc_readl(priv, REG_GCISCR), \ 1348c2ecf20Sopenharmony_ci gc_readl(priv, REG_GCRBBR), \ 1358c2ecf20Sopenharmony_ci gc_readl(priv, REG_GCRBLR), \ 1368c2ecf20Sopenharmony_ci (gc_readl(priv, REG_GCRBEXHR) - base) / 4, \ 1378c2ecf20Sopenharmony_ci (gc_readl(priv, REG_GCRBHR) - base) / 4, \ 1388c2ecf20Sopenharmony_ci (gc_readl(priv, REG_GCRBTR) - base) / 4); \ 1398c2ecf20Sopenharmony_ci } while (0) 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_cistatic void 1428c2ecf20Sopenharmony_cipxa3xx_gcu_reset(struct pxa3xx_gcu_priv *priv) 1438c2ecf20Sopenharmony_ci{ 1448c2ecf20Sopenharmony_ci QDUMP("RESET"); 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_ci /* disable interrupts */ 1478c2ecf20Sopenharmony_ci gc_writel(priv, REG_GCIECR, 0); 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_ci /* reset hardware */ 1508c2ecf20Sopenharmony_ci gc_writel(priv, REG_GCCR, GCCR_ABORT); 1518c2ecf20Sopenharmony_ci gc_writel(priv, REG_GCCR, 0); 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci memset(priv->shared, 0, SHARED_SIZE); 1548c2ecf20Sopenharmony_ci priv->shared->buffer_phys = priv->shared_phys; 1558c2ecf20Sopenharmony_ci priv->shared->magic = PXA3XX_GCU_SHARED_MAGIC; 1568c2ecf20Sopenharmony_ci 1578c2ecf20Sopenharmony_ci ktime_get_ts64(&priv->base_time); 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci /* set up the ring buffer pointers */ 1608c2ecf20Sopenharmony_ci gc_writel(priv, REG_GCRBLR, 0); 1618c2ecf20Sopenharmony_ci gc_writel(priv, REG_GCRBBR, priv->shared_phys); 1628c2ecf20Sopenharmony_ci gc_writel(priv, REG_GCRBTR, priv->shared_phys); 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_ci /* enable all IRQs except EOB */ 1658c2ecf20Sopenharmony_ci gc_writel(priv, REG_GCIECR, IE_ALL & ~IE_EOB); 1668c2ecf20Sopenharmony_ci} 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_cistatic void 1698c2ecf20Sopenharmony_cidump_whole_state(struct pxa3xx_gcu_priv *priv) 1708c2ecf20Sopenharmony_ci{ 1718c2ecf20Sopenharmony_ci struct pxa3xx_gcu_shared *sh = priv->shared; 1728c2ecf20Sopenharmony_ci u32 base = gc_readl(priv, REG_GCRBBR); 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci QDUMP("DUMP"); 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci printk(KERN_DEBUG "== PXA3XX-GCU DUMP ==\n" 1778c2ecf20Sopenharmony_ci "%s, STATUS 0x%02lx, B 0x%08lx [%ld], E %5ld, H %5ld, T %5ld\n", 1788c2ecf20Sopenharmony_ci sh->hw_running ? "running" : "idle ", 1798c2ecf20Sopenharmony_ci gc_readl(priv, REG_GCISCR), 1808c2ecf20Sopenharmony_ci gc_readl(priv, REG_GCRBBR), 1818c2ecf20Sopenharmony_ci gc_readl(priv, REG_GCRBLR), 1828c2ecf20Sopenharmony_ci (gc_readl(priv, REG_GCRBEXHR) - base) / 4, 1838c2ecf20Sopenharmony_ci (gc_readl(priv, REG_GCRBHR) - base) / 4, 1848c2ecf20Sopenharmony_ci (gc_readl(priv, REG_GCRBTR) - base) / 4); 1858c2ecf20Sopenharmony_ci} 1868c2ecf20Sopenharmony_ci 1878c2ecf20Sopenharmony_cistatic void 1888c2ecf20Sopenharmony_ciflush_running(struct pxa3xx_gcu_priv *priv) 1898c2ecf20Sopenharmony_ci{ 1908c2ecf20Sopenharmony_ci struct pxa3xx_gcu_batch *running = priv->running; 1918c2ecf20Sopenharmony_ci struct pxa3xx_gcu_batch *next; 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_ci while (running) { 1948c2ecf20Sopenharmony_ci next = running->next; 1958c2ecf20Sopenharmony_ci running->next = priv->free; 1968c2ecf20Sopenharmony_ci priv->free = running; 1978c2ecf20Sopenharmony_ci running = next; 1988c2ecf20Sopenharmony_ci } 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_ci priv->running = NULL; 2018c2ecf20Sopenharmony_ci} 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_cistatic void 2048c2ecf20Sopenharmony_cirun_ready(struct pxa3xx_gcu_priv *priv) 2058c2ecf20Sopenharmony_ci{ 2068c2ecf20Sopenharmony_ci unsigned int num = 0; 2078c2ecf20Sopenharmony_ci struct pxa3xx_gcu_shared *shared = priv->shared; 2088c2ecf20Sopenharmony_ci struct pxa3xx_gcu_batch *ready = priv->ready; 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci QDUMP("Start"); 2118c2ecf20Sopenharmony_ci 2128c2ecf20Sopenharmony_ci BUG_ON(!ready); 2138c2ecf20Sopenharmony_ci 2148c2ecf20Sopenharmony_ci shared->buffer[num++] = 0x05000000; 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_ci while (ready) { 2178c2ecf20Sopenharmony_ci shared->buffer[num++] = 0x00000001; 2188c2ecf20Sopenharmony_ci shared->buffer[num++] = ready->phys; 2198c2ecf20Sopenharmony_ci ready = ready->next; 2208c2ecf20Sopenharmony_ci } 2218c2ecf20Sopenharmony_ci 2228c2ecf20Sopenharmony_ci shared->buffer[num++] = 0x05000000; 2238c2ecf20Sopenharmony_ci priv->running = priv->ready; 2248c2ecf20Sopenharmony_ci priv->ready = priv->ready_last = NULL; 2258c2ecf20Sopenharmony_ci gc_writel(priv, REG_GCRBLR, 0); 2268c2ecf20Sopenharmony_ci shared->hw_running = 1; 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_ci /* ring base address */ 2298c2ecf20Sopenharmony_ci gc_writel(priv, REG_GCRBBR, shared->buffer_phys); 2308c2ecf20Sopenharmony_ci 2318c2ecf20Sopenharmony_ci /* ring tail address */ 2328c2ecf20Sopenharmony_ci gc_writel(priv, REG_GCRBTR, shared->buffer_phys + num * 4); 2338c2ecf20Sopenharmony_ci 2348c2ecf20Sopenharmony_ci /* ring length */ 2358c2ecf20Sopenharmony_ci gc_writel(priv, REG_GCRBLR, ((num + 63) & ~63) * 4); 2368c2ecf20Sopenharmony_ci} 2378c2ecf20Sopenharmony_ci 2388c2ecf20Sopenharmony_cistatic irqreturn_t 2398c2ecf20Sopenharmony_cipxa3xx_gcu_handle_irq(int irq, void *ctx) 2408c2ecf20Sopenharmony_ci{ 2418c2ecf20Sopenharmony_ci struct pxa3xx_gcu_priv *priv = ctx; 2428c2ecf20Sopenharmony_ci struct pxa3xx_gcu_shared *shared = priv->shared; 2438c2ecf20Sopenharmony_ci u32 status = gc_readl(priv, REG_GCISCR) & IE_ALL; 2448c2ecf20Sopenharmony_ci 2458c2ecf20Sopenharmony_ci QDUMP("-Interrupt"); 2468c2ecf20Sopenharmony_ci 2478c2ecf20Sopenharmony_ci if (!status) 2488c2ecf20Sopenharmony_ci return IRQ_NONE; 2498c2ecf20Sopenharmony_ci 2508c2ecf20Sopenharmony_ci spin_lock(&priv->spinlock); 2518c2ecf20Sopenharmony_ci shared->num_interrupts++; 2528c2ecf20Sopenharmony_ci 2538c2ecf20Sopenharmony_ci if (status & IE_EEOB) { 2548c2ecf20Sopenharmony_ci QDUMP(" [EEOB]"); 2558c2ecf20Sopenharmony_ci 2568c2ecf20Sopenharmony_ci flush_running(priv); 2578c2ecf20Sopenharmony_ci wake_up_all(&priv->wait_free); 2588c2ecf20Sopenharmony_ci 2598c2ecf20Sopenharmony_ci if (priv->ready) { 2608c2ecf20Sopenharmony_ci run_ready(priv); 2618c2ecf20Sopenharmony_ci } else { 2628c2ecf20Sopenharmony_ci /* There is no more data prepared by the userspace. 2638c2ecf20Sopenharmony_ci * Set hw_running = 0 and wait for the next userspace 2648c2ecf20Sopenharmony_ci * kick-off */ 2658c2ecf20Sopenharmony_ci shared->num_idle++; 2668c2ecf20Sopenharmony_ci shared->hw_running = 0; 2678c2ecf20Sopenharmony_ci 2688c2ecf20Sopenharmony_ci QDUMP(" '-> Idle."); 2698c2ecf20Sopenharmony_ci 2708c2ecf20Sopenharmony_ci /* set ring buffer length to zero */ 2718c2ecf20Sopenharmony_ci gc_writel(priv, REG_GCRBLR, 0); 2728c2ecf20Sopenharmony_ci 2738c2ecf20Sopenharmony_ci wake_up_all(&priv->wait_idle); 2748c2ecf20Sopenharmony_ci } 2758c2ecf20Sopenharmony_ci 2768c2ecf20Sopenharmony_ci shared->num_done++; 2778c2ecf20Sopenharmony_ci } else { 2788c2ecf20Sopenharmony_ci QERROR(" [???]"); 2798c2ecf20Sopenharmony_ci dump_whole_state(priv); 2808c2ecf20Sopenharmony_ci } 2818c2ecf20Sopenharmony_ci 2828c2ecf20Sopenharmony_ci /* Clear the interrupt */ 2838c2ecf20Sopenharmony_ci gc_writel(priv, REG_GCISCR, status); 2848c2ecf20Sopenharmony_ci spin_unlock(&priv->spinlock); 2858c2ecf20Sopenharmony_ci 2868c2ecf20Sopenharmony_ci return IRQ_HANDLED; 2878c2ecf20Sopenharmony_ci} 2888c2ecf20Sopenharmony_ci 2898c2ecf20Sopenharmony_cistatic int 2908c2ecf20Sopenharmony_cipxa3xx_gcu_wait_idle(struct pxa3xx_gcu_priv *priv) 2918c2ecf20Sopenharmony_ci{ 2928c2ecf20Sopenharmony_ci int ret = 0; 2938c2ecf20Sopenharmony_ci 2948c2ecf20Sopenharmony_ci QDUMP("Waiting for idle..."); 2958c2ecf20Sopenharmony_ci 2968c2ecf20Sopenharmony_ci /* Does not need to be atomic. There's a lock in user space, 2978c2ecf20Sopenharmony_ci * but anyhow, this is just for statistics. */ 2988c2ecf20Sopenharmony_ci priv->shared->num_wait_idle++; 2998c2ecf20Sopenharmony_ci 3008c2ecf20Sopenharmony_ci while (priv->shared->hw_running) { 3018c2ecf20Sopenharmony_ci int num = priv->shared->num_interrupts; 3028c2ecf20Sopenharmony_ci u32 rbexhr = gc_readl(priv, REG_GCRBEXHR); 3038c2ecf20Sopenharmony_ci 3048c2ecf20Sopenharmony_ci ret = wait_event_interruptible_timeout(priv->wait_idle, 3058c2ecf20Sopenharmony_ci !priv->shared->hw_running, HZ*4); 3068c2ecf20Sopenharmony_ci 3078c2ecf20Sopenharmony_ci if (ret != 0) 3088c2ecf20Sopenharmony_ci break; 3098c2ecf20Sopenharmony_ci 3108c2ecf20Sopenharmony_ci if (gc_readl(priv, REG_GCRBEXHR) == rbexhr && 3118c2ecf20Sopenharmony_ci priv->shared->num_interrupts == num) { 3128c2ecf20Sopenharmony_ci QERROR("TIMEOUT"); 3138c2ecf20Sopenharmony_ci ret = -ETIMEDOUT; 3148c2ecf20Sopenharmony_ci break; 3158c2ecf20Sopenharmony_ci } 3168c2ecf20Sopenharmony_ci } 3178c2ecf20Sopenharmony_ci 3188c2ecf20Sopenharmony_ci QDUMP("done"); 3198c2ecf20Sopenharmony_ci 3208c2ecf20Sopenharmony_ci return ret; 3218c2ecf20Sopenharmony_ci} 3228c2ecf20Sopenharmony_ci 3238c2ecf20Sopenharmony_cistatic int 3248c2ecf20Sopenharmony_cipxa3xx_gcu_wait_free(struct pxa3xx_gcu_priv *priv) 3258c2ecf20Sopenharmony_ci{ 3268c2ecf20Sopenharmony_ci int ret = 0; 3278c2ecf20Sopenharmony_ci 3288c2ecf20Sopenharmony_ci QDUMP("Waiting for free..."); 3298c2ecf20Sopenharmony_ci 3308c2ecf20Sopenharmony_ci /* Does not need to be atomic. There's a lock in user space, 3318c2ecf20Sopenharmony_ci * but anyhow, this is just for statistics. */ 3328c2ecf20Sopenharmony_ci priv->shared->num_wait_free++; 3338c2ecf20Sopenharmony_ci 3348c2ecf20Sopenharmony_ci while (!priv->free) { 3358c2ecf20Sopenharmony_ci u32 rbexhr = gc_readl(priv, REG_GCRBEXHR); 3368c2ecf20Sopenharmony_ci 3378c2ecf20Sopenharmony_ci ret = wait_event_interruptible_timeout(priv->wait_free, 3388c2ecf20Sopenharmony_ci priv->free, HZ*4); 3398c2ecf20Sopenharmony_ci 3408c2ecf20Sopenharmony_ci if (ret < 0) 3418c2ecf20Sopenharmony_ci break; 3428c2ecf20Sopenharmony_ci 3438c2ecf20Sopenharmony_ci if (ret > 0) 3448c2ecf20Sopenharmony_ci continue; 3458c2ecf20Sopenharmony_ci 3468c2ecf20Sopenharmony_ci if (gc_readl(priv, REG_GCRBEXHR) == rbexhr) { 3478c2ecf20Sopenharmony_ci QERROR("TIMEOUT"); 3488c2ecf20Sopenharmony_ci ret = -ETIMEDOUT; 3498c2ecf20Sopenharmony_ci break; 3508c2ecf20Sopenharmony_ci } 3518c2ecf20Sopenharmony_ci } 3528c2ecf20Sopenharmony_ci 3538c2ecf20Sopenharmony_ci QDUMP("done"); 3548c2ecf20Sopenharmony_ci 3558c2ecf20Sopenharmony_ci return ret; 3568c2ecf20Sopenharmony_ci} 3578c2ecf20Sopenharmony_ci 3588c2ecf20Sopenharmony_ci/* Misc device layer */ 3598c2ecf20Sopenharmony_ci 3608c2ecf20Sopenharmony_cistatic inline struct pxa3xx_gcu_priv *to_pxa3xx_gcu_priv(struct file *file) 3618c2ecf20Sopenharmony_ci{ 3628c2ecf20Sopenharmony_ci struct miscdevice *dev = file->private_data; 3638c2ecf20Sopenharmony_ci return container_of(dev, struct pxa3xx_gcu_priv, misc_dev); 3648c2ecf20Sopenharmony_ci} 3658c2ecf20Sopenharmony_ci 3668c2ecf20Sopenharmony_ci/* 3678c2ecf20Sopenharmony_ci * provide an empty .open callback, so the core sets file->private_data 3688c2ecf20Sopenharmony_ci * for us. 3698c2ecf20Sopenharmony_ci */ 3708c2ecf20Sopenharmony_cistatic int pxa3xx_gcu_open(struct inode *inode, struct file *file) 3718c2ecf20Sopenharmony_ci{ 3728c2ecf20Sopenharmony_ci return 0; 3738c2ecf20Sopenharmony_ci} 3748c2ecf20Sopenharmony_ci 3758c2ecf20Sopenharmony_cistatic ssize_t 3768c2ecf20Sopenharmony_cipxa3xx_gcu_write(struct file *file, const char *buff, 3778c2ecf20Sopenharmony_ci size_t count, loff_t *offp) 3788c2ecf20Sopenharmony_ci{ 3798c2ecf20Sopenharmony_ci int ret; 3808c2ecf20Sopenharmony_ci unsigned long flags; 3818c2ecf20Sopenharmony_ci struct pxa3xx_gcu_batch *buffer; 3828c2ecf20Sopenharmony_ci struct pxa3xx_gcu_priv *priv = to_pxa3xx_gcu_priv(file); 3838c2ecf20Sopenharmony_ci 3848c2ecf20Sopenharmony_ci size_t words = count / 4; 3858c2ecf20Sopenharmony_ci 3868c2ecf20Sopenharmony_ci /* Does not need to be atomic. There's a lock in user space, 3878c2ecf20Sopenharmony_ci * but anyhow, this is just for statistics. */ 3888c2ecf20Sopenharmony_ci priv->shared->num_writes++; 3898c2ecf20Sopenharmony_ci priv->shared->num_words += words; 3908c2ecf20Sopenharmony_ci 3918c2ecf20Sopenharmony_ci /* Last word reserved for batch buffer end command */ 3928c2ecf20Sopenharmony_ci if (words >= PXA3XX_GCU_BATCH_WORDS) 3938c2ecf20Sopenharmony_ci return -E2BIG; 3948c2ecf20Sopenharmony_ci 3958c2ecf20Sopenharmony_ci /* Wait for a free buffer */ 3968c2ecf20Sopenharmony_ci if (!priv->free) { 3978c2ecf20Sopenharmony_ci ret = pxa3xx_gcu_wait_free(priv); 3988c2ecf20Sopenharmony_ci if (ret < 0) 3998c2ecf20Sopenharmony_ci return ret; 4008c2ecf20Sopenharmony_ci } 4018c2ecf20Sopenharmony_ci 4028c2ecf20Sopenharmony_ci /* 4038c2ecf20Sopenharmony_ci * Get buffer from free list 4048c2ecf20Sopenharmony_ci */ 4058c2ecf20Sopenharmony_ci spin_lock_irqsave(&priv->spinlock, flags); 4068c2ecf20Sopenharmony_ci buffer = priv->free; 4078c2ecf20Sopenharmony_ci priv->free = buffer->next; 4088c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&priv->spinlock, flags); 4098c2ecf20Sopenharmony_ci 4108c2ecf20Sopenharmony_ci 4118c2ecf20Sopenharmony_ci /* Copy data from user into buffer */ 4128c2ecf20Sopenharmony_ci ret = copy_from_user(buffer->ptr, buff, words * 4); 4138c2ecf20Sopenharmony_ci if (ret) { 4148c2ecf20Sopenharmony_ci spin_lock_irqsave(&priv->spinlock, flags); 4158c2ecf20Sopenharmony_ci buffer->next = priv->free; 4168c2ecf20Sopenharmony_ci priv->free = buffer; 4178c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&priv->spinlock, flags); 4188c2ecf20Sopenharmony_ci return -EFAULT; 4198c2ecf20Sopenharmony_ci } 4208c2ecf20Sopenharmony_ci 4218c2ecf20Sopenharmony_ci buffer->length = words; 4228c2ecf20Sopenharmony_ci 4238c2ecf20Sopenharmony_ci /* Append batch buffer end command */ 4248c2ecf20Sopenharmony_ci buffer->ptr[words] = 0x01000000; 4258c2ecf20Sopenharmony_ci 4268c2ecf20Sopenharmony_ci /* 4278c2ecf20Sopenharmony_ci * Add buffer to ready list 4288c2ecf20Sopenharmony_ci */ 4298c2ecf20Sopenharmony_ci spin_lock_irqsave(&priv->spinlock, flags); 4308c2ecf20Sopenharmony_ci 4318c2ecf20Sopenharmony_ci buffer->next = NULL; 4328c2ecf20Sopenharmony_ci 4338c2ecf20Sopenharmony_ci if (priv->ready) { 4348c2ecf20Sopenharmony_ci BUG_ON(priv->ready_last == NULL); 4358c2ecf20Sopenharmony_ci 4368c2ecf20Sopenharmony_ci priv->ready_last->next = buffer; 4378c2ecf20Sopenharmony_ci } else 4388c2ecf20Sopenharmony_ci priv->ready = buffer; 4398c2ecf20Sopenharmony_ci 4408c2ecf20Sopenharmony_ci priv->ready_last = buffer; 4418c2ecf20Sopenharmony_ci 4428c2ecf20Sopenharmony_ci if (!priv->shared->hw_running) 4438c2ecf20Sopenharmony_ci run_ready(priv); 4448c2ecf20Sopenharmony_ci 4458c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&priv->spinlock, flags); 4468c2ecf20Sopenharmony_ci 4478c2ecf20Sopenharmony_ci return words * 4; 4488c2ecf20Sopenharmony_ci} 4498c2ecf20Sopenharmony_ci 4508c2ecf20Sopenharmony_ci 4518c2ecf20Sopenharmony_cistatic long 4528c2ecf20Sopenharmony_cipxa3xx_gcu_ioctl(struct file *file, unsigned int cmd, unsigned long arg) 4538c2ecf20Sopenharmony_ci{ 4548c2ecf20Sopenharmony_ci unsigned long flags; 4558c2ecf20Sopenharmony_ci struct pxa3xx_gcu_priv *priv = to_pxa3xx_gcu_priv(file); 4568c2ecf20Sopenharmony_ci 4578c2ecf20Sopenharmony_ci switch (cmd) { 4588c2ecf20Sopenharmony_ci case PXA3XX_GCU_IOCTL_RESET: 4598c2ecf20Sopenharmony_ci spin_lock_irqsave(&priv->spinlock, flags); 4608c2ecf20Sopenharmony_ci pxa3xx_gcu_reset(priv); 4618c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&priv->spinlock, flags); 4628c2ecf20Sopenharmony_ci return 0; 4638c2ecf20Sopenharmony_ci 4648c2ecf20Sopenharmony_ci case PXA3XX_GCU_IOCTL_WAIT_IDLE: 4658c2ecf20Sopenharmony_ci return pxa3xx_gcu_wait_idle(priv); 4668c2ecf20Sopenharmony_ci } 4678c2ecf20Sopenharmony_ci 4688c2ecf20Sopenharmony_ci return -ENOSYS; 4698c2ecf20Sopenharmony_ci} 4708c2ecf20Sopenharmony_ci 4718c2ecf20Sopenharmony_cistatic int 4728c2ecf20Sopenharmony_cipxa3xx_gcu_mmap(struct file *file, struct vm_area_struct *vma) 4738c2ecf20Sopenharmony_ci{ 4748c2ecf20Sopenharmony_ci unsigned int size = vma->vm_end - vma->vm_start; 4758c2ecf20Sopenharmony_ci struct pxa3xx_gcu_priv *priv = to_pxa3xx_gcu_priv(file); 4768c2ecf20Sopenharmony_ci 4778c2ecf20Sopenharmony_ci switch (vma->vm_pgoff) { 4788c2ecf20Sopenharmony_ci case 0: 4798c2ecf20Sopenharmony_ci /* hand out the shared data area */ 4808c2ecf20Sopenharmony_ci if (size != SHARED_SIZE) 4818c2ecf20Sopenharmony_ci return -EINVAL; 4828c2ecf20Sopenharmony_ci 4838c2ecf20Sopenharmony_ci return dma_mmap_coherent(priv->dev, vma, 4848c2ecf20Sopenharmony_ci priv->shared, priv->shared_phys, size); 4858c2ecf20Sopenharmony_ci 4868c2ecf20Sopenharmony_ci case SHARED_SIZE >> PAGE_SHIFT: 4878c2ecf20Sopenharmony_ci /* hand out the MMIO base for direct register access 4888c2ecf20Sopenharmony_ci * from userspace */ 4898c2ecf20Sopenharmony_ci if (size != resource_size(priv->resource_mem)) 4908c2ecf20Sopenharmony_ci return -EINVAL; 4918c2ecf20Sopenharmony_ci 4928c2ecf20Sopenharmony_ci vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); 4938c2ecf20Sopenharmony_ci 4948c2ecf20Sopenharmony_ci return io_remap_pfn_range(vma, vma->vm_start, 4958c2ecf20Sopenharmony_ci priv->resource_mem->start >> PAGE_SHIFT, 4968c2ecf20Sopenharmony_ci size, vma->vm_page_prot); 4978c2ecf20Sopenharmony_ci } 4988c2ecf20Sopenharmony_ci 4998c2ecf20Sopenharmony_ci return -EINVAL; 5008c2ecf20Sopenharmony_ci} 5018c2ecf20Sopenharmony_ci 5028c2ecf20Sopenharmony_ci 5038c2ecf20Sopenharmony_ci#ifdef PXA3XX_GCU_DEBUG_TIMER 5048c2ecf20Sopenharmony_cistatic struct timer_list pxa3xx_gcu_debug_timer; 5058c2ecf20Sopenharmony_cistatic struct pxa3xx_gcu_priv *debug_timer_priv; 5068c2ecf20Sopenharmony_ci 5078c2ecf20Sopenharmony_cistatic void pxa3xx_gcu_debug_timedout(struct timer_list *unused) 5088c2ecf20Sopenharmony_ci{ 5098c2ecf20Sopenharmony_ci struct pxa3xx_gcu_priv *priv = debug_timer_priv; 5108c2ecf20Sopenharmony_ci 5118c2ecf20Sopenharmony_ci QERROR("Timer DUMP"); 5128c2ecf20Sopenharmony_ci 5138c2ecf20Sopenharmony_ci mod_timer(&pxa3xx_gcu_debug_timer, jiffies + 5 * HZ); 5148c2ecf20Sopenharmony_ci} 5158c2ecf20Sopenharmony_ci 5168c2ecf20Sopenharmony_cistatic void pxa3xx_gcu_init_debug_timer(struct pxa3xx_gcu_priv *priv) 5178c2ecf20Sopenharmony_ci{ 5188c2ecf20Sopenharmony_ci /* init the timer structure */ 5198c2ecf20Sopenharmony_ci debug_timer_priv = priv; 5208c2ecf20Sopenharmony_ci timer_setup(&pxa3xx_gcu_debug_timer, pxa3xx_gcu_debug_timedout, 0); 5218c2ecf20Sopenharmony_ci pxa3xx_gcu_debug_timedout(NULL); 5228c2ecf20Sopenharmony_ci} 5238c2ecf20Sopenharmony_ci#else 5248c2ecf20Sopenharmony_cistatic inline void pxa3xx_gcu_init_debug_timer(struct pxa3xx_gcu_priv *priv) {} 5258c2ecf20Sopenharmony_ci#endif 5268c2ecf20Sopenharmony_ci 5278c2ecf20Sopenharmony_cistatic int 5288c2ecf20Sopenharmony_cipxa3xx_gcu_add_buffer(struct device *dev, 5298c2ecf20Sopenharmony_ci struct pxa3xx_gcu_priv *priv) 5308c2ecf20Sopenharmony_ci{ 5318c2ecf20Sopenharmony_ci struct pxa3xx_gcu_batch *buffer; 5328c2ecf20Sopenharmony_ci 5338c2ecf20Sopenharmony_ci buffer = kzalloc(sizeof(struct pxa3xx_gcu_batch), GFP_KERNEL); 5348c2ecf20Sopenharmony_ci if (!buffer) 5358c2ecf20Sopenharmony_ci return -ENOMEM; 5368c2ecf20Sopenharmony_ci 5378c2ecf20Sopenharmony_ci buffer->ptr = dma_alloc_coherent(dev, PXA3XX_GCU_BATCH_WORDS * 4, 5388c2ecf20Sopenharmony_ci &buffer->phys, GFP_KERNEL); 5398c2ecf20Sopenharmony_ci if (!buffer->ptr) { 5408c2ecf20Sopenharmony_ci kfree(buffer); 5418c2ecf20Sopenharmony_ci return -ENOMEM; 5428c2ecf20Sopenharmony_ci } 5438c2ecf20Sopenharmony_ci 5448c2ecf20Sopenharmony_ci buffer->next = priv->free; 5458c2ecf20Sopenharmony_ci priv->free = buffer; 5468c2ecf20Sopenharmony_ci 5478c2ecf20Sopenharmony_ci return 0; 5488c2ecf20Sopenharmony_ci} 5498c2ecf20Sopenharmony_ci 5508c2ecf20Sopenharmony_cistatic void 5518c2ecf20Sopenharmony_cipxa3xx_gcu_free_buffers(struct device *dev, 5528c2ecf20Sopenharmony_ci struct pxa3xx_gcu_priv *priv) 5538c2ecf20Sopenharmony_ci{ 5548c2ecf20Sopenharmony_ci struct pxa3xx_gcu_batch *next, *buffer = priv->free; 5558c2ecf20Sopenharmony_ci 5568c2ecf20Sopenharmony_ci while (buffer) { 5578c2ecf20Sopenharmony_ci next = buffer->next; 5588c2ecf20Sopenharmony_ci 5598c2ecf20Sopenharmony_ci dma_free_coherent(dev, PXA3XX_GCU_BATCH_WORDS * 4, 5608c2ecf20Sopenharmony_ci buffer->ptr, buffer->phys); 5618c2ecf20Sopenharmony_ci 5628c2ecf20Sopenharmony_ci kfree(buffer); 5638c2ecf20Sopenharmony_ci buffer = next; 5648c2ecf20Sopenharmony_ci } 5658c2ecf20Sopenharmony_ci 5668c2ecf20Sopenharmony_ci priv->free = NULL; 5678c2ecf20Sopenharmony_ci} 5688c2ecf20Sopenharmony_ci 5698c2ecf20Sopenharmony_cistatic const struct file_operations pxa3xx_gcu_miscdev_fops = { 5708c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 5718c2ecf20Sopenharmony_ci .open = pxa3xx_gcu_open, 5728c2ecf20Sopenharmony_ci .write = pxa3xx_gcu_write, 5738c2ecf20Sopenharmony_ci .unlocked_ioctl = pxa3xx_gcu_ioctl, 5748c2ecf20Sopenharmony_ci .mmap = pxa3xx_gcu_mmap, 5758c2ecf20Sopenharmony_ci}; 5768c2ecf20Sopenharmony_ci 5778c2ecf20Sopenharmony_cistatic int pxa3xx_gcu_probe(struct platform_device *pdev) 5788c2ecf20Sopenharmony_ci{ 5798c2ecf20Sopenharmony_ci int i, ret, irq; 5808c2ecf20Sopenharmony_ci struct resource *r; 5818c2ecf20Sopenharmony_ci struct pxa3xx_gcu_priv *priv; 5828c2ecf20Sopenharmony_ci struct device *dev = &pdev->dev; 5838c2ecf20Sopenharmony_ci 5848c2ecf20Sopenharmony_ci priv = devm_kzalloc(dev, sizeof(struct pxa3xx_gcu_priv), GFP_KERNEL); 5858c2ecf20Sopenharmony_ci if (!priv) 5868c2ecf20Sopenharmony_ci return -ENOMEM; 5878c2ecf20Sopenharmony_ci 5888c2ecf20Sopenharmony_ci init_waitqueue_head(&priv->wait_idle); 5898c2ecf20Sopenharmony_ci init_waitqueue_head(&priv->wait_free); 5908c2ecf20Sopenharmony_ci spin_lock_init(&priv->spinlock); 5918c2ecf20Sopenharmony_ci 5928c2ecf20Sopenharmony_ci /* we allocate the misc device structure as part of our own allocation, 5938c2ecf20Sopenharmony_ci * so we can get a pointer to our priv structure later on with 5948c2ecf20Sopenharmony_ci * container_of(). This isn't really necessary as we have a fixed minor 5958c2ecf20Sopenharmony_ci * number anyway, but this is to avoid statics. */ 5968c2ecf20Sopenharmony_ci 5978c2ecf20Sopenharmony_ci priv->misc_dev.minor = PXA3XX_GCU_MINOR, 5988c2ecf20Sopenharmony_ci priv->misc_dev.name = DRV_NAME, 5998c2ecf20Sopenharmony_ci priv->misc_dev.fops = &pxa3xx_gcu_miscdev_fops; 6008c2ecf20Sopenharmony_ci 6018c2ecf20Sopenharmony_ci /* handle IO resources */ 6028c2ecf20Sopenharmony_ci r = platform_get_resource(pdev, IORESOURCE_MEM, 0); 6038c2ecf20Sopenharmony_ci priv->mmio_base = devm_ioremap_resource(dev, r); 6048c2ecf20Sopenharmony_ci if (IS_ERR(priv->mmio_base)) 6058c2ecf20Sopenharmony_ci return PTR_ERR(priv->mmio_base); 6068c2ecf20Sopenharmony_ci 6078c2ecf20Sopenharmony_ci /* enable the clock */ 6088c2ecf20Sopenharmony_ci priv->clk = devm_clk_get(dev, NULL); 6098c2ecf20Sopenharmony_ci if (IS_ERR(priv->clk)) { 6108c2ecf20Sopenharmony_ci dev_err(dev, "failed to get clock\n"); 6118c2ecf20Sopenharmony_ci return PTR_ERR(priv->clk); 6128c2ecf20Sopenharmony_ci } 6138c2ecf20Sopenharmony_ci 6148c2ecf20Sopenharmony_ci /* request the IRQ */ 6158c2ecf20Sopenharmony_ci irq = platform_get_irq(pdev, 0); 6168c2ecf20Sopenharmony_ci if (irq < 0) { 6178c2ecf20Sopenharmony_ci dev_err(dev, "no IRQ defined: %d\n", irq); 6188c2ecf20Sopenharmony_ci return irq; 6198c2ecf20Sopenharmony_ci } 6208c2ecf20Sopenharmony_ci 6218c2ecf20Sopenharmony_ci ret = devm_request_irq(dev, irq, pxa3xx_gcu_handle_irq, 6228c2ecf20Sopenharmony_ci 0, DRV_NAME, priv); 6238c2ecf20Sopenharmony_ci if (ret < 0) { 6248c2ecf20Sopenharmony_ci dev_err(dev, "request_irq failed\n"); 6258c2ecf20Sopenharmony_ci return ret; 6268c2ecf20Sopenharmony_ci } 6278c2ecf20Sopenharmony_ci 6288c2ecf20Sopenharmony_ci /* allocate dma memory */ 6298c2ecf20Sopenharmony_ci priv->shared = dma_alloc_coherent(dev, SHARED_SIZE, 6308c2ecf20Sopenharmony_ci &priv->shared_phys, GFP_KERNEL); 6318c2ecf20Sopenharmony_ci if (!priv->shared) { 6328c2ecf20Sopenharmony_ci dev_err(dev, "failed to allocate DMA memory\n"); 6338c2ecf20Sopenharmony_ci return -ENOMEM; 6348c2ecf20Sopenharmony_ci } 6358c2ecf20Sopenharmony_ci 6368c2ecf20Sopenharmony_ci /* register misc device */ 6378c2ecf20Sopenharmony_ci ret = misc_register(&priv->misc_dev); 6388c2ecf20Sopenharmony_ci if (ret < 0) { 6398c2ecf20Sopenharmony_ci dev_err(dev, "misc_register() for minor %d failed\n", 6408c2ecf20Sopenharmony_ci PXA3XX_GCU_MINOR); 6418c2ecf20Sopenharmony_ci goto err_free_dma; 6428c2ecf20Sopenharmony_ci } 6438c2ecf20Sopenharmony_ci 6448c2ecf20Sopenharmony_ci ret = clk_prepare_enable(priv->clk); 6458c2ecf20Sopenharmony_ci if (ret < 0) { 6468c2ecf20Sopenharmony_ci dev_err(dev, "failed to enable clock\n"); 6478c2ecf20Sopenharmony_ci goto err_misc_deregister; 6488c2ecf20Sopenharmony_ci } 6498c2ecf20Sopenharmony_ci 6508c2ecf20Sopenharmony_ci for (i = 0; i < 8; i++) { 6518c2ecf20Sopenharmony_ci ret = pxa3xx_gcu_add_buffer(dev, priv); 6528c2ecf20Sopenharmony_ci if (ret) { 6538c2ecf20Sopenharmony_ci pxa3xx_gcu_free_buffers(dev, priv); 6548c2ecf20Sopenharmony_ci dev_err(dev, "failed to allocate DMA memory\n"); 6558c2ecf20Sopenharmony_ci goto err_disable_clk; 6568c2ecf20Sopenharmony_ci } 6578c2ecf20Sopenharmony_ci } 6588c2ecf20Sopenharmony_ci 6598c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, priv); 6608c2ecf20Sopenharmony_ci priv->resource_mem = r; 6618c2ecf20Sopenharmony_ci priv->dev = dev; 6628c2ecf20Sopenharmony_ci pxa3xx_gcu_reset(priv); 6638c2ecf20Sopenharmony_ci pxa3xx_gcu_init_debug_timer(priv); 6648c2ecf20Sopenharmony_ci 6658c2ecf20Sopenharmony_ci dev_info(dev, "registered @0x%p, DMA 0x%p (%d bytes), IRQ %d\n", 6668c2ecf20Sopenharmony_ci (void *) r->start, (void *) priv->shared_phys, 6678c2ecf20Sopenharmony_ci SHARED_SIZE, irq); 6688c2ecf20Sopenharmony_ci return 0; 6698c2ecf20Sopenharmony_ci 6708c2ecf20Sopenharmony_cierr_disable_clk: 6718c2ecf20Sopenharmony_ci clk_disable_unprepare(priv->clk); 6728c2ecf20Sopenharmony_ci 6738c2ecf20Sopenharmony_cierr_misc_deregister: 6748c2ecf20Sopenharmony_ci misc_deregister(&priv->misc_dev); 6758c2ecf20Sopenharmony_ci 6768c2ecf20Sopenharmony_cierr_free_dma: 6778c2ecf20Sopenharmony_ci dma_free_coherent(dev, SHARED_SIZE, 6788c2ecf20Sopenharmony_ci priv->shared, priv->shared_phys); 6798c2ecf20Sopenharmony_ci 6808c2ecf20Sopenharmony_ci return ret; 6818c2ecf20Sopenharmony_ci} 6828c2ecf20Sopenharmony_ci 6838c2ecf20Sopenharmony_cistatic int pxa3xx_gcu_remove(struct platform_device *pdev) 6848c2ecf20Sopenharmony_ci{ 6858c2ecf20Sopenharmony_ci struct pxa3xx_gcu_priv *priv = platform_get_drvdata(pdev); 6868c2ecf20Sopenharmony_ci struct device *dev = &pdev->dev; 6878c2ecf20Sopenharmony_ci 6888c2ecf20Sopenharmony_ci pxa3xx_gcu_wait_idle(priv); 6898c2ecf20Sopenharmony_ci misc_deregister(&priv->misc_dev); 6908c2ecf20Sopenharmony_ci dma_free_coherent(dev, SHARED_SIZE, priv->shared, priv->shared_phys); 6918c2ecf20Sopenharmony_ci clk_disable_unprepare(priv->clk); 6928c2ecf20Sopenharmony_ci pxa3xx_gcu_free_buffers(dev, priv); 6938c2ecf20Sopenharmony_ci 6948c2ecf20Sopenharmony_ci return 0; 6958c2ecf20Sopenharmony_ci} 6968c2ecf20Sopenharmony_ci 6978c2ecf20Sopenharmony_ci#ifdef CONFIG_OF 6988c2ecf20Sopenharmony_cistatic const struct of_device_id pxa3xx_gcu_of_match[] = { 6998c2ecf20Sopenharmony_ci { .compatible = "marvell,pxa300-gcu", }, 7008c2ecf20Sopenharmony_ci { } 7018c2ecf20Sopenharmony_ci}; 7028c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, pxa3xx_gcu_of_match); 7038c2ecf20Sopenharmony_ci#endif 7048c2ecf20Sopenharmony_ci 7058c2ecf20Sopenharmony_cistatic struct platform_driver pxa3xx_gcu_driver = { 7068c2ecf20Sopenharmony_ci .probe = pxa3xx_gcu_probe, 7078c2ecf20Sopenharmony_ci .remove = pxa3xx_gcu_remove, 7088c2ecf20Sopenharmony_ci .driver = { 7098c2ecf20Sopenharmony_ci .name = DRV_NAME, 7108c2ecf20Sopenharmony_ci .of_match_table = of_match_ptr(pxa3xx_gcu_of_match), 7118c2ecf20Sopenharmony_ci }, 7128c2ecf20Sopenharmony_ci}; 7138c2ecf20Sopenharmony_ci 7148c2ecf20Sopenharmony_cimodule_platform_driver(pxa3xx_gcu_driver); 7158c2ecf20Sopenharmony_ci 7168c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("PXA3xx graphics controller unit driver"); 7178c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 7188c2ecf20Sopenharmony_ciMODULE_ALIAS_MISCDEV(PXA3XX_GCU_MINOR); 7198c2ecf20Sopenharmony_ciMODULE_AUTHOR("Janine Kropp <nin@directfb.org>, " 7208c2ecf20Sopenharmony_ci "Denis Oliver Kropp <dok@directfb.org>, " 7218c2ecf20Sopenharmony_ci "Daniel Mack <daniel@caiaq.de>"); 722