Add LCD device driver to STM32L port
[fw/altos] / src / stm / ao_lcd_stm.c
diff --git a/src/stm/ao_lcd_stm.c b/src/stm/ao_lcd_stm.c
new file mode 100644 (file)
index 0000000..2d2fa9b
--- /dev/null
@@ -0,0 +1,393 @@
+/*
+ * Copyright © 2012 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#include <ao.h>
+
+struct ao_lcd_segment {
+       uint8_t reg;
+       uint8_t bit;
+};
+
+#define A      0
+#define B      1
+#define C      2
+#define D      3
+#define E      4
+
+static struct stm_gpio *gpios[] = {
+       &stm_gpioa,
+       &stm_gpiob,
+       &stm_gpioc,
+       &stm_gpiod,
+       &stm_gpioe
+};
+
+static inline int ao_lcd_stm_seg_enabled(int seg) {
+       if (seg < 32)
+               return (AO_LCD_STM_SEG_ENABLED_0 >> seg) & 1;
+       else
+               return (AO_LCD_STM_SEG_ENABLED_1 >> (seg - 32)) & 1;
+}
+
+static inline int ao_lcd_stm_com_enabled(int com) {
+       return (AO_LCD_STM_COM_ENABLED >> com) & 1;
+}
+
+#define AO_LCD_STM_GPIOA_SEGS_0        (               \
+               (1 << 0) |                      \
+               (1 << 1) |                      \
+               (1 << 2) |                      \
+               (1 << 3) |                      \
+               (1 << 4) |                      \
+               (1 << 17))
+
+#define AO_LCD_STM_GPIOA_SEGS_1 0
+
+#define AO_LCD_STM_USES_GPIOA  (!!((AO_LCD_STM_GPIOA_SEGS_0 & AO_LCD_STM_SEG_ENABLED_0) | \
+                                   (AO_LCD_STM_GPIOA_SEGS_1 & AO_LCD_STM_SEG_ENABLED_1)))
+
+
+#define AO_LCD_STM_GPIOB_SEGS_0        (               \
+               (1 << 5) |                      \
+               (1 << 6) |                      \
+               (1 << 7) |                      \
+               (1 << 8) |                      \
+               (1 << 9) |                      \
+               (1 << 10) |                     \
+               (1 << 11) |                     \
+               (1 << 12) |                     \
+               (1 << 13) |                     \
+               (1 << 14) |                     \
+               (1 << 15) |                     \
+               (1 << 16))
+
+#define AO_LCD_STM_GPIOB_SEGS_1 0
+
+#if AO_LCD_28_ON_C
+
+#define AO_LCD_STM_GPIOC_28_SEGS       (       \
+               (1 << 28) |                     \
+               (1 << 29) |                     \
+               (1 << 30))
+
+#define AO_LCD_STM_GPIOD_28_SEGS       (       \
+               (1 << 31))
+
+#else
+#define AO_LCD_STM_GPIOC_28_C_SEGS     0
+
+#define AO_LCD_STM_GPIOD_28_SEGS       (       \
+               (1 << 28) |                     \
+               (1 << 29) |                     \
+               (1 << 30) |                     \
+               (1 << 31))
+#endif
+
+#define AO_LCD_STM_GPIOC_SEGS_0        (               \
+               (1 << 18) |                     \
+               (1 << 19) |                     \
+               (1 << 20) |                     \
+               (1 << 21) |                     \
+               (1 << 22) |                     \
+               (1 << 23) |                     \
+               (1 << 24) |                     \
+               (1 << 25) |                     \
+               (1 << 26) |                     \
+               (1 << 27) |                     \
+               AO_LCD_STM_GPIOC_28_SEGS)
+
+#define AO_LCD_STM_GPIOC_SEGS_1 (              \
+               (1 << (40 - 32)) |              \
+               (1 << (41 - 32)) |              \
+               (1 << (42 - 32)))
+
+#define AO_LCD_STM_GPIOD_SEGS_0        (               \
+               AO_LCD_STM_GPIOD_28_SEGS)
+
+#define AO_LCD_STM_GPIOD_SEGS_1 (              \
+               (1 << (32 - 32)) |              \
+               (1 << (33 - 32)) |              \
+               (1 << (34 - 32)) |              \
+               (1 << (35 - 32)) |              \
+               (1 << (43 - 32)))
+
+#define AO_LCD_STM_GPIOE_SEGS_0        0
+
+#define AO_LCD_STM_GPIOE_SEGS_1 (              \
+               (1 << (36 - 32)) |              \
+               (1 << (37 - 32)) |              \
+               (1 << (38 - 32)) |              \
+               (1 << (39 - 32)))
+
+#define AO_LCD_STM_USES_GPIOA  (!!((AO_LCD_STM_GPIOA_SEGS_0 & AO_LCD_STM_SEG_ENABLED_0) | \
+                                   (AO_LCD_STM_GPIOA_SEGS_1 & AO_LCD_STM_SEG_ENABLED_1)))
+
+#define AO_LCD_STM_USES_GPIOB  (!!((AO_LCD_STM_GPIOB_SEGS_0 & AO_LCD_STM_SEG_ENABLED_0) | \
+                                   (AO_LCD_STM_GPIOB_SEGS_1 & AO_LCD_STM_SEG_ENABLED_1)))
+
+
+#define AO_LCD_STM_USES_GPIOC  (!!((AO_LCD_STM_GPIOC_SEGS_0 & AO_LCD_STM_SEG_ENABLED_0) | \
+                                   (AO_LCD_STM_GPIOC_SEGS_1 & AO_LCD_STM_SEG_ENABLED_1)))
+
+
+#define AO_LCD_STM_USES_GPIOD  (!!((AO_LCD_STM_GPIOD_SEGS_0 & AO_LCD_STM_SEG_ENABLED_0) | \
+                                   (AO_LCD_STM_GPIOD_SEGS_1 & AO_LCD_STM_SEG_ENABLED_1)))
+
+#define AO_LCD_STM_USES_GPIOE  (!!((AO_LCD_STM_GPIOE_SEGS_0 & AO_LCD_STM_SEG_ENABLED_0) | \
+                                   (AO_LCD_STM_GPIOE_SEGS_1 & AO_LCD_STM_SEG_ENABLED_1)))
+
+
+static const struct ao_lcd_segment segs[] = {
+       { A, 1 },       /* 0 */
+       { A, 2 },
+       { A, 3 },
+       { A, 6 },
+               
+       { A, 7 },       /* 4 */
+       { B, 0 },
+       { B, 1 },
+       { B, 3 },
+
+       { B, 4 },       /* 8 */
+       { B, 5 },
+       { B, 10 },
+       { B, 11 },
+
+       { B, 12 },      /* 12 */
+       { B, 13 },
+       { B, 14 },
+       { B, 15 },
+
+       { B, 8 },       /* 16 */
+       { A, 15 },
+       { C, 0 },
+       { C, 1 },
+
+       { C, 2 },       /* 20 */
+       { C, 3 },
+       { C, 4 },
+       { C, 5 },
+
+       { C, 6 },       /* 24 */
+       { C, 7 },
+       { C, 8 },
+       { C, 9 },
+
+#if AO_LCD_28_ON_C
+       { C, 10 },      /* 28 */
+       { C, 11 },
+       { C, 12 },
+       { D, 2 },
+#else
+       { D, 8 },       /* 28 */
+       { D, 9 },
+       { D, 10 },
+       { D, 11 },
+#endif
+       { D, 12 },      /* 32 */
+       { D, 13 },
+       { D, 14 },
+       { D, 15 },
+               
+       { E, 0 },       /* 36 */
+       { E, 1 },
+       { E, 2 },
+       { E, 3 },
+
+       { C, 10 },      /* 40 */
+       { C, 11 },
+       { C, 12 },
+       { D, 2 },
+};
+
+static const struct ao_lcd_segment coms[] = {
+       { A, 8 },       /* 0 */
+       { A, 9 },       /* 1 */
+       { A, 10 },      /* 2 */
+       { B, 9 },       /* 3 */
+       { C, 10 },      /* 4 */
+       { C, 11 },      /* 5 */
+       { C, 12 },      /* 6 */
+};
+
+#define NSEG   (sizeof segs/sizeof segs[0])
+#define NCOM   (sizeof coms/sizeof coms[0])
+
+static void
+ao_lcd_stm_fcr_sync(void)
+{
+       while ((stm_lcd.sr & (1 << STM_LCD_SR_FCRSF)) == 0)
+               asm("nop");
+}
+
+static void
+ao_lcd_stm_seg_set(void)
+{
+       int     com, seg, val;
+       int     n, bit;
+       ao_cmd_decimal();
+       com = ao_cmd_lex_i;
+       ao_cmd_decimal();
+       seg = ao_cmd_lex_u32;
+       ao_cmd_decimal();
+       val = ao_cmd_lex_i;
+       printf ("com: %d seg: %d val: %d\n", com, seg, val);
+       n = (seg >> 5) & 1;
+       if (com >= NCOM)
+               com = NCOM-1;
+       if (seg >= NSEG)
+               seg = NSEG-1;
+       if (val)
+               stm_lcd.ram[com * 2 + n] |= (1 << (seg & 0x1f));
+       else
+               stm_lcd.ram[com * 2 + n] &= ~(1 << (seg & 0x1f));
+       stm_lcd.sr = (1 << STM_LCD_SR_UDR);
+}
+
+static void
+ao_lcd_stm_clear(void)
+{
+       int     i;
+
+       for (i = 0; i < sizeof (stm_lcd.ram) / 4; i++)
+               stm_lcd.ram[i] = 0;
+       stm_lcd.sr = (1 << STM_LCD_SR_UDR);
+}
+
+static void
+ao_lcd_stm_text(void)
+{
+       char    string[7];
+       uint8_t c = 0;
+       ao_cmd_white();
+       while (ao_cmd_lex_c != '\n' && c < sizeof (string)) {
+               string[c++] = ao_cmd_lex_c;
+               ao_cmd_lex();
+       }
+       string[c++] = '\0';
+       ao_lcd_font_string(string);
+       stm_lcd.sr = (1 << STM_LCD_SR_UDR);
+}
+
+const struct ao_cmds ao_lcd_stm_cmds[] = {
+       { ao_lcd_stm_seg_set,   "s <com> <seg> <value>\0Set LCD segment" },
+       { ao_lcd_stm_clear,     "C\0Clear LCD" },
+       { ao_lcd_stm_text,      "t <string>\0Write <string> to LCD" },
+       { 0, NULL },
+};
+
+void
+ao_lcd_stm_init(void)
+{
+       int s, c;
+       int r;
+       uint32_t        csr;
+
+       stm_rcc.ahbenr |= ((AO_LCD_STM_USES_GPIOA << STM_RCC_AHBENR_GPIOAEN) |
+                          (AO_LCD_STM_USES_GPIOB << STM_RCC_AHBENR_GPIOBEN) |
+                          (AO_LCD_STM_USES_GPIOC << STM_RCC_AHBENR_GPIOCEN) |
+                          (AO_LCD_STM_USES_GPIOD << STM_RCC_AHBENR_GPIODEN) |
+                          (AO_LCD_STM_USES_GPIOE << STM_RCC_AHBENR_GPIOEEN));
+
+       stm_rcc.apb1enr |= (1 << STM_RCC_APB1ENR_LCDEN);
+
+       /* Turn on the LSI clock */
+       if ((stm_rcc.csr & (1 << STM_RCC_CSR_LSIRDY)) == 0) {
+               stm_rcc.csr |= (1 << STM_RCC_CSR_LSION);
+               while ((stm_rcc.csr & (1 << STM_RCC_CSR_LSIRDY)) == 0)
+                       asm("nop");
+       }
+
+       /* Enable RTC clock config (required to change the RTC/LCD clock */
+
+       stm_pwr.cr |= (1 << STM_PWR_CR_DBP);
+
+       /* Configure the LCDCLK - use the LSI clock */
+
+       csr = stm_rcc.csr;
+       csr &= ~(STM_RCC_CSR_RTCSEL_MASK << STM_RCC_CSR_RTCSEL);
+       csr |= (STM_RCC_CSR_RTCSEL_LSI << STM_RCC_CSR_RTCSEL);
+       stm_rcc.csr = csr;
+
+       for (s = 0; s < NSEG; s++) {
+               uint8_t reg = segs[s].reg;
+               uint8_t bit = segs[s].bit;
+                       
+               if (ao_lcd_stm_seg_enabled(s)) {
+                       stm_moder_set(gpios[reg], bit, STM_MODER_ALTERNATE);
+                       stm_afr_set(gpios[reg], bit, STM_AFR_AF11);
+               }
+       }
+
+       for (c = 0; c < NCOM; c++) {
+               uint8_t reg = coms[c].reg;
+               uint8_t bit = coms[c].bit;
+                       
+               if (ao_lcd_stm_com_enabled(c)) {
+                       stm_moder_set(gpios[reg], bit, STM_MODER_ALTERNATE);
+                       stm_afr_set(gpios[reg], bit, STM_AFR_AF11);
+               }
+       }
+
+       /* duty cycle 1/3, radio 352, frame rate about 33Hz */
+       stm_lcd.fcr = ((STM_LCD_FCR_PS_1 << STM_LCD_FCR_PS) |
+                      (STM_LCD_FCR_DIV_31 << STM_LCD_FCR_DIV) |
+                      (4 << STM_LCD_FCR_CC) |
+                      (4 << STM_LCD_FCR_PON) |
+                      (0 << STM_LCD_FCR_UDDIE) |
+                      (0 << STM_LCD_FCR_SOFIE) |
+                      (0 << STM_LCD_FCR_HD));
+
+       ao_lcd_stm_fcr_sync();
+
+       /* Program desired DUTY in LCD_CR */
+       /* Program desired BIAS in LCD_CR */
+       /* Enable mux seg */
+       /* Internal voltage source */
+       stm_lcd.cr = ((STM_LCD_CR_DUTY_1_4 << STM_LCD_CR_DUTY) |
+                     (STM_LCD_CR_BIAS_1_3 << STM_LCD_CR_BIAS) |
+                     (0 << STM_LCD_CR_VSEL) |
+                     (1 << STM_LCD_CR_MUX_SEG));
+
+       ao_lcd_stm_fcr_sync();
+
+       /* Enable the display (LCDEN bit in LCD_CR) */
+       stm_lcd.cr |= (1 << STM_LCD_CR_LCDEN);
+
+       while ((stm_lcd.sr & (1 << STM_LCD_SR_RDY)) == 0)
+               asm("nop");
+
+       /* Load initial data into LCD_RAM and set the
+        * UDR bit in the LCD_SR register */
+       for (r = 0; r < NCOM; r++) {
+               stm_lcd.ram[r*2] = 0;
+               stm_lcd.ram[r*2 + 1] = 0;
+       }
+
+       stm_lcd.sr = (1 << STM_LCD_SR_UDR);
+
+       /* Program desired frame rate (PS and DIV bits in LCD_FCR) */
+
+       /* Program the contrast (CC bits in LCD_FCR) */
+
+       /* Program optional features (BLINK, BLINKF, PON, DEAD, HD) */
+
+       /* Program the required interrupts */
+
+       /* All done */
+       ao_cmd_register(ao_lcd_stm_cmds);
+}