diff --git a/cpu/stm32/Kconfig b/cpu/stm32/Kconfig index 1641e79bc5..e714eca3c3 100644 --- a/cpu/stm32/Kconfig +++ b/cpu/stm32/Kconfig @@ -78,6 +78,7 @@ rsource "periph/Kconfig.fmc" if TEST_KCONFIG +rsource "lcd_fmc/Kconfig" rsource "periph/Kconfig" rsource "stmclk/Kconfig" rsource "vectors/Kconfig" diff --git a/cpu/stm32/Makefile b/cpu/stm32/Makefile index 5a56d50d65..b7de58ef40 100644 --- a/cpu/stm32/Makefile +++ b/cpu/stm32/Makefile @@ -6,4 +6,8 @@ ifneq (,$(filter bootloader_stm32,$(USEMODULE))) DIRS += bootloader endif +ifneq (,$(filter lcd_fmc,$(USEMODULE))) + DIRS += lcd_fmc +endif + include $(RIOTBASE)/Makefile.base diff --git a/cpu/stm32/Makefile.dep b/cpu/stm32/Makefile.dep index 2b85221dc1..b1aca8e5be 100644 --- a/cpu/stm32/Makefile.dep +++ b/cpu/stm32/Makefile.dep @@ -54,6 +54,10 @@ ifneq (,$(filter stm32_eth,$(USEMODULE))) endif endif +ifneq (,$(filter lcd_parallel_ll_mcu,$(USEMODULE))) + USEMODULE += lcd_fmc +endif + ifneq (,$(filter periph_can,$(FEATURES_USED))) FEATURES_REQUIRED += periph_gpio FEATURES_REQUIRED += periph_gpio_irq diff --git a/cpu/stm32/include/lcd_fmc.h b/cpu/stm32/include/lcd_fmc.h new file mode 100644 index 0000000000..c95b95126f --- /dev/null +++ b/cpu/stm32/include/lcd_fmc.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2023 Gunar Schorcht + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @defgroup cpu_stm32_lcd_fmc STM32 FMC/FSMC LCD low-level parallel interface driver + * @ingroup cpu_stm32 + * + * @{ + */ + +#ifndef LCD_FMC_H +#define LCD_FMC_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Number of LCDs using FMC banks + * + * It represents the number of elements in LCD FMC bank descriptor array of + * type @ref lcd_fmc_desc_t. Because it is used by the preprocessor, it has + * to be defined as a number. It is not possible to use the @ref ARRAY_SIZE + * macro here. + * + * @note `LCD_FMC_NUMOF` has to be equal to the number of elements in the + * LCD FMC bank descriptor array of type @ref lcd_fmc_desc_t. + */ +#if DOXYGEN +#define LCD_FMC_NUMOF 1 +#endif + +/** + * @brief Descriptor of the FMC bank used for a LCD + * + * The board definition has to specify the array @ref lcd_fmc_desc of type + * @ref lcd_fmc_desc_t which defines the FMC banks and the address offsets used + * for the LCD displays that are connected to FMC banks. + * + * @note In the case that multiple LCDs are connected to FMC banks, the FMC + * bank descriptors for LCDs of type @ref lcd_fmc_desc_t + * must be defined in same order as the LCD devices. + */ +typedef struct { + const fmc_bank_conf_t *bank; /**< FMC bank config used for the LCD */ + uint32_t cmd_offset; /**< offset to the bank address used for commands */ + uint32_t data_offset; /**< offset to the bank address used for data */ +} lcd_fmc_desc_t; + +#ifdef __cplusplus +} +#endif + +#endif /* LCD_FMC_H */ +/** @} */ diff --git a/cpu/stm32/include/periph/cpu_fmc.h b/cpu/stm32/include/periph/cpu_fmc.h index 53b69cf3e2..221d4de820 100644 --- a/cpu/stm32/include/periph/cpu_fmc.h +++ b/cpu/stm32/include/periph/cpu_fmc.h @@ -64,6 +64,17 @@ extern "C" { #endif +/** + * @brief Gives the configuration of n-th bank + * + * This macro gives a pointer to the n-th entry of type @ref fmc_bank_conf_t of + * the banks configured by the board in the @ref fmc_bank_config array. n is in + * the range 0 ... @ref FMC_BANK_NUMOF - 1. + */ +#ifndef FMC_BANK_CONFIG +#define FMC_BANK_CONFIG(n) (&fmc_bank_config[n]) +#endif + /** * @brief Number of data pins used * diff --git a/cpu/stm32/lcd_fmc/Kconfig b/cpu/stm32/lcd_fmc/Kconfig new file mode 100644 index 0000000000..96a3da5c5e --- /dev/null +++ b/cpu/stm32/lcd_fmc/Kconfig @@ -0,0 +1,13 @@ +# Copyright (c) 2023 Gunar Schorcht +# +# This file is subject to the terms and conditions of the GNU Lesser +# General Public License v2.1. See the file LICENSE in the top level +# directory for more details. +# + +config MODULE_LCD_FMC + bool + depends on MODULE_LCD + select MODULE_PERIPH_FMC + select MODULE_PERIPH_FMC_NOR_SRAM + default y if HAVE_LCD_PARALLEL_LL_MCU diff --git a/cpu/stm32/lcd_fmc/Makefile b/cpu/stm32/lcd_fmc/Makefile new file mode 100644 index 0000000000..3a49fc191f --- /dev/null +++ b/cpu/stm32/lcd_fmc/Makefile @@ -0,0 +1,3 @@ +MODULE = lcd_fmc + +include $(RIOTBASE)/Makefile.base diff --git a/cpu/stm32/lcd_fmc/lcd_fmc.c b/cpu/stm32/lcd_fmc/lcd_fmc.c new file mode 100644 index 0000000000..844bf7e215 --- /dev/null +++ b/cpu/stm32/lcd_fmc/lcd_fmc.c @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2023 Gunar Schorcht + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup cpu_stm32 + * @ingroup drivers_periph_fmc + * @{ + * + * @file + * @brief FMC peripheral driver implementation + * + * @author Gunar Schorcht + * @} + */ + +#include + +#include "periph/gpio.h" +#include "lcd.h" +#include "lcd_internal.h" +#include "ztimer.h" + +#define ENABLE_DEBUG 0 +#include "debug.h" + +#ifndef LCD_FMC_NUMOF +#define LCD_FMC_NUMOF 1 +#endif + +#define FMC_LCD_CMD(d) (*((__IO uint16_t *)(d->bank->address + d->cmd_offset))) +#define FMC_LCD_DATA(d) (*((__IO uint16_t *)(d->bank->address + d->data_offset))) + +/* sanity check */ +static_assert(LCD_FMC_NUMOF == ARRAY_SIZE(lcd_fmc_desc), + "LCD_FMC_NUMOF is not equal to the number of elements in lcd_fmc_desc"); + +#if LCD_FMC_NUMOF > 1 +/* In the case that multiple LCDs are connected to FMC banks, an array + * for mapping the LCD device pointer to the FMC bank descriptor is used. + * This requires that the FMC bank descriptors for LCDs in `lcd_fmc_desc` + * are defined in same order as the LCD devices. */ +static lcd_t *_lcd_fmc_desc_map[LCD_FMC_NUMOF]; +static uint8_t _lcd_index = 0; + +static inline uint8_t _dev_to_lcd_fmc_desc(lcd_t *dev) +{ + for (uint8_t i = 0; i < LCD_FMC_NUMOF; i++) { + if (_lcd_fmc_desc_map[i] == dev) { + return i; + } + } + assert(false); +} +#endif + +static void lcd_ll_mcu_init(lcd_t *dev) +{ +#if LCD_FMC_NUMOF > 1 + /* The FMC bank descriptors for LCDs in `lcd_fmc_desc` must be defined + * in same order as the LCD display devices. We suppose that the + * LCDs are initialized in that order. */ + assert(_lcd_index < LCD_FMC_NUMOF); + _lcd_fmc_desc_map[_lcd_index++] = dev; +#else + (void)dev; +#endif +} + +static void lcd_ll_mcu_set_data_dir(lcd_t *dev, bool output) +{ + /* no action needed */ + (void)dev; + (void)output; +} + +static void lcd_ll_mcu_cmd_start(lcd_t *dev, uint8_t cmd, bool cont) +{ + DEBUG("[lcd_ll_mcu] write cmd: %02x\n", cmd); + + (void)cont; +#if LCD_FMC_NUMOF > 1 + const lcd_fmc_desc_t *desc = &lcd_fmc_desc[_dev_to_lcd_fmc_desc(dev)]; +#else + (void)dev; + const lcd_fmc_desc_t *desc = lcd_fmc_desc; +#endif + + FMC_LCD_CMD(desc) = cmd; + /* data synchronization barrier seems to be necessary on some STM32 MCUs. */ + __DSB(); +} + +static void lcd_ll_mcu_write_byte(lcd_t *dev, bool cont, uint8_t out) +{ + DEBUG("[lcd_ll_mcu] write byte: %02x\n", out); + + (void)cont; +#if LCD_FMC_NUMOF > 1 + const lcd_fmc_desc_t *desc = &lcd_fmc_desc[_dev_to_lcd_fmc_desc(dev)]; +#else + (void)dev; + const lcd_fmc_desc_t *desc = lcd_fmc_desc; +#endif + + FMC_LCD_DATA(desc) = out; + /* data synchronization barrier seems to be necessary on some STM32 MCUs. */ + __DSB(); +} + +static uint8_t lcd_ll_mcu_read_byte(lcd_t *dev, bool cont) +{ + (void)cont; +#if LCD_FMC_NUMOF > 1 + const lcd_fmc_desc_t *desc = &lcd_fmc_desc[_dev_to_lcd_fmc_desc(dev)]; +#else + (void)dev; + const lcd_fmc_desc_t *desc = lcd_fmc_desc; +#endif + + uint8_t in = FMC_LCD_DATA(desc); + DEBUG("[lcd_ll_mcu] read byte: %02x\n", in); + return in; +} + +#if IS_USED(MODULE_LCD_PARALLEL_16BIT) + +static void lcd_ll_mcu_write_word(lcd_t *dev, bool cont, uint16_t out) +{ + DEBUG("[lcd_ll_mcu] write word: %04x\n", out); + + (void)cont; +#if LCD_FMC_NUMOF > 1 + const lcd_fmc_desc_t *desc = &lcd_fmc_desc[_dev_to_lcd_fmc_desc(dev)]; +#else + (void)dev; + const lcd_fmc_desc_t *desc = lcd_fmc_desc; +#endif + + FMC_LCD_DATA(desc) = out; + /* data synchronization barrier seems to be necessary on some STM32 MCUs. */ + __DSB(); +} + +static uint16_t lcd_ll_mcu_read_word(lcd_t *dev, bool cont) +{ + (void)cont; +#if LCD_FMC_NUMOF > 1 + const lcd_fmc_desc_t *desc = &lcd_fmc_desc[_dev_to_lcd_fmc_desc(dev)]; +#else + (void)dev; + const lcd_fmc_desc_t *desc = lcd_fmc_desc; +#endif + + uint16_t in = FMC_LCD_DATA(desc); + DEBUG("[lcd_ll_mcu] read word: %04x\n", in); + return in; +} + +#endif /* MODULE_LCD_PARALLEL_16BIT */ + +const lcd_ll_par_driver_t lcd_ll_par_driver = { + .init = lcd_ll_mcu_init, + .set_data_dir = lcd_ll_mcu_set_data_dir, + .cmd_start = lcd_ll_mcu_cmd_start, + .write_byte = lcd_ll_mcu_write_byte, + .read_byte = lcd_ll_mcu_read_byte, +#if IS_USED(MODULE_LCD_PARALLEL_16BIT) + .write_word = lcd_ll_mcu_write_word, + .read_word = lcd_ll_mcu_read_word, +#endif +};