--- /dev/null
+/*
+ * intel_mdf_battery.c - Intel Medfield MSIC Internal charger and Battery Driver
+ *
+ * Copyright (C) 2010 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: Ananth Krishna <ananth.krishna.r@intel.com>,
+ * Anantha Narayanan <anantha.narayanan@intel.com>
+ * Ramakrishna Pallala <ramakrishna.pallala@intel.com>
+ */
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
+#include <linux/jiffies.h>
+#include <linux/slab.h>
+#include <linux/kfifo.h>
+#include <linux/param.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/io.h>
+#include <linux/sched.h>
+#include <linux/pm_runtime.h>
+#include <linux/sfi.h>
+#include <linux/wakelock.h>
+#include <linux/async.h>
+#include <linux/reboot.h>
+
+#include <asm/intel_scu_ipc.h>
+#include <linux/usb/penwell_otg.h>
+#include <linux/power/intel_mdf_battery.h>
+#include <asm/intel_mid_gpadc.h>
+
+#include "intel_mdf_charger.h"
+
+#define DRIVER_NAME "intel_mdf_battery"
+#define CHARGER_PS_NAME "msic_charger"
+
+#define SFI_SIG_OEM0 "OEM0"
+#define IRQ_KFIFO_ELEMENT 1
+
+
+static void *otg_handle;
+static struct device *msic_dev;
+static struct power_supply *fg_psy;
+
+static char *msic_power_supplied_to[] = {
+ "msic_battery",
+ "max17042_battery",
+};
+
+static unsigned long long adc_ttl;
+static int adc_sensor_vals[MSIC_BATT_SENSORS];
+
+/*
+ * This array represents the Battery Pack thermistor
+ * temperature and corresponding ADC value limits
+ */
+static int const therm_curve_data[THERM_CURVE_MAX_SAMPLES]
+ [THERM_CURVE_MAX_VALUES] = {
+ /* {temp_max, temp_min, adc_max, adc_min} */
+ {-15, -20, 977, 961},
+ {-10, -15, 961, 941},
+ {-5, -10, 941, 917},
+ {0, -5, 917, 887},
+ {5, 0, 887, 853},
+ {10, 5, 853, 813},
+ {15, 10, 813, 769},
+ {20, 15, 769, 720},
+ {25, 20, 720, 669},
+ {30, 25, 669, 615},
+ {35, 30, 615, 561},
+ {40, 35, 561, 508},
+ {45, 40, 508, 456},
+ {50, 45, 456, 407},
+ {55, 50, 407, 357},
+ {60, 55, 357, 315},
+ {65, 60, 315, 277},
+ {70, 65, 277, 243},
+ {75, 70, 243, 212},
+ {80, 75, 212, 186},
+ {85, 80, 186, 162},
+ {90, 85, 162, 140},
+ {100, 90, 140, 107},
+};
+
+
+static struct batt_safety_thresholds *batt_thrshlds;
+
+
+static struct msic_batt_sfi_prop *sfi_table;
+
+static int error_count;
+
+
+/*
+ * All interrupt request are queued from interrupt
+ * handler and processed in the bottom half
+ */
+static DEFINE_KFIFO(irq_fifo, u32, IRQ_FIFO_MAX);
+
+
+/* Sysfs Entry for enable or disable Charging from user space */
+static ssize_t set_chrg_enable(struct device *device,
+ struct device_attribute *attr, const char *buf,
+ size_t count);
+static ssize_t get_chrg_enable(struct device *device,
+ struct device_attribute *attr, char *buf);
+static DEVICE_ATTR(charge_enable, S_IRUGO | S_IWUSR, get_chrg_enable,
+ set_chrg_enable);
+
+/* Sysfs Entry to show if lab power supply is used */
+static ssize_t get_is_power_supply_conn(struct device *device,
+ struct device_attribute *attr, char *buf);
+static DEVICE_ATTR(power_supply_conn, S_IRUGO, get_is_power_supply_conn, NULL);
+
+/*
+ * msic usb properties
+ */
+static enum power_supply_property msic_usb_props[] = {
+ POWER_SUPPLY_PROP_TYPE,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_MANUFACTURER,
+};
+
+static int battery_reboot_notifier_callback(struct notifier_block *notifier,
+ unsigned long event, void *data);
+
+static struct notifier_block battery_reboot_notifier = {
+ .notifier_call = battery_reboot_notifier_callback,
+};
+
+/**
+ * check_batt_psy -check for whether power supply type is battery
+ * @dev : Power Supply dev structure
+ * @data : Power Supply Driver Data
+ * Context: can sleep
+ *
+ * Return true if power supply type is battery
+ *
+ */
+static int check_batt_psy(struct device *dev, void *data)
+{
+ struct power_supply *psy = dev_get_drvdata(dev);
+
+ /* check for whether power supply type is battery */
+ if (psy->type == POWER_SUPPLY_TYPE_BATTERY) {
+ dev_info(msic_dev, "fg chip found:%s\n", psy->name);
+ fg_psy = psy;
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * get_fg_chip_psy - identify the Fuel Gauge Power Supply device
+ * Context: can sleep
+ *
+ * Return Fuel Gauge power supply structure
+ */
+static struct power_supply *get_fg_chip_psy(void)
+{
+ if (fg_psy)
+ return fg_psy;
+
+ /* loop through power supply class */
+ class_for_each_device(power_supply_class, NULL, NULL,
+ check_batt_psy);
+ return fg_psy;
+}
+
+/**
+ * fg_chip_get_property - read a power supply property from Fuel Gauge driver
+ * @psp : Power Supply property
+ *
+ * Return power supply property value
+ *
+ */
+static int fg_chip_get_property(enum power_supply_property psp)
+{
+ union power_supply_propval val;
+ int ret = -ENODEV;
+
+ if (!fg_psy)
+ fg_psy = get_fg_chip_psy();
+ if (fg_psy) {
+ ret = fg_psy->get_property(fg_psy, psp, &val);
+ if (!ret)
+ return val.intval;
+ }
+
+ return ret;
+}
+
+/* Exported Functions to use with Fuel Gauge driver */
+
+int intel_msic_is_current_sense_enabled(void)
+{
+ struct platform_device *pdev = container_of(msic_dev,
+ struct platform_device, dev);
+ struct msic_power_module_info *mbi = platform_get_drvdata(pdev);
+ int val;
+
+ mutex_lock(&mbi->batt_lock);
+ val = mbi->current_sense_enabled;
+ mutex_unlock(&mbi->batt_lock);
+
+ return val;
+}
+EXPORT_SYMBOL(intel_msic_is_current_sense_enabled);
+
+int intel_msic_check_battery_present(void)
+{
+ struct platform_device *pdev = container_of(msic_dev,
+ struct platform_device, dev);
+ struct msic_power_module_info *mbi = platform_get_drvdata(pdev);
+ int val;
+
+ mutex_lock(&mbi->batt_lock);
+ val = mbi->batt_props.present;
+ mutex_unlock(&mbi->batt_lock);
+
+ return val;
+}
+EXPORT_SYMBOL(intel_msic_check_battery_present);
+
+int intel_msic_check_battery_health(void)
+{
+ struct platform_device *pdev = container_of(msic_dev,
+ struct platform_device, dev);
+ struct msic_power_module_info *mbi = platform_get_drvdata(pdev);
+ int val;
+
+ mutex_lock(&mbi->batt_lock);
+ val = mbi->batt_props.health;
+ mutex_unlock(&mbi->batt_lock);
+
+ return val;
+}
+EXPORT_SYMBOL(intel_msic_check_battery_health);
+
+int intel_msic_check_battery_status(void)
+{
+ struct platform_device *pdev = container_of(msic_dev,
+ struct platform_device, dev);
+ struct msic_power_module_info *mbi = platform_get_drvdata(pdev);
+ int val;
+
+ mutex_lock(&mbi->batt_lock);
+ val = mbi->batt_props.status;
+ mutex_unlock(&mbi->batt_lock);
+
+ return val;
+}
+EXPORT_SYMBOL(intel_msic_check_battery_status);
+
+static int is_protected_regs(u16 addr)
+{
+ /* in unsigned kernel mode write to some registers are blocked */
+ if (addr == MSIC_BATT_CHR_CHRCVOLTAGE_ADDR ||
+ addr == MSIC_BATT_CHR_CHRCCURRENT_ADDR ||
+ addr == MSIC_BATT_CHR_PWRSRCLMT_ADDR ||
+ addr == MSIC_BATT_CHR_CHRCTRL1_ADDR)
+ return true;
+ else
+ return false;
+}
+
+/**
+ * handle_ipc_rw_status - handle msic ipc read/write status
+ * @error_val : ipc read/write status
+ * @address : msic register address
+ * @rw : read/write access
+ *
+ * If the error count is more than MAX_IPC_ERROR_COUNT, report
+ * charger and battery health as POWER_SUPPLY_HEALTH_UNSPEC_FAILURE
+ *
+ * returns error value in case of error else return 0
+ */
+
+static inline int handle_ipc_rw_status(int error_val,
+ const u16 address, char rw)
+{
+
+ struct platform_device *pdev = container_of(msic_dev,
+ struct platform_device, dev);
+ struct msic_power_module_info *mbi = platform_get_drvdata(pdev);
+
+ /*
+ * Write to protected registers in unsigned kernel
+ * mode will return -EIO
+ */
+ /* Overwriting result value when failure is not a timeout, to support
+ * properly safety charging : we must ensure that when using
+ * an unsigned kernel, the failing access to protected registers
+ * (expected behaviour, returning -EIO)) will not block the accesses
+ * to the non protected registers.
+ * */
+ if ((is_protected_regs(address)) && (rw == MSIC_IPC_WRITE) &&
+ (error_val == -EIO))
+ return 0;
+
+ if (error_count < MAX_IPC_ERROR_COUNT) {
+ error_count++;
+ dev_warn(msic_dev, "MSIC IPC %s access to %x failed",
+ (rw == MSIC_IPC_WRITE ? "write" : "read"), address);
+ } else {
+
+ dev_crit(msic_dev, "MSIC IPC %s access to %x failed",
+ (rw == MSIC_IPC_WRITE ? "write" : "read"), address);
+
+ mutex_lock(&mbi->batt_lock);
+ /* set battery health */
+ if (mbi->batt_props.health == POWER_SUPPLY_HEALTH_GOOD) {
+ mbi->batt_props.health =
+ POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ }
+ mutex_unlock(&mbi->batt_lock);
+ /* set charger health */
+ mutex_lock(&mbi->usb_chrg_lock);
+ if (mbi->usb_chrg_props.charger_health ==
+ POWER_SUPPLY_HEALTH_GOOD){
+ mbi->usb_chrg_props.charger_health =
+ POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ }
+ mutex_unlock(&mbi->usb_chrg_lock);
+ power_supply_changed(&mbi->usb);
+ }
+
+ return error_val;
+}
+
+/**
+ * get_batt_fg_curve_index - get the fg curve ID from umip
+ * @name : Power Supply name
+ *
+ * Returns FG curve ID
+ *
+ */
+static int get_batt_fg_curve_index(const char *name)
+{
+ struct power_supply *psy;
+ int mip_offset, i, ret;
+ u8 batt_id[BATTID_STR_LEN];
+ u8 num_tbls = 0;
+ int num_supplicants;
+
+ /* check if we support the device in our supplied to list */
+ num_supplicants = ARRAY_SIZE(msic_power_supplied_to);
+ for (i = 0; i < num_supplicants; i++) {
+ if (!strncmp(name, msic_power_supplied_to[i], 16))
+ break;
+ }
+
+ if (i >= num_supplicants)
+ return -ENXIO;
+
+ /* check if msic charger is ready */
+ psy = power_supply_get_by_name(CHARGER_PS_NAME);
+ if (!psy)
+ return -EAGAIN;
+
+ /* get the no.of tables from mip */
+ ret = intel_scu_ipc_read_mip((u8 *)&num_tbls, 1,
+ UMIP_NO_OF_CFG_TBLS, 0);
+ if (ret) {
+ dev_warn(msic_dev, "%s: umip read failed\n", __func__);
+ goto get_idx_failed;
+ }
+
+ /* compare the batt ID provided by SFI table and FG table in mip */
+ mip_offset = UMIP_BATT_FG_CFG_TBL1 + BATT_FG_TBL_BATTID;
+ for (i = 0; i < num_tbls; i++) {
+ ret = intel_scu_ipc_read_mip(batt_id, BATTID_STR_LEN,
+ mip_offset, 0);
+ if (ret) {
+ dev_warn(msic_dev, "%s: umip read failed\n", __func__);
+ goto get_idx_failed;
+ }
+
+ if (!strncmp(batt_id, sfi_table->batt_id, BATTID_STR_LEN))
+ break;
+
+ mip_offset += UMIP_FG_TBL_SIZE;
+ memset(batt_id, 0x0, BATTID_STR_LEN);
+ }
+
+ if (i < num_tbls)
+ ret = i;
+ else
+ ret = -ENXIO;
+
+get_idx_failed:
+ return ret;
+}
+
+/**
+ * intel_msic_restore_config_data - restore config data
+ * @name : Power Supply name
+ * @data : config data output pointer
+ * @len : length of config data
+ *
+ */
+int intel_msic_restore_config_data(const char *name, void *data, int len)
+{
+ int mip_offset, ret;
+
+ /* check if msic charger is ready */
+ if (!power_supply_get_by_name("msic_charger"))
+ return -EAGAIN;
+
+ /* Read the fuel gauge config data from umip */
+ mip_offset = UMIP_REF_FG_TBL + BATT_FG_TBL_BODY;
+ ret = intel_scu_ipc_read_mip((u8 *)data, len, mip_offset, 0);
+ if (ret)
+ dev_warn(msic_dev, "%s: umip read failed\n", __func__);
+
+ return ret;
+}
+EXPORT_SYMBOL(intel_msic_restore_config_data);
+
+/**
+ * intel_msic_save_config_data - save config data
+ * @name : Power Supply name
+ * @data : config data input pointer
+ * @len : length of config data
+ *
+ */
+int intel_msic_save_config_data(const char *name, void *data, int len)
+{
+ int mip_offset, ret;
+
+ /* check if msic charger is ready */
+ if (!power_supply_get_by_name("msic_charger"))
+ return -EAGAIN;
+
+ /* write the fuel gauge config data to umip */
+ mip_offset = UMIP_REF_FG_TBL + BATT_FG_TBL_BODY;
+ ret = intel_scu_ipc_write_umip((u8 *)data, len, mip_offset);
+ if (ret)
+ dev_warn(msic_dev, "%s: umip write failed\n", __func__);
+
+ return ret;
+}
+EXPORT_SYMBOL(intel_msic_save_config_data);
+
+/* Check for valid Temp ADC range */
+static bool is_valid_temp_adc(int adc_val)
+{
+ if (adc_val >= MSIC_BTP_ADC_MIN && adc_val <= MSIC_BTP_ADC_MAX)
+ return true;
+ else
+ return false;
+}
+
+/* Temperature conversion Macros */
+static int conv_adc_temp(int adc_val, int adc_max, int adc_diff, int temp_diff)
+{
+ int ret;
+
+ ret = (adc_max - adc_val) * temp_diff;
+ return ret / adc_diff;
+}
+
+/* Check if the adc value is in the curve sample range */
+static bool is_valid_temp_adc_range(int val, int min, int max)
+{
+ if (val > min && val <= max)
+ return true;
+ else
+ return false;
+}
+
+/**
+ * adc_to_temp - convert ADC code to temperature
+ * @adc_val : ADC sensor reading
+ *
+ * Returns temperature in Degree Celsius
+ */
+static int adc_to_temp(uint16_t adc_val)
+{
+ int temp = 0;
+ int i;
+
+ if (!is_valid_temp_adc(adc_val)) {
+ dev_warn(msic_dev, "Temperature out of Range: %u\n", adc_val);
+ return -ERANGE;
+ }
+
+ for (i = 0; i < THERM_CURVE_MAX_SAMPLES; i++) {
+ /* linear approximation for battery pack temperature */
+ if (is_valid_temp_adc_range(adc_val, therm_curve_data[i][3],
+ therm_curve_data[i][2])) {
+
+ temp = conv_adc_temp(adc_val, therm_curve_data[i][2],
+ therm_curve_data[i][2] -
+ therm_curve_data[i][3],
+ therm_curve_data[i][0] -
+ therm_curve_data[i][1]);
+
+ temp += therm_curve_data[i][1];
+ break;
+ }
+ }
+
+ if (i >= THERM_CURVE_MAX_SAMPLES)
+ dev_warn(msic_dev, "Invalid temp adc range\n");
+
+ return temp;
+}
+
+static int is_ttl_valid(u64 ttl)
+{
+
+ if (time_before64(get_jiffies_64(), ttl))
+ return 1;
+ else
+ return 0;
+}
+
+/**
+ * mdf_multi_read_adc_regs - read multiple ADC sensors
+ * @mbi : msic battery info pointer
+ * @sample_count: do sample serveral times and get the average value.
+ * @...: sensor numbers
+ *
+ * Returns 0 if success
+ */
+static int mdf_multi_read_adc_regs(struct msic_power_module_info *mbi,
+ int sample_count, int channel_count, ...)
+{
+ va_list args;
+ int ret = 0, i, sensor, tmp;
+ int *adc_val;
+ int temp_adc_val[MSIC_BATT_SENSORS];
+
+
+ mutex_lock(&mbi->adc_val_lock);
+ if (!is_ttl_valid(adc_ttl) || (sample_count > 1)) {
+ ret =
+ intel_mid_gpadc_sample(mbi->adc_handle, sample_count,
+ &adc_sensor_vals[MSIC_ADC_TEMP_IDX],
+ &adc_sensor_vals
+ [MSIC_ADC_USB_VOL_IDX],
+ &adc_sensor_vals
+ [MSIC_ADC_BATTID_IDX]);
+ if (ret) {
+ dev_err(&mbi->pdev->dev,
+ "adc driver api returned error(%d)\n", ret);
+ mutex_unlock(&mbi->adc_val_lock);
+ goto adc_multi_exit;
+ }
+ adc_ttl = get_jiffies_64() + ADC_TIME_TO_LIVE;
+ }
+ memcpy(temp_adc_val, adc_sensor_vals, sizeof(temp_adc_val));
+ mutex_unlock(&mbi->adc_val_lock);
+
+ va_start(args, channel_count);
+ for (i = 0; i < channel_count; i++) {
+ /* get sensor number */
+ sensor = va_arg(args, int);
+ /* get value pointer */
+ adc_val = va_arg(args, int *);
+ if (adc_val == NULL) {
+ ret = -EINVAL;
+ goto adc_multi_exit;
+ }
+ *adc_val = temp_adc_val[sensor];
+ switch (sensor) {
+ case MSIC_ADC_TEMP_IDX:
+ tmp = adc_to_temp(*adc_val);
+ break;
+ case MSIC_ADC_USB_VOL_IDX:
+ tmp = MSIC_ADC_TO_VBUS_VOL(*adc_val);
+ break;
+ case MSIC_ADC_BATTID_IDX:
+ tmp = *adc_val;
+ break;
+ default:
+ dev_err(&mbi->pdev->dev, "invalid sensor%d", sensor);
+ return -EINVAL;
+ }
+ *adc_val = tmp;
+ }
+ va_end(args);
+
+adc_multi_exit:
+ return ret;
+}
+
+static int mdf_read_adc_regs(int sensor, int *sensor_val,
+ struct msic_power_module_info *mbi)
+{
+ int ret;
+ ret = mdf_multi_read_adc_regs(mbi, 1, 1, sensor, sensor_val);
+
+ if (ret)
+ dev_err(&mbi->pdev->dev, "%s:mdf_multi_read_adc_regs failed",
+ __func__);
+ return ret;
+}
+
+int intel_msic_get_battery_pack_temp(int *temp)
+{
+ struct platform_device *pdev = container_of(msic_dev,
+ struct platform_device, dev);
+ struct msic_power_module_info *mbi = platform_get_drvdata(pdev);
+
+ /* check if msic charger is ready */
+ if (!power_supply_get_by_name(CHARGER_PS_NAME))
+ return -EAGAIN;
+
+ if (!mbi->current_sense_enabled)
+ return -ENODEV;
+
+ return mdf_read_adc_regs(MSIC_ADC_TEMP_IDX, temp, mbi);
+}
+EXPORT_SYMBOL(intel_msic_get_battery_pack_temp);
+
+static void dump_registers(void)
+{
+ int i, retval = 0;
+ uint8_t reg_val;
+ uint16_t chk_reg_addr;
+ uint16_t reg_addr[] = {MSIC_BATT_CHR_CHRCTRL_ADDR,
+ CHR_STATUS_FAULT_REG, MSIC_BATT_CHR_PWRSRCLMT_ADDR,
+ MSIC_BATT_CHR_CHRCVOLTAGE_ADDR, MSIC_BATT_CHR_CHRCCURRENT_ADDR};
+ char *reg_str[] = {"Chrctrl_reg", "Fault_reg", "Pwrsrclmt_reg",
+ "Chrvolt_reg", "Chrcur_reg"};
+
+ for (i = 0; i < 5; i++) {
+ retval = intel_scu_ipc_ioread8(reg_addr[i], ®_val);
+ if (retval) {
+ chk_reg_addr = reg_addr[i];
+ goto ipcread_err;
+ }
+
+ dev_info(msic_dev, "%s val: %x\n", reg_str[i], reg_val);
+ }
+
+ return;
+
+ipcread_err:
+ handle_ipc_rw_status(retval, chk_reg_addr, MSIC_IPC_READ);
+}
+
+static int is_charger_fault(void)
+{
+ uint8_t fault_reg, chrctrl_reg, stat;
+ int retval = 0;
+ int adc_temp, adc_usb_volt, batt_volt;
+ struct platform_device *pdev = container_of(msic_dev,
+ struct platform_device, dev);
+ struct msic_power_module_info *mbi = platform_get_drvdata(pdev);
+
+ retval = intel_scu_ipc_ioread8(MSIC_BATT_CHR_CHRCTRL_ADDR,
+ &chrctrl_reg);
+ if (retval) {
+ retval = handle_ipc_rw_status(retval,
+ MSIC_BATT_CHR_CHRCTRL_ADDR, MSIC_IPC_READ);
+ if (retval)
+ return retval;
+ }
+ /* if charger is disabled report false*/
+ if (chrctrl_reg & CHRCNTL_CHRG_DISABLE)
+ return false;
+
+ retval = intel_scu_ipc_ioread8(CHR_STATUS_FAULT_REG, &fault_reg);
+ if (retval) {
+ retval = handle_ipc_rw_status(retval, CHR_STATUS_FAULT_REG,
+ MSIC_IPC_READ);
+ if (retval)
+ return retval;
+ }
+
+ /*If charger is enabled and STAT(0:1) shows charging progress or
+ * charging done then we report false*/
+ stat = (fault_reg & CHR_STATUS_BIT_MASK) >> CHR_STATUS_BIT_POS;
+ if (stat == CHR_STATUS_BIT_PROGRESS ||
+ stat == CHR_STATUS_BIT_CYCLE_DONE)
+ return false;
+ else {
+ dev_info(msic_dev, "Charger fault occured\n");
+ if (!mdf_read_adc_regs(MSIC_ADC_USB_VOL_IDX,
+ &adc_usb_volt, mbi) &&
+ !mdf_read_adc_regs(MSIC_ADC_TEMP_IDX,
+ &adc_temp, mbi)) {
+ batt_volt = fg_chip_get_property(
+ POWER_SUPPLY_PROP_VOLTAGE_NOW);
+ if (batt_volt != -ENODEV && batt_volt != -EINVAL) {
+ dev_info(msic_dev, "Temp: %d, char_volt: %d, "
+ "batt_volt: %d\n", adc_temp,
+ adc_usb_volt, batt_volt/1000);
+ } else
+ dev_info(msic_dev, "Temp: %d, char_volt: %d\n",
+ adc_temp, adc_usb_volt);
+ }
+
+ dump_registers();
+
+ return true;
+ }
+}
+
+/**
+ * msic_usb_get_property - usb power source get property
+ * @psy: usb power supply context
+ * @psp: usb power source property
+ * @val: usb power source property value
+ * Context: can sleep
+ *
+ * MSIC usb power source property needs to be provided to power_supply
+ * subsystem for it to provide the information to users.
+ */
+static int msic_usb_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct msic_power_module_info *mbi =
+ container_of(psy, struct msic_power_module_info, usb);
+ int retval = 0;
+
+ mutex_lock(&mbi->usb_chrg_lock);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = mbi->usb_chrg_props.charger_present;
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ if (mbi->batt_props.status != POWER_SUPPLY_STATUS_NOT_CHARGING)
+ val->intval = mbi->usb_chrg_props.charger_present;
+ else
+ val->intval = 0;
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = mbi->usb_chrg_props.charger_health;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ retval = mdf_read_adc_regs(MSIC_ADC_USB_VOL_IDX,
+ &mbi->usb_chrg_props.vbus_vol, mbi);
+ if (retval)
+ break;
+ val->intval = mbi->usb_chrg_props.vbus_vol * 1000;
+ break;
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = mbi->usb_chrg_props.charger_model;
+ break;
+ case POWER_SUPPLY_PROP_MANUFACTURER:
+ val->strval = mbi->usb_chrg_props.charger_vender;
+ break;
+ default:
+ mutex_unlock(&mbi->usb_chrg_lock);
+ return -EINVAL;
+ }
+
+ mutex_unlock(&mbi->usb_chrg_lock);
+ return retval;
+}
+
+/**
+ * msic_log_exception_event - log battery events
+ * @event: msic event to be logged
+ * Context: can sleep
+ *
+ * There are multiple battery and internal charger events
+ * which may be of interest to users.
+ * this battery function logs the different events onto the
+ * kernel log messages.
+ */
+static void msic_log_exception_event(enum msic_event event)
+{
+ switch (event) {
+ case MSIC_EVENT_BATTOCP_EXCPT:
+ dev_warn(msic_dev,
+ "over battery charge current condition detected\n");
+ break;
+ case MSIC_EVENT_BATTOTP_EXCPT:
+ dev_warn(msic_dev,
+ "battery out of acceptable temp range condition detected\n");
+ break;
+ case MSIC_EVENT_LOWBATT_EXCPT:
+ dev_warn(msic_dev, "Low battery voltage condition detected\n");
+ break;
+ case MSIC_EVENT_BATTOVP_EXCPT:
+ dev_warn(msic_dev, "battery over voltage condition detected\n");
+ break;
+ case MSIC_EVENT_CHROTP_EXCPT:
+ dev_warn(msic_dev,
+ "charger high temperature condition detected\n");
+ break;
+ case MSIC_EVENT_USBOVP_EXCPT:
+ dev_warn(msic_dev, "USB over voltage condition detected\n");
+ break;
+ case MSIC_EVENT_USB_VINREG_EXCPT:
+ dev_warn(msic_dev, "USB Input voltage regulation "
+ "condition detected\n");
+ break;
+ case MSIC_EVENT_WEAKVIN_EXCPT:
+ dev_warn(msic_dev, "USB Weak VBUS voltage "
+ "condition detected\n");
+ break;
+ case MSIC_EVENT_TIMEEXP_EXCPT:
+ dev_warn(msic_dev, "Charger Total Time Expiration "
+ "condition detected\n");
+ break;
+ case MSIC_EVENT_WDTIMEEXP_EXCPT:
+ dev_warn(msic_dev, "Watchdog Time Expiration "
+ "condition detected\n");
+ break;
+ default:
+ dev_warn(msic_dev, "unknown error %u detected\n", event);
+ break;
+ }
+}
+
+/**
+ * msic_handle_exception - handle any exception scenario
+ * @mbi: device info structure to update the information
+ * Context: can sleep
+ *
+ */
+
+static void msic_handle_exception(struct msic_power_module_info *mbi,
+ uint8_t CHRINT_reg_value,
+ uint8_t CHRINT1_reg_value)
+{
+ enum msic_event exception;
+ int temp, retval;
+
+ /* Battery Events */
+ mutex_lock(&mbi->batt_lock);
+ if (CHRINT_reg_value & MSIC_BATT_CHR_BATTOCP_MASK) {
+ exception = MSIC_EVENT_BATTOCP_EXCPT;
+ msic_log_exception_event(exception);
+ }
+
+ if (CHRINT_reg_value & MSIC_BATT_CHR_BATTOTP_MASK) {
+ retval = mdf_read_adc_regs(MSIC_ADC_TEMP_IDX, &temp, mbi);
+ if (retval) {
+ dev_err(msic_dev, "%s(): Error in reading"
+ " temperature. Setting health as OVERHEAT\n",
+ __func__);
+ }
+ if (retval || (temp > batt_thrshlds->temp_high) ||
+ (temp < batt_thrshlds->temp_low))
+ mbi->batt_props.health = POWER_SUPPLY_HEALTH_OVERHEAT;
+ exception = MSIC_EVENT_BATTOTP_EXCPT;
+ msic_log_exception_event(exception);
+ }
+
+ if (CHRINT_reg_value & MSIC_BATT_CHR_LOWBATT_MASK) {
+ mbi->batt_props.health = POWER_SUPPLY_HEALTH_DEAD;
+ exception = MSIC_EVENT_LOWBATT_EXCPT;
+ msic_log_exception_event(exception);
+ }
+ if (CHRINT_reg_value & MSIC_BATT_CHR_TIMEEXP_MASK) {
+ exception = MSIC_EVENT_TIMEEXP_EXCPT;
+ msic_log_exception_event(exception);
+ }
+
+ if (CHRINT_reg_value & MSIC_BATT_CHR_WDTIMEEXP_MASK) {
+ exception = MSIC_EVENT_WDTIMEEXP_EXCPT;
+ msic_log_exception_event(exception);
+ }
+
+ if (CHRINT1_reg_value & MSIC_BATT_CHR_BATTOVP_MASK) {
+ mbi->batt_props.health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ exception = MSIC_EVENT_BATTOVP_EXCPT;
+ msic_log_exception_event(exception);
+ }
+ mutex_unlock(&mbi->batt_lock);
+
+ /* Charger Events */
+ mutex_lock(&mbi->usb_chrg_lock);
+ if (CHRINT1_reg_value & MSIC_BATT_CHR_CHROTP_MASK) {
+ exception = MSIC_EVENT_CHROTP_EXCPT;
+ msic_log_exception_event(exception);
+ }
+
+ if (CHRINT1_reg_value & MSIC_BATT_CHR_USBOVP_MASK) {
+ mbi->usb_chrg_props.charger_health =
+ POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ exception = MSIC_EVENT_USBOVP_EXCPT;
+ msic_log_exception_event(exception);
+ }
+ if (CHRINT1_reg_value & MSIC_BATT_CHR_WKVINDET_MASK) {
+ mbi->usb_chrg_props.charger_health = POWER_SUPPLY_HEALTH_DEAD;
+ exception = MSIC_EVENT_WEAKVIN_EXCPT;
+ msic_log_exception_event(exception);
+ }
+ if (CHRINT1_reg_value & MSIC_BATT_CHR_VINREGMINT_MASK) {
+ mbi->usb_chrg_props.charger_health = POWER_SUPPLY_HEALTH_DEAD;
+ exception = MSIC_EVENT_USB_VINREG_EXCPT;
+ msic_log_exception_event(exception);
+ }
+ mutex_unlock(&mbi->usb_chrg_lock);
+}
+
+/**
+ * msic_write_multi - multi-write helper
+ * @mbi: msic power module
+ * @address: addresses of IPC writes
+ * @data: data for IPC writes
+ * @n: size of write table
+ *
+ * Write a series of values to the SCU while respecting the ipc_rw_lock
+ * across the entire sequence. Handle any error reporting and pass back
+ * error codes on failure
+ */
+static int msic_write_multi(struct msic_power_module_info *mbi,
+ const u16 *address, const u8 *data, int n)
+{
+ int retval = 0, i;
+ mutex_lock(&mbi->ipc_rw_lock);
+ for (i = 0; i < n; i++) {
+ retval = intel_scu_ipc_iowrite8(*address++, *data++);
+ if (retval) {
+ retval = handle_ipc_rw_status(retval,
+ *(address-1), MSIC_IPC_WRITE);
+ if (retval)
+ break;
+ }
+ }
+ mutex_unlock(&mbi->ipc_rw_lock);
+ return retval;
+}
+
+/**
+ * ipc_read_modify_chr_param_reg - read and modify charger registers
+ * @mbi: msic power module
+ * @address: charger register address
+ * @data: value to be set/reset
+ * @n: set or reset
+ *
+ */
+static int ipc_read_modify_chr_param_reg(struct msic_power_module_info *mbi,
+ uint16_t addr, uint8_t val, int set)
+{
+ int ret = 0;
+ static u16 address[2] = {
+ MSIC_BATT_CHR_WDTWRITE_ADDR, 0
+ };
+ static u8 data[2] = {
+ WDTWRITE_UNLOCK_VALUE, 0
+ };
+
+ address[1] = addr;
+
+ /* Unlock Charge parameter registers before reading */
+ ret = intel_scu_ipc_iowrite8(address[0], data[0]);
+ if (ret) {
+ ret = handle_ipc_rw_status(ret,
+ address[0], MSIC_IPC_WRITE);
+ if (ret)
+ return ret;
+ }
+
+ ret = intel_scu_ipc_ioread8(address[1], &data[1]);
+ if (ret) {
+ ret = handle_ipc_rw_status(ret, address[1], MSIC_IPC_READ);
+ if (ret)
+ return ret;
+ }
+
+ if (set)
+ data[1] |= val;
+ else
+ data[1] &= (~val);
+
+ return msic_write_multi(mbi, address, data, 2);
+}
+
+/**
+ * ipc_read_modify__reg - read and modify MSIC registers
+ * @mbi: msic power module
+ * @address: charger register address
+ * @data: value to be set/reset
+ * @n: set or reset
+ *
+ */
+static int ipc_read_modify_reg(struct msic_power_module_info *mbi,
+ uint16_t addr, uint8_t val, int set)
+{
+ int ret;
+ u8 data;
+
+ ret = intel_scu_ipc_ioread8(addr, &data);
+ if (ret) {
+ ret = handle_ipc_rw_status(ret, addr, MSIC_IPC_READ);
+ if (ret)
+ return ret;
+ }
+
+ if (set)
+ data |= val;
+ else
+ data &= (~val);
+
+ ret = intel_scu_ipc_iowrite8(addr, data);
+ if (ret)
+ ret = handle_ipc_rw_status(ret, addr, MSIC_IPC_WRITE);
+
+ return ret;
+}
+
+/**
+ * update_usb_ps_info - update usb power supply parameters
+ * @mbi: msic power module structure
+ * @cap: charger capability structure
+ * @event: USB OTG events
+ * Context: can sleep
+ *
+ * Updates the USB power supply parameters based on otg event.
+ */
+static void update_usb_ps_info(struct msic_power_module_info *mbi,
+ struct otg_bc_cap *cap, int event)
+{
+ mutex_lock(&mbi->usb_chrg_lock);
+ switch (event) {
+ case USBCHRG_EVENT_DISCONN:
+ mbi->usb.type = POWER_SUPPLY_TYPE_USB;
+ case USBCHRG_EVENT_SUSPEND:
+ dev_dbg(msic_dev, "Charger Disconnected or Suspended\n");
+ mbi->usb_chrg_props.charger_health =
+ POWER_SUPPLY_HEALTH_UNKNOWN;
+ memcpy(mbi->usb_chrg_props.charger_model, "Unknown",
+ sizeof("Unknown"));
+ memcpy(mbi->usb_chrg_props.charger_vender, "Unknown",
+ sizeof("Unknown"));
+ if (event == USBCHRG_EVENT_SUSPEND)
+ mbi->usb_chrg_props.charger_present =
+ MSIC_USB_CHARGER_PRESENT;
+ else
+ mbi->usb_chrg_props.charger_present =
+ MSIC_USB_CHARGER_NOT_PRESENT;
+ break;
+ case USBCHRG_EVENT_CONNECT:
+ case USBCHRG_EVENT_UPDATE:
+ case USBCHRG_EVENT_RESUME:
+ dev_dbg(msic_dev, "Charger Connected or Updated\n");
+ if (cap->chrg_type == CHRG_CDP) {
+ mbi->usb.type = POWER_SUPPLY_TYPE_USB_CDP;
+ dev_info(msic_dev, "Charger type: CDP, "
+ "current-val: %d", cap->mA);
+ } else if (cap->chrg_type == CHRG_DCP) {
+ mbi->usb.type = POWER_SUPPLY_TYPE_USB_DCP;
+ dev_info(msic_dev, "Charger type: DCP, "
+ "current-val: %d", cap->mA);
+ } else if (cap->chrg_type == CHRG_ACA) {
+ mbi->usb.type = POWER_SUPPLY_TYPE_USB_ACA;
+ dev_info(msic_dev, "Charger type: ACA, "
+ "current-val: %d", cap->mA);
+ } else if (cap->chrg_type == CHRG_SDP) {
+ mbi->usb.type = POWER_SUPPLY_TYPE_USB;
+ dev_info(msic_dev, "Charger type: SDP, "
+ "current negotiated: %d", cap->mA);
+ } else {
+ /* CHRG_UNKNOWN */
+ dev_warn(msic_dev, "Charger type:%d unknown\n",
+ cap->chrg_type);
+ mbi->usb.type = POWER_SUPPLY_TYPE_USB;
+ goto update_usb_ps_exit;
+ }
+ mbi->usb_chrg_props.charger_present = MSIC_USB_CHARGER_PRESENT;
+ mbi->usb_chrg_props.charger_health = POWER_SUPPLY_HEALTH_GOOD;
+ memcpy(mbi->usb_chrg_props.charger_model, "msic",
+ sizeof("msic"));
+ memcpy(mbi->usb_chrg_props.charger_vender, "Intel",
+ sizeof("Intel"));
+ break;
+ default:
+ dev_warn(msic_dev, "Invalid OTG event\n");
+ }
+
+update_usb_ps_exit:
+ mutex_unlock(&mbi->usb_chrg_lock);
+
+ power_supply_changed(&mbi->usb);
+}
+
+static int msic_batt_stop_charging(struct msic_power_module_info *mbi)
+{
+ static const u16 address[] = {
+ MSIC_BATT_CHR_WDTWRITE_ADDR,
+ MSIC_BATT_CHR_CHRCTRL_ADDR,
+ MSIC_BATT_CHR_WDTWRITE_ADDR,
+ MSIC_BATT_CHR_CHRSTWDT_ADDR,
+ };
+ static const u8 data[] = {
+ WDTWRITE_UNLOCK_VALUE, /* Unlock chrg params */
+ /*Disable Charging,Enable Low Power Mode */
+ CHRCNTL_CHRG_DISABLE | CHRCNTL_CHRG_LOW_PWR_ENBL,
+ WDTWRITE_UNLOCK_VALUE, /* Unlock chrg params */
+ CHR_WDT_DISABLE, /* Disable WDT Timer */
+ };
+
+ /*
+ * Charger connect handler delayed work also modifies the
+ * MSIC charger parameter registers.To avoid concurrent
+ * read writes to same set of registers locking applied by
+ * msic_write_multi
+ */
+ return msic_write_multi(mbi, address, data, 4);
+}
+
+/**
+ * msic_batt_do_charging - set battery charger
+ * @mbi: device info structure
+ * @chrg: charge mode to set battery charger in
+ * Context: can sleep
+ *
+ * MsIC battery charger needs to be enabled based on the charger
+ * capabilities connected to the platform.
+ */
+static int msic_batt_do_charging(struct msic_power_module_info *mbi,
+ struct charge_params *params,
+ int is_maint_mode)
+{
+ int retval;
+ static u8 data[] = {
+ WDTWRITE_UNLOCK_VALUE, 0,
+ WDTWRITE_UNLOCK_VALUE, 0,
+ WDTWRITE_UNLOCK_VALUE, 0,
+ WDTWRITE_UNLOCK_VALUE, CHR_WDT_SET_60SEC,
+ WDTWRITE_UNLOCK_VALUE, 0
+ };
+ static const u16 address[] = {
+ MSIC_BATT_CHR_WDTWRITE_ADDR, MSIC_BATT_CHR_CHRCCURRENT_ADDR,
+ MSIC_BATT_CHR_WDTWRITE_ADDR, MSIC_BATT_CHR_CHRCVOLTAGE_ADDR,
+ MSIC_BATT_CHR_WDTWRITE_ADDR, MSIC_BATT_CHR_CHRCTRL_ADDR,
+ MSIC_BATT_CHR_WDTWRITE_ADDR, MSIC_BATT_CHR_CHRSTWDT_ADDR,
+ MSIC_BATT_CHR_WDTWRITE_ADDR, MSIC_BATT_CHR_SPCHARGER_ADDR
+ };
+
+ data[1] = params->ccur;
+ data[3] = params->cvol; /* charge voltage 4.14V */
+ data[5] = params->vinilmt;
+ data[9] = params->weakvin;
+
+ /*
+ * Charger disconnect handler also modifies the
+ * MSIC charger parameter registers.To avoid concurrent
+ * read writes to same set of registers locking applied
+ */
+ retval = msic_write_multi(mbi, address, data, 10);
+ if (retval < 0) {
+ dev_warn(msic_dev, "ipc multi write failed:%s\n", __func__);
+ return retval;
+ }
+
+ dev_dbg(msic_dev, "Charger Enabled\n");
+ /* prevent system from entering s3 while charger is connected */
+ if (!wake_lock_active(&mbi->wakelock))
+ wake_lock(&mbi->wakelock);
+
+ mutex_lock(&mbi->batt_lock);
+ if (is_maint_mode)
+ mbi->batt_props.status = POWER_SUPPLY_STATUS_FULL;
+ else
+ mbi->batt_props.status = POWER_SUPPLY_STATUS_CHARGING;
+ mutex_unlock(&mbi->batt_lock);
+
+ power_supply_changed(&mbi->usb);
+ return 0;
+}
+
+static void reset_wdt_timer(struct msic_power_module_info *mbi)
+{
+ static const u16 address[2] = {
+ MSIC_BATT_CHR_WDTWRITE_ADDR, MSIC_BATT_CHR_CHRSTWDT_ADDR
+ };
+ static const u8 data[2] = {
+ WDTWRITE_UNLOCK_VALUE, CHR_WDT_SET_60SEC
+ };
+
+ /*
+ * Charger disconnect handler also modifies the
+ * MSIC charger parameter registers.To avoid concurrent
+ * read writes to same set of registers locking applied
+ */
+ msic_write_multi(mbi, address, data, 2);
+}
+
+/**
+ * check_charge_full - check battery is full or not
+ * @mbi: device info structure
+ * @vref: battery voltage
+ * @temp_idx : temperature range index
+ * Context: can sleep
+ *
+ * Return true if full
+ *
+ */
+static int check_charge_full(struct msic_power_module_info *mbi,
+ int vref, int temp_idx)
+{
+ static int volt_prev;
+ int is_full = false;
+ int volt_now;
+ int cur_avg;
+
+ /* Read voltage and current from FG driver */
+ volt_now = fg_chip_get_property(POWER_SUPPLY_PROP_VOLTAGE_NOW);
+ if (volt_now == -ENODEV || volt_now == -EINVAL) {
+ dev_warn(msic_dev, "Can't read voltage from FG\n");
+ return false;
+ }
+ /* convert to milli volts */
+ volt_now /= 1000;
+
+ /* Using Current-avg instead of Current-now to take care of
+ * instantaneous spike or dip */
+ cur_avg = fg_chip_get_property(POWER_SUPPLY_PROP_CURRENT_AVG);
+ if (cur_avg == -ENODEV || cur_avg == -EINVAL) {
+ dev_warn(msic_dev, "Can't read current-avg from FG\n");
+ return false;
+ }
+ /* convert to milli amps */
+ cur_avg /= 1000;
+
+ if ((volt_now > (vref)) &&
+ (volt_prev > (vref))) {
+ if (cur_avg >= FULL_CURRENT_AVG_LOW &&
+ cur_avg <= FULL_CURRENT_AVG_HIGH)
+ is_full = true;
+ else
+ is_full = false;
+ } else {
+ is_full = false;
+ }
+
+
+ if (is_full) {
+ dev_info(msic_dev, "Charge full detected\n");
+ dev_dbg(msic_dev, "volt_now:%d, volt_prev:%d, "
+ "volt_ref:%d, cur_avg:%d\n",
+ volt_now, volt_prev, vref, cur_avg);
+ /* Disable Charging */
+ msic_batt_stop_charging(mbi);
+ }
+
+ volt_prev = volt_now;
+
+ return is_full;
+}
+
+/**
+* sfi_temp_range_lookup - lookup SFI table to find the temperature range index
+* @adc_temp : temperature in Degree Celcius
+*
+* Returns temperature range index
+*/
+static unsigned int sfi_temp_range_lookup(int adc_temp)
+{
+ int i;
+
+ for (i = 0; i < sfi_table->temp_mon_ranges; i++) {
+ if (adc_temp <= sfi_table->temp_mon_range[i].temp_up_lim &&
+ adc_temp > sfi_table->temp_mon_range[i].temp_low_lim) {
+ dev_dbg(msic_dev, "Temp Range %d\n", i);
+ break;
+ }
+ }
+
+ return i;
+}
+
+/**
+* msic_batt_temp_charging - manages the charging based on temperature
+* @charge_param: charging parameter
+* @sfi_table: SFI table structure
+*
+* To manage the charging based on the
+* temperature of the battery
+*/
+static void msic_batt_temp_charging(struct work_struct *work)
+{
+ int ret, i, is_maint_chrg = false, is_lowchrg_enbl;
+ static int iprev = -1, is_chrg_enbl;
+ short int cv = 0, cc = 0, vinlimit = 0, cvref;
+ int adc_temp, adc_vol;
+ struct charge_params charge_param;
+ struct msic_power_module_info *mbi =
+ container_of(work, struct msic_power_module_info,
+ connect_handler.work);
+ struct temp_mon_table *temp_mon = NULL;
+
+ memset(&charge_param, 0x0, sizeof(struct charge_params));
+ charge_param.vinilmt = mbi->ch_params.vinilmt;
+ charge_param.chrg_type = mbi->ch_params.chrg_type;
+
+ mutex_lock(&mbi->event_lock);
+ if (mbi->refresh_charger) {
+ /*
+ * If the charger type is unknown or None
+ * better start the charging again and compute
+ * the properties again.
+ */
+ mbi->refresh_charger = 0;
+ iprev = -1;
+ is_chrg_enbl = false;
+ }
+ mutex_unlock(&mbi->event_lock);
+
+ if (mdf_read_adc_regs(MSIC_ADC_TEMP_IDX, &adc_temp, mbi)) {
+ dev_err(msic_dev, "Error in reading temperature\n");
+ goto lbl_sched_work;
+ }
+
+ /* find the temperature range */
+ i = sfi_temp_range_lookup(adc_temp);
+
+ if (i >= sfi_table->temp_mon_ranges || is_charger_fault()) {
+ if ((adc_temp > batt_thrshlds->temp_high) ||
+ (adc_temp < batt_thrshlds->temp_low)) {
+ dev_warn(msic_dev,
+ "TEMP RANGE DOES NOT EXIST FOR %d\n",
+ adc_temp);
+ mutex_lock(&mbi->batt_lock);
+ mbi->batt_props.health = POWER_SUPPLY_HEALTH_OVERHEAT;
+ mutex_unlock(&mbi->batt_lock);
+ }
+ /* Check charger Status bits */
+ if (is_charger_fault()) {
+ mutex_lock(&mbi->batt_lock);
+ mbi->batt_props.status =
+ POWER_SUPPLY_STATUS_NOT_CHARGING;
+ mutex_unlock(&mbi->batt_lock);
+ }
+ /*
+ * If we are in middle of charge cycle is safer to Reset WDT
+ * Timer Register.Because battery temperature and charge
+ * status register are not related.
+ */
+ reset_wdt_timer(mbi);
+ dev_dbg(msic_dev, "Charger Watchdog timer reset for 60sec\n");
+ goto lbl_sched_work;
+ }
+
+ /* Set charger parameters */
+ cv = sfi_table->temp_mon_range[i].full_chrg_vol;
+ cc = sfi_table->temp_mon_range[i].full_chrg_cur;
+ cvref = cv;
+ dev_dbg(msic_dev, "cc:%d cv:%d\n", cc, cv);
+
+ mutex_lock(&mbi->event_lock);
+ /* Check on user setting for charge current */
+ if (mbi->usr_chrg_enbl == USER_SET_CHRG_LMT1)
+ vinlimit = CHRCNTL_VINLMT_100; /* VINILMT set to 100mA */
+ else if (mbi->usr_chrg_enbl == USER_SET_CHRG_LMT2)
+ vinlimit = CHRCNTL_VINLMT_500; /* VINILMT set to 500mA */
+ else if (mbi->usr_chrg_enbl == USER_SET_CHRG_LMT3)
+ vinlimit = CHRCNTL_VINLMT_950; /* VINILMT set to 950mA */
+ else {
+ /* D7,D6 bits of CHRCNTL will set the VINILMT */
+ if (charge_param.vinilmt > 950)
+ vinlimit = CHRCNTL_VINLMT_NOLMT;
+ else if (charge_param.vinilmt > 500)
+ vinlimit = CHRCNTL_VINLMT_950;
+ else if (charge_param.vinilmt > 100)
+ vinlimit = CHRCNTL_VINLMT_500;
+ else
+ vinlimit = CHRCNTL_VINLMT_100;
+ }
+ /*
+ * Check for Charge full condition and set the battery
+ * properties accordingly. Also check for charging mode
+ * whether it is normal or maintenance mode.
+ */
+ if (mbi->charging_mode == BATT_CHARGING_MODE_MAINTENANCE) {
+ cvref = sfi_table->temp_mon_range[i].maint_chrg_vol_ul;
+ is_maint_chrg = true;
+ }
+ mutex_unlock(&mbi->event_lock);
+
+ /* Check full detection only if we are charging */
+ if (is_chrg_enbl)
+ ret = check_charge_full(mbi, cvref, i);
+ else
+ ret = is_chrg_enbl;
+
+ if (ret) {
+ is_chrg_enbl = false;
+ if (!is_maint_chrg) {
+ dev_dbg(msic_dev, "Going to Maintenance CHRG Mode\n");
+
+ mutex_lock(&mbi->event_lock);
+ mbi->charging_mode = BATT_CHARGING_MODE_MAINTENANCE;
+ mutex_unlock(&mbi->event_lock);
+
+ mutex_lock(&mbi->batt_lock);
+ mbi->batt_props.status = POWER_SUPPLY_STATUS_FULL;
+ mutex_unlock(&mbi->batt_lock);
+
+ is_maint_chrg = true;
+ power_supply_changed(&mbi->usb);
+ }
+ }
+
+ /*
+ * If we are in same Temperature range check for the
+ * maintenance charging mode and enable the charging depending
+ * on the voltage.If Temperature range is changed then anyways
+ * we need to set charging parameters and enable charging.
+ */
+ if (i == iprev) {
+ /*
+ * Check if the voltage falls below lower threshold
+ * if we are in maintenance mode charging.
+ */
+ if (is_maint_chrg && !is_chrg_enbl) {
+ temp_mon = &sfi_table->temp_mon_range[i];
+ /* Read battery Voltage */
+ adc_vol = fg_chip_get_property(
+ POWER_SUPPLY_PROP_VOLTAGE_NOW);
+ if (adc_vol == -ENODEV || adc_vol == -EINVAL) {
+ dev_warn(msic_dev, "Can't read voltage from FG\n");
+ goto lbl_sched_work;
+ }
+ /* convert to milli volts */
+ adc_vol /= 1000;
+
+ if ((adc_vol <= temp_mon->maint_chrg_vol_ll)) {
+ dev_dbg(msic_dev, "restart charging\n");
+ cv = temp_mon->maint_chrg_vol_ul;
+ } else {
+ dev_dbg(msic_dev, "vbat is more than ll\n");
+ goto lbl_sched_work;
+ }
+ } else {
+ /* Reset WDT Timer Register for 60 Sec */
+ reset_wdt_timer(mbi);
+ dev_dbg(msic_dev, "Charger Watchdog timer reset for 60sec\n");
+ goto lbl_sched_work;
+ }
+ } else {
+ temp_mon = &sfi_table->temp_mon_range[i];
+ dev_info(msic_dev, "Current Temp zone is %d, "
+ "it's parameters are:\n", i);
+ dev_info(msic_dev, "full_volt:%d, full_cur:%d\n",
+ temp_mon->full_chrg_vol,
+ temp_mon->full_chrg_cur);
+ dev_info(msic_dev, "maint_vol_ll:%d, maint_vol_ul:%d, "
+ "maint_cur:%d\n", temp_mon->maint_chrg_vol_ll,
+ temp_mon->maint_chrg_vol_ul,
+ temp_mon->maint_chrg_cur);
+ dump_registers();
+ }
+
+ iprev = i;
+ mbi->ch_params.cvol = cv;
+ charge_param.cvol = CONV_VOL_DEC_MSICREG(cv);
+ /* CHRCC_MIN_CURRENT is th lowet value */
+ cc = cc - CHRCC_MIN_CURRENT;
+ /*
+ * If charge current parameter is less than 550mA we should
+ * enable LOW CHARGE mode which will limit the charge current to 325mA.
+ */
+ if (cc <= 0) {
+ dev_dbg(msic_dev, "LOW CHRG mode enabled\n");
+ cc = 0;
+ is_lowchrg_enbl = BIT_SET;
+ } else {
+ dev_dbg(msic_dev, "LOW CHRG mode NOT enabled\n");
+ cc = cc / 100;
+ is_lowchrg_enbl = BIT_RESET;
+ }
+ cc = cc << 3;
+
+ charge_param.ccur = cc;
+ charge_param.vinilmt = vinlimit;
+
+ dev_dbg(msic_dev, "params vol: %x cur:%x vinilmt:%x\n",
+ charge_param.cvol, charge_param.ccur, charge_param.vinilmt);
+
+ if (cv > WEAKVIN_VOLTAGE_LEVEL)
+ charge_param.weakvin = CHR_SPCHRGER_WEAKVIN_LVL1;
+ else
+ charge_param.weakvin = CHR_SPCHRGER_WEAKVIN_LVL2;
+
+ if (is_lowchrg_enbl)
+ charge_param.weakvin |= CHR_SPCHRGER_LOWCHR_ENABLE;
+
+ /* enable charging here */
+ ret = msic_batt_do_charging(mbi, &charge_param, is_maint_chrg);
+ if (ret) {
+ dev_warn(msic_dev, "msic_batt_do_charging failed\n");
+ goto lbl_sched_work;
+ }
+ is_chrg_enbl = true;
+
+lbl_sched_work:
+ /* Schedule the work after 30 Seconds */
+ schedule_delayed_work(&mbi->connect_handler, TEMP_CHARGE_DELAY_JIFFIES);
+}
+static void update_charger_health(struct msic_power_module_info *mbi)
+{
+ int vbus_volt;
+ unsigned char dummy_val;
+
+ /* We don't get an interrupt once charger returns from
+ error state. So check current status by reading voltage
+ and report health as good if recovered from error state */
+
+
+ /* Read charger data from ADC channels */
+ if (mdf_read_adc_regs(MSIC_ADC_USB_VOL_IDX, &vbus_volt, mbi)) {
+ dev_warn(msic_dev, "Error in reading charger"
+ " voltage:%s\n", __func__);
+ return;
+ }
+
+ /* Compute Charger health */
+ mutex_lock(&mbi->usb_chrg_lock);
+ /* check system recovered from overvoltage and dead conditions */
+ if ((mbi->usb_chrg_props.charger_health ==
+ POWER_SUPPLY_HEALTH_OVERVOLTAGE ||
+ mbi->usb_chrg_props.charger_health
+ == POWER_SUPPLY_HEALTH_DEAD) &&
+ vbus_volt >= MSIC_VBUS_LOW_VOLTAGE &&
+ vbus_volt <= MSIC_VBUS_OVER_VOLTAGE) {
+
+ mbi->usb_chrg_props.charger_health = POWER_SUPPLY_HEALTH_GOOD;
+
+ } else if (mbi->batt_props.health ==
+ POWER_SUPPLY_HEALTH_UNSPEC_FAILURE){
+
+ /* do a dummy IPC read to see IPC recovered form IPC error.
+ * If recovered reset error_count*/
+
+ if (!intel_scu_ipc_ioread8(MSIC_BATT_CHR_CHRCTRL_ADDR,
+ &dummy_val)) {
+ error_count = 0;
+ mbi->usb_chrg_props.charger_health =
+ POWER_SUPPLY_HEALTH_GOOD;
+
+ }
+ }
+ mutex_unlock(&mbi->usb_chrg_lock);
+}
+
+static void update_battery_health(struct msic_power_module_info *mbi)
+{
+ int temp, volt, chr_mode, max_volt;
+ unsigned char dummy_val;
+
+ mutex_lock(&mbi->event_lock);
+ chr_mode = mbi->charging_mode;
+ mutex_unlock(&mbi->event_lock);
+
+ /* We don't get an interrupt once battery returns from
+ error state. So check current status by reading voltagei and
+ temperature and report health as good if recovered from error state */
+
+ volt = fg_chip_get_property(POWER_SUPPLY_PROP_VOLTAGE_NOW);
+
+ if (volt == -ENODEV || volt == -EINVAL) {
+ dev_warn(msic_dev, "Can't read voltage from FG\n");
+ return;
+ }
+
+ temp = fg_chip_get_property(POWER_SUPPLY_PROP_TEMP);
+
+ if (temp == -ENODEV || temp == -EINVAL) {
+ dev_warn(msic_dev, "Can't read temp from FG\n");
+ return;
+ }
+
+ /* convert to milli volts */
+ volt /= 1000;
+
+ /*convert to degree Celcius from tenths of degree Celsius */
+ temp = temp / 10;
+
+
+
+ if (chr_mode != BATT_CHARGING_MODE_NONE)
+ max_volt = (mbi->ch_params.cvol * OVP_VAL_MULT_FACTOR) / 10;
+ else
+ max_volt = BATT_OVERVOLTAGE_CUTOFF_VOLT;
+
+ /* Check for fault and update health */
+ mutex_lock(&mbi->batt_lock);
+ /*
+ * Valid temperature window is 0 to 60 Degrees
+ * and thermistor has 2 degree hysteresis and considering
+ * 2 degree adc error, fault revert temperature will
+ * be 4 to 56 degrees.
+ */
+ if (mbi->batt_props.health == POWER_SUPPLY_HEALTH_GOOD) {
+ /* if BATTOTP is masked check temperature and
+ * update BATT HEALTH */
+ if (mbi->chrint_mask & MSIC_BATT_CHR_BATTOTP_MASK) {
+ if ((temp > (batt_thrshlds->temp_high)) ||
+ (temp < (batt_thrshlds->temp_low))) {
+ /* We report OVERHEAT in both cases*/
+ mbi->batt_props.health =
+ POWER_SUPPLY_HEALTH_OVERHEAT;
+ dev_dbg(msic_dev, "Battery pack temp: %d, "
+ "too hot or too cold\n", temp);
+ }
+ }
+ } else {
+ /* check for battery overvoltage,overheat and dead health*/
+ if ((mbi->batt_props.health !=
+ POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) &&
+ (temp <= (batt_thrshlds->temp_high -
+ MSIC_TEMP_HYST_ERR)) &&
+ (temp >= (batt_thrshlds->temp_low
+ + MSIC_TEMP_HYST_ERR)) &&
+ (volt >= batt_thrshlds->vbatt_crit) &&
+ (volt <= max_volt)) {
+
+ mbi->batt_props.health = POWER_SUPPLY_HEALTH_GOOD;
+ dev_dbg(msic_dev, "Setting battery-health, power-supply good");
+ dump_registers();
+ } else if (mbi->batt_props.health ==
+ POWER_SUPPLY_HEALTH_UNSPEC_FAILURE){
+
+ /* do a dummy IPC read to see IPC recovered from
+ * IPC error. If recovered reset error_count*/
+
+ if (!intel_scu_ipc_ioread8(MSIC_BATT_CHR_CHRCTRL_ADDR,
+ &dummy_val)) {
+ error_count = 0;
+ mbi->batt_props.health =
+ POWER_SUPPLY_HEALTH_GOOD;
+
+ }
+ }
+ }
+
+ mutex_unlock(&mbi->batt_lock);
+
+}
+
+static void msic_batt_disconn(struct work_struct *work)
+{
+ int ret, event;
+ struct msic_power_module_info *mbi =
+ container_of(work, struct msic_power_module_info,
+ disconn_handler.work);
+
+ mutex_lock(&mbi->event_lock);
+ event = mbi->batt_event;
+ mutex_unlock(&mbi->event_lock);
+
+ if (event != USBCHRG_EVENT_SUSPEND &&
+ event != USBCHRG_EVENT_DISCONN) {
+ dev_warn(msic_dev, "%s:Not a Disconn or Suspend event\n",
+ __func__);
+ return ;
+ }
+
+ dev_dbg(msic_dev, "Stopping charging due to charger event: %s\n",
+ (event == USBCHRG_EVENT_SUSPEND) ? "SUSPEND" :
+ "DISCONNECT");
+ dump_registers();
+ ret = msic_batt_stop_charging(mbi);
+ if (ret) {
+ dev_warn(msic_dev, "%s: failed\n", __func__);
+ return;
+ }
+
+ mutex_lock(&mbi->batt_lock);
+ if (event == USBCHRG_EVENT_SUSPEND)
+ mbi->batt_props.status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ else
+ mbi->batt_props.status = POWER_SUPPLY_STATUS_DISCHARGING;
+ mutex_unlock(&mbi->batt_lock);
+
+ /* release the wake lock when charger is unplugged */
+ if (wake_lock_active(&mbi->wakelock))
+ wake_unlock(&mbi->wakelock);
+
+ power_supply_changed(&mbi->usb);
+}
+
+/**
+* msic_event_handler - msic event handler
+* @arg : private data pointer
+* @event: MSIC event
+* @cap : otg capabilities
+*
+*/
+static int msic_event_handler(void *arg, int event, struct otg_bc_cap *cap)
+{
+ struct msic_power_module_info *mbi =
+ (struct msic_power_module_info *)arg;
+
+ /* Update USB power supply info */
+ update_usb_ps_info(mbi, cap, event);
+
+ /* check for valid battery condition */
+ if (!mbi->is_batt_valid)
+ return 0;
+
+ mutex_lock(&mbi->event_lock);
+ if ((mbi->batt_event == event && event != USBCHRG_EVENT_UPDATE) ||
+ (!mbi->usr_chrg_enbl)) {
+ mutex_unlock(&mbi->event_lock);
+ return 0;
+ }
+ mbi->batt_event = event;
+ mutex_unlock(&mbi->event_lock);
+
+ switch (event) {
+ case USBCHRG_EVENT_CONNECT:
+ pm_runtime_get_sync(&mbi->pdev->dev);
+ case USBCHRG_EVENT_RESUME:
+ case USBCHRG_EVENT_UPDATE:
+ /*
+ * If previous event CONNECT and current is event is
+ * UPDATE, we have already queued the work.
+ * Its better to dequeue the previous work
+ * and add the new work to the queue.
+ */
+ cancel_delayed_work_sync(&mbi->connect_handler);
+ mbi->ch_params.vinilmt = cap->mA;
+ mbi->ch_params.chrg_type = cap->chrg_type;
+ dev_dbg(msic_dev, "CHRG TYPE:%d %d\n", cap->chrg_type, cap->mA);
+ mutex_lock(&mbi->event_lock);
+ mbi->refresh_charger = 1;
+ if (mbi->charging_mode == BATT_CHARGING_MODE_NONE)
+ mbi->charging_mode = BATT_CHARGING_MODE_NORMAL;
+ mutex_unlock(&mbi->event_lock);
+
+ /* Enable charger LED */
+ ipc_read_modify_chr_param_reg(mbi, MSIC_CHRG_LED_CNTL_REG,
+ MSIC_CHRG_LED_ENBL, 1);
+ /*Disable charger LOW Power Mode */
+ ipc_read_modify_chr_param_reg(mbi, MSIC_BATT_CHR_CHRCTRL_ADDR,
+ CHRCNTL_CHRG_LOW_PWR_ENBL, 0);
+
+ schedule_delayed_work(&mbi->connect_handler, 0);
+ break;
+ case USBCHRG_EVENT_DISCONN:
+ pm_runtime_put_sync(&mbi->pdev->dev);
+ case USBCHRG_EVENT_SUSPEND:
+ dev_dbg(msic_dev, "USB DISCONN or SUSPEND\n");
+ cancel_delayed_work_sync(&mbi->connect_handler);
+ schedule_delayed_work(&mbi->disconn_handler, 0);
+
+ mutex_lock(&mbi->event_lock);
+ mbi->refresh_charger = 0;
+ mbi->charging_mode = BATT_CHARGING_MODE_NONE;
+ mutex_unlock(&mbi->event_lock);
+
+ /* Disable charger LED */
+ ipc_read_modify_chr_param_reg(mbi, MSIC_CHRG_LED_CNTL_REG,
+ MSIC_CHRG_LED_ENBL, 0);
+ /*Enable charger LOW Power Mode */
+ ipc_read_modify_chr_param_reg(mbi, MSIC_BATT_CHR_CHRCTRL_ADDR,
+ CHRCNTL_CHRG_LOW_PWR_ENBL, 1);
+ break;
+ default:
+ dev_warn(msic_dev, "Invalid OTG Event:%s\n", __func__);
+ }
+ return 0;
+}
+
+static void msic_chrg_callback_worker(struct work_struct *work)
+{
+ struct otg_bc_cap cap;
+ struct msic_power_module_info *mbi =
+ container_of(work, struct msic_power_module_info,
+ chrg_callback_dwrk.work);
+ penwell_otg_query_charging_cap(&cap);
+ msic_event_handler(mbi, cap.current_event, &cap);
+}
+
+/*
+ * msic_charger_callback - callback for USB OTG
+ * @arg: device info structure
+ * @event: USB event
+ * @cap: charging capabilities
+ * Context: Interrupt Context can not sleep
+ *
+ * Will be called from the OTG driver.Depending on the event
+ * schedules a bottom half to enable or disable the charging.
+ */
+static int msic_charger_callback(void *arg, int event, struct otg_bc_cap *cap)
+{
+ struct msic_power_module_info *mbi =
+ (struct msic_power_module_info *)arg;
+
+ schedule_delayed_work(&mbi->chrg_callback_dwrk, 0);
+ return 0;
+}
+
+/**
+ * msic_status_monitor - worker function to monitor status
+ * @work: delayed work handler structure
+ * Context: Can sleep
+ *
+ * Will be called from the threaded IRQ function.
+ * Monitors status of the charge register and temperature.
+ */
+static void msic_status_monitor(struct work_struct *work)
+{
+ int chr_mode, chr_event;
+ unsigned int delay = CHARGE_STATUS_DELAY_JIFFIES;
+ struct msic_power_module_info *mbi =
+ container_of(work, struct msic_power_module_info,
+ chr_status_monitor.work);
+
+ pm_runtime_get_sync(&mbi->pdev->dev);
+
+ mutex_lock(&mbi->event_lock);
+ chr_mode = mbi->charging_mode;
+ chr_event = mbi->batt_event;
+ mutex_unlock(&mbi->event_lock);
+
+ /*update charger and battery health */
+ update_charger_health(mbi);
+ update_battery_health(mbi);
+
+ mutex_lock(&mbi->batt_lock);
+
+ if (chr_mode == BATT_CHARGING_MODE_MAINTENANCE)
+ mbi->batt_props.status = POWER_SUPPLY_STATUS_FULL;
+ else if (chr_mode == BATT_CHARGING_MODE_NORMAL)
+ mbi->batt_props.status = POWER_SUPPLY_STATUS_CHARGING;
+ else if (chr_event == USBCHRG_EVENT_SUSPEND)
+ mbi->batt_props.status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ else
+ mbi->batt_props.status = POWER_SUPPLY_STATUS_DISCHARGING;
+
+
+ /* Check charger Status bits */
+ if (is_charger_fault()) {
+
+ dev_warn(msic_dev, "charger fault detected\n");
+ mbi->batt_props.status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ }
+
+ mutex_unlock(&mbi->batt_lock);
+
+ power_supply_changed(&mbi->usb);
+ schedule_delayed_work(&mbi->chr_status_monitor, delay);
+
+ pm_runtime_put_sync(&mbi->pdev->dev);
+}
+
+/**
+ * msic_battery_interrupt_handler - msic battery interrupt handler
+ * Context: interrupt context
+ *
+ * MSIC battery interrupt handler which will be called on insertion
+ * of valid power source to charge the battery or an exception
+ * condition occurs.
+ */
+static irqreturn_t msic_battery_interrupt_handler(int id, void *dev)
+{
+ struct msic_power_module_info *mbi = dev;
+ u32 reg_int_val;
+
+ /* We have only one concurrent fifo reader
+ * and only one concurrent writer, so we are not
+ * using any lock to protect fifo.
+ */
+ if (unlikely(kfifo_is_full(&irq_fifo))) {
+ dev_warn(&mbi->pdev->dev, "KFIFO Full\n");
+ return IRQ_WAKE_THREAD;
+ }
+ /* Copy Interrupt registers locally */
+ reg_int_val = readl(mbi->msic_intr_iomap);
+ /* Add the Interrupt regs to FIFO */
+ kfifo_in(&irq_fifo, ®_int_val, IRQ_KFIFO_ELEMENT);
+
+ return IRQ_WAKE_THREAD;
+}
+
+/**
+ * msic_battery_thread_handler - msic battery threaded IRQ function
+ * Context: can sleep
+ *
+ * MSIC battery needs to either update the battery status as full
+ * if it detects battery full condition caused the interrupt or needs
+ * to enable battery charger if it detects usb and battery detect
+ * caused the source of interrupt.
+ */
+static irqreturn_t msic_battery_thread_handler(int id, void *dev)
+{
+ int ret;
+ unsigned char data[2];
+ struct msic_power_module_info *mbi = dev;
+ u32 tmp;
+ unsigned char intr_stat;
+ u32 log_intr;
+
+ /* We have only one concurrent fifo reader
+ * and only one concurrent writer, we are not
+ * using any lock to protect fifo.
+ */
+ if (unlikely(kfifo_is_empty(&irq_fifo))) {
+ dev_warn(msic_dev, "KFIFO Empty\n");
+ return IRQ_NONE;
+ }
+ /* Get the Interrupt regs state from FIFO */
+ ret = kfifo_out(&irq_fifo, &tmp, IRQ_KFIFO_ELEMENT);
+ if (ret != IRQ_KFIFO_ELEMENT) {
+ dev_warn(msic_dev, "KFIFO underflow\n");
+ return IRQ_NONE;
+ }
+
+ /* Even though some second level charger interrupts are masked by SCU
+ * the flags for these interrupts will be set in second level interrupt
+ * status register when SCU forwards the unmasked interrupts. Kernel
+ * should ignore the status for masked registers.*/
+
+ /* CHRINT Register */
+ data[0] = ((tmp & 0x00ff0000) >> 16) & ~(mbi->chrint_mask);
+ /* CHRINT1 Register */
+ data[1] = (tmp & 0xff000000) >> 24 & ~(mbi->chrint1_mask);
+
+ dev_dbg(msic_dev, "PWRSRC Int %x %x\n", tmp & 0xff,
+ (tmp & 0xff00) >> 8);
+ dev_dbg(msic_dev, "CHR Int %x %x\n", data[0], data[1]);
+
+ /* Saving the read interrupt value for printing later */
+ log_intr = tmp;
+
+ mutex_lock(&mbi->event_lock);
+ tmp = mbi->charging_mode;
+ mutex_unlock(&mbi->event_lock);
+
+ /* Check if charge complete */
+ if (data[1] & MSIC_BATT_CHR_CHRCMPLT_MASK)
+ dev_dbg(msic_dev, "CHRG COMPLT\n");
+
+ if ((data[0] & MSIC_BATT_CHR_TIMEEXP_MASK) &&
+ (tmp == BATT_CHARGING_MODE_NORMAL)) {
+ dev_dbg(msic_dev, "force suspend event\n");
+ msic_event_handler(mbi, USBCHRG_EVENT_SUSPEND, NULL);
+ }
+
+ if (data[1] & MSIC_BATT_CHR_WKVINDET_MASK) {
+ dev_dbg(msic_dev, "CHRG WeakVIN Detected\n");
+
+ /* Sometimes for USB unplug we are receiving WeakVIN
+ * interrupts,So as SW work around we will check the
+ * SPWRSRCINT SUSBDET bit to know the USB connection.
+ */
+ ret = intel_scu_ipc_ioread8(MSIC_BATT_CHR_SPWRSRCINT_ADDR,
+ &intr_stat);
+ if (ret)
+ handle_ipc_rw_status(ret, MSIC_BATT_CHR_SPWRSRCINT_ADDR,
+ MSIC_IPC_READ);
+ else if (!(intr_stat & MSIC_BATT_CHR_USBDET_MASK) &&
+ (tmp != BATT_CHARGING_MODE_NONE)) {
+ data[1] &= ~MSIC_BATT_CHR_WKVINDET_MASK;
+ dev_dbg(msic_dev, "force disconnect event\n");
+ msic_event_handler(mbi, USBCHRG_EVENT_DISCONN, NULL);
+ }
+ }
+
+ /* Check if an exception occurred */
+ if (data[0] || (data[1] & ~(MSIC_BATT_CHR_CHRCMPLT_MASK)))
+ msic_handle_exception(mbi, data[0], data[1]);
+
+ /* Check charger Status bits */
+ if (is_charger_fault()) {
+ mutex_lock(&mbi->batt_lock);
+ mbi->batt_props.status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ mutex_unlock(&mbi->batt_lock);
+
+ dev_info(msic_dev, "PWRSRC Int %x %x\n", log_intr & 0xff,
+ (log_intr & 0xff00) >> 8);
+ dev_info(msic_dev, "CHR Int %x %x\n", data[0], data[1]);
+ }
+
+ power_supply_changed(&mbi->usb);
+ return IRQ_HANDLED;
+}
+
+/**
+ * check_charger_conn - check charger connection and handle the state
+ * @mbi : charger device info
+ * Context: can sleep
+ *
+ */
+static int check_charger_conn(struct msic_power_module_info *mbi)
+{
+ int retval;
+ struct otg_bc_cap cap;
+ unsigned char data;
+
+ retval = intel_scu_ipc_ioread8(MSIC_BATT_CHR_SPWRSRCINT_ADDR,
+ &data);
+ if (retval) {
+ retval = handle_ipc_rw_status(retval,
+ MSIC_BATT_CHR_SPWRSRCINT_ADDR, MSIC_IPC_READ);
+ if (retval)
+ goto disable_chrg_block;
+ }
+
+ if (data & MSIC_BATT_CHR_USBDET_MASK) {
+ retval = penwell_otg_query_charging_cap(&cap);
+ if (retval) {
+ dev_warn(msic_dev, "%s(): usb otg power query "
+ "failed with error code %d\n", __func__,
+ retval);
+ goto disable_chrg_block;
+ }
+ /* Enable charging only if vinilmt is >= 100mA */
+ if (cap.mA >= 100) {
+ msic_event_handler(mbi, USBCHRG_EVENT_CONNECT, &cap);
+ return retval;
+ }
+ }
+
+disable_chrg_block:
+ /* Disable the charging block */
+ msic_batt_stop_charging(mbi);
+ /*Putting the charger in LOW Power Mode */
+ ipc_read_modify_chr_param_reg(mbi, MSIC_BATT_CHR_CHRCTRL_ADDR,
+ CHRCNTL_CHRG_LOW_PWR_ENBL, 1);
+ return retval;
+}
+
+/**
+ * set_chrg_enable - sysfs set api for charge_enable attribute
+ * Parameter as define by sysfs interface
+ * Context: can sleep
+ *
+ */
+static ssize_t set_chrg_enable(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ struct platform_device *pdev =
+ container_of(dev, struct platform_device, dev);
+ struct msic_power_module_info *mbi = platform_get_drvdata(pdev);
+ unsigned long value;
+ int retval, chr_mode;
+
+ if (strict_strtoul(buf, 10, &value))
+ return -EINVAL;
+
+ /* Allow only 0 to 4 for writing */
+ if (value < USER_SET_CHRG_DISABLE || value > USER_SET_CHRG_NOLMT)
+ return -EINVAL;
+
+ mutex_lock(&mbi->event_lock);
+ chr_mode = mbi->charging_mode;
+ mutex_unlock(&mbi->event_lock);
+
+ if (!value && (chr_mode != BATT_CHARGING_MODE_NONE)) {
+ dev_dbg(msic_dev, "User App charger disable !\n");
+ /* Disable charger before setting the usr_chrg_enbl */
+ msic_event_handler(mbi, USBCHRG_EVENT_SUSPEND, NULL);
+
+ mutex_lock(&mbi->event_lock);
+ mbi->usr_chrg_enbl = value;
+ mutex_unlock(&mbi->event_lock);
+
+ } else if (value && (chr_mode == BATT_CHARGING_MODE_NONE)) {
+ dev_dbg(msic_dev, "User App charger enable!\n");
+
+ /* enable usr_chrg_enbl before checking charger connection */
+ mutex_lock(&mbi->event_lock);
+ mbi->usr_chrg_enbl = value;
+ mutex_unlock(&mbi->event_lock);
+
+ retval = check_charger_conn(mbi);
+ if (retval)
+ dev_warn(msic_dev, "check_charger_conn failed\n");
+ } else {
+ mutex_lock(&mbi->event_lock);
+ mbi->refresh_charger = 1;
+ mbi->usr_chrg_enbl = value;
+ mutex_unlock(&mbi->event_lock);
+ }
+
+ return count;
+}
+
+/**
+ * get_chrg_enable - sysfs get api for charge_enable attribute
+ * Parameter as define by sysfs interface
+ * Context: can sleep
+ *
+ */
+static ssize_t get_chrg_enable(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct platform_device *pdev =
+ container_of(dev, struct platform_device, dev);
+ struct msic_power_module_info *mbi = platform_get_drvdata(pdev);
+ unsigned int val;
+
+ mutex_lock(&mbi->event_lock);
+ val = mbi->usr_chrg_enbl;
+ mutex_unlock(&mbi->event_lock);
+
+ return sprintf(buf, "%d\n", val);
+}
+
+/* get_is_power_supply_conn - sysfs get api for power_supply_conn attribute
+ * Parameter as defined by sysfs interface
+ * Context: can sleep
+ *
+ */
+static ssize_t get_is_power_supply_conn(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%d\n", !(intel_msic_is_current_sense_enabled()));
+}
+/**
+ * sfi_table_invalid_batt - default battery SFI table values to be
+ * used in case of invalid battery
+ *
+ * @sfi_table : sfi table pointer
+ * Context: can sleep
+ *
+ */
+static void sfi_table_invalid_batt(struct msic_batt_sfi_prop *sfi_table)
+{
+
+ /*
+ * In case of invalid battery we manually set
+ * the SFI parameters and limit the battery from
+ * charging, so platform will be in discharging mode
+ */
+ memcpy(sfi_table->batt_id, "UNKNOWN", sizeof("UNKNOWN"));
+ sfi_table->voltage_max = CHR_CHRVOLTAGE_SET_DEF;
+ sfi_table->capacity = DEFAULT_MAX_CAPACITY;
+ sfi_table->battery_type = POWER_SUPPLY_TECHNOLOGY_LION;
+ sfi_table->temp_mon_ranges = 0;
+
+}
+
+/**
+ * sfi_table_populate - Simple Firmware Interface table Populate
+ * @sfi_table: Simple Firmware Interface table structure
+ *
+ * SFI table has entries for the temperature limits
+ * which is populated in a local structure
+ */
+static int __init sfi_table_populate(struct sfi_table_header *table)
+{
+ struct sfi_table_simple *sb;
+ struct msic_batt_sfi_prop *pentry;
+ struct platform_device *pdev =
+ container_of(msic_dev, struct platform_device, dev);
+ struct msic_power_module_info *mbi = platform_get_drvdata(pdev);
+ int totentrs = 0, totlen = 0;
+
+ sb = (struct sfi_table_simple *)table;
+ if (!sb) {
+ dev_warn(msic_dev, "SFI: Unable to map BATT signature\n");
+ return -ENODEV;
+ }
+
+ totentrs = SFI_GET_NUM_ENTRIES(sb, struct msic_batt_sfi_prop);
+ if (totentrs) {
+ pentry = (struct msic_batt_sfi_prop *)sb->pentry;
+ totlen = totentrs * sizeof(*pentry);
+ memcpy(sfi_table, pentry, totlen);
+ mbi->is_batt_valid = true;
+ } else {
+ dev_warn(msic_dev, "Invalid battery detected\n");
+ sfi_table_invalid_batt(sfi_table);
+ mbi->is_batt_valid = false;
+ }
+
+ return 0;
+}
+
+/**
+ * init_batt_props - initialize battery properties
+ * @mbi: msic module device structure
+ * Context: can sleep
+ *
+ * init_batt_props function initializes the
+ * MSIC battery properties.
+ */
+static void init_batt_props(struct msic_power_module_info *mbi)
+{
+ unsigned char data;
+ int retval;
+
+ mbi->batt_event = USBCHRG_EVENT_DISCONN;
+ mbi->charging_mode = BATT_CHARGING_MODE_NONE;
+ mbi->usr_chrg_enbl = USER_SET_CHRG_NOLMT;
+
+ mbi->batt_props.status = POWER_SUPPLY_STATUS_DISCHARGING;
+ mbi->batt_props.health = POWER_SUPPLY_HEALTH_GOOD;
+ mbi->batt_props.present = MSIC_BATT_NOT_PRESENT;
+
+ /* read specific to determine the status */
+ retval = intel_scu_ipc_ioread8(MSIC_BATT_CHR_SPWRSRCINT_ADDR, &data);
+ if (retval)
+ handle_ipc_rw_status(retval, MSIC_BATT_CHR_SPWRSRCINT_ADDR,
+ MSIC_IPC_READ);
+
+ /* determine battery Presence */
+ if (data & MSIC_BATT_CHR_BATTDET_MASK)
+ mbi->batt_props.present = MSIC_BATT_PRESENT;
+ else
+ mbi->batt_props.present = MSIC_BATT_NOT_PRESENT;
+
+ /* Enable Status Register */
+ retval = intel_scu_ipc_iowrite8(CHR_STATUS_FAULT_REG,
+ CHR_STATUS_TMR_RST |
+ CHR_STATUS_STAT_ENBL);
+ if (retval)
+ handle_ipc_rw_status(retval,
+ CHR_STATUS_FAULT_REG, MSIC_IPC_WRITE);
+
+ /* Disable charger LED */
+ ipc_read_modify_chr_param_reg(mbi, MSIC_CHRG_LED_CNTL_REG,
+ MSIC_CHRG_LED_ENBL, 0);
+
+ /*
+ * Mask Battery Detect Interrupt, we are not
+ * handling battery jarring in the driver.
+ */
+ ipc_read_modify_reg(mbi, MSIC_BATT_CHR_MPWRSRCINT_ADDR,
+ MSIC_MPWRSRCINT_BATTDET, 1);
+}
+
+/**
+ * init_batt_thresholds - initialize battery thresholds
+ * @mbi: msic module device structure
+ * Context: can sleep
+ */
+static void init_batt_thresholds(struct msic_power_module_info *mbi)
+{
+ int ret;
+
+ batt_thrshlds->vbatt_sh_min = BATT_DEAD_CUTOFF_VOLT;
+ batt_thrshlds->vbatt_crit = BATT_CRIT_CUTOFF_VOLT;
+ batt_thrshlds->temp_high = MSIC_BATT_TEMP_MAX;
+ batt_thrshlds->temp_low = MSIC_BATT_TEMP_MIN;
+
+ /* Read the threshold data from SMIP */
+ dev_dbg(msic_dev, "[SMIP Read] offset: %x\n", BATT_SMIP_BASE_OFFSET);
+ ret = intel_scu_ipc_read_mip((u8 *) batt_thrshlds,
+ sizeof(struct batt_safety_thresholds),
+ BATT_SMIP_BASE_OFFSET, 1);
+ if (ret)
+ dev_warn(msic_dev, "%s: smip read failed\n", __func__);
+
+ dev_dbg(msic_dev, "vbatt shutdown: %d\n", batt_thrshlds->vbatt_sh_min);
+ dev_dbg(msic_dev, "vbatt_crit: %d\n", batt_thrshlds->vbatt_crit);
+ dev_dbg(msic_dev, "Temp High Lmt: %d\n", batt_thrshlds->temp_high);
+ dev_dbg(msic_dev, "Temp Low Lmt: %d\n", batt_thrshlds->temp_low);
+}
+
+/**
+ * init_charger_props - initialize charger properties
+ * @mbi: msic module device structure
+ * Context: can sleep
+ *
+ * init_charger_props function initializes the
+ * MSIC usb charger properties.
+ */
+static void init_charger_props(struct msic_power_module_info *mbi)
+{
+ mbi->usb_chrg_props.charger_present = MSIC_USB_CHARGER_NOT_PRESENT;
+ mbi->usb_chrg_props.charger_health = POWER_SUPPLY_HEALTH_UNKNOWN;
+ memcpy(mbi->usb_chrg_props.charger_model, "Unknown", sizeof("Unknown"));
+ memcpy(mbi->usb_chrg_props.charger_vender, "Unknown",
+ sizeof("Unknown"));
+}
+
+/**
+ * init_msic_regs - initialize msic registers
+ * @mbi: msic module device structure
+ * Context: can sleep
+ *
+ * init_msic_regs function initializes the
+ * MSIC registers like CV,Power Source LMT,etc..
+ */
+static int init_msic_regs(struct msic_power_module_info *mbi)
+{
+ static const u16 address[] = {
+ MSIC_BATT_CHR_WDTWRITE_ADDR, MSIC_BATT_CHR_PWRSRCLMT_ADDR,
+ MSIC_BATT_CHR_WDTWRITE_ADDR, MSIC_BATT_CHR_CHRCVOLTAGE_ADDR,
+ MSIC_BATT_CHR_WDTWRITE_ADDR, MSIC_BATT_CHR_CHRTTIME_ADDR,
+ MSIC_BATT_CHR_WDTWRITE_ADDR, MSIC_BATT_CHR_SPCHARGER_ADDR,
+ MSIC_BATT_CHR_WDTWRITE_ADDR, MSIC_BATT_CHR_CHRSTWDT_ADDR,
+ MSIC_BATT_CHR_WDTWRITE_ADDR, MSIC_BATT_CHR_CHRCTRL1_ADDR,
+ MSIC_BATT_CHR_WDTWRITE_ADDR, MSIC_BATT_CHR_LOWBATTDET_ADDR
+ };
+ static u8 data[] = {
+ WDTWRITE_UNLOCK_VALUE, CHR_PWRSRCLMT_SET_RANGE,
+ WDTWRITE_UNLOCK_VALUE,
+ CONV_VOL_DEC_MSICREG(CHR_CHRVOLTAGE_SET_DEF),
+ WDTWRITE_UNLOCK_VALUE, CHR_CHRTIME_SET_13HRS,
+ WDTWRITE_UNLOCK_VALUE,
+ (~CHR_SPCHRGER_LOWCHR_ENABLE & CHR_SPCHRGER_WEAKVIN_LVL1),
+ WDTWRITE_UNLOCK_VALUE, CHR_WDT_DISABLE,
+ WDTWRITE_UNLOCK_VALUE, MSIC_CHRG_EXTCHRDIS,
+ WDTWRITE_UNLOCK_VALUE, MSIC_BATT_CHR_SET_LOWBATTREG
+ };
+
+ return msic_write_multi(mbi, address, data, 14);
+}
+
+/**
+ * msic_battery_probe - msic battery initialize
+ * @pdev: msic battery platform device structure
+ * Context: can sleep
+ *
+ * MSIC battery initializes its internal data structure and other
+ * infrastructure components for it to work as expected.
+ */
+static int msic_battery_probe(struct platform_device *pdev)
+{
+ int retval;
+ int read_temp, batt_id_res;
+ uint8_t data;
+ struct msic_power_module_info *mbi = NULL;
+
+ mbi = kzalloc(sizeof(struct msic_power_module_info), GFP_KERNEL);
+ if (!mbi) {
+ dev_err(&pdev->dev, "%s(): memory allocation failed\n",
+ __func__);
+ return -ENOMEM;
+ }
+
+ sfi_table = kzalloc(sizeof(struct msic_batt_sfi_prop), GFP_KERNEL);
+ if (!sfi_table) {
+ dev_err(&pdev->dev, "%s(): memory allocation failed\n",
+ __func__);
+ kfree(mbi);
+ return -ENOMEM;
+ }
+ batt_thrshlds = kzalloc(sizeof(struct batt_safety_thresholds),
+ GFP_KERNEL);
+ if (!batt_thrshlds) {
+ dev_err(&pdev->dev, "%s(): memory allocation failed\n",
+ __func__);
+ kfree(sfi_table);
+ kfree(mbi);
+ return -ENOMEM;
+ }
+
+ mbi->pdev = pdev;
+ mbi->irq = platform_get_irq(pdev, 0);
+ platform_set_drvdata(pdev, mbi);
+ msic_dev = &pdev->dev;
+
+ /* initialize all required framework before enabling interrupts */
+
+ /* OTG Disconnect is being called from IRQ context
+ * so calling ipc function is not appropriate from otg callback
+ */
+ INIT_DELAYED_WORK(&mbi->disconn_handler, msic_batt_disconn);
+ INIT_DELAYED_WORK(&mbi->connect_handler, msic_batt_temp_charging);
+ INIT_DELAYED_WORK_DEFERRABLE(&mbi->chr_status_monitor,
+ msic_status_monitor);
+ INIT_DELAYED_WORK(&mbi->chrg_callback_dwrk, msic_chrg_callback_worker);
+ wake_lock_init(&mbi->wakelock, WAKE_LOCK_SUSPEND,
+ "msicbattery_wakelock");
+
+ /* Initialize mutex locks */
+ mutex_init(&mbi->usb_chrg_lock);
+ mutex_init(&mbi->batt_lock);
+ mutex_init(&mbi->ipc_rw_lock);
+ mutex_init(&mbi->event_lock);
+ mutex_init(&mbi->adc_val_lock);
+
+ /* Allocate ADC Channels */
+ mbi->adc_handle =
+ intel_mid_gpadc_alloc(MSIC_BATT_SENSORS,
+ MSIC_BATT_PACK_TEMP | CH_NEED_VCALIB |
+ CH_NEED_VREF,
+ MSIC_USB_VOLTAGE | CH_NEED_VCALIB,
+ MSIC_BATTID | CH_NEED_VREF | CH_NEED_VCALIB);
+ if (mbi->adc_handle == NULL)
+ dev_err(&pdev->dev, "ADC allocation failed\n");
+
+ mdf_multi_read_adc_regs(mbi, HYSTR_SAMPLE_MAX, 2, MSIC_ADC_BATTID_IDX,
+ &batt_id_res, MSIC_ADC_TEMP_IDX, &read_temp);
+
+ /* When no battery is present and the board is operated from
+ * a lab power supply, the battery thermistor is absent or
+ * the BATTID is less than 24kOhm */
+ if ((read_temp == -ERANGE) || (batt_id_res <= ADC_BATTID_24KOHM)) {
+ if (read_temp == -ERANGE)
+ dev_warn(msic_dev, "Power Supply detected on PR2B\n");
+ else
+ dev_warn(msic_dev,
+ "Power Supply detected on iCDKa/iCDKb\n");
+ mbi->current_sense_enabled = 0;
+ } else
+ mbi->current_sense_enabled = 1;
+
+ /* Invalid battery is reported when Populate
+ * data from SFI Table is failed*/
+ if (sfi_table_parse(SFI_SIG_OEM0, NULL, NULL, sfi_table_populate)
+ || (!mbi->current_sense_enabled)) {
+ sfi_table_invalid_batt(sfi_table);
+ mbi->is_batt_valid = false;
+ }
+
+ /* Initialize battery and charger Properties */
+ init_batt_props(mbi);
+ init_charger_props(mbi);
+ init_batt_thresholds(mbi);
+
+ /* Re Map Phy address space for MSIC regs */
+ mbi->msic_intr_iomap = ioremap_nocache(MSIC_SRAM_INTR_ADDR, 8);
+ if (!mbi->msic_intr_iomap) {
+ dev_err(&pdev->dev, "battery: ioremap Failed\n");
+ retval = -ENOMEM;
+ goto ioremap_intr_failed;
+ }
+
+ /* Init MSIC Registers */
+ retval = init_msic_regs(mbi);
+ if (retval < 0)
+ dev_err(&pdev->dev, "MSIC registers init failed\n");
+
+ /* register msic-usb with power supply subsystem */
+ mbi->usb.name = CHARGER_PS_NAME;
+ mbi->usb.type = POWER_SUPPLY_TYPE_USB;
+ mbi->usb.supplied_to = msic_power_supplied_to;
+ mbi->usb.num_supplicants = ARRAY_SIZE(msic_power_supplied_to);
+ mbi->usb.properties = msic_usb_props;
+ mbi->usb.num_properties = ARRAY_SIZE(msic_usb_props);
+ mbi->usb.get_property = msic_usb_get_property;
+ retval = power_supply_register(&pdev->dev, &mbi->usb);
+ if (retval) {
+ dev_err(&pdev->dev, "%s(): failed to register msic usb "
+ "device with power supply subsystem\n", __func__);
+ goto power_reg_failed_usb;
+ }
+
+ retval = device_create_file(&pdev->dev, &dev_attr_charge_enable);
+ if (retval)
+ goto sysfs1_create_failed;
+ retval = device_create_file(&pdev->dev, &dev_attr_power_supply_conn);
+ if (retval)
+ goto sysfs2_create_failed;
+ /* Register with OTG */
+ otg_handle = penwell_otg_register_bc_callback(msic_charger_callback,
+ (void *)mbi);
+ if (!otg_handle) {
+ dev_err(&pdev->dev, "battery: OTG Registration failed\n");
+ retval = -EBUSY;
+ goto otg_failed;
+ }
+
+ /* Init Runtime PM State */
+ pm_runtime_put_noidle(&mbi->pdev->dev);
+ pm_schedule_suspend(&mbi->pdev->dev, MSEC_PER_SEC);
+
+ /* Check if already exist a Charger connection */
+ retval = check_charger_conn(mbi);
+ if (retval)
+ dev_err(&pdev->dev, "check charger Conn failed\n");
+
+ mbi->chrint_mask = CHRINT_MASK;
+ mbi->chrint1_mask = CHRINT1_MASK;
+
+ retval = intel_scu_ipc_iowrite8(MSIC_BATT_CHR_MCHRINT_ADDR,
+ mbi->chrint_mask);
+ if (retval)
+ handle_ipc_rw_status(retval,
+ MSIC_BATT_CHR_MCHRINT_ADDR, MSIC_IPC_WRITE);
+
+ retval = intel_scu_ipc_iowrite8(MSIC_BATT_CHR_MCHRINT1_ADDR,
+ mbi->chrint1_mask);
+ if (retval)
+ handle_ipc_rw_status(retval,
+ MSIC_BATT_CHR_MCHRINT1_ADDR, MSIC_IPC_WRITE);
+
+ /* register interrupt */
+ retval = request_threaded_irq(mbi->irq, msic_battery_interrupt_handler,
+ msic_battery_thread_handler,
+ 0, DRIVER_NAME, mbi);
+ if (retval) {
+ dev_err(&pdev->dev, "%s(): cannot get IRQ\n", __func__);
+ goto requestirq_failed;
+ }
+
+ /*
+ * When no battery is present and the board is operated from
+ * a lab power supply, the battery thermistor is absent.
+ * In this case, the MSIC reports emergency temperature warnings,
+ * which must be ignored, to avoid a rain of interrupts
+ * (KFIFO_FULL messages)
+ * By reading the thermistor value on BPTHERM1 during driver probe
+ * it is possible to detect operation without a battery and
+ * mask the undesired MSIC interrupt in this case
+ *
+ */
+
+ if (read_temp == -ERANGE) {
+ dev_warn(msic_dev,
+ "Temp read out of range:"
+ "disabling BATTOTP interrupts");
+
+ retval = intel_scu_ipc_ioread8(MSIC_BATT_CHR_MCHRINT_ADDR,
+ &data);
+ if (retval) {
+ retval = handle_ipc_rw_status(retval,
+ MSIC_BATT_CHR_MCHRINT_ADDR, MSIC_IPC_READ);
+ if (retval)
+ return retval;
+ }
+
+ /* Applying BATTOTP INT mask */
+ data |= MSIC_BATT_CHR_BATTOTP_MASK;
+ retval = intel_scu_ipc_iowrite8(MSIC_BATT_CHR_MCHRINT_ADDR,
+ data);
+ if (retval) {
+ retval = handle_ipc_rw_status(retval,
+ MSIC_BATT_CHR_MCHRINT_ADDR, MSIC_IPC_WRITE);
+ return retval;
+ }
+ }
+
+ /* Start the status monitoring worker */
+ schedule_delayed_work(&mbi->chr_status_monitor, 0);
+ return retval;
+
+requestirq_failed:
+ penwell_otg_unregister_bc_callback(otg_handle);
+otg_failed:
+ device_remove_file(&pdev->dev, &dev_attr_power_supply_conn);
+sysfs2_create_failed:
+ device_remove_file(&pdev->dev, &dev_attr_charge_enable);
+sysfs1_create_failed:
+ power_supply_unregister(&mbi->usb);
+power_reg_failed_usb:
+ iounmap(mbi->msic_intr_iomap);
+ioremap_intr_failed:
+ kfree(batt_thrshlds);
+ kfree(sfi_table);
+ kfree(mbi);
+
+ return retval;
+}
+
+static void do_exit_ops(struct msic_power_module_info *mbi)
+{
+ /* disable MSIC Charger */
+ mutex_lock(&mbi->batt_lock);
+ if (mbi->batt_props.status != POWER_SUPPLY_STATUS_DISCHARGING)
+ msic_batt_stop_charging(mbi);
+ mutex_unlock(&mbi->batt_lock);
+}
+
+/**
+ * msic_battery_remove - msic battery finalize
+ * @pdev: msic battery platform device structure
+ * Context: can sleep
+ *
+ * MSIC battery finalizes its internal data structure and other
+ * infrastructure components that it initialized in
+ * msic_battery_probe.
+ */
+static int msic_battery_remove(struct platform_device *pdev)
+{
+ struct msic_power_module_info *mbi = platform_get_drvdata(pdev);
+
+ if (mbi) {
+ penwell_otg_unregister_bc_callback(otg_handle);
+ flush_scheduled_work();
+ intel_mid_gpadc_free(mbi->adc_handle);
+ free_irq(mbi->irq, mbi);
+ pm_runtime_get_noresume(&mbi->pdev->dev);
+ do_exit_ops(mbi);
+ if (mbi->msic_intr_iomap != NULL)
+ iounmap(mbi->msic_intr_iomap);
+ device_remove_file(&pdev->dev, &dev_attr_charge_enable);
+ device_remove_file(&pdev->dev, &dev_attr_power_supply_conn);
+ power_supply_unregister(&mbi->usb);
+ wake_lock_destroy(&mbi->wakelock);
+
+ kfree(batt_thrshlds);
+ kfree(sfi_table);
+ kfree(mbi);
+ }
+
+ return 0;
+}
+
+static int battery_reboot_notifier_callback(struct notifier_block *notifier,
+ unsigned long event, void *data)
+{
+ struct platform_device *pdev = container_of(msic_dev,
+ struct platform_device, dev);
+ struct msic_power_module_info *mbi = platform_get_drvdata(pdev);
+
+ if (mbi)
+ do_exit_ops(mbi);
+
+ return NOTIFY_OK;
+}
+
+#ifdef CONFIG_PM
+static int msic_battery_suspend(struct device *dev)
+{
+ struct msic_power_module_info *mbi = dev_get_drvdata(dev);
+ int event;
+
+ mutex_lock(&mbi->event_lock);
+ event = mbi->batt_event;
+ mutex_unlock(&mbi->event_lock);
+
+ if (event == USBCHRG_EVENT_CONNECT ||
+ event == USBCHRG_EVENT_UPDATE || event == USBCHRG_EVENT_RESUME) {
+
+ msic_event_handler(mbi, USBCHRG_EVENT_SUSPEND, NULL);
+ dev_dbg(&mbi->pdev->dev, "Forced suspend\n");
+ }
+
+ cancel_delayed_work_sync(&mbi->chr_status_monitor);
+
+ return 0;
+}
+
+static int msic_battery_resume(struct device *dev)
+{
+ int retval = 0;
+ struct msic_power_module_info *mbi = dev_get_drvdata(dev);
+ int event;
+
+ mutex_lock(&mbi->event_lock);
+ event = mbi->batt_event;
+ mutex_unlock(&mbi->event_lock);
+
+ if (event == USBCHRG_EVENT_SUSPEND || event == USBCHRG_EVENT_DISCONN) {
+ /* Check if already exist a Charger connection */
+ retval = check_charger_conn(mbi);
+ if (retval)
+ dev_warn(msic_dev, "check_charger_conn failed\n");
+ }
+
+ schedule_delayed_work(&mbi->chr_status_monitor, 0);
+ return retval;
+}
+#else
+#define msic_battery_suspend NULL
+#define msic_battery_resume NULL
+#endif
+
+#ifdef CONFIG_PM_RUNTIME
+static int msic_runtime_suspend(struct device *dev)
+{
+
+ /* ToDo: Check for MSIC Power rails */
+ dev_dbg(dev, "%s called\n", __func__);
+ return 0;
+}
+
+static int msic_runtime_resume(struct device *dev)
+{
+ /* ToDo: Check for MSIC Power rails */
+ dev_dbg(dev, "%s called\n", __func__);
+ return 0;
+}
+
+static int msic_runtime_idle(struct device *dev)
+{
+ struct platform_device *pdev =
+ container_of(dev, struct platform_device, dev);
+ struct msic_power_module_info *mbi = platform_get_drvdata(pdev);
+ int event;
+
+ dev_dbg(dev, "%s called\n", __func__);
+
+ mutex_lock(&mbi->event_lock);
+ event = mbi->batt_event;
+ mutex_unlock(&mbi->event_lock);
+
+ if (event == USBCHRG_EVENT_CONNECT ||
+ event == USBCHRG_EVENT_UPDATE || event == USBCHRG_EVENT_RESUME) {
+
+ dev_warn(&mbi->pdev->dev, "%s: device busy\n", __func__);
+
+ return -EBUSY;
+ }
+
+ return 0;
+}
+#else
+#define msic_runtime_suspend NULL
+#define msic_runtime_resume NULL
+#define msic_runtime_idle NULL
+#endif
+/*********************************************************************
+ * Driver initialisation and finalization
+ *********************************************************************/
+
+static const struct platform_device_id battery_id_table[] = {
+ {"msic_battery", 1},
+};
+
+static const struct dev_pm_ops msic_batt_pm_ops = {
+ .suspend = msic_battery_suspend,
+ .resume = msic_battery_resume,
+ .runtime_suspend = msic_runtime_suspend,
+ .runtime_resume = msic_runtime_resume,
+ .runtime_idle = msic_runtime_idle,
+};
+
+static struct platform_driver msic_battery_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ .pm = &msic_batt_pm_ops,
+ },
+ .probe = msic_battery_probe,
+ .remove = __devexit_p(msic_battery_remove),
+ .id_table = battery_id_table,
+};
+
+static int __init msic_battery_module_init(void)
+{
+ int ret;
+
+ ret = platform_driver_register(&msic_battery_driver);
+ if (ret)
+ dev_err(msic_dev, "driver_register failed");
+
+ if (register_reboot_notifier(&battery_reboot_notifier))
+ dev_warn(msic_dev, "Battery: Unable to register reboot notifier");
+
+ return ret;
+}
+
+static void __exit msic_battery_module_exit(void)
+{
+ unregister_reboot_notifier(&battery_reboot_notifier);
+ platform_driver_unregister(&msic_battery_driver);
+}
+
+late_initcall_async(msic_battery_module_init);
+module_exit(msic_battery_module_exit);
+
+MODULE_AUTHOR("Ramakrishna Pallala <ramakrishna.pallala@intel.com>");
+MODULE_AUTHOR("Anantha Narayanan <anantha.narayanan@intel.com>");
+MODULE_AUTHOR("Ananth Krishna <ananth.krishna.r@intel.com>");
+MODULE_DESCRIPTION("Intel Medfield MSIC Battery Driver");
+MODULE_LICENSE("GPL");
/*
- * Fuel gauge driver for Maxim 17042 / 8966 / 8997
+ * max17042_battery.c - 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
* This driver is based on max17040_battery.c
*/
+#include <linux/module.h>
#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
#include <linux/slab.h>
#include <linux/i2c.h>
-#include <linux/mod_devicetable.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <linux/pm_runtime.h>
#include <linux/power_supply.h>
#include <linux/power/max17042_battery.h>
+#include <linux/reboot.h>
+#include <linux/delay.h>
+#include <linux/notifier.h>
+
+/* Status register bits */
+#define STATUS_POR_BIT (1 << 1)
+#define STATUS_BST_BIT (1 << 3)
+#define STATUS_VMN_BIT (1 << 8)
+#define STATUS_TMN_BIT (1 << 9)
+#define STATUS_SMN_BIT (1 << 10)
+#define STATUS_BI_BIT (1 << 11)
+#define STATUS_VMX_BIT (1 << 12)
+#define STATUS_TMX_BIT (1 << 13)
+#define STATUS_SMX_BIT (1 << 14)
+#define STATUS_BR_BIT (1 << 15)
+
+#define MAX17042_IC_VERSION 0x0092
+
+#define MAX_VOLT_THRLD 0xD2 /* 4200mv */
+#define MIN_VOLT_THRLD 0xBC /* 3760mv */
+
+#define MAX_TEMP_THRLD 0x3C /* 60 Degrees */
+#define MIN_TEMP_THRLD 0x0 /* 0 Degrees */
+
+/* Interrupt mask bits */
+#define CONFIG_ALRT_BIT_ENBL (1 << 2)
+#define STATUS_INTR_VOLT_BIT (1 << 12)
+#define STATUS_INTR_TEMP_BIT (1 << 13)
+#define STATUS_INTR_SOC_BIT (1 << 14)
+
+#define VFSOC0_LOCK 0x0000
+#define VFSOC0_UNLOCK 0x0080
+#define FG_MODEL_UNLOCK1 0X0059
+#define FG_MODEL_UNLOCK2 0X00C4
+#define FG_MODEL_LOCK1 0X0000
+#define FG_MODEL_LOCK2 0X0000
+
+#define dQ_ACC_DIV 0x4
+#define dP_ACC_100 0x1900
+#define dP_ACC_200 0x3200
+
+#define NTC_47K_TGAIN 0xE4E4
+#define NTC_47K_TOFF 0x2F1D
+
+#define BATT_CHRG_FULL_DES 1550000
+#define MAX17042_VOLT_CONV_FCTR 625
+#define MAX17042_CURR_CONV_FCTR 156
+#define MAX17042_CHRG_CONV_FCTR 500
+
+#define MAX17042_TEMP_SIGN_MASK 0x8000
+
+#define MAX17042_MAX_MEM (0xFF + 1)
+
+#define MAX17042_MODEL_MUL_FACTOR(a) ((a * 10) / 7)
+#define MAX17042_MODEL_DIV_FACTOR(a) ((a * 7) / 10)
+#define CONSTANT_TEMP_IN_POWER_SUPPLY 350
+#define POWER_SUPPLY_VOLT_MIN_THRESHOLD 3500000
+
+#define CYCLES_ROLLOVER_CUTOFF 0x0100
+#define MAX17042_DEF_RO_LRNCFG 0x0076
+
+#define MAX17042_SIGN_INDICATOR 0x8000
+
+#define BYTE_VALUE 1
+#define WORD_VALUE 0
enum max17042_register {
MAX17042_STATUS = 0x00,
MAX17042_SOC = 0x0D,
MAX17042_AvSOC = 0x0E,
MAX17042_RemCap = 0x0F,
- MAX17402_FullCAP = 0x10,
+ MAX17042_FullCAP = 0x10,
MAX17042_TTE = 0x11,
MAX17042_V_empty = 0x12,
MAX17042_ManName = 0x20,
MAX17042_DevName = 0x21,
MAX17042_DevChem = 0x22,
+ MAX17042_FullCAPNom = 0x23,
MAX17042_TempNom = 0x24,
MAX17042_TempCold = 0x25,
MAX17042_CGAIN = 0x2E,
MAX17042_COFF = 0x2F,
- MAX17042_Q_empty = 0x33,
+ MAX17042_SOCempty = 0x33,
MAX17042_T_empty = 0x34,
+ MAX17042_FullCAP0 = 0x35,
+ MAX17042_LAvg_empty = 0x36,
+ MAX17042_FCTC = 0x37,
MAX17042_RCOMP0 = 0x38,
MAX17042_TempCo = 0x39,
- MAX17042_Rx = 0x3A,
- MAX17042_T_empty0 = 0x3B,
+ MAX17042_ETC = 0x3A,
+ MAX17042_K_empty0 = 0x3B,
MAX17042_TaskPeriod = 0x3C,
MAX17042_FSTAT = 0x3D,
MAX17042_SHDNTIMER = 0x3F,
+ MAX17042_dQacc = 0x45,
+ MAX17042_dPacc = 0x46,
+ MAX17042_VFSOC0 = 0x48,
MAX17042_VFRemCap = 0x4A,
MAX17042_QH = 0x4D,
MAX17042_QL = 0x4E,
+
+ MAX17042_VFSOC0Enable = 0x60,
+ MAX17042_MLOCKReg1 = 0x62,
+ MAX17042_MLOCKReg2 = 0x63,
+ MAX17042_MODELChrTbl = 0x80,
+ MAX17042_OCV = 0xEE,
+ MAX17042_OCVInternal = 0xFB,
+ MAX17042_VFSOC = 0xFF,
+
+};
+
+#define DRV_NAME "max17042_battery"
+#define DEV_NAME "max17042"
+
+/* No of times we should retry on -EAGAIN error */
+#define NR_RETRY_CNT 3
+
+/* No of cell characterization words to be written to max17042 */
+#define CELL_CHAR_TBL_SAMPLES 48
+
+static uint16_t cell_char_tbl[] = {
+ /* Data to be written from 0x80h */
+ 0xA250, 0xB720, 0xB800, 0xB880, 0xB920, 0xBA00, 0xBA60, 0xBBF0,
+ 0xBCF0, 0xBE50, 0xC060, 0xC2D0, 0xC520, 0xC750, 0xCA00, 0xD090,
+ /* Data to be written from 0x90h */
+ 0x0120, 0x1C80, 0x0470, 0x0440, 0x0100, 0x5500, 0x0960, 0x2410,
+ 0x2250, 0x15F0, 0x0BD0, 0x0D00, 0x0B00, 0x0BB0, 0x08A0, 0x08A0,
+ /* Data to be written from 0xA0h */
+ 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,
+ 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,
};
+struct max17042_config_data {
+ /*
+ * if config_init is 0, which means new
+ * configuration has been loaded in that case
+ * we need to perform complete init of chip
+ */
+ u16 size;
+ u8 table_type;
+ u8 config_init;
+
+ u16 rcomp0;
+ u16 tempCo;
+ u16 kempty0;
+ u16 full_cap;
+ u16 cycles;
+ u16 full_capnom;
+ u16 soc_empty;
+ u16 ichgt_term;
+ u16 design_cap;
+ u16 etc;
+ u16 rsense;
+ u16 cfg;
+ u16 learn_cfg;
+ u16 filter_cfg;
+ u16 relax_cfg;
+ u16 cell_char_tbl[CELL_CHAR_TBL_SAMPLES];
+} __packed;
+
struct max17042_chip {
struct i2c_client *client;
struct power_supply battery;
struct max17042_platform_data *pdata;
+ struct mutex batt_lock;
+ struct mutex init_lock;
+
+ int present;
+ int status;
+ int health;
+ int technology;
+ int charge_full_des;
+ struct delayed_work init_worker;
+};
+
+#ifdef CONFIG_DEBUG_FS
+static struct dentry *max17042_dbgfs_root;
+static char max17042_dbg_regs[MAX17042_MAX_MEM][4];
+#endif
+
+static int max17042_reboot_callback(struct notifier_block *nfb,
+ unsigned long event, void *data);
+
+static struct notifier_block max17042_reboot_notifier_block = {
+ .notifier_call = max17042_reboot_callback,
+ .priority = 0,
+};
+
+static void set_soc_intr_thresholds(struct max17042_chip *chip, u16 off);
+static void save_runtime_params(struct max17042_chip *chip);
+static u16 fg_vfSoc;
+static struct max17042_config_data *fg_conf_data;
+static struct i2c_client *max17042_client;
+
+static enum power_supply_property max17042_battery_props[] = {
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_AVG,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CURRENT_AVG,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+ POWER_SUPPLY_PROP_CHARGE_FULL,
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
};
static int max17042_write_reg(struct i2c_client *client, u8 reg, u16 value)
{
- int ret = i2c_smbus_write_word_data(client, reg, value);
+ int ret, i;
+
+ for (i = 0; i < NR_RETRY_CNT; i++) {
+ ret = i2c_smbus_write_word_data(client, reg, value);
+ if (ret == -EAGAIN || ret == -ETIMEDOUT)
+ continue;
+ else
+ break;
+ }
if (ret < 0)
- dev_err(&client->dev, "%s: err %d\n", __func__, ret);
+ dev_err(&client->dev, "I2C SMbus Write error:%d\n", ret);
return ret;
}
static int max17042_read_reg(struct i2c_client *client, u8 reg)
{
- int ret = i2c_smbus_read_word_data(client, reg);
+ int ret, i;
+
+ for (i = 0; i < NR_RETRY_CNT; i++) {
+ ret = i2c_smbus_read_word_data(client, reg);
+ if (ret == -EAGAIN || ret == -ETIMEDOUT)
+ continue;
+ else
+ break;
+ }
if (ret < 0)
- dev_err(&client->dev, "%s: err %d\n", __func__, ret);
+ dev_err(&client->dev, "I2C SMbus Read error:%d\n", 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_write_verify_reg(struct i2c_client *client,
+ u8 reg, u16 value)
+{
+ int ret;
+
+ /* Write the value to register */
+ ret = max17042_write_reg(client, reg, value);
+
+ /* Read the value from register */
+ ret = max17042_read_reg(client, reg);
+
+ /* compare the both the values */
+ if (value != ret)
+ dev_err(&client->dev,
+ "write verify failed on Register:0x%x\n", reg);
+
+ return ret;
+}
+
+static int max17042_reg_read_modify(struct i2c_client *client, u8 reg,
+ u16 val, int bit_set)
+{
+ u16 data;
+ int ret;
+
+ data = max17042_read_reg(client, reg);
+
+ if (bit_set)
+ data |= val;
+ else
+ data &= (~val);
+
+ ret = max17042_write_reg(client, reg, data);
+ return ret;
+}
+
+static irqreturn_t max17042_intr_handler(int id, void *dev)
+{
+ return IRQ_WAKE_THREAD;
+}
+
+static irqreturn_t max17042_thread_handler(int id, void *dev)
+{
+ struct max17042_chip *chip = dev;
+ u16 val;
+
+ pm_runtime_get_sync(&chip->client->dev);
+
+ mutex_lock(&chip->batt_lock);
+ val = max17042_read_reg(chip->client, MAX17042_STATUS);
+ if (val & STATUS_INTR_VOLT_BIT)
+ dev_info(&chip->client->dev, "Volatge threshold INTR\n");
+
+ if (val & STATUS_INTR_TEMP_BIT)
+ dev_info(&chip->client->dev, "Temperature threshold INTR\n");
+
+ if (val & STATUS_INTR_SOC_BIT) {
+ dev_info(&chip->client->dev, "SOC threshold INTR\n");
+ set_soc_intr_thresholds(chip, 1);
+ }
+ mutex_unlock(&chip->batt_lock);
+
+ power_supply_changed(&chip->battery);
+ pm_runtime_put_sync(&chip->client->dev);
+ return IRQ_HANDLED;
+}
+
+static short adjust_sign_value(int value, int is_byte)
+{
+ short result, temp = (short)value;
+ if (temp & MAX17042_SIGN_INDICATOR) {
+
+ if (is_byte) {
+ result = (~temp) >> 8;
+ result &= 0xff;
+ } else {
+ result = ~temp;
+ }
+
+ result++;
+ result *= -1;
+ } else {
+ if (is_byte)
+ result = temp >> 8;
+ else
+ result = temp;
+ }
+
+ return result;
+}
+
+static int read_batt_pack_temp(struct max17042_chip *chip, int *temp)
+{
+ int ret;
+ u16 val;
+
+ /* Read battery pack temperature */
+ if (chip->pdata->battery_pack_temp) {
+ ret = chip->pdata->battery_pack_temp(temp);
+ if (ret < 0)
+ goto temp_read_err;
+ val = (0xff & (char)*temp) << 8;
+ ret = max17042_write_reg(chip->client, MAX17042_TEMP, val);
+ if (ret < 0)
+ goto temp_read_err;
+ } else {
+ ret = max17042_read_reg(chip->client, MAX17042_TEMP);
+ if (ret < 0)
+ goto temp_read_err;
+
+ /* MAX17042_TEMP register gives the signed
+ * value and we are ignoring the lower byte
+ * which represents the decimal point */
+
+ *temp = adjust_sign_value(ret, BYTE_VALUE);
+ }
+
+ return 0;
+
+temp_read_err:
+ dev_err(&chip->client->dev, "BP Temp read error:%d", ret);
+ return ret;
+}
static int max17042_get_property(struct power_supply *psy,
enum power_supply_property psp,
{
struct max17042_chip *chip = container_of(psy,
struct max17042_chip, battery);
+ short int cur;
+ int volt_ocv, ret, batt_temp;
+ mutex_lock(&chip->batt_lock);
switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = chip->status;
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = chip->health;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = chip->present;
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = chip->technology;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+ val->intval = chip->charge_full_des;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ ret = max17042_read_reg(chip->client, MAX17042_RepCap);
+ if (ret < 0)
+ goto ps_prop_read_err;
+ val->intval = ret * MAX17042_CHRG_CONV_FCTR;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL:
+ ret = max17042_read_reg(chip->client, MAX17042_FullCAP);
+ if (ret < 0)
+ goto ps_prop_read_err;
+ val->intval = ret * MAX17042_CHRG_CONV_FCTR;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ ret = max17042_read_reg(chip->client, MAX17042_Current);
+ if (ret < 0)
+ goto ps_prop_read_err;
+
+ cur = adjust_sign_value(ret, WORD_VALUE);
+
+ if (fg_conf_data->rsense)
+ val->intval = (cur * MAX17042_CURR_CONV_FCTR)
+ / fg_conf_data->rsense;
+ else
+ val->intval = cur * MAX17042_CURR_CONV_FCTR;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_AVG:
+ ret = max17042_read_reg(chip->client, MAX17042_AvgCurrent);
+ if (ret < 0)
+ goto ps_prop_read_err;
+
+ cur = adjust_sign_value(ret, WORD_VALUE);
+
+ if (fg_conf_data->rsense)
+ val->intval = (cur * MAX17042_CURR_CONV_FCTR)
+ / fg_conf_data->rsense;
+ else
+ val->intval = cur * MAX17042_CURR_CONV_FCTR;
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ if (!chip->pdata->enable_current_sense) {
+ val->intval = CONSTANT_TEMP_IN_POWER_SUPPLY;
+ break;
+ }
+ ret = read_batt_pack_temp(chip, &batt_temp);
+ if (ret < 0)
+ goto ps_prop_read_err;
+ /*
+ * Temperature is measured in units of degrees celcius, the
+ * power_supply class measures temperature in tenths of degrees
+ * celsius.
+ */
+ val->intval = batt_temp * 10;
+ break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
- val->intval = max17042_read_reg(chip->client,
- MAX17042_VCELL) * 83; /* 1000 / 12 = 83 */
+ ret = max17042_read_reg(chip->client, MAX17042_OCVInternal);
+ if (ret < 0)
+ goto ps_prop_read_err;
+ val->intval = (ret >> 3) * MAX17042_VOLT_CONV_FCTR;
break;
case POWER_SUPPLY_PROP_VOLTAGE_AVG:
- val->intval = max17042_read_reg(chip->client,
- MAX17042_AvgVCELL) * 83;
+ ret = max17042_read_reg(chip->client, MAX17042_AvgVCELL);
+ if (ret < 0)
+ goto ps_prop_read_err;
+ val->intval = (ret >> 3) * MAX17042_VOLT_CONV_FCTR;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
+ ret = max17042_read_reg(chip->client, MAX17042_V_empty);
+ if (ret < 0)
+ goto ps_prop_read_err;
+ val->intval = (ret >> 7) * 10000; /* Units of LSB = 10mV */
break;
case POWER_SUPPLY_PROP_CAPACITY:
- val->intval = max17042_read_reg(chip->client,
- MAX17042_SOC) / 256;
+ /* FIXME: WA to avoid modem crash, should be removed once
+ * everybody starts using the PR2 POR batteries */
+ ret = max17042_read_reg(chip->client, MAX17042_OCVInternal);
+ if (ret < 0)
+ goto ps_prop_read_err;
+ volt_ocv = (ret >> 3) * MAX17042_VOLT_CONV_FCTR;
+ /* Check if the OCV is less than
+ * IA_APPS_RUN(3.6V) Threshold */
+ if ((chip->pdata->enable_current_sense && volt_ocv <= 3600000)
+ || (chip->health == POWER_SUPPLY_HEALTH_DEAD)) {
+ val->intval = 0;
+ break;
+ }
+ if (!chip->pdata->enable_current_sense &&
+ volt_ocv <= POWER_SUPPLY_VOLT_MIN_THRESHOLD) {
+ val->intval = 0;
+ break;
+ }
+ /* If current sensing is not enabled then read the
+ * voltage based fuel gauge register for SOC */
+ if (chip->pdata->enable_current_sense)
+ ret = max17042_read_reg(chip->client, MAX17042_RepSOC);
+ else
+ ret = max17042_read_reg(chip->client, MAX17042_VFSOC);
+ if (ret < 0)
+ goto ps_prop_read_err;
+ val->intval = ret >> 8;
+ /* Check if MSB of lower byte is set
+ * then round off the SOC to higher digit
+ */
+ if (ret & 0x80)
+ val->intval += 1;
+
+ if (val->intval > 100)
+ val->intval = 100;
break;
default:
+ mutex_unlock(&chip->batt_lock);
return -EINVAL;
}
+
+ mutex_unlock(&chip->batt_lock);
+ return 0;
+
+ps_prop_read_err:
+ mutex_unlock(&chip->batt_lock);
+ return ret;
+}
+
+static void init_fg_config_data(void)
+{
+ fg_conf_data->cfg = 0x2210;
+ fg_conf_data->learn_cfg = 0x0076;
+ fg_conf_data->filter_cfg = 0x87A4;
+ fg_conf_data->relax_cfg = 0x506B;
+ memcpy(&fg_conf_data->cell_char_tbl, cell_char_tbl,
+ sizeof(cell_char_tbl));
+ fg_conf_data->rcomp0 = 0x0056;
+ fg_conf_data->tempCo = 0x1621;
+ fg_conf_data->etc = 0x2D51;
+ fg_conf_data->kempty0 = 0x0350;
+ fg_conf_data->ichgt_term = 0x0140;
+ fg_conf_data->full_cap = 3100;
+ fg_conf_data->design_cap = 3100;
+ fg_conf_data->full_capnom = 3100;
+ fg_conf_data->rsense = 1;
+}
+
+static void dump_fg_conf_data(struct max17042_chip *chip)
+{
+ int i;
+
+ dev_info(&chip->client->dev, "size:%x\n", fg_conf_data->size);
+ dev_info(&chip->client->dev, "table_type:%x\n",
+ fg_conf_data->table_type);
+ dev_info(&chip->client->dev, "config_init:%x\n",
+ fg_conf_data->config_init);
+ dev_info(&chip->client->dev, "rcomp0:%x\n", fg_conf_data->rcomp0);
+ dev_info(&chip->client->dev, "tempCo:%x\n", fg_conf_data->tempCo);
+ dev_info(&chip->client->dev, "kempty0:%x\n", fg_conf_data->kempty0);
+ dev_info(&chip->client->dev, "full_cap:%x\n", fg_conf_data->full_cap);
+ dev_info(&chip->client->dev, "cycles:%x\n", fg_conf_data->cycles);
+ dev_info(&chip->client->dev, "full_capnom:%x\n",
+ fg_conf_data->full_capnom);
+ dev_info(&chip->client->dev, "soc_empty:%x\n",
+ fg_conf_data->soc_empty);
+ dev_info(&chip->client->dev, "ichgt_term:%x\n",
+ fg_conf_data->ichgt_term);
+ dev_info(&chip->client->dev, "design_cap:%x\n",
+ fg_conf_data->design_cap);
+ dev_info(&chip->client->dev, "etc:%x\n", fg_conf_data->etc);
+ dev_info(&chip->client->dev, "rsense:%x\n", fg_conf_data->rsense);
+ dev_info(&chip->client->dev, "cfg:%x\n", fg_conf_data->cfg);
+ dev_info(&chip->client->dev, "learn_cfg:%x\n",
+ fg_conf_data->learn_cfg);
+ dev_info(&chip->client->dev, "filter_cfg:%x\n",
+ fg_conf_data->filter_cfg);
+ dev_info(&chip->client->dev, "relax_cfg:%x\n", fg_conf_data->relax_cfg);
+
+ for (i = 0; i < CELL_CHAR_TBL_SAMPLES; i++)
+ dev_info(&chip->client->dev, "%x, ",
+ fg_conf_data->cell_char_tbl[i]);
+ dev_info(&chip->client->dev, "\n");
+}
+
+static void enable_soft_POR(struct max17042_chip *chip)
+{
+ u16 val = 0x0000;
+
+ max17042_write_reg(chip->client, MAX17042_MLOCKReg1, val);
+ max17042_write_reg(chip->client, MAX17042_MLOCKReg2, val);
+ max17042_write_reg(chip->client, MAX17042_STATUS, val);
+
+ val = max17042_read_reg(chip->client, MAX17042_MLOCKReg1);
+ if (val)
+ dev_err(&chip->client->dev, "MLOCKReg1 read failed\n");
+
+ val = max17042_read_reg(chip->client, MAX17042_MLOCKReg2);
+ if (val)
+ dev_err(&chip->client->dev, "MLOCKReg2 read failed\n");
+
+ val = max17042_read_reg(chip->client, MAX17042_STATUS);
+ if (val)
+ dev_err(&chip->client->dev, "STATUS read failed\n");
+
+ /* send POR command */
+ max17042_write_reg(chip->client, MAX17042_VFSOC0Enable, 0x000F);
+ mdelay(2);
+
+ val = max17042_read_reg(chip->client, MAX17042_STATUS);
+ if (val & STATUS_POR_BIT)
+ dev_info(&chip->client->dev, "SoftPOR done!\n");
+ else
+ dev_err(&chip->client->dev, "SoftPOR failed\n");
+}
+
+static int write_characterization_data(struct max17042_chip *chip)
+{
+ uint16_t cell_data[CELL_CHAR_TBL_SAMPLES];
+ uint16_t temp_data[CELL_CHAR_TBL_SAMPLES];
+ int i;
+ u8 addr;
+
+ memset(cell_data, 0x0, sizeof(cell_data));
+ /* Unlock model access */
+ max17042_write_reg(chip->client, MAX17042_MLOCKReg1, FG_MODEL_UNLOCK1);
+ max17042_write_reg(chip->client, MAX17042_MLOCKReg2, FG_MODEL_UNLOCK2);
+ addr = MAX17042_MODELChrTbl;
+
+ /* write the 48 words */
+ for (i = 0; i < CELL_CHAR_TBL_SAMPLES; i++)
+ max17042_write_reg(chip->client, addr + i,
+ fg_conf_data->cell_char_tbl[i]);
+
+ /* read the 48 words */
+ for (i = 0; i < CELL_CHAR_TBL_SAMPLES; i++)
+ cell_data[i] = max17042_read_reg(chip->client, addr + i);
+
+ /* compare the data */
+ if (memcmp(cell_data, fg_conf_data->cell_char_tbl, sizeof(cell_data))) {
+ dev_err(&chip->client->dev, "%s write failed\n", __func__);
+ for (i = 0; i < CELL_CHAR_TBL_SAMPLES; i++)
+ dev_err(&chip->client->dev, "0x%x,0x%x\n", cell_data[i],
+ fg_conf_data->cell_char_tbl[i]);
+ /* Lock Model access regs */
+ max17042_write_reg(chip->client, MAX17042_MLOCKReg1,
+ FG_MODEL_LOCK1);
+ max17042_write_reg(chip->client, MAX17042_MLOCKReg2,
+ FG_MODEL_LOCK2);
+ return -EIO;
+ }
+
+ memset(temp_data, 0x0, sizeof(temp_data));
+ /* Lock Model access regs */
+ max17042_write_reg(chip->client, MAX17042_MLOCKReg1, FG_MODEL_LOCK1);
+ max17042_write_reg(chip->client, MAX17042_MLOCKReg2, FG_MODEL_LOCK2);
+
+ /* read the 48 words */
+ for (i = 0; i < CELL_CHAR_TBL_SAMPLES; i++)
+ cell_data[i] = max17042_read_reg(chip->client, addr + i);
+
+ /* compare the data */
+ if (memcmp(cell_data, temp_data, sizeof(temp_data))) {
+ dev_err(&chip->client->dev, "%s verify failed\n", __func__);
+ for (i = 0; i < CELL_CHAR_TBL_SAMPLES; i++)
+ dev_err(&chip->client->dev, "0x%x, ", cell_data[i]);
+ dev_err(&chip->client->dev, "\n");
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static void configure_learncfg(struct max17042_chip *chip)
+{
+
+ u16 cycles;
+
+ cycles = max17042_read_reg(chip->client, MAX17042_Cycles);
+ if (cycles > CYCLES_ROLLOVER_CUTOFF)
+ max17042_write_reg(chip->client, MAX17042_LearnCFG,
+ MAX17042_DEF_RO_LRNCFG);
+ else
+ max17042_write_reg(chip->client, MAX17042_LearnCFG,
+ fg_conf_data->learn_cfg);
+
+}
+
+static void write_config_regs(struct max17042_chip *chip)
+{
+ max17042_write_reg(chip->client, MAX17042_CONFIG, fg_conf_data->cfg);
+ configure_learncfg(chip);
+
+ max17042_write_reg(chip->client, MAX17042_SHFTCFG,
+ fg_conf_data->filter_cfg);
+ max17042_write_reg(chip->client, MAX17042_RelaxCFG,
+ fg_conf_data->relax_cfg);
+}
+
+static void write_custom_regs(struct max17042_chip *chip)
+{
+ max17042_write_verify_reg(chip->client, MAX17042_RCOMP0,
+ fg_conf_data->rcomp0);
+ max17042_write_verify_reg(chip->client, MAX17042_TempCo,
+ fg_conf_data->tempCo);
+ max17042_write_reg(chip->client, MAX17042_ETC, fg_conf_data->etc);
+ max17042_write_verify_reg(chip->client, MAX17042_K_empty0,
+ fg_conf_data->kempty0);
+ max17042_write_verify_reg(chip->client, MAX17042_ICHGTerm,
+ fg_conf_data->ichgt_term);
+ max17042_write_verify_reg(chip->client, MAX17042_SOCempty,
+ fg_conf_data->soc_empty);
+}
+
+static void update_capacity_regs(struct max17042_chip *chip)
+{
+ max17042_write_verify_reg(chip->client, MAX17042_FullCAP,
+ MAX17042_MODEL_MUL_FACTOR(fg_conf_data->full_cap)
+ * fg_conf_data->rsense);
+ max17042_write_reg(chip->client, MAX17042_DesignCap,
+ fg_conf_data->design_cap * fg_conf_data->rsense);
+ max17042_write_verify_reg(chip->client, MAX17042_FullCAPNom,
+ MAX17042_MODEL_MUL_FACTOR(fg_conf_data->full_capnom)
+ * fg_conf_data->rsense);
+}
+
+static void reset_vfsoc0_reg(struct max17042_chip *chip)
+{
+ fg_vfSoc = max17042_read_reg(chip->client, MAX17042_VFSOC);
+ max17042_write_reg(chip->client, MAX17042_VFSOC0Enable, VFSOC0_UNLOCK);
+ max17042_write_verify_reg(chip->client, MAX17042_VFSOC0, fg_vfSoc);
+ max17042_write_reg(chip->client, MAX17042_VFSOC0Enable, VFSOC0_LOCK);
+}
+
+static void load_new_capacity_params(struct max17042_chip *chip, bool is_por)
+{
+ u16 full_cap0, rem_cap, rep_cap, dq_acc;
+
+ if (is_por) {
+ full_cap0 = max17042_read_reg(chip->client, MAX17042_FullCAP0);
+
+ /* fg_vfSoc needs to shifted by 8 bits to get the
+ * perc in 1% accuracy, to get the right rem_cap multiply
+ * full_cap0, fg_vfSoc and devide by 100
+ */
+ rem_cap = ((fg_vfSoc >> 8) * (u32)full_cap0) / 100;
+ max17042_write_verify_reg(chip->client,
+ MAX17042_RemCap, rem_cap);
+
+ rep_cap = rem_cap;
+ max17042_write_verify_reg(chip->client,
+ MAX17042_RepCap, rep_cap);
+ }
+
+ /* Write dQ_acc to 200% of Capacity and dP_acc to 200% */
+ dq_acc = MAX17042_MODEL_MUL_FACTOR(fg_conf_data->full_cap) / dQ_ACC_DIV;
+ max17042_write_verify_reg(chip->client, MAX17042_dQacc, dq_acc);
+ max17042_write_verify_reg(chip->client, MAX17042_dPacc, dP_ACC_200);
+
+ max17042_write_verify_reg(chip->client, MAX17042_FullCAP,
+ MAX17042_MODEL_MUL_FACTOR(fg_conf_data->full_cap)
+ * fg_conf_data->rsense);
+ max17042_write_reg(chip->client, MAX17042_DesignCap,
+ fg_conf_data->design_cap * fg_conf_data->rsense);
+ max17042_write_verify_reg(chip->client, MAX17042_FullCAPNom,
+ MAX17042_MODEL_MUL_FACTOR(fg_conf_data->full_capnom)
+ * fg_conf_data->rsense);
+}
+
+static void save_runtime_params(struct max17042_chip *chip)
+{
+ int size, retval;
+
+ dev_dbg(&chip->client->dev, "%s\n", __func__);
+
+ if (!chip->pdata->save_config_data || !chip->pdata->is_init_done)
+ return ;
+
+ fg_conf_data->rcomp0 = max17042_read_reg(chip->client,
+ MAX17042_RCOMP0);
+ fg_conf_data->tempCo = max17042_read_reg(chip->client,
+ MAX17042_TempCo);
+ fg_conf_data->kempty0 = max17042_read_reg(chip->client,
+ MAX17042_K_empty0);
+ fg_conf_data->full_capnom = max17042_read_reg(chip->client,
+ MAX17042_FullCAPNom);
+ fg_conf_data->full_cap = max17042_read_reg(chip->client,
+ MAX17042_FullCAP);
+ if (fg_conf_data->rsense) {
+ fg_conf_data->full_capnom = MAX17042_MODEL_DIV_FACTOR(
+ fg_conf_data->full_capnom) / fg_conf_data->rsense;
+ fg_conf_data->full_cap /= fg_conf_data->rsense;
+ }
+ fg_conf_data->cycles = max17042_read_reg(chip->client,
+ MAX17042_Cycles);
+
+ /* Dump data before saving */
+ dump_fg_conf_data(chip);
+
+ size = sizeof(*fg_conf_data) - sizeof(fg_conf_data->cell_char_tbl);
+ retval = chip->pdata->save_config_data(DRV_NAME, fg_conf_data, size);
+ if (retval < 0) {
+ dev_err(&chip->client->dev, "%s failed\n", __func__);
+ return ;
+ }
+
+}
+
+static void restore_runtime_params(struct max17042_chip *chip)
+{
+ u16 full_cap0, rem_cap, soc, dq_acc;
+ int size, retval;
+
+ if (!chip->pdata->restore_config_data)
+ return ;
+
+ size = sizeof(*fg_conf_data) - sizeof(fg_conf_data->cell_char_tbl);
+ retval = chip->pdata->restore_config_data(DRV_NAME, fg_conf_data, size);
+ if (retval < 0) {
+ dev_err(&chip->client->dev, "%s failed\n", __func__);
+ return ;
+ }
+
+ /* Dump data after restoring */
+ dump_fg_conf_data(chip);
+
+ max17042_write_verify_reg(chip->client, MAX17042_RCOMP0,
+ fg_conf_data->rcomp0);
+ max17042_write_verify_reg(chip->client, MAX17042_TempCo,
+ fg_conf_data->tempCo);
+ max17042_write_verify_reg(chip->client, MAX17042_K_empty0,
+ fg_conf_data->kempty0);
+ max17042_write_verify_reg(chip->client, MAX17042_FullCAPNom,
+ MAX17042_MODEL_MUL_FACTOR(fg_conf_data->full_capnom)
+ * fg_conf_data->rsense);
+ max17042_write_verify_reg(chip->client, MAX17042_Cycles,
+ fg_conf_data->cycles);
+
+ /* delay must be atleast 350mS to allow SOC
+ * to be calculated from the restored configuration
+ */
+ msleep(350);
+
+ /* restore full capacity value */
+ full_cap0 = max17042_read_reg(chip->client, MAX17042_FullCAP0);
+ soc = max17042_read_reg(chip->client, MAX17042_SOC);
+
+ rem_cap = (soc * (u32)full_cap0) / 25600;
+ max17042_write_verify_reg(chip->client, MAX17042_RemCap, rem_cap);
+ max17042_write_verify_reg(chip->client, MAX17042_FullCAP,
+ fg_conf_data->full_cap * fg_conf_data->rsense);
+
+ /* Write dQ_acc to 200% of Capacity and dP_acc to 200% */
+ dq_acc = MAX17042_MODEL_MUL_FACTOR(fg_conf_data->full_cap) / dQ_ACC_DIV;
+ max17042_write_verify_reg(chip->client, MAX17042_dQacc, dq_acc);
+ max17042_write_verify_reg(chip->client, MAX17042_dPacc, dP_ACC_200);
+
+ /* delay must be atleast 350mS to write Cycles
+ * value from the restored configuration
+ */
+ msleep(350);
+
+ max17042_write_verify_reg(chip->client, MAX17042_Cycles,
+ fg_conf_data->cycles);
+}
+
+static int max17042_reboot_callback(struct notifier_block *nfb,
+ unsigned long event, void *data)
+{
+ struct max17042_chip *chip = i2c_get_clientdata(max17042_client);
+
+ save_runtime_params(chip);
+ return NOTIFY_OK;
+}
+
+static int init_max17042_chip(struct max17042_chip *chip)
+{
+ int ret = 0, val;
+ bool is_por;
+
+ val = max17042_read_reg(chip->client, MAX17042_STATUS);
+ dev_info(&chip->client->dev, "Status reg: %x\n", val);
+
+ if (val & STATUS_POR_BIT)
+ is_por = true;
+ else
+ is_por = false;
+
+ /* Initialize configuration */
+ write_config_regs(chip);
+
+ /* write cell characterization data */
+ ret = write_characterization_data(chip);
+ if (ret < 0)
+ return ret;
+
+ /* write custom parameters */
+ write_custom_regs(chip);
+
+ /* update capacity params */
+ update_capacity_regs(chip);
+
+ /* delay must be atleast 350mS to allow VFSOC
+ * to be calculated from the new configuration
+ */
+ msleep(350);
+
+ /* reset vfsoc0 reg */
+ reset_vfsoc0_reg(chip);
+
+ /* advance to coulomb counter mode */
+ max17042_write_verify_reg(chip->client,
+ MAX17042_Cycles, fg_conf_data->cycles);
+
+ /* adjust Temperature gain and offset */
+ max17042_write_reg(chip->client,
+ MAX17042_TGAIN, NTC_47K_TGAIN);
+ max17042_write_reg(chip->client,
+ MAx17042_TOFF, NTC_47K_TOFF);
+
+ /* load new capacity params */
+ load_new_capacity_params(chip, is_por);
+
+ if (is_por) {
+ /* Init complete, Clear the POR bit */
+ val = max17042_read_reg(chip->client, MAX17042_STATUS);
+ max17042_write_reg(chip->client, MAX17042_STATUS,
+ val & (~STATUS_POR_BIT));
+ }
+
+ /* reset FullCap to non inflated value */
+ max17042_write_verify_reg(chip->client, MAX17042_FullCAP,
+ fg_conf_data->full_cap * fg_conf_data->rsense);
+
+ return ret;
+}
+
+static void reset_max17042(struct max17042_chip *chip)
+{
+ int val;
+
+ /* do soft power reset */
+ enable_soft_POR(chip);
+
+ /* After Power up, the MAX17042 requires 500mS in order
+ * to perform signal debouncing and initial SOC reporting
+ */
+ msleep(500);
+
+ max17042_write_reg(chip->client, MAX17042_CONFIG, 0x2210);
+
+ /* adjust Temperature gain and offset */
+ max17042_write_reg(chip->client, MAX17042_TGAIN, NTC_47K_TGAIN);
+ max17042_write_reg(chip->client, MAx17042_TOFF, NTC_47K_TOFF);
+
+ /* Init complete, Clear the POR bit */
+ val = max17042_read_reg(chip->client, MAX17042_STATUS);
+ max17042_write_reg(chip->client, MAX17042_STATUS,
+ val & (~STATUS_POR_BIT));
+
+}
+
+static void max17042_restore_conf_data(struct max17042_chip *chip)
+{
+ int retval = 0, val, size;
+
+ /* return if lock already acquired */
+ if (!mutex_trylock(&chip->init_lock))
+ return;
+
+ if (!chip->pdata->is_init_done && chip->pdata->restore_config_data) {
+ retval = chip->pdata->restore_config_data(DRV_NAME,
+ fg_conf_data, sizeof(*fg_conf_data));
+
+ if (retval == -ENXIO) { /* no device found */
+ dev_err(&chip->client->dev, "device not found\n");
+ chip->pdata->is_init_done = 1;
+ chip->pdata->save_config_data = NULL;
+ } else if (retval < 0) { /* device not ready */
+ dev_warn(&chip->client->dev, "device not ready\n");
+ } else { /* device ready */
+ /* Dump data after restoring */
+ dump_fg_conf_data(chip);
+
+ val = max17042_read_reg(chip->client, MAX17042_STATUS);
+ dev_info(&chip->client->dev, "Status reg: %x\n", val);
+ if (!fg_conf_data->config_init ||
+ (val & STATUS_POR_BIT)) {
+ dev_info(&chip->client->dev,
+ "config data needs to be loaded\n");
+ retval = init_max17042_chip(chip);
+ if (retval < 0) {
+ dev_err(&chip->client->dev,
+ "maxim chip init failed\n");
+ reset_max17042(chip);
+ chip->pdata->save_config_data = NULL;
+ }
+ }
+ chip->pdata->is_init_done = 1;
+
+ /* mark the dirty byte in non-volatile memory */
+ if (!fg_conf_data->config_init && retval >= 0) {
+ fg_conf_data->config_init = 0x1;
+ size = sizeof(*fg_conf_data) -
+ sizeof(fg_conf_data->cell_char_tbl);
+ retval = chip->pdata->save_config_data(
+ DRV_NAME, fg_conf_data, size);
+ if (retval < 0)
+ dev_err(&chip->client->dev,
+ "%s failed\n", __func__);
+ }
+ }
+ }
+
+ /* Check if current sensing is enabled */
+ if (chip->pdata->is_init_done && (retval == 0)) {
+ if (chip->pdata->current_sense_enabled)
+ chip->pdata->enable_current_sense =
+ chip->pdata->current_sense_enabled();
+ else
+ chip->pdata->enable_current_sense = 1;
+ }
+
+ if (chip->pdata->enable_current_sense) {
+ dev_info(&chip->client->dev, "current sensing enabled\n");
+ /* enable coulomb counter based fuel gauging */
+ configure_learncfg(chip);
+
+ /* enable Alerts for SOCRep */
+ max17042_write_reg(chip->client, MAX17042_MiscCFG, 0x0000);
+
+ chip->technology = chip->pdata->technology;
+ } else {
+ dev_info(&chip->client->dev, "current sensing NOT enabled\n");
+ /* Enable voltage based Fuel Gauging */
+ max17042_write_reg(chip->client, MAX17042_LearnCFG, 0x0007);
+ /* configure interrupts for SOCvf */
+ max17042_write_reg(chip->client, MAX17042_MiscCFG, 0x0003);
+
+ chip->technology = POWER_SUPPLY_TECHNOLOGY_UNKNOWN;
+ }
+
+ mutex_unlock(&chip->init_lock);
+}
+
+static void max17042_init_worker(struct work_struct *work)
+{
+ struct max17042_chip *chip = container_of(work,
+ struct max17042_chip, init_worker.work);
+
+ dev_info(&chip->client->dev, "%s\n", __func__);
+ max17042_restore_conf_data(chip);
+}
+
+static void set_soc_intr_thresholds(struct max17042_chip *chip, u16 off)
+{
+ u16 soc, soc_tr;
+
+ /* program interrupt thesholds such that we should
+ * get interrupt for every 'off' perc change in the soc
+ */
+ soc = max17042_read_reg(chip->client, MAX17042_RepSOC) >> 8;
+ soc_tr = (soc + off) << 8;
+ soc_tr |= (soc - off);
+ max17042_write_reg(chip->client, MAX17042_SALRT_Th, soc_tr);
+}
+
+static void set_intr_thresholds(struct max17042_chip *chip)
+{
+ u16 volt_tr, temp_tr;
+
+ /* Max voltage threshold set to 4200mV */
+ volt_tr = MAX_VOLT_THRLD << 8;
+ /* Min voltage threshold set to 3760mV */
+ volt_tr |= MIN_VOLT_THRLD;
+ max17042_write_reg(chip->client, MAX17042_VALRT_Th, volt_tr);
+
+ /* Max temperature threshold set 60 Degrees */
+ temp_tr = MAX_TEMP_THRLD << 8;
+ /* Min temperature threshold set 0 Degrees */
+ temp_tr |= MIN_TEMP_THRLD;
+ max17042_write_reg(chip->client, MAX17042_TALRT_Th, temp_tr);
+
+ /* set soc threshold */
+ set_soc_intr_thresholds(chip, 1);
+}
+
+static void max17042_external_power_changed(struct power_supply *psy)
+{
+ struct max17042_chip *chip = container_of(psy,
+ struct max17042_chip, battery);
+
+ pm_runtime_get_sync(&chip->client->dev);
+
+ mutex_lock(&chip->batt_lock);
+ /* get the battery status */
+ if (chip->pdata->battery_status)
+ chip->status = chip->pdata->battery_status();
+
+ /* get the battery health */
+ if (chip->pdata->battery_health)
+ chip->health = chip->pdata->battery_health();
+ mutex_unlock(&chip->batt_lock);
+
+ /* Init maxim chip if it is not already initialized */
+ if (!chip->pdata->is_init_done)
+ schedule_delayed_work(&chip->init_worker, 0);
+
+ power_supply_changed(&chip->battery);
+ pm_runtime_put_sync(&chip->client->dev);
+}
+
+static void init_battery_props(struct max17042_chip *chip)
+{
+ u16 val;
+
+ val = max17042_read_reg(chip->client, MAX17042_STATUS);
+ /* check battery present bit */
+ if (val & STATUS_BST_BIT)
+ chip->present = 0;
+ else
+ chip->present = 1;
+
+ chip->status = POWER_SUPPLY_STATUS_UNKNOWN;
+ chip->health = POWER_SUPPLY_HEALTH_UNKNOWN;
+ chip->technology = chip->pdata->technology;
+ chip->charge_full_des = BATT_CHRG_FULL_DES;
+}
+
+#ifdef CONFIG_DEBUG_FS
+/**
+ * max17042_show - debugfs: show the state of an endpoint.
+ * @seq: The seq_file to write data to.
+ * @unused: not used
+ *
+ * This debugfs entry shows the content of the register
+ * given in the data parameter.
+*/
+static int max17042_show(struct seq_file *seq, void *unused)
+{
+ u16 val;
+ long addr;
+
+ if (strict_strtol((char *)seq->private, 16, &addr))
+ return -EINVAL;
+
+ val = max17042_read_reg(max17042_client, addr);
+ seq_printf(seq, "%x\n", val);
+
return 0;
}
+static int max17042_dbgfs_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, max17042_show, inode->i_private);
+}
+
+static const struct file_operations max17042_dbgfs_fops = {
+ .owner = THIS_MODULE,
+ .open = max17042_dbgfs_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static void max17042_create_debugfs(struct max17042_chip *chip)
+{
+ int i;
+ struct dentry *entry;
+
+ max17042_dbgfs_root = debugfs_create_dir(DEV_NAME, NULL);
+ if (IS_ERR(max17042_dbgfs_root)) {
+ dev_warn(&chip->client->dev, "DEBUGFS DIR create failed\n");
+ return ;
+ }
+
+ for (i = 0; i < MAX17042_MAX_MEM; i++) {
+ sprintf((char *)&max17042_dbg_regs[i], "%x", i);
+ entry = debugfs_create_file(
+ (const char *)&max17042_dbg_regs[i],
+ S_IRUGO,
+ max17042_dbgfs_root,
+ &max17042_dbg_regs[i],
+ &max17042_dbgfs_fops);
+ if (IS_ERR(entry)) {
+ debugfs_remove_recursive(max17042_dbgfs_root);
+ max17042_dbgfs_root = NULL;
+ dev_warn(&chip->client->dev,
+ "DEBUGFS entry Create failed\n");
+ return ;
+ }
+ }
+}
+static inline void max17042_remove_debugfs(struct max17042_chip *chip)
+{
+ if (max17042_dbgfs_root)
+ debugfs_remove_recursive(max17042_dbgfs_root);
+}
+#else
+static inline void max17042_create_debugfs(struct max17042_chip *chip)
+{
+}
+static inline void max17042_remove_debugfs(struct max17042_chip *chip)
+{
+}
+#endif
+
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 (!client->dev.platform_data) {
+ dev_err(&client->dev, "Platform Data is NULL");
+ return -EFAULT;
+ }
- if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA))
+ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA)) {
+ dev_err(&client->dev,
+ "SM bus doesn't support DWORD transactions\n");
return -EIO;
+ }
chip = kzalloc(sizeof(*chip), GFP_KERNEL);
- if (!chip)
+ if (!chip) {
+ dev_err(&client->dev, "mem alloc failed\n");
return -ENOMEM;
+ }
+ fg_conf_data = kzalloc(sizeof(*fg_conf_data), GFP_KERNEL);
+ if (!fg_conf_data) {
+ dev_err(&client->dev, "mem alloc failed\n");
+ kfree(chip);
+ return -ENOMEM;
+ }
chip->client = client;
chip->pdata = client->dev.platform_data;
i2c_set_clientdata(client, chip);
+ max17042_client = client;
+
+ ret = max17042_read_reg(chip->client, MAX17042_DevName);
+ if (ret != MAX17042_IC_VERSION) {
+ dev_err(&client->dev, "device version mismatch: %x\n", ret);
+ kfree(chip);
+ kfree(fg_conf_data);
+ return -EIO;
+ }
+
+ /* init battery properties */
+ init_battery_props(chip);
+ INIT_DELAYED_WORK(&chip->init_worker, max17042_init_worker);
+ mutex_init(&chip->batt_lock);
+ mutex_init(&chip->init_lock);
+
+ /* Initialize the chip with battery config data */
+ max17042_restore_conf_data(chip);
chip->battery.name = "max17042_battery";
chip->battery.type = POWER_SUPPLY_TYPE_BATTERY;
chip->battery.get_property = max17042_get_property;
+ chip->battery.external_power_changed = max17042_external_power_changed;
chip->battery.properties = max17042_battery_props;
chip->battery.num_properties = ARRAY_SIZE(max17042_battery_props);
dev_err(&client->dev, "failed: power supply register\n");
i2c_set_clientdata(client, NULL);
kfree(chip);
+ kfree(fg_conf_data);
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);
- }
+ /* Init Runtime PM State */
+ pm_runtime_put_noidle(&chip->client->dev);
+ pm_schedule_suspend(&chip->client->dev, MSEC_PER_SEC);
+
+
+ /* register interrupt */
+ ret = request_threaded_irq(client->irq, max17042_intr_handler,
+ max17042_thread_handler,
+ 0, DRV_NAME, chip);
+ if (ret)
+ dev_warn(&client->dev, "%s(): cannot get IRQ\n", __func__);
+ else
+ dev_info(&client->dev, "IRQ No:%d\n", client->irq);
+
+ /* Enable Interrupts */
+ max17042_reg_read_modify(client, MAX17042_CONFIG,
+ CONFIG_ALRT_BIT_ENBL, 1);
+ /* set the Interrupt threshold registers */
+ set_intr_thresholds(chip);
+
+ /* Create debugfs for maxim registers */
+ max17042_create_debugfs(chip);
+
+ /* Register reboot notifier callback */
+ register_reboot_notifier(&max17042_reboot_notifier_block);
return 0;
}
{
struct max17042_chip *chip = i2c_get_clientdata(client);
+ unregister_reboot_notifier(&max17042_reboot_notifier_block);
+ max17042_remove_debugfs(chip);
+ free_irq(client->irq, chip);
power_supply_unregister(&chip->battery);
+ pm_runtime_get_noresume(&chip->client->dev);
i2c_set_clientdata(client, NULL);
kfree(chip);
+ kfree(fg_conf_data);
return 0;
}
+#ifdef CONFIG_PM
+static int max17042_suspend(struct device *dev)
+{
+ struct max17042_chip *chip = dev_get_drvdata(dev);
+
+ /* max17042 IC automatically goes into shutdown mode
+ * if the SCL and SDA were held low for more than
+ * timeout of SHDNTIMER register value
+ */
+ dev_dbg(&chip->client->dev, "max17042 suspend\n");
+
+ return 0;
+}
+
+static int max17042_resume(struct device *dev)
+{
+ struct max17042_chip *chip = dev_get_drvdata(dev);
+
+ /* max17042 IC automatically wakes up if any edge
+ * on SDCl or SDA if we set I2CSH of CONFG reg
+ */
+ dev_dbg(&chip->client->dev, "max17042 resume\n");
+
+ return 0;
+}
+#else
+#define max17042_suspend NULL
+#define max17042_resume NULL
+#endif
+
+#ifdef CONFIG_PM_RUNTIME
+static int max17042_runtime_suspend(struct device *dev)
+{
+
+ dev_dbg(dev, "%s called\n", __func__);
+ return 0;
+}
+
+static int max17042_runtime_resume(struct device *dev)
+{
+ dev_dbg(dev, "%s called\n", __func__);
+ return 0;
+}
+
+static int max17042_runtime_idle(struct device *dev)
+{
+
+ dev_dbg(dev, "%s called\n", __func__);
+ return 0;
+}
+#else
+#define max17042_runtime_suspend NULL
+#define max17042_runtime_resume NULL
+#define max17042_runtime_idle NULL
+#endif
+
static const struct i2c_device_id max17042_id[] = {
- { "max17042", 0 },
- { }
+ { DEV_NAME, 0 },
+ { },
};
MODULE_DEVICE_TABLE(i2c, max17042_id);
+static const struct dev_pm_ops max17042_pm_ops = {
+ .suspend = max17042_suspend,
+ .resume = max17042_resume,
+ .runtime_suspend = max17042_runtime_suspend,
+ .runtime_resume = max17042_runtime_resume,
+ .runtime_idle = max17042_runtime_idle,
+};
+
static struct i2c_driver max17042_i2c_driver = {
.driver = {
- .name = "max17042",
+ .name = DEV_NAME,
+ .owner = THIS_MODULE,
+ .pm = &max17042_pm_ops,
},
.probe = max17042_probe,
.remove = __devexit_p(max17042_remove),