/**
 * @file
 *
 * @ingroup RTEMSBSPsARMLPC176X
 *
 * @brief PWM-Out controller for the mbed lpc1768 board.
 */

/*
 * Copyright (c) 2014 Taller Technologies.
 *
 * @author  Diaz Marcos (marcos.diaz@tallertechnologies.com)
 *
 * The license and distribution terms for this file may be
 * found in the file LICENSE in this distribution or at
 * http://www.rtems.org/license/LICENSE.
 */

#include <rtems/status-checks.h>
#include <bsp/pwmout.h>
#include <bsp/pwmout-defs.h>

/**
 * @brief The low level device.
 */
static lpc176x_pwm_device *const pwm_device =
  (lpc176x_pwm_device *) PWM1_BASE_ADDR;

/**
 * @brief The possible output pins for each PWM output.
 */
static const lpc176x_pwm_pin pwm_pins[ PWM_OUTPUT_NUMBER ][ PWM_NUMBER_OF_PINS
] =
{
  { { 50u, LPC176X_PIN_FUNCTION_10 }, { 64u, LPC176X_PIN_FUNCTION_01 } },
  { { 52u, LPC176X_PIN_FUNCTION_10 }, { 65u, LPC176X_PIN_FUNCTION_01 } },
  { { 53u, LPC176X_PIN_FUNCTION_10 }, { 66u, LPC176X_PIN_FUNCTION_01 } },
  { { 55u, LPC176X_PIN_FUNCTION_10 }, { 67u, LPC176X_PIN_FUNCTION_01 } },
  { { 56u, LPC176X_PIN_FUNCTION_10 }, { 68u, LPC176X_PIN_FUNCTION_01 } },
  { { 58u, LPC176X_PIN_FUNCTION_10 }, { 69u, LPC176X_PIN_FUNCTION_01 } },
};

/**
 * @brief The pointers to the low level match registers for each PWM output.
 */
static volatile uint32_t *const pwm_match[ PWM_OUTPUT_NUMBER ] = {
  &PWM1MR1,
  &PWM1MR2,
  &PWM1MR3,
  &PWM1MR4,
  &PWM1MR5,
  &PWM1MR6
};

/**
 * @brief Checks if a pin number is valid for the given PWM,
 *  and sets the corresponding pin function for that pin.
 *
 * @param pin_number The pin number to search.
 * @param pwm In which PWM search for the pin number.
 * @param pin_function If the pin number is found, here we return
 *  the pin function for that pin number.
 * @return True if found, false otherwise.
 */
static inline bool is_found_in_this_pwm(
  const lpc176x_pin_number       pin_number,
  const lpc176x_pwm_number    pwm,
  lpc176x_pin_function *const pin_function
)
{
  lpc176x_pwm_pin_number pnumber = PWM_FIRST_PIN;
  bool                   found = false;

  while (!found && ( pnumber < PWM_NUMBER_OF_PINS ))
  {
    if ( pwm_pins[ pwm ][ pnumber ].pin_number == pin_number ) {
      found = true;
      *pin_function = pwm_pins[ pwm ][ pnumber ].pin_function;
    }/*else implies that the pin number was not found. Keep looking.*/
    ++pnumber;
  }
  return found;
}

/**
 * @brief Checks if a pin number is valid for any PWM,
 *  and sets the corresponding pin function for that pin.
 *
 * @param pin_number The pin number to search.
 * @param pwm If is found here we return in which PWM was found.
 * @param pin_function If the pin number is found the pin function
 *  for this pin number one will be returned.
 * @return True if found, false otherwise.
 */
static bool is_valid_pin_number(
  const lpc176x_pin_number       pin_number,
  lpc176x_pwm_number *const   pwm,
  lpc176x_pin_function *const pin_function
)
{
  bool               found = false;
  lpc176x_pwm_number pwm_local = PWMO_1;
  while(!found && ( pwm_local < PWM_OUTPUT_NUMBER ))
  {
    if ( is_found_in_this_pwm( pin_number, pwm_local, pin_function ) ) {
      *pwm = pwm_local;
      found = true;
    } /*else implies that the pin number was not found. Keep looking.*/
    ++pwm_local;
  }

  return found;
}

/**
 * @brief Sets the period for the given PWM.
 *
 * @param pwm The PWM output in which the period will be set.
 * @param period The period to set.
 */
static void set_period(
  const lpc176x_pwm_number   pwm,
  const lpc176x_microseconds period
)
{
  pwm_device->TCR = PWM_TCR_RESET;
  pwm_device->MR0 = period * PWM_PRESCALER_USECOND;
  pwm_device->LER |= PWM_LER_LATCH_MATCH_0;
  pwm_device->TCR = PWM_TCR_PWM | PWM_TCR_ENABLE;
}

/**
 * @brief Sets the pulsewidth for the given PWM.
 *
 * @param pwm The PWM output in which the pulsewidth will be set.
 * @param pwidth The pulse width to set.
 */
static void set_pulsewidth(
  const lpc176x_pwm_number pwm,
  lpc176x_microseconds     pwidth
)
{
  pwidth *= PWM_PRESCALER_USECOND;

  if ( pwm_device->MR0 == pwidth ) {
    ++pwidth;
  } /* Not the same as the period, do nothing.*/

  *( pwm_match[ pwm ] ) = pwidth;
  pwm_device->LER |= PWM_LER_LATCH( pwm );
}

rtems_status_code pwm_init( const lpc176x_pin_number pin_number )
{
  rtems_status_code    sc = RTEMS_INVALID_NUMBER;
  lpc176x_pin_function pin_function;
  lpc176x_pwm_number   pwm;

  if ( is_valid_pin_number( pin_number, &pwm, &pin_function ) ) {
    sc = lpc176x_module_enable( LPC176X_MODULE_PWM_1,
      LPC176X_MODULE_PCLK_DEFAULT );
    RTEMS_CHECK_SC( sc, "enable pwm module" );

    pwm_device->PR = 0;
    pwm_device->MCR = PWM_MCR_RESET_ON_MATCH0;
    pwm_device->PCR |= PWM_PCR_ENABLE_PWM( pwm );

    set_period( pwm, PWM_DEFAULT_PERIOD );
    set_pulsewidth( pwm, PWM_DEFAULT_PULSEWIDTH );

    lpc176x_pin_select( pin_number, pin_function );
  } /* else implies that the pin number is not valid.
     So, a RTEMS_INVALID_NUMBER will be returned.*/

  return sc;
}

rtems_status_code pwm_period(
  const lpc176x_pin_number    pin_number,
  const lpc176x_microseconds period
)
{
  rtems_status_code    sc = RTEMS_INVALID_NUMBER;
  lpc176x_pin_function pin_function;
  lpc176x_pwm_number   pwm;

  if ( is_valid_pin_number( pin_number, &pwm, &pin_function ) ) {
    sc = RTEMS_SUCCESSFUL;
    set_period( pwm, period );
  } /* else implies that the pin number is not valid.
     So, a RTEMS_INVALID_NUMBER will be returned.*/

  return sc;
}

rtems_status_code pwm_pulsewidth(
  const lpc176x_pin_number    pin_number,
  const lpc176x_microseconds pwidth
)
{
  rtems_status_code    sc = RTEMS_INVALID_NUMBER;
  lpc176x_pin_function pin_function;
  lpc176x_pwm_number   pwm;

  if ( is_valid_pin_number( pin_number, &pwm, &pin_function ) ) {
    sc = RTEMS_SUCCESSFUL;
    set_pulsewidth( pwm, pwidth );
  } /* Else wrong pin_number return RTEMS_INVALID_NUMBER*/

  return sc;
}
