| /**************************************************************************** |
| * 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 */ |