| /* Copyright (c) 2014-2015 The Linux Foundation. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 and |
| * only version 2 as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that 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. |
| */ |
| |
| #define pr_fmt(fmt) "SMB348 %s: " fmt, __func__ |
| #define FEATURE_THERMAL_MITIGATION_ALGO |
| |
| #include <linux/i2c.h> |
| #include <linux/debugfs.h> |
| #include <linux/gpio.h> |
| #include <linux/errno.h> |
| #include <linux/delay.h> |
| #include <linux/module.h> |
| #include <linux/interrupt.h> |
| #include <linux/slab.h> |
| #include <linux/power_supply.h> |
| #include <linux/regulator/of_regulator.h> |
| #include <linux/regulator/machine.h> |
| #include <linux/regulator/consumer.h> |
| #include <linux/regulator/driver.h> |
| #include <linux/of.h> |
| #include <linux/of_gpio.h> |
| #include <linux/mutex.h> |
| #include <linux/qpnp/qpnp-adc.h> |
| #include <linux/workqueue.h> |
| |
| #include <linux/switch.h> |
| |
| #define _SMB348_MASK(BITS, POS) \ |
| ((unsigned char)(((1 << (BITS)) - 1) << (POS))) |
| #define SMB348_MASK(LEFT_BIT_POS, RIGHT_BIT_POS) \ |
| _SMB348_MASK((LEFT_BIT_POS) - (RIGHT_BIT_POS) + 1, \ |
| (RIGHT_BIT_POS)) |
| |
| /* Config/Control registers */ |
| #define CHG_CURRENT_CTRL_REG 0x0 |
| #define CHG_OTH_CURRENT_CTRL_REG 0x1 |
| #define VARIOUS_FUNC_REG 0x2 |
| #define VFLOAT_REG 0x3 |
| #define CHG_CTRL_REG 0x4 |
| #define STAT_AND_TIMER_CTRL_REG 0x5 |
| #define CHG_PIN_EN_CTRL_REG 0x6 |
| #define THERM_A_CTRL_REG 0x7 |
| #define SYSOK_AND_USB3_REG 0x8 |
| #define OTHER_CTRL_REG 0x9 |
| #define TEMP_REG 0xB |
| #define FAULT_INT_REG 0xC |
| #define STATUS_INT_REG 0xD |
| |
| /* Command registers */ |
| #define CMD_A_REG 0x30 |
| #define CMD_B_REG 0x31 |
| |
| /* IRQ status registers */ |
| #define IRQ_A_REG 0x35 |
| #define IRQ_B_REG 0x36 |
| #define IRQ_C_REG 0x37 |
| #define IRQ_D_REG 0x38 |
| #define IRQ_E_REG 0x39 |
| #define IRQ_F_REG 0x3A |
| |
| /* Status registers */ |
| #define STATUS_C_REG 0x3D |
| #define STATUS_D_REG 0x3E |
| #define STATUS_E_REG 0x3F |
| |
| /* Config bits */ |
| #define CHG_INHI_EN_MASK BIT(1) |
| #define CHG_INHI_EN_BIT BIT(1) |
| #define CMD_A_CHG_ENABLE_BIT BIT(1) |
| #define CMD_A_VOLATILE_W_PERM_BIT BIT(7) |
| #define CMD_A_CHG_SUSP_EN_BIT BIT(2) |
| #define CMD_A_CHG_SUSP_EN_MASK BIT(2) |
| #define CMD_A_OTG_ENABLE_BIT BIT(4) |
| #define CMD_A_OTG_ENABLE_MASK BIT(4) |
| #define CMD_B_CHG_HC_ENABLE_BIT BIT(0) |
| #define USB3_ENABLE_BIT BIT(5) |
| #define USB3_ENABLE_MASK BIT(5) |
| #define CMD_B_CHG_USB_500_900_ENABLE_BIT BIT(1) |
| #define CHG_CTRL_AUTO_RECHARGE_ENABLE_BIT 0x0 |
| #define CHG_CTRL_CURR_TERM_END_CHG_BIT 0x0 |
| #define CHG_CTRL_BATT_MISSING_DET_THERM_IO SMB348_MASK(5, 4) |
| #define CHG_CTRL_AUTO_RECHARGE_MASK BIT(7) |
| #define CHG_AUTO_RECHARGE_DIS_BIT BIT(7) |
| #define CHG_CTRL_CURR_TERM_END_MASK BIT(6) |
| #define CHG_CTRL_BATT_MISSING_DET_MASK SMB348_MASK(5, 4) |
| #define CHG_CTRL_APSD_EN_BIT BIT(2) |
| #define CHG_CTRL_APSD_EN_MASK BIT(2) |
| #define CHG_ITERM_MASK 0x07 |
| #define CHG_PIN_CTRL_USBCS_REG_BIT 0x0 |
| /* This is to select if use external pin EN to control CHG */ |
| #define CHG_PIN_CTRL_CHG_EN_LOW_PIN_BIT SMB348_MASK(6, 5) |
| #define CHG_PIN_CTRL_CHG_EN_LOW_REG_BIT 0x0 |
| #define CHG_PIN_CTRL_CHG_EN_MASK SMB348_MASK(6, 5) |
| |
| #define CHG_LOW_BATT_THRESHOLD \ |
| SMB348_MASK(3, 0) |
| #define CHG_PIN_CTRL_USBCS_REG_MASK BIT(4) |
| #define CHG_PIN_CTRL_APSD_IRQ_BIT BIT(1) |
| #define CHG_PIN_CTRL_APSD_IRQ_MASK BIT(1) |
| #define CHG_PIN_CTRL_CHG_ERR_IRQ_BIT BIT(2) |
| #define CHG_PIN_CTRL_CHG_ERR_IRQ_MASK BIT(2) |
| #define VARIOUS_FUNC_USB_SUSP_EN_REG_BIT BIT(6) |
| #define VARIOUS_FUNC_USB_SUSP_MASK BIT(6) |
| #define FAULT_INT_HOT_COLD_HARD_BIT BIT(7) |
| #define FAULT_INT_HOT_COLD_SOFT_BIT BIT(6) |
| #define FAULT_INT_INPUT_OV_BIT BIT(3) |
| #define FAULT_INT_INPUT_UV_BIT BIT(2) |
| #define FAULT_INT_AICL_COMPLETE_BIT BIT(1) |
| #define STATUS_INT_CHG_TIMEOUT_BIT BIT(7) |
| #define STATUS_INT_OTG_DETECT_BIT BIT(6) |
| #define STATUS_INT_BATT_OV_BIT BIT(5) |
| #define STATUS_INT_CHGING_BIT BIT(4) |
| #define STATUS_INT_CHG_INHI_BIT BIT(3) |
| #define STATUS_INT_INOK_BIT BIT(2) |
| #define STATUS_INT_MISSING_BATT_BIT BIT(1) |
| #define STATUS_INT_LOW_BATT_BIT BIT(0) |
| #define THERM_A_THERM_MONITOR_EN_BIT 0x0 |
| #define THERM_A_THERM_MONITOR_EN_MASK BIT(4) |
| #define VFLOAT_MASK 0x3F |
| |
| /* IRQ status bits */ |
| #define IRQ_A_HOT_HARD_BIT BIT(6) |
| #define IRQ_A_COLD_HARD_BIT BIT(4) |
| #define IRQ_A_HOT_SOFT_BIT BIT(2) |
| #define IRQ_A_COLD_SOFT_BIT BIT(0) |
| #define IRQ_B_BATT_MISSING_BIT BIT(4) |
| #define IRQ_B_BATT_LOW_BIT BIT(2) |
| #define IRQ_B_BATT_OV_BIT BIT(6) |
| #define IRQ_B_PRE_FAST_CHG_BIT BIT(0) |
| #define IRQ_C_TAPER_CHG_BIT BIT(2) |
| #define IRQ_C_TERM_BIT BIT(0) |
| #define IRQ_C_INT_OVER_TEMP_BIT BIT(6) |
| #define IRQ_D_CHG_TIMEOUT_BIT (BIT(0) | BIT(2)) |
| #define IRQ_D_AICL_DONE_BIT BIT(4) |
| #define IRQ_D_APSD_COMPLETE BIT(6) |
| #define IRQ_E_INPUT_UV_BIT BIT(0) |
| #define IRQ_E_INPUT_OV_BIT BIT(2) |
| #define IRQ_E_AFVC_ACTIVE BIT(4) |
| #define IRQ_F_OTG_VALID_BIT BIT(2) |
| #define IRQ_F_OTG_BATT_FAIL_BIT BIT(4) |
| #define IRQ_F_OTG_OC_BIT BIT(6) |
| #define IRQ_F_POWER_OK BIT(0) |
| |
| /* Status bits */ |
| #define STATUS_C_CHARGING_MASK SMB348_MASK(2, 1) |
| #define STATUS_C_FAST_CHARGING BIT(2) |
| #define STATUS_C_PRE_CHARGING BIT(1) |
| #define STATUS_C_TAPER_CHARGING SMB348_MASK(2, 1) |
| #define STATUS_C_CHG_ERR_STATUS_BIT BIT(6) |
| #define STATUS_C_CHG_ENABLE_STATUS_BIT BIT(0) |
| #define STATUS_C_CHG_HOLD_OFF_BIT BIT(3) |
| #define STATUS_D_CHARGING_PORT_MASK \ |
| SMB348_MASK(3, 0) |
| #define STATUS_D_PORT_ACA_DOCK BIT(3) |
| #define STATUS_D_PORT_SDP BIT(2) |
| #define STATUS_D_PORT_DCP BIT(1) |
| #define STATUS_D_PORT_CDP BIT(0) |
| #define STATUS_D_PORT_OTHER SMB348_MASK(1, 0) |
| #define STATUS_D_PORT_ACA_A (BIT(2) | BIT(0)) |
| #define STATUS_D_PORT_ACA_B SMB348_MASK(2, 1) |
| #define STATUS_D_PORT_ACA_C SMB348_MASK(2, 0) |
| |
| /* constants */ |
| #define DC_MA_MIN 100 |
| #define DC_MA_MAX 1500 |
| #define USB2_MIN_CURRENT_MA 100 |
| #define USB2_MAX_CURRENT_MA 500 |
| #define USB3_MIN_CURRENT_MA 150 |
| #define USB3_MAX_CURRENT_MA 900 |
| #define AC_CHG_CURRENT_MASK 0x70 |
| #define AC_CHG_CURRENT_SHIFT 4 |
| #define SMB348_IRQ_REG_COUNT 6 |
| #define SMB348_FAST_CHG_MIN_MA 100 |
| #define SMB348_FAST_CHG_MAX_MA 1500 |
| #define SMB348_FAST_CHG_SHIFT 5 |
| #define SMB_FAST_CHG_CURRENT_MASK 0xE0 |
| #define SMB348_DEFAULT_BATT_CAPACITY 50 |
| #define SMB348_DEFAULT_VOLTAGE 3760000 |
| #define SMB348_DEFAULT_CURRENT 99999 |
| #define SMB348_BATT_GOOD_THRE_2P5 0x1 |
| #ifdef FEATURE_THERMAL_MITIGATION_ALGO |
| #define SMB348_SW_MITIGATION_SOC 75 |
| #define SMB348_SW_RECHG_SOC 99 |
| #define SMB348_SW_RECHG_VOLTAGE 4330000 |
| #endif /* FEATURE_THERMAL_MITIGATION_ALGO */ |
| |
| /* Quanta { 3 mins delay for WPC charge */ |
| #define WPC_CHG_RESUME_CHG_MS 180000 |
| bool WPC_CHG_RESUME_ALLOW_GOTA = 1; |
| bool RECOVERY_WPC_DISABLE_FLAG = 0; |
| /* } Quanta */ |
| |
| #define RECHG_RESUME_REAL_SOC_MS 5000 |
| |
| extern char *saved_command_line; |
| // Quanta: export fo gauge driver |
| bool chg_full_flag = false; |
| bool force_full_flag = false; |
| bool recharge_flag = false; |
| #define SMB_MAX_SOC 100 |
| |
| enum { |
| USER = BIT(0), |
| THERMAL = BIT(1), |
| CURRENT = BIT(2), |
| SOC = BIT(3), |
| FAKE_BATTERY = BIT(4), |
| }; |
| |
| typedef enum { |
| HW_VER_H = 1, |
| HW_VER_I = 2, |
| HW_VER_J = 3, |
| HW_VER_K = 4, |
| HW_VER_L = 5, |
| } XU1_HW_VER; |
| |
| struct smb348_regulator { |
| struct regulator_desc rdesc; |
| struct regulator_dev *rdev; |
| }; |
| |
| struct smb348_charger { |
| struct i2c_client *client; |
| struct device *dev; |
| |
| bool inhibit_disabled; |
| bool recharge_disabled; |
| int recharge_mv; |
| bool iterm_disabled; |
| int iterm_ma; |
| int vfloat_mv; |
| int chg_valid_gpio; |
| int chg_end_gpio; |
| int wpc_dock_detect_gpio; |
| int wpc_stat_gpio; |
| int wpc_enable_gpio; |
| int chg_valid_act_low; |
| int chg_present; |
| int wpc_state; |
| int wpc_dock_present; |
| int fake_battery_soc; |
| int dc_psy_type; |
| int dc_psy_ma; |
| bool chg_autonomous_mode; |
| bool disable_apsd; |
| bool using_pmic_therm; |
| bool pmic_vbat_sns; |
| bool battery_missing; |
| const char *bms_psy_name; |
| bool resume_completed; |
| bool irq_waiting; |
| bool bms_controlled_charging; |
| bool skip_usb_suspend_for_fake_battery; |
| struct mutex read_write_lock; |
| struct mutex path_suspend_lock; |
| struct mutex irq_complete; |
| struct mutex wpc_chg_complete; |
| /* Quanta { 3 mins delay for WPC charge */ |
| struct delayed_work wpc_resume_chg_wq; |
| /* } Quanta */ |
| struct delayed_work resume_real_soc_wq; |
| struct work_struct wpc_dock_wq; |
| u8 irq_cfg_mask[2]; |
| int irq_gpio; |
| int charging_disabled; |
| int fastchg_current_max_ma; |
| #ifdef FEATURE_THERMAL_MITIGATION_ALGO |
| int fastchg_current_mitigation_ma; |
| int prop_batt_capacity; |
| int prop_batt_temp; |
| #endif /* FEATURE_THERMAL_MITIGATION_ALGO */ |
| unsigned int cool_bat_ma; |
| unsigned int warm_bat_ma; |
| unsigned int cool_bat_mv; |
| unsigned int warm_bat_mv; |
| unsigned int connected_rid; |
| XU1_HW_VER chip_hw_id; |
| |
| /* debugfs related */ |
| #if defined(CONFIG_DEBUG_FS) |
| struct dentry *debug_root; |
| u32 peek_poke_address; |
| #endif |
| /* status tracking */ |
| bool batt_full; |
| bool batt_hot; |
| bool batt_cold; |
| bool batt_warm; |
| bool batt_cool; |
| bool jeita_supported; |
| int charging_disabled_status; |
| int usb_suspended; |
| |
| /* power supply */ |
| struct power_supply *usb_psy; |
| struct power_supply *bms_psy; |
| struct power_supply batt_psy; |
| struct power_supply dc_psy; |
| |
| /* switch */ |
| struct switch_dev dock_sdev; |
| |
| /* otg 5V regulator */ |
| struct smb348_regulator otg_vreg; |
| |
| /* adc_tm paramters */ |
| struct qpnp_vadc_chip *vadc_dev; |
| struct qpnp_adc_tm_chip *adc_tm_dev; |
| struct qpnp_adc_tm_btm_param adc_param; |
| int cold_bat_decidegc; |
| int hot_bat_decidegc; |
| int cool_bat_decidegc; |
| int warm_bat_decidegc; |
| int bat_present_decidegc; |
| /* i2c pull up regulator */ |
| struct regulator *vcc_i2c; |
| }; |
| |
| struct smb_irq_info { |
| const char *name; |
| int (*smb_irq)(struct smb348_charger *chip, |
| u8 rt_stat); |
| int high; |
| int low; |
| }; |
| |
| struct irq_handler_info { |
| u8 stat_reg; |
| u8 val; |
| u8 prev_val; |
| struct smb_irq_info irq_info[4]; |
| }; |
| |
| static int chg_current[] = { |
| 300, 500, 700, 900, 1000, 1200, 1500, 1800, |
| }; |
| |
| static int fast_chg_current[] = { |
| 100, 200, 375, 450, 600, 800, 1300, 1500, |
| }; |
| |
| /* add supplied to "bms" function */ |
| static char *pm_batt_supplied_to[] = { |
| "bms", |
| }; |
| |
| #define MAX_RW_RETRIES 3 |
| static int __smb348_read_reg(struct smb348_charger *chip, u8 reg, u8 *val) |
| { |
| s32 ret, i; |
| |
| for (i = 0; i < MAX_RW_RETRIES; i++) { |
| ret = i2c_smbus_read_byte_data(chip->client, reg); |
| if (ret >= 0) |
| break; |
| /* delay between i2c retries */ |
| msleep(20); |
| } |
| if (ret < 0) { |
| dev_err(chip->dev, |
| "i2c read fail: can't read from %02x: %d\n", reg, ret); |
| return ret; |
| } |
| |
| *val = ret; |
| return 0; |
| } |
| |
| static int __smb348_write_reg(struct smb348_charger *chip, int reg, u8 val) |
| { |
| s32 ret, i; |
| |
| for (i = 0; i < MAX_RW_RETRIES; i++) { |
| ret = i2c_smbus_write_byte_data(chip->client, reg, val); |
| if (!ret) |
| break; |
| /* delay between i2c retries */ |
| msleep(20); |
| } |
| if (ret < 0) { |
| dev_err(chip->dev, |
| "i2c write fail: can't write %02x to %02x: %d\n", |
| val, reg, ret); |
| return ret; |
| } |
| return 0; |
| } |
| |
| static int smb348_read_reg(struct smb348_charger *chip, int reg, |
| u8 *val) |
| { |
| int rc; |
| |
| mutex_lock(&chip->read_write_lock); |
| rc = __smb348_read_reg(chip, reg, val); |
| mutex_unlock(&chip->read_write_lock); |
| |
| return rc; |
| } |
| |
| static int smb348_write_reg(struct smb348_charger *chip, int reg, |
| u8 val) |
| { |
| int rc; |
| |
| mutex_lock(&chip->read_write_lock); |
| rc = __smb348_write_reg(chip, reg, val); |
| mutex_unlock(&chip->read_write_lock); |
| |
| return rc; |
| } |
| |
| static int smb348_masked_write(struct smb348_charger *chip, int reg, |
| u8 mask, u8 val) |
| { |
| s32 rc; |
| u8 temp; |
| |
| mutex_lock(&chip->read_write_lock); |
| rc = __smb348_read_reg(chip, reg, &temp); |
| if (rc) { |
| dev_err(chip->dev, |
| "smb348_read_reg Failed: reg=%03X, rc=%d\n", reg, rc); |
| goto out; |
| } |
| temp &= ~mask; |
| temp |= val & mask; |
| rc = __smb348_write_reg(chip, reg, temp); |
| if (rc) { |
| dev_err(chip->dev, |
| "smb348_write Failed: reg=%03X, rc=%d\n", reg, rc); |
| } |
| out: |
| mutex_unlock(&chip->read_write_lock); |
| return rc; |
| } |
| |
| static int smb348_enable_volatile_writes(struct smb348_charger *chip) |
| { |
| int rc; |
| |
| rc = smb348_masked_write(chip, CMD_A_REG, CMD_A_VOLATILE_W_PERM_BIT, |
| CMD_A_VOLATILE_W_PERM_BIT); |
| if (rc) |
| dev_err(chip->dev, "Couldn't write VOLATILE_W_PERM_BIT rc=%d\n", |
| rc); |
| |
| return rc; |
| } |
| |
| static XU1_HW_VER smb348_check_hw_id(void) |
| { |
| char *pos = NULL; |
| char hw_id; |
| size_t len = 0; |
| |
| pos = strnstr(saved_command_line, "chip_hw_id", strlen(saved_command_line)); |
| len = strlen("chip_hw_id"); |
| pos += (len + 1); |
| |
| sscanf(pos, "%c", &hw_id); |
| if (hw_id == 'H') |
| return HW_VER_H; |
| else if (hw_id == 'I') |
| return HW_VER_I; |
| else if (hw_id == 'J') |
| return HW_VER_J; |
| else if (hw_id == 'K') |
| return HW_VER_K; |
| else if (hw_id == 'L') |
| return HW_VER_L; |
| else |
| return 0; |
| } |
| |
| static int smb348_fastchg_current_set(struct smb348_charger *chip, |
| unsigned int fastchg_current) |
| { |
| int i; |
| |
| if ((fastchg_current < SMB348_FAST_CHG_MIN_MA) || |
| (fastchg_current > SMB348_FAST_CHG_MAX_MA)) { |
| dev_dbg(chip->dev, "bad fastchg current mA=%d asked to set\n", |
| fastchg_current); |
| return -EINVAL; |
| } |
| |
| for (i = ARRAY_SIZE(fast_chg_current) - 1; i >= 0; i--) { |
| if (fast_chg_current[i] <= fastchg_current) |
| break; |
| } |
| |
| if (i < 0) { |
| dev_err(chip->dev, "Invalid current setting %dmA\n", |
| fastchg_current); |
| i = 0; |
| } |
| |
| i = i << SMB348_FAST_CHG_SHIFT; |
| dev_dbg(chip->dev, "fastchg limit=%d setting %02x\n", |
| fastchg_current, i); |
| |
| return smb348_masked_write(chip, CHG_CURRENT_CTRL_REG, |
| SMB_FAST_CHG_CURRENT_MASK, i); |
| } |
| |
| #define MIN_FLOAT_MV 3500 |
| #define MAX_FLOAT_MV 4500 |
| #define VFLOAT_STEP_MV 20 |
| #define VFLOAT_4350MV 4350 |
| static int smb348_float_voltage_set(struct smb348_charger *chip, int vfloat_mv) |
| { |
| u8 temp; |
| |
| if ((vfloat_mv < MIN_FLOAT_MV) || (vfloat_mv > MAX_FLOAT_MV)) { |
| dev_err(chip->dev, "bad float voltage mv =%d asked to set\n", |
| vfloat_mv); |
| return -EINVAL; |
| } |
| |
| if (VFLOAT_4350MV == vfloat_mv) |
| temp = 0x2B; |
| else if (vfloat_mv > VFLOAT_4350MV) |
| temp = (vfloat_mv - MIN_FLOAT_MV) / VFLOAT_STEP_MV + 1; |
| else |
| temp = (vfloat_mv - MIN_FLOAT_MV) / VFLOAT_STEP_MV; |
| |
| return smb348_masked_write(chip, VFLOAT_REG, VFLOAT_MASK, temp); |
| } |
| |
| #define CHG_ITERM_30MA 0x00 |
| #define CHG_ITERM_40MA 0x01 |
| #define CHG_ITERM_60MA 0x02 |
| #define CHG_ITERM_80MA 0x03 |
| #define CHG_ITERM_100MA 0x04 |
| #define CHG_ITERM_125MA 0x05 |
| #define CHG_ITERM_150MA 0x06 |
| #define CHG_ITERM_200MA 0x07 |
| static int smb348_term_current_set(struct smb348_charger *chip) |
| { |
| u8 reg = 0; |
| int rc; |
| |
| if (chip->iterm_ma != -EINVAL) { |
| if (chip->iterm_disabled) |
| dev_err(chip->dev, "Error: Both iterm_disabled and iterm_ma set\n"); |
| |
| if (chip->iterm_ma <= 30) |
| reg = CHG_ITERM_30MA; |
| else if (chip->iterm_ma <= 40) |
| reg = CHG_ITERM_40MA; |
| else if (chip->iterm_ma <= 60) |
| reg = CHG_ITERM_60MA; |
| else if (chip->iterm_ma <= 80) |
| reg = CHG_ITERM_80MA; |
| else if (chip->iterm_ma <= 100) |
| reg = CHG_ITERM_100MA; |
| else if (chip->iterm_ma <= 125) |
| reg = CHG_ITERM_125MA; |
| else if (chip->iterm_ma <= 150) |
| reg = CHG_ITERM_150MA; |
| else |
| reg = CHG_ITERM_200MA; |
| |
| rc = smb348_masked_write(chip, CHG_CURRENT_CTRL_REG, |
| CHG_ITERM_MASK, reg); |
| if (rc) { |
| dev_err(chip->dev, |
| "Couldn't set iterm rc = %d\n", rc); |
| return rc; |
| } |
| } |
| |
| if (chip->iterm_disabled) { |
| rc = smb348_masked_write(chip, CHG_CTRL_REG, |
| CHG_CTRL_CURR_TERM_END_MASK, |
| CHG_CTRL_CURR_TERM_END_MASK); |
| if (rc) { |
| dev_err(chip->dev, "Couldn't set iterm rc = %d\n", |
| rc); |
| return rc; |
| } |
| } else { |
| rc = smb348_masked_write(chip, CHG_CTRL_REG, |
| CHG_CTRL_CURR_TERM_END_MASK, 0); |
| if (rc) { |
| dev_err(chip->dev, |
| "Couldn't enable iterm rc = %d\n", rc); |
| return rc; |
| } |
| } |
| |
| return 0; |
| } |
| |
| #define VFLT_300MV 0x0C |
| #define VFLT_200MV 0x08 |
| #define VFLT_100MV 0x04 |
| #define VFLT_50MV 0x00 |
| #define VFLT_MASK 0x0C |
| static int smb348_recharge_and_inhibit_set(struct smb348_charger *chip) |
| { |
| u8 reg = 0; |
| int rc; |
| |
| if (chip->recharge_disabled) |
| rc = smb348_masked_write(chip, CHG_CTRL_REG, |
| CHG_CTRL_AUTO_RECHARGE_MASK, CHG_AUTO_RECHARGE_DIS_BIT); |
| else |
| rc = smb348_masked_write(chip, CHG_CTRL_REG, |
| CHG_CTRL_AUTO_RECHARGE_MASK, 0x0); |
| if (rc) { |
| dev_err(chip->dev, |
| "Couldn't set auto recharge en reg rc = %d\n", rc); |
| } |
| |
| if (chip->inhibit_disabled) |
| rc = smb348_masked_write(chip, CHG_OTH_CURRENT_CTRL_REG, |
| CHG_INHI_EN_MASK, 0x0); |
| else |
| rc = smb348_masked_write(chip, CHG_OTH_CURRENT_CTRL_REG, |
| CHG_INHI_EN_MASK, CHG_INHI_EN_BIT); |
| if (rc) { |
| dev_err(chip->dev, |
| "Couldn't set inhibit en reg rc = %d\n", rc); |
| } |
| |
| if (chip->recharge_mv != -EINVAL) { |
| if (chip->recharge_mv <= 50) |
| reg = VFLT_50MV; |
| else if (chip->recharge_mv <= 100) |
| reg = VFLT_100MV; |
| else if (chip->recharge_mv <= 200) |
| reg = VFLT_200MV; |
| else |
| reg = VFLT_300MV; |
| |
| rc = smb348_masked_write(chip, CHG_OTH_CURRENT_CTRL_REG, |
| VFLT_MASK, reg); |
| if (rc) { |
| dev_err(chip->dev, |
| "Couldn't set inhibit threshold rc = %d\n", rc); |
| return rc; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int smb348_chg_otg_regulator_enable(struct regulator_dev *rdev) |
| { |
| int rc = 0; |
| struct smb348_charger *chip = rdev_get_drvdata(rdev); |
| |
| rc = smb348_masked_write(chip, CMD_A_REG, CMD_A_OTG_ENABLE_BIT, |
| CMD_A_OTG_ENABLE_BIT); |
| if (rc) |
| dev_err(chip->dev, "Couldn't enable OTG mode rc=%d, reg=%2x\n", |
| rc, CMD_A_REG); |
| return rc; |
| } |
| |
| static int smb348_chg_otg_regulator_disable(struct regulator_dev *rdev) |
| { |
| int rc = 0; |
| struct smb348_charger *chip = rdev_get_drvdata(rdev); |
| |
| rc = smb348_masked_write(chip, CMD_A_REG, CMD_A_OTG_ENABLE_BIT, 0); |
| if (rc) |
| dev_err(chip->dev, "Couldn't disable OTG mode rc=%d, reg=%2x\n", |
| rc, CMD_A_REG); |
| return rc; |
| } |
| |
| static int smb348_chg_otg_regulator_is_enable(struct regulator_dev *rdev) |
| { |
| int rc = 0; |
| u8 reg = 0; |
| struct smb348_charger *chip = rdev_get_drvdata(rdev); |
| |
| rc = smb348_read_reg(chip, CMD_A_REG, ®); |
| if (rc) { |
| dev_err(chip->dev, |
| "Couldn't read OTG enable bit rc=%d, reg=%2x\n", |
| rc, CMD_A_REG); |
| return rc; |
| } |
| |
| return (reg & CMD_A_OTG_ENABLE_BIT) ? 1 : 0; |
| } |
| |
| struct regulator_ops smb348_chg_otg_reg_ops = { |
| .enable = smb348_chg_otg_regulator_enable, |
| .disable = smb348_chg_otg_regulator_disable, |
| .is_enabled = smb348_chg_otg_regulator_is_enable, |
| }; |
| |
| #if 0 /* No OTG regulater*/ |
| static int smb348_regulator_init(struct smb348_charger *chip) |
| { |
| int rc = 0; |
| struct regulator_init_data *init_data; |
| struct regulator_config cfg = {}; |
| |
| init_data = of_get_regulator_init_data(chip->dev, chip->dev->of_node); |
| if (!init_data) { |
| dev_err(chip->dev, "Allocate memory failed\n"); |
| return -ENOMEM; |
| } |
| |
| /* Give the name, then will register */ |
| if (init_data->constraints.name) { |
| chip->otg_vreg.rdesc.owner = THIS_MODULE; |
| chip->otg_vreg.rdesc.type = REGULATOR_VOLTAGE; |
| chip->otg_vreg.rdesc.ops = &smb348_chg_otg_reg_ops; |
| chip->otg_vreg.rdesc.name = init_data->constraints.name; |
| |
| cfg.dev = chip->dev; |
| cfg.init_data = init_data; |
| cfg.driver_data = chip; |
| cfg.of_node = chip->dev->of_node; |
| |
| init_data->constraints.valid_ops_mask |
| |= REGULATOR_CHANGE_STATUS; |
| |
| chip->otg_vreg.rdev = regulator_register( |
| &chip->otg_vreg.rdesc, &cfg); |
| if (IS_ERR(chip->otg_vreg.rdev)) { |
| rc = PTR_ERR(chip->otg_vreg.rdev); |
| chip->otg_vreg.rdev = NULL; |
| if (rc != -EPROBE_DEFER) |
| dev_err(chip->dev, |
| "OTG reg failed, rc=%d\n", rc); |
| } |
| } |
| return rc; |
| } |
| #endif |
| |
| static int __smb348_path_suspend(struct smb348_charger *chip, bool suspend) |
| { |
| int rc; |
| |
| rc = smb348_masked_write(chip, CMD_A_REG, CMD_A_CHG_SUSP_EN_MASK, |
| suspend ? CMD_A_CHG_SUSP_EN_BIT : 0); |
| if (rc < 0) |
| dev_err(chip->dev, "Couldn't set CMD_A reg, rc = %d\n", rc); |
| |
| return rc; |
| } |
| |
| static int smb348_path_suspend(struct smb348_charger *chip, int reason, |
| bool suspend) |
| { |
| int rc = 0; |
| int suspended; |
| |
| mutex_lock(&chip->path_suspend_lock); |
| suspended = chip->usb_suspended; |
| |
| if (suspend == false) |
| suspended &= ~reason; |
| else |
| suspended |= reason; |
| |
| if (!chip->usb_suspended && suspended) { |
| rc = __smb348_path_suspend(chip, true); |
| chip->usb_suspended = suspended; |
| power_supply_set_online(chip->usb_psy, !chip->usb_suspended); |
| power_supply_changed(chip->usb_psy); |
| } else if (chip->usb_suspended && !suspended) { |
| rc = __smb348_path_suspend(chip, false); |
| chip->usb_suspended = suspended; |
| power_supply_set_online(chip->usb_psy, !chip->usb_suspended); |
| power_supply_changed(chip->usb_psy); |
| } |
| |
| if (rc) |
| dev_err(chip->dev, "Couldn't set/unset suspend rc = %d\n", rc); |
| |
| mutex_unlock(&chip->path_suspend_lock); |
| |
| return rc; |
| } |
| |
| |
| static int __smb348_charging_disable(struct smb348_charger *chip, bool disable) |
| { |
| int rc; |
| |
| rc = smb348_masked_write(chip, CMD_A_REG, CMD_A_CHG_ENABLE_BIT, |
| disable ? 0 : CMD_A_CHG_ENABLE_BIT); |
| if (rc < 0) |
| pr_err("Couldn't set CHG_ENABLE_BIT diable = %d, rc = %d\n", |
| disable, rc); |
| return rc; |
| } |
| |
| static int smb348_charging_disable(struct smb348_charger *chip, |
| int reason, int disable) |
| { |
| int rc = 0; |
| int disabled; |
| |
| disabled = chip->charging_disabled_status; |
| |
| pr_debug("reason = %d requested_disable = %d disabled_status = %d\n", |
| reason, disable, disabled); |
| |
| if (disable == true) |
| disabled |= reason; |
| else |
| disabled &= ~reason; |
| |
| if (!!disabled == !!chip->charging_disabled_status) |
| goto skip; |
| |
| rc = __smb348_charging_disable(chip, !!disabled); |
| if (rc) { |
| pr_err("Failed to disable charging rc = %d\n", rc); |
| return rc; |
| } else { |
| /* will not modify online status in this condition */ |
| power_supply_changed(&chip->batt_psy); |
| } |
| |
| skip: |
| chip->charging_disabled_status = disabled; |
| return rc; |
| } |
| |
| #define MAX_INV_BATT_ID 7700 |
| #define MIN_INV_BATT_ID 7300 |
| static int smb348_hw_init(struct smb348_charger *chip) |
| { |
| int rc; |
| u8 reg = 0, mask = 0; |
| |
| /* |
| * If the charger is pre-configured for autonomous operation, |
| * do not apply additonal settings |
| */ |
| if (chip->chg_autonomous_mode) { |
| dev_dbg(chip->dev, "Charger configured for autonomous mode\n"); |
| return 0; |
| } |
| |
| rc = smb348_enable_volatile_writes(chip); |
| if (rc) { |
| dev_err(chip->dev, "Couldn't configure volatile writes rc=%d\n", |
| rc); |
| return rc; |
| } |
| |
| /* setup defaults for CHG_CNTRL_REG */ |
| reg = CHG_CTRL_BATT_MISSING_DET_THERM_IO; |
| mask = CHG_CTRL_BATT_MISSING_DET_MASK; |
| rc = smb348_masked_write(chip, CHG_CTRL_REG, mask, reg); |
| if (rc) { |
| dev_err(chip->dev, "Couldn't set CHG_CTRL_REG rc=%d\n", rc); |
| return rc; |
| } |
| /* setup defaults for PIN_CTRL_REG */ |
| reg = CHG_PIN_CTRL_USBCS_REG_BIT | CHG_PIN_CTRL_CHG_EN_LOW_REG_BIT | |
| CHG_PIN_CTRL_APSD_IRQ_BIT | CHG_PIN_CTRL_CHG_ERR_IRQ_BIT; |
| mask = CHG_PIN_CTRL_CHG_EN_MASK | CHG_PIN_CTRL_USBCS_REG_MASK | |
| CHG_PIN_CTRL_APSD_IRQ_MASK | CHG_PIN_CTRL_CHG_ERR_IRQ_MASK; |
| rc = smb348_masked_write(chip, CHG_PIN_EN_CTRL_REG, mask, reg); |
| if (rc) { |
| dev_err(chip->dev, "Couldn't set CHG_PIN_EN_CTRL_REG rc=%d\n", |
| rc); |
| return rc; |
| } |
| |
| /* setup USB suspend and APSD */ |
| rc = smb348_masked_write(chip, VARIOUS_FUNC_REG, |
| VARIOUS_FUNC_USB_SUSP_MASK, VARIOUS_FUNC_USB_SUSP_EN_REG_BIT); |
| if (rc) { |
| dev_err(chip->dev, "Couldn't set VARIOUS_FUNC_REG rc=%d\n", |
| rc); |
| return rc; |
| } |
| |
| if (!chip->disable_apsd) |
| reg = CHG_CTRL_APSD_EN_BIT; |
| else |
| reg = 0; |
| |
| rc = smb348_masked_write(chip, CHG_CTRL_REG, |
| CHG_CTRL_APSD_EN_MASK, reg); |
| if (rc) { |
| dev_err(chip->dev, "Couldn't set CHG_CTRL_REG rc=%d\n", |
| rc); |
| return rc; |
| } |
| /* Fault and Status IRQ configuration */ |
| reg = FAULT_INT_HOT_COLD_HARD_BIT | FAULT_INT_HOT_COLD_SOFT_BIT |
| | FAULT_INT_INPUT_UV_BIT | FAULT_INT_AICL_COMPLETE_BIT |
| | FAULT_INT_INPUT_OV_BIT; |
| rc = smb348_write_reg(chip, FAULT_INT_REG, reg); |
| if (rc) { |
| dev_err(chip->dev, "Couldn't set FAULT_INT_REG rc=%d\n", rc); |
| return rc; |
| } |
| reg = STATUS_INT_CHG_TIMEOUT_BIT | STATUS_INT_OTG_DETECT_BIT | |
| STATUS_INT_BATT_OV_BIT | STATUS_INT_CHGING_BIT | |
| STATUS_INT_CHG_INHI_BIT | STATUS_INT_INOK_BIT | |
| STATUS_INT_LOW_BATT_BIT | STATUS_INT_MISSING_BATT_BIT; |
| rc = smb348_write_reg(chip, STATUS_INT_REG, reg); |
| if (rc) { |
| dev_err(chip->dev, "Couldn't set STATUS_INT_REG rc=%d\n", rc); |
| return rc; |
| } |
| /* setup THERM Monitor */ |
| rc = smb348_masked_write(chip, THERM_A_CTRL_REG, |
| THERM_A_THERM_MONITOR_EN_MASK, THERM_A_THERM_MONITOR_EN_BIT); |
| if (rc) { |
| dev_err(chip->dev, "Couldn't set THERM_A_CTRL_REG rc=%d\n", |
| rc); |
| return rc; |
| } |
| /* set the fast charge current limit */ |
| rc = smb348_fastchg_current_set(chip, chip->fastchg_current_max_ma); |
| if (rc) { |
| dev_err(chip->dev, "Couldn't set fastchg current rc=%d\n", rc); |
| return rc; |
| } |
| |
| /* set the float voltage */ |
| rc = smb348_float_voltage_set(chip, chip->vfloat_mv); |
| if (rc < 0) { |
| dev_err(chip->dev, |
| "Couldn't set float voltage rc = %d\n", rc); |
| return rc; |
| } |
| |
| /* set iterm */ |
| rc = smb348_term_current_set(chip); |
| if (rc) |
| dev_err(chip->dev, "Couldn't set term current rc=%d\n", rc); |
| |
| /* set recharge */ |
| rc = smb348_recharge_and_inhibit_set(chip); |
| if (rc) |
| dev_err(chip->dev, "Couldn't set recharge para rc=%d\n", rc); |
| |
| /* suspend USB path for fake battery */ |
| if (!chip->skip_usb_suspend_for_fake_battery) { |
| if ((chip->connected_rid >= MIN_INV_BATT_ID) && |
| (chip->connected_rid <= MAX_INV_BATT_ID)) { |
| rc = smb348_path_suspend(chip, FAKE_BATTERY, true); |
| if (!rc) |
| dev_info(chip->dev, |
| "Suspended USB path reason FAKE_BATTERY\n"); |
| } |
| } |
| |
| /* enable/disable charging */ |
| if (chip->charging_disabled) { |
| rc = smb348_charging_disable(chip, USER, 1); |
| if (rc) |
| dev_err(chip->dev, "Couldn't '%s' charging rc = %d\n", |
| chip->charging_disabled ? "disable" : "enable", rc); |
| } else { |
| /* |
| * Enable charging explictly, |
| * because not sure the default behavior. |
| */ |
| rc = __smb348_charging_disable(chip, 0); |
| if (rc) |
| dev_err(chip->dev, "Couldn't enable charging\n"); |
| } |
| |
| /* |
| * Workaround for recharge frequent issue: When battery is |
| * greater than 4.2v, and charging is disabled, charger |
| * stops switching. In such a case, system load is provided |
| * by battery rather than input, even though input is still |
| * there. Make reg09[0:3] to be a non-zero value which can |
| * keep the switcher active |
| */ |
| rc = smb348_masked_write(chip, OTHER_CTRL_REG, CHG_LOW_BATT_THRESHOLD, |
| SMB348_BATT_GOOD_THRE_2P5); |
| if (rc) |
| dev_err(chip->dev, "Couldn't write OTHER_CTRL_REG, rc = %d\n", |
| rc); |
| |
| return rc; |
| } |
| |
| static enum power_supply_property smb348_battery_properties[] = { |
| POWER_SUPPLY_PROP_STATUS, |
| POWER_SUPPLY_PROP_PRESENT, |
| POWER_SUPPLY_PROP_CHARGING_ENABLED, |
| POWER_SUPPLY_PROP_CHARGE_TYPE, |
| POWER_SUPPLY_PROP_CAPACITY, |
| POWER_SUPPLY_PROP_HEALTH, |
| POWER_SUPPLY_PROP_TECHNOLOGY, |
| POWER_SUPPLY_PROP_MODEL_NAME, |
| POWER_SUPPLY_PROP_TEMP, |
| POWER_SUPPLY_PROP_VOLTAGE_NOW, |
| POWER_SUPPLY_PROP_CURRENT_NOW, |
| }; |
| |
| static int smb348_get_prop_batt_status(struct smb348_charger *chip) |
| { |
| int rc; |
| u8 reg = 0; |
| |
| if (chip->batt_full) |
| return POWER_SUPPLY_STATUS_FULL; |
| |
| rc = smb348_read_reg(chip, STATUS_C_REG, ®); |
| if (rc) { |
| dev_err(chip->dev, "Couldn't read STAT_C rc = %d\n", rc); |
| return POWER_SUPPLY_STATUS_UNKNOWN; |
| } |
| |
| dev_dbg(chip->dev, "%s: STATUS_C_REG=%x\n", __func__, reg); |
| |
| if (reg & STATUS_C_CHG_HOLD_OFF_BIT) |
| return POWER_SUPPLY_STATUS_NOT_CHARGING; |
| |
| if ((reg & STATUS_C_CHARGING_MASK) && |
| !(reg & STATUS_C_CHG_ERR_STATUS_BIT)) { |
| if ((chg_full_flag) && (chip->chip_hw_id >= HW_VER_K)) { |
| chg_full_flag = false; |
| force_full_flag = false; |
| } |
| return POWER_SUPPLY_STATUS_CHARGING; |
| } |
| |
| return POWER_SUPPLY_STATUS_DISCHARGING; |
| } |
| |
| static int smb348_get_prop_batt_present(struct smb348_charger *chip) |
| { |
| return !chip->battery_missing; |
| } |
| |
| static int smb348_get_prop_batt_capacity(struct smb348_charger *chip) |
| { |
| union power_supply_propval ret = {0, }; |
| |
| if (chip->fake_battery_soc >= 0) |
| return chip->fake_battery_soc; |
| |
| if (chip->bms_psy) { |
| chip->bms_psy->get_property(chip->bms_psy, |
| POWER_SUPPLY_PROP_CAPACITY, &ret); |
| /* Avoid battery report negative value of capacity. if happened, report 0%. */ |
| if (ret.intval < 0) { |
| pr_err("BMS capacity is mess, ret.intval=%d\n", ret.intval); |
| chip->prop_batt_capacity = 0; |
| return 0; |
| } |
| #ifdef FEATURE_THERMAL_MITIGATION_ALGO |
| // 99% recharged check is moved to smb348_get_prop_battery_voltage_now() due to need to check voltage is lower |
| chip->prop_batt_capacity = ret.intval; // smb348_get_prop_battery_voltage_now() is always called after this func |
| |
| // return 100% once charging-end is triggered. |
| if (chg_full_flag || recharge_flag) { |
| pr_err("bms soc=%d, chg_end=%d, chg_full_flag=%d, recharge_flag=%d\n", ret.intval, gpio_get_value(chip->chg_end_gpio), chg_full_flag, recharge_flag); |
| return SMB_MAX_SOC; |
| } |
| |
| pr_err("soc=%d, chg_end=%d, chg_full_flag=%d, recharge_flag=%d\n", ret.intval, gpio_get_value(chip->chg_end_gpio), chg_full_flag, recharge_flag); |
| #endif /* FEATURE_THERMAL_MITIGATION_ALGO */ |
| return ret.intval; |
| } |
| |
| dev_dbg(chip->dev, |
| "Couldn't get bms_psy, return default capacity\n"); |
| return SMB348_DEFAULT_BATT_CAPACITY; |
| } |
| |
| static int smb348_get_prop_charge_type(struct smb348_charger *chip) |
| { |
| int rc; |
| u8 reg = 0; |
| |
| rc = smb348_read_reg(chip, STATUS_C_REG, ®); |
| if (rc) { |
| dev_err(chip->dev, "Couldn't read STAT_C rc = %d\n", rc); |
| return POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; |
| } |
| |
| dev_dbg(chip->dev, "%s: STATUS_C_REG=%x\n", __func__, reg); |
| |
| reg &= STATUS_C_CHARGING_MASK; |
| |
| if (reg == STATUS_C_FAST_CHARGING) |
| return POWER_SUPPLY_CHARGE_TYPE_FAST; |
| else if (reg == STATUS_C_TAPER_CHARGING) |
| return POWER_SUPPLY_CHARGE_TYPE_TAPER; |
| else if (reg == STATUS_C_PRE_CHARGING) |
| return POWER_SUPPLY_CHARGE_TYPE_TRICKLE; |
| else |
| return POWER_SUPPLY_CHARGE_TYPE_NONE; |
| } |
| |
| static int smb348_get_prop_batt_health(struct smb348_charger *chip) |
| { |
| union power_supply_propval ret = {0, }; |
| |
| if (chip->batt_hot) |
| ret.intval = POWER_SUPPLY_HEALTH_OVERHEAT; |
| else if (chip->batt_cold) |
| ret.intval = POWER_SUPPLY_HEALTH_COLD; |
| else if (chip->batt_warm) |
| ret.intval = POWER_SUPPLY_HEALTH_WARM; |
| else if (chip->batt_cool) |
| ret.intval = POWER_SUPPLY_HEALTH_COOL; |
| else |
| ret.intval = POWER_SUPPLY_HEALTH_GOOD; |
| |
| return ret.intval; |
| } |
| |
| #define DEFAULT_BATT_HOT_TEMP 650 |
| #define DEFAULT_BATT_WARM_TEMP 450 |
| #define DEFAULT_BATT_NORM_TEMP 250 |
| #define DEFAULT_BATT_COOL_TEMP 150 |
| #define DEFAULT_BATT_COLD_TEMP 0 |
| |
| #define DEFAULT_BATT_UPPER_TEMP 580 |
| #define DEFAULT_BATT_LOWER_TEMP 20 |
| static int smb348_get_prop_batt_temp(struct smb348_charger *chip) |
| { |
| union power_supply_propval ret = {0, }; |
| |
| if (chip->bms_psy) { |
| chip->bms_psy->get_property(chip->bms_psy, |
| POWER_SUPPLY_PROP_TEMP, &ret); |
| pr_debug("Gauge temp:%d\n", ret.intval); |
| chip->prop_batt_temp = ret.intval; // Use variable to keep current temperature |
| if (chip->wpc_state) { |
| if (ret.intval >= 500) { |
| pr_err("Temp too high, wpc disable\n"); |
| gpio_set_value(chip->wpc_enable_gpio, 1); |
| } |
| } else { |
| if (ret.intval <= 450 && |
| gpio_get_value(chip->wpc_enable_gpio) && |
| WPC_CHG_RESUME_ALLOW_GOTA && |
| !RECOVERY_WPC_DISABLE_FLAG) { |
| pr_err("Temp normal, wpc enable\n"); |
| gpio_set_value(chip->wpc_enable_gpio, 0); |
| } |
| } |
| } |
| |
| if (chip->batt_hot) { |
| if (chip->bms_psy) |
| pr_err("Battery hot irq trigger, gauge temp:%d\n", ret.intval); |
| else |
| pr_err("Battery hot irq trigger\n"); |
| |
| return DEFAULT_BATT_HOT_TEMP; |
| } |
| if (chip->batt_warm) |
| return DEFAULT_BATT_WARM_TEMP; |
| if (chip->batt_cold) |
| return DEFAULT_BATT_COLD_TEMP; |
| if (chip->batt_cool) |
| return DEFAULT_BATT_COOL_TEMP; |
| |
| if (chip->bms_psy) { |
| if (ret.intval >= DEFAULT_BATT_UPPER_TEMP) { |
| pr_err("*** Temp close shutdown point, but didn't receive batt hot irq ***\n"); |
| pr_err("*** Keep current temp(%d) to 58\n", ret.intval); |
| return DEFAULT_BATT_UPPER_TEMP; |
| } else if (ret.intval <= DEFAULT_BATT_LOWER_TEMP) { |
| pr_err("*** Temp close cold point, but didn't receive batt cold irq ***\n"); |
| pr_err("*** Keep current temp(%d) to 2\n", ret.intval); |
| return DEFAULT_BATT_LOWER_TEMP; |
| } else { |
| return ret.intval; |
| } |
| } else { |
| pr_err("No bms power supply, report normal temp\n"); |
| return DEFAULT_BATT_NORM_TEMP; |
| } |
| } |
| |
| static int |
| smb348_get_prop_battery_voltage_now(struct smb348_charger *chip) |
| { |
| union power_supply_propval ret = {0, }; |
| |
| if (chip->bms_psy) { |
| chip->bms_psy->get_property(chip->bms_psy, |
| POWER_SUPPLY_PROP_VOLTAGE_NOW, &ret); |
| #ifdef FEATURE_THERMAL_MITIGATION_ALGO |
| pr_err("chg_end=%d, chg_full_flag=%d\n", gpio_get_value(chip->chg_end_gpio), chg_full_flag); |
| if (chg_full_flag) { |
| /*******************Release CHG_END critera:********************* |
| * 1. SOC <= 99% * |
| * 2. voltage < 4330 mV * |
| * If temperature lower than 10 degree C, only check SOC <= 99 * |
| ****************************************************************/ |
| if ((chip->prop_batt_capacity <= SMB348_SW_RECHG_SOC && ret.intval < 4350000) || |
| (ret.intval < SMB348_SW_RECHG_VOLTAGE && chip->prop_batt_temp > 100 )) { |
| u8 reg; |
| int rc; |
| rc = smb348_read_reg(chip, IRQ_C_REG, ®); |
| /* Check the charge full status bit(REG 37h, bit(0)) has been clear */ |
| if (!(reg & IRQ_C_TERM_BIT)) { |
| pr_err("SW recharge is due to batt capacity=%d or voltage=%d lower than %d\n", chip->prop_batt_capacity, ret.intval, SMB348_SW_RECHG_VOLTAGE); |
| gpio_set_value(chip->chg_end_gpio, 0); |
| chg_full_flag = false; |
| force_full_flag = false; |
| recharge_flag = true; |
| } else { |
| pr_err("Cannot release CHG_END, rc = %d and reg = 0x%02x\n", rc, reg); |
| } |
| } |
| } |
| #endif /* FEATURE_THERMAL_MITIGATION_ALGO */ |
| return ret.intval; |
| } |
| |
| dev_dbg(chip->dev, |
| "Couldn't get bms_psy, return default voltage\n"); |
| return SMB348_DEFAULT_VOLTAGE; |
| } |
| |
| static int |
| smb348_get_prop_battery_current_now(struct smb348_charger *chip) |
| { |
| union power_supply_propval ret = {0, }; |
| |
| if (chip->bms_psy) { |
| chip->bms_psy->get_property(chip->bms_psy, |
| POWER_SUPPLY_PROP_CURRENT_NOW, &ret); |
| return ret.intval; |
| } |
| |
| dev_dbg(chip->dev, |
| "Couldn't get bms_psy, return default current\n"); |
| return SMB348_DEFAULT_CURRENT; |
| } |
| |
| |
| static int smb348_set_usb_chg_current(struct smb348_charger *chip, |
| int current_ma) |
| { |
| int i, rc = 0; |
| u8 reg1 = 0, reg2 = 0, mask = 0; |
| |
| dev_dbg(chip->dev, "%s: USB current_ma = %d\n", __func__, current_ma); |
| |
| if (chip->chg_autonomous_mode) { |
| dev_dbg(chip->dev, "%s: Charger in autonmous mode\n", __func__); |
| return 0; |
| } |
| |
| if (current_ma < USB3_MIN_CURRENT_MA && current_ma != 2) |
| current_ma = USB2_MIN_CURRENT_MA; |
| |
| if (current_ma == USB2_MIN_CURRENT_MA) { |
| /* USB 2.0 - 100mA */ |
| reg1 &= ~USB3_ENABLE_BIT; |
| reg2 &= ~CMD_B_CHG_USB_500_900_ENABLE_BIT; |
| } else if (current_ma == USB2_MAX_CURRENT_MA) { |
| /* USB 2.0 - 500mA */ |
| reg1 &= ~USB3_ENABLE_BIT; |
| reg2 |= CMD_B_CHG_USB_500_900_ENABLE_BIT; |
| } else if (current_ma == USB3_MAX_CURRENT_MA) { |
| /* USB 3.0 - 900mA */ |
| reg1 |= USB3_ENABLE_BIT; |
| reg2 |= CMD_B_CHG_USB_500_900_ENABLE_BIT; |
| } else if (current_ma > USB2_MAX_CURRENT_MA) { |
| /* HC mode - if none of the above */ |
| reg2 |= CMD_B_CHG_HC_ENABLE_BIT; |
| |
| for (i = ARRAY_SIZE(chg_current) - 1; i >= 0; i--) { |
| if (chg_current[i] <= current_ma) |
| break; |
| } |
| if (i < 0) { |
| dev_err(chip->dev, "Cannot find %dmA\n", current_ma); |
| i = 0; |
| } |
| |
| i = i << AC_CHG_CURRENT_SHIFT; |
| rc = smb348_masked_write(chip, CHG_OTH_CURRENT_CTRL_REG, |
| AC_CHG_CURRENT_MASK, i); |
| if (rc) |
| dev_err(chip->dev, "Couldn't set input mA rc=%d\n", rc); |
| } |
| |
| mask = CMD_B_CHG_HC_ENABLE_BIT | CMD_B_CHG_USB_500_900_ENABLE_BIT; |
| rc = smb348_masked_write(chip, CMD_B_REG, mask, reg2); |
| if (rc < 0) |
| dev_err(chip->dev, "Couldn't set charging mode rc = %d\n", rc); |
| |
| mask = USB3_ENABLE_MASK; |
| rc = smb348_masked_write(chip, SYSOK_AND_USB3_REG, mask, reg1); |
| if (rc < 0) |
| dev_err(chip->dev, "Couldn't set USB3 mode rc = %d\n", rc); |
| |
| /* Only set suspend bit when chg present and current_ma = 2 */ |
| if (current_ma == 2 && chip->chg_present) { |
| rc = smb348_path_suspend(chip, CURRENT, true); |
| if (rc < 0) |
| dev_err(chip->dev, "Couldn't suspend rc = %d\n", rc); |
| } else { |
| rc = smb348_path_suspend(chip, CURRENT, false); |
| if (rc < 0) |
| dev_err(chip->dev, "Couldn't set susp rc = %d\n", rc); |
| } |
| |
| return rc; |
| } |
| |
| static enum power_supply_property smb348_dc_properties[] = { |
| POWER_SUPPLY_PROP_PRESENT, |
| POWER_SUPPLY_PROP_ONLINE, |
| POWER_SUPPLY_PROP_HEALTH, |
| POWER_SUPPLY_PROP_CURRENT_MAX, |
| }; |
| |
| |
| static int smb348_dc_get_property(struct power_supply *psy, |
| enum power_supply_property prop, |
| union power_supply_propval *val) |
| { |
| struct smb348_charger *chip = container_of(psy, |
| struct smb348_charger, dc_psy); |
| |
| switch (prop) { |
| case POWER_SUPPLY_PROP_PRESENT: |
| if (chip->chip_hw_id >= HW_VER_K) |
| val->intval = chip->wpc_dock_present; |
| else |
| val->intval = chip->wpc_state; |
| break; |
| case POWER_SUPPLY_PROP_ONLINE: |
| if (chip->chip_hw_id >= HW_VER_K) |
| val->intval = chip->wpc_dock_present; |
| else { |
| /* return if dc is charging the battery */ |
| val->intval = !chip->charging_disabled_status ? chip->wpc_state : 0; |
| } |
| break; |
| case POWER_SUPPLY_PROP_HEALTH: |
| if (chip->chip_hw_id >= HW_VER_K) |
| val->intval = chip->wpc_dock_present; |
| else |
| val->intval = chip->wpc_state; |
| break; |
| case POWER_SUPPLY_PROP_CURRENT_MAX: |
| val->intval = chip->dc_psy_ma * 1000; |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static int |
| smb348_batt_property_is_writeable(struct power_supply *psy, |
| enum power_supply_property psp) |
| { |
| switch (psp) { |
| case POWER_SUPPLY_PROP_CHARGING_ENABLED: |
| case POWER_SUPPLY_PROP_CAPACITY: |
| return 1; |
| default: |
| break; |
| } |
| |
| return 0; |
| } |
| |
| static int bound_soc(int soc) |
| { |
| soc = max(0, soc); |
| soc = min(soc, SMB_MAX_SOC); |
| return soc; |
| } |
| |
| static int smb348_battery_set_property(struct power_supply *psy, |
| enum power_supply_property prop, |
| const union power_supply_propval *val) |
| { |
| int rc; |
| struct smb348_charger *chip = container_of(psy, |
| struct smb348_charger, batt_psy); |
| |
| switch (prop) { |
| case POWER_SUPPLY_PROP_STATUS: |
| if (!chip->bms_controlled_charging) |
| return -EINVAL; |
| switch (val->intval) { |
| case POWER_SUPPLY_STATUS_FULL: |
| rc = smb348_charging_disable(chip, SOC, true); |
| if (rc < 0) { |
| dev_err(chip->dev, |
| "Couldn't set charging disable rc = %d\n", |
| rc); |
| } else { |
| chip->batt_full = true; |
| dev_dbg(chip->dev, "status = FULL, batt_full = %d\n", |
| chip->batt_full); |
| } |
| break; |
| case POWER_SUPPLY_STATUS_DISCHARGING: |
| chip->batt_full = false; |
| power_supply_changed(&chip->batt_psy); |
| dev_dbg(chip->dev, "status = DISCHARGING, batt_full = %d\n", |
| chip->batt_full); |
| break; |
| case POWER_SUPPLY_STATUS_CHARGING: |
| rc = smb348_charging_disable(chip, SOC, false); |
| if (rc < 0) { |
| dev_err(chip->dev, |
| "Couldn't set charging disable rc = %d\n", |
| rc); |
| } else { |
| chip->batt_full = false; |
| dev_dbg(chip->dev, "status = CHARGING, batt_full = %d\n", |
| chip->batt_full); |
| } |
| break; |
| default: |
| return -EINVAL; |
| } |
| break; |
| case POWER_SUPPLY_PROP_CHARGING_ENABLED: |
| smb348_charging_disable(chip, USER, !val->intval); |
| smb348_path_suspend(chip, USER, !val->intval); |
| break; |
| case POWER_SUPPLY_PROP_CAPACITY: |
| chip->fake_battery_soc = bound_soc(val->intval); |
| power_supply_changed(&chip->batt_psy); |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static int smb348_battery_get_property(struct power_supply *psy, |
| enum power_supply_property prop, |
| union power_supply_propval *val) |
| { |
| struct smb348_charger *chip = container_of(psy, |
| struct smb348_charger, batt_psy); |
| |
| switch (prop) { |
| case POWER_SUPPLY_PROP_STATUS: |
| val->intval = smb348_get_prop_batt_status(chip); |
| break; |
| case POWER_SUPPLY_PROP_PRESENT: |
| val->intval = smb348_get_prop_batt_present(chip); |
| break; |
| case POWER_SUPPLY_PROP_CAPACITY: |
| val->intval = smb348_get_prop_batt_capacity(chip); |
| break; |
| case POWER_SUPPLY_PROP_CHARGING_ENABLED: |
| val->intval = !(chip->charging_disabled_status & USER); |
| break; |
| case POWER_SUPPLY_PROP_CHARGE_TYPE: |
| val->intval = smb348_get_prop_charge_type(chip); |
| break; |
| case POWER_SUPPLY_PROP_HEALTH: |
| val->intval = smb348_get_prop_batt_health(chip); |
| break; |
| case POWER_SUPPLY_PROP_TECHNOLOGY: |
| val->intval = POWER_SUPPLY_TECHNOLOGY_LION; |
| break; |
| case POWER_SUPPLY_PROP_MODEL_NAME: |
| val->strval = "SMB348"; |
| break; |
| case POWER_SUPPLY_PROP_TEMP: |
| val->intval = smb348_get_prop_batt_temp(chip); |
| break; |
| case POWER_SUPPLY_PROP_VOLTAGE_NOW: |
| val->intval = smb348_get_prop_battery_voltage_now(chip); |
| break; |
| case POWER_SUPPLY_PROP_CURRENT_NOW: |
| val->intval = smb348_get_prop_battery_current_now(chip); |
| break; |
| default: |
| return -EINVAL; |
| } |
| return 0; |
| } |
| |
| static int apsd_complete(struct smb348_charger *chip, u8 status) |
| { |
| int rc; |
| u8 reg = 0; |
| enum power_supply_type type = POWER_SUPPLY_TYPE_UNKNOWN; |
| |
| /* |
| * If apsd is disabled, charger detection is done by |
| * DCIN UV irq. |
| * status = ZERO - indicates charger removed, handled |
| * by DCIN UV irq |
| */ |
| if (chip->disable_apsd || status == 0) { |
| dev_dbg(chip->dev, "APSD %s, status = %d\n", |
| chip->disable_apsd ? "disabled" : "enabled", !!status); |
| return 0; |
| } |
| |
| rc = smb348_read_reg(chip, STATUS_D_REG, ®); |
| if (rc) { |
| dev_err(chip->dev, "Couldn't read STATUS D rc = %d\n", rc); |
| return rc; |
| } |
| |
| dev_dbg(chip->dev, "%s: STATUS_D_REG=%x\n", __func__, reg); |
| |
| switch (reg & STATUS_D_CHARGING_PORT_MASK) { |
| case STATUS_D_PORT_ACA_DOCK: |
| case STATUS_D_PORT_ACA_C: |
| case STATUS_D_PORT_ACA_B: |
| case STATUS_D_PORT_ACA_A: |
| type = POWER_SUPPLY_TYPE_USB_ACA; |
| break; |
| case STATUS_D_PORT_CDP: |
| type = POWER_SUPPLY_TYPE_USB_CDP; |
| break; |
| case STATUS_D_PORT_DCP: |
| type = POWER_SUPPLY_TYPE_USB_DCP; |
| break; |
| case STATUS_D_PORT_SDP: |
| type = POWER_SUPPLY_TYPE_USB; |
| break; |
| case STATUS_D_PORT_OTHER: |
| type = POWER_SUPPLY_TYPE_USB_DCP; |
| break; |
| default: |
| type = POWER_SUPPLY_TYPE_USB; |
| break; |
| } |
| |
| chip->chg_present = !!status; |
| |
| dev_dbg(chip->dev, "APSD complete. USB type detected=%d chg_present=%d", |
| type, chip->chg_present); |
| power_supply_set_supply_type(chip->usb_psy, type); |
| |
| /* SMB is now done sampling the D+/D- lines, indicate USB driver */ |
| dev_dbg(chip->dev, "%s updating usb_psy present=%d", __func__, |
| chip->chg_present); |
| power_supply_set_present(chip->usb_psy, chip->chg_present); |
| |
| return 0; |
| } |
| |
| static int chg_uv(struct smb348_charger *chip, u8 status) |
| { |
| int rc; |
| |
| /* use this to detect USB insertion only if !apsd */ |
| if (chip->disable_apsd && status == 0) { |
| chip->chg_present = true; |
| dev_dbg(chip->dev, "%s updating usb_psy present=%d", |
| __func__, chip->chg_present); |
| if (!chip->wpc_state) { |
| pr_err("TYPE: USB\n"); |
| power_supply_set_supply_type(chip->usb_psy, POWER_SUPPLY_TYPE_USB); |
| } else { |
| pr_err("TYPE: USB DCP\n"); |
| power_supply_set_supply_type(chip->usb_psy, POWER_SUPPLY_TYPE_USB_DCP); |
| } |
| power_supply_set_present(chip->usb_psy, chip->chg_present); |
| |
| if (chip->bms_controlled_charging) { |
| /* |
| * Disable SOC based USB suspend to enable charging on |
| * USB insertion. |
| */ |
| rc = smb348_charging_disable(chip, SOC, false); |
| if (rc < 0) |
| dev_err(chip->dev, |
| "Couldn't disable usb suspend rc = %d\n", |
| rc); |
| } |
| } |
| |
| if (status != 0) { |
| if (chip->chip_hw_id >= HW_VER_K) { |
| if (chip->wpc_dock_present) |
| return 0; |
| } |
| chip->chg_present = false; |
| dev_dbg(chip->dev, "%s updating usb_psy present=%d", |
| __func__, chip->chg_present); |
| /* we can't set usb_psy as UNKNOWN here, will lead USERSPACE issue */ |
| power_supply_set_present(chip->usb_psy, chip->chg_present); |
| } |
| |
| power_supply_changed(chip->usb_psy); |
| |
| dev_dbg(chip->dev, "chip->chg_present = %d\n", chip->chg_present); |
| |
| return 0; |
| } |
| |
| static int chg_ov(struct smb348_charger *chip, u8 status) |
| { |
| u8 psy_health_sts; |
| if (status) |
| psy_health_sts = POWER_SUPPLY_HEALTH_OVERVOLTAGE; |
| else |
| psy_health_sts = POWER_SUPPLY_HEALTH_GOOD; |
| |
| power_supply_set_health_state( |
| chip->usb_psy, psy_health_sts); |
| power_supply_changed(chip->usb_psy); |
| |
| return 0; |
| } |
| |
| #define STATUS_FAST_CHARGING BIT(6) |
| static int fast_chg(struct smb348_charger *chip, u8 status) |
| { |
| dev_dbg(chip->dev, "%s\n", __func__); |
| |
| if (status & STATUS_FAST_CHARGING) |
| chip->batt_full = false; |
| return 0; |
| } |
| |
| static int chg_term(struct smb348_charger *chip, u8 status) |
| { |
| bool check_dock = 0; |
| /* Pull up the CHG_END gpio to stop blink LED, |
| And set chg_full_flag. */ |
| mutex_lock(&chip->wpc_chg_complete); |
| |
| if (chip->chip_hw_id >= HW_VER_K) |
| check_dock = chip->wpc_dock_present; |
| else |
| check_dock = chip->wpc_state; |
| /* chg_full_flag avoid device enter charge <-> charge full loop */ |
| if (!chg_full_flag && check_dock) { |
| pr_err("Full-Charged\n"); |
| gpio_set_value(chip->chg_end_gpio, 1); |
| chg_full_flag = true; |
| force_full_flag = true; |
| recharge_flag = false; |
| #ifndef FEATURE_THERMAL_MITIGATION_ALGO |
| msleep(100); |
| gpio_set_value(chip->chg_end_gpio, 0); |
| #endif /* FEATURE_THERMAL_MITIGATION_ALGO */ |
| } |
| mutex_unlock(&chip->wpc_chg_complete); |
| |
| pr_err("Full-Charged\n"); |
| if (chip->chip_hw_id < HW_VER_K) { |
| if (!chip->iterm_disabled) { |
| chip->batt_full = !!status; |
| } |
| } else { |
| printk("status = %d\n",status); |
| if (chg_full_flag) |
| chip->batt_full = true; |
| } |
| return 0; |
| } |
| |
| static int taper_chg(struct smb348_charger *chip, u8 status) |
| { |
| dev_dbg(chip->dev, "%s\n", __func__); |
| return 0; |
| } |
| |
| static int chg_recharge(struct smb348_charger *chip, u8 status) |
| { |
| dev_dbg(chip->dev, "%s, status = %d\n", __func__, !!status); |
| /* to check the status mean */ |
| chip->batt_full = !status; |
| return 0; |
| } |
| |
| static void smb348_wpc_chg_gpio_clear(struct smb348_charger *chip) |
| { |
| if (gpio_is_valid(chip->chg_end_gpio)) { |
| if (gpio_get_value(chip->chg_end_gpio)) { |
| pr_err("Clear chg_end_gpio\n"); |
| gpio_set_value(chip->chg_end_gpio, 0); |
| force_full_flag = false; |
| } |
| } |
| if (gpio_is_valid(chip->wpc_enable_gpio)) { |
| if (gpio_get_value(chip->wpc_enable_gpio)) { |
| pr_err("Clear wpc_enable_gpio\n"); |
| gpio_set_value(chip->wpc_enable_gpio, 0); |
| } |
| } |
| } |
| |
| static void smb348_chg_set_appropriate_battery_current( |
| struct smb348_charger *chip) |
| { |
| int rc; |
| unsigned int current_max = chip->fastchg_current_max_ma; |
| |
| if (chip->batt_cool) |
| current_max = |
| min(current_max, chip->cool_bat_ma); |
| if (chip->batt_warm) |
| current_max = |
| min(current_max, chip->warm_bat_ma); |
| dev_dbg(chip->dev, "setting %dmA", current_max); |
| rc = smb348_fastchg_current_set(chip, current_max); |
| if (rc) |
| dev_err(chip->dev, |
| "Couldn't set charging current rc = %d\n", rc); |
| } |
| |
| static void smb348_chg_set_appropriate_vddmax( |
| struct smb348_charger *chip) |
| { |
| int rc; |
| unsigned int vddmax = chip->vfloat_mv; |
| |
| if (chip->batt_cool) |
| vddmax = min(vddmax, chip->cool_bat_mv); |
| if (chip->batt_warm) |
| vddmax = min(vddmax, chip->warm_bat_mv); |
| |
| dev_dbg(chip->dev, "setting %dmV\n", vddmax); |
| rc = smb348_float_voltage_set(chip, vddmax); |
| if (rc) |
| dev_err(chip->dev, |
| "Couldn't set float voltage rc = %d\n", rc); |
| } |
| |
| #define HYSTERESIS_DECIDEGC 20 |
| static void smb_chg_adc_notification(enum qpnp_tm_state state, void *ctx) |
| { |
| struct smb348_charger *chip = ctx; |
| bool bat_hot = 0, bat_cold = 0, bat_present = 0, bat_warm = 0, |
| bat_cool = 0; |
| int temp; |
| |
| if (state >= ADC_TM_STATE_NUM) { |
| pr_err("invallid state parameter %d\n", state); |
| return; |
| } |
| |
| temp = smb348_get_prop_batt_temp(chip); |
| |
| pr_debug("temp = %d state = %s\n", temp, |
| state == ADC_TM_WARM_STATE ? "hot" : "cold"); |
| |
| if (state == ADC_TM_WARM_STATE) { |
| if (temp >= chip->hot_bat_decidegc) { |
| bat_hot = true; |
| bat_warm = false; |
| bat_cold = false; |
| bat_cool = false; |
| bat_present = true; |
| |
| chip->adc_param.low_temp = |
| chip->hot_bat_decidegc - HYSTERESIS_DECIDEGC; |
| chip->adc_param.state_request = |
| ADC_TM_COOL_THR_ENABLE; |
| } else if (temp >= |
| chip->warm_bat_decidegc && chip->jeita_supported) { |
| bat_hot = false; |
| bat_warm = true; |
| bat_cold = false; |
| bat_cool = false; |
| bat_present = true; |
| |
| chip->adc_param.low_temp = |
| chip->warm_bat_decidegc - HYSTERESIS_DECIDEGC; |
| chip->adc_param.high_temp = |
| chip->hot_bat_decidegc; |
| } else if (temp >= |
| chip->cool_bat_decidegc && chip->jeita_supported) { |
| bat_hot = false; |
| bat_warm = false; |
| bat_cold = false; |
| bat_cool = false; |
| bat_present = true; |
| |
| chip->adc_param.low_temp = |
| chip->cool_bat_decidegc - HYSTERESIS_DECIDEGC; |
| chip->adc_param.high_temp = |
| chip->warm_bat_decidegc; |
| } else if (temp >= |
| chip->cold_bat_decidegc) { |
| bat_hot = false; |
| bat_warm = false; |
| bat_cold = false; |
| bat_cool = true; |
| bat_present = true; |
| |
| chip->adc_param.low_temp = |
| chip->cold_bat_decidegc - HYSTERESIS_DECIDEGC; |
| if (chip->jeita_supported) |
| chip->adc_param.high_temp = |
| chip->cool_bat_decidegc; |
| else |
| chip->adc_param.high_temp = |
| chip->hot_bat_decidegc; |
| chip->adc_param.state_request = |
| ADC_TM_HIGH_LOW_THR_ENABLE; |
| } else if (temp >= chip->bat_present_decidegc) { |
| bat_hot = false; |
| bat_warm = false; |
| bat_cold = true; |
| bat_cool = false; |
| bat_present = true; |
| |
| chip->adc_param.high_temp = chip->cold_bat_decidegc; |
| chip->adc_param.low_temp = chip->bat_present_decidegc |
| - HYSTERESIS_DECIDEGC; |
| chip->adc_param.state_request = |
| ADC_TM_HIGH_LOW_THR_ENABLE; |
| } |
| } else { |
| if (temp <= chip->bat_present_decidegc) { |
| bat_cold = true; |
| bat_cool = false; |
| bat_hot = false; |
| bat_warm = false; |
| bat_present = false; |
| chip->adc_param.high_temp = chip->bat_present_decidegc |
| + HYSTERESIS_DECIDEGC; |
| chip->adc_param.state_request = |
| ADC_TM_WARM_THR_ENABLE; |
| } else if (temp <= chip->cold_bat_decidegc) { |
| bat_hot = false; |
| bat_warm = false; |
| bat_cold = true; |
| bat_cool = false; |
| bat_present = true; |
| chip->adc_param.high_temp = |
| chip->cold_bat_decidegc + HYSTERESIS_DECIDEGC; |
| /* add low_temp to enable batt present check */ |
| chip->adc_param.low_temp = |
| chip->bat_present_decidegc; |
| chip->adc_param.state_request = |
| ADC_TM_HIGH_LOW_THR_ENABLE; |
| } else if (temp <= chip->cool_bat_decidegc && |
| chip->jeita_supported) { |
| bat_hot = false; |
| bat_warm = false; |
| bat_cold = false; |
| bat_cool = true; |
| bat_present = true; |
| chip->adc_param.high_temp = |
| chip->cool_bat_decidegc + HYSTERESIS_DECIDEGC; |
| chip->adc_param.low_temp = |
| chip->cold_bat_decidegc; |
| chip->adc_param.state_request = |
| ADC_TM_HIGH_LOW_THR_ENABLE; |
| } else if (temp <= chip->warm_bat_decidegc && |
| chip->jeita_supported) { |
| bat_hot = false; |
| bat_warm = false; |
| bat_cold = false; |
| bat_cool = false; |
| bat_present = true; |
| chip->adc_param.high_temp = |
| chip->warm_bat_decidegc + HYSTERESIS_DECIDEGC; |
| chip->adc_param.low_temp = |
| chip->cool_bat_decidegc; |
| chip->adc_param.state_request = |
| ADC_TM_HIGH_LOW_THR_ENABLE; |
| } else if (temp <= chip->hot_bat_decidegc) { |
| bat_hot = false; |
| bat_warm = true; |
| bat_cold = false; |
| bat_cool = false; |
| bat_present = true; |
| if (chip->jeita_supported) |
| chip->adc_param.low_temp = |
| chip->warm_bat_decidegc; |
| else |
| chip->adc_param.low_temp = |
| chip->cold_bat_decidegc; |
| chip->adc_param.high_temp = |
| chip->hot_bat_decidegc + HYSTERESIS_DECIDEGC; |
| chip->adc_param.state_request = |
| ADC_TM_HIGH_LOW_THR_ENABLE; |
| } |
| } |
| |
| if (bat_present) |
| chip->battery_missing = false; |
| else |
| chip->battery_missing = true; |
| |
| if (bat_hot ^ chip->batt_hot || bat_cold ^ chip->batt_cold) { |
| chip->batt_hot = bat_hot; |
| chip->batt_cold = bat_cold; |
| /* stop charging explicitly since we use PMIC thermal pin*/ |
| if (bat_hot || bat_cold || chip->battery_missing) |
| smb348_charging_disable(chip, THERMAL, 1); |
| else |
| smb348_charging_disable(chip, THERMAL, 0); |
| } |
| |
| if ((chip->batt_warm ^ bat_warm || chip->batt_cool ^ bat_cool) |
| && chip->jeita_supported) { |
| chip->batt_warm = bat_warm; |
| chip->batt_cool = bat_cool; |
| smb348_chg_set_appropriate_battery_current(chip); |
| smb348_chg_set_appropriate_vddmax(chip); |
| } |
| |
| pr_debug("hot %d, cold %d, warm %d, cool %d, jeita supported %d, missing %d, low = %d deciDegC, high = %d deciDegC\n", |
| chip->batt_hot, chip->batt_cold, chip->batt_warm, |
| chip->batt_cool, chip->jeita_supported, chip->battery_missing, |
| chip->adc_param.low_temp, chip->adc_param.high_temp); |
| if (qpnp_adc_tm_channel_measure(chip->adc_tm_dev, &chip->adc_param)) |
| pr_err("request ADC error\n"); |
| } |
| |
| /* only for SMB thermal */ |
| static int hot_hard_handler(struct smb348_charger *chip, u8 status) |
| { |
| pr_debug("status = 0x%02x\n", status); |
| chip->batt_hot = !!status; |
| return 0; |
| } |
| static int cold_hard_handler(struct smb348_charger *chip, u8 status) |
| { |
| pr_debug("status = 0x%02x\n", status); |
| chip->batt_cold = !!status; |
| return 0; |
| } |
| static int hot_soft_handler(struct smb348_charger *chip, u8 status) |
| { |
| pr_debug("status = 0x%02x\n", status); |
| chip->batt_warm = !!status; |
| return 0; |
| } |
| static int cold_soft_handler(struct smb348_charger *chip, u8 status) |
| { |
| pr_debug("status = 0x%02x\n", status); |
| chip->batt_cool = !!status; |
| return 0; |
| } |
| |
| static int battery_missing(struct smb348_charger *chip, u8 status) |
| { |
| chip->battery_missing = !!status; |
| return 0; |
| } |
| |
| static struct irq_handler_info handlers[] = { |
| [0] = { |
| .stat_reg = IRQ_A_REG, |
| .val = 0, |
| .prev_val = 0, |
| .irq_info = { |
| { |
| .name = "cold_soft", |
| .smb_irq = cold_soft_handler, |
| }, |
| { |
| .name = "hot_soft", |
| .smb_irq = hot_soft_handler, |
| }, |
| { |
| .name = "cold_hard", |
| .smb_irq = cold_hard_handler, |
| }, |
| { |
| .name = "hot_hard", |
| .smb_irq = hot_hard_handler, |
| }, |
| }, |
| }, |
| [1] = { |
| .stat_reg = IRQ_B_REG, |
| .val = 0, |
| .prev_val = 0, |
| .irq_info = { |
| { |
| .name = "chg_hot", |
| }, |
| { |
| .name = "vbat_low", |
| }, |
| { |
| .name = "battery_missing", |
| .smb_irq = battery_missing |
| }, |
| { |
| .name = "battery_ov", |
| }, |
| }, |
| }, |
| [2] = { |
| .stat_reg = IRQ_C_REG, |
| .val = 0, |
| .prev_val = 0, |
| .irq_info = { |
| { |
| .name = "chg_term", |
| .smb_irq = chg_term, |
| }, |
| { |
| .name = "taper", |
| .smb_irq = taper_chg, |
| }, |
| { |
| .name = "recharge", |
| .smb_irq = chg_recharge, |
| }, |
| { |
| .name = "fast_chg", |
| .smb_irq = fast_chg, |
| }, |
| }, |
| }, |
| [3] = { |
| .stat_reg = IRQ_D_REG, |
| .val = 0, |
| .prev_val = 0, |
| .irq_info = { |
| { |
| .name = "prechg_timeout", |
| }, |
| { |
| .name = "safety_timeout", |
| }, |
| { |
| .name = "aicl_complete", |
| }, |
| { |
| .name = "src_detect", |
| .smb_irq = apsd_complete, |
| }, |
| }, |
| }, |
| [4] = { |
| .stat_reg = IRQ_E_REG, |
| .val = 0, |
| .prev_val = 0, |
| .irq_info = { |
| { |
| .name = "usbin_uv", |
| .smb_irq = chg_uv, |
| }, |
| { |
| .name = "usbin_ov", |
| .smb_irq = chg_ov, |
| }, |
| { |
| .name = "unknown", |
| }, |
| { |
| .name = "unknown", |
| }, |
| }, |
| }, |
| [5] = { |
| .stat_reg = IRQ_F_REG, |
| .val = 0, |
| .prev_val = 0, |
| .irq_info = { |
| { |
| .name = "power_ok", |
| }, |
| { |
| .name = "otg_det", |
| }, |
| { |
| .name = "otg_batt_uv", |
| }, |
| { |
| .name = "otg_oc", |
| }, |
| }, |
| }, |
| }; |
| |
| #define IRQ_LATCHED_MASK 0x02 |
| #define IRQ_STATUS_MASK 0x01 |
| #define BITS_PER_IRQ 2 |
| static irqreturn_t smb348_chg_stat_handler(int irq, void *dev_id) |
| { |
| struct smb348_charger *chip = dev_id; |
| int i, j; |
| u8 triggered; |
| u8 changed; |
| u8 rt_stat, prev_rt_stat; |
| int rc; |
| int handler_count = 0; |
| |
| mutex_lock(&chip->irq_complete); |
| |
| chip->irq_waiting = true; |
| if (!chip->resume_completed) { |
| dev_dbg(chip->dev, "IRQ triggered before device-resume\n"); |
| disable_irq_nosync(irq); |
| mutex_unlock(&chip->irq_complete); |
| return IRQ_HANDLED; |
| } |
| chip->irq_waiting = false; |
| |
| for (i = 0; i < ARRAY_SIZE(handlers); i++) { |
| rc = smb348_read_reg(chip, handlers[i].stat_reg, |
| &handlers[i].val); |
| if (rc < 0) { |
| dev_err(chip->dev, "Couldn't read %d rc = %d\n", |
| handlers[i].stat_reg, rc); |
| continue; |
| } |
| |
| for (j = 0; j < ARRAY_SIZE(handlers[i].irq_info); j++) { |
| triggered = handlers[i].val |
| & (IRQ_LATCHED_MASK << (j * BITS_PER_IRQ)); |
| rt_stat = handlers[i].val |
| & (IRQ_STATUS_MASK << (j * BITS_PER_IRQ)); |
| prev_rt_stat = handlers[i].prev_val |
| & (IRQ_STATUS_MASK << (j * BITS_PER_IRQ)); |
| changed = prev_rt_stat ^ rt_stat; |
| |
| if (triggered || changed) |
| rt_stat ? handlers[i].irq_info[j].high++ : |
| handlers[i].irq_info[j].low++; |
| |
| if ((triggered || changed) |
| && handlers[i].irq_info[j].smb_irq != NULL) { |
| handler_count++; |
| rc = handlers[i].irq_info[j].smb_irq(chip, |
| rt_stat); |
| if (rc < 0) |
| dev_err(chip->dev, |
| "Couldn't handle %d irq for reg 0x%02x rc = %d\n", |
| j, handlers[i].stat_reg, rc); |
| } |
| } |
| handlers[i].prev_val = handlers[i].val; |
| } |
| |
| pr_debug("handler count = %d\n", handler_count); |
| if (handler_count) { |
| pr_debug("batt psy changed\n"); |
| power_supply_changed(&chip->batt_psy); |
| } |
| |
| mutex_unlock(&chip->irq_complete); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static irqreturn_t smb348_chg_valid_handler(int irq, void *dev_id) |
| { |
| struct smb348_charger *chip = dev_id; |
| int present; |
| |
| present = gpio_get_value_cansleep(chip->chg_valid_gpio); |
| if (present < 0) { |
| dev_err(chip->dev, "Couldn't read chg_valid gpio=%d\n", |
| chip->chg_valid_gpio); |
| return IRQ_HANDLED; |
| } |
| present ^= chip->chg_valid_act_low; |
| |
| dev_dbg(chip->dev, "%s: chg_present = %d\n", __func__, present); |
| |
| if (present != chip->chg_present) { |
| chip->chg_present = present; |
| dev_dbg(chip->dev, "%s updating usb_psy present=%d", |
| __func__, chip->chg_present); |
| power_supply_set_present(chip->usb_psy, chip->chg_present); |
| } |
| |
| return IRQ_HANDLED; |
| } |
| |
| /* smb348_wpc_handler to monitor WPC in/out interrupt */ |
| #define WPC_CHG_FULL_MS 2000 |
| static irqreturn_t smb348_wpc_state_handler(int irq, void *dev_id) |
| { |
| struct smb348_charger *chip = dev_id; |
| |
| chip->wpc_state = !gpio_get_value(chip->wpc_stat_gpio); |
| pr_err("wpc status pin changed!! chg_end=%d state=%d enable=%d, detect=%d\n", gpio_get_value(chip->chg_end_gpio), chip->wpc_state, gpio_get_value(chip->wpc_enable_gpio), gpio_get_value(chip->wpc_dock_detect_gpio)); |
| |
| if (gpio_get_value(chip->chg_end_gpio) | gpio_get_value(chip->wpc_enable_gpio)) { |
| pr_err("Stop Charging: wpc_state=%d, wpc_dock_present=%d\n", chip->wpc_state, gpio_get_value(chip->wpc_dock_detect_gpio)); |
| return IRQ_HANDLED; |
| } else if (0) { // toDO |
| // stop charging (not caused by chg_eng or enable pin). Measns remove from dock |
| // toDO: device may be still on dock but |
| // 1. something wrong in dock or |
| // 2. JEITA protection |
| |
| return IRQ_HANDLED; |
| } |
| else { |
| chip->wpc_dock_present = chip->wpc_state; |
| } |
| |
| pr_err("Updated wpc_dock_present = %d wpc_state=%d\n", chip->wpc_dock_present, chip->wpc_state); |
| schedule_work(&chip->wpc_dock_wq); |
| return IRQ_HANDLED; |
| } |
| |
| /* smb348_wpc_dock_handler to monitor dock mode in/out interrupt */ |
| /* only called by HW rev. K or later */ |
| static irqreturn_t smb348_wpc_dock_handler(int irq, void *dev_id) |
| { |
| struct smb348_charger *chip = dev_id; |
| // chg eng (L: charging/blink; H: discharging/solid on) |
| // state (L: charging; H: discharging) |
| // enable (L: charging; H: discharging) |
| // detect (L: on-dock; H: off-dock) |
| chip->wpc_dock_present = !gpio_get_value(chip->wpc_dock_detect_gpio); |
| pr_err("wpc detect pin changed!! chg_end=%d state=%d enable=%d, detect=%d\n", |
| gpio_get_value(chip->chg_end_gpio), !gpio_get_value(chip->wpc_stat_gpio), gpio_get_value(chip->wpc_enable_gpio), chip->wpc_dock_present); |
| schedule_work(&chip->wpc_dock_wq); |
| return IRQ_HANDLED; |
| } |
| |
| /* Quanta { 3 mins delay for WPC charge */ |
| static void smb348_wpc_resume_to_charge(struct work_struct *work) |
| { |
| struct smb348_charger *chip = container_of(work, |
| struct smb348_charger, wpc_resume_chg_wq.work); |
| |
| pr_err("[DDDBG] resume charging - chg_full_flag = %d, wpc_state= %d\n", |
| chg_full_flag, chip->wpc_state); |
| gpio_set_value(chip->wpc_enable_gpio, 0); |
| WPC_CHG_RESUME_ALLOW_GOTA = 1; |
| } |
| /* } Quanta */ |
| |
| /* Quanta 5 secs delay for real soc report */ |
| static void smb348_resume_to_real_soc(struct work_struct *work) |
| { |
| /* Release recharge and charge full flags */ |
| if (recharge_flag) |
| recharge_flag = false; |
| if (chg_full_flag) |
| chg_full_flag = false; |
| } |
| /* Quanta */ |
| |
| static void smb348_wpc_dock_work(struct work_struct *work) |
| { |
| int state = 0; |
| struct smb348_charger *chip = container_of(work, |
| struct smb348_charger, wpc_dock_wq); |
| |
| state = chip->dock_sdev.state; |
| pr_err("current dock state = %d, chip->wpc_dock_present = %d\n", state, chip->wpc_dock_present); |
| if (chip->wpc_dock_present != state) { |
| switch_set_state(&chip->dock_sdev, chip->wpc_dock_present); |
| power_supply_changed(&chip->dc_psy); |
| pr_err("Dock state changed - %s\n", chip->dock_sdev.state?"PRESENT":"REMOVED"); |
| } |
| /* removed from dock so clear the discharge gpio */ |
| if (!chip->wpc_dock_present) { |
| smb348_wpc_chg_gpio_clear(chip); |
| /* This delay work to avoid show non full capacity in 5 secs */ |
| schedule_delayed_work(&chip->resume_real_soc_wq, msecs_to_jiffies(RECHG_RESUME_REAL_SOC_MS)); |
| chip->chg_present = false; |
| if (chip->batt_full) { |
| chip->batt_full = false; |
| } |
| power_supply_set_present(chip->usb_psy, chip->chg_present); |
| power_supply_changed(chip->usb_psy); |
| } |
| } |
| |
| static void smb348_external_power_changed(struct power_supply *psy) |
| { |
| struct smb348_charger *chip = container_of(psy, |
| struct smb348_charger, batt_psy); |
| union power_supply_propval prop = {0,}; |
| int rc, current_limit = 0; |
| |
| if (chip->bms_psy_name) |
| chip->bms_psy = |
| power_supply_get_by_name((char *)chip->bms_psy_name); |
| |
| rc = chip->usb_psy->get_property(chip->usb_psy, |
| POWER_SUPPLY_PROP_CURRENT_MAX, &prop); |
| if (rc) |
| dev_err(chip->dev, |
| "Couldn't read USB current_max property, rc=%d\n", rc); |
| else |
| current_limit = prop.intval / 1000; |
| |
| /* Hardcode 500mA if WPC present */ |
| if (chip->wpc_state || chip->wpc_dock_present || chip->chg_present) { |
| pr_debug("USB psy current max=%d!! hardcode 500mA\n", current_limit); |
| current_limit = 500; |
| } |
| |
| smb348_enable_volatile_writes(chip); |
| smb348_set_usb_chg_current(chip, current_limit); |
| |
| dev_dbg(chip->dev, "current_limit = %d\n", current_limit); |
| } |
| |
| #if defined(CONFIG_DEBUG_FS) |
| #define LAST_CNFG_REG 0x13 |
| static int show_cnfg_regs(struct seq_file *m, void *data) |
| { |
| struct smb348_charger *chip = m->private; |
| int rc; |
| u8 reg; |
| u8 addr; |
| |
| for (addr = 0; addr <= LAST_CNFG_REG; addr++) { |
| rc = smb348_read_reg(chip, addr, ®); |
| if (!rc) |
| seq_printf(m, "0x%02x = 0x%02x\n", addr, reg); |
| } |
| |
| return 0; |
| } |
| |
| static int cnfg_debugfs_open(struct inode *inode, struct file *file) |
| { |
| struct smb348_charger *chip = inode->i_private; |
| |
| return single_open(file, show_cnfg_regs, chip); |
| } |
| |
| static const struct file_operations cnfg_debugfs_ops = { |
| .owner = THIS_MODULE, |
| .open = cnfg_debugfs_open, |
| .read = seq_read, |
| .llseek = seq_lseek, |
| .release = single_release, |
| }; |
| |
| #define FIRST_CMD_REG 0x30 |
| #define LAST_CMD_REG 0x33 |
| static int show_cmd_regs(struct seq_file *m, void *data) |
| { |
| struct smb348_charger *chip = m->private; |
| int rc; |
| u8 reg; |
| u8 addr; |
| |
| for (addr = FIRST_CMD_REG; addr <= LAST_CMD_REG; addr++) { |
| rc = smb348_read_reg(chip, addr, ®); |
| if (!rc) |
| seq_printf(m, "0x%02x = 0x%02x\n", addr, reg); |
| } |
| |
| return 0; |
| } |
| |
| static int cmd_debugfs_open(struct inode *inode, struct file *file) |
| { |
| struct smb348_charger *chip = inode->i_private; |
| |
| return single_open(file, show_cmd_regs, chip); |
| } |
| |
| static const struct file_operations cmd_debugfs_ops = { |
| .owner = THIS_MODULE, |
| .open = cmd_debugfs_open, |
| .read = seq_read, |
| .llseek = seq_lseek, |
| .release = single_release, |
| }; |
| |
| #define FIRST_STATUS_REG 0x35 |
| #define LAST_STATUS_REG 0x3F |
| static int show_status_regs(struct seq_file *m, void *data) |
| { |
| struct smb348_charger *chip = m->private; |
| int rc; |
| u8 reg; |
| u8 addr; |
| |
| for (addr = FIRST_STATUS_REG; addr <= LAST_STATUS_REG; addr++) { |
| rc = smb348_read_reg(chip, addr, ®); |
| if (!rc) |
| seq_printf(m, "0x%02x = 0x%02x\n", addr, reg); |
| } |
| |
| return 0; |
| } |
| |
| static int status_debugfs_open(struct inode *inode, struct file *file) |
| { |
| struct smb348_charger *chip = inode->i_private; |
| |
| return single_open(file, show_status_regs, chip); |
| } |
| |
| static const struct file_operations status_debugfs_ops = { |
| .owner = THIS_MODULE, |
| .open = status_debugfs_open, |
| .read = seq_read, |
| .llseek = seq_lseek, |
| .release = single_release, |
| }; |
| |
| static int show_irq_count(struct seq_file *m, void *data) |
| { |
| int i, j, total = 0; |
| |
| for (i = 0; i < ARRAY_SIZE(handlers); i++) |
| for (j = 0; j < 4; j++) { |
| seq_printf(m, "%s=%d\t(high=%d low=%d)\n", |
| handlers[i].irq_info[j].name, |
| handlers[i].irq_info[j].high |
| + handlers[i].irq_info[j].low, |
| handlers[i].irq_info[j].high, |
| handlers[i].irq_info[j].low); |
| total += (handlers[i].irq_info[j].high |
| + handlers[i].irq_info[j].low); |
| } |
| |
| seq_printf(m, "\n\tTotal = %d\n", total); |
| |
| return 0; |
| } |
| |
| static int irq_count_debugfs_open(struct inode *inode, struct file *file) |
| { |
| struct smb348_charger *chip = inode->i_private; |
| |
| return single_open(file, show_irq_count, chip); |
| } |
| |
| static const struct file_operations irq_count_debugfs_ops = { |
| .owner = THIS_MODULE, |
| .open = irq_count_debugfs_open, |
| .read = seq_read, |
| .llseek = seq_lseek, |
| .release = single_release, |
| }; |
| |
| static int get_reg(void *data, u64 *val) |
| { |
| struct smb348_charger *chip = data; |
| int rc; |
| u8 temp; |
| |
| rc = smb348_read_reg(chip, chip->peek_poke_address, &temp); |
| if (rc < 0) { |
| dev_err(chip->dev, |
| "Couldn't read reg %x rc = %d\n", |
| chip->peek_poke_address, rc); |
| return -EAGAIN; |
| } |
| *val = temp; |
| return 0; |
| } |
| |
| static int set_reg(void *data, u64 val) |
| { |
| struct smb348_charger *chip = data; |
| int rc; |
| u8 temp; |
| |
| temp = (u8) val; |
| rc = smb348_write_reg(chip, chip->peek_poke_address, temp); |
| if (rc < 0) { |
| dev_err(chip->dev, |
| "Couldn't write 0x%02x to 0x%02x rc= %d\n", |
| chip->peek_poke_address, temp, rc); |
| return -EAGAIN; |
| } |
| return 0; |
| } |
| DEFINE_SIMPLE_ATTRIBUTE(poke_poke_debug_ops, get_reg, set_reg, "0x%02llx\n"); |
| |
| static int force_irq_set(void *data, u64 val) |
| { |
| struct smb348_charger *chip = data; |
| |
| smb348_chg_stat_handler(chip->client->irq, data); |
| return 0; |
| } |
| DEFINE_SIMPLE_ATTRIBUTE(force_irq_ops, NULL, force_irq_set, "0x%02llx\n"); |
| #endif |
| |
| |
| #ifdef DEBUG |
| static void dump_regs(struct smb348_charger *chip) |
| { |
| int rc; |
| u8 reg; |
| u8 addr; |
| |
| for (addr = 0; addr <= LAST_CNFG_REG; addr++) { |
| rc = smb348_read_reg(chip, addr, ®); |
| if (rc) |
| dev_err(chip->dev, "Couldn't read 0x%02x rc = %d\n", |
| addr, rc); |
| else |
| pr_debug("0x%02x = 0x%02x\n", addr, reg); |
| } |
| |
| for (addr = FIRST_STATUS_REG; addr <= LAST_STATUS_REG; addr++) { |
| rc = smb348_read_reg(chip, addr, ®); |
| if (rc) |
| dev_err(chip->dev, "Couldn't read 0x%02x rc = %d\n", |
| addr, rc); |
| else |
| pr_debug("0x%02x = 0x%02x\n", addr, reg); |
| } |
| |
| for (addr = FIRST_CMD_REG; addr <= LAST_CMD_REG; addr++) { |
| rc = smb348_read_reg(chip, addr, ®); |
| if (rc) |
| dev_err(chip->dev, "Couldn't read 0x%02x rc = %d\n", |
| addr, rc); |
| else |
| pr_debug("0x%02x = 0x%02x\n", addr, reg); |
| } |
| } |
| #else |
| static void dump_regs(struct smb348_charger *chip) |
| { |
| } |
| #endif |
| |
| static int smb_parse_batt_id(struct smb348_charger *chip) |
| { |
| int rc = 0, rpull = 0, vref = 0; |
| int64_t denom, batt_id_uv, numerator; |
| struct device_node *node = chip->dev->of_node; |
| struct qpnp_vadc_result result; |
| |
| rc = of_property_read_u32(node, "qcom,batt-id-vref-uv", &vref); |
| if (rc < 0) { |
| dev_err(chip->dev, |
| "Couldn't read batt-id-vref-uv rc=%d\n", rc); |
| return rc; |
| } |
| |
| rc = of_property_read_u32(node, "qcom,batt-id-rpullup-kohm", &rpull); |
| if (rc < 0) { |
| dev_err(chip->dev, |
| "Couldn't read batt-id-rpullup-kohm rc=%d\n", rc); |
| return rc; |
| } |
| |
| /* read battery ID */ |
| rc = qpnp_vadc_read(chip->vadc_dev, LR_MUX2_BAT_ID, &result); |
| if (rc) { |
| dev_err(chip->dev, |
| "Couldn't read batt id channel=%d, rc=%d\n", |
| LR_MUX2_BAT_ID, rc); |
| return rc; |
| } |
| batt_id_uv = result.physical; |
| |
| if (batt_id_uv == 0) { |
| /*vadc not correct or batt id line grounded, report 0 kohms */ |
| dev_warn(chip->dev, "batt_id_uv=0, batt-id grounded\n"); |
| return 0; |
| } |
| |
| numerator = batt_id_uv * rpull * 1000; |
| denom = vref - batt_id_uv; |
| |
| /* batt id connector might be open, return 0 kohms */ |
| if (denom == 0) |
| return 0; |
| |
| chip->connected_rid = div64_s64(numerator, denom); |
| |
| dev_dbg(chip->dev, |
| "batt_id_voltage=%lld numerator=%lld denom=%lld connected_rid=%d\n", |
| batt_id_uv, numerator, denom, chip->connected_rid); |
| |
| return 0; |
| } |
| |
| static int smb_parse_dt(struct smb348_charger *chip) |
| { |
| int rc; |
| enum of_gpio_flags gpio_flags; |
| struct device_node *node = chip->dev->of_node; |
| int batt_present_degree_negative; |
| const char *dc_psy_type; |
| |
| if (!node) { |
| dev_err(chip->dev, "device tree info. missing\n"); |
| return -EINVAL; |
| } |
| |
| chip->charging_disabled = of_property_read_bool(node, |
| "qcom,charger-disabled"); |
| |
| chip->inhibit_disabled = of_property_read_bool(node, |
| "qcom,chg-inhibit-disabled"); |
| chip->chg_autonomous_mode = of_property_read_bool(node, |
| "qcom,chg-autonomous-mode"); |
| |
| chip->disable_apsd = of_property_read_bool(node, "qcom,disable-apsd"); |
| |
| /*chip->using_pmic_therm = of_property_read_bool(node, |
| "qcom,using-pmic-therm");*/ |
| chip->pmic_vbat_sns = of_property_read_bool(node, |
| "qcom,using-vbat-sns"); |
| chip->bms_controlled_charging = of_property_read_bool(node, |
| "qcom,bms-controlled-charging"); |
| |
| rc = of_property_read_string(node, "qcom,bms-psy-name", |
| &chip->bms_psy_name); |
| if (rc) |
| chip->bms_psy_name = NULL; |
| |
| chip->chg_valid_gpio = of_get_named_gpio_flags(node, |
| "qcom,chg-valid-gpio", 0, &gpio_flags); |
| if (!gpio_is_valid(chip->chg_valid_gpio)) |
| dev_dbg(chip->dev, "Invalid chg-valid-gpio"); |
| else |
| chip->chg_valid_act_low = gpio_flags & OF_GPIO_ACTIVE_LOW; |
| |
| chip->chg_end_gpio = of_get_named_gpio(node, |
| "qcom,chg-end-gpio", 0); |
| chip->wpc_stat_gpio = of_get_named_gpio(node, |
| "qcom,wpc-stat-gpio", 0); |
| |
| chip->wpc_enable_gpio = of_get_named_gpio(node, |
| "qcom,wpc-enable-gpio", 0); |
| |
| if (chip->chip_hw_id >= HW_VER_K) { |
| chip->wpc_dock_detect_gpio = of_get_named_gpio(node, |
| "qcom,wpc-dock-detect-gpio", 0); |
| } |
| |
| /* parse the dc power supply configuration */ |
| rc = of_property_read_string(node, "qcom,dc-psy-type", &dc_psy_type); |
| if (rc) { |
| chip->dc_psy_type = -EINVAL; |
| rc = 0; |
| } else { |
| if (strcmp(dc_psy_type, "Mains") == 0) |
| chip->dc_psy_type = POWER_SUPPLY_TYPE_MAINS; |
| else if (strcmp(dc_psy_type, "Wireless") == 0) |
| chip->dc_psy_type = POWER_SUPPLY_TYPE_WIRELESS; |
| else if (strcmp(dc_psy_type, "Wipower") == 0) |
| chip->dc_psy_type = POWER_SUPPLY_TYPE_WIPOWER; |
| } |
| |
| if (chip->dc_psy_type != -EINVAL) { |
| rc = of_property_read_u32(node, "qcom,dc-psy-ma", |
| &chip->dc_psy_ma); |
| if (rc < 0) { |
| dev_err(chip->dev, |
| "no mA current for dc rc = %d\n", rc); |
| return rc; |
| } |
| |
| if (chip->dc_psy_ma < DC_MA_MIN |
| || chip->dc_psy_ma > DC_MA_MAX) { |
| dev_err(chip->dev, "Bad dc mA %d\n", chip->dc_psy_ma); |
| return -EINVAL; |
| } |
| } |
| |
| rc = of_property_read_u32(node, "qcom,fastchg-current-max-ma", |
| &chip->fastchg_current_max_ma); |
| if (rc) |
| chip->fastchg_current_max_ma = SMB348_FAST_CHG_MAX_MA; |
| |
| #ifdef FEATURE_THERMAL_MITIGATION_ALGO |
| rc = of_property_read_u32(node, "qcom,fastchg-current-mitigation-ma", |
| &chip->fastchg_current_mitigation_ma); |
| if (rc) |
| chip->fastchg_current_mitigation_ma = SMB348_FAST_CHG_MIN_MA; |
| #endif /* FEATURE_THERMAL_MITIGATION_ALGO */ |
| |
| chip->iterm_disabled = of_property_read_bool(node, |
| "qcom,iterm-disabled"); |
| |
| rc = of_property_read_u32(node, "qcom,iterm-ma", &chip->iterm_ma); |
| if (rc < 0) |
| chip->iterm_ma = -EINVAL; |
| |
| rc = of_property_read_u32(node, "qcom,float-voltage-mv", |
| &chip->vfloat_mv); |
| if (rc < 0) { |
| chip->vfloat_mv = -EINVAL; |
| pr_err("float-voltage-mv property missing, exit\n"); |
| return -EINVAL; |
| } |
| |
| rc = of_property_read_u32(node, "qcom,recharge-mv", |
| &chip->recharge_mv); |
| if (rc < 0) |
| chip->recharge_mv = -EINVAL; |
| |
| chip->recharge_disabled = of_property_read_bool(node, |
| "qcom,recharge-disabled"); |
| |
| rc = of_property_read_u32(node, "qcom,cold-bat-decidegc", |
| &chip->cold_bat_decidegc); |
| if (rc < 0) |
| chip->cold_bat_decidegc = -EINVAL; |
| |
| rc = of_property_read_u32(node, "qcom,hot-bat-decidegc", |
| &chip->hot_bat_decidegc); |
| if (rc < 0) |
| chip->hot_bat_decidegc = -EINVAL; |
| |
| rc = of_property_read_u32(node, "qcom,warm-bat-decidegc", |
| &chip->warm_bat_decidegc); |
| |
| rc |= of_property_read_u32(node, "qcom,cool-bat-decidegc", |
| &chip->cool_bat_decidegc); |
| |
| if (!rc) { |
| rc = of_property_read_u32(node, "qcom,cool-bat-mv", |
| &chip->cool_bat_mv); |
| |
| rc |= of_property_read_u32(node, "qcom,warm-bat-mv", |
| &chip->warm_bat_mv); |
| |
| rc |= of_property_read_u32(node, "qcom,cool-bat-ma", |
| &chip->cool_bat_ma); |
| |
| rc |= of_property_read_u32(node, "qcom,warm-bat-ma", |
| &chip->warm_bat_ma); |
| if (rc) |
| chip->jeita_supported = false; |
| else |
| chip->jeita_supported = true; |
| } |
| |
| pr_debug("jeita_supported = %d", chip->jeita_supported); |
| |
| rc = of_property_read_u32(node, "qcom,bat-present-decidegc", |
| &batt_present_degree_negative); |
| if (rc < 0) |
| chip->bat_present_decidegc = -EINVAL; |
| else |
| chip->bat_present_decidegc = -batt_present_degree_negative; |
| |
| if (of_get_property(node, "qcom,vcc-i2c-supply", NULL)) { |
| chip->vcc_i2c = devm_regulator_get(chip->dev, "vcc-i2c"); |
| if (IS_ERR(chip->vcc_i2c)) { |
| dev_err(chip->dev, |
| "%s: Failed to get vcc_i2c regulator\n", |
| __func__); |
| return PTR_ERR(chip->vcc_i2c); |
| } |
| } |
| |
| chip->skip_usb_suspend_for_fake_battery = of_property_read_bool(node, |
| "qcom,skip-usb-suspend-for-fake-battery"); |
| if (!chip->skip_usb_suspend_for_fake_battery) { |
| if (!chip->vadc_dev) { |
| dev_err(chip->dev, |
| "VADC device not present with usb suspend on fake battery\n"); |
| return -EINVAL; |
| } |
| |
| rc = smb_parse_batt_id(chip); |
| if (rc) { |
| dev_err(chip->dev, |
| "failed to read batt-id rc=%d\n", rc); |
| return rc; |
| } |
| } |
| |
| pr_debug("inhibit-disabled = %d, recharge-disabled = %d, recharge-mv = %d,", |
| chip->inhibit_disabled, chip->recharge_disabled, |
| chip->recharge_mv); |
| pr_debug("vfloat-mv = %d, iterm-disabled = %d,", |
| chip->vfloat_mv, chip->iterm_disabled); |
| pr_debug("fastchg-current = %d, charging-disabled = %d,", |
| chip->fastchg_current_max_ma, |
| chip->charging_disabled); |
| pr_debug("disable-apsd = %d bms = %s cold-bat-degree = %d,", |
| chip->disable_apsd, chip->bms_psy_name, |
| chip->cold_bat_decidegc); |
| pr_debug("hot-bat-degree = %d, bat-present-decidegc = %d\n", |
| chip->hot_bat_decidegc, chip->bat_present_decidegc); |
| |
| chip->prop_batt_capacity = SMB_MAX_SOC; // make sure recharging design can only take effect after reading capacity from gauge. |
| return 0; |
| } |
| |
| static int determine_initial_state(struct smb348_charger *chip) |
| { |
| int rc; |
| u8 reg = 0; |
| |
| rc = smb348_read_reg(chip, IRQ_B_REG, ®); |
| if (rc) { |
| dev_err(chip->dev, "Couldn't read IRQ_B rc = %d\n", rc); |
| goto fail_init_status; |
| } |
| |
| rc = smb348_read_reg(chip, IRQ_C_REG, ®); |
| if (rc) { |
| dev_err(chip->dev, "Couldn't read IRQ_C rc = %d\n", rc); |
| goto fail_init_status; |
| } |
| chip->batt_full = (reg & IRQ_C_TERM_BIT) ? true : false; |
| |
| rc = smb348_read_reg(chip, IRQ_A_REG, ®); |
| if (rc < 0) { |
| dev_err(chip->dev, "Couldn't read irq A rc = %d\n", rc); |
| return rc; |
| } |
| |
| /* For current design, can ignore this */ |
| if (reg & IRQ_A_HOT_HARD_BIT) |
| chip->batt_hot = true; |
| if (reg & IRQ_A_COLD_HARD_BIT) |
| chip->batt_cold = true; |
| if (reg & IRQ_A_HOT_SOFT_BIT) |
| chip->batt_warm = true; |
| if (reg & IRQ_A_COLD_SOFT_BIT) |
| chip->batt_cool = true; |
| |
| rc = smb348_read_reg(chip, IRQ_E_REG, ®); |
| if (rc) { |
| dev_err(chip->dev, "Couldn't read IRQ_E rc = %d\n", rc); |
| goto fail_init_status; |
| } |
| |
| if (reg & IRQ_E_INPUT_UV_BIT) { |
| chg_uv(chip, 1); |
| } else { |
| chg_uv(chip, 0); |
| apsd_complete(chip, 1); |
| } |
| |
| return 0; |
| |
| fail_init_status: |
| dev_err(chip->dev, "Couldn't determine initial status\n"); |
| return rc; |
| } |
| |
| #if defined(CONFIG_DEBUG_FS) |
| static void smb348_debugfs_init(struct smb348_charger *chip) |
| { |
| int rc; |
| chip->debug_root = debugfs_create_dir("smb348", NULL); |
| if (!chip->debug_root) |
| dev_err(chip->dev, "Couldn't create debug dir\n"); |
| |
| if (chip->debug_root) { |
| struct dentry *ent; |
| |
| ent = debugfs_create_file("config_registers", S_IFREG | S_IRUGO, |
| chip->debug_root, chip, |
| &cnfg_debugfs_ops); |
| if (!ent || IS_ERR(ent)) { |
| rc = PTR_ERR(ent); |
| dev_err(chip->dev, |
| "Couldn't create cnfg debug file rc = %d\n", |
| rc); |
| } |
| |
| ent = debugfs_create_file("status_registers", S_IFREG | S_IRUGO, |
| chip->debug_root, chip, |
| &status_debugfs_ops); |
| if (!ent || IS_ERR(ent)) { |
| rc = PTR_ERR(ent); |
| dev_err(chip->dev, |
| "Couldn't create status debug file rc = %d\n", |
| rc); |
| } |
| |
| ent = debugfs_create_file("cmd_registers", S_IFREG | S_IRUGO, |
| chip->debug_root, chip, |
| &cmd_debugfs_ops); |
| if (!ent || IS_ERR(ent)) { |
| rc = PTR_ERR(ent); |
| dev_err(chip->dev, |
| "Couldn't create cmd debug file rc = %d\n", |
| rc); |
| } |
| |
| ent = debugfs_create_x32("address", S_IFREG | S_IWUSR | S_IRUGO, |
| chip->debug_root, |
| &(chip->peek_poke_address)); |
| if (!ent || IS_ERR(ent)) { |
| rc = PTR_ERR(ent); |
| dev_err(chip->dev, |
| "Couldn't create address debug file rc = %d\n", |
| rc); |
| } |
| |
| ent = debugfs_create_file("data", S_IFREG | S_IWUSR | S_IRUGO, |
| chip->debug_root, chip, |
| &poke_poke_debug_ops); |
| if (!ent || IS_ERR(ent)) { |
| rc = PTR_ERR(ent); |
| dev_err(chip->dev, |
| "Couldn't create data debug file rc = %d\n", |
| rc); |
| } |
| |
| ent = debugfs_create_file("force_irq", |
| S_IFREG | S_IWUSR | S_IRUGO, |
| chip->debug_root, chip, |
| &force_irq_ops); |
| if (!ent || IS_ERR(ent)) { |
| rc = PTR_ERR(ent); |
| dev_err(chip->dev, |
| "Couldn't create force_irq debug file rc =%d\n", |
| rc); |
| } |
| |
| ent = debugfs_create_file("irq_count", S_IFREG | S_IRUGO, |
| chip->debug_root, chip, |
| &irq_count_debugfs_ops); |
| if (!ent || IS_ERR(ent)) { |
| rc = PTR_ERR(ent); |
| dev_err(chip->dev, |
| "Couldn't create cnfg irq_count file rc = %d\n", |
| rc); |
| } |
| |
| } |
| } |
| #else |
| static void smb348_debugfs_init(struct smb348_charger *chip) |
| { |
| } |
| #endif |
| |
| #define SMB_I2C_VTG_MIN_UV 1800000 |
| #define SMB_I2C_VTG_MAX_UV 1800000 |
| static int smb348_charger_probe(struct i2c_client *client, |
| const struct i2c_device_id *id) |
| { |
| int rc, irq; |
| struct smb348_charger *chip; |
| struct power_supply *usb_psy; |
| u8 reg = 0; |
| #if 0 |
| char *str_loc = NULL; /* Avery added for turn off WPC charger in recovery mode */ |
| char *firstboot_str_loc = NULL; /* Alan added for turn off WPC charger in first boot */ |
| #endif |
| |
| usb_psy = power_supply_get_by_name("usb"); |
| if (!usb_psy) { |
| dev_dbg(&client->dev, "USB psy not found; deferring probe\n"); |
| return -EPROBE_DEFER; |
| } |
| |
| chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); |
| if (!chip) { |
| dev_err(&client->dev, "Couldn't allocate memory\n"); |
| return -ENOMEM; |
| } |
| |
| chip->client = client; |
| chip->dev = &client->dev; |
| chip->usb_psy = usb_psy; |
| chip->fake_battery_soc = -EINVAL; |
| |
| mutex_init(&chip->wpc_chg_complete); |
| mutex_init(&chip->irq_complete); |
| mutex_init(&chip->read_write_lock); |
| mutex_init(&chip->path_suspend_lock); |
| |
| /* Quanta { 3 mins delay for WPC charge */ |
| INIT_DELAYED_WORK(&chip->wpc_resume_chg_wq, smb348_wpc_resume_to_charge); |
| /* } Quanta */ |
| INIT_DELAYED_WORK(&chip->resume_real_soc_wq, smb348_resume_to_real_soc); |
| INIT_WORK(&chip->wpc_dock_wq, smb348_wpc_dock_work); |
| |
| if (of_find_property(chip->dev->of_node, "qcom,chg-vadc", NULL)) { |
| /* early for VADC get, defer probe if needed */ |
| chip->vadc_dev = qpnp_get_vadc(chip->dev, "chg"); |
| if (IS_ERR(chip->vadc_dev)) { |
| rc = PTR_ERR(chip->vadc_dev); |
| if (rc != -EPROBE_DEFER) |
| pr_err("vadc property configured incorrectly\n"); |
| goto destroy_mutex; |
| } |
| } |
| |
| chip->chip_hw_id = smb348_check_hw_id(); |
| pr_err("%s HW_ID: %d\n", __func__, chip->chip_hw_id); |
| |
| rc = smb_parse_dt(chip); |
| if (rc) { |
| dev_err(&client->dev, "Couldn't parse DT nodes rc=%d\n", rc); |
| goto destroy_mutex; |
| } |
| |
| #if 0 /* No need to pull up regulator manually */ |
| /* i2c pull up regulator configuration */ |
| if (chip->vcc_i2c) { |
| if (regulator_count_voltages(chip->vcc_i2c) > 0) { |
| rc = regulator_set_voltage(chip->vcc_i2c, |
| SMB_I2C_VTG_MIN_UV, SMB_I2C_VTG_MAX_UV); |
| if (rc) { |
| dev_err(&client->dev, |
| "regulator vcc_i2c set failed, rc = %d\n", |
| rc); |
| return rc; |
| } |
| } |
| |
| rc = regulator_enable(chip->vcc_i2c); |
| if (rc) { |
| dev_err(&client->dev, |
| "Regulator vcc_i2c enable failed rc = %d\n", |
| rc); |
| goto err_set_vtg_i2c; |
| } |
| } |
| #endif |
| |
| /* probe the device to check if its actually connected */ |
| rc = smb348_read_reg(chip, CHG_OTH_CURRENT_CTRL_REG, ®); |
| if (rc) { |
| pr_err("Failed to detect SMB348, device absent, rc = %d\n", rc); |
| goto destroy_mutex; |
| } |
| |
| /* using adc_tm for implementing pmic therm */ |
| if (chip->using_pmic_therm) { |
| chip->adc_tm_dev = qpnp_get_adc_tm(chip->dev, "chg"); |
| if (IS_ERR(chip->adc_tm_dev)) { |
| rc = PTR_ERR(chip->adc_tm_dev); |
| if (rc != -EPROBE_DEFER) |
| pr_err("adc_tm property missing\n"); |
| goto destroy_mutex; |
| } |
| } |
| |
| i2c_set_clientdata(client, chip); |
| |
| if (chip->dc_psy_type != -EINVAL) { |
| chip->dc_psy.name = "dc"; |
| chip->dc_psy.type = chip->dc_psy_type; |
| chip->dc_psy.get_property = smb348_dc_get_property; |
| chip->dc_psy.properties = smb348_dc_properties; |
| chip->dc_psy.num_properties = ARRAY_SIZE(smb348_dc_properties); |
| |
| rc = power_supply_register(chip->dev, &chip->dc_psy); |
| if (rc < 0) { |
| dev_err(&client->dev, |
| "Unable to register dc_psy rc = %d\n", rc); |
| goto destroy_mutex; |
| } |
| } |
| |
| chip->batt_psy.name = "battery"; |
| chip->batt_psy.type = POWER_SUPPLY_TYPE_BATTERY; |
| chip->batt_psy.get_property = smb348_battery_get_property; |
| chip->batt_psy.set_property = smb348_battery_set_property; |
| chip->batt_psy.property_is_writeable = |
| smb348_batt_property_is_writeable; |
| chip->batt_psy.properties = smb348_battery_properties; |
| chip->batt_psy.num_properties = ARRAY_SIZE(smb348_battery_properties); |
| chip->batt_psy.external_power_changed = smb348_external_power_changed; |
| chip->batt_psy.supplied_to = pm_batt_supplied_to; |
| chip->batt_psy.num_supplicants = ARRAY_SIZE(pm_batt_supplied_to); |
| |
| chip->resume_completed = true; |
| |
| rc = power_supply_register(chip->dev, &chip->batt_psy); |
| if (rc < 0) { |
| dev_err(&client->dev, "Couldn't register batt psy rc = %d\n", |
| rc); |
| goto fail_dc_psy_unregister; |
| } |
| |
| dump_regs(chip); |
| |
| chip->dock_sdev.name = "dock"; |
| |
| rc = switch_dev_register(&chip->dock_sdev); |
| if (rc < 0) { |
| dev_err(&client->dev, "Couldn't register dock switch rc = %d\n", |
| rc); |
| goto fail_psy_unregister; |
| } |
| |
| #if 0 /* No OTG regulater*/ |
| rc = smb348_regulator_init(chip); |
| if (rc) { |
| dev_err(&client->dev, |
| "Couldn't initialize smb348 ragulator rc=%d\n", rc); |
| goto fail_regulator_register; |
| } |
| #endif |
| rc = smb348_hw_init(chip); |
| if (rc) { |
| dev_err(&client->dev, |
| "Couldn't intialize hardware rc=%d\n", rc); |
| goto fail_switch_dev_unregister; |
| } |
| |
| if (gpio_is_valid(chip->chg_end_gpio)) { |
| rc = gpio_request(chip->chg_end_gpio, "smb348_chg_end"); |
| if (rc) { |
| dev_err(&client->dev, |
| "Invalid CHG_END(%d) gpio_request, rc = %d\n", |
| chip->chg_end_gpio, rc); |
| goto fail_switch_dev_unregister; |
| } |
| |
| rc = gpio_direction_output(chip->chg_end_gpio, 0); |
| if (rc) { |
| dev_err(&client->dev, |
| "Invalid CHG_END(%d) gpio_direction_output, rc = %d\n", |
| chip->chg_end_gpio, rc); |
| goto fail_chg_end_gpio; |
| } |
| } |
| |
| if (gpio_is_valid(chip->wpc_stat_gpio)) { |
| rc = request_threaded_irq(gpio_to_irq(chip->wpc_stat_gpio), smb348_wpc_state_handler, NULL, |
| IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, "smb348_wpc_stat", chip); |
| if (rc) { |
| dev_err(&client->dev, |
| "Invalid WPC_STAT(%d) request irq failed, rc = %d\n", |
| chip->wpc_stat_gpio, rc); |
| goto fail_wpc_stat_gpio; |
| } |
| chip->wpc_state = !gpio_get_value(chip->wpc_stat_gpio); // state pin: low, power transfer is established. |
| dev_err(&client->dev, "WPC status=%d\n", chip->wpc_state); |
| } |
| |
| if (chip->chip_hw_id >= HW_VER_K) { |
| if (gpio_is_valid(chip->wpc_dock_detect_gpio)) { |
| rc = request_threaded_irq(gpio_to_irq(chip->wpc_dock_detect_gpio), smb348_wpc_dock_handler, NULL, |
| IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, "smb348_wpc_dock_detect", chip); |
| if (rc) { |
| dev_err(&client->dev, |
| "Invalid WPC_DETECT(%d) request irq failed, rc = %d\n", |
| chip->wpc_dock_detect_gpio, rc); |
| goto fail_wpc_dock_detect_gpio; |
| } |
| pr_err("detect_pin=%d, stat_pin=%d\n", !gpio_get_value(chip->wpc_dock_detect_gpio), !gpio_get_value(chip->wpc_stat_gpio)); |
| chip->wpc_dock_present = !gpio_get_value(chip->wpc_dock_detect_gpio); |
| switch_set_state(&chip->dock_sdev, chip->wpc_dock_present); |
| power_supply_changed(&chip->dc_psy); |
| pr_err("Dock state is %s\n", chip->dock_sdev.state?"PRESENT":"REMOVED"); |
| } |
| } |
| |
| if (gpio_is_valid(chip->wpc_enable_gpio)) { |
| rc = gpio_request(chip->wpc_enable_gpio, "smb348_wpc_enable"); |
| if (rc) { |
| dev_err(&client->dev, |
| "Invalid WPC_ENABLE(%d) gpio_request, rc = %d\n", |
| chip->wpc_enable_gpio, rc); |
| goto fail_wpc_enable_gpio; |
| } |
| |
| rc = gpio_direction_output(chip->wpc_enable_gpio, 0); |
| if (rc) { |
| dev_err(&client->dev, |
| "Invalid WPC_ENABLE(%d) gpio_direction_output, rc = %d\n", |
| chip->wpc_enable_gpio, rc); |
| goto fail_wpc_enable_gpio; |
| } |
| } |
| |
| rc = determine_initial_state(chip); |
| if (rc) { |
| dev_err(&client->dev, |
| "Couldn't determine initial state rc=%d\n", rc); |
| goto fail_wpc_enable_gpio; |
| } |
| |
| /* We will not use it by default */ |
| if (gpio_is_valid(chip->chg_valid_gpio)) { |
| rc = gpio_request(chip->chg_valid_gpio, "smb348_chg_valid"); |
| if (rc) { |
| dev_err(&client->dev, |
| "gpio_request for %d failed rc=%d\n", |
| chip->chg_valid_gpio, rc); |
| goto fail_chg_valid_irq; |
| } |
| irq = gpio_to_irq(chip->chg_valid_gpio); |
| if (irq < 0) { |
| dev_err(&client->dev, |
| "Invalid chg_valid irq = %d\n", irq); |
| goto fail_chg_valid_irq; |
| } |
| rc = devm_request_threaded_irq(&client->dev, irq, |
| NULL, smb348_chg_valid_handler, |
| IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, |
| "smb348_chg_valid_irq", chip); |
| if (rc) { |
| dev_err(&client->dev, |
| "Failed request_irq irq=%d, gpio=%d rc=%d\n", |
| irq, chip->chg_valid_gpio, rc); |
| goto fail_chg_valid_irq; |
| } |
| smb348_chg_valid_handler(irq, chip); |
| enable_irq_wake(irq); |
| } |
| /* Register IRQ */ |
| if (client->irq) { |
| rc = devm_request_threaded_irq(&client->dev, client->irq, NULL, |
| smb348_chg_stat_handler, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, |
| "smb348_chg_stat_irq", chip); |
| if (rc < 0) { |
| dev_err(&client->dev, |
| "Request IRQ(%d) failed, rc = %d\n", |
| client->irq, rc); |
| goto fail_chg_valid_irq; |
| } |
| enable_irq_wake(client->irq); |
| } |
| |
| if (chip->using_pmic_therm) { |
| if (!chip->jeita_supported) { |
| /* add hot/cold temperature monitor */ |
| chip->adc_param.low_temp = chip->cold_bat_decidegc; |
| chip->adc_param.high_temp = chip->hot_bat_decidegc; |
| } else { |
| chip->adc_param.low_temp = chip->cool_bat_decidegc; |
| chip->adc_param.high_temp = chip->warm_bat_decidegc; |
| } |
| chip->adc_param.timer_interval = ADC_MEAS2_INTERVAL_1S; |
| chip->adc_param.state_request = ADC_TM_HIGH_LOW_THR_ENABLE; |
| chip->adc_param.btm_ctx = chip; |
| chip->adc_param.threshold_notification = |
| smb_chg_adc_notification; |
| chip->adc_param.channel = LR_MUX1_BATT_THERM; |
| |
| /* update battery missing info in tm_channel_measure*/ |
| rc = qpnp_adc_tm_channel_measure(chip->adc_tm_dev, |
| &chip->adc_param); |
| if (rc) |
| pr_err("requesting ADC error %d\n", rc); |
| } |
| |
| /* Temperature Monitor addr: 0Bh, bit7:bit6 */ |
| if (chip->cold_bat_decidegc == 100) |
| rc = smb348_masked_write(chip, TEMP_REG, 0xC0, 0x00); |
| else if (chip->cold_bat_decidegc == 50) |
| rc = smb348_masked_write(chip, TEMP_REG, 0xC0, 0x40); |
| else if (chip->cold_bat_decidegc == 0) |
| rc = smb348_masked_write(chip, TEMP_REG, 0xC0, 0x80); |
| else if (chip->cold_bat_decidegc == -50) |
| rc = smb348_masked_write(chip, TEMP_REG, 0xC0, 0xC0); |
| smb348_debugfs_init(chip); |
| |
| dump_regs(chip); |
| |
| #if 0 |
| /* Avery added for turn off WPC charger in recovery mode { */ |
| str_loc = strstr(saved_command_line, "gpt"); |
| if (str_loc != NULL) { |
| pr_err("Quanta: device boots into recovery mode, disable wpc charging"); |
| RECOVERY_WPC_DISABLE_FLAG = 1; |
| gpio_set_value(chip->wpc_enable_gpio, 1); |
| } else { |
| pr_err("Quanta: device boots into normal mode"); |
| |
| firstboot_str_loc = strstr(saved_command_line, "firstboot"); |
| if (firstboot_str_loc != NULL) { /* TODO: neeed to check - first bootup after GOTA flash */ |
| /* Quanta { 3 mins delay for WPC charge */ |
| WPC_CHG_RESUME_ALLOW_GOTA = 0; |
| gpio_set_value(chip->wpc_enable_gpio, 1); |
| schedule_delayed_work(&chip->wpc_resume_chg_wq, msecs_to_jiffies(WPC_CHG_RESUME_CHG_MS)); |
| pr_err("[DDDBG] set timer for 3 mins\n"); |
| /* } Quanta */ |
| } |
| } |
| /* Avery } */ |
| #endif |
| |
| dev_info(chip->dev, "SMB348 successfully probed. charger=%d, batt=%d\n", |
| chip->chg_present, smb348_get_prop_batt_present(chip)); |
| |
| pr_err("wpc detect value 2: %d\n", gpio_get_value(chip->wpc_dock_detect_gpio)); |
| return 0; |
| |
| fail_chg_valid_irq: |
| if (gpio_is_valid(chip->chg_valid_gpio)) |
| gpio_free(chip->chg_valid_gpio); |
| fail_wpc_enable_gpio: |
| if (gpio_is_valid(chip->wpc_enable_gpio)) |
| gpio_free(chip->wpc_enable_gpio); |
| fail_wpc_dock_detect_gpio: |
| if (gpio_is_valid(chip->wpc_dock_detect_gpio)) |
| gpio_free(chip->wpc_dock_detect_gpio); |
| fail_wpc_stat_gpio: |
| if (gpio_is_valid(chip->wpc_stat_gpio)) |
| gpio_free(chip->wpc_stat_gpio); |
| |
| fail_chg_end_gpio: |
| if (gpio_is_valid(chip->chg_end_gpio)) |
| gpio_free(chip->chg_end_gpio); |
| fail_switch_dev_unregister: |
| switch_dev_unregister(&chip->dock_sdev); |
| fail_psy_unregister: |
| power_supply_unregister(&chip->batt_psy); |
| fail_dc_psy_unregister: |
| if (chip->dc_psy_type != -EINVAL) |
| power_supply_unregister(&chip->dc_psy); |
| destroy_mutex: |
| mutex_destroy(&chip->wpc_chg_complete); |
| mutex_destroy(&chip->irq_complete); |
| mutex_destroy(&chip->read_write_lock); |
| mutex_destroy(&chip->path_suspend_lock); |
| |
| return rc; |
| } |
| |
| static int smb348_charger_remove(struct i2c_client *client) |
| { |
| struct smb348_charger *chip = i2c_get_clientdata(client); |
| |
| power_supply_unregister(&chip->batt_psy); |
| switch_dev_unregister(&chip->dock_sdev); |
| if (gpio_is_valid(chip->wpc_enable_gpio)) |
| gpio_free(chip->wpc_enable_gpio); |
| |
| if (gpio_is_valid(chip->wpc_stat_gpio)) |
| gpio_free(chip->wpc_stat_gpio); |
| |
| if (gpio_is_valid(chip->chg_end_gpio)) |
| gpio_free(chip->chg_end_gpio); |
| |
| if (gpio_is_valid(chip->chg_valid_gpio)) |
| gpio_free(chip->chg_valid_gpio); |
| |
| if (chip->chip_hw_id >= HW_VER_K) { |
| if (gpio_is_valid(chip->wpc_dock_detect_gpio)) |
| gpio_free(chip->wpc_dock_detect_gpio); |
| } |
| |
| if (chip->vcc_i2c) |
| regulator_disable(chip->vcc_i2c); |
| |
| mutex_destroy(&chip->wpc_chg_complete); |
| mutex_destroy(&chip->irq_complete); |
| mutex_destroy(&chip->read_write_lock); |
| mutex_destroy(&chip->path_suspend_lock); |
| debugfs_remove_recursive(chip->debug_root); |
| return 0; |
| } |
| |
| static int smb348_suspend(struct device *dev) |
| { |
| struct i2c_client *client = to_i2c_client(dev); |
| struct smb348_charger *chip = i2c_get_clientdata(client); |
| int rc; |
| int i; |
| |
| for (i = 0; i < 2; i++) { |
| rc = smb348_read_reg(chip, FAULT_INT_REG + i, |
| &chip->irq_cfg_mask[i]); |
| if (rc) |
| dev_err(chip->dev, |
| "Couldn't save irq cfg regs rc = %d\n", rc); |
| } |
| |
| /* enable wake up IRQs */ |
| rc = smb348_write_reg(chip, FAULT_INT_REG, |
| FAULT_INT_HOT_COLD_HARD_BIT | FAULT_INT_INPUT_UV_BIT); |
| if (rc < 0) |
| dev_err(chip->dev, "Couldn't set fault_irq_cfg rc = %d\n", rc); |
| |
| rc = smb348_write_reg(chip, STATUS_INT_REG, |
| STATUS_INT_LOW_BATT_BIT | STATUS_INT_MISSING_BATT_BIT | |
| STATUS_INT_CHGING_BIT | STATUS_INT_INOK_BIT | |
| STATUS_INT_OTG_DETECT_BIT | STATUS_INT_CHG_INHI_BIT); |
| if (rc < 0) |
| dev_err(chip->dev, |
| "Couldn't set status_irq_cfg rc = %d\n", rc); |
| |
| mutex_lock(&chip->irq_complete); |
| if (chip->vcc_i2c) { |
| rc = regulator_disable(chip->vcc_i2c); |
| if (rc) { |
| dev_err(chip->dev, |
| "Regulator vcc_i2c disable failed rc=%d\n", rc); |
| mutex_unlock(&chip->irq_complete); |
| return rc; |
| } |
| } |
| |
| chip->resume_completed = false; |
| mutex_unlock(&chip->irq_complete); |
| return 0; |
| } |
| |
| static int smb348_suspend_noirq(struct device *dev) |
| { |
| struct i2c_client *client = to_i2c_client(dev); |
| struct smb348_charger *chip = i2c_get_clientdata(client); |
| |
| if (chip->irq_waiting) { |
| pr_err_ratelimited("Aborting suspend, an interrupt was detected while suspending\n"); |
| return -EBUSY; |
| } |
| return 0; |
| } |
| |
| static int smb348_resume(struct device *dev) |
| { |
| struct i2c_client *client = to_i2c_client(dev); |
| struct smb348_charger *chip = i2c_get_clientdata(client); |
| int rc; |
| int i; |
| |
| if (chip->vcc_i2c) { |
| rc = regulator_enable(chip->vcc_i2c); |
| if (rc) { |
| dev_err(chip->dev, |
| "Regulator vcc_i2c enable failed rc=%d\n", rc); |
| return rc; |
| } |
| } |
| /* Restore IRQ config */ |
| for (i = 0; i < 2; i++) { |
| rc = smb348_write_reg(chip, FAULT_INT_REG + i, |
| chip->irq_cfg_mask[i]); |
| if (rc) |
| dev_err(chip->dev, |
| "Couldn't restore irq cfg regs rc=%d\n", rc); |
| } |
| |
| mutex_lock(&chip->irq_complete); |
| chip->resume_completed = true; |
| mutex_unlock(&chip->irq_complete); |
| if (chip->irq_waiting) { |
| smb348_chg_stat_handler(client->irq, chip); |
| enable_irq(client->irq); |
| } |
| return 0; |
| } |
| |
| static const struct dev_pm_ops smb348_pm_ops = { |
| .suspend = smb348_suspend, |
| .suspend_noirq = smb348_suspend_noirq, |
| .resume = smb348_resume, |
| }; |
| |
| static struct of_device_id smb348_match_table[] = { |
| { .compatible = "qcom,smb348-charger",}, |
| { }, |
| }; |
| |
| static const struct i2c_device_id smb348_charger_id[] = { |
| {"smb348-charger", 0}, |
| {}, |
| }; |
| MODULE_DEVICE_TABLE(i2c, smb348_charger_id); |
| |
| static struct i2c_driver smb348_charger_driver = { |
| .driver = { |
| .name = "smb348-charger", |
| .owner = THIS_MODULE, |
| .of_match_table = smb348_match_table, |
| .pm = &smb348_pm_ops, |
| }, |
| .probe = smb348_charger_probe, |
| .remove = smb348_charger_remove, |
| .id_table = smb348_charger_id, |
| }; |
| |
| module_i2c_driver(smb348_charger_driver); |
| |
| MODULE_DESCRIPTION("SMB348 Charger"); |
| MODULE_LICENSE("GPL v2"); |
| MODULE_ALIAS("i2c:smb348-charger"); |