| /* |
| * byt_ec_battery.c - Baytrail EC based battery driver |
| * |
| * Copyright (C) 2013 Intel Corporation |
| * |
| * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| * |
| * 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; version 2 of the License. |
| * |
| * 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. |
| * |
| * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| * Author: Ramakrishna Pallala <ramakrishna.pallala@intel.com> |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/kernel.h> |
| #include <linux/jiffies.h> |
| #include <linux/io.h> |
| #include <linux/slab.h> |
| #include <linux/pm_runtime.h> |
| #include <linux/interrupt.h> |
| #include <linux/device.h> |
| #include <linux/platform_device.h> |
| #include <linux/delay.h> |
| #include <linux/notifier.h> |
| #include <linux/workqueue.h> |
| #include <linux/power_supply.h> |
| #include <linux/acpi.h> |
| #include <asm/intel_byt_ec.h> |
| |
| /* 8 bit registers and offsets */ |
| #define EC_MAX_PLAT_TEMP_REG 1 |
| #define EC_SENSOR_TEMP_REG 2 |
| #define EC_REAL_AC_PWR_REG 3 |
| #define REAL_AC_PWR_ON 0x01 |
| |
| #define EC_CRIT_TEMP_REG 47 |
| #define EC_VIRT_AC_PWR_REG 48 |
| #define EC_BAT0_STAT_REG 50 |
| #define BAT0_STAT_DISCHARGING (1 << 0) |
| #define BAT0_STAT_CHARGING (1 << 1) |
| #define BAT0_STAT_LOW_BATT (1 << 2) |
| #define BAT0_STAT_BATT_PRESENT (1 << 3) |
| #define EC_BAT0_CUR_RATE_REG 51 |
| #define EC_BAT0_CUR_CAP_REG 52 |
| #define EC_BAT0_VOLT_REG 53 |
| |
| /* 16 bit registers and offsets */ |
| #define EC_BAT0_DESIGN_CAP_REG 87 |
| #define EC_BAT0_REM_CAP_REG 89 |
| #define EC_BAT0_FULL_CHRG_CAP_REG 91 |
| #define EC_BAT0_VBATT_REG 93 |
| #define EC_BAT0_DIBATT_CUR_REG 95 |
| #define EC_BAT0_CIBATT_CUR_REG 97 |
| #define EC_BAT0_BATTSBS_STATUS 202 |
| #define BAT0_BATTSBS_STATUS_OTP (1 << 12) |
| #define BAT0_BATTSBS_STATUS_FC (1 << 5) |
| #define BAT0_BATTSBS_STATUS_INIT (1 << 7) |
| #define EC_BAT0_TEMP_REG 206 |
| #define EC_BAT0_AVG_CUR_REG 208 |
| |
| /* 6% is minimun threshold for platform shutdown*/ |
| #define EC_BAT_SAFE_MIN_CAPACITY 6 |
| #define EC_BAT_DEAD_VOLTAGE 6550 /* Dead = 6.55V */ |
| |
| /* Battery temperature related macros*/ |
| #define EC_BAT_TEMP_CONV_FACTOR 27315 /* K = C + 273.15*/ |
| /* Battery operating temperature range is 0C to 40C while |
| * charging and -20 C to 60C while Discharging/Full/Not Chraging. |
| */ |
| #define EC_BAT_CHRG_OVER_TEMP 3131 /* 313.1K ~ 40 C */ |
| #define EC_BAT_CHRG_UNDER_TEMP 2731 /* 273.1K ~ 0 C */ |
| |
| |
| struct ec_battery_info { |
| struct platform_device *pdev; |
| struct power_supply bat; |
| struct power_supply chrg; |
| struct mutex lock; |
| struct notifier_block nb; |
| }; |
| |
| static enum power_supply_property ec_battery_props[] = { |
| POWER_SUPPLY_PROP_STATUS, |
| POWER_SUPPLY_PROP_PRESENT, |
| POWER_SUPPLY_PROP_HEALTH, |
| POWER_SUPPLY_PROP_VOLTAGE_NOW, |
| POWER_SUPPLY_PROP_CURRENT_NOW, |
| POWER_SUPPLY_PROP_CURRENT_AVG, |
| POWER_SUPPLY_PROP_CAPACITY, |
| POWER_SUPPLY_PROP_TEMP, |
| POWER_SUPPLY_PROP_TECHNOLOGY, |
| POWER_SUPPLY_PROP_CHARGE_FULL, |
| POWER_SUPPLY_PROP_CHARGE_NOW, |
| POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, |
| }; |
| |
| static enum power_supply_property ec_charger_properties[] = { |
| POWER_SUPPLY_PROP_ONLINE, |
| POWER_SUPPLY_PROP_PRESENT, |
| POWER_SUPPLY_PROP_TYPE, |
| POWER_SUPPLY_PROP_HEALTH, |
| POWER_SUPPLY_PROP_MODEL_NAME, |
| POWER_SUPPLY_PROP_MANUFACTURER, |
| }; |
| |
| static short adjust_sign_value(int value) |
| { |
| short result, temp = (short)value; |
| |
| /* check if sign bit is set */ |
| if (temp & 0x8000) { |
| result = ~temp; |
| result++; |
| result *= -1; |
| } else { |
| result = temp; |
| } |
| |
| return result; |
| } |
| |
| static int ec_get_battery_property(struct power_supply *psy, |
| enum power_supply_property psp, |
| union power_supply_propval *val) |
| { |
| struct ec_battery_info *chip = container_of(psy, |
| struct ec_battery_info, bat); |
| int ret = 0, cur_sign = -1; |
| int comp_cap = 0; |
| u8 val8, cap; |
| u16 val16; |
| |
| mutex_lock(&chip->lock); |
| switch (psp) { |
| case POWER_SUPPLY_PROP_STATUS: |
| ret = byt_ec_read_byte(EC_BAT0_CUR_CAP_REG, &cap); |
| if (ret < 0) |
| goto ec_read_err; |
| |
| ret = byt_ec_read_byte(EC_BAT0_STAT_REG, &val8); |
| if (ret < 0) |
| goto ec_read_err; |
| |
| ret = byt_ec_read_word(EC_BAT0_BATTSBS_STATUS, &val16); |
| if (ret < 0) |
| goto ec_read_err; |
| |
| if (val8 & BAT0_STAT_DISCHARGING) |
| val->intval = POWER_SUPPLY_STATUS_DISCHARGING; |
| else if (val8 & BAT0_STAT_CHARGING) |
| val->intval = POWER_SUPPLY_STATUS_CHARGING; |
| else if (val8 & BAT0_STAT_LOW_BATT) |
| val->intval = POWER_SUPPLY_STATUS_DISCHARGING; |
| else if ((cap == 100) || (val16 & BAT0_BATTSBS_STATUS_FC)) |
| val->intval = POWER_SUPPLY_STATUS_FULL; |
| else if (cap != 0) |
| val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; |
| else |
| val->intval = POWER_SUPPLY_STATUS_UNKNOWN; |
| break; |
| case POWER_SUPPLY_PROP_HEALTH: |
| ret = byt_ec_read_byte(EC_BAT0_STAT_REG, &val8); |
| if (ret < 0) |
| goto ec_read_err; |
| /* If battery is not connected |
| * return POWER_SUPPLY_HEALTH_UNKNOWN |
| */ |
| if (!(val8 & BAT0_STAT_BATT_PRESENT)) { |
| val->intval = POWER_SUPPLY_HEALTH_UNKNOWN; |
| break; |
| } |
| |
| /* Battery operational temperature range is |
| * 0C to 40C while charging and -20 C to 60C |
| * while Discharging/Full/Not Charging. |
| */ |
| ret = byt_ec_read_word(EC_BAT0_TEMP_REG, &val16); |
| if (ret < 0) |
| goto ec_read_err; |
| |
| /* Android framwork don't differentiate b/w hot and cold. |
| *Both are treated as overheat cases. |
| *So report overheat in both high and low temp cases. |
| */ |
| if ((val16 >= EC_BAT_CHRG_OVER_TEMP) |
| | (val16 <= EC_BAT_CHRG_UNDER_TEMP)) { |
| val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; |
| break; |
| } |
| ret = byt_ec_read_word(EC_BAT0_VBATT_REG, &val16); |
| if (ret < 0) |
| goto ec_read_err; |
| /* The battery is treated as DEAD if the battery voltage is |
| *bellow <= 6.55V in BYT-M |
| */ |
| if (val16 <= EC_BAT_DEAD_VOLTAGE) { |
| val->intval = POWER_SUPPLY_HEALTH_DEAD; |
| break; |
| } |
| |
| val->intval = POWER_SUPPLY_HEALTH_GOOD; |
| break; |
| case POWER_SUPPLY_PROP_VOLTAGE_NOW: |
| ret = byt_ec_read_word(EC_BAT0_VBATT_REG, &val16); |
| if (ret < 0) |
| goto ec_read_err; |
| val->intval = val16 * 1000; |
| break; |
| case POWER_SUPPLY_PROP_CURRENT_NOW: |
| ret = byt_ec_read_byte(EC_BAT0_STAT_REG, &val8); |
| if (ret < 0) |
| goto ec_read_err; |
| |
| if (val8 & BAT0_STAT_CHARGING) { |
| ret = byt_ec_read_word(EC_BAT0_CIBATT_CUR_REG, &val16); |
| cur_sign = 1; |
| } else { |
| ret = byt_ec_read_word(EC_BAT0_DIBATT_CUR_REG, &val16); |
| cur_sign = -1; |
| } |
| if (ret < 0) |
| goto ec_read_err; |
| val->intval = cur_sign * ((int)val16 * 1000); |
| break; |
| case POWER_SUPPLY_PROP_CURRENT_AVG: |
| ret = byt_ec_read_word(EC_BAT0_AVG_CUR_REG, &val16); |
| if (ret < 0) |
| goto ec_read_err; |
| |
| val->intval = (int)adjust_sign_value(val16) * 1000; |
| break; |
| case POWER_SUPPLY_PROP_PRESENT: |
| ret = byt_ec_read_byte(EC_BAT0_CUR_CAP_REG, &cap); |
| if (ret < 0) |
| goto ec_read_err; |
| |
| ret = byt_ec_read_byte(EC_BAT0_STAT_REG, &val8); |
| if (ret < 0) |
| goto ec_read_err; |
| |
| if ((val8 & 0x7) || (cap != 0)) |
| val->intval = 1; |
| else |
| val->intval = 0; |
| break; |
| case POWER_SUPPLY_PROP_CAPACITY: |
| ret = byt_ec_read_byte(EC_BAT0_CUR_CAP_REG, &val8); |
| if (ret < 0) |
| goto ec_read_err; |
| /* 6% of battery capacity is minimun treshold for BYT-M |
| *So, the 6% is mapped to 0% in android. |
| * 6% to 100% is compensated with 0% to 100% to OS. |
| * Compensated capacity = cap - ((100 - cap)*6)/100 + 0.5 |
| */ |
| comp_cap = val8; |
| comp_cap = comp_cap*100 - ((100 - comp_cap) |
| *EC_BAT_SAFE_MIN_CAPACITY) + 50; |
| comp_cap /= 100; |
| if (comp_cap < 0) |
| comp_cap = 0; |
| val->intval = comp_cap; |
| break; |
| case POWER_SUPPLY_PROP_TEMP: |
| ret = byt_ec_read_word(EC_BAT0_TEMP_REG, &val16); |
| if (ret < 0) |
| goto ec_read_err; |
| /* |
| * convert temperature from degree kelvin |
| * to degree celsius: T(C) = T(K) - 273.15 |
| * also 1 LSB of Temp register = 0.1 Kelvin. |
| */ |
| val->intval = (((int)val16 * 10) - |
| EC_BAT_TEMP_CONV_FACTOR) / 10; |
| break; |
| case POWER_SUPPLY_PROP_TECHNOLOGY: |
| ret = byt_ec_read_byte(EC_BAT0_STAT_REG, &val8); |
| if (ret < 0) |
| goto ec_read_err; |
| /*If battery is not connected |
| *return POWER_SUPPLY_TECHNOLOGY_UNKNOWN |
| */ |
| if (val8 & BAT0_STAT_BATT_PRESENT) |
| val->intval = POWER_SUPPLY_TECHNOLOGY_LION; |
| else |
| val->intval = POWER_SUPPLY_HEALTH_UNKNOWN; |
| break; |
| case POWER_SUPPLY_PROP_CHARGE_NOW: |
| ret = byt_ec_read_word(EC_BAT0_REM_CAP_REG, &val16); |
| if (ret < 0) |
| goto ec_read_err; |
| val->intval = ((int)val16) * 1000; |
| break; |
| case POWER_SUPPLY_PROP_CHARGE_FULL: |
| ret = byt_ec_read_word(EC_BAT0_FULL_CHRG_CAP_REG, &val16); |
| if (ret < 0) |
| goto ec_read_err; |
| val->intval = ((int)val16) * 1000; |
| break; |
| case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: |
| ret = byt_ec_read_word(EC_BAT0_DESIGN_CAP_REG, &val16); |
| if (ret < 0) |
| goto ec_read_err; |
| val->intval = ((int)val16) * 1000; |
| break; |
| default: |
| mutex_unlock(&chip->lock); |
| return -EINVAL; |
| } |
| |
| mutex_unlock(&chip->lock); |
| return 0; |
| |
| ec_read_err: |
| mutex_unlock(&chip->lock); |
| return ret; |
| } |
| |
| static int ec_get_charger_property(struct power_supply *psy, |
| enum power_supply_property psp, |
| union power_supply_propval *val) |
| { |
| struct ec_battery_info *chip = container_of(psy, |
| struct ec_battery_info, chrg); |
| int ret = 0; |
| int chrg_present = 0; |
| u8 data; |
| |
| mutex_lock(&chip->lock); |
| ret = byt_ec_read_byte(EC_REAL_AC_PWR_REG, &data); |
| if (ret < 0) |
| goto ec_read_err; |
| if (data & REAL_AC_PWR_ON) |
| chrg_present = 1; |
| /* As EC provides only charger present status, all other parameters |
| * are hardcoded as per requirements. |
| */ |
| switch (psp) { |
| case POWER_SUPPLY_PROP_ONLINE: |
| case POWER_SUPPLY_PROP_PRESENT: |
| |
| if (chrg_present) |
| val->intval = 1; |
| else |
| val->intval = 0; |
| break; |
| case POWER_SUPPLY_PROP_HEALTH: |
| if (chrg_present) |
| val->intval = POWER_SUPPLY_HEALTH_GOOD; |
| else |
| val->intval = POWER_SUPPLY_HEALTH_UNKNOWN; |
| break; |
| case POWER_SUPPLY_PROP_MODEL_NAME: |
| if (chrg_present) |
| val->strval = "INBYTM"; |
| else |
| val->strval = "Unknown"; |
| break; |
| case POWER_SUPPLY_PROP_MANUFACTURER: |
| if (chrg_present) |
| val->strval = "Intel"; |
| else |
| val->strval = "Unknown"; |
| break; |
| |
| default: |
| mutex_unlock(&chip->lock); |
| return -EINVAL; |
| } |
| mutex_unlock(&chip->lock); |
| return 0; |
| |
| ec_read_err: |
| mutex_unlock(&chip->lock); |
| return ret; |
| } |
| |
| static int byt_ec_evt_batt_callback(struct notifier_block *nb, |
| unsigned long event, void *data) |
| { |
| struct ec_battery_info *chip = container_of(nb, |
| struct ec_battery_info, nb); |
| int ret = NOTIFY_DONE; |
| bool chrg_uevent = false, batt_uevent = false; |
| |
| switch (event) { |
| case BYT_EC_SCI_ACINSERTION: |
| dev_info(&chip->pdev->dev, "Charger plug event\n"); |
| chrg_uevent = true; |
| break; |
| case BYT_EC_SCI_ACREMOVAL: |
| dev_info(&chip->pdev->dev, "Charger unplug event\n"); |
| chrg_uevent = true; |
| break; |
| case BYT_EC_SCI_BATTERY: |
| dev_info(&chip->pdev->dev, "Battery related event\n"); |
| batt_uevent = true; |
| break; |
| case BYT_EC_SCI_BATTERY_PRSNT: |
| dev_info(&chip->pdev->dev, "Battery plug/unplug event\n"); |
| batt_uevent = true; |
| break; |
| case BYT_EC_SCI_BATTERY_OTP: |
| /*Battery temp >40 is considered as over temperature*/ |
| dev_info(&chip->pdev->dev, "Battery over temp event\n"); |
| batt_uevent = true; |
| break; |
| case BYT_EC_SCI_BATTERY_OTP_CLR: |
| /*Battery temp <= 39 will clear over temperature*/ |
| dev_info(&chip->pdev->dev, "Battery over temp clear event\n"); |
| batt_uevent = true; |
| break; |
| case BYT_EC_SCI_BATTERY_ETP: |
| /*Battery temp > 60 is considered as extreme temperature*/ |
| dev_info(&chip->pdev->dev, "Battery extreme temp event\n"); |
| batt_uevent = true; |
| break; |
| case BYT_EC_SCI_BATTERY_ETP_CLR: |
| /*Battery temp <= 59 will clear extreme temperature*/ |
| dev_info(&chip->pdev->dev, "Battery extreme temp clear event\n"); |
| batt_uevent = true; |
| break; |
| default: |
| dev_dbg(&chip->pdev->dev, "not valid battery event\n"); |
| } |
| |
| if (chrg_uevent) { |
| power_supply_changed(&chip->chrg); |
| power_supply_changed(&chip->bat); |
| ret = NOTIFY_OK; |
| } else if (batt_uevent) { |
| power_supply_changed(&chip->bat); |
| ret = NOTIFY_OK; |
| } else { |
| ret = NOTIFY_DONE; |
| } |
| |
| return ret; |
| } |
| |
| static int ec_battery_probe(struct platform_device *pdev) |
| { |
| struct ec_battery_info *chip; |
| int ret; |
| |
| chip = kzalloc(sizeof(*chip), GFP_KERNEL); |
| if (!chip) { |
| dev_err(&pdev->dev, "mem alloc failed\n"); |
| return -ENOMEM; |
| } |
| |
| chip->pdev = pdev; |
| platform_set_drvdata(pdev, chip); |
| mutex_init(&chip->lock); |
| |
| chip->bat.name = "byt_battery"; |
| chip->bat.type = POWER_SUPPLY_TYPE_BATTERY; |
| chip->bat.properties = ec_battery_props; |
| chip->bat.num_properties = ARRAY_SIZE(ec_battery_props); |
| chip->bat.get_property = ec_get_battery_property; |
| ret = power_supply_register(&pdev->dev, &chip->bat); |
| if (ret) { |
| dev_err(&pdev->dev, "failed to register battery: %d\n", ret); |
| goto probe_failed_1; |
| } |
| |
| chip->chrg.name = "byt_charger"; |
| chip->chrg.type = POWER_SUPPLY_TYPE_MAINS; |
| chip->chrg.properties = ec_charger_properties; |
| chip->chrg.num_properties = ARRAY_SIZE(ec_charger_properties); |
| chip->chrg.get_property = ec_get_charger_property; |
| ret = power_supply_register(&pdev->dev, &chip->chrg); |
| if (ret) { |
| dev_err(&pdev->dev, "failed to register charger: %d\n", ret); |
| goto probe_failed_2; |
| } |
| |
| /* register for EC SCI events */ |
| chip->nb.notifier_call = &byt_ec_evt_batt_callback; |
| byt_ec_evt_register_notify(&chip->nb); |
| |
| return 0; |
| |
| probe_failed_2: |
| power_supply_unregister(&chip->bat); |
| probe_failed_1: |
| kfree(chip); |
| return ret; |
| } |
| |
| static int ec_battery_remove(struct platform_device *pdev) |
| { |
| struct ec_battery_info *chip = platform_get_drvdata(pdev); |
| |
| byt_ec_evt_unregister_notify(&chip->nb); |
| power_supply_unregister(&chip->chrg); |
| power_supply_unregister(&chip->bat); |
| kfree(chip); |
| return 0; |
| } |
| |
| static struct platform_driver ec_battery_driver = { |
| .probe = ec_battery_probe, |
| .remove = ec_battery_remove, |
| .driver = { |
| .name = "ec_battery", |
| }, |
| }; |
| |
| static int __init ec_battery_init(void) |
| { |
| int ret = 0; |
| |
| ret = platform_driver_register(&ec_battery_driver); |
| if (ret < 0) { |
| pr_err("platform driver reg failed %s\n", |
| "ec_battery"); |
| return ret; |
| } |
| return 0; |
| } |
| module_init(ec_battery_init); |
| |
| static void __exit ec_battery_exit(void) |
| { |
| platform_driver_unregister(&ec_battery_driver); |
| } |
| module_exit(ec_battery_exit); |
| |
| MODULE_AUTHOR("Ramakrishna Pallala <ramakrishna.pallala@intel.com>"); |
| MODULE_DESCRIPTION("Baytrail EC Battery Driver"); |
| MODULE_LICENSE("GPL"); |