From 0c57067430a2b729bc08c92b17eb4f29d9bbfaae Mon Sep 17 00:00:00 2001 From: Laxman Dewangan Date: Sat, 6 Oct 2012 20:47:46 +0530 Subject: regulator: tps51632: Add tps51632 regulator driver The TPS51632 is a driverless step down controller with serial control. Advanced features such as D-Cap+ architecture with overlapping pulse support and OSR overshoot reduction provide fast transient response, lowest output capacitance and high efficiency. The TPS51632 supports both I2C and DVFS interfaces (through PWM) for dynamic control of the output voltage and current monitor telemetry. Add regulator driver for TPS51632. Signed-off-by: Laxman Dewangan Signed-off-by: Mark Brown --- drivers/regulator/Kconfig | 11 ++ drivers/regulator/Makefile | 1 + drivers/regulator/tps51632-regulator.c | 332 +++++++++++++++++++++++++++++++++ 3 files changed, 344 insertions(+) create mode 100644 drivers/regulator/tps51632-regulator.c (limited to 'drivers/regulator') diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index 67d47b59a66d..aa9e8a18262d 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -335,6 +335,17 @@ config REGULATOR_PALMAS on the muxing. This is handled automatically in the driver by reading the mux info from OTP. +config REGULATOR_TPS51632 + tristate "TI TPS51632 Power Regulator" + depends on I2C + select REGMAP_I2C + help + This driver supports TPS51632 voltage regulator chip. + The TPS52632 is 3-2-1 Phase D-Cap+ Step Down Driverless Controller + with Serial VID control and DVFS. + The voltage output can be configure through I2C interface or PWM + interface. + config REGULATOR_TPS6105X tristate "TI TPS6105X Power regulators" depends on TPS6105X diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index e431eed8a878..ec1aec460bf6 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -41,6 +41,7 @@ obj-$(CONFIG_REGULATOR_MC13783) += mc13783-regulator.o obj-$(CONFIG_REGULATOR_MC13892) += mc13892-regulator.o obj-$(CONFIG_REGULATOR_MC13XXX_CORE) += mc13xxx-regulator-core.o obj-$(CONFIG_REGULATOR_PALMAS) += palmas-regulator.o +obj-$(CONFIG_REGULATOR_TPS51632) += tps51632-regulator.o obj-$(CONFIG_REGULATOR_PCAP) += pcap-regulator.o obj-$(CONFIG_REGULATOR_PCF50633) += pcf50633-regulator.o obj-$(CONFIG_REGULATOR_RC5T583) += rc5t583-regulator.o diff --git a/drivers/regulator/tps51632-regulator.c b/drivers/regulator/tps51632-regulator.c new file mode 100644 index 000000000000..34603640d6d9 --- /dev/null +++ b/drivers/regulator/tps51632-regulator.c @@ -0,0 +1,332 @@ +/* + * tps51632-regulator.c -- TI TPS51632 + * + * Regulator driver for TPS51632 3-2-1 Phase D-Cap Step Down Driverless + * Controller with serial VID control and DVFS. + * + * Copyright (c) 2012, NVIDIA Corporation. + * + * Author: Laxman Dewangan + * + * 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. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any kind, + * whether express or implied; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307, USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Register definitions */ +#define TPS51632_VOLTAGE_SELECT_REG 0x0 +#define TPS51632_VOLTAGE_BASE_REG 0x1 +#define TPS51632_OFFSET_REG 0x2 +#define TPS51632_IMON_REG 0x3 +#define TPS51632_VMAX_REG 0x4 +#define TPS51632_DVFS_CONTROL_REG 0x5 +#define TPS51632_POWER_STATE_REG 0x6 +#define TPS51632_SLEW_REGS 0x7 +#define TPS51632_FAULT_REG 0x14 + +#define TPS51632_MAX_REG 0x15 + +#define TPS51632_VOUT_MASK 0x7F +#define TPS51632_VOUT_OFFSET_MASK 0x1F +#define TPS51632_VMAX_MASK 0x7F +#define TPS51632_VMAX_LOCK 0x80 + +/* TPS51632_DVFS_CONTROL_REG */ +#define TPS51632_DVFS_PWMEN 0x1 +#define TPS51632_DVFS_STEP_20 0x2 +#define TPS51632_DVFS_VMAX_PG 0x4 +#define TPS51632_DVFS_PWMRST 0x8 +#define TPS51632_DVFS_OCA_EN 0x10 +#define TPS51632_DVFS_FCCM 0x20 + +/* TPS51632_POWER_STATE_REG */ +#define TPS51632_POWER_STATE_MASK 0x03 +#define TPS51632_POWER_STATE_MULTI_PHASE_CCM 0x0 +#define TPS51632_POWER_STATE_SINGLE_PHASE_CCM 0x1 +#define TPS51632_POWER_STATE_SINGLE_PHASE_DCM 0x2 + +#define TPS51632_MIN_VOLATGE 500000 +#define TPS51632_MAX_VOLATGE 1520000 +#define TPS51632_VOLATGE_STEP_10mV 10000 +#define TPS51632_VOLATGE_STEP_20mV 20000 +#define TPS51632_MAX_VSEL 0x7F +#define TPS51632_MIN_VSEL 0x19 +#define TPS51632_DEFAULT_RAMP_DELAY 6000 +#define TPS51632_VOLT_VSEL(uV) \ + (DIV_ROUND_UP(uV - TPS51632_MIN_VOLATGE, \ + TPS51632_VOLATGE_STEP_10mV) + \ + TPS51632_MIN_VSEL) + +/* TPS51632 chip information */ +struct tps51632_chip { + struct device *dev; + struct regulator_desc desc; + struct regulator_dev *rdev; + struct regmap *regmap; + bool enable_pwm_dvfs; +}; + +static int tps51632_dcdc_get_voltage_sel(struct regulator_dev *rdev) +{ + struct tps51632_chip *tps = rdev_get_drvdata(rdev); + unsigned int data; + int ret; + unsigned int reg = TPS51632_VOLTAGE_SELECT_REG; + int vsel; + + if (tps->enable_pwm_dvfs) + reg = TPS51632_VOLTAGE_BASE_REG; + + ret = regmap_read(tps->regmap, reg, &data); + if (ret < 0) { + dev_err(tps->dev, "reg read failed, err %d\n", ret); + return ret; + } + + vsel = data & TPS51632_VOUT_MASK; + + if (vsel < TPS51632_MIN_VSEL) + return 0; + else + return vsel - TPS51632_MIN_VSEL; +} + +static int tps51632_dcdc_set_voltage_sel(struct regulator_dev *rdev, + unsigned selector) +{ + struct tps51632_chip *tps = rdev_get_drvdata(rdev); + int vsel; + int ret; + unsigned int reg = TPS51632_VOLTAGE_SELECT_REG; + + if (tps->enable_pwm_dvfs) + reg = TPS51632_VOLTAGE_BASE_REG; + + vsel = selector + TPS51632_MIN_VSEL; + if (vsel > TPS51632_MAX_VSEL) + return -EINVAL; + + ret = regmap_write(tps->regmap, TPS51632_VOLTAGE_SELECT_REG, vsel); + if (ret < 0) + dev_err(tps->dev, "reg write failed, err %d\n", ret); + return ret; +} + +static int tps51632_dcdc_set_ramp_delay(struct regulator_dev *rdev, + int ramp_delay) +{ + struct tps51632_chip *tps = rdev_get_drvdata(rdev); + int bit = ramp_delay/6000; + int ret; + + if (bit) + bit--; + ret = regmap_write(tps->regmap, TPS51632_SLEW_REGS, BIT(bit)); + if (ret < 0) + dev_err(tps->dev, "SLEW reg write failed, err %d\n", ret); + return ret; +} + +static struct regulator_ops tps51632_dcdc_ops = { + .get_voltage_sel = tps51632_dcdc_get_voltage_sel, + .set_voltage_sel = tps51632_dcdc_set_voltage_sel, + .list_voltage = regulator_list_voltage_linear, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .set_ramp_delay = tps51632_dcdc_set_ramp_delay, +}; + +static int __devinit tps51632_init_dcdc(struct tps51632_chip *tps, + struct tps51632_regulator_platform_data *pdata) +{ + int ret; + uint8_t control = 0; + int vsel; + + if (!pdata->enable_pwm_dvfs) + goto skip_pwm_config; + + control |= TPS51632_DVFS_PWMEN; + tps->enable_pwm_dvfs = pdata->enable_pwm_dvfs; + vsel = TPS51632_VOLT_VSEL(pdata->base_voltage_uV); + ret = regmap_write(tps->regmap, TPS51632_VOLTAGE_BASE_REG, vsel); + if (ret < 0) { + dev_err(tps->dev, "BASE reg write failed, err %d\n", ret); + return ret; + } + + if (pdata->dvfs_step_20mV) + control |= TPS51632_DVFS_STEP_20; + + if (pdata->max_voltage_uV) { + unsigned int vmax; + /** + * TPS51632 hw behavior: VMAX register can be write only + * once as it get locked after first write. The lock get + * reset only when device is power-reset. + * Write register only when lock bit is not enabled. + */ + ret = regmap_read(tps->regmap, TPS51632_VMAX_REG, &vmax); + if (ret < 0) { + dev_err(tps->dev, "VMAX read failed, err %d\n", ret); + return ret; + } + if (!(vmax & TPS51632_VMAX_LOCK)) { + vsel = TPS51632_VOLT_VSEL(pdata->max_voltage_uV); + ret = regmap_write(tps->regmap, TPS51632_VMAX_REG, + vsel); + if (ret < 0) { + dev_err(tps->dev, + "VMAX write failed, err %d\n", ret); + return ret; + } + } + } + +skip_pwm_config: + ret = regmap_write(tps->regmap, TPS51632_DVFS_CONTROL_REG, control); + if (ret < 0) + dev_err(tps->dev, "DVFS reg write failed, err %d\n", ret); + return ret; +} + +static bool rd_wr_reg(struct device *dev, unsigned int reg) +{ + if ((reg >= 0x8) && (reg <= 0x10)) + return false; + return true; +} + +static const struct regmap_config tps51632_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .writeable_reg = rd_wr_reg, + .readable_reg = rd_wr_reg, + .max_register = TPS51632_MAX_REG - 1, + .cache_type = REGCACHE_RBTREE, +}; + +static int __devinit tps51632_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct tps51632_regulator_platform_data *pdata; + struct regulator_dev *rdev; + struct tps51632_chip *tps; + int ret; + struct regulator_config config = { }; + + pdata = client->dev.platform_data; + if (!pdata) { + dev_err(&client->dev, "No Platform data\n"); + return -EINVAL; + } + + tps = devm_kzalloc(&client->dev, sizeof(*tps), GFP_KERNEL); + if (!tps) { + dev_err(&client->dev, "Memory allocation failed\n"); + return -ENOMEM; + } + + tps->dev = &client->dev; + tps->desc.name = id->name; + tps->desc.id = 0; + tps->desc.ramp_delay = TPS51632_DEFAULT_RAMP_DELAY; + tps->desc.min_uV = TPS51632_MIN_VOLATGE; + tps->desc.uV_step = TPS51632_VOLATGE_STEP_10mV; + tps->desc.n_voltages = (TPS51632_MAX_VSEL - TPS51632_MIN_VSEL) + 1; + tps->desc.ops = &tps51632_dcdc_ops; + tps->desc.type = REGULATOR_VOLTAGE; + tps->desc.owner = THIS_MODULE; + + tps->regmap = devm_regmap_init_i2c(client, &tps51632_regmap_config); + if (IS_ERR(tps->regmap)) { + ret = PTR_ERR(tps->regmap); + dev_err(&client->dev, "regmap init failed, err %d\n", ret); + return ret; + } + i2c_set_clientdata(client, tps); + + ret = tps51632_init_dcdc(tps, pdata); + if (ret < 0) { + dev_err(tps->dev, "Init failed, err = %d\n", ret); + return ret; + } + + /* Register the regulators */ + config.dev = &client->dev; + config.init_data = pdata->reg_init_data; + config.driver_data = tps; + config.regmap = tps->regmap; + config.of_node = client->dev.of_node; + + rdev = regulator_register(&tps->desc, &config); + if (IS_ERR(rdev)) { + dev_err(tps->dev, "regulator register failed\n"); + return PTR_ERR(rdev); + } + + tps->rdev = rdev; + return 0; +} + +static int __devexit tps51632_remove(struct i2c_client *client) +{ + struct tps51632_chip *tps = i2c_get_clientdata(client); + + regulator_unregister(tps->rdev); + return 0; +} + +static const struct i2c_device_id tps51632_id[] = { + {.name = "tps51632",}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, tps51632_id); + +static struct i2c_driver tps51632_i2c_driver = { + .driver = { + .name = "tps51632", + .owner = THIS_MODULE, + }, + .probe = tps51632_probe, + .remove = __devexit_p(tps51632_remove), + .id_table = tps51632_id, +}; + +static int __init tps51632_init(void) +{ + return i2c_add_driver(&tps51632_i2c_driver); +} +subsys_initcall(tps51632_init); + +static void __exit tps51632_cleanup(void) +{ + i2c_del_driver(&tps51632_i2c_driver); +} +module_exit(tps51632_cleanup); + +MODULE_AUTHOR("Laxman Dewangan "); +MODULE_DESCRIPTION("TPS51632 voltage regulator driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3-55-g7522 From 7bde76726fa296defc0eafc602284a7676817348 Mon Sep 17 00:00:00 2001 From: Axel Lin Date: Thu, 1 Nov 2012 14:02:36 +0800 Subject: regulator: tps51632: Fix trivial typo for TPS51632 Kconfig help text Signed-off-by: Axel Lin Acked-by: Laxman Dewangan Signed-off-by: Mark Brown --- drivers/regulator/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/regulator') diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index aa9e8a18262d..2fd5808bf668 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -341,7 +341,7 @@ config REGULATOR_TPS51632 select REGMAP_I2C help This driver supports TPS51632 voltage regulator chip. - The TPS52632 is 3-2-1 Phase D-Cap+ Step Down Driverless Controller + The TPS51632 is 3-2-1 Phase D-Cap+ Step Down Driverless Controller with Serial VID control and DVFS. The voltage output can be configure through I2C interface or PWM interface. -- cgit v1.2.3-55-g7522 From f6130be652d0b4fbf710d83a816298c007e59ed1 Mon Sep 17 00:00:00 2001 From: Ashish Jangam Date: Thu, 1 Nov 2012 13:57:56 +0530 Subject: regulator: DA9055 regulator driver This is the Regulator patch for the DA9055 PMIC and has got dependency on the DA9055 MFD core. This patch support all of the DA9055 regulators. The output voltages are fully programmable through I2C interface only. The platform data with regulation constraints is passed down from the board to the regulator. This patch is functionaly tested on SMDK6410 board. DA9055 Evaluation board was connected to the SMDK6410 board. Signed-off-by: David Dajun Chen Signed-off-by: Ashish Jangam Signed-off-by: Mark Brown --- drivers/regulator/Kconfig | 10 + drivers/regulator/Makefile | 1 + drivers/regulator/da9055-regulator.c | 663 +++++++++++++++++++++++++++++++++++ include/linux/mfd/da9055/pdata.h | 27 +- 4 files changed, 698 insertions(+), 3 deletions(-) create mode 100644 drivers/regulator/da9055-regulator.c (limited to 'drivers/regulator') diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index 67d47b59a66d..a162573d4944 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -109,6 +109,16 @@ config REGULATOR_DA9052 This driver supports the voltage regulators of DA9052-BC and DA9053-AA/Bx PMIC. +config REGULATOR_DA9055 + tristate "Dialog Semiconductor DA9055 regulators" + depends on MFD_DA9055 + help + Say y here to support the BUCKs and LDOs regulators found on + Dialog Semiconductor DA9055 PMIC. + + This driver can also be built as a module. If so, the module + will be called da9055-regulator. + config REGULATOR_FAN53555 tristate "Fairchild FAN53555 Regulator" depends on I2C diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index e431eed8a878..4b754e958aed 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -18,6 +18,7 @@ obj-$(CONFIG_REGULATOR_ANATOP) += anatop-regulator.o obj-$(CONFIG_REGULATOR_ARIZONA) += arizona-micsupp.o arizona-ldo1.o obj-$(CONFIG_REGULATOR_DA903X) += da903x.o obj-$(CONFIG_REGULATOR_DA9052) += da9052-regulator.o +obj-$(CONFIG_REGULATOR_DA9055) += da9055-regulator.o obj-$(CONFIG_REGULATOR_DBX500_PRCMU) += dbx500-prcmu.o obj-$(CONFIG_REGULATOR_DB8500_PRCMU) += db8500-prcmu.o obj-$(CONFIG_REGULATOR_FAN53555) += fan53555.o diff --git a/drivers/regulator/da9055-regulator.c b/drivers/regulator/da9055-regulator.c new file mode 100644 index 000000000000..8994178b371c --- /dev/null +++ b/drivers/regulator/da9055-regulator.c @@ -0,0 +1,663 @@ +/* +* Regulator driver for DA9055 PMIC +* +* Copyright(c) 2012 Dialog Semiconductor Ltd. +* +* Author: David Dajun Chen +* +* 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; either version 2 of the License, or +* (at your option) any later version. +* +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define DA9055_MIN_UA 0 +#define DA9055_MAX_UA 3 + +#define DA9055_LDO_MODE_SYNC 0 +#define DA9055_LDO_MODE_SLEEP 1 + +#define DA9055_BUCK_MODE_SLEEP 1 +#define DA9055_BUCK_MODE_SYNC 2 +#define DA9055_BUCK_MODE_AUTO 3 + +/* DA9055 REGULATOR IDs */ +#define DA9055_ID_BUCK1 0 +#define DA9055_ID_BUCK2 1 +#define DA9055_ID_LDO1 2 +#define DA9055_ID_LDO2 3 +#define DA9055_ID_LDO3 4 +#define DA9055_ID_LDO4 5 +#define DA9055_ID_LDO5 6 +#define DA9055_ID_LDO6 7 + +/* DA9055 BUCK current limit */ +static const int da9055_current_limits[] = { 500000, 600000, 700000, 800000 }; + +struct da9055_conf_reg { + int reg; + int sel_mask; + int en_mask; +}; + +struct da9055_volt_reg { + int reg_a; + int reg_b; + int sl_shift; + int v_offset; + int v_mask; + int v_shift; +}; + +struct da9055_mode_reg { + int reg; + int mask; + int shift; +}; + +struct da9055_regulator_info { + struct regulator_desc reg_desc; + struct da9055_conf_reg conf; + struct da9055_volt_reg volt; + struct da9055_mode_reg mode; +}; + +struct da9055_regulator { + struct da9055 *da9055; + struct da9055_regulator_info *info; + struct regulator_dev *rdev; + enum gpio_select reg_rselect; +}; + +static unsigned int da9055_buck_get_mode(struct regulator_dev *rdev) +{ + struct da9055_regulator *regulator = rdev_get_drvdata(rdev); + struct da9055_regulator_info *info = regulator->info; + int ret, mode = 0; + + ret = da9055_reg_read(regulator->da9055, info->mode.reg); + if (ret < 0) + return ret; + + switch ((ret & info->mode.mask) >> info->mode.shift) { + case DA9055_BUCK_MODE_SYNC: + mode = REGULATOR_MODE_FAST; + break; + case DA9055_BUCK_MODE_AUTO: + mode = REGULATOR_MODE_NORMAL; + break; + case DA9055_BUCK_MODE_SLEEP: + mode = REGULATOR_MODE_STANDBY; + break; + } + + return mode; +} + +static int da9055_buck_set_mode(struct regulator_dev *rdev, + unsigned int mode) +{ + struct da9055_regulator *regulator = rdev_get_drvdata(rdev); + struct da9055_regulator_info *info = regulator->info; + int val = 0; + + switch (mode) { + case REGULATOR_MODE_FAST: + val = DA9055_BUCK_MODE_SYNC << info->mode.shift; + break; + case REGULATOR_MODE_NORMAL: + val = DA9055_BUCK_MODE_AUTO << info->mode.shift; + break; + case REGULATOR_MODE_STANDBY: + val = DA9055_BUCK_MODE_SLEEP << info->mode.shift; + break; + } + + return da9055_reg_update(regulator->da9055, info->mode.reg, + info->mode.mask, val); +} + +static unsigned int da9055_ldo_get_mode(struct regulator_dev *rdev) +{ + struct da9055_regulator *regulator = rdev_get_drvdata(rdev); + struct da9055_regulator_info *info = regulator->info; + int ret; + + ret = da9055_reg_read(regulator->da9055, info->volt.reg_b); + if (ret < 0) + return ret; + + if (ret >> info->volt.sl_shift) + return REGULATOR_MODE_STANDBY; + else + return REGULATOR_MODE_NORMAL; +} + +static int da9055_ldo_set_mode(struct regulator_dev *rdev, unsigned int mode) +{ + struct da9055_regulator *regulator = rdev_get_drvdata(rdev); + struct da9055_regulator_info *info = regulator->info; + struct da9055_volt_reg volt = info->volt; + int val = 0; + + switch (mode) { + case REGULATOR_MODE_NORMAL: + case REGULATOR_MODE_FAST: + val = DA9055_LDO_MODE_SYNC; + break; + case REGULATOR_MODE_STANDBY: + val = DA9055_LDO_MODE_SLEEP; + break; + } + + return da9055_reg_update(regulator->da9055, volt.reg_b, + 1 << volt.sl_shift, + val << volt.sl_shift); +} + +static int da9055_buck_get_current_limit(struct regulator_dev *rdev) +{ + struct da9055_regulator *regulator = rdev_get_drvdata(rdev); + struct da9055_regulator_info *info = regulator->info; + int ret; + + ret = da9055_reg_read(regulator->da9055, DA9055_REG_BUCK_LIM); + if (ret < 0) + return ret; + + ret &= info->mode.mask; + return da9055_current_limits[ret >> info->mode.shift]; +} + +static int da9055_buck_set_current_limit(struct regulator_dev *rdev, int min_uA, + int max_uA) +{ + struct da9055_regulator *regulator = rdev_get_drvdata(rdev); + struct da9055_regulator_info *info = regulator->info; + int i, val = 0; + + if (min_uA > da9055_current_limits[DA9055_MAX_UA] || + max_uA < da9055_current_limits[DA9055_MIN_UA]) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(da9055_current_limits); i++) { + if (min_uA <= da9055_current_limits[i]) { + val = i; + break; + } + } + + return da9055_reg_update(regulator->da9055, DA9055_REG_BUCK_LIM, + info->mode.mask, val << info->mode.shift); +} + +static int da9055_regulator_get_voltage_sel(struct regulator_dev *rdev) +{ + struct da9055_regulator *regulator = rdev_get_drvdata(rdev); + struct da9055_regulator_info *info = regulator->info; + struct da9055_volt_reg volt = info->volt; + int ret, sel; + + /* + * There are two voltage register set A & B for voltage ramping but + * either one of then can be active therefore we first determine + * the active register set. + */ + ret = da9055_reg_read(regulator->da9055, info->conf.reg); + if (ret < 0) + return ret; + + ret &= info->conf.sel_mask; + + /* Get the voltage for the active register set A/B */ + if (ret == DA9055_REGUALTOR_SET_A) + ret = da9055_reg_read(regulator->da9055, volt.reg_a); + else + ret = da9055_reg_read(regulator->da9055, volt.reg_b); + + if (ret < 0) + return ret; + + sel = (ret & volt.v_mask); + if (sel <= volt.v_offset) + return 0; + else + return sel; +} + +static int da9055_regulator_set_voltage_bits(struct regulator_dev *rdev, + unsigned int reg, + unsigned int selector) +{ + struct da9055_regulator *regulator = rdev_get_drvdata(rdev); + struct da9055_regulator_info *info = regulator->info; + + /* Takes care of voltage range that does not start with 0 offset. */ + selector += info->volt.v_offset; + + /* Set the voltage */ + return da9055_reg_update(regulator->da9055, reg, info->volt.v_mask, + selector); + +} + +static int da9055_regulator_set_voltage_sel(struct regulator_dev *rdev, + unsigned int selector) +{ + struct da9055_regulator *regulator = rdev_get_drvdata(rdev); + struct da9055_regulator_info *info = regulator->info; + int ret; + + /* + * Regulator register set A/B is not selected through GPIO therefore + * we use default register set A for voltage ramping. + */ + if (regulator->reg_rselect == NO_GPIO) { + /* Select register set A */ + ret = da9055_reg_update(regulator->da9055, info->conf.reg, + info->conf.sel_mask, DA9055_SEL_REG_A); + if (ret < 0) + return ret; + + /* Set the voltage */ + return da9055_regulator_set_voltage_bits(rdev, info->volt.reg_a, + selector); + } + + /* + * Here regulator register set A/B is selected through GPIO. + * Therefore we first determine the selected register set A/B and + * then set the desired voltage for that register set A/B. + */ + ret = da9055_reg_read(regulator->da9055, info->conf.reg); + if (ret < 0) + return ret; + + ret &= info->conf.sel_mask; + + /* Set the voltage */ + if (ret == DA9055_REGUALTOR_SET_A) + return da9055_regulator_set_voltage_bits(rdev, info->volt.reg_a, + selector); + else + return da9055_regulator_set_voltage_bits(rdev, info->volt.reg_b, + selector); +} + +static int da9055_regulator_set_suspend_voltage(struct regulator_dev *rdev, + int uV) +{ + struct da9055_regulator *regulator = rdev_get_drvdata(rdev); + struct da9055_regulator_info *info = regulator->info; + int ret; + + /* Select register set B for suspend voltage ramping. */ + if (regulator->reg_rselect == NO_GPIO) { + ret = da9055_reg_update(regulator->da9055, info->conf.reg, + info->conf.sel_mask, DA9055_SEL_REG_B); + if (ret < 0) + return ret; + } + + ret = regulator_map_voltage_linear(rdev, uV, uV); + if (ret < 0) + return ret; + + return da9055_regulator_set_voltage_bits(rdev, info->volt.reg_b, ret); +} + +static int da9055_suspend_enable(struct regulator_dev *rdev) +{ + struct da9055_regulator *regulator = rdev_get_drvdata(rdev); + struct da9055_regulator_info *info = regulator->info; + + /* Select register set B for voltage ramping. */ + if (regulator->reg_rselect == NO_GPIO) + return da9055_reg_update(regulator->da9055, info->conf.reg, + info->conf.sel_mask, DA9055_SEL_REG_B); + else + return 0; +} + +static int da9055_suspend_disable(struct regulator_dev *rdev) +{ + struct da9055_regulator *regulator = rdev_get_drvdata(rdev); + struct da9055_regulator_info *info = regulator->info; + + /* Diselect register set B. */ + if (regulator->reg_rselect == NO_GPIO) + return da9055_reg_update(regulator->da9055, info->conf.reg, + info->conf.sel_mask, DA9055_SEL_REG_A); + else + return 0; +} + +static struct regulator_ops da9055_buck_ops = { + .get_mode = da9055_buck_get_mode, + .set_mode = da9055_buck_set_mode, + + .get_current_limit = da9055_buck_get_current_limit, + .set_current_limit = da9055_buck_set_current_limit, + + .get_voltage_sel = da9055_regulator_get_voltage_sel, + .set_voltage_sel = da9055_regulator_set_voltage_sel, + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + + .set_suspend_voltage = da9055_regulator_set_suspend_voltage, + .set_suspend_enable = da9055_suspend_enable, + .set_suspend_disable = da9055_suspend_disable, + .set_suspend_mode = da9055_buck_set_mode, +}; + +static struct regulator_ops da9055_ldo_ops = { + .get_mode = da9055_ldo_get_mode, + .set_mode = da9055_ldo_set_mode, + + .get_voltage_sel = da9055_regulator_get_voltage_sel, + .set_voltage_sel = da9055_regulator_set_voltage_sel, + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + + .set_suspend_voltage = da9055_regulator_set_suspend_voltage, + .set_suspend_enable = da9055_suspend_enable, + .set_suspend_disable = da9055_suspend_disable, + .set_suspend_mode = da9055_ldo_set_mode, + +}; + +#define DA9055_LDO(_id, step, min, max, vbits, voffset) \ +{\ + .reg_desc = {\ + .name = #_id,\ + .ops = &da9055_ldo_ops,\ + .type = REGULATOR_VOLTAGE,\ + .id = DA9055_ID_##_id,\ + .n_voltages = (max - min) / step + 1, \ + .enable_reg = DA9055_REG_BCORE_CONT + DA9055_ID_##_id, \ + .enable_mask = 1, \ + .min_uV = (min) * 1000,\ + .uV_step = (step) * 1000,\ + .owner = THIS_MODULE,\ + },\ + .conf = {\ + .reg = DA9055_REG_BCORE_CONT + DA9055_ID_##_id, \ + .sel_mask = (1 << 4),\ + .en_mask = 1,\ + },\ + .volt = {\ + .reg_a = DA9055_REG_VBCORE_A + DA9055_ID_##_id, \ + .reg_b = DA9055_REG_VBCORE_B + DA9055_ID_##_id, \ + .sl_shift = 7,\ + .v_offset = (voffset),\ + .v_mask = (1 << (vbits)) - 1,\ + .v_shift = (vbits),\ + },\ +} + +#define DA9055_BUCK(_id, step, min, max, vbits, voffset, mbits, sbits) \ +{\ + .reg_desc = {\ + .name = #_id,\ + .ops = &da9055_buck_ops,\ + .type = REGULATOR_VOLTAGE,\ + .id = DA9055_ID_##_id,\ + .n_voltages = (max - min) / step + 1, \ + .enable_reg = DA9055_REG_BCORE_CONT + DA9055_ID_##_id, \ + .enable_mask = 1,\ + .min_uV = (min) * 1000,\ + .uV_step = (step) * 1000,\ + .owner = THIS_MODULE,\ + },\ + .conf = {\ + .reg = DA9055_REG_BCORE_CONT + DA9055_ID_##_id, \ + .sel_mask = (1 << 4),\ + .en_mask = 1,\ + },\ + .volt = {\ + .reg_a = DA9055_REG_VBCORE_A + DA9055_ID_##_id, \ + .reg_b = DA9055_REG_VBCORE_B + DA9055_ID_##_id, \ + .sl_shift = 7,\ + .v_offset = (voffset),\ + .v_mask = (1 << (vbits)) - 1,\ + .v_shift = (vbits),\ + },\ + .mode = {\ + .reg = DA9055_REG_BCORE_MODE,\ + .mask = (mbits),\ + .shift = (sbits),\ + },\ +} + +static struct da9055_regulator_info da9055_regulator_info[] = { + DA9055_BUCK(BUCK1, 25, 725, 2075, 6, 9, 0xc, 2), + DA9055_BUCK(BUCK2, 25, 925, 2500, 6, 0, 3, 0), + DA9055_LDO(LDO1, 50, 900, 3300, 6, 2), + DA9055_LDO(LDO2, 50, 900, 3300, 6, 3), + DA9055_LDO(LDO3, 50, 900, 3300, 6, 2), + DA9055_LDO(LDO4, 50, 900, 3300, 6, 2), + DA9055_LDO(LDO5, 50, 900, 2750, 6, 2), + DA9055_LDO(LDO6, 20, 900, 3300, 7, 0), +}; + +/* + * Configures regulator to be controlled either through GPIO 1 or 2. + * GPIO can control regulator state and/or select the regulator register + * set A/B for voltage ramping. + */ +static __devinit int da9055_gpio_init(struct da9055_regulator *regulator, + struct regulator_config *config, + struct da9055_pdata *pdata, int id) +{ + struct da9055_regulator_info *info = regulator->info; + int ret = 0; + + if (pdata->gpio_ren && pdata->gpio_ren[id]) { + char name[18]; + int gpio_mux = pdata->gpio_ren[id]; + + config->ena_gpio = pdata->ena_gpio[id]; + config->ena_gpio_flags = GPIOF_OUT_INIT_HIGH; + config->ena_gpio_invert = 1; + + /* + * GPI pin is muxed with regulator to control the + * regulator state. + */ + sprintf(name, "DA9055 GPI %d", gpio_mux); + ret = devm_gpio_request_one(config->dev, gpio_mux, GPIOF_DIR_IN, + name); + if (ret < 0) + goto err; + + /* + * Let the regulator know that its state is controlled + * through GPI. + */ + ret = da9055_reg_update(regulator->da9055, info->conf.reg, + DA9055_E_GPI_MASK, + pdata->reg_ren[id] + << DA9055_E_GPI_SHIFT); + if (ret < 0) + goto err; + } + + if (pdata->gpio_rsel && pdata->gpio_ren[id]) { + char name[18]; + int gpio_mux = pdata->gpio_rsel[id]; + + regulator->reg_rselect = pdata->reg_rsel[id]; + + /* + * GPI pin is muxed with regulator to select the + * regulator register set A/B for voltage ramping. + */ + sprintf(name, "DA9055 GPI %d", gpio_mux); + ret = devm_gpio_request_one(config->dev, gpio_mux, GPIOF_DIR_IN, + name); + if (ret < 0) + goto err; + + /* + * Let the regulator know that its register set A/B + * will be selected through GPI for voltage ramping. + */ + ret = da9055_reg_update(regulator->da9055, info->conf.reg, + DA9055_V_GPI_MASK, + pdata->reg_rsel[id] + << DA9055_V_GPI_SHIFT); + } + +err: + return ret; +} + +static irqreturn_t da9055_ldo5_6_oc_irq(int irq, void *data) +{ + struct da9055_regulator *regulator = data; + + regulator_notifier_call_chain(regulator->rdev, + REGULATOR_EVENT_OVER_CURRENT, NULL); + + return IRQ_HANDLED; +} + +static inline struct da9055_regulator_info *find_regulator_info(int id) +{ + struct da9055_regulator_info *info; + int i; + + for (i = 0; i < ARRAY_SIZE(da9055_regulator_info); i++) { + info = &da9055_regulator_info[i]; + if (info->reg_desc.id == id) + return info; + } + + return NULL; +} + +static int __devinit da9055_regulator_probe(struct platform_device *pdev) +{ + struct regulator_config config = { }; + struct da9055_regulator *regulator; + struct da9055 *da9055 = dev_get_drvdata(pdev->dev.parent); + struct da9055_pdata *pdata = da9055->dev->platform_data; + int ret, irq; + + if (pdata == NULL || pdata->regulators[pdev->id] == NULL) + return -ENODEV; + + regulator = devm_kzalloc(&pdev->dev, sizeof(struct da9055_regulator), + GFP_KERNEL); + if (!regulator) + return -ENOMEM; + + regulator->info = find_regulator_info(pdev->id); + if (regulator->info == NULL) { + dev_err(&pdev->dev, "invalid regulator ID specified\n"); + return -EINVAL; + } + + regulator->da9055 = da9055; + config.dev = &pdev->dev; + config.driver_data = regulator; + config.regmap = da9055->regmap; + + if (pdata && pdata->regulators) + config.init_data = pdata->regulators[pdev->id]; + + ret = da9055_gpio_init(regulator, &config, pdata, pdev->id); + if (ret < 0) + return ret; + + regulator->rdev = regulator_register(®ulator->info->reg_desc, + &config); + if (IS_ERR(regulator->rdev)) { + dev_err(&pdev->dev, "Failed to register regulator %s\n", + regulator->info->reg_desc.name); + ret = PTR_ERR(regulator->rdev); + return ret; + } + + /* Only LDO 5 and 6 has got the over current interrupt */ + if (pdev->id == DA9055_ID_LDO5 || pdev->id == DA9055_ID_LDO6) { + irq = platform_get_irq_byname(pdev, "REGULATOR"); + irq = regmap_irq_get_virq(da9055->irq_data, irq); + ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, + da9055_ldo5_6_oc_irq, + IRQF_TRIGGER_HIGH | + IRQF_ONESHOT | + IRQF_PROBE_SHARED, + pdev->name, regulator); + if (ret != 0) { + if (ret != -EBUSY) { + dev_err(&pdev->dev, + "Failed to request Regulator IRQ %d: %d\n", + irq, ret); + goto err_regulator; + } + } + } + + platform_set_drvdata(pdev, regulator); + + return 0; + +err_regulator: + regulator_unregister(regulator->rdev); + return ret; +} + +static int __devexit da9055_regulator_remove(struct platform_device *pdev) +{ + struct da9055_regulator *regulator = platform_get_drvdata(pdev); + + regulator_unregister(regulator->rdev); + + return 0; +} + +static struct platform_driver da9055_regulator_driver = { + .probe = da9055_regulator_probe, + .remove = __devexit_p(da9055_regulator_remove), + .driver = { + .name = "da9055-regulator", + .owner = THIS_MODULE, + }, +}; + +static int __init da9055_regulator_init(void) +{ + return platform_driver_register(&da9055_regulator_driver); +} +subsys_initcall(da9055_regulator_init); + +static void __exit da9055_regulator_exit(void) +{ + platform_driver_unregister(&da9055_regulator_driver); +} +module_exit(da9055_regulator_exit); + +MODULE_AUTHOR("David Dajun Chen "); +MODULE_DESCRIPTION("Power Regulator driver for Dialog DA9055 PMIC"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:da9055-regulator"); diff --git a/include/linux/mfd/da9055/pdata.h b/include/linux/mfd/da9055/pdata.h index 147293b4471d..f87a6c172a91 100644 --- a/include/linux/mfd/da9055/pdata.h +++ b/include/linux/mfd/da9055/pdata.h @@ -25,8 +25,29 @@ struct da9055_pdata { int gpio_base; struct regulator_init_data *regulators[DA9055_MAX_REGULATORS]; - bool reset_enable; /* Enable RTC in RESET Mode */ - enum gpio_select *gpio_rsel; /* Select regulator set thru GPIO 1/2 */ - enum gpio_select *gpio_ren; /* Enable regulator thru GPIO 1/2 */ + /* Enable RTC in RESET Mode */ + bool reset_enable; + /* + * GPI muxed pin to control + * regulator state A/B, 0 if not available. + */ + int *gpio_ren; + /* + * GPI muxed pin to control + * regulator set, 0 if not available. + */ + int *gpio_rsel; + /* + * Regulator mode control bits value (GPI offset) that + * that controls the regulator state, 0 if not available. + */ + enum gpio_select *reg_ren; + /* + * Regulator mode control bits value (GPI offset) that + * controls the regulator set A/B, 0 if not available. + */ + enum gpio_select *reg_rsel; + /* GPIOs to enable regulator, 0 if not available */ + int *ena_gpio; }; #endif /* __DA9055_PDATA_H */ -- cgit v1.2.3-55-g7522 From 26cbd3073394b966654294c8cb9cb2fec870c567 Mon Sep 17 00:00:00 2001 From: Axel Lin Date: Fri, 16 Nov 2012 00:35:14 +0800 Subject: regulator: da9055: Fix checking wrong value in da9055_gpio_init Check pdata->gpio_rsel && pdata->gpio_rsel[id] for the case GPI pin is muxed with regulator to select the regulator register set A/B for voltage ramping. Signed-off-by: Axel Lin Signed-off-by: Mark Brown --- drivers/regulator/da9055-regulator.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/regulator') diff --git a/drivers/regulator/da9055-regulator.c b/drivers/regulator/da9055-regulator.c index 8994178b371c..79c56655b9b3 100644 --- a/drivers/regulator/da9055-regulator.c +++ b/drivers/regulator/da9055-regulator.c @@ -501,7 +501,7 @@ static __devinit int da9055_gpio_init(struct da9055_regulator *regulator, goto err; } - if (pdata->gpio_rsel && pdata->gpio_ren[id]) { + if (pdata->gpio_rsel && pdata->gpio_rsel[id]) { char name[18]; int gpio_mux = pdata->gpio_rsel[id]; -- cgit v1.2.3-55-g7522 From 369cf602f364a60c72b8258c21eca8793939328f Mon Sep 17 00:00:00 2001 From: Axel Lin Date: Tue, 20 Nov 2012 15:36:46 +0800 Subject: regulator: da9055: Properly handle voltage range that doesn't start with 0 offset This patch implements map_voltage and list_voltage callbacks to properly handle the case voltage range that doesn't start with 0 offset. Now we adjust the selector in map_voltage() before calling set_voltage_sel(). And return 0 in list_voltage() for invalid selectors. With above change, we can remove da9055_regulator_set_voltage_bits function. One tricky part is that we need adding voffset to n_voltages. Although for the cases "selector < voffset" are invalid, we need add voffset to n_voltage so regulator_list_voltage() won't fail while checking the boundary for selector before calling list_voltage callback. Signed-off-by: Axel Lin Tested-by: Ashish Jangam Signed-off-by: Mark Brown --- drivers/regulator/da9055-regulator.c | 80 ++++++++++++++++++++++-------------- 1 file changed, 50 insertions(+), 30 deletions(-) (limited to 'drivers/regulator') diff --git a/drivers/regulator/da9055-regulator.c b/drivers/regulator/da9055-regulator.c index 79c56655b9b3..121564bb5415 100644 --- a/drivers/regulator/da9055-regulator.c +++ b/drivers/regulator/da9055-regulator.c @@ -204,6 +204,41 @@ static int da9055_buck_set_current_limit(struct regulator_dev *rdev, int min_uA, info->mode.mask, val << info->mode.shift); } +static int da9055_list_voltage(struct regulator_dev *rdev, unsigned selector) +{ + struct da9055_regulator *regulator = rdev_get_drvdata(rdev); + struct da9055_regulator_info *info = regulator->info; + + if (selector >= rdev->desc->n_voltages) + return -EINVAL; + + if (selector < info->volt.v_offset) + return 0; + + selector -= info->volt.v_offset; + return rdev->desc->min_uV + (rdev->desc->uV_step * selector); +} + +static int da9055_map_voltage(struct regulator_dev *rdev, int min_uV, + int max_uV) +{ + struct da9055_regulator *regulator = rdev_get_drvdata(rdev); + struct da9055_regulator_info *info = regulator->info; + int sel, voltage; + + if (min_uV < rdev->desc->min_uV) + min_uV = rdev->desc->min_uV; + + sel = DIV_ROUND_UP(min_uV - rdev->desc->min_uV, rdev->desc->uV_step); + sel += info->volt.v_offset; + + voltage = da9055_list_voltage(rdev, sel); + if (voltage < min_uV || voltage > max_uV) + return -EINVAL; + + return sel; +} + static int da9055_regulator_get_voltage_sel(struct regulator_dev *rdev) { struct da9055_regulator *regulator = rdev_get_drvdata(rdev); @@ -238,22 +273,6 @@ static int da9055_regulator_get_voltage_sel(struct regulator_dev *rdev) return sel; } -static int da9055_regulator_set_voltage_bits(struct regulator_dev *rdev, - unsigned int reg, - unsigned int selector) -{ - struct da9055_regulator *regulator = rdev_get_drvdata(rdev); - struct da9055_regulator_info *info = regulator->info; - - /* Takes care of voltage range that does not start with 0 offset. */ - selector += info->volt.v_offset; - - /* Set the voltage */ - return da9055_reg_update(regulator->da9055, reg, info->volt.v_mask, - selector); - -} - static int da9055_regulator_set_voltage_sel(struct regulator_dev *rdev, unsigned int selector) { @@ -273,8 +292,8 @@ static int da9055_regulator_set_voltage_sel(struct regulator_dev *rdev, return ret; /* Set the voltage */ - return da9055_regulator_set_voltage_bits(rdev, info->volt.reg_a, - selector); + return da9055_reg_update(regulator->da9055, info->volt.reg_a, + info->volt.v_mask, selector); } /* @@ -290,11 +309,11 @@ static int da9055_regulator_set_voltage_sel(struct regulator_dev *rdev, /* Set the voltage */ if (ret == DA9055_REGUALTOR_SET_A) - return da9055_regulator_set_voltage_bits(rdev, info->volt.reg_a, - selector); + return da9055_reg_update(regulator->da9055, info->volt.reg_a, + info->volt.v_mask, selector); else - return da9055_regulator_set_voltage_bits(rdev, info->volt.reg_b, - selector); + return da9055_reg_update(regulator->da9055, info->volt.reg_b, + info->volt.v_mask, selector); } static int da9055_regulator_set_suspend_voltage(struct regulator_dev *rdev, @@ -312,11 +331,12 @@ static int da9055_regulator_set_suspend_voltage(struct regulator_dev *rdev, return ret; } - ret = regulator_map_voltage_linear(rdev, uV, uV); + ret = da9055_map_voltage(rdev, uV, uV); if (ret < 0) return ret; - return da9055_regulator_set_voltage_bits(rdev, info->volt.reg_b, ret); + return da9055_reg_update(regulator->da9055, info->volt.reg_b, + info->volt.v_mask, ret); } static int da9055_suspend_enable(struct regulator_dev *rdev) @@ -354,8 +374,8 @@ static struct regulator_ops da9055_buck_ops = { .get_voltage_sel = da9055_regulator_get_voltage_sel, .set_voltage_sel = da9055_regulator_set_voltage_sel, - .list_voltage = regulator_list_voltage_linear, - .map_voltage = regulator_map_voltage_linear, + .list_voltage = da9055_list_voltage, + .map_voltage = da9055_map_voltage, .is_enabled = regulator_is_enabled_regmap, .enable = regulator_enable_regmap, .disable = regulator_disable_regmap, @@ -372,8 +392,8 @@ static struct regulator_ops da9055_ldo_ops = { .get_voltage_sel = da9055_regulator_get_voltage_sel, .set_voltage_sel = da9055_regulator_set_voltage_sel, - .list_voltage = regulator_list_voltage_linear, - .map_voltage = regulator_map_voltage_linear, + .list_voltage = da9055_list_voltage, + .map_voltage = da9055_map_voltage, .is_enabled = regulator_is_enabled_regmap, .enable = regulator_enable_regmap, .disable = regulator_disable_regmap, @@ -392,7 +412,7 @@ static struct regulator_ops da9055_ldo_ops = { .ops = &da9055_ldo_ops,\ .type = REGULATOR_VOLTAGE,\ .id = DA9055_ID_##_id,\ - .n_voltages = (max - min) / step + 1, \ + .n_voltages = (max - min) / step + 1 + (voffset), \ .enable_reg = DA9055_REG_BCORE_CONT + DA9055_ID_##_id, \ .enable_mask = 1, \ .min_uV = (min) * 1000,\ @@ -421,7 +441,7 @@ static struct regulator_ops da9055_ldo_ops = { .ops = &da9055_buck_ops,\ .type = REGULATOR_VOLTAGE,\ .id = DA9055_ID_##_id,\ - .n_voltages = (max - min) / step + 1, \ + .n_voltages = (max - min) / step + 1 + (voffset), \ .enable_reg = DA9055_REG_BCORE_CONT + DA9055_ID_##_id, \ .enable_mask = 1,\ .min_uV = (min) * 1000,\ -- cgit v1.2.3-55-g7522