| Sonic Zhang | 8b385d9 | 2010-06-04 11:46:04 +0800 | [diff] [blame] | 1 | /* | 
 | 2 |  * Voltage and current regulation for AD5398 and AD5821 | 
 | 3 |  * | 
 | 4 |  * Copyright 2010 Analog Devices Inc. | 
 | 5 |  * | 
 | 6 |  * Enter bugs at http://blackfin.uclinux.org/ | 
 | 7 |  * | 
 | 8 |  * Licensed under the GPL-2 or later. | 
 | 9 |  */ | 
 | 10 |  | 
 | 11 | #include <linux/module.h> | 
 | 12 | #include <linux/err.h> | 
 | 13 | #include <linux/i2c.h> | 
 | 14 | #include <linux/slab.h> | 
 | 15 | #include <linux/platform_device.h> | 
 | 16 | #include <linux/regulator/driver.h> | 
 | 17 | #include <linux/regulator/machine.h> | 
 | 18 |  | 
 | 19 | #define AD5398_CURRENT_EN_MASK	0x8000 | 
 | 20 |  | 
 | 21 | struct ad5398_chip_info { | 
 | 22 | 	struct i2c_client *client; | 
 | 23 | 	int min_uA; | 
 | 24 | 	int max_uA; | 
 | 25 | 	unsigned int current_level; | 
 | 26 | 	unsigned int current_mask; | 
 | 27 | 	unsigned int current_offset; | 
| Axel Lin | 58d463e | 2010-09-01 10:29:18 +0800 | [diff] [blame] | 28 | 	struct regulator_dev *rdev; | 
| Sonic Zhang | 8b385d9 | 2010-06-04 11:46:04 +0800 | [diff] [blame] | 29 | }; | 
 | 30 |  | 
 | 31 | static int ad5398_calc_current(struct ad5398_chip_info *chip, | 
 | 32 | 	unsigned selector) | 
 | 33 | { | 
 | 34 | 	unsigned range_uA = chip->max_uA - chip->min_uA; | 
 | 35 |  | 
 | 36 | 	return chip->min_uA + (selector * range_uA / chip->current_level); | 
 | 37 | } | 
 | 38 |  | 
 | 39 | static int ad5398_read_reg(struct i2c_client *client, unsigned short *data) | 
 | 40 | { | 
 | 41 | 	unsigned short val; | 
 | 42 | 	int ret; | 
 | 43 |  | 
 | 44 | 	ret = i2c_master_recv(client, (char *)&val, 2); | 
 | 45 | 	if (ret < 0) { | 
 | 46 | 		dev_err(&client->dev, "I2C read error\n"); | 
 | 47 | 		return ret; | 
 | 48 | 	} | 
 | 49 | 	*data = be16_to_cpu(val); | 
 | 50 |  | 
 | 51 | 	return ret; | 
 | 52 | } | 
 | 53 |  | 
 | 54 | static int ad5398_write_reg(struct i2c_client *client, const unsigned short data) | 
 | 55 | { | 
 | 56 | 	unsigned short val; | 
 | 57 | 	int ret; | 
 | 58 |  | 
 | 59 | 	val = cpu_to_be16(data); | 
 | 60 | 	ret = i2c_master_send(client, (char *)&val, 2); | 
 | 61 | 	if (ret < 0) | 
 | 62 | 		dev_err(&client->dev, "I2C write error\n"); | 
 | 63 |  | 
 | 64 | 	return ret; | 
 | 65 | } | 
 | 66 |  | 
 | 67 | static int ad5398_get_current_limit(struct regulator_dev *rdev) | 
 | 68 | { | 
 | 69 | 	struct ad5398_chip_info *chip = rdev_get_drvdata(rdev); | 
 | 70 | 	struct i2c_client *client = chip->client; | 
 | 71 | 	unsigned short data; | 
 | 72 | 	int ret; | 
 | 73 |  | 
 | 74 | 	ret = ad5398_read_reg(client, &data); | 
 | 75 | 	if (ret < 0) | 
 | 76 | 		return ret; | 
 | 77 |  | 
 | 78 | 	ret = (data & chip->current_mask) >> chip->current_offset; | 
 | 79 |  | 
 | 80 | 	return ad5398_calc_current(chip, ret); | 
 | 81 | } | 
 | 82 |  | 
 | 83 | static int ad5398_set_current_limit(struct regulator_dev *rdev, int min_uA, int max_uA) | 
 | 84 | { | 
 | 85 | 	struct ad5398_chip_info *chip = rdev_get_drvdata(rdev); | 
 | 86 | 	struct i2c_client *client = chip->client; | 
 | 87 | 	unsigned range_uA = chip->max_uA - chip->min_uA; | 
 | 88 | 	unsigned selector; | 
 | 89 | 	unsigned short data; | 
 | 90 | 	int ret; | 
 | 91 |  | 
 | 92 | 	if (min_uA > chip->max_uA || min_uA < chip->min_uA) | 
 | 93 | 		return -EINVAL; | 
 | 94 | 	if (max_uA > chip->max_uA || max_uA < chip->min_uA) | 
 | 95 | 		return -EINVAL; | 
 | 96 |  | 
 | 97 | 	selector = ((min_uA - chip->min_uA) * chip->current_level + | 
 | 98 | 			range_uA - 1) / range_uA; | 
 | 99 | 	if (ad5398_calc_current(chip, selector) > max_uA) | 
 | 100 | 		return -EINVAL; | 
 | 101 |  | 
 | 102 | 	dev_dbg(&client->dev, "changing current %dmA\n", | 
 | 103 | 		ad5398_calc_current(chip, selector) / 1000); | 
 | 104 |  | 
 | 105 | 	/* read chip enable bit */ | 
 | 106 | 	ret = ad5398_read_reg(client, &data); | 
 | 107 | 	if (ret < 0) | 
 | 108 | 		return ret; | 
 | 109 |  | 
 | 110 | 	/* prepare register data */ | 
 | 111 | 	selector = (selector << chip->current_offset) & chip->current_mask; | 
 | 112 | 	data = (unsigned short)selector | (data & AD5398_CURRENT_EN_MASK); | 
 | 113 |  | 
 | 114 | 	/* write the new current value back as well as enable bit */ | 
 | 115 | 	ret = ad5398_write_reg(client, data); | 
 | 116 |  | 
 | 117 | 	return ret; | 
 | 118 | } | 
 | 119 |  | 
 | 120 | static int ad5398_is_enabled(struct regulator_dev *rdev) | 
 | 121 | { | 
 | 122 | 	struct ad5398_chip_info *chip = rdev_get_drvdata(rdev); | 
 | 123 | 	struct i2c_client *client = chip->client; | 
 | 124 | 	unsigned short data; | 
 | 125 | 	int ret; | 
 | 126 |  | 
 | 127 | 	ret = ad5398_read_reg(client, &data); | 
 | 128 | 	if (ret < 0) | 
 | 129 | 		return ret; | 
 | 130 |  | 
 | 131 | 	if (data & AD5398_CURRENT_EN_MASK) | 
 | 132 | 		return 1; | 
 | 133 | 	else | 
 | 134 | 		return 0; | 
 | 135 | } | 
 | 136 |  | 
 | 137 | static int ad5398_enable(struct regulator_dev *rdev) | 
 | 138 | { | 
 | 139 | 	struct ad5398_chip_info *chip = rdev_get_drvdata(rdev); | 
 | 140 | 	struct i2c_client *client = chip->client; | 
 | 141 | 	unsigned short data; | 
 | 142 | 	int ret; | 
 | 143 |  | 
 | 144 | 	ret = ad5398_read_reg(client, &data); | 
 | 145 | 	if (ret < 0) | 
 | 146 | 		return ret; | 
 | 147 |  | 
 | 148 | 	if (data & AD5398_CURRENT_EN_MASK) | 
 | 149 | 		return 0; | 
 | 150 |  | 
 | 151 | 	data |= AD5398_CURRENT_EN_MASK; | 
 | 152 |  | 
 | 153 | 	ret = ad5398_write_reg(client, data); | 
 | 154 |  | 
 | 155 | 	return ret; | 
 | 156 | } | 
 | 157 |  | 
 | 158 | static int ad5398_disable(struct regulator_dev *rdev) | 
 | 159 | { | 
 | 160 | 	struct ad5398_chip_info *chip = rdev_get_drvdata(rdev); | 
 | 161 | 	struct i2c_client *client = chip->client; | 
 | 162 | 	unsigned short data; | 
 | 163 | 	int ret; | 
 | 164 |  | 
 | 165 | 	ret = ad5398_read_reg(client, &data); | 
 | 166 | 	if (ret < 0) | 
 | 167 | 		return ret; | 
 | 168 |  | 
 | 169 | 	if (!(data & AD5398_CURRENT_EN_MASK)) | 
 | 170 | 		return 0; | 
 | 171 |  | 
 | 172 | 	data &= ~AD5398_CURRENT_EN_MASK; | 
 | 173 |  | 
 | 174 | 	ret = ad5398_write_reg(client, data); | 
 | 175 |  | 
 | 176 | 	return ret; | 
 | 177 | } | 
 | 178 |  | 
 | 179 | static struct regulator_ops ad5398_ops = { | 
 | 180 | 	.get_current_limit = ad5398_get_current_limit, | 
 | 181 | 	.set_current_limit = ad5398_set_current_limit, | 
 | 182 | 	.enable = ad5398_enable, | 
 | 183 | 	.disable = ad5398_disable, | 
 | 184 | 	.is_enabled = ad5398_is_enabled, | 
 | 185 | }; | 
 | 186 |  | 
 | 187 | static struct regulator_desc ad5398_reg = { | 
 | 188 | 	.name = "isink", | 
 | 189 | 	.id = 0, | 
 | 190 | 	.ops = &ad5398_ops, | 
 | 191 | 	.type = REGULATOR_CURRENT, | 
 | 192 | 	.owner = THIS_MODULE, | 
 | 193 | }; | 
 | 194 |  | 
 | 195 | struct ad5398_current_data_format { | 
 | 196 | 	int current_bits; | 
 | 197 | 	int current_offset; | 
 | 198 | 	int min_uA; | 
 | 199 | 	int max_uA; | 
 | 200 | }; | 
 | 201 |  | 
 | 202 | static const struct ad5398_current_data_format df_10_4_120 = {10, 4, 0, 120000}; | 
 | 203 |  | 
 | 204 | static const struct i2c_device_id ad5398_id[] = { | 
 | 205 | 	{ "ad5398", (kernel_ulong_t)&df_10_4_120 }, | 
 | 206 | 	{ "ad5821", (kernel_ulong_t)&df_10_4_120 }, | 
 | 207 | 	{ } | 
 | 208 | }; | 
 | 209 | MODULE_DEVICE_TABLE(i2c, ad5398_id); | 
 | 210 |  | 
 | 211 | static int __devinit ad5398_probe(struct i2c_client *client, | 
 | 212 | 				const struct i2c_device_id *id) | 
 | 213 | { | 
| Sonic Zhang | 8b385d9 | 2010-06-04 11:46:04 +0800 | [diff] [blame] | 214 | 	struct regulator_init_data *init_data = client->dev.platform_data; | 
 | 215 | 	struct ad5398_chip_info *chip; | 
 | 216 | 	const struct ad5398_current_data_format *df = | 
 | 217 | 			(struct ad5398_current_data_format *)id->driver_data; | 
 | 218 | 	int ret; | 
 | 219 |  | 
 | 220 | 	if (!init_data) | 
 | 221 | 		return -EINVAL; | 
 | 222 |  | 
 | 223 | 	chip = kzalloc(sizeof(*chip), GFP_KERNEL); | 
 | 224 | 	if (!chip) | 
 | 225 | 		return -ENOMEM; | 
 | 226 |  | 
 | 227 | 	chip->client = client; | 
 | 228 |  | 
 | 229 | 	chip->min_uA = df->min_uA; | 
 | 230 | 	chip->max_uA = df->max_uA; | 
 | 231 | 	chip->current_level = 1 << df->current_bits; | 
 | 232 | 	chip->current_offset = df->current_offset; | 
 | 233 | 	chip->current_mask = (chip->current_level - 1) << chip->current_offset; | 
 | 234 |  | 
| Axel Lin | 58d463e | 2010-09-01 10:29:18 +0800 | [diff] [blame] | 235 | 	chip->rdev = regulator_register(&ad5398_reg, &client->dev, | 
| Rajendra Nayak | 2c043bc | 2011-11-18 16:47:19 +0530 | [diff] [blame] | 236 | 					init_data, chip, NULL); | 
| Axel Lin | 58d463e | 2010-09-01 10:29:18 +0800 | [diff] [blame] | 237 | 	if (IS_ERR(chip->rdev)) { | 
 | 238 | 		ret = PTR_ERR(chip->rdev); | 
| Sonic Zhang | 8b385d9 | 2010-06-04 11:46:04 +0800 | [diff] [blame] | 239 | 		dev_err(&client->dev, "failed to register %s %s\n", | 
 | 240 | 			id->name, ad5398_reg.name); | 
 | 241 | 		goto err; | 
 | 242 | 	} | 
 | 243 |  | 
 | 244 | 	i2c_set_clientdata(client, chip); | 
 | 245 | 	dev_dbg(&client->dev, "%s regulator driver is registered.\n", id->name); | 
 | 246 | 	return 0; | 
 | 247 |  | 
 | 248 | err: | 
 | 249 | 	kfree(chip); | 
 | 250 | 	return ret; | 
 | 251 | } | 
 | 252 |  | 
 | 253 | static int __devexit ad5398_remove(struct i2c_client *client) | 
 | 254 | { | 
 | 255 | 	struct ad5398_chip_info *chip = i2c_get_clientdata(client); | 
 | 256 |  | 
| Axel Lin | 58d463e | 2010-09-01 10:29:18 +0800 | [diff] [blame] | 257 | 	regulator_unregister(chip->rdev); | 
| Sonic Zhang | 8b385d9 | 2010-06-04 11:46:04 +0800 | [diff] [blame] | 258 | 	kfree(chip); | 
| Sonic Zhang | 8b385d9 | 2010-06-04 11:46:04 +0800 | [diff] [blame] | 259 |  | 
 | 260 | 	return 0; | 
 | 261 | } | 
 | 262 |  | 
 | 263 | static struct i2c_driver ad5398_driver = { | 
 | 264 | 	.probe = ad5398_probe, | 
 | 265 | 	.remove = __devexit_p(ad5398_remove), | 
 | 266 | 	.driver		= { | 
 | 267 | 		.name	= "ad5398", | 
 | 268 | 	}, | 
 | 269 | 	.id_table	= ad5398_id, | 
 | 270 | }; | 
 | 271 |  | 
 | 272 | static int __init ad5398_init(void) | 
 | 273 | { | 
 | 274 | 	return i2c_add_driver(&ad5398_driver); | 
 | 275 | } | 
| Sonic Zhang | 839b836 | 2010-06-10 16:50:20 +0800 | [diff] [blame] | 276 | subsys_initcall(ad5398_init); | 
| Sonic Zhang | 8b385d9 | 2010-06-04 11:46:04 +0800 | [diff] [blame] | 277 |  | 
 | 278 | static void __exit ad5398_exit(void) | 
 | 279 | { | 
 | 280 | 	i2c_del_driver(&ad5398_driver); | 
 | 281 | } | 
 | 282 | module_exit(ad5398_exit); | 
 | 283 |  | 
 | 284 | MODULE_DESCRIPTION("AD5398 and AD5821 current regulator driver"); | 
 | 285 | MODULE_AUTHOR("Sonic Zhang"); | 
 | 286 | MODULE_LICENSE("GPL"); | 
 | 287 | MODULE_ALIAS("i2c:ad5398-regulator"); |