/****************************************************************************
 * configs/stm32ldiscovery/src/stm32_lcd.c
 *
 *   Copyright (C) 2013 Gregory Nutt. All rights reserved.
 *   Authors: Gregory Nutt <gnutt@nuttx.org>
 *
 * References:
 *   - Based on the NuttX LCD1602 driver.
 *   - "STM32L100xx, STM32L151xx, STM32L152xx and STM32L162xx advanced ARM-based
 *     32-bit MCUs", STMicroelectronics, RM0038
 *   - "STM32L1 discovery kits: STM32L-DISCOVERY and 32L152CDISCOVERY,"
 *     STMicroelectronics, UM1079
 *   - STM32L-Discovery Firmware Pack V1.0.2 (for character encoding)
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name NuttX nor the names of its contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#include <sys/types.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <semaphore.h>
#include <poll.h>
#include <errno.h>
#include <debug.h>

#include <nuttx/ascii.h>
#include <nuttx/streams.h>
#include <nuttx/fs/fs.h>
#include <nuttx/lcd/slcd_ioctl.h>
#include <nuttx/lcd/slcd_codec.h>

#include "up_arch.h"
#include "stm32_gpio.h"
#include "stm32_rcc.h"
#include "chip/stm32_lcd.h"

#include "stm32ldiscovery.h"

#ifdef CONFIG_STM32_LCD

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/
/* Configuration ************************************************************/

/* Define CONFIG_DEBUG_LCD to enable detailed LCD debug output. Verbose debug
 * must also be enabled.
 */

#ifndef CONFIG_LIB_SLCDCODEC
#  error "This SLCD driver requires CONFIG_LIB_SLCDCODEC"
#endif

#ifndef CONFIG_DEBUG
#  undef CONFIG_DEBUG_VERBOSE
#  undef CONFIG_DEBUG_GRAPHICS
#  undef CONFIG_DEBUG_LCD
#endif

#ifndef CONFIG_DEBUG_VERBOSE
#  undef CONFIG_DEBUG_LCD
#endif

/* The ever-present MIN/MAX macros ******************************************/

#ifndef MIN
#  define MIN(a,b) (a < b ? a : b)
#endif

#ifndef MAX
#  define MAX(a,b) (a > b ? a : b)
#endif

/* LCD **********************************************************************/
/* LCD.  The STM32L152RBT6 supports either a 4x32 or 8x28.  The STM32L-
 * Discovery has an LCD 24 segments, 4 commons.  See stm32ldiscovery.h for
 * the pin mapping.
 */

/* Macro to convert an LCD register offset and bit number into a bit-band
 * address:
 */

#define SLCD_OFFSET           (STM32_LCD_BASE - STM32_PERIPH_BASE)
#define SLCD_BBADDR(o,b)      (STM32_PERIPHBB_BASE + ((SLCD_OFFSET + (o)) << 5) + ((b) << 2))

/* Some useful bit-band addresses */

#define SLCD_CR_LCDEN_BB      SLCD_BBADDR(STM32_LCD_CR_OFFSET,0)
#define SLCD_SR_UDR_BB        SLCD_BBADDR(STM32_LCD_SR_OFFSET,2)

/* LCD characteristics */

#define SLCD_NROWS            1
#define SLCD_NCHARS           6
#define SLCD_MAXCONTRAST      7

/* An ASCII character may need to be decorated with a colon or decimal point */

#define SLCD_DP               0x01
#define SLCD_COLON            0x02
#define SLCD_NBARS            4

/* Macros used for set/reset the LCD bar */

#define SLCD_BAR0_ON          g_slcdstate.bar[1] |= 8
#define SLCD_BAR0_OFF         g_slcdstate.bar[1] &= ~8
#define SLCD_BAR1_ON          g_slcdstate.bar[0] |= 8
#define SLCD_BAR1_OFF         g_slcdstate.bar[0] &= ~8
#define SLCD_BAR2_ON          g_slcdstate.bar[1] |= 2
#define SLCD_BAR2_OFF         g_slcdstate.bar[1] &= ~2
#define SLCD_BAR3_ON          g_slcdstate.bar[0] |= 2
#define SLCD_BAR3_OFF         g_slcdstate.bar[0] &= ~2

/* These definitions support the logic of slcd_writemem()
 *
 * ---------- ----- ----- ----- ----- ------- ------ ------ ------ ------ ------- ------- -----------------------------
 * LCD SIGNAL COM3  COM2  COM1  COM0  RAM BIT CHAR 1 CHAR 2 CHAR 3 CHAR 4 CHAR 5  CHAR 6  MASKS
 *                                             3210   3210   3210   3210  32  10  32  10
 * ---------- ----- ----- ----- ----- ------- ------ ------ ------ ------ --  --- --  --- -----------------------------
 * LCD SEG0   1N    1P    1D    1E    Bit 0     1      0      0      0     0   0   0   0  CHAR 1: 0xcffffffc
 * LCD SEG1   1DP   1COL  1C    1M    Bit 1     1      0      0      0     0   0   0   0  CHAR 1: 0xcffffffc
 * LCD SEG2   2N    2P    2D    2E    Bit 2     0      1      0      0     0   0   0   0  CHAR 2: 0xf3ffff7b
 * LCD SEG3   2DP   2COL  2C    2M    Bit 7     0      1      0      0     0   0   0   0  CHAR 2: 0xf3ffff7b
 * LCD SEG4   3N    3P    3D    3E    Bit 8     0      0      1      0     0   0   0   0  CHAR 3: 0xfcfffcff
 * LCD SEG5   3DP   3COL  3C    3M    Bit 9     0      0      1      0     0   0   0   0  CHAR 3: 0xfcfffcff
 * LCD SEG6   4N    4P    4D    4E    Bit 10    0      0      0      1     0   0   0   0  CHAR 4: 0xffcff3ff
 * LCD SEG7   4DP   4COL  4C    4M    Bit 11    0      0      0      1     0   0   0   0  CHAR 4: 0xffcff3ff
 * LCD SEG8   5N    5P    5D    5E    Bit 12    0      0      0      0     1   1   0   0  CHAR 5: 0xfff3cfff/0xfff3efff
 * LCD SEG9   BAR2  BAR3  5C    5M    Bit 13    0      0      0      0     0   1   0   0  CHAR 5: 0xfff3cfff/0xfff3efff
 * LCD SEG10  6N    6P    6D    6E    Bit 14    0      0      0      0     0   0   1   1  CHAR 6: 0xfffc3fff/0xfffcbfff
 * LCD SEG11  BAR0  BAR1  6C    6M    Bit 15    0      0      0      0     0   0   0   1  CHAR 6: 0xfffc3fff/0xfffcbfff
 * LCD SEG12  6J    6K    6A    6B    Bit 16    0      0      0      0     0   0   1   1  CHAR 6: 0xfffc3fff/0xfffcbfff
 * LCD SEG13  6H    6Q    6F    6G    Bit 17    0      0      0      0     0   0   1   1  CHAR 6: 0xfffc3fff/0xfffcbfff
 * LCD SEG14  5J    5K    5A    5B    Bit 18    0      0      0      0     1   1   0   0  CHAR 5: 0xfff3cfff/0xfff3efff
 * LCD SEG15  5H    5Q    5F    5G    Bit 19    0      0      0      0     1   1   0   0  CHAR 5: 0xfff3cfff/0xfff3efff
 * LCD SEG16  4J    4K    4A    4B    Bit 20    0      0      0      1     0   0   0   0  CHAR 4: 0xffcff3ff
 * LCD SEG17  4H    4Q    4F    4G    Bit 21    0      0      0      1     0   0   0   0  CHAR 4: 0xffcff3ff
 * LCD SEG18  3J    3K    3A    3B    Bit 24    0      0      1      0     0   0   0   0  CHAR 3: 0xfcfffcff
 * LCD SEG19  3H    3Q    3F    3G    Bit 25    0      0      1      0     0   0   0   0  CHAR 3: 0xfcfffcff
 * LCD SEG20  2J    2K    2A    2B    Bit 26    0      1      0      0     0   0   0   0  CHAR 2: 0xf3ffff7b
 * LCD SEG21  2H    2Q    2F    2G    Bit 27    0      1      0      0     0   0   0   0  CHAR 2: 0xf3ffff7b
 * LCD SEG22  1J    1K    1A    1B    Bit 28    1      0      0      0     0   0   0   0  CHAR 1: 0xcffffffc
 * LCD SEG23  1H    1Q    1F    1G    Bit 29    1      0      0      0     0   0   0   0  CHAR 1: 0xcffffffc
 * ---------- ----- ----- ----- ----- ------- ------ ------ ------ ------ ------- ------- -----------------------------
 */

