diff options
Diffstat (limited to 'src/kern/gpio/gpio_manager.c')
-rw-r--r-- | src/kern/gpio/gpio_manager.c | 402 |
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); +} |