|
@@ -8,6 +8,7 @@
|
|
|
* pwm-atmel.c from Bo Shen
|
|
|
*/
|
|
|
|
|
|
+#include <linux/bitfield.h>
|
|
|
#include <linux/mfd/stm32-timers.h>
|
|
|
#include <linux/module.h>
|
|
|
#include <linux/of.h>
|
|
@@ -168,7 +169,7 @@ static int stm32_pwm_capture(struct pwm_chip *chip, struct pwm_device *pwm,
|
|
|
struct stm32_pwm *priv = to_stm32_pwm_dev(chip);
|
|
|
unsigned long long prd, div, dty;
|
|
|
unsigned long rate;
|
|
|
- unsigned int psc = 0, scale;
|
|
|
+ unsigned int psc = 0, icpsc, scale;
|
|
|
u32 raw_prd, raw_dty;
|
|
|
int ret = 0;
|
|
|
|
|
@@ -222,6 +223,7 @@ static int stm32_pwm_capture(struct pwm_chip *chip, struct pwm_device *pwm,
|
|
|
/*
|
|
|
* Got a capture. Try to improve accuracy at high rates:
|
|
|
* - decrease counter clock prescaler, scale up to max rate.
|
|
|
+ * - use input prescaler, capture once every /2 /4 or /8 edges.
|
|
|
*/
|
|
|
if (raw_prd) {
|
|
|
u32 max_arr = priv->max_arr - 0x1000; /* arbitrary margin */
|
|
@@ -241,8 +243,65 @@ static int stm32_pwm_capture(struct pwm_chip *chip, struct pwm_device *pwm,
|
|
|
goto stop;
|
|
|
}
|
|
|
|
|
|
+ /* Compute intermediate period not to exceed timeout at low rates */
|
|
|
prd = (unsigned long long)raw_prd * (psc + 1) * NSEC_PER_SEC;
|
|
|
- result->period = DIV_ROUND_UP_ULL(prd, rate);
|
|
|
+ do_div(prd, rate);
|
|
|
+
|
|
|
+ for (icpsc = 0; icpsc < MAX_TIM_ICPSC ; icpsc++) {
|
|
|
+ /* input prescaler: also keep arbitrary margin */
|
|
|
+ if (raw_prd >= (priv->max_arr - 0x1000) >> (icpsc + 1))
|
|
|
+ break;
|
|
|
+ if (prd >= (tmo_ms * NSEC_PER_MSEC) >> (icpsc + 2))
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!icpsc)
|
|
|
+ goto done;
|
|
|
+
|
|
|
+ /* Last chance to improve period accuracy, using input prescaler */
|
|
|
+ regmap_update_bits(priv->regmap,
|
|
|
+ pwm->hwpwm < 2 ? TIM_CCMR1 : TIM_CCMR2,
|
|
|
+ TIM_CCMR_IC1PSC | TIM_CCMR_IC2PSC,
|
|
|
+ FIELD_PREP(TIM_CCMR_IC1PSC, icpsc) |
|
|
|
+ FIELD_PREP(TIM_CCMR_IC2PSC, icpsc));
|
|
|
+
|
|
|
+ ret = stm32_pwm_raw_capture(priv, pwm, tmo_ms, &raw_prd, &raw_dty);
|
|
|
+ if (ret)
|
|
|
+ goto stop;
|
|
|
+
|
|
|
+ if (raw_dty >= (raw_prd >> icpsc)) {
|
|
|
+ /*
|
|
|
+ * We may fall here using input prescaler, when input
|
|
|
+ * capture starts on high side (before falling edge).
|
|
|
+ * Example with icpsc to capture on each 4 events:
|
|
|
+ *
|
|
|
+ * start 1st capture 2nd capture
|
|
|
+ * v v v
|
|
|
+ * ___ _____ _____ _____ _____ ____
|
|
|
+ * TI1..4 |__| |__| |__| |__| |__|
|
|
|
+ * v v . . . . . v v
|
|
|
+ * icpsc1/3: . 0 . 1 . 2 . 3 . 0
|
|
|
+ * icpsc2/4: 0 1 2 3 0
|
|
|
+ * v v v v
|
|
|
+ * CCR1/3 ......t0..............................t2
|
|
|
+ * CCR2/4 ..t1..............................t1'...
|
|
|
+ * . . .
|
|
|
+ * Capture0: .<----------------------------->.
|
|
|
+ * Capture1: .<-------------------------->. .
|
|
|
+ * . . .
|
|
|
+ * Period: .<------> . .
|
|
|
+ * Low side: .<>.
|
|
|
+ *
|
|
|
+ * Result:
|
|
|
+ * - Period = Capture0 / icpsc
|
|
|
+ * - Duty = Period - Low side = Period - (Capture0 - Capture1)
|
|
|
+ */
|
|
|
+ raw_dty = (raw_prd >> icpsc) - (raw_prd - raw_dty);
|
|
|
+ }
|
|
|
+
|
|
|
+done:
|
|
|
+ prd = (unsigned long long)raw_prd * (psc + 1) * NSEC_PER_SEC;
|
|
|
+ result->period = DIV_ROUND_UP_ULL(prd, rate << icpsc);
|
|
|
dty = (unsigned long long)raw_dty * (psc + 1) * NSEC_PER_SEC;
|
|
|
result->duty_cycle = DIV_ROUND_UP_ULL(dty, rate);
|
|
|
stop:
|