diff options
author | Ramakrishna Pallala <ramakrishna.pallala@intel.com> | 2010-12-09 10:37:47 +0000 |
---|---|---|
committer | Alan Cox <alan@linux.intel.com> | 2010-12-09 10:37:47 +0000 |
commit | e913d81603e905670a65dfb44744656a678fba31 (patch) | |
tree | 8dec4d792d6a799c6c463a02ebbbefd39a261900 /drivers/power/intel_mdf_battery.c | |
parent | af27295dacc7f65fde0940e115d6eb5440a2d72d (diff) | |
download | mrst-s0i3-test-e913d81603e905670a65dfb44744656a678fba31.tar.gz mrst-s0i3-test-e913d81603e905670a65dfb44744656a678fba31.tar.xz mrst-s0i3-test-e913d81603e905670a65dfb44744656a678fba31.zip |
intel_mid: Intel MSIC battery driver
Battery driver for the Intel MID platform devices based on the Medfield
chipset.
Signed-off-by: Ramakrishna Pallala <ramakrishna.pallala@intel.com>
Diffstat (limited to 'drivers/power/intel_mdf_battery.c')
-rw-r--r-- | drivers/power/intel_mdf_battery.c | 2158 |
1 files changed, 2158 insertions, 0 deletions
diff --git a/drivers/power/intel_mdf_battery.c b/drivers/power/intel_mdf_battery.c new file mode 100644 index 00000000000..175a0699e68 --- /dev/null +++ b/drivers/power/intel_mdf_battery.c @@ -0,0 +1,2158 @@ +/* + * msic_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 <asm/intel_scu_ipc.h> +#include <linux/usb/penwell_otg.h> + +#define DRIVER_NAME "msic_battery" + +/********************************************************************* + * Generic defines + *********************************************************************/ + +#define MSIC_BATT_PRESENT 1 +#define MSIC_BATT_NOT_PRESENT 0 +#define MSIC_USB_CHARGER_PRESENT MSIC_BATT_PRESENT +#define MSIC_USB_CHARGER_NOT_PRESENT MSIC_BATT_NOT_PRESENT + +/* Interrupt registers*/ +#define MSIC_BATT_CHR_PWRSRCINT_ADDR 0x005 +#define MSIC_BATT_CHR_BATTDET_MASK (1 << 0) +#define MSIC_BATT_CHR_USBDET_MASK (1 << 1) +#define MSIC_BATT_CHR_ADPLVDET_MASK (1 << 3) +#define MSIC_BATT_CHR_ADPHVDET_MASK (1 << 4) + +#define MSIC_BATT_PWRSRC_MASK 0x1A + +#define MSIC_BATT_CHR_PWRSRCINT1_ADDR 0x006 +#define MSIC_BATT_CHR_USBDCDET_MASK (1 << 2) +#define MSIC_BATT_CHR_USBCHPDET_MASK (1 << 6) + +#define MSIC_BATT_CHR_CHRINT_ADDR 0x007 +#define MSIC_BATT_CHR_BATTOCP_MASK (1 << 1) +#define MSIC_BATT_CHR_BATTOTP_MASK (1 << 2) +#define MSIC_BATT_CHR_LOWBATT_MASK (1 << 3) +#define MSIC_BATT_CHR_WDTIMEEXP_MASK (1 << 5) +#define MSIC_BATT_CHR_ADPOVP_MASK (1 << 6) +#define MSIC_BATT_CHR_TIMEEXP_MASK (1 << 7) + +#define MSIC_BATT_CHRINT_EXCP_MASK 0x5E +#define MSIC_BATT_CHR_CHRINT1_ADDR 0x008 +#define MSIC_BATT_CHR_WKVINDET_MASK (1 << 2) +#define MSIC_BATT_CHR_VINREGMINT_MASK (1 << 4) +#define MSIC_BATT_CHR_CHROTP_MASK (1 << 3) +#define MSIC_BATT_CHR_BATTOVP_MASK (1 << 5) +#define MSIC_BATT_CHR_USBOVP_MASK (1 << 6) +#define MSIC_BATT_CHR_CHRCMPLT_MASK (1 << 7) +#define MSIC_BATT_CHRINT1_EXCP_MASK 0x68 + +/* Interrupt Mask registers */ +#define MSIC_BATT_CHR_MPWRSRCINT_ADDR 0x014 +#define MSIC_BATT_CHR_MPWRSRCINT1_ADDR 0x015 +#define MSIC_BATT_CHR_MCHRINT_ADDR 0x016 +#define MSIC_BATT_CHR_MCHRINT1_ADDR 0x017 + +/* Internal charger control registers */ +#define MSIC_BATT_CHR_CHRCTRL_ADDR 0x188 +#define CHRCNTL_CHRG_DISABLE (1 << 2) + +#define MSIC_BATT_CHR_CHRCVOLTAGE_ADDR 0x189 +/* Set Charger Voltage to 4140 mV */ +#define CHR_CHRVOLTAGE_SET_DEF 4140 + +#define MSIC_BATT_CHR_CHRCCURRENT_ADDR 0x18A + +#define MSIC_BATT_CHR_SPCHARGER_ADDR 0x18B +#define CHR_SPCHRGER_LOWCHR_ENABLE (1 << 5) +#define CHR_SPCHRGER_WEAKVIN 0x04 + +#define MSIC_BATT_CHR_CHRTTIME_ADDR 0x18C +#define CHR_CHRTIME_SET_12HRS 0x0E + +#define MSIC_BATT_CHR_CHRCTRL1_ADDR 0x18D +#define MSIC_BATT_CHR_EXTCHRDIS_MASK (1 << 5) + +/* Safe limit registers */ +#define MSIC_BATT_CHR_PWRSRCLMT_ADDR 0x18E /*Temperature limits*/ +#define CHR_PWRSRCLMT_SET_RANGE 0xC0 + +#define MSIC_BATT_CHR_CHRSTWDT_ADDR 0x18F /*Watch dog timer*/ +#define CHR_WDT_DISABLE 0x0 +#define CHR_WDT_SET_60SEC 0x10 + +#define MSIC_BATT_CHR_WDTWRITE_ADDR 0x190 +#define WDTWRITE_UNLOCK_VALUE 0x01 + +#define MSIC_BATT_CHR_CHRSAFELMT_ADDR 0x191 /*Maximum safe charging + voltage and current*/ + +/* Status registers */ +#define MSIC_BATT_CHR_SPWRSRCINT_ADDR 0x192 +#define MSIC_BATT_CHR_SPWRSRCINT1_ADDR 0x193 +#define MSIC_BATT_CHR_USBSLOWBATT_MASK (1 << 0) + +/* ADC1 - registers */ +#define ADC_CHNL_START_ADDR 0x1C5 /* increments by 1 */ +#define ADC_DATA_START_ADDR 0x1D4 /* increments by 2 */ + +#define MSIC_ADC1CNTL1_ADDR 0x1C0 +#define MSIC_CNTL1_ADC_ENBL 0x10 +#define MSIC_CNTL1_RR_ENBL 0x08 + +#define MSIC_ADC1CNTL2_ADDR 0x1C1 +#define MSIC_ADC1CNTL3_ADDR 0x1C2 +#define MSIC_CNTL3_ADCTHERM_ENBL 0x04 +#define MSIC_CNTL3_ADCRRDATA_ENBL 0x05 +#define MSIC_CHANL_MASK_VAL 0x0F + +#define MSIC_STOPBIT_MASK 0x10 +#define MSIC_ADCTHERM_MASK 4 +#define ADC_CHANLS_MAX 15 /*no of adc channels*/ +#define MSIC_BATT_SENSORS 4 /* 3 for battery pack and one USB */ +#define ADC_LOOP_MAX (ADC_CHANLS_MAX - MSIC_BATT_SENSORS) + +/* ADC Channel Numbers */ +#define MSIC_BATT_PACK_VOL 0x0 +#define MSIC_BATT_PACK_CUR 0x1 +#define MSIC_BATT_PACK_TEMP 0x7 +#define MSIC_USB_VOLTAGE 0x5 +#define MSIC_ADC_VOL_IDX 0 +#define MSIC_ADC_CUR_IDX 1 +#define MSIC_ADC_TEMP_IDX 2 +#define MSIC_ADC_USB_VOL_IDX 3 + + +#define MSIC_VAUDA 0x0DB +#define MSIC_VAUDA_VAL 0xFF + +/*MSIC battery temperature attributes*/ +#define MSIC_BTP_ADC_MIN 107 +#define MSIC_BTP_ADC_MAX 977 + + +/*convert adc_val to voltage mV */ +#define MSIC_MAX_VOL_DEV ((5 * 4692) / 1000) +#define MSIC_ADC_TO_VOL(adc_val) ((4692 * (adc_val)) / 1000) + +/*convert adc_val to current mA */ +#define MSIC_ADC_MAX_CUR 4000 /* In milli Amph */ +#define MSIC_ADC_TO_CUR(adc_val) ((78125 * (adc_val)) / 10000) + + +/* Convert ADC value to VBUS voltage */ +#define MSIC_ADC_TO_VBUS_VOL(adc_val) ((6843 * (adc_val)) / 1000) + +/* ADC2 - Coulomb Counter registers */ +#define MSIC_BATT_ADC_CCADCHA_ADDR 0x205 +#define MSIC_BATT_ADC_CCADCLA_ADDR 0x206 + +#define MSIC_BATT_ADC_CHRGNG_MASK (1 << 31) +#define MSIC_BATT_ADC_ACCCHRGVAL_MASK 0x7FFFFFFF + +/* + * Convert the voltage form decimal to + * Register writable format + */ +#define CONV_VOL_DEC_MSICREG(a) (((a - 3500) / 20) << 2) + +/* internal return values */ +#define BIT_SET 0 +#define BIT_RESET 1 +#define BATTSUCCESS 0 +#define EBATTFAIL 1 +#define EBATTERR 2 +#define NR_ARR_ELM_MAX 5 + +/* IPC defines */ +#define IPCMSG_BATTERY 0xEF + +#define TEMP_CHARGE_DELAY_JIFFIES (HZ * 30) /*30 sec */ + +#define IRQ_FIFO_MAX 16 +#define THERM_CURVE_MAX_SAMPLES 7 +#define THERM_CURVE_MAX_VALUES 4 +#define BATT_STRING_MAX 8 + + +/* Valid msic exceptional events */ +enum msic_event { + MSIC_EVENT_BATTOCP_EXCPT, + MSIC_EVENT_BATTOTP_EXCPT, + MSIC_EVENT_LOWBATT_EXCPT, + MSIC_EVENT_BATTOVP_EXCPT, + MSIC_EVENT_ADPOVP_EXCPT, + MSIC_EVENT_CHROTP_EXCPT, + MSIC_EVENT_USBOVP_EXCPT, +}; + +/* Valid Charging modes */ +enum { + BATT_CHARGING_MODE_NONE = 0, + BATT_CHARGING_MODE_NORMAL, + BATT_CHARGING_MODE_MAINTAINENCE, +}; + + +static void *otg_handle; +static struct device *msic_dev; + +/* + * This array represents the Battery Pack thermistor + * temarature 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} */ + {-10, -20, 977, 941}, + {0, -10, 941, 887}, + {10, 0, 887, 769}, + {50, 10, 769, 357}, + {75, 50, 357, 186}, + {100, 75, 186, 107}, +}; + + +/********************************************************************* + * SFI table entries Structures + *********************************************************************/ + +/* Battery Identifier */ +struct battery_id { + unsigned char manufac[3]; + unsigned char model[5]; + unsigned char sub_ver[3]; +}; + +/* Parameters defining the range */ +struct temperature_monitoring_range { + unsigned char range_number; + char temp_low_lim; + char temp_up_lim; + short int full_chrg_cur; + short int full_chrg_vol; + short int maint_chrg_cur; + short int maint_chrg_vol_ll; + short int maint_chrg_vol_ul; +}; + +/* SFI table entries structure. This code + * will be modified or removed when the + * Firmware supports SFI entries for Battery + */ +struct msic_batt_sfi_prop { + unsigned char sign[5]; + unsigned int length; + unsigned char revision; + unsigned char checksum; + unsigned char oem_id[7]; + unsigned char oem_tid[9]; + struct battery_id batt_id; + unsigned short int voltage_max; + unsigned int capacity; + unsigned char battery_type; + char safe_temp_low_lim; + char safe_temp_up_lim; + unsigned short int safe_vol_low_lim; + unsigned short int safe_vol_up_lim; + unsigned short int chrg_cur_lim; + char chrg_term_lim; + unsigned short int term_cur; + char temp_mon_ranges; + struct temperature_monitoring_range temp_mon_range[4]; + unsigned int sram_addr; +}; + +static struct msic_batt_sfi_prop *sfi_table; + +/********************************************************************* + * Battery properties + *********************************************************************/ +struct charge_params { + short int cvol; + short int ccur; + short int vinilmt; + enum usb_charger_type chrg_type; +}; + +struct msic_batt_props { + unsigned int status; + unsigned int health; + unsigned int present; + unsigned int technology; + unsigned int vol_max_des; + unsigned int vol_now; + unsigned int cur_now; + unsigned int charge_full; /* in mAS */ + unsigned int charge_now; /* in mAS */ + unsigned int charge_avg; /* in units per second */ + unsigned int capacity; /* in units persentage */ + unsigned int temperature; /* in milli Centigrade*/ + char model[BATT_STRING_MAX]; + char vender[BATT_STRING_MAX]; +}; + +struct msic_charg_props { + unsigned int charger_type; + unsigned int charger_present; + unsigned int charger_health; + unsigned int vbus_vol; + char charger_model[BATT_STRING_MAX]; + char charger_vender[BATT_STRING_MAX]; +}; + +/* All Interrupt request are queued from Interrupt + * handler and processed in the bottem half + */ +static DEFINE_KFIFO(irq_fifo, IRQ_FIFO_MAX); + +/* + * msic battery info + */ +struct msic_power_module_info { + + struct platform_device *pdev; + + /* msic charger data */ + /* lock to protect usb charger properties + * locking is applied whereever read or write + * operation is being performed to the msic usb + * charger property structure. + */ + struct mutex usb_chrg_lock; + struct msic_charg_props usb_chrg_props; + struct power_supply usb; + + /* msic battery data */ + /* lock to protect battery properties + * locking is applied whereever read or write + * operation is being performed to the msic battery + * property structure. + */ + struct mutex batt_lock; + struct msic_batt_props batt_props; + struct power_supply batt; + + uint16_t adc_index; /* ADC Channel Index */ + int irq; /* GPE_ID or IRQ# */ + + struct delayed_work connect_handler; + struct delayed_work disconn_handler; + struct charge_params ch_params; /* holds the charge parameters */ + + unsigned long update_time; /* jiffies when data read */ + + void __iomem *msic_regs_iomap; + + /* spin lock to protect driver event related variables + * these event variables are being modified from + * interrupt context(msic charger callback) also. + */ + spinlock_t event_lock; + int batt_event; + int charging_mode; + + /* lock to avoid concurrent access to HW Registers. + * As some chargeer control and parameter registers + * can be read or write at same time, ipc_rw_lock lock + * is used to syncronize those IPC read or write calls. + */ + struct mutex ipc_rw_lock; +}; + +/* + * msic usb properties + */ +static enum power_supply_property msic_usb_props[] = { + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, +}; + +/* + * msic battery properties + */ +static enum power_supply_property msic_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_AVG, + POWER_SUPPLY_PROP_CAPACITY, /* in percents! */ + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, +}; + +static int enable_adc(struct msic_power_module_info *mbi) +{ + int ret; + uint8_t data; + /* enabling the VAUDA line + * this is a temporary workaround for MSIC issue + */ + ret = intel_scu_ipc_iowrite8(MSIC_VAUDA, MSIC_VAUDA_VAL); + if (ret) { + dev_warn(&mbi->pdev->dev, + "%s:VAUDA:ipc write failed\n", __func__); + return ret; + } + + ret = intel_scu_ipc_ioread8(MSIC_ADC1CNTL1_ADDR, &data); + if (ret) { + dev_warn(&mbi->pdev->dev, "%s:ipc read failed\n", __func__); + return ret; + } + + data |= MSIC_CNTL1_ADC_ENBL; /*enable ADC */ + data |= MSIC_CNTL1_RR_ENBL; /*Start Convertion */ + ret = intel_scu_ipc_iowrite8(MSIC_ADC1CNTL1_ADDR, data); + if (ret) + dev_warn(&mbi->pdev->dev, + "%s:ADC CNTRL1 enabling failed\n", __func__); + return ret; +} + +/** + * set_up_batt_pack_chnl - to set thermal for conversion + * @base_addr: index of free msic adc channel + * @therm: struct thermal module info + * Context: can sleep + * + * To set up the adc for reading thermistor + * and converting the same into actual temp value + * on the platform + */ +static int set_up_batt_pack_chnl(u16 ch_index, + struct msic_power_module_info *mbi) +{ + int ret; + u16 base_addr; + + base_addr = ADC_CHNL_START_ADDR + ch_index; + + /* enabling the MSIC_BATT_PACK_VOL channel */ + ret = intel_scu_ipc_iowrite8(base_addr, MSIC_BATT_PACK_VOL); + if (ret) { + dev_warn(&mbi->pdev->dev, + "%s:enabling skin therm sensor0 failed\n", __func__); + goto fail; + } + + /* enabling the MSIC_BATT_PACK_CUR channel */ + base_addr++; + ret = intel_scu_ipc_iowrite8(base_addr, MSIC_BATT_PACK_CUR); + if (ret) { + dev_warn(&mbi->pdev->dev, + "%s:enabling skin therm sensor1 failed\n", __func__); + goto fail; + } + + /* enabling the MSIC_BATT_PACK_TEMP channel */ + base_addr++; + ret = intel_scu_ipc_iowrite8(base_addr, MSIC_BATT_PACK_TEMP); + if (ret) { + dev_warn(&mbi->pdev->dev, + "%s:enabling sys therm sensor failed\n", __func__); + goto fail; + } + /* + * enabling the MSIC USB VBUS channel + * emabling stop bit for the last channel + */ + base_addr++; + ret = intel_scu_ipc_iowrite8(base_addr, + MSIC_USB_VOLTAGE | MSIC_STOPBIT_MASK); + if (ret) { + dev_warn(&mbi->pdev->dev, + "%s:enabling sys therm sensor failed\n", __func__); + goto fail; + } +fail: + enable_adc(mbi); + return ret; +} + +static void free_adc_channels(u16 ch_index, struct msic_power_module_info *mbi) +{ + int ret, i; + u16 base_addr; + + base_addr = ADC_CHNL_START_ADDR + ch_index; + for (i = 0; (i < 4) && (base_addr < ADC_CHANLS_MAX); i++) { + ret = intel_scu_ipc_iowrite8(base_addr, 0x0); + if (ret) { + dev_warn(&mbi->pdev->dev, "%s:ipc write failed\n", + __func__); + } + base_addr++; + } +} + +/* + * reset_stopbit - sets the stop bit to 0 on the given channel + * @addr: address of the channel + */ +static int reset_stopbit(uint16_t addr) +{ + int ret; + uint8_t data; + ret = intel_scu_ipc_ioread8(addr, &data); + if (ret) + return ret; + data &= ~MSIC_STOPBIT_MASK; /*setting the stop bit to zero*/ + ret = intel_scu_ipc_iowrite8(addr, data); + return ret; +} + + +/* + * find_free_channel - finds an empty channel for conversion + * @mbi: struct msic power module info + * Context: can sleep + * + * If adc is not enabled then start using 0th channel + * itself. Otherwise find an empty channel by looking for + * one in which the stopbit is set to 1. + * returns the base address if succeeds,-EINVAL otherwise + */ +static int find_free_channel(struct msic_power_module_info *mbi) +{ + int ret; + int i; + uint8_t data; + + /* Looping for empty channel */ + for (i = 0; i < ADC_CHANLS_MAX; i++) { + ret = intel_scu_ipc_ioread8(ADC_CHNL_START_ADDR + i, &data); + if (ret) { + dev_warn(&mbi->pdev->dev, "%s:ipc read failed\n", + __func__); + return ret; + } + if (data & MSIC_STOPBIT_MASK) + break; + } + + if (i >= ADC_CHANLS_MAX-1) { + /* No STOP bit found, Retrun channel number as zero */ + return 0; + } + + /* Free Channels should be more than 3(VOL,CUR,TEMP) */ + if (i > ADC_LOOP_MAX) { + dev_warn(&mbi->pdev->dev, + "%s:Cannot set up adc, no channels free : %d\n", + __func__, i); + return -EINVAL; + } + + /* + * Reset STOP bit of the current channel + * No need to reset if the current channel index is 12 + */ + if (i != ADC_LOOP_MAX) { + ret = reset_stopbit(ADC_CHNL_START_ADDR + i); + if (ret) { + dev_err(&mbi->pdev->dev, + "%s:intel_mdf_battery:ipc r/w failed", __func__); + return ret; + } + } + + /* + * i points to the current channel + * so retrun the next free channel + */ + return i + 1; +} + +/* + * mid_initialize_adc - initializing the adc + * @therm: struct thermal module info + * Context: can sleep + * + * To initialize the adc for reading thermistor + * and converting the same into actual temp value + * on the platform + */ +static int mdf_initialize_adc(struct msic_power_module_info *mbi) +{ + int ret; + int channel_index = -1; + + channel_index = find_free_channel(mbi); + if (channel_index == -EINVAL) + return ret; + + /* Program the free ADC channel */ + ret = set_up_batt_pack_chnl(channel_index, mbi); + if (ret) { + dev_warn(&mbi->pdev->dev, "adc initialization fauled\n"); + return ret; + } + + return channel_index; +} + +/* 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; +} + +static int adc_to_temp(uint16_t adc_val) +{ + int temp; + int i; + + if (!is_valid_temp_adc(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"); + + /*convert tempertaure in celsius to milli degree celsius*/ + return temp * 1000; +} + +static int mdf_read_adc_regs(int sensor, + struct msic_power_module_info *mbi) +{ + uint16_t adc_val = 0, addr; + uint8_t data; + int ret; + + /* + * After setting or enabling the ADC control register + * it should not be modified untll we read the ADC value + * through ADC DATA regs. Using the ipc rw lock we are + * ensuring all ADC ipc call are performed in a proper sequence + */ + mutex_lock(&mbi->ipc_rw_lock); + ret = intel_scu_ipc_ioread8(MSIC_ADC1CNTL3_ADDR, &data); + if (ret) { + dev_warn(&mbi->pdev->dev, "%s:ipc write failed\n", + __func__); + goto ipc_failed;; + } + + /* enable the msic for conversion before reading */ + ret = intel_scu_ipc_iowrite8(MSIC_ADC1CNTL3_ADDR, + data | MSIC_CNTL3_ADCRRDATA_ENBL); + if (ret) { + dev_warn(&mbi->pdev->dev, "%s:ipc write failed\n", + __func__); + goto ipc_failed;; + } + + /* re-toggle the RRDATARD bit + * temporary workaround */ + ret = intel_scu_ipc_iowrite8(MSIC_ADC1CNTL3_ADDR, + data | MSIC_CNTL3_ADCTHERM_ENBL); + if (ret) { + dev_warn(&mbi->pdev->dev, "%s:ipc write failed", + __func__); + goto ipc_failed;; + } + + /* reading the higher bits of data */ + addr = ADC_DATA_START_ADDR+2*(mbi->adc_index + sensor); + ret = intel_scu_ipc_ioread8(addr, &data); + if (ret) { + dev_warn(&mbi->pdev->dev, "%s:ipc read failed", __func__); + goto ipc_failed;; + } + /* shifting bits to accomodate the lower two data bits */ + adc_val = (data << 2); + addr++; + ret = intel_scu_ipc_ioread8(addr, &data);/* reading lower bits */ + if (ret) { + dev_warn(&mbi->pdev->dev, "%s:ipc read failed", __func__); + goto ipc_failed;; + } + +ipc_failed: + mutex_unlock(&mbi->ipc_rw_lock); + if (ret) + return ret; + /*adding lower two bits to the higher bits*/ + data &= 0x3; + adc_val += data; + + switch (sensor) { + case MSIC_ADC_VOL_IDX: + ret = MSIC_ADC_TO_VOL(adc_val); + break; + case MSIC_ADC_CUR_IDX: + ret = MSIC_ADC_TO_CUR(adc_val & 0x1FF); + /* if D9 bit is set battery is discharging */ + if (adc_val & 0x200) + ret = -(MSIC_ADC_MAX_CUR - ret); + break; + case MSIC_ADC_TEMP_IDX: + ret = adc_to_temp(adc_val); + break; + case MSIC_ADC_USB_VOL_IDX: + ret = MSIC_ADC_TO_VBUS_VOL(adc_val); + break; + default: + dev_err(&mbi->pdev->dev, + "intel_mdf_battery:invalid adc_code:%d", adc_val); + ret = -EINVAL; + } + + return ret; +} + +static unsigned int msic_read_coloumb_ctr(void) +{ + int err; + uint32_t cvalue; + + /* determine other parameters */ + err = intel_scu_ipc_command(IPCMSG_BATTERY, 0x01, NULL, 0, &cvalue, 1); + if (err) + dev_warn(msic_dev, "IPC Command Failed %s\n", __func__); + + return cvalue; +} + +/** + * 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 + * subsytem 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); + + 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_HEALTH: + val->intval = mbi->usb_chrg_props.charger_health; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + mbi->usb_chrg_props.vbus_vol = + mdf_read_adc_regs(MSIC_ADC_USB_VOL_IDX, mbi); + val->intval = mbi->usb_chrg_props.vbus_vol * 1000; + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + val->intval = mbi->usb_chrg_props.charger_type; + 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 0; +} + +static unsigned int mdf_cal_avg(unsigned int avg) +{ + unsigned int charge_now; + + charge_now = msic_read_coloumb_ctr(); + avg += charge_now; + + return avg / 2; +} + +/** + * msic_battery_get_property - battery power source get property + * @psy: battery power supply context + * @psp: battery power source property + * @val: battery power source property value + * Context: can sleep + * + * MSIC battery power source property needs to be provided to power_supply + * subsytem for it to provide the information to users. + */ +static int msic_battery_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, batt); + + /* + * All voltages, currents, charges, energies, time and temperatures + * in uV, µA, µAh, µWh, seconds and tenths of degree Celsius un + * less otherwise stated. It's driver's job to convert its raw values + * to units in which this class operates. + */ + + mutex_lock(&mbi->batt_lock); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = mbi->batt_props.status; + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = mbi->batt_props.health; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = mbi->batt_props.present; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = mbi->batt_props.technology; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + val->intval = mbi->batt_props.vol_max_des * 1000; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + mbi->batt_props.vol_now = + mdf_read_adc_regs(MSIC_ADC_VOL_IDX, mbi); + val->intval = mbi->batt_props.vol_now * 1000; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + mbi->batt_props.cur_now = + mdf_read_adc_regs(MSIC_ADC_CUR_IDX, mbi); + val->intval = mbi->batt_props.cur_now * 1000; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + mbi->batt_props.charge_now = msic_read_coloumb_ctr(); + val->intval = mbi->batt_props.charge_now; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + val->intval = mbi->batt_props.charge_full; + break; + case POWER_SUPPLY_PROP_CHARGE_AVG: + mbi->batt_props.charge_avg = + mdf_cal_avg(mbi->batt_props.charge_avg); + val->intval = mbi->batt_props.charge_avg; + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = mbi->batt_props.capacity; + break; + case POWER_SUPPLY_PROP_TEMP: + mbi->batt_props.temperature = + mdf_read_adc_regs(MSIC_ADC_TEMP_IDX, mbi); + val->intval = mbi->batt_props.temperature * 1000; + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = mbi->batt_props.model; + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = mbi->batt_props.vender; + break; + default: + mutex_unlock(&mbi->batt_lock); + return -EINVAL; + } + + mutex_unlock(&mbi->batt_lock); + + return 0; +} + +/** + * 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, "msic-battery: over battery charge " + "current condition detected\n"); + break; + case MSIC_EVENT_BATTOTP_EXCPT: + dev_warn(msic_dev, "msic-battery: high battery temperature " + "condition detected\n"); + break; + case MSIC_EVENT_LOWBATT_EXCPT: + dev_warn(msic_dev, "msic-battery: Low battery voltage " + "condition detected\n"); + break; + case MSIC_EVENT_BATTOVP_EXCPT: + dev_warn(msic_dev, "msic-battery: battery overvoltage " + "condition detected\n"); + break; + case MSIC_EVENT_CHROTP_EXCPT: + dev_warn(msic_dev, "msic-charger: charger high temperature " + "condition detected\n"); + break; + case MSIC_EVENT_USBOVP_EXCPT: + dev_warn(msic_dev, "USB over voltage " + "condition detected\n"); + break; + default: + dev_warn(msic_dev, "unknown error %u detected\n", + -EBATTERR); + 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; + uint8_t chrint_reg_value, chrint1_reg_value; + + chrint_reg_value = CHRINT_reg_value; + chrint1_reg_value = CHRINT1_reg_value; + + /* Battery Exceptions */ + mutex_lock(&mbi->batt_lock); + if (chrint_reg_value & MSIC_BATT_CHR_BATTOCP_MASK) { + exception = MSIC_EVENT_BATTOCP_EXCPT; + mbi->batt_props.status = POWER_SUPPLY_STATUS_NOT_CHARGING; + msic_log_exception_event(exception); + } + + if (chrint1_reg_value & MSIC_BATT_CHR_BATTOTP_MASK) { + mbi->batt_props.status = POWER_SUPPLY_STATUS_NOT_CHARGING; + 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) { + exception = MSIC_EVENT_LOWBATT_EXCPT; + msic_log_exception_event(exception); + } + + if (CHRINT1_reg_value & MSIC_BATT_CHR_BATTOVP_MASK) { + mbi->batt_props.status = POWER_SUPPLY_STATUS_NOT_CHARGING; + mbi->batt_props.health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + + exception = MSIC_EVENT_BATTOVP_EXCPT; + msic_log_exception_event(exception); + } + mutex_unlock(&mbi->batt_lock); + + + mutex_lock(&mbi->usb_chrg_lock); + /* Charger exceptions */ + if (CHRINT1_reg_value & MSIC_BATT_CHR_CHROTP_MASK) { + mbi->usb_chrg_props.charger_health = + POWER_SUPPLY_HEALTH_OVERHEAT; + + mutex_lock(&mbi->batt_lock); + mbi->batt_props.status = POWER_SUPPLY_STATUS_NOT_CHARGING; + mutex_unlock(&mbi->batt_lock); + + 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; + + mutex_lock(&mbi->batt_lock); + mbi->batt_props.status = POWER_SUPPLY_STATUS_NOT_CHARGING; + mutex_unlock(&mbi->batt_lock); + + exception = MSIC_EVENT_USBOVP_EXCPT; + msic_log_exception_event(exception); + } + mutex_unlock(&mbi->usb_chrg_lock); + +} + + +static int msic_batt_stop_charging(struct msic_power_module_info *mbi) +{ + int retval, i; + static const uint16_t address[] = { + MSIC_BATT_CHR_WDTWRITE_ADDR, + MSIC_BATT_CHR_CHRCTRL_ADDR, + MSIC_BATT_CHR_WDTWRITE_ADDR, + MSIC_BATT_CHR_CHRSTWDT_ADDR, + }; + static const unsigned char data[] = { + WDTWRITE_UNLOCK_VALUE, /* Unlock chrg params */ + CHRCNTL_CHRG_DISABLE, /* Disable Charging */ + 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 + */ + mutex_lock(&mbi->ipc_rw_lock); + for (i = 0; i < 4; i++) { + retval = intel_scu_ipc_iowrite8(address[i], data[i]); + if (retval) { + dev_warn(&mbi->pdev->dev, "%s:ipc msic write failed\n", + __func__); + goto ipc_write_failed; + } + } + +ipc_write_failed: + mutex_unlock(&mbi->ipc_rw_lock); + return retval; +} + +/** + * 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 retval, i; + uint16_t wdtwrite_addr; + uint8_t wdtwrite = WDTWRITE_UNLOCK_VALUE; + unsigned char data[4]; + static const uint16_t address[] = { + MSIC_BATT_CHR_CHRCCURRENT_ADDR, + MSIC_BATT_CHR_CHRCVOLTAGE_ADDR, + MSIC_BATT_CHR_CHRCTRL_ADDR, + MSIC_BATT_CHR_CHRSTWDT_ADDR, + }; + + if (!params) { + dev_warn(msic_dev, "%s:ipc msic write failed\n", __func__); + return -EBATTFAIL; + } + + data[0] = params->ccur; + data[1] = params->cvol; /* charge voltage 4.14V */ + data[2] = params->vinilmt; + data[3] = CHR_WDT_SET_60SEC; /* WTD Timer set to 60 Sec */ + + /* to unlock charger control regs */ + wdtwrite_addr = MSIC_BATT_CHR_WDTWRITE_ADDR; + + /* + * Charger disconnect handler also modifies the + * MSIC charger parameter registers.To avoid concurrent + * read writes to same set of registers locking applied + */ + mutex_lock(&mbi->ipc_rw_lock); + for (i = 0; i < 4; i++) { + retval = intel_scu_ipc_iowrite8(wdtwrite_addr, wdtwrite); + if (retval) { + dev_warn(msic_dev, "%s:ipc msic write failed\n", + __func__); + mutex_unlock(&mbi->ipc_rw_lock); + return retval; + } + retval = intel_scu_ipc_iowrite8(address[i], data[i]); + if (retval) { + dev_warn(msic_dev, "%s:ipc msic write failed\n", + __func__); + mutex_unlock(&mbi->ipc_rw_lock); + return retval; + } + } + mutex_unlock(&mbi->ipc_rw_lock); + dev_info(msic_dev, "Charger Enabled\n"); + + mutex_lock(&mbi->batt_lock); + mbi->batt_props.status = POWER_SUPPLY_STATUS_CHARGING; + mbi->batt_props.health = POWER_SUPPLY_HEALTH_GOOD; + mutex_unlock(&mbi->batt_lock); + + mutex_lock(&mbi->usb_chrg_lock); + mbi->usb_chrg_props.charger_health = POWER_SUPPLY_HEALTH_GOOD; + mbi->usb_chrg_props.charger_present = MSIC_USB_CHARGER_PRESENT; + memcpy(mbi->usb_chrg_props.charger_model, "msic", sizeof("msic")); + memcpy(mbi->usb_chrg_props.charger_vender, "Intel", sizeof("Intel")); + + if (mbi->ch_params.vinilmt == CHRG_CURR_SDP_LOW) { + mbi->usb_chrg_props.charger_type = + POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + } else { + mbi->usb_chrg_props.charger_type = + POWER_SUPPLY_CHARGE_TYPE_FAST; + } + mutex_unlock(&mbi->usb_chrg_lock); + + return BATTSUCCESS; +} + +static void reset_wdt_timer(struct msic_power_module_info *mbi) +{ + int retval; + + /* + * Charger disconnect handler also modifies the + * MSIC charger parameter registers.To avoid concurrent + * read writes to same set of registers locking applied + */ + mutex_lock(&mbi->ipc_rw_lock); + retval = intel_scu_ipc_iowrite8(MSIC_BATT_CHR_WDTWRITE_ADDR, + WDTWRITE_UNLOCK_VALUE); + if (retval) { + dev_warn(msic_dev, "%s:ipc msic write failed\n", __func__); + goto ipc_failed; + } + /* WDT timer set to 60 Sec */ + retval = intel_scu_ipc_iowrite8(MSIC_BATT_CHR_CHRSTWDT_ADDR, + CHR_WDT_SET_60SEC); + if (retval) + dev_warn(msic_dev, "%s:ipc msic write failed\n", __func__); +ipc_failed: + mutex_unlock(&mbi->ipc_rw_lock); +} + +static void msic_update_disconn_status(struct msic_power_module_info *mbi) +{ + int event; + + spin_lock(&mbi->event_lock); + event = mbi->batt_event; + spin_unlock(&mbi->event_lock); + + mutex_lock(&mbi->usb_chrg_lock); + 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_type = + POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; + mbi->usb_chrg_props.charger_present = + MSIC_USB_CHARGER_PRESENT; + } else { + mbi->usb_chrg_props.charger_type = + POWER_SUPPLY_CHARGE_TYPE_NONE; + mbi->usb_chrg_props.charger_present = + MSIC_USB_CHARGER_NOT_PRESENT; + } + mutex_unlock(&mbi->usb_chrg_lock); + + 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); + + power_supply_changed(&mbi->batt); +} + +/** +* 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; + static int iprev = -1; + short int cv = 0, cc = 0, vinlimit = 0; + 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 temperature_monitoring_range *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->usb_chrg_lock); + if (mbi->usb_chrg_props.charger_type == + POWER_SUPPLY_CHARGE_TYPE_UNKNOWN || + mbi->usb_chrg_props.charger_type == + POWER_SUPPLY_CHARGE_TYPE_NONE) { + + /* + * If the the charger type is unknown or None + * better start the charging again and compute + * the properties again. + */ + iprev = -1; + } + mutex_unlock(&mbi->usb_chrg_lock); + + ret = mdf_read_adc_regs(MSIC_ADC_TEMP_IDX, mbi); + /* mdf_read_adc_regs returns in milli Centigrade */ + adc_temp = ret / 1000; + + for (i = 0; i < sfi_table->temp_mon_ranges; i++) { + if ((adc_temp >= sfi_table->temp_mon_range[i].temp_low_lim) && + (adc_temp < sfi_table->temp_mon_range[i].temp_up_lim)) { + + cv = sfi_table->temp_mon_range[i].full_chrg_vol; + cc = sfi_table->temp_mon_range[i].full_chrg_cur; + + /* D7,D6 bits of CHRCNTL will set the VINILMT */ + if (charge_param.vinilmt > 950) + vinlimit = 0xC0; /* VINILMT set to No Limit */ + else if (charge_param.vinilmt > 500) + vinlimit = 0x80; /* VINILMT set to 950mA */ + else if (charge_param.vinilmt > 100) + vinlimit = 0x40; /* VINILMT set to 500mA */ + else + vinlimit = 0x00; /* VINILMT set to 100mA */ + + break; + } + } + + if (i >= sfi_table->temp_mon_ranges) { + dev_warn(msic_dev, "TEMP RANGE NOT EXIST\n"); + goto lbl_sched_work; + } + + /* + * If we are in same Temparature range check for check for the + * maintainence charging mode and enable the charging depending + * on the adc voltage and lower threshold. + * If Temparature range is changed then anyways we need to set + * charging parameters and enable charging, in that case no need + * to check for Maintainence mode. + */ + if (i == iprev) { + + spin_lock(&mbi->event_lock); + if (mbi->charging_mode == BATT_CHARGING_MODE_MAINTAINENCE) { + + spin_unlock(&mbi->event_lock); + temp_mon = &sfi_table->temp_mon_range[i]; + /* Read ADC value for battery Voltage */ + adc_vol = mdf_read_adc_regs(MSIC_ADC_VOL_IDX, mbi); + + /* + * Check if the voltage falls below lower threshold + * set charge voltage to maintainence voltage upper + * limit and enable charging otherwise just + * schedule the work + */ + if (adc_vol <= temp_mon->maint_chrg_vol_ll) + cv = temp_mon->maint_chrg_vol_ul; + else + goto lbl_sched_work; + + } else { + + spin_unlock(&mbi->event_lock); + + /* + * We are not in normal Mode and no change in temp + * range so just write the WDT timer and + * schedule the wirk again. + */ + dev_info(msic_dev, "NoChange in Temp Range\n"); + /* Reset WDT Timer Register for 60 Sec */ + reset_wdt_timer(mbi); + goto lbl_sched_work; + } + } + + iprev = i; + charge_param.cvol = CONV_VOL_DEC_MSICREG(cv); + + cc = cc - 550; + if (cc <= 0) + cc = 0; + else + cc = cc / 100; + cc = cc << 3; + + charge_param.ccur = cc; + + /* Enable Charge Termination */ + vinlimit |= 0x08; + charge_param.vinilmt = vinlimit; + + dev_info(msic_dev, "params vol: %x cur:%x vinilmt:%x\n", + charge_param.cvol, charge_param.ccur, charge_param.vinilmt); + + /* enable charging here */ + ret = msic_batt_do_charging(mbi, &charge_param); + if (ret) { + dev_warn(msic_dev, "msic_batt_do_charging failed\n"); + goto lbl_sched_work; + } + power_supply_changed(&mbi->usb); + +lbl_sched_work: + /* Schedule teh work after 30 Seconds */ + schedule_delayed_work(&mbi->connect_handler, TEMP_CHARGE_DELAY_JIFFIES); +} + + +static void msic_batt_disconn(struct work_struct *work) +{ + int ret; + struct msic_power_module_info *mbi = container_of(work, + struct msic_power_module_info, disconn_handler.work); + + ret = msic_batt_stop_charging(mbi); + if (ret) { + dev_info(msic_dev, "%s: failed\n", __func__); + return ; + } + msic_update_disconn_status(mbi); + power_supply_changed(&mbi->batt); +} + +/** + * msic_charger_callback - Callback function 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 bottem half to enbale 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; + + /* msic callback can be called from interrupt + * context, So used spin lock read or modify the + * msic event related variables and scheduling + * connection or disconnection delayed works depending + * on the USB event. + */ + spin_lock(&mbi->event_lock); + if (mbi->batt_event == event && event != USBCHRG_EVENT_UPDATE) { + spin_unlock(&mbi->event_lock); + return 0; + } + mbi->batt_event = event; + spin_unlock(&mbi->event_lock); + + switch (event) { + case USBCHRG_EVENT_CONNECT: + 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(&mbi->connect_handler); + + if (pm_runtime_suspended(&mbi->pdev->dev)) + pm_runtime_get_sync(&mbi->pdev->dev); + /* minimum charge current is 550 mA */ + mbi->ch_params.vinilmt = cap->mA; + mbi->ch_params.chrg_type = cap->chrg_type; + dev_info(msic_dev, "CHRG TYPE:%d %d\n", + cap->chrg_type, cap->mA); + + schedule_delayed_work(&mbi->connect_handler, 0); + + spin_lock(&mbi->event_lock); + mbi->charging_mode = BATT_CHARGING_MODE_NORMAL; + spin_unlock(&mbi->event_lock); + + break; + case USBCHRG_EVENT_DISCONN: + case USBCHRG_EVENT_SUSPEND: + dev_info(msic_dev, "USB DISCONN or SUSPEND\n"); + cancel_delayed_work(&mbi->connect_handler); + schedule_delayed_work(&mbi->disconn_handler, 0); + + spin_lock(&mbi->event_lock); + mbi->charging_mode = BATT_CHARGING_MODE_NONE; + spin_unlock(&mbi->event_lock); + if (!pm_runtime_suspended(&mbi->pdev->dev)) + pm_runtime_put_sync(&mbi->pdev->dev); + break; + default: + dev_warn(msic_dev, "Invalid OTG Event:%s\n", __func__); + } + + return 0; +} + +/** + * 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 = + (struct msic_power_module_info *)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_regs_iomap); + /* Add the Interrupt regs to FIFO */ + kfifo_in(&irq_fifo, ®_int_val, sizeof(u32)); + + 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 err, ret; + uint32_t batt_charge_val; + unsigned char data[2]; + struct msic_power_module_info *mbi = + (struct msic_power_module_info *)dev; + u32 tmp; + + /* 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 sate from FIFO */ + ret = kfifo_out(&irq_fifo, &tmp, sizeof(u32)); + if (ret != sizeof(u32)) { + dev_warn(msic_dev, "KFIFO underflow\n"); + return IRQ_NONE; + } + /* CHRINT Register */ + data[0] = (tmp & 0x00ff0000) >> 16; + /* CHRINT1 Register */ + data[1] = (tmp & 0xff000000) >> 24; + + dev_info(msic_dev, "PWRSRC Int %x %x\n", tmp & 0xff, + (tmp & 0xff00) >> 8); + dev_info(msic_dev, "CHR Int %x %x\n", data[0], data[1]); + + /* Check if charge complete */ + if (data[1] & MSIC_BATT_CHR_CHRCMPLT_MASK) { + dev_info(msic_dev, "CHRG COMPLT\n"); + /* Disable Charging */ + msic_batt_stop_charging(mbi); + + spin_lock(&mbi->event_lock); + mbi->charging_mode = BATT_CHARGING_MODE_MAINTAINENCE; + spin_unlock(&mbi->event_lock); + + + err = intel_scu_ipc_command(IPCMSG_BATTERY, 0x01, NULL, + 0, &batt_charge_val, 1); + mutex_lock(&mbi->batt_lock); + if (!err) + mbi->batt_props.charge_full = batt_charge_val; + mbi->batt_props.status = POWER_SUPPLY_STATUS_FULL; + mutex_unlock(&mbi->batt_lock); + + } + + if ((data[1] & MSIC_BATT_CHR_WKVINDET_MASK) || + (data[1] & MSIC_BATT_CHR_VINREGMINT_MASK)) { + + dev_info(msic_dev, "WEAK VIN or VINREGMINT DETCTED\n"); + + spin_lock(&mbi->event_lock); + /* Sometimes we may get weakVIN because of VBUS voltage + * drops even though there is no charger connected. + * So before we suspend the device check for the + * battery connection. + */ + if (mbi->batt_event != USBCHRG_EVENT_SUSPEND || + mbi->batt_event != USBCHRG_EVENT_DISCONN) { + spin_unlock(&mbi->event_lock); + /* In case of weakVIN the device can not recover from + * low voltage level.So its better to disconnect the + * charger and reconnect it again. From driver point + * of view we will just suspend the device. + */ + msic_charger_callback(mbi, USBCHRG_EVENT_SUSPEND, NULL); + } else { + spin_unlock(&mbi->event_lock); + dev_info(msic_dev, "WeakVIN when No charger preset\n"); + } + } + + /* Check if total charge time expired */ + if (data[0] & MSIC_BATT_CHR_TIMEEXP_MASK) { + dev_info(msic_dev, "CHR TIMER EXP\n"); + mutex_lock(&mbi->batt_lock); + mbi->batt_props.status = POWER_SUPPLY_STATUS_NOT_CHARGING; + mutex_unlock(&mbi->batt_lock); + } + + /* Check if an exception occured */ + if ((data[0] & ~MSIC_BATT_CHR_TIMEEXP_MASK) || + (data[1] & ~(MSIC_BATT_CHR_CHRCMPLT_MASK | + MSIC_BATT_CHR_WKVINDET_MASK | + MSIC_BATT_CHR_VINREGMINT_MASK))) { + + msic_handle_exception(mbi, data[0], data[1]); + } + + return IRQ_HANDLED; + +} + +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) { + dev_warn(msic_dev, "%s:ipc msic read failed\n", __func__); + return retval; + } + + 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); + return retval; + } + /* Enable charging only if vinilmt is >= 100mA */ + if (cap.mA >= 100) + msic_charger_callback(mbi, USBCHRG_EVENT_CONNECT, &cap); + } + + return retval; +} + +/** + * 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 + */ +void sfi_table_populate(struct msic_batt_sfi_prop *sfi_table) +{ + + /* This is temporary code, will be removed + * or modified as soon as the firmware supports + * SFI entries for MSIC battery. + */ + memcpy(sfi_table->sign, "BATT", sizeof("BATT")); + sfi_table->length = 183; + sfi_table->revision = 1; + sfi_table->checksum = 15; + memcpy(sfi_table->oem_id, "INTEL", sizeof("INTEL")); + memcpy(sfi_table->oem_tid, "OEMTID", sizeof("OEMTID")); + memcpy(sfi_table->batt_id.manufac, "NK", sizeof("NK")); + memcpy(sfi_table->batt_id.model, "BP4L", sizeof("BP4L")); + memcpy(sfi_table->batt_id.sub_ver, "00", sizeof("00")); + sfi_table->voltage_max = 4200; + sfi_table->capacity = 1500; + sfi_table->battery_type = 2; /* POWER_SUPPLY_TECHNOLOGY_LION */ + sfi_table->safe_temp_low_lim = 0; + sfi_table->safe_temp_up_lim = 60; + sfi_table->safe_vol_low_lim = 3700; + sfi_table->safe_vol_up_lim = 4200; + sfi_table->chrg_cur_lim = 1000; + sfi_table->chrg_term_lim = 1; + sfi_table->term_cur = 50; + sfi_table->temp_mon_ranges = 4; + + sfi_table->temp_mon_range[0].range_number = 0; + sfi_table->temp_mon_range[0].temp_low_lim = 45; + sfi_table->temp_mon_range[0].temp_up_lim = 60; + sfi_table->temp_mon_range[0].full_chrg_cur = 950; + sfi_table->temp_mon_range[0].full_chrg_vol = 4100; + sfi_table->temp_mon_range[0].maint_chrg_cur = 950; + sfi_table->temp_mon_range[0].maint_chrg_vol_ll = 4000; + sfi_table->temp_mon_range[0].maint_chrg_vol_ul = 4050; + sfi_table->temp_mon_range[1].range_number = 1; + sfi_table->temp_mon_range[1].temp_low_lim = 10; + sfi_table->temp_mon_range[1].temp_up_lim = 45; + sfi_table->temp_mon_range[1].full_chrg_cur = 950; + sfi_table->temp_mon_range[1].full_chrg_vol = 4200; + sfi_table->temp_mon_range[1].maint_chrg_cur = 950; + sfi_table->temp_mon_range[1].maint_chrg_vol_ll = 4100; + sfi_table->temp_mon_range[1].maint_chrg_vol_ul = 4150; + sfi_table->temp_mon_range[2].range_number = 2; + sfi_table->temp_mon_range[2].temp_low_lim = 0; + sfi_table->temp_mon_range[2].temp_up_lim = 10; + sfi_table->temp_mon_range[2].full_chrg_cur = 950; + sfi_table->temp_mon_range[2].full_chrg_vol = 4100; + sfi_table->temp_mon_range[2].maint_chrg_cur = 950; + sfi_table->temp_mon_range[2].maint_chrg_vol_ll = 4000; + sfi_table->temp_mon_range[2].maint_chrg_vol_ul = 4050; + sfi_table->temp_mon_range[3].range_number = 3; + sfi_table->temp_mon_range[3].temp_low_lim = -10; + sfi_table->temp_mon_range[3].temp_up_lim = 0; + sfi_table->temp_mon_range[3].full_chrg_cur = 350; + sfi_table->temp_mon_range[3].full_chrg_vol = 3900; + sfi_table->temp_mon_range[3].maint_chrg_cur = 350; + sfi_table->temp_mon_range[3].maint_chrg_vol_ll = 3950; + sfi_table->temp_mon_range[3].maint_chrg_vol_ul = 3950;; + + sfi_table->sram_addr = 0xFFFF7FC3; +} + +/** + * 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->batt_props.status = POWER_SUPPLY_STATUS_DISCHARGING; + mbi->batt_props.health = POWER_SUPPLY_HEALTH_GOOD; + mbi->batt_props.present = MSIC_BATT_NOT_PRESENT; + mbi->batt_props.technology = sfi_table->battery_type; + mbi->batt_props.vol_max_des = sfi_table->voltage_max; + mbi->batt_props.vol_now = 0x0; + mbi->batt_props.cur_now = 0x0; + mbi->batt_props.charge_full = 5400000; /* 5400 milii coulombs */ + mbi->batt_props.charge_now = 0x0; + mbi->batt_props.charge_avg = 0x0; + mbi->batt_props.capacity = 0; + mbi->batt_props.temperature = 0; + + memcpy(mbi->batt_props.vender, sfi_table->batt_id.manufac, + sizeof(sfi_table->batt_id.manufac)); + memcpy(mbi->batt_props.model, sfi_table->batt_id.model, + sizeof(sfi_table->batt_id.model)); + + /* read specific to determine the status */ + retval = intel_scu_ipc_ioread8(MSIC_BATT_CHR_SPWRSRCINT_ADDR, &data); + if (retval) + dev_warn(&mbi->pdev->dev, "%s:ipc read failed\n", __func__); + + /* 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; + +} + +/** + * 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_type = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; + 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 void init_msic_regs(struct msic_power_module_info *mbi) +{ + uint16_t wdtwrite_addr; + int retval, i; + uint16_t address[] = { + MSIC_BATT_CHR_PWRSRCLMT_ADDR, + MSIC_BATT_CHR_CHRCVOLTAGE_ADDR, + MSIC_BATT_CHR_CHRTTIME_ADDR, + MSIC_BATT_CHR_SPCHARGER_ADDR, + MSIC_BATT_CHR_CHRSTWDT_ADDR, + }; + unsigned char data[NR_ARR_ELM_MAX]; + + wdtwrite_addr = MSIC_BATT_CHR_WDTWRITE_ADDR; + /* Safe Temp window set from 0 to 60 Degree Centigrades */ + data[0] = CHR_PWRSRCLMT_SET_RANGE; + /* Setting CV to 4.14V */ + data[1] = CONV_VOL_DEC_MSICREG(CHR_CHRVOLTAGE_SET_DEF); + data[2] = CHR_CHRTIME_SET_12HRS; /* set charge timer to 12 hrs */ + /* Disable LOWCHR and Set WeakVin to 4.52V */ + data[3] = (~CHR_SPCHRGER_LOWCHR_ENABLE & + CHR_SPCHRGER_WEAKVIN); + data[4] = CHR_WDT_DISABLE; /* disable WDT timer */ + + for (i = 0; i < NR_ARR_ELM_MAX; i++) { + retval = intel_scu_ipc_iowrite8(wdtwrite_addr, + WDTWRITE_UNLOCK_VALUE); + if (retval) { + dev_warn(msic_dev, "%s:ipc msic write failed\n", + __func__); + } + retval = intel_scu_ipc_iowrite8(address[i], data[i]); + if (retval) { + dev_warn(msic_dev, "%s:ipc msic write failed\n", + __func__); + } + } +} + + +/** + * msic_battery_probe - msic battery initialize + * @pdev: msic battery platform device structure + * Context: can sleep + * + * MSIC battery initializes its internal data strucrue and other + * infrastructure components for it to work as expected. + */ +static int msic_battery_probe(struct platform_device *pdev) +{ + int retval; + 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; + } + + 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 approprite from otg callback + */ + INIT_DELAYED_WORK(&mbi->disconn_handler, msic_batt_disconn); + INIT_DELAYED_WORK(&mbi->connect_handler, msic_batt_temp_charging); + + + /* Initialize the spin locks */ + spin_lock_init(&mbi->event_lock); + + /* Initialize mutex locks */ + mutex_init(&mbi->usb_chrg_lock); + mutex_init(&mbi->batt_lock); + mutex_init(&mbi->ipc_rw_lock); + + /* Populate data from SFI Table */ + sfi_table_populate(sfi_table); + + /* Initialize battery and charger Properties*/ + init_batt_props(mbi); + init_charger_props(mbi); + + /* Re Map Phy address space for MSIC regs */ + mbi->msic_regs_iomap = ioremap_nocache(sfi_table->sram_addr, 8); + if (!mbi->msic_regs_iomap) { + dev_err(&pdev->dev, "battery: ioremap Failed\n"); + retval = -ENOMEM; + goto ioremap_failed; + } + + /* Init MSIC Registers */ + init_msic_regs(mbi); + + /* Initialize ADC Channels */ + mbi->adc_index = mdf_initialize_adc(mbi); + + /* register msic-batt with power supply subsystem */ + mbi->batt.name = "msic-battery"; + mbi->batt.type = POWER_SUPPLY_TYPE_BATTERY; + mbi->batt.properties = msic_battery_props; + mbi->batt.num_properties = ARRAY_SIZE(msic_battery_props); + mbi->batt.get_property = msic_battery_get_property; + retval = power_supply_register(&pdev->dev, &mbi->batt); + if (retval) { + dev_err(&pdev->dev, "%s(): failed to register msic battery " + "device with power supply subsystem\n", + __func__); + goto power_reg_failed_batt; + } + + /* register msic-usb with power supply subsystem */ + mbi->usb.name = "msic-charger"; + mbi->usb.type = POWER_SUPPLY_TYPE_USB; + 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; + } + + /* 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_set_active(&mbi->pdev->dev); + pm_runtime_enable(&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) + goto ipc_read_failed; + + /* 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; + } + + + return retval; + +requestirq_failed: + penwell_otg_unregister_bc_callback(otg_handle); +ipc_read_failed: +otg_failed: + power_supply_unregister(&mbi->usb); +power_reg_failed_usb: + power_supply_unregister(&mbi->batt); +power_reg_failed_batt: + iounmap(mbi->msic_regs_iomap); +ioremap_failed: + kfree(sfi_table); + kfree(mbi); + + return retval; +} + +/** + * 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(); + free_adc_channels(mbi->adc_index, mbi); + free_irq(mbi->irq, mbi); + if (mbi->msic_regs_iomap != NULL) + iounmap(mbi->msic_regs_iomap); + power_supply_unregister(&mbi->usb); + power_supply_unregister(&mbi->batt); + + kfree(sfi_table); + kfree(mbi); + } + + return 0; +} + +#ifdef CONFIG_PM +static int msic_battery_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct msic_power_module_info *mbi = platform_get_drvdata(pdev); + int event; + + spin_lock(&mbi->event_lock); + event = mbi->batt_event; + spin_unlock(&mbi->event_lock); + + if (event == USBCHRG_EVENT_CONNECT || + event == USBCHRG_EVENT_UPDATE || + event == USBCHRG_EVENT_RESUME) { + + msic_charger_callback(mbi, USBCHRG_EVENT_SUSPEND, NULL); + + dev_info(&mbi->pdev->dev, "Forced suspend\n"); + } + + return BATTSUCCESS; +} + +static int msic_battery_resume(struct platform_device *pdev) +{ + int retval = 0; + struct msic_power_module_info *mbi = platform_get_drvdata(pdev); + int event; + + spin_lock(&mbi->event_lock); + event = mbi->batt_event; + spin_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"); + } + + 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_info(dev, "%s called\n", __func__); + return BATTSUCCESS; +} + +static int msic_runtime_resume(struct device *dev) +{ + /* ToDo: Check for MSIC Power rails */ + dev_info(dev, "%s called\n", __func__); + return BATTSUCCESS; +} + +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_info(dev, "%s called\n", __func__); + + spin_lock(&mbi->event_lock); + event = mbi->batt_event; + spin_unlock(&mbi->event_lock); + + if (event == USBCHRG_EVENT_CONNECT || + event == USBCHRG_EVENT_UPDATE || + event == USBCHRG_EVENT_RESUME) { + + dev_warn(&mbi->pdev->dev, "%s(): devise busy\n", __func__); + + return -EBUSY; + } + + return BATTSUCCESS; +} +#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 = { + .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), + .suspend = msic_battery_suspend, + .resume = msic_battery_resume, + .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"); + + return ret; +} + +static void __exit msic_battery_module_exit(void) +{ + platform_driver_unregister(&msic_battery_driver); +} +module_init(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"); |