| Woogyom Kim | 2165c8a | 2012-01-04 08:27:43 +0400 | [diff] [blame] | 1 | /* | 
| Kim, Milo | f7bae49 | 2012-01-26 22:59:08 -0800 | [diff] [blame] | 2 |  * Driver for LP8727 Micro/Mini USB IC with integrated charger | 
| Woogyom Kim | 2165c8a | 2012-01-04 08:27:43 +0400 | [diff] [blame] | 3 |  * | 
| Kim, Milo | e39b828 | 2012-01-29 17:28:18 -0800 | [diff] [blame] | 4 |  *			Copyright (C) 2011 Texas Instruments | 
| Woogyom Kim | 2165c8a | 2012-01-04 08:27:43 +0400 | [diff] [blame] | 5 |  *			Copyright (C) 2011 National Semiconductor | 
 | 6 |  * | 
 | 7 |  * This program is free software; you can redistribute it and/or modify | 
 | 8 |  * it under the terms of the GNU General Public License version 2 as | 
 | 9 |  * published by the Free Software Foundation. | 
 | 10 |  * | 
 | 11 |  */ | 
 | 12 |  | 
 | 13 | #include <linux/module.h> | 
 | 14 | #include <linux/slab.h> | 
 | 15 | #include <linux/interrupt.h> | 
 | 16 | #include <linux/i2c.h> | 
 | 17 | #include <linux/power_supply.h> | 
 | 18 | #include <linux/lp8727.h> | 
 | 19 |  | 
 | 20 | #define DEBOUNCE_MSEC	270 | 
 | 21 |  | 
 | 22 | /* Registers */ | 
 | 23 | #define CTRL1		0x1 | 
 | 24 | #define CTRL2		0x2 | 
 | 25 | #define	SWCTRL		0x3 | 
 | 26 | #define INT1		0x4 | 
 | 27 | #define INT2		0x5 | 
 | 28 | #define STATUS1		0x6 | 
| Kim, Milo | 7336880 | 2012-01-26 22:58:51 -0800 | [diff] [blame] | 29 | #define STATUS2		0x7 | 
| Woogyom Kim | 2165c8a | 2012-01-04 08:27:43 +0400 | [diff] [blame] | 30 | #define CHGCTRL2	0x9 | 
 | 31 |  | 
 | 32 | /* CTRL1 register */ | 
 | 33 | #define CP_EN		(1 << 0) | 
 | 34 | #define ADC_EN		(1 << 1) | 
 | 35 | #define ID200_EN	(1 << 4) | 
 | 36 |  | 
 | 37 | /* CTRL2 register */ | 
 | 38 | #define CHGDET_EN	(1 << 1) | 
 | 39 | #define INT_EN		(1 << 6) | 
 | 40 |  | 
 | 41 | /* SWCTRL register */ | 
 | 42 | #define SW_DM1_DM	(0x0 << 0) | 
 | 43 | #define SW_DM1_U1	(0x1 << 0) | 
 | 44 | #define SW_DM1_HiZ	(0x7 << 0) | 
 | 45 | #define SW_DP2_DP	(0x0 << 3) | 
 | 46 | #define SW_DP2_U2	(0x1 << 3) | 
 | 47 | #define SW_DP2_HiZ	(0x7 << 3) | 
 | 48 |  | 
 | 49 | /* INT1 register */ | 
 | 50 | #define IDNO		(0xF << 0) | 
 | 51 | #define VBUS		(1 << 4) | 
 | 52 |  | 
 | 53 | /* STATUS1 register */ | 
 | 54 | #define CHGSTAT		(3 << 4) | 
 | 55 | #define CHPORT		(1 << 6) | 
 | 56 | #define DCPORT		(1 << 7) | 
 | 57 |  | 
 | 58 | /* STATUS2 register */ | 
 | 59 | #define TEMP_STAT	(3 << 5) | 
 | 60 |  | 
 | 61 | enum lp8727_dev_id { | 
 | 62 | 	ID_NONE, | 
 | 63 | 	ID_TA, | 
 | 64 | 	ID_DEDICATED_CHG, | 
 | 65 | 	ID_USB_CHG, | 
 | 66 | 	ID_USB_DS, | 
 | 67 | 	ID_MAX, | 
 | 68 | }; | 
 | 69 |  | 
 | 70 | enum lp8727_chg_stat { | 
 | 71 | 	PRECHG, | 
 | 72 | 	CC, | 
 | 73 | 	CV, | 
 | 74 | 	EOC, | 
 | 75 | }; | 
 | 76 |  | 
 | 77 | struct lp8727_psy { | 
 | 78 | 	struct power_supply ac; | 
 | 79 | 	struct power_supply usb; | 
 | 80 | 	struct power_supply batt; | 
 | 81 | }; | 
 | 82 |  | 
 | 83 | struct lp8727_chg { | 
 | 84 | 	struct device *dev; | 
 | 85 | 	struct i2c_client *client; | 
 | 86 | 	struct mutex xfer_lock; | 
 | 87 | 	struct delayed_work work; | 
 | 88 | 	struct workqueue_struct *irqthread; | 
 | 89 | 	struct lp8727_platform_data *pdata; | 
 | 90 | 	struct lp8727_psy *psy; | 
 | 91 | 	struct lp8727_chg_param *chg_parm; | 
 | 92 | 	enum lp8727_dev_id devid; | 
 | 93 | }; | 
 | 94 |  | 
