pwm: Changes for v7.4-rc1

The usual mix of fixes for older issues, devicetree changes and
 cleanups.
 
 Part of this is a shared branch that introduces device-managed variants
 of atomic_notifier_chain_register() and
 blocking_notifier_chain_register().
 -----BEGIN PGP SIGNATURE-----
 
 iQEzBAABCgAdFiEEP4GsaTp6HlmJrf7Tj4D7WH0S/k4FAmrR2QoACgkQj4D7WH0S
 /k58WwgAlViH1YkP0//3Az5Y4RQv7t6EFQUquWQqD9VqaltRhiEEuapq4q6VY5p7
 V/N3tqOtqf7GWWqZq6y8V2fxunATa4xrlz2prQYBLvryIojI6RqF/1vHdCZU1DS/
 ovZQA38KHf0vrVa4/7VmM90143KJPqLXpfQ0PlwHaWR6XmVr+TmowgDaGQQCG7Wa
 IgvpfvSxxtsV09p6vd1atBAX6j+ZvaSEiOvK5lykJdp3mzrmf09QVDpW96qQjMcm
 xcXPndavnsAee2s3QVF4I0qGqu1xd2uCLtyJuNhE4bjq5TLxyQyEVAe0bgWr9eXD
 giHIO7RjI0FtA3Ngsh/k02TdlXYNRQ==
 =gMpT
 -----END PGP SIGNATURE-----

Merge tag 'pwm/for-7.4-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/ukleinek/linux

Pull pwm updates from Uwe Kleine-König:
 "The usual mix of fixes for older issues, devicetree changes and
  cleanups.

  Part of this is a shared branch that introduces device-managed
  variants of atomic_notifier_chain_register() and
  blocking_notifier_chain_register()"

* tag 'pwm/for-7.4-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/ukleinek/linux:
  dt-bindings: pwm: Add IPQ5210 binding
  pwm: tiehrpwm: Clear period on disable to allow reconfiguration
  pwm: Allow 32-bit tasks to use the character device
  pwm: lp3943: fix NULL pointer dereference for an unconfigured channel
  pwm: mtk-disp: Fix period readback
  pwm: ipq: Prevent potential 32-bit integer overflow in hi_div calculation
  pwm: loongson: Reload PWM configuration through counter reset
  pwm: Fix typos in comments
  dt-bindings: pwm: Convert TI PWM Subsystem to DT schema
  docs: pwm: Fix bracket and punctuation
  pwm: iqs620a: Use devm_blocking_notifier_chain_register()
  pwm: renesas-tpu: Fix runtime PM reference leak
  notifier: add device-managed registration APIs
This commit is contained in:
Linus Torvalds 2026-10-22 06:44:27 -10:00
commit b0e1eda687
16 changed files with 231 additions and 89 deletions

View File

@ -1,58 +0,0 @@
TI SOC based PWM Subsystem
Required properties:
- compatible: Must be "ti,<soc>-pwmss".
for am33xx - compatible = "ti,am33xx-pwmss";
for am4372 - compatible = "ti,am4372-pwmss","ti,am33xx-pwmss";
for dra746 - compatible = "ti,dra746-pwmss", "ti,am33xx-pwmss"
- reg: physical base address and size of the registers map.
- address-cells: Specify the number of u32 entries needed in child nodes.
Should set to 1.
- size-cells: specify number of u32 entries needed to specify child nodes size
in reg property. Should set to 1.
- ranges: describes the address mapping of a memory-mapped bus. Should set to
physical address map of child's base address, physical address within
parent's address space and length of the address map. For am33xx,
3 set of child register maps present, ECAP register space, EQEP
register space, EHRPWM register space.
Also child nodes should also populated under PWMSS DT node.
Example:
epwmss0: epwmss@48300000 { /* PWMSS for am33xx */
compatible = "ti,am33xx-pwmss";
reg = <0x48300000 0x10>;
ti,hwmods = "epwmss0";
#address-cells = <1>;
#size-cells = <1>;
ranges = <0x48300100 0x48300100 0x80 /* ECAP */
0x48300180 0x48300180 0x80 /* EQEP */
0x48300200 0x48300200 0x80>; /* EHRPWM */
/* child nodes go here */
};
epwmss0: epwmss@48300000 { /* PWMSS for am4372 */
compatible = "ti,am4372-pwmss","ti,am33xx-pwmss"
reg = <0x48300000 0x10>;
ti,hwmods = "epwmss0";
#address-cells = <1>;
#size-cells = <1>;
ranges = <0x48300100 0x48300100 0x80 /* ECAP */
0x48300180 0x48300180 0x80 /* EQEP */
0x48300200 0x48300200 0x80>; /* EHRPWM */
/* child nodes go here */
};
epwmss0: epwmss@4843e000 { /* PWMSS for DRA7xx */
compatible = "ti,dra746-pwmss", "ti,am33xx-pwmss";
reg = <0x4843e000 0x30>;
ti,hwmods = "epwmss0";
#address-cells = <1>;
#size-cells = <1>;
ranges;
/* child nodes go here */
};

