summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGregory Nutt <gnutt@nuttx.org>2013-11-06 10:55:12 -0600
committerGregory Nutt <gnutt@nuttx.org>2013-11-06 10:55:12 -0600
commit270abfc7cfb4c3e8649124aff2dd5f99a03a908b (patch)
treec2485829f585842918c01d533ebaef009eaa92e7
parent2bfd17c0a201e72a9463c06169d5eaa8bcf277a3 (diff)
downloadnuttx-270abfc7cfb4c3e8649124aff2dd5f99a03a908b.tar.gz
nuttx-270abfc7cfb4c3e8649124aff2dd5f99a03a908b.tar.bz2
nuttx-270abfc7cfb4c3e8649124aff2dd5f99a03a908b.zip
SAMA5 PWM: Add first cut at PWM driver
-rw-r--r--nuttx/ChangeLog6
-rw-r--r--nuttx/arch/arm/src/sama5/chip/sam_pwm.h5
-rw-r--r--nuttx/arch/arm/src/sama5/sam_pwm.c1353
-rw-r--r--nuttx/arch/arm/src/sama5/sam_pwm.h109
4 files changed, 1471 insertions, 2 deletions
diff --git a/nuttx/ChangeLog b/nuttx/ChangeLog
index e92852bdf..e2ac1adbe 100644
--- a/nuttx/ChangeLog
+++ b/nuttx/ChangeLog
@@ -5970,4 +5970,8 @@
logic cause re-use of a stale pointer. Changed to a two pass
uninitialization for the case of the composite driver: Memory
resources are not freed until the second uninitialization pass.
- From David Sidrane (2011-1105).
+ From David Sidrane (2011-11-5).
+ * arch/arm/src/sama5/sam_pwm.c and .h: Add PWM driver for SAMA5
+ untested on initial checkout (not even incorporated in to build
+ system) (2013-11-6).
+
diff --git a/nuttx/arch/arm/src/sama5/chip/sam_pwm.h b/nuttx/arch/arm/src/sama5/chip/sam_pwm.h
index 5e3c09827..308ef1e4f 100644
--- a/nuttx/arch/arm/src/sama5/chip/sam_pwm.h
+++ b/nuttx/arch/arm/src/sama5/chip/sam_pwm.h
@@ -286,6 +286,7 @@
#define PWM_INT1_CHID(n) (1 << (n)) /* Bits 0-3: Counter Event on Channel n Interrupt, n=0..3 */
#define PWM_INT1_FCHID(n) (1 << ((n)+16)) /* Bits 16-19: Fault Protection Trigger on Channel n Interrupt, n=0..3 */
+#define PWM_INT1_ALL (0x000f000f)
/* PWM Sync Channels Mode Register */
@@ -339,6 +340,7 @@
# define PWM_INT2_CMPU5 (1 << 21) /* Bit 21: Comparison 5 Update Interrupt Enable */
# define PWM_INT2_CMPU6 (1 << 22) /* Bit 22: Comparison 6 Update Interrupt Enable */
# define PWM_INT2_CMPU7 (1 << 23) /* Bit 23: Comparison 7 Update Interrupt Enable */
+#define PWM_INT2_ALL (0x00ffff09)
/* PWM Output Override Value Register */
@@ -583,7 +585,8 @@
#define PWM_CMR_CPRE_SHIFT (0) /* Bits 0-3: Channel Pre-scaler */
#define PWM_CMR_CPRE_MASK (15 << PWM_CMR_CPRE_SHIFT)
-# define PWM_CMR_CPRE_ MCK (0 << PWM_CMR_CPRE_SHIFT) /* Master clock */
+# define PWM_CMR_CPRE_MCKDIV(n) ((uint32_t)(n) << PWM_CMR_CPRE_SHIFT) /* Master clock */
+# define PWM_CMR_CPRE_MCKDIV1 (0 << PWM_CMR_CPRE_SHIFT) /* Master clock/2 */
# define PWM_CMR_CPRE_MCKDIV2 (1 << PWM_CMR_CPRE_SHIFT) /* Master clock/2 */
# define PWM_CMR_CPRE_MCKDIV4 (2 << PWM_CMR_CPRE_SHIFT) /* Master clock/4 */
# define PWM_CMR_CPRE_MCKDIV8 (3 << PWM_CMR_CPRE_SHIFT) /* Master clock/8 */
diff --git a/nuttx/arch/arm/src/sama5/sam_pwm.c b/nuttx/arch/arm/src/sama5/sam_pwm.c
new file mode 100644
index 000000000..6e81efcc4
--- /dev/null
+++ b/nuttx/arch/arm/src/sama5/sam_pwm.c
@@ -0,0 +1,1353 @@
+/****************************************************************************
+ * arch/arm/src/sama5/sam_pwm.c
+ *
+ * Copyright (C) 2013 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 <fixedmath.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 "sam_periphclks.h"
+#include "sam_pio.h"
+#include "sam_pwm.h"
+
+#ifdef CONFIG_SAMA5_PWM
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+/* Configuration ************************************************************/
+/* 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, what frequency? Select 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.
+ *
+ * frequency = MCK / prescaler / div
+ *
+ * Pick smallest prescaler such that:
+ *
+ * prescaler = MCK / frequency / div < 256
+ *
+ * Then:
+ *
+ * div = MCK / prescaler / frequency
+ *
+ * Calulcated Values
+ *
+ * CLKn_PRE = CLKn prescaler value
+ * PWM_CLK_PREn = CLKn prescaler register setting
+ * CLKn_DIV = CLKn divider value
+ * PWM_CLK_DIVn = CLKn divider register setting
+ * CLKn_FREQUENCY = Actual resulting CLKn frequency
+ */
+
+#ifdef CONFIG_SAMA5_PWM_CLKA
+
+# if !defined(CONFIG_SAMA5_PWM_CLKA_FREQUENCY)
+# error CONFIG_SAMA5_PWM_CLKA_FREQUENCY is not defined
+
+# elif (BOARD_MCK_FREQUENCY / CONFIG_SAMA5_PWM_CLKA_FREQUENCY) < 256
+# define PWM_CLK_PREA PWM_CLK_PREA_DIV1
+# define CLKA_PRE 1
+
+# elif (BOARD_MCK_FREQUENCY / 2 / CONFIG_SAMA5_PWM_CLKA_FREQUENCY) < 256
+# define PWM_CLK_PREA PWM_CLK_PREA_DIV2
+# define CLKA_PRE 2
+
+# elif (BOARD_MCK_FREQUENCY / 4 / CONFIG_SAMA5_PWM_CLKA_FREQUENCY) < 256
+# define PWM_CLK_PREA PWM_CLK_PREA_DIV4
+# define CLKA_PRE 4
+
+# elif (BOARD_MCK_FREQUENCY / 8 / CONFIG_SAMA5_PWM_CLKA_FREQUENCY) < 256
+# define PWM_CLK_PREA PWM_CLK_PREA_DIV8
+# define CLKA_PRE 8
+
+# elif (BOARD_MCK_FREQUENCY / 16 / CONFIG_SAMA5_PWM_CLKA_FREQUENCY) < 256
+# define PWM_CLK_PREA PWM_CLK_PREA_DIV16
+# define CLKA_PRE 16
+
+# elif (BOARD_MCK_FREQUENCY / 32 / CONFIG_SAMA5_PWM_CLKA_FREQUENCY) < 256
+# define PWM_CLK_PREA PWM_CLK_PREA_DIV32
+# define CLKA_PRE 32
+
+# elif (BOARD_MCK_FREQUENCY / 64 / CONFIG_SAMA5_PWM_CLKA_FREQUENCY) < 256
+# define PWM_CLK_PREA PWM_CLK_PREA_DIV64
+# define CLKA_PRE 64
+
+# elif (BOARD_MCK_FREQUENCY / 128 / CONFIG_SAMA5_PWM_CLKA_FREQUENCY) < 256
+# define PWM_CLK_PREA PWM_CLK_PREA_DIV128
+# define CLKA_PRE 128
+
+# elif (BOARD_MCK_FREQUENCY / 256 / CONFIG_SAMA5_PWM_CLKA_FREQUENCY) < 256
+# define PWM_CLK_PREA PWM_CLK_PREA_DIV256
+# define CLKA_PRE 256
+
+# elif (BOARD_MCK_FREQUENCY / 512 / CONFIG_SAMA5_PWM_CLKA_FREQUENCY) < 256
+# define PWM_CLK_PREA PWM_CLK_PREA_DIV512
+# define CLKA_PRE 512
+
+# elif (BOARD_MCK_FREQUENCY / 1024 / CONFIG_SAMA5_PWM_CLKA_FREQUENCY) < 256
+# define PWM_CLK_PREA PWM_CLK_PREA_DIV1024
+# define CLKA_PRE 1024
+
+# else
+# error Cannot realize CONFIG_SAMA5_PWM_CLKA_FREQUENCY
+# endif
+
+# define CLKA_DIV (BOARD_MCK_FREQUENCY / CLKA_PRE / CONFIG_SAMA5_PWM_CLKA_FREQUENCY)
+# define CLKA_FREQUENCY (BOARD_MCK_FREQUENCY / CLKA_PRE / CLKA_DIV)
+# define PWM_CLK_DIVA PWM_CLK_DIVA(CLKA_DIV)
+
+#else
+# undef CONFIG_SAMA5_PWM_CLKA_FREQUENCY
+# define PWM_CLK_PREA PWM_CLK_PREA_DIV1
+# define PWM_CLK_DIVA PWM_CLK_DIVA_OFF
+#endif
+
+#ifdef CONFIG_SAMA5_PWM_CLKB
+
+# if !defined(CONFIG_SAMA5_PWM_CLKB_FREQUENCY)
+# error CONFIG_SAMA5_PWM_CLKB_FREQUENCY is not defined
+
+# elif (BOARD_MCK_FREQUENCY / CONFIG_SAMA5_PWM_CLKB_FREQUENCY) < 256
+# define PWM_CLK_PREB PWM_CLK_PREB_DIV1
+# define CLKB_PRE 1
+
+# elif (BOARD_MCK_FREQUENCY / 2 / CONFIG_SAMA5_PWM_CLKB_FREQUENCY) < 256
+# define PWM_CLK_PREB PWM_CLK_PREB_DIV2
+# define CLKB_PRE 2
+
+# elif (BOARD_MCK_FREQUENCY / 4 / CONFIG_SAMA5_PWM_CLKB_FREQUENCY) < 256
+# define PWM_CLK_PREB PWM_CLK_PREB_DIV4
+# define CLKB_PRE 4
+
+# elif (BOARD_MCK_FREQUENCY / 8 / CONFIG_SAMA5_PWM_CLKB_FREQUENCY) < 256
+# define PWM_CLK_PREB PWM_CLK_PREB_DIV8
+# define CLKB_PRE 8
+
+# elif (BOARD_MCK_FREQUENCY / 16 / CONFIG_SAMA5_PWM_CLKB_FREQUENCY) < 256
+# define PWM_CLK_PREB PWM_CLK_PREB_DIV16
+# define CLKB_PRE 16
+
+# elif (BOARD_MCK_FREQUENCY / 32 / CONFIG_SAMA5_PWM_CLKB_FREQUENCY) < 256
+# define PWM_CLK_PREB PWM_CLK_PREB_DIV32
+# define CLKB_PRE 32
+
+# elif (BOARD_MCK_FREQUENCY / 64 / CONFIG_SAMA5_PWM_CLKB_FREQUENCY) < 256
+# define PWM_CLK_PREB PWM_CLK_PREB_DIV64
+# define CLKB_PRE 64
+
+# elif (BOARD_MCK_FREQUENCY / 128 / CONFIG_SAMA5_PWM_CLKB_FREQUENCY) < 256
+# define PWM_CLK_PREB PWM_CLK_PREB_DIV128
+# define CLKB_PRE 128
+
+# elif (BOARD_MCK_FREQUENCY / 256 / CONFIG_SAMA5_PWM_CLKB_FREQUENCY) < 256
+# define PWM_CLK_PREB PWM_CLK_PREB_DIV256
+# define CLKB_PRE 256
+
+# elif (BOARD_MCK_FREQUENCY / 512 / CONFIG_SAMA5_PWM_CLKB_FREQUENCY) < 256
+# define PWM_CLK_PREB PWM_CLK_PREB_DIV512
+# define CLKB_PRE 512
+
+# elif (BOARD_MCK_FREQUENCY / 1024 / CONFIG_SAMA5_PWM_CLKB_FREQUENCY) < 256
+# define PWM_CLK_PREB PWM_CLK_PREB_DIV1024
+# define CLKB_PRE 1024
+
+# else
+# error Cannot realize CONFIG_SAMA5_PWM_CLKB_FREQUENCY
+# endif
+
+# define CLKB_DIV (BOARD_MCK_FREQUENCY / CLKB_PRE / CONFIG_SAMA5_PWM_CLKB_FREQUENCY / )
+# define CLKB_FREQUENCY (BOARD_MCK_FREQUENCY / CLKB_PRE / CLKB_DIV)
+# define PWM_CLK_DIVB PWM_CLK_DIVB(CLKB_DIV)
+
+#else
+# undef CONFIG_SAMA5_PWM_CLKB_FREQUENCY
+# define PWM_CLK_PREB PWM_CLK_PREB_DIV1
+# define PWM_CLK_DIVB PWM_CLK_DIVB_OFF
+#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
+# SAMA5_PWM_CHAN0_MCKDIV_LOG2 = 0
+# elif CONFIG_SAMA5_PWM_CHAN0_MCKDIV == 2
+# SAMA5_PWM_CHAN0_MCKDIV_LOG2 = 1
+# elif CONFIG_SAMA5_PWM_CHAN0_MCKDIV == 4
+# SAMA5_PWM_CHAN0_MCKDIV_LOG2 = 2
+# elif CONFIG_SAMA5_PWM_CHAN0_MCKDIV == 8
+# SAMA5_PWM_CHAN0_MCKDIV_LOG2 = 3
+# elif CONFIG_SAMA5_PWM_CHAN0_MCKDIV == 16
+# SAMA5_PWM_CHAN0_MCKDIV_LOG2 = 4
+# elif CONFIG_SAMA5_PWM_CHAN0_MCKDIV == 32
+# SAMA5_PWM_CHAN0_MCKDIV_LOG2 = 5
+# elif CONFIG_SAMA5_PWM_CHAN0_MCKDIV == 64
+# SAMA5_PWM_CHAN0_MCKDIV_LOG2 = 6
+# elif CONFIG_SAMA5_PWM_CHAN0_MCKDIV == 128
+# SAMA5_PWM_CHAN0_MCKDIV_LOG2 = 7
+# elif CONFIG_SAMA5_PWM_CHAN0_MCKDIV == 256
+# SAMA5_PWM_CHAN0_MCKDIV_LOG2 = 8
+# elif CONFIG_SAMA5_PWM_CHAN0_MCKDIV == 512
+# SAMA5_PWM_CHAN0_MCKDIV_LOG2 = 9
+# elif CONFIG_SAMA5_PWM_CHAN0_MCKDIV == 1024
+# 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
+# SAMA5_PWM_CHAN1_MCKDIV_LOG2 = 0
+# elif CONFIG_SAMA5_PWM_CHAN1_MCKDIV == 2
+# SAMA5_PWM_CHAN1_MCKDIV_LOG2 = 1
+# elif CONFIG_SAMA5_PWM_CHAN1_MCKDIV == 4
+# SAMA5_PWM_CHAN1_MCKDIV_LOG2 = 2
+# elif CONFIG_SAMA5_PWM_CHAN1_MCKDIV == 8
+# SAMA5_PWM_CHAN1_MCKDIV_LOG2 = 3
+# elif CONFIG_SAMA5_PWM_CHAN1_MCKDIV == 16
+# SAMA5_PWM_CHAN1_MCKDIV_LOG2 = 4
+# elif CONFIG_SAMA5_PWM_CHAN1_MCKDIV == 32
+# SAMA5_PWM_CHAN1_MCKDIV_LOG2 = 5
+# elif CONFIG_SAMA5_PWM_CHAN1_MCKDIV == 64
+# SAMA5_PWM_CHAN1_MCKDIV_LOG2 = 6
+# elif CONFIG_SAMA5_PWM_CHAN1_MCKDIV == 128
+# SAMA5_PWM_CHAN1_MCKDIV_LOG2 = 7
+# elif CONFIG_SAMA5_PWM_CHAN1_MCKDIV == 256
+# SAMA5_PWM_CHAN1_MCKDIV_LOG2 = 8
+# elif CONFIG_SAMA5_PWM_CHAN1_MCKDIV == 512
+# SAMA5_PWM_CHAN1_MCKDIV_LOG2 = 9
+# elif CONFIG_SAMA5_PWM_CHAN1_MCKDIV == 1024
+# 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
+# SAMA5_PWM_CHAN2_MCKDIV_LOG2 = 0
+# elif CONFIG_SAMA5_PWM_CHAN2_MCKDIV == 2
+# SAMA5_PWM_CHAN2_MCKDIV_LOG2 = 1
+# elif CONFIG_SAMA5_PWM_CHAN2_MCKDIV == 4
+# SAMA5_PWM_CHAN2_MCKDIV_LOG2 = 2
+# elif CONFIG_SAMA5_PWM_CHAN2_MCKDIV == 8
+# SAMA5_PWM_CHAN2_MCKDIV_LOG2 = 3
+# elif CONFIG_SAMA5_PWM_CHAN2_MCKDIV == 16
+# SAMA5_PWM_CHAN2_MCKDIV_LOG2 = 4
+# elif CONFIG_SAMA5_PWM_CHAN2_MCKDIV == 32
+# SAMA5_PWM_CHAN2_MCKDIV_LOG2 = 5
+# elif CONFIG_SAMA5_PWM_CHAN2_MCKDIV == 64
+# SAMA5_PWM_CHAN2_MCKDIV_LOG2 = 6
+# elif CONFIG_SAMA5_PWM_CHAN2_MCKDIV == 128
+# SAMA5_PWM_CHAN2_MCKDIV_LOG2 = 7
+# elif CONFIG_SAMA5_PWM_CHAN2_MCKDIV == 256
+# SAMA5_PWM_CHAN2_MCKDIV_LOG2 = 8
+# elif CONFIG_SAMA5_PWM_CHAN2_MCKDIV == 512
+# SAMA5_PWM_CHAN2_MCKDIV_LOG2 = 9
+# elif CONFIG_SAMA5_PWM_CHAN2_MCKDIV == 1024
+# 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
+# SAMA5_PWM_CHAN3_MCKDIV_LOG2 = 0
+# elif CONFIG_SAMA5_PWM_CHAN3_MCKDIV == 2
+# SAMA5_PWM_CHAN3_MCKDIV_LOG2 = 1
+# elif CONFIG_SAMA5_PWM_CHAN3_MCKDIV == 4
+# SAMA5_PWM_CHAN3_MCKDIV_LOG2 = 2
+# elif CONFIG_SAMA5_PWM_CHAN3_MCKDIV == 8
+# SAMA5_PWM_CHAN3_MCKDIV_LOG2 = 3
+# elif CONFIG_SAMA5_PWM_CHAN3_MCKDIV == 16
+# SAMA5_PWM_CHAN3_MCKDIV_LOG2 = 4
+# elif CONFIG_SAMA5_PWM_CHAN3_MCKDIV == 32
+# SAMA5_PWM_CHAN3_MCKDIV_LOG2 = 5
+# elif CONFIG_SAMA5_PWM_CHAN3_MCKDIV == 64
+# SAMA5_PWM_CHAN3_MCKDIV_LOG2 = 6
+# elif CONFIG_SAMA5_PWM_CHAN3_MCKDIV == 128
+# SAMA5_PWM_CHAN3_MCKDIV_LOG2 = 7
+# elif CONFIG_SAMA5_PWM_CHAN3_MCKDIV == 256
+# SAMA5_PWM_CHAN3_MCKDIV_LOG2 = 8
+# elif CONFIG_SAMA5_PWM_CHAN3_MCKDIV == 512
+# SAMA5_PWM_CHAN3_MCKDIV_LOG2 = 9
+# elif CONFIG_SAMA5_PWM_CHAN3_MCKDIV == 1024
+# 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)
+
+/* 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
+# else
+# define pwmlldbg(x...)
+# define pwmllvdbg(x...)
+# endif
+#else
+# define pwmdbg(x...)
+# define pwmlldbg(x...)
+# define pwmvdbg(x...)
+# define pwmllvdbg(x...)
+# define pwm_dumpgpio(p,m)
+#endif
+
+/****************************************************************************
+ * 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(FAR struct sam_dev_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(FAR struct sam_pwm_chan_s *chan, int offset);
+static void pwm_putreg(FAR struct sam_pwm_chan_s *chan, int offset, uint32_t regval);
+
+#if defined(CONFIG_DEBUG_PWM) && defined(CONFIG_DEBUG_VERBOSE)
+static void pwm_dumpregs(FAR struct sam_pwm_chan_s *chan, FAR const char *msg);
+#else
+# define pwm_dumpregs(chan,msg)
+#endif
+
+/* PWM Interrupts */
+
+#ifdef PWM_INTERRUPTS
+static int pwm_interrupt(int irq, void *context);
+#endif
+
+/* PWM driver methods */
+
+static int pwm_setup(FAR struct pwm_lowerhalf_s *dev);
+static int pwm_shutdown(FAR struct pwm_lowerhalf_s *dev);
+static int pwm_start(FAR struct pwm_lowerhalf_s *dev,
+ FAR const struct pwm_info_s *info);
+static int pwm_stop(FAR struct pwm_lowerhalf_s *dev);
+static int pwm_ioctl(FAR struct pwm_lowerhalf_s *dev,
+ int cmd, unsigned long arg);
+
+/* Initialization */
+
+static void pwm_resetpins(FAR 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 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,
+ .pwm = &g_pwm,
+ .channel = 0,
+ .base = SAM_PWM_CHAN_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,
+ .pwm = &g_pwm,
+ .channel = 1,
+ .base = SAM_PWM_CHAN_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_CHAN0_OUTPUTH
+ .ohpincfg = PIO_PWM1_H,
+#endif
+#ifdef CONFIG_SAMA5_PWM_CHAN0_OUTPUTL
+ .olpincfg = PIO_PWM1_L,
+#endif
+#ifdef CONFIG_SAMA5_PWM_CHAN0_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,
+ .pwm = &g_pwm,
+ .channel = 2,
+ .base = SAM_PWM_CHAN_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_CHAN0_OUTPUTH
+ .ohpincfg = PIO_PWM2_H,
+#endif
+#ifdef CONFIG_SAMA5_PWM_CHAN0_OUTPUTL
+ .olpincfg = PIO_PWM2_L,
+#endif
+#ifdef CONFIG_SAMA5_PWM_CHAN0_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,
+ .pwm = &g_pwm,
+ .channel = 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_CHAN0_OUTPUTH
+ .ohpincfg = PIO_PWM3_H,
+#endif
+#ifdef CONFIG_SAMA5_PWM_CHAN0_OUTPUTL
+ .olpincfg = PIO_PWM3_L,
+#endif
+#ifdef CONFIG_SAMA5_PWM_CHAN0_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_chan_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 */
+
+ lldbg("...[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)
+{
+ uintptr_t regaddr;
+ uint32_t regval;
+
+#ifdef PWM_SINGLE
+ regaddr = SAM_PWMC_VBASE + offset
+#else
+ struct sam_pwm_chan_s *pwm = chan->pwm;
+ regaddr = pwm->base + offset;
+#endif
+
+ regval = getreg32(regaddr);
+
+#ifdef CONFIG_SAMA5_PWM_REGDEBUG
+ if (pwm_checkreg(chan->pwm, false, regval, regaddr))
+ {
+ lldbg("%08x->%08x\n", regaddr, regval);
+ }
+#endif
+
+ return regval;
+}
+
+/****************************************************************************
+ * 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
+ *
+ ****************************************************************************/
+
+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
+ if (pwm_checkreg(chan->pwm, false, regval, regaddr))
+ {
+ lldbg("%08x->%08x\n", regaddr, regval);
+ }
+#endif
+
+ return regval;
+}
+
+/****************************************************************************
+ * 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)
+{
+ uintptr_t regaddr;
+
+#ifdef PWM_SINGLE
+ regaddr = SAM_PWMC_VBASE + offset
+#else
+ struct sam_pwm_chan_s *pwm = chan->pwm;
+ regaddr = pwm->base + offset;
+#endif
+
+#ifdef CONFIG_SAMA5_PWM_REGDEBUG
+ if (pwm_checkreg(chan->pwm, true, regval, regaddr))
+ {
+ lldbg("%08x<-%08x\n", regaddr, regval);
+ }
+#endif
+
+ putreg32(regval, regaddr);
+}
+
+/****************************************************************************
+ * 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
+ if (pwm_checkreg(chan->pwm, true, regval, regaddr))
+ {
+ lldbg("%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
+ *
+ ****************************************************************************/
+
+#if defined(CONFIG_DEBUG_PWM) && defined(CONFIG_DEBUG_VERBOSE)
+static void pwm_dumpregs(struct sam_pwm_chan_s *chan, FAR const char *msg)
+{
+ pwmvdbg("PWM: %s\n", msg);
+ pwmvdbg(" CLK: %04x SR: %04x IMR1: %04x ISR1: %04x\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));
+ pwmvdbg(" SCM: %04x SCUC: %04x SCUP: %04x IMR2: %04x\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));
+ pwmvdbg(" ISR2: %04x OOV: %04x OS: %04x FMR: %04x\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));
+ pwmvdbg(" FSR: %04x FPV: %04x FPE: %04x ELMR0: %04x\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));
+ pwmvdbg("ELMR1: %04x SMMR: %04x WPSR: %04x\n",
+ pwm_getreg(chan, SAM_PWM_ELMR1_OFFSET),
+ pwm_getreg(chan, SAM_PWM_SMMR_OFFSET),
+ pwm_getreg(chan, SAM_PWM_WPSR_OFFSET));
+ pwmvdbg("CMPV0: %04x CMPM0: %04x CMPV1: %04x CMPM1: %04x\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));
+ pwmvdbg("CMPV2: %04x CMPM2: %04x CMPV3: %04x CMPM3: %04x\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));
+ pwmvdbg("CMPV4: %04x CMPM4: %04x CMPV5: %04x CMPM5: %04x\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));
+ pwmvdbg("CMPV6: %04x CMPM6: %04x CMPV7: %04x CMPM7: %04x\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));
+ pwmvdbg("Channel %d: %s\n", chan->channel, msg);
+ pwmvdbg(" CMR: %04x CDTY: %04x CPRD: %04x CCNT: %04x\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));
+ pwmvdbg(" CT: %04x\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)
+{
+ /* 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(FAR struct pwm_lowerhalf_s *dev)
+{
+ FAR struct sam_pwm_chan_s *chan = (FAR struct sam_pwm_chan_s *)dev;
+
+ pwmvdbg("Channel %d: H=%08x L=%08x FI=%08x\n",
+ chan->channel, chan->ohpincfg, chan->olpincfg, chan->fipincfg);
+
+ /* Configure selected PWM pins */
+
+ if (chan->ohpincfg)
+ {
+ (void)sam_configpio(chan->ohpincfg);
+ }
+
+ if (chan->olpincfg)
+ {
+ (void)sam_configpio(chan->olpincfg);
+ }
+
+ if (chan->fipincfg)
+ {
+ (void)sam_configpio(chan->fipincfg);
+ }
+
+ pwm_dumpgpio(chan->pincfg, "PWM setup");
+ 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(FAR struct pwm_lowerhalf_s *dev)
+{
+ FAR struct sam_pwm_chan_s *chan = (FAR struct sam_pwm_chan_s *)dev;
+ uint32_t pincfg;
+
+ pwmvdbg("Channel %d pincfg: %08x\n", chan->channel, chan->pincfg);
+
+ /* 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(FAR struct pwm_lowerhalf_s *dev,
+ FAR const struct pwm_info_s *info)
+{
+ FAR struct sam_pwm_chan_s *chan = (FAR struct sam_pwm_chan_s *)dev;
+ uint32_t regval;
+ uint32_t cprd;
+ uint32_t srcfreq;
+
+ /* Disable the channel (should already be disabled) */
+
+ sam_putreg(priv, SAM_PWM_DIS_OFFSET, PWM_CHID(chan->channel));
+
+ /* Determine the clock source */
+
+ switch (chan->clksrc)
+ {
+ case PWM_CLKSRC_MCK:
+ regval = PWM_CMR_CPRE_MCKDIV(SAMA5_PWM_CHAN0_MCKDIV_LOG2);
+ fsrc = BOARD_MCK_FREQUENCY >> SAMA5_PWM_CHAN0_MCKDIV_LOG2;
+ break;
+
+ case PWM_CLKSRC_CLKA:
+ regval = PWM_CMR_CPRE_CLKA;
+ fsrc = CLKA_FREQUENCY;
+ break;
+
+ case PWM_CLKSRC_CLKB:
+ regval = PWM_CMR_CPRE_CLKB;
+ fsrc = CLKB_FREQUENCY;
+ break;
+
+ default:
+ pwmdbg("ERROR: Bad clock source value: %d\n", chan->clksrc);
+ return -EINVAL;
+ }
+
+ /* Configure the channel */
+
+ sam_chan_putreg(priv, 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 = (info->frequency + (fsrc >> 1)) / fsrc;
+ sam_chan_putreg(priv, 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 could cause the duty value to exceed CPRD */
+
+ regval = cprd;
+ }
+
+ sam_chan_putreg(priv, SAM_PWM_CDTY_OFFSET, regval);
+
+ /* Enable the channel */
+
+ sam_putreg(priv, SAM_PWM_ENA_OFFSET, PWM_CHID(chan->channel));
+ 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(FAR struct pwm_lowerhalf_s *dev)
+{
+ FAR struct sam_pwm_chan_s *chan = (FAR struct sam_pwm_chan_s *)dev;
+
+ pwmvdbg("Channel %d\n", chan->channel);
+
+ /* Disable further PWM interrupts from this channel */
+
+ sam_putreg(chan, SAM_PWM_IDR1_OFFSET,
+ PWM_INT1_CHID(chan->channel) | PWM_INT1_FCHID(chan->channel));
+
+ /* Disable the channel */
+
+ sam_putreg(priv, 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(FAR struct pwm_lowerhalf_s *dev, int cmd, unsigned long arg)
+{
+#ifdef CONFIG_DEBUG_PWM
+ FAR struct sam_pwm_chan_s *chan = (FAR struct sam_pwm_chan_s *)dev;
+
+ /* There are no platform-specific ioctl commands */
+
+ pwmvdbg("Channel %d\n", chan->channel);
+#endif
+ return -ENOTTY;
+}
+
+/****************************************************************************
+ * 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 void pwm_resetpins(FAR struct sam_pwm_chan_s *chan)
+{
+ if (chan->ohpincfg)
+ {
+ (void)sam_configpio(PWM_MKINPUT(chan->ohpincfg));
+ }
+
+ if (chan->olpincfg)
+ {
+ (void)sam_configpio(PWM_MKINPUT(chan->olpincfg));
+ }
+
+ if (chan->fipincfg)
+ {
+ (void)sam_configpio(PWM_MKINPUT(chan->fipincfg));
+ }
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: sam_pwminitialize
+ *
+ * Description:
+ * Initialize one timer 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.
+ *
+ ****************************************************************************/
+
+FAR struct pwm_lowerhalf_s *sam_pwminitialize(int channel)
+{
+ FAR struct sam_pwm_chan_s *chan;
+ uint32_t regval;
+
+ pwmvdbg("Channel %d\n", channel);
+
+ switch (timer)
+ {
+#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:
+ pwmdbg("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)
+ {
+ /* Enable the PWM peripheral clock */
+
+ sam_pwm_enableclks();
+
+ /* Set clock A and clock B */
+
+ regval = PWM_CLK_PREA | PWM_CLK_DIVA | PWM_CLK_PREB | PWM_CLK_DIVB;
+ sam_putreg(chan, SAM_PWM_CLK_OFFSET, regval);
+
+ /* Disable all PWM interrupts at the PWM peripheral */
+
+ sam_putreg(chan, SAM_PWM_IDR1_OFFSET, PWM_INT1_ALL);
+ sam_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);
+ if (ret < 0)
+ {
+ pwmdbg("ERROR: Failed to attach IRQ%d\n", channel);
+ return NULL;
+
+ }
+#endif
+
+ /* Clear any pending PWM interrupts */
+
+ (void)sam_getreg(chan, SAM_PWM_ISR1_OFFSET);
+ (void)sam_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;
+ }
+
+ /* Configure all pins for this channel as inputs */
+
+ pwm_resetpins(chan);
+
+ /* Return the lower-half driver instance for this channel */
+
+ return (FAR struct pwm_lowerhalf_s *)chan;
+}
+
+#endif /* CONFIG_SAMA5_PWM */
diff --git a/nuttx/arch/arm/src/sama5/sam_pwm.h b/nuttx/arch/arm/src/sama5/sam_pwm.h
new file mode 100644
index 000000000..43a5f47bb
--- /dev/null
+++ b/nuttx/arch/arm/src/sama5/sam_pwm.h
@@ -0,0 +1,109 @@
+/************************************************************************************
+ * arch/arm/src/sama5/sam_pwm.h
+ *
+ * Copyright (C) 2013 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.
+ *
+ ************************************************************************************/
+
+#ifndef __ARCH_ARM_SRC_SAMA5_SAM_PWM_H
+#define __ARCH_ARM_SRC_SAMA5_SAM_PWM_H
+
+/************************************************************************************
+ * Included Files
+ ************************************************************************************/
+
+#include <nuttx/config.h>
+
+#include "chip.h"
+
+#ifdef CONFIG_SAMA5_PWM
+
+/************************************************************************************
+ * Pre-processor Definitions
+ ************************************************************************************/
+/* Configuration ********************************************************************/
+
+/* Do we have any PWM channels enabled? If not, then why is the PWM enabled? */
+
+#if !defined(CONFIG_SAMA5_PWM_CHAN0) && !defined(CONFIG_SAMA5_PWM_CHAN1) && \
+ !defined(CONFIG_SAMA5_PWM_CHAN2) && !defined(CONFIG_SAMA5_PWM_CHAN3)
+# error "No PWM channels configured"
+# undef CONFIG_SAMA5_PWM
+#endif
+
+/************************************************************************************
+ * Public Types
+ ************************************************************************************/
+
+/************************************************************************************
+ * Public Data
+ ************************************************************************************/
+
+#ifndef __ASSEMBLY__
+
+#undef EXTERN
+#if defined(__cplusplus)
+#define EXTERN extern "C"
+extern "C"
+{
+#else
+#define EXTERN extern
+#endif
+
+/************************************************************************************
+ * Public Functions
+ ************************************************************************************/
+
+/****************************************************************************
+ * Name: sam_pwminitialize
+ *
+ * Description:
+ * Initialize one timer 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.
+ *
+ ****************************************************************************/
+
+FAR struct pwm_lowerhalf_s *sam_pwminitialize(int channel);
+
+#undef EXTERN
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* __ASSEMBLY__ */
+#endif /* CONFIG_SAMA5_PWM */
+#endif /* __ARCH_ARM_SRC_SAMA5_SAM_PWM_H */