altos/draw: Add a reasonable API for drawing, add lines.
[fw/altos] / src / drivers / ao_vga.c
1 /*
2  * Copyright © 2016 Keith Packard <keithp@keithp.com>
3  *
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.
8  *
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.
13  */
14
15 #include "ao.h"
16 #include "ao_vga.h"
17
18 /* VGA output from the SPI port */
19
20 /* GRF formula for 640x480 yields a pixel clock very close to 24MHz. Pad by
21  * three scanlines to hit exactly that value
22  */
23
24 #define HACTIVE         640
25 #define HSYNC_START     656
26 #define HSYNC_END       720
27 #define HTOTAL          800
28
29 #define VACTIVE         480
30 #define VSYNC_START     481
31 #define VSYNC_END       484
32 #define VTOTAL          500
33
34 /*
35  * The horizontal counter is set so that the end of hsync is reached
36  * at the maximum counter value. That means that the hblank interval
37  * is offset by HSYNC_END. We send 16 bits of zeros (which looks like
38  * 32 pixels), so the start is offset by this much
39  */
40
41 #define HSYNC           (HSYNC_END - HSYNC_START)
42 #define HBLANK_END      (HTOTAL - HSYNC_END)
43 #define HBLANK_START    (HBLANK_END + HACTIVE + 32)
44
45 /*
46  * The vertical counter is set so that the end of vsync is reached at
47  * the maximum counter value.  That means that the vblank interval is
48  * offset by VSYNC_END. We send a blank line at the start of the
49  * frame, so each of these is off by one
50  */
51 #define VSYNC           (VSYNC_END - VSYNC_START)
52 #define VBLANK_END      (VTOTAL - VSYNC_END - 1)
53 #define VBLANK_START    (VBLANK_END + VACTIVE + 1)
54
55 #define WIDTH_BYTES     (AO_VGA_WIDTH >> 3)
56 #define SCANOUT         ((WIDTH_BYTES+2) >> 1)
57
58 uint32_t        ao_vga_fb_all[AO_VGA_STRIDE * (AO_VGA_HEIGHT + AO_VGA_VPAD)];
59
60 const struct ao_bitmap ao_vga_bitmap = {
61         .base = ao_vga_fb,
62         .stride = AO_VGA_STRIDE,
63         .width = AO_VGA_WIDTH,
64         .height = AO_VGA_HEIGHT
65 };
66
67 static uint32_t *scanline;
68
69 #define DMA_INDEX       STM_DMA_INDEX(STM_DMA_CHANNEL_SPI1_TX)
70
71 static int      line;
72 static int      vblank;
73
74 #define DMA_CCR(en)     ((0 << STM_DMA_CCR_MEM2MEM) |                   \
75                          (STM_DMA_CCR_PL_VERY_HIGH << STM_DMA_CCR_PL) | \
76                          (STM_DMA_CCR_MSIZE_16 << STM_DMA_CCR_MSIZE) |  \
77                          (STM_DMA_CCR_PSIZE_16 << STM_DMA_CCR_PSIZE) |  \
78                          (1 << STM_DMA_CCR_MINC) |                      \
79                          (0 << STM_DMA_CCR_PINC) |                      \
80                          (0 << STM_DMA_CCR_CIRC) |                      \
81                          (STM_DMA_CCR_DIR_MEM_TO_PER << STM_DMA_CCR_DIR) | \
82                          (0 << STM_DMA_CCR_TCIE) |                      \
83                          (en << STM_DMA_CCR_EN))
84
85
86 void stm_tim2_isr(void)
87 {
88         if (!vblank) {
89                 /* Disable */
90                 stm_dma.channel[DMA_INDEX].ccr = DMA_CCR(0);
91                 /* Reset DMA engine for the next scanline */
92                 stm_dma.channel[DMA_INDEX].cmar = scanline;
93                 stm_dma.channel[DMA_INDEX].cndtr = SCANOUT;
94                 /* Enable */
95                 stm_dma.channel[DMA_INDEX].ccr = DMA_CCR(1);
96         }
97         stm_tim2.sr = ~(1 << STM_TIM234_SR_CC2IF);
98         line = stm_tim3.cnt;
99
100         if (VBLANK_END <= line && line < VBLANK_START) {
101                 vblank = 0;
102                 if (((line - VBLANK_END) & 1))
103                         scanline += AO_VGA_STRIDE;
104         } else if (!vblank) {
105                 scanline = ao_vga_fb_all;
106                 vblank = 1;
107         }
108 }
109
110
111 void
112 ao_vga_init(void)
113 {
114         /* Initialize spi1 using PB5 for output */
115         stm_rcc.ahbenr |= (1 << STM_RCC_AHBENR_GPIOBEN);
116
117         stm_ospeedr_set(&stm_gpiob, 5, STM_OSPEEDR_40MHz);
118         stm_afr_set(&stm_gpiob, 5, STM_AFR_AF5);
119
120         /* turn on SPI */
121         stm_rcc.apb2enr |= (1 << STM_RCC_APB2ENR_SPI1EN);
122
123         stm_spi1.cr1 = ((1 << STM_SPI_CR1_BIDIMODE) |           /* Two wire mode */
124                         (1 << STM_SPI_CR1_BIDIOE) |
125                         (0 << STM_SPI_CR1_CRCEN) |              /* CRC disabled */
126                         (0 << STM_SPI_CR1_CRCNEXT) |
127                         (1 << STM_SPI_CR1_DFF) |
128                         (0 << STM_SPI_CR1_RXONLY) |
129                         (1 << STM_SPI_CR1_SSM) |                /* Software SS handling */
130                         (1 << STM_SPI_CR1_SSI) |                /*  ... */
131                         (1 << STM_SPI_CR1_LSBFIRST) |           /* Little endian */
132                         (1 << STM_SPI_CR1_SPE) |                /* Enable SPI unit */
133                         (0 << STM_SPI_CR1_BR) |                 /* baud rate to pclk/2 */
134                         (1 << STM_SPI_CR1_MSTR) |
135                         (0 << STM_SPI_CR1_CPOL) |               /* Format 0 */
136                         (0 << STM_SPI_CR1_CPHA));
137         stm_spi1.cr2 = ((0 << STM_SPI_CR2_TXEIE) |
138                         (0 << STM_SPI_CR2_RXNEIE) |
139                         (0 << STM_SPI_CR2_ERRIE) |
140                         (0 << STM_SPI_CR2_SSOE) |
141                         (1 << STM_SPI_CR2_TXDMAEN) |
142                         (0 << STM_SPI_CR2_RXDMAEN));
143
144         (void) stm_spi1.dr;
145         (void) stm_spi1.sr;
146
147         /* Grab the DMA channel for SPI1 MOSI */
148         stm_dma.channel[DMA_INDEX].cpar = &stm_spi1.dr;
149         stm_dma.channel[DMA_INDEX].cmar = ao_vga_fb;
150
151         /*
152          * Hsync Configuration
153          */
154         /* Turn on timer 2 */
155         stm_rcc.apb1enr |= (1 << STM_RCC_APB1ENR_TIM2EN);
156
157         /* tim2 runs at full speed */
158         stm_tim2.psc = 0;
159
160         /* Disable channels while modifying */
161         stm_tim2.ccer = 0;
162
163         /* Channel 1 hsync PWM values */
164         stm_tim2.ccr1 = HSYNC;
165
166         /* Channel 2 trigger scanout */
167         /* wait for the time to start scanout */
168         stm_tim2.ccr2 = HBLANK_END - 10;
169
170         /* Configure channel 1 to output on the pin and
171          * channel 2 to to set the trigger for the vsync timer
172          */
173         stm_tim2.ccmr1 = ((0 << STM_TIM234_CCMR1_OC2CE) |
174                           (STM_TIM234_CCMR1_OC2M_SET_HIGH_ON_MATCH << STM_TIM234_CCMR1_OC2M)  |
175                           (1 << STM_TIM234_CCMR1_OC2PE) |
176                           (0 << STM_TIM234_CCMR1_OC2FE) |
177
178                           (0 << STM_TIM234_CCMR1_OC1CE) |
179                           (STM_TIM234_CCMR1_OC1M_PWM_MODE_1 << STM_TIM234_CCMR1_OC1M)  |
180                           (1 << STM_TIM234_CCMR1_OC1PE) |
181                           (0 << STM_TIM234_CCMR1_OC1FE) |
182                           (STM_TIM234_CCMR1_CC1S_OUTPUT << STM_TIM234_CCMR1_CC1S));
183
184         /* One scanline */
185         stm_tim2.arr = HTOTAL;
186
187         stm_tim2.cnt = 0;
188
189         /* Update the register contents */
190         stm_tim2.egr |= (1 << STM_TIM234_EGR_UG);
191
192         /* Enable the timer */
193
194         /* Enable the output */
195         stm_tim2.ccer = ((0 << STM_TIM234_CCER_CC1NP) |
196                          (STM_TIM234_CCER_CC1P_ACTIVE_LOW << STM_TIM234_CCER_CC1P) |
197                          (1 << STM_TIM234_CCER_CC1E));
198
199         stm_tim2.cr2 = ((0 << STM_TIM234_CR2_TI1S) |
200                         (STM_TIM234_CR2_MMS_UPDATE << STM_TIM234_CR2_MMS) |
201                         (0 << STM_TIM234_CR2_CCDS));
202
203         /* hsync is not a slave timer */
204         stm_tim2.smcr = 0;
205
206         /* Send an interrupt on channel 2 */
207         stm_tim2.dier = ((1 << STM_TIM234_DIER_CC2IE));
208
209         stm_tim2.cr1 = ((STM_TIM234_CR1_CKD_1 << STM_TIM234_CR1_CKD) |
210                         (1 << STM_TIM234_CR1_ARPE) |
211                         (STM_TIM234_CR1_CMS_EDGE << STM_TIM234_CR1_CMS) |
212                         (STM_TIM234_CR1_DIR_UP << STM_TIM234_CR1_DIR) |
213                         (0 << STM_TIM234_CR1_OPM) |
214                         (1 << STM_TIM234_CR1_URS) |
215                         (0 << STM_TIM234_CR1_UDIS) |
216                         (0 << STM_TIM234_CR1_CEN));
217
218         /* Hsync is on PA5 which is Timer 2 CH1 output */
219         stm_rcc.ahbenr |= (1 << STM_RCC_AHBENR_GPIOAEN);
220         stm_ospeedr_set(&stm_gpioa, 5, STM_OSPEEDR_40MHz);
221         stm_afr_set(&stm_gpioa, 5, STM_AFR_AF1);
222
223         /*
224          * Vsync configuration
225          */
226
227         /* Turn on timer 3, slaved to timer 1 using ITR1 (table 61) */
228         stm_rcc.apb1enr |= (1 << STM_RCC_APB1ENR_TIM3EN);
229
230         /* No prescale */
231         stm_tim3.psc = 0;
232
233         /* Channel 1 vsync PWM values */
234         stm_tim3.ccr1 = VSYNC;
235
236         stm_tim3.ccmr1 = ((0 << STM_TIM234_CCMR1_OC2CE) |
237                           (0 << STM_TIM234_CCMR1_OC2PE) |
238                           (0 << STM_TIM234_CCMR1_OC2FE) |
239
240                           (0 << STM_TIM234_CCMR1_OC1CE) |
241                           (STM_TIM234_CCMR1_OC1M_PWM_MODE_1 << STM_TIM234_CCMR1_OC1M)  |
242                           (1 << STM_TIM234_CCMR1_OC1PE) |
243                           (0 << STM_TIM234_CCMR1_OC1FE) |
244                           (STM_TIM234_CCMR1_CC1S_OUTPUT << STM_TIM234_CCMR1_CC1S));
245
246         stm_tim3.arr = VTOTAL;
247         stm_tim3.cnt = 0;
248
249         /* Update the register contents */
250         stm_tim3.egr |= (1 << STM_TIM234_EGR_UG);
251
252         /* Enable the timer */
253
254         /* Enable the output */
255         stm_tim3.ccer = ((0 << STM_TIM234_CCER_CC1NP) |
256                          (STM_TIM234_CCER_CC1P_ACTIVE_LOW << STM_TIM234_CCER_CC1P) |
257                          (1 << STM_TIM234_CCER_CC1E));
258
259         stm_tim3.cr2 = ((0 << STM_TIM234_CR2_TI1S) |
260                         (STM_TIM234_CR2_MMS_UPDATE << STM_TIM234_CR2_MMS) |
261                         (0 << STM_TIM234_CR2_CCDS));
262
263         stm_tim3.smcr = 0;
264         stm_tim3.smcr = ((0 << STM_TIM234_SMCR_ETP) |
265                          (0 << STM_TIM234_SMCR_ECE) |
266                          (STM_TIM234_SMCR_ETPS_OFF << STM_TIM234_SMCR_ETPS) |
267                          (STM_TIM234_SMCR_ETF_NONE << STM_TIM234_SMCR_ETF) |
268                          (0 << STM_TIM234_SMCR_MSM) |
269                          (STM_TIM234_SMCR_TS_ITR1 << STM_TIM234_SMCR_TS) |
270                          (0 << STM_TIM234_SMCR_OCCS) |
271                          (STM_TIM234_SMCR_SMS_EXTERNAL_CLOCK << STM_TIM234_SMCR_SMS));
272
273         stm_tim3.dier = 0;
274
275         stm_tim3.cr1 = ((STM_TIM234_CR1_CKD_1 << STM_TIM234_CR1_CKD) |
276                         (1 << STM_TIM234_CR1_ARPE) |
277                         (STM_TIM234_CR1_CMS_EDGE << STM_TIM234_CR1_CMS) |
278                         (STM_TIM234_CR1_DIR_UP << STM_TIM234_CR1_DIR) |
279                         (0 << STM_TIM234_CR1_OPM) |
280                         (1 << STM_TIM234_CR1_URS) |
281                         (0 << STM_TIM234_CR1_UDIS) |
282                         (1 << STM_TIM234_CR1_CEN));
283
284         /* Vsync is on PB4 which is is Timer 3 CH1 output */
285         stm_rcc.ahbenr |= (1 << STM_RCC_AHBENR_GPIOBEN);
286         stm_ospeedr_set(&stm_gpiob, 4, STM_OSPEEDR_40MHz);
287         stm_afr_set(&stm_gpiob, 4, STM_AFR_AF2);
288
289         /* Enable the scanline interrupt */
290         stm_nvic_set_priority(STM_ISR_TIM2_POS, AO_STM_NVIC_NON_MASK_PRIORITY);
291         stm_nvic_set_enable(STM_ISR_TIM2_POS);
292 }
293
294 uint8_t enabled;
295
296 void
297 ao_vga_enable(int enable)
298 {
299         if (enable) {
300                 if (!enabled) {
301                         ++ao_task_minimize_latency;
302                         enabled = 1;
303                 }
304                 stm_tim2.cr1 |= (1 << STM_TIM234_CR1_CEN);
305         } else {
306                 if (enabled) {
307                         --ao_task_minimize_latency;
308                         enabled = 0;
309                 }
310                 stm_tim2.cr1 &= ~(1 << STM_TIM234_CR1_CEN);
311         }
312 }