2 * Copyright © 2016 Keith Packard <keithp@keithp.com>
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
18 /* VGA output from the SPI port */
21 long dot_clock; /* in Hz */
23 /* All timings are in pixels, with the first pixel out at 0,0 */
24 int hactive; /* active pixels */
25 int hsync_start; /* start of hsync pulse */
26 int hsync_end; /* end of hsync pulse */
27 int htotal; /* total h pixels */
29 int vactive; /* active scalines */
30 int vsync_start; /* start of vsync pulse */
31 int vsync_end; /* end of vsync pulse */
32 int vtotal; /* total scanlines */
35 const struct ao_modeline vga_640x480x60 = {
36 .dot_clock = 23856000, /* 23.86MHz dot, 29.82kHz line, 60.00Hz frame */
49 const struct ao_modeline vga_640x480x30 = {
50 .dot_clock = 120000000, /* 12.00MHz dot, 29.82kHz line, 30.00Hz frame */
63 #define mode vga_640x480x60
65 #define WIDTH_BYTES (AO_VGA_WIDTH >> 3)
66 #define SCANOUT ((WIDTH_BYTES+2) >> 1)
68 uint32_t ao_vga_fb[AO_VGA_STRIDE * AO_VGA_HEIGHT];
70 static uint32_t *scanline;
72 #define DMA_INDEX STM_DMA_INDEX(STM_DMA_CHANNEL_SPI1_TX)
77 #define DMA_CCR(en) ((0 << STM_DMA_CCR_MEM2MEM) | \
78 (STM_DMA_CCR_PL_VERY_HIGH << STM_DMA_CCR_PL) | \
79 (STM_DMA_CCR_MSIZE_16 << STM_DMA_CCR_MSIZE) | \
80 (STM_DMA_CCR_PSIZE_16 << STM_DMA_CCR_PSIZE) | \
81 (1 << STM_DMA_CCR_MINC) | \
82 (0 << STM_DMA_CCR_PINC) | \
83 (0 << STM_DMA_CCR_CIRC) | \
84 (STM_DMA_CCR_DIR_MEM_TO_PER << STM_DMA_CCR_DIR) | \
85 (0 << STM_DMA_CCR_TCIE) | \
86 (en << STM_DMA_CCR_EN))
90 void stm_tim2_isr(void)
94 stm_dma.channel[DMA_INDEX].ccr = DMA_CCR(0);
95 /* Reset DMA engine for the next scanline */
96 stm_dma.channel[DMA_INDEX].cmar = scanline;
97 stm_dma.channel[DMA_INDEX].cndtr = SCANOUT;
99 stm_dma.channel[DMA_INDEX].ccr = DMA_CCR(1);
101 stm_tim2.sr = ~(1 << STM_TIM234_SR_CC2IF);
103 if (vblank_off <= line && line < ((AO_VGA_HEIGHT-1) << 1) + vblank_off) {
105 if (((line - vblank_off) & 1) == 0)
106 scanline += AO_VGA_STRIDE;
109 // stm_systick_isr();
110 scanline = ao_vga_fb;
119 ao_solid(0x0, AO_ALLONES,
127 ao_vga_fb + 10 * AO_VGA_STRIDE,
135 ao_vga_fb + 220 * AO_VGA_STRIDE,
142 ao_vga_fb + 10 * AO_VGA_STRIDE,
149 ao_vga_fb + 220 * AO_VGA_STRIDE,
155 ao_text("Hello, Bdale!",
156 ao_vga_fb + 100 * AO_VGA_STRIDE,
166 ao_vga_fb + (240 - 7) * AO_VGA_STRIDE,
170 memset(ao_vga_fb + 120 * AO_VGA_STRIDE, '\0', WIDTH_BYTES);
176 /* Initialize spi1 using PB5 for output */
177 stm_rcc.ahbenr |= (1 << STM_RCC_AHBENR_GPIOBEN);
179 stm_ospeedr_set(&stm_gpiob, 5, STM_OSPEEDR_40MHz);
180 stm_afr_set(&stm_gpiob, 5, STM_AFR_AF5);
183 stm_rcc.apb2enr |= (1 << STM_RCC_APB2ENR_SPI1EN);
185 stm_spi1.cr1 = ((1 << STM_SPI_CR1_BIDIMODE) | /* Two wire mode */
186 (1 << STM_SPI_CR1_BIDIOE) |
187 (0 << STM_SPI_CR1_CRCEN) | /* CRC disabled */
188 (0 << STM_SPI_CR1_CRCNEXT) |
189 (1 << STM_SPI_CR1_DFF) |
190 (0 << STM_SPI_CR1_RXONLY) |
191 (1 << STM_SPI_CR1_SSM) | /* Software SS handling */
192 (1 << STM_SPI_CR1_SSI) | /* ... */
193 (1 << STM_SPI_CR1_LSBFIRST) | /* Little endian */
194 (1 << STM_SPI_CR1_SPE) | /* Enable SPI unit */
195 (0 << STM_SPI_CR1_BR) | /* baud rate to pclk/2 */
196 (1 << STM_SPI_CR1_MSTR) |
197 (0 << STM_SPI_CR1_CPOL) | /* Format 0 */
198 (0 << STM_SPI_CR1_CPHA));
199 stm_spi1.cr2 = ((0 << STM_SPI_CR2_TXEIE) |
200 (0 << STM_SPI_CR2_RXNEIE) |
201 (0 << STM_SPI_CR2_ERRIE) |
202 (0 << STM_SPI_CR2_SSOE) |
203 (1 << STM_SPI_CR2_TXDMAEN) |
204 (0 << STM_SPI_CR2_RXDMAEN));
209 /* Grab the DMA channel for SPI1 MOSI */
210 stm_dma.channel[DMA_INDEX].cpar = &stm_spi1.dr;
211 stm_dma.channel[DMA_INDEX].cmar = ao_vga_fb;
213 /* hclock on timer 2 */
215 /* Turn on timer 2 */
216 stm_rcc.apb1enr |= (1 << STM_RCC_APB1ENR_TIM2EN);
219 stm_rcc.ahbenr |= (1 << STM_RCC_AHBENR_GPIOAEN);
223 /* Disable channels while modifying */
226 /* Channel 1 hsync PWM values */
227 stm_tim2.ccr1 = mode.hsync_end - mode.hsync_start;
229 /* Channel 2 trigger scanout */
230 /* wait for the time to start scanout */
231 stm_tim2.ccr2 = mode.htotal - mode.hsync_end;
233 stm_tim2.ccmr1 = ((0 << STM_TIM234_CCMR1_OC2CE) |
234 (STM_TIM234_CCMR1_OC2M_SET_HIGH_ON_MATCH << STM_TIM234_CCMR1_OC2M) |
235 (1 << STM_TIM234_CCMR1_OC2PE) |
236 (0 << STM_TIM234_CCMR1_OC2FE) |
238 (0 << STM_TIM234_CCMR1_OC1CE) |
239 (STM_TIM234_CCMR1_OC1M_PWM_MODE_1 << STM_TIM234_CCMR1_OC1M) |
240 (1 << STM_TIM234_CCMR1_OC1PE) |
241 (0 << STM_TIM234_CCMR1_OC1FE) |
242 (STM_TIM234_CCMR1_CC1S_OUTPUT << STM_TIM234_CCMR1_CC1S));
244 stm_tim2.arr = mode.htotal;
247 /* Update the register contents */
248 stm_tim2.egr |= (1 << STM_TIM234_EGR_UG);
250 /* Enable the timer */
252 /* Enable the output */
253 stm_tim2.ccer = ((0 << STM_TIM234_CCER_CC1NP) |
254 (STM_TIM234_CCER_CC1P_ACTIVE_LOW << STM_TIM234_CCER_CC1P) |
255 (1 << STM_TIM234_CCER_CC1E));
257 stm_tim2.cr2 = ((0 << STM_TIM234_CR2_TI1S) |
258 (STM_TIM234_CR2_MMS_UPDATE << STM_TIM234_CR2_MMS) |
259 (0 << STM_TIM234_CR2_CCDS));
263 stm_tim2.dier = ((1 << STM_TIM234_DIER_CC2IE));
265 stm_tim2.cr1 = ((STM_TIM234_CR1_CKD_1 << STM_TIM234_CR1_CKD) |
266 (1 << STM_TIM234_CR1_ARPE) |
267 (STM_TIM234_CR1_CMS_EDGE << STM_TIM234_CR1_CMS) |
268 (STM_TIM234_CR1_DIR_UP << STM_TIM234_CR1_DIR) |
269 (0 << STM_TIM234_CR1_OPM) |
270 (1 << STM_TIM234_CR1_URS) |
271 (0 << STM_TIM234_CR1_UDIS) |
272 (0 << STM_TIM234_CR1_CEN));
276 /* PA5 is Timer 2 CH1 output */
277 stm_ospeedr_set(&stm_gpioa, 5, STM_OSPEEDR_40MHz);
278 stm_afr_set(&stm_gpioa, 5, STM_AFR_AF1);
280 /* Turn on timer 3, slaved to timer 1 using ITR1 (table 61) */
282 /* Use CH1 on PB6 (AF2) */
284 stm_rcc.apb1enr |= (1 << STM_RCC_APB1ENR_TIM3EN);
287 stm_rcc.ahbenr |= (1 << STM_RCC_AHBENR_GPIOBEN);
292 /* Channel 1 vsync PWM values */
293 stm_tim3.ccr1 = mode.vsync_end - mode.vsync_start;
294 stm_tim3.ccmr1 = ((0 << STM_TIM234_CCMR1_OC2CE) |
295 (0 << STM_TIM234_CCMR1_OC2PE) |
296 (0 << STM_TIM234_CCMR1_OC2FE) |
298 (0 << STM_TIM234_CCMR1_OC1CE) |
299 (STM_TIM234_CCMR1_OC1M_PWM_MODE_1 << STM_TIM234_CCMR1_OC1M) |
300 (1 << STM_TIM234_CCMR1_OC1PE) |
301 (0 << STM_TIM234_CCMR1_OC1FE) |
302 (STM_TIM234_CCMR1_CC1S_OUTPUT << STM_TIM234_CCMR1_CC1S));
304 stm_tim3.arr = mode.vtotal;
307 /* Update the register contents */
308 stm_tim3.egr |= (1 << STM_TIM234_EGR_UG);
310 /* Enable the timer */
312 /* Enable the output */
313 stm_tim3.ccer = ((0 << STM_TIM234_CCER_CC1NP) |
314 (STM_TIM234_CCER_CC1P_ACTIVE_LOW << STM_TIM234_CCER_CC1P) |
315 (1 << STM_TIM234_CCER_CC1E));
317 stm_tim3.cr2 = ((0 << STM_TIM234_CR2_TI1S) |
318 (STM_TIM234_CR2_MMS_UPDATE << STM_TIM234_CR2_MMS) |
319 (0 << STM_TIM234_CR2_CCDS));
322 stm_tim3.smcr = ((0 << STM_TIM234_SMCR_ETP) |
323 (0 << STM_TIM234_SMCR_ECE) |
324 (STM_TIM234_SMCR_ETPS_OFF << STM_TIM234_SMCR_ETPS) |
325 (STM_TIM234_SMCR_ETF_NONE << STM_TIM234_SMCR_ETF) |
326 (0 << STM_TIM234_SMCR_MSM) |
327 (STM_TIM234_SMCR_TS_ITR1 << STM_TIM234_SMCR_TS) |
328 (0 << STM_TIM234_SMCR_OCCS) |
329 (STM_TIM234_SMCR_SMS_EXTERNAL_CLOCK << STM_TIM234_SMCR_SMS));
333 stm_tim3.cr1 = ((STM_TIM234_CR1_CKD_1 << STM_TIM234_CR1_CKD) |
334 (1 << STM_TIM234_CR1_ARPE) |
335 (STM_TIM234_CR1_CMS_EDGE << STM_TIM234_CR1_CMS) |
336 (STM_TIM234_CR1_DIR_UP << STM_TIM234_CR1_DIR) |
337 (0 << STM_TIM234_CR1_OPM) |
338 (1 << STM_TIM234_CR1_URS) |
339 (0 << STM_TIM234_CR1_UDIS) |
340 (1 << STM_TIM234_CR1_CEN));
344 /* PB4 is Timer 3 CH1 output */
345 stm_ospeedr_set(&stm_gpiob, 4, STM_OSPEEDR_40MHz);
346 stm_afr_set(&stm_gpiob, 4, STM_AFR_AF2);
348 /* Enable the scanline interrupt */
349 stm_nvic_set_priority(STM_ISR_TIM2_POS, 0);
350 stm_nvic_set_enable(STM_ISR_TIM2_POS);
354 ao_vga_enable(int enable)
359 stm_tim2.cr1 |= (1 << STM_TIM234_CR1_CEN);
360 // stm_systick.csr &= ~(1 << STM_SYSTICK_CSR_ENABLE);
362 stm_tim2.cr1 &= ~(1 << STM_TIM234_CR1_CEN);
363 // stm_systick.csr |= (1 << STM_SYSTICK_CSR_ENABLE);