diff --git a/epicardium/modules/buttons.c b/epicardium/modules/buttons.c index 91f5ad02d9000e22cbf13b93c9d2170891856da6..14e9e613719e3e5279f811393afdfaf843025430 100644 --- a/epicardium/modules/buttons.c +++ b/epicardium/modules/buttons.c @@ -26,7 +26,7 @@ uint8_t epic_buttons_read(uint8_t mask) * Not using PB_Get() here as that performs one I2C transcation * per button. */ - uint8_t pin_status = ~portexpander_get(); + uint8_t pin_status = ~portexpander_in_get(0xFF); hwlock_release(HWLOCK_I2C); diff --git a/lib/card10/card10.c b/lib/card10/card10.c index 371912d5909ce5f479f7761ea3fe5b5d87e159e0..306d9f4d7c0ac720145f991072ce61475d3bf528 100644 --- a/lib/card10/card10.c +++ b/lib/card10/card10.c @@ -215,6 +215,7 @@ void core1_stop(void) void card10_poll(void) { pmic_poll(); + portexpander_poll(); } void card10_reset(void) diff --git a/lib/card10/display.c b/lib/card10/display.c index f5fd471579a41a0ea520f35594dd0553b089490d..f9c2f90d7e7e0fa0a0d25d86cf0a76844217bc98 100644 --- a/lib/card10/display.c +++ b/lib/card10/display.c @@ -22,7 +22,7 @@ void display_set_reset_pin(uint8_t state) if (!portexpander_detected()) { MAX77650_setDO(state ? true : false); } else { - portexpander_set(4, state); + portexpander_out_put(PIN_4, state); } } diff --git a/lib/card10/pb.c b/lib/card10/pb.c index 42525527a86aa117121638863c47b5f20b271a3f..727882ddd57a724d25180bea1ae835b2008932c6 100644 --- a/lib/card10/pb.c +++ b/lib/card10/pb.c @@ -116,7 +116,7 @@ int PB_Get(unsigned int pb) case 3: case 4: if (portexpander_detected()) { - uint8_t port = portexpander_get(); + uint8_t port = portexpander_in_get(0xFF); return (port & (1 << expander_pins[pb - 1])) == 0; } else { return GPIO_InGet(&pb_pin[pb - 1]) == 0; diff --git a/lib/card10/portexpander.c b/lib/card10/portexpander.c index c6a8c302292828c5796d6b8f95a82fc1a9db8e26..b799e21529fe08ebd991636faf5b798487c5bd22 100644 --- a/lib/card10/portexpander.c +++ b/lib/card10/portexpander.c @@ -1,5 +1,10 @@ +/* PCAL6408A I2C port expander */ + +/* **** Includes **** */ #include "portexpander.h" +#include "mxc_config.h" +#include "mxc_assert.h" #include "i2c.h" #include <stdio.h> @@ -7,10 +12,7 @@ #include <string.h> #include <stdbool.h> -// PCAL6408A I2C port expander - -static bool detected = false; -static uint8_t output_state; +/* **** Definitions **** */ /* clang-format off */ #define PE_ADDR 0x42 @@ -40,54 +42,147 @@ static uint8_t output_state; #define PE_INPUT_MASK ((uint8_t)0b01101000) // 3, 5, 6 = input +/* **** Globals **** */ + +static bool detected = false; + +static volatile bool interrupt_pending; + +static uint8_t type_state = 0xFF; +static uint8_t output_state = 0xFF; +static uint8_t pull_enable_state = 0x00; +static uint8_t pull_selection_state = 0xFF; +static uint8_t int_mask_state = 0xFF; + +static gpio_int_pol_t int_edge_config[8] = { 0 }; + +static pe_callback callbacks[8] = { NULL }; +static void *cbparam[8] = { NULL }; + +const gpio_cfg_t pe_int_pin = { PORT_1, PIN_7, GPIO_FUNC_IN, GPIO_PAD_PULL_UP }; + +static const portexpander_cfg_t pe_pin_config[] = { + { PE_INPUT_MASK, GPIO_FUNC_IN, GPIO_PAD_PULL_UP }, + { ~PE_INPUT_MASK, GPIO_FUNC_OUT, GPIO_PAD_PULL_UP }, +}; + +/* **** Functions **** */ + static int portexpander_write(uint8_t command, uint8_t data) { uint8_t i2c_data[2] = { command, data }; return I2C_MasterWrite(MXC_I2C1_BUS0, PE_ADDR, i2c_data, 2, 0); } +/* ************************************************************************** */ static int portexpander_read(uint8_t command, uint8_t *data) { I2C_MasterWrite(MXC_I2C1_BUS0, PE_ADDR, &command, 1, 1); return I2C_MasterRead(MXC_I2C1_BUS0, PE_ADDR, data, 1, 0); } -void portexpander_init(void) +/* ************************************************************************** */ +int portexpander_init(void) { int ret; - // Enable pull-ups for buttons (type defaults to pull-up) - ret = portexpander_write(PE_C_PULL_ENABLE, PE_INPUT_MASK); - + // Set _all_ outputs to open-drain to support the high side p-channel transistors. + ret = portexpander_write(PE_C_OUTPUT_PORT_CONFIG, PE_OUT_OPEN_DRAIN); if (ret != 2) { printf("portexpander NOT detected\n"); detected = false; - return; + return E_NO_DEVICE; } detected = true; - // Set _all_ outputs to open-drain to support the high side p-channel transistors. - portexpander_write(PE_C_OUTPUT_PORT_CONFIG, PE_OUT_OPEN_DRAIN); + // Set outputs to high + portexpander_out_set(~PE_INPUT_MASK); + // Enable pull-ups for buttons // Enable outputs for the transistors, the LED and the LCD reset - portexpander_write(PE_C_CONFIG, PE_INPUT_MASK); + for (int i = 0; i < sizeof(pe_pin_config) / sizeof(pe_pin_config[0]); + i++) { + MXC_ASSERT( + portexpander_config(&pe_pin_config[i]) == E_NO_ERROR + ); + } + + // Latch inputs so we can figure out whether an interrupt was caused by a rising or falling edge + portexpander_write(PE_C_INPUT_LATCH, PE_INPUT_MASK); + + // Configure interrupt GPIO + MXC_ASSERT(GPIO_Config(&pe_int_pin) == E_NO_ERROR); + + // Configure and enable portexpander interrupt + GPIO_RegisterCallback( + &pe_int_pin, &portexpander_interrupt_callback, NULL + ); + MXC_ASSERT( + GPIO_IntConfig(&pe_int_pin, GPIO_INT_EDGE, GPIO_INT_FALLING) == + E_NO_ERROR); + GPIO_IntEnable(&pe_int_pin); + NVIC_EnableIRQ((IRQn_Type)MXC_GPIO_GET_IRQ(pe_int_pin.port)); + + return E_SUCCESS; +} + +/* ************************************************************************** */ +int portexpander_config(const portexpander_cfg_t *cfg) +{ + // Set the GPIO type + switch (cfg->func) { + case GPIO_FUNC_IN: + type_state |= cfg->mask; + break; + case GPIO_FUNC_OUT: + type_state &= ~cfg->mask; + break; + default: + return E_BAD_PARAM; + } - // Set outputs to high (i.e. open-drain) - output_state = ~PE_INPUT_MASK; - portexpander_write(PE_C_OUTPUT_PORT, output_state); + if (portexpander_write(PE_C_CONFIG, type_state) != 2) { + return E_NO_DEVICE; + } + + switch (cfg->pad) { + case GPIO_PAD_NONE: + pull_enable_state &= ~cfg->mask; + break; + case GPIO_PAD_PULL_UP: + pull_selection_state |= cfg->mask; + pull_enable_state |= cfg->mask; + break; + case GPIO_PAD_PULL_DOWN: + pull_selection_state &= ~cfg->mask; + pull_enable_state |= cfg->mask; + break; + default: + return E_BAD_PARAM; + } + + portexpander_write(PE_C_PULL_ENABLE, pull_selection_state); + portexpander_write(PE_C_PULL_ENABLE, pull_enable_state); + + return E_NO_ERROR; } -uint8_t portexpander_get(void) +/* ************************************************************************** */ +uint8_t portexpander_in_get(uint8_t mask) { + // Reading the input port clears interrupts, so we need to check them here to avoid losing information + portexpander_poll(); + uint8_t buf = 0xFF; if (detected) { portexpander_read(PE_C_INPUT_PORT, &buf); } - return buf; + return buf & mask; } +/* ************************************************************************** */ bool portexpander_detected(void) { return detected; @@ -106,6 +201,24 @@ void portexpander_set(uint8_t pin, uint8_t value) } } +/* ************************************************************************** */ +void portexpander_out_set(uint8_t mask) +{ + if (detected) { + output_state |= mask; + portexpander_write(PE_C_OUTPUT_PORT, output_state); + } +} + +/* ************************************************************************** */ +void portexpander_out_clr(uint8_t mask) +{ + if (detected) { + output_state &= ~mask; + portexpander_write(PE_C_OUTPUT_PORT, output_state); + } +} + void portexpander_prep(uint8_t pin, uint8_t value) { if (pin < 8) { @@ -117,6 +230,12 @@ void portexpander_prep(uint8_t pin, uint8_t value) } } +/* ************************************************************************** */ +uint8_t portexpander_out_get(uint8_t mask) +{ + return output_state & mask; +} + void portexpander_update(void) { if (detected) { @@ -124,11 +243,126 @@ void portexpander_update(void) } } -void portexpander_set_mask(uint8_t mask, uint8_t values) +/* ************************************************************************** */ +void portexpander_out_put(uint8_t mask, uint8_t val) +{ + if (detected) { + output_state = (output_state & ~mask) | (val & mask); + portexpander_write(PE_C_OUTPUT_PORT, output_state); + } +} + +/* ************************************************************************** */ +void portexpander_out_toggle(uint8_t mask) { if (detected) { - output_state &= ~(mask & ~values); - output_state |= mask & values; + output_state ^= mask; portexpander_write(PE_C_OUTPUT_PORT, output_state); } } + +/* ************************************************************************** */ +void portexpander_int_config(uint8_t mask, gpio_int_pol_t edge) +{ + if (detected) { + for (uint8_t pin = 0; pin < 8; ++pin) { + if (mask & (1 << pin)) { + int_edge_config[pin] = edge; + } + } + } +} + +/* ************************************************************************** */ +void portexpander_int_enable(uint8_t mask) +{ + if (detected) { + int_mask_state &= ~mask; + portexpander_write(PE_C_INT_MASK, int_mask_state); + } +} + +/* ************************************************************************** */ +void portexpander_int_disable(uint8_t mask) +{ + if (detected) { + int_mask_state |= mask; + portexpander_write(PE_C_INT_MASK, int_mask_state); + } +} + +/* ************************************************************************** */ +uint8_t portexpander_int_status() +{ + uint8_t buf = 0; + if (detected) { + portexpander_read(PE_C_INT_STATUS, &buf); + } + + return buf; +} + +/* ************************************************************************** */ +void portexpander_int_clr(uint8_t mask) +{ + if (detected) { + uint8_t tmp_mask = int_mask_state | mask; + + // Setting an interrupt mask clears the corresponding interrupt + portexpander_write(PE_C_INT_MASK, tmp_mask); + portexpander_write(PE_C_INT_MASK, int_mask_state); + } +} + +/* ************************************************************************** */ +int portexpander_register_callback( + uint8_t mask, pe_callback callback, void *cbdata +) { + if (!detected) { + return E_NO_DEVICE; + } + + for (uint8_t pin = 0; pin < 8; ++pin) { + if (mask & (1 << pin)) { + callbacks[pin] = callback; + cbparam[pin] = cbdata; + } + } + + return E_NO_ERROR; +} + +/* ************************************************************************** */ +__attribute__((weak)) void portexpander_interrupt_callback(void *_) +{ + GPIO_IntDisable(&pe_int_pin); + GPIO_IntClr(&pe_int_pin); + interrupt_pending = true; +} + +/* ************************************************************************** */ +void portexpander_poll() +{ + if (detected && interrupt_pending) { + interrupt_pending = false; + + uint8_t caused_by = portexpander_int_status(); + // Port read resets interrupts + uint8_t port_levels = portexpander_in_get(0xFF); + + GPIO_IntEnable(&pe_int_pin); + + for (uint8_t pin = 0; pin < 8; ++pin) { + if ((caused_by & (1 << pin)) && callbacks[pin]) { + gpio_int_pol_t edge_type = + (port_levels & (1 << pin) ? + GPIO_INT_RISING : + GPIO_INT_FALLING); + if ((int_edge_config[pin] == GPIO_INT_BOTH) || + (edge_type == int_edge_config[pin])) { + callbacks[pin](cbparam[pin]); + } + } + } + } +} diff --git a/lib/card10/portexpander.h b/lib/card10/portexpander.h index 86614bdd09305ae725fd7d2145c058e184602ed2..727f8dfe5eed2aa7fa992597e4209987b0abede9 100644 --- a/lib/card10/portexpander.h +++ b/lib/card10/portexpander.h @@ -1,15 +1,48 @@ #ifndef PORTEXPANDER_H #define PORTEXPANDER_H +#include "mxc_config.h" + #include <stdint.h> #include <stdbool.h> -void portexpander_init(void); -uint8_t portexpander_get(void); void portexpander_set(uint8_t pin, uint8_t value); -void portexpander_set_mask(uint8_t mask, uint8_t values); void portexpander_prep(uint8_t pin, uint8_t value); void portexpander_update(void); + +/** + * Structure type for configuring the portexpander. + */ +typedef struct { + uint8_t mask; /**< Pin mask (multiple pins may be set) */ + gpio_func_t func; /**< Function type */ + gpio_pad_t pad; /**< Pad type */ +} portexpander_cfg_t; + + +typedef void (*pe_callback)(void *cbdata); + +int portexpander_init(void); bool portexpander_detected(void); +int portexpander_config(const portexpander_cfg_t *cfg); + +uint8_t portexpander_in_get(uint8_t mask); + +void portexpander_out_set(uint8_t mask); +void portexpander_out_clr(uint8_t mask); +void portexpander_out_put(uint8_t mask, uint8_t val); +void portexpander_out_toggle(uint8_t mask); +uint8_t portexpander_out_get(uint8_t mask); + +void portexpander_int_config(uint8_t mask, gpio_int_pol_t edge); +void portexpander_int_enable(uint8_t mask); +void portexpander_int_disable(uint8_t mask); +uint8_t portexpander_int_status(); +void portexpander_int_clr(uint8_t mask); +int portexpander_register_callback(uint8_t mask, pe_callback callback, void *cbdata); +void portexpander_poll(); + +void portexpander_interrupt_callback(void *_); + #endif