From ccf3ed782a6e65e0355b36424e88d2d8b5f46e89 Mon Sep 17 00:00:00 2001 From: Matt Sealey Date: Mon, 21 Jan 2013 11:38:40 -0600 Subject: regulator: mc13892-regulator: correct/refine handling of the SWxHI bit MC13892 PMIC supports a "HI" bit for 3 of it's 4 buck switcher outputs, which enables a higher set of voltage ranges. Despite a comment in the code ('sw regulators need special care due to the "hi" bit'), it actually does not take special care since it does not modify it's use of the selector table index when this bit is set, giving us very odd behavior when setting a high voltage on supported switchers or listing current voltages. Net effect is in best case the kernel and sysfs report lower voltages than are actually set in hardware (1300mV instead of 1800mV for example) and in the worst case setting a voltage (e.g. 1800mV) will cause an undervoltage condition (e.g. 1300mV). Correct the behavior, taking into account SW1 doesn't support the HI bit, and as such we need to ignore it. While we are modifying these functions, fix and optimize the following; * set_voltage_sel callback was using .reg instead of .vsel_reg - since they were set to the same value it actually didn't break anything but it would be semantically incorrect to use .reg in this case. We now use .vsel_reg and be consistent. * vsel_shift is always 0 for every SWx regulator, and constantly shifting and masking off the bottom few bits is time consuming and makes the code very hard to read - optimize this out. * get_voltage_sel uses the variable "val" and set_voltage_sel uses the variable "selector" (and reg_value). Introduce the variable "selector" to get_voltage_sel such that it makes more sense and allow some leaner code in light of the modifications in this patch. Add better exposure to the debug print so the register value AND the selector are printed as this will adequately show the HI bit in the register. * correct a comment in probe which is doing a version check. Magic values are awful but for once instance, a comment does just as good a job as something symbolic. Signed-off-by: Matt Sealey Tested-by: Steev Klimaszewski Signed-off-by: Mark Brown --- drivers/regulator/mc13892-regulator.c | 72 +++++++++++++++++++++++++++-------- 1 file changed, 56 insertions(+), 16 deletions(-) (limited to 'drivers/regulator') diff --git a/drivers/regulator/mc13892-regulator.c b/drivers/regulator/mc13892-regulator.c index 0d84b1f33199..5dd8aaa5cfb8 100644 --- a/drivers/regulator/mc13892-regulator.c +++ b/drivers/regulator/mc13892-regulator.c @@ -164,6 +164,14 @@ static const unsigned int mc13892_sw1[] = { 1350000, 1375000 }; +/* + * Note: this table is used to derive SWxVSEL by index into + * the array. Offset the values by the index of 1100000uV + * to get the actual register value for that voltage selector + * if the HI bit is to be set as well. + */ +#define MC13892_SWxHI_SEL_OFFSET 20 + static const unsigned int mc13892_sw[] = { 600000, 625000, 650000, 675000, 700000, 725000, 750000, 775000, 800000, 825000, 850000, 875000, @@ -239,7 +247,6 @@ static const unsigned int mc13892_pwgtdrv[] = { }; static struct regulator_ops mc13892_gpo_regulator_ops; -/* sw regulators need special care due to the "hi bit" */ static struct regulator_ops mc13892_sw_regulator_ops; @@ -396,7 +403,7 @@ static int mc13892_sw_regulator_get_voltage_sel(struct regulator_dev *rdev) { struct mc13xxx_regulator_priv *priv = rdev_get_drvdata(rdev); int ret, id = rdev_get_id(rdev); - unsigned int val; + unsigned int val, selector; dev_dbg(rdev_get_dev(rdev), "%s id: %d\n", __func__, id); @@ -407,12 +414,28 @@ static int mc13892_sw_regulator_get_voltage_sel(struct regulator_dev *rdev) if (ret) return ret; - val = (val & mc13892_regulators[id].vsel_mask) - >> mc13892_regulators[id].vsel_shift; + /* + * Figure out if the HI bit is set inside the switcher mode register + * since this means the selector value we return is at a different + * offset into the selector table. + * + * According to the MC13892 documentation note 59 (Table 47) the SW1 + * buck switcher does not support output range programming therefore + * the HI bit must always remain 0. So do not do anything strange if + * our register is MC13892_SWITCHERS0. + */ + + selector = val & mc13892_regulators[id].vsel_mask; + + if ((mc13892_regulators[id].vsel_reg != MC13892_SWITCHERS0) && + (val & MC13892_SWITCHERS0_SWxHI)) { + selector += MC13892_SWxHI_SEL_OFFSET; + } - dev_dbg(rdev_get_dev(rdev), "%s id: %d val: %d\n", __func__, id, val); + dev_dbg(rdev_get_dev(rdev), "%s id: %d val: 0x%08x selector: %d\n", + __func__, id, val, selector); - return val; + return selector; } static int mc13892_sw_regulator_set_voltage_sel(struct regulator_dev *rdev, @@ -425,18 +448,35 @@ static int mc13892_sw_regulator_set_voltage_sel(struct regulator_dev *rdev, volt = rdev->desc->volt_table[selector]; mask = mc13892_regulators[id].vsel_mask; - reg_value = selector << mc13892_regulators[id].vsel_shift; - - if (volt > 1375000) { - mask |= MC13892_SWITCHERS0_SWxHI; - reg_value |= MC13892_SWITCHERS0_SWxHI; - } else if (volt < 1100000) { - mask |= MC13892_SWITCHERS0_SWxHI; - reg_value &= ~MC13892_SWITCHERS0_SWxHI; + reg_value = selector; + + /* + * Don't mess with the HI bit or support HI voltage offsets for SW1. + * + * Since the get_voltage_sel callback has given a fudged value for + * the selector offset, we need to back out that offset if HI is + * to be set so we write the correct value to the register. + * + * The HI bit addition and selector offset handling COULD be more + * complicated by shifting and masking off the voltage selector part + * of the register then logical OR it back in, but since the selector + * is at bits 4:0 there is very little point. This makes the whole + * thing more readable and we do far less work. + */ + + if (mc13892_regulators[id].vsel_reg != MC13892_SWITCHERS0) { + if (volt > 1375000) { + reg_value -= MC13892_SWxHI_SEL_OFFSET; + reg_value |= MC13892_SWITCHERS0_SWxHI; + mask |= MC13892_SWITCHERS0_SWxHI; + } else if (volt < 1100000) { + reg_value &= ~MC13892_SWITCHERS0_SWxHI; + mask |= MC13892_SWITCHERS0_SWxHI; + } } mc13xxx_lock(priv->mc13xxx); - ret = mc13xxx_reg_rmw(priv->mc13xxx, mc13892_regulators[id].reg, mask, + ret = mc13xxx_reg_rmw(priv->mc13xxx, mc13892_regulators[id].vsel_reg, mask, reg_value); mc13xxx_unlock(priv->mc13xxx); @@ -520,7 +560,7 @@ static int mc13892_regulator_probe(struct platform_device *pdev) if (ret) goto err_unlock; - /* enable switch auto mode */ + /* enable switch auto mode (on 2.0A silicon only) */ if ((val & 0x0000FFFF) == 0x45d0) { ret = mc13xxx_reg_rmw(mc13892, MC13892_SWITCHERS4, MC13892_SWITCHERS4_SW1MODE_M | -- cgit v1.2.2 From 2c8a5dcaa4143dfedf956a5349216ded3c3ad085 Mon Sep 17 00:00:00 2001 From: Matt Sealey Date: Mon, 21 Jan 2013 12:25:45 -0600 Subject: regulator: mc13892: sanity check num_regulators parsed vs. registered Imagine a situation where a device tree has a few regulators in an appropriate node: regulators { sw1 { .. }; vvideo { .. }; : vfake { .. }; vtypo { .. }; }; In the above example, the node name "vfake" is an attempt to match a regulator name inside the driver which just so happens to not exist. The node name "vtypo" represents an accidental typographical error in a regulator name which may have been introduced to a device tree. In these cases, the number of regulators the mc13892 driver thinks it has does not match the number of regulators it parsed and registered. Since it will go over this array based on this number, it will actually re-register regulator "0" (which happens to be SW1) over and over again until it reaches the number, resulting in messages on the kernel log such as these: SW1: at 1100 mV VVIDEO: at 2775mV : SW1: at 1100 mV SW1: at 1100 mV .. up to that number of "mismatched" regulators. Nobody using DT can/will consume these regulators, so it should not be possible for it to cause any real regulator problems or driver breakages, but it is an easy thing to miss in a kernel log and is an immediate indication of a problem with the device tree authoring. This patch effectively sanity checks the number of counted children of the regulators node vs. the number that actually matched driver names, and sets the appropriate num_regulators value. It also gives a little warning for device tree authors that they MAY have screwed something up, such that this patch does not hide the device tree authoring problem. Signed-off-by: Matt Sealey Tested-by: Steev Klimaszewski Signed-off-by: Mark Brown --- drivers/regulator/mc13892-regulator.c | 39 ++++++++++++++++++++++++++++-- drivers/regulator/mc13xxx-regulator-core.c | 10 ++++++-- drivers/regulator/mc13xxx.h | 4 +-- 3 files changed, 47 insertions(+), 6 deletions(-) (limited to 'drivers/regulator') diff --git a/drivers/regulator/mc13892-regulator.c b/drivers/regulator/mc13892-regulator.c index 5dd8aaa5cfb8..9891aec47b57 100644 --- a/drivers/regulator/mc13892-regulator.c +++ b/drivers/regulator/mc13892-regulator.c @@ -535,15 +535,18 @@ static int mc13892_regulator_probe(struct platform_device *pdev) struct mc13xxx_regulator_init_data *mc13xxx_data; struct regulator_config config = { }; int i, ret; - int num_regulators = 0; + int num_regulators = 0, num_parsed; u32 val; num_regulators = mc13xxx_get_num_regulators_dt(pdev); + if (num_regulators <= 0 && pdata) num_regulators = pdata->num_regulators; if (num_regulators <= 0) return -EINVAL; + num_parsed = num_regulators; + priv = devm_kzalloc(&pdev->dev, sizeof(*priv) + num_regulators * sizeof(priv->regulators[0]), GFP_KERNEL); @@ -586,7 +589,39 @@ static int mc13892_regulator_probe(struct platform_device *pdev) = mc13892_vcam_get_mode; mc13xxx_data = mc13xxx_parse_regulators_dt(pdev, mc13892_regulators, - ARRAY_SIZE(mc13892_regulators)); + ARRAY_SIZE(mc13892_regulators), + &num_parsed); + + /* + * Perform a little sanity check on the regulator tree - if we found + * a number of regulators from mc13xxx_get_num_regulators_dt and + * then parsed a smaller number in mc13xxx_parse_regulators_dt then + * there is a regulator defined in the regulators node which has + * not matched any usable regulator in the driver. In this case, + * there is one missing and what will happen is the first regulator + * will get registered again. + * + * Fix this by basically making our number of registerable regulators + * equal to the number of regulators we parsed. We are allocating + * too much memory for priv, but this is unavoidable at this point. + * + * As an example of how this can happen, try making a typo in your + * regulators node (vviohi {} instead of viohi {}) so that the name + * does not match.. + * + * The check will basically pass for platform data (non-DT) because + * mc13xxx_parse_regulators_dt for !CONFIG_OF will not touch num_parsed. + * + */ + if (num_parsed != num_regulators) { + dev_warn(&pdev->dev, + "parsed %d != regulators %d - check your device tree!\n", + num_parsed, num_regulators); + + num_regulators = num_parsed; + priv->num_regulators = num_regulators; + } + for (i = 0; i < num_regulators; i++) { struct regulator_init_data *init_data; struct regulator_desc *desc; diff --git a/drivers/regulator/mc13xxx-regulator-core.c b/drivers/regulator/mc13xxx-regulator-core.c index 4ed89c654110..2ecf1d8b6a94 100644 --- a/drivers/regulator/mc13xxx-regulator-core.c +++ b/drivers/regulator/mc13xxx-regulator-core.c @@ -181,12 +181,14 @@ EXPORT_SYMBOL_GPL(mc13xxx_get_num_regulators_dt); struct mc13xxx_regulator_init_data *mc13xxx_parse_regulators_dt( struct platform_device *pdev, struct mc13xxx_regulator *regulators, - int num_regulators) + int num_regulators, int *num_parsed) { struct mc13xxx_regulator_priv *priv = platform_get_drvdata(pdev); struct mc13xxx_regulator_init_data *data, *p; struct device_node *parent, *child; - int i; + int i, parsed = 0; + + *num_parsed = 0; of_node_get(pdev->dev.parent->of_node); parent = of_find_node_by_name(pdev->dev.parent->of_node, "regulators"); @@ -203,16 +205,20 @@ struct mc13xxx_regulator_init_data *mc13xxx_parse_regulators_dt( for (i = 0; i < num_regulators; i++) { if (!of_node_cmp(child->name, regulators[i].desc.name)) { + p->id = i; p->init_data = of_get_regulator_init_data( &pdev->dev, child); p->node = child; p++; + + parsed++; break; } } } + *num_parsed = parsed; return data; } EXPORT_SYMBOL_GPL(mc13xxx_parse_regulators_dt); diff --git a/drivers/regulator/mc13xxx.h b/drivers/regulator/mc13xxx.h index 06c8903f182a..007f83387fd6 100644 --- a/drivers/regulator/mc13xxx.h +++ b/drivers/regulator/mc13xxx.h @@ -39,7 +39,7 @@ extern int mc13xxx_fixed_regulator_set_voltage(struct regulator_dev *rdev, extern int mc13xxx_get_num_regulators_dt(struct platform_device *pdev); extern struct mc13xxx_regulator_init_data *mc13xxx_parse_regulators_dt( struct platform_device *pdev, struct mc13xxx_regulator *regulators, - int num_regulators); + int num_regulators, int *num_parsed); #else static inline int mc13xxx_get_num_regulators_dt(struct platform_device *pdev) { @@ -48,7 +48,7 @@ static inline int mc13xxx_get_num_regulators_dt(struct platform_device *pdev) static inline struct mc13xxx_regulator_init_data *mc13xxx_parse_regulators_dt( struct platform_device *pdev, struct mc13xxx_regulator *regulators, - int num_regulators) + int num_regulators, int *num_parsed) { return NULL; } -- cgit v1.2.2