Some device drivers (panel backlights especially) need to follow precise sequences for powering on and off, involving gpios, regulators, PWMs with a precise powering order and delays to respect between each steps. These sequences are board-specific, and do not belong to a particular driver - therefore they have been performed by board-specific hook functions to far. With the advent of the device tree and of ARM kernels that are not board-tied, we cannot rely on these board-specific hooks anymore but need a way to implement these sequences in a portable manner. This patch introduces a simple interpreter that can execute such power sequences encoded either as platform data or within the device tree. Signed-off-by: Alexandre Courbot <acourbot@xxxxxxxxxx> --- .../devicetree/bindings/power_seq/power_seq.txt | 122 ++++++ Documentation/power/power_seq.txt | 215 ++++++++++ drivers/power/Kconfig | 1 + drivers/power/Makefile | 1 + drivers/power/power_seq/Kconfig | 2 + drivers/power/power_seq/Makefile | 1 + drivers/power/power_seq/power_seq.c | 446 +++++++++++++++++++++ drivers/power/power_seq/power_seq_delay.c | 51 +++ drivers/power/power_seq/power_seq_gpio.c | 91 +++++ drivers/power/power_seq/power_seq_pwm.c | 87 ++++ drivers/power/power_seq/power_seq_regulator.c | 87 ++++ include/linux/power_seq.h | 172 ++++++++ 12 files changed, 1276 insertions(+) create mode 100644 Documentation/devicetree/bindings/power_seq/power_seq.txt create mode 100644 Documentation/power/power_seq.txt create mode 100644 drivers/power/power_seq/Kconfig create mode 100644 drivers/power/power_seq/Makefile create mode 100644 drivers/power/power_seq/power_seq.c create mode 100644 drivers/power/power_seq/power_seq_delay.c create mode 100644 drivers/power/power_seq/power_seq_gpio.c create mode 100644 drivers/power/power_seq/power_seq_pwm.c create mode 100644 drivers/power/power_seq/power_seq_regulator.c create mode 100644 include/linux/power_seq.h diff --git a/Documentation/devicetree/bindings/power_seq/power_seq.txt b/Documentation/devicetree/bindings/power_seq/power_seq.txt new file mode 100644 index 0000000..2b394eb --- /dev/null +++ b/Documentation/devicetree/bindings/power_seq/power_seq.txt @@ -0,0 +1,122 @@ +Runtime Interpreted Power Sequences +=================================== + +Power sequences are sequential descriptions of actions to be performed on +power-related resources. Having these descriptions in a well-defined data format +allows us to take much of the board-specific power control code out of the +kernel and place it into the device tree instead, making kernels less +board-dependant. + +A device typically makes use of multiple power sequences, for different purposes +such as powering on and off. All the power sequences of a given device are +grouped into a set. In the device tree, this set is a sub-node of the device +node named "power-sequences". + +Power Sequences Structure +------------------------- +Every device that makes use of power sequences must have a "power-sequences" +node into which individual power sequences are declared as sub-nodes. The name +of the node becomes the name of the sequence within the power sequences +framework. + +Similarly, each power sequence declares its steps as sub-nodes of itself. Steps +must be named sequentially, with the first step named step0, the second step1, +etc. Failure to follow this rule will result in a parsing error. + +Power Sequences Steps +--------------------- +Steps of a sequence describe an action to be performed on a resource. They +always include a "type" property which indicates what kind of resource this +step works on. Depending on the resource type, additional properties are defined +to control the action to be performed. + +"delay" type required properties: + - delay: delay to wait (in microseconds) + +"regulator" type required properties: + - id: name of the regulator to use. Regulator is obtained by + regulator_get(dev, id) + - enable / disable: one of these two empty properties must be present to + enable or disable the resource + +"pwm" type required properties: + - id: name of the PWM to use. PWM is obtained by pwm_get(dev, id) + - enable / disable: one of these two empty properties must be present to + enable or disable the resource + +"gpio" type required properties: + - gpio: phandle of the GPIO to use. + - value: value this GPIO should take. Must be 0 or 1. + +Example +------- +Here are example sequences declared within a backlight device that use all the +supported resources types: + + backlight { + compatible = "pwm-backlight"; + ... + + /* resources used by the power sequences */ + pwms = <&pwm 2 5000000>; + pwm-names = "backlight"; + power-supply = <&backlight_reg>; + + power-sequences { + power-on { + step0 { + type = "regulator"; + id = "power"; + enable; + }; + step1 { + type = "delay"; + delay = <10000>; + }; + step2 { + type = "pwm"; + id = "backlight"; + enable; + }; + step3 { + type = "gpio"; + gpio = <&gpio 28 0>; + value = <1>; + }; + }; + + power-off { + step0 { + type = "gpio"; + gpio = <&gpio 28 0>; + value = <0>; + }; + step1 { + type = "pwm"; + id = "backlight"; + disable; + }; + step2 { + type = "delay"; + delay = <10000>; + }; + step3 { + type = "regulator"; + id = "power"; + disable; + }; + }; + }; + }; + +The first part lists the PWM and regulator resources used by the sequences. +These resources will be requested on behalf of the backlight device when the +sequences are built and are declared according to their own framework (for +instance, regulators and pwms are resolved by name using regulator_get() or +pwm_get()). + +After the resources declaration, two sequences follow for powering the backlight +on and off. Their names are specified by the pwm-backlight device bindings. Once +the sequences are built by calling devm_power_seq_set_build() with a NULL pseq +argument, their names can be passed to power_seq_lookup() to retrieve an actual +sequence. diff --git a/Documentation/power/power_seq.txt b/Documentation/power/power_seq.txt new file mode 100644 index 0000000..7f3f2d2 --- /dev/null +++ b/Documentation/power/power_seq.txt @@ -0,0 +1,215 @@ +Runtime Interpreted Power Sequences +=================================== + +Problem +------- +Very commonly, boards need the help of out-of-driver code to turn some of their +devices on and off. For instance, SoC boards very commonly use a GPIO +(abstracted to a regulator or not) to control the power supply of a backlight, +disabling it when the backlight is not used in order to save power. The GPIO +that should be used, however, as well as the exact power sequence that may +also involve other resources, is board-dependent and thus unknown to the driver. + +This was previously addressed by having hooks in the device's platform data that +are called whenever the state of the device might reflect a power change. This +approach, however, introduces board-dependant code into the kernel and is not +compatible with the device tree. + +The Runtime Interpreted Power Sequences (or power sequences for short) aim at +turning this code into platform data or device tree nodes. Power sequences are +described using a simple format and run by a lightweight interpreter whenever +needed. This allows device drivers to work without power callbacks and makes the +kernel less board-dependant. + +What are Power Sequences? +------------------------- +A power sequence is a suite of sequential steps each describing an action to be +performed on a resource. The supported resources and actions operations are: +- delay (just wait for a given number of microseconds) +- GPIO (set to 0 or 1) +- regulator (enable or disable) +- PWM (enable or disable) + +When a power sequence is run, each of its steps is executed one after the other +until one step fails or the end of the sequence is reached. + +Power sequences are named, and grouped into "sets" which correspond to all the +sequences of a device as well as the resources they use. + +Power sequences can be declared as platform data or in the device tree. + +Platform Data Format +-------------------- +All relevant data structures for declaring power sequences are located in +include/linux/power_seq.h. + +The platform data for a device may include an instance of platform_power_seq_set +which references instances of platform_power_seq. Each platform_power_seq is a +single power sequence, and is itself made of a variable length array of steps. + +A step is a union of all the platform step structures, which are defined and +documented in include/linux/power_seq.h. Which one is to be used depends on the +type of the step. This might sound complicated, but the following example should +make it clear how the platform data for power sequences is defined. It declares +two power sequences named "power-on" and "power-off". The +"power-on" sequence enables a regulator named "power" (retrieved using +regulator_get()), waits for 10ms, and then set GPIO 110 to 1. "power-off" does +the opposite. + +static struct platform_power_seq power_on_seq = { + .id = "power-on", + .num_steps = 3, + .steps = { + { + .type = POWER_SEQ_REGULATOR, + .regulator = { + .id = "power", + .enable = true, + }, + }, + { + .type = POWER_SEQ_DELAY, + .delay = { + .delay = 10000, + }, + }, + { + .type = POWER_SEQ_GPIO, + .gpio = { + .gpio = 110, + .value = 1, + }, + }, + }, +}; + +static struct platform_power_seq power_off_seq = { + .id = "power-off", + .num_steps = 3, + .steps = { + { + .type = POWER_SEQ_GPIO, + .gpio = { + .gpio = 110, + .value = 0, + }, + }, + { + .type = POWER_SEQ_DELAY, + .delay = { + .delay = 10000, + }, + }, + { + .type = POWER_SEQ_REGULATOR, + .regulator = { + .id = "power", + .enable = false, + }, + }, + }, +}; + +static struct platform_power_seq_set platform_power_sequences = { + .num_seqs = 2, + .seqs = { + &power_on_seq, + &power_off_seq, + }, +}; + +"platform_power_sequences" can then be passed to devm_power_seq_set_build() in +order to build the corresponding sequences set and allocate all the necessary +resources. More on this later in this document. + +Device Tree +----------- +Power sequences can also be encoded as device tree nodes. The following +properties and nodes are equivalent to the platform data defined previously: + +power-supply = <&power_reg>; + +power-sequences { + power-on { + step0 { + type = "regulator"; + id = "power"; + enable; + }; + step1 { + type = "delay"; + delay = <10000>; + }; + step2 { + type = "gpio"; + gpio = <&gpio 110 0>; + value = <1>; + }; + } + power-off { + step0 { + type = "gpio"; + gpio = <&gpio 110 0>; + value = <0>; + }; + step1 { + type = "delay"; + delay = <10000>; + }; + step2 { + type = "regulator"; + id = "power"; + disable; + }; + } +}; + +See Documentation/devicetree/bindings/power_seq/power_seq.txt for the complete +syntax of the DT bindings. + +Usage by Drivers and Resources Management +----------------------------------------- +Power sequences make use of resources that must be properly allocated and +managed. The devm_power_seq_set_build() function builds a power sequence set +from platform data. It also takes care of resolving and allocating the resources +referenced by the sequence: + + struct power_seq_set *devm_power_seq_set_build(struct device *dev, + struct platform_power_seq_set *pseq); + +As its name states, all memory and resources are devm-allocated. The 'dev' +argument is the device in the name of which the resources are to be allocated. + +If the pseq argument is NULL, then the power sequences are built from the device +tree data, if any. + +On success, the function returns a devm allocated resolved sequences set for +which all the resources are allocated. In case of failure, an error code is +returned. If no set can be built because no platform data is passed and the +device tree node for the device contains no power sequence, the function returns +NULL. + +Once the set is built, power sequences can be retrieved by their name using +power_seq_lookup: + + struct power_seq *power_seq_lookup(struct power_seq_set *seqs, + const char *id); + +A retrieved power sequence can then be executed by power_seq_run: + + int power_seq_run(struct power_seq *seq); + +It returns 0 if the sequence has successfully been run, or an error code if a +problem occurred. + +Sometimes, you may want to browse the list of resources allocated by a sequence, +for instance to ensure that a resource of a given type is present. The +power_seq_set_resources() function returns a list head that can be used with +the power_seq_for_each_resource() macro to browse all the resources of a set: + + struct list_head *power_seq_set_resources(struct power_seq_set *seqs); + power_seq_for_each_resource(pos, seqs) + +Here "pos" will be a pointer to a struct power_seq_resource. This structure +contains the type of the resource, the information used for identifying it, and +the resolved resource itself. diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index fcc1bb0..5fdfd84 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -312,3 +312,4 @@ config AB8500_BATTERY_THERM_ON_BATCTRL endif # POWER_SUPPLY source "drivers/power/avs/Kconfig" +source "drivers/power/power_seq/Kconfig" diff --git a/drivers/power/Makefile b/drivers/power/Makefile index ee58afb..d3c893b 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -45,3 +45,4 @@ obj-$(CONFIG_CHARGER_MAX8997) += max8997_charger.o obj-$(CONFIG_CHARGER_MAX8998) += max8998_charger.o obj-$(CONFIG_POWER_AVS) += avs/ obj-$(CONFIG_CHARGER_SMB347) += smb347-charger.o +obj-$(CONFIG_POWER_SEQ) += power_seq/ diff --git a/drivers/power/power_seq/Kconfig b/drivers/power/power_seq/Kconfig new file mode 100644 index 0000000..3bff26e --- /dev/null +++ b/drivers/power/power_seq/Kconfig @@ -0,0 +1,2 @@ +config POWER_SEQ + bool diff --git a/drivers/power/power_seq/Makefile b/drivers/power/power_seq/Makefile new file mode 100644 index 0000000..f77a359 --- /dev/null +++ b/drivers/power/power_seq/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_POWER_SEQ) += power_seq.o diff --git a/drivers/power/power_seq/power_seq.c b/drivers/power/power_seq/power_seq.c new file mode 100644 index 0000000..0924a03 --- /dev/null +++ b/drivers/power/power_seq/power_seq.c @@ -0,0 +1,446 @@ +/* + * power_seq.c - A simple power sequence interpreter for platform devices + * and device tree. + * + * Author: Alexandre Courbot <acourbot@xxxxxxxxxx> + * + * Copyright (c) 2012 NVIDIA Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + +#include <linux/power_seq.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/device.h> + +#include <linux/of.h> + +struct power_seq_set { + struct device *dev; + struct list_head resources; + struct list_head sequences; +}; + +struct power_seq_step { + /* Copy of the platform data */ + struct platform_power_seq_step pdata; + /* Resolved resource */ + struct power_seq_resource *resource; +}; + +struct power_seq { + /* Set this sequence belongs to */ + struct power_seq_set *parent_set; + const char *id; + /* To thread into power_seqs structure */ + struct list_head list; + unsigned int num_steps; + struct power_seq_step steps[]; +}; + +#define power_seq_err(dev, seq, step_nbr, format, ...) \ + dev_err(dev, "%s[%d]: " format, seq->id, step_nbr, ##__VA_ARGS__); + +/** + * struct power_seq_res_type - operators for power sequences resources + * @name: Name of the resource type. Set to null when a resource + * type support is not compiled in + * @need_resource: Whether a resource needs to be allocated when steps of + * this kind are met. If set to false, res_compare and + * res_alloc need not be set + * @of_parse: Parse a step for this kind of resource from a device + * tree node. The result of parsing must be written into + * step step_nbr of seq + * @step_run: Run a step for this kind of resource + * @res_compare: Return true if the resource used by the resource is the + * same as the one referenced by the step, false otherwise. + * @res_alloc: Resolve and allocate the resource used by pstep into + * seq. The memory for seq is already allocated and its + * type member is already set when this function is called. + * Return error code if the resource cannot be allocated, 0 + * if allocation went successfully. + */ +struct power_seq_res_ops { + const char *name; + bool need_resource; + int (*of_parse)(struct device *dev, struct device_node *node, + struct platform_power_seq *seq, unsigned int step_nbr); + int (*step_run)(struct power_seq_step *step); + bool (*res_compare)(struct power_seq_resource *res, + struct platform_power_seq_step *step); + int (*res_alloc)(struct device *dev, + struct platform_power_seq_step *pstep, + struct power_seq_resource *seq); +}; + +static const struct power_seq_res_ops power_seq_types[POWER_SEQ_NUM_TYPES]; + +#ifdef CONFIG_OF +static int of_power_seq_parse_enable_properties(struct device *dev, + struct device_node *node, + struct platform_power_seq *seq, + unsigned int step_nbr, + bool *enable) +{ + if (of_find_property(node, "enable", NULL)) { + *enable = true; + } else if (of_find_property(node, "disable", NULL)) { + *enable = false; + } else { + power_seq_err(dev, seq, step_nbr, + "missing enable or disable property\n"); + return -EINVAL; + } + + return 0; +} + +static int of_power_seq_parse_step(struct device *dev, struct device_node *node, + struct platform_power_seq *seq, + unsigned int step_nbr) +{ + struct platform_power_seq_step *step = &seq->steps[step_nbr]; + const char *type; + int i, err; + + err = of_property_read_string(node, "type", &type); + if (err < 0) { + power_seq_err(dev, seq, step_nbr, + "cannot read type property\n"); + return err; + } + for (i = 0; i < POWER_SEQ_NUM_TYPES; i++) { + if (power_seq_types[i].name == NULL) + continue; + if (!strcmp(type, power_seq_types[i].name)) + break; + } + if (i >= POWER_SEQ_NUM_TYPES) { + power_seq_err(dev, seq, step_nbr, "unknown type %s\n", type); + return -EINVAL; + } + step->type = i; + err = power_seq_types[step->type].of_parse(dev, node, seq, step_nbr); + + return err; +} + +static struct platform_power_seq *of_parse_power_seq(struct device *dev, + struct device_node *node) +{ + struct device_node *child = NULL; + struct platform_power_seq *pseq; + int num_steps, sz; + int err; + + if (!node) + return ERR_PTR(-EINVAL); + + num_steps = of_get_child_count(node); + sz = sizeof(*pseq) + sizeof(pseq->steps[0]) * num_steps; + pseq = devm_kzalloc(dev, sz, GFP_KERNEL); + if (!pseq) + return ERR_PTR(-ENOMEM); + pseq->num_steps = num_steps; + pseq->id = node->name; + + for_each_child_of_node(node, child) { + unsigned int pos; + + /* Check that the name's format is correct and within bounds */ + if (strncmp("step", child->name, 4)) { + err = -EINVAL; + goto parse_error; + } + + err = kstrtouint(child->name + 4, 10, &pos); + if (err < 0) + goto parse_error; + + if (pos >= num_steps || pseq->steps[pos].type != 0) { + err = -EINVAL; + goto parse_error; + } + + err = of_power_seq_parse_step(dev, child, pseq, pos); + if (err) + return ERR_PTR(err); + } + + return pseq; + +parse_error: + dev_err(dev, "%s: invalid power step name %s!\n", pseq->id, + child->name); + return ERR_PTR(err); +} + +/* + * build power sequences platform data from a device tree node. + * + * Sequences must be contained into a subnode named "power-sequences" of the + * device root node. + * + * Memory for the platform sequence is allocated using devm_kzalloc on dev and + * can be freed by devm_kfree after power_seq_set_build returned. Beware that on + * top of the set itself, platform data for individual sequences should also be + * freed. + * + * Returns the built power sequence set on success, or an error code in case of + * failure. + */ +static +struct platform_power_seq_set *devm_of_parse_power_seq_set(struct device *dev) +{ + struct platform_power_seq_set *seqs; + struct device_node *root = dev->of_node; + struct device_node *seq; + int num_seqs, sz, i = 0; + + if (!root) + return NULL; + + root = of_find_node_by_name(root, "power-sequences"); + if (!root) + return NULL; + + num_seqs = of_get_child_count(root); + sz = sizeof(*seqs) + sizeof(seqs->seqs[0]) * num_seqs; + seqs = devm_kzalloc(dev, sz, GFP_KERNEL); + if (!seqs) + return ERR_PTR(-ENOMEM); + seqs->num_seqs = num_seqs; + + for_each_child_of_node(root, seq) { + struct platform_power_seq *pseq; + + pseq = of_parse_power_seq(dev, seq); + if (IS_ERR(pseq)) + return (void *)pseq; + + seqs->seqs[i++] = pseq; + } + + return seqs; +} +#endif /* CONFIG_OF */ + +static struct power_seq_resource * +power_seq_find_resource(struct list_head *ress, + struct platform_power_seq_step *step) +{ + struct power_seq_resource *res; + + list_for_each_entry(res, ress, list) { + if (res->type != step->type) + continue; + + if (power_seq_types[res->type].res_compare(res, step)) + return res; + } + + return NULL; +} + +static struct power_seq *power_seq_build_one(struct device *dev, + struct power_seq_set *seqs, + struct platform_power_seq *pseq) +{ + struct power_seq *seq; + struct power_seq_resource *res; + int i, err; + + seq = devm_kzalloc(dev, sizeof(*seq) + sizeof(seq->steps[0]) * + pseq->num_steps, GFP_KERNEL); + if (!seq) + return ERR_PTR(-ENOMEM); + + INIT_LIST_HEAD(&seq->list); + seq->parent_set = seqs; + seq->num_steps = pseq->num_steps; + seq->id = pseq->id; + + for (i = 0; i < seq->num_steps; i++) { + struct platform_power_seq_step *pstep = &pseq->steps[i]; + struct power_seq_step *step = &seq->steps[i]; + + if (pstep->type >= POWER_SEQ_NUM_TYPES || + power_seq_types[pstep->type].name == NULL) { + power_seq_err(dev, seq, i, + "invalid power sequence type %d!", + pstep->type); + return ERR_PTR(-EINVAL); + } + + memcpy(&step->pdata, pstep, sizeof(step->pdata)); + + /* Steps without resource need not to continue */ + if (!power_seq_types[pstep->type].need_resource) + continue; + + /* create resource node if not referenced already */ + res = power_seq_find_resource(&seqs->resources, pstep); + if (!res) { + res = devm_kzalloc(dev, sizeof(*res), GFP_KERNEL); + if (!res) + return ERR_PTR(-ENOMEM); + + res->type = step->pdata.type; + + err = power_seq_types[res->type].res_alloc(dev, + &step->pdata, res); + if (err < 0) + return ERR_PTR(err); + + list_add_tail(&res->list, &seqs->resources); + } + step->resource = res; + } + + return seq; +} + +/** + * power_seq_set_build() - build a set of runnable sequences from platform data or device tree + * @dev: Device that will use the power sequences. All resources will be + * devm-allocated against it + * @pseq: Platform data for the power sequences. It can be freed after + * this function returns. If this parameter is null, power + * sequences are looked up in the device tree. + * + * All memory and resources (regulators, GPIOs, etc.) are allocated using devm + * functions. + * + * Returns the built sequence on success, an error code in case or failure, + * and NULL if neither platform data or device tree nodes are defined. + */ +struct power_seq_set *devm_power_seq_set_build(struct device *dev, + struct platform_power_seq_set *pseq) +{ + struct power_seq_set *seqs; + int i; + bool use_dt = false; + + if (!pseq) { + use_dt = true; + pseq = devm_of_parse_power_seq_set(dev); + } + + if (!pseq) + return NULL; + + seqs = devm_kzalloc(dev, sizeof(*seqs), GFP_KERNEL); + + if (!seqs) + return ERR_PTR(-ENOMEM); + + INIT_LIST_HEAD(&seqs->resources); + INIT_LIST_HEAD(&seqs->sequences); + for (i = 0; i < pseq->num_seqs; i++) { + struct power_seq *seq; + + seq = power_seq_build_one(dev, seqs, pseq->seqs[i]); + if (IS_ERR(seq)) + return (void *)seq; + + list_add_tail(&seq->list, &seqs->sequences); + } + + /* if we used the DT, free the temporarily built platform data */ + if (use_dt) { + for (i = 0; i < pseq->num_seqs; i++) + devm_kfree(dev, pseq->seqs[i]); + + devm_kfree(dev, pseq); + } + + return seqs; +} +EXPORT_SYMBOL_GPL(devm_power_seq_set_build); + +/** + * power_seq_lookup - Lookup a power sequence by name from a set + * @seqs: The set to look in + * @id: Name to look after + * + * Returns a matching power sequence if it exists, NULL if it does not. + */ +struct power_seq *power_seq_lookup(struct power_seq_set *seqs, const char *id) +{ + struct power_seq *seq; + + list_for_each_entry(seq, &seqs->sequences, list) { + if (!strcmp(seq->id, id)) + return seq; + } + + return NULL; +} +EXPORT_SYMBOL_GPL(power_seq_lookup); + +/** + * power_seq_set_resources - return a list of all the resources used by a set + * @seqs: Power sequences set we are interested in getting the resources + * + * The returned list can be parsed using the power_seq_for_each_resource macro. + */ +struct list_head *power_seq_set_resources(struct power_seq_set *seqs) +{ + return &seqs->resources; +} +EXPORT_SYMBOL_GPL(power_seq_set_resources); + +/** + * power_seq_run() - run a power sequence + * @seq: The power sequence to run + * + * Returns 0 on success, error code in case of failure. + */ +int power_seq_run(struct power_seq *seq) +{ + unsigned int i; + int err; + + if (!seq) + return 0; + + for (i = 0; i < seq->num_steps; i++) { + unsigned int type = seq->steps[i].pdata.type; + + err = power_seq_types[type].step_run(&seq->steps[i]); + if (err) { + power_seq_err(seq->parent_set->dev, seq, i, + "error %d while running power sequence step\n", + err); + return err; + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(power_seq_run); + +#include "power_seq_delay.c" +#include "power_seq_regulator.c" +#include "power_seq_pwm.c" +#include "power_seq_gpio.c" + +static const struct power_seq_res_ops power_seq_types[POWER_SEQ_NUM_TYPES] = { + [POWER_SEQ_DELAY] = POWER_SEQ_DELAY_TYPE, + [POWER_SEQ_REGULATOR] = POWER_SEQ_REGULATOR_TYPE, + [POWER_SEQ_PWM] = POWER_SEQ_PWM_TYPE, + [POWER_SEQ_GPIO] = POWER_SEQ_GPIO_TYPE, +}; + +MODULE_AUTHOR("Alexandre Courbot <acourbot@xxxxxxxxxx>"); +MODULE_DESCRIPTION("Runtime Interpreted Power Sequences"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/power_seq/power_seq_delay.c b/drivers/power/power_seq/power_seq_delay.c new file mode 100644 index 0000000..ae94131 --- /dev/null +++ b/drivers/power/power_seq/power_seq_delay.c @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2012 NVIDIA Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + +#include <linux/delay.h> + +#ifdef CONFIG_OF +static int of_power_seq_parse_delay(struct device *dev, + struct device_node *node, + struct platform_power_seq *seq, + unsigned int step_nbr) +{ + struct platform_power_seq_step *step = &seq->steps[step_nbr]; + int err; + + err = of_property_read_u32(node, "delay", + &step->delay.delay); + if (err < 0) + power_seq_err(dev, seq, step_nbr, + "error reading delay property\n"); + + return err; +} +#else +#define of_power_seq_parse_delay NULL +#endif + +static int power_seq_step_run_delay(struct power_seq_step *step) +{ + usleep_range(step->pdata.delay.delay, + step->pdata.delay.delay + 1000); + + return 0; +} + +#define POWER_SEQ_DELAY_TYPE { \ + .name = "delay", \ + .need_resource = false, \ + .of_parse = of_power_seq_parse_delay, \ + .step_run = power_seq_step_run_delay, \ +} diff --git a/drivers/power/power_seq/power_seq_gpio.c b/drivers/power/power_seq/power_seq_gpio.c new file mode 100644 index 0000000..4f83d4c --- /dev/null +++ b/drivers/power/power_seq/power_seq_gpio.c @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2012 NVIDIA Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + +#include <linux/gpio.h> +#include <linux/of_gpio.h> + +#ifdef CONFIG_OF +static int power_seq_of_parse_gpio(struct device *dev, + struct device_node *node, + struct platform_power_seq *seq, + unsigned int step_nbr) +{ + struct platform_power_seq_step *step = &seq->steps[step_nbr]; + int gpio; + int err; + + gpio = of_get_named_gpio(node, "gpio", 0); + if (gpio < 0) { + power_seq_err(dev, seq, step_nbr, + "error reading gpio property\n"); + return gpio; + } + step->gpio.gpio = gpio; + + err = of_property_read_u32(node, "value", &step->gpio.value); + if (err < 0) { + power_seq_err(dev, seq, step_nbr, + "error reading value property\n"); + } else if (step->gpio.value < 0 || step->gpio.value > 1) { + power_seq_err(dev, seq, step_nbr, + "value out of range (must be 0 or 1)\n"); + err = -EINVAL; + } + + return err; +} +#else +#define of_power_seq_parse_gpio NULL +#endif + +static bool power_seq_res_compare_gpio(struct power_seq_resource *res, + struct platform_power_seq_step *step) +{ + return res->gpio.gpio == step->gpio.gpio; +} + +static int power_seq_res_alloc_gpio(struct device *dev, + struct platform_power_seq_step *pstep, + struct power_seq_resource *res) +{ + int err; + + err = devm_gpio_request_one(dev, pstep->gpio.gpio, + GPIOF_OUT_INIT_LOW, dev_name(dev)); + if (err) { + dev_err(dev, "cannot get gpio %d\n", pstep->gpio.gpio); + return err; + } + + res->gpio.gpio = pstep->gpio.gpio; + + return 0; +} + +static int power_seq_step_run_gpio(struct power_seq_step *step) +{ + gpio_set_value_cansleep(step->resource->gpio.gpio, + step->pdata.gpio.value); + + return 0; +} + +#define POWER_SEQ_GPIO_TYPE { \ + .name = "gpio", \ + .need_resource = true, \ + .of_parse = power_seq_of_parse_gpio, \ + .step_run = power_seq_step_run_gpio, \ + .res_compare = power_seq_res_compare_gpio, \ + .res_alloc = power_seq_res_alloc_gpio, \ +} diff --git a/drivers/power/power_seq/power_seq_pwm.c b/drivers/power/power_seq/power_seq_pwm.c new file mode 100644 index 0000000..14f21e1 --- /dev/null +++ b/drivers/power/power_seq/power_seq_pwm.c @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2012 NVIDIA Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + +#ifdef CONFIG_PWM + +#include <linux/pwm.h> + +#ifdef CONFIG_OF +static int power_seq_of_parse_pwm(struct device *dev, + struct device_node *node, + struct platform_power_seq *seq, + unsigned int step_nbr) +{ + struct platform_power_seq_step *step = &seq->steps[step_nbr]; + int err; + + err = of_property_read_string(node, "id", + &step->pwm.id); + if (err) { + power_seq_err(dev, seq, step_nbr, + "error reading id property\n"); + return err; + } + + err = of_power_seq_parse_enable_properties(dev, node, seq, step_nbr, + &step->pwm.enable); + return err; +} +#else +#define of_power_seq_parse_pwm NULL +#endif + +static bool power_seq_res_compare_pwm(struct power_seq_resource *res, + struct platform_power_seq_step *step) +{ + return !strcmp(res->pwm.id, step->pwm.id); +} + +static int power_seq_res_alloc_pwm(struct device *dev, + struct platform_power_seq_step *pstep, + struct power_seq_resource *res) +{ + res->pwm.pwm = devm_pwm_get(dev, pstep->pwm.id); + if (IS_ERR(res->pwm.pwm)) { + dev_err(dev, "cannot get pwm \"%s\"\n", pstep->pwm.id); + return PTR_ERR(res->pwm.pwm); + } + res->pwm.id = pstep->pwm.id; + + return 0; +} + +static int power_seq_step_run_pwm(struct power_seq_step *step) +{ + if (step->pdata.pwm.enable) { + return pwm_enable(step->resource->pwm.pwm); + } else { + pwm_disable(step->resource->pwm.pwm); + return 0; + } +} + +#define POWER_SEQ_PWM_TYPE { \ + .name = "pwm", \ + .need_resource = true, \ + .of_parse = power_seq_of_parse_pwm, \ + .step_run = power_seq_step_run_pwm, \ + .res_compare = power_seq_res_compare_pwm, \ + .res_alloc = power_seq_res_alloc_pwm, \ +} + +#else + +#define POWER_SEQ_PWM_TYPE {} + +#endif diff --git a/drivers/power/power_seq/power_seq_regulator.c b/drivers/power/power_seq/power_seq_regulator.c new file mode 100644 index 0000000..2356578 --- /dev/null +++ b/drivers/power/power_seq/power_seq_regulator.c @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2012 NVIDIA Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + +#ifdef CONFIG_REGULATOR + +#include <linux/regulator/consumer.h> + +#ifdef CONFIG_OF +static int power_seq_of_parse_regulator(struct device *dev, + struct device_node *node, + struct platform_power_seq *seq, + unsigned int step_nbr) +{ + struct platform_power_seq_step *step = &seq->steps[step_nbr]; + int err; + + err = of_property_read_string(node, "id", + &step->regulator.id); + if (err) { + power_seq_err(dev, seq, step_nbr, + "error reading id property\n"); + return err; + } + + err = of_power_seq_parse_enable_properties(dev, node, seq, step_nbr, + &step->regulator.enable); + return err; +} +#else +#define of_power_seq_parse_regulator NULL +#endif + +static bool +power_seq_res_compare_regulator(struct power_seq_resource *res, + struct platform_power_seq_step *step) +{ + return !strcmp(res->regulator.id, step->regulator.id); +} + +static int power_seq_res_alloc_regulator(struct device *dev, + struct platform_power_seq_step *pstep, + struct power_seq_resource *res) +{ + res->regulator.regulator = devm_regulator_get(dev, pstep->regulator.id); + if (IS_ERR(res->regulator.regulator)) { + dev_err(dev, "cannot get regulator \"%s\"\n", + pstep->regulator.id); + return PTR_ERR(res->regulator.regulator); + } + res->regulator.id = pstep->regulator.id; + + return 0; +} + +static int power_seq_step_run_regulator(struct power_seq_step *step) +{ + if (step->pdata.regulator.enable) + return regulator_enable(step->resource->regulator.regulator); + else + return regulator_disable(step->resource->regulator.regulator); +} + +#define POWER_SEQ_REGULATOR_TYPE { \ + .name = "regulator", \ + .need_resource = true, \ + .of_parse = power_seq_of_parse_regulator, \ + .step_run = power_seq_step_run_regulator, \ + .res_compare = power_seq_res_compare_regulator, \ + .res_alloc = power_seq_res_alloc_regulator, \ +} + +#else + +#define POWER_SEQ_REGULATOR_TYPE {} + +#endif diff --git a/include/linux/power_seq.h b/include/linux/power_seq.h new file mode 100644 index 0000000..437a61a --- /dev/null +++ b/include/linux/power_seq.h @@ -0,0 +1,172 @@ +/* + * power_seq.h + * + * Simple interpreter for defining power sequences as platform data or device + * tree properties. + * + * Power sequences are designed to replace the callbacks typically used in + * board-specific files that implement board-specific power sequences of devices + * such as backlights. A power sequence is an array of resources (which can a + * regulator, a GPIO, a PWM, ...) with an action to perform on it (enable or + * disable) and optional pre and post step delays. By having them interpreted + * instead of arbitrarily executed, it is possible to describe these in the + * device tree and thus remove board-specific code from the kernel. + * + * Author: Alexandre Courbot <acourbot@xxxxxxxxxx> + * + * Copyright (c) 2012 NVIDIA Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + +#ifndef __LINUX_POWER_SEQ_H +#define __LINUX_POWER_SEQ_H + +#include <linux/types.h> +#include <net/irda/parameters.h> + +struct device; +struct regulator; +struct pwm_device; +struct device_node; + +/** + * The different kinds of resources that can be controlled during the sequences. + */ +enum power_seq_res_type { + POWER_SEQ_DELAY, + POWER_SEQ_REGULATOR, + POWER_SEQ_PWM, + POWER_SEQ_GPIO, + POWER_SEQ_NUM_TYPES, +}; + +/** + * struct platform_power_seq_delay_step - platform data for delay steps + * @delay: Amount of time to wait, in microseconds. + */ +struct platform_power_seq_delay_step { + unsigned int delay; +}; + +/** + * struct platform_power_seq_regulator_step - platform data for regulator steps + * @id: Name of the regulator to use. The regulator will be obtained + * using devm_regulator_get(dev, name) + * @enable: Whether to enable or disable the regulator during this step + */ +struct platform_power_seq_regulator_step { + const char *id; + bool enable; +}; + +/** + * struct platform_power_seq_pwm_step - platform data for PWM steps + * @id: Name of the pwm to use. The PWM will be obtained using + * devm_pwm_get(dev, name) + * @enable: Whether to enable or disable the PWM during this step + */ +struct platform_power_seq_pwm_step { + const char *id; + bool enable; +}; + +/** + * struct platform_power_seq_gpio_step - platform data for GPIO steps + * @gpio: Number of the GPIO to use. The GPIO will be obtained using + * devm_gpio_request_one(dev, number) + * @enable: Whether to enable or disable the GPIO during this step + */ +struct platform_power_seq_gpio_step { + int gpio; + int value; +}; + +/** + * struct platform_power_seq_step - platform data for power sequences steps + * @type: The type of this step. This decides which member of the union is + * valid for this step. + * @delay: Used if @type == POWER_SEQ_DELAY + * @regulator: Used if @type == POWER_SEQ_REGULATOR + * @pwm: Used if @type == POWER_SEQ_PWN + * @gpio: Used if @type == POWER_SEQ_GPIO + */ +struct platform_power_seq_step { + enum power_seq_res_type type; + union { + struct platform_power_seq_delay_step delay; + struct platform_power_seq_regulator_step regulator; + struct platform_power_seq_pwm_step pwm; + struct platform_power_seq_gpio_step gpio; + }; +}; + +/** + * struct platform_power_seq - platform data for power sequences + * @id: Name through which this sequence is refered + * @num_steps: Number of steps in that sequence + * @steps: Array of num_steps steps describing the sequence + */ +struct platform_power_seq { + const char *id; + unsigned int num_steps; + struct platform_power_seq_step steps[]; +}; + +/** + * struct platform_power_seq_set - platform data for sets of sequences + * @num_seqs: Number of sequences in this set + * @seqs: Array of pointers to individual sequences + */ +struct platform_power_seq_set { + unsigned int num_seqs; + struct platform_power_seq *seqs[]; +}; + +/** + * struct power_seq_resource - resource used by a power sequence set + * @pdata: Pointer to the platform data used to resolve this resource + * @regulator: Resolved regulator if of type POWER_SEQ_REGULATOR + * @pwm: Resolved PWM if of type POWER_SEQ_PWM + * @list: Used to link resources together + */ +struct power_seq_resource { + enum power_seq_res_type type; + /* resolved resource and identifier */ + union { + struct { + struct regulator *regulator; + const char *id; + } regulator; + struct { + struct pwm_device *pwm; + const char *id; + } pwm; + struct { + int gpio; + } gpio; + }; + struct list_head list; +}; +#define power_seq_for_each_resource(pos, seqs) \ + list_for_each_entry(pos, power_seq_set_resources(seqs), list) + +struct power_seq_resource; +struct power_seq; +struct power_seq_set; + +struct power_seq_set *devm_power_seq_set_build(struct device *dev, + struct platform_power_seq_set *pseq); +struct list_head *power_seq_set_resources(struct power_seq_set *seqs); +struct power_seq *power_seq_lookup(struct power_seq_set *seqs, const char *id); +int power_seq_run(struct power_seq *seq); + +#endif -- 1.7.12 -- To unsubscribe from this list: send the line "unsubscribe linux-doc" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html