/****************************************************************************
 * boards/arm/stm32/mikroe-stm32f4/src/stm32_mio283qt9a.c
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

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

#include <nuttx/config.h>

#include <sys/types.h>
#include <stdint.h>
#include <stdbool.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>

#include <nuttx/arch.h>
#include <nuttx/board.h>
#include <nuttx/lcd/lcd.h>
#include <nuttx/lcd/mio283qt9a.h>

#include <arch/board/board.h>

#include "arm_internal.h"
#include "stm32.h"
#include "stm32_gpio.h"
#include "mikroe-stm32f4.h"

#ifdef CONFIG_LCD_MIO283QT9A

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

/* Mikroe-STM32F4 Hardware Definitions **************************************/

/* --- ------------------ -------------------- ------------------------
 * PIN CONFIGURATIONS         SIGNAL NAME          ON-BOARD CONNECTIONS
 *     (Family Data Sheet
  *          Table 1-1)     (PIC32MX7 Schematic)
 * --- ------------------ -------------------- ------------------------
 *  39 PE8                  LCD_RST              TFT display
 *  46 PE15                 LCD-CS#              TFT display
 *  40 PE9                  LCD_BLED             LCD backlight LED
 *  43 PE12                 LCD-RS               TFT display
 *
 *
 *  97 RE0                  T_D0                 TFT display
 *  98 RE1                  T_D1                 TFT display
 *   1 RE2                  T_D2                 TFT display
 *   2 RE3                  T_D3                 TFT display
 *   3 RE4                  T_D4                 TFT display
 *   4 RE5                  T_D5                 TFT display
 *   5 RE6                  T_D6                 TFT display
 *  38 RE7                  T_D7                 TFT display
 *
 *  41 PE10                 PMPRD
 *  42 RE11                 PMPWR
 *
 * TOUCHSCREEN PIN CONFIGURATIONS
 * PIN CONFIGURATIONS       SIGNAL NAME          ON-BOARD CONNECTIONS
 * --- ------------------ ----------------- --------------------------
 *  35 PB0                  LCD-YD               TFT display
 *   ?                      LCD-XR               TFT display
 *   ?                      LCD-YU               TFT display
 *  36 PB1                  LCD-XL               TFT display
 *  95 PB8                  DRIVEA               TFT display
 *  96 PB9                  DRIVEB               TFT display
 */

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

struct stm32f4_dev_s
{
  struct mio283qt9a_lcd_s dev;       /* The externally visible part of the driver */
  bool                    grammode;  /* true=Writing to GRAM (16-bit write vs 8-bit) */
  bool                    firstread; /* First GRAM read? */
  struct lcd_dev_s       *drvr;      /* The saved instance of the LCD driver */
};

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

/* Low Level LCD access */

static void stm32_select(struct mio283qt9a_lcd_s *dev);
static void stm32_deselect(struct mio283qt9a_lcd_s *dev);
static void stm32_index(struct mio283qt9a_lcd_s *dev, uint8_t index);
#if !defined(CONFIG_MIO283QT2_WRONLY) && !defined(CONFIG_LCD_NOGETRUN)
static uint16_t stm32_read(struct mio283qt9a_lcd_s *dev);
#endif
static void stm32_write(struct mio283qt9a_lcd_s *dev, uint16_t data);
static void stm32_backlight(struct mio283qt9a_lcd_s *dev, int power);

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

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

static struct stm32f4_dev_s g_stm32f4_lcd =
{
  {
    .select    = stm32_select,
    .deselect  = stm32_deselect,
    .index     = stm32_index,
#if !defined(CONFIG_MIO283QT2_WRONLY) && !defined(CONFIG_LCD_NOGETRUN)
    .read      = stm32_read,
#endif
    .write     = stm32_write,
    .backlight = stm32_backlight
  }
};

static uint32_t * volatile g_portsetreset = (uint32_t *) STM32_GPIOE_BSRR;

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

/****************************************************************************
 * Name: stm32_tinydelay
 *
 * Description:
 *   Delay for a few hundred NS.
 *
 ****************************************************************************/

static void stm32_tinydelay(void)
{
  volatile uint8_t x = 0;

  for (x = 1; x > 0; x--)
    ;
}

/****************************************************************************
 * Name: stm32_command
 *
 * Description:
 *   Configure to write an LCD command
 *
 ****************************************************************************/

static inline void stm32_command(void)
{
  uint32_t  * volatile portsetreset = (uint32_t *) STM32_GPIOE_BSRR;

  /* Low selects command */

  *portsetreset = (1 << LCD_RS_PIN) << 16;
}

/****************************************************************************
 * Name: stm32_data
 *
 * Description:
 *   Configure to read or write LCD data
 *
 ****************************************************************************/

static inline void stm32_data(void)
{
  /* Hi selects data */

  *g_portsetreset = 1 << LCD_RS_PIN;
}

/****************************************************************************
 * Name: stm32_select
 *
 * Description:
 *   Select the LCD device
 *
 ****************************************************************************/

