diff options
author | Rabin Vincent <rabin.vincent@stericsson.com> | 2011-04-08 14:35:42 +0530 |
---|---|---|
committer | Jonas ABERG <jonas.aberg@stericsson.com> | 2011-04-20 07:43:07 +0200 |
commit | 96101b07a6685ffb0f1b066eb58f5b3c170d60be (patch) | |
tree | c59738c94ed0e9e45c58b06c0698a94147831f46 /drivers | |
parent | c8cfae4b0ed9c8235f6f297fd4aed20567462b54 (diff) |
regulator: add AB5500 support
ST-Ericsson Linux next: -
ST-Ericsson ID: WP257121
ST-Ericsson FOSS-OUT ID: Trivial
Change-Id: I91faae392552b8d0e993e1935115b63db9e7e268
Signed-off-by: Rabin Vincent <rabin.vincent@stericsson.com>
Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/20654
Reviewed-by: Bengt JONSSON <bengt.g.jonsson@stericsson.com>
Diffstat (limited to 'drivers')
-rwxr-xr-x | drivers/mfd/ab5500-core.c | 15 | ||||
-rw-r--r-- | drivers/regulator/Kconfig | 7 | ||||
-rw-r--r-- | drivers/regulator/Makefile | 1 | ||||
-rw-r--r-- | drivers/regulator/ab5500.c | 501 |
4 files changed, 522 insertions, 2 deletions
diff --git a/drivers/mfd/ab5500-core.c b/drivers/mfd/ab5500-core.c index 91d7e8f9e1d..f800b418d04 100755 --- a/drivers/mfd/ab5500-core.c +++ b/drivers/mfd/ab5500-core.c @@ -214,7 +214,7 @@ static struct ab5500_i2c_banks ab5500_bank_ranges[AB5500_NUM_DEVICES] = { }, }, [AB5500_DEVID_REGULATORS] = { - .nbanks = 1, + .nbanks = 2, .bank = (struct ab5500_i2c_ranges[]) { { .bankid = AB5500_BANK_STARTUP, @@ -227,6 +227,17 @@ static struct ab5500_i2c_banks ab5500_bank_ranges[AB5500_NUM_DEVICES] = { }, }, }, + { + .bankid = AB5500_BANK_SIM_USBSIM, + .nranges = 1, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x14, + .last = 0x14, + .perm = AB5500_PERM_RW, + }, + }, + }, }, }, [AB5500_DEVID_SIM] = { @@ -364,7 +375,7 @@ static struct mfd_cell ab5500_devs[AB5500_NUM_DEVICES] = { .id = AB5500_DEVID_POWER, }, [AB5500_DEVID_REGULATORS] = { - .name = "ab5500-regulators", + .name = "ab5500-regulator", .id = AB5500_DEVID_REGULATORS, }, [AB5500_DEVID_SIM] = { diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index 2d7644501ad..c39731ce554 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -64,6 +64,13 @@ config REGULATOR_USERSPACE_CONSUMER If unsure, say no. +config REGULATOR_AB5500 + bool "ST-Ericsson AB5500 Power Regulators" + depends on AB5500_CORE + help + This driver supports the regulators found on the ST-Ericsson mixed + signal AB5500 PMIC + config REGULATOR_AB8500 bool "ST-Ericsson AB8500 Power Regulators" depends on AB8500_CORE diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index f6916e1384c..ce1a332dfbe 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_REGULATOR_FIXED_VOLTAGE) += fixed.o obj-$(CONFIG_REGULATOR_VIRTUAL_CONSUMER) += virtual.o obj-$(CONFIG_REGULATOR_USERSPACE_CONSUMER) += userspace-consumer.o +obj-$(CONFIG_REGULATOR_AB5500) += ab5500.o obj-$(CONFIG_REGULATOR_AB8500) += ab8500.o obj-$(CONFIG_REGULATOR_AB8500_DEBUG) += ab8500-debug.o obj-$(CONFIG_REGULATOR_BQ24022) += bq24022.o diff --git a/drivers/regulator/ab5500.c b/drivers/regulator/ab5500.c new file mode 100644 index 00000000000..907b9e0fefd --- /dev/null +++ b/drivers/regulator/ab5500.c @@ -0,0 +1,501 @@ +/* + * Copyright (C) 2011 ST-Ericsson SA + * + * License terms: GNU General Public License (GPL) version 2 + * + * Based on ab3100.c. + * + * Author: Bengt Jonsson <bengt.g.jonsson@stericsson.com> for ST-Ericsson + * Author: Rabin Vincent <rabin.vincent@stericsson.com> for ST-Ericsson + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/mfd/abx500.h> +#include <linux/regulator/ab5500.h> + +#define AB5500_LDO_VDIGMIC_ST 0x50 + +#define AB5500_LDO_S_ST 0x66 +#define AB5500_LDO_S_PWR1 0x67 +#define AB5500_LDO_S_PWR0 0x68 + +#define AB5500_LDO_D_ST 0x72 +#define AB5500_LDO_D_PWR1 0x73 +#define AB5500_LDO_D_PWR0 0x74 + +#define AB5500_LDO_G_ST 0x78 +#define AB5500_LDO_G_PWR1 0x79 +#define AB5500_LDO_G_PWR0 0x7a + +#define AB5500_LDO_H_ST 0x7b +#define AB5500_LDO_H_PWR1 0x7c +#define AB5500_LDO_H_PWR0 0x7d + +#define AB5500_LDO_K_ST 0x7e +#define AB5500_LDO_K_PWR1 0x7f +#define AB5500_LDO_K_PWR0 0x80 + +#define AB5500_LDO_L_ST 0x81 +#define AB5500_LDO_L_PWR1 0x82 +#define AB5500_LDO_L_PWR0 0x83 + +/* In SIM bank */ +#define AB5500_SIM_SUP 0x14 + +#define AB5500_LDO_MODE_MASK (0x3 << 4) +#define AB5500_LDO_MODE_FULLPOWER (0x3 << 4) +#define AB5500_LDO_MODE_PWRCTRL (0x2 << 4) +#define AB5500_LDO_MODE_LOWPOWER (0x1 << 4) +#define AB5500_LDO_MODE_OFF (0x0 << 4) +#define AB5500_LDO_VOLT_MASK 0x07 + +struct ab5500_regulator { + struct regulator_desc desc; + const int *voltages; + int voltages_len; + bool pwrctrl; + int enable_time; + u8 bank; + u8 reg; +}; + +struct ab5500_regulators { + struct device *dev; + struct ab5500_regulator *regulator[AB5500_NUM_REGULATORS]; + struct regulator_dev *rdev[AB5500_NUM_REGULATORS]; +}; + +static int ab5500_regulator_enable_time(struct regulator_dev *rdev) +{ + struct ab5500_regulators *ab5500 = rdev_get_drvdata(rdev); + struct ab5500_regulator *r = ab5500->regulator[rdev_get_id(rdev)]; + + return r->enable_time; /* microseconds */ +} + +static int ab5500_regulator_enable(struct regulator_dev *rdev) +{ + struct ab5500_regulators *ab5500 = rdev_get_drvdata(rdev); + struct ab5500_regulator *r = ab5500->regulator[rdev_get_id(rdev)]; + u8 regval = AB5500_LDO_MODE_FULLPOWER; + + return abx500_mask_and_set_register_interruptible( + ab5500->dev, r->bank, r->reg, + AB5500_LDO_MODE_MASK, regval); +} + +static int ab5500_regulator_disable(struct regulator_dev *rdev) +{ + struct ab5500_regulators *ab5500 = rdev_get_drvdata(rdev); + struct ab5500_regulator *r = ab5500->regulator[rdev_get_id(rdev)]; + u8 regval = r->pwrctrl ? AB5500_LDO_MODE_PWRCTRL : AB5500_LDO_MODE_OFF; + + return abx500_mask_and_set_register_interruptible( + ab5500->dev, r->bank, r->reg, + AB5500_LDO_MODE_MASK, regval); +} + +static int ab5500_regulator_is_enabled(struct regulator_dev *rdev) +{ + struct ab5500_regulators *ab5500 = rdev_get_drvdata(rdev); + struct ab5500_regulator *r = ab5500->regulator[rdev_get_id(rdev)]; + u8 regval; + int err; + + err = abx500_get_register_interruptible(ab5500->dev, + r->bank, r->reg, ®val); + if (err) { + dev_err(rdev_get_dev(rdev), "unable to get register 0x%x\n", + r->reg); + return err; + } + + return (regval & AB5500_LDO_MODE_MASK) == AB5500_LDO_MODE_FULLPOWER; +} + +static int +ab5500_regulator_list_voltage(struct regulator_dev *rdev, unsigned selector) +{ + struct ab5500_regulators *ab5500 = rdev_get_drvdata(rdev); + struct ab5500_regulator *r = ab5500->regulator[rdev_get_id(rdev)]; + int selindex; + int i; + + for (i = 0, selindex = 0; selindex < r->voltages_len; i++) { + int voltage = r->voltages[i]; + + if (!voltage) + continue; + + if (selindex == selector) + return voltage; + + selindex++; + } + + return -EINVAL; +} + +static int ab5500_regulator_fixed_get_voltage(struct regulator_dev *rdev) +{ + struct ab5500_regulators *ab5500 = rdev_get_drvdata(rdev); + struct ab5500_regulator *r = ab5500->regulator[rdev_get_id(rdev)]; + + return r->voltages[0]; +} + +static int ab5500_regulator_get_voltage(struct regulator_dev *rdev) +{ + struct ab5500_regulators *ab5500 = rdev_get_drvdata(rdev); + struct ab5500_regulator *r = ab5500->regulator[rdev_get_id(rdev)]; + u8 regval; + int ret; + + ret = abx500_get_register_interruptible(ab5500->dev, + r->bank, r->reg, ®val); + if (ret) { + dev_warn(rdev_get_dev(rdev), + "failed to get regulator value in register " + "%02x\n", r->reg); + return ret; + } + + regval &= AB5500_LDO_VOLT_MASK; + if (regval >= r->voltages_len || !r->voltages[regval]) + return -EINVAL; + + return r->voltages[regval]; +} + +static int ab5500_get_best_voltage_index(struct ab5500_regulator *r, + int min_uV, int max_uV) +{ + int bestmatch = INT_MAX; + int bestindex = -EINVAL; + int selindex; + int i; + + /* + * Locate the minimum voltage fitting the criteria on + * this regulator. The switchable voltages are not + * in strict falling order so we need to check them + * all for the best match. + */ + for (i = 0, selindex = 0; selindex < r->voltages_len; i++) { + int voltage = r->voltages[i]; + + if (!voltage) + continue; + + if (voltage <= max_uV && + voltage >= min_uV && + voltage < bestmatch) { + bestmatch = voltage; + bestindex = i; + } + + selindex++; + } + + return bestindex; +} + +static int ab5500_regulator_set_voltage(struct regulator_dev *rdev, + int min_uV, int max_uV) +{ + struct ab5500_regulators *ab5500 = rdev_get_drvdata(rdev); + struct ab5500_regulator *r = ab5500->regulator[rdev_get_id(rdev)]; + int bestindex; + + bestindex = ab5500_get_best_voltage_index(r, min_uV, max_uV); + if (bestindex < 0) { + dev_warn(rdev_get_dev(rdev), + "requested %d<=x<=%d uV, out of range!\n", + min_uV, max_uV); + return bestindex; + } + + return abx500_mask_and_set_register_interruptible(ab5500->dev, + r->bank, r->reg, AB5500_LDO_VOLT_MASK, bestindex); + +} + +static struct regulator_ops ab5500_regulator_variable_ops = { + .enable = ab5500_regulator_enable, + .disable = ab5500_regulator_disable, + .is_enabled = ab5500_regulator_is_enabled, + .enable_time = ab5500_regulator_enable_time, + .get_voltage = ab5500_regulator_get_voltage, + .set_voltage = ab5500_regulator_set_voltage, + .list_voltage = ab5500_regulator_list_voltage, +}; + +static struct regulator_ops ab5500_regulator_fixed_ops = { + .enable = ab5500_regulator_enable, + .disable = ab5500_regulator_disable, + .is_enabled = ab5500_regulator_is_enabled, + .enable_time = ab5500_regulator_enable_time, + .get_voltage = ab5500_regulator_fixed_get_voltage, + .list_voltage = ab5500_regulator_list_voltage, +}; + +static const int ab5500_ldo_s_voltages[] = { + [0x00] = 1200000, + [0x01] = 1050000, + [0x02] = 1100000, + [0x03] = 1500000, + [0x04] = 1800000, + [0x05] = 2200000, + [0x06] = 2500000, + [0x07] = 2790000, +}; + +static const int ab5500_ldo_d_voltages[] = { + [0x00] = 2100000, +}; + +static const int ab5500_ldo_lg_voltages[] = { + [0x00] = 1200000, + [0x01] = 0, /* not used */ + [0x02] = 1500000, + [0x03] = 1800000, + [0x04] = 0, /* not used */ + [0x05] = 2500000, + [0x06] = 2730000, + [0x07] = 2910000, +}; + +static const int ab5500_ldo_kh_voltages[] = { + [0x00] = 1200000, + [0x01] = 1500000, + [0x02] = 1800000, + [0x03] = 2100000, + [0x04] = 2500000, + [0x05] = 2750000, + [0x06] = 2790000, + [0x07] = 2910000, +}; + +static const int ab5500_ldo_vdigmic_voltages[] = { + [0x00] = 2100000, +}; + +static const int ab5500_ldo_sim_voltages[] = { + [0x00] = 1875000, + [0x01] = 2800000, + [0x02] = 2900000, +}; + +static struct ab5500_regulator ab5500_regulators[] = { + [AB5500_LDO_S] = { + .desc = { + .name = "LDO_S", + .id = AB5500_LDO_S, + .ops = &ab5500_regulator_variable_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + .bank = AB5500_BANK_STARTUP, + .reg = AB5500_LDO_S_ST, + .voltages = ab5500_ldo_s_voltages, + .voltages_len = ARRAY_SIZE(ab5500_ldo_s_voltages), + .enable_time = 400, + .pwrctrl = true, + }, + [AB5500_LDO_D] = { + .desc = { + .name = "LDO_D", + .id = AB5500_LDO_D, + .ops = &ab5500_regulator_fixed_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + .bank = AB5500_BANK_STARTUP, + .reg = AB5500_LDO_D_ST, + .voltages = ab5500_ldo_d_voltages, + .voltages_len = ARRAY_SIZE(ab5500_ldo_d_voltages), + .enable_time = 400, + .pwrctrl = true, + }, + [AB5500_LDO_L] = { + .desc = { + .name = "LDO_L", + .id = AB5500_LDO_L, + .ops = &ab5500_regulator_variable_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + .bank = AB5500_BANK_STARTUP, + .reg = AB5500_LDO_L_ST, + .voltages = ab5500_ldo_lg_voltages, + .voltages_len = ARRAY_SIZE(ab5500_ldo_lg_voltages) - 2, + .enable_time = 400, + }, + [AB5500_LDO_G] = { + .desc = { + .name = "LDO_G", + .id = AB5500_LDO_G, + .ops = &ab5500_regulator_variable_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + .bank = AB5500_BANK_STARTUP, + .reg = AB5500_LDO_G_ST, + .voltages = ab5500_ldo_lg_voltages, + .voltages_len = ARRAY_SIZE(ab5500_ldo_lg_voltages) - 2, + .enable_time = 400, + }, + [AB5500_LDO_K] = { + .desc = { + .name = "LDO_K", + .id = AB5500_LDO_K, + .ops = &ab5500_regulator_variable_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + .bank = AB5500_BANK_STARTUP, + .reg = AB5500_LDO_K_ST, + .voltages = ab5500_ldo_kh_voltages, + .voltages_len = ARRAY_SIZE(ab5500_ldo_kh_voltages), + .enable_time = 400, + }, + [AB5500_LDO_H] = { + .desc = { + .name = "LDO_H", + .id = AB5500_LDO_H, + .ops = &ab5500_regulator_variable_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + .bank = AB5500_BANK_STARTUP, + .reg = AB5500_LDO_H_ST, + .voltages = ab5500_ldo_kh_voltages, + .voltages_len = ARRAY_SIZE(ab5500_ldo_kh_voltages), + .enable_time = 400, + }, + [AB5500_LDO_VDIGMIC] = { + .desc = { + .name = "LDO_VDIGMIC", + .id = AB5500_LDO_VDIGMIC, + .ops = &ab5500_regulator_fixed_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + .bank = AB5500_BANK_STARTUP, + .reg = AB5500_LDO_VDIGMIC_ST, + .voltages = ab5500_ldo_vdigmic_voltages, + .voltages_len = ARRAY_SIZE(ab5500_ldo_vdigmic_voltages), + .enable_time = 450, + }, + [AB5500_LDO_SIM] = { + .desc = { + .name = "LDO_SIM", + .id = AB5500_LDO_SIM, + .ops = &ab5500_regulator_variable_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + .bank = AB5500_BANK_SIM_USBSIM, + .reg = AB5500_SIM_SUP, + .voltages = ab5500_ldo_sim_voltages, + .voltages_len = ARRAY_SIZE(ab5500_ldo_sim_voltages), + .enable_time = 1000, + }, +}; + + +static int __devinit ab5500_regulator_probe(struct platform_device *pdev) +{ + struct ab5500_platform_data *ppdata = pdev->dev.parent->platform_data; + struct ab5500_regulator_platform_data *pdata = ppdata->regulator; + struct ab5500_regulators *ab5500; + int err = 0; + int i; + + if (!pdata || !pdata->regulator) + return -EINVAL; + + ab5500 = kzalloc(sizeof(*ab5500), GFP_KERNEL); + if (!ab5500) + return -ENOMEM; + + ab5500->dev = &pdev->dev; + + platform_set_drvdata(pdev, ab5500); + + for (i = 0; i < AB5500_NUM_REGULATORS; i++) { + struct ab5500_regulator *regulator = &ab5500_regulators[i]; + struct regulator_dev *rdev; + + ab5500->regulator[i] = regulator; + + rdev = regulator_register(®ulator->desc, &pdev->dev, + &pdata->regulator[i], ab5500); + if (IS_ERR(rdev)) { + err = PTR_ERR(rdev); + dev_err(&pdev->dev, "failed to register regulator %s err %d\n", + regulator->desc.name, err); + goto err_unregister; + } + + ab5500->rdev[i] = rdev; + } + + return 0; + +err_unregister: + /* remove the already registered regulators */ + while (--i >= 0) + regulator_unregister(ab5500->rdev[i]); + + platform_set_drvdata(pdev, NULL); + kfree(ab5500); + + return err; +} + +static int __devexit ab5500_regulators_remove(struct platform_device *pdev) +{ + struct ab5500_regulators *ab5500 = platform_get_drvdata(pdev); + int i; + + for (i = 0; i < AB5500_NUM_REGULATORS; i++) + regulator_unregister(ab5500->rdev[i]); + + platform_set_drvdata(pdev, NULL); + kfree(ab5500); + + return 0; +} + +static struct platform_driver ab5500_regulator_driver = { + .driver = { + .name = "ab5500-regulator", + .owner = THIS_MODULE, + }, + .probe = ab5500_regulator_probe, + .remove = __devexit_p(ab5500_regulators_remove), +}; + +static __init int ab5500_regulator_init(void) +{ + return platform_driver_register(&ab5500_regulator_driver); +} + +static __exit void ab5500_regulator_exit(void) +{ + platform_driver_unregister(&ab5500_regulator_driver); +} + +subsys_initcall(ab5500_regulator_init); +module_exit(ab5500_regulator_exit); + +MODULE_DESCRIPTION("AB5500 Regulator Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:ab5500-regulator"); |