/****************************************************************************
 * arch/arm/src/sama5/sam_pwm.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 <stdint.h>
#include <stdio.h>
#include <assert.h>
#include <errno.h>
#include <fixedmath.h>
#include <debug.h>

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

#include "hardware/sam_pinmap.h"
#include <arch/board/board.h>

#include "arm_internal.h"
#include "sam_periphclks.h"
#include "sam_pio.h"
#include "sam_pwm.h"

#ifdef CONFIG_SAMA5_PWM

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

/* Configuration ************************************************************/

#ifndef CONFIG_DEBUG_PWM_INFO
#  undef CONFIG_SAMA5_PWM_REGDEBUG
#endif

/* Currently, we support only a single PWM peripheral.  However, the hooks
 * are in place to support multiple PWM peripherals.
 */

#define PWM_SINGLE 1

/* Pulse counting is not supported by this driver */

#ifdef CONFIG_PWM_PULSECOUNT
#  warning CONFIG_PWM_PULSECOUNT no supported by this driver.
#endif

/* Are we using CLKA? CLKB?  If so, at what frequency? */

#if defined(CONFIG_SAMA5_PWM_CLKA) && !defined(CONFIG_SAMA5_PWM_CLKA_FREQUENCY)
#    error CONFIG_SAMA5_PWM_CLKA_FREQUENCY is not defined
#endif

#if defined(CONFIG_SAMA5_PWM_CLKB) && !defined(CONFIG_SAMA5_PWM_CLKB_FREQUENCY)
#    error CONFIG_SAMA5_PWM_CLKB_FREQUENCY is not defined
#endif

#ifdef CONFIG_SAMA5_PWM_CHAN0
#  if defined(CONFIG_SAMA5_PWM_CHAN0_MCK)
#    undef CONFIG_SAMA5_PWM_CHAN0_CLKA
#    undef CONFIG_SAMA5_PWM_CHAN0_CLKB
#    if CONFIG_SAMA5_PWM_CHAN0_MCKDIV == 1
#      define SAMA5_PWM_CHAN0_MCKDIV_LOG2  0
#    elif CONFIG_SAMA5_PWM_CHAN0_MCKDIV == 2
#      define SAMA5_PWM_CHAN0_MCKDIV_LOG2  1
#    elif CONFIG_SAMA5_PWM_CHAN0_MCKDIV == 4
#      define SAMA5_PWM_CHAN0_MCKDIV_LOG2  2
#    elif CONFIG_SAMA5_PWM_CHAN0_MCKDIV == 8
#      define SAMA5_PWM_CHAN0_MCKDIV_LOG2  3
#    elif CONFIG_SAMA5_PWM_CHAN0_MCKDIV == 16
#      define SAMA5_PWM_CHAN0_MCKDIV_LOG2  4
#    elif CONFIG_SAMA5_PWM_CHAN0_MCKDIV == 32
#      define SAMA5_PWM_CHAN0_MCKDIV_LOG2  5
#    elif CONFIG_SAMA5_PWM_CHAN0_MCKDIV == 64
#      define SAMA5_PWM_CHAN0_MCKDIV_LOG2  6
#    elif CONFIG_SAMA5_PWM_CHAN0_MCKDIV == 128
#      define SAMA5_PWM_CHAN0_MCKDIV_LOG2  7
#    elif CONFIG_SAMA5_PWM_CHAN0_MCKDIV == 256
#      define SAMA5_PWM_CHAN0_MCKDIV_LOG2  8
#    elif CONFIG_SAMA5_PWM_CHAN0_MCKDIV == 512
#      define SAMA5_PWM_CHAN0_MCKDIV_LOG2  9
#    elif CONFIG_SAMA5_PWM_CHAN0_MCKDIV == 1024
#      define SAMA5_PWM_CHAN0_MCKDIV_LOG2  10
#    else
#      error Unsupported MCK divider value
#    endif

#  elif defined(CONFIG_SAMA5_PWM_CHAN0_CLKA)
#    undef CONFIG_SAMA5_PWM_CHAN0_CLKB

#  elif !defined(CONFIG_SAMA5_PWM_CHAN0_CLKB)
#    error CHAN0 clock source not defined

#  endif
#endif

#ifdef CONFIG_SAMA5_PWM_CHAN1
#  if defined(CONFIG_SAMA5_PWM_CHAN1_MCK)
#    undef CONFIG_SAMA5_PWM_CHAN1_CLKA
#    undef CONFIG_SAMA5_PWM_CHAN1_CLKB
#    if CONFIG_SAMA5_PWM_CHAN1_MCKDIV == 1
#      define SAMA5_PWM_CHAN1_MCKDIV_LOG2  0
#    elif CONFIG_SAMA5_PWM_CHAN1_MCKDIV == 2
#      define SAMA5_PWM_CHAN1_MCKDIV_LOG2  1
#    elif CONFIG_SAMA5_PWM_CHAN1_MCKDIV == 4
#      define SAMA5_PWM_CHAN1_MCKDIV_LOG2  2
#    elif CONFIG_SAMA5_PWM_CHAN1_MCKDIV == 8
#      define SAMA5_PWM_CHAN1_MCKDIV_LOG2  3
#    elif CONFIG_SAMA5_PWM_CHAN1_MCKDIV == 16
#      define SAMA5_PWM_CHAN1_MCKDIV_LOG2  4
#    elif CONFIG_SAMA5_PWM_CHAN1_MCKDIV == 32
#      define SAMA5_PWM_CHAN1_MCKDIV_LOG2  5
#    elif CONFIG_SAMA5_PWM_CHAN1_MCKDIV == 64
#      define SAMA5_PWM_CHAN1_MCKDIV_LOG2  6
#    elif CONFIG_SAMA5_PWM_CHAN1_MCKDIV == 128
#      define SAMA5_PWM_CHAN1_MCKDIV_LOG2  7
#    elif CONFIG_SAMA5_PWM_CHAN1_MCKDIV == 256
#      define SAMA5_PWM_CHAN1_MCKDIV_LOG2  8
#    elif CONFIG_SAMA5_PWM_CHAN1_MCKDIV == 512
#      define SAMA5_PWM_CHAN1_MCKDIV_LOG2  9
#    elif CONFIG_SAMA5_PWM_CHAN1_MCKDIV == 1024
#      define SAMA5_PWM_CHAN1_MCKDIV_LOG2  10
#    else
#      error Unsupported MCK divider value
#    endif

