| /* Copyright (c) 2012 The Linux Foundation. All rights reserved. |
| * Copyright (c) 2013 LGE Inc. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 and |
| * only version 2 as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| */ |
| #define pr_fmt(fmt) "%s: " fmt, __func__ |
| |
| #include <linux/i2c.h> |
| #include <linux/gpio.h> |
| #include <linux/errno.h> |
| #include <linux/delay.h> |
| #include <linux/module.h> |
| #include <linux/debugfs.h> |
| #include <linux/workqueue.h> |
| #include <linux/interrupt.h> |
| #include <linux/slab.h> |
| #include <linux/i2c/smb349.h> |
| #include <linux/power_supply.h> |
| #include <linux/of_gpio.h> |
| |
| #define SMB349_MASK(BITS, POS) ((unsigned char)(((1 << BITS) - 1) << POS)) |
| |
| /* Register definitions */ |
| #define CHG_CURRENT_REG 0x00 |
| #define CHG_OTHER_CURRENT_REG 0x01 |
| #define VAR_FUNC_REG 0x02 |
| #define FLOAT_VOLTAGE_REG 0x03 |
| #define CHG_CTRL_REG 0x04 |
| #define STAT_TIMER_REG 0x05 |
| #define PIN_ENABLE_CTRL_REG 0x06 |
| #define THERM_CTRL_A_REG 0x07 |
| #define CTRL_FUNCTIONS_REG 0x09 |
| #define OTG_TLIM_THERM_CNTRL_REG 0x0A |
| #define HARD_SOFT_LIMIT_CELL_TEMP_MONITOR_REG 0x0B |
| #define FAULT_IRQ_REG 0x0C |
| #define STATUS_IRQ_REG 0x0D |
| #define SYSOK_USB3_SELECT_REG 0x0E |
| #define FLEX_CHARGE_REG 0x10 |
| #define STATUS_INT_REG 0x11 |
| #define I2C_BUS_SLAVE_ADDR_REG 0x12 |
| #define CMD_A_REG 0x30 |
| #define CMD_B_REG 0x31 |
| #define CMD_C_REG 0x33 |
| #define IRQ_A_REG 0x35 |
| #define IRQ_B_REG 0x36 |
| #define IRQ_C_REG 0x37 |
| #define IRQ_D_REG 0x38 |
| #define IRQ_E_REG 0x39 |
| #define IRQ_F_REG 0x3A |
| #define STATUS_A_REG 0x3B |
| #define STATUS_B_REG 0x3C |
| #define STATUS_C_REG 0x3D |
| #define STATUS_D_REG 0x3E |
| #define STATUS_E_REG 0x3F |
| |
| /* Status bits and masks */ |
| #define CHG_STATUS_MASK SMB349_MASK(2, 1) |
| #define CHG_ENABLE_STATUS_BIT BIT(0) |
| |
| /* Control bits and masks */ |
| #define FAST_CHG_CURRENT_MASK SMB349_MASK(4, 4) |
| #define AC_INPUT_CURRENT_LIMIT_MASK SMB349_MASK(4, 0) |
| #define PRE_CHG_CURRENT_MASK SMB349_MASK(3, 5) |
| #define TERMINATION_CURRENT_MASK SMB349_MASK(3, 2) |
| #define PRE_CHG_TO_FAST_CHG_THRESH_MASK SMB349_MASK(2, 6) |
| #define FLOAT_VOLTAGE_MASK SMB349_MASK(6, 0) |
| #define EN_PIN_CTRL_MASK SMB349_MASK(2, 5) |
| #define USB_OTG_EN_BIT BIT(4) |
| #define CHG_ENABLE_BIT BIT(1) |
| #define VOLATILE_W_PERM_BIT BIT(7) |
| #define USB_SELECTION_BIT BIT(1) |
| #define SYSTEM_FET_ENABLE_BIT BIT(7) |
| #define AUTOMATIC_INPUT_CURR_LIMIT_BIT BIT(4) |
| #define AUTOMATIC_POWER_SOURCE_DETECTION_BIT BIT(2) |
| #define BATT_OV_END_CHG_BIT BIT(1) |
| #define VCHG_FUNCTION BIT(0) |
| #define CURR_TERM_END_CHG_BIT BIT(6) |
| #define AUTO_RECHG_DIS_BIT BIT(7) |
| #define USB_2_3_SEL_BIT BIT(2) |
| #define USB_5_1_MODE_BIT BIT(1) |
| #define USB_HC_MODE_BIT BIT(0) |
| #define USB_CS_BIT BIT(4) |
| #define BATT_MISSING_STATUS_BIT BIT(4) |
| #define INOK_BIT BIT(2) |
| #define MISSING_BATT_BIT BIT(1) |
| #define FAST_CHARGE_SET_BIT BIT(6) |
| #define POWER_OK_BIT BIT(0) |
| #define PRE_CHG_TO_FAST_CHG_THRESH_BIT BIT(1) |
| #define OTG_OC_LIMIT_STATUS_BIT BIT(6) |
| #define OTG_BATT_UV_STATUS_BIT BIT(4) |
| #define BOOST_BACK_PREVENTION_BIT BIT(5) |
| |
| #define SMB349_CHG_IUSB_MAX_MIN_100 100 |
| #define SMB349_CHG_IUSB_MAX_MIN_MA 200 |
| |
| enum smb349_chg_status { |
| SMB_CHG_STATUS_NONE = 0, |
| SMB_CHG_STATUS_PRE_CHARGE = 1, |
| SMB_CHG_STATUS_FAST_CHARGE = 2, |
| SMB_CHG_STATUS_TAPER_CHARGE = 3, |
| SMB_CHG_STATUS_EXCEPTION = 4, |
| }; |
| |
| static const char * const smb349_chg_status[] = { |
| "none", |
| "pre-charge", |
| "fast-charge", |
| "taper-charge", |
| "exception" |
| }; |
| |
| struct smb349_struct { |
| struct i2c_client *client; |
| struct dentry *dent; |
| |
| int chg_current_ma; |
| int term_current_ma; |
| int en_n_gpio; |
| int chg_susp_gpio; |
| int stat_gpio; |
| int otg_en_gpio; |
| int irq; |
| int ext_pwr; |
| int usb_present; |
| int usb_online; |
| bool ac_online; |
| int chg_type; |
| int charging_disabled; |
| |
| enum smb349_chg_status chg_status; |
| |
| struct delayed_work irq_work; |
| struct wake_lock chg_wake_lock; |
| struct power_supply ac_psy; |
| struct power_supply *usb_psy; |
| struct power_supply *batt_psy; |
| struct power_supply *wlc_psy; |
| struct wake_lock uevent_wake_lock; |
| int wlc_support; |
| int ext_ovp_otg_ctrl; |
| }; |
| |
| static struct smb349_struct *the_smb349_chg; |
| |
| struct debug_reg { |
| char *name; |
| u8 reg; |
| }; |
| |
| #define SMB349_DEBUG_REG(x, y) {#x#y, y##_REG} |
| |
| static struct debug_reg smb349_debug_regs[] = { |
| SMB349_DEBUG_REG(00_, CHG_CURRENT), |
| SMB349_DEBUG_REG(01_, CHG_OTHER_CURRENT), |
| SMB349_DEBUG_REG(02_, VAR_FUNC), |
| SMB349_DEBUG_REG(03_, FLOAT_VOLTAGE), |
| SMB349_DEBUG_REG(04_, CHG_CTRL), |
| SMB349_DEBUG_REG(05_, STAT_TIMER), |
| SMB349_DEBUG_REG(06_, PIN_ENABLE_CTRL), |
| SMB349_DEBUG_REG(07_, THERM_CTRL_A), |
| SMB349_DEBUG_REG(09_, CTRL_FUNCTIONS), |
| SMB349_DEBUG_REG(0A_, OTG_TLIM_THERM_CNTRL), |
| SMB349_DEBUG_REG(0B_, HARD_SOFT_LIMIT_CELL_TEMP_MONITOR), |
| SMB349_DEBUG_REG(0C_, FAULT_IRQ), |
| SMB349_DEBUG_REG(0D_, STATUS_IRQ), |
| SMB349_DEBUG_REG(0E_, SYSOK_USB3_SELECT), |
| SMB349_DEBUG_REG(10_, FLEX_CHARGE), |
| SMB349_DEBUG_REG(11_, STATUS_INT), |
| SMB349_DEBUG_REG(12_, I2C_BUS_SLAVE_ADDR), |
| SMB349_DEBUG_REG(30_, CMD_A), |
| SMB349_DEBUG_REG(31_, CMD_B), |
| SMB349_DEBUG_REG(33_, CMD_C), |
| SMB349_DEBUG_REG(35_, IRQ_A), |
| SMB349_DEBUG_REG(36_, IRQ_B), |
| SMB349_DEBUG_REG(37_, IRQ_C), |
| SMB349_DEBUG_REG(38_, IRQ_D), |
| SMB349_DEBUG_REG(39_, IRQ_E), |
| SMB349_DEBUG_REG(3A_, IRQ_F), |
| SMB349_DEBUG_REG(3B_, STATUS_A), |
| SMB349_DEBUG_REG(3C_, STATUS_B), |
| SMB349_DEBUG_REG(3D_, STATUS_C), |
| SMB349_DEBUG_REG(3E_, STATUS_D), |
| SMB349_DEBUG_REG(3F_, STATUS_E), |
| }; |
| |
| int32_t smb349_is_ready(void) |
| { |
| struct smb349_struct *smb349_chg = the_smb349_chg; |
| |
| if (!smb349_chg) |
| return -EPROBE_DEFER; |
| return 0; |
| } |
| EXPORT_SYMBOL(smb349_is_ready); |
| |
| static int smb349_read_reg(struct i2c_client *client, int reg, |
| u8 *val) |
| { |
| s32 ret; |
| struct smb349_struct *smb349_chg; |
| |
| smb349_chg = i2c_get_clientdata(client); |
| ret = i2c_smbus_read_byte_data(smb349_chg->client, reg); |
| if (ret < 0) { |
| dev_err(&smb349_chg->client->dev, |
| "i2c read fail: can't read from %02x: %d\n", reg, ret); |
| return ret; |
| } else { |
| *val = ret; |
| } |
| |
| return 0; |
| } |
| |
| static int smb349_write_reg(struct i2c_client *client, int reg, |
| u8 val) |
| { |
| s32 ret; |
| struct smb349_struct *smb349_chg; |
| |
| smb349_chg = i2c_get_clientdata(client); |
| ret = i2c_smbus_write_byte_data(smb349_chg->client, reg, val); |
| if (ret < 0) { |
| dev_err(&smb349_chg->client->dev, |
| "i2c write fail: can't write %02x to %02x: %d\n", |
| val, reg, ret); |
| return ret; |
| } |
| return 0; |
| } |
| |
| static int smb349_masked_write(struct i2c_client *client, int reg, |
| u8 mask, u8 val) |
| { |
| s32 rc; |
| u8 temp; |
| |
| rc = smb349_read_reg(client, reg, &temp); |
| if (rc) { |
| pr_err("smb349_read_reg failed: reg=%03X, rc=%d\n", reg, rc); |
| return rc; |
| } |
| temp &= ~mask; |
| temp |= val & mask; |
| rc = smb349_write_reg(client, reg, temp); |
| if (rc) { |
| pr_err("smb349_write failed: reg=%03X, rc=%d\n", reg, rc); |
| return rc; |
| } |
| return 0; |
| } |
| |
| static bool smb349_is_charger_present(struct smb349_struct *smb349_chg) |
| { |
| u8 temp; |
| bool chg_online; |
| int ret; |
| |
| ret = smb349_read_reg(smb349_chg->client, IRQ_F_REG, &temp); |
| if (ret) { |
| pr_err("failed to read IRQ_STATUS_F_REG rc=%d\n", ret); |
| return false; |
| } |
| |
| chg_online = temp & 0x01; |
| pr_debug("charger present = %d\n", chg_online); |
| |
| return chg_online; |
| } |
| |
| #define SMB349_CHG_ENABLE_SHIFT 1 |
| static int smb349_enable_charging(struct smb349_struct *smb349_chg, bool enable) |
| { |
| int ret; |
| u8 val = (u8)(!!enable << SMB349_CHG_ENABLE_SHIFT); |
| |
| pr_info("enable=%d\n", enable); |
| |
| ret = smb349_masked_write(smb349_chg->client, CMD_A_REG, |
| CHG_ENABLE_BIT, val); |
| if (ret) { |
| pr_err("failed to set CHG_ENABLE_BIT rc=%d\n", ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| /* When the status bit of a certain condition is read, |
| * the corresponding IRQ signal is cleared. |
| */ |
| static int smb349_clear_irq(struct smb349_struct *smb349_chg) |
| { |
| u8 val[6] = {0,}; |
| int ret; |
| |
| ret = smb349_read_reg(smb349_chg->client, IRQ_A_REG, &val[0]); |
| if (ret < 0) { |
| pr_err("failed to read IRQ_A_REG rc=%d\n", ret); |
| return ret; |
| } |
| |
| ret = smb349_read_reg(smb349_chg->client, IRQ_B_REG, &val[1]); |
| if (ret < 0) { |
| pr_err("failed to read IRQ_B_REG rc=%d\n", ret); |
| return ret; |
| } |
| |
| ret = smb349_read_reg(smb349_chg->client, IRQ_C_REG, &val[2]); |
| if (ret < 0) { |
| pr_err("Failed to read IRQ_C_REG rc=%d\n", ret); |
| return ret; |
| } |
| |
| ret = smb349_read_reg(smb349_chg->client, IRQ_D_REG, &val[3]); |
| if (ret < 0) { |
| pr_err("Failed to read IRQ_D_REG rc=%d\n", ret); |
| return ret; |
| } |
| |
| ret = smb349_read_reg(smb349_chg->client, IRQ_E_REG, &val[4]); |
| if (ret < 0) { |
| pr_err("Failed to read IRQ_E_REG rc=%d\n", ret); |
| return ret; |
| } |
| |
| ret = smb349_read_reg(smb349_chg->client, IRQ_F_REG, &val[5]); |
| if (ret < 0) { |
| pr_err("Failed to read IRQ_F_REG rc=%d\n", ret); |
| return ret; |
| } |
| |
| pr_debug("irq status: A[0x%02x], B[0x%02x], C[0x%02x], "\ |
| "D[0x%02x], E[0x%02x], F[0x%02x]\n", |
| val[0], val[1], val[2], val[3], val[4], val[5]); |
| return 0; |
| } |
| |
| static void smb349_irq_worker(struct work_struct *work) |
| { |
| struct smb349_struct *smb349_chg = |
| container_of(work, struct smb349_struct, irq_work.work); |
| union power_supply_propval ret = {0,}; |
| int rc = 0; |
| bool ext_pwr; |
| int wlc_pwr = 0; |
| |
| rc = smb349_clear_irq(smb349_chg); |
| ext_pwr = smb349_is_charger_present(smb349_chg); |
| if (smb349_chg->wlc_psy) { |
| smb349_chg->wlc_psy->get_property(smb349_chg->wlc_psy, |
| POWER_SUPPLY_PROP_ONLINE, &ret); |
| wlc_pwr = ret.intval; |
| pr_info("wireless charger is = %d\n", wlc_pwr); |
| } |
| |
| if ((smb349_chg->ext_pwr ^ ext_pwr) && !wlc_pwr) { |
| wake_lock_timeout(&smb349_chg->uevent_wake_lock, HZ*2); |
| smb349_chg->ext_pwr = ext_pwr; |
| pr_info("notify vbus to usb otg ext_pwr = %d\n", ext_pwr); |
| power_supply_set_present(smb349_chg->usb_psy, |
| smb349_chg->ext_pwr); |
| } |
| power_supply_changed(&smb349_chg->ac_psy); |
| } |
| |
| /* |
| * The STAT pin is low when charging and high when not charging. |
| * Interrupt is triggered on both rising or falling edge. |
| */ |
| static irqreturn_t smb349_irq(int irq, void *dev_id) |
| { |
| struct smb349_struct *smb349_chg = dev_id; |
| |
| schedule_delayed_work(&smb349_chg->irq_work, msecs_to_jiffies(100)); |
| |
| return IRQ_HANDLED; |
| } |
| |
| #define SMB349_FAST_CHG_MIN_MA 1000 |
| #define SMB349_FAST_CHG_STEP_MA 200 |
| #define SMB349_FAST_CHG_MAX_MA 4000 |
| #define SMB349_FAST_CHG_SHIFT 4 |
| static int smb349_chg_current_set(struct smb349_struct *smb349_chg) |
| { |
| u8 temp; |
| |
| if ((smb349_chg->chg_current_ma < SMB349_FAST_CHG_MIN_MA) || |
| (smb349_chg->chg_current_ma > SMB349_FAST_CHG_MAX_MA)) { |
| pr_err("bad mA=%d asked to set\n", smb349_chg->chg_current_ma); |
| return -EINVAL; |
| } |
| |
| temp = (smb349_chg->chg_current_ma - SMB349_FAST_CHG_MIN_MA) |
| / SMB349_FAST_CHG_STEP_MA; |
| |
| temp = temp << SMB349_FAST_CHG_SHIFT; |
| pr_debug("fast chg limit = %d setting = %02x\n", |
| smb349_chg->chg_current_ma, temp); |
| return smb349_masked_write(smb349_chg->client, CHG_CURRENT_REG, |
| FAST_CHG_CURRENT_MASK, temp); |
| } |
| |
| #define SMB349_TERM_CURR_MIN_MA 100 |
| #define SMB349_TERM_CURR_STEP_MA 100 |
| #define SMB349_TERM_CURR_MAX_MA 700 |
| #define SMB349_TERM_CURR_SHIFT 2 |
| static int smb349_term_current_set(struct smb349_struct *smb349_chg) |
| { |
| u8 temp; |
| |
| if ((smb349_chg->term_current_ma < SMB349_TERM_CURR_MIN_MA) || |
| (smb349_chg->term_current_ma > SMB349_TERM_CURR_MAX_MA)) { |
| pr_err("bad mA=%d asked to set\n", smb349_chg->term_current_ma); |
| return -EINVAL; |
| } |
| |
| temp = (smb349_chg->term_current_ma - SMB349_TERM_CURR_MIN_MA) |
| / SMB349_TERM_CURR_STEP_MA; |
| |
| temp -= 1; |
| if (temp == 255) |
| temp = 6; |
| |
| temp = temp << SMB349_TERM_CURR_SHIFT; |
| pr_debug("chg termination current = %d setting = %02x\n", |
| smb349_chg->term_current_ma, temp); |
| return smb349_masked_write(smb349_chg->client, CHG_OTHER_CURRENT_REG, |
| TERMINATION_CURRENT_MASK, temp); |
| } |
| |
| /* |
| * val must be EN_PIN_CTRL_MASK or 0. |
| * EN_PIN_CTRL_MASK - pin control, active low |
| * 0 - I2C control, active high |
| */ |
| static int smb349_set_pin_control(struct smb349_struct *smb349_chg, u8 val) |
| { |
| int ret; |
| |
| pr_debug("smb349_set_pin_control, val = %d\n", val); |
| |
| if (val != EN_PIN_CTRL_MASK) |
| val = 0; |
| |
| ret = smb349_masked_write(smb349_chg->client, PIN_ENABLE_CTRL_REG, |
| EN_PIN_CTRL_MASK, val); |
| if (ret) { |
| pr_err("failed to set EN_PIN_CTRL_MASK rc=%d\n", ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * val must be USB_CS_BIT or 0. |
| * USB_CS_BIT - pin control |
| * 0 - register control |
| */ |
| static int smb349_set_usbcs_control(struct smb349_struct *smb349_chg, u8 val) |
| { |
| int ret; |
| |
| pr_debug("smb349_set_usbcs_control, val = %d\n", val); |
| |
| if (val != USB_CS_BIT) |
| val = 0; |
| |
| ret = smb349_masked_write(smb349_chg->client, PIN_ENABLE_CTRL_REG, |
| USB_CS_BIT, val); |
| if (ret) { |
| pr_err("failed to set USB_CS_BIT rc=%d\n", ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int smb349_set_float_voltage(struct smb349_struct *smb349_chg, u8 val) |
| { |
| int ret; |
| |
| pr_debug("smb349_set_float_voltage, val = %d\n", val); |
| |
| if (val < 0x0 || val > 0x3f) { |
| pr_err("invalid setting value, val = %d\n", val); |
| return -EINVAL; |
| } |
| |
| ret = smb349_masked_write(smb349_chg->client, FLOAT_VOLTAGE_REG, |
| FLOAT_VOLTAGE_MASK, val); |
| if (ret) { |
| pr_err("failed to set FLOAT_VOLTAGE_REG rc=%d\n", ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| #define SMB349_USB_5_1_MODE_SHIFT 1 |
| static int smb349_set_usb_5_1_mode(struct smb349_struct *smb349_chg, u8 usb5) |
| { |
| int ret; |
| |
| pr_debug("smb349_set_usb_5_1_mode, usb5 = %d\n", usb5); |
| |
| usb5 = usb5 << SMB349_USB_5_1_MODE_SHIFT; |
| |
| ret = smb349_masked_write(smb349_chg->client, CMD_B_REG, |
| USB_5_1_MODE_BIT, usb5); |
| if (ret) { |
| pr_err("failed to set USB_5_1_MODE_BIT rc=%d\n", ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int smb349_set_irq(struct smb349_struct *smb349_chg) |
| { |
| int ret; |
| |
| ret = smb349_masked_write(smb349_chg->client, STATUS_IRQ_REG, |
| INOK_BIT | MISSING_BATT_BIT, |
| INOK_BIT | MISSING_BATT_BIT); |
| |
| if (ret) { |
| pr_err("failed to set INOK | MISSING_BATT rc = %d\n", ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int set_reg(void *data, u64 val) |
| { |
| int addr = (int)data; |
| int ret; |
| u8 temp; |
| |
| if (!the_smb349_chg) { |
| pr_err("the_smb349_chg is not initialized\n"); |
| return -EAGAIN; |
| } |
| |
| temp = (u16) val; |
| ret = smb349_write_reg(the_smb349_chg->client, addr, temp); |
| |
| if (ret) { |
| pr_err("smb349_write_reg to %x value = %d errored = %d\n", |
| addr, temp, ret); |
| return -EAGAIN; |
| } |
| return 0; |
| } |
| |
| static int get_reg(void *data, u64 *val) |
| { |
| int addr = (int)data; |
| int ret; |
| u8 temp = 0; |
| |
| if (!the_smb349_chg) { |
| pr_err("the_smb349_chg is not initialized\n"); |
| return -EAGAIN; |
| } |
| |
| ret = smb349_read_reg(the_smb349_chg->client, addr, &temp); |
| if (ret) { |
| pr_err("smb349_read_reg to %x value = %d errored = %d\n", |
| addr, temp, ret); |
| return -EAGAIN; |
| } |
| |
| *val = temp; |
| return 0; |
| } |
| |
| DEFINE_SIMPLE_ATTRIBUTE(reg_fops, get_reg, set_reg, "0x%02llx\n"); |
| |
| /* |
| * mode must be USB_HC_MODE_BIT or 0. |
| * USB_HC_MODE_BIT - high current mode. |
| * 0 - usb mode. |
| */ |
| static int smb349_usb_hc_mode(struct smb349_struct *smb349_chg, u8 mode) |
| { |
| int ret; |
| |
| pr_debug("smb349_usb_hc_mode mode = %d\n", mode); |
| |
| if (mode != USB_HC_MODE_BIT) |
| mode = 0; |
| |
| ret = smb349_masked_write(smb349_chg->client, CMD_B_REG, |
| USB_HC_MODE_BIT, mode); |
| if (ret) { |
| pr_err("failed to set USB_HC_MODE_BIT rc=%d\n", ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * FAST_CHARGE_SET_BIT - Allow fast-charge current settings. |
| * 0 - Force pre-charge current settings. |
| */ |
| static int smb349_set_fast_charge(struct smb349_struct *smb349_chg, u8 val) |
| { |
| int ret; |
| |
| if (val != FAST_CHARGE_SET_BIT) |
| val = 0; |
| |
| ret = smb349_masked_write(smb349_chg->client, CMD_A_REG, |
| FAST_CHARGE_SET_BIT, val); |
| if (ret) { |
| pr_err("failed to set FAST_CHARGE_SET_BIT rc=%d\n", ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int |
| smb349_enbable_fast_chg_thresh(struct smb349_struct *smb349_chg, u8 val) |
| { |
| int ret; |
| |
| pr_info("val = %d\n", val); |
| |
| if (val != PRE_CHG_TO_FAST_CHG_THRESH_BIT) |
| val = 0; |
| |
| ret = smb349_masked_write(smb349_chg->client, SYSOK_USB3_SELECT_REG, |
| PRE_CHG_TO_FAST_CHG_THRESH_BIT, val); |
| if (ret) { |
| pr_err("failed to set PRE_CHG_TO_FAST_CHG_THRESH_BIT\n"); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| struct input_current_ma_limit_entry { |
| int icl_ma; |
| u8 value; |
| }; |
| |
| static struct input_current_ma_limit_entry icl_ma_table[] = { |
| {500, 0x0}, |
| {900, 0x1}, |
| {1000, 0x2}, |
| {1100, 0x3}, |
| {1200, 0x4}, |
| {1300, 0x5}, |
| {1500, 0x6}, |
| {1600, 0x7}, |
| {1700, 0x8}, |
| {1800, 0x9}, |
| {2000, 0xA}, |
| {2200, 0xB}, |
| {2400, 0xC}, |
| {2500, 0xD}, |
| {3000, 0xE}, |
| {3500, 0xF}, |
| }; |
| |
| #define SMB349_INPUT_CURRENT_LIMIT_MIN_MA 500 |
| #define SMB349_INPUT_CURRENT_LIMIT_MAX_MA 3500 |
| static int |
| smb349_input_current_limit_set(struct smb349_struct *smb349_chg, int icl_ma) |
| { |
| int i; |
| u8 temp; |
| |
| if ((icl_ma < SMB349_INPUT_CURRENT_LIMIT_MIN_MA) || |
| (icl_ma > SMB349_INPUT_CURRENT_LIMIT_MAX_MA)) { |
| pr_err("bad mA = %d asked to set\n", icl_ma); |
| return -EINVAL; |
| } |
| |
| for (i = ARRAY_SIZE(icl_ma_table) - 1; i >= 0; i--) { |
| if (icl_ma_table[i].icl_ma == icl_ma) |
| break; |
| } |
| |
| if (i < 0) { |
| pr_err("can't find %d in icl_ma_table. Use min.\n", icl_ma); |
| i = 0; |
| } |
| |
| temp = icl_ma_table[i].value; |
| |
| pr_debug("input current limit=%d setting %02x\n", icl_ma, temp); |
| return smb349_masked_write(smb349_chg->client, CHG_CURRENT_REG, |
| AC_INPUT_CURRENT_LIMIT_MASK, temp); |
| } |
| |
| #define HC_INPUT_CURR_LIMIT_DEFAULT 1800 |
| #define HC_INPUT_CURR_LIMIT_FACTORY 1500 |
| |
| static int smb349_chg_is_otg_active(struct smb349_struct *smb349_chg) |
| { |
| u8 val; |
| int ret; |
| |
| ret = smb349_read_reg(smb349_chg->client, CMD_A_REG, &val); |
| if (ret) { |
| pr_err("failed to read CMD_A_REG = %d\n", ret); |
| return 0; |
| } |
| |
| if (val & USB_OTG_EN_BIT) { |
| ret = smb349_read_reg(smb349_chg->client, IRQ_F_REG, &val); |
| if (ret) { |
| pr_err("failed to read IRQ_F_REG, rc=%d\n", ret); |
| return 0; |
| } |
| |
| if ((val & OTG_OC_LIMIT_STATUS_BIT) || |
| (val & OTG_BATT_UV_STATUS_BIT)) { |
| pr_debug("IRQ Status F = 0x%x.\n", val); |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| pr_info("usb otg disabled.\n"); |
| return 0; |
| } |
| |
| static int smb349_switch_usb_to_charge_mode(struct smb349_struct *smb349_chg) |
| { |
| int ret; |
| |
| pr_info("switch to charge mode\n"); |
| |
| /* enable usb otg */ |
| ret = smb349_masked_write(smb349_chg->client, CMD_A_REG, |
| USB_OTG_EN_BIT, 0); |
| if (ret) { |
| pr_err("failed to turn on usb otg = %d\n", ret); |
| return ret; |
| } |
| |
| smb349_enable_charging(smb349_chg, true); |
| |
| if (smb349_chg->otg_en_gpio) |
| gpio_set_value(smb349_chg->otg_en_gpio, 0); |
| |
| return 0; |
| } |
| |
| static int smb349_switch_usb_to_host_mode(struct smb349_struct *smb349_chg) |
| { |
| int ret; |
| |
| pr_info("switch to host mode\n"); |
| |
| smb349_enable_charging(smb349_chg, false); |
| |
| /* force usb otg */ |
| ret = smb349_masked_write(smb349_chg->client, CMD_A_REG, |
| USB_OTG_EN_BIT, USB_OTG_EN_BIT); |
| if (ret) { |
| pr_err("failed to turn off usb otg = %d\n", ret); |
| return ret; |
| } |
| |
| if (smb349_chg->otg_en_gpio) |
| gpio_set_value(smb349_chg->otg_en_gpio, 1); |
| |
| return 0; |
| } |
| |
| static void pm_external_power_changed(struct power_supply *psy) |
| { |
| struct smb349_struct *smb349_chg = container_of(psy, |
| struct smb349_struct, ac_psy); |
| union power_supply_propval ret = {0,}; |
| int wlc_online = 0; |
| int wlc_chg_current_ma = 0; |
| |
| smb349_chg->usb_psy->get_property(smb349_chg->usb_psy, |
| POWER_SUPPLY_PROP_ONLINE, &ret); |
| smb349_chg->usb_online = ret.intval; |
| |
| if(smb349_chg->wlc_support) { |
| smb349_chg->wlc_psy->get_property(smb349_chg->wlc_psy, |
| POWER_SUPPLY_PROP_ONLINE, &ret); |
| wlc_online = ret.intval; |
| |
| smb349_chg->wlc_psy->get_property(smb349_chg->wlc_psy, |
| POWER_SUPPLY_PROP_CURRENT_MAX, &ret); |
| wlc_chg_current_ma = ret.intval / 1000; |
| pr_info(" wlc_online = %d current = %d\n", wlc_online, |
| wlc_chg_current_ma); |
| } |
| |
| if (smb349_chg->usb_online && |
| smb349_is_charger_present(smb349_chg)) { |
| smb349_usb_hc_mode(smb349_chg, 0); |
| smb349_chg->usb_psy->get_property(smb349_chg->usb_psy, |
| POWER_SUPPLY_PROP_CURRENT_MAX, &ret); |
| smb349_chg->chg_current_ma = ret.intval / 1000; |
| smb349_input_current_limit_set(smb349_chg, |
| smb349_chg->chg_current_ma); |
| |
| pr_info("usb is online! i_limit = %d\n", |
| smb349_chg->chg_current_ma); |
| } else if (smb349_chg->ac_online && |
| smb349_is_charger_present(smb349_chg)) { |
| smb349_usb_hc_mode(smb349_chg, USB_HC_MODE_BIT); |
| smb349_set_fast_charge(smb349_chg, FAST_CHARGE_SET_BIT); |
| smb349_input_current_limit_set(smb349_chg, |
| HC_INPUT_CURR_LIMIT_DEFAULT); |
| smb349_chg_current_set(smb349_chg); |
| |
| pr_info("ac is online! i_limit = %d\n", |
| smb349_chg->chg_current_ma); |
| } else if (wlc_online && smb349_is_charger_present(smb349_chg)) { |
| smb349_usb_hc_mode(smb349_chg, USB_HC_MODE_BIT); |
| smb349_set_fast_charge(smb349_chg, FAST_CHARGE_SET_BIT); |
| smb349_input_current_limit_set(smb349_chg, |
| wlc_chg_current_ma); |
| smb349_chg_current_set(smb349_chg); |
| pr_info("wlc is online! i_limit = %d\n", wlc_chg_current_ma); |
| } |
| |
| smb349_chg->usb_psy->get_property(smb349_chg->usb_psy, |
| POWER_SUPPLY_PROP_SCOPE, &ret); |
| if (ret.intval) { |
| pr_info("usb host mode = %d\n", ret.intval); |
| if ((ret.intval == POWER_SUPPLY_SCOPE_SYSTEM) |
| && !smb349_chg_is_otg_active(smb349_chg)) |
| smb349_switch_usb_to_host_mode(smb349_chg); |
| else if ((ret.intval == POWER_SUPPLY_SCOPE_DEVICE) |
| && smb349_chg_is_otg_active(smb349_chg)) |
| smb349_switch_usb_to_charge_mode(smb349_chg); |
| } |
| |
| power_supply_changed(&smb349_chg->ac_psy); |
| } |
| |
| static int smb349_get_prop_charge_type(struct smb349_struct *smb349_chg) |
| { |
| u8 status_c; |
| enum smb349_chg_status status; |
| int chg_type = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; |
| bool chg_enabled; |
| bool charger_err; |
| int ret; |
| |
| ret = smb349_read_reg(smb349_chg->client, STATUS_C_REG, &status_c); |
| if (ret) { |
| pr_err("failed to read STATUS_C_REG.\n"); |
| chg_type = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; |
| goto exception_handling; |
| } |
| |
| chg_enabled = (bool) (status_c & 0x01); |
| charger_err = (bool) (status_c & (1<<6)); |
| |
| if (!chg_enabled) { |
| pr_debug("charging not enabled.\n"); |
| chg_type = POWER_SUPPLY_CHARGE_TYPE_NONE; |
| goto exception_handling; |
| } |
| |
| if (charger_err) { |
| pr_err("charger error detected.\n"); |
| chg_type = POWER_SUPPLY_CHARGE_TYPE_NONE; |
| goto exception_handling; |
| } |
| |
| status = (status_c >> 1) & 0x3; |
| |
| switch (status) { |
| case SMB_CHG_STATUS_NONE: |
| chg_type = POWER_SUPPLY_CHARGE_TYPE_NONE; |
| break; |
| case SMB_CHG_STATUS_FAST_CHARGE: |
| case SMB_CHG_STATUS_TAPER_CHARGE: |
| chg_type = POWER_SUPPLY_CHARGE_TYPE_FAST; |
| break; |
| case SMB_CHG_STATUS_PRE_CHARGE: |
| chg_type = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; |
| default: |
| break; |
| } |
| |
| pr_info("charge status = %s\n", smb349_chg_status[status]); |
| |
| if (smb349_chg->chg_status != status) { |
| if (status == SMB_CHG_STATUS_NONE) { |
| pr_info("charging stopped.\n"); |
| wake_unlock(&smb349_chg->chg_wake_lock); |
| } else { |
| pr_info("charging started.\n"); |
| wake_lock(&smb349_chg->chg_wake_lock); |
| } |
| smb349_chg->chg_status = status; |
| } |
| |
| return chg_type; |
| |
| exception_handling: |
| smb349_chg->chg_status = SMB_CHG_STATUS_EXCEPTION; |
| if (wake_lock_active(&smb349_chg->chg_wake_lock)) { |
| pr_err("exception_handling : unlock chg_wake_lock.\n"); |
| wake_unlock(&smb349_chg->chg_wake_lock); |
| } |
| return chg_type; |
| } |
| |
| #define FAIL_DEFAULT_SOC 50 |
| static int smb349_get_soc_from_batt_psy(struct smb349_struct *smb349_chg) |
| { |
| union power_supply_propval ret = {0,}; |
| int soc = 0; |
| |
| if (!smb349_chg->batt_psy) |
| smb349_chg->batt_psy = power_supply_get_by_name("battery"); |
| |
| if (smb349_chg->batt_psy) { |
| smb349_chg->batt_psy->get_property(smb349_chg->batt_psy, |
| POWER_SUPPLY_PROP_CAPACITY, &ret); |
| soc = ret.intval; |
| } else { |
| pr_warn("battery power supply is not registered yet\n"); |
| soc = FAIL_DEFAULT_SOC; |
| } |
| |
| return soc; |
| } |
| |
| static int smb349_get_prop_chg_status(struct smb349_struct *smb349_chg) |
| { |
| int chg_type = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; |
| int chg_status = POWER_SUPPLY_STATUS_UNKNOWN; |
| int soc = 0; |
| |
| soc = smb349_get_soc_from_batt_psy(smb349_chg); |
| chg_type = smb349_get_prop_charge_type(smb349_chg); |
| |
| switch (chg_type) { |
| case POWER_SUPPLY_CHARGE_TYPE_NONE: |
| if (smb349_is_charger_present(smb349_chg)) { |
| if (soc >= 100) |
| chg_status = POWER_SUPPLY_STATUS_FULL; |
| else |
| chg_status = POWER_SUPPLY_STATUS_NOT_CHARGING; |
| } else { |
| chg_status = POWER_SUPPLY_STATUS_DISCHARGING; |
| } |
| break; |
| case POWER_SUPPLY_CHARGE_TYPE_TRICKLE: |
| case POWER_SUPPLY_CHARGE_TYPE_FAST: |
| chg_status = POWER_SUPPLY_STATUS_CHARGING; |
| break; |
| default: |
| break; |
| } |
| |
| pr_debug("chg status = %d soc = %d\n", chg_status, soc); |
| return chg_status; |
| } |
| |
| |
| static enum power_supply_property pm_power_props[] = { |
| POWER_SUPPLY_PROP_STATUS, |
| POWER_SUPPLY_PROP_HEALTH, |
| POWER_SUPPLY_PROP_PRESENT, |
| POWER_SUPPLY_PROP_ONLINE, |
| POWER_SUPPLY_PROP_CURRENT_MAX, |
| POWER_SUPPLY_PROP_CHARGE_TYPE, |
| }; |
| |
| static char *pm_power_supplied_to[] = { |
| "battery", |
| }; |
| |
| static int pm_power_get_property(struct power_supply *psy, |
| enum power_supply_property psp, |
| union power_supply_propval *val) |
| { |
| struct smb349_struct *smb349_chg = container_of(psy, |
| struct smb349_struct, |
| ac_psy); |
| |
| switch (psp) { |
| case POWER_SUPPLY_PROP_STATUS: |
| val->intval = smb349_get_prop_chg_status(smb349_chg); |
| break; |
| case POWER_SUPPLY_PROP_HEALTH: |
| val->intval = POWER_SUPPLY_HEALTH_UNKNOWN; //FIXME |
| break; |
| case POWER_SUPPLY_PROP_CURRENT_MAX: |
| val->intval = smb349_chg->chg_current_ma * 1000; |
| break; |
| case POWER_SUPPLY_PROP_PRESENT: |
| case POWER_SUPPLY_PROP_ONLINE: |
| val->intval = smb349_chg->ac_online; |
| break; |
| case POWER_SUPPLY_PROP_CHARGE_TYPE: |
| val->intval = smb349_get_prop_charge_type(smb349_chg); |
| break; |
| default: |
| return -EINVAL; |
| } |
| return 0; |
| } |
| |
| static int pm_power_set_property(struct power_supply *psy, |
| enum power_supply_property psp, |
| const union power_supply_propval *val) |
| { |
| struct smb349_struct *smb349_chg = container_of(psy, |
| struct smb349_struct, |
| ac_psy); |
| |
| switch (psp) { |
| case POWER_SUPPLY_PROP_PRESENT: |
| case POWER_SUPPLY_PROP_ONLINE: |
| smb349_chg->ac_online = val->intval; |
| break; |
| case POWER_SUPPLY_PROP_CURRENT_MAX: |
| smb349_chg->chg_current_ma = val->intval / 1000; |
| break; |
| default: |
| return -EINVAL; |
| } |
| power_supply_changed(&smb349_chg->ac_psy); |
| return 0; |
| } |
| |
| static int smb349_create_debugfs_entries(struct smb349_struct *smb349_chg) |
| { |
| int i; |
| |
| smb349_chg->dent = debugfs_create_dir(SMB349_NAME, NULL); |
| if (IS_ERR(smb349_chg->dent)) { |
| pr_err("smb349 driver couldn't create debugfs dir\n"); |
| return -EFAULT; |
| } |
| |
| for (i = 0 ; i < ARRAY_SIZE(smb349_debug_regs) ; i++) { |
| char *name = smb349_debug_regs[i].name; |
| u32 reg = smb349_debug_regs[i].reg; |
| struct dentry *file; |
| |
| file = debugfs_create_file(name, 0644, smb349_chg->dent, |
| (void *) reg, ®_fops); |
| if (IS_ERR(file)) { |
| pr_err("debugfs_create_file %s failed.\n", name); |
| return -EFAULT; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static void remove_debugfs_entries(struct smb349_struct *smb349_chg) |
| { |
| if (smb349_chg->dent) |
| debugfs_remove_recursive(smb349_chg->dent); |
| } |
| |
| static int smb349_hwinit(struct smb349_struct *smb349_chg) |
| { |
| int ret; |
| |
| ret = smb349_write_reg(smb349_chg->client, CMD_A_REG, |
| VOLATILE_W_PERM_BIT); |
| if (ret) { |
| pr_err("failed to set VOLATILE_W_PERM_BIT rc=%d\n", ret); |
| return ret; |
| } |
| |
| ret = smb349_masked_write(smb349_chg->client, CHG_CTRL_REG, |
| CURR_TERM_END_CHG_BIT|AUTO_RECHG_DIS_BIT, |
| AUTO_RECHG_DIS_BIT); |
| if (ret) { |
| pr_err("failed to set CURR_TERM_END_CHG_BIT rc=%d\n", ret); |
| return ret; |
| } |
| |
| ret = smb349_chg_current_set(smb349_chg); |
| if (ret) { |
| pr_err("failed to set FAST_CHG_CURRENT rc=%d\n", ret); |
| return ret; |
| } |
| |
| ret = smb349_term_current_set(smb349_chg); |
| if (ret) { |
| pr_err("failed to set TERM_CURRENT rc=%d\n", ret); |
| return ret; |
| } |
| |
| ret = smb349_set_usb_5_1_mode(smb349_chg, 1); |
| if (ret) { |
| pr_err("failed to set USB_5_1_MODE rc=%d\n", ret); |
| return ret; |
| } |
| |
| ret = smb349_set_irq(smb349_chg); |
| if (ret) { |
| pr_err("failed to set smb349_set_irq rc=%d\n", ret); |
| return ret; |
| } |
| |
| ret = smb349_set_pin_control(smb349_chg, 0); |
| if (ret) { |
| pr_err("failed to set pin control rc=%d\n", ret); |
| return ret; |
| } |
| |
| ret = smb349_set_usbcs_control(smb349_chg, 0); |
| if (ret) { |
| pr_err("failed to set usbcs control rc=%d\n", ret); |
| return ret; |
| } |
| |
| /*FIXME - get from dt */ |
| /* Set Floating Voltage to 4.35v */ |
| ret = smb349_set_float_voltage(smb349_chg, 0x2d); |
| if (ret) { |
| pr_err("failed to set floating voltage rc=%d\n", ret); |
| return ret; |
| } |
| |
| ret = smb349_enbable_fast_chg_thresh(smb349_chg, 0); |
| if (ret) { |
| pr_err("failed to set floating voltage rc=%d\n", ret); |
| return ret; |
| } |
| |
| ret = smb349_masked_write(smb349_chg->client, THERM_CTRL_A_REG, |
| BOOST_BACK_PREVENTION_BIT, 0); |
| if (ret) { |
| pr_err("failed to set boost_back_prevnetion bit rc=%d\n", ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int smb349_init_ac_psy(struct smb349_struct *smb349_chg) |
| { |
| int ret; |
| |
| smb349_chg->ac_psy.name = "ac"; |
| smb349_chg->ac_psy.type = POWER_SUPPLY_TYPE_MAINS; |
| smb349_chg->ac_psy.supplied_to = pm_power_supplied_to; |
| smb349_chg->ac_psy.num_supplicants = ARRAY_SIZE(pm_power_supplied_to); |
| smb349_chg->ac_psy.properties = pm_power_props; |
| smb349_chg->ac_psy.num_properties = ARRAY_SIZE(pm_power_props); |
| smb349_chg->ac_psy.get_property = pm_power_get_property; |
| smb349_chg->ac_psy.set_property = pm_power_set_property; |
| smb349_chg->ac_psy.external_power_changed = |
| pm_external_power_changed; |
| |
| ret = power_supply_register(&smb349_chg->client->dev, |
| &smb349_chg->ac_psy); |
| if (ret) { |
| pr_err("failed to register power_supply. ret=%d.\n", ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int smb349_parse_dt(struct device_node *dev_node, |
| struct smb349_struct *smb349_chg) |
| { |
| int ret = 0; |
| |
| smb349_chg->stat_gpio = |
| of_get_named_gpio(dev_node, "summit,stat-gpio", 0); |
| if (smb349_chg->stat_gpio < 0) { |
| pr_err("failed to get stat-gpio.\n"); |
| ret = smb349_chg->stat_gpio; |
| goto out; |
| } |
| |
| ret = of_property_read_u32(dev_node, "summit,chg-current-ma", |
| &(smb349_chg->chg_current_ma)); |
| if (ret) { |
| pr_err("failed to get chg_current_ma.\n"); |
| goto out; |
| } |
| |
| ret = of_property_read_u32(dev_node, "summit,term-current-ma", |
| &(smb349_chg->term_current_ma)); |
| if (ret) { |
| pr_err("failed to get term_current_ma.\n"); |
| goto out; |
| } |
| |
| smb349_chg->wlc_support = |
| of_property_read_bool(dev_node, |
| "summit,wlc-support"); |
| |
| smb349_chg->ext_ovp_otg_ctrl = |
| of_property_read_bool(dev_node, |
| "summit,ext-ovp-otg-ctrl"); |
| if (smb349_chg->ext_ovp_otg_ctrl) { |
| smb349_chg->otg_en_gpio = |
| of_get_named_gpio(dev_node, "summit,otg-en-gpio", 0); |
| if (smb349_chg->otg_en_gpio < 0) { |
| pr_err("Unable to get named gpio for otg_en_gpio.\n"); |
| return smb349_chg->otg_en_gpio; |
| } |
| } |
| |
| pr_info("chg_i_ma = %d term_i_ma = %d\n", |
| smb349_chg->chg_current_ma, |
| smb349_chg->term_current_ma); |
| out: |
| return ret; |
| } |
| |
| static int smb349_probe(struct i2c_client *client, |
| const struct i2c_device_id *id) |
| { |
| const struct smb349_platform_data *pdata; |
| struct device_node *dev_node = client->dev.of_node; |
| struct smb349_struct *smb349_chg; |
| int ret = 0; |
| |
| if (!i2c_check_functionality(client->adapter, |
| I2C_FUNC_SMBUS_BYTE_DATA)) { |
| pr_err("smb349 i2c func fail.\n"); |
| return -EIO; |
| } |
| |
| smb349_chg = kzalloc(sizeof(*smb349_chg), GFP_KERNEL); |
| if (!smb349_chg) { |
| pr_err("smb349 alloc fail.\n"); |
| return -ENOMEM; |
| } |
| |
| smb349_chg->client = client; |
| |
| smb349_chg->usb_psy = power_supply_get_by_name("usb"); |
| if (!smb349_chg->usb_psy) { |
| pr_err("usb supply not found deferring probe\n"); |
| ret = -EPROBE_DEFER; |
| goto err_stat_gpio; |
| } |
| |
| if (dev_node) { |
| ret = smb349_parse_dt(dev_node, smb349_chg); |
| if (ret) { |
| pr_err("failed to parse dt\n"); |
| goto err_stat_gpio; |
| } |
| |
| } else { |
| pdata = client->dev.platform_data; |
| |
| if (pdata == NULL) { |
| pr_err("smb349 no platform data.\n"); |
| goto err_stat_gpio; |
| } |
| |
| smb349_chg->stat_gpio = pdata->stat_gpio; |
| smb349_chg->chg_current_ma = pdata->chg_current_ma; |
| smb349_chg->term_current_ma = pdata->term_current_ma; |
| smb349_chg->wlc_support = pdata->wlc_support; |
| smb349_chg->ext_ovp_otg_ctrl = pdata->ext_ovp_otg_ctrl; |
| if (smb349_chg->ext_ovp_otg_ctrl) |
| smb349_chg->otg_en_gpio = pdata->otg_en_gpio; |
| |
| } |
| |
| if (smb349_chg->wlc_support) { |
| smb349_chg->wlc_psy = power_supply_get_by_name("wireless"); |
| if (!smb349_chg->wlc_psy) { |
| pr_err("wireless supply not found deferring probe\n"); |
| ret = -EPROBE_DEFER; |
| goto err_stat_gpio; |
| } |
| } |
| |
| if (smb349_chg->stat_gpio) { |
| ret = gpio_request(smb349_chg->stat_gpio, "smb349_stat"); |
| if (ret) { |
| pr_err("stat_gpio gpio_request failed for %d ret=%d\n", |
| smb349_chg->stat_gpio, ret); |
| goto err_stat_gpio; |
| } |
| smb349_chg->irq = gpio_to_irq(smb349_chg->stat_gpio); |
| } |
| |
| if (smb349_chg->otg_en_gpio) { |
| ret = gpio_request_one(smb349_chg->otg_en_gpio, |
| GPIOF_OUT_INIT_LOW, "otg_en"); |
| if (ret) { |
| pr_err("otg_en_gpio request failed for %d ret=%d\n", |
| smb349_chg->otg_en_gpio, ret); |
| goto err_otg_en_gpio; |
| } |
| } |
| |
| i2c_set_clientdata(client, smb349_chg); |
| |
| ret = smb349_hwinit(smb349_chg); |
| if (ret) { |
| pr_err("smb349_hwinit failed. ret=%d\n", ret); |
| goto err_hw_init; |
| } |
| |
| wake_lock_init(&smb349_chg->chg_wake_lock, |
| WAKE_LOCK_SUSPEND, SMB349_NAME); |
| wake_lock_init(&smb349_chg->uevent_wake_lock, |
| WAKE_LOCK_SUSPEND, "smb349_chg_uevent"); |
| |
| ret = smb349_init_ac_psy(smb349_chg); |
| if (ret) { |
| pr_err("smb349_init_ac_psy failed. ret=%d\n", ret); |
| goto err_reg_psy; |
| } |
| |
| the_smb349_chg = smb349_chg; |
| |
| ret = smb349_create_debugfs_entries(smb349_chg); |
| if (ret) { |
| pr_err("smb349_create_debugfs_entries failed.ret=%d\n", ret); |
| goto err_debugfs; |
| } |
| |
| INIT_DELAYED_WORK(&smb349_chg->irq_work, smb349_irq_worker); |
| |
| if (smb349_chg->irq) { |
| ret = request_irq(smb349_chg->irq, smb349_irq, |
| IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, |
| "smb349_irq", smb349_chg); |
| if (ret) { |
| pr_err("request_irq %d failed.ret=%d\n", smb349_chg->irq, ret); |
| goto err_irq; |
| } |
| |
| enable_irq_wake(smb349_chg->irq); |
| } |
| |
| smb349_enable_charging(smb349_chg, true); |
| |
| pr_info("probe done\n"); |
| return 0; |
| |
| err_irq: |
| err_debugfs: |
| power_supply_unregister(&smb349_chg->ac_psy); |
| remove_debugfs_entries(smb349_chg); |
| err_reg_psy: |
| err_hw_init: |
| if (smb349_chg->otg_en_gpio) |
| gpio_free(smb349_chg->otg_en_gpio); |
| err_otg_en_gpio: |
| if (smb349_chg->stat_gpio) |
| gpio_free(smb349_chg->stat_gpio); |
| err_stat_gpio: |
| kfree(smb349_chg); |
| pr_info("fail to probe\n"); |
| return ret; |
| } |
| |
| static int smb349_remove(struct i2c_client *client) |
| { |
| struct smb349_struct *smb349_chg = i2c_get_clientdata(client); |
| |
| power_supply_unregister(&smb349_chg->ac_psy); |
| if (smb349_chg->stat_gpio) |
| gpio_free(smb349_chg->stat_gpio); |
| if (smb349_chg->otg_en_gpio) |
| gpio_free(smb349_chg->otg_en_gpio); |
| if (smb349_chg->irq) |
| free_irq(smb349_chg->irq, smb349_chg); |
| remove_debugfs_entries(smb349_chg); |
| |
| wake_lock_destroy(&smb349_chg->uevent_wake_lock); |
| |
| kfree(smb349_chg); |
| return 0; |
| } |
| |
| static const struct i2c_device_id smb349_id[] = { |
| {SMB349_NAME, 0}, |
| {}, |
| }; |
| MODULE_DEVICE_TABLE(i2c, smb349_id); |
| |
| static const struct of_device_id smb349_match[] = { |
| { .compatible = "summit,smb349-charger", }, |
| { }, |
| }; |
| |
| static struct i2c_driver smb349_driver = { |
| .driver = { |
| .name = SMB349_NAME, |
| .owner = THIS_MODULE, |
| .of_match_table = of_match_ptr(smb349_match), |
| }, |
| .probe = smb349_probe, |
| .remove = smb349_remove, |
| .id_table = smb349_id, |
| }; |
| |
| static int __init smb349_init(void) |
| { |
| return i2c_add_driver(&smb349_driver); |
| } |
| module_init(smb349_init); |
| |
| static void __exit smb349_exit(void) |
| { |
| return i2c_del_driver(&smb349_driver); |
| } |
| module_exit(smb349_exit); |
| |
| MODULE_DESCRIPTION("Driver for SMB349 charger chip"); |
| MODULE_LICENSE("GPL v2"); |
| MODULE_ALIAS("i2c:" SMB349_NAME); |