/* SLCD_CHAR1_MASK  COM0-3 0xcffffffc ..11 .... .... .... .... .... .... ..11 */

#define SLCD_CHAR1_MASK0      0xcffffffc
#define SLCD_CHAR1_MASK1      SLCD_CHAR1_MASK0
#define SLCD_CHAR1_MASK2      SLCD_CHAR1_MASK0
#define SLCD_CHAR1_MASK3      SLCD_CHAR1_MASK0
#define SLCD_CHAR1_UPDATE0(s) (((uint32_t)(s) & 0x0c) << 26) | \
                              ((uint32_t)(s) & 0x03)
#define SLCD_CHAR1_UPDATE1(s) SLCD_CHAR1_UPDATE0(s)
#define SLCD_CHAR1_UPDATE2(s) SLCD_CHAR1_UPDATE0(s)
#define SLCD_CHAR1_UPDATE3(s) SLCD_CHAR1_UPDATE0(s)

/* SLCD_CHAR2_MASK  COM0-3 0xf3ffff03 .... 22.. .... .... .... .... 2... .2.. */

#define SLCD_CHAR2_MASK0      0xf3ffff7b
#define SLCD_CHAR2_MASK1      SLCD_CHAR2_MASK0
#define SLCD_CHAR2_MASK2      SLCD_CHAR2_MASK0
#define SLCD_CHAR2_MASK3      SLCD_CHAR2_MASK0
#define SLCD_CHAR2_UPDATE0(s) (((uint32_t)(s) & 0x0c) << 24) | \
                              (((uint32_t)(s) & 0x02) << 6) | \
                              (((uint32_t)(s) & 0x01) << 2)
#define SLCD_CHAR2_UPDATE1(s) SLCD_CHAR2_UPDATE0(s)
#define SLCD_CHAR2_UPDATE2(s) SLCD_CHAR2_UPDATE0(s)
#define SLCD_CHAR2_UPDATE3(s) SLCD_CHAR2_UPDATE0(s)

/* SLCD_CHAR3_MASK  COM0-3 0xfcfffcff .... ..33 .... .... .... ..33 .... .... */

#define SLCD_CHAR3_MASK0      0xfcfffcff
#define SLCD_CHAR3_MASK1      SLCD_CHAR3_MASK0
#define SLCD_CHAR3_MASK2      SLCD_CHAR3_MASK0
#define SLCD_CHAR3_MASK3      SLCD_CHAR3_MASK0
#define SLCD_CHAR3_UPDATE0(s) (((uint32_t)(s) & 0x0c) << 22) | \
                              (((uint32_t)(s) & 0x03) << 8)
#define SLCD_CHAR3_UPDATE1(s) SLCD_CHAR3_UPDATE0(s)
#define SLCD_CHAR3_UPDATE2(s) SLCD_CHAR3_UPDATE0(s)
#define SLCD_CHAR3_UPDATE3(s) SLCD_CHAR3_UPDATE0(s)

/* SLCD_CHAR4_MASK  COM0-3 0xffcff3ff .... .... ..44 .... .... 44.. .... .... */

#define SLCD_CHAR4_MASK0      0xffcff3ff
#define SLCD_CHAR4_MASK1      SLCD_CHAR4_MASK0
#define SLCD_CHAR4_MASK2      SLCD_CHAR4_MASK0
#define SLCD_CHAR4_MASK3      SLCD_CHAR4_MASK0
#define SLCD_CHAR4_UPDATE0(s) (((uint32_t)(s) & 0x0c) << 18) | \
                              (((uint32_t)(s) & 0x03) << 10)
#define SLCD_CHAR4_UPDATE1(s) SLCD_CHAR4_UPDATE0(s)
#define SLCD_CHAR4_UPDATE2(s) SLCD_CHAR4_UPDATE0(s)
#define SLCD_CHAR4_UPDATE3(s) SLCD_CHAR4_UPDATE0(s)

/* SLCD_CHAR5_MASK  COM0-1 0xfff3cfff .... .... .... 55.. ..55 .... .... ....
 *                  COM2-3 0xfff3efff .... .... .... 55.. ...5 .... .... ....
 */

#define SLCD_CHAR5_MASK0      0xfff3cfff
#define SLCD_CHAR5_MASK1      SLCD_CHAR5_MASK0
#define SLCD_CHAR5_MASK2      0xfff3efff
#define SLCD_CHAR5_MASK3      SLCD_CHAR5_MASK2
#define SLCD_CHAR5_UPDATE0(s) (((uint32_t)(s) & 0x0c) << 16) | \
                              (((uint32_t)(s) & 0x03) << 12)
#define SLCD_CHAR5_UPDATE1(s) SLCD_CHAR5_UPDATE0(s)
#define SLCD_CHAR5_UPDATE2(s) (((uint32_t)(s) & 0x0c) << 16) | \
                              (((uint32_t)(s) & 0x01) << 12)
#define SLCD_CHAR5_UPDATE3(s) SLCD_CHAR5_UPDATE2(s)

/* SLCD_CHAR6_MASK  COM0-1 0xfffc3fff .... .... .... ..66 66.. .... .... ....
 *                  COM2-3 0xfffc3fff .... .... .... ..66 .6.. .... .... ....
 */