#  elif defined(CONFIG_SAMA5_PWM_CHAN1_CLKA)
#    undef CONFIG_SAMA5_PWM_CHAN1_CLKB

#  elif !defined(CONFIG_SAMA5_PWM_CHAN1_CLKB)
#    error CHAN1 clock source not defined

#  endif
#endif

#ifdef CONFIG_SAMA5_PWM_CHAN2
#  if defined(CONFIG_SAMA5_PWM_CHAN2_MCK)
#    undef CONFIG_SAMA5_PWM_CHAN2_CLKA
#    undef CONFIG_SAMA5_PWM_CHAN2_CLKB
#    if CONFIG_SAMA5_PWM_CHAN2_MCKDIV == 1
#      define SAMA5_PWM_CHAN2_MCKDIV_LOG2  0
#    elif CONFIG_SAMA5_PWM_CHAN2_MCKDIV == 2
#      define SAMA5_PWM_CHAN2_MCKDIV_LOG2  1
#    elif CONFIG_SAMA5_PWM_CHAN2_MCKDIV == 4
#      define SAMA5_PWM_CHAN2_MCKDIV_LOG2  2
#    elif CONFIG_SAMA5_PWM_CHAN2_MCKDIV == 8
#      define SAMA5_PWM_CHAN2_MCKDIV_LOG2  3
#    elif CONFIG_SAMA5_PWM_CHAN2_MCKDIV == 16
#      define SAMA5_PWM_CHAN2_MCKDIV_LOG2  4
#    elif CONFIG_SAMA5_PWM_CHAN2_MCKDIV == 32
#      define SAMA5_PWM_CHAN2_MCKDIV_LOG2  5
#    elif CONFIG_SAMA5_PWM_CHAN2_MCKDIV == 64
#      define SAMA5_PWM_CHAN2_MCKDIV_LOG2  6
#    elif CONFIG_SAMA5_PWM_CHAN2_MCKDIV == 128
#      define SAMA5_PWM_CHAN2_MCKDIV_LOG2  7
#    elif CONFIG_SAMA5_PWM_CHAN2_MCKDIV == 256
#      define SAMA5_PWM_CHAN2_MCKDIV_LOG2  8
#    elif CONFIG_SAMA5_PWM_CHAN2_MCKDIV == 512
#      define SAMA5_PWM_CHAN2_MCKDIV_LOG2  9
#    elif CONFIG_SAMA5_PWM_CHAN2_MCKDIV == 1024
#      define SAMA5_PWM_CHAN2_MCKDIV_LOG2  10
#    else
#      error Unsupported MCK divider value
#    endif

#  elif defined(CONFIG_SAMA5_PWM_CHAN2_CLKA)
#    undef CONFIG_SAMA5_PWM_CHAN2_CLKB

#  elif !defined(CONFIG_SAMA5_PWM_CHAN2_CLKB)
#    error CHAN2 clock source not defined

#  endif
#endif

#ifdef CONFIG_SAMA5_PWM_CHAN3
#  if defined(CONFIG_SAMA5_PWM_CHAN3_MCK)
#    undef CONFIG_SAMA5_PWM_CHAN3_CLKA
#    undef CONFIG_SAMA5_PWM_CHAN3_CLKB
#    if CONFIG_SAMA5_PWM_CHAN3_MCKDIV == 1
#      define SAMA5_PWM_CHAN3_MCKDIV_LOG2  0
#    elif CONFIG_SAMA5_PWM_CHAN3_MCKDIV == 2
#      define SAMA5_PWM_CHAN3_MCKDIV_LOG2  1
#    elif CONFIG_SAMA5_PWM_CHAN3_MCKDIV == 4
#      define SAMA5_PWM_CHAN3_MCKDIV_LOG2  2
#    elif CONFIG_SAMA5_PWM_CHAN3_MCKDIV == 8
#      define SAMA5_PWM_CHAN3_MCKDIV_LOG2  3
#    elif CONFIG_SAMA5_PWM_CHAN3_MCKDIV == 16
#      define SAMA5_PWM_CHAN3_MCKDIV_LOG2  4
#    elif CONFIG_SAMA5_PWM_CHAN3_MCKDIV == 32
#      define SAMA5_PWM_CHAN3_MCKDIV_LOG2  5
#    elif CONFIG_SAMA5_PWM_CHAN3_MCKDIV == 64
#      define SAMA5_PWM_CHAN3_MCKDIV_LOG2  6
#    elif CONFIG_SAMA5_PWM_CHAN3_MCKDIV == 128
#      define SAMA5_PWM_CHAN3_MCKDIV_LOG2  7
#    elif CONFIG_SAMA5_PWM_CHAN3_MCKDIV == 256
#      define SAMA5_PWM_CHAN3_MCKDIV_LOG2  8
#    elif CONFIG_SAMA5_PWM_CHAN3_MCKDIV == 512
#      define SAMA5_PWM_CHAN3_MCKDIV_LOG2  9
#    elif CONFIG_SAMA5_PWM_CHAN3_MCKDIV == 1024
#      define SAMA5_PWM_CHAN3_MCKDIV_LOG2  10
#    else
#      error Unsupported MCK divider value
#    endif

#  elif defined(CONFIG_SAMA5_PWM_CHAN3_CLKA)
#    undef CONFIG_SAMA5_PWM_CHAN3_CLKB

#  elif !defined(CONFIG_SAMA5_PWM_CHAN3_CLKB)
#    error CHAN3 clock source not defined

#  endif
#endif

/* The current design does not use any PWM interrupts */

#undef PWM_INTERRUPTS

/* Pin configuration ********************************************************/

#define PWM_INPUTCFG     (PIO_INPUT | PIO_CFG_DEFAULT | PIO_DRIVE_LOW)
#define PWM_PINMASK      (PIO_PORT_MASK | PIO_PIN_MASK)
#define PWM_MKINPUT(cfg) (((cfg) & PWM_PINMASK) | PWM_INPUTCFG)

/****************************************************************************
 * Private Types
 ****************************************************************************/

/* Channel clock sources */

enum pwm_clksrc_e
{
  PWM_CLKSRC_MCK = 1,  /* Source = Divided MCK */
  PWM_CLKSRC_CLKA,     /* Source = CLKA */
  PWM_CLKSRC_CLKB,     /* Source = CLKB */
};

