6db4831e98
Android 14
3175 lines
82 KiB
C
3175 lines
82 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (c) 2019 MediaTek Inc.
|
|
*/
|
|
|
|
#include <linux/iio/consumer.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/kthread.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_irq.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/power_supply.h>
|
|
#include <linux/regulator/driver.h>
|
|
#include <linux/reboot.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/phy/phy.h>
|
|
|
|
#include <dt-bindings/mfd/mt6362.h>
|
|
#include <mt-plat/v1/charger_class.h>
|
|
#include <mtk_charger_intf.h> /* notify vbusov/eoc/rechg */
|
|
#include <mt-plat/mtk_boot.h> /* meta mode */
|
|
#include <mt-plat/upmu_common.h> /* usb phy switch */
|
|
|
|
#define MT6362_CHG_DRV_VERSION "1.0.1_MTK"
|
|
#define PHY_MODE_BC11_SET 1
|
|
#define PHY_MODE_BC11_CLR 2
|
|
|
|
struct tag_bootmode {
|
|
u32 size;
|
|
u32 tag;
|
|
u32 bootmode;
|
|
u32 boottype;
|
|
};
|
|
|
|
static bool dbg_log_en;
|
|
module_param(dbg_log_en, bool, 0644);
|
|
#define mt_dbg(dev, fmt, ...) \
|
|
do { \
|
|
if (dbg_log_en) \
|
|
dev_info(dev, fmt, ##__VA_ARGS__); \
|
|
} while (0)
|
|
|
|
/* Register Table */
|
|
#define MT6362_REG_CORE_CTRL2 (0x06)
|
|
#define MT6362_REG_TM_PASCODE1 (0x07)
|
|
#define MT6362_REG_CHG_TOP1 (0x20)
|
|
#define MT6362_REG_CHG_TOP2 (0x21)
|
|
#define MT6362_REG_CHG_AICR (0x22)
|
|
#define MT6362_REG_CHG_MIVR (0x23)
|
|
#define MT6362_REG_CHG_PREC (0x24)
|
|
#define MT6362_REG_CHG_VCHG (0x25)
|
|
#define MT6362_REG_CHG_ICHG (0x26)
|
|
#define MT6362_REG_CHG_TMR (0x27)
|
|
#define MT6362_REG_CHG_EOC (0x28)
|
|
#define MT6362_REG_CHG_WDT (0x2A)
|
|
#define MT6362_REG_CHG_PUMPX (0x2B)
|
|
#define MT6362_REG_CHG_AICC1 (0x2C)
|
|
#define MT6362_REG_CHG_AICC2 (0x2D)
|
|
#define MT6362_REG_OTG_V (0x31)
|
|
#define MT6362_REG_OTG_C (0x32)
|
|
#define MT6362_REG_BAT_COMP (0x33)
|
|
#define MT6362_REG_CHG_STAT (0x34)
|
|
#define MT6362_REG_CHG_DUMMY0 (0x35)
|
|
#define MT6362_REG_CHG_HD_TOP1 (0x3B)
|
|
#define MT6362_REG_CHG_HD_BUCK5 (0x40)
|
|
#define MT6362_REG_CHG_HD_DRV3 (0x49)
|
|
#define MT6362_REG_BC12_FUNC (0x50)
|
|
#define MT6362_REG_BC12_STAT (0x51)
|
|
#define MT6362_REG_FLED_EN (0x7E)
|
|
#define MT6362_REG_ADC_CONFIG1 (0xA4)
|
|
#define MT6362_REG_ADC_ZCV_RPT_H (0xCA)
|
|
#define MT6362_REG_CHG_STAT0 (0xE0)
|
|
#define MT6362_REG_CHG_STAT1 (0xE1)
|
|
#define MT6362_PD_I2C_TO_RST_CTRL (0x4BF)
|
|
|
|
/* Mask & Shift */
|
|
/* 0x06 */
|
|
#define MT6362_MASK_SHIP_RST_DIS BIT(0)
|
|
/* 0x20 */
|
|
#define MT6362_MASK_PP_PG_FLAG BIT(7)
|
|
#define MT6362_MASK_BATFET_DIS BIT(6)
|
|
#define MT6362_MASK_BATFET_DIS_DLY BIT(5)
|
|
#define MT6362_SHFT_BATFET_DIS_DLY (5)
|
|
#define MT6362_MASK_OTG_EN BIT(2)
|
|
#define MT6362_MASK_CHG_BUCK_EN BIT(1)
|
|
#define MT6362_MASK_CHG_EN BIT(0)
|
|
/* 0x21 */
|
|
#define MT6362_MASK_VBUS_OV (0x03)
|
|
#define MT6362_SHFT_VBUS_OV (0)
|
|
#define MT6362_MASK_SEL_CLK_FREQ (0xC0)
|
|
#define MT6362_SHFT_SEL_CLK_FREQ (6)
|
|
/* 0x22 */
|
|
#define MT6362_MASK_ILIM_EN BIT(7)
|
|
#define MT6362_SHFT_ILIM_EN (7)
|
|
#define MT6362_MASK_AICR (0x7F)
|
|
#define MT6362_SHFT_AICR (0)
|
|
/* 0x23 */
|
|
#define MT6362_MASK_MIVR (0x7F)
|
|
#define MT6362_SHFT_MIVR (0)
|
|
/* 0x24 */
|
|
#define MT6362_MASK_IPREC (0x1F)
|
|
#define MT6362_SHFT_IPREC (0)
|
|
/* 0x25 */
|
|
#define MT6362_MASK_CV (0x7F)
|
|
#define MT6362_SHFT_CV (0)
|
|
/* 0x26 */
|
|
#define MT6362_MASK_CC (0x3F)
|
|
#define MT6362_SHFT_CC (0)
|
|
/* 0x27 */
|
|
#define MT6362_MASK_CHG_TMR_EN BIT(7)
|
|
#define MT6362_MASK_CHG_TMR_TIME (0x30)
|
|
#define MT6362_SHFT_CHG_TMR_TIME (4)
|
|
/* 0x28 */
|
|
#define MT6362_MASK_IEOC (0xF0)
|
|
#define MT6362_SHFT_IEOC (4)
|
|
#define MT6362_MASK_TE BIT(1)
|
|
#define MT6362_MASK_EOC_RST BIT(0)
|
|
/* 0x2A */
|
|
#define MT6362_MASK_WDT_EN BIT(3)
|
|
#define MT6362_SHFT_WDT_EN (3)
|
|
#define MT6362_MASK_WDT_CNT_RST BIT(2)
|
|
/* 0x2B */
|
|
#define MT6362_MASK_PE_EN BIT(7)
|
|
#define MT6362_MASK_PE_SEL BIT(6)
|
|
#define MT6362_MASK_PE10_INC BIT(5)
|
|
#define MT6362_MASK_PE20_CODE (0x1F)
|
|
#define MT6362_SHFT_PE20_CODE (0)
|
|
/* 0x2C */
|
|
#define MT6362_MASK_AICC_EN BIT(7)
|
|
#define MT6362_MASK_AICC_VTH (0x7F)
|
|
/* 0x2D */
|
|
#define MT6362_MASK_AICC_ONESHOT BIT(7)
|
|
#define MT6362_SHFT_AICC_ONESHOT (7)
|
|
#define MT6362_MASK_AICC_RPT (0x7F)
|
|
#define MT6362_SHFT_AICC_RPT (0)
|
|
/* 0x31 */
|
|
#define MT6362_MASK_OTG_CV (0x3F)
|
|
/* 0x32 */
|
|
#define MT6362_MASK_OTG_CC (0x07)
|
|
#define MT6362_SHFT_OTG_CC (0)
|
|
/* 0x33 */
|
|
#define MT6362_MASK_BAT_IRCOMP_R (0x70)
|
|
#define MT6362_SHFT_BAT_IRCOMP_R (4)
|
|
#define MT6362_MASK_BAT_IRCOMP_V (0x07)
|
|
#define MT6362_SHFT_BAT_IRCOMP_V (0)
|
|
/* 0x34 */
|
|
#define MT6362_MASK_IC_STAT (0x0F)
|
|
#define MT6362_SHFT_IC_STAT (0)
|
|
/* 0x35 */
|
|
#define MT6362_MASK_COMP_CLAMP (0x03)
|
|
#define MT6362_SHFT_COMP_CLAMP (0)
|
|
/* 0x3B */
|
|
#define MT6362_MASK_DISCHG BIT(6)
|
|
/* 0x40 */
|
|
#define MT6362_MASK_BUCK_RAMPOFT (0xC0)
|
|
#define MT6362_SHFT_BUCK_RAMPOFT (6)
|
|
/* 0x49 */
|
|
#define MT6362_MASK_DISDRV_UG2LG BIT(1)
|
|
/* 0x50 */
|
|
#define MT6362_MASK_BC12_EN BIT(7)
|
|
#define MT6362_MASK_SPECTA_EN (0x40)
|
|
#define MT6362_SHFT_SPECTA_EN (6)
|
|
#define MT6362_MASK_DCDT_SEL (0x30)
|
|
#define MT6362_SHFT_DCDT_SEL (4)
|
|
/* 0x51 */
|
|
#define MT6362_MASK_PORT_STAT (0x0F)
|
|
#define MT6362_SHFT_PORT_STAT (0)
|
|
/* 0x7E */
|
|
#define MT6362_MASK_FL_STROBE BIT(2)
|
|
/* 0xA4 */
|
|
#define MT6362_MASK_ZCV_EN BIT(6)
|
|
/* 0xE0 */
|
|
#define MT6362_MASK_PWR_RDY BIT(0)
|
|
/* 0xE1 */
|
|
#define MT6362_MASK_MIVR_STAT BIT(7)
|
|
/* 0x4BF */
|
|
#define MT6362_MASK_BLEEDDIS_EN BIT(6)
|
|
|
|
/* Engineer Spec */
|
|
/* uA */
|
|
#define MT6362_AICR_MIN 50000
|
|
#define MT6362_AICR_MAX 3225000
|
|
#define MT6362_AICR_STEP 25000
|
|
/* uA */
|
|
#define MT6362_AICC_MIN 50000
|
|
#define MT6362_AICC_MAX 3225000
|
|
#define MT6362_AICC_STEP 25000
|
|
/* uV */
|
|
#define MT6362_MIVR_MIN 3900000
|
|
#define MT6362_MIVR_MAX 13400000
|
|
#define MT6362_MIVR_STEP 100000
|
|
/* uV */
|
|
#define MT6362_VPREC_MIN 2600000
|
|
#define MT6362_VPREC_MAX 3300000
|
|
#define MT6362_VPREC_STEP 100000
|
|
/* uA */
|
|
#define MT6362_IPREC_MIN 50000
|
|
#define MT6362_IPREC_MAX 1600000
|
|
#define MT6362_IPREC_STEP 50000
|
|
/* uV */
|
|
#define MT6362_CV_MIN 3900000
|
|
#define MT6362_CV_MAX 4710000
|
|
#define MT6362_CV_STEP 10000
|
|
/* uA */
|
|
#define MT6362_CC_MIN 0
|
|
#define MT6362_CC_MAX 3150000
|
|
#define MT6362_CC_STEP 50000
|
|
/* hr */
|
|
#define MT6362_TMR_MIN 5
|
|
#define MT6362_TMR_MAX 20
|
|
#define MT6362_TMR_STEP 5
|
|
/* uA */
|
|
#define MT6362_IEOC_MIN 50000
|
|
#define MT6362_IEOC_MAX 800000
|
|
#define MT6362_IEOC_STEP 50000
|
|
/* uV */
|
|
#define MT6362_PE20_MIN 5500000
|
|
#define MT6362_PE20_MAX 20000000
|
|
#define MT6362_PE20_STEP 500000
|
|
/* uohm */
|
|
#define MT6362_IR_R_MIN 0
|
|
#define MT6362_IR_R_MAX 25000
|
|
#define MT6362_IR_R_STEP 175000
|
|
/* uV */
|
|
#define MT6362_IR_V_MIN 0
|
|
#define MT6362_IR_V_MAX 32000
|
|
#define MT6362_IR_V_STEP 224000
|
|
|
|
struct mt6362_chg_platform_data {
|
|
u32 ichg;
|
|
u32 aicr;
|
|
u32 mivr;
|
|
u32 cv;
|
|
u32 ieoc;
|
|
u32 safety_timer;
|
|
u32 ircmp_resistor;
|
|
u32 ircmp_vclamp;
|
|
u32 dcdt_sel;
|
|
u32 specta_det;
|
|
u32 vbusov_sel;
|
|
u32 en_te;
|
|
u32 en_wdt;
|
|
u32 aicc_oneshot;
|
|
u32 post_aicc;
|
|
u32 post_aicc_thr;
|
|
u32 shipping_dly_en;
|
|
u32 batoc_notify;
|
|
const char *chg_name;
|
|
};
|
|
|
|
static const struct mt6362_chg_platform_data def_platform_data = {
|
|
.ichg = 2000000, /* uA */
|
|
.aicr = 500000, /* uA */
|
|
.mivr = 4400000, /* uV */
|
|
.cv = 4350000, /* uA */
|
|
.ieoc = 150000, /* uA */
|
|
.safety_timer = 10, /* hour */
|
|
.ircmp_resistor = 25000, /* uohm */
|
|
.ircmp_vclamp = 32000, /* uV */
|
|
.dcdt_sel = 2,
|
|
.specta_det = 0,
|
|
.en_te = true,
|
|
.en_wdt = true,
|
|
.aicc_oneshot = true,
|
|
.post_aicc = true,
|
|
.post_aicc_thr = 200000,
|
|
.shipping_dly_en = true,
|
|
.batoc_notify = false,
|
|
.chg_name = "primary_chg",
|
|
};
|
|
|
|
struct mt6362_chg_data {
|
|
struct device *dev;
|
|
struct regmap *regmap;
|
|
struct power_supply_desc psy_desc;
|
|
struct power_supply *psy;
|
|
struct charger_device *chg_dev;
|
|
struct regulator_dev *otg_rdev;
|
|
struct iio_channel *iio_ch;
|
|
struct mutex hidden_mode_lock;
|
|
struct mutex ichg_lock;
|
|
struct mutex tchg_lock;
|
|
struct mutex pe_lock;
|
|
struct mutex bd_lock;
|
|
struct mutex bc12_lock;
|
|
struct mutex otg_lock;
|
|
struct completion aicc_done;
|
|
struct completion pe_done;
|
|
struct completion bc12_start;
|
|
bool bd_flag;
|
|
bool pwr_rdy;
|
|
int hidden_mode_cnt;
|
|
int otg_mode_cnt;
|
|
int tchg;
|
|
u32 zcv;
|
|
u32 ichg;
|
|
u32 ichg_dis_chg;
|
|
u32 bd_mivr;
|
|
struct task_struct *bc12_task;
|
|
bool bc12_update;
|
|
bool attach;
|
|
struct power_supply *chg_psy;
|
|
enum charger_type chg_type;
|
|
/* mivr */
|
|
atomic_t mivr_cnt;
|
|
wait_queue_head_t waitq;
|
|
struct task_struct *mivr_task;
|
|
};
|
|
|
|
static const struct regulator_ops mt6362_chg_otg_ops = {
|
|
.list_voltage = regulator_list_voltage_linear,
|
|
.enable = regulator_enable_regmap,
|
|
.disable = regulator_disable_regmap,
|
|
.is_enabled = regulator_is_enabled_regmap,
|
|
.set_voltage_sel = regulator_set_voltage_sel_regmap,
|
|
.get_voltage_sel = regulator_get_voltage_sel_regmap,
|
|
};
|
|
|
|
static const struct regulator_desc mt6362_otg_rdesc = {
|
|
.of_match = "usb-otg-vbus",
|
|
.name = "usb-otg-vbus",
|
|
.ops = &mt6362_chg_otg_ops,
|
|
.owner = THIS_MODULE,
|
|
.type = REGULATOR_VOLTAGE,
|
|
.min_uV = 4350000,
|
|
.uV_step = 25000, /* 25mV per step */
|
|
.n_voltages = 59, /* 4350mV to 5800mV */
|
|
.vsel_reg = MT6362_REG_OTG_V,
|
|
.vsel_mask = MT6362_MASK_OTG_CV,
|
|
.enable_reg = MT6362_REG_CHG_TOP1,
|
|
.enable_mask = MT6362_MASK_OTG_EN,
|
|
};
|
|
|
|
static const u32 otg_cc_table[] = {
|
|
500000, 700000, 1100000, 1300000, 1800000, 2100000, 2400000, 3000000
|
|
};
|
|
|
|
enum mt6362_clk_freq {
|
|
MT6362_CLK_FREQ_1500K,
|
|
MT6362_CLK_FREQ_1000K,
|
|
MT6362_CLK_FREQ_750K,
|
|
MT6362_CLK_FREQ_MAX,
|
|
};
|
|
|
|
enum mt6362_usbsw_state {
|
|
MT6362_USBSW_CHG = 0,
|
|
MT6362_USBSW_USB,
|
|
};
|
|
|
|
enum pe_sel {
|
|
MT6362_PE_SEL_10,
|
|
MT6362_PE_SEL_20,
|
|
MT6362_PE_SEL_MAX,
|
|
};
|
|
|
|
enum mt6362_chg_type {
|
|
MT6362_CHG_TYPE_NO_INFO = 0x0,
|
|
MT6362_CHG_TYPE_APPLE_10W = 0x8,
|
|
MT6362_CHG_TYPE_SAMSUNG_10W = 0x9,
|
|
MT6362_CHG_TYPE_APPLE_5W = 0xA,
|
|
MT6362_CHG_TYPE_APPLE_12W = 0xB,
|
|
MT6362_CHG_TYPE_UNKNOWN_TA = 0xC,
|
|
MT6362_CHG_TYPE_SDP = 0xD,
|
|
MT6362_CHG_TYPE_CDP = 0xE,
|
|
MT6362_CHG_TYPE_DCP = 0xF,
|
|
};
|
|
|
|
enum mt6362_ic_stat {
|
|
MT6362_STAT_HZ = 0x0,
|
|
MT6362_STAT_READY = 0x1,
|
|
MT6362_STAT_TRI_CHG = 0x2,
|
|
MT6362_STAT_PRE_CHG = 0x3,
|
|
MT6362_STAT_FAST_CHG = 0x4,
|
|
MT6362_STAT_EOC_CHG = 0x5,
|
|
MT6362_STAT_BACKGND_CHG = 0x6,
|
|
MT6362_STAT_CHG_DONE = 0x7,
|
|
MT6362_STAT_CHG_FAULT = 0x8,
|
|
MT6362_STAT_OTG = 0xf,
|
|
};
|
|
|
|
static const char * const mt6362_ic_stat_list[] = {
|
|
"hz", "ready", "trickle_chg", "pre_chg", "fast_chg", "ieoc_chg",
|
|
"backgnd_chg", "chg_done", "chg_fault", "unknown", "unknown", "unknown",
|
|
"unknown", "unknown", "unknown", "otg",
|
|
};
|
|
|
|
enum mt6362_chg_adc_channel {
|
|
MT6362_CHG_ADCCH_CHGVINDIV5,
|
|
MT6362_CHG_ADCCH_VSYS,
|
|
MT6362_CHG_ADCCH_VBAT,
|
|
MT6362_CHG_ADCCH_IBUS,
|
|
MT6362_CHG_ADCCH_IBAT,
|
|
MT6362_CHG_ADCCH_TEMP_JC,
|
|
MT6362_CHG_ADCCH_ZCV,
|
|
};
|
|
|
|
static const char * const mt6362_adcch_list[] = {
|
|
"VBUSDIV5", "VSYS", "VBAT", "IBUS", "IBAT", "TEMP_JC", "ZCV"
|
|
};
|
|
|
|
/* for recive bat oc notify */
|
|
struct mt6362_chg_data *g_data;
|
|
|
|
/*
|
|
* =================
|
|
* internal funciton
|
|
* =================
|
|
*/
|
|
|
|
static inline u32 mt6362_map_reg_sel(u32 data, u32 min, u32 max, u32 step)
|
|
{
|
|
u32 target = 0, max_sel;
|
|
|
|
if (data >= min) {
|
|
target = (data - min) / step;
|
|
max_sel = (max - min) / step;
|
|
if (target > max_sel)
|
|
target = max_sel;
|
|
}
|
|
return target;
|
|
}
|
|
|
|
static inline u32 mt6362_map_real_val(u32 sel, u32 min, u32 max, u32 step)
|
|
{
|
|
u32 target = 0;
|
|
|
|
target = min + (sel * step);
|
|
if (target > max)
|
|
target = max;
|
|
return target;
|
|
}
|
|
|
|
static int mt6362_enable_hidden_mode(struct charger_device *chg_dev, bool en)
|
|
{
|
|
struct mt6362_chg_data *data = charger_get_data(chg_dev);
|
|
int ret = 0;
|
|
|
|
mt_dbg(data->dev, "%s: en = %d\n", __func__, en);
|
|
mutex_lock(&data->hidden_mode_lock);
|
|
if (en) {
|
|
if (data->hidden_mode_cnt == 0) {
|
|
ret = regmap_write(data->regmap,
|
|
MT6362_REG_TM_PASCODE1, 0x69);
|
|
if (ret < 0)
|
|
goto err;
|
|
}
|
|
data->hidden_mode_cnt++;
|
|
} else {
|
|
if (data->hidden_mode_cnt == 1) {
|
|
ret = regmap_write(data->regmap,
|
|
MT6362_REG_TM_PASCODE1, 0x00);
|
|
if (ret < 0)
|
|
goto err;
|
|
data->hidden_mode_cnt--;
|
|
}
|
|
}
|
|
goto out;
|
|
err:
|
|
dev_err(data->dev, "%s: fail\n", __func__);
|
|
out:
|
|
mutex_unlock(&data->hidden_mode_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int mt6362_enable_wdt(struct mt6362_chg_data *data, bool en)
|
|
{
|
|
struct mt6362_chg_platform_data *pdata = dev_get_platdata(data->dev);
|
|
|
|
mt_dbg(data->dev, "%s: en = %d\n", __func__, en);
|
|
if (!pdata->en_wdt)
|
|
return 0;
|
|
return regmap_update_bits(data->regmap,
|
|
MT6362_REG_CHG_WDT,
|
|
MT6362_MASK_WDT_EN,
|
|
en ? 0xff : 0);
|
|
}
|
|
|
|
static inline int mt6362_read_zcv(struct mt6362_chg_data *data)
|
|
{
|
|
int ret;
|
|
|
|
dev_dbg(data->dev, "%s\n", __func__);
|
|
ret = iio_read_channel_processed(&data->iio_ch[MT6362_CHG_ADCCH_ZCV],
|
|
&data->zcv);
|
|
if (ret < 0) {
|
|
dev_info(data->dev, "%s: fail(%d)\n", __func__, ret);
|
|
return ret;
|
|
}
|
|
dev_info(data->dev, "%s: zcv = %d mV\n", __func__, data->zcv/1000);
|
|
return ret;
|
|
}
|
|
|
|
static inline int mt6362_is_charger_enabled(struct mt6362_chg_data *data,
|
|
bool *en)
|
|
{
|
|
int ret = 0;
|
|
u32 regval;
|
|
|
|
ret = regmap_read(data->regmap, MT6362_REG_CHG_TOP1, ®val);
|
|
if (ret < 0)
|
|
return ret;
|
|
*en = (regval & MT6362_MASK_CHG_EN) ? true : false;
|
|
return 0;
|
|
}
|
|
|
|
static int __mt6362_set_ichg(struct mt6362_chg_data *data, u32 uA)
|
|
{
|
|
u8 sel;
|
|
int ret;
|
|
|
|
/* mapping datasheet define */
|
|
if (uA < 300000)
|
|
return -EINVAL;
|
|
sel = mt6362_map_reg_sel(uA, MT6362_CC_MIN, MT6362_CC_MAX,
|
|
MT6362_CC_STEP);
|
|
ret = regmap_update_bits(data->regmap,
|
|
MT6362_REG_CHG_ICHG,
|
|
MT6362_MASK_CC,
|
|
sel << MT6362_SHFT_CC);
|
|
if (ret < 0)
|
|
dev_err(data->dev, "%s: fail\n", __func__);
|
|
else
|
|
data->ichg = uA;
|
|
return ret;
|
|
}
|
|
|
|
static int DPDM_Switch_TO_CHG_upstream(struct mt6362_chg_data *data,
|
|
bool switch_to_chg)
|
|
{
|
|
struct phy *phy;
|
|
int mode = 0;
|
|
int ret;
|
|
|
|
mode = switch_to_chg ? PHY_MODE_BC11_SET : PHY_MODE_BC11_CLR;
|
|
phy = phy_get(data->dev, "usb2-phy");
|
|
if (IS_ERR_OR_NULL(phy)) {
|
|
dev_info(data->dev, "phy_get fail\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = phy_set_mode_ext(phy, PHY_MODE_USB_DEVICE, mode);
|
|
if (ret)
|
|
dev_info(data->dev, "phy_set_mode_ext fail\n");
|
|
|
|
phy_put(phy);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_MTK_EXTERNAL_CHARGER_TYPE_DETECT
|
|
static int mt6362_set_usbsw_state(struct mt6362_chg_data *data, int state)
|
|
{
|
|
dev_info(data->dev, "%s: state = %d\n", __func__, state);
|
|
/* Switch D+D- to AP/MT6362 */
|
|
if (state == MT6362_USBSW_CHG)
|
|
DPDM_Switch_TO_CHG_upstream(data, true);
|
|
else
|
|
DPDM_Switch_TO_CHG_upstream(data, false);
|
|
return 0;
|
|
}
|
|
|
|
static int mt6362_chg_psy_changed(struct mt6362_chg_data *data)
|
|
{
|
|
union power_supply_propval propval;
|
|
int ret;
|
|
|
|
/* Get chg type det power supply */
|
|
data->chg_psy = power_supply_get_by_name("charger");
|
|
if (!data->chg_psy) {
|
|
dev_notice(data->dev,
|
|
"%s: get power supply failed\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
propval.intval = data->attach;
|
|
ret = power_supply_set_property(data->chg_psy, POWER_SUPPLY_PROP_ONLINE,
|
|
&propval);
|
|
if (ret < 0)
|
|
dev_err(data->dev, "%s: psy online fail(%d)\n", __func__, ret);
|
|
else
|
|
dev_info(data->dev,
|
|
"%s: pwr_rdy = %d\n", __func__, data->attach);
|
|
|
|
propval.intval = data->chg_type;
|
|
ret = power_supply_set_property(data->chg_psy,
|
|
POWER_SUPPLY_PROP_CHARGE_TYPE,
|
|
&propval);
|
|
if (ret < 0)
|
|
dev_err(data->dev,
|
|
"%s: psy type fail(%d)\n", __func__, ret);
|
|
else
|
|
dev_info(data->dev,
|
|
"%s: chg_type = %d\n", __func__, data->chg_type);
|
|
return ret;
|
|
}
|
|
|
|
static int __mt6362_enable_bc12(struct mt6362_chg_data *data, bool en)
|
|
{
|
|
enum mt6362_usbsw_state usbsw =
|
|
en ? MT6362_USBSW_CHG : MT6362_USBSW_USB;
|
|
|
|
mt6362_set_usbsw_state(data, usbsw);
|
|
return regmap_update_bits(data->regmap, MT6362_REG_BC12_FUNC,
|
|
MT6362_MASK_BC12_EN, en ? 0xff : 0);
|
|
}
|
|
|
|
static int mt6362_enable_bc12(struct mt6362_chg_data *data, bool en)
|
|
{
|
|
struct mt6362_chg_platform_data *pdata = dev_get_platdata(data->dev);
|
|
int i;
|
|
const int max_wait_cnt = 200;
|
|
struct device *dev = NULL;
|
|
struct device_node *boot_node = NULL;
|
|
struct tag_bootmode *tag = NULL;
|
|
int boot_mode = 11;//UNKNOWN_BOOT
|
|
|
|
dev = data->dev;
|
|
if (dev != NULL) {
|
|
boot_node = of_parse_phandle(dev->of_node, "bootmode", 0);
|
|
if (!boot_node) {
|
|
chr_err("%s: failed to get boot mode phandle\n", __func__);
|
|
} else {
|
|
tag = (struct tag_bootmode *)of_get_property(boot_node,
|
|
"atag,boot", NULL);
|
|
if (!tag) {
|
|
chr_err("%s: failed to get atag,boot\n", __func__);
|
|
} else
|
|
boot_mode = tag->bootmode;
|
|
}
|
|
}
|
|
|
|
if (en) {
|
|
if (boot_mode == META_BOOT) {
|
|
/* Skip charger type detection to speed up meta boot.*/
|
|
dev_notice(data->dev,
|
|
"%s: force Standard USB Host in meta\n",
|
|
__func__);
|
|
data->psy_desc.type = POWER_SUPPLY_TYPE_USB;
|
|
data->chg_type = STANDARD_HOST;
|
|
power_supply_changed(data->psy);
|
|
mt6362_chg_psy_changed(data);
|
|
return 0;
|
|
}
|
|
/* add delay for disable DCD timeout */
|
|
if (!pdata->dcdt_sel)
|
|
msleep(180);
|
|
/* Workaround for CDP port */
|
|
for (i = 0; i < max_wait_cnt; i++) {
|
|
if (is_usb_rdy())
|
|
break;
|
|
dev_info(data->dev, "%s: CDP block\n", __func__);
|
|
if (!data->attach) {
|
|
dev_info(data->dev, "%s: plug out\n", __func__);
|
|
return 0;
|
|
}
|
|
msleep(100);
|
|
}
|
|
if (i == max_wait_cnt)
|
|
dev_err(data->dev, "%s: CDP timeout\n", __func__);
|
|
else
|
|
dev_info(data->dev, "%s: CDP free\n", __func__);
|
|
} else {
|
|
data->psy_desc.type = POWER_SUPPLY_TYPE_UNKNOWN;
|
|
data->chg_type = CHARGER_UNKNOWN;
|
|
power_supply_changed(data->psy);
|
|
mt6362_chg_psy_changed(data);
|
|
}
|
|
return __mt6362_enable_bc12(data, en);
|
|
}
|
|
|
|
static int mt6362_bc12_thread(void *data)
|
|
{
|
|
struct mt6362_chg_data *cdata = data;
|
|
bool bc12_en;
|
|
int ret;
|
|
|
|
while (!kthread_should_stop()) {
|
|
wait_for_completion(&cdata->bc12_start);
|
|
mutex_lock(&cdata->bc12_lock);
|
|
reinit_completion(&cdata->bc12_start);
|
|
bc12_en = cdata->attach;
|
|
mutex_unlock(&cdata->bc12_lock);
|
|
ret = mt6362_enable_bc12(cdata, bc12_en);
|
|
if (ret < 0)
|
|
dev_err(cdata->dev, "%s: handle bc12 fail, en = %d\n",
|
|
__func__, bc12_en);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void mt6362_run_bc12_thread(struct mt6362_chg_data *data, bool en)
|
|
{
|
|
struct device *dev = NULL;
|
|
struct device_node *boot_node = NULL;
|
|
struct tag_bootmode *tag = NULL;
|
|
int boot_mode = 11;//UNKNOWN_BOOT
|
|
|
|
dev = data->dev;
|
|
if (dev != NULL) {
|
|
boot_node = of_parse_phandle(dev->of_node, "bootmode", 0);
|
|
if (!boot_node) {
|
|
chr_err("%s: failed to get boot mode phandle\n", __func__);
|
|
} else {
|
|
tag = (struct tag_bootmode *)of_get_property(boot_node,
|
|
"atag,boot", NULL);
|
|
if (!tag) {
|
|
chr_err("%s: failed to get atag,boot\n", __func__);
|
|
} else
|
|
boot_mode = tag->bootmode;
|
|
}
|
|
}
|
|
|
|
if (en == false &&
|
|
(boot_mode == KERNEL_POWER_OFF_CHARGING_BOOT ||
|
|
boot_mode == LOW_POWER_OFF_CHARGING_BOOT)) {
|
|
pr_notice("%s: Unplug Charger/USB\n", __func__);
|
|
pr_notice("%s: system_state = %d\n", __func__, system_state);
|
|
if (system_state != SYSTEM_POWER_OFF)
|
|
kernel_power_off();
|
|
}
|
|
if (en == data->attach) {
|
|
dev_info(data->dev,
|
|
"%s: attach is the same, ignore\n", __func__);
|
|
return;
|
|
}
|
|
mutex_lock(&data->bc12_lock);
|
|
data->attach = en;
|
|
data->bc12_update = true;
|
|
complete(&data->bc12_start);
|
|
mutex_unlock(&data->bc12_lock);
|
|
}
|
|
#endif /* CONFIG_MTK_EXTERNAL_CHARGER_TYPE_DETECT */
|
|
|
|
static inline int mt6362_get_pwr_rdy_stat(struct mt6362_chg_data *data,
|
|
bool *pwr_rdy)
|
|
{
|
|
int ret;
|
|
u32 regval = 0;
|
|
|
|
ret = regmap_read(data->regmap, MT6362_REG_CHG_STAT0, ®val);
|
|
if (ret < 0)
|
|
return ret;
|
|
*pwr_rdy = (regval & MT6362_MASK_PWR_RDY) ? true : false;
|
|
return 0;
|
|
}
|
|
|
|
static int mt6362_get_charging_status(struct mt6362_chg_data *data,
|
|
enum mt6362_ic_stat *ic_stat)
|
|
{
|
|
int ret;
|
|
u32 regval = 0;
|
|
|
|
ret = regmap_read(data->regmap, MT6362_REG_CHG_STAT, ®val);
|
|
if (ret < 0)
|
|
return ret;
|
|
*ic_stat = (regval & MT6362_MASK_IC_STAT) >> MT6362_SHFT_IC_STAT;
|
|
return 0;
|
|
}
|
|
|
|
static int mt6362_toggle_cfo(struct mt6362_chg_data *data)
|
|
{
|
|
int ret;
|
|
u32 regval;
|
|
|
|
/* check if strobe mode */
|
|
ret = regmap_read(data->regmap, MT6362_REG_FLED_EN, ®val);
|
|
if (ret < 0)
|
|
return ret;
|
|
if (regval & MT6362_MASK_FL_STROBE) {
|
|
dev_err(data->dev, "%s: fled in strobe mode\n", __func__);
|
|
return ret;
|
|
}
|
|
/* chg_buck off */
|
|
ret = regmap_update_bits(data->regmap, MT6362_REG_CHG_TOP1,
|
|
MT6362_MASK_CHG_BUCK_EN, 0);
|
|
if (ret < 0) {
|
|
dev_err(data->dev, "%s: set chg_buck off fail\n", __func__);
|
|
return ret;
|
|
}
|
|
/* chg_buck on */
|
|
ret = regmap_update_bits(data->regmap, MT6362_REG_CHG_TOP1,
|
|
MT6362_MASK_CHG_BUCK_EN, 0xff);
|
|
if (ret < 0)
|
|
dev_err(data->dev, "%s: set chg_buck on fail\n", __func__);
|
|
return ret;
|
|
}
|
|
|
|
static void mt6362_chg_irq_enable(const char *name, int en);
|
|
static int mt6362_get_ibus(struct charger_device *chg_dev, u32 *ibus);
|
|
static int mt6362_get_mivr_state(struct charger_device *chg_dev, bool *in_loop);
|
|
static int mt6362_chg_mivr_task_threadfn(void *data)
|
|
{
|
|
struct mt6362_chg_data *cdata = data;
|
|
u32 ibus;
|
|
int ret;
|
|
bool mivr_stat;
|
|
|
|
dev_info(cdata->dev, "%s ++\n", __func__);
|
|
while (!kthread_should_stop()) {
|
|
wait_event(cdata->waitq, atomic_read(&cdata->mivr_cnt) > 0);
|
|
mt_dbg(cdata->dev, "%s: enter mivr thread\n", __func__);
|
|
pm_stay_awake(cdata->dev);
|
|
/* check real mivr stat or not */
|
|
ret = mt6362_get_mivr_state(cdata->chg_dev, &mivr_stat);
|
|
if (ret < 0)
|
|
goto loop_cont;
|
|
if (!mivr_stat) {
|
|
mt_dbg(cdata->dev, "%s: mivr stat not act\n", __func__);
|
|
goto loop_cont;
|
|
}
|
|
/* read ibus adc */
|
|
ret = mt6362_get_ibus(cdata->chg_dev, &ibus);
|
|
if (ret < 0) {
|
|
dev_err(cdata->dev,
|
|
"%s: get ibus adc fail\n", __func__);
|
|
goto loop_cont;
|
|
}
|
|
/* if ibus adc value < 100mA), toggle cfo */
|
|
if (ibus < 100000) {
|
|
dev_dbg(cdata->dev, "%s: enter toggle cfo\n", __func__);
|
|
ret = mt6362_toggle_cfo(cdata);
|
|
if (ret < 0)
|
|
dev_err(cdata->dev,
|
|
"%s: toggle cfo fail\n", __func__);
|
|
}
|
|
loop_cont:
|
|
pm_relax(cdata->dev);
|
|
atomic_set(&cdata->mivr_cnt, 0);
|
|
mt6362_chg_irq_enable("chg_mivr_evt", 1);
|
|
msleep(200);
|
|
}
|
|
dev_info(cdata->dev, "%s --\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
static int __mt6362_enable_otg_parameter(struct mt6362_chg_data *data, bool en)
|
|
{
|
|
int ret;
|
|
|
|
/* Set switch frequency to 1.5/1 MHz for otg transient */
|
|
ret = regmap_update_bits(data->regmap,
|
|
MT6362_REG_CHG_TOP2,
|
|
MT6362_MASK_SEL_CLK_FREQ,
|
|
en ? MT6362_CLK_FREQ_1500K :
|
|
MT6362_CLK_FREQ_1000K <<
|
|
MT6362_SHFT_SEL_CLK_FREQ);
|
|
if (ret < 0) {
|
|
dev_err(data->dev,
|
|
"%s: fail to set switch freq 1.5/1 MHz\n", __func__);
|
|
return ret;
|
|
}
|
|
ret = mt6362_enable_hidden_mode(data->chg_dev, true);
|
|
if (ret < 0)
|
|
goto out;
|
|
/* Set comp diode to 2/1 for otg transient */
|
|
ret = regmap_update_bits(data->regmap,
|
|
MT6362_REG_CHG_DUMMY0,
|
|
MT6362_MASK_COMP_CLAMP,
|
|
en ? 0x3 : 0 << MT6362_SHFT_COMP_CLAMP);
|
|
if (ret < 0) {
|
|
dev_err(data->dev,
|
|
"%s: fail to set comp diode 2/1\n", __func__);
|
|
goto out;
|
|
}
|
|
/* Set buck_ramp offset 390/330 mV */
|
|
/* for decrease psm->pwm trnsition current when otg */
|
|
/* and not to enter psk mode when charging in hv and low loading */
|
|
ret = regmap_update_bits(data->regmap,
|
|
MT6362_REG_CHG_HD_BUCK5,
|
|
MT6362_MASK_BUCK_RAMPOFT,
|
|
en ? 0x3 : 0x1 << MT6362_SHFT_BUCK_RAMPOFT);
|
|
if (ret < 0) {
|
|
dev_err(data->dev,
|
|
"%s: fail to set buck ramp offset 390/330 mV\n",
|
|
__func__);
|
|
goto out;
|
|
}
|
|
/* Workaround for otg pwm stop switch when enable otg */
|
|
ret = regmap_update_bits(data->regmap,
|
|
MT6362_REG_CHG_HD_DRV3,
|
|
MT6362_MASK_DISDRV_UG2LG,
|
|
en ? 0xff : 0);
|
|
if (ret < 0)
|
|
dev_err(data->dev,
|
|
"%s: fail to disdrive UG to LG\n", __func__);
|
|
out:
|
|
mt6362_enable_hidden_mode(data->chg_dev, false);
|
|
return ret;
|
|
}
|
|
|
|
static int mt6362_enable_otg_parameter(struct mt6362_chg_data *data, bool en)
|
|
{
|
|
int ret = 0;
|
|
|
|
mt_dbg(data->dev, "%s: en = %d\n", __func__, en);
|
|
mutex_lock(&data->otg_lock);
|
|
if (en) {
|
|
if (data->otg_mode_cnt == 0) {
|
|
ret = __mt6362_enable_otg_parameter(data, en);
|
|
if (ret < 0)
|
|
goto err;
|
|
}
|
|
data->otg_mode_cnt++;
|
|
} else {
|
|
if (data->otg_mode_cnt == 1) {
|
|
ret = __mt6362_enable_otg_parameter(data, en);
|
|
if (ret < 0)
|
|
goto err;
|
|
}
|
|
data->otg_mode_cnt--;
|
|
}
|
|
goto out;
|
|
err:
|
|
dev_err(data->dev, "%s: fail\n", __func__);
|
|
out:
|
|
mutex_unlock(&data->otg_lock);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* ======================
|
|
* power supply props ops
|
|
* ======================
|
|
*/
|
|
|
|
static int mt6362_charger_get_online(struct mt6362_chg_data *data,
|
|
union power_supply_propval *val)
|
|
{
|
|
int ret;
|
|
bool pwr_rdy;
|
|
|
|
ret = mt6362_get_pwr_rdy_stat(data, &pwr_rdy);
|
|
if (ret < 0)
|
|
return ret;
|
|
val->intval = pwr_rdy;
|
|
return 0;
|
|
}
|
|
|
|
static int mt6362_charger_get_status(struct mt6362_chg_data *data,
|
|
union power_supply_propval *val)
|
|
{
|
|
enum mt6362_ic_stat ic_stat;
|
|
int ret, status;
|
|
bool pwr_rdy;
|
|
|
|
ret = mt6362_get_pwr_rdy_stat(data, &pwr_rdy);
|
|
if (ret < 0)
|
|
return ret;
|
|
if (!pwr_rdy) {
|
|
status = POWER_SUPPLY_STATUS_DISCHARGING;
|
|
goto out;
|
|
}
|
|
|
|
ret = mt6362_get_charging_status(data, &ic_stat);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
switch (ic_stat) {
|
|
case MT6362_STAT_HZ:
|
|
case MT6362_STAT_READY:
|
|
/* Fall through */
|
|
case MT6362_STAT_OTG:
|
|
status = POWER_SUPPLY_STATUS_NOT_CHARGING;
|
|
break;
|
|
case MT6362_STAT_TRI_CHG ... MT6362_STAT_BACKGND_CHG:
|
|
status = POWER_SUPPLY_STATUS_CHARGING;
|
|
break;
|
|
case MT6362_STAT_CHG_FAULT:
|
|
status = POWER_SUPPLY_STATUS_UNKNOWN;
|
|
break;
|
|
case MT6362_STAT_CHG_DONE:
|
|
status = POWER_SUPPLY_STATUS_FULL;
|
|
break;
|
|
default:
|
|
ret = -EIO;
|
|
}
|
|
out:
|
|
if (!ret)
|
|
val->intval = status;
|
|
return ret;
|
|
}
|
|
|
|
static int mt6362_charger_get_ocv(struct mt6362_chg_data *data,
|
|
union power_supply_propval *val)
|
|
{
|
|
val->intval = data->zcv;
|
|
return 0;
|
|
}
|
|
|
|
static int mt6362_charger_get_charge_type(struct mt6362_chg_data *data,
|
|
union power_supply_propval *val)
|
|
{
|
|
enum mt6362_ic_stat ic_stat;
|
|
int ret, type = 0;
|
|
|
|
ret = mt6362_get_charging_status(data, &ic_stat);
|
|
if (ret < 0)
|
|
return ret;
|
|
switch (ic_stat) {
|
|
case MT6362_STAT_READY: /* Not Charging */
|
|
type = POWER_SUPPLY_CHARGE_TYPE_NONE;
|
|
break;
|
|
case MT6362_STAT_TRI_CHG: /* Trickle Charge */
|
|
type = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
|
|
break;
|
|
case MT6362_STAT_PRE_CHG ... MT6362_STAT_BACKGND_CHG:
|
|
type = POWER_SUPPLY_CHARGE_TYPE_FAST;
|
|
break;
|
|
case MT6362_STAT_CHG_DONE: /* Charge Done */
|
|
type = POWER_SUPPLY_CHARGE_TYPE_NONE;
|
|
break;
|
|
case MT6362_STAT_CHG_FAULT: /* Charge Fault */
|
|
type = POWER_SUPPLY_STATUS_UNKNOWN;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
val->intval = type;
|
|
return 0;
|
|
}
|
|
|
|
static int mt6362_charger_get_ichg(struct mt6362_chg_data *data,
|
|
union power_supply_propval *val)
|
|
{
|
|
int ret;
|
|
u32 regval = 0;
|
|
|
|
ret = regmap_read(data->regmap, MT6362_REG_CHG_ICHG, ®val);
|
|
if (ret < 0)
|
|
return ret;
|
|
regval = (regval & MT6362_MASK_CC) >> MT6362_SHFT_CC;
|
|
val->intval = mt6362_map_real_val(regval, MT6362_CC_MIN, MT6362_CC_MAX,
|
|
MT6362_CC_STEP);
|
|
return 0;
|
|
}
|
|
|
|
static int mt6362_charger_get_max_ichg(struct mt6362_chg_data *data,
|
|
union power_supply_propval *val)
|
|
{
|
|
val->intval = MT6362_CC_MAX;
|
|
return 0;
|
|
}
|
|
|
|
static int mt6362_charger_get_cv(struct mt6362_chg_data *data,
|
|
union power_supply_propval *val)
|
|
{
|
|
int ret;
|
|
u32 regval = 0;
|
|
|
|
ret = regmap_read(data->regmap, MT6362_REG_CHG_VCHG, ®val);
|
|
if (ret < 0)
|
|
return ret;
|
|
regval = (regval & MT6362_MASK_CV) >> MT6362_SHFT_CV;
|
|
val->intval = mt6362_map_real_val(regval, MT6362_CV_MIN, MT6362_CV_MAX,
|
|
MT6362_CV_STEP);
|
|
return 0;
|
|
}
|
|
|
|
static int mt6362_charger_get_max_cv(struct mt6362_chg_data *data,
|
|
union power_supply_propval *val)
|
|
{
|
|
val->intval = MT6362_CV_MAX;
|
|
return 0;
|
|
}
|
|
|
|
static int mt6362_charger_get_aicr(struct mt6362_chg_data *data,
|
|
union power_supply_propval *val)
|
|
{
|
|
int ret;
|
|
u32 regval = 0;
|
|
|
|
ret = regmap_read(data->regmap, MT6362_REG_CHG_AICR, ®val);
|
|
if (ret < 0)
|
|
return ret;
|
|
regval = (regval & MT6362_MASK_AICR) >> MT6362_SHFT_AICR;
|
|
val->intval = mt6362_map_real_val(regval,
|
|
MT6362_AICR_MIN,
|
|
MT6362_AICR_MAX,
|
|
MT6362_AICR_STEP);
|
|
return 0;
|
|
}
|
|
|
|
static int mt6362_charger_get_iprechg(struct mt6362_chg_data *data,
|
|
union power_supply_propval *val)
|
|
{
|
|
int ret;
|
|
u32 regval;
|
|
|
|
ret = regmap_read(data->regmap, MT6362_REG_CHG_PREC, ®val);
|
|
if (ret < 0)
|
|
return ret;
|
|
regval = (regval & MT6362_MASK_IPREC) >> MT6362_SHFT_IPREC;
|
|
val->intval = mt6362_map_real_val(regval,
|
|
MT6362_IPREC_MIN,
|
|
MT6362_IPREC_MAX,
|
|
MT6362_IPREC_STEP);
|
|
return 0;
|
|
}
|
|
|
|
static int mt6362_charger_get_ieoc(struct mt6362_chg_data *data,
|
|
union power_supply_propval *val)
|
|
{
|
|
int ret;
|
|
u32 regval = 0;
|
|
|
|
ret = regmap_read(data->regmap, MT6362_REG_CHG_EOC, ®val);
|
|
if (ret < 0)
|
|
return ret;
|
|
regval = (regval & MT6362_MASK_IEOC) >> MT6362_SHFT_IEOC;
|
|
val->intval = mt6362_map_real_val(regval,
|
|
MT6362_IEOC_MIN,
|
|
MT6362_IEOC_MAX,
|
|
MT6362_IEOC_STEP);
|
|
return 0;
|
|
}
|
|
|
|
static int mt6362_charger_set_online(struct mt6362_chg_data *data,
|
|
const union power_supply_propval *val)
|
|
{
|
|
dev_info(data->dev, "%s: en = %d\n", __func__, val->intval);
|
|
#ifdef CONFIG_MTK_EXTERNAL_CHARGER_TYPE_DETECT
|
|
mt6362_run_bc12_thread(data, val->intval);
|
|
#endif /* CONFIG_MTK_EXTERNAL_CHARGER_TYPE_DETECT */
|
|
return 0;
|
|
}
|
|
|
|
static int mt6362_charger_set_ichg(struct mt6362_chg_data *data,
|
|
const union power_supply_propval *val)
|
|
{
|
|
int ret;
|
|
|
|
mutex_lock(&data->ichg_lock);
|
|
ret = __mt6362_set_ichg(data, val->intval);
|
|
mutex_unlock(&data->ichg_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int mt6362_charger_set_cv(struct mt6362_chg_data *data,
|
|
const union power_supply_propval *val)
|
|
{
|
|
u8 sel;
|
|
|
|
sel = mt6362_map_reg_sel(val->intval, MT6362_CV_MIN, MT6362_CV_MAX,
|
|
MT6362_CV_STEP);
|
|
return regmap_update_bits(data->regmap,
|
|
MT6362_REG_CHG_VCHG,
|
|
MT6362_MASK_CV,
|
|
sel << MT6362_SHFT_CV);
|
|
}
|
|
|
|
static int mt6362_charger_set_aicr(struct mt6362_chg_data *data,
|
|
const union power_supply_propval *val)
|
|
{
|
|
u8 sel;
|
|
|
|
sel = mt6362_map_reg_sel(val->intval, MT6362_AICR_MIN, MT6362_AICR_MAX,
|
|
MT6362_AICR_STEP);
|
|
return regmap_update_bits(data->regmap,
|
|
MT6362_REG_CHG_AICR,
|
|
MT6362_MASK_AICR,
|
|
sel << MT6362_SHFT_AICR);
|
|
}
|
|
|
|
static int mt6362_charger_set_iprechg(struct mt6362_chg_data *data,
|
|
const union power_supply_propval *val)
|
|
{
|
|
u8 sel;
|
|
|
|
sel = mt6362_map_reg_sel(val->intval,
|
|
MT6362_IPREC_MIN,
|
|
MT6362_IPREC_MAX,
|
|
MT6362_IPREC_STEP);
|
|
return regmap_update_bits(data->regmap,
|
|
MT6362_REG_CHG_PREC,
|
|
MT6362_MASK_IPREC,
|
|
sel << MT6362_SHFT_IPREC);
|
|
}
|
|
|
|
static int mt6362_charger_set_ieoc(struct mt6362_chg_data *data,
|
|
const union power_supply_propval *val)
|
|
{
|
|
u8 sel;
|
|
|
|
sel = mt6362_map_reg_sel(val->intval, MT6362_IEOC_MIN, MT6362_IEOC_MAX,
|
|
MT6362_IEOC_STEP);
|
|
return regmap_update_bits(data->regmap,
|
|
MT6362_REG_CHG_EOC,
|
|
MT6362_MASK_IEOC,
|
|
sel << MT6362_SHFT_IEOC);
|
|
}
|
|
|
|
static int mt6362_charger_get_property(struct power_supply *psy,
|
|
enum power_supply_property psp,
|
|
union power_supply_propval *val)
|
|
{
|
|
struct mt6362_chg_data *data = power_supply_get_drvdata(psy);
|
|
int ret = 0;
|
|
|
|
switch (psp) {
|
|
case POWER_SUPPLY_PROP_ONLINE:
|
|
ret = mt6362_charger_get_online(data, val);
|
|
break;
|
|
case POWER_SUPPLY_PROP_STATUS:
|
|
ret = mt6362_charger_get_status(data, val);
|
|
break;
|
|
case POWER_SUPPLY_PROP_VOLTAGE_OCV:
|
|
ret = mt6362_charger_get_ocv(data, val);
|
|
break;
|
|
case POWER_SUPPLY_PROP_CHARGE_TYPE:
|
|
ret = mt6362_charger_get_charge_type(data, val);
|
|
break;
|
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
|
|
ret = mt6362_charger_get_ichg(data, val);
|
|
break;
|
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
|
|
ret = mt6362_charger_get_max_ichg(data, val);
|
|
break;
|
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
|
|
ret = mt6362_charger_get_cv(data, val);
|
|
break;
|
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
|
|
ret = mt6362_charger_get_max_cv(data, val);
|
|
break;
|
|
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
|
|
ret = mt6362_charger_get_aicr(data, val);
|
|
break;
|
|
case POWER_SUPPLY_PROP_PRECHARGE_CURRENT:
|
|
ret = mt6362_charger_get_iprechg(data, val);
|
|
break;
|
|
case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
|
|
ret = mt6362_charger_get_ieoc(data, val);
|
|
break;
|
|
case POWER_SUPPLY_PROP_TYPE:
|
|
val->intval = data->psy_desc.type;
|
|
break;
|
|
default:
|
|
ret = -ENODATA;
|
|
}
|
|
mt_dbg(data->dev, "%s: prop = %d, val = %d\n", __func__,
|
|
psp, val->intval);
|
|
return ret;
|
|
}
|
|
|
|
static int mt6362_charger_set_property(struct power_supply *psy,
|
|
enum power_supply_property psp,
|
|
const union power_supply_propval *val)
|
|
{
|
|
struct mt6362_chg_data *data = power_supply_get_drvdata(psy);
|
|
int ret;
|
|
|
|
mt_dbg(data->dev, "%s: prop = %d, val = %d\n", __func__,
|
|
psp, val->intval);
|
|
switch (psp) {
|
|
case POWER_SUPPLY_PROP_ONLINE:
|
|
ret = mt6362_charger_set_online(data, val);
|
|
break;
|
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
|
|
ret = mt6362_charger_set_ichg(data, val);
|
|
break;
|
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
|
|
ret = mt6362_charger_set_cv(data, val);
|
|
break;
|
|
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
|
|
ret = mt6362_charger_set_aicr(data, val);
|
|
break;
|
|
case POWER_SUPPLY_PROP_PRECHARGE_CURRENT:
|
|
ret = mt6362_charger_set_iprechg(data, val);
|
|
break;
|
|
case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
|
|
ret = mt6362_charger_set_ieoc(data, val);
|
|
break;
|
|
default:
|
|
ret = -EINVAL;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int mt6362_charger_property_is_writeable(struct power_supply *psy,
|
|
enum power_supply_property psp)
|
|
{
|
|
switch (psp) {
|
|
case POWER_SUPPLY_PROP_ONLINE:
|
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
|
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
|
|
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
|
|
case POWER_SUPPLY_PROP_PRECHARGE_CURRENT:
|
|
case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
|
|
return 1;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static enum power_supply_property mt6362_charger_properties[] = {
|
|
POWER_SUPPLY_PROP_ONLINE,
|
|
POWER_SUPPLY_PROP_STATUS,
|
|
POWER_SUPPLY_PROP_CHARGE_TYPE,
|
|
POWER_SUPPLY_PROP_VOLTAGE_OCV,
|
|
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
|
|
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
|
|
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
|
|
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
|
|
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
|
|
POWER_SUPPLY_PROP_PRECHARGE_CURRENT,
|
|
POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
|
|
};
|
|
|
|
static const struct power_supply_desc mt6362_charger_desc = {
|
|
.type = POWER_SUPPLY_TYPE_UNKNOWN,
|
|
.properties = mt6362_charger_properties,
|
|
.num_properties = ARRAY_SIZE(mt6362_charger_properties),
|
|
.get_property = mt6362_charger_get_property,
|
|
.set_property = mt6362_charger_set_property,
|
|
.property_is_writeable = mt6362_charger_property_is_writeable,
|
|
};
|
|
|
|
static char *mt6362_charger_supplied_to[] = {
|
|
};
|
|
|
|
/*
|
|
* =================
|
|
* charger class ops
|
|
* =================
|
|
*/
|
|
|
|
static int mt6362_enable_charging(struct charger_device *chg_dev, bool en)
|
|
{
|
|
struct mt6362_chg_data *data = charger_get_data(chg_dev);
|
|
int ret;
|
|
u32 ichg_ramp_t = 0;
|
|
|
|
mt_dbg(data->dev, "%s: en = %d\n", __func__, en);
|
|
|
|
/* Workaround for vsys overshoot */
|
|
mutex_lock(&data->ichg_lock);
|
|
if (data->ichg < 500000) {
|
|
dev_info(data->dev,
|
|
"%s: ichg < 500mA, bypass vsys wkard\n", __func__);
|
|
goto out;
|
|
}
|
|
if (!en) {
|
|
data->ichg_dis_chg = data->ichg;
|
|
ichg_ramp_t = (data->ichg - MT6362_CC_MIN) / MT6362_CC_STEP * 2;
|
|
/* Set ichg to 500mA */
|
|
ret = regmap_update_bits(data->regmap,
|
|
MT6362_REG_CHG_ICHG,
|
|
MT6362_MASK_CC,
|
|
0x0A << MT6362_SHFT_CC);
|
|
if (ret < 0) {
|
|
dev_err(data->dev,
|
|
"%s: set ichg 500mA fail\n", __func__);
|
|
goto vsys_wkard_fail;
|
|
}
|
|
mdelay(ichg_ramp_t);
|
|
} else {
|
|
if (data->ichg == data->ichg_dis_chg) {
|
|
ret = __mt6362_set_ichg(data, data->ichg);
|
|
if (ret < 0)
|
|
dev_err(data->dev,
|
|
"%s: recover ichg fail\n", __func__);
|
|
}
|
|
}
|
|
out:
|
|
ret = regmap_update_bits(data->regmap,
|
|
MT6362_REG_CHG_TOP1,
|
|
MT6362_MASK_CHG_EN,
|
|
en ? 0xff : 0);
|
|
vsys_wkard_fail:
|
|
mutex_unlock(&data->ichg_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int mt6362_set_ichg(struct charger_device *chg_dev, u32 uA)
|
|
{
|
|
struct mt6362_chg_data *data = charger_get_data(chg_dev);
|
|
int ret;
|
|
|
|
mt_dbg(data->dev, "%s: ichg = %d\n", __func__, uA);
|
|
mutex_lock(&data->ichg_lock);
|
|
ret = __mt6362_set_ichg(data, uA);
|
|
mutex_unlock(&data->ichg_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int mt6362_get_ichg(struct charger_device *chg_dev, u32 *uA)
|
|
{
|
|
struct mt6362_chg_data *data = charger_get_data(chg_dev);
|
|
union power_supply_propval val;
|
|
int ret;
|
|
|
|
ret = mt6362_charger_get_ichg(data, &val);
|
|
if (ret < 0)
|
|
return ret;
|
|
*uA = val.intval;
|
|
mt_dbg(data->dev, "%s: ichg = %d\n", __func__, *uA);
|
|
return 0;
|
|
}
|
|
|
|
static int mt6362_get_min_ichg(struct charger_device *chg_dev, u32 *uA)
|
|
{
|
|
*uA = 300000;
|
|
return 0;
|
|
}
|
|
|
|
static int mt6362_set_cv(struct charger_device *chg_dev, u32 uV)
|
|
{
|
|
struct mt6362_chg_data *data = charger_get_data(chg_dev);
|
|
union power_supply_propval val;
|
|
|
|
mt_dbg(data->dev, "%s: cv = %d\n", __func__, uV);
|
|
val.intval = uV;
|
|
return mt6362_charger_set_cv(data, &val);
|
|
}
|
|
|
|
static int mt6362_get_cv(struct charger_device *chg_dev, u32 *uV)
|
|
{
|
|
struct mt6362_chg_data *data = charger_get_data(chg_dev);
|
|
union power_supply_propval val;
|
|
int ret;
|
|
|
|
ret = mt6362_charger_get_cv(data, &val);
|
|
if (ret < 0)
|
|
return ret;
|
|
*uV = val.intval;
|
|
mt_dbg(data->dev, "%s: cv = %d\n", __func__, *uV);
|
|
return 0;
|
|
}
|
|
|
|
static int mt6362_set_aicr(struct charger_device *chg_dev, u32 uA)
|
|
{
|
|
struct mt6362_chg_data *data = charger_get_data(chg_dev);
|
|
union power_supply_propval val;
|
|
|
|
mt_dbg(data->dev, "%s: aicr = %d\n", __func__, uA);
|
|
val.intval = uA;
|
|
return mt6362_charger_set_aicr(data, &val);
|
|
}
|
|
|
|
static int mt6362_get_aicr(struct charger_device *chg_dev, u32 *uA)
|
|
{
|
|
struct mt6362_chg_data *data = charger_get_data(chg_dev);
|
|
union power_supply_propval val;
|
|
int ret;
|
|
|
|
ret = mt6362_charger_get_aicr(data, &val);
|
|
if (ret < 0)
|
|
return ret;
|
|
*uA = val.intval;
|
|
mt_dbg(data->dev, "%s: aicr = %d\n", __func__, *uA);
|
|
return 0;
|
|
}
|
|
|
|
static int mt6362_get_min_aicr(struct charger_device *chg_dev, u32 *uA)
|
|
{
|
|
*uA = MT6362_AICR_MIN;
|
|
return 0;
|
|
}
|
|
|
|
static int mt6362_get_ieoc(struct mt6362_chg_data *data, u32 *uA)
|
|
{
|
|
int ret;
|
|
u32 regval = 0;
|
|
|
|
ret = regmap_read(data->regmap, MT6362_REG_CHG_EOC, ®val);
|
|
if (ret < 0)
|
|
return ret;
|
|
regval = (regval & MT6362_MASK_IEOC) >> MT6362_SHFT_IEOC;
|
|
*uA = mt6362_map_real_val(regval, MT6362_IEOC_MIN, MT6362_IEOC_MAX,
|
|
MT6362_IEOC_STEP);
|
|
mt_dbg(data->dev, "%s: ieoc = %d\n", __func__, *uA);
|
|
return 0;
|
|
}
|
|
|
|
static int mt6362_set_ieoc(struct charger_device *chg_dev, u32 uA)
|
|
{
|
|
struct mt6362_chg_data *data = charger_get_data(chg_dev);
|
|
union power_supply_propval val;
|
|
|
|
mt_dbg(data->dev, "%s: ieoc = %d\n", __func__, uA);
|
|
val.intval = uA;
|
|
return mt6362_charger_set_ieoc(data, &val);
|
|
}
|
|
|
|
static int __mt6362_set_mivr(struct mt6362_chg_data *data, u32 uV)
|
|
{
|
|
u8 sel;
|
|
|
|
mt_dbg(data->dev, "%s: mivr = %d\n", __func__, uV);
|
|
sel = mt6362_map_reg_sel(uV, MT6362_MIVR_MIN, MT6362_MIVR_MAX,
|
|
MT6362_MIVR_STEP);
|
|
return regmap_update_bits(data->regmap,
|
|
MT6362_REG_CHG_MIVR,
|
|
MT6362_MASK_MIVR,
|
|
sel << MT6362_SHFT_MIVR);
|
|
}
|
|
|
|
static int mt6362_set_mivr(struct charger_device *chg_dev, u32 uV)
|
|
{
|
|
struct mt6362_chg_data *data = charger_get_data(chg_dev);
|
|
int ret;
|
|
|
|
mutex_lock(&data->bd_lock);
|
|
if (data->bd_flag) {
|
|
dev_info(data->dev,
|
|
"%s: ignore until disable flash\n", __func__);
|
|
data->bd_mivr = uV;
|
|
mutex_unlock(&data->bd_lock);
|
|
return 0;
|
|
}
|
|
ret = __mt6362_set_mivr(data, uV);
|
|
mutex_unlock(&data->bd_lock);
|
|
return ret;
|
|
}
|
|
|
|
static inline int mt6362_get_mivr(struct charger_device *chg_dev, u32 *uV)
|
|
{
|
|
struct mt6362_chg_data *data = charger_get_data(chg_dev);
|
|
int ret;
|
|
u32 regval = 0;
|
|
|
|
ret = regmap_read(data->regmap, MT6362_REG_CHG_MIVR, ®val);
|
|
if (ret < 0)
|
|
return ret;
|
|
regval = (regval & MT6362_MASK_MIVR) >> MT6362_SHFT_MIVR;
|
|
*uV = mt6362_map_real_val(regval, MT6362_MIVR_MIN, MT6362_MIVR_MAX,
|
|
MT6362_MIVR_STEP);
|
|
mt_dbg(data->dev, "%s: mivr = %d\n", __func__, *uV);
|
|
return 0;
|
|
}
|
|
|
|
static int mt6362_get_mivr_state(struct charger_device *chg_dev, bool *in_loop)
|
|
{
|
|
struct mt6362_chg_data *data = charger_get_data(chg_dev);
|
|
int ret;
|
|
u32 regval = 0;
|
|
|
|
ret = regmap_read(data->regmap, MT6362_REG_CHG_STAT1, ®val);
|
|
if (ret < 0)
|
|
return ret;
|
|
*in_loop = (regval & MT6362_MASK_MIVR_STAT) ? true : false;
|
|
mt_dbg(data->dev, "%s: in_loop = %d\n", __func__, *in_loop);
|
|
return 0;
|
|
}
|
|
|
|
static int mt6362_enable_te(struct charger_device *chg_dev, bool en)
|
|
{
|
|
struct mt6362_chg_data *data = charger_get_data(chg_dev);
|
|
struct mt6362_chg_platform_data *pdata = dev_get_platdata(data->dev);
|
|
|
|
mt_dbg(data->dev, "%s: en = %d\n", __func__, en);
|
|
if (!pdata->en_te)
|
|
return 0;
|
|
return regmap_update_bits(data->regmap,
|
|
MT6362_REG_CHG_EOC,
|
|
MT6362_MASK_TE,
|
|
en ? 0xff : 0);
|
|
}
|
|
|
|
static int mt6362_run_pump_express(struct mt6362_chg_data *data,
|
|
enum pe_sel pe_sel)
|
|
{
|
|
long timeout, pe_timeout = pe_sel ? 1400 : 2800;
|
|
int ret;
|
|
|
|
dev_info(data->dev, "%s\n", __func__);
|
|
ret = mt6362_set_aicr(data->chg_dev, 800000);
|
|
if (ret < 0)
|
|
return ret;
|
|
ret = mt6362_set_ichg(data->chg_dev, 2000000);
|
|
if (ret < 0)
|
|
return ret;
|
|
ret = mt6362_enable_charging(data->chg_dev, true);
|
|
if (ret < 0)
|
|
return ret;
|
|
/* switch pe10/pe20 select */
|
|
ret = regmap_update_bits(data->regmap, MT6362_REG_CHG_PUMPX,
|
|
MT6362_MASK_PE_SEL, pe_sel ? 0xff : 0);
|
|
if (ret < 0)
|
|
return ret;
|
|
ret = regmap_update_bits(data->regmap, MT6362_REG_CHG_PUMPX,
|
|
MT6362_MASK_PE_EN, 0x00);
|
|
if (ret < 0)
|
|
return ret;
|
|
ret = regmap_update_bits(data->regmap, MT6362_REG_CHG_PUMPX,
|
|
MT6362_MASK_PE_EN, 0xff);
|
|
if (ret < 0)
|
|
return ret;
|
|
reinit_completion(&data->pe_done);
|
|
timeout = wait_for_completion_interruptible_timeout(
|
|
&data->pe_done, msecs_to_jiffies(pe_timeout));
|
|
if (timeout == 0)
|
|
ret = -ETIMEDOUT;
|
|
else if (timeout < 0)
|
|
ret = -EINTR;
|
|
else
|
|
ret = 0;
|
|
if (ret < 0)
|
|
dev_err(data->dev,
|
|
"%s: wait pumpx timeout, ret = %d\n", __func__, ret);
|
|
return ret;
|
|
}
|
|
|
|
static int mt6362_set_pep_current_pattern(struct charger_device *chg_dev,
|
|
bool is_inc)
|
|
{
|
|
struct mt6362_chg_data *data = charger_get_data(chg_dev);
|
|
int ret;
|
|
|
|
dev_dbg(data->dev, "%s: pe1.0 pump up = %d\n", __func__, is_inc);
|
|
mutex_lock(&data->pe_lock);
|
|
/* Set Pump Up/Down */
|
|
ret = regmap_update_bits(data->regmap,
|
|
MT6362_REG_CHG_PUMPX,
|
|
MT6362_MASK_PE10_INC,
|
|
is_inc ? 0xff : 0);
|
|
if (ret < 0) {
|
|
dev_err(data->dev, "%s: set pe10 up/down fail\n", __func__);
|
|
goto out;
|
|
}
|
|
ret = mt6362_run_pump_express(data, MT6362_PE_SEL_10);
|
|
if (ret < 0)
|
|
dev_err(data->dev, "%s: run pump express fail\n", __func__);
|
|
out:
|
|
mutex_unlock(&data->pe_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int mt6362_set_pep20_efficiency_table(struct charger_device *chg_dev)
|
|
{
|
|
/* TODO: check table is the same or not */
|
|
struct charger_manager *chg_mgr = NULL;
|
|
|
|
chg_mgr = charger_dev_get_drvdata(chg_dev);
|
|
if (!chg_mgr)
|
|
return -EINVAL;
|
|
|
|
chg_mgr->pe2.profile[0].vchr = 8000000;
|
|
chg_mgr->pe2.profile[1].vchr = 8000000;
|
|
chg_mgr->pe2.profile[2].vchr = 8000000;
|
|
chg_mgr->pe2.profile[3].vchr = 8500000;
|
|
chg_mgr->pe2.profile[4].vchr = 8500000;
|
|
chg_mgr->pe2.profile[5].vchr = 8500000;
|
|
chg_mgr->pe2.profile[6].vchr = 9000000;
|
|
chg_mgr->pe2.profile[7].vchr = 9000000;
|
|
chg_mgr->pe2.profile[8].vchr = 9500000;
|
|
chg_mgr->pe2.profile[9].vchr = 9500000;
|
|
return 0;
|
|
}
|
|
|
|
static int mt6362_set_pep20_current_pattern(struct charger_device *chg_dev,
|
|
u32 uV)
|
|
{
|
|
struct mt6362_chg_data *data = charger_get_data(chg_dev);
|
|
int ret;
|
|
u8 sel;
|
|
|
|
dev_dbg(data->dev, "%s: pep2.0 = %d\n", __func__, uV);
|
|
mutex_lock(&data->pe_lock);
|
|
sel = mt6362_map_reg_sel(uV, MT6362_PE20_MIN, MT6362_PE20_MAX,
|
|
MT6362_PE20_STEP);
|
|
/* Set Voltage */
|
|
ret = regmap_update_bits(data->regmap,
|
|
MT6362_REG_CHG_PUMPX,
|
|
MT6362_MASK_PE20_CODE,
|
|
sel << MT6362_SHFT_PE20_CODE);
|
|
if (ret < 0) {
|
|
dev_err(data->dev, "%s: set pumpx voltage fail\n", __func__);
|
|
goto out;
|
|
}
|
|
ret = mt6362_run_pump_express(data, MT6362_PE_SEL_20);
|
|
if (ret < 0)
|
|
dev_err(data->dev, "%s: run pump express fail\n", __func__);
|
|
out:
|
|
mutex_unlock(&data->pe_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int mt6362_reset_ta(struct charger_device *chg_dev)
|
|
{
|
|
struct mt6362_chg_data *data = charger_get_data(chg_dev);
|
|
int ret;
|
|
|
|
dev_dbg(data->dev, "%s\n", __func__);
|
|
ret = mt6362_set_mivr(chg_dev, 4600000);
|
|
if (ret < 0)
|
|
return ret;
|
|
ret = mt6362_set_aicr(chg_dev, 100000);
|
|
if (ret < 0)
|
|
return ret;
|
|
msleep(250);
|
|
return mt6362_set_aicr(chg_dev, 500000);
|
|
}
|
|
|
|
static int mt6362_enable_cable_drop_comp(struct charger_device *chg_dev,
|
|
bool en)
|
|
{
|
|
struct mt6362_chg_data *data = charger_get_data(chg_dev);
|
|
int ret;
|
|
|
|
dev_info(data->dev, "%s: en = %d\n", __func__, en);
|
|
mutex_lock(&data->pe_lock);
|
|
if (en)
|
|
return 0;
|
|
/* Set disable cable drop compensation */
|
|
ret = regmap_update_bits(data->regmap,
|
|
MT6362_REG_CHG_PUMPX,
|
|
MT6362_MASK_PE20_CODE,
|
|
0x1F << MT6362_SHFT_PE20_CODE);
|
|
if (ret < 0) {
|
|
dev_err(data->dev,
|
|
"%s: set dis cable drop comp fail\n", __func__);
|
|
goto out;
|
|
}
|
|
ret = mt6362_run_pump_express(data, MT6362_PE_SEL_20);
|
|
if (ret < 0)
|
|
dev_err(data->dev, "%s: run pump express fail\n", __func__);
|
|
out:
|
|
mutex_unlock(&data->pe_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int mt6362_get_aicc(struct mt6362_chg_data *data, u32 *aicc_val)
|
|
{
|
|
int ret;
|
|
u32 regval = 0;
|
|
u8 aicc_sel;
|
|
|
|
ret = regmap_read(data->regmap, MT6362_REG_CHG_AICC2, ®val);
|
|
if (ret < 0)
|
|
return ret;
|
|
aicc_sel = (regval & MT6362_MASK_AICC_RPT) >> MT6362_SHFT_AICC_RPT;
|
|
*aicc_val = mt6362_map_real_val(aicc_sel,
|
|
MT6362_AICC_MIN,
|
|
MT6362_AICC_MAX,
|
|
MT6362_CC_STEP);
|
|
return 0;
|
|
}
|
|
|
|
static inline int mt6362_post_aicc_measure(struct charger_device *chg_dev,
|
|
u32 start, u32 stop, u32 step,
|
|
u32 *measure)
|
|
{
|
|
struct mt6362_chg_data *data = charger_get_data(chg_dev);
|
|
int cur, ret;
|
|
bool mivr_loop;
|
|
|
|
mt_dbg(data->dev,
|
|
"%s: post_aicc = (%d, %d, %d)\n", __func__, start, stop, step);
|
|
for (cur = start; cur < (stop + step); cur += step) {
|
|
/* set_aicr to cur */
|
|
ret = mt6362_set_aicr(chg_dev, cur + step);
|
|
if (ret < 0)
|
|
return ret;
|
|
usleep_range(150, 200);
|
|
ret = mt6362_get_mivr_state(chg_dev, &mivr_loop);
|
|
if (ret < 0)
|
|
return ret;
|
|
/* read mivr stat */
|
|
if (mivr_loop)
|
|
break;
|
|
}
|
|
*measure = cur;
|
|
return 0;
|
|
}
|
|
|
|
static int mt6362_run_aicc(struct charger_device *chg_dev, u32 *uA)
|
|
{
|
|
struct mt6362_chg_data *data = charger_get_data(chg_dev);
|
|
struct mt6362_chg_platform_data *pdata = dev_get_platdata(data->dev);
|
|
int ret;
|
|
u32 aicc_val, aicr_val;
|
|
bool mivr_loop;
|
|
long timeout;
|
|
|
|
/* check MIVR stat is act */
|
|
ret = mt6362_get_mivr_state(chg_dev, &mivr_loop);
|
|
if (ret < 0)
|
|
return ret;
|
|
if (!mivr_loop) {
|
|
mt_dbg(data->dev, "%s: mivr loop not act\n", __func__);
|
|
return ret;
|
|
}
|
|
|
|
mt_dbg(data->dev,
|
|
"%s: aicc_oneshot = %d\n", __func__, pdata->aicc_oneshot);
|
|
/* Auto run AICC */
|
|
if (!pdata->aicc_oneshot) {
|
|
if (!try_wait_for_completion(&data->aicc_done)) {
|
|
dev_info(data->dev, "%s: aicc is not act\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
/* get aicc result */
|
|
ret = mt6362_get_aicc(data, &aicc_val);
|
|
if (ret < 0) {
|
|
dev_err(data->dev,
|
|
"%s: get aicc fail\n", __func__);
|
|
return ret;
|
|
}
|
|
*uA = aicc_val;
|
|
reinit_completion(&data->aicc_done);
|
|
return ret;
|
|
}
|
|
|
|
/* Run AICC measure oneshot */
|
|
mutex_lock(&data->pe_lock);
|
|
ret = regmap_update_bits(data->regmap, MT6362_REG_CHG_AICC1,
|
|
MT6362_MASK_AICC_EN, 0xff);
|
|
if (ret < 0)
|
|
goto out;
|
|
/* Clear AICC measurement IRQ */
|
|
reinit_completion(&data->aicc_done);
|
|
timeout = wait_for_completion_interruptible_timeout(
|
|
&data->aicc_done, msecs_to_jiffies(9000));
|
|
if (timeout == 0)
|
|
ret = -ETIMEDOUT;
|
|
else if (timeout < 0)
|
|
ret = -EINTR;
|
|
else
|
|
ret = 0;
|
|
if (ret < 0) {
|
|
dev_err(data->dev,
|
|
"%s: wait AICC time out(%d)\n", __func__, ret);
|
|
goto out;
|
|
}
|
|
/* get aicc_result */
|
|
ret = mt6362_get_aicc(data, &aicc_val);
|
|
if (ret < 0) {
|
|
dev_err(data->dev, "%s: get aicc result fail\n", __func__);
|
|
goto out;
|
|
}
|
|
|
|
/* post aicc */
|
|
if (!pdata->post_aicc)
|
|
goto skip_post_aicc;
|
|
|
|
dev_info(data->dev, "%s: aicc pre val = %d\n", __func__, aicc_val);
|
|
ret = mt6362_get_aicr(chg_dev, &aicr_val);
|
|
if (ret < 0) {
|
|
dev_err(data->dev, "%s: get aicr fail\n", __func__);
|
|
goto out;
|
|
}
|
|
ret = mt6362_set_aicr(chg_dev, aicc_val);
|
|
if (ret < 0) {
|
|
dev_err(data->dev, "%s: set aicr fail\n", __func__);
|
|
goto out;
|
|
}
|
|
ret = regmap_update_bits(data->regmap, MT6362_REG_CHG_AICC1,
|
|
MT6362_MASK_AICC_EN, 0);
|
|
if (ret < 0)
|
|
goto out;
|
|
/* always start/end aicc_val/aicc_val+post_aicc_thr */
|
|
ret = mt6362_post_aicc_measure(chg_dev, aicc_val,
|
|
aicc_val + pdata->post_aicc_thr,
|
|
MT6362_AICR_STEP, &aicc_val);
|
|
if (ret < 0)
|
|
goto out;
|
|
|
|
ret = mt6362_set_aicr(chg_dev, aicc_val);
|
|
if (ret < 0) {
|
|
dev_err(data->dev, "%s: set aicr fail\n", __func__);
|
|
goto out;
|
|
}
|
|
dev_info(data->dev, "%s: aicc post val = %d\n", __func__, aicc_val);
|
|
skip_post_aicc:
|
|
*uA = aicc_val;
|
|
out:
|
|
/* Clear EN_AICC */
|
|
ret = regmap_update_bits(data->regmap, MT6362_REG_CHG_AICC1,
|
|
MT6362_MASK_AICC_EN, 0);
|
|
mutex_unlock(&data->pe_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int mt6362_enable_power_path(struct charger_device *chg_dev, bool en)
|
|
{
|
|
struct mt6362_chg_data *data = charger_get_data(chg_dev);
|
|
|
|
dev_info(data->dev, "%s: en = %d\n", __func__, en);
|
|
return regmap_update_bits(data->regmap,
|
|
MT6362_REG_CHG_TOP1,
|
|
MT6362_MASK_CHG_BUCK_EN,
|
|
en ? 0xff : 0);
|
|
}
|
|
|
|
static int mt6362_is_power_path_enabled(struct charger_device *chg_dev,
|
|
bool *en)
|
|
{
|
|
struct mt6362_chg_data *data = charger_get_data(chg_dev);
|
|
int ret;
|
|
u32 regval;
|
|
|
|
ret = regmap_read(data->regmap, MT6362_REG_CHG_TOP1, ®val);
|
|
if (ret < 0)
|
|
return ret;
|
|
*en = (regval & MT6362_MASK_CHG_BUCK_EN) ? true : false;
|
|
dev_info(data->dev, "%s: en = %d\n", __func__, *en);
|
|
return 0;
|
|
}
|
|
|
|
static int mt6362_enable_safety_timer(struct charger_device *chg_dev, bool en)
|
|
{
|
|
struct mt6362_chg_data *data = charger_get_data(chg_dev);
|
|
|
|
dev_info(data->dev, "%s: en = %d\n", __func__, en);
|
|
return regmap_update_bits(data->regmap,
|
|
MT6362_REG_CHG_TMR,
|
|
MT6362_MASK_CHG_TMR_EN,
|
|
en ? 0xff : 0);
|
|
}
|
|
|
|
static int mt6362_is_safety_timer_enabled(struct charger_device *chg_dev,
|
|
bool *en)
|
|
{
|
|
struct mt6362_chg_data *data = charger_get_data(chg_dev);
|
|
int ret;
|
|
u32 regval;
|
|
|
|
ret = regmap_read(data->regmap, MT6362_REG_CHG_TMR, ®val);
|
|
if (ret < 0)
|
|
return ret;
|
|
*en = (regval & MT6362_MASK_CHG_TMR_EN) ? true : false;
|
|
dev_info(data->dev, "%s: en = %d\n", __func__, *en);
|
|
return 0;
|
|
}
|
|
|
|
static int mt6362_set_otg_current_limit(struct charger_device *chg_dev, u32 uA)
|
|
{
|
|
struct mt6362_chg_data *data = charger_get_data(chg_dev);
|
|
int i;
|
|
|
|
/* Set higher OC threshold protect */
|
|
for (i = 0; i < ARRAY_SIZE(otg_cc_table); i++) {
|
|
if (uA <= otg_cc_table[i])
|
|
break;
|
|
}
|
|
if (i == ARRAY_SIZE(otg_cc_table))
|
|
i = ARRAY_SIZE(otg_cc_table) - 1;
|
|
dev_info(data->dev,
|
|
"%s: select otg_cc = %d\n", __func__, otg_cc_table[i]);
|
|
return regmap_update_bits(data->regmap,
|
|
MT6362_REG_OTG_C,
|
|
MT6362_MASK_OTG_CC,
|
|
i << MT6362_SHFT_OTG_CC);
|
|
}
|
|
|
|
static int mt6362_enable_otg(struct charger_device *chg_dev, bool en)
|
|
{
|
|
struct mt6362_chg_data *data = charger_get_data(chg_dev);
|
|
int ret;
|
|
|
|
dev_info(data->dev, "%s: en = %d\n", __func__, en);
|
|
ret = mt6362_enable_otg_parameter(data, en);
|
|
if (ret < 0)
|
|
return ret;
|
|
return regmap_update_bits(data->regmap,
|
|
MT6362_REG_CHG_TOP1,
|
|
MT6362_MASK_OTG_EN,
|
|
en ? 0xff : 0);
|
|
}
|
|
|
|
static int mt6362_enable_discharge(struct charger_device *chg_dev, bool en)
|
|
{
|
|
struct mt6362_chg_data *data = charger_get_data(chg_dev);
|
|
const int dischg_retry_cnt = 3;
|
|
bool is_dischg = true;
|
|
int i, ret = 0;
|
|
u32 regval;
|
|
|
|
dev_info(data->dev, "%s: en = %d\n", __func__, en);
|
|
ret = mt6362_enable_hidden_mode(chg_dev, true);
|
|
if (ret < 0)
|
|
return ret;
|
|
/* Set bit6 of reg[0x3B] to 1/0 to enable/disable discharging */
|
|
ret = regmap_update_bits(data->regmap, MT6362_REG_CHG_HD_TOP1,
|
|
MT6362_MASK_DISCHG, en ? 0xff : 0);
|
|
if (ret < 0) {
|
|
dev_err(data->dev, "%s: fail, en = %d\n", __func__, en);
|
|
goto out;
|
|
}
|
|
|
|
if (!en) {
|
|
for (i = 0; i < dischg_retry_cnt; i++) {
|
|
ret = regmap_read(data->regmap,
|
|
MT6362_REG_CHG_HD_TOP1, ®val);
|
|
is_dischg = (ret & MT6362_MASK_DISCHG) ? true : false;
|
|
if (!is_dischg)
|
|
break;
|
|
ret = regmap_update_bits(data->regmap,
|
|
MT6362_REG_CHG_HD_TOP1,
|
|
MT6362_MASK_DISCHG, 0);
|
|
if (ret < 0)
|
|
dev_err(data->dev,
|
|
"%s: disable dischg failed\n",
|
|
__func__);
|
|
}
|
|
if (i == dischg_retry_cnt)
|
|
dev_err(data->dev, "%s: dischg failed\n", __func__);
|
|
}
|
|
out:
|
|
mt6362_enable_hidden_mode(chg_dev, false);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int mt6362_enable_chg_type_det(struct charger_device *chg_dev, bool en)
|
|
{
|
|
struct mt6362_chg_data *data = charger_get_data(chg_dev);
|
|
|
|
dev_info(data->dev, "%s: en = %d\n", __func__, en);
|
|
#if defined(CONFIG_MTK_EXTERNAL_CHARGER_TYPE_DETECT)\
|
|
&& defined(CONFIG_TCPC_CLASS)
|
|
mt6362_run_bc12_thread(data, en);
|
|
#endif /* CONFIG_MTK_EXTERNAL_CHARGER_TYPE_DETECT && CONFIG_TCPC_CLASS */
|
|
return 0;
|
|
}
|
|
|
|
static int mt6362_get_adc(struct charger_device *chg_dev, u32 chan,
|
|
int *min, int *max)
|
|
{
|
|
struct mt6362_chg_data *data = charger_get_data(chg_dev);
|
|
int ret, channel;
|
|
|
|
switch (chan) {
|
|
case ADC_CHANNEL_VBUS:
|
|
channel = MT6362_CHG_ADCCH_CHGVINDIV5;
|
|
break;
|
|
case ADC_CHANNEL_VSYS:
|
|
channel = MT6362_CHG_ADCCH_VSYS;
|
|
break;
|
|
case ADC_CHANNEL_VBAT:
|
|
channel = MT6362_CHG_ADCCH_VBAT;
|
|
break;
|
|
case ADC_CHANNEL_IBUS:
|
|
channel = MT6362_CHG_ADCCH_IBUS;
|
|
break;
|
|
case ADC_CHANNEL_IBAT:
|
|
channel = MT6362_CHG_ADCCH_IBAT;
|
|
break;
|
|
case ADC_CHANNEL_TEMP_JC:
|
|
channel = MT6362_CHG_ADCCH_TEMP_JC;
|
|
break;
|
|
default:
|
|
return -ENOTSUPP;
|
|
}
|
|
mt_dbg(data->dev, "%s: read channel(%d)\n", __func__, channel);
|
|
ret = iio_read_channel_processed(&data->iio_ch[channel], min);
|
|
if (ret < 0) {
|
|
dev_info(data->dev, "%s: fail(%d)\n", __func__, ret);
|
|
return ret;
|
|
}
|
|
*max = *min;
|
|
mt_dbg(data->dev, "%s: chan[%s] = %d\n",
|
|
__func__, mt6362_adcch_list[channel], *min);
|
|
return 0;
|
|
}
|
|
|
|
static int mt6362_get_vbus(struct charger_device *chg_dev, u32 *vbus)
|
|
{
|
|
struct mt6362_chg_data *data = charger_get_data(chg_dev);
|
|
|
|
mt_dbg(data->dev, "%s\n", __func__);
|
|
return mt6362_get_adc(chg_dev, ADC_CHANNEL_VBUS, vbus, vbus);
|
|
}
|
|
|
|
static int mt6362_get_ibus(struct charger_device *chg_dev, u32 *ibus)
|
|
{
|
|
struct mt6362_chg_data *data = charger_get_data(chg_dev);
|
|
|
|
mt_dbg(data->dev, "%s\n", __func__);
|
|
return mt6362_get_adc(chg_dev, ADC_CHANNEL_IBUS, ibus, ibus);
|
|
}
|
|
|
|
static int mt6362_get_ibat(struct charger_device *chg_dev, u32 *ibat)
|
|
{
|
|
struct mt6362_chg_data *data = charger_get_data(chg_dev);
|
|
|
|
mt_dbg(data->dev, "%s\n", __func__);
|
|
return mt6362_get_adc(chg_dev, ADC_CHANNEL_IBAT, ibat, ibat);
|
|
}
|
|
|
|
static int mt6362_get_tchg(struct charger_device *chg_dev,
|
|
int *tchg_min, int *tchg_max)
|
|
{
|
|
struct mt6362_chg_data *data = charger_get_data(chg_dev);
|
|
int temp_jc = 0, ret = 0, retry_cnt = 3;
|
|
|
|
mt_dbg(data->dev, "%s\n", __func__);
|
|
/* temp abnormal Workaround */
|
|
do {
|
|
ret = mt6362_get_adc(chg_dev, ADC_CHANNEL_TEMP_JC,
|
|
&temp_jc, &temp_jc);
|
|
if (ret < 0) {
|
|
dev_err(data->dev,
|
|
"%s: failed, ret = %d\n", __func__, ret);
|
|
return ret;
|
|
}
|
|
} while (temp_jc >= 120 && (retry_cnt--) > 0);
|
|
mutex_lock(&data->tchg_lock);
|
|
if (temp_jc >= 120)
|
|
temp_jc = data->tchg;
|
|
else
|
|
data->tchg = temp_jc;
|
|
mutex_unlock(&data->tchg_lock);
|
|
*tchg_min = *tchg_max = temp_jc;
|
|
dev_info(data->dev, "%s: tchg = %d\n", __func__, temp_jc);
|
|
return 0;
|
|
}
|
|
|
|
static int mt6362_kick_wdt(struct charger_device *chg_dev)
|
|
{
|
|
struct mt6362_chg_data *data = charger_get_data(chg_dev);
|
|
|
|
mt_dbg(data->dev, "%s\n", __func__);
|
|
return regmap_update_bits(data->regmap, MT6362_REG_CHG_WDT,
|
|
MT6362_MASK_WDT_CNT_RST, 0xff);
|
|
}
|
|
|
|
static int mt6362_safety_check(struct charger_device *chg_dev, u32 polling_ieoc)
|
|
{
|
|
struct mt6362_chg_data *data = charger_get_data(chg_dev);
|
|
int ret, ibat = 0;
|
|
static int eoc_cnt;
|
|
|
|
mt_dbg(data->dev, "%s\n", __func__);
|
|
ret = mt6362_get_ibat(chg_dev, &ibat);
|
|
if (ret < 0) {
|
|
dev_err(data->dev, "%s: failed(%d)\n", __func__, ret);
|
|
return ret;
|
|
}
|
|
|
|
if (ibat <= polling_ieoc)
|
|
eoc_cnt++;
|
|
else
|
|
eoc_cnt = 0;
|
|
/* If ibat is less than polling_ieoc for 3 times, trigger EOC event */
|
|
if (eoc_cnt == 3) {
|
|
dev_info(data->dev, "%s: polling_ieoc = %d, ibat = %d\n",
|
|
__func__, polling_ieoc, ibat);
|
|
charger_dev_notify(data->chg_dev, CHARGER_DEV_NOTIFY_EOC);
|
|
eoc_cnt = 0;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int mt6362_reset_eoc_state(struct charger_device *chg_dev)
|
|
{
|
|
struct mt6362_chg_data *data = charger_get_data(chg_dev);
|
|
|
|
dev_info(data->dev, "%s\n", __func__);
|
|
return regmap_update_bits(data->regmap,
|
|
MT6362_REG_CHG_EOC,
|
|
MT6362_MASK_EOC_RST,
|
|
0xff);
|
|
}
|
|
|
|
static int mt6362_is_charging_done(struct charger_device *chg_dev, bool *done)
|
|
{
|
|
struct mt6362_chg_data *data = charger_get_data(chg_dev);
|
|
enum mt6362_ic_stat ic_stat;
|
|
int ret;
|
|
|
|
ret = mt6362_get_charging_status(data, &ic_stat);
|
|
if (ret < 0)
|
|
return ret;
|
|
*done = (ic_stat == MT6362_STAT_CHG_DONE) ? true : false;
|
|
mt_dbg(data->dev, "%s: done = %d\n", __func__, *done);
|
|
return 0;
|
|
}
|
|
|
|
static int mt6362_get_zcv(struct charger_device *chg_dev, u32 *uV)
|
|
{
|
|
struct mt6362_chg_data *data = charger_get_data(chg_dev);
|
|
|
|
dev_info(data->dev, "%s: zcv = %dmV\n", __func__, data->zcv / 1000);
|
|
*uV = data->zcv;
|
|
return 0;
|
|
}
|
|
|
|
static int mt6362_dump_registers(struct charger_device *chg_dev)
|
|
{
|
|
struct mt6362_chg_data *data = charger_get_data(chg_dev);
|
|
int i, ret;
|
|
u32 ichg = 0, aicr = 0, mivr = 0, cv = 0, ieoc = 0;
|
|
enum mt6362_ic_stat ic_stat = MT6362_STAT_HZ;
|
|
bool chg_en = false;
|
|
u32 adc_vals[5];
|
|
u8 chg_stat[2], chg_top[2];
|
|
u32 chg_eoc = 0;
|
|
|
|
ret = mt6362_kick_wdt(chg_dev);
|
|
if (ret < 0) {
|
|
dev_notice(data->dev, "%s: kick wdt fail\n", __func__);
|
|
return ret;
|
|
}
|
|
ret = mt6362_get_ichg(chg_dev, &ichg);
|
|
ret |= mt6362_get_aicr(chg_dev, &aicr);
|
|
ret |= mt6362_get_mivr(chg_dev, &mivr);
|
|
ret |= mt6362_get_cv(chg_dev, &cv);
|
|
ret |= mt6362_get_ieoc(data, &ieoc);
|
|
ret |= mt6362_get_charging_status(data, &ic_stat);
|
|
ret |= mt6362_is_charger_enabled(data, &chg_en);
|
|
if (ret < 0) {
|
|
dev_err(data->dev, "%s: get chg setting fail\n", __func__);
|
|
return ret;
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(adc_vals); i++) {
|
|
ret = iio_read_channel_processed(&data->iio_ch[i],
|
|
&adc_vals[i]);
|
|
if (ret < 0) {
|
|
dev_err(data->dev,
|
|
"%s: read [%s] adc fail(%d)\n",
|
|
__func__, mt6362_adcch_list[i], ret);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
ret = regmap_bulk_read(data->regmap, MT6362_REG_CHG_STAT0, chg_stat, 2);
|
|
if (ret < 0)
|
|
return ret;
|
|
ret = regmap_bulk_read(data->regmap, MT6362_REG_CHG_TOP1, chg_top, 2);
|
|
if (ret < 0)
|
|
return ret;
|
|
ret = regmap_read(data->regmap, MT6362_REG_CHG_EOC, &chg_eoc);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
dev_info(data->dev,
|
|
"%s: ICHG = %dmA, AICR = %dmA, MIVR = %dmV, IEOC = %dmA, CV = %dmV\n",
|
|
__func__, ichg / 1000, aicr / 1000, mivr / 1000, ieoc / 1000,
|
|
cv / 1000);
|
|
dev_info(data->dev,
|
|
"%s: VBUS = %dmV, IBUS = %dmA, VSYS = %dmV, VBAT = %dmV, IBAT = %dmA\n",
|
|
__func__,
|
|
adc_vals[MT6362_ADCCH_CHGVINDIV5] / 1000,
|
|
adc_vals[MT6362_ADCCH_IBUS] / 1000,
|
|
adc_vals[MT6362_ADCCH_VSYS] / 1000,
|
|
adc_vals[MT6362_ADCCH_VBAT] / 1000,
|
|
adc_vals[MT6362_ADCCH_IBAT] / 1000);
|
|
dev_info(data->dev, "%s: CHG_EN = %d, CHG_STATUS = %s, CHG_STAT0 = 0x%02X, CHG_STAT1 = 0x%02X\n",
|
|
__func__, chg_en, mt6362_ic_stat_list[ic_stat],
|
|
chg_stat[0], chg_stat[1]);
|
|
dev_info(data->dev, "%s: CHG_TOP1 = 0x%02X, CHG_TOP2 = 0x%02X, CHG_EOC = 0x%02X\n",
|
|
__func__, chg_top[0], chg_top[1], chg_eoc);
|
|
return 0;
|
|
}
|
|
|
|
static int mt6362_do_event(struct charger_device *chg_dev, u32 event, u32 args)
|
|
{
|
|
struct mt6362_chg_data *data = charger_get_data(chg_dev);
|
|
|
|
dev_info(data->dev, "%s\n", __func__);
|
|
switch (event) {
|
|
case EVENT_EOC:
|
|
charger_dev_notify(chg_dev, CHARGER_DEV_NOTIFY_EOC);
|
|
break;
|
|
case EVENT_RECHARGE:
|
|
charger_dev_notify(chg_dev, CHARGER_DEV_NOTIFY_RECHG);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int mt6362_handle_bleed_discharge(struct mt6362_chg_data *data)
|
|
{
|
|
int ret;
|
|
|
|
/* Decrease UUG leakage to vbus */
|
|
if (data->bd_flag) {
|
|
ret = mt6362_enable_discharge(data->chg_dev, true);
|
|
if (ret < 0)
|
|
return ret;
|
|
return regmap_update_bits(data->regmap,
|
|
MT6362_PD_I2C_TO_RST_CTRL,
|
|
MT6362_MASK_BLEEDDIS_EN, 0xff);
|
|
} else {
|
|
ret = mt6362_enable_discharge(data->chg_dev, false);
|
|
if (ret < 0)
|
|
return ret;
|
|
return regmap_update_bits(data->regmap,
|
|
MT6362_PD_I2C_TO_RST_CTRL,
|
|
MT6362_MASK_BLEEDDIS_EN,
|
|
data->pwr_rdy ? 0 : 0xff);
|
|
}
|
|
}
|
|
|
|
static int mt6362_enable_bleed_discharge(struct charger_device *chg_dev,
|
|
bool en)
|
|
{
|
|
struct mt6362_chg_data *data = charger_get_data(chg_dev);
|
|
int ret = 0;
|
|
|
|
dev_info(data->dev, "%s: en = %d\n", __func__, en);
|
|
mutex_lock(&data->bd_lock);
|
|
if (en == data->bd_flag)
|
|
goto out;
|
|
data->bd_flag = en;
|
|
|
|
if (en) {
|
|
ret = mt6362_get_mivr(chg_dev, &data->bd_mivr);
|
|
if (ret < 0)
|
|
goto out;
|
|
ret = __mt6362_set_mivr(data, MT6362_MIVR_MAX);
|
|
if (ret < 0)
|
|
goto out;
|
|
}
|
|
|
|
ret = mt6362_enable_otg_parameter(data, en);
|
|
if (ret < 0)
|
|
goto out;
|
|
ret = mt6362_handle_bleed_discharge(data);
|
|
if (ret < 0)
|
|
goto out;
|
|
if (!en) {
|
|
ret = __mt6362_set_mivr(data, data->bd_mivr);
|
|
if (ret < 0)
|
|
goto out;
|
|
}
|
|
out:
|
|
mutex_unlock(&data->bd_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int mt6362_plug_in(struct charger_device *chg_dev)
|
|
{
|
|
struct mt6362_chg_data *data = charger_get_data(chg_dev);
|
|
int ret;
|
|
|
|
dev_info(data->dev, "%s\n", __func__);
|
|
ret = mt6362_enable_wdt(data, true);
|
|
if (ret < 0) {
|
|
dev_err(data->dev, "%s: en wdt failed\n", __func__);
|
|
return ret;
|
|
}
|
|
ret = mt6362_enable_te(chg_dev, true);
|
|
if (ret < 0)
|
|
dev_err(data->dev, "%s: en te failed\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
static int mt6362_plug_out(struct charger_device *chg_dev)
|
|
{
|
|
struct mt6362_chg_data *data = charger_get_data(chg_dev);
|
|
int ret;
|
|
|
|
dev_info(data->dev, "%s\n", __func__);
|
|
ret = mt6362_enable_wdt(data, false);
|
|
if (ret < 0) {
|
|
dev_err(data->dev, "%s: disable wdt failed\n", __func__);
|
|
return ret;
|
|
}
|
|
ret = mt6362_enable_te(chg_dev, false);
|
|
if (ret < 0)
|
|
dev_err(data->dev, "%s: disable te failed\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
static const struct charger_ops mt6362_chg_ops = {
|
|
/* cable plug in/out */
|
|
.plug_in = mt6362_plug_in,
|
|
.plug_out = mt6362_plug_out,
|
|
/* enable */
|
|
.enable = mt6362_enable_charging,
|
|
/* charging current */
|
|
.set_charging_current = mt6362_set_ichg,
|
|
.get_charging_current = mt6362_get_ichg,
|
|
.get_min_charging_current = mt6362_get_min_ichg,
|
|
/* charging voltage */
|
|
.set_constant_voltage = mt6362_set_cv,
|
|
.get_constant_voltage = mt6362_get_cv,
|
|
/* charging input current */
|
|
.set_input_current = mt6362_set_aicr,
|
|
.get_input_current = mt6362_get_aicr,
|
|
.get_min_input_current = mt6362_get_min_aicr,
|
|
/* set termination current */
|
|
.set_eoc_current = mt6362_set_ieoc,
|
|
/* charging mivr */
|
|
.set_mivr = mt6362_set_mivr,
|
|
.get_mivr = mt6362_get_mivr,
|
|
.get_mivr_state = mt6362_get_mivr_state,
|
|
/* charing termination */
|
|
.enable_termination = mt6362_enable_te,
|
|
/* PE+/PE+20 */
|
|
.send_ta_current_pattern = mt6362_set_pep_current_pattern,
|
|
.set_pe20_efficiency_table = mt6362_set_pep20_efficiency_table,
|
|
.send_ta20_current_pattern = mt6362_set_pep20_current_pattern,
|
|
.reset_ta = mt6362_reset_ta,
|
|
.enable_cable_drop_comp = mt6362_enable_cable_drop_comp,
|
|
.run_aicl = mt6362_run_aicc,
|
|
/* Power path */
|
|
.enable_powerpath = mt6362_enable_power_path,
|
|
.is_powerpath_enabled = mt6362_is_power_path_enabled,
|
|
/* safety timer */
|
|
.enable_safety_timer = mt6362_enable_safety_timer,
|
|
.is_safety_timer_enabled = mt6362_is_safety_timer_enabled,
|
|
/* OTG */
|
|
.set_boost_current_limit = mt6362_set_otg_current_limit,
|
|
.enable_otg = mt6362_enable_otg,
|
|
.enable_discharge = mt6362_enable_discharge,
|
|
/* Charger type detection */
|
|
.enable_chg_type_det = mt6362_enable_chg_type_det,
|
|
/* ADC */
|
|
.get_adc = mt6362_get_adc,
|
|
.get_vbus_adc = mt6362_get_vbus,
|
|
.get_ibus_adc = mt6362_get_ibus,
|
|
.get_ibat_adc = mt6362_get_ibat,
|
|
.get_tchg_adc = mt6362_get_tchg,
|
|
/* kick wdt */
|
|
.kick_wdt = mt6362_kick_wdt,
|
|
/* misc */
|
|
.safety_check = mt6362_safety_check,
|
|
.reset_eoc_state = mt6362_reset_eoc_state,
|
|
.is_charging_done = mt6362_is_charging_done,
|
|
.get_zcv = mt6362_get_zcv,
|
|
.dump_registers = mt6362_dump_registers,
|
|
/* event */
|
|
.event = mt6362_do_event,
|
|
/* Workaround */
|
|
.enable_bleed_discharge = mt6362_enable_bleed_discharge,
|
|
};
|
|
|
|
static inline int mt6362_set_shipping_mode(struct mt6362_chg_data *data)
|
|
{
|
|
int ret;
|
|
|
|
ret = regmap_update_bits(data->regmap, MT6362_REG_CORE_CTRL2,
|
|
MT6362_MASK_SHIP_RST_DIS, 0xff);
|
|
if (ret < 0) {
|
|
dev_notice(data->dev, "%s: ship rst disable fail\n", __func__);
|
|
return ret;
|
|
}
|
|
return regmap_write(data->regmap, MT6362_REG_CHG_TOP1,
|
|
MT6362_MASK_BATFET_DIS);
|
|
}
|
|
|
|
static ssize_t shipping_mode_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct mt6362_chg_data *data = dev_get_drvdata(dev);
|
|
int32_t tmp = 0;
|
|
int ret = 0;
|
|
|
|
if (kstrtoint(buf, 10, &tmp) < 0) {
|
|
dev_notice(dev, "parsing number fail\n");
|
|
return -EINVAL;
|
|
}
|
|
if (tmp != 5526789)
|
|
return -EINVAL;
|
|
ret = mt6362_set_shipping_mode(data);
|
|
if (ret < 0)
|
|
return ret;
|
|
return count;
|
|
}
|
|
static const DEVICE_ATTR_WO(shipping_mode);
|
|
|
|
static int mt6362_chg_init_setting(struct mt6362_chg_data *data)
|
|
{
|
|
u32 regval = 0;
|
|
int ret;
|
|
struct device *dev = NULL;
|
|
struct device_node *boot_node = NULL;
|
|
struct tag_bootmode *tag = NULL;
|
|
int boot_mode = 11;//UNKNOWN_BOOT
|
|
|
|
dev = data->dev;
|
|
if (dev != NULL) {
|
|
boot_node = of_parse_phandle(dev->of_node, "bootmode", 0);
|
|
if (!boot_node) {
|
|
chr_err("%s: failed to get boot mode phandle\n", __func__);
|
|
} else {
|
|
tag = (struct tag_bootmode *)of_get_property(boot_node,
|
|
"atag,boot", NULL);
|
|
if (!tag) {
|
|
chr_err("%s: failed to get atag,boot\n", __func__);
|
|
} else
|
|
boot_mode = tag->bootmode;
|
|
}
|
|
}
|
|
|
|
/* disable ilim en need delay 5ms */
|
|
usleep_range(5000, 6000);
|
|
ret = regmap_update_bits(data->regmap, MT6362_REG_CHG_AICR,
|
|
MT6362_MASK_ILIM_EN, 0);
|
|
if (ret < 0) {
|
|
dev_err(data->dev, "%s: disable ilim_en failed\n", __func__);
|
|
return ret;
|
|
}
|
|
if (boot_mode == META_BOOT || boot_mode == ADVMETA_BOOT) {
|
|
ret = regmap_update_bits(data->regmap,
|
|
MT6362_REG_CHG_AICR,
|
|
MT6362_MASK_AICR,
|
|
0x06 << MT6362_SHFT_AICR);
|
|
dev_info(data->dev, "%s: set aicr to 200mA in meta mode\n",
|
|
__func__);
|
|
}
|
|
/* disable wdt reduce 1mA power consumption */
|
|
ret = mt6362_enable_wdt(data, false);
|
|
if (ret < 0) {
|
|
dev_err(data->dev, "%s: disable wdt failed\n", __func__);
|
|
return ret;
|
|
}
|
|
/* Disable USB charger type detect, no matter use it or not */
|
|
ret = regmap_update_bits(data->regmap, MT6362_REG_BC12_FUNC,
|
|
MT6362_MASK_BC12_EN, 0);
|
|
if (ret < 0) {
|
|
dev_err(data->dev, "%s: disable chg type detect fail\n",
|
|
__func__);
|
|
return ret;
|
|
}
|
|
/* Disable TE, set TE when plug in/out */
|
|
ret = regmap_update_bits(data->regmap, MT6362_REG_CHG_EOC,
|
|
MT6362_MASK_TE, 0);
|
|
if (ret < 0) {
|
|
dev_err(data->dev, "%s: disable te fail\n", __func__);
|
|
return ret;
|
|
}
|
|
/* Check BATSYSUV occurred last time boot-on */
|
|
ret = regmap_read(data->regmap, MT6362_REG_CHG_TOP1, ®val);
|
|
if (ret < 0)
|
|
return ret;
|
|
if (!(regval & MT6362_MASK_PP_PG_FLAG)) {
|
|
dev_warn(data->dev, "%s: BATSYSUV occurred\n", __func__);
|
|
ret = regmap_update_bits(data->regmap, MT6362_REG_CHG_TOP1,
|
|
MT6362_MASK_PP_PG_FLAG, 0xff);
|
|
if (ret < 0)
|
|
dev_err(data->dev,
|
|
"%s: set BATSYSUV flag fail\n", __func__);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static const struct charger_properties mt6362_chg_props = {
|
|
.alias_name = "mt6362_chg",
|
|
};
|
|
|
|
struct mt6362_charger_irqt {
|
|
const char *name;
|
|
irq_handler_t irqh;
|
|
int irq;
|
|
};
|
|
|
|
#define MT6362_IRQ_DECLARE(_name) \
|
|
{\
|
|
.name = #_name,\
|
|
.irqh = mt6362_##_name##_evt_handler,\
|
|
.irq = -1,\
|
|
}
|
|
|
|
static irqreturn_t mt6362_fl_pwr_rdy_evt_handler(int irq, void *data)
|
|
{
|
|
struct mt6362_chg_data *cdata = data;
|
|
|
|
dev_info(cdata->dev, "%s\n", __func__);
|
|
mutex_lock(&cdata->bd_lock);
|
|
cdata->pwr_rdy = true;
|
|
mt6362_handle_bleed_discharge(cdata);
|
|
mutex_unlock(&cdata->bd_lock);
|
|
#if defined(CONFIG_MTK_EXTERNAL_CHARGER_TYPE_DETECT)\
|
|
&& !defined(CONFIG_TCPC_CLASS)
|
|
mt6362_run_bc12_thread(cdata, true);
|
|
#endif /* CONFIG_MTK_EXTERNAL_CHARGER_TYPE_DETECT && !CONFIG_TCPC_CLASS */
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t mt6362_fl_detach_evt_handler(int irq, void *data)
|
|
{
|
|
struct mt6362_chg_data *cdata = data;
|
|
|
|
dev_info(cdata->dev, "%s\n", __func__);
|
|
mutex_lock(&cdata->bd_lock);
|
|
cdata->pwr_rdy = false;
|
|
mt6362_handle_bleed_discharge(cdata);
|
|
mutex_unlock(&cdata->bd_lock);
|
|
#if defined(CONFIG_MTK_EXTERNAL_CHARGER_TYPE_DETECT)\
|
|
&& !defined(CONFIG_TCPC_CLASS)
|
|
mt6362_run_bc12_thread(cdata, false);
|
|
#endif /* CONFIG_MTK_EXTERNAL_CHARGER_TYPE_DETECT && !CONFIG_TCPC_CLASS */
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t mt6362_fl_vbus_ov_evt_handler(int irq, void *data)
|
|
{
|
|
struct mt6362_chg_data *cdata = data;
|
|
|
|
dev_warn(cdata->dev, "%s\n", __func__);
|
|
charger_dev_notify(cdata->chg_dev, CHARGER_DEV_NOTIFY_VBUS_OVP);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t mt6362_fl_chg_batov_evt_handler(int irq, void *data)
|
|
{
|
|
struct mt6362_chg_data *cdata = data;
|
|
|
|
dev_warn(cdata->dev, "%s\n", __func__);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t mt6362_fl_chg_sysov_evt_handler(int irq, void *data)
|
|
{
|
|
struct mt6362_chg_data *cdata = data;
|
|
|
|
dev_warn(cdata->dev, "%s\n", __func__);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t mt6362_fl_chg_tout_evt_handler(int irq, void *data)
|
|
{
|
|
struct mt6362_chg_data *cdata = data;
|
|
|
|
dev_warn(cdata->dev, "%s\n", __func__);
|
|
charger_dev_notify(cdata->chg_dev, CHARGER_DEV_NOTIFY_SAFETY_TIMEOUT);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t mt6362_fl_chg_threg_evt_handler(int irq, void *data)
|
|
{
|
|
struct mt6362_chg_data *cdata = data;
|
|
|
|
dev_warn(cdata->dev, "%s: thermal regulation\n", __func__);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static void mt6362_chg_irq_enable(const char *name, int en);
|
|
static irqreturn_t mt6362_fl_chg_mivr_evt_handler(int irq, void *data)
|
|
{
|
|
struct mt6362_chg_data *cdata = data;
|
|
|
|
dev_warn(cdata->dev, "%s\n", __func__);
|
|
mt6362_chg_irq_enable("chg_mivr_evt", 0);
|
|
atomic_inc(&cdata->mivr_cnt);
|
|
wake_up(&cdata->waitq);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t mt6362_fl_aicc_done_evt_handler(int irq, void *data)
|
|
{
|
|
struct mt6362_chg_data *cdata = data;
|
|
|
|
dev_info(cdata->dev, "%s\n", __func__);
|
|
complete(&cdata->aicc_done);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t mt6362_fl_pe_done_evt_handler(int irq, void *data)
|
|
{
|
|
struct mt6362_chg_data *cdata = data;
|
|
|
|
dev_info(cdata->dev, "%s\n", __func__);
|
|
complete(&cdata->pe_done);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t mt6362_fl_wdt_evt_handler(int irq, void *data)
|
|
{
|
|
struct mt6362_chg_data *cdata = data;
|
|
int ret;
|
|
|
|
dev_info(cdata->dev, "%s\n", __func__);
|
|
ret = mt6362_kick_wdt(cdata->chg_dev);
|
|
if (ret < 0)
|
|
dev_err(cdata->dev, "%s: kick wdt failed\n", __func__);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t mt6362_fl_bc12_dn_evt_handler(int irq, void *data)
|
|
{
|
|
struct mt6362_chg_data *cdata = data;
|
|
#ifdef CONFIG_MTK_EXTERNAL_CHARGER_TYPE_DETECT
|
|
int ret;
|
|
u32 regval = 0;
|
|
enum mt6362_chg_type port_stat;
|
|
#endif /* CONFIG_MTK_EXTERNAL_CHARGER_TYPE_DETECT */
|
|
|
|
dev_info(cdata->dev, "%s\n", __func__);
|
|
#ifdef CONFIG_MTK_EXTERNAL_CHARGER_TYPE_DETECT
|
|
mutex_lock(&cdata->bc12_lock);
|
|
if (!cdata->bc12_update) {
|
|
dev_info(cdata->dev, "%s: no need update bc12\n", __func__);
|
|
mutex_unlock(&cdata->bc12_lock);
|
|
return IRQ_HANDLED;
|
|
}
|
|
cdata->bc12_update = false;
|
|
mutex_unlock(&cdata->bc12_lock);
|
|
|
|
ret = regmap_read(cdata->regmap, MT6362_REG_BC12_STAT, ®val);
|
|
if (ret < 0)
|
|
return ret;
|
|
port_stat = (regval & MT6362_MASK_PORT_STAT) >> MT6362_SHFT_PORT_STAT;
|
|
switch (port_stat) {
|
|
case MT6362_CHG_TYPE_NO_INFO:
|
|
dev_info(cdata->dev, "%s: no information\n", __func__);
|
|
return IRQ_HANDLED;
|
|
case MT6362_CHG_TYPE_UNKNOWN_TA:
|
|
cdata->chg_type = NONSTANDARD_CHARGER;
|
|
break;
|
|
case MT6362_CHG_TYPE_SDP:
|
|
cdata->psy_desc.type = POWER_SUPPLY_TYPE_USB;
|
|
cdata->chg_type = STANDARD_HOST;
|
|
break;
|
|
case MT6362_CHG_TYPE_CDP:
|
|
cdata->psy_desc.type = POWER_SUPPLY_TYPE_USB_CDP;
|
|
cdata->chg_type = CHARGING_HOST;
|
|
break;
|
|
case MT6362_CHG_TYPE_APPLE_10W:
|
|
case MT6362_CHG_TYPE_SAMSUNG_10W:
|
|
case MT6362_CHG_TYPE_APPLE_5W:
|
|
case MT6362_CHG_TYPE_APPLE_12W:
|
|
/* fall through */
|
|
case MT6362_CHG_TYPE_DCP:
|
|
cdata->psy_desc.type = POWER_SUPPLY_TYPE_USB_DCP;
|
|
cdata->chg_type = STANDARD_CHARGER;
|
|
break;
|
|
default:
|
|
return IRQ_HANDLED;
|
|
}
|
|
if (cdata->psy_desc.type != POWER_SUPPLY_TYPE_USB_DCP)
|
|
__mt6362_enable_bc12(cdata, false);
|
|
power_supply_changed(cdata->psy);
|
|
ret = mt6362_chg_psy_changed(cdata);
|
|
if (ret < 0)
|
|
dev_err(cdata->dev, "%s: report psy online fail\n", __func__);
|
|
#endif /* CONFIG_MTK_EXTERNAL_CHARGER_TYPE_DETECT */
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static struct mt6362_charger_irqt irqts[] = {
|
|
MT6362_IRQ_DECLARE(fl_pwr_rdy),
|
|
MT6362_IRQ_DECLARE(fl_detach),
|
|
MT6362_IRQ_DECLARE(fl_vbus_ov),
|
|
MT6362_IRQ_DECLARE(fl_chg_batov),
|
|
MT6362_IRQ_DECLARE(fl_chg_sysov),
|
|
MT6362_IRQ_DECLARE(fl_chg_tout),
|
|
MT6362_IRQ_DECLARE(fl_chg_threg),
|
|
MT6362_IRQ_DECLARE(fl_chg_mivr),
|
|
MT6362_IRQ_DECLARE(fl_aicc_done),
|
|
MT6362_IRQ_DECLARE(fl_pe_done),
|
|
MT6362_IRQ_DECLARE(fl_wdt),
|
|
MT6362_IRQ_DECLARE(fl_bc12_dn),
|
|
};
|
|
|
|
static int mt6362_charger_irq_register(struct platform_device *pdev)
|
|
{
|
|
int i, rv;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(irqts); i++) {
|
|
rv = platform_get_irq_byname(pdev, irqts[i].name);
|
|
if (rv <= 0)
|
|
continue;
|
|
irqts[i].irq = rv;
|
|
|
|
rv = devm_request_threaded_irq(&pdev->dev, rv, NULL,
|
|
irqts[i].irqh, 0, NULL,
|
|
platform_get_drvdata(pdev));
|
|
if (rv)
|
|
return rv;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void mt6362_chg_irq_enable(const char *name, int en)
|
|
{
|
|
struct mt6362_charger_irqt *irqt;
|
|
int i = 0;
|
|
|
|
if (unlikely(!name))
|
|
return;
|
|
for (i = 0; i < ARRAY_SIZE(irqts); i++) {
|
|
irqt = irqts + i;
|
|
if (unlikely(!irqt->name))
|
|
continue;
|
|
if (!strcmp(irqt->name, name)) {
|
|
if (en)
|
|
enable_irq(irqt->irq);
|
|
else
|
|
disable_irq_nosync(irqt->irq);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int mt6362_chg_parse_dt_data(struct device *dev,
|
|
struct mt6362_chg_platform_data *pdata)
|
|
{
|
|
struct device_node *np = dev->of_node;
|
|
int i, ret;
|
|
|
|
const struct {
|
|
const char *name;
|
|
u32 *val_ptr;
|
|
} u32_opts[] = {
|
|
{ "ichg", &pdata->ichg },
|
|
{ "aicr", &pdata->aicr },
|
|
{ "mivr", &pdata->mivr },
|
|
{ "cv", &pdata->cv },
|
|
{ "ieoc", &pdata->ieoc },
|
|
{ "safety_timer", &pdata->safety_timer },
|
|
{ "ircmp_resistor", &pdata->ircmp_resistor },
|
|
{ "ircmp_vclamp", &pdata->ircmp_vclamp },
|
|
{ "vbusov_sel", &pdata->vbusov_sel },
|
|
{ "dcdt_sel", &pdata->dcdt_sel },
|
|
{ "specta_det", &pdata->specta_det },
|
|
{ "en_te", &pdata->en_te },
|
|
{ "en_wdt", &pdata->en_wdt },
|
|
{ "aicc_oneshot", &pdata->aicc_oneshot },
|
|
{ "shipping_dly_en", &pdata->shipping_dly_en },
|
|
{ "batoc_notify", &pdata->batoc_notify },
|
|
};
|
|
|
|
dev_info(dev, "%s: ++\n", __func__);
|
|
memcpy(pdata, &def_platform_data, sizeof(*pdata));
|
|
|
|
for (i = 0; i < ARRAY_SIZE(u32_opts); i++)
|
|
if (of_property_read_u32(np, u32_opts[i].name,
|
|
u32_opts[i].val_ptr))
|
|
dev_err(dev, "error reading '%s'\n", u32_opts[i].name);
|
|
|
|
ret = of_property_read_string(np, "chg_name", &pdata->chg_name);
|
|
if (ret)
|
|
dev_err(dev, "error reading 'chg_name'(%d)\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
static int mt6362_chg_apply_pdata(struct mt6362_chg_data *data,
|
|
struct mt6362_chg_platform_data *pdata)
|
|
{
|
|
int i, ret = 0;
|
|
u8 sel;
|
|
|
|
const struct {
|
|
u32 *val_ptr;
|
|
u32 prop_min;
|
|
u32 prop_max;
|
|
u32 prop_step;
|
|
u8 reg;
|
|
u8 mask;
|
|
u8 shift;
|
|
} u32_props[] = {
|
|
{
|
|
&pdata->ichg,
|
|
MT6362_CC_MIN, MT6362_CC_MAX, MT6362_CC_STEP,
|
|
MT6362_REG_CHG_ICHG, MT6362_MASK_CC, MT6362_SHFT_CC,
|
|
},
|
|
{
|
|
&pdata->aicr,
|
|
MT6362_AICR_MIN, MT6362_AICR_MAX, MT6362_AICR_STEP,
|
|
MT6362_REG_CHG_AICR, MT6362_MASK_AICR, MT6362_SHFT_AICR,
|
|
},
|
|
{
|
|
&pdata->mivr,
|
|
MT6362_MIVR_MIN, MT6362_MIVR_MAX, MT6362_MIVR_STEP,
|
|
MT6362_REG_CHG_MIVR, MT6362_MASK_MIVR, MT6362_SHFT_MIVR,
|
|
},
|
|
{
|
|
&pdata->cv,
|
|
MT6362_CV_MIN, MT6362_CV_MAX, MT6362_CV_STEP,
|
|
MT6362_REG_CHG_VCHG, MT6362_MASK_CV, MT6362_SHFT_CV,
|
|
},
|
|
{
|
|
&pdata->ieoc,
|
|
MT6362_IEOC_MIN, MT6362_IEOC_MAX, MT6362_IEOC_STEP,
|
|
MT6362_REG_CHG_EOC, MT6362_MASK_IEOC, MT6362_SHFT_IEOC,
|
|
},
|
|
{
|
|
&pdata->safety_timer,
|
|
MT6362_TMR_MIN, MT6362_TMR_MAX, MT6362_TMR_STEP,
|
|
MT6362_REG_CHG_TMR, MT6362_MASK_CHG_TMR_TIME,
|
|
MT6362_SHFT_CHG_TMR_TIME,
|
|
},
|
|
{
|
|
&pdata->ircmp_resistor,
|
|
MT6362_IR_R_MIN, MT6362_IR_R_MAX, MT6362_IR_R_STEP,
|
|
MT6362_REG_BAT_COMP, MT6362_MASK_BAT_IRCOMP_R,
|
|
MT6362_SHFT_BAT_IRCOMP_R,
|
|
},
|
|
{
|
|
&pdata->ircmp_vclamp,
|
|
MT6362_IR_V_MIN, MT6362_IR_V_MAX, MT6362_IR_V_STEP,
|
|
MT6362_REG_BAT_COMP, MT6362_MASK_BAT_IRCOMP_V,
|
|
MT6362_SHFT_BAT_IRCOMP_V,
|
|
},
|
|
};
|
|
const struct {
|
|
u32 *val_ptr;
|
|
u8 reg;
|
|
u8 mask;
|
|
u8 shift;
|
|
} sel_props[] = {
|
|
{
|
|
&pdata->aicc_oneshot, MT6362_REG_CHG_AICC2,
|
|
MT6362_MASK_AICC_ONESHOT, MT6362_SHFT_AICC_ONESHOT,
|
|
},
|
|
{
|
|
&pdata->shipping_dly_en, MT6362_REG_CHG_TOP1,
|
|
MT6362_MASK_BATFET_DIS_DLY, MT6362_SHFT_BATFET_DIS_DLY,
|
|
},
|
|
{
|
|
&pdata->dcdt_sel,
|
|
MT6362_REG_BC12_FUNC, MT6362_MASK_DCDT_SEL,
|
|
MT6362_SHFT_DCDT_SEL,
|
|
},
|
|
{
|
|
&pdata->specta_det,
|
|
MT6362_REG_BC12_FUNC, MT6362_MASK_SPECTA_EN,
|
|
MT6362_SHFT_SPECTA_EN,
|
|
},
|
|
{
|
|
&pdata->vbusov_sel,
|
|
MT6362_REG_CHG_TOP2, MT6362_MASK_VBUS_OV,
|
|
MT6362_SHFT_VBUS_OV,
|
|
},
|
|
};
|
|
|
|
for (i = 0; i < ARRAY_SIZE(u32_props); i++) {
|
|
sel = mt6362_map_reg_sel(*u32_props[i].val_ptr,
|
|
u32_props[i].prop_min,
|
|
u32_props[i].prop_max,
|
|
u32_props[i].prop_step);
|
|
ret |= regmap_update_bits(data->regmap,
|
|
u32_props[i].reg,
|
|
u32_props[i].mask,
|
|
sel << u32_props[i].shift);
|
|
}
|
|
for (i = 0; i < ARRAY_SIZE(sel_props); i++)
|
|
ret |= regmap_update_bits(data->regmap,
|
|
sel_props[i].reg,
|
|
sel_props[i].mask,
|
|
*sel_props[i].val_ptr <<
|
|
sel_props[i].shift);
|
|
return ret;
|
|
}
|
|
|
|
void mt6362_recv_batoc_callback(BATTERY_OC_LEVEL tag)
|
|
{
|
|
int ret, cnt = 0;
|
|
|
|
if (tag != BATTERY_OC_LEVEL_1)
|
|
return;
|
|
while (!pmic_get_register_value(PMIC_RG_INT_STATUS_FG_CUR_H)) {
|
|
if (cnt >= 1) {
|
|
ret = mt6362_set_shipping_mode(g_data);
|
|
if (ret < 0)
|
|
dev_err(g_data->dev,
|
|
"%s: set shipping mode fail\n",
|
|
__func__);
|
|
else
|
|
dev_info(g_data->dev,
|
|
"%s: set shipping mode done\n",
|
|
__func__);
|
|
}
|
|
mdelay(8);
|
|
cnt++;
|
|
}
|
|
dev_info(g_data->dev, "%s exit, cnt = %d, FG_CUR_H = %d\n",
|
|
__func__, cnt,
|
|
pmic_get_register_value(PMIC_RG_INT_STATUS_FG_CUR_H));
|
|
}
|
|
|
|
static int mt6362_chg_probe(struct platform_device *pdev)
|
|
{
|
|
struct mt6362_chg_platform_data *pdata = dev_get_platdata(&pdev->dev);
|
|
struct mt6362_chg_data *data;
|
|
struct power_supply_config charger_cfg = {};
|
|
struct regulator_config config = {};
|
|
bool use_dt = pdev->dev.of_node;
|
|
int rc;
|
|
|
|
if (use_dt) {
|
|
pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
|
|
if (!pdata)
|
|
return -ENOMEM;
|
|
rc = mt6362_chg_parse_dt_data(&pdev->dev, pdata);
|
|
if (rc < 0) {
|
|
dev_err(&pdev->dev, "parse dt fail\n");
|
|
return rc;
|
|
}
|
|
pdev->dev.platform_data = pdata;
|
|
}
|
|
if (!pdata) {
|
|
dev_err(&pdev->dev, "no platform data specified\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
|
|
if (!data)
|
|
return -ENOMEM;
|
|
|
|
data->regmap = dev_get_regmap(pdev->dev.parent, NULL);
|
|
if (!data->regmap) {
|
|
dev_err(&pdev->dev, "failed to allocate regmap\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
data->dev = &pdev->dev;
|
|
init_completion(&data->aicc_done);
|
|
init_completion(&data->pe_done);
|
|
init_completion(&data->bc12_start);
|
|
mutex_init(&data->hidden_mode_lock);
|
|
mutex_init(&data->ichg_lock);
|
|
mutex_init(&data->tchg_lock);
|
|
mutex_init(&data->pe_lock);
|
|
mutex_init(&data->bd_lock);
|
|
mutex_init(&data->bc12_lock);
|
|
mutex_init(&data->otg_lock);
|
|
data->bd_flag = false;
|
|
data->pwr_rdy = false;
|
|
data->hidden_mode_cnt = 0;
|
|
data->otg_mode_cnt = 0;
|
|
data->tchg = 0;
|
|
data->zcv = 0;
|
|
data->ichg = 2000000;
|
|
data->ichg_dis_chg = 2000000;
|
|
data->bd_mivr = 4400000;
|
|
data->bc12_update = false;
|
|
data->attach = false;
|
|
atomic_set(&data->mivr_cnt, 0);
|
|
init_waitqueue_head(&data->waitq);
|
|
platform_set_drvdata(pdev, data);
|
|
g_data = data;
|
|
|
|
rc = mt6362_chg_apply_pdata(data, pdata);
|
|
if (rc < 0) {
|
|
dev_err(&pdev->dev, "apply pdata fail\n");
|
|
goto out;
|
|
}
|
|
|
|
rc = mt6362_chg_init_setting(data);
|
|
if (rc < 0) {
|
|
dev_err(&pdev->dev, "%s: init setting fail\n", __func__);
|
|
goto out;
|
|
}
|
|
|
|
/* otg regulator */
|
|
config.dev = &pdev->dev;
|
|
config.regmap = data->regmap;
|
|
data->otg_rdev = devm_regulator_register(&pdev->dev, &mt6362_otg_rdesc,
|
|
&config);
|
|
if (IS_ERR(data->otg_rdev)) {
|
|
rc = PTR_ERR(data->otg_rdev);
|
|
goto out;
|
|
}
|
|
|
|
/* power supply register */
|
|
memcpy(&data->psy_desc, &mt6362_charger_desc, sizeof(data->psy_desc));
|
|
data->psy_desc.name = dev_name(&pdev->dev);
|
|
|
|
charger_cfg.drv_data = data;
|
|
charger_cfg.of_node = pdev->dev.of_node;
|
|
charger_cfg.supplied_to = mt6362_charger_supplied_to;
|
|
charger_cfg.num_supplicants = ARRAY_SIZE(mt6362_charger_supplied_to);
|
|
data->psy = devm_power_supply_register(&pdev->dev,
|
|
&data->psy_desc, &charger_cfg);
|
|
if (IS_ERR(data->psy)) {
|
|
dev_err(&pdev->dev, "Fail to register power supply dev\n");
|
|
rc = PTR_ERR(data->psy);
|
|
goto out;
|
|
}
|
|
|
|
rc = device_create_file(data->dev, &dev_attr_shipping_mode);
|
|
if (rc < 0) {
|
|
dev_notice(&pdev->dev, "create shipping attr fail\n");
|
|
goto out;
|
|
}
|
|
|
|
/* mivr task */
|
|
data->mivr_task = kthread_run(mt6362_chg_mivr_task_threadfn, data,
|
|
devm_kasprintf(data->dev, GFP_KERNEL,
|
|
"mivr_thread.%s", dev_name(data->dev)));
|
|
rc = PTR_ERR_OR_ZERO(data->mivr_task);
|
|
if (rc < 0) {
|
|
dev_err(data->dev, "create mivr handling thread fail\n");
|
|
goto out_devfs;
|
|
}
|
|
/* register fg bat oc notify */
|
|
if (pdata->batoc_notify)
|
|
register_battery_oc_notify(&mt6362_recv_batoc_callback,
|
|
BATTERY_OC_PRIO_CHARGER);
|
|
|
|
data->iio_ch = devm_iio_channel_get_all(&pdev->dev);
|
|
if (IS_ERR(data->iio_ch)) {
|
|
rc = PTR_ERR(data->iio_ch);
|
|
goto out_devfs;
|
|
}
|
|
|
|
/* Read ZCV */
|
|
rc = mt6362_read_zcv(data);
|
|
if (rc < 0) {
|
|
dev_err(data->dev, "%s: read zcv fail\n", __func__);
|
|
goto out_devfs;
|
|
}
|
|
|
|
/* charger class register */
|
|
data->chg_dev = charger_device_register(pdata->chg_name, data->dev,
|
|
data, &mt6362_chg_ops,
|
|
&mt6362_chg_props);
|
|
if (IS_ERR(data->chg_dev)) {
|
|
dev_err(data->dev, "charger device register fail\n");
|
|
rc = PTR_ERR(data->chg_dev);
|
|
goto out_devfs;
|
|
}
|
|
|
|
rc = mt6362_charger_irq_register(pdev);
|
|
if (rc) {
|
|
dev_err(&pdev->dev,
|
|
"failed to register charger irq\n");
|
|
goto out_chgdev;
|
|
}
|
|
|
|
mt6362_get_pwr_rdy_stat(data, &data->pwr_rdy);
|
|
rc = mt6362_handle_bleed_discharge(data);
|
|
if (rc < 0) {
|
|
dev_notice(&pdev->dev, "failed to handle bleed discharge\n");
|
|
goto out_chgdev;
|
|
}
|
|
|
|
#ifdef CONFIG_MTK_EXTERNAL_CHARGER_TYPE_DETECT
|
|
data->bc12_task = kthread_run(mt6362_bc12_thread, data, "bc12_thread");
|
|
if (IS_ERR(data->bc12_task)) {
|
|
rc = PTR_ERR(data->bc12_task);
|
|
goto out_chgdev;
|
|
}
|
|
#ifndef CONFIG_TCPC_CLASS
|
|
mt6362_run_bc12_thread(data, data->pwr_rdy);
|
|
#endif /* !CONFIG_TCPC_CLASS */
|
|
#endif /* CONFIG_MTK_EXTERNAL_CHARGER_TYPE_DETECT */
|
|
|
|
dev_info(&pdev->dev, "%s: successful probe\n", __func__);
|
|
return 0;
|
|
out_chgdev:
|
|
charger_device_unregister(data->chg_dev);
|
|
out_devfs:
|
|
device_remove_file(data->dev, &dev_attr_shipping_mode);
|
|
out:
|
|
mutex_destroy(&data->hidden_mode_lock);
|
|
mutex_destroy(&data->ichg_lock);
|
|
mutex_destroy(&data->tchg_lock);
|
|
mutex_destroy(&data->pe_lock);
|
|
mutex_destroy(&data->bd_lock);
|
|
return rc;
|
|
}
|
|
|
|
static const struct of_device_id __maybe_unused mt6362_chg_ofid_tbls[] = {
|
|
{ .compatible = "mediatek,mt6362-chg", },
|
|
{ },
|
|
};
|
|
MODULE_DEVICE_TABLE(of, mt6362_chg_ofid_tbls);
|
|
|
|
static struct platform_driver mt6362_chg_driver = {
|
|
.driver = {
|
|
.name = "mt6362-chg",
|
|
.of_match_table = of_match_ptr(mt6362_chg_ofid_tbls),
|
|
},
|
|
.probe = mt6362_chg_probe,
|
|
};
|
|
module_platform_driver(mt6362_chg_driver);
|
|
|
|
MODULE_AUTHOR("ChiYuan Huang <cy_huang@richtek.com>");
|
|
MODULE_DESCRIPTION("MT6362 SPMI CHG Driver");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_VERSION(MT6362_CHG_DRV_VERSION);
|
|
|
|
/*
|
|
* Version Note
|
|
* 1.0.1
|
|
* (1) fix bleed discharge workaround lock
|
|
* (2) fix otg parameter setting both in otg and flash mode
|
|
*
|
|
* 1.0.0
|
|
* Initial Release
|
|
*/
|