From c375bcbaabdb92f0c007a044cda90450eef5ab43 Mon Sep 17 00:00:00 2001 From: Martin Blumenstingl Date: Wed, 12 Jun 2019 21:59:08 +0200 Subject: [PATCH] pwm: meson: Read the full hardware state in meson_pwm_get_state() Update the meson_pwm_get_state() implementation to take care of all information in the registers instead of only reading the "enabled" state. The PWM output is only enabled if two conditions are met: 1. the per-channel clock is enabled 2. the PWM output is enabled Calculate the PWM period and duty cycle using the reverse formula which we already have in meson_pwm_calc() and update struct pwm_state with the results. As result of this /sys/kernel/debug/pwm now shows the PWM state set by the bootloader (or firmware) after booting Linux. Reviewed-by: Neil Armstrong Signed-off-by: Martin Blumenstingl Signed-off-by: Thierry Reding --- drivers/pwm/pwm-meson.c | 52 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/drivers/pwm/pwm-meson.c b/drivers/pwm/pwm-meson.c index 99815b26f6b2..fd40bb1e4207 100644 --- a/drivers/pwm/pwm-meson.c +++ b/drivers/pwm/pwm-meson.c @@ -287,19 +287,65 @@ static int meson_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, return 0; } +static unsigned int meson_pwm_cnt_to_ns(struct pwm_chip *chip, + struct pwm_device *pwm, u32 cnt) +{ + struct meson_pwm *meson = to_meson_pwm(chip); + struct meson_pwm_channel *channel; + unsigned long fin_freq; + u32 fin_ns; + + /* to_meson_pwm() can only be used after .get_state() is called */ + channel = &meson->channels[pwm->hwpwm]; + + fin_freq = clk_get_rate(channel->clk); + if (fin_freq == 0) + return 0; + + fin_ns = div_u64(NSEC_PER_SEC, fin_freq); + + return cnt * fin_ns * (channel->pre_div + 1); +} + static void meson_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, struct pwm_state *state) { struct meson_pwm *meson = to_meson_pwm(chip); - u32 value, mask; + struct meson_pwm_channel_data *channel_data; + struct meson_pwm_channel *channel; + u32 value, tmp; if (!state) return; - mask = meson_pwm_per_channel_data[pwm->hwpwm].pwm_en_mask; + channel = &meson->channels[pwm->hwpwm]; + channel_data = &meson_pwm_per_channel_data[pwm->hwpwm]; value = readl(meson->base + REG_MISC_AB); - state->enabled = (value & mask) != 0; + + tmp = channel_data->pwm_en_mask | channel_data->clk_en_mask; + state->enabled = (value & tmp) == tmp; + + tmp = value >> channel_data->clk_div_shift; + channel->pre_div = FIELD_GET(MISC_CLK_DIV_MASK, tmp); + + value = readl(meson->base + channel_data->reg_offset); + + channel->lo = FIELD_GET(PWM_LOW_MASK, value); + channel->hi = FIELD_GET(PWM_HIGH_MASK, value); + + if (channel->lo == 0) { + state->period = meson_pwm_cnt_to_ns(chip, pwm, channel->hi); + state->duty_cycle = state->period; + } else if (channel->lo >= channel->hi) { + state->period = meson_pwm_cnt_to_ns(chip, pwm, + channel->lo + channel->hi); + state->duty_cycle = meson_pwm_cnt_to_ns(chip, pwm, + channel->hi); + } else { + state->period = 0; + state->duty_cycle = 0; + } } static const struct pwm_ops meson_pwm_ops = {