blob: 1e93a1ffdc45c7cecb2e4a7ee7aff7ab2f640766 [file] [log] [blame]
/****************************************************************************
* arch/arm64/src/imx9/imx9_flexio_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 <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <debug.h>
#include <time.h>
#include <inttypes.h>
#include <nuttx/arch.h>
#include <nuttx/clock.h>
#include <nuttx/semaphore.h>
#include <nuttx/timers/pwm.h>
#include <arch/board/board.h>
#include "imx9_flexio_pwm.h"
#include "arm64_arch.h"
#include "imx9_ccm.h"
#include "imx9_iomuxc.h"
#include "hardware/imx9_ccm.h"
#include "hardware/imx9_pinmux.h"
#include "hardware/imx9_flexio.h"
#ifdef CONFIG_IMX9_FLEXIO_PWM
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#define PIN_FOR_TIMER(priv, timer) ((uint8_t)(priv->pins >> (timer * 8)))
#ifdef CONFIG_IMX9_FLEXIO1_PWM
# if (CONFIG_PWM_NCHANNELS < CONFIG_IMX9_FLEXIO1_PWM_NCHANNELS)
# error CONFIG_PWM_NCHANNELS < CONFIG_IMX9_FLEXIO1_PWM_NCHANNELS
# endif
#endif
#ifdef CONFIG_IMX9_FLEXIO2_PWM
# if (CONFIG_PWM_NCHANNELS < CONFIG_IMX9_FLEXIO2_PWM_NCHANNELS)
# error CONFIG_PWM_NCHANNELS < CONFIG_IMX9_FLEXIO2_PWM_NCHANNELS
# endif
#endif
/****************************************************************************
* Private Types
****************************************************************************/
/* This structure represents the state of one PWM timer */
struct imx9_pwmtimer_s
{
const struct pwm_ops_s *ops; /* PWM operations */
const flexio_pwm_id_t id; /* PWM_FLEXIO1 or PWM_FLEXIO2 */
const int nchannels; /* Number of channels used */
const uintptr_t base; /* The base address of the FLEXIO */
const uint64_t pins; /* Mapping of timer outputs to flexio outputs */
const int int_trigger; /* Uses flex-io internal timer for frequency */
int trigger_ch; /* Trigger channel */
unsigned frequency; /* Current frequency setting */
int period; /* PWM period in ticks of functional clock */
};
/****************************************************************************
* Static Function Prototypes
****************************************************************************/
/* 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);
/****************************************************************************
* 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,
};
static struct imx9_pwmtimer_s g_pwmdev[] =
{
#ifdef CONFIG_IMX9_FLEXIO1_PWM
{
.ops = &g_pwmops,
.id = PWM_FLEXIO1,
.nchannels = CONFIG_IMX9_FLEXIO1_PWM_NCHANNELS,
.base = IMX9_FLEXIO1_BASE,
.pins = CONFIG_IMX9_FLEXIO1_PWM_CHANNEL_PINS,
.int_trigger = 1,
},
#endif
#ifdef CONFIG_IMX9_FLEXIO2_PWM
{
.ops = &g_pwmops,
.id = PWM_FLEXIO2,
.nchannels = CONFIG_IMX9_FLEXIO2_PWM_NCHANNELS,
.base = IMX9_FLEXIO2_BASE,
.pins = CONFIG_IMX9_FLEXIO2_PWM_CHANNEL_PINS,
.int_trigger = 1,
},
#endif
};
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: flexio_getreg
*
* Description:
* Read the value of a flex-io register.
*
* Input Parameters:
* priv - A reference to the PWM block
* offset - The offset to the register to read
*
* Returned Value:
* The current contents of the specified register
*
****************************************************************************/
static inline uint32_t flexio_getreg(struct imx9_pwmtimer_s *priv,
int offset)
{
return getreg32(priv->base + offset);
}
/****************************************************************************
* Name: flexio_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 inline void flexio_putreg(struct imx9_pwmtimer_s *priv, int offset,
uint32_t value)
{
putreg32(value, priv->base + offset);
}
/****************************************************************************
* Name: flexio_mux
*
* Description:
* Mux the flex-io output pins to pads. The macros FLEXIOn_PWMx_MUX
* need to be defined in the board.h file
*
* Input Parameters:
* None
*
* Returned Value:
* None
*
****************************************************************************/
static void flexio_mux(void)
{
#ifdef CONFIG_IMX9_FLEXIO1_PWM
# ifdef FLEXIO1_PWM0_MUX
imx9_iomux_configure(FLEXIO1_PWM0_MUX);
# endif
# ifdef FLEXIO1_PWM1_MUX
imx9_iomux_configure(FLEXIO1_PWM1_MUX);
# endif
# ifdef FLEXIO1_PWM2_MUX
imx9_iomux_configure(FLEXIO1_PWM2_MUX);
# endif
# ifdef FLEXIO1_PWM3_MUX
imx9_iomux_configure(FLEXIO1_PWM3_MUX);
# endif
# ifdef FLEXIO1_PWM4_MUX
imx9_iomux_configure(FLEXIO1_PWM4_MUX);
# endif
# ifdef FLEXIO1_PWM5_MUX
imx9_iomux_configure(FLEXIO1_PWM5_MUX);
# endif
# ifdef FLEXIO1_PWM6_MUX
imx9_iomux_configure(FLEXIO1_PWM6_MUX);
# endif
# ifdef FLEXIO1_PWM7_MUX
imx9_iomux_configure(FLEXIO1_PWM7_MUX);
# endif
#endif
#ifdef CONFIG_IMX9_FLEXIO2_PWM
# ifdef FLEXIO2_PWM0_MUX
imx9_iomux_configure(FLEXIO2_PWM0_MUX);
# endif
# ifdef FLEXIO2_PWM1_MUX
imx9_iomux_configure(FLEXIO2_PWM1_MUX);
# endif
# ifdef FLEXIO2_PWM2_MUX
imx9_iomux_configure(FLEXIO2_PWM2_MUX);
# endif
# ifdef FLEXIO2_PWM3_MUX
imx9_iomux_configure(FLEXIO2_PWM3_MUX);
# endif
# ifdef FLEXIO2_PWM4_MUX
imx9_iomux_configure(FLEXIO2_PWM4_MUX);
# endif
# ifdef FLEXIO2_PWM5_MUX
imx9_iomux_configure(FLEXIO2_PWM5_MUX);
# endif
# ifdef FLEXIO2_PWM6_MUX
imx9_iomux_configure(FLEXIO2_PWM6_MUX);
# endif
# ifdef FLEXIO2_PWM7_MUX
imx9_iomux_configure(FLEXIO2_PWM7_MUX);
# endif
#endif
}
/****************************************************************************
* Name: pwm_init_trigger_timer
*
* Description:
* Initialize the timer trigger, generating the PWM frequency
*
* Input Parameters:
* priv - A reference to the lower half PWM driver state structure
*
* Returned Value:
* OK on success, ERROR if the timer initialization fails
*
****************************************************************************/
static int pwm_init_trigger_timer(struct imx9_pwmtimer_s *priv)
{
uint32_t reg;
int num_timers;
/* For now, use the last available flexio timer to produce internal
* trigger. This can be later expanded to use external trigger from
* LPIT timer, if one more PWM channel is required
*/
/* Get parameter register and number of supported timers */
reg = flexio_getreg(priv, IMX9_FLEXIO_PARAM_OFFSET);
num_timers = (reg & FLEXIO_PARAM_TIMER_MASK) >> FLEXIO_PARAM_TIMER_SHIFT;
num_timers--;
if (num_timers < priv->nchannels)
{
pwmerr("PWM%d max channels %d\n", priv->id, num_timers);
return ERROR;
}
priv->trigger_ch = num_timers;
return OK;
}
/****************************************************************************
* Name: pwm_select_func_clock
*
* Description:
* Select best suitable functional clock for the flexio
*
* Input Parameters:
* priv - A reference to the lower half PWM driver state structure
* freq - The requested PWM frequency for this flexio block
*
* Returned Value:
* Zero on success, negated error value on failure
*
****************************************************************************/
static int pwm_select_func_clock(struct imx9_pwmtimer_s *priv, int freq)
{
const int max_div = 24000000 / 65536 + 1; /* for 1 Hz */
int div;
uint32_t period;
if (freq == 0)
{
priv->period = 0;
return 0;
}
/* Use the 24MHz OSC clock, and find the best divider to get as much
* resolution as possible using 16 bit timer
*/
for (div = 1; div < max_div; div++)
{
period = 24000000 / div / freq;
if (period < 65536)
{
priv->period = period;
break;
}
}
imx9_ccm_configure_root_clock(CCM_CR_FLEXIO1 + priv->id, OSC_24M, div);
/* Enable peripheral clock */
imx9_ccm_gate_on(CCM_LPCG_FLEXIO1 + priv->id , true);
return 0;
}
/****************************************************************************
* Name: pwm_update_frequency
*
* Description:
* Initialize the timer trigger, generating the PWM freuency
*
* Input Parameters:
* priv - A reference to the lower half PWM driver state structure
* freq - The requested PWM frequency for this flexio block
*
* Returned Value:
* Zero on success, negated error value on failure
*
****************************************************************************/
static int pwm_update_frequency(struct imx9_pwmtimer_s *priv, int freq)
{
int ret = pwm_select_func_clock(priv, freq);
if (ret < 0)
{
return ret;
}
/* Configure the timer to produce internal trigger. The following
* setting produces 50/50 pulse where duty cycle is defined by
* TIMCMP:
*
* TIMCFG:
* TIMOUT = 0 Timer output is logic one when enabled and not
* affected by timer reset
* TIMDEC = 0 Decrement counter on FLEXIO clock
* TIMRST = 0x0 Timer never reset
* TIMDIS = 0x0 Timer never disabled
* TIMENA = 0x0 Timer always enabled
* TIMCTL:
* TIMOD = 0x3 Single 16-bit counter
* PINCFG = 0x0 Output pin disabled
* TIMCMP: frequency / 2
*/
flexio_putreg(priv, IMX9_FLEXIO_TIMCMP_OFFSET(priv->trigger_ch),
priv->period / 2);
/* Enable / disable timer */
flexio_putreg(priv, IMX9_FLEXIO_TIMCTL_OFFSET(priv->trigger_ch),
freq > 0 ? FLEXIO_TIMCTL_TIMOD(0x3) :
FLEXIO_TIMCTL_TIMOD(0x0));
return ret;
}
/****************************************************************************
* Name: pwm_update_duty
*
* Description:
* Change the channel duty cycle.
*
* Input Parameters:
* priv - A reference to the lower half PWM driver state structure
* channel - Channel to by updated
* duty - New duty cycle as fraction of 65536
*
* Returned Value:
* Zero on success; a negated errno value on failure
*
****************************************************************************/
static int pwm_update_duty(struct imx9_pwmtimer_s *priv, int pwm_ch,
ub16_t duty16)
{
uint32_t edge = ub16toi(duty16 * priv->period + b16HALF);
int timer = pwm_ch - 1; /* map pwm ch 1 to timer 0 etc.. */
uint32_t regval;
if (pwm_ch == 0 || pwm_ch > priv->nchannels)
{
pwmerr("ERROR: PWM%d has no such channel: %u\n", priv->id, pwm_ch);
return -EINVAL;
}
/* Now configure the flexio timers in 16-bit counter mode */
/* Timers 0-6:
* TIMCFG:
* TIMOUT = 0 Timer output is 1 when enabled and not affected by reset
* TIMDEC = 0 Decrement counter on FLEXIO clock
* TIMRST = 0x0 Timer never reset
* TIMDIS = 0x2 Timer disabled on counter 0
* TIMENA = 0x6 Timer enabled on Trigger rising edge
* TIMCTL:
* TIMOD = 0x3 single 16-bit counter
* TRGSEL = 4 * trg_ch + 3 timer "trg_ch" trigger output
* TRGSRC = 1 internal trigger
* PINCFG = 0x3 Timer pin output
* PINSEL = timer number + mux conf
* TIMCMP: duty cycle
*/
/* If this is the first time configuring the PWMs, configure the
* timer fully, otherwise just update the duty cycle
*/
flexio_putreg(priv, IMX9_FLEXIO_TIMCMP_OFFSET(timer), edge);
if (priv->frequency == 0)
{
flexio_putreg(priv, IMX9_FLEXIO_TIMCFG_OFFSET(timer),
FLEXIO_TIMCFG_TIMDIS(0x2) |
FLEXIO_TIMCFG_TIMENA(0x6));
/* When initially configuring PINCFG=11b, FLEXIO may briefly drive the
* pin low. To avoid this, configure PINCFG=10b along with the rest of
* the control register and then perform a subsequent write to set
* PINCFG=11b
*/
regval = (FLEXIO_TIMCTL_TIMOD(0x3) |
FLEXIO_TIMCTL_TRGSEL(4 * priv->trigger_ch + 3) |
FLEXIO_TIMCTL_TRGSRC(priv->int_trigger) |
FLEXIO_TIMCTL_PINSEL(PIN_FOR_TIMER(priv, timer)));
flexio_putreg(priv, IMX9_FLEXIO_TIMCTL_OFFSET(timer),
regval | FLEXIO_TIMCTL_PINCFG(0x2));
flexio_putreg(priv, IMX9_FLEXIO_TIMCTL_OFFSET(timer),
regval | FLEXIO_TIMCTL_PINCFG(0x3));
}
pwminfo("PWM%d channel %d, p: %d e: %" PRIu32 "\n", priv->id, pwm_ch,
priv->period, edge);
return 0;
}
/****************************************************************************
* 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:
*
****************************************************************************/
static int pwm_setup(struct pwm_lowerhalf_s *dev)
{
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)
{
/* Make sure that the output has been stopped */
pwm_stop(dev);
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 imx9_pwmtimer_s *priv = (struct imx9_pwmtimer_s *)dev;
int ret = OK;
int i;
if (priv == NULL || info == NULL || info->frequency == 0)
{
return -EINVAL;
}
/* Set the frequency if not changed */
if (info->frequency != priv->frequency)
{
ret = pwm_update_frequency(priv, info->frequency);
}
/* Handle channel specific setup */
for (i = 0; i < CONFIG_PWM_NCHANNELS; i++)
{
if (ret != OK || info->channels[i].channel == -1)
{
break;
}
ret = pwm_update_duty(priv, info->channels[i].channel,
info->channels[i].duty);
}
if (ret == OK)
{
priv->frequency = info->frequency;
}
return ret;
}
/****************************************************************************
* 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.
*
****************************************************************************/
static int pwm_stop(struct pwm_lowerhalf_s *dev)
{
struct imx9_pwmtimer_s *priv = (struct imx9_pwmtimer_s *)dev;
int i;
pwminfo("PWM%d stop\n", priv->id);
/* Check that timer is valid */
if (priv == NULL)
{
return -EINVAL;
}
/* Disable all the channels */
for (i = 0; i < priv->nchannels; i++)
{
flexio_putreg(priv, IMX9_FLEXIO_TIMCTL_OFFSET(i),
FLEXIO_TIMCTL_PINCFG(0x2));
flexio_putreg(priv, IMX9_FLEXIO_TIMCTL_OFFSET(i), 0);
}
/* Setting frequency to zero disables trigger clock */
return pwm_update_frequency(priv, 0);
}
/****************************************************************************
* 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)
{
return -ENOTTY;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: imx9_flexio_pwm_init
*
* Description:
* Initialize flexio blocks to generate EPWM.
*
* Input Parameters:
* pwmid - A number identifying the pwm block. The number of valid
* IDs varies depending on the configuration.
*
* Returned Value:
* On success, a pointer to the lower half PWM driver is
* returned. NULL is returned on any failure.
*
****************************************************************************/
struct pwm_lowerhalf_s *imx9_flexio_pwm_init(flexio_pwm_id_t pwmid)
{
struct imx9_pwmtimer_s *lower = NULL;
int i;
for (i = 0; i < sizeof(g_pwmdev) / sizeof(struct imx9_pwmtimer_s); i++)
{
if (pwmid == g_pwmdev[i].id)
{
lower = &g_pwmdev[i];
break;
}
}
if (lower)
{
/* IO mux */
flexio_mux();
/* Reset FlexIO */
flexio_putreg(lower, IMX9_FLEXIO_CTRL_OFFSET, FLEXIO_CTRL_SWRST(1));
/* Enable FlexIO and de-assert reset */
flexio_putreg(lower, IMX9_FLEXIO_CTRL_OFFSET, FLEXIO_CTRL_FLEXEN(1));
/* Make sure that FlexIO is enabled and reset is cleared */
while (flexio_getreg(lower, IMX9_FLEXIO_CTRL_OFFSET) !=
FLEXIO_CTRL_FLEXEN_MASK);
/* Initialize the trigger timer used for PWM period generation */
if (pwm_init_trigger_timer(lower) != OK)
{
/* Disable FlexIO */
flexio_putreg(lower, IMX9_FLEXIO_CTRL_OFFSET, 0);
return NULL;
}
pwminfo("PWM%d at 0x%" PRIxPTR " configured\n", pwmid, lower->base);
}
else
{
pwmerr("ERROR: No such timer configured %d\n", pwmid);
}
return (struct pwm_lowerhalf_s *)lower;
}
#endif