2302 lines
59 KiB
C
2302 lines
59 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (c) 2022 MediaTek Inc.
|
|
*/
|
|
|
|
#include <linux/bits.h>
|
|
#include <linux/gpio/consumer.h>
|
|
#include <linux/iio/consumer.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/of.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/power_supply.h>
|
|
#include <linux/property.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/regulator/driver.h>
|
|
#include <linux/phy/phy.h>
|
|
|
|
#include "charger_class.h"
|
|
#include "mtk_charger.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 PHY_MODE_BC11_SET 1
|
|
#define PHY_MODE_BC11_CLR 2
|
|
|
|
#define RT9490_REG_SYS_MIN 0x00
|
|
#define RT9490_REG_VCHG_CTRL 0x01
|
|
#define RT9490_REG_ICHG_CTRL 0x03
|
|
#define RT9490_REG_MIVR_CTRL 0x05
|
|
#define RT9490_REG_AICR_CTRL 0x06
|
|
#define RT9490_REG_PRE_CHG 0x08
|
|
#define RT9490_REG_EOC_CTRL 0x09
|
|
#define RT9490_REG_RECHG 0x0A
|
|
#define RT9490_REG_VOTG 0x0B
|
|
#define RT9490_REG_IOTG 0x0D
|
|
#define RT9490_REG_SFTMR_CTRL 0x0E
|
|
#define RT9490_REG_CHG_CTRL0 0x0F
|
|
#define RT9490_REG_CHG_CTRL1 0x10
|
|
#define RT9490_REG_CHG_CTRL2 0x11
|
|
#define RT9490_REG_CHG_CTRL3 0x12
|
|
#define RT9490_REG_CHG_CTRL4 0x13
|
|
#define RT9490_REG_CHG_CTRL5 0x14
|
|
#define RT9490_REG_THREG_CTRL 0x16
|
|
#define RT9490_REG_JEITA_CTRL1 0x18
|
|
#define RT9490_REG_AICC_CTRL 0x19
|
|
#define RT9490_REG_CHG_STAT0 0x1B
|
|
#define RT9490_REG_CHG_STAT1 0x1C
|
|
#define RT9490_REG_CHG_STAT2 0x1D
|
|
#define RT9490_REG_CHG_STAT4 0x1F
|
|
#define RT9490_REG_FAULT_STAT1 0x21
|
|
#define RT9490_REG_PUMP_EXP 0x49
|
|
#define RT9490_REG_ADD_CTRL0 0x4A
|
|
#define RT9490_REG_ADD_CTRL2 0x4C
|
|
|
|
#define RT9490_OTGLBP_MASK BIT(4)
|
|
#define RT9490_OTGOVP_MASK BIT(5)
|
|
#define RT9490_OTGUVP_MASK BIT(4)
|
|
|
|
#define RT9490_ICHG_MINUA 50000
|
|
#define RT9490_ICHG_MAXUA 5000000
|
|
#define RT9490_CV_MAXUV 1880000
|
|
#define RT9490_OTG_MINUV 2800000
|
|
#define RT9490_OTG_STEPUV 10000
|
|
#define RT9490_OTG_MAXUV 22000000
|
|
#define RT9490_OTG_N_VOLTAGES 1920
|
|
#define RT9490_AICR_WAITUS 4000
|
|
#define RT9490_VMIVR_MAXUV 22000000
|
|
|
|
#define NORMAL_CHARGING_CURR_UA 500000
|
|
#define FAST_CHARGING_CURR_UA 1500000
|
|
enum {
|
|
RT9490_STAT_NOT_CHARGING = 0,
|
|
RT9490_STAT_TRICKLE_CHARGE,
|
|
RT9490_STAT_PRE_CHARGE,
|
|
RT9490_STAT_FAST_CHARGE_CC,
|
|
RT9490_STAT_FAST_CHARGE_CV,
|
|
RT9490_STAT_IEOC,
|
|
RT9490_STAT_BACKGROUND_CHARGE,
|
|
RT9490_STAT_CHARGE_DONE,
|
|
};
|
|
|
|
/* 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 rt9490_fields {
|
|
F_VSYSMIN = 0,
|
|
F_MIVR,
|
|
F_VPRECHG,
|
|
F_IPRECHG,
|
|
F_IEOC,
|
|
F_TRECHG,
|
|
F_VRECHG,
|
|
F_IOTG,
|
|
F_EN_FST_TMR,
|
|
F_EN_CHG,
|
|
F_EN_AICC,
|
|
F_FORCE_AICC,
|
|
F_EN_HZ,
|
|
F_EN_TE,
|
|
F_VACOVP,
|
|
F_WDRST,
|
|
F_WATCHDOG,
|
|
F_EN_FUSBDET,
|
|
F_EN_BC12,
|
|
F_SDRV_CTRL,
|
|
F_SDRV_DLY,
|
|
F_IBAT_REG,
|
|
F_EN_AICR,
|
|
F_EN_ILIM,
|
|
F_THREG,
|
|
F_TOTP,
|
|
F_JEITA_DIS,
|
|
F_MIVR_STAT,
|
|
F_VAC1_PG,
|
|
F_VBUS_PG,
|
|
F_CHG_STAT,
|
|
F_VBUS_STAT,
|
|
F_AICC_STAT,
|
|
F_PE_EN,
|
|
F_PE_SEL,
|
|
F_PE10_INC,
|
|
F_PE20_CODE,
|
|
F_TD_EOC,
|
|
F_EOC_RST,
|
|
F_SPEC_TA_EN,
|
|
F_MAX_FIELDS
|
|
};
|
|
|
|
enum {
|
|
RT9490_RANGE_VSYSMIN = 0,
|
|
RT9490_RANGE_CV,
|
|
RT9490_RANGE_ICHG,
|
|
RT9490_RANGE_MIVR,
|
|
RT9490_RANGE_AICR,
|
|
RT9490_RANGE_IPRECHG,
|
|
RT9490_RANGE_IEOC,
|
|
RT9490_RANGE_IOTG,
|
|
RT9490_RANGE_PE20_CODE,
|
|
RT9490_MAX_RANGES
|
|
};
|
|
|
|
enum {
|
|
RT9490_CABLE_NO_INPUT = 0,
|
|
RT9490_CABLE_USB_SDP,
|
|
RT9490_CABLE_USB_CDP,
|
|
RT9490_CABLE_USB_DCP,
|
|
RT9490_CABLE_UNKNOWN_DCP,
|
|
RT9490_CABLE_NSTD,
|
|
RT9490_CABLE_APPLE_TA,
|
|
RT9490_CABLE_IN_OTG,
|
|
RT9490_CABLE_BAD_ADAPTER,
|
|
RT9490_CABLE_POWER_BY_VBUS = 11
|
|
};
|
|
|
|
enum rt9490_adc_chan {
|
|
RT9490_ADC_TDIE,
|
|
RT9490_ADC_TS,
|
|
RT9490_ADC_VSYS,
|
|
RT9490_ADC_VBAT,
|
|
RT9490_ADC_VBUS,
|
|
RT9490_ADC_IBAT,
|
|
RT9490_ADC_IBUS,
|
|
RT9490_ADC_VAC1,
|
|
RT9490_ADC_VAC2,
|
|
RT9490_ADC_DM,
|
|
RT9490_ADC_DP,
|
|
RT9490_ADC_MAX
|
|
};
|
|
|
|
enum rt9490_charging_stat {
|
|
RT9490_CHG_STAT_READY = 0,
|
|
RT9490_CHG_STAT_TRICKLE,
|
|
RT9490_CHG_STAT_PRECHG,
|
|
RT9490_CHG_STAT_FASTCHG_CC,
|
|
RT9490_CHG_STAT_FASTCHG_CV,
|
|
RT9490_CHG_STAT_IEOC,
|
|
RT9490_CHG_STAT_BG_CHG,
|
|
RT9490_CHG_STAT_CHG_DONE,
|
|
RT9490_CHG_STAT_MAX,
|
|
};
|
|
|
|
enum rt9490_attach_trigger {
|
|
ATTACH_TRIG_IGNORE,
|
|
ATTACH_TRIG_PWR_RDY,
|
|
ATTACH_TRIG_TYPEC,
|
|
};
|
|
|
|
enum rt9490_usbsw {
|
|
USBSW_CHG = 0,
|
|
USBSW_USB,
|
|
};
|
|
|
|
struct rt9490_chg_data {
|
|
struct device *dev;
|
|
struct regmap *regmap;
|
|
struct regmap_field *rm_field[F_MAX_FIELDS];
|
|
struct power_supply *psy;
|
|
struct iio_channel *adc_chans[RT9490_ADC_MAX];
|
|
const char *chg_name;
|
|
struct charger_device *chgdev;
|
|
struct mutex lock;
|
|
struct mutex pe_lock;
|
|
struct power_supply_desc chg_desc;
|
|
enum power_supply_usb_type usb_type;
|
|
unsigned int vbus_ready;
|
|
struct completion aicc_done;
|
|
atomic_t aicc_once;
|
|
atomic_t attach;
|
|
enum rt9490_attach_trigger attach_trig;
|
|
u32 boot_mode;
|
|
u32 boot_type;
|
|
bool bc12_dn;
|
|
struct workqueue_struct *wq;
|
|
struct work_struct bc12_work;
|
|
struct gpio_desc *ceb_gpio;
|
|
};
|
|
|
|
static const char *const rt9490_attach_trig_names[] = {
|
|
"ignore", "pwr_rdy", "typec",
|
|
};
|
|
|
|
static const char *const rt9490_port_stat_names[] = {
|
|
[RT9490_CABLE_NO_INPUT] = "No Input",
|
|
[RT9490_CABLE_USB_SDP] = "SDP",
|
|
[RT9490_CABLE_USB_CDP] = "CDP",
|
|
[RT9490_CABLE_USB_DCP] = "DCP",
|
|
[RT9490_CABLE_UNKNOWN_DCP] = "Unknown DCP",
|
|
[RT9490_CABLE_NSTD] = "NSTD",
|
|
[RT9490_CABLE_APPLE_TA] = "Apple TA",
|
|
[RT9490_CABLE_IN_OTG] = "In OTG",
|
|
[RT9490_CABLE_BAD_ADAPTER] = "Bad Adapter",
|
|
[RT9490_CABLE_POWER_BY_VBUS] = "Power By Vbus",
|
|
};
|
|
|
|
static struct reg_field rt9490_reg_fields[] = {
|
|
[F_VSYSMIN] = REG_FIELD(RT9490_REG_SYS_MIN, 0, 5),
|
|
[F_MIVR] = REG_FIELD(RT9490_REG_MIVR_CTRL, 0, 7),
|
|
[F_VPRECHG] = REG_FIELD(RT9490_REG_PRE_CHG, 6, 7),
|
|
[F_IPRECHG] = REG_FIELD(RT9490_REG_PRE_CHG, 0, 5),
|
|
[F_IEOC] = REG_FIELD(RT9490_REG_EOC_CTRL, 0, 4),
|
|
[F_TRECHG] = REG_FIELD(RT9490_REG_RECHG, 4, 5),
|
|
[F_VRECHG] = REG_FIELD(RT9490_REG_RECHG, 0, 3),
|
|
[F_IOTG] = REG_FIELD(RT9490_REG_IOTG, 0, 6),
|
|
[F_EN_FST_TMR] = REG_FIELD(RT9490_REG_SFTMR_CTRL, 3, 3),
|
|
[F_EN_CHG] = REG_FIELD(RT9490_REG_CHG_CTRL0, 5, 5),
|
|
[F_EN_AICC] = REG_FIELD(RT9490_REG_CHG_CTRL0, 4, 4),
|
|
[F_FORCE_AICC] = REG_FIELD(RT9490_REG_CHG_CTRL0, 3, 3),
|
|
[F_EN_HZ] = REG_FIELD(RT9490_REG_CHG_CTRL0, 2, 2),
|
|
[F_EN_TE] = REG_FIELD(RT9490_REG_CHG_CTRL0, 1, 1),
|
|
[F_VACOVP] = REG_FIELD(RT9490_REG_CHG_CTRL1, 4, 5),
|
|
[F_WDRST] = REG_FIELD(RT9490_REG_CHG_CTRL1, 3, 3),
|
|
[F_WATCHDOG] = REG_FIELD(RT9490_REG_CHG_CTRL1, 0, 2),
|
|
[F_EN_FUSBDET] = REG_FIELD(RT9490_REG_CHG_CTRL2, 7, 7),
|
|
[F_EN_BC12] = REG_FIELD(RT9490_REG_CHG_CTRL2, 6, 6),
|
|
[F_SDRV_CTRL] = REG_FIELD(RT9490_REG_CHG_CTRL2, 1, 2),
|
|
[F_SDRV_DLY] = REG_FIELD(RT9490_REG_CHG_CTRL2, 0, 0),
|
|
[F_IBAT_REG] = REG_FIELD(RT9490_REG_CHG_CTRL5, 3, 4),
|
|
[F_EN_AICR] = REG_FIELD(RT9490_REG_CHG_CTRL5, 2, 2),
|
|
[F_EN_ILIM] = REG_FIELD(RT9490_REG_CHG_CTRL5, 1, 1),
|
|
[F_THREG] = REG_FIELD(RT9490_REG_THREG_CTRL, 6, 7),
|
|
[F_TOTP] = REG_FIELD(RT9490_REG_THREG_CTRL, 4, 5),
|
|
[F_JEITA_DIS] = REG_FIELD(RT9490_REG_JEITA_CTRL1, 0, 0),
|
|
[F_MIVR_STAT] = REG_FIELD(RT9490_REG_CHG_STAT0, 6, 6),
|
|
[F_VAC1_PG] = REG_FIELD(RT9490_REG_CHG_STAT0, 1, 1),
|
|
[F_VBUS_PG] = REG_FIELD(RT9490_REG_CHG_STAT0, 0, 0),
|
|
[F_CHG_STAT] = REG_FIELD(RT9490_REG_CHG_STAT1, 5, 7),
|
|
[F_VBUS_STAT] = REG_FIELD(RT9490_REG_CHG_STAT1, 1, 4),
|
|
[F_AICC_STAT] = REG_FIELD(RT9490_REG_CHG_STAT2, 6, 7),
|
|
[F_PE_EN] = REG_FIELD(RT9490_REG_PUMP_EXP, 7, 7),
|
|
[F_PE_SEL] = REG_FIELD(RT9490_REG_PUMP_EXP, 6, 6),
|
|
[F_PE10_INC] = REG_FIELD(RT9490_REG_PUMP_EXP, 5, 5),
|
|
[F_PE20_CODE] = REG_FIELD(RT9490_REG_PUMP_EXP, 0, 4),
|
|
[F_TD_EOC] = REG_FIELD(RT9490_REG_ADD_CTRL0, 4, 4),
|
|
[F_EOC_RST] = REG_FIELD(RT9490_REG_ADD_CTRL0, 3, 3),
|
|
[F_SPEC_TA_EN] = REG_FIELD(RT9490_REG_ADD_CTRL2, 2, 2),
|
|
};
|
|
|
|
#define RT9490_LINEAR_RANGE(_idx, _min, _min_sel, _max_sel, _step) \
|
|
[_idx] = REGULATOR_LINEAR_RANGE(_min, _min_sel, _max_sel, _step)
|
|
|
|
/* All converted to microvolt or microamp */
|
|
static const struct linear_range rt9490_ranges[RT9490_MAX_RANGES] = {
|
|
RT9490_LINEAR_RANGE(RT9490_RANGE_VSYSMIN, 2500000, 0, 54, 250000),
|
|
RT9490_LINEAR_RANGE(RT9490_RANGE_CV, 3000000, 300, 1880, 10000),
|
|
RT9490_LINEAR_RANGE(RT9490_RANGE_ICHG, 50000, 5, 500, 10000),
|
|
RT9490_LINEAR_RANGE(RT9490_RANGE_MIVR, 3600000, 36, 220, 100000),
|
|
RT9490_LINEAR_RANGE(RT9490_RANGE_AICR, 100000, 10, 330, 10000),
|
|
RT9490_LINEAR_RANGE(RT9490_RANGE_IPRECHG, 40000, 1, 50, 40000),
|
|
RT9490_LINEAR_RANGE(RT9490_RANGE_IEOC, 40000, 1, 25, 40000),
|
|
RT9490_LINEAR_RANGE(RT9490_RANGE_IOTG, 120000, 3, 83, 40000),
|
|
RT9490_LINEAR_RANGE(RT9490_RANGE_PE20_CODE, 5500000, 0, 29, 500000),
|
|
};
|
|
|
|
static const enum power_supply_property rt9490_charger_properties[] = {
|
|
POWER_SUPPLY_PROP_STATUS,
|
|
POWER_SUPPLY_PROP_CHARGE_TYPE,
|
|
POWER_SUPPLY_PROP_ONLINE,
|
|
POWER_SUPPLY_PROP_VOLTAGE_NOW,
|
|
POWER_SUPPLY_PROP_CURRENT_NOW,
|
|
POWER_SUPPLY_PROP_CURRENT_MAX,
|
|
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
|
|
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
|
|
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
|
|
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
|
|
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
|
|
POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT,
|
|
POWER_SUPPLY_PROP_USB_TYPE,
|
|
POWER_SUPPLY_PROP_PRECHARGE_CURRENT,
|
|
POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
|
|
POWER_SUPPLY_PROP_MANUFACTURER,
|
|
POWER_SUPPLY_PROP_TYPE,
|
|
};
|
|
|
|
static const enum power_supply_usb_type rt9490_charger_usb_types[] = {
|
|
POWER_SUPPLY_USB_TYPE_UNKNOWN,
|
|
POWER_SUPPLY_USB_TYPE_SDP,
|
|
POWER_SUPPLY_USB_TYPE_DCP,
|
|
POWER_SUPPLY_USB_TYPE_CDP,
|
|
};
|
|
|
|
static char *rt9490_charger_supplied_to[] = {
|
|
"battery",
|
|
"mtk-master-charger"
|
|
};
|
|
|
|
static int rt9490_get_be16_selector_to_value(struct rt9490_chg_data *data,
|
|
unsigned int reg,
|
|
unsigned int range_idx,
|
|
unsigned int *val)
|
|
{
|
|
__be16 be16_sel;
|
|
unsigned int sel;
|
|
int ret;
|
|
|
|
ret = regmap_raw_read(data->regmap, reg, &be16_sel, sizeof(be16_sel));
|
|
if (ret)
|
|
return ret;
|
|
|
|
sel = be16_to_cpu(be16_sel);
|
|
|
|
return linear_range_get_value(rt9490_ranges + range_idx, sel, val);
|
|
}
|
|
|
|
static int rt9490_set_value_to_be16_selector(struct rt9490_chg_data *data,
|
|
unsigned int reg,
|
|
unsigned int range_idx,
|
|
unsigned int val)
|
|
{
|
|
unsigned int sel = 0;
|
|
__be16 be16_sel;
|
|
|
|
linear_range_get_selector_within(rt9490_ranges + range_idx, val, &sel);
|
|
|
|
be16_sel = cpu_to_be16(sel);
|
|
|
|
return regmap_raw_write(data->regmap, reg, &be16_sel, sizeof(be16_sel));
|
|
}
|
|
|
|
static int rt9490_charger_get_status(struct rt9490_chg_data *data,
|
|
union power_supply_propval *val)
|
|
{
|
|
unsigned int status;
|
|
int ret;
|
|
|
|
ret = regmap_field_read(data->rm_field[F_CHG_STAT], &status);
|
|
if (ret)
|
|
return ret;
|
|
|
|
switch (status) {
|
|
case RT9490_STAT_NOT_CHARGING:
|
|
val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
|
|
break;
|
|
case RT9490_STAT_CHARGE_DONE:
|
|
val->intval = POWER_SUPPLY_STATUS_FULL;
|
|
break;
|
|
default:
|
|
val->intval = POWER_SUPPLY_STATUS_CHARGING;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rt9490_charger_get_charge_type(struct rt9490_chg_data *data,
|
|
union power_supply_propval *val)
|
|
{
|
|
unsigned int status;
|
|
int ret;
|
|
|
|
ret = regmap_field_read(data->rm_field[F_CHG_STAT], &status);
|
|
if (ret)
|
|
return ret;
|
|
|
|
switch (status) {
|
|
case RT9490_STAT_NOT_CHARGING:
|
|
val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE;
|
|
break;
|
|
case RT9490_STAT_TRICKLE_CHARGE:
|
|
val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
|
|
break;
|
|
default:
|
|
val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rt9490_charger_get_online(struct rt9490_chg_data *data,
|
|
union power_supply_propval *val)
|
|
{
|
|
mutex_lock(&data->lock);
|
|
val->intval = atomic_read(&data->attach);
|
|
mutex_unlock(&data->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rt9490_charger_get_adc(struct rt9490_chg_data *data, u8 chan_idx,
|
|
union power_supply_propval *val)
|
|
{
|
|
int adc_val, ret;
|
|
|
|
if (chan_idx >= RT9490_ADC_MAX)
|
|
return -EINVAL;
|
|
|
|
ret = iio_read_channel_processed(data->adc_chans[chan_idx], &adc_val);
|
|
if (ret)
|
|
return ret;
|
|
|
|
val->intval = adc_val;
|
|
return 0;
|
|
}
|
|
|
|
static int rt9490_charger_get_ichg(struct rt9490_chg_data *data,
|
|
union power_supply_propval *val)
|
|
{
|
|
unsigned int value;
|
|
int ret;
|
|
|
|
ret = rt9490_get_be16_selector_to_value(data, RT9490_REG_ICHG_CTRL,
|
|
RT9490_RANGE_ICHG, &value);
|
|
if (ret)
|
|
return ret;
|
|
|
|
val->intval = value;
|
|
return 0;
|
|
}
|
|
|
|
static int rt9490_charger_get_max_ichg(struct rt9490_chg_data *data,
|
|
union power_supply_propval *val)
|
|
{
|
|
val->intval = RT9490_ICHG_MAXUA;
|
|
return 0;
|
|
}
|
|
|
|
static int rt9490_charger_get_cv(struct rt9490_chg_data *data,
|
|
union power_supply_propval *val)
|
|
{
|
|
unsigned int value;
|
|
int ret;
|
|
|
|
ret = rt9490_get_be16_selector_to_value(data, RT9490_REG_VCHG_CTRL,
|
|
RT9490_RANGE_CV, &value);
|
|
if (ret)
|
|
return ret;
|
|
|
|
val->intval = value;
|
|
return 0;
|
|
}
|
|
|
|
static int rt9490_charger_get_max_cv(struct rt9490_chg_data *data,
|
|
union power_supply_propval *val)
|
|
{
|
|
val->intval = RT9490_CV_MAXUV;
|
|
return 0;
|
|
}
|
|
|
|
static int rt9490_charger_get_aicr(struct rt9490_chg_data *data,
|
|
union power_supply_propval *val)
|
|
{
|
|
unsigned int value;
|
|
int ret;
|
|
|
|
ret = rt9490_get_be16_selector_to_value(data, RT9490_REG_AICR_CTRL,
|
|
RT9490_RANGE_AICR, &value);
|
|
if (ret)
|
|
return ret;
|
|
|
|
val->intval = value;
|
|
return 0;
|
|
}
|
|
|
|
static int rt9490_charger_get_mivr(struct rt9490_chg_data *data,
|
|
union power_supply_propval *val)
|
|
{
|
|
unsigned int sel, value;
|
|
int ret;
|
|
|
|
ret = regmap_field_read(data->rm_field[F_MIVR], &sel);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = linear_range_get_value(rt9490_ranges + RT9490_RANGE_MIVR, sel,
|
|
&value);
|
|
if (ret)
|
|
return ret;
|
|
|
|
val->intval = value;
|
|
return 0;
|
|
}
|
|
|
|
static int rt9490_charger_get_usb_type(struct rt9490_chg_data *data,
|
|
union power_supply_propval *val)
|
|
{
|
|
mutex_lock(&data->lock);
|
|
val->intval = data->usb_type;
|
|
dev_info(data->dev, "%s: usb_type=%d\n", __func__, data->usb_type);
|
|
mutex_unlock(&data->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rt9490_charger_get_iprechg(struct rt9490_chg_data *data,
|
|
union power_supply_propval *val)
|
|
{
|
|
unsigned int sel, value;
|
|
int ret;
|
|
|
|
ret = regmap_field_read(data->rm_field[F_IPRECHG], &sel);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = linear_range_get_value(rt9490_ranges + RT9490_RANGE_IPRECHG, sel,
|
|
&value);
|
|
if (ret)
|
|
return ret;
|
|
|
|
val->intval = value;
|
|
return 0;
|
|
}
|
|
|
|
static int rt9490_charger_get_ieoc(struct rt9490_chg_data *data,
|
|
union power_supply_propval *val)
|
|
{
|
|
unsigned int sel, value;
|
|
int ret;
|
|
|
|
ret = regmap_field_read(data->rm_field[F_IEOC], &sel);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = linear_range_get_value(rt9490_ranges + RT9490_RANGE_IEOC, sel,
|
|
&value);
|
|
if (ret)
|
|
return ret;
|
|
|
|
val->intval = value;
|
|
return 0;
|
|
}
|
|
|
|
static int rt9490_charger_get_manufacturer(struct rt9490_chg_data *data,
|
|
union power_supply_propval *val)
|
|
{
|
|
static const char *manufacturer = "Richtek Technology Corp";
|
|
|
|
val->strval = manufacturer;
|
|
return 0;
|
|
}
|
|
|
|
static void rt9490_chg_attach_pre_process(struct rt9490_chg_data *data,
|
|
enum rt9490_attach_trigger trig,
|
|
int attach)
|
|
{
|
|
bool bc12_dn;
|
|
int ret;
|
|
|
|
ret = regmap_field_read(data->rm_field[F_VAC1_PG], &data->vbus_ready);
|
|
if (ret) {
|
|
dev_info(data->dev, "%s failed to get F_VAC1_PG(%d)\n",
|
|
__func__, ret);
|
|
return;
|
|
}
|
|
|
|
dev_info(data->dev, "trig=%s,attach=%d,vbus_ready=%d\n",
|
|
rt9490_attach_trig_names[trig], attach, data->vbus_ready);
|
|
dev_info(data->dev, "data_bc12_dn=%d\n", data->bc12_dn);
|
|
/* if attach trigger is not match, ignore it */
|
|
if (data->attach_trig != trig) {
|
|
dev_notice(data->dev, "trig=%s ignore\n",
|
|
rt9490_attach_trig_names[trig]);
|
|
return;
|
|
}
|
|
|
|
if (attach == ATTACH_TYPE_NONE)
|
|
data->bc12_dn = false;
|
|
|
|
bc12_dn = data->bc12_dn;
|
|
if (!bc12_dn)
|
|
atomic_set(&data->attach, attach);
|
|
|
|
if (attach > ATTACH_TYPE_PD && bc12_dn)
|
|
return;
|
|
|
|
if (!queue_work(data->wq, &data->bc12_work))
|
|
dev_notice(data->dev, "bc12 work already queued\n");
|
|
}
|
|
|
|
static int rt9490_charger_set_ichg(struct rt9490_chg_data *data,
|
|
const union power_supply_propval *val)
|
|
{
|
|
return rt9490_set_value_to_be16_selector(data, RT9490_REG_ICHG_CTRL,
|
|
RT9490_RANGE_ICHG,
|
|
val->intval);
|
|
}
|
|
|
|
static int rt9490_enable_charging(struct charger_device *chgdev, bool en);
|
|
static int rt9490_charger_set_cv(struct rt9490_chg_data *data,
|
|
const union power_supply_propval *val)
|
|
{
|
|
union power_supply_propval vbat_val;
|
|
int ret;
|
|
|
|
ret = rt9490_charger_get_adc(data, RT9490_ADC_VBAT, &vbat_val);
|
|
if (ret)
|
|
return ret;
|
|
if (val->intval < vbat_val.intval) {
|
|
dev_notice(data->dev, "cv(%d)<vbat(%d), disable charging\n",
|
|
val->intval, vbat_val.intval);
|
|
ret = rt9490_enable_charging(data->chgdev, false);
|
|
return ret;
|
|
}
|
|
return rt9490_set_value_to_be16_selector(data, RT9490_REG_VCHG_CTRL,
|
|
RT9490_RANGE_CV,
|
|
val->intval);
|
|
}
|
|
|
|
static int rt9490_charger_set_aicr(struct rt9490_chg_data *data,
|
|
const union power_supply_propval *val)
|
|
{
|
|
return rt9490_set_value_to_be16_selector(data, RT9490_REG_AICR_CTRL,
|
|
RT9490_RANGE_AICR,
|
|
val->intval);
|
|
}
|
|
|
|
static int rt9490_charger_set_mivr(struct rt9490_chg_data *data,
|
|
const union power_supply_propval *val)
|
|
{
|
|
unsigned int sel = 0;
|
|
|
|
linear_range_get_selector_within(rt9490_ranges + RT9490_RANGE_MIVR,
|
|
val->intval, &sel);
|
|
return regmap_field_write(data->rm_field[F_MIVR], sel);
|
|
}
|
|
|
|
static int rt9490_charger_set_iprechg(struct rt9490_chg_data *data,
|
|
const union power_supply_propval *val)
|
|
{
|
|
unsigned int sel = 0;
|
|
|
|
linear_range_get_selector_within(rt9490_ranges + RT9490_RANGE_IPRECHG,
|
|
val->intval, &sel);
|
|
return regmap_field_write(data->rm_field[F_IPRECHG], sel);
|
|
}
|
|
|
|
static int rt9490_charger_set_ieoc(struct rt9490_chg_data *data,
|
|
const union power_supply_propval *val)
|
|
{
|
|
unsigned int sel = 0;
|
|
|
|
linear_range_get_selector_within(rt9490_ranges + RT9490_RANGE_IEOC,
|
|
val->intval, &sel);
|
|
return regmap_field_write(data->rm_field[F_IEOC], sel);
|
|
}
|
|
|
|
static int rt9490_charger_get_property(struct power_supply *psy,
|
|
enum power_supply_property psp,
|
|
union power_supply_propval *val)
|
|
{
|
|
struct rt9490_chg_data *data = power_supply_get_drvdata(psy);
|
|
|
|
if (!data)
|
|
return -ENODATA;
|
|
|
|
switch (psp) {
|
|
case POWER_SUPPLY_PROP_STATUS:
|
|
return rt9490_charger_get_status(data, val);
|
|
case POWER_SUPPLY_PROP_CHARGE_TYPE:
|
|
return rt9490_charger_get_charge_type(data, val);
|
|
case POWER_SUPPLY_PROP_ONLINE:
|
|
return rt9490_charger_get_online(data, val);
|
|
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
|
|
return rt9490_charger_get_adc(data, RT9490_ADC_VBUS, val);
|
|
case POWER_SUPPLY_PROP_CURRENT_NOW:
|
|
return rt9490_charger_get_adc(data, RT9490_ADC_IBUS, val);
|
|
case POWER_SUPPLY_PROP_CURRENT_MAX:
|
|
if (data->chg_desc.type == POWER_SUPPLY_TYPE_USB)
|
|
val->intval = NORMAL_CHARGING_CURR_UA;
|
|
else if (data->chg_desc.type == POWER_SUPPLY_TYPE_USB_DCP)
|
|
val->intval = FAST_CHARGING_CURR_UA;
|
|
return 0;
|
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
|
|
return rt9490_charger_get_ichg(data, val);
|
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
|
|
return rt9490_charger_get_max_ichg(data, val);
|
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
|
|
return rt9490_charger_get_cv(data, val);
|
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
|
|
return rt9490_charger_get_max_cv(data, val);
|
|
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
|
|
return rt9490_charger_get_aicr(data, val);
|
|
case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT:
|
|
return rt9490_charger_get_mivr(data, val);
|
|
case POWER_SUPPLY_PROP_USB_TYPE:
|
|
return rt9490_charger_get_usb_type(data, val);
|
|
case POWER_SUPPLY_PROP_PRECHARGE_CURRENT:
|
|
return rt9490_charger_get_iprechg(data, val);
|
|
case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
|
|
return rt9490_charger_get_ieoc(data, val);
|
|
case POWER_SUPPLY_PROP_MANUFACTURER:
|
|
return rt9490_charger_get_manufacturer(data, val);
|
|
case POWER_SUPPLY_PROP_TYPE:
|
|
val->intval = data->chg_desc.type;
|
|
return 0;
|
|
default:
|
|
return -ENODATA;
|
|
}
|
|
}
|
|
|
|
static int rt9490_charger_set_property(struct power_supply *psy,
|
|
enum power_supply_property psp,
|
|
const union power_supply_propval *val)
|
|
{
|
|
struct rt9490_chg_data *data = power_supply_get_drvdata(psy);
|
|
|
|
if (!data)
|
|
return -ENODATA;
|
|
|
|
switch (psp) {
|
|
case POWER_SUPPLY_PROP_ONLINE:
|
|
mutex_lock(&data->lock);
|
|
rt9490_chg_attach_pre_process(data, ATTACH_TRIG_TYPEC,
|
|
val->intval);
|
|
mutex_unlock(&data->lock);
|
|
return 0;
|
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
|
|
return rt9490_charger_set_ichg(data, val);
|
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
|
|
return rt9490_charger_set_cv(data, val);
|
|
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
|
|
return rt9490_charger_set_aicr(data, val);
|
|
case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT:
|
|
return rt9490_charger_set_mivr(data, val);
|
|
case POWER_SUPPLY_PROP_PRECHARGE_CURRENT:
|
|
return rt9490_charger_set_iprechg(data, val);
|
|
case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
|
|
return rt9490_charger_set_ieoc(data, val);
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
/* MTK Charger Interface */
|
|
static int rt9490_psy_prop_ops(struct rt9490_chg_data *data, bool is_set,
|
|
enum power_supply_property psp, uint32_t *value)
|
|
{
|
|
union power_supply_propval val;
|
|
int ret = 0;
|
|
|
|
if (is_set) {
|
|
val.intval = *value;
|
|
ret = power_supply_set_property(data->psy, psp, &val);
|
|
if (ret)
|
|
return ret;
|
|
} else {
|
|
ret = power_supply_get_property(data->psy, psp, &val);
|
|
if (ret)
|
|
return ret;
|
|
|
|
*value = val.intval;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rt9490_plug_in(struct charger_device *chgdev)
|
|
{
|
|
struct rt9490_chg_data *data = charger_get_data(chgdev);
|
|
|
|
dev_info(data->dev, "%s: ++\n", __func__);
|
|
|
|
/* Set WDT 40s */
|
|
return regmap_field_write(data->rm_field[F_WATCHDOG], 5);
|
|
}
|
|
|
|
static int rt9490_plug_out(struct charger_device *chgdev)
|
|
{
|
|
struct rt9490_chg_data *data = charger_get_data(chgdev);
|
|
uint32_t value = 0;
|
|
int ret;
|
|
|
|
dev_info(data->dev, "%s: ++\n", __func__);
|
|
atomic_set(&data->aicc_once, 0);
|
|
ret = regmap_field_write(data->rm_field[F_EN_AICC], 0);
|
|
if (ret) {
|
|
dev_info(data->dev, "Failed to disable aicc\n");
|
|
return ret;
|
|
}
|
|
regmap_field_write(data->rm_field[F_WATCHDOG], 0);
|
|
if (ret) {
|
|
dev_info(data->dev, "Failed to disable watchdog\n");
|
|
return ret;
|
|
}
|
|
return rt9490_psy_prop_ops(data, true, POWER_SUPPLY_PROP_ONLINE,
|
|
&value);
|
|
}
|
|
|
|
static int rt9490_is_charging_enable(struct charger_device *chgdev, bool *en)
|
|
{
|
|
struct rt9490_chg_data *data = charger_get_data(chgdev);
|
|
unsigned int val;
|
|
int ret;
|
|
|
|
dev_info(data->dev, "%s: ++\n", __func__);
|
|
ret = regmap_field_read(data->rm_field[F_EN_CHG], &val);
|
|
if (ret)
|
|
return ret;
|
|
|
|
*en = val;
|
|
return 0;
|
|
}
|
|
|
|
static int rt9490_init_chip(struct charger_device *chg_dev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int rt9490_kick_wdt(struct charger_device *chgdev)
|
|
{
|
|
struct rt9490_chg_data *data = charger_get_data(chgdev);
|
|
|
|
dev_info(data->dev, "%s: ++\n", __func__);
|
|
/* Trigger watchdog time reset */
|
|
return regmap_field_write(data->rm_field[F_WDRST], 1);
|
|
}
|
|
|
|
#define DUMP_REG_BUF_SIZE 1024
|
|
static int rt9490_dump_registers(struct charger_device *chgdev)
|
|
{
|
|
struct rt9490_chg_data *data = charger_get_data(chgdev);
|
|
int ret, i;
|
|
u32 val;
|
|
char buf[DUMP_REG_BUF_SIZE] = "\0";
|
|
static const struct {
|
|
const char *name;
|
|
const char *unit;
|
|
enum power_supply_property psp;
|
|
} settings[] = {
|
|
{ .psp = POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
|
|
.name = "ICHG", .unit = "mA" },
|
|
{ .psp = POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
|
|
.name = "AICR", .unit = "mA" },
|
|
{ .psp = POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT,
|
|
.name = "MIVR", .unit = "mV" },
|
|
{ .psp = POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
|
|
.name = "IEOC", .unit = "mA" },
|
|
{ .psp = POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
|
|
.name = "CV", .unit = "mV" },
|
|
};
|
|
static const struct {
|
|
const char *name;
|
|
const char *unit;
|
|
enum rt9490_adc_chan chan;
|
|
} adcs[] = {
|
|
{ .chan = RT9490_ADC_VBUS, .name = "VBUS", .unit = "mV" },
|
|
{ .chan = RT9490_ADC_IBUS, .name = "IBUS", .unit = "mA" },
|
|
{ .chan = RT9490_ADC_VBAT, .name = "VBAT", .unit = "mV" },
|
|
{ .chan = RT9490_ADC_IBAT, .name = "IBAT", .unit = "mA" },
|
|
{ .chan = RT9490_ADC_VSYS, .name = "VSYS", .unit = "mV" },
|
|
};
|
|
static const struct {
|
|
const u8 reg;
|
|
const char *name;
|
|
} regs[] = {
|
|
{ .reg = RT9490_REG_CHG_CTRL0, .name = "CHG_CTRL0" },
|
|
{ .reg = RT9490_REG_CHG_CTRL1, .name = "CHG_CTRL1" },
|
|
{ .reg = RT9490_REG_CHG_STAT0, .name = "CHG_STAT0" },
|
|
{ .reg = RT9490_REG_CHG_STAT1, .name = "CHG_STAT1" },
|
|
};
|
|
|
|
for (i = 0; i < ARRAY_SIZE(settings); i++) {
|
|
ret = rt9490_psy_prop_ops(data, false, settings[i].psp, &val);
|
|
if (ret) {
|
|
dev_err(data->dev, "failed to get %s\n",
|
|
settings[i].name);
|
|
return ret;
|
|
}
|
|
val /= 1000;
|
|
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(data->adc_chans[adcs[i].chan],
|
|
&val);
|
|
if (ret) {
|
|
dev_err(data->dev, "failed to read adc %s\n",
|
|
adcs[i].name);
|
|
return ret;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(regs); i++) {
|
|
ret = regmap_read(data->regmap, regs[i].reg, &val);
|
|
if (ret) {
|
|
dev_err(data->dev, "failed to get %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(data->dev, "%s %s", __func__, buf);
|
|
|
|
ret = rt9490_kick_wdt(chgdev);
|
|
if (ret)
|
|
dev_info(data->dev, "Failed to kick watchdog\n");
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int rt9490_enable_charging(struct charger_device *chgdev, bool en)
|
|
{
|
|
struct rt9490_chg_data *data = charger_get_data(chgdev);
|
|
|
|
dev_info(data->dev, "%s: en = %d\n", __func__, en);
|
|
if (data->ceb_gpio)
|
|
gpiod_set_value(data->ceb_gpio, !en);
|
|
mdelay(1);
|
|
return regmap_field_write(data->rm_field[F_EN_CHG], en);
|
|
}
|
|
|
|
static int rt9490_get_ichg(struct charger_device *chgdev, uint32_t *ichg)
|
|
{
|
|
struct rt9490_chg_data *data = charger_get_data(chgdev);
|
|
|
|
dev_info(data->dev, "%s: ++\n", __func__);
|
|
return rt9490_psy_prop_ops(data, false,
|
|
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
|
|
ichg);
|
|
}
|
|
|
|
static int rt9490_set_ichg(struct charger_device *chgdev, uint32_t ichg)
|
|
{
|
|
struct rt9490_chg_data *data = charger_get_data(chgdev);
|
|
|
|
dev_info(data->dev, "%s: ichg = %d\n", __func__, ichg);
|
|
return rt9490_psy_prop_ops(data, true,
|
|
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
|
|
&ichg);
|
|
}
|
|
|
|
static int rt9490_get_aicr(struct charger_device *chgdev, uint32_t *aicr)
|
|
{
|
|
struct rt9490_chg_data *data = charger_get_data(chgdev);
|
|
|
|
dev_info(data->dev, "%s: ++\n", __func__);
|
|
return rt9490_psy_prop_ops(data, false,
|
|
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, aicr);
|
|
}
|
|
|
|
static int rt9490_set_aicr(struct charger_device *chgdev, uint32_t aicr)
|
|
{
|
|
struct rt9490_chg_data *data = charger_get_data(chgdev);
|
|
|
|
dev_info(data->dev, "%s: aicr = %d\n", __func__, aicr);
|
|
return rt9490_psy_prop_ops(data, true,
|
|
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
|
|
&aicr);
|
|
}
|
|
|
|
static int rt9490_get_min_aicr(struct charger_device *chgdev, u32 *uA)
|
|
{
|
|
*uA = 100000;
|
|
return 0;
|
|
}
|
|
|
|
static int rt9490_get_cv(struct charger_device *chgdev, uint32_t *cv)
|
|
{
|
|
struct rt9490_chg_data *data = charger_get_data(chgdev);
|
|
|
|
dev_info(data->dev, "%s: ++\n", __func__);
|
|
return rt9490_psy_prop_ops(data, false,
|
|
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
|
|
cv);
|
|
}
|
|
|
|
static int rt9490_set_cv(struct charger_device *chgdev, uint32_t cv)
|
|
{
|
|
struct rt9490_chg_data *data = charger_get_data(chgdev);
|
|
|
|
dev_info(data->dev, "%s: cv = %d\n", __func__, cv);
|
|
return rt9490_psy_prop_ops(data, true,
|
|
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
|
|
&cv);
|
|
}
|
|
|
|
static int rt9490_set_mivr(struct charger_device *chgdev, uint32_t mivr)
|
|
{
|
|
struct rt9490_chg_data *data = charger_get_data(chgdev);
|
|
|
|
dev_info(data->dev, "%s: mivr = %d\n", __func__, mivr);
|
|
return rt9490_psy_prop_ops(data, true,
|
|
POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT,
|
|
&mivr);
|
|
}
|
|
|
|
static int rt9490_get_mivr(struct charger_device *chgdev, uint32_t *mivr)
|
|
{
|
|
struct rt9490_chg_data *data = charger_get_data(chgdev);
|
|
|
|
dev_info(data->dev, "%s: ++\n", __func__);
|
|
return rt9490_psy_prop_ops(data, false,
|
|
POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT, mivr);
|
|
}
|
|
|
|
static int rt9490_is_charging_done(struct charger_device *chgdev, bool *done)
|
|
{
|
|
struct rt9490_chg_data *data = charger_get_data(chgdev);
|
|
union power_supply_propval val;
|
|
int ret;
|
|
|
|
ret = power_supply_get_property(data->psy,
|
|
POWER_SUPPLY_PROP_STATUS, &val);
|
|
if (ret) {
|
|
dev_err(data->dev, "%s: Failed to get chg status\n", __func__);
|
|
return ret;
|
|
}
|
|
|
|
*done = val.intval == POWER_SUPPLY_STATUS_FULL;
|
|
dev_info(data->dev, "%s: done = %d\n", __func__, *done);
|
|
return ret;
|
|
}
|
|
|
|
static int rt9490_get_min_ichg(struct charger_device *chgdev, uint32_t *uA)
|
|
{
|
|
struct rt9490_chg_data *data = charger_get_data(chgdev);
|
|
|
|
dev_info(data->dev, "%s: ++\n", __func__);
|
|
*uA = RT9490_ICHG_MINUA;
|
|
return 0;
|
|
}
|
|
|
|
static int rt9490_set_ieoc(struct charger_device *chgdev, uint32_t ieoc)
|
|
{
|
|
struct rt9490_chg_data *data = charger_get_data(chgdev);
|
|
|
|
dev_info(data->dev, "%s: ieoc = %d\n", __func__, ieoc);
|
|
return rt9490_psy_prop_ops(data, true,
|
|
POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
|
|
&ieoc);
|
|
}
|
|
|
|
static int rt9490_get_ieoc(struct charger_device *chgdev, uint32_t *ieoc)
|
|
{
|
|
struct rt9490_chg_data *data = charger_get_data(chgdev);
|
|
|
|
dev_info(data->dev, "%s: ++\n", __func__);
|
|
return rt9490_psy_prop_ops(data, false,
|
|
POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, ieoc);
|
|
}
|
|
|
|
static int rt9490_enable_te(struct charger_device *chgdev, bool en)
|
|
{
|
|
struct rt9490_chg_data *data = charger_get_data(chgdev);
|
|
|
|
dev_info(data->dev, "%s: en = %d\n", __func__, en);
|
|
return regmap_field_write(data->rm_field[F_EN_TE], en);
|
|
}
|
|
|
|
static int rt9490_reset_eoc_state(struct charger_device *chgdev)
|
|
{
|
|
struct rt9490_chg_data *data = charger_get_data(chgdev);
|
|
|
|
dev_info(data->dev, "%s: ++\n", __func__);
|
|
return regmap_field_write(data->rm_field[F_EOC_RST], 1);
|
|
}
|
|
|
|
static int rt9490_enable_hz(struct charger_device *chgdev, bool en)
|
|
{
|
|
struct rt9490_chg_data *data = charger_get_data(chgdev);
|
|
|
|
dev_info(data->dev, "%s: en = %d\n", __func__, en);
|
|
return regmap_field_write(data->rm_field[F_EN_HZ], en);
|
|
}
|
|
|
|
static int rt9490_set_vac_ovp(struct charger_device *chgdev, u32 uV)
|
|
{
|
|
struct rt9490_chg_data *data = charger_get_data(chgdev);
|
|
|
|
dev_info(data->dev, "%s: ovp_lvl = %d", __func__, uV);
|
|
if (uV < 7000000)
|
|
return regmap_field_write(data->rm_field[F_VACOVP], 0x3);
|
|
else if (uV >= 7000000 && uV < 12000000)
|
|
return regmap_field_write(data->rm_field[F_VACOVP], 0x2);
|
|
else if (uV >= 12000000 && uV < 22000000)
|
|
return regmap_field_write(data->rm_field[F_VACOVP], 0x1);
|
|
else
|
|
return regmap_field_write(data->rm_field[F_VACOVP], 0x0);
|
|
}
|
|
|
|
static int rt9490_enable_safety_timer(struct charger_device *chgdev, bool en)
|
|
{
|
|
struct rt9490_chg_data *data = charger_get_data(chgdev);
|
|
|
|
dev_info(data->dev, "%s: en = %d\n", __func__, en);
|
|
return regmap_field_write(data->rm_field[F_EN_FST_TMR], en);
|
|
}
|
|
|
|
static int rt9490_is_safety_timer_enable(
|
|
struct charger_device *chgdev, bool *en)
|
|
{
|
|
struct rt9490_chg_data *data = charger_get_data(chgdev);
|
|
unsigned int val;
|
|
int ret;
|
|
|
|
dev_info(data->dev, "%s: ++\n", __func__);
|
|
ret = regmap_field_read(data->rm_field[F_EN_FST_TMR], &val);
|
|
if (ret)
|
|
return ret;
|
|
|
|
*en = val;
|
|
return 0;
|
|
}
|
|
|
|
static int rt9490_enable_power_path(struct charger_device *chgdev, bool en)
|
|
{
|
|
struct rt9490_chg_data *data = charger_get_data(chgdev);
|
|
|
|
dev_info(data->dev, "%s: en = %d\n", __func__, en);
|
|
return rt9490_enable_hz(chgdev, !en);
|
|
}
|
|
|
|
static int rt9490_is_power_path_enable(struct charger_device *chgdev, bool *en)
|
|
{
|
|
struct rt9490_chg_data *data = charger_get_data(chgdev);
|
|
unsigned int val;
|
|
int ret;
|
|
|
|
dev_info(data->dev, "%s: ++\n", __func__);
|
|
ret = regmap_field_read(data->rm_field[F_EN_HZ], &val);
|
|
if (ret)
|
|
return ret;
|
|
|
|
*en = !val;
|
|
return 0;
|
|
}
|
|
|
|
static int rt9490_enable_otg(struct charger_device *chgdev, bool en)
|
|
{
|
|
struct rt9490_chg_data *data = charger_get_data(chgdev);
|
|
struct regulator *regulator;
|
|
int ret;
|
|
|
|
dev_info(data->dev, "%s: en = %d\n", __func__, en);
|
|
regulator = devm_regulator_get(data->dev, "rt9490-otg-vbus");
|
|
if (IS_ERR(regulator)) {
|
|
dev_err(data->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 rt9490_set_otg_cc(struct charger_device *chgdev, u32 uA)
|
|
{
|
|
int ret;
|
|
struct regulator *regulator;
|
|
struct rt9490_chg_data *data = charger_get_data(chgdev);
|
|
|
|
dev_info(data->dev, "uA = %d\n", uA);
|
|
regulator = devm_regulator_get(data->dev, "rt9490-otg-vbus");
|
|
if (IS_ERR(regulator)) {
|
|
dev_err(data->dev, "failed to get otg regulator\n");
|
|
return PTR_ERR(regulator);
|
|
}
|
|
ret = regulator_set_current_limit(regulator, uA, uA);
|
|
devm_regulator_put(regulator);
|
|
return ret;
|
|
}
|
|
|
|
static int rt9490_run_pe(struct rt9490_chg_data *data, bool pe20)
|
|
{
|
|
int ret = 0;
|
|
unsigned long timeout = pe20 ? 1400 : 2800;
|
|
uint32_t value;
|
|
unsigned int pumpx_en;
|
|
|
|
value = 800000;
|
|
ret = rt9490_psy_prop_ops(data, true,
|
|
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, &value);
|
|
if (ret)
|
|
return ret;
|
|
|
|
value = 2000000;
|
|
ret = rt9490_psy_prop_ops(data, true,
|
|
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, &value);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = regmap_field_write(data->rm_field[F_EN_CHG], 1);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = regmap_field_write(data->rm_field[F_PE_SEL], pe20);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = regmap_field_write(data->rm_field[F_PE_EN], 1);
|
|
if (ret)
|
|
return ret;
|
|
|
|
msleep(timeout);
|
|
|
|
/* Each round wait 50ms, timeout 1000ms */
|
|
ret = regmap_field_read_poll_timeout(data->rm_field[F_PE_EN], pumpx_en,
|
|
!pumpx_en, 50000, 1000000);
|
|
if (ret)
|
|
dev_err(data->dev, "Failed to wait pe (%d)\n", ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int rt9490_set_pep_current_pattern(struct charger_device *chgdev,
|
|
bool inc)
|
|
{
|
|
struct rt9490_chg_data *data = charger_get_data(chgdev);
|
|
int ret;
|
|
|
|
dev_info(data->dev, "%s: inc = %d\n", __func__, inc);
|
|
mutex_lock(&data->pe_lock);
|
|
ret = regmap_field_write(data->rm_field[F_PE10_INC], inc);
|
|
if (ret) {
|
|
dev_err(data->dev, "Failed to set pe10 up/down\n");
|
|
goto out;
|
|
}
|
|
ret = rt9490_run_pe(data, false);
|
|
out:
|
|
mutex_unlock(&data->pe_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int rt9490_set_pep20_efficiency_table(struct charger_device *chgdev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int rt9490_set_pep20_current_pattern(struct charger_device *chgdev,
|
|
uint32_t uV)
|
|
{
|
|
struct rt9490_chg_data *data = charger_get_data(chgdev);
|
|
unsigned int sel = 0;
|
|
int ret;
|
|
|
|
dev_info(data->dev, "%s: pe20 = %d\n", __func__, uV);
|
|
mutex_lock(&data->pe_lock);
|
|
|
|
linear_range_get_selector_within(rt9490_ranges + RT9490_RANGE_PE20_CODE,
|
|
uV, &sel);
|
|
ret = regmap_field_write(data->rm_field[F_PE20_CODE], sel);
|
|
if (ret) {
|
|
dev_err(data->dev, "Failed to set pe20 code\n");
|
|
goto out;
|
|
}
|
|
|
|
ret = rt9490_run_pe(data, true);
|
|
out:
|
|
mutex_unlock(&data->pe_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int rt9490_set_pep20_reset(struct charger_device *chgdev)
|
|
{
|
|
struct rt9490_chg_data *data = charger_get_data(chgdev);
|
|
uint32_t value;
|
|
int ret;
|
|
|
|
dev_info(data->dev, "++\n");
|
|
mutex_lock(&data->pe_lock);
|
|
|
|
value = 4600000;
|
|
ret = rt9490_psy_prop_ops(data, true,
|
|
POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT, &value);
|
|
if (ret)
|
|
goto out;
|
|
|
|
value = 100000;
|
|
ret = rt9490_psy_prop_ops(data, true,
|
|
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, &value);
|
|
if (ret)
|
|
goto out;
|
|
|
|
msleep(250);
|
|
|
|
value = 500000;
|
|
ret = rt9490_psy_prop_ops(data, true,
|
|
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, &value);
|
|
out:
|
|
mutex_unlock(&data->pe_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int rt9490_get_tchg(struct charger_device *chgdev, int *tmin, int *tmax)
|
|
{
|
|
struct rt9490_chg_data *data = charger_get_data(chgdev);
|
|
int adc_val, ret;
|
|
|
|
ret = iio_read_channel_processed(data->adc_chans[RT9490_ADC_TDIE],
|
|
&adc_val);
|
|
if (ret)
|
|
return ret;
|
|
|
|
dev_info(data->dev, "tchg = %d\n", adc_val);
|
|
*tmin = *tmax = adc_val;
|
|
return ret;
|
|
}
|
|
|
|
static int rt9490_get_adc(struct charger_device *chgdev, enum adc_channel chan,
|
|
int *min, int *max)
|
|
{
|
|
struct rt9490_chg_data *data = charger_get_data(chgdev);
|
|
enum rt9490_adc_chan adc_chan;
|
|
int adc_val, ret;
|
|
|
|
switch (chan) {
|
|
case ADC_CHANNEL_VBUS:
|
|
adc_chan = RT9490_ADC_VBUS;
|
|
break;
|
|
case ADC_CHANNEL_VSYS:
|
|
adc_chan = RT9490_ADC_VSYS;
|
|
break;
|
|
case ADC_CHANNEL_VBAT:
|
|
adc_chan = RT9490_ADC_VBAT;
|
|
break;
|
|
case ADC_CHANNEL_IBUS:
|
|
adc_chan = RT9490_ADC_IBUS;
|
|
break;
|
|
case ADC_CHANNEL_IBAT:
|
|
adc_chan = RT9490_ADC_IBAT;
|
|
break;
|
|
case ADC_CHANNEL_TEMP_JC:
|
|
adc_chan = RT9490_ADC_TDIE;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = iio_read_channel_processed(data->adc_chans[adc_chan], &adc_val);
|
|
if (ret) {
|
|
dev_err(data->dev, "Failed to read adc\n");
|
|
return ret;
|
|
}
|
|
|
|
*max = *min = adc_val * 1000;
|
|
return 0;
|
|
}
|
|
|
|
static int rt9490_get_vbus(struct charger_device *chgdev, uint32_t *vbus)
|
|
{
|
|
return rt9490_get_adc(chgdev, ADC_CHANNEL_VBUS, vbus, vbus);
|
|
}
|
|
|
|
static int rt9490_get_ibat(struct charger_device *chgdev, uint32_t *ibat)
|
|
{
|
|
return rt9490_get_adc(chgdev, ADC_CHANNEL_IBAT, ibat, ibat);
|
|
}
|
|
|
|
static int rt9490_get_ibus(struct charger_device *chgdev, uint32_t *ibus)
|
|
{
|
|
return rt9490_get_adc(chgdev, ADC_CHANNEL_IBUS, ibus, ibus);
|
|
}
|
|
|
|
static const struct charger_properties rt9490_chg_props = {
|
|
.alias_name = "rt9490_chg",
|
|
};
|
|
|
|
static int rt9490_do_event(struct charger_device *chgdev,
|
|
uint32_t event, uint32_t args)
|
|
{
|
|
struct rt9490_chg_data *data = charger_get_data(chgdev);
|
|
|
|
switch (event) {
|
|
case EVENT_FULL:
|
|
case EVENT_RECHARGE:
|
|
case EVENT_DISCHARGE:
|
|
power_supply_changed(data->psy);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int rt9490_get_mivr_state(struct charger_device *chgdev, bool *active)
|
|
{
|
|
int ret;
|
|
unsigned int val;
|
|
struct rt9490_chg_data *data = charger_get_data(chgdev);
|
|
|
|
ret = regmap_field_read(data->rm_field[F_MIVR_STAT], &val);
|
|
if (ret)
|
|
return ret;
|
|
*active = val;
|
|
return 0;
|
|
}
|
|
|
|
static int rt9490_charger_get_aicc(struct rt9490_chg_data *data, u32 *val)
|
|
{
|
|
unsigned int value;
|
|
int ret;
|
|
|
|
ret = rt9490_get_be16_selector_to_value(data, RT9490_REG_AICC_CTRL,
|
|
RT9490_RANGE_AICR, &value);
|
|
if (ret)
|
|
return ret;
|
|
|
|
*val = value;
|
|
return 0;
|
|
}
|
|
|
|
static int rt9490_run_aicc(struct charger_device *chgdev, u32 *uA)
|
|
{
|
|
int ret;
|
|
bool active = false;
|
|
struct rt9490_chg_data *data = charger_get_data(chgdev);
|
|
long ret_comp;
|
|
unsigned int aicc_stat = 0;
|
|
u32 val = 0;
|
|
|
|
ret = rt9490_get_mivr_state(chgdev, &active);
|
|
if (ret)
|
|
return ret;
|
|
if (!active) {
|
|
dev_notice(data->dev, "mivr loop is not active\n");
|
|
return 0;
|
|
}
|
|
|
|
mutex_lock(&data->pe_lock);
|
|
if (!atomic_read(&data->aicc_once)) {
|
|
atomic_set(&data->aicc_once, 1);
|
|
ret = regmap_field_write(data->rm_field[F_EN_AICC], 1);
|
|
if (ret)
|
|
goto out;
|
|
} else {
|
|
ret = regmap_field_write(data->rm_field[F_FORCE_AICC], 1);
|
|
if (ret)
|
|
goto out;
|
|
}
|
|
reinit_completion(&data->aicc_done);
|
|
|
|
ret_comp = wait_for_completion_interruptible_timeout(&data->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(data->dev, "failed to wait aicc(%d)\n", ret);
|
|
goto out;
|
|
}
|
|
|
|
ret = regmap_field_read(data->rm_field[F_AICC_STAT], &aicc_stat);
|
|
if (ret)
|
|
goto out;
|
|
|
|
/* maximum input current detected */
|
|
if (aicc_stat == 2) {
|
|
ret = rt9490_charger_get_aicc(data, &val);
|
|
if (ret)
|
|
goto out;
|
|
*uA = val;
|
|
}
|
|
dev_info(data->dev, "%s IAICC = 0x%04X\n", __func__, val);
|
|
|
|
out:
|
|
mutex_unlock(&data->pe_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int rt9490_enable_chg_type_det(struct charger_device *chgdev, bool en)
|
|
{
|
|
struct rt9490_chg_data *data = charger_get_data(chgdev);
|
|
int attach = en ? ATTACH_TYPE_TYPEC : ATTACH_TYPE_NONE;
|
|
|
|
dev_info(data->dev, "%s en=d\n", __func__, en);
|
|
mutex_lock(&data->lock);
|
|
rt9490_chg_attach_pre_process(data, ATTACH_TRIG_TYPEC, attach);
|
|
mutex_unlock(&data->lock);
|
|
return 0;
|
|
}
|
|
|
|
static const struct charger_ops rt9490_chg_ops = {
|
|
/* Normal charging */
|
|
.plug_in = rt9490_plug_in,
|
|
.plug_out = rt9490_plug_out,
|
|
.dump_registers = rt9490_dump_registers,
|
|
.enable = rt9490_enable_charging,
|
|
.is_enabled = rt9490_is_charging_enable,
|
|
.init_chip = rt9490_init_chip,
|
|
.get_charging_current = rt9490_get_ichg,
|
|
.set_charging_current = rt9490_set_ichg,
|
|
.get_input_current = rt9490_get_aicr,
|
|
.set_input_current = rt9490_set_aicr,
|
|
.get_min_input_current = rt9490_get_min_aicr,
|
|
.get_constant_voltage = rt9490_get_cv,
|
|
.set_constant_voltage = rt9490_set_cv,
|
|
.kick_wdt = rt9490_kick_wdt,
|
|
.set_mivr = rt9490_set_mivr,
|
|
.get_mivr = rt9490_get_mivr,
|
|
.get_mivr_state = rt9490_get_mivr_state,
|
|
.is_charging_done = rt9490_is_charging_done,
|
|
.get_min_charging_current = rt9490_get_min_ichg,
|
|
.set_eoc_current = rt9490_set_ieoc,
|
|
.get_eoc_current = rt9490_get_ieoc,
|
|
.enable_termination = rt9490_enable_te,
|
|
.run_aicl = rt9490_run_aicc,
|
|
.reset_eoc_state = rt9490_reset_eoc_state,
|
|
.enable_hz = rt9490_enable_hz,
|
|
.set_vac_ovp = rt9490_set_vac_ovp,
|
|
|
|
/* Safety timer */
|
|
.enable_safety_timer = rt9490_enable_safety_timer,
|
|
.is_safety_timer_enabled = rt9490_is_safety_timer_enable,
|
|
|
|
/* Power path */
|
|
.enable_powerpath = rt9490_enable_power_path,
|
|
.is_powerpath_enabled = rt9490_is_power_path_enable,
|
|
|
|
/* OTG */
|
|
.enable_otg = rt9490_enable_otg,
|
|
.set_boost_current_limit = rt9490_set_otg_cc,
|
|
|
|
/* PE+/PE+20 */
|
|
.send_ta_current_pattern = rt9490_set_pep_current_pattern,
|
|
.set_pe20_efficiency_table = rt9490_set_pep20_efficiency_table,
|
|
.send_ta20_current_pattern = rt9490_set_pep20_current_pattern,
|
|
.reset_ta = rt9490_set_pep20_reset,
|
|
|
|
/* ADC */
|
|
.get_tchg_adc = rt9490_get_tchg,
|
|
.get_adc = rt9490_get_adc,
|
|
.get_vbus_adc = rt9490_get_vbus,
|
|
.get_ibat_adc = rt9490_get_ibat,
|
|
.get_ibus_adc = rt9490_get_ibus,
|
|
|
|
/* charger type detection */
|
|
.enable_chg_type_det = rt9490_enable_chg_type_det,
|
|
|
|
/* Event */
|
|
.event = rt9490_do_event,
|
|
};
|
|
|
|
static int rt9490_otg_set_voltage_sel(struct regulator_dev *rdev,
|
|
unsigned int selector)
|
|
{
|
|
struct rt9490_chg_data *data = rdev_get_drvdata(rdev);
|
|
const struct regulator_desc *desc = rdev->desc;
|
|
__be16 be16_sel = cpu_to_be16(selector);
|
|
|
|
return regmap_raw_write(data->regmap, desc->vsel_reg, &be16_sel,
|
|
sizeof(be16_sel));
|
|
}
|
|
|
|
static int rt9490_otg_get_voltage_sel(struct regulator_dev *rdev)
|
|
{
|
|
struct rt9490_chg_data *data = rdev_get_drvdata(rdev);
|
|
const struct regulator_desc *desc = rdev->desc;
|
|
__be16 be16_sel;
|
|
int ret;
|
|
|
|
ret = regmap_raw_read(data->regmap, desc->vsel_reg, &be16_sel,
|
|
sizeof(be16_sel));
|
|
if (ret)
|
|
return ret;
|
|
|
|
return be16_to_cpu(be16_sel);
|
|
}
|
|
|
|
static int rt9490_otg_set_current_limit(struct regulator_dev *rdev, int min_uA,
|
|
int max_uA)
|
|
{
|
|
struct rt9490_chg_data *data = rdev_get_drvdata(rdev);
|
|
unsigned int sel = 0;
|
|
|
|
linear_range_get_selector_within(rt9490_ranges + RT9490_RANGE_IOTG,
|
|
max_uA, &sel);
|
|
return regmap_field_write(data->rm_field[F_IOTG], sel);
|
|
}
|
|
|
|
static int rt9490_otg_get_current_limit(struct regulator_dev *rdev)
|
|
{
|
|
struct rt9490_chg_data *data = rdev_get_drvdata(rdev);
|
|
unsigned int sel, cval;
|
|
int ret;
|
|
|
|
ret = regmap_field_read(data->rm_field[F_IOTG], &sel);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = linear_range_get_value(rt9490_ranges + RT9490_RANGE_IOTG, sel,
|
|
&cval);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return cval;
|
|
}
|
|
|
|
static int rt9490_otg_get_error_flags(struct regulator_dev *rdev,
|
|
unsigned int *flags)
|
|
{
|
|
struct rt9490_chg_data *data = rdev_get_drvdata(rdev);
|
|
unsigned int events = 0, lbp_status, fault_status;
|
|
int ret;
|
|
|
|
ret = regmap_read(data->regmap, RT9490_REG_CHG_STAT4, &lbp_status);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = regmap_read(data->regmap, RT9490_REG_FAULT_STAT1, &fault_status);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (lbp_status & RT9490_OTGLBP_MASK)
|
|
events |= REGULATOR_ERROR_FAIL;
|
|
|
|
if (fault_status & RT9490_OTGUVP_MASK)
|
|
events |= REGULATOR_ERROR_UNDER_VOLTAGE;
|
|
|
|
if (fault_status & RT9490_OTGOVP_MASK)
|
|
events |= REGULATOR_ERROR_REGULATION_OUT;
|
|
|
|
*flags = events;
|
|
return 0;
|
|
}
|
|
|
|
static const struct regulator_ops rt9490_otg_regulator_ops = {
|
|
.list_voltage = regulator_list_voltage_linear,
|
|
.set_voltage_sel = rt9490_otg_set_voltage_sel,
|
|
.get_voltage_sel = rt9490_otg_get_voltage_sel,
|
|
.set_current_limit = rt9490_otg_set_current_limit,
|
|
.get_current_limit = rt9490_otg_get_current_limit,
|
|
.enable = regulator_enable_regmap,
|
|
.disable = regulator_disable_regmap,
|
|
.is_enabled = regulator_is_enabled_regmap,
|
|
.get_error_flags = rt9490_otg_get_error_flags,
|
|
};
|
|
|
|
static const struct regulator_desc rt9490_otg_desc = {
|
|
.name = "otg-vbus",
|
|
.of_match = of_match_ptr("otg_vbus"),
|
|
.type = REGULATOR_VOLTAGE,
|
|
.owner = THIS_MODULE,
|
|
.min_uV = RT9490_OTG_MINUV,
|
|
.uV_step = RT9490_OTG_STEPUV,
|
|
.n_voltages = RT9490_OTG_N_VOLTAGES,
|
|
.ops = &rt9490_otg_regulator_ops,
|
|
.vsel_reg = RT9490_REG_VOTG,
|
|
.vsel_mask = GENMASK(10, 0),
|
|
.enable_reg = RT9490_REG_CHG_CTRL3,
|
|
.enable_mask = BIT(6),
|
|
};
|
|
|
|
static int rt9490_do_charger_init(struct rt9490_chg_data *data)
|
|
{
|
|
unsigned int vbus_ready, cv;
|
|
union power_supply_propval val;
|
|
int ret;
|
|
u32 tmp;
|
|
struct device_node *bc12_np, *boot_np, *np = data->dev->of_node;
|
|
const struct {
|
|
u32 size;
|
|
u32 tag;
|
|
u32 boot_mode;
|
|
u32 boot_type;
|
|
} *tag;
|
|
|
|
ret = device_property_read_u32(data->dev, "richtek,cv-microvolt", &cv);
|
|
if (!ret) {
|
|
val.intval = cv;
|
|
ret = rt9490_charger_set_cv(data, &val);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
/* mediatek chgdev name */
|
|
ret = device_property_read_string(data->dev, "chg_name",
|
|
&data->chg_name);
|
|
if (ret) {
|
|
dev_notice(data->dev, "failed to get chg_name\n");
|
|
data->chg_name = "primary_chg";
|
|
}
|
|
|
|
/*
|
|
* 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(data->dev, "failed to get bc12_sel phandle\n");
|
|
return -ENODEV;
|
|
}
|
|
if (of_property_read_u32(bc12_np, "bc12_sel", &tmp) < 0) {
|
|
dev_err(data->dev, "property bc12_sel not found\n");
|
|
return -EINVAL;
|
|
}
|
|
if (tmp != 0)
|
|
data->attach_trig = ATTACH_TRIG_IGNORE;
|
|
else if (IS_ENABLED(CONFIG_TCPC_CLASS))
|
|
data->attach_trig = ATTACH_TRIG_TYPEC;
|
|
else
|
|
data->attach_trig = ATTACH_TRIG_PWR_RDY;
|
|
|
|
ret = regmap_field_write(data->rm_field[F_WATCHDOG], 0);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* mediatek boot mode */
|
|
boot_np = of_parse_phandle(np, "boot_mode", 0);
|
|
if (!boot_np) {
|
|
dev_err(data->dev, "failed to get bootmode phandle\n");
|
|
return -ENODEV;
|
|
}
|
|
tag = of_get_property(boot_np, "atag,boot", NULL);
|
|
if (!tag) {
|
|
dev_err(data->dev, "failed to get atag,boot\n");
|
|
return -EINVAL;
|
|
}
|
|
dev_info(data->dev, "sz:0x%x tag:0x%x mode:0x%x type:0x%x\n",
|
|
tag->size, tag->tag, tag->boot_mode, tag->boot_type);
|
|
data->boot_mode = tag->boot_mode;
|
|
data->boot_type = tag->boot_type;
|
|
/* set aicr = 200mA in 1:META_BOOT 5:ADVMETA_BOOT */
|
|
if (data->boot_mode == 1 || data->boot_mode == 5)
|
|
val.intval = 200000;
|
|
else
|
|
/* 500mA is the minimum input current to fit all types of charger */
|
|
val.intval = 500000;
|
|
ret = rt9490_charger_set_aicr(data, &val);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* IC default 3000mA, per 10mA need 16uS, need 4000uS for rampping*/
|
|
usleep_range(RT9490_AICR_WAITUS, RT9490_AICR_WAITUS + 1000);
|
|
|
|
/* After AICR internal rampping time to disable ILIM */
|
|
ret = regmap_field_write(data->rm_field[F_EN_ILIM], 0);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = regmap_field_write(data->rm_field[F_VACOVP], 0);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = regmap_field_read(data->rm_field[F_VAC1_PG], &vbus_ready);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = regmap_field_write(data->rm_field[F_EN_BC12], 0);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* F_TD_EOC = 0: for E2 sample */
|
|
ret = regmap_field_write(data->rm_field[F_TD_EOC], 0);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Disable Special TA detecion */
|
|
ret = regmap_field_write(data->rm_field[F_SPEC_TA_EN], 0);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (data->vbus_ready == vbus_ready)
|
|
return 0;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int rt9490_get_all_adcs(struct rt9490_chg_data *data)
|
|
{
|
|
const char *chan_names[RT9490_ADC_MAX] = {
|
|
"TDIE", "TS", "VSYS", "VBAT", "VBUS", "IBAT",
|
|
"IBUS", "VAC1", "VAC2", "DM", "DP" };
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(chan_names); i++) {
|
|
data->adc_chans[i] =
|
|
devm_iio_channel_get(data->dev, chan_names[i]);
|
|
if (IS_ERR(data->adc_chans[i])) {
|
|
dev_err(data->dev, "Failed to %s adc\n", chan_names[i]);
|
|
return PTR_ERR(data->adc_chans[i]);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rt9490_chg_set_usbsw(struct rt9490_chg_data *data,
|
|
enum rt9490_usbsw usbsw)
|
|
{
|
|
struct phy *phy;
|
|
int ret, mode = (usbsw == USBSW_CHG) ? PHY_MODE_BC11_SET :
|
|
PHY_MODE_BC11_CLR;
|
|
|
|
dev_info(data->dev, "usbsw=%d\n", usbsw);
|
|
phy = phy_get(data->dev, "usb2-phy");
|
|
if (IS_ERR_OR_NULL(phy)) {
|
|
dev_err(data->dev, "Failed to get usb2-phy");
|
|
return -ENODEV;
|
|
}
|
|
ret = phy_set_mode_ext(phy, PHY_MODE_USB_DEVICE, mode);
|
|
if (ret)
|
|
dev_err(data->dev, "Failed to set phy ext mode\n");
|
|
phy_put(data->dev, phy);
|
|
return ret;
|
|
}
|
|
|
|
static bool is_usb_rdy(struct device *dev)
|
|
{
|
|
bool rdy = true;
|
|
struct device_node *node;
|
|
|
|
node = of_parse_phandle(dev->of_node, "usb", 0);
|
|
if (node) {
|
|
rdy = !of_property_read_bool(node, "cdp-block");
|
|
dev_info(dev, "usb ready = %d\n", rdy);
|
|
} else
|
|
dev_warn(dev, "usb node missing or invalid\n");
|
|
return rdy;
|
|
}
|
|
|
|
static int rt9490_chg_enable_bc12(struct rt9490_chg_data *data, bool en)
|
|
{
|
|
int i, ret, attach;
|
|
static const int max_wait_cnt = 250;
|
|
|
|
dev_info(data->dev, "%s en=%d\n", __func__, en);
|
|
if (en) {
|
|
/* set aicr = 100mA */
|
|
ret = rt9490_set_aicr(data->chgdev, 100000);
|
|
if (ret)
|
|
return ret;
|
|
/* disable HZ */
|
|
ret = rt9490_enable_hz(data->chgdev, false);
|
|
if (ret)
|
|
return ret;
|
|
/* CDP port specific process */
|
|
dev_info(data->dev, "check CDP block\n");
|
|
for (i = 0; i < max_wait_cnt; i++) {
|
|
if (is_usb_rdy(data->dev))
|
|
break;
|
|
attach = atomic_read(&data->attach);
|
|
if (attach == ATTACH_TYPE_TYPEC)
|
|
msleep(100);
|
|
else {
|
|
dev_notice(data->dev, "Change attach:%d, disable bc12\n",
|
|
attach);
|
|
en = false;
|
|
break;
|
|
}
|
|
}
|
|
if (i == max_wait_cnt)
|
|
dev_notice(data->dev, "CDP timeout\n");
|
|
else
|
|
dev_info(data->dev, "CDP free\n");
|
|
}
|
|
ret = rt9490_chg_set_usbsw(data, en ? USBSW_CHG : USBSW_USB);
|
|
if (ret)
|
|
return ret;
|
|
return regmap_field_write(data->rm_field[F_EN_BC12], en);
|
|
}
|
|
|
|
static void rt9490_chg_bc12_work_func(struct work_struct *work)
|
|
{
|
|
int ret, attach;
|
|
bool bc12_ctrl = true, bc12_en = false, rpt_psy = true;
|
|
struct rt9490_chg_data *data = container_of(work,
|
|
struct rt9490_chg_data,
|
|
bc12_work);
|
|
u32 result = 0;
|
|
|
|
mutex_lock(&data->lock);
|
|
attach = atomic_read(&data->attach);
|
|
if (attach > ATTACH_TYPE_NONE && data->boot_mode == 5) {
|
|
/* skip bc12 to speed up ADVMETA_BOOT */
|
|
dev_notice(data->dev, "Force SDP in meta mode\n");
|
|
data->chg_desc.type = POWER_SUPPLY_TYPE_USB;
|
|
data->usb_type = POWER_SUPPLY_USB_TYPE_SDP;
|
|
data->bc12_dn = false;
|
|
goto out;
|
|
}
|
|
|
|
switch (attach) {
|
|
case ATTACH_TYPE_NONE:
|
|
data->chg_desc.type = POWER_SUPPLY_TYPE_USB;
|
|
data->usb_type = POWER_SUPPLY_USB_TYPE_UNKNOWN;
|
|
goto out;
|
|
case ATTACH_TYPE_TYPEC:
|
|
dev_info(data->dev, "data_bc12_dn=%d\n", data->bc12_dn);
|
|
if (!data->bc12_dn) {
|
|
bc12_en = true;
|
|
rpt_psy = false;
|
|
goto out;
|
|
}
|
|
ret = regmap_field_read(data->rm_field[F_VBUS_STAT], &result);
|
|
if (ret) {
|
|
dev_err(data->dev, "Failed to get vbus stat\n");
|
|
rpt_psy = false;
|
|
goto out;
|
|
}
|
|
break;
|
|
case ATTACH_TYPE_PD_SDP:
|
|
result = RT9490_CABLE_USB_SDP;
|
|
break;
|
|
case ATTACH_TYPE_PD_DCP:
|
|
/* not to enable bc12 */
|
|
data->chg_desc.type = POWER_SUPPLY_TYPE_USB_DCP;
|
|
data->usb_type = POWER_SUPPLY_USB_TYPE_DCP;
|
|
goto out;
|
|
case ATTACH_TYPE_PD_NONSTD:
|
|
result = RT9490_CABLE_NSTD;
|
|
break;
|
|
default:
|
|
dev_info(data->dev, "Using traditional bc12 flow!\n");
|
|
break;
|
|
}
|
|
|
|
switch (result) {
|
|
case RT9490_CABLE_USB_SDP:
|
|
data->chg_desc.type = POWER_SUPPLY_TYPE_USB;
|
|
data->usb_type = POWER_SUPPLY_USB_TYPE_SDP;
|
|
break;
|
|
case RT9490_CABLE_NSTD:
|
|
data->chg_desc.type = POWER_SUPPLY_TYPE_USB;
|
|
data->usb_type = POWER_SUPPLY_USB_TYPE_DCP;
|
|
break;
|
|
case RT9490_CABLE_USB_CDP:
|
|
data->chg_desc.type = POWER_SUPPLY_TYPE_USB_CDP;
|
|
data->usb_type = POWER_SUPPLY_USB_TYPE_CDP;
|
|
break;
|
|
case RT9490_CABLE_USB_DCP:
|
|
data->chg_desc.type = POWER_SUPPLY_TYPE_USB_DCP;
|
|
data->usb_type = POWER_SUPPLY_USB_TYPE_DCP;
|
|
bc12_en = true;
|
|
break;
|
|
case RT9490_CABLE_UNKNOWN_DCP:
|
|
/* HVDCP case, please implement it */
|
|
data->chg_desc.type = POWER_SUPPLY_TYPE_USB_DCP;
|
|
data->usb_type = POWER_SUPPLY_USB_TYPE_DCP;
|
|
bc12_en = true;
|
|
break;
|
|
case RT9490_CABLE_APPLE_TA:
|
|
data->chg_desc.type = POWER_SUPPLY_TYPE_APPLE_BRICK_ID;
|
|
data->usb_type = POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID;
|
|
break;
|
|
default:
|
|
data->chg_desc.type = POWER_SUPPLY_TYPE_UNKNOWN;
|
|
data->usb_type = POWER_SUPPLY_USB_TYPE_UNKNOWN;
|
|
bc12_ctrl = false;
|
|
rpt_psy = false;
|
|
dev_info(data->dev, "Unknown port stat %d\n", result);
|
|
goto out;
|
|
}
|
|
dev_info(data->dev, "port stat = %s\n", rt9490_port_stat_names[result]);
|
|
out:
|
|
mutex_unlock(&data->lock);
|
|
if (bc12_ctrl && (rt9490_chg_enable_bc12(data, bc12_en) < 0))
|
|
dev_err(data->dev, "Failed to set bc12 = %d\n", bc12_en);
|
|
if (rpt_psy)
|
|
power_supply_changed(data->psy);
|
|
}
|
|
|
|
static irqreturn_t rt9490_vbus_ready_handler(int irqno, void *priv)
|
|
{
|
|
struct rt9490_chg_data *data = priv;
|
|
int ret;
|
|
|
|
dev_info(data->dev, "%s ++\n", __func__);
|
|
mutex_lock(&data->lock);
|
|
|
|
ret = regmap_field_read(data->rm_field[F_VAC1_PG],
|
|
&data->vbus_ready);
|
|
if (ret) {
|
|
mutex_unlock(&data->lock);
|
|
return IRQ_NONE;
|
|
}
|
|
|
|
rt9490_chg_attach_pre_process(data, ATTACH_TRIG_PWR_RDY,
|
|
data->vbus_ready ?
|
|
ATTACH_TYPE_PWR_RDY : ATTACH_TYPE_NONE);
|
|
mutex_unlock(&data->lock);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t rt9490_bc12_done_handler(int irqno, void *priv)
|
|
{
|
|
struct rt9490_chg_data *data = priv;
|
|
int attach;
|
|
|
|
dev_info(data->dev, "%s ++\n", __func__);
|
|
mutex_lock(&data->lock);
|
|
data->bc12_dn = true;
|
|
attach = atomic_read(&data->attach);
|
|
mutex_unlock(&data->lock);
|
|
if (attach < ATTACH_TYPE_PD && !queue_work(data->wq, &data->bc12_work))
|
|
dev_notice(data->dev, "%s bc12 work already queued\n",
|
|
__func__);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t rt9490_wdt_handler(int irqno, void *priv)
|
|
{
|
|
struct rt9490_chg_data *data = priv;
|
|
int ret;
|
|
|
|
dev_info(data->dev, "%s ++\n", __func__);
|
|
ret = regmap_field_write(data->rm_field[F_WDRST], 1);
|
|
if (ret)
|
|
dev_err(data->dev, "Failed to do watchdog reset\n");
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t rt9490_aicc_handler(int irqno, void *priv)
|
|
{
|
|
struct rt9490_chg_data *data = priv;
|
|
|
|
dev_info(data->dev, "%s ++\n", __func__);
|
|
complete(&data->aicc_done);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static const struct {
|
|
const char *irq_name;
|
|
irq_handler_t handler;
|
|
} rt9490_irqs[] = {
|
|
{ "vbus-rdy", rt9490_vbus_ready_handler },
|
|
{ "wdt", rt9490_wdt_handler },
|
|
{ "aicc", rt9490_aicc_handler },
|
|
{ "bc12-done", rt9490_bc12_done_handler },
|
|
};
|
|
|
|
static int rt9490_set_shipping_mode(struct rt9490_chg_data *data)
|
|
{
|
|
int ret;
|
|
|
|
ret = regmap_field_write(data->rm_field[F_SDRV_DLY], 1);
|
|
if (ret) {
|
|
dev_err(data->dev, "failed to disable ship mode delay\n");
|
|
return ret;
|
|
}
|
|
|
|
return regmap_field_write(data->rm_field[F_SDRV_CTRL], 2);
|
|
}
|
|
|
|
static ssize_t shipping_mode_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct rt9490_chg_data *data = dev_get_drvdata(dev);
|
|
int32_t tmp = 0;
|
|
int ret = 0;
|
|
|
|
if (kstrtoint(buf, 10, &tmp) < 0) {
|
|
dev_err(dev, "parsing number fail\n");
|
|
return -EINVAL;
|
|
}
|
|
if (tmp != 5526789)
|
|
return -EINVAL;
|
|
ret = rt9490_set_shipping_mode(data);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return count;
|
|
}
|
|
|
|
static const DEVICE_ATTR_WO(shipping_mode);
|
|
|
|
static int rt9490_charger_probe(struct platform_device *pdev)
|
|
{
|
|
struct rt9490_chg_data *data;
|
|
struct power_supply_desc *desc;
|
|
struct power_supply_config cfg = {};
|
|
struct regulator_config reg_cfg = {};
|
|
struct regulator_dev *rdev;
|
|
int i, irqno, ret;
|
|
|
|
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
|
|
if (!data)
|
|
return -ENOMEM;
|
|
|
|
data->dev = &pdev->dev;
|
|
mutex_init(&data->lock);
|
|
mutex_init(&data->pe_lock);
|
|
atomic_set(&data->aicc_once, 0);
|
|
atomic_set(&data->attach, 0);
|
|
init_completion(&data->aicc_done);
|
|
data->wq = create_singlethread_workqueue(dev_name(data->dev));
|
|
if (!data->wq) {
|
|
dev_err(data->dev, "failed to create workqueue\n");
|
|
return -ENOMEM;
|
|
}
|
|
INIT_WORK(&data->bc12_work, rt9490_chg_bc12_work_func);
|
|
platform_set_drvdata(pdev, data);
|
|
|
|
data->regmap = dev_get_regmap(pdev->dev.parent, NULL);
|
|
if (!data->regmap) {
|
|
dev_err(&pdev->dev, "Failed to get parent regmap\n");
|
|
ret = -ENODEV;
|
|
goto out_wq;
|
|
}
|
|
|
|
ret = devm_regmap_field_bulk_alloc(&pdev->dev, data->regmap,
|
|
data->rm_field, rt9490_reg_fields,
|
|
ARRAY_SIZE(rt9490_reg_fields));
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "Failed to alloc regmap fields\n");
|
|
goto out_wq;
|
|
}
|
|
ret = rt9490_get_all_adcs(data);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "Failed to get all adcs\n");
|
|
goto out_wq;
|
|
}
|
|
|
|
ret = rt9490_do_charger_init(data);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "Failed to do charger init\n");
|
|
goto out_wq;
|
|
}
|
|
|
|
data->ceb_gpio = devm_gpiod_get_optional(&pdev->dev, "ceb",
|
|
GPIOD_OUT_LOW);
|
|
if (IS_ERR(data->ceb_gpio)) {
|
|
dev_info(&pdev->dev, "config ceb-gpio faiil\n");
|
|
ret = PTR_ERR(data->ceb_gpio);
|
|
goto out_wq;
|
|
}
|
|
|
|
reg_cfg.dev = &pdev->dev;
|
|
reg_cfg.driver_data = data;
|
|
reg_cfg.regmap = data->regmap;
|
|
rdev = devm_regulator_register(&pdev->dev, &rt9490_otg_desc, ®_cfg);
|
|
if (IS_ERR(rdev)) {
|
|
dev_err(&pdev->dev, "Failed to register otg vbus regulator\n");
|
|
ret = PTR_ERR(rdev);
|
|
goto out_wq;
|
|
}
|
|
|
|
desc = &data->chg_desc;
|
|
desc->name = data->chg_name;
|
|
desc->type = POWER_SUPPLY_TYPE_USB;
|
|
desc->properties = rt9490_charger_properties;
|
|
desc->num_properties = ARRAY_SIZE(rt9490_charger_properties);
|
|
desc->usb_types = rt9490_charger_usb_types;
|
|
desc->num_usb_types = ARRAY_SIZE(rt9490_charger_usb_types);
|
|
desc->get_property = rt9490_charger_get_property;
|
|
desc->set_property = rt9490_charger_set_property;
|
|
|
|
cfg.of_node = pdev->dev.of_node;
|
|
cfg.drv_data = data;
|
|
cfg.supplied_to = rt9490_charger_supplied_to;
|
|
cfg.num_supplicants = ARRAY_SIZE(rt9490_charger_supplied_to);
|
|
|
|
|
|
data->psy = devm_power_supply_register(&pdev->dev, desc, &cfg);
|
|
if (IS_ERR(data->psy)) {
|
|
dev_err(&pdev->dev, "Failed to register psy\n");
|
|
ret = PTR_ERR(data->psy);
|
|
goto out_wq;
|
|
}
|
|
|
|
data->chgdev = charger_device_register(data->chg_name, data->dev,
|
|
data, &rt9490_chg_ops,
|
|
&rt9490_chg_props);
|
|
if (IS_ERR(data->chgdev)) {
|
|
dev_err(&pdev->dev, "Failed to init chgdev\n");
|
|
ret = PTR_ERR(data->chgdev);
|
|
goto out_wq;
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(rt9490_irqs); i++) {
|
|
irqno = platform_get_irq_byname(pdev, rt9490_irqs[i].irq_name);
|
|
if (irqno < 0) {
|
|
ret = irqno;
|
|
goto out_get_irq;
|
|
}
|
|
|
|
ret = devm_request_threaded_irq(&pdev->dev, irqno, NULL,
|
|
rt9490_irqs[i].handler, 0,
|
|
dev_name(&pdev->dev), data);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "Failed to request irq [%d]",
|
|
irqno);
|
|
goto out_get_irq;
|
|
}
|
|
}
|
|
|
|
ret = device_create_file(data->dev, &dev_attr_shipping_mode);
|
|
if (ret < 0) {
|
|
dev_err(data->dev,
|
|
"Failed to create shipping mode attribute\n");
|
|
goto out_get_irq;
|
|
}
|
|
|
|
dev_info(data->dev, "%s ok\n", __func__);
|
|
return 0;
|
|
out_get_irq:
|
|
charger_device_unregister(data->chgdev);
|
|
out_wq:
|
|
destroy_workqueue(data->wq);
|
|
mutex_destroy(&data->lock);
|
|
mutex_destroy(&data->pe_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int rt9490_charger_remove(struct platform_device *pdev)
|
|
{
|
|
struct rt9490_chg_data *data = platform_get_drvdata(pdev);
|
|
|
|
charger_device_unregister(data->chgdev);
|
|
device_remove_file(data->dev, &dev_attr_shipping_mode);
|
|
destroy_workqueue(data->wq);
|
|
mutex_destroy(&data->lock);
|
|
mutex_destroy(&data->pe_lock);
|
|
return 0;
|
|
}
|
|
static void rt9490_charger_shutdown(struct platform_device *pdev)
|
|
{
|
|
struct rt9490_chg_data *data = platform_get_drvdata(pdev);
|
|
|
|
if (data->ceb_gpio)
|
|
gpiod_set_value(data->ceb_gpio, true);
|
|
}
|
|
static const struct of_device_id rt9490_charger_of_match_table[] = {
|
|
{ .compatible = "richtek,rt9490-chg", },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(of, rt9490_charger_of_match_table);
|
|
|
|
static struct platform_driver rt9490_charger_driver = {
|
|
.driver = {
|
|
.name = "rt9490-charger",
|
|
.of_match_table = rt9490_charger_of_match_table,
|
|
},
|
|
.probe = rt9490_charger_probe,
|
|
.remove = rt9490_charger_remove,
|
|
.shutdown = rt9490_charger_shutdown,
|
|
};
|
|
module_platform_driver(rt9490_charger_driver);
|
|
|
|
MODULE_DESCRIPTION("Richtek RT9490 charger driver");
|
|
MODULE_AUTHOR("ChiYuan Huang <cy_huang@richtek.com>");
|
|
MODULE_LICENSE("GPL");
|