/* * max77865_charger.c * Samsung max77865 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. */ #define DEBUG #include #if defined(CONFIG_BATTERY_NOTIFIER) #include #endif #include #include #include #include #include #include "include/charger/max77865_charger.h" #ifdef CONFIG_USB_HOST_NOTIFY #include #endif #define ENABLE 1 #define DISABLE 0 #if defined(CONFIG_SEC_FACTORY) #define WC_CURRENT_WORK_STEP 250 #else #define WC_CURRENT_WORK_STEP 1000 #endif extern unsigned int lpcharge; static enum power_supply_property max77865_charger_props[] = { }; static enum power_supply_property max77865_otg_props[] = { POWER_SUPPLY_PROP_ONLINE, }; static struct device_attribute max77865_charger_attrs[] = { MAX77865_CHARGER_ATTR(chip_id), MAX77865_CHARGER_ATTR(data), }; static void max77865_charger_initialize(struct max77865_charger_data *charger); static int max77865_get_vbus_state(struct max77865_charger_data *charger); static int max77865_get_charger_state(struct max77865_charger_data *charger); static void max77865_set_charger_state(struct max77865_charger_data *charger, int enable); static void max77865_enable_aicl_irq(struct max77865_charger_data *charger); static bool max77865_charger_unlock(struct max77865_charger_data *charger) { u8 reg_data; u8 chgprot; int retry_cnt = 0; bool need_init = false; do { max77865_read_reg(charger->i2c, MAX77865_CHG_REG_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); max77865_update_reg(charger->i2c, MAX77865_CHG_REG_CNFG_06, (0x03 << 2), (0x03 << 2)); need_init = true; msleep(20); } else { pr_debug("%s: unlock success, chgprot(0x%x)\n", __func__, chgprot); break; } } while ((chgprot != 0x03) && (++retry_cnt < 10)); return need_init; } static void check_charger_unlock_state(struct max77865_charger_data *charger) { bool need_reg_init; pr_debug("%s\n", __func__); need_reg_init = max77865_charger_unlock(charger); if (need_reg_init) { pr_err("%s: charger locked state, reg init\n", __func__); max77865_charger_initialize(charger); } } static void max77865_test_read(struct max77865_charger_data *charger) { u8 data = 0; u32 addr = 0; char str[1024]={0,}; for (addr = 0xB1; addr <= 0xC3; addr++) { max77865_read_reg(charger->i2c, addr, &data); sprintf(str + strlen(str), "[0x%02x]0x%02x, ", addr, data); } pr_info("max77865 : %s\n", str); } static int max77865_get_vbus_state(struct max77865_charger_data *charger) { u8 reg_data; max77865_read_reg(charger->i2c, MAX77865_CHG_REG_DETAILS_00, ®_data); if (is_wireless_type(charger->cable_type)) reg_data = ((reg_data & MAX77865_WCIN_DTLS) >> MAX77865_WCIN_DTLS_SHIFT); else reg_data = ((reg_data & MAX77865_CHGIN_DTLS) >> MAX77865_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 max77865_get_charger_state(struct max77865_charger_data *charger) { int status = POWER_SUPPLY_STATUS_UNKNOWN; u8 reg_data; max77865_read_reg(charger->i2c, MAX77865_CHG_REG_DETAILS_01, ®_data); pr_info("%s : charger status (0x%02x)\n", __func__, reg_data); reg_data &= 0x0f; switch (reg_data) { case 0x00: case 0x01: case 0x02: status = POWER_SUPPLY_STATUS_CHARGING; break; case 0x03: case 0x04: status = POWER_SUPPLY_STATUS_FULL; break; case 0x05: case 0x06: case 0x07: status = POWER_SUPPLY_STATUS_NOT_CHARGING; break; case 0x08: case 0xA: case 0xB: status = POWER_SUPPLY_STATUS_DISCHARGING; break; default: status = POWER_SUPPLY_STATUS_UNKNOWN; break; } return (int)status; } static bool max77865_is_constant_current(struct max77865_charger_data *charger) { u8 reg_data; max77865_read_reg(charger->i2c, MAX77865_CHG_REG_DETAILS_01, ®_data); pr_info("%s : charger status (0x%02x)\n", __func__, reg_data); reg_data &= 0x0f; if (reg_data == 0x01) return true; return false; } static void max77865_set_float_voltage(struct max77865_charger_data *charger, int float_voltage) { u8 reg_data = 0; reg_data = float_voltage <= 40500 ? 0x0 : float_voltage >= 45000 ? 0x24 : (float_voltage - 40500) / 125; max77865_update_reg(charger->i2c, MAX77865_CHG_REG_CNFG_04, (reg_data << CHG_CNFG_04_CHG_CV_PRM_SHIFT), CHG_CNFG_04_CHG_CV_PRM_MASK); max77865_read_reg(charger->i2c, MAX77865_CHG_REG_CNFG_04, ®_data); pr_info("%s: battery cv voltage 0x%x\n", __func__, reg_data); } static int max77865_get_float_voltage(struct max77865_charger_data *charger) { u8 reg_data = 0; int float_voltage; max77865_read_reg(charger->i2c, MAX77865_CHG_REG_CNFG_04, ®_data); reg_data &= 0x3F; float_voltage = reg_data * 125 + 40500; pr_debug("%s: battery cv reg : 0x%x, float voltage val : %d\n", __func__, reg_data, float_voltage); return float_voltage; } static int max77865_get_charging_health(struct max77865_charger_data *charger) { int state = POWER_SUPPLY_HEALTH_GOOD; int vbus_state; int retry_cnt; u8 chg_dtls, reg_data; u8 chg_cnfg_00; union power_supply_propval value; /* watchdog kick */ max77865_update_reg(charger->i2c, MAX77865_CHG_REG_CNFG_06, MAX77865_WDTCLR, MAX77865_WDTCLR); max77865_read_reg(charger->i2c, MAX77865_CHG_REG_DETAILS_01, ®_data); reg_data = ((reg_data & MAX77865_BAT_DTLS) >> MAX77865_BAT_DTLS_SHIFT); pr_info("%s: reg_data(0x%x)\n", __func__, reg_data); switch (reg_data) { case 0x00: pr_info("%s: No battery and the charger is suspended\n", __func__); break; case 0x01: pr_info("%s: battery is okay " "but its voltage is low(~VPQLB)\n", __func__); break; case 0x02: pr_info("%s: battery dead\n", __func__); break; case 0x03: break; case 0x04: pr_info("%s: battery is okay" \ "but its voltage is low\n", __func__); break; case 0x05: pr_info("%s: battery ovp\n", __func__); break; default: pr_info("%s: battery unknown\n", __func__); break; } psy_do_property("battery", get, POWER_SUPPLY_PROP_HEALTH, value); /* VBUS OVP state return battery OVP state */ vbus_state = max77865_get_vbus_state(charger); /* read CHG_DTLS and detecting battery terminal error */ max77865_read_reg(charger->i2c, MAX77865_CHG_REG_DETAILS_01, &chg_dtls); chg_dtls = ((chg_dtls & MAX77865_CHG_DTLS) >> MAX77865_CHG_DTLS_SHIFT); max77865_read_reg(charger->i2c, MAX77865_CHG_REG_CNFG_00, &chg_cnfg_00); /* print the log at the abnormal case */ if((charger->is_charging == 1) && ((chg_dtls == 0x08) || (chg_dtls == 0x0B))) { max77865_test_read(charger); max77865_set_charger_state(charger, DISABLE); max77865_set_float_voltage(charger, charger->float_voltage); max77865_set_charger_state(charger, ENABLE); } pr_info("%s: vbus_state : 0x%x, chg_dtls : 0x%x\n", __func__, vbus_state, chg_dtls); /* OVP is higher priority */ if (vbus_state == 0x02) { /* CHGIN_OVLO */ pr_info("%s: vbus ovp\n", __func__); state = POWER_SUPPLY_HEALTH_OVERVOLTAGE; if (is_wireless_type(charger->cable_type)) { retry_cnt = 0; do { msleep(50); vbus_state = max77865_get_vbus_state(charger); } while((retry_cnt++ < 2) && (vbus_state == 0x02)); if (vbus_state == 0x02) { state = POWER_SUPPLY_HEALTH_OVERVOLTAGE; pr_info("%s: wpc and over-voltage\n", __func__); } else state = POWER_SUPPLY_HEALTH_GOOD; } } else if (((vbus_state == 0x0) || (vbus_state == 0x01)) && (chg_dtls & 0x08) && \ (chg_cnfg_00 & MAX77865_MODE_BUCK) && \ (chg_cnfg_00 & MAX77865_MODE_CHGR) && \ is_not_wireless_type(charger->cable_type)) { pr_info("%s: vbus is under\n", __func__); state = POWER_SUPPLY_HEALTH_UNDERVOLTAGE; } else if ((value.intval == POWER_SUPPLY_HEALTH_UNDERVOLTAGE) && \ ((vbus_state == 0x0) || (vbus_state == 0x01)) && \ is_not_wireless_type(charger->cable_type)) { pr_info("%s: keep under-voltage\n", __func__); state = POWER_SUPPLY_HEALTH_UNDERVOLTAGE; } return (int)state; } static int max77865_get_charge_current(struct max77865_charger_data *charger) { u8 reg_data; int get_current = 0; max77865_read_reg(charger->i2c, MAX77865_CHG_REG_CNFG_02, ®_data); reg_data &= MAX77865_CHG_CC; get_current = reg_data <= 0x2 ? 100 : reg_data * 50; return get_current; } static int max77865_get_input_current_type(struct max77865_charger_data *charger, int cable_type) { u8 reg_data; int get_current = 0; if (cable_type == SEC_BATTERY_CABLE_WIRELESS) { max77865_read_reg(charger->i2c, MAX77865_CHG_REG_CNFG_10, ®_data); /* AND operation for removing the formal 2bit */ reg_data &= 0x3F; if (reg_data <= 0x4) get_current = 100; else if (reg_data >= 0x3F) get_current = 1575; else get_current = reg_data * 25; } else { max77865_read_reg(charger->i2c, MAX77865_CHG_REG_CNFG_09, ®_data); /* AND operation for removing the formal 1bit */ reg_data &= 0x7F; if (reg_data <= 0x4) get_current = 100; else if (reg_data >= 0x7F) get_current = 3175; else get_current = reg_data * 25; } return get_current; } static int max77865_get_input_current(struct max77865_charger_data *charger) { if (is_wireless_type(charger->cable_type)) return max77865_get_input_current_type(charger, SEC_BATTERY_CABLE_WIRELESS); else return max77865_get_input_current_type(charger, SEC_BATTERY_CABLE_TA); } static void reduce_input_current(struct max77865_charger_data *charger, int cur) { u8 set_reg, set_value; unsigned int input_curr_limit_step = 25; if (is_wireless_type(charger->cable_type)) { set_reg = MAX77865_CHG_REG_CNFG_10; } else { set_reg = MAX77865_CHG_REG_CNFG_09; } if (!max77865_read_reg(charger->i2c, set_reg, &set_value)) { if (set_value <= (MINIMUM_INPUT_CURRENT / input_curr_limit_step) || set_value <= (cur / input_curr_limit_step)) return; set_value -= (cur / input_curr_limit_step); set_value = (set_value < (MINIMUM_INPUT_CURRENT / input_curr_limit_step)) ? (MINIMUM_INPUT_CURRENT / input_curr_limit_step) : set_value; max77865_write_reg(charger->i2c, set_reg, set_value); pr_info("%s: set current: reg:(0x%x), val:(0x%x)\n", __func__, set_reg, set_value); charger->input_current = max77865_get_input_current(charger); charger->aicl_on = true; } } static bool max77865_check_battery(struct max77865_charger_data *charger) { u8 reg_data; u8 reg_data2; max77865_read_reg(charger->i2c, MAX77865_CHG_REG_INT_OK, ®_data); pr_info("%s : CHG_INT_OK(0x%x)\n", __func__, reg_data); max77865_read_reg(charger->i2c, MAX77865_CHG_REG_DETAILS_00, ®_data2); pr_info("%s : CHG_DETAILS00(0x%x)\n", __func__, reg_data2); if ((reg_data & MAX77865_BATP_OK) || !(reg_data2 & MAX77865_BATP_DTLS)) return true; else return false; } static void max77865_set_buck(struct max77865_charger_data *charger, int enable) { u8 reg_data; if (enable) { max77865_update_reg(charger->i2c, MAX77865_CHG_REG_CNFG_00, CHG_CNFG_00_BUCK_MASK, CHG_CNFG_00_BUCK_MASK); } else { max77865_update_reg(charger->i2c, MAX77865_CHG_REG_CNFG_00, 0, CHG_CNFG_00_BUCK_MASK); } max77865_read_reg(charger->i2c, MAX77865_CHG_REG_CNFG_00, ®_data); pr_info("%s : CHG_CNFG_00(0x%02x)\n", __func__, reg_data); } static void max77865_change_charge_path(struct max77865_charger_data *charger, int path) { u8 cnfg12; if (is_wireless_type(path)) { cnfg12 = (0 << CHG_CNFG_12_CHGINSEL_SHIFT); } else { cnfg12 = (1 << CHG_CNFG_12_CHGINSEL_SHIFT); } max77865_update_reg(charger->i2c, MAX77865_CHG_REG_CNFG_12, cnfg12, CHG_CNFG_12_CHGINSEL_MASK); max77865_read_reg(charger->i2c, MAX77865_CHG_REG_CNFG_12, &cnfg12); pr_info("%s : CHG_CNFG_12(0x%02x)\n", __func__, cnfg12); } static void max77865_set_input_current(struct max77865_charger_data *charger, int input_current) { int curr_step = 25; u8 set_reg, reg_data; mutex_lock(&charger->charger_mutex); if (is_wireless_type(charger->cable_type)) { set_reg = MAX77865_CHG_REG_CNFG_10; max77865_read_reg(charger->i2c, set_reg, ®_data); reg_data &= ~MAX77865_CHG_WCIN_LIM; } else { set_reg = MAX77865_CHG_REG_CNFG_09; max77865_read_reg(charger->i2c, set_reg, ®_data); reg_data &= ~MAX77865_CHG_CHGIN_LIM; } if (!input_current) { max77865_write_reg(charger->i2c, set_reg, reg_data); } else if (is_wireless_type(charger->cable_type)) { input_current = (input_current > 1575) ? 1575 : input_current; reg_data |= (input_current / curr_step); max77865_write_reg(charger->i2c, set_reg, reg_data); } else { input_current = (input_current > 3175) ? 3175 : input_current; reg_data |= (input_current / curr_step); max77865_write_reg(charger->i2c, set_reg, reg_data); } mutex_unlock(&charger->charger_mutex); pr_info("[%s] REG(0x%02x) DATA(0x%02x), CURRENT(%d)\n", __func__, set_reg, reg_data, input_current); } static void max77865_set_charge_current(struct max77865_charger_data *charger, int fast_charging_current) { int curr_step = 50; u8 reg_data; max77865_read_reg(charger->i2c, MAX77865_CHG_REG_CNFG_02, ®_data); reg_data &= ~MAX77865_CHG_CC; if (!fast_charging_current) { max77865_write_reg(charger->i2c, MAX77865_CHG_REG_CNFG_02, reg_data); } else { fast_charging_current = (fast_charging_current > 3150) ? 3150 : fast_charging_current; reg_data |= (fast_charging_current / curr_step); max77865_write_reg(charger->i2c, MAX77865_CHG_REG_CNFG_02, reg_data); } pr_info("[%s] REG(0x%02x) DATA(0x%02x), CURRENT(%d)\n", __func__, MAX77865_CHG_REG_CNFG_02, reg_data, fast_charging_current); } static void max77865_set_wireless_input_current(struct max77865_charger_data *charger, int input_current) { union power_supply_propval value; wake_lock(&charger->wc_current_wake_lock); if (is_wireless_type(charger->cable_type)) { /* Wcurr-A) In cases of wireless input current change, configure the Vrect adj room to 270mV for safe wireless charging. */ wake_lock(&charger->wc_current_wake_lock); value.intval = WIRELESS_VRECT_ADJ_ROOM_1; /* 270mV */ psy_do_property(charger->pdata->wireless_charger_name, set, POWER_SUPPLY_PROP_INPUT_VOLTAGE_REGULATION, value); msleep(500); /* delay 0.5sec */ charger->wc_pre_current = max77865_get_input_current(charger); charger->wc_current = input_current; if (charger->wc_current > charger->wc_pre_current) { max77865_set_charge_current(charger, charger->charging_current); } } queue_delayed_work(charger->wqueue, &charger->wc_current_work, 0); } static void max77865_set_topoff_current(struct max77865_charger_data *charger, int termination_current) { int curr_base = 150, curr_step = 50; u8 reg_data; if (termination_current < curr_base) termination_current = curr_base; else if (termination_current > 500) termination_current = 500; reg_data = (termination_current - curr_base) / curr_step; max77865_update_reg(charger->i2c, MAX77865_CHG_REG_CNFG_03, reg_data, MAX77865_CHG_TO_ITH); pr_info("%s: reg_data(0x%02x), topoff(%dmA)\n", __func__, reg_data, termination_current); } static void max77865_set_charger_state(struct max77865_charger_data *charger, int enable) { u8 cnfg_00, cnfg_12; if (enable) { max77865_update_reg(charger->i2c, MAX77865_CHG_REG_CNFG_00, CHG_CNFG_00_CHG_MASK, CHG_CNFG_00_CHG_MASK); max77865_update_reg(charger->i2c, MAX77865_CHG_REG_CNFG_12, 0x1, CHG_CNFG_12_DISSKIP); } else { max77865_update_reg(charger->i2c, MAX77865_CHG_REG_CNFG_00, 0, CHG_CNFG_00_CHG_MASK); max77865_update_reg(charger->i2c, MAX77865_CHG_REG_CNFG_12, 0x0, CHG_CNFG_12_DISSKIP); } max77865_read_reg(charger->i2c, MAX77865_CHG_REG_CNFG_00, &cnfg_00); max77865_read_reg(charger->i2c, MAX77865_CHG_REG_CNFG_12, &cnfg_12); pr_info("%s : CHG_CNFG_00(0x%02x), CHG_CNFG_12(0x%02x)\n", __func__, cnfg_00, cnfg_12); } static void max77865_set_otg(struct max77865_charger_data *charger, int enable) { union power_supply_propval value; u8 reg = 0; static u8 chg_int_state; pr_info("%s: CHGIN-OTG %s\n", __func__, enable > 0 ? "on" : "off"); if (charger->otg_on == enable || lpcharge) return; wake_lock(&charger->otg_wake_lock); mutex_lock(&charger->charger_mutex); /* CHGIN-OTG */ value.intval = enable; if (enable) { psy_do_property("wireless", set, POWER_SUPPLY_PROP_CHARGE_OTG_CONTROL, value); max77865_read_reg(charger->i2c, MAX77865_CHG_REG_INT_MASK, &chg_int_state); /* disable charger interrupt: CHG_I, CHGIN_I */ /* enable charger interrupt: BYP_I */ max77865_update_reg(charger->i2c, MAX77865_CHG_REG_INT_MASK, MAX77865_CHG_IM | MAX77865_CHGIN_IM, MAX77865_CHG_IM | MAX77865_CHGIN_IM | MAX77865_BYP_IM); /* Update CHG_CNFG_11 to 0x16(5.020V) */ max77865_write_reg(charger->i2c, MAX77865_CHG_REG_CNFG_11, 0x16); if (charger->enable_boost_mode_wa) { /* OTG off, boost on */ max77865_update_reg(charger->i2c, MAX77865_CHG_REG_CNFG_00, (CHG_CNFG_00_BOOST_MASK | CHG_CNFG_00_CHG_MASK), CHG_CNFG_00_MODE_MASK); msleep(100); /* OTG on, boost on */ max77865_update_reg(charger->i2c, MAX77865_CHG_REG_CNFG_00, (CHG_CNFG_00_BOOST_MASK | CHG_CNFG_00_OTG_MASK), CHG_CNFG_00_MODE_MASK); } else { /* OTG off, boost on */ max77865_update_reg(charger->i2c, MAX77865_CHG_REG_CNFG_00, CHG_CNFG_00_BOOST_MASK, CHG_CNFG_00_OTG_CTRL); msleep(100); /* OTG on, boost on */ max77865_update_reg(charger->i2c, MAX77865_CHG_REG_CNFG_00, CHG_CNFG_00_OTG_CTRL, CHG_CNFG_00_OTG_CTRL); } } else { /* OTG off(UNO on), boost off */ if (charger->enable_boost_mode_wa) { max77865_update_reg(charger->i2c, MAX77865_CHG_REG_CNFG_00, CHG_CNFG_00_BUCK_MASK, CHG_CNFG_00_MODE_MASK); } else { max77865_update_reg(charger->i2c, MAX77865_CHG_REG_CNFG_00, 0, CHG_CNFG_00_OTG_CTRL); } /* Update CHG_CNFG_11 to 0x00(3.485V) */ max77865_write_reg(charger->i2c, MAX77865_CHG_REG_CNFG_11, 0x00); msleep(50); /* enable charger interrupt */ max77865_write_reg(charger->i2c, MAX77865_CHG_REG_INT_MASK, chg_int_state); psy_do_property("wireless", set, POWER_SUPPLY_PROP_CHARGE_OTG_CONTROL, value); } charger->otg_on = enable; max77865_read_reg(charger->i2c, MAX77865_CHG_REG_INT_MASK, &chg_int_state); max77865_read_reg(charger->i2c, MAX77865_CHG_REG_CNFG_00, ®); mutex_unlock(&charger->charger_mutex); wake_unlock(&charger->otg_wake_lock); pr_info("%s: INT_MASK(0x%x), CHG_CNFG_00(0x%x)\n", __func__, chg_int_state, reg); power_supply_changed(charger->psy_otg); } static void max77865_check_slow_charging(struct max77865_charger_data *charger, int input_current) { /* under 400mA considered as slow charging concept for VZW */ if (input_current <= charger->slow_charging_current && charger->cable_type != SEC_BATTERY_CABLE_NONE) { union power_supply_propval value; charger->slow_charging = true; pr_info("%s: slow charging on : input current(%dmA), cable type(%d)\n", __func__, input_current, charger->cable_type); psy_do_property("battery", set, POWER_SUPPLY_PROP_CHARGE_TYPE, value); } else charger->slow_charging = false; } static void max77865_charger_initialize(struct max77865_charger_data *charger) { u8 reg_data; int jig_gpio; pr_info("%s\n", __func__); /* unmasked: CHGIN_I, WCIN_I, BATP_I, BYP_I */ /*max77865_write_reg(charger->i2c, max77865_CHG_REG_INT_MASK, 0x9a);*/ /* unlock charger setting protect * slowest LX slope */ reg_data = (0x03 << 2); reg_data |= 0x60; max77865_update_reg(charger->i2c, MAX77865_CHG_REG_CNFG_06, reg_data, reg_data); /* * fast charge timer disable * restart threshold disable * pre-qual charge disable */ reg_data = (0x03 << 4); reg_data |= 0x08; max77865_write_reg(charger->i2c, MAX77865_CHG_REG_CNFG_01, reg_data); /* * charge current 450mA(default) * otg current limit 1500mA */ max77865_update_reg(charger->i2c, MAX77865_CHG_REG_CNFG_02, 0xC0, 0xC0); /* BAT to SYS OCP 4.50A */ max77865_update_reg(charger->i2c, MAX77865_CHG_REG_CNFG_05, charger->vsys_ocp, 0x07); /* Junction Temperature Thermal Regulation Loop Set point (130'C) */ max77865_update_reg(charger->i2c, MAX77865_CHG_REG_CNFG_07, 0x78, 0x78); /* * top off current 150mA * top off timer 30min */ reg_data = 0x18; max77865_write_reg(charger->i2c, MAX77865_CHG_REG_CNFG_03, reg_data); /* * cv voltage 4.3V or 4.35V */ max77865_set_float_voltage(charger, charger->pdata->chg_float_voltage); /* * VCHGIN & WCHGIN regulation threshold set 4.3v, uvlo 4.5V * Auto skip mode */ max77865_update_reg(charger->i2c, MAX77865_CHG_REG_CNFG_12, 0x12, CHG_CNFG_12_VCHGIN_REG_MASK|CHG_CNFG_12_WCIN_REG_MASK); /* Boost mode possible in FACTORY MODE */ max77865_update_reg(charger->i2c, MAX77865_CHG_REG_CNFG_07, MAX77865_CHG_FMBST, CHG_CNFG_07_REG_FMBST_MASK); if (charger->jig_low_active) jig_gpio = !gpio_get_value(charger->jig_gpio); else jig_gpio = gpio_get_value(charger->jig_gpio); max77865_update_reg(charger->i2c, MAX77865_CHG_REG_CNFG_07, (jig_gpio << CHG_CNFG_07_REG_FGSRC_SHIFT), CHG_CNFG_07_REG_FGSRC_MASK); /* Watchdog Enable */ max77865_update_reg(charger->i2c, MAX77865_CHG_REG_CNFG_00, MAX77865_WDTEN, MAX77865_WDTEN); /* Active Discharge Enable */ max77865_update_reg(charger->pmic_i2c, MAX77865_PMIC_REG_MAINCTRL1, 0x01, 0x01); //max77865_test_read(charger); } static void max77865_set_sysovlo(struct max77865_charger_data *charger, int enable) { u8 reg_data; max77865_read_reg(charger->pmic_i2c, MAX77865_PMIC_REG_SYSTEM_INT_MASK, ®_data); if (enable) { reg_data = reg_data & 0xDF; } else { reg_data = reg_data | 0x20; } max77865_write_reg(charger->pmic_i2c, MAX77865_PMIC_REG_SYSTEM_INT_MASK, reg_data); max77865_read_reg(charger->pmic_i2c, MAX77865_PMIC_REG_SYSTEM_INT_MASK, ®_data); pr_info("%s: check topsys irq mask(0x%x), enable(%d)\n", __func__, reg_data, enable); } static int max77865_chg_create_attrs(struct device *dev) { int i, rc; for (i = 0; i < ARRAY_SIZE(max77865_charger_attrs); i++) { rc = device_create_file(dev, &max77865_charger_attrs[i]); if (rc) goto create_attrs_failed; } return rc; create_attrs_failed: dev_err(dev, "%s: failed (%d)\n", __func__, rc); while (i--) device_remove_file(dev, &max77865_charger_attrs[i]); return rc; } ssize_t max77865_chg_show_attrs(struct device *dev, struct device_attribute *attr, char *buf) { struct power_supply *psy = dev_get_drvdata(dev); struct max77865_charger_data *charger = power_supply_get_drvdata(psy); const ptrdiff_t offset = attr - max77865_charger_attrs; int i = 0; u8 addr, data; switch(offset) { case CHIP_ID: max77865_read_reg(charger->pmic_i2c, MAX77865_PMIC_REG_PMICID1, &data); i += scnprintf(buf + i, PAGE_SIZE - i, "%x\n", data); break; case DATA: for (addr = 0xB1; addr <= 0xC3; addr++) { max77865_read_reg(charger->i2c, addr, &data); i += scnprintf(buf + i, PAGE_SIZE - i, "0x%02x : 0x%02x\n", addr, data); } break; default: return -EINVAL; } return i; } ssize_t max77865_chg_store_attrs(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct power_supply *psy = dev_get_drvdata(dev); struct max77865_charger_data *charger = power_supply_get_drvdata(psy); const ptrdiff_t offset = attr - max77865_charger_attrs; int ret = 0; int x,y; switch(offset) { case CHIP_ID: ret = count; break; case DATA: if (sscanf(buf, "0x%8x 0x%8x", &x, &y) == 2) { if (x >= 0xB1 && x <= 0xC3) { u8 addr = x; u8 data = y; if (max77865_write_reg(charger->i2c, addr, data) < 0) { dev_info(charger->dev, "%s: addr: 0x%x write fail\n", __func__, addr); } } else { dev_info(charger->dev, "%s: addr: 0x%x is wrong\n", __func__, x); } } ret = count; break; default: ret = -EINVAL; } return ret; } static int max77865_chg_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct max77865_charger_data *charger = power_supply_get_drvdata(psy); u8 reg_data; enum power_supply_ext_property ext_psp = psp; switch (psp) { case POWER_SUPPLY_PROP_ONLINE: val->intval = SEC_BATTERY_CABLE_NONE; if (max77865_read_reg(charger->i2c, MAX77865_CHG_REG_INT_OK, ®_data) == 0) { if (reg_data & MAX77865_WCIN_OK) { val->intval = SEC_BATTERY_CABLE_WIRELESS; charger->wc_w_state = 1; } else if (reg_data & MAX77865_CHGIN_OK) { val->intval = SEC_BATTERY_CABLE_TA; } } break; case POWER_SUPPLY_PROP_PRESENT: val->intval = max77865_check_battery(charger); break; case POWER_SUPPLY_PROP_STATUS: val->intval = max77865_get_charger_state(charger); break; case POWER_SUPPLY_PROP_CHARGE_TYPE: if (!charger->is_charging) val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; else if (charger->slow_charging) { 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_HEALTH: val->intval = max77865_get_charging_health(charger); max77865_test_read(charger); break; case POWER_SUPPLY_PROP_CURRENT_MAX: val->intval = charger->input_current; break; case POWER_SUPPLY_PROP_CURRENT_AVG: if (is_wireless_type(val->intval)) val->intval = max77865_get_input_current_type(charger, SEC_BATTERY_CABLE_WIRELESS); else val->intval = max77865_get_input_current_type(charger, SEC_BATTERY_CABLE_TA); break; case POWER_SUPPLY_PROP_CURRENT_NOW: val->intval = charger->charging_current; break; case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: val->intval = max77865_get_charge_current(charger); break; case POWER_SUPPLY_PROP_VOLTAGE_MAX: val->intval = max77865_get_float_voltage(charger); break; #if defined(CONFIG_AFC_CHARGER_MODE) case POWER_SUPPLY_PROP_AFC_CHARGER_MODE: return -ENODATA; #endif case POWER_SUPPLY_PROP_CHARGE_NOW: max77865_read_reg(charger->i2c, MAX77865_CHG_REG_DETAILS_01, ®_data); reg_data &= 0x0F; switch (reg_data) { case 0x01: val->strval = "CC Mode"; break; case 0x02: val->strval = "CV Mode"; break; case 0x03: val->strval = "EOC"; break; case 0x04: val->strval = "DONE"; break; default: val->strval = "NONE"; break; } break; case POWER_SUPPLY_PROP_CHARGE_OTG_CONTROL: mutex_lock(&charger->charger_mutex); val->intval = charger->otg_on; mutex_unlock(&charger->charger_mutex); break; case POWER_SUPPLY_PROP_CHARGE_COUNTER_SHADOW: break; case POWER_SUPPLY_PROP_CHARGE_UNO_CONTROL: max77865_read_reg(charger->i2c, MAX77865_CHG_REG_CNFG_00, ®_data); if ((reg_data & CHG_CNFG_00_UNO_CTRL) == CHG_CNFG_00_BOOST_MASK) val->intval = 1; else val->intval = 0; break; case POWER_SUPPLY_PROP_USB_HC: return -ENODATA; case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: if (max77865_is_constant_current(charger)) { val->intval = 0; } else { val->intval = 1; } break; case POWER_SUPPLY_PROP_MAX ... POWER_SUPPLY_EXT_PROP_MAX: switch (ext_psp) { case POWER_SUPPLY_EXT_PROP_CHIP_ID: if (max77865_read_reg(charger->i2c, MAX77865_PMIC_REG_PMICREV, ®_data) == 0) { val->intval = (charger->pmic_ver >= 0x1 && charger->pmic_ver <= 0x03); pr_info("%s : IF PMIC ver.0x%x\n", __func__, charger->pmic_ver); } else { val->intval = 0; pr_info("%s : IF PMIC I2C fail.\n", __func__); } break; default: return -EINVAL; } break; default: return -EINVAL; } return 0; } #if defined(CONFIG_UPDATE_BATTERY_DATA) static int max77865_charger_parse_dt(struct max77865_charger_data *charger); #endif static int max77865_chg_set_property(struct power_supply *psy, enum power_supply_property psp, const union power_supply_propval *val) { struct max77865_charger_data *charger = power_supply_get_drvdata(psy); u8 reg = 0; static u8 chg_int_state; int buck_state = ENABLE; enum power_supply_ext_property ext_psp = psp; switch (psp) { /* val->intval : type */ case POWER_SUPPLY_PROP_STATUS: charger->status = val->intval; break; case POWER_SUPPLY_PROP_CHARGING_ENABLED: /* check boost mode bit */ if ((charger->enable_boost_mode_wa) && (max77865_read_reg(charger->i2c, MAX77865_CHG_REG_CNFG_00, ®) == 0) && (reg & CHG_CNFG_00_BOOST_MASK)) { pr_info("%s: prevent charging mode when boost mode is on(reg:0x%x).\n", __func__, reg); break; } charger->charge_mode = val->intval; switch (charger->charge_mode) { case SEC_BAT_CHG_MODE_BUCK_OFF: buck_state = DISABLE; case SEC_BAT_CHG_MODE_CHARGING_OFF: charger->is_charging = false; break; case SEC_BAT_CHG_MODE_CHARGING: charger->is_charging = true; break; } max77865_set_buck(charger, buck_state); max77865_set_charger_state(charger, charger->is_charging); break; case POWER_SUPPLY_PROP_ONLINE: charger->cable_type = val->intval; charger->aicl_on = false; charger->slow_charging = false; charger->input_current = max77865_get_input_current(charger); max77865_change_charge_path(charger, charger->cable_type); if (charger->cable_type == SEC_BATTERY_CABLE_NONE) { charger->wc_pre_current = WC_CURRENT_START; wake_unlock(&charger->wpc_wake_lock); cancel_delayed_work(&charger->wpc_work); max77865_update_reg(charger->i2c, MAX77865_CHG_REG_INT_MASK, 0, MAX77865_WCIN_IM); if (charger->enable_sysovlo_irq) { max77865_set_sysovlo(charger, 1); } /* Enable AICL IRQ */ if (charger->irq_aicl_enabled == 0) { u8 reg_data; charger->irq_aicl_enabled = 1; enable_irq(charger->irq_aicl); max77865_read_reg(charger->i2c, MAX77865_CHG_REG_INT_MASK, ®_data); pr_info("%s : enable aicl : 0x%x\n", __func__, reg_data); } } else if (is_hv_wire_type(charger->cable_type) || (charger->cable_type == SEC_BATTERY_CABLE_HV_TA_CHG_LIMIT)) { /* Disable AICL IRQ */ if (charger->irq_aicl_enabled == 1) { u8 reg_data; charger->irq_aicl_enabled = 0; disable_irq_nosync(charger->irq_aicl); cancel_delayed_work(&charger->aicl_work); max77865_read_reg(charger->i2c, MAX77865_CHG_REG_INT_MASK, ®_data); pr_info("%s : disable aicl : 0x%x\n", __func__, reg_data); charger->aicl_on = false; charger->slow_charging = false; } } break; /* val->intval : input charging current */ case POWER_SUPPLY_PROP_CURRENT_MAX: { int input_current = val->intval; if (is_wireless_type(charger->cable_type)) { max77865_set_wireless_input_current(charger, input_current); } else { max77865_set_input_current(charger, input_current); } if (charger->cable_type == SEC_BATTERY_CABLE_NONE) max77865_set_wireless_input_current(charger, input_current); charger->input_current = input_current; } break; /* val->intval : charging current */ case POWER_SUPPLY_PROP_CURRENT_AVG: charger->charging_current = val->intval; max77865_set_charge_current(charger, val->intval); break; /* val->intval : charging current */ case POWER_SUPPLY_PROP_CURRENT_NOW: charger->charging_current = val->intval; if (is_not_wireless_type(charger->cable_type)) { max77865_set_charge_current(charger, charger->charging_current); } break; case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: break; case POWER_SUPPLY_PROP_CURRENT_FULL: max77865_set_topoff_current(charger, val->intval); break; #if defined(CONFIG_AFC_CHARGER_MODE) case POWER_SUPPLY_PROP_AFC_CHARGER_MODE: #if defined(CONFIG_MUIC_HV) max77865_hv_muic_charger_init(); #endif break; #endif #if defined(CONFIG_BATTERY_SWELLING) case POWER_SUPPLY_PROP_VOLTAGE_MAX: charger->float_voltage = val->intval; pr_info("%s: float voltage(%d)\n", __func__, val->intval); max77865_set_float_voltage(charger, val->intval); break; #endif case POWER_SUPPLY_PROP_USB_HC: break; case POWER_SUPPLY_PROP_ENERGY_NOW: /* if jig attached, change the power source from the VBATFG to the internal VSYS*/ max77865_update_reg(charger->i2c, MAX77865_CHG_REG_CNFG_07, (val->intval << CHG_CNFG_07_REG_FGSRC_SHIFT), CHG_CNFG_07_REG_FGSRC_MASK); break; case POWER_SUPPLY_PROP_CHARGE_COUNTER_SHADOW: charger->otg_on = false; break; case POWER_SUPPLY_PROP_CHARGE_OTG_CONTROL: max77865_set_otg(charger, val->intval); break; case POWER_SUPPLY_PROP_CHARGE_UNO_CONTROL: pr_info("%s: WCIN-UNO %s\n", __func__, val->intval > 0 ? "on" : "off"); /* WCIN-UNO */ if (val->intval) { max77865_read_reg(charger->i2c, MAX77865_CHG_REG_INT_MASK, &chg_int_state); /* disable charger interrupt: CHG_I, CHGIN_I */ /* enable charger interrupt: BYP_I */ max77865_update_reg(charger->i2c, MAX77865_CHG_REG_INT_MASK, MAX77865_CHG_IM | MAX77865_CHGIN_IM, MAX77865_CHG_IM | MAX77865_CHGIN_IM | MAX77865_BYP_IM); /* UNO on, boost on */ max77865_update_reg(charger->i2c, MAX77865_CHG_REG_CNFG_00, CHG_CNFG_00_BOOST_MASK, CHG_CNFG_00_UNO_CTRL); /* Update CHG_CNFG_11 to 0x16(5.020V) */ max77865_write_reg(charger->i2c, MAX77865_CHG_REG_CNFG_11, 0x16); } else { /* boost off*/ max77865_update_reg(charger->i2c, MAX77865_CHG_REG_CNFG_00, 0, CHG_CNFG_00_BOOST_MASK); /* Update CHG_CNFG_11 to 0x00(3.485V) */ max77865_write_reg(charger->i2c, MAX77865_CHG_REG_CNFG_11, 0x00); msleep(50); /* enable charger interrupt */ max77865_write_reg(charger->i2c, MAX77865_CHG_REG_INT_MASK, chg_int_state); } max77865_read_reg(charger->i2c, MAX77865_CHG_REG_INT_MASK, &chg_int_state); max77865_read_reg(charger->i2c, MAX77865_CHG_REG_CNFG_00, ®); pr_info("%s: INT_MASK(0x%x), CHG_CNFG_00(0x%x)\n", __func__, chg_int_state, reg); break; case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX: max77865_enable_aicl_irq(charger); max77865_read_reg(charger->i2c, MAX77865_CHG_REG_INT_OK, ®); if (reg & MAX77865_AICL_I) queue_delayed_work(charger->wqueue, &charger->aicl_work, msecs_to_jiffies(50)); break; #if defined(CONFIG_UPDATE_BATTERY_DATA) case POWER_SUPPLY_PROP_POWER_DESIGN: max77865_charger_parse_dt(charger); break; #endif case POWER_SUPPLY_PROP_MAX ... POWER_SUPPLY_EXT_PROP_MAX: switch (ext_psp) { case POWER_SUPPLY_EXT_PROP_SURGE: if (val->intval) { pr_info("%s : Charger IC reset by surge. charger re-initialize\n", __func__); check_charger_unlock_state(charger); } break; case POWER_SUPPLY_EXT_PROP_INBAT_VOLTAGE_FGSRC_SWITCHING: /* if jig attached, change the power source from the VBATFG to the internal VSYS*/ max77865_update_reg(charger->i2c, MAX77865_CHG_REG_CNFG_07, (val->intval << CHG_CNFG_07_REG_FGSRC_SHIFT), CHG_CNFG_07_REG_FGSRC_MASK); max77865_read_reg(charger->i2c, MAX77865_CHG_REG_CNFG_07, ®); pr_info("%s: POWER_SUPPLY_EXT_PROP_INBAT_VOLTAGE_FGSRC_SWITCHING: reg(0x%x) val(0x%x)\n", __func__, MAX77865_CHG_REG_CNFG_07, reg); break; case POWER_SUPPLY_EXT_PROP_WD_QBATTOFF: max77865_update_reg(charger->i2c, MAX77865_CHG_REG_CNFG_07, ((!!(val->intval)) << CHG_CNFG_07_REG_WD_QBATTOFF_SHIFT), CHG_CNFG_07_REG_WD_QBATTOFF_MASK); break; default: return -EINVAL; } break; default: return -EINVAL; } return 0; } static int max77865_otg_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct max77865_charger_data *charger = power_supply_get_drvdata(psy); switch (psp) { case POWER_SUPPLY_PROP_ONLINE: mutex_lock(&charger->charger_mutex); val->intval = charger->otg_on; mutex_unlock(&charger->charger_mutex); break; default: return -EINVAL; } return 0; } static int max77865_otg_set_property(struct power_supply *psy, enum power_supply_property psp, const union power_supply_propval *val) { struct max77865_charger_data *charger = power_supply_get_drvdata(psy); switch (psp) { case POWER_SUPPLY_PROP_ONLINE: max77865_set_otg(charger, val->intval); break; default: return -EINVAL; } return 0; } static int max77865_debugfs_show(struct seq_file *s, void *data) { struct max77865_charger_data *charger = s->private; u8 reg; u8 reg_data; seq_printf(s, "MAX77865 CHARGER IC :\n"); seq_printf(s, "===================\n"); for (reg = 0xB0; reg <= 0xC3; reg++) { max77865_read_reg(charger->i2c, reg, ®_data); seq_printf(s, "0x%02x:\t0x%02x\n", reg, reg_data); } seq_printf(s, "\n"); return 0; } static int max77865_debugfs_open(struct inode *inode, struct file *file) { return single_open(file, max77865_debugfs_show, inode->i_private); } static const struct file_operations max77865_debugfs_fops = { .open = max77865_debugfs_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static void max77865_chg_isr_work(struct work_struct *work) { struct max77865_charger_data *charger = container_of(work, struct max77865_charger_data, isr_work.work); union power_supply_propval val; if (charger->pdata->full_check_type == SEC_BATTERY_FULLCHARGED_CHGINT) { val.intval = max77865_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 = max77865_get_charging_health(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 irqreturn_t max77865_chg_irq_thread(int irq, void *irq_data) { struct max77865_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 we have external wireless TRx, enable WCIN interrupt to detect Mis-align only */ static void wpc_detect_work(struct work_struct *work) { struct max77865_charger_data *charger = container_of(work, struct max77865_charger_data, wpc_work.work); max77865_update_reg(charger->i2c, MAX77865_CHG_REG_INT_MASK, 0, MAX77865_WCIN_IM); if (is_wireless_type(charger->cable_type)) { u8 reg_data, wcin_state, wcin_dtls, wcin_cnt = 0; do { wcin_cnt++; max77865_read_reg(charger->i2c, MAX77865_CHG_REG_INT_OK, ®_data); wcin_state = (reg_data & MAX77865_WCIN_OK) >> MAX77865_WCIN_OK_SHIFT; max77865_read_reg(charger->i2c, MAX77865_CHG_REG_DETAILS_00, ®_data); wcin_dtls = (reg_data & MAX77865_WCIN_DTLS) >> MAX77865_WCIN_DTLS_SHIFT; if (!wcin_state && !wcin_dtls && wcin_cnt >= 2) { union power_supply_propval value; pr_info("%s: invalid WCIN, Misalign occurs!\n", __func__); value.intval = POWER_SUPPLY_STATUS_NOT_CHARGING; psy_do_property(charger->pdata->wireless_charger_name, set, POWER_SUPPLY_PROP_STATUS, value); } msleep(50); } while (!wcin_state && !wcin_dtls && wcin_cnt < 2); } /* Do unmask again. (for frequent wcin irq problem) */ max77865_update_reg(charger->i2c, MAX77865_CHG_REG_INT_MASK, 0, MAX77865_WCIN_IM); wake_unlock(&charger->wpc_wake_lock); } static irqreturn_t wpc_charger_irq(int irq, void *data) { struct max77865_charger_data *charger = data; pr_info("%s: irq(%d)\n", __func__, irq); max77865_update_reg(charger->i2c, MAX77865_CHG_REG_INT_MASK, MAX77865_WCIN_IM, MAX77865_WCIN_IM); wake_lock(&charger->wpc_wake_lock); queue_delayed_work(charger->wqueue, &charger->wpc_work, msecs_to_jiffies(10000)); return IRQ_HANDLED; } static irqreturn_t max77865_batp_irq(int irq, void *data) { struct max77865_charger_data *charger = data; union power_supply_propval value; u8 reg_data; pr_info("%s : irq(%d)\n", __func__, irq); max77865_read_reg(charger->i2c, MAX77865_CHG_REG_INT_MASK, ®_data); reg_data |= (1 << 2); max77865_write_reg(charger->i2c, MAX77865_CHG_REG_INT_MASK, reg_data); check_charger_unlock_state(charger); max77865_read_reg(charger->i2c, MAX77865_CHG_REG_INT_OK, ®_data); if (!(reg_data & MAX77865_BATP_OK)) psy_do_property("battery", set, POWER_SUPPLY_PROP_PRESENT, value); max77865_read_reg(charger->i2c, MAX77865_CHG_REG_INT_MASK, ®_data); reg_data &= ~(1 << 2); max77865_write_reg(charger->i2c, MAX77865_CHG_REG_INT_MASK, reg_data); return IRQ_HANDLED; } static irqreturn_t max77865_bypass_irq(int irq, void *data) { struct max77865_charger_data *charger = data; u8 dtls_02; u8 byp_dtls; u8 vbus_state; #ifdef CONFIG_USB_HOST_NOTIFY struct otg_notify *o_notify; o_notify = get_otg_notify(); #endif pr_info("%s: irq(%d)\n", __func__, irq); /* check and unlock */ check_charger_unlock_state(charger); max77865_read_reg(charger->i2c, MAX77865_CHG_REG_DETAILS_02, &dtls_02); byp_dtls = ((dtls_02 & MAX77865_BYP_DTLS) >> MAX77865_BYP_DTLS_SHIFT); pr_info("%s: BYP_DTLS(0x%02x)\n", __func__, byp_dtls); vbus_state = max77865_get_vbus_state(charger); if (byp_dtls & 0x1) { union power_supply_propval val; pr_info("%s: bypass overcurrent limit\n", __func__); #ifdef CONFIG_USB_HOST_NOTIFY if (o_notify) { send_otg_notify(o_notify, NOTIFY_EVENT_OVERCURRENT, 0); #ifdef CONFIG_USB_HW_PARAM inc_hw_param(o_notify, USB_CCIC_OVC_COUNT); #endif } #endif /* disable the register values just related to OTG and keep the values about the charging */ val.intval = 0; psy_do_property("otg", set, POWER_SUPPLY_PROP_ONLINE, val); } return IRQ_HANDLED; } static void max77865_aicl_isr_work(struct work_struct *work) { struct max77865_charger_data *charger = container_of(work, struct max77865_charger_data, aicl_work.work); u8 aicl_state, aicl_cnt = 0; pr_info("%s: \n", __func__); wake_lock(&charger->aicl_wake_lock); mutex_lock(&charger->charger_mutex); max77865_update_reg(charger->i2c, MAX77865_CHG_REG_INT_MASK, MAX77865_AICL_IM, MAX77865_AICL_IM); /* check and unlock */ check_charger_unlock_state(charger); max77865_read_reg(charger->i2c, MAX77865_CHG_REG_INT_OK, &aicl_state); while (!(aicl_state & 0x80) && charger->cable_type != SEC_BATTERY_CABLE_NONE) { if (++aicl_cnt >= 2) { reduce_input_current(charger, REDUCE_CURRENT_STEP); aicl_cnt = 0; } msleep(50); max77865_read_reg(charger->i2c, MAX77865_CHG_REG_INT_OK, &aicl_state); if (max77865_get_input_current(charger) <= MINIMUM_INPUT_CURRENT) break; } if (charger->aicl_on) { union power_supply_propval value; value.intval = max77865_get_input_current(charger); psy_do_property("battery", set, POWER_SUPPLY_EXT_PROP_AICL_CURRENT, value); if (is_not_wireless_type(charger->cable_type)) max77865_check_slow_charging(charger, charger->input_current); } max77865_update_reg(charger->i2c, MAX77865_CHG_REG_INT_MASK, 0, MAX77865_AICL_IM); mutex_unlock(&charger->charger_mutex); wake_unlock(&charger->aicl_wake_lock); } static irqreturn_t max77865_aicl_irq(int irq, void *data) { struct max77865_charger_data *charger = data; queue_delayed_work(charger->wqueue, &charger->aicl_work, msecs_to_jiffies(50)); pr_info("%s: irq(%d)\n", __func__, irq); wake_unlock(&charger->wc_current_wake_lock); cancel_delayed_work(&charger->wc_current_work); return IRQ_HANDLED; } static void max77865_enable_aicl_irq(struct max77865_charger_data *charger) { int ret; ret = request_threaded_irq(charger->irq_aicl, NULL, max77865_aicl_irq, 0, "aicl-irq", charger); if (ret < 0) { pr_err("%s: fail to request aicl IRQ: %d: %d\n", __func__, charger->irq_aicl, ret); charger->irq_aicl_enabled = -1; } else { max77865_update_reg(charger->i2c, MAX77865_CHG_REG_INT_MASK, 0, MAX77865_AICL_IM); charger->irq_aicl_enabled = 1; } pr_info("%s enabled : %d\n", __func__, charger->irq_aicl_enabled); } static void max77865_chgin_isr_work(struct work_struct *work) { struct max77865_charger_data *charger = container_of(work, struct max77865_charger_data, chgin_work); u8 chgin_dtls, chg_dtls, chg_cnfg_00; u8 prev_chgin_dtls = 0xff; int battery_health; union power_supply_propval value; int stable_count = 0; wake_lock(&charger->chgin_wake_lock); max77865_update_reg(charger->i2c, MAX77865_CHG_REG_INT_MASK, MAX77865_CHGIN_IM, MAX77865_CHGIN_IM); while (1) { psy_do_property("battery", get, POWER_SUPPLY_PROP_HEALTH, value); battery_health = value.intval; max77865_read_reg(charger->i2c, MAX77865_CHG_REG_DETAILS_00, &chgin_dtls); chgin_dtls = ((chgin_dtls & MAX77865_CHGIN_DTLS) >> MAX77865_CHGIN_DTLS_SHIFT); max77865_read_reg(charger->i2c, MAX77865_CHG_REG_DETAILS_01, &chg_dtls); chg_dtls = ((chg_dtls & MAX77865_CHG_DTLS) >> MAX77865_CHG_DTLS_SHIFT); max77865_read_reg(charger->i2c, MAX77865_CHG_REG_CNFG_00, &chg_cnfg_00); if (prev_chgin_dtls == chgin_dtls) stable_count++; else stable_count = 0; if (stable_count > 10) { pr_info("%s: irq(%d), chgin(0x%x), chg_dtls(0x%x) prev 0x%x\n", __func__, charger->irq_chgin, chgin_dtls, chg_dtls, prev_chgin_dtls); if (charger->is_charging) { if ((chgin_dtls == 0x02) && \ (battery_health != POWER_SUPPLY_HEALTH_OVERVOLTAGE)) { 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 (((chgin_dtls == 0x0) || (chgin_dtls == 0x01)) &&(chg_dtls & 0x08) && \ (chg_cnfg_00 & MAX77865_MODE_BUCK) && \ (chg_cnfg_00 & MAX77865_MODE_CHGR) && \ (battery_health != POWER_SUPPLY_HEALTH_UNDERVOLTAGE) && \ is_not_wireless_type(charger->cable_type)) { pr_info("%s, vbus_state : 0x%d, chg_state : 0x%d\n", __func__, chgin_dtls, chg_dtls); pr_info("%s: vBus is undervoltage\n", __func__); value.intval = POWER_SUPPLY_HEALTH_UNDERVOLTAGE; psy_do_property("battery", set, POWER_SUPPLY_PROP_HEALTH, value); } } else { if ((battery_health == \ POWER_SUPPLY_HEALTH_OVERVOLTAGE) && (chgin_dtls != 0x02)) { pr_info("%s: vbus_state : 0x%d, chg_state : 0x%d\n", __func__, chgin_dtls, chg_dtls); pr_info("%s: overvoltage->normal\n", __func__); value.intval = POWER_SUPPLY_HEALTH_GOOD; psy_do_property("battery", set, POWER_SUPPLY_PROP_HEALTH, value); } else if ((battery_health == \ POWER_SUPPLY_HEALTH_UNDERVOLTAGE) && !((chgin_dtls == 0x0) || (chgin_dtls == 0x01))){ pr_info("%s: vbus_state : 0x%d, chg_state : 0x%d\n", __func__, chgin_dtls, chg_dtls); pr_info("%s: undervoltage->normal\n", __func__); value.intval = POWER_SUPPLY_HEALTH_GOOD; psy_do_property("battery", set, POWER_SUPPLY_PROP_HEALTH, value); max77865_set_input_current(charger, charger->input_current); } } break; } prev_chgin_dtls = chgin_dtls; msleep(100); } max77865_update_reg(charger->i2c, MAX77865_CHG_REG_INT_MASK, 0, MAX77865_CHGIN_IM); wake_unlock(&charger->chgin_wake_lock); } static irqreturn_t max77865_chgin_irq(int irq, void *data) { struct max77865_charger_data *charger = data; queue_work(charger->wqueue, &charger->chgin_work); return IRQ_HANDLED; } /* register chgin isr after sec_battery_probe */ static void max77865_chgin_init_work(struct work_struct *work) { struct max77865_charger_data *charger = container_of(work, struct max77865_charger_data, chgin_init_work.work); int ret; pr_info("%s \n", __func__); ret = request_threaded_irq(charger->irq_chgin, NULL, max77865_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); } else { max77865_update_reg(charger->i2c, MAX77865_CHG_REG_INT_MASK, 0, MAX77865_CHGIN_IM); } } static void max77865_wc_current_work(struct work_struct *work) { struct max77865_charger_data *charger = container_of(work, struct max77865_charger_data, wc_current_work.work); int diff_current = 0; if (is_not_wireless_type(charger->cable_type)) { charger->wc_pre_current = WC_CURRENT_START; max77865_write_reg(charger->i2c, MAX77865_CHG_REG_CNFG_10, 0x10); wake_unlock(&charger->wc_current_wake_lock); return; } if (charger->wc_pre_current == charger->wc_current) { union power_supply_propval value; max77865_set_charge_current(charger, charger->charging_current); /* Wcurr-B) Restore Vrect adj room to previous value after finishing wireless input current setting. Refer to Wcurr-A) step */ msleep(500); if (is_nv_wireless_type(charger->cable_type)) { psy_do_property("battery", get, POWER_SUPPLY_PROP_CAPACITY, value); if (value.intval < charger->pdata->wireless_cc_cv) value.intval = WIRELESS_VRECT_ADJ_ROOM_4; /* WPC 4.5W, Vrect Room 30mV */ else value.intval = WIRELESS_VRECT_ADJ_ROOM_5; /* WPC 4.5W, Vrect Room 80mV */ } else if (is_hv_wireless_type(charger->cable_type)) { value.intval = WIRELESS_VRECT_ADJ_ROOM_5; /* WPC 9W, Vrect Room 80mV */ } else value.intval = WIRELESS_VRECT_ADJ_OFF; /* PMA 4.5W, Vrect Room 0mV */ psy_do_property(charger->pdata->wireless_charger_name, set, POWER_SUPPLY_PROP_INPUT_VOLTAGE_REGULATION, value); wake_unlock(&charger->wc_current_wake_lock); } else { if (charger->wc_pre_current > charger->wc_current) { diff_current = charger->wc_pre_current - charger->wc_current; if (diff_current < WC_CURRENT_STEP) charger->wc_pre_current -= diff_current; else charger->wc_pre_current -= WC_CURRENT_STEP; } else { diff_current = charger->wc_current - charger->wc_pre_current; if (diff_current < WC_CURRENT_STEP) charger->wc_pre_current += diff_current; else charger->wc_pre_current += WC_CURRENT_STEP; } max77865_set_input_current(charger, charger->wc_pre_current); queue_delayed_work(charger->wqueue, &charger->wc_current_work, msecs_to_jiffies(WC_CURRENT_WORK_STEP)); } pr_info("%s: wc_current(%d), wc_pre_current(%d), diff(%d)\n", __func__, charger->wc_current, charger->wc_pre_current, diff_current); } static irqreturn_t max77865_sysovlo_irq(int irq, void *data) { struct max77865_charger_data *charger = data; union power_supply_propval value; pr_info("%s \n", __func__); wake_lock_timeout(&charger->sysovlo_wake_lock, HZ * 5); psy_do_property("battery", set, POWER_SUPPLY_EXT_PROP_SYSOVLO, value); max77865_set_sysovlo(charger, 0); return IRQ_HANDLED; } static irqreturn_t max77865_chg_bat_irq(int irq, void *data) { struct max77865_charger_data *charger = data; u8 bat_dtls; pr_info("%s: \n", __func__); max77865_read_reg(charger->i2c, MAX77865_CHG_REG_DETAILS_01, &bat_dtls); bat_dtls = (bat_dtls & 0x70) >> 4; pr_info("%s: read bat dtls(0x%2x)\n", __func__, bat_dtls); if (bat_dtls == 0x6) { //panic("BATTERY - OCP"); pr_info("%s: BATTERY - OCP\n", __func__); } return IRQ_HANDLED; } static irqreturn_t max77865_systm_irq(int irq, void *data) { struct max77865_charger_data *charger = data; u8 treg_state; pr_info("%s \n", __func__); max77865_read_reg(charger->i2c, MAX77865_CHG_REG_DETAILS_01, &treg_state); treg_state = treg_state >> 7;; pr_info("%s: read treg(0x%2x)\n", __func__, treg_state); if (treg_state) { //panic("BATTERY - TREG"); pr_info("%s: BATTERY - TREG\n", __func__); } return IRQ_HANDLED; } static irqreturn_t max77865_chg_irq(int irq, void *data) { struct max77865_charger_data *charger = data; u8 chg_dtls; max77865_read_reg(charger->i2c, MAX77865_CHG_REG_DETAILS_01, &chg_dtls); chg_dtls = chg_dtls & 0x0F; pr_info("%s: chg dtls(0x%x)\n", __func__, chg_dtls); if (chg_dtls == 0x0B) { pr_info("%s: watchdog timer expiration\n", __func__); max77865_test_read(charger); } return IRQ_HANDLED; } #ifdef CONFIG_OF static int max77865_charger_parse_dt(struct max77865_charger_data *charger) { struct device_node *np; sec_charger_platform_data_t *pdata = charger->pdata; int ret = 0; np = of_find_node_by_name(NULL, "battery"); if (!np) { pr_err("%s np NULL\n", __func__); } else { charger->enable_sysovlo_irq = of_property_read_bool(np, "battery,enable_sysovlo_irq"); ret = of_property_read_u32(np, "battery,chg_float_voltage", &pdata->chg_float_voltage); if (ret) { pr_info("%s: battery,chg_float_voltage is Empty\n", __func__); pdata->chg_float_voltage = 42000; } pr_info("%s: battery,chg_float_voltage is %d\n", __func__, pdata->chg_float_voltage); charger->float_voltage = pdata->chg_float_voltage; ret = of_property_read_string(np, "battery,wireless_charger_name", (char const **)&pdata->wireless_charger_name); if (ret) pr_info("%s: Wireless charger name is Empty\n", __func__); ret = of_property_read_u32(np, "battery,full_check_type_2nd", &pdata->full_check_type_2nd); if (ret) pr_info("%s : Full check type 2nd is Empty\n", __func__); ret = of_property_read_u32(np, "battery,wireless_cc_cv", &pdata->wireless_cc_cv); if (ret) pr_info("%s : wireless_cc_cv is Empty\n", __func__); } np = of_find_node_by_name(NULL, "max77865-charger"); if (!np) { pr_err("%s: np(max77865-charger) is NULL\n", __func__); } else { charger->enable_boost_mode_wa = of_property_read_bool(np, "charger,enable_boost_mode_wa"); ret = of_property_read_u8(np, "charger,vsys_ocp", &charger->vsys_ocp); if (ret) { pr_info("%s : default vsys ocp\n", __func__); charger->vsys_ocp = 0x04; } ret = of_property_read_u32(np, "charger,slow_charging_current", &charger->slow_charging_current); if (ret) { pr_info("%s : slow_charging_current is Empty\n", __func__); charger->slow_charging_current = SLOW_CHARGING_CURRENT_STANDARD; } } np = of_find_node_by_name(NULL, "max77865-fuelgauge"); if (!np) { pr_err("%s: np(max77865_fuelgauge) is NULL\n", __func__); } else { charger->jig_low_active = of_property_read_bool(np, "fuelgauge,jig_low_active"); charger->jig_gpio = of_get_named_gpio(np, "fuelgauge,jig_gpio", 0); if (charger->jig_gpio < 0) { pr_err("%s error reading jig_gpio = %d\n", __func__, charger->jig_gpio); charger->jig_gpio = 0; } } return ret; } #endif static const struct power_supply_desc max77865_charger_power_supply_desc = { .name = "max77865-charger", .type = POWER_SUPPLY_TYPE_UNKNOWN, .properties = max77865_charger_props, .num_properties = ARRAY_SIZE(max77865_charger_props), .get_property = max77865_chg_get_property, .set_property = max77865_chg_set_property, .no_thermal = true, }; static const struct power_supply_desc otg_power_supply_desc = { .name = "otg", .type = POWER_SUPPLY_TYPE_OTG, .properties = max77865_otg_props, .num_properties = ARRAY_SIZE(max77865_otg_props), .get_property = max77865_otg_get_property, .set_property = max77865_otg_set_property, }; static int max77865_charger_probe(struct platform_device *pdev) { struct max77865_dev *max77865 = dev_get_drvdata(pdev->dev.parent); struct max77865_platform_data *pdata = dev_get_platdata(max77865->dev); sec_charger_platform_data_t *charger_data; struct max77865_charger_data *charger; struct power_supply_config charger_cfg = {}; int ret = 0; u8 reg_data; pr_info("%s: max77865 Charger Driver Loading\n", __func__); charger = kzalloc(sizeof(*charger), GFP_KERNEL); if (!charger) return -ENOMEM; charger_data = kzalloc(sizeof(sec_charger_platform_data_t), GFP_KERNEL); if (!charger_data) { ret = -ENOMEM; goto err_free; } mutex_init(&charger->charger_mutex); charger->dev = &pdev->dev; charger->i2c = max77865->charger; charger->pmic_i2c = max77865->i2c; charger->pdata = charger_data; charger->aicl_on = false; charger->slow_charging = false; charger->is_mdock = false; charger->otg_on = false; charger->max77865_pdata = pdata; charger->wc_pre_current = WC_CURRENT_START; charger->vsys_ocp = 0x04; #if defined(CONFIG_OF) ret = max77865_charger_parse_dt(charger); if (ret < 0) { pr_err("%s not found charger dt! ret[%d]\n", __func__, ret); } #endif platform_set_drvdata(pdev, charger); max77865_charger_initialize(charger); max77865_read_reg(charger->i2c, MAX77865_CHG_REG_INT_OK, ®_data); if (reg_data & MAX77865_WCIN_OK) charger->cable_type = SEC_BATTERY_CABLE_WIRELESS; charger->input_current = max77865_get_input_current(charger); charger->charging_current = max77865_get_charge_current(charger); if (max77865_read_reg(max77865->i2c, MAX77865_PMIC_REG_PMICREV, ®_data) < 0) { pr_err("device not found on this channel (this is not an error)\n"); ret = -ENOMEM; goto err_pdata_free; } else { charger->pmic_ver = (reg_data & 0x7); pr_info("%s : device found : ver.0x%x\n", __func__, charger->pmic_ver); } (void) debugfs_create_file("max77865-regs", S_IRUGO, NULL, (void *)charger, &max77865_debugfs_fops); charger->wqueue = create_singlethread_workqueue(dev_name(&pdev->dev)); if (!charger->wqueue) { pr_err("%s: Fail to Create Workqueue\n", __func__); goto err_pdata_free; } wake_lock_init(&charger->chgin_wake_lock, WAKE_LOCK_SUSPEND, "charger->chgin"); INIT_WORK(&charger->chgin_work, max77865_chgin_isr_work); wake_lock_init(&charger->aicl_wake_lock, WAKE_LOCK_SUSPEND, "charger-aicl"); INIT_DELAYED_WORK(&charger->aicl_work, max77865_aicl_isr_work); INIT_DELAYED_WORK(&charger->chgin_init_work, max77865_chgin_init_work); wake_lock_init(&charger->wpc_wake_lock, WAKE_LOCK_SUSPEND, "charger-wpc"); INIT_DELAYED_WORK(&charger->wpc_work, wpc_detect_work); wake_lock_init(&charger->wc_current_wake_lock, WAKE_LOCK_SUSPEND, "charger->wc-current"); INIT_DELAYED_WORK(&charger->wc_current_work, max77865_wc_current_work); wake_lock_init(&charger->otg_wake_lock, WAKE_LOCK_SUSPEND, "otg-path"); charger_cfg.drv_data = charger; charger->psy_chg = power_supply_register(&pdev->dev, &max77865_charger_power_supply_desc, &charger_cfg); if (!charger->psy_chg) { pr_err("%s: Failed to Register psy_chg\n", __func__); goto err_power_supply_register; } charger->psy_otg = power_supply_register(&pdev->dev, &otg_power_supply_desc, &charger_cfg); if (!charger->psy_otg) { pr_err("%s: Failed to Register otg_chg\n", __func__); goto err_power_supply_register_otg; } if (charger->pdata->chg_irq) { INIT_DELAYED_WORK(&charger->isr_work, max77865_chg_isr_work); ret = request_threaded_irq(charger->pdata->chg_irq, NULL, max77865_chg_irq_thread, charger->pdata->chg_irq_attr, "charger-irq", charger); if (ret) { pr_err("%s: Failed to Request IRQ\n", __func__); goto err_irq; } ret = enable_irq_wake(charger->pdata->chg_irq); if (ret < 0) pr_err("%s: Failed to Enable Wakeup Source(%d)\n", __func__, ret); } charger->wc_w_irq = pdata->irq_base + MAX77865_CHG_IRQ_WCIN_I; ret = request_threaded_irq(charger->wc_w_irq, NULL, wpc_charger_irq, IRQF_TRIGGER_FALLING, "wpc-int", charger); if (ret) { pr_err("%s: Failed to Request IRQ\n", __func__); goto err_wc_irq; } max77865_read_reg(charger->i2c, MAX77865_CHG_REG_INT_OK, ®_data); charger->wc_w_state = (reg_data & MAX77865_WCIN_OK) >> MAX77865_WCIN_OK_SHIFT; charger->irq_chgin = pdata->irq_base + MAX77865_CHG_IRQ_CHGIN_I; /* enable chgin irq after sec_battery_probe */ queue_delayed_work(charger->wqueue, &charger->chgin_init_work, msecs_to_jiffies(3000)); charger->irq_bypass = pdata->irq_base + MAX77865_CHG_IRQ_BYP_I; ret = request_threaded_irq(charger->irq_bypass, NULL, max77865_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); } else { max77865_update_reg(charger->i2c, MAX77865_CHG_REG_INT_MASK, 0, MAX77865_BYP_IM); } charger->irq_aicl_enabled = -1; charger->irq_aicl = pdata->irq_base + MAX77865_CHG_IRQ_AICL_I; charger->irq_batp = pdata->irq_base + MAX77865_CHG_IRQ_BATP_I; ret = request_threaded_irq(charger->irq_batp, NULL, max77865_batp_irq, 0, "batp-irq", charger); if (ret < 0) pr_err("%s: fail to request bypass IRQ: %d: %d\n", __func__, charger->irq_batp, ret); if (charger->enable_sysovlo_irq) { wake_lock_init(&charger->sysovlo_wake_lock, WAKE_LOCK_SUSPEND, "max77865-sysovlo"); /* Enable BIAS */ max77865_update_reg(max77865->i2c, MAX77865_PMIC_REG_MAINCTRL1, 0x80, 0x80); /* set IRQ thread */ charger->irq_sysovlo = pdata->irq_base + MAX77865_SYSTEM_IRQ_SYSOVLO_INT; ret = request_threaded_irq(charger->irq_sysovlo, NULL, max77865_sysovlo_irq, 0, "sysovlo-irq", charger); if (ret < 0) { pr_err("%s: fail to request sysovlo IRQ: %d: %d\n", __func__, charger->irq_sysovlo, ret); } enable_irq_wake(charger->irq_sysovlo); } charger->irq_bat = pdata->irq_base + MAX77865_CHG_IRQ_BAT_I; ret = request_threaded_irq(charger->irq_bat, NULL, max77865_chg_bat_irq, 0, "bat-irq", charger); if (ret < 0) pr_err("%s: fail to request battery IRQ: %d: %d\n", __func__, charger->irq_bat, ret); charger->irq_tm = pdata->irq_base + MAX77865_SYSTEM_IRQ_TM_INT; ret = request_threaded_irq(charger->irq_tm, NULL, max77865_systm_irq, 0, "sys-tm", charger); if (ret < 0) pr_err("%s: fail to request tm IRQ: %d: %d\n", __func__, charger->irq_tm, ret); charger->irq_chg = pdata->irq_base + MAX77865_CHG_IRQ_CHG_I; ret = request_threaded_irq(charger->irq_chg, NULL, max77865_chg_irq, 0, "chg-ok", charger); if (ret < 0) pr_err("%s: fail to request chg IRQ: %d: %d\n", __func__, charger->irq_chg, ret); ret = max77865_chg_create_attrs(&charger->psy_chg->dev); if (ret) { dev_err(charger->dev, "%s : Failed to create_attrs\n", __func__); goto err_wc_irq; } /* watchdog kick */ max77865_update_reg(charger->i2c, MAX77865_CHG_REG_CNFG_06, MAX77865_WDTCLR, MAX77865_WDTCLR); pr_info("%s: MAX77865 Charger Driver Loaded\n", __func__); return 0; err_wc_irq: free_irq(charger->pdata->chg_irq, NULL); err_irq: power_supply_unregister(charger->psy_otg); err_power_supply_register_otg: power_supply_unregister(charger->psy_chg); err_power_supply_register: destroy_workqueue(charger->wqueue); err_pdata_free: kfree(charger_data); err_free: kfree(charger); return ret; } static int max77865_charger_remove(struct platform_device *pdev) { struct max77865_charger_data *charger = platform_get_drvdata(pdev); destroy_workqueue(charger->wqueue); if (charger->enable_sysovlo_irq) { free_irq(charger->irq_sysovlo, NULL); } free_irq(charger->wc_w_irq, NULL); free_irq(charger->pdata->chg_irq, NULL); power_supply_unregister(charger->psy_chg); power_supply_unregister(charger->psy_otg); kfree(charger); return 0; } #if defined CONFIG_PM static int max77865_charger_suspend(struct device *dev) { return 0; } static int max77865_charger_resume(struct device *dev) { return 0; } #else #define max77865_charger_suspend NULL #define max77865_charger_resume NULL #endif static void max77865_charger_shutdown(struct platform_device *pdev) { struct max77865_charger_data *charger = platform_get_drvdata(pdev); u8 reg_data; pr_info("%s: max77865 Charger driver shutdown\n", __func__); if (!charger->i2c) { pr_err("%s: no max77865 i2c client\n", __func__); return; } reg_data = 0x04; max77865_write_reg(charger->i2c, MAX77865_CHG_REG_CNFG_00, reg_data); reg_data = 0x0F; max77865_write_reg(charger->i2c, MAX77865_CHG_REG_CNFG_09, reg_data); reg_data = 0x10; max77865_write_reg(charger->i2c, MAX77865_CHG_REG_CNFG_10, reg_data); reg_data = 0x60; max77865_write_reg(charger->i2c, MAX77865_CHG_REG_CNFG_12, reg_data); /* Disable WD_QBATTOFF */ reg_data = 0x00; max77865_update_reg(charger->i2c, MAX77865_CHG_REG_CNFG_07, reg_data, CHG_CNFG_07_REG_WD_QBATTOFF_MASK); pr_info("func:%s \n", __func__); } static SIMPLE_DEV_PM_OPS(max77865_charger_pm_ops, max77865_charger_suspend, max77865_charger_resume); static struct platform_driver max77865_charger_driver = { .driver = { .name = "max77865-charger", .owner = THIS_MODULE, #ifdef CONFIG_PM .pm = &max77865_charger_pm_ops, #endif }, .probe = max77865_charger_probe, .remove = max77865_charger_remove, .shutdown = max77865_charger_shutdown, }; static int __init max77865_charger_init(void) { pr_info("%s : \n", __func__); return platform_driver_register(&max77865_charger_driver); } static void __exit max77865_charger_exit(void) { platform_driver_unregister(&max77865_charger_driver); } module_init(max77865_charger_init); module_exit(max77865_charger_exit); MODULE_DESCRIPTION("Samsung MAX77865 Charger Driver"); MODULE_AUTHOR("Samsung Electronics"); MODULE_LICENSE("GPL");