aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRamakrishna Pallala <ramakrishna.pallala@intel.com>2010-12-09 10:37:47 +0000
committerAlan Cox <alan@linux.intel.com>2010-12-09 10:37:47 +0000
commite913d81603e905670a65dfb44744656a678fba31 (patch)
tree8dec4d792d6a799c6c463a02ebbbefd39a261900
parentaf27295dacc7f65fde0940e115d6eb5440a2d72d (diff)
downloadmrst-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>
-rw-r--r--drivers/power/Kconfig7
-rw-r--r--drivers/power/Makefile1
-rw-r--r--drivers/power/intel_mdf_battery.c2158
3 files changed, 2166 insertions, 0 deletions
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
index 60d83d983a3..b6e41e6cb87 100644
--- a/drivers/power/Kconfig
+++ b/drivers/power/Kconfig
@@ -185,4 +185,11 @@ config CHARGER_TWL4030
help
Say Y here to enable support for TWL4030 Battery Charge Interface.
+config BATTERY_INTEL_MDF
+ tristate "Battery driver for Intel MDFLD platforms"
+ depends on INTEL_SCU_IPC && USB_PENWELL_OTG
+ help
+ Say Y here to enable the battery driver on Intel MFLD
+ platforms.
+
endif # POWER_SUPPLY
diff --git a/drivers/power/Makefile b/drivers/power/Makefile
index c75772eb157..ddeed547e56 100644
--- a/drivers/power/Makefile
+++ b/drivers/power/Makefile
@@ -32,3 +32,4 @@ obj-$(CONFIG_BATTERY_JZ4740) += jz4740-battery.o
obj-$(CONFIG_BATTERY_INTEL_MID) += intel_mid_battery.o
obj-$(CONFIG_CHARGER_ISP1704) += isp1704_charger.o
obj-$(CONFIG_CHARGER_TWL4030) += twl4030_charger.o
+obj-$(CONFIG_BATTERY_INTEL_MDF) += intel_mdf_battery.o
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, &reg_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");