/***************************************************************************//**
 * @file em_lcd.h
 * @brief Liquid Crystal Display (LCD) peripheral API
 * @version 4.2.1
 *******************************************************************************
 * @section License
 * <b>(C) Copyright 2015 Silicon Labs, http://www.silabs.com</b>
 *******************************************************************************
 *
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you must not
 *    claim that you wrote the original software.
 * 2. Altered source versions must be plainly marked as such, and must not be
 *    misrepresented as being the original software.
 * 3. This notice may not be removed or altered from any source distribution.
 *
 * DISCLAIMER OF WARRANTY/LIMITATION OF REMEDIES: Silicon Labs has no
 * obligation to support this Software. Silicon Labs is providing the
 * Software "AS IS", with no express or implied warranties of any kind,
 * including, but not limited to, any implied warranties of merchantability
 * or fitness for any particular purpose or warranties against infringement
 * of any proprietary rights of a third party.
 *
 * Silicon Labs will not be liable for any consequential, incidental, or
 * special damages, or any other relief, or for any claim by any third party,
 * arising from your use of this Software.
 *
 ******************************************************************************/

#ifndef __SILICON_LABS_EM_LCD_H__
#define __SILICON_LABS_EM_LCD_H__

#include "em_device.h"

#if defined(LCD_COUNT) && (LCD_COUNT > 0)
#include <stdint.h>
#include <stdbool.h>

