nuttx/drivers/lcd/st7796.c
Piyush Patle 0dccc8ba21 include/debug.h: Move to include/nuttx/debug.h
debug.h is a NuttX-specific, non-POSIX header. Placing it in the
top-level include/ directory creates naming conflicts with external
projects that define their own debug.h.
This commit moves the canonical header to include/nuttx/debug.h,
following the NuttX convention for non-POSIX/non-standard headers,
and updates all in-tree references.

A backward-compatibility shim is left at include/debug.h that
emits a deprecation #warning and re-includes <nuttx/debug.h>,
allowing out-of-tree code to continue building while migrating.

Signed-off-by: Piyush Patle <piyushpatle228@gmail.com>
2026-04-07 07:50:06 -03:00

1130 lines
34 KiB
C

/****************************************************************************
* drivers/lcd/st7796.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 <nuttx/config.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <stdint.h>
#include <stdbool.h>
#include <assert.h>
#include <errno.h>
#include <nuttx/debug.h>
#include <nuttx/arch.h>
#include <nuttx/spi/spi.h>
#include <nuttx/video/fb.h>
#include <nuttx/kmalloc.h>
#include <nuttx/clock.h>
#include <nuttx/signal.h>
#include <nuttx/lcd/st7796.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* ST7796 Commands - Standard */
#define ST7796_NOP 0x00 /* NOP */
#define ST7796_SWRESET 0x01 /* Software Reset */
#define ST7796_RDDID 0x04 /* Read Display ID */
#define ST7796_RDDST 0x09 /* Read Display Status */
#define ST7796_SLPIN 0x10 /* Sleep In */
#define ST7796_SLPOUT 0x11 /* Sleep Out */
#define ST7796_PTLON 0x12 /* Partial Display Mode On */
#define ST7796_NORON 0x13 /* Normal Display Mode On */
#define ST7796_RDIMGFMT 0x0a /* Read Display Image Format */
#define ST7796_RDSELFDIAG 0x0f /* Read Display Self-Diagnostic */
#define ST7796_INVOFF 0x20 /* Display Inversion Off */
#define ST7796_INVON 0x21 /* Display Inversion On */
#define ST7796_GAMMASET 0x26 /* Gamma Set */
#define ST7796_DISPOFF 0x28 /* Display Off */
#define ST7796_DISPON 0x29 /* Display On */
#define ST7796_CASET 0x2a /* Column Address Set */
#define ST7796_RASET 0x2b /* Row Address Set */
#define ST7796_RAMWR 0x2c /* Memory Write */
#define ST7796_RAMRD 0x2e /* Memory Read */
#define ST7796_PTLAR 0x30 /* Partial Area */
#define ST7796_VSCRDEF 0x33 /* Vertical Scrolling Definition */
#define ST7796_TEOFF 0x34 /* Tearing Effect Line Off */
#define ST7796_TEON 0x35 /* Tearing Effect Line On */
#define ST7796_MADCTL 0x36 /* Memory Access Control */
#define ST7796_VSCRSADD 0x37 /* Vertical Scrolling Start Address */
#define ST7796_PIXFMT 0x3a /* Pixel Format Set */
#define ST7796_WRDISPBRIGHT 0x51 /* Write Display Brightness */
#define ST7796_RDDISPBRIGHT 0x52 /* Read Display Brightness */
#define ST7796_WRCTRLD 0x53 /* Write Control Display */
#define ST7796_RDCTRLD 0x54 /* Read Control Display */
#define ST7796_WRCABC 0x55 /* Write Content Adaptive Brightness */
#define ST7796_RDCABC 0x56 /* Read Content Adaptive Brightness */
#define ST7796_WRCABCMIN 0x5e /* Write CABC Minimum Brightness */
#define ST7796_RDCABCMIN 0x5f /* Read CABC Minimum Brightness */
/* ST7796 Commands - Extended */
#define ST7796_INVCTR 0xb4 /* Display Inversion Control */
#define ST7796_DFC 0xb6 /* Display Function Control */
#define ST7796_PWCTRL1 0xc0 /* Power Control 1 */
#define ST7796_PWCTRL2 0xc1 /* Power Control 2 */
#define ST7796_PWCTRL3 0xc2 /* Power Control 3 */
#define ST7796_PWCTRL4 0xc3 /* Power Control 4 */
#define ST7796_PWCTRL5 0xc4 /* Power Control 5 */
#define ST7796_VCOM 0xc5 /* VCOM Control */
#define ST7796_PWCTRL6 0xc6 /* Power Control 6 */
#define ST7796_GAMMAPOS 0xe0 /* Positive Gamma Correction */
#define ST7796_GAMMANEG 0xe1 /* Negative Gamma Correction */
#define ST7796_DOCA 0xe9 /* Set DDB Write Address */
#define ST7796_CSCON 0xf0 /* Command Set Control */
/* ST7796 MADCTL bits */
#define ST7796_MADCTL_MY 0x80 /* Row Address Order */
#define ST7796_MADCTL_MX 0x40 /* Column Address Order */
#define ST7796_MADCTL_MV 0x20 /* Row/Column Exchange */
#define ST7796_MADCTL_ML 0x10 /* Vertical Refresh Order */
#define ST7796_MADCTL_BGR 0x08 /* BGR color filter panel */
#define ST7796_MADCTL_MH 0x04 /* Horizontal Refresh Order */
/* Pre-defined MADCTL values for common orientations */
#define ST7796_MADCTL_PORTRAIT (ST7796_MADCTL_MX)
#define ST7796_MADCTL_PORTRAIT_BGR (ST7796_MADCTL_MX | ST7796_MADCTL_BGR)
#define ST7796_MADCTL_RPORTRAIT (ST7796_MADCTL_MY)
#define ST7796_MADCTL_RPORTRAIT_BGR (ST7796_MADCTL_MY | ST7796_MADCTL_BGR)
#define ST7796_MADCTL_LANDSCAPE (ST7796_MADCTL_MV)
#define ST7796_MADCTL_LANDSCAPE_BGR (ST7796_MADCTL_MV | ST7796_MADCTL_BGR)
#define ST7796_MADCTL_RLANDSCAPE (ST7796_MADCTL_MY | ST7796_MADCTL_MX | \
ST7796_MADCTL_MV)
#define ST7796_MADCTL_RLANDSCAPE_BGR (ST7796_MADCTL_MY | ST7796_MADCTL_MX | \
ST7796_MADCTL_MV | ST7796_MADCTL_BGR)
/* Effective resolution from raw panel size and orientation */
#if defined(CONFIG_LCD_ST7796_LANDSCAPE) || \
defined(CONFIG_LCD_ST7796_RLANDSCAPE)
# define ST7796_XRES CONFIG_LCD_ST7796_YRES
# define ST7796_YRES CONFIG_LCD_ST7796_XRES
#else
# define ST7796_XRES CONFIG_LCD_ST7796_XRES
# define ST7796_YRES CONFIG_LCD_ST7796_YRES
#endif
/* Base MADCTL from orientation and BGR Kconfig settings */
#if defined(CONFIG_LCD_ST7796_LANDSCAPE)
# ifdef CONFIG_LCD_ST7796_BGR
# define ST7796_MADCTL_BASE ST7796_MADCTL_LANDSCAPE_BGR
# else
# define ST7796_MADCTL_BASE ST7796_MADCTL_LANDSCAPE
# endif
#elif defined(CONFIG_LCD_ST7796_RPORTRAIT)
# ifdef CONFIG_LCD_ST7796_BGR
# define ST7796_MADCTL_BASE ST7796_MADCTL_RPORTRAIT_BGR
# else
# define ST7796_MADCTL_BASE ST7796_MADCTL_RPORTRAIT
# endif
#elif defined(CONFIG_LCD_ST7796_RLANDSCAPE)
# ifdef CONFIG_LCD_ST7796_BGR
# define ST7796_MADCTL_BASE ST7796_MADCTL_RLANDSCAPE_BGR
# else
# define ST7796_MADCTL_BASE ST7796_MADCTL_RLANDSCAPE
# endif
#else /* Portrait (default) */
# ifdef CONFIG_LCD_ST7796_BGR
# define ST7796_MADCTL_BASE ST7796_MADCTL_PORTRAIT_BGR
# else
# define ST7796_MADCTL_BASE ST7796_MADCTL_PORTRAIT
# endif
#endif
/* SPI mode: map Kconfig integer to NuttX enum value */
#if CONFIG_LCD_ST7796_SPIMODE == 0
# define ST7796_SPIMODE SPIDEV_MODE0
#elif CONFIG_LCD_ST7796_SPIMODE == 1
# define ST7796_SPIMODE SPIDEV_MODE1
#elif CONFIG_LCD_ST7796_SPIMODE == 2
# define ST7796_SPIMODE SPIDEV_MODE2
#elif CONFIG_LCD_ST7796_SPIMODE == 3
# define ST7796_SPIMODE SPIDEV_MODE3
#else
# error "Invalid CONFIG_LCD_ST7796_SPIMODE (must be 0-3)"
#endif
/* Bytes per pixel */
#define ST7796_BYTESPP ((CONFIG_LCD_ST7796_BPP == 18) ? 3 : 2)
/* Color format */
#define ST7796_COLORFMT ((CONFIG_LCD_ST7796_BPP == 18) ? \
FB_FMT_RGB24 : FB_FMT_RGB16_565)
/****************************************************************************
* Private Types
****************************************************************************/
/* Command sequence entry for initialization */
struct st7796_cmd_s
{
uint8_t cmd; /* Command byte */
FAR const uint8_t *data; /* Parameter data (NULL if no params) */
uint8_t len; /* Number of parameter bytes */
uint16_t delay_ms; /* Delay after command in milliseconds */
};
struct st7796_dev_s
{
struct fb_vtable_s vtable;
FAR struct spi_dev_s *spi;
FAR uint8_t *fbmem;
FAR uint16_t *swap_buf; /* Persistent buffer for byte-swapping */
uint16_t rotation; /* Current rotation in degrees */
bool power;
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
static int st7796_getvideoinfo(FAR struct fb_vtable_s *vtable,
FAR struct fb_videoinfo_s *vinfo);
static int st7796_getplaneinfo(FAR struct fb_vtable_s *vtable, int planeno,
FAR struct fb_planeinfo_s *pinfo);
static int st7796_updatearea(FAR struct fb_vtable_s *vtable,
FAR const struct fb_area_s *area);
static int st7796_getpower(FAR struct fb_vtable_s *vtable);
static int st7796_setpower(FAR struct fb_vtable_s *vtable,
int power);
static int st7796_ioctl(FAR struct fb_vtable_s *vtable, int cmd,
unsigned long arg);
static void st7796_select(FAR struct st7796_dev_s *priv);
static void st7796_deselect(FAR struct st7796_dev_s *priv);
static void st7796_sendcmd(FAR struct st7796_dev_s *priv, uint8_t cmd);
static void st7796_senddata(FAR struct st7796_dev_s *priv,
FAR const uint8_t *data, size_t len);
static void st7796_send_sequence(FAR struct st7796_dev_s *priv,
FAR const struct st7796_cmd_s *seq,
size_t count);
static void st7796_setarea(FAR struct st7796_dev_s *priv,
uint16_t x0, uint16_t y0,
uint16_t x1, uint16_t y1);
static void st7796_apply_rotation(FAR struct st7796_dev_s *priv);
/****************************************************************************
* Private Data
****************************************************************************/
static struct st7796_dev_s g_st7796dev;
/* Initialization sequence data arrays
*
* ST7796S Register Configuration Reference:
*
* CSCON (Command Set Control):
* 0xc3, 0x96 - Enable extended command access (unlock)
* 0x3c, 0x69 - Disable extended command access (lock)
*
* PIXFMT (Pixel Format):
* 0x55 - RGB565 16bpp (0101 0101b: 16-bit RGB + 16-bit MCU)
*
* INVCTR (Display Inversion):
* 0x01 - Column inversion mode
*
* DFC (Display Function Control):
* 0x80 - Source output scan direction
* 0x02 - Gate output scan direction
* 0x3b - Number of lines (59 * 8 = 472 lines)
*
* CMD 0xE8 (Undocumented):
* Internal timing adjustment, from manufacturer reference
*
* PWCTRL2/3 (Power Control):
* 0x06 - VGH/VGL voltage setting
* 0xa7 - VCOM voltage adjustment
*
* VCOM:
* 0x18 - VCOM voltage for contrast tuning
*
* GAMMAPOS/GAMMANEG:
* 14-byte gamma correction curves for color linearity
*
* MADCTL values provided by board configuration
*/
static const uint8_t g_cscon1_data[] =
{
0xc3
};
static const uint8_t g_cscon2_data[] =
{
0x96
};
static const uint8_t g_pixfmt_data[] =
{
0x55
};
static const uint8_t g_invctr_data[] =
{
0x01
};
static const uint8_t g_dfc_data[] =
{
0x80, 0x02, 0x3b
};
static const uint8_t g_cmd_e8_data[] =
{
0x40, 0x8a, 0x00, 0x00, 0x29, 0x19, 0xa5, 0x33
};
static const uint8_t g_pwctrl2_data[] =
{
0x06
};
static const uint8_t g_pwctrl3_data[] =
{
0xa7
};
static const uint8_t g_vcom_data[] =
{
0x18
};
static const uint8_t g_gammapos_data[] =
{
0xf0, 0x09, 0x0b, 0x06, 0x04, 0x15,
0x2f, 0x54, 0x42, 0x3c, 0x17, 0x14,
0x18, 0x1b
};
static const uint8_t g_gammaneg_data[] =
{
0xf0, 0x09, 0x0b, 0x06, 0x04, 0x03,
0x2d, 0x43, 0x42, 0x3b, 0x16, 0x14,
0x17, 0x1b
};
static const uint8_t g_cscon3_data[] =
{
0x3c
};
static const uint8_t g_cscon4_data[] =
{
0x69
};
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: st7796_select
*
* Description:
* Select the SPI device, locking the bus and configuring SPI parameters.
* This function locks the SPI bus, sets the SPI mode, bit width, and
* frequency, then asserts the chip select signal.
*
* Input Parameters:
* priv - Reference to the ST7796 device structure
*
* Returned Value:
* None
*
****************************************************************************/
static void st7796_select(FAR struct st7796_dev_s *priv)
{
SPI_LOCK(priv->spi, true);
SPI_SETMODE(priv->spi, ST7796_SPIMODE);
SPI_SETBITS(priv->spi, 8);
SPI_SETFREQUENCY(priv->spi, CONFIG_LCD_ST7796_FREQUENCY);
SPI_SELECT(priv->spi, SPIDEV_DISPLAY(0), true);
}
/****************************************************************************
* Name: st7796_deselect
*
* Description:
* Deselect the SPI device, releasing the chip select and unlocking the
* SPI bus for other devices.
*
* Input Parameters:
* priv - Reference to the ST7796 device structure
*
* Returned Value:
* None
*
****************************************************************************/
static void st7796_deselect(FAR struct st7796_dev_s *priv)
{
SPI_SELECT(priv->spi, SPIDEV_DISPLAY(0), false);
SPI_LOCK(priv->spi, false);
}
/****************************************************************************
* Name: st7796_sendcmd
*
* Description:
* Send a command byte to the ST7796 display controller. The D/C (Data/
* Command) line is set to command mode before transmission.
*
* Input Parameters:
* priv - Reference to the ST7796 device structure
* cmd - Command byte to send
*
* Returned Value:
* None
*
****************************************************************************/
static void st7796_sendcmd(FAR struct st7796_dev_s *priv, uint8_t cmd)
{
#ifdef CONFIG_SPI_CMDDATA
SPI_CMDDATA(priv->spi, SPIDEV_DISPLAY(0), true);
SPI_SEND(priv->spi, cmd);
#else
# error "CONFIG_SPI_CMDDATA must be enabled for ST7796"
#endif
}
/****************************************************************************
* Name: st7796_senddata
*
* Description:
* Send data bytes to the ST7796 display controller. The D/C (Data/
* Command) line is set to data mode before transmission.
*
* Input Parameters:
* priv - Reference to the ST7796 device structure
* data - Pointer to the data buffer to send
* len - Number of bytes to send
*
* Returned Value:
* None
*
****************************************************************************/
static void st7796_senddata(FAR struct st7796_dev_s *priv,
FAR const uint8_t *data, size_t len)
{
if (len > 0 && data != NULL)
{
#ifdef CONFIG_SPI_CMDDATA
SPI_CMDDATA(priv->spi, SPIDEV_DISPLAY(0), false);
SPI_SNDBLOCK(priv->spi, data, len);
#else
# error "CONFIG_SPI_CMDDATA must be enabled for ST7796"
#endif
}
}
/****************************************************************************
* Name: st7796_send_sequence
*
* Description:
* Send a sequence of commands and data to the ST7796 display controller.
* Each entry in the sequence contains a command, optional data, and an
* optional delay. This function is typically used for display
* initialization.
*
* Input Parameters:
* priv - Reference to the ST7796 device structure
* seq - Pointer to an array of command/data sequence entries
* count - Number of entries in the sequence array
*
* Returned Value:
* None
*
****************************************************************************/
static void st7796_send_sequence(FAR struct st7796_dev_s *priv,
FAR const struct st7796_cmd_s *seq,
size_t count)
{
size_t i;
lcdinfo("ST7796: Sending initialization sequence (%zu commands)\n", count);
for (i = 0; i < count; i++)
{
st7796_sendcmd(priv, seq[i].cmd);
if (seq[i].data != NULL && seq[i].len > 0)
{
st7796_senddata(priv, seq[i].data, seq[i].len);
}
if (seq[i].delay_ms > 0)
{
nxsig_usleep(seq[i].delay_ms * 1000);
}
}
lcdinfo("ST7796: Initialization sequence complete\n");
}
/****************************************************************************
* Name: st7796_setarea
*
* Description:
* Set the active drawing area on the ST7796 display. This defines the
* rectangular region where subsequent pixel data will be written. The
* function sends CASET (Column Address Set) and RASET (Row Address Set)
* commands to define the window boundaries.
*
* Input Parameters:
* priv - Reference to the ST7796 device structure
* x0 - Start column (left edge)
* y0 - Start row (top edge)
* x1 - End column (right edge, inclusive)
* y1 - End row (bottom edge, inclusive)
*
* Returned Value:
* None
*
****************************************************************************/
static void st7796_setarea(FAR struct st7796_dev_s *priv,
uint16_t x0, uint16_t y0,
uint16_t x1, uint16_t y1)
{
uint8_t data[4];
st7796_sendcmd(priv, ST7796_CASET);
data[0] = (x0 >> 8) & 0xff;
data[1] = x0 & 0xff;
data[2] = (x1 >> 8) & 0xff;
data[3] = x1 & 0xff;
st7796_senddata(priv, data, 4);
st7796_sendcmd(priv, ST7796_RASET);
data[0] = (y0 >> 8) & 0xff;
data[1] = y0 & 0xff;
data[2] = (y1 >> 8) & 0xff;
data[3] = y1 & 0xff;
st7796_senddata(priv, data, 4);
}
/****************************************************************************
* Name: st7796_apply_rotation
*
* Description:
* Apply rotation by modifying the MADCTL register. This function
* calculates the appropriate MADCTL value based on the base orientation
* and the requested rotation angle, then sends it to the display.
*
* Input Parameters:
* priv - Reference to the ST7796 device structure
*
* Returned Value:
* None
*
****************************************************************************/
static void st7796_apply_rotation(FAR struct st7796_dev_s *priv)
{
uint8_t madctl;
uint8_t data[1];
/* Start with base MADCTL from orientation and BGR config */
madctl = ST7796_MADCTL_BASE;
/* Apply rotation by modifying MX/MY bits */
switch (priv->rotation)
{
case 0:
/* No rotation - use base MADCTL as-is */
break;
case 90:
/* 90 degrees: swap MV and toggle MX */
madctl ^= (ST7796_MADCTL_MV | ST7796_MADCTL_MX);
break;
case 180:
/* 180 degrees: toggle both MX and MY */
madctl ^= (ST7796_MADCTL_MX | ST7796_MADCTL_MY);
break;
case 270:
/* 270 degrees: swap MV and toggle MY */
madctl ^= (ST7796_MADCTL_MV | ST7796_MADCTL_MY);
break;
default:
lcdwarn("ST7796: Invalid rotation %d, using 0\n", priv->rotation);
priv->rotation = 0;
break;
}
/* Send MADCTL command */
data[0] = madctl;
st7796_select(priv);
st7796_sendcmd(priv, ST7796_MADCTL);
st7796_senddata(priv, data, 1);
st7796_deselect(priv);
lcdinfo("ST7796: Applied rotation %d (MADCTL=0x%02x)\n",
priv->rotation, madctl);
}
/****************************************************************************
* Name: st7796_getvideoinfo
*
* Description:
* Get information about the video controller and the configuration of
* the video plane. This is part of the framebuffer interface required
* by NuttX.
*
* Input Parameters:
* vtable - Reference to the framebuffer virtual table
* vinfo - Pointer to the video info structure to be filled
*
* Returned Value:
* OK on success; a negated errno value on failure.
*
****************************************************************************/
static int st7796_getvideoinfo(FAR struct fb_vtable_s *vtable,
FAR struct fb_videoinfo_s *vinfo)
{
lcdinfo("ST7796: getvideoinfo\n");
vinfo->fmt = ST7796_COLORFMT;
vinfo->xres = ST7796_XRES;
vinfo->yres = ST7796_YRES;
vinfo->nplanes = 1;
return OK;
}
/****************************************************************************
* Name: st7796_getplaneinfo
*
* Description:
* Get information about the framebuffer plane. Returns details about
* the framebuffer memory, stride, bits per pixel, and virtual
* resolution. This is part of the framebuffer interface required by
* NuttX.
*
* Input Parameters:
* vtable - Reference to the framebuffer virtual table
* planeno - Plane number (must be 0 for this single-plane display)
* pinfo - Pointer to the plane info structure to be filled
*
* Returned Value:
* OK on success; a negated errno value on failure.
*
****************************************************************************/
static int st7796_getplaneinfo(FAR struct fb_vtable_s *vtable, int planeno,
FAR struct fb_planeinfo_s *pinfo)
{
FAR struct st7796_dev_s *priv = (FAR struct st7796_dev_s *)vtable;
size_t fbsize;
uint8_t bytespp;
lcdinfo("ST7796: getplaneinfo - plane %d\n", planeno);
bytespp = ST7796_BYTESPP;
fbsize = ST7796_XRES * ST7796_YRES * bytespp;
pinfo->fbmem = priv->fbmem;
pinfo->fblen = fbsize;
pinfo->stride = ST7796_XRES * bytespp;
pinfo->bpp = CONFIG_LCD_ST7796_BPP;
pinfo->xres_virtual = ST7796_XRES;
pinfo->yres_virtual = ST7796_YRES;
pinfo->xoffset = 0;
pinfo->yoffset = 0;
return OK;
}
/****************************************************************************
* Name: st7796_updatearea
*
* Description:
* Update a rectangular area of the display from the framebuffer. This
* function transfers pixel data from the in-memory framebuffer to the
* ST7796 display controller via SPI. The pixel data is byte-swapped
* from little-endian (CPU native) to big-endian (display native) format
* during transfer.
*
* Input Parameters:
* vtable - Reference to the framebuffer virtual table
* area - Pointer to structure describing the rectangular area to
* update (x, y, width, height)
*
* Returned Value:
* OK on success; a negated errno value on failure.
*
****************************************************************************/
static int st7796_updatearea(FAR struct fb_vtable_s *vtable,
FAR const struct fb_area_s *area)
{
FAR struct st7796_dev_s *priv = (FAR struct st7796_dev_s *)vtable;
FAR uint16_t *src_fbptr;
size_t row_size_pixels;
uint8_t bytespp;
int row;
int i;
lcdinfo("ST7796: updatearea - x=%d y=%d w=%d h=%d\n",
area->x, area->y, area->w, area->h);
bytespp = ST7796_BYTESPP;
st7796_select(priv);
st7796_setarea(priv, area->x, area->y,
area->x + area->w - 1,
area->y + area->h - 1);
st7796_sendcmd(priv, ST7796_RAMWR);
#ifdef CONFIG_SPI_CMDDATA
SPI_CMDDATA(priv->spi, SPIDEV_DISPLAY(0), false);
#endif
row_size_pixels = area->w;
src_fbptr = (FAR uint16_t *)
(priv->fbmem +
(area->y * ST7796_XRES + area->x) * bytespp);
for (row = 0; row < area->h; row++)
{
for (i = 0; i < row_size_pixels; i++)
{
uint16_t pixel = src_fbptr[i];
priv->swap_buf[i] = (pixel << 8) | (pixel >> 8);
}
SPI_SNDBLOCK(priv->spi, (FAR const uint8_t *)priv->swap_buf,
row_size_pixels * bytespp);
src_fbptr += ST7796_XRES;
}
st7796_deselect(priv);
return OK;
}
/****************************************************************************
* Name: st7796_getpower
*
* Description:
* Get current panel power state.
*
* Returned Value:
* Current power level (0 = off, 1 = on).
****************************************************************************/
static int st7796_getpower(FAR struct fb_vtable_s *vtable)
{
FAR struct st7796_dev_s *priv =
(FAR struct st7796_dev_s *)vtable;
return priv->power ? 1 : 0;
}
/****************************************************************************
* Name: st7796_setpower
*
* Description:
* Enable or disable panel power (backlight).
* Delegates to st7796_board_power() which the board must
* implement to control the backlight GPIO.
*
* Input Parameters:
* vtable - Reference to the framebuffer virtual table
* power - 0 to turn off, >0 to turn on
*
* Returned Value:
* OK on success.
****************************************************************************/
static int st7796_setpower(FAR struct fb_vtable_s *vtable,
int power)
{
FAR struct st7796_dev_s *priv =
(FAR struct st7796_dev_s *)vtable;
priv->power = (power > 0);
st7796_board_power(priv->power);
lcdinfo("ST7796: power %s\n", priv->power ? "ON" : "OFF");
return OK;
}
/****************************************************************************
* Name: st7796_ioctl
*
* Description:
* Handle framebuffer ioctl commands. Supports rotation control via
* FBIOSET_ROTATION and FBIOGET_ROTATION commands.
*
* Input Parameters:
* vtable - Reference to the framebuffer virtual table
* cmd - ioctl command
* arg - ioctl argument
*
* Returned Value:
* OK on success; a negated errno value on failure.
*
****************************************************************************/
static int st7796_ioctl(FAR struct fb_vtable_s *vtable, int cmd,
unsigned long arg)
{
FAR struct st7796_dev_s *priv = (FAR struct st7796_dev_s *)vtable;
int ret = OK;
switch (cmd)
{
case FBIOSET_ROTATION:
{
uint16_t rotation = (uint16_t)arg;
/* Validate rotation value */
if (rotation != 0 && rotation != 90 &&
rotation != 180 && rotation != 270)
{
lcderr("ERROR: Invalid rotation %d\n", rotation);
ret = -EINVAL;
break;
}
priv->rotation = rotation;
st7796_apply_rotation(priv);
}
break;
case FBIOGET_ROTATION:
{
FAR uint16_t *rotation = (FAR uint16_t *)arg;
if (rotation == NULL)
{
ret = -EINVAL;
break;
}
*rotation = priv->rotation;
}
break;
case FBIO_UPDATE:
{
FAR struct fb_area_s *area = (FAR struct fb_area_s *)arg;
ret = st7796_updatearea(vtable, area);
}
break;
default:
ret = -ENOTTY;
break;
}
return ret;
}
/****************************************************************************
* Name: st7796_init_display
*
* Description:
* Initialize the ST7796 display hardware with the provided configuration.
* This sends the initialization sequence and configures MADCTL based on
* board-provided settings.
*
* Input Parameters:
* priv - Reference to the ST7796 device structure
*
* Returned Value:
* None
*
* Timing requirements from ST7796S datasheet section 9.16:
*
* SWRESET: 120ms minimum recovery time (using 150ms for margin)
* SLPOUT: 120ms minimum for DC-DC and oscillator stabilization
* INVON: No minimum specified, 10ms for safety
* NORON: No minimum specified, 10ms for safety
* DISPON: Frame memory must be written first, 120ms for stabilization
*
* Sequence follows recommended power-on flow:
* 1. Software reset
* 2. Exit sleep mode
* 3. Unlock extended registers (CSCON)
* 4. Configure display parameters (including board-provided MADCTL)
* 5. Lock extended registers (CSCON)
* 6. Enable inversion and normal mode
* 7. Turn on display
*
****************************************************************************/
static void st7796_init_display(FAR struct st7796_dev_s *priv)
{
uint8_t madctl_data[1];
/* MADCTL value from orientation and BGR Kconfig settings */
madctl_data[0] = ST7796_MADCTL_BASE;
/* Initialization sequence - Part 1: Reset and wake */
static const struct st7796_cmd_s init_seq_part1[] =
{
{ ST7796_SWRESET, NULL, 0, 150 },
{ ST7796_SLPOUT, NULL, 0, 150 },
{ ST7796_CSCON, g_cscon1_data, 1, 0 },
{ ST7796_CSCON, g_cscon2_data, 1, 0 },
};
/* Initialization sequence - Part 2: Configuration (after MADCTL) */
static const struct st7796_cmd_s init_seq_part2[] =
{
{ ST7796_PIXFMT, g_pixfmt_data, 1, 0 },
{ ST7796_INVCTR, g_invctr_data, 1, 0 },
{ ST7796_DFC, g_dfc_data, 3, 0 },
{ 0xe8, g_cmd_e8_data, 8, 0 },
{ ST7796_PWCTRL2, g_pwctrl2_data, 1, 0 },
{ ST7796_PWCTRL3, g_pwctrl3_data, 1, 0 },
{ ST7796_VCOM, g_vcom_data, 1, 0 },
{ ST7796_GAMMAPOS, g_gammapos_data, 14, 0 },
{ ST7796_GAMMANEG, g_gammaneg_data, 14, 0 },
{ ST7796_CSCON, g_cscon3_data, 1, 0 },
{ ST7796_CSCON, g_cscon4_data, 1, 0 },
};
/* Initialization sequence - Part 3: Enable */
static const struct st7796_cmd_s init_seq_part3[] =
{
{ ST7796_NORON, NULL, 0, 10 },
{ ST7796_DISPON, NULL, 0, 120 },
};
st7796_select(priv);
/* Send part 1: Reset and unlock */
st7796_send_sequence(priv, init_seq_part1,
sizeof(init_seq_part1) / sizeof(struct st7796_cmd_s));
/* Send MADCTL with board-provided value */
st7796_sendcmd(priv, ST7796_MADCTL);
st7796_senddata(priv, madctl_data, 1);
/* Send part 2: Configuration */
st7796_send_sequence(priv, init_seq_part2,
sizeof(init_seq_part2) /
sizeof(struct st7796_cmd_s));
/* Apply color inversion from Kconfig */
#ifdef CONFIG_LCD_ST7796_INVCOLOR
st7796_sendcmd(priv, ST7796_INVON);
#else
st7796_sendcmd(priv, ST7796_INVOFF);
#endif
nxsig_usleep(10 * 1000);
/* Send part 3: Enable display */
st7796_send_sequence(priv, init_seq_part3,
sizeof(init_seq_part3) /
sizeof(struct st7796_cmd_s));
st7796_deselect(priv);
lcdinfo("ST7796: Display initialized (MADCTL=0x%02x)\n",
ST7796_MADCTL_BASE);
/* Apply initial rotation if configured */
if (priv->rotation != 0)
{
st7796_apply_rotation(priv);
}
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: st7796_fbinitialize
*
* Description:
* Initialize the ST7796 LCD controller and framebuffer driver. This
* function allocates the framebuffer memory, initializes the driver
* structure, sends the display initialization sequence, and returns
* a pointer to the framebuffer virtual table for use with the NuttX
* framebuffer interface.
*
* Input Parameters:
* spi - Reference to the SPI driver structure to use for communication
* config - Board-specific configuration (frequency, resolution, etc.)
*
* Returned Value:
* On success, a pointer to the framebuffer virtual table is returned.
* On failure, NULL is returned.
*
****************************************************************************/
FAR struct fb_vtable_s *st7796_fbinitialize(FAR struct spi_dev_s *spi)
{
FAR struct st7796_dev_s *priv = &g_st7796dev;
size_t fbsize;
uint8_t bytespp;
DEBUGASSERT(spi != NULL);
lcdinfo("ST7796: Initializing framebuffer driver\n");
lcdinfo("ST7796: Resolution: %dx%d @ %d bpp\n",
ST7796_XRES, ST7796_YRES, CONFIG_LCD_ST7796_BPP);
lcdinfo("ST7796: SPI Frequency: %lu Hz\n",
(unsigned long)CONFIG_LCD_ST7796_FREQUENCY);
lcdinfo("ST7796: MADCTL: 0x%02x, Rotation: %d\n",
ST7796_MADCTL_BASE, CONFIG_LCD_ST7796_ROTATION);
/* Set initial rotation from Kconfig */
priv->rotation = CONFIG_LCD_ST7796_ROTATION;
/* Calculate framebuffer size */
bytespp = ST7796_BYTESPP;
fbsize = ST7796_XRES * ST7796_YRES * bytespp;
/* Allocate framebuffer memory */
priv->fbmem = (FAR uint8_t *)kmm_zalloc(fbsize);
if (priv->fbmem == NULL)
{
lcderr("ERROR: Failed to allocate framebuffer (%zu bytes)\n", fbsize);
return NULL;
}
/* Allocate persistent row swap buffer to avoid malloc in updatearea */
priv->swap_buf = (FAR uint16_t *)kmm_malloc(ST7796_XRES * bytespp);
if (priv->swap_buf == NULL)
{
lcderr("ERROR: Failed to allocate swap buffer\n");
kmm_free(priv->fbmem);
return NULL;
}
/* Initialize driver structure */
priv->vtable.getvideoinfo = st7796_getvideoinfo;
priv->vtable.getplaneinfo = st7796_getplaneinfo;
priv->vtable.updatearea = st7796_updatearea;
priv->vtable.getpower = st7796_getpower;
priv->vtable.setpower = st7796_setpower;
priv->vtable.ioctl = st7796_ioctl;
priv->spi = spi;
priv->power = false;
/* Initialize display hardware */
st7796_init_display(priv);
priv->power = true;
lcdinfo("ST7796: Display ready\n");
return &priv->vtable;
}
/****************************************************************************
* Name: st7796_setrotation
*
* Description:
* Set display rotation at runtime. This function can be called directly
* by board code or applications that have access to the vtable pointer.
*
* Input Parameters:
* vtable - Reference to the framebuffer virtual table
* rotation - Rotation angle in degrees (0, 90, 180, or 270)
*
* Returned Value:
* OK on success; a negated errno value on failure.
*
****************************************************************************/
int st7796_setrotation(FAR struct fb_vtable_s *vtable, uint16_t rotation)
{
return st7796_ioctl(vtable, FBIOSET_ROTATION, (unsigned long)rotation);
}
/****************************************************************************
* Name: st7796_getrotation
*
* Description:
* Get current display rotation.
*
* Input Parameters:
* vtable - Reference to the framebuffer virtual table
*
* Returned Value:
* Current rotation in degrees (0, 90, 180, or 270).
*
****************************************************************************/
uint16_t st7796_getrotation(FAR struct fb_vtable_s *vtable)
{
FAR struct st7796_dev_s *priv = (FAR struct st7796_dev_s *)vtable;
return priv->rotation;
}