diff options
Diffstat (limited to 'drivers')
| -rw-r--r-- | drivers/regulator/core.c | 124 | ||||
| -rw-r--r-- | drivers/regulator/sy7636a-regulator.c | 27 |
2 files changed, 151 insertions, 0 deletions
diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index dd7b10e768c0..84bc38911dba 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -83,6 +83,19 @@ struct regulator_supply_alias { const char *alias_supply; }; +/* + * Work item used to forward regulator events. + * + * @work: workqueue entry + * @rdev: regulator device to notify (consumer receiving the forwarded event) + * @event: event code to be forwarded + */ +struct regulator_event_work { + struct work_struct work; + struct regulator_dev *rdev; + unsigned long event; +}; + static int _regulator_is_enabled(struct regulator_dev *rdev); static int _regulator_disable(struct regulator *regulator); static int _regulator_get_error_flags(struct regulator_dev *rdev, unsigned int *flags); @@ -1659,6 +1672,104 @@ static int set_machine_constraints(struct regulator_dev *rdev) } /** + * regulator_event_work_fn - process a deferred regulator event + * @work: work_struct queued by the notifier + * + * Calls the regulator's notifier chain in process context while holding + * the rdev lock, then releases the device reference. + */ +static void regulator_event_work_fn(struct work_struct *work) +{ + struct regulator_event_work *rew = + container_of(work, struct regulator_event_work, work); + struct regulator_dev *rdev = rew->rdev; + int ret; + + regulator_lock(rdev); + ret = regulator_notifier_call_chain(rdev, rew->event, NULL); + regulator_unlock(rdev); + if (ret == NOTIFY_BAD) + dev_err(rdev_get_dev(rdev), "failed to forward regulator event\n"); + + put_device(rdev_get_dev(rdev)); + kfree(rew); +} + +/** + * regulator_event_forward_notifier - notifier callback for supply events + * @nb: notifier block embedded in the regulator + * @event: regulator event code + * @data: unused + * + * Packages the event into a work item and schedules it in process context. + * Takes a reference on @rdev->dev to pin the regulator until the work + * completes (see put_device() in the worker). + * + * Return: NOTIFY_OK on success, NOTIFY_DONE for events that are not forwarded. + */ +static int regulator_event_forward_notifier(struct notifier_block *nb, + unsigned long event, + void __always_unused *data) +{ + struct regulator_dev *rdev = container_of(nb, struct regulator_dev, + supply_fwd_nb); + struct regulator_event_work *rew; + + switch (event) { + case REGULATOR_EVENT_UNDER_VOLTAGE: + break; + default: + /* Only forward allowed events downstream. */ + return NOTIFY_DONE; + } + + rew = kmalloc(sizeof(*rew), GFP_ATOMIC); + if (!rew) + return NOTIFY_DONE; + + get_device(rdev_get_dev(rdev)); + rew->rdev = rdev; + rew->event = event; + INIT_WORK(&rew->work, regulator_event_work_fn); + + queue_work(system_highpri_wq, &rew->work); + + return NOTIFY_OK; +} + +/** + * register_regulator_event_forwarding - enable supply event forwarding + * @rdev: regulator device + * + * Registers a notifier on the regulator's supply so that supply events + * are forwarded to the consumer regulator via the deferred work handler. + * + * Return: 0 on success, -EALREADY if already enabled, or a negative error code. + */ +static int register_regulator_event_forwarding(struct regulator_dev *rdev) +{ + int ret; + + if (!rdev->supply) + return 0; /* top-level regulator: nothing to forward */ + + if (rdev->supply_fwd_nb.notifier_call) + return -EALREADY; + + rdev->supply_fwd_nb.notifier_call = regulator_event_forward_notifier; + + ret = regulator_register_notifier(rdev->supply, &rdev->supply_fwd_nb); + if (ret) { + dev_err(&rdev->dev, "failed to register supply notifier: %pe\n", + ERR_PTR(ret)); + rdev->supply_fwd_nb.notifier_call = NULL; + return ret; + } + + return 0; +} + +/** * set_supply - set regulator supply regulator * @rdev: regulator (locked) * @supply_rdev: supply regulator (locked)) @@ -2144,6 +2255,16 @@ static int regulator_resolve_supply(struct regulator_dev *rdev) goto out; } + /* + * Automatically register for event forwarding from the new supply. + * This creates the downstream propagation link for events like + * under-voltage. + */ + ret = register_regulator_event_forwarding(rdev); + if (ret < 0) + rdev_warn(rdev, "Failed to register event forwarding: %pe\n", + ERR_PTR(ret)); + regulator_unlock_two(rdev, r, &ww_ctx); /* rdev->supply was created in set_supply() */ @@ -6031,6 +6152,9 @@ void regulator_unregister(struct regulator_dev *rdev) return; if (rdev->supply) { + regulator_unregister_notifier(rdev->supply, + &rdev->supply_fwd_nb); + while (rdev->use_count--) regulator_disable(rdev->supply); regulator_put(rdev->supply); diff --git a/drivers/regulator/sy7636a-regulator.c b/drivers/regulator/sy7636a-regulator.c index 27e3d939b7bb..551647bc1052 100644 --- a/drivers/regulator/sy7636a-regulator.c +++ b/drivers/regulator/sy7636a-regulator.c @@ -12,6 +12,7 @@ #include <linux/mfd/sy7636a.h> #include <linux/module.h> #include <linux/platform_device.h> +#include <linux/regulator/consumer.h> #include <linux/regulator/driver.h> #include <linux/regulator/machine.h> #include <linux/regmap.h> @@ -19,6 +20,8 @@ struct sy7636a_data { struct regmap *regmap; struct gpio_desc *pgood_gpio; + struct gpio_desc *en_gpio; + struct gpio_desc *vcom_en_gpio; }; static int sy7636a_get_vcom_voltage_op(struct regulator_dev *rdev) @@ -98,6 +101,30 @@ static int sy7636a_regulator_probe(struct platform_device *pdev) data->regmap = regmap; data->pgood_gpio = gdp; + ret = devm_regulator_get_enable_optional(&pdev->dev, "vin"); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "failed to get vin regulator\n"); + + data->en_gpio = devm_gpiod_get_optional(&pdev->dev, "enable", + GPIOD_OUT_HIGH); + if (IS_ERR(data->en_gpio)) + return dev_err_probe(&pdev->dev, + PTR_ERR(data->en_gpio), + "failed to get en gpio\n"); + + /* Let VCOM just follow the default power on sequence */ + data->vcom_en_gpio = devm_gpiod_get_optional(&pdev->dev, + "vcom-en", GPIOD_OUT_LOW); + if (IS_ERR(data->vcom_en_gpio)) + return dev_err_probe(&pdev->dev, + PTR_ERR(data->vcom_en_gpio), + "failed to get vcom-en gpio\n"); + + /* if chip was not enabled, give it time to wake up */ + if (data->en_gpio) + usleep_range(2500, 4000); + platform_set_drvdata(pdev, data); ret = regmap_write(regmap, SY7636A_REG_POWER_ON_DELAY_TIME, 0x0); |