View File

@ -15,6 +15,7 @@ properties:
- items:
- enum:
- qcom,ipq5018-pwm
- qcom,ipq5210-pwm
- qcom,ipq5332-pwm
- qcom,ipq9574-pwm
- const: qcom,ipq6018-pwm

View File

@ -0,0 +1,82 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/pwm/ti,am33xx-pwmss.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Texas Instruments SoC PWM Subsystem
maintainers:
- Eduard Bostina <egbostina@gmail.com>
properties:
compatible:
oneOf:
- const: ti,am33xx-pwmss
- items:
- enum:
- ti,am4372-pwmss
- ti,dra746-pwmss
- const: ti,am33xx-pwmss
reg:
maxItems: 1
"#address-cells":
const: 1
"#size-cells":
const: 1
ranges: true
patternProperties:
"^(ecap|eqep|pwm|counter)@[0-9a-f]+$":
type: object
description: Child nodes for ECAP, EQEP (counter), and EHRPWM
required:
- compatible
required:
- compatible
- reg
- "#address-cells"
- "#size-cells"
- ranges
additionalProperties: false
examples:
- |
pwm@48300000 {
compatible = "ti,am33xx-pwmss";
reg = <0x48300000 0x10>;
#address-cells = <1>;
#size-cells = <1>;
ranges = <0x48300100 0x48300100 0x80>, /* ECAP */
<0x48300180 0x48300180 0x80>, /* EQEP */
<0x48300200 0x48300200 0x80>; /* EHRPWM */
pwm@48300100 {
compatible = "ti,am3352-ecap";
#pwm-cells = <3>;
reg = <0x48300100 0x80>;
clocks = <&l4ls_gclk>;
clock-names = "fck";
};
counter@48300180 {
compatible = "ti,am3352-eqep";
reg = <0x48300180 0x80>;
clocks = <&l4ls_gclk>;
interrupts = <79>;
};
pwm@48300200 {
compatible = "ti,am3352-ehrpwm";
#pwm-cells = <3>;
reg = <0x48300200 0x80>;
clocks = <&ehrpwm0_tbclk>, <&l4ls_gclk>;
clock-names = "tbclk", "fck";
};
};

View File