#define SLCD_CHAR6_MASK0       0xfffc3fff
#define SLCD_CHAR6_MASK1      SLCD_CHAR6_MASK0
#define SLCD_CHAR6_MASK2       0xfffcbfff
#define SLCD_CHAR6_MASK3      SLCD_CHAR6_MASK2
#define SLCD_CHAR6_UPDATE0(s) (((uint32_t)(s) & 0x04) << 15) | \
                              (((uint32_t)(s) & 0x08) << 13) | \
                              (((uint32_t)(s) & 0x03) << 14)
#define SLCD_CHAR6_UPDATE1(s) SLCD_CHAR6_UPDATE0(s)
#define SLCD_CHAR6_UPDATE2(s) (((uint32_t)(s) & 0x04) << 15) | \
                              (((uint32_t)(s) & 0x08) << 13) | \
                              (((uint32_t)(s) & 0x03) << 14)
#define SLCD_CHAR6_UPDATE3(s) SLCD_CHAR6_UPDATE2(s)

/* Debug ********************************************************************/

#ifdef CONFIG_DEBUG_LCD
#  define lcddbg              dbg
#  define lcdvdbg             vdbg
#else
#  define lcddbg(x...)
#  define lcdvdbg(x...)
#endif

/****************************************************************************
 * Private Type Definition
 ****************************************************************************/

/* SLCD incoming stream structure */

struct slcd_instream_s
{
  struct lib_instream_s stream;
  FAR const char *buffer;
  ssize_t nbytes;
};

/* Global SLCD state */

struct stm32_slcdstate_s
{
  bool initialized;             /* True: Completed initialization sequence */
  uint8_t curpos;               /* The current cursor position */
  uint8_t buffer[SLCD_NCHARS];  /* SLCD ASCII content */
  uint8_t options[SLCD_NCHARS]; /* With colon or decimal point decoration */
  uint8_t bar[2];               /* Controls the bars on the far right of the SLCD */
};

/****************************************************************************
 * Private Function Protototypes
 ****************************************************************************/
/* Debug */

#if defined(CONFIG_DEBUG_LCD) && defined(CONFIG_DEBUG_VERBOSE)
static void slcd_dumpstate(FAR const char *msg);
static void slcd_dumpslcd(FAR const char *msg);
#else
#  define slcd_dumpstate(msg)
#  define slcd_dumpslcd(msg)
#endif

/* Internal utilities */

static void slcd_clear(void);
static int slcd_getstream(FAR struct lib_instream_s *instream);
static uint8_t slcd_getcontrast(void);
static int slcd_setcontrast(uint8_t contrast);
static void slcd_writebar(void);
static inline uint16_t slcd_mapch(uint8_t ch);
static inline void slcd_writemem(uint16_t segset, int curpos);
static void slcd_writech(uint8_t ch, uint8_t curpos, uint8_t options);
static void slcd_appendch(uint8_t ch, uint8_t options);
static void slcd_action(enum slcdcode_e code, uint8_t count);

/* Character driver methods */

static ssize_t slcd_read(FAR struct file *, FAR char *, size_t);
static ssize_t slcd_write(FAR struct file *, FAR const char *, size_t);
static int slcd_ioctl(FAR struct file *filep, int cmd, unsigned long arg);
#ifndef CONFIG_DISABLE_POLL
static int slcd_poll(FAR struct file *filep, FAR struct pollfd *fds, bool setup);
#endif

/****************************************************************************
 * Private Data
 ****************************************************************************/

/* This is the driver state structure (there is no retained state information) */

static const struct file_operations g_slcdops =
{
  0,             /* open */
  0,             /* close */
  slcd_read,     /* read */
  slcd_write,    /* write */
  0,             /* seek */
  slcd_ioctl     /* ioctl */
#ifndef CONFIG_DISABLE_POLL
  , slcd_poll    /* poll */
#endif
};

/* LCD state data */

static struct stm32_slcdstate_s g_slcdstate;

/* LCD Mapping
 *
 *              A
 *          ---------    _
 *         |\   |J  /|  |_| COL
 *        F| H  |  K |B
 *         |  \ | /  |   _
 *         --G-- --M-+  |_| COL
 *         |   /| \  |
 *        E|  Q |  N |C
 *         | /  |P  \|   _
 *          ---------   |_| DP
 *              D
 *
 * LCD character 16-bit-encoding:
 * { E , D , P , N,   M , C , COL , DP,   B , A , K , J,   G , F , Q , H }
 */

#warning "Encodings for all punctuation are incomplete"

/* Space and ASCII punctuation: 0x20-0x2f */

static const uint16_t g_slcdpunct1[ASCII_0 -  ASCII_SPACE] =
{
  0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,  /* <space> ! " # $ % & '  */
  0x0000, 0x0000, 0xa0dd, 0x0000, 0x0000, 0xa000, 0x0000, 0x00c0   /*       () * + , - . /  */
};

/* ASCII numerals 0-9: 0x30-0x39 */

static const uint16_t g_slcdnummap[ASCII_COLON - ASCII_0] =
{
  0x5f00, 0x4200, 0xf500, 0x6700, 0xea00, 0xaf00, 0xbf00, 0x4600,  /* 0-7 */
  0xff00, 0xef00                                                   /* 8-9 */
};

/* ASCII punctuation: 0x3a-0x40 */

static const uint16_t g_slcdpunct2[ASCII_A - ASCII_COLON] =
{
  0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000           /*  : ; < = > ? @   */
};

/* Upper case letters A-Z: 0x41-0x5a.  Also lower case letters a-z: 0x61-0x7a */

static const uint16_t g_slcdalphamap[ASCII_LBRACKET - ASCII_A] =
{
  0xfe00, 0x6714, 0x1d00, 0x4714, 0x9d00, 0x9c00, 0x3f00, 0xfa00,  /* A-H */
  0x0014, 0x5300, 0x9841, 0x1900, 0x5a48, 0x5a09, 0x5f00, 0xfc00,  /* I-P */
  0x5f01, 0xfc01, 0xaf00, 0x0414, 0x5b00, 0x18c0, 0x5a81, 0x00c9,  /* Q-X */
  0x0058, 0x05c0                                                   /* y-Z */
};

/* ASCII punctuation: 0x5b-0x60 */

static const uint16_t g_slcdpunct3[ASCII_a -  ASCII_LBRACKET] =
{
  0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000                   /*  [ \ ] ^ _  <right quote>  */
};

/* ASCII punctuation: 0x7b-0x7e */

static const uint16_t g_slcdpunct4[ASCII_DEL -  ASCII_LBRACE]=
{
  0x0000, 0x0000, 0x0000, 0x0000                                    /*  { | } ~  */
};

/* All GPIOs that need to be configured for the STM32L-Discovery LCD */