static void stm32_select(struct mio283qt9a_lcd_s *dev)
{
  /* CS low selects */

  *g_portsetreset = (1 << LCD_CS_PIN) << 16;
}

/****************************************************************************
 * Name: stm32_deselect
 *
 * Description:
 *   De-select the LCD device
 *
 ****************************************************************************/

static void stm32_deselect(struct mio283qt9a_lcd_s *dev)
{
  /* CS high de-selects */

  *g_portsetreset = 1 << LCD_CS_PIN;
}

/****************************************************************************
 * Name: stm32_index
 *
 * Description:
 *   Set the index register
 *
 ****************************************************************************/

static void stm32_index(struct mio283qt9a_lcd_s *dev, uint8_t index)
{
  struct stm32f4_dev_s *priv = (struct stm32f4_dev_s *)dev;

  /* Setup to write in command mode (vs data mode) */

  stm32_command();

  /* Write the index register to the 8-bit GPIO pin bus.  We are violating
   * the datasheet here a little by driving the WR pin low at the same time
   * as the data, but the fact is that all ASIC logic will latch on the
   * rising edge of WR anyway, not the falling edge.  We are just shaving off
   * a few cycles every time this routine is called, which will be fairly
   * often.
   */

  *g_portsetreset = index | ((uint8_t) (~index) << 16) |
      ((1 << LCD_PMPWR_PIN) << 16);

  /* Record if we are accessing GRAM or not (16 vs 8 bit accesses)
   * NOTE.  This also serves as a delay between WR low to WR high
   * transition.
   */

  priv->grammode = index == 0x2c;
  priv->firstread = true;

  /* Now raise the WR line */

  *g_portsetreset = (1 << LCD_PMPWR_PIN);

  /* Back to data mode to read/write the data */

  stm32_data();
}

/****************************************************************************
 * Name: stm32_read
 *
 * Description:
 *   Read LCD data (GRAM data or register contents)
 *
 ****************************************************************************/

#if !defined(CONFIG_MIO283QT2_WRONLY) && !defined(CONFIG_LCD_NOGETRUN)
static uint16_t stm32_read(struct mio283qt9a_lcd_s *dev)
{
  struct stm32f4_dev_s *priv = (struct stm32f4_dev_s *)dev;
  uint32_t  * volatile portsetreset = (uint32_t *) STM32_GPIOE_BSRR;
  uint32_t  * volatile portmode = (uint32_t *) STM32_GPIOE_MODER;
  uint32_t  * volatile portinput = (uint32_t *) STM32_GPIOE_IDR;
  uint16_t data;

  /* Set the I/O Port to input mode.  Ugly, but fast. */

  *portmode &= 0xffff0000;

  /* Read the data */

  *portsetreset = (1 << LCD_PMPRD_PIN) << 16;
  stm32_tinydelay();
  data = *portinput & 0x00ff;
  *portsetreset = (1 << LCD_PMPRD_PIN);

  /* Test if a 16-bit read is needed (GRAM mode) */

  if (priv->grammode)
    {
      /* If this is the 1st GRAM read, then discard the dummy byte */

      if (priv->firstread)
        {
          priv->firstread = false;
          *portsetreset = (1 << LCD_PMPRD_PIN) << 16;
          stm32_tinydelay();
          data = *portinput;
          *portsetreset = (1 << LCD_PMPRD_PIN);
        }

      /* Okay, a 16-bit read is actually a 24-bit read from the LCD.
       * this is because the read color format of the MIO283QT-2 is a bit
       * different than the 16-bit write color format.  During a read,
       * the R,G and B samples are read on subsequent bytes, and the
       * data is MSB aligned.  We must re-construct the 16-bit 565 data.
       */

      /* Clip RED sample to 5-bits and shit to MSB */

      data = (data & 0xf8) << 8;

      /* Now read Green sample */

      *portsetreset = (1 << LCD_PMPRD_PIN) << 16;
      stm32_tinydelay();
      data |= (*portinput & 0x00fc) << 3;
      *portsetreset = (1 << LCD_PMPRD_PIN);

      /* Now read Blue sample */

      *portsetreset = (1 << LCD_PMPRD_PIN) << 16;
      stm32_tinydelay();
      data |= (*portinput & 0x00f8) >> 3;
      *portsetreset = (1 << LCD_PMPRD_PIN);
    }

  /* Put the port back in output mode.  Ugly, but fast. */

  *portmode |= 0x00005555;

  return data;
}
#endif

/****************************************************************************
 * Name: stm32_write
 *
 * Description:
 *   Write LCD data (GRAM data or register contents)
 *
 ****************************************************************************/

