/* * Power-sequencing framework for linux. * * This subsystem allows power sequence providers to register a set of targets * that consumers may request and power-up/down. * * Glossary: * * Unit - a unit is a discreet chunk of a power sequence. For instance one unit * may enable a set of regulators, another may enable a specific GPIO. Units * can define dependencies in the form of other units that must be enabled * before it itself can be. * * Target - a target is a set of units (composed of the "final" unit and its * dependencies) that a consumer selects by its name when requesting a handle * to the power sequencer. Via the dependency system, multiple targets may * share the same parts of a power sequence but ignore parts that are * irrelevant. * * Descriptor - a handle passed by the pwrseq core to every consumer that * serves as the entry point to the provider layer. It ensures coherence * between different users and keeps reference counting consistent. * * Each provider must define a .match() callback whose role is to determine * whether a potential consumer is in fact associated with this sequencer. * This allows creating abstraction layers on top of regular device-tree * resources like regulators, clocks and other nodes connected to the consumer * via phandle.
*/
static DEFINE_IDA(pwrseq_ida);
/* * Protects the device list on the pwrseq bus from concurrent modifications * but allows simultaneous read-only access.
*/ static DECLARE_RWSEM(pwrseq_sem);
/** * struct pwrseq_unit - Private power-sequence unit data. * @ref: Reference count for this object. When it goes to 0, the object is * destroyed. * @name: Name of this target. * @list: Link to siblings on the list of all units of a single sequencer. * @deps: List of units on which this unit depends. * @enable: Callback running the part of the power-on sequence provided by * this unit. * @disable: Callback running the part of the power-off sequence provided * by this unit. * @enable_count: Current number of users that enabled this unit. May be the * consumer of the power sequencer or other units that depend * on this one.
*/ struct pwrseq_unit { struct kref ref; constchar *name; struct list_head list; struct list_head deps;
pwrseq_power_state_func enable;
pwrseq_power_state_func disable; unsignedint enable_count;
};
/** * struct pwrseq_unit_dep - Wrapper around a reference to the unit structure * allowing to keep it on multiple dependency lists * in different units. * @list: Siblings on the list. * @unit: Address of the referenced unit.
*/ struct pwrseq_unit_dep { struct list_head list; struct pwrseq_unit *unit;
};
/** * struct pwrseq_target - Private power-sequence target data. * @list: Siblings on the list of all targets exposed by a power sequencer. * @name: Name of the target. * @unit: Final unit for this target. * @post_enable: Callback run after the target unit has been enabled, *after* * the state lock has been released. It's useful for implementing * boot-up delays without blocking other users from powering up * using the same power sequencer.
*/ struct pwrseq_target { struct list_head list; constchar *name; struct pwrseq_unit *unit;
pwrseq_power_state_func post_enable;
};
/** * struct pwrseq_device - Private power sequencing data. * @dev: Device struct associated with this sequencer. * @id: Device ID. * @owner: Prevents removal of active power sequencing providers. * @rw_lock: Protects the device from being unregistered while in use. * @state_lock: Prevents multiple users running the power sequence at the same * time. * @match: Power sequencer matching callback. * @targets: List of targets exposed by this sequencer. * @units: List of all units supported by this sequencer.
*/ struct pwrseq_device { struct device dev; int id; struct module *owner; struct rw_semaphore rw_lock; struct mutex state_lock;
pwrseq_match_func match; struct list_head targets; struct list_head units;
};
/** * struct pwrseq_desc - Wraps access to the pwrseq_device and ensures that one * user cannot break the reference counting for others. * @pwrseq: Reference to the power sequencing device. * @target: Reference to the target this descriptor allows to control. * @powered_on: Power state set by the holder of the descriptor (not necessarily * corresponding to the actual power state of the device).
*/ struct pwrseq_desc { struct pwrseq_device *pwrseq; struct pwrseq_target *target; bool powered_on;
};
/** * pwrseq_device_register() - Register a new power sequencer. * @config: Configuration of the new power sequencing device. * * The config structure is only used during the call and can be freed after * the function returns. The config structure *must* have the parent device * as well as the match() callback and at least one target set. * * Returns: * Returns the address of the new pwrseq device or ERR_PTR() on failure.
*/ struct pwrseq_device *
pwrseq_device_register(conststruct pwrseq_config *config)
{ struct pwrseq_device *pwrseq; int ret, id;
if (!config->parent || !config->match || !config->targets ||
!config->targets[0]) return ERR_PTR(-EINVAL);
pwrseq = kzalloc(sizeof(*pwrseq), GFP_KERNEL); if (!pwrseq) return ERR_PTR(-ENOMEM);
/** * devm_pwrseq_device_register() - Managed variant of pwrseq_device_register(). * @dev: Managing device. * @config: Configuration of the new power sequencing device. * * Returns: * Returns the address of the new pwrseq device or ERR_PTR() on failure.
*/ struct pwrseq_device *
devm_pwrseq_device_register(struct device *dev, conststruct pwrseq_config *config)
{ struct pwrseq_device *pwrseq; int ret;
pwrseq = pwrseq_device_register(config); if (IS_ERR(pwrseq)) return pwrseq;
ret = devm_add_action_or_reset(dev, devm_pwrseq_device_unregister,
pwrseq); if (ret) return ERR_PTR(ret);
/** * pwrseq_device_get_drvdata() - Get the driver private data associated with * this sequencer. * @pwrseq: Power sequencer object. * * Returns: * Address of the private driver data.
*/ void *pwrseq_device_get_drvdata(struct pwrseq_device *pwrseq)
{ return dev_get_drvdata(&pwrseq->dev);
}
EXPORT_SYMBOL_GPL(pwrseq_device_get_drvdata);
guard(rwsem_read)(&pwrseq->rw_lock); if (!device_is_registered(&pwrseq->dev)) return 0;
ret = pwrseq->match(pwrseq, match_data->dev); if (ret == PWRSEQ_NO_MATCH || ret < 0) return ret;
/* We got the matching device, let's find the right target. */
list_for_each_entry(target, &pwrseq->targets, list) { if (strcmp(target->name, match_data->target)) continue;
match_data->desc->target = target;
}
/* * This device does not have this target. No point in deferring as it * will not get a new target dynamically later.
*/ if (!match_data->desc->target) return -ENOENT;
if (!try_module_get(pwrseq->owner)) return -EPROBE_DEFER;
/** * pwrseq_get() - Get the power sequencer associated with this device. * @dev: Device for which to get the sequencer. * @target: Name of the target exposed by the sequencer this device wants to * reach. * * Returns: * New power sequencer descriptor for use by the consumer driver or ERR_PTR() * on failure.
*/ struct pwrseq_desc *pwrseq_get(struct device *dev, constchar *target)
{ struct pwrseq_match_data match_data; int ret;
/** * devm_pwrseq_get() - Managed variant of pwrseq_get(). * @dev: Device for which to get the sequencer and which also manages its * lifetime. * @target: Name of the target exposed by the sequencer this device wants to * reach. * * Returns: * New power sequencer descriptor for use by the consumer driver or ERR_PTR() * on failure.
*/ struct pwrseq_desc *devm_pwrseq_get(struct device *dev, constchar *target)
{ struct pwrseq_desc *desc; int ret;
desc = pwrseq_get(dev, target); if (IS_ERR(desc)) return desc;
ret = devm_add_action_or_reset(dev, devm_pwrseq_put, desc); if (ret) return ERR_PTR(ret);
if (unit->enable_count != 0) {
unit->enable_count++; return 0;
}
ret = pwrseq_unit_enable_deps(pwrseq, &unit->deps); if (ret) {
dev_err(&pwrseq->dev, "Failed to enable dependencies before power-on for target '%s': %d\n",
unit->name, ret); return ret;
}
if (unit->enable) {
ret = unit->enable(pwrseq); if (ret) {
dev_err(&pwrseq->dev, "Failed to enable target '%s': %d\n",
unit->name, ret);
pwrseq_unit_disable_deps(pwrseq, &unit->deps); return ret;
}
}
unit->enable_count++;
return 0;
}
staticint pwrseq_unit_disable(struct pwrseq_device *pwrseq, struct pwrseq_unit *unit)
{ int ret;
if (unit->enable_count == 0) {
WARN(1, "Unmatched power-off for target '%s'\n",
unit->name); return -EBUSY;
}
if (unit->enable_count != 1) {
unit->enable_count--; return 0;
}
if (unit->disable) {
ret = unit->disable(pwrseq); if (ret) {
dev_err(&pwrseq->dev, "Failed to disable target '%s': %d\n",
unit->name, ret); return ret;
}
}
ret = pwrseq_unit_disable_deps(pwrseq, &unit->deps); if (ret) {
dev_err(&pwrseq->dev, "Failed to disable dependencies after power-off for target '%s': %d\n",
unit->name, ret); if (unit->enable)
unit->enable(pwrseq); return ret;
}
unit->enable_count--;
return 0;
}
/** * pwrseq_power_on() - Issue a power-on request on behalf of the consumer * device. * @desc: Descriptor referencing the power sequencer. * * This function tells the power sequencer that the consumer wants to be * powered-up. The sequencer may already have powered-up the device in which * case the function returns 0. If the power-up sequence is already in * progress, the function will block until it's done and return 0. If this is * the first request, the device will be powered up. * * Returns: * 0 on success, negative error number on failure.
*/ int pwrseq_power_on(struct pwrseq_desc *desc)
{ struct pwrseq_device *pwrseq; struct pwrseq_target *target; struct pwrseq_unit *unit; int ret;
might_sleep();
if (!desc || desc->powered_on) return 0;
pwrseq = desc->pwrseq;
target = desc->target;
unit = target->unit;
guard(rwsem_read)(&pwrseq->rw_lock); if (!device_is_registered(&pwrseq->dev)) return -ENODEV;
scoped_guard(mutex, &pwrseq->state_lock) {
ret = pwrseq_unit_enable(pwrseq, unit); if (!ret)
desc->powered_on = true;
}
if (target->post_enable) {
ret = target->post_enable(pwrseq); if (ret) {
pwrseq_unit_disable(pwrseq, unit);
desc->powered_on = false;
}
}
return ret;
}
EXPORT_SYMBOL_GPL(pwrseq_power_on);
/** * pwrseq_power_off() - Issue a power-off request on behalf of the consumer * device. * @desc: Descriptor referencing the power sequencer. * * This undoes the effects of pwrseq_power_on(). It issues a power-off request * on behalf of the consumer and when the last remaining user does so, the * power-down sequence will be started. If one is in progress, the function * will block until it's complete and then return. * * Returns: * 0 on success, negative error number on failure.
*/ int pwrseq_power_off(struct pwrseq_desc *desc)
{ struct pwrseq_device *pwrseq; struct pwrseq_unit *unit; int ret;
might_sleep();
if (!desc || !desc->powered_on) return 0;
pwrseq = desc->pwrseq;
unit = desc->target->unit;
guard(rwsem_read)(&pwrseq->rw_lock); if (!device_is_registered(&pwrseq->dev)) return -ENODEV;
guard(mutex)(&pwrseq->state_lock);
ret = pwrseq_unit_disable(pwrseq, unit); if (!ret)
desc->powered_on = false;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.