162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * axp288_charger.c - X-power AXP288 PMIC Charger driver 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2016-2017 Hans de Goede <hdegoede@redhat.com> 662306a36Sopenharmony_ci * Copyright (C) 2014 Intel Corporation 762306a36Sopenharmony_ci * Author: Ramakrishna Pallala <ramakrishna.pallala@intel.com> 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include <linux/acpi.h> 1162306a36Sopenharmony_ci#include <linux/bitops.h> 1262306a36Sopenharmony_ci#include <linux/module.h> 1362306a36Sopenharmony_ci#include <linux/device.h> 1462306a36Sopenharmony_ci#include <linux/regmap.h> 1562306a36Sopenharmony_ci#include <linux/workqueue.h> 1662306a36Sopenharmony_ci#include <linux/delay.h> 1762306a36Sopenharmony_ci#include <linux/platform_device.h> 1862306a36Sopenharmony_ci#include <linux/usb/otg.h> 1962306a36Sopenharmony_ci#include <linux/notifier.h> 2062306a36Sopenharmony_ci#include <linux/power_supply.h> 2162306a36Sopenharmony_ci#include <linux/property.h> 2262306a36Sopenharmony_ci#include <linux/mfd/axp20x.h> 2362306a36Sopenharmony_ci#include <linux/extcon.h> 2462306a36Sopenharmony_ci#include <linux/dmi.h> 2562306a36Sopenharmony_ci#include <asm/iosf_mbi.h> 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_ci#define PS_STAT_VBUS_TRIGGER BIT(0) 2862306a36Sopenharmony_ci#define PS_STAT_BAT_CHRG_DIR BIT(2) 2962306a36Sopenharmony_ci#define PS_STAT_VBAT_ABOVE_VHOLD BIT(3) 3062306a36Sopenharmony_ci#define PS_STAT_VBUS_VALID BIT(4) 3162306a36Sopenharmony_ci#define PS_STAT_VBUS_PRESENT BIT(5) 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci#define CHRG_STAT_BAT_SAFE_MODE BIT(3) 3462306a36Sopenharmony_ci#define CHRG_STAT_BAT_VALID BIT(4) 3562306a36Sopenharmony_ci#define CHRG_STAT_BAT_PRESENT BIT(5) 3662306a36Sopenharmony_ci#define CHRG_STAT_CHARGING BIT(6) 3762306a36Sopenharmony_ci#define CHRG_STAT_PMIC_OTP BIT(7) 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci#define VBUS_ISPOUT_CUR_LIM_MASK 0x03 4062306a36Sopenharmony_ci#define VBUS_ISPOUT_CUR_LIM_BIT_POS 0 4162306a36Sopenharmony_ci#define VBUS_ISPOUT_CUR_LIM_900MA 0x0 /* 900mA */ 4262306a36Sopenharmony_ci#define VBUS_ISPOUT_CUR_LIM_1500MA 0x1 /* 1500mA */ 4362306a36Sopenharmony_ci#define VBUS_ISPOUT_CUR_LIM_2000MA 0x2 /* 2000mA */ 4462306a36Sopenharmony_ci#define VBUS_ISPOUT_CUR_NO_LIM 0x3 /* 2500mA */ 4562306a36Sopenharmony_ci#define VBUS_ISPOUT_VHOLD_SET_MASK 0x38 4662306a36Sopenharmony_ci#define VBUS_ISPOUT_VHOLD_SET_BIT_POS 0x3 4762306a36Sopenharmony_ci#define VBUS_ISPOUT_VHOLD_SET_OFFSET 4000 /* 4000mV */ 4862306a36Sopenharmony_ci#define VBUS_ISPOUT_VHOLD_SET_LSB_RES 100 /* 100mV */ 4962306a36Sopenharmony_ci#define VBUS_ISPOUT_VHOLD_SET_4400MV 0x4 /* 4400mV */ 5062306a36Sopenharmony_ci#define VBUS_ISPOUT_VBUS_PATH_DIS BIT(7) 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci#define CHRG_CCCV_CC_MASK 0xf /* 4 bits */ 5362306a36Sopenharmony_ci#define CHRG_CCCV_CC_BIT_POS 0 5462306a36Sopenharmony_ci#define CHRG_CCCV_CC_OFFSET 200 /* 200mA */ 5562306a36Sopenharmony_ci#define CHRG_CCCV_CC_LSB_RES 200 /* 200mA */ 5662306a36Sopenharmony_ci#define CHRG_CCCV_ITERM_20P BIT(4) /* 20% of CC */ 5762306a36Sopenharmony_ci#define CHRG_CCCV_CV_MASK 0x60 /* 2 bits */ 5862306a36Sopenharmony_ci#define CHRG_CCCV_CV_BIT_POS 5 5962306a36Sopenharmony_ci#define CHRG_CCCV_CV_4100MV 0x0 /* 4.10V */ 6062306a36Sopenharmony_ci#define CHRG_CCCV_CV_4150MV 0x1 /* 4.15V */ 6162306a36Sopenharmony_ci#define CHRG_CCCV_CV_4200MV 0x2 /* 4.20V */ 6262306a36Sopenharmony_ci#define CHRG_CCCV_CV_4350MV 0x3 /* 4.35V */ 6362306a36Sopenharmony_ci#define CHRG_CCCV_CHG_EN BIT(7) 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci#define CNTL2_CC_TIMEOUT_MASK 0x3 /* 2 bits */ 6662306a36Sopenharmony_ci#define CNTL2_CC_TIMEOUT_OFFSET 6 /* 6 Hrs */ 6762306a36Sopenharmony_ci#define CNTL2_CC_TIMEOUT_LSB_RES 2 /* 2 Hrs */ 6862306a36Sopenharmony_ci#define CNTL2_CC_TIMEOUT_12HRS 0x3 /* 12 Hrs */ 6962306a36Sopenharmony_ci#define CNTL2_CHGLED_TYPEB BIT(4) 7062306a36Sopenharmony_ci#define CNTL2_CHG_OUT_TURNON BIT(5) 7162306a36Sopenharmony_ci#define CNTL2_PC_TIMEOUT_MASK 0xC0 7262306a36Sopenharmony_ci#define CNTL2_PC_TIMEOUT_OFFSET 40 /* 40 mins */ 7362306a36Sopenharmony_ci#define CNTL2_PC_TIMEOUT_LSB_RES 10 /* 10 mins */ 7462306a36Sopenharmony_ci#define CNTL2_PC_TIMEOUT_70MINS 0x3 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci#define CHRG_ILIM_TEMP_LOOP_EN BIT(3) 7762306a36Sopenharmony_ci#define CHRG_VBUS_ILIM_MASK 0xf0 7862306a36Sopenharmony_ci#define CHRG_VBUS_ILIM_BIT_POS 4 7962306a36Sopenharmony_ci#define CHRG_VBUS_ILIM_100MA 0x0 /* 100mA */ 8062306a36Sopenharmony_ci#define CHRG_VBUS_ILIM_500MA 0x1 /* 500mA */ 8162306a36Sopenharmony_ci#define CHRG_VBUS_ILIM_900MA 0x2 /* 900mA */ 8262306a36Sopenharmony_ci#define CHRG_VBUS_ILIM_1500MA 0x3 /* 1500mA */ 8362306a36Sopenharmony_ci#define CHRG_VBUS_ILIM_2000MA 0x4 /* 2000mA */ 8462306a36Sopenharmony_ci#define CHRG_VBUS_ILIM_2500MA 0x5 /* 2500mA */ 8562306a36Sopenharmony_ci#define CHRG_VBUS_ILIM_3000MA 0x6 /* 3000mA */ 8662306a36Sopenharmony_ci#define CHRG_VBUS_ILIM_3500MA 0x7 /* 3500mA */ 8762306a36Sopenharmony_ci#define CHRG_VBUS_ILIM_4000MA 0x8 /* 4000mA */ 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci#define CHRG_VLTFC_0C 0xA5 /* 0 DegC */ 9062306a36Sopenharmony_ci#define CHRG_VHTFC_45C 0x1F /* 45 DegC */ 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci#define FG_CNTL_OCV_ADJ_EN BIT(3) 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci#define CV_4100MV 4100 /* 4100mV */ 9562306a36Sopenharmony_ci#define CV_4150MV 4150 /* 4150mV */ 9662306a36Sopenharmony_ci#define CV_4200MV 4200 /* 4200mV */ 9762306a36Sopenharmony_ci#define CV_4350MV 4350 /* 4350mV */ 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_ci#define AXP288_REG_UPDATE_INTERVAL (60 * HZ) 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci#define AXP288_EXTCON_DEV_NAME "axp288_extcon" 10262306a36Sopenharmony_ci#define USB_HOST_EXTCON_HID "INT3496" 10362306a36Sopenharmony_ci#define USB_HOST_EXTCON_NAME "INT3496:00" 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_cienum { 10662306a36Sopenharmony_ci VBUS_OV_IRQ = 0, 10762306a36Sopenharmony_ci CHARGE_DONE_IRQ, 10862306a36Sopenharmony_ci CHARGE_CHARGING_IRQ, 10962306a36Sopenharmony_ci BAT_SAFE_QUIT_IRQ, 11062306a36Sopenharmony_ci BAT_SAFE_ENTER_IRQ, 11162306a36Sopenharmony_ci QCBTU_IRQ, 11262306a36Sopenharmony_ci CBTU_IRQ, 11362306a36Sopenharmony_ci QCBTO_IRQ, 11462306a36Sopenharmony_ci CBTO_IRQ, 11562306a36Sopenharmony_ci CHRG_INTR_END, 11662306a36Sopenharmony_ci}; 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_cistruct axp288_chrg_info { 11962306a36Sopenharmony_ci struct platform_device *pdev; 12062306a36Sopenharmony_ci struct regmap *regmap; 12162306a36Sopenharmony_ci struct regmap_irq_chip_data *regmap_irqc; 12262306a36Sopenharmony_ci int irq[CHRG_INTR_END]; 12362306a36Sopenharmony_ci struct power_supply *psy_usb; 12462306a36Sopenharmony_ci struct mutex lock; 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci /* OTG/Host mode */ 12762306a36Sopenharmony_ci struct { 12862306a36Sopenharmony_ci struct work_struct work; 12962306a36Sopenharmony_ci struct extcon_dev *cable; 13062306a36Sopenharmony_ci struct notifier_block id_nb; 13162306a36Sopenharmony_ci bool id_short; 13262306a36Sopenharmony_ci } otg; 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci /* SDP/CDP/DCP USB charging cable notifications */ 13562306a36Sopenharmony_ci struct { 13662306a36Sopenharmony_ci struct extcon_dev *edev; 13762306a36Sopenharmony_ci struct notifier_block nb; 13862306a36Sopenharmony_ci struct work_struct work; 13962306a36Sopenharmony_ci } cable; 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci int cc; 14262306a36Sopenharmony_ci int cv; 14362306a36Sopenharmony_ci int max_cc; 14462306a36Sopenharmony_ci int max_cv; 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci unsigned long last_updated; 14762306a36Sopenharmony_ci unsigned int input_status; 14862306a36Sopenharmony_ci unsigned int op_mode; 14962306a36Sopenharmony_ci unsigned int backend_control; 15062306a36Sopenharmony_ci bool valid; 15162306a36Sopenharmony_ci}; 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_cistatic inline int axp288_charger_set_cc(struct axp288_chrg_info *info, int cc) 15462306a36Sopenharmony_ci{ 15562306a36Sopenharmony_ci u8 reg_val; 15662306a36Sopenharmony_ci int ret; 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci if (cc < CHRG_CCCV_CC_OFFSET) 15962306a36Sopenharmony_ci cc = CHRG_CCCV_CC_OFFSET; 16062306a36Sopenharmony_ci else if (cc > info->max_cc) 16162306a36Sopenharmony_ci cc = info->max_cc; 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci reg_val = (cc - CHRG_CCCV_CC_OFFSET) / CHRG_CCCV_CC_LSB_RES; 16462306a36Sopenharmony_ci cc = (reg_val * CHRG_CCCV_CC_LSB_RES) + CHRG_CCCV_CC_OFFSET; 16562306a36Sopenharmony_ci reg_val = reg_val << CHRG_CCCV_CC_BIT_POS; 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci ret = regmap_update_bits(info->regmap, 16862306a36Sopenharmony_ci AXP20X_CHRG_CTRL1, 16962306a36Sopenharmony_ci CHRG_CCCV_CC_MASK, reg_val); 17062306a36Sopenharmony_ci if (ret >= 0) 17162306a36Sopenharmony_ci info->cc = cc; 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci return ret; 17462306a36Sopenharmony_ci} 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_cistatic inline int axp288_charger_set_cv(struct axp288_chrg_info *info, int cv) 17762306a36Sopenharmony_ci{ 17862306a36Sopenharmony_ci u8 reg_val; 17962306a36Sopenharmony_ci int ret; 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci if (cv <= CV_4100MV) { 18262306a36Sopenharmony_ci reg_val = CHRG_CCCV_CV_4100MV; 18362306a36Sopenharmony_ci cv = CV_4100MV; 18462306a36Sopenharmony_ci } else if (cv <= CV_4150MV) { 18562306a36Sopenharmony_ci reg_val = CHRG_CCCV_CV_4150MV; 18662306a36Sopenharmony_ci cv = CV_4150MV; 18762306a36Sopenharmony_ci } else if (cv <= CV_4200MV) { 18862306a36Sopenharmony_ci reg_val = CHRG_CCCV_CV_4200MV; 18962306a36Sopenharmony_ci cv = CV_4200MV; 19062306a36Sopenharmony_ci } else { 19162306a36Sopenharmony_ci reg_val = CHRG_CCCV_CV_4350MV; 19262306a36Sopenharmony_ci cv = CV_4350MV; 19362306a36Sopenharmony_ci } 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci reg_val = reg_val << CHRG_CCCV_CV_BIT_POS; 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci ret = regmap_update_bits(info->regmap, 19862306a36Sopenharmony_ci AXP20X_CHRG_CTRL1, 19962306a36Sopenharmony_ci CHRG_CCCV_CV_MASK, reg_val); 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_ci if (ret >= 0) 20262306a36Sopenharmony_ci info->cv = cv; 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_ci return ret; 20562306a36Sopenharmony_ci} 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_cistatic int axp288_charger_get_vbus_inlmt(struct axp288_chrg_info *info) 20862306a36Sopenharmony_ci{ 20962306a36Sopenharmony_ci unsigned int val; 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci val = info->backend_control; 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci val >>= CHRG_VBUS_ILIM_BIT_POS; 21462306a36Sopenharmony_ci switch (val) { 21562306a36Sopenharmony_ci case CHRG_VBUS_ILIM_100MA: 21662306a36Sopenharmony_ci return 100000; 21762306a36Sopenharmony_ci case CHRG_VBUS_ILIM_500MA: 21862306a36Sopenharmony_ci return 500000; 21962306a36Sopenharmony_ci case CHRG_VBUS_ILIM_900MA: 22062306a36Sopenharmony_ci return 900000; 22162306a36Sopenharmony_ci case CHRG_VBUS_ILIM_1500MA: 22262306a36Sopenharmony_ci return 1500000; 22362306a36Sopenharmony_ci case CHRG_VBUS_ILIM_2000MA: 22462306a36Sopenharmony_ci return 2000000; 22562306a36Sopenharmony_ci case CHRG_VBUS_ILIM_2500MA: 22662306a36Sopenharmony_ci return 2500000; 22762306a36Sopenharmony_ci case CHRG_VBUS_ILIM_3000MA: 22862306a36Sopenharmony_ci return 3000000; 22962306a36Sopenharmony_ci case CHRG_VBUS_ILIM_3500MA: 23062306a36Sopenharmony_ci return 3500000; 23162306a36Sopenharmony_ci default: 23262306a36Sopenharmony_ci /* All b1xxx values map to 4000 mA */ 23362306a36Sopenharmony_ci return 4000000; 23462306a36Sopenharmony_ci } 23562306a36Sopenharmony_ci} 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_cistatic inline int axp288_charger_set_vbus_inlmt(struct axp288_chrg_info *info, 23862306a36Sopenharmony_ci int inlmt) 23962306a36Sopenharmony_ci{ 24062306a36Sopenharmony_ci int ret; 24162306a36Sopenharmony_ci u8 reg_val; 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_ci if (inlmt >= 4000000) 24462306a36Sopenharmony_ci reg_val = CHRG_VBUS_ILIM_4000MA << CHRG_VBUS_ILIM_BIT_POS; 24562306a36Sopenharmony_ci else if (inlmt >= 3500000) 24662306a36Sopenharmony_ci reg_val = CHRG_VBUS_ILIM_3500MA << CHRG_VBUS_ILIM_BIT_POS; 24762306a36Sopenharmony_ci else if (inlmt >= 3000000) 24862306a36Sopenharmony_ci reg_val = CHRG_VBUS_ILIM_3000MA << CHRG_VBUS_ILIM_BIT_POS; 24962306a36Sopenharmony_ci else if (inlmt >= 2500000) 25062306a36Sopenharmony_ci reg_val = CHRG_VBUS_ILIM_2500MA << CHRG_VBUS_ILIM_BIT_POS; 25162306a36Sopenharmony_ci else if (inlmt >= 2000000) 25262306a36Sopenharmony_ci reg_val = CHRG_VBUS_ILIM_2000MA << CHRG_VBUS_ILIM_BIT_POS; 25362306a36Sopenharmony_ci else if (inlmt >= 1500000) 25462306a36Sopenharmony_ci reg_val = CHRG_VBUS_ILIM_1500MA << CHRG_VBUS_ILIM_BIT_POS; 25562306a36Sopenharmony_ci else if (inlmt >= 900000) 25662306a36Sopenharmony_ci reg_val = CHRG_VBUS_ILIM_900MA << CHRG_VBUS_ILIM_BIT_POS; 25762306a36Sopenharmony_ci else if (inlmt >= 500000) 25862306a36Sopenharmony_ci reg_val = CHRG_VBUS_ILIM_500MA << CHRG_VBUS_ILIM_BIT_POS; 25962306a36Sopenharmony_ci else 26062306a36Sopenharmony_ci reg_val = CHRG_VBUS_ILIM_100MA << CHRG_VBUS_ILIM_BIT_POS; 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_ci ret = regmap_update_bits(info->regmap, AXP20X_CHRG_BAK_CTRL, 26362306a36Sopenharmony_ci CHRG_VBUS_ILIM_MASK, reg_val); 26462306a36Sopenharmony_ci if (ret < 0) 26562306a36Sopenharmony_ci dev_err(&info->pdev->dev, "charger BAK control %d\n", ret); 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ci return ret; 26862306a36Sopenharmony_ci} 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_cistatic int axp288_charger_vbus_path_select(struct axp288_chrg_info *info, 27162306a36Sopenharmony_ci bool enable) 27262306a36Sopenharmony_ci{ 27362306a36Sopenharmony_ci int ret; 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_ci if (enable) 27662306a36Sopenharmony_ci ret = regmap_update_bits(info->regmap, AXP20X_VBUS_IPSOUT_MGMT, 27762306a36Sopenharmony_ci VBUS_ISPOUT_VBUS_PATH_DIS, 0); 27862306a36Sopenharmony_ci else 27962306a36Sopenharmony_ci ret = regmap_update_bits(info->regmap, AXP20X_VBUS_IPSOUT_MGMT, 28062306a36Sopenharmony_ci VBUS_ISPOUT_VBUS_PATH_DIS, VBUS_ISPOUT_VBUS_PATH_DIS); 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci if (ret < 0) 28362306a36Sopenharmony_ci dev_err(&info->pdev->dev, "axp288 vbus path select %d\n", ret); 28462306a36Sopenharmony_ci 28562306a36Sopenharmony_ci return ret; 28662306a36Sopenharmony_ci} 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_cistatic int axp288_charger_enable_charger(struct axp288_chrg_info *info, 28962306a36Sopenharmony_ci bool enable) 29062306a36Sopenharmony_ci{ 29162306a36Sopenharmony_ci int ret; 29262306a36Sopenharmony_ci 29362306a36Sopenharmony_ci if (enable) 29462306a36Sopenharmony_ci ret = regmap_update_bits(info->regmap, AXP20X_CHRG_CTRL1, 29562306a36Sopenharmony_ci CHRG_CCCV_CHG_EN, CHRG_CCCV_CHG_EN); 29662306a36Sopenharmony_ci else 29762306a36Sopenharmony_ci ret = regmap_update_bits(info->regmap, AXP20X_CHRG_CTRL1, 29862306a36Sopenharmony_ci CHRG_CCCV_CHG_EN, 0); 29962306a36Sopenharmony_ci if (ret < 0) 30062306a36Sopenharmony_ci dev_err(&info->pdev->dev, "axp288 enable charger %d\n", ret); 30162306a36Sopenharmony_ci 30262306a36Sopenharmony_ci return ret; 30362306a36Sopenharmony_ci} 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_cistatic int axp288_get_charger_health(struct axp288_chrg_info *info) 30662306a36Sopenharmony_ci{ 30762306a36Sopenharmony_ci if (!(info->input_status & PS_STAT_VBUS_PRESENT)) 30862306a36Sopenharmony_ci return POWER_SUPPLY_HEALTH_UNKNOWN; 30962306a36Sopenharmony_ci 31062306a36Sopenharmony_ci if (!(info->input_status & PS_STAT_VBUS_VALID)) 31162306a36Sopenharmony_ci return POWER_SUPPLY_HEALTH_DEAD; 31262306a36Sopenharmony_ci else if (info->op_mode & CHRG_STAT_PMIC_OTP) 31362306a36Sopenharmony_ci return POWER_SUPPLY_HEALTH_OVERHEAT; 31462306a36Sopenharmony_ci else if (info->op_mode & CHRG_STAT_BAT_SAFE_MODE) 31562306a36Sopenharmony_ci return POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE; 31662306a36Sopenharmony_ci else 31762306a36Sopenharmony_ci return POWER_SUPPLY_HEALTH_GOOD; 31862306a36Sopenharmony_ci} 31962306a36Sopenharmony_ci 32062306a36Sopenharmony_cistatic int axp288_charger_usb_set_property(struct power_supply *psy, 32162306a36Sopenharmony_ci enum power_supply_property psp, 32262306a36Sopenharmony_ci const union power_supply_propval *val) 32362306a36Sopenharmony_ci{ 32462306a36Sopenharmony_ci struct axp288_chrg_info *info = power_supply_get_drvdata(psy); 32562306a36Sopenharmony_ci int ret = 0; 32662306a36Sopenharmony_ci int scaled_val; 32762306a36Sopenharmony_ci 32862306a36Sopenharmony_ci mutex_lock(&info->lock); 32962306a36Sopenharmony_ci switch (psp) { 33062306a36Sopenharmony_ci case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: 33162306a36Sopenharmony_ci scaled_val = min(val->intval, info->max_cc); 33262306a36Sopenharmony_ci scaled_val = DIV_ROUND_CLOSEST(scaled_val, 1000); 33362306a36Sopenharmony_ci ret = axp288_charger_set_cc(info, scaled_val); 33462306a36Sopenharmony_ci if (ret < 0) { 33562306a36Sopenharmony_ci dev_warn(&info->pdev->dev, "set charge current failed\n"); 33662306a36Sopenharmony_ci goto out; 33762306a36Sopenharmony_ci } 33862306a36Sopenharmony_ci break; 33962306a36Sopenharmony_ci case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: 34062306a36Sopenharmony_ci scaled_val = min(val->intval, info->max_cv); 34162306a36Sopenharmony_ci scaled_val = DIV_ROUND_CLOSEST(scaled_val, 1000); 34262306a36Sopenharmony_ci ret = axp288_charger_set_cv(info, scaled_val); 34362306a36Sopenharmony_ci if (ret < 0) { 34462306a36Sopenharmony_ci dev_warn(&info->pdev->dev, "set charge voltage failed\n"); 34562306a36Sopenharmony_ci goto out; 34662306a36Sopenharmony_ci } 34762306a36Sopenharmony_ci break; 34862306a36Sopenharmony_ci case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: 34962306a36Sopenharmony_ci ret = axp288_charger_set_vbus_inlmt(info, val->intval); 35062306a36Sopenharmony_ci if (ret < 0) { 35162306a36Sopenharmony_ci dev_warn(&info->pdev->dev, "set input current limit failed\n"); 35262306a36Sopenharmony_ci goto out; 35362306a36Sopenharmony_ci } 35462306a36Sopenharmony_ci info->valid = false; 35562306a36Sopenharmony_ci break; 35662306a36Sopenharmony_ci default: 35762306a36Sopenharmony_ci ret = -EINVAL; 35862306a36Sopenharmony_ci } 35962306a36Sopenharmony_ci 36062306a36Sopenharmony_ciout: 36162306a36Sopenharmony_ci mutex_unlock(&info->lock); 36262306a36Sopenharmony_ci return ret; 36362306a36Sopenharmony_ci} 36462306a36Sopenharmony_ci 36562306a36Sopenharmony_cistatic int axp288_charger_reg_readb(struct axp288_chrg_info *info, int reg, unsigned int *ret_val) 36662306a36Sopenharmony_ci{ 36762306a36Sopenharmony_ci int ret; 36862306a36Sopenharmony_ci 36962306a36Sopenharmony_ci ret = regmap_read(info->regmap, reg, ret_val); 37062306a36Sopenharmony_ci if (ret < 0) { 37162306a36Sopenharmony_ci dev_err(&info->pdev->dev, "Error %d on reading value from register 0x%04x\n", 37262306a36Sopenharmony_ci ret, 37362306a36Sopenharmony_ci reg); 37462306a36Sopenharmony_ci return ret; 37562306a36Sopenharmony_ci } 37662306a36Sopenharmony_ci return 0; 37762306a36Sopenharmony_ci} 37862306a36Sopenharmony_ci 37962306a36Sopenharmony_cistatic int axp288_charger_usb_update_property(struct axp288_chrg_info *info) 38062306a36Sopenharmony_ci{ 38162306a36Sopenharmony_ci int ret = 0; 38262306a36Sopenharmony_ci 38362306a36Sopenharmony_ci if (info->valid && time_before(jiffies, info->last_updated + AXP288_REG_UPDATE_INTERVAL)) 38462306a36Sopenharmony_ci return 0; 38562306a36Sopenharmony_ci 38662306a36Sopenharmony_ci dev_dbg(&info->pdev->dev, "Charger updating register values...\n"); 38762306a36Sopenharmony_ci 38862306a36Sopenharmony_ci ret = iosf_mbi_block_punit_i2c_access(); 38962306a36Sopenharmony_ci if (ret < 0) 39062306a36Sopenharmony_ci return ret; 39162306a36Sopenharmony_ci 39262306a36Sopenharmony_ci ret = axp288_charger_reg_readb(info, AXP20X_PWR_INPUT_STATUS, &info->input_status); 39362306a36Sopenharmony_ci if (ret < 0) 39462306a36Sopenharmony_ci goto out; 39562306a36Sopenharmony_ci 39662306a36Sopenharmony_ci ret = axp288_charger_reg_readb(info, AXP20X_PWR_OP_MODE, &info->op_mode); 39762306a36Sopenharmony_ci if (ret < 0) 39862306a36Sopenharmony_ci goto out; 39962306a36Sopenharmony_ci 40062306a36Sopenharmony_ci ret = axp288_charger_reg_readb(info, AXP20X_CHRG_BAK_CTRL, &info->backend_control); 40162306a36Sopenharmony_ci if (ret < 0) 40262306a36Sopenharmony_ci goto out; 40362306a36Sopenharmony_ci 40462306a36Sopenharmony_ci info->last_updated = jiffies; 40562306a36Sopenharmony_ci info->valid = true; 40662306a36Sopenharmony_ciout: 40762306a36Sopenharmony_ci iosf_mbi_unblock_punit_i2c_access(); 40862306a36Sopenharmony_ci return ret; 40962306a36Sopenharmony_ci} 41062306a36Sopenharmony_ci 41162306a36Sopenharmony_cistatic int axp288_charger_usb_get_property(struct power_supply *psy, 41262306a36Sopenharmony_ci enum power_supply_property psp, 41362306a36Sopenharmony_ci union power_supply_propval *val) 41462306a36Sopenharmony_ci{ 41562306a36Sopenharmony_ci struct axp288_chrg_info *info = power_supply_get_drvdata(psy); 41662306a36Sopenharmony_ci int ret; 41762306a36Sopenharmony_ci 41862306a36Sopenharmony_ci mutex_lock(&info->lock); 41962306a36Sopenharmony_ci ret = axp288_charger_usb_update_property(info); 42062306a36Sopenharmony_ci if (ret < 0) 42162306a36Sopenharmony_ci goto out; 42262306a36Sopenharmony_ci 42362306a36Sopenharmony_ci switch (psp) { 42462306a36Sopenharmony_ci case POWER_SUPPLY_PROP_PRESENT: 42562306a36Sopenharmony_ci /* Check for OTG case first */ 42662306a36Sopenharmony_ci if (info->otg.id_short) { 42762306a36Sopenharmony_ci val->intval = 0; 42862306a36Sopenharmony_ci break; 42962306a36Sopenharmony_ci } 43062306a36Sopenharmony_ci val->intval = (info->input_status & PS_STAT_VBUS_PRESENT) ? 1 : 0; 43162306a36Sopenharmony_ci break; 43262306a36Sopenharmony_ci case POWER_SUPPLY_PROP_ONLINE: 43362306a36Sopenharmony_ci /* Check for OTG case first */ 43462306a36Sopenharmony_ci if (info->otg.id_short) { 43562306a36Sopenharmony_ci val->intval = 0; 43662306a36Sopenharmony_ci break; 43762306a36Sopenharmony_ci } 43862306a36Sopenharmony_ci val->intval = (info->input_status & PS_STAT_VBUS_VALID) ? 1 : 0; 43962306a36Sopenharmony_ci break; 44062306a36Sopenharmony_ci case POWER_SUPPLY_PROP_HEALTH: 44162306a36Sopenharmony_ci val->intval = axp288_get_charger_health(info); 44262306a36Sopenharmony_ci break; 44362306a36Sopenharmony_ci case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: 44462306a36Sopenharmony_ci val->intval = info->cc * 1000; 44562306a36Sopenharmony_ci break; 44662306a36Sopenharmony_ci case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: 44762306a36Sopenharmony_ci val->intval = info->max_cc * 1000; 44862306a36Sopenharmony_ci break; 44962306a36Sopenharmony_ci case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: 45062306a36Sopenharmony_ci val->intval = info->cv * 1000; 45162306a36Sopenharmony_ci break; 45262306a36Sopenharmony_ci case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: 45362306a36Sopenharmony_ci val->intval = info->max_cv * 1000; 45462306a36Sopenharmony_ci break; 45562306a36Sopenharmony_ci case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: 45662306a36Sopenharmony_ci val->intval = axp288_charger_get_vbus_inlmt(info); 45762306a36Sopenharmony_ci break; 45862306a36Sopenharmony_ci default: 45962306a36Sopenharmony_ci ret = -EINVAL; 46062306a36Sopenharmony_ci } 46162306a36Sopenharmony_ci 46262306a36Sopenharmony_ciout: 46362306a36Sopenharmony_ci mutex_unlock(&info->lock); 46462306a36Sopenharmony_ci return ret; 46562306a36Sopenharmony_ci} 46662306a36Sopenharmony_ci 46762306a36Sopenharmony_cistatic int axp288_charger_property_is_writeable(struct power_supply *psy, 46862306a36Sopenharmony_ci enum power_supply_property psp) 46962306a36Sopenharmony_ci{ 47062306a36Sopenharmony_ci int ret; 47162306a36Sopenharmony_ci 47262306a36Sopenharmony_ci switch (psp) { 47362306a36Sopenharmony_ci case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: 47462306a36Sopenharmony_ci case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: 47562306a36Sopenharmony_ci case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: 47662306a36Sopenharmony_ci ret = 1; 47762306a36Sopenharmony_ci break; 47862306a36Sopenharmony_ci default: 47962306a36Sopenharmony_ci ret = 0; 48062306a36Sopenharmony_ci } 48162306a36Sopenharmony_ci 48262306a36Sopenharmony_ci return ret; 48362306a36Sopenharmony_ci} 48462306a36Sopenharmony_ci 48562306a36Sopenharmony_cistatic enum power_supply_property axp288_usb_props[] = { 48662306a36Sopenharmony_ci POWER_SUPPLY_PROP_PRESENT, 48762306a36Sopenharmony_ci POWER_SUPPLY_PROP_ONLINE, 48862306a36Sopenharmony_ci POWER_SUPPLY_PROP_TYPE, 48962306a36Sopenharmony_ci POWER_SUPPLY_PROP_HEALTH, 49062306a36Sopenharmony_ci POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, 49162306a36Sopenharmony_ci POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, 49262306a36Sopenharmony_ci POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, 49362306a36Sopenharmony_ci POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, 49462306a36Sopenharmony_ci POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, 49562306a36Sopenharmony_ci}; 49662306a36Sopenharmony_ci 49762306a36Sopenharmony_cistatic const struct power_supply_desc axp288_charger_desc = { 49862306a36Sopenharmony_ci .name = "axp288_charger", 49962306a36Sopenharmony_ci .type = POWER_SUPPLY_TYPE_USB, 50062306a36Sopenharmony_ci .properties = axp288_usb_props, 50162306a36Sopenharmony_ci .num_properties = ARRAY_SIZE(axp288_usb_props), 50262306a36Sopenharmony_ci .get_property = axp288_charger_usb_get_property, 50362306a36Sopenharmony_ci .set_property = axp288_charger_usb_set_property, 50462306a36Sopenharmony_ci .property_is_writeable = axp288_charger_property_is_writeable, 50562306a36Sopenharmony_ci}; 50662306a36Sopenharmony_ci 50762306a36Sopenharmony_cistatic irqreturn_t axp288_charger_irq_thread_handler(int irq, void *dev) 50862306a36Sopenharmony_ci{ 50962306a36Sopenharmony_ci struct axp288_chrg_info *info = dev; 51062306a36Sopenharmony_ci int i; 51162306a36Sopenharmony_ci 51262306a36Sopenharmony_ci for (i = 0; i < CHRG_INTR_END; i++) { 51362306a36Sopenharmony_ci if (info->irq[i] == irq) 51462306a36Sopenharmony_ci break; 51562306a36Sopenharmony_ci } 51662306a36Sopenharmony_ci 51762306a36Sopenharmony_ci if (i >= CHRG_INTR_END) { 51862306a36Sopenharmony_ci dev_warn(&info->pdev->dev, "spurious interrupt!!\n"); 51962306a36Sopenharmony_ci return IRQ_NONE; 52062306a36Sopenharmony_ci } 52162306a36Sopenharmony_ci 52262306a36Sopenharmony_ci switch (i) { 52362306a36Sopenharmony_ci case VBUS_OV_IRQ: 52462306a36Sopenharmony_ci dev_dbg(&info->pdev->dev, "VBUS Over Voltage INTR\n"); 52562306a36Sopenharmony_ci break; 52662306a36Sopenharmony_ci case CHARGE_DONE_IRQ: 52762306a36Sopenharmony_ci dev_dbg(&info->pdev->dev, "Charging Done INTR\n"); 52862306a36Sopenharmony_ci break; 52962306a36Sopenharmony_ci case CHARGE_CHARGING_IRQ: 53062306a36Sopenharmony_ci dev_dbg(&info->pdev->dev, "Start Charging IRQ\n"); 53162306a36Sopenharmony_ci break; 53262306a36Sopenharmony_ci case BAT_SAFE_QUIT_IRQ: 53362306a36Sopenharmony_ci dev_dbg(&info->pdev->dev, 53462306a36Sopenharmony_ci "Quit Safe Mode(restart timer) Charging IRQ\n"); 53562306a36Sopenharmony_ci break; 53662306a36Sopenharmony_ci case BAT_SAFE_ENTER_IRQ: 53762306a36Sopenharmony_ci dev_dbg(&info->pdev->dev, 53862306a36Sopenharmony_ci "Enter Safe Mode(timer expire) Charging IRQ\n"); 53962306a36Sopenharmony_ci break; 54062306a36Sopenharmony_ci case QCBTU_IRQ: 54162306a36Sopenharmony_ci dev_dbg(&info->pdev->dev, 54262306a36Sopenharmony_ci "Quit Battery Under Temperature(CHRG) INTR\n"); 54362306a36Sopenharmony_ci break; 54462306a36Sopenharmony_ci case CBTU_IRQ: 54562306a36Sopenharmony_ci dev_dbg(&info->pdev->dev, 54662306a36Sopenharmony_ci "Hit Battery Under Temperature(CHRG) INTR\n"); 54762306a36Sopenharmony_ci break; 54862306a36Sopenharmony_ci case QCBTO_IRQ: 54962306a36Sopenharmony_ci dev_dbg(&info->pdev->dev, 55062306a36Sopenharmony_ci "Quit Battery Over Temperature(CHRG) INTR\n"); 55162306a36Sopenharmony_ci break; 55262306a36Sopenharmony_ci case CBTO_IRQ: 55362306a36Sopenharmony_ci dev_dbg(&info->pdev->dev, 55462306a36Sopenharmony_ci "Hit Battery Over Temperature(CHRG) INTR\n"); 55562306a36Sopenharmony_ci break; 55662306a36Sopenharmony_ci default: 55762306a36Sopenharmony_ci dev_warn(&info->pdev->dev, "Spurious Interrupt!!!\n"); 55862306a36Sopenharmony_ci goto out; 55962306a36Sopenharmony_ci } 56062306a36Sopenharmony_ci mutex_lock(&info->lock); 56162306a36Sopenharmony_ci info->valid = false; 56262306a36Sopenharmony_ci mutex_unlock(&info->lock); 56362306a36Sopenharmony_ci power_supply_changed(info->psy_usb); 56462306a36Sopenharmony_ciout: 56562306a36Sopenharmony_ci return IRQ_HANDLED; 56662306a36Sopenharmony_ci} 56762306a36Sopenharmony_ci 56862306a36Sopenharmony_ci/* 56962306a36Sopenharmony_ci * The HP Pavilion x2 10 series comes in a number of variants: 57062306a36Sopenharmony_ci * Bay Trail SoC + AXP288 PMIC, Micro-USB, DMI_BOARD_NAME: "8021" 57162306a36Sopenharmony_ci * Bay Trail SoC + AXP288 PMIC, Type-C, DMI_BOARD_NAME: "815D" 57262306a36Sopenharmony_ci * Cherry Trail SoC + AXP288 PMIC, Type-C, DMI_BOARD_NAME: "813E" 57362306a36Sopenharmony_ci * Cherry Trail SoC + TI PMIC, Type-C, DMI_BOARD_NAME: "827C" or "82F4" 57462306a36Sopenharmony_ci * 57562306a36Sopenharmony_ci * The variants with the AXP288 + Type-C connector are all kinds of special: 57662306a36Sopenharmony_ci * 57762306a36Sopenharmony_ci * 1. They use a Type-C connector which the AXP288 does not support, so when 57862306a36Sopenharmony_ci * using a Type-C charger it is not recognized. Unlike most AXP288 devices, 57962306a36Sopenharmony_ci * this model actually has mostly working ACPI AC / Battery code, the ACPI code 58062306a36Sopenharmony_ci * "solves" this by simply setting the input_current_limit to 3A. 58162306a36Sopenharmony_ci * There are still some issues with the ACPI code, so we use this native driver, 58262306a36Sopenharmony_ci * and to solve the charging not working (500mA is not enough) issue we hardcode 58362306a36Sopenharmony_ci * the 3A input_current_limit like the ACPI code does. 58462306a36Sopenharmony_ci * 58562306a36Sopenharmony_ci * 2. If no charger is connected the machine boots with the vbus-path disabled. 58662306a36Sopenharmony_ci * Normally this is done when a 5V boost converter is active to avoid the PMIC 58762306a36Sopenharmony_ci * trying to charge from the 5V boost converter's output. This is done when 58862306a36Sopenharmony_ci * an OTG host cable is inserted and the ID pin on the micro-B receptacle is 58962306a36Sopenharmony_ci * pulled low and the ID pin has an ACPI event handler associated with it 59062306a36Sopenharmony_ci * which re-enables the vbus-path when the ID pin is pulled high when the 59162306a36Sopenharmony_ci * OTG host cable is removed. The Type-C connector has no ID pin, there is 59262306a36Sopenharmony_ci * no ID pin handler and there appears to be no 5V boost converter, so we 59362306a36Sopenharmony_ci * end up not charging because the vbus-path is disabled, until we unplug 59462306a36Sopenharmony_ci * the charger which automatically clears the vbus-path disable bit and then 59562306a36Sopenharmony_ci * on the second plug-in of the adapter we start charging. To solve the not 59662306a36Sopenharmony_ci * charging on first charger plugin we unconditionally enable the vbus-path at 59762306a36Sopenharmony_ci * probe on this model, which is safe since there is no 5V boost converter. 59862306a36Sopenharmony_ci */ 59962306a36Sopenharmony_cistatic const struct dmi_system_id axp288_hp_x2_dmi_ids[] = { 60062306a36Sopenharmony_ci { 60162306a36Sopenharmony_ci .matches = { 60262306a36Sopenharmony_ci DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"), 60362306a36Sopenharmony_ci DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "HP Pavilion x2 Detachable"), 60462306a36Sopenharmony_ci DMI_EXACT_MATCH(DMI_BOARD_NAME, "815D"), 60562306a36Sopenharmony_ci }, 60662306a36Sopenharmony_ci }, 60762306a36Sopenharmony_ci { 60862306a36Sopenharmony_ci .matches = { 60962306a36Sopenharmony_ci DMI_EXACT_MATCH(DMI_SYS_VENDOR, "HP"), 61062306a36Sopenharmony_ci DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "HP Pavilion x2 Detachable"), 61162306a36Sopenharmony_ci DMI_EXACT_MATCH(DMI_BOARD_NAME, "813E"), 61262306a36Sopenharmony_ci }, 61362306a36Sopenharmony_ci }, 61462306a36Sopenharmony_ci {} /* Terminating entry */ 61562306a36Sopenharmony_ci}; 61662306a36Sopenharmony_ci 61762306a36Sopenharmony_cistatic void axp288_charger_extcon_evt_worker(struct work_struct *work) 61862306a36Sopenharmony_ci{ 61962306a36Sopenharmony_ci struct axp288_chrg_info *info = 62062306a36Sopenharmony_ci container_of(work, struct axp288_chrg_info, cable.work); 62162306a36Sopenharmony_ci int ret, current_limit; 62262306a36Sopenharmony_ci struct extcon_dev *edev = info->cable.edev; 62362306a36Sopenharmony_ci unsigned int val; 62462306a36Sopenharmony_ci 62562306a36Sopenharmony_ci ret = regmap_read(info->regmap, AXP20X_PWR_INPUT_STATUS, &val); 62662306a36Sopenharmony_ci if (ret < 0) { 62762306a36Sopenharmony_ci dev_err(&info->pdev->dev, "Error reading status (%d)\n", ret); 62862306a36Sopenharmony_ci return; 62962306a36Sopenharmony_ci } 63062306a36Sopenharmony_ci 63162306a36Sopenharmony_ci /* Offline? Disable charging and bail */ 63262306a36Sopenharmony_ci if (!(val & PS_STAT_VBUS_VALID)) { 63362306a36Sopenharmony_ci dev_dbg(&info->pdev->dev, "USB charger disconnected\n"); 63462306a36Sopenharmony_ci axp288_charger_enable_charger(info, false); 63562306a36Sopenharmony_ci mutex_lock(&info->lock); 63662306a36Sopenharmony_ci info->valid = false; 63762306a36Sopenharmony_ci mutex_unlock(&info->lock); 63862306a36Sopenharmony_ci power_supply_changed(info->psy_usb); 63962306a36Sopenharmony_ci return; 64062306a36Sopenharmony_ci } 64162306a36Sopenharmony_ci 64262306a36Sopenharmony_ci /* Determine cable/charger type */ 64362306a36Sopenharmony_ci if (dmi_check_system(axp288_hp_x2_dmi_ids)) { 64462306a36Sopenharmony_ci /* See comment above axp288_hp_x2_dmi_ids declaration */ 64562306a36Sopenharmony_ci dev_dbg(&info->pdev->dev, "HP X2 with Type-C, setting inlmt to 3A\n"); 64662306a36Sopenharmony_ci current_limit = 3000000; 64762306a36Sopenharmony_ci } else if (extcon_get_state(edev, EXTCON_CHG_USB_SDP) > 0) { 64862306a36Sopenharmony_ci dev_dbg(&info->pdev->dev, "USB SDP charger is connected\n"); 64962306a36Sopenharmony_ci current_limit = 500000; 65062306a36Sopenharmony_ci } else if (extcon_get_state(edev, EXTCON_CHG_USB_CDP) > 0) { 65162306a36Sopenharmony_ci dev_dbg(&info->pdev->dev, "USB CDP charger is connected\n"); 65262306a36Sopenharmony_ci current_limit = 1500000; 65362306a36Sopenharmony_ci } else if (extcon_get_state(edev, EXTCON_CHG_USB_DCP) > 0) { 65462306a36Sopenharmony_ci dev_dbg(&info->pdev->dev, "USB DCP charger is connected\n"); 65562306a36Sopenharmony_ci current_limit = 2000000; 65662306a36Sopenharmony_ci } else { 65762306a36Sopenharmony_ci /* Charger type detection still in progress, bail. */ 65862306a36Sopenharmony_ci return; 65962306a36Sopenharmony_ci } 66062306a36Sopenharmony_ci 66162306a36Sopenharmony_ci /* Set vbus current limit first, then enable charger */ 66262306a36Sopenharmony_ci ret = axp288_charger_set_vbus_inlmt(info, current_limit); 66362306a36Sopenharmony_ci if (ret == 0) 66462306a36Sopenharmony_ci axp288_charger_enable_charger(info, true); 66562306a36Sopenharmony_ci else 66662306a36Sopenharmony_ci dev_err(&info->pdev->dev, 66762306a36Sopenharmony_ci "error setting current limit (%d)\n", ret); 66862306a36Sopenharmony_ci 66962306a36Sopenharmony_ci mutex_lock(&info->lock); 67062306a36Sopenharmony_ci info->valid = false; 67162306a36Sopenharmony_ci mutex_unlock(&info->lock); 67262306a36Sopenharmony_ci power_supply_changed(info->psy_usb); 67362306a36Sopenharmony_ci} 67462306a36Sopenharmony_ci 67562306a36Sopenharmony_cistatic int axp288_charger_handle_cable_evt(struct notifier_block *nb, 67662306a36Sopenharmony_ci unsigned long event, void *param) 67762306a36Sopenharmony_ci{ 67862306a36Sopenharmony_ci struct axp288_chrg_info *info = 67962306a36Sopenharmony_ci container_of(nb, struct axp288_chrg_info, cable.nb); 68062306a36Sopenharmony_ci schedule_work(&info->cable.work); 68162306a36Sopenharmony_ci return NOTIFY_OK; 68262306a36Sopenharmony_ci} 68362306a36Sopenharmony_ci 68462306a36Sopenharmony_cistatic void axp288_charger_otg_evt_worker(struct work_struct *work) 68562306a36Sopenharmony_ci{ 68662306a36Sopenharmony_ci struct axp288_chrg_info *info = 68762306a36Sopenharmony_ci container_of(work, struct axp288_chrg_info, otg.work); 68862306a36Sopenharmony_ci struct extcon_dev *edev = info->otg.cable; 68962306a36Sopenharmony_ci int ret, usb_host = extcon_get_state(edev, EXTCON_USB_HOST); 69062306a36Sopenharmony_ci 69162306a36Sopenharmony_ci dev_dbg(&info->pdev->dev, "external connector USB-Host is %s\n", 69262306a36Sopenharmony_ci usb_host ? "attached" : "detached"); 69362306a36Sopenharmony_ci 69462306a36Sopenharmony_ci /* 69562306a36Sopenharmony_ci * Set usb_id_short flag to avoid running charger detection logic 69662306a36Sopenharmony_ci * in case usb host. 69762306a36Sopenharmony_ci */ 69862306a36Sopenharmony_ci info->otg.id_short = usb_host; 69962306a36Sopenharmony_ci 70062306a36Sopenharmony_ci /* Disable VBUS path before enabling the 5V boost */ 70162306a36Sopenharmony_ci ret = axp288_charger_vbus_path_select(info, !info->otg.id_short); 70262306a36Sopenharmony_ci if (ret < 0) 70362306a36Sopenharmony_ci dev_warn(&info->pdev->dev, "vbus path disable failed\n"); 70462306a36Sopenharmony_ci} 70562306a36Sopenharmony_ci 70662306a36Sopenharmony_cistatic int axp288_charger_handle_otg_evt(struct notifier_block *nb, 70762306a36Sopenharmony_ci unsigned long event, void *param) 70862306a36Sopenharmony_ci{ 70962306a36Sopenharmony_ci struct axp288_chrg_info *info = 71062306a36Sopenharmony_ci container_of(nb, struct axp288_chrg_info, otg.id_nb); 71162306a36Sopenharmony_ci 71262306a36Sopenharmony_ci schedule_work(&info->otg.work); 71362306a36Sopenharmony_ci 71462306a36Sopenharmony_ci return NOTIFY_OK; 71562306a36Sopenharmony_ci} 71662306a36Sopenharmony_ci 71762306a36Sopenharmony_cistatic int charger_init_hw_regs(struct axp288_chrg_info *info) 71862306a36Sopenharmony_ci{ 71962306a36Sopenharmony_ci int ret, cc, cv; 72062306a36Sopenharmony_ci unsigned int val; 72162306a36Sopenharmony_ci 72262306a36Sopenharmony_ci /* Program temperature thresholds */ 72362306a36Sopenharmony_ci ret = regmap_write(info->regmap, AXP20X_V_LTF_CHRG, CHRG_VLTFC_0C); 72462306a36Sopenharmony_ci if (ret < 0) { 72562306a36Sopenharmony_ci dev_err(&info->pdev->dev, "register(%x) write error(%d)\n", 72662306a36Sopenharmony_ci AXP20X_V_LTF_CHRG, ret); 72762306a36Sopenharmony_ci return ret; 72862306a36Sopenharmony_ci } 72962306a36Sopenharmony_ci 73062306a36Sopenharmony_ci ret = regmap_write(info->regmap, AXP20X_V_HTF_CHRG, CHRG_VHTFC_45C); 73162306a36Sopenharmony_ci if (ret < 0) { 73262306a36Sopenharmony_ci dev_err(&info->pdev->dev, "register(%x) write error(%d)\n", 73362306a36Sopenharmony_ci AXP20X_V_HTF_CHRG, ret); 73462306a36Sopenharmony_ci return ret; 73562306a36Sopenharmony_ci } 73662306a36Sopenharmony_ci 73762306a36Sopenharmony_ci /* Do not turn-off charger o/p after charge cycle ends */ 73862306a36Sopenharmony_ci ret = regmap_update_bits(info->regmap, 73962306a36Sopenharmony_ci AXP20X_CHRG_CTRL2, 74062306a36Sopenharmony_ci CNTL2_CHG_OUT_TURNON, CNTL2_CHG_OUT_TURNON); 74162306a36Sopenharmony_ci if (ret < 0) { 74262306a36Sopenharmony_ci dev_err(&info->pdev->dev, "register(%x) write error(%d)\n", 74362306a36Sopenharmony_ci AXP20X_CHRG_CTRL2, ret); 74462306a36Sopenharmony_ci return ret; 74562306a36Sopenharmony_ci } 74662306a36Sopenharmony_ci 74762306a36Sopenharmony_ci /* Setup ending condition for charging to be 10% of I(chrg) */ 74862306a36Sopenharmony_ci ret = regmap_update_bits(info->regmap, 74962306a36Sopenharmony_ci AXP20X_CHRG_CTRL1, 75062306a36Sopenharmony_ci CHRG_CCCV_ITERM_20P, 0); 75162306a36Sopenharmony_ci if (ret < 0) { 75262306a36Sopenharmony_ci dev_err(&info->pdev->dev, "register(%x) write error(%d)\n", 75362306a36Sopenharmony_ci AXP20X_CHRG_CTRL1, ret); 75462306a36Sopenharmony_ci return ret; 75562306a36Sopenharmony_ci } 75662306a36Sopenharmony_ci 75762306a36Sopenharmony_ci /* Disable OCV-SOC curve calibration */ 75862306a36Sopenharmony_ci ret = regmap_update_bits(info->regmap, 75962306a36Sopenharmony_ci AXP20X_CC_CTRL, 76062306a36Sopenharmony_ci FG_CNTL_OCV_ADJ_EN, 0); 76162306a36Sopenharmony_ci if (ret < 0) { 76262306a36Sopenharmony_ci dev_err(&info->pdev->dev, "register(%x) write error(%d)\n", 76362306a36Sopenharmony_ci AXP20X_CC_CTRL, ret); 76462306a36Sopenharmony_ci return ret; 76562306a36Sopenharmony_ci } 76662306a36Sopenharmony_ci 76762306a36Sopenharmony_ci if (dmi_check_system(axp288_hp_x2_dmi_ids)) { 76862306a36Sopenharmony_ci /* See comment above axp288_hp_x2_dmi_ids declaration */ 76962306a36Sopenharmony_ci ret = axp288_charger_vbus_path_select(info, true); 77062306a36Sopenharmony_ci if (ret < 0) 77162306a36Sopenharmony_ci return ret; 77262306a36Sopenharmony_ci } else { 77362306a36Sopenharmony_ci /* Set Vhold to the factory default / recommended 4.4V */ 77462306a36Sopenharmony_ci val = VBUS_ISPOUT_VHOLD_SET_4400MV << VBUS_ISPOUT_VHOLD_SET_BIT_POS; 77562306a36Sopenharmony_ci ret = regmap_update_bits(info->regmap, AXP20X_VBUS_IPSOUT_MGMT, 77662306a36Sopenharmony_ci VBUS_ISPOUT_VHOLD_SET_MASK, val); 77762306a36Sopenharmony_ci if (ret < 0) { 77862306a36Sopenharmony_ci dev_err(&info->pdev->dev, "register(%x) write error(%d)\n", 77962306a36Sopenharmony_ci AXP20X_VBUS_IPSOUT_MGMT, ret); 78062306a36Sopenharmony_ci return ret; 78162306a36Sopenharmony_ci } 78262306a36Sopenharmony_ci } 78362306a36Sopenharmony_ci 78462306a36Sopenharmony_ci /* Read current charge voltage and current limit */ 78562306a36Sopenharmony_ci ret = regmap_read(info->regmap, AXP20X_CHRG_CTRL1, &val); 78662306a36Sopenharmony_ci if (ret < 0) { 78762306a36Sopenharmony_ci dev_err(&info->pdev->dev, "register(%x) read error(%d)\n", 78862306a36Sopenharmony_ci AXP20X_CHRG_CTRL1, ret); 78962306a36Sopenharmony_ci return ret; 79062306a36Sopenharmony_ci } 79162306a36Sopenharmony_ci 79262306a36Sopenharmony_ci /* Determine charge voltage */ 79362306a36Sopenharmony_ci cv = (val & CHRG_CCCV_CV_MASK) >> CHRG_CCCV_CV_BIT_POS; 79462306a36Sopenharmony_ci switch (cv) { 79562306a36Sopenharmony_ci case CHRG_CCCV_CV_4100MV: 79662306a36Sopenharmony_ci info->cv = CV_4100MV; 79762306a36Sopenharmony_ci break; 79862306a36Sopenharmony_ci case CHRG_CCCV_CV_4150MV: 79962306a36Sopenharmony_ci info->cv = CV_4150MV; 80062306a36Sopenharmony_ci break; 80162306a36Sopenharmony_ci case CHRG_CCCV_CV_4200MV: 80262306a36Sopenharmony_ci info->cv = CV_4200MV; 80362306a36Sopenharmony_ci break; 80462306a36Sopenharmony_ci case CHRG_CCCV_CV_4350MV: 80562306a36Sopenharmony_ci info->cv = CV_4350MV; 80662306a36Sopenharmony_ci break; 80762306a36Sopenharmony_ci } 80862306a36Sopenharmony_ci 80962306a36Sopenharmony_ci /* Determine charge current limit */ 81062306a36Sopenharmony_ci cc = (val & CHRG_CCCV_CC_MASK) >> CHRG_CCCV_CC_BIT_POS; 81162306a36Sopenharmony_ci cc = (cc * CHRG_CCCV_CC_LSB_RES) + CHRG_CCCV_CC_OFFSET; 81262306a36Sopenharmony_ci info->cc = cc; 81362306a36Sopenharmony_ci 81462306a36Sopenharmony_ci /* 81562306a36Sopenharmony_ci * Do not allow the user to configure higher settings then those 81662306a36Sopenharmony_ci * set by the firmware 81762306a36Sopenharmony_ci */ 81862306a36Sopenharmony_ci info->max_cv = info->cv; 81962306a36Sopenharmony_ci info->max_cc = info->cc; 82062306a36Sopenharmony_ci 82162306a36Sopenharmony_ci return 0; 82262306a36Sopenharmony_ci} 82362306a36Sopenharmony_ci 82462306a36Sopenharmony_cistatic void axp288_charger_cancel_work(void *data) 82562306a36Sopenharmony_ci{ 82662306a36Sopenharmony_ci struct axp288_chrg_info *info = data; 82762306a36Sopenharmony_ci 82862306a36Sopenharmony_ci cancel_work_sync(&info->otg.work); 82962306a36Sopenharmony_ci cancel_work_sync(&info->cable.work); 83062306a36Sopenharmony_ci} 83162306a36Sopenharmony_ci 83262306a36Sopenharmony_cistatic int axp288_charger_probe(struct platform_device *pdev) 83362306a36Sopenharmony_ci{ 83462306a36Sopenharmony_ci int ret, i, pirq; 83562306a36Sopenharmony_ci struct axp288_chrg_info *info; 83662306a36Sopenharmony_ci struct device *dev = &pdev->dev; 83762306a36Sopenharmony_ci struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent); 83862306a36Sopenharmony_ci struct power_supply_config charger_cfg = {}; 83962306a36Sopenharmony_ci const char *extcon_name = NULL; 84062306a36Sopenharmony_ci unsigned int val; 84162306a36Sopenharmony_ci 84262306a36Sopenharmony_ci /* 84362306a36Sopenharmony_ci * Normally the native AXP288 fg/charger drivers are preferred but 84462306a36Sopenharmony_ci * on some devices the ACPI drivers should be used instead. 84562306a36Sopenharmony_ci */ 84662306a36Sopenharmony_ci if (!acpi_quirk_skip_acpi_ac_and_battery()) 84762306a36Sopenharmony_ci return -ENODEV; 84862306a36Sopenharmony_ci 84962306a36Sopenharmony_ci /* 85062306a36Sopenharmony_ci * On some devices the fuelgauge and charger parts of the axp288 are 85162306a36Sopenharmony_ci * not used, check that the fuelgauge is enabled (CC_CTRL != 0). 85262306a36Sopenharmony_ci */ 85362306a36Sopenharmony_ci ret = regmap_read(axp20x->regmap, AXP20X_CC_CTRL, &val); 85462306a36Sopenharmony_ci if (ret < 0) 85562306a36Sopenharmony_ci return ret; 85662306a36Sopenharmony_ci if (val == 0) 85762306a36Sopenharmony_ci return -ENODEV; 85862306a36Sopenharmony_ci 85962306a36Sopenharmony_ci info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL); 86062306a36Sopenharmony_ci if (!info) 86162306a36Sopenharmony_ci return -ENOMEM; 86262306a36Sopenharmony_ci 86362306a36Sopenharmony_ci mutex_init(&info->lock); 86462306a36Sopenharmony_ci info->pdev = pdev; 86562306a36Sopenharmony_ci info->regmap = axp20x->regmap; 86662306a36Sopenharmony_ci info->regmap_irqc = axp20x->regmap_irqc; 86762306a36Sopenharmony_ci 86862306a36Sopenharmony_ci info->cable.edev = extcon_get_extcon_dev(AXP288_EXTCON_DEV_NAME); 86962306a36Sopenharmony_ci if (IS_ERR(info->cable.edev)) { 87062306a36Sopenharmony_ci dev_err_probe(dev, PTR_ERR(info->cable.edev), 87162306a36Sopenharmony_ci "extcon_get_extcon_dev(%s) failed\n", 87262306a36Sopenharmony_ci AXP288_EXTCON_DEV_NAME); 87362306a36Sopenharmony_ci return PTR_ERR(info->cable.edev); 87462306a36Sopenharmony_ci } 87562306a36Sopenharmony_ci 87662306a36Sopenharmony_ci /* 87762306a36Sopenharmony_ci * On devices with broken ACPI GPIO event handlers there also is no ACPI 87862306a36Sopenharmony_ci * "INT3496" (USB_HOST_EXTCON_HID) device. x86-android-tablets.ko 87962306a36Sopenharmony_ci * instantiates an "intel-int3496" extcon on these devs as a workaround. 88062306a36Sopenharmony_ci */ 88162306a36Sopenharmony_ci if (acpi_quirk_skip_gpio_event_handlers()) 88262306a36Sopenharmony_ci extcon_name = "intel-int3496"; 88362306a36Sopenharmony_ci else if (acpi_dev_present(USB_HOST_EXTCON_HID, NULL, -1)) 88462306a36Sopenharmony_ci extcon_name = USB_HOST_EXTCON_NAME; 88562306a36Sopenharmony_ci 88662306a36Sopenharmony_ci if (extcon_name) { 88762306a36Sopenharmony_ci info->otg.cable = extcon_get_extcon_dev(extcon_name); 88862306a36Sopenharmony_ci if (IS_ERR(info->otg.cable)) { 88962306a36Sopenharmony_ci dev_err_probe(dev, PTR_ERR(info->otg.cable), 89062306a36Sopenharmony_ci "extcon_get_extcon_dev(%s) failed\n", 89162306a36Sopenharmony_ci USB_HOST_EXTCON_NAME); 89262306a36Sopenharmony_ci return PTR_ERR(info->otg.cable); 89362306a36Sopenharmony_ci } 89462306a36Sopenharmony_ci dev_info(dev, "Using " USB_HOST_EXTCON_HID " extcon for usb-id\n"); 89562306a36Sopenharmony_ci } 89662306a36Sopenharmony_ci 89762306a36Sopenharmony_ci platform_set_drvdata(pdev, info); 89862306a36Sopenharmony_ci 89962306a36Sopenharmony_ci ret = charger_init_hw_regs(info); 90062306a36Sopenharmony_ci if (ret) 90162306a36Sopenharmony_ci return ret; 90262306a36Sopenharmony_ci 90362306a36Sopenharmony_ci /* Register with power supply class */ 90462306a36Sopenharmony_ci charger_cfg.drv_data = info; 90562306a36Sopenharmony_ci info->psy_usb = devm_power_supply_register(dev, &axp288_charger_desc, 90662306a36Sopenharmony_ci &charger_cfg); 90762306a36Sopenharmony_ci if (IS_ERR(info->psy_usb)) { 90862306a36Sopenharmony_ci ret = PTR_ERR(info->psy_usb); 90962306a36Sopenharmony_ci dev_err(dev, "failed to register power supply: %d\n", ret); 91062306a36Sopenharmony_ci return ret; 91162306a36Sopenharmony_ci } 91262306a36Sopenharmony_ci 91362306a36Sopenharmony_ci /* Cancel our work on cleanup, register this before the notifiers */ 91462306a36Sopenharmony_ci ret = devm_add_action(dev, axp288_charger_cancel_work, info); 91562306a36Sopenharmony_ci if (ret) 91662306a36Sopenharmony_ci return ret; 91762306a36Sopenharmony_ci 91862306a36Sopenharmony_ci /* Register for extcon notification */ 91962306a36Sopenharmony_ci INIT_WORK(&info->cable.work, axp288_charger_extcon_evt_worker); 92062306a36Sopenharmony_ci info->cable.nb.notifier_call = axp288_charger_handle_cable_evt; 92162306a36Sopenharmony_ci ret = devm_extcon_register_notifier_all(dev, info->cable.edev, 92262306a36Sopenharmony_ci &info->cable.nb); 92362306a36Sopenharmony_ci if (ret) { 92462306a36Sopenharmony_ci dev_err(dev, "failed to register cable extcon notifier\n"); 92562306a36Sopenharmony_ci return ret; 92662306a36Sopenharmony_ci } 92762306a36Sopenharmony_ci schedule_work(&info->cable.work); 92862306a36Sopenharmony_ci 92962306a36Sopenharmony_ci /* Register for OTG notification */ 93062306a36Sopenharmony_ci INIT_WORK(&info->otg.work, axp288_charger_otg_evt_worker); 93162306a36Sopenharmony_ci info->otg.id_nb.notifier_call = axp288_charger_handle_otg_evt; 93262306a36Sopenharmony_ci if (info->otg.cable) { 93362306a36Sopenharmony_ci ret = devm_extcon_register_notifier(dev, info->otg.cable, 93462306a36Sopenharmony_ci EXTCON_USB_HOST, &info->otg.id_nb); 93562306a36Sopenharmony_ci if (ret) { 93662306a36Sopenharmony_ci dev_err(dev, "failed to register EXTCON_USB_HOST notifier\n"); 93762306a36Sopenharmony_ci return ret; 93862306a36Sopenharmony_ci } 93962306a36Sopenharmony_ci schedule_work(&info->otg.work); 94062306a36Sopenharmony_ci } 94162306a36Sopenharmony_ci 94262306a36Sopenharmony_ci /* Register charger interrupts */ 94362306a36Sopenharmony_ci for (i = 0; i < CHRG_INTR_END; i++) { 94462306a36Sopenharmony_ci pirq = platform_get_irq(info->pdev, i); 94562306a36Sopenharmony_ci if (pirq < 0) 94662306a36Sopenharmony_ci return pirq; 94762306a36Sopenharmony_ci 94862306a36Sopenharmony_ci info->irq[i] = regmap_irq_get_virq(info->regmap_irqc, pirq); 94962306a36Sopenharmony_ci if (info->irq[i] < 0) { 95062306a36Sopenharmony_ci dev_warn(&info->pdev->dev, 95162306a36Sopenharmony_ci "failed to get virtual interrupt=%d\n", pirq); 95262306a36Sopenharmony_ci return info->irq[i]; 95362306a36Sopenharmony_ci } 95462306a36Sopenharmony_ci ret = devm_request_threaded_irq(&info->pdev->dev, info->irq[i], 95562306a36Sopenharmony_ci NULL, axp288_charger_irq_thread_handler, 95662306a36Sopenharmony_ci IRQF_ONESHOT, info->pdev->name, info); 95762306a36Sopenharmony_ci if (ret) { 95862306a36Sopenharmony_ci dev_err(dev, "failed to request interrupt=%d\n", 95962306a36Sopenharmony_ci info->irq[i]); 96062306a36Sopenharmony_ci return ret; 96162306a36Sopenharmony_ci } 96262306a36Sopenharmony_ci } 96362306a36Sopenharmony_ci 96462306a36Sopenharmony_ci return 0; 96562306a36Sopenharmony_ci} 96662306a36Sopenharmony_ci 96762306a36Sopenharmony_cistatic const struct platform_device_id axp288_charger_id_table[] = { 96862306a36Sopenharmony_ci { .name = "axp288_charger" }, 96962306a36Sopenharmony_ci {}, 97062306a36Sopenharmony_ci}; 97162306a36Sopenharmony_ciMODULE_DEVICE_TABLE(platform, axp288_charger_id_table); 97262306a36Sopenharmony_ci 97362306a36Sopenharmony_cistatic struct platform_driver axp288_charger_driver = { 97462306a36Sopenharmony_ci .probe = axp288_charger_probe, 97562306a36Sopenharmony_ci .id_table = axp288_charger_id_table, 97662306a36Sopenharmony_ci .driver = { 97762306a36Sopenharmony_ci .name = "axp288_charger", 97862306a36Sopenharmony_ci }, 97962306a36Sopenharmony_ci}; 98062306a36Sopenharmony_ci 98162306a36Sopenharmony_cimodule_platform_driver(axp288_charger_driver); 98262306a36Sopenharmony_ci 98362306a36Sopenharmony_ciMODULE_AUTHOR("Ramakrishna Pallala <ramakrishna.pallala@intel.com>"); 98462306a36Sopenharmony_ciMODULE_DESCRIPTION("X-power AXP288 Charger Driver"); 98562306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 986