static uint32_t g_slcdgpio[BOARD_SLCD_NGPIOS] =
{
  BOARD_SLCD_COM0,  BOARD_SLCD_COM1, BOARD_SLCD_COM2, BOARD_SLCD_COM3,

  BOARD_SLCD_SEG0,  BOARD_SLCD_SEG1,  BOARD_SLCD_SEG2,  BOARD_SLCD_SEG3,
  BOARD_SLCD_SEG4,  BOARD_SLCD_SEG5,  BOARD_SLCD_SEG6,  BOARD_SLCD_SEG7,
  BOARD_SLCD_SEG8,  BOARD_SLCD_SEG9,  BOARD_SLCD_SEG10, BOARD_SLCD_SEG11,
  BOARD_SLCD_SEG12, BOARD_SLCD_SEG13, BOARD_SLCD_SEG14, BOARD_SLCD_SEG15,
  BOARD_SLCD_SEG16, BOARD_SLCD_SEG17, BOARD_SLCD_SEG18, BOARD_SLCD_SEG19,
  BOARD_SLCD_SEG20, BOARD_SLCD_SEG21, BOARD_SLCD_SEG22, BOARD_SLCD_SEG23
};

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: slcd_dumpstate
 ****************************************************************************/

#if defined(CONFIG_DEBUG_LCD) && defined(CONFIG_DEBUG_VERBOSE)
static void slcd_dumpstate(FAR const char *msg)
{
  lcdvdbg("%s:\n", msg);
  lcdvdbg("  curpos: %d\n",
          g_slcdstate.curpos);
  lcdvdbg("  Display: [%c%c%c%c%c%c]\n",
          g_slcdstate.buffer[0], g_slcdstate.buffer[1], g_slcdstate.buffer[2],
          g_slcdstate.buffer[3], g_slcdstate.buffer[4], g_slcdstate.buffer[5]);
  lcdvdbg("  Options: [%d%d%d%d%d%d]\n",
          g_slcdstate.options[0], g_slcdstate.options[1], g_slcdstate.options[2],
          g_slcdstate.options[3], g_slcdstate.options[4], g_slcdstate.options[5]);
  lcdvdbg("  Bar:     %02x %02x\n",
          g_slcdstate.bar[0], g_slcdstate.bar[1]);
}
#endif

/****************************************************************************
 * Name: slcd_dumpslcd
 ****************************************************************************/

#if defined(CONFIG_DEBUG_LCD) && defined(CONFIG_DEBUG_VERBOSE)
static void slcd_dumpslcd(FAR const char *msg)
{
  lcdvdbg("%s:\n", msg);
  lcdvdbg("  CR: %08x FCR: %08x SR: %08x CLR: %08x\n",
          getreg32(STM32_LCD_CR), getreg32(STM32_LCD_FCR),
          getreg32(STM32_LCD_SR), getreg32(STM32_LCD_CLR));
  lcdvdbg("  RAM0L: %08x RAM1L: %08x RAM2L: %08x RAM3L: %08x\n",
          getreg32(STM32_LCD_RAM0L), getreg32(STM32_LCD_RAM1L),
          getreg32(STM32_LCD_RAM2L), getreg32(STM32_LCD_RAM3L));
}
#endif

/****************************************************************************
 * Name: slcd_clear
 ****************************************************************************/

static void slcd_clear(void)
{
  uint32_t regaddr;

  lvdbg("Clearing\n");

  /* Make sure that any previous transfer is complete.  The firmware sets
   * the UDR each it modifies the LCD_RAM. The UDR bit stays set until the
   * end of the update.  During this time the LCD_RAM is write protected.
   */

  while ((getreg32(STM32_LCD_SR) & LCD_SR_UDR) != 0);

  /* Write all zerios in to the LCD RAM */

  for (regaddr = STM32_LCD_RAML(0); regaddr <= STM32_LCD_RAMH(7); regaddr++)
    {
      putreg32(0, regaddr);
    }

  /* Set all buffered data to undecorated spaces and home the cursor */

  memset(g_slcdstate.buffer, ' ', SLCD_NCHARS);
  memset(g_slcdstate.options, 0, SLCD_NCHARS);
  g_slcdstate.curpos = 0;

  /* Set the UDR bit to transfer the updated data to the second level
   * buffer.
   */

  putreg32(1, SLCD_SR_UDR_BB);
}

/****************************************************************************
 * Name: slcd_getstream
 *
 * Description:
 *   Get one character from the keyboard.
 *
 ****************************************************************************/

static int slcd_getstream(FAR struct lib_instream_s *instream)
{
  FAR struct slcd_instream_s *slcdstream = (FAR struct slcd_instream_s *)instream;

  DEBUGASSERT(slcdstream && slcdstream->buffer);
  if (slcdstream->nbytes > 0)
    {
      slcdstream->nbytes--;
      slcdstream->stream.nget++;
      return (int)*slcdstream->buffer++;
    }

  return EOF;
}

/****************************************************************************
 * Name: slcd_getcontrast
 ****************************************************************************/

static uint8_t slcd_getcontrast(void)
{
  return (getreg32(STM32_LCD_FCR) & LCD_FCR_CC_MASK) >> LCD_FCR_CC_SHIFT;
}

/****************************************************************************
 * Name: slcd_setcontrast
 ****************************************************************************/

static int slcd_setcontrast(uint8_t contrast)
{
  uint32_t regval;
  int ret = OK;

  /* Make sure that the contrast setting is within range */

  if (contrast > 7)
    {
      contrast = 7;
      ret = -ERANGE;
    }

  regval = getreg32(STM32_LCD_FCR);
  regval &= !LCD_FCR_CC_MASK;
  regval |= contrast << LCD_FCR_CC_SHIFT;
  putreg32(regval, STM32_LCD_FCR);

  lcdvdbg("contrast: %d FCR: %08x\n",
          getreg32(STM32_LCD_FCR), contrast);

  return ret;
}

/****************************************************************************
 * Name: slcd_writebar
 ****************************************************************************/

static void slcd_writebar(void)
{
  uint32_t regval;

  lcdvdbg("bar: %02x %02x\n", g_slcdstate.bar[0], g_slcdstate.bar[1]);
  slcd_dumpslcd("BEFORE WRITE");

  /* Make sure that any previous transfer is complete.  The firmware sets
   * the UDR each it modifies the LCD_RAM. The UDR bit stays set until the
   * end of the update.  During this time the LCD_RAM is write protected.
   */

  while ((getreg32(STM32_LCD_SR) & LCD_SR_UDR) != 0);

  /* Update the BAR */

  regval  = getreg32(STM32_LCD_RAM2L);
  regval &= 0xffff5fff;
  regval |= (uint32_t)(g_slcdstate.bar[0] << 12);
  putreg32(regval, STM32_LCD_RAM2L);

  regval  = getreg32(STM32_LCD_RAM3L);
  regval &= 0xffff5fff;
  regval |= (uint32_t)(g_slcdstate.bar[1] << 12);
  putreg32(regval, STM32_LCD_RAM3L);

  /* Set the UDR bit to transfer the updated data to the second level
   * buffer.
   */

  putreg32(1, SLCD_SR_UDR_BB);
  slcd_dumpslcd("AFTER WRITE");
}

