aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJosh Rahm <joshuarahm@gmail.com>2020-11-24 13:46:41 -0700
committerJosh Rahm <joshuarahm@gmail.com>2020-11-24 13:46:41 -0700
commit93b063fedfcf7409a67df035170ea5670cad22e1 (patch)
treea23321a7465d966b1ccf196ca00e65a70c9f9110 /src
parentb040195d31df6ad759f16ea3456471897f55daa1 (diff)
downloadstm32l4-93b063fedfcf7409a67df035170ea5670cad22e1.tar.gz
stm32l4-93b063fedfcf7409a67df035170ea5670cad22e1.tar.bz2
stm32l4-93b063fedfcf7409a67df035170ea5670cad22e1.zip
Moved action to top level.
Removed old iterations of the project and moved the files from 02-usart to the root directory since that's the sole place where the action is and that subproject has outgrown its initial title.
Diffstat (limited to 'src')
-rw-r--r--src/arch/stm32l4xxx/peripherals/clock.c117
-rw-r--r--src/arch/stm32l4xxx/peripherals/irq.c96
-rw-r--r--src/arch/stm32l4xxx/peripherals/usart.c152
-rw-r--r--src/kern/delay.c9
-rw-r--r--src/kern/dma/dma_manager.c311
-rw-r--r--src/kern/gpio/gpio_manager.c402
-rw-r--r--src/kern/gpio/sysled.c14
-rw-r--r--src/kern/init.c82
-rw-r--r--src/kern/lib.c56
-rw-r--r--src/kern/log.c55
-rw-r--r--src/kern/main.c29
-rw-r--r--src/kern/mem.c280
-rw-r--r--src/kern/stdlibrepl.c13
-rw-r--r--src/kern/string.c9
-rw-r--r--src/kern/vector.c0
15 files changed, 1625 insertions, 0 deletions
diff --git a/src/arch/stm32l4xxx/peripherals/clock.c b/src/arch/stm32l4xxx/peripherals/clock.c
new file mode 100644
index 0000000..9051572
--- /dev/null
+++ b/src/arch/stm32l4xxx/peripherals/clock.c
@@ -0,0 +1,117 @@
+/*
+ * This file sets the system clock to its full glory of 80Mhz
+ */
+
+#include "arch/stm32l4xxx/peripherals/clock.h"
+#include "arch/stm32l4xxx/peripherals/flash.h"
+
+#include "kern/init.h"
+
+#include <stdint.h>
+
+#define TIMEOUT 10000
+
+
+int pll_off()
+{
+ uint32_t c;
+
+ RCC.c_r &= ~BIT(24); /* Turn off pll. */
+ for (c = 0; c < TIMEOUT && RCC.c_r & BIT(25); ++c)
+ ; /* Wait for OFF. */
+
+ if (c == TIMEOUT) {
+ return E_TIMEOUT;
+ }
+
+ return 0;
+}
+
+int pll_on()
+{
+ uint32_t c;
+
+ RCC.c_r |= BIT(24); /* Turn on PLL. */
+ for (c = 0; c < TIMEOUT && !(RCC.c_r & BIT(25)); ++c)
+ ; /* Wait for RDY. */
+
+ if (c == TIMEOUT) {
+ return E_TIMEOUT;
+ }
+
+ return 0;
+}
+
+int configure_pll(
+ uint8_t pllp_div_factor, pll_divisor_t pllr, /* System clock divisor. */
+ pll_divisor_t pllq, /* Divison factor for PLL48M1CLK. */
+ pllp_divisor_t pllp, /* Divison factor for PLLSAI2CLK. */
+ uint8_t plln, /* PLL numerator. */
+ pllm_divisor_t pllm, /* PLL denominator. */
+ pll_src_t pllsrc /* PLL source */)
+{
+ if (RCC.c_r & BIT(25)) {
+ /* PLL must be off to configure it. */
+ return E_NOT_OFF;
+ }
+
+ /* Make sure inputs are valid. */
+ if (pllp_div_factor == 1 || pllp_div_factor > 31) {
+ return E_BADPLLP_DIV;
+ }
+ if (plln < 8 || plln > 86) {
+ return E_BADPLLN;
+ }
+
+ RCC.pllcfg_r = (pllp_div_factor << 27) | (pllr << 24) | (pllq << 20) |
+ (pllp << 16) | (plln << 8) | (pllm << 4) | (pllsrc << 0);
+
+ return 0;
+}
+
+static _no_init uint8_t clock_mHz;
+uint8_t get_clock_mhz()
+{
+ return clock_mHz;
+}
+
+int set_system_clock_MHz(uint8_t mhz)
+{
+ clock_mHz = mhz;
+
+ /* Set the source of the system colck to MSI temporarily. */
+ set_system_clock_src(SYSTEM_CLOCK_SRC_MSI);
+
+ if (mhz <= 8 || mhz > 80) {
+ return E_BAD_ARG;
+ }
+
+ pll_off();
+
+ configure_pll(
+ 0 /* pllp_div_factor */, PLL_DIVISOR_4 /* pllr: VCO / 4 = mhz MHz. */,
+ PLL_DIVISOR_4 /* pllq: VCO / 4 = mhz MHz */, PLLP_DIVISOR_7 /* pllp */,
+
+ /* The following set the frequency of VCO to (mhz*4)MHz: mhz * 1 * 4MHz.
+ */
+ mhz /* plln | mhz */, PLLM_DIVISOR_1 /* pllm | 01 */,
+ PLL_SRC_MSI /* pll src | 04 Mhz */);
+
+ pll_on();
+
+ /* Configure the flash to have 4 wait states. This is required at
+ * 80 MHz. */
+ FLASH.ac_r &= ~0x07;
+ FLASH.ac_r |= 0x04;
+
+ /* Set the source of the system colck to PLL. */
+ set_system_clock_src(SYSTEM_CLOCK_SRC_PLL);
+ return 0;
+}
+
+int set_system_clock_src(system_clock_src_t src)
+{
+ uint8_t value = RCC.cfg_r & ~0x03;
+ RCC.cfg_r = value | src;
+ return 0;
+}
diff --git a/src/arch/stm32l4xxx/peripherals/irq.c b/src/arch/stm32l4xxx/peripherals/irq.c
new file mode 100644
index 0000000..364b9a7
--- /dev/null
+++ b/src/arch/stm32l4xxx/peripherals/irq.c
@@ -0,0 +1,96 @@
+#include "arch/stm32l4xxx/peripherals/irq.h"
+#include "arch/stm32l4xxx/peripherals/gpio.h"
+#include "arch/stm32l4xxx/peripherals/nvic.h"
+
+#include "arch.h"
+#include "kern/delay.h"
+#include "kern/gpio/gpio_manager.h"
+
+#define IRQ_RESERVED(n)
+#define IRQ(name, uname_, n) \
+ void WEAK name () { \
+ unhandled_isr(n); \
+ }
+#include "arch/stm32l4xxx/peripherals/isrs.inc"
+#undef IRQ_RESERVED
+#undef IRQ
+
+void isr_simple_pin_on()
+{
+ int ec;
+ gpio_pin_opts_t opts = DEFAULT_GPIO_OPTS_OUTPUT;
+ gpio_reserved_pin_t pin3 = reserve_gpio_pin(GPIO_PIN_PB3, &opts, &ec);
+
+ set_gpio_pin_high(pin3);
+}
+
+#define IRQ_RESERVED(n) 0,
+#define IRQ(name, uname_, n) name,
+const void* vectors[] __attribute__((section(".vectors"))) = {
+ (void*)0x2000c000, /* Top of stack at top of sram1. 48k */
+#include "arch/stm32l4xxx/peripherals/isrs.inc"
+};
+#undef IRQ_RESERVED
+#undef IRQ
+
+/* Encodes the provided number as a series of flashes on the on-board
+ * LED. The flashes follow as such:
+ *
+ * Before the bits of the code are flashed, a rapid succession of 20 flashes
+ * followed by a pause will occur indicating that the next 8 flashes indicate
+ * the bits of the provided code.
+ *
+ * Eoch of the next eight flashes indicate either a 1 or 0 depending on the
+ * length of flash. The first flash is the least-significant bit, the next the
+ * second least, the third third least, etc.
+ *
+ * - A quick flash followed by a long pause indicates a 0 bit.
+ * - A "long" flash followed by a equally long pause indicates a 1 bit.
+ */
+void unhandled_isr(uint8_t number)
+{
+ int ec;
+ gpio_pin_opts_t opts = DEFAULT_GPIO_OPTS_OUTPUT;
+ gpio_reserved_pin_t pin3 = reserve_gpio_pin(GPIO_PIN_PB3, &opts, &ec);
+
+ for (;;) {
+ for (int i = 0; i < 20; ++ i) {
+ set_gpio_pin_high(pin3);
+ delay(1000000);
+ set_gpio_pin_low(pin3);
+ delay(1000000);
+ }
+ delay(50000000);
+
+ int n = number;
+ for (int i = 0; i < 8; ++ i) {
+ if (n & 1) {
+ // LSB is a 1
+ set_gpio_pin_high(pin3);
+ delay(15000000);
+ set_gpio_pin_low(pin3);
+ delay(15000000);
+ } else {
+ // LSB is a 0
+ set_gpio_pin_high(pin3);
+ delay(1000000);
+ set_gpio_pin_low(pin3);
+ delay(29000000);
+ }
+
+ n >>= 1;
+ }
+ }
+}
+
+void enable_interrupts(interrupt_set_t* interrupts)
+{
+ for (int i = 0; i < sizeof(NVIC.ise_r) / sizeof(uint32_t); ++ i)
+ NVIC.ise_r[i] = interrupts->irqs[i];
+}
+
+void disable_interrupts(interrupt_set_t* interrupts)
+{
+ for (int i = 0; i < sizeof(NVIC.ise_r) / sizeof(uint32_t); ++ i)
+ NVIC.ice_r[i] = interrupts->irqs[i];
+}
diff --git a/src/arch/stm32l4xxx/peripherals/usart.c b/src/arch/stm32l4xxx/peripherals/usart.c
new file mode 100644
index 0000000..7309b48
--- /dev/null
+++ b/src/arch/stm32l4xxx/peripherals/usart.c
@@ -0,0 +1,152 @@
+#include "arch/stm32l4xxx/peripherals/usart.h"
+#include "kern/delay.h"
+#include "kern/lib.h"
+#include <stdarg.h>
+
+void set_usart1_clock_src(__IO rcc_t* rcc, usart_clk_src_t usart_clk_src)
+{
+ rcc->ccip_r = (rcc->ccip_r & (~0x03)) | usart_clk_src;
+}
+
+void set_usart2_clock_src(__IO rcc_t* rcc, usart_clk_src_t usart_clk_src)
+{
+ rcc->ccip_r = (rcc->ccip_r & ~(0x03 << 2)) | (usart_clk_src << 2);
+}
+
+void set_usart2_clock_enabled(__IO rcc_t* rcc, bool enable)
+{
+ if (enable) {
+ rcc->apb1en1_r |= BIT(17);
+ } else {
+ rcc->apb1en1_r &= ~BIT(17);
+ }
+}
+
+void set_usart1_clock_enabled(__IO rcc_t* rcc, bool enable)
+{
+ if (enable) {
+ rcc->apb2en_r |= BIT(14);
+ } else {
+ rcc->apb2en_r &= ~BIT(14);
+ }
+}
+
+void usart_set_parity(__IO usart_t* usart, usart_parity_t parity)
+{
+ uint32_t c_r1 = usart->c_r1;
+ c_r1 &= ~(0x3 << 9);
+ c_r1 |= parity;
+ usart->c_r1 = c_r1;
+}
+
+void usart_set_enabled(__IO usart_t* usart, usart_enable_t enabled)
+{
+ if (!enabled) {
+ regset(usart->c_r1, usart_ue, 0);
+ } else {
+ /* Set the rx enabled. */
+ regset(usart->c_r1, usart_re, !!(enabled & USART_ENABLE_RX));
+ regset(usart->c_r1, usart_te, !!(enabled & USART_ENABLE_TX));
+ regset(usart->c_r1, usart_ue, 1);
+ }
+}
+
+void usart_transmit_byte_sync(__IO usart_t* usart, uint8_t byte)
+{
+ usart->td_r = byte;
+ /* Per the manual, when bit 7 of the IS register is set, then the usart
+ * data has been sent to the shift register.
+ *
+ * This bit is cleared by writing to the TD register. */
+ while (!(usart->is_r & BIT(7)))
+ ;
+}
+
+void usart_transmit_bytes_sync(__IO usart_t* usart, const uint8_t* bytes, uint32_t n)
+{
+ while (n --) {
+ usart_transmit_byte_sync(usart, *(bytes ++));
+ }
+}
+
+void usart_transmit_str_sync(__IO usart_t* usart, const char* str)
+{
+ while (*str) {
+ if (*str == '\n') {
+ usart_transmit_byte_sync(usart, '\r');
+ }
+ usart_transmit_byte_sync(usart, *(str ++));
+ }
+}
+
+void usart_enable_dma(__IO usart_t* usart, usart_enable_t enabled)
+{
+ switch(enabled) {
+ case USART_ENABLE_DISABLED:
+ regset(usart->c_r3, usart_dmar, 0);
+ regset(usart->c_r3, usart_dmat, 0);
+ break;
+
+ case USART_ENABLE_TX:
+ regset(usart->c_r3, usart_dmat, 1);
+ break;
+
+ case USART_ENABLE_RX:
+ regset(usart->c_r3, usart_dmar, 1);
+ break;
+ };
+}
+
+void usart_vprintf(__IO usart_t* usart, const char* fmt, va_list l)
+{
+ union {
+ void* ptr;
+ char* str;
+ int i;
+ } b;
+ char buf[128];
+
+ while (*fmt != 0) {
+ if (*fmt == '%') {
+ switch (*(++fmt)) {
+ case 0:
+ goto end;
+ case '%':
+ usart_transmit_byte_sync(usart, '%');
+ break;
+ case 'p':
+ b.ptr = va_arg(l, void*);
+ hexify(ptr2reg(b.ptr), buf);
+ usart_transmit_str_sync(usart, "0x");
+ usart_transmit_str_sync(usart, buf);
+ break;
+ case 'd':
+ case 'i':
+ b.i = va_arg(l, int);
+ decimalify(b.i, buf);
+ usart_transmit_str_sync(usart, buf);
+ break;
+ case 's':
+ b.str = va_arg(l, char*);
+ usart_transmit_str_sync(usart, b.str);
+ }
+ ++ fmt;
+ } else {
+ if (*fmt == '\n') {
+ usart_transmit_byte_sync(usart, '\r');
+ }
+ usart_transmit_byte_sync(usart, *(fmt ++));
+ }
+ }
+
+end:
+ va_end(l);
+}
+
+void usart_printf(__IO usart_t* usart, const char* fmt, ...)
+{
+ va_list l;
+ va_start(l, fmt);
+
+ usart_vprintf(usart, fmt, l);
+}
diff --git a/src/kern/delay.c b/src/kern/delay.c
new file mode 100644
index 0000000..28ef710
--- /dev/null
+++ b/src/kern/delay.c
@@ -0,0 +1,9 @@
+#include "kern/delay.h"
+
+void delay(uint32_t delay)
+{
+ while (delay--) {
+ /* needed to keep the compiler from optimizing away the loop. */
+ asm volatile("");
+ }
+}
diff --git a/src/kern/dma/dma_manager.c b/src/kern/dma/dma_manager.c
new file mode 100644
index 0000000..00e9f3d
--- /dev/null
+++ b/src/kern/dma/dma_manager.c
@@ -0,0 +1,311 @@
+#include "kern/dma/dma_manager.h"
+#include "arch/stm32l4xxx/peripherals/dma.h"
+#include "arch/stm32l4xxx/peripherals/usart.h"
+#include "arch/stm32l4xxx/peripherals/rcc.h"
+
+
+/* Bitmask of DMA2 channels in use. */
+static uint8_t dma_inuse[2];
+
+static inline dma_t* get_dma(int dma)
+{
+ if (dma) {
+ return &DMA2;
+ } else {
+ return &DMA1;
+ }
+}
+
+static dma_t* get_raw_dma(dma_channel_t chan)
+{
+ return get_dma(chan.dma);
+}
+
+static dma_channel_config_t* get_raw_channel_config(dma_channel_t chan)
+{
+ dma_t* dma = get_raw_dma(chan);
+ return &dma->channel_config[chan.chan];
+}
+
+static uint32_t get_periph_location(dma_peripheral_t operipheral)
+{
+#define CASE(p, n) case p: return ptr2reg(n);
+ switch (operipheral) {
+ CASE(DMA1_PERIPH_USART1_RX, &USART1.rd_r)
+ CASE(DMA1_PERIPH_USART1_TX, &USART1.td_r)
+ CASE(DMA1_PERIPH_USART2_RX, &USART2.rd_r)
+ CASE(DMA1_PERIPH_USART2_TX, &USART2.td_r)
+
+ default:
+ return 0;
+ };
+#undef CASE
+}
+
+static dma_channel_t allocate_dma_channel(
+ dma_peripheral_t operipheral, int* modesel)
+{
+ dma_peripheral_t peripheral = operipheral & 0xff;
+ int dmasel = peripheral >= DMA2_DMA1_SWITCH__;
+ if (dmasel) {
+ peripheral -= DMA2_DMA1_SWITCH__;
+ }
+ int chan = peripheral % DMA_N_CHANNELS;
+
+ *modesel = peripheral / 7;
+ return (dma_channel_t) {
+ .dma = dmasel,
+ .chan = chan
+ };
+}
+
+/*
+ * Atomically reserves the DMA channel so other calls
+ * cannot erroneously reserve the same DMA channel.
+ *
+ * Returns 0 if this function was unable to reserve
+ * the channel.
+ */
+static int try_reserve_dma_channel(
+ dma_channel_t chan)
+{
+ int in_use = __sync_fetch_and_or(
+ &dma_inuse[chan.dma], 1 << chan.chan);
+
+ return !(in_use & (1 << chan.chan));
+}
+
+void release_dma_channel(dma_channel_t chan)
+{
+ dma_channel_config_t* config = get_raw_channel_config(chan);
+ regset(config->cc_r, dma_cc_en, 0); /* Disable the register. */
+ dma_inuse[chan.dma] &= ~(1 << chan.chan); /* Release the DMA. */
+
+ if (!dma_inuse[chan.dma]) {
+ /* Power-down the DMA if not in use. */
+ if (chan.dma) {
+ regset(RCC.ahb1en_r, rcc_dma2en, 0);
+ } else {
+ regset(RCC.ahb1en_r, rcc_dma1en, 0);
+ }
+ }
+}
+
+void configure_dma_channel(
+ dma_channel_t chan,
+ dma_peripheral_t operipheral,
+ dma_opts_t* opts,
+ dma_dir_t dir,
+ int selmode,
+ bool mem2mem,
+ int* error_out)
+{
+ if (chan.dma) {
+ regset(RCC.ahb1en_r, rcc_dma2en, 1);
+ } else {
+ regset(RCC.ahb1en_r, rcc_dma1en, 1);
+ }
+
+ dma_t* dma = get_raw_dma(chan);
+ regset(dma->csel_r, 0xF << (4 * chan.chan), selmode);
+ dma_channel_config_t* config =
+ &dma->channel_config[chan.chan];
+
+ uint32_t reg = 0;
+
+ regset(reg, dma_cc_dir, dir);
+ regset(reg, dma_cc_tcie, opts->transfer_complete_interrupt_enable);
+ regset(reg, dma_cc_htie, opts->half_transfer_interrupt_enable);
+ regset(reg, dma_cc_teie, opts->transfer_error_interrupt_enable);
+ regset(reg, dma_cc_circ, opts->circular_mode);
+ regset(reg, dma_cc_pinc, opts->peripheral_increment);
+ regset(reg, dma_cc_minc, opts->memory_increment);
+ regset(reg, dma_cc_psize, opts->peripheral_block_size);
+ regset(reg, dma_cc_msize, opts->memory_block_size);
+ regset(reg, dma_cc_pl, opts->priority);
+ regset(reg, dma_cc_mem2mem, mem2mem);
+
+ config->cc_r = reg;
+ config->cpa_r = get_periph_location(operipheral);
+
+ *error_out = 0;
+}
+
+dma_mem2mem_channel_t select_dma_channel_mem2mem(
+ int channel,
+ dma_opts_t* opts,
+ int* error_out)
+{
+
+#define WRAP(c) ((dma_mem2mem_channel_t) { .c_ = c })
+ // TODO this should probably be in a critical section.
+ dma_channel_t chan;
+ if (channel == -1) {
+ chan.dma = 1;
+ if ((dma_inuse[chan.dma] & 0x7F) == 0x7F) {
+ chan.dma = 0;
+ }
+
+ if ((dma_inuse[chan.dma] & 0x7F) == 0x7F) {
+ *error_out = DMA_ERROR_CHANNEL_IN_USE;
+ return WRAP(DMA_CHAN_ERROR);
+ }
+
+ uint8_t t = ~(dma_inuse[chan.dma] << 1);
+ chan.chan = 6 - (__builtin_clz(t) - 24);
+ } else {
+ if (channel < 7) {
+ chan.dma = 0;
+ chan.chan = channel;
+ } else {
+ chan.dma = 0;
+ chan.chan = channel - 7;
+ }
+ }
+
+ if (!try_reserve_dma_channel(chan)) {
+ *error_out = DMA_ERROR_CHANNEL_IN_USE;
+ return WRAP(DMA_CHAN_ERROR);
+ }
+
+ int ec = 0;
+ configure_dma_channel(
+ chan,
+ -1 /* No peripheral */,
+ opts,
+ READ_FROM_PERIPHERAL,
+ /* selmode = */ 0x8,
+ /* mem2mem = */ true,
+ &ec);
+
+ if (ec) {
+ *error_out = ec;
+ return WRAP(DMA_CHAN_ERROR);
+ }
+
+ *error_out = 0;
+ return WRAP(chan);
+#undef WRAP
+}
+
+dma_mem2p_channel_t select_dma_channel_mem2p(
+ dma_peripheral_t peripheral,
+ dma_opts_t* opts_in,
+ int* error_out)
+{
+#define WRAP(c) ((dma_mem2p_channel_t) { .c_ = c })
+ *error_out = 0;
+
+ int modesel;
+ dma_channel_t ret =
+ allocate_dma_channel(peripheral, &modesel);
+
+ if (!try_reserve_dma_channel(ret)) {
+ *error_out = DMA_ERROR_CHANNEL_IN_USE;
+ return WRAP(DMA_CHAN_ERROR);
+ }
+
+ configure_dma_channel(
+ ret,
+ peripheral,
+ opts_in,
+ READ_FROM_MEMORY,
+ modesel,
+ /* mem2mem = */ false,
+ error_out);
+
+ if (*error_out) {
+ return WRAP(DMA_CHAN_ERROR);
+ }
+
+ *error_out = 0;
+ return WRAP(ret);
+#undef WRAP
+}
+
+dma_p2mem_channel_t select_dma_channel_p2mem(
+ dma_peripheral_t peripheral,
+ dma_opts_t* opts_in,
+ int* error_out)
+{
+#define WRAP(c) ((dma_p2mem_channel_t) { .c_ = c })
+ *error_out = 0;
+
+ int modesel;
+ dma_channel_t ret =
+ allocate_dma_channel(peripheral, &modesel);
+
+ if (!try_reserve_dma_channel(ret)) {
+ *error_out = DMA_ERROR_CHANNEL_IN_USE;
+ return WRAP(DMA_CHAN_ERROR);
+ }
+
+ configure_dma_channel(
+ ret,
+ peripheral,
+ opts_in,
+ READ_FROM_PERIPHERAL,
+ modesel,
+ /* mem2mem = */ false,
+ error_out);
+
+ if (*error_out) {
+ return WRAP(DMA_CHAN_ERROR);
+ }
+
+ *error_out = 0;
+ return WRAP(ret);
+#undef WRAP
+}
+
+
+void dma_mem2p_initiate_transfer(
+ dma_mem2p_channel_t chan, const void* from_loc, uint16_t nblocks)
+{
+ dma_channel_config_t* config = get_raw_channel_config(chan.c_);
+ config->cma_r = ptr2reg(from_loc);
+ config->cndt_r = nblocks;
+
+ regset(config->cc_r, dma_cc_en, 1);
+}
+
+void dma_mem2mem_initiate_transfer(
+ dma_mem2mem_channel_t chan,
+ void* to_loc,
+ const void* from_loc,
+ uint16_t nblocks)
+{
+ dma_channel_config_t* config = get_raw_channel_config(chan.c_);
+ config->cma_r = ptr2reg(to_loc);
+ config->cpa_r = ptr2reg(from_loc);
+ config->cndt_r = nblocks;
+
+ regset(config->cc_r, dma_cc_en, 1);
+}
+
+void dma_p2mem_initiate_transfer(
+ dma_p2mem_channel_t chan, void* to_loc, uint16_t nblocks)
+{
+ dma_channel_config_t* config = get_raw_channel_config(chan.c_);
+
+ config->cma_r = ptr2reg(to_loc);
+ config->cndt_r = nblocks;
+
+ regset(config->cc_r, dma_cc_en, 1);
+}
+
+interrupt_t dma_channel_get_interrupt(dma_channel_t chan)
+{
+ if (chan.dma == 0) {
+ return IRQ_DMA1_CHANNEL1_IRQ + chan.chan;
+ } else {
+ switch (chan.chan) {
+ case 5:
+ return IRQ_DMA1_CHANNEL6_IRQ;
+ case 6:
+ return IRQ_DMA1_CHANNEL7_IRQ;
+ default:
+ return IRQ_DMA2_CHANNEL1_IRQ + chan.chan;
+ }
+ }
+}
diff --git a/src/kern/gpio/gpio_manager.c b/src/kern/gpio/gpio_manager.c
new file mode 100644
index 0000000..82dd0ba
--- /dev/null
+++ b/src/kern/gpio/gpio_manager.c
@@ -0,0 +1,402 @@
+#include "kern/gpio/gpio_manager.h"
+
+#include "arch/stm32l4xxx/peripherals/irq.h"
+#include "arch/stm32l4xxx/peripherals/rcc.h"
+
+/* A list of whether the pins are in use or not as a bitmask. */
+uint32_t pins_inuse[N_GPIO_PINS / 32 + (N_GPIO_PINS % 32 != 0)];
+
+struct gpio_afn_and_pin {
+ int8_t afn_number;
+ gpio_pin_t gpio_pin;
+};
+
+/*
+ * Returns which (pin, afn) pairs provide the given alternate function.
+ * The out array needs to have 5 positions.
+ *
+ * This function will use afn_number = -1 as the terminal.
+ *
+ * Note that EVENTOUT is a special case because all pins have an event out
+ * at afn=15 and should be assumed by other logic and thus is not handled
+ * by this function.
+ */
+static void get_ports_and_pins_for_alternate_function(
+ gpio_alternate_function_t afn, struct gpio_afn_and_pin* out)
+{
+ switch (afn) {
+#define AFN1(fn, ...) \
+ static_assert(false, "Unable to parse afn_table at " #fn);
+#define AFN3(fn, ...) \
+ static_assert(false, "Unable to parse afn_table at " #fn);
+#define AFN5(fn, ...) \
+ static_assert(false, "Unable to parse afn_table at " #fn);
+#define AFN7(fn, ...) \
+ static_assert(false, "Unable to parse afn_table at " #fn);
+
+#define AFN2(fn, afn, pin) \
+ out[0].afn_number = afn; \
+ out[0].gpio_pin = GPIO_PIN_ ## pin
+
+#define AFN4(fn, afn0, pin0, afn1, pin1) \
+ AFN2(fn, afn0, pin0); \
+ out[1].afn_number = afn1; \
+ out[1].gpio_pin = GPIO_PIN_ ## pin1
+
+#define AFN6(fn, afn0, pin0, afn1, pin1, afn2, pin2) \
+ AFN4(fn, afn0, pin0, afn1, pin1); \
+ out[2].afn_number = afn2; \
+ out[2].gpio_pin = GPIO_PIN_ ## pin2
+
+#define AFN8(fn, afn0, pin0, afn1, pin1, afn2, pin2, afn3, pin3) \
+ AFN6(fn, afn0, pin0, afn1, pin1, afn2, pin2); \
+ out[2].afn_number = afn3; \
+ out[2].gpio_pin = GPIO_PIN_ ## pin3
+
+#define GET_MACRO(_1, _2, _3, _4, _5, _6, _7, _8, NAME, ...) NAME
+#define GET_N(_1, _2, _3, _4, _5, _6, _7, _8, NAME, ...) NAME
+#define AFN(fn, ...) \
+ case GPIO_ALTERNATE_FUNCTION_ ## fn: \
+ GET_MACRO(__VA_ARGS__, AFN8, AFN7, AFN6, AFN5, AFN4, AFN3, AFN2, AFN1)\
+ (fn, __VA_ARGS__); \
+ out[GET_N(__VA_ARGS__, 4, 4, 3, 3, 2, 2, 1, 1)] = \
+ (struct gpio_afn_and_pin){-1, -1}; \
+ break;
+
+#include "arch/stm32l4xxx/peripherals/tables/stm32l432xx/gpio/afn_table.inc"
+ case GPIO_ALTERNATE_FUNCTION_EVENTOUT:
+ return;
+ }
+}
+
+static inline int offset_for_gpio_pin(gpio_pin_t pin)
+{
+ switch (pin) {
+#define PORT(p, pn) \
+ case GPIO_PIN_P ## p ## pn: return pn;
+#include "arch/stm32l4xxx/peripherals/tables/stm32l432xx/gpio/port_table.inc"
+#undef PORT
+ case N_GPIO_PINS: return -1;
+ }
+
+ /* Should be unreachable. */
+ return -1;
+}
+
+inline bool gpio_pin_in_use(gpio_pin_t pin)
+{
+ return !!(pins_inuse[pin / 32] & (1 << (pin % 32)));
+}
+
+#define A(...)
+#define B(...)
+#define C(...)
+#define D(...)
+#define E(...)
+#define F(...)
+#define G(...)
+#define H(...)
+#define I(...)
+#define SELECT_MACRO(PORT) PORT
+#define PORT(port, pin) \
+ SELECT_MACRO(port)(GPIO_PIN_P ## port ## pin, pin)
+static int gc_port_a()
+{
+ return 0
+#undef A
+#define A(abspin, relpin) \
+ | (gpio_pin_in_use(abspin) << (relpin))
+#include "arch/stm32l4xxx/peripherals/tables/stm32l432xx/gpio/port_table.inc"
+ ;
+#undef A
+#define A(...)
+}
+
+static int gc_port_b()
+{
+ return 0
+#undef B
+#define B(abspin, relpin) \
+ | (gpio_pin_in_use(abspin) << (relpin))
+#include "arch/stm32l4xxx/peripherals/tables/stm32l432xx/gpio/port_table.inc"
+ ;
+#undef B
+#define B(...)
+}
+
+static int gc_port_c()
+{
+ return 0
+#undef C
+#define C(abspin, relpin) \
+ | (gpio_pin_in_use(abspin) << (relpin))
+#include "arch/stm32l4xxx/peripherals/tables/stm32l432xx/gpio/port_table.inc"
+ ;
+#undef C
+#define C(...)
+}
+
+static int gc_port_d()
+{
+ return 0
+#undef D
+#define D(abspin, relpin) \
+ | (gpio_pin_in_use(abspin) << (relpin))
+#include "arch/stm32l4xxx/peripherals/tables/stm32l432xx/gpio/port_table.inc"
+ ;
+#undef D
+#define D(...)
+}
+
+static int gc_port_e()
+{
+ return 0
+#undef E
+#define E(abspin, relpin) \
+ | (gpio_pin_in_use(abspin) << (relpin))
+#include "arch/stm32l4xxx/peripherals/tables/stm32l432xx/gpio/port_table.inc"
+ ;
+#undef E
+#define E(...)
+}
+
+static int gc_port_f()
+{
+ return 0
+#undef F
+#define F(abspin, relpin) \
+ | (gpio_pin_in_use(abspin) << (relpin))
+#include "arch/stm32l4xxx/peripherals/tables/stm32l432xx/gpio/port_table.inc"
+ ;
+#undef F
+#define F(...)
+}
+
+static int gc_port_g()
+{
+ return 0
+#undef G
+#define G(abspin, relpin) \
+ | (gpio_pin_in_use(abspin) << (relpin))
+#include "arch/stm32l4xxx/peripherals/tables/stm32l432xx/gpio/port_table.inc"
+ ;
+#undef G
+#define G(...)
+}
+
+static int gc_port_h()
+{
+ return 0
+#undef H
+#define H(abspin, relpin) \
+ | (gpio_pin_in_use(abspin) << (relpin))
+#include "arch/stm32l4xxx/peripherals/tables/stm32l432xx/gpio/port_table.inc"
+ ;
+#undef H
+#define H(...)
+}
+
+static int gc_port_i()
+{
+ return 0
+#undef I
+#define I(abspin, relpin) \
+ | (gpio_pin_in_use(abspin) << (relpin))
+#include "arch/stm32l4xxx/peripherals/tables/stm32l432xx/gpio/port_table.inc"
+ ;
+#undef I
+#define I(...)
+}
+
+
+static inline bool gpio_pin_try_reserve(gpio_pin_t pin)
+{
+ int in_use = __sync_fetch_and_or(
+ &pins_inuse[pin / 32], 1 << (pin % 32));
+ return !(in_use & (1 << (pin % 32)));
+}
+
+inline static gpio_port_config_t* get_gpio_port_config(gpio_port_t port)
+{
+ switch(port) {
+ case GPIO_PORT_A: return (gpio_port_config_t*) GPIOA_BASE;
+ case GPIO_PORT_B: return (gpio_port_config_t*) GPIOB_BASE;
+ case GPIO_PORT_C: return (gpio_port_config_t*) GPIOC_BASE;
+ case GPIO_PORT_H: return (gpio_port_config_t*) GPIOH_BASE;
+ default: return NULL;
+ }
+}
+
+inline static gpio_port_config_t* get_gpio_port_config_for_pin(gpio_pin_t pin)
+{
+ gpio_port_t port = get_port_for_pin(pin);
+ return get_gpio_port_config(port);
+}
+
+gpio_reserved_pin_t reserve_gpio_pin(
+ gpio_pin_t pin, gpio_pin_opts_t* opts, int* error_out)
+{
+ *error_out = 0;
+ if (!gpio_pin_try_reserve(pin)) {
+ *error_out = GPIO_ERROR_IN_USE;
+ return (gpio_reserved_pin_t) { .v_ = -1 };
+ }
+
+ gpio_port_t port = get_port_for_pin(pin);
+ regset(RCC.ahb2en_r, rcc_gpioen(port), 1);
+
+ gpio_port_config_t* port_config = get_gpio_port_config(port);
+
+
+ int off = offset_for_gpio_pin(pin);
+
+ regset(port_config->mode_r, gpio_mode_n(off), opts->mode);
+ regset(port_config->pupd_r, gpio_pupd_n(off), opts->pull_dir);
+
+ switch(opts->mode) {
+ case GPIO_MODE_INPUT:
+ break;
+
+ case GPIO_MODE_OUTPUT:
+ regset(port_config->ospeed_r, gpio_ospeed_n(off), opts->output_opts.speed);
+ regset(port_config->otype_r, gpio_otype_n(off), opts->output_opts.type);
+ break;
+
+ case GPIO_MODE_ALTERNATE:
+ if (off < 8) {
+ regset(
+ port_config->af_rl,
+ gpio_afsel_n(off),
+ opts->alternate_opts.function);
+ } else {
+ regset(
+ port_config->af_rh,
+ gpio_afsel_n(off - 8),
+ opts->alternate_opts.function);
+ }
+ break;
+
+ case GPIO_MODE_ANALOG:
+ regset(port_config->asc_r, gpio_asc_n(off), 1);
+ break;
+ }
+
+ return (gpio_reserved_pin_t) { .v_ = pin };
+}
+
+gpio_reserved_pin_t gpio_enable_alternate_function(
+ gpio_alternate_function_t fn,
+ gpio_pin_t hint,
+ int* error_out)
+{
+ int i = 0;
+ gpio_pin_opts_t opts;
+ struct gpio_afn_and_pin afn_and_pin[5];
+
+ if (gpio_pin_out_of_range(hint) && hint != -1) {
+ *error_out = GPIO_ERROR_INVALID_PIN;
+ return (gpio_reserved_pin_t) { .v_ = -1 };
+ }
+
+ opts.mode = GPIO_MODE_ALTERNATE;
+
+ if (fn == GPIO_ALTERNATE_FUNCTION_EVENTOUT) {
+ afn_and_pin[i].afn_number = GPIO_ALTERNATE_FUNCTION_EVENTOUT;
+ if (hint == -1) {
+ hint = GPIO_PIN_PA0;
+ }
+ afn_and_pin[i].gpio_pin = hint;
+ } else {
+ get_ports_and_pins_for_alternate_function(fn, afn_and_pin);
+
+ if (hint == -1) {
+ hint = afn_and_pin[0].gpio_pin;
+ }
+
+ for(i = 0;
+ i < 5
+ && afn_and_pin[i].gpio_pin != hint
+ && afn_and_pin[i].gpio_pin != -1;
+ ++ i);
+
+ if (afn_and_pin[i].gpio_pin == -1 || i == 5) {
+ *error_out = GPIO_ERROR_INVALID_PIN_FOR_ALTERNATE_FUNCTION;
+ return (gpio_reserved_pin_t) { .v_ = -1 };
+ }
+ }
+
+ opts.alternate_opts.function = afn_and_pin[i].afn_number;
+ return reserve_gpio_pin(afn_and_pin[i].gpio_pin, &opts, error_out);
+}
+
+void release_gpio_pin(gpio_reserved_pin_t rpin)
+{
+ gpio_pin_t pin = rpin.v_;
+ // TODO this should be a critical section.
+ gpio_port_t port = get_port_for_pin(pin);
+ pins_inuse[pin / 32] &= ~(1 << (pin % 32));
+
+ int used;
+ switch(port) {
+ case GPIO_PORT_A:
+ used = gc_port_a();
+ break;
+ case GPIO_PORT_B:
+ used = gc_port_b();
+ break;
+ case GPIO_PORT_C:
+ used = gc_port_c();
+ break;
+ case GPIO_PORT_D:
+ used = gc_port_d();
+ break;
+ case GPIO_PORT_E:
+ used = gc_port_e();
+ break;
+ case GPIO_PORT_F:
+ used = gc_port_f();
+ break;
+ case GPIO_PORT_G:
+ used = gc_port_g();
+ break;
+ case GPIO_PORT_H:
+ used = gc_port_h();
+ break;
+ case GPIO_PORT_I:
+ used = gc_port_i();
+ break;
+
+ case N_GPIO_PORTS:
+ used = 1;
+ break;
+ }
+
+ if (!used) {
+ regset(RCC.ahb2en_r, rcc_gpioen(port), 0);
+ }
+}
+
+inline void get_gpio_pin