aboutsummaryrefslogtreecommitdiff
path: root/src/kern/gpio/gpio_manager.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/kern/gpio/gpio_manager.c')
-rw-r--r--src/kern/gpio/gpio_manager.c402
1 files changed, 402 insertions, 0 deletions
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_port_off(
+ gpio_pin_t pin, gpio_port_config_t** out_cfg, int* out_off)
+{
+ *out_cfg = get_gpio_port_config_for_pin(pin);
+ *out_off = offset_for_gpio_pin(pin);
+}
+
+void set_gpio_pin_high(gpio_reserved_pin_t pin)
+{
+ int off;
+ gpio_port_config_t* portcfg;
+ get_gpio_pin_port_off(pin.v_, &portcfg, &off);
+
+ regset(portcfg->od_r, (1 << off), 1);
+}
+
+void set_gpio_pin_low(gpio_reserved_pin_t pin)
+{
+ int off;
+ gpio_port_config_t* portcfg;
+ get_gpio_pin_port_off(pin.v_, &portcfg, &off);
+
+ regset(portcfg->od_r, (1 << off), 0);
+}