/****************************************************************************
 * Name: slcd_mapch
 ****************************************************************************/

static inline uint16_t slcd_mapch(uint8_t ch)
{
  /* ASCII control characters, the forward delete character, period, colon,
   * and all 8-bit ASCII character have already been handled prior to this
   * function.
   */

  /* Return spaces all control characters (this should not happen) */

  if (ch < ASCII_SPACE)
    {
      return 0x0000;
    }

  /* Handle space and the first block of puncutation */

  if (ch < ASCII_0)
    {
      return g_slcdpunct1[(int)ch - ASCII_SPACE];
    }

  /* Handle numbers */

  else if (ch < ASCII_COLON)
    {
      return g_slcdnummap[(int)ch - ASCII_0];
    }

  /* Handle the next block of puncutation */

  else if (ch < ASCII_A)
    {
      return g_slcdpunct2[(int)ch - ASCII_COLON];
    }

  /* Handle upper case letters */

  else if (ch < ASCII_LBRACKET)
    {
      return g_slcdalphamap[(int)ch - ASCII_A];
    }

  /* Handle the next block of puncutation */

  else if (ch < ASCII_a)
    {
      return g_slcdpunct3[(int)ch - ASCII_LBRACKET];
    }

  /* Handle lower case letters (by mapping them to upper case */

  else if (ch < ASCII_LBRACE)
    {
      return g_slcdalphamap[(int)ch - ASCII_a];
    }

  /* Handle the final block of puncutation */

  else if (ch < ASCII_DEL)
    {
      return g_slcdpunct4[(int)ch - ASCII_LBRACE];
    }

  /* Ignore 8-bit ASCII and DEL (this should not happen) */

   return 0x0000;
}

/****************************************************************************
 * Name: slcd_writemem
 ****************************************************************************/

static inline void slcd_writemem(uint16_t segset, int curpos)
{
  uint8_t segments[4];
  uint32_t ram0;
  uint32_t ram1;
  uint32_t ram2;
  uint32_t ram3;
  int i;
  int j;

  lcdvdbg("segset: %04x curpos: %d\n", segset, curpos);
  slcd_dumpslcd("BEFORE WRITE");

  /* Isolate the least significant bits
   *
   * LCD character 16-bit-encoding:
   * { E , D , P , N,   M , C , COL , DP,   B , A , K , J,   G , F , Q , H }
   *
   * segments[0] = { E , D , P , N }
   * segments[1] = { M , C , COL , DP }
   * segments[2] = { B , A , K , J }
   * segments[3] = { G , F , Q , H }
   */

  for (i = 12, j = 0; j < 4; i -= 4, j++)
    {
      segments[j] = (segset >> i) & 0x0f;
    }

  lcdvdbg("segments: %02x %02x %02x %02x\n",
          segments[0], segments[1], segments[2], segments[3]);

  /* Make sure that any previous transfer is complete.  The firmware sets
   * the UDR each it modifies the LCD_RAM. The UDR bit stays set until the
   * end of the update.  During this time the LCD_RAM is write protected.
   */

  while ((getreg32(STM32_LCD_SR) & LCD_SR_UDR) != 0);

  /* Now update the SLCD memory for the character at this cursor position by
   * decoding the bit-mapped value
   */

  ram0 = getreg32(STM32_LCD_RAM0L);
  ram1 = getreg32(STM32_LCD_RAM1L);
  ram2 = getreg32(STM32_LCD_RAM2L);
  ram3 = getreg32(STM32_LCD_RAM3L);

  switch (curpos)
    {
    case 0:
      ram0 &= SLCD_CHAR1_MASK0;
      ram0 |= SLCD_CHAR1_UPDATE0(segments[0]);

      ram1 &= SLCD_CHAR1_MASK1;
      ram1 |= SLCD_CHAR1_UPDATE1(segments[1]);

      ram2 &= SLCD_CHAR1_MASK2;
      ram2 |= SLCD_CHAR1_UPDATE2(segments[2]);

      ram3 &= SLCD_CHAR1_MASK3;
      ram3 |= SLCD_CHAR1_UPDATE3(segments[3]);
      break;

    case 1:
      ram0 &= SLCD_CHAR2_MASK0;
      ram0 |= SLCD_CHAR2_UPDATE0(segments[0]);

      ram1 &= SLCD_CHAR2_MASK1;
      ram1 |= SLCD_CHAR2_UPDATE1(segments[1]);

      ram2 &= SLCD_CHAR2_MASK2;
      ram2 |= SLCD_CHAR2_UPDATE2(segments[2]);

      ram3 &= SLCD_CHAR2_MASK3;
      ram3 |= SLCD_CHAR2_UPDATE3(segments[3]);
      break;

    case 2:
      ram0 &= SLCD_CHAR3_MASK0;
      ram0 |= SLCD_CHAR3_UPDATE0(segments[0]);

      ram1 &= SLCD_CHAR3_MASK1;
      ram1 |= SLCD_CHAR3_UPDATE1(segments[1]);

      ram2 &= SLCD_CHAR3_MASK2;
      ram2 |= SLCD_CHAR3_UPDATE2(segments[2]);

      ram3 &= SLCD_CHAR3_MASK3;
      ram3 |= SLCD_CHAR3_UPDATE3(segments[3]);
      break;

    case 3:
      ram0 &= SLCD_CHAR4_MASK0;
      ram0 |= SLCD_CHAR4_UPDATE0(segments[0]);

      ram1 &= SLCD_CHAR4_MASK1;
      ram1 |= SLCD_CHAR4_UPDATE1(segments[1]);

      ram2 &= SLCD_CHAR4_MASK2;
      ram2 |= SLCD_CHAR4_UPDATE2(segments[2]);

      ram3 &= SLCD_CHAR4_MASK3;
      ram3 |= SLCD_CHAR4_UPDATE3(segments[3]);
      break;

    case 4:
      ram0 &= SLCD_CHAR5_MASK0;
      ram0 |= SLCD_CHAR5_UPDATE0(segments[0]);

      ram1 &= SLCD_CHAR5_MASK1;
      ram1 |= SLCD_CHAR5_UPDATE1(segments[1]);

      ram2 &= SLCD_CHAR5_MASK2;
      ram2 |= SLCD_CHAR5_UPDATE2(segments[2]);

      ram3 &= SLCD_CHAR5_MASK3;
      ram3 |= SLCD_CHAR5_UPDATE3(segments[3]);
      break;

    case 5:
      ram0 &= SLCD_CHAR6_MASK0;
      ram0 |= SLCD_CHAR6_UPDATE0(segments[0]);

      ram1 &= SLCD_CHAR6_MASK1;
      ram1 |= SLCD_CHAR6_UPDATE1(segments[1]);

      ram2 &= SLCD_CHAR6_MASK2;
      ram2 |= SLCD_CHAR6_UPDATE2(segments[2]);

      ram3 &= SLCD_CHAR6_MASK3;
      ram3 |= SLCD_CHAR6_UPDATE3(segments[3]);
      break;

    default:
      return;
  }

  putreg32(ram0, STM32_LCD_RAM0L);
  putreg32(ram1, STM32_LCD_RAM1L);
  putreg32(ram2, STM32_LCD_RAM2L);
  putreg32(ram3, STM32_LCD_RAM3L);

  /* Set the UDR bit to transfer the updated data to the second level
   * buffer.
   */

  putreg32(1, SLCD_SR_UDR_BB);
  slcd_dumpslcd("AFTER WRITE");
}