/* This structure represents the state of one PWM channel */

struct sam_pwm_s;
struct sam_pwm_chan_s
{
  const struct pwm_ops_s *ops;     /* PWM operations */
#ifndef PWM_SINGLE
  struct sam_pwm_s *pwm;           /* Parent PWM peripheral */
#endif
  uintptr_t base;                  /* Base address of channel registers */
  uint8_t channel;                 /* PWM channel: {0,..3} */
  uint8_t clksrc;                  /* 0=MCK; 1=CLKA; 2=CLKB */
  uint8_t divlog2;                 /* Log2 MCK divisor: 0->1, 1->2, 2->4, ... 10->1024 */
  pio_pinset_t ohpincfg;           /* Output high pin configuration */
  pio_pinset_t olpincfg;           /* Output low pin configuration */
  pio_pinset_t fipincfg;           /* Fault input pin configuration */
};

/* This structure represents the overall state of the PWM peripheral */

struct sam_pwm_s
{
  bool initialized;                /* True: one time initialization has been performed */
#ifndef PWM_SINGLE
  uintptr_t base;                  /* Base address of peripheral registers */
#endif
  /* Debug stuff */

#ifdef CONFIG_SAMA5_PWM_REGDEBUG
  bool wr;                         /* Last was a write */
  uint32_t regaddr;                /* Last address */
  uint32_t regval;                 /* Last value */
  int count;                       /* Number of times */
#endif
};

/****************************************************************************
 * Static Function Prototypes
 ****************************************************************************/

/* Register access */

#ifdef CONFIG_SAMA5_PWM_REGDEBUG
static bool pwm_checkreg(struct sam_pwm_s *chan,
                         bool wr, uint32_t regval,
                         uintptr_t regaddr);
#else
#  define pwm_checkreg(chan,wr,regval,regaddr) (false)
#endif

static uint32_t pwm_getreg(struct sam_pwm_chan_s *chan, int offset);
static void pwm_putreg(struct sam_pwm_chan_s *chan,
                       int offset, uint32_t regval);

#ifdef CONFIG_DEBUG_PWM_INFO
static void pwm_dumpregs(struct sam_pwm_chan_s *chan,
                         const char *msg);
#else
#  define pwm_dumpregs(chan,msg)
#endif

/* PWM Interrupts */

#ifdef PWM_INTERRUPTS
static int pwm_interrupt(int irq, void *context, void *arg);
#endif

/* PWM driver methods */

static int pwm_setup(struct pwm_lowerhalf_s *dev);
static int pwm_shutdown(struct pwm_lowerhalf_s *dev);
static int pwm_start(struct pwm_lowerhalf_s *dev,
                     const struct pwm_info_s *info);
static int pwm_stop(struct pwm_lowerhalf_s *dev);
static int pwm_ioctl(struct pwm_lowerhalf_s *dev,
                     int cmd, unsigned long arg);

/* Initialization */

static unsigned int pwm_clk_prescaler_log2(uint32_t mck, uint32_t fclk);
static unsigned int pwm_clk_divider(uint32_t mck, uint32_t fclk,
                                    unsigned int prelog2);
static uint32_t pwm_clk_frequency(uint32_t mck, unsigned int prelog2,
                                  unsigned int div);
static void pwm_resetpins(struct sam_pwm_chan_s *chan);

/****************************************************************************
 * 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       = pwm_setup,
  .shutdown    = pwm_shutdown,
  .start       = pwm_start,
  .stop        = pwm_stop,
  .ioctl       = pwm_ioctl,
};

/* This is the overall state of the PWM peripheral */

static struct sam_pwm_s g_pwm =
{
  .initialized = false,
#ifndef PWM_SINGLE
  .base        = SAM_PWMC_VBASE,
#endif
};

#ifdef CONFIG_SAMA5_PWM_CHAN0
/* This is the state of the PWM channel 0 */

static struct sam_pwm_chan_s g_pwm_chan0 =
{
  .ops         = &g_pwmops,
#ifndef PWM_SINGLE
  .pwm         = &g_pwm,
#endif
  .channel     = 0,
  .base        = SAM_PWM_CHANA_BASE(0),

#if defined(CONFIG_SAMA5_PWM_CHAN0_MCK)
  .clksrc      = PWM_CLKSRC_MCK,
  .divlog2     = SAMA5_PWM_CHAN0_MCKDIV_LOG2,
#elif defined(CONFIG_SAMA5_PWM_CHAN0_CLKA)
  .clksrc      = PWM_CLKSRC_CLKA,
#elif defined(CONFIG_SAMA5_PWM_CHAN0_CLKB)
  .clksrc      = PWM_CLKSRC_CLKB,
#else
#  error No clock source for channel 0
#endif

#ifdef CONFIG_SAMA5_PWM_CHAN0_OUTPUTH
  .ohpincfg    = PIO_PWM0_H,
#endif
#ifdef CONFIG_SAMA5_PWM_CHAN0_OUTPUTL
  .olpincfg    = PIO_PWM0_L,
#endif
#ifdef CONFIG_SAMA5_PWM_CHAN0_FAULTINPUT
  .fipincfg    = PIO_PWM0_FI,
#endif
};
#endif

#ifdef CONFIG_SAMA5_PWM_CHAN1
/* This is the state of the PWM channel 1 */

static struct sam_pwm_chan_s g_pwm_chan1 =
{
  .ops         = &g_pwmops,
#ifndef PWM_SINGLE
  .pwm         = &g_pwm,
#endif
  .channel     = 1,
  .base        = SAM_PWM_CHANA_BASE(1),

#if defined(CONFIG_SAMA5_PWM_CHAN1_MCK)
  .clksrc      = PWM_CLKSRC_MCK,
  .divlog2     = SAMA5_PWM_CHAN1_MCKDIV_LOG2,
#elif defined(CONFIG_SAMA5_PWM_CHAN1_CLKA)
  .clksrc      = PWM_CLKSRC_CLKA,
#elif defined(CONFIG_SAMA5_PWM_CHAN1_CLKB)
  .clksrc      = PWM_CLKSRC_CLKB,
#else
#  error No clock source for channel 0
#endif

#ifdef CONFIG_SAMA5_PWM_CHAN1_OUTPUTH
  .ohpincfg    = PIO_PWM1_H,
#endif
#ifdef CONFIG_SAMA5_PWM_CHAN1_OUTPUTL
  .olpincfg    = PIO_PWM1_L,
#endif
#ifdef CONFIG_SAMA5_PWM_CHAN1_FAULTINPUT
  .fipincfg    = PIO_PWM1_FI,
#endif
};
#endif

