Re: [PATCH v7 2/4] pwm: Make it possible to apply PWM changes in atomic context

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Mon, Dec 11, 2023 at 08:24:53AM +0000, Sean Young wrote:
> Some PWM devices require sleeping, for example if the pwm device is
> connected over I2C. However, many PWM devices could be used from atomic
> context, e.g. memory mapped PWM. This is useful for, for example, the
> pwm-ir-tx driver which requires precise timing. Sleeping causes havoc
> with the generated IR signal.
> 
> Since not all PWM devices can support atomic context, we also add a
> pwm_might_sleep() function to check if is not supported.
> 
> Signed-off-by: Sean Young <sean@xxxxxxxx>
> ---
>  Documentation/driver-api/pwm.rst |  9 +++++
>  MAINTAINERS                      |  2 +-
>  drivers/pwm/core.c               | 64 ++++++++++++++++++++++++++------
>  drivers/pwm/pwm-renesas-tpu.c    |  1 -
>  include/linux/pwm.h              | 29 ++++++++++++++-
>  5 files changed, 89 insertions(+), 16 deletions(-)
> 
> diff --git a/Documentation/driver-api/pwm.rst b/Documentation/driver-api/pwm.rst
> index f1d8197c8c430..3c28ccc4b6113 100644
> --- a/Documentation/driver-api/pwm.rst
> +++ b/Documentation/driver-api/pwm.rst
> @@ -46,6 +46,15 @@ After being requested, a PWM has to be configured using::
>  This API controls both the PWM period/duty_cycle config and the
>  enable/disable state.
>  
> +PWM devices can be used from atomic context, if the PWM does not sleep. You
> +can check if this the case with::
> +
> +        bool pwm_might_sleep(struct pwm_device *pwm);
> +
> +If false, the PWM can also be configured from atomic context with::
> +
> +	int pwm_apply_atomic(struct pwm_device *pwm, struct pwm_state *state);
> +
>  As a consumer, don't rely on the output's state for a disabled PWM. If it's
>  easily possible, drivers are supposed to emit the inactive state, but some
>  drivers cannot. If you rely on getting the inactive state, use .duty_cycle=0,
> diff --git a/MAINTAINERS b/MAINTAINERS
> index c2a9e0b5594e7..b55ac220b923d 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -17585,7 +17585,7 @@ F:	drivers/video/backlight/pwm_bl.c
>  F:	include/dt-bindings/pwm/
>  F:	include/linux/pwm.h
>  F:	include/linux/pwm_backlight.h
> -K:	pwm_(config|apply_might_sleep|ops)
> +K:	pwm_(config|apply_might_sleep|apply_atomic|ops)
>  
>  PXA GPIO DRIVER
>  M:	Robert Jarzmik <robert.jarzmik@xxxxxxx>
> diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c
> index c2d78136625d5..5fd35cda5786b 100644
> --- a/drivers/pwm/core.c
> +++ b/drivers/pwm/core.c
> @@ -433,24 +433,15 @@ static void pwm_apply_debug(struct pwm_device *pwm,
>  }
>  
>  /**
> - * pwm_apply_might_sleep() - atomically apply a new state to a PWM device
> + * pwm_apply_unchecked() - atomically apply a new state to a PWM device
>   * @pwm: PWM device
>   * @state: new state to apply
>   */
> -int pwm_apply_might_sleep(struct pwm_device *pwm, const struct pwm_state *state)
> +static int pwm_apply_unchecked(struct pwm_device *pwm, const struct pwm_state *state)
>  {
>  	struct pwm_chip *chip;
>  	int err;
>  
> -	/*
> -	 * Some lowlevel driver's implementations of .apply() make use of
> -	 * mutexes, also with some drivers only returning when the new
> -	 * configuration is active calling pwm_apply_might_sleep() from atomic context
> -	 * is a bad idea. So make it explicit that calling this function might
> -	 * sleep.
> -	 */
> -	might_sleep();
> -
>  	if (!pwm || !state || !state->period ||
>  	    state->duty_cycle > state->period)
>  		return -EINVAL;
> @@ -471,16 +462,65 @@ int pwm_apply_might_sleep(struct pwm_device *pwm, const struct pwm_state *state)
>  
>  	pwm->state = *state;
>  
> +	return 0;
> +}
> +
> +/**
> + * pwm_apply_might_sleep() - atomically apply a new state to a PWM device
> + * Cannot be used in atomic context.
> + * @pwm: PWM device
> + * @state: new state to apply
> + */
> +int pwm_apply_might_sleep(struct pwm_device *pwm, const struct pwm_state *state)
> +{
> +	int err;
> +
> +	/*
> +	 * Some lowlevel driver's implementations of .apply() make use of
> +	 * mutexes, also with some drivers only returning when the new
> +	 * configuration is active calling pwm_apply_might_sleep() from atomic context
> +	 * is a bad idea. So make it explicit that calling this function might
> +	 * sleep.
> +	 */
> +	might_sleep();
> +
> +	if (IS_ENABLED(CONFIG_PWM_DEBUG) && pwm->chip->atomic) {
> +		/*
> +		 * Catch any drivers that have been marked as atomic but
> +		 * that will sleep anyway.
> +		 */
> +		non_block_start();
> +		err = pwm_apply_unchecked(pwm, state);
> +		non_block_end();
> +	} else {
> +		err = pwm_apply_unchecked(pwm, state);
> +	}
> +
>  	/*
>  	 * only do this after pwm->state was applied as some
>  	 * implementations of .get_state depend on this
>  	 */
>  	pwm_apply_debug(pwm, state);

I think this is broken. Currently pwm_apply_state_debug() is only called
if chip->ops->apply() succeeds.

> -	return 0;
> +	return err;
>  }
>  EXPORT_SYMBOL_GPL(pwm_apply_might_sleep);
>  
> +/**
> + * pwm_apply_atomic() - apply a new state to a PWM device from atomic context
> + * Not all PWM devices support this function, check with pwm_might_sleep().
> + * @pwm: PWM device
> + * @state: new state to apply
> + */
> +int pwm_apply_atomic(struct pwm_device *pwm, const struct pwm_state *state)
> +{
> +	WARN_ONCE(!pwm->chip->atomic,
> +		  "sleeping PWM driver used in atomic context");
> +
> +	return pwm_apply_unchecked(pwm, state);
> +}
> +EXPORT_SYMBOL_GPL(pwm_apply_atomic);
> +
>  /**
>   * pwm_capture() - capture and report a PWM signal
>   * @pwm: PWM device
> diff --git a/drivers/pwm/pwm-renesas-tpu.c b/drivers/pwm/pwm-renesas-tpu.c
> index ce92db1f85113..28265fdfc92a9 100644
> --- a/drivers/pwm/pwm-renesas-tpu.c
> +++ b/drivers/pwm/pwm-renesas-tpu.c
> @@ -11,7 +11,6 @@
>  #include <linux/init.h>
>  #include <linux/ioport.h>
>  #include <linux/module.h>
> -#include <linux/mutex.h>
>  #include <linux/of.h>
>  #include <linux/platform_device.h>
>  #include <linux/pm_runtime.h>

Unrelated change

> diff --git a/include/linux/pwm.h b/include/linux/pwm.h
> index b64b8a82415c4..495af3627939c 100644
> --- a/include/linux/pwm.h
> +++ b/include/linux/pwm.h
> @@ -285,6 +285,7 @@ struct pwm_ops {
>   * @npwm: number of PWMs controlled by this chip
>   * @of_xlate: request a PWM device given a device tree PWM specifier
>   * @of_pwm_n_cells: number of cells expected in the device tree PWM specifier
> + * @atomic: can the driver's ->apply() be called in atomic context
>   * @pwms: array of PWM devices allocated by the framework
>   */
>  struct pwm_chip {
> @@ -297,6 +298,7 @@ struct pwm_chip {
>  	struct pwm_device * (*of_xlate)(struct pwm_chip *chip,
>  					const struct of_phandle_args *args);
>  	unsigned int of_pwm_n_cells;
> +	bool atomic;
>  
>  	/* only used internally by the PWM framework */
>  	struct pwm_device *pwms;
> @@ -305,6 +307,7 @@ struct pwm_chip {
>  #if IS_ENABLED(CONFIG_PWM)
>  /* PWM user APIs */
>  int pwm_apply_might_sleep(struct pwm_device *pwm, const struct pwm_state *state);
> +int pwm_apply_atomic(struct pwm_device *pwm, const struct pwm_state *state);
>  int pwm_adjust_config(struct pwm_device *pwm);
>  
>  /**
> @@ -375,6 +378,17 @@ static inline void pwm_disable(struct pwm_device *pwm)
>  	pwm_apply_might_sleep(pwm, &state);
>  }
>  
> +/**
> + * pwm_might_sleep() - is pwm_apply_atomic() supported?
> + * @pwm: PWM device
> + *
> + * Returns: false if pwm_apply_atomic() can be called from atomic context.
> + */
> +static inline bool pwm_might_sleep(struct pwm_device *pwm)
> +{
> +	return !pwm->chip->atomic;
> +}
> +
>  /* PWM provider APIs */
>  int pwm_capture(struct pwm_device *pwm, struct pwm_capture *result,
>  		unsigned long timeout);
> @@ -403,16 +417,27 @@ struct pwm_device *devm_fwnode_pwm_get(struct device *dev,
>  				       struct fwnode_handle *fwnode,
>  				       const char *con_id);
>  #else
> +static inline bool pwm_might_sleep(struct pwm_device *pwm)
> +{
> +	return true;
> +}
> +
>  static inline int pwm_apply_might_sleep(struct pwm_device *pwm,
>  					const struct pwm_state *state)
>  {
>  	might_sleep();
> -	return -ENOTSUPP;
> +	return -EOPNOTSUPP;
> +}
> +
> +static inline int pwm_apply_atomic(struct pwm_device *pwm,
> +				   const struct pwm_state *state)
> +{
> +	return -EOPNOTSUPP;
>  }
>  
>  static inline int pwm_adjust_config(struct pwm_device *pwm)
>  {
> -	return -ENOTSUPP;
> +	return -EOPNOTSUPP;

I'd do s/-ENOTSUPP/-EOPNOTSUPP/ in a separate change.

Best regards
Uwe

-- 
Pengutronix e.K.                           | Uwe Kleine-König            |
Industrial Linux Solutions                 | https://www.pengutronix.de/ |

Attachment: signature.asc
Description: PGP signature


[Index of Archives]     [Linux Input]     [Video for Linux]     [Gstreamer Embedded]     [Mplayer Users]     [Linux USB Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]

  Powered by Linux