/****************************************************************************
 * Name: slcd_writech
 ****************************************************************************/

static void slcd_writech(uint8_t ch, uint8_t curpos, uint8_t options)
{
  uint16_t segset;

  /* Map the character code to a 16-bit encoded value */

  segset = slcd_mapch(ch);

  /* Check if the character should be decorated with a decimal point or colon */

  if ((options & SLCD_DP) != 0)
    {
      segset |= 0x0002;
    }
  else if ((options & SLCD_COLON) != 0)
    {
      segset |= 0x0020;
    }

  lcdvdbg("ch: [%c] options: %02x segset: %04x\n", ch, options, segset);

  /* Decode the value and write it to the SLCD segment memory */

  slcd_writemem(segset, curpos);

  /* Save these values in the state structure */

  g_slcdstate.buffer[curpos]  = ch;
  g_slcdstate.options[curpos] = options;

  slcd_dumpstate("AFTER WRITE");
}

/****************************************************************************
 * Name: slcd_appendch
 ****************************************************************************/

static void slcd_appendch(uint8_t ch, uint8_t options)
{
  lcdvdbg("ch: [%c] options: %02x\n", ch, options);

  /* Write the character at the current cursor position */

  slcd_writech(ch, g_slcdstate.curpos, options);
  if (g_slcdstate.curpos < (SLCD_NCHARS - 1))
    {
      g_slcdstate.curpos++;
    }

  slcd_dumpstate("AFTER APPEND");
}

/****************************************************************************
 * Name: slcd_action
 ****************************************************************************/

static void slcd_action(enum slcdcode_e code, uint8_t count)
{
  lcdvdbg("Action: %d count: %d\n", code, count);
  slcd_dumpstate("BEFORE ACTION");

  switch (code)
    {
      /* Erasure */

      case SLCDCODE_BACKDEL:         /* Backspace (backward delete) N characters */
        {
          int tmp;

          /* If we are at the home position or if the count is zero, then ignore the action */

          if (g_slcdstate.curpos < 1 || count < 1)
            {
              break;
            }

          /* Otherwise, BACKDEL is like moving the cursor back N characters then doing a
           * forward deletion.  Decrement the cursor position and fall through.
           */

           tmp = (int)g_slcdstate.curpos - count;
           if (tmp < 0)
             {
               tmp   = 0;
               count = g_slcdstate.curpos;
             }

           /* Save the updated cursor positions */

           g_slcdstate.curpos = tmp;
         }

      case SLCDCODE_FWDDEL:          /* DELete (forward delete) N characters moving text */
        if (count > 0)
          {
            int nchars;
            int nmove;
            int i;

            /* How many characters are to the right of the cursor position
             * (including the one at the cursor position)?  Then get the
             * number of characters to move.
             */

            nchars = SLCD_NCHARS - g_slcdstate.curpos;
            nmove  = MIN(nchars, count) - 1;

            /* Move all characters after the current cursor position left by 'nmove' characters */

            for (i = g_slcdstate.curpos + nmove; i < SLCD_NCHARS - 1; i++)
              {
                slcd_writech(g_slcdstate.buffer[i-nmove], i, g_slcdstate.options[i-nmove]);
              }

            /* Erase the last 'nmove' characters on the display */

            for (i = SLCD_NCHARS - nmove; i < SLCD_NCHARS; i++)
              {
                slcd_writech(' ', i, 0);
              }
          }
        break;

      case SLCDCODE_ERASE:           /* Erase N characters from the cursor position */
        if (count > 0)
          {
            int last;
            int i;

            /* Get the last position to clear and make sure that the last
             * position is on the SLCD.
             */

            last = g_slcdstate.curpos + count - 1;
            if (last >= SLCD_NCHARS)
              {
                last = SLCD_NCHARS - 1;
              }

            /* Erase N characters after the current cursor position left by one */

            for (i = g_slcdstate.curpos; i < last; i++)
              {
                slcd_writech(' ', i, 0);
              }
          }
        break;

      case SLCDCODE_CLEAR:           /* Home the cursor and erase the entire display */
        {
          /* This is like HOME followed by ERASEEOL.  Home the cursor and
           * fall through.
           */

          g_slcdstate.curpos = 0;
        }

      case SLCDCODE_ERASEEOL:        /* Erase from the cursor position to the end of line */
        {
          int i;

          /* Erase characters after the current cursor position to the end of the line */

          for (i = g_slcdstate.curpos; i < SLCD_NCHARS; i++)
            {
              slcd_writech(' ', i, 0);
            }
        }
        break;

      /* Cursor movement */

      case SLCDCODE_HOME:            /* Cursor home */
        {
          g_slcdstate.curpos = 0;
        }
        break;

      case SLCDCODE_END:             /* Cursor end */
        {
          g_slcdstate.curpos = SLCD_NCHARS - 1;
        }
        break;

      case SLCDCODE_LEFT:            /* Cursor left by N characters */
        {
          int tmp = (int)g_slcdstate.curpos - count;

          /* Don't permit movement past the beginning of the SLCD */

          if (tmp < 0)
            {
              tmp = 0;
            }

          /* Save the new cursor position */

          g_slcdstate.curpos = (uint8_t)tmp;
        }
        break;

      case SLCDCODE_RIGHT:           /* Cursor right by N characters */
        {
          int tmp = (int)g_slcdstate.curpos + count;

          /* Don't permit movement past the end of the SLCD */

          if (tmp >= SLCD_NCHARS)
            {
              tmp = SLCD_NCHARS - 1;
            }

          /* Save the new cursor position */

          g_slcdstate.curpos = (uint8_t)tmp;
        }
        break;

      case SLCDCODE_UP:              /* Cursor up by N lines */
      case SLCDCODE_DOWN:            /* Cursor down by N lines */
      case SLCDCODE_PAGEUP:          /* Cursor up by N pages */
      case SLCDCODE_PAGEDOWN:        /* Cursor down by N pages */
        break;                       /* Not supportable on this SLCD */

      /* Blinking */

      case SLCDCODE_BLINKSTART:      /* Start blinking with current cursor position */
      case SLCDCODE_BLINKEND:        /* End blinking after the current cursor position */
      case SLCDCODE_BLINKOFF:        /* Turn blinking off */
        break;                       /* Not implemented */

      /* These are actually unreportable errors */

      default:
      case SLCDCODE_NORMAL:          /* Not a special keycode */
        break;
    }

  slcd_dumpstate("AFTER ACTION");
}

