/* * max77693_charger.c * Samsung max77693 Charger Driver * * Copyright (C) 2012 Samsung Electronics * * * 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. */ #include #include #include #include #define DEBUG #define ENABLE 1 #define DISABLE 0 static struct dentry *max77693_dentry; struct max77693_charger_data { struct max77693_dev *max77693; struct power_supply psy_chg; struct workqueue_struct *wqueue; struct work_struct chgin_work; struct delayed_work isr_work; /* mutex */ struct mutex irq_lock; struct mutex ops_lock; /* wakelock */ struct wake_lock update_wake_lock; unsigned int is_charging; unsigned int charging_type; unsigned int battery_state; unsigned int battery_present; unsigned int cable_type; unsigned int charging_current_max; unsigned int charging_current; unsigned int input_current_limit; unsigned int vbus_state; int status; bool aicl_on; int siop_level; int irq_bypass; int irq_therm; int irq_battery; int irq_chg; int irq_chgin; /* software regulation */ bool soft_reg_state; int soft_reg_current; /* unsufficient power */ bool reg_loop_deted; #if defined(CONFIG_WIRELESS_CHARGING) /* wireless charge, w(wpc), v(vbus) */ int wc_w_gpio; int wc_w_irq; int wc_w_state; int wc_v_gpio; int wc_v_irq; int wc_v_state; bool wc_pwr_det; #endif sec_battery_platform_data_t *pdata; }; static enum power_supply_property sec_charger_props[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_CHARGE_TYPE, POWER_SUPPLY_PROP_HEALTH, POWER_SUPPLY_PROP_ONLINE, POWER_SUPPLY_PROP_CURRENT_MAX, POWER_SUPPLY_PROP_CURRENT_AVG, POWER_SUPPLY_PROP_CURRENT_NOW, }; /* static void MAX77693_charger_initialize(struct max77693_charger_data *charger); */ static int max77693_get_vbus_state(struct max77693_charger_data *charger); static void max77693_dump_reg(struct max77693_charger_data *charger) { u8 reg_data; u32 reg_addr; pr_info("%s\n", __func__); for (reg_addr = 0xB0; reg_addr <= 0xC5; reg_addr++) { max77693_read_reg(charger->max77693->i2c, reg_addr, ®_data); pr_info("max77693: c: 0x%02x(0x%02x)\n", reg_addr, reg_data); } } static int max77693_get_battery_present(struct max77693_charger_data *charger) { u8 reg_data; if (max77693_read_reg(charger->max77693->i2c, MAX77693_CHG_REG_CHG_INT_OK, ®_data) < 0) { /* Eventhough there is an error, don't do power-off */ return 1; } pr_debug("%s: CHG_INT_OK(0x%02x)\n", __func__, reg_data); reg_data = ((reg_data & MAX77693_BATP_OK) >> MAX77693_BATP_OK_SHIFT); return reg_data; } static void max77693_set_charger_state(struct max77693_charger_data *charger, int enable) { u8 reg_data; max77693_read_reg(charger->max77693->i2c, MAX77693_CHG_REG_CHG_CNFG_00, ®_data); if (enable) reg_data |= MAX77693_MODE_CHGR; else reg_data &= ~MAX77693_MODE_CHGR; pr_info("%s: CHG_CNFG_00(0x%02x)\n", __func__, reg_data); max77693_write_reg(charger->max77693->i2c, MAX77693_CHG_REG_CHG_CNFG_00, reg_data); } static void max77693_set_buck(struct max77693_charger_data *charger, int enable) { u8 reg_data; max77693_read_reg(charger->max77693->i2c, MAX77693_CHG_REG_CHG_CNFG_00, ®_data); if (enable) reg_data |= MAX77693_MODE_BUCK; else reg_data &= ~MAX77693_MODE_BUCK; pr_debug("%s: CHG_CNFG_00(0x%02x)\n", __func__, reg_data); max77693_write_reg(charger->max77693->i2c, MAX77693_CHG_REG_CHG_CNFG_00, reg_data); } static void max77693_set_input_current(struct max77693_charger_data *charger, int cur) { int set_current_reg, now_current_reg; int vbus_state, curr_step, delay; u8 set_reg, reg_data; mutex_lock(&charger->ops_lock); disable_irq(charger->irq_chgin); if (charger->cable_type == POWER_SUPPLY_TYPE_WPC) set_reg = MAX77693_CHG_REG_CHG_CNFG_10; else set_reg = MAX77693_CHG_REG_CHG_CNFG_09; if (cur <= 0) { max77693_write_reg(charger->max77693->i2c, set_reg, 0); // max77693_set_buck(charger, DISABLE); enable_irq(charger->irq_chgin); mutex_unlock(&charger->ops_lock); return; } else max77693_set_buck(charger, ENABLE); set_current_reg = cur / 20; max77693_read_reg(charger->max77693->i2c, set_reg, ®_data); if (reg_data == set_current_reg) { /* check uvlo */ while(1) { vbus_state = max77693_get_vbus_state(charger); if (((vbus_state == 0x00) || (vbus_state == 0x01)) && (charger->cable_type != POWER_SUPPLY_TYPE_WPC)) { /* UVLO */ set_current_reg -= 5; if (set_current_reg <= 0) break; max77693_write_reg(charger->max77693->i2c, set_reg, set_current_reg); pr_info("%s: reg_data(0x%02x)\n", __func__, set_current_reg); /* under 1.3A, slow rate */ if (set_current_reg < (1300 / 20) && (charger->cable_type == POWER_SUPPLY_TYPE_MAINS)) charger->aicl_on = true; msleep(50); } else break; } enable_irq(charger->irq_chgin); mutex_unlock(&charger->ops_lock); return; } if (reg_data == 0) { now_current_reg = SOFT_CHG_START_CURR / 20; max77693_write_reg(charger->max77693->i2c, set_reg, now_current_reg); msleep(SOFT_CHG_START_DUR); } else now_current_reg = reg_data; if (cur < 500) { curr_step = 1; delay = 50; } else { curr_step = SOFT_CHG_CURR_STEP / 20; delay = SOFT_CHG_STEP_DUR; } now_current_reg += (curr_step); while (now_current_reg < set_current_reg && charger->cable_type != POWER_SUPPLY_TYPE_BATTERY) { now_current_reg = min(now_current_reg, set_current_reg); max77693_write_reg(charger->max77693->i2c, set_reg, now_current_reg); msleep(delay); vbus_state = max77693_get_vbus_state(charger); if (((vbus_state == 0x00) || (vbus_state == 0x01)) && !(charger->cable_type == POWER_SUPPLY_TYPE_WPC)) { /* UVLO */ now_current_reg -= (curr_step * 3); curr_step /= 2; max77693_write_reg(charger->max77693->i2c, set_reg, now_current_reg); pr_info("%s: reg_data(0x%02x)\n", __func__, now_current_reg); if (curr_step < 5) { /* under 1.3A, slow rate */ if (now_current_reg < (1300 / 20) && (charger->cable_type == POWER_SUPPLY_TYPE_MAINS)) charger->aicl_on = true; enable_irq(charger->irq_chgin); mutex_unlock(&charger->ops_lock); return; } msleep(50); } else now_current_reg += (curr_step); } pr_info("%s: reg_data(0x%02x)\n", __func__, set_current_reg); max77693_write_reg(charger->max77693->i2c, set_reg, set_current_reg); enable_irq(charger->irq_chgin); mutex_unlock(&charger->ops_lock); } static int max77693_get_input_current(struct max77693_charger_data *charger) { u8 reg_data; int get_current = 0; if (charger->cable_type == POWER_SUPPLY_TYPE_WPC) { max77693_read_reg(charger->max77693->i2c, MAX77693_CHG_REG_CHG_CNFG_10, ®_data); pr_info("%s: CHG_CNFG_10(0x%02x)\n", __func__, reg_data); } else { max77693_read_reg(charger->max77693->i2c, MAX77693_CHG_REG_CHG_CNFG_09, ®_data); pr_info("%s: CHG_CNFG_09(0x%02x)\n", __func__, reg_data); } get_current = reg_data * 20; pr_debug("%s: get input current: %dmA\n", __func__, get_current); return get_current; } static void max77693_set_topoff_current(struct max77693_charger_data *charger, int cur, int timeout) { u8 reg_data; if (cur >= 350) reg_data = 0x07; else if (cur >= 300) reg_data = 0x06; else if (cur >= 250) reg_data = 0x05; else if (cur >= 200) reg_data = 0x04; else if (cur >= 175) reg_data = 0x03; else if (cur >= 150) reg_data = 0x02; else if (cur >= 125) reg_data = 0x01; else reg_data = 0x00; /* the unit of timeout is second*/ timeout = timeout / 60; reg_data |= ((timeout / 10) << 3); pr_info("%s: reg_data(0x%02x), topoff(%d)\n", __func__, reg_data, cur); max77693_write_reg(charger->max77693->i2c, MAX77693_CHG_REG_CHG_CNFG_03, reg_data); } static void max77693_set_charge_current(struct max77693_charger_data *charger, int cur) { u8 reg_data = 0; pr_info("%s: set current value : %d\n", __func__, cur); if (!cur) { /* No charger */ max77693_write_reg(charger->max77693->i2c, MAX77693_CHG_REG_CHG_CNFG_02, 0x0); } else { reg_data &= ~MAX77693_CHG_CC; reg_data |= ((cur * 3 / 100) << 0); // reg_data |= MAX77693_OTG_ILIM; pr_info("%s: charge current %d mA, reg_data(0x%02x)\n", __func__, cur, reg_data); max77693_write_reg(charger->max77693->i2c, MAX77693_CHG_REG_CHG_CNFG_02, reg_data); } } static int max77693_get_charge_current(struct max77693_charger_data *charger) { u8 reg_data; int get_current = 0; max77693_read_reg(charger->max77693->i2c, MAX77693_CHG_REG_CHG_CNFG_02, ®_data); pr_debug("%s: CHG_CNFG_02(0x%02x)\n", __func__, reg_data); reg_data &= MAX77693_CHG_CC; get_current = reg_data * 333 / 10; pr_debug("%s: get charge current: %dmA\n", __func__, get_current); return get_current; } static void reduce_input_current(struct max77693_charger_data *charger, int cur) { u8 set_reg; u8 set_value; if ((!charger->is_charging) || mutex_is_locked(&charger->ops_lock) || (charger->cable_type == POWER_SUPPLY_TYPE_WPC)) return; set_reg = MAX77693_CHG_REG_CHG_CNFG_09; if (!max77693_read_reg(charger->max77693->i2c, set_reg, &set_value)) { if (set_value == 0) return; set_value -= (cur / 20); set_value = (set_value < 10) ? 10 : set_value; max77693_write_reg(charger->max77693->i2c, set_reg, set_value); pr_info("%s: set current: reg:(0x%x), val:(0x%x)\n", __func__, set_reg, set_value); /* under 1.3A, slow rate */ if (set_value < (1300 / 20) && (charger->cable_type == POWER_SUPPLY_TYPE_MAINS)) charger->aicl_on = true; } } static int max77693_get_vbus_state(struct max77693_charger_data *charger) { u8 reg_data; max77693_read_reg(charger->max77693->i2c, MAX77693_CHG_REG_CHG_DTLS_00, ®_data); if (charger->cable_type == POWER_SUPPLY_TYPE_WPC) reg_data = ((reg_data & MAX77693_WCIN_DTLS) >> MAX77693_WCIN_DTLS_SHIFT); else reg_data = ((reg_data & MAX77693_CHGIN_DTLS) >> MAX77693_CHGIN_DTLS_SHIFT); switch (reg_data) { case 0x00: pr_info("%s: VBUS is invalid. CHGIN < CHGIN_UVLO\n", __func__); break; case 0x01: pr_info("%s: VBUS is invalid. CHGIN < MBAT+CHGIN2SYS" \ "and CHGIN > CHGIN_UVLO\n", __func__); break; case 0x02: pr_info("%s: VBUS is invalid. CHGIN > CHGIN_OVLO", __func__); break; case 0x03: pr_info("%s: VBUS is valid. CHGIN < CHGIN_OVLO", __func__); break; default: break; } return reg_data; } static int max77693_get_charger_state(struct max77693_charger_data *charger) { int state; u8 reg_data; max77693_read_reg(charger->max77693->i2c, MAX77693_CHG_REG_CHG_DTLS_01, ®_data); reg_data = ((reg_data & MAX77693_CHG_DTLS) >> MAX77693_CHG_DTLS_SHIFT); pr_info("%s: CHG_DTLS : 0x%2x\n", __func__, reg_data); switch (reg_data) { case 0x0: case 0x1: case 0x2: state = POWER_SUPPLY_STATUS_CHARGING; break; case 0x3: case 0x4: state = POWER_SUPPLY_STATUS_FULL; break; case 0x5: case 0x6: case 0x7: state = POWER_SUPPLY_STATUS_NOT_CHARGING; break; case 0x8: case 0xA: case 0xB: state = POWER_SUPPLY_STATUS_DISCHARGING; break; default: state = POWER_SUPPLY_STATUS_UNKNOWN; break; } return state; } static int max77693_get_health_state(struct max77693_charger_data *charger) { int state; int vbus_state; int chg_state; u8 reg_data; max77693_read_reg(charger->max77693->i2c, MAX77693_CHG_REG_CHG_DTLS_01, ®_data); reg_data = ((reg_data & MAX77693_BAT_DTLS) >> MAX77693_BAT_DTLS_SHIFT); switch (reg_data) { case 0x00: pr_info("%s: No battery and the charger is suspended\n", __func__); state = POWER_SUPPLY_HEALTH_UNKNOWN; break; case 0x01: pr_info("%s: battery unspec failure\n", __func__); state = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; break; case 0x02: pr_info("%s: battery dead\n", __func__); state = POWER_SUPPLY_HEALTH_DEAD; break; case 0x03: pr_info("%s: battery good\n", __func__); state = POWER_SUPPLY_HEALTH_GOOD; break; case 0x04: pr_info("%s: battery is okay" \ "but its voltage is low\n", __func__); state = POWER_SUPPLY_HEALTH_GOOD; break; case 0x05: pr_info("%s: battery ovp\n", __func__); state = POWER_SUPPLY_HEALTH_OVERVOLTAGE; break; default: pr_info("%s: battery unknown : 0x%d\n", __func__, reg_data); state = POWER_SUPPLY_HEALTH_UNKNOWN; break; } if (state == POWER_SUPPLY_HEALTH_GOOD) { /* VBUS OVP state return battery OVP state */ vbus_state = max77693_get_vbus_state(charger); if ((vbus_state == 0x00) || (vbus_state == 0x01)) reduce_input_current(charger, 20); /* read CHG_DTLS and detecting battery terminal error */ chg_state = max77693_get_charger_state(charger); /* OVP is higher priority */ if (vbus_state == 0x02) { /* CHGIN_OVLO */ pr_info("%s: vbus ovp\n", __func__); state = POWER_SUPPLY_HEALTH_OVERVOLTAGE; } else if (reg_data == 0x04 && chg_state == POWER_SUPPLY_STATUS_FULL) { pr_info("%s: battery terminal error\n", __func__); state = POWER_SUPPLY_HEALTH_UNDERVOLTAGE; } } return state; } static bool max77693_charger_unlock(struct max77693_charger_data *chg_data) { struct i2c_client *i2c = chg_data->max77693->i2c; u8 reg_data; u8 chgprot; int retry_cnt = 0; bool need_init = false; pr_debug("%s\n", __func__); pr_info("%s: charger unlock enable \n", __func__); max77693_read_reg(i2c, MAX77693_CHG_REG_CHG_CNFG_06, ®_data); chgprot = ((reg_data & 0x0C) >> 2); if (chgprot == 0x03) { pr_info("%s: unlocked state, return\n", __func__); need_init = false; goto unlock_finish; } do { max77693_write_reg(i2c, MAX77693_CHG_REG_CHG_CNFG_06, (0x03 << 2)); max77693_read_reg(i2c, MAX77693_CHG_REG_CHG_CNFG_06, ®_data); chgprot = ((reg_data & 0x0C) >> 2); if (chgprot != 0x03) { pr_err("%s: unlock err, chgprot(0x%x), retry(%d)\n", __func__, chgprot, retry_cnt); msleep(100); } else { pr_info("%s: unlock success, chgprot(0x%x)\n", __func__, chgprot); need_init = true; break; } } while ((chgprot != 0x03) && (++retry_cnt < 10)); unlock_finish: return need_init; } static void max77693_charger_initialize(struct max77693_charger_data *charger) { u8 reg_data, float_voltage = 0x1D; pr_debug("%s\n", __func__); max77693_set_buck(charger, ENABLE); /* unlock charger setting protect */ reg_data = (0x03 << 2); max77693_write_reg(charger->max77693->i2c, MAX77693_CHG_REG_CHG_CNFG_06, reg_data); /* * fast charge timer disable * restart threshold disable * pre-qual charge enable(default) */ reg_data = (0x0 << 0) | (0x03 << 4); max77693_write_reg(charger->max77693->i2c, MAX77693_CHG_REG_CHG_CNFG_01, reg_data); /* * confirm whether TA is connected or NOT */ if (max77693_read_reg(charger->max77693->i2c, MAX77693_CHG_REG_CHG_INT_OK, ®_data) == 0) { /* Check the charging status*/ if ((reg_data & (MAX77693_CHGIN_OK|MAX77693_WCIN_OK)) == 0) { max77693_read_reg(charger->max77693->i2c, MAX77693_CHG_REG_CHG_CNFG_00, ®_data); reg_data &= ~(CHG_CNFG_00_CHG_MASK | CHG_CNFG_00_BUCK_MASK); max77693_write_reg(charger->max77693->i2c, MAX77693_CHG_REG_CHG_CNFG_00, reg_data); } } /* * charge current 466mA(default) * otg current limit 900mA */ max77693_read_reg(charger->max77693->i2c, MAX77693_CHG_REG_CHG_CNFG_02, ®_data); reg_data |= (1 << 7); max77693_write_reg(charger->max77693->i2c, MAX77693_CHG_REG_CHG_CNFG_02, reg_data); /* * top off current 100mA * top off timer 40min */ reg_data = (0x04 << 3); max77693_write_reg(charger->max77693->i2c, MAX77693_CHG_REG_CHG_CNFG_03, reg_data); /* * cv voltage 4.2V or 4.35V * MINVSYS 3.6V(default) */ max77693_read_reg(charger->max77693->i2c, MAX77693_CHG_REG_CHG_CNFG_04, ®_data); reg_data &= ~0x1F; if (charger->pdata->chg_float_voltage) { float_voltage = charger->pdata->chg_float_voltage <= 4325 ? (charger->pdata->chg_float_voltage - 3650) / 25 : (charger->pdata->chg_float_voltage - 3650) / 25 + 1; } reg_data |= float_voltage; /* pr_info("%s: battery cv voltage %s, (sysrev %d)\n", __func__, (((reg_data & max77693_CHG_PRM_MASK) == \ (0x1D << max77693_CHG_PRM_SHIFT)) ? "4.35V" : "4.2V"), system_rev); */ max77693_write_reg(charger->max77693->i2c, MAX77693_CHG_REG_CHG_CNFG_04, reg_data); /* VBYPSET Default set 3V */ reg_data = 0x00; max77693_write_reg(charger->max77693->i2c, MAX77693_CHG_REG_CHG_CNFG_11, reg_data); max77693_dump_reg(charger); } static void check_charger_unlock_state(struct max77693_charger_data *chg_data) { bool need_reg_init = false; pr_debug("%s\n", __func__); pr_info("%s: unlock state enable\n", __func__); need_reg_init = max77693_charger_unlock(chg_data); if (need_reg_init) { pr_err("%s: charger locked state, reg init\n", __func__); max77693_charger_initialize(chg_data); } } static int sec_chg_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct max77693_charger_data *charger = container_of(psy, struct max77693_charger_data, psy_chg); u8 reg_data; switch (psp) { case POWER_SUPPLY_PROP_ONLINE: val->intval = POWER_SUPPLY_TYPE_BATTERY; if (max77693_read_reg(charger->max77693->i2c, MAX77693_CHG_REG_CHG_INT_OK, ®_data) == 0) { if (reg_data & MAX77693_CHGIN_OK) val->intval = POWER_SUPPLY_TYPE_MAINS; else if (reg_data & MAX77693_WCIN_OK) val->intval = POWER_SUPPLY_TYPE_WPC; } break; case POWER_SUPPLY_PROP_STATUS: val->intval = max77693_get_charger_state(charger); break; case POWER_SUPPLY_PROP_HEALTH: val->intval = max77693_get_health_state(charger); break; case POWER_SUPPLY_PROP_CURRENT_MAX: val->intval = charger->charging_current_max; break; case POWER_SUPPLY_PROP_CURRENT_AVG: val->intval = charger->charging_current; break; case POWER_SUPPLY_PROP_CURRENT_NOW: val->intval = min(max77693_get_input_current(charger), max77693_get_charge_current(charger)); break; case POWER_SUPPLY_PROP_CHARGE_TYPE: if (!charger->is_charging) val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; else if (charger->aicl_on) val->intval = POWER_SUPPLY_CHARGE_TYPE_SLOW; else val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; break; case POWER_SUPPLY_PROP_PRESENT: val->intval = max77693_get_battery_present(charger); break; 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 max77693_charger_data *charger = container_of(psy, struct max77693_charger_data, psy_chg); union power_supply_propval value; int set_charging_current, set_charging_current_max; const int usb_charging_current = charger->pdata->charging_current[ POWER_SUPPLY_TYPE_USB].fast_charging_current; const int wpc_charging_current = charger->pdata->charging_current[ POWER_SUPPLY_TYPE_WPC].input_current_limit; /* check and unlock */ check_charger_unlock_state(charger); 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; psy_do_property("battery", get, POWER_SUPPLY_PROP_HEALTH, value); if (val->intval == POWER_SUPPLY_TYPE_BATTERY) { charger->is_charging = false; charger->aicl_on = false; set_charging_current = 0; set_charging_current_max = 0; } else { charger->is_charging = true; /* decrease the charging current according to siop level */ set_charging_current = charger->charging_current * charger->siop_level / 100; if (set_charging_current > 0 && set_charging_current < usb_charging_current) set_charging_current = usb_charging_current; if (val->intval == POWER_SUPPLY_TYPE_WPC) set_charging_current_max = wpc_charging_current; else set_charging_current_max = charger->charging_current_max; } max77693_set_charger_state(charger, charger->is_charging); /* if battery full, only disable charging */ if ((charger->status == POWER_SUPPLY_STATUS_CHARGING) || (charger->status == POWER_SUPPLY_STATUS_DISCHARGING) || (value.intval == POWER_SUPPLY_HEALTH_UNSPEC_FAILURE)) { /* current setting */ max77693_set_charge_current(charger, set_charging_current); max77693_set_input_current(charger, set_charging_current_max); max77693_set_topoff_current(charger, charger->pdata->charging_current[ val->intval].full_check_current_1st, charger->pdata->charging_current[ val->intval].full_check_current_2nd); } break; /* val->intval : input charging current */ case POWER_SUPPLY_PROP_CURRENT_MAX: charger->charging_current_max = val->intval; break; /* val->intval : charging current */ case POWER_SUPPLY_PROP_CURRENT_AVG: charger->charging_current = val->intval; break; case POWER_SUPPLY_PROP_CURRENT_NOW: charger->siop_level = val->intval; if (charger->is_charging) { /* decrease the charging current according to siop level */ int current_now = charger->charging_current * val->intval / 100; if (current_now > 0 && current_now < usb_charging_current) current_now = usb_charging_current; max77693_set_charge_current(charger, current_now); } break; case POWER_SUPPLY_PROP_POWER_NOW: max77693_set_charge_current(charger, val->intval); max77693_set_input_current(charger, val->intval); break; default: return -EINVAL; } return 0; } static void sec_chg_isr_work(struct work_struct *work) { struct max77693_charger_data *charger = container_of(work, struct max77693_charger_data, isr_work.work); union power_supply_propval val; if (charger->pdata->full_check_type == SEC_BATTERY_FULLCHARGED_CHGINT) { val.intval = max77693_get_charger_state(charger); switch (val.intval) { case POWER_SUPPLY_STATUS_DISCHARGING: pr_err("%s: Interrupted but Discharging\n", __func__); break; case POWER_SUPPLY_STATUS_NOT_CHARGING: pr_err("%s: Interrupted but NOT Charging\n", __func__); break; case POWER_SUPPLY_STATUS_FULL: pr_info("%s: Interrupted by Full\n", __func__); psy_do_property("battery", set, POWER_SUPPLY_PROP_STATUS, val); break; case POWER_SUPPLY_STATUS_CHARGING: pr_err("%s: Interrupted but Charging\n", __func__); break; case POWER_SUPPLY_STATUS_UNKNOWN: default: pr_err("%s: Invalid Charger Status\n", __func__); break; } } if (charger->pdata->ovp_uvlo_check_type == SEC_BATTERY_OVP_UVLO_CHGINT) { val.intval = max77693_get_health_state(charger); switch (val.intval) { case POWER_SUPPLY_HEALTH_OVERHEAT: case POWER_SUPPLY_HEALTH_COLD: pr_err("%s: Interrupted but Hot/Cold\n", __func__); break; case POWER_SUPPLY_HEALTH_DEAD: pr_err("%s: Interrupted but Dead\n", __func__); break; case POWER_SUPPLY_HEALTH_OVERVOLTAGE: case POWER_SUPPLY_HEALTH_UNDERVOLTAGE: pr_info("%s: Interrupted by OVP/UVLO\n", __func__); psy_do_property("battery", set, POWER_SUPPLY_PROP_HEALTH, val); break; case POWER_SUPPLY_HEALTH_UNSPEC_FAILURE: pr_err("%s: Interrupted but Unspec\n", __func__); break; case POWER_SUPPLY_HEALTH_GOOD: pr_err("%s: Interrupted but Good\n", __func__); break; case POWER_SUPPLY_HEALTH_UNKNOWN: default: pr_err("%s: Invalid Charger Health\n", __func__); break; } } } static int max77693_debugfs_show(struct seq_file *s, void *data) { struct max77693_charger_data *charger = s->private; u8 reg; u8 reg_data; seq_printf(s, "MAX77693 CHARGER IC :\n"); seq_printf(s, "==================\n"); for (reg = 0xB0; reg <= 0xC5; reg++) { max77693_read_reg(charger->max77693->i2c, reg, ®_data); seq_printf(s, "0x%02x:\t0x%02x\n", reg, reg_data); } seq_printf(s, "\n"); return 0; } static int max77693_debugfs_open(struct inode *inode, struct file *file) { return single_open(file, max77693_debugfs_show, inode->i_private); } static const struct file_operations max77693_debugfs_fops = { .open = max77693_debugfs_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static irqreturn_t sec_chg_irq_thread(int irq, void *irq_data) { struct max77693_charger_data *charger = irq_data; pr_info("%s: Charger interrupt occured\n", __func__); if ((charger->pdata->full_check_type == SEC_BATTERY_FULLCHARGED_CHGINT) || (charger->pdata->ovp_uvlo_check_type == SEC_BATTERY_OVP_UVLO_CHGINT)) schedule_delayed_work(&charger->isr_work, 0); return IRQ_HANDLED; } #if defined(CONFIG_WIRELESS_CHARGING) static irqreturn_t wpc_charger_irq(int irq, void *data) { struct max77693_charger_data *chg_data = data; int wc_w_state; union power_supply_propval value; pr_info("%s: irq(%d)\n", __func__, irq); /* check and unlock */ check_charger_unlock_state(chg_data); wc_w_state = 0; wc_w_state = !gpio_get_value(chg_data->wc_w_gpio); if ((chg_data->wc_w_state == 0) && (wc_w_state == 1)) { value.intval = POWER_SUPPLY_TYPE_WPC<wc_w_state == 1) && (wc_w_state == 0)) { value.intval = POWER_SUPPLY_TYPE_BATTERY<wc_w_state, wc_w_state); chg_data->wc_w_state = wc_w_state; return IRQ_HANDLED; } #endif static irqreturn_t max77693_bypass_irq(int irq, void *data) { struct max77693_charger_data *chg_data = data; u8 dtls_02; u8 byp_dtls; u8 chg_cnfg_00; pr_info("%s: irq(%d)\n", __func__, irq); /* check and unlock */ check_charger_unlock_state(chg_data); max77693_read_reg(chg_data->max77693->i2c, MAX77693_CHG_REG_CHG_DTLS_02, &dtls_02); byp_dtls = ((dtls_02 & MAX77693_BYP_DTLS) >> MAX77693_BYP_DTLS_SHIFT); pr_info("%s: BYP_DTLS(0x%02x)\n", __func__, byp_dtls); if (byp_dtls & 0x1) { pr_info("%s: bypass overcurrent limit\n", __func__); #ifdef CONFIG_USB_HOST_NOTIFY /* max77693_muic_host_notify_cb(0); */ #endif /* disable the register values just related to OTG and keep the values about the charging */ max77693_read_reg(chg_data->max77693->i2c, MAX77693_CHG_REG_CHG_CNFG_00, &chg_cnfg_00); chg_cnfg_00 &= ~(CHG_CNFG_00_OTG_MASK | CHG_CNFG_00_BOOST_MASK | CHG_CNFG_00_DIS_MUIC_CTRL_MASK); max77693_write_reg(chg_data->max77693->i2c, MAX77693_CHG_REG_CHG_CNFG_00, chg_cnfg_00); } if (byp_dtls & 0x8) reduce_input_current(chg_data, 100); return IRQ_HANDLED; } static void max77693_chgin_isr_work(struct work_struct *work) { struct max77693_charger_data *charger = container_of(work, struct max77693_charger_data, chgin_work); u8 chgin_dtls; u8 prev_chgin_dtls = 0xff; int battery_health; union power_supply_propval value; int stable_count = 0; disable_irq(charger->irq_chgin); while (1) { psy_do_property("battery", get, POWER_SUPPLY_PROP_HEALTH, value); battery_health = value.intval; max77693_read_reg(charger->max77693->i2c, MAX77693_CHG_REG_CHG_DTLS_00, &chgin_dtls); chgin_dtls = ((chgin_dtls & MAX77693_CHGIN_DTLS) >> MAX77693_CHGIN_DTLS_SHIFT); if (prev_chgin_dtls == chgin_dtls) stable_count++; else stable_count = 0; if (stable_count > 10) { pr_info("%s: irq(%d), chgin(0x%x), prev 0x%x\n", __func__, charger->irq_chgin, chgin_dtls, prev_chgin_dtls); break; } if (charger->is_charging) { if ((chgin_dtls == 0x02) && \ (battery_health == POWER_SUPPLY_HEALTH_GOOD)) { pr_info("%s: charger is over voltage\n", __func__); value.intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; psy_do_property("battery", set, POWER_SUPPLY_PROP_HEALTH, value); } else if (battery_health == \ POWER_SUPPLY_HEALTH_OVERVOLTAGE) { pr_info("%s: charger is good\n", __func__); value.intval = POWER_SUPPLY_HEALTH_GOOD; psy_do_property("battery", set, POWER_SUPPLY_PROP_HEALTH, value); } if ((chgin_dtls == 0x0) || (chgin_dtls == 0x01)) reduce_input_current(charger, 20); } prev_chgin_dtls = chgin_dtls; msleep(100); } enable_irq(charger->irq_chgin); } static irqreturn_t max77693_chgin_irq(int irq, void *data) { struct max77693_charger_data *charger = data; queue_work(charger->wqueue, &charger->chgin_work); return IRQ_HANDLED; } static __devinit int max77693_charger_probe(struct platform_device *pdev) { struct max77693_dev *iodev = dev_get_drvdata(pdev->dev.parent); struct max77693_platform_data *pdata = dev_get_platdata(iodev->dev); struct max77693_charger_data *charger; int ret = 0; pr_info("%s: max77693 Charger driver probe\n", __func__); charger = kzalloc(sizeof(*charger), GFP_KERNEL); if (!charger) return -ENOMEM; charger->max77693 = iodev; charger->pdata = pdata->charger_data; charger->aicl_on = false; charger->siop_level = 100; platform_set_drvdata(pdev, charger); charger->psy_chg.name = "sec-charger"; 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); mutex_init(&charger->ops_lock); if (charger->pdata->chg_gpio_init) { if (!charger->pdata->chg_gpio_init()) { pr_err("%s: Failed to Initialize GPIO\n", __func__); goto err_free; } } max77693_charger_initialize(charger); ret = power_supply_register(&pdev->dev, &charger->psy_chg); if (ret) { pr_err("%s: Failed to Register psy_chg\n", __func__); goto err_free; } if (charger->pdata->chg_irq) { INIT_DELAYED_WORK_DEFERRABLE( &charger->isr_work, sec_chg_isr_work); ret = request_threaded_irq(charger->pdata->chg_irq, NULL, sec_chg_irq_thread, charger->pdata->chg_irq_attr, "charger-irq", charger); if (ret) { pr_err("%s: Failed to Reqeust IRQ\n", __func__); goto err_irq; } } #if defined(CONFIG_WIRELESS_CHARGING) charger->wc_w_gpio = pdata->wc_irq_gpio; if (charger->wc_w_gpio) { charger->wc_w_irq = gpio_to_irq(charger->wc_w_gpio); ret = gpio_request(charger->wc_w_gpio, "wpc_charger-irq"); if (ret < 0) { pr_err("%s: failed requesting gpio %d\n", __func__, charger->wc_w_gpio); goto err_wc_irq; } ret = request_threaded_irq(charger->wc_w_irq, NULL, wpc_charger_irq, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, "wpc-int", charger); if (ret) { pr_err("%s: Failed to Reqeust IRQ\n", __func__); goto err_wc_irq; } enable_irq_wake(charger->wc_w_irq); charger->wc_w_state = !gpio_get_value(charger->wc_w_gpio); } #endif charger->wqueue = create_singlethread_workqueue(dev_name(&pdev->dev)); INIT_WORK(&charger->chgin_work, max77693_chgin_isr_work); if (!charger->wqueue) { pr_err("%s: Fail to Create Workqueue\n", __func__); goto err_workqueue; } charger->irq_chgin = pdata->irq_base + MAX77693_CHG_IRQ_CHGIN_I; ret = request_threaded_irq(charger->irq_chgin, NULL, max77693_chgin_irq, 0, "chgin-irq", charger); if (ret < 0) { pr_err("%s: fail to request chgin IRQ: %d: %d\n", __func__, charger->irq_chgin, ret); goto err_chgin_irq; } charger->irq_bypass = pdata->irq_base + MAX77693_CHG_IRQ_BYP_I; ret = request_threaded_irq(charger->irq_bypass, NULL, max77693_bypass_irq, 0, "bypass-irq", charger); if (ret < 0) pr_err("%s: fail to request bypass IRQ: %d: %d\n", __func__, charger->irq_bypass, ret); max77693_dentry = debugfs_create_file("max77693-regs", S_IRUSR, NULL, charger, &max77693_debugfs_fops); return 0; err_chgin_irq: destroy_workqueue(charger->wqueue); err_workqueue: #if defined(CONFIG_WIRELESS_CHARGING) if (charger->wc_w_irq) free_irq(charger->wc_w_irq, NULL); err_wc_irq: #endif if (charger->pdata->chg_irq) free_irq(charger->pdata->chg_irq, NULL); err_irq: power_supply_unregister(&charger->psy_chg); err_free: kfree(charger); return ret; } static int __devexit max77693_charger_remove(struct platform_device *pdev) { struct max77693_charger_data *charger = platform_get_drvdata(pdev); if (!IS_ERR_OR_NULL(max77693_dentry)) debugfs_remove(max77693_dentry); destroy_workqueue(charger->wqueue); #if defined(CONFIG_WIRELESS_CHARGING) if (charger->wc_w_irq) free_irq(charger->wc_w_irq, NULL); #endif if (charger->pdata->chg_irq) free_irq(charger->pdata->chg_irq, NULL); power_supply_unregister(&charger->psy_chg); kfree(charger); return 0; } #if defined CONFIG_PM static int max77693_charger_suspend(struct device *dev) { return 0; } static int max77693_charger_resume(struct device *dev) { return 0; } #else #define max77693_charger_suspend NULL #define max77693_charger_resume NULL #endif static SIMPLE_DEV_PM_OPS(max77693_charger_pm_ops, max77693_charger_suspend, max77693_charger_resume); void max77693_charger_shutdown(struct device *dev) { struct max77693_charger_data *charger = dev_get_drvdata(dev); u8 reg_data; if (!charger->max77693->i2c) { pr_err("%s: no max77693 i2c client\n", __func__); return; } reg_data = 0x04; max77693_write_reg(charger->max77693->i2c, MAX77693_CHG_REG_CHG_CNFG_00, reg_data); reg_data = 0x19; max77693_write_reg(charger->max77693->i2c, MAX77693_CHG_REG_CHG_CNFG_09, reg_data); reg_data = 0x19; max77693_write_reg(charger->max77693->i2c, MAX77693_CHG_REG_CHG_CNFG_10, reg_data); pr_info("func:%s \n", __func__); } static struct platform_driver max77693_charger_driver = { .driver = { .name = "max77693-charger", .owner = THIS_MODULE, .pm = &max77693_charger_pm_ops, .shutdown = max77693_charger_shutdown, }, .probe = max77693_charger_probe, .remove = __devexit_p(max77693_charger_remove), }; static int __init max77693_charger_init(void) { pr_info("func:%s\n", __func__); return platform_driver_register(&max77693_charger_driver); } module_init(max77693_charger_init); static void __exit max77693_charger_exit(void) { platform_driver_register(&max77693_charger_driver); } module_exit(max77693_charger_exit); MODULE_DESCRIPTION("max77693 charger driver"); MODULE_AUTHOR("Samsung Electronics"); MODULE_LICENSE("GPL");