Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

power_supply: Add driver for TI BQ20Z75 gas gauge IC

This driver depends on I2C and uses SMBUS for communication with
the host.

Signed-off-by: Rhyland Klein <rklein@nvidia.com>
Reviewed-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
Reviewed-by: Jean Delvare <khali@linux-fr.org>
Signed-off-by: Anton Vorontsov <cbouatmailru@gmail.com>

authored by

Rhyland Klein and committed by
Anton Vorontsov
a7640bfa 2d98dae2

+393
+7
drivers/power/Kconfig
··· 109 109 help 110 110 Say Y to enable support for battery measured by WM97xx aux port. 111 111 112 + config BATTERY_BQ20Z75 113 + tristate "TI BQ20z75 gas gauge" 114 + depends on I2C 115 + help 116 + Say Y to include support for TI BQ20z75 SBS-compliant 117 + gas gauge and protection IC. 118 + 112 119 config BATTERY_BQ27x00 113 120 tristate "BQ27x00 battery driver" 114 121 depends on I2C
+1
drivers/power/Makefile
··· 29 29 obj-$(CONFIG_BATTERY_TOSA) += tosa_battery.o 30 30 obj-$(CONFIG_BATTERY_COLLIE) += collie_battery.o 31 31 obj-$(CONFIG_BATTERY_WM97XX) += wm97xx_battery.o 32 + obj-$(CONFIG_BATTERY_BQ20Z75) += bq20z75.o 32 33 obj-$(CONFIG_BATTERY_BQ27x00) += bq27x00_battery.o 33 34 obj-$(CONFIG_BATTERY_DA9030) += da9030_battery.o 34 35 obj-$(CONFIG_BATTERY_MAX17040) += max17040_battery.o
+385
drivers/power/bq20z75.c
··· 1 + /* 2 + * Gas Gauge driver for TI's BQ20Z75 3 + * 4 + * Copyright (c) 2010, NVIDIA Corporation. 5 + * 6 + * This program is free software; you can redistribute it and/or modify 7 + * it under the terms of the GNU General Public License as published by 8 + * the Free Software Foundation; either version 2 of the License, or 9 + * (at your option) any later version. 10 + * 11 + * This program is distributed in the hope that it will be useful, but WITHOUT 12 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 14 + * more details. 15 + * 16 + * You should have received a copy of the GNU General Public License along 17 + * with this program; if not, write to the Free Software Foundation, Inc., 18 + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 + */ 20 + 21 + #include <linux/init.h> 22 + #include <linux/module.h> 23 + #include <linux/kernel.h> 24 + #include <linux/err.h> 25 + #include <linux/power_supply.h> 26 + #include <linux/i2c.h> 27 + #include <linux/slab.h> 28 + 29 + enum { 30 + REG_MANUFACTURER_DATA, 31 + REG_TEMPERATURE, 32 + REG_VOLTAGE, 33 + REG_CURRENT, 34 + REG_CAPACITY, 35 + REG_TIME_TO_EMPTY, 36 + REG_TIME_TO_FULL, 37 + REG_STATUS, 38 + REG_CYCLE_COUNT, 39 + REG_SERIAL_NUMBER 40 + }; 41 + 42 + /* manufacturer access defines */ 43 + #define MANUFACTURER_ACCESS_STATUS 0x0006 44 + #define MANUFACTURER_ACCESS_SLEEP 0x0011 45 + 46 + /* battery status value bits */ 47 + #define BATTERY_CHARGING 0x40 48 + #define BATTERY_FULL_CHARGED 0x20 49 + #define BATTERY_FULL_DISCHARGED 0x10 50 + 51 + #define BQ20Z75_DATA(_psp, _addr, _min_value, _max_value) { \ 52 + .psp = _psp, \ 53 + .addr = _addr, \ 54 + .min_value = _min_value, \ 55 + .max_value = _max_value, \ 56 + } 57 + 58 + static const struct bq20z75_device_data { 59 + enum power_supply_property psp; 60 + u8 addr; 61 + int min_value; 62 + int max_value; 63 + } bq20z75_data[] = { 64 + [REG_MANUFACTURER_DATA] = 65 + BQ20Z75_DATA(POWER_SUPPLY_PROP_PRESENT, 0x00, 0, 65535), 66 + [REG_TEMPERATURE] = 67 + BQ20Z75_DATA(POWER_SUPPLY_PROP_TEMP, 0x08, 0, 65535), 68 + [REG_VOLTAGE] = 69 + BQ20Z75_DATA(POWER_SUPPLY_PROP_VOLTAGE_NOW, 0x09, 0, 20000), 70 + [REG_CURRENT] = 71 + BQ20Z75_DATA(POWER_SUPPLY_PROP_CURRENT_NOW, 0x0A, -32768, 72 + 32767), 73 + [REG_CAPACITY] = 74 + BQ20Z75_DATA(POWER_SUPPLY_PROP_CAPACITY, 0x0E, 0, 100), 75 + [REG_TIME_TO_EMPTY] = 76 + BQ20Z75_DATA(POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, 0x12, 0, 77 + 65535), 78 + [REG_TIME_TO_FULL] = 79 + BQ20Z75_DATA(POWER_SUPPLY_PROP_TIME_TO_FULL_AVG, 0x13, 0, 80 + 65535), 81 + [REG_STATUS] = 82 + BQ20Z75_DATA(POWER_SUPPLY_PROP_STATUS, 0x16, 0, 65535), 83 + [REG_CYCLE_COUNT] = 84 + BQ20Z75_DATA(POWER_SUPPLY_PROP_CYCLE_COUNT, 0x17, 0, 65535), 85 + [REG_SERIAL_NUMBER] = 86 + BQ20Z75_DATA(POWER_SUPPLY_PROP_SERIAL_NUMBER, 0x1C, 0, 65535), 87 + }; 88 + 89 + static enum power_supply_property bq20z75_properties[] = { 90 + POWER_SUPPLY_PROP_STATUS, 91 + POWER_SUPPLY_PROP_HEALTH, 92 + POWER_SUPPLY_PROP_PRESENT, 93 + POWER_SUPPLY_PROP_TECHNOLOGY, 94 + POWER_SUPPLY_PROP_CYCLE_COUNT, 95 + POWER_SUPPLY_PROP_VOLTAGE_NOW, 96 + POWER_SUPPLY_PROP_CURRENT_NOW, 97 + POWER_SUPPLY_PROP_CAPACITY, 98 + POWER_SUPPLY_PROP_TEMP, 99 + POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, 100 + POWER_SUPPLY_PROP_TIME_TO_FULL_AVG, 101 + POWER_SUPPLY_PROP_SERIAL_NUMBER, 102 + }; 103 + 104 + struct bq20z75_info { 105 + struct i2c_client *client; 106 + struct power_supply power_supply; 107 + }; 108 + 109 + static int bq20z75_get_battery_presence_and_health( 110 + struct i2c_client *client, enum power_supply_property psp, 111 + union power_supply_propval *val) 112 + { 113 + s32 ret; 114 + 115 + /* Write to ManufacturerAccess with 116 + * ManufacturerAccess command and then 117 + * read the status */ 118 + ret = i2c_smbus_write_word_data(client, 119 + bq20z75_data[REG_MANUFACTURER_DATA].addr, 120 + MANUFACTURER_ACCESS_STATUS); 121 + if (ret < 0) { 122 + dev_err(&client->dev, 123 + "%s: i2c write for battery presence failed\n", 124 + __func__); 125 + return -ENODEV; 126 + } 127 + 128 + ret = i2c_smbus_read_word_data(client, 129 + bq20z75_data[REG_MANUFACTURER_DATA].addr); 130 + if (ret < 0) { 131 + dev_err(&client->dev, 132 + "%s: i2c read for battery presence failed\n", 133 + __func__); 134 + return -EIO; 135 + } 136 + 137 + if (ret < bq20z75_data[REG_MANUFACTURER_DATA].min_value || 138 + ret > bq20z75_data[REG_MANUFACTURER_DATA].max_value) { 139 + val->intval = 0; 140 + return 0; 141 + } 142 + 143 + /* Mask the upper nibble of 2nd byte and 144 + * lower byte of response then 145 + * shift the result by 8 to get status*/ 146 + ret &= 0x0F00; 147 + ret >>= 8; 148 + if (psp == POWER_SUPPLY_PROP_PRESENT) { 149 + if (ret == 0x0F) 150 + /* battery removed */ 151 + val->intval = 0; 152 + else 153 + val->intval = 1; 154 + } else if (psp == POWER_SUPPLY_PROP_HEALTH) { 155 + if (ret == 0x09) 156 + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; 157 + else if (ret == 0x0B) 158 + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; 159 + else if (ret == 0x0C) 160 + val->intval = POWER_SUPPLY_HEALTH_DEAD; 161 + else 162 + val->intval = POWER_SUPPLY_HEALTH_GOOD; 163 + } 164 + 165 + return 0; 166 + } 167 + 168 + static int bq20z75_get_battery_property(struct i2c_client *client, 169 + int reg_offset, enum power_supply_property psp, 170 + union power_supply_propval *val) 171 + { 172 + s32 ret; 173 + 174 + ret = i2c_smbus_read_word_data(client, 175 + bq20z75_data[reg_offset].addr); 176 + if (ret < 0) { 177 + dev_err(&client->dev, 178 + "%s: i2c read for %d failed\n", __func__, reg_offset); 179 + return -EIO; 180 + } 181 + 182 + if (ret >= bq20z75_data[reg_offset].min_value && 183 + ret <= bq20z75_data[reg_offset].max_value) { 184 + val->intval = ret; 185 + if (psp == POWER_SUPPLY_PROP_STATUS) { 186 + if (ret & BATTERY_CHARGING) 187 + val->intval = POWER_SUPPLY_STATUS_CHARGING; 188 + else if (ret & BATTERY_FULL_CHARGED) 189 + val->intval = POWER_SUPPLY_STATUS_FULL; 190 + else if (ret & BATTERY_FULL_DISCHARGED) 191 + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; 192 + else 193 + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; 194 + } 195 + /* bq20z75 provides battery tempreture in 0.1°K 196 + * so convert it to °C */ 197 + else if (psp == POWER_SUPPLY_PROP_TEMP) 198 + val->intval = ret - 2731; 199 + } else { 200 + if (psp == POWER_SUPPLY_PROP_STATUS) 201 + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; 202 + else 203 + val->intval = 0; 204 + } 205 + 206 + return 0; 207 + } 208 + 209 + static int bq20z75_get_battery_capacity(struct i2c_client *client, 210 + union power_supply_propval *val) 211 + { 212 + s32 ret; 213 + 214 + ret = i2c_smbus_read_byte_data(client, bq20z75_data[REG_CAPACITY].addr); 215 + if (ret < 0) { 216 + dev_err(&client->dev, 217 + "%s: i2c read for %d failed\n", __func__, REG_CAPACITY); 218 + return -EIO; 219 + } 220 + 221 + /* bq20z75 spec says that this can be >100 % 222 + * even if max value is 100 % */ 223 + val->intval = min(ret, 100); 224 + 225 + return 0; 226 + } 227 + 228 + static int bq20z75_get_property(struct power_supply *psy, 229 + enum power_supply_property psp, 230 + union power_supply_propval *val) 231 + { 232 + int count; 233 + int ret; 234 + struct bq20z75_info *bq20z75_device = container_of(psy, 235 + struct bq20z75_info, power_supply); 236 + struct i2c_client *client = bq20z75_device->client; 237 + 238 + switch (psp) { 239 + case POWER_SUPPLY_PROP_PRESENT: 240 + case POWER_SUPPLY_PROP_HEALTH: 241 + ret = bq20z75_get_battery_presence_and_health(client, psp, val); 242 + if (ret) 243 + return ret; 244 + break; 245 + 246 + case POWER_SUPPLY_PROP_TECHNOLOGY: 247 + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; 248 + break; 249 + 250 + case POWER_SUPPLY_PROP_CAPACITY: 251 + ret = bq20z75_get_battery_capacity(client, val); 252 + if (ret) 253 + return ret; 254 + break; 255 + 256 + case POWER_SUPPLY_PROP_STATUS: 257 + case POWER_SUPPLY_PROP_CYCLE_COUNT: 258 + case POWER_SUPPLY_PROP_VOLTAGE_NOW: 259 + case POWER_SUPPLY_PROP_CURRENT_NOW: 260 + case POWER_SUPPLY_PROP_TEMP: 261 + case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: 262 + case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG: 263 + case POWER_SUPPLY_PROP_SERIAL_NUMBER: 264 + for (count = 0; count < ARRAY_SIZE(bq20z75_data); count++) { 265 + if (psp == bq20z75_data[count].psp) 266 + break; 267 + } 268 + 269 + ret = bq20z75_get_battery_property(client, count, psp, val); 270 + if (ret) 271 + return ret; 272 + break; 273 + 274 + default: 275 + dev_err(&client->dev, 276 + "%s: INVALID property\n", __func__); 277 + return -EINVAL; 278 + } 279 + 280 + dev_dbg(&client->dev, 281 + "%s: property = %d, value = %d\n", __func__, psp, val->intval); 282 + 283 + return 0; 284 + } 285 + 286 + static int bq20z75_probe(struct i2c_client *client, 287 + const struct i2c_device_id *id) 288 + { 289 + struct bq20z75_info *bq20z75_device; 290 + int rc; 291 + 292 + bq20z75_device = kzalloc(sizeof(struct bq20z75_info), GFP_KERNEL); 293 + if (!bq20z75_device) 294 + return -ENOMEM; 295 + 296 + bq20z75_device->client = client; 297 + bq20z75_device->power_supply.name = "battery"; 298 + bq20z75_device->power_supply.type = POWER_SUPPLY_TYPE_BATTERY; 299 + bq20z75_device->power_supply.properties = bq20z75_properties; 300 + bq20z75_device->power_supply.num_properties = 301 + ARRAY_SIZE(bq20z75_properties); 302 + bq20z75_device->power_supply.get_property = bq20z75_get_property; 303 + 304 + i2c_set_clientdata(client, bq20z75_device); 305 + 306 + rc = power_supply_register(&client->dev, &bq20z75_device->power_supply); 307 + if (rc) { 308 + dev_err(&client->dev, 309 + "%s: Failed to register power supply\n", __func__); 310 + kfree(bq20z75_device); 311 + return rc; 312 + } 313 + 314 + dev_info(&client->dev, 315 + "%s: battery gas gauge device registered\n", client->name); 316 + 317 + return 0; 318 + } 319 + 320 + static int bq20z75_remove(struct i2c_client *client) 321 + { 322 + struct bq20z75_info *bq20z75_device = i2c_get_clientdata(client); 323 + 324 + power_supply_unregister(&bq20z75_device->power_supply); 325 + kfree(bq20z75_device); 326 + bq20z75_device = NULL; 327 + 328 + return 0; 329 + } 330 + 331 + #if defined CONFIG_PM 332 + static int bq20z75_suspend(struct i2c_client *client, 333 + pm_message_t state) 334 + { 335 + s32 ret; 336 + 337 + /* write to manufacturer access with sleep command */ 338 + ret = i2c_smbus_write_word_data(client, 339 + bq20z75_data[REG_MANUFACTURER_DATA].addr, 340 + MANUFACTURER_ACCESS_SLEEP); 341 + if (ret < 0) { 342 + dev_err(&client->dev, 343 + "%s: i2c write for %d failed\n", 344 + __func__, MANUFACTURER_ACCESS_SLEEP); 345 + return -EIO; 346 + } 347 + 348 + return 0; 349 + } 350 + #else 351 + #define bq20z75_suspend NULL 352 + #endif 353 + /* any smbus transaction will wake up bq20z75 */ 354 + #define bq20z75_resume NULL 355 + 356 + static const struct i2c_device_id bq20z75_id[] = { 357 + { "bq20z75", 0 }, 358 + {} 359 + }; 360 + 361 + static struct i2c_driver bq20z75_battery_driver = { 362 + .probe = bq20z75_probe, 363 + .remove = bq20z75_remove, 364 + .suspend = bq20z75_suspend, 365 + .resume = bq20z75_resume, 366 + .id_table = bq20z75_id, 367 + .driver = { 368 + .name = "bq20z75-battery", 369 + }, 370 + }; 371 + 372 + static int __init bq20z75_battery_init(void) 373 + { 374 + return i2c_add_driver(&bq20z75_battery_driver); 375 + } 376 + module_init(bq20z75_battery_init); 377 + 378 + static void __exit bq20z75_battery_exit(void) 379 + { 380 + i2c_del_driver(&bq20z75_battery_driver); 381 + } 382 + module_exit(bq20z75_battery_exit); 383 + 384 + MODULE_DESCRIPTION("BQ20z75 battery monitor driver"); 385 + MODULE_LICENSE("GPL");