blob: 7d11e32c42256bcb27ca1d6628b7919eb7c4cc01 [file] [log] [blame]
/****************************************************************************
* arch/arm/src/samv7/sam_pwm.c
*
* SPDX-License-Identifier: Apache-2.0
*
* 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 <inttypes.h>
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
#include <time.h>
#include <string.h>
#include <debug.h>
#include <errno.h>
#include <nuttx/irq.h>
#include <nuttx/timers/pwm.h>
#include "arm_internal.h"
#include "chip.h"
#include "sam_config.h"
#include "sam_pwm.h"
#include "sam_periphclks.h"
#include "sam_gpio.h"
#include "hardware/sam_pwm.h"
#include "hardware/sam_pinmap.h"
#include "hardware/sam_pio.h"
#include <arch/board/board.h>
#include <sys/time.h>
#ifdef CONFIG_SAMV7_PWM
#ifdef CONFIG_PWM_NCHANNELS
# define PWM_NCHANNELS CONFIG_PWM_NCHANNELS
#else
# define PWM_NCHANNELS 1
#endif
#define CHANNEL_OFFSET 0x20
#define COMP_OFFSET 0x10
#define FAULT_SEL_OFFSET 0x8
#define CLK_FREQ BOARD_MCK_FREQUENCY
#define PWM_RES 65535
#define COMP_UNITS_NUM 8
/* Sync offset flags defines */
#define CH0_SYNC_FLAG (1 << 0)
#define CH1_SYNC_FLAG (1 << 1)
#define CH2_SYNC_FLAG (1 << 2)
#define CH3_SYNC_FLAG (1 << 3)
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/****************************************************************************
* Private Types
****************************************************************************/
struct sam_pwm_channel_s
{
uint8_t channel; /* Number of PWM module */
gpio_pinset_t pin_h; /* PWM H output pin */
gpio_pinset_t pin_l; /* PWM L output pin */
};
struct sam_pwm_comparison_s
{
int comp_vals[COMP_UNITS_NUM];
int event0;
int event1;
};
struct sam_pwm_fault_s
{
uint8_t source; /* Source of fault input */
uint8_t polarity;
uint8_t latched; /* Latched fault inputs */
gpio_pinset_t gpio_0; /* GPIO 1 fault input */
gpio_pinset_t gpio_1; /* GPIO 2 fault input */
gpio_pinset_t gpio_2; /* GPIO 3 fault input */
};
struct sam_pwm_s
{
const struct pwm_ops_s *ops; /* PWM operations */
const struct sam_pwm_channel_s *channels;
const struct sam_pwm_comparison_s *comparison;
const struct sam_pwm_fault_s *fault;
uint8_t channels_num; /* Number of channels */
uintptr_t base; /* Base address of peripheral register */
uint8_t sync; /* Flags of synchronized channels */
};
/* 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
****************************************************************************/
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_SAMV7_PWM0
static struct sam_pwm_channel_s g_pwm0_channels[] =
{
#ifdef CONFIG_SAMV7_PWM0_CH0
{
.channel = 0,
#ifdef CONFIG_SAMV7_PWM0_CH0_LONLY
.pin_h = 0,
.pin_l = GPIO_PWMC0_L0,
#else
.pin_h = GPIO_PWMC0_H0,
#ifdef CONFIG_SAMV7_PWM0_CH0_COMP
.pin_l = GPIO_PWMC0_L0,
#else
.pin_l = 0,
#endif
#endif
},
#endif
#ifdef CONFIG_SAMV7_PWM0_CH1
{
.channel = 1,
#ifdef CONFIG_SAMV7_PWM0_CH1_LONLY
.pin_h = 0,
.pin_l = GPIO_PWMC0_L1,
#else
.pin_h = GPIO_PWMC0_H1,
#ifdef CONFIG_SAMV7_PWM0_CH1_COMP
.pin_l = GPIO_PWMC0_L1,
#else
.pin_l = 0,
#endif
#endif
},
#endif
#ifdef CONFIG_SAMV7_PWM0_CH2
{
.channel = 2,
#ifdef CONFIG_SAMV7_PWM0_CH2_LONLY
.pin_h = 0,
.pin_l = GPIO_PWMC0_L2,
#else
.pin_h = GPIO_PWMC0_H2,
#ifdef CONFIG_SAMV7_PWM0_CH2_COMP
.pin_l = GPIO_PWMC0_L2,
#else
.pin_l = 0,
#endif
#endif
},
#endif
#ifdef CONFIG_SAMV7_PWM0_CH3
{
.channel = 3,
#ifdef CONFIG_SAMV7_PWM0_CH3_LONLY
.pin_h = 0,
.pin_l = GPIO_PWMC0_L3,
#else
.pin_h = GPIO_PWMC0_H3,
#ifdef CONFIG_SAMV7_PWM0_CH3_COMP
.pin_l = GPIO_PWMC0_L3,
#else
.pin_l = 0,
#endif
#endif
},
#endif
};
static struct sam_pwm_comparison_s g_pwm0_comparison =
{
.comp_vals =
{
CONFIG_SAMV7_PWM0_TRIG0, CONFIG_SAMV7_PWM0_TRIG1,
CONFIG_SAMV7_PWM0_TRIG2, CONFIG_SAMV7_PWM0_TRIG3,
CONFIG_SAMV7_PWM0_TRIG4, CONFIG_SAMV7_PWM0_TRIG5,
CONFIG_SAMV7_PWM0_TRIG6, CONFIG_SAMV7_PWM0_TRIG7
},
#ifdef CONFIG_SAMV7_PWM0_EVENT0
.event0 = 1,
#else
.event0 = 0,
#endif
#ifdef CONFIG_SAMV7_PWM0_EVENT1
.event1 = 1,
#else
.event1 = 0,
#endif
};
static struct sam_pwm_fault_s g_pwm0_fault =
{
.source = PWM0_FAULTS,
.polarity = PWM0_POL,
.latched = PWM0_LATCH,
.gpio_0 = GPIO_PWMC0_FI0,
.gpio_1 = GPIO_PWMC0_FI1,
.gpio_2 = GPIO_PWMC0_FI2,
};
/* Define sync flags */
#ifdef CONFIG_SAMV7_PWM0_SYNC
#define PWM0_CH0_SYNC_FLAG CH0_SYNC_FLAG
#ifdef CONFIG_SAMV7_PWM0_CH1_SYNC
#define PWM0_CH1_SYNC_FLAG CH1_SYNC_FLAG
#else
#define PWM0_CH1_SYNC_FLAG 0
#endif
#ifdef CONFIG_SAMV7_PWM0_CH2_SYNC
#define PWM0_CH2_SYNC_FLAG CH2_SYNC_FLAG
#else
#define PWM0_CH2_SYNC_FLAG 0
#endif
#ifdef CONFIG_SAMV7_PWM0_CH3_SYNC
#define PWM0_CH3_SYNC_FLAG CH3_SYNC_FLAG
#else
#define PWM0_CH3_SYNC_FLAG 0
#endif
#else
#define PWM0_CH0_SYNC_FLAG 0
#define PWM0_CH1_SYNC_FLAG 0
#define PWM0_CH2_SYNC_FLAG 0
#define PWM0_CH3_SYNC_FLAG 0
#endif
#define PWM0_SYNC_FLAGS (PWM0_CH0_SYNC_FLAG | PWM0_CH1_SYNC_FLAG | \
PWM0_CH2_SYNC_FLAG | PWM0_CH3_SYNC_FLAG)
static struct sam_pwm_s g_pwm0 =
{
.ops = &g_pwmops,
.channels = g_pwm0_channels,
.comparison = &g_pwm0_comparison,
.fault = &g_pwm0_fault,
.channels_num = PWM0_NCHANNELS,
.base = SAM_PWM0_BASE,
.sync = PWM0_SYNC_FLAGS,
};
#endif /* CONFIG_SAMV7_PWM0 */
#ifdef CONFIG_SAMV7_PWM1
static struct sam_pwm_channel_s g_pwm1_channels[] =
{
#ifdef CONFIG_SAMV7_PWM1_CH0
{
.channel = 0,
#ifdef CONFIG_SAMV7_PWM1_CH0_LONLY
.pin_h = 0,
.pin_l = GPIO_PWMC1_L0,
#else
.pin_h = GPIO_PWMC1_H0,
#ifdef CONFIG_SAMV7_PWM1_CH0_COMP
.pin_l = GPIO_PWMC1_L0,
#else
.pin_l = 0,
#endif
#endif
},
#endif
#ifdef CONFIG_SAMV7_PWM1_CH1
{
.channel = 1,
#ifdef CONFIG_SAMV7_PWM1_CH1_LONLY
.pin_h = 0,
.pin_l = GPIO_PWMC1_L1,
#else
.pin_h = GPIO_PWMC1_H1,
#ifdef CONFIG_SAMV7_PWM1_CH1_COMP
.pin_l = GPIO_PWMC1_L1,
#else
.pin_l = 0,
#endif
#endif
},
#endif
#ifdef CONFIG_SAMV7_PWM1_CH2
{
.channel = 2,
#ifdef CONFIG_SAMV7_PWM1_CH2_LONLY
.pin_h = 0,
.pin_l = GPIO_PWMC1_L2,
#else
.pin_h = GPIO_PWMC1_H2,
#ifdef CONFIG_SAMV7_PWM1_CH2_COMP
.pin_l = GPIO_PWMC1_L2,
#else
.pin_l = 0,
#endif
#endif
},
#endif
#ifdef CONFIG_SAMV7_PWM1_CH3
{
.channel = 3,
#ifdef CONFIG_SAMV7_PWM1_CH3_LONLY
.pin_h = 0,
.pin_l = GPIO_PWMC1_L3,
#else
.pin_h = GPIO_PWMC1_H3,
#ifdef CONFIG_SAMV7_PWM1_CH3_COMP
.pin_l = GPIO_PWMC1_L3,
#else
.pin_l = 0,
#endif
#endif
},
#endif
}; /* CONFIG_SAMV7_PWM1 */
static struct sam_pwm_comparison_s g_pwm1_comparison =
{
.comp_vals =
{
CONFIG_SAMV7_PWM1_TRIG0, CONFIG_SAMV7_PWM1_TRIG1,
CONFIG_SAMV7_PWM1_TRIG2, CONFIG_SAMV7_PWM1_TRIG3,
CONFIG_SAMV7_PWM1_TRIG4, CONFIG_SAMV7_PWM1_TRIG5,
CONFIG_SAMV7_PWM1_TRIG6, CONFIG_SAMV7_PWM1_TRIG7
},
#ifdef CONFIG_SAMV7_PWM0_EVENT0
.event0 = 1,
#else
.event0 = 0,
#endif
#ifdef CONFIG_SAMV7_PWM0_EVENT1
.event1 = 1,
#else
.event1 = 0,
#endif
};
static struct sam_pwm_fault_s g_pwm1_fault =
{
.source = PWM1_FAULTS,
.polarity = PWM1_POL,
.latched = PWM1_LATCH,
.gpio_0 = GPIO_PWMC1_FI0,
.gpio_1 = GPIO_PWMC1_FI1,
.gpio_2 = GPIO_PWMC1_FI2,
};
/* Define sync flags */
#ifdef CONFIG_SAMV7_PWM1_SYNC
#define PWM1_CH0_SYNC_FLAG CH0_SYNC_FLAG
#ifdef CONFIG_SAMV7_PWM1_CH1_SYNC
#define PWM1_CH1_SYNC_FLAG CH1_SYNC_FLAG
#else
#define PWM1_CH1_SYNC_FLAG 0
#endif
#ifdef CONFIG_SAMV7_PWM1_CH2_SYNC
#define PWM1_CH2_SYNC_FLAG CH2_SYNC_FLAG
#else
#define PWM1_CH2_SYNC_FLAG 0
#endif
#ifdef CONFIG_SAMV7_PWM1_CH3_SYNC
#define PWM1_CH3_SYNC_FLAG CH3_SYNC_FLAG
#else
#define PWM1_CH3_SYNC_FLAG 0
#endif
#else
#define PWM1_CH0_SYNC_FLAG 0
#define PWM1_CH1_SYNC_FLAG 0
#define PWM1_CH2_SYNC_FLAG 0
#define PWM1_CH3_SYNC_FLAG 0
#endif
#define PWM1_SYNC_FLAGS (PWM1_CH0_SYNC_FLAG | PWM1_CH1_SYNC_FLAG | \
PWM1_CH2_SYNC_FLAG | PWM1_CH3_SYNC_FLAG)
static struct sam_pwm_s g_pwm1 =
{
.ops = &g_pwmops,
.channels = g_pwm1_channels,
.comparison = &g_pwm1_comparison,
.fault = &g_pwm1_fault,
.channels_num = PWM1_NCHANNELS,
.base = SAM_PWM1_BASE,
.sync = PWM1_SYNC_FLAGS,
};
#endif
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
static void pwm_putreg(struct sam_pwm_s *priv, uint32_t offset,
uint32_t value);
static uint32_t pwm_getreg(struct sam_pwm_s *priv, uint32_t offset);
/* Helper functions */
static void pwm_set_output(struct pwm_lowerhalf_s *dev, uint8_t channel,
ub16_t duty);
static void pwm_set_freq(struct pwm_lowerhalf_s *dev, uint8_t channel,
uint32_t frequency);
static void pwm_set_comparison(struct pwm_lowerhalf_s *dev);
#ifdef CONFIG_PWM_DEADTIME
static void pwm_set_deadtime(struct pwm_lowerhalf_s *dev, uint8_t channel,
ub16_t dead_time_a, ub16_t dead_time_b,
ub16_t duty);
#endif
static void pwm_set_polarity(struct pwm_lowerhalf_s *dev, uint8_t channel,
uint8_t cpol, uint8_t dcpol);
/****************************************************************************
* Private Functions
****************************************************************************/
static void pwm_putreg(struct sam_pwm_s *priv, uint32_t offset,
uint32_t value)
{
putreg32(value, priv->base + offset);
}
static uint32_t pwm_getreg(struct sam_pwm_s *priv, uint32_t offset)
{
return getreg32(priv->base + offset);
}
/****************************************************************************
* Name: pwm_set_freq
*
* Description:
* Set timer frequency and change registers value to respect that
* frequency.
*
* Input Parameters:
* dev - A reference to the lower half PWM driver state structure
* channel - Channel to by updated
* frequency - New frequency
*
* Returned Value:
* None
*
****************************************************************************/
static void pwm_set_freq(struct pwm_lowerhalf_s *dev, uint8_t channel,
uint32_t frequency)
{
struct sam_pwm_s *priv = (struct sam_pwm_s *)dev;
uint32_t regval;
uint32_t newdiv = (CLK_FREQ + (frequency / 2)) / frequency - 1;
uint32_t prescale = 0;
while (newdiv > PWM_RES && prescale < 11)
{
newdiv = newdiv >> 1;
prescale++;
}
if (newdiv > PWM_RES)
{
newdiv = PWM_RES;
}
else if (newdiv < 2)
{
newdiv = 2;
}
regval = pwm_getreg(priv, SAMV7_PWM_CMRX + (channel * CHANNEL_OFFSET));
regval &= ~CMR_CPRE_MASK;
regval |= CMR_CPRE_SEL(prescale);
if (pwm_getreg(priv, SAMV7_PWM_SR) & CHID_SEL(1 << channel))
{
pwm_putreg(priv, SAMV7_PWM_CMUPDX + (channel * CHANNEL_OFFSET),
regval);
pwm_putreg(priv, SAMV7_PWM_CPRDUPDX + (channel * CHANNEL_OFFSET),
CPRD_CPRD_SEL(newdiv));
}
else
{
pwm_putreg(priv, SAMV7_PWM_CMRX + (channel * CHANNEL_OFFSET),
regval);
pwm_putreg(priv, SAMV7_PWM_CPRDX + (channel * CHANNEL_OFFSET),
CPRD_CPRD_SEL(newdiv));
}
}
/****************************************************************************
* Name: pwm_set_output
*
* Description:
* Set duty cycle and enable PWM output.
*
* Input Parameters:
* dev - A reference to the lower half PWM driver state structure
* channel - Channel to by updated
* duty - New duty
*
* Returned Value:
* None
*
****************************************************************************/
static void pwm_set_output(struct pwm_lowerhalf_s *dev, uint8_t channel,
ub16_t duty)
{
struct sam_pwm_s *priv = (struct sam_pwm_s *)dev;
uint16_t period;
uint16_t width;
uint16_t regval;
/* Get the period value */
period = pwm_getreg(priv, SAMV7_PWM_CPRDX + (channel * CHANNEL_OFFSET));
/* Compute PWM width (count value to set PWM low) */
width = b16toi(duty * period + b16HALF);
/* Update duty cycle */
if (pwm_getreg(priv, SAMV7_PWM_SR) & CHID_SEL(1 << channel))
{
pwm_putreg(priv, SAMV7_PWM_CDTYUPDX + (channel * CHANNEL_OFFSET),
width);
}
else
{
pwm_putreg(priv, SAMV7_PWM_CDTYX + (channel * CHANNEL_OFFSET),
width);
}
regval = CHID_SEL(1 << channel);
/* The enabling of a channel should be only done on unsynced channels */
if (!(priv->sync & regval))
{
pwm_putreg(priv, SAMV7_PWM_ENA, regval);
}
}
/****************************************************************************
* Name: pwm_set_comparison
*
* Description:
* Set comparison units.
*
* Input Parameters:
* dev - A reference to the lower half PWM driver state structure
*
* Returned Value:
* None
*
****************************************************************************/
static void pwm_set_comparison(struct pwm_lowerhalf_s *dev)
{
struct sam_pwm_s *priv = (struct sam_pwm_s *)dev;
uint16_t period;
uint16_t width;
uint16_t comp_value;
uint8_t comp_en;
int i;
/* Get the period value */
period = pwm_getreg(priv, SAMV7_PWM_CPRDX);
/* Compute PWM width (count value to set PWM low) */
comp_en = 0;
for (i = 0; i < COMP_UNITS_NUM; i++)
{
if (priv->comparison->comp_vals[i] == 0)
{
continue;
}
comp_value = b16divi(uitoub16(priv->comparison->comp_vals[i]), 100);
width = b16toi(comp_value * period + b16HALF);
/* Generate event line */
if (pwm_getreg(priv, SAMV7_PWM_CMPMX + COMP_OFFSET * i) & CMPM_CEN)
{
/* Use update register if comparison unit is used */
pwm_putreg(priv, SAMV7_PWM_CMPVUPDX + COMP_OFFSET * i, width);
pwm_putreg(priv, SAMV7_PWM_CMPMUPDX + COMP_OFFSET * i, CMPM_CEN);
}
else
{
pwm_putreg(priv, SAMV7_PWM_CMPVX + COMP_OFFSET * i, width);
pwm_putreg(priv, SAMV7_PWM_CMPMX + COMP_OFFSET * i, CMPM_CEN);
}
comp_en += 1 << i;
}
if (priv->comparison->event0)
{
/* Enable output on Event Line 0 */
pwm_putreg(priv, SAMV7_PWM_ELMR1, comp_en);
}
if (priv->comparison->event1)
{
/* Enable output on Event Line 1 */
pwm_putreg(priv, SAMV7_PWM_ELMR2, comp_en);
}
}
/****************************************************************************
* Name: pwm_set_deadtime
*
* Description:
* Set deadtime generator values.
*
* Input Parameters:
* dev - A reference to the lower half PWM driver state structure
* channel - Channel to by updated
* dead_time_a - dead time value for output A
* dead_time_b - dead time value for output B
* duty - channel duty cycle
*
* Returned Value:
* None
*
****************************************************************************/
#ifdef CONFIG_PWM_DEADTIME
static void pwm_set_deadtime(struct pwm_lowerhalf_s *dev, uint8_t channel,
ub16_t dead_time_a, ub16_t dead_time_b,
ub16_t duty)
{
struct sam_pwm_s *priv = (struct sam_pwm_s *)dev;
uint16_t period;
uint16_t width_1;
uint16_t width_2;
uint16_t regval;
/* Get the period value */
period = pwm_getreg(priv, SAMV7_PWM_CPRDX + (channel * CHANNEL_OFFSET));
/* Compute channel's duty cycle value. Dead time counter has only 12 bits
* and not 16 as duty cycle or period counter. Therefore a 12 bits recount
* is necessary to set the dead time value corresponding to selected
* frequency. This expects the dead time value selected in the application
* is moved left by 12 and divided by 100. For example:
* dead_time_a = (selected_dead_time_duty << 12) / 100
* This approach is the same as with duty cycle setup in the application
* but with 12 bits.
*
* Also note that it might not be possible to get correct delay on lower
* frequencies since dead time register has only 12 bits.
*/
width_1 = (dead_time_a * period) >> 12;
width_2 = (dead_time_b * period) >> 12;
regval = b16toi(duty * period + b16HALF);
/* It is required width_1 < (CORD - CDTY) and
* width_2 < CDTY
*/
if (width_1 > (period - regval))
{
pwmerr("ERROR: Dead Time value DTH has to be < period - duty! "
"Setting DTH to 0\n");
width_1 = 0;
}
if (width_2 > regval)
{
pwmerr("ERROR: Dead Time value DTL has to be < duty! "
"Setting DTL to 0\n");
width_2 = 0;
}
/* Update dead time value */
if (pwm_getreg(priv, SAMV7_PWM_SR) & CHID_SEL(1 << channel))
{
pwm_putreg(priv, SAMV7_PWM_DTUPDX + (channel * CHANNEL_OFFSET),
DTUPD_DTHUPD_SEL(width_1) | DTUPD_DTLUPD_SEL(width_2));
}
else
{
pwm_putreg(priv, SAMV7_PWM_DTX + (channel * CHANNEL_OFFSET),
DT_DTH_SEL(width_1) | DT_DTL_SEL(width_2));
}
}
#endif
/****************************************************************************
* Name: pwm_set_polarity
*
* Description:
* Set channel polarity
*
* Input Parameters:
* dev - A reference to the lower half PWM driver state structure
* channel - Channel to by updated
* cpol - Desired polarity
* dcpol - Desired default polarity of a disabled channel
*
* Returned Value:
* None
*
****************************************************************************/
static void pwm_set_polarity(struct pwm_lowerhalf_s *dev, uint8_t channel,
uint8_t cpol, uint8_t dcpol)
{
struct sam_pwm_s *priv = (struct sam_pwm_s *)dev;
uint32_t regval;
/* Can't change polarity, if the channel is enabled! */
if (pwm_getreg(priv, SAMV7_PWM_SR) & CHID_SEL(1 << channel))
{
return;
}
regval = pwm_getreg(priv, SAMV7_PWM_CMRX + (channel * CHANNEL_OFFSET));
regval &= ~CMR_CPOL;
regval &= ~CMR_DPOLI;
if (cpol == PWM_CPOL_HIGH)
{
regval |= CMR_CPOL;
}
if ((dcpol == PWM_DCPOL_LOW && cpol == PWM_CPOL_HIGH) ||
(dcpol == PWM_DCPOL_HIGH && cpol == PWM_CPOL_LOW))
{
regval |= CMR_DPOLI;
}
pwm_putreg(priv, SAMV7_PWM_CMRX + (channel * CHANNEL_OFFSET), regval);
}
/****************************************************************************
* 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
*
****************************************************************************/
static int pwm_setup(struct pwm_lowerhalf_s *dev)
{
struct sam_pwm_s *priv = (struct sam_pwm_s *)dev;
gpio_pinset_t pin_h = 0;
gpio_pinset_t pin_l = 0;
uint8_t channel;
uint32_t regval;
/* Unlock User Interface */
regval = WPCR_WPRCMD_SEL(0) | WPCR_WPRG_MASK | WPCR_WPKEY_SEL(0x50574d);
pwm_putreg(priv, SAMV7_PWM_WPCR, regval);
/* Disable all channels */
regval = CHID_SEL(CHID_MASK);
pwm_putreg(priv, SAMV7_PWM_DIS, regval);
for (int i = 0; i < priv->channels_num; i++)
{
pin_h = priv->channels[i].pin_h;
pin_l = priv->channels[i].pin_l;
if (pin_h != 0)
{
sam_configgpio(pin_h);
}
if (pin_l != 0)
{
sam_configgpio(pin_l);
}
channel = priv->channels[i].channel;
#ifdef CONFIG_PWM_DEADTIME
pwm_putreg(priv, SAMV7_PWM_CMRX + (channel * CHANNEL_OFFSET), CMR_DTE);
#else
pwm_putreg(priv, SAMV7_PWM_CMRX + (channel * CHANNEL_OFFSET), 0);
#endif
/* Reset duty cycle register */
pwm_putreg(priv, SAMV7_PWM_CDTYX + (channel * CHANNEL_OFFSET), 0);
/* Reset period register */
pwm_putreg(priv, SAMV7_PWM_CPRDX + (channel * CHANNEL_OFFSET), 0);
/* Reset Dead Time Register */
pwm_putreg(priv, SAMV7_PWM_DTX + (channel * CHANNEL_OFFSET), 0);
/* Enable fault protection if configured. The protection has to
* be enabled for every configured channel separately.
*/
regval = pwm_getreg(priv, SAMV7_PWM_FPE);
regval |= priv->fault->source << (FAULT_SEL_OFFSET * channel);
pwm_putreg(priv, SAMV7_PWM_FPE, regval);
}
/* Configure fault GPIOs if used */
if (priv->fault->source & 1)
{
sam_configgpio(priv->fault->gpio_0);
}
if (priv->fault->source & (1 << 1))
{
sam_configgpio(priv->fault->gpio_1);
}
if (priv->fault->source & (1 << 2))
{
sam_configgpio(priv->fault->gpio_2);
}
/* Set fault polarity. This has to be 1 for peripheral fault
* generation (from ADC for example), GPIO generated fault
* is set via configuration options.
*/
regval = FMR_FPOL_SEL(priv->fault->polarity) |
FMR_FMOD_SEL(priv->fault->latched);
pwm_putreg(priv, SAMV7_PWM_FMR, regval);
/* Force both outputs to 0 if fault occurs */
pwm_putreg(priv, SAMV7_PWM_FPV1, 0);
pwm_putreg(priv, SAMV7_PWM_FPV2, 0);
/* Enable synchronous channels. The flags in priv->sync
* correspond to the lowest bits in PWM_SCM.
* UPDM[1:0] is set to zero (manual update of deadtime, duty).
*/
regval = (uint32_t)priv->sync;
pwm_putreg(priv, SAMV7_PWM_SCM, regval);
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_s *priv = (struct sam_pwm_s *)dev;
uint32_t regval;
/* Disable all channels and interrupts */
regval = CHID_SEL(CHID_MASK);
pwm_putreg(priv, SAMV7_PWM_DIS, regval);
regval = IR1_CHID_SEL(CHID_MASK);
pwm_putreg(priv, SAMV7_PWM_IDR1, regval);
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_s *priv = (struct sam_pwm_s *)dev;
#ifdef CONFIG_PWM_MULTICHAN
uint32_t regval;
for (int i = 0; i < PWM_NCHANNELS; i++)
{
int8_t index = info->channels[i].channel;
/* Break the loop if all following channels are not configured */
if (index == -1)
{
break;
}
/* Configure the module freq only if is set to be used */
if (index > 0 && (index - 1) < priv->channels_num)
{
/* Set the frequency and enable PWM output for each channel */
pwm_set_freq(dev, priv->channels[index - 1].channel,
info->frequency);
#ifdef CONFIG_PWM_DEADTIME
pwm_set_deadtime(dev, priv->channels[index - 1].channel,
info->channels[i].dead_time_a,
info->channels[i].dead_time_b,
info->channels[i].duty);
#endif
pwm_set_polarity(dev, priv->channels[index - 1].channel,
info->channels[i].cpol,
info->channels[i].dcpol);
pwm_set_output(dev, priv->channels[index - 1].channel,
info->channels[i].duty);
#ifdef CONFIG_PWM_OVERWRITE
if (info->channels[i].ch_outp_ovrwr)
{
regval = pwm_getreg(priv, SAMV7_PWM_OOV);
regval &= ~(info->channels[i].ch_outp_ovrwr_val
<< priv->channels[i].channel);
pwm_putreg(priv, SAMV7_PWM_OOV, regval);
regval = (1 << priv->channels[i].channel);
pwm_putreg(priv, SAMV7_PWM_OSS, regval);
}
else
{
/* Release overwrite of channel */
regval = (1 << priv->channels[i].channel);
pwm_putreg(priv, SAMV7_PWM_OSC, regval);
}
#endif
}
}
/* Perform the update of synchronized PWM channels */
if (priv->sync)
{
regval = SCUC_UPDULOCK;
/* Enable the Channel 0 if synchronous channels are used.
* Channel 0's counter is used by all synchronous channels and
* enabling CH0 results in enabling all synchronous channels.
*
* Enable the CH0 here after all setting all channel parameters,
* because setting polarity configurations requires disabled
* channels.
*/
pwm_putreg(priv, SAMV7_PWM_ENA, CHID_SEL(1));
pwm_putreg(priv, SAMV7_PWM_SCUC, regval);
}
#else
/* Set the frequency and enable PWM output just for first channel */
pwm_set_freq(dev, priv->channels[0].channel, info->frequency);
#ifdef CONFIG_PWM_DEADTIME
pwm_set_deadtime(dev, priv->channels[0].channel,
info->dead_time_a, info->dead_time_b);
#endif
pwm_set_polarity(dev, priv->channels[0].channel,
info->cpol, info->dcpol);
pwm_set_output(dev, priv->channels[0].channel, info->duty);
#endif
pwm_set_comparison(dev);
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_s *priv = (struct sam_pwm_s *)dev;
uint32_t regval;
#ifdef CONFIG_PWM_MULTICHAN
for (int i = 0; i < priv->channels_num; i++)
{
regval = CHID_SEL(1 << priv->channels[i].channel);
pwm_putreg(priv, SAMV7_PWM_DIS, regval);
}
/* Just to be sure, disable all sync channels too */
regval = pwm_getreg(priv, SAMV7_PWM_SCM);
regval &= ~(CHID_SEL(1 << 0) | CHID_SEL(1 << 1) |
CHID_SEL(1 << 2) | CHID_SEL(1 << 3));
pwm_putreg(priv, SAMV7_PWM_SCM, regval);
#else
regval = CHID_SEL(1 << priv->channels[0].channel);
pwm_putreg(priv, SAMV7_PWM_DIS, regval);
#endif /* CONFIG_PWM_MULTICHAN */
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)
{
struct sam_pwm_s *priv = (struct sam_pwm_s *)dev;
uint32_t regval;
int ret = OK;
switch (cmd)
{
case PWMIOC_FAULTS_FETCH_AND_CLEAR:
{
unsigned long clear = arg != 0 ?
*(unsigned long *)(uintptr_t)arg : FCR_FCLR_MASK;
/* Get current faults. */
regval = pwm_getreg(priv, SAMV7_PWM_FSR);
/* Clear the faults. */
clear &= ((regval & FSR_FS_MASK) >> FSR_FS_SHIFT);
pwm_putreg(priv, SAMV7_PWM_FCR, FCR_FCLR_SEL(clear));
/* And return the previously read faults. */
if (arg != 0)
{
*(unsigned long *)(uintptr_t)arg =
(regval & FSR_FS_MASK) >> FSR_FS_SHIFT;
}
}
break;
default:
{
pwmerr("ERROR: Unknown cmd: %d\n", cmd);
ret = -ENOTTY;
}
}
return ret;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Function: sam_pwminitialize
*
* Description:
* Initialize the PWM channel for use with the upper level PWM driver.
*
* Input Parameters:
* channel - a number identifying the PWM channel.
*
* Returned Value:
* A pointer to the lower half PWM driver is returned on success,
* NULL on failure.
*
****************************************************************************/
struct pwm_lowerhalf_s *sam_pwminitialize(int pwm)
{
struct sam_pwm_s *priv;
pwminfo("Initializing pwm %d\n", pwm);
switch (pwm)
{
#ifdef CONFIG_SAMV7_PWM0
case 0:
sam_pwm0_enableclk();
priv = &g_pwm0;
break;
#endif
#ifdef CONFIG_SAMV7_PWM1
case 1:
sam_pwm1_enableclk();
priv = &g_pwm1;
break;
#endif
default:
pwmerr("ERROR: PWM number invalid or not configured %d\n", pwm);
return NULL;
}
return (struct pwm_lowerhalf_s *)priv;
}
#endif /* CONFIG_SAMV7_PWM */