@ -177,7 +177,7 @@ and pwm_put() may not be called from an atomic context.
Most functions in the PWM consumer API might sleep and so must not be called
from atomic context. The notable exception is pwm_apply_atomic() which has the
same semantics as pwm_apply_might_sleep() but can be called from atomic context.
(The price for that is that it doesn't work for all PWM devices, use
The price for that is that it doesn't work for all PWM devices. Use
pwm_might_sleep() to check if a given PWM supports atomic operation.
Locking in the PWM core ensures that callbacks related to a single chip are

View File

@ -2389,6 +2389,7 @@ static const struct file_operations pwm_cdev_fileops = {
.release = pwm_cdev_release,
.owner = THIS_MODULE,
.unlocked_ioctl = pwm_cdev_ioctl,
.compat_ioctl = compat_ptr_ioctl,
};
static dev_t pwm_devt;

View File

@ -101,7 +101,7 @@ static int brcmstb_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
u32 value;
/*
* If asking for a duty_ns equal to period_ns, we need to substract
* If asking for a duty_ns equal to period_ns, we need to subtract
* the period value by 1 to make it shorter than the "on" time and
* produce a flat 100% duty cycle signal, and max out the "on" time
*/

View File

@ -186,7 +186,7 @@ static int ipq_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
state->period = DIV64_U64_ROUND_UP(effective_div * NSEC_PER_SEC,
ipq_chip->clk_rate);
hi_div = hi_dur * (pre_div + 1);
hi_div = (u64)hi_dur * (pre_div + 1);
state->duty_cycle = DIV64_U64_ROUND_UP(hi_div * NSEC_PER_SEC,
ipq_chip->clk_rate);

View File

@ -173,18 +173,6 @@ static const struct pwm_ops iqs620_pwm_ops = {
.get_state = iqs620_pwm_get_state,
};
static void iqs620_pwm_notifier_unregister(void *context)
{
struct iqs620_pwm_private *iqs620_pwm = context;
int ret;
ret = blocking_notifier_chain_unregister(&iqs620_pwm->iqs62x->nh,
&iqs620_pwm->notifier);
if (ret)
dev_err(iqs620_pwm->dev,
"Failed to unregister notifier: %d\n", ret);
}
static int iqs620_pwm_probe(struct platform_device *pdev)
{
struct iqs62x_core *iqs62x = dev_get_drvdata(pdev->dev.parent);
@ -218,19 +206,14 @@ static int iqs620_pwm_probe(struct platform_device *pdev)
mutex_init(&iqs620_pwm->lock);
iqs620_pwm->notifier.notifier_call = iqs620_pwm_notifier;
ret = blocking_notifier_chain_register(&iqs620_pwm->iqs62x->nh,
&iqs620_pwm->notifier);
ret = devm_blocking_notifier_chain_register(&pdev->dev,
&iqs620_pwm->iqs62x->nh,
&iqs620_pwm->notifier);
if (ret) {
dev_err(&pdev->dev, "Failed to register notifier: %d\n", ret);
return ret;
}
ret = devm_add_action_or_reset(&pdev->dev,
iqs620_pwm_notifier_unregister,
iqs620_pwm);
if (ret)
return ret;
ret = devm_pwmchip_add(&pdev->dev, chip);
if (ret)
dev_err(&pdev->dev, "Failed to add device: %d\n", ret);

View File

@ -99,6 +99,7 @@ static void pwm_loongson_disable(struct pwm_chip *chip, struct pwm_device *pwm)
struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip);
val = pwm_loongson_readl(ddata, LOONGSON_PWM_REG_CTRL);
val |= LOONGSON_PWM_CTRL_REG_RST;
val &= ~LOONGSON_PWM_CTRL_REG_EN;
pwm_loongson_writel(ddata, val, LOONGSON_PWM_REG_CTRL);
}
@ -109,6 +110,7 @@ static int pwm_loongson_enable(struct pwm_chip *chip, struct pwm_device *pwm)
struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip);
val = pwm_loongson_readl(ddata, LOONGSON_PWM_REG_CTRL);
val &= ~LOONGSON_PWM_CTRL_REG_RST;
val |= LOONGSON_PWM_CTRL_REG_EN;
pwm_loongson_writel(ddata, val, LOONGSON_PWM_REG_CTRL);

View File

@ -38,6 +38,9 @@ lp3943_pwm_request_map(struct lp3943_pwm *lp3943_pwm, int hwpwm)
struct lp3943_pwm_map *pwm_map = &lp3943_pwm->pwm_map[hwpwm];
int i, offset;
if (!pdata->pwms[hwpwm])
return ERR_PTR(-ENODEV);
pwm_map->output = pdata->pwms[hwpwm]->output;
pwm_map->num_outputs = pdata->pwms[hwpwm]->num_outputs;

View File

