linux-gpio.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
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
>


  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).