#ifdef CONFIG_SAMA5_PWM_CHAN2
/* This is the state of the PWM channel 2 */

static struct sam_pwm_chan_s g_pwm_chan2 =
{
  .ops         = &g_pwmops,
#ifndef PWM_SINGLE
  .pwm         = &g_pwm,
#endif
  .channel     = 2,
  .base        = SAM_PWM_CHANA_BASE(2),

#if defined(CONFIG_SAMA5_PWM_CHAN2_MCK)
  .clksrc      = PWM_CLKSRC_MCK,
  .divlog2     = SAMA5_PWM_CHAN2_MCKDIV_LOG2,
#elif defined(CONFIG_SAMA5_PWM_CHAN2_CLKA)
  .clksrc      = PWM_CLKSRC_CLKA,
#elif defined(CONFIG_SAMA5_PWM_CHAN2_CLKB)
  .clksrc      = PWM_CLKSRC_CLKB,
#else
#  error No clock source for channel 0
#endif

#ifdef CONFIG_SAMA5_PWM_CHAN2_OUTPUTH
  .ohpincfg    = PIO_PWM2_H,
#endif
#ifdef CONFIG_SAMA5_PWM_CHAN2_OUTPUTL
  .olpincfg    = PIO_PWM2_L,
#endif
#ifdef CONFIG_SAMA5_PWM_CHAN2_FAULTINPUT
  .fipincfg    = PIO_PWM2_FI,
#endif
};
#endif

#ifdef CONFIG_SAMA5_PWM_CHAN3
/* This is the state of the PWM channel 3 */

static struct sam_pwm_chan_s g_pwm_chan3 =
{
  .ops         = &g_pwmops,
#ifndef PWM_SINGLE
  .pwm         = &g_pwm,
#endif
  .channel     = 3,
  .base        = SAM_PWM_CHANA_BASE(3),

#if defined(CONFIG_SAMA5_PWM_CHAN3_MCK)
  .clksrc      = PWM_CLKSRC_MCK,
  .divlog2     = SAMA5_PWM_CHAN3_MCKDIV_LOG2,
#elif defined(CONFIG_SAMA5_PWM_CHAN3_CLKA)
  .clksrc      = PWM_CLKSRC_CLKA,
#elif defined(CONFIG_SAMA5_PWM_CHAN3_CLKB)
  .clksrc      = PWM_CLKSRC_CLKB,
#else
#  error No clock source for channel 0
#endif

#ifdef CONFIG_SAMA5_PWM_CHAN3_OUTPUTH
  .ohpincfg    = PIO_PWM3_H,
#endif
#ifdef CONFIG_SAMA5_PWM_CHAN3_OUTPUTL
  .olpincfg    = PIO_PWM3_L,
#endif
#ifdef CONFIG_SAMA5_PWM_CHAN3_FAULTINPUT
  .fipincfg    = PIO_PWM3_FI,
#endif
};
#endif

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

/****************************************************************************
 * Name: pwm_checkreg
 *
 * Description:
 *   Check if the current register access is a duplicate of the preceding.
 *
 * Input Parameters:
 *   regval  - The value to be written
 *   regaddr - The address of the register to write to
 *
 * Returned Value:
 *   true:  This is the first register access of this type.
 *   flase: This is the same as the preceding register access.
 *
 ****************************************************************************/

#ifdef CONFIG_SAMA5_PWM_REGDEBUG
static bool pwm_checkreg(struct sam_pwm_s *pwm, bool wr, uint32_t regval,
                         uintptr_t regaddr)
{
  if (wr      == pwm->wr &&      /* Same kind of access? */
      regval  == pwm->regval &&  /* Same value? */
      regaddr == pwm->regaddr)   /* Same address? */
    {
      /* Yes, then just keep a count of the number of times we did this. */

      pwm->count++;
      return false;
    }
  else
    {
      /* Did we do the previous operation more than once? */

      if (pwm->count > 0)
        {
          /* Yes... show how many times we did it */

          pwminfo("...[Repeats %d times]...\n", pwm->count);
        }

      /* Save information about the new access */

      pwm->wr   = wr;
      pwm->regval  = regval;
      pwm->regaddr = regaddr;
      pwm->count   = 0;
    }

  /* Return true if this is the first time that we have done this operation */

  return true;
}
#endif

/****************************************************************************
 * Name: pwm_getreg
 *
 * Description:
 *   Read the value of a PWM register.
 *
 * Input Parameters:
 *   chan - A reference to the PWM channel instance
 *   offset - The offset to the PWM register to read
 *
 * Returned Value:
 *   The current contents of the specified register
 *
 ****************************************************************************/

static uint32_t pwm_getreg(struct sam_pwm_chan_s *chan, int offset)
{
#ifdef PWM_SINGLE
  uintptr_t regaddr;
  uint32_t  regval;

  regaddr = SAM_PWMC_VBASE + offset;
  regval  = getreg32(regaddr);

#ifdef CONFIG_SAMA5_PWM_REGDEBUG
  if (pwm_checkreg(&g_pwm, false, regval, regaddr))
    {
      pwminfo("%08x->%08x\n", regaddr, regval);
    }
#endif

  return regval;

#else
  struct sam_pwm_chan_s *pwm = chan->pwm;
  uintptr_t regaddr;
  uint32_t  regval;

  regaddr = pwm->base + offset;
  regval  = getreg32(regaddr);

#ifdef CONFIG_SAMA5_PWM_REGDEBUG
  if (pwm_checkreg(pwm, false, regval, regaddr))
    {
      pwminfo("%08x->%08x\n", regaddr, regval);
    }
#endif

  return regval;

#endif
}

/****************************************************************************
 * Name: pwm_chan_getreg
 *
 * Description:
 *   Read the value of a PWM channel register.
 *
 * Input Parameters:
 *   chan - A reference to the PWM channel instance
 *   offset - The offset to the channel register to read
 *
 * Returned Value:
 *   The current contents of the specified register
 *
 ****************************************************************************/

