nuttx/drivers/lcd/ssd1680.c
Huang Qi e09a79ece9 driver/ssd1680: Add support for 1.54 inch e-paper display
Add configuration and driver support for the SSD1681 1.54 inch e-paper display,
including the necessary waveforms and settings for proper operation. This extends
the existing SSD1680 e-paper driver to accommodate the new display module.

The changes involve updates to the Kconfig file for additional configuration options,
modifications to the driver implementation in ssd1680.c to include SSD1681 specific
logic, and updates to the header file ssd1680.h to reflect the added support.

Signed-off-by: Huang Qi <huangqi3@xiaomi.com>
2024-08-04 23:14:55 +08:00

1477 lines
42 KiB
C

/****************************************************************************
* drivers/lcd/ssd1680.c
*
* 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.
*
****************************************************************************/
/* Driver for e-paper displays with SSD1680 controller.
*
* References:
* 1. https://www.crystalfontz.com/controllers/SolomonSystech/SSD1680/497/
* 2. https://www.adafruit.com/product/4947
* (driver for display 2.9 inch mono)
* 3. https://github.com/waveshare/e-Paper
* (driver for display 2.13 v2 mono)
* TTGO-Electronic-Badge mixed with commands found in waveshare library that
* display picture. Additional commands from Waveshare library have suffixes
* with letters e.g. 1a, 2b
* cmd: dta:
* Hardware reset and busy wait
* 1) 0x01 27 01 00
* 1a) 0x74 0x54
* 2b) 0x7E 0x3B
* 2) 0x0C D7 D6 9D boost soft start
* 3) 0x2c A8 write VCom
* 4) 0x3A 0x1A Set dumy line. Can't find it in in SSD documentation
* 5) 0x3B 0x08 Set gate time. Can't find it in in SSD documentation
* 5a) 0x3C 0x03 write border with data. Only in 2.13 v2
* 6) 0x11 01 Data Mode
* 7) 0x44 00 0F
* 8) 0x45 27 01 00 00
* 9) 0x4E 00
* 10) 0x4F 27 01
* 11) 0x32 50 AA 55 AA 11 00 00 00 00 00 00 00 00 00 00 00
* 00 00 00 00 FF FF 1F 00 00 00 00 00 00 00
* LUT depends on display and step 5a
* 12) 0x22 C0
* 13) 0x20
* Busy Wait
* 14) 0x24 0xFF ... 4736 bytes with bitmap
* 15) 0x22 C4
* 16) 0x20
* Busy Wait
* 17) 0xFF
* 18) 0x22 C3
* 19) 0x20
* Busy Wait
*/
/****************************************************************************
* Device memory organization:
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <sys/types.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>
#include <nuttx/arch.h>
#include <nuttx/spi/spi.h>
#include <nuttx/lcd/lcd.h>
#include <nuttx/lcd/ssd1680.h>
#include <nuttx/signal.h>
#include <arch/irq.h>
#include "ssd1680.h"
#ifdef CONFIG_LCD_SSD1680
/* This structure describes the state of the SSD1680 driver */
/****************************************************************************
* Private Types
****************************************************************************/
struct ssd1680_dev_s
{
struct lcd_dev_s dev; /* Publicly visible device structure */
/* Private LCD-specific information follows */
FAR struct spi_dev_s *spi; /* Cached SPI device reference */
uint8_t contrast; /* Current contrast setting */
bool on; /* true: display is on */
bool is_conf; /* true: display had been configured */
FAR const struct ssd1680_priv_s *board_priv; /* Board specific structure */
/* The SSD1680 does not support reading from the display memory in SPI
* mode. Since there is 1 BPP and access is byte-by-byte.
* Also shared line (MISO/MOSI) could be problematic. Now implementation
* uses shadow buffer.
* Its size depends on resolution and is between 4kB and 32 kB.
*/
uint8_t shadow_fb[SSD1680_DEV_FBSIZE];
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
/* Libc extension */
static FAR void *bitscpy_ds(FAR void *dest, int dest_offset,
FAR const void *src, size_t nbits);
static FAR void *bitscpy_ss(FAR void *dest, FAR const void *src,
int src_offset, size_t nbits);
/* LCD Data Transfer Methods */
static int ssd1680_busy_wait(FAR struct ssd1680_dev_s *priv);
static void ssd1680_snd_cmd_with_data0(FAR struct ssd1680_dev_s *priv,
uint8_t cmd);
static void ssd1680_snd_cmd_with_data1(FAR struct ssd1680_dev_s *priv,
uint8_t cmd, uint8_t dta1);
static void ssd1680_snd_cmd_with_data2(FAR struct ssd1680_dev_s *priv,
uint8_t cmd, uint8_t dta1,
uint8_t dta2);
static void ssd1680_snd_cmd_with_data3(FAR struct ssd1680_dev_s *priv,
uint8_t cmd, uint8_t dta1,
uint8_t dta2, uint8_t dta3);
static void ssd1680_snd_cmd_with_data4(FAR struct ssd1680_dev_s *priv,
uint8_t cmd, uint8_t dta1,
uint8_t dta2, uint8_t dta3,
uint8_t dta4);
#ifndef CONFIG_LCD_SSD1681_1_54
static void ssd1680_snd_cmd_with_data(FAR struct ssd1680_dev_s *priv,
uint8_t cmd, const uint8_t *dta,
int dta_len);
#endif
#if !defined(CONFIG_LCD_PORTRAIT) && !defined(CONFIG_LCD_RPORTRAIT)
# if SSD1680_DEV_BPP == 1
static void ssd1680_snd_cmd_with_data_bitstrip(
FAR struct ssd1680_dev_s *priv, uint8_t cmd, const uint8_t *dta,
int dta_len, int strip_len);
# else
/* Special functions for sending to RAM1 and RAM2 should be implemented
* ssd1680_snd_cmd_with_data_bitstrip works fine with 1 bit per pixel
*/
# error "SSD1680 driver has no implementation for 3 color with landscape"
# endif
#endif
static void ssd1680_configspi(FAR struct spi_dev_s *spi);
static void ssd1680_select(FAR struct ssd1680_dev_s *priv, bool cs);
static void ssd1680_cmddata(FAR struct ssd1680_dev_s *priv, bool cmd);
static int ssd1680_putrun(FAR struct lcd_dev_s *dev, fb_coord_t row,
fb_coord_t col, FAR const uint8_t *buffer,
size_t npixels);
static int ssd1680_getrun(FAR struct lcd_dev_s *dev, fb_coord_t row,
fb_coord_t col, FAR uint8_t *buffer,
size_t npixels);
static int ssd1680_redraw(FAR struct lcd_dev_s *dev);
/* LCD Configuration */
static int ssd1680_getvideoinfo(FAR struct lcd_dev_s *dev,
FAR struct fb_videoinfo_s *vinfo);
static int ssd1680_getplaneinfo(FAR struct lcd_dev_s *dev,
unsigned int planeno,
FAR struct lcd_planeinfo_s *pinfo);
/* LCD RGB Mapping */
#ifdef CONFIG_FB_CMAP
# error "RGB color mapping not supported by this driver"
#endif
/* Cursor Controls */
#ifdef CONFIG_FB_HWCURSOR
# error "Cursor control not supported by this driver"
#endif
/* LCD Specific Controls */
static int ssd1680_getpower(struct lcd_dev_s *dev);
static int ssd1680_setpower(struct lcd_dev_s *dev, int power);
static int ssd1680_configuredisplay(struct ssd1680_dev_s *priv);
static int ssd1680_redraw_display(struct ssd1680_dev_s *priv);
static int ssd1680_update_row(struct ssd1680_dev_s *priv, int row);
static int ssd1680_update_all_and_redraw(struct ssd1680_dev_s *priv);
/****************************************************************************
* Private Data
****************************************************************************/
/* This is working memory allocated by the LCD driver for each LCD device
* and for each color plane. This memory will hold one raster line of data.
* The size of the allocated run buffer must therefore be at least
* (bpp * xres / 8). Actual alignment of the buffer must conform to the
* bitwidth of the underlying pixel type.
*
* If there are multiple planes, they may share the same working buffer
* because different planes will not be operate on concurrently. However,
* if there are multiple LCD devices, they must each have unique run buffers.
*/
static uint8_t g_runbuffer[SSD1680_DEV_ROWSIZE];
/* This structure describes the overall LCD video controller */
static const struct fb_videoinfo_s g_videoinfo =
{
.fmt = SSD1680_DEV_COLORFMT, /* Color format: B&W */
.xres = SSD1680_DEV_FB_XRES, /* Horizontal resolution in pixel col */
.yres = SSD1680_DEV_FB_YRES, /* Vertical resolution in pixel rows */
.nplanes = SSD1680_NO_OF_PLANES, /* Number of color planes supported */
};
/* This is the standard, NuttX Plane information object */
static const struct lcd_planeinfo_s g_planeinfo =
{
.putrun = ssd1680_putrun, /* Put a run into LCD memory */
.putarea = NULL, /* Not need to implement */
.getrun = ssd1680_getrun, /* Read content of shadow memory */
.getarea = NULL, /* Not need to implement */
.redraw = ssd1680_redraw, /* Update drivers memory and redraw */
.buffer = (FAR uint8_t *)g_runbuffer, /* Run scratch buffer */
.bpp = SSD1680_DEV_BPP, /* Bits-per-pixel */
};
/* This is the outside visible interface for the OLED driver */
static const struct lcd_dev_s g_lcd_epaper_dev =
{
/* LCD Configuration */
.getvideoinfo = ssd1680_getvideoinfo,
.getplaneinfo = ssd1680_getplaneinfo,
/* LCD RGB Mapping -- Not supported */
/* Cursor Controls -- Not supported */
/* LCD Specific Controls */
.getpower = ssd1680_getpower,
.setpower = ssd1680_setpower,
/* setcontrast could be implemented in future by changing
* dispalys voltage and LUT table
*/
};
/* This is the OLED driver instance. Only a single device is supported
* for now.
*/
static struct ssd1680_dev_s g_epaperdev;
#if !defined(CONFIG_LCD_SSD1681_1_54)
static const uint8_t ssd1680_lut[] =
{
#if defined(CONFIG_LCD_SSD1680_2_90)
0x50, 0xaa, 0x55, 0xaa, 0x11, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xff, 0xff, 0x1f, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00
#elif defined(CONFIG_LCD_SSD1680_2_13_V2)
0x80, 0x60, 0x40, 0x00, 0x00, 0x00, 0x00, /* LUT0: BB: VS 0 ~7 */
0x10, 0x60, 0x20, 0x00, 0x00, 0x00, 0x00, /* LUT1: BW: VS 0 ~7 */
0x80, 0x60, 0x40, 0x00, 0x00, 0x00, 0x00, /* LUT2: WB: VS 0 ~7 */
0x10, 0x60, 0x20, 0x00, 0x00, 0x00, 0x00, /* LUT3: WW: VS 0 ~7 */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* LUT4: VCOM: VS 0 ~7 */
0x03, 0x03, 0x00, 0x00, 0x02, /* TP0 A~D RP0 */
0x09, 0x09, 0x00, 0x00, 0x02, /* TP1 A~D RP1 */
0x03, 0x03, 0x00, 0x00, 0x02, /* TP2 A~D RP2 */
0x00, 0x00, 0x00, 0x00, 0x00, /* TP3 A~D RP3 */
0x00, 0x00, 0x00, 0x00, 0x00, /* TP4 A~D RP4 */
0x00, 0x00, 0x00, 0x00, 0x00, /* TP5 A~D RP5 */
0x00, 0x00, 0x00, 0x00, 0x00 /* TP6 A~D RP6 */
#else
# error "Missing LUT table"
#endif
};
#endif
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: ssd1680_putrun
*
* Description:
* This method can be used to write a partial raster line to the LCD.
*
* Input Parameters:
* dev - The lcd device
* row - Starting row to write to (range: 0 <= row < yres)
* col - Starting column to write to (range: 0 <= col <= xres-npixels)
* buffer - The buffer containing the run to be written to the LCD
* npixels - The number of pixels to write to the LCD.
*
****************************************************************************/
static int ssd1680_putrun(FAR struct lcd_dev_s *dev, fb_coord_t row,
fb_coord_t col, FAR const uint8_t *buffer,
size_t npixels)
{
FAR struct ssd1680_dev_s *priv = (FAR struct ssd1680_dev_s *)dev;
uint8_t *dst = priv->shadow_fb +
row * SSD1680_DEV_ROWSIZE + (col >> SSD1680_PDF);
int dst_start_bitshift = col % (SSD1680_PDV);
/* Write data to shadow memory */
bitscpy_ds(dst, dst_start_bitshift, buffer, npixels);
return OK;
}
/****************************************************************************
* Name: ssd1680_getrun
*
* Description:
* This method can be used to read a partial raster line from the LCD:
*
* Input Parameters:
*
* dev - The lcd device
* row - Starting row to read from (range: 0 <= row < yres)
* col - Starting column to read read
* (range: 0 <= col <= xres-npixels)
* buffer - The buffer in which to return the run read from the LCD
* npixels - The number of pixels to read from the LCD
* (range: 0 < npixels <= xres-col)
*/
static int ssd1680_getrun(FAR struct lcd_dev_s *dev, fb_coord_t row,
fb_coord_t col, FAR uint8_t *buffer,
size_t npixels)
{
FAR struct ssd1680_dev_s *priv = (FAR struct ssd1680_dev_s *)dev;
bitscpy_ss(buffer,
priv->shadow_fb + row * SSD1680_DEV_FBSIZE + (col >> SSD1680_PDF),
col % SSD1680_PDV,
npixels);
return OK;
}
static int ssd1680_redraw(FAR struct lcd_dev_s *dev)
{
FAR struct ssd1680_dev_s *priv = (FAR struct ssd1680_dev_s *)dev;
return ssd1680_update_all_and_redraw(priv);
}
/****************************************************************************
* Name: ssd1680_getvideoinfo
*
* Description:
* Get information about the LCD video controller configuration.
*
****************************************************************************/
static int ssd1680_getvideoinfo(FAR struct lcd_dev_s *dev,
FAR struct fb_videoinfo_s *vinfo)
{
DEBUGASSERT(dev && vinfo);
lcdinfo("fmt: %d xres: %d yres: %d nplanes: %d\n",
g_videoinfo.fmt, g_videoinfo.xres, g_videoinfo.yres,
g_videoinfo.nplanes);
memcpy(vinfo, &g_videoinfo, sizeof(struct fb_videoinfo_s));
return OK;
}
/****************************************************************************
* Name: ssd1680_getplaneinfo
*
* Description:
* Get information about the configuration of each LCD color plane.
*
****************************************************************************/
static int ssd1680_getplaneinfo(FAR struct lcd_dev_s *dev,
unsigned int planeno,
FAR struct lcd_planeinfo_s *pinfo)
{
DEBUGASSERT(pinfo && planeno == 0);
lcdinfo("planeno: %d bpp: %d\n", planeno, g_planeinfo.bpp);
memcpy(pinfo, &g_planeinfo, sizeof(struct lcd_planeinfo_s));
pinfo->dev = dev;
return OK;
}
/****************************************************************************
* Name: ssd1680_getpower
*
* Description:
* Get the LCD panel power status:
* 0: full off
* CONFIG_LCD_MAXPOWER: full on
* On backlit LCDs, this setting may correspond to the backlight setting.
*
****************************************************************************/
static int ssd1680_getpower(FAR struct lcd_dev_s *dev)
{
struct ssd1680_dev_s *priv = (struct ssd1680_dev_s *) dev;
DEBUGASSERT(priv);
lcdinfo("power: %s\n", priv->on ? "ON" : "OFF");
return priv->on ? CONFIG_LCD_MAXPOWER : 0;
}
static void ssd1680_reset(struct ssd1680_dev_s *priv)
{
if (priv->board_priv && priv->board_priv->set_rst)
{
lcdinfo("Hardware reset\n");
priv->board_priv->set_rst(false);
nxsig_usleep(10);
priv->board_priv->set_rst(true);
}
else
{
lcdinfo("Hardware reset is not available. Operation skipped.\n");
}
}
/****************************************************************************
* Name: ssd1680_setpower
*
* Description:
* Enable/disable LCD panel power:
* 0: full off
* CONFIG_LCD_MAXPOWER: full on
* On backlit LCDs, this setting may correspond to the backlight setting.
*
****************************************************************************/
static int ssd1680_setpower(FAR struct lcd_dev_s *dev, int power)
{
int ret = OK;
struct ssd1680_dev_s *priv = (struct ssd1680_dev_s *) dev;
DEBUGASSERT(dev);
lcdinfo("power: %d -> %d\n", priv->on ? CONFIG_LCD_MAXPOWER : 0, power);
DEBUGASSERT((unsigned)power <= CONFIG_LCD_MAXPOWER);
if (power <= 0)
{
priv->on = false;
/* Try turn off power completely */
if (priv->board_priv && priv->board_priv->set_vcc)
{
/* Do power off. */
if (priv->board_priv->set_vcc(false))
{
/* Display is completely powered off, not configured anymore. */
priv->is_conf = false;
}
}
}
else
{
if (priv->board_priv && priv->board_priv->set_vcc)
{
/* Do power on. */
lcdinfo("Set Pwr Ctrl Linepower ON\n");
priv->board_priv->set_vcc(true);
nxsig_usleep(10000);
}
else
{
lcdinfo("No line for controlling PWR, operation skipped\n");
}
if (!priv->is_conf)
{
ssd1680_reset(priv);
ret = ssd1680_configuredisplay(priv);
if (ret != OK)
{
lcderr("Failed to configure display\n");
return ret;
}
/* Draw the framebuffer */
ret = ssd1680_update_all_and_redraw(priv);
if (ret != OK)
{
lcderr("Failed to update and redraw\n");
return ret;
}
}
priv->on = true;
}
return ret;
}
/****************************************************************************
* Name: ssd1680_configuredisplay
*
* Description:
* Setup LCD display.
*
****************************************************************************/
static int ssd1680_configuredisplay(struct ssd1680_dev_s *priv)
{
int ret;
/* Software Reset */
lcdinfo("Software reset: (0x%02x)\n", SSD1680_SW_RESET);
ssd1680_snd_cmd_with_data0(priv, SSD1680_SW_RESET);
/* Busy wait */
ret = ssd1680_busy_wait(priv);
if (ret != OK)
{
lcderr("Configuration is not ready\n");
return ret;
}
/* Step 1: Driver Output Control 3 bytes of data:
* - A[8:0] MUX Gate lines
* - B[2:0] sequence
* last data byte depends on connection between display and controller
*/
lcdinfo("Set the driver output control (0x%02x): %d 0x%02x, 0x%02x\n",
SSD1680_DRIVER_CONTROL,
SSD1680_DEV_NATIVE_YRES - 1,
(SSD1680_DEV_NATIVE_YRES - 1) >> 8,
SSD1680_DEV_GATE_LAYOUT);
ssd1680_snd_cmd_with_data3(priv, SSD1680_DRIVER_CONTROL,
(uint8_t)((SSD1680_DEV_NATIVE_YRES - 1) & 0xff),
(SSD1680_DEV_NATIVE_YRES - 1) >> 8,
SSD1680_DEV_GATE_LAYOUT);
#if defined(CONFIG_LCD_SSD1680_2_13_V2)
/* Step 1a: Set analog block control
* After reset this register should have proper value
*/
lcdinfo("Set analog block control (0x74): 0x54\n");
ssd1680_snd_cmd_with_data1(priv, SSD1680_SET_ANALOG_BLOCK_CTRL, 0x54);
/* Step 1b: Set digital block control
* After reset this register should have proper value
*/
lcdinfo("Set digital block control (0x7e): 0x3b\n");
ssd1680_snd_cmd_with_data1(priv, SSD1680_SET_DIGITAL_BLOCK_CTRL, 0x3b);
#endif
#ifndef CONFIG_LCD_SSD1681_1_54
/* Step 2: SSD1680_BOOST_SOFTSTART 0x0C D7 D6 9D */
lcdinfo("Set boost soft start (0x%02x): 0x%02x, 0x%02x, 0x%02x\n",
SSD1680_BOOST_SOFTSTART, 0xd7, 0xd6, 0x9d);
ssd1680_snd_cmd_with_data3(priv, SSD1680_BOOST_SOFTSTART,
0xd7, 0xd6, 0x9d);
/* Step 3: Vcom Voltage SSD1680_WRITE_VCOM */
lcdinfo("Set Vcom voltage (0x%02x): 0x%02x\n", SSD1680_WRITE_VCOM,
SSD1680_VALUE_VCOM);
ssd1680_snd_cmd_with_data1(priv, SSD1680_WRITE_VCOM, SSD1680_VALUE_VCOM);
lcdinfo("Set Gate voltage (0x%02x): 0x%02x\n", SSD1680_GATE_VOLTAGE,
SSD1680_VALUE_G_VOLTAGE);
ssd1680_snd_cmd_with_data1(priv, SSD1680_GATE_VOLTAGE,
SSD1680_VALUE_G_VOLTAGE);
lcdinfo("Set Source voltage (0x%02x): 0x%02x, 0x%02x, 0x%02x\n",
SSD1680_SOURCE_VOLTAGE, SSD1680_VALUE_S_VOLTAGE_1,
SSD1680_VALUE_S_VOLTAGE_2, SSD1680_VALUE_S_VOLTAGE_3);
ssd1680_snd_cmd_with_data3(priv, SSD1680_SOURCE_VOLTAGE,
SSD1680_VALUE_S_VOLTAGE_1, SSD1680_VALUE_S_VOLTAGE_2,
SSD1680_VALUE_S_VOLTAGE_3);
/* Step 4: Sending undocumented command: 0x3a with data 1A */
lcdinfo("Set dummy line (0x%02x): 0x%02x\n", SSD1680_DUMMY_LINE,
SSD1680_VALUE_DUMMY_LINE);
ssd1680_snd_cmd_with_data1(priv, SSD1680_DUMMY_LINE,
SSD1680_VALUE_DUMMY_LINE);
/* Step 5: Sending undocumented command: 0x3b with data 08 */
lcdinfo("Set gate time (0x%02x): 0x%02x\n", SSD1680_GATE_TIME,
SSD1680_VALUE_GATE_TIME);
ssd1680_snd_cmd_with_data1(priv, SSD1680_GATE_TIME,
SSD1680_VALUE_GATE_TIME);
#endif
#if defined(CONFIG_LCD_SSD1680_2_13_V2)
/* Step 5a: Sending command write border with data 0x03:
* - Follow LUT (Output VCOM @ RED)
* - Transition setting for VBD: LUT3
*/
lcdinfo("Set Border Waveform (0x%02x): 0x%02x\n",
SSD1680_WRITE_BORDER, 0x03);
ssd1680_snd_cmd_with_data1(priv, SSD1680_WRITE_BORDER, 0x03);
#elif defined(CONFIG_LCD_SSD1681_1_54)
/* Step 5a: Sending command write border with data 0x01:
* - Follow LUT (Output VCOM @ RED)
* - Transition setting for VBD: LUT0
*/
lcdinfo("Set Border Waveform (0x%02x): 0x%02x\n",
SSD1680_WRITE_BORDER, 0x01);
ssd1680_snd_cmd_with_data1(priv, SSD1680_WRITE_BORDER, 0x05);
#endif
/* Step 6: Data entry mode SSD1680_DATA_MODE */
lcdinfo("Set data entry mode (0x%02x): 0x%02x\n",
SSD1680_DATA_MODE, SSD1680_VAL_DATA_MODE);
ssd1680_snd_cmd_with_data1(priv,
SSD1680_DATA_MODE, SSD1680_VAL_DATA_MODE);
/* Step 7: Set ram X start/end position 00 FF */
lcdinfo("Set ram X start/end position (0x%02x): 0, %d\n",
SSD1680_SET_RAMXPOS, (SSD1680_DEV_X_ROUND_UP >> 3)-1);
ssd1680_snd_cmd_with_data2(priv, SSD1680_SET_RAMXPOS,
0x00, (SSD1680_DEV_X_ROUND_UP >> 3)-1);
/* Step 8: Set ram Y start/end position */
lcdinfo("Set ram Y start/end position (0x%02x): 0, %d\n",
SSD1680_SET_RAMYPOS, SSD1680_DEV_NATIVE_YRES - 1);
ssd1680_snd_cmd_with_data4(priv, SSD1680_SET_RAMYPOS,
0x00, 0x00,
(uint8_t)((SSD1680_DEV_NATIVE_YRES - 1) & 0xff),
(SSD1680_DEV_NATIVE_YRES - 1) >> 8);
/* Step 9: SSD1680_SET_RAMXCOUNT, 0 */
lcdinfo("Set ram X count (0x%02x): 0\n", SSD1680_SET_RAMXCOUNT);
ssd1680_snd_cmd_with_data1(priv, SSD1680_SET_RAMXCOUNT, 0x00);
/* Step 10: SSD1680_SET_RAMYCOUNT, 0, 0 */
lcdinfo("Set ram Y count (0x%02x): 0\n", SSD1680_SET_RAMYCOUNT);
ssd1680_snd_cmd_with_data2(priv, SSD1680_SET_RAMYCOUNT, 0x00, 0x00);
/* Step 11: Lookup table */
#ifndef CONFIG_LCD_SSD1681_1_54
lcdinfo("Write lookup table (0x%02x): (%d bytes)\n", SSD1680_WRITE_LUT,
sizeof (ssd1680_lut));
ssd1680_snd_cmd_with_data(priv, SSD1680_WRITE_LUT, ssd1680_lut,
sizeof (ssd1680_lut));
#endif
/* Step 12: Set temperature control */
#ifdef CONFIG_LCD_SSD1681_1_54
ssd1680_snd_cmd_with_data1(priv, SSD1680_TEMP_CONTROL, 0x80);
#endif
/* Step 13: Write sequence */
lcdinfo("Write control sequence (0x%02x): 0x%02x\n", SSD1680_DISP_CTRL2,
SSD1680_VALUE_UPDATE_CTRL);
ssd1680_snd_cmd_with_data1(priv, SSD1680_DISP_CTRL2,
SSD1680_VALUE_UPDATE_CTRL);
/* Step 14: Master Activate and busy wait */
lcdinfo("Write master activate (0x%02x) command\n",
SSD1680_MASTER_ACTIVATE);
ssd1680_snd_cmd_with_data0(priv, SSD1680_MASTER_ACTIVATE);
ret = ssd1680_busy_wait(priv);
if (ret == OK)
{
lcdinfo("Configuration ready\n");
priv->is_conf = true;
}
else
{
lcderr("Configuration is not ready\n");
}
return ret;
}
/****************************************************************************
* Name: ssd1680_update_all_and_redraw
*
* Description:
* Copy content of shadow buffer to drivers memory.
* Redraws the display
*
* Input Parameters:
* priv - Reference to private driver structure
*
* Assumptions:
* E-ink is slow, so we don't need to optimize code in order to send all
* lines at once using DMA. Now function sends each line in separate
* function calls. E-ink driver lets to send all frame buffer using one
* transmission (only in portrait and reverse portrait mode)
*
****************************************************************************/
static int ssd1680_update_all_and_redraw(struct ssd1680_dev_s *priv)
{
int row;
#if defined(CONFIG_LCD_PORTRAIT) || defined(CONFIG_LCD_RPORTRAIT)
const int row_incr = 1;
#else
# if SSD1680_DEV_BPP == 1
const int row_incr = 8;
# else
const int row_incr = 4;
# endif
#endif
for (row = 0; row < SSD1680_DEV_FB_YRES; row += row_incr)
{
ssd1680_update_row(priv, row);
}
ssd1680_redraw_display(priv);
return OK;
}
static int ssd1680_redraw_display(struct ssd1680_dev_s *priv)
{
/* Synchronize shadow buffer with drivers memory */
int ret;
/* Step 15:
* Set control register 2.
* 1 byte of data with following bits:
* 0x80 - enable clock signal
* 0x40 - enable analog
* 0x20 - load temperature value
* 0x10 - Load LUT
* 0x08 - Display Mode 2
* 0x04 - disable OSC
* 0x02 - disable analog
* 0x01 - disable clock signal
*/
ssd1680_snd_cmd_with_data1(priv, SSD1680_DISP_CTRL2, 0xc4);
/* Step 16: */
ssd1680_snd_cmd_with_data0(priv, SSD1680_MASTER_ACTIVATE);
ret = ssd1680_busy_wait(priv);
/* Step 18: */
ssd1680_snd_cmd_with_data1(priv, SSD1680_DISP_CTRL2, 0xc3);
/* Step 19: */
ssd1680_snd_cmd_with_data0(priv, SSD1680_MASTER_ACTIVATE);
ssd1680_busy_wait(priv);
if (ret != OK)
{
lcderr("FAILED!!! Busy state timeout\n");
}
else
{
lcdinfo("Done\n");
}
return OK;
}
static int ssd1680_update_row(struct ssd1680_dev_s *priv, int row)
{
#if defined(CONFIG_LCD_PORTRAIT) || defined(CONFIG_LCD_RPORTRAIT)
uint8_t *src = priv->shadow_fb + row * SSD1680_DEV_ROWSIZE;
ssd1680_snd_cmd_with_data1(priv, SSD1680_SET_RAMXCOUNT, 0);
ssd1680_snd_cmd_with_data2(priv, SSD1680_SET_RAMYCOUNT, row, row >> 8);
#if SSD1680_DEV_BPP == 1
ssd1680_snd_cmd_with_data(priv, SSD1680_WRITE_RAM1, src,
SSD1680_DEV_ROWSIZE);
#else
ssd1680_snd_cmd_with_data_even_bits(priv, SSD1680_WRITE_RAM1, src,
SSD1680_DEV_ROWSIZE);
ssd1680_snd_cmd_with_data1(priv, SSD1680_SET_RAMXCOUNT, 1);
ssd1680_snd_cmd_with_data2(priv, SSD1680_SET_RAMYCOUNT,
row, row >> 8);
ssd1680_snd_cmd_with_data_odd_bits(priv,
SSD1680_WRITE_RAM2, src, SSD1680_DEV_FBSIZE);
#endif
#else
int row_group = (row >> 3) << 3;
uint8_t *src = priv->shadow_fb + row_group * SSD1680_DEV_ROWSIZE;
ssd1680_snd_cmd_with_data1(priv, SSD1680_SET_RAMXCOUNT, row >> 3);
ssd1680_snd_cmd_with_data2(priv, SSD1680_SET_RAMYCOUNT, 0, 0);
# if SSD1680_DEV_BPP == 1
ssd1680_snd_cmd_with_data_bitstrip(priv, SSD1680_WRITE_RAM1, src,
SSD1680_DEV_NATIVE_YRES, SSD1680_DEV_ROWSIZE);
# else
# error "Landscape mode with 3 colors is not implemented"
/* TODO send ssd1680_snd_cmd_with_data_even_bits_bitstrip
* (priv, SSD1680_WRITE_RAM1, src, SSD1680_DEV_NATIVE_YRES,
* SSD1680_DEV_ROWSIZE);
*/
ssd1680_snd_cmd_with_data1(priv, SSD1680_SET_RAMXCOUNT, row >> 3);
ssd1680_snd_cmd_with_data2(priv, SSD1680_SET_RAMYCOUNT, 0, 0);
/* TODO send ssd1680_snd_cmd_with_data_odd_bits_bitstrip(priv,
* SSD1680_WRITE_RAM2, src, SSD1680_DEV_NATIVE_YRESSSD1680_DEV_ROWSIZE, );
*/
# endif
#endif
return OK;
}
/****************************************************************************
* Name: ssd1680_configspi
*
* Description:
*
****************************************************************************/
static void ssd1680_configspi(FAR struct spi_dev_s *spi)
{
/* Configure SPI for the SSD1680 */
SPI_SETMODE(spi, CONFIG_SSD1680_SPIMODE);
SPI_SETBITS(spi, 8);
SPI_HWFEATURES(spi, 0);
SPI_SETFREQUENCY(spi, CONFIG_SSD1680_FREQUENCY);
}
/****************************************************************************
* Name: ssd1680_select
*
* Description:
* Enable/Disable SSD1680 SPI CS
*
****************************************************************************/
static void ssd1680_select(FAR struct ssd1680_dev_s *priv, bool cs)
{
/* If we are selecting the device */
if (cs == true)
{
/* If SPI bus is shared then lock and configure it */
SPI_LOCK(priv->spi, true);
ssd1680_configspi(priv->spi);
}
/* Select/deselect SPI device */
SPI_SELECT(priv->spi, SPIDEV_DISPLAY(0), cs);
/* If we are deselecting the device */
if (cs == false)
{
/* Unlock bus */
SPI_LOCK(priv->spi, false);
}
}
static int ssd1680_busy_wait(FAR struct ssd1680_dev_s *priv)
{
int max_wait_time = 200;
if ((priv->board_priv != NULL) && (priv->board_priv->check_busy != NULL))
{
while (priv->board_priv->check_busy() && max_wait_time-- > 0)
{
nxsig_usleep(1000);
}
}
else
{
nxsig_usleep(max_wait_time * 1000);
}
if (max_wait_time == 0)
{
lcderr("Timeout. Ignoring Busy state... "
"Display is probably not ready\n");
return ERROR;
}
return OK;
}
static void ssd1680_snd_cmd_with_data0(FAR struct ssd1680_dev_s *priv,
uint8_t cmd)
{
ssd1680_select(priv, true);
ssd1680_cmddata(priv, true);
SPI_SEND(priv->spi, cmd);
ssd1680_select(priv, false);
}
static void ssd1680_snd_cmd_with_data1(FAR struct ssd1680_dev_s *priv,
uint8_t cmd, uint8_t dta1)
{
ssd1680_select(priv, true);
ssd1680_cmddata(priv, true);
SPI_SEND(priv->spi, cmd);
ssd1680_cmddata(priv, false);
SPI_SEND(priv->spi, dta1);
ssd1680_select(priv, false);
}
static void ssd1680_snd_cmd_with_data2(FAR struct ssd1680_dev_s *priv,
uint8_t cmd, uint8_t dta1, uint8_t dta2)
{
ssd1680_select(priv, true);
ssd1680_cmddata(priv, true);
SPI_SEND(priv->spi, cmd);
ssd1680_cmddata(priv, false);
SPI_SEND(priv->spi, dta1);
SPI_SEND(priv->spi, dta2);
ssd1680_select(priv, false);
}
static void ssd1680_snd_cmd_with_data3(FAR struct ssd1680_dev_s *priv,
uint8_t cmd, uint8_t dta1, uint8_t dta2, uint8_t dta3)
{
ssd1680_select(priv, true);
ssd1680_cmddata(priv, true);
SPI_SEND(priv->spi, cmd);
ssd1680_cmddata(priv, false);
SPI_SEND(priv->spi, dta1);
SPI_SEND(priv->spi, dta2);
SPI_SEND(priv->spi, dta3);
ssd1680_select(priv, false);
}
static void ssd1680_snd_cmd_with_data4(FAR struct ssd1680_dev_s *priv,
uint8_t cmd, uint8_t dta1, uint8_t dta2, uint8_t dta3, uint8_t dta4)
{
ssd1680_select(priv, true);
ssd1680_cmddata(priv, true);
SPI_SEND(priv->spi, cmd);
ssd1680_cmddata(priv, false);
SPI_SEND(priv->spi, dta1);
SPI_SEND(priv->spi, dta2);
SPI_SEND(priv->spi, dta3);
SPI_SEND(priv->spi, dta4);
ssd1680_select(priv, false);
}
#ifndef CONFIG_LCD_SSD1681_1_54
static void ssd1680_snd_cmd_with_data(FAR struct ssd1680_dev_s *priv,
uint8_t cmd, const uint8_t *dta, int dta_len)
{
ssd1680_select(priv, true);
ssd1680_cmddata(priv, true);
SPI_SEND(priv->spi, cmd);
ssd1680_cmddata(priv, false);
SPI_SNDBLOCK(priv->spi, dta, dta_len);
ssd1680_select(priv, false);
}
#endif
#if !defined(CONFIG_LCD_PORTRAIT) && !defined(CONFIG_LCD_RPORTRAIT)
static void ssd1680_snd_cmd_with_data_bitstrip(
FAR struct ssd1680_dev_s *priv, uint8_t cmd, const uint8_t *dta,
int nopix, int strip_len)
# if SSD1680_DEV_BPP == 1
{
int i;
int j;
uint8_t bytes[8];
uint8_t val;
# if defined(CONFIG_LCD_LANDSCAPE)
const int offset = (SSD1680_DEV_ROWSIZE * SSD1680_PDV -
SSD1680_DEV_NATIVE_YRES) % SSD1680_PDV;
dta += (strip_len - 1);
# endif
ssd1680_select(priv, true);
ssd1680_cmddata(priv, true);
SPI_SEND(priv->spi, cmd);
ssd1680_cmddata(priv, false);
while (nopix > 0)
{
for (j = 0; j < 8; j++)
{
# if defined(CONFIG_LCD_LANDSCAPE)
if (offset == 0)
{
bytes[j] = *(dta + j * strip_len);
}
else
{
bytes[j] = (*(dta + j * strip_len)) >> offset;
bytes[j] |= ((*(dta + j * strip_len - 1)) << (8 - offset)
& (0xff << (8 - offset)));
}
# else
bytes[j] = *(dta + j * strip_len);
# endif
}
for (i = 0; i < 8; i++)
{
val = 0;
for (j = 0; j < 8; j++)
{
# if defined(CONFIG_LCD_LANDSCAPE)
val |= ((bytes[j] << (7 - j)) & (1 << (7 - j)));
bytes[j] = bytes[j] >> 1;
# else
val |= ((bytes[j] >> j) & (1 << (7 - j)));
bytes[j] = bytes[j] << 1;
# endif
}
SPI_SEND(priv->spi, val);
nopix--;
if (nopix == 0)
{
break;
}
}
# if defined(CONFIG_LCD_LANDSCAPE)
dta--;
# else
dta++;
# endif
}
ssd1680_select(priv, false);
}
# else
{
int i;
int j;
uint8_t rows[8];
uint16_t tmp[8];
uint8_t val;
# if defined(CONFIG_LCD_LANDSCAPE)
dta += (strip_len - 1);
# endif
ssd1680_select(priv, true);
ssd1680_cmddata(priv, true);
SPI_SEND(priv->spi, cmd);
ssd1680_cmddata(priv, false);
while (nopix > 0)
{
for (j = 0; j < 8; j++)
{
# if defined(CONFIG_LCD_LANDSCAPE)
tmp = *(dta + j * strip_len)
+ ((*(dta + j * strip_len + 1)) << 8);
# else
tmp = *(dta + j * strip_len + 1)
+ ((*(dta + j * strip_len)) << 8);
# endif
rows[j] =
(tmp & 0x01) | ((tmp >> 1) & 0x02)
| ((tmp >> 2) & 0x04) | ((tmp >> 3) & 0x08)
| ((tmp >> 4) & 0x10) | ((tmp >> 5) & 0x20)
| ((tmp >> 6) & 0x40) | ((tmp >> 7) & 0x80);
}
for (i = 0; i < 8; i++)
{
val = 0;
for (j = 0; j < 8; j++)
{
# if defined(CONFIG_LCD_LANDSCAPE)
val |= ((rows[j] << (7 - j)) & (1 << (7 - j)));
rows[j] = rows[j] >> 1;
# else
val |= ((rows[j] >> (j)) & (1 << (7 - j)));
rows[j] = rows[j] << 1;
# endif
}
SPI_SEND(priv->spi, val);
nopix--;
if (nopix == 0)
{
break;
}
}
# if defined(CONFIG_LCD_LANDSCAPE)
dta--;
# else
dta++;
# endif
}
ssd1680_select(priv, false);
}
static void ssd1680_snd_cmd_with_data_odd_bits_bitstrip(
FAR struct ssd1680_dev_s *priv, uint8_t cmd, const uint8_t *dta,
int nopix, int strip_len)
{
int i;
int j;
uint16_t rows[8];
uint8_t val;
#if defined(CONFIG_LCD_LANDSCAPE)
dta += (strip_len - 1);
#endif
ssd1680_select(priv, true);
ssd1680_cmddata(priv, true);
SPI_SEND(priv->spi, cmd);
ssd1680_cmddata(priv, false);
while (nopix > 0)
{
for (j = 0; j < 8; j++)
{
#if defined(CONFIG_LCD_LANDSCAPE)
rows[j] = *(dta + j * strip_len)
+ ((*(dta + j * strip_len + 1)) << 8);
#else
rows[j] = *(dta + j * strip_len + 1)
+ ((*(dta + j * strip_len)) << 8);
#endif
}
for (i = 0; i < 8; i++)
{
val = 0;
for (j = 0; j < 8; j++)
{
#if defined(CONFIG_LCD_LANDSCAPE)
val |= ((rows[j] << (7 - j)) & (1 << (7 - j)));
rows[j] = rows[j] >> 2;
#else
val |= ((rows[j] >> (j)) & (1 << (7 - j)));
rows[j] = rows[j] << 2;
#endif
}
SPI_SEND(priv->spi, val);
nopix--;
if (nopix == 0)
{
break;
}
}
#if defined(CONFIG_LCD_LANDSCAPE)
dta--;
#else
dta++;
#endif
}
ssd1680_select(priv, false);
}
# endif
#endif
#if SSD1680_DEV_BPP == 2
static void ssd1680_snd_cmd_with_data_even_bits(
FAR struct ssd1680_dev_s *priv, uint8_t cmd, const uint8_t *dta,
int dta_len)
{
int i;
uint8_t dta_byte;
uint8_t src1;
uint8_t src2;
ssd1680_select(priv, true);
ssd1680_cmddata(priv, true);
SPI_SEND(priv->spi, cmd);
ssd1680_cmddata(priv, false);
for (i = 0; i < dta_len; i++)
{
src1 = *(dta++);
src2 = *(dta++);
dta_byte = (src1 & 0x01) | ((src1 >> 1) & 0x02)
| ((src1 >> 2) & 0x04) | ((src1 >> 3) & 0x08)
| ((src2 << 4) & 0x10) | ((src2 << 3) & 0x20)
| ((src2 << 2) & 0x40) | ((src2 << 1) & 0x80);
SPI_SEND(priv->spi, dta_byte);
}
ssd1680_select(priv, false);
}
static void ssd1680_snd_cmd_with_data_odd_bits(
FAR struct ssd1680_dev_s *priv, uint8_t cmd, const uint8_t *dta,
int dta_len)
{
int i;
uint8_t dta_byte;
uint8_t src1;
uint8_t src2;
ssd1680_select(priv, true);
ssd1680_cmddata(priv, true);
SPI_SEND(priv->spi, cmd);
ssd1680_cmddata(priv, false);
for (i = 0; i < dta_len; i++)
{
src1 = *(dta++);
src2 = *(dta++);
dta_byte = ((src1 >> 1 & 0x01)) | ((src1 >> 2) & 0x02)
| ((src1 >> 3) & 0x04) | ((src1 >> 4) & 0x08)
| ((src2 << 3) & 0x10) | ((src2 << 2) & 0x20)
| ((src2 << 1) & 0x40) | (src2 & 0x80);
SPI_SEND(priv->spi, dta_byte);
}
ssd1680_select(priv, false);
}
#endif
/****************************************************************************
* Name: ssd1680_cmddata
*
* Description:
* Select Command/Data mode for SSD1680
*
****************************************************************************/
inline static void ssd1680_cmddata(FAR struct ssd1680_dev_s *priv, bool cmd)
{
SPI_CMDDATA(priv->spi, SPIDEV_DISPLAY(0), cmd);
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: ssd1680_initialize
*
* Description:
* Initialize the video hardware. The initial state of the OLED is
* fully initialized, display memory cleared, and the OLED ready
* to use, but with the power setting at 0 (full off == sleep mode).
*
* Input Parameters:
*
* dev - A reference to the SPI driver instance.
* board_priv - Board specific structure.
*
* Returned Value:
*
* On success, this function returns a reference to the LCD object for
* the specified OLED. NULL is returned on any failure.
*
****************************************************************************/
FAR struct lcd_dev_s *ssd1680_initialize(FAR struct spi_dev_s *dev,
FAR const struct ssd1680_priv_s *board_priv)
{
int ret;
FAR struct ssd1680_dev_s *priv = &g_epaperdev;
DEBUGASSERT(dev);
priv->dev = g_lcd_epaper_dev;
priv->on = false;
priv->is_conf = false;
/* Register board specific functions */
priv->board_priv = board_priv;
priv->spi = dev;
/* Configure the SPI */
ssd1680_configspi(priv->spi);
/* Initialize the framebuffer */
memset(priv->shadow_fb, SSD1680_Y1_BLACK & 1 ?
0xff : 0x00, SSD1680_DEV_FBSIZE);
/* Power on and configure display */
ret = ssd1680_setpower(&priv->dev, true);
if (ret != OK)
{
return NULL;
}
return &priv->dev;
}
static FAR void *bitscpy_ds(FAR void *dest, int dest_offset,
FAR const void *src, size_t nbits)
{
FAR unsigned char *pout = (FAR unsigned char *)dest;
FAR unsigned char *pin = (FAR unsigned char *)src;
uint8_t val;
uint8_t mask;
/* Copy block of bytes */
while (nbits >= 8)
{
/* Read. MSB is first */
val = *pin++;
/* Write */
if (dest_offset == 0)
{
*pout++ = val;
}
else
{
mask = 0xff >> dest_offset;
*pout &= ~mask;
*pout |= (val >> dest_offset);
pout++;
mask = (0xff << (8 - dest_offset)) & 0xff;
*pout &= ~mask;
*pout |= (val << (8 - dest_offset)); /* no need to & mask */
}
nbits -= 8;
}
/* Copy last bits 1-7 */
if (nbits > 0)
{
val = *pin;
if (nbits + dest_offset <= 8)
{
mask = ((0xff << (8 - nbits)) & 0xff) >> dest_offset;
*pout &= ~mask;
*pout |= ((val >> dest_offset) & mask);
}
else
{
mask = 0xff >> dest_offset;
*pout &= ~mask;
*pout |= (val >> dest_offset);
pout++;
nbits -= (8 - dest_offset);
mask = 0xff << (8 - nbits);
*pout &= ~mask;
*pout |= ((val << (8 - dest_offset)) & mask);
}
}
return dest;
}
static FAR void *bitscpy_ss(FAR void *dest, FAR const void *src,
int src_offset, size_t nbits)
{
FAR unsigned char *pout = (FAR unsigned char *)dest;
FAR unsigned char *pin = (FAR unsigned char *)src;
uint8_t val;
/* Copy block of bytes */
while (nbits >= 8)
{
/* Read. MSB is first */
if (src_offset == 0)
{
val = *pin++;
}
else
{
val = ((*pin) << src_offset);
pin++;
val |= ((*pin) >> (8 - src_offset));
}
/* Write */
*pout++ = val;
nbits -= 8;
}
/* Copy last bits 1-7 */
if (nbits > 0)
{
val = ((*pin) << src_offset);
if (nbits + src_offset > 8)
{
pin++;
val |= ((*pin & (~(0xff >> (nbits + src_offset - 8))
>> (8 - src_offset))));
}
*pout &= (~((0xff << (8 - nbits))));
*pout |= (val & (0xff << (8 - nbits)));
}
return dest;
}
#endif /* CONFIG_LCD_SSD1680 */