/****************************************************************************
 * arch/arm/src/lpc17xx/lpc17_timer.c
 *
 *   Copyright (C) 2014 Gregory Nutt. All rights reserved.
 *   Author: Gregory Nutt <gnutt@nuttx.org>
 *
 * 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 <stdint.h>
#include <stdio.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>

#include <nuttx/arch.h>
#include <nuttx/pwm.h>
#include <arch/board/board.h>

#include "up_internal.h"
#include "up_arch.h"

#include "chip.h"
#include "chip/lpc17_syscon.h"
#include "lpc17_timer.h"
#include "chip/lpc176x_pinconfig.h"
#include "lpc17_gpio.h"
#include "lpc176x_gpio.h"

/* This module then only compiles if there is at least one enabled timer
 * intended for use with the TIMER upper half driver.
 */

#if defined(CONFIG_LPC17_TMR0)

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/
/* PWM/Timer Definitions ****************************************************/
/* The following definitions are used to identify the various time types */

#define TIMTYPE_BASIC      0  /* Basic timers: TIM6-7 */
#define TIMTYPE_GENERAL16  1  /* General 16-bit timers: TIM2-5 on F1 */
#define TIMTYPE_COUNTUP16  2  /* General 16-bit count-up timers: TIM9-14 on F4 */
#define TIMTYPE_GENERAL32  3  /* General 32-bit timers: TIM2-5 on F4 */
#define TIMTYPE_ADVANCED   4  /* Advanced timers:  TIM1-8 */

#define TIMTYPE_TIM1       TIMTYPE_ADVANCED


/* Debug ********************************************************************/
/* Non-standard debug that may be enabled just for testing PWM */

#ifndef CONFIG_DEBUG
#  undef CONFIG_DEBUG_PWM
#endif

#ifdef CONFIG_DEBUG_PWM
#  define pwmdbg              dbg
#  define pwmlldbg            lldbg
#  ifdef CONFIG_DEBUG_VERBOSE
#    define pwmvdbg           vdbg
#    define pwmllvdbg         llvdbg
#    define pwm_dumpgpio(p,m) stm32_dumpgpio(p,m)
#  else
#    define pwmlldbg(x...)
#    define pwmllvdbg(x...)
#    define pwm_dumpgpio(p,m)
#  endif
#else
#  define pwmdbg(x...)
#  define pwmlldbg(x...)
#  define pwmvdbg(x...)
#  define pwmllvdbg(x...)
#  define pwm_dumpgpio(p,m)
#endif

/****************************************************************************
 * Private Types
 ****************************************************************************/
/* This structure represents the state of one PWM timer */

struct lpc17_timer_s
{
  FAR const struct pwm_ops_s *ops;     /* PWM operations */
  uint8_t                     timid;   /* Timer ID {0,...,7} */
  uint8_t                     channel; /* Timer output channel: {1,..4} */
  uint8_t                     timtype; /* See the TIMTYPE_* definitions */
  uint32_t                    base;    /* The base address of the timer */
  uint32_t                    pincfg;  /* Output pin configuration */
  uint32_t                    pclk;    /* The frequency of the peripheral clock
                                        * that drives the timer module. */
};

/****************************************************************************
 * Static Function Prototypes
 ****************************************************************************/
/* Register access */

static uint32_t timer_getreg(struct lpc17_timer_s *priv, int offset);
static void timer_putreg(struct lpc17_timer_s *priv, int offset, uint32_t value);

#if defined(CONFIG_DEBUG_PWM) && defined(CONFIG_DEBUG_VERBOSE)
static void timer_dumpregs(struct lpc17_timer_s *priv, FAR const char *msg);
#else
#  define timer_dumpregs(priv,msg)
#endif

/* Timer management */

static int timer_timer(FAR struct lpc17_timer_s *priv,
                     FAR const struct pwm_info_s *info);

/* PWM driver methods */

