/* drivers/battery/sm5703_charger.c * SM5703 Charger Driver * * Copyright (C) 2016 Siliconmitus Technology Corp. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. */ #include #ifdef CONFIG_USB_HOST_NOTIFY #include #include #endif #include #ifdef CONFIG_SM5703_MUIC #include #endif #include //#ifdef CONFIG_FLED_SM5703 #include #include #include #include //#endif /* CONFIG_FLED_SM5703 */ #include #include #include #include #define EN_NOBAT_IRQ 0 #define EN_DONE_IRQ 1 #define EN_TOPOFF_IRQ 1 #define EN_CHGON_IRQ 0 #define EN_OTGFAIL_IRQ 1 #define EN_VBUSLIMIT_IRQ 0 #define EN_AICL_IRQ 1 #define DEFAULT_CHARGING_CURRENT 500 #define MINVAL(a, b) ((a <= b) ? a : b) #ifndef EN_TEST_READ #define EN_TEST_READ 1 #endif static int sm5703_reg_map[] = { SM5703_INTMSK1, SM5703_INTMSK2, SM5703_INTMSK3, SM5703_INTMSK4, SM5703_STATUS1, SM5703_STATUS2, SM5703_STATUS3, SM5703_STATUS4, SM5703_CNTL, SM5703_VBUSCNTL, SM5703_CHGCNTL1, SM5703_CHGCNTL2, SM5703_CHGCNTL3, SM5703_CHGCNTL4, SM5703_CHGCNTL5, SM5703_CHGCNTL6, SM5703_OTGCURRENTCNTL, SM5703_Q3LIMITCNTL, SM5703_STATUS5, }; typedef struct sm5703_charger_data { struct i2c_client *client; sm5703_mfd_chip_t *sm5703; struct power_supply psy_chg; struct power_supply psy_otg; sm5703_charger_platform_data_t *pdata; int charging_current; struct wake_lock vbuslimit_wake_lock; struct delayed_work vbuslimit_work; int current_max; bool is_current_reduced; int siop_level; int cable_type; bool is_charging; struct mutex io_lock; /* register programming */ int reg_addr; int reg_data; int nchgen; bool full_charged; bool ovp; bool is_mdock; struct workqueue_struct *wq; int status; #ifdef CONFIG_FLED_SM5703 struct sm_fled_info *fled_info; #endif /* CONFIG_FLED_SM5703 */ } sm5703_charger_data_t; static enum power_supply_property sec_charger_props[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_CHARGE_TYPE, POWER_SUPPLY_PROP_HEALTH, POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_ONLINE, POWER_SUPPLY_PROP_CURRENT_MAX, POWER_SUPPLY_PROP_CURRENT_AVG, POWER_SUPPLY_PROP_CURRENT_NOW, #if defined(CONFIG_BATTERY_SWELLING) || defined(CONFIG_BATTERY_SWELLING_SELF_DISCHARGING) POWER_SUPPLY_PROP_VOLTAGE_MAX, #endif POWER_SUPPLY_PROP_CHARGE_OTG_CONTROL, POWER_SUPPLY_PROP_ENERGY_NOW, }; static enum power_supply_property sm5703_otg_props[] = { POWER_SUPPLY_PROP_ONLINE, }; int otg_enable_flag; static int sm5703_get_charging_health( struct sm5703_charger_data *charger); static int sm5703_get_charging_status(struct sm5703_charger_data *charger); static void sm5703_read_regs(struct i2c_client *i2c, char *str) { u8 data = 0; int i = 0; for (i = SM5703_INTMSK1; i < ARRAY_SIZE(sm5703_reg_map); i++) { data = sm5703_reg_read(i2c, sm5703_reg_map[i]); sprintf(str+strlen(str), "0x%02x, ", data); } } static void sm5703_test_read(struct i2c_client *i2c) { char str[1000] = {0,}; int i; /* SM5703 REG: 0x04 ~ 0x13 */ for (i = SM5703_INTMSK1; i <= SM5703_CHGCNTL6; i++) { int data; data = sm5703_reg_read(i2c, i); sprintf(str+strlen(str), "0x%0x = 0x%02x, ", i, data); } sprintf(str+strlen(str), "0x%0x = 0x%02x, ",SM5703_OTGCURRENTCNTL, sm5703_reg_read(i2c, SM5703_OTGCURRENTCNTL)); sprintf(str+strlen(str), "0x%0x = 0x%02x, ", SM5703_STATUS5, sm5703_reg_read(i2c, SM5703_STATUS5)); sprintf(str+strlen(str), "0x%0x = 0x%02x, ", SM5703_Q3LIMITCNTL, sm5703_reg_read(i2c, SM5703_Q3LIMITCNTL)); pr_info("%s: %s\n", __func__, str); } #define SM5703_FLEDCNTL6 0x19 static void sm5703_charger_otg_control(struct sm5703_charger_data *charger, bool enable) { pr_info("%s: called charger otg control : %s\n", __func__, enable ? "on" : "off"); otg_enable_flag = enable; if (!enable) { /* turn off OTG */ sm5703_assign_bits(charger->sm5703->i2c_client, SM5703_FLEDCNTL6, SM5703_BSTOUT_MASK, SM5703_BSTOUT_4P5); #ifdef CONFIG_FLED_SM5703 if (charger->fled_info == NULL) charger->fled_info = sm_fled_get_info_by_name(NULL); if (charger->fled_info) sm5703_boost_notification(charger->fled_info, 0); #else sm5703_assign_bits(charger->sm5703->i2c_client, SM5703_CNTL, SM5703_OPERATION_MODE_MASK, SM5703_OPERATION_MODE_CHARGING_ON); #endif } else { sm5703_assign_bits(charger->sm5703->i2c_client, SM5703_FLEDCNTL6, SM5703_BSTOUT_MASK, SM5703_BSTOUT_5P0); #ifdef CONFIG_FLED_SM5703 if (charger->fled_info == NULL) charger->fled_info = sm_fled_get_info_by_name(NULL); if (charger->fled_info) sm5703_boost_notification(charger->fled_info, 1); #else sm5703_assign_bits(charger->sm5703->i2c_client, SM5703_CNTL, SM5703_OPERATION_MODE_MASK, SM5703_OPERATION_MODE_USB_OTG_MODE); #endif charger->cable_type = POWER_SUPPLY_TYPE_OTG; } } #ifdef CONFIG_CHARGER_SM5703_SOFT_START_CHARGING static int sm5703_get_input_current_limit(struct i2c_client *i2c); #endif static void sm5703_enable_charger_switch(struct sm5703_charger_data *charger, int onoff) { #ifdef CONFIG_CHARGER_SM5703_SOFT_START_CHARGING int get_input_current=0; int data=0; #endif #ifdef CONFIG_CHARGER_SM5703_DUALPATH union power_supply_propval batt_pres; #endif int prev_charging_status = charger->is_charging; charger->is_charging = onoff ? true : false; if ((onoff > 0) && (prev_charging_status == false)) { pr_info("%s: turn on charger\n", __func__); #ifdef CONFIG_FLED_SM5703 if (charger->fled_info == NULL) charger->fled_info = sm_fled_get_info_by_name(NULL); if (charger->fled_info) sm5703_charger_notification(charger->fled_info,1); #endif #ifdef CONFIG_CHARGER_SM5703_SOFT_START_CHARGING get_input_current=sm5703_get_input_current_limit(charger->sm5703->i2c_client); mutex_lock(&charger->io_lock); data = sm5703_reg_read(charger->sm5703->i2c_client, SM5703_VBUSCNTL); data &= ~SM5703_VBUSLIMIT; sm5703_reg_write(charger->sm5703->i2c_client, SM5703_VBUSCNTL, data); #endif sm5703_assign_bits(charger->sm5703->i2c_client, SM5703_CNTL, SM5703_OPERATION_MODE_MASK, SM5703_OPERATION_MODE_CHARGING_ON); charger->nchgen = false; gpio_direction_output(charger->pdata->chgen_gpio, charger->nchgen); /* nCHG enable */ #ifdef CONFIG_CHARGER_SM5703_SOFT_START_CHARGING mdelay(100); if (get_input_current > 100) { int temp; temp = ((get_input_current - 100) / 50) | data; sm5703_reg_write(charger->sm5703->i2c_client, SM5703_VBUSCNTL, temp); } mutex_unlock(&charger->io_lock); #endif pr_info("%s : STATUS OF CHARGER ON(0)/OFF(1): %d\n", __func__, charger->nchgen); } else if (onoff == 0) { charger->full_charged = false; pr_info("%s: turn off charger\n", __func__); charger->charging_current = DEFAULT_CHARGING_CURRENT; charger->nchgen = true; #ifdef CONFIG_FLED_SM5703 if (charger->fled_info == NULL) charger->fled_info = sm_fled_get_info_by_name(NULL); if (charger->fled_info) sm5703_charger_notification(charger->fled_info,0); #endif gpio_direction_output(charger->pdata->chgen_gpio, charger->nchgen); /* nCHG disable */ pr_info("%s : STATUS OF CHARGER ON(0)/OFF(1): %d\n", __func__, charger->nchgen); #ifdef CONFIG_CHARGER_SM5703_DUALPATH psy_do_property("battery", get, POWER_SUPPLY_PROP_PRESENT, batt_pres); if(batt_pres.intval== false){ sm5703_assign_bits(charger->sm5703->i2c_client, SM5703_CNTL, SM5703_OPERATION_MODE_MASK, SM5703_OPERATION_MODE_SUSPEND); pr_info("%s: DUALPATH set to SM5703_OPERATION_MODE_SUSPEND \n",__func__); } #endif } else { pr_info("%s: repeated to set charger switch(%d), prev stat = %d\n", __func__, onoff, prev_charging_status ? 1 : 0); } } static int sm5703_CHG_set_TOPOFF_TMR(struct sm5703_charger_data *charger, unsigned char topoff_timer) { struct i2c_client *i2c = charger->sm5703->i2c_client; sm5703_assign_bits(i2c, SM5703_CHGCNTL5, SM5703_TOPOFF_TIMER_MASK, ((topoff_timer & SM5703_TOPOFF_TIMER) << SM5703_TOPOFF_TIMER_SHIFT)); pr_info("TOPOFF_TMR set (timer=%d)\n", topoff_timer); return 0; } static void sm5703_enable_autostop(struct sm5703_charger_data *charger, int onoff) { struct i2c_client *i2c = charger->sm5703->i2c_client; pr_info("%s:[BATT] Autostop set(%d)\n", __func__, onoff); mutex_lock(&charger->io_lock); if (onoff) sm5703_set_bits(i2c, SM5703_CHGCNTL4, SM5703_AUTOSTOP_MASK); else sm5703_clr_bits(i2c, SM5703_CHGCNTL4, SM5703_AUTOSTOP_MASK); mutex_unlock(&charger->io_lock); } static void sm5703_enable_autoset(struct sm5703_charger_data *charger, int onoff) { struct i2c_client *i2c = charger->sm5703->i2c_client; pr_info("%s:[BATT] Autoset set(%d)\n", __func__, onoff); mutex_lock(&charger->io_lock); if (onoff) sm5703_set_bits(i2c, SM5703_CNTL, SM5703_AUTOSET_MASK); else sm5703_clr_bits(i2c, SM5703_CNTL, SM5703_AUTOSET_MASK); mutex_unlock(&charger->io_lock); } static void sm5703_enable_aiclen(struct sm5703_charger_data *charger, int onoff) { struct i2c_client *i2c = charger->sm5703->i2c_client; pr_info("%s:[BATT] AICLEN set(%d)\n", __func__, onoff); mutex_lock(&charger->io_lock); if (onoff) sm5703_set_bits(i2c, SM5703_CHGCNTL5, SM5703_AICLEN_MASK); else sm5703_clr_bits(i2c, SM5703_CHGCNTL5, SM5703_AICLEN_MASK); mutex_unlock(&charger->io_lock); } static void sm5703_set_aiclth(struct sm5703_charger_data *charger, int aiclth) { struct i2c_client *i2c = charger->sm5703->i2c_client; int data = 0, temp = 0; mutex_lock(&charger->io_lock); data = sm5703_reg_read(i2c, SM5703_CHGCNTL5); data &= ~SM5703_AICLTH; if (aiclth >= 4900) aiclth = 4900; if (aiclth >= 4300){ temp = (aiclth - 4300)/100; data |= temp; } sm5703_reg_write(i2c, SM5703_CHGCNTL5, data); data = sm5703_reg_read(i2c, SM5703_CHGCNTL5); pr_info("%s : SM5703_CHGCNTL5 (AICHTH) : 0x%02x\n", __func__, data); mutex_unlock(&charger->io_lock); } #if EN_AICL_IRQ static void sm5703_set_aicl_irq(struct sm5703_charger_data *charger, int mask) { struct i2c_client *i2c = charger->sm5703->i2c_client; int data = 0; mutex_lock(&charger->io_lock); data = sm5703_reg_read(i2c, SM5703_INTMSK1); data &= 0xFE; if (mask) data |= 0x01; sm5703_reg_write(i2c, SM5703_INTMSK1, data); data = sm5703_reg_read(i2c, SM5703_INTMSK1); pr_info("%s : SM5703_INTMSK1 (AICH-MASK) : 0x%02x, mask : %d\n", __func__, data, mask); mutex_unlock(&charger->io_lock); } #endif static void sm5703_set_freqsel(struct sm5703_charger_data *charger, int freqsel_hz) { struct i2c_client *i2c = charger->sm5703->i2c_client; int data = 0; mutex_lock(&charger->io_lock); data = sm5703_reg_read(i2c, SM5703_CHGCNTL6); data &= ~SM5703_FREQSEL_MASK; data |= (freqsel_hz << SM5703_FREQSEL_SHIFT); sm5703_reg_write(i2c, SM5703_CHGCNTL6, data); data = sm5703_reg_read(i2c, SM5703_CHGCNTL6); pr_info("%s : SM5703_CHGCNTL6 (FREQSEL) : 0x%02x\n", __func__, data); mutex_unlock(&charger->io_lock); } static void sm5703_set_input_current_limit(struct sm5703_charger_data *charger, int current_limit) { struct i2c_client *i2c = charger->sm5703->i2c_client; int data = 0, temp = 0; mutex_lock(&charger->io_lock); data = sm5703_reg_read(i2c, SM5703_VBUSCNTL); data &= ~SM5703_VBUSLIMIT; if (charger->siop_level < 100 && current_limit >= SIOP_INPUT_LIMIT_CURRENT) current_limit = SIOP_INPUT_LIMIT_CURRENT; if (current_limit >= 2100) current_limit = 2100; if (charger->current_max < current_limit && charger->is_current_reduced) { pr_info("%s: skip set input current limit(%d <--> %d)\n", __func__, charger->current_max, current_limit); } else { if (current_limit > 100) { temp = ((current_limit - 100) / 50) | data; sm5703_reg_write(i2c, SM5703_VBUSCNTL, temp); } data = sm5703_reg_read(i2c, SM5703_VBUSCNTL); pr_info("%s : SM5703_VBUSCNTL (Input current limit) : 0x%02x\n", __func__, data); if (charger->pdata->chg_vbuslimit #if EN_AICL_IRQ /* check aicl state */ && (sm5703_reg_read(i2c, SM5703_STATUS1) & 0x01) #endif ) { /* start vbuslimit work */ wake_lock(&charger->vbuslimit_wake_lock); queue_delayed_work_on(0, charger->wq, &charger->vbuslimit_work, msecs_to_jiffies(START_VBUSLIMIT_DELAY)); } } mutex_unlock(&charger->io_lock); } static int sm5703_get_input_current_limit(struct i2c_client *i2c) { int ret, current_limit = 0; ret = sm5703_reg_read(i2c, SM5703_VBUSCNTL); if (ret < 0) return ret; ret&=SM5703_VBUSLIMIT_MASK; current_limit = (100 + (ret*50)); return current_limit; } static void sm5703_set_regulation_voltage(struct sm5703_charger_data *charger, int float_voltage) { struct i2c_client *i2c = charger->sm5703->i2c_client; int data = 0; data = sm5703_reg_read(i2c, SM5703_CHGCNTL3); data &= ~SM5703_BATREG_MASK; if ((float_voltage) <= 4120) data = 0x00; else if ((float_voltage) >= 4430) data = 0x1f; else data = ((float_voltage - 4120) / 10); mutex_lock(&charger->io_lock); sm5703_reg_write(i2c, SM5703_CHGCNTL3, data); data = sm5703_reg_read(i2c, SM5703_CHGCNTL3); pr_info("%s : SM5703_CHGCNTL3 (Battery regulation voltage) : 0x%02x\n", __func__, data); mutex_unlock(&charger->io_lock); } #if defined(CONFIG_BATTERY_SWELLING) || defined(CONFIG_BATTERY_SWELLING_SELF_DISCHARGING) static int sm5703_get_regulation_voltage(struct sm5703_charger_data *charger) { struct i2c_client *i2c = charger->sm5703->i2c_client; int data = 0; data = sm5703_reg_read(i2c, SM5703_CHGCNTL3); data &= SM5703_BATREG_MASK; return (4120 + (data * 10)); } #endif static void __sm5703_set_fast_charging_current(struct i2c_client *i2c, int charging_current) { int data = 0; if(charging_current <= 100) charging_current = 100; else if (charging_current >= 2500) charging_current = 2500; data = (charging_current - 100) / 50; sm5703_reg_write(i2c, SM5703_CHGCNTL2, data); data = sm5703_reg_read(i2c, SM5703_CHGCNTL2); pr_info("%s : SM5703_CHGCNTL2 (fastchg current) : 0x%02x\n", __func__, data); } static int sm5703_get_fast_charging_current(struct i2c_client *i2c) { int data = sm5703_reg_read(i2c, SM5703_CHGCNTL2); int charging_current = 0; if (data < 0) return data; data &= SM5703_FASTCHG_MASK; charging_current = (100 + (data*50)); return charging_current; } static int sm5703_get_current_topoff_setting(struct sm5703_charger_data *charger) { int ret, data = 0, topoff_current = 0; mutex_lock(&charger->io_lock); ret = sm5703_reg_read(charger->sm5703->i2c_client, SM5703_CHGCNTL4); mutex_unlock(&charger->io_lock); if (ret < 0) { pr_info("%s: warning --> fail to read i2c register(%d)\n", __func__, ret); return ret; } data = ((ret & SM5703_TOPOFF_MASK) >> SM5703_TOPOFF_SHIFT); topoff_current = (100 + (data*25)); return topoff_current; } static void __sm5703_set_termination_current_limit(struct i2c_client *i2c, int current_limit) { int data = 0, temp = 0; pr_info("%s : Set Termination\n", __func__); data = sm5703_reg_read(i2c, SM5703_CHGCNTL4); data &= ~SM5703_TOPOFF_MASK; if(current_limit <= 100) current_limit = 100; else if (current_limit >= 475) current_limit = 475; temp = (current_limit - 100) / 25; data |= (temp << SM5703_TOPOFF_SHIFT); sm5703_reg_write(i2c, SM5703_CHGCNTL4, data); data = sm5703_reg_read(i2c, SM5703_CHGCNTL4); pr_info("%s : SM5703_CHGCNTL4 (Top-off current threshold) : 0x%02x\n", __func__, data); } static void sm5703_set_charging_current(struct sm5703_charger_data *charger, int topoff) { int adj_current = 0; #ifndef CONFIG_DISABLE_MINIMUM_SIOP_CHARGING const int usb_charging_current = charger->pdata->charging_current_table[ POWER_SUPPLY_TYPE_USB].fast_charging_current; #endif adj_current = charger->charging_current * charger->siop_level / 100; #ifndef CONFIG_DISABLE_MINIMUM_SIOP_CHARGING if (adj_current > 0 && adj_current < usb_charging_current) adj_current = usb_charging_current; #endif #if CONFIG_SIOP_CHARGING_LIMIT_CURRENT if(charger->siop_level < 100 && adj_current > CONFIG_SIOP_CHARGING_LIMIT_CURRENT) adj_current = CONFIG_SIOP_CHARGING_LIMIT_CURRENT; #endif pr_info("%s adj_current = %dmA charger->siop_level = %d\n",__func__, adj_current,charger->siop_level); mutex_lock(&charger->io_lock); __sm5703_set_fast_charging_current(charger->sm5703->i2c_client, adj_current); __sm5703_set_termination_current_limit( charger->sm5703->i2c_client, topoff); mutex_unlock(&charger->io_lock); } static void sm5703_set_otgcurrent(struct sm5703_charger_data *charger, int otg_current) { struct i2c_client *i2c = charger->sm5703->i2c_client; int data = 0; data = sm5703_reg_read(i2c, SM5703_OTGCURRENTCNTL); data &= ~SM5703_OTGCURRENT_MASK; if (otg_current <= 500) data = 0x00; else if (otg_current <= 700) data = 0x01; else if (otg_current <= 900) data = 0x02; else data = 0x3; mutex_lock(&charger->io_lock); sm5703_reg_write(i2c, SM5703_OTGCURRENTCNTL, data); data = sm5703_reg_read(i2c, SM5703_OTGCURRENTCNTL); pr_info("%s : SM5703_OTGCURRENTCNTL (OTG current) : 0x%02x\n", __func__, data); mutex_unlock(&charger->io_lock); } static void sm5703_set_bst_iq3limit(struct sm5703_charger_data *charger, int iq3limit) { int data = 0; mutex_lock(&charger->io_lock); data = sm5703_reg_read(charger->sm5703->i2c_client, SM5703_Q3LIMITCNTL); data &= ~SM5703_BST_IQ3LIMIT_MASK; data |= (iq3limit << SM5703_BST_IQ3LIMIT_SHIFT); sm5703_reg_write(charger->sm5703->i2c_client, SM5703_Q3LIMITCNTL, data); data = sm5703_reg_read(charger->sm5703->i2c_client, SM5703_Q3LIMITCNTL); pr_info("%s : SM5703_Q3LIMITCNTL (BST_IQ3LIMIT) : 0x%02x\n", __func__, data); mutex_unlock(&charger->io_lock); } static void sm5703_configure_charger(struct sm5703_charger_data *charger) { int topoff; union power_supply_propval val, chg_now, swelling_state; int full_check_type; pr_info("%s : Set config charging\n", __func__); if (charger->charging_current < 0) { pr_info("%s : OTG is activated. Ignore command!\n", __func__); return; } psy_do_property("battery", get, POWER_SUPPLY_PROP_CHARGE_NOW, val); /* Input current limit */ pr_info("%s : input current (%dmA)\n", __func__, charger->pdata->charging_current_table [charger->cable_type].input_current_limit); sm5703_set_input_current_limit(charger, charger->pdata->charging_current_table [charger->cable_type].input_current_limit); /* Float voltage */ pr_info("%s : float voltage (%dmV)\n", __func__, charger->pdata->chg_float_voltage); sm5703_set_regulation_voltage(charger, charger->pdata->chg_float_voltage); /* Fast charge and Termination current */ charger->charging_current = charger->pdata->charging_current_table [charger->cable_type].fast_charging_current; topoff = charger->pdata->charging_current_table [charger->cable_type].full_check_current_1st; psy_do_property("battery", get, POWER_SUPPLY_PROP_CHARGE_NOW, chg_now); if (chg_now.intval == SEC_BATTERY_CHARGING_1ST) full_check_type = charger->pdata->full_check_type; else full_check_type = charger->pdata->full_check_type_2nd; switch (full_check_type) { case SEC_BATTERY_FULLCHARGED_CHGPSY: case SEC_BATTERY_FULLCHARGED_FG_CURRENT: #if defined(CONFIG_BATTERY_SWELLING) psy_do_property("battery", get, POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT, swelling_state); #else swelling_state.intval = 0; #endif if (chg_now.intval == SEC_BATTERY_CHARGING_1ST && (!swelling_state.intval)) { pr_info("%s : termination current (%dmA)\n", __func__, charger->pdata->charging_current_table[ charger->cable_type].full_check_current_1st); /** Setting 1st termination current as charger termination current*/ topoff = charger->pdata->charging_current_table [charger->cable_type].full_check_current_1st; } else { pr_info("%s : termination current (%dmA)\n", __func__, charger->pdata->charging_current_table[ charger->cable_type].full_check_current_2nd); if (sm5703_get_charging_status(charger) == POWER_SUPPLY_STATUS_FULL) { sm5703_enable_charger_switch(charger, 0); charger->charging_current = charger->pdata->charging_current_table [charger->cable_type].fast_charging_current; } /** Setting 2nd termination current as new charger termination current*/ topoff = charger->pdata->charging_current_table [charger->cable_type].full_check_current_2nd; } break; } pr_info("%s : fast charging current (%dmA), topoff current (%dmA)\n", __func__, charger->charging_current, topoff); sm5703_set_charging_current(charger, topoff); sm5703_enable_charger_switch(charger, 1); } int sm5703_chg_fled_init(struct i2c_client *client) { int ret = 0;//, rev_id; #if 0 sm5703_mfd_chip_t *chip = i2c_get_clientdata(client); struct sm5703_charger_data *charger = chip->charger; #endif return ret; } EXPORT_SYMBOL(sm5703_chg_fled_init); /* here is set init charger data */ static bool sm5703_chg_init(struct sm5703_charger_data *charger) { sm5703_mfd_chip_t *chip = i2c_get_clientdata(charger->sm5703->i2c_client); chip->charger = charger; sm5703_chg_fled_init(charger->sm5703->i2c_client); //int data = 0; charger->full_charged = false; /* AUTOSTOP */ sm5703_enable_autostop(chip->charger, (int)charger->pdata->chg_autostop); /* AUTOSET */ sm5703_enable_autoset(chip->charger, (int)charger->pdata->chg_autoset); /* AICLEN */ sm5703_enable_aiclen(chip->charger, (int)charger->pdata->chg_aiclen); /* AICLTH */ sm5703_set_aiclth(chip->charger, (int)charger->pdata->chg_aiclth); /* FREQSEL */ sm5703_set_freqsel(chip->charger, SM5703_FREQSEL_1P5MHZ); /* Auto-Stop configuration for Emergency status */ __sm5703_set_termination_current_limit(charger->sm5703->i2c_client, 300); sm5703_CHG_set_TOPOFF_TMR(charger, SM5703_TOPOFF_TIMER_45m); /* MUST set correct regulation voltage first * Before MUIC pass cable type information to charger * charger would be already enabled (default setting) * it might cause EOC event by incorrect regulation voltage */ sm5703_set_regulation_voltage(charger, charger->pdata->chg_float_voltage); sm5703_set_otgcurrent(charger, 1200); /* OTGCURRENT : 1.2A */ sm5703_set_bst_iq3limit(charger, SM5703_BST_IQ3LIMIT_1X); sm5703_test_read(charger->sm5703->i2c_client); return true; } static int sm5703_get_charging_status(struct sm5703_charger_data *charger) { int status = POWER_SUPPLY_STATUS_UNKNOWN; int chg_status3,chg_status5; int nCHG = 0; chg_status3 = sm5703_reg_read(charger->sm5703->i2c_client, SM5703_STATUS3); if (chg_status3<0) { pr_info("Error : SM5703_STATUS3 can't get charging status (%d)\n", chg_status3); } pr_info("%s chg_status3 = %d \n",__func__, chg_status3); chg_status5 = sm5703_reg_read(charger->sm5703->i2c_client, SM5703_STATUS5); if (chg_status5<0) { pr_info("Error : SM5703_STATUS5 can't get charging status (%d)\n", chg_status5); } pr_info("%s charger->full_charged = %d, charger->cable_type = %d \n",__func__,charger->full_charged,charger->cable_type); nCHG = gpio_get_value(charger->pdata->chgen_gpio); if(((chg_status3 & SM5703_STATUS3_DONE) || (chg_status3 & SM5703_STATUS3_TOPOFF)) && (chg_status5 & SM5703_STATUS5_VBUSOK) && (charger->cable_type != POWER_SUPPLY_TYPE_OTG) && (charger->cable_type != POWER_SUPPLY_TYPE_POWER_SHARING)) { status = POWER_SUPPLY_STATUS_FULL; charger->full_charged = true; pr_info("%s : Status, Power Supply Full \n", __func__); }else if(chg_status3 & SM5703_STATUS3_CHGON){ status = POWER_SUPPLY_STATUS_CHARGING; }else { if (nCHG) status = POWER_SUPPLY_STATUS_DISCHARGING; else status = POWER_SUPPLY_STATUS_NOT_CHARGING; } /* TEMP_TEST : when OTG is enabled(charging_current -1), handle OTG func. */ if (charger->charging_current < 0) { int chg_status1; /* For OTG mode, SM5703 would still report "charging" */ status = POWER_SUPPLY_STATUS_DISCHARGING; chg_status1 = sm5703_reg_read(charger->sm5703->i2c_client, SM5703_STATUS1); if (chg_status1 & SM5703_STATUS1_OTGFAIL) { pr_info("%s: otg overcurrent limit\n", __func__); sm5703_charger_otg_control(charger, false); } } return status; } static int sm5703_get_charging_health(struct sm5703_charger_data *charger) { int vbus_status = sm5703_reg_read(charger->sm5703->i2c_client, SM5703_STATUS5); int health = POWER_SUPPLY_HEALTH_GOOD; int chg_status3; int nCHG = 0; chg_status3 = sm5703_reg_read(charger->sm5703->i2c_client, SM5703_STATUS3); pr_info("%s : charger->is_charging = %d, charger->cable_type = %d, is_current_reduced = %d\n", __func__, charger->is_charging, charger->cable_type, charger->is_current_reduced); /* temp for test */ pr_info("%s : vbus_status = %d\n", __func__, vbus_status); if (vbus_status < 0) { health = POWER_SUPPLY_HEALTH_UNKNOWN; pr_info("%s : Health : %d, vbus_status : %d\n", __func__, health,vbus_status); return (int)health; } if (vbus_status & SM5703_STATUS5_VBUSOK) health = POWER_SUPPLY_HEALTH_GOOD; else if (vbus_status & SM5703_STATUS5_VBUSOVP) health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; else if (vbus_status & SM5703_STATUS5_VBUSUVLO) health = POWER_SUPPLY_HEALTH_UNDERVOLTAGE; else health = POWER_SUPPLY_HEALTH_UNKNOWN; if (health == POWER_SUPPLY_HEALTH_GOOD) { /* check if chgen */ nCHG = gpio_get_value(charger->pdata->chgen_gpio); /* print the log at the abnormal case */ if ((charger->is_charging == 1) && (chg_status3 & SM5703_STATUS3_DONE) && (nCHG)) { sm5703_test_read(charger->sm5703->i2c_client); gpio_direction_output(charger->pdata->chgen_gpio, !(charger->is_charging)); /* re-enable Charger */ pr_info("%s : FORCE RE-ENABLE Charger in Fake DONE state\n", __func__); } } pr_info("%s : Health : %d\n", __func__, health); return (int)health; } static int sec_chg_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { int chg_curr, aicr, vbus_status; int data = 0; struct sm5703_charger_data *charger = container_of(psy, struct sm5703_charger_data, psy_chg); switch (psp) { case POWER_SUPPLY_PROP_ONLINE: vbus_status = sm5703_reg_read(charger->sm5703->i2c_client, SM5703_STATUS5); if (charger->cable_type != POWER_SUPPLY_TYPE_BATTERY && !(vbus_status & SM5703_STATUS5_VBUSOK)) charger->cable_type = POWER_SUPPLY_TYPE_BATTERY; val->intval = charger->cable_type; pr_info("%s: Charger Cable type : %d\n", __func__, charger->cable_type); break; case POWER_SUPPLY_PROP_STATUS: val->intval = sm5703_get_charging_status(charger); break; case POWER_SUPPLY_PROP_HEALTH: val->intval = sm5703_get_charging_health(charger); break; case POWER_SUPPLY_PROP_CURRENT_MAX: sm5703_test_read(charger->sm5703->i2c_client); val->intval = sm5703_get_fast_charging_current(charger->sm5703->i2c_client); break; case POWER_SUPPLY_PROP_CURRENT_AVG: break; case POWER_SUPPLY_PROP_CURRENT_NOW: if (charger->charging_current) { aicr = sm5703_get_input_current_limit(charger->sm5703->i2c_client); chg_curr = sm5703_get_fast_charging_current(charger->sm5703->i2c_client); val->intval = MINVAL(aicr, chg_curr); } else val->intval = 0; break; #if defined(CONFIG_BATTERY_SWELLING) || defined(CONFIG_BATTERY_SWELLING_SELF_DISCHARGING) case POWER_SUPPLY_PROP_VOLTAGE_MAX: val->intval = sm5703_get_regulation_voltage(charger); break; #endif case POWER_SUPPLY_PROP_CHARGE_TYPE: if (!charger->is_charging || charger->cable_type == POWER_SUPPLY_TYPE_BATTERY || charger->cable_type == POWER_SUPPLY_TYPE_OTG) { val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; } else if (charger->current_max <= SLOW_CHARGING_CURRENT_STANDARD) { val->intval = POWER_SUPPLY_CHARGE_TYPE_SLOW; pr_info("%s: slow-charging mode\n", __func__); } else val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; break; case POWER_SUPPLY_PROP_PRESENT: val->intval = 0; data = sm5703_reg_read(charger->sm5703->i2c_client, SM5703_STATUS2); if((data & (1 << 4)) == 0x0) val->intval = 1; pr_info("%s: batt_present : %d\n", __func__, val->intval); break; case POWER_SUPPLY_PROP_CHARGE_OTG_CONTROL: break; case POWER_SUPPLY_PROP_ENERGY_NOW: return -ENODATA; default: return -EINVAL; } return 0; } static int sec_chg_set_property(struct power_supply *psy, enum power_supply_property psp, const union power_supply_propval *val) { struct sm5703_charger_data *charger = container_of(psy, struct sm5703_charger_data, psy_chg); int topoff; union power_supply_propval value; int previous_cable_type = charger->cable_type; switch (psp) { case POWER_SUPPLY_PROP_STATUS: charger->status = val->intval; break; /* val->intval : type */ case POWER_SUPPLY_PROP_ONLINE: charger->cable_type = val->intval; if (previous_cable_type != charger->cable_type) { charger->current_max = charger->pdata->charging_current_table [charger->cable_type].input_current_limit; charger->charging_current = charger->pdata->charging_current_table [charger->cable_type].fast_charging_current; charger->is_current_reduced = false; if (charger->pdata->chg_vbuslimit) { wake_unlock(&charger->vbuslimit_wake_lock); cancel_delayed_work(&charger->vbuslimit_work); } #if EN_AICL_IRQ sm5703_set_aicl_irq(charger, 0); #endif } if (val->intval == POWER_SUPPLY_TYPE_POWER_SHARING) { psy_do_property("ps", get, POWER_SUPPLY_PROP_STATUS, value); sm5703_charger_otg_control(charger, value.intval); } else if (charger->cable_type == POWER_SUPPLY_TYPE_BATTERY) { pr_info("%s:[BATT] Type Battery\n", __func__); /* sm5703_enable_charger_switch(charger, 0); */ if (previous_cable_type == POWER_SUPPLY_TYPE_OTG) sm5703_charger_otg_control(charger, false); else sm5703_enable_charger_switch(charger, 0); /* set default input current */ charger->current_max = charger->pdata->charging_current_table [POWER_SUPPLY_TYPE_USB].input_current_limit; charger->is_mdock = false; sm5703_set_input_current_limit(charger, charger->current_max); } else if (charger->cable_type == POWER_SUPPLY_TYPE_OTG) { pr_info("%s: OTG mode\n", __func__); //2017.01.06 : If Lanhub cable is changed to OTG cable, needed to disable charger operation. pr_info("%s: previous_cable_type = %d, cable_type = %d\n", __func__,previous_cable_type, charger->cable_type); if (previous_cable_type == POWER_SUPPLY_TYPE_LAN_HUB) { pr_info("%s: LAN HUB condition is turned off by charger driver\n", __func__); sm5703_enable_charger_switch(charger, 0); } sm5703_charger_otg_control(charger, true); charger->full_charged = false; } else { pr_info("%s:[BATT] Set charging" ", Cable type = %d\n", __func__, charger->cable_type); /* check mdock */ if (charger->is_mdock) { /* if mdock was alread inserted, then check OTG, or NOTG state */ if (charger->cable_type == POWER_SUPPLY_TYPE_SMART_NOTG) { charger->charging_current = charger->pdata->charging_current_table [POWER_SUPPLY_TYPE_MDOCK_TA].fast_charging_current; charger->current_max = charger->pdata->charging_current_table [POWER_SUPPLY_TYPE_MDOCK_TA].input_current_limit; } else if (charger->cable_type == POWER_SUPPLY_TYPE_SMART_OTG) { charger->charging_current = charger->pdata->charging_current_table [POWER_SUPPLY_TYPE_MDOCK_TA].fast_charging_current - 500; charger->current_max = charger->pdata->charging_current_table [POWER_SUPPLY_TYPE_MDOCK_TA].input_current_limit - 500; } } else { /* if mdock wasn't inserted, then check mdock state */ if (charger->cable_type == POWER_SUPPLY_TYPE_MDOCK_TA) { charger->is_mdock = true; } } //2017.01.06 : If OTG cable is changed to Lanhub cable, needed to disable OTG operation. pr_info("%s: previous_cable_type = %d\n", __func__,previous_cable_type); if (previous_cable_type == POWER_SUPPLY_TYPE_OTG && charger->cable_type == POWER_SUPPLY_TYPE_LAN_HUB) { pr_info("%s:OTG condition is turned off by charger driver\n", __func__); sm5703_charger_otg_control(charger, false); } /* Enable charger */ sm5703_configure_charger(charger); } if (sec_bat_get_slate_mode() == ENABLE) { sm5703_enable_charger_switch(charger, false); sm5703_assign_bits(charger->sm5703->i2c_client, SM5703_CNTL, SM5703_OPERATION_MODE_MASK, SM5703_OPERATION_MODE_SUSPEND); pr_info("%s: SM5703 OPERATION MODE SUSPEND\n",__func__); } else { sm5703_enable_charger_switch(charger, charger->is_charging); } #if EN_TEST_READ /* msleep(100); */ sm5703_test_read(charger->sm5703->i2c_client); #endif break; case POWER_SUPPLY_PROP_CURRENT_AVG: #if defined(CONFIG_BATTERY_SWELLING) if (val->intval > charger->pdata->charging_current_table [charger->cable_type].fast_charging_current) { break; } #endif topoff = sm5703_get_current_topoff_setting(charger); pr_info("%s:Set chg current = %d mA, topoff = %d mA\n", __func__, val->intval, topoff); charger->charging_current = val->intval; sm5703_set_charging_current(charger, topoff); break; case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: /* decrease the charging current according to siop level */ charger->siop_level = val->intval; pr_info("%s:SIOP level = %d, chg current = %d\n", __func__, val->intval, charger->charging_current); if (charger->is_charging) { sm5703_configure_charger(charger); } break; case POWER_SUPPLY_PROP_CURRENT_NOW: /* set charging current */ if (charger->is_charging) { sm5703_configure_charger(charger); } if (sec_bat_get_slate_mode() == ENABLE) { sm5703_enable_charger_switch(charger, false); sm5703_assign_bits(charger->sm5703->i2c_client, SM5703_CNTL, SM5703_OPERATION_MODE_MASK, SM5703_OPERATION_MODE_SUSPEND); pr_info("%s: SM5703 OPERATION MODE SUSPEND\n",__func__); } else { sm5703_enable_charger_switch(charger, charger->is_charging); } break; case POWER_SUPPLY_PROP_POWER_NOW: topoff = sm5703_get_current_topoff_setting(charger); pr_info("%s:Set Power Now -> chg current = %d mA, topoff = %d mA\n", __func__, val->intval, topoff); sm5703_set_charging_current(charger, topoff); break; #if defined(CONFIG_BATTERY_SWELLING) || defined(CONFIG_BATTERY_SWELLING_SELF_DISCHARGING) case POWER_SUPPLY_PROP_VOLTAGE_MAX: pr_info("%s: float voltage(%d)\n", __func__, val->intval); charger->pdata->chg_float_voltage = val->intval; sm5703_set_regulation_voltage(charger, val->intval); break; #endif case POWER_SUPPLY_PROP_HEALTH: /* charger->ovp = val->intval; */ break; case POWER_SUPPLY_PROP_CHARGE_OTG_CONTROL: sm5703_charger_otg_control(charger, val->intval); break; case POWER_SUPPLY_PROP_ENERGY_NOW: /* Switch-off charger if JIG is connected */ if (val->intval) { pr_info("%s: JIG Connection status: %d \n", __func__, val->intval); sm5703_enable_charger_switch(charger, false); } break; default: return -EINVAL; } return 0; } static int sm5703_otg_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { switch (psp) { case POWER_SUPPLY_PROP_ONLINE: val->intval = otg_enable_flag; break; default: return -EINVAL; } return 0; } static int sm5703_otg_set_property(struct power_supply *psy, enum power_supply_property psp, const union power_supply_propval *val) { struct sm5703_charger_data *charger = container_of(psy, struct sm5703_charger_data, psy_otg); union power_supply_propval value; switch (psp) { case POWER_SUPPLY_PROP_ONLINE: value.intval = val->intval; pr_info("%s: OTG %s\n", __func__, value.intval > 0 ? "on" : "off"); psy_do_property(charger->pdata->charger_name, set, POWER_SUPPLY_PROP_CHARGE_OTG_CONTROL, value); break; default: return -EINVAL; } return 0; } ssize_t sm5703_chg_show_attrs(struct device *dev, const ptrdiff_t offset, char *buf) { struct power_supply *psy = dev_get_drvdata(dev); struct sm5703_charger_data *charger = container_of(psy, struct sm5703_charger_data, psy_chg); int i = 0; char *str = NULL; switch (offset) { case CHG_REG: i += scnprintf(buf + i, PAGE_SIZE - i, "%x\n", charger->reg_addr); break; case CHG_DATA: i += scnprintf(buf + i, PAGE_SIZE - i, "%x\n", charger->reg_data); break; case CHG_REGS: str = kzalloc(sizeof(char) * 256, GFP_KERNEL); if (!str) return -ENOMEM; sm5703_read_regs(charger->sm5703->i2c_client, str); i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n", str); kfree(str); break; default: i = -EINVAL; break; } return i; } ssize_t sm5703_chg_store_attrs(struct device *dev, const ptrdiff_t offset, const char *buf, size_t count) { struct power_supply *psy = dev_get_drvdata(dev); struct sm5703_charger_data *charger = container_of(psy, struct sm5703_charger_data, psy_chg); int ret = 0; int x = 0; uint8_t data = 0; switch (offset) { case CHG_REG: if (sscanf(buf, "%x\n", &x) == 1) { charger->reg_addr = x; data = sm5703_reg_read(charger->sm5703->i2c_client, charger->reg_addr); charger->reg_data = data; dev_dbg(dev, "%s: (read) addr = 0x%x, data = 0x%x\n", __func__, charger->reg_addr, charger->reg_data); ret = count; } break; case CHG_DATA: if (sscanf(buf, "%x\n", &x) == 1) { data = (u8)x; dev_dbg(dev, "%s: (write) addr = 0x%x, data = 0x%x\n", __func__, charger->reg_addr, data); ret = sm5703_reg_write(charger->sm5703->i2c_client, charger->reg_addr, data); if (ret < 0) { dev_dbg(dev, "I2C write fail Reg0x%x = 0x%x\n", (int)charger->reg_addr, (int)data); } ret = count; } break; default: ret = -EINVAL; break; } return ret; } struct sm5703_chg_irq_handler { char *name; int irq_index; irqreturn_t (*handler)(int irq, void *data); }; #if EN_NOBAT_IRQ static irqreturn_t sm5703_chg_nobat_irq_handler(int irq, void *data) { struct sm5703_charger_data *info = data; struct i2c_client *iic = info->sm5703->i2c_client; /* set full charged flag * until TA/USB unplug event / stop charging by PSY */ pr_info("%s : Nobat\n", __func__); #if EN_TEST_READ sm5703_test_read(iic); #endif return IRQ_HANDLED; } #endif /*EN_NOBAT_IRQ*/ #if EN_DONE_IRQ static irqreturn_t sm5703_chg_done_irq_handler(int irq, void *data) { struct sm5703_charger_data *info = data; struct i2c_client *iic = info->sm5703->i2c_client; /* set full charged flag * until TA/USB unplug event / stop charging by PSY */ pr_info("%s : Full charged(done)\n", __func__); info->full_charged = true; /* nCHG pin toggle */ gpio_direction_output(info->pdata->chgen_gpio, info->is_charging); msleep(10); /* Removed so to be able to check top-off timer at the factory */ /* gpio_direction_output(info->pdata->chgen_gpio, !(info->is_charging)); */ #if EN_TEST_READ sm5703_test_read(iic); #endif return IRQ_HANDLED; } #endif/*EN_DONE_IRQ*/ #if EN_TOPOFF_IRQ static irqreturn_t sm5703_chg_topoff_irq_handler(int irq, void *data) { struct sm5703_charger_data *info = data; struct i2c_client *iic = info->sm5703->i2c_client; /* set full charged flag * until TA/USB unplug event / stop charging by PSY */ pr_info("%s : Full charged(topoff)\n", __func__); info->full_charged = true; #if EN_TEST_READ sm5703_test_read(iic); #endif return IRQ_HANDLED; } #endif /*EN_TOPOFF_IRQ*/ #if EN_CHGON_IRQ static irqreturn_t sm5703_chg_chgon_irq_handler(int irq, void *data) { struct sm5703_charger_data *info = data; struct i2c_client *iic = info->sm5703->i2c_client; pr_info("%s : Chgon\n", __func__); #if EN_TEST_READ sm5703_test_read(iic); #endif return IRQ_HANDLED; } #endif /*EN_CHGON_IRQ*/ #if EN_OTGFAIL_IRQ static irqreturn_t sm5703_chg_otgfail_irq_handler(int irq, void *data) { struct sm5703_charger_data *info = data; /* struct i2c_client *iic = info->sm5703->i2c_client; */ int ret; #ifdef CONFIG_USB_HOST_NOTIFY struct otg_notify *o_notify; o_notify = get_otg_notify(); #endif pr_info("%s : OTG Failed\n", __func__); ret = sm5703_reg_read(info->sm5703->i2c_client, SM5703_STATUS1); if (ret & SM5703_STATUS1_OTGFAIL) { pr_info("%s: otg overcurrent limit\n", __func__); #ifdef CONFIG_USB_HOST_NOTIFY send_otg_notify(o_notify, NOTIFY_EVENT_OVERCURRENT, 0); #endif sm5703_charger_otg_control(info, false); } return IRQ_HANDLED; } #endif /*EN_CHGON_IRQ*/ static void sm5703_chg_vbuslimit_work(struct work_struct *work) { struct sm5703_charger_data *charger = container_of(work, struct sm5703_charger_data, vbuslimit_work.work); struct i2c_client *i2c = charger->sm5703->i2c_client; if (charger->cable_type != POWER_SUPPLY_TYPE_BATTERY) { int vbuslimit_state; vbuslimit_state = sm5703_reg_read(i2c, SM5703_STATUS1) & 0x08; if (vbuslimit_state || (charger->current_max <= MINIMUM_INPUT_CURRENT)) { /* check slow charging */ if (charger->is_current_reduced && charger->current_max <= SLOW_CHARGING_CURRENT_STANDARD) { union power_supply_propval value; psy_do_property("battery", set, POWER_SUPPLY_PROP_CHARGE_TYPE, value); pr_info("%s: slow charging on : input current(%dmA), cable type(%d)\n", __func__, charger->current_max, charger->cable_type); } wake_unlock(&charger->vbuslimit_wake_lock); } else { /* reduce input current & restart vbuslimit work */ int reg_data, temp; mutex_lock(&charger->io_lock); charger->is_current_reduced = true; charger->current_max -= REDUCE_CURRENT_STEP; reg_data = sm5703_reg_read(i2c, SM5703_VBUSCNTL); reg_data &= ~SM5703_VBUSLIMIT; temp = ((charger->current_max - 100) / 50) | reg_data; sm5703_reg_write(i2c, SM5703_VBUSCNTL, temp); pr_info("%s: reduce input current(%d)\n", __func__, charger->current_max); mutex_unlock(&charger->io_lock); queue_delayed_work_on(0, charger->wq, &charger->vbuslimit_work, msecs_to_jiffies(VBUSLIMIT_DELAY)); } pr_info("%s: vbuslimit state(%d)\n", __func__, vbuslimit_state); } else { wake_unlock(&charger->vbuslimit_wake_lock); } } #if EN_VBUSLIMIT_IRQ static irqreturn_t sm5703_chg_vbuslimit_irq_handler(int irq, void *data) { struct sm5703_charger_data *charger = data; struct i2c_client *i2c = charger->sm5703->i2c_client; pr_info("%s: VBUS Limit\n", __func__); #if EN_TEST_READ sm5703_test_read(i2c); #endif return IRQ_HANDLED; } #endif /* EN_VBUSLIMIT_IRQ */ #if EN_AICL_IRQ static irqreturn_t sm5703_chg_aicl_irq_handler(int irq, void *data) { struct sm5703_charger_data *charger = data; struct i2c_client *i2c = charger->sm5703->i2c_client; pr_info("%s: AICL\n", __func__); sm5703_set_aicl_irq(charger, 1); if (charger->pdata->chg_vbuslimit && charger->cable_type != POWER_SUPPLY_TYPE_BATTERY) { /* start vbuslimit work */ wake_lock(&charger->vbuslimit_wake_lock); queue_delayed_work_on(0, charger->wq, &charger->vbuslimit_work, msecs_to_jiffies(START_VBUSLIMIT_DELAY)); } #if EN_TEST_READ sm5703_test_read(i2c); #endif return IRQ_HANDLED; } #endif /* EN_AICL_IRQ */ const struct sm5703_chg_irq_handler sm5703_chg_irq_handlers[] = { #if EN_NOBAT_IRQ { .name = "NOBAT", .handler = sm5703_chg_nobat_irq_handler, .irq_index = SM5703_NOBAT_IRQ, }, #endif /*EN_NOBAT_IRQ*/ #if EN_DONE_IRQ { .name = "DONE", .handler = sm5703_chg_done_irq_handler, .irq_index = SM5703_DONE_IRQ, }, #endif/*EN_DONE_IRQ*/ #if EN_TOPOFF_IRQ { .name = "TOPOFF", .handler = sm5703_chg_topoff_irq_handler, .irq_index = SM5703_TOPOFF_IRQ, }, #endif /*EN_TOPOFF_IRQ*/ #if EN_CHGON_IRQ { .name = "CHGON", .handler = sm5703_chg_chgon_irq_handler, .irq_index = SM5703_CHGON_IRQ, }, #endif /*EN_CHGON_IRQ*/ #if EN_OTGFAIL_IRQ { .name = "OTGFAIL", .handler = sm5703_chg_otgfail_irq_handler, .irq_index = SM5703_OTGFAIL_IRQ, }, #endif /* EN_OTGFAIL_IRQ */ #if EN_VBUSLIMIT_IRQ { .name = "VBUSLIMIT", .handler = sm5703_chg_vbuslimit_irq_handler, .irq_index = SM5703_VBUSLIMIT_IRQ, }, #endif /* EN_VBUSLIMIT_IRQ */ #if EN_AICL_IRQ { .name = "AICL", .handler = sm5703_chg_aicl_irq_handler, .irq_index = SM5703_AICL_IRQ, }, #endif /* EN_AICL_IRQ */ }; static int register_irq(struct platform_device *pdev, struct sm5703_charger_data *info) { int irq; int i, j; int ret; const struct sm5703_chg_irq_handler *irq_handler = sm5703_chg_irq_handlers; const char *irq_name; for (i = 0; i < ARRAY_SIZE(sm5703_chg_irq_handlers); i++) { irq_name = sm5703_get_irq_name_by_index(irq_handler[i].irq_index); irq = platform_get_irq_byname(pdev, irq_name); ret = request_threaded_irq(irq, NULL, irq_handler[i].handler, IRQF_ONESHOT | IRQF_TRIGGER_FALLING | IRQF_NO_SUSPEND, irq_name, info); if (ret < 0) { pr_err("%s : Failed to request IRQ (%s): #%d: %d\n", __func__, irq_name, irq, ret); goto err_irq; } pr_info("%s : Register IRQ%d(%s) successfully\n", __func__, irq, irq_name); } return 0; err_irq: for (j = 0; j < i; j++) { irq_name = sm5703_get_irq_name_by_index(irq_handler[j].irq_index); irq = platform_get_irq_byname(pdev, irq_name); free_irq(irq, info); } return ret; } static void unregister_irq(struct platform_device *pdev, struct sm5703_charger_data *info) { int irq; int i; const char *irq_name; const struct sm5703_chg_irq_handler *irq_handler = sm5703_chg_irq_handlers; for (i = 0; i < ARRAY_SIZE(sm5703_chg_irq_handlers); i++) { irq_name = sm5703_get_irq_name_by_index(irq_handler[i].irq_index); irq = platform_get_irq_byname(pdev, irq_name); free_irq(irq, info); } } #ifdef CONFIG_OF static int sec_bat_read_u32_index_dt(const struct device_node *np, const char *propname, u32 index, u32 *out_value) { struct property *prop = of_find_property(np, propname, NULL); u32 len = (index + 1) * sizeof(*out_value); if (!prop) return (-EINVAL); if (!prop->value) return (-ENODATA); if (len > prop->length) return (-EOVERFLOW); *out_value = be32_to_cpup(((__be32 *)prop->value) + index); return 0; } static int sm5703_charger_parse_dt(struct device *dev, struct sm5703_charger_platform_data *pdata) { struct device_node *np = dev->of_node; const u32 *p; int ret, i, len; /* chg_autostop */ ret = of_property_read_u32(np, "chg_autostop", &pdata->chg_autostop); if (ret < 0) { pr_info("%s : cannot get chg autostop\n", __func__); return -ENODATA; } /* chg_autoset */ ret = of_property_read_u32(np, "chg_autoset", &pdata->chg_autoset); if (ret < 0) { pr_info("%s : cannot get chg autoset\n", __func__); return -ENODATA; } /* chg_aiclen */ ret = of_property_read_u32(np, "chg_aiclen", &pdata->chg_aiclen); if (ret < 0) { pr_info("%s : cannot get chg aiclen\n", __func__); return -ENODATA; } /* chg_aiclth */ ret = of_property_read_u32(np, "chg_aiclth", &pdata->chg_aiclth); if (ret < 0) { pr_info("%s : cannot get chg aiclth\n", __func__); pdata->chg_aiclth = 4500; return -ENODATA; } /* fg_vol_val */ ret = of_property_read_u32(np, "fg_vol_val", &pdata->fg_vol_val); if (ret < 0) { pr_info("%s : cannot get fg_vol_val\n", __func__); return -ENODATA; } /* fg_soc_val */ ret = of_property_read_u32(np, "fg_soc_val", &pdata->fg_soc_val); if (ret < 0) { pr_info("%s : cannot get fg_soc_val\n", __func__); return -ENODATA; } /* fg_curr_avr_val */ ret = of_property_read_u32(np, "fg_curr_avr_val", &pdata->fg_curr_avr_val); if (ret < 0) { pr_info("%s : cannot get fg_curr_avr_val\n", __func__); return -ENODATA; } np = of_find_node_by_name(NULL, "battery"); if (!np) { pr_info("%s : np NULL\n", __func__); return -ENODATA; } ret = of_property_read_u32(np, "battery,full_check_type", &pdata->full_check_type); pr_info("%s full_check_type: %d\n", __func__, pdata->full_check_type); if (ret < 0) pr_err("%s error reading battery,full_check_type %d\n", __func__, ret); ret = of_property_read_u32(np, "battery,full_check_type_2nd", &pdata->full_check_type_2nd); pr_info("%s full_check_type_2nd: %d\n", __func__, pdata->full_check_type_2nd); if (ret < 0) pr_err("%s error reading battery,full_check_type_2nd %d\n", __func__, ret); np = of_find_node_by_name(NULL, "charger"); if (!np) { pr_info("%s : np NULL\n", __func__); return -ENODATA; } ret = of_property_read_u32(np, "battery,chg_vbuslimit", &pdata->chg_vbuslimit); if (ret < 0) { pr_info("%s : cannot get chg vbuslimit\n", __func__); pdata->chg_vbuslimit = 0; } p = of_get_property(np, "battery,input_current_limit", &len); len = len / sizeof(u32); pdata->charging_current_table = kzalloc(sizeof(sec_charging_current_t) * len, GFP_KERNEL); for(i = 0; i < len; i++) { ret = sec_bat_read_u32_index_dt(np, "battery,input_current_limit", i, &pdata->charging_current_table[i].input_current_limit); ret = sec_bat_read_u32_index_dt(np, "battery,fast_charging_current", i, &pdata->charging_current_table[i].fast_charging_current); ret = sec_bat_read_u32_index_dt(np, "battery,full_check_current_1st", i, &pdata->charging_current_table[i].full_check_current_1st); ret = sec_bat_read_u32_index_dt(np, "battery,full_check_current_2nd", i, &pdata->charging_current_table[i].full_check_current_2nd); } /* battery,chg_float_voltage */ ret = of_property_read_u32(np, "battery,chg_float_voltage", &pdata->chg_float_voltage); if (ret < 0) { pr_info("%s : cannot get chg float voltage\n", __func__); return -ENODATA; } ret = of_property_read_string(np, "battery,charger_name", (char const **)&pdata->charger_name); if (ret) { pdata->charger_name = "sm5703-charger"; pr_info("%s: Charger name is Empty. Set default.\n", __func__); } pdata->chgen_gpio = of_get_named_gpio(np, "battery,chg_gpio_en", 0); if (pdata->chgen_gpio < 0) { pr_err("%s : cannot get chgen gpio : %d\n", __func__, pdata->chgen_gpio); return -ENODATA; } else { pr_info("%s: chgen gpio : %d\n", __func__, pdata->chgen_gpio); } dev_info(dev,"sm5703 charger parse dt retval = %d\n", ret); return ret; } static struct of_device_id sm5703_charger_match_table[] = { { .compatible = "siliconmitus,sm5703-charger",}, {}, }; #else static int sm5703_charger_parse_dt(struct device *dev, struct sm5703_charger_platform_data *pdata) { return -ENOSYS; } #define sm5703_charger_match_table NULL #endif /* CONFIG_OF */ static int sm5703_charger_probe(struct platform_device *pdev) { sm5703_mfd_chip_t *chip = dev_get_drvdata(pdev->dev.parent); struct sm5703_mfd_platform_data *mfd_pdata = dev_get_platdata(chip->dev); struct sm5703_charger_data *charger; int ret = 0; otg_enable_flag = 0; pr_info("%s:[BATT] SM5703 Charger driver probe..\n", __func__); charger = kzalloc(sizeof(*charger), GFP_KERNEL); if (!charger) return -ENOMEM; #ifdef CONFIG_OF #if (LINUX_VERSION_CODE < KERNEL_VERSION(3,10,0)) if (pdev->dev.parent->of_node) { pdev->dev.of_node = of_find_compatible_node( of_node_get(pdev->dev.parent->of_node), NULL, sm5703_charger_match_table[0].compatible); } #endif #endif mutex_init(&charger->io_lock); charger->wq = create_workqueue("sm5703chg_workqueue"); charger->sm5703= chip; /* if (pdev->dev.of_node) */ if(1) { charger->pdata = devm_kzalloc(&pdev->dev, sizeof(*(charger->pdata)), GFP_KERNEL); if (!charger->pdata) { dev_err(&pdev->dev, "Failed to allocate memory\n"); ret = -ENOMEM; goto err_parse_dt_nomem; } ret = sm5703_charger_parse_dt(&pdev->dev, charger->pdata); if (ret < 0) goto err_parse_dt; } else charger->pdata = mfd_pdata->charger_platform_data; platform_set_drvdata(pdev, charger); if (charger->pdata->charger_name == NULL) charger->pdata->charger_name = "sm5703-charger"; charger->psy_chg.name = charger->pdata->charger_name; charger->psy_chg.type = POWER_SUPPLY_TYPE_UNKNOWN; charger->psy_chg.get_property = sec_chg_get_property; charger->psy_chg.set_property = sec_chg_set_property; charger->psy_chg.properties = sec_charger_props; charger->psy_chg.num_properties = ARRAY_SIZE(sec_charger_props); charger->psy_otg.name = "otg"; charger->psy_otg.type = POWER_SUPPLY_TYPE_OTG; charger->psy_otg.get_property = sm5703_otg_get_property; charger->psy_otg.set_property = sm5703_otg_set_property; charger->psy_otg.properties = sm5703_otg_props; charger->psy_otg.num_properties = ARRAY_SIZE(sm5703_otg_props); charger->siop_level = 100; charger->ovp = 0; charger->is_mdock = false; sm5703_chg_init(charger); if (charger->pdata->chg_vbuslimit) { INIT_DELAYED_WORK(&charger->vbuslimit_work, sm5703_chg_vbuslimit_work); wake_lock_init(&charger->vbuslimit_wake_lock, WAKE_LOCK_SUSPEND, "sm5703-vbuslimit"); } ret = power_supply_register(&pdev->dev, &charger->psy_chg); if (ret) { pr_err("%s: Failed to Register psy_chg\n", __func__); goto err_power_supply_register; } ret = power_supply_register(&pdev->dev, &charger->psy_otg); if (ret) { pr_err("%s: Failed to Register otg_chg\n", __func__); goto err_power_supply_register_otg; } ret = register_irq(pdev, charger); if (ret < 0) goto err_reg_irq; ret = gpio_request(charger->pdata->chgen_gpio, "sm5703_nCHGEN"); if (ret) { pr_info("%s : Request GPIO %d failed\n", __func__, (int)charger->pdata->chgen_gpio); } sm5703_test_read(charger->sm5703->i2c_client); pr_info("%s:[BATT] SM5703 charger driver loaded OK\n", __func__); return 0; err_reg_irq: power_supply_unregister(&charger->psy_otg); err_power_supply_register_otg: power_supply_unregister(&charger->psy_chg); err_power_supply_register: if (charger->pdata->chg_vbuslimit) { wake_lock_destroy(&charger->vbuslimit_wake_lock); } err_parse_dt: err_parse_dt_nomem: destroy_workqueue(charger->wq); mutex_destroy(&charger->io_lock); kfree(charger); return ret; } static int sm5703_charger_remove(struct platform_device *pdev) { struct sm5703_charger_data *charger = platform_get_drvdata(pdev); unregister_irq(pdev, charger); power_supply_unregister(&charger->psy_chg); destroy_workqueue(charger->wq); if (charger->pdata->chg_vbuslimit) { wake_lock_destroy(&charger->vbuslimit_wake_lock); } mutex_destroy(&charger->io_lock); kfree(charger); return 0; } #if defined CONFIG_PM static int sm5703_charger_suspend(struct device *dev) { return 0; } static int sm5703_charger_resume(struct device *dev) { return 0; } #else #define sm5703_charger_suspend NULL #define sm5703_charger_resume NULL #endif static void sm5703_charger_shutdown(struct device *dev) { pr_info("%s: SM5703 Charger driver shutdown\n", __func__); } static SIMPLE_DEV_PM_OPS(sm5703_charger_pm_ops, sm5703_charger_suspend, sm5703_charger_resume); static struct platform_driver sm5703_charger_driver = { .driver = { .name = "sm5703-charger", .owner = THIS_MODULE, .of_match_table = sm5703_charger_match_table, .pm = &sm5703_charger_pm_ops, .shutdown = sm5703_charger_shutdown, }, .probe = sm5703_charger_probe, .remove = sm5703_charger_remove, }; static int __init sm5703_charger_init(void) { int ret = 0; pr_info("%s \n", __func__); ret = platform_driver_register(&sm5703_charger_driver); return ret; } device_initcall(sm5703_charger_init); static void __exit sm5703_charger_exit(void) { platform_driver_unregister(&sm5703_charger_driver); } module_exit(sm5703_charger_exit); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Charger driver for SM5703");