diff options
| -rw-r--r-- | Documentation/devicetree/bindings/pwm/pwm-tipwmss.txt | 58 | ||||
| -rw-r--r-- | Documentation/devicetree/bindings/pwm/qcom,ipq6018-pwm.yaml | 1 | ||||
| -rw-r--r-- | Documentation/devicetree/bindings/pwm/ti,am33xx-pwmss.yaml | 82 | ||||
| -rw-r--r-- | Documentation/driver-api/pwm.rst | 2 | ||||
| -rw-r--r-- | drivers/pwm/core.c | 1 | ||||
| -rw-r--r-- | drivers/pwm/pwm-brcmstb.c | 2 | ||||
| -rw-r--r-- | drivers/pwm/pwm-ipq.c | 2 | ||||
| -rw-r--r-- | drivers/pwm/pwm-iqs620a.c | 23 | ||||
| -rw-r--r-- | drivers/pwm/pwm-loongson.c | 2 | ||||
| -rw-r--r-- | drivers/pwm/pwm-lp3943.c | 3 | ||||
| -rw-r--r-- | drivers/pwm/pwm-mtk-disp.c | 4 | ||||
| -rw-r--r-- | drivers/pwm/pwm-pca9685.c | 2 | ||||
| -rw-r--r-- | drivers/pwm/pwm-renesas-tpu.c | 23 | ||||
| -rw-r--r-- | drivers/pwm/pwm-tiehrpwm.c | 3 | ||||
| -rw-r--r-- | include/linux/notifier.h | 7 | ||||
| -rw-r--r-- | kernel/notifier.c | 105 |
16 files changed, 231 insertions, 89 deletions
diff --git a/Documentation/devicetree/bindings/pwm/pwm-tipwmss.txt b/Documentation/devicetree/bindings/pwm/pwm-tipwmss.txt deleted file mode 100644 index 4633697fbda1..000000000000 --- a/Documentation/devicetree/bindings/pwm/pwm-tipwmss.txt +++ /dev/null @@ -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 */ -}; diff --git a/Documentation/devicetree/bindings/pwm/qcom,ipq6018-pwm.yaml b/Documentation/devicetree/bindings/pwm/qcom,ipq6018-pwm.yaml index f9f1f652e752..c6b9004dd30e 100644 --- a/Documentation/devicetree/bindings/pwm/qcom,ipq6018-pwm.yaml +++ b/Documentation/devicetree/bindings/pwm/qcom,ipq6018-pwm.yaml @@ -15,6 +15,7 @@ properties: - items: - enum: - qcom,ipq5018-pwm + - qcom,ipq5210-pwm - qcom,ipq5332-pwm - qcom,ipq9574-pwm - const: qcom,ipq6018-pwm diff --git a/Documentation/devicetree/bindings/pwm/ti,am33xx-pwmss.yaml b/Documentation/devicetree/bindings/pwm/ti,am33xx-pwmss.yaml new file mode 100644 index 000000000000..89ce6b02b70c --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/ti,am33xx-pwmss.yaml @@ -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"; + }; + }; diff --git a/Documentation/driver-api/pwm.rst b/Documentation/driver-api/pwm.rst index 0d27a40f5818..b6b67f7d4905 100644 --- a/Documentation/driver-api/pwm.rst +++ b/Documentation/driver-api/pwm.rst @@ -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 diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c index 2a050ff4608b..4c6d716e5fde 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -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; diff --git a/drivers/pwm/pwm-brcmstb.c b/drivers/pwm/pwm-brcmstb.c index 790ef7ffbbe5..78378787e4e9 100644 --- a/drivers/pwm/pwm-brcmstb.c +++ b/drivers/pwm/pwm-brcmstb.c @@ -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 */ diff --git a/drivers/pwm/pwm-ipq.c b/drivers/pwm/pwm-ipq.c index 6b7aec566f7e..3e1231b212b1 100644 --- a/drivers/pwm/pwm-ipq.c +++ b/drivers/pwm/pwm-ipq.c @@ -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); diff --git a/drivers/pwm/pwm-iqs620a.c b/drivers/pwm/pwm-iqs620a.c index 13e5e138c8e9..6c6caee65b8f 100644 --- a/drivers/pwm/pwm-iqs620a.c +++ b/drivers/pwm/pwm-iqs620a.c @@ -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); diff --git a/drivers/pwm/pwm-loongson.c b/drivers/pwm/pwm-loongson.c index f2fb35b7af2b..98c809bde2b3 100644 --- a/drivers/pwm/pwm-loongson.c +++ b/drivers/pwm/pwm-loongson.c @@ -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); diff --git a/drivers/pwm/pwm-lp3943.c b/drivers/pwm/pwm-lp3943.c index 10537e74be28..e42af9007546 100644 --- a/drivers/pwm/pwm-lp3943.c +++ b/drivers/pwm/pwm-lp3943.c @@ -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; diff --git a/drivers/pwm/pwm-mtk-disp.c b/drivers/pwm/pwm-mtk-disp.c index b5409e15dac8..56d10cde5b46 100644 --- a/drivers/pwm/pwm-mtk-disp.c +++ b/drivers/pwm/pwm-mtk-disp.c @@ -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); diff --git a/drivers/pwm/pwm-pca9685.c b/drivers/pwm/pwm-pca9685.c index a2792b803643..3374ed76f8d3 100644 --- a/drivers/pwm/pwm-pca9685.c +++ b/drivers/pwm/pwm-pca9685.c @@ -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)) diff --git a/drivers/pwm/pwm-renesas-tpu.c b/drivers/pwm/pwm-renesas-tpu.c index 140fd0bcb93f..e35f2e14777d 100644 --- a/drivers/pwm/pwm-renesas-tpu.c +++ b/drivers/pwm/pwm-renesas-tpu.c @@ -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; } diff --git a/drivers/pwm/pwm-tiehrpwm.c b/drivers/pwm/pwm-tiehrpwm.c index 3a53489f0b98..e3407d0a1f7f 100644 --- a/drivers/pwm/pwm-tiehrpwm.c +++ b/drivers/pwm/pwm-tiehrpwm.c @@ -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) diff --git a/include/linux/notifier.h b/include/linux/notifier.h index 01b6c9d9956f..4eeae9741a6e 100644 --- a/include/linux/notifier.h +++ b/include/linux/notifier.h @@ -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, diff --git a/kernel/notifier.c b/kernel/notifier.c index 2f9fe7c30287..55fbd2be7407 100644 --- a/kernel/notifier.c +++ b/kernel/notifier.c @@ -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 |
