174ba9207SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
2364dbdf3SDaniel Mack /*
30b7f1cc7Saxel lin * pxa3xx-gcu.c - Linux kernel module for PXA3xx graphics controllers
4364dbdf3SDaniel Mack *
5364dbdf3SDaniel Mack * This driver needs a DirectFB counterpart in user space, communication
6364dbdf3SDaniel Mack * is handled via mmap()ed memory areas and an ioctl.
7364dbdf3SDaniel Mack *
8364dbdf3SDaniel Mack * Copyright (c) 2009 Daniel Mack <daniel@caiaq.de>
9364dbdf3SDaniel Mack * Copyright (c) 2009 Janine Kropp <nin@directfb.org>
10364dbdf3SDaniel Mack * Copyright (c) 2009 Denis Oliver Kropp <dok@directfb.org>
11364dbdf3SDaniel Mack */
12364dbdf3SDaniel Mack
13364dbdf3SDaniel Mack /*
14364dbdf3SDaniel Mack * WARNING: This controller is attached to System Bus 2 of the PXA which
1525985edcSLucas De Marchi * needs its arbiter to be enabled explicitly (CKENB & 1<<9).
16364dbdf3SDaniel Mack * There is currently no way to do this from Linux, so you need to teach
17364dbdf3SDaniel Mack * your bootloader for now.
18364dbdf3SDaniel Mack */
19364dbdf3SDaniel Mack
20364dbdf3SDaniel Mack #include <linux/module.h>
21364dbdf3SDaniel Mack #include <linux/platform_device.h>
22364dbdf3SDaniel Mack #include <linux/dma-mapping.h>
23364dbdf3SDaniel Mack #include <linux/miscdevice.h>
24364dbdf3SDaniel Mack #include <linux/interrupt.h>
25364dbdf3SDaniel Mack #include <linux/spinlock.h>
26364dbdf3SDaniel Mack #include <linux/uaccess.h>
27364dbdf3SDaniel Mack #include <linux/ioctl.h>
28364dbdf3SDaniel Mack #include <linux/delay.h>
29364dbdf3SDaniel Mack #include <linux/sched.h>
30364dbdf3SDaniel Mack #include <linux/slab.h>
31364dbdf3SDaniel Mack #include <linux/clk.h>
32364dbdf3SDaniel Mack #include <linux/fs.h>
33364dbdf3SDaniel Mack #include <linux/io.h>
34aa45ee8eSDaniel Mack #include <linux/of.h>
35364dbdf3SDaniel Mack
36364dbdf3SDaniel Mack #include "pxa3xx-gcu.h"
37364dbdf3SDaniel Mack
38364dbdf3SDaniel Mack #define DRV_NAME "pxa3xx-gcu"
39364dbdf3SDaniel Mack
40364dbdf3SDaniel Mack #define REG_GCCR 0x00
41364dbdf3SDaniel Mack #define GCCR_SYNC_CLR (1 << 9)
42364dbdf3SDaniel Mack #define GCCR_BP_RST (1 << 8)
43364dbdf3SDaniel Mack #define GCCR_ABORT (1 << 6)
44364dbdf3SDaniel Mack #define GCCR_STOP (1 << 4)
45364dbdf3SDaniel Mack
46364dbdf3SDaniel Mack #define REG_GCISCR 0x04
47364dbdf3SDaniel Mack #define REG_GCIECR 0x08
48364dbdf3SDaniel Mack #define REG_GCRBBR 0x20
49364dbdf3SDaniel Mack #define REG_GCRBLR 0x24
50364dbdf3SDaniel Mack #define REG_GCRBHR 0x28
51364dbdf3SDaniel Mack #define REG_GCRBTR 0x2C
52364dbdf3SDaniel Mack #define REG_GCRBEXHR 0x30
53364dbdf3SDaniel Mack
54364dbdf3SDaniel Mack #define IE_EOB (1 << 0)
55364dbdf3SDaniel Mack #define IE_EEOB (1 << 5)
56364dbdf3SDaniel Mack #define IE_ALL 0xff
57364dbdf3SDaniel Mack
58364dbdf3SDaniel Mack #define SHARED_SIZE PAGE_ALIGN(sizeof(struct pxa3xx_gcu_shared))
59364dbdf3SDaniel Mack
60364dbdf3SDaniel Mack /* #define PXA3XX_GCU_DEBUG */
61364dbdf3SDaniel Mack /* #define PXA3XX_GCU_DEBUG_TIMER */
62364dbdf3SDaniel Mack
63364dbdf3SDaniel Mack #ifdef PXA3XX_GCU_DEBUG
64364dbdf3SDaniel Mack #define QDUMP(msg) \
65364dbdf3SDaniel Mack do { \
66364dbdf3SDaniel Mack QPRINT(priv, KERN_DEBUG, msg); \
67364dbdf3SDaniel Mack } while (0)
68364dbdf3SDaniel Mack #else
69364dbdf3SDaniel Mack #define QDUMP(msg) do {} while (0)
70364dbdf3SDaniel Mack #endif
71364dbdf3SDaniel Mack
72364dbdf3SDaniel Mack #define QERROR(msg) \
73364dbdf3SDaniel Mack do { \
74364dbdf3SDaniel Mack QPRINT(priv, KERN_ERR, msg); \
75364dbdf3SDaniel Mack } while (0)
76364dbdf3SDaniel Mack
77364dbdf3SDaniel Mack struct pxa3xx_gcu_batch {
78364dbdf3SDaniel Mack struct pxa3xx_gcu_batch *next;
79364dbdf3SDaniel Mack u32 *ptr;
80364dbdf3SDaniel Mack dma_addr_t phys;
81364dbdf3SDaniel Mack unsigned long length;
82364dbdf3SDaniel Mack };
83364dbdf3SDaniel Mack
84364dbdf3SDaniel Mack struct pxa3xx_gcu_priv {
8502c486f4SChristoph Hellwig struct device *dev;
86364dbdf3SDaniel Mack void __iomem *mmio_base;
87364dbdf3SDaniel Mack struct clk *clk;
88364dbdf3SDaniel Mack struct pxa3xx_gcu_shared *shared;
89364dbdf3SDaniel Mack dma_addr_t shared_phys;
90364dbdf3SDaniel Mack struct resource *resource_mem;
91364dbdf3SDaniel Mack struct miscdevice misc_dev;
92364dbdf3SDaniel Mack wait_queue_head_t wait_idle;
93364dbdf3SDaniel Mack wait_queue_head_t wait_free;
94364dbdf3SDaniel Mack spinlock_t spinlock;
95f7a75354SArnd Bergmann struct timespec64 base_time;
96364dbdf3SDaniel Mack
97364dbdf3SDaniel Mack struct pxa3xx_gcu_batch *free;
98364dbdf3SDaniel Mack struct pxa3xx_gcu_batch *ready;
99364dbdf3SDaniel Mack struct pxa3xx_gcu_batch *ready_last;
100364dbdf3SDaniel Mack struct pxa3xx_gcu_batch *running;
101364dbdf3SDaniel Mack };
102364dbdf3SDaniel Mack
103364dbdf3SDaniel Mack static inline unsigned long
gc_readl(struct pxa3xx_gcu_priv * priv,unsigned int off)104364dbdf3SDaniel Mack gc_readl(struct pxa3xx_gcu_priv *priv, unsigned int off)
105364dbdf3SDaniel Mack {
106364dbdf3SDaniel Mack return __raw_readl(priv->mmio_base + off);
107364dbdf3SDaniel Mack }
108364dbdf3SDaniel Mack
109364dbdf3SDaniel Mack static inline void
gc_writel(struct pxa3xx_gcu_priv * priv,unsigned int off,unsigned long val)110364dbdf3SDaniel Mack gc_writel(struct pxa3xx_gcu_priv *priv, unsigned int off, unsigned long val)
111364dbdf3SDaniel Mack {
112364dbdf3SDaniel Mack __raw_writel(val, priv->mmio_base + off);
113364dbdf3SDaniel Mack }
114364dbdf3SDaniel Mack
115364dbdf3SDaniel Mack #define QPRINT(priv, level, msg) \
116364dbdf3SDaniel Mack do { \
117f7a75354SArnd Bergmann struct timespec64 ts; \
118364dbdf3SDaniel Mack struct pxa3xx_gcu_shared *shared = priv->shared; \
119364dbdf3SDaniel Mack u32 base = gc_readl(priv, REG_GCRBBR); \
120364dbdf3SDaniel Mack \
121f7a75354SArnd Bergmann ktime_get_ts64(&ts); \
122f7a75354SArnd Bergmann ts = timespec64_sub(ts, priv->base_time); \
123364dbdf3SDaniel Mack \
124f7a75354SArnd Bergmann printk(level "%lld.%03ld.%03ld - %-17s: %-21s (%s, " \
125364dbdf3SDaniel Mack "STATUS " \
126364dbdf3SDaniel Mack "0x%02lx, B 0x%08lx [%ld], E %5ld, H %5ld, " \
127364dbdf3SDaniel Mack "T %5ld)\n", \
128f7a75354SArnd Bergmann (s64)(ts.tv_sec), \
129f7a75354SArnd Bergmann ts.tv_nsec / NSEC_PER_MSEC, \
130f7a75354SArnd Bergmann (ts.tv_nsec % NSEC_PER_MSEC) / USEC_PER_MSEC, \
131364dbdf3SDaniel Mack __func__, msg, \
132364dbdf3SDaniel Mack shared->hw_running ? "running" : " idle", \
133364dbdf3SDaniel Mack gc_readl(priv, REG_GCISCR), \
134364dbdf3SDaniel Mack gc_readl(priv, REG_GCRBBR), \
135364dbdf3SDaniel Mack gc_readl(priv, REG_GCRBLR), \
136364dbdf3SDaniel Mack (gc_readl(priv, REG_GCRBEXHR) - base) / 4, \
137364dbdf3SDaniel Mack (gc_readl(priv, REG_GCRBHR) - base) / 4, \
138364dbdf3SDaniel Mack (gc_readl(priv, REG_GCRBTR) - base) / 4); \
139364dbdf3SDaniel Mack } while (0)
140364dbdf3SDaniel Mack
141364dbdf3SDaniel Mack static void
pxa3xx_gcu_reset(struct pxa3xx_gcu_priv * priv)142364dbdf3SDaniel Mack pxa3xx_gcu_reset(struct pxa3xx_gcu_priv *priv)
143364dbdf3SDaniel Mack {
144364dbdf3SDaniel Mack QDUMP("RESET");
145364dbdf3SDaniel Mack
146364dbdf3SDaniel Mack /* disable interrupts */
147364dbdf3SDaniel Mack gc_writel(priv, REG_GCIECR, 0);
148364dbdf3SDaniel Mack
149364dbdf3SDaniel Mack /* reset hardware */
150364dbdf3SDaniel Mack gc_writel(priv, REG_GCCR, GCCR_ABORT);
151364dbdf3SDaniel Mack gc_writel(priv, REG_GCCR, 0);
152364dbdf3SDaniel Mack
153364dbdf3SDaniel Mack memset(priv->shared, 0, SHARED_SIZE);
154364dbdf3SDaniel Mack priv->shared->buffer_phys = priv->shared_phys;
155364dbdf3SDaniel Mack priv->shared->magic = PXA3XX_GCU_SHARED_MAGIC;
156364dbdf3SDaniel Mack
157f7a75354SArnd Bergmann ktime_get_ts64(&priv->base_time);
158364dbdf3SDaniel Mack
159364dbdf3SDaniel Mack /* set up the ring buffer pointers */
160364dbdf3SDaniel Mack gc_writel(priv, REG_GCRBLR, 0);
161364dbdf3SDaniel Mack gc_writel(priv, REG_GCRBBR, priv->shared_phys);
162364dbdf3SDaniel Mack gc_writel(priv, REG_GCRBTR, priv->shared_phys);
163364dbdf3SDaniel Mack
164364dbdf3SDaniel Mack /* enable all IRQs except EOB */
165364dbdf3SDaniel Mack gc_writel(priv, REG_GCIECR, IE_ALL & ~IE_EOB);
166364dbdf3SDaniel Mack }
167364dbdf3SDaniel Mack
168364dbdf3SDaniel Mack static void
dump_whole_state(struct pxa3xx_gcu_priv * priv)169364dbdf3SDaniel Mack dump_whole_state(struct pxa3xx_gcu_priv *priv)
170364dbdf3SDaniel Mack {
171364dbdf3SDaniel Mack struct pxa3xx_gcu_shared *sh = priv->shared;
172364dbdf3SDaniel Mack u32 base = gc_readl(priv, REG_GCRBBR);
173364dbdf3SDaniel Mack
174364dbdf3SDaniel Mack QDUMP("DUMP");
175364dbdf3SDaniel Mack
176364dbdf3SDaniel Mack printk(KERN_DEBUG "== PXA3XX-GCU DUMP ==\n"
177364dbdf3SDaniel Mack "%s, STATUS 0x%02lx, B 0x%08lx [%ld], E %5ld, H %5ld, T %5ld\n",
178364dbdf3SDaniel Mack sh->hw_running ? "running" : "idle ",
179364dbdf3SDaniel Mack gc_readl(priv, REG_GCISCR),
180364dbdf3SDaniel Mack gc_readl(priv, REG_GCRBBR),
181364dbdf3SDaniel Mack gc_readl(priv, REG_GCRBLR),
182364dbdf3SDaniel Mack (gc_readl(priv, REG_GCRBEXHR) - base) / 4,
183364dbdf3SDaniel Mack (gc_readl(priv, REG_GCRBHR) - base) / 4,
184364dbdf3SDaniel Mack (gc_readl(priv, REG_GCRBTR) - base) / 4);
185364dbdf3SDaniel Mack }
186364dbdf3SDaniel Mack
187364dbdf3SDaniel Mack static void
flush_running(struct pxa3xx_gcu_priv * priv)188364dbdf3SDaniel Mack flush_running(struct pxa3xx_gcu_priv *priv)
189364dbdf3SDaniel Mack {
190364dbdf3SDaniel Mack struct pxa3xx_gcu_batch *running = priv->running;
191364dbdf3SDaniel Mack struct pxa3xx_gcu_batch *next;
192364dbdf3SDaniel Mack
193364dbdf3SDaniel Mack while (running) {
194364dbdf3SDaniel Mack next = running->next;
195364dbdf3SDaniel Mack running->next = priv->free;
196364dbdf3SDaniel Mack priv->free = running;
197364dbdf3SDaniel Mack running = next;
198364dbdf3SDaniel Mack }
199364dbdf3SDaniel Mack
200364dbdf3SDaniel Mack priv->running = NULL;
201364dbdf3SDaniel Mack }
202364dbdf3SDaniel Mack
203364dbdf3SDaniel Mack static void
run_ready(struct pxa3xx_gcu_priv * priv)204364dbdf3SDaniel Mack run_ready(struct pxa3xx_gcu_priv *priv)
205364dbdf3SDaniel Mack {
206364dbdf3SDaniel Mack unsigned int num = 0;
207364dbdf3SDaniel Mack struct pxa3xx_gcu_shared *shared = priv->shared;
208364dbdf3SDaniel Mack struct pxa3xx_gcu_batch *ready = priv->ready;
209364dbdf3SDaniel Mack
210364dbdf3SDaniel Mack QDUMP("Start");
211364dbdf3SDaniel Mack
212364dbdf3SDaniel Mack BUG_ON(!ready);
213364dbdf3SDaniel Mack
214364dbdf3SDaniel Mack shared->buffer[num++] = 0x05000000;
215364dbdf3SDaniel Mack
216364dbdf3SDaniel Mack while (ready) {
217364dbdf3SDaniel Mack shared->buffer[num++] = 0x00000001;
218364dbdf3SDaniel Mack shared->buffer[num++] = ready->phys;
219364dbdf3SDaniel Mack ready = ready->next;
220364dbdf3SDaniel Mack }
221364dbdf3SDaniel Mack
222364dbdf3SDaniel Mack shared->buffer[num++] = 0x05000000;
223364dbdf3SDaniel Mack priv->running = priv->ready;
224364dbdf3SDaniel Mack priv->ready = priv->ready_last = NULL;
225364dbdf3SDaniel Mack gc_writel(priv, REG_GCRBLR, 0);
226364dbdf3SDaniel Mack shared->hw_running = 1;
227364dbdf3SDaniel Mack
228364dbdf3SDaniel Mack /* ring base address */
229364dbdf3SDaniel Mack gc_writel(priv, REG_GCRBBR, shared->buffer_phys);
230364dbdf3SDaniel Mack
231364dbdf3SDaniel Mack /* ring tail address */
232364dbdf3SDaniel Mack gc_writel(priv, REG_GCRBTR, shared->buffer_phys + num * 4);
233364dbdf3SDaniel Mack
234364dbdf3SDaniel Mack /* ring length */
235364dbdf3SDaniel Mack gc_writel(priv, REG_GCRBLR, ((num + 63) & ~63) * 4);
236364dbdf3SDaniel Mack }
237364dbdf3SDaniel Mack
238364dbdf3SDaniel Mack static irqreturn_t
pxa3xx_gcu_handle_irq(int irq,void * ctx)239364dbdf3SDaniel Mack pxa3xx_gcu_handle_irq(int irq, void *ctx)
240364dbdf3SDaniel Mack {
241364dbdf3SDaniel Mack struct pxa3xx_gcu_priv *priv = ctx;
242364dbdf3SDaniel Mack struct pxa3xx_gcu_shared *shared = priv->shared;
243364dbdf3SDaniel Mack u32 status = gc_readl(priv, REG_GCISCR) & IE_ALL;
244364dbdf3SDaniel Mack
245364dbdf3SDaniel Mack QDUMP("-Interrupt");
246364dbdf3SDaniel Mack
247364dbdf3SDaniel Mack if (!status)
248364dbdf3SDaniel Mack return IRQ_NONE;
249364dbdf3SDaniel Mack
250364dbdf3SDaniel Mack spin_lock(&priv->spinlock);
251364dbdf3SDaniel Mack shared->num_interrupts++;
252364dbdf3SDaniel Mack
253364dbdf3SDaniel Mack if (status & IE_EEOB) {
254364dbdf3SDaniel Mack QDUMP(" [EEOB]");
255364dbdf3SDaniel Mack
256364dbdf3SDaniel Mack flush_running(priv);
257364dbdf3SDaniel Mack wake_up_all(&priv->wait_free);
258364dbdf3SDaniel Mack
259364dbdf3SDaniel Mack if (priv->ready) {
260364dbdf3SDaniel Mack run_ready(priv);
261364dbdf3SDaniel Mack } else {
262364dbdf3SDaniel Mack /* There is no more data prepared by the userspace.
263364dbdf3SDaniel Mack * Set hw_running = 0 and wait for the next userspace
264364dbdf3SDaniel Mack * kick-off */
265364dbdf3SDaniel Mack shared->num_idle++;
266364dbdf3SDaniel Mack shared->hw_running = 0;
267364dbdf3SDaniel Mack
268364dbdf3SDaniel Mack QDUMP(" '-> Idle.");
269364dbdf3SDaniel Mack
270364dbdf3SDaniel Mack /* set ring buffer length to zero */
271364dbdf3SDaniel Mack gc_writel(priv, REG_GCRBLR, 0);
272364dbdf3SDaniel Mack
273364dbdf3SDaniel Mack wake_up_all(&priv->wait_idle);
274364dbdf3SDaniel Mack }
275364dbdf3SDaniel Mack
276364dbdf3SDaniel Mack shared->num_done++;
277364dbdf3SDaniel Mack } else {
278364dbdf3SDaniel Mack QERROR(" [???]");
279364dbdf3SDaniel Mack dump_whole_state(priv);
280364dbdf3SDaniel Mack }
281364dbdf3SDaniel Mack
282364dbdf3SDaniel Mack /* Clear the interrupt */
283364dbdf3SDaniel Mack gc_writel(priv, REG_GCISCR, status);
284364dbdf3SDaniel Mack spin_unlock(&priv->spinlock);
285364dbdf3SDaniel Mack
286364dbdf3SDaniel Mack return IRQ_HANDLED;
287364dbdf3SDaniel Mack }
288364dbdf3SDaniel Mack
289364dbdf3SDaniel Mack static int
pxa3xx_gcu_wait_idle(struct pxa3xx_gcu_priv * priv)290364dbdf3SDaniel Mack pxa3xx_gcu_wait_idle(struct pxa3xx_gcu_priv *priv)
291364dbdf3SDaniel Mack {
292364dbdf3SDaniel Mack int ret = 0;
293364dbdf3SDaniel Mack
294364dbdf3SDaniel Mack QDUMP("Waiting for idle...");
295364dbdf3SDaniel Mack
296364dbdf3SDaniel Mack /* Does not need to be atomic. There's a lock in user space,
297364dbdf3SDaniel Mack * but anyhow, this is just for statistics. */
298364dbdf3SDaniel Mack priv->shared->num_wait_idle++;
299364dbdf3SDaniel Mack
300364dbdf3SDaniel Mack while (priv->shared->hw_running) {
301364dbdf3SDaniel Mack int num = priv->shared->num_interrupts;
302364dbdf3SDaniel Mack u32 rbexhr = gc_readl(priv, REG_GCRBEXHR);
303364dbdf3SDaniel Mack
304364dbdf3SDaniel Mack ret = wait_event_interruptible_timeout(priv->wait_idle,
305364dbdf3SDaniel Mack !priv->shared->hw_running, HZ*4);
306364dbdf3SDaniel Mack
307688ec344SAxel Lin if (ret != 0)
308364dbdf3SDaniel Mack break;
309364dbdf3SDaniel Mack
310364dbdf3SDaniel Mack if (gc_readl(priv, REG_GCRBEXHR) == rbexhr &&
311364dbdf3SDaniel Mack priv->shared->num_interrupts == num) {
312364dbdf3SDaniel Mack QERROR("TIMEOUT");
313364dbdf3SDaniel Mack ret = -ETIMEDOUT;
314364dbdf3SDaniel Mack break;
315364dbdf3SDaniel Mack }
316364dbdf3SDaniel Mack }
317364dbdf3SDaniel Mack
318364dbdf3SDaniel Mack QDUMP("done");
319364dbdf3SDaniel Mack
320364dbdf3SDaniel Mack return ret;
321364dbdf3SDaniel Mack }
322364dbdf3SDaniel Mack
323364dbdf3SDaniel Mack static int
pxa3xx_gcu_wait_free(struct pxa3xx_gcu_priv * priv)324364dbdf3SDaniel Mack pxa3xx_gcu_wait_free(struct pxa3xx_gcu_priv *priv)
325364dbdf3SDaniel Mack {
326364dbdf3SDaniel Mack int ret = 0;
327364dbdf3SDaniel Mack
328364dbdf3SDaniel Mack QDUMP("Waiting for free...");
329364dbdf3SDaniel Mack
330364dbdf3SDaniel Mack /* Does not need to be atomic. There's a lock in user space,
331364dbdf3SDaniel Mack * but anyhow, this is just for statistics. */
332364dbdf3SDaniel Mack priv->shared->num_wait_free++;
333364dbdf3SDaniel Mack
334364dbdf3SDaniel Mack while (!priv->free) {
335364dbdf3SDaniel Mack u32 rbexhr = gc_readl(priv, REG_GCRBEXHR);
336364dbdf3SDaniel Mack
337364dbdf3SDaniel Mack ret = wait_event_interruptible_timeout(priv->wait_free,
338364dbdf3SDaniel Mack priv->free, HZ*4);
339364dbdf3SDaniel Mack
340364dbdf3SDaniel Mack if (ret < 0)
341364dbdf3SDaniel Mack break;
342364dbdf3SDaniel Mack
343364dbdf3SDaniel Mack if (ret > 0)
344364dbdf3SDaniel Mack continue;
345364dbdf3SDaniel Mack
346364dbdf3SDaniel Mack if (gc_readl(priv, REG_GCRBEXHR) == rbexhr) {
347364dbdf3SDaniel Mack QERROR("TIMEOUT");
348364dbdf3SDaniel Mack ret = -ETIMEDOUT;
349364dbdf3SDaniel Mack break;
350364dbdf3SDaniel Mack }
351364dbdf3SDaniel Mack }
352364dbdf3SDaniel Mack
353364dbdf3SDaniel Mack QDUMP("done");
354364dbdf3SDaniel Mack
355364dbdf3SDaniel Mack return ret;
356364dbdf3SDaniel Mack }
357364dbdf3SDaniel Mack
358364dbdf3SDaniel Mack /* Misc device layer */
359364dbdf3SDaniel Mack
to_pxa3xx_gcu_priv(struct file * file)360109393afSDaniel Mack static inline struct pxa3xx_gcu_priv *to_pxa3xx_gcu_priv(struct file *file)
361996142e6SAl Viro {
362996142e6SAl Viro struct miscdevice *dev = file->private_data;
363996142e6SAl Viro return container_of(dev, struct pxa3xx_gcu_priv, misc_dev);
364996142e6SAl Viro }
365996142e6SAl Viro
3663437b2b8SDaniel Mack /*
3673437b2b8SDaniel Mack * provide an empty .open callback, so the core sets file->private_data
3683437b2b8SDaniel Mack * for us.
3693437b2b8SDaniel Mack */
pxa3xx_gcu_open(struct inode * inode,struct file * file)3703437b2b8SDaniel Mack static int pxa3xx_gcu_open(struct inode *inode, struct file *file)
3713437b2b8SDaniel Mack {
3723437b2b8SDaniel Mack return 0;
3733437b2b8SDaniel Mack }
3743437b2b8SDaniel Mack
375364dbdf3SDaniel Mack static ssize_t
pxa3xx_gcu_write(struct file * file,const char * buff,size_t count,loff_t * offp)376109393afSDaniel Mack pxa3xx_gcu_write(struct file *file, const char *buff,
377364dbdf3SDaniel Mack size_t count, loff_t *offp)
378364dbdf3SDaniel Mack {
379364dbdf3SDaniel Mack int ret;
380364dbdf3SDaniel Mack unsigned long flags;
381364dbdf3SDaniel Mack struct pxa3xx_gcu_batch *buffer;
382109393afSDaniel Mack struct pxa3xx_gcu_priv *priv = to_pxa3xx_gcu_priv(file);
383364dbdf3SDaniel Mack
384a09d2d00SHyunwoo Kim size_t words = count / 4;
385364dbdf3SDaniel Mack
386364dbdf3SDaniel Mack /* Does not need to be atomic. There's a lock in user space,
387364dbdf3SDaniel Mack * but anyhow, this is just for statistics. */
388364dbdf3SDaniel Mack priv->shared->num_writes++;
389364dbdf3SDaniel Mack priv->shared->num_words += words;
390364dbdf3SDaniel Mack
391364dbdf3SDaniel Mack /* Last word reserved for batch buffer end command */
392364dbdf3SDaniel Mack if (words >= PXA3XX_GCU_BATCH_WORDS)
393364dbdf3SDaniel Mack return -E2BIG;
394364dbdf3SDaniel Mack
395364dbdf3SDaniel Mack /* Wait for a free buffer */
396364dbdf3SDaniel Mack if (!priv->free) {
397364dbdf3SDaniel Mack ret = pxa3xx_gcu_wait_free(priv);
398364dbdf3SDaniel Mack if (ret < 0)
399364dbdf3SDaniel Mack return ret;
400364dbdf3SDaniel Mack }
401364dbdf3SDaniel Mack
402364dbdf3SDaniel Mack /*
403364dbdf3SDaniel Mack * Get buffer from free list
404364dbdf3SDaniel Mack */
405364dbdf3SDaniel Mack spin_lock_irqsave(&priv->spinlock, flags);
406364dbdf3SDaniel Mack buffer = priv->free;
407364dbdf3SDaniel Mack priv->free = buffer->next;
408364dbdf3SDaniel Mack spin_unlock_irqrestore(&priv->spinlock, flags);
409364dbdf3SDaniel Mack
410364dbdf3SDaniel Mack
411364dbdf3SDaniel Mack /* Copy data from user into buffer */
412364dbdf3SDaniel Mack ret = copy_from_user(buffer->ptr, buff, words * 4);
413364dbdf3SDaniel Mack if (ret) {
414364dbdf3SDaniel Mack spin_lock_irqsave(&priv->spinlock, flags);
415364dbdf3SDaniel Mack buffer->next = priv->free;
416364dbdf3SDaniel Mack priv->free = buffer;
417364dbdf3SDaniel Mack spin_unlock_irqrestore(&priv->spinlock, flags);
4180b7f1cc7Saxel lin return -EFAULT;
419364dbdf3SDaniel Mack }
420364dbdf3SDaniel Mack
421364dbdf3SDaniel Mack buffer->length = words;
422364dbdf3SDaniel Mack
423364dbdf3SDaniel Mack /* Append batch buffer end command */
424364dbdf3SDaniel Mack buffer->ptr[words] = 0x01000000;
425364dbdf3SDaniel Mack
426364dbdf3SDaniel Mack /*
427364dbdf3SDaniel Mack * Add buffer to ready list
428364dbdf3SDaniel Mack */
429364dbdf3SDaniel Mack spin_lock_irqsave(&priv->spinlock, flags);
430364dbdf3SDaniel Mack
431364dbdf3SDaniel Mack buffer->next = NULL;
432364dbdf3SDaniel Mack
433364dbdf3SDaniel Mack if (priv->ready) {
434364dbdf3SDaniel Mack BUG_ON(priv->ready_last == NULL);
435364dbdf3SDaniel Mack
436364dbdf3SDaniel Mack priv->ready_last->next = buffer;
437364dbdf3SDaniel Mack } else
438364dbdf3SDaniel Mack priv->ready = buffer;
439364dbdf3SDaniel Mack
440364dbdf3SDaniel Mack priv->ready_last = buffer;
441364dbdf3SDaniel Mack
442364dbdf3SDaniel Mack if (!priv->shared->hw_running)
443364dbdf3SDaniel Mack run_ready(priv);
444364dbdf3SDaniel Mack
445364dbdf3SDaniel Mack spin_unlock_irqrestore(&priv->spinlock, flags);
446364dbdf3SDaniel Mack
447364dbdf3SDaniel Mack return words * 4;
448364dbdf3SDaniel Mack }
449364dbdf3SDaniel Mack
450364dbdf3SDaniel Mack
451364dbdf3SDaniel Mack static long
pxa3xx_gcu_ioctl(struct file * file,unsigned int cmd,unsigned long arg)452109393afSDaniel Mack pxa3xx_gcu_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
453364dbdf3SDaniel Mack {
454364dbdf3SDaniel Mack unsigned long flags;
455109393afSDaniel Mack struct pxa3xx_gcu_priv *priv = to_pxa3xx_gcu_priv(file);
456364dbdf3SDaniel Mack
457364dbdf3SDaniel Mack switch (cmd) {
458364dbdf3SDaniel Mack case PXA3XX_GCU_IOCTL_RESET:
459364dbdf3SDaniel Mack spin_lock_irqsave(&priv->spinlock, flags);
460364dbdf3SDaniel Mack pxa3xx_gcu_reset(priv);
461364dbdf3SDaniel Mack spin_unlock_irqrestore(&priv->spinlock, flags);
462364dbdf3SDaniel Mack return 0;
463364dbdf3SDaniel Mack
464364dbdf3SDaniel Mack case PXA3XX_GCU_IOCTL_WAIT_IDLE:
465364dbdf3SDaniel Mack return pxa3xx_gcu_wait_idle(priv);
466364dbdf3SDaniel Mack }
467364dbdf3SDaniel Mack
468364dbdf3SDaniel Mack return -ENOSYS;
469364dbdf3SDaniel Mack }
470364dbdf3SDaniel Mack
471364dbdf3SDaniel Mack static int
pxa3xx_gcu_mmap(struct file * file,struct vm_area_struct * vma)472109393afSDaniel Mack pxa3xx_gcu_mmap(struct file *file, struct vm_area_struct *vma)
473364dbdf3SDaniel Mack {
474364dbdf3SDaniel Mack unsigned int size = vma->vm_end - vma->vm_start;
475109393afSDaniel Mack struct pxa3xx_gcu_priv *priv = to_pxa3xx_gcu_priv(file);
476364dbdf3SDaniel Mack
477364dbdf3SDaniel Mack switch (vma->vm_pgoff) {
478364dbdf3SDaniel Mack case 0:
479364dbdf3SDaniel Mack /* hand out the shared data area */
480364dbdf3SDaniel Mack if (size != SHARED_SIZE)
481364dbdf3SDaniel Mack return -EINVAL;
482364dbdf3SDaniel Mack
48302c486f4SChristoph Hellwig return dma_mmap_coherent(priv->dev, vma,
484364dbdf3SDaniel Mack priv->shared, priv->shared_phys, size);
485364dbdf3SDaniel Mack
486364dbdf3SDaniel Mack case SHARED_SIZE >> PAGE_SHIFT:
487364dbdf3SDaniel Mack /* hand out the MMIO base for direct register access
488364dbdf3SDaniel Mack * from userspace */
489364dbdf3SDaniel Mack if (size != resource_size(priv->resource_mem))
490364dbdf3SDaniel Mack return -EINVAL;
491364dbdf3SDaniel Mack
492364dbdf3SDaniel Mack vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
493364dbdf3SDaniel Mack
494364dbdf3SDaniel Mack return io_remap_pfn_range(vma, vma->vm_start,
495364dbdf3SDaniel Mack priv->resource_mem->start >> PAGE_SHIFT,
496364dbdf3SDaniel Mack size, vma->vm_page_prot);
497364dbdf3SDaniel Mack }
498364dbdf3SDaniel Mack
499364dbdf3SDaniel Mack return -EINVAL;
500364dbdf3SDaniel Mack }
501364dbdf3SDaniel Mack
502364dbdf3SDaniel Mack
503364dbdf3SDaniel Mack #ifdef PXA3XX_GCU_DEBUG_TIMER
504364dbdf3SDaniel Mack static struct timer_list pxa3xx_gcu_debug_timer;
505e4a67df7SKees Cook static struct pxa3xx_gcu_priv *debug_timer_priv;
506364dbdf3SDaniel Mack
pxa3xx_gcu_debug_timedout(struct timer_list * unused)507e4a67df7SKees Cook static void pxa3xx_gcu_debug_timedout(struct timer_list *unused)
508364dbdf3SDaniel Mack {
509e4a67df7SKees Cook struct pxa3xx_gcu_priv *priv = debug_timer_priv;
510364dbdf3SDaniel Mack
511364dbdf3SDaniel Mack QERROR("Timer DUMP");
512364dbdf3SDaniel Mack
5135eabff1cSHimanshu Jha mod_timer(&pxa3xx_gcu_debug_timer, jiffies + 5 * HZ);
514364dbdf3SDaniel Mack }
515364dbdf3SDaniel Mack
pxa3xx_gcu_init_debug_timer(struct pxa3xx_gcu_priv * priv)516e4a67df7SKees Cook static void pxa3xx_gcu_init_debug_timer(struct pxa3xx_gcu_priv *priv)
517364dbdf3SDaniel Mack {
518e4a67df7SKees Cook /* init the timer structure */
519e4a67df7SKees Cook debug_timer_priv = priv;
520e4a67df7SKees Cook timer_setup(&pxa3xx_gcu_debug_timer, pxa3xx_gcu_debug_timedout, 0);
521e4a67df7SKees Cook pxa3xx_gcu_debug_timedout(NULL);
522364dbdf3SDaniel Mack }
523364dbdf3SDaniel Mack #else
pxa3xx_gcu_init_debug_timer(struct pxa3xx_gcu_priv * priv)524e4a67df7SKees Cook static inline void pxa3xx_gcu_init_debug_timer(struct pxa3xx_gcu_priv *priv) {}
525364dbdf3SDaniel Mack #endif
526364dbdf3SDaniel Mack
527364dbdf3SDaniel Mack static int
pxa3xx_gcu_add_buffer(struct device * dev,struct pxa3xx_gcu_priv * priv)5289e4f9675SDaniel Mack pxa3xx_gcu_add_buffer(struct device *dev,
529364dbdf3SDaniel Mack struct pxa3xx_gcu_priv *priv)
530364dbdf3SDaniel Mack {
531364dbdf3SDaniel Mack struct pxa3xx_gcu_batch *buffer;
532364dbdf3SDaniel Mack
533364dbdf3SDaniel Mack buffer = kzalloc(sizeof(struct pxa3xx_gcu_batch), GFP_KERNEL);
534364dbdf3SDaniel Mack if (!buffer)
535364dbdf3SDaniel Mack return -ENOMEM;
536364dbdf3SDaniel Mack
5379e4f9675SDaniel Mack buffer->ptr = dma_alloc_coherent(dev, PXA3XX_GCU_BATCH_WORDS * 4,
538364dbdf3SDaniel Mack &buffer->phys, GFP_KERNEL);
539364dbdf3SDaniel Mack if (!buffer->ptr) {
540364dbdf3SDaniel Mack kfree(buffer);
541364dbdf3SDaniel Mack return -ENOMEM;
542364dbdf3SDaniel Mack }
543364dbdf3SDaniel Mack
544364dbdf3SDaniel Mack buffer->next = priv->free;
545364dbdf3SDaniel Mack priv->free = buffer;
546364dbdf3SDaniel Mack
547364dbdf3SDaniel Mack return 0;
548364dbdf3SDaniel Mack }
549364dbdf3SDaniel Mack
550364dbdf3SDaniel Mack static void
pxa3xx_gcu_free_buffers(struct device * dev,struct pxa3xx_gcu_priv * priv)5519e4f9675SDaniel Mack pxa3xx_gcu_free_buffers(struct device *dev,
552364dbdf3SDaniel Mack struct pxa3xx_gcu_priv *priv)
553364dbdf3SDaniel Mack {
554364dbdf3SDaniel Mack struct pxa3xx_gcu_batch *next, *buffer = priv->free;
555364dbdf3SDaniel Mack
556364dbdf3SDaniel Mack while (buffer) {
557364dbdf3SDaniel Mack next = buffer->next;
558364dbdf3SDaniel Mack
5599e4f9675SDaniel Mack dma_free_coherent(dev, PXA3XX_GCU_BATCH_WORDS * 4,
560364dbdf3SDaniel Mack buffer->ptr, buffer->phys);
561364dbdf3SDaniel Mack
562364dbdf3SDaniel Mack kfree(buffer);
563364dbdf3SDaniel Mack buffer = next;
564364dbdf3SDaniel Mack }
565364dbdf3SDaniel Mack
566364dbdf3SDaniel Mack priv->free = NULL;
567364dbdf3SDaniel Mack }
568364dbdf3SDaniel Mack
569109393afSDaniel Mack static const struct file_operations pxa3xx_gcu_miscdev_fops = {
570264bd660SAl Viro .owner = THIS_MODULE,
5713437b2b8SDaniel Mack .open = pxa3xx_gcu_open,
572109393afSDaniel Mack .write = pxa3xx_gcu_write,
573109393afSDaniel Mack .unlocked_ioctl = pxa3xx_gcu_ioctl,
574109393afSDaniel Mack .mmap = pxa3xx_gcu_mmap,
575264bd660SAl Viro };
576264bd660SAl Viro
pxa3xx_gcu_probe(struct platform_device * pdev)5779e4f9675SDaniel Mack static int pxa3xx_gcu_probe(struct platform_device *pdev)
578364dbdf3SDaniel Mack {
579364dbdf3SDaniel Mack int i, ret, irq;
580364dbdf3SDaniel Mack struct resource *r;
581364dbdf3SDaniel Mack struct pxa3xx_gcu_priv *priv;
5829e4f9675SDaniel Mack struct device *dev = &pdev->dev;
583364dbdf3SDaniel Mack
584a9b47c7fSDaniel Mack priv = devm_kzalloc(dev, sizeof(struct pxa3xx_gcu_priv), GFP_KERNEL);
585364dbdf3SDaniel Mack if (!priv)
586364dbdf3SDaniel Mack return -ENOMEM;
587364dbdf3SDaniel Mack
588364dbdf3SDaniel Mack init_waitqueue_head(&priv->wait_idle);
589364dbdf3SDaniel Mack init_waitqueue_head(&priv->wait_free);
590364dbdf3SDaniel Mack spin_lock_init(&priv->spinlock);
591364dbdf3SDaniel Mack
592364dbdf3SDaniel Mack /* we allocate the misc device structure as part of our own allocation,
593364dbdf3SDaniel Mack * so we can get a pointer to our priv structure later on with
594364dbdf3SDaniel Mack * container_of(). This isn't really necessary as we have a fixed minor
595364dbdf3SDaniel Mack * number anyway, but this is to avoid statics. */
596364dbdf3SDaniel Mack
5972ff86df2SChen Ni priv->misc_dev.minor = PXA3XX_GCU_MINOR;
5982ff86df2SChen Ni priv->misc_dev.name = DRV_NAME;
599109393afSDaniel Mack priv->misc_dev.fops = &pxa3xx_gcu_miscdev_fops;
600364dbdf3SDaniel Mack
601364dbdf3SDaniel Mack /* handle IO resources */
6020db0a1ebSYang Li priv->mmio_base = devm_platform_get_and_ioremap_resource(pdev, 0, &r);
6039b22b8c5SJingoo Han if (IS_ERR(priv->mmio_base))
604a9b47c7fSDaniel Mack return PTR_ERR(priv->mmio_base);
605364dbdf3SDaniel Mack
606364dbdf3SDaniel Mack /* enable the clock */
607a9b47c7fSDaniel Mack priv->clk = devm_clk_get(dev, NULL);
608626e021dSCai Huoqing if (IS_ERR(priv->clk))
609626e021dSCai Huoqing return dev_err_probe(dev, PTR_ERR(priv->clk), "failed to get clock\n");
610364dbdf3SDaniel Mack
611364dbdf3SDaniel Mack /* request the IRQ */
6129e4f9675SDaniel Mack irq = platform_get_irq(pdev, 0);
613e2bc5533SYang Li if (irq < 0)
6147588f1ecSGustavo A. R. Silva return irq;
615364dbdf3SDaniel Mack
616a9b47c7fSDaniel Mack ret = devm_request_irq(dev, irq, pxa3xx_gcu_handle_irq,
617f8798ccbSYong Zhang 0, DRV_NAME, priv);
618a9b47c7fSDaniel Mack if (ret < 0) {
6199e4f9675SDaniel Mack dev_err(dev, "request_irq failed\n");
620a9b47c7fSDaniel Mack return ret;
621a9b47c7fSDaniel Mack }
622a9b47c7fSDaniel Mack
623a9b47c7fSDaniel Mack /* allocate dma memory */
624a9b47c7fSDaniel Mack priv->shared = dma_alloc_coherent(dev, SHARED_SIZE,
625a9b47c7fSDaniel Mack &priv->shared_phys, GFP_KERNEL);
626a9b47c7fSDaniel Mack if (!priv->shared) {
627a9b47c7fSDaniel Mack dev_err(dev, "failed to allocate DMA memory\n");
628a9b47c7fSDaniel Mack return -ENOMEM;
629a9b47c7fSDaniel Mack }
630a9b47c7fSDaniel Mack
631a9b47c7fSDaniel Mack /* register misc device */
632a9b47c7fSDaniel Mack ret = misc_register(&priv->misc_dev);
633a9b47c7fSDaniel Mack if (ret < 0) {
634a9b47c7fSDaniel Mack dev_err(dev, "misc_register() for minor %d failed\n",
6356ce6ae7cSZhenzhong Duan PXA3XX_GCU_MINOR);
636a9b47c7fSDaniel Mack goto err_free_dma;
637a9b47c7fSDaniel Mack }
638a9b47c7fSDaniel Mack
6399e6e35edSRobert Jarzmik ret = clk_prepare_enable(priv->clk);
640a9b47c7fSDaniel Mack if (ret < 0) {
641a9b47c7fSDaniel Mack dev_err(dev, "failed to enable clock\n");
642a9b47c7fSDaniel Mack goto err_misc_deregister;
643a9b47c7fSDaniel Mack }
644a9b47c7fSDaniel Mack
645a9b47c7fSDaniel Mack for (i = 0; i < 8; i++) {
646a9b47c7fSDaniel Mack ret = pxa3xx_gcu_add_buffer(dev, priv);
647a9b47c7fSDaniel Mack if (ret) {
648d87ad457SYang Yingliang pxa3xx_gcu_free_buffers(dev, priv);
649a9b47c7fSDaniel Mack dev_err(dev, "failed to allocate DMA memory\n");
650a9b47c7fSDaniel Mack goto err_disable_clk;
651a9b47c7fSDaniel Mack }
652364dbdf3SDaniel Mack }
653364dbdf3SDaniel Mack
6549e4f9675SDaniel Mack platform_set_drvdata(pdev, priv);
655364dbdf3SDaniel Mack priv->resource_mem = r;
65602c486f4SChristoph Hellwig priv->dev = dev;
657364dbdf3SDaniel Mack pxa3xx_gcu_reset(priv);
658e4a67df7SKees Cook pxa3xx_gcu_init_debug_timer(priv);
659364dbdf3SDaniel Mack
6609e4f9675SDaniel Mack dev_info(dev, "registered @0x%p, DMA 0x%p (%d bytes), IRQ %d\n",
661364dbdf3SDaniel Mack (void *) r->start, (void *) priv->shared_phys,
662364dbdf3SDaniel Mack SHARED_SIZE, irq);
663364dbdf3SDaniel Mack return 0;
664364dbdf3SDaniel Mack
665d87ad457SYang Yingliang err_disable_clk:
666d87ad457SYang Yingliang clk_disable_unprepare(priv->clk);
667364dbdf3SDaniel Mack
668364dbdf3SDaniel Mack err_misc_deregister:
669364dbdf3SDaniel Mack misc_deregister(&priv->misc_dev);
670364dbdf3SDaniel Mack
671d87ad457SYang Yingliang err_free_dma:
672d87ad457SYang Yingliang dma_free_coherent(dev, SHARED_SIZE,
673d87ad457SYang Yingliang priv->shared, priv->shared_phys);
674a9b47c7fSDaniel Mack
675364dbdf3SDaniel Mack return ret;
676364dbdf3SDaniel Mack }
677364dbdf3SDaniel Mack
pxa3xx_gcu_remove(struct platform_device * pdev)6782872c291SUwe Kleine-König static void pxa3xx_gcu_remove(struct platform_device *pdev)
679364dbdf3SDaniel Mack {
6809e4f9675SDaniel Mack struct pxa3xx_gcu_priv *priv = platform_get_drvdata(pdev);
6819e4f9675SDaniel Mack struct device *dev = &pdev->dev;
682364dbdf3SDaniel Mack
683364dbdf3SDaniel Mack pxa3xx_gcu_wait_idle(priv);
684364dbdf3SDaniel Mack misc_deregister(&priv->misc_dev);
685a9b47c7fSDaniel Mack dma_free_coherent(dev, SHARED_SIZE, priv->shared, priv->shared_phys);
686d87ad457SYang Yingliang clk_disable_unprepare(priv->clk);
687109393afSDaniel Mack pxa3xx_gcu_free_buffers(dev, priv);
688364dbdf3SDaniel Mack }
689364dbdf3SDaniel Mack
690aa45ee8eSDaniel Mack #ifdef CONFIG_OF
691aa45ee8eSDaniel Mack static const struct of_device_id pxa3xx_gcu_of_match[] = {
692aa45ee8eSDaniel Mack { .compatible = "marvell,pxa300-gcu", },
693aa45ee8eSDaniel Mack { }
694aa45ee8eSDaniel Mack };
695aa45ee8eSDaniel Mack MODULE_DEVICE_TABLE(of, pxa3xx_gcu_of_match);
696aa45ee8eSDaniel Mack #endif
697aa45ee8eSDaniel Mack
698364dbdf3SDaniel Mack static struct platform_driver pxa3xx_gcu_driver = {
699364dbdf3SDaniel Mack .probe = pxa3xx_gcu_probe,
70001ecc142SUwe Kleine-König .remove = pxa3xx_gcu_remove,
701364dbdf3SDaniel Mack .driver = {
702364dbdf3SDaniel Mack .name = DRV_NAME,
703aa45ee8eSDaniel Mack .of_match_table = of_match_ptr(pxa3xx_gcu_of_match),
704364dbdf3SDaniel Mack },
705364dbdf3SDaniel Mack };
706364dbdf3SDaniel Mack
7074277f2c4SAxel Lin module_platform_driver(pxa3xx_gcu_driver);
708364dbdf3SDaniel Mack
709364dbdf3SDaniel Mack MODULE_DESCRIPTION("PXA3xx graphics controller unit driver");
710364dbdf3SDaniel Mack MODULE_LICENSE("GPL");
7116ce6ae7cSZhenzhong Duan MODULE_ALIAS_MISCDEV(PXA3XX_GCU_MINOR);
712364dbdf3SDaniel Mack MODULE_AUTHOR("Janine Kropp <nin@directfb.org>, "
713364dbdf3SDaniel Mack "Denis Oliver Kropp <dok@directfb.org>, "
714364dbdf3SDaniel Mack "Daniel Mack <daniel@caiaq.de>");
715