| /* |
| * max77833_charger.c |
| * Samsung MAX77833 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 <linux/of.h> |
| #include <linux/of_gpio.h> |
| |
| #include <linux/mfd/max77833-private.h> |
| #include <linux/debugfs.h> |
| #include <linux/seq_file.h> |
| #include <linux/power_supply.h> |
| #include <linux/mfd/max77833.h> |
| #ifdef CONFIG_USB_HOST_NOTIFY |
| #include <linux/usb_notify.h> |
| #endif |
| |
| #define ENABLE 1 |
| #define DISABLE 0 |
| |
| static enum power_supply_property max77833_charger_props[] = { |
| POWER_SUPPLY_PROP_STATUS, |
| POWER_SUPPLY_PROP_PRESENT, |
| 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, |
| POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, |
| POWER_SUPPLY_PROP_CHARGE_OTG_CONTROL, |
| POWER_SUPPLY_PROP_USB_HC, |
| #if defined(CONFIG_BATTERY_SWELLING) || defined(CONFIG_BATTERY_SWELLING_SELF_DISCHARGING) |
| POWER_SUPPLY_PROP_VOLTAGE_MAX, |
| #endif |
| #if defined(CONFIG_AFC_CHARGER_MODE) |
| POWER_SUPPLY_PROP_AFC_CHARGER_MODE, |
| #endif |
| POWER_SUPPLY_PROP_CHARGE_NOW, |
| }; |
| |
| static struct device_attribute max77833_charger_attrs[] = { |
| MAX77833_CHARGER_ATTR(chip_id), |
| }; |
| |
| static void max77833_charger_initialize(struct max77833_charger_data *charger); |
| static int max77833_get_vbus_state(struct max77833_charger_data *charger); |
| static int max77833_get_charger_state(struct max77833_charger_data *charger); |
| static void max77833_set_charger_state(struct max77833_charger_data *charger, |
| int enable); |
| static bool max77833_charger_unlock(struct max77833_charger_data *charger) |
| { |
| u8 reg_data; |
| u8 chgprot; |
| int retry_cnt = 0; |
| bool need_init = false; |
| |
| do { |
| max77833_read_reg(charger->i2c, MAX77833_CHG_REG_PROTECT, ®_data); |
| chgprot = reg_data & 0x03; |
| if (chgprot != 0x03) { |
| pr_err("%s: unlock err, chgprot(0x%x), retry(%d)\n", |
| __func__, chgprot, retry_cnt); |
| max77833_write_reg(charger->i2c, MAX77833_CHG_REG_PROTECT, |
| 0x03); |
| 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 max77833_charger_data *charger) |
| { |
| bool need_reg_init; |
| pr_debug("%s\n", __func__); |
| |
| need_reg_init = max77833_charger_unlock(charger); |
| if (need_reg_init) { |
| pr_err("%s: charger locked state, reg init\n", __func__); |
| max77833_charger_initialize(charger); |
| } |
| } |
| |
| static void max77833_test_read(struct max77833_charger_data *charger) |
| { |
| u8 data = 0; |
| u32 addr = 0; |
| for (addr = 0x81; addr <= 0x9D; addr++) { |
| max77833_read_reg(charger->i2c, addr, &data); |
| pr_debug("MAX77833 addr : 0x%02x data : 0x%02x\n", addr, data); |
| } |
| } |
| |
| static int max77833_get_vbus_state(struct max77833_charger_data *charger) |
| { |
| u8 reg_data; |
| union power_supply_propval value; |
| |
| max77833_read_reg(charger->i2c, |
| MAX77833_CHG_REG_DTLS_00, ®_data); |
| |
| psy_do_property("battery", get, POWER_SUPPLY_PROP_ONLINE, |
| value); |
| if (value.intval == POWER_SUPPLY_TYPE_WIRELESS) |
| reg_data = ((reg_data & MAX77833_WCIN_DTLS) >> |
| MAX77833_WCIN_DTLS_SHIFT); |
| else |
| reg_data = ((reg_data & MAX77833_CHGIN_DTLS) >> |
| MAX77833_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 max77833_get_charger_state(struct max77833_charger_data *charger) |
| { |
| int status = POWER_SUPPLY_STATUS_UNKNOWN; |
| u8 reg_data; |
| |
| max77833_read_reg(charger->i2c, |
| MAX77833_CHG_REG_DTLS_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 int max77833_get_charging_health(struct max77833_charger_data *charger) |
| { |
| int state; |
| int vbus_state; |
| int retry_cnt; |
| u8 chg_dtls_00, chg_dtls, reg_data; |
| u8 chg_cnfg_00, chg_cnfg_04 ,chg_cnfg_05, chg_cnfg_06, chg_cnfg_16, chg_cnfg_18; |
| |
| max77833_read_reg(charger->i2c, |
| MAX77833_CHG_REG_DTLS_01, ®_data); |
| reg_data = ((reg_data & MAX77833_BAT_DTLS) >> MAX77833_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__); |
| state = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; |
| break; |
| case 0x01: |
| pr_info("%s: battery is okay " |
| "but its voltage is low(~VPQLB)\n", __func__); |
| state = POWER_SUPPLY_HEALTH_GOOD; |
| break; |
| case 0x02: |
| pr_info("%s: battery dead\n", __func__); |
| state = POWER_SUPPLY_HEALTH_DEAD; |
| break; |
| case 0x03: |
| 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 0x07: |
| pr_info("%s: battery voltage information not available\n", |
| __func__); |
| state = POWER_SUPPLY_HEALTH_UNKNOWN; |
| 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) { |
| union power_supply_propval value; |
| psy_do_property("battery", get, |
| POWER_SUPPLY_PROP_HEALTH, value); |
| /* VBUS OVP state return battery OVP state */ |
| vbus_state = max77833_get_vbus_state(charger); |
| /* read CHG_DTLS and detecting battery terminal error */ |
| max77833_read_reg(charger->i2c, |
| MAX77833_CHG_REG_DTLS_01, &chg_dtls); |
| chg_dtls = ((chg_dtls & MAX77833_CHG_DTLS) >> |
| MAX77833_CHG_DTLS_SHIFT); |
| max77833_read_reg(charger->i2c, |
| MAX77833_CHG_REG_CNFG_00, &chg_cnfg_00); |
| |
| /* print the log at the abnormal case */ |
| if((charger->is_charging == 1) && (chg_dtls & 0x08)) { |
| max77833_read_reg(charger->i2c, |
| MAX77833_CHG_REG_DTLS_00, &chg_dtls_00); |
| max77833_read_reg(charger->i2c, |
| MAX77833_CHG_REG_CNFG_04, &chg_cnfg_04); |
| max77833_read_reg(charger->i2c, |
| MAX77833_CHG_REG_CNFG_05, &chg_cnfg_05); |
| max77833_read_reg(charger->i2c, |
| MAX77833_CHG_REG_CNFG_06, &chg_cnfg_06); |
| max77833_read_reg(charger->i2c, |
| MAX77833_CHG_REG_CNFG_16, &chg_cnfg_16); |
| max77833_read_reg(charger->i2c, |
| MAX77833_CHG_REG_CNFG_18, &chg_cnfg_18); |
| |
| pr_info("%s: CHG_DTLS_00(0x%x), CHG_DTLS_01(0x%x), CHG_CNFG_00(0x%x)\n", |
| __func__, chg_dtls_00, chg_dtls, chg_cnfg_00); |
| pr_info("%s: CHG_CNFG_04(0x%x), CHG_CNFG_05(0x%x), CHG_CNFG_06(0x%x)\n", |
| __func__, chg_cnfg_04, chg_cnfg_05, chg_cnfg_06); |
| pr_info("%s: CHG_CNFG_16(0x%x), CHG_CNFG_18(0x%x)\n", |
| __func__, chg_cnfg_16, chg_cnfg_18); |
| max77833_set_charger_state(charger, 0); |
| max77833_set_charger_state(charger, 1); |
| } |
| |
| pr_info("%s: vbus_state : 0x%d, chg_dtls : 0x%d\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 (charger->cable_type == POWER_SUPPLY_TYPE_WIRELESS) { |
| retry_cnt = 0; |
| do { |
| msleep(50); |
| vbus_state = max77833_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 & MAX77833_MODE_BUCK) && \ |
| (chg_cnfg_00 & MAX77833_MODE_CHGR) && \ |
| (charger->cable_type != POWER_SUPPLY_TYPE_WIRELESS)) { |
| 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)) && \ |
| (charger->cable_type != POWER_SUPPLY_TYPE_WIRELESS)) { |
| pr_info("%s: keep under-voltage\n", __func__); |
| state = POWER_SUPPLY_HEALTH_UNDERVOLTAGE; |
| } |
| } |
| |
| return (int)state; |
| } |
| |
| static u8 max77833_get_float_voltage_data(int float_voltage) |
| { |
| int voltage = 3000; |
| int i; |
| |
| for (i = 0; voltage <= 4500; i++) { |
| if (float_voltage <= voltage) |
| break; |
| voltage += 10; |
| } |
| |
| return i; |
| } |
| |
| static int max77833_get_input_current(struct max77833_charger_data *charger) |
| { |
| u8 reg_data; |
| int get_current = 0; |
| |
| if (charger->cable_type == POWER_SUPPLY_TYPE_WIRELESS) { |
| max77833_read_reg(charger->i2c, |
| MAX77833_CHG_REG_CNFG_17, ®_data); |
| /* AND operation for removing the formal 2bit */ |
| reg_data = reg_data & 0x7F; |
| |
| if (reg_data <= 0x3) |
| get_current = 75; |
| else if (reg_data >= 0x50) |
| get_current = 2000; |
| else |
| get_current = reg_data * 25; |
| } else { |
| max77833_read_reg(charger->i2c, |
| MAX77833_CHG_REG_CNFG_16, ®_data); |
| /* AND operation for removing the formal 1bit */ |
| |
| if (reg_data <= 0x3) |
| get_current = 75; |
| else if (reg_data >= 0xA0) |
| get_current = 4000; |
| else |
| get_current = reg_data * 25; |
| } |
| |
| return get_current; |
| } |
| |
| static bool max77833_check_battery(struct max77833_charger_data *charger) |
| { |
| u8 reg_data; |
| u8 reg_data2; |
| |
| max77833_read_reg(charger->i2c, |
| MAX77833_CHG_REG_INT_OK, ®_data); |
| |
| pr_info("%s : CHG_INT_OK(0x%x)\n", __func__, reg_data); |
| |
| max77833_read_reg(charger->i2c, |
| MAX77833_CHG_REG_DTLS_00, ®_data2); |
| |
| pr_info("%s : CHG_DETAILS00(0x%x)\n", __func__, reg_data2); |
| |
| if ((reg_data & MAX77833_BATP_OK) || |
| !(reg_data2 & MAX77833_BATP_DTLS)) |
| return true; |
| else |
| return false; |
| } |
| |
| static void max77833_set_buck(struct max77833_charger_data *charger, |
| int enable) |
| { |
| u8 reg_data; |
| |
| if (enable) { |
| max77833_update_reg(charger->i2c, MAX77833_CHG_REG_CNFG_00, |
| CHG_CNFG_00_BUCK_MASK, CHG_CNFG_00_BUCK_MASK); |
| } else { |
| max77833_update_reg(charger->i2c, MAX77833_CHG_REG_CNFG_00, |
| 0, CHG_CNFG_00_BUCK_MASK); |
| } |
| max77833_read_reg(charger->i2c, MAX77833_CHG_REG_CNFG_00, ®_data); |
| pr_debug("%s : CHG_CNFG_00(0x%02x)\n", __func__, reg_data); |
| } |
| |
| static void max77833_check_slow_charging(struct max77833_charger_data *charger, |
| int input_current) |
| { |
| /* under 400mA considered as slow charging concept for VZW */ |
| if (input_current <= SLOW_CHARGING_CURRENT_STANDARD && |
| charger->cable_type != POWER_SUPPLY_TYPE_BATTERY) { |
| union power_supply_propval value; |
| |
| charger->aicl_on = 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->aicl_on = false; |
| } |
| |
| static void max77833_set_input_current(struct max77833_charger_data *charger, |
| int input_current) |
| { |
| u8 set_reg, reg_data; |
| |
| mutex_lock(&charger->charger_mutex); |
| if (charger->cable_type == POWER_SUPPLY_TYPE_WIRELESS) { |
| set_reg = MAX77833_CHG_REG_CNFG_17; |
| max77833_read_reg(charger->i2c, |
| set_reg, ®_data); |
| reg_data &= ~MAX77833_CHG_WCIN_LIM; |
| } else { |
| set_reg = MAX77833_CHG_REG_CNFG_16; |
| max77833_read_reg(charger->i2c, |
| set_reg, ®_data); |
| } |
| |
| if (input_current <= 0) |
| max77833_set_buck(charger, DISABLE); |
| else { |
| max77833_set_buck(charger, ENABLE); |
| } |
| |
| if (!input_current) { |
| max77833_write_reg(charger->i2c, |
| set_reg, reg_data); |
| } else if(charger->cable_type == POWER_SUPPLY_TYPE_WIRELESS) { |
| if (input_current >= 2000) |
| reg_data = 0x50; |
| else |
| reg_data = input_current / 25; |
| max77833_write_reg(charger->i2c, |
| set_reg, reg_data); |
| } else { |
| input_current = (input_current > charger->charging_current_max) ? |
| charger->charging_current_max : input_current; |
| |
| if (input_current >= 4000) |
| reg_data = 0xA0; |
| else |
| reg_data = input_current / 25; |
| max77833_write_reg(charger->i2c, |
| set_reg, reg_data); |
| } |
| |
| mutex_unlock(&charger->charger_mutex); |
| pr_info("[%s] REG(0x%02x) DATA(0x%02x)\n", |
| __func__, set_reg, reg_data); |
| } |
| |
| static void max77833_set_charge_current(struct max77833_charger_data *charger, |
| int fast_charging_current) |
| { |
| int curr_step = 50; |
| u8 reg_data; |
| |
| max77833_read_reg(charger->i2c, |
| MAX77833_CHG_REG_CNFG_05, ®_data); |
| |
| if (!fast_charging_current) { |
| max77833_write_reg(charger->i2c, |
| MAX77833_CHG_REG_CNFG_05, 0x00); |
| } else { |
| reg_data = fast_charging_current / curr_step; |
| max77833_write_reg(charger->i2c,MAX77833_CHG_REG_CNFG_05, reg_data); |
| } |
| |
| pr_info("[%s] REG(0x%02x) DATA(0x%02x), CURRENT(%d)\n", |
| __func__, MAX77833_CHG_REG_CNFG_05, |
| reg_data, fast_charging_current); |
| } |
| |
| static int max77833_check_aicl_state(struct max77833_charger_data *charger) |
| { |
| u8 aicl_state; |
| if (!max77833_read_reg(charger->i2c, MAX77833_CHG_REG_INT_OK, &aicl_state)) { |
| pr_info("%s aicl state \n", __func__); |
| return !(aicl_state & 0x80); |
| } |
| return 0; |
| } |
| |
| static void max77833_set_current(struct max77833_charger_data *charger) |
| { |
| int current_now = charger->charging_current, |
| current_max = charger->charging_current_max; |
| int usb_charging_current = charger->pdata->charging_current[ |
| POWER_SUPPLY_TYPE_USB].fast_charging_current; |
| |
| pr_info("%s: siop_level=%d, afc_detec=%d, current_max=%d, current_now=%d\n", |
| __func__, charger->siop_level, charger->afc_detect, current_max, current_now); |
| |
| if (charger->is_charging) { |
| /* decrease the charging current according to siop level */ |
| current_now = current_now * charger->siop_level / 100; |
| |
| /* do forced set charging current */ |
| if (current_now > 0 && current_now < usb_charging_current) |
| current_now = usb_charging_current; |
| |
| if (charger->siop_level < 100) { |
| if (charger->cable_type == POWER_SUPPLY_TYPE_WIRELESS) { |
| current_max = SIOP_WIRELESS_INPUT_LIMIT_CURRENT; |
| if (current_now > SIOP_WIRELESS_CHARGING_LIMIT_CURRENT) |
| current_now = SIOP_WIRELESS_CHARGING_LIMIT_CURRENT; |
| } else if (charger->cable_type == POWER_SUPPLY_TYPE_HV_MAINS || |
| charger->cable_type == POWER_SUPPLY_TYPE_HV_ERR){ |
| if (current_max > SIOP_HV_INPUT_LIMIT_CURRENT) |
| current_max = SIOP_HV_INPUT_LIMIT_CURRENT; |
| if (current_now > SIOP_HV_CHARGING_LIMIT_CURRENT) |
| current_now = SIOP_HV_CHARGING_LIMIT_CURRENT; |
| } else { |
| if (current_max > SIOP_INPUT_LIMIT_CURRENT) |
| current_max = SIOP_INPUT_LIMIT_CURRENT; |
| if (current_now > SIOP_CHARGING_LIMIT_CURRENT) |
| current_now = SIOP_CHARGING_LIMIT_CURRENT; |
| } |
| } |
| } |
| |
| pr_info("%s: siop_level=%d, afc_detec=%d, current_max=%d, current_now=%d\n", |
| __func__, charger->siop_level, charger->afc_detect, current_max, current_now); |
| |
| if (max77833_check_aicl_state(charger)) { |
| wake_lock(&charger->aicl_wake_lock); |
| queue_delayed_work(charger->wqueue, &charger->aicl_work, |
| msecs_to_jiffies(50)); |
| } |
| |
| max77833_set_charge_current(charger, current_now); |
| max77833_set_input_current(charger, current_max); |
| |
| max77833_test_read(charger); |
| } |
| |
| static void afc_detect_work(struct work_struct *work) |
| { |
| struct max77833_charger_data *charger = container_of(work, |
| struct max77833_charger_data, |
| afc_work.work); |
| pr_info("%s\n", __func__); |
| |
| if ((charger->cable_type == POWER_SUPPLY_TYPE_MAINS) && charger->is_charging && charger->afc_detect) { |
| charger->afc_detect = false; |
| |
| if (charger->charging_current_max >= INPUT_CURRENT_TA) { |
| charger->charging_current_max = charger->pdata->charging_current[ |
| POWER_SUPPLY_TYPE_MAINS].input_current_limit; |
| } |
| pr_info("%s: current_max(%d)\n", __func__, charger->charging_current_max); |
| max77833_set_current(charger); |
| } |
| } |
| |
| static void max77833_set_topoff_current(struct max77833_charger_data *charger, |
| int termination_current, |
| int termination_time) |
| { |
| int curr_base, curr_step; |
| u8 reg_data; |
| |
| curr_base = 125; |
| curr_step = 75; |
| |
| if (termination_current < curr_base) |
| termination_current = curr_base; |
| else if (termination_current > 650) |
| termination_current = 650; |
| |
| reg_data = (termination_current - curr_base) / curr_step; |
| max77833_update_reg(charger->i2c, MAX77833_CHG_REG_CNFG_02, |
| reg_data, 0x7); |
| |
| pr_info("%s: reg_data(0x%02x), topoff(%d)\n", |
| __func__, reg_data, termination_current); |
| } |
| |
| static void max77833_set_charger_state(struct max77833_charger_data *charger, |
| int enable) |
| { |
| u8 reg_data; |
| |
| max77833_read_reg(charger->i2c, MAX77833_CHG_REG_CNFG_00, ®_data); |
| |
| if (enable) { |
| max77833_update_reg(charger->i2c, MAX77833_CHG_REG_CNFG_00, |
| CHG_CNFG_00_CHG_MASK, CHG_CNFG_00_CHG_MASK); |
| } else { |
| max77833_update_reg(charger->i2c, MAX77833_CHG_REG_CNFG_00, |
| 0, CHG_CNFG_00_CHG_MASK); |
| } |
| max77833_read_reg(charger->i2c, MAX77833_CHG_REG_CNFG_00, ®_data); |
| pr_debug("%s : CHG_CNFG_00(0x%02x)\n", __func__, reg_data); |
| } |
| |
| static void reduce_input_current(struct max77833_charger_data *charger, int cur) |
| { |
| u8 set_value; |
| unsigned int min_input_current = 0; |
| |
| min_input_current = MINIMUM_INPUT_CURRENT; |
| |
| if (!max77833_read_reg(charger->i2c, MAX77833_CHG_REG_CNFG_16, &set_value)) { |
| if ((set_value <= (min_input_current / charger->input_curr_limit_step)) || |
| (set_value <= (cur / charger->input_curr_limit_step))) |
| return; |
| set_value -= (cur / charger->input_curr_limit_step); |
| set_value = (set_value < (min_input_current / charger->input_curr_limit_step)) ? |
| (min_input_current / charger->input_curr_limit_step) : set_value; |
| max77833_write_reg(charger->i2c, MAX77833_CHG_REG_CNFG_16, set_value); |
| charger->charging_current_max = max77833_get_input_current(charger); |
| pr_info("%s: set current: reg:(0x%x), val:(0x%x), input_current(%d)\n", |
| __func__, MAX77833_CHG_REG_CNFG_16, set_value, charger->charging_current_max); |
| } |
| } |
| |
| static void max77833_charger_function_control( |
| struct max77833_charger_data *charger) |
| { |
| u8 chg_cnfg_00 = 0; |
| union power_supply_propval value; |
| union power_supply_propval chg_mode; |
| union power_supply_propval swelling_state; |
| union power_supply_propval battery_status; |
| |
| psy_do_property("battery", get, POWER_SUPPLY_PROP_ONLINE, value); |
| psy_do_property("battery", get, POWER_SUPPLY_PROP_CHARGE_COUNTER_SHADOW, battery_status); |
| |
| /* OTG_EN control */ |
| if (charger->pdata->otg_en) { |
| if (battery_status.intval == POWER_SUPPLY_TYPE_USB) { |
| gpio_direction_output(charger->pdata->otg_en, 1); |
| pr_info("%s: OTG_EN set to HIGH. cable(%d)\n", __func__, value.intval); |
| } else { |
| if (value.intval != POWER_SUPPLY_TYPE_WIRELESS) { |
| gpio_direction_output(charger->pdata->otg_en, 0); |
| pr_info("%s: OTG_EN set to LOW. cable(%d)\n", __func__, value.intval); |
| } |
| } |
| } |
| |
| psy_do_property("battery", get, POWER_SUPPLY_PROP_HEALTH, value); |
| |
| pr_info("####%s####\n", __func__); |
| |
| if (charger->cable_type == POWER_SUPPLY_TYPE_BATTERY || |
| charger->cable_type == POWER_SUPPLY_TYPE_OTG) { |
| charger->is_charging = false; |
| charger->afc_detect = false; |
| charger->aicl_on = false; |
| charger->is_mdock = false; |
| charger->charging_current = 0; |
| |
| if ((charger->status == POWER_SUPPLY_STATUS_DISCHARGING) || |
| (value.intval == POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) || |
| (value.intval == POWER_SUPPLY_HEALTH_OVERHEATLIMIT)) { |
| charger->charging_current_max = |
| ((value.intval == POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) || \ |
| (value.intval == POWER_SUPPLY_HEALTH_OVERHEATLIMIT)) ? |
| 0 : charger->pdata->charging_current[POWER_SUPPLY_TYPE_USB].input_current_limit; |
| } |
| |
| if (charger->cable_type == POWER_SUPPLY_TYPE_OTG) { |
| /* OTG_EN set to HIGH */ |
| if (charger->pdata->otg_en) { |
| gpio_direction_output(charger->pdata->otg_en, 1); |
| pr_info("%s: OTG_EN set to HIGH. cable type OTG\n", __func__); |
| } |
| |
| chg_cnfg_00 |= (CHG_CNFG_00_OTG_MASK |
| | CHG_CNFG_00_BOOST_MASK); |
| |
| chg_cnfg_00 &= ~(CHG_CNFG_00_BUCK_MASK); |
| |
| max77833_update_reg(charger->i2c, |
| MAX77833_CHG_REG_CNFG_00, |
| chg_cnfg_00, |
| (CHG_CNFG_00_OTG_MASK | |
| CHG_CNFG_00_BOOST_MASK | |
| CHG_CNFG_00_BUCK_MASK)); |
| } else { |
| if (charger->status == POWER_SUPPLY_STATUS_DISCHARGING) { /* cable type battery + discharging */ |
| max77833_write_reg(charger->i2c, MAX77833_CHG_REG_CNFG_07, 0x00); |
| pr_info("%s: Vsysmin set to 3.0V. cable(%d)\n", __func__, charger->cable_type); |
| } |
| psy_do_property("battery", get, POWER_SUPPLY_PROP_CHARGE_COUNTER_SHADOW, value); |
| /* OTG_EN set to LOW */ |
| if (charger->pdata->otg_en && value.intval != POWER_SUPPLY_TYPE_USB) { |
| gpio_direction_output(charger->pdata->otg_en, 0); |
| pr_info("%s: OTG_EN set to LOW. cable type BATTERY\n", __func__); |
| } |
| |
| chg_cnfg_00 &= ~(CHG_CNFG_00_CHG_MASK |
| | CHG_CNFG_00_OTG_MASK |
| | CHG_CNFG_00_BOOST_MASK); |
| |
| max77833_update_reg(charger->i2c, |
| MAX77833_CHG_REG_CNFG_00, |
| chg_cnfg_00, |
| (CHG_CNFG_00_CHG_MASK | |
| CHG_CNFG_00_OTG_MASK | |
| CHG_CNFG_00_BOOST_MASK)); |
| } |
| } else { |
| if (charger->cable_type == POWER_SUPPLY_TYPE_HMT_CONNECTED) |
| charger->is_charging = false; |
| else |
| charger->is_charging = true; |
| charger->afc_detect = false; |
| charger->charging_current_max = |
| charger->pdata->charging_current |
| [charger->cable_type].input_current_limit; |
| charger->charging_current = |
| charger->pdata->charging_current |
| [charger->cable_type].fast_charging_current; |
| if (charger->is_mdock) { /* if mdock was alread inserted, then check OTG, or NOTG state */ |
| if (charger->cable_type == POWER_SUPPLY_TYPE_SMART_NOTG) { |
| charger->charging_current = |
| charger->pdata->charging_current |
| [POWER_SUPPLY_TYPE_MDOCK_TA].fast_charging_current; |
| charger->charging_current_max = |
| charger->pdata->charging_current |
| [POWER_SUPPLY_TYPE_MDOCK_TA].input_current_limit; |
| } else if (charger->cable_type == POWER_SUPPLY_TYPE_SMART_OTG) { |
| charger->charging_current = |
| charger->pdata->charging_current |
| [POWER_SUPPLY_TYPE_MDOCK_TA].fast_charging_current - 500; |
| charger->charging_current_max = |
| charger->pdata->charging_current |
| [POWER_SUPPLY_TYPE_MDOCK_TA].input_current_limit - 500; |
| } |
| } else { /*if mdock wasn't inserted, then check mdock state*/ |
| if (charger->cable_type == POWER_SUPPLY_TYPE_MDOCK_TA) |
| charger->is_mdock = true; |
| } |
| |
| if (charger->cable_type == POWER_SUPPLY_TYPE_MAINS) { |
| charger->afc_detect = true; |
| charger->charging_current_max = INPUT_CURRENT_TA; |
| queue_delayed_work(charger->wqueue, &charger->afc_work, msecs_to_jiffies(2000)); |
| wake_lock_timeout(&charger->afc_wake_lock, HZ * 3); |
| } |
| } |
| |
| pr_info("charging = %d, fc = %d, il = %d, t1 = %d, t2 = %d, cable = %d\n", |
| charger->is_charging, |
| charger->charging_current, |
| charger->charging_current_max, |
| charger->pdata->charging_current[charger->cable_type].full_check_current_1st, |
| charger->pdata->charging_current[charger->cable_type].full_check_current_2nd, |
| charger->cable_type); |
| |
| if (charger->pdata->full_check_type_2nd == SEC_BATTERY_FULLCHARGED_CHGPSY) { |
| psy_do_property("battery", get, |
| POWER_SUPPLY_PROP_CHARGE_NOW, |
| chg_mode); |
| #if defined(CONFIG_BATTERY_SWELLING) |
| psy_do_property("battery", get, |
| POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT, |
| swelling_state); |
| #else |
| swelling_state.intval = 0; |
| #endif |
| if (chg_mode.intval == SEC_BATTERY_CHARGING_2ND || swelling_state.intval) { |
| max77833_set_charger_state(charger, 0); |
| max77833_set_topoff_current(charger, |
| charger->pdata->charging_current[ |
| charger->cable_type].full_check_current_2nd, |
| (70 * 60)); |
| } else { |
| max77833_set_topoff_current(charger, |
| charger->pdata->charging_current[ |
| charger->cable_type].full_check_current_1st, |
| (70 * 60)); |
| } |
| } else { |
| max77833_set_topoff_current(charger, |
| charger->pdata->charging_current[ |
| charger->cable_type].full_check_current_1st, |
| charger->pdata->charging_current[ |
| charger->cable_type].full_check_current_2nd); |
| } |
| |
| max77833_set_charger_state(charger, charger->is_charging); |
| |
| |
| pr_info("charging = %d, fc = %d, il = %d, t1 = %d, t2 = %d, cable = %d\n", |
| charger->is_charging, |
| charger->charging_current, |
| charger->charging_current_max, |
| charger->pdata->charging_current[charger->cable_type].full_check_current_1st, |
| charger->pdata->charging_current[charger->cable_type].full_check_current_2nd, |
| charger->cable_type); |
| |
| max77833_test_read(charger); |
| |
| } |
| |
| static void max77833_charger_initialize(struct max77833_charger_data *charger) |
| { |
| u8 reg_data; |
| pr_info("%s\n", __func__); |
| |
| /* unmasked: CHGIN_I, WCIN_I, BATP_I, BYP_I */ |
| /*max77833_write_reg(charger->i2c, MAX77833_CHG_REG_INT_MASK, 0x9a);*/ |
| |
| /* unlock charger setting protect */ |
| reg_data = 0x03; |
| max77833_write_reg(charger->i2c, MAX77833_CHG_REG_PROTECT, reg_data); |
| |
| /* |
| * fast charge timer disable |
| * restart threshold disable |
| * pre-qual charge enable(default) |
| */ |
| reg_data = (0x03 << 4); |
| max77833_write_reg(charger->i2c, MAX77833_CHG_REG_CNFG_04, reg_data); |
| |
| /* |
| * top off current 125mA |
| * top off timer 70min |
| * otg current limit 1200mA |
| */ |
| reg_data = 0xB8; |
| max77833_write_reg(charger->i2c, MAX77833_CHG_REG_CNFG_02, reg_data); |
| |
| /* |
| * cv voltage 4.2V or 4.35V |
| * MINVSYS 3.6V(default) |
| */ |
| reg_data = max77833_get_float_voltage_data(charger->pdata->chg_float_voltage); |
| max77833_write_word(charger->i2c, MAX77833_CHG_REG_CNFG_06, reg_data); |
| pr_info("%s: battery cv voltage 0x%x\n", __func__, reg_data); |
| |
| /* |
| * CHGIN falling AICL threshold 4.3V(default) |
| */ |
| reg_data = 0x03; |
| max77833_write_word(charger->i2c, MAX77833_CHG_REG_CNFG_11, reg_data); |
| pr_info("%s: CHGIN AICL threshold 0x%x\n", __func__, reg_data); |
| |
| /* SYS_OCT_ACT = 0 */ |
| max77833_update_reg(charger->i2c, MAX77833_CHG_REG_CNFG_15, 0, 1); |
| |
| max77833_test_read(charger); |
| } |
| |
| #if defined(CONFIG_BATTERY_SWELLING) || defined(CONFIG_BATTERY_SWELLING_SELF_DISCHARGING) |
| static void max77833_set_float_voltage(struct max77833_charger_data *charger, int float_voltage) |
| { |
| u8 reg_data = 0; |
| |
| reg_data = max77833_get_float_voltage_data(float_voltage); |
| max77833_write_word(charger->i2c, MAX77833_CHG_REG_CNFG_06, reg_data); |
| charger->pdata->chg_float_voltage = float_voltage; |
| pr_info("%s: battery cv voltage 0x%x, chg_float_voltage = %dmV \n", __func__, reg_data, charger->pdata->chg_float_voltage); |
| } |
| |
| static u8 max77833_get_float_voltage(struct max77833_charger_data *charger) |
| { |
| u8 reg_data = 0; |
| |
| max77833_read_reg(charger->i2c, MAX77833_CHG_REG_CNFG_06, ®_data); |
| pr_info("%s: battery cv voltage 0x%x, chg_float_voltage = %dmV \n", __func__, reg_data, charger->pdata->chg_float_voltage); |
| return reg_data; |
| } |
| |
| #endif |
| |
| static int max77833_chg_create_attrs(struct device *dev) |
| { |
| unsigned long i; |
| int rc; |
| |
| for (i = 0; i < ARRAY_SIZE(max77833_charger_attrs); i++) { |
| rc = device_create_file(dev, &max77833_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, &max77833_charger_attrs[i]); |
| return rc; |
| } |
| |
| ssize_t max77833_chg_show_attrs(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| const ptrdiff_t offset = attr - max77833_charger_attrs; |
| int i = 0; |
| |
| switch(offset) { |
| case CHIP_ID: |
| i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n", "MAX77833"); |
| break; |
| default: |
| return -EINVAL; |
| } |
| return i; |
| } |
| |
| ssize_t max77833_chg_store_attrs(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| const ptrdiff_t offset = attr - max77833_charger_attrs; |
| int ret = 0; |
| |
| switch(offset) { |
| case CHIP_ID: |
| ret = count; |
| break; |
| default: |
| ret = -EINVAL; |
| } |
| return ret; |
| } |
| |
| static void max77833_chg_otype_check(struct max77833_charger_data *charger) |
| { |
| u8 reg_data; |
| max77833_read_reg(charger->i2c, MAX77833_CHG_REG_CNFG_06, ®_data); |
| |
| if (reg_data != 0x78) { |
| return; |
| } else { |
| pr_info("%s : o-type register reset.\n", __func__); |
| |
| /* unlock charger setting protect */ |
| reg_data = 0x03; |
| max77833_write_reg(charger->i2c, MAX77833_CHG_REG_PROTECT, reg_data); |
| |
| reg_data = max77833_get_float_voltage_data(charger->pdata->chg_float_voltage); |
| max77833_write_word(charger->i2c, MAX77833_CHG_REG_CNFG_06, reg_data); |
| pr_info("%s: battery cv voltage 0x%x\n", __func__, reg_data); |
| |
| if (!charger->is_charging) { |
| return; |
| } else { |
| max77833_set_charger_state(charger, 0); |
| max77833_charger_function_control(charger); |
| } |
| } |
| } |
| |
| static int max77833_chg_get_property(struct power_supply *psy, |
| enum power_supply_property psp, |
| union power_supply_propval *val) |
| { |
| struct max77833_charger_data *charger = |
| container_of(psy, struct max77833_charger_data, psy_chg); |
| u8 reg_data; |
| |
| max77833_chg_otype_check(charger); |
| |
| switch (psp) { |
| case POWER_SUPPLY_PROP_ONLINE: |
| val->intval = POWER_SUPPLY_TYPE_BATTERY; |
| if (max77833_read_reg(charger->i2c, |
| MAX77833_CHG_REG_INT_OK, ®_data) == 0) { |
| if (reg_data & MAX77833_WCIN_OK) { |
| val->intval = POWER_SUPPLY_TYPE_WIRELESS; |
| charger->wc_w_state = 1; |
| } else if (reg_data & MAX77833_CHGIN_OK) { |
| val->intval = POWER_SUPPLY_TYPE_MAINS; |
| } |
| } |
| break; |
| case POWER_SUPPLY_PROP_PRESENT: |
| val->intval = max77833_check_battery(charger); |
| break; |
| case POWER_SUPPLY_PROP_STATUS: |
| val->intval = max77833_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->aicl_on) |
| { |
| 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 = max77833_get_charging_health(charger); |
| break; |
| case POWER_SUPPLY_PROP_CURRENT_MAX: |
| val->intval = charger->charging_current_max; |
| break; |
| case POWER_SUPPLY_PROP_CURRENT_AVG: |
| val->intval = max77833_get_input_current(charger); |
| break; |
| case POWER_SUPPLY_PROP_CURRENT_NOW: |
| val->intval = max77833_get_input_current(charger); |
| pr_debug("%s : set-current(%dmA), current now(%dmA)\n", |
| __func__, charger->charging_current, val->intval); |
| break; |
| case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: |
| break; |
| #if defined(CONFIG_BATTERY_SWELLING) || defined(CONFIG_BATTERY_SWELLING_SELF_DISCHARGING) |
| case POWER_SUPPLY_PROP_VOLTAGE_MAX: |
| val->intval = max77833_get_float_voltage(charger); |
| break; |
| #endif |
| #if defined(CONFIG_AFC_CHARGER_MODE) |
| case POWER_SUPPLY_PROP_AFC_CHARGER_MODE: |
| return -ENODATA; |
| #endif |
| case POWER_SUPPLY_PROP_CHARGE_OTG_CONTROL: |
| return -ENODATA; |
| case POWER_SUPPLY_PROP_USB_HC: |
| return -ENODATA; |
| case POWER_SUPPLY_PROP_CHARGE_NOW: |
| max77833_read_reg(charger->i2c, |
| MAX77833_CHG_REG_DTLS_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; |
| default: |
| return -EINVAL; |
| } |
| return 0; |
| } |
| |
| static int max77833_chg_set_property(struct power_supply *psy, |
| enum power_supply_property psp, |
| const union power_supply_propval *val) |
| { |
| struct max77833_charger_data *charger = |
| container_of(psy, struct max77833_charger_data, psy_chg); |
| union power_supply_propval value; |
| u8 chg_cnfg_00 = 0; |
| static u8 chg_int_state; |
| |
| switch (psp) { |
| /* val->intval : type */ |
| case POWER_SUPPLY_PROP_STATUS: |
| charger->status = val->intval; |
| break; |
| case POWER_SUPPLY_PROP_ONLINE: |
| /* check and unlock */ |
| check_charger_unlock_state(charger); |
| if (val->intval == POWER_SUPPLY_TYPE_POWER_SHARING) { |
| psy_do_property("ps", get, |
| POWER_SUPPLY_PROP_STATUS, value); |
| if (value.intval) { |
| max77833_update_reg(charger->i2c, MAX77833_CHG_REG_CNFG_00, |
| CHG_CNFG_00_OTG_CTRL, CHG_CNFG_00_OTG_CTRL); |
| } else { |
| max77833_update_reg(charger->i2c, MAX77833_CHG_REG_CNFG_00, |
| 0, CHG_CNFG_00_OTG_CTRL); |
| } |
| break; |
| } |
| |
| charger->cable_type = val->intval; |
| max77833_charger_function_control(charger); |
| max77833_set_current(charger); |
| break; |
| /* val->intval : input charging current */ |
| case POWER_SUPPLY_PROP_CURRENT_MAX: |
| charger->charging_current_max = val->intval; |
| max77833_set_input_current(charger, val->intval); |
| break; |
| /* val->intval : charging current */ |
| case POWER_SUPPLY_PROP_CURRENT_AVG: |
| #if defined(CONFIG_BATTERY_SWELLING) |
| if (val->intval > charger->pdata->charging_current |
| [charger->cable_type].fast_charging_current) { |
| break; |
| } |
| #endif |
| charger->charging_current = val->intval; |
| max77833_set_charge_current(charger, |
| val->intval); |
| break; |
| /* val->intval : charging current */ |
| case POWER_SUPPLY_PROP_CURRENT_NOW: |
| max77833_set_charge_current(charger, |
| val->intval); |
| max77833_set_input_current(charger, |
| val->intval); |
| break; |
| #if defined(CONFIG_AFC_CHARGER_MODE) |
| case POWER_SUPPLY_PROP_AFC_CHARGER_MODE: |
| // max77833_hv_muic_charger_init(); |
| break; |
| #endif |
| #if defined(CONFIG_BATTERY_SWELLING) || defined(CONFIG_BATTERY_SWELLING_SELF_DISCHARGING) |
| case POWER_SUPPLY_PROP_VOLTAGE_MAX: |
| pr_info("%s: float voltage(%d)\n", __func__, val->intval); |
| max77833_set_float_voltage(charger, val->intval); |
| break; |
| #endif |
| case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: |
| charger->siop_level = val->intval; |
| max77833_set_current(charger); |
| break; |
| case POWER_SUPPLY_PROP_USB_HC: |
| /* set input/charging current for usb up to TA's current */ |
| if (val->intval) { |
| charger->pdata->charging_current[ |
| POWER_SUPPLY_TYPE_USB].fast_charging_current = |
| charger->pdata->charging_current[ |
| POWER_SUPPLY_TYPE_MAINS].fast_charging_current; |
| charger->pdata->charging_current[ |
| POWER_SUPPLY_TYPE_USB].input_current_limit = |
| charger->pdata->charging_current[ |
| POWER_SUPPLY_TYPE_MAINS].input_current_limit; |
| /* restore input/charging current for usb */ |
| } else { |
| charger->pdata->charging_current[ |
| POWER_SUPPLY_TYPE_USB].fast_charging_current = |
| charger->pdata->charging_current[ |
| POWER_SUPPLY_TYPE_BATTERY].input_current_limit; |
| charger->pdata->charging_current[ |
| POWER_SUPPLY_TYPE_USB].input_current_limit = |
| charger->pdata->charging_current[ |
| POWER_SUPPLY_TYPE_BATTERY].input_current_limit; |
| } |
| break; |
| case POWER_SUPPLY_PROP_CHARGE_OTG_CONTROL: |
| if (val->intval) { |
| max77833_read_reg(charger->i2c, MAX77833_CHG_REG_INT_MASK, |
| &chg_int_state); |
| |
| /* OTG_EN set to HIGH */ |
| if (charger->pdata->otg_en) |
| gpio_direction_output(charger->pdata->otg_en, 1); |
| |
| /* eable charger interrupt: CHG_I, CHGIN_I */ |
| /* enable charger interrupt: BYP_I */ |
| max77833_update_reg(charger->i2c, MAX77833_CHG_REG_INT_MASK, |
| 0, |
| MAX77833_CHG_IM | MAX77833_CHGIN_IM | MAX77833_BYP_IM); |
| |
| /* OTG on, boost on */ |
| max77833_update_reg(charger->i2c, MAX77833_CHG_REG_CNFG_00, |
| CHG_CNFG_00_OTG_CTRL, CHG_CNFG_00_OTG_CTRL); |
| |
| } else { |
| psy_do_property("battery", get, POWER_SUPPLY_PROP_CHARGE_COUNTER_SHADOW, value); |
| if (charger->pdata->otg_en && value.intval != POWER_SUPPLY_TYPE_USB) { |
| gpio_direction_output(charger->pdata->otg_en, 0); |
| pr_info("%s: OTG_EN set to LOW. cable(%d)\n", __func__, charger->cable_type); |
| } |
| |
| /* OTG off, boost off, (buck on) */ |
| max77833_update_reg(charger->i2c, MAX77833_CHG_REG_CNFG_00, |
| CHG_CNFG_00_BUCK_MASK, CHG_CNFG_00_BUCK_MASK | CHG_CNFG_00_OTG_CTRL); |
| |
| |
| /* enable charger interrupt */ |
| max77833_write_reg(charger->i2c, |
| MAX77833_CHG_REG_INT_MASK, chg_int_state); |
| } |
| max77833_read_reg(charger->i2c, MAX77833_CHG_REG_INT_MASK, |
| &chg_int_state); |
| max77833_read_reg(charger->i2c, MAX77833_CHG_REG_CNFG_00, |
| &chg_cnfg_00); |
| pr_info("%s: INT_MASK(0x%x), CHG_CNFG_00(0x%x)\n", |
| __func__, chg_int_state, chg_cnfg_00); |
| break; |
| case POWER_SUPPLY_PROP_CHARGE_NOW: |
| default: |
| return -EINVAL; |
| } |
| return 0; |
| } |
| |
| static int max77833_debugfs_show(struct seq_file *s, void *data) |
| { |
| struct max77833_charger_data *charger = s->private; |
| u8 reg; |
| u8 reg_data; |
| |
| seq_printf(s, "MAX77833 CHARGER IC :\n"); |
| seq_printf(s, "===================\n"); |
| for (reg = 0x80; reg <= 0x9D; reg++) { |
| max77833_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 max77833_debugfs_open(struct inode *inode, struct file *file) |
| { |
| return single_open(file, max77833_debugfs_show, inode->i_private); |
| } |
| |
| static const struct file_operations max77833_debugfs_fops = { |
| .open = max77833_debugfs_open, |
| .read = seq_read, |
| .llseek = seq_lseek, |
| .release = single_release, |
| }; |
| |
| static void max77833_chg_isr_work(struct work_struct *work) |
| { |
| struct max77833_charger_data *charger = |
| container_of(work, struct max77833_charger_data, isr_work.work); |
| |
| union power_supply_propval val; |
| |
| if (charger->pdata->full_check_type == |
| SEC_BATTERY_FULLCHARGED_CHGINT) { |
| |
| val.intval = max77833_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 = max77833_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 max77833_chg_irq_thread(int irq, void *irq_data) |
| { |
| struct max77833_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; |
| } |
| |
| static void wpc_detect_work(struct work_struct *work) |
| { |
| struct max77833_charger_data *charger = container_of(work, |
| struct max77833_charger_data, |
| wpc_work.work); |
| int wc_w_state; |
| int retry_cnt; |
| union power_supply_propval value; |
| u8 reg_data; |
| |
| pr_info("%s\n", __func__); |
| |
| max77833_update_reg(charger->i2c, |
| MAX77833_CHG_REG_INT_MASK, 0, MAX77833_WCIN_IM); |
| |
| /* check and unlock */ |
| check_charger_unlock_state(charger); |
| |
| retry_cnt = 0; |
| do { |
| max77833_read_reg(charger->i2c, |
| MAX77833_CHG_REG_INT_OK, ®_data); |
| wc_w_state = (reg_data & MAX77833_WCIN_OK) |
| >> MAX77833_WCIN_OK_SHIFT; |
| msleep(50); |
| } while((retry_cnt++ < 2) && (wc_w_state == 0)); |
| |
| if ((charger->wc_w_state == 0) && (wc_w_state == 1)) { |
| value.intval = 1; |
| psy_do_property("wireless", set, |
| POWER_SUPPLY_PROP_ONLINE, value); |
| value.intval = POWER_SUPPLY_TYPE_WIRELESS; |
| pr_info("%s: wpc activated, set V_INT as PN\n", |
| __func__); |
| } else if ((charger->wc_w_state == 1) && (wc_w_state == 0)) { |
| if (!charger->is_charging) |
| max77833_set_charger_state(charger, true); |
| |
| retry_cnt = 0; |
| do { |
| max77833_read_reg(charger->i2c, |
| MAX77833_CHG_REG_DTLS_01, ®_data); |
| reg_data = ((reg_data & MAX77833_CHG_DTLS) |
| >> MAX77833_CHG_DTLS_SHIFT); |
| msleep(50); |
| } while((retry_cnt++ < 2) && (reg_data == 0x8)); |
| pr_info("%s: reg_data: 0x%x, charging: %d\n", __func__, |
| reg_data, charger->is_charging); |
| if (!charger->is_charging) |
| max77833_set_charger_state(charger, false); |
| if ((reg_data != 0x08) |
| && (charger->cable_type == POWER_SUPPLY_TYPE_WIRELESS)) { |
| pr_info("%s: wpc uvlo, but charging\n", __func__); |
| queue_delayed_work(charger->wqueue, &charger->wpc_work, |
| msecs_to_jiffies(500)); |
| return; |
| } else { |
| value.intval = 0; |
| psy_do_property("wireless", set, |
| POWER_SUPPLY_PROP_ONLINE, value); |
| pr_info("%s: wpc deactivated, set V_INT as PD\n", |
| __func__); |
| } |
| } |
| pr_info("%s: w(%d to %d)\n", __func__, |
| charger->wc_w_state, wc_w_state); |
| |
| charger->wc_w_state = wc_w_state; |
| |
| /* Do unmask again. (for frequent wcin irq problem) */ |
| max77833_update_reg(charger->i2c, |
| MAX77833_CHG_REG_INT_MASK, 0, MAX77833_WCIN_IM); |
| |
| wake_unlock(&charger->wpc_wake_lock); |
| } |
| |
| static irqreturn_t wpc_charger_irq(int irq, void *data) |
| { |
| struct max77833_charger_data *charger = data; |
| unsigned long delay; |
| u8 reg_data; |
| |
| max77833_read_reg(charger->i2c, |
| MAX77833_CHG_REG_INT_MASK, ®_data); |
| reg_data |= (1 << 5); |
| max77833_write_reg(charger->i2c, |
| MAX77833_CHG_REG_INT_MASK, reg_data); |
| |
| wake_lock(&charger->wpc_wake_lock); |
| #ifdef CONFIG_SAMSUNG_BATTERY_FACTORY |
| delay = msecs_to_jiffies(0); |
| #else |
| if (charger->wc_w_state) |
| delay = msecs_to_jiffies(500); |
| else |
| delay = msecs_to_jiffies(0); |
| #endif |
| queue_delayed_work(charger->wqueue, &charger->wpc_work, |
| delay); |
| return IRQ_HANDLED; |
| } |
| |
| static irqreturn_t max77833_batp_irq(int irq, void *data) |
| { |
| struct max77833_charger_data *charger = data; |
| union power_supply_propval value; |
| u8 reg_data; |
| |
| pr_info("%s : irq(%d)\n", __func__, irq); |
| |
| max77833_read_reg(charger->i2c, |
| MAX77833_CHG_REG_INT_MASK, ®_data); |
| reg_data |= (1 << 2); |
| max77833_write_reg(charger->i2c, |
| MAX77833_CHG_REG_INT_MASK, reg_data); |
| |
| check_charger_unlock_state(charger); |
| |
| max77833_read_reg(charger->i2c, |
| MAX77833_CHG_REG_INT_OK, |
| ®_data); |
| |
| if (!(reg_data & MAX77833_BATP_OK)) |
| psy_do_property("battery", set, POWER_SUPPLY_PROP_PRESENT, value); |
| |
| max77833_read_reg(charger->i2c, |
| MAX77833_CHG_REG_INT_MASK, ®_data); |
| reg_data &= ~(1 << 2); |
| max77833_write_reg(charger->i2c, |
| MAX77833_CHG_REG_INT_MASK, reg_data); |
| |
| return IRQ_HANDLED; |
| } |
| |
| |
| static irqreturn_t max77833_bypass_irq(int irq, void *data) |
| { |
| struct max77833_charger_data *charger = data; |
| u8 dtls_02; |
| u8 byp_dtls; |
| u8 chg_cnfg_00; |
| 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); |
| |
| max77833_read_reg(charger->i2c, |
| MAX77833_CHG_REG_DTLS_02, |
| &dtls_02); |
| |
| byp_dtls = ((dtls_02 & MAX77833_BYP_DTLS) >> |
| MAX77833_BYP_DTLS_SHIFT); |
| pr_info("%s: BYP_DTLS(0x%02x)\n", __func__, byp_dtls); |
| vbus_state = max77833_get_vbus_state(charger); |
| |
| if (byp_dtls & 0x1) { |
| pr_info("%s: bypass overcurrent limit\n", __func__); |
| #ifdef CONFIG_USB_HOST_NOTIFY |
| send_otg_notify(o_notify, NOTIFY_EVENT_OVERCURRENT, 0); |
| #endif |
| /* disable the register values just related to OTG and |
| keep the values about the charging */ |
| max77833_read_reg(charger->i2c, |
| MAX77833_CHG_REG_CNFG_00, &chg_cnfg_00); |
| chg_cnfg_00 &= ~(CHG_CNFG_00_OTG_MASK |
| | CHG_CNFG_00_BOOST_MASK); |
| max77833_write_reg(charger->i2c, |
| MAX77833_CHG_REG_CNFG_00, |
| chg_cnfg_00); |
| } |
| return IRQ_HANDLED; |
| } |
| |
| static void max77833_aicl_work(struct work_struct *work) |
| { |
| struct max77833_charger_data *charger = container_of(work, |
| struct max77833_charger_data, aicl_work.work); |
| |
| charger->afc_detect = false; |
| |
| if ((charger->is_charging) && |
| (charger->cable_type != POWER_SUPPLY_TYPE_WIRELESS)) { |
| int now_count = 0, |
| max_count = charger->charging_current_max / REDUCE_CURRENT_STEP; |
| int prev_current_max = charger->charging_current_max; |
| |
| mutex_lock(&charger->charger_mutex); |
| check_charger_unlock_state(charger); |
| |
| while (max77833_check_aicl_state(charger) && |
| (now_count++ < max_count) && (charger->is_charging) && |
| (charger->cable_type != POWER_SUPPLY_TYPE_WIRELESS)) { |
| reduce_input_current(charger, REDUCE_CURRENT_STEP); |
| msleep(50); |
| } |
| pr_info("%s: charging_current_max(%d --> %d)\n", |
| __func__, prev_current_max, charger->charging_current_max); |
| |
| if (prev_current_max > charger->charging_current_max) { |
| max77833_check_slow_charging(charger, charger->charging_current_max); |
| } |
| mutex_unlock(&charger->charger_mutex); |
| } |
| |
| wake_unlock(&charger->aicl_wake_lock); |
| } |
| |
| static irqreturn_t max77833_aicl_irq(int irq, void *data) |
| { |
| struct max77833_charger_data *charger = data; |
| |
| pr_info("%s: irq(%d)\n", __func__, irq); |
| |
| wake_lock(&charger->aicl_wake_lock); |
| queue_delayed_work(charger->wqueue, &charger->aicl_work, |
| msecs_to_jiffies(50)); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static void max77833_chgin_isr_work(struct work_struct *work) |
| { |
| struct max77833_charger_data *charger = container_of(work, |
| struct max77833_charger_data, chgin_work); |
| u8 chgin_dtls, chg_dtls, chg_cnfg_00, reg_data; |
| u8 prev_chgin_dtls = 0xff; |
| int battery_health; |
| union power_supply_propval value; |
| int stable_count = 0; |
| |
| wake_lock(&charger->chgin_wake_lock); |
| |
| max77833_read_reg(charger->i2c, |
| MAX77833_CHG_REG_INT_MASK, ®_data); |
| reg_data |= (1 << 6); |
| max77833_write_reg(charger->i2c, |
| MAX77833_CHG_REG_INT_MASK, reg_data); |
| |
| while (1) { |
| psy_do_property("battery", get, |
| POWER_SUPPLY_PROP_HEALTH, value); |
| battery_health = value.intval; |
| |
| max77833_read_reg(charger->i2c, |
| MAX77833_CHG_REG_DTLS_00, |
| &chgin_dtls); |
| chgin_dtls = ((chgin_dtls & MAX77833_CHGIN_DTLS) >> |
| MAX77833_CHGIN_DTLS_SHIFT); |
| max77833_read_reg(charger->i2c, |
| MAX77833_CHG_REG_DTLS_01, &chg_dtls); |
| chg_dtls = ((chg_dtls & MAX77833_CHG_DTLS) >> |
| MAX77833_CHG_DTLS_SHIFT); |
| max77833_read_reg(charger->i2c, |
| MAX77833_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 & MAX77833_MODE_BUCK) && \ |
| (chg_cnfg_00 & MAX77833_MODE_CHGR) && \ |
| (battery_health != POWER_SUPPLY_HEALTH_UNDERVOLTAGE) && \ |
| (charger->cable_type != POWER_SUPPLY_TYPE_WIRELESS)) { |
| 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); |
| max77833_set_input_current(charger, |
| charger->charging_current_max); |
| } |
| } |
| break; |
| } |
| |
| prev_chgin_dtls = chgin_dtls; |
| msleep(100); |
| } |
| max77833_read_reg(charger->i2c, |
| MAX77833_CHG_REG_INT_MASK, ®_data); |
| reg_data &= ~(1 << 6); |
| max77833_write_reg(charger->i2c, |
| MAX77833_CHG_REG_INT_MASK, reg_data); |
| wake_unlock(&charger->chgin_wake_lock); |
| } |
| |
| static irqreturn_t max77833_chgin_irq(int irq, void *data) |
| { |
| struct max77833_charger_data *charger = data; |
| queue_work(charger->wqueue, &charger->chgin_work); |
| |
| return IRQ_HANDLED; |
| } |
| |
| /* register chgin isr after sec_battery_probe */ |
| static void max77833_chgin_init_work(struct work_struct *work) |
| { |
| struct max77833_charger_data *charger = container_of(work, |
| struct max77833_charger_data, |
| chgin_init_work.work); |
| int ret; |
| |
| pr_info("%s \n", __func__); |
| ret = request_threaded_irq(charger->irq_chgin, NULL, |
| max77833_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 { |
| max77833_update_reg(charger->i2c, |
| MAX77833_CHG_REG_INT_MASK, 0, MAX77833_CHGIN_IM); |
| } |
| } |
| |
| #ifdef CONFIG_OF |
| static int max77833_charger_parse_dt(struct max77833_charger_data *charger) |
| { |
| struct device_node *np = of_find_node_by_name(NULL, "max77833-charger"); |
| sec_charger_platform_data_t *pdata = charger->pdata; |
| int ret = 0; |
| int i, len; |
| const u32 *p; |
| u32 irq_gpio_flags; |
| |
| if (np == NULL) { |
| pr_err("%s np NULL\n", __func__); |
| } else { |
| ret = of_property_read_u32(np, "battery,chg_float_voltage", |
| &pdata->chg_float_voltage); |
| } |
| |
| np = of_find_node_by_name(NULL, "battery"); |
| if (!np) { |
| pr_err("%s np NULL\n", __func__); |
| } else { |
| ret = pdata->irq_gpio = of_get_named_gpio_flags(np, "battery,irq_gpio", |
| 0, &irq_gpio_flags); |
| if (ret < 0) |
| pr_err("%s : can't get irq-gpio \n", __func__); |
| else { |
| pr_info("%s : irq_gpio = %d \n",__func__, pdata->irq_gpio); |
| pdata->chg_irq = gpio_to_irq(pdata->irq_gpio); |
| pr_info("%s : chg_irq = 0x%x \n",__func__, pdata->chg_irq); |
| } |
| |
| pdata->wpc_det = of_get_named_gpio(np, "battery,wpc_det", 0); |
| if (pdata->wpc_det < 0) |
| pdata->wpc_det = 0; |
| |
| pdata->otg_en = of_get_named_gpio(np, "battery,otg_en", 0); |
| if (pdata->otg_en < 0) |
| pdata->otg_en = 0; |
| |
| 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__); |
| |
| p = of_get_property(np, "battery,input_current_limit", &len); |
| if (!p) |
| return 1; |
| |
| len = len / sizeof(u32); |
| |
| pdata->charging_current = kzalloc(sizeof(sec_charging_current_t) * len, |
| GFP_KERNEL); |
| |
| for(i = 0; i < len; i++) { |
| ret = of_property_read_u32_index(np, |
| "battery,input_current_limit", i, |
| &pdata->charging_current[i].input_current_limit); |
| ret = of_property_read_u32_index(np, |
| "battery,fast_charging_current", i, |
| &pdata->charging_current[i].fast_charging_current); |
| ret = of_property_read_u32_index(np, |
| "battery,full_check_current_1st", i, |
| &pdata->charging_current[i].full_check_current_1st); |
| ret = of_property_read_u32_index(np, |
| "battery,full_check_current_2nd", i, |
| &pdata->charging_current[i].full_check_current_2nd); |
| } |
| } |
| return ret; |
| } |
| #endif |
| |
| static int __devinit max77833_charger_probe(struct platform_device *pdev) |
| { |
| struct max77833_dev *max77833 = dev_get_drvdata(pdev->dev.parent); |
| struct max77833_platform_data *pdata = dev_get_platdata(max77833->dev); |
| struct max77833_charger_data *charger; |
| int ret = 0; |
| u8 reg_data; |
| |
| pr_info("%s: Max77833 Charger Driver Loading\n", __func__); |
| |
| charger = kzalloc(sizeof(*charger), GFP_KERNEL); |
| if (!charger) |
| return -ENOMEM; |
| |
| pdata->charger_data = kzalloc(sizeof(sec_charger_platform_data_t), GFP_KERNEL); |
| if (!pdata->charger_data) { |
| ret = -ENOMEM; |
| goto err_free; |
| } |
| |
| mutex_init(&charger->charger_mutex); |
| charger->dev = &pdev->dev; |
| charger->i2c = max77833->i2c; |
| charger->pdata = pdata->charger_data; |
| charger->aicl_on = false; |
| charger->afc_detect = false; |
| charger->is_mdock = false; |
| charger->siop_level = 100; |
| charger->max77833_pdata = pdata; |
| charger->input_curr_limit_step = 25; |
| |
| #if defined(CONFIG_OF) |
| ret = max77833_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); |
| |
| charger->psy_chg.name = "max77833-charger"; |
| charger->psy_chg.type = POWER_SUPPLY_TYPE_UNKNOWN; |
| charger->psy_chg.get_property = max77833_chg_get_property; |
| charger->psy_chg.set_property = max77833_chg_set_property; |
| charger->psy_chg.properties = max77833_charger_props; |
| charger->psy_chg.num_properties = ARRAY_SIZE(max77833_charger_props); |
| |
| max77833_charger_initialize(charger); |
| |
| if (charger->pdata->otg_en) { |
| ret = gpio_request(charger->pdata->otg_en, "OTG_EN"); |
| if (ret) { |
| pr_err("failed to request GPIO %u\n", charger->pdata->otg_en); |
| goto err_gpio; |
| } |
| } |
| |
| if (max77833_read_reg(max77833->i2c, MAX77833_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("max77833-regs", |
| S_IRUGO, NULL, (void *)charger, &max77833_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, max77833_chgin_isr_work); |
| INIT_DELAYED_WORK(&charger->chgin_init_work, max77833_chgin_init_work); |
| wake_lock_init(&charger->wpc_wake_lock, WAKE_LOCK_SUSPEND, |
| "charger-wpc"); |
| wake_lock_init(&charger->afc_wake_lock, WAKE_LOCK_SUSPEND, |
| "charger-afc"); |
| INIT_DELAYED_WORK(&charger->wpc_work, wpc_detect_work); |
| INIT_DELAYED_WORK(&charger->afc_work, afc_detect_work); |
| |
| wake_lock_init(&charger->aicl_wake_lock, WAKE_LOCK_SUSPEND, |
| "charger-aicl"); |
| INIT_DELAYED_WORK(&charger->aicl_work, max77833_aicl_work); |
| |
| ret = power_supply_register(&pdev->dev, &charger->psy_chg); |
| if (ret) { |
| pr_err("%s: Failed to Register psy_chg\n", __func__); |
| goto err_power_supply_register; |
| } |
| |
| if (charger->pdata->chg_irq) { |
| INIT_DELAYED_WORK(&charger->isr_work, max77833_chg_isr_work); |
| |
| ret = request_threaded_irq(charger->pdata->chg_irq, |
| NULL, max77833_chg_irq_thread, |
| IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | |
| IRQF_ONESHOT, |
| "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 + MAX77833_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; |
| } |
| |
| max77833_read_reg(charger->i2c, |
| MAX77833_CHG_REG_INT_OK, ®_data); |
| charger->wc_w_state = (reg_data & MAX77833_WCIN_OK) |
| >> MAX77833_WCIN_OK_SHIFT; |
| |
| charger->irq_chgin = pdata->irq_base + MAX77833_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 + MAX77833_CHG_IRQ_BYP_I; |
| ret = request_threaded_irq(charger->irq_bypass, NULL, |
| max77833_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 { |
| max77833_update_reg(charger->i2c, |
| MAX77833_CHG_REG_INT_MASK, 0, MAX77833_BYP_IM); |
| } |
| |
| charger->irq_batp = pdata->irq_base + MAX77833_CHG_IRQ_BATP_I; |
| ret = request_threaded_irq(charger->irq_batp, NULL, |
| max77833_batp_irq, 0, |
| "batp-irq", charger); |
| if (ret < 0) { |
| pr_err("%s: fail to request batp IRQ: %d: %d\n", |
| __func__, charger->irq_batp, ret); |
| } else { |
| max77833_update_reg(charger->i2c, |
| MAX77833_CHG_REG_INT_MASK, 0, MAX77833_BATP_IM); |
| } |
| |
| charger->irq_aicl = pdata->irq_base + MAX77833_CHG_IRQ_AICL_I; |
| ret = request_threaded_irq(charger->irq_aicl, NULL, |
| max77833_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); |
| } else { |
| max77833_update_reg(charger->i2c, |
| MAX77833_CHG_REG_INT_MASK, 0, MAX77833_AICL_IM); |
| } |
| |
| ret = max77833_chg_create_attrs(charger->psy_chg.dev); |
| if (ret) { |
| dev_err(charger->dev, |
| "%s : Failed to create_attrs\n", __func__); |
| goto err_wc_irq; |
| } |
| pr_info("%s: MAX77833 Charger Driver Loaded\n", __func__); |
| |
| return 0; |
| |
| err_wc_irq: |
| free_irq(charger->pdata->chg_irq, NULL); |
| err_irq: |
| power_supply_unregister(&charger->psy_chg); |
| err_power_supply_register: |
| destroy_workqueue(charger->wqueue); |
| err_pdata_free: |
| if (charger->pdata->otg_en) |
| gpio_free(charger->pdata->otg_en); |
| err_gpio: |
| kfree(pdata->charger_data); |
| mutex_destroy(&charger->charger_mutex); |
| err_free: |
| kfree(charger); |
| |
| return ret; |
| } |
| |
| static int __devexit max77833_charger_remove(struct platform_device *pdev) |
| { |
| struct max77833_charger_data *charger = |
| platform_get_drvdata(pdev); |
| |
| destroy_workqueue(charger->wqueue); |
| free_irq(charger->wc_w_irq, NULL); |
| free_irq(charger->pdata->chg_irq, NULL); |
| power_supply_unregister(&charger->psy_chg); |
| mutex_destroy(&charger->charger_mutex); |
| kfree(charger); |
| |
| return 0; |
| } |
| |
| #if defined CONFIG_PM |
| static int max77833_charger_suspend(struct device *dev) |
| { |
| return 0; |
| } |
| |
| static int max77833_charger_resume(struct device *dev) |
| { |
| return 0; |
| } |
| #else |
| #define max77833_charger_suspend NULL |
| #define max77833_charger_resume NULL |
| #endif |
| |
| static void max77833_charger_shutdown(struct device *dev) |
| { |
| struct max77833_charger_data *charger = |
| dev_get_drvdata(dev); |
| u8 reg_data; |
| |
| pr_info("%s: MAX77833 Charger driver shutdown\n", __func__); |
| if (!charger->i2c) { |
| pr_err("%s: no max77833 i2c client\n", __func__); |
| return; |
| } |
| reg_data = 0x04; |
| max77833_write_reg(charger->i2c, |
| MAX77833_CHG_REG_CNFG_00, reg_data); |
| reg_data = 0x14; |
| max77833_write_reg(charger->i2c, |
| MAX77833_CHG_REG_CNFG_16, reg_data); |
| reg_data = 0x14; |
| max77833_write_reg(charger->i2c, |
| MAX77833_CHG_REG_CNFG_17, reg_data); |
| reg_data = 0xE7; |
| max77833_write_reg(charger->i2c, |
| MAX77833_CHG_REG_CNFG_18, reg_data); |
| pr_info("func:%s \n", __func__); |
| } |
| |
| static SIMPLE_DEV_PM_OPS(max77833_charger_pm_ops, max77833_charger_suspend, |
| max77833_charger_resume); |
| |
| static struct platform_driver max77833_charger_driver = { |
| .driver = { |
| .name = "max77833-charger", |
| .owner = THIS_MODULE, |
| #ifdef CONFIG_PM |
| .pm = &max77833_charger_pm_ops, |
| #endif |
| .shutdown = max77833_charger_shutdown, |
| }, |
| .probe = max77833_charger_probe, |
| .remove = __devexit_p(max77833_charger_remove), |
| }; |
| |
| static int __init max77833_charger_init(void) |
| { |
| pr_info("%s : \n", __func__); |
| return platform_driver_register(&max77833_charger_driver); |
| } |
| |
| static void __exit max77833_charger_exit(void) |
| { |
| platform_driver_unregister(&max77833_charger_driver); |
| } |
| |
| module_init(max77833_charger_init); |
| module_exit(max77833_charger_exit); |
| |
| MODULE_DESCRIPTION("Samsung MAX77833 Charger Driver"); |
| MODULE_AUTHOR("Samsung Electronics"); |
| MODULE_LICENSE("GPL"); |