[PATCH RFC 3/4] pwm: sunxi: Add support Allwinner D1 PWM

John Watts contact at jookia.org
Mon May 20 08:10:37 CEST 2024


After a long time reading the datasheet I found out my approach was
actually wrong and led to an off by on error.

Signed-off-by: John Watts <contact at jookia.org>
---
 drivers/pwm/sunxi_pwm_d1.c | 57 +++++++++++++++++++++-----------------
 1 file changed, 32 insertions(+), 25 deletions(-)

diff --git a/drivers/pwm/sunxi_pwm_d1.c b/drivers/pwm/sunxi_pwm_d1.c
index 6c57bc6e85..f396afff3e 100644
--- a/drivers/pwm/sunxi_pwm_d1.c
+++ b/drivers/pwm/sunxi_pwm_d1.c
@@ -1,34 +1,41 @@
 // SPDX-License-Identifier: GPL-2.0
 // Copyright 2022 Jookia <contact at jookia.org>
 /*
- * The Allwinner D1's PWM channels are 16-bit counters with up to
- * 65537 cycles (yes, you read that correctly).
+ * The Allwinner D1's PWM channels are paired 16-bit counters.
  *
  * Each channel must be programmed using three variables:
  * - The entire cycle count (used for the period)
- * - The active cycle count (the count of inactive cycles)
- * - The polarity (specifies if the signal is active high or low)
- * The cycle counts are at minimum 1 and at maximum 65536.
+ * - The active cycle count (used for the duty cycle)
+ * - The active state polarity (specifies if the signal goes high or low)
  *
- * The controller will output the number of entire cycles plus one
- * extra, with any cycles after the active cycle output as active.
+ * All counts are zero based, but the datasheet spends a lot of time
+ * adding 1 to the entire cycle count. There's no hidden extra cycle,
+ * it's just trying to make it human understandable. After all, you can
+ * have 0 active counts for a 100% duty cycle, but 0 entire cycles
+ * doesn't really mean sense or work in time calculations.
  *
- * Consider a PWM period of 128 nanoseconds and a cycle period of 32.
- * Setting the entire cycle count to 3 and active cycle count to 4
- * gives an output like so:
+ * The counter works like this (quoting the datasheet):
+ * - PCNTR = (PCNTR == PWM_ENTIRE_CYCLE) ? 0 : PCNTR + 1
+ * - PCNTR > (PWM_ENTIRE_CYCLE - PWM_ACT_CYCLE) = Output active state
+ * - PCNTR <= (PWM_ENTIRE_CYCLE - PWM_ACT_CYCLE) = Output inactive state
  *
- * - Cycle 1 runs 0 to 32 nanoseconds, inactive
- * - Cycle 2 runs 32 to 64 nanoseconds, inactive
- * - Cycle 3 runs 64 to 96 nanoseconds, inactive
- * - Cycle 4 runs 96 to 128 nanoseconds, inactive
- * - Cycle 5 is skipped but would run 128 to 160 nanoseconds, active
+ * Here's a 2-bit table of cycle counts versus active cycle counts:
+ *   Active:  |       0 |        1  |        2  |        3 |
+ *   Count 0  | Active  | Inactive  | Inactive  | Inactive |
+ *   Count 1  | Active  | Active    | Inactive  | Inactive |
+ *   Count 2  | Active  | Active    | Active    | Inactive |
+ *   Count 3  | Active  | Active    | Active    | Active   |
  *
- * If we set the entire count to 4, cycle 5 would run and we wouldn't be
- * able to specify it as inactive as the active count only goes up to 4.
+ * An entire count of 2 and active count of 3 would always be inactive.
  *
- * In practice this means we want to set the entire cycle to be one less
- * then the actual number of cycles we want, so we can set the number of
- * active cycles to be up to maximum for a fully inactive signal.
+ * The main takeaways here for us are:
+ * - The counter wraps around when it hits the entire cycle count
+ * - The output is active after the counter equals the active cycle count
+ * - An active count of 0 means the period is a 100% active cycle
+ * - An active count larger than the entire cycle count is a 0% active cycle
+ *
+ * This driver deals with the last problem by limiting the entire cycles
+ * to 65534 so we can always specify 65535 for a 0% active cycle.
  *
  * The PWM channels are paired and clocked together, resulting in a
  * cycle time found using the following formula:
@@ -123,7 +130,7 @@ int find_channel_dividers(uint period_ns,
 			  uint parent_hz,
 			  struct pwm_timings *out)
 {
-	uint ideal_cycle_ns = div_ns(period_ns, 65536);
+	uint ideal_cycle_ns = div_ns(period_ns, 65535);
 	int common_div = out->common_div;
 	int prescaler = 1;
 	uint cycle_ns = 0;
@@ -160,10 +167,10 @@ int find_channel_timings(const struct pwm_channel *in,
 	if (find_channel_dividers(in->period_ns, parent_hz, &new))
 		return -1;
 
-	new.entire_cycles = (in->period_ns / new.cycle_ns) - 1;
+	new.entire_cycles = (in->period_ns / new.cycle_ns);
 	new.active_cycles = (in->duty_ns / new.cycle_ns);
-	new.period_ns = (new.entire_cycles + 1) * new.cycle_ns;
-	new.duty_ns = new.active_cycles * new.cycle_ns;
+	new.period_ns = (new.entire_cycles * new.cycle_ns);
+	new.duty_ns = (new.active_cycles * new.cycle_ns);
 	new.polarity = in->polarity;
 
 	if (error_too_large(new.period_ns, in->period_ns))
@@ -311,7 +318,7 @@ void enable_channel(void *base, int channel, struct pwm_timings *timings)
 {
 	u32 pwm_active = (timings->polarity) ? 0 : PCR_PWM_ACTIVE;
 	u32 prescale = (timings->prescale_k - 1);
-	u32 entire_cycles = timings->entire_cycles;
+	u32 entire_cycles = (timings->entire_cycles - 1);
 	u32 active_cycles = timings->active_cycles;
 
 	u32 PCR_clear = (PCR_PRESCAL_K_MASK | PCR_PWM_ACTIVE);
-- 
2.45.1



More information about the U-Boot mailing list