From 2b8e30b1bd500df67554f1c183caf333de82f8d2 Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Sun, 16 Apr 2023 11:02:13 +0200 Subject: pwm: clk: Use the devm_clk_get_prepared() helper function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use the devm_clk_get_prepared() helper function instead of hand-writing it. It saves some line of codes. Signed-off-by: Christophe JAILLET Acked-by: Uwe Kleine-König Signed-off-by: Thierry Reding --- drivers/pwm/pwm-clk.c | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) (limited to 'drivers/pwm') diff --git a/drivers/pwm/pwm-clk.c b/drivers/pwm/pwm-clk.c index f1da99881adf..0ee4d2aee4df 100644 --- a/drivers/pwm/pwm-clk.c +++ b/drivers/pwm/pwm-clk.c @@ -89,7 +89,7 @@ static int pwm_clk_probe(struct platform_device *pdev) if (!pcchip) return -ENOMEM; - pcchip->clk = devm_clk_get(&pdev->dev, NULL); + pcchip->clk = devm_clk_get_prepared(&pdev->dev, NULL); if (IS_ERR(pcchip->clk)) return dev_err_probe(&pdev->dev, PTR_ERR(pcchip->clk), "Failed to get clock\n"); @@ -98,15 +98,9 @@ static int pwm_clk_probe(struct platform_device *pdev) pcchip->chip.ops = &pwm_clk_ops; pcchip->chip.npwm = 1; - ret = clk_prepare(pcchip->clk); - if (ret < 0) - return dev_err_probe(&pdev->dev, ret, "Failed to prepare clock\n"); - ret = pwmchip_add(&pcchip->chip); - if (ret < 0) { - clk_unprepare(pcchip->clk); + if (ret < 0) return dev_err_probe(&pdev->dev, ret, "Failed to add pwm chip\n"); - } platform_set_drvdata(pdev, pcchip); return 0; @@ -120,8 +114,6 @@ static void pwm_clk_remove(struct platform_device *pdev) if (pcchip->clk_enabled) clk_disable(pcchip->clk); - - clk_unprepare(pcchip->clk); } static const struct of_device_id pwm_clk_dt_ids[] = { -- cgit v1.2.3 From 55e644b840baf7a884a27be81b7582794f54a808 Mon Sep 17 00:00:00 2001 From: Uwe Kleine-König Date: Tue, 18 Apr 2023 22:21:02 +0200 Subject: pwm: sifive: Simplify using devm_clk_get_prepared() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of preparing the clk after it was requested and unpreparing in .probe()'s error path and .remove(), use devm_clk_get_prepared() which copes for unpreparing automatically. Signed-off-by: Uwe Kleine-König Reviewed-by: Emil Renner Berthing Signed-off-by: Thierry Reding --- drivers/pwm/pwm-sifive.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) (limited to 'drivers/pwm') diff --git a/drivers/pwm/pwm-sifive.c b/drivers/pwm/pwm-sifive.c index 5b0574f635f6..ae49d67ab2b1 100644 --- a/drivers/pwm/pwm-sifive.c +++ b/drivers/pwm/pwm-sifive.c @@ -244,12 +244,12 @@ static int pwm_sifive_probe(struct platform_device *pdev) if (IS_ERR(ddata->regs)) return PTR_ERR(ddata->regs); - ddata->clk = devm_clk_get(dev, NULL); + ddata->clk = devm_clk_get_prepared(dev, NULL); if (IS_ERR(ddata->clk)) return dev_err_probe(dev, PTR_ERR(ddata->clk), "Unable to find controller clock\n"); - ret = clk_prepare_enable(ddata->clk); + ret = clk_enable(ddata->clk); if (ret) { dev_err(dev, "failed to enable clock for pwm: %d\n", ret); return ret; @@ -308,7 +308,6 @@ disable_clk: clk_disable(ddata->clk); --enabled_clks; } - clk_unprepare(ddata->clk); return ret; } @@ -327,8 +326,6 @@ static void pwm_sifive_remove(struct platform_device *dev) if (pwm->state.enabled) clk_disable(ddata->clk); } - - clk_unprepare(ddata->clk); } static const struct of_device_id pwm_sifive_of_match[] = { -- cgit v1.2.3 From 967da67a745fb73fd0fc7aa61fd197b76fceb273 Mon Sep 17 00:00:00 2001 From: Daniel Golle Date: Fri, 21 Apr 2023 00:23:21 +0100 Subject: pwm: mediatek: Add support for MT7981 The PWM unit on MT7981 uses different register offsets than previous MediaTek PWM units. Add support for these new offsets and add support for PWM on MT7981 which has 3 PWM channels, one of them is typically used for a temperature controlled fan. While at it, also reorder pwm_mediatek_of_data entries to restore alphabetic order. Signed-off-by: Daniel Golle Reviewed-by: AngeloGioacchino Del Regno Reviewed-by: Matthias Brugger Signed-off-by: Thierry Reding --- drivers/pwm/pwm-mediatek.c | 39 +++++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) (limited to 'drivers/pwm') diff --git a/drivers/pwm/pwm-mediatek.c b/drivers/pwm/pwm-mediatek.c index 5b5eeaff35da..7a51d210a877 100644 --- a/drivers/pwm/pwm-mediatek.c +++ b/drivers/pwm/pwm-mediatek.c @@ -38,6 +38,7 @@ struct pwm_mediatek_of_data { unsigned int num_pwms; bool pwm45_fixup; bool has_ck_26m_sel; + const unsigned int *reg_offset; }; /** @@ -59,10 +60,14 @@ struct pwm_mediatek_chip { const struct pwm_mediatek_of_data *soc; }; -static const unsigned int pwm_mediatek_reg_offset[] = { +static const unsigned int mtk_pwm_reg_offset_v1[] = { 0x0010, 0x0050, 0x0090, 0x00d0, 0x0110, 0x0150, 0x0190, 0x0220 }; +static const unsigned int mtk_pwm_reg_offset_v2[] = { + 0x0080, 0x00c0, 0x0100, 0x0140, 0x0180, 0x01c0, 0x0200, 0x0240 +}; + static inline struct pwm_mediatek_chip * to_pwm_mediatek_chip(struct pwm_chip *chip) { @@ -111,7 +116,7 @@ static inline void pwm_mediatek_writel(struct pwm_mediatek_chip *chip, unsigned int num, unsigned int offset, u32 value) { - writel(value, chip->regs + pwm_mediatek_reg_offset[num] + offset); + writel(value, chip->regs + chip->soc->reg_offset[num] + offset); } static int pwm_mediatek_config(struct pwm_chip *chip, struct pwm_device *pwm, @@ -285,60 +290,77 @@ static const struct pwm_mediatek_of_data mt2712_pwm_data = { .num_pwms = 8, .pwm45_fixup = false, .has_ck_26m_sel = false, + .reg_offset = mtk_pwm_reg_offset_v1, }; static const struct pwm_mediatek_of_data mt6795_pwm_data = { .num_pwms = 7, .pwm45_fixup = false, .has_ck_26m_sel = false, + .reg_offset = mtk_pwm_reg_offset_v1, }; static const struct pwm_mediatek_of_data mt7622_pwm_data = { .num_pwms = 6, .pwm45_fixup = false, .has_ck_26m_sel = true, + .reg_offset = mtk_pwm_reg_offset_v1, }; static const struct pwm_mediatek_of_data mt7623_pwm_data = { .num_pwms = 5, .pwm45_fixup = true, .has_ck_26m_sel = false, + .reg_offset = mtk_pwm_reg_offset_v1, }; static const struct pwm_mediatek_of_data mt7628_pwm_data = { .num_pwms = 4, .pwm45_fixup = true, .has_ck_26m_sel = false, + .reg_offset = mtk_pwm_reg_offset_v1, }; static const struct pwm_mediatek_of_data mt7629_pwm_data = { .num_pwms = 1, .pwm45_fixup = false, .has_ck_26m_sel = false, + .reg_offset = mtk_pwm_reg_offset_v1, }; -static const struct pwm_mediatek_of_data mt8183_pwm_data = { - .num_pwms = 4, +static const struct pwm_mediatek_of_data mt7981_pwm_data = { + .num_pwms = 3, .pwm45_fixup = false, .has_ck_26m_sel = true, + .reg_offset = mtk_pwm_reg_offset_v2, }; -static const struct pwm_mediatek_of_data mt8365_pwm_data = { - .num_pwms = 3, +static const struct pwm_mediatek_of_data mt7986_pwm_data = { + .num_pwms = 2, .pwm45_fixup = false, .has_ck_26m_sel = true, + .reg_offset = mtk_pwm_reg_offset_v1, }; -static const struct pwm_mediatek_of_data mt7986_pwm_data = { - .num_pwms = 2, +static const struct pwm_mediatek_of_data mt8183_pwm_data = { + .num_pwms = 4, + .pwm45_fixup = false, + .has_ck_26m_sel = true, + .reg_offset = mtk_pwm_reg_offset_v1, +}; + +static const struct pwm_mediatek_of_data mt8365_pwm_data = { + .num_pwms = 3, .pwm45_fixup = false, .has_ck_26m_sel = true, + .reg_offset = mtk_pwm_reg_offset_v1, }; static const struct pwm_mediatek_of_data mt8516_pwm_data = { .num_pwms = 5, .pwm45_fixup = false, .has_ck_26m_sel = true, + .reg_offset = mtk_pwm_reg_offset_v1, }; static const struct of_device_id pwm_mediatek_of_match[] = { @@ -348,6 +370,7 @@ static const struct of_device_id pwm_mediatek_of_match[] = { { .compatible = "mediatek,mt7623-pwm", .data = &mt7623_pwm_data }, { .compatible = "mediatek,mt7628-pwm", .data = &mt7628_pwm_data }, { .compatible = "mediatek,mt7629-pwm", .data = &mt7629_pwm_data }, + { .compatible = "mediatek,mt7981-pwm", .data = &mt7981_pwm_data }, { .compatible = "mediatek,mt7986-pwm", .data = &mt7986_pwm_data }, { .compatible = "mediatek,mt8183-pwm", .data = &mt8183_pwm_data }, { .compatible = "mediatek,mt8365-pwm", .data = &mt8365_pwm_data }, -- cgit v1.2.3 From 6b9352f3f8a1a35faf0efc1ad1807ee303467796 Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Wed, 24 May 2023 21:47:43 +0200 Subject: pwm: meson: modify and simplify calculation in meson_pwm_get_state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I don't see a reason why we should treat the case lo < hi differently and return 0 as period and duty_cycle. The current logic was added with c375bcbaabdb ("pwm: meson: Read the full hardware state in meson_pwm_get_state()"), Martin as original author doesn't remember why it was implemented this way back then. So let's handle it as normal use case and also remove the optimization for lo == 0. I think the improved readability is worth it. Fixes: c375bcbaabdb ("pwm: meson: Read the full hardware state in meson_pwm_get_state()") Reviewed-by: Uwe Kleine-König Reviewed-by: Dmitry Rokosov Acked-by: Martin Blumenstingl Cc: stable@vger.kernel.org Signed-off-by: Heiner Kallweit Signed-off-by: Thierry Reding --- drivers/pwm/pwm-meson.c | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) (limited to 'drivers/pwm') diff --git a/drivers/pwm/pwm-meson.c b/drivers/pwm/pwm-meson.c index 5732300eb004..3865538dd2d6 100644 --- a/drivers/pwm/pwm-meson.c +++ b/drivers/pwm/pwm-meson.c @@ -351,18 +351,8 @@ static int meson_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, 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; -- cgit v1.2.3 From 87a2cbf02d7701255f9fcca7e5bd864a7bb397cf Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Wed, 24 May 2023 21:48:36 +0200 Subject: pwm: meson: fix handling of period/duty if greater than UINT_MAX MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit state->period/duty are of type u64, and if their value is greater than UINT_MAX, then the cast to uint will cause problems. Fix this by changing the type of the respective local variables to u64. Fixes: b79c3670e120 ("pwm: meson: Don't duplicate the polarity internally") Cc: stable@vger.kernel.org Suggested-by: Uwe Kleine-König Reviewed-by: Martin Blumenstingl Signed-off-by: Heiner Kallweit Signed-off-by: Thierry Reding --- drivers/pwm/pwm-meson.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'drivers/pwm') diff --git a/drivers/pwm/pwm-meson.c b/drivers/pwm/pwm-meson.c index 3865538dd2d6..33107204a951 100644 --- a/drivers/pwm/pwm-meson.c +++ b/drivers/pwm/pwm-meson.c @@ -156,8 +156,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 pre_div, cnt, duty_cnt; unsigned long fin_freq; + u64 duty, period; duty = state->duty_cycle; period = state->period; @@ -179,19 +180,19 @@ 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); + pre_div = div64_u64(fin_freq * 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 = div64_u64(fin_freq * period, NSEC_PER_SEC * (pre_div + 1)); 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, + dev_dbg(meson->chip.dev, "period=%llu pre_div=%u cnt=%u\n", period, pre_div, cnt); if (duty == period) { @@ -204,14 +205,13 @@ static int meson_pwm_calc(struct meson_pwm *meson, struct pwm_device *pwm, 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)); + duty_cnt = div64_u64(fin_freq * duty, NSEC_PER_SEC * (pre_div + 1)); if (duty_cnt > 0xffff) { dev_err(meson->chip.dev, "unable to get duty cycle\n"); return -EINVAL; } - dev_dbg(meson->chip.dev, "duty=%u pre_div=%u duty_cnt=%u\n", + dev_dbg(meson->chip.dev, "duty=%llu pre_div=%u duty_cnt=%u\n", duty, pre_div, duty_cnt); channel->pre_div = pre_div; -- cgit v1.2.3 From bafa23b6c07caac63a9637e83a605c26771b43ee Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Wed, 24 May 2023 21:49:56 +0200 Subject: pwm: meson: remove not needed check in meson_pwm_calc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit period >= duty implies that cnt >= duty_cnt. We verified before that cnt <= 0xffff, therefore we can omit the check here. Suggested-by: Uwe Kleine-König Signed-off-by: Heiner Kallweit Signed-off-by: Thierry Reding --- drivers/pwm/pwm-meson.c | 5 ----- 1 file changed, 5 deletions(-) (limited to 'drivers/pwm') diff --git a/drivers/pwm/pwm-meson.c b/drivers/pwm/pwm-meson.c index 33107204a951..aad4a0ed3b5a 100644 --- a/drivers/pwm/pwm-meson.c +++ b/drivers/pwm/pwm-meson.c @@ -204,12 +204,7 @@ static int meson_pwm_calc(struct meson_pwm *meson, struct pwm_device *pwm, 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 * duty, NSEC_PER_SEC * (pre_div + 1)); - if (duty_cnt > 0xffff) { - dev_err(meson->chip.dev, "unable to get duty cycle\n"); - return -EINVAL; - } dev_dbg(meson->chip.dev, "duty=%llu pre_div=%u duty_cnt=%u\n", duty, pre_div, duty_cnt); -- cgit v1.2.3 From ed73300326ec67d2a0c35fb7f911314cc6d7d918 Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Wed, 24 May 2023 21:50:47 +0200 Subject: pwm: meson: switch to using struct clk_parent_data for mux parents We'll use struct clk_parent_data for mux/div/gate initialization in the follow-up patches. As a first step switch the mux from using parent_names to clk_parent_data. Suggested-by: Martin Blumenstingl Reviewed-by: Martin Blumenstingl Tested-by: Martin Blumenstingl Signed-off-by: Heiner Kallweit Signed-off-by: Thierry Reding --- drivers/pwm/pwm-meson.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'drivers/pwm') diff --git a/drivers/pwm/pwm-meson.c b/drivers/pwm/pwm-meson.c index aad4a0ed3b5a..1654fdbb035f 100644 --- a/drivers/pwm/pwm-meson.c +++ b/drivers/pwm/pwm-meson.c @@ -61,6 +61,7 @@ #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; @@ -477,21 +478,27 @@ 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_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; -- cgit v1.2.3 From 3bddf73285d578a1798189e0eed2e7f82ebf0e11 Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Wed, 24 May 2023 21:51:31 +0200 Subject: pwm: meson: don't use hdmi/video clock as mux parent The meson_vclk code from the display driver may change the rate of the video clock. Therefore better don't use it as pwm mux parent. After removing this clock from the parent list pwm_gxbb_data and pwm_g12a_ee_data are the same as pwm_meson8b_data. So we can remove them. Reported-by: Martin Blumenstingl Reviewed-by: Martin Blumenstingl Signed-off-by: Heiner Kallweit Signed-off-by: Thierry Reding --- drivers/pwm/pwm-meson.c | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) (limited to 'drivers/pwm') diff --git a/drivers/pwm/pwm-meson.c b/drivers/pwm/pwm-meson.c index 1654fdbb035f..48dcc44df241 100644 --- a/drivers/pwm/pwm-meson.c +++ b/drivers/pwm/pwm-meson.c @@ -364,7 +364,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 = { @@ -372,15 +372,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 @@ -430,15 +421,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", @@ -446,7 +428,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", @@ -462,7 +444,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", -- cgit v1.2.3 From 329db102a26da0ba876974dbe04308d08acfad94 Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Wed, 24 May 2023 21:52:21 +0200 Subject: pwm: meson: make full use of common clock framework Newer versions of the PWM block use a core clock with external mux, divider, and gate. These components either don't exist any longer in the PWM block, or they are bypassed. To minimize needed changes for supporting the new version, the internal divider and gate should be handled by CCF too. I didn't see a good way to split the patch, therefore it's somewhat bigger. What it does: - The internal mux is handled by CCF already. Register also internal divider and gate with CCF, so that we have one representation of the input clock: [mux] parent of [divider] parent of [gate] - Now that CCF selects an appropriate mux parent, we don't need the DT-provided default parent any longer. Accordingly we can also omit setting the mux parent directly in the driver. - Instead of manually handling the pre-div divider value, let CCF set the input clock. Targeted input clock frequency is 0xffff * 1/period for best precision. - For the "inverted pwm disabled" scenario target an input clock frequency of ULONG_MAX. This ensures that the remaining low pulses have minimum length. I don't have hw with the old PWM block, therefore I couldn't test this patch. With the not yet included extension for the new PWM block (channel->clk coming directly from get_clk(external_clk)) I didn't notice any problem. My system uses PWM for the CPU voltage regulator and for the SDIO 32kHz clock. Note: The clock gate in the old PWM block is permanently disabled. This seems to indicate that it's not used by the new PWM block. Signed-off-by: Heiner Kallweit Signed-off-by: Thierry Reding --- drivers/pwm/pwm-meson.c | 158 +++++++++++++++++++++++++++--------------------- 1 file changed, 90 insertions(+), 68 deletions(-) (limited to 'drivers/pwm') diff --git a/drivers/pwm/pwm-meson.c b/drivers/pwm/pwm-meson.c index 48dcc44df241..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 @@ -67,32 +67,33 @@ 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; }; @@ -125,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", @@ -157,9 +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 pre_div, cnt, duty_cnt; + unsigned int cnt, duty_cnt; unsigned long fin_freq; - u64 duty, period; + u64 duty, period, freq; duty = state->duty_cycle; period = state->period; @@ -173,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; @@ -181,40 +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 * 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 * 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=%llu 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 { - duty_cnt = div64_u64(fin_freq * duty, NSEC_PER_SEC * (pre_div + 1)); + duty_cnt = div_u64(fin_freq * duty, NSEC_PER_SEC); - dev_dbg(meson->chip.dev, "duty=%llu 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; } @@ -224,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); @@ -272,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; @@ -300,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]; @@ -315,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, @@ -326,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; @@ -335,15 +317,9 @@ 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); @@ -473,6 +449,7 @@ static int meson_pwm_init_channels(struct meson_pwm *meson) 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); @@ -492,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; -- cgit v1.2.3 From 661dfb7f46298e53f6c3deaa772fa527aae86193 Mon Sep 17 00:00:00 2001 From: Fancy Fang Date: Fri, 5 May 2023 14:58:39 +0800 Subject: pwm: imx-tpm: force 'real_period' to be zero in suspend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit During suspend, all the tpm registers will lose values. So the 'real_period' value of struct 'imx_tpm_pwm_chip' should be forced to be zero to force the period update code can be executed after system resume back. Signed-off-by: Fancy Fang Signed-off-by: Clark Wang Acked-by: Uwe Kleine-König Fixes: 738a1cfec2ed ("pwm: Add i.MX TPM PWM driver support") Signed-off-by: Thierry Reding --- drivers/pwm/pwm-imx-tpm.c | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'drivers/pwm') diff --git a/drivers/pwm/pwm-imx-tpm.c b/drivers/pwm/pwm-imx-tpm.c index 5e2b452ee5f2..98ab65c89685 100644 --- a/drivers/pwm/pwm-imx-tpm.c +++ b/drivers/pwm/pwm-imx-tpm.c @@ -397,6 +397,13 @@ static int __maybe_unused pwm_imx_tpm_suspend(struct device *dev) if (tpm->enable_count > 0) return -EBUSY; + /* + * Force 'real_period' to be zero to force period update code + * can be executed after system resume back, since suspend causes + * the period related registers to become their reset values. + */ + tpm->real_period = 0; + clk_disable_unprepare(tpm->clk); return 0; -- cgit v1.2.3 From 38ba83598633373f47951384cfc389181c8d1bed Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Fri, 12 May 2023 18:47:36 +0200 Subject: pwm: sysfs: Do not apply state to already disabled PWMs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If the PWM is exported but not enabled, do not call pwm_class_apply_state(). First of all, in this case, period may still be unconfigured and this would make pwm_class_apply_state() return -EINVAL, and then suspend would fail. Second, it makes little sense to apply state onto PWM that is not enabled before suspend. Failing case: " $ echo 1 > /sys/class/pwm/pwmchip4/export $ echo mem > /sys/power/state ... pwm pwmchip4: PM: dpm_run_callback(): pwm_class_suspend+0x1/0xa8 returns -22 pwm pwmchip4: PM: failed to suspend: error -22 PM: Some devices failed to suspend, or early wake event detected " Working case: " $ echo 1 > /sys/class/pwm/pwmchip4/export $ echo 100 > /sys/class/pwm/pwmchip4/pwm1/period $ echo 10 > /sys/class/pwm/pwmchip4/pwm1/duty_cycle $ echo mem > /sys/power/state ... " Do not call pwm_class_apply_state() in case the PWM is disabled to fix this issue. Fixes: 7fd4edc57bbae ("pwm: sysfs: Add suspend/resume support") Signed-off-by: Marek Vasut Fixes: ef2bf4997f7d ("pwm: Improve args checking in pwm_apply_state()") Reviewed-by: Brian Norris Reviewed-by: Uwe Kleine-König Signed-off-by: Thierry Reding --- drivers/pwm/sysfs.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'drivers/pwm') diff --git a/drivers/pwm/sysfs.c b/drivers/pwm/sysfs.c index 1a106ec32939..8d1254761e4d 100644 --- a/drivers/pwm/sysfs.c +++ b/drivers/pwm/sysfs.c @@ -424,6 +424,13 @@ static int pwm_class_resume_npwm(struct device *parent, unsigned int npwm) if (!export) continue; + /* If pwmchip was not enabled before suspend, do nothing. */ + if (!export->suspend.enabled) { + /* release lock taken in pwm_class_get_state */ + mutex_unlock(&export->lock); + continue; + } + state.enabled = export->suspend.enabled; ret = pwm_class_apply_state(export, pwm, &state); if (ret < 0) @@ -448,7 +455,17 @@ static int pwm_class_suspend(struct device *parent) if (!export) continue; + /* + * If pwmchip was not enabled before suspend, save + * state for resume time and do nothing else. + */ export->suspend = state; + if (!state.enabled) { + /* release lock taken in pwm_class_get_state */ + mutex_unlock(&export->lock); + continue; + } + state.enabled = false; ret = pwm_class_apply_state(export, pwm, &state); if (ret < 0) { -- cgit v1.2.3 From 2bf7ecf7b4fffd87d8eb5c072395239d6ff54728 Mon Sep 17 00:00:00 2001 From: Conor Dooley Date: Thu, 18 May 2023 13:29:20 +0100 Subject: pwm: add microchip soft ip corePWM driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a driver that supports the Microchip FPGA "soft" PWM IP core. Signed-off-by: Conor Dooley Reviewed-by: Uwe Kleine-König Signed-off-by: Thierry Reding --- drivers/pwm/Kconfig | 10 + drivers/pwm/Makefile | 1 + drivers/pwm/pwm-microchip-core.c | 507 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 518 insertions(+) create mode 100644 drivers/pwm/pwm-microchip-core.c (limited to 'drivers/pwm') diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 8df861b1f4a3..41cdafbc17a8 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -405,6 +405,16 @@ config PWM_MEDIATEK To compile this driver as a module, choose M here: the module will be called pwm-mediatek. +config PWM_MICROCHIP_CORE + tristate "Microchip corePWM PWM support" + depends on SOC_MICROCHIP_POLARFIRE || COMPILE_TEST + depends on HAS_IOMEM && OF + help + PWM driver for Microchip FPGA soft IP core. + + To compile this driver as a module, choose M here: the module + will be called pwm-microchip-core. + config PWM_MXS tristate "Freescale MXS PWM support" depends on ARCH_MXS || COMPILE_TEST diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 19899b912e00..fce34f7a2d62 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -35,6 +35,7 @@ obj-$(CONFIG_PWM_LPSS_PCI) += pwm-lpss-pci.o obj-$(CONFIG_PWM_LPSS_PLATFORM) += pwm-lpss-platform.o obj-$(CONFIG_PWM_MESON) += pwm-meson.o obj-$(CONFIG_PWM_MEDIATEK) += pwm-mediatek.o +obj-$(CONFIG_PWM_MICROCHIP_CORE) += pwm-microchip-core.o obj-$(CONFIG_PWM_MTK_DISP) += pwm-mtk-disp.o obj-$(CONFIG_PWM_MXS) += pwm-mxs.o obj-$(CONFIG_PWM_NTXEC) += pwm-ntxec.o diff --git a/drivers/pwm/pwm-microchip-core.c b/drivers/pwm/pwm-microchip-core.c new file mode 100644 index 000000000000..8750b57684a9 --- /dev/null +++ b/drivers/pwm/pwm-microchip-core.c @@ -0,0 +1,507 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * corePWM driver for Microchip "soft" FPGA IP cores. + * + * Copyright (c) 2021-2023 Microchip Corporation. All rights reserved. + * Author: Conor Dooley + * Documentation: + * https://www.microsemi.com/document-portal/doc_download/1245275-corepwm-hb + * + * Limitations: + * - If the IP block is configured without "shadow registers", all register + * writes will take effect immediately, causing glitches on the output. + * If shadow registers *are* enabled, setting the "SYNC_UPDATE" register + * notifies the core that it needs to update the registers defining the + * waveform from the contents of the "shadow registers". Otherwise, changes + * will take effective immediately, even for those channels. + * As setting the period/duty cycle takes 4 register writes, there is a window + * in which this races against the start of a new period. + * - The IP block has no concept of a duty cycle, only rising/falling edges of + * the waveform. Unfortunately, if the rising & falling edges registers have + * the same value written to them the IP block will do whichever of a rising + * or a falling edge is possible. I.E. a 50% waveform at twice the requested + * period. Therefore to get a 0% waveform, the output is set the max high/low + * time depending on polarity. + * If the duty cycle is 0%, and the requested period is less than the + * available period resolution, this will manifest as a ~100% waveform (with + * some output glitches) rather than 50%. + * - The PWM period is set for the whole IP block not per channel. The driver + * will only change the period if no other PWM output is enabled. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MCHPCOREPWM_PRESCALE_MAX 0xff +#define MCHPCOREPWM_PERIOD_STEPS_MAX 0xfe +#define MCHPCOREPWM_PERIOD_MAX 0xff00 + +#define MCHPCOREPWM_PRESCALE 0x00 +#define MCHPCOREPWM_PERIOD 0x04 +#define MCHPCOREPWM_EN(i) (0x08 + 0x04 * (i)) /* 0x08, 0x0c */ +#define MCHPCOREPWM_POSEDGE(i) (0x10 + 0x08 * (i)) /* 0x10, 0x18, ..., 0x88 */ +#define MCHPCOREPWM_NEGEDGE(i) (0x14 + 0x08 * (i)) /* 0x14, 0x1c, ..., 0x8c */ +#define MCHPCOREPWM_SYNC_UPD 0xe4 +#define MCHPCOREPWM_TIMEOUT_MS 100u + +struct mchp_core_pwm_chip { + struct pwm_chip chip; + struct clk *clk; + void __iomem *base; + struct mutex lock; /* protects the shared period */ + ktime_t update_timestamp; + u32 sync_update_mask; + u16 channel_enabled; +}; + +static inline struct mchp_core_pwm_chip *to_mchp_core_pwm(struct pwm_chip *chip) +{ + return container_of(chip, struct mchp_core_pwm_chip, chip); +} + +static void mchp_core_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm, + bool enable, u64 period) +{ + struct mchp_core_pwm_chip *mchp_core_pwm = to_mchp_core_pwm(chip); + u8 channel_enable, reg_offset, shift; + + /* + * There are two adjacent 8 bit control regs, the lower reg controls + * 0-7 and the upper reg 8-15. Check if the pwm is in the upper reg + * and if so, offset by the bus width. + */ + reg_offset = MCHPCOREPWM_EN(pwm->hwpwm >> 3); + shift = pwm->hwpwm & 7; + + channel_enable = readb_relaxed(mchp_core_pwm->base + reg_offset); + channel_enable &= ~(1 << shift); + channel_enable |= (enable << shift); + + writel_relaxed(channel_enable, mchp_core_pwm->base + reg_offset); + mchp_core_pwm->channel_enabled &= ~BIT(pwm->hwpwm); + mchp_core_pwm->channel_enabled |= enable << pwm->hwpwm; + + /* + * The updated values will not appear on the bus until they have been + * applied to the waveform at the beginning of the next period. + * This is a NO-OP if the channel does not have shadow registers. + */ + if (mchp_core_pwm->sync_update_mask & (1 << pwm->hwpwm)) + mchp_core_pwm->update_timestamp = ktime_add_ns(ktime_get(), period); +} + +static void mchp_core_pwm_wait_for_sync_update(struct mchp_core_pwm_chip *mchp_core_pwm, + unsigned int channel) +{ + /* + * If a shadow register is used for this PWM channel, and iff there is + * a pending update to the waveform, we must wait for it to be applied + * before attempting to read its state. Reading the registers yields + * the currently implemented settings & the new ones are only readable + * once the current period has ended. + */ + + if (mchp_core_pwm->sync_update_mask & (1 << channel)) { + ktime_t current_time = ktime_get(); + s64 remaining_ns; + u32 delay_us; + + remaining_ns = ktime_to_ns(ktime_sub(mchp_core_pwm->update_timestamp, + current_time)); + + /* + * If the update has gone through, don't bother waiting for + * obvious reasons. Otherwise wait around for an appropriate + * amount of time for the update to go through. + */ + if (remaining_ns <= 0) + return; + + delay_us = DIV_ROUND_UP_ULL(remaining_ns, NSEC_PER_USEC); + fsleep(delay_us); + } +} + +static u64 mchp_core_pwm_calc_duty(const struct pwm_state *state, u64 clk_rate, + u8 prescale, u8 period_steps) +{ + u64 duty_steps, tmp; + + /* + * Calculate the duty cycle in multiples of the prescaled period: + * duty_steps = duty_in_ns / step_in_ns + * step_in_ns = (prescale * NSEC_PER_SEC) / clk_rate + * The code below is rearranged slightly to only divide once. + */ + tmp = (((u64)prescale) + 1) * NSEC_PER_SEC; + duty_steps = mul_u64_u64_div_u64(state->duty_cycle, clk_rate, tmp); + + return duty_steps; +} + +static void mchp_core_pwm_apply_duty(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state, u64 duty_steps, + u16 period_steps) +{ + struct mchp_core_pwm_chip *mchp_core_pwm = to_mchp_core_pwm(chip); + u8 posedge, negedge; + u8 first_edge = 0, second_edge = duty_steps; + + /* + * Setting posedge == negedge doesn't yield a constant output, + * so that's an unsuitable setting to model duty_steps = 0. + * In that case set the unwanted edge to a value that never + * triggers. + */ + if (duty_steps == 0) + first_edge = period_steps + 1; + + if (state->polarity == PWM_POLARITY_INVERSED) { + negedge = first_edge; + posedge = second_edge; + } else { + posedge = first_edge; + negedge = second_edge; + } + + /* + * Set the sync bit which ensures that periods that already started are + * completed unaltered. At each counter reset event the values are + * updated from the shadow registers. + */ + writel_relaxed(posedge, mchp_core_pwm->base + MCHPCOREPWM_POSEDGE(pwm->hwpwm)); + writel_relaxed(negedge, mchp_core_pwm->base + MCHPCOREPWM_NEGEDGE(pwm->hwpwm)); +} + +static int mchp_core_pwm_calc_period(const struct pwm_state *state, unsigned long clk_rate, + u16 *prescale, u16 *period_steps) +{ + u64 tmp; + + /* + * Calculate the period cycles and prescale values. + * The registers are each 8 bits wide & multiplied to compute the period + * using the formula: + * (prescale + 1) * (period_steps + 1) + * period = ------------------------------------- + * clk_rate + * so the maximum period that can be generated is 0x10000 times the + * period of the input clock. + * However, due to the design of the "hardware", it is not possible to + * attain a 100% duty cycle if the full range of period_steps is used. + * Therefore period_steps is restricted to 0xfe and the maximum multiple + * of the clock period attainable is (0xff + 1) * (0xfe + 1) = 0xff00 + * + * The prescale and period_steps registers operate similarly to + * CLK_DIVIDER_ONE_BASED, where the value used by the hardware is that + * in the register plus one. + * It's therefore not possible to set a period lower than 1/clk_rate, so + * if tmp is 0, abort. Without aborting, we will set a period that is + * greater than that requested and, more importantly, will trigger the + * neg-/pos-edge issue described in the limitations. + */ + tmp = mul_u64_u64_div_u64(state->period, clk_rate, NSEC_PER_SEC); + if (tmp >= MCHPCOREPWM_PERIOD_MAX) { + *prescale = MCHPCOREPWM_PRESCALE_MAX; + *period_steps = MCHPCOREPWM_PERIOD_STEPS_MAX; + + return 0; + } + + /* + * There are multiple strategies that could be used to choose the + * prescale & period_steps values. + * Here the idea is to pick values so that the selection of duty cycles + * is as finegrain as possible, while also keeping the period less than + * that requested. + * + * A simple way to satisfy the first condition is to always set + * period_steps to its maximum value. This neatly also satisfies the + * second condition too, since using the maximum value of period_steps + * to calculate prescale actually calculates its upper bound. + * Integer division will ensure a round down, so the period will thereby + * always be less than that requested. + * + * The downside of this approach is a significant degree of inaccuracy, + * especially as tmp approaches integer multiples of + * MCHPCOREPWM_PERIOD_STEPS_MAX. + * + * As we must produce a period less than that requested, and for the + * sake of creating a simple algorithm, disallow small values of tmp + * that would need special handling. + */ + if (tmp < MCHPCOREPWM_PERIOD_STEPS_MAX + 1) + return -EINVAL; + + /* + * This "optimal" value for prescale is be calculated using the maximum + * permitted value of period_steps, 0xfe. + * + * period * clk_rate + * prescale = ------------------------- - 1 + * NSEC_PER_SEC * (0xfe + 1) + * + * + * period * clk_rate + * ------------------- was precomputed as `tmp` + * NSEC_PER_SEC + */ + *prescale = ((u16)tmp) / (MCHPCOREPWM_PERIOD_STEPS_MAX + 1) - 1; + + /* + * period_steps can be computed from prescale: + * period * clk_rate + * period_steps = ----------------------------- - 1 + * NSEC_PER_SEC * (prescale + 1) + * + * However, in this approximation, we simply use the maximum value that + * was used to compute prescale. + */ + *period_steps = MCHPCOREPWM_PERIOD_STEPS_MAX; + + return 0; +} + +static int mchp_core_pwm_apply_locked(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct mchp_core_pwm_chip *mchp_core_pwm = to_mchp_core_pwm(chip); + bool period_locked; + unsigned long clk_rate; + u64 duty_steps; + u16 prescale, period_steps; + int ret; + + if (!state->enabled) { + mchp_core_pwm_enable(chip, pwm, false, pwm->state.period); + return 0; + } + + /* + * If clk_rate is too big, the following multiplication might overflow. + * However this is implausible, as the fabric of current FPGAs cannot + * provide clocks at a rate high enough. + */ + clk_rate = clk_get_rate(mchp_core_pwm->clk); + if (clk_rate >= NSEC_PER_SEC) + return -EINVAL; + + ret = mchp_core_pwm_calc_period(state, clk_rate, &prescale, &period_steps); + if (ret) + return ret; + + /* + * If the only thing that has changed is the duty cycle or the polarity, + * we can shortcut the calculations and just compute/apply the new duty + * cycle pos & neg edges + * As all the channels share the same period, do not allow it to be + * changed if any other channels are enabled. + * If the period is locked, it may not be possible to use a period + * less than that requested. In that case, we just abort. + */ + period_locked = mchp_core_pwm->channel_enabled & ~(1 << pwm->hwpwm); + + if (period_locked) { + u16 hw_prescale; + u16 hw_period_steps; + + hw_prescale = readb_relaxed(mchp_core_pwm->base + MCHPCOREPWM_PRESCALE); + hw_period_steps = readb_relaxed(mchp_core_pwm->base + MCHPCOREPWM_PERIOD); + + if ((period_steps + 1) * (prescale + 1) < + (hw_period_steps + 1) * (hw_prescale + 1)) + return -EINVAL; + + /* + * It is possible that something could have set the period_steps + * register to 0xff, which would prevent us from setting a 100% + * or 0% relative duty cycle, as explained above in + * mchp_core_pwm_calc_period(). + * The period is locked and we cannot change this, so we abort. + */ + if (hw_period_steps == MCHPCOREPWM_PERIOD_STEPS_MAX) + return -EINVAL; + + prescale = hw_prescale; + period_steps = hw_period_steps; + } + + duty_steps = mchp_core_pwm_calc_duty(state, clk_rate, prescale, period_steps); + + /* + * Because the period is not per channel, it is possible that the + * requested duty cycle is longer than the period, in which case cap it + * to the period, IOW a 100% duty cycle. + */ + if (duty_steps > period_steps) + duty_steps = period_steps + 1; + + if (!period_locked) { + writel_relaxed(prescale, mchp_core_pwm->base + MCHPCOREPWM_PRESCALE); + writel_relaxed(period_steps, mchp_core_pwm->base + MCHPCOREPWM_PERIOD); + } + + mchp_core_pwm_apply_duty(chip, pwm, state, duty_steps, period_steps); + + mchp_core_pwm_enable(chip, pwm, true, pwm->state.period); + + return 0; +} + +static int mchp_core_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct mchp_core_pwm_chip *mchp_core_pwm = to_mchp_core_pwm(chip); + int ret; + + mutex_lock(&mchp_core_pwm->lock); + + mchp_core_pwm_wait_for_sync_update(mchp_core_pwm, pwm->hwpwm); + + ret = mchp_core_pwm_apply_locked(chip, pwm, state); + + mutex_unlock(&mchp_core_pwm->lock); + + return ret; +} + +static int mchp_core_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) +{ + struct mchp_core_pwm_chip *mchp_core_pwm = to_mchp_core_pwm(chip); + u64 rate; + u16 prescale, period_steps; + u8 duty_steps, posedge, negedge; + + mutex_lock(&mchp_core_pwm->lock); + + mchp_core_pwm_wait_for_sync_update(mchp_core_pwm, pwm->hwpwm); + + if (mchp_core_pwm->channel_enabled & (1 << pwm->hwpwm)) + state->enabled = true; + else + state->enabled = false; + + rate = clk_get_rate(mchp_core_pwm->clk); + + /* + * Calculating the period: + * The registers are each 8 bits wide & multiplied to compute the period + * using the formula: + * (prescale + 1) * (period_steps + 1) + * period = ------------------------------------- + * clk_rate + * + * Note: + * The prescale and period_steps registers operate similarly to + * CLK_DIVIDER_ONE_BASED, where the value used by the hardware is that + * in the register plus one. + */ + prescale = readb_relaxed(mchp_core_pwm->base + MCHPCOREPWM_PRESCALE); + period_steps = readb_relaxed(mchp_core_pwm->base + MCHPCOREPWM_PERIOD); + + state->period = (period_steps + 1) * (prescale + 1); + state->period *= NSEC_PER_SEC; + state->period = DIV64_U64_ROUND_UP(state->period, rate); + + posedge = readb_relaxed(mchp_core_pwm->base + MCHPCOREPWM_POSEDGE(pwm->hwpwm)); + negedge = readb_relaxed(mchp_core_pwm->base + MCHPCOREPWM_NEGEDGE(pwm->hwpwm)); + + mutex_unlock(&mchp_core_pwm->lock); + + if (negedge == posedge) { + state->duty_cycle = state->period; + state->period *= 2; + } else { + duty_steps = abs((s16)posedge - (s16)negedge); + state->duty_cycle = duty_steps * (prescale + 1) * NSEC_PER_SEC; + state->duty_cycle = DIV64_U64_ROUND_UP(state->duty_cycle, rate); + } + + state->polarity = negedge < posedge ? PWM_POLARITY_INVERSED : PWM_POLARITY_NORMAL; + + return 0; +} + +static const struct pwm_ops mchp_core_pwm_ops = { + .apply = mchp_core_pwm_apply, + .get_state = mchp_core_pwm_get_state, + .owner = THIS_MODULE, +}; + +static const struct of_device_id mchp_core_of_match[] = { + { + .compatible = "microchip,corepwm-rtl-v4", + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, mchp_core_of_match); + +static int mchp_core_pwm_probe(struct platform_device *pdev) +{ + struct mchp_core_pwm_chip *mchp_core_pwm; + struct resource *regs; + int ret; + + mchp_core_pwm = devm_kzalloc(&pdev->dev, sizeof(*mchp_core_pwm), GFP_KERNEL); + if (!mchp_core_pwm) + return -ENOMEM; + + mchp_core_pwm->base = devm_platform_get_and_ioremap_resource(pdev, 0, ®s); + if (IS_ERR(mchp_core_pwm->base)) + return PTR_ERR(mchp_core_pwm->base); + + mchp_core_pwm->clk = devm_clk_get_enabled(&pdev->dev, NULL); + if (IS_ERR(mchp_core_pwm->clk)) + return dev_err_probe(&pdev->dev, PTR_ERR(mchp_core_pwm->clk), + "failed to get PWM clock\n"); + + if (of_property_read_u32(pdev->dev.of_node, "microchip,sync-update-mask", + &mchp_core_pwm->sync_update_mask)) + mchp_core_pwm->sync_update_mask = 0; + + mutex_init(&mchp_core_pwm->lock); + + mchp_core_pwm->chip.dev = &pdev->dev; + mchp_core_pwm->chip.ops = &mchp_core_pwm_ops; + mchp_core_pwm->chip.npwm = 16; + + mchp_core_pwm->channel_enabled = readb_relaxed(mchp_core_pwm->base + MCHPCOREPWM_EN(0)); + mchp_core_pwm->channel_enabled |= + readb_relaxed(mchp_core_pwm->base + MCHPCOREPWM_EN(1)) << 8; + + /* + * Enable synchronous update mode for all channels for which shadow + * registers have been synthesised. + */ + writel_relaxed(1U, mchp_core_pwm->base + MCHPCOREPWM_SYNC_UPD); + mchp_core_pwm->update_timestamp = ktime_get(); + + ret = devm_pwmchip_add(&pdev->dev, &mchp_core_pwm->chip); + if (ret) + return dev_err_probe(&pdev->dev, ret, "Failed to add pwmchip\n"); + + return 0; +} + +static struct platform_driver mchp_core_pwm_driver = { + .driver = { + .name = "mchp-core-pwm", + .of_match_table = mchp_core_of_match, + }, + .probe = mchp_core_pwm_probe, +}; +module_platform_driver(mchp_core_pwm_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Conor Dooley "); +MODULE_DESCRIPTION("corePWM driver for Microchip FPGAs"); -- cgit v1.2.3 From cdcffafc4d845cc0c6392cba168c7a942734cce7 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Mon, 22 May 2023 14:07:42 +0300 Subject: pwm: ab8500: Fix error code in probe() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This code accidentally return positive EINVAL instead of negative -EINVAL. Fixes: eb41f334589d ("pwm: ab8500: Fix register offset calculation to not depend on probe order") Signed-off-by: Dan Carpenter Reviewed-by: Uwe Kleine-König Reviewed-by: Linus Walleij Signed-off-by: Thierry Reding --- drivers/pwm/pwm-ab8500.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/pwm') diff --git a/drivers/pwm/pwm-ab8500.c b/drivers/pwm/pwm-ab8500.c index 507ff0d5f7bd..583a7d69c741 100644 --- a/drivers/pwm/pwm-ab8500.c +++ b/drivers/pwm/pwm-ab8500.c @@ -190,7 +190,7 @@ static int ab8500_pwm_probe(struct platform_device *pdev) int err; if (pdev->id < 1 || pdev->id > 31) - return dev_err_probe(&pdev->dev, EINVAL, "Invalid device id %d\n", pdev->id); + return dev_err_probe(&pdev->dev, -EINVAL, "Invalid device id %d\n", pdev->id); /* * Nothing to be done in probe, this is required to get the -- cgit v1.2.3 From 8bfc651a28ceaab5334d7284a31fce7cb1615348 Mon Sep 17 00:00:00 2001 From: Uwe Kleine-König Date: Thu, 25 May 2023 23:13:05 +0200 Subject: pwm: pca9685: Switch i2c driver back to use .probe() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After commit b8a1a4cd5a98 ("i2c: Provide a temporary .probe_new() call-back type"), all drivers being converted to .probe_new() and then 03c835f498b5 ("i2c: Switch .probe() to not take an id parameter") convert back to (the new) .probe() to be able to eventually drop .probe_new() from struct i2c_driver. Signed-off-by: Uwe Kleine-König Signed-off-by: Thierry Reding --- drivers/pwm/pwm-pca9685.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/pwm') diff --git a/drivers/pwm/pwm-pca9685.c b/drivers/pwm/pwm-pca9685.c index 3ed5a48ca581..3038a68412a7 100644 --- a/drivers/pwm/pwm-pca9685.c +++ b/drivers/pwm/pwm-pca9685.c @@ -665,7 +665,7 @@ static struct i2c_driver pca9685_i2c_driver = { .of_match_table = of_match_ptr(pca9685_dt_ids), .pm = &pca9685_pwm_pm, }, - .probe_new = pca9685_pwm_probe, + .probe = pca9685_pwm_probe, .remove = pca9685_pwm_remove, .id_table = pca9685_id, }; -- cgit v1.2.3 From bc13d60e4e1e945b34769a4a4c2b172e8552abe5 Mon Sep 17 00:00:00 2001 From: Shuijing Li Date: Wed, 31 May 2023 11:10:01 +0800 Subject: pwm: mtk_disp: Fix the disable flow of disp_pwm MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There is a flow error in the original mtk_disp_pwm_apply() function. If this function is called when the clock is disabled, there will be a chance to operate the disp_pwm register, resulting in disp_pwm exception. Fix this accordingly. Fixes: 888a623db5d0 ("pwm: mtk-disp: Implement atomic API .apply()") Signed-off-by: Shuijing Li Reviewed-by: Matthias Brugger Tested-by: Fei Shao Acked-by: Uwe Kleine-König Reviewed-by: Alexandre Mergnat Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Thierry Reding --- drivers/pwm/pwm-mtk-disp.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) (limited to 'drivers/pwm') diff --git a/drivers/pwm/pwm-mtk-disp.c b/drivers/pwm/pwm-mtk-disp.c index 79e321e96f56..2401b6733241 100644 --- a/drivers/pwm/pwm-mtk-disp.c +++ b/drivers/pwm/pwm-mtk-disp.c @@ -79,14 +79,11 @@ static int mtk_disp_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, if (state->polarity != PWM_POLARITY_NORMAL) return -EINVAL; - if (!state->enabled) { - mtk_disp_pwm_update_bits(mdp, DISP_PWM_EN, mdp->data->enable_mask, - 0x0); - - if (mdp->enabled) { - clk_disable_unprepare(mdp->clk_mm); - clk_disable_unprepare(mdp->clk_main); - } + if (!state->enabled && mdp->enabled) { + mtk_disp_pwm_update_bits(mdp, DISP_PWM_EN, + mdp->data->enable_mask, 0x0); + clk_disable_unprepare(mdp->clk_mm); + clk_disable_unprepare(mdp->clk_main); mdp->enabled = false; return 0; -- cgit v1.2.3 From 254d3a727421ccc935f085eaa9bae51cb6c9df25 Mon Sep 17 00:00:00 2001 From: Biju Das Date: Fri, 2 Jun 2023 11:11:21 +0100 Subject: pwm: Add Renesas RZ/G2L MTU3a PWM driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The RZ/G2L Multi-Function Timer Pulse Unit 3 (a.k.a MTU3a) uses one counter and two match components to configure duty_cycle and period to generate PWM output waveform. Add basic support for RZ/G2L MTU3a PWM driver by creating separate PWM channels for each IOs. Signed-off-by: Biju Das Reviewed-by: Uwe Kleine-König Signed-off-by: Thierry Reding --- drivers/pwm/Kconfig | 11 + drivers/pwm/Makefile | 1 + drivers/pwm/pwm-rz-mtu3.c | 551 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 563 insertions(+) create mode 100644 drivers/pwm/pwm-rz-mtu3.c (limited to 'drivers/pwm') diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 41cdafbc17a8..6210babb0741 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -503,6 +503,17 @@ config PWM_ROCKCHIP Generic PWM framework driver for the PWM controller found on Rockchip SoCs. +config PWM_RZ_MTU3 + tristate "Renesas RZ/G2L MTU3a PWM Timer support" + depends on RZ_MTU3 || COMPILE_TEST + depends on HAS_IOMEM + help + This driver exposes the MTU3a PWM Timer controller found in Renesas + RZ/G2L like chips through the PWM API. + + To compile this driver as a module, choose M here: the module + will be called pwm-rz-mtu3. + config PWM_SAMSUNG tristate "Samsung PWM support" depends on PLAT_SAMSUNG || ARCH_S5PV210 || ARCH_EXYNOS || COMPILE_TEST diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index fce34f7a2d62..c822389c2a24 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -46,6 +46,7 @@ obj-$(CONFIG_PWM_RASPBERRYPI_POE) += pwm-raspberrypi-poe.o obj-$(CONFIG_PWM_RCAR) += pwm-rcar.o obj-$(CONFIG_PWM_RENESAS_TPU) += pwm-renesas-tpu.o obj-$(CONFIG_PWM_ROCKCHIP) += pwm-rockchip.o +obj-$(CONFIG_PWM_RZ_MTU3) += pwm-rz-mtu3.o obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o obj-$(CONFIG_PWM_SIFIVE) += pwm-sifive.o obj-$(CONFIG_PWM_SL28CPLD) += pwm-sl28cpld.o diff --git a/drivers/pwm/pwm-rz-mtu3.c b/drivers/pwm/pwm-rz-mtu3.c new file mode 100644 index 000000000000..bed8bd671e37 --- /dev/null +++ b/drivers/pwm/pwm-rz-mtu3.c @@ -0,0 +1,551 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Renesas RZ/G2L MTU3a PWM Timer driver + * + * Copyright (C) 2023 Renesas Electronics Corporation + * + * Hardware manual for this IP can be found here + * https://www.renesas.com/eu/en/document/mah/rzg2l-group-rzg2lc-group-users-manual-hardware-0?language=en + * + * Limitations: + * - When PWM is disabled, the output is driven to Hi-Z. + * - While the hardware supports both polarities, the driver (for now) + * only handles normal polarity. + * - HW uses one counter and two match components to configure duty_cycle + * and period. + * - Multi-Function Timer Pulse Unit (a.k.a MTU) has 7 HW channels for PWM + * operations. (The channels are MTU{0..4, 6, 7}.) + * - MTU{1, 2} channels have a single IO, whereas all other HW channels have + * 2 IOs. + * - Each IO is modelled as an independent PWM channel. + * - rz_mtu3_channel_io_map table is used to map the PWM channel to the + * corresponding HW channel as there are difference in number of IOs + * between HW channels. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define RZ_MTU3_MAX_PWM_CHANNELS 12 +#define RZ_MTU3_MAX_HW_CHANNELS 7 + +/** + * struct rz_mtu3_channel_io_map - MTU3 pwm channel map + * + * @base_pwm_number: First PWM of a channel + * @num: number of IOs on the HW channel. + */ +struct rz_mtu3_channel_io_map { + u8 base_pwm_number; + u8 num_channel_ios; +}; + +/** + * struct rz_mtu3_pwm_channel - MTU3 pwm channel data + * + * @mtu: MTU3 channel data + * @map: MTU3 pwm channel map + */ +struct rz_mtu3_pwm_channel { + struct rz_mtu3_channel *mtu; + const struct rz_mtu3_channel_io_map *map; +}; + +/** + * struct rz_mtu3_pwm_chip - MTU3 pwm private data + * + * @chip: MTU3 pwm chip data + * @clk: MTU3 module clock + * @lock: Lock to prevent concurrent access for usage count + * @rate: MTU3 clock rate + * @user_count: MTU3 usage count + * @enable_count: MTU3 enable count + * @prescale: MTU3 prescale + * @channel_data: MTU3 pwm channel data + */ + +struct rz_mtu3_pwm_chip { + struct pwm_chip chip; + struct clk *clk; + struct mutex lock; + unsigned long rate; + u32 user_count[RZ_MTU3_MAX_HW_CHANNELS]; + u32 enable_count[RZ_MTU3_MAX_HW_CHANNELS]; + u8 prescale[RZ_MTU3_MAX_HW_CHANNELS]; + struct rz_mtu3_pwm_channel channel_data[RZ_MTU3_MAX_HW_CHANNELS]; +}; + +/* + * The MTU channels are {0..4, 6, 7} and the number of IO on MTU1 + * and MTU2 channel is 1 compared to 2 on others. + */ +static const struct rz_mtu3_channel_io_map channel_map[] = { + { 0, 2 }, { 2, 1 }, { 3, 1 }, { 4, 2 }, { 6, 2 }, { 8, 2 }, { 10, 2 } +}; + +static inline struct rz_mtu3_pwm_chip *to_rz_mtu3_pwm_chip(struct pwm_chip *chip) +{ + return container_of(chip, struct rz_mtu3_pwm_chip, chip); +} + +static void rz_mtu3_pwm_read_tgr_registers(struct rz_mtu3_pwm_channel *priv, + u16 reg_pv_offset, u16 *pv_val, + u16 reg_dc_offset, u16 *dc_val) +{ + *pv_val = rz_mtu3_16bit_ch_read(priv->mtu, reg_pv_offset); + *dc_val = rz_mtu3_16bit_ch_read(priv->mtu, reg_dc_offset); +} + +static void rz_mtu3_pwm_write_tgr_registers(struct rz_mtu3_pwm_channel *priv, + u16 reg_pv_offset, u16 pv_val, + u16 reg_dc_offset, u16 dc_val) +{ + rz_mtu3_16bit_ch_write(priv->mtu, reg_pv_offset, pv_val); + rz_mtu3_16bit_ch_write(priv->mtu, reg_dc_offset, dc_val); +} + +static u8 rz_mtu3_pwm_calculate_prescale(struct rz_mtu3_pwm_chip *rz_mtu3, + u64 period_cycles) +{ + u32 prescaled_period_cycles; + u8 prescale; + + /* + * Supported prescale values are 1, 4, 16 and 64. + * TODO: Support prescale values 2, 8, 32, 256 and 1024. + */ + prescaled_period_cycles = period_cycles >> 16; + if (prescaled_period_cycles >= 16) + prescale = 3; + else + prescale = (fls(prescaled_period_cycles) + 1) / 2; + + return prescale; +} + +static struct rz_mtu3_pwm_channel * +rz_mtu3_get_channel(struct rz_mtu3_pwm_chip *rz_mtu3_pwm, u32 hwpwm) +{ + struct rz_mtu3_pwm_channel *priv = rz_mtu3_pwm->channel_data; + unsigned int ch; + + for (ch = 0; ch < RZ_MTU3_MAX_HW_CHANNELS; ch++, priv++) { + if (priv->map->base_pwm_number + priv->map->num_channel_ios > hwpwm) + break; + } + + return priv; +} + +static bool rz_mtu3_pwm_is_ch_enabled(struct rz_mtu3_pwm_chip *rz_mtu3_pwm, + u32 hwpwm) +{ + struct rz_mtu3_pwm_channel *priv; + bool is_channel_en; + u8 val; + + priv = rz_mtu3_get_channel(rz_mtu3_pwm, hwpwm); + is_channel_en = rz_mtu3_is_enabled(priv->mtu); + if (!is_channel_en) + return false; + + if (priv->map->base_pwm_number == hwpwm) + val = rz_mtu3_8bit_ch_read(priv->mtu, RZ_MTU3_TIORH); + else + val = rz_mtu3_8bit_ch_read(priv->mtu, RZ_MTU3_TIORL); + + return val & RZ_MTU3_TIOR_IOA; +} + +static int rz_mtu3_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct rz_mtu3_pwm_chip *rz_mtu3_pwm = to_rz_mtu3_pwm_chip(chip); + struct rz_mtu3_pwm_channel *priv; + bool is_mtu3_channel_available; + u32 ch; + + priv = rz_mtu3_get_channel(rz_mtu3_pwm, pwm->hwpwm); + ch = priv - rz_mtu3_pwm->channel_data; + + mutex_lock(&rz_mtu3_pwm->lock); + /* + * Each channel must be requested only once, so if the channel + * serves two PWMs and the other is already requested, skip over + * rz_mtu3_request_channel() + */ + if (!rz_mtu3_pwm->user_count[ch]) { + is_mtu3_channel_available = rz_mtu3_request_channel(priv->mtu); + if (!is_mtu3_channel_available) { + mutex_unlock(&rz_mtu3_pwm->lock); + return -EBUSY; + } + } + + rz_mtu3_pwm->user_count[ch]++; + mutex_unlock(&rz_mtu3_pwm->lock); + + return 0; +} + +static void rz_mtu3_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct rz_mtu3_pwm_chip *rz_mtu3_pwm = to_rz_mtu3_pwm_chip(chip); + struct rz_mtu3_pwm_channel *priv; + u32 ch; + + priv = rz_mtu3_get_channel(rz_mtu3_pwm, pwm->hwpwm); + ch = priv - rz_mtu3_pwm->channel_data; + + mutex_lock(&rz_mtu3_pwm->lock); + rz_mtu3_pwm->user_count[ch]--; + if (!rz_mtu3_pwm->user_count[ch]) + rz_mtu3_release_channel(priv->mtu); + + mutex_unlock(&rz_mtu3_pwm->lock); +} + +static int rz_mtu3_pwm_enable(struct rz_mtu3_pwm_chip *rz_mtu3_pwm, + struct pwm_device *pwm) +{ + struct rz_mtu3_pwm_channel *priv; + u32 ch; + u8 val; + int rc; + + rc = pm_runtime_resume_and_get(rz_mtu3_pwm->chip.dev); + if (rc) + return rc; + + priv = rz_mtu3_get_channel(rz_mtu3_pwm, pwm->hwpwm); + ch = priv - rz_mtu3_pwm->channel_data; + val = RZ_MTU3_TIOR_OC_IOB_TOGGLE | RZ_MTU3_TIOR_OC_IOA_H_COMP_MATCH; + + rz_mtu3_8bit_ch_write(priv->mtu, RZ_MTU3_TMDR1, RZ_MTU3_TMDR1_MD_PWMMODE1); + if (priv->map->base_pwm_number == pwm->hwpwm) + rz_mtu3_8bit_ch_write(priv->mtu, RZ_MTU3_TIORH, val); + else + rz_mtu3_8bit_ch_write(priv->mtu, RZ_MTU3_TIORL, val); + + mutex_lock(&rz_mtu3_pwm->lock); + if (!rz_mtu3_pwm->enable_count[ch]) + rz_mtu3_enable(priv->mtu); + + rz_mtu3_pwm->enable_count[ch]++; + mutex_unlock(&rz_mtu3_pwm->lock); + + return 0; +} + +static void rz_mtu3_pwm_disable(struct rz_mtu3_pwm_chip *rz_mtu3_pwm, + struct pwm_device *pwm) +{ + struct rz_mtu3_pwm_channel *priv; + u32 ch; + + priv = rz_mtu3_get_channel(rz_mtu3_pwm, pwm->hwpwm); + ch = priv - rz_mtu3_pwm->channel_data; + + /* Disable output pins of MTU3 channel */ + if (priv->map->base_pwm_number == pwm->hwpwm) + rz_mtu3_8bit_ch_write(priv->mtu, RZ_MTU3_TIORH, RZ_MTU3_TIOR_OC_RETAIN); + else + rz_mtu3_8bit_ch_write(priv->mtu, RZ_MTU3_TIORL, RZ_MTU3_TIOR_OC_RETAIN); + + mutex_lock(&rz_mtu3_pwm->lock); + rz_mtu3_pwm->enable_count[ch]--; + if (!rz_mtu3_pwm->enable_count[ch]) + rz_mtu3_disable(priv->mtu); + + mutex_unlock(&rz_mtu3_pwm->lock); + + pm_runtime_put_sync(rz_mtu3_pwm->chip.dev); +} + +static int rz_mtu3_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) +{ + struct rz_mtu3_pwm_chip *rz_mtu3_pwm = to_rz_mtu3_pwm_chip(chip); + int rc; + + rc = pm_runtime_resume_and_get(chip->dev); + if (rc) + return rc; + + state->enabled = rz_mtu3_pwm_is_ch_enabled(rz_mtu3_pwm, pwm->hwpwm); + if (state->enabled) { + struct rz_mtu3_pwm_channel *priv; + u8 prescale, val; + u16 dc, pv; + u64 tmp; + + priv = rz_mtu3_get_channel(rz_mtu3_pwm, pwm->hwpwm); + if (priv->map->base_pwm_number == pwm->hwpwm) + rz_mtu3_pwm_read_tgr_registers(priv, RZ_MTU3_TGRA, &pv, + RZ_MTU3_TGRB, &dc); + else + rz_mtu3_pwm_read_tgr_registers(priv, RZ_MTU3_TGRC, &pv, + RZ_MTU3_TGRD, &dc); + + val = rz_mtu3_8bit_ch_read(priv->mtu, RZ_MTU3_TCR); + prescale = FIELD_GET(RZ_MTU3_TCR_TPCS, val); + + /* With prescale <= 7 and pv <= 0xffff this doesn't overflow. */ + tmp = NSEC_PER_SEC * (u64)pv << (2 * prescale); + state->period = DIV_ROUND_UP_ULL(tmp, rz_mtu3_pwm->rate); + tmp = NSEC_PER_SEC * (u64)dc << (2 * prescale); + state->duty_cycle = DIV_ROUND_UP_ULL(tmp, rz_mtu3_pwm->rate); + + if (state->duty_cycle > state->period) + state->duty_cycle = state->period; + } + + state->polarity = PWM_POLARITY_NORMAL; + pm_runtime_put(chip->dev); + + return 0; +} + +static u16 rz_mtu3_pwm_calculate_pv_or_dc(u64 period_or_duty_cycle, u8 prescale) +{ + return min(period_or_duty_cycle >> (2 * prescale), (u64)U16_MAX); +} + +static int rz_mtu3_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct rz_mtu3_pwm_chip *rz_mtu3_pwm = to_rz_mtu3_pwm_chip(chip); + struct rz_mtu3_pwm_channel *priv; + u64 period_cycles; + u64 duty_cycles; + u8 prescale; + u16 pv, dc; + u8 val; + u32 ch; + + priv = rz_mtu3_get_channel(rz_mtu3_pwm, pwm->hwpwm); + ch = priv - rz_mtu3_pwm->channel_data; + + period_cycles = mul_u64_u32_div(state->period, rz_mtu3_pwm->rate, + NSEC_PER_SEC); + prescale = rz_mtu3_pwm_calculate_prescale(rz_mtu3_pwm, period_cycles); + + /* + * Prescalar is shared by multiple channels, so prescale can + * NOT be modified when there are multiple channels in use with + * different settings. Modify prescalar if other PWM is off or handle + * it, if current prescale value is less than the one we want to set. + */ + if (rz_mtu3_pwm->enable_count[ch] > 1) { + if (rz_mtu3_pwm->prescale[ch] > prescale) + return -EBUSY; + + prescale = rz_mtu3_pwm->prescale[ch]; + } + + pv = rz_mtu3_pwm_calculate_pv_or_dc(period_cycles, prescale); + + duty_cycles = mul_u64_u32_div(state->duty_cycle, rz_mtu3_pwm->rate, + NSEC_PER_SEC); + dc = rz_mtu3_pwm_calculate_pv_or_dc(duty_cycles, prescale); + + /* + * If the PWM channel is disabled, make sure to turn on the clock + * before writing the register. + */ + if (!pwm->state.enabled) { + int rc; + + rc = pm_runtime_resume_and_get(chip->dev); + if (rc) + return rc; + } + + val = RZ_MTU3_TCR_CKEG_RISING | prescale; + + /* Counter must be stopped while updating TCR register */ + if (rz_mtu3_pwm->prescale[ch] != prescale && rz_mtu3_pwm->enable_count[ch]) + rz_mtu3_disable(priv->mtu); + + if (priv->map->base_pwm_number == pwm->hwpwm) { + rz_mtu3_8bit_ch_write(priv->mtu, RZ_MTU3_TCR, + RZ_MTU3_TCR_CCLR_TGRA | val); + rz_mtu3_pwm_write_tgr_registers(priv, RZ_MTU3_TGRA, pv, + RZ_MTU3_TGRB, dc); + } else { + rz_mtu3_8bit_ch_write(priv->mtu, RZ_MTU3_TCR, + RZ_MTU3_TCR_CCLR_TGRC | val); + rz_mtu3_pwm_write_tgr_registers(priv, RZ_MTU3_TGRC, pv, + RZ_MTU3_TGRD, dc); + } + + if (rz_mtu3_pwm->prescale[ch] != prescale) { + /* + * Prescalar is shared by multiple channels, we cache the + * prescalar value from first enabled channel and use the same + * value for both channels. + */ + rz_mtu3_pwm->prescale[ch] = prescale; + + if (rz_mtu3_pwm->enable_count[ch]) + rz_mtu3_enable(priv->mtu); + } + + /* If the PWM is not enabled, turn the clock off again to save power. */ + if (!pwm->state.enabled) + pm_runtime_put(chip->dev); + + return 0; +} + +static int rz_mtu3_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct rz_mtu3_pwm_chip *rz_mtu3_pwm = to_rz_mtu3_pwm_chip(chip); + bool enabled = pwm->state.enabled; + int ret; + + if (state->polarity != PWM_POLARITY_NORMAL) + return -EINVAL; + + if (!state->enabled) { + if (enabled) + rz_mtu3_pwm_disable(rz_mtu3_pwm, pwm); + + return 0; + } + + mutex_lock(&rz_mtu3_pwm->lock); + ret = rz_mtu3_pwm_config(chip, pwm, state); + mutex_unlock(&rz_mtu3_pwm->lock); + if (ret) + return ret; + + if (!enabled) + ret = rz_mtu3_pwm_enable(rz_mtu3_pwm, pwm); + + return ret; +} + +static const struct pwm_ops rz_mtu3_pwm_ops = { + .request = rz_mtu3_pwm_request, + .free = rz_mtu3_pwm_free, + .get_state = rz_mtu3_pwm_get_state, + .apply = rz_mtu3_pwm_apply, + .owner = THIS_MODULE, +}; + +static int rz_mtu3_pwm_pm_runtime_suspend(struct device *dev) +{ + struct rz_mtu3_pwm_chip *rz_mtu3_pwm = dev_get_drvdata(dev); + + clk_disable_unprepare(rz_mtu3_pwm->clk); + + return 0; +} + +static int rz_mtu3_pwm_pm_runtime_resume(struct device *dev) +{ + struct rz_mtu3_pwm_chip *rz_mtu3_pwm = dev_get_drvdata(dev); + + return clk_prepare_enable(rz_mtu3_pwm->clk); +} + +static DEFINE_RUNTIME_DEV_PM_OPS(rz_mtu3_pwm_pm_ops, + rz_mtu3_pwm_pm_runtime_suspend, + rz_mtu3_pwm_pm_runtime_resume, NULL); + +static void rz_mtu3_pwm_pm_disable(void *data) +{ + struct rz_mtu3_pwm_chip *rz_mtu3_pwm = data; + + clk_rate_exclusive_put(rz_mtu3_pwm->clk); + pm_runtime_disable(rz_mtu3_pwm->chip.dev); + pm_runtime_set_suspended(rz_mtu3_pwm->chip.dev); +} + +static int rz_mtu3_pwm_probe(struct platform_device *pdev) +{ + struct rz_mtu3 *parent_ddata = dev_get_drvdata(pdev->dev.parent); + struct rz_mtu3_pwm_chip *rz_mtu3_pwm; + struct device *dev = &pdev->dev; + unsigned int i, j = 0; + int ret; + + rz_mtu3_pwm = devm_kzalloc(&pdev->dev, sizeof(*rz_mtu3_pwm), GFP_KERNEL); + if (!rz_mtu3_pwm) + return -ENOMEM; + + rz_mtu3_pwm->clk = parent_ddata->clk; + + for (i = 0; i < RZ_MTU_NUM_CHANNELS; i++) { + if (i == RZ_MTU3_CHAN_5 || i == RZ_MTU3_CHAN_8) + continue; + + rz_mtu3_pwm->channel_data[j].mtu = &parent_ddata->channels[i]; + rz_mtu3_pwm->channel_data[j].mtu->dev = dev; + rz_mtu3_pwm->channel_data[j].map = &channel_map[j]; + j++; + } + + mutex_init(&rz_mtu3_pwm->lock); + platform_set_drvdata(pdev, rz_mtu3_pwm); + ret = clk_prepare_enable(rz_mtu3_pwm->clk); + if (ret) + return dev_err_probe(dev, ret, "Clock enable failed\n"); + + clk_rate_exclusive_get(rz_mtu3_pwm->clk); + + rz_mtu3_pwm->rate = clk_get_rate(rz_mtu3_pwm->clk); + /* + * Refuse clk rates > 1 GHz to prevent overflow later for computing + * period and duty cycle. + */ + if (rz_mtu3_pwm->rate > NSEC_PER_SEC) { + ret = -EINVAL; + clk_rate_exclusive_put(rz_mtu3_pwm->clk); + goto disable_clock; + } + + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + rz_mtu3_pwm->chip.dev = &pdev->dev; + ret = devm_add_action_or_reset(&pdev->dev, rz_mtu3_pwm_pm_disable, + rz_mtu3_pwm); + if (ret < 0) + return ret; + + rz_mtu3_pwm->chip.ops = &rz_mtu3_pwm_ops; + rz_mtu3_pwm->chip.npwm = RZ_MTU3_MAX_PWM_CHANNELS; + ret = devm_pwmchip_add(&pdev->dev, &rz_mtu3_pwm->chip); + if (ret) + return dev_err_probe(&pdev->dev, ret, "failed to add PWM chip\n"); + + pm_runtime_idle(&pdev->dev); + + return 0; + +disable_clock: + clk_disable_unprepare(rz_mtu3_pwm->clk); + return ret; +} + +static struct platform_driver rz_mtu3_pwm_driver = { + .driver = { + .name = "pwm-rz-mtu3", + .pm = pm_ptr(&rz_mtu3_pwm_pm_ops), + }, + .probe = rz_mtu3_pwm_probe, +}; +module_platform_driver(rz_mtu3_pwm_driver); + +MODULE_AUTHOR("Biju Das "); +MODULE_ALIAS("platform:pwm-rz-mtu3"); +MODULE_DESCRIPTION("Renesas RZ/G2L MTU3a PWM Timer Driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3