You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1799 lines
46 KiB
1799 lines
46 KiB
/*
|
|
* rk817 charger driver
|
|
*
|
|
* Copyright (C) 2018 Rockchip Electronics Co., Ltd
|
|
* xsf <xsf@rock-chips.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms and conditions of the GNU General Public License,
|
|
* version 2, as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
*/
|
|
|
|
#include <linux/delay.h>
|
|
#include <linux/extcon.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/iio/consumer.h>
|
|
#include <linux/iio/iio.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/jiffies.h>
|
|
#include <linux/mfd/rk808.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/of_gpio.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/power_supply.h>
|
|
#include <linux/power/rk_usbbc.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/rtc.h>
|
|
#include <linux/timer.h>
|
|
#include <linux/workqueue.h>
|
|
|
|
static int dbg_enable;
|
|
module_param_named(dbg_level, dbg_enable, int, 0644);
|
|
|
|
#define DBG(args...) \
|
|
do { \
|
|
if (dbg_enable) { \
|
|
pr_info(args); \
|
|
} \
|
|
} while (0)
|
|
|
|
#define CHARGE_DRIVER_VERSION "1.0"
|
|
|
|
#define DISABLE 0x00
|
|
#define ENABLE 0x01
|
|
#define OTG_SLP_ENABLE 0x01
|
|
#define OTG_SLP_DISABLE 0x00
|
|
#define OTG_ENABLE 0x11
|
|
#define OTG_DISABLE 0x10
|
|
#define RK817_BOOST_ENABLE 0x11
|
|
#define RK817_BOOST_DISABLE 0x10
|
|
#define OTG_MODE 0x01
|
|
#define OTG_MODE_ON 0x01
|
|
#define DEFAULT_INPUT_VOLTAGE 4500
|
|
#define DEFAULT_INPUT_CURRENT 2000
|
|
#define DEFAULT_CHRG_VOLTAGE 4200
|
|
#define DEFAULT_CHRG_CURRENT 1400
|
|
#define DEFAULT_CHRG_TERM_MODE 1
|
|
#define DEFAULT_CHRG_TERM_CUR 150
|
|
#define SAMPLE_RES_10MR 10
|
|
#define SAMPLE_RES_20MR 20
|
|
#define SAMPLE_RES_DIV1 1
|
|
#define SAMPLE_RES_DIV2 2
|
|
|
|
#define INPUT_450MA 450
|
|
#define INPUT_1500MA 1500
|
|
|
|
#define CURRENT_TO_ADC(current, samp_res) \
|
|
(current * 1000 * samp_res / 172)
|
|
|
|
enum charge_current {
|
|
CHRG_CUR_1000MA,
|
|
CHRG_CUR_1500MA,
|
|
CHRG_CUR_2000MA,
|
|
CHRG_CUR_2500MA,
|
|
CHRG_CUR_2750MA,
|
|
CHRG_CUR_3000MA,
|
|
CHRG_CUR_3500MA,
|
|
CHRG_CUR_500MA,
|
|
};
|
|
|
|
enum charge_voltage {
|
|
CHRG_VOL_4100MV,
|
|
CHRG_VOL_4150MV,
|
|
CHRG_VOL_4200MV,
|
|
CHRG_VOL_4250MV,
|
|
CHRG_VOL_4300MV,
|
|
CHRG_VOL_4350MV,
|
|
CHRG_VOL_4400MV,
|
|
CHRG_VOL_4450MV,
|
|
};
|
|
|
|
enum input_voltage {
|
|
INPUT_VOL_4000MV,
|
|
INPUT_VOL_4100MV,
|
|
INPUT_VOL_4200MV,
|
|
INPUT_VOL_4300MV,
|
|
INPUT_VOL_4400MV,
|
|
INPUT_VOL_4500MV,
|
|
INPUT_VOL_4600MV,
|
|
INPUT_VOL_4700MV,
|
|
};
|
|
|
|
enum input_current {
|
|
INPUT_CUR_450MA,
|
|
INPUT_CUR_80MA,
|
|
INPUT_CUR_850MA,
|
|
INPUT_CUR_1500MA,
|
|
INPUT_CUR_1750MA,
|
|
INPUT_CUR_2000MA,
|
|
INPUT_CUR_2500MA,
|
|
INPUT_CUR_3000MA,
|
|
};
|
|
|
|
enum charge_clk {
|
|
CHRG_CLK_1M,
|
|
CHRG_CLK_2M,
|
|
};
|
|
|
|
enum charge_term_sel {
|
|
CHRG_TERM_150MA,
|
|
CHRG_TERM_200MA,
|
|
CHRG_TERM_300MA,
|
|
CHRG_TERM_400MA,
|
|
};
|
|
|
|
enum charge_timer_trickle {
|
|
CHRG_TIMER_TRIKL_30MIN,
|
|
CHRG_TIMER_TRIKL_45MIN,
|
|
CHRG_TIMER_TRIKL_60MIN,
|
|
CHRG_TIMER_TRIKL_90MIN,
|
|
CHRG_TIMER_TRIKL_120MIN,
|
|
CHRG_TIMER_TRIKL_150MIN,
|
|
CHRG_TIMER_TRIKL_180MIN,
|
|
CHRG_TIMER_TRIKL_210MIN,
|
|
};
|
|
|
|
enum charge_timer_cccv {
|
|
CHRG_TIMER_CCCV_4H,
|
|
CHRG_TIMER_CCCV_5H,
|
|
CHRG_TIMER_CCCV_6H,
|
|
CHRG_TIMER_CCCV_8H,
|
|
CHRG_TIMER_CCCV_10H,
|
|
CHRG_TIMER_CCCV_12H,
|
|
CHRG_TIMER_CCCV_14H,
|
|
CHRG_TIMER_CCCV_16H,
|
|
};
|
|
|
|
enum charge_status {
|
|
CHRG_OFF,
|
|
DEAD_CHRG,
|
|
TRICKLE_CHRG,
|
|
CC_OR_CV_CHRG,
|
|
CHRG_TERM,
|
|
USB_OVER_VOL,
|
|
BAT_TMP_ERR,
|
|
BAT_TIM_ERR,
|
|
};
|
|
|
|
enum discharge_ilimit {
|
|
DISCHRG_2000MA,
|
|
DISCHRG_2500MA,
|
|
DISCHRG_3000MA,
|
|
DISCHRG_3500MA,
|
|
DISCHRG_4000MA,
|
|
};
|
|
|
|
enum bat_system_comp_time {
|
|
DLY_20US,
|
|
DLY_10US,
|
|
DLY_40US,
|
|
DLY_20US_AGAIN,
|
|
};
|
|
|
|
enum charge_term_mode {
|
|
CHRG_ANALOG,
|
|
CHRG_DIGITAL,
|
|
};
|
|
|
|
enum charger_t {
|
|
USB_TYPE_UNKNOWN_CHARGER,
|
|
USB_TYPE_NONE_CHARGER,
|
|
USB_TYPE_USB_CHARGER,
|
|
USB_TYPE_AC_CHARGER,
|
|
USB_TYPE_CDP_CHARGER,
|
|
DC_TYPE_DC_CHARGER,
|
|
DC_TYPE_NONE_CHARGER,
|
|
};
|
|
|
|
enum charger_state {
|
|
OFFLINE = 0,
|
|
ONLINE
|
|
};
|
|
|
|
enum rk817_charge_fields {
|
|
BOOST_EN, OTG_EN, OTG_SLP_EN, CHRG_CLK_SEL,
|
|
CHRG_EN, CHRG_VOL_SEL, CHRG_CT_EN, CHRG_CUR_SEL,
|
|
USB_VLIM_EN, USB_VLIM_SEL, USB_ILIM_EN, USB_ILIM_SEL,
|
|
SYS_CAN_SD, USB_SYS_EN, BAT_OVP_EN, CHRG_TERM_ANA_DIG,
|
|
CHRG_TERM_ANA_SEL,
|
|
CHRG_TERM_DIG,
|
|
BAT_HTS_TS, BAT_LTS_TS,
|
|
CHRG_TIMER_TRIKL_EN, CHRG_TIMER_TRIKL,
|
|
CHRG_TIMER_CCCV_EN, CHRG_TIMER_CCCV,
|
|
BAT_EXS, CHG_STS, BAT_OVP_STS, CHRG_IN_CLAMP,
|
|
USB_EXS, USB_EFF,
|
|
BAT_DIS_ILIM_STS, BAT_SYS_CMP_DLY, BAT_DIS_ILIM_EN,
|
|
BAT_DISCHRG_ILIM,
|
|
PLUG_IN_STS, SOC_REG0, SOC_REG1, SOC_REG2,
|
|
F_MAX_FIELDS
|
|
};
|
|
|
|
static const struct reg_field rk817_charge_reg_fields[] = {
|
|
[SOC_REG0] = REG_FIELD(0x9A, 0, 7),
|
|
[SOC_REG1] = REG_FIELD(0x9B, 0, 7),
|
|
[SOC_REG2] = REG_FIELD(0x9C, 0, 7),
|
|
[BOOST_EN] = REG_FIELD(0xB4, 1, 5),
|
|
[OTG_EN] = REG_FIELD(0xB4, 2, 6),
|
|
[OTG_SLP_EN] = REG_FIELD(0xB5, 6, 6),
|
|
[CHRG_EN] = REG_FIELD(0xE4, 7, 7),
|
|
[CHRG_VOL_SEL] = REG_FIELD(0xE4, 4, 6),
|
|
[CHRG_CT_EN] = REG_FIELD(0xE4, 3, 3),
|
|
[CHRG_CUR_SEL] = REG_FIELD(0xE4, 0, 2),
|
|
|
|
[USB_VLIM_EN] = REG_FIELD(0xE5, 7, 7),
|
|
[USB_VLIM_SEL] = REG_FIELD(0xE5, 4, 6),
|
|
[USB_ILIM_EN] = REG_FIELD(0xE5, 3, 3),
|
|
[USB_ILIM_SEL] = REG_FIELD(0xE5, 0, 2),
|
|
|
|
[SYS_CAN_SD] = REG_FIELD(0xE6, 7, 7),
|
|
[USB_SYS_EN] = REG_FIELD(0xE6, 6, 6),
|
|
[BAT_OVP_EN] = REG_FIELD(0xE6, 3, 3),
|
|
[CHRG_TERM_ANA_DIG] = REG_FIELD(0xE6, 2, 2),
|
|
[CHRG_TERM_ANA_SEL] = REG_FIELD(0xE6, 0, 1),
|
|
|
|
[CHRG_TERM_DIG] = REG_FIELD(0xE7, 0, 7),
|
|
|
|
[BAT_HTS_TS] = REG_FIELD(0xE8, 0, 7),
|
|
|
|
[BAT_LTS_TS] = REG_FIELD(0xE9, 0, 7),
|
|
|
|
[CHRG_TIMER_TRIKL_EN] = REG_FIELD(0xEA, 7, 7),
|
|
[CHRG_TIMER_TRIKL] = REG_FIELD(0xEA, 4, 6),
|
|
[CHRG_TIMER_CCCV_EN] = REG_FIELD(0xEA, 3, 3),
|
|
[CHRG_TIMER_CCCV] = REG_FIELD(0xEA, 0, 2),
|
|
|
|
[BAT_EXS] = REG_FIELD(0xEB, 7, 7),
|
|
[CHG_STS] = REG_FIELD(0xEB, 4, 6),
|
|
[BAT_OVP_STS] = REG_FIELD(0xEB, 3, 3),
|
|
[CHRG_IN_CLAMP] = REG_FIELD(0xEB, 2, 2),
|
|
[USB_EXS] = REG_FIELD(0xEB, 1, 1),
|
|
[USB_EFF] = REG_FIELD(0xEB, 0, 0),
|
|
|
|
[BAT_DIS_ILIM_STS] = REG_FIELD(0xEC, 6, 6),
|
|
[BAT_SYS_CMP_DLY] = REG_FIELD(0xEC, 4, 5),
|
|
[BAT_DIS_ILIM_EN] = REG_FIELD(0xEC, 3, 3),
|
|
[BAT_DISCHRG_ILIM] = REG_FIELD(0xEC, 0, 2),
|
|
[PLUG_IN_STS] = REG_FIELD(0xf0, 6, 6),
|
|
[CHRG_CLK_SEL] = REG_FIELD(0xF3, 6, 6),
|
|
};
|
|
|
|
struct charger_platform_data {
|
|
u32 max_input_current;
|
|
u32 min_input_voltage;
|
|
|
|
u32 max_chrg_current;
|
|
u32 max_chrg_voltage;
|
|
|
|
u32 chrg_finish_cur;
|
|
u32 chrg_term_mode;
|
|
|
|
u32 power_dc2otg;
|
|
u32 dc_det_level;
|
|
int dc_det_pin;
|
|
bool support_dc_det;
|
|
int virtual_power;
|
|
int sample_res;
|
|
int otg5v_suspend_enable;
|
|
bool extcon;
|
|
int gate_function_disable;
|
|
};
|
|
|
|
struct rk817_charger {
|
|
struct i2c_client *client;
|
|
struct platform_device *pdev;
|
|
struct device *dev;
|
|
struct rk808 *rk817;
|
|
struct regmap *regmap;
|
|
struct regmap_field *rmap_fields[F_MAX_FIELDS];
|
|
|
|
struct power_supply *ac_psy;
|
|
struct power_supply *usb_psy;
|
|
struct extcon_dev *cable_edev;
|
|
struct charger_platform_data *pdata;
|
|
struct workqueue_struct *usb_charger_wq;
|
|
struct workqueue_struct *dc_charger_wq;
|
|
struct delayed_work dc_work;
|
|
struct delayed_work usb_work;
|
|
struct delayed_work host_work;
|
|
struct delayed_work discnt_work;
|
|
struct delayed_work irq_work;
|
|
struct notifier_block bc_nb;
|
|
struct notifier_block cable_cg_nb;
|
|
struct notifier_block cable_host_nb;
|
|
struct notifier_block cable_discnt_nb;
|
|
unsigned int bc_event;
|
|
enum charger_t usb_charger;
|
|
enum charger_t dc_charger;
|
|
struct regulator *otg5v_rdev;
|
|
u8 ac_in;
|
|
u8 usb_in;
|
|
u8 otg_in;
|
|
u8 dc_in;
|
|
u8 prop_status;
|
|
|
|
u32 max_input_current;
|
|
u32 min_input_voltage;
|
|
|
|
u32 max_chrg_current;
|
|
u32 max_chrg_voltage;
|
|
|
|
u32 chrg_finish_cur;
|
|
u32 chrg_term_mode;
|
|
|
|
u8 res_div;
|
|
u8 otg_slp_state;
|
|
u8 plugin_trigger;
|
|
u8 plugout_trigger;
|
|
int plugin_irq;
|
|
int plugout_irq;
|
|
};
|
|
|
|
static enum power_supply_property rk817_ac_props[] = {
|
|
POWER_SUPPLY_PROP_ONLINE,
|
|
POWER_SUPPLY_PROP_STATUS,
|
|
POWER_SUPPLY_PROP_VOLTAGE_MAX,
|
|
POWER_SUPPLY_PROP_CURRENT_MAX,
|
|
};
|
|
|
|
static enum power_supply_property rk817_usb_props[] = {
|
|
POWER_SUPPLY_PROP_ONLINE,
|
|
POWER_SUPPLY_PROP_STATUS,
|
|
POWER_SUPPLY_PROP_VOLTAGE_MAX,
|
|
POWER_SUPPLY_PROP_CURRENT_MAX,
|
|
};
|
|
|
|
static int rk817_charge_ac_get_property(struct power_supply *psy,
|
|
enum power_supply_property psp,
|
|
union power_supply_propval *val)
|
|
{
|
|
struct rk817_charger *charge = power_supply_get_drvdata(psy);
|
|
int ret = 0;
|
|
|
|
switch (psp) {
|
|
case POWER_SUPPLY_PROP_ONLINE:
|
|
if (charge->pdata->virtual_power)
|
|
val->intval = 1;
|
|
else
|
|
val->intval = (charge->ac_in | charge->dc_in);
|
|
|
|
DBG("ac report online: %d\n", val->intval);
|
|
break;
|
|
case POWER_SUPPLY_PROP_STATUS:
|
|
if (charge->pdata->virtual_power)
|
|
val->intval = POWER_SUPPLY_STATUS_CHARGING;
|
|
else
|
|
val->intval = charge->prop_status;
|
|
|
|
DBG("report prop: %d\n", val->intval);
|
|
break;
|
|
case POWER_SUPPLY_PROP_VOLTAGE_MAX:
|
|
val->intval = charge->max_chrg_voltage * 1000; /* uV */
|
|
break;
|
|
case POWER_SUPPLY_PROP_CURRENT_MAX:
|
|
val->intval = charge->max_chrg_current * 1000; /* uA */
|
|
break;
|
|
default:
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int rk817_charge_usb_get_property(struct power_supply *psy,
|
|
enum power_supply_property psp,
|
|
union power_supply_propval *val)
|
|
{
|
|
struct rk817_charger *charge = power_supply_get_drvdata(psy);
|
|
int ret = 0;
|
|
|
|
switch (psp) {
|
|
case POWER_SUPPLY_PROP_ONLINE:
|
|
if (charge->pdata->virtual_power)
|
|
val->intval = 1;
|
|
else
|
|
val->intval = charge->usb_in;
|
|
|
|
DBG("usb report online: %d\n", val->intval);
|
|
break;
|
|
case POWER_SUPPLY_PROP_STATUS:
|
|
if (charge->pdata->virtual_power)
|
|
val->intval = POWER_SUPPLY_STATUS_CHARGING;
|
|
else
|
|
val->intval = charge->prop_status;
|
|
|
|
DBG("report prop: %d\n", val->intval);
|
|
break;
|
|
case POWER_SUPPLY_PROP_VOLTAGE_MAX:
|
|
val->intval = charge->max_chrg_voltage;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CURRENT_MAX:
|
|
val->intval = charge->max_chrg_current;
|
|
break;
|
|
default:
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const struct power_supply_desc rk817_ac_desc = {
|
|
.name = "ac",
|
|
.type = POWER_SUPPLY_TYPE_MAINS,
|
|
.properties = rk817_ac_props,
|
|
.num_properties = ARRAY_SIZE(rk817_ac_props),
|
|
.get_property = rk817_charge_ac_get_property,
|
|
};
|
|
|
|
static const struct power_supply_desc rk817_usb_desc = {
|
|
.name = "usb",
|
|
.type = POWER_SUPPLY_TYPE_USB,
|
|
.properties = rk817_usb_props,
|
|
.num_properties = ARRAY_SIZE(rk817_usb_props),
|
|
.get_property = rk817_charge_usb_get_property,
|
|
};
|
|
|
|
static int rk817_charge_init_power_supply(struct rk817_charger *charge)
|
|
{
|
|
struct power_supply_config psy_cfg = { .drv_data = charge, };
|
|
|
|
charge->usb_psy = devm_power_supply_register(charge->dev,
|
|
&rk817_usb_desc,
|
|
&psy_cfg);
|
|
if (IS_ERR(charge->usb_psy)) {
|
|
dev_err(charge->dev, "register usb power supply fail\n");
|
|
return PTR_ERR(charge->usb_psy);
|
|
}
|
|
|
|
charge->ac_psy = devm_power_supply_register(charge->dev, &rk817_ac_desc,
|
|
&psy_cfg);
|
|
if (IS_ERR(charge->ac_psy)) {
|
|
dev_err(charge->dev, "register ac power supply fail\n");
|
|
return PTR_ERR(charge->ac_psy);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rk817_charge_field_read(struct rk817_charger *charge,
|
|
enum rk817_charge_fields field_id)
|
|
{
|
|
int ret;
|
|
int val;
|
|
|
|
ret = regmap_field_read(charge->rmap_fields[field_id], &val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return val;
|
|
}
|
|
|
|
static int rk817_charge_field_write(struct rk817_charger *charge,
|
|
enum rk817_charge_fields field_id,
|
|
unsigned int val)
|
|
{
|
|
return regmap_field_write(charge->rmap_fields[field_id], val);
|
|
}
|
|
|
|
static int rk817_charge_get_otg_state(struct rk817_charger *charge)
|
|
{
|
|
return regulator_is_enabled(charge->otg5v_rdev);
|
|
}
|
|
|
|
static void rk817_charge_boost_disable(struct rk817_charger *charge)
|
|
{
|
|
rk817_charge_field_write(charge, BOOST_EN, RK817_BOOST_DISABLE);
|
|
}
|
|
|
|
static void rk817_charge_boost_enable(struct rk817_charger *charge)
|
|
{
|
|
rk817_charge_field_write(charge, BOOST_EN, RK817_BOOST_ENABLE);
|
|
}
|
|
|
|
static void rk817_charge_otg_disable(struct rk817_charger *charge)
|
|
{
|
|
int ret;
|
|
|
|
ret = regulator_disable(charge->otg5v_rdev);
|
|
|
|
if (ret) {
|
|
DBG("disable otg5v failed:%d\n", ret);
|
|
return;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
static void rk817_charge_otg_enable(struct rk817_charger *charge)
|
|
{
|
|
int ret;
|
|
|
|
ret = regulator_enable(charge->otg5v_rdev);
|
|
|
|
if (ret) {
|
|
DBG("enable otg5v failed:%d\n", ret);
|
|
return;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
static int rk817_charge_get_otg_slp_state(struct rk817_charger *charge)
|
|
{
|
|
return (rk817_charge_field_read(charge, OTG_SLP_EN) & OTG_SLP_ENABLE);
|
|
}
|
|
|
|
static void rk817_charge_otg_slp_disable(struct rk817_charger *charge)
|
|
{
|
|
rk817_charge_field_write(charge, OTG_SLP_EN, OTG_SLP_DISABLE);
|
|
}
|
|
|
|
static void rk817_charge_otg_slp_enable(struct rk817_charger *charge)
|
|
{
|
|
rk817_charge_field_write(charge, OTG_SLP_EN, OTG_SLP_ENABLE);
|
|
}
|
|
#endif
|
|
|
|
static int rk817_charge_get_charge_state(struct rk817_charger *charge)
|
|
{
|
|
return rk817_charge_field_read(charge, CHRG_EN);
|
|
}
|
|
|
|
static void rk817_charge_enable_charge(struct rk817_charger *charge)
|
|
{
|
|
rk817_charge_field_write(charge, CHRG_EN, ENABLE);
|
|
}
|
|
|
|
static void rk817_charge_usb_to_sys_enable(struct rk817_charger *charge)
|
|
{
|
|
rk817_charge_field_write(charge, USB_SYS_EN, ENABLE);
|
|
}
|
|
|
|
static void rk817_charge_sys_can_sd_disable(struct rk817_charger *charge)
|
|
{
|
|
rk817_charge_field_write(charge, SYS_CAN_SD, DISABLE);
|
|
}
|
|
|
|
static int rk817_charge_get_charge_status(struct rk817_charger *charge)
|
|
{
|
|
int status;
|
|
|
|
status = rk817_charge_field_read(charge, CHG_STS);
|
|
|
|
switch (status) {
|
|
case CHRG_OFF:
|
|
DBG("charge off...\n");
|
|
break;
|
|
case DEAD_CHRG:
|
|
DBG("dead charge...\n");
|
|
break;
|
|
case TRICKLE_CHRG:
|
|
DBG("trickle charge...\n");
|
|
break;
|
|
case CC_OR_CV_CHRG:
|
|
DBG("CC or CV charge...\n");
|
|
break;
|
|
case CHRG_TERM:
|
|
DBG("charge TERM...\n");
|
|
break;
|
|
case USB_OVER_VOL:
|
|
DBG("USB over voltage...\n");
|
|
break;
|
|
case BAT_TMP_ERR:
|
|
DBG("battery temperature error...\n");
|
|
break;
|
|
case BAT_TIM_ERR:
|
|
DBG("battery timer error..\n");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
static int rk817_charge_get_plug_in_status(struct rk817_charger *charge)
|
|
{
|
|
return rk817_charge_field_read(charge, PLUG_IN_STS);
|
|
}
|
|
|
|
static void rk817_charge_set_charge_clock(struct rk817_charger *charge,
|
|
enum charge_clk clock)
|
|
{
|
|
rk817_charge_field_write(charge, CHRG_CLK_SEL, clock);
|
|
}
|
|
|
|
static int is_battery_exist(struct rk817_charger *charge)
|
|
{
|
|
return rk817_charge_field_read(charge, BAT_EXS);
|
|
}
|
|
|
|
static void rk817_charge_set_chrg_voltage(struct rk817_charger *charge,
|
|
int chrg_vol)
|
|
{
|
|
int voltage;
|
|
|
|
if (chrg_vol < 4100 || chrg_vol > 4500) {
|
|
dev_err(charge->dev, "the charge voltage is error!\n");
|
|
} else {
|
|
voltage = (chrg_vol - 4100) / 50;
|
|
rk817_charge_field_write(charge,
|
|
CHRG_VOL_SEL,
|
|
CHRG_VOL_4100MV + voltage);
|
|
}
|
|
}
|
|
|
|
static void rk817_charge_set_chrg_current(struct rk817_charger *charge,
|
|
int chrg_current)
|
|
{
|
|
if (chrg_current < 500 || chrg_current > 3500)
|
|
dev_err(charge->dev, "the charge current is error!\n");
|
|
|
|
if (chrg_current < 1000)
|
|
rk817_charge_field_write(charge, CHRG_CUR_SEL, CHRG_CUR_500MA);
|
|
else if (chrg_current < 1500)
|
|
rk817_charge_field_write(charge, CHRG_CUR_SEL, CHRG_CUR_1000MA);
|
|
else if (chrg_current < 2000)
|
|
rk817_charge_field_write(charge, CHRG_CUR_SEL, CHRG_CUR_1500MA);
|
|
else if (chrg_current < 2500)
|
|
rk817_charge_field_write(charge, CHRG_CUR_SEL, CHRG_CUR_2000MA);
|
|
else if (chrg_current < 3000)
|
|
rk817_charge_field_write(charge, CHRG_CUR_SEL, CHRG_CUR_2500MA);
|
|
else if (chrg_current < 3500)
|
|
rk817_charge_field_write(charge, CHRG_CUR_SEL, CHRG_CUR_3000MA);
|
|
else
|
|
rk817_charge_field_write(charge, CHRG_CUR_SEL, CHRG_CUR_3500MA);
|
|
}
|
|
|
|
static void rk817_charge_vlimit_enable(struct rk817_charger *charge)
|
|
{
|
|
rk817_charge_field_write(charge, USB_VLIM_EN, ENABLE);
|
|
}
|
|
|
|
static void rk817_charge_set_input_voltage(struct rk817_charger *charge,
|
|
int input_voltage)
|
|
{
|
|
int voltage;
|
|
|
|
if (input_voltage < 4000)
|
|
dev_err(charge->dev, "the input voltage is error.\n");
|
|
|
|
voltage = INPUT_VOL_4000MV + (input_voltage - 4000) / 100;
|
|
|
|
rk817_charge_field_write(charge, USB_VLIM_SEL, voltage);
|
|
rk817_charge_vlimit_enable(charge);
|
|
}
|
|
|
|
static void rk817_charge_ilimit_enable(struct rk817_charger *charge)
|
|
{
|
|
rk817_charge_field_write(charge, USB_ILIM_EN, ENABLE);
|
|
}
|
|
|
|
static void rk817_charge_set_input_current(struct rk817_charger *charge,
|
|
int input_current)
|
|
{
|
|
if (input_current < 80 || input_current > 3000)
|
|
dev_err(charge->dev, "the input current is error.\n");
|
|
|
|
if (input_current < 450)
|
|
rk817_charge_field_write(charge, USB_ILIM_SEL,
|
|
INPUT_CUR_80MA);
|
|
else if (input_current < 850)
|
|
rk817_charge_field_write(charge, USB_ILIM_SEL,
|
|
INPUT_CUR_450MA);
|
|
else if (input_current < 1500)
|
|
rk817_charge_field_write(charge, USB_ILIM_SEL,
|
|
INPUT_CUR_850MA);
|
|
else if (input_current < 1750)
|
|
rk817_charge_field_write(charge, USB_ILIM_SEL,
|
|
INPUT_CUR_1500MA);
|
|
else if (input_current < 2000)
|
|
rk817_charge_field_write(charge, USB_ILIM_SEL,
|
|
INPUT_CUR_1750MA);
|
|
else if (input_current < 2500)
|
|
rk817_charge_field_write(charge, USB_ILIM_SEL,
|
|
INPUT_CUR_2000MA);
|
|
else if (input_current < 3000)
|
|
rk817_charge_field_write(charge, USB_ILIM_SEL,
|
|
INPUT_CUR_2500MA);
|
|
else
|
|
rk817_charge_field_write(charge, USB_ILIM_SEL,
|
|
INPUT_CUR_3000MA);
|
|
|
|
rk817_charge_ilimit_enable(charge);
|
|
}
|
|
|
|
static void rk817_charge_set_chrg_term_mod(struct rk817_charger *charge,
|
|
int mode)
|
|
{
|
|
rk817_charge_field_write(charge, CHRG_TERM_ANA_DIG, mode);
|
|
}
|
|
|
|
static void rk817_charge_set_term_current_analog(struct rk817_charger *charge,
|
|
int chrg_current)
|
|
{
|
|
int value;
|
|
|
|
if (chrg_current < 200)
|
|
value = CHRG_TERM_150MA;
|
|
else if (chrg_current < 300)
|
|
value = CHRG_TERM_200MA;
|
|
else if (chrg_current < 400)
|
|
value = CHRG_TERM_300MA;
|
|
else
|
|
value = CHRG_TERM_400MA;
|
|
|
|
rk817_charge_field_write(charge,
|
|
CHRG_TERM_ANA_SEL,
|
|
value);
|
|
}
|
|
|
|
static void rk817_charge_set_term_current_digital(struct rk817_charger *charge,
|
|
int chrg_current)
|
|
{
|
|
int value;
|
|
u8 current_adc;
|
|
|
|
value = CURRENT_TO_ADC(chrg_current, charge->res_div);
|
|
|
|
value &= (0xff << 5);
|
|
current_adc = value >> 5;
|
|
rk817_charge_field_write(charge, CHRG_TERM_DIG, current_adc);
|
|
}
|
|
|
|
static void rk817_charge_set_chrg_finish_condition(struct rk817_charger *charge)
|
|
{
|
|
if (charge->chrg_term_mode == CHRG_ANALOG)
|
|
rk817_charge_set_term_current_analog(charge,
|
|
charge->chrg_finish_cur);
|
|
else
|
|
rk817_charge_set_term_current_digital(charge,
|
|
charge->chrg_finish_cur);
|
|
|
|
rk817_charge_set_chrg_term_mod(charge, charge->chrg_term_mode);
|
|
}
|
|
|
|
static int rk817_charge_online(struct rk817_charger *charge)
|
|
{
|
|
return (charge->ac_in | charge->usb_in | charge->dc_in);
|
|
}
|
|
|
|
static int rk817_charge_get_dsoc(struct rk817_charger *charge)
|
|
{
|
|
int soc_save;
|
|
|
|
soc_save = rk817_charge_field_read(charge, SOC_REG0);
|
|
soc_save |= (rk817_charge_field_read(charge, SOC_REG1) << 8);
|
|
soc_save |= (rk817_charge_field_read(charge, SOC_REG2) << 16);
|
|
|
|
return soc_save / 1000;
|
|
}
|
|
|
|
static void rk817_charge_set_otg_in(struct rk817_charger *charge, int online)
|
|
{
|
|
charge->otg_in = online;
|
|
}
|
|
|
|
static void rk817_charge_set_chrg_param(struct rk817_charger *charge,
|
|
enum charger_t charger)
|
|
{
|
|
switch (charger) {
|
|
case USB_TYPE_NONE_CHARGER:
|
|
charge->usb_in = 0;
|
|
charge->ac_in = 0;
|
|
if (charge->dc_in == 0) {
|
|
charge->prop_status = POWER_SUPPLY_STATUS_DISCHARGING;
|
|
rk817_charge_set_input_current(charge, INPUT_450MA);
|
|
}
|
|
power_supply_changed(charge->usb_psy);
|
|
power_supply_changed(charge->ac_psy);
|
|
break;
|
|
case USB_TYPE_USB_CHARGER:
|
|
charge->usb_in = 1;
|
|
charge->ac_in = 0;
|
|
charge->prop_status = POWER_SUPPLY_STATUS_CHARGING;
|
|
if (charge->dc_in == 0)
|
|
rk817_charge_set_input_current(charge, INPUT_450MA);
|
|
power_supply_changed(charge->usb_psy);
|
|
power_supply_changed(charge->ac_psy);
|
|
break;
|
|
case USB_TYPE_AC_CHARGER:
|
|
case USB_TYPE_CDP_CHARGER:
|
|
charge->ac_in = 1;
|
|
charge->usb_in = 0;
|
|
charge->prop_status = POWER_SUPPLY_STATUS_CHARGING;
|
|
if (charger == USB_TYPE_AC_CHARGER)
|
|
rk817_charge_set_input_current(charge,
|
|
charge->max_input_current);
|
|
else
|
|
rk817_charge_set_input_current(charge,
|
|
INPUT_1500MA);
|
|
power_supply_changed(charge->usb_psy);
|
|
power_supply_changed(charge->ac_psy);
|
|
break;
|
|
case DC_TYPE_DC_CHARGER:
|
|
charge->dc_in = 1;
|
|
charge->prop_status = POWER_SUPPLY_STATUS_CHARGING;
|
|
rk817_charge_set_input_current(charge,
|
|
charge->max_input_current);
|
|
power_supply_changed(charge->usb_psy);
|
|
power_supply_changed(charge->ac_psy);
|
|
break;
|
|
case DC_TYPE_NONE_CHARGER:
|
|
charge->dc_in = 0;
|
|
if (!rk817_charge_get_plug_in_status(charge)) {
|
|
charge->ac_in = 0;
|
|
charge->usb_in = 0;
|
|
charge->prop_status = POWER_SUPPLY_STATUS_DISCHARGING;
|
|
rk817_charge_set_input_current(charge, INPUT_450MA);
|
|
} else if (charge->usb_in) {
|
|
rk817_charge_set_input_current(charge, INPUT_450MA);
|
|
charge->prop_status = POWER_SUPPLY_STATUS_CHARGING;
|
|
}
|
|
power_supply_changed(charge->usb_psy);
|
|
power_supply_changed(charge->ac_psy);
|
|
break;
|
|
default:
|
|
charge->prop_status = POWER_SUPPLY_STATUS_DISCHARGING;
|
|
rk817_charge_set_input_current(charge, INPUT_450MA);
|
|
break;
|
|
}
|
|
|
|
if (rk817_charge_online(charge) && rk817_charge_get_dsoc(charge) == 100)
|
|
charge->prop_status = POWER_SUPPLY_STATUS_FULL;
|
|
}
|
|
|
|
static void rk817_charge_set_otg_state(struct rk817_charger *charge, int state)
|
|
{
|
|
switch (state) {
|
|
case USB_OTG_POWER_ON:
|
|
if (charge->otg_in) {
|
|
DBG("otg5v is on yet, ignore..\n");
|
|
} else {
|
|
|
|
if (!rk817_charge_get_otg_state(charge)) {
|
|
rk817_charge_otg_enable(charge);
|
|
if (!rk817_charge_get_otg_state(charge)) {
|
|
DBG("enable otg5v failed\n");
|
|
return;
|
|
}
|
|
}
|
|
disable_irq(charge->plugin_irq);
|
|
disable_irq(charge->plugout_irq);
|
|
DBG("enable otg5v\n");
|
|
}
|
|
break;
|
|
|
|
case USB_OTG_POWER_OFF:
|
|
if (!charge->otg_in) {
|
|
DBG("otg5v is off yet, ignore..\n");
|
|
} else {
|
|
|
|
if (rk817_charge_get_otg_state(charge)) {
|
|
rk817_charge_otg_disable(charge);
|
|
if (rk817_charge_get_otg_state(charge)) {
|
|
DBG("disable otg5v failed\n");
|
|
return;
|
|
}
|
|
}
|
|
enable_irq(charge->plugin_irq);
|
|
enable_irq(charge->plugout_irq);
|
|
DBG("disable otg5v\n");
|
|
}
|
|
break;
|
|
default:
|
|
dev_err(charge->dev, "error otg type\n");
|
|
break;
|
|
}
|
|
}
|
|
|
|
static irqreturn_t rk817_charge_dc_det_isr(int irq, void *charger)
|
|
{
|
|
struct rk817_charger *charge = (struct rk817_charger *)charger;
|
|
|
|
if (gpio_get_value(charge->pdata->dc_det_pin))
|
|
irq_set_irq_type(irq, IRQF_TRIGGER_LOW);
|
|
else
|
|
irq_set_irq_type(irq, IRQF_TRIGGER_HIGH);
|
|
|
|
queue_delayed_work(charge->dc_charger_wq, &charge->dc_work,
|
|
msecs_to_jiffies(10));
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static enum charger_t rk817_charge_get_dc_state(struct rk817_charger *charge)
|
|
{
|
|
int level;
|
|
|
|
if (!gpio_is_valid(charge->pdata->dc_det_pin))
|
|
return DC_TYPE_NONE_CHARGER;
|
|
|
|
level = gpio_get_value(charge->pdata->dc_det_pin);
|
|
|
|
return (level == charge->pdata->dc_det_level) ?
|
|
DC_TYPE_DC_CHARGER : DC_TYPE_NONE_CHARGER;
|
|
}
|
|
|
|
static void rk817_charge_dc_det_worker(struct work_struct *work)
|
|
{
|
|
enum charger_t charger;
|
|
struct rk817_charger *charge = container_of(work,
|
|
struct rk817_charger, dc_work.work);
|
|
|
|
charger = rk817_charge_get_dc_state(charge);
|
|
if (charger == DC_TYPE_DC_CHARGER) {
|
|
DBG("detect dc charger in..\n");
|
|
rk817_charge_set_chrg_param(charge, DC_TYPE_DC_CHARGER);
|
|
/* check otg supply */
|
|
if (charge->otg_in && charge->pdata->power_dc2otg) {
|
|
DBG("otg power from dc adapter\n");
|
|
rk817_charge_set_otg_state(charge, USB_OTG_POWER_OFF);
|
|
}
|
|
|
|
rk817_charge_boost_disable(charge);
|
|
} else {
|
|
DBG("detect dc charger out..\n");
|
|
rk817_charge_set_chrg_param(charge, DC_TYPE_NONE_CHARGER);
|
|
rk817_charge_boost_enable(charge);
|
|
/* check otg supply, power on anyway */
|
|
if (charge->otg_in)
|
|
rk817_charge_set_otg_state(charge, USB_OTG_POWER_ON);
|
|
}
|
|
}
|
|
|
|
static int rk817_charge_init_dc(struct rk817_charger *charge)
|
|
{
|
|
int ret, level;
|
|
unsigned long irq_flags;
|
|
unsigned int dc_det_irq;
|
|
|
|
charge->dc_charger_wq = alloc_ordered_workqueue("%s",
|
|
WQ_MEM_RECLAIM | WQ_FREEZABLE,
|
|
"rk817-dc-wq");
|
|
INIT_DELAYED_WORK(&charge->dc_work, rk817_charge_dc_det_worker);
|
|
charge->dc_charger = DC_TYPE_NONE_CHARGER;
|
|
|
|
if (!charge->pdata->support_dc_det)
|
|
return 0;
|
|
|
|
ret = devm_gpio_request(charge->dev,
|
|
charge->pdata->dc_det_pin,
|
|
"rk817_dc_det");
|
|
if (ret < 0) {
|
|
dev_err(charge->dev, "failed to request gpio %d\n",
|
|
charge->pdata->dc_det_pin);
|
|
return ret;
|
|
}
|
|
|
|
ret = gpio_direction_input(charge->pdata->dc_det_pin);
|
|
if (ret) {
|
|
dev_err(charge->dev, "failed to set gpio input\n");
|
|
return ret;
|
|
}
|
|
|
|
level = gpio_get_value(charge->pdata->dc_det_pin);
|
|
if (level == charge->pdata->dc_det_level)
|
|
charge->dc_charger = DC_TYPE_DC_CHARGER;
|
|
else
|
|
charge->dc_charger = DC_TYPE_NONE_CHARGER;
|
|
|
|
if (level)
|
|
irq_flags = IRQF_TRIGGER_LOW;
|
|
else
|
|
irq_flags = IRQF_TRIGGER_HIGH;
|
|
|
|
dc_det_irq = gpio_to_irq(charge->pdata->dc_det_pin);
|
|
ret = devm_request_irq(charge->dev, dc_det_irq, rk817_charge_dc_det_isr,
|
|
irq_flags, "rk817_dc_det", charge);
|
|
if (ret != 0) {
|
|
dev_err(charge->dev, "rk817_dc_det_irq request failed!\n");
|
|
return ret;
|
|
}
|
|
|
|
enable_irq_wake(dc_det_irq);
|
|
|
|
if (charge->dc_charger != DC_TYPE_NONE_CHARGER)
|
|
rk817_charge_set_chrg_param(charge, charge->dc_charger);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void rk817_charge_host_evt_worker(struct work_struct *work)
|
|
{
|
|
struct rk817_charger *charge = container_of(work,
|
|
struct rk817_charger, host_work.work);
|
|
struct extcon_dev *edev = charge->cable_edev;
|
|
|
|
/* Determine cable/charger type */
|
|
if (extcon_get_state(edev, EXTCON_USB_VBUS_EN) > 0) {
|
|
DBG("receive type-c notifier event: OTG ON...\n");
|
|
if (charge->dc_in && charge->pdata->power_dc2otg) {
|
|
if (charge->otg_in)
|
|
rk817_charge_set_otg_state(charge,
|
|
USB_OTG_POWER_OFF);
|
|
DBG("otg power from dc adapter\n");
|
|
} else {
|
|
rk817_charge_set_otg_state(charge, USB_OTG_POWER_ON);
|
|
}
|
|
rk817_charge_set_otg_in(charge, ONLINE);
|
|
} else if (extcon_get_state(edev, EXTCON_USB_VBUS_EN) == 0) {
|
|
DBG("receive type-c notifier event: OTG OFF...\n");
|
|
rk817_charge_set_otg_state(charge, USB_OTG_POWER_OFF);
|
|
rk817_charge_set_otg_in(charge, OFFLINE);
|
|
}
|
|
}
|
|
|
|
static void rk817_charger_evt_worker(struct work_struct *work)
|
|
{
|
|
struct rk817_charger *charge = container_of(work,
|
|
struct rk817_charger, usb_work.work);
|
|
struct extcon_dev *edev = charge->cable_edev;
|
|
enum charger_t charger = USB_TYPE_UNKNOWN_CHARGER;
|
|
static const char * const event[] = {"UN", "NONE", "USB",
|
|
"AC", "CDP1.5A"};
|
|
|
|
/* Determine cable/charger type */
|
|
if (extcon_get_state(edev, EXTCON_CHG_USB_SDP) > 0)
|
|
charger = USB_TYPE_USB_CHARGER;
|
|
else if (extcon_get_state(edev, EXTCON_CHG_USB_DCP) > 0)
|
|
charger = USB_TYPE_AC_CHARGER;
|
|
else if (extcon_get_state(edev, EXTCON_CHG_USB_CDP) > 0)
|
|
charger = USB_TYPE_CDP_CHARGER;
|
|
|
|
if (charger != USB_TYPE_UNKNOWN_CHARGER) {
|
|
DBG("receive type-c notifier event: %s...\n",
|
|
event[charger]);
|
|
charge->usb_charger = charger;
|
|
rk817_charge_set_chrg_param(charge, charger);
|
|
}
|
|
}
|
|
|
|
static void rk817_charge_discnt_evt_worker(struct work_struct *work)
|
|
{
|
|
struct rk817_charger *charge = container_of(work,
|
|
struct rk817_charger, discnt_work.work);
|
|
|
|
if (extcon_get_state(charge->cable_edev, EXTCON_USB) == 0) {
|
|
DBG("receive type-c notifier event: DISCNT...\n");
|
|
|
|
rk817_charge_set_chrg_param(charge, USB_TYPE_NONE_CHARGER);
|
|
}
|
|
}
|
|
|
|
static void rk817_charge_bc_evt_worker(struct work_struct *work)
|
|
{
|
|
struct rk817_charger *charge = container_of(work,
|
|
struct rk817_charger,
|
|
usb_work.work);
|
|
static const char * const event_name[] = {"DISCNT", "USB", "AC",
|
|
"CDP1.5A", "UNKNOWN",
|
|
"OTG ON", "OTG OFF"};
|
|
|
|
switch (charge->bc_event) {
|
|
case USB_BC_TYPE_DISCNT:
|
|
rk817_charge_set_chrg_param(charge, USB_TYPE_NONE_CHARGER);
|
|
break;
|
|
case USB_BC_TYPE_SDP:
|
|
rk817_charge_set_chrg_param(charge, USB_TYPE_USB_CHARGER);
|
|
break;
|
|
case USB_BC_TYPE_DCP:
|
|
rk817_charge_set_chrg_param(charge, USB_TYPE_AC_CHARGER);
|
|
break;
|
|
case USB_BC_TYPE_CDP:
|
|
rk817_charge_set_chrg_param(charge, USB_TYPE_CDP_CHARGER);
|
|
break;
|
|
case USB_OTG_POWER_ON:
|
|
if (charge->pdata->power_dc2otg && charge->dc_in)
|
|
DBG("otg power from dc adapter\n");
|
|
else
|
|
rk817_charge_set_otg_state(charge, USB_OTG_POWER_ON);
|
|
break;
|
|
case USB_OTG_POWER_OFF:
|
|
rk817_charge_set_otg_state(charge, USB_OTG_POWER_OFF);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
DBG("receive bc notifier event: %s..\n", event_name[charge->bc_event]);
|
|
}
|
|
|
|
static int rk817_charger_evt_notifier(struct notifier_block *nb,
|
|
unsigned long event, void *ptr)
|
|
{
|
|
struct rk817_charger *charge =
|
|
container_of(nb, struct rk817_charger, cable_cg_nb);
|
|
|
|
queue_delayed_work(charge->usb_charger_wq, &charge->usb_work,
|
|
msecs_to_jiffies(10));
|
|
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
static int rk817_charge_host_evt_notifier(struct notifier_block *nb,
|
|
unsigned long event, void *ptr)
|
|
{
|
|
struct rk817_charger *charge =
|
|
container_of(nb, struct rk817_charger, cable_host_nb);
|
|
|
|
queue_delayed_work(charge->usb_charger_wq, &charge->host_work,
|
|
msecs_to_jiffies(10));
|
|
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
static int rk817_charge_discnt_evt_notfier(struct notifier_block *nb,
|
|
unsigned long event, void *ptr)
|
|
{
|
|
struct rk817_charger *charge =
|
|
container_of(nb, struct rk817_charger, cable_discnt_nb);
|
|
|
|
queue_delayed_work(charge->usb_charger_wq, &charge->discnt_work,
|
|
msecs_to_jiffies(10));
|
|
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
static int rk817_charge_bc_evt_notifier(struct notifier_block *nb,
|
|
unsigned long event, void *ptr)
|
|
{
|
|
struct rk817_charger *charge =
|
|
container_of(nb, struct rk817_charger, bc_nb);
|
|
|
|
charge->bc_event = event;
|
|
queue_delayed_work(charge->usb_charger_wq, &charge->usb_work,
|
|
msecs_to_jiffies(10));
|
|
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
static int rk817_charge_usb_init(struct rk817_charger *charge)
|
|
{
|
|
enum charger_t charger;
|
|
enum bc_port_type bc_type;
|
|
struct extcon_dev *edev;
|
|
struct device *dev = charge->dev;
|
|
int ret;
|
|
|
|
charge->usb_charger_wq = alloc_ordered_workqueue("%s",
|
|
WQ_MEM_RECLAIM | WQ_FREEZABLE,
|
|
"rk817-usb-wq");
|
|
|
|
/* type-C */
|
|
if (charge->pdata->extcon) {
|
|
edev = extcon_get_edev_by_phandle(dev, 0);
|
|
if (IS_ERR(edev)) {
|
|
if (PTR_ERR(edev) != -EPROBE_DEFER)
|
|
dev_err(dev, "Invalid or missing extcon\n");
|
|
return PTR_ERR(edev);
|
|
}
|
|
|
|
/* Register chargers */
|
|
INIT_DELAYED_WORK(&charge->usb_work, rk817_charger_evt_worker);
|
|
charge->cable_cg_nb.notifier_call = rk817_charger_evt_notifier;
|
|
ret = extcon_register_notifier(edev, EXTCON_CHG_USB_SDP,
|
|
&charge->cable_cg_nb);
|
|
if (ret < 0) {
|
|
dev_err(dev, "failed to register notifier for SDP\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = extcon_register_notifier(edev, EXTCON_CHG_USB_DCP,
|
|
&charge->cable_cg_nb);
|
|
if (ret < 0) {
|
|
dev_err(dev, "failed to register notifier for DCP\n");
|
|
extcon_unregister_notifier(edev, EXTCON_CHG_USB_SDP,
|
|
&charge->cable_cg_nb);
|
|
return ret;
|
|
}
|
|
|
|
ret = extcon_register_notifier(edev, EXTCON_CHG_USB_CDP,
|
|
&charge->cable_cg_nb);
|
|
if (ret < 0) {
|
|
dev_err(dev, "failed to register notifier for CDP\n");
|
|
extcon_unregister_notifier(edev, EXTCON_CHG_USB_SDP,
|
|
&charge->cable_cg_nb);
|
|
extcon_unregister_notifier(edev, EXTCON_CHG_USB_DCP,
|
|
&charge->cable_cg_nb);
|
|
return ret;
|
|
}
|
|
|
|
/* Register host */
|
|
INIT_DELAYED_WORK(&charge->host_work,
|
|
rk817_charge_host_evt_worker);
|
|
charge->cable_host_nb.notifier_call =
|
|
rk817_charge_host_evt_notifier;
|
|
ret = extcon_register_notifier(edev, EXTCON_USB_VBUS_EN,
|
|
&charge->cable_host_nb);
|
|
if (ret < 0) {
|
|
dev_err(dev, "failed to register notifier for HOST\n");
|
|
extcon_unregister_notifier(edev, EXTCON_CHG_USB_SDP,
|
|
&charge->cable_cg_nb);
|
|
extcon_unregister_notifier(edev, EXTCON_CHG_USB_DCP,
|
|
&charge->cable_cg_nb);
|
|
extcon_unregister_notifier(edev, EXTCON_CHG_USB_CDP,
|
|
&charge->cable_cg_nb);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Register discnt usb */
|
|
INIT_DELAYED_WORK(&charge->discnt_work,
|
|
rk817_charge_discnt_evt_worker);
|
|
charge->cable_discnt_nb.notifier_call =
|
|
rk817_charge_discnt_evt_notfier;
|
|
ret = extcon_register_notifier(edev, EXTCON_USB,
|
|
&charge->cable_discnt_nb);
|
|
if (ret < 0) {
|
|
dev_err(dev, "failed to register notifier for HOST\n");
|
|
extcon_unregister_notifier(edev, EXTCON_CHG_USB_SDP,
|
|
&charge->cable_cg_nb);
|
|
extcon_unregister_notifier(edev, EXTCON_CHG_USB_DCP,
|
|
&charge->cable_cg_nb);
|
|
extcon_unregister_notifier(edev, EXTCON_CHG_USB_CDP,
|
|
&charge->cable_cg_nb);
|
|
extcon_unregister_notifier(edev, EXTCON_USB_VBUS_EN,
|
|
&charge->cable_host_nb);
|
|
return ret;
|
|
}
|
|
|
|
charge->cable_edev = edev;
|
|
|
|
DBG("register typec extcon evt notifier\n");
|
|
} else {
|
|
INIT_DELAYED_WORK(&charge->usb_work,
|
|
rk817_charge_bc_evt_worker);
|
|
charge->bc_nb.notifier_call = rk817_charge_bc_evt_notifier;
|
|
ret = rk_bc_detect_notifier_register(&charge->bc_nb, &bc_type);
|
|
if (ret) {
|
|
dev_err(dev, "failed to register notifier for bc\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
switch (bc_type) {
|
|
case USB_BC_TYPE_DISCNT:
|
|
charger = USB_TYPE_NONE_CHARGER;
|
|
break;
|
|
case USB_BC_TYPE_SDP:
|
|
case USB_BC_TYPE_CDP:
|
|
charger = USB_TYPE_USB_CHARGER;
|
|
break;
|
|
case USB_BC_TYPE_DCP:
|
|
charger = USB_TYPE_AC_CHARGER;
|
|
break;
|
|
default:
|
|
charger = USB_TYPE_NONE_CHARGER;
|
|
break;
|
|
}
|
|
|
|
charge->usb_charger = charger;
|
|
if (charge->dc_charger != DC_TYPE_NONE_CHARGER)
|
|
rk817_charge_set_chrg_param(charge,
|
|
charge->usb_charger);
|
|
|
|
DBG("register bc evt notifier\n");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void rk817_charge_pre_init(struct rk817_charger *charge)
|
|
{
|
|
charge->max_chrg_current = charge->pdata->max_chrg_current;
|
|
charge->max_input_current = charge->pdata->max_input_current;
|
|
charge->max_chrg_voltage = charge->pdata->max_chrg_voltage;
|
|
charge->min_input_voltage = charge->pdata->min_input_voltage;
|
|
charge->chrg_finish_cur = charge->pdata->chrg_finish_cur;
|
|
charge->chrg_term_mode = charge->pdata->chrg_term_mode;
|
|
|
|
rk817_charge_set_input_voltage(charge, charge->min_input_voltage);
|
|
|
|
rk817_charge_set_chrg_voltage(charge, charge->max_chrg_voltage);
|
|
rk817_charge_set_chrg_current(charge, charge->max_chrg_current);
|
|
|
|
rk817_charge_set_chrg_finish_condition(charge);
|
|
|
|
if (rk817_charge_get_otg_state(charge))
|
|
rk817_charge_otg_disable(charge);
|
|
rk817_charge_field_write(charge, OTG_EN, OTG_DISABLE);
|
|
rk817_charge_set_otg_in(charge, OFFLINE);
|
|
|
|
if (!charge->pdata->gate_function_disable)
|
|
rk817_charge_sys_can_sd_disable(charge);
|
|
rk817_charge_usb_to_sys_enable(charge);
|
|
rk817_charge_enable_charge(charge);
|
|
|
|
rk817_charge_set_charge_clock(charge, CHRG_CLK_2M);
|
|
}
|
|
|
|
static void rk817_chage_debug(struct rk817_charger *charge)
|
|
{
|
|
rk817_charge_get_charge_status(charge);
|
|
DBG("OTG state : %d\n", rk817_charge_get_otg_state(charge));
|
|
DBG("charge state: %d\n", rk817_charge_get_charge_state(charge));
|
|
DBG("max_chrg_current: %d\n"
|
|
"max_input_current: %d\n"
|
|
"min_input_voltage: %d\n"
|
|
"max_chrg_voltage: %d\n"
|
|
"max_chrg_finish_cur: %d\n"
|
|
"chrg_term_mode: %d\n",
|
|
charge->max_chrg_current,
|
|
charge->max_input_current,
|
|
charge->min_input_voltage,
|
|
charge->max_chrg_voltage,
|
|
charge->chrg_finish_cur,
|
|
charge->chrg_term_mode);
|
|
}
|
|
|
|
static int rk817_charge_get_otg5v_regulator(struct rk817_charger *charge)
|
|
{
|
|
int ret;
|
|
|
|
charge->otg5v_rdev = devm_regulator_get(charge->dev, "otg_switch");
|
|
if (IS_ERR(charge->otg5v_rdev)) {
|
|
ret = PTR_ERR(charge->otg5v_rdev);
|
|
dev_warn(charge->dev, "failed to get otg regulator: %d\n", ret);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_OF
|
|
static int rk817_charge_parse_dt(struct rk817_charger *charge)
|
|
{
|
|
struct charger_platform_data *pdata;
|
|
enum of_gpio_flags flags;
|
|
struct device *dev = charge->dev;
|
|
struct device_node *np = charge->dev->of_node;
|
|
int ret;
|
|
|
|
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
|
|
if (!pdata)
|
|
return -ENOMEM;
|
|
|
|
charge->pdata = pdata;
|
|
pdata->max_chrg_current = DEFAULT_CHRG_CURRENT;
|
|
pdata->max_input_current = DEFAULT_INPUT_CURRENT;
|
|
pdata->max_chrg_voltage = DEFAULT_CHRG_VOLTAGE;
|
|
pdata->min_input_voltage = DEFAULT_INPUT_VOLTAGE;
|
|
pdata->chrg_finish_cur = DEFAULT_CHRG_TERM_CUR;
|
|
pdata->chrg_term_mode = DEFAULT_CHRG_TERM_MODE;
|
|
|
|
pdata->extcon = of_property_read_bool(np, "extcon");
|
|
|
|
ret = of_property_read_u32(np, "max_chrg_current",
|
|
&pdata->max_chrg_current);
|
|
if (ret < 0)
|
|
dev_err(dev, "max_chrg_current missing!\n");
|
|
|
|
ret = of_property_read_u32(np, "max_input_current",
|
|
&pdata->max_input_current);
|
|
if (ret < 0)
|
|
dev_err(dev, "max_input_current missing!\n");
|
|
|
|
ret = of_property_read_u32(np, "max_chrg_voltage",
|
|
&pdata->max_chrg_voltage);
|
|
if (ret < 0)
|
|
dev_err(dev, "max_chrg_voltage missing!\n");
|
|
|
|
ret = of_property_read_u32(np, "min_input_voltage",
|
|
&pdata->min_input_voltage);
|
|
if (ret < 0)
|
|
dev_WARN(dev, "min_input_voltage missing!\n");
|
|
|
|
ret = of_property_read_u32(np, "chrg_finish_cur",
|
|
&pdata->chrg_finish_cur);
|
|
|
|
if (ret < 0)
|
|
dev_WARN(dev, "chrg_term_mode missing!\n");
|
|
|
|
ret = of_property_read_u32(np, "chrg_term_mode",
|
|
&pdata->chrg_term_mode);
|
|
if (ret < 0)
|
|
dev_WARN(dev, "chrg_term_mode missing!\n");
|
|
|
|
ret = of_property_read_u32(np, "virtual_power", &pdata->virtual_power);
|
|
if (ret < 0)
|
|
dev_err(dev, "virtual_power missing!\n");
|
|
|
|
ret = of_property_read_u32(np, "power_dc2otg", &pdata->power_dc2otg);
|
|
if (ret < 0)
|
|
dev_err(dev, "power_dc2otg missing!\n");
|
|
|
|
ret = of_property_read_u32(np, "sample_res", &pdata->sample_res);
|
|
if (ret < 0) {
|
|
pdata->sample_res = SAMPLE_RES_10MR;
|
|
dev_err(dev, "sample_res missing!\n");
|
|
}
|
|
|
|
ret = of_property_read_u32(np, "otg5v_suspend_enable",
|
|
&pdata->otg5v_suspend_enable);
|
|
if (ret < 0) {
|
|
pdata->otg5v_suspend_enable = 1;
|
|
dev_err(dev, "otg5v_suspend_enable missing!\n");
|
|
}
|
|
|
|
ret = of_property_read_u32(np, "gate_function_disable",
|
|
&pdata->gate_function_disable);
|
|
if (ret < 0)
|
|
dev_err(dev, "gate_function_disable missing!\n");
|
|
|
|
if (!is_battery_exist(charge))
|
|
pdata->virtual_power = 1;
|
|
|
|
charge->res_div = (charge->pdata->sample_res == SAMPLE_RES_10MR) ?
|
|
SAMPLE_RES_DIV1 : SAMPLE_RES_DIV2;
|
|
|
|
if (!of_find_property(np, "dc_det_gpio", &ret)) {
|
|
pdata->support_dc_det = false;
|
|
DBG("not support dc\n");
|
|
} else {
|
|
pdata->support_dc_det = true;
|
|
pdata->dc_det_pin = of_get_named_gpio_flags(np, "dc_det_gpio",
|
|
0, &flags);
|
|
if (gpio_is_valid(pdata->dc_det_pin)) {
|
|
DBG("support dc\n");
|
|
pdata->dc_det_level = (flags & OF_GPIO_ACTIVE_LOW) ?
|
|
0 : 1;
|
|
} else {
|
|
dev_err(dev, "invalid dc det gpio!\n");
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
DBG("input_current:%d\n"
|
|
"input_min_voltage: %d\n"
|
|
"chrg_current:%d\n"
|
|
"chrg_voltage:%d\n"
|
|
"sample_res:%d\n"
|
|
"extcon:%d\n"
|
|
"virtual_power:%d\n"
|
|
"power_dc2otg:%d\n",
|
|
pdata->max_input_current, pdata->min_input_voltage,
|
|
pdata->max_chrg_current, pdata->max_chrg_voltage,
|
|
pdata->sample_res, pdata->extcon,
|
|
pdata->virtual_power, pdata->power_dc2otg);
|
|
|
|
return 0;
|
|
}
|
|
#else
|
|
static int rk817_charge_parse_dt(struct rk817_charger *charge)
|
|
{
|
|
return -ENODEV;
|
|
}
|
|
#endif
|
|
|
|
static void rk817_charge_irq_delay_work(struct work_struct *work)
|
|
{
|
|
struct rk817_charger *charge = container_of(work,
|
|
struct rk817_charger, irq_work.work);
|
|
|
|
if (charge->plugin_trigger) {
|
|
DBG("pmic: plug in\n");
|
|
charge->plugin_trigger = 0;
|
|
if (charge->pdata->extcon)
|
|
queue_delayed_work(charge->usb_charger_wq, &charge->usb_work,
|
|
msecs_to_jiffies(10));
|
|
} else if (charge->plugout_trigger) {
|
|
DBG("pmic: plug out\n");
|
|
charge->plugout_trigger = 0;
|
|
rk817_charge_set_chrg_param(charge, USB_TYPE_NONE_CHARGER);
|
|
rk817_charge_set_chrg_param(charge, DC_TYPE_NONE_CHARGER);
|
|
} else {
|
|
DBG("pmic: unknown irq\n");
|
|
}
|
|
}
|
|
|
|
static irqreturn_t rk817_plug_in_isr(int irq, void *cg)
|
|
{
|
|
struct rk817_charger *charge;
|
|
|
|
charge = (struct rk817_charger *)cg;
|
|
charge->plugin_trigger = 1;
|
|
queue_delayed_work(charge->usb_charger_wq, &charge->irq_work,
|
|
msecs_to_jiffies(10));
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t rk817_plug_out_isr(int irq, void *cg)
|
|
{
|
|
struct rk817_charger *charge;
|
|
|
|
charge = (struct rk817_charger *)cg;
|
|
charge->plugout_trigger = 1;
|
|
queue_delayed_work(charge->usb_charger_wq, &charge->irq_work,
|
|
msecs_to_jiffies(10));
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int rk817_charge_init_irqs(struct rk817_charger *charge)
|
|
{
|
|
struct rk808 *rk817 = charge->rk817;
|
|
struct platform_device *pdev = charge->pdev;
|
|
int ret, plug_in_irq, plug_out_irq;
|
|
|
|
plug_in_irq = regmap_irq_get_virq(rk817->irq_data, RK817_IRQ_PLUG_IN);
|
|
if (plug_in_irq < 0) {
|
|
dev_err(charge->dev, "plug_in_irq request failed!\n");
|
|
return plug_in_irq;
|
|
}
|
|
|
|
plug_out_irq = regmap_irq_get_virq(rk817->irq_data, RK817_IRQ_PLUG_OUT);
|
|
if (plug_out_irq < 0) {
|
|
dev_err(charge->dev, "plug_out_irq request failed!\n");
|
|
return plug_out_irq;
|
|
}
|
|
|
|
ret = devm_request_threaded_irq(charge->dev, plug_in_irq, NULL,
|
|
rk817_plug_in_isr,
|
|
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
|
|
"rk817_plug_in", charge);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "plug_in_irq request failed!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = devm_request_threaded_irq(charge->dev, plug_out_irq, NULL,
|
|
rk817_plug_out_isr,
|
|
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
|
|
"rk817_plug_out", charge);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "plug_out_irq request failed!\n");
|
|
return ret;
|
|
}
|
|
|
|
charge->plugin_irq = plug_in_irq;
|
|
charge->plugout_irq = plug_out_irq;
|
|
|
|
INIT_DELAYED_WORK(&charge->irq_work, rk817_charge_irq_delay_work);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id rk817_charge_of_match[] = {
|
|
{ .compatible = "rk817,charger", },
|
|
{ },
|
|
};
|
|
|
|
static int rk817_charge_probe(struct platform_device *pdev)
|
|
{
|
|
struct rk808 *rk817 = dev_get_drvdata(pdev->dev.parent);
|
|
const struct of_device_id *of_id =
|
|
of_match_device(rk817_charge_of_match, &pdev->dev);
|
|
struct i2c_client *client = rk817->i2c;
|
|
struct rk817_charger *charge;
|
|
int i;
|
|
int ret;
|
|
|
|
if (!of_id) {
|
|
dev_err(&pdev->dev, "Failed to find matching dt id\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
charge = devm_kzalloc(&pdev->dev, sizeof(*charge), GFP_KERNEL);
|
|
if (!charge)
|
|
return -EINVAL;
|
|
|
|
charge->rk817 = rk817;
|
|
charge->pdev = pdev;
|
|
charge->dev = &pdev->dev;
|
|
charge->client = client;
|
|
platform_set_drvdata(pdev, charge);
|
|
|
|
charge->regmap = rk817->regmap;
|
|
if (IS_ERR(charge->regmap)) {
|
|
dev_err(charge->dev, "Failed to initialize regmap\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(rk817_charge_reg_fields); i++) {
|
|
const struct reg_field *reg_fields = rk817_charge_reg_fields;
|
|
|
|
charge->rmap_fields[i] =
|
|
devm_regmap_field_alloc(charge->dev,
|
|
charge->regmap,
|
|
reg_fields[i]);
|
|
if (IS_ERR(charge->rmap_fields[i])) {
|
|
dev_err(charge->dev, "cannot allocate regmap field\n");
|
|
return PTR_ERR(charge->rmap_fields[i]);
|
|
}
|
|
}
|
|
|
|
ret = rk817_charge_parse_dt(charge);
|
|
if (ret < 0) {
|
|
dev_err(charge->dev, "charge parse dt failed!\n");
|
|
return ret;
|
|
}
|
|
rk817_charge_get_otg5v_regulator(charge);
|
|
|
|
rk817_charge_pre_init(charge);
|
|
|
|
ret = rk817_charge_init_power_supply(charge);
|
|
if (ret) {
|
|
dev_err(charge->dev, "init power supply fail!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = rk817_charge_init_dc(charge);
|
|
if (ret) {
|
|
dev_err(charge->dev, "init dc failed!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = rk817_charge_usb_init(charge);
|
|
if (ret) {
|
|
dev_err(charge->dev, "init usb failed!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = rk817_charge_init_irqs(charge);
|
|
if (ret) {
|
|
dev_err(charge->dev, "init irqs failed!\n");
|
|
goto irq_fail;
|
|
}
|
|
|
|
if (charge->pdata->extcon) {
|
|
schedule_delayed_work(&charge->host_work, 0);
|
|
schedule_delayed_work(&charge->usb_work, 0);
|
|
}
|
|
|
|
rk817_chage_debug(charge);
|
|
DBG("driver version: %s\n", CHARGE_DRIVER_VERSION);
|
|
|
|
return 0;
|
|
irq_fail:
|
|
if (charge->pdata->extcon) {
|
|
cancel_delayed_work_sync(&charge->host_work);
|
|
cancel_delayed_work_sync(&charge->discnt_work);
|
|
}
|
|
|
|
cancel_delayed_work_sync(&charge->usb_work);
|
|
cancel_delayed_work_sync(&charge->dc_work);
|
|
cancel_delayed_work_sync(&charge->irq_work);
|
|
destroy_workqueue(charge->usb_charger_wq);
|
|
destroy_workqueue(charge->dc_charger_wq);
|
|
|
|
if (charge->pdata->extcon) {
|
|
extcon_unregister_notifier(charge->cable_edev,
|
|
EXTCON_CHG_USB_SDP,
|
|
&charge->cable_cg_nb);
|
|
extcon_unregister_notifier(charge->cable_edev,
|
|
EXTCON_CHG_USB_DCP,
|
|
&charge->cable_cg_nb);
|
|
extcon_unregister_notifier(charge->cable_edev,
|
|
EXTCON_CHG_USB_CDP,
|
|
&charge->cable_cg_nb);
|
|
extcon_unregister_notifier(charge->cable_edev,
|
|
EXTCON_USB_VBUS_EN,
|
|
&charge->cable_host_nb);
|
|
extcon_unregister_notifier(charge->cable_edev,
|
|
EXTCON_USB,
|
|
&charge->cable_discnt_nb);
|
|
} else {
|
|
rk_bc_detect_notifier_unregister(&charge->bc_nb);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
static int rk817_charge_pm_suspend(struct device *dev)
|
|
{
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
struct rk817_charger *charge = dev_get_drvdata(&pdev->dev);
|
|
|
|
charge->otg_slp_state = rk817_charge_get_otg_slp_state(charge);
|
|
|
|
/* enable sleep boost5v and otg5v */
|
|
if (charge->pdata->otg5v_suspend_enable) {
|
|
if ((charge->otg_in && !charge->dc_in) ||
|
|
(charge->otg_in && charge->dc_in &&
|
|
!charge->pdata->power_dc2otg)) {
|
|
rk817_charge_otg_slp_enable(charge);
|
|
DBG("suspend: otg 5v on\n");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* disable sleep otg5v */
|
|
rk817_charge_otg_slp_disable(charge);
|
|
DBG("suspend: otg 5v off\n");
|
|
return 0;
|
|
}
|
|
|
|
static int rk817_charge_pm_resume(struct device *dev)
|
|
{
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
struct rk817_charger *charge = dev_get_drvdata(&pdev->dev);
|
|
|
|
/* resume sleep boost5v and otg5v */
|
|
if (charge->otg_slp_state)
|
|
rk817_charge_otg_slp_enable(charge);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
static SIMPLE_DEV_PM_OPS(rk817_charge_pm_ops,
|
|
rk817_charge_pm_suspend, rk817_charge_pm_resume);
|
|
|
|
static void rk817_charger_shutdown(struct platform_device *dev)
|
|
{
|
|
struct rk817_charger *charge = platform_get_drvdata(dev);
|
|
|
|
/* type-c only */
|
|
if (charge->pdata->extcon) {
|
|
cancel_delayed_work_sync(&charge->host_work);
|
|
cancel_delayed_work_sync(&charge->discnt_work);
|
|
}
|
|
|
|
rk817_charge_set_otg_state(charge, USB_OTG_POWER_OFF);
|
|
rk817_charge_boost_disable(charge);
|
|
disable_irq(charge->plugin_irq);
|
|
disable_irq(charge->plugout_irq);
|
|
|
|
cancel_delayed_work_sync(&charge->usb_work);
|
|
cancel_delayed_work_sync(&charge->dc_work);
|
|
cancel_delayed_work_sync(&charge->irq_work);
|
|
flush_workqueue(charge->usb_charger_wq);
|
|
flush_workqueue(charge->dc_charger_wq);
|
|
|
|
if (charge->pdata->extcon) {
|
|
extcon_unregister_notifier(charge->cable_edev,
|
|
EXTCON_CHG_USB_SDP,
|
|
&charge->cable_cg_nb);
|
|
extcon_unregister_notifier(charge->cable_edev,
|
|
EXTCON_CHG_USB_DCP,
|
|
&charge->cable_cg_nb);
|
|
extcon_unregister_notifier(charge->cable_edev,
|
|
EXTCON_CHG_USB_CDP,
|
|
&charge->cable_cg_nb);
|
|
extcon_unregister_notifier(charge->cable_edev,
|
|
EXTCON_USB_VBUS_EN,
|
|
&charge->cable_host_nb);
|
|
extcon_unregister_notifier(charge->cable_edev, EXTCON_USB,
|
|
&charge->cable_discnt_nb);
|
|
} else {
|
|
rk_bc_detect_notifier_unregister(&charge->bc_nb);
|
|
}
|
|
|
|
DBG("shutdown: ac=%d usb=%d dc=%d otg=%d\n",
|
|
charge->ac_in, charge->usb_in, charge->dc_in, charge->otg_in);
|
|
}
|
|
|
|
static struct platform_driver rk817_charge_driver = {
|
|
.probe = rk817_charge_probe,
|
|
.shutdown = rk817_charger_shutdown,
|
|
.driver = {
|
|
.name = "rk817-charger",
|
|
.pm = &rk817_charge_pm_ops,
|
|
.of_match_table = of_match_ptr(rk817_charge_of_match),
|
|
},
|
|
};
|
|
|
|
static int __init rk817_charge_init(void)
|
|
{
|
|
return platform_driver_register(&rk817_charge_driver);
|
|
}
|
|
fs_initcall_sync(rk817_charge_init);
|
|
|
|
static void __exit rk817_charge_exit(void)
|
|
{
|
|
platform_driver_unregister(&rk817_charge_driver);
|
|
}
|
|
module_exit(rk817_charge_exit);
|
|
|
|
MODULE_DESCRIPTION("RK817 Charge driver");
|
|
MODULE_LICENSE("GPL");
|