/**************************************************************************** * arch/arm/src/sama5/sam_pwm.c * * Copyright (C) 2013 Gregory Nutt. All rights reserved. * Author: Gregory Nutt * * 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 #include #include #include #include #include #include #include #include #include "chip/sam_pinmap.h" #include #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 CLKA_PRE_BITS PWM_CLK_PREA_DIV1 # define CLKA_PRE 1 # elif (BOARD_MCK_FREQUENCY / 2 / CONFIG_SAMA5_PWM_CLKA_FREQUENCY) < 256 # define CLKA_PRE_BITS PWM_CLK_PREA_DIV2 # define CLKA_PRE 2 # elif (BOARD_MCK_FREQUENCY / 4 / CONFIG_SAMA5_PWM_CLKA_FREQUENCY) < 256 # define CLKA_PRE_BITS PWM_CLK_PREA_DIV4 # define CLKA_PRE 4 # elif (BOARD_MCK_FREQUENCY / 8 / CONFIG_SAMA5_PWM_CLKA_FREQUENCY) < 256 # define CLKA_PRE_BITS PWM_CLK_PREA_DIV8 # define CLKA_PRE 8 # elif (BOARD_MCK_FREQUENCY / 16 / CONFIG_SAMA5_PWM_CLKA_FREQUENCY) < 256 # define CLKA_PRE_BITS PWM_CLK_PREA_DIV16 # define CLKA_PRE 16 # elif (BOARD_MCK_FREQUENCY / 32 / CONFIG_SAMA5_PWM_CLKA_FREQUENCY) < 256 # define CLKA_PRE_BITS PWM_CLK_PREA_DIV32 # define CLKA_PRE 32 # elif (BOARD_MCK_FREQUENCY / 64 / CONFIG_SAMA5_PWM_CLKA_FREQUENCY) < 256 # define CLKA_PRE_BITS PWM_CLK_PREA_DIV64 # define CLKA_PRE 64 # elif (BOARD_MCK_FREQUENCY / 128 / CONFIG_SAMA5_PWM_CLKA_FREQUENCY) < 256 # define CLKA_PRE_BITS PWM_CLK_PREA_DIV128 # define CLKA_PRE 128 # elif (BOARD_MCK_FREQUENCY / 256 / CONFIG_SAMA5_PWM_CLKA_FREQUENCY) < 256 # define CLKA_PRE_BITS PWM_CLK_PREA_DIV256 # define CLKA_PRE 256 # elif (BOARD_MCK_FREQUENCY / 512 / CONFIG_SAMA5_PWM_CLKA_FREQUENCY) < 256 # define CLKA_PRE_BITS PWM_CLK_PREA_DIV512 # define CLKA_PRE 512 # elif (BOARD_MCK_FREQUENCY / 1024 / CONFIG_SAMA5_PWM_CLKA_FREQUENCY) < 256 # define CLKA_PRE_BITS 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 CLKA_DIV_BITS PWM_CLK_DIVA(CLKA_DIV) #else # undef CONFIG_SAMA5_PWM_CLKA_FREQUENCY # define CLKA_PRE_BITS PWM_CLK_PREA_DIV1 # define CLKA_DIV_BITS 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 CLKB_PRE_BITS PWM_CLK_PREB_DIV1 # define CLKB_PRE 1 # elif (BOARD_MCK_FREQUENCY / 2 / CONFIG_SAMA5_PWM_CLKB_FREQUENCY) < 256 # define CLKB_PRE_BITS PWM_CLK_PREB_DIV2 # define CLKB_PRE 2 # elif (BOARD_MCK_FREQUENCY / 4 / CONFIG_SAMA5_PWM_CLKB_FREQUENCY) < 256 # define CLKB_PRE_BITS PWM_CLK_PREB_DIV4 # define CLKB_PRE 4 # elif (BOARD_MCK_FREQUENCY / 8 / CONFIG_SAMA5_PWM_CLKB_FREQUENCY) < 256 # define CLKB_PRE_BITS PWM_CLK_PREB_DIV8 # define CLKB_PRE 8 # elif (BOARD_MCK_FREQUENCY / 16 / CONFIG_SAMA5_PWM_CLKB_FREQUENCY) < 256 # define CLKB_PRE_BITS PWM_CLK_PREB_DIV16 # define CLKB_PRE 16 # elif (BOARD_MCK_FREQUENCY / 32 / CONFIG_SAMA5_PWM_CLKB_FREQUENCY) < 256 # define CLKB_PRE_BITS PWM_CLK_PREB_DIV32 # define CLKB_PRE 32 # elif (BOARD_MCK_FREQUENCY / 64 / CONFIG_SAMA5_PWM_CLKB_FREQUENCY) < 256 # define CLKB_PRE_BITS PWM_CLK_PREB_DIV64 # define CLKB_PRE 64 # elif (BOARD_MCK_FREQUENCY / 128 / CONFIG_SAMA5_PWM_CLKB_FREQUENCY) < 256 # define CLKB_PRE_BITS PWM_CLK_PREB_DIV128 # define CLKB_PRE 128 # elif (BOARD_MCK_FREQUENCY / 256 / CONFIG_SAMA5_PWM_CLKB_FREQUENCY) < 256 # define CLKB_PRE_BITS PWM_CLK_PREB_DIV256 # define CLKB_PRE 256 # elif (BOARD_MCK_FREQUENCY / 512 / CONFIG_SAMA5_PWM_CLKB_FREQUENCY) < 256 # define CLKB_PRE_BITS PWM_CLK_PREB_DIV512 # define CLKB_PRE 512 # elif (BOARD_MCK_FREQUENCY / 1024 / CONFIG_SAMA5_PWM_CLKB_FREQUENCY) < 256 # define CLKB_PRE_BITS 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 CLKB_DIV_BITS PWM_CLK_DIVB(CLKB_DIV) #else # undef CONFIG_SAMA5_PWM_CLKB_FREQUENCY # define CLKB_PRE_BITS PWM_CLK_PREB_DIV1 # define CLKB_DIV_BITS 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 # 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) /* 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...) #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_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(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 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(FAR 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 */ 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) { #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)) { lldbg("%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)) { lldbg("%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 * ****************************************************************************/ 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 { 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) { #ifdef PWM_SINGLE uintptr_t regaddr = SAM_PWMC_VBASE + offset; #ifdef CONFIG_SAMA5_PWM_REGDEBUG if (pwm_checkreg(&g_pwm, true, regval, regaddr)) { lldbg("%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)) { lldbg("%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 { 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: %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)); pwmvdbg(" 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)); pwmvdbg(" 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)); pwmvdbg(" 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)); pwmvdbg(" 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)); pwmvdbg(" 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)); pwmvdbg(" 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)); pwmvdbg(" 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)); pwmvdbg(" 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)); pwmvdbg("Channel %d: %s\n", chan->channel, msg); pwmvdbg(" 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)); pwmvdbg(" 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) { /* 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); } 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; pwmvdbg("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(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 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_CMR_CPRE_CLKA; fsrc = CLKA_FREQUENCY; break; #endif #ifdef CONFIG_SAMA5_PWM_CLKB case PWM_CLKSRC_CLKB: regval = PWM_CMR_CPRE_CLKB; fsrc = CLKB_FREQUENCY; break; #endif default: pwmdbg("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); pwmvdbg("Fsrc=%d cprd=%d cdty=%d\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(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 */ 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(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 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. * ****************************************************************************/ 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 (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: 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_enableclk(); /* Set clock A and clock B */ regval = (CLKA_PRE_BITS | CLKA_DIV_BITS | CLKB_PRE_BITS | CLKB_DIV_BITS); 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); if (ret < 0) { pwmdbg("ERROR: Failed to attach IRQ%d\n", channel); return NULL; } #endif /* Clear any pending PWM interrupts */ (void)pwm_getreg(chan, SAM_PWM_ISR1_OFFSET); (void)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 (FAR struct pwm_lowerhalf_s *)chan; } #endif /* CONFIG_SAMA5_PWM */