2664 lines
64 KiB
C
2664 lines
64 KiB
C
/*
|
|
* Summit Microelectronics SMB347 Battery Charger Driver
|
|
*
|
|
* Copyright (C) 2011, Intel Corporation
|
|
*
|
|
* Authors: Bruce E. Robertson <bruce.e.robertson@intel.com>
|
|
* Mika Westerberg <mika.westerberg@linux.intel.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*/
|
|
|
|
#include <linux/debugfs.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/init.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/power_supply.h>
|
|
#include <linux/workqueue.h>
|
|
#include <linux/power/smb347-charger.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/usb/phy.h>
|
|
#include <linux/notifier.h>
|
|
#include <linux/extcon.h>
|
|
#include <linux/acpi.h>
|
|
#include <linux/acpi_gpio.h>
|
|
#include <linux/wakelock.h>
|
|
#include <linux/regulator/of_regulator.h>
|
|
#include <linux/regulator/consumer.h>
|
|
/*
|
|
* Configuration registers. These are mirrored to volatile RAM and can be
|
|
* written once %CMD_A_ALLOW_WRITE is set in %CMD_A register. They will be
|
|
* reloaded from non-volatile registers after POR.
|
|
*/
|
|
#define CFG_CHARGE_CURRENT 0x00
|
|
#define CFG_CHARGE_CURRENT_FCC_MASK 0xe0
|
|
#define CFG_CHARGE_CURRENT_FCC_SHIFT 5
|
|
#define CFG_CHARGE_CURRENT_PCC_MASK 0x18
|
|
#define CFG_CHARGE_CURRENT_PCC_SHIFT 3
|
|
#define CFG_CHARGE_CURRENT_TC_MASK 0x07
|
|
#define CFG_CURRENT_LIMIT 0x01
|
|
#define CFG_CURRENT_LIMIT_DC_MASK 0xf0
|
|
#define CFG_CURRENT_LIMIT_DC_SHIFT 4
|
|
#define CFG_CURRENT_LIMIT_USB_MASK 0x0f
|
|
#define CFG_VARIOUS_FUNCS 0x02
|
|
#define CFG_VARIOUS_FUNCS_PRIORITY_USB BIT(2)
|
|
#define CFG_FLOAT_VOLTAGE 0x03
|
|
#define CFG_FLOAT_VOLTAGE_THRESHOLD_MASK 0xc0
|
|
#define CFG_FLOAT_VOLTAGE_THRESHOLD_SHIFT 6
|
|
#define CFG_CHRG_CONTROL 0x4
|
|
#define CFG_CHRG_CTRL_HW_TERM BIT(6)
|
|
#define CFG_STAT 0x05
|
|
#define CFG_STAT_DISABLED BIT(5)
|
|
#define CFG_STAT_ACTIVE_HIGH BIT(7)
|
|
#define CFG_PIN 0x06
|
|
#define CFG_PIN_EN_CTRL_MASK 0x60
|
|
#define CFG_PIN_EN_CTRL_ACTIVE_HIGH 0x40
|
|
#define CFG_PIN_EN_CTRL_ACTIVE_LOW 0x60
|
|
#define CFG_PIN_EN_APSD_IRQ BIT(1)
|
|
#define CFG_PIN_EN_CHARGER_ERROR BIT(2)
|
|
#define CFG_THERM 0x07
|
|
#define CFG_THERM_SOFT_HOT_COMPENSATION_MASK 0x03
|
|
#define CFG_THERM_SOFT_HOT_COMPENSATION_SHIFT 0
|
|
#define CFG_THERM_SOFT_COLD_COMPENSATION_MASK 0x0c
|
|
#define CFG_THERM_SOFT_COLD_COMPENSATION_SHIFT 2
|
|
#define CFG_THERM_MONITOR_DISABLED BIT(4)
|
|
#define CFG_SYSOK 0x08
|
|
#define CFG_SYSOK_SUSPEND_HARD_LIMIT_DISABLED BIT(2)
|
|
#define CFG_OTHER 0x09
|
|
#define CFG_OTHER_RID_MASK 0xc0
|
|
#define CFG_OTHER_RID_DISABLED_OTG_I2C 0x00
|
|
#define CFG_OTHER_RID_DISABLED_OTG_PIN 0x40
|
|
#define CFG_OTHER_RID_ENABLED_OTG_I2C 0x80
|
|
#define CFG_OTHER_RID_ENABLED_AUTO_OTG 0xc0
|
|
#define CFG_OTHER_OTG_PIN_ACTIVE_LOW BIT(5)
|
|
#define CFG_OTG 0x0a
|
|
#define CFG_OTG_TEMP_THRESHOLD_MASK 0x30
|
|
#define CFG_OTG_TEMP_THRESHOLD_SHIFT 4
|
|
#define CFG_OTG_CC_COMPENSATION_MASK 0xc0
|
|
#define CFG_OTG_CC_COMPENSATION_SHIFT 6
|
|
#define CFG_OTG_BATTERY_UVLO_THRESHOLD_MASK 0x03
|
|
#define CFG_TEMP_LIMIT 0x0b
|
|
#define CFG_TEMP_LIMIT_SOFT_HOT_MASK 0x03
|
|
#define CFG_TEMP_LIMIT_SOFT_HOT_SHIFT 0
|
|
#define CFG_TEMP_LIMIT_SOFT_COLD_MASK 0x0c
|
|
#define CFG_TEMP_LIMIT_SOFT_COLD_SHIFT 2
|
|
#define CFG_TEMP_LIMIT_HARD_HOT_MASK 0x30
|
|
#define CFG_TEMP_LIMIT_HARD_HOT_SHIFT 4
|
|
#define CFG_TEMP_LIMIT_HARD_COLD_MASK 0xc0
|
|
#define CFG_TEMP_LIMIT_HARD_COLD_SHIFT 6
|
|
#define CFG_FAULT_IRQ 0x0c
|
|
#define CFG_FAULT_IRQ_DCIN_UV BIT(2)
|
|
#define CFG_FAULT_IRQ_OTG_UV BIT(5)
|
|
#define CFG_STATUS_IRQ 0x0d
|
|
#define CFG_STATUS_IRQ_CHARGE_TIMEOUT BIT(7)
|
|
#define CFG_STATUS_OTG_DET BIT(6)
|
|
#define CFG_STATUS_IRQ_TERMINATION_OR_TAPER BIT(4)
|
|
#define CFG_STATUS_IRQ_INOK BIT(2)
|
|
#define CFG_ADDRESS 0x0e
|
|
|
|
/* Command registers */
|
|
#define CMD_A 0x30
|
|
#define CMD_A_CHG_ENABLED BIT(1)
|
|
#define CMD_A_SUSPEND_ENABLED BIT(2)
|
|
#define CMD_A_OTG_ENABLED BIT(4)
|
|
#define CMD_A_FORCE_FCC BIT(6)
|
|
#define CMD_A_ALLOW_WRITE BIT(7)
|
|
#define CMD_B 0x31
|
|
#define CMD_B_MODE_HC BIT(0)
|
|
#define CMD_C 0x33
|
|
|
|
/* Interrupt Status registers */
|
|
#define IRQSTAT_A 0x35
|
|
#define IRQSTAT_A_HOT_HARD_STAT BIT(6)
|
|
#define IRQSTAT_A_HOT_HARD_IRQ BIT(7)
|
|
#define IRQSTAT_A_COLD_HARD_STAT BIT(4)
|
|
#define IRQSTAT_A_COLD_HARD_IRQ BIT(5)
|
|
#define IRQSTAT_B 0x36
|
|
#define IRQSTAT_B_BATOVP_STAT BIT(6)
|
|
#define IRQSTAT_B_BATOVP_IRQ BIT(7)
|
|
#define IRQSTAT_C 0x37
|
|
#define IRQSTAT_C_TERMINATION_STAT BIT(0)
|
|
#define IRQSTAT_C_TERMINATION_IRQ BIT(1)
|
|
#define IRQSTAT_C_TAPER_IRQ BIT(3)
|
|
#define IRQSTAT_D 0x38
|
|
#define IRQSTAT_D_CHARGE_TIMEOUT_STAT BIT(2)
|
|
#define IRQSTAT_D_CHARGE_TIMEOUT_IRQ BIT(3)
|
|
#define IRQSTAT_D_APSD_STAT BIT(6)
|
|
#define IRQSTAT_D_APSD_IRQ BIT(7)
|
|
#define IRQSTAT_E 0x39
|
|
#define IRQSTAT_E_USBIN_UV_STAT BIT(0)
|
|
#define IRQSTAT_E_USBIN_UV_IRQ BIT(1)
|
|
#define IRQSTAT_E_DCIN_UV_STAT BIT(4)
|
|
#define IRQSTAT_E_DCIN_UV_IRQ BIT(5)
|
|
/*
|
|
* In SMB349 the DCIN UV status bits are reversed in the
|
|
* IRQSTAT_E register, the DCIN_UV IRQ & Status correspond
|
|
* to bits 1 & 0 respectively.
|
|
*/
|
|
#define SMB349_IRQSTAT_E_DCIN_UV_STAT BIT(0)
|
|
#define SMB349_IRQSTAT_E_DCIN_UV_IRQ BIT(1)
|
|
#define SMB349_IRQSTAT_E_DCIN_OV_STAT BIT(2)
|
|
#define SMB349_IRQSTAT_E_DCIN_OV_IRQ BIT(3)
|
|
#define IRQSTAT_F 0x3a
|
|
#define IRQSTAT_F_OTG_UV_IRQ BIT(5)
|
|
#define IRQSTAT_F_OTG_UV_STAT BIT(4)
|
|
#define IRQSTAT_F_OTG_DET_IRQ BIT(3)
|
|
#define IRQSTAT_F_OTG_DET_STAT BIT(2)
|
|
#define IRQSTAT_F_PWR_OK_IRQ BIT(1)
|
|
#define IRQSTAT_F_PWR_OK_STAT BIT(0)
|
|
|
|
/* Status registers */
|
|
#define STAT_A 0x3b
|
|
#define STAT_A_FLOAT_VOLTAGE_MASK 0x3f
|
|
#define STAT_B 0x3c
|
|
#define STAT_B_RID_GROUND 0x10
|
|
#define STAT_C 0x3d
|
|
#define STAT_C_CHG_ENABLED BIT(0)
|
|
#define STAT_C_HOLDOFF_STAT BIT(3)
|
|
#define STAT_C_CHG_MASK 0x06
|
|
#define STAT_C_CHG_SHIFT 1
|
|
#define STAT_C_CHG_TERM BIT(5)
|
|
#define STAT_C_CHARGER_ERROR BIT(6)
|
|
#define STAT_D 0x3E
|
|
#define STAT_E 0x3F
|
|
#define SMB349_SUSPENDED (1<<7)
|
|
|
|
#define STATUS_UPDATE_INTERVAL (HZ * 60) /*60 sec */
|
|
|
|
#define SMB_FCC_1000 (0<<4)
|
|
#define SMB_FCC_1200 (1<<4)
|
|
#define SMB_FCC_1400 (2<<4)
|
|
#define SMB_FCC_1600 (3<<4)
|
|
#define SMB_FCC_1800 (4<<4)
|
|
#define SMB_FCC_2000 (5<<4)
|
|
#define SMB_FCC_MASK (0xf<<4)
|
|
|
|
#define CC_1800 1800
|
|
#define CC_1600 1600
|
|
#define CC_1400 1400
|
|
#define CC_1200 1200
|
|
#define CC_1000 1000
|
|
|
|
#define SMB_INLMT_500 0
|
|
#define SMB_INLMT_900 1
|
|
#define SMB_INLMT_1000 2
|
|
#define SMB_INLMT_1100 3
|
|
#define SMB_INLMT_1200 4
|
|
#define SMB_INLMT_1300 5
|
|
#define SMB_INLMT_1500 6
|
|
#define SMB_INLMT_1600 7
|
|
#define SMB_INLMT_1700 8
|
|
#define SMB_INLMT_1800 9
|
|
#define SMB_INLMT_2000 0xa
|
|
#define SMB_INLMT_MASK 0xf
|
|
|
|
#define ILIM_1800 1800
|
|
#define ILIM_1500 1500
|
|
#define ILIM_1000 1000
|
|
#define ILIM_500 500
|
|
|
|
#define SMB_FVLTG_4350 0x2D
|
|
#define SMB_FVLTG_4200 0x25
|
|
#define SMB_FVLTG_4100 0x20
|
|
#define SMB_FVLTG_4000 0x1B
|
|
#define SMB_FVLTG_MASK 0x3F
|
|
|
|
#define CV_4350 4350
|
|
#define CV_4200 4200
|
|
#define CV_4100 4100
|
|
#define CV_4000 4000
|
|
|
|
#define ITERM_400 400
|
|
#define ITERM_300 300
|
|
#define ITERM_200 200
|
|
#define ITERM_100 100
|
|
|
|
#define SMB_ITERM_200 (0<<2)
|
|
#define SMB_ITERM_300 (1<<2)
|
|
#define SMB_ITERM_400 (2<<2)
|
|
#define SMB_ITERM_500 (3<<2)
|
|
#define SMB_ITERM_600 (4<<2)
|
|
#define SMB_ITERM_700 (5<<2)
|
|
#define SMB_ITERM_100 (6<<2)
|
|
|
|
#define SMB_ITERM_MASK (7<<2)
|
|
|
|
#define SMB_CHRG_TYPE_ACA_DOCK (1<<7)
|
|
#define SMB_CHRG_TYPE_ACA_C (1<<6)
|
|
#define SMB_CHRG_TYPE_ACA_B (1<<5)
|
|
#define SMB_CHRG_TYPE_ACA_A (1<<4)
|
|
#define SMB_CHRG_TYPE_CDP (1<<3)
|
|
#define SMB_CHRG_TYPE_DCP (1<<2)
|
|
#define SMB_CHRG_TYPE_SDP (1<<1)
|
|
#define SMB_CHRG_TYPE_UNKNOWN (1<<0)
|
|
|
|
#define SMB_CHRG_CUR_DCP 1800
|
|
#define SMB_CHRG_CUR_ACA 1800
|
|
#define SMB_CHRG_CUR_CDP 1500
|
|
#define SMB_CHRG_CUR_SDP 500
|
|
|
|
#define CFG_PIN_DEFAULT_CFG 0x7E
|
|
#define SMB34X_FULL_WORK_JIFFIES (30*HZ)
|
|
#define SMB34X_TEMP_WORK_JIFFIES (30*HZ)
|
|
#define SMB34X_CHG_UPD_JIFFIES (HZ)
|
|
|
|
#define RETRY_WRITE 3
|
|
#define RETRY_READ 3
|
|
|
|
#define SMB34X_EXTCON_SDP "CHARGER_USB_SDP"
|
|
#define SMB34X_EXTCON_DCP "CHARGER_USB_DCP"
|
|
#define SMB34X_EXTCON_CDP "CHARGER_USB_CDP"
|
|
|
|
#define REGULATOR_V3P3SX "v3p3sx"
|
|
|
|
static const char *smb34x_extcon_cable[] = {
|
|
SMB34X_EXTCON_SDP,
|
|
SMB34X_EXTCON_DCP,
|
|
SMB34X_EXTCON_CDP,
|
|
NULL,
|
|
};
|
|
|
|
static const short smb349_inlim[] = { /* mA */
|
|
500, 900, 1000, 1100, 1200, 1300, 1500, 1600,
|
|
1700, 1800, 2000, 2200, 2400, 2500, 3000, 3500
|
|
};
|
|
|
|
struct smb347_otg_event {
|
|
struct list_head node;
|
|
bool param;
|
|
};
|
|
|
|
/**
|
|
* struct smb347_charger - smb347 charger instance
|
|
* @lock: protects concurrent access to online variables
|
|
* @client: pointer to i2c client
|
|
* @mains: power_supply instance for AC/DC power
|
|
* @usb: power_supply instance for USB power
|
|
* @battery: power_supply instance for battery
|
|
* @mains_online: is AC/DC input connected
|
|
* @usb_online: is USB input connected
|
|
* @charging_enabled: is charging enabled
|
|
* @running: the driver is up and running
|
|
* @dentry: for debugfs
|
|
* @otg: pointer to OTG transceiver if any
|
|
* @otg_nb: notifier for OTG notifications
|
|
* @otg_work: work struct for OTG notifications
|
|
* @otg_queue: queue holding received OTG notifications
|
|
* @otg_queue_lock: protects concurrent access to @otg_queue
|
|
* @otg_last_param: last parameter value from OTG notification
|
|
* @otg_enabled: OTG VBUS is enabled
|
|
* @otg_battery_uv: OTG battery undervoltage condition is on
|
|
* @pdata: pointer to platform data
|
|
*/
|
|
struct smb347_charger {
|
|
struct mutex lock;
|
|
struct i2c_client *client;
|
|
struct power_supply mains;
|
|
struct power_supply usb;
|
|
struct power_supply battery;
|
|
struct delayed_work chg_upd_worker;
|
|
struct delayed_work temp_upd_worker;
|
|
bool mains_online;
|
|
bool usb_online;
|
|
bool charging_enabled;
|
|
bool running;
|
|
bool is_smb349;
|
|
bool drive_vbus;
|
|
bool a_bus_enable;
|
|
struct dentry *dentry;
|
|
struct usb_phy *otg;
|
|
struct notifier_block otg_nb;
|
|
struct work_struct otg_work;
|
|
struct list_head otg_queue;
|
|
spinlock_t otg_queue_lock;
|
|
bool otg_enabled;
|
|
bool otg_battery_uv;
|
|
bool is_disabled;
|
|
const struct smb347_charger_platform_data *pdata;
|
|
struct extcon_dev *edev;
|
|
/* power supply properties */
|
|
enum power_supply_charger_cable_type cable_type;
|
|
int inlmt;
|
|
int cc;
|
|
int cv;
|
|
int max_cc;
|
|
int max_cv;
|
|
int max_temp;
|
|
int min_temp;
|
|
int iterm;
|
|
int cntl_state;
|
|
int online;
|
|
int present;
|
|
/*
|
|
* regulator v3p3sx used by display driver to save 7mW in
|
|
* S3 for USB Host
|
|
*/
|
|
struct regulator *regulator_v3p3sx;
|
|
bool regulator_enabled;
|
|
#ifdef CONFIG_POWER_SUPPLY_CHARGER
|
|
struct delayed_work full_worker;
|
|
#endif
|
|
struct wake_lock wakelock;
|
|
};
|
|
|
|
static struct smb347_charger *smb347_dev;
|
|
static int smb347_set_iterm(struct smb347_charger *smb, int iterm);
|
|
static int smb347_disable_suspend(struct smb347_charger *smb);
|
|
static int sm347_reload_setting(struct smb347_charger *smb);
|
|
|
|
static int smb347_read(struct smb347_charger *smb, u8 reg)
|
|
{
|
|
int ret, i;
|
|
|
|
for (i = 0; i < RETRY_READ; i++) {
|
|
ret = i2c_smbus_read_byte_data(smb->client, reg);
|
|
if (ret < 0) {
|
|
dev_warn(&smb->client->dev,
|
|
"failed to read reg 0x%x: %d\n", reg, ret);
|
|
mdelay(1);
|
|
continue;
|
|
} else
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int smb347_write(struct smb347_charger *smb, u8 reg, u8 val)
|
|
{
|
|
int ret, i;
|
|
|
|
for (i = 0; i < RETRY_WRITE; i++) {
|
|
ret = i2c_smbus_write_byte_data(smb->client, reg, val);
|
|
if (ret < 0) {
|
|
dev_warn(&smb->client->dev,
|
|
"failed to write reg 0x%x: %d\n", reg, ret);
|
|
mdelay(1);
|
|
continue;
|
|
} else
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static bool smb347_is_suspended(struct smb347_charger *smb)
|
|
{
|
|
int ret;
|
|
|
|
ret = smb347_read(smb, STAT_E);
|
|
if (ret < 0) {
|
|
dev_warn(&smb->client->dev, "i2c failed %d", ret);
|
|
return false;
|
|
}
|
|
return (ret & SMB349_SUSPENDED) == SMB349_SUSPENDED;
|
|
}
|
|
|
|
/*
|
|
* smb347_set_writable - enables/disables writing to non-volatile registers
|
|
* @smb: pointer to smb347 charger instance
|
|
*
|
|
* You can enable/disable writing to the non-volatile configuration
|
|
* registers by calling this function.
|
|
*
|
|
* Returns %0 on success and negative errno in case of failure.
|
|
*/
|
|
static int smb347_set_writable(struct smb347_charger *smb, bool writable)
|
|
{
|
|
int ret;
|
|
|
|
ret = smb347_read(smb, CMD_A);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (writable)
|
|
ret |= CMD_A_ALLOW_WRITE;
|
|
else
|
|
ret &= ~CMD_A_ALLOW_WRITE;
|
|
|
|
return smb347_write(smb, CMD_A, ret);
|
|
}
|
|
|
|
static inline int smb347_force_fcc(struct smb347_charger *smb)
|
|
{
|
|
int ret;
|
|
|
|
ret = smb347_read(smb, CMD_A);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret |= CMD_A_FORCE_FCC;
|
|
|
|
return smb347_write(smb, CMD_A, ret);
|
|
}
|
|
|
|
static int smb34x_get_health(struct smb347_charger *smb)
|
|
{
|
|
int stat_e = 0, usb;
|
|
int chrg_health;
|
|
|
|
if (!smb->is_smb349) {
|
|
chrg_health = POWER_SUPPLY_HEALTH_UNKNOWN;
|
|
goto end;
|
|
}
|
|
if (smb->pdata->detect_chg) {
|
|
usb = smb347_read(smb, STAT_D);
|
|
if (usb < 0) {
|
|
dev_err(&smb->client->dev, "%s:i2c read error", __func__);
|
|
chrg_health = POWER_SUPPLY_HEALTH_UNKNOWN;
|
|
goto end;
|
|
}
|
|
usb = !smb->is_disabled && usb;
|
|
} else {
|
|
usb = !smb->is_disabled;
|
|
}
|
|
|
|
stat_e = smb347_read(smb, IRQSTAT_E);
|
|
if (stat_e < 0) {
|
|
dev_warn(&smb->client->dev, "i2c failed %d", stat_e);
|
|
chrg_health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
|
|
goto end;
|
|
}
|
|
|
|
if (usb) {
|
|
/* charger present && charger not disabled */
|
|
if (stat_e & SMB349_IRQSTAT_E_DCIN_UV_STAT)
|
|
chrg_health = POWER_SUPPLY_HEALTH_DEAD;
|
|
else if (stat_e & SMB349_IRQSTAT_E_DCIN_OV_STAT)
|
|
chrg_health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
|
|
else
|
|
chrg_health = POWER_SUPPLY_HEALTH_GOOD;
|
|
|
|
} else {
|
|
chrg_health = POWER_SUPPLY_HEALTH_UNKNOWN;
|
|
}
|
|
if (chrg_health != POWER_SUPPLY_HEALTH_GOOD)
|
|
smb->online = 0;
|
|
end:
|
|
return chrg_health;
|
|
}
|
|
|
|
/**
|
|
* smb347_update_status - updates the charging status
|
|
* @smb: pointer to smb347 charger instance
|
|
*
|
|
* Function checks status of the charging and updates internal state
|
|
* accordingly. Returns %0 if there is no change in status, %1 if the
|
|
* status has changed and negative errno in case of failure.
|
|
*/
|
|
static int smb347_update_status(struct smb347_charger *smb)
|
|
{
|
|
bool usb = false;
|
|
bool dc = false;
|
|
int ret;
|
|
|
|
ret = smb347_read(smb, IRQSTAT_E);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (smb->is_smb349) {
|
|
if (smb->pdata->use_mains)
|
|
dc = !(ret & SMB349_IRQSTAT_E_DCIN_UV_STAT);
|
|
else if (smb->pdata->use_usb) {
|
|
usb = !(ret & (SMB349_IRQSTAT_E_DCIN_UV_STAT |
|
|
SMB349_IRQSTAT_E_DCIN_OV_STAT)) &&
|
|
!smb->is_disabled;
|
|
}
|
|
|
|
mutex_lock(&smb->lock);
|
|
ret = smb->mains_online != dc || smb->usb_online != usb;
|
|
smb->mains_online = dc;
|
|
smb->usb_online = usb;
|
|
mutex_unlock(&smb->lock);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Dc and usb are set depending on whether they are enabled in
|
|
* platform data _and_ whether corresponding undervoltage is set.
|
|
*/
|
|
if (smb->pdata->use_mains)
|
|
dc = !(ret & IRQSTAT_E_DCIN_UV_STAT);
|
|
if (smb->pdata->use_usb)
|
|
usb = !(ret & IRQSTAT_E_USBIN_UV_STAT);
|
|
|
|
mutex_lock(&smb->lock);
|
|
ret = smb->mains_online != dc || smb->usb_online != usb;
|
|
smb->mains_online = dc;
|
|
smb->usb_online = usb;
|
|
mutex_unlock(&smb->lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* smb347_is_online - returns whether input power source is connected
|
|
* @smb: pointer to smb347 charger instance
|
|
*
|
|
* Returns %true if input power source is connected. Note that this is
|
|
* dependent on what platform has configured for usable power sources. For
|
|
* example if USB is disabled, this will return %false even if the USB
|
|
* cable is connected.
|
|
*/
|
|
static bool smb347_is_online(struct smb347_charger *smb)
|
|
{
|
|
bool ret;
|
|
|
|
mutex_lock(&smb->lock);
|
|
ret = (smb->usb_online || smb->mains_online) && !smb->otg_enabled;
|
|
mutex_unlock(&smb->lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* smb347_charging_status - returns status of charging
|
|
* @smb: pointer to smb347 charger instance
|
|
*
|
|
* Function returns charging status. %0 means no charging is in progress,
|
|
* %1 means pre-charging, %2 fast-charging and %3 taper-charging.
|
|
*/
|
|
static int smb347_charging_status(struct smb347_charger *smb)
|
|
{
|
|
int ret;
|
|
|
|
if (!smb347_is_online(smb))
|
|
return 0;
|
|
|
|
ret = smb347_read(smb, STAT_C);
|
|
if (ret < 0)
|
|
return 0;
|
|
|
|
return (ret & STAT_C_CHG_MASK) >> STAT_C_CHG_SHIFT;
|
|
}
|
|
|
|
static void smb347_enable_termination(struct smb347_charger *smb,
|
|
bool enable)
|
|
{
|
|
int ret;
|
|
|
|
mutex_lock(&smb->lock);
|
|
ret = smb347_set_writable(smb, true);
|
|
if (ret < 0) {
|
|
dev_warn(&smb->client->dev, "i2c error %d", ret);
|
|
return;
|
|
}
|
|
|
|
ret = smb347_read(smb, CFG_STATUS_IRQ);
|
|
if (ret < 0) {
|
|
dev_warn(&smb->client->dev, "i2c error %d", ret);
|
|
goto err_term;
|
|
}
|
|
|
|
if (enable)
|
|
ret |= CFG_STATUS_IRQ_TERMINATION_OR_TAPER;
|
|
else
|
|
ret &= ~CFG_STATUS_IRQ_TERMINATION_OR_TAPER;
|
|
|
|
ret = smb347_write(smb, CFG_STATUS_IRQ, ret);
|
|
|
|
if (ret < 0)
|
|
dev_warn(&smb->client->dev, "i2c error %d", ret);
|
|
|
|
err_term:
|
|
ret = smb347_set_writable(smb, false);
|
|
if (ret < 0)
|
|
dev_warn(&smb->client->dev, "i2c error %d", ret);
|
|
mutex_unlock(&smb->lock);
|
|
|
|
}
|
|
|
|
static int smb347_is_charger_present(struct smb347_charger *smb)
|
|
{
|
|
int chg_type;
|
|
if (smb->pdata->detect_chg) {
|
|
chg_type = smb347_read(smb, STAT_D);
|
|
|
|
return (chg_type <= 0) ? 0 : 1;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int smb347_set_otg_reg_ctrl(struct smb347_charger *smb)
|
|
{
|
|
int ret;
|
|
|
|
if (smb->pdata->detect_chg) {
|
|
mutex_lock(&smb->lock);
|
|
smb347_set_writable(smb, true);
|
|
ret = smb347_read(smb, CFG_OTHER);
|
|
if (ret < 0)
|
|
goto err_reg_ctrl;
|
|
ret |= CFG_OTHER_RID_ENABLED_OTG_I2C;
|
|
ret = smb347_write(smb, CFG_OTHER, ret);
|
|
smb347_set_writable(smb, false);
|
|
mutex_unlock(&smb->lock);
|
|
} else {
|
|
return 0;
|
|
}
|
|
|
|
err_reg_ctrl:
|
|
return ret;
|
|
|
|
|
|
}
|
|
|
|
static int smb347_charging_set(struct smb347_charger *smb, bool enable)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (smb->pdata->enable_control != SMB347_CHG_ENABLE_SW) {
|
|
dev_info(&smb->client->dev,
|
|
"charging enable/disable in SW disabled\n");
|
|
return 0;
|
|
}
|
|
|
|
mutex_lock(&smb->lock);
|
|
if (smb->charging_enabled != enable) {
|
|
ret = smb347_read(smb, CMD_A);
|
|
if (ret < 0)
|
|
goto out;
|
|
|
|
smb->charging_enabled = enable;
|
|
|
|
if (enable)
|
|
ret |= CMD_A_CHG_ENABLED;
|
|
else
|
|
ret &= ~CMD_A_CHG_ENABLED;
|
|
|
|
ret |= CMD_A_FORCE_FCC;
|
|
ret = smb347_write(smb, CMD_A, ret);
|
|
}
|
|
out:
|
|
mutex_unlock(&smb->lock);
|
|
return ret;
|
|
}
|
|
|
|
static inline int smb347_charging_enable(struct smb347_charger *smb)
|
|
{
|
|
return smb347_charging_set(smb, true);
|
|
}
|
|
|
|
static inline int smb347_charging_disable(struct smb347_charger *smb)
|
|
{
|
|
return smb347_charging_set(smb, false);
|
|
}
|
|
|
|
static int smb347_enable_suspend(struct smb347_charger *smb)
|
|
{
|
|
int ret;
|
|
|
|
if (!smb)
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&smb->lock);
|
|
ret = smb347_read(smb, CMD_A);
|
|
if (ret < 0)
|
|
goto en_sus_err;
|
|
|
|
ret |= CMD_A_SUSPEND_ENABLED;
|
|
|
|
ret = smb347_write(smb, CMD_A, ret);
|
|
if (ret < 0)
|
|
goto en_sus_err;
|
|
|
|
mutex_unlock(&smb->lock);
|
|
|
|
return 0;
|
|
|
|
en_sus_err:
|
|
dev_info(&smb->client->dev, "i2c error %d", ret);
|
|
mutex_unlock(&smb->lock);
|
|
return ret;
|
|
}
|
|
|
|
static int smb347_disable_suspend(struct smb347_charger *smb)
|
|
{
|
|
int ret;
|
|
|
|
if (!smb)
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&smb->lock);
|
|
ret = smb347_read(smb, CMD_A);
|
|
if (ret < 0)
|
|
goto dis_sus_err;
|
|
|
|
ret &= ~CMD_A_SUSPEND_ENABLED;
|
|
|
|
ret = smb347_write(smb, CMD_A, ret);
|
|
if (ret < 0)
|
|
goto dis_sus_err;
|
|
|
|
mutex_unlock(&smb->lock);
|
|
|
|
return 0;
|
|
|
|
dis_sus_err:
|
|
dev_info(&smb->client->dev, "i2c error %d", ret);
|
|
mutex_unlock(&smb->lock);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* smb347_enable_charger:
|
|
* Exposed to EXTCON for enabling charging
|
|
* Update the status variable, for device active state
|
|
*/
|
|
int smb347_enable_charger()
|
|
{
|
|
struct smb347_charger *smb = smb347_dev;
|
|
int ret;
|
|
|
|
ret = smb347_disable_suspend(smb);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
mutex_lock(&smb->lock);
|
|
smb->is_disabled = false;
|
|
mutex_unlock(&smb->lock);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(smb347_enable_charger);
|
|
|
|
/*
|
|
* smb347_disable_charger:
|
|
* Exposed to EXTCON for disabling charging via SDP
|
|
* Update the status variable, for device active state
|
|
*/
|
|
int smb347_disable_charger()
|
|
{
|
|
struct smb347_charger *smb = smb347_dev;
|
|
int ret;
|
|
|
|
ret = smb347_enable_suspend(smb);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
mutex_lock(&smb->lock);
|
|
smb->is_disabled = true;
|
|
mutex_unlock(&smb->lock);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(smb347_disable_charger);
|
|
|
|
static int smb347_update_online(struct smb347_charger *smb)
|
|
{
|
|
int ret;
|
|
|
|
/*
|
|
* Depending on whether valid power source is connected or not, we
|
|
* disable or enable the charging. We do it manually because it
|
|
* depends on how the platform has configured the valid inputs.
|
|
*/
|
|
if (smb347_is_online(smb)) {
|
|
ret = smb347_charging_enable(smb);
|
|
if (ret < 0)
|
|
dev_err(&smb->client->dev,
|
|
"failed to enable charging\n");
|
|
} else {
|
|
ret = smb347_charging_disable(smb);
|
|
if (ret < 0)
|
|
dev_err(&smb->client->dev,
|
|
"failed to disable charging\n");
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int smb347_otg_set(struct smb347_charger *smb, bool enable)
|
|
{
|
|
const struct smb347_charger_platform_data *pdata = smb->pdata;
|
|
int ret;
|
|
|
|
mutex_lock(&smb->lock);
|
|
|
|
if (pdata->otg_control == SMB347_OTG_CONTROL_SW) {
|
|
ret = smb347_read(smb, CMD_A);
|
|
if (ret < 0)
|
|
goto out;
|
|
|
|
if (enable)
|
|
ret |= CMD_A_OTG_ENABLED;
|
|
else
|
|
ret &= ~CMD_A_OTG_ENABLED;
|
|
|
|
ret = smb347_write(smb, CMD_A, ret);
|
|
if (ret < 0)
|
|
goto out;
|
|
} else {
|
|
/*
|
|
* Switch to pin control or auto-OTG depending on how
|
|
* platform has configured.
|
|
*/
|
|
smb347_set_writable(smb, true);
|
|
|
|
ret = smb347_read(smb, CFG_OTHER);
|
|
if (ret < 0) {
|
|
smb347_set_writable(smb, false);
|
|
goto out;
|
|
}
|
|
|
|
ret &= ~CFG_OTHER_RID_MASK;
|
|
|
|
switch (pdata->otg_control) {
|
|
case SMB347_OTG_CONTROL_SW_PIN:
|
|
if (enable) {
|
|
ret |= CFG_OTHER_RID_DISABLED_OTG_PIN;
|
|
ret |= CFG_OTHER_OTG_PIN_ACTIVE_LOW;
|
|
} else {
|
|
ret &= ~CFG_OTHER_OTG_PIN_ACTIVE_LOW;
|
|
}
|
|
break;
|
|
|
|
case SMB347_OTG_CONTROL_SW_AUTO:
|
|
if (enable)
|
|
ret |= CFG_OTHER_RID_ENABLED_AUTO_OTG;
|
|
break;
|
|
|
|
default:
|
|
dev_err(&smb->client->dev,
|
|
"impossible OTG control configuration: %d\n",
|
|
pdata->otg_control);
|
|
break;
|
|
}
|
|
|
|
ret = smb347_write(smb, CFG_OTHER, ret);
|
|
smb347_set_writable(smb, false);
|
|
if (ret < 0)
|
|
goto out;
|
|
}
|
|
|
|
smb->otg_enabled = enable;
|
|
|
|
out:
|
|
mutex_unlock(&smb->lock);
|
|
return ret;
|
|
}
|
|
|
|
static inline int smb347_otg_enable(struct smb347_charger *smb)
|
|
{
|
|
return smb347_otg_set(smb, true);
|
|
}
|
|
|
|
static inline int smb347_otg_disable(struct smb347_charger *smb)
|
|
{
|
|
return smb347_otg_set(smb, false);
|
|
}
|
|
|
|
static void smb347_otg_drive_vbus(struct smb347_charger *smb, bool enable)
|
|
{
|
|
if (enable == smb->otg_enabled)
|
|
return;
|
|
|
|
if (enable) {
|
|
if (smb->otg_battery_uv) {
|
|
dev_dbg(&smb->client->dev,
|
|
"battery low voltage, won't enable OTG VBUS\n");
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Normal charging must be disabled first before we try to
|
|
* enable OTG VBUS.
|
|
*/
|
|
smb347_charging_disable(smb);
|
|
smb347_otg_enable(smb);
|
|
|
|
if (smb->pdata->use_mains)
|
|
power_supply_changed(&smb->mains);
|
|
if (smb->pdata->use_usb)
|
|
power_supply_changed(&smb->usb);
|
|
|
|
dev_dbg(&smb->client->dev, "OTG VBUS on\n");
|
|
} else {
|
|
smb347_otg_disable(smb);
|
|
/*
|
|
* Only re-enable charging if we have some power supply
|
|
* connected.
|
|
*/
|
|
if (smb347_is_online(smb)) {
|
|
smb347_charging_enable(smb);
|
|
smb->otg_battery_uv = false;
|
|
if (smb->pdata->use_mains)
|
|
power_supply_changed(&smb->mains);
|
|
if (smb->pdata->use_usb)
|
|
power_supply_changed(&smb->usb);
|
|
}
|
|
|
|
dev_dbg(&smb->client->dev, "OTG VBUS off\n");
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_POWER_SUPPLY_CHARGER
|
|
static void smb347_full_worker(struct work_struct *work)
|
|
{
|
|
struct smb347_charger *smb =
|
|
container_of(work, struct smb347_charger, full_worker.work);
|
|
|
|
dev_info(&smb->client->dev, "%s", __func__);
|
|
|
|
power_supply_changed(NULL);
|
|
|
|
schedule_delayed_work(&smb->full_worker,
|
|
SMB34X_FULL_WORK_JIFFIES);
|
|
}
|
|
#endif
|
|
|
|
|
|
static void smb347_otg_detect(struct smb347_charger *smb)
|
|
{
|
|
int ret;
|
|
|
|
ret = smb347_read(smb, STAT_B);
|
|
dev_info(&smb->client->dev, "stat_b = %x", ret);
|
|
if (ret < 0)
|
|
dev_err(&smb->client->dev, "i2c error %d", ret);
|
|
else if (ret & STAT_B_RID_GROUND) {
|
|
smb->drive_vbus = true;
|
|
if (smb->pdata->gpio_mux >= 0)
|
|
gpio_direction_output(smb->pdata->gpio_mux, 0);
|
|
if (smb->a_bus_enable) {
|
|
smb347_otg_enable(smb);
|
|
if (smb->regulator_v3p3sx) {
|
|
regulator_enable(smb->regulator_v3p3sx);
|
|
smb->regulator_enabled = true;
|
|
}
|
|
}
|
|
} else {
|
|
smb->drive_vbus = false;
|
|
smb347_otg_disable(smb);
|
|
if (smb->regulator_v3p3sx && smb->regulator_enabled) {
|
|
regulator_disable(smb->regulator_v3p3sx);
|
|
smb->regulator_enabled = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void smb34x_update_charger_type(struct smb347_charger *smb)
|
|
{
|
|
static struct power_supply_cable_props cable_props;
|
|
static int notify_chrg, notify_usb;
|
|
int ret, vbus_present, power_ok;
|
|
|
|
ret = smb347_read(smb, STAT_D);
|
|
if (ret < 0) {
|
|
dev_err(&smb->client->dev, "%s:i2c read error", __func__);
|
|
return;
|
|
}
|
|
|
|
dev_info(&smb->client->dev, "charger type %x\n", ret);
|
|
|
|
/*
|
|
* sometimes, charger type is present on removal,
|
|
* check the UV status and decide disconnect
|
|
*/
|
|
power_ok = smb347_read(smb, IRQSTAT_E);
|
|
if ((power_ok & (SMB349_IRQSTAT_E_DCIN_UV_STAT |
|
|
SMB349_IRQSTAT_E_DCIN_OV_STAT)) && ret != 0) {
|
|
/*
|
|
* during UV condition,chgr removal is
|
|
* not identified and using worker thread
|
|
* status is updated
|
|
*/
|
|
schedule_delayed_work(&smb->chg_upd_worker,
|
|
SMB34X_CHG_UPD_JIFFIES);
|
|
return;
|
|
}
|
|
|
|
switch (ret) {
|
|
case SMB_CHRG_TYPE_ACA_DOCK:
|
|
case SMB_CHRG_TYPE_ACA_C:
|
|
case SMB_CHRG_TYPE_ACA_B:
|
|
case SMB_CHRG_TYPE_ACA_A:
|
|
cable_props.chrg_evt =
|
|
POWER_SUPPLY_CHARGER_EVENT_CONNECT;
|
|
cable_props.chrg_type =
|
|
POWER_SUPPLY_CHARGER_TYPE_USB_ACA;
|
|
cable_props.ma = SMB_CHRG_CUR_ACA;
|
|
notify_chrg = 1;
|
|
vbus_present = 1;
|
|
break;
|
|
|
|
case SMB_CHRG_TYPE_CDP:
|
|
cable_props.chrg_evt = POWER_SUPPLY_CHARGER_EVENT_CONNECT;
|
|
cable_props.chrg_type = POWER_SUPPLY_CHARGER_TYPE_USB_CDP;
|
|
cable_props.ma = SMB_CHRG_CUR_CDP;
|
|
notify_chrg = 1;
|
|
notify_usb = 1;
|
|
vbus_present = 1;
|
|
break;
|
|
|
|
case SMB_CHRG_TYPE_SDP:
|
|
cable_props.chrg_evt = POWER_SUPPLY_CHARGER_EVENT_CONNECT;
|
|
cable_props.chrg_type = POWER_SUPPLY_CHARGER_TYPE_USB_SDP;
|
|
cable_props.ma = SMB_CHRG_CUR_SDP;
|
|
notify_usb = 1;
|
|
notify_chrg = 1;
|
|
vbus_present = 1;
|
|
break;
|
|
|
|
case SMB_CHRG_TYPE_DCP:
|
|
cable_props.chrg_evt = POWER_SUPPLY_CHARGER_EVENT_CONNECT;
|
|
cable_props.chrg_type = POWER_SUPPLY_CHARGER_TYPE_USB_DCP;
|
|
cable_props.ma = SMB_CHRG_CUR_DCP;
|
|
notify_chrg = 1;
|
|
vbus_present = 1;
|
|
break;
|
|
|
|
default:
|
|
cable_props.chrg_evt = POWER_SUPPLY_CHARGER_EVENT_DISCONNECT;
|
|
cable_props.ma = 0;
|
|
vbus_present = 0;
|
|
break;
|
|
}
|
|
|
|
dev_info(&smb->client->dev, "notify usb %d notify charger %d",
|
|
notify_usb, notify_chrg);
|
|
|
|
if (notify_usb) {
|
|
if (smb->pdata->gpio_mux >= 0)
|
|
gpio_direction_output(smb->pdata->gpio_mux, 1);
|
|
atomic_notifier_call_chain(&smb->otg->notifier,
|
|
USB_EVENT_VBUS, &vbus_present);
|
|
}
|
|
|
|
if (notify_chrg) {
|
|
if (vbus_present) {
|
|
sm347_reload_setting(smb);
|
|
if (!wake_lock_active(&smb->wakelock))
|
|
wake_lock(&smb->wakelock);
|
|
} else {
|
|
if (wake_lock_active(&smb->wakelock))
|
|
wake_unlock(&smb->wakelock);
|
|
}
|
|
atomic_notifier_call_chain(&power_supply_notifier,
|
|
POWER_SUPPLY_CABLE_EVENT, &cable_props);
|
|
}
|
|
|
|
if (cable_props.chrg_evt == POWER_SUPPLY_CHARGER_EVENT_DISCONNECT) {
|
|
notify_chrg = 0;
|
|
notify_usb = 0;
|
|
if (smb->pdata->gpio_mux >= 0)
|
|
gpio_direction_output(smb->pdata->gpio_mux, 0);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
static void smb347_chg_upd_worker(struct work_struct *work)
|
|
{
|
|
struct smb347_charger *smb =
|
|
container_of(work, struct smb347_charger, chg_upd_worker.work);
|
|
|
|
dev_info(&smb->client->dev, "%s", __func__);
|
|
|
|
smb34x_update_charger_type(smb);
|
|
}
|
|
|
|
static void smb347_temp_upd_worker(struct work_struct *work)
|
|
{
|
|
struct smb347_charger *smb =
|
|
container_of(work, struct smb347_charger,
|
|
temp_upd_worker.work);
|
|
int stat_c, irqstat_e, irqstat_a;
|
|
int chg_status, ov_uv_stat, temp_stat;
|
|
|
|
dev_info(&smb->client->dev, "%s", __func__);
|
|
stat_c = smb347_read(smb, STAT_C);
|
|
if (stat_c < 0)
|
|
goto err_upd;
|
|
|
|
irqstat_e = smb347_read(smb, IRQSTAT_E);
|
|
if (irqstat_e < 0)
|
|
goto err_upd;
|
|
|
|
irqstat_a = smb347_read(smb, IRQSTAT_A);
|
|
if (irqstat_a < 0)
|
|
goto err_upd;
|
|
|
|
chg_status = (stat_c & STAT_C_CHG_MASK) >> STAT_C_CHG_SHIFT;
|
|
ov_uv_stat = irqstat_e & (SMB349_IRQSTAT_E_DCIN_UV_STAT |
|
|
SMB349_IRQSTAT_E_DCIN_OV_STAT);
|
|
temp_stat = irqstat_a &
|
|
(IRQSTAT_A_HOT_HARD_STAT|IRQSTAT_A_COLD_HARD_STAT);
|
|
|
|
/* status = not charging, no uv, ov status, hard temp stat */
|
|
if (!chg_status && !ov_uv_stat && temp_stat)
|
|
power_supply_changed(&smb->usb);
|
|
else
|
|
return;
|
|
err_upd:
|
|
schedule_delayed_work(&smb->temp_upd_worker,
|
|
SMB34X_TEMP_WORK_JIFFIES);
|
|
}
|
|
|
|
static void smb347_otg_work(struct work_struct *work)
|
|
{
|
|
struct smb347_charger *smb =
|
|
container_of(work, struct smb347_charger, otg_work);
|
|
struct smb347_otg_event *evt, *tmp;
|
|
unsigned long flags;
|
|
|
|
/* Process the whole event list in one go. */
|
|
spin_lock_irqsave(&smb->otg_queue_lock, flags);
|
|
list_for_each_entry_safe(evt, tmp, &smb->otg_queue, node) {
|
|
list_del(&evt->node);
|
|
spin_unlock_irqrestore(&smb->otg_queue_lock, flags);
|
|
|
|
/* For now we only support set vbus events */
|
|
smb347_otg_drive_vbus(smb, evt->param);
|
|
kfree(evt);
|
|
|
|
spin_lock_irqsave(&smb->otg_queue_lock, flags);
|
|
}
|
|
spin_unlock_irqrestore(&smb->otg_queue_lock, flags);
|
|
}
|
|
|
|
static int smb347_otg_notifier(struct notifier_block *nb, unsigned long event,
|
|
void *param)
|
|
{
|
|
struct smb347_charger *smb =
|
|
container_of(nb, struct smb347_charger, otg_nb);
|
|
struct smb347_otg_event *evt;
|
|
|
|
dev_dbg(&smb->client->dev, "OTG notification: %lu\n", event);
|
|
if (!param || event != USB_EVENT_DRIVE_VBUS || !smb->running)
|
|
return NOTIFY_DONE;
|
|
|
|
evt = kzalloc(sizeof(*evt), GFP_ATOMIC);
|
|
if (!evt) {
|
|
dev_err(&smb->client->dev,
|
|
"failed to allocate memory for OTG event\n");
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
evt->param = *(bool *)param;
|
|
INIT_LIST_HEAD(&evt->node);
|
|
|
|
spin_lock(&smb->otg_queue_lock);
|
|
list_add_tail(&evt->node, &smb->otg_queue);
|
|
spin_unlock(&smb->otg_queue_lock);
|
|
|
|
queue_work(system_nrt_wq, &smb->otg_work);
|
|
return NOTIFY_OK;
|
|
}
|
|
|
|
static int sm347_reload_setting(struct smb347_charger *smb)
|
|
{
|
|
int ret, i, loop_count;
|
|
int reg_offset = 0;
|
|
|
|
mutex_lock(&smb->lock);
|
|
ret = smb347_set_writable(smb, true);
|
|
if (ret < 0)
|
|
goto err_reload;
|
|
|
|
loop_count = MAXSMB347_CONFIG_DATA_SIZE / 2;
|
|
|
|
/*
|
|
* Program the platform specific configuration values to the device
|
|
*/
|
|
for (i = 0; (i < loop_count) &&
|
|
(smb->pdata->char_config_regs[reg_offset] != 0xff); i++) {
|
|
smb347_write(smb, smb->pdata->char_config_regs[reg_offset],
|
|
smb->pdata->char_config_regs[reg_offset+1]);
|
|
reg_offset += 2;
|
|
}
|
|
|
|
smb347_write(smb, CMD_B, CMD_B_MODE_HC);
|
|
ret = smb347_set_writable(smb, false);
|
|
|
|
err_reload:
|
|
mutex_unlock(&smb->lock);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static void smb347_usb_otg_enable(struct usb_phy *phy)
|
|
{
|
|
struct smb347_charger *smb = smb347_dev;
|
|
|
|
if (!smb)
|
|
return;
|
|
|
|
dev_info(&smb->client->dev, "%s:%d", __func__, __LINE__);
|
|
|
|
if (phy->vbus_state == VBUS_DISABLED) {
|
|
dev_info(&smb->client->dev, "OTG Disable");
|
|
smb->a_bus_enable = false;
|
|
if (smb->drive_vbus) {
|
|
smb347_otg_disable(smb);
|
|
if (smb->regulator_v3p3sx &&
|
|
smb->regulator_enabled) {
|
|
regulator_disable(smb->regulator_v3p3sx);
|
|
smb->regulator_enabled = false;
|
|
}
|
|
}
|
|
} else {
|
|
dev_info(&smb->client->dev, "OTG Enable");
|
|
smb->a_bus_enable = true;
|
|
if (smb->drive_vbus) {
|
|
smb347_otg_enable(smb);
|
|
if (smb->regulator_v3p3sx) {
|
|
regulator_enable(smb->regulator_v3p3sx);
|
|
smb->regulator_enabled = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static int smb347_hw_init(struct smb347_charger *smb)
|
|
{
|
|
int ret, loop_count, i;
|
|
int reg_offset = 0;
|
|
|
|
ret = smb347_set_writable(smb, true);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
loop_count = MAXSMB347_CONFIG_DATA_SIZE / 2;
|
|
|
|
/*
|
|
* Program the platform specific configuration values to the device
|
|
*/
|
|
for (i = 0; (i < loop_count) &&
|
|
(smb->pdata->char_config_regs[reg_offset] != 0xff); i++) {
|
|
smb347_write(smb, smb->pdata->char_config_regs[reg_offset],
|
|
smb->pdata->char_config_regs[reg_offset+1]);
|
|
reg_offset += 2;
|
|
}
|
|
|
|
/* disable charging to recover from previous errors */
|
|
ret = smb347_read(smb, CMD_A);
|
|
if (ret >= 0) {
|
|
ret &= ~CMD_A_CHG_ENABLED;
|
|
smb347_write(smb, CMD_A, ret);
|
|
}
|
|
|
|
switch (smb->pdata->otg_control) {
|
|
case SMB347_OTG_CONTROL_DISABLED:
|
|
break;
|
|
|
|
case SMB347_OTG_CONTROL_SW:
|
|
case SMB347_OTG_CONTROL_SW_PIN:
|
|
case SMB347_OTG_CONTROL_SW_AUTO:
|
|
smb->otg = usb_get_phy(USB_PHY_TYPE_USB2);
|
|
if (smb->otg) {
|
|
INIT_WORK(&smb->otg_work, smb347_otg_work);
|
|
INIT_LIST_HEAD(&smb->otg_queue);
|
|
spin_lock_init(&smb->otg_queue_lock);
|
|
|
|
smb->a_bus_enable = true;
|
|
smb->otg->a_bus_drop = smb347_usb_otg_enable;
|
|
smb->otg_nb.notifier_call = smb347_otg_notifier;
|
|
ret = usb_register_notifier(smb->otg, &smb->otg_nb);
|
|
if (ret < 0) {
|
|
usb_put_phy(smb->otg);
|
|
smb->otg = NULL;
|
|
goto fail;
|
|
}
|
|
|
|
dev_info(&smb->client->dev,
|
|
"registered to OTG notifications\n");
|
|
}
|
|
break;
|
|
|
|
case SMB347_OTG_CONTROL_PIN:
|
|
case SMB347_OTG_CONTROL_AUTO:
|
|
/*
|
|
* no need to program again as the OTG settings
|
|
* as they are already configured as part of platform
|
|
* specific register configuration.
|
|
*/
|
|
break;
|
|
}
|
|
|
|
ret = smb347_update_status(smb);
|
|
if (ret < 0)
|
|
goto fail;
|
|
|
|
smb347_set_writable(smb, false);
|
|
return ret;
|
|
|
|
fail:
|
|
if (smb->otg) {
|
|
usb_unregister_notifier(smb->otg, &smb->otg_nb);
|
|
usb_put_phy(smb->otg);
|
|
smb->otg = NULL;
|
|
}
|
|
smb347_set_writable(smb, false);
|
|
return ret;
|
|
}
|
|
|
|
static void smb347_hw_uninit(struct smb347_charger *smb)
|
|
{
|
|
if (smb->otg) {
|
|
struct smb347_otg_event *evt, *tmp;
|
|
|
|
usb_unregister_notifier(smb->otg, &smb->otg_nb);
|
|
smb347_otg_disable(smb);
|
|
usb_put_phy(smb->otg);
|
|
|
|
/* Clear all the queued events. */
|
|
flush_work_sync(&smb->otg_work);
|
|
list_for_each_entry_safe(evt, tmp, &smb->otg_queue, node) {
|
|
list_del(&evt->node);
|
|
kfree(evt);
|
|
}
|
|
}
|
|
}
|
|
|
|
static irqreturn_t smb347_interrupt(int irq, void *data)
|
|
{
|
|
struct smb347_charger *smb = data;
|
|
int stat_c, irqstat_c, irqstat_d, irqstat_e, irqstat_f;
|
|
int irqstat_a, irqstat_b, stat_a;
|
|
irqreturn_t ret = IRQ_NONE;
|
|
|
|
stat_c = smb347_read(smb, STAT_C);
|
|
if (stat_c < 0) {
|
|
dev_warn(&smb->client->dev, "reading STAT_C failed\n");
|
|
return IRQ_NONE;
|
|
}
|
|
|
|
stat_a = smb347_read(smb, STAT_A);
|
|
if (stat_a < 0) {
|
|
dev_warn(&smb->client->dev, "reading STAT_A failed\n");
|
|
return IRQ_NONE;
|
|
}
|
|
|
|
irqstat_a = smb347_read(smb, IRQSTAT_A);
|
|
if (irqstat_a < 0) {
|
|
dev_warn(&smb->client->dev, "reading IRQSTAT_A failed\n");
|
|
return IRQ_NONE;
|
|
}
|
|
|
|
irqstat_b = smb347_read(smb, IRQSTAT_B);
|
|
if (irqstat_b < 0) {
|
|
dev_warn(&smb->client->dev, "reading IRQSTAT_B failed\n");
|
|
return IRQ_NONE;
|
|
}
|
|
|
|
irqstat_c = smb347_read(smb, IRQSTAT_C);
|
|
if (irqstat_c < 0) {
|
|
dev_warn(&smb->client->dev, "reading IRQSTAT_C failed\n");
|
|
return IRQ_NONE;
|
|
}
|
|
|
|
irqstat_d = smb347_read(smb, IRQSTAT_D);
|
|
if (irqstat_d < 0) {
|
|
dev_warn(&smb->client->dev, "reading IRQSTAT_D failed\n");
|
|
return IRQ_NONE;
|
|
}
|
|
|
|
irqstat_e = smb347_read(smb, IRQSTAT_E);
|
|
if (irqstat_e < 0) {
|
|
dev_warn(&smb->client->dev, "reading IRQSTAT_E failed\n");
|
|
return IRQ_NONE;
|
|
}
|
|
|
|
irqstat_f = smb347_read(smb, IRQSTAT_F);
|
|
if (irqstat_f < 0) {
|
|
dev_warn(&smb->client->dev, "reading IRQSTAT_F failed\n");
|
|
return IRQ_NONE;
|
|
}
|
|
|
|
/*
|
|
* If we get charger error we report the error back to user.
|
|
* If the error is recovered charging will resume again.
|
|
*/
|
|
if (stat_c & STAT_C_CHARGER_ERROR) {
|
|
dev_err(&smb->client->dev,
|
|
"charging stopped due to charger error\n");
|
|
if (smb->pdata->use_usb && smb->is_smb349) {
|
|
smb->usb_online = 0;
|
|
power_supply_changed(&smb->usb);
|
|
}
|
|
if (smb->pdata->show_battery)
|
|
power_supply_changed(&smb->battery);
|
|
|
|
ret = IRQ_HANDLED;
|
|
}
|
|
|
|
if (irqstat_a & (IRQSTAT_A_HOT_HARD_IRQ|IRQSTAT_A_COLD_HARD_IRQ)) {
|
|
dev_info(&smb->client->dev, "extreme temperature interrupt");
|
|
schedule_delayed_work(&smb->temp_upd_worker, 0);
|
|
}
|
|
|
|
if (irqstat_b & IRQSTAT_B_BATOVP_IRQ) {
|
|
dev_info(&smb->client->dev, "BATOVP interrupt");
|
|
/* Reset charging in case of battery OV */
|
|
smb347_charging_set(smb, false);
|
|
if (smb->pdata->use_usb)
|
|
power_supply_changed(&smb->usb);
|
|
}
|
|
/*
|
|
* If we reached the termination current the battery is charged and
|
|
* we can update the status now. Charging is automatically
|
|
* disabled by the hardware.
|
|
*/
|
|
#ifndef CONFIG_POWER_SUPPLY_CHARGER
|
|
if (irqstat_c & (IRQSTAT_C_TERMINATION_IRQ | IRQSTAT_C_TAPER_IRQ)) {
|
|
if ((irqstat_c & IRQSTAT_C_TERMINATION_STAT) &&
|
|
smb->pdata->show_battery)
|
|
power_supply_changed(&smb->battery);
|
|
dev_info(&smb->client->dev,
|
|
"[Charge Terminated] Going to HW Maintenance mode\n");
|
|
ret = IRQ_HANDLED;
|
|
}
|
|
#else
|
|
if (irqstat_c & (IRQSTAT_C_TAPER_IRQ | IRQSTAT_C_TERMINATION_IRQ)) {
|
|
if (smb->charging_enabled) {
|
|
/*
|
|
* reduce the termination current value, to avoid
|
|
* repeated interrupts.
|
|
*/
|
|
smb347_set_iterm(smb, SMB_ITERM_100);
|
|
smb347_enable_termination(smb, false);
|
|
/*
|
|
* since termination has happened, charging will not be
|
|
* re-enabled until, disable and enable charging is
|
|
* done.
|
|
*/
|
|
smb347_charging_set(smb, false);
|
|
smb347_charging_set(smb, true);
|
|
schedule_delayed_work(&smb->full_worker, 0);
|
|
}
|
|
ret = IRQ_HANDLED;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* If we got a complete charger timeout int that means the charge
|
|
* full is not detected with in charge timeout value.
|
|
*/
|
|
if (irqstat_d & IRQSTAT_D_CHARGE_TIMEOUT_IRQ) {
|
|
dev_info(&smb->client->dev,
|
|
"[Charge Timeout]:Total Charge Timeout INT recieved\n");
|
|
if (irqstat_d & IRQSTAT_D_CHARGE_TIMEOUT_STAT)
|
|
dev_info(&smb->client->dev,
|
|
"[Charge Timeout]:charging stopped\n");
|
|
|
|
/* Restart charging once timeout has happened */
|
|
smb347_charging_set(smb, false);
|
|
smb347_charging_set(smb, true);
|
|
if (smb->pdata->show_battery)
|
|
power_supply_changed(&smb->battery);
|
|
ret = IRQ_HANDLED;
|
|
}
|
|
|
|
if (smb->pdata->detect_chg) {
|
|
if (irqstat_d & IRQSTAT_D_APSD_STAT) {
|
|
smb347_disable_suspend(smb);
|
|
ret = IRQ_HANDLED;
|
|
}
|
|
}
|
|
/*
|
|
* If we got an under voltage interrupt it means that AC/USB input
|
|
* was connected or disconnected.
|
|
*/
|
|
if (irqstat_e & (IRQSTAT_E_USBIN_UV_IRQ | IRQSTAT_E_DCIN_UV_IRQ |
|
|
SMB349_IRQSTAT_E_DCIN_OV_IRQ |
|
|
SMB349_IRQSTAT_E_DCIN_OV_STAT)) {
|
|
if (smb347_update_status(smb) > 0) {
|
|
smb347_update_online(smb);
|
|
/*
|
|
* In SMB349 chip the charging is not starting
|
|
* immediately after charger connect. We have to
|
|
* wait untill POWER OK signal/pin becomes good.
|
|
* So only send the uevent notification for smb347.
|
|
* For smb349 uevent notification will be sent upon
|
|
* Power Ok interrupt.
|
|
*/
|
|
#ifndef CONFIG_POWER_SUPPLY_CHARGER
|
|
if (smb->pdata->use_mains && !smb->is_smb349)
|
|
power_supply_changed(&smb->mains);
|
|
if (smb->pdata->use_usb && !smb->is_smb349)
|
|
power_supply_changed(&smb->usb);
|
|
#endif
|
|
}
|
|
|
|
ret = IRQ_HANDLED;
|
|
}
|
|
|
|
/*
|
|
* If the battery voltage falls below OTG UVLO the VBUS is
|
|
* automatically turned off but we must not enable it again unless
|
|
* UVLO is cleared. It will be cleared when external power supply
|
|
* is connected and the battery voltage goes over the UVLO
|
|
* threshold.
|
|
*/
|
|
if (irqstat_f & IRQSTAT_F_OTG_UV_IRQ) {
|
|
smb->otg_battery_uv = !!(irqstat_f & IRQSTAT_F_OTG_UV_STAT);
|
|
dev_info(&smb->client->dev, "Vbatt is below OTG UVLO\n");
|
|
smb347_otg_disable(smb);
|
|
ret = IRQ_HANDLED;
|
|
}
|
|
|
|
if (irqstat_f & IRQSTAT_F_OTG_DET_IRQ) {
|
|
smb347_otg_detect(smb);
|
|
ret = IRQ_HANDLED;
|
|
}
|
|
|
|
if (irqstat_f & IRQSTAT_F_PWR_OK_IRQ) {
|
|
dev_info(&smb->client->dev, "PowerOK INTR recieved\n");
|
|
|
|
smb347_update_status(smb);
|
|
|
|
if (smb->pdata->detect_chg)
|
|
smb34x_update_charger_type(smb);
|
|
|
|
/*
|
|
* In case of charger OV/UV update_charger_type
|
|
* will not send power_supply_changed. Also during
|
|
* charger unplug UV interrupt will be triggered
|
|
* Hence check if the charger is present and health
|
|
* is over voltage or under voltage send power
|
|
* supply changed notification.
|
|
*/
|
|
if (smb347_is_charger_present(smb)) {
|
|
int health = smb34x_get_health(smb);
|
|
if (health == POWER_SUPPLY_HEALTH_OVERVOLTAGE ||
|
|
health == POWER_SUPPLY_HEALTH_DEAD) {
|
|
if (smb->pdata->use_usb)
|
|
power_supply_changed(&smb->usb);
|
|
if (smb->pdata->use_mains)
|
|
power_supply_changed(&smb->mains);
|
|
}
|
|
}
|
|
|
|
ret = IRQ_HANDLED;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int smb347_irq_set(struct smb347_charger *smb, bool enable)
|
|
{
|
|
int ret;
|
|
|
|
ret = smb347_set_writable(smb, true);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/*
|
|
* Enable/disable interrupts for:
|
|
* - under voltage
|
|
* - termination current reached
|
|
* - charger error
|
|
*/
|
|
if (enable) {
|
|
int val = CFG_FAULT_IRQ_DCIN_UV;
|
|
|
|
if (smb->otg)
|
|
val |= CFG_FAULT_IRQ_OTG_UV;
|
|
|
|
ret = smb347_write(smb, CFG_FAULT_IRQ, val);
|
|
if (ret < 0)
|
|
goto fail;
|
|
|
|
val = CFG_STATUS_IRQ_CHARGE_TIMEOUT |
|
|
CFG_STATUS_IRQ_TERMINATION_OR_TAPER |
|
|
CFG_STATUS_OTG_DET |
|
|
CFG_STATUS_IRQ_INOK;
|
|
ret = smb347_write(smb, CFG_STATUS_IRQ, val);
|
|
if (ret < 0)
|
|
goto fail;
|
|
|
|
ret = smb347_read(smb, CFG_PIN);
|
|
if (ret < 0)
|
|
goto fail;
|
|
|
|
ret |= (CFG_PIN_EN_CHARGER_ERROR | CFG_PIN_EN_APSD_IRQ);
|
|
|
|
ret = smb347_write(smb, CFG_PIN, ret);
|
|
} else {
|
|
ret = smb347_write(smb, CFG_FAULT_IRQ, 0);
|
|
if (ret < 0)
|
|
goto fail;
|
|
|
|
ret = smb347_write(smb, CFG_STATUS_IRQ, 0);
|
|
if (ret < 0)
|
|
goto fail;
|
|
|
|
ret = smb347_read(smb, CFG_PIN);
|
|
if (ret < 0)
|
|
goto fail;
|
|
|
|
ret &= ~CFG_PIN_EN_CHARGER_ERROR;
|
|
|
|
ret = smb347_write(smb, CFG_PIN, ret);
|
|
}
|
|
|
|
fail:
|
|
smb347_set_writable(smb, false);
|
|
return ret;
|
|
}
|
|
|
|
static inline int smb347_irq_enable(struct smb347_charger *smb)
|
|
{
|
|
return smb347_irq_set(smb, true);
|
|
}
|
|
|
|
static inline int smb347_irq_disable(struct smb347_charger *smb)
|
|
{
|
|
return smb347_irq_set(smb, false);
|
|
}
|
|
|
|
static int smb347_irq_init(struct smb347_charger *smb)
|
|
{
|
|
const struct smb347_charger_platform_data *pdata = smb->pdata;
|
|
int ret, irq = gpio_to_irq(pdata->irq_gpio);
|
|
|
|
ret = gpio_request_one(pdata->irq_gpio, GPIOF_IN, smb->client->name);
|
|
if (ret < 0)
|
|
goto fail;
|
|
|
|
ret = request_threaded_irq(irq, NULL, smb347_interrupt,
|
|
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
|
|
smb->client->name,
|
|
smb);
|
|
if (ret < 0) {
|
|
dev_info(&smb->client->dev, "request irq failed");
|
|
goto fail_gpio;
|
|
}
|
|
|
|
mutex_lock(&smb->lock);
|
|
ret = smb347_set_writable(smb, true);
|
|
if (ret < 0)
|
|
goto fail_irq;
|
|
|
|
/*
|
|
* Configure the STAT output to be suitable for interrupts: disable
|
|
* all other output (except interrupts) and make it active low.
|
|
*/
|
|
ret = smb347_read(smb, CFG_STAT);
|
|
if (ret < 0)
|
|
goto fail_readonly;
|
|
|
|
ret &= ~CFG_STAT_ACTIVE_HIGH;
|
|
ret |= CFG_STAT_DISABLED;
|
|
|
|
ret = smb347_write(smb, CFG_STAT, ret);
|
|
if (ret < 0)
|
|
goto fail_readonly;
|
|
|
|
ret = smb347_irq_enable(smb);
|
|
if (ret < 0)
|
|
goto fail_readonly;
|
|
|
|
smb347_set_writable(smb, false);
|
|
smb->client->irq = irq;
|
|
enable_irq_wake(smb->client->irq);
|
|
mutex_unlock(&smb->lock);
|
|
return 0;
|
|
|
|
fail_readonly:
|
|
smb347_set_writable(smb, false);
|
|
mutex_unlock(&smb->lock);
|
|
fail_irq:
|
|
free_irq(irq, smb);
|
|
fail_gpio:
|
|
gpio_free(pdata->irq_gpio);
|
|
fail:
|
|
smb->client->irq = 0;
|
|
return ret;
|
|
}
|
|
|
|
static int smb347_mains_get_property(struct power_supply *psy,
|
|
enum power_supply_property prop,
|
|
union power_supply_propval *val)
|
|
{
|
|
struct smb347_charger *smb =
|
|
container_of(psy, struct smb347_charger, mains);
|
|
|
|
if (prop == POWER_SUPPLY_PROP_ONLINE) {
|
|
val->intval = smb->mains_online;
|
|
return 0;
|
|
}
|
|
return -EINVAL;
|
|
}
|
|
|
|
static enum power_supply_property smb347_mains_properties[] = {
|
|
POWER_SUPPLY_PROP_ONLINE,
|
|
};
|
|
|
|
#ifdef CONFIG_POWER_SUPPLY_CHARGER
|
|
static enum power_supply_type power_supply_cable_type(
|
|
enum power_supply_charger_cable_type cable)
|
|
{
|
|
|
|
switch (cable) {
|
|
|
|
case POWER_SUPPLY_CHARGER_TYPE_USB_DCP:
|
|
return POWER_SUPPLY_TYPE_USB_DCP;
|
|
case POWER_SUPPLY_CHARGER_TYPE_USB_CDP:
|
|
return POWER_SUPPLY_TYPE_USB_CDP;
|
|
case POWER_SUPPLY_CHARGER_TYPE_USB_ACA:
|
|
return POWER_SUPPLY_TYPE_USB_ACA;
|
|
case POWER_SUPPLY_CHARGER_TYPE_AC:
|
|
return POWER_SUPPLY_TYPE_MAINS;
|
|
case POWER_SUPPLY_CHARGER_TYPE_NONE:
|
|
case POWER_SUPPLY_CHARGER_TYPE_USB_SDP:
|
|
default:
|
|
return POWER_SUPPLY_TYPE_USB;
|
|
}
|
|
|
|
return POWER_SUPPLY_TYPE_USB;
|
|
}
|
|
#endif
|
|
|
|
static int smb347_set_cc(struct smb347_charger *smb, int cc)
|
|
{
|
|
int ret;
|
|
int smb_cc;
|
|
|
|
mutex_lock(&smb->lock);
|
|
ret = smb347_set_writable(smb, true);
|
|
if (ret < 0)
|
|
goto err_cc;
|
|
|
|
if (cc >= CC_1800)
|
|
smb_cc = SMB_FCC_1800;
|
|
else if (cc >= CC_1600)
|
|
smb_cc = SMB_FCC_1600;
|
|
else if (cc >= CC_1400)
|
|
smb_cc = SMB_FCC_1400;
|
|
else if (cc >= CC_1200)
|
|
smb_cc = SMB_FCC_1200;
|
|
else
|
|
smb_cc = SMB_FCC_1000;
|
|
|
|
ret = smb347_read(smb, CFG_CHARGE_CURRENT);
|
|
if (ret < 0)
|
|
goto err_cc;
|
|
|
|
ret &= ~SMB_FCC_MASK;
|
|
smb_cc |= ret;
|
|
ret = smb347_write(smb, CFG_CHARGE_CURRENT, smb_cc);
|
|
if (ret < 0)
|
|
goto err_cc;
|
|
|
|
ret = smb347_set_writable(smb, false);
|
|
mutex_unlock(&smb->lock);
|
|
return 0;
|
|
|
|
err_cc:
|
|
ret = smb347_set_writable(smb, false);
|
|
mutex_unlock(&smb->lock);
|
|
dev_info(&smb->client->dev, "%s:error writing to i2c", __func__);
|
|
return ret;
|
|
}
|
|
|
|
static int smb347_set_inlmt(struct smb347_charger *smb, int inlmt)
|
|
{
|
|
int ret;
|
|
int smb_inlmt;
|
|
|
|
mutex_lock(&smb->lock);
|
|
ret = smb347_set_writable(smb, true);
|
|
if (ret < 0)
|
|
goto err_inlmt;
|
|
|
|
for (smb_inlmt = 0; smb_inlmt < ARRAY_SIZE(smb349_inlim); smb_inlmt++)
|
|
if (inlmt <= smb349_inlim[smb_inlmt])
|
|
break;
|
|
|
|
ret = smb347_read(smb, CFG_CHARGE_CURRENT);
|
|
if (ret < 0)
|
|
goto err_inlmt;
|
|
|
|
ret &= ~SMB_INLMT_MASK;
|
|
smb_inlmt |= ret;
|
|
|
|
ret = smb347_write(smb, CFG_CHARGE_CURRENT, smb_inlmt);
|
|
if (ret < 0)
|
|
goto err_inlmt;
|
|
|
|
ret = smb347_set_writable(smb, false);
|
|
mutex_unlock(&smb->lock);
|
|
return 0;
|
|
|
|
err_inlmt:
|
|
ret = smb347_set_writable(smb, false);
|
|
mutex_unlock(&smb->lock);
|
|
dev_info(&smb->client->dev, "%s:error writing to i2c", __func__);
|
|
return ret;
|
|
}
|
|
|
|
static int smb347_set_cv(struct smb347_charger *smb, int cv)
|
|
{
|
|
int ret;
|
|
int smb_cv;
|
|
|
|
mutex_lock(&smb->lock);
|
|
ret = smb347_set_writable(smb, true);
|
|
if (ret < 0)
|
|
goto err_cv;
|
|
|
|
if (cv >= CV_4350)
|
|
smb_cv = SMB_FVLTG_4350;
|
|
else if (cv >= CV_4200)
|
|
smb_cv = SMB_FVLTG_4200;
|
|
else if (cv >= CV_4100)
|
|
smb_cv = SMB_FVLTG_4100;
|
|
else
|
|
smb_cv = SMB_FVLTG_4000;
|
|
|
|
ret = smb347_read(smb, CFG_FLOAT_VOLTAGE);
|
|
if (ret < 0)
|
|
goto err_cv;
|
|
|
|
ret &= ~SMB_FVLTG_MASK;
|
|
smb_cv |= ret;
|
|
|
|
ret = smb347_write(smb, CFG_FLOAT_VOLTAGE, smb_cv);
|
|
if (ret < 0)
|
|
goto err_cv;
|
|
|
|
ret = smb347_set_writable(smb, false);
|
|
mutex_unlock(&smb->lock);
|
|
return 0;
|
|
|
|
err_cv:
|
|
ret = smb347_set_writable(smb, false);
|
|
mutex_unlock(&smb->lock);
|
|
dev_info(&smb->client->dev, "%s:error writing to i2c", __func__);
|
|
return ret;
|
|
}
|
|
|
|
static int smb347_set_iterm(struct smb347_charger *smb, int iterm)
|
|
{
|
|
int ret;
|
|
int smb_iterm;
|
|
|
|
mutex_lock(&smb->lock);
|
|
ret = smb347_set_writable(smb, true);
|
|
if (ret < 0)
|
|
goto err_iterm;
|
|
|
|
if (iterm >= ITERM_400)
|
|
smb_iterm = SMB_ITERM_400;
|
|
else if (iterm >= ITERM_300)
|
|
smb_iterm = SMB_ITERM_300;
|
|
else if (iterm >= ITERM_200)
|
|
smb_iterm = SMB_ITERM_200;
|
|
else
|
|
smb_iterm = SMB_ITERM_100;
|
|
|
|
ret = smb347_read(smb, CFG_CURRENT_LIMIT);
|
|
if (ret < 0)
|
|
goto err_iterm;
|
|
|
|
ret &= ~SMB_ITERM_MASK;
|
|
smb_iterm |= ret;
|
|
|
|
ret = smb347_write(smb, CFG_CURRENT_LIMIT, smb_iterm);
|
|
if (ret < 0)
|
|
goto err_iterm;
|
|
|
|
ret = smb347_set_writable(smb, false);
|
|
mutex_unlock(&smb->lock);
|
|
return 0;
|
|
|
|
err_iterm:
|
|
ret = smb347_set_writable(smb, false);
|
|
dev_info(&smb->client->dev, "%s:error writing to i2c", __func__);
|
|
mutex_unlock(&smb->lock);
|
|
return ret;
|
|
}
|
|
|
|
#ifndef CONFIG_POWER_SUPPLY_CHARGER
|
|
static int smb347_throttle_charging(struct smb347_charger *smb, int lim)
|
|
{
|
|
struct power_supply_throttle *throttle_states =
|
|
smb->pdata->throttle_states;
|
|
int ret;
|
|
|
|
if (lim < 0 || lim > (smb->pdata->num_throttle_states - 1))
|
|
return -ERANGE;
|
|
|
|
if (throttle_states[lim].throttle_action ==
|
|
PSY_THROTTLE_CC_LIMIT) {
|
|
ret = smb347_enable_charger();
|
|
if (ret < 0)
|
|
goto throttle_fail;
|
|
ret = smb347_set_cc(smb, throttle_states[lim].throttle_val);
|
|
if (ret < 0)
|
|
goto throttle_fail;
|
|
ret = smb347_charging_set(smb, true);
|
|
} else if (throttle_states[lim].throttle_action ==
|
|
PSY_THROTTLE_INPUT_LIMIT) {
|
|
ret = smb347_enable_charger();
|
|
if (ret < 0)
|
|
goto throttle_fail;
|
|
ret = smb347_set_inlmt(smb, throttle_states[lim].throttle_val);
|
|
if (ret < 0)
|
|
goto throttle_fail;
|
|
ret = smb347_charging_set(smb, true);
|
|
} else if (throttle_states[lim].throttle_action ==
|
|
PSY_THROTTLE_DISABLE_CHARGING) {
|
|
ret = smb347_enable_charger();
|
|
if (ret < 0)
|
|
goto throttle_fail;
|
|
ret = smb347_charging_set(smb, false);
|
|
} else if (throttle_states[lim].throttle_action ==
|
|
PSY_THROTTLE_DISABLE_CHARGER) {
|
|
ret = smb347_disable_charger();
|
|
} else {
|
|
return -EINVAL;
|
|
}
|
|
|
|
throttle_fail:
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
static int smb347_usb_set_property(struct power_supply *psy,
|
|
enum power_supply_property psp,
|
|
const union power_supply_propval *val)
|
|
{
|
|
struct smb347_charger *smb =
|
|
container_of(psy, struct smb347_charger, usb);
|
|
int ret = 0;
|
|
|
|
switch (psp) {
|
|
case POWER_SUPPLY_PROP_PRESENT:
|
|
smb->present = val->intval;
|
|
break;
|
|
case POWER_SUPPLY_PROP_ONLINE:
|
|
smb->online = val->intval;
|
|
break;
|
|
case POWER_SUPPLY_PROP_MAX_CHARGE_CURRENT:
|
|
smb->max_cc = val->intval;
|
|
break;
|
|
case POWER_SUPPLY_PROP_MAX_CHARGE_VOLTAGE:
|
|
smb->max_cv = val->intval;
|
|
break;
|
|
case POWER_SUPPLY_PROP_ENABLE_CHARGING:
|
|
ret = smb347_charging_set(smb, (bool)val->intval);
|
|
if (ret < 0)
|
|
dev_err(&smb->client->dev,
|
|
"Error %d in %s charging", ret,
|
|
(val->intval ? "enable" : "disable"));
|
|
#ifdef CONFIG_POWER_SUPPLY_CHARGER
|
|
if (!val->intval)
|
|
cancel_delayed_work(&smb->full_worker);
|
|
else {
|
|
smb347_set_iterm(smb, smb->iterm);
|
|
smb347_enable_termination(smb, true);
|
|
}
|
|
#endif
|
|
break;
|
|
case POWER_SUPPLY_PROP_ENABLE_CHARGER:
|
|
if (val->intval)
|
|
ret = smb347_enable_charger();
|
|
else {
|
|
/*
|
|
* OTG cannot function if charger is put to
|
|
* suspend state, send fake ntf in such case
|
|
*/
|
|
if (!smb->pdata->detect_chg ||
|
|
smb347_is_charger_present(smb))
|
|
ret = smb347_disable_charger();
|
|
else
|
|
smb->is_disabled = true;
|
|
}
|
|
if (ret < 0)
|
|
dev_err(&smb->client->dev,
|
|
"Error %d in %s charger", ret,
|
|
(val->intval ? "enable" : "disable"));
|
|
/*
|
|
* Set OTG in Register control, as default HW
|
|
* settings did not enable OTG.
|
|
*/
|
|
smb347_set_otg_reg_ctrl(smb);
|
|
break;
|
|
case POWER_SUPPLY_PROP_CHARGE_CURRENT:
|
|
ret = smb347_set_cc(smb, val->intval);
|
|
if (!ret) {
|
|
mutex_lock(&smb->lock);
|
|
smb->cc = val->intval;
|
|
mutex_unlock(&smb->lock);
|
|
}
|
|
break;
|
|
case POWER_SUPPLY_PROP_CHARGE_VOLTAGE:
|
|
ret = smb347_set_cv(smb, val->intval);
|
|
if (!ret) {
|
|
mutex_lock(&smb->lock);
|
|
smb->cv = val->intval;
|
|
mutex_unlock(&smb->lock);
|
|
}
|
|
break;
|
|
case POWER_SUPPLY_PROP_CHARGE_TERM_CUR:
|
|
ret = smb347_set_iterm(smb, val->intval);
|
|
if (!ret) {
|
|
mutex_lock(&smb->lock);
|
|
smb->iterm = val->intval;
|
|
mutex_unlock(&smb->lock);
|
|
}
|
|
break;
|
|
#ifdef CONFIG_POWER_SUPPLY_CHARGER
|
|
case POWER_SUPPLY_PROP_CABLE_TYPE:
|
|
mutex_lock(&smb->lock);
|
|
smb->cable_type = val->intval;
|
|
smb->usb.type = power_supply_cable_type(smb->cable_type);
|
|
mutex_unlock(&smb->lock);
|
|
break;
|
|
#endif
|
|
case POWER_SUPPLY_PROP_INLMT:
|
|
ret = smb347_set_inlmt(smb, val->intval);
|
|
if (!ret) {
|
|
mutex_lock(&smb->lock);
|
|
smb->inlmt = val->intval;
|
|
mutex_unlock(&smb->lock);
|
|
}
|
|
break;
|
|
case POWER_SUPPLY_PROP_MAX_TEMP:
|
|
mutex_lock(&smb->lock);
|
|
smb->max_temp = val->intval;
|
|
mutex_unlock(&smb->lock);
|
|
break;
|
|
case POWER_SUPPLY_PROP_MIN_TEMP:
|
|
mutex_lock(&smb->lock);
|
|
smb->min_temp = val->intval;
|
|
mutex_unlock(&smb->lock);
|
|
break;
|
|
case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
|
|
#ifdef CONFIG_POWER_SUPPLY_CHARGER
|
|
if (val->intval < smb->pdata->num_throttle_states)
|
|
smb->cntl_state = val->intval;
|
|
else
|
|
ret = -ERANGE;
|
|
#else
|
|
if (val->intval < smb->pdata->num_throttle_states) {
|
|
ret = smb347_throttle_charging(smb, val->intval);
|
|
if (ret < 0)
|
|
break;
|
|
smb->cntl_state = val->intval;
|
|
} else {
|
|
ret = -ERANGE;
|
|
}
|
|
#endif
|
|
break;
|
|
default:
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int smb347_usb_get_property(struct power_supply *psy,
|
|
enum power_supply_property prop,
|
|
union power_supply_propval *val)
|
|
{
|
|
struct smb347_charger *smb =
|
|
container_of(psy, struct smb347_charger, usb);
|
|
int ret = 0;
|
|
|
|
mutex_lock(&smb->lock);
|
|
switch (prop) {
|
|
case POWER_SUPPLY_PROP_ONLINE:
|
|
val->intval = smb->online;
|
|
break;
|
|
case POWER_SUPPLY_PROP_PRESENT:
|
|
val->intval = smb->present;
|
|
break;
|
|
case POWER_SUPPLY_PROP_HEALTH:
|
|
val->intval = smb34x_get_health(smb);
|
|
break;
|
|
case POWER_SUPPLY_PROP_MAX_CHARGE_CURRENT:
|
|
val->intval = smb->max_cc;
|
|
break;
|
|
case POWER_SUPPLY_PROP_MAX_CHARGE_VOLTAGE:
|
|
val->intval = smb->max_cv;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CHARGE_CURRENT:
|
|
val->intval = smb->cc;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CHARGE_VOLTAGE:
|
|
val->intval = smb->cv;
|
|
break;
|
|
case POWER_SUPPLY_PROP_INLMT:
|
|
val->intval = smb->inlmt;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CHARGE_TERM_CUR:
|
|
val->intval = smb->iterm;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CABLE_TYPE:
|
|
val->intval = smb->cable_type;
|
|
break;
|
|
case POWER_SUPPLY_PROP_ENABLE_CHARGING:
|
|
val->intval = smb->charging_enabled;
|
|
break;
|
|
case POWER_SUPPLY_PROP_ENABLE_CHARGER:
|
|
val->intval = !smb->is_disabled;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
|
|
val->intval = smb->cntl_state;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX:
|
|
val->intval = smb->pdata->num_throttle_states;
|
|
break;
|
|
case POWER_SUPPLY_PROP_MAX_TEMP:
|
|
val->intval = smb->max_temp;
|
|
break;
|
|
case POWER_SUPPLY_PROP_MIN_TEMP:
|
|
val->intval = smb->min_temp;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
mutex_unlock(&smb->lock);
|
|
return ret;
|
|
}
|
|
|
|
int smb347_get_charging_status(void)
|
|
{
|
|
int ret, status;
|
|
|
|
if (!smb347_dev)
|
|
return -EINVAL;
|
|
|
|
if (!smb347_is_online(smb347_dev))
|
|
return POWER_SUPPLY_STATUS_DISCHARGING;
|
|
|
|
ret = smb347_read(smb347_dev, STAT_C);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if ((ret & STAT_C_CHARGER_ERROR) ||
|
|
(ret & STAT_C_HOLDOFF_STAT)) {
|
|
/* set to NOT CHARGING upon charger error
|
|
* or charging has stopped.
|
|
*/
|
|
status = POWER_SUPPLY_STATUS_NOT_CHARGING;
|
|
} else {
|
|
if ((ret & STAT_C_CHG_MASK) >> STAT_C_CHG_SHIFT) {
|
|
/* set to charging if battery is in pre-charge,
|
|
* fast charge or taper charging mode.
|
|
*/
|
|
status = POWER_SUPPLY_STATUS_CHARGING;
|
|
} else if (ret & STAT_C_CHG_TERM) {
|
|
/* set the status to FULL if battery is not in pre
|
|
* charge, fast charge or taper charging mode AND
|
|
* charging is terminated at least once.
|
|
*/
|
|
status = POWER_SUPPLY_STATUS_FULL;
|
|
} else {
|
|
/* in this case no charger error or termination
|
|
* occured but charging is not in progress!!!
|
|
*/
|
|
status = POWER_SUPPLY_STATUS_NOT_CHARGING;
|
|
}
|
|
}
|
|
|
|
return status;
|
|
}
|
|
EXPORT_SYMBOL_GPL(smb347_get_charging_status);
|
|
|
|
static enum power_supply_property smb347_usb_properties[] = {
|
|
POWER_SUPPLY_PROP_ONLINE,
|
|
POWER_SUPPLY_PROP_PRESENT,
|
|
POWER_SUPPLY_PROP_HEALTH,
|
|
POWER_SUPPLY_PROP_TYPE,
|
|
POWER_SUPPLY_PROP_MAX_CHARGE_CURRENT,
|
|
POWER_SUPPLY_PROP_MAX_CHARGE_VOLTAGE,
|
|
POWER_SUPPLY_PROP_CHARGE_CURRENT,
|
|
POWER_SUPPLY_PROP_CHARGE_VOLTAGE,
|
|
POWER_SUPPLY_PROP_INLMT,
|
|
POWER_SUPPLY_PROP_ENABLE_CHARGING,
|
|
POWER_SUPPLY_PROP_ENABLE_CHARGER,
|
|
POWER_SUPPLY_PROP_CHARGE_TERM_CUR,
|
|
#ifdef CONFIG_POWER_SUPPLY_CHARGER
|
|
POWER_SUPPLY_PROP_CABLE_TYPE,
|
|
#endif
|
|
POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT,
|
|
POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX,
|
|
POWER_SUPPLY_PROP_MAX_TEMP,
|
|
POWER_SUPPLY_PROP_MIN_TEMP
|
|
};
|
|
|
|
static int smb347_battery_get_property(struct power_supply *psy,
|
|
enum power_supply_property prop,
|
|
union power_supply_propval *val)
|
|
{
|
|
struct smb347_charger *smb =
|
|
container_of(psy, struct smb347_charger, battery);
|
|
const struct smb347_charger_platform_data *pdata = smb->pdata;
|
|
int ret;
|
|
|
|
ret = smb347_update_status(smb);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
switch (prop) {
|
|
case POWER_SUPPLY_PROP_STATUS:
|
|
ret = smb347_get_charging_status();
|
|
if (ret < 0)
|
|
return ret;
|
|
val->intval = ret;
|
|
break;
|
|
|
|
case POWER_SUPPLY_PROP_CHARGE_TYPE:
|
|
if (!smb347_is_online(smb))
|
|
return -ENODATA;
|
|
|
|
/*
|
|
* We handle trickle and pre-charging the same, and taper
|
|
* and none the same.
|
|
*/
|
|
switch (smb347_charging_status(smb)) {
|
|
case 1:
|
|
val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
|
|
break;
|
|
case 2:
|
|
val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST;
|
|
break;
|
|
default:
|
|
val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case POWER_SUPPLY_PROP_TECHNOLOGY:
|
|
val->intval = pdata->battery_info.technology;
|
|
break;
|
|
|
|
case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
|
|
val->intval = pdata->battery_info.voltage_min_design;
|
|
break;
|
|
|
|
case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
|
|
val->intval = pdata->battery_info.voltage_max_design;
|
|
break;
|
|
|
|
case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
|
|
val->intval = pdata->battery_info.charge_full_design;
|
|
break;
|
|
|
|
case POWER_SUPPLY_PROP_MODEL_NAME:
|
|
val->strval = pdata->battery_info.name;
|
|
break;
|
|
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static enum power_supply_property smb347_battery_properties[] = {
|
|
POWER_SUPPLY_PROP_STATUS,
|
|
POWER_SUPPLY_PROP_CHARGE_TYPE,
|
|
POWER_SUPPLY_PROP_TECHNOLOGY,
|
|
POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
|
|
POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
|
|
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
|
|
POWER_SUPPLY_PROP_MODEL_NAME,
|
|
};
|
|
|
|
static int smb347_debugfs_show(struct seq_file *s, void *data)
|
|
{
|
|
struct smb347_charger *smb = s->private;
|
|
int ret;
|
|
u8 reg;
|
|
|
|
seq_printf(s, "Control registers:\n");
|
|
seq_printf(s, "==================\n");
|
|
for (reg = CFG_CHARGE_CURRENT; reg <= CFG_ADDRESS; reg++) {
|
|
ret = smb347_read(smb, reg);
|
|
if (ret < 0)
|
|
return ret;
|
|
seq_printf(s, "0x%02x:\t0x%02x\n", reg, ret);
|
|
}
|
|
seq_printf(s, "\n");
|
|
|
|
seq_printf(s, "Command registers:\n");
|
|
seq_printf(s, "==================\n");
|
|
ret = smb347_read(smb, CMD_A);
|
|
if (ret < 0)
|
|
return ret;
|
|
seq_printf(s, "0x%02x:\t0x%02x\n", CMD_A, ret);
|
|
ret = smb347_read(smb, CMD_B);
|
|
if (ret < 0)
|
|
return ret;
|
|
seq_printf(s, "0x%02x:\t0x%02x\n", CMD_B, ret);
|
|
ret = smb347_read(smb, CMD_C);
|
|
if (ret < 0)
|
|
return ret;
|
|
seq_printf(s, "0x%02x:\t0x%02x\n", CMD_C, ret);
|
|
seq_printf(s, "\n");
|
|
|
|
seq_printf(s, "Interrupt status registers:\n");
|
|
seq_printf(s, "===========================\n");
|
|
for (reg = IRQSTAT_A; reg <= IRQSTAT_F; reg++) {
|
|
ret = smb347_read(smb, reg);
|
|
if (ret < 0)
|
|
return ret;
|
|
seq_printf(s, "0x%02x:\t0x%02x\n", reg, ret);
|
|
}
|
|
seq_printf(s, "\n");
|
|
|
|
seq_printf(s, "Status registers:\n");
|
|
seq_printf(s, "=================\n");
|
|
for (reg = STAT_A; reg <= STAT_E; reg++) {
|
|
ret = smb347_read(smb, reg);
|
|
if (ret < 0)
|
|
return ret;
|
|
seq_printf(s, "0x%02x:\t0x%02x\n", reg, ret);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int smb347_debugfs_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, smb347_debugfs_show, inode->i_private);
|
|
}
|
|
|
|
static const struct file_operations smb347_debugfs_fops = {
|
|
.open = smb347_debugfs_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
#ifdef CONFIG_ACPI
|
|
extern void *smb347_platform_data(void *);
|
|
#endif
|
|
static int smb347_probe(struct i2c_client *client,
|
|
const struct i2c_device_id *id)
|
|
{
|
|
struct smb347_charger_platform_data *pdata;
|
|
struct device *dev = &client->dev;
|
|
struct smb347_charger *smb;
|
|
int ret;
|
|
struct acpi_gpio_info gpio_info;
|
|
|
|
pdata = dev->platform_data;
|
|
|
|
#ifdef CONFIG_ACPI
|
|
pdata = smb347_platform_data(NULL);
|
|
#endif
|
|
if (!pdata)
|
|
return -EINVAL;
|
|
|
|
#ifdef CONFIG_ACPI
|
|
pdata->irq_gpio = acpi_get_gpio_by_index(&client->dev, 0, &gpio_info);
|
|
if (pdata->irq_gpio < 0)
|
|
return -EINVAL;
|
|
else
|
|
dev_info(&client->dev, "gpio no:%d\n", pdata->irq_gpio);
|
|
#endif
|
|
|
|
if (!pdata->use_mains && !pdata->use_usb)
|
|
return -EINVAL;
|
|
|
|
smb = devm_kzalloc(dev, sizeof(*smb), GFP_KERNEL);
|
|
if (!smb)
|
|
return -ENOMEM;
|
|
|
|
i2c_set_clientdata(client, smb);
|
|
|
|
mutex_init(&smb->lock);
|
|
smb->client = client;
|
|
smb->pdata = pdata;
|
|
|
|
#ifndef CONFIG_ACPI
|
|
smb->is_smb349 = strcmp(id->name, "smb349") ? 0 : 1;
|
|
#else
|
|
smb->is_smb349 = 1;
|
|
#endif
|
|
dev_info(&smb->client->dev, "%s chip in use.\n",
|
|
smb->is_smb349 ? "smb349" : "smb347");
|
|
|
|
ret = smb347_hw_init(smb);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
wake_lock_init(&smb->wakelock, WAKE_LOCK_SUSPEND, "smb_wakelock");
|
|
|
|
smb347_dev = smb;
|
|
if (smb->pdata->use_regulator) {
|
|
smb->regulator_v3p3sx = regulator_get(dev, REGULATOR_V3P3SX);
|
|
if (IS_ERR(smb->regulator_v3p3sx)) {
|
|
dev_warn(&smb->client->dev, "V3P3SX failed");
|
|
smb->regulator_v3p3sx = NULL;
|
|
}
|
|
}
|
|
|
|
INIT_DELAYED_WORK(&smb->chg_upd_worker, smb347_chg_upd_worker);
|
|
#ifdef CONFIG_POWER_SUPPLY_CHARGER
|
|
INIT_DELAYED_WORK(&smb->full_worker, smb347_full_worker);
|
|
#endif
|
|
INIT_DELAYED_WORK(&smb->temp_upd_worker, smb347_temp_upd_worker);
|
|
|
|
if (smb->pdata->use_mains) {
|
|
smb->mains.name = "smb34x-ac_charger";
|
|
smb->mains.type = POWER_SUPPLY_TYPE_MAINS;
|
|
smb->mains.get_property = smb347_mains_get_property;
|
|
smb->mains.properties = smb347_mains_properties;
|
|
smb->mains.num_properties = ARRAY_SIZE(smb347_mains_properties);
|
|
smb->mains.supplied_to = pdata->supplied_to;
|
|
smb->mains.num_supplicants = pdata->num_supplicants;
|
|
ret = power_supply_register(dev, &smb->mains);
|
|
if (ret < 0)
|
|
goto psy_reg1_failed;
|
|
}
|
|
|
|
if (smb->pdata->use_usb) {
|
|
smb->usb.name = "smb34x-usb_charger";
|
|
smb->usb.type = POWER_SUPPLY_TYPE_USB;
|
|
smb->usb.get_property = smb347_usb_get_property;
|
|
smb->usb.properties = smb347_usb_properties;
|
|
smb->usb.num_properties = ARRAY_SIZE(smb347_usb_properties);
|
|
smb->usb.supplied_to = pdata->supplied_to;
|
|
smb->usb.num_supplicants = pdata->num_supplicants;
|
|
smb->usb.throttle_states = pdata->throttle_states;
|
|
smb->usb.num_throttle_states = pdata->num_throttle_states;
|
|
smb->usb.supported_cables = pdata->supported_cables;
|
|
smb->usb.set_property = smb347_usb_set_property;
|
|
smb->max_cc = 1800;
|
|
smb->max_cv = 4350;
|
|
ret = power_supply_register(dev, &smb->usb);
|
|
if (ret < 0)
|
|
goto psy_reg2_failed;
|
|
}
|
|
|
|
if (smb->pdata->show_battery) {
|
|
smb->battery.name = "smb34x_battery";
|
|
smb->battery.type = POWER_SUPPLY_TYPE_BATTERY;
|
|
smb->battery.get_property = smb347_battery_get_property;
|
|
smb->battery.properties = smb347_battery_properties;
|
|
smb->battery.num_properties =
|
|
ARRAY_SIZE(smb347_battery_properties);
|
|
ret = power_supply_register(dev, &smb->battery);
|
|
if (ret < 0)
|
|
goto psy_reg3_failed;
|
|
}
|
|
|
|
/* register with extcon */
|
|
smb->edev = devm_kzalloc(dev, sizeof(struct extcon_dev), GFP_KERNEL);
|
|
if (!smb->edev) {
|
|
dev_err(&client->dev, "mem alloc failed\n");
|
|
ret = -ENOMEM;
|
|
goto psy_reg4_failed;
|
|
}
|
|
smb->edev->name = "smb34x";
|
|
smb->edev->supported_cable = smb34x_extcon_cable;
|
|
ret = extcon_dev_register(smb->edev, &client->dev);
|
|
if (ret) {
|
|
dev_err(&client->dev, "extcon registration failed!!\n");
|
|
goto psy_reg4_failed;
|
|
}
|
|
|
|
/*
|
|
* Interrupt pin is optional. If it is connected, we setup the
|
|
* interrupt support here.
|
|
*/
|
|
if (pdata->irq_gpio >= 0) {
|
|
ret = smb347_irq_init(smb);
|
|
if (ret < 0) {
|
|
dev_warn(dev, "failed to initialize IRQ: %d\n", ret);
|
|
dev_warn(dev, "disabling IRQ support\n");
|
|
goto psy_irq_failed;
|
|
}
|
|
}
|
|
|
|
if (smb->pdata->detect_chg)
|
|
smb34x_update_charger_type(smb);
|
|
smb347_otg_detect(smb);
|
|
|
|
smb->running = true;
|
|
smb->dentry = debugfs_create_file("smb347-regs", S_IRUSR, NULL, smb,
|
|
&smb347_debugfs_fops);
|
|
return 0;
|
|
|
|
psy_irq_failed:
|
|
extcon_dev_unregister(smb->edev);
|
|
psy_reg4_failed:
|
|
if (smb->pdata->show_battery)
|
|
power_supply_unregister(&smb->battery);
|
|
psy_reg3_failed:
|
|
if (smb->pdata->use_usb)
|
|
power_supply_unregister(&smb->usb);
|
|
psy_reg2_failed:
|
|
if (smb->pdata->use_mains)
|
|
power_supply_unregister(&smb->mains);
|
|
psy_reg1_failed:
|
|
smb347_hw_uninit(smb);
|
|
wake_lock_destroy(&smb->wakelock);
|
|
smb347_dev = NULL;
|
|
return ret;
|
|
}
|
|
|
|
static int smb347_remove(struct i2c_client *client)
|
|
{
|
|
struct smb347_charger *smb = i2c_get_clientdata(client);
|
|
|
|
if (!IS_ERR_OR_NULL(smb->dentry))
|
|
debugfs_remove(smb->dentry);
|
|
|
|
smb->running = false;
|
|
|
|
if (client->irq) {
|
|
smb347_irq_disable(smb);
|
|
free_irq(client->irq, smb);
|
|
gpio_free(smb->pdata->irq_gpio);
|
|
}
|
|
|
|
smb347_hw_uninit(smb);
|
|
extcon_dev_unregister(smb->edev);
|
|
if (smb->pdata->show_battery)
|
|
power_supply_unregister(&smb->battery);
|
|
if (smb->pdata->use_usb)
|
|
power_supply_unregister(&smb->usb);
|
|
if (smb->pdata->use_mains)
|
|
power_supply_unregister(&smb->mains);
|
|
wake_lock_destroy(&smb->wakelock);
|
|
|
|
if (smb->regulator_v3p3sx)
|
|
regulator_put(smb->regulator_v3p3sx);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void smb347_shutdown(struct i2c_client *client)
|
|
{
|
|
struct smb347_charger *smb = i2c_get_clientdata(client);
|
|
|
|
if (client->irq > 0)
|
|
disable_irq(client->irq);
|
|
|
|
if (smb->drive_vbus)
|
|
smb347_otg_disable(smb);
|
|
|
|
return;
|
|
}
|
|
|
|
static int smb347_suspend(struct device *dev)
|
|
{
|
|
struct smb347_charger *smb = dev_get_drvdata(dev);
|
|
|
|
if (smb->client->irq > 0)
|
|
disable_irq(smb->client->irq);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int smb347_resume(struct device *dev)
|
|
{
|
|
struct smb347_charger *smb = dev_get_drvdata(dev);
|
|
|
|
if (smb->client->irq > 0)
|
|
enable_irq(smb->client->irq);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct dev_pm_ops smb347_pm_ops = {
|
|
SET_SYSTEM_SLEEP_PM_OPS(smb347_suspend, smb347_resume)
|
|
};
|
|
|
|
static const struct i2c_device_id smb347_id[] = {
|
|
{ "smb347", 0},
|
|
{ "smb349", 1},
|
|
{ "SMB0347", 0},
|
|
{ "SMB0349", 1},
|
|
{}
|
|
};
|
|
MODULE_DEVICE_TABLE(i2c, smb347_id);
|
|
|
|
#ifdef CONFIG_ACPI
|
|
static struct acpi_device_id smb349_acpi_match[] = {
|
|
{"SMB0349", 0},
|
|
{}
|
|
};
|
|
MODULE_DEVICE_TABLE(acpi, smb349_acpi_match);
|
|
|
|
#endif
|
|
|
|
|
|
static struct i2c_driver smb347_driver = {
|
|
.driver = {
|
|
.name = "smb347",
|
|
.owner = THIS_MODULE,
|
|
.pm = &smb347_pm_ops,
|
|
#ifdef CONFIG_ACPI
|
|
.acpi_match_table = ACPI_PTR(smb349_acpi_match),
|
|
#endif
|
|
},
|
|
.probe = smb347_probe,
|
|
.remove = smb347_remove,
|
|
.id_table = smb347_id,
|
|
.shutdown = smb347_shutdown,
|
|
};
|
|
|
|
static int __init smb347_init(void)
|
|
{
|
|
return i2c_add_driver(&smb347_driver);
|
|
}
|
|
late_initcall(smb347_init);
|
|
|
|
static void __exit smb347_exit(void)
|
|
{
|
|
i2c_del_driver(&smb347_driver);
|
|
}
|
|
module_exit(smb347_exit);
|
|
|
|
MODULE_AUTHOR("Bruce E. Robertson <bruce.e.robertson@intel.com>");
|
|
MODULE_AUTHOR("Mika Westerberg <mika.westerberg@linux.intel.com>");
|
|
MODULE_DESCRIPTION("SMB347 battery charger driver");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_ALIAS("i2c:smb347");
|