/* * max77660-charger-extcon.c -- MAXIM MAX77660 VBUS detection. * * Copyright (c) 2013, NVIDIA CORPORATION. All rights reserved. * * Author: Darbha Sriharsha * Author: Syed Rafiuddin * Author: Laxman Dewangan * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation version 2. * * This program is distributed "as is" WITHOUT ANY WARRANTY of any kind, * whether express or implied; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA * 02111-1307, USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define CHARGER_USB_EXTCON_REGISTRATION_DELAY 5000 #define CHARGER_TYPE_DETECTION_DEBOUNCE_TIME_MS 500 #define MAX77660_RESTART_CHARGING_AFTER_DONE (15 * 60) #define BATT_TEMP_HOT 56 #define BATT_TEMP_WARM 45 #define BATT_TEMP_COOL 10 #define BATT_TEMP_COLD 3 #define NO_LIMIT 99999 enum charging_states { ENABLED_HALF_IBAT = 1, ENABLED_FULL_IBAT, DISABLED, }; static int max77660_dccrnt_current_lookup[] = { 1000, 1000, 1000, 2750, 3000, 3250, 3500, 3750, 4000, 4250, 4500, 4750, 5000, 5250, 5500, 5750, 6000, 6250, 6500, 6750, 7000, 7250, 7500, 7750, 8000, 8250, 8500, 8750, 9000, 9250, 9500, 9750, 10000, 10250, 10500, 10750, 11000, 11250, 11500, 11750, 12000, 12250, 12500, 12750, 13000, 13250, 13500, 13750, 14000, 14250, 14500, 14750, 15000, 15375, 15750, 16125, 16500, 16875, 17250, 17625, 18000, 18375, 18750, NO_LIMIT, }; static int max77660_chrg_wdt[] = {16, 32, 64, 128}; struct max77660_charger { struct max77660_bcharger_platform_data *bcharger_pdata; struct device *dev; int irq; int status; int in_current_lim; int wdt_timeout; }; struct max77660_chg_extcon { struct device *dev; struct device *parent; struct extcon_dev *edev; struct regulator_dev *chg_rdev; struct regulator_dev *vbus_rdev; struct regulator_desc vbus_reg_desc; struct regulator_init_data vbus_reg_init_data; struct max77660_charger *charger; int chg_irq; int chg_wdt_irq; int cable_connected; struct regulator_desc chg_reg_desc; struct regulator_init_data chg_reg_init_data; struct battery_charger_dev *bc_dev; int charging_state; int cable_connected_state; int battery_present; int chg_restart_time_sec; int last_charging_current; }; struct max77660_chg_extcon *max77660_ext; const char *max77660_excon_cable[] = { [0] = "USB", NULL, }; /* IRQ definitions */ enum { MAX77660_CHG_BAT_I = 0, MAX77660_CHG_CHG_I, MAX77660_CHG_DC_UVP, MAX77660_CHG_DC_OVP, MAX77660_CHG_DC_I, MAX77660_CHG_DC_V, MAX77660_CHG_NR_IRQS, }; static inline int fchg_current(int x) { int i, ret; for (i = 0; i < ARRAY_SIZE(max77660_dccrnt_current_lookup); i++) { if (10*x < max77660_dccrnt_current_lookup[i]) break; } ret = i-1; if (ret < 3) return 0; else return ret; } static int max77660_battery_detect(struct max77660_chg_extcon *chip) { int ret = 0; u8 status; ret = max77660_reg_read(chip->parent, MAX77660_CHG_SLAVE, MAX77660_CHARGER_DETAILS1, &status); if (ret < 0) { dev_err(chip->dev, "CHARGER_CHGINT read failed: %d\n", ret); return ret; } if ((status & MAX77660_BATDET_DTLS) == MAX77660_BATDET_DTLS_NO_BAT) return -EPERM; return 0; } static int max77660_charger_init(struct max77660_chg_extcon *chip, int enable) { struct max77660_charger *charger = chip->charger; int ret; u8 read_val; if (!chip->battery_present) return 0; /* Enable USB suspend if 2mA is current configuration */ if (charger->in_current_lim == 2) ret = max77660_reg_set_bits(chip->parent, MAX77660_CHG_SLAVE, MAX77660_CHARGER_USBCHGCTRL, MAX77660_USBCHGCTRL_USB_SUSPEND); else ret = max77660_reg_clr_bits(chip->parent, MAX77660_CHG_SLAVE, MAX77660_CHARGER_USBCHGCTRL, MAX77660_USBCHGCTRL_USB_SUSPEND); if (ret < 0) return ret; charger->in_current_lim = fchg_current(charger->in_current_lim); /* unlock charger protection */ ret = max77660_reg_write(chip->parent, MAX77660_CHG_SLAVE, MAX77660_CHARGER_CHGCTRL1, MAX77660_CHGCC_MASK); if (ret < 0) return ret; if (enable) { /* enable charger */ /* Set DCILMT to 1A */ ret = max77660_reg_write(chip->parent, MAX77660_CHG_SLAVE, MAX77660_CHARGER_DCCRNT, charger->in_current_lim); if (ret < 0) return ret; /* Fast charge to 5 hours, fast charge current to 1.1A */ ret = max77660_reg_write(chip->parent, MAX77660_CHG_SLAVE, MAX77660_CHARGER_FCHGCRNT, MAX77660_FCHG_CRNT); if (ret < 0) return ret; ret = max77660_reg_read(chip->parent, MAX77660_CHG_SLAVE, MAX77660_CHARGER_FCHGCRNT, &read_val); if (ret < 0) return ret; /* Set TOPOFF to 10 min */ ret = max77660_reg_write(chip->parent, MAX77660_CHG_SLAVE, MAX77660_CHARGER_TOPOFF, MAX77660_ITOPOFF_200MA | MAX77660_TOPOFFT_10MIN); if (ret < 0) return ret; /* MBATREG to 4.2V */ ret = max77660_reg_write(chip->parent, MAX77660_CHG_SLAVE, MAX77660_CHARGER_BATREGCTRL, MAX77660_MBATREG_4200MV); if (ret < 0) return ret; /* MBATREGMAX to 4.2V */ ret = max77660_reg_write(chip->parent, MAX77660_CHG_SLAVE, MAX77660_CHARGER_MBATREGMAX, MAX77660_MBATREG_4200MV); if (ret < 0) return ret; /* DSILIM_EN = 1; CEN= 1; QBATEN = 0; VSYSREG = 3.6V */ ret = max77660_reg_write(chip->parent, MAX77660_CHG_SLAVE, MAX77660_CHARGER_CHGCTRL2, MAX77660_VSYSREG_3600MV | MAX77660_CEN_MASK | MAX77660_PREQ_CURNT | MAX77660_DCILIM_EN_MASK); if (ret < 0) return ret; /* Enable register settings for charging*/ ret = max77660_reg_write(chip->parent, MAX77660_CHG_SLAVE, MAX77660_CHARGER_CHGCCMAX, MAX77660_CHGCCMAX_CRNT); if (ret < 0) return ret; /* Enable top level charging */ ret = max77660_reg_write(chip->parent, MAX77660_PWR_SLAVE, MAX77660_REG_GLOBAL_CFG1, MAX77660_GLBLCNFG1_MASK); if (ret < 0) return ret; ret = max77660_reg_write(chip->parent, MAX77660_PWR_SLAVE, MAX77660_REG_GLOBAL_CFG4, MAX77660_GLBLCNFG4_WDTC_SYS_CLR); if (ret < 0) return ret; ret = max77660_reg_write(chip->parent, MAX77660_PWR_SLAVE, MAX77660_REG_GLOBAL_CFG6, MAX77660_GLBLCNFG6_MASK); if (ret < 0) return ret; } else { ret = max77660_reg_write(chip->parent, MAX77660_CHG_SLAVE, MAX77660_CHARGER_DCCRNT, 0); if (ret < 0) return ret; /* disable charge */ /* Clear CEN */ ret = max77660_reg_clr_bits(chip->parent, MAX77660_CHG_SLAVE, MAX77660_CHARGER_CHGCTRL2, MAX77660_CEN_MASK); if (ret < 0) return ret; /* Clear top level charge */ ret = max77660_reg_clr_bits(chip->parent, MAX77660_PWR_SLAVE, MAX77660_REG_GLOBAL_CFG1, MAX77660_GLBLCNFG1_ENCHGTL); if (ret < 0) return ret; } dev_info(chip->dev, "%s\n", (enable) ? "Enable charger" : "Disable charger"); return 0; } int max77660_full_current_enable(struct max77660_chg_extcon *chip) { int ret; ret = max77660_charger_init(chip, true); if (ret < 0) { dev_err(chip->dev, "Failed to initialise full current charging\n"); return ret; } chip->charging_state = ENABLED_FULL_IBAT; return 0; } int max77660_half_current_enable(struct max77660_chg_extcon *chip) { int ret; int temp; temp = chip->charger->in_current_lim; chip->charger->in_current_lim = chip->charger->in_current_lim/2; ret = max77660_charger_init(chip, true); if (ret < 0) { dev_err(chip->dev, "Failed to initialise full current charging\n"); return ret; } chip->charger->in_current_lim = temp; chip->charging_state = ENABLED_HALF_IBAT; return 0; } int max77660_charging_disable(struct max77660_chg_extcon *chip) { int ret; ret = max77660_charger_init(chip, false); if (ret < 0) { dev_err(chip->dev, "Failed to disable charging\n"); return ret; } chip->charging_state = DISABLED; return 0; } static int max77660_set_charging_current(struct regulator_dev *rdev, int min_uA, int max_uA) { struct max77660_chg_extcon *chip = rdev_get_drvdata(rdev); struct max77660_charger *charger = chip->charger; int ret; u8 status; ret = max77660_battery_detect(chip); if (ret < 0) { dev_err(chip->dev, "Battery detection failed\n"); goto error; } chip->last_charging_current = max_uA; charger->in_current_lim = max_uA/1000; charger->status = BATTERY_DISCHARGING; ret = max77660_reg_read(chip->parent, MAX77660_CHG_SLAVE, MAX77660_CHARGER_CHGSTAT, &status); if (ret < 0) { dev_err(chip->dev, "CHSTAT read failed: %d\n", ret); return ret; } if (charger->in_current_lim == 0 && !(status & MAX77660_CHG_CHGINT_DC_UVP)) return 0; if (charger->in_current_lim == 0) { chip->cable_connected = 0; ret = max77660_charging_disable(chip); if (ret < 0) goto error; battery_charging_status_update(chip->bc_dev, BATTERY_DISCHARGING); battery_charger_thermal_stop_monitoring(chip->bc_dev); } else { chip->cable_connected = 1; charger->status = BATTERY_CHARGING; ret = max77660_full_current_enable(chip); if (ret < 0) goto error; battery_charging_status_update(chip->bc_dev, BATTERY_CHARGING); battery_charger_thermal_start_monitoring(chip->bc_dev); } return 0; error: return ret; } static struct regulator_ops max77660_charger_ops = { .set_current_limit = max77660_set_charging_current, }; static int max77660_init_charger_regulator(struct max77660_chg_extcon *chip, struct max77660_bcharger_platform_data *bcharger_pdata) { struct regulator_config config = { }; int ret = 0; if (!bcharger_pdata) { dev_err(chip->dev, "No charger platform data\n"); return 0; } chip->chg_reg_desc.name = "max77660-charger"; chip->chg_reg_desc.ops = &max77660_charger_ops; chip->chg_reg_desc.type = REGULATOR_CURRENT; chip->chg_reg_desc.owner = THIS_MODULE; chip->chg_reg_init_data.supply_regulator = NULL; chip->chg_reg_init_data.regulator_init = NULL; chip->chg_reg_init_data.num_consumer_supplies = bcharger_pdata->num_consumer_supplies; chip->chg_reg_init_data.consumer_supplies = bcharger_pdata->consumer_supplies; chip->chg_reg_init_data.driver_data = chip->charger; chip->chg_reg_init_data.constraints.name = "max77660-charger"; chip->chg_reg_init_data.constraints.min_uA = 0; chip->chg_reg_init_data.constraints.max_uA = bcharger_pdata->max_charge_current_mA * 1000; chip->chg_reg_init_data.constraints.valid_modes_mask = REGULATOR_MODE_NORMAL | REGULATOR_MODE_STANDBY; chip->chg_reg_init_data.constraints.valid_ops_mask = REGULATOR_CHANGE_MODE | REGULATOR_CHANGE_STATUS | REGULATOR_CHANGE_CURRENT; config.dev = chip->dev; config.init_data = &chip->chg_reg_init_data; config.driver_data = chip; chip->chg_rdev = regulator_register(&chip->chg_reg_desc, &config); if (IS_ERR(chip->chg_rdev)) { ret = PTR_ERR(chip->chg_rdev); dev_err(chip->dev, "vbus-charger regulator register failed %d\n", ret); } return ret; } static int max77660_chg_extcon_cable_update( struct max77660_chg_extcon *chg_extcon) { int ret; u8 status; ret = max77660_reg_read(chg_extcon->parent, MAX77660_CHG_SLAVE, MAX77660_CHARGER_CHGSTAT, &status); if (ret < 0) { dev_err(chg_extcon->dev, "CHSTAT read failed: %d\n", ret); return ret; } if (status & MAX77660_CHG_CHGINT_DC_UVP) extcon_set_cable_state(chg_extcon->edev, "USB", false); else extcon_set_cable_state(chg_extcon->edev, "USB", true); dev_info(chg_extcon->dev, "VBUS %s status: 0x%02x\n", (status & MAX77660_CHG_CHGINT_DC_UVP) ? "Invalid" : "Valid", status); return 0; } static int max77660_charger_detail_irq(int irq, void *data, u8 *val) { struct max77660_chg_extcon *chip = (struct max77660_chg_extcon *)data; switch (irq) { case MAX77660_CHG_BAT_I: switch ((val[2] & MAX77660_BAT_DTLS_MASK) >>MAX77660_BAT_DTLS_SHIFT) { case MAX77660_BAT_DTLS_BATDEAD: dev_info(chip->dev, "Battery Interrupt: Battery Dead\n"); break; case MAX77660_BAT_DTLS_TIMER_FAULT: dev_info(chip->dev, "Battery Interrupt: Charger Watchdog Timer fault\n"); break; case MAX77660_BAT_DTLS_BATOK: dev_info(chip->dev, "Battery Interrupt: Battery Ok\n"); break; case MAX77660_BAT_DTLS_GTBATOVF: dev_info(chip->dev, "Battery Interrupt: GTBATOVF\n"); break; default: dev_info(chip->dev, "Battery Interrupt: details-0x%x\n", (val[2] & MAX77660_BAT_DTLS_MASK)); break; } break; case MAX77660_CHG_CHG_I: switch (val[2] & MAX77660_CHG_DTLS_MASK) { case MAX77660_CHG_DTLS_DEAD_BAT: dev_info(chip->dev, "Fast Charge current Interrupt: Dead Battery\n"); break; case MAX77660_CHG_DTLS_PREQUAL: dev_info(chip->dev, "Fast Charge current Interrupt: PREQUAL\n"); break; case MAX77660_CHG_DTLS_FAST_CHARGE_CC: dev_info(chip->dev, "Fast Charge current Interrupt: FAST_CHARGE_CC\n"); break; case MAX77660_CHG_DTLS_FAST_CHARGE_CV: dev_info(chip->dev, "Fast Charge current Interrupt: FAST_CHARGE_CV\n"); break; case MAX77660_CHG_DTLS_TOP_OFF: dev_info(chip->dev, "Fast Charge current Interrupt: TOP_OFF\n"); break; case MAX77660_CHG_DTLS_DONE: dev_info(chip->dev, "Fast Charge current Interrupt: DONE\n"); battery_charging_status_update(chip->bc_dev, BATTERY_CHARGING_DONE); battery_charger_thermal_stop_monitoring(chip->bc_dev); battery_charging_restart(chip->bc_dev, chip->chg_restart_time_sec); break; case MAX77660_CHG_DTLS_DONE_QBAT_ON: dev_info(chip->dev, "Fast Charge current Interrupt: DONE_QBAT_ON\n"); break; case MAX77660_CHG_DTLS_TIMER_FAULT: dev_info(chip->dev, "Fast Charge current Interrupt: TIMER_FAULT\n"); break; case MAX77660_CHG_DTLS_DC_INVALID: dev_info(chip->dev, "Fast Charge current Interrupt: DC_INVALID\n"); break; case MAX77660_CHG_DTLS_THERMAL_LOOP_ACTIVE: dev_info(chip->dev, "Fast Charge current Interrupt:" "THERMAL_LOOP_ACTIVE\n"); break; case MAX77660_CHG_DTLS_CHG_OFF: dev_info(chip->dev, "Fast Charge current Interrupt: CHG_OFF\n"); break; default: dev_info(chip->dev, "Fast Charge current Interrupt: details-0x%x\n", (val[2] & MAX77660_CHG_DTLS_MASK)); break; } break; case MAX77660_CHG_DC_UVP: max77660_chg_extcon_cable_update(chip); break; case MAX77660_CHG_DC_OVP: if ((val[1] & MAX77660_DC_OVP_MASK) == MAX77660_DC_OVP_MASK) { dev_info(chip->dev, "DC Over voltage Interrupt: VDC > VDC_OVLO\n"); /* VBUS is invalid. VDC > VDC_OVLO */ max77660_charger_init(chip, 0); } else if ((val[1] & MAX77660_DC_OVP_MASK) == 0) { dev_info(chip->dev, "DC Over voltage Interrupt: VDC < VDC_OVLO\n"); /* VBUS is valid. VDC < VDC_OVLO */ max77660_charger_init(chip, 1); } break; case MAX77660_CHG_DC_I: dev_info(chip->dev, "DC Input Current Limit Interrupt: details-0x%x\n", (val[1] & MAX77660_DC_I_MASK)); break; case MAX77660_CHG_DC_V: dev_info(chip->dev, "DC Input Voltage Limit Interrupt: details-0x%x\n", (val[1] & MAX77660_DC_V_MASK)); break; } return 0; } static irqreturn_t max77660_chg_extcon_irq(int irq, void *data) { struct max77660_chg_extcon *chg_extcon = data; u8 irq_val = 0; u8 irq_mask = 0; u8 irq_name = 0; u8 val[3]; int ret; val[0] = 0; val[1] = 0; val[2] = 0; ret = max77660_reg_read(chg_extcon->parent, MAX77660_CHG_SLAVE, MAX77660_CHARGER_CHGINT, &irq_val); ret = max77660_reg_read(chg_extcon->parent, MAX77660_CHG_SLAVE, MAX77660_CHARGER_CHGINTM, &irq_mask); ret = max77660_reg_read(chg_extcon->parent, MAX77660_CHG_SLAVE, MAX77660_CHARGER_CHGSTAT, &val[0]); ret = max77660_reg_read(chg_extcon->parent, MAX77660_CHG_SLAVE, MAX77660_CHARGER_DETAILS1, &val[1]); ret = max77660_reg_read(chg_extcon->parent, MAX77660_CHG_SLAVE, MAX77660_CHARGER_DETAILS2, &val[2]); for (irq_name = MAX77660_CHG_BAT_I; irq_name < MAX77660_CHG_NR_IRQS; irq_name++) { if ((irq_val & (0x01<<(irq_name+2))) && !(irq_mask & (0x01<<(irq_name+2)))) max77660_charger_detail_irq(irq_name, data, val); } return IRQ_HANDLED; } static int max77660_charger_wdt(struct max77660_chg_extcon *chip) { struct max77660_charger *charger = chip->charger; int i, ret = 0; for (i = 0; i < ARRAY_SIZE(max77660_chrg_wdt); ++i) { if (max77660_chrg_wdt[i] >= charger->wdt_timeout) break; } if (i == ARRAY_SIZE(max77660_chrg_wdt)) { dev_err(chip->dev, "Charger WDT %d sec is not supported\n", charger->wdt_timeout); return -EINVAL; } ret = max77660_reg_update(chip->parent, MAX77660_PWR_SLAVE, MAX77660_REG_GLOBAL_CFG2, MAX77660_GLBLCNFG2_TWD_CHG_MASK, MAX77660_GLBLCNFG2_TWD_CHG(i)); if (ret < 0) { dev_err(chip->dev, "GLOBAL_CFG2 update failed: %d\n", ret); return ret; } if (charger->wdt_timeout) ret = max77660_reg_set_bits(chip->parent, MAX77660_PWR_SLAVE, MAX77660_REG_GLOBAL_CFG1, MAX77660_GLBLCNFG1_ENCHGTL); else ret = max77660_reg_clr_bits(chip->parent, MAX77660_PWR_SLAVE, MAX77660_REG_GLOBAL_CFG1, MAX77660_GLBLCNFG1_ENCHGTL); if (ret < 0) { dev_err(chip->dev, "GLBLCNFG1_ENCHGTL update failed: %d\n", ret); return ret; } return ret; } static irqreturn_t max77660_chg_wdt_irq(int irq, void *data) { struct max77660_chg_extcon *chip = (struct max77660_chg_extcon *)data; int ret; ret = max77660_reg_write(chip->parent, MAX77660_PWR_SLAVE, MAX77660_REG_GLOBAL_CFG6, MAX77660_GLBLCNFG4_WDTC_SYS_CLR); if (ret < 0) dev_err(chip->dev, "GLOBAL_CFG4 update failed: %d\n", ret); return IRQ_HANDLED; } static int max77660_vbus_enable_time(struct regulator_dev *rdev) { return 500; } static int max77660_vbus_is_enabled(struct regulator_dev *rdev) { struct max77660_chg_extcon *chg_extcon =rdev_get_drvdata(rdev); int ret; u8 val; ret = max77660_reg_read(chg_extcon->parent, MAX77660_CHG_SLAVE, MAX77660_CHARGER_RBOOST, &val); if (ret < 0) { dev_err(chg_extcon->dev, "RBOOST read failed: %d\n", ret); return ret; } return !!(val & MAX77660_RBOOST_RBOOSTEN); } static int max77660_vbus_enable(struct regulator_dev *rdev) { struct max77660_chg_extcon *chg_extcon =rdev_get_drvdata(rdev); int ret; ret = max77660_reg_update(chg_extcon->parent, MAX77660_CHG_SLAVE, MAX77660_CHARGER_RBOOST, MAX77660_RBOOST_RBOUT_VOUT(0x6), MAX77660_RBOOST_RBOUT_MASK); if (ret < 0) { dev_err(chg_extcon->dev, "RBOOST update failed: %d\n", ret); return ret; } ret = max77660_reg_set_bits(chg_extcon->parent, MAX77660_CHG_SLAVE, MAX77660_CHARGER_RBOOST, MAX77660_RBOOST_RBOOSTEN); if (ret < 0) dev_err(chg_extcon->dev, "RBOOST setbits failed: %d\n", ret); return ret; } static int max77660_vbus_disable(struct regulator_dev *rdev) { struct max77660_chg_extcon *chg_extcon =rdev_get_drvdata(rdev); int ret; ret = max77660_reg_clr_bits(chg_extcon->parent, MAX77660_CHG_SLAVE, MAX77660_CHARGER_RBOOST, MAX77660_RBOOST_RBOOSTEN); if (ret < 0) dev_err(chg_extcon->dev, "RBOOST clrbits failed: %d\n", ret); return ret; } static struct regulator_ops max77660_vbus_ops = { .enable = max77660_vbus_enable, .disable = max77660_vbus_disable, .is_enabled = max77660_vbus_is_enabled, .enable_time = max77660_vbus_enable_time, }; static int max77660_init_vbus_regulator(struct max77660_chg_extcon *chg_extcon, struct max77660_vbus_platform_data *vbus_pdata) { struct regulator_config rconfig = { }; int ret = 0; if (!vbus_pdata) { dev_err(chg_extcon->dev, "No vbus platform data\n"); return 0; } chg_extcon->vbus_reg_desc.name = "max77660-vbus"; chg_extcon->vbus_reg_desc.ops = &max77660_vbus_ops; chg_extcon->vbus_reg_desc.type = REGULATOR_VOLTAGE; chg_extcon->vbus_reg_desc.owner = THIS_MODULE; chg_extcon->vbus_reg_init_data.supply_regulator = NULL; chg_extcon->vbus_reg_init_data.regulator_init = NULL; chg_extcon->vbus_reg_init_data.num_consumer_supplies = vbus_pdata->num_consumer_supplies; chg_extcon->vbus_reg_init_data.consumer_supplies = vbus_pdata->consumer_supplies; chg_extcon->vbus_reg_init_data.driver_data = chg_extcon; chg_extcon->vbus_reg_init_data.constraints.name = "max77660-vbus"; chg_extcon->vbus_reg_init_data.constraints.min_uV = 0; chg_extcon->vbus_reg_init_data.constraints.max_uV = 5000000, chg_extcon->vbus_reg_init_data.constraints.valid_modes_mask = REGULATOR_MODE_NORMAL | REGULATOR_MODE_STANDBY; chg_extcon->vbus_reg_init_data.constraints.valid_ops_mask = REGULATOR_CHANGE_MODE | REGULATOR_CHANGE_STATUS | REGULATOR_CHANGE_VOLTAGE; rconfig.dev = chg_extcon->dev; rconfig.of_node = NULL; rconfig.init_data = &chg_extcon->vbus_reg_init_data; rconfig.driver_data = chg_extcon; chg_extcon->vbus_rdev = regulator_register(&chg_extcon->vbus_reg_desc, &rconfig); if (IS_ERR(chg_extcon->vbus_rdev)) { ret = PTR_ERR(chg_extcon->vbus_rdev); dev_err(chg_extcon->dev, "Failed to register VBUS regulator: %d\n", ret); return ret; } return ret; } static int max77660_charger_get_status(struct battery_charger_dev *bc_dev) { struct max77660_chg_extcon *chip = battery_charger_get_drvdata(bc_dev); struct max77660_charger *charger = chip->charger; return charger->status; } static int max77660_charger_thermal_configure( struct battery_charger_dev *bc_dev, int temp, bool enable_charger, bool enable_charg_half_current, int battery_voltage) { struct max77660_chg_extcon *chip = battery_charger_get_drvdata(bc_dev); int temperature; int ret; if (!chip->cable_connected) return 0; temperature = temp; dev_info(chip->dev, "Battery temp %d\n", temperature); if (enable_charger) { if (!enable_charg_half_current && chip->charging_state != ENABLED_FULL_IBAT) { max77660_full_current_enable(chip); battery_charging_status_update(chip->bc_dev, BATTERY_CHARGING); } else if (enable_charg_half_current && chip->charging_state != ENABLED_HALF_IBAT) max77660_half_current_enable(chip); /* MBATREGMAX to 4.05V */ ret = max77660_reg_write(chip->parent, MAX77660_CHG_SLAVE, MAX77660_CHARGER_MBATREGMAX, MAX77660_MBATREG_4050MV); if (ret < 0) return ret; battery_charging_status_update(chip->bc_dev, BATTERY_CHARGING); } else { if (chip->charging_state != DISABLED) { max77660_charging_disable(chip); battery_charging_status_update(chip->bc_dev, BATTERY_DISCHARGING); } } return 0; } static int max77660_charging_restart(struct battery_charger_dev *bc_dev) { struct max77660_chg_extcon *chip = battery_charger_get_drvdata(bc_dev); int ret; if (!chip->cable_connected) return 0; dev_info(chip->dev, "Restarting the charging\n"); ret = max77660_set_charging_current(chip->chg_rdev, chip->last_charging_current, chip->last_charging_current); if (ret < 0) { dev_err(chip->dev, "restarting of charging failed: %d\n", ret); battery_charging_restart(chip->bc_dev, chip->chg_restart_time_sec); } return ret; } static int max77660_init_oc_alert(struct max77660_chg_extcon *chip) { int ret; u8 octh; octh = chip->charger->bcharger_pdata->oc_thresh; if (octh >= OC_THRESH_DIS) { ret = max77660_reg_clr_bits(chip->parent, MAX77660_CHG_SLAVE, MAX77660_CHARGER_BAT2SYS, MAX77660_CHARGER_BAT2SYS_OCEN); if (ret < 0) dev_err(chip->dev, "BAT2SYS update failed: %d\n", ret); return ret; } ret = max77660_reg_set_bits(chip->parent, MAX77660_PWR_SLAVE, MAX77660_REG_GLOBAL_CFG1, MAX77660_GLBLCNFG1_ENPGOC); if (ret < 0) { dev_err(chip->dev, "GLBLCNFG1 update failed: %d\n", ret); return ret; } ret = max77660_reg_update(chip->parent, MAX77660_CHG_SLAVE, MAX77660_CHARGER_BAT2SYS, MAX77660_CHARGER_BAT2SYS_OCEN | MAX77660_CHARGER_BAT2SYS_OC_MASK, MAX77660_CHARGER_BAT2SYS_OCEN | octh); if (ret < 0) dev_err(chip->dev, "BAT2SYS update failed: %d\n", ret); return ret; } static struct battery_charging_ops max77660_charger_bci_ops = { .get_charging_status = max77660_charger_get_status, .thermal_configure = max77660_charger_thermal_configure, .restart_charging = max77660_charging_restart, }; static struct battery_charger_info max77660_charger_bci = { .cell_id = 0, .bc_ops = &max77660_charger_bci_ops, }; static int max77660_chg_extcon_probe(struct platform_device *pdev) { struct max77660_chg_extcon *chg_extcon; struct max77660_platform_data *pdata; struct max77660_charger_platform_data *chg_pdata; struct max77660_bcharger_platform_data *bcharger_pdata; struct max77660_vbus_platform_data *vbus_pdata; struct extcon_dev *edev; struct max77660_charger *charger; int ret; pdata = dev_get_platdata(pdev->dev.parent); if (!pdata || !pdata->charger_pdata) { dev_err(&pdev->dev, "No platform data\n"); return -ENODEV; } chg_pdata = pdata->charger_pdata; bcharger_pdata = chg_pdata->bcharger_pdata; vbus_pdata = chg_pdata->vbus_pdata; chg_extcon = devm_kzalloc(&pdev->dev, sizeof(*chg_extcon), GFP_KERNEL); if (!chg_extcon) { dev_err(&pdev->dev, "Memory allocation failed for chg_extcon\n"); return -ENOMEM; } edev = devm_kzalloc(&pdev->dev, sizeof(*edev), GFP_KERNEL); if (!edev) { dev_err(&pdev->dev, "Memory allocation failed for edev\n"); return -ENOMEM; } chg_extcon->charger = devm_kzalloc(&pdev->dev, sizeof(*(chg_extcon->charger)), GFP_KERNEL); if (!chg_extcon->charger) { dev_err(&pdev->dev, "Memory allocation failed for charger\n"); return -ENOMEM; } charger = chg_extcon->charger; charger->status = BATTERY_DISCHARGING; charger->bcharger_pdata = bcharger_pdata; chg_extcon->edev = edev; chg_extcon->edev->name = (chg_pdata->ext_conn_name) ? chg_pdata->ext_conn_name : dev_name(&pdev->dev); chg_extcon->edev->supported_cable = max77660_excon_cable; chg_extcon->dev = &pdev->dev; chg_extcon->parent = pdev->dev.parent; dev_set_drvdata(&pdev->dev, chg_extcon); chg_extcon->chg_irq = platform_get_irq(pdev, 0); chg_extcon->chg_wdt_irq = platform_get_irq(pdev, 1); max77660_ext = chg_extcon; ret = extcon_dev_register(chg_extcon->edev, NULL); if (ret < 0) { dev_err(&pdev->dev, "failed to register extcon device\n"); return ret; } /* Set initial state */ ret = max77660_chg_extcon_cable_update(chg_extcon); if (ret < 0) { dev_err(&pdev->dev, "Cable init failed: %d\n", ret); goto extcon_free; } ret = max77660_reg_clr_bits(chg_extcon->parent, MAX77660_CHG_SLAVE, MAX77660_CHARGER_CHGINTM, MAX77660_CHG_CHGINT_DC_UVP); if (ret < 0) { dev_err(chg_extcon->dev, "CHGINTM update failed: %d\n", ret); goto extcon_free; } ret = request_threaded_irq(chg_extcon->chg_irq, NULL, max77660_chg_extcon_irq, IRQF_ONESHOT | IRQF_EARLY_RESUME, "max77660-charger", chg_extcon); if (ret < 0) { dev_err(chg_extcon->dev, "request irq %d failed: %dn", chg_extcon->chg_irq, ret); goto extcon_free; } ret = max77660_init_vbus_regulator(chg_extcon, vbus_pdata); if (ret < 0) { dev_err(chg_extcon->dev, "Vbus regulator init failed %d\n", ret); goto chg_irq_free; } if (!bcharger_pdata) { dev_info(chg_extcon->dev, "Battery not connected, charging not supported\n"); goto skip_bcharger_init; } chg_extcon->battery_present = true; charger->wdt_timeout = bcharger_pdata->wdt_timeout; ret = request_threaded_irq(chg_extcon->chg_wdt_irq, NULL, max77660_chg_wdt_irq, IRQF_ONESHOT | IRQF_EARLY_RESUME, "max77660-charger-wdt", chg_extcon); if (ret < 0) { dev_err(chg_extcon->dev, "request irq %d failed: %d\n", chg_extcon->chg_wdt_irq, ret); goto vbus_reg_err; } ret = max77660_init_charger_regulator(chg_extcon, bcharger_pdata); if (ret < 0) { dev_err(&pdev->dev, "Failed to register charger regulator: %d\n", ret); goto wdt_irq_free; } ret = max77660_charger_wdt(chg_extcon); if (ret < 0) { dev_err(&pdev->dev, "Charger watchdog timer init failed %d\n", ret); goto chg_reg_err; } ret = max77660_init_oc_alert(chg_extcon); if (ret < 0) { dev_err(&pdev->dev, "OC init failed: %d\n", ret); goto chg_reg_err; } /* Charging auto restart time */ chg_extcon->chg_restart_time_sec = MAX77660_RESTART_CHARGING_AFTER_DONE; max77660_charger_bci.polling_time_sec = bcharger_pdata->temperature_poll_period_secs; max77660_charger_bci.tz_name = bcharger_pdata->tz_name; chg_extcon->bc_dev = battery_charger_register(&pdev->dev, &max77660_charger_bci, chg_extcon); if (IS_ERR(chg_extcon->bc_dev)) { ret = PTR_ERR(chg_extcon->bc_dev); dev_err(&pdev->dev, "battery charger register failed: %d\n", ret); goto chg_reg_err; } skip_bcharger_init: device_set_wakeup_capable(&pdev->dev, 1); return 0; chg_reg_err: regulator_unregister(chg_extcon->chg_rdev); wdt_irq_free: free_irq(chg_extcon->chg_wdt_irq, chg_extcon); vbus_reg_err: regulator_unregister(chg_extcon->vbus_rdev); chg_irq_free: free_irq(chg_extcon->chg_irq, chg_extcon); extcon_free: extcon_dev_unregister(chg_extcon->edev); return ret; } static int max77660_chg_extcon_remove(struct platform_device *pdev) { struct max77660_chg_extcon *chg_extcon = dev_get_drvdata(&pdev->dev); free_irq(chg_extcon->chg_irq, chg_extcon); regulator_unregister(chg_extcon->vbus_rdev); if (chg_extcon->battery_present) { battery_charger_unregister(chg_extcon->bc_dev); extcon_dev_unregister(chg_extcon->edev); free_irq(chg_extcon->chg_wdt_irq, chg_extcon); } return 0; } #ifdef CONFIG_PM_SLEEP static int max77660_chg_extcon_suspend(struct device *dev) { struct max77660_chg_extcon *chg_extcon = dev_get_drvdata(dev); int ret; if (device_may_wakeup(dev)) { enable_irq_wake(chg_extcon->chg_irq); if (chg_extcon->battery_present && chg_extcon->charger->wdt_timeout) enable_irq_wake(chg_extcon->chg_wdt_irq); } else { if (chg_extcon->battery_present && chg_extcon->charger->wdt_timeout) { ret = max77660_reg_clr_bits(chg_extcon->parent, MAX77660_PWR_SLAVE, MAX77660_REG_GLOBAL_CFG1, MAX77660_GLBLCNFG1_ENCHGTL); if (ret < 0) dev_err(chg_extcon->dev, "GLBLCNFG1_ENCHGTL update failed: %d\n", ret); } } return 0; } static int max77660_chg_extcon_resume(struct device *dev) { struct max77660_chg_extcon *chg_extcon = dev_get_drvdata(dev); int ret; if (device_may_wakeup(dev)) { disable_irq_wake(chg_extcon->chg_irq); if (chg_extcon->battery_present && chg_extcon->charger->wdt_timeout) disable_irq_wake(chg_extcon->chg_wdt_irq); } else { if (chg_extcon->battery_present && chg_extcon->charger->wdt_timeout) { ret = max77660_reg_set_bits(chg_extcon->parent, MAX77660_PWR_SLAVE, MAX77660_REG_GLOBAL_CFG1, MAX77660_GLBLCNFG1_ENCHGTL); if (ret < 0) dev_err(chg_extcon->dev, "GLBLCNFG1_ENCHGTL update failed: %d\n", ret); } } return 0; }; #endif static const struct dev_pm_ops max77660_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(max77660_chg_extcon_suspend, max77660_chg_extcon_resume) }; static struct platform_driver max77660_chg_extcon_driver = { .probe = max77660_chg_extcon_probe, .remove = max77660_chg_extcon_remove, .driver = { .name = "max77660-charger-extcon", .owner = THIS_MODULE, .pm = &max77660_pm_ops, }, }; static int __init max77660_chg_extcon_driver_init(void) { return platform_driver_register(&max77660_chg_extcon_driver); } subsys_initcall_sync(max77660_chg_extcon_driver_init); static void __exit max77660_chg_extcon_driver_exit(void) { platform_driver_unregister(&max77660_chg_extcon_driver); } module_exit(max77660_chg_extcon_driver_exit); MODULE_DESCRIPTION("max77660 charger-extcon driver"); MODULE_AUTHOR("Darbha Sriharsha"); MODULE_AUTHOR("Syed Rafiuddin"); MODULE_AUTHOR("Laxman Dewangan"); MODULE_ALIAS("platform:max77660-charger-extcon"); MODULE_LICENSE("GPL v2");