@ -210,9 +210,9 @@ static int mtk_disp_pwm_get_state(struct pwm_chip *chip,
period = FIELD_GET(PWM_PERIOD_MASK, con1);
/*
* period has 12 bits, clk_div 11 and NSEC_PER_SEC has 30,
* so period * (clk_div + 1) * NSEC_PER_SEC doesn't overflow.
* so (period + 1) * (clk_div + 1) * NSEC_PER_SEC doesn't overflow.
*/
state->period = DIV64_U64_ROUND_UP(period * (clk_div + 1) * NSEC_PER_SEC, rate);
state->period = DIV64_U64_ROUND_UP((period + 1) * (clk_div + 1) * NSEC_PER_SEC, rate);
high_width = FIELD_GET(PWM_HIGH_WIDTH_MASK, con1);
state->duty_cycle = DIV64_U64_ROUND_UP(high_width * (clk_div + 1) * NSEC_PER_SEC,
rate);

View File

@ -54,7 +54,7 @@
/*
* The time value of one counter tick. Note that NSEC_PER_SEC is an integer
* multiple of PCA9685_OSC_CLOCK_HZ, so there is no rounding involved and we're
* not loosing precision due to the early division.
* not losing precision due to the early division.
*/
#define PCA9685_QUANTUM_NS(_prescale) ((NSEC_PER_SEC / PCA9685_OSC_CLOCK_HZ) * (_prescale + 1))

View File

@ -149,10 +149,14 @@ static int tpu_pwm_timer_start(struct tpu_pwm_device *tpd)
if (!tpd->timer_on) {
/* Wake up device and enable clock. */
pm_runtime_get_sync(&tpd->tpu->pdev->dev);
ret = pm_runtime_resume_and_get(&tpd->tpu->pdev->dev);
if (ret < 0)
return ret;
ret = clk_prepare_enable(tpd->tpu->clk);
if (ret) {
dev_err(&tpd->tpu->pdev->dev, "cannot enable clock\n");
pm_runtime_put(&tpd->tpu->pdev->dev);
return ret;
}
tpd->timer_on = true;
@ -382,15 +386,21 @@ static int tpu_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
return 0;
}
static void tpu_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
static int tpu_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct tpu_device *tpu = to_tpu_device(chip);
struct tpu_pwm_device *tpd = &tpu->tpd[pwm->hwpwm];
int ret;
/* The timer must be running to modify the pin output configuration. */
tpu_pwm_timer_start(tpd);
ret = tpu_pwm_timer_start(tpd);
if (ret < 0)
return ret;
tpu_pwm_set_pin(tpd, TPU_PIN_INACTIVE);
tpu_pwm_timer_stop(tpd);
return 0;
}
static int tpu_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
@ -401,7 +411,10 @@ static int tpu_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
if (state->polarity != pwm->state.polarity) {
if (enabled) {
tpu_pwm_disable(chip, pwm);
err = tpu_pwm_disable(chip, pwm);
if (err)
return err;
enabled = false;
}
@ -412,7 +425,7 @@ static int tpu_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
if (!state->enabled) {
if (enabled)
tpu_pwm_disable(chip, pwm);
return tpu_pwm_disable(chip, pwm);
return 0;
}

View File

@ -358,6 +358,9 @@ static void ehrpwm_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
/* Disable clock on PWM disable */
pm_runtime_put_sync(pwmchip_parent(chip));
/* A disabled channel must not constrain the sibling's period */
pc->period_cycles[pwm->hwpwm] = 0;
}
static void ehrpwm_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)

View File

