| /* Copyright (c) 2015-2016 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) "SMB:%s: " fmt, __func__ |
| |
| #include <linux/i2c.h> |
| #include <linux/delay.h> |
| #include <linux/errno.h> |
| #include <linux/module.h> |
| #include <linux/interrupt.h> |
| #include <linux/power_supply.h> |
| #include <linux/of.h> |
| #include <linux/bitops.h> |
| #include <linux/mutex.h> |
| #include <linux/debugfs.h> |
| #include <linux/pm_wakeup.h> |
| #include <linux/spinlock.h> |
| #include <linux/gpio.h> |
| #include <linux/of_gpio.h> |
| #include <linux/alarmtimer.h> |
| #include <net/sock.h> |
| #include <net/netlink.h> |
| #include <linux/skbuff.h> |
| |
| #define NETLINK_LLOBSOCK 27 |
| static struct sock *netlink_sock; |
| |
| static void udp_broadcast(int gid,void *payload, unsigned int size) |
| { |
| struct sk_buff *skb; |
| struct nlmsghdr *nlh; |
| |
| int len = NLMSG_SPACE(size); |
| void *data; |
| int ret; |
| |
| skb = alloc_skb(len, GFP_KERNEL); |
| pr_debug(" send data from kernelspace!!! size=%d, length=%d \n", size, len); |
| if (!skb){ |
| pr_err(" alloc_skb() failed!!!!\n"); |
| return; |
| } |
| nlh= nlmsg_put(skb, 0, 0, 0, size, 0); |
| nlh->nlmsg_flags = 0; |
| data=NLMSG_DATA(nlh); |
| memcpy(data, payload, size); |
| NETLINK_CB(skb).dst_group = gid; /* unicast */ |
| |
| ret=netlink_broadcast(netlink_sock, skb, 0, gid, GFP_KERNEL); |
| |
| if (ret <0){ |
| pr_err(" send failed\n"); |
| return; |
| } |
| return; |
| } |
| |
| static void udp_receive(struct sk_buff *skb) |
| { |
| pr_debug(" receieve data from userspace!!! \n"); |
| udp_broadcast(1,"LLOB SOCK READY\n", 15); |
| return; |
| } |
| |
| static void udp_init(void) |
| { |
| struct netlink_kernel_cfg cfg = { |
| .input = udp_receive, |
| }; |
| netlink_sock = netlink_kernel_create(&init_net, NETLINK_LLOBSOCK, &cfg); |
| pr_debug(" netlink sock init successfully\n"); |
| } |
| |
| static void udp_exit(void) |
| { |
| sock_release(netlink_sock->sk_socket); |
| pr_debug(" netlink sock remove successfully\n"); |
| } |
| |
| static void udp_sendmsg_charging(int power_status) |
| { |
| static bool last_status = false; |
| if(power_status != 0 && last_status==false){ |
| udp_broadcast(1,"Charging\0", 9); |
| last_status=true; |
| pr_debug(" Discharging to Charging\n"); |
| } |
| else if(power_status == 0 && last_status==true){ |
| udp_broadcast(1,"Discharging\0", 12); |
| last_status=false; |
| pr_debug(" Charging to Discharging\n"); |
| } |
| } |
| |
| static void udp_sendmsg_tempature(int value) |
| { |
| const int thresholdTempRaising=2; |
| const int thresholdTempFalling=2; |
| static int compareTemp=0; |
| static int tempStatus=0; |
| int currentTemp=value/10; |
| |
| if(compareTemp==0){ |
| compareTemp=currentTemp; |
| } |
| else if( currentTemp!=compareTemp ){ |
| if( (currentTemp-compareTemp)>=thresholdTempRaising ){ |
| //Raising |
| if( (tempStatus&0x03)==0 ){ |
| tempStatus=1; |
| } |
| compareTemp=currentTemp; |
| } |
| else if( (compareTemp-currentTemp)>=thresholdTempFalling ){ |
| //falling |
| if( (tempStatus&0x0C)==0 ){ |
| tempStatus=4; |
| } |
| compareTemp=currentTemp; |
| } |
| else if( ((currentTemp>compareTemp)&&(tempStatus&0x03)) || |
| ((currentTemp<compareTemp)&&(tempStatus&0x0C)) ){ |
| compareTemp=currentTemp; |
| } |
| } |
| |
| if((tempStatus&0x0F)==1){ |
| udp_broadcast(1,"TempRaising\0", 12); |
| tempStatus=tempStatus|0x02; |
| pr_debug("Tempature raising\n"); |
| } |
| else if((tempStatus&0x0F)==4){ |
| udp_broadcast(1,"TempFalling\0", 12); |
| tempStatus=tempStatus|0x08; |
| pr_debug("Tempature falling\n"); |
| } |
| } |
| |
| struct smb23x_wakeup_source { |
| struct wakeup_source source; |
| unsigned long enabled_bitmap; |
| spinlock_t ws_lock; |
| }; |
| |
| enum wakeup_src { |
| WAKEUP_SRC_IRQ_POLLING = 0, |
| WAKEUP_SRC_MAX, |
| }; |
| #define WAKEUP_SRC_MASK (~(~0 << WAKEUP_SRC_MAX)) |
| |
| struct smb23x_chip { |
| struct i2c_client *client; |
| struct device *dev; |
| |
| /* charger configurations -- via devicetree */ |
| bool cfg_charging_disabled; |
| bool cfg_recharge_disabled; |
| bool cfg_chg_inhibit_disabled; |
| bool cfg_iterm_disabled; |
| bool cfg_aicl_disabled; |
| bool cfg_apsd_disabled; |
| bool cfg_bms_controlled_charging; |
| |
| int cfg_vfloat_mv; |
| int cfg_resume_delta_mv; |
| int cfg_chg_inhibit_delta_mv; |
| int cfg_iterm_ma; |
| int cfg_fastchg_ma; |
| int cfg_cold_bat_decidegc; |
| int cfg_cool_bat_decidegc; |
| int cfg_warm_bat_decidegc; |
| int cfg_hot_bat_decidegc; |
| int cfg_temp_comp_mv; |
| int cfg_temp_comp_ma; |
| int cfg_safety_time; |
| |
| int *cfg_thermal_mitigation; |
| |
| /* status */ |
| bool batt_present; |
| bool batt_full; |
| bool batt_hot; |
| bool batt_cold; |
| bool batt_warm; |
| bool batt_cool; |
| bool usb_present; |
| bool apsd_enabled; |
| |
| int chg_disabled_status; |
| int usb_suspended_status; |
| int usb_psy_ma; |
| |
| /* others */ |
| bool irq_waiting; |
| bool resume_completed; |
| u8 irq_cfg_mask; |
| u32 peek_poke_address; |
| int fake_battery_soc; |
| int therm_lvl_sel; |
| int thermal_levels; |
| u32 workaround_flags; |
| const char *bms_psy_name; |
| struct power_supply *usb_psy; |
| struct power_supply *bms_psy; |
| struct power_supply batt_psy; |
| struct mutex read_write_lock; |
| struct mutex irq_complete; |
| struct mutex chg_disable_lock; |
| struct mutex usb_suspend_lock; |
| struct mutex icl_set_lock; |
| struct dentry *debug_root; |
| struct delayed_work irq_polling_work; |
| struct smb23x_wakeup_source smb23x_ws; |
| |
| /* extend */ |
| int cfg_cool_temp_comp_mv; |
| int index_soft_temp_comp_mv; |
| int last_temp; |
| int cfg_en_active; |
| int sys_voltage; |
| int sys_vthreshold; |
| int max_ac_current_ma; |
| int prechg_current_ma; |
| int charger_plugin; |
| int reg_addr; |
| int reg_print_count; |
| struct workqueue_struct *workqueue; |
| struct timer_list timer_init_register; |
| struct delayed_work delaywork_init_register; |
| struct timer_list timer_print_register; |
| struct delayed_work delaywork_print_register; |
| |
| struct delayed_work delaywork_usb_removal; |
| struct delayed_work boot_check_work; |
| struct alarm wpc_check_alarm; |
| struct work_struct wpc_check_work; |
| |
| int susp_gpio; |
| int eoc_gpio; |
| }; |
| |
| static struct smb23x_chip *g_chip; |
| |
| static int usbin_current_ma_table[] = { |
| 100, |
| 300, |
| 500, |
| 650, |
| 900, |
| 1000, |
| 1500, |
| }; |
| |
| static int fastchg_current_ma_table[] = { |
| 100, |
| 250, |
| 300, |
| 370, |
| 500, |
| 600, |
| 700, |
| 1000, |
| }; |
| |
| static int iterm_ma_table[] = { |
| 20, |
| 30, |
| 50, |
| 75, |
| }; |
| |
| static int recharge_mv_table[] = { |
| 150, |
| 80, |
| }; |
| |
| static int safety_time_min_table[] = { |
| 180, |
| 270, |
| 360, |
| 0, |
| }; |
| |
| static int vfloat_compensation_mv_table[] = { |
| 80, |
| 120, |
| 160, |
| 200, |
| }; |
| |
| static int inhibit_mv_table[] = { |
| 0, |
| 265, |
| 385, |
| 512, |
| }; |
| |
| static int cold_bat_decidegc_table[] = { |
| 100, |
| 50, |
| 0, |
| -50, |
| }; |
| |
| static int cool_bat_decidegc_table[] = { |
| 150, |
| 100, |
| 50, |
| 0, |
| }; |
| |
| static int warm_bat_decidegc_table[] = { |
| 400, |
| 450, |
| 500, |
| 550, |
| }; |
| |
| static int hot_bat_decidegc_table[] = { |
| 500, |
| 550, |
| 600, |
| 650, |
| }; |
| |
| enum BattStatus |
| { |
| STATUS_NORMAL = 0, |
| STATUS_OV = 1, |
| STATUS_OT = 2 |
| }; |
| |
| static int g_BattStatus = STATUS_NORMAL; |
| static int g_BootPhase = 1; |
| static bool g_IsRetailMode = false; |
| |
| |
| #define MIN_FLOAT_MV 3480 |
| #define MAX_FLOAT_MV 4720 |
| #define FLOAT_STEP_MV 40 |
| |
| #define BATT_NO_OVER_VOLT 4240000 |
| #define BATT_OVER_VOLT 4500000 |
| #define BATT_OVER_TEMP 430 |
| #define BATT_OVER_TEMP_RE 470 |
| #define TRIM_PERIOD_NS (1LL * NSEC_PER_SEC) |
| |
| #define REG0_DEFAULT 0x54 |
| |
| #define _SMB_MASK(BITS, POS) \ |
| ((unsigned char)(((1 << (BITS)) - 1) << (POS))) |
| #define SMB_MASK(LEFT_BIT_POS, RIGHT_BIT_POS) \ |
| _SMB_MASK((LEFT_BIT_POS) - (RIGHT_BIT_POS) + 1, \ |
| (RIGHT_BIT_POS)) |
| |
| /* register mapping definitions */ |
| #define CFG_REG_0 0x00 |
| #define USBIN_ICL_MASK SMB_MASK(4, 2) |
| #define USBIN_ICL_OFFSET 2 |
| #define ITERM_MASK SMB_MASK(1, 0) |
| |
| #define CFG_REG_1 0x01 |
| #define PRECHG_CURR_MASK SMB_MASK(7, 6) |
| #define PRECHG_CURR_OFFSET 6 |
| |
| #define CFG_REG_2 0x02 |
| #define RECHARGE_DIS_BIT BIT(7) |
| #define RECHARGE_THRESH_MASK BIT(6) |
| #define RECHARGE_THRESH_OFFSET 6 |
| #define ITERM_DIS_BIT BIT(5) |
| #define FASTCHG_CURR_MASK SMB_MASK(2, 0) |
| |
| #define CFG_REG_3 0x03 |
| #define FASTCHG_CURR_SOFT_COMP SMB_MASK(7, 5) |
| #define FASTCHG_CURR_SOFT_COMP_OFFSET 5 |
| #define FLOAT_VOLTAGE_MASK SMB_MASK(4, 0) |
| |
| #define CFG_REG_4 0x04 |
| #define CHG_EN_ACTIVE_LOW_BIT BIT(5) |
| #define CHG_EN_ACTIVE_OFFSET 5 |
| #define SAFETY_TIMER_MASK SMB_MASK(4, 3) |
| #define SAFETY_TIMER_OFFSET 3 |
| #define SAFETY_TIMER_DISABLE SMB_MASK(4, 3) |
| #define SYS_VOLTAGE_MASK SMB_MASK(1, 0) |
| |
| #define CFG_REG_5 0x05 |
| #define BAT_THERM_DIS_BIT BIT(7) |
| #define HARD_THERM_NOT_SUSPEND BIT(6) |
| #define SOFT_THERM_BEHAVIOR_MASK SMB_MASK(5, 4) |
| #define SOFT_THERM_VFLT_CHG_COMP SMB_MASK(5, 4) |
| #define VFLOAT_COMP_MASK SMB_MASK(3, 2) |
| #define VFLOAT_COMP_OFFSET 2 |
| #define APSD_EN_BIT BIT(1) |
| #define AICL_EN_BIT BIT(0) |
| |
| #define CFG_REG_6 0x06 |
| #define CHG_INHIBIT_THRESH_MASK SMB_MASK(7, 6) |
| #define INHIBIT_THRESH_OFFSET 6 |
| #define SYS_VTHRESHOLD_MASK SMB_MASK(5, 4) |
| #define SYS_VTHRESHOLD_OFFSET 4 |
| #define BMD_ALGO_MASK SMB_MASK(1, 0) |
| #define BMD_ALGO_THERM_IO SMB_MASK(1, 0) |
| |
| #define CFG_REG_7 0x07 |
| #define USB1_5_PIN_CNTRL_BIT BIT(7) |
| #define USB_AC_PIN_CNTRL_BIT BIT(6) |
| #define CHG_EN_PIN_CNTRL_BIT BIT(5) |
| #define SUSPEND_SW_CNTRL_BIT BIT(3) |
| |
| #define CFG_REG_8 0x08 |
| #define HARD_COLD_TEMP_MASK SMB_MASK(7, 6) |
| #define HARD_COLD_TEMP_OFFSET 6 |
| #define HARD_HOT_TEMP_MASK SMB_MASK(5, 4) |
| #define HARD_HOT_TEMP_OFFSET 4 |
| #define SOFT_COLD_TEMP_MASK SMB_MASK(3, 2) |
| #define SOFT_COLD_TEMP_OFFSET 2 |
| #define SOFT_HOT_TEMP_MASK SMB_MASK(1, 0) |
| #define SOFT_HOT_TEMP_OFFSET 0 |
| |
| #define IRQ_CFG_REG_9 0x09 |
| #define SAFETY_TIMER_IRQ_EN_BIT BIT(7) |
| #define BATT_OV_IRQ_EN_BIT BIT(6) |
| #define BATT_MISSING_IRQ_EN_BIT BIT(5) |
| #define ITERM_IRQ_EN_BIT BIT(4) |
| #define HARD_TEMP_IRQ_EN_BIT BIT(3) |
| #define SOFT_TEMP_IRQ_EN_BIT BIT(2) |
| #define AICL_DONE_IRQ_EN_BIT BIT(1) |
| #define INOK_IRQ_EN_BIT BIT(0) |
| |
| #define I2C_COMM_CFG_REG 0x0a |
| #define VOLATILE_WRITE_EN_BIT BIT(0) |
| |
| #define NV_CFG_REG 0x14 |
| #define UNPLUG_RELOAD_DIS_BIT BIT(2) |
| |
| #define CMD_REG_0 0x30 |
| #define VOLATILE_WRITE_ALLOW BIT(7) |
| #define RESET_BIT BIT(6) |
| #define USB_SUSPEND_BIT BIT(2) |
| #define CHARGE_EN_BIT BIT(1) |
| #define STATE_PIN_OUT_DIS_BIT BIT(0) |
| |
| #define CMD_REG_1 0x31 |
| #define USB500_MODE_BIT BIT(1) |
| #define USBAC_MODE_BIT BIT(0) |
| |
| #define IRQ_A_STATUS_REG 0x38 |
| #define HOT_HARD_BIT BIT(6) |
| #define COLD_HARD_BIT BIT(4) |
| #define HOT_SOFT_BIT BIT(2) |
| #define COLD_SOFT_BIT BIT(0) |
| |
| #define IRQ_B_STATUS_REG 0x39 |
| #define BATT_OV_BIT BIT(6) |
| #define BATT_ABSENT_BIT BIT(4) |
| #define BATT_UV_BIT BIT(2) |
| #define BATT_P2F_BIT BIT(0) |
| |
| #define IRQ_C_STATUS_REG 0x3A |
| #define CHG_ERROR_BIT BIT(6) |
| #define RECHG_THRESH_BIT BIT(4) |
| #define TAPER_CHG_BIT BIT(2) |
| #define ITERM_BIT BIT(0) |
| |
| #define IRQ_D_STATUS_REG 0x3B |
| #define APSD_DONE_BIT BIT(6) |
| #define AICL_DONE_BIT BIT(4) |
| #define SAFETYTIMER_EXPIRED_BIT BIT(2) |
| |
| #define CHG_STATUS_A_REG 0x3C |
| #define USBIN_OV_BIT BIT(6) |
| #define USBIN_UV_BIT BIT(4) |
| #define POWER_OK_BIT BIT(2) |
| #define DIE_TEMP_LIMIT_BIT BIT(0) |
| |
| #define CHG_STATUS_B_REG 0x3D |
| #define HOLD_OFF_BIT BIT(3) |
| #define CHARGE_TYPE_MASK SMB_MASK(2, 1) |
| #define CHARGE_TYPE_OFFSET 1 |
| #define NO_CHARGE_VAL 0x00 |
| #define PRE_CHARGE_VAL BIT(1) |
| #define FAST_CHARGE_VAL BIT(2) |
| #define TAPER_CHARGE_VAL SMB_MASK(2, 1) |
| #define CHARGE_EN_STS_BIT BIT(0) |
| |
| #define CHG_STATUS_C_REG 0x3E |
| #define APSD_STATUS_BIT BIT(4) |
| #define APSD_RESULT_MASK SMB_MASK(3, 0) |
| #define CDP_TYPE_VAL BIT(3) |
| #define DCP_TYPE_VAL BIT(2) |
| #define SDP_TYPE_VAL BIT(0) |
| #define OTHERS_TYPE_VAL BIT(1) |
| |
| #define AICL_STATUS_REG 0x3F |
| #define AICL_DONE_STS_BIT BIT(6) |
| #define AICL_RESULT_MASK SMB_MASK(5, 0) |
| #define AICL_75_MA BIT(3) |
| #define AICL_100_MA BIT(4) |
| #define AICL_300_MA BIT(0) |
| #define AICL_500_MA BIT(1) |
| #define AICL_650_MA SMB_MASK(1, 0) |
| #define AICL_900_MA BIT(2) |
| #define AICL_1000_MA (BIT(2) | BIT(0)) |
| #define AICL_1500_MA (BIT(2) | BIT(1)) |
| #define USB5_LIMIT BIT(4) |
| #define USB1_LIMIT BIT(5) |
| |
| struct smb_irq_info { |
| const char *name; |
| int (*smb_irq)(struct smb23x_chip *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]; |
| }; |
| |
| enum { |
| USER = BIT(0), |
| CURRENT = BIT(1), |
| BMS = BIT(2), |
| THERMAL = BIT(3), |
| }; |
| |
| enum { |
| WRKRND_IRQ_POLLING = BIT(0), |
| WRKRND_USB_SUSPEND = BIT(1), |
| }; |
| |
| enum { |
| NORMAL = 0, |
| LOW, |
| HIGH |
| }; |
| |
| #ifdef QTI_SMB231 |
| static irqreturn_t smb23x_stat_handler(int irq, void *dev_id); |
| #else |
| static int smb23x_get_prop_batt_capacity(struct smb23x_chip *chip); |
| static int smb23x_vfloat_compensation(struct smb23x_chip *chip, int temp_comp_mv); |
| static int check_charger_thermal_state(struct smb23x_chip *chip, int batt_temp); |
| static int smb23x_get_prop_batt_voltage(struct smb23x_chip *chip); |
| static int smb23x_get_prop_batt_temp(struct smb23x_chip *chip); |
| #endif |
| |
| static int smb23x_check_gpio(struct smb23x_chip *chip); |
| static void smb23x_check_batt_ov(struct smb23x_chip *chip); |
| |
| |
| static int smb23x_check_gpio(struct smb23x_chip *chip) |
| { |
| int ret = 0; |
| int val = 0; |
| |
| if (gpio_is_valid(chip->susp_gpio)) |
| { |
| ret = gpio_request(chip->susp_gpio, "SMB_SUSP_PIN"); |
| if(ret < 0) |
| { |
| pr_err("[smb23x] gpio req failed for susp (%d)\n", ret); |
| } |
| else |
| { |
| val = gpio_get_value(chip->susp_gpio); |
| pr_info("[smb23x] SMB_SUSP_PIN = %d\n", val); |
| } |
| } |
| |
| if (gpio_is_valid(chip->eoc_gpio)) |
| { |
| ret = gpio_request(chip->eoc_gpio, "WPC_EOC_PIN"); |
| if(ret < 0) |
| { |
| pr_err("[smb23x] gpio req failed for eoc (%d)\n", ret); |
| } |
| else |
| { |
| val = gpio_get_value(chip->eoc_gpio); |
| pr_info("[smb23x] WPC_EOC_PIN = %d\n", val); |
| } |
| } |
| |
| return 0; |
| } |
| |
| static void smb23x_check_batt_ov(struct smb23x_chip *chip) |
| { |
| int battery_voltage = 0; |
| ktime_t kt = {0}; |
| |
| battery_voltage = smb23x_get_prop_batt_voltage(chip); |
| pr_debug("[smb23x] current battery voltage = %d, status = %d\n", battery_voltage, g_BattStatus); |
| |
| |
| if(g_BattStatus != STATUS_NORMAL) |
| { |
| return; |
| } |
| |
| if(battery_voltage >= BATT_OVER_VOLT) |
| { |
| g_BattStatus = STATUS_OV; |
| |
| pr_info("[smb23x] OV, attempt to turn off the WPC\n"); |
| |
| if(gpio_is_valid(chip->eoc_gpio)) |
| { |
| gpio_direction_output(chip->eoc_gpio, 1); |
| pr_info("[smb23x] eoc pin (%d)\n", gpio_get_value(chip->eoc_gpio)); |
| } |
| |
| |
| kt = ns_to_ktime(TRIM_PERIOD_NS * 60 * 1); |
| alarm_start_relative(&chip->wpc_check_alarm, kt); |
| } |
| |
| return; |
| } |
| |
| static void smb23x_check_batt_ot(struct smb23x_chip *chip) |
| { |
| int battery_ot = BATT_OVER_TEMP; |
| int battery_temp = 0; |
| ktime_t kt = {0}; |
| |
| battery_temp = smb23x_get_prop_batt_temp(chip); |
| |
| pr_debug("[smb23x] current battery temperature = %d, status = %d\n", battery_temp, g_BattStatus); |
| |
| if(g_BootPhase) |
| { |
| return; |
| } |
| |
| if(g_BattStatus != STATUS_NORMAL) |
| { |
| return; |
| } |
| |
| if(g_IsRetailMode) |
| { |
| battery_ot = BATT_OVER_TEMP_RE; |
| } |
| |
| if(battery_temp >= battery_ot) |
| { |
| g_BattStatus = STATUS_OT; |
| |
| pr_info("[smb23x] OT(>=%d), attempt to turn off the WPC\n", battery_ot); |
| |
| if(gpio_is_valid(chip->eoc_gpio)) |
| { |
| gpio_direction_output(chip->eoc_gpio, 1); |
| pr_info("[smb23x] eoc pin (%d)\n", gpio_get_value(chip->eoc_gpio)); |
| } |
| |
| |
| kt = ns_to_ktime(TRIM_PERIOD_NS * 90 * 1); |
| alarm_start_relative(&chip->wpc_check_alarm, kt); |
| } |
| |
| return; |
| } |
| |
| #define MAX_RW_RETRIES 3 |
| static int __smb23x_read(struct smb23x_chip *chip, u8 reg, u8 *val) |
| { |
| int rc, i; |
| |
| for (i = 0; i < MAX_RW_RETRIES; i++) { |
| rc = i2c_smbus_read_byte_data(chip->client, reg); |
| if (rc >= 0) |
| break; |
| /* delay between i2c retries */ |
| msleep(20); |
| } |
| if (rc < 0) { |
| pr_err("Reading 0x%02x failed, rc = %d\n", reg, rc); |
| return rc; |
| } |
| |
| *val = rc; |
| pr_debug("Reading 0x%02x = 0x%02x\n", reg, *val); |
| |
| return 0; |
| } |
| |
| static int __smb23x_write(struct smb23x_chip *chip, u8 reg, u8 val) |
| { |
| int rc, i; |
| |
| for (i = 0; i < MAX_RW_RETRIES; i++) { |
| rc = i2c_smbus_write_byte_data(chip->client, reg, val); |
| if (!rc) |
| break; |
| /* delay between i2c retries */ |
| msleep(20); |
| } |
| if (rc < 0) { |
| pr_err("Writing val 0x%02x to reg 0x%02x failed, rc = %d\n", |
| val, reg, rc); |
| return rc; |
| } |
| |
| pr_debug("Writing 0x%02x = 0x%02x\n", reg, val); |
| |
| return 0; |
| } |
| |
| static int smb23x_read(struct smb23x_chip *chip, u8 reg, u8 *val) |
| { |
| int rc; |
| |
| mutex_lock(&chip->read_write_lock); |
| rc = __smb23x_read(chip, reg, val); |
| mutex_unlock(&chip->read_write_lock); |
| |
| return rc; |
| } |
| |
| static int smb23x_write(struct smb23x_chip *chip, int reg, int val) |
| { |
| int rc; |
| |
| mutex_lock(&chip->read_write_lock); |
| rc = __smb23x_write(chip, reg, val); |
| mutex_unlock(&chip->read_write_lock); |
| return rc; |
| } |
| |
| static int smb23x_masked_write(struct smb23x_chip *chip, |
| u8 reg, u8 mask, u8 val) |
| { |
| int rc; |
| u8 tmp; |
| |
| mutex_lock(&chip->read_write_lock); |
| rc = __smb23x_read(chip, reg, &tmp); |
| if (rc < 0) { |
| pr_err("Read failed\n"); |
| goto i2c_error; |
| } |
| tmp &= ~mask; |
| tmp |= val & mask; |
| rc = __smb23x_write(chip, reg, tmp); |
| if (rc < 0) |
| pr_err("Write failed\n"); |
| i2c_error: |
| mutex_unlock(&chip->read_write_lock); |
| return rc; |
| } |
| |
| #ifdef QTI_SMB231 |
| static void smb23x_wakeup_src_init(struct smb23x_chip *chip) |
| { |
| spin_lock_init(&chip->smb23x_ws.ws_lock); |
| wakeup_source_init(&chip->smb23x_ws.source, "smb23x"); |
| } |
| |
| static void smb23x_stay_awake(struct smb23x_wakeup_source *source, |
| enum wakeup_src wk_src) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&source->ws_lock, flags); |
| |
| if (!__test_and_set_bit(wk_src, &source->enabled_bitmap)) { |
| __pm_stay_awake(&source->source); |
| pr_debug("enabled source %s, wakeup_src %d\n", |
| source->source.name, wk_src); |
| } |
| spin_unlock_irqrestore(&source->ws_lock, flags); |
| } |
| |
| static void smb23x_relax(struct smb23x_wakeup_source *source, |
| enum wakeup_src wk_src) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&source->ws_lock, flags); |
| if (__test_and_clear_bit(wk_src, &source->enabled_bitmap) && |
| !(source->enabled_bitmap & WAKEUP_SRC_MASK)) { |
| __pm_relax(&source->source); |
| pr_debug("disabled source %s\n", source->source.name); |
| } |
| spin_unlock_irqrestore(&source->ws_lock, flags); |
| |
| pr_debug("relax source %s, wakeup_src %d\n", |
| source->source.name, wk_src); |
| } |
| #endif |
| |
| #ifndef QTI_SMB231 |
| static void smb23x_wakeup_src_init(struct smb23x_chip *chip) |
| { |
| spin_lock_init(&chip->smb23x_ws.ws_lock); |
| wakeup_source_init(&chip->smb23x_ws.source, "smb23x"); |
| } |
| |
| static void smb23x_stay_awake(struct smb23x_wakeup_source *source, |
| enum wakeup_src wk_src) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&source->ws_lock, flags); |
| |
| if (!__test_and_set_bit(wk_src, &source->enabled_bitmap)) |
| { |
| __pm_stay_awake(&source->source); |
| pr_debug("enabled source %s, wakeup_src %d\n", source->source.name, wk_src); |
| } |
| |
| spin_unlock_irqrestore(&source->ws_lock, flags); |
| } |
| |
| static void smb23x_relax(struct smb23x_wakeup_source *source, |
| enum wakeup_src wk_src) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&source->ws_lock, flags); |
| if (__test_and_clear_bit(wk_src, &source->enabled_bitmap) && |
| !(source->enabled_bitmap & WAKEUP_SRC_MASK)) |
| { |
| __pm_relax(&source->source); |
| pr_debug("disabled source %s\n", source->source.name); |
| } |
| spin_unlock_irqrestore(&source->ws_lock, flags); |
| |
| pr_debug("relax source %s, wakeup_src %d\n", source->source.name, wk_src); |
| } |
| #endif |
| |
| static int smb23x_parse_dt(struct smb23x_chip *chip) |
| { |
| int rc = 0; |
| struct device_node *node = chip->dev->of_node; |
| |
| if (!node) { |
| pr_err("device node invalid\n"); |
| return (-EINVAL); |
| } |
| |
| chip->cfg_charging_disabled = |
| of_property_read_bool(node, "qcom,charging-disabled"); |
| chip->cfg_recharge_disabled = |
| of_property_read_bool(node, "qcom,recharge-disabled"); |
| chip->cfg_chg_inhibit_disabled = |
| of_property_read_bool(node, "qcom,chg-inhibit-disabled"); |
| chip->cfg_iterm_disabled = |
| of_property_read_bool(node, "qcom,iterm-disabled"); |
| chip->cfg_aicl_disabled = |
| of_property_read_bool(node, "qcom,aicl-disabled"); |
| chip->cfg_apsd_disabled = |
| of_property_read_bool(node, "qcom,apsd-disabled"); |
| chip->cfg_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->cfg_bms_controlled_charging = false; |
| } else { |
| chip->bms_psy = power_supply_get_by_name((char *)chip->bms_psy_name); |
| if (chip->bms_psy == NULL) |
| pr_err("smb23x can't find bms device \n"); |
| } |
| |
| /* |
| * If bms-controlled-charging is defined, then the charging |
| * termination and recharge behavior will be controlled by |
| * BMS power supply instead of the SMB chip itself, so we |
| * need to keep iterm and recharge on the chip disabled. |
| */ |
| if (chip->cfg_bms_controlled_charging) { |
| chip->cfg_iterm_disabled = true; |
| chip->cfg_recharge_disabled = true; |
| } |
| rc = of_property_read_u32(node, "qcom,float-voltage-mv", |
| &chip->cfg_vfloat_mv); |
| if (rc < 0) { |
| chip->cfg_vfloat_mv = -EINVAL; |
| } else { |
| if (chip->cfg_vfloat_mv > MAX_FLOAT_MV |
| || chip->cfg_vfloat_mv < MIN_FLOAT_MV) { |
| pr_err("Float voltage out of range\n"); |
| return (-EINVAL); |
| } |
| } |
| |
| rc = of_property_read_u32(node, "qcom,recharge-thresh-mv", |
| &chip->cfg_resume_delta_mv); |
| if (rc < 0) |
| chip->cfg_resume_delta_mv = -EINVAL; |
| |
| rc = of_property_read_u32(node, "qcom,chg-inhibit-thresh-mv", |
| &chip->cfg_chg_inhibit_delta_mv); |
| if (rc < 0) |
| chip->cfg_chg_inhibit_delta_mv = -EINVAL; |
| |
| rc = of_property_read_u32(node, "qcom,iterm-ma", |
| &chip->cfg_iterm_ma); |
| if (rc < 0) |
| chip->cfg_iterm_ma = -EINVAL; |
| |
| rc = of_property_read_u32(node, "qcom,fastchg-ma", |
| &chip->cfg_fastchg_ma); |
| if (rc < 0) |
| chip->cfg_fastchg_ma = -EINVAL; |
| |
| rc = of_property_read_u32(node, "qcom,cold-bat-decidegc", |
| &chip->cfg_cold_bat_decidegc); |
| if (rc < 0) |
| chip->cfg_cold_bat_decidegc = -EINVAL; |
| |
| rc = of_property_read_u32(node, "qcom,cool-bat-decidegc", |
| &chip->cfg_cool_bat_decidegc); |
| if (rc < 0) |
| chip->cfg_cool_bat_decidegc = -EINVAL; |
| |
| rc = of_property_read_u32(node, "qcom,warm-bat-decidegc", |
| &chip->cfg_warm_bat_decidegc); |
| if (rc < 0) |
| chip->cfg_warm_bat_decidegc = -EINVAL; |
| |
| rc = of_property_read_u32(node, "qcom,hot-bat-decidegc", |
| &chip->cfg_hot_bat_decidegc); |
| if (rc < 0) |
| chip->cfg_hot_bat_decidegc = -EINVAL; |
| |
| rc = of_property_read_u32(node, "qcom,soft-temp-vfloat-comp-mv", |
| &chip->cfg_temp_comp_mv); |
| if (rc < 0) |
| chip->cfg_temp_comp_mv = -EINVAL; |
| |
| rc = of_property_read_u32(node, "qcom,soft-temp-current-comp-ma", |
| &chip->cfg_temp_comp_ma); |
| if (rc < 0) |
| chip->cfg_temp_comp_ma = -EINVAL; |
| |
| rc = of_property_read_u32(node, "qcom,charging-timeout", |
| &chip->cfg_safety_time); |
| if (rc < 0) |
| chip->cfg_safety_time = -EINVAL; |
| |
| if (of_find_property(node, "qcom,thermal-mitigation", |
| &chip->thermal_levels)) { |
| chip->cfg_thermal_mitigation = devm_kzalloc(chip->dev, |
| chip->thermal_levels, |
| GFP_KERNEL); |
| |
| if (chip->cfg_thermal_mitigation == NULL) { |
| pr_err("thermal mitigation kzalloc() failed.\n"); |
| return (-ENOMEM); |
| } |
| |
| chip->thermal_levels /= sizeof(int); |
| rc = of_property_read_u32_array(node, |
| "qcom,thermal-mitigation", |
| chip->cfg_thermal_mitigation, |
| chip->thermal_levels); |
| if (rc) { |
| pr_err("Read therm limits failed, rc=%d\n", rc); |
| return rc; |
| } |
| } |
| |
| rc = of_property_read_u32(node, "cei,chg-en-active", |
| &chip->cfg_en_active); |
| if (rc < 0) |
| chip->cfg_en_active = -EINVAL; |
| |
| rc = of_property_read_u32(node, "cei,sys-voltage", |
| &chip->sys_voltage); |
| if (rc < 0) |
| chip->sys_voltage = -EINVAL; |
| |
| rc = of_property_read_u32(node, "cei,sys-voltage-threshold", |
| &chip->sys_vthreshold); |
| if (rc < 0) |
| chip->sys_vthreshold = -EINVAL; |
| |
| rc = of_property_read_u32(node, "cei,max-ac-current-ma", |
| &chip->max_ac_current_ma); |
| if (rc < 0) |
| chip->max_ac_current_ma = -EINVAL; |
| |
| rc = of_property_read_u32(node, "cei,prechg-current-ma", |
| &chip->prechg_current_ma); |
| if (rc < 0) |
| chip->prechg_current_ma = -EINVAL; |
| |
| rc = of_property_read_u32(node, "cei,cool-temp-vfloat-comp-mv", |
| &chip->cfg_cool_temp_comp_mv); |
| if (rc < 0) |
| chip->cfg_cool_temp_comp_mv = -EINVAL; |
| |
| chip->susp_gpio = of_get_named_gpio(node, "qcom,smb-susp", 0); |
| if (chip->susp_gpio < 0) |
| { |
| pr_err("[smb23x] susp_gpio is not available.\n"); |
| } |
| |
| chip->eoc_gpio = of_get_named_gpio(node, "cei,wpc-eoc", 0); |
| if (chip->eoc_gpio < 0) |
| { |
| pr_err("[smb23x] eoc_gpio is not available.\n"); |
| } |
| |
| return 0; |
| } |
| |
| static int smb23x_enable_volatile_writes(struct smb23x_chip *chip) |
| { |
| int rc; |
| #ifdef QTI_SMB231 |
| u8 reg; |
| |
| rc = smb23x_read(chip, I2C_COMM_CFG_REG, ®); |
| if (rc < 0) { |
| pr_err("Read I2C_COMM_CFG_REG, rc=%d\n", rc); |
| return rc; |
| } |
| if (!(reg & VOLATILE_WRITE_EN_BIT)) { |
| pr_err("Volatile write is not allowed!"); |
| return (-EACCES); |
| } |
| #endif |
| |
| rc = smb23x_masked_write(chip, CMD_REG_0, |
| VOLATILE_WRITE_ALLOW, VOLATILE_WRITE_ALLOW); |
| if (rc < 0) { |
| pr_err("Set VOLATILE_WRITE_ALLOW bit failed\n"); |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| static inline int find_closest_in_ascendant_list( |
| int val, int lst[], int length) |
| { |
| int i; |
| |
| for (i = 0; i < length; i++) { |
| if (val <= lst[i]) |
| break; |
| } |
| if (i == length) |
| i--; |
| |
| return i; |
| } |
| |
| static inline int find_closest_in_descendant_list( |
| int val, int lst[], int length) |
| { |
| int i; |
| |
| for (i = 0; i < length; i++) { |
| if (val >= lst[i]) |
| break; |
| } |
| if (i == length) |
| i--; |
| |
| return i; |
| } |
| |
| #ifdef QTI_SMB231 |
| static int __smb23x_charging_disable(struct smb23x_chip *chip, bool disable) |
| { |
| int rc; |
| |
| rc = smb23x_masked_write(chip, CMD_REG_0, |
| CHARGE_EN_BIT, disable ? 0 : CHARGE_EN_BIT); |
| if (rc < 0) |
| pr_err("%s charging failed, rc=%d\n", |
| disable ? "Disable" : "Enable", rc); |
| return rc; |
| } |
| |
| static int smb23x_charging_disable(struct smb23x_chip *chip, |
| int reason, int disable) |
| { |
| int rc = 0; |
| int disabled; |
| |
| mutex_lock(&chip->chg_disable_lock); |
| disabled = chip->chg_disabled_status; |
| if (disable) |
| disabled |= reason; |
| else |
| disabled &= ~reason; |
| |
| rc = __smb23x_charging_disable(chip, disabled ? true : false); |
| if (rc < 0) |
| pr_err("%s charging for reason 0x%x failed\n", |
| disable ? "Disable" : "Enable", reason); |
| else |
| chip->chg_disabled_status = disabled; |
| mutex_unlock(&chip->chg_disable_lock); |
| return rc; |
| } |
| |
| static int smb23x_suspend_usb(struct smb23x_chip *chip, |
| int reason, bool suspend) |
| { |
| int rc = 0; |
| int suspended; |
| |
| mutex_lock(&chip->usb_suspend_lock); |
| suspended = chip->usb_suspended_status; |
| if (suspend) |
| suspended |= reason; |
| else |
| suspended &= ~reason; |
| |
| rc = smb23x_masked_write(chip, CMD_REG_0, USB_SUSPEND_BIT, |
| suspended ? USB_SUSPEND_BIT : 0); |
| if (rc < 0) { |
| pr_err("Write USB_SUSPEND failed, rc=%d\n", rc); |
| } else { |
| chip->usb_suspended_status = suspended; |
| pr_debug("%suspend USB!\n", suspend ? "S" : "Un-s"); |
| } |
| mutex_unlock(&chip->usb_suspend_lock); |
| |
| return rc; |
| } |
| |
| #define CURRENT_SUSPEND 2 |
| #define CURRENT_100_MA 100 |
| #define CURRENT_500_MA 500 |
| static int smb23x_set_appropriate_usb_current(struct smb23x_chip *chip) |
| { |
| int rc = 0, therm_ma, current_ma; |
| int usb_current = chip->usb_psy_ma; |
| u8 tmp; |
| |
| if (chip->therm_lvl_sel > 0 |
| && chip->therm_lvl_sel < (chip->thermal_levels - 1)) |
| /* |
| * consider thermal limit only when it is active and not at |
| * the highest level |
| */ |
| therm_ma = chip->cfg_thermal_mitigation[chip->therm_lvl_sel]; |
| else |
| therm_ma = usb_current; |
| |
| current_ma = min(therm_ma, usb_current); |
| |
| if (current_ma <= CURRENT_SUSPEND) { |
| if (chip->workaround_flags & WRKRND_USB_SUSPEND) { |
| current_ma = CURRENT_100_MA; |
| } else { |
| /* suspend USB input */ |
| rc = smb23x_suspend_usb(chip, CURRENT, true); |
| if (rc) |
| pr_err("Suspend USB failed, rc=%d\n", rc); |
| return rc; |
| } |
| } |
| |
| if (current_ma <= CURRENT_100_MA) { |
| /* USB 100 */ |
| rc = smb23x_masked_write(chip, CMD_REG_1, |
| USB500_MODE_BIT | USBAC_MODE_BIT, 0); |
| if (rc) |
| pr_err("Set USB100 failed, rc=%d\n", rc); |
| pr_debug("Setting USB 100\n"); |
| } else if (current_ma <= CURRENT_500_MA) { |
| /* USB 500 */ |
| rc = smb23x_masked_write(chip, CMD_REG_1, |
| USB500_MODE_BIT | USBAC_MODE_BIT, |
| USB500_MODE_BIT); |
| if (rc) |
| pr_err("Set USB500 failed, rc=%d\n", rc); |
| pr_debug("Setting USB 500\n"); |
| } else { |
| /* USB AC */ |
| rc = smb23x_masked_write(chip, CMD_REG_1, |
| USBAC_MODE_BIT, USBAC_MODE_BIT); |
| if (rc) |
| pr_err("Set USBAC failed, rc=%d\n", rc); |
| pr_debug("Setting USB AC\n"); |
| } |
| |
| /* set ICL */ |
| tmp = find_closest_in_ascendant_list(current_ma, usbin_current_ma_table, |
| ARRAY_SIZE(usbin_current_ma_table)); |
| tmp = tmp << USBIN_ICL_OFFSET; |
| rc = smb23x_masked_write(chip, CFG_REG_0, USBIN_ICL_MASK, tmp); |
| if (rc < 0) { |
| pr_err("Set ICL failed\n, rc=%d\n", rc); |
| return rc; |
| } |
| pr_debug("ICL set to = %d\n", |
| usbin_current_ma_table[tmp >> USBIN_ICL_OFFSET]); |
| |
| if (!(chip->workaround_flags & WRKRND_USB_SUSPEND)) { |
| /* un-suspend USB input */ |
| rc = smb23x_suspend_usb(chip, CURRENT, false); |
| if (rc < 0) |
| pr_err("Un-suspend USB failed, rc=%d\n", rc); |
| } |
| |
| return rc; |
| } |
| #else |
| static int smb23x_charging_enable(struct smb23x_chip *chip, int enable) |
| { |
| int rc; |
| u8 reg; |
| |
| rc = smb23x_read(chip, CFG_REG_4, ®); |
| if (rc) |
| return rc; |
| reg &= CHG_EN_ACTIVE_LOW_BIT; |
| |
| mutex_lock(&chip->chg_disable_lock); |
| if (enable) { |
| rc = smb23x_masked_write(chip, CMD_REG_0, CHARGE_EN_BIT, reg ? 0 : CHARGE_EN_BIT); |
| if (rc) |
| pr_err("enable fail, enable polarity=0x%02x, rc=%d\n", reg, rc); |
| } else { |
| rc = smb23x_masked_write(chip, CMD_REG_0, CHARGE_EN_BIT, reg ? CHARGE_EN_BIT : 0); |
| if (rc) |
| pr_err("disable fail, enable polarity=0x%02x, rc=%d\n", reg, rc); |
| } |
| mutex_unlock(&chip->chg_disable_lock); |
| return rc; |
| } |
| |
| static int smb23x_vfloat_compensation(struct smb23x_chip *chip, int temp_comp_mv) |
| { |
| int rc = 0, i = 0; |
| u8 tmp; |
| |
| if (temp_comp_mv != -EINVAL) { |
| i = find_closest_in_ascendant_list( |
| temp_comp_mv, vfloat_compensation_mv_table, |
| ARRAY_SIZE(vfloat_compensation_mv_table)); |
| tmp = i << VFLOAT_COMP_OFFSET; |
| rc = smb23x_masked_write(chip, CFG_REG_5, VFLOAT_COMP_MASK, tmp); |
| } |
| |
| return rc; |
| } |
| |
| //Change thermal setting by battery temperature |
| static int check_charger_thermal_state(struct smb23x_chip *chip, int batt_temp) |
| { |
| int rc; |
| |
| if (chip->charger_plugin == 0) |
| return (-EINVAL); |
| |
| if ((HIGH != chip->index_soft_temp_comp_mv) && (batt_temp > 350)) { |
| rc = smb23x_enable_volatile_writes(chip); |
| if (rc < 0) { |
| pr_err("Enable volatile writes failed, rc=%d\n", rc); |
| return rc; |
| } |
| |
| rc = smb23x_vfloat_compensation(chip, chip->cfg_temp_comp_mv); |
| if (rc < 0) { |
| pr_err("Set VFLOAT_COMP failed, rc=%d\n", rc); |
| return rc; |
| } |
| chip->index_soft_temp_comp_mv = HIGH; |
| } else if ((LOW != chip->index_soft_temp_comp_mv) && (batt_temp < 250)) { |
| rc = smb23x_enable_volatile_writes(chip); |
| if (rc < 0) { |
| pr_err("Enable volatile writes failed, rc=%d\n", rc); |
| return rc; |
| } |
| |
| rc = smb23x_vfloat_compensation(chip, chip->cfg_cool_temp_comp_mv); |
| if (rc < 0) { |
| pr_err("Set VFLOAT_COMP failed, rc=%d\n", rc); |
| return rc; |
| } |
| chip->index_soft_temp_comp_mv = LOW; |
| } |
| |
| return 0; |
| } |
| #endif |
| |
| static int smb23x_hw_init(struct smb23x_chip *chip) |
| { |
| int rc, i = 0; |
| u8 tmp; |
| |
| rc = smb23x_enable_volatile_writes(chip); |
| if (rc < 0) { |
| pr_err("Enable volatile writes failed, rc=%d\n", rc); |
| return rc; |
| } |
| |
| /* recharging setting */ |
| if (chip->cfg_recharge_disabled) { |
| rc = smb23x_masked_write(chip, CFG_REG_2, |
| RECHARGE_DIS_BIT, RECHARGE_DIS_BIT); |
| if (rc < 0) { |
| pr_err("Disable recharging failed, rc=%d\n", rc); |
| return rc; |
| } |
| } else if (chip->cfg_resume_delta_mv != -EINVAL) { |
| i = find_closest_in_descendant_list( |
| chip->cfg_resume_delta_mv, recharge_mv_table, |
| ARRAY_SIZE(recharge_mv_table)); |
| tmp = i << RECHARGE_THRESH_OFFSET; |
| rc = smb23x_masked_write(chip, CFG_REG_2, |
| RECHARGE_THRESH_MASK, tmp); |
| if (rc < 0) { |
| pr_err("Set recharge thresh failed, rc=%d\n", rc); |
| return rc; |
| } |
| } |
| |
| /* iterm setting */ |
| if (chip->cfg_iterm_disabled) { |
| rc = smb23x_masked_write(chip, CFG_REG_2, |
| ITERM_DIS_BIT, ITERM_DIS_BIT); |
| if (rc < 0) { |
| pr_err("Disable ITERM failed, rc=%d\n", rc); |
| return rc; |
| } |
| } else if (chip->cfg_iterm_ma != -EINVAL) { |
| i = find_closest_in_ascendant_list(chip->cfg_iterm_ma, |
| iterm_ma_table, ARRAY_SIZE(iterm_ma_table)); |
| tmp = i; |
| rc = smb23x_masked_write(chip, CFG_REG_0, ITERM_MASK, tmp); |
| if (rc < 0) { |
| pr_err("Set ITERM failed, rc=%d\n", rc); |
| return rc; |
| } |
| } |
| |
| /* fastchg current setting */ |
| if (chip->cfg_fastchg_ma != -EINVAL) { |
| i = find_closest_in_ascendant_list( |
| chip->cfg_fastchg_ma, fastchg_current_ma_table, |
| ARRAY_SIZE(fastchg_current_ma_table)); |
| tmp = i; |
| rc = smb23x_masked_write(chip, CFG_REG_2, |
| FASTCHG_CURR_MASK, tmp); |
| if (rc < 0) { |
| pr_err("Set fastchg current failed, rc=%d\n", rc); |
| return rc; |
| } |
| } |
| |
| /* float voltage setting */ |
| if (chip->cfg_vfloat_mv != -EINVAL) { |
| tmp = (chip->cfg_vfloat_mv - MIN_FLOAT_MV) / FLOAT_STEP_MV; |
| rc = smb23x_masked_write(chip, CFG_REG_3, |
| FLOAT_VOLTAGE_MASK, tmp); |
| if (rc < 0) { |
| pr_err("Set float voltage failed, rc=%d\n", rc); |
| return rc; |
| } |
| } |
| |
| /* safety timer setting */ |
| if (chip->cfg_safety_time != -EINVAL) { |
| i = find_closest_in_ascendant_list( |
| chip->cfg_safety_time, safety_time_min_table, |
| ARRAY_SIZE(safety_time_min_table)); |
| tmp = i << SAFETY_TIMER_OFFSET; |
| /* disable safety timer if the value equals 0 */ |
| if (chip->cfg_safety_time == 0) |
| tmp = SAFETY_TIMER_DISABLE; |
| rc = smb23x_masked_write(chip, CFG_REG_4, |
| SAFETY_TIMER_MASK, tmp); |
| if (rc < 0) { |
| pr_err("Set safety timer failed, rc=%d\n", rc); |
| return rc; |
| } |
| } |
| |
| /* hard JEITA settings */ |
| if (chip->cfg_cold_bat_decidegc != -EINVAL || |
| chip->cfg_hot_bat_decidegc != -EINVAL) { |
| u8 mask = 0; |
| |
| rc = smb23x_masked_write(chip, CFG_REG_5, |
| BAT_THERM_DIS_BIT | HARD_THERM_NOT_SUSPEND, 0); |
| if (rc < 0) { |
| pr_err("Enable thermal monitor failed, rc=%d\n", rc); |
| return rc; |
| } |
| if (chip->cfg_cold_bat_decidegc != -EINVAL) { |
| i = find_closest_in_descendant_list( |
| chip->cfg_cold_bat_decidegc, |
| cold_bat_decidegc_table, |
| ARRAY_SIZE(cold_bat_decidegc_table)); |
| mask |= HARD_COLD_TEMP_MASK; |
| tmp = i << HARD_COLD_TEMP_OFFSET; |
| } |
| |
| if (chip->cfg_hot_bat_decidegc != -EINVAL) { |
| i = find_closest_in_ascendant_list( |
| chip->cfg_hot_bat_decidegc, |
| hot_bat_decidegc_table, |
| ARRAY_SIZE(hot_bat_decidegc_table)); |
| mask |= HARD_HOT_TEMP_MASK; |
| tmp |= i << HARD_HOT_TEMP_OFFSET; |
| } |
| rc = smb23x_masked_write(chip, CFG_REG_8, mask, tmp); |
| if (rc < 0) { |
| pr_err("Set hard cold/hot temperature failed, rc=%d\n", |
| rc); |
| return rc; |
| } |
| } |
| |
| /* soft JEITA settings */ |
| if (chip->cfg_cool_bat_decidegc != -EINVAL || |
| chip->cfg_warm_bat_decidegc != -EINVAL) { |
| u8 mask = 0; |
| |
| rc = smb23x_masked_write(chip, CFG_REG_5, |
| SOFT_THERM_BEHAVIOR_MASK, SOFT_THERM_VFLT_CHG_COMP); |
| if (rc < 0) { |
| pr_err("Set soft JEITA behavior failed, rc=%d\n", rc); |
| return rc; |
| } |
| if (chip->cfg_cool_bat_decidegc != -EINVAL) { |
| i = find_closest_in_descendant_list( |
| chip->cfg_cool_bat_decidegc, |
| cool_bat_decidegc_table, |
| ARRAY_SIZE(cool_bat_decidegc_table)); |
| mask |= SOFT_COLD_TEMP_MASK; |
| tmp = i << SOFT_COLD_TEMP_OFFSET; |
| } |
| |
| if (chip->cfg_warm_bat_decidegc != -EINVAL) { |
| i = find_closest_in_ascendant_list( |
| chip->cfg_warm_bat_decidegc, |
| warm_bat_decidegc_table, |
| ARRAY_SIZE(warm_bat_decidegc_table)); |
| mask |= SOFT_HOT_TEMP_MASK; |
| tmp |= i << SOFT_HOT_TEMP_OFFSET; |
| } |
| |
| rc = smb23x_masked_write(chip, CFG_REG_8, mask, tmp); |
| if (rc < 0) { |
| pr_err("Set soft cold/hot temperature failed, rc=%d\n", |
| rc); |
| return rc; |
| } |
| } |
| |
| /* float voltage and fastchg current compensation for soft JEITA */ |
| if ((300 > chip->last_temp) && (chip->cfg_cool_temp_comp_mv != -EINVAL)) { |
| rc = smb23x_vfloat_compensation(chip, chip->cfg_cool_temp_comp_mv); |
| if (rc < 0) { |
| pr_err("Set VFLOAT_COMP failed, rc=%d\n", rc); |
| return rc; |
| } |
| chip->index_soft_temp_comp_mv = LOW; |
| } else { |
| rc = smb23x_vfloat_compensation(chip, chip->cfg_temp_comp_mv); |
| if (rc < 0) { |
| pr_err("Set VFLOAT_COMP failed, rc=%d\n", rc); |
| return rc; |
| } |
| chip->index_soft_temp_comp_mv = HIGH; |
| } |
| |
| if (chip->cfg_temp_comp_ma != -EINVAL) { |
| i = find_closest_in_ascendant_list( |
| chip->cfg_temp_comp_ma, fastchg_current_ma_table, |
| ARRAY_SIZE(fastchg_current_ma_table)); |
| tmp = i << FASTCHG_CURR_SOFT_COMP_OFFSET; |
| rc = smb23x_masked_write(chip, CFG_REG_3, |
| FASTCHG_CURR_SOFT_COMP, tmp); |
| if (rc < 0) { |
| pr_err("Set FASTCHG_COMP failed, rc=%d\n", rc); |
| return rc; |
| } |
| } |
| |
| /* disable APSD */ |
| if (chip->cfg_apsd_disabled) { |
| rc = smb23x_masked_write(chip, CFG_REG_5, APSD_EN_BIT, 0); |
| if (rc < 0) { |
| pr_err("Disable APSD failed, rc=%d\n", rc); |
| return rc; |
| } |
| chip->apsd_enabled = false; |
| } else { |
| rc = smb23x_read(chip, CFG_REG_5, &tmp); |
| if (rc < 0) { |
| pr_err("read CFG_REG_5 failed, rc=%d\n", rc); |
| return rc; |
| } |
| chip->apsd_enabled = !!(tmp & APSD_EN_BIT); |
| } |
| |
| /* disable AICL */ |
| if (chip->cfg_aicl_disabled) { |
| rc = smb23x_masked_write(chip, CFG_REG_5, AICL_EN_BIT, 0); |
| if (rc < 0) { |
| pr_err("Disable AICL failed, rc=%d\n", rc); |
| return rc; |
| } |
| } |
| |
| /* charging inhibit setting */ |
| if (chip->cfg_chg_inhibit_disabled) { |
| rc = smb23x_masked_write(chip, CFG_REG_6, |
| CHG_INHIBIT_THRESH_MASK, 0); |
| if (rc < 0) { |
| pr_err("Disable charge inhibit failed, rc=%d\n", rc); |
| return rc; |
| } |
| } else if (chip->cfg_chg_inhibit_delta_mv != -EINVAL) { |
| i = find_closest_in_ascendant_list( |
| chip->cfg_chg_inhibit_delta_mv, inhibit_mv_table, |
| ARRAY_SIZE(inhibit_mv_table)); |
| tmp = i << INHIBIT_THRESH_OFFSET; |
| rc = smb23x_masked_write(chip, CFG_REG_6, |
| CHG_INHIBIT_THRESH_MASK, tmp); |
| if (rc < 0) { |
| pr_err("Set inhibit threshold failed, rc=%d\n", rc); |
| return rc; |
| } |
| } |
| |
| #ifdef QTI_SMB231 |
| /* |
| * Disable the STAT pin output, to make the pin keep at open drain |
| * state and detect the IRQ on the falling edge |
| */ |
| rc = smb23x_masked_write(chip, CMD_REG_0, |
| STATE_PIN_OUT_DIS_BIT, |
| STATE_PIN_OUT_DIS_BIT); |
| if (rc < 0) { |
| pr_err("Disable state pin output failed, rc=%d\n", rc); |
| return rc; |
| } |
| |
| rc = smb23x_masked_write(chip, CFG_REG_4, |
| CHG_EN_ACTIVE_LOW_BIT, 0); /* EN active high */ |
| rc |= smb23x_masked_write(chip, CFG_REG_7, |
| USB1_5_PIN_CNTRL_BIT | |
| USB_AC_PIN_CNTRL_BIT | |
| CHG_EN_PIN_CNTRL_BIT | |
| SUSPEND_SW_CNTRL_BIT, |
| SUSPEND_SW_CNTRL_BIT); /* suspend ctrl by sw */ |
| if (rc < 0) { |
| pr_err("Configure board setting failed, rc=%d\n", rc); |
| return rc; |
| } |
| |
| /* Enable necessary IRQs */ |
| rc = smb23x_masked_write(chip, IRQ_CFG_REG_9, 0xff, |
| SAFETY_TIMER_IRQ_EN_BIT | |
| BATT_MISSING_IRQ_EN_BIT | |
| ITERM_IRQ_EN_BIT | |
| HARD_TEMP_IRQ_EN_BIT | |
| SOFT_TEMP_IRQ_EN_BIT | |
| AICL_DONE_IRQ_EN_BIT | |
| INOK_IRQ_EN_BIT); |
| if (rc < 0) { |
| pr_err("Configure IRQ failed, rc=%d\n", rc); |
| return rc; |
| } |
| #else //QTI_SMB231 |
| //Max AC input current limit |
| if (chip->max_ac_current_ma != -EINVAL) { |
| i = find_closest_in_ascendant_list(chip->max_ac_current_ma, |
| usbin_current_ma_table, ARRAY_SIZE(usbin_current_ma_table)); |
| tmp = i << USBIN_ICL_OFFSET; |
| rc = smb23x_masked_write(chip, CFG_REG_0, USBIN_ICL_MASK, tmp); |
| if (rc < 0) { |
| pr_err("Set Max AC input current limit failed, rc=%d\n", rc); |
| return rc; |
| } |
| } |
| |
| //Pre-charge current |
| if (chip->prechg_current_ma != -EINVAL) { |
| i = find_closest_in_ascendant_list(chip->prechg_current_ma, |
| iterm_ma_table, ARRAY_SIZE(iterm_ma_table)); |
| tmp = i << PRECHG_CURR_OFFSET; |
| rc = smb23x_masked_write(chip, CFG_REG_1, PRECHG_CURR_MASK, tmp); |
| if (rc < 0) { |
| pr_err("Set Pre-charge current failed, rc=%d\n", rc); |
| return rc; |
| } |
| } |
| |
| //Charger enable polarity |
| if (chip->cfg_en_active != -EINVAL) { |
| tmp = chip->cfg_en_active << CHG_EN_ACTIVE_OFFSET; |
| rc = smb23x_masked_write(chip, CFG_REG_4, |
| CHG_EN_ACTIVE_LOW_BIT, tmp); |
| if (rc < 0) { |
| pr_err("Set Charger enable polarity failed, rc=%d\n", rc); |
| return rc; |
| } |
| } |
| |
| //System voltage |
| if (chip->sys_voltage != -EINVAL) { |
| rc = smb23x_masked_write(chip, CFG_REG_4, |
| SYS_VOLTAGE_MASK, chip->sys_voltage); |
| if (rc < 0) { |
| pr_err("Set System voltage failed, rc=%d\n", rc); |
| return rc; |
| } |
| } |
| |
| //System voltage threshold for initiating charge current reduction |
| if (chip->sys_vthreshold != -EINVAL) { |
| tmp = chip->sys_vthreshold << SYS_VTHRESHOLD_OFFSET; |
| rc = smb23x_masked_write(chip, CFG_REG_6, |
| SYS_VTHRESHOLD_MASK, tmp); |
| if (rc < 0) { |
| pr_err("Set System voltage threshold failed, rc=%d\n", rc); |
| return rc; |
| } |
| } |
| |
| //Battery charge enable = Enable |
| rc = smb23x_charging_enable(chip, 1); |
| if (rc) |
| return rc; |
| #endif //QTI_SMB231 |
| return rc; |
| } |
| |
| #ifdef QTI_SMB231 |
| static int hot_hard_irq_handler(struct smb23x_chip *chip, u8 rt_sts) |
| { |
| pr_warn("rt_sts = 0x02%x\n", rt_sts); |
| chip->batt_hot = !!rt_sts; |
| return 0; |
| } |
| |
| static int cold_hard_irq_handler(struct smb23x_chip *chip, u8 rt_sts) |
| { |
| pr_debug("rt_sts = 0x02%x\n", rt_sts); |
| chip->batt_cold = !!rt_sts; |
| return 0; |
| } |
| |
| static int hot_soft_irq_handler(struct smb23x_chip *chip, u8 rt_sts) |
| { |
| pr_debug("rt_sts = 0x02%x\n", rt_sts); |
| chip->batt_warm = !!rt_sts; |
| return 0; |
| } |
| |
| static int cold_soft_irq_handler(struct smb23x_chip *chip, u8 rt_sts) |
| { |
| pr_debug("rt_sts = 0x02%x\n", rt_sts); |
| chip->batt_cool = !!rt_sts; |
| return 0; |
| } |
| |
| static int batt_ov_irq_handler(struct smb23x_chip *chip, u8 rt_sts) |
| { |
| pr_debug("rt_sts = 0x02%x\n", rt_sts); |
| return 0; |
| } |
| |
| static int batt_missing_irq_handler(struct smb23x_chip *chip, u8 rt_sts) |
| { |
| pr_debug("rt_sts = 0x02%x\n", rt_sts); |
| chip->batt_present = !rt_sts; |
| return 0; |
| } |
| |
| static int batt_low_irq_handler(struct smb23x_chip *chip, u8 rt_sts) |
| { |
| pr_warn("rt_sts = 0x02%x\n", rt_sts); |
| return 0; |
| } |
| |
| static int pre_to_fast_irq_handler(struct smb23x_chip *chip, u8 rt_sts) |
| { |
| pr_debug("rt_sts = 0x02%x\n", rt_sts); |
| chip->batt_full = false; |
| |
| return 0; |
| } |
| |
| static int chg_error_irq_handler(struct smb23x_chip *chip, u8 rt_sts) |
| { |
| pr_warn("rt_sts = 0x02%x\n", rt_sts); |
| return 0; |
| } |
| |
| static int recharge_irq_handler(struct smb23x_chip *chip, u8 rt_sts) |
| { |
| pr_debug("rt_sts = 0x02%x\n", rt_sts); |
| chip->batt_full = !rt_sts; |
| return 0; |
| } |
| |
| static int taper_irq_handler(struct smb23x_chip *chip, u8 rt_sts) |
| { |
| pr_debug("rt_sts = 0x02%x\n", rt_sts); |
| return 0; |
| } |
| |
| static int iterm_irq_handler(struct smb23x_chip *chip, u8 rt_sts) |
| { |
| pr_debug("rt_sts = 0x02%x\n", rt_sts); |
| chip->batt_full = !!rt_sts; |
| return 0; |
| } |
| |
| static const char * const usb_type_str[] = { |
| "SDP", |
| "UNKNOWN", |
| "DCP", |
| "CDP", |
| }; |
| static int get_usb_supply_type(struct smb23x_chip *chip) |
| { |
| int rc; |
| u8 reg, tmp; |
| enum power_supply_type type; |
| |
| rc = smb23x_read(chip, CHG_STATUS_C_REG, ®); |
| if (rc < 0) { |
| pr_err("Read STATUS_C failed, rc=%d\n", rc); |
| return rc; |
| } |
| tmp = reg & APSD_STATUS_BIT; |
| |
| if (!tmp) { |
| pr_debug("APSD not completed\n"); |
| return POWER_SUPPLY_TYPE_UNKNOWN; |
| } |
| |
| tmp = reg & APSD_RESULT_MASK; |
| if (tmp == CDP_TYPE_VAL) { |
| type = POWER_SUPPLY_TYPE_USB_CDP; |
| tmp = 3; |
| } else if (tmp == DCP_TYPE_VAL) { |
| type = POWER_SUPPLY_TYPE_USB_DCP; |
| tmp = 2; |
| } else if (tmp == SDP_TYPE_VAL) { |
| type = POWER_SUPPLY_TYPE_USB; |
| tmp = 0; |
| } else { |
| type = POWER_SUPPLY_TYPE_UNKNOWN; |
| tmp = 1; |
| } |
| |
| pr_debug("Charger type %s detected\n", usb_type_str[tmp]); |
| |
| return type; |
| } |
| |
| static int handle_usb_insertion(struct smb23x_chip *chip) |
| { |
| enum power_supply_type usb_type; |
| |
| usb_type = get_usb_supply_type(chip); |
| power_supply_set_supply_type(chip->usb_psy, usb_type); |
| power_supply_set_present(chip->usb_psy, chip->usb_present); |
| power_supply_set_online(chip->usb_psy, true); |
| |
| return 0; |
| } |
| |
| static int handle_usb_removal(struct smb23x_chip *chip) |
| { |
| power_supply_set_supply_type(chip->usb_psy, |
| POWER_SUPPLY_TYPE_UNKNOWN); |
| power_supply_set_present(chip->usb_psy, chip->usb_present); |
| power_supply_set_online(chip->usb_psy, false); |
| |
| return 0; |
| } |
| |
| static int src_detect_irq_handler(struct smb23x_chip *chip, u8 rt_sts) |
| { |
| bool usb_present = !!rt_sts; |
| |
| if (!chip->apsd_enabled) |
| return 0; |
| |
| pr_debug("chip->usb_present = %d, usb_present = %d\n", |
| chip->usb_present, usb_present); |
| |
| if (usb_present && !chip->usb_present) { |
| chip->usb_present = usb_present; |
| handle_usb_insertion(chip); |
| } else if (!usb_present && chip->usb_present) { |
| chip->usb_present = usb_present; |
| handle_usb_removal(chip); |
| } |
| |
| return 0; |
| } |
| |
| static int aicl_done_irq_handler(struct smb23x_chip *chip, u8 rt_sts) |
| { |
| pr_debug("rt_sts = 0x02%x\n", rt_sts); |
| return 0; |
| } |
| |
| static int chg_timeout_irq_handler(struct smb23x_chip *chip, u8 rt_sts) |
| { |
| pr_debug("rt_sts = 0x02%x\n", rt_sts); |
| return 0; |
| } |
| |
| static int pre_chg_timeout_irq_handler(struct smb23x_chip *chip, u8 rt_sts) |
| { |
| pr_debug("rt_sts = 0x02%x\n", rt_sts); |
| return 0; |
| } |
| |
| static int usbin_ov_irq_handler(struct smb23x_chip *chip, u8 rt_sts) |
| { |
| bool usb_present = !rt_sts; |
| int health = !!rt_sts ? POWER_SUPPLY_HEALTH_OVERVOLTAGE : |
| POWER_SUPPLY_HEALTH_GOOD; |
| |
| pr_debug("chip->usb_present = %d, usb_present = %d\n", |
| chip->usb_present, usb_present); |
| if (chip->usb_present != usb_present) { |
| chip->usb_present = usb_present; |
| power_supply_set_present(chip->usb_psy, usb_present); |
| power_supply_set_health_state(chip->usb_psy, health); |
| } |
| |
| return 0; |
| } |
| |
| #define IRQ_POLLING_MS 500 |
| static void smb23x_irq_polling_work_fn(struct work_struct *work) |
| { |
| struct smb23x_chip *chip = container_of(work, struct smb23x_chip, |
| irq_polling_work.work); |
| |
| smb23x_stat_handler(chip->client->irq, (void *)chip); |
| |
| if (chip->usb_present) |
| schedule_delayed_work(&chip->irq_polling_work, |
| msecs_to_jiffies(IRQ_POLLING_MS)); |
| } |
| |
| /* |
| * On some of the parts, the Non-volatile register values will |
| * be reloaded upon unplug event. Even the unplug event won't be |
| * detected because the IRQ isn't enabled by default in chip |
| * internally. |
| * Use polling to detect the unplug event, and after that, redo |
| * hw_init() to repropgram the software configurations. |
| */ |
| static void reconfig_upon_unplug(struct smb23x_chip *chip) |
| { |
| int rc; |
| int reason; |
| |
| if (chip->workaround_flags & WRKRND_IRQ_POLLING) { |
| if (chip->usb_present) { |
| smb23x_stay_awake(&chip->smb23x_ws, |
| WAKEUP_SRC_IRQ_POLLING); |
| schedule_delayed_work(&chip->irq_polling_work, |
| msecs_to_jiffies(IRQ_POLLING_MS)); |
| } else { |
| pr_debug("restore software settings after unplug\n"); |
| smb23x_relax(&chip->smb23x_ws, WAKEUP_SRC_IRQ_POLLING); |
| rc = smb23x_hw_init(chip); |
| if (rc) |
| pr_err("smb23x init upon unplug failed, rc=%d\n", |
| rc); |
| /* |
| * Retore the CHARGE_EN && USB_SUSPEND bit |
| * according to the status maintained in sw. |
| */ |
| mutex_lock(&chip->chg_disable_lock); |
| reason = chip->chg_disabled_status; |
| mutex_unlock(&chip->chg_disable_lock); |
| rc = smb23x_charging_disable(chip, reason, |
| !!reason ? true : false); |
| if (rc < 0) |
| pr_err("%s charging failed\n", |
| !!reason ? "Disable" : "Enable"); |
| |
| mutex_lock(&chip->usb_suspend_lock); |
| reason = chip->usb_suspended_status; |
| mutex_unlock(&chip->usb_suspend_lock); |
| if (!(chip->workaround_flags & WRKRND_USB_SUSPEND)) { |
| rc = smb23x_suspend_usb(chip, reason, |
| !!reason ? true : false); |
| if (rc < 0) |
| pr_err("%suspend USB failed\n", |
| !!reason ? "S" : "Un-s"); |
| } |
| } |
| } |
| } |
| |
| static int usbin_uv_irq_handler(struct smb23x_chip *chip, u8 rt_sts) |
| { |
| bool usb_present = !rt_sts; |
| |
| pr_debug("chip->usb_present = %d, usb_present = %d\n", |
| chip->usb_present, usb_present); |
| if (chip->usb_present == usb_present) |
| return 0; |
| |
| chip->usb_present = usb_present; |
| reconfig_upon_unplug(chip); |
| power_supply_set_present(chip->usb_psy, usb_present); |
| |
| return 0; |
| } |
| |
| static int power_ok_irq_handler(struct smb23x_chip *chip, u8 rt_sts) |
| { |
| pr_debug("rt_sts = 0x02%x\n", rt_sts); |
| return 0; |
| } |
| |
| static int die_temp_irq_handler(struct smb23x_chip *chip, u8 rt_sts) |
| { |
| pr_warn("rt_sts = 0x02%x\n", rt_sts); |
| return 0; |
| } |
| |
| static struct irq_handler_info handlers[] = { |
| { IRQ_A_STATUS_REG, 0, 0, |
| { |
| { |
| .name = "cold_soft", |
| .smb_irq = cold_soft_irq_handler, |
| }, |
| { |
| .name = "hot_soft", |
| .smb_irq = hot_soft_irq_handler, |
| }, |
| { |
| .name = "cold_hard", |
| .smb_irq = cold_hard_irq_handler, |
| }, |
| { |
| .name = "hot_hard", |
| .smb_irq = hot_hard_irq_handler, |
| }, |
| }, |
| }, |
| { IRQ_B_STATUS_REG, 0, 0, |
| { |
| { |
| .name = "p2f", |
| .smb_irq = pre_to_fast_irq_handler, |
| }, |
| { |
| .name = "batt_low", |
| .smb_irq = batt_low_irq_handler, |
| }, |
| { |
| .name = "batt_missing", |
| .smb_irq = batt_missing_irq_handler, |
| }, |
| { |
| .name = "batt_ov", |
| .smb_irq = batt_ov_irq_handler, |
| }, |
| }, |
| }, |
| { IRQ_C_STATUS_REG, 0, 0, |
| { |
| { |
| .name = "iterm", |
| .smb_irq = iterm_irq_handler, |
| }, |
| { |
| .name = "taper", |
| .smb_irq = taper_irq_handler, |
| }, |
| { |
| .name = "recharge", |
| .smb_irq = recharge_irq_handler, |
| }, |
| { |
| .name = "chg_error", |
| .smb_irq = chg_error_irq_handler, |
| }, |
| }, |
| }, |
| { IRQ_D_STATUS_REG, 0, 0, |
| { |
| { |
| .name = "pre_chg_timeout", |
| .smb_irq = pre_chg_timeout_irq_handler, |
| }, |
| { |
| .name = "chg_timeout", |
| .smb_irq = chg_timeout_irq_handler, |
| }, |
| { |
| .name = "aicl_done", |
| .smb_irq = aicl_done_irq_handler, |
| }, |
| { |
| .name = "src_detect", |
| .smb_irq = src_detect_irq_handler, |
| }, |
| }, |
| }, |
| { CHG_STATUS_A_REG, 0, 0, |
| { |
| { |
| .name = "die_temp", |
| .smb_irq = die_temp_irq_handler, |
| }, |
| { |
| .name = "power_ok", |
| .smb_irq = power_ok_irq_handler, |
| }, |
| { |
| .name = "usbin_uv", |
| .smb_irq = usbin_uv_irq_handler, |
| }, |
| { |
| .name = "usbin_ov", |
| .smb_irq = usbin_ov_irq_handler, |
| }, |
| }, |
| }, |
| }; |
| |
| #define UPDATE_IRQ_STAT(irq_reg, value) \ |
| handlers[irq_reg - IRQ_A_STATUS_REG].prev_val = value |
| static int smb23x_determine_initial_status(struct smb23x_chip *chip) |
| { |
| int rc = 0; |
| u8 reg; |
| |
| rc = smb23x_read(chip, IRQ_A_STATUS_REG, ®); |
| if (rc < 0) { |
| pr_err("Read IRQ_A failed, rc=%d\n", rc); |
| return rc; |
| } |
| UPDATE_IRQ_STAT(IRQ_A_STATUS_REG, reg); |
| if (reg & HOT_HARD_BIT) |
| chip->batt_hot = true; |
| else if (reg & COLD_HARD_BIT) |
| chip->batt_cold = true; |
| else if (reg & HOT_SOFT_BIT) |
| chip->batt_warm = true; |
| else if (reg & COLD_SOFT_BIT) |
| chip->batt_cool = true; |
| |
| chip->batt_present = true; |
| rc = smb23x_read(chip, IRQ_B_STATUS_REG, ®); |
| if (rc < 0) { |
| pr_err("Read IRQ_B failed, rc=%d\n", rc); |
| return rc; |
| } |
| UPDATE_IRQ_STAT(IRQ_B_STATUS_REG, reg); |
| if (reg & BATT_ABSENT_BIT) |
| chip->batt_present = false; |
| |
| rc = smb23x_read(chip, IRQ_C_STATUS_REG, ®); |
| if (rc < 0) { |
| pr_err("Read IRQ_C failed, rc=%d\n", rc); |
| return rc; |
| } |
| UPDATE_IRQ_STAT(IRQ_C_STATUS_REG, reg); |
| if (reg & ITERM_BIT) |
| chip->batt_full = true; |
| |
| rc = smb23x_read(chip, IRQ_D_STATUS_REG, ®); |
| if (rc < 0) { |
| pr_err("Read IRQ_D failed, rc=%d\n", rc); |
| return rc; |
| } |
| UPDATE_IRQ_STAT(IRQ_D_STATUS_REG, reg); |
| if (chip->apsd_enabled && (reg & APSD_DONE_BIT)) |
| chip->usb_present = true; |
| |
| rc = smb23x_read(chip, CHG_STATUS_A_REG, ®); |
| if (rc < 0) { |
| pr_err("Read CHG_STATUS_A failed, rc=%d\n", rc); |
| return rc; |
| } |
| UPDATE_IRQ_STAT(CHG_STATUS_A_REG, reg); |
| if (!(reg & USBIN_OV_BIT) && !(reg & USBIN_UV_BIT)) |
| chip->usb_present = true; |
| else if (reg & USBIN_OV_BIT) |
| power_supply_set_health_state(chip->usb_psy, |
| POWER_SUPPLY_HEALTH_OVERVOLTAGE); |
| |
| if (chip->usb_present && chip->apsd_enabled) { |
| handle_usb_insertion(chip); |
| } else if (chip->usb_present) { |
| power_supply_set_present(chip->usb_psy, chip->usb_present); |
| reconfig_upon_unplug(chip); |
| } |
| |
| return rc; |
| } |
| |
| static int smb23x_irq_read(struct smb23x_chip *chip) |
| { |
| int rc, i; |
| |
| for (i = 0; i < ARRAY_SIZE(handlers); i++) { |
| rc = smb23x_read(chip, handlers[i].stat_reg, |
| &handlers[i].val); |
| if (rc < 0) { |
| pr_err("Couldn't read %d rc = %d\n", |
| handlers[i].stat_reg, rc); |
| handlers[i].val = 0; |
| continue; |
| } |
| } |
| |
| return rc; |
| } |
| |
| #define IRQ_LATCHED_MASK 0x02 |
| #define IRQ_STATUS_MASK 0x01 |
| #define BITS_PER_IRQ 2 |
| static irqreturn_t smb23x_stat_handler(int irq, void *dev_id) |
| { |
| struct smb23x_chip *chip = dev_id; |
| int i, j; |
| u8 triggered; |
| u8 changed; |
| u8 rt_stat, prev_rt_stat; |
| int rc; |
| int handler_count = 0; |
| |
| pr_debug("entering\n"); |
| mutex_lock(&chip->irq_complete); |
| chip->irq_waiting = true; |
| if (!chip->resume_completed) { |
| pr_debug("IRQ triggered before device-resume\n"); |
| disable_irq_nosync(irq); |
| mutex_unlock(&chip->irq_complete); |
| return IRQ_HANDLED; |
| } |
| chip->irq_waiting = false; |
| |
| smb23x_irq_read(chip); |
| for (i = 0; i < ARRAY_SIZE(handlers); i++) { |
| 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) |
| pr_err("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); |
| if (chip->usb_psy) { |
| pr_debug("usb psy changed\n"); |
| power_supply_changed(chip->usb_psy); |
| } |
| } |
| |
| mutex_unlock(&chip->irq_complete); |
| |
| return IRQ_HANDLED; |
| } |
| #endif //QTI_SMB231 |
| |
| #ifndef QTI_SMB231 |
| #define IRQ_POLLING_MS 3000 |
| static void smb23x_irq_polling_work_fn(struct work_struct *work) |
| { |
| struct smb23x_chip *chip = container_of(work, struct smb23x_chip, irq_polling_work.work); |
| int rc = 0; |
| u8 reg = 0; |
| |
| pr_debug("smb23x_irq_polling_work_fn+++(%d)\n", chip->charger_plugin); |
| |
| //check status |
| smb23x_check_batt_ov(chip); |
| smb23x_check_batt_ot(chip); |
| |
| if(chip->charger_plugin) |
| { |
| rc = smb23x_read(chip, CFG_REG_0, ®); |
| if(rc < 0) |
| { |
| pr_err("Failed reading CFG_REG_0, rc= %d\n", rc); |
| } |
| else |
| { |
| if(reg == REG0_DEFAULT) |
| { |
| pr_err("SMB registers have been reset somehow. Redo the initialization here."); |
| |
| rc = smb23x_hw_init(chip); |
| if(rc < 0) |
| { |
| pr_err("Initialize hardware failed!\n"); |
| } |
| } |
| } |
| |
| schedule_delayed_work(&chip->irq_polling_work, msecs_to_jiffies(IRQ_POLLING_MS)); |
| } |
| } |
| |
| static void reconfig_upon_unplug(struct smb23x_chip *chip) |
| { |
| pr_info("reconfig_upon_unplug+++(%d)\n", chip->charger_plugin); |
| |
| if(chip->charger_plugin) |
| { |
| smb23x_stay_awake(&chip->smb23x_ws, WAKEUP_SRC_IRQ_POLLING); |
| schedule_delayed_work(&chip->irq_polling_work, msecs_to_jiffies(IRQ_POLLING_MS)); |
| } |
| else |
| { |
| smb23x_relax(&chip->smb23x_ws, WAKEUP_SRC_IRQ_POLLING); |
| } |
| } |
| #endif |
| |
| static enum alarmtimer_restart smb23x_wpc_check_alarm_callback(struct alarm *alarm, ktime_t now) |
| { |
| struct smb23x_chip *chip = container_of(alarm, struct smb23x_chip, wpc_check_alarm); |
| |
| schedule_work(&chip->wpc_check_work); |
| |
| |
| return ALARMTIMER_NORESTART; |
| } |
| |
| static void smb23x_wpc_check_work(struct work_struct *work) |
| { |
| struct smb23x_chip *chip = container_of(work, struct smb23x_chip, wpc_check_work); |
| |
| ktime_t kt = {0}; |
| |
| bool bVal = gpio_is_valid(chip->eoc_gpio); |
| |
| int battery_ot = BATT_OVER_TEMP; |
| |
| int battery_voltage = smb23x_get_prop_batt_voltage(chip); |
| int battery_temperature = smb23x_get_prop_batt_temp(chip); |
| |
| pr_info("[smb23x] battery voltage = %d, temperature = %d, status = %d\n", battery_voltage, battery_temperature, g_BattStatus); |
| |
| |
| if(!bVal) |
| { |
| pr_err("[smb23x] eoc gpio is invalid\n"); |
| |
| kt = ns_to_ktime(TRIM_PERIOD_NS * 30 * 1); |
| alarm_start_relative(&chip->wpc_check_alarm, kt); |
| |
| return; |
| } |
| |
| |
| pr_info("[smb23x] eoc pin+(%d)\n", gpio_get_value(chip->eoc_gpio)); |
| |
| if(g_IsRetailMode) |
| { |
| battery_ot = BATT_OVER_TEMP_RE; |
| } |
| |
| if(g_BattStatus == STATUS_OT) |
| { |
| if (battery_temperature <= (battery_ot - 20)) |
| { |
| pr_info("[smb23x] No OT(<=%d), attempt to turn on the WPC\n", battery_ot); |
| |
| gpio_direction_output(chip->eoc_gpio, 0); |
| g_BattStatus = STATUS_NORMAL; |
| } |
| else |
| { |
| gpio_direction_output(chip->eoc_gpio, 1); |
| |
| kt = ns_to_ktime(TRIM_PERIOD_NS * 90 * 1); |
| alarm_start_relative(&chip->wpc_check_alarm, kt); |
| } |
| } |
| |
| if(g_BattStatus == STATUS_OV) |
| { |
| if (battery_voltage <= BATT_NO_OVER_VOLT) |
| { |
| pr_info("[smb23x] No OV, attempt to turn on the WPC\n"); |
| |
| gpio_direction_output(chip->eoc_gpio, 0); |
| g_BattStatus = STATUS_NORMAL; |
| } |
| else |
| { |
| gpio_direction_output(chip->eoc_gpio, 1); |
| |
| kt = ns_to_ktime(TRIM_PERIOD_NS * 60 * 1); |
| alarm_start_relative(&chip->wpc_check_alarm, kt); |
| } |
| } |
| |
| pr_info("[smb23x] eoc pin-(%d)\n", gpio_get_value(chip->eoc_gpio)); |
| |
| return; |
| } |
| |
| static enum power_supply_property smb23x_battery_properties[] = { |
| #ifdef QTI_SMB231 |
| POWER_SUPPLY_PROP_HEALTH, |
| POWER_SUPPLY_PROP_STATUS, |
| POWER_SUPPLY_PROP_PRESENT, |
| POWER_SUPPLY_PROP_CHARGING_ENABLED, |
| POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED, |
| POWER_SUPPLY_PROP_CHARGE_TYPE, |
| POWER_SUPPLY_PROP_CAPACITY, |
| POWER_SUPPLY_PROP_TEMP, |
| POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL, |
| #else |
| POWER_SUPPLY_PROP_HEALTH, |
| POWER_SUPPLY_PROP_STATUS, |
| POWER_SUPPLY_PROP_PRESENT, |
| POWER_SUPPLY_PROP_CHARGING_ENABLED, |
| POWER_SUPPLY_PROP_CHARGE_TYPE, |
| POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, |
| POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, |
| POWER_SUPPLY_PROP_RESISTANCE, |
| POWER_SUPPLY_PROP_CAPACITY, |
| POWER_SUPPLY_PROP_VOLTAGE_NOW, |
| POWER_SUPPLY_PROP_CURRENT_NOW, |
| POWER_SUPPLY_PROP_TEMP, |
| POWER_SUPPLY_PROP_CHARGE_ENABLED, |
| POWER_SUPPLY_PROP_RESTRICTED_CHARGING |
| #endif |
| }; |
| |
| static int smb23x_get_prop_batt_health(struct smb23x_chip *chip) |
| { |
| int health; |
| |
| if (chip->batt_hot) |
| health = POWER_SUPPLY_HEALTH_OVERHEAT; |
| else if (chip->batt_cold) |
| health = POWER_SUPPLY_HEALTH_COLD; |
| else if (chip->batt_warm) |
| health = POWER_SUPPLY_HEALTH_WARM; |
| else if (chip->batt_cool) |
| health = POWER_SUPPLY_HEALTH_COOL; |
| else |
| health = POWER_SUPPLY_HEALTH_GOOD; |
| |
| return health; |
| } |
| |
| static int smb23x_get_prop_batt_status(struct smb23x_chip *chip) |
| { |
| if (chip->batt_full) |
| { |
| return POWER_SUPPLY_STATUS_FULL; |
| } |
| |
| return (chip->charger_plugin == 1) ? POWER_SUPPLY_STATUS_CHARGING : POWER_SUPPLY_STATUS_DISCHARGING; |
| } |
| |
| #ifdef QTI_SMB231 |
| static int smb23x_get_prop_battery_charging_enabled(struct smb23x_chip *chip) |
| { |
| int rc, status; |
| u8 tmp; |
| |
| rc = smb23x_read(chip, CHG_STATUS_B_REG, &tmp); |
| if (rc < 0) { |
| pr_err("Read STATUS_B failed, rc=%d\n", rc); |
| return !chip->chg_disabled_status; |
| } |
| |
| status = tmp & CHARGE_EN_STS_BIT; |
| if (status && !chip->chg_disabled_status) |
| return 1; |
| else |
| return 0; |
| } |
| #endif //QTI_SMB231 |
| |
| static int smb23x_get_prop_charging_enabled(struct smb23x_chip *chip) |
| { |
| int rc; |
| u8 reg = 0; |
| |
| if (chip->charger_plugin == 0) { |
| pr_debug("charger_plugin = 0\n"); |
| return 0; |
| } |
| |
| rc = smb23x_read(chip, CHG_STATUS_B_REG, ®); |
| if (rc) { |
| pr_err("CHG_STATUS_B_REG read fail. rc=%d\n", rc); |
| return 0; |
| } |
| |
| return (reg & CHARGE_EN_STS_BIT) ? 1 : 0; |
| } |
| |
| static int smb23x_get_prop_batt_present(struct smb23x_chip *chip) |
| { |
| return true; |
| } |
| |
| static int smb23x_get_prop_charge_type(struct smb23x_chip *chip) |
| { |
| int rc; |
| u8 reg = 0; |
| |
| if (chip->charger_plugin == 0) { |
| pr_debug("charger_plugin = 0\n"); |
| return POWER_SUPPLY_CHARGE_TYPE_NONE; |
| } |
| |
| rc = smb23x_read(chip, CHG_STATUS_B_REG, ®); |
| if (rc) { |
| pr_err("CHG_STATUS_B_REG read fail. rc=%d\n", rc); |
| return POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; |
| } |
| |
| reg &= CHARGE_TYPE_MASK; |
| |
| if (reg == TAPER_CHARGE_VAL) |
| return POWER_SUPPLY_CHARGE_TYPE_TAPER; |
| else if (reg == FAST_CHARGE_VAL) |
| return POWER_SUPPLY_CHARGE_TYPE_FAST; |
| else if (reg == PRE_CHARGE_VAL) |
| return POWER_SUPPLY_CHARGE_TYPE_TRICKLE; |
| else |
| return POWER_SUPPLY_CHARGE_TYPE_NONE; |
| } |
| |
| #define DEFAULT_BATT_VOLTAGE 3700 |
| static int smb23x_get_prop_batt_voltage(struct smb23x_chip *chip) |
| { |
| union power_supply_propval ret = {0, }; |
| |
| chip->bms_psy = power_supply_get_by_name((char *)chip->bms_psy_name); |
| if (chip->bms_psy == NULL) |
| pr_err("smb23x can't find bms device \n"); |
| |
| if (chip->bms_psy) { |
| chip->bms_psy->get_property(chip->bms_psy, |
| POWER_SUPPLY_PROP_VOLTAGE_NOW, &ret); |
| return ret.intval; |
| } |
| return DEFAULT_BATT_VOLTAGE; |
| } |
| |
| #define DEFAULT_BATT_CURRENT 0 |
| static int smb23x_get_prop_batt_current(struct smb23x_chip *chip) |
| { |
| union power_supply_propval ret = {0, }; |
| |
| chip->bms_psy = power_supply_get_by_name((char *)chip->bms_psy_name); |
| if (chip->bms_psy == NULL) |
| pr_err("smb23x can't find bms device \n"); |
| |
| if (chip->bms_psy) { |
| chip->bms_psy->get_property(chip->bms_psy, |
| POWER_SUPPLY_PROP_CURRENT_NOW, &ret); |
| return ret.intval; |
| } |
| return DEFAULT_BATT_CURRENT; |
| } |
| |
| #define DEFAULT_BATT_CAPACITY 60 |
| static int smb23x_get_prop_batt_capacity(struct smb23x_chip *chip) |
| { |
| union power_supply_propval ret = {0, }; |
| |
| chip->bms_psy = power_supply_get_by_name((char *)chip->bms_psy_name); |
| if (chip->bms_psy == NULL) |
| pr_err("smb23x can't find bms device \n"); |
| |
| if (chip->fake_battery_soc != -EINVAL) |
| return chip->fake_battery_soc; |
| |
| if (chip->bms_psy) { |
| chip->bms_psy->get_property(chip->bms_psy, |
| POWER_SUPPLY_PROP_CAPACITY, &ret); |
| return ret.intval; |
| } |
| |
| return DEFAULT_BATT_CAPACITY; |
| } |
| |
| #define DEFAULT_BATT_TEMP 280 |
| static int smb23x_get_prop_batt_temp(struct smb23x_chip *chip) |
| { |
| union power_supply_propval ret = {0, }; |
| |
| chip->bms_psy = power_supply_get_by_name((char *)chip->bms_psy_name); |
| if (chip->bms_psy == NULL) |
| pr_err("smb23x can't find bms device \n"); |
| |
| if (chip->bms_psy) { |
| chip->bms_psy->get_property(chip->bms_psy, |
| POWER_SUPPLY_PROP_TEMP, &ret); |
| |
| if (chip->cfg_cool_temp_comp_mv != -EINVAL) |
| check_charger_thermal_state(chip, ret.intval); |
| chip->last_temp = ret.intval; |
| |
| return ret.intval; |
| } |
| |
| return DEFAULT_BATT_TEMP; |
| } |
| #ifdef QTI_SMB231 |
| static int smb23x_system_temp_level_set(struct smb23x_chip *chip, int lvl_sel) |
| { |
| int rc = 0; |
| |
| if (!chip->cfg_thermal_mitigation) { |
| pr_err("Thermal mitigation not supported\n"); |
| return (-EINVAL); |
| } |
| |
| if (lvl_sel < 0) { |
| pr_err("Unsupported level selected %d\n", lvl_sel); |
| return (-EINVAL); |
| } |
| |
| if (lvl_sel >= chip->thermal_levels) { |
| pr_err("Unsupported level selected %d forcing %d\n", lvl_sel, |
| chip->thermal_levels - 1); |
| lvl_sel = chip->thermal_levels - 1; |
| } |
| |
| if (lvl_sel == chip->therm_lvl_sel) |
| return 0; |
| |
| mutex_lock(&chip->icl_set_lock); |
| chip->therm_lvl_sel = lvl_sel; |
| |
| rc = smb23x_set_appropriate_usb_current(chip); |
| if (rc) |
| pr_err("Couldn't set USB current rc = %d\n", rc); |
| |
| mutex_unlock(&chip->icl_set_lock); |
| return rc; |
| } |
| #endif //QTI_SMB231 |
| |
| static int smb23x_print_register(struct smb23x_chip *chip) |
| { |
| int rc; |
| u8 reg, addr = 0; |
| |
| pr_err("Enter !\n"); |
| for(addr = CFG_REG_0 ; addr <= I2C_COMM_CFG_REG ; addr++) { |
| reg = 0; |
| rc = smb23x_read(chip, addr, ®); |
| if (rc) { |
| pr_err("Read fail. addr=0x%02x\n", addr); |
| return rc; |
| } else { |
| pr_err("Reg 0x%02x=0x%02x\n", addr, reg); |
| } |
| } |
| |
| for(addr = CMD_REG_0 ; addr <= CMD_REG_1 ; addr++) { |
| reg = 0; |
| rc = smb23x_read(chip, addr, ®); |
| if (rc) { |
| pr_err("Read fail. addr=0x%02x\n", addr); |
| return rc; |
| } else { |
| pr_err("Reg 0x%02x=0x%02x\n", addr, reg); |
| } |
| } |
| |
| for(addr = IRQ_A_STATUS_REG ; addr <= AICL_STATUS_REG ; addr++) { |
| reg = 0; |
| rc = smb23x_read(chip, addr, ®); |
| if (rc) { |
| pr_err("Read fail. addr=0x%02x\n", addr); |
| return rc; |
| } else { |
| pr_err("Reg 0x%02x=0x%02x\n", addr, reg); |
| } |
| } |
| |
| return 0; |
| } |
| |
| void smb23x_timer_init_register(unsigned long dev) |
| { |
| queue_delayed_work(g_chip->workqueue, &g_chip->delaywork_init_register, 0); |
| } |
| |
| void smb23x_delaywork_init_register(struct work_struct *work) |
| { |
| int rc; |
| |
| if (g_chip->charger_plugin == 0xFF) { |
| rc = smb23x_enable_volatile_writes(g_chip); |
| g_chip->charger_plugin = (rc < 0) ? 0 : 1; |
| } |
| |
| rc = smb23x_hw_init(g_chip); |
| power_supply_changed(g_chip->usb_psy); |
| if (rc < 0) { |
| pr_err("Initialize register failed!\n"); |
| } else { |
| rc = smb23x_print_register(g_chip); |
| if (rc < 0) |
| pr_err("print register failed!\n"); |
| del_timer(&g_chip->timer_print_register); |
| g_chip->timer_print_register.expires = jiffies + 30*HZ; |
| add_timer(&g_chip->timer_print_register); |
| g_chip->reg_print_count = 0; |
| } |
| } |
| |
| void smb23x_timer_print_register(unsigned long dev) |
| { |
| queue_delayed_work(g_chip->workqueue, &g_chip->delaywork_print_register, 0); |
| } |
| |
| void smb23x_delaywork_print_register(struct work_struct *work) |
| { |
| int rc; |
| |
| pr_err("Enter !\n"); |
| rc = smb23x_print_register(g_chip); |
| if (rc < 0) |
| pr_err("print register failed!\n"); |
| |
| if (g_chip->reg_print_count < 3) { |
| g_chip->timer_print_register.expires = jiffies + 30*HZ; |
| add_timer(&g_chip->timer_print_register); |
| g_chip->reg_print_count++; |
| } |
| } |
| |
| void smb23x_delaywork_usb_removal(struct work_struct *work) |
| { |
| int rc = 0; |
| u8 reg = 0x00; |
| |
| pr_info("[smb23x] smb23x_delaywork_usb_removal+++\n"); |
| |
| if (!gpio_is_valid(g_chip->susp_gpio)) |
| { |
| return; |
| } |
| |
| pr_info("[smb23x] susp pin+(%d)\n", gpio_get_value(g_chip->susp_gpio)); |
| |
| |
| rc = smb23x_masked_write(g_chip, CMD_REG_0, RESET_BIT, RESET_BIT); |
| if (rc < 0) |
| { |
| pr_err("[smb23x] failed to reset\n"); |
| } |
| |
| msleep(150); |
| |
| rc = smb23x_read(g_chip, CHG_STATUS_A_REG, ®); |
| if (rc < 0) |
| { |
| pr_err("[smb23x] failed to read CHG_STATUS_A, rc=%d\n", rc); |
| } |
| |
| if (reg & POWER_OK_BIT) |
| { |
| pr_info("[smb23x] POWER_OK\n"); |
| } |
| |
| gpio_direction_output(g_chip->susp_gpio, 0); |
| |
| pr_info("[smb23x] susp pin-(%d)\n", gpio_get_value(g_chip->susp_gpio)); |
| |
| return; |
| } |
| |
| static void smb23x_boot_check_work(struct work_struct *work) |
| { |
| g_BootPhase = 0; |
| |
| pr_info("[smb23x] g_BootPhase(%d)\n", g_BootPhase); |
| } |
| |
| static int smb23x_battery_get_property(struct power_supply *psy, |
| enum power_supply_property prop, |
| union power_supply_propval *val) |
| { |
| struct smb23x_chip *chip = container_of(psy, |
| struct smb23x_chip, batt_psy); |
| int rc; |
| u8 reg = 0; |
| |
| switch (prop) { |
| #ifdef QTI_SMB231 |
| case POWER_SUPPLY_PROP_HEALTH: |
| val->intval = smb23x_get_prop_batt_health(chip); |
| break; |
| case POWER_SUPPLY_PROP_STATUS: |
| val->intval = smb23x_get_prop_batt_status(chip); |
| break; |
| case POWER_SUPPLY_PROP_PRESENT: |
| val->intval = smb23x_get_prop_batt_present(chip); |
| break; |
| case POWER_SUPPLY_PROP_CHARGING_ENABLED: |
| val->intval = smb23x_get_prop_charging_enabled(chip); |
| break; |
| case POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED: |
| val->intval = smb23x_get_prop_battery_charging_enabled(chip); |
| break; |
| case POWER_SUPPLY_PROP_CHARGE_TYPE: |
| val->intval = smb23x_get_prop_charge_type(chip); |
| break; |
| case POWER_SUPPLY_PROP_CAPACITY: |
| val->intval = smb23x_get_prop_batt_capacity(chip); |
| break; |
| case POWER_SUPPLY_PROP_TEMP: |
| val->intval = smb23x_get_prop_batt_temp(chip); |
| break; |
| case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL: |
| val->intval = chip->therm_lvl_sel; |
| break; |
| #else |
| case POWER_SUPPLY_PROP_HEALTH: |
| val->intval = smb23x_get_prop_batt_health(chip); |
| break; |
| case POWER_SUPPLY_PROP_STATUS: |
| val->intval = smb23x_get_prop_batt_status(chip); |
| break; |
| case POWER_SUPPLY_PROP_PRESENT: |
| val->intval = smb23x_get_prop_batt_present(chip); |
| break; |
| case POWER_SUPPLY_PROP_CHARGING_ENABLED: |
| val->intval = smb23x_get_prop_charging_enabled(chip); |
| break; |
| case POWER_SUPPLY_PROP_CHARGE_TYPE: |
| val->intval = smb23x_get_prop_charge_type(chip); |
| break; |
| case POWER_SUPPLY_PROP_RESISTANCE: |
| rc = smb23x_read(chip, CFG_REG_0, ®); |
| if (rc) |
| val->intval = 0x00; |
| else |
| val->intval = reg; |
| pr_err("RESISTANCE 0x00=0x%02x\n", reg); |
| break; |
| #endif |
| case POWER_SUPPLY_PROP_VOLTAGE_NOW: |
| val->intval = smb23x_get_prop_batt_voltage(chip); |
| break; |
| case POWER_SUPPLY_PROP_CURRENT_NOW: |
| val->intval = smb23x_get_prop_batt_current(chip); |
| break; |
| case POWER_SUPPLY_PROP_CAPACITY: |
| val->intval = smb23x_get_prop_batt_capacity(chip); |
| break; |
| case POWER_SUPPLY_PROP_TEMP: |
| val->intval = smb23x_get_prop_batt_temp(chip); |
| udp_sendmsg_tempature(val->intval); |
| break; |
| case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: |
| val->intval = chip->reg_addr; |
| smb23x_print_register(chip); |
| break; |
| case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: |
| val->intval = val->intval; |
| break; |
| case POWER_SUPPLY_PROP_RESTRICTED_CHARGING: |
| val->intval = g_IsRetailMode; |
| break; |
| default: |
| return (-EINVAL); |
| } |
| pr_debug("get_property: prop(%d) = %d\n", (int)prop, (int)val->intval); |
| |
| return 0; |
| } |
| |
| static int smb23x_battery_set_property(struct power_supply *psy, |
| enum power_supply_property prop, |
| const union power_supply_propval *val) |
| { |
| struct smb23x_chip *chip = container_of(psy, |
| struct smb23x_chip, batt_psy); |
| #ifdef QTI_SMB231 |
| int rc; |
| #endif |
| pr_debug("set_property: prop(%d) = %d\n", (int)prop, (int)val->intval); |
| |
| switch (prop) { |
| #ifdef QTI_SMB231 |
| case POWER_SUPPLY_PROP_STATUS: |
| if (!chip->cfg_bms_controlled_charging) |
| return (-EINVAL); |
| |
| switch (val->intval) { |
| case POWER_SUPPLY_STATUS_FULL: |
| pr_debug("BMS notify: battery FULL!\n"); |
| chip->batt_full = true; |
| rc = smb23x_charging_disable(chip, BMS, true); |
| if (rc < 0) { |
| pr_err("Disable charging for BMS failed, rc=%d\n", |
| rc); |
| return rc; |
| } |
| break; |
| case POWER_SUPPLY_STATUS_CHARGING: |
| pr_debug("BMS notify: battery CHARGING!\n"); |
| chip->batt_full = false; |
| rc = smb23x_charging_disable(chip, BMS, false); |
| if (rc < 0) { |
| pr_err("Enable charging for BMS failed, rc=%d\n", |
| rc); |
| return rc; |
| } |
| break; |
| case POWER_SUPPLY_STATUS_DISCHARGING: |
| pr_debug("BMS notify: battery DISCHARGING!\n"); |
| chip->batt_full = false; |
| break; |
| default: |
| break; |
| } |
| case POWER_SUPPLY_PROP_CHARGING_ENABLED: |
| smb23x_suspend_usb(chip, USER, !val->intval); |
| power_supply_changed(&chip->batt_psy); |
| power_supply_changed(chip->usb_psy); |
| break; |
| case POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED: |
| smb23x_charging_disable(chip, USER, !val->intval); |
| power_supply_changed(&chip->batt_psy); |
| break; |
| case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL: |
| smb23x_system_temp_level_set(chip, val->intval); |
| break; |
| case POWER_SUPPLY_PROP_CAPACITY: |
| chip->fake_battery_soc = val->intval; |
| power_supply_changed(&chip->batt_psy); |
| break; |
| #else //QTI_SMB231 |
| case POWER_SUPPLY_PROP_STATUS: |
| chip->charger_plugin = val->intval; |
| del_timer(&chip->timer_init_register); |
| del_timer(&chip->timer_print_register); |
| cancel_delayed_work(&chip->delaywork_usb_removal); |
| |
| if (chip->charger_plugin) { |
| chip->timer_init_register.expires = jiffies + HZ; |
| add_timer(&chip->timer_init_register); |
| } |
| if(!chip->charger_plugin) |
| { |
| queue_delayed_work(g_chip->workqueue, &chip->delaywork_usb_removal, 0); |
| } |
| |
| reconfig_upon_unplug(chip); |
| |
| pr_info("Charger plug, state=%d\n", chip->charger_plugin); |
| power_supply_changed(chip->usb_psy); |
| udp_sendmsg_charging(chip->charger_plugin); |
| break; |
| case POWER_SUPPLY_PROP_CHARGING_ENABLED: |
| smb23x_charging_enable(chip, val->intval); |
| break; |
| case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: |
| //Register addr |
| chip->reg_addr = val->intval; |
| break; |
| case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: |
| //Register value |
| smb23x_write(chip, chip->reg_addr, val->intval); |
| break; |
| |
| case POWER_SUPPLY_PROP_CHARGE_ENABLED: |
| { |
| if(gpio_is_valid(chip->susp_gpio)) |
| { |
| if(!val->intval) |
| { |
| gpio_direction_output(chip->susp_gpio, 0); |
| } |
| else |
| { |
| gpio_direction_output(chip->susp_gpio, 1); |
| } |
| } |
| |
| break; |
| } |
| |
| case POWER_SUPPLY_PROP_RESTRICTED_CHARGING: |
| { |
| if(val->intval) |
| { |
| g_IsRetailMode = true; |
| } |
| else |
| { |
| g_IsRetailMode = false; |
| } |
| |
| break; |
| } |
| #endif //QTI_SMB231 |
| default: |
| return (-EINVAL); |
| } |
| |
| return 0; |
| } |
| |
| static int smb23x_battery_is_writeable(struct power_supply *psy, |
| enum power_supply_property prop) |
| { |
| int rc; |
| |
| switch (prop) { |
| case POWER_SUPPLY_PROP_CHARGING_ENABLED: |
| #ifdef QTI_SMB231 |
| case POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED: |
| case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL: |
| case POWER_SUPPLY_PROP_CAPACITY: |
| #else |
| case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: |
| case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: |
| case POWER_SUPPLY_PROP_RESTRICTED_CHARGING: |
| #endif //QTI_SMB231 |
| rc = 1; |
| break; |
| default: |
| rc = 0; |
| break; |
| } |
| return rc; |
| } |
| |
| #ifdef QTI_SMB231 |
| static void smb23x_external_power_changed(struct power_supply *psy) |
| { |
| struct smb23x_chip *chip = container_of(psy, |
| struct smb23x_chip, batt_psy); |
| union power_supply_propval prop = {0,}; |
| int icl = 0, rc; |
| bool online; |
| |
| if (chip->bms_psy_name && chip->bms_psy == NULL) |
| 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 < 0) |
| pr_err("Get CURRENT_MAX from usb failed, rc=%d\n", rc); |
| else |
| icl = prop.intval / 1000; |
| pr_debug("current_limit = %d\n", icl); |
| |
| if (chip->usb_psy_ma != icl) { |
| mutex_lock(&chip->icl_set_lock); |
| chip->usb_psy_ma = icl; |
| rc = smb23x_set_appropriate_usb_current(chip); |
| mutex_unlock(&chip->icl_set_lock); |
| if (rc < 0) |
| pr_err("Set appropriate current failed, rc=%d\n", rc); |
| } |
| |
| rc = chip->usb_psy->get_property(chip->usb_psy, |
| POWER_SUPPLY_PROP_ONLINE, &prop); |
| if (rc < 0) |
| pr_err("Get ONLINE from usb failed, rc=%d\n", rc); |
| else |
| online = !!prop.intval; |
| |
| if (chip->usb_present && chip->usb_psy_ma != 0) { |
| if (!online && !chip->apsd_enabled) |
| power_supply_set_online(chip->usb_psy, true); |
| } else if (online && !chip->apsd_enabled) { |
| power_supply_set_online(chip->usb_psy, false); |
| } |
| } |
| |
| #define LAST_CNFG_REG 0x1F |
| static int show_cnfg_regs(struct seq_file *m, void *data) |
| { |
| struct smb23x_chip *chip = m->private; |
| int rc; |
| u8 reg; |
| u8 addr; |
| |
| for (addr = CFG_REG_0; addr <= I2C_COMM_CFG_REG; addr++) { |
| rc = smb23x_read(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 smb23x_chip *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, |
| }; |
| |
| static int show_cmd_regs(struct seq_file *m, void *data) |
| { |
| struct smb23x_chip *chip = m->private; |
| int rc; |
| u8 reg; |
| u8 addr; |
| |
| for (addr = CMD_REG_0; addr <= CMD_REG_1; addr++) { |
| rc = smb23x_read(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 smb23x_chip *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, |
| }; |
| |
| static int show_status_regs(struct seq_file *m, void *data) |
| { |
| struct smb23x_chip *chip = m->private; |
| int rc; |
| u8 reg; |
| u8 addr; |
| |
| for (addr = IRQ_A_STATUS_REG; addr <= AICL_STATUS_REG; addr++) { |
| rc = smb23x_read(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 smb23x_chip *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 smb23x_chip *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 smb23x_chip *chip = data; |
| int rc; |
| u8 temp; |
| |
| rc = smb23x_read(chip, chip->peek_poke_address, &temp); |
| if (rc < 0) { |
| pr_err("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 smb23x_chip *chip = data; |
| int rc; |
| u8 temp; |
| |
| temp = (u8) val; |
| rc = smb23x_write(chip, chip->peek_poke_address, temp); |
| if (rc < 0) { |
| pr_err("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 smb23x_chip *chip = data; |
| |
| smb23x_stat_handler(chip->client->irq, data); |
| return 0; |
| } |
| DEFINE_SIMPLE_ATTRIBUTE(force_irq_ops, NULL, force_irq_set, "0x%02llx\n"); |
| |
| static int create_debugfs_entries(struct smb23x_chip *chip) |
| { |
| chip->debug_root = debugfs_create_dir("smb23x", NULL); |
| if (!chip->debug_root) |
| pr_err("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) |
| pr_err("Couldn't create cnfg debug file\n"); |
| |
| ent = debugfs_create_file("status_registers", S_IFREG | S_IRUGO, |
| chip->debug_root, chip, |
| &status_debugfs_ops); |
| if (!ent) |
| pr_err("Couldn't create status debug file\n"); |
| |
| ent = debugfs_create_file("cmd_registers", S_IFREG | S_IRUGO, |
| chip->debug_root, chip, |
| &cmd_debugfs_ops); |
| if (!ent) |
| pr_err("Couldn't create cmd debug file\n"); |
| |
| ent = debugfs_create_x32("address", S_IFREG | S_IWUSR | S_IRUGO, |
| chip->debug_root, |
| &(chip->peek_poke_address)); |
| if (!ent) |
| pr_err("Couldn't create address debug file\n"); |
| |
| ent = debugfs_create_file("data", S_IFREG | S_IWUSR | S_IRUGO, |
| chip->debug_root, chip, |
| &poke_poke_debug_ops); |
| if (!ent) |
| pr_err("Couldn't create data debug file\n"); |
| |
| ent = debugfs_create_file("force_irq", |
| S_IFREG | S_IWUSR | S_IRUGO, |
| chip->debug_root, chip, |
| &force_irq_ops); |
| if (!ent) |
| pr_err("Couldn't create force_irq debug file\n"); |
| |
| ent = debugfs_create_file("irq_count", S_IFREG | S_IRUGO, |
| chip->debug_root, chip, |
| &irq_count_debugfs_ops); |
| if (!ent) |
| pr_err("Couldn't create irq_count debug file\n"); |
| } |
| return 0; |
| } |
| |
| static char *batt_supplied_to[] = { |
| "bms", |
| }; |
| |
| static void smb23x_irq_polling_wa_check(struct smb23x_chip *chip) |
| { |
| int rc; |
| u8 reg; |
| |
| rc = smb23x_read(chip, NV_CFG_REG, ®); |
| if (rc) { |
| pr_err("Read NV_CFG_REG failed, rc=%d\n", rc); |
| return; |
| } |
| |
| if (!(reg & UNPLUG_RELOAD_DIS_BIT)) |
| chip->workaround_flags |= WRKRND_IRQ_POLLING; |
| |
| pr_debug("use polling: %d\n", !(reg & UNPLUG_RELOAD_DIS_BIT)); |
| } |
| #else |
| static char *batt_supplied_to[] = { |
| "bms", |
| }; |
| #endif |
| |
| static int smb23x_probe(struct i2c_client *client, |
| const struct i2c_device_id *id) |
| { |
| int rc; |
| struct power_supply *usb_psy; |
| struct smb23x_chip *chip; |
| |
| usb_psy = power_supply_get_by_name("usb"); |
| if (!usb_psy) { |
| dev_dbg(&client->dev, "USB supply not found; defer probe\n"); |
| return (-EPROBE_DEFER); |
| } |
| |
| chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); |
| if (chip == NULL) |
| return (-ENOMEM); |
| |
| udp_init(); |
| |
| pr_err("Enter !\n"); |
| chip->client = client; |
| chip->dev = &client->dev; |
| chip->usb_psy = usb_psy; |
| chip->fake_battery_soc = -EINVAL; |
| i2c_set_clientdata(client, chip); |
| |
| mutex_init(&chip->read_write_lock); |
| #ifdef QTI_SMB231 |
| mutex_init(&chip->irq_complete); |
| mutex_init(&chip->chg_disable_lock); |
| mutex_init(&chip->usb_suspend_lock); |
| mutex_init(&chip->icl_set_lock); |
| smb23x_wakeup_src_init(chip); |
| INIT_DELAYED_WORK(&chip->irq_polling_work, smb23x_irq_polling_work_fn); |
| #else |
| mutex_init(&chip->chg_disable_lock); |
| |
| smb23x_wakeup_src_init(chip); |
| INIT_DELAYED_WORK(&chip->irq_polling_work, smb23x_irq_polling_work_fn); |
| #endif |
| |
| /* enable the USB_SUSPEND always */ |
| chip->workaround_flags |= WRKRND_USB_SUSPEND; |
| |
| rc = smb23x_parse_dt(chip); |
| if (rc < 0) { |
| pr_err("Parse DT nodes failed!\n"); |
| goto destroy_mutex; |
| } |
| |
| smb23x_check_gpio(chip); |
| |
| //Set timer to init register |
| g_chip = chip; |
| INIT_DELAYED_WORK(&chip->delaywork_init_register, smb23x_delaywork_init_register); |
| chip->workqueue = create_singlethread_workqueue("smb23x_workqueue"); |
| if (chip->workqueue == NULL) { |
| pr_err("failed to create work queue\n"); |
| goto destroy_mutex; |
| } |
| init_timer(&chip->timer_init_register); |
| chip->timer_init_register.function = smb23x_timer_init_register; |
| chip->timer_init_register.expires = jiffies + 10*HZ; |
| add_timer(&chip->timer_init_register); |
| |
| //Init variable |
| chip->charger_plugin = 0xFF; |
| chip->last_temp = DEFAULT_BATT_TEMP; |
| chip->index_soft_temp_comp_mv = NORMAL; |
| |
| //Set timer to print register value |
| INIT_DELAYED_WORK(&chip->delaywork_print_register, smb23x_delaywork_print_register); |
| init_timer(&chip->timer_print_register); |
| chip->timer_print_register.function = smb23x_timer_print_register; |
| |
| INIT_DELAYED_WORK(&chip->delaywork_usb_removal, smb23x_delaywork_usb_removal); |
| |
| INIT_DEFERRABLE_WORK(&chip->boot_check_work, smb23x_boot_check_work); |
| schedule_delayed_work(&chip->boot_check_work, 8500); |
| |
| alarm_init(&chip->wpc_check_alarm, ALARM_REALTIME, smb23x_wpc_check_alarm_callback); |
| INIT_WORK(&chip->wpc_check_work, smb23x_wpc_check_work); |
| |
| #ifdef QTI_SMB231 |
| /* |
| * Enable register based battery charging as the hw_init moves CHG_EN |
| * control from pin-based to register based. |
| */ |
| rc = smb23x_charging_disable(chip, USER, false); |
| if (rc < 0) { |
| pr_err("Register control based charging enable failed\n"); |
| goto destroy_mutex; |
| } |
| |
| rc = smb23x_hw_init(chip); |
| if (rc < 0) { |
| pr_err("Initialize hardware failed!\n"); |
| goto destroy_mutex; |
| } |
| |
| smb23x_irq_polling_wa_check(chip); |
| |
| /* |
| * Disable charging if device tree (USER) requested: |
| * set USB_SUSPEND to cutoff USB power completely |
| */ |
| rc = smb23x_suspend_usb(chip, USER, |
| chip->cfg_charging_disabled ? true : false); |
| if (rc < 0) { |
| pr_err("%suspend USB failed\n", |
| chip->cfg_charging_disabled ? "S" : "Un-s"); |
| goto destroy_mutex; |
| } |
| |
| rc = smb23x_determine_initial_status(chip); |
| if (rc < 0) { |
| pr_err("Update initial status failed\n"); |
| goto destroy_mutex; |
| } |
| #endif |
| |
| /* register battery power_supply */ |
| chip->batt_psy.name = "battery"; |
| chip->batt_psy.type = POWER_SUPPLY_TYPE_BATTERY; |
| chip->batt_psy.get_property = smb23x_battery_get_property; |
| chip->batt_psy.set_property = smb23x_battery_set_property; |
| chip->batt_psy.properties = smb23x_battery_properties; |
| chip->batt_psy.num_properties = ARRAY_SIZE(smb23x_battery_properties); |
| #ifdef QTI_SMB231 |
| chip->batt_psy.external_power_changed = smb23x_external_power_changed; |
| #endif |
| chip->batt_psy.property_is_writeable = smb23x_battery_is_writeable; |
| |
| if (chip->cfg_bms_controlled_charging) { |
| chip->batt_psy.supplied_to = batt_supplied_to; |
| chip->batt_psy.num_supplicants = |
| ARRAY_SIZE(batt_supplied_to); |
| } |
| |
| rc = power_supply_register(chip->dev, &chip->batt_psy); |
| if (rc < 0) { |
| pr_err("Register power_supply failed, rc = %d\n", rc); |
| goto destroy_mutex; |
| } |
| |
| #ifdef QTI_SMB231 |
| chip->resume_completed = true; |
| /* Register IRQ */ |
| if (client->irq) { |
| rc = devm_request_threaded_irq(&client->dev, client->irq, NULL, |
| smb23x_stat_handler, IRQF_ONESHOT, |
| "smb23x_stat_irq", chip); |
| if (rc < 0) { |
| pr_err("Request IRQ(%d) failed, rc = %d\n", |
| client->irq, rc); |
| goto unregister_batt_psy; |
| } |
| enable_irq_wake(client->irq); |
| } |
| |
| create_debugfs_entries(chip); |
| #endif |
| |
| pr_info("SMB23x successfully probed batt=%d usb = %d\n", |
| smb23x_get_prop_batt_present(chip), chip->usb_present); |
| |
| return 0; |
| #ifdef QTI_SMB231 |
| unregister_batt_psy: |
| power_supply_unregister(&chip->batt_psy); |
| destroy_mutex: |
| wakeup_source_trash(&chip->smb23x_ws.source); |
| mutex_destroy(&chip->read_write_lock); |
| mutex_destroy(&chip->irq_complete); |
| mutex_destroy(&chip->chg_disable_lock); |
| mutex_destroy(&chip->usb_suspend_lock); |
| mutex_destroy(&chip->icl_set_lock); |
| #else |
| destroy_mutex: |
| wakeup_source_trash(&chip->smb23x_ws.source); |
| |
| mutex_destroy(&chip->read_write_lock); |
| mutex_destroy(&chip->chg_disable_lock); |
| #endif |
| |
| return rc; |
| } |
| |
| static int smb23x_suspend(struct device *dev) |
| { |
| #ifdef QTI_SMB231 |
| struct i2c_client *client = to_i2c_client(dev); |
| struct smb23x_chip *chip = i2c_get_clientdata(client); |
| int rc; |
| |
| /* Save the current IRQ config */ |
| rc = smb23x_read(chip, IRQ_CFG_REG_9, &chip->irq_cfg_mask); |
| if (rc) |
| pr_err("Save irq config failed, rc=%d\n", rc); |
| |
| /* enable only important IRQs */ |
| rc = smb23x_write(chip, IRQ_CFG_REG_9, |
| BATT_MISSING_IRQ_EN_BIT | INOK_IRQ_EN_BIT); |
| if (rc < 0) |
| pr_err("Set irq_cfg failed, rc = %d\n", rc); |
| |
| mutex_lock(&chip->irq_complete); |
| chip->resume_completed = false; |
| mutex_unlock(&chip->irq_complete); |
| #else //QTI_SMB231 |
| pr_err("smb23x_suspend\n"); |
| #endif |
| return 0; |
| } |
| |
| #ifdef QTI_SMB231 |
| static int smb23x_suspend_noirq(struct device *dev) |
| { |
| struct i2c_client *client = to_i2c_client(dev); |
| struct smb23x_chip *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; |
| } |
| #endif |
| |
| static int smb23x_resume(struct device *dev) |
| { |
| #ifdef QTI_SMB231 |
| struct i2c_client *client = to_i2c_client(dev); |
| struct smb23x_chip *chip = i2c_get_clientdata(client); |
| int rc; |
| |
| rc = smb23x_write(chip, IRQ_CFG_REG_9, chip->irq_cfg_mask); |
| if (rc) |
| pr_err("Restore irq cfg reg failed, rc=%d\n", rc); |
| |
| mutex_lock(&chip->irq_complete); |
| chip->resume_completed = true; |
| if (chip->irq_waiting) { |
| mutex_unlock(&chip->irq_complete); |
| smb23x_stat_handler(client->irq, chip); |
| enable_irq(client->irq); |
| } else { |
| mutex_unlock(&chip->irq_complete); |
| } |
| #else //QTI_SMB231 |
| pr_err("smb23x_resume\n"); |
| #endif |
| return 0; |
| } |
| |
| static int smb23x_remove(struct i2c_client *client) |
| { |
| struct smb23x_chip *chip = i2c_get_clientdata(client); |
| |
| power_supply_unregister(&chip->batt_psy); |
| #ifdef QTI_SMB231 |
| wakeup_source_trash(&chip->smb23x_ws.source); |
| mutex_destroy(&chip->read_write_lock); |
| mutex_destroy(&chip->irq_complete); |
| mutex_destroy(&chip->chg_disable_lock); |
| mutex_destroy(&chip->usb_suspend_lock); |
| mutex_destroy(&chip->icl_set_lock); |
| #else |
| wakeup_source_trash(&chip->smb23x_ws.source); |
| |
| mutex_destroy(&chip->read_write_lock); |
| mutex_destroy(&chip->chg_disable_lock); |
| #endif |
| udp_exit(); |
| return 0; |
| } |
| |
| static const struct dev_pm_ops smb23x_pm_ops = { |
| .resume = smb23x_resume, |
| #ifdef QTI_SMB231 |
| .suspend_noirq = smb23x_suspend_noirq, |
| #endif |
| .suspend = smb23x_suspend, |
| }; |
| |
| static struct of_device_id smb23x_match_table[] = { |
| #ifdef QTI_SMB231 |
| { .compatible = "qcom,smb231-lbc",}, |
| { .compatible = "qcom,smb232-lbc",}, |
| { .compatible = "qcom,smb233-lbc",}, |
| #else |
| { .compatible = "qcom,smb231-charger",}, |
| #endif |
| { }, |
| }; |
| |
| static const struct i2c_device_id smb23x_id[] = { |
| {"smb23x-lbc", 0}, |
| {}, |
| }; |
| MODULE_DEVICE_TABLE(i2c, smb23x_id); |
| |
| static struct i2c_driver smb23x_driver = { |
| .driver = { |
| .name = "smb23x-lbc", |
| .owner = THIS_MODULE, |
| .of_match_table = smb23x_match_table, |
| .pm = &smb23x_pm_ops, |
| }, |
| .probe = smb23x_probe, |
| .remove = smb23x_remove, |
| .id_table = smb23x_id, |
| }; |
| |
| module_i2c_driver(smb23x_driver); |
| |
| MODULE_DESCRIPTION("SMB23x Linear Battery Charger"); |
| MODULE_LICENSE("GPL v2"); |
| MODULE_ALIAS("i2c:smb23x-lbc"); |