diff options
Diffstat (limited to 'firmware/target/mips/ingenic_x1000/lcd-x1000.c')
-rw-r--r-- | firmware/target/mips/ingenic_x1000/lcd-x1000.c | 477 |
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 | |||
42 | struct 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 | */ | ||
60 | static struct lcd_dma_desc lcd_dma_desc[2]; | ||
61 | |||
62 | /* Shadow copy of main framebuffer, needed to avoid tearing */ | ||
63 | static fb_data shadowfb[LCD_HEIGHT*LCD_WIDTH] __attribute__((aligned(64))); | ||
64 | |||
65 | /* Signals DMA copy to shadow FB is done */ | ||
66 | static volatile int fbcopy_done; | ||
67 | |||
68 | /* True if we're in sleep mode */ | ||
69 | static 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 | |||
75 | static 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 | |||
132 | static void lcd_fbcopy_dma_cb(int evt); | ||
133 | |||
134 | static 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 | |||
180 | static void lcd_fbcopy_dma_cb(int evt) | ||
181 | { | ||
182 | (void)evt; | ||
183 | fbcopy_done = 1; | ||
184 | } | ||
185 | |||
186 | static 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 | |||
209 | static 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 | */ | ||
230 | static 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 | ||
259 | static 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 | |||
275 | static 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 | |||
293 | static 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 | |||
308 | static 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 | |||
324 | static void lcd_send(uint32_t d) | ||
325 | { | ||
326 | while(jz_readf(LCD_MSTATE, BUSY)); | ||
327 | REG_LCD_MDATA = d; | ||
328 | } | ||
329 | |||
330 | void 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 | |||
341 | void 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 | |||
366 | void 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 | ||
379 | void 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) | ||
392 | bool lcd_active(void) | ||
393 | { | ||
394 | return jz_readf(LCD_CTRL, ENABLE); | ||
395 | } | ||
396 | |||
397 | void 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 | |||
429 | void 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 | |||
439 | void 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 | |||
449 | void 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 | } | ||