| Kim, Milo | 27aefa3 | 2012-01-26 22:58:39 -0800 | [diff] [blame] | 95 | static int lp8727_read_bytes(struct lp8727_chg *pchg, u8 reg, u8 *data, u8 len) | 
| Woogyom Kim | 2165c8a | 2012-01-04 08:27:43 +0400 | [diff] [blame] | 96 | { | 
 | 97 | 	s32 ret; | 
 | 98 |  | 
 | 99 | 	mutex_lock(&pchg->xfer_lock); | 
 | 100 | 	ret = i2c_smbus_read_i2c_block_data(pchg->client, reg, len, data); | 
 | 101 | 	mutex_unlock(&pchg->xfer_lock); | 
 | 102 |  | 
 | 103 | 	return (ret != len) ? -EIO : 0; | 
 | 104 | } | 
 | 105 |  | 
| Kim, Milo | 27aefa3 | 2012-01-26 22:58:39 -0800 | [diff] [blame] | 106 | static inline int lp8727_read_byte(struct lp8727_chg *pchg, u8 reg, u8 *data) | 
| Woogyom Kim | 2165c8a | 2012-01-04 08:27:43 +0400 | [diff] [blame] | 107 | { | 
| Kim, Milo | 27aefa3 | 2012-01-26 22:58:39 -0800 | [diff] [blame] | 108 | 	return lp8727_read_bytes(pchg, reg, data, 1); | 
 | 109 | } | 
 | 110 |  | 
 | 111 | static int lp8727_write_byte(struct lp8727_chg *pchg, u8 reg, u8 data) | 
 | 112 | { | 
 | 113 | 	int ret; | 
| Woogyom Kim | 2165c8a | 2012-01-04 08:27:43 +0400 | [diff] [blame] | 114 |  | 
 | 115 | 	mutex_lock(&pchg->xfer_lock); | 
| Kim, Milo | 27aefa3 | 2012-01-26 22:58:39 -0800 | [diff] [blame] | 116 | 	ret = i2c_smbus_write_byte_data(pchg->client, reg, data); | 
| Woogyom Kim | 2165c8a | 2012-01-04 08:27:43 +0400 | [diff] [blame] | 117 | 	mutex_unlock(&pchg->xfer_lock); | 
 | 118 |  | 
 | 119 | 	return ret; | 
 | 120 | } | 
 | 121 |  | 
| Woogyom Kim | 2165c8a | 2012-01-04 08:27:43 +0400 | [diff] [blame] | 122 | static int lp8727_is_charger_attached(const char *name, int id) | 
 | 123 | { | 
 | 124 | 	if (name) { | 
 | 125 | 		if (!strcmp(name, "ac")) | 
 | 126 | 			return (id == ID_TA || id == ID_DEDICATED_CHG) ? 1 : 0; | 
 | 127 | 		else if (!strcmp(name, "usb")) | 
 | 128 | 			return (id == ID_USB_CHG) ? 1 : 0; | 
 | 129 | 	} | 
 | 130 |  | 
 | 131 | 	return (id >= ID_TA && id <= ID_USB_CHG) ? 1 : 0; | 
 | 132 | } | 
 | 133 |  | 
