On Wednesday, November 20, 2013 06:54:34 PM Bartlomiej Zolnierkiewicz wrote: > > Hi Krzysztof, > > On Wednesday, November 20, 2013 03:12:11 PM Krzysztof Kozlowski wrote: > > MAX14577 chip is a multi-function device which includes MUIC, > > charger and voltage regulator. The driver is located in drivers/mfd. > > > > This patch adds regulator driver for MAX14577 chip. There are two > > regulators in this chip: > > 1. Safeout LDO with constant voltage output of 4.9V. It can be only > > enabled or disabled. > > 2. Current regulator for the charger. It provides current from 90mA up > > to 950mA. > > Driver supports Device Tree. > > > > Signed-off-by: Krzysztof Kozlowski <k.kozlowski@xxxxxxxxxxx> > > Signed-off-by: Kyungmin Park <kyungmin.park@xxxxxxxxxxx> > > --- > > drivers/regulator/Kconfig | 7 ++ > > drivers/regulator/Makefile | 1 + > > drivers/regulator/max14577.c | 282 ++++++++++++++++++++++++++++++++++++++++++ > > 3 files changed, 290 insertions(+) > > create mode 100644 drivers/regulator/max14577.c > > > > diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig > > index ce785f4..11ee053 100644 > > --- a/drivers/regulator/Kconfig > > +++ b/drivers/regulator/Kconfig > > @@ -249,6 +249,13 @@ config REGULATOR_LP8788 > > help > > This driver supports LP8788 voltage regulator chip. > > > > +config REGULATOR_MAX14577 > > + tristate "Maxim 14577 regulator" > > + depends on MFD_MAX14577 > > + help > > + This driver controls a Maxim 14577 regulator via I2C bus. > > + The regulators include safeout LDO and current regulator 'CHARGER'. > > + > > config REGULATOR_MAX1586 > > tristate "Maxim 1586/1587 voltage regulator" > > depends on I2C > > diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile > > index 01c597e..654bd43 100644 > > --- a/drivers/regulator/Makefile > > +++ b/drivers/regulator/Makefile > > @@ -35,6 +35,7 @@ obj-$(CONFIG_REGULATOR_LP872X) += lp872x.o > > obj-$(CONFIG_REGULATOR_LP8788) += lp8788-buck.o > > obj-$(CONFIG_REGULATOR_LP8788) += lp8788-ldo.o > > obj-$(CONFIG_REGULATOR_LP8755) += lp8755.o > > +obj-$(CONFIG_REGULATOR_MAX14577) += max14577.o > > obj-$(CONFIG_REGULATOR_MAX1586) += max1586.o > > obj-$(CONFIG_REGULATOR_MAX8649) += max8649.o > > obj-$(CONFIG_REGULATOR_MAX8660) += max8660.o > > diff --git a/drivers/regulator/max14577.c b/drivers/regulator/max14577.c > > new file mode 100644 > > index 0000000..f449e57 > > --- /dev/null > > +++ b/drivers/regulator/max14577.c > > @@ -0,0 +1,282 @@ > > +/* > > + * max14577.c - Regulator driver for the Maxim 14577 > > + * > > + * Copyright (C) 2013 Samsung Electronics > > + * Krzysztof Kozlowski <k.kozlowski@xxxxxxxxxxx> > > + * > > + * 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. > > + * > > + * 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/platform_device.h> > > +#include <linux/regulator/driver.h> > > +#include <linux/mfd/max14577.h> > > +#include <linux/mfd/max14577-private.h> > > +#include <linux/regulator/of_regulator.h> > > + > > +struct max14577_regulator { > > + struct device *dev; > > + struct max14577 *max14577; > > + int num_regulators; > > + struct regulator_dev **regulators; > > +}; > > + > > +static int max14577_reg_is_enabled(struct regulator_dev *rdev) > > +{ > > + int rid = rdev_get_id(rdev); > > + struct regmap *rmap = rdev->regmap; > > + u8 reg_data; > > + > > + switch (rid) { > > + case MAX14577_CHARGER: > > + max14577_read_reg(rmap, MAX14577_CHG_REG_CHG_CTRL2, ®_data); > > + if ((reg_data & CHGCTRL2_MBCHOSTEN_MASK) == 0) > > + return 0; > > + max14577_read_reg(rmap, MAX14577_CHG_REG_STATUS3, ®_data); > > + if ((reg_data & STATUS3_CGMBC_MASK) == 0) > > + return 0; > > + /* MBCHOSTEN and CGMBC are on */ > > + return 1; > > + default: > > + return -EINVAL; > > + } > > +} > > + > > +static int max14577_reg_get_current_limit(struct regulator_dev *rdev) > > +{ > > + u8 reg_data; > > + struct regmap *rmap = rdev->regmap; > > + > > + if (rdev_get_id(rdev) != MAX14577_CHARGER) > > + return -EINVAL; > > + > > + max14577_read_reg(rmap, MAX14577_CHG_REG_CHG_CTRL4, ®_data); > > + > > + if ((reg_data & CHGCTRL4_MBCICHWRCL_MASK) == 0) > > + return MAX14577_REGULATOR_CURRENT_LIMIT_MIN; > > + > > + reg_data = ((reg_data & CHGCTRL4_MBCICHWRCH_MASK) >> > > + CHGCTRL4_MBCICHWRCH_SHIFT); > > + return MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_START + > > + reg_data * MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_STEP; > > +} > > + > > +static int max14577_reg_set_current_limit(struct regulator_dev *rdev, > > + int min_uA, int max_uA) > > +{ > > + int i, current_bits = 0xf; > > + u8 reg_data; > > + > > + if (rdev_get_id(rdev) != MAX14577_CHARGER) > > + return -EINVAL; > > + > > + if (min_uA > MAX14577_REGULATOR_CURRENT_LIMIT_MAX || > > + max_uA < MAX14577_REGULATOR_CURRENT_LIMIT_MIN) > > + return -EINVAL; > > + > > + if (max_uA < MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_START) { > > + /* Less than 200 mA, so set 90mA (turn only Low Bit off) */ > > + u8 reg_data = 0x0 << CHGCTRL4_MBCICHWRCL_SHIFT; > > + return max14577_update_reg(rdev->regmap, > > + MAX14577_CHG_REG_CHG_CTRL4, > > + CHGCTRL4_MBCICHWRCL_MASK, reg_data); > > + } > > + > > + /* max_uA is in range: <LIMIT_HIGH_START, inifinite>, so search for > > + * valid current starting from LIMIT_MAX. */ > > + for (i = MAX14577_REGULATOR_CURRENT_LIMIT_MAX; > > + i >= MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_START; > > + i -= MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_STEP) { > > + if (i <= max_uA) > > + break; > > + current_bits--; > > + } > > + BUG_ON(current_bits < 0); /* Cannot happen */ > > + /* Turn Low Bit on (use range 200mA-950 mA) */ > > + reg_data = 0x1 << CHGCTRL4_MBCICHWRCL_SHIFT; > > + /* and set proper High Bits */ > > + reg_data |= current_bits << CHGCTRL4_MBCICHWRCH_SHIFT; > > + > > + return max14577_update_reg(rdev->regmap, MAX14577_CHG_REG_CHG_CTRL4, > > + CHGCTRL4_MBCICHWRCL_MASK | CHGCTRL4_MBCICHWRCH_MASK, > > + reg_data); > > +} > > + > > +static struct regulator_ops max14577_safeout_ops = { > > + .is_enabled = regulator_is_enabled_regmap, > > + .enable = regulator_enable_regmap, > > + .disable = regulator_disable_regmap, > > + .list_voltage = regulator_list_voltage_linear, > > +}; > > + > > +static struct regulator_ops max14577_charger_ops = { > > + .is_enabled = max14577_reg_is_enabled, > > + .enable = regulator_enable_regmap, > > + .disable = regulator_disable_regmap, > > + .get_current_limit = max14577_reg_get_current_limit, > > + .set_current_limit = max14577_reg_set_current_limit, > > +}; > > + > > +static struct regulator_desc supported_regulators[] = { > > + { > > + .name = "SAFEOUT", > > + .id = MAX14577_SAFEOUT, > > + .ops = &max14577_safeout_ops, > > + .type = REGULATOR_VOLTAGE, > > + .owner = THIS_MODULE, > > + .n_voltages = 1, > > + .min_uV = MAX14577_REGULATOR_SAFEOUT_VOLTAGE, > > + .enable_reg = MAX14577_REG_CONTROL2, > > + .enable_mask = CTRL2_SFOUTORD_MASK, > > + }, { > > + .name = "CHARGER", > > + .id = MAX14577_CHARGER, > > + .ops = &max14577_charger_ops, > > + .type = REGULATOR_CURRENT, > > + .owner = THIS_MODULE, > > + .enable_reg = MAX14577_CHG_REG_CHG_CTRL2, > > + .enable_mask = CHGCTRL2_MBCHOSTEN_MASK, > > + } > > +}; > > + > > +#ifdef CONFIG_OF > > +static int max14577_regulator_dt_parse_pdata(struct platform_device *pdev, > > + struct max14577_platform_data *pdata) > > +{ > > + struct max14577 *max14577 = dev_get_drvdata(pdev->dev.parent); > > + struct device_node *np; > > + struct max14577_regulator_platform_data *reg_pdata; > > + struct of_regulator_match rmatch; > > + int i, ret, cnt = 0; > > + > > + np = of_get_child_by_name(max14577->dev->of_node, "regulators"); > > + if (!np) > > + return -EINVAL; > > + > > + reg_pdata = devm_kzalloc(&pdev->dev, sizeof(*reg_pdata) * > > + ARRAY_SIZE(supported_regulators), GFP_KERNEL); > > + if (!reg_pdata) > > + return -ENOMEM; > > + > > + for (i = 0; i < ARRAY_SIZE(supported_regulators); i++) { > > + rmatch.name = supported_regulators[i].name; > > + ret = of_regulator_match(&pdev->dev, np, &rmatch, 1); > > + if (ret != 1) > > + continue; > > + dev_dbg(&pdev->dev, "Found regulator %d/%s\n", > > + supported_regulators[i].id, > > + supported_regulators[i].name); > > + reg_pdata[cnt].id = supported_regulators[i].id; > > + reg_pdata[cnt].initdata = rmatch.init_data; > > + reg_pdata[cnt].of_node = rmatch.of_node; > > + cnt++; > > + } > > + > > + pdata->num_regulators = cnt; > > + pdata->regulators = reg_pdata; > > + > > + return 0; > > +} > > +#else > > +static int max14577_regulator_dt_parse_pdata(struct platform_device *pdev, > > + struct max14577_platform_data *pdata) > > +{ > > + return 0; > > +} > > +#endif > > + > > +static int max14577_regulator_probe(struct platform_device *pdev) > > +{ > > + struct max14577_regulator *info; > > + struct max14577 *max14577 = dev_get_drvdata(pdev->dev.parent); > > + struct max14577_platform_data *pdata = dev_get_platdata(max14577->dev); > > + int i, size; > > + struct regulator_config config = {}; > > + > > + if (!pdata) { > > + /* Parent must provide pdata */ > > + dev_err(&pdev->dev, "No MFD driver platform data found.\n"); > > + return -ENODEV; > > + } > > + > > + if (max14577->dev->of_node) { > > + int ret = max14577_regulator_dt_parse_pdata(pdev, pdata); > > + if (ret) > > + return ret; > > + } > > + > > + info = devm_kzalloc(&pdev->dev, sizeof(struct max14577_regulator), > > + GFP_KERNEL); > > + if (!info) > > + return -ENOMEM; > > + > > + size = sizeof(struct regulator_dev *) * pdata->num_regulators; > > + info->regulators = devm_kzalloc(&pdev->dev, size, GFP_KERNEL); > > + if (!info->regulators) { > > + dev_err(&pdev->dev, "Cannot allocate memory for regulators\n"); > > + return -ENOMEM; > > + } > > + > > + info->dev = &pdev->dev; > > + info->max14577 = max14577; > > + info->num_regulators = pdata->num_regulators; > > + > > + config.dev = &pdev->dev; > > + config.regmap = max14577->regmap; > > + config.driver_data = info; > > + platform_set_drvdata(pdev, info); > > I don't see any code in this driver that reads from "info" so unless Err.. > I'm missing something obvious "info" is redundant and can be removed > altogether. > > Best regards, > -- > Bartlomiej Zolnierkiewicz > Samsung R&D Institute Poland > Samsung Electronics > > > + for (i = 0; i < pdata->num_regulators; i++) { > > + int id = pdata->regulators[i].id; > > + > > + config.init_data = pdata->regulators[i].initdata; > > + config.of_node = pdata->regulators[i].of_node; > > + > > + info->regulators[i] = devm_regulator_register(&pdev->dev, > > + &supported_regulators[id], &config); > > + if (IS_ERR(info->regulators[i])) { This code actually reads from "info" but it can be fixed trivially to not require "info". Best regards, -- Bartlomiej Zolnierkiewicz Samsung R&D Institute Poland Samsung Electronics > > + int ret = PTR_ERR(info->regulators[i]); > > + dev_err(&pdev->dev, > > + "Regulator init failed for ID %d with error: %d\n", > > + id, ret); > > + return ret; > > + } > > + } > > + > > + return 0; > > +} > > + > > +static struct platform_driver max14577_regulator_driver = { > > + .driver = { > > + .owner = THIS_MODULE, > > + .name = "max14577-regulator", > > + }, > > + .probe = max14577_regulator_probe, > > +}; > > + > > +static int __init max14577_regulator_init(void) > > +{ > > + BUILD_BUG_ON(MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_START + > > + MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_STEP * 0xf != > > + MAX14577_REGULATOR_CURRENT_LIMIT_MAX); > > + return platform_driver_register(&max14577_regulator_driver); > > +} > > +subsys_initcall(max14577_regulator_init); > > + > > +static void __exit max14577_regulator_exit(void) > > +{ > > + platform_driver_unregister(&max14577_regulator_driver); > > +} > > +module_exit(max14577_regulator_exit); > > + > > +MODULE_AUTHOR("Krzysztof Kozlowski <k.kozlowski@xxxxxxxxxxx>"); > > +MODULE_DESCRIPTION("MAXIM 14577 regulator driver"); > > +MODULE_LICENSE("GPL"); -- To unsubscribe from this list: send the line "unsubscribe linux-doc" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html