static int timer_setup(FAR struct pwm_lowerhalf_s *dev);
static int timer_shutdown(FAR struct pwm_lowerhalf_s *dev);

static int timer_start(FAR struct pwm_lowerhalf_s *dev,
                     FAR const struct pwm_info_s *info);

static int timer_stop(FAR struct pwm_lowerhalf_s *dev);
static int timer_ioctl(FAR struct pwm_lowerhalf_s *dev,
                     int cmd, unsigned long arg);

/****************************************************************************
 * Private Data
 ****************************************************************************/
/* This is the list of lower half PWM driver methods used by the upper half driver */

static const struct pwm_ops_s g_pwmops =
{
  .setup      = timer_setup,
  .shutdown   = timer_shutdown,
  .start      = timer_start,
  .stop       = timer_stop,
  .ioctl      = timer_ioctl,
};

#ifdef CONFIG_LPC17_TMR0
static struct lpc17_timer_s g_pwm1dev =
{
  .ops        = &g_pwmops,
  .timid      = 1,
  .channel    = CONFIG_LPC17_MAT0_PIN,
  .timtype    = TIMTYPE_TIM1,
  .base       = LPC17_TMR1_BASE,
  .pincfg     = GPIO_MAT0p1_2,
  .pclk       = (0x1 << 12),
};
#endif

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

/****************************************************************************
 * Name: timer_getreg
 *
 * Description:
 *   Read the value of an PWM timer register.
 *
 * Input Parameters:
 *   priv - A reference to the PWM block status
 *   offset - The offset to the register to read
 *
 * Returned Value:
 *   The current contents of the specified register
 *
 ****************************************************************************/

static uint32_t timer_getreg(struct lpc17_timer_s *priv, int offset)
{
  return getreg32(priv->base + offset);
}

/****************************************************************************
 * Name: timer_putreg
 *
 * Description:
 *   Read the value of an PWM timer register.
 *
 * Input Parameters:
 *   priv - A reference to the PWM block status
 *   offset - The offset to the register to read
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void timer_putreg(struct lpc17_timer_s *priv, int offset,
                         uint32_t value)
{
  putreg32(value, priv->base + offset);
}

/****************************************************************************
 * Name: timer_dumpregs
 *
 * Description:
 *   Dump all timer registers.
 *
 * Input parameters:
 *   priv - A reference to the PWM block status
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

#if defined(CONFIG_DEBUG_PWM) && defined(CONFIG_DEBUG_VERBOSE)
static void timer_dumpregs(struct lpc17_timer_s *priv, FAR const char *msg)
{
  pwmdbg("%s:\n", msg);
  pwmdbg("  CR1: %04x CR2:  %04x SMCR:  %04x DIER:  %04x\n",
          timer_getreg(priv, LPC17_PWM_MR0_OFFSET),
          timer_getreg(priv, LPC17_PWM_MR1_OFFSET),
          timer_getreg(priv, LPC17_PWM_MR2_OFFSET),
          timer_getreg(priv, LPC17_PWM_MR3_OFFSET));
#if defined(CONFIG_LPC17_TMR0)
  if (priv->timtype == TIMTYPE_ADVANCED)
    {
      pwmdbg("  RCR: %04x BDTR: %04x DCR:   %04x DMAR:  %04x\n",
              timer_getreg(priv, LPC17_PWM_MR0_OFFSET),
              timer_getreg(priv, LPC17_PWM_MR1_OFFSET),
              timer_getreg(priv, LPC17_PWM_MR2_OFFSET),
              timer_getreg(priv, LPC17_PWM_MR3_OFFSET));
    }
  else
#endif
    {
      pwmdbg("  DCR: %04x DMAR: %04x\n",
              timer_getreg(priv, LPC17_PWM_MR2_OFFSET),
              timer_getreg(priv, LPC17_PWM_MR3_OFFSET));
    }
}
#endif

/****************************************************************************
 * Name: timer_timer
 *
 * Description:
 *   (Re-)initialize the timer resources and start the pulsed output
 *
 * Input parameters:
 *   priv - A reference to the lower half PWM driver state structure
 *   info - A reference to the characteristics of the pulsed output
 *
 * Returned Value:
 *   Zero on success; a negated errno value on failure
 *
 ****************************************************************************/