/****************************************************************************
 * Name: slcd_read
 ****************************************************************************/

static ssize_t slcd_read(FAR struct file *filep, FAR char *buffer, size_t len)
{
  int ret = 0;
  int i;

  /* Try to read the entire display.  Notice that the seek offset
   * (filep->f_pos) is ignored.  It probably should be taken into account
   * and also updated after each read and write.
   */

  for (i = 0; i < SLCD_NCHARS && ret < len; i++)
    {
      /* Return the character */

      *buffer++ = g_slcdstate.buffer[i];
      ret++;

      /* Check if the character is decorated with a folling period or colon */

      if (ret < len && g_slcdstate.buffer[i] != 0)
        {
          if ((g_slcdstate.buffer[i] & SLCD_DP) != 0)
            {
              *buffer++ = '.';
              ret++;
            }
          else if ((g_slcdstate.buffer[i] & SLCD_COLON) != 0)
            {
              *buffer++ = ':';
              ret++;
            }
        }
    }

  slcd_dumpstate("READ");
  return ret;
}

/****************************************************************************
 * Name: slcd_write
 ****************************************************************************/

static ssize_t slcd_write(FAR struct file *filep,
                          FAR const char *buffer, size_t len)
{
  struct slcd_instream_s instream;
  struct slcdstate_s state;
  enum slcdret_e result;
  uint8_t ch;
  uint8_t count;
  uint8_t prev = ' ';
  bool valid = false;

  /* Initialize the stream for use with the SLCD CODEC */

  instream.stream.get  = slcd_getstream;
  instream.stream.nget = 0;
  instream.buffer      = buffer;
  instream.nbytes      = len;

  /* Prime the pump.  This is messy, but necessary to handle decoration on a
   * character based on any following period or colon.
   */

  memset(&state, 0, sizeof(struct slcdstate_s));
  result = slcd_decode(&instream.stream, &state, &prev, &count);

  lcdvdbg("slcd_decode returned result=%d char=%d count=%d\n",
           result, prev, count);

  switch (result)
    {
      case SLCDRET_CHAR:
        valid = true;
        break;

      case SLCDRET_SPEC:
        {
          slcd_action((enum slcdcode_e)prev, count);
          prev = ' ';
        }
        break;

      case SLCDRET_EOF:
        return 0;
    }

  /* Now decode and process every byte in the input buffer */

  while ((result = slcd_decode(&instream.stream, &state, &ch, &count)) != SLCDRET_EOF)
    {
      lcdvdbg("slcd_decode returned result=%d char=%d count=%d\n",
              result, ch, count);

      if (result == SLCDRET_CHAR)          /* A normal character was returned */
        {
          /* Check for ASCII control characters */

          if (ch < ASCII_SPACE)
            {
              /* All are ignored except for backspace and carriage return */

              if (ch == ASCII_BS)
                {
                  /* If there is a pending character, then output it now before
                   * performing the action.
                   */

                  if (valid)
                    {
                      slcd_appendch(prev, 0);
                      prev = ' ';
                      valid = false;
                    }

                  /* Then perform the backward deletion */

                  slcd_action(SLCDCODE_BACKDEL, 1);
                }
              else if (ch == ASCII_CR)
                {
                  /* If there is a pending character, then output it now before
                   * performing the action.
                   */

                  if (valid)
                    {
                      slcd_appendch(prev, 0);
                      prev = ' ';
                      valid = false;
                    }

                  /* Then perform the carriage return */

                  slcd_action(SLCDCODE_HOME, 0);
                }
            }

          /* Handle characters decoreated with a period or a colon */

          else if (ch == '.')
            {
              /* Write the previous character with the decimal point appended */

              slcd_appendch(prev, SLCD_DP);
              prev = ' ';
              valid = false;
            }
          else if (ch == ':')
            {
              /* Write the previous character with the colon appended */

              slcd_appendch(prev, SLCD_COLON);
              prev = ' ';
              valid = false;
            }

          /* Handle ASCII_DEL */

          else if (ch == ASCII_DEL)
            {
              /* If there is a pending character, then output it now before
               * performing the action.
               */

              if (valid)
                {
                  slcd_appendch(prev, 0);
                  prev = ' ';
                  valid = false;
                }

              /* Then perform the foward deletion */

              slcd_action(SLCDCODE_FWDDEL, 1);
            }

          /* The rest of the 7-bit ASCII characters are fair game */

          else if (ch < 128)
            {
              /* Write the previous character if it valid */

              if (valid)
                {
                  slcd_appendch(prev, 0);
                }

              /* There is now a valid output character */

              prev = ch;
              valid = true;
            }
        }
      else /* (result == SLCDRET_SPEC) */  /* A special SLCD action was returned */
        {
          /* If there is a pending character, then output it now before
           * performing the action.
           */

          if (valid)
            {
              slcd_appendch(prev, 0);
              prev = ' ';
              valid = false;
            }

          /* Then perform the action */

          slcd_action((enum slcdcode_e)ch, count);
        }
    }

  /* Handle any unfinished output */

  if (valid)
    {
      slcd_appendch(prev, 0);
    }

  /* Assume that the entire input buffer was processed */

  return (ssize_t)len;
}

/****************************************************************************
 * Name: slcd_poll
 ****************************************************************************/