#ifdef CONFIG_DEBUG_PWM_INFO /* Currently only used for debug output */
static uint32_t pwm_chan_getreg(struct sam_pwm_chan_s *chan, int offset)
{
  uintptr_t regaddr;
  uint32_t  regval;

  regaddr = chan->base + offset;
  regval  = getreg32(regaddr);

#ifdef CONFIG_SAMA5_PWM_REGDEBUG
#ifdef PWM_SINGLE
  if (pwm_checkreg(&g_pwm, false, regval, regaddr))
#else
  if (pwm_checkreg(chan->pwm, false, regval, regaddr))
#endif
    {
      pwminfo("%08x->%08x\n", regaddr, regval);
    }
#endif

  return regval;
}
#endif

/****************************************************************************
 * Name: pwm_putreg
 *
 * Description:
 *   Write a value to a PWM register.
 *
 * Input Parameters:
 *   chan - A reference to the PWM channel instance
 *   offset - The offset to the register to read
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void pwm_putreg(struct sam_pwm_chan_s *chan, int offset,
                       uint32_t regval)
{
#ifdef PWM_SINGLE
  uintptr_t regaddr = SAM_PWMC_VBASE + offset;

#ifdef CONFIG_SAMA5_PWM_REGDEBUG
  if (pwm_checkreg(&g_pwm, true, regval, regaddr))
    {
      pwminfo("%08x<-%08x\n", regaddr, regval);
    }
#endif

  putreg32(regval, regaddr);

#else
  struct sam_pwm_chan_s *pwm = chan->pwm;
  uintptr_t regaddr = pwm->base + offset;

#ifdef CONFIG_SAMA5_PWM_REGDEBUG
  if (pwm_checkreg(pwm, true, regval, regaddr))
    {
      pwminfo("%08x<-%08x\n", regaddr, regval);
    }
#endif

  putreg32(regval, regaddr);

#endif
}

/****************************************************************************
 * Name: pwm_chan_putreg
 *
 * Description:
 *   Read the value of an PWM channel register.
 *
 * Input Parameters:
 *   chan - A reference to the PWM channel instance
 *   offset - The offset to the channel register to read
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void pwm_chan_putreg(struct sam_pwm_chan_s *chan, int offset,
                            uint32_t regval)
{
  uintptr_t regaddr = chan->base + offset;

#ifdef CONFIG_SAMA5_PWM_REGDEBUG
#ifdef PWM_SINGLE
  if (pwm_checkreg(&g_pwm, true, regval, regaddr))
#else
  if (pwm_checkreg(chan->pwm, true, regval, regaddr))
#endif
    {
      pwminfo("%08x<-%08x\n", regaddr, regval);
    }
#endif

  putreg32(regval, regaddr);
}

/****************************************************************************
 * Name: pwm_dumpregs
 *
 * Description:
 *   Dump all timer registers.
 *
 * Input Parameters:
 *   chan - A reference to the PWM channel instance
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

#ifdef CONFIG_DEBUG_PWM_INFO
static void pwm_dumpregs(struct sam_pwm_chan_s *chan, const char *msg)
{
  pwminfo("PWM: %s\n", msg);
  pwminfo("   CLK: %08x    SR: %08x  IMR1: %08x  ISR1: %08x\n",
          pwm_getreg(chan, SAM_PWM_CLK_OFFSET),
          pwm_getreg(chan, SAM_PWM_SR_OFFSET),
          pwm_getreg(chan, SAM_PWM_IMR1_OFFSET),
          pwm_getreg(chan, SAM_PWM_ISR1_OFFSET));
  pwminfo("   SCM: %08x  SCUC: %08x  SCUP: %08x  IMR2: %08x\n",
          pwm_getreg(chan, SAM_PWM_SCM_OFFSET),
          pwm_getreg(chan, SAM_PWM_SCUC_OFFSET),
          pwm_getreg(chan, SAM_PWM_SCUP_OFFSET),
          pwm_getreg(chan, SAM_PWM_IMR2_OFFSET));
  pwminfo("  ISR2: %08x   OOV: %08x    OS: %08x   FMR: %08x\n",
          pwm_getreg(chan, SAM_PWM_ISR2_OFFSET),
          pwm_getreg(chan, SAM_PWM_OOV_OFFSET),
          pwm_getreg(chan, SAM_PWM_OS_OFFSET),
          pwm_getreg(chan, SAM_PWM_FMR_OFFSET));
  pwminfo("   FSR: %08x   FPV: %08x   FPE: %08x ELMR0: %08x\n",
          pwm_getreg(chan, SAM_PWM_FSR_OFFSET),
          pwm_getreg(chan, SAM_PWM_FPV_OFFSET),
          pwm_getreg(chan, SAM_PWM_FPE_OFFSET),
          pwm_getreg(chan, SAM_PWM_ELMR0_OFFSET));
  pwminfo(" ELMR1: %08x  SMMR: %08x  WPSR: %08x\n",
          pwm_getreg(chan, SAM_PWM_ELMR1_OFFSET),
          pwm_getreg(chan, SAM_PWM_SMMR_OFFSET),
          pwm_getreg(chan, SAM_PWM_WPSR_OFFSET));
  pwminfo(" CMPV0: %08x CMPM0: %08x CMPV1: %08x CMPM1: %08x\n",
          pwm_getreg(chan, SAM_PWM_CMPV0_OFFSET),
          pwm_getreg(chan, SAM_PWM_CMPM0_OFFSET),
          pwm_getreg(chan, SAM_PWM_CMPV1_OFFSET),
          pwm_getreg(chan, SAM_PWM_CMPM1_OFFSET));
  pwminfo(" CMPV2: %08x CMPM2: %08x CMPV3: %08x CMPM3: %08x\n",
          pwm_getreg(chan, SAM_PWM_CMPV2_OFFSET),
          pwm_getreg(chan, SAM_PWM_CMPM2_OFFSET),
          pwm_getreg(chan, SAM_PWM_CMPV3_OFFSET),
          pwm_getreg(chan, SAM_PWM_CMPM3_OFFSET));
  pwminfo(" CMPV4: %08x CMPM4: %08x CMPV5: %08x CMPM5: %08x\n",
          pwm_getreg(chan, SAM_PWM_CMPV4_OFFSET),
          pwm_getreg(chan, SAM_PWM_CMPM4_OFFSET),
          pwm_getreg(chan, SAM_PWM_CMPV5_OFFSET),
          pwm_getreg(chan, SAM_PWM_CMPM5_OFFSET));
  pwminfo(" CMPV6: %08x CMPM6: %08x CMPV7: %08x CMPM7: %08x\n",
          pwm_getreg(chan, SAM_PWM_CMPV6_OFFSET),
          pwm_getreg(chan, SAM_PWM_CMPM6_OFFSET),
          pwm_getreg(chan, SAM_PWM_CMPV7_OFFSET),
          pwm_getreg(chan, SAM_PWM_CMPM7_OFFSET));
  pwminfo("Channel %d: %s\n", chan->channel, msg);
  pwminfo("   CMR: %08x  CDTY: %08x  CPRD: %08x  CCNT: %08x\n",
          pwm_chan_getreg(chan, SAM_PWM_CMR_OFFSET),
          pwm_chan_getreg(chan, SAM_PWM_CDTY_OFFSET),
          pwm_chan_getreg(chan, SAM_PWM_CPRD_OFFSET),
          pwm_chan_getreg(chan, SAM_PWM_CCNT_OFFSET));
  pwminfo("    CT: %08x\n",
          pwm_chan_getreg(chan, SAM_PWM_DT_OFFSET));
}
#endif

/****************************************************************************
 * Name: pwm_interrupt
 *
 * Description:
 *   Handle timer interrupts.
 *
 * Input Parameters:
 *   Standard interrupt handler inputs
 *
 * Returned Value:
 *   Zero on success; a negated errno value on failure
 *
 ****************************************************************************/

