lkml.org 
[lkml]   [2022]   [Aug]   [6]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
Patch in this message
/
Date
From
SubjectRe: [PATCH 6/8] pwm: dwc: add timer clock
Hello Ben,

On Fri, Aug 05, 2022 at 05:50:31PM +0100, Ben Dooks wrote:
> Add a configurable clock base rate for the pwm as when being built
> for non-PCI the block may be sourced from an internal clock.
>
> Signed-off-by: Ben Dooks <ben.dooks@sifive.com>
> ---
> v2:
> - removed the ifdef and merged the other clock patch in here
> ---
> drivers/pwm/pwm-dwc.c | 22 +++++++++++++++++-----
> 1 file changed, 17 insertions(+), 5 deletions(-)
>
> diff --git a/drivers/pwm/pwm-dwc.c b/drivers/pwm/pwm-dwc.c
> index d5f2df6fee62..5c319d0e3d52 100644
> --- a/drivers/pwm/pwm-dwc.c
> +++ b/drivers/pwm/pwm-dwc.c
> @@ -18,6 +18,7 @@
> #include <linux/kernel.h>
> #include <linux/module.h>
> #include <linux/pci.h>
> +#include <linux/clk.h>
> #include <linux/platform_device.h>
> #include <linux/pm_runtime.h>
> #include <linux/pwm.h>
> @@ -35,7 +36,6 @@
> #define DWC_TIMERS_COMP_VERSION 0xac
>
> #define DWC_TIMERS_TOTAL 8
> -#define DWC_CLK_PERIOD_NS 10
>
> /* Timer Control Register */
> #define DWC_TIM_CTRL_EN BIT(0)
> @@ -54,6 +54,8 @@ struct dwc_pwm_ctx {
> struct dwc_pwm {
> struct pwm_chip chip;
> void __iomem *base;
> + struct clk *clk;
> + unsigned int clk_ns;
> struct dwc_pwm_ctx ctx[DWC_TIMERS_TOTAL];
> };
> #define to_dwc_pwm(p) (container_of((p), struct dwc_pwm, chip))
> @@ -96,13 +98,13 @@ static int __dwc_pwm_configure_timer(struct dwc_pwm *dwc,
> * periods and check are the result within HW limits between 1 and
> * 2^32 periods.
> */
> - tmp = DIV_ROUND_CLOSEST_ULL(state->duty_cycle, DWC_CLK_PERIOD_NS);
> + tmp = DIV_ROUND_CLOSEST_ULL(state->duty_cycle, dwc->clk_ns);
> if (tmp < 1 || tmp > (1ULL << 32))
> return -ERANGE;
> low = tmp - 1;
>
> tmp = DIV_ROUND_CLOSEST_ULL(state->period - state->duty_cycle,
> - DWC_CLK_PERIOD_NS);
> + dwc->clk_ns);

You're loosing precision here as clk_ns is already the result of a
division. We're having

dwc->clk_ns = 1000000000 / clk_get_rate(dwc->clk);

from dwc_pwm_plat_probe() (in the platform case).

Consider clk_rate = 285714285 and state->period - state->duty_cycle =
300000. Then you get tmp = 100000 while the exact result would be:

300000 * 285714285 / 1000000000 = 85714.2855

Note that even doing

dwc->clk_ns = DIV_ROUND_CLOSEST(1000000000, clk_get_rate(dwc->clk))

only somewhat weakens the problem, with the above numbers you then get
75000.

Also note that rounding closest is also wrong in the calculation of tmp
because the driver is supposed to implement the biggest period not
bigger than the requested period and for that period implement the
biggest duty cycle not bigger than the requested duty cycle.

Can the hardware emit 0% relative duty cycle (e.g. by disabling)?

> if (tmp < 1 || tmp > (1ULL << 32))
> return -ERANGE;
> high = tmp - 1;
> @@ -177,12 +179,12 @@ static void dwc_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
>
> duty = dwc_pwm_readl(dwc, DWC_TIM_LD_CNT(pwm->hwpwm));
> duty += 1;
> - duty *= DWC_CLK_PERIOD_NS;
> + duty *= dwc->clk_ns;
> state->duty_cycle = duty;
>
> period = dwc_pwm_readl(dwc, DWC_TIM_LD_CNT2(pwm->hwpwm));
> period += 1;
> - period *= DWC_CLK_PERIOD_NS;
> + period *= dwc->clk_ns;
> period += duty;
> state->period = period;
>
> @@ -205,6 +207,7 @@ static struct dwc_pwm *dwc_pwm_alloc(struct device *dev)
> if (!dwc)
> return NULL;
>
> + dwc->clk_ns = 10;
> dwc->chip.dev = dev;
> dwc->chip.ops = &dwc_pwm_ops;
> dwc->chip.npwm = DWC_TIMERS_TOTAL;
> @@ -336,6 +339,14 @@ static int dwc_pwm_plat_probe(struct platform_device *pdev)
> return dev_err_probe(dev, PTR_ERR(dwc->base),
> "failed to map IO\n");
>
> + dwc->clk = devm_clk_get(dev, "timer");
> + if (IS_ERR(dwc->clk))
> + return dev_err_probe(dev, PTR_ERR(dwc->clk),
> + "failed to get timer clock\n");
> +
> + clk_prepare_enable(dwc->clk);

If you used devm_clk_get_enabled() you wouldn't need to care separately
for enabling. (If you stick to separate calls, please add error checking
for clk_prepare_enable().)

> + dwc->clk_ns = 1000000000 / clk_get_rate(dwc->clk);

s/1000000000/NSEC_PER_SEC/

> +
> ret = pwmchip_add(&dwc->chip);
> if (ret)
> return ret;
> @@ -347,6 +358,7 @@ static int dwc_pwm_plat_remove(struct platform_device *pdev)
> {
> struct dwc_pwm *dwc = platform_get_drvdata(pdev);
>
> + clk_disable_unprepare(dwc->clk);
> pwmchip_remove(&dwc->chip);

This is wrong, you must not disable the clock before calling
pwmchip_remove() as the PWM is supposed to stay functional until
pwmchip_remove() returns.

> return 0;
> }

--
Pengutronix e.K. | Uwe Kleine-König |
Industrial Linux Solutions | https://www.pengutronix.de/ |
[unhandled content-type:application/pgp-signature]
\
 
 \ /
  Last update: 2022-08-06 12:09    [W:0.169 / U:0.180 seconds]
©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site