| /**************************************************************************** |
| * drivers/lcd/ssd1680.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. |
| * |
| ****************************************************************************/ |
| |
| /* 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 |
| * displays 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); |
| nxsched_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); |
| nxsched_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) |
| { |
| nxsched_usleep(1000); |
| } |
| } |
| else |
| { |
| nxsched_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 */ |