diff options
-rw-r--r-- | Documentation/devicetree/bindings/regulator/max20010.txt | 77 | ||||
-rw-r--r-- | drivers/regulator/Kconfig | 9 | ||||
-rw-r--r-- | drivers/regulator/Makefile | 1 | ||||
-rw-r--r-- | drivers/regulator/max20010-regulator.c | 490 | ||||
-rw-r--r-- | include/dt-bindings/regulator/max20010.h | 20 |
5 files changed, 597 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/regulator/max20010.txt b/Documentation/devicetree/bindings/regulator/max20010.txt new file mode 100644 index 000000000000..3dd8f6d1cf19 --- /dev/null +++ b/Documentation/devicetree/bindings/regulator/max20010.txt @@ -0,0 +1,77 @@ +Binding for Maxim MAX20010 regulator + +MAX20010 is a synchronous step-down converter. It is able to deliver upto 6A +with 2 different programmable output voltages from 0.5V to 1.27V in 10mV steps +and from 0.625V to 1.5875V in 12.5mV steps. It supports synchronous +rectification and automatic PWM/PFM transitions. + +The MAX20010 interface is via I2C bus. + +======================= +Supported Properties +======================= + +- compatible + Usage: required + Value type: <string> + Definition: should be "maxim,max20010". + +- reg + Usage: required + Value type: <u32> + Definition: The device 8-bit I2C address. + +- vin-supply + Usage: optional + Value type: <phandle> + Definition: This is the phandle for the parent regulator. Typically used + for EN pin control of the buck. + +- regulator-initial-mode + Usage: optional + Value type: <u32> + Definition: The regulator operating mode. Should be either + "MAX20010_OPMODE_SYNC" or "MAX20010_OPMODE_FPWM". + These constants are defined in file + include/dt-bindings/regulator/max20010.h + +- maxim,vrange-sel + Usage: optional + Value type: <u32> + Definition: Integer value specifies the voltage range to be used. + Supported values are 0 or 1. + Value 0 supports voltage range from 0.5V to 1.27V in 10mV + steps. Value 1 supports voltage range from 0.625V to 1.5875V + in 12.5mV steps. + +- maxim,soft-start-slew-rate + Usage: optional + Value type: <u32> + Definition: An integer value specifies the slew rate in uV/uS to be used + for soft-start operation of the buck. Supported values are + 5500, 11000, 22000 and 44000. + +- maxim,dvs-slew-rate + Usage: optional + Value type: <u32> + Definition: An integer value specifies the slew rate in uV/uS to be used + for buck dynamic voltage scaling operations. Supported + values are 5500, 11000, 22000 and 44000. + +======= +Example +======= + + i2c_0 { + max20010-regulator@74 { + compatible = "maxim,max20010"; + reg = <0x74>; + vin-supply = <&parent_reg>; + regulator-min-microvolt = <600000>; + regulator-max-microvolt = <1270000>; + regulator-initial-mode = <MAX20010_OPMODE_SYNC>; + maxim,vrange-sel = <0>; + maxim,soft-start-slew-rate = <5500>; + maxim,dvs-slew-rate = <5500>; + } + } diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index 88956d3ba674..068c7ddfb739 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -344,6 +344,15 @@ config REGULATOR_MAX1586 regulator via I2C bus. The provided regulator is suitable for PXA27x chips to control VCC_CORE and VCC_USIM voltages. +config REGULATOR_MAX20010 + tristate "Maxim MAX20010 regulator support" + depends on I2C + help + This driver supports the Maxim MAX20010 switching voltage regulator + (buck converter). The regulator is controlled using an I2C interface + and supports 2 programmable voltage ranges from 0.5V to 1.27V in 10mV + steps and 0.625V to 1.5875V in 12.5mV steps. + config REGULATOR_MAX8649 tristate "Maxim 8649 voltage regulator" depends on I2C diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index e345f10f94af..856ebe2b5c1b 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -46,6 +46,7 @@ obj-$(CONFIG_REGULATOR_LP8755) += lp8755.o obj-$(CONFIG_REGULATOR_LTC3589) += ltc3589.o obj-$(CONFIG_REGULATOR_MAX14577) += max14577.o obj-$(CONFIG_REGULATOR_MAX1586) += max1586.o +obj-$(CONFIG_REGULATOR_MAX20010) += max20010-regulator.o obj-$(CONFIG_REGULATOR_MAX8649) += max8649.o obj-$(CONFIG_REGULATOR_MAX8660) += max8660.o obj-$(CONFIG_REGULATOR_MAX8907) += max8907-regulator.o diff --git a/drivers/regulator/max20010-regulator.c b/drivers/regulator/max20010-regulator.c new file mode 100644 index 000000000000..a914ca70ccb7 --- /dev/null +++ b/drivers/regulator/max20010-regulator.c @@ -0,0 +1,490 @@ +/* Copyright (c) 2017 The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * 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/module.h> +#include <linux/param.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/of_regulator.h> +#include <linux/of_device.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/regmap.h> + +struct voltage_range { + int vrange_sel; + int min_uV; + int max_uV; + int step_uV; +}; + +struct max20010_slew_rate { + int slew_sel; + int soft_start; + int dvs; +}; + +struct max20010_device_info { + struct device *dev; + struct regulator_dev *rdev; + struct regulator_init_data *init_data; + struct regmap *regmap; + const struct voltage_range *range; + const struct max20010_slew_rate *slew_rate; + unsigned vout_sel; + bool enabled; +}; + +#define MAX20010_ID_REG 0x00 + +#define MAX20010_VMAX_REG 0x02 +#define MAX20010_VMAX_MASK GENMASK(6, 0) + +#define MAX20010_CONFIG_REG 0x05 +#define MAX20010_CONFIG_SYNC_IO_MASK GENMASK(1, 0) +#define MAX20010_CONFIG_MODE_MASK BIT(3) +#define MAX20010_CONFIG_MODE_SYNC 0 +#define MAX20010_CONFIG_MODE_FPWM 8 +#define MAX20010_CONFIG_VSTEP_MASK BIT(7) +#define MAX20010_CONFIG_VSTEP_SHIFT 7 + +#define MAX20010_SLEW_REG 0x06 +#define MAX20010_SLEW_MASK GENMASK(3, 0) + +#define MAX20010_VSET_REG 0x07 +#define MAX20010_VSET_MASK GENMASK(6, 0) + +static const struct max20010_slew_rate slew_rates[] = { + {0, 22000, 22000}, + {1, 11000, 22000}, + {2, 5500, 22000}, + {3, 11000, 11000}, + {4, 5500, 11000}, + {5, 44000, 44000}, + {6, 22000, 44000}, + {7, 11000, 44000}, + {8, 5500, 44000}, + {9, 5500, 5500}, +}; + +static const struct voltage_range max20010_range0 = {0, 500000, 1270000, 10000}; +static const struct voltage_range max20010_range1 = {1, 625000, 1587500, 12500}; + +static const struct regmap_config max20010_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = MAX20010_VSET_REG, +}; + +static int max20010_set_voltage_sel(struct regulator_dev *rdev, unsigned sel) +{ + struct max20010_device_info *info = rdev_get_drvdata(rdev); + int rc = 0; + + /* Set the voltage only if the regulator was enabled earlier */ + if (info->enabled) { + rc = regulator_set_voltage_sel_regmap(rdev, sel); + if (rc) { + dev_err(info->dev, + "regulator set voltage failed for selector = 0x%2x, rc=%d\n", + sel, rc); + return rc; + } + } + + info->vout_sel = sel; + return rc; +} + +static int max20010_regulator_is_enabled(struct regulator_dev *rdev) +{ + struct max20010_device_info *info = rdev_get_drvdata(rdev); + + return (info->enabled == true) ? 1 : 0; +} + +static int max20010_regulator_enable(struct regulator_dev *rdev) +{ + struct max20010_device_info *info = rdev_get_drvdata(rdev); + int rc = 0; + + rc = regulator_set_voltage_sel_regmap(rdev, info->vout_sel); + if (rc) { + dev_err(info->dev, "regulator enable failed, rc=%d\n", rc); + return rc; + } + info->enabled = true; + + return rc; +} + +static int max20010_regulator_disable(struct regulator_dev *rdev) +{ + struct max20010_device_info *info = rdev_get_drvdata(rdev); + int rc = 0; + + rc = regulator_set_voltage_sel_regmap(rdev, 0x0); + if (rc) { + dev_err(info->dev, "regulator disable failed, rc=%d\n", rc); + return rc; + } + info->enabled = false; + + return rc; +} + +static inline unsigned int max20010_map_mode(unsigned int mode) +{ + return (mode == MAX20010_CONFIG_MODE_FPWM) ? + REGULATOR_MODE_NORMAL : REGULATOR_MODE_IDLE; +} + +static int max20010_set_mode(struct regulator_dev *rdev, unsigned int mode) +{ + struct max20010_device_info *info = rdev_get_drvdata(rdev); + int rc = 0; + + switch (mode) { + case REGULATOR_MODE_NORMAL: + rc = regmap_update_bits(info->regmap, MAX20010_CONFIG_REG, + MAX20010_CONFIG_MODE_MASK, + MAX20010_CONFIG_MODE_FPWM); + break; + case REGULATOR_MODE_IDLE: + rc = regmap_update_bits(info->regmap, MAX20010_CONFIG_REG, + (MAX20010_CONFIG_MODE_MASK + | MAX20010_CONFIG_SYNC_IO_MASK), + MAX20010_CONFIG_MODE_SYNC); + break; + default: + return -EINVAL; + } + + if (rc) + dev_err(info->dev, "failed to set %s mode, rc=%d\n", + mode == REGULATOR_MODE_NORMAL ? "Force PWM" : "SYNC", + rc); + return rc; +} + +static unsigned int max20010_get_mode(struct regulator_dev *rdev) +{ + struct max20010_device_info *info = rdev_get_drvdata(rdev); + unsigned int val; + int rc = 0; + + rc = regmap_read(info->regmap, MAX20010_CONFIG_REG, &val); + if (rc) { + dev_err(info->dev, "failed to read mode configuration, rc=%d\n", + rc); + return rc; + } + + return max20010_map_mode(val & MAX20010_CONFIG_MODE_MASK); +} + +static int max20010_enable_time(struct regulator_dev *rdev) +{ + struct max20010_device_info *info = rdev_get_drvdata(rdev); + int volt_uV; + + volt_uV = regulator_list_voltage_linear(rdev, info->vout_sel); + return DIV_ROUND_UP(volt_uV, info->slew_rate->soft_start); +} + +static struct regulator_ops max20010_regulator_ops = { + .set_voltage_sel = max20010_set_voltage_sel, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .map_voltage = regulator_map_voltage_linear, + .list_voltage = regulator_list_voltage_linear, + .is_enabled = max20010_regulator_is_enabled, + .enable = max20010_regulator_enable, + .disable = max20010_regulator_disable, + .set_mode = max20010_set_mode, + .get_mode = max20010_get_mode, + .enable_time = max20010_enable_time, +}; + +static struct regulator_desc rdesc = { + .name = "max20010-reg", + .supply_name = "vin", + .owner = THIS_MODULE, + .ops = &max20010_regulator_ops, + .type = REGULATOR_VOLTAGE, + .linear_min_sel = 1, + .vsel_reg = MAX20010_VSET_REG, + .vsel_mask = MAX20010_VSET_MASK, + .of_map_mode = max20010_map_mode, +}; + +static int max20010_device_setup(struct max20010_device_info *info) +{ + int max_uV, rc = 0; + unsigned int val; + + rc = regmap_update_bits(info->regmap, MAX20010_CONFIG_REG, + MAX20010_CONFIG_VSTEP_MASK, + (info->range->vrange_sel + << MAX20010_CONFIG_VSTEP_SHIFT)); + if (rc) { + dev_err(info->dev, "failed to update vstep configuration, rc=%d\n", + rc); + return rc; + } + + max_uV = min(info->init_data->constraints.max_uV, info->range->max_uV); + val = DIV_ROUND_UP(max_uV - info->range->min_uV, + info->range->step_uV) + 1; + rc = regmap_update_bits(info->regmap, MAX20010_VMAX_REG, + MAX20010_VMAX_MASK, val); + if (rc) { + dev_err(info->dev, "failed to write VMAX configuration, rc=%d\n", + rc); + return rc; + } + + rc = regmap_update_bits(info->regmap, MAX20010_SLEW_REG, + MAX20010_SLEW_MASK, info->slew_rate->slew_sel); + if (rc) { + dev_err(info->dev, "failed to write slew configuration, rc=%d\n", + rc); + return rc; + } + + /* Store default voltage register value */ + rc = regmap_read(info->regmap, MAX20010_VSET_REG, &val); + if (rc) { + dev_err(info->dev, "failed to read voltage register, rc=%d\n", + rc); + return rc; + } + + info->vout_sel = val & MAX20010_VSET_MASK; + info->enabled = (info->vout_sel != 0x0) ? true : false; + + return rc; +} + +static int max20010_parse_init_data(struct max20010_device_info *info) +{ + struct device_node *of_node = info->dev->of_node; + int i, slew_index, ss_slew_rate, dvs_slew_rate, rc = 0; + unsigned int val; + + if (of_find_property(of_node, "maxim,vrange-sel", NULL)) { + rc = of_property_read_u32(of_node, "maxim,vrange-sel", &val); + if (rc) { + dev_err(info->dev, "maxim,vrange-sel property read failed, rc=%d\n", + rc); + return rc; + } else if (val > 1) { + dev_err(info->dev, "unsupported vrange-sel value = %d, should be either 0 or 1\n", + val); + return -EINVAL; + } + } else { + /* Read default voltage range value */ + rc = regmap_read(info->regmap, MAX20010_CONFIG_REG, &val); + if (rc) { + dev_err(info->dev, "failed to read config register, rc=%d\n", + rc); + return rc; + } + + val = (val & MAX20010_CONFIG_VSTEP_MASK) + >> MAX20010_CONFIG_VSTEP_SHIFT; + } + + info->range = (val == 0) ? &max20010_range0 : &max20010_range1; + + /* + * Verify the min and max constraints specified through regulator device + * properties are fit with in that of the selected voltage range of the + * device. + */ + if (info->init_data->constraints.min_uV < info->range->min_uV || + info->init_data->constraints.max_uV > info->range->max_uV) { + dev_err(info->dev, + "Regulator min/max constraints are not fit with in the device min/max constraints\n"); + return -EINVAL; + } + + /* + * Read soft-start and dvs slew rates from device node. Use default + * values if not specified. + * + * Read the register default values and modify them with the slew-rates + * defined through device node. + */ + rc = regmap_read(info->regmap, MAX20010_SLEW_REG, &val); + if (rc) { + dev_err(info->dev, "failed to read slew register, rc=%d\n", + rc); + return rc; + } + + slew_index = val & MAX20010_SLEW_MASK; + + if (slew_index >= ARRAY_SIZE(slew_rates)) { + dev_err(info->dev, "unsupported default slew configuration\n"); + return -EINVAL; + } + + ss_slew_rate = slew_rates[slew_index].soft_start; + dvs_slew_rate = slew_rates[slew_index].dvs; + + if (of_find_property(of_node, "maxim,soft-start-slew-rate", NULL)) { + rc = of_property_read_u32(of_node, "maxim,soft-start-slew-rate", + &val); + if (rc) { + dev_err(info->dev, "maxim,soft-start-slew-rate read failed, rc=%d\n", + rc); + return rc; + } + + ss_slew_rate = val; + } + + if (of_find_property(of_node, "maxim,dvs-slew-rate", NULL)) { + rc = of_property_read_u32(of_node, "maxim,dvs-slew-rate", + &val); + if (rc) { + dev_err(info->dev, "maxim,dvs-slew-rate read failed, rc=%d\n", + rc); + return rc; + } + + dvs_slew_rate = val; + } + + for (i = 0; i < ARRAY_SIZE(slew_rates); i++) { + if (ss_slew_rate == slew_rates[i].soft_start + && dvs_slew_rate == slew_rates[i].dvs) { + info->slew_rate = &slew_rates[i]; + break; + } + } + + if (i == ARRAY_SIZE(slew_rates)) { + dev_err(info->dev, "invalid slew-rate values are specified.\n"); + return -EINVAL; + } + + return rc; +} + +static int max20010_regulator_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct max20010_device_info *info; + struct regulator_config config = { }; + int val, rc = 0; + + info = devm_kzalloc(&client->dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->dev = &client->dev; + info->init_data = of_get_regulator_init_data(info->dev, + info->dev->of_node, &rdesc); + if (!info->init_data) { + dev_err(info->dev, "regulator init_data is missing\n"); + return -ENODEV; + } + + info->init_data->constraints.valid_ops_mask |= REGULATOR_CHANGE_MODE; + info->init_data->constraints.valid_modes_mask + = REGULATOR_MODE_NORMAL | REGULATOR_MODE_IDLE; + + info->regmap = devm_regmap_init_i2c(client, &max20010_regmap_config); + if (IS_ERR(info->regmap)) { + dev_err(info->dev, "Error in allocating regmap\n"); + return PTR_ERR(info->regmap); + } + + i2c_set_clientdata(client, info); + + /* Get chip Id */ + rc = regmap_read(info->regmap, MAX20010_ID_REG, &val); + if (rc) { + dev_err(info->dev, "Failed to get chip ID!\n"); + return rc; + } + + rc = max20010_parse_init_data(info); + if (rc) { + dev_err(info->dev, "max20010 init data parsing failed, rc=%d\n", + rc); + return rc; + } + + rc = max20010_device_setup(info); + if (rc) { + dev_err(info->dev, "Failed to setup device, rc=%d\n", + rc); + return rc; + } + + config.dev = info->dev; + config.init_data = info->init_data; + config.regmap = info->regmap; + config.driver_data = info; + config.of_node = client->dev.of_node; + + rdesc.min_uV = info->range->min_uV; + rdesc.uV_step = info->range->step_uV; + rdesc.n_voltages = DIV_ROUND_UP((info->range->max_uV + - info->range->min_uV), + info->range->step_uV); + rdesc.ramp_delay = info->slew_rate->dvs; + + info->rdev = devm_regulator_register(info->dev, &rdesc, &config); + if (IS_ERR(info->rdev)) { + dev_err(info->dev, "Failed to register regulator, rc=%d\n", rc); + return PTR_ERR(info->rdev); + } + + dev_info(info->dev, "Detected regulator MAX20010 PID = %d : voltage-range(%d) : (%d - %d) uV, step = %d uV\n", + val, info->range->vrange_sel, info->range->min_uV, + info->range->max_uV, info->range->step_uV); + + return rc; +} + +static const struct of_device_id max20010_match_table[] = { + {.compatible = "maxim,max20010", }, + { }, +}; +MODULE_DEVICE_TABLE(of, max20010_match_table); + +static const struct i2c_device_id max20010_id[] = { + {"max20010", -1}, + { }, +}; +MODULE_DEVICE_TABLE(i2c, max20010_id); + +static struct i2c_driver max20010_regulator_driver = { + .driver = { + .name = "max20010-regulator", + .owner = THIS_MODULE, + .of_match_table = max20010_match_table, + }, + .probe = max20010_regulator_probe, + .id_table = max20010_id, +}; +module_i2c_driver(max20010_regulator_driver); + +MODULE_DESCRIPTION("MAX20010 regulator driver"); +MODULE_LICENSE("GPL v2"); diff --git a/include/dt-bindings/regulator/max20010.h b/include/dt-bindings/regulator/max20010.h new file mode 100644 index 000000000000..492e7287216f --- /dev/null +++ b/include/dt-bindings/regulator/max20010.h @@ -0,0 +1,20 @@ +/* Copyright (c) 2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * 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 _DT_BINDINGS_REGULATOR_MAX20010_H +#define _DT_BINDINGS_REGULATOR_MAX20010_H + +/* Regulator operating modes */ +#define MAX20010_OPMODE_SYNC 0 +#define MAX20010_OPMODE_FPWM 8 + +#endif /* _DT_BINDINGS_REGULATOR_MAX20010_H */ |