@ -46,6 +46,7 @@
* often but notifier_blocks will seldom be removed.
*/
struct device;
struct notifier_block;
typedef int (*notifier_fn_t)(struct notifier_block *nb,
@ -145,8 +146,14 @@ extern void srcu_init_notifier_head(struct srcu_notifier_head *nh);
extern int atomic_notifier_chain_register(struct atomic_notifier_head *nh,
struct notifier_block *nb);
int devm_atomic_notifier_chain_register(struct device *dev,
struct atomic_notifier_head *nh,
struct notifier_block *nb);
extern int blocking_notifier_chain_register(struct blocking_notifier_head *nh,
struct notifier_block *nb);
int devm_blocking_notifier_chain_register(struct device *dev,
struct blocking_notifier_head *nh,
struct notifier_block *nb);
extern int raw_notifier_chain_register(struct raw_notifier_head *nh,
struct notifier_block *nb);
extern int srcu_notifier_chain_register(struct srcu_notifier_head *nh,

View File

@ -1,4 +1,5 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/device/devres.h>
#include <linux/kdebug.h>
#include <linux/kprobes.h>
#include <linux/export.h>
@ -197,6 +198,58 @@ int atomic_notifier_chain_unregister(struct atomic_notifier_head *nh,
}
EXPORT_SYMBOL_GPL(atomic_notifier_chain_unregister);
struct atomic_notifier_chain_devres {
struct atomic_notifier_head *nh;
struct notifier_block *nb;
};
static void devm_atomic_notifier_chain_unregister(struct device *dev, void *res)
{
struct atomic_notifier_chain_devres *dr = res;
atomic_notifier_chain_unregister(dr->nh, dr->nb);
}
/**
* devm_atomic_notifier_chain_register - Device-managed atomic notifier registration
* @dev: Device to tie the notifier lifetime to
* @nh: Pointer to head of the atomic notifier chain
* @nb: New entry in notifier chain
*
* Adds a notifier to an atomic notifier chain and registers a cleanup
* action to automatically unregister it when @dev is unbound.
*
* Must be called in process context.
*
* Return:
* 0 on success, negative errno on error.
*/
int devm_atomic_notifier_chain_register(struct device *dev,
struct atomic_notifier_head *nh,
struct notifier_block *nb)
{
struct atomic_notifier_chain_devres *dr;
int ret;
dr = devres_alloc(devm_atomic_notifier_chain_unregister,
sizeof(*dr), GFP_KERNEL);
if (!dr)
return -ENOMEM;
ret = atomic_notifier_chain_register(nh, nb);
if (ret) {
devres_free(dr);
return ret;
}
dr->nh = nh;
dr->nb = nb;
devres_add(dev, dr);
return 0;
}
EXPORT_SYMBOL_GPL(devm_atomic_notifier_chain_register);
/**
* atomic_notifier_call_chain - Call functions in an atomic notifier chain
* @nh: Pointer to head of the atomic notifier chain
@ -349,6 +402,58 @@ int blocking_notifier_call_chain_robust(struct blocking_notifier_head *nh,
}
EXPORT_SYMBOL_GPL(blocking_notifier_call_chain_robust);
struct blocking_notifier_chain_devres {
struct blocking_notifier_head *nh;
struct notifier_block *nb;
};
static void devm_blocking_notifier_chain_unregister(struct device *dev,
void *res)
{
struct blocking_notifier_chain_devres *dr = res;
blocking_notifier_chain_unregister(dr->nh, dr->nb);
}
/**
* devm_blocking_notifier_chain_register - Device-managed blocking notifier registration
* @dev: Device to tie the notifier lifetime to
* @nh: Pointer to head of the blocking notifier chain
* @nb: New entry in notifier chain
*
* Adds a notifier to a blocking notifier chain and registers a cleanup
* action to automatically unregister it when @dev is unbound.
* Must be called in process context.
*
* Return:
* 0 on success, negative errno on error.
*/
int devm_blocking_notifier_chain_register(struct device *dev,
struct blocking_notifier_head *nh,
struct notifier_block *nb)
{
struct blocking_notifier_chain_devres *dr;
int ret;
dr = devres_alloc(devm_blocking_notifier_chain_unregister,
sizeof(*dr), GFP_KERNEL);
if (!dr)
return -ENOMEM;
ret = blocking_notifier_chain_register(nh, nb);
if (ret) {
devres_free(dr);
return ret;
}
dr->nh = nh;
dr->nb = nb;
devres_add(dev, dr);
return 0;
}
EXPORT_SYMBOL_GPL(devm_blocking_notifier_chain_register);
/**
* blocking_notifier_call_chain - Call functions in a blocking notifier chain
* @nh: Pointer to head of the blocking notifier chain