| /* Copyright (c) 2012-2013 The Linux Foundation. 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/power_supply.h> |
| #include <linux/i2c/smb350.h> |
| #include <linux/bitops.h> |
| #include <linux/of.h> |
| #include <linux/of_device.h> |
| #include <linux/of_gpio.h> |
| #include <linux/printk.h> |
| |
| /* Register definitions */ |
| #define CHG_CURRENT_REG 0x00 /* Non-Volatile + mirror */ |
| #define CHG_OTHER_CURRENT_REG 0x01 /* Non-Volatile + mirror */ |
| #define VAR_FUNC_REG 0x02 /* Non-Volatile + mirror */ |
| #define FLOAT_VOLTAGE_REG 0x03 /* Non-Volatile + mirror */ |
| #define CHG_CTRL_REG 0x04 /* Non-Volatile + mirror */ |
| #define STAT_TIMER_REG 0x05 /* Non-Volatile + mirror */ |
| #define PIN_ENABLE_CTRL_REG 0x06 /* Non-Volatile + mirror */ |
| #define THERM_CTRL_A_REG 0x07 /* Non-Volatile + mirror */ |
| #define SYSOK_USB3_SELECT_REG 0x08 /* Non-Volatile + mirror */ |
| #define CTRL_FUNCTIONS_REG 0x09 /* Non-Volatile + mirror */ |
| #define OTG_TLIM_THERM_CNTRL_REG 0x0A /* Non-Volatile + mirror */ |
| #define TEMP_MONITOR_REG 0x0B /* Non-Volatile + mirror */ |
| #define FAULT_IRQ_REG 0x0C /* Non-Volatile */ |
| #define IRQ_ENABLE_REG 0x0D /* Non-Volatile */ |
| #define SYSOK_REG 0x0E /* Non-Volatile + mirror */ |
| |
| #define AUTO_INPUT_VOLT_DETECT_REG 0x10 /* Non-Volatile Read-Only */ |
| #define STATUS_IRQ_REG 0x11 /* Non-Volatile Read-Only */ |
| #define I2C_SLAVE_ADDR_REG 0x12 /* Non-Volatile Read-Only */ |
| |
| #define CMD_A_REG 0x30 /* Volatile Read-Write */ |
| #define CMD_B_REG 0x31 /* Volatile Read-Write */ |
| #define CMD_C_REG 0x33 /* Volatile Read-Write */ |
| |
| #define HW_VERSION_REG 0x34 /* Volatile Read-Only */ |
| |
| #define IRQ_STATUS_A_REG 0x35 /* Volatile Read-Only */ |
| #define IRQ_STATUS_B_REG 0x36 /* Volatile Read-Only */ |
| #define IRQ_STATUS_C_REG 0x37 /* Volatile Read-Only */ |
| #define IRQ_STATUS_D_REG 0x38 /* Volatile Read-Only */ |
| #define IRQ_STATUS_E_REG 0x39 /* Volatile Read-Only */ |
| #define IRQ_STATUS_F_REG 0x3A /* Volatile Read-Only */ |
| |
| #define STATUS_A_REG 0x3B /* Volatile Read-Only */ |
| #define STATUS_B_REG 0x3D /* Volatile Read-Only */ |
| /* Note: STATUS_C_REG was removed from SMB349 to SMB350 */ |
| #define STATUS_D_REG 0x3E /* Volatile Read-Only */ |
| #define STATUS_E_REG 0x3F /* Volatile Read-Only */ |
| |
| #define IRQ_STATUS_NUM (IRQ_STATUS_F_REG - IRQ_STATUS_A_REG + 1) |
| |
| /* Status bits and masks */ |
| #define SMB350_MASK(BITS, POS) ((u8)(((1 << BITS) - 1) << POS)) |
| #define FAST_CHG_CURRENT_MASK SMB350_MASK(4, 4) |
| |
| #define SMB350_FAST_CHG_MIN_MA 1000 |
| #define SMB350_FAST_CHG_STEP_MA 200 |
| #define SMB350_FAST_CHG_MAX_MA 3600 |
| |
| #define TERM_CURRENT_MASK SMB350_MASK(3, 2) |
| |
| #define SMB350_TERM_CUR_MIN_MA 200 |
| #define SMB350_TERM_CUR_STEP_MA 100 |
| #define SMB350_TERM_CUR_MAX_MA 700 |
| |
| #define CMD_A_VOLATILE_WR_PERM BIT(7) |
| #define CHG_CTRL_CURR_TERM_END_CHG BIT(6) |
| |
| enum smb350_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, |
| }; |
| |
| static const char * const smb350_chg_status[] = { |
| "none", |
| "pre-charge", |
| "fast-charge", |
| "taper-charge" |
| }; |
| |
| struct smb350_device { |
| /* setup */ |
| int chg_current_ma; |
| int term_current_ma; |
| int chg_en_n_gpio; |
| int chg_susp_n_gpio; |
| int stat_gpio; |
| int irq; |
| /* internal */ |
| enum smb350_chg_status chg_status; |
| struct i2c_client *client; |
| struct delayed_work irq_work; |
| struct dentry *dent; |
| struct wake_lock chg_wake_lock; |
| struct power_supply dc_psy; |
| }; |
| |
| static struct smb350_device *smb350_dev; |
| |
| struct debug_reg { |
| char *name; |
| u8 reg; |
| }; |
| |
| #define SMB350_DEBUG_REG(x) {#x, x##_REG} |
| |
| static struct debug_reg smb350_debug_regs[] = { |
| SMB350_DEBUG_REG(CHG_CURRENT), |
| SMB350_DEBUG_REG(CHG_OTHER_CURRENT), |
| SMB350_DEBUG_REG(VAR_FUNC), |
| SMB350_DEBUG_REG(FLOAT_VOLTAGE), |
| SMB350_DEBUG_REG(CHG_CTRL), |
| SMB350_DEBUG_REG(STAT_TIMER), |
| SMB350_DEBUG_REG(PIN_ENABLE_CTRL), |
| SMB350_DEBUG_REG(THERM_CTRL_A), |
| SMB350_DEBUG_REG(SYSOK_USB3_SELECT), |
| SMB350_DEBUG_REG(CTRL_FUNCTIONS), |
| SMB350_DEBUG_REG(OTG_TLIM_THERM_CNTRL), |
| SMB350_DEBUG_REG(TEMP_MONITOR), |
| SMB350_DEBUG_REG(FAULT_IRQ), |
| SMB350_DEBUG_REG(IRQ_ENABLE), |
| SMB350_DEBUG_REG(SYSOK), |
| SMB350_DEBUG_REG(AUTO_INPUT_VOLT_DETECT), |
| SMB350_DEBUG_REG(STATUS_IRQ), |
| SMB350_DEBUG_REG(I2C_SLAVE_ADDR), |
| SMB350_DEBUG_REG(CMD_A), |
| SMB350_DEBUG_REG(CMD_B), |
| SMB350_DEBUG_REG(CMD_C), |
| SMB350_DEBUG_REG(HW_VERSION), |
| SMB350_DEBUG_REG(IRQ_STATUS_A), |
| SMB350_DEBUG_REG(IRQ_STATUS_B), |
| SMB350_DEBUG_REG(IRQ_STATUS_C), |
| SMB350_DEBUG_REG(IRQ_STATUS_D), |
| SMB350_DEBUG_REG(IRQ_STATUS_E), |
| SMB350_DEBUG_REG(IRQ_STATUS_F), |
| SMB350_DEBUG_REG(STATUS_A), |
| SMB350_DEBUG_REG(STATUS_B), |
| SMB350_DEBUG_REG(STATUS_D), |
| SMB350_DEBUG_REG(STATUS_E), |
| }; |
| |
| /* |
| * Read 8-bit register value. return negative value on error. |
| */ |
| static int smb350_read_reg(struct i2c_client *client, u8 reg) |
| { |
| int val; |
| |
| val = i2c_smbus_read_byte_data(client, reg); |
| if (val < 0) |
| pr_err("i2c read fail. reg=0x%x.ret=%d.\n", reg, val); |
| else |
| pr_debug("reg=0x%02X.val=0x%02X.\n", reg , val); |
| |
| return val; |
| } |
| |
| /* |
| * Write 8-bit register value. return negative value on error. |
| */ |
| static int smb350_write_reg(struct i2c_client *client, u8 reg, u8 val) |
| { |
| int ret; |
| |
| ret = i2c_smbus_write_byte_data(client, reg, val); |
| if (ret < 0) |
| pr_err("i2c read fail. reg=0x%x.val=0x%x.ret=%d.\n", |
| reg, val, ret); |
| else |
| pr_debug("reg=0x%02X.val=0x%02X.\n", reg , val); |
| |
| return ret; |
| } |
| |
| static int smb350_masked_write(struct i2c_client *client, int reg, u8 mask, |
| u8 val) |
| { |
| int ret; |
| int temp; |
| int shift = find_first_bit((unsigned long *) &mask, 8); |
| |
| temp = smb350_read_reg(client, reg); |
| if (temp < 0) |
| return temp; |
| |
| temp &= ~mask; |
| temp |= (val << shift) & mask; |
| ret = smb350_write_reg(client, reg, temp); |
| |
| return ret; |
| } |
| |
| static bool smb350_is_dc_present(struct i2c_client *client) |
| { |
| u16 irq_status_f = smb350_read_reg(client, IRQ_STATUS_F_REG); |
| bool power_ok = irq_status_f & 0x01; |
| |
| /* Power-ok , IRQ_STATUS_F_REG bit#0 */ |
| if (power_ok) |
| pr_debug("DC is present.\n"); |
| else |
| pr_debug("DC is missing.\n"); |
| |
| return power_ok; |
| } |
| |
| static bool smb350_is_charger_present(struct i2c_client *client) |
| { |
| int val; |
| |
| /* Normally the device is non-removable and embedded on the board. |
| * Verify that charger is present by getting I2C response. |
| */ |
| val = smb350_read_reg(client, STATUS_B_REG); |
| if (val < 0) |
| return false; |
| |
| return true; |
| } |
| |
| static int smb350_get_prop_charge_type(struct smb350_device *dev) |
| { |
| int status_b; |
| enum smb350_chg_status status; |
| int chg_type = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; |
| bool chg_enabled; |
| bool charger_err; |
| struct i2c_client *client = dev->client; |
| |
| status_b = smb350_read_reg(client, STATUS_B_REG); |
| if (status_b < 0) { |
| pr_err("failed to read STATUS_B_REG.\n"); |
| return POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; |
| } |
| |
| chg_enabled = (bool) (status_b & 0x01); |
| charger_err = (bool) (status_b & (1<<6)); |
| |
| if (!chg_enabled) { |
| pr_warn("Charging not enabled.\n"); |
| /* release the wake-lock when DC power removed */ |
| if (wake_lock_active(&dev->chg_wake_lock)) |
| wake_unlock(&dev->chg_wake_lock); |
| return POWER_SUPPLY_CHARGE_TYPE_NONE; |
| } |
| |
| if (charger_err) { |
| pr_warn("Charger error detected.\n"); |
| return POWER_SUPPLY_CHARGE_TYPE_NONE; |
| } |
| |
| status = (status_b >> 1) & 0x3; |
| |
| if (status == SMB_CHG_STATUS_NONE) |
| chg_type = POWER_SUPPLY_CHARGE_TYPE_NONE; |
| else if (status == SMB_CHG_STATUS_FAST_CHARGE) /* constant current */ |
| chg_type = POWER_SUPPLY_CHARGE_TYPE_FAST; |
| else if (status == SMB_CHG_STATUS_TAPER_CHARGE) /* constant voltage */ |
| chg_type = POWER_SUPPLY_CHARGE_TYPE_FAST; |
| else if (status == SMB_CHG_STATUS_PRE_CHARGE) |
| chg_type = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; |
| |
| pr_debug("smb-chg-status=%d=%s.\n", status, smb350_chg_status[status]); |
| |
| if (dev->chg_status != status) { /* Status changed */ |
| if (status == SMB_CHG_STATUS_NONE) { |
| pr_debug("Charging stopped.\n"); |
| wake_unlock(&dev->chg_wake_lock); |
| } else { |
| pr_debug("Charging started.\n"); |
| wake_lock(&dev->chg_wake_lock); |
| } |
| } |
| |
| dev->chg_status = status; |
| |
| return chg_type; |
| } |
| |
| static void smb350_enable_charging(struct smb350_device *dev, bool enable) |
| { |
| int val = !enable; /* active low */ |
| |
| pr_debug("enable=%d.\n", enable); |
| |
| gpio_set_value_cansleep(dev->chg_en_n_gpio, val); |
| } |
| |
| /* When the status bit of a certain condition is read, |
| * the corresponding IRQ signal is cleared. |
| */ |
| static int smb350_clear_irq(struct i2c_client *client) |
| { |
| int ret; |
| |
| ret = smb350_read_reg(client, IRQ_STATUS_A_REG); |
| if (ret < 0) |
| return ret; |
| ret = smb350_read_reg(client, IRQ_STATUS_B_REG); |
| if (ret < 0) |
| return ret; |
| ret = smb350_read_reg(client, IRQ_STATUS_C_REG); |
| if (ret < 0) |
| return ret; |
| ret = smb350_read_reg(client, IRQ_STATUS_D_REG); |
| if (ret < 0) |
| return ret; |
| ret = smb350_read_reg(client, IRQ_STATUS_E_REG); |
| if (ret < 0) |
| return ret; |
| ret = smb350_read_reg(client, IRQ_STATUS_F_REG); |
| if (ret < 0) |
| return ret; |
| |
| return 0; |
| } |
| |
| /* |
| * Do the IRQ work from a thread context rather than interrupt context. |
| * Read status registers to clear interrupt source. |
| * Notify the power-supply driver about change detected. |
| * Relevant events for start/stop charging: |
| * 1. DC insert/remove |
| * 2. End-Of-Charging |
| * 3. Battery insert/remove |
| * 4. Temperture too hot/cold |
| * 5. Charging timeout expired. |
| */ |
| static void smb350_irq_worker(struct work_struct *work) |
| { |
| int ret = 0; |
| struct smb350_device *dev = |
| container_of(work, struct smb350_device, irq_work.work); |
| |
| ret = smb350_clear_irq(dev->client); |
| if (ret == 0) { /* Cleared ok */ |
| /* Notify Battery-psy about status changed */ |
| pr_debug("Notify power_supply_changed.\n"); |
| power_supply_changed(&dev->dc_psy); |
| } |
| } |
| |
| /* |
| * The STAT pin is low when charging and high when not charging. |
| * When the smb350 start/stop charging the STAT pin triggers an interrupt. |
| * Interrupt is triggered on both rising or falling edge. |
| */ |
| static irqreturn_t smb350_irq(int irq, void *dev_id) |
| { |
| struct smb350_device *dev = dev_id; |
| |
| pr_debug("\n"); |
| |
| /* I2C transfers API should not run in interrupt context */ |
| schedule_delayed_work(&dev->irq_work, msecs_to_jiffies(100)); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static enum power_supply_property pm_power_props[] = { |
| /* real time */ |
| POWER_SUPPLY_PROP_PRESENT, |
| POWER_SUPPLY_PROP_ONLINE, |
| POWER_SUPPLY_PROP_CHARGE_TYPE, |
| /* fixed */ |
| POWER_SUPPLY_PROP_MANUFACTURER, |
| POWER_SUPPLY_PROP_MODEL_NAME, |
| POWER_SUPPLY_PROP_CURRENT_MAX, |
| }; |
| |
| static char *pm_power_supplied_to[] = { |
| "battery", |
| }; |
| |
| static int smb350_get_property(struct power_supply *psy, |
| enum power_supply_property psp, |
| union power_supply_propval *val) |
| { |
| int ret = 0; |
| struct smb350_device *dev = container_of(psy, |
| struct smb350_device, |
| dc_psy); |
| struct i2c_client *client = dev->client; |
| |
| switch (psp) { |
| case POWER_SUPPLY_PROP_PRESENT: |
| val->intval = smb350_is_charger_present(client); |
| break; |
| case POWER_SUPPLY_PROP_ONLINE: |
| val->intval = smb350_is_dc_present(client); |
| break; |
| case POWER_SUPPLY_PROP_CHARGE_TYPE: |
| val->intval = smb350_get_prop_charge_type(dev); |
| break; |
| case POWER_SUPPLY_PROP_MODEL_NAME: |
| val->strval = SMB350_NAME; |
| break; |
| case POWER_SUPPLY_PROP_MANUFACTURER: |
| val->strval = "Summit Microelectronics"; |
| break; |
| case POWER_SUPPLY_PROP_CURRENT_MAX: |
| val->intval = dev->chg_current_ma; |
| break; |
| default: |
| pr_err("Invalid prop = %d.\n", psp); |
| ret = -EINVAL; |
| break; |
| } |
| |
| return ret; |
| } |
| |
| static int smb350_set_property(struct power_supply *psy, |
| enum power_supply_property psp, |
| const union power_supply_propval *val) |
| { |
| int ret = 0; |
| struct smb350_device *dev = |
| container_of(psy, struct smb350_device, dc_psy); |
| |
| switch (psp) { |
| /* |
| * Allow a smart battery to Start/Stop charging. |
| * i.e. when End-Of-Charging detected. |
| * The SMB350 can be configured to terminate charging |
| * when charge-current reaching Termination-Current. |
| */ |
| case POWER_SUPPLY_PROP_ONLINE: |
| smb350_enable_charging(dev, val->intval); |
| break; |
| default: |
| pr_err("Invalid prop = %d.\n", psp); |
| ret = -EINVAL; |
| } |
| |
| return ret; |
| } |
| |
| static int smb350_set_chg_current(struct i2c_client *client, int current_ma) |
| { |
| int ret; |
| u8 temp; |
| |
| if ((current_ma < SMB350_FAST_CHG_MIN_MA) || |
| (current_ma > SMB350_FAST_CHG_MAX_MA)) { |
| pr_err("invalid current %d mA.\n", current_ma); |
| return -EINVAL; |
| } |
| |
| temp = (current_ma - SMB350_FAST_CHG_MIN_MA) / SMB350_FAST_CHG_STEP_MA; |
| |
| pr_debug("fast-chg-current=%d mA setting %02x\n", current_ma, temp); |
| |
| ret = smb350_masked_write(client, CHG_CURRENT_REG, |
| FAST_CHG_CURRENT_MASK, temp); |
| |
| return ret; |
| } |
| |
| static int smb350_set_term_current(struct i2c_client *client, int current_ma) |
| { |
| int ret; |
| u8 temp; |
| |
| if ((current_ma < SMB350_TERM_CUR_MIN_MA) || |
| (current_ma > SMB350_TERM_CUR_MAX_MA)) { |
| pr_err("invalid current %d mA to set\n", current_ma); |
| return -EINVAL; |
| } |
| |
| temp = (current_ma - SMB350_TERM_CUR_MIN_MA) / SMB350_TERM_CUR_STEP_MA; |
| |
| pr_debug("term-current=%d mA setting %02x\n", current_ma, temp); |
| |
| ret = smb350_masked_write(client, CHG_OTHER_CURRENT_REG, |
| TERM_CURRENT_MASK, temp); |
| |
| return ret; |
| } |
| |
| static int smb350_set_reg(void *data, u64 val) |
| { |
| u32 addr = (u32) data; |
| int ret; |
| struct i2c_client *client = smb350_dev->client; |
| |
| ret = smb350_write_reg(client, addr, (u8) val); |
| |
| return ret; |
| } |
| |
| static int smb350_get_reg(void *data, u64 *val) |
| { |
| u32 addr = (u32) data; |
| int ret; |
| struct i2c_client *client = smb350_dev->client; |
| |
| ret = smb350_read_reg(client, addr); |
| if (ret < 0) |
| return ret; |
| |
| *val = ret; |
| |
| return 0; |
| } |
| |
| DEFINE_SIMPLE_ATTRIBUTE(reg_fops, smb350_get_reg, smb350_set_reg, "0x%02llx\n"); |
| |
| static int smb350_create_debugfs_entries(struct smb350_device *dev) |
| { |
| int i; |
| dev->dent = debugfs_create_dir(SMB350_NAME, NULL); |
| if (IS_ERR(dev->dent)) { |
| pr_err("smb350 driver couldn't create debugfs dir\n"); |
| return -EFAULT; |
| } |
| |
| for (i = 0 ; i < ARRAY_SIZE(smb350_debug_regs) ; i++) { |
| char *name = smb350_debug_regs[i].name; |
| u32 reg = smb350_debug_regs[i].reg; |
| struct dentry *file; |
| |
| file = debugfs_create_file(name, 0644, dev->dent, |
| (void *) reg, ®_fops); |
| if (IS_ERR(file)) { |
| pr_err("debugfs_create_file %s failed.\n", name); |
| return -EFAULT; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int smb350_set_volatile_params(struct smb350_device *dev) |
| { |
| int ret; |
| struct i2c_client *client = dev->client; |
| |
| pr_debug("\n"); |
| |
| ret = smb350_write_reg(client, CMD_A_REG, CMD_A_VOLATILE_WR_PERM); |
| if (ret) { |
| pr_err("Failed to set VOLATILE_WR_PERM ret=%d\n", ret); |
| return ret; |
| } |
| |
| /* Disable SMB350 pulse-IRQ mechanism, |
| * we use interrupts based on charging-status-transition |
| */ |
| /* Enable STATUS output (regardless of IRQ-pulses) */ |
| smb350_masked_write(client, CMD_A_REG, BIT(0), 0); |
| |
| /* Disable LED blinking - avoid periodic irq */ |
| smb350_masked_write(client, PIN_ENABLE_CTRL_REG, BIT(7), 0); |
| |
| /* Disable Failure SMB-IRQ */ |
| ret = smb350_write_reg(client, FAULT_IRQ_REG, 0x00); |
| if (ret) { |
| pr_err("Failed to set FAULT_IRQ_REG ret=%d\n", ret); |
| return ret; |
| } |
| |
| /* Disable Event IRQ */ |
| ret = smb350_write_reg(client, IRQ_ENABLE_REG, 0x00); |
| if (ret) { |
| pr_err("Failed to set IRQ_ENABLE_REG ret=%d\n", ret); |
| return ret; |
| } |
| |
| /* Enable charging/not-charging status output via STAT pin */ |
| smb350_masked_write(client, STAT_TIMER_REG, BIT(5), 0); |
| |
| /* Disable Automatic Recharge */ |
| smb350_masked_write(client, CHG_CTRL_REG, BIT(7), 1); |
| |
| /* Set fast-charge current */ |
| ret = smb350_set_chg_current(client, dev->chg_current_ma); |
| if (ret) { |
| pr_err("Failed to set FAST_CHG_CURRENT ret=%d\n", ret); |
| return ret; |
| } |
| |
| if (dev->term_current_ma > 0) { |
| /* Enable Current Termination */ |
| smb350_masked_write(client, CHG_CTRL_REG, BIT(6), 0); |
| |
| /* Set Termination current */ |
| smb350_set_term_current(client, dev->term_current_ma); |
| } else { |
| /* Disable Current Termination */ |
| smb350_masked_write(client, CHG_CTRL_REG, BIT(6), 1); |
| } |
| |
| return 0; |
| } |
| |
| static int __devinit smb350_register_psy(struct smb350_device *dev) |
| { |
| int ret; |
| |
| dev->dc_psy.name = "dc"; |
| dev->dc_psy.type = POWER_SUPPLY_TYPE_MAINS; |
| dev->dc_psy.supplied_to = pm_power_supplied_to; |
| dev->dc_psy.num_supplicants = ARRAY_SIZE(pm_power_supplied_to); |
| dev->dc_psy.properties = pm_power_props; |
| dev->dc_psy.num_properties = ARRAY_SIZE(pm_power_props); |
| dev->dc_psy.get_property = smb350_get_property; |
| dev->dc_psy.set_property = smb350_set_property; |
| |
| ret = power_supply_register(&dev->client->dev, |
| &dev->dc_psy); |
| if (ret) { |
| pr_err("failed to register power_supply. ret=%d.\n", ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int __devinit smb350_probe(struct i2c_client *client, |
| const struct i2c_device_id *id) |
| { |
| int ret = 0; |
| const struct smb350_platform_data *pdata; |
| struct device_node *dev_node = client->dev.of_node; |
| struct smb350_device *dev; |
| u8 version; |
| |
| /* STAT pin change on start/stop charging */ |
| u32 irq_flags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING; |
| |
| if (!i2c_check_functionality(client->adapter, |
| I2C_FUNC_SMBUS_BYTE_DATA)) { |
| pr_err("i2c func fail.\n"); |
| return -EIO; |
| } |
| |
| dev = kzalloc(sizeof(*dev), GFP_KERNEL); |
| if (!dev) { |
| pr_err("alloc fail.\n"); |
| return -ENOMEM; |
| } |
| |
| smb350_dev = dev; |
| dev->client = client; |
| |
| if (dev_node) { |
| dev->chg_en_n_gpio = |
| of_get_named_gpio(dev_node, "summit,chg-en-n-gpio", 0); |
| pr_debug("chg_en_n_gpio = %d.\n", dev->chg_en_n_gpio); |
| |
| dev->chg_susp_n_gpio = |
| of_get_named_gpio(dev_node, |
| "summit,chg-susp-n-gpio", 0); |
| pr_debug("chg_susp_n_gpio = %d.\n", dev->chg_susp_n_gpio); |
| |
| dev->stat_gpio = |
| of_get_named_gpio(dev_node, "summit,stat-gpio", 0); |
| pr_debug("stat_gpio = %d.\n", dev->stat_gpio); |
| |
| ret = of_property_read_u32(dev_node, "summit,chg-current-ma", |
| &(dev->chg_current_ma)); |
| pr_debug("chg_current_ma = %d.\n", dev->chg_current_ma); |
| if (ret) { |
| pr_err("Unable to read chg_current.\n"); |
| return ret; |
| } |
| ret = of_property_read_u32(dev_node, "summit,term-current-ma", |
| &(dev->term_current_ma)); |
| pr_debug("term_current_ma = %d.\n", dev->term_current_ma); |
| if (ret) { |
| pr_err("Unable to read term_current_ma.\n"); |
| return ret; |
| } |
| } else { |
| pdata = client->dev.platform_data; |
| |
| if (pdata == NULL) { |
| pr_err("no platform data.\n"); |
| return -EINVAL; |
| } |
| |
| dev->chg_en_n_gpio = pdata->chg_en_n_gpio; |
| dev->chg_susp_n_gpio = pdata->chg_susp_n_gpio; |
| dev->stat_gpio = pdata->stat_gpio; |
| |
| dev->chg_current_ma = pdata->chg_current_ma; |
| dev->term_current_ma = pdata->term_current_ma; |
| } |
| |
| ret = gpio_request(dev->stat_gpio, "smb350_stat"); |
| if (ret) { |
| pr_err("gpio_request failed for %d ret=%d\n", |
| dev->stat_gpio, ret); |
| goto err_stat_gpio; |
| } |
| dev->irq = gpio_to_irq(dev->stat_gpio); |
| pr_debug("irq#=%d.\n", dev->irq); |
| |
| ret = gpio_request(dev->chg_susp_n_gpio, "smb350_suspend"); |
| if (ret) { |
| pr_err("gpio_request failed for %d ret=%d\n", |
| dev->chg_susp_n_gpio, ret); |
| goto err_susp_gpio; |
| } |
| |
| ret = gpio_request(dev->chg_en_n_gpio, "smb350_charger_enable"); |
| if (ret) { |
| pr_err("gpio_request failed for %d ret=%d\n", |
| dev->chg_en_n_gpio, ret); |
| goto err_en_gpio; |
| } |
| |
| i2c_set_clientdata(client, dev); |
| |
| /* Disable battery charging by default on power up. |
| * Battery charging is enabled by BMS or Battery-Gauge |
| * by using the set_property callback. |
| */ |
| smb350_enable_charging(dev, false); |
| msleep(100); |
| gpio_set_value_cansleep(dev->chg_susp_n_gpio, 1); /* Normal */ |
| msleep(100); /* Allow the device to exist shutdown */ |
| |
| /* I2C transaction allowed only after device exit suspend */ |
| ret = smb350_read_reg(client, I2C_SLAVE_ADDR_REG); |
| if ((ret>>1) != client->addr) { |
| pr_err("No device.\n"); |
| ret = -ENODEV; |
| goto err_no_dev; |
| } |
| |
| version = smb350_read_reg(client, HW_VERSION_REG); |
| version &= 0x0F; /* bits 0..3 */ |
| |
| ret = smb350_set_volatile_params(dev); |
| if (ret) |
| goto err_set_params; |
| |
| ret = smb350_register_psy(dev); |
| if (ret) |
| goto err_set_params; |
| |
| ret = smb350_create_debugfs_entries(dev); |
| if (ret) |
| goto err_debugfs; |
| |
| INIT_DELAYED_WORK(&dev->irq_work, smb350_irq_worker); |
| wake_lock_init(&dev->chg_wake_lock, |
| WAKE_LOCK_SUSPEND, SMB350_NAME); |
| |
| ret = request_irq(dev->irq, smb350_irq, irq_flags, |
| "smb350_irq", dev); |
| if (ret) { |
| pr_err("request_irq %d failed.ret=%d\n", dev->irq, ret); |
| goto err_irq; |
| } |
| |
| pr_info("HW Version = 0x%X.\n", version); |
| |
| return 0; |
| |
| err_irq: |
| err_debugfs: |
| if (dev->dent) |
| debugfs_remove_recursive(dev->dent); |
| err_no_dev: |
| err_set_params: |
| gpio_free(dev->chg_en_n_gpio); |
| err_en_gpio: |
| gpio_free(dev->chg_susp_n_gpio); |
| err_susp_gpio: |
| gpio_free(dev->stat_gpio); |
| err_stat_gpio: |
| kfree(smb350_dev); |
| smb350_dev = NULL; |
| |
| pr_info("FAIL.\n"); |
| |
| return ret; |
| } |
| |
| static int __devexit smb350_remove(struct i2c_client *client) |
| { |
| struct smb350_device *dev = i2c_get_clientdata(client); |
| |
| power_supply_unregister(&dev->dc_psy); |
| gpio_free(dev->chg_en_n_gpio); |
| gpio_free(dev->chg_susp_n_gpio); |
| if (dev->stat_gpio) |
| gpio_free(dev->stat_gpio); |
| if (dev->irq) |
| free_irq(dev->irq, dev); |
| if (dev->dent) |
| debugfs_remove_recursive(dev->dent); |
| kfree(smb350_dev); |
| smb350_dev = NULL; |
| |
| return 0; |
| } |
| |
| static const struct i2c_device_id smb350_id[] = { |
| {SMB350_NAME, 0}, |
| {}, |
| }; |
| MODULE_DEVICE_TABLE(i2c, smb350_id); |
| |
| static const struct of_device_id smb350_match[] = { |
| { .compatible = "summit,smb350-charger", }, |
| { }, |
| }; |
| |
| static struct i2c_driver smb350_driver = { |
| .driver = { |
| .name = SMB350_NAME, |
| .owner = THIS_MODULE, |
| .of_match_table = of_match_ptr(smb350_match), |
| }, |
| .probe = smb350_probe, |
| .remove = __devexit_p(smb350_remove), |
| .id_table = smb350_id, |
| }; |
| |
| static int __init smb350_init(void) |
| { |
| return i2c_add_driver(&smb350_driver); |
| } |
| module_init(smb350_init); |
| |
| static void __exit smb350_exit(void) |
| { |
| return i2c_del_driver(&smb350_driver); |
| } |
| module_exit(smb350_exit); |
| |
| MODULE_DESCRIPTION("Driver for SMB350 charger chip"); |
| MODULE_LICENSE("GPL v2"); |
| MODULE_ALIAS("i2c:" SMB350_NAME); |