static int timer_timer(FAR struct lpc17_timer_s *priv,
                       FAR const struct pwm_info_s *info)
{
  irqstate_t flags;
  uint32_t regval;

  flags = irqsave();

  putreg32(info->frequency, LPC17_TMR0_MR1);  /* Set TIMER0 MR1 = number of counts */
  putreg32(info->frequency, LPC17_TMR1_MR0);  /* Set TIMER1 MR0 = number of counts */

  putreg32(1, LPC17_TMR0_TCR);                /* Start timer0 */
  putreg32(1, LPC17_TMR1_TCR);                /* Start timer1 */

  irqrestore(flags);
  timer_dumpregs(priv, "After starting");
  return OK;
}

#ifdef XXXXX
/****************************************************************************
 * Name: timer_interrupt
 *
 * Description:
 *   Handle timer interrupts.
 *
 * Input parameters:
 *   priv - A reference to the lower half PWM driver state structure
 *
 * Returned Value:
 *   Zero on success; a negated errno value on failure
 *
 ****************************************************************************/

static int timer_interrupt(struct lpc17_timer_s *priv)
{
  uint16_t regval;

  /* Verify that this is an update interrupt.  Nothing else is expected. */

  regval = timer_getreg(priv, STM32_ATIM_SR_OFFSET);
  DEBUGASSERT((regval & ATIM_SR_UIF) != 0);

  /* Clear the UIF interrupt bit */

  timer_putreg(priv, STM32_ATIM_SR_OFFSET, regval & ~ATIM_SR_UIF);

  /* Calculate the new count by subtracting the number of pulses
   * since the last interrupt.
   */

  return OK;
}

/****************************************************************************
 * Name: timer_tim1/8interrupt
 *
 * Description:
 *   Handle timer 1 and 8 interrupts.
 *
 * Input parameters:
 *   Standard NuttX interrupt inputs
 *
 * Returned Value:
 *   Zero on success; a negated errno value on failure
 *
 ****************************************************************************/

static int timer_tim1interrupt(int irq, void *context)
{
  return timer_interrupt(&g_pwm1dev);
}

#endif /* XXXXX */

/****************************************************************************
 * Name: timer_setup
 *
 * Description:
 *   This method is called when the driver is opened.  The lower half driver
 *   should configure and initialize the device so that it is ready for use.
 *   It should not, however, output pulses until the start method is called.
 *
 * Input parameters:
 *   dev - A reference to the lower half PWM driver state structure
 *
 * Returned Value:
 *   Zero on success; a negated errno value on failure
 *
 * Assumptions:
 *   APB1 or 2 clocking for the GPIOs has already been configured by the RCC
 *   logic at power up.
 *
 ****************************************************************************/

