diff --git a/Documentation/components/drivers/special/ioexpander.rst b/Documentation/components/drivers/special/ioexpander.rst index 2f2fa32f1b1b5..5a4e19af89641 100644 --- a/Documentation/components/drivers/special/ioexpander.rst +++ b/Documentation/components/drivers/special/ioexpander.rst @@ -62,64 +62,78 @@ routine in the header of the same name under * - Awinic AW9523B - I2C - 16 - - ``IOEXPANDER_AW9523B`` + - ``CONFIG_IOEXPANDER_AW9523B`` + * - WCH CH422G + - I2C + - 12 + - ``CONFIG_IOEXPANDER_CH422G`` * - iC-Haus iC-JX - SPI - 16 - - ``IOEXPANDER_ICJX`` + - ``CONFIG_IOEXPANDER_ICJX`` * - ISO1H812G - SPI - 8 - - ``IOEXPANDER_ISO1H812G`` + - ``CONFIG_IOEXPANDER_ISO1H812G`` * - ISO1I813T - SPI - 8 - - ``IOEXPANDER_ISO1I813T`` + - ``CONFIG_IOEXPANDER_ISO1I813T`` * - Microchip MCP23008 / MCP23S08 - I2C - 8 - - ``IOEXPANDER_MCP23X08`` + - ``CONFIG_IOEXPANDER_MCP23X08`` * - Microchip MCP23017 / MCP23S17 - I2C - 16 - - ``IOEXPANDER_MCP23X17`` + - ``CONFIG_IOEXPANDER_MCP23X17`` * - NXP PCA9538 - I2C - 8 - - ``IOEXPANDER_PCA9538`` + - ``CONFIG_IOEXPANDER_PCA9538`` * - NXP PCA9555 - I2C - 16 - - ``IOEXPANDER_PCA9555`` + - ``CONFIG_IOEXPANDER_PCA9555`` * - NXP PCA9557 - I2C - 8 - - ``IOEXPANDER_PCA9557`` + - ``CONFIG_IOEXPANDER_PCA9557`` * - PCF8574 - I2C - 8 - - ``IOEXPANDER_PCF8574`` + - ``CONFIG_IOEXPANDER_PCF8574`` * - PCF8575 - I2C - 16 - - ``IOEXPANDER_PCF8575`` + - ``CONFIG_IOEXPANDER_PCF8575`` * - Diodes PI4IOE5V6408 - I2C - 8 - - ``IOEXPANDER_PI4IOE5V6408`` + - ``CONFIG_IOEXPANDER_PI4IOE5V6408`` * - Semtech SX1509 - I2C - 16 - - ``IOEXPANDER_SX1509`` + - ``CONFIG_IOEXPANDER_SX1509`` * - TCA6408 / TCA6416 / TCA6424 / PCAL6416A - I2C - 8 / 16 / 24 / 16 - - ``IOEXPANDER_TCA64XX`` + - ``CONFIG_IOEXPANDER_TCA64XX`` Notes on individual drivers: -- ``IOEXPANDER_TCA64XX`` and ``IOEXPANDER_PCF8574`` additionally depend on +- ``CONFIG_IOEXPANDER_TCA64XX`` and ``CONFIG_IOEXPANDER_PCF8574`` additionally depend on ``CONFIG_EXPERIMENTAL``. +- ``CONFIG_IOEXPANDER_CH422G`` presents the eight bi-directional pins, IO0-IO7, as + pins 0-7 and the four open-drain outputs, OC0-OC3, as pins 8-11, so + ``CONFIG_IOEXPANDER_NPINS`` must be at least 12. The device selects a + register by the I2C address a transfer is addressed to rather than by a + register address written ahead of the data, and none of its write-only + registers can be read back, so the driver shadows them. IO0-IO7 share a + single direction control in the hardware: the driver records the direction + asked of each pin and puts the group in output mode once at least one of + them is an output. Reading a pin of a group held in output mode reports + the value last written, because the device cannot report the pin level. - Drivers with a ``_MULTIPLE`` option support more than one instance of the same chip on a board. - Drivers with a ``_SHADOW_MODE`` option keep the output and diff --git a/Documentation/platforms/xtensa/esp32s3/boards/esp32s3-touch-lcd7/index.rst b/Documentation/platforms/xtensa/esp32s3/boards/esp32s3-touch-lcd7/index.rst index 8d9231c79f771..b0f5068333273 100644 --- a/Documentation/platforms/xtensa/esp32s3/boards/esp32s3-touch-lcd7/index.rst +++ b/Documentation/platforms/xtensa/esp32s3/boards/esp32s3-touch-lcd7/index.rst @@ -78,6 +78,42 @@ The board has a 7-inch LCD: EXIO2 DISP Backlight enable pin ========= ======== ========================= +The panel is an EK9716 driven over a 16-bit parallel RGB565 bus by the +LCD_CAM peripheral. Its timing is: + + ==================== ======= + Parameter Value + ==================== ======= + Resolution 800x480 + Pixel clock 16 MHz + HSYNC pulse width 4 + HSYNC back porch 8 + HSYNC front porch 8 + VSYNC pulse width 4 + VSYNC back porch 8 + VSYNC front porch 8 + ==================== ======= + +The RGB bus takes most of the usable pins, so the panel reset and the +display enable are not wired to GPIOs. They hang off a CH422G I/O +expander, reached over the I2C bus shared with the touch controller: + + ========= ============ ================================== + Signal Connection Description + ========= ============ ================================== + SDA GPIO8 I2C data, expander and touch + SCL GPIO9 I2C clock, expander and touch + TP_RST CH422G EXIO1 GT911 touch controller reset + DISP CH422G EXIO2 Display and backlight enable + LCD_RST CH422G EXIO3 Panel reset + SD_CS CH422G EXIO4 TF card chip select + USB_SEL CH422G EXIO5 USB / CAN transceiver select + ========= ============ ================================== + +A 800x480 RGB565 framebuffer is 768000 bytes and does not fit in internal +RAM, so any configuration that drives the panel must also enable the +onboard 8MB octal PSRAM and leave it in the common heap. + Configurations ============== @@ -92,6 +128,50 @@ All of the configurations presented below can be tested by running the following Configuration Directories ------------------------- +lcd +--- + +Brings up the onboard 7 inch 800x480 panel as a framebuffer character +driver at ``/dev/fb0``, on top of the ``usbnsh`` console. The CH422G I/O +expander is used to take the panel out of reset and to switch the display +on once the framebuffer exists, and the PSRAM the framebuffer is allocated +from is enabled. + +Note that the console is on UART0, GPIO43 and GPIO44, reached through the +USB-to-UART bridge on the UART USB-C connector. It is deliberately not on +the native USB peripheral, because that is the connector a USB host +configuration drives. + +``apps/examples/fb`` is included to prove the panel out. It reports the +geometry of the framebuffer it found and then draws a series of nested +rectangles over the whole display:: + + nsh> fb + VideoInfo: + fmt: 11 + xres: 800 + yres: 480 + nplanes: 1 + PlaneInfo (plane 0): + fbmem: 0x3c050040 + fblen: 1536000 + stride: 1600 + display: 0 + bpp: 16 + Mapped FB: 0x3c050040 + Use consecutive fbmem2 = 0x3c10b840, yoffset = 480 + 0: ( 0, 0) (800,480) + 1: ( 72, 43) (656,394) + 2: (144, 86) (512,308) + 3: (216,129) (368,222) + 4: (288,172) (224,136) + 5: (360,215) ( 80, 50) + Test finished + +The framebuffer is in PSRAM, which is why ``fbmem`` is in the 0x3c000000 +range, and ``fblen`` is 1536000 rather than 768000 because the driver is +double buffered by default. + usbnsh ------ diff --git a/arch/xtensa/src/esp32s3/Kconfig b/arch/xtensa/src/esp32s3/Kconfig index 23c017e1acd70..4d189c7baa56b 100644 --- a/arch/xtensa/src/esp32s3/Kconfig +++ b/arch/xtensa/src/esp32s3/Kconfig @@ -2650,6 +2650,48 @@ config ESP32S3_LCD_CLOCK_MHZ int "LCD Pixel Clock Frequency in MHz" default 2 +config ESP32S3_LCD_BOUNCE_LINES + int "Bounce buffer height in lines (0 to disable)" + default 0 + ---help--- + Scan the panel out of two small buffers in internal RAM, each this + many lines tall, refilled from the framebuffer as they are consumed, + rather than having the DMA read the framebuffer directly. + + This matters when the framebuffer is in PSRAM. The DMA then has to + fetch every pixel from PSRAM in real time, and it is competing with + whatever else uses that memory; a CPU writing to the framebuffer, or + flushing it out of the cache, can stall those fetches for long + enough to empty the LCD FIFO. The panel sees the data stream + falter, and what shows up is an image that shakes and then rolls. + + With a bounce buffer the only thing on the critical path is internal + RAM, which nothing else can stall. The copy from PSRAM happens in + the interrupt that follows each buffer being consumed and has a + whole buffer's worth of time to complete. This is the arrangement + the ESP-IDF RGB panel driver calls a bounce buffer. + + The framebuffer must be a whole number of these buffers, so the + value has to divide the vertical resolution. Two buffers of this + size are taken out of internal RAM. Ten lines of an 800 pixel wide + panel at 16bpp costs 32000 bytes. + +config ESP32S3_LCD_PCLK_ACTIVE_NEG + bool "Latch data on the falling edge of the pixel clock" + default n + ---help--- + By default the pixel clock is low during the first half of a cycle, + so the panel latches the data lines, and DE and the sync signals + with them, on the rising edge. Enable this for a panel that + expects the falling edge instead. + + A panel driven on the wrong edge samples the sync signals at the + moment they change, which usually shows up as an image that is + recognisable but will not hold vertical lock. + + This is the equivalent of pclk_active_neg in the ESP-IDF RGB panel + driver. + config ESP32S3_LCD_VFRONTPORCH int "LCD Vertical Front Porch" default 40 diff --git a/arch/xtensa/src/esp32s3/esp32s3_lcd.c b/arch/xtensa/src/esp32s3/esp32s3_lcd.c index c3f12193e8858..a047e6a2d31b7 100644 --- a/arch/xtensa/src/esp32s3/esp32s3_lcd.c +++ b/arch/xtensa/src/esp32s3/esp32s3_lcd.c @@ -26,6 +26,7 @@ #include #include +#include #include #include #include @@ -40,6 +41,7 @@ #include "esp_clk.h" #include "esp_gpio.h" #include "esp32s3_dma.h" +#include "hal/gdma_ll.h" #include "esp_irq.h" #include "xtensa.h" @@ -150,6 +152,28 @@ #define ESP32S3_LCD_DMADESC_NUM (ESP32S3_LCD_FB_SIZE / \ ESP32S3_DMA_BUFLEN_MAX + 1) +#if CONFIG_ESP32S3_LCD_BOUNCE_LINES > 0 + +/* Scan out of two buffers in internal RAM, refilled from the framebuffer as + * they are consumed, so that nothing on the panel's critical path depends on + * PSRAM. + */ + +# define ESP32S3_LCD_HAVE_BOUNCE 1 + +# define ESP32S3_LCD_BB_SIZE (CONFIG_ESP32S3_LCD_HRES * \ + CONFIG_ESP32S3_LCD_BOUNCE_LINES * \ + ESP32S3_LCD_DATA_WIDTH) + +# define ESP32S3_LCD_BB_DESCS ((ESP32S3_LCD_BB_SIZE + \ + ESP32S3_DMA_BUFLEN_MAX - 1) / \ + ESP32S3_DMA_BUFLEN_MAX) + +# if (ESP32S3_LCD_FB_SIZE % ESP32S3_LCD_BB_SIZE) != 0 +# error "CONFIG_ESP32S3_LCD_BOUNCE_LINES must divide the vertical resolution" +# endif +#endif + #define ESP32S3_LCD_LAYERS CONFIG_ESP32S3_LCD_BUFFER_LAYERS /* Get current layer pointer */ @@ -202,6 +226,18 @@ struct esp32s3_lcd_s uint32_t yoffset; /* The current pan offset */ +#ifdef ESP32S3_LCD_HAVE_BOUNCE + /* Descriptors for the two bounce buffers, linked into a ring so that the + * transfer never ends and the controller never has to be restarted. + */ + + struct esp32s3_dmadesc_s bbdesc[2][ESP32S3_LCD_BB_DESCS]; + + uint32_t bouncepos; /* Offset of the next chunk to copy */ + uint32_t bbeof; /* Buffers consumed so far */ + int bbcpuint; /* CPU interrupt for the DMA channel */ +#endif + int cpuint; /* CPU interrupt assigned to this LCD */ uint8_t cpu; /* CPU ID */ int32_t dma_channel; /* DMA channel */ @@ -331,6 +367,16 @@ static struct fb_vtable_s g_base_vtable = #endif }; +#ifdef ESP32S3_LCD_HAVE_BOUNCE +/* The bounce buffers. Ordinary static data, so they are in internal RAM, + * which is the whole point of them. + */ + +static uint8_t g_bounce[2][ESP32S3_LCD_BB_SIZE] + aligned_data(64); + +#endif + /**************************************************************************** * Private Functions ****************************************************************************/ @@ -731,6 +777,16 @@ static int IRAM_ATTR lcd_interrupt(int irq, void *context, void *arg) ESP32S3_LCD_FB_SIZE); #endif +#ifdef ESP32S3_LCD_HAVE_BOUNCE + /* Anchor the ring at the first bounce buffer again. The read + * position needs no correction: the framebuffer is a whole number of + * bounce buffers, so by the time a frame has been sent the buffers + * already hold the first two pieces of the next one. + */ + + esp32s3_dma_load(priv->bbdesc[0], priv->dma_channel, true); +#endif + /* Enable DMA TX */ esp32s3_dma_enable(priv->dma_channel, true); @@ -749,6 +805,74 @@ static int IRAM_ATTR lcd_interrupt(int irq, void *context, void *arg) return 0; } +#ifdef ESP32S3_LCD_HAVE_BOUNCE +/**************************************************************************** + * Name: esp32s3_lcd_bounce_fill + * + * Description: + * Copy the next piece of the framebuffer into a bounce buffer that has + * just been sent, and step the read position on. The position wraps at + * the end of the framebuffer, which is also the end of a frame because + * the framebuffer is a whole number of bounce buffers, so the two stay in + * step without anything having to resynchronise them. + * + * Input Parameters: + * priv - The LCD driver state. + * bb - Which of the two buffers to refill. + * + ****************************************************************************/ + +static void IRAM_ATTR esp32s3_lcd_bounce_fill(struct esp32s3_lcd_s *priv, + int bb) +{ + struct esp32s3_layer_s *layer = CURRENT_LAYER(priv); + + memcpy(g_bounce[bb], + &layer->framebuffer[priv->yoffset * ESP32S3_LCD_STRIDE + + priv->bouncepos], + ESP32S3_LCD_BB_SIZE); + + priv->bouncepos += ESP32S3_LCD_BB_SIZE; + if (priv->bouncepos >= ESP32S3_LCD_FB_SIZE) + { + priv->bouncepos = 0; + } +} + +/**************************************************************************** + * Name: esp32s3_lcd_dma_interrupt + * + * Description: + * One bounce buffer has been sent to the panel. Refill it while the + * other one is being sent. + * + ****************************************************************************/ + +static int IRAM_ATTR esp32s3_lcd_dma_interrupt(int irq, void *context, + void *arg) +{ + struct esp32s3_lcd_s *priv = &g_lcd_priv; + int status = esp32s3_dma_get_interrupt(priv->dma_channel, true); + + if (status <= 0) + { + return 0; + } + + esp32s3_dma_clear_interrupt(priv->dma_channel, true, status); + + if ((status & GDMA_LL_EVENT_TX_EOF) != 0) + { + /* The buffer that finished is the one this transfer started from */ + + esp32s3_lcd_bounce_fill(priv, priv->bbeof & 1); + priv->bbeof++; + } + + return 0; +} +#endif + /**************************************************************************** * Name: esp32s3_lcd_dmasetup * @@ -783,8 +907,20 @@ static int esp32s3_lcd_dmasetup(void) { struct esp32s3_layer_s *layer = &priv->layer[i]; + /* A framebuffer is large: at 800x480 in 16bpp one is 768000 bytes, + * twice that when double buffered, so it does not come from internal + * RAM and the allocation is one that can realistically fail. Say so + * rather than faulting on the memset that follows. + */ + layer->framebuffer = memalign(64, ESP32S3_LCD_FB_MEM_SIZE); - DEBUGASSERT(layer->framebuffer != NULL); + if (layer->framebuffer == NULL) + { + lcderr("Failed to allocate %d bytes for framebuffer %d\n", + ESP32S3_LCD_FB_MEM_SIZE, i); + goto errout; + } + memset(layer->framebuffer, 0, ESP32S3_LCD_FB_MEM_SIZE); esp32s3_dma_setup(layer->dmadesc, @@ -794,7 +930,63 @@ static int esp32s3_lcd_dmasetup(void) true, priv->dma_channel); } +#ifdef ESP32S3_LCD_HAVE_BOUNCE + { + struct esp32s3_dmadesc_s *tail; + int i; + + /* One chain per bounce buffer, each ending in the end of frame mark + * that raises the interrupt this driver refills on, and the two of + * them joined into a ring. The transfer then never finishes, so the + * controller runs continuously and the panel sees an unbroken stream. + */ + + for (i = 0; i < 2; i++) + { + esp32s3_dma_setup(priv->bbdesc[i], + ESP32S3_LCD_BB_DESCS, + g_bounce[i], + ESP32S3_LCD_BB_SIZE, + true, priv->dma_channel); + } + + for (i = 0; i < 2; i++) + { + for (tail = priv->bbdesc[i]; tail->next != NULL; tail = tail->next) + { + } + + tail->next = priv->bbdesc[i ^ 1]; + } + + /* Prime both buffers with the start of the first frame */ + + priv->bouncepos = 0; + priv->bbeof = 0; + + esp32s3_lcd_bounce_fill(priv, 0); + esp32s3_lcd_bounce_fill(priv, 1); + } +#endif + return OK; + +errout: + for (int i = 0; i < ESP32S3_LCD_LAYERS; i++) + { + struct esp32s3_layer_s *layer = &priv->layer[i]; + + if (layer->framebuffer != NULL) + { + free(layer->framebuffer); + layer->framebuffer = NULL; + } + } + + esp32s3_dma_release(priv->dma_channel); + priv->dma_channel = -1; + + return -ENOMEM; } /**************************************************************************** @@ -871,6 +1063,16 @@ static void esp32s3_lcd_enableclk(void) (ESP32S3_LCD_CLK_N << LCD_CAM_LCD_CLKM_DIV_NUM_S) | (clk_a << LCD_CAM_LCD_CLKM_DIV_A_S) | (clk_b << LCD_CAM_LCD_CLKM_DIV_B_S); + +#ifdef CONFIG_ESP32S3_LCD_PCLK_ACTIVE_NEG + /* Drive the pixel clock high for the first half of the cycle, so that the + * data lines, and DE and the sync signals with them, are stable across the + * falling edge the panel latches on. + */ + + regval |= LCD_CAM_LCD_CK_OUT_EDGE_M; +#endif + esp32s3_lcd_putreg(LCD_CAM_LCD_CLOCK_REG, regval); } @@ -1006,7 +1208,25 @@ static void esp32s3_lcd_enable(void) struct esp32s3_lcd_s *priv = &g_lcd_priv; struct esp32s3_layer_s *layer = CURRENT_LAYER(priv); +#ifdef ESP32S3_LCD_HAVE_BOUNCE + priv->bbcpuint = esp_setup_irq(ESP32S3_PERIPH_DMA_OUT_CH0 + + priv->dma_channel, + 1, ESP_IRQ_TRIGGER_LEVEL, + esp32s3_lcd_dma_interrupt, NULL); + if (priv->bbcpuint < 0) + { + lcderr("Failed to attach the DMA interrupt: %d\n", priv->bbcpuint); + return; + } + + esp32s3_dma_enable_interrupt(priv->dma_channel, true, + GDMA_LL_EVENT_TX_EOF, true); + up_enable_irq(ESP32S3_IRQ_DMA_OUT_CH0 + priv->dma_channel); + + esp32s3_dma_load(priv->bbdesc[0], priv->dma_channel, true); +#else esp32s3_dma_load(layer->dmadesc, priv->dma_channel, true); +#endif esp32s3_dma_enable(priv->dma_channel, true); /* Delay 1 microsecond to wait the DMA start */ diff --git a/boards/xtensa/esp32s3/esp32s3-touch-lcd7/Kconfig b/boards/xtensa/esp32s3/esp32s3-touch-lcd7/Kconfig index e763d2ebc50b3..cc5875291ef9e 100644 --- a/boards/xtensa/esp32s3/esp32s3-touch-lcd7/Kconfig +++ b/boards/xtensa/esp32s3/esp32s3-touch-lcd7/Kconfig @@ -5,4 +5,16 @@ if ARCH_BOARD_ESP32S3_TOUCH_LCD_7 +config ESP32S3_BOARD_LCD + bool "Enable Board LCD" + default n + depends on ESP32S3_LCD + select ESP32S3_I2C0 + select IOEXPANDER + select IOEXPANDER_CH422G + ---help--- + Enable the onboard 7 inch 800x480 RGB panel. The panel reset and + the display enable are driven through the CH422G I/O expander on + I2C0, so both are selected here. + endif # ARCH_BOARD_ESP32S3_TOUCH_LCD_7 diff --git a/boards/xtensa/esp32s3/esp32s3-touch-lcd7/configs/lcd/defconfig b/boards/xtensa/esp32s3/esp32s3-touch-lcd7/configs/lcd/defconfig new file mode 100644 index 0000000000000..9e6adb92e17f3 --- /dev/null +++ b/boards/xtensa/esp32s3/esp32s3-touch-lcd7/configs/lcd/defconfig @@ -0,0 +1,86 @@ +# +# This file is autogenerated: PLEASE DO NOT EDIT IT. +# +# You can use "make menuconfig" to make any modifications to the installed .config file. +# You can then do "make savedefconfig" to generate a new defconfig file that includes your +# modifications. +# +# CONFIG_NSH_ARGCAT is not set +# CONFIG_NSH_CMDOPT_HEXDUMP is not set +CONFIG_ARCH="xtensa" +CONFIG_ARCH_BOARD="esp32s3-touch-lcd7" +CONFIG_ARCH_BOARD_COMMON=y +CONFIG_ARCH_BOARD_ESP32S3_TOUCH_LCD_7=y +CONFIG_ARCH_CHIP="esp32s3" +CONFIG_ARCH_CHIP_ESP32S3=y +CONFIG_ARCH_CHIP_ESP32S3WROOM1N16R8=y +CONFIG_ARCH_INTERRUPTSTACK=2048 +CONFIG_ARCH_IRQ_TO_NDX=y +CONFIG_ARCH_MINIMAL_VECTORTABLE_DYNAMIC=y +CONFIG_ARCH_NUSER_INTERRUPTS=2 +CONFIG_ARCH_STACKDUMP=y +CONFIG_ARCH_XTENSA=y +CONFIG_BOARD_LOOPSPERMSEC=16717 +CONFIG_BUILTIN=y +CONFIG_DEBUG_FULLOPT=y +CONFIG_DEBUG_SYMBOLS=y +CONFIG_ESP32S3_BOARD_LCD=y +CONFIG_ESP32S3_DMA=y +CONFIG_ESP32S3_I2C0_SCLPIN=9 +CONFIG_ESP32S3_I2C0_SDAPIN=8 +CONFIG_ESP32S3_LCD=y +CONFIG_ESP32S3_LCD_BOUNCE_LINES=10 +CONFIG_ESP32S3_LCD_CLOCK_MHZ=16 +CONFIG_ESP32S3_LCD_DATA0_PIN=14 +CONFIG_ESP32S3_LCD_DATA10_PIN=21 +CONFIG_ESP32S3_LCD_DATA11_PIN=1 +CONFIG_ESP32S3_LCD_DATA12_PIN=2 +CONFIG_ESP32S3_LCD_DATA14_PIN=41 +CONFIG_ESP32S3_LCD_DATA15_PIN=40 +CONFIG_ESP32S3_LCD_DATA1_PIN=38 +CONFIG_ESP32S3_LCD_DATA2_PIN=18 +CONFIG_ESP32S3_LCD_DATA3_PIN=17 +CONFIG_ESP32S3_LCD_DATA4_PIN=10 +CONFIG_ESP32S3_LCD_DATA5_PIN=39 +CONFIG_ESP32S3_LCD_DATA6_PIN=0 +CONFIG_ESP32S3_LCD_DATA7_PIN=45 +CONFIG_ESP32S3_LCD_DATA8_PIN=48 +CONFIG_ESP32S3_LCD_DATA9_PIN=47 +CONFIG_ESP32S3_LCD_HBACKPORCH=8 +CONFIG_ESP32S3_LCD_HE_PIN=5 +CONFIG_ESP32S3_LCD_HFRONTPORCH=8 +CONFIG_ESP32S3_LCD_HPULSEWIDTH=4 +CONFIG_ESP32S3_LCD_HRES=800 +CONFIG_ESP32S3_LCD_PCLK_ACTIVE_NEG=y +CONFIG_ESP32S3_LCD_PCLK_PIN=7 +CONFIG_ESP32S3_LCD_VBACKPORCH=8 +CONFIG_ESP32S3_LCD_VFRONTPORCH=8 +CONFIG_ESP32S3_LCD_VPULSEWIDTH=4 +CONFIG_ESP32S3_SPIRAM=y +CONFIG_ESP32S3_SPIRAM_MODE_OCT=y +CONFIG_ESP32S3_SPIRAM_SPEED_80M=y +CONFIG_ESP32S3_UART0=y +CONFIG_EXAMPLES_FB=y +CONFIG_FS_PROCFS=y +CONFIG_HAVE_CXX=y +CONFIG_HAVE_CXXINITIALIZE=y +CONFIG_IDLETHREAD_STACKSIZE=3072 +CONFIG_INIT_ENTRYPOINT="nsh_main" +CONFIG_INIT_STACKSIZE=4096 +CONFIG_INTELHEX_BINARY=y +CONFIG_LINE_MAX=64 +CONFIG_MM_REGIONS=2 +CONFIG_NSH_BUILTIN_APPS=y +CONFIG_NSH_FILEIOSIZE=512 +CONFIG_NSH_READLINE=y +CONFIG_PREALLOC_TIMERS=4 +CONFIG_RAM_SIZE=114688 +CONFIG_RAM_START=0x20000000 +CONFIG_RR_INTERVAL=200 +CONFIG_SCHED_WAITPID=y +CONFIG_START_DAY=6 +CONFIG_START_MONTH=12 +CONFIG_START_YEAR=2011 +CONFIG_SYSLOG_BUFFER=y +CONFIG_SYSTEM_NSH=y +CONFIG_UART0_SERIAL_CONSOLE=y diff --git a/boards/xtensa/esp32s3/esp32s3-touch-lcd7/src/Make.defs b/boards/xtensa/esp32s3/esp32s3-touch-lcd7/src/Make.defs index 152d8d79276df..db59a4ed4a4a9 100644 --- a/boards/xtensa/esp32s3/esp32s3-touch-lcd7/src/Make.defs +++ b/boards/xtensa/esp32s3/esp32s3-touch-lcd7/src/Make.defs @@ -26,6 +26,14 @@ ifeq ($(CONFIG_BOARDCTL_RESET),y) CSRCS += esp32s3_reset.c endif +ifeq ($(CONFIG_IOEXPANDER_CH422G),y) +CSRCS += esp32s3_board_ioexpander.c +endif + +ifeq ($(CONFIG_ESP32S3_BOARD_LCD),y) +CSRCS += esp32s3_lcd.c +endif + DEPPATH += --dep-path board VPATH += :board CFLAGS += ${INCDIR_PREFIX}$(TOPDIR)$(DELIM)arch$(DELIM)$(CONFIG_ARCH)$(DELIM)src$(DELIM)board$(DELIM)board diff --git a/boards/xtensa/esp32s3/esp32s3-touch-lcd7/src/esp32s3-touch-lcd7.h b/boards/xtensa/esp32s3/esp32s3-touch-lcd7/src/esp32s3-touch-lcd7.h index 7f63afbf247cd..3d02d955f7dbf 100644 --- a/boards/xtensa/esp32s3/esp32s3-touch-lcd7/src/esp32s3-touch-lcd7.h +++ b/boards/xtensa/esp32s3/esp32s3-touch-lcd7/src/esp32s3-touch-lcd7.h @@ -31,10 +31,35 @@ #include #include +#ifdef CONFIG_IOEXPANDER_CH422G +# include +#endif + /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ +/* I2C bus ******************************************************************/ + +/* One I2C bus, GPIO8 (SDA) and GPIO9 (SCL), is shared by the CH422G I/O + * expander, the GT911 touch controller and the external I2C header. + */ + +#define BOARD_I2C_BUS 0 + +/* CH422G I/O expander ******************************************************/ + +/* The board labels the expander pins EXIO0-EXIO7, and they are the CH422G + * pins IO0-IO7 in order, so the ioexpander pin number is the EXIO number. + * EXIO0, EXIO6 and EXIO7 are not connected to anything on this board. + */ + +#define BOARD_EXIO_TP_RST CH422G_IO1 /* GT911 touch controller reset */ +#define BOARD_EXIO_DISP CH422G_IO2 /* LCD display and backlight enable */ +#define BOARD_EXIO_LCD_RST CH422G_IO3 /* LCD panel reset */ +#define BOARD_EXIO_SD_CS CH422G_IO4 /* TF card chip select */ +#define BOARD_EXIO_USB_SEL CH422G_IO5 /* USB / CAN transceiver select */ + /**************************************************************************** * Public Types ****************************************************************************/ @@ -62,5 +87,38 @@ int esp32s3_bringup(void); +#ifdef CONFIG_IOEXPANDER_CH422G +/**************************************************************************** + * Name: board_ioexpander_initialize + * + * Description: + * Bring up the CH422G I/O expander, if it has not been brought up + * already, and return it. Several parts of the board hang off the + * expander, so the first caller initialises it and the rest share it. + * + * Returned Value: + * The expander instance on success, NULL on failure. + * + ****************************************************************************/ + +FAR struct ioexpander_dev_s *board_ioexpander_initialize(void); +#endif + +#ifdef CONFIG_ESP32S3_BOARD_LCD +/**************************************************************************** + * Name: board_lcd_initialize + * + * Description: + * Bring the RGB panel out of reset, switch the backlight on and register + * the framebuffer character driver. + * + * Returned Value: + * Zero (OK) on success; a negated errno value on failure. + * + ****************************************************************************/ + +int board_lcd_initialize(void); +#endif + #endif /* __ASSEMBLY__ */ #endif /* __BOARDS_XTENSA_ESP32S3_ESP32S3_TOUCH_LCD7_SRC_ESP32S3_TOUCH_LCD7_H */ diff --git a/boards/xtensa/esp32s3/esp32s3-touch-lcd7/src/esp32s3_board_ioexpander.c b/boards/xtensa/esp32s3/esp32s3-touch-lcd7/src/esp32s3_board_ioexpander.c new file mode 100644 index 0000000000000..2d056bb1c3b8d --- /dev/null +++ b/boards/xtensa/esp32s3/esp32s3-touch-lcd7/src/esp32s3_board_ioexpander.c @@ -0,0 +1,98 @@ +/**************************************************************************** + * boards/xtensa/esp32s3/esp32s3-touch-lcd7/src/esp32s3_board_ioexpander.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include + +#include +#include +#include + +#include "esp32s3_i2c.h" +#include "esp32s3-touch-lcd7.h" + +#ifdef CONFIG_IOEXPANDER_CH422G + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static struct ch422g_config_s g_ch422g_config = +{ + .frequency = 400000, +}; + +/* The expander is shared, so it is brought up once and handed out. */ + +static FAR struct ioexpander_dev_s *g_ioe; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: board_ioexpander_initialize + * + * Description: + * Bring up the CH422G I/O expander, if it has not been brought up + * already, and return it. Several parts of the board hang off the + * expander, so the first caller initialises it and the rest share it. + * + * Returned Value: + * The expander instance on success, NULL on failure. + * + ****************************************************************************/ + +FAR struct ioexpander_dev_s *board_ioexpander_initialize(void) +{ + FAR struct i2c_master_s *i2c; + + if (g_ioe != NULL) + { + return g_ioe; + } + + i2c = esp32s3_i2cbus_initialize(BOARD_I2C_BUS); + if (i2c == NULL) + { + syslog(LOG_ERR, "ERROR: Failed to initialize I2C%d\n", BOARD_I2C_BUS); + return NULL; + } + + g_ioe = ch422g_initialize(i2c, &g_ch422g_config); + if (g_ioe == NULL) + { + syslog(LOG_ERR, "ERROR: Failed to initialize CH422G\n"); + esp32s3_i2cbus_uninitialize(i2c); + return NULL; + } + + return g_ioe; +} + +#endif /* CONFIG_IOEXPANDER_CH422G */ diff --git a/boards/xtensa/esp32s3/esp32s3-touch-lcd7/src/esp32s3_bringup.c b/boards/xtensa/esp32s3/esp32s3-touch-lcd7/src/esp32s3_bringup.c index 63caf1f6a7f72..b7da58a11f648 100644 --- a/boards/xtensa/esp32s3/esp32s3-touch-lcd7/src/esp32s3_bringup.c +++ b/boards/xtensa/esp32s3/esp32s3-touch-lcd7/src/esp32s3_bringup.c @@ -97,6 +97,16 @@ int esp32s3_bringup(void) } #endif +#ifdef CONFIG_ESP32S3_BOARD_LCD + /* Bring up the RGB panel and register the framebuffer */ + + ret = board_lcd_initialize(); + if (ret < 0) + { + syslog(LOG_ERR, "ERROR: board_lcd_initialize() failed: %d\n", ret); + } +#endif + /* If we got here then perhaps not all initialization was successful, but * at least enough succeeded to bring-up NSH with perhaps reduced * capabilities. diff --git a/boards/xtensa/esp32s3/esp32s3-touch-lcd7/src/esp32s3_lcd.c b/boards/xtensa/esp32s3/esp32s3-touch-lcd7/src/esp32s3_lcd.c new file mode 100644 index 0000000000000..75d7f8599e099 --- /dev/null +++ b/boards/xtensa/esp32s3/esp32s3-touch-lcd7/src/esp32s3_lcd.c @@ -0,0 +1,138 @@ +/**************************************************************************** + * boards/xtensa/esp32s3/esp32s3-touch-lcd7/src/esp32s3_lcd.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/* The 7" 800x480 panel is driven by the LCD_CAM peripheral over a 16-bit + * parallel RGB565 bus, which arch/xtensa/src/esp32s3/esp32s3_lcd.c owns. + * Everything that is specific to this board is here: the panel reset and + * the display enable are not wired to GPIOs but to the CH422G I/O expander, + * because the RGB bus has taken most of the usable pins. + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include "esp32s3-touch-lcd7.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* The EK9716 wants its reset held for at least 10us and needs time to come + * up before it is clocked. These are comfortably longer than that. + */ + +#define LCD_RESET_HOLD_US (20 * 1000) +#define LCD_RESET_WAIT_US (120 * 1000) + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: board_lcd_initialize + * + * Description: + * Bring the RGB panel out of reset, switch the backlight on and register + * the framebuffer character driver. + * + * Returned Value: + * Zero (OK) on success; a negated errno value on failure. + * + ****************************************************************************/ + +int board_lcd_initialize(void) +{ + FAR struct ioexpander_dev_s *ioe; + int ret; + + ioe = board_ioexpander_initialize(); + if (ioe == NULL) + { + return -ENODEV; + } + + /* The panel reset and the display enable are outputs of the expander. + * Drive the panel into reset and keep the display off while it settles. + */ + + ret = IOEXP_SETDIRECTION(ioe, BOARD_EXIO_LCD_RST, + IOEXPANDER_DIRECTION_OUT); + if (ret < 0) + { + syslog(LOG_ERR, "ERROR: Failed to configure LCD reset: %d\n", ret); + return ret; + } + + ret = IOEXP_SETDIRECTION(ioe, BOARD_EXIO_DISP, IOEXPANDER_DIRECTION_OUT); + if (ret < 0) + { + syslog(LOG_ERR, "ERROR: Failed to configure display enable: %d\n", + ret); + return ret; + } + + IOEXP_WRITEPIN(ioe, BOARD_EXIO_DISP, false); + IOEXP_WRITEPIN(ioe, BOARD_EXIO_LCD_RST, false); + up_udelay(LCD_RESET_HOLD_US); + + ret = IOEXP_WRITEPIN(ioe, BOARD_EXIO_LCD_RST, true); + if (ret < 0) + { + syslog(LOG_ERR, "ERROR: Failed to release LCD reset: %d\n", ret); + return ret; + } + + up_udelay(LCD_RESET_WAIT_US); + + /* The framebuffer has to exist, and so be scanning out a defined buffer, + * before the display is enabled. Enabling it first shows whatever the + * panel happens to latch until the first frame arrives. + */ + + ret = fb_register(0, 0); + if (ret < 0) + { + syslog(LOG_ERR, "ERROR: fb_register() failed: %d\n", ret); + return ret; + } + + ret = IOEXP_WRITEPIN(ioe, BOARD_EXIO_DISP, true); + if (ret < 0) + { + syslog(LOG_ERR, "ERROR: Failed to enable the display: %d\n", ret); + return ret; + } + + return OK; +} diff --git a/drivers/ioexpander/CMakeLists.txt b/drivers/ioexpander/CMakeLists.txt index 34bde646335f2..62d6660492866 100644 --- a/drivers/ioexpander/CMakeLists.txt +++ b/drivers/ioexpander/CMakeLists.txt @@ -59,6 +59,10 @@ if(CONFIG_IOEXPANDER) list(APPEND SRCS pi4ioe5v6408.c) endif() + if(CONFIG_IOEXPANDER_CH422G) + list(APPEND SRCS ch422g.c) + endif() + if(CONFIG_IOEXPANDER_PCA9557) list(APPEND SRCS pca9557.c) endif() diff --git a/drivers/ioexpander/Kconfig b/drivers/ioexpander/Kconfig index 489ca1b0c7c69..a0ff52ec85b95 100644 --- a/drivers/ioexpander/Kconfig +++ b/drivers/ioexpander/Kconfig @@ -56,6 +56,18 @@ config IOEXPANDER_DUMMY_INT_POLLDELAY endif # IOEXPANDER_DUMMY +config IOEXPANDER_CH422G + bool "CH422G I2C IO expander" + default n + depends on I2C + ---help--- + Enable support for the WCH CH422G IO Expander, which offers eight + bi-directional pins, IO0-IO7, and four open-drain outputs, OC0-OC3. + + IO0-IO7 share a single direction control in the hardware. The + driver puts the group in output mode when at least one of them has + been configured as an output. + config IOEXPANDER_ICJX bool "iC-JX SPI IO expander" default n diff --git a/drivers/ioexpander/Make.defs b/drivers/ioexpander/Make.defs index 2c949558bb29b..7ca38196d81e6 100644 --- a/drivers/ioexpander/Make.defs +++ b/drivers/ioexpander/Make.defs @@ -58,6 +58,10 @@ ifeq ($(CONFIG_IOEXPANDER_PI4IOE5V6408),y) CSRCS += pi4ioe5v6408.c endif +ifeq ($(CONFIG_IOEXPANDER_CH422G),y) + CSRCS += ch422g.c +endif + ifeq ($(CONFIG_IOEXPANDER_PCA9557),y) CSRCS += pca9557.c endif diff --git a/drivers/ioexpander/ch422g.c b/drivers/ioexpander/ch422g.c new file mode 100644 index 0000000000000..97f75895ca4c6 --- /dev/null +++ b/drivers/ioexpander/ch422g.c @@ -0,0 +1,750 @@ +/**************************************************************************** + * drivers/ioexpander/ch422g.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/* The WCH CH422G is an I2C I/O expander offering eight bi-directional + * pins, IO0-IO7, and four open-drain outputs, OC0-OC3. + * + * It does not follow the usual convention of a register address written + * ahead of the data. Each register is reached through an I2C address of + * its own and every access carries a single data byte: + * + * 0x24 write System parameter register + * 0x23 write OC0-OC3 output register + * 0x38 write IO0-IO7 output register + * 0x26 read IO0-IO7 input register + * + * None of the write-only registers can be read back, so the driver keeps a + * shadow copy of each and updates it in step with the device. + * + * IO0-IO7 do not have individual direction control. A single bit of the + * system parameter register, IO_OE, drives the whole group. The driver + * records the direction requested for each pin and puts the group in output + * mode when at least one of them is an output, which is what a board that + * mixes the two would expect of the pins it drives. Reading a pin of a + * group held in output mode returns the shadowed output value rather than + * the level on the pin, because the hardware cannot report it. + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* I2C addresses that select a register. See the comment above. */ + +#define CH422G_ADDR_WR_SET 0x24 /* System parameter register */ +#define CH422G_ADDR_WR_OC 0x23 /* OC0-OC3 output register */ +#define CH422G_ADDR_WR_IO 0x38 /* IO0-IO7 output register */ +#define CH422G_ADDR_RD_IO 0x26 /* IO0-IO7 input register */ + +/* System parameter register bits */ + +#define CH422G_SET_IO_OE (1 << 0) /* IO0-IO7 drive their pins */ +#define CH422G_SET_A_SCAN (1 << 1) /* Digital tube scan enable */ +#define CH422G_SET_OD_EN (1 << 2) /* OC0-OC3 open-drain enable */ +#define CH422G_SET_SLEEP (1 << 3) /* Low power mode */ + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct ch422g_dev_s +{ + /* Must appear first so the structure can be cast to the public type */ + + struct ioexpander_dev_s dev; + + FAR struct i2c_master_s *i2c; /* Saved I2C driver instance */ + FAR struct ch422g_config_s *config; /* Board configuration data */ + mutex_t lock; /* Mutual exclusion */ + + uint8_t sysparam; /* Shadow of system parameter reg */ + uint8_t outio; /* Shadow of IO0-IO7 output reg */ + uint8_t outoc; /* Shadow of OC0-OC3 output reg */ + uint8_t outmask; /* IO0-IO7 configured as outputs */ +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static int ch422g_direction(FAR struct ioexpander_dev_s *dev, uint8_t pin, + int dir); +static int ch422g_option(FAR struct ioexpander_dev_s *dev, uint8_t pin, + int opt, FAR void *val); +static int ch422g_writepin(FAR struct ioexpander_dev_s *dev, uint8_t pin, + bool value); +static int ch422g_readpin(FAR struct ioexpander_dev_s *dev, uint8_t pin, + FAR bool *value); +static int ch422g_readbuf(FAR struct ioexpander_dev_s *dev, uint8_t pin, + FAR bool *value); +#ifdef CONFIG_IOEXPANDER_MULTIPIN +static int ch422g_multiwritepin(FAR struct ioexpander_dev_s *dev, + FAR const uint8_t *pins, + FAR const bool *values, int count); +static int ch422g_multireadpin(FAR struct ioexpander_dev_s *dev, + FAR const uint8_t *pins, FAR bool *values, + int count); +static int ch422g_multireadbuf(FAR struct ioexpander_dev_s *dev, + FAR const uint8_t *pins, FAR bool *values, + int count); +#endif + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct ioexpander_ops_s g_ch422g_ops = +{ + .ioe_direction = ch422g_direction, + .ioe_option = ch422g_option, + .ioe_writepin = ch422g_writepin, + .ioe_readpin = ch422g_readpin, + .ioe_readbuf = ch422g_readbuf, +#ifdef CONFIG_IOEXPANDER_MULTIPIN + .ioe_multiwritepin = ch422g_multiwritepin, + .ioe_multireadpin = ch422g_multireadpin, + .ioe_multireadbuf = ch422g_multireadbuf, +#endif +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: ch422g_write_reg + * + * Description: + * Write the single data byte of one register. The register is selected + * by the I2C address the transfer is addressed to. + * + ****************************************************************************/ + +static int ch422g_write_reg(FAR struct ch422g_dev_s *priv, uint8_t addr, + uint8_t value) +{ + struct i2c_msg_s msg; + + msg.frequency = priv->config->frequency; + msg.addr = addr; + msg.flags = 0; + msg.buffer = &value; + msg.length = 1; + + return I2C_TRANSFER(priv->i2c, &msg, 1); +} + +/**************************************************************************** + * Name: ch422g_read_io + * + * Description: + * Read the level of IO0-IO7. Only meaningful while the group is in + * input mode. + * + ****************************************************************************/ + +static int ch422g_read_io(FAR struct ch422g_dev_s *priv, FAR uint8_t *value) +{ + struct i2c_msg_s msg; + + msg.frequency = priv->config->frequency; + msg.addr = CH422G_ADDR_RD_IO; + msg.flags = I2C_M_READ; + msg.buffer = value; + msg.length = 1; + + return I2C_TRANSFER(priv->i2c, &msg, 1); +} + +/**************************************************************************** + * Name: ch422g_setdir + * + * Description: + * Bring the IO_OE bit into agreement with the recorded per-pin + * directions. Must be called with the lock held. + * + ****************************************************************************/ + +static int ch422g_setdir(FAR struct ch422g_dev_s *priv) +{ + uint8_t sysparam = priv->sysparam; + + if (priv->outmask != 0) + { + sysparam |= CH422G_SET_IO_OE; + } + else + { + sysparam &= ~CH422G_SET_IO_OE; + } + + if (sysparam == priv->sysparam) + { + return OK; + } + + priv->sysparam = sysparam; + return ch422g_write_reg(priv, CH422G_ADDR_WR_SET, sysparam); +} + +/**************************************************************************** + * Name: ch422g_direction + * + * Description: + * Set the direction of an I/O pin. + * + ****************************************************************************/ + +static int ch422g_direction(FAR struct ioexpander_dev_s *dev, uint8_t pin, + int dir) +{ + FAR struct ch422g_dev_s *priv = (FAR struct ch422g_dev_s *)dev; + int ret; + + if (pin >= CH422G_NPINS) + { + return -ENXIO; + } + + if (dir != IOEXPANDER_DIRECTION_IN && dir != IOEXPANDER_DIRECTION_OUT) + { + return -EINVAL; + } + + /* OC0-OC3 are open-drain outputs and cannot be inputs */ + + if (pin >= CH422G_NIO) + { + return dir == IOEXPANDER_DIRECTION_OUT ? OK : -EINVAL; + } + + ret = nxmutex_lock(&priv->lock); + if (ret < 0) + { + return ret; + } + + if (dir == IOEXPANDER_DIRECTION_OUT) + { + priv->outmask |= (1 << pin); + } + else + { + priv->outmask &= ~(1 << pin); + } + + ret = ch422g_setdir(priv); + + nxmutex_unlock(&priv->lock); + return ret; +} + +/**************************************************************************** + * Name: ch422g_option + * + * Description: + * Set pin options. The CH422G offers none of the options the interface + * defines, so only a request that asks for nothing succeeds. + * + ****************************************************************************/ + +static int ch422g_option(FAR struct ioexpander_dev_s *dev, uint8_t pin, + int opt, FAR void *val) +{ + FAR struct ch422g_dev_s *priv = (FAR struct ch422g_dev_s *)dev; + int ret = -ENOSYS; + + if (pin >= CH422G_NPINS) + { + return -ENXIO; + } + + if (opt == IOEXPANDER_OPTION_INVERT) + { + ret = ((uintptr_t)val == IOEXPANDER_VAL_NORMAL) ? OK : -ENOSYS; + } + else if (opt == IOEXPANDER_OPTION_INTCFG) + { + ret = ((uintptr_t)val == IOEXPANDER_VAL_DISABLE) ? OK : -ENOSYS; + } + + UNUSED(priv); + return ret; +} + +/**************************************************************************** + * Name: ch422g_writepin + * + * Description: + * Set the level of an output pin. + * + ****************************************************************************/ + +static int ch422g_writepin(FAR struct ioexpander_dev_s *dev, uint8_t pin, + bool value) +{ + FAR struct ch422g_dev_s *priv = (FAR struct ch422g_dev_s *)dev; + FAR uint8_t *shadow; + uint8_t addr; + uint8_t bit; + int ret; + + if (pin >= CH422G_NPINS) + { + return -ENXIO; + } + + ret = nxmutex_lock(&priv->lock); + if (ret < 0) + { + return ret; + } + + if (pin < CH422G_NIO) + { + shadow = &priv->outio; + addr = CH422G_ADDR_WR_IO; + bit = 1 << pin; + } + else + { + shadow = &priv->outoc; + addr = CH422G_ADDR_WR_OC; + bit = 1 << (pin - CH422G_NIO); + } + + if (value) + { + *shadow |= bit; + } + else + { + *shadow &= ~bit; + } + + ret = ch422g_write_reg(priv, addr, *shadow); + + nxmutex_unlock(&priv->lock); + return ret; +} + +/**************************************************************************** + * Name: ch422g_readpin + * + * Description: + * Read the level of a pin. A pin of a group held in output mode, and any + * of the write-only open-drain outputs, reports the value last written. + * + ****************************************************************************/ + +static int ch422g_readpin(FAR struct ioexpander_dev_s *dev, uint8_t pin, + FAR bool *value) +{ + FAR struct ch422g_dev_s *priv = (FAR struct ch422g_dev_s *)dev; + uint8_t regval; + int ret; + + if (pin >= CH422G_NPINS) + { + return -ENXIO; + } + + DEBUGASSERT(value != NULL); + + ret = nxmutex_lock(&priv->lock); + if (ret < 0) + { + return ret; + } + + if (pin >= CH422G_NIO) + { + *value = ((priv->outoc >> (pin - CH422G_NIO)) & 1) != 0; + ret = OK; + } + else if ((priv->sysparam & CH422G_SET_IO_OE) != 0) + { + *value = ((priv->outio >> pin) & 1) != 0; + ret = OK; + } + else + { + ret = ch422g_read_io(priv, ®val); + if (ret >= 0) + { + *value = ((regval >> pin) & 1) != 0; + } + } + + nxmutex_unlock(&priv->lock); + return ret; +} + +/**************************************************************************** + * Name: ch422g_readbuf + * + * Description: + * Read the last value written to a pin. The CH422G does not buffer a + * separately readable copy, so this is the shadowed output value. + * + ****************************************************************************/ + +static int ch422g_readbuf(FAR struct ioexpander_dev_s *dev, uint8_t pin, + FAR bool *value) +{ + FAR struct ch422g_dev_s *priv = (FAR struct ch422g_dev_s *)dev; + int ret; + + if (pin >= CH422G_NPINS) + { + return -ENXIO; + } + + DEBUGASSERT(value != NULL); + + ret = nxmutex_lock(&priv->lock); + if (ret < 0) + { + return ret; + } + + if (pin < CH422G_NIO) + { + *value = ((priv->outio >> pin) & 1) != 0; + } + else + { + *value = ((priv->outoc >> (pin - CH422G_NIO)) & 1) != 0; + } + + nxmutex_unlock(&priv->lock); + return OK; +} + +#ifdef CONFIG_IOEXPANDER_MULTIPIN + +/**************************************************************************** + * Name: ch422g_multiwritepin + * + * Description: + * Set the level of several pins. Pins that share a register are gathered + * so that the register is written once. + * + ****************************************************************************/ + +static int ch422g_multiwritepin(FAR struct ioexpander_dev_s *dev, + FAR const uint8_t *pins, + FAR const bool *values, int count) +{ + FAR struct ch422g_dev_s *priv = (FAR struct ch422g_dev_s *)dev; + bool touchio = false; + bool touchoc = false; + int ret; + int i; + + DEBUGASSERT(pins != NULL && values != NULL); + + for (i = 0; i < count; i++) + { + if (pins[i] >= CH422G_NPINS) + { + return -ENXIO; + } + } + + ret = nxmutex_lock(&priv->lock); + if (ret < 0) + { + return ret; + } + + for (i = 0; i < count; i++) + { + uint8_t pin = pins[i]; + + if (pin < CH422G_NIO) + { + if (values[i]) + { + priv->outio |= (1 << pin); + } + else + { + priv->outio &= ~(1 << pin); + } + + touchio = true; + } + else + { + if (values[i]) + { + priv->outoc |= (1 << (pin - CH422G_NIO)); + } + else + { + priv->outoc &= ~(1 << (pin - CH422G_NIO)); + } + + touchoc = true; + } + } + + ret = OK; + + if (touchio) + { + ret = ch422g_write_reg(priv, CH422G_ADDR_WR_IO, priv->outio); + } + + if (ret >= 0 && touchoc) + { + ret = ch422g_write_reg(priv, CH422G_ADDR_WR_OC, priv->outoc); + } + + nxmutex_unlock(&priv->lock); + return ret; +} + +/**************************************************************************** + * Name: ch422g_multireadpin + * + * Description: + * Read the level of several pins with a single read of the device. + * + ****************************************************************************/ + +static int ch422g_multireadpin(FAR struct ioexpander_dev_s *dev, + FAR const uint8_t *pins, FAR bool *values, + int count) +{ + FAR struct ch422g_dev_s *priv = (FAR struct ch422g_dev_s *)dev; + uint8_t regval = 0; + bool haveinput = false; + int ret; + int i; + + DEBUGASSERT(pins != NULL && values != NULL); + + for (i = 0; i < count; i++) + { + if (pins[i] >= CH422G_NPINS) + { + return -ENXIO; + } + } + + ret = nxmutex_lock(&priv->lock); + if (ret < 0) + { + return ret; + } + + if ((priv->sysparam & CH422G_SET_IO_OE) == 0) + { + for (i = 0; i < count; i++) + { + if (pins[i] < CH422G_NIO) + { + haveinput = true; + break; + } + } + } + + if (haveinput) + { + ret = ch422g_read_io(priv, ®val); + if (ret < 0) + { + nxmutex_unlock(&priv->lock); + return ret; + } + } + + for (i = 0; i < count; i++) + { + uint8_t pin = pins[i]; + + if (pin >= CH422G_NIO) + { + values[i] = ((priv->outoc >> (pin - CH422G_NIO)) & 1) != 0; + } + else if (haveinput) + { + values[i] = ((regval >> pin) & 1) != 0; + } + else + { + values[i] = ((priv->outio >> pin) & 1) != 0; + } + } + + nxmutex_unlock(&priv->lock); + return OK; +} + +/**************************************************************************** + * Name: ch422g_multireadbuf + * + * Description: + * Read the last value written to several pins. + * + ****************************************************************************/ + +static int ch422g_multireadbuf(FAR struct ioexpander_dev_s *dev, + FAR const uint8_t *pins, FAR bool *values, + int count) +{ + FAR struct ch422g_dev_s *priv = (FAR struct ch422g_dev_s *)dev; + int ret; + int i; + + DEBUGASSERT(pins != NULL && values != NULL); + + for (i = 0; i < count; i++) + { + if (pins[i] >= CH422G_NPINS) + { + return -ENXIO; + } + } + + ret = nxmutex_lock(&priv->lock); + if (ret < 0) + { + return ret; + } + + for (i = 0; i < count; i++) + { + uint8_t pin = pins[i]; + + if (pin < CH422G_NIO) + { + values[i] = ((priv->outio >> pin) & 1) != 0; + } + else + { + values[i] = ((priv->outoc >> (pin - CH422G_NIO)) & 1) != 0; + } + } + + nxmutex_unlock(&priv->lock); + return OK; +} + +#endif /* CONFIG_IOEXPANDER_MULTIPIN */ + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: ch422g_initialize + * + * Description: + * Instantiate and configure the CH422G device driver to use the provided + * I2C device instance. + * + * Input Parameters: + * i2c - An I2C driver instance + * config - Persistent board configuration data + * + * Returned Value: + * An ioexpander_dev_s instance on success, NULL on failure. + * + ****************************************************************************/ + +FAR struct ioexpander_dev_s * +ch422g_initialize(FAR struct i2c_master_s *i2c, + FAR struct ch422g_config_s *config) +{ + FAR struct ch422g_dev_s *priv; + int ret; + + DEBUGASSERT(i2c != NULL && config != NULL); + + priv = kmm_zalloc(sizeof(struct ch422g_dev_s)); + if (priv == NULL) + { + gpioerr("ERROR: Failed to allocate driver instance\n"); + return NULL; + } + + priv->dev.ops = &g_ch422g_ops; + priv->i2c = i2c; + priv->config = config; + + nxmutex_init(&priv->lock); + + /* Leave the device in a known state: the open-drain outputs off, the + * bi-directional pins low, and the group in input mode until a board + * asks for an output. Writing the system parameter register first also + * confirms that the device is answering. + */ + + ret = ch422g_write_reg(priv, CH422G_ADDR_WR_SET, priv->sysparam); + if (ret < 0) + { + gpioerr("ERROR: CH422G not responding: %d\n", ret); + goto errout; + } + + ret = ch422g_write_reg(priv, CH422G_ADDR_WR_OC, priv->outoc); + if (ret < 0) + { + goto errout; + } + + ret = ch422g_write_reg(priv, CH422G_ADDR_WR_IO, priv->outio); + if (ret < 0) + { + goto errout; + } + + return &priv->dev; + +errout: + nxmutex_destroy(&priv->lock); + kmm_free(priv); + return NULL; +} + diff --git a/include/nuttx/ioexpander/ch422g.h b/include/nuttx/ioexpander/ch422g.h new file mode 100644 index 0000000000000..2d48bb308b884 --- /dev/null +++ b/include/nuttx/ioexpander/ch422g.h @@ -0,0 +1,116 @@ +/**************************************************************************** + * include/nuttx/ioexpander/ch422g.h + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __INCLUDE_NUTTX_IOEXPANDER_CH422G_H +#define __INCLUDE_NUTTX_IOEXPANDER_CH422G_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Pin numbering used by the ioexpander interface. + * + * The CH422G offers eight bi-directional pins, IO0-IO7, and four + * open-drain outputs, OC0-OC3. They are presented as one contiguous pin + * space so that a single ioexpander_dev_s covers the whole chip. + */ + +#define CH422G_IO0 0 +#define CH422G_IO1 1 +#define CH422G_IO2 2 +#define CH422G_IO3 3 +#define CH422G_IO4 4 +#define CH422G_IO5 5 +#define CH422G_IO6 6 +#define CH422G_IO7 7 +#define CH422G_OC0 8 +#define CH422G_OC1 9 +#define CH422G_OC2 10 +#define CH422G_OC3 11 + +#define CH422G_NPINS 12 +#define CH422G_NIO 8 /* IO0-IO7 */ +#define CH422G_NOC 4 /* OC0-OC3 */ + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +/* A reference to a structure of this type must be passed to the CH422G + * driver when the driver is instantiated. Memory for this structure is + * provided by the caller. It is not copied by the driver and is presumed + * to persist while the driver is active. + */ + +struct ch422g_config_s +{ + uint32_t frequency; /* I2C frequency */ +}; + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +#ifdef __cplusplus +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/**************************************************************************** + * Name: ch422g_initialize + * + * Description: + * Instantiate and configure the CH422G device driver to use the provided + * I2C device instance. + * + * Input Parameters: + * i2c - An I2C driver instance + * config - Persistent board configuration data + * + * Returned Value: + * An ioexpander_dev_s instance on success, NULL on failure. + * + ****************************************************************************/ + +FAR struct ioexpander_dev_s * +ch422g_initialize(FAR struct i2c_master_s *i2c, + FAR struct ch422g_config_s *config); + +#undef EXTERN +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_IOEXPANDER_CH422G_H */