| Kim, Milo | 7da6334 | 2012-01-26 22:58:30 -0800 | [diff] [blame] | 134 | static int lp8727_init_device(struct lp8727_chg *pchg) | 
| Woogyom Kim | 2165c8a | 2012-01-04 08:27:43 +0400 | [diff] [blame] | 135 | { | 
 | 136 | 	u8 val; | 
| Kim, Milo | 7da6334 | 2012-01-26 22:58:30 -0800 | [diff] [blame] | 137 | 	int ret; | 
| Woogyom Kim | 2165c8a | 2012-01-04 08:27:43 +0400 | [diff] [blame] | 138 |  | 
 | 139 | 	val = ID200_EN | ADC_EN | CP_EN; | 
| Kim, Milo | 27aefa3 | 2012-01-26 22:58:39 -0800 | [diff] [blame] | 140 | 	ret = lp8727_write_byte(pchg, CTRL1, val); | 
| Kim, Milo | 7da6334 | 2012-01-26 22:58:30 -0800 | [diff] [blame] | 141 | 	if (ret) | 
 | 142 | 		return ret; | 
| Woogyom Kim | 2165c8a | 2012-01-04 08:27:43 +0400 | [diff] [blame] | 143 |  | 
 | 144 | 	val = INT_EN | CHGDET_EN; | 
| Kim, Milo | 27aefa3 | 2012-01-26 22:58:39 -0800 | [diff] [blame] | 145 | 	ret = lp8727_write_byte(pchg, CTRL2, val); | 
| Kim, Milo | 7da6334 | 2012-01-26 22:58:30 -0800 | [diff] [blame] | 146 | 	if (ret) | 
 | 147 | 		return ret; | 
 | 148 |  | 
 | 149 | 	return 0; | 
| Woogyom Kim | 2165c8a | 2012-01-04 08:27:43 +0400 | [diff] [blame] | 150 | } | 
 | 151 |  | 
 | 152 | static int lp8727_is_dedicated_charger(struct lp8727_chg *pchg) | 
 | 153 | { | 
 | 154 | 	u8 val; | 
| Kim, Milo | 27aefa3 | 2012-01-26 22:58:39 -0800 | [diff] [blame] | 155 | 	lp8727_read_byte(pchg, STATUS1, &val); | 
| Kim, Milo | 7336880 | 2012-01-26 22:58:51 -0800 | [diff] [blame] | 156 | 	return val & DCPORT; | 
| Woogyom Kim | 2165c8a | 2012-01-04 08:27:43 +0400 | [diff] [blame] | 157 | } | 
 | 158 |  | 
 | 159 | static int lp8727_is_usb_charger(struct lp8727_chg *pchg) | 
 | 160 | { | 
 | 161 | 	u8 val; | 
| Kim, Milo | 27aefa3 | 2012-01-26 22:58:39 -0800 | [diff] [blame] | 162 | 	lp8727_read_byte(pchg, STATUS1, &val); | 
| Kim, Milo | 7336880 | 2012-01-26 22:58:51 -0800 | [diff] [blame] | 163 | 	return val & CHPORT; | 
| Woogyom Kim | 2165c8a | 2012-01-04 08:27:43 +0400 | [diff] [blame] | 164 | } | 
 | 165 |  | 
 | 166 | static void lp8727_ctrl_switch(struct lp8727_chg *pchg, u8 sw) | 
 | 167 | { | 
| Kim, Milo | 27aefa3 | 2012-01-26 22:58:39 -0800 | [diff] [blame] | 168 | 	lp8727_write_byte(pchg, SWCTRL, sw); | 
| Woogyom Kim | 2165c8a | 2012-01-04 08:27:43 +0400 | [diff] [blame] | 169 | } | 
 | 170 |  | 
 | 171 | static void lp8727_id_detection(struct lp8727_chg *pchg, u8 id, int vbusin) | 
 | 172 | { | 
 | 173 | 	u8 devid = ID_NONE; | 
 | 174 | 	u8 swctrl = SW_DM1_HiZ | SW_DP2_HiZ; | 
 | 175 |  | 
 | 176 | 	switch (id) { | 
 | 177 | 	case 0x5: | 
 | 178 | 		devid = ID_TA; | 
 | 179 | 		pchg->chg_parm = &pchg->pdata->ac; | 
 | 180 | 		break; | 
 | 181 | 	case 0xB: | 
 | 182 | 		if (lp8727_is_dedicated_charger(pchg)) { | 
 | 183 | 			pchg->chg_parm = &pchg->pdata->ac; | 
 | 184 | 			devid = ID_DEDICATED_CHG; | 
 | 185 | 		} else if (lp8727_is_usb_charger(pchg)) { | 
 | 186 | 			pchg->chg_parm = &pchg->pdata->usb; | 
 | 187 | 			devid = ID_USB_CHG; | 
 | 188 | 			swctrl = SW_DM1_DM | SW_DP2_DP; | 
 | 189 | 		} else if (vbusin) { | 
 | 190 | 			devid = ID_USB_DS; | 
 | 191 | 			swctrl = SW_DM1_DM | SW_DP2_DP; | 
 | 192 | 		} | 
 | 193 | 		break; | 
 | 194 | 	default: | 
 | 195 | 		devid = ID_NONE; | 
 | 196 | 		pchg->chg_parm = NULL; | 
 | 197 | 		break; | 
 | 198 | 	} | 
 | 199 |  | 
 | 200 | 	pchg->devid = devid; | 
 | 201 | 	lp8727_ctrl_switch(pchg, swctrl); | 
 | 202 | } | 
 | 203 |  | 
 | 204 | static void lp8727_enable_chgdet(struct lp8727_chg *pchg) | 
 | 205 | { | 
 | 206 | 	u8 val; | 
 | 207 |  | 
| Kim, Milo | 27aefa3 | 2012-01-26 22:58:39 -0800 | [diff] [blame] | 208 | 	lp8727_read_byte(pchg, CTRL2, &val); | 
| Woogyom Kim | 2165c8a | 2012-01-04 08:27:43 +0400 | [diff] [blame] | 209 | 	val |= CHGDET_EN; | 
| Kim, Milo | 27aefa3 | 2012-01-26 22:58:39 -0800 | [diff] [blame] | 210 | 	lp8727_write_byte(pchg, CTRL2, val); | 
| Woogyom Kim | 2165c8a | 2012-01-04 08:27:43 +0400 | [diff] [blame] | 211 | } | 
 | 212 |  | 
 | 213 | static void lp8727_delayed_func(struct work_struct *_work) | 
 | 214 | { | 
 | 215 | 	u8 intstat[2], idno, vbus; | 
 | 216 | 	struct lp8727_chg *pchg = | 
 | 217 | 	    container_of(_work, struct lp8727_chg, work.work); | 
 | 218 |  | 
| Kim, Milo | 27aefa3 | 2012-01-26 22:58:39 -0800 | [diff] [blame] | 219 | 	if (lp8727_read_bytes(pchg, INT1, intstat, 2)) { | 
| Woogyom Kim | 2165c8a | 2012-01-04 08:27:43 +0400 | [diff] [blame] | 220 | 		dev_err(pchg->dev, "can not read INT registers\n"); | 
 | 221 | 		return; | 
 | 222 | 	} | 
 | 223 |  | 
 | 224 | 	idno = intstat[0] & IDNO; | 
 | 225 | 	vbus = intstat[0] & VBUS; | 
 | 226 |  | 
 | 227 | 	lp8727_id_detection(pchg, idno, vbus); | 
 | 228 | 	lp8727_enable_chgdet(pchg); | 
 | 229 |  | 
 | 230 | 	power_supply_changed(&pchg->psy->ac); | 
 | 231 | 	power_supply_changed(&pchg->psy->usb); | 
 | 232 | 	power_supply_changed(&pchg->psy->batt); | 
 | 233 | } | 
 | 234 |  | 
 | 235 | static irqreturn_t lp8727_isr_func(int irq, void *ptr) | 
 | 236 | { | 
 | 237 | 	struct lp8727_chg *pchg = ptr; | 
 | 238 | 	unsigned long delay = msecs_to_jiffies(DEBOUNCE_MSEC); | 
 | 239 |  | 
 | 240 | 	queue_delayed_work(pchg->irqthread, &pchg->work, delay); | 
 | 241 |  | 
 | 242 | 	return IRQ_HANDLED; | 
 | 243 | } | 
 | 244 |  | 