static int timer_setup(FAR struct pwm_lowerhalf_s *dev)
{
  FAR struct lpc17_timer_s *priv = (FAR struct lpc17_timer_s *)dev;
  irqstate_t flags;
  uint32_t regval;

  flags = irqsave();

  /* Power on the timer peripherals */

  regval  = getreg32(LPC17_SYSCON_PCONP);
  regval |= SYSCON_PCONP_PCTIM0;
  regval |= SYSCON_PCONP_PCTIM1;
  regval |= SYSCON_PCONP_PCTIM2;
  regval |= SYSCON_PCONP_PCTIM3;
  putreg32(regval, LPC17_SYSCON_PCONP);

  /* Select clock for the timer peripheral */

  regval  = getreg32(LPC17_SYSCON_PCLKSEL0);
  regval &= ~(0x3 << 2);
  regval |= (0x1 << 2);                   /* PCLK_MC peripheral clk=CCLK=12.5 MHz */
  regval &= ~(0x3 << 4);
  regval |= (0x1 << 4);                   /* PCLK_MC peripheral clk=CCLK=12.5 MHz */
  putreg32(regval, LPC17_SYSCON_PCLKSEL0);
  regval  = getreg32(LPC17_SYSCON_PCLKSEL1);
  regval &= ~(0x3 << 12);
  regval |= (0x1 << 12);                  /* PCLK_MC peripheral clk=CCLK=12.5 MHz */
  regval &= ~(0x3 << 14);
  regval |= (0x1 << 14);                  /* PCLK_MC peripheral clk=CCLK=12.5 MHz */
  putreg32(regval, LPC17_SYSCON_PCLKSEL1);
  priv->pclk = (0x1 << 12) | (0x1 << 4);

  putreg32(1000, LPC17_TMR0_MR1);         /* Set TIMER0 MR1 = number of counts */

  putreg32(1, LPC17_TMR0_PR);             /* Prescaler count frequency: Fpclk/1 */
  putreg32(~(0x3 << 0), LPC17_TMR0_CCR);  /* Prescaler count frequency: Fpclk/1 */
  putreg32(~(0x3 << 0), LPC17_TMR0_CTCR); /* Prescaler count frequency: Fpclk/1 */
  putreg32((2 << 3), LPC17_TMR0_MCR);     /* Reset on match register MR1 */

  /* Output bit toggle on external match event External match on MR1, Toggle
   * external bit
   */

  putreg32(((1 << 1) | (3 << 6)), LPC17_TMR0_EMR);
  putreg32((1 << 0), LPC17_TMR0_TCR);     /* Start timer0 */

  /* Configure the output pins GPIO3.26 */

  lpc17_configgpio(GPIO_MAT0p1_2);

  putreg32(500, LPC17_TMR1_MR0);          /* Set TIMER1 MR0 = number of counts */

  putreg32(1, LPC17_TMR1_PR);             /* Prescaler count frequency:Fpclk/1 */
  putreg32(~(0x3 << 0), LPC17_TMR1_CCR);  /* Prescaler count frequency:Fpclk/1 */
  putreg32(~(0x3 << 0), LPC17_TMR1_CTCR); /* Prescaler count frequency:Fpclk/1 */
  putreg32((2 << 0), LPC17_TMR1_MCR);     /* Reset on match register MR0 */
//  putreg32(((1 << 0 )| (3 << 4)), LPC17_TMR1_EMR); /* Output bit toggle on external match event MAT0 */
  putreg32((1 << 0), LPC17_TMR1_TCR);     /* Start timer1 */

  /* configure the output pins GPIO3.26 */
//  lpc17_configgpio(GPIO_MAT0p1_2);

  irqrestore(flags);
  pwm_dumpgpio(priv->pincfg, "TIMER setup");
  return OK;
}

/****************************************************************************
 * Name: timer_shutdown
 *
 * Description:
 *   This method is called when the driver is closed.  The lower half driver
 *   stop pulsed output, free any resources, disable the timer hardware, and
 *   put the system into the lowest possible power usage state
 *
 * Input parameters:
 *   dev - A reference to the lower half TIMER driver state structure
 *
 * Returned Value:
 *   Zero on success; a negated errno value on failure
 *
 ****************************************************************************/

static int timer_shutdown(FAR struct pwm_lowerhalf_s *dev)
{
  FAR struct lpc17_timer_s *priv = (FAR struct lpc17_timer_s *)dev;
  uint32_t pincfg;

  pwmdbg("TIM%d pincfg: %08x\n", priv->timid, priv->pincfg);

  /* Make sure that the output has been stopped */

  return OK;
}

/****************************************************************************
 * Name: timer_start
 *
 * Description:
 *   (Re-)initialize the timer resources and start the pulsed output
 *
 * Input parameters:
 *   dev - A reference to the lower half TIMER driver state structure
 *   info - A reference to the characteristics of the pulsed output
 *
 * Returned Value:
 *   Zero on success; a negated errno value on failure
 *
 ****************************************************************************/

