Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 4 additions & 8 deletions arch/riscv/boot/dts/sifive/hifive-unleashed-a00.dts
Original file line number Diff line number Diff line change
Expand Up @@ -49,32 +49,28 @@
compatible = "pwm-leds";

led-d1 {
pwms = <&pwm0 0 7812500 PWM_POLARITY_INVERTED>;
active-low;
pwms = <&pwm0 0 7812500 0>;
color = <LED_COLOR_ID_GREEN>;
max-brightness = <255>;
label = "d1";
};

led-d2 {
pwms = <&pwm0 1 7812500 PWM_POLARITY_INVERTED>;
active-low;
pwms = <&pwm0 1 7812500 0>;
color = <LED_COLOR_ID_GREEN>;
max-brightness = <255>;
label = "d2";
};

led-d3 {
pwms = <&pwm0 2 7812500 PWM_POLARITY_INVERTED>;
active-low;
pwms = <&pwm0 2 7812500 0>;
color = <LED_COLOR_ID_GREEN>;
max-brightness = <255>;
label = "d3";
};

led-d4 {
pwms = <&pwm0 3 7812500 PWM_POLARITY_INVERTED>;
active-low;
pwms = <&pwm0 3 7812500 0>;
color = <LED_COLOR_ID_GREEN>;
max-brightness = <255>;
label = "d4";
Expand Down
12 changes: 4 additions & 8 deletions arch/riscv/boot/dts/sifive/hifive-unmatched-a00.dts
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,7 @@
compatible = "pwm-leds";

led-d12 {
pwms = <&pwm0 0 7812500 PWM_POLARITY_INVERTED>;
active-low;
pwms = <&pwm0 0 7812500 0>;
color = <LED_COLOR_ID_GREEN>;
max-brightness = <255>;
label = "d12";
Expand All @@ -68,20 +67,17 @@
label = "d2";

led-red {
pwms = <&pwm0 2 7812500 PWM_POLARITY_INVERTED>;
active-low;
pwms = <&pwm0 2 7812500 0>;
color = <LED_COLOR_ID_RED>;
};

led-green {
pwms = <&pwm0 1 7812500 PWM_POLARITY_INVERTED>;
active-low;
pwms = <&pwm0 1 7812500 0>;
color = <LED_COLOR_ID_GREEN>;
};

led-blue {
pwms = <&pwm0 3 7812500 PWM_POLARITY_INVERTED>;
active-low;
pwms = <&pwm0 3 7812500 0>;
color = <LED_COLOR_ID_BLUE>;
};
};
Expand Down
49 changes: 37 additions & 12 deletions drivers/pwm/pwm-sifive.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,28 @@
* For SiFive's PWM IP block documentation please refer Chapter 14 of
* Reference Manual : https://static.dev.sifive.com/FU540-C000-v1.0.pdf
*
* PWM output inversion: According to the SiFive Reference manual
* the output of each comparator is high whenever the value of pwms is
* greater than or equal to the corresponding pwmcmpX[Reference Manual].
*
* Figure 29 in the same manual shows that the pwmcmpXcenter bit is
* hard-tied to 0 (XNOR), which effectively inverts the comparison so that
* the output goes HIGH when `pwms < pwmcmpX`.
*
* In other words, each pwmcmp register actually defines the **inactive**
* (low) period of the pulse, not the active time exactly opposite to what
* the documentation text implies.
*
* To compensate, this driver always **inverts** the duty value when reading
* or writing pwmcmp registers , so that users interact with a conventional
* **active-high** PWM interface.
*
*
* Limitations:
* - When changing both duty cycle and period, we cannot prevent in
* software that the output might produce a period with mixed
* settings (new period length and old duty cycle).
* - The hardware cannot generate a 100% duty cycle.
* - The hardware cannot generate a 0% duty cycle.
* - The hardware generates only inverted output.
*/
#include <linux/clk.h>
Expand Down Expand Up @@ -101,7 +118,7 @@ static void pwm_sifive_update_clock(struct pwm_sifive_ddata *ddata,

/* As scale <= 15 the shift operation cannot overflow. */
num = (unsigned long long)NSEC_PER_SEC << (PWM_SIFIVE_CMPWIDTH + scale);
ddata->real_period = div64_ul(num, rate);
ddata->real_period = DIV_ROUND_UP_ULL(num, rate);
dev_dbg(ddata->parent,
"New real_period = %u ns\n", ddata->real_period);
}
Expand All @@ -110,9 +127,14 @@ static int pwm_sifive_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
struct pwm_state *state)
{
struct pwm_sifive_ddata *ddata = pwm_sifive_chip_to_ddata(chip);
u32 duty, val;
u32 duty, val, inactive;

duty = readl(ddata->regs + PWM_SIFIVE_PWMCMP(pwm->hwpwm));
inactive = readl(ddata->regs + PWM_SIFIVE_PWMCMP(pwm->hwpwm));
/*
* PWM hardware uses 'inactive' counts in pwmcmp, so invert to get actual duty.
* Here, 'inactive' is the low time and we compute duty as max_count - inactive.
*/
duty = (1U << PWM_SIFIVE_CMPWIDTH) - 1 - inactive;

state->enabled = duty > 0;

Expand All @@ -121,9 +143,9 @@ static int pwm_sifive_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
state->enabled = false;

state->period = ddata->real_period;
state->duty_cycle =
(u64)duty * ddata->real_period >> PWM_SIFIVE_CMPWIDTH;
state->polarity = PWM_POLARITY_INVERSED;
state->duty_cycle = DIV_ROUND_UP_ULL((u64)duty * ddata->real_period,
(1U << PWM_SIFIVE_CMPWIDTH));
state->polarity = PWM_POLARITY_NORMAL;

return 0;
}
Expand All @@ -137,9 +159,9 @@ static int pwm_sifive_apply(struct pwm_chip *chip, struct pwm_device *pwm,
unsigned long long num;
bool enabled;
int ret = 0;
u32 frac;
u32 frac, inactive;

if (state->polarity != PWM_POLARITY_INVERSED)
if (state->polarity != PWM_POLARITY_NORMAL)
return -EINVAL;

cur_state = pwm->state;
Expand All @@ -156,9 +178,12 @@ static int pwm_sifive_apply(struct pwm_chip *chip, struct pwm_device *pwm,
* consecutively
*/
num = (u64)duty_cycle * (1U << PWM_SIFIVE_CMPWIDTH);
frac = DIV64_U64_ROUND_CLOSEST(num, state->period);
/* The hardware cannot generate a 100% duty cycle */
frac = num;
do_div(frac, state->period;
/* The hardware cannot generate a 0% duty cycle */
frac = min(frac, (1U << PWM_SIFIVE_CMPWIDTH) - 1);
/* pwmcmp register must be loaded with the inactive(invert the duty) */
inactive = (1U << PWM_SIFIVE_CMPWIDTH) - 1 - frac;

mutex_lock(&ddata->lock);
if (state->period != ddata->approx_period) {
Expand Down Expand Up @@ -190,7 +215,7 @@ static int pwm_sifive_apply(struct pwm_chip *chip, struct pwm_device *pwm,
}
}

writel(frac, ddata->regs + PWM_SIFIVE_PWMCMP(pwm->hwpwm));
writel(inactive, ddata->regs + PWM_SIFIVE_PWMCMP(pwm->hwpwm));

if (!state->enabled)
clk_disable(ddata->clk);
Expand Down
Loading