altos/stm32l0: Add adc and flash drivers
[fw/altos] / src / stm32l0 / ao_adc_stm32l0.c
diff --git a/src/stm32l0/ao_adc_stm32l0.c b/src/stm32l0/ao_adc_stm32l0.c
new file mode 100644 (file)
index 0000000..a7af0a0
--- /dev/null
@@ -0,0 +1,111 @@
+/*
+ * Copyright © 2020 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, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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>
+#include <ao_adc_stm32l0.h>
+
+void
+ao_adc_init(void)
+{
+       stm_rcc.apb2enr |= (1 << STM_RCC_APB2ENR_ADCEN);
+
+       /* Configure */
+       stm_adc.cfgr1 = ((0 << STM_ADC_CFGR1_AWDCH) |                             /* analog watchdog channel 0 */
+                        (0 << STM_ADC_CFGR1_AWDEN) |                             /* Disable analog watchdog */
+                        (0 << STM_ADC_CFGR1_AWDSGL) |                            /* analog watchdog on all channels */
+                        (0 << STM_ADC_CFGR1_DISCEN) |                            /* Not discontinuous mode. All channels converted with one trigger */
+                        (0 << STM_ADC_CFGR1_AUTOOFF) |                           /* Leave ADC running */
+                        (1 << STM_ADC_CFGR1_WAIT) |                              /* Wait for data to be read before next conversion */
+                        (0 << STM_ADC_CFGR1_CONT) |                              /* only one set of conversions per trigger */
+                        (1 << STM_ADC_CFGR1_OVRMOD) |                            /* overwrite on overrun */
+                        (STM_ADC_CFGR1_EXTEN_DISABLE << STM_ADC_CFGR1_EXTEN) |   /* SW trigger */
+                        (0 << STM_ADC_CFGR1_ALIGN) |                             /* Align to LSB */
+                        (STM_ADC_CFGR1_RES_12 << STM_ADC_CFGR1_RES) |            /* 12 bit resolution */
+                        (STM_ADC_CFGR1_SCANDIR_UP << STM_ADC_CFGR1_SCANDIR) |    /* scan 0 .. n */
+                        (STM_ADC_CFGR1_DMACFG_ONESHOT << STM_ADC_CFGR1_DMACFG) | /* one set of conversions then stop */
+                        (0 << STM_ADC_CFGR1_DMAEN));                             /* disable DMA */
+
+       /* Set the clock */
+       stm_adc.cfgr2 = STM_ADC_CFGR2_CKMODE_PCLK_2 << STM_ADC_CFGR2_CKMODE;
+
+       /* Shortest sample time */
+       stm_adc.smpr = STM_ADC_SMPR_SMP_71_5 << STM_ADC_SMPR_SMP;
+
+#define AO_ADC_LFMEN   (AO_SYSCLK < 3500000)
+
+       stm_adc.ccr = ((AO_ADC_LFMEN << STM_ADC_CCR_LFMEN) |
+                      (0 << STM_ADC_CCR_VLCDEN) |
+                      (0 << STM_ADC_CCR_TSEN) |
+                      (0 << STM_ADC_CCR_VREFEN));
+
+       /* Calibrate. This also enables the ADC vreg */
+       stm_adc.cr |= (1 << STM_ADC_CR_ADCAL);
+       while ((stm_adc.cr & (1 << STM_ADC_CR_ADCAL)) != 0)
+               ;
+
+       /* Enable */
+       stm_adc.isr = (1 << STM_ADC_ISR_ADRDY); /* Clear ADRDY bit */
+       stm_adc.cr |= (1 << STM_ADC_CR_ADEN);
+       while ((stm_adc.isr & (1 << STM_ADC_ISR_ADRDY)) == 0)
+               ;
+}
+
+static void
+ao_adc_shutdown(void)
+{
+       /* Disable ADC */
+       stm_adc.cr |= (1 << STM_ADC_CR_ADDIS);
+       while ((stm_adc.cr & (1 << STM_ADC_CR_ADEN)) != 0)
+               ;
+
+       /* Clear ADRDY bit */
+       stm_adc.isr = (1 << STM_ADC_ISR_ADRDY);
+
+       /* Disable ADC vreg */
+       stm_adc.cr &= ~(1 << STM_ADC_CR_ADVREGEN);
+
+       /* Disable ADC clocks */
+       stm_rcc.apb2enr &= ~(1 << STM_RCC_APB2ENR_ADCEN);
+}
+
+uint16_t
+ao_adc_read_vref(void)
+{
+       uint16_t value;
+
+       ao_adc_init();
+
+       /* Turn on voltage reference */
+       stm_adc.ccr |= (1 << STM_ADC_CCR_VREFEN);
+
+       /* Select VREF */
+       stm_adc.chselr = (1 << STM_ADC_CHSEL_VREF);
+
+       /* Start conversion */
+       stm_adc.cr |= (1 << STM_ADC_CR_ADSTART);
+
+       /* Wait for conversion complete */
+       while ((stm_adc.isr & (1 << STM_ADC_ISR_EOC)) == 0)
+               ;
+
+       /* Fetch result */
+       value = stm_adc.dr;
+
+       ao_adc_shutdown();
+       return value;
+}