Commit 5a9fc9c6 authored by Vladimir Zapolskiy's avatar Vladimir Zapolskiy Committed by Thierry Reding

pwm: lpc32xx: fix and simplify duty cycle and period calculations

The change fixes a problem, if duty_ns is too small in comparison
to period_ns (as a valid corner case duty_ns is 0 ns), then due to
PWM_DUTY() macro applied on a value the result is overflowed over 8
bits, and instead of the highest bitfield duty cycle value 0xff the
invalid duty cycle bitfield value 0x00 is written.

For reference the LPC32xx spec defines PWMx_DUTY bitfield description
is this way and it seems to be correct:

 [Low]/[High] = [PWM_DUTY]/[256-PWM_DUTY], where 0 < PWM_DUTY <= 255.

In addition according to my oscilloscope measurements LPC32xx PWM is
"tristate" in sense that it produces a wave with floating min/max
voltage levels for different duty cycle values, for corner cases:

  PWM_DUTY == 0x01 => signal is in range from -1.05v to 0v
  ....
  PWM_DUTY == 0x80 => signal is in range from -0.75v to +0.75v
  ....
  PWM_DUTY == 0xff => signal is in range from 0v to +1.05v

  PWM_DUTY == 0x00 => signal is around 0v, PWM is off

Due to this peculiarity on very long period ranges (less than 1KHz)
and odd pre-divider values PWM generated wave does not remind a
clock shape signal, but rather a heartbit shape signal with positive
and negative peaks, so I would recommend to use high-speed HCLK clock
as a PWM parent clock and avoid using RTC clock as a parent.

The change corrects PWM output in corner cases and prevents any
possible overflows in calculation of values for PWM_DUTY and
PWM_RELOADV bitfields, thus helper macro definitions may be removed.
Signed-off-by: default avatarVladimir Zapolskiy <vz@mleia.com>
Signed-off-by: default avatarThierry Reding <thierry.reding@gmail.com>
parent 82aff048
...@@ -24,9 +24,7 @@ struct lpc32xx_pwm_chip { ...@@ -24,9 +24,7 @@ struct lpc32xx_pwm_chip {
void __iomem *base; void __iomem *base;
}; };
#define PWM_ENABLE (1 << 31) #define PWM_ENABLE BIT(31)
#define PWM_RELOADV(x) (((x) & 0xFF) << 8)
#define PWM_DUTY(x) ((x) & 0xFF)
#define to_lpc32xx_pwm_chip(_chip) \ #define to_lpc32xx_pwm_chip(_chip) \
container_of(_chip, struct lpc32xx_pwm_chip, chip) container_of(_chip, struct lpc32xx_pwm_chip, chip)
...@@ -38,40 +36,27 @@ static int lpc32xx_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, ...@@ -38,40 +36,27 @@ static int lpc32xx_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
unsigned long long c; unsigned long long c;
int period_cycles, duty_cycles; int period_cycles, duty_cycles;
u32 val; u32 val;
c = clk_get_rate(lpc32xx->clk);
c = clk_get_rate(lpc32xx->clk) / 256;
c = c * period_ns; /* The highest acceptable divisor is 256, which is represented by 0 */
do_div(c, NSEC_PER_SEC); period_cycles = div64_u64(c * period_ns,
(unsigned long long)NSEC_PER_SEC * 256);
/* Handle high and low extremes */ if (!period_cycles)
if (c == 0) period_cycles = 1;
c = 1; if (period_cycles > 255)
if (c > 255) period_cycles = 0;
c = 0; /* 0 set division by 256 */
period_cycles = c; /* Compute 256 x #duty/period value and care for corner cases */
duty_cycles = div64_u64((unsigned long long)(period_ns - duty_ns) * 256,
/* The duty-cycle value is as follows: period_ns);
* if (!duty_cycles)
* DUTY-CYCLE HIGH LEVEL duty_cycles = 1;
* 1 99.9% if (duty_cycles > 255)
* 25 90.0% duty_cycles = 255;
* 128 50.0%
* 220 10.0%
* 255 0.1%
* 0 0.0%
*
* In other words, the register value is duty-cycle % 256 with
* duty-cycle in the range 1-256.
*/
c = 256 * duty_ns;
do_div(c, period_ns);
if (c > 255)
c = 255;
duty_cycles = 256 - c;
val = readl(lpc32xx->base + (pwm->hwpwm << 2)); val = readl(lpc32xx->base + (pwm->hwpwm << 2));
val &= ~0xFFFF; val &= ~0xFFFF;
val |= PWM_RELOADV(period_cycles) | PWM_DUTY(duty_cycles); val |= (period_cycles << 8) | duty_cycles;
writel(val, lpc32xx->base + (pwm->hwpwm << 2)); writel(val, lpc32xx->base + (pwm->hwpwm << 2));
return 0; return 0;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment