android_kernel_modules_leno.../drivers/power/pmic_ccsm.c

2344 lines
59 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* pmic_ccsm.c - Intel MID PMIC Charger Driver
*
* Copyright (C) 2011 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: Jenny TC <jenny.tc@intel.com>
* Author: Yegnesh Iyer <yegnesh.s.iyer@intel.com>
*/
/* Includes */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
#include <linux/jiffies.h>
#include <linux/seq_file.h>
#include <linux/debugfs.h>
#include <linux/slab.h>
#include <linux/kfifo.h>
#include <linux/param.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/usb/otg.h>
#include <linux/power_supply.h>
#include <linux/wakelock.h>
#include <linux/power_supply.h>
#include <linux/rpmsg.h>
#include <linux/version.h>
#include <asm/intel_basincove_gpadc.h>
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0))
#include <linux/iio/consumer.h>
#else
#include "../../../kernel/drivers/staging/iio/consumer.h"
#endif
#include <asm/intel_scu_pmic.h>
#include <asm/intel_mid_rpmsg.h>
#include <asm/intel_mid_remoteproc.h>
#include <linux/io.h>
#include <linux/sched.h>
#include <linux/pm_runtime.h>
#include <linux/sfi.h>
#include <linux/async.h>
#include <linux/reboot.h>
#include <linux/notifier.h>
#include <linux/power/battery_id.h>
#include "pmic_ccsm.h"
/* Macros */
#define DRIVER_NAME "pmic_ccsm"
#define PMIC_SRAM_INTR_ADDR 0xFFFFF616
#define ADC_TO_TEMP 1
#define TEMP_TO_ADC 0
#define is_valid_temp(tmp)\
(!(tmp > chc.pdata->adc_tbl[0].temp ||\
tmp < chc.pdata->adc_tbl[chc.pdata->max_tbl_row_cnt - 1].temp))
#define is_valid_adc_code(val)\
(!(val < chc.pdata->adc_tbl[0].adc_val ||\
val > chc.pdata->adc_tbl[chc.pdata->max_tbl_row_cnt - 1].adc_val))
#define CONVERT_ADC_TO_TEMP(adc_val, temp)\
adc_temp_conv(adc_val, temp, ADC_TO_TEMP)
#define CONVERT_TEMP_TO_ADC(temp, adc_val)\
adc_temp_conv(temp, adc_val, TEMP_TO_ADC)
#define NEED_ZONE_SPLIT(bprof)\
((bprof->temp_mon_ranges < MIN_BATT_PROF))
#define USB_WAKE_LOCK_TIMEOUT (5 * HZ)
/* 100mA value definition for setting the inlimit in bq24261 */
#define USBINPUTICC100VAL 100
#define OHM_MULTIPLIER 10
/* Type definitions */
static void pmic_bat_zone_changed(void);
static void pmic_battery_overheat_handler(bool);
/* Extern definitions */
/* Global declarations */
static DEFINE_MUTEX(pmic_lock);
static struct pmic_chrgr_drv_context chc;
static struct interrupt_info chgrirq0_info[] = {
{
CHGIRQ0_BZIRQ_MASK,
0,
"Battery temperature zone changed",
NULL,
NULL,
pmic_bat_zone_changed,
NULL,
},
{
CHGIRQ0_BAT_CRIT_MASK,
SCHGIRQ0_SBAT_CRIT_MASK,
NULL,
"Battery Over heat exception",
"Battery Over heat exception Recovered",
NULL,
pmic_battery_overheat_handler
},
{
CHGIRQ0_BAT0_ALRT_MASK,
SCHGIRQ0_SBAT0_ALRT_MASK,
NULL,
"Battery0 temperature inside boundary",
"Battery0 temperature outside boundary",
NULL,
pmic_battery_overheat_handler
},
{
CHGIRQ0_BAT1_ALRT_MASK,
SCHGIRQ0_SBAT1_ALRT_MASK,
NULL,
"Battery1 temperature inside boundary",
"Battery1 temperature outside boundary",
NULL,
NULL
},
};
u16 pmic_inlmt[][2] = {
{ 100, CHGRCTRL1_FUSB_INLMT_100},
{ 150, CHGRCTRL1_FUSB_INLMT_150},
{ 500, CHGRCTRL1_FUSB_INLMT_500},
{ 900, CHGRCTRL1_FUSB_INLMT_900},
{ 1500, CHGRCTRL1_FUSB_INLMT_1500},
{ 2000, CHGRCTRL1_FUSB_INLMT_1500},
{ 2500, CHGRCTRL1_FUSB_INLMT_1500},
};
static inline struct power_supply *get_psy_battery(void)
{
struct class_dev_iter iter;
struct device *dev;
struct power_supply *pst;
class_dev_iter_init(&iter, power_supply_class, NULL, NULL);
while ((dev = class_dev_iter_next(&iter))) {
pst = (struct power_supply *)dev_get_drvdata(dev);
if (pst->type == POWER_SUPPLY_TYPE_BATTERY) {
class_dev_iter_exit(&iter);
return pst;
}
}
class_dev_iter_exit(&iter);
return NULL;
}
/* Function definitions */
static void lookup_regval(u16 tbl[][2], size_t size, u16 in_val, u8 *out_val)
{
int i;
for (i = 1; i < size; ++i)
if (in_val < tbl[i][0])
break;
*out_val = (u8)tbl[i-1][1];
}
static int interpolate_y(int dx1x0, int dy1y0, int dxx0, int y0)
{
return y0 + DIV_ROUND_CLOSEST((dxx0 * dy1y0), dx1x0);
}
static int interpolate_x(int dy1y0, int dx1x0, int dyy0, int x0)
{
return x0 + DIV_ROUND_CLOSEST((dyy0 * dx1x0), dy1y0);
}
static int adc_temp_conv(int in_val, int *out_val, int conv)
{
int tbl_row_cnt, i;
struct temp_lookup *adc_temp_tbl;
if (!chc.pdata) {
dev_err(chc.dev, "ADC-lookup table not yet available\n");
return -ERANGE;
}
tbl_row_cnt = chc.pdata->max_tbl_row_cnt;
adc_temp_tbl = chc.pdata->adc_tbl;
if (conv == ADC_TO_TEMP) {
if (!is_valid_adc_code(in_val))
return -ERANGE;
if (in_val == adc_temp_tbl[tbl_row_cnt-1].adc_val)
i = tbl_row_cnt - 1;
else {
for (i = 0; i < tbl_row_cnt; ++i)
if (in_val < adc_temp_tbl[i].adc_val)
break;
}
*out_val =
interpolate_y((adc_temp_tbl[i].adc_val
- adc_temp_tbl[i - 1].adc_val),
(adc_temp_tbl[i].temp
- adc_temp_tbl[i - 1].temp),
(in_val - adc_temp_tbl[i - 1].adc_val),
adc_temp_tbl[i - 1].temp);
} else {
if (!is_valid_temp(in_val))
return -ERANGE;
if (in_val == adc_temp_tbl[tbl_row_cnt-1].temp)
i = tbl_row_cnt - 1;
else {
for (i = 0; i < tbl_row_cnt; ++i)
if (in_val > adc_temp_tbl[i].temp)
break;
}
*((short int *)out_val) =
interpolate_x((adc_temp_tbl[i].temp
- adc_temp_tbl[i - 1].temp),
(adc_temp_tbl[i].adc_val
- adc_temp_tbl[i - 1].adc_val),
(in_val - adc_temp_tbl[i - 1].temp),
adc_temp_tbl[i - 1].adc_val);
}
return 0;
}
static int pmic_read_reg(u16 addr, u8 *val)
{
int ret;
ret = intel_scu_ipc_ioread8(addr, val);
if (ret) {
dev_err(chc.dev,
"Error in intel_scu_ipc_ioread8 0x%.4x\n", addr);
return -EIO;
}
return 0;
}
static int __pmic_write_tt(u8 addr, u8 data)
{
int ret;
ret = intel_scu_ipc_iowrite8(CHRTTADDR_ADDR, addr);
if (unlikely(ret))
return ret;
return intel_scu_ipc_iowrite8(CHRTTDATA_ADDR, data);
}
static inline int pmic_write_tt(u8 addr, u8 data)
{
int ret;
mutex_lock(&pmic_lock);
ret = __pmic_write_tt(addr, data);
mutex_unlock(&pmic_lock);
/* If access is blocked return success to avoid additional
* error handling at client side
*/
if (ret == -EACCES) {
dev_warn(chc.dev, "IPC write blocked due to unsigned kernel/invalid battery\n");
ret = 0;
}
return ret;
}
static int __pmic_read_tt(u8 addr, u8 *data)
{
int ret;
ret = intel_scu_ipc_iowrite8(CHRTTADDR_ADDR, addr);
if (ret)
return ret;
usleep_range(2000, 3000);
return intel_scu_ipc_ioread8(CHRTTDATA_ADDR, data);
}
static inline int pmic_read_tt(u8 addr, u8 *data)
{
int ret;
mutex_lock(&pmic_lock);
ret = __pmic_read_tt(addr, data);
mutex_unlock(&pmic_lock);
return ret;
}
static int pmic_update_tt(u8 addr, u8 mask, u8 data)
{
u8 tdata;
int ret;
mutex_lock(&pmic_lock);
ret = __pmic_read_tt(addr, &tdata);
if (unlikely(ret))
goto exit;
tdata = (tdata & ~mask) | (data & mask);
ret = __pmic_write_tt(addr, tdata);
exit:
mutex_unlock(&pmic_lock);
return ret;
}
#ifdef CONFIG_DEBUG_FS
static int pmic_chrgr_reg_show(struct seq_file *seq, void *unused)
{
int ret;
u16 addr;
u16 val1;
u8 val;
addr = *((u8 *)seq->private);
if (addr == CHRGRIRQ1_ADDR) {
val1 = ioread16(chc.pmic_intr_iomap);
val = (u8)(val1 >> 8);
} else if (addr == CHGRIRQ0_ADDR) {
val1 = ioread16(chc.pmic_intr_iomap);
val = (u8)val1;
} else {
ret = pmic_read_reg(addr, &val);
if (ret != 0) {
dev_err(chc.dev,
"Error reading tt register 0x%2x\n",
addr);
return -EIO;
}
}
seq_printf(seq, "0x%x\n", val);
return 0;
}
static int pmic_chrgr_tt_reg_show(struct seq_file *seq, void *unused)
{
int ret;
u8 addr;
u8 val;
addr = *((u8 *)seq->private);
ret = pmic_read_tt(addr, &val);
if (ret != 0) {
dev_err(chc.dev,
"Error reading tt register 0x%2x\n",
addr);
return -EIO;
}
seq_printf(seq, "0x%x\n", val);
return 0;
}
static int pmic_chrgr_tt_reg_open(struct inode *inode, struct file *file)
{
return single_open(file, pmic_chrgr_tt_reg_show, inode->i_private);
}
static int pmic_chrgr_reg_open(struct inode *inode, struct file *file)
{
return single_open(file, pmic_chrgr_reg_show, inode->i_private);
}
static struct dentry *charger_debug_dir;
static struct pmic_regs_def pmic_regs_bc[] = {
PMIC_REG_DEF(PMIC_ID_ADDR),
PMIC_REG_DEF(IRQLVL1_ADDR),
PMIC_REG_DEF(IRQLVL1_MASK_ADDR),
PMIC_REG_DEF(CHGRIRQ0_ADDR),
PMIC_REG_DEF(SCHGRIRQ0_ADDR),
PMIC_REG_DEF(MCHGRIRQ0_ADDR),
PMIC_REG_DEF(LOWBATTDET0_ADDR),
PMIC_REG_DEF(LOWBATTDET1_ADDR),
PMIC_REG_DEF(BATTDETCTRL_ADDR),
PMIC_REG_DEF(VBUSDETCTRL_ADDR),
PMIC_REG_DEF(VDCINDETCTRL_ADDR),
PMIC_REG_DEF(CHRGRIRQ1_ADDR),
PMIC_REG_DEF(SCHGRIRQ1_ADDR),
PMIC_REG_DEF(MCHGRIRQ1_ADDR),
PMIC_REG_DEF(CHGRCTRL0_ADDR),
PMIC_REG_DEF(CHGRCTRL1_ADDR),
PMIC_REG_DEF(CHGRSTATUS_ADDR),
PMIC_REG_DEF(USBIDCTRL_ADDR),
PMIC_REG_DEF(USBIDSTAT_ADDR),
PMIC_REG_DEF(WAKESRC_ADDR),
PMIC_REG_DEF(THRMBATZONE_ADDR_BC),
PMIC_REG_DEF(THRMZN0L_ADDR_BC),
PMIC_REG_DEF(THRMZN0H_ADDR_BC),
PMIC_REG_DEF(THRMZN1L_ADDR_BC),
PMIC_REG_DEF(THRMZN1H_ADDR_BC),
PMIC_REG_DEF(THRMZN2L_ADDR_BC),
PMIC_REG_DEF(THRMZN2H_ADDR_BC),
PMIC_REG_DEF(THRMZN3L_ADDR_BC),
PMIC_REG_DEF(THRMZN3H_ADDR_BC),
PMIC_REG_DEF(THRMZN4L_ADDR_BC),
PMIC_REG_DEF(THRMZN4H_ADDR_BC),
};
static struct pmic_regs_def pmic_regs_sc[] = {
PMIC_REG_DEF(PMIC_ID_ADDR),
PMIC_REG_DEF(IRQLVL1_ADDR),
PMIC_REG_DEF(IRQLVL1_MASK_ADDR),
PMIC_REG_DEF(CHGRIRQ0_ADDR),
PMIC_REG_DEF(SCHGRIRQ0_ADDR),
PMIC_REG_DEF(MCHGRIRQ0_ADDR),
PMIC_REG_DEF(LOWBATTDET0_ADDR),
PMIC_REG_DEF(LOWBATTDET1_ADDR),
PMIC_REG_DEF(BATTDETCTRL_ADDR),
PMIC_REG_DEF(VBUSDETCTRL_ADDR),
PMIC_REG_DEF(VDCINDETCTRL_ADDR),
PMIC_REG_DEF(CHRGRIRQ1_ADDR),
PMIC_REG_DEF(SCHGRIRQ1_ADDR),
PMIC_REG_DEF(MCHGRIRQ1_ADDR),
PMIC_REG_DEF(CHGRCTRL0_ADDR),
PMIC_REG_DEF(CHGRCTRL1_ADDR),
PMIC_REG_DEF(CHGRSTATUS_ADDR),
PMIC_REG_DEF(USBIDCTRL_ADDR),
PMIC_REG_DEF(USBIDSTAT_ADDR),
PMIC_REG_DEF(WAKESRC_ADDR),
PMIC_REG_DEF(USBPHYCTRL_ADDR),
PMIC_REG_DEF(DBG_USBBC1_ADDR),
PMIC_REG_DEF(DBG_USBBC2_ADDR),
PMIC_REG_DEF(DBG_USBBCSTAT_ADDR),
PMIC_REG_DEF(USBPATH_ADDR),
PMIC_REG_DEF(USBSRCDETSTATUS_ADDR),
PMIC_REG_DEF(THRMBATZONE_ADDR_SC),
PMIC_REG_DEF(THRMZN0L_ADDR_SC),
PMIC_REG_DEF(THRMZN0H_ADDR_SC),
PMIC_REG_DEF(THRMZN1L_ADDR_SC),
PMIC_REG_DEF(THRMZN1H_ADDR_SC),
PMIC_REG_DEF(THRMZN2L_ADDR_SC),
PMIC_REG_DEF(THRMZN2H_ADDR_SC),
PMIC_REG_DEF(THRMZN3L_ADDR_SC),
PMIC_REG_DEF(THRMZN3H_ADDR_SC),
PMIC_REG_DEF(THRMZN4L_ADDR_SC),
PMIC_REG_DEF(THRMZN4H_ADDR_SC),
};
static struct pmic_regs_def pmic_tt_regs[] = {
PMIC_REG_DEF(TT_I2CDADDR_ADDR),
PMIC_REG_DEF(TT_CHGRINIT0OS_ADDR),
PMIC_REG_DEF(TT_CHGRINIT1OS_ADDR),
PMIC_REG_DEF(TT_CHGRINIT2OS_ADDR),
PMIC_REG_DEF(TT_CHGRINIT3OS_ADDR),
PMIC_REG_DEF(TT_CHGRINIT4OS_ADDR),
PMIC_REG_DEF(TT_CHGRINIT5OS_ADDR),
PMIC_REG_DEF(TT_CHGRINIT6OS_ADDR),
PMIC_REG_DEF(TT_CHGRINIT7OS_ADDR),
PMIC_REG_DEF(TT_USBINPUTICCOS_ADDR),
PMIC_REG_DEF(TT_USBINPUTICCMASK_ADDR),
PMIC_REG_DEF(TT_CHRCVOS_ADDR),
PMIC_REG_DEF(TT_CHRCVMASK_ADDR),
PMIC_REG_DEF(TT_CHRCCOS_ADDR),
PMIC_REG_DEF(TT_CHRCCMASK_ADDR),
PMIC_REG_DEF(TT_LOWCHROS_ADDR),
PMIC_REG_DEF(TT_LOWCHRMASK_ADDR),
PMIC_REG_DEF(TT_WDOGRSTOS_ADDR),
PMIC_REG_DEF(TT_WDOGRSTMASK_ADDR),
PMIC_REG_DEF(TT_CHGRENOS_ADDR),
PMIC_REG_DEF(TT_CHGRENMASK_ADDR),
PMIC_REG_DEF(TT_CUSTOMFIELDEN_ADDR),
PMIC_REG_DEF(TT_CHGRINIT0VAL_ADDR),
PMIC_REG_DEF(TT_CHGRINIT1VAL_ADDR),
PMIC_REG_DEF(TT_CHGRINIT2VAL_ADDR),
PMIC_REG_DEF(TT_CHGRINIT3VAL_ADDR),
PMIC_REG_DEF(TT_CHGRINIT4VAL_ADDR),
PMIC_REG_DEF(TT_CHGRINIT5VAL_ADDR),
PMIC_REG_DEF(TT_CHGRINIT6VAL_ADDR),
PMIC_REG_DEF(TT_CHGRINIT7VAL_ADDR),
PMIC_REG_DEF(TT_USBINPUTICC100VAL_ADDR),
PMIC_REG_DEF(TT_USBINPUTICC150VAL_ADDR),
PMIC_REG_DEF(TT_USBINPUTICC500VAL_ADDR),
PMIC_REG_DEF(TT_USBINPUTICC900VAL_ADDR),
PMIC_REG_DEF(TT_USBINPUTICC1500VAL_ADDR),
PMIC_REG_DEF(TT_CHRCVEMRGLOWVAL_ADDR),
PMIC_REG_DEF(TT_CHRCVCOLDVAL_ADDR),
PMIC_REG_DEF(TT_CHRCVCOOLVAL_ADDR),
PMIC_REG_DEF(TT_CHRCVWARMVAL_ADDR),
PMIC_REG_DEF(TT_CHRCVHOTVAL_ADDR),
PMIC_REG_DEF(TT_CHRCVEMRGHIVAL_ADDR),
PMIC_REG_DEF(TT_CHRCCEMRGLOWVAL_ADDR),
PMIC_REG_DEF(TT_CHRCCCOLDVAL_ADDR),
PMIC_REG_DEF(TT_CHRCCCOOLVAL_ADDR),
PMIC_REG_DEF(TT_CHRCCWARMVAL_ADDR),
PMIC_REG_DEF(TT_CHRCCHOTVAL_ADDR),
PMIC_REG_DEF(TT_CHRCCEMRGHIVAL_ADDR),
PMIC_REG_DEF(TT_LOWCHRENVAL_ADDR),
PMIC_REG_DEF(TT_LOWCHRDISVAL_ADDR),
};
void dump_pmic_regs(void)
{
int vendor_id = chc.pmic_id & PMIC_VENDOR_ID_MASK;
u32 pmic_reg_cnt = 0;
u32 reg_index;
u8 data;
int retval;
struct pmic_regs_def *pmic_regs = NULL;
if (vendor_id == BASINCOVE_VENDORID) {
pmic_reg_cnt = ARRAY_SIZE(pmic_regs_bc);
pmic_regs = pmic_regs_bc;
} else if (vendor_id == SHADYCOVE_VENDORID) {
pmic_reg_cnt = ARRAY_SIZE(pmic_regs_sc);
pmic_regs = pmic_regs_sc;
}
dev_info(chc.dev, "PMIC Register dump\n");
dev_info(chc.dev, "====================\n");
for (reg_index = 0; reg_index < pmic_reg_cnt; reg_index++) {
retval = intel_scu_ipc_ioread8(pmic_regs[reg_index].addr,
&data);
if (retval)
dev_err(chc.dev, "Error in reading %x\n",
pmic_regs[reg_index].addr);
else
dev_info(chc.dev, "0x%x=0x%x\n",
pmic_regs[reg_index].addr, data);
}
dev_info(chc.dev, "====================\n");
}
void dump_pmic_tt_regs(void)
{
u32 pmic_tt_reg_cnt = ARRAY_SIZE(pmic_tt_regs);
u32 reg_index;
u8 data;
int retval;
dev_info(chc.dev, "PMIC CHRGR TT dump\n");
dev_info(chc.dev, "====================\n");
for (reg_index = 0; reg_index < pmic_tt_reg_cnt; reg_index++) {
retval = pmic_read_tt(pmic_tt_regs[reg_index].addr, &data);
if (retval)
dev_err(chc.dev, "Error in reading %x\n",
pmic_tt_regs[reg_index].addr);
else
dev_info(chc.dev, "0x%x=0x%x\n",
pmic_tt_regs[reg_index].addr, data);
}
dev_info(chc.dev, "====================\n");
}
static const struct file_operations pmic_chrgr_reg_fops = {
.open = pmic_chrgr_reg_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release
};
static const struct file_operations pmic_chrgr_tt_reg_fops = {
.open = pmic_chrgr_tt_reg_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release
};
static void pmic_debugfs_init(void)
{
struct dentry *fentry;
struct dentry *pmic_regs_dir;
struct dentry *pmic_tt_regs_dir;
u32 reg_index;
int vendor_id = chc.pmic_id & PMIC_VENDOR_ID_MASK;
u32 pmic_reg_cnt = 0;
u32 pmic_tt_reg_cnt = ARRAY_SIZE(pmic_tt_regs);
char name[PMIC_REG_NAME_LEN] = {0};
struct pmic_regs_def *pmic_regs = NULL;
if (vendor_id == BASINCOVE_VENDORID) {
pmic_reg_cnt = ARRAY_SIZE(pmic_regs_bc);
pmic_regs = pmic_regs_bc;
} else if (vendor_id == SHADYCOVE_VENDORID) {
pmic_reg_cnt = ARRAY_SIZE(pmic_regs_sc);
pmic_regs = pmic_regs_sc;
}
/* Creating a directory under debug fs for charger */
charger_debug_dir = debugfs_create_dir(DRIVER_NAME , NULL) ;
if (charger_debug_dir == NULL)
goto debugfs_root_exit;
/* Create a directory for pmic charger registers */
pmic_regs_dir = debugfs_create_dir("pmic_ccsm_regs",
charger_debug_dir);
if (pmic_regs_dir == NULL)
goto debugfs_err_exit;
for (reg_index = 0; reg_index < pmic_reg_cnt; reg_index++) {
sprintf(name, "%s",
pmic_regs[reg_index].reg_name);
fentry = debugfs_create_file(name,
S_IRUGO,
pmic_regs_dir,
&pmic_regs[reg_index].addr,
&pmic_chrgr_reg_fops);
if (fentry == NULL)
goto debugfs_err_exit;
}
/* Create a directory for pmic tt charger registers */
pmic_tt_regs_dir = debugfs_create_dir("pmic_ccsm_tt_regs",
charger_debug_dir);
if (pmic_tt_regs_dir == NULL)
goto debugfs_err_exit;
for (reg_index = 0; reg_index < pmic_tt_reg_cnt; reg_index++) {
sprintf(name, "%s", pmic_tt_regs[reg_index].reg_name);
fentry = debugfs_create_file(name,
S_IRUGO,
pmic_tt_regs_dir,
&pmic_tt_regs[reg_index].addr,
&pmic_chrgr_tt_reg_fops);
if (fentry == NULL)
goto debugfs_err_exit;
}
dev_dbg(chc.dev, "Debugfs created successfully!!");
return;
debugfs_err_exit:
debugfs_remove_recursive(charger_debug_dir);
debugfs_root_exit:
dev_err(chc.dev, "Error creating debugfs entry!!");
return;
}
static void pmic_debugfs_exit(void)
{
if (charger_debug_dir != NULL)
debugfs_remove_recursive(charger_debug_dir);
}
#endif
static void pmic_get_bat_zone(int *bat_zone)
{
u8 data = 0;
u16 addr = 0;
int vendor_id, ret;
vendor_id = chc.pmic_id & PMIC_VENDOR_ID_MASK;
if (vendor_id == BASINCOVE_VENDORID)
addr = THRMBATZONE_ADDR_BC;
else if (vendor_id == SHADYCOVE_VENDORID)
addr = THRMBATZONE_ADDR_SC;
ret = intel_scu_ipc_ioread8(addr, &data);
if (ret) {
dev_err(chc.dev, "Error:%d in reading battery zone\n", ret);
/* Return undetermined zone in case of IPC failure */
*bat_zone = PMIC_BZONE_UNKNOWN;
return;
}
*bat_zone = (data & THRMBATZONE_MASK);
}
static void pmic_bat_zone_changed(void)
{
int retval;
int cur_zone, temp = 0;
u16 addr = 0;
u8 data = 0;
struct power_supply *psy_bat;
int vendor_id;
pmic_get_bat_zone(&cur_zone);
pmic_get_battery_pack_temp(&temp);
dev_info(chc.dev, "Battery Zone changed. Current zone:%d, temp:%d\n",
cur_zone, temp);
/* if current zone is the top and bottom zones then report OVERHEAT
*/
if ((cur_zone == PMIC_BZONE_LOW) || (cur_zone == PMIC_BZONE_HIGH))
chc.health = POWER_SUPPLY_HEALTH_OVERHEAT;
else if (cur_zone == PMIC_BZONE_UNKNOWN)
chc.health = POWER_SUPPLY_HEALTH_UNKNOWN;
else
chc.health = POWER_SUPPLY_HEALTH_GOOD;
psy_bat = get_psy_battery();
if (psy_bat && psy_bat->external_power_changed)
psy_bat->external_power_changed(psy_bat);
return;
}
static void pmic_battery_overheat_handler(bool stat)
{
if (stat)
chc.health = POWER_SUPPLY_HEALTH_OVERHEAT;
else
chc.health = POWER_SUPPLY_HEALTH_GOOD;
return;
}
int pmic_get_health(void)
{
return chc.health;
}
int pmic_enable_vbus(bool enable)
{
int ret = 0;
if (enable)
ret = intel_scu_ipc_update_register(CHGRCTRL0_ADDR,
WDT_NOKICK_ENABLE, CHGRCTRL0_WDT_NOKICK_MASK);
else
ret = intel_scu_ipc_update_register(CHGRCTRL0_ADDR,
WDT_NOKICK_DISABLE, CHGRCTRL0_WDT_NOKICK_MASK);
/* If access is blocked return success to avoid additional
* error handling at client side
*/
if (ret == -EACCES) {
dev_warn(chc.dev, "IPC blocked due to unsigned kernel/invalid battery\n");
ret = 0;
}
return ret;
}
int pmic_handle_otgmode(bool enable)
{
int ret = 0;
int vendor_id;
vendor_id = chc.pmic_id & PMIC_VENDOR_ID_MASK;
if (vendor_id != SHADYCOVE_VENDORID) {
dev_err(chc.dev, "Ignore otg-mode event received\n");
return 0;
}
if (enable) {
ret = intel_scu_ipc_update_register(CHGRCTRL1_ADDR,
CHGRCTRL1_OTGMODE_MASK,
CHGRCTRL1_OTGMODE_MASK);
/* ShadyCove PMIC doesnt kick charger-WDT during host-mode.
* Driver does this regularly as a w/a. But, during suspend,
* since this driver code doesnt run, VBUS drops, DUT wakes
* up, and re-enumerates again.
* Hence, during host-mode, driver shall hold a wakelock.
*/
dev_info(chc.dev, "Hold wakelock for host-mode WDT-kick\n");
if (!wake_lock_active(&chc.otg_wa_wakelock)) {
wake_lock(&chc.otg_wa_wakelock);
}
} else {
ret = intel_scu_ipc_update_register(CHGRCTRL1_ADDR,
0x0, CHGRCTRL1_OTGMODE_MASK);
dev_info(chc.dev, "Release wakelock for host-mode WDT-kick\n");
if (wake_lock_active(&chc.otg_wa_wakelock)) {
wake_unlock(&chc.otg_wa_wakelock);
}
}
/* If access is blocked return success to avoid additional
* error handling at client side
*/
if (ret == -EACCES) {
dev_warn(chc.dev, "IPC blocked due to unsigned kernel/invalid battery\n");
ret = 0;
}
return ret;
}
int pmic_enable_charging(bool enable)
{
int ret;
u8 val;
if (enable) {
ret = intel_scu_ipc_update_register(CHGRCTRL1_ADDR,
CHGRCTRL1_FTEMP_EVENT_MASK, CHGRCTRL1_FTEMP_EVENT_MASK);
if (ret)
return ret;
}
val = (enable) ? 0 : EXTCHRDIS_ENABLE;
ret = intel_scu_ipc_update_register(CHGRCTRL0_ADDR,
val, CHGRCTRL0_EXTCHRDIS_MASK);
/* If access is blocked return success to avoid additional
* error handling at client side
*/
if (ret == -EACCES) {
dev_warn(chc.dev, "IPC blocked due to unsigned kernel/invalid battery\n");
ret = 0;
}
return ret;
}
static inline int update_zone_cc(int zone, u8 reg_val)
{
u8 addr_cc = TT_CHRCCHOTVAL_ADDR - zone;
dev_dbg(chc.dev, "%s:%X=%X\n", __func__, addr_cc, reg_val);
return pmic_write_tt(addr_cc, reg_val);
}
static inline int update_zone_cv(int zone, u8 reg_val)
{
u8 addr_cv = TT_CHRCVHOTVAL_ADDR - zone;
dev_dbg(chc.dev, "%s:%X=%X\n", __func__, addr_cv, reg_val);
return pmic_write_tt(addr_cv, reg_val);
}
/**
* get_scove_tempzone_val - get tempzone register val for a particular zone
* @adc_val: adc_value passed for zone temp
* @temp: zone temperature
*
* Returns temp zone alert value
*/
static u16 get_scove_tempzone_val(u16 resi_val, int temp)
{
u8 cursel = 0, hys = 0;
u16 trsh = 0, count = 0, bsr_num = 0;
u16 adc_thold = 0, tempzone_val = 0;
s16 hyst = 0;
int retval;
/* multiply to convert into Ohm*/
resi_val *= OHM_MULTIPLIER;
/* CUR = max(floor(log2(round(ADCNORM/2^5)))-7,0)
* TRSH = round(ADCNORM/(2^(4+CUR)))
* HYS = if(∂ADCNORM>0 then max(round(∂ADCNORM/(2^(7+CUR))),1) else 0
*/
/*
* while calculating the CUR[2:0], instead of log2
* do a BSR (bit scan reverse) since we are dealing with integer values
*/
bsr_num = resi_val;
bsr_num /= (1 << 5);
while (bsr_num >>= 1)
count++;
/* cursel = max((count - 7), 0);
* Clamp cursel to 3-bit value
*/
cursel = clamp_t(s8, (count-7), 0, 7);
/* calculate the TRSH[8:0] to be programmed */
trsh = ((resi_val) / (1 << (4 + cursel)));
/* calculate HYS[3:0] */
/* add the temp hysteresis depending upon the zones */
if (temp <= 0 || temp >= 60)
temp += 1;
else
temp += 2;
/* retrieve the resistance corresponding to temp with hysteresis */
retval = CONVERT_TEMP_TO_ADC(temp, (int *)&adc_thold);
if (unlikely(retval)) {
dev_err(chc.dev,
"Error converting temperature for zone\n");
return retval;
}
/* multiply to convert into Ohm*/
adc_thold *= OHM_MULTIPLIER;
hyst = (resi_val - adc_thold);
if (hyst > 0)
hys = max((hyst / (1 << (7 + cursel))), 1);
else
hys = 0;
/* Clamp hys to 4-bit value */
hys = clamp_t(u8, hys, 0, 15);
tempzone_val = (hys << 12) | (cursel << 9) | trsh;
return tempzone_val;
}
static inline int update_zone_temp(int zone, u16 adc_val)
{
int ret;
u16 addr_tzone;
int vendor_id = chc.pmic_id & PMIC_VENDOR_ID_MASK;
if (vendor_id == BASINCOVE_VENDORID)
addr_tzone = THRMZN4H_ADDR_BC - (2 * zone);
else if (vendor_id == SHADYCOVE_VENDORID) {
/* to take care of address-discontinuity of zone-registers */
int offset_zone = zone;
if (zone >= 3)
offset_zone += 1;
addr_tzone = THRMZN4H_ADDR_SC - (2 * offset_zone);
} else {
dev_err(chc.dev, "%s: invalid vendor id %X\n", __func__, vendor_id);
return -EINVAL;
}
ret = intel_scu_ipc_iowrite8(addr_tzone, (u8)(adc_val >> 8));
if (unlikely(ret))
return ret;
dev_dbg(chc.dev, "%s:%X:%X=%X\n", __func__, addr_tzone,
(addr_tzone+1), adc_val);
return intel_scu_ipc_iowrite8(addr_tzone+1, (u8)(adc_val & 0xFF));
}
int pmic_set_cc(int new_cc)
{
struct ps_pse_mod_prof *bcprof = chc.actual_bcprof;
struct ps_pse_mod_prof *r_bcprof = chc.runtime_bcprof;
int temp_mon_ranges;
int new_cc1;
int ret;
int i, cur_zone;
u8 reg_val = 0;
pmic_get_bat_zone(&cur_zone);
dev_info(chc.dev, "%s: Battery Zone:%d\n", __func__, cur_zone);
/* No need to write PMIC if CC = 0 */
if (!new_cc)
return 0;
temp_mon_ranges = min_t(u16, bcprof->temp_mon_ranges,
BATT_TEMP_NR_RNG);
for (i = 0; i < temp_mon_ranges; ++i) {
new_cc1 = min_t(int, new_cc,
bcprof->temp_mon_range[i].full_chrg_cur);
if (new_cc1 != r_bcprof->temp_mon_range[i].full_chrg_cur) {
if (chc.pdata->cc_to_reg) {
chc.pdata->cc_to_reg(new_cc1, &reg_val);
ret = update_zone_cc(i, reg_val);
if (unlikely(ret))
return ret;
}
r_bcprof->temp_mon_range[i].full_chrg_cur = new_cc1;
}
}
/* send the new CC and CV */
intel_scu_ipc_update_register(CHGRCTRL1_ADDR,
CHGRCTRL1_FTEMP_EVENT_MASK, CHGRCTRL1_FTEMP_EVENT_MASK);
return 0;
}
int pmic_set_cv(int new_cv)
{
struct ps_pse_mod_prof *bcprof = chc.actual_bcprof;
struct ps_pse_mod_prof *r_bcprof = chc.runtime_bcprof;
int temp_mon_ranges;
int new_cv1;
int ret;
int i, cur_zone;
u8 reg_val = 0;
pmic_get_bat_zone(&cur_zone);
dev_info(chc.dev, "%s: Battery Zone:%d\n", __func__, cur_zone);
/* No need to write PMIC if CV = 0 */
if (!new_cv)
return 0;
temp_mon_ranges = min_t(u16, bcprof->temp_mon_ranges,
BATT_TEMP_NR_RNG);
for (i = 0; i < temp_mon_ranges; ++i) {
new_cv1 = min_t(int, new_cv,
bcprof->temp_mon_range[i].full_chrg_vol);
if (new_cv1 != r_bcprof->temp_mon_range[i].full_chrg_vol) {
if (chc.pdata->cv_to_reg) {
chc.pdata->cv_to_reg(new_cv1, &reg_val);
ret = update_zone_cv(i, reg_val);
if (unlikely(ret))
return ret;
}
r_bcprof->temp_mon_range[i].full_chrg_vol = new_cv1;
}
}
/* send the new CC and CV */
intel_scu_ipc_update_register(CHGRCTRL1_ADDR,
CHGRCTRL1_FTEMP_EVENT_MASK, CHGRCTRL1_FTEMP_EVENT_MASK);
return 0;
}
int pmic_set_ilimma(int ilim_ma)
{
u8 reg_val;
int ret;
if (ilim_ma >= 1500) {
if (chc.pdata->inlmt_to_reg)
chc.pdata->inlmt_to_reg(ilim_ma, &reg_val);
ret = pmic_write_tt(TT_USBINPUTICC1500VAL_ADDR, reg_val);
if (ret) {
dev_err(chc.dev, "Error in updating TT-reg(%x): %d\n",
TT_USBINPUTICC1500VAL_ADDR, ret);
return ret;
}
}
lookup_regval(pmic_inlmt, ARRAY_SIZE(pmic_inlmt),
ilim_ma, &reg_val);
dev_dbg(chc.dev, "Setting inlmt %d in register %x=%x\n", ilim_ma,
CHGRCTRL1_ADDR, reg_val);
ret = intel_scu_ipc_iowrite8(CHGRCTRL1_ADDR, reg_val);
/* If access is blocked return success to avoid additional
* error handling at client side
*/
if (ret == -EACCES) {
dev_warn(chc.dev, "IPC blocked due to unsigned kernel/invalid battery\n");
ret = 0;
}
return ret;
}
/**
* pmic_read_adc_val - read ADC value of specified sensors
* @channel: channel of the sensor to be sampled
* @sensor_val: pointer to the charger property to hold sampled value
* @chc : battery info pointer
*
* Returns 0 if success
*/
static int pmic_read_adc_val(int channel, int *sensor_val,
struct pmic_chrgr_drv_context *chc)
{
int val;
int ret;
struct iio_channel *indio_chan;
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0))
indio_chan = iio_st_channel_get("BATTEMP", "BATTEMP0");
#else
indio_chan = iio_channel_get(NULL, "BATTEMP0");
#endif
if (IS_ERR_OR_NULL(indio_chan)) {
ret = PTR_ERR(indio_chan);
goto exit;
}
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0))
ret = iio_st_read_channel_raw(indio_chan, &val);
#else
ret = iio_read_channel_raw(indio_chan, &val);
#endif
if (ret) {
dev_err(chc->dev, "IIO channel read error\n");
goto err_exit;
}
switch (channel) {
case GPADC_BATTEMP0:
ret = CONVERT_ADC_TO_TEMP(val, sensor_val);
break;
default:
dev_err(chc->dev, "invalid sensor%d", channel);
ret = -EINVAL;
}
dev_dbg(chc->dev, "pmic_ccsm pmic_ccsm.0: %s adc val=%x, %d temp=%d\n",
__func__, val, val, *sensor_val);
err_exit:
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0))
iio_st_channel_release(indio_chan);
#else
iio_channel_release(indio_chan);
#endif
exit:
return ret;
}
int pmic_get_battery_pack_temp(int *temp)
{
if (chc.invalid_batt)
return -ENODEV;
return pmic_read_adc_val(GPADC_BATTEMP0, temp, &chc);
}
static bool is_hvdcp_charging_enabled(int mask)
{
int ret;
u8 val;
if (mask) {
/* Enable VDPSRC so that it stays on when switch is closed */
ret = intel_scu_ipc_update_register(DBG_USBBC2_ADDR,
DBG_USBBC2_EN_VDPSRC_MASK,
DBG_USBBC2_EN_VDPSRC_MASK);
if (ret) {
dev_err(chc.dev,
"Error updating DBG_USBBC2-register 0x%3x\n",
DBG_USBBC2_ADDR);
return false;
}
/* Enable SW control of the USB charger type detection */
ret = intel_scu_ipc_update_register(DBG_USBBC1_ADDR,
DBG_USBBC1_SWCTRL_EN_MASK | DBG_USBBC1_EN_CMP_DM_MASK |
DBG_USBBC1_EN_CMP_DP_MASK | DBG_USBBC1_EN_CHG_DET_MASK,
DBG_USBBC1_SWCTRL_EN_MASK | DBG_USBBC1_EN_CMP_DM_MASK |
DBG_USBBC1_EN_CMP_DP_MASK |
DBG_USBBC1_EN_CHG_DET_MASK);
if (ret) {
dev_err(chc.dev,
"Error updating DBG_USBBC1-register 0x%3x\n",
DBG_USBBC1_ADDR);
return false;
}
/* Wait >1.5 sec */
msleep(HVDCPDET_SLEEP_TIME);
/* Check if DM<VDATDET. If a HVDCP is attached, it will remove
* the DP/DM short & enable Rdm_dwn, pulling down DM to 0V.
* DCP will keep DM at 0.6V
*/
ret = pmic_read_reg(DBG_USBBCSTAT_ADDR, &val);
if (ret) {
dev_err(chc.dev,
"Error reading DBG_USBBCSTAT-register 0x%3x\n",
DBG_USBBCSTAT_ADDR);
return false;
}
/* If “0”, HVDCP detected */
dev_info(chc.dev,
"DBG_USBBCSTAT-register 0x%3x: %x\n",
DBG_USBBCSTAT_ADDR, val);
if (!(val & DBG_USBBCSTAT_CMP_DM_MASK)) {
/* Enable HVDCP 12V charging by enabling VDMSRC */
ret = intel_scu_ipc_update_register(DBG_USBBC2_ADDR,
DBG_USBBC2_EN_VDMSRC_MASK,
DBG_USBBC2_EN_VDMSRC_MASK);
if (ret) {
dev_err(chc.dev,
"Error updating DBG_USBBC2-register 0x%3x\n",
DBG_USBBC2_ADDR);
} else {
/* HVDCP detection completed successfully */
dev_info(chc.dev,
"HVDCP 12V charging enabled\n");
return true;
}
}
}
/* Cleanup on either HVDCP-not-detected or HVDCP-disconnected */
dev_info(chc.dev, "HVDCP 12V charging disabled");
/* Open PMIC switch */
intel_scu_ipc_iowrite8(USBPHYCTRL_ADDR, 0x00);
/* Disable VDMSRC & VDPSRC */
intel_scu_ipc_iowrite8(DBG_USBBC2_ADDR, 0x00);
/* Disable SW control of the USB charger type detection */
intel_scu_ipc_iowrite8(DBG_USBBC1_ADDR, 0x00);
return false;
}
static int scove_get_usbid(void)
{
int ret;
struct iio_channel *indio_chan;
int rid, id = RID_UNKNOWN;
u8 val;
ret = pmic_read_reg(SCHGRIRQ1_ADDR, &val);
if (ret) {
dev_err(chc.dev,
"Error reading SCHGRIRQ1-register 0x%2x\n",
SCHGRIRQ1_ADDR);
return ret;
}
/* SCHGRIRQ1_REG SUSBIDDET bit definition:
* 00 = RID_A/B/C ; 01 = RID_GND ; 10 = RID_FLOAT */
if ((val & SCHRGRIRQ1_SUSBIDGNDDET_MASK) == SHRT_FLT_DET)
return RID_FLOAT;
else if ((val & SCHRGRIRQ1_SUSBIDGNDDET_MASK) == SHRT_GND_DET)
return RID_GND;
indio_chan = iio_channel_get(NULL, "USBID");
if (IS_ERR_OR_NULL(indio_chan)) {
dev_err(chc.dev, "Failed to get IIO channel USBID\n");
ret = PTR_ERR(indio_chan);
goto exit;
}
ret = iio_read_channel_raw(indio_chan, &rid);
if (ret) {
dev_err(chc.dev, "IIO channel read error for USBID\n");
goto err_exit;
}
if ((rid > 11150) && (rid < 13640))
id = RID_A;
else if ((rid > 6120) && (rid < 7480))
id = RID_B;
else if ((rid > 3285) && (rid < 4015))
id = RID_C;
err_exit:
iio_channel_release(indio_chan);
exit:
return id;
}
static int get_charger_type(void)
{
int ret, i = 0;
u8 val;
int chgr_type, rid;
do {
ret = pmic_read_reg(USBSRCDETSTATUS_ADDR, &val);
if (ret != 0) {
dev_err(chc.dev,
"Error reading USBSRCDETSTAT-register 0x%2x\n",
USBSRCDETSTATUS_ADDR);
return 0;
}
i++;
dev_info(chc.dev, "Read USBSRCDETSTATUS val: %x\n", val);
if (val & USBSRCDET_SUSBHWDET_DETSUCC)
break;
else
msleep(USBSRCDET_SLEEP_TIME);
} while (i < USBSRCDET_RETRY_CNT);
if (!(val & USBSRCDET_SUSBHWDET_DETSUCC)) {
dev_err(chc.dev, "Charger detection unsuccessful after %dms\n",
i * USBSRCDET_SLEEP_TIME);
return 0;
}
chgr_type = (val & USBSRCDET_USBSRCRSLT_MASK) >> 2;
dev_info(chc.dev, "Charger type after detection complete: %d\n",
(val & USBSRCDET_USBSRCRSLT_MASK) >> 2);
switch (chgr_type) {
case PMIC_CHARGER_TYPE_SDP:
case PMIC_CHARGER_TYPE_FLOAT_DP_DN:
return POWER_SUPPLY_CHARGER_TYPE_USB_SDP;
case PMIC_CHARGER_TYPE_DCP:
return POWER_SUPPLY_CHARGER_TYPE_USB_DCP;
case PMIC_CHARGER_TYPE_CDP:
return POWER_SUPPLY_CHARGER_TYPE_USB_CDP;
case PMIC_CHARGER_TYPE_ACA:
rid = scove_get_usbid();
if (rid == RID_A)
return POWER_SUPPLY_CHARGER_TYPE_ACA_DOCK;
else if (rid != RID_UNKNOWN)
return POWER_SUPPLY_CHARGER_TYPE_USB_ACA;
case PMIC_CHARGER_TYPE_SE1:
return POWER_SUPPLY_CHARGER_TYPE_SE1;
case PMIC_CHARGER_TYPE_MHL:
return POWER_SUPPLY_CHARGER_TYPE_MHL;
default:
return POWER_SUPPLY_CHARGER_TYPE_NONE;
}
}
static void handle_internal_usbphy_notifications(int mask)
{
struct power_supply_cable_props cap = {0};
bool hvdcp_chgr = false;
if (mask) {
cap.chrg_evt = POWER_SUPPLY_CHARGER_EVENT_CONNECT;
cap.chrg_type = get_charger_type();
chc.charger_type = cap.chrg_type;
} else {
cap.chrg_evt = POWER_SUPPLY_CHARGER_EVENT_DISCONNECT;
cap.chrg_type = chc.charger_type;
}
if (cap.chrg_type == POWER_SUPPLY_CHARGER_TYPE_USB_SDP)
cap.ma = 0;
else if ((cap.chrg_type == POWER_SUPPLY_CHARGER_TYPE_USB_DCP)
|| (cap.chrg_type == POWER_SUPPLY_CHARGER_TYPE_USB_CDP)
|| (cap.chrg_type == POWER_SUPPLY_CHARGER_TYPE_SE1)
|| (cap.chrg_type == POWER_SUPPLY_CHARGER_TYPE_USB_ACA)
|| (cap.chrg_type == POWER_SUPPLY_CHARGER_TYPE_ACA_DOCK))
cap.ma = 1500;
dev_info(chc.dev, "Notifying OTG ev:%d, evt:%d, chrg_type:%d, mA:%d\n",
USB_EVENT_CHARGER, cap.chrg_evt, cap.chrg_type,
cap.ma);
atomic_notifier_call_chain(&chc.otg->notifier,
USB_EVENT_CHARGER, &cap);
if (cap.chrg_type == POWER_SUPPLY_CHARGER_TYPE_USB_DCP) {
hvdcp_chgr = is_hvdcp_charging_enabled(mask);
if (hvdcp_chgr && mask) {
cap.chrg_evt = POWER_SUPPLY_CHARGER_EVENT_UPDATE;
cap.ma = 2000;
dev_info(chc.dev, "Notifying OTG ev:%d, evt:%d,"
" chrg_type:%d, mA:%d\n",
USB_EVENT_CHARGER, cap.chrg_evt,
cap.chrg_type, cap.ma);
atomic_notifier_call_chain(&chc.otg->notifier,
USB_EVENT_CHARGER, &cap);
}
}
}
/* ShadyCove-WA for VBUS removal detect issue */
int pmic_handle_low_supply(void)
{
int ret;
u8 val;
int vendor_id = chc.pmic_id & PMIC_VENDOR_ID_MASK;
dev_info(chc.dev, "Low-supply event received from external-charger\n");
if (vendor_id == BASINCOVE_VENDORID || !chc.vbus_connect_status) {
dev_err(chc.dev, "Ignore Low-supply event received\n");
return 0;
}
msleep(200);
ret = pmic_read_reg(SCHGRIRQ1_ADDR, &val);
if (ret) {
dev_err(chc.dev,
"Error reading SCHGRIRQ1-register 0x%2x\n",
SCHGRIRQ1_ADDR);
return ret;
}
if (!(val & SCHRGRIRQ1_SVBUSDET_MASK)) {
int mask = 0;
dev_info(chc.dev, "USB VBUS Removed. Notifying OTG driver\n");
mutex_lock(&chc.evt_queue_lock);
chc.vbus_connect_status = false;
mutex_unlock(&chc.evt_queue_lock);
if (chc.is_internal_usb_phy && !chc.otg_mode_enabled)
handle_internal_usbphy_notifications(mask);
else {
atomic_notifier_call_chain(&chc.otg->notifier,
USB_EVENT_VBUS, &mask);
mutex_lock(&chc.evt_queue_lock);
chc.otg_mode_enabled = false;
mutex_unlock(&chc.evt_queue_lock);
}
}
return ret;
}
static void handle_level0_interrupt(u8 int_reg, u8 stat_reg,
struct interrupt_info int_info[],
int int_info_size)
{
int i;
bool int_stat;
char *log_msg;
for (i = 0; i < int_info_size; ++i) {
/*continue if interrupt register bit is not set */
if (!(int_reg & int_info[i].int_reg_mask))
continue;
/*log message if interrupt bit is set */
if (int_info[i].log_msg_int_reg_true)
dev_err(chc.dev, "%s",
int_info[i].log_msg_int_reg_true);
/* interrupt bit is set.call int handler. */
if (int_info[i].int_handle)
int_info[i].int_handle();
/* continue if stat_reg_mask is zero which
* means ignore status register
*/
if (!(int_info[i].stat_reg_mask))
continue;
dev_dbg(chc.dev,
"stat_reg=%X int_info[i].stat_reg_mask=%X",
stat_reg, int_info[i].stat_reg_mask);
/* check if the interrupt status is true */
int_stat = (stat_reg & int_info[i].stat_reg_mask);
/* log message */
log_msg = int_stat ? int_info[i].log_msg_stat_true :
int_info[i].log_msg_stat_false;
if (log_msg)
dev_err(chc.dev, "%s", log_msg);
/* call status handler function */
if (int_info[i].stat_handle)
int_info[i].stat_handle(int_stat);
}
return ;
}
static void handle_level1_interrupt(u8 int_reg, u8 stat_reg)
{
int mask;
u8 val;
int ret;
int vendor_id = chc.pmic_id & PMIC_VENDOR_ID_MASK;
if (!int_reg)
return;
if (vendor_id == SHADYCOVE_VENDORID) {
if (int_reg & CHRGRIRQ1_SUSBIDFLTDET_MASK)
dev_info(chc.dev,
"USBID-FLT interrupt received\n");
mask = ((stat_reg & SCHRGRIRQ1_SUSBIDGNDDET_MASK)
== SHRT_GND_DET) ? 1 : 0;
if (int_reg & CHRGRIRQ1_SUSBIDGNDDET_MASK) {
if (mask)
dev_info(chc.dev,
"USBID-GND Detected. Notifying OTG\n");
else
dev_info(chc.dev,
"USBID-GND Removed. Notifying OTG\n");
atomic_notifier_call_chain(&chc.otg->notifier,
USB_EVENT_ID, &mask);
}
}
mask = !!(int_reg & stat_reg);
if ((vendor_id == BASINCOVE_VENDORID) &&
(int_reg & CHRGRIRQ1_SUSBIDDET_MASK)) {
if (mask)
dev_info(chc.dev,
"USB ID Detected. Notifying OTG driver\n");
else
dev_info(chc.dev,
"USB ID Removed. Notifying OTG driver\n");
atomic_notifier_call_chain(&chc.otg->notifier,
USB_EVENT_ID, &mask);
}
if (int_reg & CHRGRIRQ1_SVBUSDET_MASK) {
if (mask) {
/* This is to handle the scenario where a connect event
* is received with already connect-status for USB.
* Previous disconnect event could have been missed due
* to synchronization issues of low-supply fault or
* late update to SCHRGRIRQ1_SVBUSDET
*/
if (chc.vbus_connect_status) {
int rmv_mask = 0;
dev_info(chc.dev, "Previous USB VBUS removal not received\n");
dev_info(chc.dev, "USB VBUS removal forced. Notifying OTG driver\n");
if (chc.is_internal_usb_phy
&& !chc.otg_mode_enabled)
handle_internal_usbphy_notifications(rmv_mask);
else {
atomic_notifier_call_chain(&chc.otg->notifier,
USB_EVENT_VBUS, &rmv_mask);
chc.otg_mode_enabled = false;
}
msleep(50);
}
dev_info(chc.dev,
"USB VBUS Detected. Notifying OTG driver\n");
chc.vbus_connect_status = true;
ret = pmic_read_reg(CHGRCTRL1_ADDR, &val);
if (ret != 0) {
dev_err(chc.dev,
"Error reading CHGRCTRL1-register 0x%2x\n",
CHGRCTRL1_ADDR);
return;
}
if (val & CHGRCTRL1_OTGMODE_MASK)
chc.otg_mode_enabled = true;
} else {
dev_info(chc.dev, "USB VBUS Removed. Notifying OTG driver\n");
chc.vbus_connect_status = false;
}
/* Avoid charger-detection flow in case of host-mode */
if (chc.is_internal_usb_phy && !chc.otg_mode_enabled)
handle_internal_usbphy_notifications(mask);
else {
atomic_notifier_call_chain(&chc.otg->notifier,
USB_EVENT_VBUS, &mask);
if (!mask)
chc.otg_mode_enabled = false;
}
}
return;
}
static void pmic_event_worker(struct work_struct *work)
{
struct pmic_event *evt, *tmp;
dev_dbg(chc.dev, "%s\n", __func__);
mutex_lock(&chc.evt_queue_lock);
list_for_each_entry_safe(evt, tmp, &chc.evt_queue, node) {
list_del(&evt->node);
dev_info(chc.dev, "CHGRIRQ0=%X SCHGRIRQ0=%X CHGRIRQ1=%x SCHGRIRQ1=%X\n",
evt->chgrirq0_int, evt->chgrirq0_stat,
evt->chgrirq1_int, evt->chgrirq1_stat);
if (evt->chgrirq0_int)
handle_level0_interrupt(evt->chgrirq0_int,
evt->chgrirq0_stat, chgrirq0_info,
ARRAY_SIZE(chgrirq0_info));
if (evt->chgrirq1_stat)
handle_level1_interrupt(evt->chgrirq1_int,
evt->chgrirq1_stat);
kfree(evt);
}
mutex_unlock(&chc.evt_queue_lock);
}
static irqreturn_t pmic_isr(int irq, void *data)
{
u16 pmic_intr;
u8 chgrirq0_int;
u8 chgrirq1_int;
u8 mask = 0;
int vendor_id = chc.pmic_id & PMIC_VENDOR_ID_MASK;
if (vendor_id == BASINCOVE_VENDORID)
mask = ((CHRGRIRQ1_SVBUSDET_MASK) |
(CHRGRIRQ1_SUSBIDDET_MASK));
else if (vendor_id == SHADYCOVE_VENDORID)
mask = ((CHRGRIRQ1_SVBUSDET_MASK) |
(CHRGRIRQ1_SUSBIDFLTDET_MASK) |
(CHRGRIRQ1_SUSBIDGNDDET_MASK));
pmic_intr = ioread16(chc.pmic_intr_iomap);
chgrirq0_int = (u8)pmic_intr;
chgrirq1_int = (u8)(pmic_intr >> 8);
if (!chgrirq1_int && !(chgrirq0_int & PMIC_CHRGR_INT0_MASK))
return IRQ_NONE;
if ((chgrirq1_int & mask) && (!wake_lock_active(&chc.wakelock))) {
/*
Setting the Usb wake lock hold timeout to a safe value of 5s.
*/
wake_lock_timeout(&chc.wakelock, USB_WAKE_LOCK_TIMEOUT);
}
dev_dbg(chc.dev, "%s", __func__);
return IRQ_WAKE_THREAD;
}
static irqreturn_t pmic_thread_handler(int id, void *data)
{
u16 pmic_intr;
struct pmic_event *evt;
int ret;
evt = kzalloc(sizeof(*evt), GFP_ATOMIC);
if (evt == NULL) {
dev_dbg(chc.dev, "Error allocating evt structure in fn:%s\n",
__func__);
return IRQ_NONE;
}
pmic_intr = ioread16(chc.pmic_intr_iomap);
evt->chgrirq0_int = (u8)pmic_intr;
evt->chgrirq1_int = (u8)(pmic_intr >> 8);
dev_dbg(chc.dev, "irq0=%x irq1=%x\n",
evt->chgrirq0_int, evt->chgrirq1_int);
/*
In case this is an external charger interrupt, we are
clearing the level 1 irq register and let external charger
driver handle the interrupt.
*/
if (!(evt->chgrirq1_int) &&
!(evt->chgrirq0_int & PMIC_CHRGR_CCSM_INT0_MASK)) {
intel_scu_ipc_update_register(IRQLVL1_MASK_ADDR, 0x00,
IRQLVL1_CHRGR_MASK);
if ((chc.invalid_batt) &&
(evt->chgrirq0_int & PMIC_CHRGR_EXT_CHRGR_INT_MASK)) {
dev_dbg(chc.dev, "Handling external charger interrupt!!\n");
kfree(evt);
return IRQ_HANDLED;
}
kfree(evt);
dev_dbg(chc.dev, "Unhandled interrupt!!\n");
return IRQ_NONE;
}
if (evt->chgrirq0_int & PMIC_CHRGR_CCSM_INT0_MASK) {
ret = intel_scu_ipc_ioread8(SCHGRIRQ0_ADDR,
&evt->chgrirq0_stat);
if (ret) {
dev_err(chc.dev,
"%s: Error(%d) in intel_scu_ipc_ioread8. Faile to read SCHGRIRQ0_ADDR\n",
__func__, ret);
kfree(evt);
goto end;
}
}
if (evt->chgrirq1_int) {
ret = intel_scu_ipc_ioread8(SCHGRIRQ1_ADDR,
&evt->chgrirq1_stat);
if (ret) {
dev_err(chc.dev,
"%s: Error(%d) in intel_scu_ipc_ioread8. Faile to read SCHGRIRQ1_ADDR\n",
__func__, ret);
kfree(evt);
goto end;
}
}
INIT_LIST_HEAD(&evt->node);
mutex_lock(&chc.evt_queue_lock);
list_add_tail(&evt->node, &chc.evt_queue);
mutex_unlock(&chc.evt_queue_lock);
queue_work(system_nrt_wq, &chc.evt_work);
end:
/*clear first level IRQ */
dev_dbg(chc.dev, "Clearing IRQLVL1_MASK_ADDR\n");
intel_scu_ipc_update_register(IRQLVL1_MASK_ADDR, 0x00,
IRQLVL1_CHRGR_MASK);
return IRQ_HANDLED;
}
static int pmic_init(void)
{
int ret = 0, i, temp_mon_ranges;
u16 adc_val;
u8 reg_val;
int vendor_id = chc.pmic_id & PMIC_VENDOR_ID_MASK;
struct ps_pse_mod_prof *bcprof = chc.actual_bcprof;
temp_mon_ranges = min_t(u16, bcprof->temp_mon_ranges,
BATT_TEMP_NR_RNG);
for (i = 0; i < temp_mon_ranges; ++i) {
ret =
CONVERT_TEMP_TO_ADC(bcprof->temp_mon_range[i].temp_up_lim,
(int *)&adc_val);
if (unlikely(ret)) {
dev_err(chc.dev,
"Error converting temperature for zone %d!!\n",
i);
return ret;
}
if (vendor_id == SHADYCOVE_VENDORID) {
/* Values obtained from lookup-table are resistance values.
* Convert these to raw adc-codes
*/
adc_val = get_scove_tempzone_val(adc_val,
bcprof->temp_mon_range[i].temp_up_lim);
dev_info(chc.dev,
"adc-val:%x configured for temp:%d\n",
adc_val,
bcprof->temp_mon_range[i].temp_up_lim);
}
ret = update_zone_temp(i, adc_val);
if (unlikely(ret)) {
dev_err(chc.dev,
"Error updating zone temp for zone %d\n",
i);
return ret;
}
if (chc.pdata->cc_to_reg)
chc.pdata->cc_to_reg(bcprof->temp_mon_range[i].
full_chrg_cur, &reg_val);
ret = update_zone_cc(i, reg_val);
if (unlikely(ret)) {
dev_err(chc.dev,
"Error updating zone cc for zone %d\n",
i);
return ret;
}
if (chc.pdata->cv_to_reg)
chc.pdata->cv_to_reg(bcprof->temp_mon_range[i].
full_chrg_vol, &reg_val);
ret = update_zone_cv(i, reg_val);
if (unlikely(ret)) {
dev_err(chc.dev,
"Error updating zone cv for zone %d\n",
i);
return ret;
}
/* Write lowest temp limit */
if (i == (bcprof->temp_mon_ranges - 1)) {
ret = CONVERT_TEMP_TO_ADC(bcprof->temp_low_lim,
(int *)&adc_val);
if (unlikely(ret)) {
dev_err(chc.dev,
"Error converting low lim temp!!\n");
return ret;
}
if (vendor_id == SHADYCOVE_VENDORID) {
adc_val = get_scove_tempzone_val(adc_val,
bcprof->temp_low_lim);
dev_info(chc.dev,
"adc-val:%x configured for temp:%d\n",
adc_val, bcprof->temp_low_lim);
}
ret = update_zone_temp(i+1, adc_val);
if (unlikely(ret)) {
dev_err(chc.dev,
"Error updating last temp for zone %d\n",
i+1);
return ret;
}
}
}
ret = pmic_update_tt(TT_CUSTOMFIELDEN_ADDR,
TT_HOT_COLD_LC_MASK,
TT_HOT_COLD_LC_DIS);
if (unlikely(ret)) {
dev_err(chc.dev, "Error updating TT_CUSTOMFIELD_EN reg\n");
return ret;
}
if (chc.pdata->inlmt_to_reg)
chc.pdata->inlmt_to_reg(USBINPUTICC100VAL, &reg_val);
ret = pmic_write_tt(TT_USBINPUTICC100VAL_ADDR, reg_val);
return ret;
}
static inline void print_ps_pse_mod_prof(struct ps_pse_mod_prof *bcprof)
{
int i, temp_mon_ranges;
dev_info(chc.dev, "ChrgProf: batt_id:%s\n", bcprof->batt_id);
dev_info(chc.dev, "ChrgProf: battery_type:%u\n", bcprof->battery_type);
dev_info(chc.dev, "ChrgProf: capacity:%u\n", bcprof->capacity);
dev_info(chc.dev, "ChrgProf: voltage_max:%u\n", bcprof->voltage_max);
dev_info(chc.dev, "ChrgProf: chrg_term_ma:%u\n", bcprof->chrg_term_ma);
dev_info(chc.dev, "ChrgProf: low_batt_mV:%u\n", bcprof->low_batt_mV);
dev_info(chc.dev, "ChrgProf: disch_tmp_ul:%d\n", bcprof->disch_tmp_ul);
dev_info(chc.dev, "ChrgProf: disch_tmp_ll:%d\n", bcprof->disch_tmp_ll);
dev_info(chc.dev, "ChrgProf: temp_mon_ranges:%u\n",
bcprof->temp_mon_ranges);
temp_mon_ranges = min_t(u16, bcprof->temp_mon_ranges,
BATT_TEMP_NR_RNG);
for (i = 0; i < temp_mon_ranges; ++i) {
dev_info(chc.dev, "ChrgProf: temp_up_lim[%d]:%d\n",
i, bcprof->temp_mon_range[i].temp_up_lim);
dev_info(chc.dev, "ChrgProf: full_chrg_vol[%d]:%d\n",
i, bcprof->temp_mon_range[i].full_chrg_vol);
dev_info(chc.dev, "ChrgProf: full_chrg_cur[%d]:%d\n",
i, bcprof->temp_mon_range[i].full_chrg_cur);
dev_info(chc.dev, "ChrgProf: maint_chrgr_vol_ll[%d]:%d\n",
i, bcprof->temp_mon_range[i].maint_chrg_vol_ll);
dev_info(chc.dev, "ChrgProf: maint_chrgr_vol_ul[%d]:%d\n",
i, bcprof->temp_mon_range[i].maint_chrg_vol_ul);
dev_info(chc.dev, "ChrgProf: maint_chrg_cur[%d]:%d\n",
i, bcprof->temp_mon_range[i].maint_chrg_cur);
}
dev_info(chc.dev, "ChrgProf: temp_low_lim:%d\n", bcprof->temp_low_lim);
}
static int find_tempzone_index(short int *interval,
int *num_zones,
short int *temp_up_lim)
{
struct ps_pse_mod_prof *bprof = chc.sfi_bcprof->batt_prof;
int up_lim_index = 0, low_lim_index = -1;
int diff = 0;
int i;
*num_zones = MIN_BATT_PROF - bprof->temp_mon_ranges + 1;
if ((*num_zones) <= 0)
return 0;
for (i = 0 ; i < bprof->temp_mon_ranges ; i++) {
if (bprof->temp_mon_range[i].temp_up_lim == BATT_TEMP_WARM)
up_lim_index = i;
}
low_lim_index = up_lim_index + 1;
if (low_lim_index == bprof->temp_mon_ranges)
diff = bprof->temp_low_lim -
bprof->temp_mon_range[up_lim_index].temp_up_lim;
else
diff = bprof->temp_mon_range[low_lim_index].temp_up_lim -
bprof->temp_mon_range[up_lim_index].temp_up_lim;
*interval = diff / (*num_zones);
*temp_up_lim = bprof->temp_mon_range[up_lim_index].temp_up_lim;
return up_lim_index;
}
static void set_pmic_batt_prof(struct ps_pse_mod_prof *new_prof,
struct ps_pse_mod_prof *bprof)
{
int num_zones;
int split_index;
int i, j = 0;
short int temp_up_lim;
short int interval;
if ((new_prof == NULL) || (bprof == NULL))
return;
if (!NEED_ZONE_SPLIT(bprof)) {
dev_info(chc.dev, "No need to split the zones!!\n");
memcpy(new_prof, bprof, sizeof(struct ps_pse_mod_prof));
return;
}
strcpy(&(new_prof->batt_id[0]), &(bprof->batt_id[0]));
new_prof->battery_type = bprof->battery_type;
new_prof->capacity = bprof->capacity;
new_prof->voltage_max = bprof->voltage_max;
new_prof->chrg_term_ma = bprof->chrg_term_ma;
new_prof->low_batt_mV = bprof->low_batt_mV;
new_prof->disch_tmp_ul = bprof->disch_tmp_ul;
new_prof->disch_tmp_ll = bprof->disch_tmp_ll;
split_index = find_tempzone_index(&interval, &num_zones, &temp_up_lim);
for (i = 0 ; i < bprof->temp_mon_ranges; i++) {
if ((i == split_index) && (num_zones > 0)) {
for (j = 0; j < num_zones; j++,
temp_up_lim += interval) {
memcpy(&new_prof->temp_mon_range[i+j],
&bprof->temp_mon_range[i],
sizeof(bprof->temp_mon_range[i]));
new_prof->temp_mon_range[i+j].temp_up_lim =
temp_up_lim;
}
j--;
} else {
memcpy(&new_prof->temp_mon_range[i+j],
&bprof->temp_mon_range[i],
sizeof(bprof->temp_mon_range[i]));
}
}
new_prof->temp_mon_ranges = i+j;
new_prof->temp_low_lim = bprof->temp_low_lim;
return;
}
static int pmic_check_initial_events(void)
{
struct pmic_event *evt;
int ret;
u8 mask = (CHRGRIRQ1_SVBUSDET_MASK);
int vendor_id = chc.pmic_id & PMIC_VENDOR_ID_MASK;
evt = kzalloc(sizeof(struct pmic_event), GFP_KERNEL);
if (evt == NULL) {
dev_dbg(chc.dev, "Error allocating evt structure in fn:%s\n",
__func__);
return -1;
}
ret = intel_scu_ipc_ioread8(SCHGRIRQ0_ADDR, &evt->chgrirq0_stat);
evt->chgrirq0_int = evt->chgrirq0_stat;
ret = intel_scu_ipc_ioread8(SCHGRIRQ1_ADDR, &evt->chgrirq1_stat);
evt->chgrirq1_int = evt->chgrirq1_stat;
/* For ShadyCove, CHGRIRQ1_REG & SCHGRIRQ1_REG cannot be directly
* mapped. If status has (01 = Short to ground detected), it means
* USBIDGNDDET should be handled. If status has (10 = Floating pin
* detected), it means USBIDFLTDET should be handled.
*/
if (vendor_id == SHADYCOVE_VENDORID) {
if ((evt->chgrirq1_stat & SCHRGRIRQ1_SUSBIDGNDDET_MASK)
== SHRT_FLT_DET) {
evt->chgrirq1_int |= CHRGRIRQ1_SUSBIDFLTDET_MASK;
evt->chgrirq1_int &= ~CHRGRIRQ1_SUSBIDGNDDET_MASK;
} else if ((evt->chgrirq1_stat & SCHRGRIRQ1_SUSBIDGNDDET_MASK)
== SHRT_GND_DET)
evt->chgrirq1_int |= CHRGRIRQ1_SUSBIDGNDDET_MASK;
}
if (evt->chgrirq1_stat || evt->chgrirq0_int) {
INIT_LIST_HEAD(&evt->node);
mutex_lock(&chc.evt_queue_lock);
list_add_tail(&evt->node, &chc.evt_queue);
mutex_unlock(&chc.evt_queue_lock);
schedule_work(&chc.evt_work);
}
if ((evt->chgrirq1_stat & mask) && !wake_lock_active(&chc.wakelock)) {
/*
Setting the Usb wake lock hold timeout to a safe value of 5s.
*/
wake_lock_timeout(&chc.wakelock, USB_WAKE_LOCK_TIMEOUT);
}
pmic_bat_zone_changed();
return ret;
}
/**
* pmic_charger_probe - PMIC charger probe function
* @pdev: pmic platform device structure
* Context: can sleep
*
* pmic charger driver initializes its internal data
* structure and other infrastructure components for it
* to work as expected.
*/
static int pmic_chrgr_probe(struct platform_device *pdev)
{
int retval = 0;
u8 val;
if (!pdev)
return -ENODEV;
chc.health = POWER_SUPPLY_HEALTH_UNKNOWN;
chc.dev = &pdev->dev;
chc.irq = platform_get_irq(pdev, 0);
chc.pdata = pdev->dev.platform_data;
platform_set_drvdata(pdev, &chc);
if (chc.pdata == NULL) {
dev_err(chc.dev, "Platform data not initialized\n");
return -EFAULT;
}
retval = intel_scu_ipc_ioread8(PMIC_ID_ADDR, &chc.pmic_id);
if (retval) {
dev_err(chc.dev,
"Error reading PMIC ID register\n");
return retval;
}
dev_info(chc.dev, "PMIC-ID: %x\n", chc.pmic_id);
if ((chc.pmic_id & PMIC_VENDOR_ID_MASK) == SHADYCOVE_VENDORID) {
retval = pmic_read_reg(USBPATH_ADDR, &val);
if (retval) {
dev_err(chc.dev,
"Error reading CHGRSTATUS-register 0x%2x\n",
CHGRSTATUS_ADDR);
return retval;
}
if (val & USBPATH_USBSEL_MASK) {
dev_info(chc.dev, "SOC-Internal-USBPHY used\n");
chc.is_internal_usb_phy = true;
} else
dev_info(chc.dev, "External-USBPHY used\n");
}
chc.sfi_bcprof = kzalloc(sizeof(struct ps_batt_chg_prof),
GFP_KERNEL);
if (chc.sfi_bcprof == NULL) {
dev_err(chc.dev,
"Error allocating memeory SFI battery profile\n");
return -ENOMEM;
}
retval = get_batt_prop(chc.sfi_bcprof);
if (retval) {
dev_err(chc.dev,
"Error reading battery profile from battid frmwrk\n");
kfree(chc.sfi_bcprof);
chc.invalid_batt = true;
chc.sfi_bcprof = NULL;
}
retval = intel_scu_ipc_update_register(CHGRCTRL0_ADDR, SWCONTROL_ENABLE,
CHGRCTRL0_SWCONTROL_MASK);
if (retval)
dev_err(chc.dev, "Error enabling sw control. Charging may continue in h/w control mode\n");
if (!chc.invalid_batt) {
chc.actual_bcprof = kzalloc(sizeof(struct ps_pse_mod_prof),
GFP_KERNEL);
if (chc.actual_bcprof == NULL) {
dev_err(chc.dev,
"Error allocating mem for local battery profile\n");
kfree(chc.sfi_bcprof);
return -ENOMEM;
}
chc.runtime_bcprof = kzalloc(sizeof(struct ps_pse_mod_prof),
GFP_KERNEL);
if (chc.runtime_bcprof == NULL) {
dev_err(chc.dev,
"Error allocating mem for runtime batt profile\n");
kfree(chc.sfi_bcprof);
kfree(chc.actual_bcprof);
return -ENOMEM;
}
set_pmic_batt_prof(chc.actual_bcprof,
chc.sfi_bcprof->batt_prof);
print_ps_pse_mod_prof(chc.actual_bcprof);
retval = pmic_init();
if (retval)
dev_err(chc.dev, "Error in Initializing PMIC. Continue in h/w charging mode\n");
memcpy(chc.runtime_bcprof, chc.actual_bcprof,
sizeof(struct ps_pse_mod_prof));
}
chc.pmic_intr_iomap = ioremap_nocache(PMIC_SRAM_INTR_ADDR, 8);
if (!chc.pmic_intr_iomap) {
dev_err(&pdev->dev, "ioremap Failed\n");
retval = -ENOMEM;
goto ioremap_failed;
}
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0))
chc.otg = usb_get_transceiver();
#else
chc.otg = usb_get_phy(USB_PHY_TYPE_USB2);
#endif
if (!chc.otg || IS_ERR(chc.otg)) {
dev_err(&pdev->dev, "Failed to get otg transceiver!!\n");
retval = -ENOMEM;
goto otg_req_failed;
}
INIT_WORK(&chc.evt_work, pmic_event_worker);
INIT_LIST_HEAD(&chc.evt_queue);
mutex_init(&chc.evt_queue_lock);
wake_lock_init(&chc.wakelock, WAKE_LOCK_SUSPEND, "pmic_wakelock");
wake_lock_init(&chc.otg_wa_wakelock, WAKE_LOCK_SUSPEND,
"pmic_otg_wa_wakelock");
/* register interrupt */
retval = request_threaded_irq(chc.irq, pmic_isr,
pmic_thread_handler,
IRQF_SHARED|IRQF_NO_SUSPEND ,
DRIVER_NAME, &chc);
if (retval) {
dev_err(&pdev->dev,
"Error in request_threaded_irq(irq(%d)!!\n",
chc.irq);
goto otg_req_failed;
}
retval = pmic_check_initial_events();
if (unlikely(retval)) {
dev_err(&pdev->dev,
"Error posting initial events\n");
goto req_irq_failed;
}
/* unmask charger interrupts in second level IRQ register*/
retval = intel_scu_ipc_update_register(MCHGRIRQ0_ADDR, 0x00,
PMIC_CHRGR_INT0_MASK);
/* unmask charger interrupts in second level IRQ register*/
retval = intel_scu_ipc_iowrite8(MCHGRIRQ1_ADDR, 0x00);
if (unlikely(retval))
goto unmask_irq_failed;
/* unmask IRQLVL1 register */
retval = intel_scu_ipc_update_register(IRQLVL1_MASK_ADDR, 0x00,
IRQLVL1_CHRGR_MASK);
if (unlikely(retval))
goto unmask_irq_failed;
retval = intel_scu_ipc_update_register(USBIDCTRL_ADDR,
ACADETEN_MASK | USBIDEN_MASK,
ACADETEN_MASK | USBIDEN_MASK);
if (unlikely(retval))
goto unmask_irq_failed;
chc.health = POWER_SUPPLY_HEALTH_GOOD;
#ifdef CONFIG_DEBUG_FS
pmic_debugfs_init();
#endif
return 0;
unmask_irq_failed:
req_irq_failed:
free_irq(chc.irq, &chc);
otg_req_failed:
iounmap(chc.pmic_intr_iomap);
ioremap_failed:
kfree(chc.sfi_bcprof);
kfree(chc.actual_bcprof);
kfree(chc.runtime_bcprof);
return retval;
}
static void pmic_chrgr_do_exit_ops(struct pmic_chrgr_drv_context *chc)
{
/*TODO:
* If charger is connected send IPC message to SCU to continue charging
*/
#ifdef CONFIG_DEBUG_FS
pmic_debugfs_exit();
#endif
}
/**
* pmic_charger_remove - PMIC Charger driver remove
* @pdev: PMIC charger platform device structure
* Context: can sleep
*
* PMIC charger finalizes its internal data structure and other
* infrastructure components that it initialized in
* pmic_chrgr_probe.
*/
static int pmic_chrgr_remove(struct platform_device *pdev)
{
struct pmic_chrgr_drv_context *chc = platform_get_drvdata(pdev);
if (chc) {
pmic_chrgr_do_exit_ops(chc);
wake_lock_destroy(&chc->wakelock);
wake_lock_destroy(&chc->otg_wa_wakelock);
free_irq(chc->irq, chc);
iounmap(chc->pmic_intr_iomap);
kfree(chc->sfi_bcprof);
kfree(chc->actual_bcprof);
kfree(chc->runtime_bcprof);
}
return 0;
}
#ifdef CONFIG_PM
static int pmic_chrgr_suspend(struct device *dev)
{
dev_dbg(dev, "%s called\n", __func__);
return 0;
}
static int pmic_chrgr_resume(struct device *dev)
{
dev_dbg(dev, "%s called\n", __func__);
return 0;
}
#endif
#ifdef CONFIG_PM_RUNTIME
static int pmic_chrgr_runtime_suspend(struct device *dev)
{
dev_dbg(dev, "%s called\n", __func__);
return 0;
}
static int pmic_chrgr_runtime_resume(struct device *dev)
{
dev_dbg(dev, "%s called\n", __func__);
return 0;
}
static int pmic_chrgr_runtime_idle(struct device *dev)
{
dev_dbg(dev, "%s called\n", __func__);
return 0;
}
#endif
/*********************************************************************
* Driver initialisation and finalization
*********************************************************************/
static const struct dev_pm_ops pmic_chrgr_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(pmic_chrgr_suspend,
pmic_chrgr_resume)
SET_RUNTIME_PM_OPS(pmic_chrgr_runtime_suspend,
pmic_chrgr_runtime_resume,
pmic_chrgr_runtime_idle)
};
static struct platform_driver pmic_chrgr_driver = {
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
.pm = &pmic_chrgr_pm_ops,
},
.probe = pmic_chrgr_probe,
.remove = pmic_chrgr_remove,
};
static int pmic_chrgr_init(void)
{
return platform_driver_register(&pmic_chrgr_driver);
}
static void pmic_chrgr_exit(void)
{
platform_driver_unregister(&pmic_chrgr_driver);
}
static int pmic_ccsm_rpmsg_probe(struct rpmsg_channel *rpdev)
{
int ret = 0;
if (rpdev == NULL) {
pr_err("rpmsg channel not created\n");
ret = -ENODEV;
goto out;
}
dev_info(&rpdev->dev, "Probed pmic_ccsm rpmsg device\n");
ret = pmic_chrgr_init();
out:
return ret;
}
static void pmic_ccsm_rpmsg_remove(struct rpmsg_channel *rpdev)
{
pmic_chrgr_exit();
dev_info(&rpdev->dev, "Removed pmic_ccsm rpmsg device\n");
}
static void pmic_ccsm_rpmsg_cb(struct rpmsg_channel *rpdev, void *data,
int len, void *priv, u32 src)
{
dev_warn(&rpdev->dev, "unexpected, message\n");
print_hex_dump(KERN_DEBUG, __func__, DUMP_PREFIX_NONE, 16, 1,
data, len, true);
}
static struct rpmsg_device_id pmic_ccsm_rpmsg_id_table[] = {
{ .name = "rpmsg_pmic_ccsm" },
{ },
};
MODULE_DEVICE_TABLE(rpmsg, pmic_ccsm_rpmsg_id_table);
static struct rpmsg_driver pmic_ccsm_rpmsg = {
.drv.name = KBUILD_MODNAME,
.drv.owner = THIS_MODULE,
.id_table = pmic_ccsm_rpmsg_id_table,
.probe = pmic_ccsm_rpmsg_probe,
.callback = pmic_ccsm_rpmsg_cb,
.remove = pmic_ccsm_rpmsg_remove,
};
static int __init pmic_ccsm_rpmsg_init(void)
{
return register_rpmsg_driver(&pmic_ccsm_rpmsg);
}
static void __exit pmic_ccsm_rpmsg_exit(void)
{
return unregister_rpmsg_driver(&pmic_ccsm_rpmsg);
}
/* Defer init call so that dependant drivers will be loaded. Using async
* for parallel driver initialization */
late_initcall(pmic_ccsm_rpmsg_init);
module_exit(pmic_ccsm_rpmsg_exit);
MODULE_AUTHOR("Jenny TC <jenny.tc@intel.com>");
MODULE_DESCRIPTION("PMIC Charger Driver");
MODULE_LICENSE("GPL");