Commit 7111c6d1 authored by Matti Vaittinen's avatar Matti Vaittinen Committed by Mark Brown

regulator: IRQ based event/error notification helpers

Provide helper function for IC's implementing regulator notifications
when an IRQ fires. The helper also works for IRQs which can not be acked.
Helper can be set to disable the IRQ at handler and then re-enabling it
on delayed work later. The helper also adds regulator_get_error_flags()
errors in cache for the duration of IRQ disabling.
Signed-off-by: default avatarMatti Vaittinen <matti.vaittinen@fi.rohmeurope.com>
Reviewed-by: default avatarAndy Shevchenko <andy.shevchenko@gmail.com>
Link: https://lore.kernel.org/r/ebdf86d8c22b924667ec2385330e30fcbfac0119.1622628334.git.matti.vaittinen@fi.rohmeurope.comSigned-off-by: default avatarMark Brown <broonie@kernel.org>
parent 157d2230
......@@ -4,7 +4,7 @@
#
obj-$(CONFIG_REGULATOR) += core.o dummy.o fixed-helper.o helpers.o devres.o
obj-$(CONFIG_REGULATOR) += core.o dummy.o fixed-helper.o helpers.o devres.o irq_helpers.o
obj-$(CONFIG_OF) += of_regulator.o
obj-$(CONFIG_REGULATOR_FIXED_VOLTAGE) += fixed.o
obj-$(CONFIG_REGULATOR_VIRTUAL_CONSUMER) += virtual.o
......
......@@ -4370,22 +4370,36 @@ unsigned int regulator_get_mode(struct regulator *regulator)
}
EXPORT_SYMBOL_GPL(regulator_get_mode);
static int rdev_get_cached_err_flags(struct regulator_dev *rdev)
{
int ret = 0;
if (rdev->use_cached_err) {
spin_lock(&rdev->err_lock);
ret = rdev->cached_err;
spin_unlock(&rdev->err_lock);
}
return ret;
}
static int _regulator_get_error_flags(struct regulator_dev *rdev,
unsigned int *flags)
{
int ret;
int cached_flags, ret = 0;
regulator_lock(rdev);
/* sanity check */
if (!rdev->desc->ops->get_error_flags) {
cached_flags = rdev_get_cached_err_flags(rdev);
if (rdev->desc->ops->get_error_flags)
ret = rdev->desc->ops->get_error_flags(rdev, flags);
else if (!rdev->use_cached_err)
ret = -EINVAL;
goto out;
}
ret = rdev->desc->ops->get_error_flags(rdev, flags);
out:
*flags |= cached_flags;
regulator_unlock(rdev);
return ret;
}
......@@ -5218,6 +5232,7 @@ regulator_register(const struct regulator_desc *regulator_desc,
goto rinse;
}
device_initialize(&rdev->dev);
spin_lock_init(&rdev->err_lock);
/*
* Duplicate the config so the driver could override it after
......
......@@ -481,3 +481,55 @@ void devm_regulator_unregister_notifier(struct regulator *regulator,
WARN_ON(rc);
}
EXPORT_SYMBOL_GPL(devm_regulator_unregister_notifier);
static void regulator_irq_helper_drop(void *res)
{
regulator_irq_helper_cancel(&res);
}
/**
* devm_regulator_irq_helper - resource managed registration of IRQ based
* regulator event/error notifier
*
* @dev: device to which lifetime the helper's lifetime is
* bound.
* @d: IRQ helper descriptor.
* @irq: IRQ used to inform events/errors to be notified.
* @irq_flags: Extra IRQ flags to be OR'ed with the default
* IRQF_ONESHOT when requesting the (threaded) irq.
* @common_errs: Errors which can be flagged by this IRQ for all rdevs.
* When IRQ is re-enabled these errors will be cleared
* from all associated regulators
* @per_rdev_errs: Optional error flag array describing errors specific
* for only some of the regulators. These errors will be
* or'ed with common errors. If this is given the array
* should contain rdev_amount flags. Can be set to NULL
* if there is no regulator specific error flags for this
* IRQ.
* @rdev: Array of pointers to regulators associated with this
* IRQ.
* @rdev_amount: Amount of regulators associated with this IRQ.
*
* Return: handle to irq_helper or an ERR_PTR() encoded error code.
*/
void *devm_regulator_irq_helper(struct device *dev,
const struct regulator_irq_desc *d, int irq,
int irq_flags, int common_errs,
int *per_rdev_errs,
struct regulator_dev **rdev, int rdev_amount)
{
void *ptr;
int ret;
ptr = regulator_irq_helper(dev, d, irq, irq_flags, common_errs,
per_rdev_errs, rdev, rdev_amount);
if (IS_ERR(ptr))
return ptr;
ret = devm_add_action_or_reset(dev, regulator_irq_helper_drop, ptr);
if (ret)
return ERR_PTR(ret);
return ptr;
}
EXPORT_SYMBOL_GPL(devm_regulator_irq_helper);
This diff is collapsed.
......@@ -413,6 +413,128 @@ struct regulator_config {
struct gpio_desc *ena_gpiod;
};
/**
* struct regulator_err_state - regulator error/notification status
*
* @rdev: Regulator which status the struct indicates.
* @notifs: Events which have occurred on the regulator.
* @errors: Errors which are active on the regulator.
* @possible_errs: Errors which can be signaled (by given IRQ).
*/
struct regulator_err_state {
struct regulator_dev *rdev;
unsigned long notifs;
unsigned long errors;
int possible_errs;
};
/**
* struct regulator_irq_data - regulator error/notification status date
*
* @states: Status structs for each of the associated regulators.
* @num_states: Amount of associated regulators.
* @data: Driver data pointer given at regulator_irq_desc.
* @opaque: Value storage for IC driver. Core does not update this. ICs
* may want to store status register value here at map_event and
* compare contents at 'renable' callback to see if new problems
* have been added to status. If that is the case it may be
* desirable to return REGULATOR_ERROR_CLEARED and not
* REGULATOR_ERROR_ON to allow IRQ fire again and to generate
* notifications also for the new issues.
*
* This structure is passed to 'map_event' and 'renable' callbacks for
* reporting regulator status to core.
*/
struct regulator_irq_data {
struct regulator_err_state *states;
int num_states;
void *data;
long opaque;
};
/**
* struct regulator_irq_desc - notification sender for IRQ based events.
*
* @name: The visible name for the IRQ
* @fatal_cnt: If this IRQ is used to signal HW damaging condition it may be
* best to shut-down regulator(s) or reboot the SOC if error
* handling is repeatedly failing. If fatal_cnt is given the IRQ
* handling is aborted if it fails for fatal_cnt times and die()
* callback (if populated) or BUG() is called to try to prevent
* further damage.
* @reread_ms: The time which is waited before attempting to re-read status
* at the worker if IC reading fails. Immediate re-read is done
* if time is not specified.
* @irq_off_ms: The time which IRQ is kept disabled before re-evaluating the
* status for devices which keep IRQ disabled for duration of the
* error. If this is not given the IRQ is left enabled and renable
* is not called.
* @skip_off: If set to true the IRQ handler will attempt to check if any of
* the associated regulators are enabled prior to taking other
* actions. If no regulators are enabled and this is set to true
* a spurious IRQ is assumed and IRQ_NONE is returned.
* @high_prio: Boolean to indicate that high priority WQ should be used.
* @data: Driver private data pointer which will be passed as such to
* the renable, map_event and die callbacks in regulator_irq_data.
* @die: Protection callback. If IC status reading or recovery actions
* fail fatal_cnt times this callback or BUG() is called. This
* callback should implement a final protection attempt like
* disabling the regulator. If protection succeeded this may
* return 0. If anything else is returned the core assumes final
* protection failed and calls BUG() as a last resort.
* @map_event: Driver callback to map IRQ status into regulator devices with
* events / errors. NOTE: callback MUST initialize both the
* errors and notifs for all rdevs which it signals having
* active events as core does not clean the map data.
* REGULATOR_FAILED_RETRY can be returned to indicate that the
* status reading from IC failed. If this is repeated for
* fatal_cnt times the core will call die() callback or BUG()
* as a last resort to protect the HW.
* @renable: Optional callback to check status (if HW supports that) before
* re-enabling IRQ. If implemented this should clear the error
* flags so that errors fetched by regulator_get_error_flags()
* are updated. If callback is not implemented then errors are
* assumed to be cleared and IRQ is re-enabled.
* REGULATOR_FAILED_RETRY can be returned to
* indicate that the status reading from IC failed. If this is
* repeated for 'fatal_cnt' times the core will call die()
* callback or BUG() as a last resort to protect the HW.
* Returning zero indicates that the problem in HW has been solved
* and IRQ will be re-enabled. Returning REGULATOR_ERROR_ON
* indicates the error condition is still active and keeps IRQ
* disabled. Please note that returning REGULATOR_ERROR_ON does
* not retrigger evaluating what events are active or resending
* notifications. If this is needed you probably want to return
* zero and allow IRQ to retrigger causing events to be
* re-evaluated and re-sent.
*
* This structure is used for registering regulator IRQ notification helper.
*/
struct regulator_irq_desc {
const char *name;
int irq_flags;
int fatal_cnt;
int reread_ms;
int irq_off_ms;
bool skip_off;
bool high_prio;
void *data;
int (*die)(struct regulator_irq_data *rid);
int (*map_event)(int irq, struct regulator_irq_data *rid,
unsigned long *dev_mask);
int (*renable)(struct regulator_irq_data *rid);
};
/*
* Return values for regulator IRQ helpers.
*/
enum {
REGULATOR_ERROR_CLEARED,
REGULATOR_FAILED_RETRY,
REGULATOR_ERROR_ON,
};
/*
* struct coupling_desc
*
......@@ -477,6 +599,9 @@ struct regulator_dev {
/* time when this regulator was disabled last time */
ktime_t last_off;
int cached_err;
bool use_cached_err;
spinlock_t err_lock;
};
struct regulator_dev *
......@@ -491,6 +616,16 @@ void devm_regulator_unregister(struct device *dev, struct regulator_dev *rdev);
int regulator_notifier_call_chain(struct regulator_dev *rdev,
unsigned long event, void *data);
void *devm_regulator_irq_helper(struct device *dev,
const struct regulator_irq_desc *d, int irq,
int irq_flags, int common_errs,
int *per_rdev_errs, struct regulator_dev **rdev,
int rdev_amount);
void *regulator_irq_helper(struct device *dev,
const struct regulator_irq_desc *d, int irq,
int irq_flags, int common_errs, int *per_rdev_errs,
struct regulator_dev **rdev, int rdev_amount);
void regulator_irq_helper_cancel(void **handle);
void *rdev_get_drvdata(struct regulator_dev *rdev);
struct device *rdev_get_dev(struct regulator_dev *rdev);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment