| /* | 
 |  * Fuel gauge driver for Maxim 17042 / 8966 / 8997 | 
 |  *  Note that Maxim 8966 and 8997 are mfd and this is its subdevice. | 
 |  * | 
 |  * Copyright (C) 2011 Samsung Electronics | 
 |  * MyungJoo Ham <myungjoo.ham@samsung.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; either version 2 of the License, or | 
 |  * (at your option) any later version. | 
 |  * | 
 |  * 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 | 
 |  * | 
 |  * This driver is based on max17040_battery.c | 
 |  */ | 
 |  | 
 | #include <linux/init.h> | 
 | #include <linux/slab.h> | 
 | #include <linux/i2c.h> | 
 | #include <linux/mod_devicetable.h> | 
 | #include <linux/power_supply.h> | 
 | #include <linux/power/max17042_battery.h> | 
 |  | 
 | enum max17042_register { | 
 | 	MAX17042_STATUS		= 0x00, | 
 | 	MAX17042_VALRT_Th	= 0x01, | 
 | 	MAX17042_TALRT_Th	= 0x02, | 
 | 	MAX17042_SALRT_Th	= 0x03, | 
 | 	MAX17042_AtRate		= 0x04, | 
 | 	MAX17042_RepCap		= 0x05, | 
 | 	MAX17042_RepSOC		= 0x06, | 
 | 	MAX17042_Age		= 0x07, | 
 | 	MAX17042_TEMP		= 0x08, | 
 | 	MAX17042_VCELL		= 0x09, | 
 | 	MAX17042_Current	= 0x0A, | 
 | 	MAX17042_AvgCurrent	= 0x0B, | 
 | 	MAX17042_Qresidual	= 0x0C, | 
 | 	MAX17042_SOC		= 0x0D, | 
 | 	MAX17042_AvSOC		= 0x0E, | 
 | 	MAX17042_RemCap		= 0x0F, | 
 | 	MAX17402_FullCAP	= 0x10, | 
 | 	MAX17042_TTE		= 0x11, | 
 | 	MAX17042_V_empty	= 0x12, | 
 |  | 
 | 	MAX17042_RSLOW		= 0x14, | 
 |  | 
 | 	MAX17042_AvgTA		= 0x16, | 
 | 	MAX17042_Cycles		= 0x17, | 
 | 	MAX17042_DesignCap	= 0x18, | 
 | 	MAX17042_AvgVCELL	= 0x19, | 
 | 	MAX17042_MinMaxTemp	= 0x1A, | 
 | 	MAX17042_MinMaxVolt	= 0x1B, | 
 | 	MAX17042_MinMaxCurr	= 0x1C, | 
 | 	MAX17042_CONFIG		= 0x1D, | 
 | 	MAX17042_ICHGTerm	= 0x1E, | 
 | 	MAX17042_AvCap		= 0x1F, | 
 | 	MAX17042_ManName	= 0x20, | 
 | 	MAX17042_DevName	= 0x21, | 
 | 	MAX17042_DevChem	= 0x22, | 
 |  | 
 | 	MAX17042_TempNom	= 0x24, | 
 | 	MAX17042_TempCold	= 0x25, | 
 | 	MAX17042_TempHot	= 0x26, | 
 | 	MAX17042_AIN		= 0x27, | 
 | 	MAX17042_LearnCFG	= 0x28, | 
 | 	MAX17042_SHFTCFG	= 0x29, | 
 | 	MAX17042_RelaxCFG	= 0x2A, | 
 | 	MAX17042_MiscCFG	= 0x2B, | 
 | 	MAX17042_TGAIN		= 0x2C, | 
 | 	MAx17042_TOFF		= 0x2D, | 
 | 	MAX17042_CGAIN		= 0x2E, | 
 | 	MAX17042_COFF		= 0x2F, | 
 |  | 
 | 	MAX17042_Q_empty	= 0x33, | 
 | 	MAX17042_T_empty	= 0x34, | 
 |  | 
 | 	MAX17042_RCOMP0		= 0x38, | 
 | 	MAX17042_TempCo		= 0x39, | 
 | 	MAX17042_Rx		= 0x3A, | 
 | 	MAX17042_T_empty0	= 0x3B, | 
 | 	MAX17042_TaskPeriod	= 0x3C, | 
 | 	MAX17042_FSTAT		= 0x3D, | 
 |  | 
 | 	MAX17042_SHDNTIMER	= 0x3F, | 
 |  | 
 | 	MAX17042_VFRemCap	= 0x4A, | 
 |  | 
 | 	MAX17042_QH		= 0x4D, | 
 | 	MAX17042_QL		= 0x4E, | 
 | }; | 
 |  | 
 | struct max17042_chip { | 
 | 	struct i2c_client *client; | 
 | 	struct power_supply battery; | 
 | 	struct max17042_platform_data *pdata; | 
 | }; | 
 |  | 
 | static int max17042_write_reg(struct i2c_client *client, u8 reg, u16 value) | 
 | { | 
 | 	int ret = i2c_smbus_write_word_data(client, reg, value); | 
 |  | 
 | 	if (ret < 0) | 
 | 		dev_err(&client->dev, "%s: err %d\n", __func__, ret); | 
 |  | 
 | 	return ret; | 
 | } | 
 |  | 
 | static int max17042_read_reg(struct i2c_client *client, u8 reg) | 
 | { | 
 | 	int ret = i2c_smbus_read_word_data(client, reg); | 
 |  | 
 | 	if (ret < 0) | 
 | 		dev_err(&client->dev, "%s: err %d\n", __func__, ret); | 
 |  | 
 | 	return ret; | 
 | } | 
 |  | 
 | static enum power_supply_property max17042_battery_props[] = { | 
 | 	POWER_SUPPLY_PROP_VOLTAGE_NOW, | 
 | 	POWER_SUPPLY_PROP_VOLTAGE_AVG, | 
 | 	POWER_SUPPLY_PROP_CAPACITY, | 
 | }; | 
 |  | 
 | static int max17042_get_property(struct power_supply *psy, | 
 | 			    enum power_supply_property psp, | 
 | 			    union power_supply_propval *val) | 
 | { | 
 | 	struct max17042_chip *chip = container_of(psy, | 
 | 				struct max17042_chip, battery); | 
 |  | 
 | 	switch (psp) { | 
 | 	case POWER_SUPPLY_PROP_VOLTAGE_NOW: | 
 | 		val->intval = max17042_read_reg(chip->client, | 
 | 				MAX17042_VCELL) * 83; /* 1000 / 12 = 83 */ | 
 | 		break; | 
 | 	case POWER_SUPPLY_PROP_VOLTAGE_AVG: | 
 | 		val->intval = max17042_read_reg(chip->client, | 
 | 				MAX17042_AvgVCELL) * 83; | 
 | 		break; | 
 | 	case POWER_SUPPLY_PROP_CAPACITY: | 
 | 		val->intval = max17042_read_reg(chip->client, | 
 | 				MAX17042_SOC) / 256; | 
 | 		break; | 
 | 	default: | 
 | 		return -EINVAL; | 
 | 	} | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int __devinit max17042_probe(struct i2c_client *client, | 
 | 			const struct i2c_device_id *id) | 
 | { | 
 | 	struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); | 
 | 	struct max17042_chip *chip; | 
 | 	int ret; | 
 |  | 
 | 	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA)) | 
 | 		return -EIO; | 
 |  | 
 | 	chip = kzalloc(sizeof(*chip), GFP_KERNEL); | 
 | 	if (!chip) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	chip->client = client; | 
 | 	chip->pdata = client->dev.platform_data; | 
 |  | 
 | 	i2c_set_clientdata(client, chip); | 
 |  | 
 | 	chip->battery.name		= "max17042_battery"; | 
 | 	chip->battery.type		= POWER_SUPPLY_TYPE_BATTERY; | 
 | 	chip->battery.get_property	= max17042_get_property; | 
 | 	chip->battery.properties	= max17042_battery_props; | 
 | 	chip->battery.num_properties	= ARRAY_SIZE(max17042_battery_props); | 
 |  | 
 | 	ret = power_supply_register(&client->dev, &chip->battery); | 
 | 	if (ret) { | 
 | 		dev_err(&client->dev, "failed: power supply register\n"); | 
 | 		i2c_set_clientdata(client, NULL); | 
 | 		kfree(chip); | 
 | 		return ret; | 
 | 	} | 
 |  | 
 | 	if (!chip->pdata->enable_current_sense) { | 
 | 		max17042_write_reg(client, MAX17042_CGAIN, 0x0000); | 
 | 		max17042_write_reg(client, MAX17042_MiscCFG, 0x0003); | 
 | 		max17042_write_reg(client, MAX17042_LearnCFG, 0x0007); | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int __devexit max17042_remove(struct i2c_client *client) | 
 | { | 
 | 	struct max17042_chip *chip = i2c_get_clientdata(client); | 
 |  | 
 | 	power_supply_unregister(&chip->battery); | 
 | 	i2c_set_clientdata(client, NULL); | 
 | 	kfree(chip); | 
 | 	return 0; | 
 | } | 
 |  | 
 | static const struct i2c_device_id max17042_id[] = { | 
 | 	{ "max17042", 0 }, | 
 | 	{ } | 
 | }; | 
 | MODULE_DEVICE_TABLE(i2c, max17042_id); | 
 |  | 
 | static struct i2c_driver max17042_i2c_driver = { | 
 | 	.driver	= { | 
 | 		.name	= "max17042", | 
 | 	}, | 
 | 	.probe		= max17042_probe, | 
 | 	.remove		= __devexit_p(max17042_remove), | 
 | 	.id_table	= max17042_id, | 
 | }; | 
 |  | 
 | static int __init max17042_init(void) | 
 | { | 
 | 	return i2c_add_driver(&max17042_i2c_driver); | 
 | } | 
 | module_init(max17042_init); | 
 |  | 
 | static void __exit max17042_exit(void) | 
 | { | 
 | 	i2c_del_driver(&max17042_i2c_driver); | 
 | } | 
 | module_exit(max17042_exit); | 
 |  | 
 | MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>"); | 
 | MODULE_DESCRIPTION("MAX17042 Fuel Gauge"); | 
 | MODULE_LICENSE("GPL"); |