diff options
author | Gregory Nutt <gnutt@nuttx.org> | 2013-11-10 10:34:45 -0600 |
---|---|---|
committer | Gregory Nutt <gnutt@nuttx.org> | 2013-11-10 10:34:45 -0600 |
commit | a8e57973a0a0fd5e8ac867cf5f49a4ba47b25c1e (patch) | |
tree | 6c8b16220722d507d34e20469802deb070236ae9 | |
parent | 24973d755acae8154fc78534832342fba6f07bda (diff) | |
download | nuttx-a8e57973a0a0fd5e8ac867cf5f49a4ba47b25c1e.tar.gz nuttx-a8e57973a0a0fd5e8ac867cf5f49a4ba47b25c1e.tar.bz2 nuttx-a8e57973a0a0fd5e8ac867cf5f49a4ba47b25c1e.zip |
Kinetis KL: PWM driver from Alan Carvalho de Assis
-rw-r--r-- | nuttx/ChangeLog | 2 | ||||
-rw-r--r-- | nuttx/arch/arm/src/kl/Kconfig | 65 | ||||
-rw-r--r-- | nuttx/arch/arm/src/kl/Make.defs | 4 | ||||
-rw-r--r-- | nuttx/arch/arm/src/kl/kl_pwm.c | 743 | ||||
-rw-r--r-- | nuttx/arch/arm/src/kl/kl_pwm.h | 197 |
5 files changed, 1011 insertions, 0 deletions
diff --git a/nuttx/ChangeLog b/nuttx/ChangeLog index da3f62e13..e45f154e3 100644 --- a/nuttx/ChangeLog +++ b/nuttx/ChangeLog @@ -6013,3 +6013,5 @@ accesses. This driver in its current state is intended only to support I2C testing and would not be appropriate to used for any real driver application. (2013-11-10). + * arch/arm/src/kl/kl_pwm.c and .h: PWM driver for the Freescale + Kinetis KL family from Alan Carvalho de Assis (2013-11-10). diff --git a/nuttx/arch/arm/src/kl/Kconfig b/nuttx/arch/arm/src/kl/Kconfig index 808ca87bc..34f86b150 100644 --- a/nuttx/arch/arm/src/kl/Kconfig +++ b/nuttx/arch/arm/src/kl/Kconfig @@ -269,6 +269,71 @@ config KL_SYSTICK_CORECLK_DIV16 endchoice +config KL_TPM0_PWM + bool "TPM0 PWM" + default n + depends on KL_TPM0 + ---help--- + Reserve timer 0 for use by PWM + + Timer devices may be used for different purposes. One special purpose is + to generate modulated outputs for such things as motor control. If KL_TPM0 + is defined then THIS following may also be defined to indicate that + the timer is intended to be used for pulsed output modulation. + +config KL_TPM0_CHANNEL + int "TPM0 PWM Output Channel" + default 0 + range 0 5 + depends on KL_TPM0_PWM + ---help--- + If TIM0 is enabled for PWM usage, you also need specifies the timer output + channel {0,..,5} + +config KL_TPM1_PWM + bool "TPM1 PWM" + default n + depends on KL_TPM1 + ---help--- + Reserve timer 1 for use by PWM + + Timer devices may be used for different purposes. One special purpose is + to generate modulated outputs for such things as motor control. If KL_TPM1 + is defined then THIS following may also be defined to indicate that + the timer is intended to be used for pulsed output modulation. + +config KL_TPM1_CHANNEL + int "TPM1 PWM Output Channel" + default 0 + range 0 1 if ARCH_CHIP_MKL25Z128 + range 0 5 if !ARCH_CHIP_MKL25Z128 + depends on KL_TPM1_PWM + ---help--- + If TIM1 is enabled for PWM usage, you also need specifies the timer output + channel {0,..,5} + +config KL_TPM2_PWM + bool "TPM2 PWM" + default n + depends on KL_TPM2 + ---help--- + Reserve timer 2 for use by PWM + + Timer devices may be used for different purposes. One special purpose is + to generate modulated outputs for such things as motor control. If KL_TPM2 + is defined then THIS following may also be defined to indicate that + the timer is intended to be used for pulsed output modulation. + +config KL_TPM2_CHANNEL + int "TPM2 PWM Output Channel" + default 0 + range 0 1 if ARCH_CHIP_MKL25Z128 + range 0 5 if !ARCH_CHIP_MKL25Z128 + depends on KL_TPM2_PWM + ---help--- + If TIM2 is enabled for PWM usage, you also need specifies the timer output + channel {0,..,5} + comment "Kinetis GPIO Interrupt Configuration" config GPIO_IRQ diff --git a/nuttx/arch/arm/src/kl/Make.defs b/nuttx/arch/arm/src/kl/Make.defs index 9553a90db..085a72827 100644 --- a/nuttx/arch/arm/src/kl/Make.defs +++ b/nuttx/arch/arm/src/kl/Make.defs @@ -83,6 +83,10 @@ CHIP_CSRCS += kl_spi.c endif endif +ifeq ($(CONFIG_PWM),y) +CHIP_CSRCS += kl_pwm.c +endif + ifeq ($(CONFIG_DEBUG),y) CHIP_CSRCS += kl_dumpgpio.c endif diff --git a/nuttx/arch/arm/src/kl/kl_pwm.c b/nuttx/arch/arm/src/kl/kl_pwm.c new file mode 100644 index 000000000..0b675fe1c --- /dev/null +++ b/nuttx/arch/arm/src/kl/kl_pwm.c @@ -0,0 +1,743 @@ +/**************************************************************************** + * arch/arm/src/kl/kl_pwm.c + * + * Copyright (C) 2013 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <gnutt@nuttx.org> + * Alan Carvalho de Assis <acassis@gmail.com> + * + * 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 <debug.h> + +#include <nuttx/arch.h> +#include <nuttx/pwm.h> +#include <arch/board/board.h> + +#include "up_internal.h" +#include "up_arch.h" + +#include "chip.h" +#include "kl_pwm.h" +#include "kl_gpio.h" +#include "chip/kl_tpm.h" +#include "chip/kl_sim.h" + +/* This module then only compiles if there is at least one enabled timer + * intended for use with the PWM upper half driver. + */ + +#if defined(CONFIG_KL_TPM0_PWM) || defined(CONFIG_KL_TPM1_PWM) || \ + defined(CONFIG_KL_TPM2_PWM) + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ +/* PWM/Timer Definitions ****************************************************/ + +/* 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 +# define pwm_dumpgpio(p,m) kl_dumpgpio(p,m) +# else +# define pwmlldbg(x...) +# define pwmllvdbg(x...) +# define pwm_dumpgpio(p,m) +# endif +#else +# define pwmdbg(x...) +# define pwmlldbg(x...) +# define pwmvdbg(x...) +# define pwmllvdbg(x...) +# define pwm_dumpgpio(p,m) +#endif + +/**************************************************************************** + * Private Types + ****************************************************************************/ +/* This structure representst the state of one PWM timer */ + +struct kl_pwmtimer_s +{ + FAR const struct pwm_ops_s *ops; /* PWM operations */ + uint8_t tpmid; /* Timer/PWM Module ID {0,..,2} */ + uint8_t channel; /* Timer/PWM Module channel: {0,..5} */ + uint32_t base; /* The base address of the timer */ + uint32_t pincfg; /* Output pin configuration */ + uint32_t pclk; /* The frequency of the peripheral clock */ +}; + +/**************************************************************************** + * Static Function Prototypes + ****************************************************************************/ +/* Register access */ + +static uint32_t pwm_getreg(struct kl_pwmtimer_s *priv, int offset); +static void pwm_putreg(struct kl_pwmtimer_s *priv, int offset, uint32_t value); + +#if defined(CONFIG_DEBUG_PWM) && defined(CONFIG_DEBUG_VERBOSE) +static void pwm_dumpregs(struct kl_pwmtimer_s *priv, FAR const char *msg); +#else +# define pwm_dumpregs(priv,msg) +#endif + +/* Timer management */ + +static int pwm_timer(FAR struct kl_pwmtimer_s *priv, + FAR const struct pwm_info_s *info); + +/* 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); + +/**************************************************************************** + * 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, +}; + +#ifdef CONFIG_KL_TPM0_PWM +static struct kl_pwmtimer_s g_pwm0dev = +{ + .ops = &g_pwmops, + .tpmid = 0, + .channel = CONFIG_KL_TPM0_CHANNEL, + .base = KL_TPM0_BASE, + .pincfg = PWM_TPM0_PINCFG, + .pclk = BOARD_CORECLK_FREQ, +}; +#endif + +#ifdef CONFIG_KL_TPM1_PWM +static struct kl_pwmtimer_s g_pwm1dev = +{ + .ops = &g_pwmops, + .tpmid = 1, + .channel = CONFIG_KL_TPM1_CHANNEL, + .base = KL_TPM1_BASE, + .pincfg = PWM_TPM1_PINCFG, + .pclk = BOARD_CORECLK_FREQ, +}; +#endif + +#ifdef CONFIG_KL_TPM2_PWM +static struct kl_pwmtimer_s g_pwm2dev = +{ + .ops = &g_pwmops, + .tpmid = 2, + .channel = CONFIG_KL_TPM2_CHANNEL, + .base = KL_TPM2_BASE, + .pincfg = PWM_TPM2_PINCFG, + .pclk = BOARD_CORECLK_FREQ, +}; +#endif + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: pwm_getreg + * + * Description: + * Read the value of an PWM timer register. + * + * Input Parameters: + * priv - A reference to the PWM block status + * offset - The offset to the register to read + * + * Returned Value: + * The current contents of the specified register + * + ****************************************************************************/ + +static uint32_t pwm_getreg(struct kl_pwmtimer_s *priv, int offset) +{ + return getreg32(priv->base + offset); +} + +/**************************************************************************** + * Name: pwm_putreg + * + * Description: + * Read the value of an PWM timer register. + * + * Input Parameters: + * priv - A reference to the PWM block status + * offset - The offset to the register to read + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void pwm_putreg(struct kl_pwmtimer_s *priv, int offset, uint32_t value) +{ + putreg32(value, priv->base + offset); +} + +/**************************************************************************** + * Name: pwm_dumpregs + * + * Description: + * Dump all timer registers. + * + * Input parameters: + * priv - A reference to the PWM block status + * + * Returned Value: + * None + * + ****************************************************************************/ + +#if defined(CONFIG_DEBUG_PWM) && defined(CONFIG_DEBUG_VERBOSE) +static void pwm_dumpregs(struct kl_pwmtimer_s *priv, FAR const char *msg) +{ + pwmvdbg("%s:\n", msg); + pwmvdbg(" TPM%d_SC: %04x TPM%d_CNT: %04x TPM%d_MOD: %04x\n", + priv->tpmid, + pwm_getreg(priv, TPM_SC_OFFSET), + priv->tpmid, + pwm_getreg(priv, TPM_CNT_OFFSET), + priv->tpmid, + pwm_getreg(priv, TPM_MOD_OFFSET)); + pwmvdbg(" TPM%d_STATUS: %04x TPM%d_CONF: %04x\n", + priv->tpmid, + pwm_getreg(priv, TPM_STATUS_OFFSET), + priv->tpmid, + pwm_getreg(priv, TPM_CONF_OFFSET)); + pwmvdbg(" TPM%d_C0SC: %04x TPM%d_C0V: %04x\n", + priv->tpmid, + pwm_getreg(priv, TPM_C0SC_OFFSET), + priv->tpmid, + pwm_getreg(priv, TPM_C0V_OFFSET)); + pwmvdbg(" TPM%d_C1SC: %04x TPM%d_C1V: %04x\n", + priv->tpmid, + pwm_getreg(priv, TPM_C1SC_OFFSET), + priv->tpmid, + pwm_getreg(priv, TPM_C1V_OFFSET)); + pwmvdbg(" TPM%d_C2SC: %04x TPM%d_C2V: %04x\n", + priv->tpmid, + pwm_getreg(priv, TPM_C2SC_OFFSET), + priv->tpmid, + pwm_getreg(priv, TPM_C2V_OFFSET)); + pwmvdbg(" TPM%d_C3SC: %04x TPM%d_C3V: %04x\n", + priv->tpmid, + pwm_getreg(priv, TPM_C3SC_OFFSET), + priv->tpmid, + pwm_getreg(priv, TPM_C3V_OFFSET)); + pwmvdbg(" TPM%d_C4SC: %04x TPM%d_C4V: %04x\n", + priv->tpmid, + pwm_getreg(priv, TPM_C4SC_OFFSET), + priv->tpmid, + pwm_getreg(priv, TPM_C4V_OFFSET)); + pwmvdbg(" TPM%d_C5SC: %04x TPM%d_C5V: %04x\n", + priv->tpmid, + pwm_getreg(priv, TPM_C5SC_OFFSET), + priv->tpmid, + pwm_getreg(priv, TPM_C5V_OFFSET)); +} +#endif + +/**************************************************************************** + * Name: pwm_timer + * + * Description: + * (Re-)initialize the timer resources and start the pulsed output + * + * Input parameters: + * priv - 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_timer(FAR struct kl_pwmtimer_s *priv, + FAR const struct pwm_info_s *info) +{ + /* Calculated values */ + + uint32_t prescaler; + uint32_t tpmclk; + uint32_t modulo; + uint32_t regval; + uint32_t cv; + uint8_t i; + + uint8_t presc_values[8] = {1, 2, 4, 8, 16, 32, 64, 128}; + + /* Register contents */ + + DEBUGASSERT(priv != NULL && info != NULL); + + pwmvdbg("TPM%d channel: %d frequency: %d duty: %08x\n", + priv->tpmid, priv->channel, info->frequency, info->duty); + + DEBUGASSERT(info->frequency > 0 && info->duty > 0 && + info->duty < uitoub16(100)); + + /* Calculate optimal values for the timer prescaler and for the timer modulo + * register. If' frequency' is the desired frequency, then + * + * modulo = tpmclk / frequency + * tpmclk = pclk / presc + * + * Or, + * + * modulo = pclk / presc / frequency + * + * There are many solutions to do this, but the best solution will be the + * one that has the largest modulo value and the smallest prescaler value. + * That is the solution that should give us the most accuracy in the timer + * control. Subject to: + * + * 1 <= presc <= 128 (need to be 1, 2, 4, 8, 16, 32, 64, 128) + * 1 <= modulo <= 65535 + * + * So presc = pclk / 65535 / frequency would be optimal. + * + * Example: + * + * pclk = 24 MHz + * frequency = 100 Hz + * + * prescaler = 24,000,000 / 65,535 / 100 + * = 3.6 (or 4 -- taking the ceiling always) + * timclk = 24,000,000 / 4 + * = 6,000,000 + * modulo = 6,000,000 / 100 + * = 60,000 + */ + + prescaler = (priv->pclk / info->frequency + 65534) / 65535; + + for (i = 0; i < 7; i++) + { + if (prescaler <= presc_values[i]) + { + break; + } + } + + prescaler = i; + + tpmclk = priv->pclk / presc_values[prescaler]; + + modulo = tpmclk / info->frequency; + if (modulo < 1) + { + modulo = 1; + } + else if (modulo > 65535) + { + modulo = 65535; + } + + /* Duty cycle: + * + * duty cycle = cv / modulo (fractional value) + */ + + cv = b16toi(info->duty * modulo + b16HALF); + + pwmvdbg("TPM%d PCLK: %d frequency: %d TPMCLK: %d prescaler: %d modulo: %d c0v: %d\n", + priv->tpmid, priv->pclk, info->frequency, tpmclk, + presc_values[prescaler], modulo, c0v); + + /* Disable TPM and reset CNT before writing MOD and PS */ + + pwm_putreg(priv, TPM_SC_OFFSET, TPM_SC_CMOD_DIS); + pwm_putreg(priv, TPM_CNT_OFFSET, 0); + + /* Set the modulo value */ + + pwm_putreg(priv, TPM_MOD_OFFSET, (uint16_t)modulo); + + /* Set the duty cycle for channel specific */ + + switch (priv->channel) + { + case 0: /* PWM Mode configuration: Channel 0 */ + { + pwm_putreg(priv, TPM_C0SC_OFFSET, TPM_CnSC_MSB | TPM_CnSC_ELSB); + pwm_putreg(priv, TPM_C0V_OFFSET, (uint16_t) cv); + } + break; + + case 1: /* PWM Mode configuration: Channel 1 */ + { + pwm_putreg(priv, TPM_C1SC_OFFSET, TPM_CnSC_MSB | TPM_CnSC_ELSB); + pwm_putreg(priv, TPM_C1V_OFFSET, (uint16_t) cv); + } + break; + + case 2: /* PWM Mode configuration: Channel 2 */ + { + pwm_putreg(priv, TPM_C2SC_OFFSET, TPM_CnSC_MSB | TPM_CnSC_ELSB); + pwm_putreg(priv, TPM_C2V_OFFSET, (uint16_t) cv); + } + break; + + case 3: /* PWM Mode configuration: Channel 3 */ + { + pwm_putreg(priv, TPM_C3SC_OFFSET, TPM_CnSC_MSB | TPM_CnSC_ELSB); + pwm_putreg(priv, TPM_C3V_OFFSET, (uint16_t) cv); + } + break; + + case 4: /* PWM Mode configuration: Channel 4 */ + { + pwm_putreg(priv, TPM_C4SC_OFFSET, TPM_CnSC_MSB | TPM_CnSC_ELSB); + pwm_putreg(priv, TPM_C4V_OFFSET, (uint16_t) cv); + } + break; + + case 5: /* PWM Mode configuration: Channel 5 */ + { + pwm_putreg(priv, TPM_C5SC_OFFSET, TPM_CnSC_MSB | TPM_CnSC_ELSB); + pwm_putreg(priv, TPM_C5V_OFFSET, (uint16_t) cv); + } + break; + + default: + pwmdbg("No such channel: %d\n", priv->channel); + return -EINVAL; + } + + /* Set prescaler and enable clock */ + + regval = pwm_getreg(priv, TPM_SC_OFFSET); + regval &= ~(TPM_SC_PS_MASK); + regval &= ~(TPM_SC_CMOD_MASK); + regval |= prescaler | TPM_SC_CMOD_LPTPM_CLK; + pwm_putreg(priv, TPM_SC_OFFSET, (uint16_t)regval); + + pwm_dumpregs(priv, "After starting"); + return OK; +} + +/**************************************************************************** + * Name: pwm_setup + * + * Description: + * This method is called when the driver is opened. The lower half driver + * should configure and initialize the device so that it is ready for use. + * It should 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 + * + * Assumptions: + * AHB1 or 2 clocking for the GPIOs and timer has already been configured + * by the RCC logic at power up. + * + ****************************************************************************/ + +static int pwm_setup(FAR struct pwm_lowerhalf_s *dev) +{ + uint32_t regval; + FAR struct kl_pwmtimer_s *priv = (FAR struct kl_pwmtimer_s *)dev; + + /* Enable access to TPM modules */ + + regval = getreg32(KL_SIM_SCGC6); + regval |= SIM_SCGC6_TPM0 | SIM_SCGC6_TPM1 | SIM_SCGC6_TPM2; + putreg32(regval, KL_SIM_SCGC6); + + pwmvdbg("TPM%d pincfg: %08x\n", priv->tpmid, priv->pincfg); + pwm_dumpregs(priv, "Initially"); + + /* Configure the PWM output pin, but do not start the timer yet */ + + kl_configgpio(priv->pincfg); + pwm_dumpgpio(priv->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 kl_pwmtimer_s *priv = (FAR struct kl_pwmtimer_s *)dev; + uint32_t pincfg; + + pwmvdbg("TPM%d pincfg: %08x\n", priv->tpmid, priv->pincfg); + + /* Make sure that the output has been stopped */ + + pwm_stop(dev); + + /* Then put the GPIO pin back to the default state */ + + pincfg = (priv->pincfg & ~(_PIN_MODE_MASK)); + pincfg |= GPIO_INPUT; + kl_configgpio(pincfg); + 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 kl_pwmtimer_s *priv = (FAR struct kl_pwmtimer_s *)dev; + return pwm_timer(priv, info); +} + +/**************************************************************************** + * 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 kl_pwmtimer_s *priv = (FAR struct kl_pwmtimer_s *)dev; + irqstate_t flags; + + pwmvdbg("TPM%d\n", priv->tpmid); + + /* Disable interrupts momentary to stop any ongoing timer processing and + * to prevent any concurrent access to the reset register. + */ + + flags = irqsave(); + + /* Disable further interrupts and stop the timer */ + + pwm_putreg(priv, TPM_SC_OFFSET, TPM_SC_CMOD_DIS); + pwm_putreg(priv, TPM_CNT_OFFSET, 0); + + /* Determine which timer channel to clear */ + + switch (priv->channel) + { + case 0: + pwm_putreg(priv, TPM_C0V_OFFSET, 0); + break; + + case 1: + pwm_putreg(priv, TPM_C1V_OFFSET, 0); + break; + + case 2: + pwm_putreg(priv, TPM_C2V_OFFSET, 0); + break; + + case 3: + pwm_putreg(priv, TPM_C3V_OFFSET, 0); + break; + + case 4: + pwm_putreg(priv, TPM_C4V_OFFSET, 0); + break; + + case 5: + pwm_putreg(priv, TPM_C5V_OFFSET, 0); + break; + + default: + pwmdbg("No such channel: %d\n", priv->channel); + return -EINVAL; + } + + irqrestore(flags); + + pwm_dumpregs(priv, "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 kl_pwmtimer_s *priv = (FAR struct kl_pwmtimer_s *)dev; + + /* There are no platform-specific ioctl commands */ + + pwmvdbg("TPM%d\n", priv->tpmid); +#endif + return -ENOTTY; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: kl_pwminitialize + * + * Description: + * Initialize one timer for use with the upper_level PWM driver. + * + * Input Parameters: + * timer - A number identifying the timer use. The number of valid timer + * IDs varies with the STM32 MCU and MCU family but is somewhere in + * the range of {1,..,14}. + * + * Returned Value: + * On success, a pointer to the STM32 lower half PWM driver is returned. + * NULL is returned on any failure. + * + ****************************************************************************/ + +FAR struct pwm_lowerhalf_s *kl_pwminitialize(int timer) +{ + FAR struct kl_pwmtimer_s *lower; + + pwmvdbg("TPM%d\n", timer); + + switch (timer) + { +#ifdef CONFIG_KL_TPM0_PWM + case 0: + lower = &g_pwm0dev; + + break; +#endif + +#ifdef CONFIG_KL_TPM1_PWM + case 1: + lower = &g_pwm1dev; + + break; +#endif + +#ifdef CONFIG_KL_TPM2_PWM + case 2: + lower = &g_pwm2dev; + + break; +#endif + + default: + pwmdbg("No such timer configured\n"); + return NULL; + } + + return (FAR struct pwm_lowerhalf_s *)lower; +} + +#endif /* CONFIG_KL_TPMn_PWM, n = 0,...,2 */ diff --git a/nuttx/arch/arm/src/kl/kl_pwm.h b/nuttx/arch/arm/src/kl/kl_pwm.h new file mode 100644 index 000000000..104df3bf9 --- /dev/null +++ b/nuttx/arch/arm/src/kl/kl_pwm.h @@ -0,0 +1,197 @@ +/************************************************************************************ + * arch/arm/src/kl/kl_pwm.h + * + * Copyright (C) 2013 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <gnutt@nuttx.org> + * Alan Carvalho de Assis <acassis@gmail.com> + * + * 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_KL_KINETIS_PWM_H +#define __ARCH_ARM_SRC_KL_KINETIS_PWM_H + +/************************************************************************************ + * Included Files + ************************************************************************************/ + +#include <nuttx/config.h> + +#include "chip.h" + +/************************************************************************************ + * Pre-processor Definitions + ************************************************************************************/ +/* Configuration ********************************************************************/ +/* Timer devices may be used for different purposes. One special purpose is + * to generate modulated outputs for such things as motor control. If CONFIG_KL_TPMn + * is defined then the CONFIG_KL_TPMn_PWM must also be defined to indicate that + * timer "n" is intended to be used for pulsed output signal generation. + */ + +#ifndef CONFIG_KL_TPM0 +# undef CONFIG_KL_TPM0_PWM +#endif +#ifndef CONFIG_KL_TPM1 +# undef CONFIG_KL_TPM1_PWM +#endif +#ifndef CONFIG_KL_TPM2 +# undef CONFIG_KL_TPM2_PWM +#endif + +/* Check if PWM support for any channel is enabled. */ + +#if defined(CONFIG_KL_TPM0_PWM) || defined(CONFIG_KL_TPM1_PWM) || \ + defined(CONFIG_KL_TPM2_PWM) + +#include <arch/board/board.h> +#include "chip/kl_pinmux.h" + +/* For each timer that is enabled for PWM usage, we need the following additional + * configuration settings: + * + * CONFIG_KL_TPMx_CHANNEL - Specifies the timer output channel {1,..,4} + * PWM_TPMx_CHn - One of the values defined in chip/stm32*_pinmap.h. In the case + * where there are multiple pin selections, the correct setting must be provided + * in the arch/board/board.h file. + * + * NOTE: The STM32 timers are each capable of generating different signals on + * each of the four channels with different duty cycles. That capability is + * not supported by this driver: Only one output channel per timer. + */ + +#ifdef CONFIG_KL_TPM0_PWM +# if !defined(CONFIG_KL_TPM0_CHANNEL) +# error "CONFIG_KL_TPM0_CHANNEL must be provided" +# elif CONFIG_KL_TPM0_CHANNEL == 0 +# define PWM_TPM0_PINCFG GPIO_TPM0_CH0OUT +# elif CONFIG_KL_TPM0_CHANNEL == 1 +# define PWM_TPM0_PINCFG GPIO_TPM0_CH1OUT +# elif CONFIG_KL_TPM0_CHANNEL == 2 +# define PWM_TPM0_PINCFG GPIO_TPM1_CH2OUT +# elif CONFIG_KL_TPM0_CHANNEL == 3 +# define PWM_TPM0_PINCFG GPIO_TPM1_CH3OUT +# elif CONFIG_KL_TPM0_CHANNEL == 4 +# define PWM_TPM0_PINCFG GPIO_TPM1_CH4OUT +# elif CONFIG_KL_TPM0_CHANNEL == 5 +# define PWM_TPM0_PINCFG GPIO_TPM1_CH5OUT +# else +# error "Unsupported value of CONFIG_KL_TPM1_CHANNEL" +# endif +#endif + +#ifdef CONFIG_KL_TPM1_PWM +# if !defined(CONFIG_KL_TPM1_CHANNEL) +# error "CONFIG_KL_TPM1_CHANNEL must be provided" +# elif CONFIG_KL_TPM1_CHANNEL == 0 +# define PWM_TPM1_PINCFG GPIO_TPM1_CH0OUT +# elif CONFIG_KL_TPM1_CHANNEL == 1 +# define PWM_TPM1_PINCFG GPIO_TPM1_CH1OUT +# elif CONFIG_KL_TPM1_CHANNEL == 2 +# define PWM_TPM1_PINCFG GPIO_TPM1_CH2OUT +# elif CONFIG_KL_TPM1_CHANNEL == 3 +# define PWM_TPM1_PINCFG GPIO_TPM1_CH3OUT +# elif CONFIG_KL_TPM1_CHANNEL == 4 +# define PWM_TPM1_PINCFG GPIO_TPM1_CH4OUT +# elif CONFIG_KL_TPM1_CHANNEL == 5 +# define PWM_TPM1_PINCFG GPIO_TPM1_CH5OUT +# else +# error "Unsupported value of CONFIG_KL_TPM2_CHANNEL" +# endif +#endif + +#ifdef CONFIG_KL_TPM2_PWM +# if !defined(CONFIG_KL_TPM2_CHANNEL) +# error "CONFIG_KL_TPM2_CHANNEL must be provided" +# elif CONFIG_KL_TPM2_CHANNEL == 0 +# define PWM_TPM2_PINCFG GPIO_TPM2_CH0OUT +# elif CONFIG_KL_TPM2_CHANNEL == 1 +# define PWM_TPM2_PINCFG GPIO_TPM2_CH1OUT +# elif CONFIG_KL_TPM2_CHANNEL == 2 +# define PWM_TPM2_PINCFG GPIO_TPM2_CH2OUT +# elif CONFIG_KL_TPM2_CHANNEL == 3 +# define PWM_TPM2_PINCFG GPIO_TPM2_CH3OUT +# elif CONFIG_KL_TPM2_CHANNEL == 4 +# define PWM_TPM2_PINCFG GPIO_TPM2_CH4OUT +# elif CONFIG_KL_TPM2_CHANNEL == 5 +# define PWM_TPM2_PINCFG GPIO_TPM2_CH5OUT +# else +# error "Unsupported value of CONFIG_KL_TPM3_CHANNEL" +# endif +#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: kl_pwminitialize + * + * Description: + * Initialize one timer for use with the upper_level PWM driver. + * + * Input Parameters: + * timer - A number identifying the timer use. The number of valid timer + * IDs varies with the STM32 MCU and MCU family but is somewhere in + * the range of {1,..,14}. + * + * Returned Value: + * On success, a pointer to the STM32 lower half PWM driver is returned. + * NULL is returned on any failure. + * + ************************************************************************************/ + +FAR struct pwm_lowerhalf_s *kl_pwminitialize(int timer); + +#undef EXTERN +#if defined(__cplusplus) +} +#endif + +#endif /* __ASSEMBLY__ */ +#endif /* CONFIG_KL_TPMx_PWM */ +#endif /* __ARCH_ARM_SRC_KL_KINETIS_PWM_H */ |