Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 14e6108d authored by Linux Build Service Account's avatar Linux Build Service Account Committed by Gerrit - the friendly Code Review server
Browse files

Merge "regulator: max20010: Add max20010 regulator driver"

parents a9412921 1a9dfe24
Loading
Loading
Loading
Loading
+77 −0
Original line number Diff line number Diff line
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>;
		}
	}
+9 −0
Original line number Diff line number Diff line
@@ -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
+1 −0
Original line number Diff line number Diff line
@@ -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
+490 −0
Original line number Diff line number Diff line
/* 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");
+20 −0
Original line number Diff line number Diff line
/* 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 */