blob: d00b3ea5792fe8da9cdbc84428bc67c193ce119d [file] [log] [blame]
/*
* max77660-charger-extcon.c -- MAXIM MAX77660 VBUS detection.
*
* Copyright (c) 2013, NVIDIA CORPORATION. All rights reserved.
*
* Author: Darbha Sriharsha <dsriharsha@nvidia.com>
* Author: Syed Rafiuddin <srafiuddin@nvidia.com>
* Author: Laxman Dewangan <ldewangan@nvidia.com>
*
* 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 <linux/module.h>
#include <linux/err.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/iio/consumer.h>
#include <linux/pm.h>
#include <linux/extcon.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/machine.h>
#include <linux/mfd/max77660/max77660-core.h>
#include <linux/workqueue.h>
#include <linux/kthread.h>
#include <linux/delay.h>
#include <linux/sched.h>
#include <linux/time.h>
#include <linux/timer.h>
#include <linux/power_supply.h>
#include <linux/power/battery-charger-gauge-comm.h>
#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 500000;
}
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<dsriharsha@nvidia.com>");
MODULE_AUTHOR("Syed Rafiuddin<srafiuddin@nvidia.com>");
MODULE_AUTHOR("Laxman Dewangan<ldewangan@nvidia.com>");
MODULE_ALIAS("platform:max77660-charger-extcon");
MODULE_LICENSE("GPL v2");