#ifdef PWM_INTERRUPTS
static int pwm_interrupt(int irq, void *context, void *arg)
{
  /* No PWM interrupts are used in the current design */

#warning Missing logic
  return OK;
}
#endif

/****************************************************************************
 * Name: pwm_setup
 *
 * Description:
 *   This method is called when the driver is opened.  The lower half driver
 *   will be configured and initialized the device so that it is ready for
 *   use.  It will 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
 *
 ****************************************************************************/

static int pwm_setup(struct pwm_lowerhalf_s *dev)
{
  struct sam_pwm_chan_s *chan = (struct sam_pwm_chan_s *)dev;

  pwminfo("Channel %d: H=%08" PRIx32 " L=%08" PRIx32 " FI=%08" PRIx32 "\n",
          chan->channel, chan->ohpincfg, chan->olpincfg, chan->fipincfg);

  /* Configure selected PWM pins */

  if (chan->ohpincfg)
    {
      sam_configpio(chan->ohpincfg);
    }

  if (chan->olpincfg)
    {
      sam_configpio(chan->olpincfg);
    }

  if (chan->fipincfg)
    {
      sam_configpio(chan->fipincfg);
    }

  return OK;
}

/****************************************************************************
 * Name: pwm_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 PWM driver state structure
 *
 * Returned Value:
 *   Zero on success; a negated errno value on failure
 *
 ****************************************************************************/

static int pwm_shutdown(struct pwm_lowerhalf_s *dev)
{
  struct sam_pwm_chan_s *chan = (struct sam_pwm_chan_s *)dev;

  pwminfo("Channel %d\n", chan->channel);

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

  pwm_stop(dev);

  /* Then put the GPIO pins back to the default, input state */

  pwm_resetpins(chan);
  return OK;
}

/****************************************************************************
 * Name: pwm_start
 *
 * Description:
 *   (Re-)initialize the timer resources and start the pulsed output
 *
 * Input Parameters:
 *   dev  - 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 pwm_start(struct pwm_lowerhalf_s *dev,
                     const struct pwm_info_s *info)
{
  struct sam_pwm_chan_s *chan = (struct sam_pwm_chan_s *)dev;
#if defined(CONFIG_SAMA5_PWM_CLKA) || defined(CONFIG_SAMA5_PWM_CLKB)
  unsigned int prelog2;
  unsigned int div;
  uint32_t mck;
#endif
  uint32_t regval;
  uint32_t cprd;
  uint32_t fsrc;

  /* Disable the channel (should already be disabled) */

  pwm_putreg(chan, SAM_PWM_DIS_OFFSET, PWM_CHID(chan->channel));

  /* Determine the clock source */

  switch (chan->clksrc)
    {
    case PWM_CLKSRC_MCK:
      {
        regval = PWM_CMR_CPRE_MCKDIV(chan->divlog2);
        fsrc   = BOARD_MCK_FREQUENCY >> chan->divlog2;
      }
      break;

#ifdef CONFIG_SAMA5_PWM_CLKA
    case PWM_CLKSRC_CLKA:
      {
        regval  = pwm_getreg(chan, SAM_PWM_CLK_OFFSET);
        prelog2 = (unsigned int)((regval & PWM_CLK_PREA_MASK) >>
                                  PWM_CLK_PREA_SHIFT);
        div     = (unsigned int)((regval & PWM_CLK_DIVA_MASK) >>
                                  PWM_CLK_DIVA_SHIFT);
        mck     = BOARD_MCK_FREQUENCY;
        fsrc    = pwm_clk_frequency(mck, prelog2, div);
        regval  = PWM_CMR_CPRE_CLKA;
      }
      break;
#endif

#ifdef CONFIG_SAMA5_PWM_CLKB
    case PWM_CLKSRC_CLKB:
      {
        regval  = pwm_getreg(chan, SAM_PWM_CLK_OFFSET);
        prelog2 = (unsigned int)((regval & PWM_CLK_PREB_MASK) >>
                                  PWM_CLK_PREB_SHIFT);
        div     = (unsigned int)((regval & PWM_CLK_DIVB_MASK) >>
                                  PWM_CLK_DIVB_SHIFT);
        mck     = BOARD_MCK_FREQUENCY;
        fsrc    = pwm_clk_frequency(mck, prelog2, div);
        regval  = PWM_CMR_CPRE_CLKB;
      }
      break;
#endif

    default:
      pwmerr("ERROR: Invalid or unsupported clock source value: %d\n",
             chan->clksrc);
      return -EINVAL;
    }

  /* Configure the channel */

  pwm_chan_putreg(chan, SAM_PWM_CMR_OFFSET, PWM_CMR_CPRE_CLKA);

  /* Set the PWM period.
   *
   * If the waveform is left-aligned, then the output waveform period
   * depends on the channel counter source clock and can be calculated
   * as follows:
   *
   *   Tchan = cprd / Fsrc
   *   cprd  = Fsrc / Fchan
   *
   * If the waveform is center-aligned, then the output waveform period
   * depends on the channel counter source clock and can be calculated:
   *
   *   Tchan = 2 * cprd / Fsrc
   *   cprd  = Fsrc / 2 / Fchan
   *
   * Since the PWM is disabled, we can write directly to the CPRD (vs.
   * the CPRDUPD) register.
   */

  cprd = (fsrc + (info->frequency >> 1)) / info->frequency;
  pwm_chan_putreg(chan, SAM_PWM_CPRD_OFFSET, cprd);

  /* Set the PWM duty.  Since the PWM is disabled, we can write directly
   * to the CTDY (vs. the CTDYUPD) register.
   */

  regval = b16toi(info->duty * cprd + b16HALF);
  if (regval > cprd)
    {
      /* Rounding up could cause the duty value to exceed CPRD (?) */

      regval = cprd;
    }

  pwm_chan_putreg(chan, SAM_PWM_CDTY_OFFSET, regval);
  pwminfo("Fsrc=%" PRIu32 " cprd=%" PRIi32 " cdty=%" PRIx32 "\n",
           fsrc, cprd, regval);

  /* Enable the channel */

  pwm_putreg(chan, SAM_PWM_ENA_OFFSET, PWM_CHID(chan->channel));
  pwm_dumpregs(chan, "After start");
  return OK;
}