static int timer_start(FAR struct pwm_lowerhalf_s *dev,
                       FAR const struct pwm_info_s *info)
{
  FAR struct lpc17_timer_s *priv = (FAR struct lpc17_timer_s *)dev;
  return timer_timer(priv, info);
}

/****************************************************************************
 * Name: timer_stop
 *
 * Description:
 *   Stop the pulsed output and reset the timer resources
 *
 * Input parameters:
 *   dev - A reference to the lower half TIMER driver state structure
 *
 * Returned Value:
 *   Zero on success; a negated errno value on failure
 *
 * Assumptions:
 *   This function is called to stop the pulsed output at anytime.  This
 *   method is also called from the timer interrupt handler when a repetition
 *   count expires... automatically stopping the timer.
 *
 ****************************************************************************/

static int timer_stop(FAR struct pwm_lowerhalf_s *dev)
{
  FAR struct lpc17_timer_s *priv = (FAR struct lpc17_timer_s *)dev;
  uint32_t resetbit;
  uint32_t regaddr;
  uint32_t regval;
  irqstate_t flags;

  pwmdbg("TIM%d\n", priv->timid);

  /* Disable interrupts momentary to stop any ongoing timer processing and
   * to prevent any concurrent access to the reset register.
  */

  flags = irqsave();

  /* Disable further interrupts and stop the timer */

  /* Determine which timer to reset */

  switch (priv->timid)
    {
#ifdef CONFIG_LPC17_TMR0
      case 1:
        break;
#endif
    }

  /* Reset the timer - stopping the output and putting the timer back
   * into a state where timer_start() can be called.
   */

  irqrestore(flags);

  pwmdbg("regaddr: %08x resetbit: %08x\n", regaddr, resetbit);
  timer_dumpregs(priv, "After stop");
  return OK;
}

/****************************************************************************
 * Name: timer_ioctl
 *
 * Description:
 *   Lower-half logic may support platform-specific ioctl commands
 *
 * Input parameters:
 *   dev - A reference to the lower half TIMER driver state structure
 *   cmd - The ioctl command
 *   arg - The argument accompanying the ioctl command
 *
 * Returned Value:
 *   Zero on success; a negated errno value on failure
 *
 ****************************************************************************/

static int timer_ioctl(FAR struct pwm_lowerhalf_s *dev, int cmd, unsigned long arg)
{
#ifdef CONFIG_DEBUG_TIMER
  FAR struct lpc17_timer_s *priv = (FAR struct lpc17_timer_s *)dev;

  /* There are no platform-specific ioctl commands */

  pwmdbg("TIM%d\n", priv->timid);
#endif
  return -ENOTTY;
}

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

/****************************************************************************
 * Name: lpc17_timerinitialize
 *
 * Description:
 *   Initialize one timer for use with the upper_level TIMER driver.
 *
 * Input Parameters:
 *   timer - A number identifying the timer use.  The number of valid timer
 *     IDs varies with the STM32 MCU and MCU family but is somewhere in
 *     the range of {1,..,14}.
 *
 * Returned Value:
 *   On success, a pointer to the STM32 lower half TIMER driver is returned.
 *   NULL is returned on any failure.
 *
 ****************************************************************************/

FAR struct pwm_lowerhalf_s *lpc17_timerinitialize(int timer)
{
  FAR struct lpc17_timer_s *lower;

  pwmdbg("TIM%d\n", timer);

  switch (timer)
    {
#ifdef CONFIG_LPC17_TMR0
      case 0:
        lower = &g_pwm1dev;

        /* Attach but disable the TIM1 update interrupt */

        break;
#endif

      default:
        pwmdbg("No such timer configured\n");
        return NULL;
    }

  return (FAR struct pwm_lowerhalf_s *)lower;
}

#endif /* CONFIG_LPC17_TIMn_TIMER, n = 1,...,14 */
