From 70ebc0651759a48eda9a4c746ea24594f486d893 Mon Sep 17 00:00:00 2001 From: dron0gus Date: Tue, 12 Mar 2019 17:12:37 +0300 Subject: [PATCH] gpiochips API, core and unit test (#703) * Add external gpiochips driver API and core * gpiochips: add unit test --- firmware/Makefile | 4 + firmware/hw_layer/drivers/drivers.mk | 9 + firmware/hw_layer/drivers/gpio/core.c | 285 ++++++++++++++++++++++ firmware/hw_layer/drivers/gpio/gpio_ext.h | 61 +++++ unit_tests/Makefile | 3 + unit_tests/efifeatures.h | 2 + unit_tests/test.mk | 1 + unit_tests/tests/test_gpiochip.cpp | 146 +++++++++++ 8 files changed, 511 insertions(+) create mode 100644 firmware/hw_layer/drivers/drivers.mk create mode 100644 firmware/hw_layer/drivers/gpio/core.c create mode 100644 firmware/hw_layer/drivers/gpio/gpio_ext.h create mode 100644 unit_tests/tests/test_gpiochip.cpp diff --git a/firmware/Makefile b/firmware/Makefile index 3aa48de0f9..b1df315330 100644 --- a/firmware/Makefile +++ b/firmware/Makefile @@ -132,6 +132,7 @@ include console/binary/tunerstudio.mk include $(PROJECT_DIR)/ext/fatfs.mk include $(PROJECT_DIR)/hw_layer/hw_layer.mk +include $(PROJECT_DIR)/hw_layer/drivers/drivers.mk include $(PROJECT_DIR)/hw_layer/sensors/sensors.mk include $(PROJECT_DIR)/hw_layer/mass_storage/mass_storage.mk include $(PROJECT_DIR)/development/development.mk @@ -181,6 +182,8 @@ CSRC = $(STARTUPSRC) \ $(CONSOLESRC) \ $(DEV_SRC) \ $(HW_LAYER_EMS) \ + $(HW_LAYER_DRIVERS_CORE) \ + $(HW_LAYER_DRIVERS) \ $(CONTROLLERSSRC) \ $(CONTROLLERS_ALGO_SRC) \ $(CONTROLLERS_CORE_SRC) \ @@ -272,6 +275,7 @@ INCDIR = $(CHIBIOS)/os/license \ hw_layer/sensors \ hw_layer/mass_storage \ hw_layer/$(CPU_HWLAYER) \ + $(HW_LAYER_DRIVERS_INC) \ development \ development/hw_layer \ development/test \ diff --git a/firmware/hw_layer/drivers/drivers.mk b/firmware/hw_layer/drivers/drivers.mk new file mode 100644 index 0000000000..d23c06ed1c --- /dev/null +++ b/firmware/hw_layer/drivers/drivers.mk @@ -0,0 +1,9 @@ +DRIVERS_DIR=$(PROJECT_DIR)/hw_layer/drivers + +HW_LAYER_DRIVERS_INC = \ + $(DRIVERS_DIR) + +HW_LAYER_DRIVERS_CORE = \ + $(DRIVERS_DIR)/gpio/core.c \ + +HW_LAYER_DRIVERS = diff --git a/firmware/hw_layer/drivers/gpio/core.c b/firmware/hw_layer/drivers/gpio/core.c new file mode 100644 index 0000000000..d3f903abde --- /dev/null +++ b/firmware/hw_layer/drivers/gpio/core.c @@ -0,0 +1,285 @@ +/** + * @file gpio/core.c + * @brief EFI-related GPIO code for external gpio chips + * + * @date Mar 8, 2019 + * @author Andrey Gusakov, (c) 2019 + */ + +#include "global.h" +#include "gpio/gpio_ext.h" + +#if (BOARD_EXT_GPIOCHIPS > 0) + +/*==========================================================================*/ +/* Local definitions. */ +/*==========================================================================*/ + +/* fist available gpio number after on-chip gpios */ +#define EXT_GPIOS_FIRST (GPIOH_15 + 1) +static size_t gpio_base_free = EXT_GPIOS_FIRST; + +/*==========================================================================*/ +/* Exported variables. */ +/*==========================================================================*/ + +/*==========================================================================*/ +/* Local variables and types. */ +/*==========================================================================*/ + +/* TODO: chnage array to list? */ +struct gpiochip { + size_t base; + size_t size; + struct gpiochip_ops *ops; + const char *name; + /* optional names of each gpio */ + const char **gpio_names; + /* private driver data passed to ops */ + void *priv; +}; + +static struct gpiochip chips[BOARD_EXT_GPIOCHIPS]; + +/*==========================================================================*/ +/* Local functions. */ +/*==========================================================================*/ + +static struct gpiochip *gpiochip_find(unsigned int pin) +{ + int i; + + for (i = 0; i < BOARD_EXT_GPIOCHIPS; i++) { + struct gpiochip *chip = &chips[i]; + + if ((pin >= chip->base) && (pin < (chip->base + chip->size))) + return chip; + } + + return NULL; +} + +/*==========================================================================*/ +/* Exported functions. */ +/*==========================================================================*/ + +/** + * return numeric part of EXTERNAL pin name. + * @details + */ + +int getHwPinExt(unsigned int pin) +{ + struct gpiochip *chip = gpiochip_find(pin); + + if (chip) + return pin - chip->base; + + return EFI_ERROR_CODE; +} + +/** + * @brief Register gpiochip + * @details return pin name or gpiochip name (if no pins names provided) + */ + +const char *portNameExt(unsigned int pin) +{ + struct gpiochip *chip = gpiochip_find(pin); + + if (chip) { + if (chip->gpio_names) + return chip->gpio_names[pin - chip->base]; + else + return chip->name; + } + + return NULL; +} + +/** + * @brief Register gpiochip + * @details should be called from board file. Can be called before os ready. + * All chips should be registered before gpiochips_init() called. + * returns -1 in case of no free chips left + * returns -1 in case of no ops provided, incorrect chip size + * else returns chip base + */ + +int gpiochip_register(const char *name, struct gpiochip_ops *ops, size_t size, void *priv) +{ + int i; + struct gpiochip *chip = NULL; + + /* no ops provided, zero size? */ + if ((!ops) || (!size)) + return -1; + + if ((!ops->writePad) && (!ops->readPad)) + return -1; + + /* find free gpiochip struct */ + for (i = 0; i < BOARD_EXT_GPIOCHIPS; i++) { + if (chips[i].base == 0) { + chip = &chips[i]; + break; + } + } + + /* no free chips left */ + if (chip == NULL) + return -1; + + /* register chip */ + chip->name = name; + chip->ops = ops; + chip->base = gpio_base_free; + chip->size = size; + chip->priv = priv; + + gpio_base_free += size; + + return (chip->base); +} + +/** + * @brief Init all registered gpiochips + * @details will call gpiochip init ops for all registered chips + * calles when OS is ready, so gpiochip can start threads, use drivers and so on. + */ + +int gpiochips_init(void) +{ + int i; + int ret; + int pins_added = 0; + + for (i = 0; i < BOARD_EXT_GPIOCHIPS; i++) { + struct gpiochip *chip = &chips[i]; + + if (!chip->base) + continue; + + if (chip->ops->init) + ret = chip->ops->init(chip->priv); + + if (ret < 0) { + /* remove chip if it fails to init */ + /* TODO: we will have a gap, is it ok? */ + chip->base = 0; + } else { + pins_added += chip->size; + } + } + + return pins_added; +} + +/** + * @brief Set pin mode of gpiochip + * @details set pad mode for given pin. + * return -1 if driver does not implemet setPadMode ops + * else return value from gpiochip driver. + */ + +int gpiochips_setPadMode(brain_pin_e pin, int mode) +{ + struct gpiochip *chip = gpiochip_find(pin); + + if (!chip) + return -1; + + if ((chip->ops->setPadMode)) + return chip->ops->setPadMode(chip->priv, pin - chip->base, mode); + + return -1; +} + +/** + * @brief Set value to gpio of gpiochip + * @details actual output value depent on current gpiochip implementation + * for smart switch inactive supposed to be closed switch (no current flows) + * returns -1 in case of pin not belong to any gpio chip + * returns -1 in case of chip does not support seting output value (input only) + * else return value from gpiochip driver; + */ + +int gpiochips_writePad(brain_pin_e pin, int value) +{ + struct gpiochip *chip = gpiochip_find(pin); + + if (!chip) + return -1; + + if (chip->ops->writePad) + return chip->ops->writePad(chip->priv, pin - chip->base, value); + + return -1; +} + +/** + * @brief Get value to gpio of gpiochip + * @details actual input value depent on current gpiochip implementation + * returns -1 in case of pin not belong to any gpio chip + * returns -1 in case of chip does not support getting output value (output only) + * else return value from gpiochip driver; + */ + +int gpiochips_readPad(brain_pin_e pin) +{ + struct gpiochip *chip = gpiochip_find(pin); + + if (!chip) + return -1; + + if (chip->ops->readPad) + return chip->ops->readPad(chip->priv, pin - chip->base); + + return -1; +} + +/** + * @brief Get total pin count allocated for external gpio chips. + * @details Will also include unused pins for chips that was registred + * but later fails to init. + */ + +int gpiochips_get_total_pins(void) +{ + return (gpio_base_free - EXT_GPIOS_FIRST); +} + +#else /* BOARD_EXT_GPIOCHIPS > 0 */ + +int getHwPinExt(unsigned int pin) +{ + (void)pin; + + return -1; +} + +const char *portNameExt(unsigned int pin) +{ + (void)pin; + + return NULL; +} + +int gpiochip_register(const char *name, struct gpiochip_ops *ops, size_t size, void *priv) +{ + (void)name; (void)ops; (void)size; (void)priv; + + return 0; +} + +int gpiochips_init(void) +{ + return 0; +} + +int gpiochips_get_total_pins(void) +{ + return 0; +} + +#endif /* BOARD_EXT_GPIOCHIPS > 0 */ diff --git a/firmware/hw_layer/drivers/gpio/gpio_ext.h b/firmware/hw_layer/drivers/gpio/gpio_ext.h new file mode 100644 index 0000000000..924a87e9f7 --- /dev/null +++ b/firmware/hw_layer/drivers/gpio/gpio_ext.h @@ -0,0 +1,61 @@ +/* + * gpio_ext.h + * + * Abstraction layer definitions for extrenal gpios + * + * @date Mar 8, 2019 + * @author Andrey Gusakov, (c) 2019 + */ + +#ifndef GPIO_EXT_H_ +#define GPIO_EXT_H_ + +#if EFI_PROD_CODE +#include "board.h" +#endif + +#include "rusefi_enums.h" + +/*==========================================================================*/ +/* Checks */ +/*==========================================================================*/ + +#ifndef BOARD_EXT_GPIOCHIPS + #define BOARD_EXT_GPIOCHIPS 0 +#endif + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +struct gpiochip_ops { + int (*setPadMode)(void *data, unsigned int pin, int mode); + int (*writePad)(void *data, unsigned int pin, int value); + int (*readPad)(void *data, unsigned int pin); + int (*getDiag)(void *data, unsigned int pin); + int (*init)(void *data); + int (*deinit)(void *data); +}; + +int getHwPinExt(unsigned int pin); +const char *portNameExt(unsigned int pin); + +/* register gpio cgip */ +int gpiochip_register(const char *name, struct gpiochip_ops *ops, size_t size, void *priv); + +/* gpio extenders subsystem init */ +int gpiochips_init(void); + +int gpiochips_setPadMode(brain_pin_e pin, int mode); +int gpiochips_writePad(brain_pin_e pin, int value); +int gpiochips_readPad(brain_pin_e pin); + +/* return total number of external gpios */ +int gpiochips_get_total_pins(void); + +#ifdef __cplusplus +} +#endif + +#endif /* GPIO_EXT_H_ */ diff --git a/unit_tests/Makefile b/unit_tests/Makefile index dfd8aa0bd7..6977c4a8fc 100644 --- a/unit_tests/Makefile +++ b/unit_tests/Makefile @@ -79,6 +79,7 @@ include $(PROJECT_DIR)/controllers/system/system.mk include $(PROJECT_DIR)/controllers/sensors/sensors.mk include $(PROJECT_DIR)/controllers/trigger/trigger.mk include $(PROJECT_DIR)/hw_layer/hw_layer.mk +include $(PROJECT_DIR)/hw_layer/drivers/drivers.mk include $(PROJECT_DIR)/hw_layer/sensors/sensors.mk include test.mk @@ -94,6 +95,7 @@ CSRC = $(UTILSRC) \ $(CONTROLLERS_MATH_SRC) \ $(CONTROLLERS_SENSORS_SRC) \ $(ENGINES_SRC) \ + $(HW_LAYER_DRIVERS_CORE) \ $(TEST_SRC_C) # C++ sources that can be compiled in ARM or THUMB mode depending on the global @@ -155,6 +157,7 @@ INCDIR = . \ $(PROJECT_DIR)/hw_layer \ $(PROJECT_DIR)/hw_layer/algo \ $(PROJECT_DIR)/hw_layer/sensors/ \ + $(HW_LAYER_DRIVERS_INC) \ test_data_structures \ googletest/googlemock/include \ googletest/googletest \ diff --git a/unit_tests/efifeatures.h b/unit_tests/efifeatures.h index 23203a3a51..f2c6b7b3b9 100644 --- a/unit_tests/efifeatures.h +++ b/unit_tests/efifeatures.h @@ -53,4 +53,6 @@ #define EFI_ANALOG_SENSORS TRUE +#define BOARD_EXT_GPIOCHIPS 3 + #endif /* EFIFEATURES_H_ */ diff --git a/unit_tests/test.mk b/unit_tests/test.mk index f2d439c39d..e95a67be75 100644 --- a/unit_tests/test.mk +++ b/unit_tests/test.mk @@ -28,5 +28,6 @@ TEST_SRC_CPP = unit_test_framework.cpp \ tests/test_sensors.cpp \ tests/test_pid_auto.cpp \ tests/test_accel_enrichment.cpp \ + tests/test_gpiochip.cpp \ afm2mapConverter.cpp diff --git a/unit_tests/tests/test_gpiochip.cpp b/unit_tests/tests/test_gpiochip.cpp new file mode 100644 index 0000000000..9daf6c043d --- /dev/null +++ b/unit_tests/tests/test_gpiochip.cpp @@ -0,0 +1,146 @@ +/** + * @file test_gpiochip.cpp + * + * @date Mar 12, 2019 + */ + +#include "unit_test_framework.h" +#include "global.h" +#include "gpio/gpio_ext.h" + +using ::testing::_; + +static int testchip_readPad(void *data, unsigned int pin) +{ + if (pin & 0x01) + return 1; + return 0; +} + +static int io_state = 0; + +static int testchip_writePad(void *data, unsigned int pin, int value) +{ + if (value) + io_state |= (1 << value); + else + io_state &= ~(1 << value); + + return 0; +} + +static int initcalls = 0; + +static int testchip_init(void *data) +{ + initcalls++; + + return 0; +} + +static int calls_to_failed_chip = 0; +static int testchip_failed_writePad(void *data, unsigned int pin, int value) +{ + calls_to_failed_chip++; +} + +static int testchip_failed_init(void *data) +{ + return -1; +} + +/* invalid chip */ +struct gpiochip_ops testchip0 = { + /*.setPadMode =*/ NULL, + /*.writePad =*/ NULL, + /*.readPad =*/ NULL, + /*.getDiag =*/ NULL, + /*.init =*/ testchip_init, + /*.deinit =*/ NULL, +}; + +/* Input only chip */ +struct gpiochip_ops testchip1 = { + /*.setPadMode =*/ NULL, + /*.writePad =*/ NULL, + /*.readPad =*/ testchip_readPad, + /*.getDiag =*/ NULL, + /*.init =*/ testchip_init, + /*.deinit =*/ NULL, +}; + +/* Input only chip */ +struct gpiochip_ops testchip2 = { + /*.setPadMode =*/ NULL, + /*.writePad =*/ testchip_writePad, + /*.readPad =*/ NULL, + /*.getDiag =*/ NULL, + /*.init =*/ testchip_init, + /*.deinit =*/ NULL, +}; + +/* testchi[ failed to init */ +struct gpiochip_ops testchip3 = { + /*.setPadMode =*/ NULL, + /*.writePad =*/ testchip_failed_writePad, + /*.readPad =*/ NULL, + /*.getDiag =*/ NULL, + /*.init =*/ testchip_failed_init, + /*.deinit =*/ NULL, +}; + +TEST(gpioext, testGpioExt) { + int ret; + int chip1_base, chip2_base, chip3_base; + + printf("====================================================================================== testGpioExt\r\n"); + + /* should fail to register chip with no readPad and writePad */ + EXPECT_FALSE(gpiochip_register("invalid", &testchip0, 16, NULL) > 0); + + /* should fail to register chip with zero gpios */ + EXPECT_FALSE(gpiochip_register("invalid", &testchip1, 0, NULL) > 0); + + chip1_base = gpiochip_register("input only", &testchip1, 16, NULL); + EXPECT_TRUE(chip1_base > 0); + + EXPECT_EQ(16, gpiochips_get_total_pins()); + + chip2_base = gpiochip_register("output only", &testchip2, 16, NULL); + EXPECT_TRUE(chip2_base > 0); + + /* this chip will fail to init, but should be registered without errors */ + chip3_base = gpiochip_register("failed chip", &testchip3, 16, NULL); + EXPECT_TRUE(chip2_base > 0); + + EXPECT_EQ(48, gpiochips_get_total_pins()); + + /* init 3 chips, one will fail */ + ret = gpiochips_init(); + EXPECT_EQ(32, ret); + + /* two drivers should be inited */ + EXPECT_EQ(2, initcalls); + + /* gpio reads */ + EXPECT_TRUE(gpiochips_readPad(chip1_base + 0) == 0); + EXPECT_TRUE(gpiochips_readPad(chip1_base + 1) != 0); + + /* gpio write */ + gpiochips_writePad(chip2_base + 0, 0); + gpiochips_writePad(chip2_base + 1, 1); + EXPECT_EQ(0x02, io_state); + + /* try to access failed chip */ + EXPECT_FALSE(gpiochips_writePad(chip3_base + 0, 0) >= 0); + EXPECT_FALSE(gpiochips_writePad(chip3_base + 1, 1) >= 0); + EXPECT_EQ(0, calls_to_failed_chip); + + /* read/write outside range */ + EXPECT_TRUE(gpiochips_readPad(chip1_base - 1) < 0); + EXPECT_TRUE(gpiochips_writePad(chip1_base - 1, 1) < 0); + + EXPECT_TRUE(gpiochips_readPad(chip3_base + 16) < 0); + EXPECT_TRUE(gpiochips_writePad(chip3_base + 16, 1) < 0); + +}