altos: Allow LCD segments to not be multiplexed across digits
[fw/altos] / src / stm / ao_lcd_stm.c
1 /*
2  * Copyright © 2012 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; version 2 of the License.
7  *
8  * This program is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11  * General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License along
14  * with this program; if not, write to the Free Software Foundation, Inc.,
15  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
16  */
17
18 #include <ao.h>
19 #include <ao_lcd_stm.h>
20
21 #ifndef LCD_DEBUG
22 #define LCD_DEBUG       0
23 #endif
24
25 struct ao_lcd_segment {
26         uint8_t reg;
27         uint8_t bit;
28 };
29
30 #define A       0
31 #define B       1
32 #define C       2
33 #define D       3
34 #define E       4
35
36 static struct stm_gpio *gpios[] = {
37         &stm_gpioa,
38         &stm_gpiob,
39         &stm_gpioc,
40         &stm_gpiod,
41         &stm_gpioe
42 };
43
44 static inline int ao_lcd_stm_seg_enabled(int seg) {
45         if (seg < 32)
46                 return (AO_LCD_STM_SEG_ENABLED_0 >> seg) & 1;
47         else
48                 return (AO_LCD_STM_SEG_ENABLED_1 >> (seg - 32)) & 1;
49 }
50
51 static inline int ao_lcd_stm_com_enabled(int com) {
52         return (AO_LCD_STM_COM_ENABLED >> com) & 1;
53 }
54
55 #define AO_LCD_STM_GPIOA_SEGS_0 (               \
56                 (1 << 0) |                      \
57                 (1 << 1) |                      \
58                 (1 << 2) |                      \
59                 (1 << 3) |                      \
60                 (1 << 4) |                      \
61                 (1 << 17))
62
63 #define AO_LCD_STM_GPIOA_SEGS_1 0
64
65 #define AO_LCD_STM_USES_GPIOA   (!!((AO_LCD_STM_GPIOA_SEGS_0 & AO_LCD_STM_SEG_ENABLED_0) | \
66                                     (AO_LCD_STM_GPIOA_SEGS_1 & AO_LCD_STM_SEG_ENABLED_1)))
67
68
69 #define AO_LCD_STM_GPIOB_SEGS_0 (               \
70                 (1 << 5) |                      \
71                 (1 << 6) |                      \
72                 (1 << 7) |                      \
73                 (1 << 8) |                      \
74                 (1 << 9) |                      \
75                 (1 << 10) |                     \
76                 (1 << 11) |                     \
77                 (1 << 12) |                     \
78                 (1 << 13) |                     \
79                 (1 << 14) |                     \
80                 (1 << 15) |                     \
81                 (1 << 16))
82
83 #define AO_LCD_STM_GPIOB_SEGS_1 0
84
85 #if AO_LCD_28_ON_C
86
87 #define AO_LCD_STM_GPIOC_28_SEGS        (       \
88                 (1 << 28) |                     \
89                 (1 << 29) |                     \
90                 (1 << 30))
91
92 #define AO_LCD_STM_GPIOD_28_SEGS        (       \
93                 (1 << 31))
94
95 #else
96 #define AO_LCD_STM_GPIOC_28_SEGS        0
97
98 #define AO_LCD_STM_GPIOD_28_SEGS        (       \
99                 (1 << 28) |                     \
100                 (1 << 29) |                     \
101                 (1 << 30) |                     \
102                 (1 << 31))
103 #endif
104
105 #define AO_LCD_STM_GPIOC_SEGS_0 (               \
106                 (1 << 18) |                     \
107                 (1 << 19) |                     \
108                 (1 << 20) |                     \
109                 (1 << 21) |                     \
110                 (1 << 22) |                     \
111                 (1 << 23) |                     \
112                 (1 << 24) |                     \
113                 (1 << 25) |                     \
114                 (1 << 26) |                     \
115                 (1 << 27) |                     \
116                 AO_LCD_STM_GPIOC_28_SEGS)
117
118 #define AO_LCD_STM_GPIOC_SEGS_1 (               \
119                 (1 << (40 - 32)) |              \
120                 (1 << (41 - 32)) |              \
121                 (1 << (42 - 32)))
122
123 #define AO_LCD_STM_GPIOD_SEGS_0 (               \
124                 AO_LCD_STM_GPIOD_28_SEGS)
125
126 #define AO_LCD_STM_GPIOD_SEGS_1 (               \
127                 (1 << (32 - 32)) |              \
128                 (1 << (33 - 32)) |              \
129                 (1 << (34 - 32)) |              \
130                 (1 << (35 - 32)) |              \
131                 (1 << (43 - 32)))
132
133 #define AO_LCD_STM_GPIOE_SEGS_0 0
134
135 #define AO_LCD_STM_GPIOE_SEGS_1 (               \
136                 (1 << (36 - 32)) |              \
137                 (1 << (37 - 32)) |              \
138                 (1 << (38 - 32)) |              \
139                 (1 << (39 - 32)))
140
141 #define AO_LCD_STM_USES_GPIOA   (!!((AO_LCD_STM_GPIOA_SEGS_0 & AO_LCD_STM_SEG_ENABLED_0) | \
142                                     (AO_LCD_STM_GPIOA_SEGS_1 & AO_LCD_STM_SEG_ENABLED_1)))
143
144 #define AO_LCD_STM_USES_GPIOB   (!!((AO_LCD_STM_GPIOB_SEGS_0 & AO_LCD_STM_SEG_ENABLED_0) | \
145                                     (AO_LCD_STM_GPIOB_SEGS_1 & AO_LCD_STM_SEG_ENABLED_1)))
146
147
148 #define AO_LCD_STM_USES_GPIOC   (!!((AO_LCD_STM_GPIOC_SEGS_0 & AO_LCD_STM_SEG_ENABLED_0) | \
149                                     (AO_LCD_STM_GPIOC_SEGS_1 & AO_LCD_STM_SEG_ENABLED_1)))
150
151
152 #define AO_LCD_STM_USES_GPIOD   (!!((AO_LCD_STM_GPIOD_SEGS_0 & AO_LCD_STM_SEG_ENABLED_0) | \
153                                     (AO_LCD_STM_GPIOD_SEGS_1 & AO_LCD_STM_SEG_ENABLED_1)))
154
155 #define AO_LCD_STM_USES_GPIOE   (!!((AO_LCD_STM_GPIOE_SEGS_0 & AO_LCD_STM_SEG_ENABLED_0) | \
156                                     (AO_LCD_STM_GPIOE_SEGS_1 & AO_LCD_STM_SEG_ENABLED_1)))
157
158
159 static const struct ao_lcd_segment segs[] = {
160         { A, 1 },       /* 0 */
161         { A, 2 },
162         { A, 3 },
163         { A, 6 },
164                 
165         { A, 7 },       /* 4 */
166         { B, 0 },
167         { B, 1 },
168         { B, 3 },
169
170         { B, 4 },       /* 8 */
171         { B, 5 },
172         { B, 10 },
173         { B, 11 },
174
175         { B, 12 },      /* 12 */
176         { B, 13 },
177         { B, 14 },
178         { B, 15 },
179
180         { B, 8 },       /* 16 */
181         { A, 15 },
182         { C, 0 },
183         { C, 1 },
184
185         { C, 2 },       /* 20 */
186         { C, 3 },
187         { C, 4 },
188         { C, 5 },
189
190         { C, 6 },       /* 24 */
191         { C, 7 },
192         { C, 8 },
193         { C, 9 },
194
195 #if AO_LCD_28_ON_C
196         { C, 10 },      /* 28 */
197         { C, 11 },
198         { C, 12 },
199         { D, 2 },
200 #else
201         { D, 8 },       /* 28 */
202         { D, 9 },
203         { D, 10 },
204         { D, 11 },
205 #endif
206         { D, 12 },      /* 32 */
207         { D, 13 },
208         { D, 14 },
209         { D, 15 },
210                 
211         { E, 0 },       /* 36 */
212         { E, 1 },
213         { E, 2 },
214         { E, 3 },
215
216         { C, 10 },      /* 40 */
217         { C, 11 },
218         { C, 12 },
219         { D, 2 },
220 };
221
222 static const struct ao_lcd_segment coms[] = {
223         { A, 8 },       /* 0 */
224         { A, 9 },       /* 1 */
225         { A, 10 },      /* 2 */
226         { B, 9 },       /* 3 */
227         { C, 10 },      /* 4 */
228         { C, 11 },      /* 5 */
229         { C, 12 },      /* 6 */
230 };
231
232 #define NSEG    (sizeof segs/sizeof segs[0])
233 #define NCOM    (sizeof coms/sizeof coms[0])
234
235 static uint8_t  ao_lcd_update_active;
236
237 void
238 stm_lcd_isr(void)
239 {
240         if (stm_lcd.sr & (1 << STM_LCD_SR_UDD)) {
241                 stm_lcd.clr = (1 << STM_LCD_CLR_UDDC);
242                 if (ao_lcd_update_active) {
243                         ao_lcd_update_active = 0;
244                         ao_wakeup(&ao_lcd_update_active);
245                 }
246         }
247 }
248
249
250 static void
251 ao_lcd_stm_fcr_sync(void)
252 {
253         while ((stm_lcd.sr & (1 << STM_LCD_SR_FCRSF)) == 0)
254                 asm("nop");
255 }
256
257 void
258 ao_lcd_flush(void)
259 {
260         ao_arch_block_interrupts();
261         ao_lcd_update_active = 1;
262         stm_lcd.sr = (1 << STM_LCD_SR_UDR);
263         while (ao_lcd_update_active)
264                 ao_sleep(&ao_lcd_update_active);
265         ao_arch_release_interrupts();
266 }
267
268 void
269 ao_lcd_clear(void)
270 {
271         uint8_t i;
272
273         for (i = 0; i < sizeof (stm_lcd.ram) / 4; i++)
274                 stm_lcd.ram[i] = 0;
275         ao_lcd_flush();
276 }
277
278 #ifdef AO_SEGMENT_MAP
279 #if AO_LCD_PER_DIGIT
280 static const struct ao_lcd_map {
281         uint8_t com, seg;
282 } ao_lcd_map[AO_LCD_DIGITS * AO_LCD_SEGMENTS] = AO_SEGMENT_MAP;
283 #else
284 static const uint8_t ao_lcd_map[] = AO_SEGMENT_MAP;
285 #endif
286 #endif
287
288 void
289 ao_lcd_set(uint8_t digit, uint8_t segment, uint8_t value)
290 {
291         uint8_t n;
292         uint8_t com, seg;
293
294 #ifdef AO_SEGMENT_MAP
295 #if AO_LCD_PER_DIGIT
296         n = digit * AO_LCD_SEGMENTS + segment;
297         com = ao_lcd_map[n].com;
298         seg = ao_lcd_map[n].seg;
299 #else
300         com = digit;
301         seg = ao_lcd_map[segment];
302 #endif
303 #else
304         com = digit;
305         seg = segment;
306 #endif
307         if (com >= NCOM)
308                 com = NCOM-1;
309         if (seg >= NSEG)
310                 seg = NSEG-1;
311
312 #if LCD_DEBUG
313         printf ("digit %d segment %d -> com %d seg %d\n", digit, segment, com, seg);
314 #endif
315         n = (seg >> 5) & 1;
316         if (value)
317                 stm_lcd.ram[com * 2 + n] |= (1 << (seg & 0x1f));
318         else
319                 stm_lcd.ram[com * 2 + n] &= ~(1 << (seg & 0x1f));
320 }
321
322 #if LCD_DEBUG
323 static void
324 ao_lcd_stm_seg_set(void)
325 {
326         int     com, seg, val;
327         int     n, bit;
328         ao_cmd_decimal();
329         com = ao_cmd_lex_i;
330         ao_cmd_decimal();
331         seg = ao_cmd_lex_u32;
332         ao_cmd_decimal();
333         val = ao_cmd_lex_i;
334         printf ("com: %d seg: %d val: %d\n", com, seg, val);
335         ao_lcd_set(com, seg, val);
336         ao_lcd_flush();
337 }
338
339 static const struct ao_cmds ao_lcd_stm_cmds[] = {
340         { ao_lcd_stm_seg_set,   "s <com> <seg> <value>\0Set LCD segment" },
341         { ao_lcd_clear,         "x\0Clear LCD" },
342         { 0, NULL },
343 };
344 #endif
345
346 void
347 ao_lcd_stm_init(void)
348 {
349         int s, c;
350         int r;
351         uint32_t        csr;
352
353         stm_rcc.ahbenr |= ((AO_LCD_STM_USES_GPIOA << STM_RCC_AHBENR_GPIOAEN) |
354                            (AO_LCD_STM_USES_GPIOB << STM_RCC_AHBENR_GPIOBEN) |
355                            (AO_LCD_STM_USES_GPIOC << STM_RCC_AHBENR_GPIOCEN) |
356                            (AO_LCD_STM_USES_GPIOD << STM_RCC_AHBENR_GPIODEN) |
357                            (AO_LCD_STM_USES_GPIOE << STM_RCC_AHBENR_GPIOEEN));
358
359         stm_rcc.apb1enr |= (1 << STM_RCC_APB1ENR_LCDEN);
360
361         /* Turn on the LSI clock */
362         if ((stm_rcc.csr & (1 << STM_RCC_CSR_LSIRDY)) == 0) {
363                 stm_rcc.csr |= (1 << STM_RCC_CSR_LSION);
364                 while ((stm_rcc.csr & (1 << STM_RCC_CSR_LSIRDY)) == 0)
365                         asm("nop");
366         }
367
368         /* Enable RTC clock config (required to change the RTC/LCD clock */
369
370         stm_pwr.cr |= (1 << STM_PWR_CR_DBP);
371
372         /* Configure the LCDCLK - use the LSI clock */
373
374         csr = stm_rcc.csr;
375         csr &= ~(STM_RCC_CSR_RTCSEL_MASK << STM_RCC_CSR_RTCSEL);
376         csr |= (STM_RCC_CSR_RTCSEL_LSI << STM_RCC_CSR_RTCSEL);
377         stm_rcc.csr = csr;
378
379         for (s = 0; s < NSEG; s++) {
380                 uint8_t reg = segs[s].reg;
381                 uint8_t bit = segs[s].bit;
382                         
383                 if (ao_lcd_stm_seg_enabled(s)) {
384                         stm_moder_set(gpios[reg], bit, STM_MODER_ALTERNATE);
385                         stm_afr_set(gpios[reg], bit, STM_AFR_AF11);
386                 }
387         }
388
389         for (c = 0; c < NCOM; c++) {
390                 uint8_t reg = coms[c].reg;
391                 uint8_t bit = coms[c].bit;
392                         
393                 if (ao_lcd_stm_com_enabled(c)) {
394                         stm_moder_set(gpios[reg], bit, STM_MODER_ALTERNATE);
395                         stm_afr_set(gpios[reg], bit, STM_AFR_AF11);
396                 }
397         }
398
399         /* Disable the LCD */
400         stm_lcd.cr = 0;
401
402         /* duty cycle 1/3, radio 352, frame rate about 33Hz */
403         stm_lcd.fcr = ((STM_LCD_FCR_PS_8 << STM_LCD_FCR_PS) |
404                        (STM_LCD_FCR_DIV_20 << STM_LCD_FCR_DIV) |
405                        (7 << STM_LCD_FCR_CC) |
406                        (0 << STM_LCD_FCR_DEAD) |
407                        (1 << STM_LCD_FCR_PON) |
408                        (1 << STM_LCD_FCR_UDDIE) |
409                        (0 << STM_LCD_FCR_SOFIE) |
410                        (1 << STM_LCD_FCR_HD));
411
412         ao_lcd_stm_fcr_sync();
413
414         /* Program desired DUTY in LCD_CR */
415         /* Program desired BIAS in LCD_CR */
416         /* Enable mux seg */
417         /* Internal voltage source */
418         stm_lcd.cr = ((AO_LCD_DUTY << STM_LCD_CR_DUTY) |
419                       (STM_LCD_CR_BIAS_1_2 << STM_LCD_CR_BIAS) |
420                       (0 << STM_LCD_CR_VSEL) |
421                       (0 << STM_LCD_CR_MUX_SEG));
422
423         ao_lcd_stm_fcr_sync();
424
425         /* Enable the display (LCDEN bit in LCD_CR) */
426         stm_lcd.cr |= (1 << STM_LCD_CR_LCDEN);
427
428         while ((stm_lcd.sr & (1 << STM_LCD_SR_RDY)) == 0)
429                 asm("nop");
430
431         /* Load initial data into LCD_RAM and set the
432          * UDR bit in the LCD_SR register */
433
434         /* Program desired frame rate (PS and DIV bits in LCD_FCR) */
435
436         /* Program the contrast (CC bits in LCD_FCR) */
437
438         /* Program optional features (BLINK, BLINKF, PON, DEAD, HD) */
439
440         /* Program the required interrupts */
441         stm_nvic_set_enable(STM_ISR_LCD_POS);
442         stm_nvic_set_priority(STM_ISR_LCD_POS, AO_STM_NVIC_LOW_PRIORITY);
443
444         /* All done */
445 #if LCD_DEBUG
446         ao_cmd_register(ao_lcd_stm_cmds);
447 #endif
448 }