]> git.karo-electronics.de Git - karo-tx-linux.git/blob - drivers/media/platform/vsp1/vsp1_dl.c
1a9a58588f84989a166ef3556581508f249e4207
[karo-tx-linux.git] / drivers / media / platform / vsp1 / vsp1_dl.c
1 /*
2  * vsp1_dl.h  --  R-Car VSP1 Display List
3  *
4  * Copyright (C) 2015 Renesas Corporation
5  *
6  * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  */
13
14 #include <linux/device.h>
15 #include <linux/dma-mapping.h>
16 #include <linux/gfp.h>
17 #include <linux/slab.h>
18
19 #include "vsp1.h"
20 #include "vsp1_dl.h"
21 #include "vsp1_pipe.h"
22
23 /*
24  * Global resources
25  *
26  * - Display-related interrupts (can be used for vblank evasion ?)
27  * - Display-list enable
28  * - Header-less for WPF0
29  * - DL swap
30  */
31
32 #define VSP1_DL_BODY_SIZE               (2 * 4 * 256)
33 #define VSP1_DL_NUM_LISTS               3
34
35 struct vsp1_dl_entry {
36         u32 addr;
37         u32 data;
38 } __attribute__((__packed__));
39
40 struct vsp1_dl_list {
41         size_t size;
42         int reg_count;
43
44         bool in_use;
45
46         struct vsp1_dl_entry *body;
47         dma_addr_t dma;
48 };
49
50 /**
51  * struct vsp1_dl - Display List manager
52  * @vsp1: the VSP1 device
53  * @lock: protects the active, queued and pending lists
54  * @lists.all: array of all allocate display lists
55  * @lists.active: list currently being processed (loaded) by hardware
56  * @lists.queued: list queued to the hardware (written to the DL registers)
57  * @lists.pending: list waiting to be queued to the hardware
58  * @lists.write: list being written to by software
59  */
60 struct vsp1_dl {
61         struct vsp1_device *vsp1;
62
63         spinlock_t lock;
64
65         size_t size;
66         dma_addr_t dma;
67         void *mem;
68
69         struct {
70                 struct vsp1_dl_list all[VSP1_DL_NUM_LISTS];
71
72                 struct vsp1_dl_list *active;
73                 struct vsp1_dl_list *queued;
74                 struct vsp1_dl_list *pending;
75                 struct vsp1_dl_list *write;
76         } lists;
77 };
78
79 /* -----------------------------------------------------------------------------
80  * Display List Transaction Management
81  */
82
83 static void vsp1_dl_free_list(struct vsp1_dl_list *list)
84 {
85         if (!list)
86                 return;
87
88         list->in_use = false;
89 }
90
91 void vsp1_dl_reset(struct vsp1_dl *dl)
92 {
93         unsigned int i;
94
95         dl->lists.active = NULL;
96         dl->lists.queued = NULL;
97         dl->lists.pending = NULL;
98         dl->lists.write = NULL;
99
100         for (i = 0; i < ARRAY_SIZE(dl->lists.all); ++i)
101                 dl->lists.all[i].in_use = false;
102 }
103
104 void vsp1_dl_begin(struct vsp1_dl *dl)
105 {
106         struct vsp1_dl_list *list = NULL;
107         unsigned long flags;
108         unsigned int i;
109
110         spin_lock_irqsave(&dl->lock, flags);
111
112         for (i = 0; i < ARRAY_SIZE(dl->lists.all); ++i) {
113                 if (!dl->lists.all[i].in_use) {
114                         list = &dl->lists.all[i];
115                         break;
116                 }
117         }
118
119         if (!list) {
120                 list = dl->lists.pending;
121                 dl->lists.pending = NULL;
122         }
123
124         spin_unlock_irqrestore(&dl->lock, flags);
125
126         dl->lists.write = list;
127
128         list->in_use = true;
129         list->reg_count = 0;
130 }
131
132 void vsp1_dl_add(struct vsp1_entity *e, u32 reg, u32 data)
133 {
134         struct vsp1_pipeline *pipe = to_vsp1_pipeline(&e->subdev.entity);
135         struct vsp1_dl *dl = pipe->dl;
136         struct vsp1_dl_list *list = dl->lists.write;
137
138         list->body[list->reg_count].addr = reg;
139         list->body[list->reg_count].data = data;
140         list->reg_count++;
141 }
142
143 void vsp1_dl_commit(struct vsp1_dl *dl)
144 {
145         struct vsp1_device *vsp1 = dl->vsp1;
146         struct vsp1_dl_list *list;
147         unsigned long flags;
148         bool update;
149
150         list = dl->lists.write;
151         dl->lists.write = NULL;
152
153         spin_lock_irqsave(&dl->lock, flags);
154
155         /* Once the UPD bit has been set the hardware can start processing the
156          * display list at any time and we can't touch the address and size
157          * registers. In that case mark the update as pending, it will be
158          * queued up to the hardware by the frame end interrupt handler.
159          */
160         update = !!(vsp1_read(vsp1, VI6_DL_BODY_SIZE) & VI6_DL_BODY_SIZE_UPD);
161         if (update) {
162                 vsp1_dl_free_list(dl->lists.pending);
163                 dl->lists.pending = list;
164                 goto done;
165         }
166
167         /* Program the hardware with the display list body address and size.
168          * The UPD bit will be cleared by the device when the display list is
169          * processed.
170          */
171         vsp1_write(vsp1, VI6_DL_HDR_ADDR(0), list->dma);
172         vsp1_write(vsp1, VI6_DL_BODY_SIZE, VI6_DL_BODY_SIZE_UPD |
173                    (list->reg_count * 8));
174
175         vsp1_dl_free_list(dl->lists.queued);
176         dl->lists.queued = list;
177
178 done:
179         spin_unlock_irqrestore(&dl->lock, flags);
180 }
181
182 /* -----------------------------------------------------------------------------
183  * Interrupt Handling
184  */
185
186 void vsp1_dl_irq_display_start(struct vsp1_dl *dl)
187 {
188         spin_lock(&dl->lock);
189
190         /* The display start interrupt signals the end of the display list
191          * processing by the device. The active display list, if any, won't be
192          * accessed anymore and can be reused.
193          */
194         if (dl->lists.active) {
195                 vsp1_dl_free_list(dl->lists.active);
196                 dl->lists.active = NULL;
197         }
198
199         spin_unlock(&dl->lock);
200 }
201
202 void vsp1_dl_irq_frame_end(struct vsp1_dl *dl)
203 {
204         struct vsp1_device *vsp1 = dl->vsp1;
205
206         spin_lock(&dl->lock);
207
208         /* The UPD bit set indicates that the commit operation raced with the
209          * interrupt and occurred after the frame end event and UPD clear but
210          * before interrupt processing. The hardware hasn't taken the update
211          * into account yet, we'll thus skip one frame and retry.
212          */
213         if (vsp1_read(vsp1, VI6_DL_BODY_SIZE) & VI6_DL_BODY_SIZE_UPD)
214                 goto done;
215
216         /* The device starts processing the queued display list right after the
217          * frame end interrupt. The display list thus becomes active.
218          */
219         if (dl->lists.queued) {
220                 WARN_ON(dl->lists.active);
221                 dl->lists.active = dl->lists.queued;
222                 dl->lists.queued = NULL;
223         }
224
225         /* Now that the UPD bit has been cleared we can queue the next display
226          * list to the hardware if one has been prepared.
227          */
228         if (dl->lists.pending) {
229                 struct vsp1_dl_list *list = dl->lists.pending;
230
231                 vsp1_write(vsp1, VI6_DL_HDR_ADDR(0), list->dma);
232                 vsp1_write(vsp1, VI6_DL_BODY_SIZE, VI6_DL_BODY_SIZE_UPD |
233                            (list->reg_count * 8));
234
235                 dl->lists.queued = list;
236                 dl->lists.pending = NULL;
237         }
238
239 done:
240         spin_unlock(&dl->lock);
241 }
242
243 /* -----------------------------------------------------------------------------
244  * Hardware Setup
245  */
246
247 void vsp1_dl_setup(struct vsp1_device *vsp1)
248 {
249         u32 ctrl = (256 << VI6_DL_CTRL_AR_WAIT_SHIFT)
250                  | VI6_DL_CTRL_DC2 | VI6_DL_CTRL_DC1 | VI6_DL_CTRL_DC0
251                  | VI6_DL_CTRL_DLE;
252
253         /* The DRM pipeline operates with header-less display lists in
254          * Continuous Frame Mode.
255          */
256         if (vsp1->drm)
257                 ctrl |= VI6_DL_CTRL_CFM0 | VI6_DL_CTRL_NH0;
258
259         vsp1_write(vsp1, VI6_DL_CTRL, ctrl);
260         vsp1_write(vsp1, VI6_DL_SWAP, VI6_DL_SWAP_LWS);
261 }
262
263 /* -----------------------------------------------------------------------------
264  * Initialization and Cleanup
265  */
266
267 struct vsp1_dl *vsp1_dl_create(struct vsp1_device *vsp1)
268 {
269         struct vsp1_dl *dl;
270         unsigned int i;
271
272         dl = kzalloc(sizeof(*dl), GFP_KERNEL);
273         if (!dl)
274                 return NULL;
275
276         spin_lock_init(&dl->lock);
277
278         dl->vsp1 = vsp1;
279         dl->size = VSP1_DL_BODY_SIZE * ARRAY_SIZE(dl->lists.all);
280
281         dl->mem = dma_alloc_wc(vsp1->dev, dl->size, &dl->dma,
282                                          GFP_KERNEL);
283         if (!dl->mem) {
284                 kfree(dl);
285                 return NULL;
286         }
287
288         for (i = 0; i < ARRAY_SIZE(dl->lists.all); ++i) {
289                 struct vsp1_dl_list *list = &dl->lists.all[i];
290
291                 list->size = VSP1_DL_BODY_SIZE;
292                 list->reg_count = 0;
293                 list->in_use = false;
294                 list->dma = dl->dma + VSP1_DL_BODY_SIZE * i;
295                 list->body = dl->mem + VSP1_DL_BODY_SIZE * i;
296         }
297
298         return dl;
299 }
300
301 void vsp1_dl_destroy(struct vsp1_dl *dl)
302 {
303         dma_free_wc(dl->vsp1->dev, dl->size, dl->mem, dl->dma);
304         kfree(dl);
305 }