#ifdef __cplusplus
extern "C" {
#endif

/***************************************************************************//**
 * @addtogroup EM_Library
 * @{
 ******************************************************************************/

/***************************************************************************//**
 * @addtogroup LCD
 * @{
 ******************************************************************************/

/*******************************************************************************
 ********************************   ENUMS   ************************************
 ******************************************************************************/

/** MUX setting */
typedef enum
{
  /** Static (segments can be multiplexed with LCD_COM[0]) */
  lcdMuxStatic     = LCD_DISPCTRL_MUX_STATIC,
  /** Duplex / 1/2 Duty cycle (segments can be multiplexed with LCD_COM[0:1]) */
  lcdMuxDuplex     = LCD_DISPCTRL_MUX_DUPLEX,
  /** Triplex / 1/3 Duty cycle (segments can be multiplexed with LCD_COM[0:2]) */
  lcdMuxTriplex    = LCD_DISPCTRL_MUX_TRIPLEX,
  /** Quadruplex / 1/4 Duty cycle (segments can be multiplexed with LCD_COM[0:3]) */
  lcdMuxQuadruplex = LCD_DISPCTRL_MUX_QUADRUPLEX,
#if defined(LCD_DISPCTRL_MUXE_MUXE)
  /** Sextaplex / 1/6 Duty cycle (segments can be multiplexed with LCD_COM[0:5]) */
  lcdMuxSextaplex  = LCD_DISPCTRL_MUXE_MUXE | LCD_DISPCTRL_MUX_DUPLEX,
  /** Octaplex / 1/6 Duty cycle (segments can be multiplexed with LCD_COM[0:5]) */
  lcdMuxOctaplex   = LCD_DISPCTRL_MUXE_MUXE | LCD_DISPCTRL_MUX_QUADRUPLEX
#endif
} LCD_Mux_TypeDef;

/** Bias setting */
typedef enum
{
  /** Static (2 levels) */
  lcdBiasStatic    = LCD_DISPCTRL_BIAS_STATIC,
  /** 1/2 Bias (3 levels) */
  lcdBiasOneHalf   = LCD_DISPCTRL_BIAS_ONEHALF,
  /** 1/3 Bias (4 levels) */
  lcdBiasOneThird  = LCD_DISPCTRL_BIAS_ONETHIRD,
#if defined(LCD_DISPCTRL_BIAS_ONEFOURTH)
  /** 1/4 Bias (5 levels) */
  lcdBiasOneFourth = LCD_DISPCTRL_BIAS_ONEFOURTH,
#endif
} LCD_Bias_TypeDef;

/** Wave type */
typedef enum
{
  /** Low power optimized waveform output */
  lcdWaveLowPower = LCD_DISPCTRL_WAVE_LOWPOWER,
  /** Regular waveform output */
  lcdWaveNormal   = LCD_DISPCTRL_WAVE_NORMAL
} LCD_Wave_TypeDef;

/** VLCD Voltage Source */
typedef enum
{
  /** VLCD Powered by VDD */
  lcdVLCDSelVDD       = LCD_DISPCTRL_VLCDSEL_VDD,
  /** VLCD Powered by external VDD / Voltage Boost */
  lcdVLCDSelVExtBoost = LCD_DISPCTRL_VLCDSEL_VEXTBOOST
} LCD_VLCDSel_TypeDef;

/** Contrast Configuration */
typedef enum
{
  /** Contrast is adjusted relative to VDD (VLCD) */
  lcdConConfVLCD = LCD_DISPCTRL_CONCONF_VLCD,
  /** Contrast is adjusted relative to Ground */
  lcdConConfGND  = LCD_DISPCTRL_CONCONF_GND
} LCD_ConConf_TypeDef;

/** Voltage Boost Level - Datasheets document setting for each part number */
typedef enum
{
  lcdVBoostLevel0 = LCD_DISPCTRL_VBLEV_LEVEL0, /**< Voltage boost LEVEL0 */
  lcdVBoostLevel1 = LCD_DISPCTRL_VBLEV_LEVEL1, /**< Voltage boost LEVEL1 */
  lcdVBoostLevel2 = LCD_DISPCTRL_VBLEV_LEVEL2, /**< Voltage boost LEVEL2 */
  lcdVBoostLevel3 = LCD_DISPCTRL_VBLEV_LEVEL3, /**< Voltage boost LEVEL3 */
  lcdVBoostLevel4 = LCD_DISPCTRL_VBLEV_LEVEL4, /**< Voltage boost LEVEL4 */
  lcdVBoostLevel5 = LCD_DISPCTRL_VBLEV_LEVEL5, /**< Voltage boost LEVEL5 */
  lcdVBoostLevel6 = LCD_DISPCTRL_VBLEV_LEVEL6, /**< Voltage boost LEVEL6 */
  lcdVBoostLevel7 = LCD_DISPCTRL_VBLEV_LEVEL7  /**< Voltage boost LEVEL7 */
} LCD_VBoostLevel_TypeDef;

/** Frame Counter Clock Prescaler, FC-CLK = FrameRate (Hz) / this factor */
typedef enum
{
  /** Prescale Div 1 */
  lcdFCPrescDiv1 = LCD_BACTRL_FCPRESC_DIV1,
  /** Prescale Div 2 */
  lcdFCPrescDiv2 = LCD_BACTRL_FCPRESC_DIV2,
  /** Prescale Div 4 */
  lcdFCPrescDiv4 = LCD_BACTRL_FCPRESC_DIV4,
  /** Prescale Div 8 */
  lcdFCPrescDiv8 = LCD_BACTRL_FCPRESC_DIV8
} LCD_FCPreScale_TypeDef;

/** Segment selection */
typedef enum
{
  /** Select segment lines 0 to 3 */
  lcdSegment0_3   = (1 << 0),
  /** Select segment lines 4 to 7 */
  lcdSegment4_7   = (1 << 1),
  /** Select segment lines 8 to 11 */
  lcdSegment8_11  = (1 << 2),
  /** Select segment lines 12 to 15 */
  lcdSegment12_15 = (1 << 3),
  /** Select segment lines 16 to 19 */
  lcdSegment16_19 = (1 << 4),
  /** Select segment lines 20 to 23 */
  lcdSegment20_23 = (1 << 5),
#if defined(_LCD_SEGD0L_MASK) && (_LCD_SEGD0L_MASK == 0x00FFFFFFUL)
  /** Select all segment lines */
  lcdSegmentAll   = (0x003f)
#elif defined(_LCD_SEGD0H_MASK) && (_LCD_SEGD0H_MASK == 0x000000FFUL)
  /** Select segment lines 24 to 27 */
  lcdSegment24_27 = (1 << 6),
  /** Select segment lines 28 to 31 */
  lcdSegment28_31 = (1 << 7),
  /** Select segment lines 32 to 35 */
  lcdSegment32_35 = (1 << 8),
  /** Select segment lines 36 to 39 */
  lcdSegment36_39 = (1 << 9),
  /** Select all segment lines */
  lcdSegmentAll   = (0x03ff)
#endif
} LCD_SegmentRange_TypeDef;

/** Update Data Control */
typedef enum
{
  /** Regular update, data transfer done immediately */
  lcdUpdateCtrlRegular    = LCD_CTRL_UDCTRL_REGULAR,
  /** Data transfer done at Frame Counter event */
  lcdUpdateCtrlFCEvent    = LCD_CTRL_UDCTRL_FCEVENT,
  /** Data transfer done at Frame Start  */
  lcdUpdateCtrlFrameStart = LCD_CTRL_UDCTRL_FRAMESTART
} LCD_UpdateCtrl_TypeDef;

/** Animation Shift operation; none, left or right */
typedef enum
{
  /** No shift */
  lcdAnimShiftNone  = _LCD_BACTRL_AREGASC_NOSHIFT,
  /** Shift segment bits left */
  lcdAnimShiftLeft  = _LCD_BACTRL_AREGASC_SHIFTLEFT,
  /** Shift segment bits right */
  lcdAnimShiftRight = _LCD_BACTRL_AREGASC_SHIFTRIGHT
} LCD_AnimShift_TypeDef;

/** Animation Logic Control, how AReg and BReg should be combined */
typedef enum
{
  /** Use bitwise logic AND to mix animation register A (AREGA) and B (AREGB) */
  lcdAnimLogicAnd = LCD_BACTRL_ALOGSEL_AND,
  /** Use bitwise logic OR to mix animation register A (AREGA) and B (AREGB) */
  lcdAnimLogicOr  = LCD_BACTRL_ALOGSEL_OR
} LCD_AnimLogic_TypeDef;


/*******************************************************************************
 *******************************   STRUCTS   ***********************************
 ******************************************************************************/

/** LCD Animation Configuration */
typedef struct
{
  /** Enable Animation at end of initialization */
  bool                  enable;
  /** Initial Animation Register A Value */
  uint32_t              AReg;
  /** Shift operation of Animation Register A */
  LCD_AnimShift_TypeDef AShift;
  /** Initial Animation Register B Value */
  uint32_t              BReg;
  /** Shift operation of Animation Register B */
  LCD_AnimShift_TypeDef BShift;
  /** A and B Logical Operation to use for mixing and outputting resulting segments */
  LCD_AnimLogic_TypeDef animLogic;
#if defined(LCD_BACTRL_ALOC)
  /** Number of first segment to animate. Options are 0 or 8 for Giant/Leopard. End is startSeg+7 */
  int                   startSeg;
#endif
} LCD_AnimInit_TypeDef;

/** LCD Frame Control Initialization */
typedef struct
{
  /** Enable at end */
  bool                   enable;
  /** Frame Counter top value */
  uint32_t               top;
  /** Frame Counter clock prescaler */
  LCD_FCPreScale_TypeDef prescale;
} LCD_FrameCountInit_TypeDef;

/** LCD Controller Initialization structure */
typedef struct
{
  /** Enable controller at end of initialization */
  bool                enable;
  /** Mux configuration */
  LCD_Mux_TypeDef     mux;
  /** Bias configuration */
  LCD_Bias_TypeDef    bias;
  /** Wave configuration */
  LCD_Wave_TypeDef    wave;
  /** VLCD Select */
  LCD_VLCDSel_TypeDef vlcd;
  /** Contrast Configuration */
  LCD_ConConf_TypeDef contrast;
} LCD_Init_TypeDef;

/** Default config for LCD init structure, enables 160 segments  */
#define LCD_INIT_DEFAULT \
{                        \
  true,                  \
  lcdMuxQuadruplex,      \
  lcdBiasOneThird,       \
  lcdWaveLowPower,       \
  lcdVLCDSelVDD,         \
  lcdConConfVLCD         \
}

/*******************************************************************************
 *****************************   PROTOTYPES   **********************************
 ******************************************************************************/

void LCD_Init(const LCD_Init_TypeDef *lcdInit);
void LCD_VLCDSelect(LCD_VLCDSel_TypeDef vlcd);
void LCD_UpdateCtrl(LCD_UpdateCtrl_TypeDef ud);
void LCD_FrameCountInit(const LCD_FrameCountInit_TypeDef *fcInit);
void LCD_AnimInit(const LCD_AnimInit_TypeDef *animInit);

void LCD_SegmentRangeEnable(LCD_SegmentRange_TypeDef segment, bool enable);
void LCD_SegmentSet(int com, int bit, bool enable);
void LCD_SegmentSetLow(int com, uint32_t mask, uint32_t bits);
#if defined(_LCD_SEGD0H_MASK)
void LCD_SegmentSetHigh(int com, uint32_t mask, uint32_t bits);
#endif
void LCD_ContrastSet(int level);
void LCD_VBoostSet(LCD_VBoostLevel_TypeDef vboost);

#if defined(LCD_CTRL_DSC)
void LCD_BiasSegmentSet(int segment, int biasLevel);
void LCD_BiasComSet(int com, int biasLevel);
#endif

/***************************************************************************//**
 * @brief
 *   Enable or disable LCD controller
 *
 * @param[in] enable
 *   If true, enables LCD controller with current configuration, if false
 *   disables LCD controller. CMU clock for LCD must be enabled for correct
 *   operation.
 ******************************************************************************/
__STATIC_INLINE void LCD_Enable(bool enable)
{
  if (enable)
  {
    LCD->CTRL |= LCD_CTRL_EN;
  }
  else
  {
    LCD->CTRL &= ~LCD_CTRL_EN;
  }
}


/***************************************************************************//**
 * @brief
 *   Enables or disables LCD Animation feature
 *
 * @param[in] enable
 *   Boolean true enables animation, false disables animation
 ******************************************************************************/
__STATIC_INLINE void LCD_AnimEnable(bool enable)
{
  if (enable)
  {
    LCD->BACTRL |= LCD_BACTRL_AEN;
  }
  else
  {
    LCD->BACTRL &= ~LCD_BACTRL_AEN;
  }
}


/***************************************************************************//**
 * @brief
 *   Enables or disables LCD blink
 *
 * @param[in] enable
 *   Boolean true enables blink, false disables blink
 ******************************************************************************/
__STATIC_INLINE void LCD_BlinkEnable(bool enable)
{
  if (enable)
  {
    LCD->BACTRL |= LCD_BACTRL_BLINKEN;
  }
  else
  {
    LCD->BACTRL &= ~LCD_BACTRL_BLINKEN;
  }
}


/***************************************************************************//**
 * @brief
 *   Disables all segments, while keeping segment state
 *
 * @param[in] enable
 *   Boolean true clears all segments, boolean false restores all segment lines
 ******************************************************************************/
__STATIC_INLINE void LCD_BlankEnable(bool enable)
{
  if (enable)
  {
    LCD->BACTRL |= LCD_BACTRL_BLANK;
  }
  else
  {
    LCD->BACTRL &= ~LCD_BACTRL_BLANK;
  }
}


/***************************************************************************//**
 * @brief
 *   Enables or disables LCD Frame Control
 *
 * @param[in] enable
 *   Boolean true enables frame counter, false disables frame counter
 ******************************************************************************/
__STATIC_INLINE void LCD_FrameCountEnable(bool enable)
{
  if (enable)
  {
    LCD->BACTRL |= LCD_BACTRL_FCEN;
  }
  else
  {
    LCD->BACTRL &= ~LCD_BACTRL_FCEN;
  }
}


/***************************************************************************//**
 * @brief
 *   Returns current animation state
 *
 * @return
 *   Animation state, in range 0-15
 ******************************************************************************/
__STATIC_INLINE int LCD_AnimState(void)
{
  return (int)(LCD->STATUS & _LCD_STATUS_ASTATE_MASK) >> _LCD_STATUS_ASTATE_SHIFT;
}


/***************************************************************************//**
 * @brief
 *   Returns current blink state
 *
 * @return
 *   Return value is 1 if segments are enabled, 0 if disabled
 ******************************************************************************/
__STATIC_INLINE int LCD_BlinkState(void)
{
  return (int)(LCD->STATUS & _LCD_STATUS_BLINK_MASK) >> _LCD_STATUS_BLINK_SHIFT;
}


/***************************************************************************//**
 * @brief
 *   When set, LCD registers will not be updated until cleared,
 *
 * @param[in] enable
 *   When enable is true, update is stopped, when false all registers are
 *   updated
 ******************************************************************************/
__STATIC_INLINE void LCD_FreezeEnable(bool enable)
{
  if (enable)
  {
    LCD->FREEZE = LCD_FREEZE_REGFREEZE_FREEZE;
  }
  else
  {
    LCD->FREEZE = LCD_FREEZE_REGFREEZE_UPDATE;
  }
}


/***************************************************************************//**
 * @brief
 *   Returns SYNCBUSY bits, indicating which registers have pending updates
 *
 * @return
 *   Bit fields for LCD registers which have pending updates
 ******************************************************************************/
__STATIC_INLINE uint32_t LCD_SyncBusyGet(void)
{
  return LCD->SYNCBUSY;
}


/***************************************************************************//**
 * @brief
 *   Polls LCD SYNCBUSY flags, until flag has been cleared
 *
 * @param[in] flags
 *   Bit fields for LCD registers that shall be updated before we continue
 ******************************************************************************/
__STATIC_INLINE void LCD_SyncBusyDelay(uint32_t flags)
{
  while (LCD->SYNCBUSY & flags)
    ;
}


/***************************************************************************//**
 * @brief
 *    Get pending LCD interrupt flags
 *
 * @return
 *   Pending LCD interrupt sources. Returns a set of interrupt flags OR-ed
 *   together for multiple interrupt sources in the LCD module (LCD_IFS_nnn).
 ******************************************************************************/
__STATIC_INLINE uint32_t LCD_IntGet(void)
{
  return LCD->IF;
}


/***************************************************************************//**
 * @brief
 *   Get enabled and pending LCD interrupt flags.
 *
 * @details
 *   Useful for handling more interrupt sources in the same interrupt handler.
 *
 * @note
 *   The event bits are not cleared by the use of this function.
 *
 * @return
 *   Pending and enabled LCD interrupt sources.
 *   The return value is the bitwise AND combination of
 *   - the OR combination of enabled interrupt sources in LCD_IEN_nnn
 *   register (LCD_IEN_nnn) and
 *   - the bitwise OR combination of valid interrupt flags of the LCD module
 *   (LCD_IF_nnn).
 ******************************************************************************/
__STATIC_INLINE uint32_t LCD_IntGetEnabled(void)
{
  uint32_t ien;

  /* Store LCD->IEN in temporary variable in order to define explicit order
   * of volatile accesses. */
  ien = LCD->IEN;

  /* Bitwise AND of pending and enabled interrupts */
  return LCD->IF & ien;
}


/***************************************************************************//**
 * @brief
 *    Set one or more pending LCD interrupts from SW.
 *
 * @param[in] flags
 *   LCD interrupt sources to set to pending. Use a set of interrupt flags
 *   OR-ed together to set multiple interrupt sources for the LCD module
 *   (LCD_IFS_nnn).
 ******************************************************************************/
__STATIC_INLINE void LCD_IntSet(uint32_t flags)
{
  LCD->IFS = flags;
}


/***************************************************************************//**
 * @brief
 *    Enable LCD interrupts
 *
 * @param[in] flags
 *   LCD interrupt sources to enable. Use a set of interrupt flags OR-ed
 *   together to set multiple interrupt sources for the LCD module
 *   (LCD_IFS_nnn).
 ******************************************************************************/
__STATIC_INLINE void LCD_IntEnable(uint32_t flags)
{
  LCD->IEN |= flags;
}


/***************************************************************************//**
 * @brief
 *    Disable LCD interrupts
 *
 * @param[in] flags
 *   LCD interrupt sources to disable. Use a set of interrupt flags OR-ed
 *   together to disable multiple interrupt sources for the LCD module
 *   (LCD_IFS_nnn).
 ******************************************************************************/
__STATIC_INLINE void LCD_IntDisable(uint32_t flags)
{
  LCD->IEN &= ~flags;
}


/***************************************************************************//**
 * @brief
 *   Clear one or more interrupt flags
 *
 * @param[in] flags
 *   LCD interrupt sources to clear. Use a set of interrupt flags OR-ed
 *   together to clear multiple interrupt sources for the LCD module
 *   (LCD_IFS_nnn).
 ******************************************************************************/
__STATIC_INLINE void LCD_IntClear(uint32_t flags)
{
  LCD->IFC = flags;
}


#if defined(LCD_CTRL_DSC)
/***************************************************************************//**
 * @brief
 *   Enable or disable LCD Direct Segment Control
 *
 * @param[in] enable
 *   If true, enables LCD controller Direct Segment Control
 *   Segment and COM line bias levels needs to be set explicitly with the
 *   LCD_BiasSegmentSet() and LCD_BiasComSet() function calls.
 ******************************************************************************/
__STATIC_INLINE void LCD_DSCEnable(bool enable)
{
  if (enable)
  {
    LCD->CTRL |= LCD_CTRL_DSC;
  }
  else
  {
    LCD->CTRL &= ~LCD_CTRL_DSC;
  }
}
#endif

/** @} (end addtogroup LCD) */
/** @} (end addtogroup EM_Library) */

#ifdef __cplusplus
}
#endif

#endif /* defined(LCD_COUNT) && (LCD_COUNT > 0) */

#endif /* __SILICON_LABS_EM_LCD_H__ */
