From f59c8f9fe689790248ae7aa7426579982050638c Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 31 Aug 2012 10:36:37 -0700 Subject: regulator: core: Support bypass mode Many regulators support a bypass mode where they simply switch their input supply to the output. This is mainly used in low power retention states where power consumption is extremely low so higher voltage or less clean supplies can be used. Support this by providing ops for the drivers and a consumer API which allows the device to be put into bypass mode if all consumers enable it and the machine enables permission for this. This is not supported as a mode since the existing modes are rarely used due to fuzzy definition and mostly redundant with modern hardware which is able to respond promptly to load changes. Signed-off-by: Mark Brown Reviewed-by: Graeme Gregory --- Documentation/ABI/testing/sysfs-class-regulator | 21 ++++++ drivers/regulator/core.c | 85 +++++++++++++++++++++++++ include/linux/regulator/consumer.h | 8 +++ include/linux/regulator/driver.h | 10 +++ include/linux/regulator/machine.h | 2 + 5 files changed, 126 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-class-regulator b/Documentation/ABI/testing/sysfs-class-regulator index e091fa8..bc578bc 100644 --- a/Documentation/ABI/testing/sysfs-class-regulator +++ b/Documentation/ABI/testing/sysfs-class-regulator @@ -349,3 +349,24 @@ Description: This will be one of the same strings reported by the "state" attribute. + +What: /sys/class/regulator/.../bypass +Date: September 2012 +KernelVersion: 3.7 +Contact: Mark Brown +Description: + Some regulator directories will contain a field called + bypass. This indicates if the device is in bypass mode. + + This will be one of the following strings: + + 'enabled' + 'disabled' + 'unknown' + + 'enabled' means the regulator is in bypass mode. + + 'disabled' means that the regulator is regulating. + + 'unknown' means software cannot determine the state, or + the reported state is invalid. diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index 4838531..64e1605 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -77,6 +77,7 @@ struct regulator { struct device *dev; struct list_head list; unsigned int always_on:1; + unsigned int bypass:1; int uA_load; int min_uV; int max_uV; @@ -394,6 +395,9 @@ static ssize_t regulator_status_show(struct device *dev, case REGULATOR_STATUS_STANDBY: label = "standby"; break; + case REGULATOR_STATUS_BYPASS: + label = "bypass"; + break; case REGULATOR_STATUS_UNDEFINED: label = "undefined"; break; @@ -585,6 +589,27 @@ static ssize_t regulator_suspend_standby_state_show(struct device *dev, static DEVICE_ATTR(suspend_standby_state, 0444, regulator_suspend_standby_state_show, NULL); +static ssize_t regulator_bypass_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct regulator_dev *rdev = dev_get_drvdata(dev); + const char *report; + bool bypass; + int ret; + + ret = rdev->desc->ops->get_bypass(rdev, &bypass); + + if (ret != 0) + report = "unknown"; + else if (bypass) + report = "enabled"; + else + report = "disabled"; + + return sprintf(buf, "%s\n", report); +} +static DEVICE_ATTR(bypass, 0444, + regulator_bypass_show, NULL); /* * These are the only attributes are present for all regulators. @@ -2674,6 +2699,59 @@ out: EXPORT_SYMBOL_GPL(regulator_set_optimum_mode); /** + * regulator_allow_bypass - allow the regulator to go into bypass mode + * + * @regulator: Regulator to configure + * @allow: enable or disable bypass mode + * + * Allow the regulator to go into bypass mode if all other consumers + * for the regulator also enable bypass mode and the machine + * constraints allow this. Bypass mode means that the regulator is + * simply passing the input directly to the output with no regulation. + */ +int regulator_allow_bypass(struct regulator *regulator, bool enable) +{ + struct regulator_dev *rdev = regulator->rdev; + int ret = 0; + + if (!rdev->desc->ops->set_bypass) + return 0; + + if (rdev->constraints && + !(rdev->constraints->valid_ops_mask & REGULATOR_CHANGE_BYPASS)) + return 0; + + mutex_lock(&rdev->mutex); + + if (enable && !regulator->bypass) { + rdev->bypass_count++; + + if (rdev->bypass_count == rdev->open_count) { + ret = rdev->desc->ops->set_bypass(rdev, enable); + if (ret != 0) + rdev->bypass_count--; + } + + } else if (!enable && regulator->bypass) { + rdev->bypass_count--; + + if (rdev->bypass_count != rdev->open_count) { + ret = rdev->desc->ops->set_bypass(rdev, enable); + if (ret != 0) + rdev->bypass_count++; + } + } + + if (ret == 0) + regulator->bypass = enable; + + mutex_unlock(&rdev->mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(regulator_allow_bypass); + +/** * regulator_register_notifier - register regulator event notifier * @regulator: regulator source * @nb: notifier block @@ -3036,6 +3114,11 @@ static int add_regulator_attributes(struct regulator_dev *rdev) if (status < 0) return status; } + if (ops->get_bypass) { + status = device_create_file(dev, &dev_attr_bypass); + if (status < 0) + return status; + } /* some attributes are type-specific */ if (rdev->desc->type == REGULATOR_CURRENT) { @@ -3124,6 +3207,8 @@ static void rdev_init_debugfs(struct regulator_dev *rdev) &rdev->use_count); debugfs_create_u32("open_count", 0444, rdev->debugfs, &rdev->open_count); + debugfs_create_u32("bypass_count", 0444, rdev->debugfs, + &rdev->bypass_count); } /** diff --git a/include/linux/regulator/consumer.h b/include/linux/regulator/consumer.h index da339fd..ea3e358 100644 --- a/include/linux/regulator/consumer.h +++ b/include/linux/regulator/consumer.h @@ -177,6 +177,8 @@ int regulator_set_mode(struct regulator *regulator, unsigned int mode); unsigned int regulator_get_mode(struct regulator *regulator); int regulator_set_optimum_mode(struct regulator *regulator, int load_uA); +int regulator_allow_bypass(struct regulator *regulator, bool allow); + /* regulator notifier block */ int regulator_register_notifier(struct regulator *regulator, struct notifier_block *nb); @@ -328,6 +330,12 @@ static inline int regulator_set_optimum_mode(struct regulator *regulator, return REGULATOR_MODE_NORMAL; } +static inline int regulator_allow_bypass(struct regulator *regulator, + bool allow) +{ + return 0; +} + static inline int regulator_register_notifier(struct regulator *regulator, struct notifier_block *nb) { diff --git a/include/linux/regulator/driver.h b/include/linux/regulator/driver.h index bac4c87..c9869cfb 100644 --- a/include/linux/regulator/driver.h +++ b/include/linux/regulator/driver.h @@ -32,6 +32,8 @@ enum regulator_status { REGULATOR_STATUS_NORMAL, REGULATOR_STATUS_IDLE, REGULATOR_STATUS_STANDBY, + /* The regulator is enabled but not regulating */ + REGULATOR_STATUS_BYPASS, /* in case that any other status doesn't apply */ REGULATOR_STATUS_UNDEFINED, }; @@ -67,6 +69,9 @@ enum regulator_status { * @get_optimum_mode: Get the most efficient operating mode for the regulator * when running with the specified parameters. * + * @set_bypass: Set the regulator in bypass mode. + * @get_bypass: Get the regulator bypass mode state. + * * @enable_time: Time taken for the regulator voltage output voltage to * stabilise after being enabled, in microseconds. * @set_ramp_delay: Set the ramp delay for the regulator. The driver should @@ -133,6 +138,10 @@ struct regulator_ops { unsigned int (*get_optimum_mode) (struct regulator_dev *, int input_uV, int output_uV, int load_uA); + /* control and report on bypass mode */ + int (*set_bypass)(struct regulator_dev *dev, bool enable); + int (*get_bypass)(struct regulator_dev *dev, bool *enable); + /* the operations below are for configuration of regulator state when * its parent PMIC enters a global STANDBY/HIBERNATE state */ @@ -253,6 +262,7 @@ struct regulator_dev { int exclusive; u32 use_count; u32 open_count; + u32 bypass_count; /* lists we belong to */ struct list_head list; /* list of all regulators */ diff --git a/include/linux/regulator/machine.h b/include/linux/regulator/machine.h index 40dd0a3..36adbc8 100644 --- a/include/linux/regulator/machine.h +++ b/include/linux/regulator/machine.h @@ -32,6 +32,7 @@ struct regulator; * board/machine. * STATUS: Regulator can be enabled and disabled. * DRMS: Dynamic Regulator Mode Switching is enabled for this regulator. + * BYPASS: Regulator can be put into bypass mode */ #define REGULATOR_CHANGE_VOLTAGE 0x1 @@ -39,6 +40,7 @@ struct regulator; #define REGULATOR_CHANGE_MODE 0x4 #define REGULATOR_CHANGE_STATUS 0x8 #define REGULATOR_CHANGE_DRMS 0x10 +#define REGULATOR_CHANGE_BYPASS 0x20 /** * struct regulator_state - regulator state during low power system states -- cgit v1.1 From df36793115b4f68181877a1c89bac54feadd965d Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 27 Aug 2012 16:04:23 -0700 Subject: regulator: core: Provide regmap get/set bypass operations Signed-off-by: Mark Brown --- drivers/regulator/core.c | 41 ++++++++++++++++++++++++++++++++++++++++ include/linux/regulator/driver.h | 4 ++++ 2 files changed, 45 insertions(+) diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index 64e1605..419805c 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -2699,6 +2699,47 @@ out: EXPORT_SYMBOL_GPL(regulator_set_optimum_mode); /** + * regulator_set_bypass_regmap - Default set_bypass() using regmap + * + * @rdev: device to operate on. + * @enable: state to set. + */ +int regulator_set_bypass_regmap(struct regulator_dev *rdev, bool enable) +{ + unsigned int val; + + if (enable) + val = rdev->desc->bypass_mask; + else + val = 0; + + return regmap_update_bits(rdev->regmap, rdev->desc->bypass_reg, + rdev->desc->bypass_mask, val); +} +EXPORT_SYMBOL_GPL(regulator_set_bypass_regmap); + +/** + * regulator_get_bypass_regmap - Default get_bypass() using regmap + * + * @rdev: device to operate on. + * @enable: current state. + */ +int regulator_get_bypass_regmap(struct regulator_dev *rdev, bool *enable) +{ + unsigned int val; + int ret; + + ret = regmap_read(rdev->regmap, rdev->desc->bypass_reg, &val); + if (ret != 0) + return ret; + + *enable = val & rdev->desc->bypass_mask; + + return 0; +} +EXPORT_SYMBOL_GPL(regulator_get_bypass_regmap); + +/** * regulator_allow_bypass - allow the regulator to go into bypass mode * * @regulator: Regulator to configure diff --git a/include/linux/regulator/driver.h b/include/linux/regulator/driver.h index c9869cfb..7274a46 100644 --- a/include/linux/regulator/driver.h +++ b/include/linux/regulator/driver.h @@ -214,6 +214,8 @@ struct regulator_desc { unsigned int vsel_mask; unsigned int enable_reg; unsigned int enable_mask; + unsigned int bypass_reg; + unsigned int bypass_mask; unsigned int enable_time; }; @@ -320,6 +322,8 @@ int regulator_disable_regmap(struct regulator_dev *rdev); int regulator_set_voltage_time_sel(struct regulator_dev *rdev, unsigned int old_selector, unsigned int new_selector); +int regulator_set_bypass_regmap(struct regulator_dev *rdev, bool enable); +int regulator_get_bypass_regmap(struct regulator_dev *rdev, bool *enable); void *regulator_get_init_drvdata(struct regulator_init_data *reg_init_data); -- cgit v1.1 From 9a17de04ef847f674ffe64eb46b9b99e14e663fa Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 27 Aug 2012 16:04:35 -0700 Subject: regulator: arizona-ldo: Support get/set bypass Signed-off-by: Mark Brown --- drivers/regulator/arizona-ldo1.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/regulator/arizona-ldo1.c b/drivers/regulator/arizona-ldo1.c index c8f95c0..80e012f1 100644 --- a/drivers/regulator/arizona-ldo1.c +++ b/drivers/regulator/arizona-ldo1.c @@ -39,6 +39,8 @@ static struct regulator_ops arizona_ldo1_ops = { .map_voltage = regulator_map_voltage_linear, .get_voltage_sel = regulator_get_voltage_sel_regmap, .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_bypass = regulator_get_bypass_regmap, + .set_bypass = regulator_set_bypass_regmap, }; static const struct regulator_desc arizona_ldo1 = { @@ -49,6 +51,8 @@ static const struct regulator_desc arizona_ldo1 = { .vsel_reg = ARIZONA_LDO1_CONTROL_1, .vsel_mask = ARIZONA_LDO1_VSEL_MASK, + .bypass_reg = ARIZONA_LDO1_CONTROL_1, + .bypass_mask = ARIZONA_LDO1_BYPASS, .min_uV = 900000, .uV_step = 50000, .n_voltages = 7, -- cgit v1.1 From e477ce075d205f9eee3e28795eae7a0efd1e54f6 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 27 Aug 2012 16:04:47 -0700 Subject: regulator: arizona-micsupp: Support get/set bypass Signed-off-by: Mark Brown --- drivers/regulator/arizona-micsupp.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/regulator/arizona-micsupp.c b/drivers/regulator/arizona-micsupp.c index 450a069..d9b1f82 100644 --- a/drivers/regulator/arizona-micsupp.c +++ b/drivers/regulator/arizona-micsupp.c @@ -82,6 +82,9 @@ static struct regulator_ops arizona_micsupp_ops = { .get_voltage_sel = regulator_get_voltage_sel_regmap, .set_voltage_sel = regulator_set_voltage_sel_regmap, + + .get_bypass = regulator_get_bypass_regmap, + .set_bypass = regulator_set_bypass_regmap, }; static const struct regulator_desc arizona_micsupp = { @@ -95,6 +98,8 @@ static const struct regulator_desc arizona_micsupp = { .vsel_mask = ARIZONA_LDO2_VSEL_MASK, .enable_reg = ARIZONA_MIC_CHARGE_PUMP_1, .enable_mask = ARIZONA_CPMIC_ENA, + .bypass_reg = ARIZONA_MIC_CHARGE_PUMP_1, + .bypass_mask = ARIZONA_CPMIC_BYPASS, .owner = THIS_MODULE, }; -- cgit v1.1 From 22c5fb6a7dc8674424394122337c84eaf48efbdf Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 27 Aug 2012 21:52:29 -0700 Subject: regulator: wm831x-ldo: Add bypass support Signed-off-by: Mark Brown --- drivers/regulator/wm831x-ldo.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/regulator/wm831x-ldo.c b/drivers/regulator/wm831x-ldo.c index 5cb70ca..f203a97 100644 --- a/drivers/regulator/wm831x-ldo.c +++ b/drivers/regulator/wm831x-ldo.c @@ -237,6 +237,8 @@ static struct regulator_ops wm831x_gp_ldo_ops = { .set_mode = wm831x_gp_ldo_set_mode, .get_status = wm831x_gp_ldo_get_status, .get_optimum_mode = wm831x_gp_ldo_get_optimum_mode, + .get_bypass = regulator_get_bypass_regmap, + .set_bypass = regulator_set_bypass_regmap, .is_enabled = regulator_is_enabled_regmap, .enable = regulator_enable_regmap, @@ -293,6 +295,8 @@ static __devinit int wm831x_gp_ldo_probe(struct platform_device *pdev) ldo->desc.vsel_mask = WM831X_LDO1_ON_VSEL_MASK; ldo->desc.enable_reg = WM831X_LDO_ENABLE; ldo->desc.enable_mask = 1 << id; + ldo->desc.bypass_reg = ldo->base; + ldo->desc.bypass_mask = WM831X_LDO1_SWI; config.dev = pdev->dev.parent; if (pdata) @@ -488,6 +492,8 @@ static struct regulator_ops wm831x_aldo_ops = { .get_mode = wm831x_aldo_get_mode, .set_mode = wm831x_aldo_set_mode, .get_status = wm831x_aldo_get_status, + .set_bypass = regulator_set_bypass_regmap, + .get_bypass = regulator_get_bypass_regmap, .is_enabled = regulator_is_enabled_regmap, .enable = regulator_enable_regmap, @@ -544,6 +550,8 @@ static __devinit int wm831x_aldo_probe(struct platform_device *pdev) ldo->desc.vsel_mask = WM831X_LDO7_ON_VSEL_MASK; ldo->desc.enable_reg = WM831X_LDO_ENABLE; ldo->desc.enable_mask = 1 << id; + ldo->desc.bypass_reg = ldo->base; + ldo->desc.bypass_mask = WM831X_LDO7_SWI; config.dev = pdev->dev.parent; if (pdata) -- cgit v1.1 From b8575a1143f6d57a791c946291ba4363e07e32a3 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 7 Sep 2012 17:01:15 +0800 Subject: extcon: arizona: Use bypass mode for MICVDD We can perform microphone detection with MICVDD in bypass mode so try to enable that during startup for minimal power - other users or machine constraints will prevent bypass mode being activated if it is unsuitable. Signed-off-by: Mark Brown Acked-by: Greg Kroah-Hartman --- drivers/extcon/extcon-arizona.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index 427a289..6c19833 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -434,6 +434,11 @@ static int __devinit arizona_extcon_probe(struct platform_device *pdev) regmap_update_bits(arizona->regmap, ARIZONA_JACK_DETECT_ANALOGUE, ARIZONA_JD1_ENA, ARIZONA_JD1_ENA); + ret = regulator_allow_bypass(info->micvdd, true); + if (ret != 0) + dev_warn(arizona->dev, "Failed to set MICVDD to bypass: %d\n", + ret); + pm_runtime_put(&pdev->dev); return 0; -- cgit v1.1