From: Stefan Wahren <wahrenst@gmx.net>
To: "Uwe Kleine-König" <u.kleine-koenig@pengutronix.de>
Cc: Rob Herring <robh+dt@kernel.org>,
Krzysztof Kozlowski <krzysztof.kozlowski+dt@linaro.org>,
Conor Dooley <conor+dt@kernel.org>,
andy.shevchenko@gmail.com,
Angelo Compagnucci <angelo.compagnucci@gmail.com>,
Philip Howard <phil@gadgetoid.com>, Sean Young <sean@mess.org>,
Linus Walleij <linus.walleij@linaro.org>,
linux-pwm@vger.kernel.org, devicetree@vger.kernel.org,
linux-gpio@vger.kernel.org,
Vincent Whitchurch <vincent.whitchurch@axis.com>
Subject: Re: [PATCH V4 2/2] pwm: Add GPIO PWM driver
Date: Mon, 5 Feb 2024 13:21:36 +0100 [thread overview]
Message-ID: <06e166f0-b6ac-4ffd-b194-a2ba0031605f@gmx.net> (raw)
In-Reply-To: <b3bh4srxjc5s5yrceugn2bry4j7srvuyyc2zc7uorynn3esbbq@xtpfu3xnsi3q>
Hi Uwe,
Am 05.02.24 um 11:09 schrieb Uwe Kleine-König:
> Hello,
>
> On Sun, Feb 04, 2024 at 11:08:51PM +0100, Stefan Wahren wrote:
>> +static enum hrtimer_restart pwm_gpio_timer(struct hrtimer *gpio_timer)
>> +{
>> + struct pwm_gpio *gpwm = container_of(gpio_timer, struct pwm_gpio,
>> + gpio_timer);
>> + unsigned long next_toggle;
>> + unsigned long flags;
>> + bool new_level;
>> +
>> + spin_lock_irqsave(&gpwm->lock, flags);
>> +
>> + /* Apply new state at end of current period */
>> + if (!gpwm->level && gpwm->changing) {
>> + gpwm->changing = false;
>> + gpwm->state = gpwm->next_state;
>> + new_level = !!gpwm->state.duty_cycle;
>> + } else {
>> + new_level = !gpwm->level;
>> + }
>> +
>> + next_toggle = pwm_gpio_toggle(gpwm, new_level);
>> + if (next_toggle) {
>> + hrtimer_forward(gpio_timer, hrtimer_get_expires(gpio_timer),
>> + ns_to_ktime(next_toggle));
> How does this work in relation to hrtimer_resolution? If the resolution
> is (say) 300 and next_toggle is 2000. Does the trigger trigger at
> hrtimer_get_expires(...) + 1800, or at ... + 2100?
>
> If you assume we have period = 6000 and duty_cycle = 2000, the delta to
> forward the driver alternates between 1800 and 3900 (if rounding down)
> or between 2100 and 4200 if rounding up. Both is not optimal.
>
> Ideally you'd round down the active phase (i.e. pick 1800) and for the
> inactive phase you'd use rounddown(period) - rounddown(duty_cycle) which
> gives 4200 here. Does this make sense?
oh dear, looks like a can of worms. I will look into it. Btw according
to multi_v7_defconfig the hrtimer_resolution on the Pi is 1 ns.
Does it make sense to log the hrtimer_resolution e.g. at probe?
>
>> + }
>> +
>> + spin_unlock_irqrestore(&gpwm->lock, flags);
>> +
>> + return next_toggle ? HRTIMER_RESTART : HRTIMER_NORESTART;
>> +}
>> +
>> +static int pwm_gpio_apply(struct pwm_chip *chip, struct pwm_device *pwm,
>> + const struct pwm_state *state)
>> +{
>> + struct pwm_gpio *gpwm = container_of(chip, struct pwm_gpio, chip);
>> + bool invert = state->polarity == PWM_POLARITY_INVERSED;
>> + unsigned long flags;
>> +
>> + if (state->duty_cycle && state->duty_cycle < hrtimer_resolution)
>> + return -EINVAL;
>> +
>> + if (state->duty_cycle != state->period &&
>> + (state->period - state->duty_cycle < hrtimer_resolution))
>> + return -EINVAL;
> If you assume that hrtimer_resolution = 300 again, you don't want to
> refuse
>
> .duty_cycle = 200
> .period = 200
>
> do you?
Actually i had rejected it. Yes, this specific corner case does work
with such a resolution. But if the user want a steady level, the user
would use a plain GPIO not a PWM. As soon the duty cycle get lower this
would be rejected and as a user i would be confused.
Another issue here, is that we don't have a good interface to tell the
limitations.
> I think you want:
>
> mininterval = min(state->duty_cycle, state->period - state->duty_cycle);
> if (mininterval && mininterval < hrtimer_resolution)
> return -EINVAL;
>
> to catch both cases in a single check.
>
>> + if (!state->enabled) {
>> + hrtimer_cancel(&gpwm->gpio_timer);
>> + } else if (!gpwm->running) {
>> + /*
>> + * This just enables the output, but pwm_gpio_toggle()
>> + * really starts the duty cycle.
>> + */
>> + int ret = gpiod_direction_output(gpwm->gpio, invert);
>> +
>> + if (ret)
>> + return ret;
>> + }
>> +
>> + spin_lock_irqsave(&gpwm->lock, flags);
>> +
>> + if (!state->enabled) {
>> + gpwm->state = *state;
>> + gpwm->running = false;
>> + gpwm->changing = false;
>> +
>> + gpiod_set_value(gpwm->gpio, invert);
>> + } else if (gpwm->running) {
>> + gpwm->next_state = *state;
>> + gpwm->changing = true;
>> + } else {
>> + unsigned long next_toggle;
>> +
>> + gpwm->state = *state;
>> + gpwm->changing = false;
>> +
>> + next_toggle = pwm_gpio_toggle(gpwm, !!state->duty_cycle);
>> + if (next_toggle) {
>> + hrtimer_start(&gpwm->gpio_timer, next_toggle,
>> + HRTIMER_MODE_REL);
>> + }
> The curly braces can be dropped here and in a few more locations.
>
>> + }
>> +
>> + spin_unlock_irqrestore(&gpwm->lock, flags);
>> +
>> + return 0;
>> +}
>> +
>> +static int pwm_gpio_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
>> + struct pwm_state *state)
>> +{
>> + struct pwm_gpio *gpwm = container_of(chip, struct pwm_gpio, chip);
>> + unsigned long flags;
>> +
>> + spin_lock_irqsave(&gpwm->lock, flags);
>> +
>> + if (gpwm->changing)
>> + *state = gpwm->next_state;
>> + else
>> + *state = gpwm->state;
>> +
>> + spin_unlock_irqrestore(&gpwm->lock, flags);
>> +
>> + return 0;
>> +}
>> +
>> +static const struct pwm_ops pwm_gpio_ops = {
>> + .apply = pwm_gpio_apply,
>> + .get_state = pwm_gpio_get_state,
>> +};
>> +
>> +static int pwm_gpio_probe(struct platform_device *pdev)
>> +{
>> + struct device *dev = &pdev->dev;
>> + struct pwm_gpio *gpwm;
>> + int ret;
>> +
>> + gpwm = devm_kzalloc(dev, sizeof(*gpwm), GFP_KERNEL);
>> + if (!gpwm)
>> + return -ENOMEM;
>> +
>> + spin_lock_init(&gpwm->lock);
>> +
>> + gpwm->gpio = devm_gpiod_get(dev, NULL, GPIOD_ASIS);
>> + if (IS_ERR(gpwm->gpio)) {
>> + return dev_err_probe(dev, PTR_ERR(gpwm->gpio),
>> + "could not get gpio\n");
>> + }
>> +
>> + if (gpiod_cansleep(gpwm->gpio)) {
>> + return dev_err_probe(dev, -EINVAL,
>> + "sleeping GPIO %d not supported\n",
>> + desc_to_gpio(gpwm->gpio));
>> + }
> Is it still state of the art to add gpio numbers to error messages?
I was unsure how to handle this user-friendly. Just simply logging
"sleeping GPIO not supported" doesn't provide a reference on the
affected GPIO. GPIO names are optional, so maybe empty.
I'm open to suggestions :-)
Best regards
>
>> + gpwm->chip.dev = dev;
>> + gpwm->chip.ops = &pwm_gpio_ops;
>> + gpwm->chip.npwm = 1;
>> + gpwm->chip.atomic = true;
>> +
>> + hrtimer_init(&gpwm->gpio_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
>> + gpwm->gpio_timer.function = pwm_gpio_timer;
>> +
>> + ret = pwmchip_add(&gpwm->chip);
>> + if (ret < 0)
>> + return dev_err_probe(dev, ret, "could not add pwmchip\n");
>> +
>> + platform_set_drvdata(pdev, gpwm);
>> +
>> + return 0;
>> +}
> Best regards
> Uwe
>
next prev parent reply other threads:[~2024-02-05 12:21 UTC|newest]
Thread overview: 28+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-02-04 22:08 [PATCH V4 0/2] pwm: Add GPIO PWM driver Stefan Wahren
2024-02-04 22:08 ` [PATCH V4 1/2] dt-bindings: pwm: Add pwm-gpio Stefan Wahren
2024-02-04 22:47 ` Linus Walleij
2024-02-05 5:58 ` Dhruva Gole
2024-02-05 7:44 ` Krzysztof Kozlowski
2024-02-05 9:15 ` Uwe Kleine-König
2024-02-05 11:58 ` Stefan Wahren
2024-02-04 22:08 ` [PATCH V4 2/2] pwm: Add GPIO PWM driver Stefan Wahren
2024-02-04 22:50 ` Linus Walleij
2024-02-05 6:11 ` Dhruva Gole
2024-02-05 7:16 ` Stefan Wahren
2024-02-05 10:09 ` Uwe Kleine-König
2024-02-05 12:21 ` Stefan Wahren [this message]
2024-02-27 15:32 ` Chris Morgan
2024-02-27 16:59 ` Stefan Wahren
2024-02-28 12:43 ` Phil Howard
2024-02-29 8:45 ` Sean Young
2024-02-29 8:57 ` Sean Young
2024-02-27 16:41 ` Andy Shevchenko
2024-02-27 20:24 ` Stefan Wahren
2024-02-27 20:47 ` Andy Shevchenko
2024-02-28 18:06 ` Stefan Wahren
2024-02-05 5:55 ` [PATCH V4 0/2] " Dhruva Gole
2024-02-05 7:08 ` Stefan Wahren
2024-02-05 13:06 ` Phil Howard
2024-05-27 8:22 ` Linus Walleij
2024-05-27 8:44 ` Stefan Wahren
2024-05-27 9:56 ` Linus Walleij
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=06e166f0-b6ac-4ffd-b194-a2ba0031605f@gmx.net \
--to=wahrenst@gmx.net \
--cc=andy.shevchenko@gmail.com \
--cc=angelo.compagnucci@gmail.com \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=krzysztof.kozlowski+dt@linaro.org \
--cc=linus.walleij@linaro.org \
--cc=linux-gpio@vger.kernel.org \
--cc=linux-pwm@vger.kernel.org \
--cc=phil@gadgetoid.com \
--cc=robh+dt@kernel.org \
--cc=sean@mess.org \
--cc=u.kleine-koenig@pengutronix.de \
--cc=vincent.whitchurch@axis.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).