diff options
Diffstat (limited to 'drivers/regulator/cpcap-regulator.c')
| -rw-r--r-- | drivers/regulator/cpcap-regulator.c | 605 | 
1 files changed, 605 insertions, 0 deletions
| diff --git a/drivers/regulator/cpcap-regulator.c b/drivers/regulator/cpcap-regulator.c new file mode 100644 index 00000000000..1bed6fb32ac --- /dev/null +++ b/drivers/regulator/cpcap-regulator.c @@ -0,0 +1,605 @@ +/* + * Copyright (C) 2009-2011 Motorola, Inc. + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307, USA + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/delay.h> + +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> + +#include <linux/spi/spi.h> +#include <linux/spi/cpcap.h> +#include <linux/spi/cpcap-regbits.h> + +#define CPCAP_REGULATOR(_name, _id) 		\ +	{					\ +		.name = _name, 			\ +		.id = _id,			\ +		.ops = &cpcap_regulator_ops,	\ +		.type = REGULATOR_VOLTAGE, 	\ +		.owner = THIS_MODULE, 		\ +	} + + +static const int sw4_val_tbl[] = {600000, 612500, 625000, 637500, 650000, +				  662500, 675000, 687500, 700000, 712500, +				  725000, 737500, 750000, 762500, 775000, +				  787500, 800000, 812500, 825000, 837500, +				  850000, 862500, 885000, 887500, 900000, +				  912500, 925000, 937500, 950000, 962500, +				  975000, 987500, 1000000, 1012500, 1025000, +				  1037500, 1050000, 1062500, 1075000, 1087500, +				  1100000, 1112500, 1125000, 1137500, 1150000, +				  1162500, 1175000, 1187500, 1200000, 1212500, +				  1225000, 1237500, 1250000, 1262500, 1275000, +				  1287500, 1300000, 1312500, 1325000, 1337500, +				  1350000, 1362500, 1375000, 1387500, 1400000, +				  1412500, 1425000, 1437500, 1450000}; +static const int sw5_val_tbl[] = {0, 5050000}; +static const int vcam_val_tbl[] = {2600000, 2700000, 2800000, 2900000}; +static const int vcsi_val_tbl[] = {1200000, 1800000}; +static const int vdac_val_tbl[] = {1200000, 1500000, 1800000, 2500000}; +static const int vdig_val_tbl[] = {1200000, 1350000, 1500000, 1875000}; +static const int vfuse_val_tbl[] = {1500000, 1600000, 1700000, 1800000, 1900000, +				    2000000, 2100000, 2200000, 2300000, 2400000, +				    2500000, 2600000, 2700000, 3150000}; +static const int vhvio_val_tbl[] = {2775000}; +static const int vsdio_val_tbl[] = {1500000, 1600000, 1800000, 2600000, +				    2700000, 2800000, 2900000, 3000000}; +static const int vpll_val_tbl[] = {1200000, 1300000, 1400000, 1800000}; +static const int vrf1_val_tbl[] = {2775000, 2500000}; /* Yes, this is correct */ +static const int vrf2_val_tbl[] = {0, 2775000}; +static const int vrfref_val_tbl[] = {2500000, 2775000}; +static const int vwlan1_val_tbl[] = {1800000, 1900000}; +static const int vwlan2_val_tbl[] = {2775000, 3000000, 3300000, 3300000}; +static const int vsim_val_tbl[] = {1800000, 2900000}; +static const int vsimcard_val_tbl[] = {1800000, 2900000}; +static const int vvib_val_tbl[] = {1300000, 1800000, 2000000, 3000000}; +static const int vusb_val_tbl[] = {0, 3300000}; +static const int vaudio_val_tbl[] = {0, 2775000}; + +static struct { +	const enum cpcap_reg reg; +	const unsigned short mode_mask; +	const unsigned short volt_mask; +	const unsigned char volt_shft; +	unsigned short mode_val; +	unsigned short off_mode_val; +	const int val_tbl_sz; +	const int *val_tbl; +	unsigned int mode_cntr; +	const unsigned int volt_trans_time; /* in micro seconds */ +	const unsigned int turn_on_time; /* in micro seconds */ +} cpcap_regltr_data[CPCAP_NUM_REGULATORS] = { +	[CPCAP_SW4]      = {CPCAP_REG_S4C1, +			    0x6F00, +			    0x007F, +			    0, +			    0x0000, +			    0x0000, +			    ARRAY_SIZE(sw4_val_tbl), +			    sw4_val_tbl, +			    0, +			    100, +			    1500}, + +	[CPCAP_SW5]      = {CPCAP_REG_S5C, +			    0x002A, +			    0x0000, +			    0, +			    0x0000, +			    0x0000, +			    ARRAY_SIZE(sw5_val_tbl), +			    sw5_val_tbl, +			    0, +			    0, +			    1500}, + +	[CPCAP_VCAM]     = {CPCAP_REG_VCAMC, +			    0x0087, +			    0x0030, +			    4, +			    0x0000, +			    0x0000, +			    ARRAY_SIZE(vcam_val_tbl), +			    vcam_val_tbl, +			    0, +			    420, +			    1000}, + +	[CPCAP_VCSI]     = {CPCAP_REG_VCSIC, +			    0x0047, +			    0x0010, +			    4, +			    0x0000, +			    0x0000, +			    ARRAY_SIZE(vcsi_val_tbl), +			    vcsi_val_tbl, +			    0, +			    350, +			    1000}, + +	[CPCAP_VDAC]     = {CPCAP_REG_VDACC, +			    0x0087, +			    0x0030, +			    4, +			    0x0000, +			    0x0000, +			    ARRAY_SIZE(vdac_val_tbl), +			    vdac_val_tbl, +			    0, +			    420, +			    1000}, + +	[CPCAP_VDIG]     = {CPCAP_REG_VDIGC, +			    0x0087, +			    0x0030, +			    4, +			    0x0000, +			    0x0000, +			    ARRAY_SIZE(vdig_val_tbl), +			    vdig_val_tbl, +			    0, +			    420, +			    1000}, + +	[CPCAP_VFUSE]    = {CPCAP_REG_VFUSEC, +			    0x0080, +			    0x000F, +			    0, +			    0x0000, +			    0x0000, +			    ARRAY_SIZE(vfuse_val_tbl), +			    vfuse_val_tbl, +			    0, +			    420, +			    1000}, + +	[CPCAP_VHVIO]    = {CPCAP_REG_VHVIOC, +			    0x0017, +			    0x0000, +			    0, +			    0x0000, +			    0x0000, +			    ARRAY_SIZE(vhvio_val_tbl), +			    vhvio_val_tbl, +			    0, +			    0, +			    1000}, + +	[CPCAP_VSDIO]    = {CPCAP_REG_VSDIOC, +			    0x0087, +			    0x0038, +			    3, +			    0x0000, +			    0x0000, +			    ARRAY_SIZE(vsdio_val_tbl), +			    vsdio_val_tbl, +			    0, +			    420, +			    1000}, + +	[CPCAP_VPLL]     = {CPCAP_REG_VPLLC, +			    0x0043, +			    0x0018, +			    3, +			    0x0000, +			    0x0000, +			    ARRAY_SIZE(vpll_val_tbl), +			    vpll_val_tbl, +			    0, +			    420, +			    100}, + +	[CPCAP_VRF1]     = {CPCAP_REG_VRF1C, +			    0x00AC, +			    0x0002, +			    1, +			    0x0000, +			    0x0000, +			    ARRAY_SIZE(vrf1_val_tbl), +			    vrf1_val_tbl, +			    0, +			    10, +			    1000}, + +	[CPCAP_VRF2]     = {CPCAP_REG_VRF2C, +			    0x0023, +			    0x0008, +			    3, +			    0x0000, +			    0x0000, +			    ARRAY_SIZE(vrf2_val_tbl), +			    vrf2_val_tbl, +			    0, +			    10, +			    1000}, + +	[CPCAP_VRFREF]   = {CPCAP_REG_VRFREFC, +			    0x0023, +			    0x0008, +			    3, +			    0x0000, +			    0x0000, +			    ARRAY_SIZE(vrfref_val_tbl), +			    vrfref_val_tbl, +			    0, +			    420, +			    100}, + +	[CPCAP_VWLAN1]   = {CPCAP_REG_VWLAN1C, +			    0x0047, +			    0x0010, +			    4, +			    0x0000, +			    0x0000, +			    ARRAY_SIZE(vwlan1_val_tbl), +			    vwlan1_val_tbl, +			    0, +			    420, +			    1000}, + +	[CPCAP_VWLAN2]   = {CPCAP_REG_VWLAN2C, +			    0x020C, +			    0x00C0, +			    6, +			    0x0000, +			    0x0000, +			    ARRAY_SIZE(vwlan2_val_tbl), +			    vwlan2_val_tbl, +			    0, +			    420, +			    1000}, + +	[CPCAP_VSIM]     = {CPCAP_REG_VSIMC, +			    0x0023, +			    0x0008, +			    3, +			    0x0000, +			    0x0000, +			    ARRAY_SIZE(vsim_val_tbl), +			    vsim_val_tbl, +			    0, +			    420, +			    1000}, + +	[CPCAP_VSIMCARD] = {CPCAP_REG_VSIMC, +			    0x1E80, +			    0x0008, +			    3, +			    0x0000, +			    0x0000, +			    ARRAY_SIZE(vsimcard_val_tbl), +			    vsimcard_val_tbl, +			    0, +			    420, +			    1000}, + +	[CPCAP_VVIB]     = {CPCAP_REG_VVIBC, +			    0x0001, +			    0x000C, +			    2, +			    0x0000, +			    0x0000, +			    ARRAY_SIZE(vvib_val_tbl), +			    vvib_val_tbl, +			    0, +			    500, +			    500}, + +	[CPCAP_VUSB]     = {CPCAP_REG_VUSBC, +			    0x011C, +			    0x0040, +			    6, +			    0x0000, +			    0x0000, +			    ARRAY_SIZE(vusb_val_tbl), +			    vusb_val_tbl, +			    0, +			    0, +			    1000}, + +	[CPCAP_VAUDIO]   = {CPCAP_REG_VAUDIOC, +			    0x0016, +			    0x0001, +			    0, +			    0x0000, +			    0x0000, +			    ARRAY_SIZE(vaudio_val_tbl), +			    vaudio_val_tbl, +			    0, +			    0, +			    1000}, +}; + +static int cpcap_regulator_set_voltage(struct regulator_dev *rdev, int min_uV, +				       int max_uV, unsigned *selector) +{ +	struct cpcap_device *cpcap; +	int regltr_id; +	int retval; +	enum cpcap_reg regnr; +	int i; + +	cpcap = rdev_get_drvdata(rdev); + +	regltr_id = rdev_get_id(rdev); +	if (regltr_id >= CPCAP_NUM_REGULATORS) +		return -EINVAL; + +	regnr = cpcap_regltr_data[regltr_id].reg; + +	if (regltr_id == CPCAP_VRF1) { +		if (min_uV > 2500000) +			i = 0; +		else +			i = cpcap_regltr_data[regltr_id].volt_mask; +	} else { +		for (i = 0; i < cpcap_regltr_data[regltr_id].val_tbl_sz; i++) +			if (cpcap_regltr_data[regltr_id].val_tbl[i] >= min_uV) +				break; + +		if (i >= cpcap_regltr_data[regltr_id].val_tbl_sz) +			i--; + +		i <<= cpcap_regltr_data[regltr_id].volt_shft; +	} + +	retval = cpcap_regacc_write(cpcap, regnr, i, +				    cpcap_regltr_data[regltr_id].volt_mask); + +	if ((cpcap_regltr_data[regltr_id].volt_trans_time) && (retval == 0)) +		udelay(cpcap_regltr_data[regltr_id].volt_trans_time); + +	return retval; +} + +static int cpcap_regulator_get_voltage(struct regulator_dev *rdev) +{ +	struct cpcap_device *cpcap; +	int regltr_id; +	unsigned short volt_bits; +	enum cpcap_reg regnr; +	unsigned int shift; + +	cpcap = rdev_get_drvdata(rdev); + +	regltr_id = rdev_get_id(rdev); +	if (regltr_id >= CPCAP_NUM_REGULATORS) +		return -EINVAL; + +	regnr = cpcap_regltr_data[regltr_id].reg; + +	if (cpcap_regacc_read(cpcap, regnr, &volt_bits) < 0) +		return -1; + +	if (!(volt_bits & cpcap_regltr_data[regltr_id].mode_mask)) +		return 0; + +	volt_bits &= cpcap_regltr_data[regltr_id].volt_mask; +	shift = cpcap_regltr_data[regltr_id].volt_shft; + +	return cpcap_regltr_data[regltr_id].val_tbl[volt_bits >> shift]; +} + +static int cpcap_regulator_enable(struct regulator_dev *rdev) +{ +	struct cpcap_device *cpcap = rdev_get_drvdata(rdev); +	int regltr_id; +	int retval; +	enum cpcap_reg regnr; + +	regltr_id = rdev_get_id(rdev); +	if (regltr_id >= CPCAP_NUM_REGULATORS) +		return -EINVAL; + +	regnr = cpcap_regltr_data[regltr_id].reg; + +	retval = cpcap_regacc_write(cpcap, regnr, +				    cpcap_regltr_data[regltr_id].mode_val, +				    cpcap_regltr_data[regltr_id].mode_mask); + +	if ((cpcap_regltr_data[regltr_id].turn_on_time) && (retval == 0)) +		udelay(cpcap_regltr_data[regltr_id].turn_on_time); + +	return retval; +} + +static int cpcap_regulator_disable(struct regulator_dev *rdev) +{ +	struct cpcap_device *cpcap = rdev_get_drvdata(rdev); +	int regltr_id; +	enum cpcap_reg regnr; + +	regltr_id = rdev_get_id(rdev); +	if (regltr_id >= CPCAP_NUM_REGULATORS) +		return -EINVAL; + +	regnr = cpcap_regltr_data[regltr_id].reg; + +	return cpcap_regacc_write(cpcap, regnr, +				  cpcap_regltr_data[regltr_id].off_mode_val, +				  cpcap_regltr_data[regltr_id].mode_mask); +} + +static int cpcap_regulator_is_enabled(struct regulator_dev *rdev) +{ +	struct cpcap_device *cpcap = rdev_get_drvdata(rdev); +	int regltr_id; +	enum cpcap_reg regnr; +	unsigned short value; + +	regltr_id = rdev_get_id(rdev); +	if (regltr_id >= CPCAP_NUM_REGULATORS) +		return -EINVAL; + +	regnr = cpcap_regltr_data[regltr_id].reg; + +	if (cpcap_regacc_read(cpcap, regnr, &value)) +		return -1; + +	return ((value & cpcap_regltr_data[regltr_id].mode_mask) +		== cpcap_regltr_data[regltr_id].mode_val) ? 1 : 0; +} + +static int cpcap_regulator_set_mode(struct regulator_dev *rdev, +				    unsigned int mode) +{ +	struct cpcap_device *cpcap = rdev_get_drvdata(rdev); +	int regltr_id; +	enum cpcap_reg regnr; +	int ret = 0; + +	regltr_id = rdev_get_id(rdev); +	if (regltr_id != CPCAP_VAUDIO) +		return -EINVAL; + +	regnr = cpcap_regltr_data[regltr_id].reg; + +	if (mode == REGULATOR_MODE_NORMAL) { +		if (cpcap_regltr_data[regltr_id].mode_cntr == 0) { +			ret = cpcap_regacc_write(cpcap, regnr, +						 0, +						 CPCAP_BIT_AUDIO_LOW_PWR); +		} +		if (ret == 0) +			cpcap_regltr_data[regltr_id].mode_cntr++; +	} else if (mode == REGULATOR_MODE_STANDBY) { +		if (cpcap_regltr_data[regltr_id].mode_cntr == 1) { +			ret = cpcap_regacc_write(cpcap, regnr, +						 CPCAP_BIT_AUDIO_LOW_PWR, +						 CPCAP_BIT_AUDIO_LOW_PWR); +		} else if (WARN((cpcap_regltr_data[regltr_id].mode_cntr == 0), +				"Unbalanced modes for supply vaudio\n")) +			ret = -EIO; + +		if (ret == 0) +			cpcap_regltr_data[regltr_id].mode_cntr--; +	} + +	return ret; +} + +static struct regulator_ops cpcap_regulator_ops = { +	.set_voltage = cpcap_regulator_set_voltage, +	.get_voltage = cpcap_regulator_get_voltage, +	.enable = cpcap_regulator_enable, +	.disable = cpcap_regulator_disable, +	.is_enabled = cpcap_regulator_is_enabled, +	.set_mode = cpcap_regulator_set_mode, +}; + +static struct regulator_desc regulators[] = { +	[CPCAP_SW4]      = CPCAP_REGULATOR("sw4", CPCAP_SW4), +	[CPCAP_SW5]      = CPCAP_REGULATOR("sw5", CPCAP_SW5), +	[CPCAP_VCAM]     = CPCAP_REGULATOR("vcam", CPCAP_VCAM), +	[CPCAP_VCSI]     = CPCAP_REGULATOR("vcsi", CPCAP_VCSI), +	[CPCAP_VDAC]     = CPCAP_REGULATOR("vdac", CPCAP_VDAC), +	[CPCAP_VDIG]     = CPCAP_REGULATOR("vdig", CPCAP_VDIG), +	[CPCAP_VFUSE]    = CPCAP_REGULATOR("vfuse", CPCAP_VFUSE), +	[CPCAP_VHVIO]    = CPCAP_REGULATOR("vhvio", CPCAP_VHVIO), +	[CPCAP_VSDIO]    = CPCAP_REGULATOR("vsdio", CPCAP_VSDIO), +	[CPCAP_VPLL]     = CPCAP_REGULATOR("vpll", CPCAP_VPLL), +	[CPCAP_VRF1]     = CPCAP_REGULATOR("vrf1", CPCAP_VRF1), +	[CPCAP_VRF2]     = CPCAP_REGULATOR("vrf2", CPCAP_VRF2), +	[CPCAP_VRFREF]   = CPCAP_REGULATOR("vrfref", CPCAP_VRFREF), +	[CPCAP_VWLAN1]   = CPCAP_REGULATOR("vwlan1", CPCAP_VWLAN1), +	[CPCAP_VWLAN2]   = CPCAP_REGULATOR("vwlan2", CPCAP_VWLAN2), +	[CPCAP_VSIM]     = CPCAP_REGULATOR("vsim", CPCAP_VSIM), +	[CPCAP_VSIMCARD] = CPCAP_REGULATOR("vsimcard", CPCAP_VSIMCARD), +	[CPCAP_VVIB]     = CPCAP_REGULATOR("vvib", CPCAP_VVIB), +	[CPCAP_VUSB]     = CPCAP_REGULATOR("vusb", CPCAP_VUSB), +	[CPCAP_VAUDIO]   = CPCAP_REGULATOR("vaudio", CPCAP_VAUDIO), +}; + +static int cpcap_regulator_probe(struct platform_device *pdev) +{ +	struct regulator_dev *rdev; +	struct cpcap_device *cpcap; +	struct cpcap_platform_data *data; +	struct regulator_config config; +	int i; + +	/* Already set by core driver */ +	cpcap = platform_get_drvdata(pdev); +	data = cpcap->spi->controller_data; +	config.dev = &pdev->dev; +	config.init_data = pdev->dev.platform_data; +	config.driver_data = platform_get_drvdata(pdev); + +	for (i = 0; i < CPCAP_NUM_REGULATORS; i++) { +		cpcap_regltr_data[i].mode_val = data->regulator_mode_values[i]; +		cpcap_regltr_data[i].off_mode_val = +			data->regulator_off_mode_values[i]; +	} + +	rdev = regulator_register(®ulators[pdev->id], &config); +	if (IS_ERR(rdev)) +		return PTR_ERR(rdev); +	/* this is ok since the cpcap is still reachable from the rdev */ +	platform_set_drvdata(pdev, rdev); + +	if (pdev->id == CPCAP_SW5) { +		data->regulator_init = +			cpcap->regulator_pdev[CPCAP_VUSB]->dev.platform_data; +		data->regulator_init->supply_regulator = +			regulators[CPCAP_SW5].name; +		platform_device_add(cpcap->regulator_pdev[CPCAP_VUSB]); +	} + +	return 0; +} + +static int cpcap_regulator_remove(struct platform_device *pdev) +{ +	struct regulator_dev *rdev = platform_get_drvdata(pdev); + +	regulator_unregister(rdev); + +	return 0; +} + +static struct platform_driver cpcap_regulator_driver = { +	.driver = { +		.name = "cpcap-regltr", +	}, +	.probe = cpcap_regulator_probe, +	.remove = cpcap_regulator_remove, +}; + +static int __init cpcap_regulator_init(void) +{ +	return platform_driver_register(&cpcap_regulator_driver); +} +subsys_initcall(cpcap_regulator_init); + +static void __exit cpcap_regulator_exit(void) +{ +	platform_driver_unregister(&cpcap_regulator_driver); +} +module_exit(cpcap_regulator_exit); + +MODULE_ALIAS("platform:cpcap-regulator"); +MODULE_DESCRIPTION("CPCAP regulator driver"); +MODULE_AUTHOR("Motorola"); +MODULE_LICENSE("GPL"); |