static int slcd_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
{
  switch (cmd)
    {

      /* SLCDIOC_GETATTRIBUTES:  Get the attributes of the SLCD
       *
       * argument:  Pointer to struct slcd_attributes_s in which values will be
       *            returned
       */

      case SLCDIOC_GETATTRIBUTES:
        {
          FAR struct slcd_attributes_s *attr = (FAR struct slcd_attributes_s *)((uintptr_t)arg);

          lcdvdbg("SLCDIOC_GETATTRIBUTES:\n");

          if (!attr)
            {
              return -EINVAL;
            }

          attr->nrows         = SLCD_NROWS;
          attr->ncolumns      = SLCD_NCHARS;
          attr->nbars         = SLCD_NBARS;
          attr->maxcontrast   = SLCD_MAXCONTRAST;
          attr->maxbrightness = 0;
        }
        break;

      /* SLCDIOC_CURPOS:  Get the SLCD cursor positioni (rows x characters)
       *
       * argument:  Pointer to struct slcd_curpos_s in which values will be
       *            returned
       */

      case SLCDIOC_CURPOS:
        {
          FAR struct slcd_curpos_s *curpos = (FAR struct slcd_curpos_s *)((uintptr_t)arg);

          lcdvdbg("SLCDIOC_CURPOS: row=0 column=%d\n", g_slcdstate.curpos);

          if (!curpos)
            {
              return -EINVAL;
            }

          curpos->row    = 0;
          curpos->column = g_slcdstate.curpos;
        }
        break;

      /* SLCDIOC_SETBAR: Set bars on a bar display
       *
       * argument:  32-bit bitset, with each bit corresponding to one bar.
       */

      case SLCDIOC_SETBAR:
        {
          lcdvdbg("SLCDIOC_SETBAR: arg=0x%02lx\n", arg);

          /* Format the bar */

          g_slcdstate.bar[0] = 0;
          g_slcdstate.bar[1] = 0;

          if ((arg & 1) != 0)
            {
              SLCD_BAR0_ON;
            }

          if ((arg & 2) != 0)
            {
              SLCD_BAR1_ON;
            }

          if ((arg & 4) != 0)
            {
              SLCD_BAR2_ON;
            }

          if ((arg & 8) != 0)
            {
              SLCD_BAR3_ON;
            }

          /* Write the bar to SLCD memory */

          slcd_writebar();
        }
        break;

      /* SLCDIOC_GETCONTRAST: Get the current contrast setting
       *
       * argument:  Pointer type int that will receive the current contrast
       *            setting
       */

      case SLCDIOC_GETCONTRAST:
        {
          FAR int *contrast = (FAR int *)((uintptr_t)arg);
          if (!contrast)
            {
              return -EINVAL;
            }

          *contrast = (int)slcd_getcontrast();
          lcdvdbg("SLCDIOC_GETCONTRAST: contrast=%d\n", *contrast);
        }
        break;

      /* SLCDIOC_SETCONTRAST: Set the contrast to a new value
       *
       * argument:  The new contrast value
       */

      case SLCDIOC_SETCONTRAST:
        {
          lcdvdbg("SLCDIOC_SETCONTRAST: arg=%ld\n", arg);

          if (arg > SLCD_MAXCONTRAST)
            {
              return -ERANGE;
            }

          return slcd_setcontrast((uint8_t)arg);
        }
        break;

      case SLCDIOC_GETBRIGHTNESS:  /* Get the current brightness setting */
      case SLCDIOC_SETBRIGHTNESS:  /* Set the brightness to a new value */
      default:
        return -ENOTTY;
    }

  return OK;
}

/****************************************************************************
 * Name: slcd_poll
 ****************************************************************************/

#ifndef CONFIG_DISABLE_POLL
static int slcd_poll(FAR struct file *filep, FAR struct pollfd *fds,
                        bool setup)
{
  if (setup)
    {
      /* Data is always avaialble to be read / Data can always be written */

      fds->revents |= (fds->events & (POLLIN|POLLOUT));
      if (fds->revents != 0)
        {
          sem_post(fds->sem);
        }
    }

  return OK;
}
#endif

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: stm32_slcd_initialize
 *
 * Description:
 *   Initialize the STM32L-Discovery LCD hardware and register the character
 *   driver as /dev/slcd.
 *
 ****************************************************************************/

int stm32_slcd_initialize(void)
{
  uint32_t regval;
  int ret = OK;
  int i;

  /* Only initialize the driver once. */

  if (!g_slcdstate.initialized)
    {
      lcdvdbg("Initializing\n");

      /* Configure LCD GPIO pins */

      for (i = 0; i < BOARD_SLCD_NGPIOS; i++)
        {
          stm32_configgpio(g_slcdgpio[i]);
        }

      /* Enable the External Low-Speed (LSE) oscillator and select it as the
       * LCD clock source.
       *
       * NOTE: LCD clocking should already be enabled in the RCC APB1ENR register.
       */

      stm32_rcc_enablelse();

      lcdvdbg("APB1ENR: %08x CSR: %08x\n",
              getreg32(STM32_RCC_APB1ENR), getreg32(STM32_RCC_CSR));

      /* Set the LCD prescaler and divider values */

      regval  = getreg32(STM32_LCD_FCR);
      regval &= ~(LCD_FCR_DIV_MASK | LCD_FCR_PS_MASK);
      regval |= (LCD_FCR_PS_DIV1 |  LCD_FCR_DIV(31));
      putreg32(regval, STM32_LCD_FCR);

      /* Wait for the FCRSF flag to be set */

      lcdvdbg("Wait for FCRSF, FSR: %08x SR: %08x\n",
              getreg32(STM32_LCD_FCR), getreg32(STM32_LCD_SR));

      while ((getreg32(STM32_LCD_SR) & LCD_SR_FCRSF) == 0);

      /* Set the duty (1/4), bias (1/3), and the internal voltage source (VSEL=0) */

      regval  = getreg32(STM32_LCD_CR);
      regval &= ~(LCD_CR_BIAS_MASK | LCD_CR_DUTY_MASK | LCD_CR_VSEL);
      regval |= (LCD_CR_DUTY_1TO4 | LCD_CR_BIAS_1TO3);
      putreg32(regval, STM32_LCD_CR);

      /* SEG[31:28] are multiplexed with SEG[43:40] */

      regval |= LCD_CR_MUX_SEG;
      putreg32(regval, STM32_LCD_CR);

      /* Set the contrast to the mean value */

      regval  = getreg32(STM32_LCD_FCR);
      regval &= ~LCD_FCR_CC_MASK;
      regval |=  LCD_FCR_CC_VLCD(4);
      putreg32(regval, STM32_LCD_FCR);

      /* No dead time */

      regval &= ~LCD_FCR_DEAD_MASK;
      putreg32(regval, STM32_LCD_FCR);

      /* Set the pulse-on duration to 4/ck_ps */

      regval &= ~LCD_FCR_PON_MASK;
      regval |= LCD_FCR_PON(4);
      putreg32(regval, STM32_LCD_FCR);

      /* Wait Until the LCD FCR register is synchronized */

      lcdvdbg("Wait for FCRSF, FSR: %08x SR: %08x\n",
              getreg32(STM32_LCD_FCR), getreg32(STM32_LCD_SR));

      while ((getreg32(STM32_LCD_SR) & LCD_SR_FCRSF) == 0);

      /* Enable LCD peripheral */

      putreg32(1, SLCD_CR_LCDEN_BB);

      /* Wait Until the LCD is enabled and the LCD booster is ready */

      lcdvdbg("Wait for LCD_SR_ENS and LCD_SR_RDY, CR: %08x SR: %08x\n",
              getreg32(STM32_LCD_CR), getreg32(STM32_LCD_SR));

      while ((getreg32(STM32_LCD_SR) & (LCD_SR_ENS | LCD_SR_RDY)) != (LCD_SR_ENS | LCD_SR_RDY));

      /* Disable blinking */

      regval  = getreg32(STM32_LCD_FCR);
      regval &= ~(LCD_FCR_BLINKF_MASK | LCD_FCR_BLINK_MASK);
      regval |=  (LCD_FCR_BLINK_DISABLE | LCD_FCR_BLINKF_DIV32);
      putreg32(regval, STM32_LCD_FCR);

      slcd_dumpslcd("AFTER INITIALIZATION");

      /* Register the LCD device driver */

      ret = register_driver("/dev/slcd", &g_slcdops, 0644, &g_slcdstate);
      g_slcdstate.initialized = true;

      /* Then clear the display */

      slcd_clear();
      slcd_dumpstate("AFTER INITIALIZATION");
    }

  return ret;
}

#endif /* CONFIG_STM32_LCD */
