diff options
Diffstat (limited to 'drivers/pwm/pwm-meson.c')
-rw-r--r-- | drivers/pwm/pwm-meson.c | 212 |
1 files changed, 104 insertions, 108 deletions
diff --git a/drivers/pwm/pwm-meson.c b/drivers/pwm/pwm-meson.c index 5732300eb004..22f54db3ae8e 100644 --- a/drivers/pwm/pwm-meson.c +++ b/drivers/pwm/pwm-meson.c @@ -49,9 +49,9 @@ #define PWM_HIGH_MASK GENMASK(31, 16) #define REG_MISC_AB 0x8 -#define MISC_B_CLK_EN BIT(23) -#define MISC_A_CLK_EN BIT(15) -#define MISC_CLK_DIV_MASK 0x7f +#define MISC_B_CLK_EN_SHIFT 23 +#define MISC_A_CLK_EN_SHIFT 15 +#define MISC_CLK_DIV_WIDTH 7 #define MISC_B_CLK_DIV_SHIFT 16 #define MISC_A_CLK_DIV_SHIFT 8 #define MISC_B_CLK_SEL_SHIFT 6 @@ -61,37 +61,39 @@ #define MISC_A_EN BIT(0) #define MESON_NUM_PWMS 2 +#define MESON_MAX_MUX_PARENTS 4 static struct meson_pwm_channel_data { u8 reg_offset; u8 clk_sel_shift; u8 clk_div_shift; - u32 clk_en_mask; + u8 clk_en_shift; u32 pwm_en_mask; } meson_pwm_per_channel_data[MESON_NUM_PWMS] = { { .reg_offset = REG_PWM_A, .clk_sel_shift = MISC_A_CLK_SEL_SHIFT, .clk_div_shift = MISC_A_CLK_DIV_SHIFT, - .clk_en_mask = MISC_A_CLK_EN, + .clk_en_shift = MISC_A_CLK_EN_SHIFT, .pwm_en_mask = MISC_A_EN, }, { .reg_offset = REG_PWM_B, .clk_sel_shift = MISC_B_CLK_SEL_SHIFT, .clk_div_shift = MISC_B_CLK_DIV_SHIFT, - .clk_en_mask = MISC_B_CLK_EN, + .clk_en_shift = MISC_B_CLK_EN_SHIFT, .pwm_en_mask = MISC_B_EN, } }; struct meson_pwm_channel { + unsigned long rate; unsigned int hi; unsigned int lo; - u8 pre_div; - struct clk *clk_parent; struct clk_mux mux; + struct clk_divider div; + struct clk_gate gate; struct clk *clk; }; @@ -124,16 +126,6 @@ static int meson_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) struct device *dev = chip->dev; int err; - if (channel->clk_parent) { - err = clk_set_parent(channel->clk, channel->clk_parent); - if (err < 0) { - dev_err(dev, "failed to set parent %s for %s: %d\n", - __clk_get_name(channel->clk_parent), - __clk_get_name(channel->clk), err); - return err; - } - } - err = clk_prepare_enable(channel->clk); if (err < 0) { dev_err(dev, "failed to enable clock %s: %d\n", @@ -156,8 +148,9 @@ static int meson_pwm_calc(struct meson_pwm *meson, struct pwm_device *pwm, const struct pwm_state *state) { struct meson_pwm_channel *channel = &meson->channels[pwm->hwpwm]; - unsigned int duty, period, pre_div, cnt, duty_cnt; + unsigned int cnt, duty_cnt; unsigned long fin_freq; + u64 duty, period, freq; duty = state->duty_cycle; period = state->period; @@ -171,7 +164,11 @@ static int meson_pwm_calc(struct meson_pwm *meson, struct pwm_device *pwm, if (state->polarity == PWM_POLARITY_INVERSED) duty = period - duty; - fin_freq = clk_get_rate(channel->clk); + freq = div64_u64(NSEC_PER_SEC * 0xffffULL, period); + if (freq > ULONG_MAX) + freq = ULONG_MAX; + + fin_freq = clk_round_rate(channel->clk, freq); if (fin_freq == 0) { dev_err(meson->chip.dev, "invalid source clock frequency\n"); return -EINVAL; @@ -179,46 +176,31 @@ static int meson_pwm_calc(struct meson_pwm *meson, struct pwm_device *pwm, dev_dbg(meson->chip.dev, "fin_freq: %lu Hz\n", fin_freq); - pre_div = div64_u64(fin_freq * (u64)period, NSEC_PER_SEC * 0xffffLL); - if (pre_div > MISC_CLK_DIV_MASK) { - dev_err(meson->chip.dev, "unable to get period pre_div\n"); - return -EINVAL; - } - - cnt = div64_u64(fin_freq * (u64)period, NSEC_PER_SEC * (pre_div + 1)); + cnt = div_u64(fin_freq * period, NSEC_PER_SEC); if (cnt > 0xffff) { dev_err(meson->chip.dev, "unable to get period cnt\n"); return -EINVAL; } - dev_dbg(meson->chip.dev, "period=%u pre_div=%u cnt=%u\n", period, - pre_div, cnt); + dev_dbg(meson->chip.dev, "period=%llu cnt=%u\n", period, cnt); if (duty == period) { - channel->pre_div = pre_div; channel->hi = cnt; channel->lo = 0; } else if (duty == 0) { - channel->pre_div = pre_div; channel->hi = 0; channel->lo = cnt; } else { - /* Then check is we can have the duty with the same pre_div */ - duty_cnt = div64_u64(fin_freq * (u64)duty, - NSEC_PER_SEC * (pre_div + 1)); - if (duty_cnt > 0xffff) { - dev_err(meson->chip.dev, "unable to get duty cycle\n"); - return -EINVAL; - } + duty_cnt = div_u64(fin_freq * duty, NSEC_PER_SEC); - dev_dbg(meson->chip.dev, "duty=%u pre_div=%u duty_cnt=%u\n", - duty, pre_div, duty_cnt); + dev_dbg(meson->chip.dev, "duty=%llu duty_cnt=%u\n", duty, duty_cnt); - channel->pre_div = pre_div; channel->hi = duty_cnt; channel->lo = cnt - duty_cnt; } + channel->rate = fin_freq; + return 0; } @@ -228,16 +210,15 @@ static void meson_pwm_enable(struct meson_pwm *meson, struct pwm_device *pwm) struct meson_pwm_channel_data *channel_data; unsigned long flags; u32 value; + int err; channel_data = &meson_pwm_per_channel_data[pwm->hwpwm]; - spin_lock_irqsave(&meson->lock, flags); + err = clk_set_rate(channel->clk, channel->rate); + if (err) + dev_err(meson->chip.dev, "setting clock rate failed\n"); - value = readl(meson->base + REG_MISC_AB); - value &= ~(MISC_CLK_DIV_MASK << channel_data->clk_div_shift); - value |= channel->pre_div << channel_data->clk_div_shift; - value |= channel_data->clk_en_mask; - writel(value, meson->base + REG_MISC_AB); + spin_lock_irqsave(&meson->lock, flags); value = FIELD_PREP(PWM_HIGH_MASK, channel->hi) | FIELD_PREP(PWM_LOW_MASK, channel->lo); @@ -276,16 +257,16 @@ static int meson_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, /* * This IP block revision doesn't have an "always high" * setting which we can use for "inverted disabled". - * Instead we achieve this using the same settings - * that we use a pre_div of 0 (to get the shortest - * possible duration for one "count") and - * "period == duty_cycle". This results in a signal + * Instead we achieve this by setting mux parent with + * highest rate and minimum divider value, resulting + * in the shortest possible duration for one "count" + * and "period == duty_cycle". This results in a signal * which is LOW for one "count", while being HIGH for * the rest of the (so the signal is HIGH for slightly * less than 100% of the period, but this is the best * we can achieve). */ - channel->pre_div = 0; + channel->rate = ULONG_MAX; channel->hi = ~0; channel->lo = 0; @@ -304,13 +285,12 @@ 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) +static u64 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]; @@ -319,9 +299,7 @@ static unsigned int meson_pwm_cnt_to_ns(struct pwm_chip *chip, if (fin_freq == 0) return 0; - fin_ns = div_u64(NSEC_PER_SEC, fin_freq); - - return cnt * fin_ns * (channel->pre_div + 1); + return div64_ul(NSEC_PER_SEC * (u64)cnt, fin_freq); } static int meson_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, @@ -330,7 +308,7 @@ static int meson_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, struct meson_pwm *meson = to_meson_pwm(chip); struct meson_pwm_channel_data *channel_data; struct meson_pwm_channel *channel; - u32 value, tmp; + u32 value; if (!state) return 0; @@ -339,30 +317,14 @@ static int meson_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, channel_data = &meson_pwm_per_channel_data[pwm->hwpwm]; value = readl(meson->base + REG_MISC_AB); - - 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); + state->enabled = value & channel_data->pwm_en_mask; 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; - } + 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); state->polarity = PWM_POLARITY_NORMAL; @@ -378,7 +340,7 @@ static const struct pwm_ops meson_pwm_ops = { }; static const char * const pwm_meson8b_parent_names[] = { - "xtal", "vid_pll", "fclk_div4", "fclk_div3" + "xtal", NULL, "fclk_div4", "fclk_div3" }; static const struct meson_pwm_data pwm_meson8b_data = { @@ -386,15 +348,6 @@ static const struct meson_pwm_data pwm_meson8b_data = { .num_parents = ARRAY_SIZE(pwm_meson8b_parent_names), }; -static const char * const pwm_gxbb_parent_names[] = { - "xtal", "hdmi_pll", "fclk_div4", "fclk_div3" -}; - -static const struct meson_pwm_data pwm_gxbb_data = { - .parent_names = pwm_gxbb_parent_names, - .num_parents = ARRAY_SIZE(pwm_gxbb_parent_names), -}; - /* * Only the 2 first inputs of the GXBB AO PWMs are valid * The last 2 are grounded @@ -444,15 +397,6 @@ static const struct meson_pwm_data pwm_g12a_ao_cd_data = { .num_parents = ARRAY_SIZE(pwm_g12a_ao_cd_parent_names), }; -static const char * const pwm_g12a_ee_parent_names[] = { - "xtal", "hdmi_pll", "fclk_div4", "fclk_div3" -}; - -static const struct meson_pwm_data pwm_g12a_ee_data = { - .parent_names = pwm_g12a_ee_parent_names, - .num_parents = ARRAY_SIZE(pwm_g12a_ee_parent_names), -}; - static const struct of_device_id meson_pwm_matches[] = { { .compatible = "amlogic,meson8b-pwm", @@ -460,7 +404,7 @@ static const struct of_device_id meson_pwm_matches[] = { }, { .compatible = "amlogic,meson-gxbb-pwm", - .data = &pwm_gxbb_data + .data = &pwm_meson8b_data }, { .compatible = "amlogic,meson-gxbb-ao-pwm", @@ -476,7 +420,7 @@ static const struct of_device_id meson_pwm_matches[] = { }, { .compatible = "amlogic,meson-g12a-ee-pwm", - .data = &pwm_g12a_ee_data + .data = &pwm_meson8b_data }, { .compatible = "amlogic,meson-g12a-ao-pwm-ab", @@ -492,21 +436,28 @@ MODULE_DEVICE_TABLE(of, meson_pwm_matches); static int meson_pwm_init_channels(struct meson_pwm *meson) { + struct clk_parent_data mux_parent_data[MESON_MAX_MUX_PARENTS] = {}; struct device *dev = meson->chip.dev; - struct clk_init_data init; unsigned int i; char name[255]; int err; + for (i = 0; i < meson->data->num_parents; i++) { + mux_parent_data[i].index = -1; + mux_parent_data[i].name = meson->data->parent_names[i]; + } + for (i = 0; i < meson->chip.npwm; i++) { struct meson_pwm_channel *channel = &meson->channels[i]; + struct clk_parent_data div_parent = {}, gate_parent = {}; + struct clk_init_data init = {}; snprintf(name, sizeof(name), "%s#mux%u", dev_name(dev), i); init.name = name; init.ops = &clk_mux_ops; init.flags = 0; - init.parent_names = meson->data->parent_names; + init.parent_data = mux_parent_data; init.num_parents = meson->data->num_parents; channel->mux.reg = meson->base + REG_MISC_AB; @@ -518,18 +469,63 @@ static int meson_pwm_init_channels(struct meson_pwm *meson) channel->mux.table = NULL; channel->mux.hw.init = &init; - channel->clk = devm_clk_register(dev, &channel->mux.hw); - if (IS_ERR(channel->clk)) { - err = PTR_ERR(channel->clk); + err = devm_clk_hw_register(dev, &channel->mux.hw); + if (err) { + dev_err(dev, "failed to register %s: %d\n", name, err); + return err; + } + + snprintf(name, sizeof(name), "%s#div%u", dev_name(dev), i); + + init.name = name; + init.ops = &clk_divider_ops; + init.flags = CLK_SET_RATE_PARENT; + div_parent.index = -1; + div_parent.hw = &channel->mux.hw; + init.parent_data = &div_parent; + init.num_parents = 1; + + channel->div.reg = meson->base + REG_MISC_AB; + channel->div.shift = meson_pwm_per_channel_data[i].clk_div_shift; + channel->div.width = MISC_CLK_DIV_WIDTH; + channel->div.hw.init = &init; + channel->div.flags = 0; + channel->div.lock = &meson->lock; + + err = devm_clk_hw_register(dev, &channel->div.hw); + if (err) { dev_err(dev, "failed to register %s: %d\n", name, err); return err; } - snprintf(name, sizeof(name), "clkin%u", i); + snprintf(name, sizeof(name), "%s#gate%u", dev_name(dev), i); - channel->clk_parent = devm_clk_get_optional(dev, name); - if (IS_ERR(channel->clk_parent)) - return PTR_ERR(channel->clk_parent); + init.name = name; + init.ops = &clk_gate_ops; + init.flags = CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED; + gate_parent.index = -1; + gate_parent.hw = &channel->div.hw; + init.parent_data = &gate_parent; + init.num_parents = 1; + + channel->gate.reg = meson->base + REG_MISC_AB; + channel->gate.bit_idx = meson_pwm_per_channel_data[i].clk_en_shift; + channel->gate.hw.init = &init; + channel->gate.flags = 0; + channel->gate.lock = &meson->lock; + + err = devm_clk_hw_register(dev, &channel->gate.hw); + if (err) { + dev_err(dev, "failed to register %s: %d\n", name, err); + return err; + } + + channel->clk = devm_clk_hw_get_clk(dev, &channel->gate.hw, NULL); + if (IS_ERR(channel->clk)) { + err = PTR_ERR(channel->clk); + dev_err(dev, "failed to register %s: %d\n", name, err); + return err; + } } return 0; |