mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
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:
commit
b0e1eda687
|
|
@ -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 */
|
||||
};
|
||||
|
|
@ -15,6 +15,7 @@ properties:
|
|||
- items:
|
||||
- enum:
|
||||
- qcom,ipq5018-pwm
|
||||
- qcom,ipq5210-pwm
|
||||
- qcom,ipq5332-pwm
|
||||
- qcom,ipq9574-pwm
|
||||
- const: qcom,ipq6018-pwm
|
||||
|
|
|
|||
82
Documentation/devicetree/bindings/pwm/ti,am33xx-pwmss.yaml
Normal file
82
Documentation/devicetree/bindings/pwm/ti,am33xx-pwmss.yaml
Normal 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";
|
||||
};
|
||||
};
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user