/****************************************************************************
 * Name: pwm_stop
 *
 * Description:
 *   Stop the pulsed output and reset the timer resources
 *
 * 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:
 *   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 pwm_stop(struct pwm_lowerhalf_s *dev)
{
  struct sam_pwm_chan_s *chan = (struct sam_pwm_chan_s *)dev;

  pwminfo("Channel %d\n", chan->channel);

  /* Disable further PWM interrupts from this channel */

  pwm_putreg(chan, SAM_PWM_IDR1_OFFSET,
             PWM_INT1_CHID(chan->channel) | PWM_INT1_FCHID(chan->channel));

  /* Disable the channel */

  pwm_putreg(chan, SAM_PWM_DIS_OFFSET, PWM_CHID(chan->channel));
  pwm_dumpregs(chan, "After stop");
  return OK;
}

/****************************************************************************
 * Name: pwm_ioctl
 *
 * Description:
 *   Lower-half logic may support platform-specific ioctl commands
 *
 * Input Parameters:
 *   dev - A reference to the lower half PWM 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 pwm_ioctl(struct pwm_lowerhalf_s *dev,
                     int cmd, unsigned long arg)
{
#ifdef CONFIG_DEBUG_PWM_INFO
  struct sam_pwm_chan_s *chan = (struct sam_pwm_chan_s *)dev;

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

  pwminfo("Channel %d\n", chan->channel);
#endif
  return -ENOTTY;
}

/****************************************************************************
 * Name: pwm_clk_prescaler_log2
 *
 * Description:
 *   Return log2 of the clock prescaler value.  The PWM clock divisor
 *   register fields use this kind of value.  The return value of this
 *   function can be converted into a PWM clock register value or an absolute
 *   prescaler value by applying the following operations (macros defined in
 *   chip/sam_pwm.h):
 *
 *   This function selects the prescaler value that allows the largest, valid
 *   divider value.  This may not be optimal in all cases, but in general
 *   should provide a reasonable frequency value.  The frequency is given by:
 *
 *     frequency = MCK / prescaler / div
 *
 *   The divider has a range of 1-255.  Pick smallest prescaler such that:
 *
 *     prescaler = MCK / frequency / div < 256
 *
 *   Example usage given:
 *     unsigned int prelog2;
 *     unsigned int prescaler;
 *     uint32_t regbits;
 *
 *   For clock A:
 *     prelog2   = pwm_clk_prescaler_log2(BOARD_MCK_FREQUENCY,
 *                                    CONFIG_SAMA5_PWM_CLKA_FREQUENCY )
 *     regbits   = PWM_CLK_PREA_DIV(prelog2);
 *     prescaler = (1 << prelog2)
 *
 *   For clock B:
 *     prelog2   = pwm_clk_prescaler_log2(BOARD_MCK_FREQUENCY,
 *                                    CONFIG_SAMA5_PWM_CLKB_FREQUENCY )
 *     regbits   = PWM_CLK_PREB_DIV(prelog2);
 *     prescaler = (1 << prelog2)
 *
 * Input Parameters:
 *     mck  - The main clock frequency
 *     fclk - The desired clock A or B frequency
 *
 * Returned Value:
 *   The select value of log2(prescaler) in the range 0-10 corresponding to
 *   the actual prescaler value in the range 1-1024.
 *
 ****************************************************************************/

static unsigned int pwm_clk_prescaler_log2(uint32_t mck, uint32_t fclk)
{
  uint32_t unscaled;
  unsigned int prelog2;

  unscaled = mck / fclk;
  prelog2  = 0;

  /* Loop, incrementing the log2(prescaler) value.  Exit with either:
   *
   *   1) unscaled < 256 and prelog2 <= 10, or with
   *   2) unscaled >= 256 and prelog2 == 10
   */

  while (unscaled >= 256 && prelog2 < 10)
    {
      unscaled >>= 1;
      prelog2++;
    }

  DEBUGASSERT(unscaled < 256);
  return prelog2;
}

/****************************************************************************
 * Name: pwm_clk_divider
 *
 * Description:
 *   Given that we have already selected the prescaler value, select the
 *   divider in the range of 1 through 255.  The CLKA/B frequency is
 *   determined by both the prescaler and divider values:
 *
 *   frequency = MCK / prescaler / div
 *
 * Then:
 *
 *   div = MCK / prescaler / frequency
 *
 * Input Parameters:
 *     mck     - The main clock frequency
 *     fclk    - The desired clock A or B frequency
 *     prelog2 - The log2(prescaler) value previously selected by
 *               pwm_prescale_log2().
 *
 * Returned Value:
 *   The select value of log2(prescaler) in the range 0-10 corresponding to
 *   the actual prescaler value in the range 1-1024.
 *
 ****************************************************************************/

