altos: Get SAMD21 SPI driver working in non-DMA mode
[fw/altos] / src / samd21 / ao_spi_samd21.c
diff --git a/src/samd21/ao_spi_samd21.c b/src/samd21/ao_spi_samd21.c
new file mode 100644 (file)
index 0000000..487c23e
--- /dev/null
@@ -0,0 +1,259 @@
+/*
+ * Copyright © 2022 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.
+ */
+
+#include <ao.h>
+
+static uint8_t         ao_spi_mutex[SAMD21_NUM_SERCOM];
+static uint8_t         ao_spi_pin_config[SAMD21_NUM_SERCOM];
+
+struct ao_spi_samd21_info {
+       struct samd21_sercom    *sercom;
+};
+
+static const struct ao_spi_samd21_info ao_spi_samd21_info[SAMD21_NUM_SERCOM] = {
+       {
+               .sercom = &samd21_sercom0,
+       },
+       {
+               .sercom = &samd21_sercom1,
+       },
+       {
+               .sercom = &samd21_sercom2,
+       },
+       {
+               .sercom = &samd21_sercom3,
+       },
+       {
+               .sercom = &samd21_sercom4,
+       },
+       {
+               .sercom = &samd21_sercom5,
+       },
+};
+
+//static uint8_t       spi_dev_null;
+
+void
+ao_spi_send(const void *block, uint16_t len, uint8_t spi_index)
+{
+       uint8_t                 id = AO_SPI_INDEX(spi_index);
+       struct samd21_sercom    *sercom = ao_spi_samd21_info[id].sercom;
+
+       const uint8_t *b = block;
+
+       while (len--) {
+               sercom->data = *b++;
+               while ((sercom->intflag & (1 << SAMD21_SERCOM_INTFLAG_RXC)) == 0)
+                       ;
+               (void) sercom->data;
+       }
+}
+
+void
+ao_spi_recv(void *block, uint16_t len, uint8_t spi_index)
+{
+       uint8_t                 id = AO_SPI_INDEX(spi_index);
+       struct samd21_sercom    *sercom = ao_spi_samd21_info[id].sercom;
+
+       uint8_t *b = block;
+
+       while (len--) {
+               sercom->data = 0xff;
+               while ((sercom->intflag & (1 << SAMD21_SERCOM_INTFLAG_RXC)) == 0)
+                       ;
+               *b++ = (uint8_t) sercom->data;
+       }
+}
+
+
+void
+ao_spi_duplex(const void *out, void *in, uint16_t len, uint8_t spi_index)
+{
+       uint8_t                 id = AO_SPI_INDEX(spi_index);
+       struct samd21_sercom    *sercom = ao_spi_samd21_info[id].sercom;
+
+       const uint8_t *o = out;
+       uint8_t *i = in;
+
+       while (len--) {
+               sercom->data = *o++;
+               while ((sercom->intflag & (1 << SAMD21_SERCOM_INTFLAG_RXC)) == 0)
+                       ;
+               *i++ = (uint8_t) sercom->data;
+       }
+}
+
+static void
+ao_spi_disable_pin_config(uint8_t spi_pin_config)
+{
+       switch (spi_pin_config) {
+#if HAS_SPI_0
+       case AO_SPI_0_PA08_PA09_PA10:
+               samd21_port_pmux_clr(&samd21_port_a, 8);        /* MOSI */
+               samd21_port_pmux_clr(&samd21_port_a, 9);        /* SCLK */
+               samd21_port_pmux_clr(&samd21_port_a, 10);       /* MISO */
+               break;
+       case AO_SPI_0_PA04_PA05_PA06:
+               samd21_port_pmux_clr(&samd21_port_a, 4);        /* MOSI */
+               samd21_port_pmux_clr(&samd21_port_a, 5);        /* SCLK */
+               samd21_port_pmux_clr(&samd21_port_a, 6);        /* MISO */
+               break;
+#endif
+       }
+}
+
+static void
+ao_spi_enable_pin_config(uint8_t spi_pin_config)
+{
+       switch (spi_pin_config) {
+#if HAS_SPI_0
+       case AO_SPI_0_PA08_PA09_PA10:
+               ao_enable_output(&samd21_port_a, 8, 1);
+               ao_enable_output(&samd21_port_a, 9, 1);
+               ao_enable_input(&samd21_port_a, 10, AO_MODE_PULL_NONE);
+               samd21_port_pmux_set(&samd21_port_a, 8, SAMD21_PORT_PMUX_FUNC_C);       /* MOSI */
+               samd21_port_pmux_set(&samd21_port_a, 9, SAMD21_PORT_PMUX_FUNC_C);       /* SCLK */
+               samd21_port_pmux_set(&samd21_port_a, 10, SAMD21_PORT_PMUX_FUNC_C);      /* MISO */
+               break;
+       case AO_SPI_0_PA04_PA05_PA06:
+               ao_enable_output(&samd21_port_a, 4, 1);
+               ao_enable_output(&samd21_port_a, 5, 1);
+               ao_enable_input(&samd21_port_a, 6, AO_MODE_PULL_NONE);
+               samd21_port_pmux_set(&samd21_port_a, 4, SAMD21_PORT_PMUX_FUNC_C);       /* MOSI */
+               samd21_port_pmux_set(&samd21_port_a, 5, SAMD21_PORT_PMUX_FUNC_C);       /* SCLK */
+               samd21_port_pmux_set(&samd21_port_a, 6, SAMD21_PORT_PMUX_FUNC_C);       /* MISO */
+               break;
+#endif
+       }
+}
+
+static void
+ao_spi_config(uint8_t spi_index, uint32_t baud)
+{
+       uint8_t                 spi_pin_config = AO_SPI_PIN_CONFIG(spi_index);
+       uint8_t                 id = AO_SPI_INDEX(spi_index);
+       struct samd21_sercom    *sercom = ao_spi_samd21_info[id].sercom;
+
+       if (spi_pin_config != ao_spi_pin_config[id]) {
+               ao_spi_disable_pin_config(ao_spi_pin_config[id]);
+               ao_spi_enable_pin_config(spi_pin_config);
+               ao_spi_pin_config[id] = spi_pin_config;
+       }
+
+       sercom->baud = (uint16_t) baud;
+
+       /* Set spi mode */
+       uint32_t ctrla = sercom->ctrla;
+       ctrla &= ~((1UL << SAMD21_SERCOM_CTRLA_CPOL) |
+                  (1UL << SAMD21_SERCOM_CTRLA_CPHA));
+       ctrla |= ((AO_SPI_CPOL(spi_index) << SAMD21_SERCOM_CTRLA_CPOL) |
+                 (AO_SPI_CPHA(spi_index) << SAMD21_SERCOM_CTRLA_CPHA));
+
+       /* finish setup and enable the hardware */
+       ctrla |= (1 << SAMD21_SERCOM_CTRLA_ENABLE);
+
+       sercom->ctrla = ctrla;
+
+       while (sercom->syncbusy & (1 << SAMD21_SERCOM_SYNCBUSY_ENABLE))
+               ;
+}
+
+void
+ao_spi_get(uint8_t spi_index, uint32_t speed)
+{
+       uint8_t         id = AO_SPI_INDEX(spi_index);
+
+       ao_mutex_get(&ao_spi_mutex[id]);
+       ao_spi_config(spi_index, speed);
+}
+
+void
+ao_spi_put(uint8_t spi_index)
+{
+       uint8_t                 id = AO_SPI_INDEX(spi_index);
+       struct samd21_sercom    *sercom = ao_spi_samd21_info[id].sercom;
+
+       sercom->ctrla &= ~(1UL << SAMD21_SERCOM_CTRLA_ENABLE);
+       while (sercom->syncbusy & (1 << SAMD21_SERCOM_SYNCBUSY_ENABLE))
+               ;
+       ao_mutex_put(&ao_spi_mutex[id]);
+}
+
+static void
+ao_spi_init_sercom(uint8_t id)
+{
+       struct samd21_sercom *sercom = ao_spi_samd21_info[id].sercom;
+
+       /* Send a clock along */
+       samd21_gclk_clkctrl(0, SAMD21_GCLK_CLKCTRL_ID_SERCOM0_CORE + id);
+
+       samd21_nvic_set_enable(SAMD21_NVIC_ISR_SERCOM0_POS + id);
+       samd21_nvic_set_priority(SAMD21_NVIC_ISR_SERCOM0_POS + id, 4);
+
+       /* Enable */
+       samd21_pm.apbcmask |= (1 << (SAMD21_PM_APBCMASK_SERCOM0 + id));
+
+       /* Reset */
+       sercom->ctrla = (1 << SAMD21_SERCOM_CTRLA_SWRST);
+
+       while ((sercom->ctrla & (1 << SAMD21_SERCOM_CTRLA_SWRST)) ||
+              (sercom->syncbusy & (1 << SAMD21_SERCOM_SYNCBUSY_SWRST)))
+               ;
+
+       /* set SPI mode */
+       sercom->ctrla = ((SAMD21_SERCOM_CTRLA_DORD_MSB << SAMD21_SERCOM_CTRLA_DORD) |
+                        (0 << SAMD21_SERCOM_CTRLA_CPOL) |
+                        (0 << SAMD21_SERCOM_CTRLA_CPHA) |
+                        (0 << SAMD21_SERCOM_CTRLA_FORM) |
+                        (2 << SAMD21_SERCOM_CTRLA_DIPO) |
+                        (0 << SAMD21_SERCOM_CTRLA_DOPO) |
+                        (0 << SAMD21_SERCOM_CTRLA_IBON) |
+                        (0 << SAMD21_SERCOM_CTRLA_RUNSTDBY) |
+                        (SAMD21_SERCOM_CTRLA_MODE_SPI_HOST << SAMD21_SERCOM_CTRLA_MODE) |
+                        (0 << SAMD21_SERCOM_CTRLA_ENABLE) |
+                        (0 << SAMD21_SERCOM_CTRLA_SWRST));
+
+       sercom->ctrlb = ((1 << SAMD21_SERCOM_CTRLB_RXEN) |
+                        (0 << SAMD21_SERCOM_CTRLB_AMODE) |
+                        (0 << SAMD21_SERCOM_CTRLB_MSSEN) |
+                        (0 << SAMD21_SERCOM_CTRLB_SSDE) |
+                        (0 << SAMD21_SERCOM_CTRLB_PLOADEN) |
+                        (SAMD21_SERCOM_CTRLB_CHSIZE_8 << SAMD21_SERCOM_CTRLB_CHSIZE));
+
+
+       ao_spi_enable_pin_config(id);
+}
+
+void
+ao_spi_init(void)
+{
+#if HAS_SPI_0
+       ao_spi_init_sercom(0);
+#endif
+#if HAS_SPI_1
+       ao_spi_init_sercom(1);
+#endif
+#if HAS_SPI_2
+       ao_spi_init_sercom(2);
+#endif
+#if HAS_SPI_3
+       ao_spi_init_sercom(3);
+#endif
+#if HAS_SPI_4
+       ao_spi_init_sercom(4);
+#endif
+#if HAS_SPI_5
+       ao_spi_init_sercom(5);
+#endif
+}