static void stm32_write(struct mio283qt9a_lcd_s *dev, uint16_t data)
{
  struct stm32f4_dev_s *priv = (struct stm32f4_dev_s *)dev;

  /* Write the data register to the 8-bit GPIO pin bus.  We are violating the
   * datasheet here a little by driving the WR pin low at the same time as
   * the data, but the fact is that all ASIC logic will latch on the rising
   * edge of WR anyway, not the falling edge.  We are just shaving off a few
   * cycles every time this routine is called, which will be fairly often.
   */

  if (priv->grammode)
    {
      /* Need to write 16-bit pixel data (16 BPP).
       *  Write the upper pixel data first
       */

      *g_portsetreset = ((data >> 8) & 0xff) | (((~data >> 8) & 0xff) <<
                           16) | ((1 << LCD_PMPWR_PIN) << 16);
      stm32_tinydelay();
      *g_portsetreset = (1 << LCD_PMPWR_PIN);
    }

  /* Now write the lower 8-bit of data */

  *g_portsetreset = (data & 0xff) | ((~data & 0xff) << 16) |
    ((1 << LCD_PMPWR_PIN) << 16);
  stm32_tinydelay();
  *g_portsetreset = (1 << LCD_PMPWR_PIN);
}

/****************************************************************************
 * Name: stm32_backlight
 *
 * Description:
 *   Set the backlight power level.
 *
 ****************************************************************************/

static void stm32_backlight(struct mio283qt9a_lcd_s *dev, int power)
{
  /* For now, we just control the backlight as a discrete.  Pulse width
   * modulation would be required to vary the backlight level.  A low value
   * turns the backlight off.
   */

  stm32_gpiowrite(GPIO_LCD_BLED, power > 0);
}

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

/****************************************************************************
 * Name: stm32_lcdinitialize
 *
 * Description:
 *   Initialize the LCD.  This function should be called early in the boot
 *   sequendce -- Even if the LCD is not enabled.  In that case we should
 *   at a minimum at least disable the LCD backlight.
 *
 ****************************************************************************/

void stm32_lcdinitialize(void)
{
  /* Configure all LCD discrete controls.  LCD will be left in this state:
   * 1. Held in reset,
   * 2. Not selected,
   * 3. Backlight off,
   * 4. Command selected.
   */

#ifdef CONFIG_LCD_MIO283QT9A
  stm32_configgpio(GPIO_LCD_RST);
  stm32_configgpio(GPIO_LCD_CS);
  stm32_configgpio(GPIO_LCD_BLED);
  stm32_gpiowrite(GPIO_LCD_BLED, false);
  stm32_configgpio(GPIO_LCD_RS);
  stm32_configgpio(GPIO_LCD_PMPWR);
  stm32_configgpio(GPIO_LCD_PMPRD);

  /* Configure PE0-7 for output */

  stm32_configgpio(GPIO_LCD_T_D0);
  stm32_configgpio(GPIO_LCD_T_D1);
  stm32_configgpio(GPIO_LCD_T_D2);
  stm32_configgpio(GPIO_LCD_T_D3);
  stm32_configgpio(GPIO_LCD_T_D4);
  stm32_configgpio(GPIO_LCD_T_D5);
  stm32_configgpio(GPIO_LCD_T_D6);
  stm32_configgpio(GPIO_LCD_T_D7);

#else
  /* Just configure the backlight control as an output and turn off the
   * backlight for now.
   */

  stm32_configgpio(GPIO_LCD_BLED);
#endif
}

/****************************************************************************
 * Name:  board_lcd_initialize
 *
 * Description:
 *   Initialize the LCD video hardware.  The initial state of the LCD is
 *   fully initialized, display memory cleared, and the LCD ready to use,
 *   but with the power setting at 0 (full off).
 *
 ****************************************************************************/

int board_lcd_initialize(void)
{
  /* Only initialize the driver once.
   * NOTE: The LCD GPIOs were already configured by stm32_lcdinitialize.
   */

  if (!g_stm32f4_lcd.drvr)
    {
      lcdinfo("Initializing\n");

      /* Hold the LCD in reset (active low)  */

      stm32_gpiowrite(GPIO_LCD_RST, false);

      /* Bring the LCD out of reset */

      up_mdelay(5);
      stm32_gpiowrite(GPIO_LCD_RST, true);

      /* Configure and enable the LCD */

      up_mdelay(50);
      g_stm32f4_lcd.drvr = mio283qt9a_lcdinitialize(&g_stm32f4_lcd.dev);
      if (!g_stm32f4_lcd.drvr)
        {
          lcderr("ERROR: mio283qt9a_lcdinitialize failed\n");
          return -ENODEV;
        }
    }

  /* Turn the display off */

  g_stm32f4_lcd.drvr->setpower(g_stm32f4_lcd.drvr, 0);
  return OK;
}

/****************************************************************************
 * Name:  board_lcd_getdev
 *
 * Description:
 *   Return a a reference to the LCD object for the specified LCD.  This
 *   allows support for multiple LCD devices.
 *
 ****************************************************************************/

struct lcd_dev_s *board_lcd_getdev(int lcddev)
{
  DEBUGASSERT(lcddev == 0);
  return g_stm32f4_lcd.drvr;
}

/****************************************************************************
 * Name:  board_lcd_uninitialize
 *
 * Description:
 *   Uninitialize the LCD support
 *
 ****************************************************************************/

void board_lcd_uninitialize(void)
{
  /* Turn the display off */

  g_stm32f4_lcd.drvr->setpower(g_stm32f4_lcd.drvr, 0);
}

#endif /* CONFIG_LCD_MIO283QT9A */