static unsigned int pwm_clk_divider(uint32_t mck, uint32_t fclk,
                                    unsigned int prelog2)
{
  uint32_t div = (mck >> prelog2) / fclk;

  if (div < 1)
    {
      div = 1;
    }
  else if (div > 255)
    {
      div = 255;
    }

  return div;
}

/****************************************************************************
 * Name: pwm_clk_frequency
 *
 * Description:
 *   Given that we have already selected the prescaler value and calculated
 *   the corresponding divider, the result clock frequency is give by:
 *
 *   frequency = MCK / prescaler / div
 *
 * Input Parameters:
 *     mck     - The main clock frequency
 *     prelog2 - The log2(prescaler) value previously selected by
 *               pwm_prescale_log2().
 *     div     - The divider previously calculated from pwm_clk_divider().
 *
 * Returned Value:
 *   The select value of log2(prescaler) in the range 0-10 corresponding to
 *   the actual prescaler value in the range 1-1024.
 *
 ****************************************************************************/

static uint32_t pwm_clk_frequency(uint32_t mck, unsigned int prelog2,
                                  unsigned int div)
{
  return (mck >> prelog2) / div;
}

/****************************************************************************
 * Name: pwm_resetpins
 *
 * Description:
 *   Lower-half logic may support platform-specific ioctl commands
 *
 * Input Parameters:
 *   chan - A reference to the PWM channel instance
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void pwm_resetpins(struct sam_pwm_chan_s *chan)
{
  if (chan->ohpincfg)
    {
      sam_configpio(PWM_MKINPUT(chan->ohpincfg));
    }

  if (chan->olpincfg)
    {
      sam_configpio(PWM_MKINPUT(chan->olpincfg));
    }

  if (chan->fipincfg)
    {
      sam_configpio(PWM_MKINPUT(chan->fipincfg));
    }
}

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

/****************************************************************************
 * Name: sam_pwminitialize
 *
 * Description:
 *   Initialize one PWM channel for use with the upper_level PWM driver.
 *
 * Input Parameters:
 *   channel - A number identifying the PWM channel use.
 *
 * Returned Value:
 *   On success, a pointer to the SAMA5 lower half PWM driver is returned.
 *   NULL is returned on any failure.
 *
 ****************************************************************************/

struct pwm_lowerhalf_s *sam_pwminitialize(int channel)
{
  struct sam_pwm_chan_s *chan;
  uint32_t regval;

  pwminfo("Channel %d\n", channel);

  switch (channel)
    {
#ifdef CONFIG_SAMA5_PWM_CHAN0
      case 0:

        /* Select the Channel 0 interface */

        chan = &g_pwm_chan0;
        break;
#endif

#ifdef CONFIG_SAMA5_PWM_CHAN1
      case 1:

        /* Select the Channel 1 interface */

        chan = &g_pwm_chan1;
        break;
#endif

#ifdef CONFIG_SAMA5_PWM_CHAN2
      case 2:

        /* Select the Channel 2 interface */

        chan = &g_pwm_chan2;
        break;
#endif

#ifdef CONFIG_SAMA5_PWM_CHAN3
      case 3:

        /* Select the Channel 3 interface */

        chan = &g_pwm_chan3;
        break;
#endif

      default:
        pwmerr("ERROR: Channel invalid or not configured: %d\n", channel);
        return NULL;
    }

  /* Have we already performed the one time initialization of the overall
   * PWM peripheral?
   */

  if (!g_pwm.initialized)
    {
#if defined(CONFIG_SAMA5_PWM_CLKA) || defined(CONFIG_SAMA5_PWM_CLKB)
      uint32_t mck;
      unsigned int prelog2;
      unsigned int div;
#endif

      /* Enable the PWM peripheral clock */

      sam_pwm_enableclk();

#if defined(CONFIG_SAMA5_PWM_CLKA) || defined(CONFIG_SAMA5_PWM_CLKB)
      mck      = BOARD_MCK_FREQUENCY;
#endif
#ifdef CONFIG_SAMA5_PWM_CLKA
      /* Set clock A configuration */

      prelog2  = pwm_clk_prescaler_log2(mck,
                                        CONFIG_SAMA5_PWM_CLKA_FREQUENCY);
      div      = pwm_clk_divider(mck,
                                 CONFIG_SAMA5_PWM_CLKA_FREQUENCY, prelog2);
      regval   = (PWM_CLK_DIVA(div) | PWM_CLK_PREA_DIV(prelog2));
#else
      regval   = 0;
#endif

#ifdef CONFIG_SAMA5_PWM_CLKB
      /* Set clock B configuration */

      prelog2  = pwm_clk_prescaler_log2(mck,
                                        CONFIG_SAMA5_PWM_CLKB_FREQUENCY);
      div      = pwm_clk_divider(mck,
                                 CONFIG_SAMA5_PWM_CLKA_FREQUENCY, prelog2);
      regval  |= (PWM_CLK_DIVB(div) | PWM_CLK_PREB_DIV(prelog2));
#endif

      pwm_putreg(chan, SAM_PWM_CLK_OFFSET, regval);

      /* Disable all PWM interrupts at the PWM peripheral */

      pwm_putreg(chan, SAM_PWM_IDR1_OFFSET, PWM_INT1_ALL);
      pwm_putreg(chan, SAM_PWM_IDR2_OFFSET, PWM_INT2_ALL);

      /* Attach the PWM interrupt handler */

#ifdef PWM_INTERRUPTS
      ret = irq_attach(SAM_IRQ_PWM, pwm_interrupt, NULL);
      if (ret < 0)
        {
          pwmerr("ERROR: Failed to attach IRQ%d\n", channel);
          return NULL;
        }
#endif

      /* Clear any pending PWM interrupts */

      pwm_getreg(chan, SAM_PWM_ISR1_OFFSET);
      pwm_getreg(chan, SAM_PWM_ISR2_OFFSET);

      /* Enable PWM interrupts at the AIC */

#ifdef PWM_INTERRUPTS
      up_enable_irq(SAM_IRQ_PWM);
#endif

      /* Now were are initialized */

      g_pwm.initialized = true;
      pwm_dumpregs(chan, "After Initialization");
    }

  /* Configure all pins for this channel as inputs */

  pwm_resetpins(chan);

  /* Return the lower-half driver instance for this channel */

  return (struct pwm_lowerhalf_s *)chan;
}

#endif /* CONFIG_SAMA5_PWM */