summaryrefslogtreecommitdiff
path: root/firmware/target/mips/ingenic_x1000/lcd-x1000.c
diff options
context:
space:
mode:
authorAidan MacDonald <amachronic@protonmail.com>2021-02-27 22:08:58 +0000
committerAidan MacDonald <amachronic@protonmail.com>2021-03-28 00:01:37 +0000
commit3ec66893e377b088c1284d2d23adb2aeea6d7965 (patch)
treeb647717f83ad56b15dc42cfdef5d04d68cd9bd6b /firmware/target/mips/ingenic_x1000/lcd-x1000.c
parent83fcbedc65f4b9ae7e491ecf6f07c0af4b245f74 (diff)
downloadrockbox-3ec66893e377b088c1284d2d23adb2aeea6d7965.tar.gz
rockbox-3ec66893e377b088c1284d2d23adb2aeea6d7965.zip
New port: FiiO M3K on bare metal
Change-Id: I7517e7d5459e129dcfc9465c6fbd708619888fbe
Diffstat (limited to 'firmware/target/mips/ingenic_x1000/lcd-x1000.c')
-rw-r--r--firmware/target/mips/ingenic_x1000/lcd-x1000.c477
1 files changed, 477 insertions, 0 deletions
diff --git a/firmware/target/mips/ingenic_x1000/lcd-x1000.c b/firmware/target/mips/ingenic_x1000/lcd-x1000.c
new file mode 100644
index 0000000000..aadf93c8ff
--- /dev/null
+++ b/firmware/target/mips/ingenic_x1000/lcd-x1000.c
@@ -0,0 +1,477 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2021 Aidan MacDonald
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22#include "lcd.h"
23#include "system.h"
24#include "kernel.h"
25#include "lcd-x1000.h"
26#include "dma-x1000.h"
27#include "irq-x1000.h"
28#include "x1000/lcd.h"
29#include "x1000/cpm.h"
30#include <stdint.h>
31#include <string.h>
32
33#define LCD_DMA_CMD_SOFINT (1 << 31)
34#define LCD_DMA_CMD_EOFINT (1 << 30)
35#define LCD_DMA_CMD_COMMAND (1 << 29)
36#define LCD_DMA_CMD_FRM_EN (1 << 26)
37
38#define LCD_DMA_CNT_BPP_15BIT ((4 << 27)|(1<<30))
39#define LCD_DMA_CNT_BPP_16BIT (4 << 27)
40#define LCD_DMA_CNT_BPP_18BIT_OR_24BIT (5 << 27)
41
42struct lcd_dma_desc {
43 uint32_t da; /* Next descriptor address */
44 uint32_t sa; /* Source buffer address */
45 uint32_t fid; /* Frame ID */
46 uint32_t cmd; /* Command bits */
47 uint32_t osz; /* OFFSIZE register */
48 uint32_t pw; /* page width */
49 uint32_t cnt; /* CNUM / CPOS, depending on LCD_DMA_CMD_COMMAND bit */
50 uint32_t fsz; /* Frame size (set to 0 for commands) */
51} __attribute__((aligned(32)));
52
53/* We need two descriptors, one for framebuffer write command and one for
54 * frame data. Even if no command is needed we need a dummy command descriptor
55 * with cnt=0, or the hardware will refuse to transfer the frame data.
56 *
57 * First descriptor always has to be a command (lcd_dma_desc[0] here) or
58 * the hardware will give up.
59 */
60static struct lcd_dma_desc lcd_dma_desc[2];
61
62/* Shadow copy of main framebuffer, needed to avoid tearing */
63static fb_data shadowfb[LCD_HEIGHT*LCD_WIDTH] __attribute__((aligned(64)));
64
65/* Signals DMA copy to shadow FB is done */
66static volatile int fbcopy_done;
67
68/* True if we're in sleep mode */
69static bool lcd_sleeping = false;
70
71/* Check if running with interrupts disabled (eg: panic screen) */
72#define lcd_panic_mode \
73 UNLIKELY((read_c0_status() & 1) == 0)
74
75static void lcd_init_controller(const struct lcd_tgt_config* cfg)
76{
77 /* Set MCFG/MCFG_NEW according to target interface settings */
78 unsigned mcfg = 0, mcfg_new = 0;
79
80 switch(cfg->cmd_width) {
81 case 8: mcfg |= BF_LCD_MCFG_CWIDTH_V(8BIT); break;
82 case 9: mcfg |= BF_LCD_MCFG_CWIDTH_V(16BIT_OR_9BIT); break;
83 case 16: mcfg |= BF_LCD_MCFG_CWIDTH_V(16BIT_OR_9BIT); break;
84 case 18: mcfg |= BF_LCD_MCFG_CWIDTH_V(18BIT); break;
85 case 24: mcfg |= BF_LCD_MCFG_CWIDTH_V(24BIT); break;
86 default: break;
87 }
88
89 if(cfg->cmd_width == 9)
90 mcfg_new |= BM_LCD_MCFG_NEW_CMD_9BIT;
91
92 switch(cfg->bus_width) {
93 case 8: mcfg_new |= BF_LCD_MCFG_NEW_DWIDTH_V(8BIT); break;
94 case 9: mcfg_new |= BF_LCD_MCFG_NEW_DWIDTH_V(9BIT); break;
95 case 16: mcfg_new |= BF_LCD_MCFG_NEW_DWIDTH_V(16BIT); break;
96 case 18: mcfg_new |= BF_LCD_MCFG_NEW_DWIDTH_V(18BIT); break;
97 case 24: mcfg_new |= BF_LCD_MCFG_NEW_DWIDTH_V(24BIT); break;
98 default: break;
99 }
100
101 if(lcd_tgt_config.use_serial)
102 mcfg_new |= jz_orf(LCD_MCFG_NEW, DTYPE_V(SERIAL), CTYPE_V(SERIAL));
103 else
104 mcfg_new |= jz_orf(LCD_MCFG_NEW, DTYPE_V(PARALLEL), CTYPE_V(PARALLEL));
105
106 jz_vwritef(mcfg_new, LCD_MCFG_NEW,
107 6800_MODE(lcd_tgt_config.use_6800_mode),
108 CSPLY(lcd_tgt_config.wr_polarity ? 0 : 1),
109 RSPLY(lcd_tgt_config.dc_polarity),
110 CLKPLY(lcd_tgt_config.clk_polarity));
111
112 /* Program the configuration. Note we cannot enable TE signal at
113 * this stage, because the panel will need to be configured first.
114 */
115 jz_write(LCD_MCFG, mcfg);
116 jz_write(LCD_MCFG_NEW, mcfg_new);
117 jz_writef(LCD_MCTRL, NARROW_TE(0), TE_INV(0), NOT_USE_TE(1),
118 DCSI_SEL(0), MIPI_SLCD(0), FAST_MODE(1), GATE_MASK(0),
119 DMA_MODE(1), DMA_START(0), DMA_TX_EN(0));
120 jz_writef(LCD_WTIME, DHTIME(0), DLTIME(0), CHTIME(0), CLTIME(0));
121 jz_writef(LCD_TASH, TAH(0), TAS(0));
122 jz_write(LCD_SMWT, 0);
123
124 /* DMA settings */
125 jz_writef(LCD_CTRL, BURST_V(64WORD),
126 EOFM(1), SOFM(0), IFUM(0), QDM(0),
127 BEDN(0), PEDN(0), ENABLE(0));
128 jz_write(LCD_DAH, LCD_WIDTH);
129 jz_write(LCD_DAV, LCD_HEIGHT);
130}
131
132static void lcd_fbcopy_dma_cb(int evt);
133
134static void lcd_init_descriptors(const struct lcd_tgt_config* cfg)
135{
136 struct lcd_dma_desc* desc = &lcd_dma_desc[0];
137 int cmdsize = cfg->dma_wr_cmd_size / 4;
138
139 /* Set up the command descriptor */
140 desc[0].da = PHYSADDR(&desc[1]);
141 desc[0].sa = PHYSADDR(cfg->dma_wr_cmd_buf);
142 desc[0].fid = 0xc0;
143 desc[0].cmd = LCD_DMA_CMD_COMMAND | cmdsize;
144 desc[0].osz = 0;
145 desc[0].pw = 0;
146 desc[0].fsz = 0;
147 switch(cfg->cmd_width) {
148 case 8: desc[0].cnt = 4*cmdsize; break;
149 case 9:
150 case 16: desc[0].cnt = 2*cmdsize; break;
151 case 18:
152 case 24: desc[0].cnt = cmdsize; break;
153 default: break;
154 }
155
156 /* Set up the frame descriptor */
157 desc[1].da = PHYSADDR(&desc[0]);
158 desc[1].sa = PHYSADDR(shadowfb);
159 desc[1].fid = 0xf0;
160 desc[1].cmd = LCD_DMA_CMD_EOFINT | LCD_DMA_CMD_FRM_EN |
161 (LCD_WIDTH * LCD_HEIGHT * sizeof(fb_data) / 4);
162 desc[1].osz = 0;
163 desc[1].pw = 0;
164 desc[1].fsz = (LCD_WIDTH - 1) | ((LCD_HEIGHT - 1) << 12);
165#if LCD_DEPTH == 16
166 desc[1].cnt = LCD_DMA_CNT_BPP_16BIT;
167#elif LCD_DEPTH == 24
168 desc[1].cnt = LCD_DMA_CNT_BPP_18BIT_OR_24BIT;
169#else
170# error "unsupported LCD bit depth"
171#endif
172
173 /* Commit LCD DMA descriptors */
174 commit_dcache_range(&desc[0], 2*sizeof(struct lcd_dma_desc));
175
176 /* Set fbcopy channel callback */
177 dma_set_callback(DMA_CHANNEL_FBCOPY, lcd_fbcopy_dma_cb);
178}
179
180static void lcd_fbcopy_dma_cb(int evt)
181{
182 (void)evt;
183 fbcopy_done = 1;
184}
185
186static void lcd_fbcopy_dma_run(dma_desc* d)
187{
188 if(lcd_panic_mode) {
189 /* Can't use DMA if interrupts are off, so just do a memcpy().
190 * Doesn't need to be efficient, since AFAIK the panic screen is
191 * the only place that can update the LCD with interrupts disabled. */
192 memcpy(shadowfb, FBADDR(0, 0), LCD_WIDTH*LCD_HEIGHT*sizeof(fb_data));
193 commit_dcache();
194 return;
195 }
196
197 commit_dcache_range(d, sizeof(struct dma_desc));
198
199 /* Start the transfer */
200 fbcopy_done = 0;
201 REG_DMA_CHN_DA(DMA_CHANNEL_FBCOPY) = PHYSADDR(d);
202 jz_writef(DMA_CHN_CS(DMA_CHANNEL_FBCOPY), DES8(1), NDES(0));
203 jz_set(DMA_DB, 1 << DMA_CHANNEL_FBCOPY);
204 jz_writef(DMA_CHN_CS(DMA_CHANNEL_FBCOPY), CTE(1));
205
206 while(!fbcopy_done);
207}
208
209static void lcd_fbcopy_dma_full(void)
210{
211 dma_desc d;
212 d.cm = jz_orf(DMA_CHN_CM, SAI(1), DAI(1), RDIL(9),
213 SP_V(32BIT), DP_V(32BIT), TSZ_V(AUTO),
214 STDE(0), TIE(1), LINK(0));
215 d.sa = PHYSADDR(FBADDR(0, 0));
216 d.ta = PHYSADDR(shadowfb);
217 d.tc = LCD_WIDTH * LCD_HEIGHT * sizeof(fb_data);
218 d.sd = 0;
219 d.rt = jz_orf(DMA_CHN_RT, TYPE_V(AUTO));
220 d.pad0 = 0;
221 d.pad1 = 0;
222 lcd_fbcopy_dma_run(&d);
223}
224
225/* NOTE: DMA stride mode can only transfer up to 255 blocks at once.
226 *
227 * - for LCD_STRIDEFORMAT == VERTICAL_STRIDE, keep width <= 255
228 * - for LCD_STRIDEFORMAT == HORIZONTAL_STRIDE, keep height <= 255
229 */
230static void lcd_fbcopy_dma_partial1(int x, int y, int width, int height)
231{
232 int stride = STRIDE_MAIN(LCD_WIDTH - width, LCD_HEIGHT - height);
233
234 dma_desc d;
235 d.cm = jz_orf(DMA_CHN_CM, SAI(1), DAI(1), RDIL(9),
236 SP_V(32BIT), DP_V(32BIT), TSZ_V(AUTO),
237 STDE(stride ? 1 : 0), TIE(1), LINK(0));
238 d.sa = PHYSADDR(FBADDR(x, y));
239 d.ta = PHYSADDR(&shadowfb[STRIDE_MAIN(y * LCD_WIDTH + x,
240 x * LCD_HEIGHT + y)]);
241 d.rt = jz_orf(DMA_CHN_RT, TYPE_V(AUTO));
242 d.pad0 = 0;
243 d.pad1 = 0;
244
245 if(stride) {
246 stride *= sizeof(fb_data);
247 d.sd = (stride << 16) | stride;
248 d.tc = (STRIDE_MAIN(height, width) << 16) |
249 (STRIDE_MAIN(width, height) * sizeof(fb_data));
250 } else {
251 d.sd = 0;
252 d.tc = width * height * sizeof(fb_data);
253 }
254
255 lcd_fbcopy_dma_run(&d);
256}
257
258#if STRIDE_MAIN(LCD_HEIGHT, LCD_WIDTH) > 255
259static void lcd_fbcopy_dma_partial(int x, int y, int width, int height)
260{
261 do {
262 int count = MIN(STRIDE_MAIN(height, width), 255);
263
264 lcd_fbcopy_dma_partial1(x, y, STRIDE_MAIN(width, count),
265 STRIDE_MAIN(count, height));
266
267 STRIDE_MAIN(height, width) -= count;
268 STRIDE_MAIN(y, x) += count;
269 } while(STRIDE_MAIN(height, width) != 0);
270}
271#else
272# define lcd_fbcopy_dma_partial lcd_fbcopy_dma_partial1
273#endif
274
275static void lcd_dma_start(void)
276{
277 /* Set format conversion bit, seems necessary for DMA mode */
278 jz_writef(LCD_MCFG_NEW, FMT_CONV(1));
279
280 /* Program vsync configuration */
281 jz_writef(LCD_MCTRL, NARROW_TE(lcd_tgt_config.te_narrow),
282 TE_INV(lcd_tgt_config.te_polarity ? 0 : 1),
283 NOT_USE_TE(lcd_tgt_config.te_enable ? 0 : 1));
284
285 /* Begin DMA transfer. Need to start a dummy frame or else we will
286 * not be able to pass lcd_wait_frame() at the first lcd_update(). */
287 jz_write(LCD_STATE, 0);
288 jz_write(LCD_DA, PHYSADDR(&lcd_dma_desc[0]));
289 jz_writef(LCD_MCTRL, DMA_MODE(1), DMA_START(1), DMA_TX_EN(1));
290 jz_writef(LCD_CTRL, ENABLE(1));
291}
292
293static void lcd_dma_stop(void)
294{
295 /* Stop the DMA transfer */
296 jz_writef(LCD_CTRL, ENABLE(0));
297 jz_writef(LCD_MCTRL, DMA_TX_EN(0));
298
299 /* Wait for disable to take effect */
300 while(jz_readf(LCD_STATE, QD) == 0);
301 jz_writef(LCD_STATE, QD(0));
302
303 /* Clear format conversion bit, disable vsync */
304 jz_writef(LCD_MCFG_NEW, FMT_CONV(0));
305 jz_writef(LCD_MCTRL, NARROW_TE(0), TE_INV(0), NOT_USE_TE(1));
306}
307
308static bool lcd_wait_frame(void)
309{
310 /* Bail out if DMA is not enabled */
311 int irq = disable_irq_save();
312 int bit = jz_readf(LCD_CTRL, ENABLE);
313 restore_irq(irq);
314 if(!bit)
315 return false;
316
317 /* Usual case -- wait for EOF, wait for FIFO to drain, clear EOF */
318 while(jz_readf(LCD_STATE, EOF) == 0);
319 while(jz_readf(LCD_MSTATE, BUSY));
320 jz_writef(LCD_STATE, EOF(0));
321 return true;
322}
323
324static void lcd_send(uint32_t d)
325{
326 while(jz_readf(LCD_MSTATE, BUSY));
327 REG_LCD_MDATA = d;
328}
329
330void lcd_set_clock(x1000_clk_t clk, uint32_t freq)
331{
332 uint32_t in_freq = clk_get(clk);
333 uint32_t div = clk_calc_div(in_freq, freq);
334
335 jz_writef(CPM_LPCDR, CE(1), CLKDIV(div - 1),
336 CLKSRC(clk == X1000_CLK_MPLL ? 1 : 0));
337 while(jz_readf(CPM_LPCDR, BUSY));
338 jz_writef(CPM_LPCDR, CE(0));
339}
340
341void lcd_exec_commands(const uint32_t* cmdseq)
342{
343 while(*cmdseq != LCD_INSTR_END) {
344 uint32_t instr = *cmdseq++;
345 uint32_t d = 0;
346 switch(instr) {
347 case LCD_INSTR_CMD:
348 d = jz_orf(LCD_MDATA, TYPE_V(CMD));
349 /* fallthrough */
350
351 case LCD_INSTR_DAT:
352 d |= *cmdseq++;
353 lcd_send(d);
354 break;
355
356 case LCD_INSTR_UDELAY:
357 udelay(*cmdseq++);
358 break;
359
360 default:
361 break;
362 }
363 }
364}
365
366void lcd_init_device(void)
367{
368 jz_writef(CPM_CLKGR, LCD(0));
369
370 lcd_init_controller(&lcd_tgt_config);
371 lcd_init_descriptors(&lcd_tgt_config);
372
373 lcd_tgt_enable(true);
374
375 lcd_dma_start();
376}
377
378#ifdef HAVE_LCD_SHUTDOWN
379void lcd_shutdown(void)
380{
381 if(lcd_sleeping)
382 lcd_tgt_sleep(false);
383 else if(jz_readf(LCD_CTRL, ENABLE))
384 lcd_dma_stop();
385
386 lcd_tgt_enable(false);
387 jz_writef(CPM_CLKGR, LCD(1));
388}
389#endif
390
391#if defined(HAVE_LCD_ENABLE) || defined(HAVE_LCD_SLEEP)
392bool lcd_active(void)
393{
394 return jz_readf(LCD_CTRL, ENABLE);
395}
396
397void lcd_enable(bool en)
398{
399 /* Must disable IRQs to turn off the running LCD */
400 int irq = disable_irq_save();
401 int bit = jz_readf(LCD_CTRL, ENABLE);
402 if(bit && !en)
403 lcd_dma_stop();
404 restore_irq(irq);
405
406 /* Deal with sleep mode */
407#ifdef LCD_X1000_FASTSLEEP
408 if(bit && !en) {
409 lcd_tgt_sleep(true);
410 lcd_sleeping = true;
411 } else
412#endif
413 if(!bit && en && lcd_sleeping) {
414 lcd_tgt_sleep(false);
415 lcd_sleeping = false;
416 }
417
418 /* Handle turning the LCD back on */
419 if(!bit && en)
420 lcd_dma_start();
421}
422#endif
423
424#if defined(HAVE_LCD_SLEEP)
425#if defined(LCD_X1000_FASTSLEEP)
426# error "Do not define HAVE_LCD_SLEEP if target has LCD_X1000_FASTSLEEP"
427#endif
428
429void lcd_sleep(void)
430{
431 if(!lcd_sleeping) {
432 lcd_enable(false);
433 lcd_tgt_sleep(true);
434 lcd_sleeping = true;
435 }
436}
437#endif
438
439void lcd_update(void)
440{
441 if(!lcd_wait_frame())
442 return;
443
444 commit_dcache();
445 lcd_fbcopy_dma_full();
446 jz_writef(LCD_MCTRL, DMA_START(1), DMA_MODE(1));
447}
448
449void lcd_update_rect(int x, int y, int width, int height)
450{
451 /* Clamp the coordinates */
452 if(x < 0) {
453 width += x;
454 x = 0;
455 }
456
457 if(y < 0) {
458 height += y;
459 y = 0;
460 }
461
462 if(width > LCD_WIDTH - x)
463 width = LCD_WIDTH - x;
464
465 if(height > LCD_HEIGHT - y)
466 height = LCD_HEIGHT - y;
467
468 if(width < 0 || height < 0)
469 return;
470
471 if(!lcd_wait_frame())
472 return;
473
474 commit_dcache();
475 lcd_fbcopy_dma_partial(x, y, width, height);
476 jz_writef(LCD_MCTRL, DMA_START(1), DMA_MODE(1));
477}