kernel-brax3-ubuntu-touch/drivers/power/supply/mt6360_pmu_chg.c
erascape f319b992b1 kernel-5.15: Initial import brax3 UT kernel
* halium configs enabled

Signed-off-by: erascape <erascape@proton.me>
2025-09-23 15:17:10 +00:00

3418 lines
90 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2020 MediaTek Inc.
*/
#include <linux/atomic.h>
#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/iio/consumer.h>
#include <linux/kconfig.h>
#include <linux/kernel.h>
#include <linux/kthread.h>
#include <linux/linear_range.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/phy/phy.h>
#include <linux/pm.h>
#include <linux/power_supply.h>
#include <linux/reboot.h>
#include <linux/regulator/driver.h>
#include <linux/slab.h>
#include <linux/wait.h>
#if IS_ENABLED(CONFIG_MTK_CHARGER)
#include "charger_class.h"
#include "mtk_charger.h"
#endif
#include <tcpm.h>
#include <linux/mfd/mt6360-private.h>
static bool dbg_log_en = true;
module_param(dbg_log_en, bool, 0644);
#define mt_dbg(dev, fmt, ...) \
do { \
if (dbg_log_en) \
dev_info(dev, "%s " fmt, __func__, ##__VA_ARGS__); \
} while (0)
/* Define this macro if DCD timeout is supported */
#define CONFIG_MT6360_DCDTOUT_SUPPORT
#define MT6360_PMU_DEV_INFO 0x300
#define MT6360_PMU_CORE_CTRL2 0x306
#define MT6360_PMU_TM_PAS_CODE1 0x307
#define MT6360_PMU_CHG_CTRL1 0x311
#define MT6360_PMU_CHG_CTRL2 0x312
#define MT6360_PMU_CHG_CTRL3 0x313
#define MT6360_PMU_CHG_CTRL4 0x314
#define MT6360_PMU_CHG_CTRL5 0x315
#define MT6360_PMU_CHG_CTRL6 0x316
#define MT6360_PMU_CHG_CTRL7 0x317
#define MT6360_PMU_CHG_CTRL9 0x319
#define MT6360_PMU_CHG_CTRL10 0x31A
#define MT6360_PMU_CHG_CTRL12 0x31C
#define MT6360_PMU_CHG_CTRL13 0x31D
#define MT6360_PMU_CHG_CTRL14 0x31E
#define MT6360_PMU_CHG_CTRL16 0x320
#define MT6360_PMU_CHG_AICC_RESULT 0x321
#define MT6360_PMU_DEVICE_TYPE 0x322
#define MT6360_PMU_USB_STATUS1 0x327
#define MT6360_PMU_CHG_CTRL17 0x32B
#define MT6360_PMU_CHG_CTRL18 0x32C
#define MT6360_PMU_CHG_HIDDEN_CTRL1 0x330
#define MT6360_PMU_CHG_HIDDEN_CTRL2 0x331
#define MT6360_PMU_CHG_HIDDEN_CTRL22 0x345
#define MT6360_PMU_CHG_STAT 0x34A
#define MT6360_PMU_TYPEC_OTP_CTRL 0x351
#define MT6360_PMU_ADC_BAT_DATA_H 0x352
#define MT6360_PMU_ADC_CONFIG 0x356
#define MT6360_PMU_CHG_CTRL19 0x361
#define MT6360_PMU_FOD_CTRL 0x365
#define MT6360_PMU_CHG_CTRL20 0x366
#define MT6360_PMU_USBID_CTRL1 0x36D
#define MT6360_PMU_USBID_CTRL2 0x36E
#define MT6360_PMU_USBID_CTRL3 0x36F
#define MT6360_PMU_FLED_EN 0x37E
#define MT6360_PMU_CHG_STAT1 0x3E0
#define MT6360_PMU_CHG_STAT2 0x3E1
#define MT6360_PMU_CHG_STAT4 0x3E3
#define MT6360_PMU_FOD_STAT 0x3E7
/* MT6360_PMU_DEV_INFO : 0x300 */
#define MT6360_CHIP_REV_MASK GENMASK(3, 0)
/* MT6360_PMU_CORE_CTRL2 : 0x306 */
#define MT6360_MASK_SHIP_RST_DIS BIT(0)
/* MT6360_PMU_CHG_CTRL1 : 0x311 */
#define MT6360_FSLP_MASK BIT(3)
#define MT6360_HZ_EN_MASK BIT(2)
#define MT6360_OPA_MODE_MASK BIT(0)
/* MT6360_PMU_CHG_CTRL2 : 0x312 */
#define MT6360_IINLMTSEL_SHFT (2)
#define MT6360_IINLMTSEL_MASK GENMASK(3, 2)
#define MT6360_TE_EN_MASK BIT(4)
#define MT6360_CFO_EN_MASK BIT(1)
#define MT6360_CHG_EN_MASK BIT(0)
/* MT6360_PMU_CHG_CTRL3 : 0x313 */
#define MT6360_AICR_SHFT (2)
#define MT6360_AICR_MASK GENMASK(7, 2)
#define MT6360_ILIM_EN_MASK BIT(0)
/* MT6360_PMU_CHG_CTRL4 : 0x314 */
#define MT6360_VOREG_SHFT (1)
#define MT6360_VOREG_MASK GENMASK(7, 1)
/* MT6360_PMU_CHG_CTRL5 : 0x315 */
#define MT6360_MASK_BOOST_VOREG GENMASK(7, 2)
/* MT6360_PMU_CHG_CTRL6 : 0x316 */
#define MT6360_MIVR_SHFT (1)
#define MT6360_MIVR_MASK GENMASK(7, 1)
/* MT6360_PMU_CHG_CTRL7 : 0x317 */
#define MT6360_ICHG_SHFT (2)
#define MT6360_ICHG_MASK GENMASK(7, 2)
/* MT6360_PMU_CHG_CTRL9 : 0x319 */
#define MT6360_IEOC_SHFT (4)
#define MT6360_IEOC_MASK GENMASK(7, 4)
/* MT6360_PMU_CHG_AICC_RESULT : 0x321 */
#define MT6360_AICC_RESULT_SHFT (2)
#define MT6360_AICC_RESULT_MASK GENMASK(7, 2)
/* MT6360_PMU_CHG_CTRL10 : 0x31A */
#define MT6360_OTG_OC_SHFT (0)
#define MT6360_OTG_OC_MASK GENMASK(2, 0)
/* MT6360_PMU_CHG_CTRL12 : 0x31C */
#define MT6360_TMR_EN_MASK BIT(1)
/* MT6360_PMU_CHG_CTRL13 : 0x31D */
#define MT6360_CHG_WDT_EN_MASK BIT(7)
/* MT6360_PMU_CHG_CTRL14 : 0x31E */
#define MT6360_RG_EN_AICC_MASK BIT(7)
/* MT6360_PMU_DEVICE_TYPE : 0x322 */
#define MT6360_USBCHGEN_MASK BIT(7)
#define MT6360_MASK_DCDTOUTEN BIT(6)
/* MT6360_PMU_USB_STATUS1 : 0x327 */
#define MT6360_USB_STATUS_SHFT (4)
#define MT6360_USB_STATUS_MASK GENMASK(6, 4)
/* MT6360_PMU_CHG_CTRL16 : 0x32A */
#define MT6360_AICC_VTH_SHFT (1)
#define MT6360_AICC_VTH_MASK GENMASK(7, 1)
/* MT6360_PMU_CHG_CTRL17 : 0x32B */
#define MT6360_EN_PUMPX_MASK BIT(7)
#define MT6360_PUMPX_20_10_MASK BIT(6)
#define MT6360_PUMPX_UP_DN_MASK BIT(5)
#define MT6360_PUMPX_DEC_SHFT (0)
#define MT6360_PUMPX_DEC_MASK GENMASK(4, 0)
/* MT6360_PMU_CHG_HIDDEN_CTRL2 : 0x331 */
#define MT6360_EOC_RST_MASK BIT(7)
#define MT6360_DISCHG_MASK BIT(2)
/* MT6360_PMU_CHG_HIDDEN_CTRL22 : 0x345 */
#define MT6360_BATOVP_LVL_MASK GENMASK(6, 5)
/* MT6360_PMU_CHG_STAT : 0x34A */
#define MT6360_CHG_STAT_SHFT (6)
#define MT6360_CHG_STAT_MASK GENMASK(7, 6)
#define MT6360_CHG_BATSYSUV_MASK BIT(1)
/* MT6360_PMU_TYPEC_OTP_CTRL: 0x351 */
#define MT6360_TYPEC_OTP_SWEN_MASK BIT(2)
#define MT6360_TYPEC_OTP_EN_MASK BIT(0)
/* MT6360_PMU_ADC_CONFIG : 0x356 */
#define MT6360_ZCV_EN_SHFT (6)
#define MT6360_ZCV_EN_MASK BIT(6)
/* MT6360_PMU_CHG_CTRL19 : 0x361 */
#define MT6360_CHG_VIN_OVP_VTHSEL_SHFT (5)
#define MT6360_CHG_VIN_OVP_VTHSEL_MASK GENMASK(6, 5)
/* MT6360_PMU_CHG_CTRL20 : 0x366 */
#define MT6360_EN_SDI_MASK BIT(1)
/* MT6360_PMU_USBID_CTRL1 : 0x36D */
#define MT6360_USBID_EN_MASK BIT(7)
#define MT6360_ID_RPULSEL_SHFT 5
#define MT6360_ID_RPULSEL_MASK GENMASK(6, 5)
#define MT6360_ISTDET_SHFT 2
#define MT6360_ISTDET_MASK GENMASK(4, 2)
/* MT6360_PMU_USBID_CTRL2 : 0x36E */
#define MT6360_IDTD_MASK GENMASK(7, 5)
#define MT6360_USBID_FLOAT_MASK BIT(1)
/* MT6360_PMU_FLED_EN : 0x37E */
#define MT6360_STROBE_EN_MASK BIT(2)
/* MT6360_PMU_CHG_STAT1 : 0x3E0 */
#define MT6360_PWR_RDY_EVT_MASK BIT(7)
#define MT6360_MIVR_EVT_SHFT (6)
#define MT6360_MIVR_EVT_MASK BIT(6)
#define MT6360_CHG_TREG_SHFT (4)
#define MT6360_CHG_TREG_MASK BIT(4)
/* MT6360_PMU_CHG_STAT4 : 0x3E3 */
#define MT6360_CHG_TMRI_MASK BIT(3)
/* uV */
#define MT6360_VMIVR_MIN 3900000
#define MT6360_VMIVR_MAX 13400000
#define MT6360_VMIVR_STEP 100000
/* uA */
#define MT6360_ICHG_MIN 100000
#define MT6360_ICHG_MAX 5000000
#define MT6360_ICHG_STEP 100000
/* uV */
#define MT6360_VOREG_MIN 3900000
#define MT6360_VOREG_MAX 4710000
#define MT6360_VOREG_STEP 10000
/* uA */
#define MT6360_AICR_MIN 100000
#define MT6360_AICR_MAX 3250000
#define MT6360_AICR_STEP 50000
/* uA */
#define MT6360_IPREC_MIN 100000
#define MT6360_IPREC_MAX 850000
#define MT6360_IPREC_STEP 50000
/* uA */
#define MT6360_IEOC_MIN 100000
#define MT6360_IEOC_MAX 850000
#define MT6360_IEOC_STEP 50000
enum {
MT6360_RANGE_VMIVR,
MT6360_RANGE_ICHG,
MT6360_RANGE_VOREG,
MT6360_RANGE_AICR,
MT6360_RANGE_IPREC,
MT6360_RANGE_IEOC,
MT6360_RANGE_IRCMP_R,
MT6360_RANGE_VCLAMP,
MT6360_RANGE_PUMPX20,
MT6360_RANGE_AICC_VTH,
MT6360_RANGE_MAX,
};
#define MT6360_LINEAR_RANGE(idx, _min, _min_sel, _max_sel, _step) \
[idx] = REGULATOR_LINEAR_RANGE(_min, _min_sel, _max_sel, _step)
static const struct linear_range mt6360_chg_range[MT6360_RANGE_MAX] = {
MT6360_LINEAR_RANGE(MT6360_RANGE_VMIVR, 3900000, 0, 0x5F, 100000),
MT6360_LINEAR_RANGE(MT6360_RANGE_ICHG, 100000, 0, 0x31, 100000),
MT6360_LINEAR_RANGE(MT6360_RANGE_VOREG, 3900000, 0, 0x51, 10000),
MT6360_LINEAR_RANGE(MT6360_RANGE_AICR, 100000, 0, 0x3F, 50000),
MT6360_LINEAR_RANGE(MT6360_RANGE_IPREC, 100000, 0, 0x0F, 50000),
MT6360_LINEAR_RANGE(MT6360_RANGE_IEOC, 100000, 0, 0x0F, 50000),
MT6360_LINEAR_RANGE(MT6360_RANGE_IRCMP_R, 0, 0, 0x07, 25000),
MT6360_LINEAR_RANGE(MT6360_RANGE_VCLAMP, 0, 0, 0x07, 32000),
MT6360_LINEAR_RANGE(MT6360_RANGE_PUMPX20, 5500000, 0, 0x1D, 500000),
MT6360_LINEAR_RANGE(MT6360_RANGE_AICC_VTH, 3900000, 0, 0x51, 10000),
};
#define PHY_MODE_BC11_SET 1
#define PHY_MODE_BC11_CLR 2
void __attribute__ ((weak)) Charger_Detect_Init(void)
{
}
void __attribute__ ((weak)) Charger_Detect_Release(void)
{
}
struct tag_bootmode {
u32 size;
u32 tag;
u32 bootmode;
u32 boottype;
};
enum mt6360_adc_channel {
MT6360_ADC_VBUSDIV5,
MT6360_ADC_VSYS,
MT6360_ADC_VBAT,
MT6360_ADC_IBUS,
MT6360_ADC_IBAT,
MT6360_ADC_NODUMP,
MT6360_ADC_TEMP_JC = MT6360_ADC_NODUMP,
MT6360_ADC_USBID,
MT6360_ADC_TS,
MT6360_ADC_MAX,
};
struct mt6360_chg_platform_data {
u32 ichg;
u32 aicr;
u32 mivr;
u32 cv;
u32 ieoc;
u32 safety_timer;
u32 ircmp_resistor;
u32 ircmp_vclamp;
u32 en_te;
u32 en_wdt;
u32 post_aicc;
u32 batoc_notify;
u32 bc12_sel;
const char *chg_name;
};
struct mt6360_chg_info {
struct device *dev;
struct regmap *regmap;
struct iio_channel *channels[MT6360_ADC_MAX];
struct power_supply *psy;
struct charger_device *chg_dev;
int hidden_mode_cnt;
struct mutex hidden_mode_lock;
struct mutex pe_lock;
struct mutex aicr_lock;
struct mutex tchg_lock;
struct mutex ichg_lock;
int tchg;
u32 zcv;
u32 ichg;
u32 ichg_dis_chg;
u8 chip_rev;
/* boot_mode */
u32 bootmode;
u32 boottype;
/* Charger type detection */
struct mutex chgdet_lock;
bool attach;
bool pwr_rdy;
bool bc12_en;
int psy_usb_type;
atomic_t tcpc_attach;
/* dts: charger */
struct power_supply *chg_psy;
/* type_c_port0 */
struct tcpc_device *tcpc_dev;
struct notifier_block pd_nb;
struct work_struct chgdet_work;
/* power supply */
struct power_supply_desc psy_desc;
struct completion aicc_done;
struct completion pumpx_done;
atomic_t pe_complete;
/* mivr */
atomic_t mivr_cnt;
wait_queue_head_t mivr_wq;
struct task_struct *mivr_task;
/* unfinish pe pattern */
struct workqueue_struct *pe_wq;
struct work_struct pe_work;
u8 ctd_dischg_status;
/* otg_vbus */
struct regulator_dev *otg_rdev; };
/* for recive bat oc notify */
struct mt6360_chg_info *g_mci;
static const u32 mt6360_otg_oc_threshold[] = {
500000, 700000, 1100000, 1300000, 1800000, 2100000, 2400000, 3000000,
}; /* uA */
enum mt6360_iinlmtsel {
MT6360_IINLMTSEL_AICR_3250 = 0,
MT6360_IINLMTSEL_CHG_TYPE,
MT6360_IINLMTSEL_AICR,
MT6360_IINLMTSEL_LOWER_LEVEL,
};
enum mt6360_charging_status {
MT6360_CHG_STATUS_READY = 0,
MT6360_CHG_STATUS_PROGRESS,
MT6360_CHG_STATUS_DONE,
MT6360_CHG_STATUS_FAULT,
MT6360_CHG_STATUS_MAX,
};
enum mt6360_usbsw_state {
MT6360_USBSW_CHG = 0,
MT6360_USBSW_USB,
};
enum mt6360_pmu_chg_type {
MT6360_CHG_TYPE_NOVBUS = 0,
MT6360_CHG_TYPE_UNDER_GOING,
MT6360_CHG_TYPE_SDP,
MT6360_CHG_TYPE_SDPNSTD,
MT6360_CHG_TYPE_DCP,
MT6360_CHG_TYPE_CDP,
MT6360_CHG_TYPE_MAX,
};
static enum power_supply_usb_type mt6360_charger_usb_types[] = {
POWER_SUPPLY_USB_TYPE_UNKNOWN,
POWER_SUPPLY_USB_TYPE_SDP,
POWER_SUPPLY_USB_TYPE_DCP,
POWER_SUPPLY_USB_TYPE_CDP,
};
static const char * const mt6360_adc_chan_list[] = {
"VBUSDIV5", "VSYS", "VBAT", "IBUS", "IBAT", "TEMP_JC", "USBID", "TS",
};
static const char __maybe_unused *mt6360_chg_status_name[] = {
"ready", "progress", "done", "fault",
};
static const struct mt6360_chg_platform_data def_platform_data = {
.ichg = 2000000, /* uA */
.aicr = 500000, /* uA */
.mivr = 4400000, /* uV */
.cv = 4350000, /* uA */
.ieoc = 250000, /* uA */
.safety_timer = 12, /* hour */
#ifdef CONFIG_MTK_BIF_SUPPORT
.ircmp_resistor = 0, /* uohm */
.ircmp_vclamp = 0, /* uV */
#else
.ircmp_resistor = 25000, /* uohm */
.ircmp_vclamp = 32000, /* uV */
#endif
.en_te = true,
.en_wdt = true,
.post_aicc = true,
.batoc_notify = false,
.bc12_sel = 0,
.chg_name = "primary_chg",
};
/* ================== */
/* Internal Functions */
/* ================== */
static u32 mt6360_trans_ichg_sel(u32 uA)
{
u32 data;
linear_range_get_selector_within(&mt6360_chg_range[MT6360_RANGE_ICHG],
uA, &data);
return data;
}
static u32 mt6360_trans_aicr_sel(u32 uA)
{
u32 data;
linear_range_get_selector_within(&mt6360_chg_range[MT6360_RANGE_AICR],
uA, &data);
return data;
}
static u32 mt6360_trans_mivr_sel(u32 uV)
{
u32 data;
linear_range_get_selector_within(&mt6360_chg_range[MT6360_RANGE_VMIVR],
uV, &data);
return data;
}
static u32 mt6360_trans_cv_sel(u32 uV)
{
u32 data;
linear_range_get_selector_within(&mt6360_chg_range[MT6360_RANGE_VOREG],
uV, &data);
return data;
}
static u32 mt6360_trans_ieoc_sel(u32 uA)
{
u32 data;
linear_range_get_selector_within(&mt6360_chg_range[MT6360_RANGE_IEOC],
uA, &data);
return data;
}
static u32 mt6360_trans_safety_timer_sel(u32 hr)
{
u32 mt6360_st_tbl[] = {4, 6, 8, 10, 12, 14, 16, 20};
u32 cur_val, next_val;
int i;
if (hr < mt6360_st_tbl[0])
return 0;
for (i = 0; i < ARRAY_SIZE(mt6360_st_tbl) - 1; i++) {
cur_val = mt6360_st_tbl[i];
next_val = mt6360_st_tbl[i+1];
if (hr >= cur_val && hr < next_val)
return i;
}
return ARRAY_SIZE(mt6360_st_tbl) - 1;
}
static u32 mt6360_trans_ircmp_r_sel(u32 uohm)
{
u32 data;
linear_range_get_selector_within(
&mt6360_chg_range[MT6360_RANGE_IRCMP_R], uohm, &data);
return data;
}
static u32 mt6360_trans_ircmp_vclamp_sel(u32 uV)
{
u32 data;
linear_range_get_selector_within(
&mt6360_chg_range[MT6360_RANGE_VCLAMP], uV, &data);
return data;
}
static const u32 mt6360_usbid_rup[] = {
500000, 75000, 5000, 1000
};
static inline u32 mt6360_trans_usbid_rup(u32 rup)
{
int i;
int maxidx = ARRAY_SIZE(mt6360_usbid_rup) - 1;
if (rup >= mt6360_usbid_rup[0])
return 0;
if (rup <= mt6360_usbid_rup[maxidx])
return maxidx;
for (i = 0; i < maxidx; i++) {
if (rup == mt6360_usbid_rup[i])
return i;
if (rup < mt6360_usbid_rup[i] &&
rup > mt6360_usbid_rup[i + 1]) {
if ((mt6360_usbid_rup[i] - rup) <=
(rup - mt6360_usbid_rup[i + 1]))
return i;
else
return i + 1;
}
}
return maxidx;
}
static const u32 mt6360_usbid_src_ton[] = {
400, 1000, 4000, 10000, 40000, 100000, 400000,
};
static inline u32 mt6360_trans_usbid_src_ton(u32 src_ton)
{
int i;
int maxidx = ARRAY_SIZE(mt6360_usbid_src_ton) - 1;
/* There is actually an option, always on, after 400000 */
if (src_ton == 0)
return maxidx + 1;
if (src_ton < mt6360_usbid_src_ton[0])
return 0;
if (src_ton > mt6360_usbid_src_ton[maxidx])
return maxidx;
for (i = 0; i < maxidx; i++) {
if (src_ton == mt6360_usbid_src_ton[i])
return i;
if (src_ton > mt6360_usbid_src_ton[i] &&
src_ton < mt6360_usbid_src_ton[i + 1]) {
if ((src_ton - mt6360_usbid_src_ton[i]) <=
(mt6360_usbid_src_ton[i + 1] - src_ton))
return i;
else
return i + 1;
}
}
return maxidx;
}
static inline int mt6360_get_ieoc(struct mt6360_chg_info *mci, u32 *uA)
{
int ret = 0;
unsigned int regval, value;
ret = regmap_read(mci->regmap, MT6360_PMU_CHG_CTRL9, &regval);
if (ret < 0)
return ret;
regval = (regval & MT6360_IEOC_MASK) >> MT6360_IEOC_SHFT;
ret = linear_range_get_value(&mt6360_chg_range[MT6360_RANGE_IEOC],
regval, &value);
if (!ret)
*uA = value;
return 0;
}
static inline int mt6360_get_charging_status(
struct mt6360_chg_info *mci,
enum mt6360_charging_status *chg_stat)
{
int ret = 0;
unsigned int regval;
ret = regmap_read(mci->regmap, MT6360_PMU_CHG_STAT, &regval);
if (ret < 0)
return ret;
*chg_stat = (regval & MT6360_CHG_STAT_MASK) >> MT6360_CHG_STAT_SHFT;
return 0;
}
static inline int mt6360_is_charger_enabled(struct mt6360_chg_info *mci,
bool *en)
{
int ret = 0;
unsigned int regval;
ret = regmap_read(mci->regmap, MT6360_PMU_CHG_CTRL2, &regval);
if (ret < 0)
return ret;
*en = (regval & MT6360_CHG_EN_MASK) ? true : false;
return 0;
}
static inline int mt6360_select_input_current_limit(
struct mt6360_chg_info *mci, enum mt6360_iinlmtsel sel)
{
dev_dbg(mci->dev,
"%s: select input current limit = %d\n", __func__, sel);
return regmap_update_bits(mci->regmap,
MT6360_PMU_CHG_CTRL2,
MT6360_IINLMTSEL_MASK,
sel << MT6360_IINLMTSEL_SHFT);
}
static int mt6360_enable_wdt(struct mt6360_chg_info *mci, bool en)
{
struct mt6360_chg_platform_data *pdata = dev_get_platdata(mci->dev);
dev_dbg(mci->dev, "%s enable wdt, en = %d\n", __func__, en);
if (!pdata->en_wdt)
return 0;
return regmap_update_bits(mci->regmap,
MT6360_PMU_CHG_CTRL13,
MT6360_CHG_WDT_EN_MASK,
en ? 0xff : 0);
}
static inline int mt6360_get_chrdet_ext_stat(struct mt6360_chg_info *mci,
bool *pwr_rdy)
{
int ret = 0;
unsigned int regval;
ret = regmap_read(mci->regmap, MT6360_PMU_FOD_STAT, &regval);
if (ret < 0)
return ret;
*pwr_rdy = (regval & BIT(4)) ? true : false;
return 0;
}
static int DPDM_Switch_TO_CHG_upstream(struct mt6360_chg_info *mci,
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(mci->dev, "usb2-phy");
if (IS_ERR_OR_NULL(phy)) {
dev_info(mci->dev, "phy_get fail\n");
return -EINVAL;
}
ret = phy_set_mode_ext(phy, PHY_MODE_USB_DEVICE, mode);
if (ret)
dev_info(mci->dev, "phy_set_mode_ext fail\n");
phy_put(mci->dev, phy);
return 0;
}
static int mt6360_set_usbsw_state(struct mt6360_chg_info *mci, int state)
{
dev_info(mci->dev, "%s: state = %d\n", __func__, state);
/* Switch D+D- to AP/MT6360 */
if (state == MT6360_USBSW_CHG)
DPDM_Switch_TO_CHG_upstream(mci, true);
else
DPDM_Switch_TO_CHG_upstream(mci, false);
return 0;
}
#ifndef CONFIG_MT6360_DCDTOUT_SUPPORT
static int __maybe_unused mt6360_enable_dcd_tout(
struct mt6360_chg_info *mci, bool en)
{
dev_info(mci->dev, "%s en = %d\n", __func__, en);
return regmap_update_bits(mci->regmap, MT6360_PMU_DEVICE_TYPE,
MT6360_MASK_DCDTOUTEN, en ? 0xff : 0);
}
static int __maybe_unused mt6360_is_dcd_tout_enable(
struct mt6360_chg_info *mci, bool *en)
{
int ret;
unsigned int regval;
ret = regmap_read(mci->regmap, MT6360_PMU_DEVICE_TYPE, &regval);
if (ret < 0) {
*en = false;
return ret;
}
*en = (regval & MT6360_MASK_DCDTOUTEN ? true : false);
return 0;
}
#endif
static bool is_usb_rdy(struct device *dev)
{
struct device_node *node;
bool ready = true;
node = of_parse_phandle(dev->of_node, "usb", 0);
if (node) {
ready = of_property_read_bool(node, "gadget-ready");
dev_info(dev, "gadget-ready=%d\n", ready);
} else
dev_info(dev, "usb node missing or invalid\n");
return ready;
}
static int __mt6360_enable_usbchgen(struct mt6360_chg_info *mci, bool en)
{
int i, ret = 0;
const int max_wait_cnt = 200;
bool pwr_rdy = false;
enum mt6360_usbsw_state usbsw =
en ? MT6360_USBSW_CHG : MT6360_USBSW_USB;
#ifndef CONFIG_MT6360_DCDTOUT_SUPPORT
bool dcd_en = false;
#endif /* CONFIG_MT6360_DCDTOUT_SUPPORT */
dev_info(mci->dev, "%s: en = %d\n", __func__, en);
if (en) {
#ifndef CONFIG_MT6360_DCDTOUT_SUPPORT
ret = mt6360_is_dcd_tout_enable(mci, &dcd_en);
if (!dcd_en)
msleep(180);
#endif /* CONFIG_MT6360_DCDTOUT_SUPPORT */
/* Workaround for CDP port */
for (i = 0; i < max_wait_cnt; i++) {
if (is_usb_rdy(mci->dev)) {
dev_info(mci->dev, "%s: USB ready\n",
__func__);
break;
}
dev_info(mci->dev, "%s: CDP block\n", __func__);
if (IS_ENABLED(CONFIG_TCPC_CLASS)) {
if (!atomic_read(&mci->tcpc_attach)) {
dev_info(mci->dev,
"%s: plug out\n", __func__);
return 0;
}
} else {
/* Check vbus */
ret = mt6360_get_chrdet_ext_stat(mci, &pwr_rdy);
if (ret < 0) {
dev_err(mci->dev,
"%s: fail, ret = %d\n",
__func__, ret);
return ret;
}
dev_info(mci->dev,
"%s: pwr_rdy = %d\n", __func__,
pwr_rdy);
if (!pwr_rdy) {
dev_info(mci->dev,
"%s: plug out\n", __func__);
return ret;
}
}
msleep(100);
}
if (i == max_wait_cnt)
dev_err(mci->dev, "%s: CDP timeout\n", __func__);
else
dev_info(mci->dev, "%s: CDP free\n", __func__);
}
mt6360_set_usbsw_state(mci, usbsw);
ret = regmap_update_bits(mci->regmap, MT6360_PMU_DEVICE_TYPE,
MT6360_USBCHGEN_MASK, en ? 0xff : 0);
if (ret >= 0)
mci->bc12_en = en;
return ret;
}
static int mt6360_enable_usbchgen(struct mt6360_chg_info *mci, bool en)
{
int ret = 0;
mutex_lock(&mci->chgdet_lock);
ret = __mt6360_enable_usbchgen(mci, en);
mutex_unlock(&mci->chgdet_lock);
return ret;
}
static int mt6360_chgdet_pre_process(struct mt6360_chg_info *mci)
{
bool attach = false;
if (IS_ENABLED(CONFIG_TCPC_CLASS))
attach = atomic_read(&mci->tcpc_attach);
else
attach = mci->pwr_rdy;
if (attach && (mci->bootmode == 5)) {
/* Skip charger type detection to speed up meta boot.*/
dev_notice(mci->dev, "%s: force Standard USB Host in meta\n",
__func__);
mci->attach = attach;
mci->psy_desc.type = POWER_SUPPLY_TYPE_USB;
mci->psy_usb_type = POWER_SUPPLY_USB_TYPE_SDP;
power_supply_changed(mci->psy);
return 0;
}
return __mt6360_enable_usbchgen(mci, attach);
}
static int mt6360_chgdet_post_process(struct mt6360_chg_info *mci)
{
int ret = 0;
bool attach = false, inform_psy = true;
u8 usb_status = MT6360_CHG_TYPE_NOVBUS;
unsigned int regval;
if (IS_ENABLED(CONFIG_TCPC_CLASS))
attach = atomic_read(&mci->tcpc_attach);
else
attach = mci->pwr_rdy;
if (mci->attach == attach) {
dev_info(mci->dev, "%s: attach(%d) is the same\n",
__func__, attach);
inform_psy = !attach;
goto out;
}
mci->attach = attach;
dev_info(mci->dev, "%s: attach = %d\n", __func__, attach);
/* Plug out during BC12 */
if (!attach) {
dev_info(mci->dev, "%s: Charger Type: UNKONWN\n", __func__);
mci->psy_desc.type = POWER_SUPPLY_TYPE_UNKNOWN;
mci->psy_usb_type = POWER_SUPPLY_USB_TYPE_UNKNOWN;
goto out;
}
/* Plug in */
ret = regmap_read(mci->regmap, MT6360_PMU_USB_STATUS1, &regval);
if (ret < 0)
goto out;
usb_status = (regval & MT6360_USB_STATUS_MASK) >>
MT6360_USB_STATUS_SHFT;
switch (usb_status) {
case MT6360_CHG_TYPE_UNDER_GOING:
dev_info(mci->dev, "%s: under going...\n", __func__);
return ret;
case MT6360_CHG_TYPE_SDP:
dev_info(mci->dev,
"%s: Charger Type: STANDARD_HOST\n", __func__);
mci->psy_desc.type = POWER_SUPPLY_TYPE_USB;
mci->psy_usb_type = POWER_SUPPLY_USB_TYPE_SDP;
break;
case MT6360_CHG_TYPE_SDPNSTD:
dev_info(mci->dev,
"%s: Charger Type: NONSTANDARD_CHARGER\n", __func__);
mci->psy_desc.type = POWER_SUPPLY_TYPE_USB;
mci->psy_usb_type = POWER_SUPPLY_USB_TYPE_DCP;
break;
case MT6360_CHG_TYPE_CDP:
dev_info(mci->dev,
"%s: Charger Type: CHARGING_HOST\n", __func__);
mci->psy_desc.type = POWER_SUPPLY_TYPE_USB_CDP;
mci->psy_usb_type = POWER_SUPPLY_USB_TYPE_CDP;
break;
case MT6360_CHG_TYPE_DCP:
dev_info(mci->dev,
"%s: Charger Type: STANDARD_CHARGER\n", __func__);
mci->psy_desc.type = POWER_SUPPLY_TYPE_USB_DCP;
mci->psy_usb_type = POWER_SUPPLY_USB_TYPE_DCP;
break;
}
out:
if (!attach) {
ret = __mt6360_enable_usbchgen(mci, false);
if (ret < 0)
dev_notice(mci->dev, "%s: disable chgdet fail\n",
__func__);
} else if (mci->psy_desc.type != POWER_SUPPLY_TYPE_USB_DCP)
mt6360_set_usbsw_state(mci, MT6360_USBSW_USB);
if (!inform_psy)
return ret;
power_supply_changed(mci->psy);
return ret;
}
static const u32 mt6360_vinovp_list[] = {
5500000, 6500000, 10500000, 14500000,
};
static int mt6360_select_vinovp(struct mt6360_chg_info *mci, u32 uV)
{
int i;
if (uV < mt6360_vinovp_list[0])
return -EINVAL;
for (i = 1; i < ARRAY_SIZE(mt6360_vinovp_list); i++) {
if (uV < mt6360_vinovp_list[i])
break;
}
i--;
return regmap_update_bits(mci->regmap,
MT6360_PMU_CHG_CTRL19,
MT6360_CHG_VIN_OVP_VTHSEL_MASK,
i << MT6360_CHG_VIN_OVP_VTHSEL_SHFT);
}
static inline int mt6360_read_zcv(struct mt6360_chg_info *mci)
{
int ret = 0;
u8 zcv_data[2] = {0};
dev_dbg(mci->dev, "%s\n", __func__);
/* Read ZCV data */
ret = regmap_bulk_read(mci->regmap, MT6360_PMU_ADC_BAT_DATA_H,
zcv_data, 2);
if (ret < 0) {
dev_err(mci->dev, "%s: read zcv data fail\n", __func__);
return ret;
}
mci->zcv = 1250 * (zcv_data[0] * 256 + zcv_data[1]);
dev_info(mci->dev, "%s: zcv = (0x%02X, 0x%02X, %dmV)\n",
__func__, zcv_data[0], zcv_data[1], mci->zcv/1000);
/* Disable ZCV */
ret = regmap_update_bits(mci->regmap, MT6360_PMU_ADC_CONFIG,
MT6360_ZCV_EN_MASK, 0);
if (ret < 0)
dev_err(mci->dev, "%s: disable zcv fail\n", __func__);
return ret;
}
static int __mt6360_set_aicr(struct mt6360_chg_info *mci, u32 uA)
{
int ret = 0;
u32 data = 0;
dev_dbg(mci->dev, "%s\n", __func__);
/* Disable sys drop improvement for download mode */
ret = regmap_update_bits(mci->regmap, MT6360_PMU_CHG_CTRL20,
MT6360_EN_SDI_MASK, 0);
if (ret < 0) {
dev_err(mci->dev, "%s: disable en_sdi fail\n", __func__);
return ret;
}
linear_range_get_selector_within(&mt6360_chg_range[MT6360_RANGE_AICR],
uA, &data);
return regmap_update_bits(mci->regmap,
MT6360_PMU_CHG_CTRL3,
MT6360_AICR_MASK,
data << MT6360_AICR_SHFT);
}
static int __mt6360_set_ichg(struct mt6360_chg_info *mci, u32 uA)
{
int ret = 0;
u32 data = 0;
mt_dbg(mci->dev, "%s\n", __func__);
linear_range_get_selector_within(&mt6360_chg_range[MT6360_RANGE_ICHG],
uA, &data);
ret = regmap_update_bits(mci->regmap,
MT6360_PMU_CHG_CTRL7,
MT6360_ICHG_MASK,
data << MT6360_ICHG_SHFT);
if (ret < 0)
dev_notice(mci->dev, "%s: fail\n", __func__);
else
mci->ichg = uA;
return ret;
}
static int __mt6360_enable(struct mt6360_chg_info *mci, bool en)
{
int ret = 0;
u32 ichg_ramp_t = 0;
mt_dbg(mci->dev, "%s: en = %d\n", __func__, en);
/* Workaround for vsys overshoot */
mutex_lock(&mci->ichg_lock);
if (mci->ichg < 500000) {
dev_info(mci->dev,
"%s: ichg < 500mA, bypass vsys wkard\n", __func__);
goto out;
}
if (!en) {
mci->ichg_dis_chg = mci->ichg;
ichg_ramp_t = (mci->ichg - 500000) / 50000 * 2;
/* Set ichg to 500mA */
ret = regmap_update_bits(mci->regmap,
MT6360_PMU_CHG_CTRL7,
MT6360_ICHG_MASK,
0x04 << MT6360_ICHG_SHFT);
if (ret < 0) {
dev_notice(mci->dev,
"%s: set ichg fail\n", __func__);
goto vsys_wkard_fail;
}
mdelay(ichg_ramp_t);
} else {
if (mci->ichg == mci->ichg_dis_chg) {
ret = __mt6360_set_ichg(mci, mci->ichg);
if (ret < 0) {
dev_notice(mci->dev,
"%s: set ichg fail\n", __func__);
goto out;
}
}
}
out:
ret = regmap_update_bits(mci->regmap,
MT6360_PMU_CHG_CTRL2,
MT6360_CHG_EN_MASK, en ? 0xff : 0);
if (ret < 0)
dev_notice(mci->dev, "%s: fail, en = %d\n", __func__, en);
vsys_wkard_fail:
mutex_unlock(&mci->ichg_lock);
return ret;
}
static int __mt6360_is_enabled(struct mt6360_chg_info *mci, bool *en)
{
int ret;
unsigned int regval;
ret = regmap_read(mci->regmap, MT6360_PMU_CHG_CTRL2, &regval);
if (ret < 0)
return ret;
*en = regval & MT6360_CHG_EN_MASK ? true : false;
return 0;
}
static int mt6360_enable_pump_express(struct mt6360_chg_info *mci,
bool pe20)
{
long timeout, pe_timeout = pe20 ? 1400 : 2800;
int ret = 0;
dev_info(mci->dev, "%s\n", __func__);
ret = __mt6360_set_aicr(mci, 800000);
if (ret < 0)
return ret;
mutex_lock(&mci->ichg_lock);
ret = __mt6360_set_ichg(mci, 2000000);
mutex_unlock(&mci->ichg_lock);
if (ret < 0)
return ret;
ret = __mt6360_enable(mci, true);
if (ret < 0)
return ret;
ret = regmap_update_bits(mci->regmap, MT6360_PMU_CHG_CTRL17,
MT6360_EN_PUMPX_MASK, 0);
if (ret < 0)
return ret;
ret = regmap_update_bits(mci->regmap, MT6360_PMU_CHG_CTRL17,
MT6360_EN_PUMPX_MASK, 0xff);
if (ret < 0)
return ret;
reinit_completion(&mci->pumpx_done);
atomic_set(&mci->pe_complete, 1);
timeout = wait_for_completion_interruptible_timeout(
&mci->pumpx_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(mci->dev,
"%s: wait pumpx timeout, ret = %d\n", __func__, ret);
return ret;
}
static int __mt6360_set_pep20_current_pattern(struct mt6360_chg_info *mci,
u32 uV)
{
int ret = 0;
u32 data = 0;
dev_dbg(mci->dev, "%s: pep2.0 = %d\n", __func__, uV);
mutex_lock(&mci->pe_lock);
linear_range_get_selector_within(
&mt6360_chg_range[MT6360_RANGE_PUMPX20], uV, &data);
/* Set to PE2.0 */
ret = regmap_update_bits(mci->regmap, MT6360_PMU_CHG_CTRL17,
MT6360_PUMPX_20_10_MASK, 0xff);
if (ret < 0) {
dev_err(mci->dev, "%s: enable pumpx 20 fail\n", __func__);
goto out;
}
/* Set Voltage */
ret = regmap_update_bits(mci->regmap,
MT6360_PMU_CHG_CTRL17,
MT6360_PUMPX_DEC_MASK,
data << MT6360_PUMPX_DEC_SHFT);
if (ret < 0) {
dev_err(mci->dev, "%s: set pumpx voltage fail\n", __func__);
goto out;
}
ret = mt6360_enable_pump_express(mci, true);
out:
mutex_unlock(&mci->pe_lock);
return ret;
}
static int __mt6360_get_adc(struct mt6360_chg_info *mci,
enum mt6360_adc_channel channel, int *min, int *max)
{
int ret = 0;
ret = iio_read_channel_processed(mci->channels[channel], min);
if (ret < 0) {
dev_info(mci->dev, "%s: fail(%d)\n", __func__, ret);
return ret;
}
*max = *min;
return 0;
}
static int __mt6360_enable_chg_type_det(struct mt6360_chg_info *mci, bool en)
{
int ret = 0;
struct mt6360_chg_platform_data *pdata = dev_get_platdata(mci->dev);
dev_info(mci->dev, "%s: en = %d\n", __func__, en);
if (!IS_ENABLED(CONFIG_TCPC_CLASS) || pdata->bc12_sel != 0)
return ret;
mutex_lock(&mci->chgdet_lock);
if (atomic_read(&mci->tcpc_attach) == en) {
dev_info(mci->dev, "%s attach(%d) is the same\n",
__func__, atomic_read(&mci->tcpc_attach));
goto out;
}
atomic_set(&mci->tcpc_attach, en);
ret = (en ? mt6360_chgdet_pre_process :
mt6360_chgdet_post_process)(mci);
out:
mutex_unlock(&mci->chgdet_lock);
return ret;
}
static int __mt6360_enable_otg(struct mt6360_chg_info *mci, bool en)
{
int ret = 0;
dev_dbg(mci->dev, "%s: en = %d\n", __func__, en);
ret = mt6360_enable_wdt(mci, en ? true : false);
if (ret < 0) {
dev_err(mci->dev, "%s: set wdt fail, en = %d\n", __func__, en);
return ret;
}
return regmap_update_bits(mci->regmap, MT6360_PMU_CHG_CTRL1,
MT6360_OPA_MODE_MASK, en ? 0xff : 0);
}
/* ================== */
/* External Functions */
/* ================== */
#if IS_ENABLED(CONFIG_MTK_CHARGER)
static int mt6360_set_ichg(struct charger_device *chg_dev, u32 uA)
{
struct mt6360_chg_info *mci = charger_get_data(chg_dev);
int ret = 0;
mutex_lock(&mci->ichg_lock);
ret = __mt6360_set_ichg(mci, uA);
mutex_unlock(&mci->ichg_lock);
return ret;
}
static int mt6360_get_ichg(struct charger_device *chg_dev, u32 *uA)
{
struct mt6360_chg_info *mci = charger_get_data(chg_dev);
int ret = 0;
unsigned int regval, value;
ret = regmap_read(mci->regmap, MT6360_PMU_CHG_CTRL7, &regval);
if (ret < 0)
return ret;
regval = (regval & MT6360_ICHG_MASK) >> MT6360_ICHG_SHFT;
ret = linear_range_get_value(&mt6360_chg_range[MT6360_RANGE_ICHG],
regval, &value);
if (!ret)
*uA = value;
return 0;
}
static int mt6360_enable_hidden_mode(struct charger_device *chg_dev, bool en)
{
struct mt6360_chg_info *mci = charger_get_data(chg_dev);
static const u8 pascode[] = { 0x69, 0x96, 0x63, 0x72, };
int ret = 0;
mutex_lock(&mci->hidden_mode_lock);
if (en) {
if (mci->hidden_mode_cnt == 0) {
ret = regmap_bulk_write(mci->regmap,
MT6360_PMU_TM_PAS_CODE1, pascode, 4);
if (ret < 0)
goto err;
}
mci->hidden_mode_cnt++;
} else {
if (mci->hidden_mode_cnt == 1)
ret = regmap_write(mci->regmap,
MT6360_PMU_TM_PAS_CODE1, 0x00);
mci->hidden_mode_cnt--;
if (ret < 0)
goto err;
}
mt_dbg(mci->dev, "%s: en = %d\n", __func__, en);
goto out;
err:
dev_err(mci->dev, "%s failed, en = %d\n", __func__, en);
out:
mutex_unlock(&mci->hidden_mode_lock);
return ret;
}
static int mt6360_enable(struct charger_device *chg_dev, bool en)
{
struct mt6360_chg_info *mci = charger_get_data(chg_dev);
return __mt6360_enable(mci, en);
}
static int mt6360_is_enabled(struct charger_device *chg_dev, bool *en)
{
struct mt6360_chg_info *mci = charger_get_data(chg_dev);
return __mt6360_is_enabled(mci, en);
}
static int mt6360_get_min_ichg(struct charger_device *chg_dev, u32 *uA)
{
*uA = 300000;
return 0;
}
static int mt6360_get_cv(struct charger_device *chg_dev, u32 *uV)
{
struct mt6360_chg_info *mci = charger_get_data(chg_dev);
int ret = 0;
unsigned int regval, value;
ret = regmap_read(mci->regmap, MT6360_PMU_CHG_CTRL4, &regval);
if (ret < 0)
return ret;
regval = (regval & MT6360_VOREG_MASK) >> MT6360_VOREG_SHFT;
ret = linear_range_get_value(&mt6360_chg_range[MT6360_RANGE_VOREG],
regval, &value);
if (!ret)
*uV = value;
return 0;
}
static int mt6360_set_cv(struct charger_device *chg_dev, u32 uV)
{
struct mt6360_chg_info *mci = charger_get_data(chg_dev);
u32 data = 0;
unsigned int regval = 0;
int ret = 0;
dev_dbg(mci->dev, "%s: cv = %d\n", __func__, uV);
ret = mt6360_get_cv(chg_dev, &data);
if (ret < 0 || (data == uV)) /* get cv fail or same */
return ret;
ret = mt6360_enable_hidden_mode(chg_dev, true);
if (ret < 0)
return ret;
ret = regmap_read(mci->regmap, MT6360_PMU_CHG_HIDDEN_CTRL22, &regval);
if (ret < 0)
goto out;
/* set reg0x45[6:5]=11 */
ret = regmap_set_bits(mci->regmap, MT6360_PMU_CHG_HIDDEN_CTRL22,
MT6360_BATOVP_LVL_MASK);
if (ret < 0)
goto out;
linear_range_get_selector_within(&mt6360_chg_range[MT6360_RANGE_VOREG],
uV, &data);
ret = regmap_update_bits(mci->regmap, MT6360_PMU_CHG_CTRL4,
MT6360_VOREG_MASK, data << MT6360_VOREG_SHFT);
if (ret < 0)
goto out;
mdelay(5);
ret = regmap_write(mci->regmap, MT6360_PMU_CHG_HIDDEN_CTRL22, regval);
if (ret < 0)
goto out;
out:
mt6360_enable_hidden_mode(chg_dev, false);
return ret;
}
static int mt6360_set_aicr(struct charger_device *chg_dev, u32 uA)
{
struct mt6360_chg_info *mci = charger_get_data(chg_dev);
return __mt6360_set_aicr(mci, uA);
}
static int mt6360_get_aicr(struct charger_device *chg_dev, u32 *uA)
{
struct mt6360_chg_info *mci = charger_get_data(chg_dev);
int ret = 0;
unsigned int regval, value;
ret = regmap_read(mci->regmap, MT6360_PMU_CHG_CTRL3, &regval);
if (ret < 0)
return ret;
regval = (regval & MT6360_AICR_MASK) >> MT6360_AICR_SHFT;
ret = linear_range_get_value(&mt6360_chg_range[MT6360_RANGE_AICR],
regval, &value);
if (!ret)
*uA = value;
return 0;
}
static int mt6360_get_min_aicr(struct charger_device *chg_dev, u32 *uA)
{
*uA = 100000;
return 0;
}
static int mt6360_set_ieoc(struct charger_device *chg_dev, u32 uA)
{
struct mt6360_chg_info *mci = charger_get_data(chg_dev);
u32 data = 0;
dev_dbg(mci->dev, "%s: ieoc = %d\n", __func__, uA);
linear_range_get_selector_within(&mt6360_chg_range[MT6360_RANGE_IEOC],
uA, &data);
return regmap_update_bits(mci->regmap,
MT6360_PMU_CHG_CTRL9,
MT6360_IEOC_MASK,
data << MT6360_IEOC_SHFT);
}
static int mt6360_set_mivr(struct charger_device *chg_dev, u32 uV)
{
struct mt6360_chg_info *mci = charger_get_data(chg_dev);
u32 aicc_vth = 0, data = 0;
u8 aicc_vth_sel = 0;
int ret = 0;
mt_dbg(mci->dev, "%s: mivr = %d\n", __func__, uV);
if (uV < 3900000 || uV > 13400000) {
dev_err(mci->dev,
"%s: unsuitable mivr val(%d)\n", __func__, uV);
return -EINVAL;
}
aicc_vth = uV + 200000;
linear_range_get_selector_within(
&mt6360_chg_range[MT6360_RANGE_AICC_VTH], aicc_vth, &data);
ret = regmap_update_bits(mci->regmap,
MT6360_PMU_CHG_CTRL16,
MT6360_AICC_VTH_MASK,
aicc_vth_sel << MT6360_AICC_VTH_SHFT);
if (ret < 0)
return ret;
linear_range_get_selector_within(&mt6360_chg_range[MT6360_RANGE_VMIVR],
uV, &data);
return regmap_update_bits(mci->regmap,
MT6360_PMU_CHG_CTRL6,
MT6360_MIVR_MASK,
data << MT6360_MIVR_SHFT);
}
static inline int mt6360_get_mivr(struct charger_device *chg_dev, u32 *uV)
{
struct mt6360_chg_info *mci = charger_get_data(chg_dev);
int ret = 0;
unsigned int regval, value;
ret = regmap_read(mci->regmap, MT6360_PMU_CHG_CTRL6, &regval);
if (ret < 0)
return ret;
regval = (regval & MT6360_MIVR_MASK) >> MT6360_MIVR_SHFT;
ret = linear_range_get_value(&mt6360_chg_range[MT6360_RANGE_VMIVR],
regval, &value);
if (!ret)
*uV = value;
return 0;
}
static int mt6360_get_mivr_state(struct charger_device *chg_dev, bool *in_loop)
{
struct mt6360_chg_info *mci = charger_get_data(chg_dev);
int ret = 0;
unsigned int regval;
ret = regmap_read(mci->regmap, MT6360_PMU_CHG_STAT1, &regval);
if (ret < 0)
return ret;
*in_loop = (regval & MT6360_MIVR_EVT_MASK) >> MT6360_MIVR_EVT_SHFT;
return 0;
}
static int mt6360_enable_te(struct charger_device *chg_dev, bool en)
{
struct mt6360_chg_info *mci = charger_get_data(chg_dev);
dev_info(mci->dev, "%s: en = %d\n", __func__, en);
return regmap_update_bits(mci->regmap, MT6360_PMU_CHG_CTRL2,
MT6360_TE_EN_MASK, en ? 0xff : 0);
}
static int mt6360_set_pep_current_pattern(struct charger_device *chg_dev,
bool is_inc)
{
struct mt6360_chg_info *mci = charger_get_data(chg_dev);
int ret = 0;
dev_dbg(mci->dev, "%s: pe1.0 pump up = %d\n", __func__, is_inc);
mutex_lock(&mci->pe_lock);
/* Set to PE1.0 */
ret = regmap_update_bits(mci->regmap, MT6360_PMU_CHG_CTRL17,
MT6360_PUMPX_20_10_MASK, 0);
if (ret < 0) {
dev_err(mci->dev, "%s: enable pumpx 10 fail\n", __func__);
goto out;
}
/* Set Pump Up/Down */
ret = regmap_update_bits(mci->regmap,
MT6360_PMU_CHG_CTRL17,
MT6360_PUMPX_UP_DN_MASK,
is_inc ? 0xff : 0);
if (ret < 0) {
dev_err(mci->dev, "%s: set pumpx up/down fail\n", __func__);
goto out;
}
ret = mt6360_enable_pump_express(mci, false);
out:
mutex_unlock(&mci->pe_lock);
return ret;
}
static int mt6360_set_pep20_efficiency_table(struct charger_device *chg_dev)
{
return 0;
}
static int mt6360_set_pep20_current_pattern(struct charger_device *chg_dev,
u32 uV)
{
struct mt6360_chg_info *mci = charger_get_data(chg_dev);
return __mt6360_set_pep20_current_pattern(mci, uV);
}
static int mt6360_reset_ta(struct charger_device *chg_dev)
{
struct mt6360_chg_info *mci = charger_get_data(chg_dev);
int ret = 0;
dev_dbg(mci->dev, "%s\n", __func__);
ret = mt6360_set_mivr(chg_dev, 4600000);
if (ret < 0)
return ret;
ret = mt6360_select_input_current_limit(mci, MT6360_IINLMTSEL_AICR);
if (ret < 0)
return ret;
ret = mt6360_set_aicr(chg_dev, 100000);
if (ret < 0)
return ret;
msleep(250);
return mt6360_set_aicr(chg_dev, 500000);
}
static int mt6360_enable_cable_drop_comp(struct charger_device *chg_dev,
bool en)
{
struct mt6360_chg_info *mci = charger_get_data(chg_dev);
int ret = 0;
dev_info(mci->dev, "%s: en = %d\n", __func__, en);
if (en)
return ret;
/* Set to PE2.0 */
mutex_lock(&mci->pe_lock);
ret = regmap_update_bits(mci->regmap, MT6360_PMU_CHG_CTRL17,
MT6360_PUMPX_20_10_MASK, 0xff);
if (ret < 0) {
dev_err(mci->dev, "%s: enable pumpx 20 fail\n", __func__);
goto out;
}
/* Disable cable drop compensation */
ret = regmap_update_bits(mci->regmap,
MT6360_PMU_CHG_CTRL17,
MT6360_PUMPX_DEC_MASK,
0x1F << MT6360_PUMPX_DEC_SHFT);
if (ret < 0) {
dev_err(mci->dev, "%s: set pumpx voltage fail\n", __func__);
goto out;
}
ret = mt6360_enable_pump_express(mci, true);
out:
mutex_unlock(&mci->pe_lock);
return ret;
}
static inline int mt6360_get_aicc(struct mt6360_chg_info *mci,
u32 *aicc_val)
{
u8 aicc_sel = 0;
int ret = 0;
unsigned int regval;
ret = regmap_read(mci->regmap, MT6360_PMU_CHG_AICC_RESULT, &regval);
if (ret < 0) {
dev_err(mci->dev, "%s: read aicc result fail\n", __func__);
return ret;
}
aicc_sel = (regval & MT6360_AICC_RESULT_MASK) >>
MT6360_AICC_RESULT_SHFT;
*aicc_val = (aicc_sel * 50000) + 100000;
return 0;
}
static inline int mt6360_post_aicc_measure(struct charger_device *chg_dev,
u32 start, u32 stop, u32 step,
u32 *measure)
{
struct mt6360_chg_info *mci = charger_get_data(chg_dev);
int cur, ret;
unsigned int regval;
mt_dbg(mci->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 = mt6360_set_aicr(chg_dev, cur + step);
if (ret < 0)
return ret;
usleep_range(150, 200);
ret = regmap_read(mci->regmap, MT6360_PMU_CHG_STAT1, &regval);
if (ret < 0)
return ret;
/* read mivr stat */
if (regval & MT6360_MIVR_EVT_MASK)
break;
}
*measure = cur;
return 0;
}
static int mt6360_run_aicc(struct charger_device *chg_dev, u32 *uA)
{
struct mt6360_chg_info *mci = charger_get_data(chg_dev);
struct mt6360_chg_platform_data *pdata = dev_get_platdata(mci->dev);
int ret = 0;
u32 aicc_val = 0, aicr_val;
long timeout;
bool mivr_stat = false;
unsigned int regval;
ret = regmap_read(mci->regmap, MT6360_PMU_CHG_STAT1, &regval);
if (ret < 0) {
dev_err(mci->dev, "%s: read mivr stat fail\n", __func__);
return ret;
}
mivr_stat = (regval & MT6360_MIVR_EVT_MASK) ? true : false;
if (!mivr_stat) {
dev_err(mci->dev, "%s: mivr stat not act\n", __func__);
return ret;
}
mutex_lock(&mci->pe_lock);
ret = regmap_update_bits(mci->regmap, MT6360_PMU_CHG_CTRL14,
MT6360_RG_EN_AICC_MASK, 0xff);
if (ret < 0)
goto out;
/* Clear AICC measurement IRQ */
reinit_completion(&mci->aicc_done);
timeout = wait_for_completion_interruptible_timeout(
&mci->aicc_done, msecs_to_jiffies(3000));
if (timeout == 0)
ret = -ETIMEDOUT;
else if (timeout < 0)
ret = -EINTR;
else
ret = 0;
if (ret < 0) {
dev_err(mci->dev,
"%s: wait AICC time out, ret = %d\n", __func__, ret);
goto out;
}
/* get aicc_result */
ret = mt6360_get_aicc(mci, &aicc_val);
if (ret < 0) {
dev_err(mci->dev, "%s: get aicc result fail\n", __func__);
goto out;
}
if (!pdata->post_aicc)
goto skip_post_aicc;
dev_info(mci->dev, "%s: aicc pre val = %d\n", __func__, aicc_val);
ret = mt6360_get_aicr(chg_dev, &aicr_val);
if (ret < 0) {
dev_err(mci->dev, "%s: get aicr fail\n", __func__);
goto out;
}
ret = mt6360_set_aicr(chg_dev, aicc_val);
if (ret < 0) {
dev_err(mci->dev, "%s: set aicr fail\n", __func__);
goto out;
}
ret = regmap_update_bits(mci->regmap, MT6360_PMU_CHG_CTRL14,
MT6360_RG_EN_AICC_MASK, 0);
if (ret < 0)
goto out;
/* always start/end aicc_val/aicc_val+200mA */
ret = mt6360_post_aicc_measure(chg_dev, aicc_val,
aicc_val + 200000, 50000, &aicc_val);
if (ret < 0)
goto out;
ret = mt6360_set_aicr(chg_dev, aicr_val);
if (ret < 0) {
dev_err(mci->dev, "%s: set aicr fail\n", __func__);
goto out;
}
dev_info(mci->dev, "%s: aicc post val = %d\n", __func__, aicc_val);
skip_post_aicc:
*uA = aicc_val;
out:
/* Clear EN_AICC */
ret = regmap_update_bits(mci->regmap, MT6360_PMU_CHG_CTRL14,
MT6360_RG_EN_AICC_MASK, 0);
mutex_unlock(&mci->pe_lock);
return ret;
}
static int mt6360_enable_power_path(struct charger_device *chg_dev,
bool en)
{
struct mt6360_chg_info *mci = charger_get_data(chg_dev);
dev_dbg(mci->dev, "%s: en = %d\n", __func__, en);
return regmap_update_bits(mci->regmap, MT6360_PMU_CHG_CTRL1,
MT6360_FSLP_MASK, en ? 0 : 0xff);
}
static int mt6360_is_power_path_enabled(struct charger_device *chg_dev,
bool *en)
{
struct mt6360_chg_info *mci = charger_get_data(chg_dev);
int ret = 0;
unsigned int regval;
ret = regmap_read(mci->regmap, MT6360_PMU_CHG_CTRL1, &regval);
if (ret < 0)
return ret;
*en = (regval & MT6360_FSLP_MASK) ? false : true;
return 0;
}
static int mt6360_enable_safety_timer(struct charger_device *chg_dev,
bool en)
{
struct mt6360_chg_info *mci = charger_get_data(chg_dev);
dev_dbg(mci->dev, "%s: en = %d\n", __func__, en);
return regmap_update_bits(mci->regmap, MT6360_PMU_CHG_CTRL12,
MT6360_TMR_EN_MASK, en ? 0xff : 0);
}
static int mt6360_is_safety_timer_enabled(
struct charger_device *chg_dev, bool *en)
{
struct mt6360_chg_info *mci = charger_get_data(chg_dev);
int ret = 0;
unsigned int regval;
ret = regmap_read(mci->regmap, MT6360_PMU_CHG_CTRL12, &regval);
if (ret < 0)
return ret;
*en = (regval & MT6360_TMR_EN_MASK) ? true : false;
return 0;
}
static int mt6360_enable_hz(struct charger_device *chg_dev, bool en)
{
struct mt6360_chg_info *mci = charger_get_data(chg_dev);
dev_info(mci->dev, "%s: en = %d\n", __func__, en);
return regmap_update_bits(mci->regmap, MT6360_PMU_CHG_CTRL1,
MT6360_HZ_EN_MASK, en ? 0xff : 0);
}
static const u32 otg_oc_table[] = {
500000, 700000, 1100000, 1300000, 1800000, 2100000, 2400000,
};
static int mt6360_set_otg_current_limit(struct charger_device *chg_dev,
u32 uA)
{
struct mt6360_chg_info *mci = charger_get_data(chg_dev);
int i;
/* Set higher OC threshold protect */
for (i = 0; i < ARRAY_SIZE(otg_oc_table); i++) {
if (uA <= otg_oc_table[i])
break;
}
dev_dbg(mci->dev,
"%s: select oc threshold = %d\n", __func__, otg_oc_table[i]);
return regmap_update_bits(mci->regmap,
MT6360_PMU_CHG_CTRL10,
MT6360_OTG_OC_MASK,
i << MT6360_OTG_OC_SHFT);
}
static int mt6360_enable_otg(struct charger_device *chg_dev, bool en)
{
struct mt6360_chg_info *mci = charger_get_data(chg_dev);
return __mt6360_enable_otg(mci, en);
}
static int mt6360_enable_discharge(struct charger_device *chg_dev,
bool en)
{
struct mt6360_chg_info *mci = charger_get_data(chg_dev);
int i, ret = 0;
const int dischg_retry_cnt = 3;
bool is_dischg;
unsigned int regval;
dev_dbg(mci->dev, "%s: en = %d\n", __func__, en);
ret = mt6360_enable_hidden_mode(mci->chg_dev, true);
if (ret < 0)
return ret;
/* Set bit2 of reg[0x31] to 1/0 to enable/disable discharging */
ret = regmap_update_bits(mci->regmap, MT6360_PMU_CHG_HIDDEN_CTRL2,
MT6360_DISCHG_MASK, en ? 0xff : 0);
if (ret < 0) {
dev_err(mci->dev, "%s: fail, en = %d\n", __func__, en);
goto out;
}
if (!en) {
for (i = 0; i < dischg_retry_cnt; i++) {
ret = regmap_read(mci->regmap,
MT6360_PMU_CHG_HIDDEN_CTRL2, &regval);
is_dischg = (regval & MT6360_DISCHG_MASK) ?
true : false;
if (!is_dischg)
break;
ret = regmap_update_bits(mci->regmap,
MT6360_PMU_CHG_HIDDEN_CTRL2,
MT6360_DISCHG_MASK, 0);
if (ret < 0) {
dev_err(mci->dev,
"%s: disable dischg failed\n",
__func__);
goto out;
}
}
if (i == dischg_retry_cnt) {
dev_err(mci->dev, "%s: dischg failed\n", __func__);
ret = -EINVAL;
}
}
out:
mt6360_enable_hidden_mode(mci->chg_dev, false);
return ret;
}
static int mt6360_enable_chg_type_det(struct charger_device *chg_dev, bool en)
{
struct mt6360_chg_info *mci = charger_get_data(chg_dev);
return __mt6360_enable_chg_type_det(mci, en);
}
static int mt6360_get_adc(struct charger_device *chg_dev, enum adc_channel chan,
int *min, int *max)
{
struct mt6360_chg_info *mci = charger_get_data(chg_dev);
enum mt6360_adc_channel channel;
switch (chan) {
case ADC_CHANNEL_VBUS:
channel = MT6360_ADC_VBUSDIV5;
break;
case ADC_CHANNEL_VSYS:
channel = MT6360_ADC_VSYS;
break;
case ADC_CHANNEL_VBAT:
channel = MT6360_ADC_VBAT;
break;
case ADC_CHANNEL_IBUS:
channel = MT6360_ADC_IBUS;
break;
case ADC_CHANNEL_IBAT:
channel = MT6360_ADC_IBAT;
break;
case ADC_CHANNEL_TEMP_JC:
channel = MT6360_ADC_TEMP_JC;
break;
case ADC_CHANNEL_USBID:
channel = MT6360_ADC_USBID;
break;
case ADC_CHANNEL_TS:
channel = MT6360_ADC_TS;
break;
default:
return -ENOTSUPP;
}
return __mt6360_get_adc(mci, channel, min, max);
}
static int mt6360_get_vbus(struct charger_device *chg_dev, u32 *vbus)
{
struct mt6360_chg_info *mci = charger_get_data(chg_dev);
mt_dbg(mci->dev, "%s\n", __func__);
return mt6360_get_adc(chg_dev, ADC_CHANNEL_VBUS, vbus, vbus);
}
static int mt6360_get_ibus(struct charger_device *chg_dev, u32 *ibus)
{
struct mt6360_chg_info *mci = charger_get_data(chg_dev);
mt_dbg(mci->dev, "%s\n", __func__);
return mt6360_get_adc(chg_dev, ADC_CHANNEL_IBUS, ibus, ibus);
}
static int mt6360_get_ibat(struct charger_device *chg_dev, u32 *ibat)
{
struct mt6360_chg_info *mci = charger_get_data(chg_dev);
mt_dbg(mci->dev, "%s\n", __func__);
return mt6360_get_adc(chg_dev, ADC_CHANNEL_IBAT, ibat, ibat);
}
static int mt6360_get_tchg(struct charger_device *chg_dev,
int *tchg_min, int *tchg_max)
{
struct mt6360_chg_info *mci = charger_get_data(chg_dev);
int temp_jc = 0, ret = 0, retry_cnt = 3;
mt_dbg(mci->dev, "%s\n", __func__);
/* temp abnormal Workaround */
do {
ret = mt6360_get_adc(chg_dev, ADC_CHANNEL_TEMP_JC,
&temp_jc, &temp_jc);
if (ret < 0) {
dev_err(mci->dev,
"%s: failed, ret = %d\n", __func__, ret);
return ret;
}
} while (temp_jc >= 120 && (retry_cnt--) > 0);
mutex_lock(&mci->tchg_lock);
if (temp_jc >= 120)
temp_jc = mci->tchg;
else
mci->tchg = temp_jc;
mutex_unlock(&mci->tchg_lock);
*tchg_min = *tchg_max = temp_jc;
dev_info(mci->dev, "%s: tchg = %d\n", __func__, temp_jc);
return 0;
}
static int mt6360_kick_wdt(struct charger_device *chg_dev)
{
struct mt6360_chg_info *mci = charger_get_data(chg_dev);
dev_dbg(mci->dev, "%s\n", __func__);
return regmap_read(mci->regmap, MT6360_PMU_CHG_CTRL1, NULL);
}
static int mt6360_safety_check(struct charger_device *chg_dev, u32 polling_ieoc)
{
struct mt6360_chg_info *mci = charger_get_data(chg_dev);
int ret, ibat = 0;
static int eoc_cnt;
mt_dbg(mci->dev, "%s\n", __func__);
ret = iio_read_channel_processed(mci->channels[MT6360_ADC_IBAT],
&ibat);
if (ret < 0)
dev_err(mci->dev, "%s: failed, ret = %d\n", __func__, 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(mci->dev, "%s: polling_ieoc = %d, ibat = %d\n",
__func__, polling_ieoc, ibat);
charger_dev_notify(mci->chg_dev, CHARGER_DEV_NOTIFY_EOC);
eoc_cnt = 0;
}
return ret;
}
static int mt6360_reset_eoc_state(struct charger_device *chg_dev)
{
struct mt6360_chg_info *mci = charger_get_data(chg_dev);
int ret = 0;
dev_dbg(mci->dev, "%s\n", __func__);
ret = mt6360_enable_hidden_mode(mci->chg_dev, true);
if (ret < 0)
return ret;
ret = regmap_update_bits(mci->regmap, MT6360_PMU_CHG_HIDDEN_CTRL1,
MT6360_EOC_RST_MASK, 0xff);
if (ret < 0) {
dev_err(mci->dev, "%s: set failed, ret = %d\n", __func__, ret);
goto out;
}
udelay(100);
ret = regmap_update_bits(mci->regmap, MT6360_PMU_CHG_HIDDEN_CTRL1,
MT6360_EOC_RST_MASK, 0);
if (ret < 0) {
dev_err(mci->dev,
"%s: clear failed, ret = %d\n", __func__, ret);
goto out;
}
out:
mt6360_enable_hidden_mode(mci->chg_dev, false);
return ret;
}
static int mt6360_is_charging_done(struct charger_device *chg_dev,
bool *done)
{
struct mt6360_chg_info *mci = charger_get_data(chg_dev);
enum mt6360_charging_status chg_stat;
int ret = 0;
mt_dbg(mci->dev, "%s\n", __func__);
ret = mt6360_get_charging_status(mci, &chg_stat);
if (ret < 0)
return ret;
*done = (chg_stat == MT6360_CHG_STATUS_DONE) ? true : false;
return 0;
}
static int mt6360_get_zcv(struct charger_device *chg_dev, u32 *uV)
{
struct mt6360_chg_info *mci = charger_get_data(chg_dev);
dev_info(mci->dev, "%s: zcv = %dmV\n", __func__, mci->zcv / 1000);
*uV = mci->zcv;
return 0;
}
static int mt6360_dump_registers(struct charger_device *chg_dev)
{
struct mt6360_chg_info *mci = charger_get_data(chg_dev);
int i, ret = 0;
int adc_vals[MT6360_ADC_MAX];
u32 ichg = 0, aicr = 0, mivr = 0, cv = 0, ieoc = 0;
enum mt6360_charging_status chg_stat = MT6360_CHG_STATUS_READY;
bool chg_en = false;
u8 chg_stat1 = 0, chg_ctrl[2] = {0};
unsigned int regval;
dev_dbg(mci->dev, "%s\n", __func__);
ret = mt6360_get_ichg(chg_dev, &ichg);
ret |= mt6360_get_aicr(chg_dev, &aicr);
ret |= mt6360_get_mivr(chg_dev, &mivr);
ret |= mt6360_get_cv(chg_dev, &cv);
ret |= mt6360_get_ieoc(mci, &ieoc);
ret |= mt6360_get_charging_status(mci, &chg_stat);
ret |= mt6360_is_charger_enabled(mci, &chg_en);
if (ret < 0) {
dev_notice(mci->dev, "%s: parse chg setting fail\n", __func__);
return ret;
}
for (i = 0; i < MT6360_ADC_MAX; i++) {
/* Skip unnecessary channel */
if (i >= MT6360_ADC_NODUMP)
break;
ret = iio_read_channel_processed(mci->channels[i],
&adc_vals[i]);
if (ret < 0) {
dev_err(mci->dev,
"%s: read [%s] adc fail(%d)\n",
__func__, mt6360_adc_chan_list[i], ret);
return ret;
}
}
ret = regmap_read(mci->regmap, MT6360_PMU_CHG_STAT1, &regval);
if (ret < 0)
return ret;
chg_stat1 = regval;
ret = regmap_bulk_read(mci->regmap, MT6360_PMU_CHG_CTRL1, chg_ctrl, 2);
if (ret < 0)
return ret;
dev_info(mci->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(mci->dev,
"%s: VBUS = %dmV, IBUS = %dmA, VSYS = %dmV, VBAT = %dmV, IBAT = %dmA\n",
__func__,
adc_vals[MT6360_ADC_VBUSDIV5] / 1000,
adc_vals[MT6360_ADC_IBUS] / 1000,
adc_vals[MT6360_ADC_VSYS] / 1000,
adc_vals[MT6360_ADC_VBAT] / 1000,
adc_vals[MT6360_ADC_IBAT] / 1000);
dev_info(mci->dev, "%s: CHG_EN = %d, CHG_STATUS = %s, CHG_STAT1 = 0x%02X\n",
__func__, chg_en, mt6360_chg_status_name[chg_stat], chg_stat1);
dev_info(mci->dev, "%s: CHG_CTRL1 = 0x%02X, CHG_CTRL2 = 0x%02X\n",
__func__, chg_ctrl[0], chg_ctrl[1]);
return 0;
}
static int mt6360_do_event(struct charger_device *chg_dev, u32 event,
u32 args)
{
struct mt6360_chg_info *mci = charger_get_data(chg_dev);
mt_dbg(mci->dev, "%s\n", __func__);
switch (event) {
case EVENT_FULL:
case EVENT_RECHARGE:
case EVENT_DISCHARGE:
power_supply_changed(mci->psy);
break;
default:
break;
}
return 0;
}
static int mt6360_plug_in(struct charger_device *chg_dev)
{
struct mt6360_chg_info *mci = charger_get_data(chg_dev);
int ret = 0;
dev_dbg(mci->dev, "%s\n", __func__);
ret = mt6360_enable_wdt(mci, true);
if (ret < 0) {
dev_err(mci->dev, "%s: en wdt failed\n", __func__);
return ret;
}
return mt6360_enable_te(chg_dev, true);
}
static int mt6360_plug_out(struct charger_device *chg_dev)
{
struct mt6360_chg_info *mci = charger_get_data(chg_dev);
int ret = 0;
dev_dbg(mci->dev, "%s\n", __func__);
ret = mt6360_enable_wdt(mci, false);
if (ret < 0) {
dev_err(mci->dev, "%s: disable wdt failed\n", __func__);
return ret;
}
return mt6360_enable_te(chg_dev, false);
}
static int mt6360_enable_usbid(struct charger_device *chg_dev, bool en)
{
struct mt6360_chg_info *mci = charger_get_data(chg_dev);
return regmap_update_bits(mci->regmap, MT6360_PMU_USBID_CTRL1,
MT6360_USBID_EN_MASK, en ? 0xff : 0);
}
static int mt6360_set_usbid_rup(struct charger_device *chg_dev, u32 rup)
{
struct mt6360_chg_info *mci = charger_get_data(chg_dev);
u32 data = mt6360_trans_usbid_rup(rup);
return regmap_update_bits(mci->regmap, MT6360_PMU_USBID_CTRL1,
MT6360_ID_RPULSEL_MASK,
data << MT6360_ID_RPULSEL_SHFT);
}
static int mt6360_set_usbid_src_ton(struct charger_device *chg_dev, u32 src_ton)
{
struct mt6360_chg_info *mci = charger_get_data(chg_dev);
u32 data = mt6360_trans_usbid_src_ton(src_ton);
return regmap_update_bits(mci->regmap, MT6360_PMU_USBID_CTRL1,
MT6360_ISTDET_MASK,
data << MT6360_ISTDET_SHFT);
}
static int mt6360_enable_usbid_floating(struct charger_device *chg_dev, bool en)
{
struct mt6360_chg_info *mci = charger_get_data(chg_dev);
return regmap_update_bits(mci->regmap, MT6360_PMU_USBID_CTRL2,
MT6360_USBID_FLOAT_MASK, en ? 0xff : 0);
}
static int mt6360_enable_force_typec_otp(struct charger_device *chg_dev,
bool en)
{
struct mt6360_chg_info *mci = charger_get_data(chg_dev);
return regmap_update_bits(mci->regmap, MT6360_PMU_TYPEC_OTP_CTRL,
MT6360_TYPEC_OTP_SWEN_MASK, en ? 0xff : 0);
}
static int mt6360_get_ctd_dischg_status(struct charger_device *chg_dev,
u8 *status)
{
struct mt6360_chg_info *mci = charger_get_data(chg_dev);
*status = mci->ctd_dischg_status;
return 0;
}
static const struct charger_ops mt6360_chg_ops = {
/* cable plug in/out */
.plug_in = mt6360_plug_in,
.plug_out = mt6360_plug_out,
/* enable */
.enable = mt6360_enable,
.is_enabled = mt6360_is_enabled,
/* charging current */
.set_charging_current = mt6360_set_ichg,
.get_charging_current = mt6360_get_ichg,
.get_min_charging_current = mt6360_get_min_ichg,
/* charging voltage */
.set_constant_voltage = mt6360_set_cv,
.get_constant_voltage = mt6360_get_cv,
/* charging input current */
.set_input_current = mt6360_set_aicr,
.get_input_current = mt6360_get_aicr,
.get_min_input_current = mt6360_get_min_aicr,
/* set termination current */
.set_eoc_current = mt6360_set_ieoc,
/* charging mivr */
.set_mivr = mt6360_set_mivr,
.get_mivr = mt6360_get_mivr,
.get_mivr_state = mt6360_get_mivr_state,
/* charing termination */
.enable_termination = mt6360_enable_te,
/* PE+/PE+20 */
.send_ta_current_pattern = mt6360_set_pep_current_pattern,
.set_pe20_efficiency_table = mt6360_set_pep20_efficiency_table,
.send_ta20_current_pattern = mt6360_set_pep20_current_pattern,
.reset_ta = mt6360_reset_ta,
.enable_cable_drop_comp = mt6360_enable_cable_drop_comp,
.run_aicl = mt6360_run_aicc,
/* Power path */
.enable_powerpath = mt6360_enable_power_path,
.is_powerpath_enabled = mt6360_is_power_path_enabled,
/* safety timer */
.enable_safety_timer = mt6360_enable_safety_timer,
.is_safety_timer_enabled = mt6360_is_safety_timer_enabled,
/* OTG */
.enable_otg = mt6360_enable_otg,
.set_boost_current_limit = mt6360_set_otg_current_limit,
.enable_discharge = mt6360_enable_discharge,
/* Charger type detection */
.enable_chg_type_det = mt6360_enable_chg_type_det,
/* ADC */
.get_adc = mt6360_get_adc,
.get_vbus_adc = mt6360_get_vbus,
.get_ibus_adc = mt6360_get_ibus,
.get_ibat_adc = mt6360_get_ibat,
.get_tchg_adc = mt6360_get_tchg,
/* kick wdt */
.kick_wdt = mt6360_kick_wdt,
/* misc */
.safety_check = mt6360_safety_check,
.reset_eoc_state = mt6360_reset_eoc_state,
.is_charging_done = mt6360_is_charging_done,
.get_zcv = mt6360_get_zcv,
.dump_registers = mt6360_dump_registers,
.enable_hz = mt6360_enable_hz,
/* event */
.event = mt6360_do_event,
/* TypeC */
.enable_usbid = mt6360_enable_usbid,
.set_usbid_rup = mt6360_set_usbid_rup,
.set_usbid_src_ton = mt6360_set_usbid_src_ton,
.enable_usbid_floating = mt6360_enable_usbid_floating,
.enable_force_typec_otp = mt6360_enable_force_typec_otp,
.get_ctd_dischg_status = mt6360_get_ctd_dischg_status,
.enable_hidden_mode = mt6360_enable_hidden_mode,
};
static const struct charger_properties mt6360_chg_props = {
.alias_name = "mt6360_chg",
};
#endif /* CONFIG_MTK_CHARGER */
static irqreturn_t mt6360_pmu_chg_treg_evt_handler(int irq, void *data)
{
struct mt6360_chg_info *mci = data;
int ret = 0;
unsigned int regval;
dev_err(mci->dev, "%s\n", __func__);
ret = regmap_read(mci->regmap, MT6360_PMU_CHG_STAT1, &regval);
if (ret < 0)
return ret;
if ((regval & MT6360_CHG_TREG_MASK) >> MT6360_CHG_TREG_SHFT)
dev_err(mci->dev,
"%s: thermal regulation loop is active\n", __func__);
return IRQ_HANDLED;
}
static void mt6360_pmu_chg_irq_enable(const char *name, int en);
static irqreturn_t mt6360_pmu_chg_mivr_evt_handler(int irq, void *data)
{
struct mt6360_chg_info *mci = data;
mt_dbg(mci->dev, "%s\n", __func__);
mt6360_pmu_chg_irq_enable("chg_mivr_evt", 0);
atomic_inc(&mci->mivr_cnt);
wake_up(&mci->mivr_wq);
return IRQ_HANDLED;
}
static irqreturn_t mt6360_pmu_pwr_rdy_evt_handler(int irq, void *data)
{
struct mt6360_chg_info *mci = data;
bool pwr_rdy = false;
int ret = 0;
unsigned int regval;
ret = regmap_read(mci->regmap, MT6360_PMU_CHG_STAT1, &regval);
if (ret < 0)
return ret;
pwr_rdy = (regval & MT6360_PWR_RDY_EVT_MASK);
dev_info(mci->dev, "%s: pwr_rdy = %d\n", __func__, pwr_rdy);
return IRQ_HANDLED;
}
static irqreturn_t mt6360_pmu_chg_batsysuv_evt_handler(int irq, void *data)
{
struct mt6360_chg_info *mci = data;
dev_warn(mci->dev, "%s\n", __func__);
return IRQ_HANDLED;
}
static irqreturn_t mt6360_pmu_chg_vsysuv_evt_handler(int irq, void *data)
{
struct mt6360_chg_info *mci = data;
dev_warn(mci->dev, "%s\n", __func__);
return IRQ_HANDLED;
}
static irqreturn_t mt6360_pmu_chg_vsysov_evt_handler(int irq, void *data)
{
struct mt6360_chg_info *mci = data;
dev_warn(mci->dev, "%s\n", __func__);
return IRQ_HANDLED;
}
static irqreturn_t mt6360_pmu_chg_vbatov_evt_handler(int irq, void *data)
{
struct mt6360_chg_info *mci = data;
dev_warn(mci->dev, "%s\n", __func__);
return IRQ_HANDLED;
}
static irqreturn_t mt6360_pmu_chg_vbusov_evt_handler(int irq, void *data)
{
struct mt6360_chg_info *mci = data;
#if IS_ENABLED(CONFIG_MTK_CHARGER)
struct chgdev_notify *noti = &(mci->chg_dev->noti);
bool vbusov_stat = false;
int ret = 0;
unsigned int regval;
dev_warn(mci->dev, "%s\n", __func__);
ret = regmap_read(mci->regmap, MT6360_PMU_CHG_STAT2, &regval);
if (ret < 0)
goto out;
vbusov_stat = (regval & BIT(7));
noti->vbusov_stat = vbusov_stat;
dev_info(mci->dev, "%s: stat = %d\n", __func__, vbusov_stat);
out:
#else
dev_info(mci->dev, "%s\n", __func__);
#endif /* CONFIG_MTK_CHARGER */
return IRQ_HANDLED;
}
static irqreturn_t mt6360_pmu_wd_pmu_det_handler(int irq, void *data)
{
struct mt6360_chg_info *mci = data;
dev_info(mci->dev, "%s\n", __func__);
return IRQ_HANDLED;
}
static irqreturn_t mt6360_pmu_wd_pmu_done_handler(int irq, void *data)
{
struct mt6360_chg_info *mci = data;
dev_info(mci->dev, "%s\n", __func__);
return IRQ_HANDLED;
}
static irqreturn_t mt6360_pmu_chg_tmri_handler(int irq, void *data)
{
struct mt6360_chg_info *mci = data;
int ret = 0;
unsigned int regval;
dev_notice(mci->dev, "%s\n", __func__);
ret = regmap_read(mci->regmap, MT6360_PMU_CHG_STAT4, &regval);
if (ret < 0)
return IRQ_HANDLED;
dev_info(mci->dev, "%s: chg_stat4 = 0x%02x\n", __func__, ret);
if (!(regval & MT6360_CHG_TMRI_MASK))
return IRQ_HANDLED;
#if IS_ENABLED(CONFIG_MTK_CHARGER)
charger_dev_notify(mci->chg_dev, CHARGER_DEV_NOTIFY_SAFETY_TIMEOUT);
#endif /* CONFIG_MTK_CHARGER */
return IRQ_HANDLED;
}
static irqreturn_t mt6360_pmu_chg_aiccmeasl_handler(int irq, void *data)
{
struct mt6360_chg_info *mci = data;
dev_dbg(mci->dev, "%s\n", __func__);
complete(&mci->aicc_done);
return IRQ_HANDLED;
}
static irqreturn_t mt6360_pmu_wdtmri_handler(int irq, void *data)
{
struct mt6360_chg_info *mci = data;
int ret;
unsigned int regval;
dev_warn(mci->dev, "%s\n", __func__);
/* Any I2C R/W can kick watchdog timer */
ret = regmap_read(mci->regmap, MT6360_PMU_CHG_CTRL1, &regval);
if (ret < 0)
dev_err(mci->dev, "%s: kick wdt failed\n", __func__);
return IRQ_HANDLED;
}
static irqreturn_t mt6360_pmu_chg_rechgi_handler(int irq, void *data)
{
struct mt6360_chg_info *mci = data;
dev_dbg(mci->dev, "%s\n", __func__);
power_supply_changed(mci->psy);
return IRQ_HANDLED;
}
static irqreturn_t mt6360_pmu_chg_termi_handler(int irq, void *data)
{
struct mt6360_chg_info *mci = data;
dev_dbg(mci->dev, "%s\n", __func__);
return IRQ_HANDLED;
}
static irqreturn_t mt6360_pmu_chg_ieoci_handler(int irq, void *data)
{
struct mt6360_chg_info *mci = data;
bool ieoc_stat = false;
int ret = 0;
unsigned int regval;
dev_dbg(mci->dev, "%s\n", __func__);
ret = regmap_read(mci->regmap, MT6360_PMU_CHG_STAT4, &regval);
if (ret < 0)
goto out;
ieoc_stat = (regval & BIT(7));
if (!ieoc_stat)
goto out;
power_supply_changed(mci->psy);
out:
return IRQ_HANDLED;
}
static irqreturn_t mt6360_pmu_pumpx_donei_handler(int irq, void *data)
{
struct mt6360_chg_info *mci = data;
dev_info(mci->dev, "%s\n", __func__);
atomic_set(&mci->pe_complete, 0);
complete(&mci->pumpx_done);
return IRQ_HANDLED;
}
static irqreturn_t mt6360_pmu_attach_i_handler(int irq, void *data)
{
struct mt6360_chg_info *mci = data;
struct mt6360_chg_platform_data *pdata = dev_get_platdata(mci->dev);
dev_dbg(mci->dev, "%s\n", __func__);
if (pdata->bc12_sel != 0)
return IRQ_HANDLED;
mutex_lock(&mci->chgdet_lock);
if (!mci->bc12_en) {
dev_err(mci->dev, "%s: bc12 disabled, ignore irq\n",
__func__);
goto out;
}
mt6360_chgdet_post_process(mci);
out:
mutex_unlock(&mci->chgdet_lock);
return IRQ_HANDLED;
}
static irqreturn_t mt6360_pmu_hvdcp_det_handler(int irq, void *data)
{
struct mt6360_chg_info *mci = data;
dev_dbg(mci->dev, "%s\n", __func__);
return IRQ_HANDLED;
}
static irqreturn_t mt6360_pmu_dcdti_handler(int irq, void *data)
{
struct mt6360_chg_info *mci = data;
dev_dbg(mci->dev, "%s\n", __func__);
return IRQ_HANDLED;
}
static irqreturn_t mt6360_pmu_chrdet_ext_evt_handler(int irq, void *data)
{
struct mt6360_chg_info *mci = data;
struct mt6360_chg_platform_data *pdata = dev_get_platdata(mci->dev);
int ret = 0;
bool pwr_rdy = false;
ret = mt6360_get_chrdet_ext_stat(mci, &pwr_rdy);
dev_info(mci->dev, "%s: pwr_rdy = %d\n", __func__, pwr_rdy);
if (ret < 0)
goto out;
if (mci->pwr_rdy == pwr_rdy)
goto out;
mci->pwr_rdy = pwr_rdy;
if (!IS_ENABLED(CONFIG_TCPC_CLASS) || pdata->bc12_sel != 0) {
mutex_lock(&mci->chgdet_lock);
(pwr_rdy ? mt6360_chgdet_pre_process :
mt6360_chgdet_post_process)(mci);
mutex_unlock(&mci->chgdet_lock);
}
if (atomic_read(&mci->pe_complete) && pwr_rdy == true &&
mci->chip_rev <= 0x02) {
dev_info(mci->dev, "%s: re-trigger pe20 pattern\n", __func__);
queue_work(mci->pe_wq, &mci->pe_work);
}
out:
return IRQ_HANDLED;
}
static struct mt6360_pmu_irq_desc mt6360_pmu_chg_irq_desc[] = {
{ "chg_treg_evt", mt6360_pmu_chg_treg_evt_handler },
{ "chg_mivr_evt", mt6360_pmu_chg_mivr_evt_handler },
{ "pwr_rdy_evt", mt6360_pmu_pwr_rdy_evt_handler },
{ "chg_batsysuv_evt", mt6360_pmu_chg_batsysuv_evt_handler },
{ "chg_vsysuv_evt", mt6360_pmu_chg_vsysuv_evt_handler },
{ "chg_vsysov_evt", mt6360_pmu_chg_vsysov_evt_handler },
{ "chg_vbatov_evt", mt6360_pmu_chg_vbatov_evt_handler },
{ "chg_vbusov_evt", mt6360_pmu_chg_vbusov_evt_handler },
{ "wd_pmu_det", mt6360_pmu_wd_pmu_det_handler },
{ "wd_pmu_done", mt6360_pmu_wd_pmu_done_handler },
{ "chg_tmri", mt6360_pmu_chg_tmri_handler },
{ "chg_aiccmeasl", mt6360_pmu_chg_aiccmeasl_handler },
{ "wdtmri", mt6360_pmu_wdtmri_handler },
{ "chg_rechgi", mt6360_pmu_chg_rechgi_handler },
{ "chg_termi", mt6360_pmu_chg_termi_handler },
{ "chg_ieoci", mt6360_pmu_chg_ieoci_handler },
{ "pumpx_donei", mt6360_pmu_pumpx_donei_handler },
{ "attach_i", mt6360_pmu_attach_i_handler },
{ "hvdcp_det", mt6360_pmu_hvdcp_det_handler },
{ "dcdti", mt6360_pmu_dcdti_handler },
{ "chrdet_ext_evt", mt6360_pmu_chrdet_ext_evt_handler },
};
static void mt6360_pmu_chg_irq_enable(const char *name, int en)
{
struct mt6360_pmu_irq_desc *irq_desc;
int i = 0;
if (unlikely(!name))
return;
for (i = 0; i < ARRAY_SIZE(mt6360_pmu_chg_irq_desc); i++) {
irq_desc = mt6360_pmu_chg_irq_desc + i;
if (unlikely(!irq_desc->name))
continue;
if (!strcmp(irq_desc->name, name)) {
if (en)
enable_irq(irq_desc->irq);
else
disable_irq_nosync(irq_desc->irq);
break;
}
}
}
static void mt6360_pmu_chg_irq_register(struct platform_device *pdev)
{
struct mt6360_pmu_irq_desc *irq_desc;
int i, ret;
for (i = 0; i < ARRAY_SIZE(mt6360_pmu_chg_irq_desc); i++) {
irq_desc = mt6360_pmu_chg_irq_desc + i;
if (unlikely(!irq_desc->name))
continue;
ret = platform_get_irq_byname(pdev, irq_desc->name);
if (ret < 0)
continue;
irq_desc->irq = ret;
ret = devm_request_threaded_irq(&pdev->dev, irq_desc->irq, NULL,
irq_desc->irq_handler,
IRQF_TRIGGER_FALLING,
irq_desc->name,
platform_get_drvdata(pdev));
if (ret < 0)
dev_err(&pdev->dev,
"request %s irq fail\n", irq_desc->name);
}
}
static int mt6360_toggle_cfo(struct mt6360_chg_info *mci)
{
int ret = 0;
u32 data = 0;
ret = regmap_read(mci->regmap, MT6360_PMU_FLED_EN, &data);
if (ret < 0)
return ret;
if (data & MT6360_STROBE_EN_MASK) {
dev_notice(mci->dev, "%s: fled in strobe mode\n", __func__);
goto out;
}
ret = regmap_update_bits(mci->regmap, MT6360_PMU_CHG_CTRL2,
MT6360_CFO_EN_MASK, 0);
if (ret < 0) {
dev_err(mci->dev, "%s: failed to clear cfo_en\n", __func__);
goto out;
}
if (regmap_update_bits(mci->regmap, MT6360_PMU_CHG_CTRL2,
MT6360_CFO_EN_MASK, 0xFF) < 0)
dev_err(mci->dev, "%s: failed to set cfo_en\n", __func__);
out:
return ret;
}
static int mt6360_chg_mivr_task_threadfn(void *data)
{
struct mt6360_chg_info *mci = data;
u32 ibus;
int ret;
unsigned int regval;
dev_info(mci->dev, "%s ++\n", __func__);
while (!kthread_should_stop()) {
wait_event(mci->mivr_wq, atomic_read(&mci->mivr_cnt) > 0 ||
kthread_should_stop());
mt_dbg(mci->dev, "%s: enter mivr thread\n", __func__);
if (kthread_should_stop())
break;
pm_stay_awake(mci->dev);
/* check real mivr stat or not */
ret = regmap_read(mci->regmap, MT6360_PMU_CHG_STAT1, &regval);
if (ret < 0)
goto loop_cont;
if (!(regval & MT6360_MIVR_EVT_MASK)) {
mt_dbg(mci->dev, "%s: mivr stat not act\n", __func__);
goto loop_cont;
}
/* read ibus adc */
ret = __mt6360_get_adc(mci, MT6360_ADC_IBUS, &ibus, &ibus);
if (ret < 0) {
dev_err(mci->dev, "%s: get ibus adc fail\n", __func__);
goto loop_cont;
}
/* if ibus adc value < 100mA), toggle cfo */
if (ibus < 100000) {
dev_dbg(mci->dev, "%s: enter toggle cfo\n", __func__);
ret = mt6360_toggle_cfo(mci);
if (ret < 0)
dev_err(mci->dev,
"%s: toggle cfo fail\n", __func__);
}
loop_cont:
pm_relax(mci->dev);
atomic_set(&mci->mivr_cnt, 0);
mt6360_pmu_chg_irq_enable("chg_mivr_evt", 1);
msleep(200);
}
dev_info(mci->dev, "%s --\n", __func__);
return 0;
}
static void mt6360_trigger_pep_work_handler(struct work_struct *work)
{
struct mt6360_chg_info *mci =
(struct mt6360_chg_info *)container_of(work,
struct mt6360_chg_info, pe_work);
int ret = 0;
ret = __mt6360_set_pep20_current_pattern(mci, 5000000);
if (ret < 0)
dev_err(mci->dev, "%s: trigger pe20 pattern fail\n",
__func__);
}
static void mt6360_chgdet_work_handler(struct work_struct *work)
{
int ret = 0;
bool pwr_rdy = false;
struct mt6360_chg_info *mci =
(struct mt6360_chg_info *)container_of(work,
struct mt6360_chg_info, chgdet_work);
mutex_lock(&mci->chgdet_lock);
/* Check PWR_RDY_STAT */
ret = mt6360_get_chrdet_ext_stat(mci, &pwr_rdy);
if (ret < 0)
goto out;
/* power not good */
if (!pwr_rdy)
goto out;
/* power good */
mci->pwr_rdy = pwr_rdy;
/* Turn on USB charger detection */
ret = __mt6360_enable_usbchgen(mci, true);
if (ret < 0)
dev_err(mci->dev, "%s: en bc12 fail\n", __func__);
out:
mutex_unlock(&mci->chgdet_lock);
}
static const struct mt6360_pdata_prop mt6360_pdata_props[] = {
MT6360_PDATA_VALPROP(ichg, struct mt6360_chg_platform_data,
MT6360_PMU_CHG_CTRL7, 2, 0xFC,
mt6360_trans_ichg_sel, 0),
MT6360_PDATA_VALPROP(aicr, struct mt6360_chg_platform_data,
MT6360_PMU_CHG_CTRL3, 2, 0xFC,
mt6360_trans_aicr_sel, 0),
MT6360_PDATA_VALPROP(mivr, struct mt6360_chg_platform_data,
MT6360_PMU_CHG_CTRL6, 1, 0xFE,
mt6360_trans_mivr_sel, 0),
MT6360_PDATA_VALPROP(cv, struct mt6360_chg_platform_data,
MT6360_PMU_CHG_CTRL4, 1, 0xFE,
mt6360_trans_cv_sel, 0),
MT6360_PDATA_VALPROP(ieoc, struct mt6360_chg_platform_data,
MT6360_PMU_CHG_CTRL9, 4, 0xF0,
mt6360_trans_ieoc_sel, 0),
MT6360_PDATA_VALPROP(safety_timer, struct mt6360_chg_platform_data,
MT6360_PMU_CHG_CTRL12, 5, 0xE0,
mt6360_trans_safety_timer_sel, 0),
MT6360_PDATA_VALPROP(ircmp_resistor, struct mt6360_chg_platform_data,
MT6360_PMU_CHG_CTRL18, 3, 0x38,
mt6360_trans_ircmp_r_sel, 0),
MT6360_PDATA_VALPROP(ircmp_vclamp, struct mt6360_chg_platform_data,
MT6360_PMU_CHG_CTRL18, 0, 0x07,
mt6360_trans_ircmp_vclamp_sel, 0),
};
static const struct mt6360_val_prop mt6360_val_props[] = {
MT6360_DT_VALPROP(ichg, struct mt6360_chg_platform_data),
MT6360_DT_VALPROP(aicr, struct mt6360_chg_platform_data),
MT6360_DT_VALPROP(mivr, struct mt6360_chg_platform_data),
MT6360_DT_VALPROP(cv, struct mt6360_chg_platform_data),
MT6360_DT_VALPROP(ieoc, struct mt6360_chg_platform_data),
MT6360_DT_VALPROP(safety_timer, struct mt6360_chg_platform_data),
MT6360_DT_VALPROP(ircmp_resistor, struct mt6360_chg_platform_data),
MT6360_DT_VALPROP(ircmp_vclamp, struct mt6360_chg_platform_data),
MT6360_DT_VALPROP(en_te, struct mt6360_chg_platform_data),
MT6360_DT_VALPROP(en_wdt, struct mt6360_chg_platform_data),
MT6360_DT_VALPROP(post_aicc, struct mt6360_chg_platform_data),
MT6360_DT_VALPROP(batoc_notify, struct mt6360_chg_platform_data),
};
static int mt6360_enable_ilim(struct mt6360_chg_info *mci, bool en)
{
return regmap_update_bits(mci->regmap, MT6360_PMU_CHG_CTRL3,
MT6360_ILIM_EN_MASK, en ? 0xff : 0);
}
static void mt6360_get_boot_mode(struct mt6360_chg_info *mci)
{
struct device_node *boot_node = NULL;
struct tag_bootmode *tag = NULL;
boot_node = of_parse_phandle(mci->dev->of_node, "bootmode", 0);
if (!boot_node)
dev_info(mci->dev, "%s: failed to get boot mode phandle\n",
__func__);
else {
tag = (struct tag_bootmode *)of_get_property(boot_node,
"atag,boot", NULL);
if (!tag)
dev_info(mci->dev, "%s: failed to get atag,boot\n",
__func__);
else {
dev_info(mci->dev,
"%s: size:0x%x tag:0x%x bootmode:0x%x boottype:0x%x\n",
__func__, tag->size, tag->tag,
tag->bootmode, tag->boottype);
mci->bootmode = tag->bootmode;
mci->boottype = tag->boottype;
}
}
}
static int mt6360_chg_init_setting(struct mt6360_chg_info *mci)
{
struct mt6360_chg_platform_data *pdata = dev_get_platdata(mci->dev);
int ret = 0;
unsigned int regval;
dev_info(mci->dev, "%s\n", __func__);
ret = regmap_read(mci->regmap, MT6360_PMU_DEV_INFO, &regval);
if (ret >= 0)
mci->chip_rev = regval & MT6360_CHIP_REV_MASK;
ret = regmap_read(mci->regmap, MT6360_PMU_FOD_STAT, &regval);
if (ret >= 0)
mci->ctd_dischg_status = regval & 0xE3;
if (regmap_update_bits(mci->regmap, MT6360_PMU_FOD_CTRL, 0x40, 0) < 0)
dev_err(mci->dev, "%s: disable fod ctrl fail\n", __func__);
ret = mt6360_select_input_current_limit(mci, MT6360_IINLMTSEL_AICR);
if (ret < 0) {
dev_err(mci->dev, "%s: select iinlmtsel by aicr fail\n",
__func__);
return ret;
}
usleep_range(5000, 6000);
ret = mt6360_enable_ilim(mci, false);
if (ret < 0) {
dev_err(mci->dev, "%s: disable ilim fail\n", __func__);
return ret;
}
mt6360_get_boot_mode(mci);
if (mci->bootmode == 1 || mci->bootmode == 5) {
/*1:META_BOOT 5:ADVMETA_BOOT*/
ret = regmap_update_bits(mci->regmap,
MT6360_PMU_CHG_CTRL3,
MT6360_AICR_MASK,
0x02 << MT6360_AICR_SHFT);
dev_info(mci->dev, "%s: set aicr to 200mA in meta mode\n",
__func__);
}
ret = mt6360_enable_wdt(mci, false);
if (ret < 0) {
dev_err(mci->dev, "%s: disable wdt fail\n", __func__);
return ret;
}
ret = mt6360_enable_usbchgen(mci, false);
if (ret < 0) {
dev_err(mci->dev, "%s: disable chg type detect fail\n",
__func__);
return ret;
}
ret = mt6360_select_vinovp(mci, 14500000);
if (ret < 0) {
dev_err(mci->dev, "%s: unlimit vin for pump express\n",
__func__);
return ret;
}
if (pdata->en_te) {
ret = regmap_update_bits(mci->regmap, MT6360_PMU_CHG_CTRL2,
MT6360_TE_EN_MASK, 0);
if (ret < 0) {
dev_err(mci->dev, "%s: disable te fail\n", __func__);
return ret;
}
}
ret = mt6360_read_zcv(mci);
if (ret < 0) {
dev_err(mci->dev, "%s: read zcv fail\n", __func__);
return ret;
}
#ifndef CONFIG_MT6360_DCDTOUT_SUPPORT
ret = mt6360_enable_dcd_tout(mci, false);
if (ret < 0)
dev_notice(mci->dev, "%s disable dcd fail\n", __func__);
#endif
ret = regmap_read(mci->regmap, MT6360_PMU_CHG_STAT, &regval);
if (ret < 0) {
dev_err(mci->dev, "%s: read BATSYSUV fail\n", __func__);
return ret;
}
if (!(ret & MT6360_CHG_BATSYSUV_MASK)) {
dev_info(mci->dev, "%s: BATSYSUV occurred\n", __func__);
if (regmap_update_bits(mci->regmap, MT6360_PMU_CHG_STAT,
MT6360_CHG_BATSYSUV_MASK, 0xff) < 0)
dev_err(mci->dev,
"%s: clear BATSYSUV fail\n", __func__);
}
/* USBID ID_TD = 32T */
ret = regmap_update_bits(mci->regmap, MT6360_PMU_USBID_CTRL2,
MT6360_IDTD_MASK | MT6360_USBID_FLOAT_MASK,
0x62);
/* Disable TypeC OTP for check EVB version by TS pin */
return regmap_update_bits(mci->regmap, MT6360_PMU_TYPEC_OTP_CTRL,
MT6360_TYPEC_OTP_EN_MASK, 0);
}
static int mt6360_set_shipping_mode(struct mt6360_chg_info *mci)
{
int ret;
dev_info(mci->dev, "%s\n", __func__);
/* disable shipping mode rst */
ret = regmap_update_bits(mci->regmap, MT6360_PMU_CORE_CTRL2,
MT6360_MASK_SHIP_RST_DIS, 0xff);
if (ret < 0) {
dev_err(mci->dev,
"%s: fail to disable ship reset\n", __func__);
goto out;
}
/* enter shipping mode and disable cfo_en/chg_en */
ret = regmap_write(mci->regmap, MT6360_PMU_CHG_CTRL2, 0x80);
if (ret < 0)
dev_err(mci->dev,
"%s: fail to enter shipping mode\n", __func__);
out:
return ret;
}
static ssize_t shipping_mode_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct mt6360_chg_info *mci = 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 = mt6360_set_shipping_mode(mci);
if (ret < 0)
return ret;
return count;
}
static const DEVICE_ATTR_WO(shipping_mode);
/* ======================= */
/* MT6360 Power Supply Ops */
/* ======================= */
static int mt6360_charger_get_online(struct mt6360_chg_info *mci,
bool *val)
{
int ret;
bool pwr_rdy;
if (IS_ENABLED(CONFIG_TCPC_CLASS)) {
pwr_rdy = atomic_read(&mci->tcpc_attach);
} else {
/*uvp_d_stat=true => vbus_on=1*/
ret = mt6360_get_chrdet_ext_stat(mci, &pwr_rdy);
if (ret < 0) {
dev_notice(mci->dev,
"%s: read uvp_d_stat fail\n", __func__);
return ret;
}
}
dev_info(mci->dev, "%s: online = %d\n", __func__, pwr_rdy);
*val = pwr_rdy;
return 0;
}
static int mt6360_charger_set_online(struct mt6360_chg_info *mci,
const union power_supply_propval *val)
{
return __mt6360_enable_chg_type_det(mci, val->intval);
}
static int mt6360_charger_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct mt6360_chg_info *mci = power_supply_get_drvdata(psy);
enum mt6360_charging_status chg_stat = MT6360_CHG_STATUS_MAX;
int ret = 0;
bool pwr_rdy = false, chg_en = false;
if (!mci)
return -EINVAL;
dev_dbg(mci->dev, "%s: prop = %d\n", __func__, psp);
switch (psp) {
case POWER_SUPPLY_PROP_ONLINE:
ret = mt6360_charger_get_online(mci, &pwr_rdy);
val->intval = pwr_rdy;
break;
case POWER_SUPPLY_PROP_TYPE:
val->intval = mci->psy_desc.type;
break;
case POWER_SUPPLY_PROP_USB_TYPE:
val->intval = mci->psy_usb_type;
break;
case POWER_SUPPLY_PROP_CURRENT_MAX:
if (mci->psy_desc.type == POWER_SUPPLY_TYPE_USB)
val->intval = 500000;
break;
case POWER_SUPPLY_PROP_STATUS:
ret = mt6360_charger_get_online(mci, &pwr_rdy);
ret |= __mt6360_is_enabled(mci, &chg_en);
ret |= mt6360_get_charging_status(mci, &chg_stat);
if (ret < 0)
return ret;
if (!pwr_rdy) {
val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
return ret;
}
switch (chg_stat) {
case MT6360_CHG_STATUS_READY:
fallthrough;
case MT6360_CHG_STATUS_PROGRESS:
if (chg_en)
val->intval = POWER_SUPPLY_STATUS_CHARGING;
else
val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
break;
case MT6360_CHG_STATUS_DONE:
val->intval = POWER_SUPPLY_STATUS_FULL;
break;
case MT6360_CHG_STATUS_FAULT:
val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
break;
default:
ret = -ENODATA;
break;
}
break;
default:
ret = -ENODATA;
}
return ret;
}
static int mt6360_charger_set_property(struct power_supply *psy,
enum power_supply_property psp,
const union power_supply_propval *val)
{
struct mt6360_chg_info *mci = power_supply_get_drvdata(psy);
int ret;
if (!mci)
return -EINVAL;
dev_dbg(mci->dev, "%s: prop = %d\n", __func__, psp);
switch (psp) {
case POWER_SUPPLY_PROP_ONLINE:
ret = mt6360_charger_set_online(mci, val);
break;
default:
ret = -EINVAL;
}
return ret;
}
static int mt6360_charger_property_is_writeable(struct power_supply *psy,
enum power_supply_property psp)
{
switch (psp) {
case POWER_SUPPLY_PROP_ONLINE:
return 1;
default:
return 0;
}
}
static enum power_supply_property mt6360_charger_properties[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_TYPE,
POWER_SUPPLY_PROP_USB_TYPE,
POWER_SUPPLY_PROP_CURRENT_MAX,
};
static const struct power_supply_desc mt6360_charger_desc = {
.type = POWER_SUPPLY_TYPE_USB,
.properties = mt6360_charger_properties,
.num_properties = ARRAY_SIZE(mt6360_charger_properties),
.get_property = mt6360_charger_get_property,
.set_property = mt6360_charger_set_property,
.property_is_writeable = mt6360_charger_property_is_writeable,
.usb_types = mt6360_charger_usb_types,
.num_usb_types = ARRAY_SIZE(mt6360_charger_usb_types),
};
static char *mt6360_charger_supplied_to[] = {
"battery",
"mtk-master-charger"
};
/*otg_vbus*/
static int mt6360_boost_enable(struct regulator_dev *rdev)
{
struct mt6360_chg_info *mci = rdev_get_drvdata(rdev);
return __mt6360_enable_otg(mci, true);
}
static int mt6360_boost_disable(struct regulator_dev *rdev)
{
struct mt6360_chg_info *mci = rdev_get_drvdata(rdev);
return __mt6360_enable_otg(mci, false);
}
static int mt6360_boost_is_enabled(struct regulator_dev *rdev)
{
struct mt6360_chg_info *mci = rdev_get_drvdata(rdev);
const struct regulator_desc *desc = rdev->desc;
int ret = 0;
unsigned int regval;
ret = regmap_read(mci->regmap, desc->enable_reg, &regval);
if (ret < 0)
return ret;
return regval & desc->enable_mask ? true : false;
}
static int mt6360_boost_set_voltage_sel(struct regulator_dev *rdev,
unsigned int sel)
{
struct mt6360_chg_info *mci = rdev_get_drvdata(rdev);
const struct regulator_desc *desc = rdev->desc;
int shift = ffs(desc->vsel_mask) - 1;
return regmap_update_bits(mci->regmap, desc->enable_reg,
desc->enable_mask, sel << shift);
}
static int mt6360_boost_get_voltage_sel(struct regulator_dev *rdev)
{
struct mt6360_chg_info *mci = rdev_get_drvdata(rdev);
const struct regulator_desc *desc = rdev->desc;
int shift = ffs(desc->vsel_mask) - 1, ret;
unsigned int regval;
ret = regmap_read(mci->regmap, desc->vsel_reg, &regval);
if (ret < 0)
return ret;
return (regval & desc->vsel_mask) >> shift;
}
static int mt6360_boost_set_current_limit(struct regulator_dev *rdev,
int min_uA, int max_uA)
{
struct mt6360_chg_info *mci = rdev_get_drvdata(rdev);
const struct regulator_desc *desc = rdev->desc;
int i, shift = ffs(desc->csel_mask) - 1;
for (i = 0; i < ARRAY_SIZE(mt6360_otg_oc_threshold); i++) {
if (min_uA <= mt6360_otg_oc_threshold[i])
break;
}
if (i == ARRAY_SIZE(mt6360_otg_oc_threshold) ||
mt6360_otg_oc_threshold[i] > max_uA) {
dev_notice(mci->dev,
"%s: out of current range\n", __func__);
return -EINVAL;
}
dev_info(mci->dev, "%s: select otg_oc = %d\n",
__func__, mt6360_otg_oc_threshold[i]);
return regmap_update_bits(mci->regmap, desc->csel_reg,
desc->csel_mask, i << shift);
}
static int mt6360_boost_get_current_limit(struct regulator_dev *rdev)
{
struct mt6360_chg_info *mci = rdev_get_drvdata(rdev);
const struct regulator_desc *desc = rdev->desc;
int shift = ffs(desc->csel_mask) - 1, ret;
unsigned int regval;
ret = regmap_read(mci->regmap, desc->csel_reg, &regval);
if (ret < 0)
return ret;
ret = (regval & desc->csel_mask) >> shift;
if (ret < 0 || ret >= ARRAY_SIZE(mt6360_otg_oc_threshold))
return -EINVAL;
return mt6360_otg_oc_threshold[ret];
}
static const struct regulator_ops mt6360_chg_otg_ops = {
.list_voltage = regulator_list_voltage_linear,
.enable = mt6360_boost_enable,
.disable = mt6360_boost_disable,
.is_enabled = mt6360_boost_is_enabled,
.set_voltage_sel = mt6360_boost_set_voltage_sel,
.get_voltage_sel = mt6360_boost_get_voltage_sel,
.set_current_limit = mt6360_boost_set_current_limit,
.get_current_limit = mt6360_boost_get_current_limit,
};
static const struct regulator_desc mt6360_otg_rdesc = {
.of_match = "usb-otg-vbus",
.name = "usb-otg-vbus",
.ops = &mt6360_chg_otg_ops,
.owner = THIS_MODULE,
.type = REGULATOR_VOLTAGE,
.min_uV = 4425000,
.uV_step = 25000, /* 25mV per step */
.n_voltages = 57, /* 4425mV to 5825mV */
.vsel_reg = MT6360_PMU_CHG_CTRL5,
.vsel_mask = MT6360_MASK_BOOST_VOREG,
.enable_reg = MT6360_PMU_CHG_CTRL1,
.enable_mask = MT6360_OPA_MODE_MASK,
.csel_reg = MT6360_PMU_CHG_CTRL10,
.csel_mask = MT6360_OTG_OC_MASK,
};
static int mt6360_pmu_chg_probe(struct platform_device *pdev)
{
struct mt6360_chg_platform_data *pdata = dev_get_platdata(&pdev->dev);
struct mt6360_chg_info *mci;
struct iio_channel *channel;
struct power_supply_config charger_cfg = {};
struct regulator_config config = { };
struct device_node *np = pdev->dev.of_node;
struct device_node *node;
int i, ret = 0;
dev_info(&pdev->dev, "%s\n", __func__);
if (np) {
pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata)
return -ENOMEM;
memcpy(pdata, &def_platform_data, sizeof(*pdata));
mt6360_dt_parser_helper(np, (void *)pdata,
mt6360_val_props, ARRAY_SIZE(mt6360_val_props));
node = of_parse_phandle(np, "bc12_ref", 0);
if (!node)
dev_notice(&pdev->dev,
"%s: can't parse phandle bc12_ref\n", __func__);
if (of_property_read_u32(node, "bc12_sel",
&pdata->bc12_sel) < 0)
dev_notice(&pdev->dev,
"%s: can't parse bc12_sel\n", __func__);
pdev->dev.platform_data = pdata;
}
if (!pdata) {
dev_err(&pdev->dev, "no platform data specified\n");
return -EINVAL;
}
mci = devm_kzalloc(&pdev->dev, sizeof(*mci), GFP_KERNEL);
if (!mci)
return -ENOMEM;
mci->dev = &pdev->dev;
mci->hidden_mode_cnt = 0;
mutex_init(&mci->hidden_mode_lock);
mutex_init(&mci->pe_lock);
mutex_init(&mci->aicr_lock);
mutex_init(&mci->chgdet_lock);
mutex_init(&mci->tchg_lock);
mutex_init(&mci->ichg_lock);
mci->tchg = 0;
mci->ichg = 2000000;
mci->ichg_dis_chg = 2000000;
mci->attach = false;
g_mci = mci;
init_completion(&mci->aicc_done);
init_completion(&mci->pumpx_done);
atomic_set(&mci->pe_complete, 0);
atomic_set(&mci->mivr_cnt, 0);
atomic_set(&mci->tcpc_attach, 0);
init_waitqueue_head(&mci->mivr_wq);
if (!IS_ENABLED(CONFIG_TCPC_CLASS) && pdata->bc12_sel == 0)
INIT_WORK(&mci->chgdet_work, mt6360_chgdet_work_handler);
platform_set_drvdata(pdev, mci);
/* get parent regmap */
mci->regmap = dev_get_regmap(pdev->dev.parent, NULL);
if (!mci->regmap) {
dev_err(&pdev->dev, "Fail to get parent regmap\n");
ret = -ENODEV;
goto err_mutex_init;
}
/* apply platform data */
ret = mt6360_pdata_apply_helper(mci->regmap, pdata, mt6360_pdata_props,
ARRAY_SIZE(mt6360_pdata_props));
if (ret < 0) {
dev_err(&pdev->dev, "apply pdata fail\n");
goto err_mutex_init;
}
/* Initial Setting */
ret = mt6360_chg_init_setting(mci);
if (ret < 0) {
dev_err(&pdev->dev, "%s: init setting fail\n", __func__);
goto err_mutex_init;
}
/* Get ADC iio channels */
for (i = 0; i < MT6360_ADC_MAX; i++) {
channel = devm_iio_channel_get(&pdev->dev,
mt6360_adc_chan_list[i]);
if (IS_ERR(channel)) {
dev_err(&pdev->dev,
"%s: iio channel get fail\n", __func__);
ret = PTR_ERR(channel);
goto err_mutex_init;
}
mci->channels[i] = channel;
}
#if IS_ENABLED(CONFIG_MTK_CHARGER)
mci->chg_dev = charger_device_register(pdata->chg_name, mci->dev,
mci, &mt6360_chg_ops,
&mt6360_chg_props);
if (IS_ERR(mci->chg_dev)) {
dev_err(mci->dev, "charger device register fail\n");
ret = PTR_ERR(mci->chg_dev);
goto err_mutex_init;
}
#endif /* CONFIG_MTK_CHARGER */
mci->mivr_task = kthread_run(mt6360_chg_mivr_task_threadfn, mci,
devm_kasprintf(mci->dev, GFP_KERNEL,
"mivr_thread.%s", dev_name(mci->dev)));
ret = PTR_ERR_OR_ZERO(mci->mivr_task);
if (ret < 0) {
dev_err(mci->dev, "create mivr handling thread fail\n");
goto err_register_chg_dev;
}
ret = device_create_file(mci->dev, &dev_attr_shipping_mode);
if (ret < 0) {
dev_notice(&pdev->dev, "create shipping attr fail\n");
goto err_create_mivr_thread_run;
}
mci->pe_wq = create_singlethread_workqueue("pe_pattern");
if (!mci->pe_wq) {
dev_err(mci->dev, "%s: create pe_pattern work queue fail\n",
__func__);
goto err_shipping_mode_attr;
}
INIT_WORK(&mci->pe_work, mt6360_trigger_pep_work_handler);
/* otg regulator */
config.dev = &pdev->dev;
config.driver_data = mci;
mci->otg_rdev = devm_regulator_register(&pdev->dev,
&mt6360_otg_rdesc, &config);
if (IS_ERR(mci->otg_rdev)) {
ret = PTR_ERR(mci->otg_rdev);
goto err_register_otg;
}
/* power supply register */
memcpy(&mci->psy_desc,
&mt6360_charger_desc, sizeof(mci->psy_desc));
mci->psy_desc.name = dev_name(&pdev->dev);
charger_cfg.drv_data = mci;
charger_cfg.of_node = pdev->dev.of_node;
charger_cfg.supplied_to = mt6360_charger_supplied_to;
charger_cfg.num_supplicants = ARRAY_SIZE(mt6360_charger_supplied_to);
mci->psy = devm_power_supply_register(&pdev->dev,
&mci->psy_desc, &charger_cfg);
if (IS_ERR(mci->psy)) {
dev_notice(&pdev->dev, "Fail to register power supply dev\n");
ret = PTR_ERR(mci->psy);
goto err_register_psy;
}
/* irq register */
mt6360_pmu_chg_irq_register(pdev);
device_init_wakeup(&pdev->dev, true);
/* Schedule work for microB's BC1.2 */
if (!IS_ENABLED(CONFIG_TCPC_CLASS) && pdata->bc12_sel == 0)
schedule_work(&mci->chgdet_work);
dev_info(&pdev->dev, "%s: successfully probed\n", __func__);
return 0;
err_register_psy:
err_register_otg:
destroy_workqueue(mci->pe_wq);
err_shipping_mode_attr:
device_remove_file(mci->dev, &dev_attr_shipping_mode);
err_create_mivr_thread_run:
if (mci->mivr_task)
kthread_stop(mci->mivr_task);
err_register_chg_dev:
#if IS_ENABLED(CONFIG_MTK_CHARGER)
charger_device_unregister(mci->chg_dev);
#endif /* CONFIG_MTK_CHARGER */
err_mutex_init:
mutex_destroy(&mci->tchg_lock);
mutex_destroy(&mci->chgdet_lock);
mutex_destroy(&mci->aicr_lock);
mutex_destroy(&mci->pe_lock);
mutex_destroy(&mci->hidden_mode_lock);
return -EPROBE_DEFER;
}
static int mt6360_pmu_chg_remove(struct platform_device *pdev)
{
struct mt6360_chg_info *mci = platform_get_drvdata(pdev);
dev_dbg(mci->dev, "%s\n", __func__);
flush_workqueue(mci->pe_wq);
destroy_workqueue(mci->pe_wq);
if (mci->mivr_task) {
atomic_inc(&mci->mivr_cnt);
wake_up(&mci->mivr_wq);
kthread_stop(mci->mivr_task);
}
device_remove_file(mci->dev, &dev_attr_shipping_mode);
#if IS_ENABLED(CONFIG_MTK_CHARGER)
charger_device_unregister(mci->chg_dev);
#endif /* CONFIG_MTK_CHARGER */
mutex_destroy(&mci->tchg_lock);
mutex_destroy(&mci->chgdet_lock);
mutex_destroy(&mci->aicr_lock);
mutex_destroy(&mci->pe_lock);
mutex_destroy(&mci->hidden_mode_lock);
return 0;
}
static int __maybe_unused mt6360_pmu_chg_suspend(struct device *dev)
{
return 0;
}
static int __maybe_unused mt6360_pmu_chg_resume(struct device *dev)
{
return 0;
}
static SIMPLE_DEV_PM_OPS(mt6360_pmu_chg_pm_ops,
mt6360_pmu_chg_suspend, mt6360_pmu_chg_resume);
static const struct of_device_id __maybe_unused mt6360_pmu_chg_of_id[] = {
{ .compatible = "mediatek,mt6360_pmu_chg", },
{ .compatible = "mediatek,mt6360-chg", },
{},
};
MODULE_DEVICE_TABLE(of, mt6360_pmu_chg_of_id);
static const struct platform_device_id mt6360_pmu_chg_id[] = {
{ "mt6360_pmu_chg", 0 },
{},
};
MODULE_DEVICE_TABLE(platform, mt6360_pmu_chg_id);
static struct platform_driver mt6360_pmu_chg_driver = {
.driver = {
.name = "mt6360_pmu_chg",
.owner = THIS_MODULE,
.pm = &mt6360_pmu_chg_pm_ops,
.of_match_table = of_match_ptr(mt6360_pmu_chg_of_id),
},
.probe = mt6360_pmu_chg_probe,
.remove = mt6360_pmu_chg_remove,
.id_table = mt6360_pmu_chg_id,
};
static int __init mt6360_pmu_chg_init(void)
{
return platform_driver_register(&mt6360_pmu_chg_driver);
}
device_initcall_sync(mt6360_pmu_chg_init);
static void __exit mt6360_pmu_chg_exit(void)
{
platform_driver_unregister(&mt6360_pmu_chg_driver);
}
module_exit(mt6360_pmu_chg_exit);
MODULE_AUTHOR("CY_Huang <cy_huang@richtek.com>");
MODULE_DESCRIPTION("MT6360 PMU CHG Driver");
MODULE_LICENSE("GPL");