2832 lines
74 KiB
C
2832 lines
74 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (c) 2021 MediaTek Inc.
|
|
*
|
|
* Author: ShuFan Lee <shufan_lee@richtek.com>
|
|
*/
|
|
|
|
#include <linux/completion.h>
|
|
#include <linux/iio/consumer.h>
|
|
#include <linux/atomic.h>
|
|
#include <linux/init.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/jiffies.h>
|
|
#include <linux/mod_devicetable.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/phy/phy.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/power_supply.h>
|
|
#include <linux/reboot.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/regulator/driver.h>
|
|
#include <linux/workqueue.h>
|
|
|
|
#include "charger_class.h"
|
|
#include "mtk_charger.h"
|
|
|
|
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, "%s " fmt, __func__, ##__VA_ARGS__); \
|
|
} while (0)
|
|
#define PHY_MODE_BC11_SET 1
|
|
#define PHY_MODE_BC11_CLR 2
|
|
|
|
#define M_TO_U(val) ((val) * 1000)
|
|
#define U_TO_M(val) ((val) / 1000)
|
|
|
|
#define MT6375_MANUFACTURER "Mediatek"
|
|
|
|
#define MT6375_REG_CORE_CTRL2 0x106
|
|
#define MT6375_REG_TM_PAS_CODE1 0x107
|
|
#define MT6375_REG_CHG_BATPRO 0x117
|
|
#define MT6375_REG_CHG_TOP1 0x120
|
|
#define MT6375_REG_CHG_TOP2 0x121
|
|
#define MT6375_REG_CHG_AICR 0x122
|
|
#define MT6375_REG_CHG_MIVR 0x123
|
|
#define MT6375_REG_CHG_VCHG 0x125
|
|
#define MT6375_REG_CHG_ICHG 0x126
|
|
#define MT6375_REG_CHG_TMR 0x127
|
|
#define MT6375_REG_CHG_EOC 0x128
|
|
#define MT6375_REG_CHG_VSYS 0x129
|
|
#define MT6375_REG_CHG_WDT 0x12A
|
|
#define MT6375_REG_CHG_PUMPX 0x12B
|
|
#define MT6375_REG_CHG_AICC1 0x12C
|
|
#define MT6375_REG_CHG_AICC2 0x12D
|
|
#define MT6375_REG_OTG_LBP 0x130
|
|
#define MT6375_REG_OTG_V 0x131
|
|
#define MT6375_REG_OTG_C 0x132
|
|
#define MT6375_REG_BAT_COMP 0x133
|
|
#define MT6375_REG_CHG_STAT 0x134
|
|
#define MT6375_REG_CHG_DUMY0 0x135
|
|
#define MT6375_REG_CHG_HD_TOP1 0x13B
|
|
#define MT6375_REG_CHG_HD_BUCK5 0x140
|
|
#define MT6375_REG_BC12_FUNC 0x150
|
|
#define MT6375_REG_BC12_STAT 0x151
|
|
#define MT6375_REG_DPDM_CTRL1 0x153
|
|
#define MT6375_REG_DPDM_CTRL2 0x154
|
|
#define MT6375_REG_DPDM_CTRL4 0x156
|
|
#define MT6375_REG_VBAT_MON_RPT 0x19C
|
|
#define MT6375_REG_BATEND_CODE 0x19E
|
|
#define MT6375_REG_ADC_CONFG1 0x1A4
|
|
#define MT6375_REG_ADC_ZCV_RPT 0x1CA
|
|
#define MT6375_REG_CHG_STAT0 0x1E0
|
|
#define MT6375_REG_CHG_STAT1 0x1E1
|
|
|
|
#define MT6375_MSK_BATFET_DIS 0x40
|
|
#define MT6375_MSK_BLEED_DIS_EN BIT(7)
|
|
#define MT6375_MSK_OTG_EN 0x04
|
|
#define MT6375_MSK_OTG_CV 0x3F
|
|
#define MT6375_MSK_OTG_CC 0x07
|
|
#define MT6375_MSK_CLK_FREQ 0xC0
|
|
#define MT6375_MSK_COMP_CLAMP 0x03
|
|
#define MT6375_MSK_BUCK_RAMPOFT 0xC0
|
|
|
|
#define ADC_CONV_TIME_US 2200
|
|
#define ADC_VBAT_SCALE 1250
|
|
#define ADC_TO_VBAT_RAW(vbat) ((vbat) * 1000 / ADC_VBAT_SCALE)
|
|
#define ADC_FROM_VBAT_RAW(raw) ((raw) * ADC_VBAT_SCALE / 1000)
|
|
|
|
#define NORMAL_CHARGING_CURR_UA 500000
|
|
#define FAST_CHARGING_CURR_UA 1500000
|
|
#define RECHG_THRESHOLD 100
|
|
#define DEFAULT_PMIC_UVLO_mV 2000
|
|
|
|
enum mt6375_chg_reg_field {
|
|
/* MT6375_REG_CORE_CTRL2 */
|
|
F_SHIP_RST_DIS,
|
|
/* MT6375_REG_CHG_BATPRO */
|
|
F_BATINT, F_BATPROTECT_EN,
|
|
/* MT6375_REG_CHG_TOP1 */
|
|
F_CHG_EN, F_BUCK_EN, F_HZ, F_BATFET_DISDLY, F_BATFET_DIS, F_PP_PG_FLAG,
|
|
/* MT6375_REG_CHG_TOP2 */
|
|
F_VBUS_OV,
|
|
/* MT6375_REG_CHG_AICR */
|
|
F_ILIM_EN, F_IAICR,
|
|
/* MT6375_REG_CHG_MIVR */
|
|
F_VMIVR,
|
|
/* MT6375_REG_CHG_VCHG */
|
|
F_CV, F_VREC,
|
|
/* MT6375_REG_CHG_ICHG */
|
|
F_CC,
|
|
/* MT6375_REG_CHG_TMR */
|
|
F_CHG_TMR, F_CHG_TMR_EN,
|
|
/* MT6375_REG_CHG_EOC */
|
|
F_EOC_RST, F_TE, F_IEOC,
|
|
/* MT6375_REG_CHG_VSYS */
|
|
F_BLEED_DIS_EN,
|
|
/* MT6375_REG_CHG_WDT */
|
|
F_WDT, F_WDT_RST, F_WDT_EN,
|
|
/* MT6375_REG_CHG_PUMPX */
|
|
F_PE20_CODE, F_PE10_INC, F_PE_SEL, F_PE_EN,
|
|
/* MT6375_REG_CHG_AICC1 */
|
|
F_AICC_VTH, F_AICC_EN,
|
|
/* MT6375_REG_CHG_AICC2 */
|
|
F_AICC_RPT, F_AICC_ONESHOT,
|
|
/* MT6375_REG_OTG_LBP */
|
|
F_OTG_LBP,
|
|
/* MT6375_REG_OTG_C */
|
|
F_OTG_CC,
|
|
/* MT6375_REG_BAT_COMP */
|
|
F_IRCMP_V, F_IRCMP_R,
|
|
/* MT6375_REG_CHG_STAT */
|
|
F_IC_STAT,
|
|
/* MT6375_REG_CHG_HD_TOP1 */
|
|
F_FORCE_VBUS_SINK,
|
|
/* MT6375_REG_BC12_FUNC */
|
|
F_DCDT_SEL, F_BC12_EN,
|
|
/* MT6375_REG_BC12_STAT */
|
|
F_PORT_STAT,
|
|
/* MT6375_REG_DPDM_CTRL1 */
|
|
F_DM_DET_EN, F_DP_DET_EN, F_DPDM_SW_VCP_EN, F_MANUAL_MODE,
|
|
/* MT6375_REG_DPDM_CTRL2 */
|
|
F_DP_LDO_VSEL, F_DP_LDO_EN,
|
|
/* MT6375_REG_DPDM_CTRL4 */
|
|
F_DP_PULL_RSEL, F_DP_PULL_REN,
|
|
/* MT6375_REG_ADC_CONFG1 */
|
|
F_VBAT_MON_EN,
|
|
/* MT6375_REG_CHG_STAT0 */
|
|
F_ST_PWR_RDY,
|
|
/* MT6375_REG_CHG_STAT1 */
|
|
F_ST_MIVR,
|
|
F_MAX,
|
|
};
|
|
|
|
enum {
|
|
CHG_STAT_SLEEP,
|
|
CHG_STAT_VBUS_RDY,
|
|
CHG_STAT_TRICKLE,
|
|
CHG_STAT_PRE,
|
|
CHG_STAT_FAST,
|
|
CHG_STAT_EOC,
|
|
CHG_STAT_BKGND,
|
|
CHG_STAT_DONE,
|
|
CHG_STAT_FAULT,
|
|
CHG_STAT_OTG = 15,
|
|
CHG_STAT_MAX,
|
|
};
|
|
|
|
enum {
|
|
PORT_STAT_NOINFO,
|
|
PORT_STAT_APPLE_10W = 8,
|
|
PORT_STAT_SAMSUNG,
|
|
PORT_STAT_APPLE_5W,
|
|
PORT_STAT_APPLE_12W,
|
|
PORT_STAT_UNKNOWN_TA,
|
|
PORT_STAT_SDP,
|
|
PORT_STAT_CDP,
|
|
PORT_STAT_DCP,
|
|
};
|
|
|
|
enum mt6375_adc_chan {
|
|
ADC_CHAN_CHGVINDIV5,
|
|
ADC_CHAN_VSYS,
|
|
ADC_CHAN_VBAT,
|
|
ADC_CHAN_IBUS,
|
|
ADC_CHAN_IBAT,
|
|
ADC_CHAN_TEMP_JC,
|
|
ADC_CHAN_USBDP,
|
|
ADC_CHAN_USBDM,
|
|
ADC_CHAN_MAX,
|
|
};
|
|
|
|
/* map with mtk_chg_type_det.c */
|
|
enum attach_type {
|
|
ATTACH_TYPE_NONE,
|
|
ATTACH_TYPE_PWR_RDY,
|
|
ATTACH_TYPE_TYPEC,
|
|
ATTACH_TYPE_PD,
|
|
ATTACH_TYPE_PD_SDP,
|
|
ATTACH_TYPE_PD_DCP,
|
|
ATTACH_TYPE_PD_NONSTD,
|
|
};
|
|
|
|
enum mt6375_attach_trigger {
|
|
ATTACH_TRIG_IGNORE,
|
|
ATTACH_TRIG_PWR_RDY,
|
|
ATTACH_TRIG_TYPEC,
|
|
};
|
|
|
|
enum mt6375_usbsw {
|
|
USBSW_CHG = 0,
|
|
USBSW_USB,
|
|
};
|
|
|
|
enum mt6375_chg_dtprop_type {
|
|
DTPROP_U32,
|
|
DTPROP_BOOL,
|
|
};
|
|
|
|
struct mt6375_chg_data {
|
|
struct device *dev;
|
|
struct regmap *rmap;
|
|
struct regmap_field *rmap_fields[F_MAX];
|
|
struct power_supply *psy;
|
|
struct power_supply_desc psy_desc;
|
|
struct regulator_dev *rdev;
|
|
struct regulator_desc rdesc;
|
|
struct iio_channel *iio_adcs;
|
|
struct mutex attach_lock;
|
|
struct mutex pe_lock;
|
|
struct mutex cv_lock;
|
|
struct mutex hm_lock;
|
|
struct workqueue_struct *wq;
|
|
struct work_struct bc12_work;
|
|
struct completion pe_done;
|
|
struct completion aicc_done;
|
|
struct charger_device *chgdev;
|
|
|
|
enum power_supply_usb_type psy_usb_type;
|
|
bool pwr_rdy;
|
|
atomic_t attach;
|
|
bool bc12_dn;
|
|
bool batprotect_en;
|
|
u32 hm_use_cnt;
|
|
u32 zcv;
|
|
u32 cv;
|
|
atomic_t eoc_cnt;
|
|
atomic_t tchg;
|
|
int vbat0_flag;
|
|
unsigned int detach_irq;
|
|
atomic_t no_6pin_used;
|
|
};
|
|
|
|
struct mt6375_chg_platform_data {
|
|
u32 aicr;
|
|
u32 mivr;
|
|
u32 ichg;
|
|
u32 ieoc;
|
|
u32 cv;
|
|
u32 wdt;
|
|
u32 otg_lbp;
|
|
u32 ircmp_v;
|
|
u32 ircmp_r;
|
|
u32 vbus_ov;
|
|
u32 vrec;
|
|
u32 chg_tmr;
|
|
u32 dcdt_sel;
|
|
u32 bc12_sel;
|
|
u32 boot_mode;
|
|
u32 boot_type;
|
|
u32 pmic_uvlo;
|
|
enum mt6375_attach_trigger attach_trig;
|
|
const char *chg_name;
|
|
bool chg_tmr_en;
|
|
bool wdt_en;
|
|
bool te_en;
|
|
bool usb_killer_detect;
|
|
};
|
|
|
|
struct mt6375_chg_range {
|
|
u32 min;
|
|
u32 max;
|
|
u16 step;
|
|
u8 offset;
|
|
const u32 *table;
|
|
u16 num_table;
|
|
bool round_up;
|
|
};
|
|
|
|
struct mt6375_chg_field {
|
|
const char *name;
|
|
const struct mt6375_chg_range *range;
|
|
struct reg_field field;
|
|
};
|
|
|
|
static const char *const mt6375_port_stat_names[] = {
|
|
[PORT_STAT_NOINFO] = "No Info",
|
|
[PORT_STAT_APPLE_10W] = "Apple 10W",
|
|
[PORT_STAT_SAMSUNG] = "Samsung",
|
|
[PORT_STAT_APPLE_5W] = "Apple 5W",
|
|
[PORT_STAT_APPLE_12W] = "Apple 12W",
|
|
[PORT_STAT_UNKNOWN_TA] = "Unknown TA",
|
|
[PORT_STAT_SDP] = "SDP",
|
|
[PORT_STAT_CDP] = "CDP",
|
|
[PORT_STAT_DCP] = "DCP",
|
|
};
|
|
|
|
static const char *const mt6375_attach_trig_names[] = {
|
|
"ignore", "pwr_rdy", "typec",
|
|
};
|
|
|
|
static const u32 mt6375_chg_vbus_ov[] = {
|
|
5800, 6500, 11000, 14500,
|
|
};
|
|
|
|
static const u32 mt6375_chg_wdt[] = {
|
|
8000, 40000, 80000, 160000,
|
|
};
|
|
|
|
static const u32 mt6375_chg_otg_cc[] = {
|
|
500, 700, 1100, 1300, 1800, 2100, 2400,
|
|
};
|
|
|
|
/* for regulator usage */
|
|
static const u32 mt6375_chg_otg_cc_micro[] = {
|
|
500000, 700000, 1100000, 1300000, 1800000, 2100000, 2400000,
|
|
};
|
|
|
|
static const u32 mt6375_chg_dpdm_ldo_vsel[] = {
|
|
600, 650, 700, 750, 1800, 2800, 3300,
|
|
};
|
|
|
|
#define MT6375_CHG_RANGE(_min, _max, _step, _offset, _ru) \
|
|
{ \
|
|
.min = _min, \
|
|
.max = _max, \
|
|
.step = _step, \
|
|
.offset = _offset, \
|
|
.round_up = _ru, \
|
|
}
|
|
|
|
#define MT6375_CHG_RANGE_T(_table, _ru) \
|
|
{ .table = _table, .num_table = ARRAY_SIZE(_table), .round_up = _ru, }
|
|
|
|
static const struct mt6375_chg_range mt6375_chg_ranges[F_MAX] = {
|
|
[F_BATINT] = MT6375_CHG_RANGE(3900, 4710, 10, 0, false),
|
|
[F_VBUS_OV] = MT6375_CHG_RANGE_T(mt6375_chg_vbus_ov, false),
|
|
[F_IAICR] = MT6375_CHG_RANGE(100, 3225, 25, 2, false),
|
|
[F_VMIVR] = MT6375_CHG_RANGE(3900, 13400, 100, 0, true),
|
|
[F_CV] = MT6375_CHG_RANGE(3900, 4710, 10, 0, false),
|
|
[F_VREC] = MT6375_CHG_RANGE(100, 200, 100, 0, false),
|
|
[F_CC] = MT6375_CHG_RANGE(300, 3150, 50, 6, false),
|
|
[F_CHG_TMR] = MT6375_CHG_RANGE(5, 20, 5, 0, false),
|
|
[F_IEOC] = MT6375_CHG_RANGE(100, 800, 50, 1, false),
|
|
[F_WDT] = MT6375_CHG_RANGE_T(mt6375_chg_wdt, false),
|
|
[F_PE20_CODE] = MT6375_CHG_RANGE(5500, 20000, 500, 0, false),
|
|
[F_AICC_VTH] = MT6375_CHG_RANGE(3900, 13400, 100, 0, true),
|
|
[F_AICC_RPT] = MT6375_CHG_RANGE(100, 3225, 25, 2, false),
|
|
[F_OTG_LBP] = MT6375_CHG_RANGE(2700, 3800, 100, 4, false),
|
|
[F_OTG_CC] = MT6375_CHG_RANGE_T(mt6375_chg_otg_cc, true),
|
|
[F_IRCMP_V] = MT6375_CHG_RANGE(0, 224, 32, 0, false),
|
|
[F_IRCMP_R] = MT6375_CHG_RANGE(0, 116900, 16700, 0, false),
|
|
[F_DCDT_SEL] = MT6375_CHG_RANGE(0, 600, 300, 0, false),
|
|
[F_DP_LDO_VSEL] = MT6375_CHG_RANGE_T(mt6375_chg_dpdm_ldo_vsel, false),
|
|
};
|
|
|
|
#define MT6375_CHG_FIELD_RANGE(_fd, _reg, _lsb, _msb, _range) \
|
|
[_fd] = { \
|
|
.name = #_fd, \
|
|
.range = _range ? &mt6375_chg_ranges[_fd] : NULL, \
|
|
.field = REG_FIELD(_reg, _lsb, _msb) \
|
|
}
|
|
|
|
#define MT6375_CHG_FIELD(_fd, _reg, _lsb, _msb) \
|
|
MT6375_CHG_FIELD_RANGE(_fd, _reg, _lsb, _msb, (_msb > _lsb))
|
|
|
|
static const struct mt6375_chg_field mt6375_chg_fields[F_MAX] = {
|
|
MT6375_CHG_FIELD(F_SHIP_RST_DIS, MT6375_REG_CORE_CTRL2, 0, 0),
|
|
MT6375_CHG_FIELD(F_BATINT, MT6375_REG_CHG_BATPRO, 0, 6),
|
|
MT6375_CHG_FIELD(F_BATPROTECT_EN, MT6375_REG_CHG_BATPRO, 7, 7),
|
|
MT6375_CHG_FIELD(F_CHG_EN, MT6375_REG_CHG_TOP1, 0, 0),
|
|
MT6375_CHG_FIELD(F_BUCK_EN, MT6375_REG_CHG_TOP1, 1, 1),
|
|
MT6375_CHG_FIELD(F_HZ, MT6375_REG_CHG_TOP1, 3, 3),
|
|
MT6375_CHG_FIELD(F_BATFET_DISDLY, MT6375_REG_CHG_TOP1, 5, 5),
|
|
MT6375_CHG_FIELD(F_BATFET_DIS, MT6375_REG_CHG_TOP1, 6, 6),
|
|
MT6375_CHG_FIELD(F_PP_PG_FLAG, MT6375_REG_CHG_TOP1, 7, 7),
|
|
MT6375_CHG_FIELD(F_VBUS_OV, MT6375_REG_CHG_TOP2, 0, 1),
|
|
MT6375_CHG_FIELD(F_IAICR, MT6375_REG_CHG_AICR, 0, 6),
|
|
MT6375_CHG_FIELD(F_ILIM_EN, MT6375_REG_CHG_AICR, 7, 7),
|
|
MT6375_CHG_FIELD(F_VMIVR, MT6375_REG_CHG_MIVR, 0, 6),
|
|
MT6375_CHG_FIELD(F_CV, MT6375_REG_CHG_VCHG, 0, 6),
|
|
MT6375_CHG_FIELD_RANGE(F_VREC, MT6375_REG_CHG_VCHG, 7, 7, true),
|
|
MT6375_CHG_FIELD(F_CC, MT6375_REG_CHG_ICHG, 0, 5),
|
|
MT6375_CHG_FIELD(F_CHG_TMR, MT6375_REG_CHG_TMR, 4, 5),
|
|
MT6375_CHG_FIELD(F_CHG_TMR_EN, MT6375_REG_CHG_TMR, 7, 7),
|
|
MT6375_CHG_FIELD(F_EOC_RST, MT6375_REG_CHG_EOC, 0, 0),
|
|
MT6375_CHG_FIELD(F_TE, MT6375_REG_CHG_EOC, 1, 1),
|
|
MT6375_CHG_FIELD(F_IEOC, MT6375_REG_CHG_EOC, 4, 7),
|
|
MT6375_CHG_FIELD(F_BLEED_DIS_EN, MT6375_REG_CHG_VSYS, 7, 7),
|
|
MT6375_CHG_FIELD(F_WDT, MT6375_REG_CHG_WDT, 0, 1),
|
|
MT6375_CHG_FIELD(F_WDT_RST, MT6375_REG_CHG_WDT, 2, 2),
|
|
MT6375_CHG_FIELD(F_WDT_EN, MT6375_REG_CHG_WDT, 3, 3),
|
|
MT6375_CHG_FIELD(F_PE20_CODE, MT6375_REG_CHG_PUMPX, 0, 4),
|
|
MT6375_CHG_FIELD(F_PE10_INC, MT6375_REG_CHG_PUMPX, 5, 5),
|
|
MT6375_CHG_FIELD(F_PE_SEL, MT6375_REG_CHG_PUMPX, 6, 6),
|
|
MT6375_CHG_FIELD(F_PE_EN, MT6375_REG_CHG_PUMPX, 7, 7),
|
|
MT6375_CHG_FIELD(F_AICC_VTH, MT6375_REG_CHG_AICC1, 0, 6),
|
|
MT6375_CHG_FIELD(F_AICC_EN, MT6375_REG_CHG_AICC1, 7, 7),
|
|
MT6375_CHG_FIELD(F_AICC_RPT, MT6375_REG_CHG_AICC2, 0, 6),
|
|
MT6375_CHG_FIELD(F_AICC_ONESHOT, MT6375_REG_CHG_AICC2, 7, 7),
|
|
MT6375_CHG_FIELD(F_OTG_CC, MT6375_REG_OTG_C, 0, 2),
|
|
MT6375_CHG_FIELD(F_OTG_LBP, MT6375_REG_OTG_LBP, 0, 3),
|
|
MT6375_CHG_FIELD(F_IRCMP_V, MT6375_REG_BAT_COMP, 0, 2),
|
|
MT6375_CHG_FIELD(F_IRCMP_R, MT6375_REG_BAT_COMP, 4, 6),
|
|
MT6375_CHG_FIELD_RANGE(F_IC_STAT, MT6375_REG_CHG_STAT, 0, 3, false),
|
|
MT6375_CHG_FIELD(F_FORCE_VBUS_SINK, MT6375_REG_CHG_HD_TOP1, 6, 6),
|
|
MT6375_CHG_FIELD(F_DCDT_SEL, MT6375_REG_BC12_FUNC, 4, 5),
|
|
MT6375_CHG_FIELD(F_BC12_EN, MT6375_REG_BC12_FUNC, 7, 7),
|
|
MT6375_CHG_FIELD_RANGE(F_PORT_STAT, MT6375_REG_BC12_STAT, 0, 3, false),
|
|
MT6375_CHG_FIELD(F_DM_DET_EN, MT6375_REG_DPDM_CTRL1, 0, 0),
|
|
MT6375_CHG_FIELD(F_DP_DET_EN, MT6375_REG_DPDM_CTRL1, 1, 1),
|
|
MT6375_CHG_FIELD(F_DPDM_SW_VCP_EN, MT6375_REG_DPDM_CTRL1, 5, 5),
|
|
MT6375_CHG_FIELD(F_MANUAL_MODE, MT6375_REG_DPDM_CTRL1, 7, 7),
|
|
MT6375_CHG_FIELD(F_DP_LDO_VSEL, MT6375_REG_DPDM_CTRL2, 4, 6),
|
|
MT6375_CHG_FIELD(F_DP_LDO_EN, MT6375_REG_DPDM_CTRL2, 7, 7),
|
|
MT6375_CHG_FIELD(F_DP_PULL_RSEL, MT6375_REG_DPDM_CTRL4, 6, 6),
|
|
MT6375_CHG_FIELD(F_DP_PULL_REN, MT6375_REG_DPDM_CTRL4, 7, 7),
|
|
MT6375_CHG_FIELD(F_VBAT_MON_EN, MT6375_REG_ADC_CONFG1, 5, 5),
|
|
MT6375_CHG_FIELD(F_ST_PWR_RDY, MT6375_REG_CHG_STAT0, 0, 0),
|
|
MT6375_CHG_FIELD(F_ST_MIVR, MT6375_REG_CHG_STAT1, 7, 7),
|
|
};
|
|
|
|
static inline int mt6375_chg_field_set(struct mt6375_chg_data *ddata,
|
|
enum mt6375_chg_reg_field fd, u32 val);
|
|
static int mt6375_enable_hm(struct mt6375_chg_data *ddata, bool en)
|
|
{
|
|
int ret = 0;
|
|
|
|
mutex_lock(&ddata->hm_lock);
|
|
if (en) {
|
|
if (ddata->hm_use_cnt == 0) {
|
|
ret = regmap_write(ddata->rmap, MT6375_REG_TM_PAS_CODE1,
|
|
0x69);
|
|
if (ret < 0)
|
|
goto out;
|
|
}
|
|
ddata->hm_use_cnt++;
|
|
} else {
|
|
if (ddata->hm_use_cnt == 1) {
|
|
ret = regmap_write(ddata->rmap, MT6375_REG_TM_PAS_CODE1,
|
|
0x00);
|
|
if (ret < 0)
|
|
goto out;
|
|
}
|
|
if (ddata->hm_use_cnt > 0)
|
|
ddata->hm_use_cnt--;
|
|
}
|
|
out:
|
|
mutex_unlock(&ddata->hm_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int mt6375_set_boost_param(struct mt6375_chg_data *ddata, bool bst)
|
|
{
|
|
int i, ret;
|
|
u8 val;
|
|
|
|
static const u16 regs[] = {
|
|
MT6375_REG_CHG_TOP2,
|
|
MT6375_REG_CHG_DUMY0,
|
|
MT6375_REG_CHG_HD_BUCK5,
|
|
MT6375_REG_CHG_VSYS,
|
|
};
|
|
static const u8 msks[] = {
|
|
MT6375_MSK_CLK_FREQ,
|
|
MT6375_MSK_COMP_CLAMP,
|
|
MT6375_MSK_BUCK_RAMPOFT,
|
|
MT6375_MSK_BLEED_DIS_EN,
|
|
};
|
|
static const u8 buck[] = {
|
|
0x01, 0x00, 0x01, 0x01,
|
|
};
|
|
static const u8 boost[] = {
|
|
0x00, 0x03, 0x03, 0x00,
|
|
};
|
|
|
|
ret = mt6375_enable_hm(ddata, true);
|
|
if (ret < 0)
|
|
return ret;
|
|
for (i = 0; i < ARRAY_SIZE(regs); i++) {
|
|
val = bst ? boost[i] : buck[i];
|
|
val <<= ffs(msks[i]) - 1;
|
|
ret = regmap_update_bits(ddata->rmap, regs[i], msks[i], val);
|
|
if (ret < 0) {
|
|
dev_err(ddata->dev,
|
|
"failed to set reg0x%02X=0x%02X, msk0x%02X\n",
|
|
regs[i], val, msks[i]);
|
|
goto recover;
|
|
}
|
|
}
|
|
goto out;
|
|
recover:
|
|
/*
|
|
* we do not guarantee the recovery is OK
|
|
* keep the error code from above
|
|
*/
|
|
for (; i >= 0; i--) {
|
|
val = bst ? buck[i] : boost[i];
|
|
val <<= ffs(msks[i]) - 1;
|
|
if (regmap_update_bits(ddata->rmap, regs[i], msks[i],
|
|
val) < 0) {
|
|
dev_err(ddata->dev,
|
|
"failed to set reg0x%02X=0x%02X, msk0x%02X\n",
|
|
regs[i], val, msks[i]);
|
|
}
|
|
}
|
|
out:
|
|
mt6375_enable_hm(ddata, false);
|
|
return ret;
|
|
}
|
|
|
|
static bool mt6375_chg_is_usb_killer(struct mt6375_chg_data *ddata)
|
|
{
|
|
int i, ret, vdp, vdm;
|
|
bool killer = false;
|
|
static const u32 vdiff = 200;
|
|
struct mt6375_chg_platform_data *pdata = dev_get_platdata(ddata->dev);
|
|
static const struct {
|
|
enum mt6375_chg_reg_field fd;
|
|
u32 val;
|
|
} settings[] = {
|
|
{ F_MANUAL_MODE, 1 },
|
|
{ F_DPDM_SW_VCP_EN, 1 },
|
|
{ F_DP_DET_EN, 1 },
|
|
{ F_DM_DET_EN, 1 },
|
|
{ F_DP_LDO_VSEL, 1800 },
|
|
{ F_DP_LDO_EN, 1 },
|
|
{ F_DP_PULL_RSEL, 0 },
|
|
{ F_DP_PULL_REN, 1 },
|
|
};
|
|
|
|
if (!pdata->usb_killer_detect) {
|
|
mt_dbg(ddata->dev, "disabled\n");
|
|
return false;
|
|
}
|
|
|
|
/* turn on usb dp 1.8V */
|
|
for (i = 0; i < ARRAY_SIZE(settings); i++) {
|
|
ret = mt6375_chg_field_set(ddata, settings[i].fd,
|
|
settings[i].val);
|
|
if (ret < 0)
|
|
goto recover;
|
|
}
|
|
--i;
|
|
|
|
/* check usb dpdm */
|
|
ret = iio_read_channel_processed(&ddata->iio_adcs[ADC_CHAN_USBDP],
|
|
&vdp);
|
|
if (ret < 0) {
|
|
dev_err(ddata->dev, "failed to read usbdp voltage\n");
|
|
goto recover;
|
|
}
|
|
ret = iio_read_channel_processed(&ddata->iio_adcs[ADC_CHAN_USBDM],
|
|
&vdm);
|
|
if (ret < 0) {
|
|
dev_err(ddata->dev, "failed to read usbdm voltage\n");
|
|
goto recover;
|
|
}
|
|
vdp = U_TO_M(vdp);
|
|
vdm = U_TO_M(vdm);
|
|
mt_dbg(ddata->dev, "dp=%dmV, dm=%dmV, vdiff=%dmV\n", vdp, vdm,
|
|
abs(vdp - vdm));
|
|
if (abs(vdp - vdm) < vdiff) {
|
|
mt_dbg(ddata->dev, "suspect usb killer\n");
|
|
killer = true;
|
|
}
|
|
recover:
|
|
/* we do not guarantee the recovery is OK */
|
|
for (; i >= 0; i--)
|
|
mt6375_chg_field_set(ddata, settings[i].fd, 0);
|
|
return killer;
|
|
}
|
|
|
|
static int mt6375_chg_regulator_enable(struct regulator_dev *rdev)
|
|
{
|
|
int ret;
|
|
struct mt6375_chg_data *ddata = rdev->reg_data;
|
|
|
|
if (mt6375_chg_is_usb_killer(ddata))
|
|
return -EIO;
|
|
ret = mt6375_set_boost_param(ddata, true);
|
|
if (ret < 0)
|
|
return ret;
|
|
ret = regulator_enable_regmap(rdev);
|
|
if (ret < 0) {
|
|
mt6375_set_boost_param(ddata, false);
|
|
return ret;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int mt6375_chg_regulator_disable(struct regulator_dev *rdev)
|
|
{
|
|
int ret;
|
|
struct mt6375_chg_data *ddata = rdev->reg_data;
|
|
|
|
ret = mt6375_set_boost_param(ddata, false);
|
|
if (ret < 0)
|
|
return ret;
|
|
ret = regulator_disable_regmap(rdev);
|
|
if (ret < 0) {
|
|
mt6375_set_boost_param(ddata, true);
|
|
return ret;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static const struct regulator_ops mt6375_chg_otg_rops = {
|
|
.enable = mt6375_chg_regulator_enable,
|
|
.disable = mt6375_chg_regulator_disable,
|
|
.is_enabled = regulator_is_enabled_regmap,
|
|
.list_voltage = regulator_list_voltage_linear,
|
|
.set_voltage_sel = regulator_set_voltage_sel_regmap,
|
|
.get_voltage_sel = regulator_get_voltage_sel_regmap,
|
|
.set_current_limit = regulator_set_current_limit_regmap,
|
|
.get_current_limit = regulator_get_current_limit_regmap,
|
|
};
|
|
|
|
static const struct regulator_desc mt6375_chg_otg_rdesc = {
|
|
.of_match = "mt6375,otg-vbus",
|
|
.ops = &mt6375_chg_otg_rops,
|
|
.owner = THIS_MODULE,
|
|
.type = REGULATOR_VOLTAGE,
|
|
.min_uV = 4850000,
|
|
.uV_step = 25000,
|
|
.n_voltages = 47,
|
|
.linear_min_sel = 20,
|
|
.curr_table = mt6375_chg_otg_cc_micro,
|
|
.n_current_limits = ARRAY_SIZE(mt6375_chg_otg_cc_micro),
|
|
.csel_reg = MT6375_REG_OTG_C,
|
|
.csel_mask = MT6375_MSK_OTG_CC,
|
|
.vsel_reg = MT6375_REG_OTG_V,
|
|
.vsel_mask = MT6375_MSK_OTG_CV,
|
|
.enable_reg = MT6375_REG_CHG_TOP1,
|
|
.enable_mask = MT6375_MSK_OTG_EN,
|
|
};
|
|
|
|
static const struct mt6375_chg_platform_data mt6375_chg_pdata_def = {
|
|
.aicr = 3225,
|
|
.mivr = 4400,
|
|
.ichg = 2000,
|
|
.ieoc = 150,
|
|
.cv = 4200,
|
|
.wdt = 40000,
|
|
.vbus_ov = 14500,
|
|
.vrec = 100,
|
|
.ircmp_v = 0,
|
|
.ircmp_r = 0, /* uOhm */
|
|
.chg_tmr = 10, /* hr */
|
|
.dcdt_sel = 600,
|
|
.wdt_en = false,
|
|
.te_en = true,
|
|
.chg_tmr_en = true,
|
|
.chg_name = "primary_chg",
|
|
.usb_killer_detect = false,
|
|
};
|
|
|
|
static inline u8 mt6375_chg_val_toreg(const struct mt6375_chg_range *range,
|
|
u32 val)
|
|
{
|
|
int i;
|
|
u8 reg;
|
|
|
|
if (!range)
|
|
return val;
|
|
|
|
if (range->table) {
|
|
if (val <= range->table[0])
|
|
return 0;
|
|
for (i = 1; i < range->num_table - 1; i++) {
|
|
if (val == range->table[i])
|
|
return i;
|
|
if (val > range->table[i] &&
|
|
val < range->table[i + 1])
|
|
return range->round_up ? i + 1 : i;
|
|
}
|
|
return range->num_table - 1;
|
|
}
|
|
if (val <= range->min)
|
|
reg = 0;
|
|
else if (val >= range->max)
|
|
reg = (range->max - range->min) / range->step;
|
|
else if (range->round_up)
|
|
reg = DIV_ROUND_UP(val - range->min, range->step);
|
|
else
|
|
reg = (val - range->min) / range->step;
|
|
return reg + range->offset;
|
|
}
|
|
|
|
static inline u32 mt6375_chg_reg_toval(const struct mt6375_chg_range *range,
|
|
u8 reg)
|
|
{
|
|
if (!range)
|
|
return reg;
|
|
return range->table ? range->table[reg] :
|
|
range->min + range->step * (reg - range->offset);
|
|
}
|
|
|
|
static inline int mt6375_chg_field_get(struct mt6375_chg_data *ddata,
|
|
enum mt6375_chg_reg_field fd, u32 *val)
|
|
{
|
|
int ret;
|
|
u32 regval;
|
|
|
|
ret = regmap_field_read(ddata->rmap_fields[fd], ®val);
|
|
if (ret < 0)
|
|
return ret;
|
|
*val = mt6375_chg_reg_toval(mt6375_chg_fields[fd].range, regval);
|
|
mt_dbg(ddata->dev, "%s, reg=0x%02X, val=%d\n",
|
|
mt6375_chg_fields[fd].name, regval, *val);
|
|
return 0;
|
|
}
|
|
|
|
static inline int mt6375_chg_field_set(struct mt6375_chg_data *ddata,
|
|
enum mt6375_chg_reg_field fd, u32 val)
|
|
{
|
|
mt_dbg(ddata->dev, "%s, val=%d\n", mt6375_chg_fields[fd].name,
|
|
val);
|
|
val = mt6375_chg_val_toreg(mt6375_chg_fields[fd].range, val);
|
|
return regmap_field_write(ddata->rmap_fields[fd], val);
|
|
}
|
|
|
|
static int mt6375_chg_enable_charging(struct mt6375_chg_data *ddata, bool en)
|
|
{
|
|
int ret;
|
|
|
|
mutex_lock(&ddata->cv_lock);
|
|
ret = mt6375_chg_field_set(ddata, F_CHG_EN, en);
|
|
mutex_unlock(&ddata->cv_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int mt6375_chg_is_enabled(struct mt6375_chg_data *ddata, bool *en)
|
|
{
|
|
int ret = 0;
|
|
u32 val = 0;
|
|
|
|
ret = mt6375_chg_field_get(ddata, F_CHG_EN, &val);
|
|
if (ret < 0)
|
|
return ret;
|
|
*en = val;
|
|
return 0;
|
|
}
|
|
|
|
static int mt6375_chg_is_charge_done(struct mt6375_chg_data *ddata, bool *done)
|
|
{
|
|
int ret;
|
|
union power_supply_propval val;
|
|
|
|
ret = power_supply_get_property(ddata->psy, POWER_SUPPLY_PROP_STATUS,
|
|
&val);
|
|
if (ret < 0)
|
|
return ret;
|
|
*done = (val.intval == POWER_SUPPLY_STATUS_FULL);
|
|
return 0;
|
|
}
|
|
|
|
static int mt6375_chg_set_cv(struct mt6375_chg_data *ddata, u32 mV)
|
|
{
|
|
int ret = 0;
|
|
bool done = false, enabled = false;
|
|
|
|
mutex_lock(&ddata->cv_lock);
|
|
if (ddata->batprotect_en) {
|
|
dev_notice(ddata->dev,
|
|
"batprotect enabled, should not set cv\n");
|
|
goto out;
|
|
}
|
|
if (mV <= ddata->cv || mV >= ddata->cv + RECHG_THRESHOLD)
|
|
goto out_cv;
|
|
ret = mt6375_chg_is_charge_done(ddata, &done);
|
|
if (ret < 0 || !done)
|
|
goto out_cv;
|
|
ret = mt6375_chg_is_enabled(ddata, &enabled);
|
|
if (ret < 0 || !enabled)
|
|
goto out_cv;
|
|
if (mt6375_chg_field_set(ddata, F_CHG_EN, false) < 0)
|
|
dev_notice(ddata->dev, "failed to disable charging\n");
|
|
out_cv:
|
|
ret = mt6375_chg_field_set(ddata, F_CV, mV);
|
|
if (!ret)
|
|
ddata->cv = mV;
|
|
if (done && enabled)
|
|
mt6375_chg_field_set(ddata, F_CHG_EN, true);
|
|
out:
|
|
mutex_unlock(&ddata->cv_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int mt6375_get_chg_status(struct mt6375_chg_data *ddata)
|
|
{
|
|
int ret = 0, attach;
|
|
u32 stat;
|
|
bool chg_en = false;
|
|
|
|
attach = atomic_read(&ddata->attach);
|
|
if (!attach)
|
|
return POWER_SUPPLY_STATUS_NOT_CHARGING;
|
|
|
|
ret = mt6375_chg_is_enabled(ddata, &chg_en);
|
|
if (ret < 0)
|
|
return ret;
|
|
ret = mt6375_chg_field_get(ddata, F_IC_STAT, &stat);
|
|
if (ret < 0)
|
|
return ret;
|
|
switch (stat) {
|
|
case CHG_STAT_OTG:
|
|
return POWER_SUPPLY_STATUS_DISCHARGING;
|
|
case CHG_STAT_SLEEP:
|
|
case CHG_STAT_VBUS_RDY:
|
|
case CHG_STAT_TRICKLE:
|
|
case CHG_STAT_PRE:
|
|
case CHG_STAT_FAST:
|
|
case CHG_STAT_EOC:
|
|
case CHG_STAT_BKGND:
|
|
if (chg_en)
|
|
return POWER_SUPPLY_STATUS_CHARGING;
|
|
else
|
|
return POWER_SUPPLY_STATUS_NOT_CHARGING;
|
|
case CHG_STAT_DONE:
|
|
return POWER_SUPPLY_STATUS_FULL;
|
|
case CHG_STAT_FAULT:
|
|
return POWER_SUPPLY_STATUS_NOT_CHARGING;
|
|
default:
|
|
return POWER_SUPPLY_STATUS_UNKNOWN;
|
|
}
|
|
}
|
|
|
|
static void mt6375_chg_attach_pre_process(struct mt6375_chg_data *ddata,
|
|
enum mt6375_attach_trigger trig,
|
|
int attach)
|
|
{
|
|
struct mt6375_chg_platform_data *pdata = dev_get_platdata(ddata->dev);
|
|
bool bc12_dn;
|
|
|
|
mt_dbg(ddata->dev, "trig=%s,attach=%d\n",
|
|
mt6375_attach_trig_names[trig], attach);
|
|
/* if attach trigger is not match, ignore it */
|
|
if (pdata->attach_trig != trig) {
|
|
mt_dbg(ddata->dev, "trig=%s ignored\n",
|
|
mt6375_attach_trig_names[trig]);
|
|
return;
|
|
}
|
|
|
|
mutex_lock(&ddata->attach_lock);
|
|
if (attach == ATTACH_TYPE_NONE)
|
|
ddata->bc12_dn = false;
|
|
|
|
bc12_dn = ddata->bc12_dn;
|
|
if (!bc12_dn)
|
|
atomic_set(&ddata->attach, attach);
|
|
mutex_unlock(&ddata->attach_lock);
|
|
|
|
if (attach > ATTACH_TYPE_PD && bc12_dn)
|
|
return;
|
|
|
|
if (!queue_work(ddata->wq, &ddata->bc12_work))
|
|
dev_notice(ddata->dev, "%s bc12 work already queued\n", __func__);
|
|
}
|
|
|
|
static void mt6375_chg_pwr_rdy_process(struct mt6375_chg_data *ddata)
|
|
{
|
|
int ret;
|
|
u32 val;
|
|
|
|
ret = mt6375_chg_field_get(ddata, F_ST_PWR_RDY, &val);
|
|
if (ret < 0 || ddata->pwr_rdy == val)
|
|
return;
|
|
ddata->pwr_rdy = val;
|
|
mt_dbg(ddata->dev, "pwr_rdy=%d\n", val);
|
|
ret = mt6375_chg_field_set(ddata, F_BLEED_DIS_EN, !ddata->pwr_rdy);
|
|
if (ret < 0)
|
|
dev_err(ddata->dev, "failed to set bleed discharge = %d\n",
|
|
!ddata->pwr_rdy);
|
|
mt6375_chg_attach_pre_process(ddata, ATTACH_TRIG_PWR_RDY,
|
|
val ? ATTACH_TYPE_PWR_RDY : ATTACH_TYPE_NONE);
|
|
}
|
|
|
|
static int mt6375_chg_set_usbsw(struct mt6375_chg_data *ddata,
|
|
enum mt6375_usbsw usbsw)
|
|
{
|
|
struct phy *phy;
|
|
int ret, mode = (usbsw == USBSW_CHG) ? PHY_MODE_BC11_SET :
|
|
PHY_MODE_BC11_CLR;
|
|
|
|
mt_dbg(ddata->dev, "usbsw=%d\n", usbsw);
|
|
phy = phy_get(ddata->dev, "usb2-phy");
|
|
if (IS_ERR_OR_NULL(phy)) {
|
|
dev_err(ddata->dev, "failed to get usb2-phy\n");
|
|
return -ENODEV;
|
|
}
|
|
ret = phy_set_mode_ext(phy, PHY_MODE_USB_DEVICE, mode);
|
|
if (ret)
|
|
dev_err(ddata->dev, "failed to set phy ext mode\n");
|
|
phy_put(ddata->dev, phy);
|
|
return ret;
|
|
}
|
|
|
|
static bool is_usb_rdy(struct device *dev)
|
|
{
|
|
bool ready = true;
|
|
struct device_node *node;
|
|
|
|
node = of_parse_phandle(dev->of_node, "usb", 0);
|
|
if (node) {
|
|
ready = !of_property_read_bool(node, "cdp-block");
|
|
mt_dbg(dev, "usb ready = %d\n", ready);
|
|
} else
|
|
dev_warn(dev, "usb node missing or invalid\n");
|
|
return ready;
|
|
}
|
|
|
|
static int mt6375_chg_enable_bc12(struct mt6375_chg_data *ddata, bool en)
|
|
{
|
|
int i, ret, attach;
|
|
static const int max_wait_cnt = 250;
|
|
|
|
mt_dbg(ddata->dev, "en=%d\n", en);
|
|
if (en) {
|
|
/* CDP port specific process */
|
|
dev_info(ddata->dev, "check CDP block\n");
|
|
for (i = 0; i < max_wait_cnt; i++) {
|
|
if (is_usb_rdy(ddata->dev))
|
|
break;
|
|
attach = atomic_read(&ddata->attach);
|
|
if (attach == ATTACH_TYPE_TYPEC)
|
|
msleep(100);
|
|
else {
|
|
dev_notice(ddata->dev, "%s: change attach:%d, disable bc12\n",
|
|
__func__, attach);
|
|
en = false;
|
|
break;
|
|
}
|
|
}
|
|
if (i == max_wait_cnt)
|
|
dev_notice(ddata->dev, "CDP timeout\n", __func__);
|
|
else
|
|
dev_info(ddata->dev, "CDP free\n", __func__);
|
|
}
|
|
ret = mt6375_chg_set_usbsw(ddata, en ? USBSW_CHG : USBSW_USB);
|
|
if (ret)
|
|
return ret;
|
|
return mt6375_chg_field_set(ddata, F_BC12_EN, en);
|
|
}
|
|
|
|
static void mt6375_chg_bc12_work_func(struct work_struct *work)
|
|
{
|
|
struct mt6375_chg_data *ddata = container_of(work,
|
|
struct mt6375_chg_data,
|
|
bc12_work);
|
|
struct mt6375_chg_platform_data *pdata = dev_get_platdata(ddata->dev);
|
|
bool bc12_ctrl = true, bc12_en = false, rpt_psy = true;
|
|
int ret, attach;
|
|
u32 val = 0;
|
|
|
|
mutex_lock(&ddata->attach_lock);
|
|
attach = atomic_read(&ddata->attach);
|
|
mt_dbg(ddata->dev, "attach=%d\n", attach);
|
|
|
|
if (attach > ATTACH_TYPE_NONE && pdata->boot_mode == 5) {
|
|
/* skip bc12 to speed up ADVMETA_BOOT */
|
|
dev_notice(ddata->dev, "force SDP in meta mode\n");
|
|
ddata->psy_desc.type = POWER_SUPPLY_TYPE_USB;
|
|
ddata->psy_usb_type = POWER_SUPPLY_USB_TYPE_SDP;
|
|
goto out;
|
|
}
|
|
|
|
switch (attach) {
|
|
case ATTACH_TYPE_NONE:
|
|
ddata->psy_desc.type = POWER_SUPPLY_TYPE_USB;
|
|
ddata->psy_usb_type = POWER_SUPPLY_USB_TYPE_UNKNOWN;
|
|
goto out;
|
|
case ATTACH_TYPE_TYPEC:
|
|
if (!ddata->bc12_dn) {
|
|
bc12_en = true;
|
|
rpt_psy = false;
|
|
goto out;
|
|
}
|
|
ret = mt6375_chg_field_get(ddata, F_PORT_STAT, &val);
|
|
if (ret < 0) {
|
|
dev_err(ddata->dev, "failed to get port stat\n");
|
|
rpt_psy = false;
|
|
goto out;
|
|
}
|
|
break;
|
|
case ATTACH_TYPE_PD_SDP:
|
|
val = PORT_STAT_SDP;
|
|
break;
|
|
case ATTACH_TYPE_PD_DCP:
|
|
/* not to enable bc12 */
|
|
ddata->psy_desc.type = POWER_SUPPLY_TYPE_USB_DCP;
|
|
ddata->psy_usb_type = POWER_SUPPLY_USB_TYPE_DCP;
|
|
goto out;
|
|
case ATTACH_TYPE_PD_NONSTD:
|
|
val = PORT_STAT_UNKNOWN_TA;
|
|
break;
|
|
default:
|
|
dev_info(ddata->dev,
|
|
"%s: using tradtional bc12 flow!\n", __func__);
|
|
break;
|
|
}
|
|
|
|
switch (val) {
|
|
case PORT_STAT_NOINFO:
|
|
bc12_ctrl = false;
|
|
rpt_psy = false;
|
|
dev_info(ddata->dev, "%s no info\n", __func__);
|
|
goto out;
|
|
case PORT_STAT_APPLE_5W:
|
|
case PORT_STAT_APPLE_10W:
|
|
case PORT_STAT_APPLE_12W:
|
|
case PORT_STAT_SAMSUNG:
|
|
case PORT_STAT_DCP:
|
|
ddata->psy_desc.type = POWER_SUPPLY_TYPE_USB_DCP;
|
|
ddata->psy_usb_type = POWER_SUPPLY_USB_TYPE_DCP;
|
|
bc12_en = true;
|
|
break;
|
|
case PORT_STAT_SDP:
|
|
ddata->psy_desc.type = POWER_SUPPLY_TYPE_USB;
|
|
ddata->psy_usb_type = POWER_SUPPLY_USB_TYPE_SDP;
|
|
break;
|
|
case PORT_STAT_CDP:
|
|
ddata->psy_desc.type = POWER_SUPPLY_TYPE_USB_CDP;
|
|
ddata->psy_usb_type = POWER_SUPPLY_USB_TYPE_CDP;
|
|
break;
|
|
case PORT_STAT_UNKNOWN_TA:
|
|
ddata->psy_desc.type = POWER_SUPPLY_TYPE_USB;
|
|
ddata->psy_usb_type = POWER_SUPPLY_USB_TYPE_DCP;
|
|
break;
|
|
default:
|
|
bc12_ctrl = false;
|
|
rpt_psy = false;
|
|
dev_info(ddata->dev, "Unknown port stat %d\n", val);
|
|
goto out;
|
|
}
|
|
mt_dbg(ddata->dev, "port stat = %s\n", mt6375_port_stat_names[val]);
|
|
out:
|
|
mutex_unlock(&ddata->attach_lock);
|
|
if (bc12_ctrl && (mt6375_chg_enable_bc12(ddata, bc12_en) < 0))
|
|
dev_err(ddata->dev, "failed to set bc12 = %d\n", bc12_en);
|
|
if (rpt_psy)
|
|
power_supply_changed(ddata->psy);
|
|
}
|
|
|
|
static enum power_supply_usb_type mt6375_chg_psy_usb_types[] = {
|
|
POWER_SUPPLY_USB_TYPE_UNKNOWN,
|
|
POWER_SUPPLY_USB_TYPE_SDP,
|
|
POWER_SUPPLY_USB_TYPE_CDP,
|
|
POWER_SUPPLY_USB_TYPE_DCP,
|
|
};
|
|
|
|
static enum power_supply_property mt6375_chg_psy_properties[] = {
|
|
POWER_SUPPLY_PROP_MANUFACTURER,
|
|
POWER_SUPPLY_PROP_ONLINE,
|
|
POWER_SUPPLY_PROP_STATUS,
|
|
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
|
|
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
|
|
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
|
|
POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT,
|
|
POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
|
|
POWER_SUPPLY_PROP_USB_TYPE,
|
|
POWER_SUPPLY_PROP_CURRENT_MAX,
|
|
POWER_SUPPLY_PROP_VOLTAGE_MAX,
|
|
POWER_SUPPLY_PROP_CALIBRATE,
|
|
POWER_SUPPLY_PROP_ENERGY_EMPTY,
|
|
};
|
|
|
|
static int mt6375_chg_property_is_writeable(struct power_supply *psy,
|
|
enum power_supply_property psp)
|
|
{
|
|
switch (psp) {
|
|
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_INPUT_VOLTAGE_LIMIT:
|
|
case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
|
|
case POWER_SUPPLY_PROP_STATUS:
|
|
case POWER_SUPPLY_PROP_ONLINE:
|
|
case POWER_SUPPLY_PROP_ENERGY_EMPTY:
|
|
return 1;
|
|
default:
|
|
return 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int mt6375_chg_get_property(struct power_supply *psy,
|
|
enum power_supply_property psp,
|
|
union power_supply_propval *val)
|
|
{
|
|
struct mt6375_chg_data *ddata = power_supply_get_drvdata(psy);
|
|
int ret = 0;
|
|
u16 data;
|
|
u32 _val = 0;
|
|
|
|
mt_dbg(ddata->dev, "psp=%d\n", psp);
|
|
switch (psp) {
|
|
case POWER_SUPPLY_PROP_MANUFACTURER:
|
|
val->strval = MT6375_MANUFACTURER;
|
|
break;
|
|
case POWER_SUPPLY_PROP_ONLINE:
|
|
val->intval = atomic_read(&ddata->attach);
|
|
break;
|
|
case POWER_SUPPLY_PROP_STATUS:
|
|
ret = mt6375_get_chg_status(ddata);
|
|
if (ret < 0)
|
|
return ret;
|
|
val->intval = ret;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
|
|
mutex_lock(&ddata->pe_lock);
|
|
ret = mt6375_chg_field_get(ddata, F_CC, &val->intval);
|
|
mutex_unlock(&ddata->pe_lock);
|
|
break;
|
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
|
|
mutex_lock(&ddata->cv_lock);
|
|
ret = mt6375_chg_field_get(ddata, F_CV, &val->intval);
|
|
mutex_unlock(&ddata->cv_lock);
|
|
break;
|
|
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
|
|
mutex_lock(&ddata->pe_lock);
|
|
ret = mt6375_chg_field_get(ddata, F_IAICR, &val->intval);
|
|
mutex_unlock(&ddata->pe_lock);
|
|
break;
|
|
case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT:
|
|
mutex_lock(&ddata->pe_lock);
|
|
ret = mt6375_chg_field_get(ddata, F_VMIVR, &val->intval);
|
|
mutex_unlock(&ddata->pe_lock);
|
|
break;
|
|
case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
|
|
ret = mt6375_chg_field_get(ddata, F_IEOC, &val->intval);
|
|
break;
|
|
case POWER_SUPPLY_PROP_USB_TYPE:
|
|
mutex_lock(&ddata->attach_lock);
|
|
val->intval = ddata->psy_usb_type;
|
|
mutex_unlock(&ddata->attach_lock);
|
|
break;
|
|
case POWER_SUPPLY_PROP_CURRENT_MAX:
|
|
if (ddata->psy_desc.type == POWER_SUPPLY_TYPE_USB)
|
|
val->intval = NORMAL_CHARGING_CURR_UA;
|
|
else if (ddata->psy_desc.type == POWER_SUPPLY_TYPE_USB_DCP)
|
|
val->intval = FAST_CHARGING_CURR_UA;
|
|
break;
|
|
case POWER_SUPPLY_PROP_VOLTAGE_MAX:
|
|
if (ddata->psy_desc.type == POWER_SUPPLY_TYPE_USB)
|
|
val->intval = 5000000;
|
|
break;
|
|
case POWER_SUPPLY_PROP_TYPE:
|
|
val->intval = ddata->psy_desc.type;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CALIBRATE:
|
|
mutex_lock(&ddata->cv_lock);
|
|
ret = mt6375_chg_field_get(ddata, F_VBAT_MON_EN, &_val);
|
|
if (_val) {
|
|
ret = -EBUSY;
|
|
dev_notice(ddata->dev, "vbat_mon is enabled\n");
|
|
mutex_unlock(&ddata->cv_lock);
|
|
break;
|
|
}
|
|
ret = mt6375_chg_field_set(ddata, F_VBAT_MON_EN, 1);
|
|
if (ret < 0) {
|
|
dev_notice(ddata->dev, "failed to enable vbat monitor\n");
|
|
mutex_unlock(&ddata->cv_lock);
|
|
break;
|
|
}
|
|
usleep_range(ADC_CONV_TIME_US * 2, ADC_CONV_TIME_US * 3);
|
|
ret = regmap_bulk_read(ddata->rmap, MT6375_REG_VBAT_MON_RPT, &data, 2);
|
|
if (ret < 0)
|
|
dev_notice(ddata->dev, "failed to get vbat monitor report\n");
|
|
else
|
|
val->intval = ADC_FROM_VBAT_RAW(be16_to_cpu(data));
|
|
if (mt6375_chg_field_set(ddata, F_VBAT_MON_EN, 0) < 0)
|
|
dev_notice(ddata->dev, "failed to disable vbat monitor\n");
|
|
mutex_unlock(&ddata->cv_lock);
|
|
break;
|
|
case POWER_SUPPLY_PROP_ENERGY_EMPTY:
|
|
val->intval = ddata->vbat0_flag;
|
|
break;
|
|
default:
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int mt6375_chg_set_property(struct power_supply *psy,
|
|
enum power_supply_property psp,
|
|
const union power_supply_propval *val)
|
|
{
|
|
int ret = 0;
|
|
struct mt6375_chg_data *ddata = power_supply_get_drvdata(psy);
|
|
|
|
mt_dbg(ddata->dev, "psp=%d\n", psp);
|
|
switch (psp) {
|
|
case POWER_SUPPLY_PROP_ONLINE:
|
|
mt6375_chg_attach_pre_process(ddata, ATTACH_TRIG_TYPEC,
|
|
val->intval);
|
|
break;
|
|
case POWER_SUPPLY_PROP_STATUS:
|
|
ret = mt6375_chg_enable_charging(ddata, val->intval);
|
|
break;
|
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
|
|
mutex_lock(&ddata->pe_lock);
|
|
ret = mt6375_chg_field_set(ddata, F_CC, val->intval);
|
|
mutex_unlock(&ddata->pe_lock);
|
|
break;
|
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
|
|
ret = mt6375_chg_set_cv(ddata, val->intval);
|
|
break;
|
|
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
|
|
mutex_lock(&ddata->pe_lock);
|
|
ret = mt6375_chg_field_set(ddata, F_IAICR, val->intval);
|
|
mutex_unlock(&ddata->pe_lock);
|
|
break;
|
|
case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT:
|
|
mutex_lock(&ddata->pe_lock);
|
|
ret = mt6375_chg_field_set(ddata, F_VMIVR, val->intval);
|
|
mutex_unlock(&ddata->pe_lock);
|
|
break;
|
|
case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
|
|
ret = mt6375_chg_field_set(ddata, F_IEOC, val->intval);
|
|
break;
|
|
case POWER_SUPPLY_PROP_ENERGY_EMPTY:
|
|
ddata->vbat0_flag = val->intval;
|
|
break;
|
|
default:
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static char *mt6375_psy_supplied_to[] = {
|
|
"battery",
|
|
"mtk-master-charger",
|
|
};
|
|
|
|
static const struct power_supply_desc mt6375_psy_desc = {
|
|
.type = POWER_SUPPLY_TYPE_USB,
|
|
.usb_types = mt6375_chg_psy_usb_types,
|
|
.num_usb_types = ARRAY_SIZE(mt6375_chg_psy_usb_types),
|
|
.properties = mt6375_chg_psy_properties,
|
|
.num_properties = ARRAY_SIZE(mt6375_chg_psy_properties),
|
|
.property_is_writeable = mt6375_chg_property_is_writeable,
|
|
.get_property = mt6375_chg_get_property,
|
|
.set_property = mt6375_chg_set_property,
|
|
};
|
|
|
|
/* The following functions are for mtk charger device */
|
|
|
|
static int mt6375_enable_charging(struct charger_device *chgdev, bool en)
|
|
{
|
|
struct mt6375_chg_data *ddata = charger_get_data(chgdev);
|
|
|
|
return mt6375_chg_enable_charging(ddata, en);
|
|
}
|
|
|
|
static int mt6375_is_enabled(struct charger_device *chgdev, bool *en)
|
|
{
|
|
struct mt6375_chg_data *ddata = charger_get_data(chgdev);
|
|
|
|
return mt6375_chg_is_enabled(ddata, en);
|
|
}
|
|
|
|
static int mt6375_set_ichg(struct charger_device *chgdev, u32 uA)
|
|
{
|
|
struct mt6375_chg_data *ddata = charger_get_data(chgdev);
|
|
union power_supply_propval val = { .intval = U_TO_M(uA) };
|
|
|
|
mt_dbg(ddata->dev, "ichg=%d\n", uA);
|
|
return power_supply_set_property(ddata->psy,
|
|
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, &val);
|
|
}
|
|
|
|
static int mt6375_get_ichg(struct charger_device *chgdev, u32 *uA)
|
|
{
|
|
int ret;
|
|
struct mt6375_chg_data *ddata = charger_get_data(chgdev);
|
|
union power_supply_propval val;
|
|
|
|
ret = power_supply_get_property(ddata->psy,
|
|
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, &val);
|
|
if (ret < 0)
|
|
return ret;
|
|
*uA = M_TO_U(val.intval);
|
|
return 0;
|
|
}
|
|
|
|
static int mt6375_get_min_ichg(struct charger_device *chgdev, u32 *uA)
|
|
{
|
|
*uA = M_TO_U(mt6375_chg_fields[F_CC].range->min);
|
|
return 0;
|
|
}
|
|
|
|
static int mt6375_set_cv(struct charger_device *chgdev, u32 uV)
|
|
{
|
|
struct mt6375_chg_data *ddata = charger_get_data(chgdev);
|
|
|
|
mt_dbg(ddata->dev, "cv=%d\n", uV);
|
|
return mt6375_chg_set_cv(ddata, U_TO_M(uV));
|
|
}
|
|
|
|
static int mt6375_get_cv(struct charger_device *chgdev, u32 *uV)
|
|
{
|
|
int ret;
|
|
struct mt6375_chg_data *ddata = charger_get_data(chgdev);
|
|
union power_supply_propval val;
|
|
|
|
ret = power_supply_get_property(ddata->psy,
|
|
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, &val);
|
|
if (ret < 0)
|
|
return ret;
|
|
*uV = M_TO_U(val.intval);
|
|
return 0;
|
|
}
|
|
|
|
static int mt6375_set_aicr(struct charger_device *chgdev, u32 uA)
|
|
{
|
|
struct mt6375_chg_data *ddata = charger_get_data(chgdev);
|
|
union power_supply_propval val = { .intval = U_TO_M(uA) };
|
|
|
|
mt_dbg(ddata->dev, "aicr=%d\n", uA);
|
|
return power_supply_set_property(ddata->psy,
|
|
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, &val);
|
|
}
|
|
|
|
static int mt6375_get_aicr(struct charger_device *chgdev, u32 *uA)
|
|
{
|
|
int ret;
|
|
struct mt6375_chg_data *ddata = charger_get_data(chgdev);
|
|
union power_supply_propval val;
|
|
|
|
ret = power_supply_get_property(ddata->psy,
|
|
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, &val);
|
|
if (ret < 0)
|
|
return ret;
|
|
*uA = M_TO_U(val.intval);
|
|
return 0;
|
|
}
|
|
|
|
static int mt6375_get_min_aicr(struct charger_device *chgdev, u32 *uA)
|
|
{
|
|
*uA = M_TO_U(mt6375_chg_fields[F_IAICR].range->min);
|
|
return 0;
|
|
}
|
|
|
|
static int mt6375_set_mivr(struct charger_device *chgdev, u32 uV)
|
|
{
|
|
struct mt6375_chg_data *ddata = charger_get_data(chgdev);
|
|
union power_supply_propval val = { .intval = U_TO_M(uV) };
|
|
|
|
mt_dbg(ddata->dev, "mivr=%d\n", uV);
|
|
return power_supply_set_property(ddata->psy,
|
|
POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT, &val);
|
|
}
|
|
|
|
static int mt6375_get_mivr(struct charger_device *chgdev, u32 *uV)
|
|
{
|
|
int ret;
|
|
struct mt6375_chg_data *ddata = charger_get_data(chgdev);
|
|
union power_supply_propval val;
|
|
|
|
ret = power_supply_get_property(ddata->psy,
|
|
POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT, &val);
|
|
if (ret < 0)
|
|
return ret;
|
|
*uV = M_TO_U(val.intval);
|
|
return 0;
|
|
}
|
|
|
|
static int mt6375_get_mivr_state(struct charger_device *chgdev, bool *active)
|
|
{
|
|
int ret;
|
|
u32 val;
|
|
struct mt6375_chg_data *ddata = charger_get_data(chgdev);
|
|
|
|
*active = false;
|
|
ret = mt6375_chg_field_get(ddata, F_ST_MIVR, &val);
|
|
if (ret < 0)
|
|
return ret;
|
|
*active = val;
|
|
return 0;
|
|
}
|
|
|
|
static int mt6375_get_adc(struct charger_device *chgdev, enum adc_channel chan,
|
|
int *min, int *max)
|
|
{
|
|
int ret;
|
|
enum mt6375_adc_chan adc_chan;
|
|
struct mt6375_chg_data *ddata = charger_get_data(chgdev);
|
|
|
|
switch (chan) {
|
|
case ADC_CHANNEL_VBUS:
|
|
adc_chan = ADC_CHAN_CHGVINDIV5;
|
|
break;
|
|
case ADC_CHANNEL_VSYS:
|
|
adc_chan = ADC_CHAN_VSYS;
|
|
break;
|
|
case ADC_CHANNEL_VBAT:
|
|
adc_chan = ADC_CHAN_VBAT;
|
|
break;
|
|
case ADC_CHANNEL_IBUS:
|
|
adc_chan = ADC_CHAN_IBUS;
|
|
break;
|
|
case ADC_CHANNEL_IBAT:
|
|
adc_chan = ADC_CHAN_IBAT;
|
|
break;
|
|
case ADC_CHANNEL_TEMP_JC:
|
|
adc_chan = ADC_CHAN_TEMP_JC;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
ret = iio_read_channel_processed(&ddata->iio_adcs[adc_chan], min);
|
|
if (ret < 0) {
|
|
dev_err(ddata->dev, "failed to read adc\n");
|
|
return ret;
|
|
}
|
|
*max = *min;
|
|
return 0;
|
|
}
|
|
|
|
static int mt6375_get_vbus(struct charger_device *chgdev, u32 *vbus)
|
|
{
|
|
return mt6375_get_adc(chgdev, ADC_CHANNEL_VBUS, vbus, vbus);
|
|
}
|
|
|
|
static int mt6375_get_ibus(struct charger_device *chgdev, u32 *ibus)
|
|
{
|
|
return mt6375_get_adc(chgdev, ADC_CHANNEL_IBUS, ibus, ibus);
|
|
}
|
|
|
|
static int mt6375_get_ibat(struct charger_device *chgdev, u32 *ibat)
|
|
{
|
|
return mt6375_get_adc(chgdev, ADC_CHANNEL_IBAT, ibat, ibat);
|
|
}
|
|
|
|
#define ABNORMAL_TEMP_JC 120
|
|
static int mt6375_get_tchg(struct charger_device *chgdev,
|
|
int *tchg_min, int *tchg_max)
|
|
{
|
|
struct mt6375_chg_data *ddata = charger_get_data(chgdev);
|
|
int temp_jc = 0, ret = 0, retry_cnt = 3;
|
|
|
|
/* temp abnormal workaround */
|
|
do {
|
|
ret = mt6375_get_adc(chgdev, ADC_CHANNEL_TEMP_JC, &temp_jc,
|
|
&temp_jc);
|
|
} while ((ret < 0 || temp_jc >= ABNORMAL_TEMP_JC) && (--retry_cnt) > 0);
|
|
if (ret < 0) {
|
|
dev_err(ddata->dev, "failed to get temp jc\n");
|
|
return ret;
|
|
}
|
|
/* use last result if temp_jc is abnormal */
|
|
if (temp_jc >= ABNORMAL_TEMP_JC)
|
|
temp_jc = atomic_read(&ddata->tchg);
|
|
else
|
|
atomic_set(&ddata->tchg, temp_jc);
|
|
*tchg_min = *tchg_max = temp_jc;
|
|
mt_dbg(ddata->dev, "tchg=%d\n", temp_jc);
|
|
return 0;
|
|
}
|
|
|
|
static int mt6375_get_zcv(struct charger_device *chgdev, u32 *uV)
|
|
{
|
|
struct mt6375_chg_data *ddata = charger_get_data(chgdev);
|
|
|
|
*uV = M_TO_U(ddata->zcv);
|
|
return 0;
|
|
}
|
|
|
|
static int mt6375_set_ieoc(struct charger_device *chgdev, u32 uA)
|
|
{
|
|
struct mt6375_chg_data *ddata = charger_get_data(chgdev);
|
|
union power_supply_propval val = { .intval = U_TO_M(uA) };
|
|
|
|
mt_dbg(ddata->dev, "ieoc=%d\n", uA);
|
|
return power_supply_set_property(ddata->psy,
|
|
POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, &val);
|
|
}
|
|
|
|
static int mt6375_reset_eoc_state(struct charger_device *chgdev)
|
|
{
|
|
struct mt6375_chg_data *ddata = charger_get_data(chgdev);
|
|
|
|
return mt6375_chg_field_set(ddata, F_EOC_RST, 1);
|
|
}
|
|
|
|
static int mt6375_sw_check_eoc(struct charger_device *chgdev, u32 uA)
|
|
{
|
|
int ret, ibat;
|
|
struct mt6375_chg_data *ddata = charger_get_data(chgdev);
|
|
|
|
ret = mt6375_get_ibat(chgdev, &ibat);
|
|
if (ret < 0) {
|
|
dev_err(ddata->dev, "failed to get ibat\n");
|
|
return ret;
|
|
}
|
|
|
|
if (ibat <= uA) {
|
|
/* if it happens 3 times, trigger EOC event */
|
|
if (atomic_read(&ddata->eoc_cnt) == 2) {
|
|
atomic_set(&ddata->eoc_cnt, 0);
|
|
mt_dbg(ddata->dev, "ieoc=%d, ibat=%d\n", uA, ibat);
|
|
charger_dev_notify(ddata->chgdev,
|
|
CHARGER_DEV_NOTIFY_EOC);
|
|
} else
|
|
atomic_inc(&ddata->eoc_cnt);
|
|
} else
|
|
atomic_set(&ddata->eoc_cnt, 0);
|
|
return 0;
|
|
}
|
|
|
|
static int mt6375_is_charge_done(struct charger_device *chgdev, bool *done)
|
|
{
|
|
struct mt6375_chg_data *ddata = charger_get_data(chgdev);
|
|
|
|
return mt6375_chg_is_charge_done(ddata, done);
|
|
}
|
|
|
|
static int mt6375_enable_te(struct charger_device *chgdev, bool en)
|
|
{
|
|
struct mt6375_chg_data *ddata = charger_get_data(chgdev);
|
|
|
|
mt_dbg(ddata->dev, "en=%d\n", en);
|
|
return mt6375_chg_field_set(ddata, F_TE, en);
|
|
}
|
|
|
|
static int mt6375_enable_buck(struct charger_device *chgdev, bool en)
|
|
{
|
|
struct mt6375_chg_data *ddata = charger_get_data(chgdev);
|
|
|
|
mt_dbg(ddata->dev, "en=%d\n", en);
|
|
return mt6375_chg_field_set(ddata, F_BUCK_EN, en);
|
|
}
|
|
|
|
static int mt6375_is_buck_enabled(struct charger_device *chgdev, bool *en)
|
|
{
|
|
int ret;
|
|
u32 val;
|
|
struct mt6375_chg_data *ddata = charger_get_data(chgdev);
|
|
|
|
ret = mt6375_chg_field_get(ddata, F_BUCK_EN, &val);
|
|
if (ret < 0)
|
|
return ret;
|
|
*en = val;
|
|
return 0;
|
|
}
|
|
|
|
static int mt6375_enable_chg_timer(struct charger_device *chgdev, bool en)
|
|
{
|
|
struct mt6375_chg_data *ddata = charger_get_data(chgdev);
|
|
|
|
mt_dbg(ddata->dev, "en=%d\n", en);
|
|
return mt6375_chg_field_set(ddata, F_CHG_TMR_EN, en);
|
|
}
|
|
|
|
static int mt6375_is_chg_timer_enabled(struct charger_device *chgdev, bool *en)
|
|
{
|
|
int ret;
|
|
u32 val;
|
|
struct mt6375_chg_data *ddata = charger_get_data(chgdev);
|
|
|
|
ret = mt6375_chg_field_get(ddata, F_CHG_TMR_EN, &val);
|
|
if (ret < 0)
|
|
return ret;
|
|
*en = val;
|
|
return 0;
|
|
}
|
|
|
|
static int mt6375_enable_hz(struct charger_device *chgdev, bool en)
|
|
{
|
|
struct mt6375_chg_data *ddata = charger_get_data(chgdev);
|
|
|
|
return mt6375_chg_field_set(ddata, F_HZ, en ? 1 : 0);
|
|
}
|
|
|
|
static int mt6375_kick_wdt(struct charger_device *chgdev)
|
|
{
|
|
struct mt6375_chg_data *ddata = charger_get_data(chgdev);
|
|
|
|
return mt6375_chg_field_set(ddata, F_WDT_RST, 1);
|
|
}
|
|
|
|
static int mt6375_run_aicc(struct charger_device *chgdev, u32 *uA)
|
|
{
|
|
int ret;
|
|
bool active;
|
|
struct mt6375_chg_data *ddata = charger_get_data(chgdev);
|
|
long ret_comp;
|
|
|
|
ret = mt6375_get_mivr_state(chgdev, &active);
|
|
if (ret < 0)
|
|
return ret;
|
|
if (!active) {
|
|
mt_dbg(ddata->dev, "mivr loop is not active\n");
|
|
return 0;
|
|
}
|
|
|
|
mutex_lock(&ddata->pe_lock);
|
|
disable_irq(ddata->detach_irq);
|
|
if (!ddata->pwr_rdy) {
|
|
enable_irq(ddata->detach_irq);
|
|
dev_info(ddata->dev, "detach\n");
|
|
ret = -EPERM;
|
|
goto out;
|
|
}
|
|
reinit_completion(&ddata->aicc_done);
|
|
ret = mt6375_chg_field_set(ddata, F_AICC_EN, 1);
|
|
if (ret < 0) {
|
|
enable_irq(ddata->detach_irq);
|
|
goto out;
|
|
}
|
|
enable_irq(ddata->detach_irq);
|
|
|
|
/* worst case = 128steps * 52msec = 6656ms */
|
|
ret_comp = wait_for_completion_interruptible_timeout(&ddata->aicc_done,
|
|
msecs_to_jiffies(7000));
|
|
if (ret_comp == 0)
|
|
ret = -ETIMEDOUT;
|
|
else if (ret_comp < 0)
|
|
ret = -EINTR;
|
|
else
|
|
ret = 0;
|
|
if (ret < 0) {
|
|
dev_err(ddata->dev, "failed to wait aicc (%d)\n", ret);
|
|
goto out;
|
|
}
|
|
if (!ddata->pwr_rdy) {
|
|
dev_info(ddata->dev, "detach\n");
|
|
ret = -EPERM;
|
|
goto out;
|
|
}
|
|
ret = mt6375_chg_field_get(ddata, F_AICC_RPT, uA);
|
|
if (ret < 0) {
|
|
dev_err(ddata->dev, "failed to get aicc report\n");
|
|
goto out;
|
|
}
|
|
*uA = M_TO_U(*uA);
|
|
out:
|
|
mt6375_chg_field_set(ddata, F_AICC_EN, 0);
|
|
mutex_unlock(&ddata->pe_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int mt6375_run_pe(struct mt6375_chg_data *ddata, bool pe20)
|
|
{
|
|
int ret;
|
|
unsigned long timeout = pe20 ? 1400 : 2800;
|
|
long ret_comp;
|
|
|
|
ret = mt6375_chg_field_set(ddata, F_IAICR, 800);
|
|
if (ret < 0)
|
|
return ret;
|
|
ret = mt6375_chg_field_set(ddata, F_CC, 2000);
|
|
if (ret < 0)
|
|
return ret;
|
|
ret = mt6375_chg_field_set(ddata, F_CHG_EN, 1);
|
|
if (ret < 0)
|
|
return ret;
|
|
ret = mt6375_chg_field_set(ddata, F_PE_SEL, pe20);
|
|
if (ret < 0)
|
|
return ret;
|
|
disable_irq(ddata->detach_irq);
|
|
if (!ddata->pwr_rdy) {
|
|
enable_irq(ddata->detach_irq);
|
|
dev_info(ddata->dev, "detach\n");
|
|
ret = -EPERM;
|
|
goto out;
|
|
}
|
|
reinit_completion(&ddata->pe_done);
|
|
ret = mt6375_chg_field_set(ddata, F_PE_EN, 1);
|
|
if (ret < 0) {
|
|
enable_irq(ddata->detach_irq);
|
|
goto out;
|
|
}
|
|
enable_irq(ddata->detach_irq);
|
|
|
|
ret_comp = wait_for_completion_interruptible_timeout(&ddata->pe_done,
|
|
msecs_to_jiffies(timeout));
|
|
if (ret_comp == 0)
|
|
ret = -ETIMEDOUT;
|
|
else if (ret_comp < 0)
|
|
ret = -EINTR;
|
|
else
|
|
ret = 0;
|
|
if (ret < 0) {
|
|
dev_err(ddata->dev, "failed to wait pe (%d)\n", ret);
|
|
goto out;
|
|
}
|
|
if (!ddata->pwr_rdy) {
|
|
dev_info(ddata->dev, "detach\n");
|
|
ret = -EPERM;
|
|
goto out;
|
|
}
|
|
out:
|
|
mt6375_chg_field_set(ddata, F_PE_EN, 0);
|
|
return ret;
|
|
}
|
|
|
|
static int mt6375_set_pe_current_pattern(struct charger_device *chgdev,
|
|
bool inc)
|
|
{
|
|
int ret;
|
|
struct mt6375_chg_data *ddata = charger_get_data(chgdev);
|
|
|
|
mutex_lock(&ddata->pe_lock);
|
|
mt_dbg(ddata->dev, "inc=%d\n", inc);
|
|
ret = mt6375_chg_field_set(ddata, F_PE10_INC, inc);
|
|
if (ret < 0) {
|
|
dev_err(ddata->dev, "failed to set pe10 up/down\n");
|
|
goto out;
|
|
}
|
|
ret = mt6375_run_pe(ddata, false);
|
|
out:
|
|
mutex_unlock(&ddata->pe_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int mt6375_set_pe20_efficiency_table(struct charger_device *chgdev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int mt6375_set_pe20_current_pattern(struct charger_device *chgdev,
|
|
u32 uV)
|
|
{
|
|
int ret;
|
|
struct mt6375_chg_data *ddata = charger_get_data(chgdev);
|
|
|
|
mutex_lock(&ddata->pe_lock);
|
|
mt_dbg(ddata->dev, "pe20=%d\n", uV);
|
|
ret = mt6375_chg_field_set(ddata, F_PE20_CODE, U_TO_M(uV));
|
|
if (ret < 0) {
|
|
dev_err(ddata->dev, "failed to set pe20 code\n", __func__);
|
|
goto out;
|
|
}
|
|
ret = mt6375_run_pe(ddata, true);
|
|
out:
|
|
mutex_unlock(&ddata->pe_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int mt6375_enable_pe_cable_drop_comp(struct charger_device *chgdev,
|
|
bool en)
|
|
{
|
|
int ret;
|
|
struct mt6375_chg_data *ddata = charger_get_data(chgdev);
|
|
|
|
if (en)
|
|
return 0;
|
|
mutex_lock(&ddata->pe_lock);
|
|
mt_dbg(ddata->dev, "en=%d\n", en);
|
|
ret = mt6375_chg_field_set(ddata, F_PE20_CODE, 0x1F);
|
|
if (ret < 0) {
|
|
dev_err(ddata->dev, "failed to set cable drop comp code\n");
|
|
goto out;
|
|
}
|
|
ret = mt6375_run_pe(ddata, true);
|
|
out:
|
|
mutex_unlock(&ddata->pe_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int mt6375_reset_pe_ta(struct charger_device *chgdev)
|
|
{
|
|
int ret;
|
|
struct mt6375_chg_data *ddata = charger_get_data(chgdev);
|
|
|
|
mutex_lock(&ddata->pe_lock);
|
|
mt_dbg(ddata->dev, "++\n");
|
|
ret = mt6375_chg_field_set(ddata, F_VMIVR, 4600);
|
|
if (ret < 0)
|
|
goto out;
|
|
ret = mt6375_chg_field_set(ddata, F_IAICR, 100);
|
|
if (ret < 0)
|
|
goto out;
|
|
msleep(250);
|
|
ret = mt6375_chg_field_set(ddata, F_IAICR, 500);
|
|
out:
|
|
mutex_unlock(&ddata->pe_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int mt6375_set_otg_cc(struct charger_device *chgdev, u32 uA)
|
|
{
|
|
struct mt6375_chg_data *ddata = charger_get_data(chgdev);
|
|
|
|
mt_dbg(ddata->dev, "otg_cc=%d\n", uA);
|
|
return mt6375_chg_field_set(ddata, F_OTG_CC, U_TO_M(uA));
|
|
}
|
|
|
|
static int mt6375_enable_otg(struct charger_device *chgdev, bool en)
|
|
{
|
|
int ret;
|
|
struct regulator *regulator;
|
|
struct mt6375_chg_data *ddata = charger_get_data(chgdev);
|
|
|
|
mt_dbg(ddata->dev, "en=%d\n", en);
|
|
regulator = devm_regulator_get(ddata->dev, "usb-otg-vbus");
|
|
if (IS_ERR(regulator)) {
|
|
dev_err(ddata->dev, "failed to get otg regulator\n");
|
|
return PTR_ERR(regulator);
|
|
}
|
|
ret = en ? regulator_enable(regulator) : regulator_disable(regulator);
|
|
devm_regulator_put(regulator);
|
|
return ret;
|
|
}
|
|
|
|
static int mt6375_enable_discharge(struct charger_device *chgdev, bool en)
|
|
{
|
|
int i, ret;
|
|
u32 val;
|
|
const int dischg_retry_cnt = 3;
|
|
struct mt6375_chg_data *ddata = charger_get_data(chgdev);
|
|
|
|
mt_dbg(ddata->dev, "en=%d\n", en);
|
|
ret = mt6375_enable_hm(ddata, true);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = mt6375_chg_field_set(ddata, F_FORCE_VBUS_SINK, en);
|
|
if (ret < 0) {
|
|
dev_err(ddata->dev, "failed to set dischg %d\n", __func__, en);
|
|
goto out;
|
|
}
|
|
|
|
/* for disable, make sure it is disabled */
|
|
if (!en) {
|
|
for (i = 0; i < dischg_retry_cnt; i++) {
|
|
ret = mt6375_chg_field_get(ddata, F_FORCE_VBUS_SINK,
|
|
&val);
|
|
if (ret < 0)
|
|
continue;
|
|
if (!val)
|
|
break;
|
|
mt6375_chg_field_set(ddata, F_FORCE_VBUS_SINK, 0);
|
|
}
|
|
if (i == dischg_retry_cnt) {
|
|
dev_err(ddata->dev, "failed to disable dischg\n");
|
|
ret = -EINVAL;
|
|
}
|
|
}
|
|
out:
|
|
mt6375_enable_hm(ddata, false);
|
|
return ret;
|
|
}
|
|
|
|
static int mt6375_enable_chg_type_det(struct charger_device *chgdev, bool en)
|
|
{
|
|
struct mt6375_chg_data *ddata = charger_get_data(chgdev);
|
|
int attach = en ? ATTACH_TYPE_TYPEC : ATTACH_TYPE_NONE;
|
|
|
|
mt_dbg(ddata->dev, "en=%d\n", en);
|
|
mt6375_chg_attach_pre_process(ddata, ATTACH_TRIG_TYPEC, attach);
|
|
return 0;
|
|
}
|
|
|
|
#define DUMP_REG_BUF_SIZE 1024
|
|
static int mt6375_dump_registers(struct charger_device *chgdev)
|
|
{
|
|
int i, ret;
|
|
u32 val;
|
|
u16 data;
|
|
char buf[DUMP_REG_BUF_SIZE] = "\0";
|
|
static struct {
|
|
const char *name;
|
|
const char *unit;
|
|
enum mt6375_chg_reg_field fd;
|
|
} settings[] = {
|
|
{ .fd = F_CC, .name = "CC", .unit = "mA" },
|
|
{ .fd = F_IAICR, .name = "AICR", .unit = "mA" },
|
|
{ .fd = F_VMIVR, .name = "MIVR", .unit = "mV" },
|
|
{ .fd = F_IEOC, .name = "IEOC", .unit = "mA" },
|
|
{ .fd = F_CV, .name = "CV", .unit = "mV" },
|
|
};
|
|
static struct {
|
|
const char *name;
|
|
const char *unit;
|
|
enum mt6375_adc_chan chan;
|
|
} adcs[] = {
|
|
{ .chan = ADC_CHAN_CHGVINDIV5, .name = "VBUS", .unit = "mV" },
|
|
{ .chan = ADC_CHAN_IBUS, .name = "IBUS", .unit = "mA" },
|
|
{ .chan = ADC_CHAN_VBAT, .name = "VBAT", .unit = "mV" },
|
|
{ .chan = ADC_CHAN_IBAT, .name = "IBAT", .unit = "mA" },
|
|
{ .chan = ADC_CHAN_VSYS, .name = "VSYS", .unit = "mV" },
|
|
};
|
|
static struct {
|
|
const u16 reg;
|
|
const char *name;
|
|
} regs[] = {
|
|
{ .reg = MT6375_REG_CHG_STAT, .name = "CHG_STAT" },
|
|
{ .reg = MT6375_REG_CHG_STAT0, .name = "CHG_STAT0" },
|
|
{ .reg = MT6375_REG_CHG_STAT1, .name = "CHG_STAT1" },
|
|
{ .reg = MT6375_REG_CHG_TOP1, .name = "CHG_TOP1" },
|
|
{ .reg = MT6375_REG_CHG_TOP2, .name = "CHG_TOP2" },
|
|
{ .reg = MT6375_REG_CHG_EOC, .name = "CHG_EOC" },
|
|
};
|
|
struct mt6375_chg_data *ddata = charger_get_data(chgdev);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(settings); i++) {
|
|
ret = mt6375_chg_field_get(ddata, settings[i].fd, &val);
|
|
if (ret < 0) {
|
|
dev_err(ddata->dev, "failed to get %s\n",
|
|
settings[i].name);
|
|
return ret;
|
|
}
|
|
if (i == ARRAY_SIZE(settings) - 1)
|
|
scnprintf(buf + strlen(buf), DUMP_REG_BUF_SIZE,
|
|
"%s = %d%s\n", settings[i].name, val,
|
|
settings[i].unit);
|
|
else
|
|
scnprintf(buf + strlen(buf), DUMP_REG_BUF_SIZE,
|
|
"%s = %d%s, ", settings[i].name, val,
|
|
settings[i].unit);
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(adcs); i++) {
|
|
ret = iio_read_channel_processed(&ddata->iio_adcs[adcs[i].chan],
|
|
&val);
|
|
if (ret < 0) {
|
|
dev_err(ddata->dev, "failed to read adc %s\n",
|
|
adcs[i].name);
|
|
return ret;
|
|
}
|
|
val = U_TO_M(val);
|
|
if (i == ARRAY_SIZE(adcs) - 1)
|
|
scnprintf(buf + strlen(buf), DUMP_REG_BUF_SIZE,
|
|
"%s = %d%s\n", adcs[i].name, val,
|
|
adcs[i].unit);
|
|
else
|
|
scnprintf(buf + strlen(buf), DUMP_REG_BUF_SIZE,
|
|
"%s = %d%s, ", adcs[i].name, val,
|
|
adcs[i].unit);
|
|
}
|
|
|
|
/* No need to lock, it is only for information dump */
|
|
if (ddata->batprotect_en) {
|
|
ret = regmap_bulk_read(ddata->rmap, MT6375_REG_VBAT_MON_RPT,
|
|
&data, 2);
|
|
if (ret < 0)
|
|
dev_err(ddata->dev,
|
|
"failed to get vbat monitor report\n");
|
|
else {
|
|
val = ADC_FROM_VBAT_RAW(be16_to_cpu(data));
|
|
scnprintf(buf + strlen(buf), DUMP_REG_BUF_SIZE,
|
|
"VBATCELL = %dmV\n", val);
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(regs); i++) {
|
|
ret = regmap_read(ddata->rmap, regs[i].reg, &val);
|
|
if (ret < 0) {
|
|
dev_err(ddata->dev, "failed to read %s\n",
|
|
regs[i].name);
|
|
return ret;
|
|
}
|
|
if (i == ARRAY_SIZE(regs) - 1)
|
|
scnprintf(buf + strlen(buf), DUMP_REG_BUF_SIZE,
|
|
"%s = 0x%02X\n", regs[i].name, val);
|
|
else
|
|
scnprintf(buf + strlen(buf), DUMP_REG_BUF_SIZE,
|
|
"%s = 0x%02X, ", regs[i].name, val);
|
|
}
|
|
dev_info(ddata->dev, "%s %s", __func__, buf);
|
|
return 0;
|
|
}
|
|
|
|
static int mt6375_do_event(struct charger_device *chgdev, u32 event, u32 args)
|
|
{
|
|
struct mt6375_chg_data *ddata = charger_get_data(chgdev);
|
|
|
|
switch (event) {
|
|
case EVENT_FULL:
|
|
case EVENT_RECHARGE:
|
|
case EVENT_DISCHARGE:
|
|
power_supply_changed(ddata->psy);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int mt6375_plug_in(struct charger_device *chgdev)
|
|
{
|
|
int ret = 0;
|
|
struct mt6375_chg_data *ddata = charger_get_data(chgdev);
|
|
struct mt6375_chg_platform_data *pdata = dev_get_platdata(ddata->dev);
|
|
|
|
mt_dbg(ddata->dev, "++\n");
|
|
if (pdata->wdt_en) {
|
|
ret = mt6375_chg_field_set(ddata, F_WDT_EN, 1);
|
|
if (ret < 0) {
|
|
dev_err(ddata->dev, "failed to enable WDT\n");
|
|
return ret;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int mt6375_plug_out(struct charger_device *chgdev)
|
|
{
|
|
int ret = 0;
|
|
struct mt6375_chg_data *ddata = charger_get_data(chgdev);
|
|
struct mt6375_chg_platform_data *pdata = dev_get_platdata(ddata->dev);
|
|
|
|
mt_dbg(ddata->dev, "++\n");
|
|
if (pdata->wdt_en) {
|
|
ret = mt6375_chg_field_set(ddata, F_WDT_EN, 0);
|
|
if (ret < 0) {
|
|
dev_err(ddata->dev, "failed to disable WDT\n");
|
|
return ret;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int mt6375_enable_6pin_battery_charging(struct charger_device *chgdev,
|
|
bool en)
|
|
{
|
|
int ret = 0;
|
|
u16 data, batend_code;
|
|
u32 vbat, cv;
|
|
struct mt6375_chg_data *ddata = charger_get_data(chgdev);
|
|
struct mt6375_chg_platform_data *pdata = dev_get_platdata(ddata->dev);
|
|
|
|
mutex_lock(&ddata->cv_lock);
|
|
if (ddata->batprotect_en == en)
|
|
goto out;
|
|
|
|
mt_dbg(ddata->dev, "en=%d\n", en);
|
|
if (!en)
|
|
goto dis_pro;
|
|
|
|
/* If no 6pin used, always bypass this function */
|
|
if (atomic_read(&ddata->no_6pin_used) == 1)
|
|
goto dis_pro;
|
|
|
|
ret = mt6375_chg_field_set(ddata, F_VBAT_MON_EN, 1);
|
|
if (ret < 0) {
|
|
dev_err(ddata->dev, "failed to enable vbat monitor\n");
|
|
goto out;
|
|
}
|
|
usleep_range(ADC_CONV_TIME_US * 2, ADC_CONV_TIME_US * 3);
|
|
ret = regmap_bulk_read(ddata->rmap, MT6375_REG_VBAT_MON_RPT, &data, 2);
|
|
if (ret < 0) {
|
|
dev_err(ddata->dev, "failed to get vbat monitor report\n");
|
|
goto dis_mon;
|
|
}
|
|
vbat = ADC_FROM_VBAT_RAW(be16_to_cpu(data));
|
|
ret = mt6375_chg_field_get(ddata, F_CV, &cv);
|
|
if (ret < 0) {
|
|
dev_err(ddata->dev, "failed to get cv\n");
|
|
goto dis_mon;
|
|
}
|
|
mt_dbg(ddata->dev, "vbat = %dmV, cv = %dmV\n", vbat, cv);
|
|
if (vbat >= cv) {
|
|
dev_warn(ddata->dev, "vbat(%d) >= cv(%d), should not start\n",
|
|
vbat, cv);
|
|
goto dis_mon;
|
|
} else if (vbat <= pdata->pmic_uvlo) {
|
|
/*
|
|
* If no 6pin used, vbat detected by vbat mon will be much
|
|
* lower than the PMIC UVLO.
|
|
*/
|
|
atomic_set(&ddata->no_6pin_used, 1);
|
|
dev_notice(ddata->dev, "vbat <= PMIC UVLO(%d mV), should not start\n",
|
|
pdata->pmic_uvlo);
|
|
goto dis_mon;
|
|
}
|
|
|
|
ret = mt6375_chg_field_set(ddata, F_BATINT, cv);
|
|
if (ret < 0) {
|
|
dev_err(ddata->dev, "failed to set batint\n");
|
|
goto dis_mon;
|
|
}
|
|
batend_code = ADC_TO_VBAT_RAW(cv);
|
|
batend_code = cpu_to_be16(batend_code);
|
|
mt_dbg(ddata->dev, "batend code = 0x%04X\n", batend_code);
|
|
ret = regmap_bulk_write(ddata->rmap, MT6375_REG_BATEND_CODE,
|
|
&batend_code, 2);
|
|
if (ret < 0) {
|
|
dev_err(ddata->dev, "failed to set batend code\n");
|
|
goto dis_mon;
|
|
}
|
|
ret = mt6375_chg_field_set(ddata, F_BATPROTECT_EN, 1);
|
|
if (ret < 0) {
|
|
dev_err(ddata->dev, "failed to enable bat protect\n");
|
|
goto dis_mon;
|
|
}
|
|
ret = mt6375_chg_field_set(ddata, F_CV,
|
|
mt6375_chg_fields[F_CV].range->max);
|
|
if (ret < 0) {
|
|
dev_err(ddata->dev, "failed to set maximum cv\n");
|
|
goto dis_pro;
|
|
}
|
|
ddata->batprotect_en = true;
|
|
mt_dbg(ddata->dev, "successfully\n");
|
|
goto out;
|
|
dis_pro:
|
|
if (mt6375_chg_field_set(ddata, F_BATPROTECT_EN, 0) < 0)
|
|
dev_notice(ddata->dev, "failed to disable bat protect\n");
|
|
if (mt6375_chg_field_set(ddata, F_CV, ddata->cv) < 0)
|
|
dev_notice(ddata->dev, "failed to set cv\n");
|
|
dis_mon:
|
|
if (mt6375_chg_field_set(ddata, F_VBAT_MON_EN, 0) < 0)
|
|
dev_notice(ddata->dev, "failed to disable vbat monitor\n");
|
|
ddata->batprotect_en = false;
|
|
out:
|
|
mutex_unlock(&ddata->cv_lock);
|
|
return ret;
|
|
}
|
|
|
|
static const struct charger_properties mt6375_chg_props = {
|
|
.alias_name = "mt6375_chg",
|
|
};
|
|
|
|
static const struct charger_ops mt6375_chg_ops = {
|
|
/* cable plug in/out */
|
|
.plug_in = mt6375_plug_in,
|
|
.plug_out = mt6375_plug_out,
|
|
/* enable */
|
|
.enable = mt6375_enable_charging,
|
|
.is_enabled = mt6375_is_enabled,
|
|
/* charging current */
|
|
.set_charging_current = mt6375_set_ichg,
|
|
.get_charging_current = mt6375_get_ichg,
|
|
.get_min_charging_current = mt6375_get_min_ichg,
|
|
/* charging voltage */
|
|
.set_constant_voltage = mt6375_set_cv,
|
|
.get_constant_voltage = mt6375_get_cv,
|
|
/* input current limit */
|
|
.set_input_current = mt6375_set_aicr,
|
|
.get_input_current = mt6375_get_aicr,
|
|
.get_min_input_current = mt6375_get_min_aicr,
|
|
/* MIVR */
|
|
.set_mivr = mt6375_set_mivr,
|
|
.get_mivr = mt6375_get_mivr,
|
|
.get_mivr_state = mt6375_get_mivr_state,
|
|
/* ADC */
|
|
.get_adc = mt6375_get_adc,
|
|
.get_vbus_adc = mt6375_get_vbus,
|
|
.get_ibus_adc = mt6375_get_ibus,
|
|
.get_ibat_adc = mt6375_get_ibat,
|
|
.get_tchg_adc = mt6375_get_tchg,
|
|
.get_zcv = mt6375_get_zcv,
|
|
/* charing termination */
|
|
.set_eoc_current = mt6375_set_ieoc,
|
|
.enable_termination = mt6375_enable_te,
|
|
.reset_eoc_state = mt6375_reset_eoc_state,
|
|
.safety_check = mt6375_sw_check_eoc,
|
|
.is_charging_done = mt6375_is_charge_done,
|
|
/* power path */
|
|
.enable_powerpath = mt6375_enable_buck,
|
|
.is_powerpath_enabled = mt6375_is_buck_enabled,
|
|
/* timer */
|
|
.enable_safety_timer = mt6375_enable_chg_timer,
|
|
.is_safety_timer_enabled = mt6375_is_chg_timer_enabled,
|
|
.kick_wdt = mt6375_kick_wdt,
|
|
/* AICL */
|
|
.run_aicl = mt6375_run_aicc,
|
|
/* PE+/PE+20 */
|
|
.send_ta_current_pattern = mt6375_set_pe_current_pattern,
|
|
.set_pe20_efficiency_table = mt6375_set_pe20_efficiency_table,
|
|
.send_ta20_current_pattern = mt6375_set_pe20_current_pattern,
|
|
.reset_ta = mt6375_reset_pe_ta,
|
|
.enable_cable_drop_comp = mt6375_enable_pe_cable_drop_comp,
|
|
/* OTG */
|
|
.set_boost_current_limit = mt6375_set_otg_cc,
|
|
.enable_otg = mt6375_enable_otg,
|
|
.enable_discharge = mt6375_enable_discharge,
|
|
/* charger type detection */
|
|
.enable_chg_type_det = mt6375_enable_chg_type_det,
|
|
/* misc */
|
|
.dump_registers = mt6375_dump_registers,
|
|
.enable_hz = mt6375_enable_hz,
|
|
/* event */
|
|
.event = mt6375_do_event,
|
|
/* 6pin battery */
|
|
.enable_6pin_battery_charging = mt6375_enable_6pin_battery_charging,
|
|
};
|
|
|
|
static irqreturn_t mt6375_fl_wdt_handler(int irq, void *data)
|
|
{
|
|
int ret;
|
|
struct mt6375_chg_data *ddata = data;
|
|
|
|
ret = mt6375_chg_field_set(ddata, F_WDT_RST, 1);
|
|
if (ret < 0)
|
|
dev_notice(ddata->dev, "failed to kick wdt\n");
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t mt6375_fl_pwr_rdy_handler(int irq, void *data)
|
|
{
|
|
struct mt6375_chg_data *ddata = data;
|
|
|
|
mt_dbg(ddata->dev, "++\n");
|
|
mt6375_chg_pwr_rdy_process(ddata);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t mt6375_fl_detach_handler(int irq, void *data)
|
|
{
|
|
struct mt6375_chg_data *ddata = data;
|
|
|
|
mt_dbg(ddata->dev, "++\n");
|
|
mt6375_chg_pwr_rdy_process(ddata);
|
|
complete(&ddata->aicc_done);
|
|
complete(&ddata->pe_done);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t mt6375_fl_vbus_ov_handler(int irq, void *data)
|
|
{
|
|
struct mt6375_chg_data *ddata = data;
|
|
|
|
mt_dbg(ddata->dev, "++\n");
|
|
charger_dev_notify(ddata->chgdev, CHARGER_DEV_NOTIFY_VBUS_OVP);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t mt6375_fl_chg_tout_handler(int irq, void *data)
|
|
{
|
|
struct mt6375_chg_data *ddata = data;
|
|
|
|
mt_dbg(ddata->dev, "++\n");
|
|
charger_dev_notify(ddata->chgdev, CHARGER_DEV_NOTIFY_SAFETY_TIMEOUT);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t mt6375_fl_bc12_dn_handler(int irq, void *data)
|
|
{
|
|
struct mt6375_chg_data *ddata = data;
|
|
int attach;
|
|
|
|
mt_dbg(ddata->dev, "++\n");
|
|
mutex_lock(&ddata->attach_lock);
|
|
attach = atomic_read(&ddata->attach);
|
|
ddata->bc12_dn = (attach == ATTACH_TYPE_NONE) ? false : true;
|
|
mutex_unlock(&ddata->attach_lock);
|
|
|
|
if (!ddata->bc12_dn) {
|
|
dev_notice(ddata->dev, "%s attach=%d, bc12_dn=%d",
|
|
__func__, attach, ddata->bc12_dn);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
if (attach < ATTACH_TYPE_PD && !queue_work(ddata->wq, &ddata->bc12_work))
|
|
dev_notice(ddata->dev, "%s bc12 work already queued\n", __func__);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t mt6375_fl_pe_done_handler(int irq, void *data)
|
|
{
|
|
struct mt6375_chg_data *ddata = data;
|
|
|
|
mt_dbg(ddata->dev, "++\n");
|
|
complete(&ddata->pe_done);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t mt6375_fl_aicc_done_handler(int irq, void *data)
|
|
{
|
|
struct mt6375_chg_data *ddata = data;
|
|
|
|
mt_dbg(ddata->dev, "++\n");
|
|
complete(&ddata->aicc_done);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t mt6375_fl_batpro_done_handler(int irq, void *data)
|
|
{
|
|
struct mt6375_chg_data *ddata = data;
|
|
int ret;
|
|
|
|
mt_dbg(ddata->dev, "++\n");
|
|
ret = mt6375_enable_6pin_battery_charging(ddata->chgdev, false);
|
|
charger_dev_notify(ddata->chgdev, CHARGER_DEV_NOTIFY_BATPRO_DONE);
|
|
return ret < 0 ? ret : IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t mt6375_adc_vbat_mon_ov_handler(int irq, void *data)
|
|
{
|
|
struct mt6375_chg_data *ddata = data;
|
|
u32 cv;
|
|
|
|
mt6375_get_cv(ddata->chgdev, &cv);
|
|
mt_dbg(ddata->dev, "cv = %dmV\n", U_TO_M(cv));
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
struct mt6375_chg_dtprop {
|
|
const char *name;
|
|
size_t offset;
|
|
enum mt6375_chg_reg_field field;
|
|
enum mt6375_chg_dtprop_type type;
|
|
bool optional;
|
|
};
|
|
|
|
#define MT6375_CHG_DTPROP(_name, _field, _type, _opt) \
|
|
{ \
|
|
.name = #_name, \
|
|
.field = _field, \
|
|
.type = _type, \
|
|
.offset = offsetof(struct mt6375_chg_platform_data, _name), \
|
|
.optional = _opt, \
|
|
}
|
|
|
|
static const struct mt6375_chg_dtprop mt6375_chg_dtprops[] = {
|
|
MT6375_CHG_DTPROP(vbus_ov, F_VBUS_OV, DTPROP_U32, false),
|
|
MT6375_CHG_DTPROP(chg_tmr, F_CHG_TMR, DTPROP_U32, false),
|
|
MT6375_CHG_DTPROP(chg_tmr_en, F_CHG_TMR_EN, DTPROP_BOOL, false),
|
|
MT6375_CHG_DTPROP(otg_lbp, F_OTG_LBP, DTPROP_U32, false),
|
|
MT6375_CHG_DTPROP(ircmp_v, F_IRCMP_V, DTPROP_U32, false),
|
|
MT6375_CHG_DTPROP(ircmp_r, F_IRCMP_R, DTPROP_U32, false),
|
|
MT6375_CHG_DTPROP(wdt, F_WDT, DTPROP_U32, false),
|
|
MT6375_CHG_DTPROP(wdt_en, F_WDT_EN, DTPROP_BOOL, false),
|
|
MT6375_CHG_DTPROP(te_en, F_TE, DTPROP_BOOL, false),
|
|
MT6375_CHG_DTPROP(mivr, F_VMIVR, DTPROP_U32, true),
|
|
MT6375_CHG_DTPROP(aicr, F_IAICR, DTPROP_U32, true),
|
|
MT6375_CHG_DTPROP(ichg, F_CC, DTPROP_U32, true),
|
|
MT6375_CHG_DTPROP(ieoc, F_IEOC, DTPROP_U32, true),
|
|
MT6375_CHG_DTPROP(cv, F_CV, DTPROP_U32, true),
|
|
MT6375_CHG_DTPROP(vrec, F_VREC, DTPROP_U32, true),
|
|
MT6375_CHG_DTPROP(dcdt_sel, F_DCDT_SEL, DTPROP_U32, true),
|
|
};
|
|
|
|
static inline u32 pdata_get_val(void *pdata, const struct mt6375_chg_dtprop *dp)
|
|
{
|
|
if (dp->type == DTPROP_BOOL)
|
|
return *((bool *)(pdata + dp->offset));
|
|
return *((u32 *)(pdata + dp->offset));
|
|
}
|
|
|
|
static int mt6375_chg_apply_dt(struct mt6375_chg_data *ddata)
|
|
{
|
|
int i, ret;
|
|
u32 val;
|
|
const struct mt6375_chg_dtprop *dp;
|
|
|
|
mt_dbg(ddata->dev, "++\n");
|
|
for (i = 0; i < ARRAY_SIZE(mt6375_chg_dtprops); i++) {
|
|
dp = &mt6375_chg_dtprops[i];
|
|
val = pdata_get_val(dev_get_platdata(ddata->dev), dp);
|
|
ret = mt6375_chg_field_set(ddata, dp->field, val);
|
|
if (ret < 0) {
|
|
dev_err(ddata->dev, "failed to write dtprop %s\n",
|
|
dp->name);
|
|
if (!dp->optional)
|
|
return ret;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void mt6375_chg_parse_dt_helper(struct device *dev, void *pdata,
|
|
const struct mt6375_chg_dtprop *dp)
|
|
{
|
|
int ret;
|
|
void *val = pdata + dp->offset;
|
|
|
|
if (dp->type == DTPROP_BOOL)
|
|
*((bool *)val) = device_property_read_bool(dev, dp->name);
|
|
else {
|
|
ret = device_property_read_u32(dev, dp->name, val);
|
|
if (ret < 0)
|
|
dev_info(dev, "property %s not found\n", dp->name);
|
|
}
|
|
}
|
|
|
|
static int mt6375_chg_get_pdata(struct device *dev)
|
|
{
|
|
int i;
|
|
u32 val;
|
|
const struct {
|
|
u32 size;
|
|
u32 tag;
|
|
u32 boot_mode;
|
|
u32 boot_type;
|
|
} *tag;
|
|
struct device_node *bc12_np, *boot_np, *pmic_uvlo_np, *np = dev->of_node;
|
|
struct mt6375_chg_platform_data *pdata = dev_get_platdata(dev);
|
|
|
|
mt_dbg(dev, "%s: entry. Get pdata now.\n", __func__);
|
|
if (np) {
|
|
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
|
|
if (!pdata)
|
|
return -ENOMEM;
|
|
memcpy(pdata, &mt6375_chg_pdata_def, sizeof(*pdata));
|
|
for (i = 0; i < ARRAY_SIZE(mt6375_chg_dtprops); i++)
|
|
mt6375_chg_parse_dt_helper(dev, pdata,
|
|
&mt6375_chg_dtprops[i]);
|
|
pdata->usb_killer_detect =
|
|
device_property_read_bool(dev, "usb_killer_detect");
|
|
|
|
/* mediatek chgdev name */
|
|
if (of_property_read_string(np, "chg_name", &pdata->chg_name))
|
|
dev_notice(dev, "failed to get chg_name\n");
|
|
|
|
/* mediatek boot mode */
|
|
boot_np = of_parse_phandle(np, "boot_mode", 0);
|
|
if (!boot_np) {
|
|
dev_err(dev, "failed to get bootmode phandle\n");
|
|
return -ENODEV;
|
|
}
|
|
tag = of_get_property(boot_np, "atag,boot", NULL);
|
|
if (!tag) {
|
|
dev_err(dev, "failed to get atag,boot\n");
|
|
return -EINVAL;
|
|
}
|
|
dev_info(dev, "sz:0x%x tag:0x%x mode:0x%x type:0x%x\n",
|
|
tag->size, tag->tag, tag->boot_mode, tag->boot_type);
|
|
pdata->boot_mode = tag->boot_mode;
|
|
pdata->boot_type = tag->boot_type;
|
|
|
|
/*
|
|
* mediatek bc12_sel
|
|
* 0 means bc12 owner is THIS_MODULE,
|
|
* if it is not 0, always ignore
|
|
*/
|
|
bc12_np = of_parse_phandle(np, "bc12_sel", 0);
|
|
if (!bc12_np) {
|
|
dev_err(dev, "failed to get bc12_sel phandle\n");
|
|
return -ENODEV;
|
|
}
|
|
if (of_property_read_u32(bc12_np, "bc12_sel", &val) < 0) {
|
|
dev_err(dev, "property bc12_sel not found\n");
|
|
return -EINVAL;
|
|
}
|
|
if (val != 0)
|
|
pdata->attach_trig = ATTACH_TRIG_IGNORE;
|
|
else if (IS_ENABLED(CONFIG_TCPC_CLASS))
|
|
pdata->attach_trig = ATTACH_TRIG_TYPEC;
|
|
else
|
|
pdata->attach_trig = ATTACH_TRIG_PWR_RDY;
|
|
|
|
pmic_uvlo_np = of_parse_phandle(np, "pmic-uvlo", 0);
|
|
if (!pmic_uvlo_np)
|
|
dev_notice(dev, "Failed to get pmic-uvlo phandle\n");
|
|
|
|
if (of_property_read_u32(pmic_uvlo_np, "uvlo-level", &val) < 0)
|
|
dev_notice(dev, "property uvlo-level not found, use default: %d mv\n",
|
|
DEFAULT_PMIC_UVLO_mV);
|
|
|
|
if (val != 0)
|
|
pdata->pmic_uvlo = val;
|
|
else
|
|
pdata->pmic_uvlo = DEFAULT_PMIC_UVLO_mV;
|
|
|
|
dev->platform_data = pdata;
|
|
}
|
|
return pdata ? 0 : -ENODEV;
|
|
}
|
|
|
|
static int mt6375_chg_init_setting(struct mt6375_chg_data *ddata)
|
|
{
|
|
int ret;
|
|
u32 val;
|
|
struct mt6375_chg_platform_data *pdata = dev_get_platdata(ddata->dev);
|
|
|
|
mt_dbg(ddata->dev, "%s: entry. Init setting now.\n", __func__);
|
|
ret = mt6375_chg_field_set(ddata, F_AICC_ONESHOT, 1);
|
|
if (ret < 0) {
|
|
dev_err(ddata->dev, "failed to set aicc oneshot\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = mt6375_chg_field_set(ddata, F_BC12_EN, 0);
|
|
if (ret < 0) {
|
|
dev_err(ddata->dev, "failed to disable bc12\n");
|
|
return ret;
|
|
}
|
|
|
|
/* set aicr = 200mA in 1:META_BOOT 5:ADVMETA_BOOT */
|
|
if (pdata->boot_mode == 1 || pdata->boot_mode == 5) {
|
|
ret = mt6375_chg_field_set(ddata, F_IAICR, 200);
|
|
if (ret < 0) {
|
|
dev_err(ddata->dev, "failed to set aicr 200mA\n");
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
ret = mt6375_chg_apply_dt(ddata);
|
|
if (ret < 0) {
|
|
dev_err(ddata->dev, "failed to apply dt property\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = mt6375_chg_field_set(ddata, F_ILIM_EN, 0);
|
|
if (ret < 0) {
|
|
dev_err(ddata->dev, "failed to disable ilim\n");
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* disable wdt to save 1mA power consumption
|
|
* it will be turned back on later
|
|
* if it is enabled in dt property and TA attached
|
|
*/
|
|
ret = mt6375_chg_field_set(ddata, F_WDT_EN, 0);
|
|
if (ret < 0) {
|
|
dev_err(ddata->dev, "failed to disable WDT\n");
|
|
return ret;
|
|
}
|
|
|
|
/* if get failed, just ignore it */
|
|
ret = mt6375_chg_field_get(ddata, F_PP_PG_FLAG, &val);
|
|
if (ret >= 0 && val)
|
|
dev_warn(ddata->dev, "BATSYSUV occurred\n");
|
|
return mt6375_chg_field_set(ddata, F_PP_PG_FLAG, 1);
|
|
}
|
|
|
|
static int mt6375_chg_get_iio_adc(struct mt6375_chg_data *ddata)
|
|
{
|
|
int ret;
|
|
u16 zcv;
|
|
|
|
mt_dbg(ddata->dev, "%s: entry. Get 6375 ADC data.\n", __func__);
|
|
ddata->iio_adcs = devm_iio_channel_get_all(ddata->dev);
|
|
if (IS_ERR(ddata->iio_adcs))
|
|
return PTR_ERR(ddata->iio_adcs);
|
|
ret = regmap_bulk_read(ddata->rmap, MT6375_REG_ADC_ZCV_RPT, &zcv, 2);
|
|
if (ret < 0) {
|
|
dev_err(ddata->dev, "failed to get zcv\n");
|
|
return ret;
|
|
}
|
|
ddata->zcv = ADC_FROM_VBAT_RAW(be16_to_cpu(zcv));
|
|
return 0;
|
|
}
|
|
|
|
static int mt6375_chg_init_psy(struct mt6375_chg_data *ddata)
|
|
{
|
|
struct mt6375_chg_platform_data *pdata = dev_get_platdata(ddata->dev);
|
|
struct power_supply_config cfg = {
|
|
.drv_data = ddata,
|
|
.of_node = ddata->dev->of_node,
|
|
.supplied_to = mt6375_psy_supplied_to,
|
|
.num_supplicants = ARRAY_SIZE(mt6375_psy_supplied_to),
|
|
};
|
|
|
|
mt_dbg(ddata->dev, "%s: entry. Init power supply now.\n", __func__);
|
|
memcpy(&ddata->psy_desc, &mt6375_psy_desc, sizeof(ddata->psy_desc));
|
|
ddata->psy_desc.name = pdata->chg_name;
|
|
ddata->psy = devm_power_supply_register(ddata->dev, &ddata->psy_desc,
|
|
&cfg);
|
|
return IS_ERR(ddata->psy) ? PTR_ERR(ddata->psy) : 0;
|
|
}
|
|
|
|
static int mt6375_chg_init_regulator(struct mt6375_chg_data *ddata)
|
|
{
|
|
struct regulator_config cfg = {
|
|
.dev = ddata->dev,
|
|
.driver_data = ddata,
|
|
.regmap = ddata->rmap,
|
|
};
|
|
|
|
mt_dbg(ddata->dev, "%s: entry. Init regulator now.\n", __func__);
|
|
memcpy(&ddata->rdesc, &mt6375_chg_otg_rdesc, sizeof(ddata->rdesc));
|
|
ddata->rdesc.name = dev_name(ddata->dev);
|
|
ddata->rdev = devm_regulator_register(ddata->dev, &ddata->rdesc, &cfg);
|
|
return IS_ERR(ddata->rdev) ? PTR_ERR(ddata->rdev) : 0;
|
|
}
|
|
|
|
static int mt6375_chg_init_chgdev(struct mt6375_chg_data *ddata)
|
|
{
|
|
struct mt6375_chg_platform_data *pdata = dev_get_platdata(ddata->dev);
|
|
|
|
mt_dbg(ddata->dev, "%s: entry. Init charger device now.\n", __func__);
|
|
ddata->chgdev = charger_device_register(pdata->chg_name, ddata->dev,
|
|
ddata, &mt6375_chg_ops,
|
|
&mt6375_chg_props);
|
|
return IS_ERR(ddata->chgdev) ? PTR_ERR(ddata->chgdev) : 0;
|
|
}
|
|
|
|
#define MT6375_CHG_IRQ(_name) \
|
|
{ \
|
|
.name = #_name, \
|
|
.hdlr = mt6375_##_name##_handler, \
|
|
}
|
|
|
|
static int mt6375_chg_init_irq(struct mt6375_chg_data *ddata)
|
|
{
|
|
int i, ret;
|
|
struct {
|
|
const char * const name;
|
|
irq_handler_t const hdlr;
|
|
unsigned int irq;
|
|
} mt6375_chg_irqs[] = {
|
|
MT6375_CHG_IRQ(fl_wdt),
|
|
MT6375_CHG_IRQ(fl_pwr_rdy),
|
|
MT6375_CHG_IRQ(fl_vbus_ov),
|
|
MT6375_CHG_IRQ(fl_chg_tout),
|
|
MT6375_CHG_IRQ(fl_detach),
|
|
MT6375_CHG_IRQ(fl_bc12_dn),
|
|
MT6375_CHG_IRQ(fl_pe_done),
|
|
MT6375_CHG_IRQ(fl_aicc_done),
|
|
MT6375_CHG_IRQ(fl_batpro_done),
|
|
MT6375_CHG_IRQ(adc_vbat_mon_ov),
|
|
};
|
|
|
|
mt_dbg(ddata->dev, "%s: entry. Init irq now.\n", __func__);
|
|
for (i = 0; i < ARRAY_SIZE(mt6375_chg_irqs); i++) {
|
|
ret = platform_get_irq_byname(to_platform_device(ddata->dev),
|
|
mt6375_chg_irqs[i].name);
|
|
if (ret < 0) {
|
|
dev_err(ddata->dev, "failed to get irq %s\n",
|
|
mt6375_chg_irqs[i].name);
|
|
return ret;
|
|
}
|
|
ret = devm_request_threaded_irq(ddata->dev, ret, NULL,
|
|
mt6375_chg_irqs[i].hdlr,
|
|
IRQF_ONESHOT,
|
|
dev_name(ddata->dev), ddata);
|
|
if (ret < 0) {
|
|
dev_err(ddata->dev, "failed to request irq %s\n",
|
|
mt6375_chg_irqs[i].name);
|
|
return ret;
|
|
}
|
|
mt6375_chg_irqs[i].irq = ret;
|
|
}
|
|
ddata->detach_irq = mt6375_chg_irqs[4].irq;
|
|
return 0;
|
|
}
|
|
|
|
static int mt6375_set_shipping_mode(struct mt6375_chg_data *ddata)
|
|
{
|
|
int ret;
|
|
|
|
ret = mt6375_chg_field_set(ddata, F_SHIP_RST_DIS, 1);
|
|
if (ret < 0) {
|
|
dev_err(ddata->dev, "failed to disable ship reset\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = mt6375_chg_field_set(ddata, F_BATFET_DISDLY, 0);
|
|
if (ret < 0) {
|
|
dev_err(ddata->dev, "failed to disable ship mode delay\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = mt6375_chg_field_set(ddata, F_BUCK_EN, 0);
|
|
if (ret < 0) {
|
|
dev_notice(ddata->dev, "failed to disable chg buck en\n");
|
|
return ret;
|
|
}
|
|
|
|
return regmap_update_bits(ddata->rmap, MT6375_REG_CHG_TOP1,
|
|
MT6375_MSK_BATFET_DIS, 0xFF);
|
|
}
|
|
|
|
static ssize_t shipping_mode_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
int ret;
|
|
unsigned long magic;
|
|
struct mt6375_chg_data *ddata = dev_get_drvdata(dev);
|
|
|
|
ret = kstrtoul(buf, 0, &magic);
|
|
if (ret < 0) {
|
|
dev_warn(dev, "parsing number fail\n");
|
|
return ret;
|
|
}
|
|
if (magic != 5526789)
|
|
return -EINVAL;
|
|
ret = mt6375_set_shipping_mode(ddata);
|
|
return ret < 0 ? ret : count;
|
|
}
|
|
static const DEVICE_ATTR_WO(shipping_mode);
|
|
|
|
static int mt6375_chg_probe(struct platform_device *pdev)
|
|
{
|
|
int i, ret;
|
|
struct mt6375_chg_data *ddata;
|
|
struct device *dev = &pdev->dev;
|
|
const struct mt6375_chg_field *fds = mt6375_chg_fields;
|
|
|
|
dev_info(dev, "%s: entry. 6375 charger probe now.\n", __func__);
|
|
ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL);
|
|
if (!ddata)
|
|
return -ENOMEM;
|
|
|
|
ddata->rmap = dev_get_regmap(dev->parent, NULL);
|
|
if (!ddata->rmap) {
|
|
dev_err(dev, "failed to get regmap\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
for (i = 0; i < F_MAX; i++) {
|
|
ddata->rmap_fields[i] = devm_regmap_field_alloc(dev,
|
|
ddata->rmap,
|
|
fds[i].field);
|
|
if (IS_ERR(ddata->rmap_fields[i])) {
|
|
dev_err(dev, "failed to allocate regmap field\n");
|
|
return PTR_ERR(ddata->rmap_fields[i]);
|
|
}
|
|
}
|
|
|
|
ret = mt6375_chg_get_pdata(dev);
|
|
if (ret < 0) {
|
|
dev_err(dev, "failed to get platform data\n");
|
|
return ret;
|
|
}
|
|
|
|
ddata->dev = dev;
|
|
init_completion(&ddata->pe_done);
|
|
init_completion(&ddata->aicc_done);
|
|
mutex_init(&ddata->attach_lock);
|
|
mutex_init(&ddata->pe_lock);
|
|
mutex_init(&ddata->cv_lock);
|
|
mutex_init(&ddata->hm_lock);
|
|
atomic_set(&ddata->attach, 0);
|
|
atomic_set(&ddata->eoc_cnt, 0);
|
|
atomic_set(&ddata->no_6pin_used, 0);
|
|
ddata->wq = create_singlethread_workqueue(dev_name(dev));
|
|
if (!ddata->wq) {
|
|
dev_err(dev, "failed to create workqueue\n");
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
INIT_WORK(&ddata->bc12_work, mt6375_chg_bc12_work_func);
|
|
platform_set_drvdata(pdev, ddata);
|
|
|
|
ret = device_create_file(dev, &dev_attr_shipping_mode);
|
|
if (ret < 0) {
|
|
dev_err(dev, "failed to create shipping mode attribute\n");
|
|
goto out_wq;
|
|
}
|
|
|
|
ret = mt6375_chg_init_setting(ddata);
|
|
if (ret < 0) {
|
|
dev_err(dev, "failed to init setting\n");
|
|
goto out_attr;
|
|
}
|
|
|
|
ret = mt6375_chg_get_iio_adc(ddata);
|
|
if (ret < 0) {
|
|
dev_err(dev, "failed to get iio adc\n");
|
|
goto out_attr;
|
|
}
|
|
|
|
ret = mt6375_chg_init_psy(ddata);
|
|
if (ret < 0) {
|
|
dev_err(dev, "failed to init power supply\n");
|
|
goto out_attr;
|
|
}
|
|
|
|
ret = mt6375_chg_init_regulator(ddata);
|
|
if (ret < 0) {
|
|
dev_err(dev, "failed to init regulator\n");
|
|
goto out_attr;
|
|
}
|
|
|
|
ret = mt6375_chg_init_chgdev(ddata);
|
|
if (ret < 0) {
|
|
dev_err(dev, "failed to init chgdev\n");
|
|
goto out_attr;
|
|
}
|
|
|
|
ret = mt6375_chg_init_irq(ddata);
|
|
if (ret < 0) {
|
|
dev_err(dev, "failed to init irq\n");
|
|
goto out_chgdev;
|
|
}
|
|
mt6375_chg_pwr_rdy_process(ddata);
|
|
mt_dbg(dev, "successfully\n");
|
|
return 0;
|
|
out_chgdev:
|
|
charger_device_unregister(ddata->chgdev);
|
|
out_attr:
|
|
device_remove_file(ddata->dev, &dev_attr_shipping_mode);
|
|
out_wq:
|
|
destroy_workqueue(ddata->wq);
|
|
out:
|
|
mutex_destroy(&ddata->hm_lock);
|
|
mutex_destroy(&ddata->cv_lock);
|
|
mutex_destroy(&ddata->pe_lock);
|
|
mutex_destroy(&ddata->attach_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int mt6375_chg_remove(struct platform_device *pdev)
|
|
{
|
|
struct mt6375_chg_data *ddata = platform_get_drvdata(pdev);
|
|
|
|
mt_dbg(&pdev->dev, "%s: entry. 6375 charger remove now.\n", __func__);
|
|
if (ddata) {
|
|
charger_device_unregister(ddata->chgdev);
|
|
device_remove_file(ddata->dev, &dev_attr_shipping_mode);
|
|
destroy_workqueue(ddata->wq);
|
|
mutex_destroy(&ddata->hm_lock);
|
|
mutex_destroy(&ddata->cv_lock);
|
|
mutex_destroy(&ddata->pe_lock);
|
|
mutex_destroy(&ddata->attach_lock);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id __maybe_unused mt6375_chg_of_match[] = {
|
|
{ .compatible = "mediatek,mt6375-chg", },
|
|
{ },
|
|
};
|
|
MODULE_DEVICE_TABLE(of, mt6375_chg_of_match);
|
|
|
|
static struct platform_driver mt6375_chg_driver = {
|
|
.probe = mt6375_chg_probe,
|
|
.remove = mt6375_chg_remove,
|
|
.driver = {
|
|
.name = "mt6375-chg",
|
|
.of_match_table = of_match_ptr(mt6375_chg_of_match),
|
|
},
|
|
};
|
|
module_platform_driver(mt6375_chg_driver);
|
|
|
|
MODULE_AUTHOR("ShuFan Lee <shufan_lee@richtek.com>");
|
|
MODULE_DESCRIPTION("MT6375 Charger Driver");
|
|
MODULE_LICENSE("GPL v2");
|