| Kim, Milo | 7da6334 | 2012-01-26 22:58:30 -0800 | [diff] [blame] | 245 | static int lp8727_intr_config(struct lp8727_chg *pchg) | 
| Woogyom Kim | 2165c8a | 2012-01-04 08:27:43 +0400 | [diff] [blame] | 246 | { | 
 | 247 | 	INIT_DELAYED_WORK(&pchg->work, lp8727_delayed_func); | 
 | 248 |  | 
 | 249 | 	pchg->irqthread = create_singlethread_workqueue("lp8727-irqthd"); | 
| Kim, Milo | 7da6334 | 2012-01-26 22:58:30 -0800 | [diff] [blame] | 250 | 	if (!pchg->irqthread) { | 
| Woogyom Kim | 2165c8a | 2012-01-04 08:27:43 +0400 | [diff] [blame] | 251 | 		dev_err(pchg->dev, "can not create thread for lp8727\n"); | 
| Kim, Milo | 7da6334 | 2012-01-26 22:58:30 -0800 | [diff] [blame] | 252 | 		return -ENOMEM; | 
| Woogyom Kim | 2165c8a | 2012-01-04 08:27:43 +0400 | [diff] [blame] | 253 | 	} | 
| Kim, Milo | 7da6334 | 2012-01-26 22:58:30 -0800 | [diff] [blame] | 254 |  | 
 | 255 | 	return request_threaded_irq(pchg->client->irq, | 
 | 256 | 				NULL, | 
 | 257 | 				lp8727_isr_func, | 
 | 258 | 				IRQF_TRIGGER_FALLING, | 
 | 259 | 				"lp8727_irq", | 
 | 260 | 				pchg); | 
| Woogyom Kim | 2165c8a | 2012-01-04 08:27:43 +0400 | [diff] [blame] | 261 | } | 
 | 262 |  | 
 | 263 | static enum power_supply_property lp8727_charger_prop[] = { | 
 | 264 | 	POWER_SUPPLY_PROP_ONLINE, | 
 | 265 | }; | 
 | 266 |  | 
 | 267 | static enum power_supply_property lp8727_battery_prop[] = { | 
 | 268 | 	POWER_SUPPLY_PROP_STATUS, | 
 | 269 | 	POWER_SUPPLY_PROP_HEALTH, | 
 | 270 | 	POWER_SUPPLY_PROP_PRESENT, | 
 | 271 | 	POWER_SUPPLY_PROP_VOLTAGE_NOW, | 
 | 272 | 	POWER_SUPPLY_PROP_CAPACITY, | 
 | 273 | 	POWER_SUPPLY_PROP_TEMP, | 
 | 274 | }; | 
 | 275 |  | 
 | 276 | static char *battery_supplied_to[] = { | 
 | 277 | 	"main_batt", | 
 | 278 | }; | 
 | 279 |  | 
 | 280 | static int lp8727_charger_get_property(struct power_supply *psy, | 
 | 281 | 				       enum power_supply_property psp, | 
 | 282 | 				       union power_supply_propval *val) | 
 | 283 | { | 
 | 284 | 	struct lp8727_chg *pchg = dev_get_drvdata(psy->dev->parent); | 
 | 285 |  | 
| Kim, Milo | ce09aff | 2012-01-04 09:03:18 +0400 | [diff] [blame] | 286 | 	if (psp == POWER_SUPPLY_PROP_ONLINE) | 
| Woogyom Kim | 2165c8a | 2012-01-04 08:27:43 +0400 | [diff] [blame] | 287 | 		val->intval = lp8727_is_charger_attached(psy->name, | 
 | 288 | 							 pchg->devid); | 
| Woogyom Kim | 2165c8a | 2012-01-04 08:27:43 +0400 | [diff] [blame] | 289 |  | 
 | 290 | 	return 0; | 
 | 291 | } | 
 | 292 |  | 
 | 293 | static int lp8727_battery_get_property(struct power_supply *psy, | 
 | 294 | 				       enum power_supply_property psp, | 
 | 295 | 				       union power_supply_propval *val) | 
 | 296 | { | 
 | 297 | 	struct lp8727_chg *pchg = dev_get_drvdata(psy->dev->parent); | 
 | 298 | 	u8 read; | 
 | 299 |  | 
 | 300 | 	switch (psp) { | 
 | 301 | 	case POWER_SUPPLY_PROP_STATUS: | 
 | 302 | 		if (lp8727_is_charger_attached(psy->name, pchg->devid)) { | 
| Kim, Milo | 27aefa3 | 2012-01-26 22:58:39 -0800 | [diff] [blame] | 303 | 			lp8727_read_byte(pchg, STATUS1, &read); | 
| Woogyom Kim | 2165c8a | 2012-01-04 08:27:43 +0400 | [diff] [blame] | 304 | 			if (((read & CHGSTAT) >> 4) == EOC) | 
 | 305 | 				val->intval = POWER_SUPPLY_STATUS_FULL; | 
 | 306 | 			else | 
 | 307 | 				val->intval = POWER_SUPPLY_STATUS_CHARGING; | 
 | 308 | 		} else { | 
 | 309 | 			val->intval = POWER_SUPPLY_STATUS_DISCHARGING; | 
 | 310 | 		} | 
 | 311 | 		break; | 
 | 312 | 	case POWER_SUPPLY_PROP_HEALTH: | 
| Kim, Milo | 27aefa3 | 2012-01-26 22:58:39 -0800 | [diff] [blame] | 313 | 		lp8727_read_byte(pchg, STATUS2, &read); | 
| Woogyom Kim | 2165c8a | 2012-01-04 08:27:43 +0400 | [diff] [blame] | 314 | 		read = (read & TEMP_STAT) >> 5; | 
 | 315 | 		if (read >= 0x1 && read <= 0x3) | 
 | 316 | 			val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; | 
 | 317 | 		else | 
 | 318 | 			val->intval = POWER_SUPPLY_HEALTH_GOOD; | 
 | 319 | 		break; | 
 | 320 | 	case POWER_SUPPLY_PROP_PRESENT: | 
 | 321 | 		if (pchg->pdata->get_batt_present) | 
 | 322 | 			val->intval = pchg->pdata->get_batt_present(); | 
 | 323 | 		break; | 
 | 324 | 	case POWER_SUPPLY_PROP_VOLTAGE_NOW: | 
 | 325 | 		if (pchg->pdata->get_batt_level) | 
 | 326 | 			val->intval = pchg->pdata->get_batt_level(); | 
 | 327 | 		break; | 
 | 328 | 	case POWER_SUPPLY_PROP_CAPACITY: | 
 | 329 | 		if (pchg->pdata->get_batt_capacity) | 
 | 330 | 			val->intval = pchg->pdata->get_batt_capacity(); | 
 | 331 | 		break; | 
 | 332 | 	case POWER_SUPPLY_PROP_TEMP: | 
 | 333 | 		if (pchg->pdata->get_batt_temp) | 
 | 334 | 			val->intval = pchg->pdata->get_batt_temp(); | 
 | 335 | 		break; | 
 | 336 | 	default: | 
 | 337 | 		break; | 
 | 338 | 	} | 
 | 339 |  | 
 | 340 | 	return 0; | 
 | 341 | } | 
 | 342 |  | 
 | 343 | static void lp8727_charger_changed(struct power_supply *psy) | 
 | 344 | { | 
 | 345 | 	struct lp8727_chg *pchg = dev_get_drvdata(psy->dev->parent); | 
 | 346 | 	u8 val; | 
 | 347 | 	u8 eoc_level, ichg; | 
 | 348 |  | 
 | 349 | 	if (lp8727_is_charger_attached(psy->name, pchg->devid)) { | 
 | 350 | 		if (pchg->chg_parm) { | 
 | 351 | 			eoc_level = pchg->chg_parm->eoc_level; | 
 | 352 | 			ichg = pchg->chg_parm->ichg; | 
 | 353 | 			val = (ichg << 4) | eoc_level; | 
| Kim, Milo | 27aefa3 | 2012-01-26 22:58:39 -0800 | [diff] [blame] | 354 | 			lp8727_write_byte(pchg, CHGCTRL2, val); | 
| Woogyom Kim | 2165c8a | 2012-01-04 08:27:43 +0400 | [diff] [blame] | 355 | 		} | 
 | 356 | 	} | 
 | 357 | } | 
 | 358 |  | 
 | 359 | static int lp8727_register_psy(struct lp8727_chg *pchg) | 
 | 360 | { | 
 | 361 | 	struct lp8727_psy *psy; | 
 | 362 |  | 
 | 363 | 	psy = kzalloc(sizeof(*psy), GFP_KERNEL); | 
 | 364 | 	if (!psy) | 
 | 365 | 		goto err_mem; | 
 | 366 |  | 
 | 367 | 	pchg->psy = psy; | 
 | 368 |  | 
 | 369 | 	psy->ac.name = "ac"; | 
 | 370 | 	psy->ac.type = POWER_SUPPLY_TYPE_MAINS; | 
 | 371 | 	psy->ac.properties = lp8727_charger_prop; | 
 | 372 | 	psy->ac.num_properties = ARRAY_SIZE(lp8727_charger_prop); | 
 | 373 | 	psy->ac.get_property = lp8727_charger_get_property; | 
 | 374 | 	psy->ac.supplied_to = battery_supplied_to; | 
 | 375 | 	psy->ac.num_supplicants = ARRAY_SIZE(battery_supplied_to); | 
 | 376 |  | 
 | 377 | 	if (power_supply_register(pchg->dev, &psy->ac)) | 
 | 378 | 		goto err_psy; | 
 | 379 |  | 
 | 380 | 	psy->usb.name = "usb"; | 
 | 381 | 	psy->usb.type = POWER_SUPPLY_TYPE_USB; | 
 | 382 | 	psy->usb.properties = lp8727_charger_prop; | 
 | 383 | 	psy->usb.num_properties = ARRAY_SIZE(lp8727_charger_prop); | 
 | 384 | 	psy->usb.get_property = lp8727_charger_get_property; | 
 | 385 | 	psy->usb.supplied_to = battery_supplied_to; | 
 | 386 | 	psy->usb.num_supplicants = ARRAY_SIZE(battery_supplied_to); | 
 | 387 |  | 
 | 388 | 	if (power_supply_register(pchg->dev, &psy->usb)) | 
 | 389 | 		goto err_psy; | 
 | 390 |  | 
 | 391 | 	psy->batt.name = "main_batt"; | 
 | 392 | 	psy->batt.type = POWER_SUPPLY_TYPE_BATTERY; | 
 | 393 | 	psy->batt.properties = lp8727_battery_prop; | 
 | 394 | 	psy->batt.num_properties = ARRAY_SIZE(lp8727_battery_prop); | 
 | 395 | 	psy->batt.get_property = lp8727_battery_get_property; | 
 | 396 | 	psy->batt.external_power_changed = lp8727_charger_changed; | 
 | 397 |  | 
 | 398 | 	if (power_supply_register(pchg->dev, &psy->batt)) | 
 | 399 | 		goto err_psy; | 
 | 400 |  | 
 | 401 | 	return 0; | 
 | 402 |  | 
 | 403 | err_mem: | 
 | 404 | 	return -ENOMEM; | 
 | 405 | err_psy: | 
 | 406 | 	kfree(psy); | 
 | 407 | 	return -EPERM; | 
 | 408 | } | 
 | 409 |  | 
 | 410 | static void lp8727_unregister_psy(struct lp8727_chg *pchg) | 
 | 411 | { | 
 | 412 | 	struct lp8727_psy *psy = pchg->psy; | 
 | 413 |  | 
| Kim, Milo | ce09aff | 2012-01-04 09:03:18 +0400 | [diff] [blame] | 414 | 	if (!psy) | 
 | 415 | 		return; | 
 | 416 |  | 
 | 417 | 	power_supply_unregister(&psy->ac); | 
 | 418 | 	power_supply_unregister(&psy->usb); | 
 | 419 | 	power_supply_unregister(&psy->batt); | 
 | 420 | 	kfree(psy); | 
| Woogyom Kim | 2165c8a | 2012-01-04 08:27:43 +0400 | [diff] [blame] | 421 | } | 
 | 422 |  | 
 | 423 | static int lp8727_probe(struct i2c_client *cl, const struct i2c_device_id *id) | 
 | 424 | { | 
 | 425 | 	struct lp8727_chg *pchg; | 
 | 426 | 	int ret; | 
 | 427 |  | 
| Kim, Milo | 998a8e7 | 2011-11-17 21:43:06 -0800 | [diff] [blame] | 428 | 	if (!i2c_check_functionality(cl->adapter, I2C_FUNC_SMBUS_I2C_BLOCK)) | 
 | 429 | 		return -EIO; | 
 | 430 |  | 
| Woogyom Kim | 2165c8a | 2012-01-04 08:27:43 +0400 | [diff] [blame] | 431 | 	pchg = kzalloc(sizeof(*pchg), GFP_KERNEL); | 
 | 432 | 	if (!pchg) | 
 | 433 | 		return -ENOMEM; | 
 | 434 |  | 
 | 435 | 	pchg->client = cl; | 
 | 436 | 	pchg->dev = &cl->dev; | 
 | 437 | 	pchg->pdata = cl->dev.platform_data; | 
 | 438 | 	i2c_set_clientdata(cl, pchg); | 
 | 439 |  | 
 | 440 | 	mutex_init(&pchg->xfer_lock); | 
 | 441 |  | 
| Kim, Milo | 7da6334 | 2012-01-26 22:58:30 -0800 | [diff] [blame] | 442 | 	ret = lp8727_init_device(pchg); | 
 | 443 | 	if (ret) { | 
 | 444 | 		dev_err(pchg->dev, "i2c communication err: %d", ret); | 
 | 445 | 		goto error; | 
 | 446 | 	} | 
 | 447 |  | 
 | 448 | 	ret = lp8727_intr_config(pchg); | 
 | 449 | 	if (ret) { | 
 | 450 | 		dev_err(pchg->dev, "irq handler err: %d", ret); | 
 | 451 | 		goto error; | 
 | 452 | 	} | 
| Woogyom Kim | 2165c8a | 2012-01-04 08:27:43 +0400 | [diff] [blame] | 453 |  | 
 | 454 | 	ret = lp8727_register_psy(pchg); | 
| Kim, Milo | 7da6334 | 2012-01-26 22:58:30 -0800 | [diff] [blame] | 455 | 	if (ret) { | 
 | 456 | 		dev_err(pchg->dev, "power supplies register err: %d", ret); | 
 | 457 | 		goto error; | 
 | 458 | 	} | 
| Woogyom Kim | 2165c8a | 2012-01-04 08:27:43 +0400 | [diff] [blame] | 459 |  | 
 | 460 | 	return 0; | 
| Kim, Milo | 7da6334 | 2012-01-26 22:58:30 -0800 | [diff] [blame] | 461 |  | 
 | 462 | error: | 
 | 463 | 	kfree(pchg); | 
 | 464 | 	return ret; | 
| Woogyom Kim | 2165c8a | 2012-01-04 08:27:43 +0400 | [diff] [blame] | 465 | } | 
 | 466 |  | 
 | 467 | static int __devexit lp8727_remove(struct i2c_client *cl) | 
 | 468 | { | 
 | 469 | 	struct lp8727_chg *pchg = i2c_get_clientdata(cl); | 
 | 470 |  | 
 | 471 | 	lp8727_unregister_psy(pchg); | 
 | 472 | 	free_irq(pchg->client->irq, pchg); | 
 | 473 | 	flush_workqueue(pchg->irqthread); | 
 | 474 | 	destroy_workqueue(pchg->irqthread); | 
 | 475 | 	kfree(pchg); | 
 | 476 | 	return 0; | 
 | 477 | } | 
 | 478 |  | 
 | 479 | static const struct i2c_device_id lp8727_ids[] = { | 
 | 480 | 	{"lp8727", 0}, | 
| Axel Lin | 455a0e2 | 2012-01-16 13:48:20 +0800 | [diff] [blame] | 481 | 	{ } | 
| Woogyom Kim | 2165c8a | 2012-01-04 08:27:43 +0400 | [diff] [blame] | 482 | }; | 
| Axel Lin | e2c5f7d | 2012-01-12 20:45:02 +0800 | [diff] [blame] | 483 | MODULE_DEVICE_TABLE(i2c, lp8727_ids); | 
| Woogyom Kim | 2165c8a | 2012-01-04 08:27:43 +0400 | [diff] [blame] | 484 |  | 
 | 485 | static struct i2c_driver lp8727_driver = { | 
 | 486 | 	.driver = { | 
 | 487 | 		   .name = "lp8727", | 
 | 488 | 		   }, | 
 | 489 | 	.probe = lp8727_probe, | 
 | 490 | 	.remove = __devexit_p(lp8727_remove), | 
 | 491 | 	.id_table = lp8727_ids, | 
 | 492 | }; | 
| Axel Lin | 5ff92e7 | 2012-01-21 14:42:54 +0800 | [diff] [blame] | 493 | module_i2c_driver(lp8727_driver); | 
| Woogyom Kim | 2165c8a | 2012-01-04 08:27:43 +0400 | [diff] [blame] | 494 |  | 
| Kim, Milo | e39b828 | 2012-01-29 17:28:18 -0800 | [diff] [blame] | 495 | MODULE_DESCRIPTION("TI/National Semiconductor LP8727 charger driver"); | 
| Kim, Milo | 7336880 | 2012-01-26 22:58:51 -0800 | [diff] [blame] | 496 | MODULE_AUTHOR("Woogyom Kim <milo.kim@ti.com>, " | 
 | 497 | 	      "Daniel Jeong <daniel.jeong@ti.com>"); | 
| Woogyom Kim | 2165c8a | 2012-01-04 08:27:43 +0400 | [diff] [blame] | 498 | MODULE_LICENSE("GPL"); |