On Mon, May 27, 2024 at 2:29 PM Stefan Wahren <wahrenst@xxxxxxx> wrote: > > From: Vincent Whitchurch <vincent.whitchurch@xxxxxxxx> > > Add a software PWM which toggles a GPIO from a high-resolution timer. > > This will naturally not be as accurate or as efficient as a hardware > PWM, but it is useful in some cases. I have for example used it for > evaluating LED brightness handling (via leds-pwm) on a board where the > LED was just hooked up to a GPIO, and for a simple verification of the > timer frequency on another platform. > > Since high-resolution timers are used, sleeping GPIO chips are not > supported and are rejected in the probe function. ... > +#include <linux/cleanup.h> > +#include <linux/container_of.h> > +#include <linux/device.h> > +#include <linux/err.h> > +#include <linux/gpio/consumer.h> > +#include <linux/hrtimer.h> + math.h > +#include <linux/module.h> > +#include <linux/mod_devicetable.h> > +#include <linux/platform_device.h> + property.h > +#include <linux/pwm.h> > +#include <linux/spinlock.h> > +#include <linux/time.h> > +#include <linux/types.h> ... > +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 flags; > + u64 next_toggle; > + bool new_level; > + spin_lock_irqsave(&gpwm->lock, flags); You added cleanup.h, why not scoped_guard() here? > + /* 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); > + Unneeded blank line. > + if (next_toggle) > + hrtimer_forward(gpio_timer, hrtimer_get_expires(gpio_timer), > + ns_to_ktime(next_toggle)); > + > + spin_unlock_irqrestore(&gpwm->lock, flags); > + > + return next_toggle ? HRTIMER_RESTART : HRTIMER_NORESTART; > +} ... > + spin_lock_irqsave(&gpwm->lock, flags); guard() > + if (!state->enabled) { > + pwm_gpio_round(&gpwm->state, state); > + gpwm->running = false; > + gpwm->changing = false; > + > + gpiod_set_value(gpwm->gpio, invert); > + } else if (gpwm->running) { > + pwm_gpio_round(&gpwm->next_state, state); > + gpwm->changing = true; > + } else { > + unsigned long next_toggle; > + > + pwm_gpio_round(&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); > + } > + > + 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 = pwmchip_get_drvdata(chip); > + unsigned long flags; > + > + spin_lock_irqsave(&gpwm->lock, flags); Ditto. > + if (gpwm->changing) > + *state = gpwm->next_state; > + else > + *state = gpwm->state; > + > + spin_unlock_irqrestore(&gpwm->lock, flags); > + > + return 0; > +} ... > +static int pwm_gpio_probe(struct platform_device *pdev) > +{ > + struct pwm_chip *chip; > + struct device *dev = &pdev->dev; > + struct pwm_gpio *gpwm; > + int ret; > + > + chip = devm_pwmchip_alloc(&pdev->dev, 1, sizeof(*gpwm)); You have dev, use it. > + if (IS_ERR(chip)) > + return PTR_ERR(chip); > + > + gpwm = pwmchip_get_drvdata(chip); > + > + 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), > + "%pfw: could not get gpio\n", > + dev_fwnode(dev)); > + > + if (gpiod_cansleep(gpwm->gpio)) > + return dev_err_probe(dev, -EINVAL, > + "%pfw: sleeping GPIO not supported\n", > + dev_fwnode(dev)); > + > + chip->ops = &pwm_gpio_ops; > + chip->atomic = true; > + > + hrtimer_init(&gpwm->gpio_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); > + gpwm->gpio_timer.function = pwm_gpio_timer; > + > + ret = pwmchip_add(chip); > + if (ret < 0) > + return dev_err_probe(dev, ret, "could not add pwmchip\n"); > + > + platform_set_drvdata(pdev, gpwm); > + dev_info(dev, "pwm-gpio probed, hr timer resolution: %u ns\n", hrtimer_resolution); Is this important info? Can it be retrieved differently (via sysfs or procfs or so)? > + return 0; > +} > + > +static void pwm_gpio_remove(struct platform_device *pdev) > +{ > + struct pwm_gpio *gpwm = platform_get_drvdata(pdev); > + > + hrtimer_cancel(&gpwm->gpio_timer); This is a bit worrying. The probe sequence is to init timer followed by adding PWM, here seems the broken order. Shouldn't you need to wrapt hrtimer_init() into devm_add_action_or_reset()? > +} -- With Best Regards, Andy Shevchenko