From 39b1772a24126d74699cea623f96b50ca6b6f08f Mon Sep 17 00:00:00 2001 From: Daniel Ribeiro Date: Sat, 27 Jun 2009 00:18:02 -0300 Subject: regulator: add pcap driver Add (partial) support for the voltage regulators on the PCAP2 PMIC. Signed-off-by: Daniel Ribeiro Signed-off-by: Liam Girdwood Signed-off-by: Samuel Ortiz --- drivers/regulator/Kconfig | 7 + drivers/regulator/Makefile | 1 + drivers/regulator/pcap-regulator.c | 318 +++++++++++++++++++++++++++++++++++++ 3 files changed, 326 insertions(+) create mode 100644 drivers/regulator/pcap-regulator.c (limited to 'drivers/regulator') diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index f4317798e47..da7483ef32b 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -117,4 +117,11 @@ config REGULATOR_LP3971 Say Y here to support the voltage regulators and convertors on National Semiconductors LP3971 PMIC +config REGULATOR_PCAP + tristate "PCAP2 regulator driver" + depends on EZX_PCAP + help + This driver provides support for the voltage regulators of the + PCAP2 PMIC. + endif diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index 4d762c4cccf..3a9748ff860 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -16,5 +16,6 @@ obj-$(CONFIG_REGULATOR_WM8350) += wm8350-regulator.o obj-$(CONFIG_REGULATOR_WM8400) += wm8400-regulator.o obj-$(CONFIG_REGULATOR_DA903X) += da903x.o obj-$(CONFIG_REGULATOR_PCF50633) += pcf50633-regulator.o +obj-$(CONFIG_REGULATOR_PCAP) += pcap-regulator.o ccflags-$(CONFIG_REGULATOR_DEBUG) += -DDEBUG diff --git a/drivers/regulator/pcap-regulator.c b/drivers/regulator/pcap-regulator.c new file mode 100644 index 00000000000..fdb90940ef0 --- /dev/null +++ b/drivers/regulator/pcap-regulator.c @@ -0,0 +1,318 @@ +/* + * PCAP2 Regulator Driver + * + * Copyright (c) 2009 Daniel Ribeiro + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +static const u16 V1_table[] = { + 2775, 1275, 1600, 1725, 1825, 1925, 2075, 2275, +}; + +static const u16 V2_table[] = { + 2500, 2775, +}; + +static const u16 V3_table[] = { + 1075, 1275, 1550, 1725, 1876, 1950, 2075, 2275, +}; + +static const u16 V4_table[] = { + 1275, 1550, 1725, 1875, 1950, 2075, 2275, 2775, +}; + +static const u16 V5_table[] = { + 1875, 2275, 2475, 2775, +}; + +static const u16 V6_table[] = { + 2475, 2775, +}; + +static const u16 V7_table[] = { + 1875, 2775, +}; + +#define V8_table V4_table + +static const u16 V9_table[] = { + 1575, 1875, 2475, 2775, +}; + +static const u16 V10_table[] = { + 5000, +}; + +static const u16 VAUX1_table[] = { + 1875, 2475, 2775, 3000, +}; + +#define VAUX2_table VAUX1_table + +static const u16 VAUX3_table[] = { + 1200, 1200, 1200, 1200, 1400, 1600, 1800, 2000, + 2200, 2400, 2600, 2800, 3000, 3200, 3400, 3600, +}; + +static const u16 VAUX4_table[] = { + 1800, 1800, 3000, 5000, +}; + +static const u16 VSIM_table[] = { + 1875, 3000, +}; + +static const u16 VSIM2_table[] = { + 1875, +}; + +static const u16 VVIB_table[] = { + 1300, 1800, 2000, 3000, +}; + +static const u16 SW1_table[] = { + 900, 950, 1000, 1050, 1100, 1150, 1200, 1250, + 1300, 1350, 1400, 1450, 1500, 1600, 1875, 2250, +}; + +#define SW2_table SW1_table + +static const u16 SW3_table[] = { + 4000, 4500, 5000, 5500, +}; + +struct pcap_regulator { + const u8 reg; + const u8 en; + const u8 index; + const u8 stby; + const u8 lowpwr; + const u8 n_voltages; + const u16 *voltage_table; +}; + +#define NA 0xff + +#define VREG_INFO(_vreg, _reg, _en, _index, _stby, _lowpwr) \ + [_vreg] = { \ + .reg = _reg, \ + .en = _en, \ + .index = _index, \ + .stby = _stby, \ + .lowpwr = _lowpwr, \ + .n_voltages = ARRAY_SIZE(_vreg##_table), \ + .voltage_table = _vreg##_table, \ + } + +static struct pcap_regulator vreg_table[] = { + VREG_INFO(V1, PCAP_REG_VREG1, 1, 2, 18, 0), + VREG_INFO(V2, PCAP_REG_VREG1, 5, 6, 19, 22), + VREG_INFO(V3, PCAP_REG_VREG1, 7, 8, 20, 23), + VREG_INFO(V4, PCAP_REG_VREG1, 11, 12, 21, 24), + /* V5 STBY and LOWPWR are on PCAP_REG_VREG2 */ + VREG_INFO(V5, PCAP_REG_VREG1, 15, 16, 12, 19), + + VREG_INFO(V6, PCAP_REG_VREG2, 1, 2, 14, 20), + VREG_INFO(V7, PCAP_REG_VREG2, 3, 4, 15, 21), + VREG_INFO(V8, PCAP_REG_VREG2, 5, 6, 16, 22), + VREG_INFO(V9, PCAP_REG_VREG2, 9, 10, 17, 23), + VREG_INFO(V10, PCAP_REG_VREG2, 10, NA, 18, 24), + + VREG_INFO(VAUX1, PCAP_REG_AUXVREG, 1, 2, 22, 23), + /* VAUX2 ... VSIM2 STBY and LOWPWR are on PCAP_REG_LOWPWR */ + VREG_INFO(VAUX2, PCAP_REG_AUXVREG, 4, 5, 0, 1), + VREG_INFO(VAUX3, PCAP_REG_AUXVREG, 7, 8, 2, 3), + VREG_INFO(VAUX4, PCAP_REG_AUXVREG, 12, 13, 4, 5), + VREG_INFO(VSIM, PCAP_REG_AUXVREG, 17, 18, NA, 6), + VREG_INFO(VSIM2, PCAP_REG_AUXVREG, 16, NA, NA, 7), + VREG_INFO(VVIB, PCAP_REG_AUXVREG, 19, 20, NA, NA), + + VREG_INFO(SW1, PCAP_REG_SWCTRL, 1, 2, NA, NA), + VREG_INFO(SW2, PCAP_REG_SWCTRL, 6, 7, NA, NA), + /* SW3 STBY is on PCAP_REG_AUXVREG */ + VREG_INFO(SW3, PCAP_REG_SWCTRL, 11, 12, 24, NA), + + /* SWxS used to control SWx voltage on standby */ +/* VREG_INFO(SW1S, PCAP_REG_LOWPWR, NA, 12, NA, NA), + VREG_INFO(SW2S, PCAP_REG_LOWPWR, NA, 20, NA, NA), */ +}; + +static int pcap_regulator_set_voltage(struct regulator_dev *rdev, + int min_uV, int max_uV) +{ + struct pcap_regulator *vreg = &vreg_table[rdev_get_id(rdev)]; + void *pcap = rdev_get_drvdata(rdev); + int uV; + u8 i; + + /* the regulator doesn't support voltage switching */ + if (vreg->n_voltages == 1) + return -EINVAL; + + for (i = 0; i < vreg->n_voltages; i++) { + /* For V1 the first is not the best match */ + if (i == 0 && rdev_get_id(rdev) == V1) + i = 1; + else if (i + 1 == vreg->n_voltages && rdev_get_id(rdev) == V1) + i = 0; + + uV = vreg->voltage_table[i] * 1000; + if (min_uV <= uV && uV <= max_uV) + return ezx_pcap_set_bits(pcap, vreg->reg, + (vreg->n_voltages - 1) << vreg->index, + i << vreg->index); + + if (i == 0 && rdev_get_id(rdev) == V1) + i = vreg->n_voltages - 1; + } + + /* the requested voltage range is not supported by this regulator */ + return -EINVAL; +} + +static int pcap_regulator_get_voltage(struct regulator_dev *rdev) +{ + struct pcap_regulator *vreg = &vreg_table[rdev_get_id(rdev)]; + void *pcap = rdev_get_drvdata(rdev); + u32 tmp; + int mV; + + if (vreg->n_voltages == 1) + return vreg->voltage_table[0] * 1000; + + ezx_pcap_read(pcap, vreg->reg, &tmp); + tmp = ((tmp >> vreg->index) & (vreg->n_voltages - 1)); + mV = vreg->voltage_table[tmp]; + + return mV * 1000; +} + +static int pcap_regulator_enable(struct regulator_dev *rdev) +{ + struct pcap_regulator *vreg = &vreg_table[rdev_get_id(rdev)]; + void *pcap = rdev_get_drvdata(rdev); + + if (vreg->en == NA) + return -EINVAL; + + return ezx_pcap_set_bits(pcap, vreg->reg, 1 << vreg->en, 1 << vreg->en); +} + +static int pcap_regulator_disable(struct regulator_dev *rdev) +{ + struct pcap_regulator *vreg = &vreg_table[rdev_get_id(rdev)]; + void *pcap = rdev_get_drvdata(rdev); + + if (vreg->en == NA) + return -EINVAL; + + return ezx_pcap_set_bits(pcap, vreg->reg, 1 << vreg->en, 0); +} + +static int pcap_regulator_is_enabled(struct regulator_dev *rdev) +{ + struct pcap_regulator *vreg = &vreg_table[rdev_get_id(rdev)]; + void *pcap = rdev_get_drvdata(rdev); + u32 tmp; + + if (vreg->en == NA) + return -EINVAL; + + ezx_pcap_read(pcap, vreg->reg, &tmp); + return (tmp >> vreg->en) & 1; +} + +static int pcap_regulator_list_voltage(struct regulator_dev *rdev, + unsigned int index) +{ + struct pcap_regulator *vreg = &vreg_table[rdev_get_id(rdev)]; + + return vreg->voltage_table[index] * 1000; +} + +static struct regulator_ops pcap_regulator_ops = { + .list_voltage = pcap_regulator_list_voltage, + .set_voltage = pcap_regulator_set_voltage, + .get_voltage = pcap_regulator_get_voltage, + .enable = pcap_regulator_enable, + .disable = pcap_regulator_disable, + .is_enabled = pcap_regulator_is_enabled, +}; + +#define VREG(_vreg) \ + [_vreg] = { \ + .name = #_vreg, \ + .id = _vreg, \ + .n_voltages = ARRAY_SIZE(_vreg##_table), \ + .ops = &pcap_regulator_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + } + +static struct regulator_desc pcap_regulators[] = { + VREG(V1), VREG(V2), VREG(V3), VREG(V4), VREG(V5), VREG(V6), VREG(V7), + VREG(V8), VREG(V9), VREG(V10), VREG(VAUX1), VREG(VAUX2), VREG(VAUX3), + VREG(VAUX4), VREG(VSIM), VREG(VSIM2), VREG(VVIB), VREG(SW1), VREG(SW2), +}; + +static int __devinit pcap_regulator_probe(struct platform_device *pdev) +{ + struct regulator_dev *rdev; + void *pcap = platform_get_drvdata(pdev); + + rdev = regulator_register(&pcap_regulators[pdev->id], &pdev->dev, + pdev->dev.platform_data, pcap); + if (IS_ERR(rdev)) + return PTR_ERR(rdev); + + platform_set_drvdata(pdev, rdev); + + return 0; +} + +static int __devexit pcap_regulator_remove(struct platform_device *pdev) +{ + struct regulator_dev *rdev = platform_get_drvdata(pdev); + + regulator_unregister(rdev); + + return 0; +} + +static struct platform_driver pcap_regulator_driver = { + .driver = { + .name = "pcap-regulator", + }, + .probe = pcap_regulator_probe, + .remove = __devexit_p(pcap_regulator_remove), +}; + +static int __init pcap_regulator_init(void) +{ + return platform_driver_register(&pcap_regulator_driver); +} + +static void __exit pcap_regulator_exit(void) +{ + platform_driver_unregister(&pcap_regulator_driver); +} + +module_init(pcap_regulator_init); +module_exit(pcap_regulator_exit); + +MODULE_AUTHOR("Daniel Ribeiro "); +MODULE_DESCRIPTION("PCAP2 Regulator Driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From be721979dd6b335e4ab6f83abb5cc11c33662aa8 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 4 Aug 2009 20:09:52 +0200 Subject: regulator: Provide mode to status conversion function This is useful for implementing get_status() in terms of get_mode(). Signed-off-by: Mark Brown Signed-off-by: Samuel Ortiz --- drivers/regulator/core.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) (limited to 'drivers/regulator') diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index 98c3a74e994..91ba9bfaa70 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -1864,6 +1864,30 @@ int regulator_notifier_call_chain(struct regulator_dev *rdev, } EXPORT_SYMBOL_GPL(regulator_notifier_call_chain); +/** + * regulator_mode_to_status - convert a regulator mode into a status + * + * @mode: Mode to convert + * + * Convert a regulator mode into a status. + */ +int regulator_mode_to_status(unsigned int mode) +{ + switch (mode) { + case REGULATOR_MODE_FAST: + return REGULATOR_STATUS_FAST; + case REGULATOR_MODE_NORMAL: + return REGULATOR_STATUS_NORMAL; + case REGULATOR_MODE_IDLE: + return REGULATOR_STATUS_IDLE; + case REGULATOR_STATUS_STANDBY: + return REGULATOR_STATUS_STANDBY; + default: + return 0; + } +} +EXPORT_SYMBOL_GPL(regulator_mode_to_status); + /* * To avoid cluttering sysfs (and memory) with useless state, only * create attributes that can be meaningfully displayed. -- cgit v1.2.3 From e4ee831f949a7c7746a56bcf1e7ca057d6f69e2a Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 28 Jul 2009 15:21:49 +0100 Subject: regulator: Add WM831x DC-DC buck convertor support The WM831x series of devices all have 3 DC-DC buck convertors. This driver implements software control for these regulators via the regulator API. Use with split hardware/software control of individual regulators is not supported, though regulators not controlled by software may be controlled via the hardware control interfaces. Signed-off-by: Mark Brown Acked-by: Liam Girdwood Signed-off-by: Samuel Ortiz --- drivers/regulator/Kconfig | 7 + drivers/regulator/Makefile | 1 + drivers/regulator/wm831x-dcdc.c | 643 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 651 insertions(+) create mode 100644 drivers/regulator/wm831x-dcdc.c (limited to 'drivers/regulator') diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index da7483ef32b..38ea5dc8e14 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -82,6 +82,13 @@ config REGULATOR_TWL4030 This driver supports the voltage regulators provided by this family of companion chips. +config REGULATOR_WM831X + tristate "Wolfson Microelcronics WM831x PMIC regulators" + depends on MFD_WM831X + help + Support the voltage and current regulators of the WM831x series + of PMIC devices. + config REGULATOR_WM8350 tristate "Wolfson Microelectroncis WM8350 AudioPlus PMIC" depends on MFD_WM8350 diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index 3a9748ff860..b1d2b826f53 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_REGULATOR_BQ24022) += bq24022.o obj-$(CONFIG_REGULATOR_LP3971) += lp3971.o obj-$(CONFIG_REGULATOR_MAX1586) += max1586.o obj-$(CONFIG_REGULATOR_TWL4030) += twl4030-regulator.o +obj-$(CONFIG_REGULATOR_WM831X) += wm831x-dcdc.o obj-$(CONFIG_REGULATOR_WM8350) += wm8350-regulator.o obj-$(CONFIG_REGULATOR_WM8400) += wm8400-regulator.o obj-$(CONFIG_REGULATOR_DA903X) += da903x.o diff --git a/drivers/regulator/wm831x-dcdc.c b/drivers/regulator/wm831x-dcdc.c new file mode 100644 index 00000000000..fa5126e38ac --- /dev/null +++ b/drivers/regulator/wm831x-dcdc.c @@ -0,0 +1,643 @@ +/* + * wm831x-dcdc.c -- DC-DC buck convertor driver for the WM831x series + * + * Copyright 2009 Wolfson Microelectronics PLC. + * + * Author: Mark Brown + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define WM831X_BUCKV_MAX_SELECTOR 0x68 +#define WM831X_BUCKP_MAX_SELECTOR 0x66 + +#define WM831X_DCDC_MODE_FAST 0 +#define WM831X_DCDC_MODE_NORMAL 1 +#define WM831X_DCDC_MODE_IDLE 2 +#define WM831X_DCDC_MODE_STANDBY 3 + +#define WM831X_DCDC_MAX_NAME 6 + +/* Register offsets in control block */ +#define WM831X_DCDC_CONTROL_1 0 +#define WM831X_DCDC_CONTROL_2 1 +#define WM831X_DCDC_ON_CONFIG 2 +#define WM831X_DCDC_SLEEP_CONTROL 3 + +/* + * Shared + */ + +struct wm831x_dcdc { + char name[WM831X_DCDC_MAX_NAME]; + struct regulator_desc desc; + int base; + struct wm831x *wm831x; + struct regulator_dev *regulator; +}; + +static int wm831x_dcdc_is_enabled(struct regulator_dev *rdev) +{ + struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev); + struct wm831x *wm831x = dcdc->wm831x; + int mask = 1 << rdev_get_id(rdev); + int reg; + + reg = wm831x_reg_read(wm831x, WM831X_DCDC_ENABLE); + if (reg < 0) + return reg; + + if (reg & mask) + return 1; + else + return 0; +} + +static int wm831x_dcdc_enable(struct regulator_dev *rdev) +{ + struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev); + struct wm831x *wm831x = dcdc->wm831x; + int mask = 1 << rdev_get_id(rdev); + + return wm831x_set_bits(wm831x, WM831X_DCDC_ENABLE, mask, mask); +} + +static int wm831x_dcdc_disable(struct regulator_dev *rdev) +{ + struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev); + struct wm831x *wm831x = dcdc->wm831x; + int mask = 1 << rdev_get_id(rdev); + + return wm831x_set_bits(wm831x, WM831X_DCDC_ENABLE, mask, 0); +} + +static unsigned int wm831x_dcdc_get_mode(struct regulator_dev *rdev) + +{ + struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev); + struct wm831x *wm831x = dcdc->wm831x; + u16 reg = dcdc->base + WM831X_DCDC_ON_CONFIG; + int val; + + val = wm831x_reg_read(wm831x, reg); + if (val < 0) + return val; + + val = (val & WM831X_DC1_ON_MODE_MASK) >> WM831X_DC1_ON_MODE_SHIFT; + + switch (val) { + case WM831X_DCDC_MODE_FAST: + return REGULATOR_MODE_FAST; + case WM831X_DCDC_MODE_NORMAL: + return REGULATOR_MODE_NORMAL; + case WM831X_DCDC_MODE_STANDBY: + return REGULATOR_MODE_STANDBY; + case WM831X_DCDC_MODE_IDLE: + return REGULATOR_MODE_IDLE; + default: + BUG(); + } +} + +static int wm831x_dcdc_set_mode_int(struct wm831x *wm831x, int reg, + unsigned int mode) +{ + int val; + + switch (mode) { + case REGULATOR_MODE_FAST: + val = WM831X_DCDC_MODE_FAST; + break; + case REGULATOR_MODE_NORMAL: + val = WM831X_DCDC_MODE_NORMAL; + break; + case REGULATOR_MODE_STANDBY: + val = WM831X_DCDC_MODE_STANDBY; + break; + case REGULATOR_MODE_IDLE: + val = WM831X_DCDC_MODE_IDLE; + break; + default: + return -EINVAL; + } + + return wm831x_set_bits(wm831x, reg, WM831X_DC1_ON_MODE_MASK, + val << WM831X_DC1_ON_MODE_SHIFT); +} + +static int wm831x_dcdc_set_mode(struct regulator_dev *rdev, unsigned int mode) +{ + struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev); + struct wm831x *wm831x = dcdc->wm831x; + u16 reg = dcdc->base + WM831X_DCDC_ON_CONFIG; + + return wm831x_dcdc_set_mode_int(wm831x, reg, mode); +} + +static int wm831x_dcdc_set_suspend_mode(struct regulator_dev *rdev, + unsigned int mode) +{ + struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev); + struct wm831x *wm831x = dcdc->wm831x; + u16 reg = dcdc->base + WM831X_DCDC_SLEEP_CONTROL; + + return wm831x_dcdc_set_mode_int(wm831x, reg, mode); +} + +static int wm831x_dcdc_get_status(struct regulator_dev *rdev) +{ + struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev); + struct wm831x *wm831x = dcdc->wm831x; + int ret; + + /* First, check for errors */ + ret = wm831x_reg_read(wm831x, WM831X_DCDC_UV_STATUS); + if (ret < 0) + return ret; + + if (ret & (1 << rdev_get_id(rdev))) { + dev_dbg(wm831x->dev, "DCDC%d under voltage\n", + rdev_get_id(rdev) + 1); + return REGULATOR_STATUS_ERROR; + } + + /* DCDC1 and DCDC2 can additionally detect high voltage/current */ + if (rdev_get_id(rdev) < 2) { + if (ret & (WM831X_DC1_OV_STS << rdev_get_id(rdev))) { + dev_dbg(wm831x->dev, "DCDC%d over voltage\n", + rdev_get_id(rdev) + 1); + return REGULATOR_STATUS_ERROR; + } + + if (ret & (WM831X_DC1_HC_STS << rdev_get_id(rdev))) { + dev_dbg(wm831x->dev, "DCDC%d over current\n", + rdev_get_id(rdev) + 1); + return REGULATOR_STATUS_ERROR; + } + } + + /* Is the regulator on? */ + ret = wm831x_reg_read(wm831x, WM831X_DCDC_STATUS); + if (ret < 0) + return ret; + if (!(ret & (1 << rdev_get_id(rdev)))) + return REGULATOR_STATUS_OFF; + + /* TODO: When we handle hardware control modes so we can report the + * current mode. */ + return REGULATOR_STATUS_ON; +} + +static irqreturn_t wm831x_dcdc_uv_irq(int irq, void *data) +{ + struct wm831x_dcdc *dcdc = data; + + regulator_notifier_call_chain(dcdc->regulator, + REGULATOR_EVENT_UNDER_VOLTAGE, + NULL); + + return IRQ_HANDLED; +} + +static irqreturn_t wm831x_dcdc_oc_irq(int irq, void *data) +{ + struct wm831x_dcdc *dcdc = data; + + regulator_notifier_call_chain(dcdc->regulator, + REGULATOR_EVENT_OVER_CURRENT, + NULL); + + return IRQ_HANDLED; +} + +/* + * BUCKV specifics + */ + +static int wm831x_buckv_list_voltage(struct regulator_dev *rdev, + unsigned selector) +{ + if (selector <= 0x8) + return 600000; + if (selector <= WM831X_BUCKV_MAX_SELECTOR) + return 600000 + ((selector - 0x8) * 12500); + return -EINVAL; +} + +static int wm831x_buckv_set_voltage_int(struct regulator_dev *rdev, int reg, + int min_uV, int max_uV) +{ + struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev); + struct wm831x *wm831x = dcdc->wm831x; + u16 vsel; + + if (min_uV < 600000) + vsel = 0; + else if (min_uV <= 1800000) + vsel = ((min_uV - 600000) / 12500) + 8; + else + return -EINVAL; + + if (wm831x_buckv_list_voltage(rdev, vsel) > max_uV) + return -EINVAL; + + return wm831x_set_bits(wm831x, reg, WM831X_DC1_ON_VSEL_MASK, vsel); +} + +static int wm831x_buckv_set_voltage(struct regulator_dev *rdev, + int min_uV, int max_uV) +{ + struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev); + u16 reg = dcdc->base + WM831X_DCDC_ON_CONFIG; + + return wm831x_buckv_set_voltage_int(rdev, reg, min_uV, max_uV); +} + +static int wm831x_buckv_set_suspend_voltage(struct regulator_dev *rdev, + int uV) +{ + struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev); + u16 reg = dcdc->base + WM831X_DCDC_SLEEP_CONTROL; + + return wm831x_buckv_set_voltage_int(rdev, reg, uV, uV); +} + +static int wm831x_buckv_get_voltage(struct regulator_dev *rdev) +{ + struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev); + struct wm831x *wm831x = dcdc->wm831x; + u16 reg = dcdc->base + WM831X_DCDC_ON_CONFIG; + int val; + + val = wm831x_reg_read(wm831x, reg); + if (val < 0) + return val; + + return wm831x_buckv_list_voltage(rdev, val & WM831X_DC1_ON_VSEL_MASK); +} + +/* Current limit options */ +static u16 wm831x_dcdc_ilim[] = { + 125, 250, 375, 500, 625, 750, 875, 1000 +}; + +static int wm831x_buckv_set_current_limit(struct regulator_dev *rdev, + int min_uA, int max_uA) +{ + struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev); + struct wm831x *wm831x = dcdc->wm831x; + u16 reg = dcdc->base + WM831X_DCDC_CONTROL_2; + int i; + + for (i = 0; i < ARRAY_SIZE(wm831x_dcdc_ilim); i++) { + if (max_uA <= wm831x_dcdc_ilim[i]) + break; + } + if (i == ARRAY_SIZE(wm831x_dcdc_ilim)) + return -EINVAL; + + return wm831x_set_bits(wm831x, reg, WM831X_DC1_HC_THR_MASK, i); +} + +static int wm831x_buckv_get_current_limit(struct regulator_dev *rdev) +{ + struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev); + struct wm831x *wm831x = dcdc->wm831x; + u16 reg = dcdc->base + WM831X_DCDC_CONTROL_2; + int val; + + val = wm831x_reg_read(wm831x, reg); + if (val < 0) + return val; + + return wm831x_dcdc_ilim[val & WM831X_DC1_HC_THR_MASK]; +} + +static struct regulator_ops wm831x_buckv_ops = { + .set_voltage = wm831x_buckv_set_voltage, + .get_voltage = wm831x_buckv_get_voltage, + .list_voltage = wm831x_buckv_list_voltage, + .set_suspend_voltage = wm831x_buckv_set_suspend_voltage, + .set_current_limit = wm831x_buckv_set_current_limit, + .get_current_limit = wm831x_buckv_get_current_limit, + + .is_enabled = wm831x_dcdc_is_enabled, + .enable = wm831x_dcdc_enable, + .disable = wm831x_dcdc_disable, + .get_status = wm831x_dcdc_get_status, + .get_mode = wm831x_dcdc_get_mode, + .set_mode = wm831x_dcdc_set_mode, + .set_suspend_mode = wm831x_dcdc_set_suspend_mode, +}; + +static __devinit int wm831x_buckv_probe(struct platform_device *pdev) +{ + struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent); + struct wm831x_pdata *pdata = wm831x->dev->platform_data; + int id = pdev->id % ARRAY_SIZE(pdata->dcdc); + struct wm831x_dcdc *dcdc; + struct resource *res; + int ret, irq; + + dev_dbg(&pdev->dev, "Probing DCDC%d\n", id + 1); + + if (pdata == NULL || pdata->dcdc[id] == NULL) + return -ENODEV; + + dcdc = kzalloc(sizeof(struct wm831x_dcdc), GFP_KERNEL); + if (dcdc == NULL) { + dev_err(&pdev->dev, "Unable to allocate private data\n"); + return -ENOMEM; + } + + dcdc->wm831x = wm831x; + + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (res == NULL) { + dev_err(&pdev->dev, "No I/O resource\n"); + ret = -EINVAL; + goto err; + } + dcdc->base = res->start; + + snprintf(dcdc->name, sizeof(dcdc->name), "DCDC%d", id + 1); + dcdc->desc.name = dcdc->name; + dcdc->desc.id = id; + dcdc->desc.type = REGULATOR_VOLTAGE; + dcdc->desc.n_voltages = WM831X_BUCKV_MAX_SELECTOR + 1; + dcdc->desc.ops = &wm831x_buckv_ops; + dcdc->desc.owner = THIS_MODULE; + + dcdc->regulator = regulator_register(&dcdc->desc, &pdev->dev, + pdata->dcdc[id], dcdc); + if (IS_ERR(dcdc->regulator)) { + ret = PTR_ERR(dcdc->regulator); + dev_err(wm831x->dev, "Failed to register DCDC%d: %d\n", + id + 1, ret); + goto err; + } + + irq = platform_get_irq_byname(pdev, "UV"); + ret = wm831x_request_irq(wm831x, irq, wm831x_dcdc_uv_irq, + IRQF_TRIGGER_RISING, dcdc->name, + dcdc); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to request UV IRQ %d: %d\n", + irq, ret); + goto err_regulator; + } + + irq = platform_get_irq_byname(pdev, "HC"); + ret = wm831x_request_irq(wm831x, irq, wm831x_dcdc_oc_irq, + IRQF_TRIGGER_RISING, dcdc->name, + dcdc); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to request HC IRQ %d: %d\n", + irq, ret); + goto err_uv; + } + + platform_set_drvdata(pdev, dcdc); + + return 0; + +err_uv: + wm831x_free_irq(wm831x, platform_get_irq_byname(pdev, "UV"), dcdc); +err_regulator: + regulator_unregister(dcdc->regulator); +err: + kfree(dcdc); + return ret; +} + +static __devexit int wm831x_buckv_remove(struct platform_device *pdev) +{ + struct wm831x_dcdc *dcdc = platform_get_drvdata(pdev); + struct wm831x *wm831x = dcdc->wm831x; + + wm831x_free_irq(wm831x, platform_get_irq_byname(pdev, "HC"), dcdc); + wm831x_free_irq(wm831x, platform_get_irq_byname(pdev, "UV"), dcdc); + regulator_unregister(dcdc->regulator); + kfree(dcdc); + + return 0; +} + +static struct platform_driver wm831x_buckv_driver = { + .probe = wm831x_buckv_probe, + .remove = __devexit_p(wm831x_buckv_remove), + .driver = { + .name = "wm831x-buckv", + }, +}; + +/* + * BUCKP specifics + */ + +static int wm831x_buckp_list_voltage(struct regulator_dev *rdev, + unsigned selector) +{ + if (selector <= WM831X_BUCKP_MAX_SELECTOR) + return 850000 + (selector * 25000); + else + return -EINVAL; +} + +static int wm831x_buckp_set_voltage_int(struct regulator_dev *rdev, int reg, + int min_uV, int max_uV) +{ + struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev); + struct wm831x *wm831x = dcdc->wm831x; + u16 vsel; + + if (min_uV <= 34000000) + vsel = (min_uV - 850000) / 25000; + else + return -EINVAL; + + if (wm831x_buckp_list_voltage(rdev, vsel) > max_uV) + return -EINVAL; + + return wm831x_set_bits(wm831x, reg, WM831X_DC3_ON_VSEL_MASK, vsel); +} + +static int wm831x_buckp_set_voltage(struct regulator_dev *rdev, + int min_uV, int max_uV) +{ + struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev); + u16 reg = dcdc->base + WM831X_DCDC_ON_CONFIG; + + return wm831x_buckp_set_voltage_int(rdev, reg, min_uV, max_uV); +} + +static int wm831x_buckp_set_suspend_voltage(struct regulator_dev *rdev, + int uV) +{ + struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev); + u16 reg = dcdc->base + WM831X_DCDC_SLEEP_CONTROL; + + return wm831x_buckp_set_voltage_int(rdev, reg, uV, uV); +} + +static int wm831x_buckp_get_voltage(struct regulator_dev *rdev) +{ + struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev); + struct wm831x *wm831x = dcdc->wm831x; + u16 reg = dcdc->base + WM831X_DCDC_ON_CONFIG; + int val; + + val = wm831x_reg_read(wm831x, reg); + if (val < 0) + return val; + + return wm831x_buckp_list_voltage(rdev, val & WM831X_DC3_ON_VSEL_MASK); +} + +static struct regulator_ops wm831x_buckp_ops = { + .set_voltage = wm831x_buckp_set_voltage, + .get_voltage = wm831x_buckp_get_voltage, + .list_voltage = wm831x_buckp_list_voltage, + .set_suspend_voltage = wm831x_buckp_set_suspend_voltage, + + .is_enabled = wm831x_dcdc_is_enabled, + .enable = wm831x_dcdc_enable, + .disable = wm831x_dcdc_disable, + .get_status = wm831x_dcdc_get_status, + .get_mode = wm831x_dcdc_get_mode, + .set_mode = wm831x_dcdc_set_mode, + .set_suspend_mode = wm831x_dcdc_set_suspend_mode, +}; + +static __devinit int wm831x_buckp_probe(struct platform_device *pdev) +{ + struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent); + struct wm831x_pdata *pdata = wm831x->dev->platform_data; + int id = pdev->id % ARRAY_SIZE(pdata->dcdc); + struct wm831x_dcdc *dcdc; + struct resource *res; + int ret, irq; + + dev_dbg(&pdev->dev, "Probing DCDC%d\n", id + 1); + + if (pdata == NULL || pdata->dcdc[id] == NULL) + return -ENODEV; + + dcdc = kzalloc(sizeof(struct wm831x_dcdc), GFP_KERNEL); + if (dcdc == NULL) { + dev_err(&pdev->dev, "Unable to allocate private data\n"); + return -ENOMEM; + } + + dcdc->wm831x = wm831x; + + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (res == NULL) { + dev_err(&pdev->dev, "No I/O resource\n"); + ret = -EINVAL; + goto err; + } + dcdc->base = res->start; + + snprintf(dcdc->name, sizeof(dcdc->name), "DCDC%d", id + 1); + dcdc->desc.name = dcdc->name; + dcdc->desc.id = id; + dcdc->desc.type = REGULATOR_VOLTAGE; + dcdc->desc.n_voltages = WM831X_BUCKP_MAX_SELECTOR + 1; + dcdc->desc.ops = &wm831x_buckp_ops; + dcdc->desc.owner = THIS_MODULE; + + dcdc->regulator = regulator_register(&dcdc->desc, &pdev->dev, + pdata->dcdc[id], dcdc); + if (IS_ERR(dcdc->regulator)) { + ret = PTR_ERR(dcdc->regulator); + dev_err(wm831x->dev, "Failed to register DCDC%d: %d\n", + id + 1, ret); + goto err; + } + + irq = platform_get_irq_byname(pdev, "UV"); + ret = wm831x_request_irq(wm831x, irq, wm831x_dcdc_uv_irq, + IRQF_TRIGGER_RISING, dcdc->name, + dcdc); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to request UV IRQ %d: %d\n", + irq, ret); + goto err_regulator; + } + + platform_set_drvdata(pdev, dcdc); + + return 0; + +err_regulator: + regulator_unregister(dcdc->regulator); +err: + kfree(dcdc); + return ret; +} + +static __devexit int wm831x_buckp_remove(struct platform_device *pdev) +{ + struct wm831x_dcdc *dcdc = platform_get_drvdata(pdev); + struct wm831x *wm831x = dcdc->wm831x; + + wm831x_free_irq(wm831x, platform_get_irq_byname(pdev, "UV"), dcdc); + regulator_unregister(dcdc->regulator); + kfree(dcdc); + + return 0; +} + +static struct platform_driver wm831x_buckp_driver = { + .probe = wm831x_buckp_probe, + .remove = __devexit_p(wm831x_buckp_remove), + .driver = { + .name = "wm831x-buckp", + }, +}; + +static int __init wm831x_dcdc_init(void) +{ + int ret; + ret = platform_driver_register(&wm831x_buckv_driver); + if (ret != 0) + pr_err("Failed to register WM831x BUCKV driver: %d\n", ret); + + ret = platform_driver_register(&wm831x_buckp_driver); + if (ret != 0) + pr_err("Failed to register WM831x BUCKP driver: %d\n", ret); + + return 0; +} +subsys_initcall(wm831x_dcdc_init); + +static void __exit wm831x_dcdc_exit(void) +{ + platform_driver_unregister(&wm831x_buckp_driver); + platform_driver_unregister(&wm831x_buckv_driver); +} +module_exit(wm831x_dcdc_exit); + +/* Module information */ +MODULE_AUTHOR("Mark Brown"); +MODULE_DESCRIPTION("WM831x DC-DC convertor driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:wm831x-buckv"); +MODULE_ALIAS("platform:wm831x-buckp"); -- cgit v1.2.3 From d1c6b4fe668b2ae02f490deee86eaab60822a362 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 28 Jul 2009 15:22:02 +0100 Subject: regulator: Add WM831x LDO support The WM831x series of devices provide three types of LDO: - General purpose LDOs supporting voltages from 0.9-3.3V - High performance analogue LDOs supporting voltages from 1-3.5V - Very low power consumption LDOs intended to support always on functionality. This patch adds support for all three kinds of LDO. Each regulator is probed as an individual platform device with resources used to provide the register map location of the regulator. Mixed hardware and software control of regulators is not current supported. Signed-off-by: Mark Brown Acked-by: Liam Girdwood Signed-off-by: Samuel Ortiz --- drivers/regulator/Makefile | 1 + drivers/regulator/wm831x-ldo.c | 852 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 853 insertions(+) create mode 100644 drivers/regulator/wm831x-ldo.c (limited to 'drivers/regulator') diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index b1d2b826f53..a0a635fdae8 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -13,6 +13,7 @@ obj-$(CONFIG_REGULATOR_LP3971) += lp3971.o obj-$(CONFIG_REGULATOR_MAX1586) += max1586.o obj-$(CONFIG_REGULATOR_TWL4030) += twl4030-regulator.o obj-$(CONFIG_REGULATOR_WM831X) += wm831x-dcdc.o +obj-$(CONFIG_REGULATOR_WM831X) += wm831x-ldo.o obj-$(CONFIG_REGULATOR_WM8350) += wm8350-regulator.o obj-$(CONFIG_REGULATOR_WM8400) += wm8400-regulator.o obj-$(CONFIG_REGULATOR_DA903X) += da903x.o diff --git a/drivers/regulator/wm831x-ldo.c b/drivers/regulator/wm831x-ldo.c new file mode 100644 index 00000000000..bb61aede480 --- /dev/null +++ b/drivers/regulator/wm831x-ldo.c @@ -0,0 +1,852 @@ +/* + * wm831x-ldo.c -- LDO driver for the WM831x series + * + * Copyright 2009 Wolfson Microelectronics PLC. + * + * Author: Mark Brown + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define WM831X_LDO_MAX_NAME 6 + +#define WM831X_LDO_CONTROL 0 +#define WM831X_LDO_ON_CONTROL 1 +#define WM831X_LDO_SLEEP_CONTROL 2 + +#define WM831X_ALIVE_LDO_ON_CONTROL 0 +#define WM831X_ALIVE_LDO_SLEEP_CONTROL 1 + +struct wm831x_ldo { + char name[WM831X_LDO_MAX_NAME]; + struct regulator_desc desc; + int base; + struct wm831x *wm831x; + struct regulator_dev *regulator; +}; + +/* + * Shared + */ + +static int wm831x_ldo_is_enabled(struct regulator_dev *rdev) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + struct wm831x *wm831x = ldo->wm831x; + int mask = 1 << rdev_get_id(rdev); + int reg; + + reg = wm831x_reg_read(wm831x, WM831X_LDO_ENABLE); + if (reg < 0) + return reg; + + if (reg & mask) + return 1; + else + return 0; +} + +static int wm831x_ldo_enable(struct regulator_dev *rdev) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + struct wm831x *wm831x = ldo->wm831x; + int mask = 1 << rdev_get_id(rdev); + + return wm831x_set_bits(wm831x, WM831X_LDO_ENABLE, mask, mask); +} + +static int wm831x_ldo_disable(struct regulator_dev *rdev) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + struct wm831x *wm831x = ldo->wm831x; + int mask = 1 << rdev_get_id(rdev); + + return wm831x_set_bits(wm831x, WM831X_LDO_ENABLE, mask, 0); +} + +static irqreturn_t wm831x_ldo_uv_irq(int irq, void *data) +{ + struct wm831x_ldo *ldo = data; + + regulator_notifier_call_chain(ldo->regulator, + REGULATOR_EVENT_UNDER_VOLTAGE, + NULL); + + return IRQ_HANDLED; +} + +/* + * General purpose LDOs + */ + +#define WM831X_GP_LDO_SELECTOR_LOW 0xe +#define WM831X_GP_LDO_MAX_SELECTOR 0x1f + +static int wm831x_gp_ldo_list_voltage(struct regulator_dev *rdev, + unsigned int selector) +{ + /* 0.9-1.6V in 50mV steps */ + if (selector <= WM831X_GP_LDO_SELECTOR_LOW) + return 900000 + (selector * 50000); + /* 1.7-3.3V in 50mV steps */ + if (selector <= WM831X_GP_LDO_MAX_SELECTOR) + return 1600000 + ((selector - WM831X_GP_LDO_SELECTOR_LOW) + * 100000); + return -EINVAL; +} + +static int wm831x_gp_ldo_set_voltage_int(struct regulator_dev *rdev, int reg, + int min_uV, int max_uV) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + struct wm831x *wm831x = ldo->wm831x; + int vsel, ret; + + if (min_uV < 900000) + vsel = 0; + else if (min_uV < 1700000) + vsel = ((min_uV - 900000) / 50000); + else + vsel = ((min_uV - 1700000) / 100000) + + WM831X_GP_LDO_SELECTOR_LOW + 1; + + ret = wm831x_gp_ldo_list_voltage(rdev, vsel); + if (ret < 0) + return ret; + if (ret < min_uV || ret > max_uV) + return -EINVAL; + + return wm831x_set_bits(wm831x, reg, WM831X_LDO1_ON_VSEL_MASK, vsel); +} + +static int wm831x_gp_ldo_set_voltage(struct regulator_dev *rdev, + int min_uV, int max_uV) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + int reg = ldo->base + WM831X_LDO_ON_CONTROL; + + return wm831x_gp_ldo_set_voltage_int(rdev, reg, min_uV, max_uV); +} + +static int wm831x_gp_ldo_set_suspend_voltage(struct regulator_dev *rdev, + int uV) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + int reg = ldo->base + WM831X_LDO_SLEEP_CONTROL; + + return wm831x_gp_ldo_set_voltage_int(rdev, reg, uV, uV); +} + +static int wm831x_gp_ldo_get_voltage(struct regulator_dev *rdev) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + struct wm831x *wm831x = ldo->wm831x; + int reg = ldo->base + WM831X_LDO_ON_CONTROL; + int ret; + + ret = wm831x_reg_read(wm831x, reg); + if (ret < 0) + return ret; + + ret &= WM831X_LDO1_ON_VSEL_MASK; + + return wm831x_gp_ldo_list_voltage(rdev, ret); +} + +static unsigned int wm831x_gp_ldo_get_mode(struct regulator_dev *rdev) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + struct wm831x *wm831x = ldo->wm831x; + int ctrl_reg = ldo->base + WM831X_LDO_CONTROL; + int on_reg = ldo->base + WM831X_LDO_ON_CONTROL; + unsigned int ret; + + ret = wm831x_reg_read(wm831x, on_reg); + if (ret < 0) + return 0; + + if (!(ret & WM831X_LDO1_ON_MODE)) + return REGULATOR_MODE_NORMAL; + + ret = wm831x_reg_read(wm831x, ctrl_reg); + if (ret < 0) + return 0; + + if (ret & WM831X_LDO1_LP_MODE) + return REGULATOR_MODE_STANDBY; + else + return REGULATOR_MODE_IDLE; +} + +static int wm831x_gp_ldo_set_mode(struct regulator_dev *rdev, + unsigned int mode) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + struct wm831x *wm831x = ldo->wm831x; + int ctrl_reg = ldo->base + WM831X_LDO_CONTROL; + int on_reg = ldo->base + WM831X_LDO_ON_CONTROL; + int ret; + + + switch (mode) { + case REGULATOR_MODE_NORMAL: + ret = wm831x_set_bits(wm831x, on_reg, + WM831X_LDO1_ON_MODE, 0); + if (ret < 0) + return ret; + break; + + case REGULATOR_MODE_IDLE: + ret = wm831x_set_bits(wm831x, ctrl_reg, + WM831X_LDO1_LP_MODE, + WM831X_LDO1_LP_MODE); + if (ret < 0) + return ret; + + ret = wm831x_set_bits(wm831x, on_reg, + WM831X_LDO1_ON_MODE, + WM831X_LDO1_ON_MODE); + if (ret < 0) + return ret; + + case REGULATOR_MODE_STANDBY: + ret = wm831x_set_bits(wm831x, ctrl_reg, + WM831X_LDO1_LP_MODE, 0); + if (ret < 0) + return ret; + + ret = wm831x_set_bits(wm831x, on_reg, + WM831X_LDO1_ON_MODE, + WM831X_LDO1_ON_MODE); + if (ret < 0) + return ret; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int wm831x_gp_ldo_get_status(struct regulator_dev *rdev) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + struct wm831x *wm831x = ldo->wm831x; + int mask = 1 << rdev_get_id(rdev); + int ret; + + /* Is the regulator on? */ + ret = wm831x_reg_read(wm831x, WM831X_LDO_STATUS); + if (ret < 0) + return ret; + if (!(ret & mask)) + return REGULATOR_STATUS_OFF; + + /* Is it reporting under voltage? */ + ret = wm831x_reg_read(wm831x, WM831X_LDO_UV_STATUS); + if (ret & mask) + return REGULATOR_STATUS_ERROR; + + ret = wm831x_gp_ldo_get_mode(rdev); + if (ret < 0) + return ret; + else + return regulator_mode_to_status(ret); +} + +static unsigned int wm831x_gp_ldo_get_optimum_mode(struct regulator_dev *rdev, + int input_uV, + int output_uV, int load_uA) +{ + if (load_uA < 20000) + return REGULATOR_MODE_STANDBY; + if (load_uA < 50000) + return REGULATOR_MODE_IDLE; + return REGULATOR_MODE_NORMAL; +} + + +static struct regulator_ops wm831x_gp_ldo_ops = { + .list_voltage = wm831x_gp_ldo_list_voltage, + .get_voltage = wm831x_gp_ldo_get_voltage, + .set_voltage = wm831x_gp_ldo_set_voltage, + .set_suspend_voltage = wm831x_gp_ldo_set_suspend_voltage, + .get_mode = wm831x_gp_ldo_get_mode, + .set_mode = wm831x_gp_ldo_set_mode, + .get_status = wm831x_gp_ldo_get_status, + .get_optimum_mode = wm831x_gp_ldo_get_optimum_mode, + + .is_enabled = wm831x_ldo_is_enabled, + .enable = wm831x_ldo_enable, + .disable = wm831x_ldo_disable, +}; + +static __devinit int wm831x_gp_ldo_probe(struct platform_device *pdev) +{ + struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent); + struct wm831x_pdata *pdata = wm831x->dev->platform_data; + int id = pdev->id % ARRAY_SIZE(pdata->ldo); + struct wm831x_ldo *ldo; + struct resource *res; + int ret, irq; + + dev_dbg(&pdev->dev, "Probing LDO%d\n", id + 1); + + if (pdata == NULL || pdata->ldo[id] == NULL) + return -ENODEV; + + ldo = kzalloc(sizeof(struct wm831x_ldo), GFP_KERNEL); + if (ldo == NULL) { + dev_err(&pdev->dev, "Unable to allocate private data\n"); + return -ENOMEM; + } + + ldo->wm831x = wm831x; + + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (res == NULL) { + dev_err(&pdev->dev, "No I/O resource\n"); + ret = -EINVAL; + goto err; + } + ldo->base = res->start; + + snprintf(ldo->name, sizeof(ldo->name), "LDO%d", id + 1); + ldo->desc.name = ldo->name; + ldo->desc.id = id; + ldo->desc.type = REGULATOR_VOLTAGE; + ldo->desc.n_voltages = WM831X_GP_LDO_MAX_SELECTOR + 1; + ldo->desc.ops = &wm831x_gp_ldo_ops; + ldo->desc.owner = THIS_MODULE; + + ldo->regulator = regulator_register(&ldo->desc, &pdev->dev, + pdata->ldo[id], ldo); + if (IS_ERR(ldo->regulator)) { + ret = PTR_ERR(ldo->regulator); + dev_err(wm831x->dev, "Failed to register LDO%d: %d\n", + id + 1, ret); + goto err; + } + + irq = platform_get_irq_byname(pdev, "UV"); + ret = wm831x_request_irq(wm831x, irq, wm831x_ldo_uv_irq, + IRQF_TRIGGER_RISING, ldo->name, + ldo); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to request UV IRQ %d: %d\n", + irq, ret); + goto err_regulator; + } + + platform_set_drvdata(pdev, ldo); + + return 0; + +err_regulator: + regulator_unregister(ldo->regulator); +err: + kfree(ldo); + return ret; +} + +static __devexit int wm831x_gp_ldo_remove(struct platform_device *pdev) +{ + struct wm831x_ldo *ldo = platform_get_drvdata(pdev); + struct wm831x *wm831x = ldo->wm831x; + + wm831x_free_irq(wm831x, platform_get_irq_byname(pdev, "UV"), ldo); + regulator_unregister(ldo->regulator); + kfree(ldo); + + return 0; +} + +static struct platform_driver wm831x_gp_ldo_driver = { + .probe = wm831x_gp_ldo_probe, + .remove = __devexit_p(wm831x_gp_ldo_remove), + .driver = { + .name = "wm831x-ldo", + }, +}; + +/* + * Analogue LDOs + */ + + +#define WM831X_ALDO_SELECTOR_LOW 0xc +#define WM831X_ALDO_MAX_SELECTOR 0x1f + +static int wm831x_aldo_list_voltage(struct regulator_dev *rdev, + unsigned int selector) +{ + /* 1-1.6V in 50mV steps */ + if (selector <= WM831X_ALDO_SELECTOR_LOW) + return 1000000 + (selector * 50000); + /* 1.7-3.5V in 50mV steps */ + if (selector <= WM831X_ALDO_MAX_SELECTOR) + return 1600000 + ((selector - WM831X_ALDO_SELECTOR_LOW) + * 100000); + return -EINVAL; +} + +static int wm831x_aldo_set_voltage_int(struct regulator_dev *rdev, int reg, + int min_uV, int max_uV) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + struct wm831x *wm831x = ldo->wm831x; + int vsel, ret; + + if (min_uV < 1000000) + vsel = 0; + else if (min_uV < 1700000) + vsel = ((min_uV - 1000000) / 50000); + else + vsel = ((min_uV - 1700000) / 100000) + + WM831X_ALDO_SELECTOR_LOW + 1; + + ret = wm831x_aldo_list_voltage(rdev, vsel); + if (ret < 0) + return ret; + if (ret < min_uV || ret > max_uV) + return -EINVAL; + + return wm831x_set_bits(wm831x, reg, WM831X_LDO7_ON_VSEL_MASK, vsel); +} + +static int wm831x_aldo_set_voltage(struct regulator_dev *rdev, + int min_uV, int max_uV) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + int reg = ldo->base + WM831X_LDO_ON_CONTROL; + + return wm831x_aldo_set_voltage_int(rdev, reg, min_uV, max_uV); +} + +static int wm831x_aldo_set_suspend_voltage(struct regulator_dev *rdev, + int uV) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + int reg = ldo->base + WM831X_LDO_SLEEP_CONTROL; + + return wm831x_aldo_set_voltage_int(rdev, reg, uV, uV); +} + +static int wm831x_aldo_get_voltage(struct regulator_dev *rdev) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + struct wm831x *wm831x = ldo->wm831x; + int reg = ldo->base + WM831X_LDO_ON_CONTROL; + int ret; + + ret = wm831x_reg_read(wm831x, reg); + if (ret < 0) + return ret; + + ret &= WM831X_LDO7_ON_VSEL_MASK; + + return wm831x_aldo_list_voltage(rdev, ret); +} + +static unsigned int wm831x_aldo_get_mode(struct regulator_dev *rdev) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + struct wm831x *wm831x = ldo->wm831x; + int on_reg = ldo->base + WM831X_LDO_ON_CONTROL; + unsigned int ret; + + ret = wm831x_reg_read(wm831x, on_reg); + if (ret < 0) + return 0; + + if (ret & WM831X_LDO7_ON_MODE) + return REGULATOR_MODE_IDLE; + else + return REGULATOR_MODE_NORMAL; +} + +static int wm831x_aldo_set_mode(struct regulator_dev *rdev, + unsigned int mode) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + struct wm831x *wm831x = ldo->wm831x; + int ctrl_reg = ldo->base + WM831X_LDO_CONTROL; + int on_reg = ldo->base + WM831X_LDO_ON_CONTROL; + int ret; + + + switch (mode) { + case REGULATOR_MODE_NORMAL: + ret = wm831x_set_bits(wm831x, on_reg, + WM831X_LDO7_ON_MODE, 0); + if (ret < 0) + return ret; + break; + + case REGULATOR_MODE_IDLE: + ret = wm831x_set_bits(wm831x, ctrl_reg, + WM831X_LDO7_ON_MODE, + WM831X_LDO7_ON_MODE); + if (ret < 0) + return ret; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int wm831x_aldo_get_status(struct regulator_dev *rdev) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + struct wm831x *wm831x = ldo->wm831x; + int mask = 1 << rdev_get_id(rdev); + int ret; + + /* Is the regulator on? */ + ret = wm831x_reg_read(wm831x, WM831X_LDO_STATUS); + if (ret < 0) + return ret; + if (!(ret & mask)) + return REGULATOR_STATUS_OFF; + + /* Is it reporting under voltage? */ + ret = wm831x_reg_read(wm831x, WM831X_LDO_UV_STATUS); + if (ret & mask) + return REGULATOR_STATUS_ERROR; + + ret = wm831x_aldo_get_mode(rdev); + if (ret < 0) + return ret; + else + return regulator_mode_to_status(ret); +} + +static struct regulator_ops wm831x_aldo_ops = { + .list_voltage = wm831x_aldo_list_voltage, + .get_voltage = wm831x_aldo_get_voltage, + .set_voltage = wm831x_aldo_set_voltage, + .set_suspend_voltage = wm831x_aldo_set_suspend_voltage, + .get_mode = wm831x_aldo_get_mode, + .set_mode = wm831x_aldo_set_mode, + .get_status = wm831x_aldo_get_status, + + .is_enabled = wm831x_ldo_is_enabled, + .enable = wm831x_ldo_enable, + .disable = wm831x_ldo_disable, +}; + +static __devinit int wm831x_aldo_probe(struct platform_device *pdev) +{ + struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent); + struct wm831x_pdata *pdata = wm831x->dev->platform_data; + int id = pdev->id % ARRAY_SIZE(pdata->ldo); + struct wm831x_ldo *ldo; + struct resource *res; + int ret, irq; + + dev_dbg(&pdev->dev, "Probing LDO%d\n", id + 1); + + if (pdata == NULL || pdata->ldo[id] == NULL) + return -ENODEV; + + ldo = kzalloc(sizeof(struct wm831x_ldo), GFP_KERNEL); + if (ldo == NULL) { + dev_err(&pdev->dev, "Unable to allocate private data\n"); + return -ENOMEM; + } + + ldo->wm831x = wm831x; + + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (res == NULL) { + dev_err(&pdev->dev, "No I/O resource\n"); + ret = -EINVAL; + goto err; + } + ldo->base = res->start; + + snprintf(ldo->name, sizeof(ldo->name), "LDO%d", id + 1); + ldo->desc.name = ldo->name; + ldo->desc.id = id; + ldo->desc.type = REGULATOR_VOLTAGE; + ldo->desc.n_voltages = WM831X_ALDO_MAX_SELECTOR + 1; + ldo->desc.ops = &wm831x_aldo_ops; + ldo->desc.owner = THIS_MODULE; + + ldo->regulator = regulator_register(&ldo->desc, &pdev->dev, + pdata->ldo[id], ldo); + if (IS_ERR(ldo->regulator)) { + ret = PTR_ERR(ldo->regulator); + dev_err(wm831x->dev, "Failed to register LDO%d: %d\n", + id + 1, ret); + goto err; + } + + irq = platform_get_irq_byname(pdev, "UV"); + ret = wm831x_request_irq(wm831x, irq, wm831x_ldo_uv_irq, + IRQF_TRIGGER_RISING, ldo->name, + ldo); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to request UV IRQ %d: %d\n", + irq, ret); + goto err_regulator; + } + + platform_set_drvdata(pdev, ldo); + + return 0; + +err_regulator: + regulator_unregister(ldo->regulator); +err: + kfree(ldo); + return ret; +} + +static __devexit int wm831x_aldo_remove(struct platform_device *pdev) +{ + struct wm831x_ldo *ldo = platform_get_drvdata(pdev); + struct wm831x *wm831x = ldo->wm831x; + + wm831x_free_irq(wm831x, platform_get_irq_byname(pdev, "UV"), ldo); + regulator_unregister(ldo->regulator); + kfree(ldo); + + return 0; +} + +static struct platform_driver wm831x_aldo_driver = { + .probe = wm831x_aldo_probe, + .remove = __devexit_p(wm831x_aldo_remove), + .driver = { + .name = "wm831x-aldo", + }, +}; + +/* + * Alive LDO + */ + +#define WM831X_ALIVE_LDO_MAX_SELECTOR 0xf + +static int wm831x_alive_ldo_list_voltage(struct regulator_dev *rdev, + unsigned int selector) +{ + /* 0.8-1.55V in 50mV steps */ + if (selector <= WM831X_ALIVE_LDO_MAX_SELECTOR) + return 800000 + (selector * 50000); + return -EINVAL; +} + +static int wm831x_alive_ldo_set_voltage_int(struct regulator_dev *rdev, + int reg, + int min_uV, int max_uV) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + struct wm831x *wm831x = ldo->wm831x; + int vsel, ret; + + vsel = (min_uV - 800000) / 50000; + + ret = wm831x_alive_ldo_list_voltage(rdev, vsel); + if (ret < 0) + return ret; + if (ret < min_uV || ret > max_uV) + return -EINVAL; + + return wm831x_set_bits(wm831x, reg, WM831X_LDO11_ON_VSEL_MASK, vsel); +} + +static int wm831x_alive_ldo_set_voltage(struct regulator_dev *rdev, + int min_uV, int max_uV) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + int reg = ldo->base + WM831X_ALIVE_LDO_ON_CONTROL; + + return wm831x_alive_ldo_set_voltage_int(rdev, reg, min_uV, max_uV); +} + +static int wm831x_alive_ldo_set_suspend_voltage(struct regulator_dev *rdev, + int uV) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + int reg = ldo->base + WM831X_ALIVE_LDO_SLEEP_CONTROL; + + return wm831x_alive_ldo_set_voltage_int(rdev, reg, uV, uV); +} + +static int wm831x_alive_ldo_get_voltage(struct regulator_dev *rdev) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + struct wm831x *wm831x = ldo->wm831x; + int reg = ldo->base + WM831X_ALIVE_LDO_ON_CONTROL; + int ret; + + ret = wm831x_reg_read(wm831x, reg); + if (ret < 0) + return ret; + + ret &= WM831X_LDO11_ON_VSEL_MASK; + + return wm831x_alive_ldo_list_voltage(rdev, ret); +} + +static int wm831x_alive_ldo_get_status(struct regulator_dev *rdev) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + struct wm831x *wm831x = ldo->wm831x; + int mask = 1 << rdev_get_id(rdev); + int ret; + + /* Is the regulator on? */ + ret = wm831x_reg_read(wm831x, WM831X_LDO_STATUS); + if (ret < 0) + return ret; + if (ret & mask) + return REGULATOR_STATUS_ON; + else + return REGULATOR_STATUS_OFF; +} + +static struct regulator_ops wm831x_alive_ldo_ops = { + .list_voltage = wm831x_alive_ldo_list_voltage, + .get_voltage = wm831x_alive_ldo_get_voltage, + .set_voltage = wm831x_alive_ldo_set_voltage, + .set_suspend_voltage = wm831x_alive_ldo_set_suspend_voltage, + .get_status = wm831x_alive_ldo_get_status, + + .is_enabled = wm831x_ldo_is_enabled, + .enable = wm831x_ldo_enable, + .disable = wm831x_ldo_disable, +}; + +static __devinit int wm831x_alive_ldo_probe(struct platform_device *pdev) +{ + struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent); + struct wm831x_pdata *pdata = wm831x->dev->platform_data; + int id = pdev->id % ARRAY_SIZE(pdata->ldo); + struct wm831x_ldo *ldo; + struct resource *res; + int ret; + + dev_dbg(&pdev->dev, "Probing LDO%d\n", id + 1); + + if (pdata == NULL || pdata->ldo[id] == NULL) + return -ENODEV; + + ldo = kzalloc(sizeof(struct wm831x_ldo), GFP_KERNEL); + if (ldo == NULL) { + dev_err(&pdev->dev, "Unable to allocate private data\n"); + return -ENOMEM; + } + + ldo->wm831x = wm831x; + + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (res == NULL) { + dev_err(&pdev->dev, "No I/O resource\n"); + ret = -EINVAL; + goto err; + } + ldo->base = res->start; + + snprintf(ldo->name, sizeof(ldo->name), "LDO%d", id + 1); + ldo->desc.name = ldo->name; + ldo->desc.id = id; + ldo->desc.type = REGULATOR_VOLTAGE; + ldo->desc.n_voltages = WM831X_ALIVE_LDO_MAX_SELECTOR + 1; + ldo->desc.ops = &wm831x_alive_ldo_ops; + ldo->desc.owner = THIS_MODULE; + + ldo->regulator = regulator_register(&ldo->desc, &pdev->dev, + pdata->ldo[id], ldo); + if (IS_ERR(ldo->regulator)) { + ret = PTR_ERR(ldo->regulator); + dev_err(wm831x->dev, "Failed to register LDO%d: %d\n", + id + 1, ret); + goto err; + } + + platform_set_drvdata(pdev, ldo); + + return 0; + +err: + kfree(ldo); + return ret; +} + +static __devexit int wm831x_alive_ldo_remove(struct platform_device *pdev) +{ + struct wm831x_ldo *ldo = platform_get_drvdata(pdev); + + regulator_unregister(ldo->regulator); + kfree(ldo); + + return 0; +} + +static struct platform_driver wm831x_alive_ldo_driver = { + .probe = wm831x_alive_ldo_probe, + .remove = __devexit_p(wm831x_alive_ldo_remove), + .driver = { + .name = "wm831x-alive-ldo", + }, +}; + +static int __init wm831x_ldo_init(void) +{ + int ret; + + ret = platform_driver_register(&wm831x_gp_ldo_driver); + if (ret != 0) + pr_err("Failed to register WM831x GP LDO driver: %d\n", ret); + + ret = platform_driver_register(&wm831x_aldo_driver); + if (ret != 0) + pr_err("Failed to register WM831x ALDO driver: %d\n", ret); + + ret = platform_driver_register(&wm831x_alive_ldo_driver); + if (ret != 0) + pr_err("Failed to register WM831x alive LDO driver: %d\n", + ret); + + return 0; +} +subsys_initcall(wm831x_ldo_init); + +static void __exit wm831x_ldo_exit(void) +{ + platform_driver_unregister(&wm831x_alive_ldo_driver); + platform_driver_unregister(&wm831x_aldo_driver); + platform_driver_unregister(&wm831x_gp_ldo_driver); +} +module_exit(wm831x_ldo_exit); + +/* Module information */ +MODULE_AUTHOR("Mark Brown "); +MODULE_DESCRIPTION("WM831x LDO driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:wm831x-ldo"); +MODULE_ALIAS("platform:wm831x-aldo"); +MODULE_ALIAS("platform:wm831x-aliveldo"); -- cgit v1.2.3 From 8267a9ba8299e1e70d54c7666da6aada637de4fc Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 28 Jul 2009 15:22:23 +0100 Subject: regulator: Add WM831x EPE support The WM831x series of PMICs provide two optional outputs for controlling external devices during power sequencing, for example an external regulator. While in essence these are GPIOs the hardware presents them as DCDCs with very little control so provide support via the regulator API in that fashion. Signed-off-by: Mark Brown Acked-by: Liam Girdwood Signed-off-by: Samuel Ortiz --- drivers/regulator/wm831x-dcdc.c | 88 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) (limited to 'drivers/regulator') diff --git a/drivers/regulator/wm831x-dcdc.c b/drivers/regulator/wm831x-dcdc.c index fa5126e38ac..88a7dbac750 100644 --- a/drivers/regulator/wm831x-dcdc.c +++ b/drivers/regulator/wm831x-dcdc.c @@ -613,6 +613,89 @@ static struct platform_driver wm831x_buckp_driver = { }, }; +/* + * External Power Enable + * + * These aren't actually DCDCs but look like them in hardware so share + * code. + */ + +#define WM831X_EPE_BASE 6 + +static struct regulator_ops wm831x_epe_ops = { + .is_enabled = wm831x_dcdc_is_enabled, + .enable = wm831x_dcdc_enable, + .disable = wm831x_dcdc_disable, + .get_status = wm831x_dcdc_get_status, +}; + +static __devinit int wm831x_epe_probe(struct platform_device *pdev) +{ + struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent); + struct wm831x_pdata *pdata = wm831x->dev->platform_data; + int id = pdev->id % ARRAY_SIZE(pdata->epe); + struct wm831x_dcdc *dcdc; + int ret; + + dev_dbg(&pdev->dev, "Probing EPE%d\n", id + 1); + + if (pdata == NULL || pdata->epe[id] == NULL) + return -ENODEV; + + dcdc = kzalloc(sizeof(struct wm831x_dcdc), GFP_KERNEL); + if (dcdc == NULL) { + dev_err(&pdev->dev, "Unable to allocate private data\n"); + return -ENOMEM; + } + + dcdc->wm831x = wm831x; + + /* For current parts this is correct; probably need to revisit + * in future. + */ + snprintf(dcdc->name, sizeof(dcdc->name), "EPE%d", id + 1); + dcdc->desc.name = dcdc->name; + dcdc->desc.id = id + WM831X_EPE_BASE; /* Offset in DCDC registers */ + dcdc->desc.ops = &wm831x_epe_ops; + dcdc->desc.type = REGULATOR_VOLTAGE; + dcdc->desc.owner = THIS_MODULE; + + dcdc->regulator = regulator_register(&dcdc->desc, &pdev->dev, + pdata->epe[id], dcdc); + if (IS_ERR(dcdc->regulator)) { + ret = PTR_ERR(dcdc->regulator); + dev_err(wm831x->dev, "Failed to register EPE%d: %d\n", + id + 1, ret); + goto err; + } + + platform_set_drvdata(pdev, dcdc); + + return 0; + +err: + kfree(dcdc); + return ret; +} + +static __devexit int wm831x_epe_remove(struct platform_device *pdev) +{ + struct wm831x_dcdc *dcdc = platform_get_drvdata(pdev); + + regulator_unregister(dcdc->regulator); + kfree(dcdc); + + return 0; +} + +static struct platform_driver wm831x_epe_driver = { + .probe = wm831x_epe_probe, + .remove = __devexit_p(wm831x_epe_remove), + .driver = { + .name = "wm831x-epe", + }, +}; + static int __init wm831x_dcdc_init(void) { int ret; @@ -624,12 +707,17 @@ static int __init wm831x_dcdc_init(void) if (ret != 0) pr_err("Failed to register WM831x BUCKP driver: %d\n", ret); + ret = platform_driver_register(&wm831x_epe_driver); + if (ret != 0) + pr_err("Failed to register WM831x EPE driver: %d\n", ret); + return 0; } subsys_initcall(wm831x_dcdc_init); static void __exit wm831x_dcdc_exit(void) { + platform_driver_unregister(&wm831x_epe_driver); platform_driver_unregister(&wm831x_buckp_driver); platform_driver_unregister(&wm831x_buckv_driver); } -- cgit v1.2.3 From 1304850d4c5d2f915bdcb8d547f3ef26c60cc825 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 28 Jul 2009 15:23:16 +0100 Subject: regulator: Add WM831x DC-DC boost convertor support The WM831x series of PMICs include a single DC-DC boost convertor. This adds basic support for this convertor. Signed-off-by: Mark Brown Acked-by: Liam Girdwood Signed-off-by: Samuel Ortiz --- drivers/regulator/wm831x-dcdc.c | 131 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) (limited to 'drivers/regulator') diff --git a/drivers/regulator/wm831x-dcdc.c b/drivers/regulator/wm831x-dcdc.c index 88a7dbac750..2eefc1a0cf0 100644 --- a/drivers/regulator/wm831x-dcdc.c +++ b/drivers/regulator/wm831x-dcdc.c @@ -613,6 +613,132 @@ static struct platform_driver wm831x_buckp_driver = { }, }; +/* + * DCDC boost convertors + */ + +static int wm831x_boostp_get_status(struct regulator_dev *rdev) +{ + struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev); + struct wm831x *wm831x = dcdc->wm831x; + int ret; + + /* First, check for errors */ + ret = wm831x_reg_read(wm831x, WM831X_DCDC_UV_STATUS); + if (ret < 0) + return ret; + + if (ret & (1 << rdev_get_id(rdev))) { + dev_dbg(wm831x->dev, "DCDC%d under voltage\n", + rdev_get_id(rdev) + 1); + return REGULATOR_STATUS_ERROR; + } + + /* Is the regulator on? */ + ret = wm831x_reg_read(wm831x, WM831X_DCDC_STATUS); + if (ret < 0) + return ret; + if (ret & (1 << rdev_get_id(rdev))) + return REGULATOR_STATUS_ON; + else + return REGULATOR_STATUS_OFF; +} + +static struct regulator_ops wm831x_boostp_ops = { + .get_status = wm831x_boostp_get_status, + + .is_enabled = wm831x_dcdc_is_enabled, + .enable = wm831x_dcdc_enable, + .disable = wm831x_dcdc_disable, +}; + +static __devinit int wm831x_boostp_probe(struct platform_device *pdev) +{ + struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent); + struct wm831x_pdata *pdata = wm831x->dev->platform_data; + int id = pdev->id % ARRAY_SIZE(pdata->dcdc); + struct wm831x_dcdc *dcdc; + struct resource *res; + int ret, irq; + + dev_dbg(&pdev->dev, "Probing DCDC%d\n", id + 1); + + if (pdata == NULL || pdata->dcdc[id] == NULL) + return -ENODEV; + + dcdc = kzalloc(sizeof(struct wm831x_dcdc), GFP_KERNEL); + if (dcdc == NULL) { + dev_err(&pdev->dev, "Unable to allocate private data\n"); + return -ENOMEM; + } + + dcdc->wm831x = wm831x; + + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (res == NULL) { + dev_err(&pdev->dev, "No I/O resource\n"); + ret = -EINVAL; + goto err; + } + dcdc->base = res->start; + + snprintf(dcdc->name, sizeof(dcdc->name), "DCDC%d", id + 1); + dcdc->desc.name = dcdc->name; + dcdc->desc.id = id; + dcdc->desc.type = REGULATOR_VOLTAGE; + dcdc->desc.ops = &wm831x_boostp_ops; + dcdc->desc.owner = THIS_MODULE; + + dcdc->regulator = regulator_register(&dcdc->desc, &pdev->dev, + pdata->dcdc[id], dcdc); + if (IS_ERR(dcdc->regulator)) { + ret = PTR_ERR(dcdc->regulator); + dev_err(wm831x->dev, "Failed to register DCDC%d: %d\n", + id + 1, ret); + goto err; + } + + irq = platform_get_irq_byname(pdev, "UV"); + ret = wm831x_request_irq(wm831x, irq, wm831x_dcdc_uv_irq, + IRQF_TRIGGER_RISING, dcdc->name, + dcdc); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to request UV IRQ %d: %d\n", + irq, ret); + goto err_regulator; + } + + platform_set_drvdata(pdev, dcdc); + + return 0; + +err_regulator: + regulator_unregister(dcdc->regulator); +err: + kfree(dcdc); + return ret; +} + +static __devexit int wm831x_boostp_remove(struct platform_device *pdev) +{ + struct wm831x_dcdc *dcdc = platform_get_drvdata(pdev); + struct wm831x *wm831x = dcdc->wm831x; + + wm831x_free_irq(wm831x, platform_get_irq_byname(pdev, "UV"), dcdc); + regulator_unregister(dcdc->regulator); + kfree(dcdc); + + return 0; +} + +static struct platform_driver wm831x_boostp_driver = { + .probe = wm831x_boostp_probe, + .remove = __devexit_p(wm831x_boostp_remove), + .driver = { + .name = "wm831x-boostp", + }, +}; + /* * External Power Enable * @@ -707,6 +833,10 @@ static int __init wm831x_dcdc_init(void) if (ret != 0) pr_err("Failed to register WM831x BUCKP driver: %d\n", ret); + ret = platform_driver_register(&wm831x_boostp_driver); + if (ret != 0) + pr_err("Failed to register WM831x BOOST driver: %d\n", ret); + ret = platform_driver_register(&wm831x_epe_driver); if (ret != 0) pr_err("Failed to register WM831x EPE driver: %d\n", ret); @@ -718,6 +848,7 @@ subsys_initcall(wm831x_dcdc_init); static void __exit wm831x_dcdc_exit(void) { platform_driver_unregister(&wm831x_epe_driver); + platform_driver_unregister(&wm831x_boostp_driver); platform_driver_unregister(&wm831x_buckp_driver); platform_driver_unregister(&wm831x_buckv_driver); } -- cgit v1.2.3 From d4d6b722e780f005f0d4e43a43909fa51cc33a11 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 28 Jul 2009 15:23:46 +0100 Subject: regulator: Add WM831x ISINK support The WM831x series of PMICs provide two constant current sinks designed to drive strings of serially connected LEDs for applications such as backlights. This driver adds support for those regulators. Signed-off-by: Mark Brown Acked-by: Liam Girdwood Signed-off-by: Samuel Ortiz --- drivers/regulator/Makefile | 1 + drivers/regulator/wm831x-isink.c | 260 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 261 insertions(+) create mode 100644 drivers/regulator/wm831x-isink.c (limited to 'drivers/regulator') diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index a0a635fdae8..3a4cdf5f793 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -13,6 +13,7 @@ obj-$(CONFIG_REGULATOR_LP3971) += lp3971.o obj-$(CONFIG_REGULATOR_MAX1586) += max1586.o obj-$(CONFIG_REGULATOR_TWL4030) += twl4030-regulator.o obj-$(CONFIG_REGULATOR_WM831X) += wm831x-dcdc.o +obj-$(CONFIG_REGULATOR_WM831X) += wm831x-isink.o obj-$(CONFIG_REGULATOR_WM831X) += wm831x-ldo.o obj-$(CONFIG_REGULATOR_WM8350) += wm8350-regulator.o obj-$(CONFIG_REGULATOR_WM8400) += wm8400-regulator.o diff --git a/drivers/regulator/wm831x-isink.c b/drivers/regulator/wm831x-isink.c new file mode 100644 index 00000000000..1d8d9879d3a --- /dev/null +++ b/drivers/regulator/wm831x-isink.c @@ -0,0 +1,260 @@ +/* + * wm831x-isink.c -- Current sink driver for the WM831x series + * + * Copyright 2009 Wolfson Microelectronics PLC. + * + * Author: Mark Brown + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define WM831X_ISINK_MAX_NAME 7 + +struct wm831x_isink { + char name[WM831X_ISINK_MAX_NAME]; + struct regulator_desc desc; + int reg; + struct wm831x *wm831x; + struct regulator_dev *regulator; +}; + +static int wm831x_isink_enable(struct regulator_dev *rdev) +{ + struct wm831x_isink *isink = rdev_get_drvdata(rdev); + struct wm831x *wm831x = isink->wm831x; + int ret; + + /* We have a two stage enable: first start the ISINK... */ + ret = wm831x_set_bits(wm831x, isink->reg, WM831X_CS1_ENA, + WM831X_CS1_ENA); + if (ret != 0) + return ret; + + /* ...then enable drive */ + ret = wm831x_set_bits(wm831x, isink->reg, WM831X_CS1_DRIVE, + WM831X_CS1_DRIVE); + if (ret != 0) + wm831x_set_bits(wm831x, isink->reg, WM831X_CS1_ENA, 0); + + return ret; + +} + +static int wm831x_isink_disable(struct regulator_dev *rdev) +{ + struct wm831x_isink *isink = rdev_get_drvdata(rdev); + struct wm831x *wm831x = isink->wm831x; + int ret; + + ret = wm831x_set_bits(wm831x, isink->reg, WM831X_CS1_DRIVE, 0); + if (ret < 0) + return ret; + + ret = wm831x_set_bits(wm831x, isink->reg, WM831X_CS1_ENA, 0); + if (ret < 0) + return ret; + + return ret; + +} + +static int wm831x_isink_is_enabled(struct regulator_dev *rdev) +{ + struct wm831x_isink *isink = rdev_get_drvdata(rdev); + struct wm831x *wm831x = isink->wm831x; + int ret; + + ret = wm831x_reg_read(wm831x, isink->reg); + if (ret < 0) + return ret; + + if ((ret & (WM831X_CS1_ENA | WM831X_CS1_DRIVE)) == + (WM831X_CS1_ENA | WM831X_CS1_DRIVE)) + return 1; + else + return 0; +} + +static int wm831x_isink_set_current(struct regulator_dev *rdev, + int min_uA, int max_uA) +{ + struct wm831x_isink *isink = rdev_get_drvdata(rdev); + struct wm831x *wm831x = isink->wm831x; + int ret, i; + + for (i = 0; i < ARRAY_SIZE(wm831x_isinkv_values); i++) { + int val = wm831x_isinkv_values[i]; + if (min_uA >= val && val <= max_uA) { + ret = wm831x_set_bits(wm831x, isink->reg, + WM831X_CS1_ISEL_MASK, i); + return ret; + } + } + + return -EINVAL; +} + +static int wm831x_isink_get_current(struct regulator_dev *rdev) +{ + struct wm831x_isink *isink = rdev_get_drvdata(rdev); + struct wm831x *wm831x = isink->wm831x; + int ret; + + ret = wm831x_reg_read(wm831x, isink->reg); + if (ret < 0) + return ret; + + ret &= WM831X_CS1_ISEL_MASK; + if (ret > WM831X_ISINK_MAX_ISEL) + ret = WM831X_ISINK_MAX_ISEL; + + return wm831x_isinkv_values[ret]; +} + +static struct regulator_ops wm831x_isink_ops = { + .is_enabled = wm831x_isink_is_enabled, + .enable = wm831x_isink_enable, + .disable = wm831x_isink_disable, + .set_current_limit = wm831x_isink_set_current, + .get_current_limit = wm831x_isink_get_current, +}; + +static irqreturn_t wm831x_isink_irq(int irq, void *data) +{ + struct wm831x_isink *isink = data; + + regulator_notifier_call_chain(isink->regulator, + REGULATOR_EVENT_OVER_CURRENT, + NULL); + + return IRQ_HANDLED; +} + + +static __devinit int wm831x_isink_probe(struct platform_device *pdev) +{ + struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent); + struct wm831x_pdata *pdata = wm831x->dev->platform_data; + struct wm831x_isink *isink; + int id = pdev->id % ARRAY_SIZE(pdata->isink); + struct resource *res; + int ret, irq; + + dev_dbg(&pdev->dev, "Probing ISINK%d\n", id + 1); + + if (pdata == NULL || pdata->isink[id] == NULL) + return -ENODEV; + + isink = kzalloc(sizeof(struct wm831x_isink), GFP_KERNEL); + if (isink == NULL) { + dev_err(&pdev->dev, "Unable to allocate private data\n"); + return -ENOMEM; + } + + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (res == NULL) { + dev_err(&pdev->dev, "No I/O resource\n"); + ret = -EINVAL; + goto err; + } + isink->reg = res->start; + + /* For current parts this is correct; probably need to revisit + * in future. + */ + snprintf(isink->name, sizeof(isink->name), "ISINK%d", id + 1); + isink->desc.name = isink->name; + isink->desc.id = id; + isink->desc.ops = &wm831x_isink_ops; + isink->desc.type = REGULATOR_CURRENT; + isink->desc.owner = THIS_MODULE; + + isink->regulator = regulator_register(&isink->desc, &pdev->dev, + pdata->isink[id], isink); + if (IS_ERR(isink->regulator)) { + ret = PTR_ERR(isink->regulator); + dev_err(wm831x->dev, "Failed to register ISINK%d: %d\n", + id + 1, ret); + goto err; + } + + irq = platform_get_irq(pdev, 0); + ret = wm831x_request_irq(wm831x, irq, wm831x_isink_irq, + IRQF_TRIGGER_RISING, isink->name, + isink); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to request ISINK IRQ %d: %d\n", + irq, ret); + goto err_regulator; + } + + platform_set_drvdata(pdev, isink); + + return 0; + +err_regulator: + regulator_unregister(isink->regulator); +err: + kfree(isink); + return ret; +} + +static __devexit int wm831x_isink_remove(struct platform_device *pdev) +{ + struct wm831x_isink *isink = platform_get_drvdata(pdev); + struct wm831x *wm831x = isink->wm831x; + + wm831x_free_irq(wm831x, platform_get_irq(pdev, 0), isink); + + regulator_unregister(isink->regulator); + kfree(isink); + + return 0; +} + +static struct platform_driver wm831x_isink_driver = { + .probe = wm831x_isink_probe, + .remove = __devexit_p(wm831x_isink_remove), + .driver = { + .name = "wm831x-isink", + }, +}; + +static int __init wm831x_isink_init(void) +{ + int ret; + ret = platform_driver_register(&wm831x_isink_driver); + if (ret != 0) + pr_err("Failed to register WM831x ISINK driver: %d\n", ret); + + return ret; +} +subsys_initcall(wm831x_isink_init); + +static void __exit wm831x_isink_exit(void) +{ + platform_driver_unregister(&wm831x_isink_driver); +} +module_exit(wm831x_isink_exit); + +/* Module information */ +MODULE_AUTHOR("Mark Brown"); +MODULE_DESCRIPTION("WM831x current sink driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:wm831x-isink"); -- cgit v1.2.3 From e397e7ed50e3fb573aa5de183ae308dc7bf20b9e Mon Sep 17 00:00:00 2001 From: Antonio Ospite Date: Thu, 6 Aug 2009 16:08:52 -0700 Subject: regulator: register pcap earlier Register pcap-regulator earlier so it can be used with cpufreq Signed-off-by: Daniel Ribeiro Acked-by: Mark Brown Signed-off-by: Andrew Morton Signed-off-by: Samuel Ortiz --- drivers/regulator/pcap-regulator.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/regulator') diff --git a/drivers/regulator/pcap-regulator.c b/drivers/regulator/pcap-regulator.c index fdb90940ef0..137b455ecb4 100644 --- a/drivers/regulator/pcap-regulator.c +++ b/drivers/regulator/pcap-regulator.c @@ -310,7 +310,7 @@ static void __exit pcap_regulator_exit(void) platform_driver_unregister(&pcap_regulator_driver); } -module_init(pcap_regulator_init); +subsys_initcall(pcap_regulator_init); module_exit(pcap_regulator_exit); MODULE_AUTHOR("Daniel Ribeiro "); -- cgit v1.2.3 From 70fde5cbd421773f0b9d684933ecb441efe89c84 Mon Sep 17 00:00:00 2001 From: Antonio Ospite Date: Fri, 7 Aug 2009 23:18:41 +0200 Subject: regulator: get pcap data from the parent device Right now the pcap core driver passes a reference to its pcap data abusing the subdrivers platform drvdata, this is not good. Get the reference directly from the parent device. Signed-off-by: Antonio Ospite Acked-by: Mark Brown Signed-off-by: Samuel Ortiz --- drivers/regulator/pcap-regulator.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/regulator') diff --git a/drivers/regulator/pcap-regulator.c b/drivers/regulator/pcap-regulator.c index 137b455ecb4..33d7d899e03 100644 --- a/drivers/regulator/pcap-regulator.c +++ b/drivers/regulator/pcap-regulator.c @@ -271,7 +271,7 @@ static struct regulator_desc pcap_regulators[] = { static int __devinit pcap_regulator_probe(struct platform_device *pdev) { struct regulator_dev *rdev; - void *pcap = platform_get_drvdata(pdev); + void *pcap = dev_get_drvdata(pdev->dev.parent); rdev = regulator_register(&pcap_regulators[pdev->id], &pdev->dev, pdev->dev.platform_data, pcap); -- cgit v1.2.3 From 295c08bc69a5dd8cef69ceaeaaf551a17f50c34b Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Wed, 19 Aug 2009 01:43:50 +0200 Subject: regulator: Add Freescale MC13783 driver This driver provides basic support for the voltage regulators integrated into the Freescale MC13783 PMIC. It is currently only possible to enable/disable outputs, not to actually set the voltage. Signed-off-by: Sascha Hauer Signed-off-by: Liam Girdwood Signed-off-by: Samuel Ortiz --- drivers/regulator/Kconfig | 8 + drivers/regulator/Makefile | 1 + drivers/regulator/mc13783.c | 410 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 419 insertions(+) create mode 100644 drivers/regulator/mc13783.c (limited to 'drivers/regulator') diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index 38ea5dc8e14..c505714b0a9 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -131,4 +131,12 @@ config REGULATOR_PCAP This driver provides support for the voltage regulators of the PCAP2 PMIC. +config REGULATOR_MC13783 + tristate "Support regulators on Freescale MC13783 PMIC" + depends on MFD_MC13783 + help + Say y here to support the regulators found on the Freescale MC13783 + PMIC. + endif + diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index 3a4cdf5f793..5034830a395 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -20,5 +20,6 @@ obj-$(CONFIG_REGULATOR_WM8400) += wm8400-regulator.o obj-$(CONFIG_REGULATOR_DA903X) += da903x.o obj-$(CONFIG_REGULATOR_PCF50633) += pcf50633-regulator.o obj-$(CONFIG_REGULATOR_PCAP) += pcap-regulator.o +obj-$(CONFIG_REGULATOR_MC13783) += mc13783.o ccflags-$(CONFIG_REGULATOR_DEBUG) += -DDEBUG diff --git a/drivers/regulator/mc13783.c b/drivers/regulator/mc13783.c new file mode 100644 index 00000000000..710211f6744 --- /dev/null +++ b/drivers/regulator/mc13783.c @@ -0,0 +1,410 @@ +/* + * Regulator Driver for Freescale MC13783 PMIC + * + * Copyright (C) 2008 Sascha Hauer, Pengutronix + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +struct mc13783_regulator { + struct regulator_desc desc; + int reg; + int enable_bit; +}; + +static struct regulator_ops mc13783_regulator_ops; + +static struct mc13783_regulator mc13783_regulators[] = { + [MC13783_SW_SW3] = { + .desc = { + .name = "SW_SW3", + .ops = &mc13783_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = MC13783_SW_SW3, + .owner = THIS_MODULE, + }, + .reg = MC13783_REG_SWITCHERS_5, + .enable_bit = MC13783_SWCTRL_SW3_EN, + }, + [MC13783_SW_PLL] = { + .desc = { + .name = "SW_PLL", + .ops = &mc13783_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = MC13783_SW_PLL, + .owner = THIS_MODULE, + }, + .reg = MC13783_REG_SWITCHERS_4, + .enable_bit = MC13783_SWCTRL_PLL_EN, + }, + [MC13783_REGU_VAUDIO] = { + .desc = { + .name = "REGU_VAUDIO", + .ops = &mc13783_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = MC13783_REGU_VAUDIO, + .owner = THIS_MODULE, + }, + .reg = MC13783_REG_REGULATOR_MODE_0, + .enable_bit = MC13783_REGCTRL_VAUDIO_EN, + }, + [MC13783_REGU_VIOHI] = { + .desc = { + .name = "REGU_VIOHI", + .ops = &mc13783_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = MC13783_REGU_VIOHI, + .owner = THIS_MODULE, + }, + .reg = MC13783_REG_REGULATOR_MODE_0, + .enable_bit = MC13783_REGCTRL_VIOHI_EN, + }, + [MC13783_REGU_VIOLO] = { + .desc = { + .name = "REGU_VIOLO", + .ops = &mc13783_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = MC13783_REGU_VIOLO, + .owner = THIS_MODULE, + }, + .reg = MC13783_REG_REGULATOR_MODE_0, + .enable_bit = MC13783_REGCTRL_VIOLO_EN, + }, + [MC13783_REGU_VDIG] = { + .desc = { + .name = "REGU_VDIG", + .ops = &mc13783_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = MC13783_REGU_VDIG, + .owner = THIS_MODULE, + }, + .reg = MC13783_REG_REGULATOR_MODE_0, + .enable_bit = MC13783_REGCTRL_VDIG_EN, + }, + [MC13783_REGU_VGEN] = { + .desc = { + .name = "REGU_VGEN", + .ops = &mc13783_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = MC13783_REGU_VGEN, + .owner = THIS_MODULE, + }, + .reg = MC13783_REG_REGULATOR_MODE_0, + .enable_bit = MC13783_REGCTRL_VGEN_EN, + }, + [MC13783_REGU_VRFDIG] = { + .desc = { + .name = "REGU_VRFDIG", + .ops = &mc13783_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = MC13783_REGU_VRFDIG, + .owner = THIS_MODULE, + }, + .reg = MC13783_REG_REGULATOR_MODE_0, + .enable_bit = MC13783_REGCTRL_VRFDIG_EN, + }, + [MC13783_REGU_VRFREF] = { + .desc = { + .name = "REGU_VRFREF", + .ops = &mc13783_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = MC13783_REGU_VRFREF, + .owner = THIS_MODULE, + }, + .reg = MC13783_REG_REGULATOR_MODE_0, + .enable_bit = MC13783_REGCTRL_VRFREF_EN, + }, + [MC13783_REGU_VRFCP] = { + .desc = { + .name = "REGU_VRFCP", + .ops = &mc13783_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = MC13783_REGU_VRFCP, + .owner = THIS_MODULE, + }, + .reg = MC13783_REG_REGULATOR_MODE_0, + .enable_bit = MC13783_REGCTRL_VRFCP_EN, + }, + [MC13783_REGU_VSIM] = { + .desc = { + .name = "REGU_VSIM", + .ops = &mc13783_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = MC13783_REGU_VSIM, + .owner = THIS_MODULE, + }, + .reg = MC13783_REG_REGULATOR_MODE_1, + .enable_bit = MC13783_REGCTRL_VSIM_EN, + }, + [MC13783_REGU_VESIM] = { + .desc = { + .name = "REGU_VESIM", + .ops = &mc13783_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = MC13783_REGU_VESIM, + .owner = THIS_MODULE, + }, + .reg = MC13783_REG_REGULATOR_MODE_1, + .enable_bit = MC13783_REGCTRL_VESIM_EN, + }, + [MC13783_REGU_VCAM] = { + .desc = { + .name = "REGU_VCAM", + .ops = &mc13783_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = MC13783_REGU_VCAM, + .owner = THIS_MODULE, + }, + .reg = MC13783_REG_REGULATOR_MODE_1, + .enable_bit = MC13783_REGCTRL_VCAM_EN, + }, + [MC13783_REGU_VRFBG] = { + .desc = { + .name = "REGU_VRFBG", + .ops = &mc13783_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = MC13783_REGU_VRFBG, + .owner = THIS_MODULE, + }, + .reg = MC13783_REG_REGULATOR_MODE_1, + .enable_bit = MC13783_REGCTRL_VRFBG_EN, + }, + [MC13783_REGU_VVIB] = { + .desc = { + .name = "REGU_VVIB", + .ops = &mc13783_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = MC13783_REGU_VVIB, + .owner = THIS_MODULE, + }, + .reg = MC13783_REG_REGULATOR_MODE_1, + .enable_bit = MC13783_REGCTRL_VVIB_EN, + }, + [MC13783_REGU_VRF1] = { + .desc = { + .name = "REGU_VRF1", + .ops = &mc13783_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = MC13783_REGU_VRF1, + .owner = THIS_MODULE, + }, + .reg = MC13783_REG_REGULATOR_MODE_1, + .enable_bit = MC13783_REGCTRL_VRF1_EN, + }, + [MC13783_REGU_VRF2] = { + .desc = { + .name = "REGU_VRF2", + .ops = &mc13783_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = MC13783_REGU_VRF2, + .owner = THIS_MODULE, + }, + .reg = MC13783_REG_REGULATOR_MODE_1, + .enable_bit = MC13783_REGCTRL_VRF2_EN, + }, + [MC13783_REGU_VMMC1] = { + .desc = { + .name = "REGU_VMMC1", + .ops = &mc13783_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = MC13783_REGU_VMMC1, + .owner = THIS_MODULE, + }, + .reg = MC13783_REG_REGULATOR_MODE_1, + .enable_bit = MC13783_REGCTRL_VMMC1_EN, + }, + [MC13783_REGU_VMMC2] = { + .desc = { + .name = "REGU_VMMC2", + .ops = &mc13783_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = MC13783_REGU_VMMC2, + .owner = THIS_MODULE, + }, + .reg = MC13783_REG_REGULATOR_MODE_1, + .enable_bit = MC13783_REGCTRL_VMMC2_EN, + }, + [MC13783_REGU_GPO1] = { + .desc = { + .name = "REGU_GPO1", + .ops = &mc13783_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = MC13783_REGU_GPO1, + .owner = THIS_MODULE, + }, + .reg = MC13783_REG_POWER_MISCELLANEOUS, + .enable_bit = MC13783_REGCTRL_GPO1_EN, + }, + [MC13783_REGU_GPO2] = { + .desc = { + .name = "REGU_GPO2", + .ops = &mc13783_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = MC13783_REGU_GPO2, + .owner = THIS_MODULE, + }, + .reg = MC13783_REG_POWER_MISCELLANEOUS, + .enable_bit = MC13783_REGCTRL_GPO2_EN, + }, + [MC13783_REGU_GPO3] = { + .desc = { + .name = "REGU_GPO3", + .ops = &mc13783_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = MC13783_REGU_GPO3, + .owner = THIS_MODULE, + }, + .reg = MC13783_REG_POWER_MISCELLANEOUS, + .enable_bit = MC13783_REGCTRL_GPO3_EN, + }, + [MC13783_REGU_GPO4] = { + .desc = { + .name = "REGU_GPO4", + .ops = &mc13783_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = MC13783_REGU_GPO4, + .owner = THIS_MODULE, + }, + .reg = MC13783_REG_POWER_MISCELLANEOUS, + .enable_bit = MC13783_REGCTRL_GPO4_EN, + }, +}; + +struct mc13783_priv { + struct regulator_desc desc[ARRAY_SIZE(mc13783_regulators)]; + struct mc13783 *mc13783; + struct regulator_dev *regulators[0]; +}; + +static int mc13783_enable(struct regulator_dev *rdev) +{ + struct mc13783_priv *priv = rdev_get_drvdata(rdev); + int id = rdev_get_id(rdev); + + dev_dbg(rdev_get_dev(rdev), "%s id: %d\n", __func__, id); + + return mc13783_set_bits(priv->mc13783, mc13783_regulators[id].reg, + mc13783_regulators[id].enable_bit, + mc13783_regulators[id].enable_bit); +} + +static int mc13783_disable(struct regulator_dev *rdev) +{ + struct mc13783_priv *priv = rdev_get_drvdata(rdev); + int id = rdev_get_id(rdev); + + dev_dbg(rdev_get_dev(rdev), "%s id: %d\n", __func__, id); + + return mc13783_set_bits(priv->mc13783, mc13783_regulators[id].reg, + mc13783_regulators[id].enable_bit, 0); +} + +static int mc13783_is_enabled(struct regulator_dev *rdev) +{ + struct mc13783_priv *priv = rdev_get_drvdata(rdev); + int ret, id = rdev_get_id(rdev); + unsigned int val; + + ret = mc13783_reg_read(priv->mc13783, mc13783_regulators[id].reg, &val); + if (ret) + return ret; + + return (val & mc13783_regulators[id].enable_bit) != 0; +} + +static struct regulator_ops mc13783_regulator_ops = { + .enable = mc13783_enable, + .disable = mc13783_disable, + .is_enabled = mc13783_is_enabled, +}; + +static int __devinit mc13783_regulator_probe(struct platform_device *pdev) +{ + struct mc13783_priv *priv; + struct mc13783 *mc13783 = dev_get_drvdata(pdev->dev.parent); + struct mc13783_regulator_init_data *init_data; + int i, ret; + + dev_dbg(&pdev->dev, "mc13783_regulator_probe id %d\n", pdev->id); + + priv = kzalloc(sizeof(*priv) + mc13783->num_regulators * sizeof(void *), + GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->mc13783 = mc13783; + + for (i = 0; i < mc13783->num_regulators; i++) { + init_data = &mc13783->regulators[i]; + priv->regulators[i] = regulator_register( + &mc13783_regulators[init_data->id].desc, + &pdev->dev, init_data->init_data, priv); + + if (IS_ERR(priv->regulators[i])) { + dev_err(&pdev->dev, "failed to register regulator %s\n", + mc13783_regulators[i].desc.name); + ret = PTR_ERR(priv->regulators[i]); + goto err; + } + } + + platform_set_drvdata(pdev, priv); + + return 0; +err: + while (--i >= 0) + regulator_unregister(priv->regulators[i]); + + kfree(priv); + + return ret; +} + +static int __devexit mc13783_regulator_remove(struct platform_device *pdev) +{ + struct mc13783_priv *priv = platform_get_drvdata(pdev); + struct mc13783 *mc13783 = priv->mc13783; + int i; + + for (i = 0; i < mc13783->num_regulators; i++) + regulator_unregister(priv->regulators[i]); + + return 0; +} + +static struct platform_driver mc13783_regulator_driver = { + .driver = { + .name = "mc13783-regulator", + .owner = THIS_MODULE, + }, + .remove = __devexit_p(mc13783_regulator_remove), +}; + +static int __init mc13783_regulator_init(void) +{ + return platform_driver_probe(&mc13783_regulator_driver, + mc13783_regulator_probe); +} +subsys_initcall(mc13783_regulator_init); + +static void __exit mc13783_regulator_exit(void) +{ + platform_driver_unregister(&mc13783_regulator_driver); +} +module_exit(mc13783_regulator_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Sascha Hauer Date: Wed, 9 Sep 2009 11:31:00 +0200 Subject: regulator: AB3100 support This adds support for the regulators found in the AB3100 Mixed-Signal IC. It further also defines platform data for the ST-Ericsson U300 platform and extends the AB3100 MFD driver so that platform/board data with regulation constraints and an init function can be passed down all the way from the board to the regulators. Signed-off-by: Linus Walleij Acked-by: Mark Brown Signed-off-by: Liam Girdwood Signed-off-by: Samuel Ortiz --- drivers/regulator/Kconfig | 9 + drivers/regulator/Makefile | 1 + drivers/regulator/ab3100.c | 694 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 704 insertions(+) create mode 100644 drivers/regulator/ab3100.c (limited to 'drivers/regulator') diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index c505714b0a9..2dc42bbf6fe 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -138,5 +138,14 @@ config REGULATOR_MC13783 Say y here to support the regulators found on the Freescale MC13783 PMIC. +config REGULATOR_AB3100 + tristate "ST-Ericsson AB3100 Regulator functions" + depends on AB3100_CORE + default y if AB3100_CORE + help + These regulators correspond to functionality in the + AB3100 analog baseband dealing with power regulators + for the system. + endif diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index 5034830a395..768b3316d6e 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -21,5 +21,6 @@ obj-$(CONFIG_REGULATOR_DA903X) += da903x.o obj-$(CONFIG_REGULATOR_PCF50633) += pcf50633-regulator.o obj-$(CONFIG_REGULATOR_PCAP) += pcap-regulator.o obj-$(CONFIG_REGULATOR_MC13783) += mc13783.o +obj-$(CONFIG_REGULATOR_AB3100) += ab3100.o ccflags-$(CONFIG_REGULATOR_DEBUG) += -DDEBUG diff --git a/drivers/regulator/ab3100.c b/drivers/regulator/ab3100.c new file mode 100644 index 00000000000..156979d1f41 --- /dev/null +++ b/drivers/regulator/ab3100.c @@ -0,0 +1,694 @@ +/* + * drivers/regulator/ab3100.c + * + * Copyright (C) 2008-2009 ST-Ericsson AB + * License terms: GNU General Public License (GPL) version 2 + * Low-level control of the AB3100 IC Low Dropout (LDO) + * regulators, external regulator and buck converter + * Author: Mattias Wallin + * Author: Linus Walleij + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* LDO registers and some handy masking definitions for AB3100 */ +#define AB3100_LDO_A 0x40 +#define AB3100_LDO_C 0x41 +#define AB3100_LDO_D 0x42 +#define AB3100_LDO_E 0x43 +#define AB3100_LDO_E_SLEEP 0x44 +#define AB3100_LDO_F 0x45 +#define AB3100_LDO_G 0x46 +#define AB3100_LDO_H 0x47 +#define AB3100_LDO_H_SLEEP_MODE 0 +#define AB3100_LDO_H_SLEEP_EN 2 +#define AB3100_LDO_ON 4 +#define AB3100_LDO_H_VSEL_AC 5 +#define AB3100_LDO_K 0x48 +#define AB3100_LDO_EXT 0x49 +#define AB3100_BUCK 0x4A +#define AB3100_BUCK_SLEEP 0x4B +#define AB3100_REG_ON_MASK 0x10 + +/** + * struct ab3100_regulator + * A struct passed around the individual regulator functions + * @platform_device: platform device holding this regulator + * @ab3100: handle to the AB3100 parent chip + * @plfdata: AB3100 platform data passed in at probe time + * @regreg: regulator register number in the AB3100 + * @fixed_voltage: a fixed voltage for this regulator, if this + * 0 the voltages array is used instead. + * @typ_voltages: an array of available typical voltages for + * this regulator + * @voltages_len: length of the array of available voltages + */ +struct ab3100_regulator { + struct regulator_dev *rdev; + struct ab3100 *ab3100; + struct ab3100_platform_data *plfdata; + u8 regreg; + int fixed_voltage; + int const *typ_voltages; + u8 voltages_len; +}; + +/* The order in which registers are initialized */ +static const u8 ab3100_reg_init_order[AB3100_NUM_REGULATORS+2] = { + AB3100_LDO_A, + AB3100_LDO_C, + AB3100_LDO_E, + AB3100_LDO_E_SLEEP, + AB3100_LDO_F, + AB3100_LDO_G, + AB3100_LDO_H, + AB3100_LDO_K, + AB3100_LDO_EXT, + AB3100_BUCK, + AB3100_BUCK_SLEEP, + AB3100_LDO_D, +}; + +/* Preset (hardware defined) voltages for these regulators */ +#define LDO_A_VOLTAGE 2750000 +#define LDO_C_VOLTAGE 2650000 +#define LDO_D_VOLTAGE 2650000 + +static const int const ldo_e_buck_typ_voltages[] = { + 1800000, + 1400000, + 1300000, + 1200000, + 1100000, + 1050000, + 900000, +}; + +static const int const ldo_f_typ_voltages[] = { + 1800000, + 1400000, + 1300000, + 1200000, + 1100000, + 1050000, + 2500000, + 2650000, +}; + +static const int const ldo_g_typ_voltages[] = { + 2850000, + 2750000, + 1800000, + 1500000, +}; + +static const int const ldo_h_typ_voltages[] = { + 2750000, + 1800000, + 1500000, + 1200000, +}; + +static const int const ldo_k_typ_voltages[] = { + 2750000, + 1800000, +}; + + +/* The regulator devices */ +static struct ab3100_regulator +ab3100_regulators[AB3100_NUM_REGULATORS] = { + { + .regreg = AB3100_LDO_A, + .fixed_voltage = LDO_A_VOLTAGE, + }, + { + .regreg = AB3100_LDO_C, + .fixed_voltage = LDO_C_VOLTAGE, + }, + { + .regreg = AB3100_LDO_D, + .fixed_voltage = LDO_D_VOLTAGE, + }, + { + .regreg = AB3100_LDO_E, + .typ_voltages = ldo_e_buck_typ_voltages, + .voltages_len = ARRAY_SIZE(ldo_e_buck_typ_voltages), + }, + { + .regreg = AB3100_LDO_F, + .typ_voltages = ldo_f_typ_voltages, + .voltages_len = ARRAY_SIZE(ldo_f_typ_voltages), + }, + { + .regreg = AB3100_LDO_G, + .typ_voltages = ldo_g_typ_voltages, + .voltages_len = ARRAY_SIZE(ldo_g_typ_voltages), + }, + { + .regreg = AB3100_LDO_H, + .typ_voltages = ldo_h_typ_voltages, + .voltages_len = ARRAY_SIZE(ldo_h_typ_voltages), + }, + { + .regreg = AB3100_LDO_K, + .typ_voltages = ldo_k_typ_voltages, + .voltages_len = ARRAY_SIZE(ldo_k_typ_voltages), + }, + { + .regreg = AB3100_LDO_EXT, + /* No voltages for the external regulator */ + }, + { + .regreg = AB3100_BUCK, + .typ_voltages = ldo_e_buck_typ_voltages, + .voltages_len = ARRAY_SIZE(ldo_e_buck_typ_voltages), + }, +}; + +/* + * General functions for enable, disable and is_enabled used for + * LDO: A,C,E,F,G,H,K,EXT and BUCK + */ +static int ab3100_enable_regulator(struct regulator_dev *reg) +{ + struct ab3100_regulator *abreg = reg->reg_data; + int err; + u8 regval; + + err = ab3100_get_register_interruptible(abreg->ab3100, abreg->regreg, + ®val); + if (err) { + dev_warn(®->dev, "failed to get regid %d value\n", + abreg->regreg); + return err; + } + + /* The regulator is already on, no reason to go further */ + if (regval & AB3100_REG_ON_MASK) + return 0; + + regval |= AB3100_REG_ON_MASK; + + err = ab3100_set_register_interruptible(abreg->ab3100, abreg->regreg, + regval); + if (err) { + dev_warn(®->dev, "failed to set regid %d value\n", + abreg->regreg); + return err; + } + + /* Per-regulator power on delay from spec */ + switch (abreg->regreg) { + case AB3100_LDO_A: /* Fallthrough */ + case AB3100_LDO_C: /* Fallthrough */ + case AB3100_LDO_D: /* Fallthrough */ + case AB3100_LDO_E: /* Fallthrough */ + case AB3100_LDO_H: /* Fallthrough */ + case AB3100_LDO_K: + udelay(200); + break; + case AB3100_LDO_F: + udelay(600); + break; + case AB3100_LDO_G: + udelay(400); + break; + case AB3100_BUCK: + mdelay(1); + break; + default: + break; + } + + return 0; +} + +static int ab3100_disable_regulator(struct regulator_dev *reg) +{ + struct ab3100_regulator *abreg = reg->reg_data; + int err; + u8 regval; + + /* + * LDO D is a special regulator. When it is disabled, the entire + * system is shut down. So this is handled specially. + */ + if (abreg->regreg == AB3100_LDO_D) { + int i; + + dev_info(®->dev, "disabling LDO D - shut down system\n"); + /* + * Set regulators to default values, ignore any errors, + * we're going DOWN + */ + for (i = 0; i < ARRAY_SIZE(ab3100_reg_init_order); i++) { + (void) ab3100_set_register_interruptible(abreg->ab3100, + ab3100_reg_init_order[i], + abreg->plfdata->reg_initvals[i]); + } + + /* Setting LDO D to 0x00 cuts the power to the SoC */ + return ab3100_set_register_interruptible(abreg->ab3100, + AB3100_LDO_D, 0x00U); + + } + + /* + * All other regulators are handled here + */ + err = ab3100_get_register_interruptible(abreg->ab3100, abreg->regreg, + ®val); + if (err) { + dev_err(®->dev, "unable to get register 0x%x\n", + abreg->regreg); + return err; + } + regval &= ~AB3100_REG_ON_MASK; + return ab3100_set_register_interruptible(abreg->ab3100, abreg->regreg, + regval); +} + +static int ab3100_is_enabled_regulator(struct regulator_dev *reg) +{ + struct ab3100_regulator *abreg = reg->reg_data; + u8 regval; + int err; + + err = ab3100_get_register_interruptible(abreg->ab3100, abreg->regreg, + ®val); + if (err) { + dev_err(®->dev, "unable to get register 0x%x\n", + abreg->regreg); + return err; + } + + return regval & AB3100_REG_ON_MASK; +} + +static int ab3100_list_voltage_regulator(struct regulator_dev *reg, + unsigned selector) +{ + struct ab3100_regulator *abreg = reg->reg_data; + + if (selector > abreg->voltages_len) + return -EINVAL; + return abreg->typ_voltages[selector]; +} + +static int ab3100_get_voltage_regulator(struct regulator_dev *reg) +{ + struct ab3100_regulator *abreg = reg->reg_data; + u8 regval; + int err; + + /* Return the voltage for fixed regulators immediately */ + if (abreg->fixed_voltage) + return abreg->fixed_voltage; + + /* + * For variable types, read out setting and index into + * supplied voltage list. + */ + err = ab3100_get_register_interruptible(abreg->ab3100, + abreg->regreg, ®val); + if (err) { + dev_warn(®->dev, + "failed to get regulator value in register %02x\n", + abreg->regreg); + return err; + } + + /* The 3 highest bits index voltages */ + regval &= 0xE0; + regval >>= 5; + + if (regval > abreg->voltages_len) { + dev_err(®->dev, + "regulator register %02x contains an illegal voltage setting\n", + abreg->regreg); + return -EINVAL; + } + + return abreg->typ_voltages[regval]; +} + +static int ab3100_get_best_voltage_index(struct regulator_dev *reg, + int min_uV, int max_uV) +{ + struct ab3100_regulator *abreg = reg->reg_data; + int i; + int bestmatch; + int bestindex; + + /* + * 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. + */ + bestmatch = INT_MAX; + bestindex = -1; + for (i = 0; i < abreg->voltages_len; i++) { + if (abreg->typ_voltages[i] <= max_uV && + abreg->typ_voltages[i] >= min_uV && + abreg->typ_voltages[i] < bestmatch) { + bestmatch = abreg->typ_voltages[i]; + bestindex = i; + } + } + + if (bestindex < 0) { + dev_warn(®->dev, "requested %d<=x<=%d uV, out of range!\n", + min_uV, max_uV); + return -EINVAL; + } + return bestindex; +} + +static int ab3100_set_voltage_regulator(struct regulator_dev *reg, + int min_uV, int max_uV) +{ + struct ab3100_regulator *abreg = reg->reg_data; + u8 regval; + int err; + int bestindex; + + bestindex = ab3100_get_best_voltage_index(reg, min_uV, max_uV); + if (bestindex < 0) + return bestindex; + + err = ab3100_get_register_interruptible(abreg->ab3100, + abreg->regreg, ®val); + if (err) { + dev_warn(®->dev, + "failed to get regulator register %02x\n", + abreg->regreg); + return err; + } + + /* The highest three bits control the variable regulators */ + regval &= ~0xE0; + regval |= (bestindex << 5); + + err = ab3100_set_register_interruptible(abreg->ab3100, + abreg->regreg, regval); + if (err) + dev_warn(®->dev, "failed to set regulator register %02x\n", + abreg->regreg); + + return err; +} + +static int ab3100_set_suspend_voltage_regulator(struct regulator_dev *reg, + int uV) +{ + struct ab3100_regulator *abreg = reg->reg_data; + u8 regval; + int err; + int bestindex; + u8 targetreg; + + if (abreg->regreg == AB3100_LDO_E) + targetreg = AB3100_LDO_E_SLEEP; + else if (abreg->regreg == AB3100_BUCK) + targetreg = AB3100_BUCK_SLEEP; + else + return -EINVAL; + + /* LDO E and BUCK have special suspend voltages you can set */ + bestindex = ab3100_get_best_voltage_index(reg, uV, uV); + + err = ab3100_get_register_interruptible(abreg->ab3100, + targetreg, ®val); + if (err) { + dev_warn(®->dev, + "failed to get regulator register %02x\n", + targetreg); + return err; + } + + /* The highest three bits control the variable regulators */ + regval &= ~0xE0; + regval |= (bestindex << 5); + + err = ab3100_set_register_interruptible(abreg->ab3100, + targetreg, regval); + if (err) + dev_warn(®->dev, "failed to set regulator register %02x\n", + abreg->regreg); + + return err; +} + +/* + * The external regulator can just define a fixed voltage. + */ +static int ab3100_get_voltage_regulator_external(struct regulator_dev *reg) +{ + struct ab3100_regulator *abreg = reg->reg_data; + + return abreg->plfdata->external_voltage; +} + +static struct regulator_ops regulator_ops_fixed = { + .enable = ab3100_enable_regulator, + .disable = ab3100_disable_regulator, + .is_enabled = ab3100_is_enabled_regulator, + .get_voltage = ab3100_get_voltage_regulator, +}; + +static struct regulator_ops regulator_ops_variable = { + .enable = ab3100_enable_regulator, + .disable = ab3100_disable_regulator, + .is_enabled = ab3100_is_enabled_regulator, + .get_voltage = ab3100_get_voltage_regulator, + .set_voltage = ab3100_set_voltage_regulator, + .list_voltage = ab3100_list_voltage_regulator, +}; + +static struct regulator_ops regulator_ops_variable_sleepable = { + .enable = ab3100_enable_regulator, + .disable = ab3100_disable_regulator, + .is_enabled = ab3100_is_enabled_regulator, + .get_voltage = ab3100_get_voltage_regulator, + .set_voltage = ab3100_set_voltage_regulator, + .set_suspend_voltage = ab3100_set_suspend_voltage_regulator, + .list_voltage = ab3100_list_voltage_regulator, +}; + +/* + * LDO EXT is an external regulator so it is really + * not possible to set any voltage locally here, AB3100 + * is an on/off switch plain an simple. The external + * voltage is defined in the board set-up if any. + */ +static struct regulator_ops regulator_ops_external = { + .enable = ab3100_enable_regulator, + .disable = ab3100_disable_regulator, + .is_enabled = ab3100_is_enabled_regulator, + .get_voltage = ab3100_get_voltage_regulator_external, +}; + +static struct regulator_desc +ab3100_regulator_desc[AB3100_NUM_REGULATORS] = { + { + .name = "LDO_A", + .id = AB3100_LDO_A, + .ops = ®ulator_ops_fixed, + .type = REGULATOR_VOLTAGE, + }, + { + .name = "LDO_C", + .id = AB3100_LDO_C, + .ops = ®ulator_ops_fixed, + .type = REGULATOR_VOLTAGE, + }, + { + .name = "LDO_D", + .id = AB3100_LDO_D, + .ops = ®ulator_ops_fixed, + .type = REGULATOR_VOLTAGE, + }, + { + .name = "LDO_E", + .id = AB3100_LDO_E, + .ops = ®ulator_ops_variable_sleepable, + .type = REGULATOR_VOLTAGE, + }, + { + .name = "LDO_F", + .id = AB3100_LDO_F, + .ops = ®ulator_ops_variable, + .type = REGULATOR_VOLTAGE, + }, + { + .name = "LDO_G", + .id = AB3100_LDO_G, + .ops = ®ulator_ops_variable, + .type = REGULATOR_VOLTAGE, + }, + { + .name = "LDO_H", + .id = AB3100_LDO_H, + .ops = ®ulator_ops_variable, + .type = REGULATOR_VOLTAGE, + }, + { + .name = "LDO_K", + .id = AB3100_LDO_K, + .ops = ®ulator_ops_variable, + .type = REGULATOR_VOLTAGE, + }, + { + .name = "LDO_EXT", + .id = AB3100_LDO_EXT, + .ops = ®ulator_ops_external, + .type = REGULATOR_VOLTAGE, + }, + { + .name = "BUCK", + .id = AB3100_BUCK, + .ops = ®ulator_ops_variable_sleepable, + .type = REGULATOR_VOLTAGE, + }, +}; + +/* + * NOTE: the following functions are regulators pluralis - it is the + * binding to the AB3100 core driver and the parent platform device + * for all the different regulators. + */ + +static int __init ab3100_regulators_probe(struct platform_device *pdev) +{ + struct ab3100_platform_data *plfdata = pdev->dev.platform_data; + struct ab3100 *ab3100 = platform_get_drvdata(pdev); + int err = 0; + u8 data; + int i; + + /* Check chip state */ + err = ab3100_get_register_interruptible(ab3100, + AB3100_LDO_D, &data); + if (err) { + dev_err(&pdev->dev, "could not read initial status of LDO_D\n"); + return err; + } + if (data & 0x10) + dev_notice(&pdev->dev, + "chip is already in active mode (Warm start)\n"); + else + dev_notice(&pdev->dev, + "chip is in inactive mode (Cold start)\n"); + + /* Set up regulators */ + for (i = 0; i < ARRAY_SIZE(ab3100_reg_init_order); i++) { + err = ab3100_set_register_interruptible(ab3100, + ab3100_reg_init_order[i], + plfdata->reg_initvals[i]); + if (err) { + dev_err(&pdev->dev, "regulator initialization failed with error %d\n", + err); + return err; + } + } + + if (err) { + dev_err(&pdev->dev, + "LDO D regulator initialization failed with error %d\n", + err); + return err; + } + + /* Register the regulators */ + for (i = 0; i < AB3100_NUM_REGULATORS; i++) { + struct ab3100_regulator *reg = &ab3100_regulators[i]; + struct regulator_dev *rdev; + + /* + * Initialize per-regulator struct. + * Inherit platform data, this comes down from the + * i2c boarddata, from the machine. So if you want to + * see what it looks like for a certain machine, go + * into the machine I2C setup. + */ + reg->ab3100 = ab3100; + reg->plfdata = plfdata; + + /* + * Register the regulator, pass around + * the ab3100_regulator struct + */ + rdev = regulator_register(&ab3100_regulator_desc[i], + &pdev->dev, + &plfdata->reg_constraints[i], + reg); + + if (IS_ERR(rdev)) { + err = PTR_ERR(rdev); + dev_err(&pdev->dev, + "%s: failed to register regulator %s err %d\n", + __func__, ab3100_regulator_desc[i].name, + err); + i--; + /* remove the already registered regulators */ + while (i > 0) { + regulator_unregister(ab3100_regulators[i].rdev); + i--; + } + return err; + } + + /* Then set a pointer back to the registered regulator */ + reg->rdev = rdev; + } + + return 0; +} + +static int __exit ab3100_regulators_remove(struct platform_device *pdev) +{ + int i; + + for (i = 0; i < AB3100_NUM_REGULATORS; i++) { + struct ab3100_regulator *reg = &ab3100_regulators[i]; + + regulator_unregister(reg->rdev); + } + return 0; +} + +static struct platform_driver ab3100_regulators_driver = { + .driver = { + .name = "ab3100-regulators", + .owner = THIS_MODULE, + }, + .probe = ab3100_regulators_probe, + .remove = __exit_p(ab3100_regulators_remove), +}; + +static __init int ab3100_regulators_init(void) +{ + return platform_driver_register(&ab3100_regulators_driver); +} + +static __exit void ab3100_regulators_exit(void) +{ + platform_driver_register(&ab3100_regulators_driver); +} + +subsys_initcall(ab3100_regulators_init); +module_exit(ab3100_regulators_exit); + +MODULE_AUTHOR("Mattias Wallin "); +MODULE_DESCRIPTION("AB3100 Regulator driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:ab3100-regulators"); -- cgit v1.2.3 From 75f2ba8f0006440e720e47ae14c917e07c452d72 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Thu, 17 Sep 2009 09:17:33 +0200 Subject: regulator: Voltage count for AB3100 This sets the number of voltages for the AB3100 regulators so that they play well with the voltage listing functions and can be used properly with the MMC regulator integration glue for example. Signed-off-by: Linus Walleij Signed-off-by: Samuel Ortiz --- drivers/regulator/ab3100.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'drivers/regulator') diff --git a/drivers/regulator/ab3100.c b/drivers/regulator/ab3100.c index 156979d1f41..49aeee823a2 100644 --- a/drivers/regulator/ab3100.c +++ b/drivers/regulator/ab3100.c @@ -521,30 +521,35 @@ ab3100_regulator_desc[AB3100_NUM_REGULATORS] = { .name = "LDO_E", .id = AB3100_LDO_E, .ops = ®ulator_ops_variable_sleepable, + .n_voltages = ARRAY_SIZE(ldo_e_buck_typ_voltages), .type = REGULATOR_VOLTAGE, }, { .name = "LDO_F", .id = AB3100_LDO_F, .ops = ®ulator_ops_variable, + .n_voltages = ARRAY_SIZE(ldo_f_typ_voltages), .type = REGULATOR_VOLTAGE, }, { .name = "LDO_G", .id = AB3100_LDO_G, .ops = ®ulator_ops_variable, + .n_voltages = ARRAY_SIZE(ldo_g_typ_voltages), .type = REGULATOR_VOLTAGE, }, { .name = "LDO_H", .id = AB3100_LDO_H, .ops = ®ulator_ops_variable, + .n_voltages = ARRAY_SIZE(ldo_h_typ_voltages), .type = REGULATOR_VOLTAGE, }, { .name = "LDO_K", .id = AB3100_LDO_K, .ops = ®ulator_ops_variable, + .n_voltages = ARRAY_SIZE(ldo_k_typ_voltages), .type = REGULATOR_VOLTAGE, }, { @@ -557,6 +562,7 @@ ab3100_regulator_desc[AB3100_NUM_REGULATORS] = { .name = "BUCK", .id = AB3100_BUCK, .ops = ®ulator_ops_variable_sleepable, + .n_voltages = ARRAY_SIZE(ldo_e_buck_typ_voltages), .type = REGULATOR_VOLTAGE, }, }; -- cgit v1.2.3 From 4dee4d441d3f90cd8cec10a9eb222d8a4f2fa2a3 Mon Sep 17 00:00:00 2001 From: Roel Kluin Date: Mon, 15 Jun 2009 22:30:39 +0200 Subject: regulator: add check index of wm8350->pmic.pdev[] Ensure that reg is within the bounds of array wm8350->pmic.pdev[]. Signed-off-by: Roel Kluin Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- drivers/regulator/wm8350-regulator.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers/regulator') diff --git a/drivers/regulator/wm8350-regulator.c b/drivers/regulator/wm8350-regulator.c index 17a00b0fafd..768bd0e5b48 100644 --- a/drivers/regulator/wm8350-regulator.c +++ b/drivers/regulator/wm8350-regulator.c @@ -1419,6 +1419,8 @@ int wm8350_register_regulator(struct wm8350 *wm8350, int reg, { struct platform_device *pdev; int ret; + if (reg < 0 || reg >= NUM_WM8350_REGULATORS) + return -EINVAL; if (wm8350->pmic.pdev[reg]) return -EBUSY; -- cgit v1.2.3 From d61c3d56e23b3548a91b70ecce9dc226a8655a57 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 15 Jun 2009 20:01:01 +0100 Subject: regulator: Report regulator_get() failure in virtual consumer The core will no longer complain so we should log an error here. Signed-off-by: Mark Brown Signed-off-by: Liam Girdwood --- drivers/regulator/virtual.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers/regulator') diff --git a/drivers/regulator/virtual.c b/drivers/regulator/virtual.c index e7db5664722..e953c1810c7 100644 --- a/drivers/regulator/virtual.c +++ b/drivers/regulator/virtual.c @@ -285,6 +285,8 @@ static int regulator_virtual_consumer_probe(struct platform_device *pdev) drvdata->regulator = regulator_get(&pdev->dev, reg_id); if (IS_ERR(drvdata->regulator)) { ret = PTR_ERR(drvdata->regulator); + dev_err(&pdev->dev, "Failed to obtain supply '%s': %d\n", + reg_id, ret); goto err; } -- cgit v1.2.3 From a07ac217146e0fac18c80d93e02109f2c96574d0 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 17 Jun 2009 15:45:06 +0100 Subject: regulator: Make virtual consumer use dev_printk Signed-off-by: Mark Brown Signed-off-by: Liam Girdwood --- drivers/regulator/virtual.c | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) (limited to 'drivers/regulator') diff --git a/drivers/regulator/virtual.c b/drivers/regulator/virtual.c index e953c1810c7..5c1d75662b8 100644 --- a/drivers/regulator/virtual.c +++ b/drivers/regulator/virtual.c @@ -27,17 +27,18 @@ struct virtual_consumer_data { unsigned int mode; }; -static void update_voltage_constraints(struct virtual_consumer_data *data) +static void update_voltage_constraints(struct device *dev, + struct virtual_consumer_data *data) { int ret; if (data->min_uV && data->max_uV && data->min_uV <= data->max_uV) { ret = regulator_set_voltage(data->regulator, - data->min_uV, data->max_uV); + data->min_uV, data->max_uV); if (ret != 0) { - printk(KERN_ERR "regulator_set_voltage() failed: %d\n", - ret); + dev_err(dev, + "regulator_set_voltage() failed: %d\n", ret); return; } } @@ -47,7 +48,7 @@ static void update_voltage_constraints(struct virtual_consumer_data *data) if (ret == 0) data->enabled = 1; else - printk(KERN_ERR "regulator_enable() failed: %d\n", + dev_err(dev, "regulator_enable() failed: %d\n", ret); } @@ -56,13 +57,13 @@ static void update_voltage_constraints(struct virtual_consumer_data *data) if (ret == 0) data->enabled = 0; else - printk(KERN_ERR "regulator_disable() failed: %d\n", + dev_err(dev, "regulator_disable() failed: %d\n", ret); } } -static void update_current_limit_constraints(struct virtual_consumer_data - *data) +static void update_current_limit_constraints(struct device *dev, + struct virtual_consumer_data *data) { int ret; @@ -71,8 +72,9 @@ static void update_current_limit_constraints(struct virtual_consumer_data ret = regulator_set_current_limit(data->regulator, data->min_uA, data->max_uA); if (ret != 0) { - pr_err("regulator_set_current_limit() failed: %d\n", - ret); + dev_err(dev, + "regulator_set_current_limit() failed: %d\n", + ret); return; } } @@ -82,7 +84,7 @@ static void update_current_limit_constraints(struct virtual_consumer_data if (ret == 0) data->enabled = 1; else - printk(KERN_ERR "regulator_enable() failed: %d\n", + dev_err(dev, "regulator_enable() failed: %d\n", ret); } @@ -91,7 +93,7 @@ static void update_current_limit_constraints(struct virtual_consumer_data if (ret == 0) data->enabled = 0; else - printk(KERN_ERR "regulator_disable() failed: %d\n", + dev_err(dev, "regulator_disable() failed: %d\n", ret); } } @@ -115,7 +117,7 @@ static ssize_t set_min_uV(struct device *dev, struct device_attribute *attr, mutex_lock(&data->lock); data->min_uV = val; - update_voltage_constraints(data); + update_voltage_constraints(dev, data); mutex_unlock(&data->lock); @@ -141,7 +143,7 @@ static ssize_t set_max_uV(struct device *dev, struct device_attribute *attr, mutex_lock(&data->lock); data->max_uV = val; - update_voltage_constraints(data); + update_voltage_constraints(dev, data); mutex_unlock(&data->lock); @@ -167,7 +169,7 @@ static ssize_t set_min_uA(struct device *dev, struct device_attribute *attr, mutex_lock(&data->lock); data->min_uA = val; - update_current_limit_constraints(data); + update_current_limit_constraints(dev, data); mutex_unlock(&data->lock); @@ -193,7 +195,7 @@ static ssize_t set_max_uA(struct device *dev, struct device_attribute *attr, mutex_lock(&data->lock); data->max_uA = val; - update_current_limit_constraints(data); + update_current_limit_constraints(dev, data); mutex_unlock(&data->lock); -- cgit v1.2.3 From a5d2abce4373810c0109c5939c0094ac16698625 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 17 Jun 2009 15:45:07 +0100 Subject: regulator: Make virtual consumer a bit more chatty This makes it easier to read the logs when doing testing. Signed-off-by: Mark Brown Signed-off-by: Liam Girdwood --- drivers/regulator/virtual.c | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'drivers/regulator') diff --git a/drivers/regulator/virtual.c b/drivers/regulator/virtual.c index 5c1d75662b8..144110788fd 100644 --- a/drivers/regulator/virtual.c +++ b/drivers/regulator/virtual.c @@ -34,6 +34,8 @@ static void update_voltage_constraints(struct device *dev, if (data->min_uV && data->max_uV && data->min_uV <= data->max_uV) { + dev_dbg(dev, "Requesting %d-%duV\n", + data->min_uV, data->max_uV); ret = regulator_set_voltage(data->regulator, data->min_uV, data->max_uV); if (ret != 0) { @@ -44,6 +46,7 @@ static void update_voltage_constraints(struct device *dev, } if (data->min_uV && data->max_uV && !data->enabled) { + dev_dbg(dev, "Enabling regulator\n"); ret = regulator_enable(data->regulator); if (ret == 0) data->enabled = 1; @@ -53,6 +56,7 @@ static void update_voltage_constraints(struct device *dev, } if (!(data->min_uV && data->max_uV) && data->enabled) { + dev_dbg(dev, "Disabling regulator\n"); ret = regulator_disable(data->regulator); if (ret == 0) data->enabled = 0; @@ -69,6 +73,8 @@ static void update_current_limit_constraints(struct device *dev, if (data->max_uA && data->min_uA <= data->max_uA) { + dev_dbg(dev, "Requesting %d-%duA\n", + data->min_uA, data->max_uA); ret = regulator_set_current_limit(data->regulator, data->min_uA, data->max_uA); if (ret != 0) { @@ -80,6 +86,7 @@ static void update_current_limit_constraints(struct device *dev, } if (data->max_uA && !data->enabled) { + dev_dbg(dev, "Enabling regulator\n"); ret = regulator_enable(data->regulator); if (ret == 0) data->enabled = 1; @@ -89,6 +96,7 @@ static void update_current_limit_constraints(struct device *dev, } if (!(data->min_uA && data->max_uA) && data->enabled) { + dev_dbg(dev, "Disabling regulator\n"); ret = regulator_disable(data->regulator); if (ret == 0) data->enabled = 0; -- cgit v1.2.3 From 40f9244f4da8976eeb6d5ed6313c635ba238a9d3 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 17 Jun 2009 17:56:39 +0100 Subject: regulator: Allow consumer supplies to be set up with dev_name() Follow the approach suggested by Russell King and implemented by him in the clkdev API and allow consumer device supply mapings to be set up using the dev_name() for the consumer instead of the struct device. In order to avoid making existing machines instabuggy and creating merge issues the use of struct device is still supported for the time being. This resolves problems working with buses such as I2C which make the struct device available late providing that the final device name is known, which is the case for most embedded systems with fixed setups. Consumers must still use the struct device when calling regulator_get(). Signed-off-by: Mark Brown Signed-off-by: Liam Girdwood --- drivers/regulator/core.c | 62 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 48 insertions(+), 14 deletions(-) (limited to 'drivers/regulator') diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index 91ba9bfaa70..24e05b7607b 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -37,7 +37,7 @@ static int has_full_constraints; */ struct regulator_map { struct list_head list; - struct device *dev; + const char *dev_name; /* The dev_name() for the consumer */ const char *supply; struct regulator_dev *regulator; }; @@ -857,23 +857,33 @@ out: * set_consumer_device_supply: Bind a regulator to a symbolic supply * @rdev: regulator source * @consumer_dev: device the supply applies to + * @consumer_dev_name: dev_name() string for device supply applies to * @supply: symbolic name for supply * * Allows platform initialisation code to map physical regulator * sources to symbolic names for supplies for use by devices. Devices * should use these symbolic names to request regulators, avoiding the * need to provide board-specific regulator names as platform data. + * + * Only one of consumer_dev and consumer_dev_name may be specified. */ static int set_consumer_device_supply(struct regulator_dev *rdev, - struct device *consumer_dev, const char *supply) + struct device *consumer_dev, const char *consumer_dev_name, + const char *supply) { struct regulator_map *node; + if (consumer_dev && consumer_dev_name) + return -EINVAL; + + if (!consumer_dev_name && consumer_dev) + consumer_dev_name = dev_name(consumer_dev); + if (supply == NULL) return -EINVAL; list_for_each_entry(node, ®ulator_map_list, list) { - if (consumer_dev != node->dev) + if (consumer_dev_name != node->dev_name) continue; if (strcmp(node->supply, supply) != 0) continue; @@ -891,25 +901,38 @@ static int set_consumer_device_supply(struct regulator_dev *rdev, return -ENOMEM; node->regulator = rdev; - node->dev = consumer_dev; + node->dev_name = kstrdup(consumer_dev_name, GFP_KERNEL); node->supply = supply; + if (node->dev_name == NULL) { + kfree(node); + return -ENOMEM; + } + list_add(&node->list, ®ulator_map_list); return 0; } static void unset_consumer_device_supply(struct regulator_dev *rdev, - struct device *consumer_dev) + const char *consumer_dev_name, struct device *consumer_dev) { struct regulator_map *node, *n; + if (consumer_dev && !consumer_dev_name) + consumer_dev_name = dev_name(consumer_dev); + list_for_each_entry_safe(node, n, ®ulator_map_list, list) { - if (rdev == node->regulator && - consumer_dev == node->dev) { - list_del(&node->list); - kfree(node); - return; - } + if (rdev != node->regulator) + continue; + + if (consumer_dev_name && node->dev_name && + strcmp(consumer_dev_name, node->dev_name)) + continue; + + list_del(&node->list); + kfree(node->dev_name); + kfree(node); + return; } } @@ -920,6 +943,7 @@ static void unset_regulator_supplies(struct regulator_dev *rdev) list_for_each_entry_safe(node, n, ®ulator_map_list, list) { if (rdev == node->regulator) { list_del(&node->list); + kfree(node->dev_name); kfree(node); return; } @@ -1019,17 +1043,25 @@ struct regulator *regulator_get(struct device *dev, const char *id) struct regulator_dev *rdev; struct regulator_map *map; struct regulator *regulator = ERR_PTR(-ENODEV); + const char *devname = NULL; if (id == NULL) { printk(KERN_ERR "regulator: get() with no identifier\n"); return regulator; } + if (dev) + devname = dev_name(dev); + mutex_lock(®ulator_list_mutex); list_for_each_entry(map, ®ulator_map_list, list) { - if (dev == map->dev && - strcmp(map->supply, id) == 0) { + /* If the mapping has a device set up it must match */ + if (map->dev_name && + (!devname || strcmp(map->dev_name, devname))) + continue; + + if (strcmp(map->supply, id) == 0) { rdev = map->regulator; goto found; } @@ -2091,11 +2123,13 @@ struct regulator_dev *regulator_register(struct regulator_desc *regulator_desc, for (i = 0; i < init_data->num_consumer_supplies; i++) { ret = set_consumer_device_supply(rdev, init_data->consumer_supplies[i].dev, + init_data->consumer_supplies[i].dev_name, init_data->consumer_supplies[i].supply); if (ret < 0) { for (--i; i >= 0; i--) unset_consumer_device_supply(rdev, - init_data->consumer_supplies[i].dev); + init_data->consumer_supplies[i].dev_name, + init_data->consumer_supplies[i].dev); goto scrub; } } -- cgit v1.2.3 From 561864e8e3c263ff72bd0888aca80089027195ca Mon Sep 17 00:00:00 2001 From: Joe Perches Date: Sun, 28 Jun 2009 09:26:42 -0700 Subject: drivers/regulator/pcf50633-regulator.c: Remove unnecessary semicolons Signed-off-by: Joe Perches Signed-off-by: Liam Girdwood --- drivers/regulator/pcf50633-regulator.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/regulator') diff --git a/drivers/regulator/pcf50633-regulator.c b/drivers/regulator/pcf50633-regulator.c index 8e14900eb68..70ba7755765 100644 --- a/drivers/regulator/pcf50633-regulator.c +++ b/drivers/regulator/pcf50633-regulator.c @@ -155,7 +155,7 @@ static int pcf50633_regulator_get_voltage(struct regulator_dev *rdev) int regulator_id, millivolts, volt_bits; u8 regnr; - pcf = rdev_get_drvdata(rdev);; + pcf = rdev_get_drvdata(rdev); regulator_id = rdev_get_id(rdev); if (regulator_id >= PCF50633_NUM_REGULATORS) -- cgit v1.2.3 From 0198d1163b3e0313b3f073b62384abfab1a17cff Mon Sep 17 00:00:00 2001 From: Haojian Zhuang Date: Fri, 26 Jun 2009 19:20:59 +0800 Subject: regulator: add buck3 in da903x driver BUCK3 is the new component in DA9035. So there're three BUCKs in DA9035. And there're two BUCKs in DA9034. Signed-off-by: Haojian Zhuang Acked-by: Mark Brown Acked-by: Eric Miao Signed-off-by: Liam Girdwood --- drivers/regulator/da903x.c | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) (limited to 'drivers/regulator') diff --git a/drivers/regulator/da903x.c b/drivers/regulator/da903x.c index b8b89ef10a8..33dfeeb9407 100644 --- a/drivers/regulator/da903x.c +++ b/drivers/regulator/da903x.c @@ -64,6 +64,14 @@ #define DA9034_MDTV2 (0x33) #define DA9034_MVRC (0x34) +/* DA9035 Registers. DA9034 Registers are comptabile to DA9035. */ +#define DA9035_OVER3 (0x12) +#define DA9035_VCC2 (0x1f) +#define DA9035_3DTV1 (0x2c) +#define DA9035_3DTV2 (0x2d) +#define DA9035_3VRC (0x2e) +#define DA9035_AUTOSKIP (0x2f) + struct da903x_regulator_info { struct regulator_desc desc; @@ -388,6 +396,27 @@ static struct regulator_ops da9034_regulator_ldo12_ops = { .enable_bit = (ebit), \ } +#define DA9035_DVC(_id, min, max, step, vreg, nbits, ureg, ubit, ereg, ebit) \ +{ \ + .desc = { \ + .name = #_id, \ + .ops = &da9034_regulator_dvc_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = DA9035_ID_##_id, \ + .owner = THIS_MODULE, \ + }, \ + .min_uV = (min) * 1000, \ + .max_uV = (max) * 1000, \ + .step_uV = (step) * 1000, \ + .vol_reg = DA9035_##vreg, \ + .vol_shift = (0), \ + .vol_nbits = (nbits), \ + .update_reg = DA9035_##ureg, \ + .update_bit = (ubit), \ + .enable_reg = DA9035_##ereg, \ + .enable_bit = (ebit), \ +} + #define DA9034_LDO(_id, min, max, step, vreg, shift, nbits, ereg, ebit) \ DA903x_LDO(DA9034, _id, min, max, step, vreg, shift, nbits, ereg, ebit) @@ -435,6 +464,9 @@ static struct da903x_regulator_info da903x_regulator_info[] = { DA9034_LDO(14, 1800, 3300, 100, LDO1514, 0, 4, OVER3, 0), DA9034_LDO(15, 1800, 3300, 100, LDO1514, 4, 4, OVER3, 1), DA9034_LDO(5, 3100, 3100, 0, INVAL, 0, 0, OVER3, 7), /* fixed @3.1V */ + + /* DA9035 */ + DA9035_DVC(BUCK3, 1800, 2200, 100, 3DTV1, 3, VCC2, 0, OVER3, 3), }; static inline struct da903x_regulator_info *find_regulator_info(int id) -- cgit v1.2.3 From fc4f42e7fb021340c14dfd726313be6cfdeab19e Mon Sep 17 00:00:00 2001 From: Haojian Zhuang Date: Wed, 8 Jul 2009 17:57:24 +0800 Subject: regulator: support da9030 BUCK in da903x driver Support the operation of DA9030 BUCK2 in da903x driver. Signed-off-by: Haojian Zhuang Acked-by: Mark Brown Acked-by: Eric Miao Acked-by: Mike Rapoport Signed-off-by: Liam Girdwood --- drivers/regulator/da903x.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) (limited to 'drivers/regulator') diff --git a/drivers/regulator/da903x.c b/drivers/regulator/da903x.c index 33dfeeb9407..49081b44bf5 100644 --- a/drivers/regulator/da903x.c +++ b/drivers/regulator/da903x.c @@ -375,6 +375,27 @@ static struct regulator_ops da9034_regulator_ldo12_ops = { .enable_bit = (ebit), \ } +#define DA9030_DVC(_id, min, max, step, vreg, nbits, ureg, ubit, ereg, ebit) \ +{ \ + .desc = { \ + .name = #_id, \ + .ops = &da9034_regulator_dvc_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = DA9030_ID_##_id, \ + .owner = THIS_MODULE, \ + }, \ + .min_uV = (min) * 1000, \ + .max_uV = (max) * 1000, \ + .step_uV = (step) * 1000, \ + .vol_reg = DA9030_##vreg, \ + .vol_shift = (0), \ + .vol_nbits = (nbits), \ + .update_reg = DA9030_##ureg, \ + .update_bit = (ubit), \ + .enable_reg = DA9030_##ereg, \ + .enable_bit = (ebit), \ +} + #define DA9034_DVC(_id, min, max, step, vreg, nbits, ureg, ubit, ereg, ebit) \ { \ .desc = { \ @@ -425,6 +446,8 @@ static struct regulator_ops da9034_regulator_ldo12_ops = { static struct da903x_regulator_info da903x_regulator_info[] = { /* DA9030 */ + DA9030_DVC(BUCK2, 850, 1625, 25, BUCK2DVM1, 5, BUCK2DVM1, 7, RCTL11, 0), + DA9030_LDO( 1, 1200, 3200, 100, LDO1, 0, 5, RCTL12, 1), DA9030_LDO( 2, 1800, 3200, 100, LDO23, 0, 4, RCTL12, 2), DA9030_LDO( 3, 1800, 3200, 100, LDO23, 4, 4, RCTL12, 3), -- cgit v1.2.3 From e88267e1646037fa2c155515c78bd01a5c81f058 Mon Sep 17 00:00:00 2001 From: Haojian Zhuang Date: Thu, 9 Jul 2009 17:52:30 +0800 Subject: regulator: replace ADTV1 register by ADTV2 in da903x In PXA3xx SoC family, V_CORE power doamin is supplied by BUCK1 that is controller by ADTV1 or ADTV2 register. By default, v1 and v2 has the same copy. If v1 or v2 is updated, the last value that is written to either register takes effect. It means that v1 and v2 has different copy. And the actual voltage output is determinated by last update on either register. DA9034/35 is binded with PXA3xx SoC family. While SoC is scaling OP or entering/exiting lower power mode, SoC needs to change voltage of V_CORE power doamin. In order to be efficient, POWER I2C (hardcode) mode could be enabled in SoC. In this mode, SoC will control v2 register directly. In original DA903x driver, software will only read regulator data from v1 register. But SoC controls v2 register directly. It results that v1 and v2 isn't synchronized. Wrong data will be read from v1 register. So access v2 register in da903x driver instead. Signed-off-by: Haojian Zhuang Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- drivers/regulator/da903x.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers/regulator') diff --git a/drivers/regulator/da903x.c b/drivers/regulator/da903x.c index 49081b44bf5..d8d251f066a 100644 --- a/drivers/regulator/da903x.c +++ b/drivers/regulator/da903x.c @@ -469,9 +469,9 @@ static struct da903x_regulator_info da903x_regulator_info[] = { DA9030_LDO(13, 2100, 2100, 0, INVAL, 0, 0, RCTL11, 3), /* fixed @2.1V */ /* DA9034 */ - DA9034_DVC(BUCK1, 725, 1500, 25, ADTV1, 5, VCC1, 0, OVER1, 0), - DA9034_DVC(BUCK2, 725, 1500, 25, CDTV1, 5, VCC1, 2, OVER1, 1), - DA9034_DVC(LDO2, 725, 1500, 25, SDTV1, 5, VCC1, 4, OVER1, 2), + DA9034_DVC(BUCK1, 725, 1500, 25, ADTV2, 5, VCC1, 0, OVER1, 0), + DA9034_DVC(BUCK2, 725, 1500, 25, CDTV2, 5, VCC1, 2, OVER1, 1), + DA9034_DVC(LDO2, 725, 1500, 25, SDTV2, 5, VCC1, 4, OVER1, 2), DA9034_DVC(LDO1, 1700, 2075, 25, MDTV1, 4, VCC1, 6, OVER3, 4), DA9034_LDO( 3, 1800, 3300, 100, LDO643, 0, 4, OVER3, 5), -- cgit v1.2.3 From c1b60873ca2078bfca94b73bc88ef1c5adcc928b Mon Sep 17 00:00:00 2001 From: Haojian Zhuang Date: Fri, 10 Jul 2009 16:03:36 +0800 Subject: regulator: support list voltage in da903x Make da903x driver to list voltage and count voltage. Signed-off-by: Haojian Zhuang Acked-by: Mark Brown Acked-by: Eric Miao Signed-off-by: Liam Girdwood --- drivers/regulator/da903x.c | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) (limited to 'drivers/regulator') diff --git a/drivers/regulator/da903x.c b/drivers/regulator/da903x.c index d8d251f066a..236de113439 100644 --- a/drivers/regulator/da903x.c +++ b/drivers/regulator/da903x.c @@ -87,6 +87,10 @@ struct da903x_regulator_info { int enable_bit; }; +static int da9034_ldo12_data[] = { 1700, 1750, 1800, 1850, 1900, 1950, + 2000, 2050, 2700, 2750, 2800, 2850, + 2900, 2950, 3000, 3050 }; + static inline struct device *to_da903x_dev(struct regulator_dev *rdev) { return rdev_get_dev(rdev)->parent->parent; @@ -170,6 +174,17 @@ static int da903x_is_enabled(struct regulator_dev *rdev) return !!(reg_val & (1 << info->enable_bit)); } +static int da903x_list_voltage(struct regulator_dev *rdev, unsigned selector) +{ + struct da903x_regulator_info *info = rdev_get_drvdata(rdev); + int ret; + + ret = info->min_uV + info->step_uV * selector; + if (ret > info->max_uV) + return -EINVAL; + return ret; +} + /* DA9030 specific operations */ static int da9030_set_ldo1_15_voltage(struct regulator_dev *rdev, int min_uV, int max_uV) @@ -313,9 +328,18 @@ static int da9034_get_ldo12_voltage(struct regulator_dev *rdev) return info->min_uV + info->step_uV * val; } +static int da9034_list_ldo12_voltage(struct regulator_dev *rdev, + unsigned selector) +{ + if (selector > ARRAY_SIZE(da9034_ldo12_data)) + return -EINVAL; + return da9034_ldo12_data[selector] * 1000; +} + static struct regulator_ops da903x_regulator_ldo_ops = { .set_voltage = da903x_set_ldo_voltage, .get_voltage = da903x_get_voltage, + .list_voltage = da903x_list_voltage, .enable = da903x_enable, .disable = da903x_disable, .is_enabled = da903x_is_enabled, @@ -325,6 +349,7 @@ static struct regulator_ops da903x_regulator_ldo_ops = { static struct regulator_ops da9030_regulator_ldo14_ops = { .set_voltage = da9030_set_ldo14_voltage, .get_voltage = da9030_get_ldo14_voltage, + .list_voltage = da903x_list_voltage, .enable = da903x_enable, .disable = da903x_disable, .is_enabled = da903x_is_enabled, @@ -334,6 +359,7 @@ static struct regulator_ops da9030_regulator_ldo14_ops = { static struct regulator_ops da9030_regulator_ldo1_15_ops = { .set_voltage = da9030_set_ldo1_15_voltage, .get_voltage = da903x_get_voltage, + .list_voltage = da903x_list_voltage, .enable = da903x_enable, .disable = da903x_disable, .is_enabled = da903x_is_enabled, @@ -342,6 +368,7 @@ static struct regulator_ops da9030_regulator_ldo1_15_ops = { static struct regulator_ops da9034_regulator_dvc_ops = { .set_voltage = da9034_set_dvc_voltage, .get_voltage = da903x_get_voltage, + .list_voltage = da903x_list_voltage, .enable = da903x_enable, .disable = da903x_disable, .is_enabled = da903x_is_enabled, @@ -351,6 +378,7 @@ static struct regulator_ops da9034_regulator_dvc_ops = { static struct regulator_ops da9034_regulator_ldo12_ops = { .set_voltage = da9034_set_ldo12_voltage, .get_voltage = da9034_get_ldo12_voltage, + .list_voltage = da9034_list_ldo12_voltage, .enable = da903x_enable, .disable = da903x_disable, .is_enabled = da903x_is_enabled, @@ -363,6 +391,7 @@ static struct regulator_ops da9034_regulator_ldo12_ops = { .ops = &da903x_regulator_ldo_ops, \ .type = REGULATOR_VOLTAGE, \ .id = _pmic##_ID_LDO##_id, \ + .n_voltages = (step) ? ((max - min) / step + 1) : 1, \ .owner = THIS_MODULE, \ }, \ .min_uV = (min) * 1000, \ @@ -382,6 +411,7 @@ static struct regulator_ops da9034_regulator_ldo12_ops = { .ops = &da9034_regulator_dvc_ops, \ .type = REGULATOR_VOLTAGE, \ .id = DA9030_ID_##_id, \ + .n_voltages = (step) ? ((max - min) / step + 1) : 1, \ .owner = THIS_MODULE, \ }, \ .min_uV = (min) * 1000, \ @@ -403,6 +433,7 @@ static struct regulator_ops da9034_regulator_ldo12_ops = { .ops = &da9034_regulator_dvc_ops, \ .type = REGULATOR_VOLTAGE, \ .id = DA9034_ID_##_id, \ + .n_voltages = (step) ? ((max - min) / step + 1) : 1, \ .owner = THIS_MODULE, \ }, \ .min_uV = (min) * 1000, \ @@ -424,6 +455,7 @@ static struct regulator_ops da9034_regulator_ldo12_ops = { .ops = &da9034_regulator_dvc_ops, \ .type = REGULATOR_VOLTAGE, \ .id = DA9035_ID_##_id, \ + .n_voltages = (step) ? ((max - min) / step + 1) : 1, \ .owner = THIS_MODULE, \ }, \ .min_uV = (min) * 1000, \ @@ -517,8 +549,10 @@ static int __devinit da903x_regulator_probe(struct platform_device *pdev) } /* Workaround for the weird LDO12 voltage setting */ - if (ri->desc.id == DA9034_ID_LDO12) + if (ri->desc.id == DA9034_ID_LDO12) { ri->desc.ops = &da9034_regulator_ldo12_ops; + ri->desc.n_voltages = ARRAY_SIZE(da9034_ldo12_data); + } if (ri->desc.id == DA9030_ID_LDO14) ri->desc.ops = &da9030_regulator_ldo14_ops; -- cgit v1.2.3 From 5ffbd136e6c51c8d1eec7a4a0c5d2180c81aea30 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 21 Jul 2009 16:00:23 +0100 Subject: regulator: Add regulator_get_exclusive() API Some consumers require complete control of the regulator and can't tolerate sharing it with other consumers, most commonly because they need to have the regulator actually disabled so can't have other consumers forcing it on. This new regulator_get_exclusive() API call allows these consumers to explicitly request this, documenting the assumptions that they are making. In order to simplify coding of such consumers the use count for regulators they request is forced to match the enabled state of the regulator when it is requested. This is not possible for consumers which can share regulators due to the need to keep track of the ownership of use counts. A new API call is used rather than an additional argument to the existing regulator_get() in order to avoid merge headaches with driver code in other trees. Signed-off-by: Mark Brown Signed-off-by: Liam Girdwood --- drivers/regulator/core.c | 88 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 74 insertions(+), 14 deletions(-) (limited to 'drivers/regulator') diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index 24e05b7607b..68549008582 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -1025,25 +1025,15 @@ overflow_err: return NULL; } -/** - * regulator_get - lookup and obtain a reference to a regulator. - * @dev: device for regulator "consumer" - * @id: Supply name or regulator ID. - * - * Returns a struct regulator corresponding to the regulator producer, - * or IS_ERR() condition containing errno. - * - * Use of supply names configured via regulator_set_device_supply() is - * strongly encouraged. It is recommended that the supply name used - * should match the name used for the supply and/or the relevant - * device pins in the datasheet. - */ -struct regulator *regulator_get(struct device *dev, const char *id) +/* Internal regulator request function */ +static struct regulator *_regulator_get(struct device *dev, const char *id, + int exclusive) { struct regulator_dev *rdev; struct regulator_map *map; struct regulator *regulator = ERR_PTR(-ENODEV); const char *devname = NULL; + int ret; if (id == NULL) { printk(KERN_ERR "regulator: get() with no identifier\n"); @@ -1070,6 +1060,16 @@ struct regulator *regulator_get(struct device *dev, const char *id) return regulator; found: + if (rdev->exclusive) { + regulator = ERR_PTR(-EPERM); + goto out; + } + + if (exclusive && rdev->open_count) { + regulator = ERR_PTR(-EBUSY); + goto out; + } + if (!try_module_get(rdev->owner)) goto out; @@ -1079,12 +1079,69 @@ found: module_put(rdev->owner); } + rdev->open_count++; + if (exclusive) { + rdev->exclusive = 1; + + ret = _regulator_is_enabled(rdev); + if (ret > 0) + rdev->use_count = 1; + else + rdev->use_count = 0; + } + out: mutex_unlock(®ulator_list_mutex); + return regulator; } + +/** + * regulator_get - lookup and obtain a reference to a regulator. + * @dev: device for regulator "consumer" + * @id: Supply name or regulator ID. + * + * Returns a struct regulator corresponding to the regulator producer, + * or IS_ERR() condition containing errno. + * + * Use of supply names configured via regulator_set_device_supply() is + * strongly encouraged. It is recommended that the supply name used + * should match the name used for the supply and/or the relevant + * device pins in the datasheet. + */ +struct regulator *regulator_get(struct device *dev, const char *id) +{ + return _regulator_get(dev, id, 0); +} EXPORT_SYMBOL_GPL(regulator_get); +/** + * regulator_get_exclusive - obtain exclusive access to a regulator. + * @dev: device for regulator "consumer" + * @id: Supply name or regulator ID. + * + * Returns a struct regulator corresponding to the regulator producer, + * or IS_ERR() condition containing errno. Other consumers will be + * unable to obtain this reference is held and the use count for the + * regulator will be initialised to reflect the current state of the + * regulator. + * + * This is intended for use by consumers which cannot tolerate shared + * use of the regulator such as those which need to force the + * regulator off for correct operation of the hardware they are + * controlling. + * + * Use of supply names configured via regulator_set_device_supply() is + * strongly encouraged. It is recommended that the supply name used + * should match the name used for the supply and/or the relevant + * device pins in the datasheet. + */ +struct regulator *regulator_get_exclusive(struct device *dev, const char *id) +{ + return _regulator_get(dev, id, 1); +} +EXPORT_SYMBOL_GPL(regulator_get_exclusive); + /** * regulator_put - "free" the regulator source * @regulator: regulator source @@ -1113,6 +1170,9 @@ void regulator_put(struct regulator *regulator) list_del(®ulator->list); kfree(regulator); + rdev->open_count--; + rdev->exclusive = 0; + module_put(rdev->owner); mutex_unlock(®ulator_list_mutex); } -- cgit v1.2.3 From a7a1ad9066e0266c8a4357ba3dbaeebfb80f531d Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 21 Jul 2009 16:00:24 +0100 Subject: regulator: Add regulator voltage range check API Simplify checking of support for voltage ranges by providing an API which wraps the existing count and list operations. Signed-off-by: Mark Brown Signed-off-by: Liam Girdwood --- drivers/regulator/core.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) (limited to 'drivers/regulator') diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index 68549008582..e11c2222d9a 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -1441,6 +1441,35 @@ int regulator_list_voltage(struct regulator *regulator, unsigned selector) } EXPORT_SYMBOL_GPL(regulator_list_voltage); +/** + * regulator_is_supported_voltage - check if a voltage range can be supported + * + * @regulator: Regulator to check. + * @min_uV: Minimum required voltage in uV. + * @max_uV: Maximum required voltage in uV. + * + * Returns a boolean or a negative error code. + */ +int regulator_is_supported_voltage(struct regulator *regulator, + int min_uV, int max_uV) +{ + int i, voltages, ret; + + ret = regulator_count_voltages(regulator); + if (ret < 0) + return ret; + voltages = ret; + + for (i = 0; i < voltages; i++) { + ret = regulator_list_voltage(regulator, i); + + if (ret >= min_uV && ret <= max_uV) + return 1; + } + + return 0; +} + /** * regulator_set_voltage - set regulator output voltage * @regulator: regulator source -- cgit v1.2.3 From 6bf87d17c9f5b855e9dde7b3d6f726385b966814 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 21 Jul 2009 16:00:25 +0100 Subject: regulator: Warn when unregistering an in-use regulator We're probably going to start oopsing fairly soon after this happens. Signed-off-by: Mark Brown Signed-off-by: Liam Girdwood --- drivers/regulator/core.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/regulator') diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index e11c2222d9a..79a6910eb89 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -2253,6 +2253,7 @@ void regulator_unregister(struct regulator_dev *rdev) return; mutex_lock(®ulator_list_mutex); + WARN_ON(rdev->open_count); unset_regulator_supplies(rdev); list_del(&rdev->list); if (rdev->supply) -- cgit v1.2.3 From 9ed2099edca26d07947beb42c12bd1d6669e82bc Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 21 Jul 2009 16:00:26 +0100 Subject: regulator: Fix support for deviceless supply mappings The patch to add support for looking up consumers by device name had the side effect of causing us to require a device which is at best premature since at least cpufreq still operates outside the device model. Remove that requirement. Reported-by: Haojian Zhuang Signed-off-by: Mark Brown Signed-off-by: Liam Girdwood --- drivers/regulator/core.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) (limited to 'drivers/regulator') diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index 79a6910eb89..e38db55600e 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -872,6 +872,7 @@ static int set_consumer_device_supply(struct regulator_dev *rdev, const char *supply) { struct regulator_map *node; + int has_dev; if (consumer_dev && consumer_dev_name) return -EINVAL; @@ -882,6 +883,11 @@ static int set_consumer_device_supply(struct regulator_dev *rdev, if (supply == NULL) return -EINVAL; + if (consumer_dev_name != NULL) + has_dev = 1; + else + has_dev = 0; + list_for_each_entry(node, ®ulator_map_list, list) { if (consumer_dev_name != node->dev_name) continue; @@ -896,17 +902,19 @@ static int set_consumer_device_supply(struct regulator_dev *rdev, return -EBUSY; } - node = kmalloc(sizeof(struct regulator_map), GFP_KERNEL); + node = kzalloc(sizeof(struct regulator_map), GFP_KERNEL); if (node == NULL) return -ENOMEM; node->regulator = rdev; - node->dev_name = kstrdup(consumer_dev_name, GFP_KERNEL); node->supply = supply; - if (node->dev_name == NULL) { - kfree(node); - return -ENOMEM; + if (has_dev) { + node->dev_name = kstrdup(consumer_dev_name, GFP_KERNEL); + if (node->dev_name == NULL) { + kfree(node); + return -ENOMEM; + } } list_add(&node->list, ®ulator_map_list); -- cgit v1.2.3 From 72b86876d437a33253a47373579787b6dcc3bd36 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 21 Jul 2009 16:00:27 +0100 Subject: regulator: Improve virtual consumer probe error handling Report errors to the user and try harder to clean up if we're not able to probe. Signed-off-by: Mark Brown Signed-off-by: Liam Girdwood --- drivers/regulator/virtual.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'drivers/regulator') diff --git a/drivers/regulator/virtual.c b/drivers/regulator/virtual.c index 144110788fd..addc032c84b 100644 --- a/drivers/regulator/virtual.c +++ b/drivers/regulator/virtual.c @@ -286,8 +286,7 @@ static int regulator_virtual_consumer_probe(struct platform_device *pdev) drvdata = kzalloc(sizeof(struct virtual_consumer_data), GFP_KERNEL); if (drvdata == NULL) { - ret = -ENOMEM; - goto err; + return -ENOMEM; } mutex_init(&drvdata->lock); @@ -302,8 +301,11 @@ static int regulator_virtual_consumer_probe(struct platform_device *pdev) for (i = 0; i < ARRAY_SIZE(attributes); i++) { ret = device_create_file(&pdev->dev, attributes[i]); - if (ret != 0) - goto err; + if (ret != 0) { + dev_err(&pdev->dev, "Failed to create attr %d: %d\n", + i, ret); + goto err_regulator; + } } drvdata->mode = regulator_get_mode(drvdata->regulator); @@ -312,6 +314,8 @@ static int regulator_virtual_consumer_probe(struct platform_device *pdev) return 0; +err_regulator: + regulator_put(drvdata->regulator); err: for (i = 0; i < ARRAY_SIZE(attributes); i++) device_remove_file(&pdev->dev, attributes[i]); -- cgit v1.2.3 From c6db182822e292575b5beb56c003e95f616407f4 Mon Sep 17 00:00:00 2001 From: Mike Rapoport Date: Sun, 26 Jul 2009 13:33:04 +0300 Subject: regulator: da903x: consolidate DA903[045]_DVC macros Signed-off-by: Mike Rapoport Signed-off-by: Liam Girdwood --- drivers/regulator/da903x.c | 66 ++++++++++++---------------------------------- 1 file changed, 17 insertions(+), 49 deletions(-) (limited to 'drivers/regulator') diff --git a/drivers/regulator/da903x.c b/drivers/regulator/da903x.c index 236de113439..7d9c2506d21 100644 --- a/drivers/regulator/da903x.c +++ b/drivers/regulator/da903x.c @@ -404,69 +404,25 @@ static struct regulator_ops da9034_regulator_ldo12_ops = { .enable_bit = (ebit), \ } -#define DA9030_DVC(_id, min, max, step, vreg, nbits, ureg, ubit, ereg, ebit) \ +#define DA903x_DVC(_pmic, _id, min, max, step, vreg, nbits, ureg, ubit, ereg, ebit) \ { \ .desc = { \ .name = #_id, \ .ops = &da9034_regulator_dvc_ops, \ .type = REGULATOR_VOLTAGE, \ - .id = DA9030_ID_##_id, \ + .id = _pmic##_ID_##_id, \ .n_voltages = (step) ? ((max - min) / step + 1) : 1, \ .owner = THIS_MODULE, \ }, \ .min_uV = (min) * 1000, \ .max_uV = (max) * 1000, \ .step_uV = (step) * 1000, \ - .vol_reg = DA9030_##vreg, \ - .vol_shift = (0), \ - .vol_nbits = (nbits), \ - .update_reg = DA9030_##ureg, \ - .update_bit = (ubit), \ - .enable_reg = DA9030_##ereg, \ - .enable_bit = (ebit), \ -} - -#define DA9034_DVC(_id, min, max, step, vreg, nbits, ureg, ubit, ereg, ebit) \ -{ \ - .desc = { \ - .name = #_id, \ - .ops = &da9034_regulator_dvc_ops, \ - .type = REGULATOR_VOLTAGE, \ - .id = DA9034_ID_##_id, \ - .n_voltages = (step) ? ((max - min) / step + 1) : 1, \ - .owner = THIS_MODULE, \ - }, \ - .min_uV = (min) * 1000, \ - .max_uV = (max) * 1000, \ - .step_uV = (step) * 1000, \ - .vol_reg = DA9034_##vreg, \ - .vol_shift = (0), \ - .vol_nbits = (nbits), \ - .update_reg = DA9034_##ureg, \ - .update_bit = (ubit), \ - .enable_reg = DA9034_##ereg, \ - .enable_bit = (ebit), \ -} - -#define DA9035_DVC(_id, min, max, step, vreg, nbits, ureg, ubit, ereg, ebit) \ -{ \ - .desc = { \ - .name = #_id, \ - .ops = &da9034_regulator_dvc_ops, \ - .type = REGULATOR_VOLTAGE, \ - .id = DA9035_ID_##_id, \ - .n_voltages = (step) ? ((max - min) / step + 1) : 1, \ - .owner = THIS_MODULE, \ - }, \ - .min_uV = (min) * 1000, \ - .max_uV = (max) * 1000, \ - .step_uV = (step) * 1000, \ - .vol_reg = DA9035_##vreg, \ + .vol_reg = _pmic##_##vreg, \ .vol_shift = (0), \ .vol_nbits = (nbits), \ - .update_reg = DA9035_##ureg, \ + .update_reg = _pmic##_##ureg, \ .update_bit = (ubit), \ - .enable_reg = DA9035_##ereg, \ + .enable_reg = _pmic##_##ereg, \ .enable_bit = (ebit), \ } @@ -476,6 +432,18 @@ static struct regulator_ops da9034_regulator_ldo12_ops = { #define DA9030_LDO(_id, min, max, step, vreg, shift, nbits, ereg, ebit) \ DA903x_LDO(DA9030, _id, min, max, step, vreg, shift, nbits, ereg, ebit) +#define DA9030_DVC(_id, min, max, step, vreg, nbits, ureg, ubit, ereg, ebit) \ + DA903x_DVC(DA9030, _id, min, max, step, vreg, nbits, ureg, ubit, \ + ereg, ebit) + +#define DA9034_DVC(_id, min, max, step, vreg, nbits, ureg, ubit, ereg, ebit) \ + DA903x_DVC(DA9034, _id, min, max, step, vreg, nbits, ureg, ubit, \ + ereg, ebit) + +#define DA9035_DVC(_id, min, max, step, vreg, nbits, ureg, ubit, ereg, ebit) \ + DA903x_DVC(DA9035, _id, min, max, step, vreg, nbits, ureg, ubit, \ + ereg, ebit) + static struct da903x_regulator_info da903x_regulator_info[] = { /* DA9030 */ DA9030_DVC(BUCK2, 850, 1625, 25, BUCK2DVM1, 5, BUCK2DVM1, 7, RCTL11, 0), -- cgit v1.2.3 From c53ad7fe5759cea10137c9e176d14f8c8f22d286 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 3 Aug 2009 18:49:53 +0100 Subject: regulator: More explict error reporting for fixed regulator Signed-off-by: Mark Brown Signed-off-by: Liam Girdwood --- drivers/regulator/fixed.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers/regulator') diff --git a/drivers/regulator/fixed.c b/drivers/regulator/fixed.c index cdc674fb46c..9c7f956d57c 100644 --- a/drivers/regulator/fixed.c +++ b/drivers/regulator/fixed.c @@ -70,12 +70,14 @@ static int regulator_fixed_voltage_probe(struct platform_device *pdev) drvdata = kzalloc(sizeof(struct fixed_voltage_data), GFP_KERNEL); if (drvdata == NULL) { + dev_err(&pdev->dev, "Failed to allocate device data\n"); ret = -ENOMEM; goto err; } drvdata->desc.name = kstrdup(config->supply_name, GFP_KERNEL); if (drvdata->desc.name == NULL) { + dev_err(&pdev->dev, "Failed to allocate supply name\n"); ret = -ENOMEM; goto err; } @@ -90,6 +92,7 @@ static int regulator_fixed_voltage_probe(struct platform_device *pdev) config->init_data, drvdata); if (IS_ERR(drvdata->dev)) { ret = PTR_ERR(drvdata->dev); + dev_err(&pdev->dev, "Failed to register regulator: %d\n", ret); goto err_name; } -- cgit v1.2.3 From b39480ac37951de126455991744c9dbb61bbb839 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 3 Aug 2009 18:49:54 +0100 Subject: regulator: Check for constraints before using them for name Signed-off-by: Mark Brown Signed-off-by: Liam Girdwood --- drivers/regulator/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/regulator') diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index e38db55600e..6e0c723371d 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -232,7 +232,7 @@ static ssize_t regulator_name_show(struct device *dev, struct regulator_dev *rdev = dev_get_drvdata(dev); const char *name; - if (rdev->constraints->name) + if (rdev->constraints && rdev->constraints->name) name = rdev->constraints->name; else if (rdev->desc->name) name = rdev->desc->name; -- cgit v1.2.3 From f25e0b4fcc38d120e704c377791158c4b2a54daa Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 3 Aug 2009 18:49:55 +0100 Subject: regulator: Check for constraints in regulator_init_complete() Signed-off-by: Mark Brown Signed-off-by: Liam Girdwood --- drivers/regulator/core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/regulator') diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index 6e0c723371d..dfbf4312ec3 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -2409,14 +2409,14 @@ static int __init regulator_init_complete(void) ops = rdev->desc->ops; c = rdev->constraints; - if (c->name) + if (c && c->name) name = c->name; else if (rdev->desc->name) name = rdev->desc->name; else name = "regulator"; - if (!ops->disable || c->always_on) + if (!ops->disable || (c && c->always_on)) continue; mutex_lock(&rdev->mutex); -- cgit v1.2.3 From 9332546fe88fa88bf6a7d9b1dce53ff5d314934e Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 3 Aug 2009 18:49:56 +0100 Subject: regulator: Push locking for regulator_is_enabled() out Allows use by more of the internal regulator API code. Signed-off-by: Mark Brown Signed-off-by: Liam Girdwood --- drivers/regulator/core.c | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) (limited to 'drivers/regulator') diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index dfbf4312ec3..60fcd986ff3 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -280,8 +280,13 @@ static ssize_t regulator_state_show(struct device *dev, struct device_attribute *attr, char *buf) { struct regulator_dev *rdev = dev_get_drvdata(dev); + ssize_t ret; + + mutex_lock(&rdev->mutex); + ret = regulator_print_state(buf, _regulator_is_enabled(rdev)); + mutex_unlock(&rdev->mutex); - return regulator_print_state(buf, _regulator_is_enabled(rdev)); + return ret; } static DEVICE_ATTR(state, 0444, regulator_state_show, NULL); @@ -1365,20 +1370,11 @@ EXPORT_SYMBOL_GPL(regulator_force_disable); static int _regulator_is_enabled(struct regulator_dev *rdev) { - int ret; - - mutex_lock(&rdev->mutex); - /* sanity check */ - if (!rdev->desc->ops->is_enabled) { - ret = -EINVAL; - goto out; - } + if (!rdev->desc->ops->is_enabled) + return -EINVAL; - ret = rdev->desc->ops->is_enabled(rdev); -out: - mutex_unlock(&rdev->mutex); - return ret; + return rdev->desc->ops->is_enabled(rdev); } /** @@ -1395,7 +1391,13 @@ out: */ int regulator_is_enabled(struct regulator *regulator) { - return _regulator_is_enabled(regulator->rdev); + int ret; + + mutex_lock(®ulator->rdev->mutex); + ret = _regulator_is_enabled(regulator->rdev); + mutex_unlock(®ulator->rdev->mutex); + + return ret; } EXPORT_SYMBOL_GPL(regulator_is_enabled); -- cgit v1.2.3 From 9a2372fa7a403ba327873d0208a619d781a8a150 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 3 Aug 2009 18:49:57 +0100 Subject: regulator: regulator_enable() permission checking The regulator_enable() code wasn't actually checking that the machine constraints had given permission to enable the regulator. Add code to do that, but only if the regulator is not already on due to something like always_on or being left on at startup since in those cases there's no physical change being introduced and the constraint wouldn't make any sense. Also add matching code for disable(). We need to do less there since either regulator_enable() should have succeeded first or the board setup makes no sense. Signed-off-by: Mark Brown Signed-off-by: Liam Girdwood --- drivers/regulator/core.c | 56 +++++++++++++++++++++++++++++++----------------- 1 file changed, 36 insertions(+), 20 deletions(-) (limited to 'drivers/regulator') diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index 60fcd986ff3..dbf27bf028c 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -1191,16 +1191,21 @@ void regulator_put(struct regulator *regulator) } EXPORT_SYMBOL_GPL(regulator_put); +static int _regulator_can_change_status(struct regulator_dev *rdev) +{ + if (!rdev->constraints) + return 0; + + if (rdev->constraints->valid_ops_mask & REGULATOR_CHANGE_STATUS) + return 1; + else + return 0; +} + /* locks held by regulator_enable() */ static int _regulator_enable(struct regulator_dev *rdev) { - int ret = -EINVAL; - - if (!rdev->constraints) { - printk(KERN_ERR "%s: %s has no constraints\n", - __func__, rdev->desc->name); - return ret; - } + int ret; /* do we need to enable the supply regulator first */ if (rdev->supply) { @@ -1213,24 +1218,34 @@ static int _regulator_enable(struct regulator_dev *rdev) } /* check voltage and requested load before enabling */ - if (rdev->desc->ops->enable) { + if (rdev->constraints && + (rdev->constraints->valid_ops_mask & REGULATOR_CHANGE_DRMS)) + drms_uA_update(rdev); - if (rdev->constraints && - (rdev->constraints->valid_ops_mask & - REGULATOR_CHANGE_DRMS)) - drms_uA_update(rdev); - - ret = rdev->desc->ops->enable(rdev); - if (ret < 0) { - printk(KERN_ERR "%s: failed to enable %s: %d\n", + if (rdev->use_count == 0) { + /* The regulator may on if it's not switchable or left on */ + ret = _regulator_is_enabled(rdev); + if (ret == -EINVAL || ret == 0) { + if (!_regulator_can_change_status(rdev)) + return -EPERM; + + if (rdev->desc->ops->enable) { + ret = rdev->desc->ops->enable(rdev); + if (ret < 0) + return ret; + } else { + return -EINVAL; + } + } else { + printk(KERN_ERR "%s: is_enabled() failed for %s: %d\n", __func__, rdev->desc->name, ret); return ret; } - rdev->use_count++; - return ret; } - return ret; + rdev->use_count++; + + return 0; } /** @@ -1270,7 +1285,8 @@ static int _regulator_disable(struct regulator_dev *rdev) if (rdev->use_count == 1 && !rdev->constraints->always_on) { /* we are last user */ - if (rdev->desc->ops->disable) { + if (_regulator_can_change_status(rdev) && + rdev->desc->ops->disable) { ret = rdev->desc->ops->disable(rdev); if (ret < 0) { printk(KERN_ERR "%s: failed to disable %s\n", -- cgit v1.2.3 From a6576cff1801e2f1a9f328f02bd4cbcab7b03f91 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Tue, 4 Aug 2009 02:03:52 +0200 Subject: Regulator: Implement list_voltage for pcf50633 regulator driver. This patch implements list_voltage for the pcf50644 regulator driver. As the voltages are linearly scaled the code to convert register values to voltages can be reused and most of the code can be shared with get_voltage. Signed-off-by: Lars-Peter Clausen Signed-off-by: Liam Girdwood --- drivers/regulator/pcf50633-regulator.c | 96 ++++++++++++++++++++++------------ 1 file changed, 64 insertions(+), 32 deletions(-) (limited to 'drivers/regulator') diff --git a/drivers/regulator/pcf50633-regulator.c b/drivers/regulator/pcf50633-regulator.c index 70ba7755765..0803ffe6236 100644 --- a/drivers/regulator/pcf50633-regulator.c +++ b/drivers/regulator/pcf50633-regulator.c @@ -24,11 +24,12 @@ #include #include -#define PCF50633_REGULATOR(_name, _id) \ +#define PCF50633_REGULATOR(_name, _id, _n) \ { \ .name = _name, \ .id = _id, \ .ops = &pcf50633_regulator_ops, \ + .n_voltages = _n, \ .type = REGULATOR_VOLTAGE, \ .owner = THIS_MODULE, \ } @@ -149,11 +150,42 @@ static int pcf50633_regulator_set_voltage(struct regulator_dev *rdev, return pcf50633_reg_write(pcf, regnr, volt_bits); } +static int pcf50633_regulator_voltage_value(enum pcf50633_regulator_id id, + u8 bits) +{ + int millivolts; + + switch (id) { + case PCF50633_REGULATOR_AUTO: + millivolts = auto_voltage_value(bits); + break; + case PCF50633_REGULATOR_DOWN1: + millivolts = down_voltage_value(bits); + break; + case PCF50633_REGULATOR_DOWN2: + millivolts = down_voltage_value(bits); + break; + case PCF50633_REGULATOR_LDO1: + case PCF50633_REGULATOR_LDO2: + case PCF50633_REGULATOR_LDO3: + case PCF50633_REGULATOR_LDO4: + case PCF50633_REGULATOR_LDO5: + case PCF50633_REGULATOR_LDO6: + case PCF50633_REGULATOR_HCLDO: + millivolts = ldo_voltage_value(bits); + break; + default: + return -EINVAL; + } + + return millivolts * 1000; +} + static int pcf50633_regulator_get_voltage(struct regulator_dev *rdev) { struct pcf50633 *pcf; - int regulator_id, millivolts, volt_bits; - u8 regnr; + int regulator_id; + u8 volt_bits, regnr; pcf = rdev_get_drvdata(rdev); @@ -164,33 +196,32 @@ static int pcf50633_regulator_get_voltage(struct regulator_dev *rdev) regnr = pcf50633_regulator_registers[regulator_id]; volt_bits = pcf50633_reg_read(pcf, regnr); - if (volt_bits < 0) - return -1; + + return pcf50633_regulator_voltage_value(regulator_id, volt_bits); +} + +static int pcf50633_regulator_list_voltage(struct regulator_dev *rdev, + unsigned int index) +{ + struct pcf50633 *pcf; + int regulator_id; + + pcf = rdev_get_drvdata(rdev); + + regulator_id = rdev_get_id(rdev); switch (regulator_id) { case PCF50633_REGULATOR_AUTO: - millivolts = auto_voltage_value(volt_bits); - break; - case PCF50633_REGULATOR_DOWN1: - millivolts = down_voltage_value(volt_bits); - break; - case PCF50633_REGULATOR_DOWN2: - millivolts = down_voltage_value(volt_bits); + index += 0x2f; break; - case PCF50633_REGULATOR_LDO1: - case PCF50633_REGULATOR_LDO2: - case PCF50633_REGULATOR_LDO3: - case PCF50633_REGULATOR_LDO4: - case PCF50633_REGULATOR_LDO5: - case PCF50633_REGULATOR_LDO6: case PCF50633_REGULATOR_HCLDO: - millivolts = ldo_voltage_value(volt_bits); + index += 0x01; break; default: - return -EINVAL; + break; } - return millivolts * 1000; + return pcf50633_regulator_voltage_value(regulator_id, index); } static int pcf50633_regulator_enable(struct regulator_dev *rdev) @@ -246,6 +277,7 @@ static int pcf50633_regulator_is_enabled(struct regulator_dev *rdev) static struct regulator_ops pcf50633_regulator_ops = { .set_voltage = pcf50633_regulator_set_voltage, .get_voltage = pcf50633_regulator_get_voltage, + .list_voltage = pcf50633_regulator_list_voltage, .enable = pcf50633_regulator_enable, .disable = pcf50633_regulator_disable, .is_enabled = pcf50633_regulator_is_enabled, @@ -253,27 +285,27 @@ static struct regulator_ops pcf50633_regulator_ops = { static struct regulator_desc regulators[] = { [PCF50633_REGULATOR_AUTO] = - PCF50633_REGULATOR("auto", PCF50633_REGULATOR_AUTO), + PCF50633_REGULATOR("auto", PCF50633_REGULATOR_AUTO, 80), [PCF50633_REGULATOR_DOWN1] = - PCF50633_REGULATOR("down1", PCF50633_REGULATOR_DOWN1), + PCF50633_REGULATOR("down1", PCF50633_REGULATOR_DOWN1, 95), [PCF50633_REGULATOR_DOWN2] = - PCF50633_REGULATOR("down2", PCF50633_REGULATOR_DOWN2), + PCF50633_REGULATOR("down2", PCF50633_REGULATOR_DOWN2, 95), [PCF50633_REGULATOR_LDO1] = - PCF50633_REGULATOR("ldo1", PCF50633_REGULATOR_LDO1), + PCF50633_REGULATOR("ldo1", PCF50633_REGULATOR_LDO1, 27), [PCF50633_REGULATOR_LDO2] = - PCF50633_REGULATOR("ldo2", PCF50633_REGULATOR_LDO2), + PCF50633_REGULATOR("ldo2", PCF50633_REGULATOR_LDO2, 27), [PCF50633_REGULATOR_LDO3] = - PCF50633_REGULATOR("ldo3", PCF50633_REGULATOR_LDO3), + PCF50633_REGULATOR("ldo3", PCF50633_REGULATOR_LDO3, 27), [PCF50633_REGULATOR_LDO4] = - PCF50633_REGULATOR("ldo4", PCF50633_REGULATOR_LDO4), + PCF50633_REGULATOR("ldo4", PCF50633_REGULATOR_LDO4, 27), [PCF50633_REGULATOR_LDO5] = - PCF50633_REGULATOR("ldo5", PCF50633_REGULATOR_LDO5), + PCF50633_REGULATOR("ldo5", PCF50633_REGULATOR_LDO5, 27), [PCF50633_REGULATOR_LDO6] = - PCF50633_REGULATOR("ldo6", PCF50633_REGULATOR_LDO6), + PCF50633_REGULATOR("ldo6", PCF50633_REGULATOR_LDO6, 27), [PCF50633_REGULATOR_HCLDO] = - PCF50633_REGULATOR("hcldo", PCF50633_REGULATOR_HCLDO), + PCF50633_REGULATOR("hcldo", PCF50633_REGULATOR_HCLDO, 26), [PCF50633_REGULATOR_MEMLDO] = - PCF50633_REGULATOR("memldo", PCF50633_REGULATOR_MEMLDO), + PCF50633_REGULATOR("memldo", PCF50633_REGULATOR_MEMLDO, 0), }; static int __devinit pcf50633_regulator_probe(struct platform_device *pdev) -- cgit v1.2.3 From 86d9884b6a3646bc24e57430f1f694c5171c1bf6 Mon Sep 17 00:00:00 2001 From: Roger Quadros Date: Thu, 6 Aug 2009 19:37:29 +0300 Subject: regulator: Add GPIO enable control to fixed voltage regulator driver Now fixed regulators that have their enable pin connected to a GPIO line can use the fixed regulator driver for regulator enable/disable control. The GPIO number and polarity information is passed through platform data. GPIO enable control is achieved using gpiolib. Signed-off-by: Roger Quadros Reviewed-by: Philipp Zabel Reviewed-by: Felipe Balbi Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- drivers/regulator/fixed.c | 88 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 86 insertions(+), 2 deletions(-) (limited to 'drivers/regulator') diff --git a/drivers/regulator/fixed.c b/drivers/regulator/fixed.c index 9c7f956d57c..f8b295700d7 100644 --- a/drivers/regulator/fixed.c +++ b/drivers/regulator/fixed.c @@ -5,6 +5,9 @@ * * Author: Mark Brown * + * Copyright (c) 2009 Nokia Corporation + * Roger Quadros + * * 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 @@ -20,20 +23,45 @@ #include #include #include +#include struct fixed_voltage_data { struct regulator_desc desc; struct regulator_dev *dev; int microvolts; + int gpio; + unsigned enable_high:1; + unsigned is_enabled:1; }; static int fixed_voltage_is_enabled(struct regulator_dev *dev) { - return 1; + struct fixed_voltage_data *data = rdev_get_drvdata(dev); + + return data->is_enabled; } static int fixed_voltage_enable(struct regulator_dev *dev) { + struct fixed_voltage_data *data = rdev_get_drvdata(dev); + + if (gpio_is_valid(data->gpio)) { + gpio_set_value_cansleep(data->gpio, data->enable_high); + data->is_enabled = 1; + } + + return 0; +} + +static int fixed_voltage_disable(struct regulator_dev *dev) +{ + struct fixed_voltage_data *data = rdev_get_drvdata(dev); + + if (gpio_is_valid(data->gpio)) { + gpio_set_value_cansleep(data->gpio, !data->enable_high); + data->is_enabled = 0; + } + return 0; } @@ -58,6 +86,7 @@ static int fixed_voltage_list_voltage(struct regulator_dev *dev, static struct regulator_ops fixed_voltage_ops = { .is_enabled = fixed_voltage_is_enabled, .enable = fixed_voltage_enable, + .disable = fixed_voltage_disable, .get_voltage = fixed_voltage_get_voltage, .list_voltage = fixed_voltage_list_voltage, }; @@ -87,13 +116,62 @@ static int regulator_fixed_voltage_probe(struct platform_device *pdev) drvdata->desc.n_voltages = 1; drvdata->microvolts = config->microvolts; + drvdata->gpio = config->gpio; + + if (gpio_is_valid(config->gpio)) { + drvdata->enable_high = config->enable_high; + + /* FIXME: Remove below print warning + * + * config->gpio must be set to -EINVAL by platform code if + * GPIO control is not required. However, early adopters + * not requiring GPIO control may forget to initialize + * config->gpio to -EINVAL. This will cause GPIO 0 to be used + * for GPIO control. + * + * This warning will be removed once there are a couple of users + * for this driver. + */ + if (!config->gpio) + dev_warn(&pdev->dev, + "using GPIO 0 for regulator enable control\n"); + + ret = gpio_request(config->gpio, config->supply_name); + if (ret) { + dev_err(&pdev->dev, + "Could not obtain regulator enable GPIO %d: %d\n", + config->gpio, ret); + goto err_name; + } + + /* set output direction without changing state + * to prevent glitch + */ + drvdata->is_enabled = config->enabled_at_boot; + ret = drvdata->is_enabled ? + config->enable_high : !config->enable_high; + + ret = gpio_direction_output(config->gpio, ret); + if (ret) { + dev_err(&pdev->dev, + "Could not configure regulator enable GPIO %d direction: %d\n", + config->gpio, ret); + goto err_gpio; + } + + } else { + /* Regulator without GPIO control is considered + * always enabled + */ + drvdata->is_enabled = 1; + } drvdata->dev = regulator_register(&drvdata->desc, &pdev->dev, config->init_data, drvdata); if (IS_ERR(drvdata->dev)) { ret = PTR_ERR(drvdata->dev); dev_err(&pdev->dev, "Failed to register regulator: %d\n", ret); - goto err_name; + goto err_gpio; } platform_set_drvdata(pdev, drvdata); @@ -103,6 +181,9 @@ static int regulator_fixed_voltage_probe(struct platform_device *pdev) return 0; +err_gpio: + if (gpio_is_valid(config->gpio)) + gpio_free(config->gpio); err_name: kfree(drvdata->desc.name); err: @@ -118,6 +199,9 @@ static int regulator_fixed_voltage_remove(struct platform_device *pdev) kfree(drvdata->desc.name); kfree(drvdata); + if (gpio_is_valid(drvdata->gpio)) + gpio_free(drvdata->gpio); + return 0; } -- cgit v1.2.3 From e9d62698e8e5228638093c48783eb9dda788f1c3 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Mon, 10 Aug 2009 09:05:13 +0300 Subject: regulator: userspace: use sysfs_create_group and avoid introducing our own loops for creating several sysfs entries. Signed-off-by: Felipe Balbi Acked-by: Mark Brown Acked-by: Mike Rapoport Signed-off-by: Liam Girdwood --- drivers/regulator/userspace-consumer.c | 45 +++++++++++++++++----------------- 1 file changed, 23 insertions(+), 22 deletions(-) (limited to 'drivers/regulator') diff --git a/drivers/regulator/userspace-consumer.c b/drivers/regulator/userspace-consumer.c index 06d2fa96a8b..44917da4ac9 100644 --- a/drivers/regulator/userspace-consumer.c +++ b/drivers/regulator/userspace-consumer.c @@ -93,16 +93,21 @@ static ssize_t reg_set_state(struct device *dev, struct device_attribute *attr, static DEVICE_ATTR(name, 0444, reg_show_name, NULL); static DEVICE_ATTR(state, 0644, reg_show_state, reg_set_state); -static struct device_attribute *attributes[] = { - &dev_attr_name, - &dev_attr_state, +static struct attribute *attributes[] = { + &dev_attr_name.attr, + &dev_attr_state.attr, + NULL, +}; + +static const struct attribute_group attr_group = { + .attrs = attributes, }; static int regulator_userspace_consumer_probe(struct platform_device *pdev) { struct regulator_userspace_consumer_data *pdata; struct userspace_consumer_data *drvdata; - int ret, i; + int ret; pdata = pdev->dev.platform_data; if (!pdata) @@ -125,31 +130,29 @@ static int regulator_userspace_consumer_probe(struct platform_device *pdev) goto err_alloc_supplies; } - for (i = 0; i < ARRAY_SIZE(attributes); i++) { - ret = device_create_file(&pdev->dev, attributes[i]); - if (ret != 0) - goto err_create_attrs; - } + ret = sysfs_create_group(&pdev->dev.kobj, &attr_group); + if (ret != 0) + goto err_create_attrs; - if (pdata->init_on) + if (pdata->init_on) { ret = regulator_bulk_enable(drvdata->num_supplies, drvdata->supplies); - - drvdata->enabled = pdata->init_on; - - if (ret) { - dev_err(&pdev->dev, "Failed to set initial state: %d\n", ret); - goto err_create_attrs; + if (ret) { + dev_err(&pdev->dev, + "Failed to set initial state: %d\n", ret); + goto err_enable; + } } + drvdata->enabled = pdata->init_on; platform_set_drvdata(pdev, drvdata); return 0; -err_create_attrs: - for (i = 0; i < ARRAY_SIZE(attributes); i++) - device_remove_file(&pdev->dev, attributes[i]); +err_enable: + sysfs_remove_group(&pdev->dev.kobj, &attr_group); +err_create_attrs: regulator_bulk_free(drvdata->num_supplies, drvdata->supplies); err_alloc_supplies: @@ -160,10 +163,8 @@ err_alloc_supplies: static int regulator_userspace_consumer_remove(struct platform_device *pdev) { struct userspace_consumer_data *data = platform_get_drvdata(pdev); - int i; - for (i = 0; i < ARRAY_SIZE(attributes); i++) - device_remove_file(&pdev->dev, attributes[i]); + sysfs_remove_group(&pdev->dev.kobj, &attr_group); if (data->enabled) regulator_bulk_disable(data->num_supplies, data->supplies); -- cgit v1.2.3 From 30e6599d317ec83c664f341f18b5b2b57b831a6d Mon Sep 17 00:00:00 2001 From: Anuj Aggarwal Date: Fri, 21 Aug 2009 00:39:31 +0530 Subject: Regulator: Add TPS65023 regulator driver Adding support for TI TPS65023 regulator driver Signed-off-by: Anuj Aggarwal Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- drivers/regulator/tps65023-regulator.c | 631 +++++++++++++++++++++++++++++++++ 1 file changed, 631 insertions(+) create mode 100644 drivers/regulator/tps65023-regulator.c (limited to 'drivers/regulator') diff --git a/drivers/regulator/tps65023-regulator.c b/drivers/regulator/tps65023-regulator.c new file mode 100644 index 00000000000..1e54f46af67 --- /dev/null +++ b/drivers/regulator/tps65023-regulator.c @@ -0,0 +1,631 @@ +/* + * tps65023-regulator.c + * + * Supports TPS65023 Regulator + * + * Copyright (C) 2009 Texas Instrument Incorporated - http://www.ti.com/ + * + * 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 version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any kind, + * whether express or implied; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Register definitions */ +#define TPS65023_REG_VERSION 0 +#define TPS65023_REG_PGOODZ 1 +#define TPS65023_REG_MASK 2 +#define TPS65023_REG_REG_CTRL 3 +#define TPS65023_REG_CON_CTRL 4 +#define TPS65023_REG_CON_CTRL2 5 +#define TPS65023_REG_DEF_CORE 6 +#define TPS65023_REG_DEFSLEW 7 +#define TPS65023_REG_LDO_CTRL 8 + +/* PGOODZ bitfields */ +#define TPS65023_PGOODZ_PWRFAILZ BIT(7) +#define TPS65023_PGOODZ_LOWBATTZ BIT(6) +#define TPS65023_PGOODZ_VDCDC1 BIT(5) +#define TPS65023_PGOODZ_VDCDC2 BIT(4) +#define TPS65023_PGOODZ_VDCDC3 BIT(3) +#define TPS65023_PGOODZ_LDO2 BIT(2) +#define TPS65023_PGOODZ_LDO1 BIT(1) + +/* MASK bitfields */ +#define TPS65023_MASK_PWRFAILZ BIT(7) +#define TPS65023_MASK_LOWBATTZ BIT(6) +#define TPS65023_MASK_VDCDC1 BIT(5) +#define TPS65023_MASK_VDCDC2 BIT(4) +#define TPS65023_MASK_VDCDC3 BIT(3) +#define TPS65023_MASK_LDO2 BIT(2) +#define TPS65023_MASK_LDO1 BIT(1) + +/* REG_CTRL bitfields */ +#define TPS65023_REG_CTRL_VDCDC1_EN BIT(5) +#define TPS65023_REG_CTRL_VDCDC2_EN BIT(4) +#define TPS65023_REG_CTRL_VDCDC3_EN BIT(3) +#define TPS65023_REG_CTRL_LDO2_EN BIT(2) +#define TPS65023_REG_CTRL_LDO1_EN BIT(1) + +/* LDO_CTRL bitfields */ +#define TPS65023_LDO_CTRL_LDOx_SHIFT(ldo_id) ((ldo_id)*4) +#define TPS65023_LDO_CTRL_LDOx_MASK(ldo_id) (0xF0 >> ((ldo_id)*4)) + +/* Number of step-down converters available */ +#define TPS65023_NUM_DCDC 3 +/* Number of LDO voltage regulators available */ +#define TPS65023_NUM_LDO 2 +/* Number of total regulators available */ +#define TPS65023_NUM_REGULATOR (TPS65023_NUM_DCDC + TPS65023_NUM_LDO) + +/* DCDCs */ +#define TPS65023_DCDC_1 0 +#define TPS65023_DCDC_2 1 +#define TPS65023_DCDC_3 2 +/* LDOs */ +#define TPS65023_LDO_1 3 +#define TPS65023_LDO_2 4 + +#define TPS65023_MAX_REG_ID TPS65023_LDO_2 + +/* Supported voltage values for regulators */ +static const u16 VDCDC1_VSEL_table[] = { + 800, 825, 850, 875, + 900, 925, 950, 975, + 1000, 1025, 1050, 1075, + 1100, 1125, 1150, 1175, + 1200, 1225, 1250, 1275, + 1300, 1325, 1350, 1375, + 1400, 1425, 1450, 1475, + 1500, 1525, 1550, 1600, +}; + +static const u16 LDO1_VSEL_table[] = { + 1000, 1100, 1300, 1800, + 2200, 2600, 2800, 3150, +}; + +static const u16 LDO2_VSEL_table[] = { + 1050, 1200, 1300, 1800, + 2500, 2800, 3000, 3300, +}; + +static unsigned int num_voltages[] = {ARRAY_SIZE(VDCDC1_VSEL_table), + 0, 0, ARRAY_SIZE(LDO1_VSEL_table), + ARRAY_SIZE(LDO2_VSEL_table)}; + +/* Regulator specific details */ +struct tps_info { + const char *name; + unsigned min_uV; + unsigned max_uV; + bool fixed; + u8 table_len; + const u16 *table; +}; + +/* PMIC details */ +struct tps_pmic { + struct regulator_desc desc[TPS65023_NUM_REGULATOR]; + struct i2c_client *client; + struct regulator_dev *rdev[TPS65023_NUM_REGULATOR]; + const struct tps_info *info[TPS65023_NUM_REGULATOR]; + struct mutex io_lock; +}; + +static inline int tps_65023_read(struct tps_pmic *tps, u8 reg) +{ + return i2c_smbus_read_byte_data(tps->client, reg); +} + +static inline int tps_65023_write(struct tps_pmic *tps, u8 reg, u8 val) +{ + return i2c_smbus_write_byte_data(tps->client, reg, val); +} + +static int tps_65023_set_bits(struct tps_pmic *tps, u8 reg, u8 mask) +{ + int err, data; + + mutex_lock(&tps->io_lock); + + data = tps_65023_read(tps, reg); + if (data < 0) { + dev_err(&tps->client->dev, "Read from reg 0x%x failed\n", reg); + err = data; + goto out; + } + + data |= mask; + err = tps_65023_write(tps, reg, data); + if (err) + dev_err(&tps->client->dev, "Write for reg 0x%x failed\n", reg); + +out: + mutex_unlock(&tps->io_lock); + return err; +} + +static int tps_65023_clear_bits(struct tps_pmic *tps, u8 reg, u8 mask) +{ + int err, data; + + mutex_lock(&tps->io_lock); + + data = tps_65023_read(tps, reg); + if (data < 0) { + dev_err(&tps->client->dev, "Read from reg 0x%x failed\n", reg); + err = data; + goto out; + } + + data &= ~mask; + + err = tps_65023_write(tps, reg, data); + if (err) + dev_err(&tps->client->dev, "Write for reg 0x%x failed\n", reg); + +out: + mutex_unlock(&tps->io_lock); + return err; + +} + +static int tps_65023_reg_read(struct tps_pmic *tps, u8 reg) +{ + int data; + + mutex_lock(&tps->io_lock); + + data = tps_65023_read(tps, reg); + if (data < 0) + dev_err(&tps->client->dev, "Read from reg 0x%x failed\n", reg); + + mutex_unlock(&tps->io_lock); + return data; +} + +static int tps_65023_reg_write(struct tps_pmic *tps, u8 reg, u8 val) +{ + int err; + + mutex_lock(&tps->io_lock); + + err = tps_65023_write(tps, reg, val); + if (err < 0) + dev_err(&tps->client->dev, "Write for reg 0x%x failed\n", reg); + + mutex_unlock(&tps->io_lock); + return err; +} + +static int tps65023_dcdc_is_enabled(struct regulator_dev *dev) +{ + struct tps_pmic *tps = rdev_get_drvdata(dev); + int data, dcdc = rdev_get_id(dev); + u8 shift; + + if (dcdc < TPS65023_DCDC_1 || dcdc > TPS65023_DCDC_3) + return -EINVAL; + + shift = TPS65023_NUM_REGULATOR - dcdc; + data = tps_65023_reg_read(tps, TPS65023_REG_REG_CTRL); + + if (data < 0) + return data; + else + return (data & 1< TPS65023_LDO_2) + return -EINVAL; + + shift = (ldo == TPS65023_LDO_1 ? 1 : 2); + data = tps_65023_reg_read(tps, TPS65023_REG_REG_CTRL); + + if (data < 0) + return data; + else + return (data & 1< TPS65023_DCDC_3) + return -EINVAL; + + shift = TPS65023_NUM_REGULATOR - dcdc; + return tps_65023_set_bits(tps, TPS65023_REG_REG_CTRL, 1 << shift); +} + +static int tps65023_dcdc_disable(struct regulator_dev *dev) +{ + struct tps_pmic *tps = rdev_get_drvdata(dev); + int dcdc = rdev_get_id(dev); + u8 shift; + + if (dcdc < TPS65023_DCDC_1 || dcdc > TPS65023_DCDC_3) + return -EINVAL; + + shift = TPS65023_NUM_REGULATOR - dcdc; + return tps_65023_clear_bits(tps, TPS65023_REG_REG_CTRL, 1 << shift); +} + +static int tps65023_ldo_enable(struct regulator_dev *dev) +{ + struct tps_pmic *tps = rdev_get_drvdata(dev); + int ldo = rdev_get_id(dev); + u8 shift; + + if (ldo < TPS65023_LDO_1 || ldo > TPS65023_LDO_2) + return -EINVAL; + + shift = (ldo == TPS65023_LDO_1 ? 1 : 2); + return tps_65023_set_bits(tps, TPS65023_REG_REG_CTRL, 1 << shift); +} + +static int tps65023_ldo_disable(struct regulator_dev *dev) +{ + struct tps_pmic *tps = rdev_get_drvdata(dev); + int ldo = rdev_get_id(dev); + u8 shift; + + if (ldo < TPS65023_LDO_1 || ldo > TPS65023_LDO_2) + return -EINVAL; + + shift = (ldo == TPS65023_LDO_1 ? 1 : 2); + return tps_65023_clear_bits(tps, TPS65023_REG_REG_CTRL, 1 << shift); +} + +static int tps65023_dcdc_get_voltage(struct regulator_dev *dev) +{ + struct tps_pmic *tps = rdev_get_drvdata(dev); + int data, dcdc = rdev_get_id(dev); + + if (dcdc < TPS65023_DCDC_1 || dcdc > TPS65023_DCDC_3) + return -EINVAL; + + if (dcdc == TPS65023_DCDC_1) { + data = tps_65023_reg_read(tps, TPS65023_REG_DEF_CORE); + if (data < 0) + return data; + data &= (tps->info[dcdc]->table_len - 1); + return tps->info[dcdc]->table[data] * 1000; + } else + return tps->info[dcdc]->min_uV; +} + +static int tps65023_dcdc_set_voltage(struct regulator_dev *dev, + int min_uV, int max_uV) +{ + struct tps_pmic *tps = rdev_get_drvdata(dev); + int dcdc = rdev_get_id(dev); + int vsel; + + if (dcdc != TPS65023_DCDC_1) + return -EINVAL; + + if (min_uV < tps->info[dcdc]->min_uV + || min_uV > tps->info[dcdc]->max_uV) + return -EINVAL; + if (max_uV < tps->info[dcdc]->min_uV + || max_uV > tps->info[dcdc]->max_uV) + return -EINVAL; + + for (vsel = 0; vsel < tps->info[dcdc]->table_len; vsel++) { + int mV = tps->info[dcdc]->table[vsel]; + int uV = mV * 1000; + + /* Break at the first in-range value */ + if (min_uV <= uV && uV <= max_uV) + break; + } + + /* write to the register in case we found a match */ + if (vsel == tps->info[dcdc]->table_len) + return -EINVAL; + else + return tps_65023_reg_write(tps, TPS65023_REG_DEF_CORE, vsel); +} + +static int tps65023_ldo_get_voltage(struct regulator_dev *dev) +{ + struct tps_pmic *tps = rdev_get_drvdata(dev); + int data, ldo = rdev_get_id(dev); + + if (ldo < TPS65023_LDO_1 || ldo > TPS65023_LDO_2) + return -EINVAL; + + data = tps_65023_reg_read(tps, TPS65023_REG_LDO_CTRL); + if (data < 0) + return data; + + data >>= (TPS65023_LDO_CTRL_LDOx_SHIFT(ldo - TPS65023_LDO_1)); + data &= (tps->info[ldo]->table_len - 1); + return tps->info[ldo]->table[data] * 1000; +} + +static int tps65023_ldo_set_voltage(struct regulator_dev *dev, + int min_uV, int max_uV) +{ + struct tps_pmic *tps = rdev_get_drvdata(dev); + int data, vsel, ldo = rdev_get_id(dev); + + if (ldo < TPS65023_LDO_1 || ldo > TPS65023_LDO_2) + return -EINVAL; + + if (min_uV < tps->info[ldo]->min_uV || min_uV > tps->info[ldo]->max_uV) + return -EINVAL; + if (max_uV < tps->info[ldo]->min_uV || max_uV > tps->info[ldo]->max_uV) + return -EINVAL; + + for (vsel = 0; vsel < tps->info[ldo]->table_len; vsel++) { + int mV = tps->info[ldo]->table[vsel]; + int uV = mV * 1000; + + /* Break at the first in-range value */ + if (min_uV <= uV && uV <= max_uV) + break; + } + + if (vsel == tps->info[ldo]->table_len) + return -EINVAL; + + data = tps_65023_reg_read(tps, TPS65023_REG_LDO_CTRL); + if (data < 0) + return data; + + data &= TPS65023_LDO_CTRL_LDOx_MASK(ldo - TPS65023_LDO_1); + data |= (vsel << (TPS65023_LDO_CTRL_LDOx_SHIFT(ldo - TPS65023_LDO_1))); + return tps_65023_reg_write(tps, TPS65023_REG_LDO_CTRL, data); +} + +static int tps65023_dcdc_list_voltage(struct regulator_dev *dev, + unsigned selector) +{ + struct tps_pmic *tps = rdev_get_drvdata(dev); + int dcdc = rdev_get_id(dev); + + if (dcdc < TPS65023_DCDC_1 || dcdc > TPS65023_DCDC_3) + return -EINVAL; + + if (dcdc == TPS65023_DCDC_1) { + if (selector >= tps->info[dcdc]->table_len) + return -EINVAL; + else + return tps->info[dcdc]->table[selector] * 1000; + } else + return tps->info[dcdc]->min_uV; +} + +static int tps65023_ldo_list_voltage(struct regulator_dev *dev, + unsigned selector) +{ + struct tps_pmic *tps = rdev_get_drvdata(dev); + int ldo = rdev_get_id(dev); + + if (ldo < TPS65023_LDO_1 || ldo > TPS65023_LDO_2) + return -EINVAL; + + if (selector >= tps->info[ldo]->table_len) + return -EINVAL; + else + return tps->info[ldo]->table[selector] * 1000; +} + +/* Operations permitted on VDCDCx */ +static struct regulator_ops tps65023_dcdc_ops = { + .is_enabled = tps65023_dcdc_is_enabled, + .enable = tps65023_dcdc_enable, + .disable = tps65023_dcdc_disable, + .get_voltage = tps65023_dcdc_get_voltage, + .set_voltage = tps65023_dcdc_set_voltage, + .list_voltage = tps65023_dcdc_list_voltage, +}; + +/* Operations permitted on LDOx */ +static struct regulator_ops tps65023_ldo_ops = { + .is_enabled = tps65023_ldo_is_enabled, + .enable = tps65023_ldo_enable, + .disable = tps65023_ldo_disable, + .get_voltage = tps65023_ldo_get_voltage, + .set_voltage = tps65023_ldo_set_voltage, + .list_voltage = tps65023_ldo_list_voltage, +}; + +static +int tps_65023_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + static int desc_id; + const struct tps_info *info = (void *)id->driver_data; + struct regulator_init_data *init_data; + struct regulator_dev *rdev; + struct tps_pmic *tps; + int i; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + /** + * init_data points to array of regulator_init structures + * coming from the board-evm file. + */ + init_data = client->dev.platform_data; + + if (!init_data) + return -EIO; + + tps = kzalloc(sizeof(*tps), GFP_KERNEL); + if (!tps) + return -ENOMEM; + + mutex_init(&tps->io_lock); + + /* common for all regulators */ + tps->client = client; + + for (i = 0; i < TPS65023_NUM_REGULATOR; i++, info++, init_data++) { + /* Store regulator specific information */ + tps->info[i] = info; + + tps->desc[i].name = info->name; + tps->desc[i].id = desc_id++; + tps->desc[i].n_voltages = num_voltages[i]; + tps->desc[i].ops = (i > TPS65023_DCDC_3 ? + &tps65023_ldo_ops : &tps65023_dcdc_ops); + tps->desc[i].type = REGULATOR_VOLTAGE; + tps->desc[i].owner = THIS_MODULE; + + /* Register the regulators */ + rdev = regulator_register(&tps->desc[i], &client->dev, + init_data, tps); + if (IS_ERR(rdev)) { + dev_err(&client->dev, "failed to register %s\n", + id->name); + + /* Unregister */ + while (i) + regulator_unregister(tps->rdev[--i]); + + tps->client = NULL; + + /* clear the client data in i2c */ + i2c_set_clientdata(client, NULL); + kfree(tps); + return PTR_ERR(rdev); + } + + /* Save regulator for cleanup */ + tps->rdev[i] = rdev; + } + + i2c_set_clientdata(client, tps); + + return 0; +} + +/** + * tps_65023_remove - TPS65023 driver i2c remove handler + * @client: i2c driver client device structure + * + * Unregister TPS driver as an i2c client device driver + */ +static int __devexit tps_65023_remove(struct i2c_client *client) +{ + struct tps_pmic *tps = i2c_get_clientdata(client); + int i; + + for (i = 0; i < TPS65023_NUM_REGULATOR; i++) + regulator_unregister(tps->rdev[i]); + + tps->client = NULL; + + /* clear the client data in i2c */ + i2c_set_clientdata(client, NULL); + kfree(tps); + + return 0; +} + +static const struct tps_info tps65023_regs[] = { + { + .name = "VDCDC1", + .min_uV = 800000, + .max_uV = 1600000, + .table_len = ARRAY_SIZE(VDCDC1_VSEL_table), + .table = VDCDC1_VSEL_table, + }, + { + .name = "VDCDC2", + .min_uV = 3300000, + .max_uV = 3300000, + .fixed = 1, + }, + { + .name = "VDCDC3", + .min_uV = 1800000, + .max_uV = 1800000, + .fixed = 1, + }, + { + .name = "LDO1", + .min_uV = 1000000, + .max_uV = 3150000, + .table_len = ARRAY_SIZE(LDO1_VSEL_table), + .table = LDO1_VSEL_table, + }, + { + .name = "LDO2", + .min_uV = 1050000, + .max_uV = 3300000, + .table_len = ARRAY_SIZE(LDO2_VSEL_table), + .table = LDO2_VSEL_table, + }, +}; + +static const struct i2c_device_id tps_65023_id = { + .name = "tps65023", + .driver_data = (unsigned long) &tps65023_regs[0], +}; + +MODULE_DEVICE_TABLE(i2c, tps_65023_id); + +static struct i2c_driver tps_65023_i2c_driver = { + .driver = { + .name = "tps65023", + .owner = THIS_MODULE, + }, + .probe = tps_65023_probe, + .remove = __devexit_p(tps_65023_remove), + .id_table = &tps_65023_id, +}; + +/** + * tps_65023_init + * + * Module init function + */ +static int __init tps_65023_init(void) +{ + return i2c_add_driver(&tps_65023_i2c_driver); +} +subsys_initcall(tps_65023_init); + +/** + * tps_65023_cleanup + * + * Module exit function + */ +static void __exit tps_65023_cleanup(void) +{ + i2c_del_driver(&tps_65023_i2c_driver); +} +module_exit(tps_65023_cleanup); + +MODULE_AUTHOR("Texas Instruments"); +MODULE_DESCRIPTION("TPS65023 voltage regulator driver"); +MODULE_LICENSE("GPLv2"); -- cgit v1.2.3 From 3fa5b8e08296b250088b1a6b8e3db500ab1b847d Mon Sep 17 00:00:00 2001 From: Anuj Aggarwal Date: Fri, 21 Aug 2009 00:39:39 +0530 Subject: Regulator: Add TPS6507x regulator driver Adding support for TI TPS6507x regulator driver Signed-off-by: Anuj Aggarwal Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- drivers/regulator/tps6507x-regulator.c | 713 +++++++++++++++++++++++++++++++++ 1 file changed, 713 insertions(+) create mode 100644 drivers/regulator/tps6507x-regulator.c (limited to 'drivers/regulator') diff --git a/drivers/regulator/tps6507x-regulator.c b/drivers/regulator/tps6507x-regulator.c new file mode 100644 index 00000000000..1aa36369512 --- /dev/null +++ b/drivers/regulator/tps6507x-regulator.c @@ -0,0 +1,713 @@ +/* + * tps6507x-regulator.c + * + * Regulator driver for TPS65073 PMIC + * + * Copyright (C) 2009 Texas Instrument Incorporated - http://www.ti.com/ + * + * 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 version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any kind, + * whether express or implied; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Register definitions */ +#define TPS6507X_REG_PPATH1 0X01 +#define TPS6507X_REG_INT 0X02 +#define TPS6507X_REG_CHGCONFIG0 0X03 +#define TPS6507X_REG_CHGCONFIG1 0X04 +#define TPS6507X_REG_CHGCONFIG2 0X05 +#define TPS6507X_REG_CHGCONFIG3 0X06 +#define TPS6507X_REG_REG_ADCONFIG 0X07 +#define TPS6507X_REG_TSCMODE 0X08 +#define TPS6507X_REG_ADRESULT_1 0X09 +#define TPS6507X_REG_ADRESULT_2 0X0A +#define TPS6507X_REG_PGOOD 0X0B +#define TPS6507X_REG_PGOODMASK 0X0C +#define TPS6507X_REG_CON_CTRL1 0X0D +#define TPS6507X_REG_CON_CTRL2 0X0E +#define TPS6507X_REG_CON_CTRL3 0X0F +#define TPS6507X_REG_DEFDCDC1 0X10 +#define TPS6507X_REG_DEFDCDC2_LOW 0X11 +#define TPS6507X_REG_DEFDCDC2_HIGH 0X12 +#define TPS6507X_REG_DEFDCDC3_LOW 0X13 +#define TPS6507X_REG_DEFDCDC3_HIGH 0X14 +#define TPS6507X_REG_DEFSLEW 0X15 +#define TPS6507X_REG_LDO_CTRL1 0X16 +#define TPS6507X_REG_DEFLDO2 0X17 +#define TPS6507X_REG_WLED_CTRL1 0X18 +#define TPS6507X_REG_WLED_CTRL2 0X19 + +/* CON_CTRL1 bitfields */ +#define TPS6507X_CON_CTRL1_DCDC1_ENABLE BIT(4) +#define TPS6507X_CON_CTRL1_DCDC2_ENABLE BIT(3) +#define TPS6507X_CON_CTRL1_DCDC3_ENABLE BIT(2) +#define TPS6507X_CON_CTRL1_LDO1_ENABLE BIT(1) +#define TPS6507X_CON_CTRL1_LDO2_ENABLE BIT(0) + +/* DEFDCDC1 bitfields */ +#define TPS6507X_DEFDCDC1_DCDC1_EXT_ADJ_EN BIT(7) +#define TPS6507X_DEFDCDC1_DCDC1_MASK 0X3F + +/* DEFDCDC2_LOW bitfields */ +#define TPS6507X_DEFDCDC2_LOW_DCDC2_MASK 0X3F + +/* DEFDCDC2_HIGH bitfields */ +#define TPS6507X_DEFDCDC2_HIGH_DCDC2_MASK 0X3F + +/* DEFDCDC3_LOW bitfields */ +#define TPS6507X_DEFDCDC3_LOW_DCDC3_MASK 0X3F + +/* DEFDCDC3_HIGH bitfields */ +#define TPS6507X_DEFDCDC3_HIGH_DCDC3_MASK 0X3F + +/* TPS6507X_REG_LDO_CTRL1 bitfields */ +#define TPS6507X_REG_LDO_CTRL1_LDO1_MASK 0X0F + +/* TPS6507X_REG_DEFLDO2 bitfields */ +#define TPS6507X_REG_DEFLDO2_LDO2_MASK 0X3F + +/* VDCDC MASK */ +#define TPS6507X_DEFDCDCX_DCDC_MASK 0X3F + +/* DCDC's */ +#define TPS6507X_DCDC_1 0 +#define TPS6507X_DCDC_2 1 +#define TPS6507X_DCDC_3 2 +/* LDOs */ +#define TPS6507X_LDO_1 3 +#define TPS6507X_LDO_2 4 + +#define TPS6507X_MAX_REG_ID TPS6507X_LDO_2 + +/* Number of step-down converters available */ +#define TPS6507X_NUM_DCDC 3 +/* Number of LDO voltage regulators available */ +#define TPS6507X_NUM_LDO 2 +/* Number of total regulators available */ +#define TPS6507X_NUM_REGULATOR (TPS6507X_NUM_DCDC + TPS6507X_NUM_LDO) + +/* Supported voltage values for regulators (in milliVolts) */ +static const u16 VDCDCx_VSEL_table[] = { + 725, 750, 775, 800, + 825, 850, 875, 900, + 925, 950, 975, 1000, + 1025, 1050, 1075, 1100, + 1125, 1150, 1175, 1200, + 1225, 1250, 1275, 1300, + 1325, 1350, 1375, 1400, + 1425, 1450, 1475, 1500, + 1550, 1600, 1650, 1700, + 1750, 1800, 1850, 1900, + 1950, 2000, 2050, 2100, + 2150, 2200, 2250, 2300, + 2350, 2400, 2450, 2500, + 2550, 2600, 2650, 2700, + 2750, 2800, 2850, 2900, + 3000, 3100, 3200, 3300, +}; + +static const u16 LDO1_VSEL_table[] = { + 1000, 1100, 1200, 1250, + 1300, 1350, 1400, 1500, + 1600, 1800, 2500, 2750, + 2800, 3000, 3100, 3300, +}; + +static const u16 LDO2_VSEL_table[] = { + 725, 750, 775, 800, + 825, 850, 875, 900, + 925, 950, 975, 1000, + 1025, 1050, 1075, 1100, + 1125, 1150, 1175, 1200, + 1225, 1250, 1275, 1300, + 1325, 1350, 1375, 1400, + 1425, 1450, 1475, 1500, + 1550, 1600, 1650, 1700, + 1750, 1800, 1850, 1900, + 1950, 2000, 2050, 2100, + 2150, 2200, 2250, 2300, + 2350, 2400, 2450, 2500, + 2550, 2600, 2650, 2700, + 2750, 2800, 2850, 2900, + 3000, 3100, 3200, 3300, +}; + +static unsigned int num_voltages[] = {ARRAY_SIZE(VDCDCx_VSEL_table), + ARRAY_SIZE(VDCDCx_VSEL_table), + ARRAY_SIZE(VDCDCx_VSEL_table), + ARRAY_SIZE(LDO1_VSEL_table), + ARRAY_SIZE(LDO2_VSEL_table)}; + +struct tps_info { + const char *name; + unsigned min_uV; + unsigned max_uV; + u8 table_len; + const u16 *table; +}; + +struct tps_pmic { + struct regulator_desc desc[TPS6507X_NUM_REGULATOR]; + struct i2c_client *client; + struct regulator_dev *rdev[TPS6507X_NUM_REGULATOR]; + const struct tps_info *info[TPS6507X_NUM_REGULATOR]; + struct mutex io_lock; +}; + +static inline int tps_6507x_read(struct tps_pmic *tps, u8 reg) +{ + return i2c_smbus_read_byte_data(tps->client, reg); +} + +static inline int tps_6507x_write(struct tps_pmic *tps, u8 reg, u8 val) +{ + return i2c_smbus_write_byte_data(tps->client, reg, val); +} + +static int tps_6507x_set_bits(struct tps_pmic *tps, u8 reg, u8 mask) +{ + int err, data; + + mutex_lock(&tps->io_lock); + + data = tps_6507x_read(tps, reg); + if (data < 0) { + dev_err(&tps->client->dev, "Read from reg 0x%x failed\n", reg); + err = data; + goto out; + } + + data |= mask; + err = tps_6507x_write(tps, reg, data); + if (err) + dev_err(&tps->client->dev, "Write for reg 0x%x failed\n", reg); + +out: + mutex_unlock(&tps->io_lock); + return err; +} + +static int tps_6507x_clear_bits(struct tps_pmic *tps, u8 reg, u8 mask) +{ + int err, data; + + mutex_lock(&tps->io_lock); + + data = tps_6507x_read(tps, reg); + if (data < 0) { + dev_err(&tps->client->dev, "Read from reg 0x%x failed\n", reg); + err = data; + goto out; + } + + data &= ~mask; + err = tps_6507x_write(tps, reg, data); + if (err) + dev_err(&tps->client->dev, "Write for reg 0x%x failed\n", reg); + +out: + mutex_unlock(&tps->io_lock); + return err; +} + +static int tps_6507x_reg_read(struct tps_pmic *tps, u8 reg) +{ + int data; + + mutex_lock(&tps->io_lock); + + data = tps_6507x_read(tps, reg); + if (data < 0) + dev_err(&tps->client->dev, "Read from reg 0x%x failed\n", reg); + + mutex_unlock(&tps->io_lock); + return data; +} + +static int tps_6507x_reg_write(struct tps_pmic *tps, u8 reg, u8 val) +{ + int err; + + mutex_lock(&tps->io_lock); + + err = tps_6507x_write(tps, reg, val); + if (err < 0) + dev_err(&tps->client->dev, "Write for reg 0x%x failed\n", reg); + + mutex_unlock(&tps->io_lock); + return err; +} + +static int tps6507x_dcdc_is_enabled(struct regulator_dev *dev) +{ + struct tps_pmic *tps = rdev_get_drvdata(dev); + int data, dcdc = rdev_get_id(dev); + u8 shift; + + if (dcdc < TPS6507X_DCDC_1 || dcdc > TPS6507X_DCDC_3) + return -EINVAL; + + shift = TPS6507X_MAX_REG_ID - dcdc; + data = tps_6507x_reg_read(tps, TPS6507X_REG_CON_CTRL1); + + if (data < 0) + return data; + else + return (data & 1< TPS6507X_LDO_2) + return -EINVAL; + + shift = TPS6507X_MAX_REG_ID - ldo; + data = tps_6507x_reg_read(tps, TPS6507X_REG_CON_CTRL1); + + if (data < 0) + return data; + else + return (data & 1< TPS6507X_DCDC_3) + return -EINVAL; + + shift = TPS6507X_MAX_REG_ID - dcdc; + return tps_6507x_set_bits(tps, TPS6507X_REG_CON_CTRL1, 1 << shift); +} + +static int tps6507x_dcdc_disable(struct regulator_dev *dev) +{ + struct tps_pmic *tps = rdev_get_drvdata(dev); + int dcdc = rdev_get_id(dev); + u8 shift; + + if (dcdc < TPS6507X_DCDC_1 || dcdc > TPS6507X_DCDC_3) + return -EINVAL; + + shift = TPS6507X_MAX_REG_ID - dcdc; + return tps_6507x_clear_bits(tps, TPS6507X_REG_CON_CTRL1, 1 << shift); +} + +static int tps6507x_ldo_enable(struct regulator_dev *dev) +{ + struct tps_pmic *tps = rdev_get_drvdata(dev); + int ldo = rdev_get_id(dev); + u8 shift; + + if (ldo < TPS6507X_LDO_1 || ldo > TPS6507X_LDO_2) + return -EINVAL; + + shift = TPS6507X_MAX_REG_ID - ldo; + return tps_6507x_set_bits(tps, TPS6507X_REG_CON_CTRL1, 1 << shift); +} + +static int tps6507x_ldo_disable(struct regulator_dev *dev) +{ + struct tps_pmic *tps = rdev_get_drvdata(dev); + int ldo = rdev_get_id(dev); + u8 shift; + + if (ldo < TPS6507X_LDO_1 || ldo > TPS6507X_LDO_2) + return -EINVAL; + + shift = TPS6507X_MAX_REG_ID - ldo; + return tps_6507x_clear_bits(tps, TPS6507X_REG_CON_CTRL1, 1 << shift); +} + +static int tps6507x_dcdc_get_voltage(struct regulator_dev *dev) +{ + struct tps_pmic *tps = rdev_get_drvdata(dev); + int data, dcdc = rdev_get_id(dev); + u8 reg; + + switch (dcdc) { + case TPS6507X_DCDC_1: + reg = TPS6507X_REG_DEFDCDC1; + break; + case TPS6507X_DCDC_2: + reg = TPS6507X_REG_DEFDCDC2_LOW; + break; + case TPS6507X_DCDC_3: + reg = TPS6507X_REG_DEFDCDC3_LOW; + break; + default: + return -EINVAL; + } + + data = tps_6507x_reg_read(tps, reg); + if (data < 0) + return data; + + data &= TPS6507X_DEFDCDCX_DCDC_MASK; + return tps->info[dcdc]->table[data] * 1000; +} + +static int tps6507x_dcdc_set_voltage(struct regulator_dev *dev, + int min_uV, int max_uV) +{ + struct tps_pmic *tps = rdev_get_drvdata(dev); + int data, vsel, dcdc = rdev_get_id(dev); + u8 reg; + + switch (dcdc) { + case TPS6507X_DCDC_1: + reg = TPS6507X_REG_DEFDCDC1; + break; + case TPS6507X_DCDC_2: + reg = TPS6507X_REG_DEFDCDC2_LOW; + break; + case TPS6507X_DCDC_3: + reg = TPS6507X_REG_DEFDCDC3_LOW; + break; + default: + return -EINVAL; + } + + if (min_uV < tps->info[dcdc]->min_uV + || min_uV > tps->info[dcdc]->max_uV) + return -EINVAL; + if (max_uV < tps->info[dcdc]->min_uV + || max_uV > tps->info[dcdc]->max_uV) + return -EINVAL; + + for (vsel = 0; vsel < tps->info[dcdc]->table_len; vsel++) { + int mV = tps->info[dcdc]->table[vsel]; + int uV = mV * 1000; + + /* Break at the first in-range value */ + if (min_uV <= uV && uV <= max_uV) + break; + } + + /* write to the register in case we found a match */ + if (vsel == tps->info[dcdc]->table_len) + return -EINVAL; + + data = tps_6507x_reg_read(tps, reg); + if (data < 0) + return data; + + data &= ~TPS6507X_DEFDCDCX_DCDC_MASK; + data |= vsel; + + return tps_6507x_reg_write(tps, reg, data); +} + +static int tps6507x_ldo_get_voltage(struct regulator_dev *dev) +{ + struct tps_pmic *tps = rdev_get_drvdata(dev); + int data, ldo = rdev_get_id(dev); + u8 reg, mask; + + if (ldo < TPS6507X_LDO_1 || ldo > TPS6507X_LDO_2) + return -EINVAL; + else { + reg = (ldo == TPS6507X_LDO_1 ? + TPS6507X_REG_LDO_CTRL1 : TPS6507X_REG_DEFLDO2); + mask = (ldo == TPS6507X_LDO_1 ? + TPS6507X_REG_LDO_CTRL1_LDO1_MASK : + TPS6507X_REG_DEFLDO2_LDO2_MASK); + } + + data = tps_6507x_reg_read(tps, reg); + if (data < 0) + return data; + + data &= mask; + return tps->info[ldo]->table[data] * 1000; +} + +static int tps6507x_ldo_set_voltage(struct regulator_dev *dev, + int min_uV, int max_uV) +{ + struct tps_pmic *tps = rdev_get_drvdata(dev); + int data, vsel, ldo = rdev_get_id(dev); + u8 reg, mask; + + if (ldo < TPS6507X_LDO_1 || ldo > TPS6507X_LDO_2) + return -EINVAL; + else { + reg = (ldo == TPS6507X_LDO_1 ? + TPS6507X_REG_LDO_CTRL1 : TPS6507X_REG_DEFLDO2); + mask = (ldo == TPS6507X_LDO_1 ? + TPS6507X_REG_LDO_CTRL1_LDO1_MASK : + TPS6507X_REG_DEFLDO2_LDO2_MASK); + } + + if (min_uV < tps->info[ldo]->min_uV || min_uV > tps->info[ldo]->max_uV) + return -EINVAL; + if (max_uV < tps->info[ldo]->min_uV || max_uV > tps->info[ldo]->max_uV) + return -EINVAL; + + for (vsel = 0; vsel < tps->info[ldo]->table_len; vsel++) { + int mV = tps->info[ldo]->table[vsel]; + int uV = mV * 1000; + + /* Break at the first in-range value */ + if (min_uV <= uV && uV <= max_uV) + break; + } + + if (vsel == tps->info[ldo]->table_len) + return -EINVAL; + + data = tps_6507x_reg_read(tps, reg); + if (data < 0) + return data; + + data &= ~mask; + data |= vsel; + + return tps_6507x_reg_write(tps, reg, data); +} + +static int tps6507x_dcdc_list_voltage(struct regulator_dev *dev, + unsigned selector) +{ + struct tps_pmic *tps = rdev_get_drvdata(dev); + int dcdc = rdev_get_id(dev); + + if (dcdc < TPS6507X_DCDC_1 || dcdc > TPS6507X_DCDC_3) + return -EINVAL; + + if (selector >= tps->info[dcdc]->table_len) + return -EINVAL; + else + return tps->info[dcdc]->table[selector] * 1000; +} + +static int tps6507x_ldo_list_voltage(struct regulator_dev *dev, + unsigned selector) +{ + struct tps_pmic *tps = rdev_get_drvdata(dev); + int ldo = rdev_get_id(dev); + + if (ldo < TPS6507X_LDO_1 || ldo > TPS6507X_LDO_2) + return -EINVAL; + + if (selector >= tps->info[ldo]->table_len) + return -EINVAL; + else + return tps->info[ldo]->table[selector] * 1000; +} + +/* Operations permitted on VDCDCx */ +static struct regulator_ops tps6507x_dcdc_ops = { + .is_enabled = tps6507x_dcdc_is_enabled, + .enable = tps6507x_dcdc_enable, + .disable = tps6507x_dcdc_disable, + .get_voltage = tps6507x_dcdc_get_voltage, + .set_voltage = tps6507x_dcdc_set_voltage, + .list_voltage = tps6507x_dcdc_list_voltage, +}; + +/* Operations permitted on LDOx */ +static struct regulator_ops tps6507x_ldo_ops = { + .is_enabled = tps6507x_ldo_is_enabled, + .enable = tps6507x_ldo_enable, + .disable = tps6507x_ldo_disable, + .get_voltage = tps6507x_ldo_get_voltage, + .set_voltage = tps6507x_ldo_set_voltage, + .list_voltage = tps6507x_ldo_list_voltage, +}; + +static +int tps_6507x_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + static int desc_id; + const struct tps_info *info = (void *)id->driver_data; + struct regulator_init_data *init_data; + struct regulator_dev *rdev; + struct tps_pmic *tps; + int i; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + /** + * init_data points to array of regulator_init structures + * coming from the board-evm file. + */ + init_data = client->dev.platform_data; + + if (!init_data) + return -EIO; + + tps = kzalloc(sizeof(*tps), GFP_KERNEL); + if (!tps) + return -ENOMEM; + + mutex_init(&tps->io_lock); + + /* common for all regulators */ + tps->client = client; + + for (i = 0; i < TPS6507X_NUM_REGULATOR; i++, info++, init_data++) { + /* Register the regulators */ + tps->info[i] = info; + tps->desc[i].name = info->name; + tps->desc[i].id = desc_id++; + tps->desc[i].n_voltages = num_voltages[i]; + tps->desc[i].ops = (i > TPS6507X_DCDC_3 ? + &tps6507x_ldo_ops : &tps6507x_dcdc_ops); + tps->desc[i].type = REGULATOR_VOLTAGE; + tps->desc[i].owner = THIS_MODULE; + + rdev = regulator_register(&tps->desc[i], + &client->dev, init_data, tps); + if (IS_ERR(rdev)) { + dev_err(&client->dev, "failed to register %s\n", + id->name); + + /* Unregister */ + while (i) + regulator_unregister(tps->rdev[--i]); + + tps->client = NULL; + + /* clear the client data in i2c */ + i2c_set_clientdata(client, NULL); + + kfree(tps); + return PTR_ERR(rdev); + } + + /* Save regulator for cleanup */ + tps->rdev[i] = rdev; + } + + i2c_set_clientdata(client, tps); + + return 0; +} + +/** + * tps_6507x_remove - TPS6507x driver i2c remove handler + * @client: i2c driver client device structure + * + * Unregister TPS driver as an i2c client device driver + */ +static int __devexit tps_6507x_remove(struct i2c_client *client) +{ + struct tps_pmic *tps = i2c_get_clientdata(client); + int i; + + for (i = 0; i < TPS6507X_NUM_REGULATOR; i++) + regulator_unregister(tps->rdev[i]); + + tps->client = NULL; + + /* clear the client data in i2c */ + i2c_set_clientdata(client, NULL); + kfree(tps); + + return 0; +} + +static const struct tps_info tps6507x_regs[] = { + { + .name = "VDCDC1", + .min_uV = 725000, + .max_uV = 3300000, + .table_len = ARRAY_SIZE(VDCDCx_VSEL_table), + .table = VDCDCx_VSEL_table, + }, + { + .name = "VDCDC2", + .min_uV = 725000, + .max_uV = 3300000, + .table_len = ARRAY_SIZE(VDCDCx_VSEL_table), + .table = VDCDCx_VSEL_table, + }, + { + .name = "VDCDC3", + .min_uV = 725000, + .max_uV = 3300000, + .table_len = ARRAY_SIZE(VDCDCx_VSEL_table), + .table = VDCDCx_VSEL_table, + }, + { + .name = "LDO1", + .min_uV = 1000000, + .max_uV = 3300000, + .table_len = ARRAY_SIZE(LDO1_VSEL_table), + .table = LDO1_VSEL_table, + }, + { + .name = "LDO2", + .min_uV = 725000, + .max_uV = 3300000, + .table_len = ARRAY_SIZE(LDO2_VSEL_table), + .table = LDO2_VSEL_table, + }, +}; + +static const struct i2c_device_id tps_6507x_id = { + .name = "tps6507x", + .driver_data = (unsigned long) &tps6507x_regs[0], +}; +MODULE_DEVICE_TABLE(i2c, tps_6507x_id); + +static struct i2c_driver tps_6507x_i2c_driver = { + .driver = { + .name = "tps6507x", + .owner = THIS_MODULE, + }, + .probe = tps_6507x_probe, + .remove = __devexit_p(tps_6507x_remove), + .id_table = &tps_6507x_id, +}; + +/** + * tps_6507x_init + * + * Module init function + */ +static int __init tps_6507x_init(void) +{ + return i2c_add_driver(&tps_6507x_i2c_driver); +} +subsys_initcall(tps_6507x_init); + +/** + * tps_6507x_cleanup + * + * Module exit function + */ +static void __exit tps_6507x_cleanup(void) +{ + i2c_del_driver(&tps_6507x_i2c_driver); +} +module_exit(tps_6507x_cleanup); + +MODULE_AUTHOR("Texas Instruments"); +MODULE_DESCRIPTION("TPS6507x voltage regulator driver"); +MODULE_LICENSE("GPLv2"); -- cgit v1.2.3 From 2de798506d7300830a102e18d3d299f740475bc8 Mon Sep 17 00:00:00 2001 From: Anuj Aggarwal Date: Fri, 21 Aug 2009 00:39:44 +0530 Subject: Regulator: Adding TPS65023 and TPS6507x in Kconfig and Makefile Adding TPS65023 and TPS6507x regulator driver support in drivers/regulator/Makefile and drivers/regulator/Kconfig Signed-off-by: Anuj Aggarwal Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- drivers/regulator/Kconfig | 16 ++++++++++++++++ drivers/regulator/Makefile | 3 +++ 2 files changed, 19 insertions(+) (limited to 'drivers/regulator') diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index 2dc42bbf6fe..29910e3a3e4 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -147,5 +147,21 @@ config REGULATOR_AB3100 AB3100 analog baseband dealing with power regulators for the system. +config REGULATOR_TPS65023 + tristate "TI TPS65023 Power regulators" + depends on I2C + help + This driver supports TPS65023 voltage regulator chips. TPS65023 provides + three step-down converters and two general-purpose LDO voltage regulators. + It supports TI's software based Class-2 SmartReflex implementation. + +config REGULATOR_TPS6507X + tristate "TI TPS6507X Power regulators" + depends on I2C + help + This driver supports TPS6507X voltage regulator chips. TPS6507X provides + three step-down converters and two general-purpose LDO voltage regulators. + It supports TI's software based Class-2 SmartReflex implementation. + endif diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index 768b3316d6e..4257a868377 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -23,4 +23,7 @@ obj-$(CONFIG_REGULATOR_PCAP) += pcap-regulator.o obj-$(CONFIG_REGULATOR_MC13783) += mc13783.o obj-$(CONFIG_REGULATOR_AB3100) += ab3100.o +obj-$(CONFIG_REGULATOR_TPS65023) += tps65023-regulator.o +obj-$(CONFIG_REGULATOR_TPS6507X) += tps6507x-regulator.o + ccflags-$(CONFIG_REGULATOR_DEBUG) += -DDEBUG -- cgit v1.2.3 From 9e108d33edcb88bac3db39ba1683fc2c0591d7d4 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Mon, 24 Aug 2009 10:31:34 +0100 Subject: regulator: tps650xx - build fixes for x86_64 Fixes the following errors on both tps650xx regulator drivers :- drivers/regulator/tps65023-regulator: struct i2c_device_id is 32 bytes. The last of 1 is: 0x74 0x70 0x73 0x36 0x35 0x30 0x32 0x33 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 FATAL: drivers/regulator/tps65023-regulator: struct i2c_device_id is not terminated with a NULL entry! This patch also fixes the GPL v2 licence string for both drivers. Signed-off-by: Liam Girdwood --- drivers/regulator/tps65023-regulator.c | 11 ++++++----- drivers/regulator/tps6507x-regulator.c | 11 ++++++----- 2 files changed, 12 insertions(+), 10 deletions(-) (limited to 'drivers/regulator') diff --git a/drivers/regulator/tps65023-regulator.c b/drivers/regulator/tps65023-regulator.c index 1e54f46af67..07fda0a75ad 100644 --- a/drivers/regulator/tps65023-regulator.c +++ b/drivers/regulator/tps65023-regulator.c @@ -587,9 +587,10 @@ static const struct tps_info tps65023_regs[] = { }, }; -static const struct i2c_device_id tps_65023_id = { - .name = "tps65023", - .driver_data = (unsigned long) &tps65023_regs[0], +static const struct i2c_device_id tps_65023_id[] = { + {.name = "tps65023", + .driver_data = (unsigned long) tps65023_regs,}, + { }, }; MODULE_DEVICE_TABLE(i2c, tps_65023_id); @@ -601,7 +602,7 @@ static struct i2c_driver tps_65023_i2c_driver = { }, .probe = tps_65023_probe, .remove = __devexit_p(tps_65023_remove), - .id_table = &tps_65023_id, + .id_table = tps_65023_id, }; /** @@ -628,4 +629,4 @@ module_exit(tps_65023_cleanup); MODULE_AUTHOR("Texas Instruments"); MODULE_DESCRIPTION("TPS65023 voltage regulator driver"); -MODULE_LICENSE("GPLv2"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/regulator/tps6507x-regulator.c b/drivers/regulator/tps6507x-regulator.c index 1aa36369512..f8a6dfbef75 100644 --- a/drivers/regulator/tps6507x-regulator.c +++ b/drivers/regulator/tps6507x-regulator.c @@ -670,9 +670,10 @@ static const struct tps_info tps6507x_regs[] = { }, }; -static const struct i2c_device_id tps_6507x_id = { - .name = "tps6507x", - .driver_data = (unsigned long) &tps6507x_regs[0], +static const struct i2c_device_id tps_6507x_id[] = { + {.name = "tps6507x", + .driver_data = (unsigned long) tps6507x_regs,}, + { }, }; MODULE_DEVICE_TABLE(i2c, tps_6507x_id); @@ -683,7 +684,7 @@ static struct i2c_driver tps_6507x_i2c_driver = { }, .probe = tps_6507x_probe, .remove = __devexit_p(tps_6507x_remove), - .id_table = &tps_6507x_id, + .id_table = tps_6507x_id, }; /** @@ -710,4 +711,4 @@ module_exit(tps_6507x_cleanup); MODULE_AUTHOR("Texas Instruments"); MODULE_DESCRIPTION("TPS6507x voltage regulator driver"); -MODULE_LICENSE("GPLv2"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From a7433cff9ed8e7982de8e0f210f0325d0f3d1949 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 26 Aug 2009 12:54:04 +0200 Subject: REGULATOR Handle positive returncode from enable This makes _regulator_enable() properly handle the case where a regulator is already on when you try to enable it. Currently it will erroneously handle positive return values as an error. Signed-off-by: Linus Walleij Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- drivers/regulator/core.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers/regulator') diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index dbf27bf028c..744ea1d0b59 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -1236,11 +1236,12 @@ static int _regulator_enable(struct regulator_dev *rdev) } else { return -EINVAL; } - } else { + } else if (ret < 0) { printk(KERN_ERR "%s: is_enabled() failed for %s: %d\n", __func__, rdev->desc->name, ret); return ret; } + /* Fallthrough on positive return values - already enabled */ } rdev->use_count++; -- cgit v1.2.3 From 6f2653e63a4aedf877efbbcdbd4cea7db088bf29 Mon Sep 17 00:00:00 2001 From: Michael Prokop Date: Sat, 5 Sep 2009 02:59:14 +0200 Subject: drivers/regulator/Kconfig: fix typo (s/Usersapce/Userspace/) in REGULATOR_USERSPACE_CONSUMER description Signed-off-by: Michael Prokop Signed-off-by: Liam Girdwood --- drivers/regulator/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/regulator') diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index 29910e3a3e4..2b52a4ffcd3 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -52,7 +52,7 @@ config REGULATOR_USERSPACE_CONSUMER default n help There are some classes of devices that are controlled entirely - from user space. Usersapce consumer driver provides ability to + from user space. Userspace consumer driver provides ability to control power supplies for such devices. If unsure, say no. -- cgit v1.2.3 From 656d0498ea14c51cd8ec00081b5e0662acc72614 Mon Sep 17 00:00:00 2001 From: Roel Kluin Date: Fri, 18 Sep 2009 12:56:20 -0700 Subject: regulator: fix calculation of voltage range in da9034_set_ldo12_voltage() For val to be greater than 7 or less than 20 is logically always true. Signed-off-by: Roel Kluin Cc: Liam Girdwood Cc: Mark Brown Signed-off-by: Andrew Morton Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- drivers/regulator/da903x.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/regulator') diff --git a/drivers/regulator/da903x.c b/drivers/regulator/da903x.c index 7d9c2506d21..c9de7309847 100644 --- a/drivers/regulator/da903x.c +++ b/drivers/regulator/da903x.c @@ -301,7 +301,7 @@ static int da9034_set_ldo12_voltage(struct regulator_dev *rdev, } val = (min_uV - info->min_uV + info->step_uV - 1) / info->step_uV; - val = (val > 7 || val < 20) ? 8 : val - 12; + val = (val > 7 && val < 20) ? 8 : val - 12; val <<= info->vol_shift; mask = ((1 << info->vol_nbits) - 1) << info->vol_shift; -- cgit v1.2.3 From a954c487b95e7061d9546f6897edbc20a73454d3 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Fri, 18 Sep 2009 22:44:44 +0200 Subject: regulator: drop 'default n' Specifying 'default n' is superfluous. Signed-off-by: Wolfram Sang Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- drivers/regulator/Kconfig | 6 ------ 1 file changed, 6 deletions(-) (limited to 'drivers/regulator') diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index 2b52a4ffcd3..bcbb161bde0 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -1,6 +1,5 @@ menuconfig REGULATOR bool "Voltage and Current Regulator Support" - default n help Generic Voltage and Current Regulator support. @@ -30,7 +29,6 @@ config REGULATOR_DEBUG config REGULATOR_FIXED_VOLTAGE tristate "Fixed voltage regulator support" - default n help This driver provides support for fixed voltage regulators, useful for systems which use a combination of software @@ -38,7 +36,6 @@ config REGULATOR_FIXED_VOLTAGE config REGULATOR_VIRTUAL_CONSUMER tristate "Virtual regulator consumer support" - default n help This driver provides a virtual consumer for the voltage and current regulator API which provides sysfs controls for @@ -49,7 +46,6 @@ config REGULATOR_VIRTUAL_CONSUMER config REGULATOR_USERSPACE_CONSUMER tristate "Userspace regulator consumer support" - default n help There are some classes of devices that are controlled entirely from user space. Userspace consumer driver provides ability to @@ -59,7 +55,6 @@ config REGULATOR_USERSPACE_CONSUMER config REGULATOR_BQ24022 tristate "TI bq24022 Dual Input 1-Cell Li-Ion Charger IC" - default n help This driver controls a TI bq24022 Charger attached via GPIOs. The provided current regulator can enable/disable @@ -69,7 +64,6 @@ config REGULATOR_BQ24022 config REGULATOR_MAX1586 tristate "Maxim 1586/1587 voltage regulator" depends on I2C - default n help This driver controls a Maxim 1586 or 1587 voltage output regulator via I2C bus. The provided regulator is suitable -- cgit v1.2.3 From 12a1d933a99e1a2901575390dceea3819f2a575a Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Fri, 18 Sep 2009 22:44:45 +0200 Subject: regulator/lp3971: drop unnecessary initialization Signed-off-by: Wolfram Sang Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- drivers/regulator/lp3971.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/regulator') diff --git a/drivers/regulator/lp3971.c b/drivers/regulator/lp3971.c index a61018a2769..7803a320543 100644 --- a/drivers/regulator/lp3971.c +++ b/drivers/regulator/lp3971.c @@ -541,7 +541,7 @@ static struct i2c_driver lp3971_i2c_driver = { static int __init lp3971_module_init(void) { - int ret = -ENODEV; + int ret; ret = i2c_add_driver(&lp3971_i2c_driver); if (ret != 0) -- cgit v1.2.3 From 55c1d7c60d9b269551cd7cc31e6be8323e1d94ec Mon Sep 17 00:00:00 2001 From: Haojian Zhuang Date: Mon, 21 Sep 2009 12:14:12 -0400 Subject: regulator: fix voltage range in da9034 ldo12 Signed-off-by: Roel Kluin Signed-off-by: Haojian Zhuang Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- drivers/regulator/da903x.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/regulator') diff --git a/drivers/regulator/da903x.c b/drivers/regulator/da903x.c index c9de7309847..aa224d936e0 100644 --- a/drivers/regulator/da903x.c +++ b/drivers/regulator/da903x.c @@ -301,7 +301,7 @@ static int da9034_set_ldo12_voltage(struct regulator_dev *rdev, } val = (min_uV - info->min_uV + info->step_uV - 1) / info->step_uV; - val = (val > 7 && val < 20) ? 8 : val - 12; + val = (val >= 20) ? val - 12 : ((val > 7) ? 8 : val); val <<= info->vol_shift; mask = ((1 << info->vol_nbits) - 1) << info->vol_shift; -- cgit v1.2.3