arch/arm/samv7: Fix PWM operation for single channel mode

The current SAMv7 PWM driver assumes that all PWM channels should
work in sync mode, but that is a partial case of a generic PWM
driver operation.

Start SAMv7 PWM channels in async mode. The sync mode should be
implemeted either using ioctl command or via a separate Kconfig
option.

Signed-off-by: Petro Karashchenko <petro.karashchenko@gmail.com>
This commit is contained in:
Petro Karashchenko
2022-05-24 03:26:13 +08:00
committed by Xiang Xiao
parent 3ea7d4bab4
commit 7a6253cb89
5 changed files with 93 additions and 186 deletions
+5 -7
View File
@@ -569,13 +569,13 @@ static int pwm_change_freq(struct pwm_lowerhalf_s *dev,
uint16_t regval;
uint16_t olddiv = getreg16(priv->base + IMXRT_FLEXPWM_SM0VAL1_OFFSET
+ MODULE_OFFSET * shift);
uint16_t newdiv = (uint32_t)((float)CLK_FREQ / info->frequency + 0.5f);
uint32_t newdiv = (CLK_FREQ + (info->frequency / 2)) / info->frequency - 1;
uint16_t prescale = 0;
while (newdiv > PWM_RES && prescale < 7)
{
newdiv = newdiv >> 1;
prescale = prescale + 1;
prescale++;
}
if (newdiv > PWM_RES)
@@ -595,8 +595,8 @@ static int pwm_change_freq(struct pwm_lowerhalf_s *dev,
putreg16(regval, priv->base + IMXRT_FLEXPWM_SM0CTRL_OFFSET
+ MODULE_OFFSET * shift);
putreg16(newdiv - 1, priv->base + IMXRT_FLEXPWM_SM0VAL1_OFFSET
+ MODULE_OFFSET * shift);
putreg16(newdiv, priv->base + IMXRT_FLEXPWM_SM0VAL1_OFFSET
+ MODULE_OFFSET * shift);
/* Update VAL0, VAL3 and VAL5 registers */
@@ -644,7 +644,6 @@ static int pwm_set_output(struct pwm_lowerhalf_s *dev, uint8_t channel,
uint16_t period;
uint16_t width;
uint16_t regval;
double duty_pct;
uint8_t shift = channel - 1; /* Shift submodle offset addresses */
/* Get the period value */
@@ -654,8 +653,7 @@ static int pwm_set_output(struct pwm_lowerhalf_s *dev, uint8_t channel,
/* Compute PWM width (count value to set PWM low) */
duty_pct = (duty / 65536.0) * 100;
width = (uint16_t)(((uint16_t)duty_pct * period) / 100);
width = b16toi(duty * period + b16HALF);
/* Clear corresponding MCTRL[LDOK] bit */
+2 -2
View File
@@ -119,7 +119,7 @@
# define CLK_DIVB_CLKB_POFF (0 << CLK_DIVB_SHIFT) /* CLKB clock is turned off */
# define CLK_DIVB_PREB (1 << CLK_DIVB_SHIFT) /* CLKB clock is selected by PREB */
#define CLK_PREB_SHIFT (24) /* Bits: 24-27 CLKA Source Clock Selection */
#define CLK_PREB_SHIFT (24) /* Bits: 24-27 CLKB Source Clock Selection */
#define CLK_PREB_MASK (0x7 << CLK_PREB_SHIFT)
# define CLK_PREB_CLK (0 << CLK_PREB_SHIFT) /* Peripheral Clock */
# define CLK_PREB_CLK_DIV2 (1 << CLK_PREB_SHIFT) /* Peripheral Clock/2 */
@@ -172,7 +172,7 @@
/* DMA Register */
#define DMAR_DMADUTY_SHIFT (0) /* Bits: 0-23 Duty Cycle Holding Register */
#define DMAR_DMADUTY_MAS (0xfffff << DMAR_DMADUTY_SHIFT)
#define DMAR_DMADUTY_MASK (0xffff << DMAR_DMADUTY_SHIFT)
# define DMAR_DMADUTY_SEL(n) ((uint32_t)(n) << DMAR_DMADUTY_SHIFT)
/* Sync Channels Update Control Register */
+83 -172
View File
@@ -53,14 +53,14 @@
#ifdef CONFIG_SAMV7_PWM
#ifdef CONFIG_PWM_NCHANNELS
# define PWM_NCHANNELS CONFIG_PWM_NCHANNELS
# define PWM_NCHANNELS CONFIG_PWM_NCHANNELS
#else
# define PWM_NCHANNELS 1
# define PWM_NCHANNELS 1
#endif
#define CHANNEL_OFFSET 0x20
#define CLK_FREQ BOARD_MCK_FREQUENCY
#define PWM_RES 65535
#define CHANNEL_OFFSET 0x20
#define CLK_FREQ BOARD_MCK_FREQUENCY
#define PWM_RES 65535
/****************************************************************************
* Pre-processor Definitions
@@ -73,8 +73,7 @@
struct sam_pwm_channel_s
{
uint8_t channel; /* Number of PWM module */
bool used; /* True if the module is used */
uint32_t pin; /* PWM output pin */
gpio_pinset_t pin; /* PWM output pin */
};
struct sam_pwm_s
@@ -82,8 +81,7 @@ struct sam_pwm_s
const struct pwm_ops_s *ops; /* PWM operations */
const struct sam_pwm_channel_s *channels;
uint8_t channels_num; /* Number of channels */
uint32_t frequency; /* PWM frequency */
uint32_t base; /* Base address of peripheral register */
uintptr_t base; /* Base address of peripheral register */
};
/* PWM driver methods */
@@ -116,28 +114,24 @@ static struct sam_pwm_channel_s g_pwm0_channels[] =
#ifdef CONFIG_SAMV7_PWM0_CH0
{
.channel = 0,
.used = true,
.pin = GPIO_PWMC0_H0,
},
#endif
#ifdef CONFIG_SAMV7_PWM0_CH1
{
.channel = 1,
.used = true,
.pin = GPIO_PWMC0_H1,
},
#endif
#ifdef CONFIG_SAMV7_PWM0_CH2
{
.channel = 2,
.used = true,
.pin = GPIO_PWMC0_H2,
},
#endif
#ifdef CONFIG_SAMV7_PWM0_CH3
{
.channel = 3,
.used = true,
.pin = GPIO_PWMC0_H3,
},
#endif
@@ -148,7 +142,6 @@ static struct sam_pwm_s g_pwm0 =
.ops = &g_pwmops,
.channels = g_pwm0_channels,
.channels_num = PWM0_NCHANNELS,
.frequency = 0,
.base = SAM_PWM0_BASE,
};
#endif /* CONFIG_SAMV7_PWM0 */
@@ -160,28 +153,24 @@ static struct sam_pwm_channel_s g_pwm1_channels[] =
#ifdef CONFIG_SAMV7_PWM1_CH0
{
.channel = 0,
.used = true,
.pin = GPIO_PWMC1_H0
},
#endif
#ifdef CONFIG_SAMV7_PWM1_CH1
{
.channel = 1,
.used = true,
.pin = GPIO_PWMC1_H1
},
#endif
#ifdef CONFIG_SAMV7_PWM1_CH2
{
.channel = 2,
.used = true,
.pin = GPIO_PWMC1_H2
},
#endif
#ifdef CONFIG_SAMV7_PWM1_CH3
{
.channel = 3,
.used = true,
.pin = GPIO_PWMC1_H3
},
#endif
@@ -192,7 +181,6 @@ static struct sam_pwm_s g_pwm1 =
.ops = &g_pwmops,
.channels = g_pwm1_channels,
.channels_num = PWM1_NCHANNELS,
.frequency = 0,
.base = SAM_PWM1_BASE,
};
@@ -208,10 +196,10 @@ static uint32_t pwm_getreg(struct sam_pwm_s *priv, uint32_t offset);
/* Helper functions */
static int pwm_set_output(struct pwm_lowerhalf_s *dev, uint8_t channel,
ub16_t duty);
static int pwm_change_freq(struct pwm_lowerhalf_s *dev,
const struct pwm_info_s *info, uint8_t channel);
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);
/****************************************************************************
* Private Functions
@@ -229,40 +217,34 @@ static uint32_t pwm_getreg(struct sam_pwm_s *priv, uint32_t offset)
}
/****************************************************************************
* Name: pwm_change_freq
* 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
* info - A reference to the characteristics of the pulsed output
* dev - A reference to the lower half PWM driver state structure
* channel - Channel to by updated
* frequency - New frequency
*
* Returned Value:
* Zero on success; a negated errno value on failure
* None
*
****************************************************************************/
static int pwm_change_freq(struct pwm_lowerhalf_s *dev,
const struct pwm_info_s *info, uint8_t channel)
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;
#ifdef CONFIG_PWM_MULTICHAN
uint8_t shift = info->channels[channel].channel - 1;
#else
uint8_t shift = priv->channels[0].channel;
#endif
uint32_t regval;
uint32_t olddiv = pwm_getreg(priv, SAMV7_PWM_CPRDX
+ (shift * CHANNEL_OFFSET));
uint32_t newdiv = (uint32_t)((float)CLK_FREQ / info->frequency + 0.5f);
uint32_t newdiv = (CLK_FREQ + (frequency / 2)) / frequency - 1;
uint32_t prescale = 0;
while (newdiv > PWM_RES && prescale < 11)
{
newdiv = newdiv >> 1;
prescale = prescale + 1;
prescale++;
}
if (newdiv > PWM_RES)
@@ -274,19 +256,26 @@ static int pwm_change_freq(struct pwm_lowerhalf_s *dev,
newdiv = 2;
}
regval = pwm_getreg(priv, SAMV7_PWM_CMRX + (shift * CHANNEL_OFFSET));
regval = pwm_getreg(priv, SAMV7_PWM_CMRX + (channel * CHANNEL_OFFSET));
regval &= ~CMR_CPRE_MASK;
regval |= CMR_CPRE_SEL(prescale);
pwm_putreg(priv, SAMV7_PWM_CMRX + (shift * CHANNEL_OFFSET), regval);
pwm_putreg(priv, SAMV7_PWM_CPRDUPDX + (shift * CHANNEL_OFFSET),
newdiv - 1);
if (pwm_getreg(priv, SAMV7_PWM_SR) & CHID_SEL(1 << channel))
{
pwm_putreg(priv, SAMV7_PWM_CMUPDX + (channel * CHANNEL_OFFSET),
regval);
regval = pwm_getreg(priv, SAMV7_PWM_CDTYX + (shift * CHANNEL_OFFSET));
regval = regval * newdiv / olddiv;
pwm_putreg(priv, SAMV7_PWM_CDTYUPDX + (shift * 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);
return OK;
pwm_putreg(priv, SAMV7_PWM_CPRDX + (channel * CHANNEL_OFFSET),
CPRD_CPRD_SEL(newdiv));
}
}
/****************************************************************************
@@ -301,37 +290,43 @@ static int pwm_change_freq(struct pwm_lowerhalf_s *dev,
* duty - New duty
*
* Returned Value:
* Zero on success; a negated errno value on failure
* None
*
****************************************************************************/
static int pwm_set_output(struct pwm_lowerhalf_s *dev, uint8_t channel,
ub16_t duty)
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;
double duty_pct;
uint8_t shift = channel; /* Shift submodle offset addresses */
/* Get the period value */
period = pwm_getreg(priv, SAMV7_PWM_CPRDX + (shift * CHANNEL_OFFSET));
period = pwm_getreg(priv, SAMV7_PWM_CPRDX + (channel * CHANNEL_OFFSET));
/* Compute PWM width (count value to set PWM low) */
duty_pct = (duty / 65536.0) * 100;
width = (uint16_t)(((uint16_t)duty_pct * period) / 100);
width = b16toi(duty * period + b16HALF);
/* Update duty cycle */
pwm_putreg(priv, SAMV7_PWM_CDTYUPDX + (shift * CHANNEL_OFFSET), width);
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 << shift);
/* Enable the channel */
regval = CHID_SEL(1 << channel);
pwm_putreg(priv, SAMV7_PWM_ENA, regval);
return OK;
}
/****************************************************************************
@@ -353,7 +348,8 @@ static int pwm_set_output(struct pwm_lowerhalf_s *dev, uint8_t channel,
static int pwm_setup(struct pwm_lowerhalf_s *dev)
{
struct sam_pwm_s *priv = (struct sam_pwm_s *)dev;
uint32_t pin = 0;
gpio_pinset_t pin = 0;
uint8_t channel;
uint32_t regval;
/* Unlock User Interface */
@@ -368,13 +364,6 @@ static int pwm_setup(struct pwm_lowerhalf_s *dev)
for (int i = 0; i < priv->channels_num; i++)
{
/* Configure the channel only if is set to be used */
if (priv->channels[i].used != 1)
{
continue;
}
pin = priv->channels[i].pin;
if (pin != 0)
@@ -382,39 +371,28 @@ static int pwm_setup(struct pwm_lowerhalf_s *dev)
sam_configgpio(pin);
}
sam_configgpio(pin);
channel = priv->channels[i].channel;
regval = CMR_CPOL | CMR_DPOLI;
pwm_putreg(priv, SAMV7_PWM_CMRX + (i * CHANNEL_OFFSET), regval);
pwm_putreg(priv, SAMV7_PWM_CMRX + (channel * CHANNEL_OFFSET), regval);
/* Set duty cycle register */
/* Reset duty cycle register */
pwm_putreg(priv, SAMV7_PWM_CDTYX + (i * CHANNEL_OFFSET), 0);
pwm_putreg(priv, SAMV7_PWM_CDTYX + (channel * CHANNEL_OFFSET), 0);
/* Set period register with default period */
/* Reset period register */
regval = 0x82b8;
pwm_putreg(priv, SAMV7_PWM_CPRDX + (i * CHANNEL_OFFSET), regval);
pwm_putreg(priv, SAMV7_PWM_CPRDX + (channel * CHANNEL_OFFSET), 0);
/* Reset Dead Time Register */
pwm_putreg(priv, SAMV7_PWM_DTX + (i * CHANNEL_OFFSET), 0);
pwm_putreg(priv, SAMV7_PWM_DTX + (channel * CHANNEL_OFFSET), 0);
/* Fault protection registers */
pwm_putreg(priv, SAMV7_PWM_FPV1, 0);
pwm_putreg(priv, SAMV7_PWM_FPV2, 0);
pwm_putreg(priv, SAMV7_PWM_FPE, 0);
/* Enable the channel */
regval = CHID_SEL(1 << i);
pwm_putreg(priv, SAMV7_PWM_ENA, regval);
/* Set synchronous output */
regval = SCM_SYNC_SEL(1 << i);
pwm_putreg(priv, SAMV7_PWM_SCM, regval);
}
return OK;
@@ -449,28 +427,6 @@ static int pwm_shutdown(struct pwm_lowerhalf_s *dev)
regval = IR1_CHID_SEL(CHID_MASK);
pwm_putreg(priv, SAMV7_PWM_IDR1, regval);
for (int i = 0; i < priv->channels_num; i++)
{
/* Skip modules that are not used */
if (priv->channels[i].used != 1)
{
continue;
}
/* Reset period register */
pwm_putreg(priv, SAMV7_PWM_CPRDX + (i * CHANNEL_OFFSET), 0);
/* Reset duty cycle register */
pwm_putreg(priv, SAMV7_PWM_CDTYX + (i * CHANNEL_OFFSET), 0);
/* Reset Dead Time Register */
pwm_putreg(priv, SAMV7_PWM_DTX + (i * CHANNEL_OFFSET), 0);
}
return OK;
}
@@ -493,71 +449,39 @@ 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;
int ret = OK;
/* Change frequency only if it is needed */
if (info->frequency != priv->frequency)
{
#ifdef CONFIG_PWM_MULTICHAN
for (int i = 0; i < PWM_NCHANNELS; i++)
{
#ifdef CONFIG_PWM_MULTICHAN
int8_t index = info->channels[i].channel;
/* Break the loop if all following channels are not configured */
if (info->channels[i].channel == -1)
if (index == -1)
{
break;
}
/* Configure the module freq only if is set to be used */
if (info->channels[i].channel != 0)
if (index > 0 && (index - 1) < priv->channels_num)
{
ret = pwm_change_freq(dev, info, i);
/* Set the frequency and enable PWM output for each channel */
pwm_set_freq(dev, priv->channels[index - 1].channel,
info->frequency);
pwm_set_output(dev, priv->channels[index - 1].channel,
info->channels[i].duty);
}
}
#else
ret = pwm_change_freq(dev, info, i);
/* Set the frequency and enable PWM output just for first channel */
pwm_set_freq(dev, priv->channels[0].channel, info->frequency);
pwm_set_output(dev, priv->channels[0].channel, info->duty);
#endif
}
/* Save current frequency */
if (ret == OK)
{
priv->frequency = info->frequency;
}
}
#ifdef CONFIG_PWM_MULTICHAN
for (int i = 0; ret == OK && i < PWM_NCHANNELS; i++)
{
/* Break the loop if all following channels are not configured */
if (info->channels[i].channel == -1)
{
break;
}
/* Enable PWM output for each channel */
if (info->channels[i].channel != 0)
{
ret = pwm_set_output(dev, info->channels[i].channel - 1,
info->channels[i].duty);
}
}
#else
/* Enable PWM output just for first channel */
ret = pwm_set_output(dev, priv->channels[0].channel, info->duty);
#endif /* CONFIG_PWM_MULTICHAN */
/* Set sychnronous outputs */
pwm_putreg(priv, SAMV7_PWM_SCUC, SCUC_UPDULOCK);
return ret;
return OK;
}
/****************************************************************************
@@ -582,31 +506,18 @@ static int pwm_start(struct pwm_lowerhalf_s *dev,
static int pwm_stop(struct pwm_lowerhalf_s *dev)
{
struct sam_pwm_s *priv = (struct sam_pwm_s *)dev;
uint8_t shift;
uint32_t regval;
#ifdef CONFIG_PWM_MULTICHAN
for (int i = 0; i < priv->channels_num; i++)
{
/* Skip settings if channel is not configured */
if (!priv->channels[i].used)
{
continue;
}
shift = priv->channels[i].channel - 1;
regval = CHID_SEL(1 << shift);
regval = CHID_SEL(1 << priv->channels[i].channel);
pwm_putreg(priv, SAMV7_PWM_DIS, regval);
}
#else
shift = priv->channels[0].channel;
regval = CHID_SEL(1 << shift);
pwm_putreg(priv, SAMV7_PWM_DIS, regval);
regval = CHID_SEL(1 << priv->channels[0].channel);
pwm_putreg(priv, SAMV7_PWM_DIS, regval);
#endif /* CONFIG_PWM_MULTICHAN */
return OK;
+2 -4
View File
@@ -60,8 +60,7 @@
#define PWM0_CH3 0
#endif
#define PWM0_NCHANNELS (PWM0_CH0 + PWM0_CH1 + \
PWM0_CH2 + PWM0_CH3)
#define PWM0_NCHANNELS (PWM0_CH0 + PWM0_CH1 + PWM0_CH2 + PWM0_CH3)
#ifdef CONFIG_SAMV7_PWM1_CH0
#define PWM1_CH0 1
@@ -87,8 +86,7 @@
#define PWM1_CH3 0
#endif
#define PWM1_NCHANNELS (PWM1_CH0 + PWM1_CH1 + \
PWM1_CH2 + PWM1_CH3)
#define PWM1_NCHANNELS (PWM1_CH0 + PWM1_CH1 + PWM1_CH2 + PWM1_CH3)
/****************************************************************************
* Public Function Prototypes
+1 -1
View File
@@ -119,7 +119,7 @@
#ifdef CONFIG_PWM_MULTICHAN
struct pwm_chan_s
{
ub16_t duty;
ub16_t duty;
int8_t channel;
};
#endif