Nithish Mahalingam | 6721081 | 2010-06-17 18:12:36 +0100 | [diff] [blame] | 1 | /* |
| 2 | * intel_mid_battery.c - Intel MID PMIC Battery Driver |
| 3 | * |
| 4 | * Copyright (C) 2009 Intel Corporation |
| 5 | * |
| 6 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 7 | * |
| 8 | * This program is free software; you can redistribute it and/or modify |
| 9 | * it under the terms of the GNU General Public License as published by |
| 10 | * the Free Software Foundation; version 2 of the License. |
| 11 | * |
| 12 | * This program is distributed in the hope that it will be useful, but |
| 13 | * WITHOUT ANY WARRANTY; without even the implied warranty of |
| 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 15 | * General Public License for more details. |
| 16 | * |
| 17 | * You should have received a copy of the GNU General Public License along |
| 18 | * with this program; if not, write to the Free Software Foundation, Inc., |
| 19 | * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. |
| 20 | * |
| 21 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 22 | * Author: Nithish Mahalingam <nithish.mahalingam@intel.com> |
| 23 | */ |
| 24 | |
| 25 | #include <linux/module.h> |
| 26 | #include <linux/init.h> |
| 27 | #include <linux/err.h> |
| 28 | #include <linux/interrupt.h> |
| 29 | #include <linux/workqueue.h> |
| 30 | #include <linux/jiffies.h> |
| 31 | #include <linux/param.h> |
| 32 | #include <linux/device.h> |
| 33 | #include <linux/spi/spi.h> |
| 34 | #include <linux/platform_device.h> |
| 35 | #include <linux/power_supply.h> |
| 36 | |
| 37 | #include <asm/intel_scu_ipc.h> |
| 38 | |
| 39 | #define DRIVER_NAME "pmic_battery" |
| 40 | |
| 41 | /********************************************************************* |
| 42 | * Generic defines |
| 43 | *********************************************************************/ |
| 44 | |
| 45 | static int debug; |
| 46 | module_param(debug, int, 0444); |
| 47 | MODULE_PARM_DESC(debug, "Flag to enable PMIC Battery debug messages."); |
| 48 | |
| 49 | #define PMIC_BATT_DRV_INFO_UPDATED 1 |
| 50 | #define PMIC_BATT_PRESENT 1 |
| 51 | #define PMIC_BATT_NOT_PRESENT 0 |
| 52 | #define PMIC_USB_PRESENT PMIC_BATT_PRESENT |
| 53 | #define PMIC_USB_NOT_PRESENT PMIC_BATT_NOT_PRESENT |
| 54 | |
| 55 | /* pmic battery register related */ |
| 56 | #define PMIC_BATT_CHR_SCHRGINT_ADDR 0xD2 |
| 57 | #define PMIC_BATT_CHR_SBATOVP_MASK (1 << 1) |
| 58 | #define PMIC_BATT_CHR_STEMP_MASK (1 << 2) |
| 59 | #define PMIC_BATT_CHR_SCOMP_MASK (1 << 3) |
| 60 | #define PMIC_BATT_CHR_SUSBDET_MASK (1 << 4) |
| 61 | #define PMIC_BATT_CHR_SBATDET_MASK (1 << 5) |
| 62 | #define PMIC_BATT_CHR_SDCLMT_MASK (1 << 6) |
| 63 | #define PMIC_BATT_CHR_SUSBOVP_MASK (1 << 7) |
Major Lee | 7925231 | 2011-08-26 12:22:00 +0100 | [diff] [blame] | 64 | #define PMIC_BATT_CHR_EXCPT_MASK 0x86 |
| 65 | |
Nithish Mahalingam | 6721081 | 2010-06-17 18:12:36 +0100 | [diff] [blame] | 66 | #define PMIC_BATT_ADC_ACCCHRG_MASK (1 << 31) |
| 67 | #define PMIC_BATT_ADC_ACCCHRGVAL_MASK 0x7FFFFFFF |
| 68 | |
| 69 | /* pmic ipc related */ |
| 70 | #define PMIC_BATT_CHR_IPC_FCHRG_SUBID 0x4 |
| 71 | #define PMIC_BATT_CHR_IPC_TCHRG_SUBID 0x6 |
| 72 | |
| 73 | /* types of battery charging */ |
| 74 | enum batt_charge_type { |
| 75 | BATT_USBOTG_500MA_CHARGE, |
| 76 | BATT_USBOTG_TRICKLE_CHARGE, |
| 77 | }; |
| 78 | |
| 79 | /* valid battery events */ |
| 80 | enum batt_event { |
| 81 | BATT_EVENT_BATOVP_EXCPT, |
| 82 | BATT_EVENT_USBOVP_EXCPT, |
| 83 | BATT_EVENT_TEMP_EXCPT, |
| 84 | BATT_EVENT_DCLMT_EXCPT, |
| 85 | BATT_EVENT_EXCPT |
| 86 | }; |
| 87 | |
| 88 | |
| 89 | /********************************************************************* |
| 90 | * Battery properties |
| 91 | *********************************************************************/ |
| 92 | |
| 93 | /* |
| 94 | * pmic battery info |
| 95 | */ |
| 96 | struct pmic_power_module_info { |
| 97 | bool is_dev_info_updated; |
| 98 | struct device *dev; |
| 99 | /* pmic battery data */ |
| 100 | unsigned long update_time; /* jiffies when data read */ |
| 101 | unsigned int usb_is_present; |
| 102 | unsigned int batt_is_present; |
| 103 | unsigned int batt_health; |
| 104 | unsigned int usb_health; |
| 105 | unsigned int batt_status; |
| 106 | unsigned int batt_charge_now; /* in mAS */ |
| 107 | unsigned int batt_prev_charge_full; /* in mAS */ |
| 108 | unsigned int batt_charge_rate; /* in units per second */ |
| 109 | |
Krzysztof Kozlowski | 297d716 | 2015-03-12 08:44:11 +0100 | [diff] [blame] | 110 | struct power_supply *usb; |
| 111 | struct power_supply *batt; |
Nithish Mahalingam | 6721081 | 2010-06-17 18:12:36 +0100 | [diff] [blame] | 112 | int irq; /* GPE_ID or IRQ# */ |
| 113 | struct workqueue_struct *monitor_wqueue; |
| 114 | struct delayed_work monitor_battery; |
| 115 | struct work_struct handler; |
| 116 | }; |
| 117 | |
| 118 | static unsigned int delay_time = 2000; /* in ms */ |
| 119 | |
| 120 | /* |
| 121 | * pmic ac properties |
| 122 | */ |
| 123 | static enum power_supply_property pmic_usb_props[] = { |
| 124 | POWER_SUPPLY_PROP_PRESENT, |
| 125 | POWER_SUPPLY_PROP_HEALTH, |
| 126 | }; |
| 127 | |
| 128 | /* |
| 129 | * pmic battery properties |
| 130 | */ |
| 131 | static enum power_supply_property pmic_battery_props[] = { |
| 132 | POWER_SUPPLY_PROP_STATUS, |
| 133 | POWER_SUPPLY_PROP_HEALTH, |
| 134 | POWER_SUPPLY_PROP_PRESENT, |
| 135 | POWER_SUPPLY_PROP_CHARGE_NOW, |
| 136 | POWER_SUPPLY_PROP_CHARGE_FULL, |
| 137 | }; |
| 138 | |
| 139 | |
| 140 | /* |
| 141 | * Glue functions for talking to the IPC |
| 142 | */ |
| 143 | |
| 144 | struct battery_property { |
| 145 | u32 capacity; /* Charger capacity */ |
| 146 | u8 crnt; /* Quick charge current value*/ |
| 147 | u8 volt; /* Fine adjustment of constant charge voltage */ |
| 148 | u8 prot; /* CHRGPROT register value */ |
| 149 | u8 prot2; /* CHRGPROT1 register value */ |
| 150 | u8 timer; /* Charging timer */ |
| 151 | }; |
| 152 | |
| 153 | #define IPCMSG_BATTERY 0xEF |
| 154 | |
| 155 | /* Battery coulomb counter accumulator commands */ |
| 156 | #define IPC_CMD_CC_WR 0 /* Update coulomb counter value */ |
| 157 | #define IPC_CMD_CC_RD 1 /* Read coulomb counter value */ |
| 158 | #define IPC_CMD_BATTERY_PROPERTY 2 /* Read Battery property */ |
| 159 | |
| 160 | /** |
| 161 | * pmic_scu_ipc_battery_cc_read - read battery cc |
| 162 | * @value: battery coulomb counter read |
| 163 | * |
| 164 | * Reads the battery couloumb counter value, returns 0 on success, or |
| 165 | * an error code |
| 166 | * |
| 167 | * This function may sleep. Locking for SCU accesses is handled for |
| 168 | * the caller. |
| 169 | */ |
| 170 | static int pmic_scu_ipc_battery_cc_read(u32 *value) |
| 171 | { |
Arjan van de Ven | 08a9e07 | 2010-08-10 10:27:57 +0100 | [diff] [blame] | 172 | return intel_scu_ipc_command(IPCMSG_BATTERY, IPC_CMD_CC_RD, |
Nithish Mahalingam | 6721081 | 2010-06-17 18:12:36 +0100 | [diff] [blame] | 173 | NULL, 0, value, 1); |
| 174 | } |
| 175 | |
| 176 | /** |
| 177 | * pmic_scu_ipc_battery_property_get - fetch properties |
| 178 | * @prop: battery properties |
| 179 | * |
| 180 | * Retrieve the battery properties from the power management |
| 181 | * |
| 182 | * This function may sleep. Locking for SCU accesses is handled for |
| 183 | * the caller. |
| 184 | */ |
| 185 | static int pmic_scu_ipc_battery_property_get(struct battery_property *prop) |
| 186 | { |
| 187 | u32 data[3]; |
| 188 | u8 *p = (u8 *)&data[1]; |
Shuduo Sang | 77f4b9f | 2010-08-24 14:35:17 +0100 | [diff] [blame] | 189 | int err = intel_scu_ipc_command(IPCMSG_BATTERY, |
| 190 | IPC_CMD_BATTERY_PROPERTY, NULL, 0, data, 3); |
Nithish Mahalingam | 6721081 | 2010-06-17 18:12:36 +0100 | [diff] [blame] | 191 | |
| 192 | prop->capacity = data[0]; |
| 193 | prop->crnt = *p++; |
| 194 | prop->volt = *p++; |
| 195 | prop->prot = *p++; |
| 196 | prop->prot2 = *p++; |
| 197 | prop->timer = *p++; |
| 198 | |
| 199 | return err; |
| 200 | } |
| 201 | |
| 202 | /** |
| 203 | * pmic_scu_ipc_set_charger - set charger |
| 204 | * @charger: charger to select |
| 205 | * |
| 206 | * Switch the charging mode for the SCU |
| 207 | */ |
| 208 | |
| 209 | static int pmic_scu_ipc_set_charger(int charger) |
| 210 | { |
Shuduo Sang | 77f4b9f | 2010-08-24 14:35:17 +0100 | [diff] [blame] | 211 | return intel_scu_ipc_simple_command(IPCMSG_BATTERY, charger); |
Nithish Mahalingam | 6721081 | 2010-06-17 18:12:36 +0100 | [diff] [blame] | 212 | } |
| 213 | |
| 214 | /** |
| 215 | * pmic_battery_log_event - log battery events |
| 216 | * @event: battery event to be logged |
| 217 | * Context: can sleep |
| 218 | * |
| 219 | * There are multiple battery events which may be of interest to users; |
| 220 | * this battery function logs the different battery events onto the |
| 221 | * kernel log messages. |
| 222 | */ |
| 223 | static void pmic_battery_log_event(enum batt_event event) |
| 224 | { |
| 225 | printk(KERN_WARNING "pmic-battery: "); |
| 226 | switch (event) { |
| 227 | case BATT_EVENT_BATOVP_EXCPT: |
| 228 | printk(KERN_CONT "battery overvoltage condition\n"); |
| 229 | break; |
| 230 | case BATT_EVENT_USBOVP_EXCPT: |
| 231 | printk(KERN_CONT "usb charger overvoltage condition\n"); |
| 232 | break; |
| 233 | case BATT_EVENT_TEMP_EXCPT: |
| 234 | printk(KERN_CONT "high battery temperature condition\n"); |
| 235 | break; |
| 236 | case BATT_EVENT_DCLMT_EXCPT: |
| 237 | printk(KERN_CONT "over battery charge current condition\n"); |
| 238 | break; |
| 239 | default: |
| 240 | printk(KERN_CONT "charger/battery exception %d\n", event); |
| 241 | break; |
| 242 | } |
| 243 | } |
| 244 | |
| 245 | /** |
| 246 | * pmic_battery_read_status - read battery status information |
| 247 | * @pbi: device info structure to update the read information |
| 248 | * Context: can sleep |
| 249 | * |
| 250 | * PMIC power source information need to be updated based on the data read |
| 251 | * from the PMIC battery registers. |
| 252 | * |
| 253 | */ |
| 254 | static void pmic_battery_read_status(struct pmic_power_module_info *pbi) |
| 255 | { |
| 256 | unsigned int update_time_intrvl; |
| 257 | unsigned int chrg_val; |
| 258 | u32 ccval; |
| 259 | u8 r8; |
| 260 | struct battery_property batt_prop; |
| 261 | int batt_present = 0; |
| 262 | int usb_present = 0; |
| 263 | int batt_exception = 0; |
| 264 | |
| 265 | /* make sure the last batt_status read happened delay_time before */ |
| 266 | if (pbi->update_time && time_before(jiffies, pbi->update_time + |
| 267 | msecs_to_jiffies(delay_time))) |
| 268 | return; |
| 269 | |
| 270 | update_time_intrvl = jiffies_to_msecs(jiffies - pbi->update_time); |
| 271 | pbi->update_time = jiffies; |
| 272 | |
| 273 | /* read coulomb counter registers and schrgint register */ |
| 274 | if (pmic_scu_ipc_battery_cc_read(&ccval)) { |
| 275 | dev_warn(pbi->dev, "%s(): ipc config cmd failed\n", |
| 276 | __func__); |
| 277 | return; |
| 278 | } |
| 279 | |
| 280 | if (intel_scu_ipc_ioread8(PMIC_BATT_CHR_SCHRGINT_ADDR, &r8)) { |
| 281 | dev_warn(pbi->dev, "%s(): ipc pmic read failed\n", |
| 282 | __func__); |
| 283 | return; |
| 284 | } |
| 285 | |
| 286 | /* |
| 287 | * set pmic_power_module_info members based on pmic register values |
| 288 | * read. |
| 289 | */ |
| 290 | |
| 291 | /* set batt_is_present */ |
| 292 | if (r8 & PMIC_BATT_CHR_SBATDET_MASK) { |
| 293 | pbi->batt_is_present = PMIC_BATT_PRESENT; |
| 294 | batt_present = 1; |
| 295 | } else { |
| 296 | pbi->batt_is_present = PMIC_BATT_NOT_PRESENT; |
| 297 | pbi->batt_health = POWER_SUPPLY_HEALTH_UNKNOWN; |
| 298 | pbi->batt_status = POWER_SUPPLY_STATUS_UNKNOWN; |
| 299 | } |
| 300 | |
| 301 | /* set batt_health */ |
| 302 | if (batt_present) { |
| 303 | if (r8 & PMIC_BATT_CHR_SBATOVP_MASK) { |
| 304 | pbi->batt_health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; |
| 305 | pbi->batt_status = POWER_SUPPLY_STATUS_NOT_CHARGING; |
| 306 | pmic_battery_log_event(BATT_EVENT_BATOVP_EXCPT); |
| 307 | batt_exception = 1; |
Nithish Mahalingam | 6721081 | 2010-06-17 18:12:36 +0100 | [diff] [blame] | 308 | } else if (r8 & PMIC_BATT_CHR_STEMP_MASK) { |
| 309 | pbi->batt_health = POWER_SUPPLY_HEALTH_OVERHEAT; |
| 310 | pbi->batt_status = POWER_SUPPLY_STATUS_NOT_CHARGING; |
| 311 | pmic_battery_log_event(BATT_EVENT_TEMP_EXCPT); |
| 312 | batt_exception = 1; |
| 313 | } else { |
| 314 | pbi->batt_health = POWER_SUPPLY_HEALTH_GOOD; |
Major Lee | 7925231 | 2011-08-26 12:22:00 +0100 | [diff] [blame] | 315 | if (r8 & PMIC_BATT_CHR_SDCLMT_MASK) { |
| 316 | /* PMIC will change charging current automatically */ |
| 317 | pmic_battery_log_event(BATT_EVENT_DCLMT_EXCPT); |
| 318 | } |
Nithish Mahalingam | 6721081 | 2010-06-17 18:12:36 +0100 | [diff] [blame] | 319 | } |
| 320 | } |
| 321 | |
| 322 | /* set usb_is_present */ |
| 323 | if (r8 & PMIC_BATT_CHR_SUSBDET_MASK) { |
| 324 | pbi->usb_is_present = PMIC_USB_PRESENT; |
| 325 | usb_present = 1; |
| 326 | } else { |
| 327 | pbi->usb_is_present = PMIC_USB_NOT_PRESENT; |
| 328 | pbi->usb_health = POWER_SUPPLY_HEALTH_UNKNOWN; |
| 329 | } |
| 330 | |
| 331 | if (usb_present) { |
| 332 | if (r8 & PMIC_BATT_CHR_SUSBOVP_MASK) { |
| 333 | pbi->usb_health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; |
| 334 | pmic_battery_log_event(BATT_EVENT_USBOVP_EXCPT); |
| 335 | } else { |
| 336 | pbi->usb_health = POWER_SUPPLY_HEALTH_GOOD; |
| 337 | } |
| 338 | } |
| 339 | |
| 340 | chrg_val = ccval & PMIC_BATT_ADC_ACCCHRGVAL_MASK; |
| 341 | |
| 342 | /* set batt_prev_charge_full to battery capacity the first time */ |
| 343 | if (!pbi->is_dev_info_updated) { |
| 344 | if (pmic_scu_ipc_battery_property_get(&batt_prop)) { |
| 345 | dev_warn(pbi->dev, "%s(): ipc config cmd failed\n", |
| 346 | __func__); |
| 347 | return; |
| 348 | } |
| 349 | pbi->batt_prev_charge_full = batt_prop.capacity; |
| 350 | } |
| 351 | |
| 352 | /* set batt_status */ |
| 353 | if (batt_present && !batt_exception) { |
| 354 | if (r8 & PMIC_BATT_CHR_SCOMP_MASK) { |
| 355 | pbi->batt_status = POWER_SUPPLY_STATUS_FULL; |
| 356 | pbi->batt_prev_charge_full = chrg_val; |
| 357 | } else if (ccval & PMIC_BATT_ADC_ACCCHRG_MASK) { |
| 358 | pbi->batt_status = POWER_SUPPLY_STATUS_DISCHARGING; |
| 359 | } else { |
| 360 | pbi->batt_status = POWER_SUPPLY_STATUS_CHARGING; |
| 361 | } |
| 362 | } |
| 363 | |
| 364 | /* set batt_charge_rate */ |
| 365 | if (pbi->is_dev_info_updated && batt_present && !batt_exception) { |
| 366 | if (pbi->batt_status == POWER_SUPPLY_STATUS_DISCHARGING) { |
| 367 | if (pbi->batt_charge_now - chrg_val) { |
| 368 | pbi->batt_charge_rate = ((pbi->batt_charge_now - |
| 369 | chrg_val) * 1000 * 60) / |
| 370 | update_time_intrvl; |
| 371 | } |
| 372 | } else if (pbi->batt_status == POWER_SUPPLY_STATUS_CHARGING) { |
| 373 | if (chrg_val - pbi->batt_charge_now) { |
| 374 | pbi->batt_charge_rate = ((chrg_val - |
| 375 | pbi->batt_charge_now) * 1000 * 60) / |
| 376 | update_time_intrvl; |
| 377 | } |
| 378 | } else |
| 379 | pbi->batt_charge_rate = 0; |
| 380 | } else { |
| 381 | pbi->batt_charge_rate = -1; |
| 382 | } |
| 383 | |
| 384 | /* batt_charge_now */ |
| 385 | if (batt_present && !batt_exception) |
| 386 | pbi->batt_charge_now = chrg_val; |
| 387 | else |
| 388 | pbi->batt_charge_now = -1; |
| 389 | |
| 390 | pbi->is_dev_info_updated = PMIC_BATT_DRV_INFO_UPDATED; |
| 391 | } |
| 392 | |
| 393 | /** |
| 394 | * pmic_usb_get_property - usb power source get property |
| 395 | * @psy: usb power supply context |
| 396 | * @psp: usb power source property |
| 397 | * @val: usb power source property value |
| 398 | * Context: can sleep |
| 399 | * |
| 400 | * PMIC usb power source property needs to be provided to power_supply |
| 401 | * subsytem for it to provide the information to users. |
| 402 | */ |
| 403 | static int pmic_usb_get_property(struct power_supply *psy, |
| 404 | enum power_supply_property psp, |
| 405 | union power_supply_propval *val) |
| 406 | { |
Krzysztof Kozlowski | 297d716 | 2015-03-12 08:44:11 +0100 | [diff] [blame] | 407 | struct pmic_power_module_info *pbi = power_supply_get_drvdata(psy); |
Nithish Mahalingam | 6721081 | 2010-06-17 18:12:36 +0100 | [diff] [blame] | 408 | |
| 409 | /* update pmic_power_module_info members */ |
| 410 | pmic_battery_read_status(pbi); |
| 411 | |
| 412 | switch (psp) { |
| 413 | case POWER_SUPPLY_PROP_PRESENT: |
| 414 | val->intval = pbi->usb_is_present; |
| 415 | break; |
| 416 | case POWER_SUPPLY_PROP_HEALTH: |
| 417 | val->intval = pbi->usb_health; |
| 418 | break; |
| 419 | default: |
| 420 | return -EINVAL; |
| 421 | } |
| 422 | |
| 423 | return 0; |
| 424 | } |
| 425 | |
| 426 | static inline unsigned long mAStouAh(unsigned long v) |
| 427 | { |
| 428 | /* seconds to hours, mA to µA */ |
Alan Cox | f59f5bc | 2010-08-10 10:28:04 +0100 | [diff] [blame] | 429 | return (v * 1000) / 3600; |
Nithish Mahalingam | 6721081 | 2010-06-17 18:12:36 +0100 | [diff] [blame] | 430 | } |
| 431 | |
| 432 | /** |
| 433 | * pmic_battery_get_property - battery power source get property |
| 434 | * @psy: battery power supply context |
| 435 | * @psp: battery power source property |
| 436 | * @val: battery power source property value |
| 437 | * Context: can sleep |
| 438 | * |
| 439 | * PMIC battery power source property needs to be provided to power_supply |
| 440 | * subsytem for it to provide the information to users. |
| 441 | */ |
| 442 | static int pmic_battery_get_property(struct power_supply *psy, |
| 443 | enum power_supply_property psp, |
| 444 | union power_supply_propval *val) |
| 445 | { |
Krzysztof Kozlowski | 297d716 | 2015-03-12 08:44:11 +0100 | [diff] [blame] | 446 | struct pmic_power_module_info *pbi = power_supply_get_drvdata(psy); |
Nithish Mahalingam | 6721081 | 2010-06-17 18:12:36 +0100 | [diff] [blame] | 447 | |
| 448 | /* update pmic_power_module_info members */ |
| 449 | pmic_battery_read_status(pbi); |
| 450 | |
| 451 | switch (psp) { |
| 452 | case POWER_SUPPLY_PROP_STATUS: |
| 453 | val->intval = pbi->batt_status; |
| 454 | break; |
| 455 | case POWER_SUPPLY_PROP_HEALTH: |
| 456 | val->intval = pbi->batt_health; |
| 457 | break; |
| 458 | case POWER_SUPPLY_PROP_PRESENT: |
| 459 | val->intval = pbi->batt_is_present; |
| 460 | break; |
| 461 | case POWER_SUPPLY_PROP_CHARGE_NOW: |
| 462 | val->intval = mAStouAh(pbi->batt_charge_now); |
| 463 | break; |
| 464 | case POWER_SUPPLY_PROP_CHARGE_FULL: |
| 465 | val->intval = mAStouAh(pbi->batt_prev_charge_full); |
| 466 | break; |
| 467 | default: |
| 468 | return -EINVAL; |
| 469 | } |
| 470 | |
| 471 | return 0; |
| 472 | } |
| 473 | |
| 474 | /** |
| 475 | * pmic_battery_monitor - monitor battery status |
| 476 | * @work: work structure |
| 477 | * Context: can sleep |
| 478 | * |
| 479 | * PMIC battery status needs to be monitored for any change |
| 480 | * and information needs to be frequently updated. |
| 481 | */ |
| 482 | static void pmic_battery_monitor(struct work_struct *work) |
| 483 | { |
| 484 | struct pmic_power_module_info *pbi = container_of(work, |
| 485 | struct pmic_power_module_info, monitor_battery.work); |
| 486 | |
| 487 | /* update pmic_power_module_info members */ |
| 488 | pmic_battery_read_status(pbi); |
| 489 | queue_delayed_work(pbi->monitor_wqueue, &pbi->monitor_battery, HZ * 10); |
| 490 | } |
| 491 | |
| 492 | /** |
| 493 | * pmic_battery_set_charger - set battery charger |
| 494 | * @pbi: device info structure |
| 495 | * @chrg: charge mode to set battery charger in |
| 496 | * Context: can sleep |
| 497 | * |
| 498 | * PMIC battery charger needs to be enabled based on the usb charge |
| 499 | * capabilities connected to the platform. |
| 500 | */ |
| 501 | static int pmic_battery_set_charger(struct pmic_power_module_info *pbi, |
| 502 | enum batt_charge_type chrg) |
| 503 | { |
| 504 | int retval; |
| 505 | |
| 506 | /* set usblmt bits and chrgcntl register bits appropriately */ |
| 507 | switch (chrg) { |
| 508 | case BATT_USBOTG_500MA_CHARGE: |
| 509 | retval = pmic_scu_ipc_set_charger(PMIC_BATT_CHR_IPC_FCHRG_SUBID); |
| 510 | break; |
| 511 | case BATT_USBOTG_TRICKLE_CHARGE: |
| 512 | retval = pmic_scu_ipc_set_charger(PMIC_BATT_CHR_IPC_TCHRG_SUBID); |
| 513 | break; |
| 514 | default: |
| 515 | dev_warn(pbi->dev, "%s(): out of range usb charger " |
| 516 | "charge detected\n", __func__); |
| 517 | return -EINVAL; |
| 518 | } |
| 519 | |
| 520 | if (retval) { |
| 521 | dev_warn(pbi->dev, "%s(): ipc pmic read failed\n", |
| 522 | __func__); |
Justin P. Mattock | 6eab04a | 2011-04-08 19:49:08 -0700 | [diff] [blame] | 523 | return retval; |
Nithish Mahalingam | 6721081 | 2010-06-17 18:12:36 +0100 | [diff] [blame] | 524 | } |
| 525 | |
| 526 | return 0; |
| 527 | } |
| 528 | |
| 529 | /** |
| 530 | * pmic_battery_interrupt_handler - pmic battery interrupt handler |
| 531 | * Context: interrupt context |
| 532 | * |
| 533 | * PMIC battery interrupt handler which will be called with either |
| 534 | * battery full condition occurs or usb otg & battery connect |
| 535 | * condition occurs. |
| 536 | */ |
| 537 | static irqreturn_t pmic_battery_interrupt_handler(int id, void *dev) |
| 538 | { |
| 539 | struct pmic_power_module_info *pbi = dev; |
| 540 | |
| 541 | schedule_work(&pbi->handler); |
| 542 | |
| 543 | return IRQ_HANDLED; |
| 544 | } |
| 545 | |
| 546 | /** |
| 547 | * pmic_battery_handle_intrpt - pmic battery service interrupt |
| 548 | * @work: work structure |
| 549 | * Context: can sleep |
| 550 | * |
| 551 | * PMIC battery needs to either update the battery status as full |
| 552 | * if it detects battery full condition caused the interrupt or needs |
| 553 | * to enable battery charger if it detects usb and battery detect |
| 554 | * caused the source of interrupt. |
| 555 | */ |
| 556 | static void pmic_battery_handle_intrpt(struct work_struct *work) |
| 557 | { |
| 558 | struct pmic_power_module_info *pbi = container_of(work, |
| 559 | struct pmic_power_module_info, handler); |
| 560 | enum batt_charge_type chrg; |
| 561 | u8 r8; |
| 562 | |
| 563 | if (intel_scu_ipc_ioread8(PMIC_BATT_CHR_SCHRGINT_ADDR, &r8)) { |
| 564 | dev_warn(pbi->dev, "%s(): ipc pmic read failed\n", |
| 565 | __func__); |
| 566 | return; |
| 567 | } |
| 568 | /* find the cause of the interrupt */ |
| 569 | if (r8 & PMIC_BATT_CHR_SBATDET_MASK) { |
| 570 | pbi->batt_is_present = PMIC_BATT_PRESENT; |
| 571 | } else { |
| 572 | pbi->batt_is_present = PMIC_BATT_NOT_PRESENT; |
| 573 | pbi->batt_health = POWER_SUPPLY_HEALTH_UNKNOWN; |
| 574 | pbi->batt_status = POWER_SUPPLY_STATUS_UNKNOWN; |
| 575 | return; |
| 576 | } |
| 577 | |
| 578 | if (r8 & PMIC_BATT_CHR_EXCPT_MASK) { |
| 579 | pbi->batt_health = POWER_SUPPLY_HEALTH_UNKNOWN; |
| 580 | pbi->batt_status = POWER_SUPPLY_STATUS_NOT_CHARGING; |
| 581 | pbi->usb_health = POWER_SUPPLY_HEALTH_UNKNOWN; |
| 582 | pmic_battery_log_event(BATT_EVENT_EXCPT); |
| 583 | return; |
| 584 | } else { |
| 585 | pbi->batt_health = POWER_SUPPLY_HEALTH_GOOD; |
| 586 | pbi->usb_health = POWER_SUPPLY_HEALTH_GOOD; |
| 587 | } |
| 588 | |
| 589 | if (r8 & PMIC_BATT_CHR_SCOMP_MASK) { |
| 590 | u32 ccval; |
| 591 | pbi->batt_status = POWER_SUPPLY_STATUS_FULL; |
| 592 | |
| 593 | if (pmic_scu_ipc_battery_cc_read(&ccval)) { |
| 594 | dev_warn(pbi->dev, "%s(): ipc config cmd " |
| 595 | "failed\n", __func__); |
| 596 | return; |
| 597 | } |
| 598 | pbi->batt_prev_charge_full = ccval & |
| 599 | PMIC_BATT_ADC_ACCCHRGVAL_MASK; |
| 600 | return; |
| 601 | } |
| 602 | |
| 603 | if (r8 & PMIC_BATT_CHR_SUSBDET_MASK) { |
| 604 | pbi->usb_is_present = PMIC_USB_PRESENT; |
| 605 | } else { |
| 606 | pbi->usb_is_present = PMIC_USB_NOT_PRESENT; |
| 607 | pbi->usb_health = POWER_SUPPLY_HEALTH_UNKNOWN; |
| 608 | return; |
| 609 | } |
| 610 | |
| 611 | /* setup battery charging */ |
| 612 | |
| 613 | #if 0 |
| 614 | /* check usb otg power capability and set charger accordingly */ |
| 615 | retval = langwell_udc_maxpower(&power); |
| 616 | if (retval) { |
| 617 | dev_warn(pbi->dev, |
| 618 | "%s(): usb otg power query failed with error code %d\n", |
| 619 | __func__, retval); |
| 620 | return; |
| 621 | } |
| 622 | |
| 623 | if (power >= 500) |
| 624 | chrg = BATT_USBOTG_500MA_CHARGE; |
| 625 | else |
| 626 | #endif |
| 627 | chrg = BATT_USBOTG_TRICKLE_CHARGE; |
| 628 | |
| 629 | /* enable battery charging */ |
| 630 | if (pmic_battery_set_charger(pbi, chrg)) { |
| 631 | dev_warn(pbi->dev, |
| 632 | "%s(): failed to set up battery charging\n", __func__); |
| 633 | return; |
| 634 | } |
| 635 | |
| 636 | dev_dbg(pbi->dev, |
| 637 | "pmic-battery: %s() - setting up battery charger successful\n", |
| 638 | __func__); |
| 639 | } |
| 640 | |
Krzysztof Kozlowski | 297d716 | 2015-03-12 08:44:11 +0100 | [diff] [blame] | 641 | /* |
| 642 | * Description of power supplies |
| 643 | */ |
| 644 | static const struct power_supply_desc pmic_usb_desc = { |
| 645 | .name = "pmic-usb", |
| 646 | .type = POWER_SUPPLY_TYPE_USB, |
| 647 | .properties = pmic_usb_props, |
| 648 | .num_properties = ARRAY_SIZE(pmic_usb_props), |
| 649 | .get_property = pmic_usb_get_property, |
| 650 | }; |
| 651 | |
| 652 | static const struct power_supply_desc pmic_batt_desc = { |
| 653 | .name = "pmic-batt", |
| 654 | .type = POWER_SUPPLY_TYPE_BATTERY, |
| 655 | .properties = pmic_battery_props, |
| 656 | .num_properties = ARRAY_SIZE(pmic_battery_props), |
| 657 | .get_property = pmic_battery_get_property, |
| 658 | }; |
| 659 | |
Nithish Mahalingam | 6721081 | 2010-06-17 18:12:36 +0100 | [diff] [blame] | 660 | /** |
| 661 | * pmic_battery_probe - pmic battery initialize |
| 662 | * @irq: pmic battery device irq |
| 663 | * @dev: pmic battery device structure |
| 664 | * Context: can sleep |
| 665 | * |
| 666 | * PMIC battery initializes its internal data structue and other |
| 667 | * infrastructure components for it to work as expected. |
| 668 | */ |
Bill Pemberton | c8afa64 | 2012-11-19 13:22:23 -0500 | [diff] [blame] | 669 | static int probe(int irq, struct device *dev) |
Nithish Mahalingam | 6721081 | 2010-06-17 18:12:36 +0100 | [diff] [blame] | 670 | { |
| 671 | int retval = 0; |
| 672 | struct pmic_power_module_info *pbi; |
Krzysztof Kozlowski | 297d716 | 2015-03-12 08:44:11 +0100 | [diff] [blame] | 673 | struct power_supply_config psy_cfg = {}; |
Nithish Mahalingam | 6721081 | 2010-06-17 18:12:36 +0100 | [diff] [blame] | 674 | |
| 675 | dev_dbg(dev, "pmic-battery: found pmic battery device\n"); |
| 676 | |
| 677 | pbi = kzalloc(sizeof(*pbi), GFP_KERNEL); |
| 678 | if (!pbi) { |
| 679 | dev_err(dev, "%s(): memory allocation failed\n", |
| 680 | __func__); |
| 681 | return -ENOMEM; |
| 682 | } |
| 683 | |
| 684 | pbi->dev = dev; |
| 685 | pbi->irq = irq; |
| 686 | dev_set_drvdata(dev, pbi); |
Krzysztof Kozlowski | 297d716 | 2015-03-12 08:44:11 +0100 | [diff] [blame] | 687 | psy_cfg.drv_data = pbi; |
Nithish Mahalingam | 6721081 | 2010-06-17 18:12:36 +0100 | [diff] [blame] | 688 | |
| 689 | /* initialize all required framework before enabling interrupts */ |
| 690 | INIT_WORK(&pbi->handler, pmic_battery_handle_intrpt); |
| 691 | INIT_DELAYED_WORK(&pbi->monitor_battery, pmic_battery_monitor); |
| 692 | pbi->monitor_wqueue = |
| 693 | create_singlethread_workqueue(dev_name(dev)); |
| 694 | if (!pbi->monitor_wqueue) { |
| 695 | dev_err(dev, "%s(): wqueue init failed\n", __func__); |
| 696 | retval = -ESRCH; |
| 697 | goto wqueue_failed; |
| 698 | } |
| 699 | |
| 700 | /* register interrupt */ |
| 701 | retval = request_irq(pbi->irq, pmic_battery_interrupt_handler, |
| 702 | 0, DRIVER_NAME, pbi); |
| 703 | if (retval) { |
| 704 | dev_err(dev, "%s(): cannot get IRQ\n", __func__); |
| 705 | goto requestirq_failed; |
| 706 | } |
| 707 | |
| 708 | /* register pmic-batt with power supply subsystem */ |
Krzysztof Kozlowski | 297d716 | 2015-03-12 08:44:11 +0100 | [diff] [blame] | 709 | pbi->batt = power_supply_register(dev, &pmic_usb_desc, &psy_cfg); |
| 710 | if (IS_ERR(pbi->batt)) { |
Nithish Mahalingam | 6721081 | 2010-06-17 18:12:36 +0100 | [diff] [blame] | 711 | dev_err(dev, |
| 712 | "%s(): failed to register pmic battery device with power supply subsystem\n", |
| 713 | __func__); |
Krzysztof Kozlowski | 297d716 | 2015-03-12 08:44:11 +0100 | [diff] [blame] | 714 | retval = PTR_ERR(pbi->batt); |
Nithish Mahalingam | 6721081 | 2010-06-17 18:12:36 +0100 | [diff] [blame] | 715 | goto power_reg_failed; |
| 716 | } |
| 717 | |
| 718 | dev_dbg(dev, "pmic-battery: %s() - pmic battery device " |
| 719 | "registration with power supply subsystem successful\n", |
| 720 | __func__); |
| 721 | |
| 722 | queue_delayed_work(pbi->monitor_wqueue, &pbi->monitor_battery, HZ * 1); |
| 723 | |
| 724 | /* register pmic-usb with power supply subsystem */ |
Krzysztof Kozlowski | 297d716 | 2015-03-12 08:44:11 +0100 | [diff] [blame] | 725 | pbi->usb = power_supply_register(dev, &pmic_batt_desc, &psy_cfg); |
| 726 | if (IS_ERR(pbi->usb)) { |
Nithish Mahalingam | 6721081 | 2010-06-17 18:12:36 +0100 | [diff] [blame] | 727 | dev_err(dev, |
| 728 | "%s(): failed to register pmic usb device with power supply subsystem\n", |
| 729 | __func__); |
Krzysztof Kozlowski | 297d716 | 2015-03-12 08:44:11 +0100 | [diff] [blame] | 730 | retval = PTR_ERR(pbi->usb); |
Nithish Mahalingam | 6721081 | 2010-06-17 18:12:36 +0100 | [diff] [blame] | 731 | goto power_reg_failed_1; |
| 732 | } |
| 733 | |
| 734 | if (debug) |
| 735 | printk(KERN_INFO "pmic-battery: %s() - pmic usb device " |
| 736 | "registration with power supply subsystem successful\n", |
| 737 | __func__); |
| 738 | |
| 739 | return retval; |
| 740 | |
| 741 | power_reg_failed_1: |
Krzysztof Kozlowski | 297d716 | 2015-03-12 08:44:11 +0100 | [diff] [blame] | 742 | power_supply_unregister(pbi->batt); |
Nithish Mahalingam | 6721081 | 2010-06-17 18:12:36 +0100 | [diff] [blame] | 743 | power_reg_failed: |
Tejun Heo | afe2c51 | 2010-12-14 16:21:17 +0100 | [diff] [blame] | 744 | cancel_delayed_work_sync(&pbi->monitor_battery); |
Nithish Mahalingam | 6721081 | 2010-06-17 18:12:36 +0100 | [diff] [blame] | 745 | requestirq_failed: |
| 746 | destroy_workqueue(pbi->monitor_wqueue); |
| 747 | wqueue_failed: |
| 748 | kfree(pbi); |
| 749 | |
| 750 | return retval; |
| 751 | } |
| 752 | |
Bill Pemberton | c8afa64 | 2012-11-19 13:22:23 -0500 | [diff] [blame] | 753 | static int platform_pmic_battery_probe(struct platform_device *pdev) |
Nithish Mahalingam | 6721081 | 2010-06-17 18:12:36 +0100 | [diff] [blame] | 754 | { |
| 755 | return probe(pdev->id, &pdev->dev); |
| 756 | } |
| 757 | |
| 758 | /** |
| 759 | * pmic_battery_remove - pmic battery finalize |
| 760 | * @dev: pmic battery device structure |
| 761 | * Context: can sleep |
| 762 | * |
| 763 | * PMIC battery finalizes its internal data structue and other |
| 764 | * infrastructure components that it initialized in |
| 765 | * pmic_battery_probe. |
| 766 | */ |
| 767 | |
Bill Pemberton | 415ec69 | 2012-11-19 13:26:07 -0500 | [diff] [blame] | 768 | static int platform_pmic_battery_remove(struct platform_device *pdev) |
Nithish Mahalingam | 6721081 | 2010-06-17 18:12:36 +0100 | [diff] [blame] | 769 | { |
Jingoo Han | e083587 | 2013-05-23 19:34:39 +0900 | [diff] [blame] | 770 | struct pmic_power_module_info *pbi = platform_get_drvdata(pdev); |
Nithish Mahalingam | 6721081 | 2010-06-17 18:12:36 +0100 | [diff] [blame] | 771 | |
| 772 | free_irq(pbi->irq, pbi); |
Tejun Heo | afe2c51 | 2010-12-14 16:21:17 +0100 | [diff] [blame] | 773 | cancel_delayed_work_sync(&pbi->monitor_battery); |
Nithish Mahalingam | 6721081 | 2010-06-17 18:12:36 +0100 | [diff] [blame] | 774 | destroy_workqueue(pbi->monitor_wqueue); |
| 775 | |
Krzysztof Kozlowski | 297d716 | 2015-03-12 08:44:11 +0100 | [diff] [blame] | 776 | power_supply_unregister(pbi->usb); |
| 777 | power_supply_unregister(pbi->batt); |
Nithish Mahalingam | 6721081 | 2010-06-17 18:12:36 +0100 | [diff] [blame] | 778 | |
Tejun Heo | bc51e7f | 2010-12-11 17:51:45 +0100 | [diff] [blame] | 779 | cancel_work_sync(&pbi->handler); |
Nithish Mahalingam | 6721081 | 2010-06-17 18:12:36 +0100 | [diff] [blame] | 780 | kfree(pbi); |
| 781 | return 0; |
| 782 | } |
| 783 | |
| 784 | static struct platform_driver platform_pmic_battery_driver = { |
| 785 | .driver = { |
| 786 | .name = DRIVER_NAME, |
Nithish Mahalingam | 6721081 | 2010-06-17 18:12:36 +0100 | [diff] [blame] | 787 | }, |
| 788 | .probe = platform_pmic_battery_probe, |
Bill Pemberton | 28ea73f | 2012-11-19 13:20:40 -0500 | [diff] [blame] | 789 | .remove = platform_pmic_battery_remove, |
Nithish Mahalingam | 6721081 | 2010-06-17 18:12:36 +0100 | [diff] [blame] | 790 | }; |
| 791 | |
Axel Lin | 300bac7 | 2011-11-26 12:01:10 +0800 | [diff] [blame] | 792 | module_platform_driver(platform_pmic_battery_driver); |
Nithish Mahalingam | 6721081 | 2010-06-17 18:12:36 +0100 | [diff] [blame] | 793 | |
| 794 | MODULE_AUTHOR("Nithish Mahalingam <nithish.mahalingam@intel.com>"); |
| 795 | MODULE_DESCRIPTION("Intel Moorestown PMIC Battery Driver"); |
| 796 | MODULE_LICENSE("GPL"); |