1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Copyright (c) 2010 Sascha Hauer <s.hauer@pengutronix.de> |
4 | * Copyright (C) 2005-2009 Freescale Semiconductor, Inc. |
5 | */ |
6 | |
7 | #include <linux/export.h> |
8 | #include <linux/module.h> |
9 | #include <linux/types.h> |
10 | #include <linux/errno.h> |
11 | #include <linux/delay.h> |
12 | #include <linux/interrupt.h> |
13 | #include <linux/io.h> |
14 | |
15 | #include <video/imx-ipu-v3.h> |
16 | #include "ipu-prv.h" |
17 | |
18 | #define DC_MAP_CONF_PTR(n) (0x108 + ((n) & ~0x1) * 2) |
19 | #define DC_MAP_CONF_VAL(n) (0x144 + ((n) & ~0x1) * 2) |
20 | |
21 | #define DC_EVT_NF 0 |
22 | #define DC_EVT_NL 1 |
23 | #define DC_EVT_EOF 2 |
24 | #define DC_EVT_NFIELD 3 |
25 | #define DC_EVT_EOL 4 |
26 | #define DC_EVT_EOFIELD 5 |
27 | #define DC_EVT_NEW_ADDR 6 |
28 | #define DC_EVT_NEW_CHAN 7 |
29 | #define DC_EVT_NEW_DATA 8 |
30 | |
31 | #define DC_EVT_NEW_ADDR_W_0 0 |
32 | #define DC_EVT_NEW_ADDR_W_1 1 |
33 | #define DC_EVT_NEW_CHAN_W_0 2 |
34 | #define DC_EVT_NEW_CHAN_W_1 3 |
35 | #define DC_EVT_NEW_DATA_W_0 4 |
36 | #define DC_EVT_NEW_DATA_W_1 5 |
37 | #define DC_EVT_NEW_ADDR_R_0 6 |
38 | #define DC_EVT_NEW_ADDR_R_1 7 |
39 | #define DC_EVT_NEW_CHAN_R_0 8 |
40 | #define DC_EVT_NEW_CHAN_R_1 9 |
41 | #define DC_EVT_NEW_DATA_R_0 10 |
42 | #define DC_EVT_NEW_DATA_R_1 11 |
43 | |
44 | #define DC_WR_CH_CONF 0x0 |
45 | #define DC_WR_CH_ADDR 0x4 |
46 | #define DC_RL_CH(evt) (8 + ((evt) & ~0x1) * 2) |
47 | |
48 | #define DC_GEN 0xd4 |
49 | #define DC_DISP_CONF1(disp) (0xd8 + (disp) * 4) |
50 | #define DC_DISP_CONF2(disp) (0xe8 + (disp) * 4) |
51 | #define DC_STAT 0x1c8 |
52 | |
53 | #define WROD(lf) (0x18 | ((lf) << 1)) |
54 | #define WRG 0x01 |
55 | #define WCLK 0xc9 |
56 | |
57 | #define SYNC_WAVE 0 |
58 | #define NULL_WAVE (-1) |
59 | |
60 | #define DC_GEN_SYNC_1_6_SYNC (2 << 1) |
61 | #define DC_GEN_SYNC_PRIORITY_1 (1 << 7) |
62 | |
63 | #define DC_WR_CH_CONF_WORD_SIZE_8 (0 << 0) |
64 | #define DC_WR_CH_CONF_WORD_SIZE_16 (1 << 0) |
65 | #define DC_WR_CH_CONF_WORD_SIZE_24 (2 << 0) |
66 | #define DC_WR_CH_CONF_WORD_SIZE_32 (3 << 0) |
67 | #define DC_WR_CH_CONF_DISP_ID_PARALLEL(i) (((i) & 0x1) << 3) |
68 | #define DC_WR_CH_CONF_DISP_ID_SERIAL (2 << 3) |
69 | #define DC_WR_CH_CONF_DISP_ID_ASYNC (3 << 4) |
70 | #define DC_WR_CH_CONF_FIELD_MODE (1 << 9) |
71 | #define DC_WR_CH_CONF_PROG_TYPE_NORMAL (4 << 5) |
72 | #define DC_WR_CH_CONF_PROG_TYPE_MASK (7 << 5) |
73 | #define DC_WR_CH_CONF_PROG_DI_ID (1 << 2) |
74 | #define DC_WR_CH_CONF_PROG_DISP_ID(i) (((i) & 0x1) << 3) |
75 | |
76 | #define IPU_DC_NUM_CHANNELS 10 |
77 | |
78 | struct ipu_dc_priv; |
79 | |
80 | enum ipu_dc_map { |
81 | IPU_DC_MAP_RGB24, |
82 | IPU_DC_MAP_RGB565, |
83 | IPU_DC_MAP_GBR24, /* TVEv2 */ |
84 | IPU_DC_MAP_BGR666, |
85 | IPU_DC_MAP_LVDS666, |
86 | IPU_DC_MAP_BGR24, |
87 | }; |
88 | |
89 | struct ipu_dc { |
90 | /* The display interface number assigned to this dc channel */ |
91 | unsigned int di; |
92 | void __iomem *base; |
93 | struct ipu_dc_priv *priv; |
94 | int chno; |
95 | bool in_use; |
96 | }; |
97 | |
98 | struct ipu_dc_priv { |
99 | void __iomem *dc_reg; |
100 | void __iomem *dc_tmpl_reg; |
101 | struct ipu_soc *ipu; |
102 | struct device *dev; |
103 | struct ipu_dc channels[IPU_DC_NUM_CHANNELS]; |
104 | struct mutex mutex; |
105 | struct completion comp; |
106 | int use_count; |
107 | }; |
108 | |
109 | static void dc_link_event(struct ipu_dc *dc, int event, int addr, int priority) |
110 | { |
111 | u32 reg; |
112 | |
113 | reg = readl(addr: dc->base + DC_RL_CH(event)); |
114 | reg &= ~(0xffff << (16 * (event & 0x1))); |
115 | reg |= ((addr << 8) | priority) << (16 * (event & 0x1)); |
116 | writel(val: reg, addr: dc->base + DC_RL_CH(event)); |
117 | } |
118 | |
119 | static void dc_write_tmpl(struct ipu_dc *dc, int word, u32 opcode, u32 operand, |
120 | int map, int wave, int glue, int sync, int stop) |
121 | { |
122 | struct ipu_dc_priv *priv = dc->priv; |
123 | u32 reg1, reg2; |
124 | |
125 | if (opcode == WCLK) { |
126 | reg1 = (operand << 20) & 0xfff00000; |
127 | reg2 = operand >> 12 | opcode << 1 | stop << 9; |
128 | } else if (opcode == WRG) { |
129 | reg1 = sync | glue << 4 | ++wave << 11 | ((operand << 15) & 0xffff8000); |
130 | reg2 = operand >> 17 | opcode << 7 | stop << 9; |
131 | } else { |
132 | reg1 = sync | glue << 4 | ++wave << 11 | ++map << 15 | ((operand << 20) & 0xfff00000); |
133 | reg2 = operand >> 12 | opcode << 4 | stop << 9; |
134 | } |
135 | writel(val: reg1, addr: priv->dc_tmpl_reg + word * 8); |
136 | writel(val: reg2, addr: priv->dc_tmpl_reg + word * 8 + 4); |
137 | } |
138 | |
139 | static int ipu_bus_format_to_map(u32 fmt) |
140 | { |
141 | switch (fmt) { |
142 | default: |
143 | WARN_ON(1); |
144 | fallthrough; |
145 | case MEDIA_BUS_FMT_RGB888_1X24: |
146 | return IPU_DC_MAP_RGB24; |
147 | case MEDIA_BUS_FMT_RGB565_1X16: |
148 | return IPU_DC_MAP_RGB565; |
149 | case MEDIA_BUS_FMT_GBR888_1X24: |
150 | return IPU_DC_MAP_GBR24; |
151 | case MEDIA_BUS_FMT_RGB666_1X18: |
152 | return IPU_DC_MAP_BGR666; |
153 | case MEDIA_BUS_FMT_RGB666_1X24_CPADHI: |
154 | return IPU_DC_MAP_LVDS666; |
155 | case MEDIA_BUS_FMT_BGR888_1X24: |
156 | return IPU_DC_MAP_BGR24; |
157 | } |
158 | } |
159 | |
160 | int ipu_dc_init_sync(struct ipu_dc *dc, struct ipu_di *di, bool interlaced, |
161 | u32 bus_format, u32 width) |
162 | { |
163 | struct ipu_dc_priv *priv = dc->priv; |
164 | int addr, sync; |
165 | u32 reg = 0; |
166 | int map; |
167 | |
168 | dc->di = ipu_di_get_num(di); |
169 | |
170 | if (!IS_ALIGNED(width, 8)) { |
171 | dev_warn(priv->dev, |
172 | "%s: hactive does not align to 8 byte\n" , __func__); |
173 | } |
174 | |
175 | map = ipu_bus_format_to_map(fmt: bus_format); |
176 | |
177 | /* |
178 | * In interlaced mode we need more counters to create the asymmetric |
179 | * per-field VSYNC signals. The pixel active signal synchronising DC |
180 | * to DI moves to signal generator #6 (see ipu-di.c). In progressive |
181 | * mode counter #5 is used. |
182 | */ |
183 | sync = interlaced ? 6 : 5; |
184 | |
185 | /* Reserve 5 microcode template words for each DI */ |
186 | if (dc->di) |
187 | addr = 5; |
188 | else |
189 | addr = 0; |
190 | |
191 | if (interlaced) { |
192 | dc_link_event(dc, DC_EVT_NL, addr, priority: 3); |
193 | dc_link_event(dc, DC_EVT_EOL, addr, priority: 2); |
194 | dc_link_event(dc, DC_EVT_NEW_DATA, addr, priority: 1); |
195 | |
196 | /* Init template microcode */ |
197 | dc_write_tmpl(dc, word: addr, WROD(0), operand: 0, map, SYNC_WAVE, glue: 0, sync, stop: 1); |
198 | } else { |
199 | dc_link_event(dc, DC_EVT_NL, addr: addr + 2, priority: 3); |
200 | dc_link_event(dc, DC_EVT_EOL, addr: addr + 3, priority: 2); |
201 | dc_link_event(dc, DC_EVT_NEW_DATA, addr: addr + 1, priority: 1); |
202 | |
203 | /* Init template microcode */ |
204 | dc_write_tmpl(dc, word: addr + 2, WROD(0), operand: 0, map, SYNC_WAVE, glue: 8, sync, stop: 1); |
205 | dc_write_tmpl(dc, word: addr + 3, WROD(0), operand: 0, map, SYNC_WAVE, glue: 4, sync, stop: 0); |
206 | dc_write_tmpl(dc, word: addr + 4, WRG, operand: 0, map, NULL_WAVE, glue: 0, sync: 0, stop: 1); |
207 | dc_write_tmpl(dc, word: addr + 1, WROD(0), operand: 0, map, SYNC_WAVE, glue: 0, sync, stop: 1); |
208 | } |
209 | |
210 | dc_link_event(dc, DC_EVT_NF, addr: 0, priority: 0); |
211 | dc_link_event(dc, DC_EVT_NFIELD, addr: 0, priority: 0); |
212 | dc_link_event(dc, DC_EVT_EOF, addr: 0, priority: 0); |
213 | dc_link_event(dc, DC_EVT_EOFIELD, addr: 0, priority: 0); |
214 | dc_link_event(dc, DC_EVT_NEW_CHAN, addr: 0, priority: 0); |
215 | dc_link_event(dc, DC_EVT_NEW_ADDR, addr: 0, priority: 0); |
216 | |
217 | reg = readl(addr: dc->base + DC_WR_CH_CONF); |
218 | if (interlaced) |
219 | reg |= DC_WR_CH_CONF_FIELD_MODE; |
220 | else |
221 | reg &= ~DC_WR_CH_CONF_FIELD_MODE; |
222 | writel(val: reg, addr: dc->base + DC_WR_CH_CONF); |
223 | |
224 | writel(val: 0x0, addr: dc->base + DC_WR_CH_ADDR); |
225 | writel(val: width, addr: priv->dc_reg + DC_DISP_CONF2(dc->di)); |
226 | |
227 | return 0; |
228 | } |
229 | EXPORT_SYMBOL_GPL(ipu_dc_init_sync); |
230 | |
231 | void ipu_dc_enable(struct ipu_soc *ipu) |
232 | { |
233 | struct ipu_dc_priv *priv = ipu->dc_priv; |
234 | |
235 | mutex_lock(&priv->mutex); |
236 | |
237 | if (!priv->use_count) |
238 | ipu_module_enable(ipu: priv->ipu, mask: IPU_CONF_DC_EN); |
239 | |
240 | priv->use_count++; |
241 | |
242 | mutex_unlock(lock: &priv->mutex); |
243 | } |
244 | EXPORT_SYMBOL_GPL(ipu_dc_enable); |
245 | |
246 | void ipu_dc_enable_channel(struct ipu_dc *dc) |
247 | { |
248 | u32 reg; |
249 | |
250 | reg = readl(addr: dc->base + DC_WR_CH_CONF); |
251 | reg |= DC_WR_CH_CONF_PROG_TYPE_NORMAL; |
252 | writel(val: reg, addr: dc->base + DC_WR_CH_CONF); |
253 | } |
254 | EXPORT_SYMBOL_GPL(ipu_dc_enable_channel); |
255 | |
256 | void ipu_dc_disable_channel(struct ipu_dc *dc) |
257 | { |
258 | u32 val; |
259 | |
260 | val = readl(addr: dc->base + DC_WR_CH_CONF); |
261 | val &= ~DC_WR_CH_CONF_PROG_TYPE_MASK; |
262 | writel(val, addr: dc->base + DC_WR_CH_CONF); |
263 | } |
264 | EXPORT_SYMBOL_GPL(ipu_dc_disable_channel); |
265 | |
266 | void ipu_dc_disable(struct ipu_soc *ipu) |
267 | { |
268 | struct ipu_dc_priv *priv = ipu->dc_priv; |
269 | |
270 | mutex_lock(&priv->mutex); |
271 | |
272 | priv->use_count--; |
273 | if (!priv->use_count) |
274 | ipu_module_disable(ipu: priv->ipu, mask: IPU_CONF_DC_EN); |
275 | |
276 | if (priv->use_count < 0) |
277 | priv->use_count = 0; |
278 | |
279 | mutex_unlock(lock: &priv->mutex); |
280 | } |
281 | EXPORT_SYMBOL_GPL(ipu_dc_disable); |
282 | |
283 | static void ipu_dc_map_config(struct ipu_dc_priv *priv, enum ipu_dc_map map, |
284 | int byte_num, int offset, int mask) |
285 | { |
286 | int ptr = map * 3 + byte_num; |
287 | u32 reg; |
288 | |
289 | reg = readl(addr: priv->dc_reg + DC_MAP_CONF_VAL(ptr)); |
290 | reg &= ~(0xffff << (16 * (ptr & 0x1))); |
291 | reg |= ((offset << 8) | mask) << (16 * (ptr & 0x1)); |
292 | writel(val: reg, addr: priv->dc_reg + DC_MAP_CONF_VAL(ptr)); |
293 | |
294 | reg = readl(addr: priv->dc_reg + DC_MAP_CONF_PTR(map)); |
295 | reg &= ~(0x1f << ((16 * (map & 0x1)) + (5 * byte_num))); |
296 | reg |= ptr << ((16 * (map & 0x1)) + (5 * byte_num)); |
297 | writel(val: reg, addr: priv->dc_reg + DC_MAP_CONF_PTR(map)); |
298 | } |
299 | |
300 | static void ipu_dc_map_clear(struct ipu_dc_priv *priv, int map) |
301 | { |
302 | u32 reg = readl(addr: priv->dc_reg + DC_MAP_CONF_PTR(map)); |
303 | |
304 | writel(val: reg & ~(0xffff << (16 * (map & 0x1))), |
305 | addr: priv->dc_reg + DC_MAP_CONF_PTR(map)); |
306 | } |
307 | |
308 | struct ipu_dc *ipu_dc_get(struct ipu_soc *ipu, int channel) |
309 | { |
310 | struct ipu_dc_priv *priv = ipu->dc_priv; |
311 | struct ipu_dc *dc; |
312 | |
313 | if (channel >= IPU_DC_NUM_CHANNELS) |
314 | return ERR_PTR(error: -ENODEV); |
315 | |
316 | dc = &priv->channels[channel]; |
317 | |
318 | mutex_lock(&priv->mutex); |
319 | |
320 | if (dc->in_use) { |
321 | mutex_unlock(lock: &priv->mutex); |
322 | return ERR_PTR(error: -EBUSY); |
323 | } |
324 | |
325 | dc->in_use = true; |
326 | |
327 | mutex_unlock(lock: &priv->mutex); |
328 | |
329 | return dc; |
330 | } |
331 | EXPORT_SYMBOL_GPL(ipu_dc_get); |
332 | |
333 | void ipu_dc_put(struct ipu_dc *dc) |
334 | { |
335 | struct ipu_dc_priv *priv = dc->priv; |
336 | |
337 | mutex_lock(&priv->mutex); |
338 | dc->in_use = false; |
339 | mutex_unlock(lock: &priv->mutex); |
340 | } |
341 | EXPORT_SYMBOL_GPL(ipu_dc_put); |
342 | |
343 | int ipu_dc_init(struct ipu_soc *ipu, struct device *dev, |
344 | unsigned long base, unsigned long template_base) |
345 | { |
346 | struct ipu_dc_priv *priv; |
347 | static const int channel_offsets[] = { |
348 | 0, 0x1c, 0x38, 0x54, 0x58, 0x5c, 0x78, 0, 0x94, 0xb4 |
349 | }; |
350 | int i; |
351 | |
352 | priv = devm_kzalloc(dev, size: sizeof(*priv), GFP_KERNEL); |
353 | if (!priv) |
354 | return -ENOMEM; |
355 | |
356 | mutex_init(&priv->mutex); |
357 | |
358 | priv->dev = dev; |
359 | priv->ipu = ipu; |
360 | priv->dc_reg = devm_ioremap(dev, offset: base, PAGE_SIZE); |
361 | priv->dc_tmpl_reg = devm_ioremap(dev, offset: template_base, PAGE_SIZE); |
362 | if (!priv->dc_reg || !priv->dc_tmpl_reg) |
363 | return -ENOMEM; |
364 | |
365 | for (i = 0; i < IPU_DC_NUM_CHANNELS; i++) { |
366 | priv->channels[i].chno = i; |
367 | priv->channels[i].priv = priv; |
368 | priv->channels[i].base = priv->dc_reg + channel_offsets[i]; |
369 | } |
370 | |
371 | writel(DC_WR_CH_CONF_WORD_SIZE_24 | DC_WR_CH_CONF_DISP_ID_PARALLEL(1) | |
372 | DC_WR_CH_CONF_PROG_DI_ID, |
373 | addr: priv->channels[1].base + DC_WR_CH_CONF); |
374 | writel(DC_WR_CH_CONF_WORD_SIZE_24 | DC_WR_CH_CONF_DISP_ID_PARALLEL(0), |
375 | addr: priv->channels[5].base + DC_WR_CH_CONF); |
376 | |
377 | writel(DC_GEN_SYNC_1_6_SYNC | DC_GEN_SYNC_PRIORITY_1, |
378 | addr: priv->dc_reg + DC_GEN); |
379 | |
380 | ipu->dc_priv = priv; |
381 | |
382 | dev_dbg(dev, "DC base: 0x%08lx template base: 0x%08lx\n" , |
383 | base, template_base); |
384 | |
385 | /* rgb24 */ |
386 | ipu_dc_map_clear(priv, map: IPU_DC_MAP_RGB24); |
387 | ipu_dc_map_config(priv, map: IPU_DC_MAP_RGB24, byte_num: 0, offset: 7, mask: 0xff); /* blue */ |
388 | ipu_dc_map_config(priv, map: IPU_DC_MAP_RGB24, byte_num: 1, offset: 15, mask: 0xff); /* green */ |
389 | ipu_dc_map_config(priv, map: IPU_DC_MAP_RGB24, byte_num: 2, offset: 23, mask: 0xff); /* red */ |
390 | |
391 | /* rgb565 */ |
392 | ipu_dc_map_clear(priv, map: IPU_DC_MAP_RGB565); |
393 | ipu_dc_map_config(priv, map: IPU_DC_MAP_RGB565, byte_num: 0, offset: 4, mask: 0xf8); /* blue */ |
394 | ipu_dc_map_config(priv, map: IPU_DC_MAP_RGB565, byte_num: 1, offset: 10, mask: 0xfc); /* green */ |
395 | ipu_dc_map_config(priv, map: IPU_DC_MAP_RGB565, byte_num: 2, offset: 15, mask: 0xf8); /* red */ |
396 | |
397 | /* gbr24 */ |
398 | ipu_dc_map_clear(priv, map: IPU_DC_MAP_GBR24); |
399 | ipu_dc_map_config(priv, map: IPU_DC_MAP_GBR24, byte_num: 2, offset: 15, mask: 0xff); /* green */ |
400 | ipu_dc_map_config(priv, map: IPU_DC_MAP_GBR24, byte_num: 1, offset: 7, mask: 0xff); /* blue */ |
401 | ipu_dc_map_config(priv, map: IPU_DC_MAP_GBR24, byte_num: 0, offset: 23, mask: 0xff); /* red */ |
402 | |
403 | /* bgr666 */ |
404 | ipu_dc_map_clear(priv, map: IPU_DC_MAP_BGR666); |
405 | ipu_dc_map_config(priv, map: IPU_DC_MAP_BGR666, byte_num: 0, offset: 5, mask: 0xfc); /* blue */ |
406 | ipu_dc_map_config(priv, map: IPU_DC_MAP_BGR666, byte_num: 1, offset: 11, mask: 0xfc); /* green */ |
407 | ipu_dc_map_config(priv, map: IPU_DC_MAP_BGR666, byte_num: 2, offset: 17, mask: 0xfc); /* red */ |
408 | |
409 | /* lvds666 */ |
410 | ipu_dc_map_clear(priv, map: IPU_DC_MAP_LVDS666); |
411 | ipu_dc_map_config(priv, map: IPU_DC_MAP_LVDS666, byte_num: 0, offset: 5, mask: 0xfc); /* blue */ |
412 | ipu_dc_map_config(priv, map: IPU_DC_MAP_LVDS666, byte_num: 1, offset: 13, mask: 0xfc); /* green */ |
413 | ipu_dc_map_config(priv, map: IPU_DC_MAP_LVDS666, byte_num: 2, offset: 21, mask: 0xfc); /* red */ |
414 | |
415 | /* bgr24 */ |
416 | ipu_dc_map_clear(priv, map: IPU_DC_MAP_BGR24); |
417 | ipu_dc_map_config(priv, map: IPU_DC_MAP_BGR24, byte_num: 2, offset: 7, mask: 0xff); /* red */ |
418 | ipu_dc_map_config(priv, map: IPU_DC_MAP_BGR24, byte_num: 1, offset: 15, mask: 0xff); /* green */ |
419 | ipu_dc_map_config(priv, map: IPU_DC_MAP_BGR24, byte_num: 0, offset: 23, mask: 0xff); /* blue */ |
420 | |
421 | return 0; |
422 | } |
423 | |
424 | void ipu_dc_exit(struct ipu_soc *ipu) |
425 | { |
426 | } |
427 | |