| /* |
| * Copyright (C) 2014 Nest Labs |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License as |
| * published by the Free Software Foundation version 2. |
| * |
| * This program is distributed "as is" WITHOUT ANY WARRANTY of any |
| * kind, whether express or implied; without even the implied warranty |
| * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| */ |
| |
| #include <linux/delay.h> |
| #include <linux/err.h> |
| #include <linux/gpio.h> |
| #include <linux/i2c.h> |
| #include <linux/iio/consumer.h> |
| #include <linux/iio/types.h> |
| #include <linux/interrupt.h> |
| #include <linux/module.h> |
| #include <linux/of_gpio.h> |
| #include <linux/power_supply.h> |
| #include <linux/printk.h> |
| #include <linux/workqueue.h> |
| |
| #define WORK_DELAY_SECS (10 * 60) |
| |
| #define DCP_CURRENT_LIMIT 1500 |
| |
| #define REG1_READ_COUNT_MAX 10 /* Max loops reading reg1 */ |
| |
| #define REG1_ADDR 0x00 |
| |
| #define REG1_WD_EN 0x40 |
| |
| #define REG1_STATUS_MASK 0x30 |
| #define REG1_STATUS_READY 0x00 |
| #define REG1_STATUS_CHARGE_IN_PROGRESS 0x10 |
| #define REG1_STATUS_CHARGE_DONE 0x20 |
| #define REG1_STATUS_FAULT 0x30 |
| |
| #define REG1_FAULT_MASK 0x0f |
| #define REG1_FAULT_NORMAL 0x00 |
| #define REG1_FAULT_INPUT_OVP 0x01 |
| #define REG1_FAULT_INPUT_UVLO 0x02 |
| #define REG1_FAULT_SLEEP 0x03 |
| #define REG1_FAULT_BATTERY_TEMPERATURE_FAULT 0x04 |
| #define REG1_FAULT_BATTERY_OVP 0x05 |
| #define REG1_FAULT_THERMAL_SHUTDOWN 0x06 |
| #define REG1_FAULT_TIMER_FAULT 0x07 |
| #define REG1_FAULT_NO_BATTERY_CONNECTED 0x08 |
| #define REG1_FAULT_ISET_SHORT 0x09 |
| #define REG1_FAULT_INPUT_FAULT_AND_LDO_LOW 0x0a |
| |
| #define REG2_ADDR 0x01 |
| |
| #define REG2_RESET_MASK 0x80 |
| #define REG2_IIN_LIMIT_MASK 0x70 |
| #define REG2_IIN_LIMIT_100 0x00 |
| #define REG2_IIN_LIMIT_150 0x10 |
| #define REG2_IIN_LIMIT_500 0x20 |
| #define REG2_IIN_LIMIT_900 0x30 |
| #define REG2_IIN_LIMIT_1500 0x40 |
| #define REG2_IIN_LIMIT_2000 0x50 |
| #define REG2_IIN_LIMIT_EXT 0x60 |
| #define REG2_CE_MASK 0x02 |
| #define REG2_HZ_MODE_MASK 0x01 |
| |
| |
| #define REG3_ADDR 0x02 |
| |
| #define REG3_VBATREG_MASK 0xfc |
| #define REG3_VBATREG_MIN 3500000 |
| #define REG3_VBATREG_MAX 4440000 |
| #define REG3_VBATREG_STEP 20000 |
| #define REG3_VBATREG_SHIFT 2 |
| |
| #define REG3_USB_DET_MASK 0x03 |
| #define REG3_USB_DET_DCD 0x00 |
| #define REG3_USB_DET_CDP 0x01 |
| #define REG3_USB_DET_SDP 0x02 |
| #define REG3_USB_DET_NON_STANDARD 0x03 |
| |
| |
| #define REG4_ADDR 0x03 |
| |
| #define REG4_ICHG_MASK 0xf8 |
| #define REG4_ICHG_MIN 500000 |
| #define REG4_ICHG_MAX 2000000 |
| #define REG4_ICHG_STEP 50000 |
| #define REG4_ICHG_SHIFT 3 |
| |
| #define REG4_ITERM_MASK 0x07 |
| #define REG4_ITERM_MIN 50000 |
| #define REG4_ITERM_MAX 225000 |
| #define REG4_ITERM_STEP 25000 |
| #define REG4_ITERM_SHIFT 0 |
| |
| |
| #define REG5_ADDR 0x04 |
| |
| #define REG5_LOW_CHG 0x20 |
| |
| #define REG5_VINDPM_MASK 0x07 |
| #define REG5_VINDPM_MIN 4200000 |
| #define REG5_VINDPM_MAX 4760000 |
| #define REG5_VINDPM_STEP 80000 |
| #define REG5_VINDPM_SHIFT 0 |
| |
| |
| #define REG6_ADDR 0x05 |
| |
| #define REG6_SYSOFF_MASK 0x10 |
| |
| #define REG6_TMR_MASK 0x60 |
| #define REG6_TMR_MIN 0 |
| #define REG6_TMR_MAX 3 |
| #define REG6_TMR_STEP 1 |
| #define REG6_TMR_SHIFT 5 |
| |
| #define REG6_TS_EN_MASK 0x08 |
| |
| #define REG6_TS_STAT_MASK 0x07 |
| #define REG6_TS_STAT_NORMAL 0x00 |
| #define REG6_TS_STAT_TEMP_GT_THOT 0x01 |
| #define REG6_TS_STAT_TWARM_LT_TEMP 0x02 |
| #define REG6_TS_STAT_TCOLD_LT_TEMP 0x03 |
| #define REG6_TS_STAT_TEMP_LT_TCOLD 0x04 |
| #define REG6_TS_STAT_TFREEZE_LT_TEMP_LT_TCOLD 0x05 |
| #define REG6_TS_STAT_TEMP_LT_TFREEZE 0x06 |
| #define REG6_TS_STAT_TS_OPEN 0x07 |
| |
| #define REG7_ADDR 0x06 |
| |
| #define REG7_VOVP_MASK 0xe0 |
| #define REG7_VOVP_VAL0 6000000 |
| #define REG7_VOVP_VAL1 6500000 |
| #define REG7_VOVP_VAL2 7000000 |
| #define REG7_VOVP_VAL3 8000000 |
| #define REG7_VOVP_VAL4 9000000 |
| #define REG7_VOVP_VAL5 9500000 |
| #define REG7_VOVP_VAL6 10000000 |
| #define REG7_VOVP_VAL7 10500000 |
| #define REG7_VOVP_SHIFT 5 |
| |
| #define LENGTH(a) (sizeof(a) / sizeof((a)[0])) |
| |
| // Q macros where QsN (signed) and QuN (unsigned) have N fractional bits |
| #define Q(type, p, x) (type)((x) * ((type)1 << (p))) |
| |
| #define Qs16(x) Q(int32_t, 16, (x)) |
| |
| // Convert down |
| #define Qdown(from, to, x) (((x) + (1 << (((from) - (to)) - 1))) >> ((from) - (to))) |
| #define Qint(from, x) Qdown((from), 0, (x)) |
| |
| // Convert up |
| #define Qup(from, to, x) ((x) << ((to) - (from))) |
| |
| #define APPLY_LINEAR_CONFIG(client, string, reg, field) \ |
| bq24251_apply_linear_config( \ |
| client, \ |
| string, \ |
| reg##_ADDR, \ |
| reg##_##field##_MASK, \ |
| reg##_##field##_MIN, \ |
| reg##_##field##_MAX, \ |
| reg##_##field##_STEP, \ |
| reg##_##field##_SHIFT) |
| |
| |
| struct bq24251_device { |
| struct mutex thread_mutex; |
| struct power_supply_desc bat_psd; |
| struct power_supply_config bat_psc; |
| struct power_supply *bat_ps; |
| struct i2c_client *client; |
| struct delayed_work work; |
| int stat_gpio; |
| int status; |
| int health; |
| bool online; |
| struct iio_channel *chan; |
| unsigned int r1_ohm; |
| unsigned int r2_ohm; |
| }; |
| |
| static enum power_supply_property bq24251_bat_props[] = { |
| POWER_SUPPLY_PROP_STATUS, |
| POWER_SUPPLY_PROP_HEALTH, |
| POWER_SUPPLY_PROP_PRESENT, |
| POWER_SUPPLY_PROP_ONLINE, |
| POWER_SUPPLY_PROP_TECHNOLOGY, |
| POWER_SUPPLY_PROP_MODEL_NAME, |
| POWER_SUPPLY_PROP_MANUFACTURER, |
| POWER_SUPPLY_PROP_VOLTAGE_NOW, /* Keep this one last */ |
| }; |
| |
| static const int vovp_vals[] = { |
| REG7_VOVP_VAL0, |
| REG7_VOVP_VAL1, |
| REG7_VOVP_VAL2, |
| REG7_VOVP_VAL3, |
| REG7_VOVP_VAL4, |
| REG7_VOVP_VAL5, |
| REG7_VOVP_VAL6, |
| REG7_VOVP_VAL7, |
| }; |
| |
| static struct workqueue_struct *bq24251_wq; |
| |
| static int force_input_limit = 0; |
| module_param(force_input_limit, int, 0444); |
| MODULE_PARM_DESC(force_input_limit, "Force 2 A input current limit"); |
| |
| static int maximum_battery_regulation_mv = 4400; |
| |
| static int bq24251_detect_battery(struct bq24251_device *bq); |
| static int bq24251_read_status(struct bq24251_device *bq); |
| static int bq24251_update_status(struct bq24251_device *bq); |
| |
| |
| static void bq24251_wq_function(struct work_struct *work) |
| { |
| struct bq24251_device *bq = container_of(work, struct bq24251_device, work.work); |
| |
| mutex_lock(&bq->thread_mutex); |
| bq24251_detect_battery(bq); |
| mutex_unlock(&bq->thread_mutex); |
| |
| power_supply_changed(bq->bat_ps); |
| |
| return; |
| } |
| |
| static ssize_t bq24251_show_bit(struct device *dev, |
| struct device_attribute *attr, char *buf, |
| uint8_t addr, uint8_t mask, |
| const char *err) |
| { |
| struct i2c_client *client = container_of(dev, struct i2c_client, dev); |
| int reg_value; |
| |
| reg_value = i2c_smbus_read_byte_data(client, addr); |
| if (reg_value < 0) { |
| dev_err(dev, err); |
| return -EIO; |
| } |
| |
| return sprintf(buf, "%u\n", !(~reg_value & mask)); |
| } |
| |
| |
| static ssize_t bq24251_store_bit(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count, |
| uint8_t addr, uint8_t mask, |
| const char *err) |
| { |
| struct i2c_client *client = container_of(dev, struct i2c_client, dev); |
| struct bq24251_device *bq = i2c_get_clientdata(client); |
| int reg_value; |
| unsigned int value; |
| int ret; |
| |
| ret = kstrtouint(buf, 10, &value); |
| |
| if (ret) |
| return ret; |
| |
| mutex_lock(&bq->thread_mutex); |
| |
| reg_value = i2c_smbus_read_byte_data(client, addr); |
| |
| if (reg_value < 0) { |
| dev_err(dev, err); |
| ret = reg_value; |
| } else { |
| /* Clear out the reset bit */ |
| if (addr == REG2_ADDR) |
| reg_value &= ~REG2_RESET_MASK; |
| |
| if (value) |
| reg_value |= mask; |
| else |
| reg_value &= ~mask; |
| |
| ret = i2c_smbus_write_byte_data(client, addr, reg_value); |
| if (ret == 0) |
| ret = count; |
| } |
| |
| mutex_unlock(&bq->thread_mutex); |
| |
| return ret; |
| } |
| |
| static ssize_t bq24251_sysoff_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| return bq24251_show_bit(dev, attr, buf, REG6_ADDR, REG6_SYSOFF_MASK, "Couldn't read Register 6\n"); |
| } |
| |
| static ssize_t bq24251_sysoff_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| return bq24251_store_bit(dev, attr, buf, count, REG6_ADDR, REG6_SYSOFF_MASK, "Couldn't read Register 6\n"); |
| } |
| |
| static ssize_t bq24251_ce_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| return bq24251_show_bit(dev, attr, buf, REG2_ADDR, REG2_CE_MASK, "Couldn't read Register 2\n"); |
| } |
| |
| static ssize_t bq24251_ce_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| return bq24251_store_bit(dev, attr, buf, count, REG2_ADDR, REG2_CE_MASK, "Couldn't read Register 2\n"); |
| } |
| |
| static ssize_t bq24251_hz_mode_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| return bq24251_show_bit(dev, attr, buf, REG2_ADDR, REG2_HZ_MODE_MASK, "Couldn't read Register 2\n"); |
| } |
| |
| static ssize_t bq24251_hz_mode_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| return bq24251_store_bit(dev, attr, buf, count, REG2_ADDR, REG2_HZ_MODE_MASK, "Couldn't read Register 2\n"); |
| } |
| |
| static ssize_t bq24251_show_num(struct device *dev, |
| struct device_attribute *attr, char *buf, |
| uint8_t addr, uint8_t mask, unsigned int shift, |
| int32_t scale, /* Qs16 fixed point */ |
| int32_t offset, /* in scaled units */ |
| const char *err) |
| { |
| struct i2c_client *client = container_of(dev, struct i2c_client, dev); |
| int reg_value; |
| int32_t scaled_value, raw_value; |
| |
| reg_value = i2c_smbus_read_byte_data(client, addr); |
| if (reg_value < 0) { |
| dev_err(dev, err); |
| return -EIO; |
| } |
| |
| raw_value = (reg_value & mask) >> shift; |
| scaled_value = Qint(16, (int64_t)raw_value * scale) + offset; |
| |
| return sprintf(buf, "%d (%.2x)\n", scaled_value, raw_value); |
| } |
| |
| static ssize_t bq24251_store_num(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count, |
| uint8_t addr, uint8_t mask, unsigned int shift, |
| int32_t recip_scale, /* Qs16 fixed point */ |
| int32_t offset, int32_t min, int32_t max, /* in scaled units */ |
| const char *err) |
| { |
| struct i2c_client *client = container_of(dev, struct i2c_client, dev); |
| struct bq24251_device *bq = i2c_get_clientdata(client); |
| int reg_value; |
| int32_t scaled_value, raw_value; |
| int ret; |
| |
| ret = kstrtos32(buf, 10, &scaled_value); |
| |
| if (ret) { |
| return ret; |
| } |
| |
| mutex_lock(&bq->thread_mutex); |
| |
| reg_value = i2c_smbus_read_byte_data(client, addr); |
| |
| if (reg_value < 0) { |
| dev_err(dev, err); |
| ret = reg_value; |
| } else { |
| reg_value &= ~mask; |
| |
| scaled_value = clamp(scaled_value, min, max) - offset; |
| raw_value = Qint(16, (int64_t)scaled_value * recip_scale); |
| reg_value |= (raw_value << shift) & mask; |
| |
| ret = i2c_smbus_write_byte_data(client, addr, reg_value); |
| if (ret == 0) |
| ret = count; |
| } |
| |
| mutex_unlock(&bq->thread_mutex); |
| |
| return ret; |
| } |
| |
| static ssize_t bq24251_vbatreg_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| return bq24251_show_num(dev, attr, buf, |
| REG3_ADDR, REG3_VBATREG_MASK, REG3_VBATREG_SHIFT, |
| Qs16(20) /* 20 mV/LSB */, |
| REG3_VBATREG_MIN / 1000, |
| "Couldn't read Register 3\n"); |
| } |
| |
| static ssize_t bq24251_vbatreg_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| return bq24251_store_num(dev, attr, buf, count, |
| REG3_ADDR, REG3_VBATREG_MASK, REG3_VBATREG_SHIFT, |
| Qs16(0.05) /* 0.05 LSB/mV */, |
| REG3_VBATREG_MIN / 1000, REG3_VBATREG_MIN / 1000, maximum_battery_regulation_mv, |
| "Couldn't read Register 3\n"); |
| } |
| |
| static ssize_t bq24251_r1_ohms_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct i2c_client *client = container_of(dev, struct i2c_client, dev); |
| struct bq24251_device *bq = i2c_get_clientdata(client); |
| |
| return snprintf(buf, PAGE_SIZE, "%u\n", bq->r1_ohm); |
| } |
| |
| static ssize_t bq24251_r2_ohms_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct i2c_client *client = container_of(dev, struct i2c_client, dev); |
| struct bq24251_device *bq = i2c_get_clientdata(client); |
| |
| return snprintf(buf, PAGE_SIZE, "%u\n", bq->r2_ohm); |
| } |
| |
| /* |
| * Battery Detection Dance Steps AKA the BQ Twist |
| * |
| * +-----------------------------+ |
| * |Is CE disabled? If so, enable| |
| * +-----------------------------+ |
| * | |
| * +--------------------------------------------------------+ |
| * |Is there a temp fault that masks a disconnected battery?| |
| * +--------------------------------------------------------+ |
| * | | |
| * | YES | NO |
| * | | |
| * +------------------------+ +-----------+ |
| * |Disable TS and check for| |Some other | |
| * |disconnected fault | |temp fault?| |
| * +------------------------+ +-----------+ |
| * | | | \ |
| * Disconnected | | Connected | \ NO |
| * +--------------+ | | \ |
| * | Disable CE to| | | +------+ |
| * | stop cycling | | | | Exit | |
| * +--------------+ | | YES +------+ |
| * \ | | |
| * \ | | |
| * \ +------------+ | |
| * \|Re-enable TS|\ | |
| * +------------+ \ | |
| * \ | |
| * \ | |
| * +-----------------------------------+ |
| * |Re-schedule work, as a change in | |
| * |battery connection will not trigger| |
| * |an interrupt | |
| * +-----------------------------------+ |
| */ |
| |
| /* Call while holding thread_mutex */ |
| static int bq24251_detect_battery(struct bq24251_device *bq) |
| { |
| int ret; |
| int reg1_value; |
| int reg2_value; |
| int reg6_value; |
| |
| reg2_value = i2c_smbus_read_byte_data(bq->client, REG2_ADDR); |
| if (reg2_value < 0) { |
| dev_err(&bq->client->dev, "Couldn't read Register 2\n"); |
| return reg2_value; |
| } |
| if (reg2_value & REG2_CE_MASK) { |
| /* Charging disabled, re-enable */ |
| reg2_value &= ~(REG2_CE_MASK | REG2_RESET_MASK); |
| ret = i2c_smbus_write_byte_data(bq->client, REG2_ADDR, reg2_value); |
| if (ret < 0) { |
| dev_err(&bq->client->dev, "Couldn't write Register 2\n"); |
| return ret; |
| } |
| msleep(32); /* deglitch */ |
| } |
| /* If temperature sensing is enabled, a disconnected battery |
| * will appear as fault 100 - TS temp < T_COLD, so clear TS_EN |
| * to check for the disconnected fault */ |
| reg6_value = i2c_smbus_read_byte_data(bq->client, REG6_ADDR); |
| if ((reg6_value & REG6_TS_STAT_MASK) == REG6_TS_STAT_TEMP_LT_TCOLD) { |
| reg6_value &= ~REG6_TS_EN_MASK; |
| ret = i2c_smbus_write_byte_data(bq->client, REG6_ADDR, reg6_value); |
| if (ret < 0) { |
| dev_err(&bq->client->dev, "Couldn't write Register 6\n"); |
| return ret; |
| } |
| msleep(250); /* deglitch + battery detection times */ |
| |
| /* Check for disconnected fault */ |
| reg1_value = bq24251_read_status(bq); |
| if ((reg1_value & REG1_FAULT_MASK) == REG1_FAULT_NO_BATTERY_CONNECTED) { |
| /* A battery disconnected fault was masked. Disable charging to |
| * stop SYS cycling. */ |
| reg2_value = i2c_smbus_read_byte_data(bq->client, REG2_ADDR); |
| if (reg2_value < 0) { |
| dev_err(&bq->client->dev, "Couldn't read Register 2\n"); |
| return reg2_value; |
| } |
| reg2_value |= REG2_CE_MASK; |
| reg2_value &= ~REG2_RESET_MASK; |
| ret = i2c_smbus_write_byte_data(bq->client, REG2_ADDR, reg2_value); |
| if (ret < 0) { |
| dev_err(&bq->client->dev, "Couldn't write Register 2\n"); |
| return ret; |
| } |
| |
| bq->online = false; |
| } else { |
| /* No battery disconnected fault detected - a true temp fault */ |
| bq->health = POWER_SUPPLY_HEALTH_OVERHEAT; |
| bq->online = true; |
| } |
| /* Re-enable temperature sensing */ |
| reg6_value = i2c_smbus_read_byte_data(bq->client, REG6_ADDR); |
| reg6_value |= REG6_TS_EN_MASK; |
| ret = i2c_smbus_write_byte_data(bq->client, REG6_ADDR, reg6_value); |
| if (ret < 0) { |
| dev_err(&bq->client->dev, "Couldn't write Register 6\n"); |
| return ret; |
| } |
| msleep(32); /* deglitch */ |
| queue_delayed_work(bq24251_wq, &bq->work, msecs_to_jiffies(WORK_DELAY_SECS * MSEC_PER_SEC)); |
| } else if (reg6_value & REG6_TS_STAT_MASK) { |
| bq->health = POWER_SUPPLY_HEALTH_OVERHEAT; |
| bq->online = true; |
| queue_delayed_work(bq24251_wq, &bq->work, msecs_to_jiffies(WORK_DELAY_SECS * MSEC_PER_SEC)); |
| } else { |
| bq->online = true; |
| bq24251_update_status(bq); |
| } |
| |
| return 0; |
| } |
| |
| static int bq24251_bat_get_property(struct power_supply *bat_ps, |
| enum power_supply_property prop, |
| union power_supply_propval *val) |
| { |
| struct bq24251_device *bq = container_of(bat_ps->desc, struct |
| bq24251_device, bat_psd); |
| int ret = 0; |
| |
| switch (prop) { |
| |
| case POWER_SUPPLY_PROP_STATUS: |
| val->intval = bq->status; |
| break; |
| |
| case POWER_SUPPLY_PROP_HEALTH: |
| val->intval = bq->health; |
| break; |
| |
| case POWER_SUPPLY_PROP_PRESENT: |
| /* Always present */ |
| val->intval = 1; |
| break; |
| |
| case POWER_SUPPLY_PROP_ONLINE: |
| val->intval = bq->online; |
| break; |
| |
| case POWER_SUPPLY_PROP_TECHNOLOGY: |
| /* Only support for Lithium Ion */ |
| val->intval = POWER_SUPPLY_TECHNOLOGY_LION; |
| break; |
| |
| case POWER_SUPPLY_PROP_VOLTAGE_NOW: |
| BUG_ON(!bq->chan); |
| |
| mutex_lock(&bq->thread_mutex); |
| |
| if (ret >= 0) { |
| /* Read ADC voltage and compensate for the divider */ |
| ret = iio_read_channel_processed(bq->chan, &val->intval); |
| val->intval *= bq->r1_ohm + bq->r2_ohm; |
| val->intval /= bq->r2_ohm; |
| |
| /* Processed IIO returns mV but power supply returns uV */ |
| val->intval *= 1000; |
| } |
| |
| mutex_unlock(&bq->thread_mutex); |
| |
| break; |
| |
| case POWER_SUPPLY_PROP_MODEL_NAME: |
| val->strval = "bq24251"; |
| break; |
| |
| case POWER_SUPPLY_PROP_MANUFACTURER: |
| val->strval = "TI"; |
| break; |
| |
| default: |
| ret = -EINVAL; |
| } |
| |
| return ret; |
| } |
| |
| static int bq24251_read_status(struct bq24251_device *bq) |
| { |
| int reg1_read_count = 0; |
| int reg1_value = -1; |
| int reg1_value_old; |
| |
| /* Read Register 1 until status is stable */ |
| do { |
| reg1_value_old = reg1_value; |
| reg1_value = i2c_smbus_read_byte_data(bq->client, REG1_ADDR); |
| if (reg1_value < 0) { |
| dev_err(&bq->client->dev, "Couldn't read Register 1\n"); |
| return reg1_value; |
| } |
| |
| dev_info(&bq->client->dev, "Register 1: 0x%02X\n", reg1_value); |
| reg1_read_count++; |
| } while (reg1_value != reg1_value_old && |
| reg1_read_count < REG1_READ_COUNT_MAX); |
| |
| return reg1_value; |
| } |
| |
| /* Call while holding thread_mutex */ |
| static int bq24251_update_status(struct bq24251_device *bq) |
| { |
| int reg1_value; |
| |
| reg1_value = bq24251_read_status(bq); |
| if (reg1_value < 0) |
| return reg1_value; |
| |
| /* Set Battery Status */ |
| switch (reg1_value & REG1_STATUS_MASK) { |
| |
| case REG1_STATUS_READY: |
| bq->status = POWER_SUPPLY_STATUS_DISCHARGING; |
| break; |
| |
| case REG1_STATUS_CHARGE_IN_PROGRESS: |
| bq->status = POWER_SUPPLY_STATUS_CHARGING; |
| break; |
| |
| case REG1_STATUS_CHARGE_DONE: |
| bq->status = POWER_SUPPLY_STATUS_FULL; |
| break; |
| |
| default: |
| bq->status = POWER_SUPPLY_STATUS_DISCHARGING; |
| break; |
| } |
| |
| /* Set Battery Health */ |
| switch (reg1_value & REG1_FAULT_MASK) { |
| |
| case REG1_FAULT_NORMAL: |
| case REG1_FAULT_INPUT_UVLO: |
| case REG1_FAULT_INPUT_FAULT_AND_LDO_LOW: |
| bq->health = POWER_SUPPLY_HEALTH_GOOD; |
| break; |
| |
| case REG1_FAULT_INPUT_OVP: |
| case REG1_FAULT_BATTERY_OVP: |
| bq->health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; |
| break; |
| |
| case REG1_FAULT_BATTERY_TEMPERATURE_FAULT: |
| #ifdef CONFIG_CHARGER_BQ24251_BATTERY_DISCONNECT_DETECTION |
| /* Distinguishing temperature faults from the disconnected |
| * battery fault requires a particular set of dance steps that |
| * triggers activity on the STAT pin, leading to more |
| * interrupts. Schedule the dance one second in the future to |
| * avoid an interrupt loop. If necessary the scheduled work |
| * will reschedule itself with a longer delay. */ |
| if (!delayed_work_pending(&bq->work)) |
| queue_delayed_work(bq24251_wq, &bq->work, msecs_to_jiffies(1 * MSEC_PER_SEC)); |
| #endif |
| bq->health = POWER_SUPPLY_HEALTH_OVERHEAT; |
| break; |
| |
| case REG1_FAULT_THERMAL_SHUTDOWN: |
| bq->health = POWER_SUPPLY_HEALTH_OVERHEAT; |
| break; |
| |
| default: |
| bq->health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; |
| break; |
| } |
| |
| #ifdef CONFIG_CHARGER_BQ24251_BATTERY_DISCONNECT_DETECTION |
| if (!delayed_work_pending(&bq->work)) |
| bq->online = true; |
| #endif |
| |
| return 0; |
| } |
| |
| static irqreturn_t bq24251_irq_handler(int irq, void *devid) |
| { |
| struct i2c_client *client = devid; |
| struct bq24251_device *bq = i2c_get_clientdata(client); |
| |
| mutex_lock(&bq->thread_mutex); |
| bq24251_update_status(bq); |
| mutex_unlock(&bq->thread_mutex); |
| |
| pm_wakeup_event(&client->dev, 0); |
| |
| power_supply_changed(bq->bat_ps); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static int bq24251_apply_linear_config(struct i2c_client *client, |
| const char *string, |
| unsigned int reg, |
| unsigned int mask, |
| unsigned int min, |
| unsigned int max, |
| unsigned int step, |
| unsigned int shift) |
| { |
| struct device_node *np = client->dev.of_node; |
| unsigned int prop; |
| int reg_value; |
| int ret; |
| |
| ret = of_property_read_u32(np, string, &prop); |
| if (ret == 0) { |
| if ((prop < min) | (prop > max)) { |
| dev_err(&client->dev, "Invalid %s property\n", string); |
| return -EINVAL; |
| } |
| |
| reg_value = i2c_smbus_read_byte_data(client, reg); |
| if (reg_value < 0) |
| return reg_value; |
| |
| reg_value &= ~mask; |
| reg_value |= (((prop - min) / step) << shift) & mask; |
| |
| ret = i2c_smbus_write_byte_data(client, reg, reg_value); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int bq24251_parse_configuration(struct i2c_client *client) |
| { |
| struct device_node *np = client->dev.of_node; |
| unsigned int prop; |
| int reg_value; |
| int ret; |
| int i; |
| |
| /* Disable watchdog so software written parameters take effect */ |
| reg_value = i2c_smbus_read_byte_data(client, REG1_ADDR); |
| if (reg_value < 0) |
| return reg_value; |
| |
| reg_value &= ~REG1_WD_EN; |
| |
| ret = i2c_smbus_write_byte_data(client, REG1_ADDR, reg_value); |
| |
| |
| ret = APPLY_LINEAR_CONFIG(client, "battery-regulation-voltage", REG3, VBATREG); |
| if (ret) |
| return ret; |
| |
| ret = of_property_read_u32(np, "max-battery-regulation-voltage", &prop); |
| if (ret) |
| return ret; |
| |
| maximum_battery_regulation_mv = (int)clamp_t(unsigned int, prop, REG3_VBATREG_MIN, REG3_VBATREG_MAX) / 1000; |
| |
| ret = APPLY_LINEAR_CONFIG(client, "term-current-sense-threshold", REG4, ITERM); |
| if (ret) |
| return ret; |
| |
| ret = APPLY_LINEAR_CONFIG(client, "charge-current", REG4, ICHG); |
| if (ret) |
| return ret; |
| |
| ret = APPLY_LINEAR_CONFIG(client, "vindpm-threshold", REG5, VINDPM); |
| if (ret) |
| return ret; |
| |
| ret = APPLY_LINEAR_CONFIG(client, "safety-timer-time-limit", REG6, TMR); |
| if (ret) |
| return ret; |
| |
| ret = of_property_read_u32(np, "ovp-voltage", &prop); |
| if (ret == 0) { |
| for (i = 0; i < ARRAY_SIZE(vovp_vals); i++) |
| if (prop == vovp_vals[i]) { |
| reg_value = i2c_smbus_read_byte_data(client, REG7_ADDR); |
| if (reg_value < 0) |
| return reg_value; |
| |
| reg_value &= ~REG7_VOVP_MASK; |
| reg_value |= i << REG7_VOVP_SHIFT; |
| |
| ret = i2c_smbus_write_byte_data(client, REG7_ADDR, reg_value); |
| |
| return ret; |
| } |
| |
| dev_err(&client->dev, "Invalid ovp-voltage property\n"); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| |
| static int bq24251_bat_get_ext_psy_data(struct device *dev, void *data) |
| { |
| struct power_supply *ext_ps = dev_get_drvdata(dev); |
| struct bq24251_device *bq = (struct bq24251_device *) data; |
| union power_supply_propval val; |
| int i, found = 0; |
| int reg2_value; |
| int ret; |
| |
| /* See if bq->bat_psd is a supplicant of ext_ps */ |
| if (!ext_ps->desc->name) |
| return 0; |
| |
| for (i = 0; i < bq->bat_ps->num_supplies; i++) |
| if (!strcmp(ext_ps->desc->name, bq->bat_ps->supplied_from[i])) |
| found = 1; |
| |
| if (!found) |
| return 0; |
| |
| /* Get the latest max current property */ |
| if (ext_ps->desc->get_property(ext_ps, POWER_SUPPLY_PROP_CURRENT_MAX, &val)) |
| return 0; |
| |
| dev_info(&bq->client->dev, "Supply limits current to %d mA\n", val.intval); |
| |
| mutex_lock(&bq->thread_mutex); |
| |
| /* Read Register 2 */ |
| reg2_value = i2c_smbus_read_byte_data(bq->client, REG2_ADDR); |
| if (reg2_value < 0) { |
| dev_err(&bq->client->dev, "Couldn't read Register 2\n"); |
| mutex_unlock(&bq->thread_mutex); |
| return reg2_value; |
| } |
| |
| /* Apply current limit based on what type of charger */ |
| reg2_value &= ~(REG2_RESET_MASK | REG2_IIN_LIMIT_MASK); |
| |
| #ifdef CONFIG_CHARGER_BQ24251_FORCE_2A |
| reg2_value |= REG2_IIN_LIMIT_2000; |
| #else |
| if (val.intval == DCP_CURRENT_LIMIT || force_input_limit) |
| reg2_value |= REG2_IIN_LIMIT_2000; /* Our DCP charger will supply 2A */ |
| else |
| reg2_value |= REG2_IIN_LIMIT_500; /* Otherwise use post-enumeration SDP limit */ |
| #endif |
| |
| ret = i2c_smbus_write_byte_data(bq->client, REG2_ADDR, reg2_value); |
| if (ret < 0) |
| dev_err(&bq->client->dev, "Couldn't write Register 2\n"); |
| |
| mutex_unlock(&bq->thread_mutex); |
| |
| return ret; |
| } |
| |
| static void bq24251_bat_ext_changed(struct power_supply *bat_ps) |
| { |
| struct bq24251_device *bq = container_of(bat_ps->desc, struct |
| bq24251_device, bat_psd); |
| |
| class_for_each_device(power_supply_class, NULL, bq, |
| bq24251_bat_get_ext_psy_data); |
| } |
| |
| static DEVICE_ATTR(sysoff, S_IRUSR | S_IWUSR, bq24251_sysoff_show, bq24251_sysoff_store); |
| static DEVICE_ATTR(charging_enabled, S_IRUSR | S_IWUSR, bq24251_ce_show, bq24251_ce_store); |
| static DEVICE_ATTR(high_impedance_mode_enabled, S_IRUSR | S_IWUSR, bq24251_hz_mode_show, bq24251_hz_mode_store); |
| static DEVICE_ATTR(battery_regulation_voltage, S_IRUSR | S_IWUSR, bq24251_vbatreg_show, bq24251_vbatreg_store); |
| static DEVICE_ATTR(r1_ohms, S_IRUSR, bq24251_r1_ohms_show, NULL); |
| static DEVICE_ATTR(r2_ohms, S_IRUSR, bq24251_r2_ohms_show, NULL); |
| |
| static struct attribute *bq24251_attrs[] = { |
| &dev_attr_sysoff.attr, |
| &dev_attr_charging_enabled.attr, |
| &dev_attr_high_impedance_mode_enabled.attr, |
| &dev_attr_battery_regulation_voltage.attr, |
| &dev_attr_r1_ohms.attr, |
| &dev_attr_r2_ohms.attr, |
| NULL, |
| }; |
| |
| static const struct attribute_group bq24251_attr_group = { |
| .attrs = bq24251_attrs, |
| }; |
| |
| #ifdef CONFIG_CHARGER_BQ24251_POWER_OFF |
| struct device *power_off_dev; |
| |
| static void bq24251_power_off(void) |
| { |
| bq24251_sysoff_store(power_off_dev, NULL, "1", 1); |
| |
| mdelay(100); |
| |
| bq24251_sysoff_store(power_off_dev, NULL, "0", 1); |
| } |
| #endif |
| |
| static int bq24251_probe(struct i2c_client *client, |
| const struct i2c_device_id *id) |
| { |
| struct bq24251_device *bq; |
| struct device_node *np; |
| int ret; |
| |
| /* Check for OF node */ |
| np = client->dev.of_node; |
| if (!np) { |
| dev_err(&client->dev, "Missing DT node\n"); |
| return -ENODEV; |
| } |
| |
| /* Claim memory */ |
| bq = devm_kzalloc(&client->dev, sizeof(*bq), GFP_KERNEL); |
| if (!bq) |
| return -ENOMEM; |
| |
| i2c_set_clientdata(client, bq); |
| bq->client = client; |
| |
| /* Parse configuration constraints */ |
| ret = bq24251_parse_configuration(client); |
| if (ret) |
| return ret; |
| |
| bq->bat_psd.name = "bq24251-battery"; |
| bq->bat_psd.type = POWER_SUPPLY_TYPE_BATTERY; |
| bq->bat_psd.get_property = bq24251_bat_get_property; |
| bq->bat_psd.properties = bq24251_bat_props; |
| bq->bat_psd.num_properties = ARRAY_SIZE(bq24251_bat_props); |
| bq->bat_psd.external_power_changed = bq24251_bat_ext_changed; |
| bq->bat_psc.of_node = client->dev.of_node; |
| |
| mutex_init(&bq->thread_mutex); |
| |
| device_init_wakeup(&client->dev, 1); |
| |
| /* Get STAT gpio and interrupt */ |
| bq->stat_gpio = of_get_named_gpio(np, "stat-gpio", 0); |
| if (!gpio_is_valid(bq->stat_gpio)) { |
| dev_err(&client->dev, "DT missing STAT gpio property\n"); |
| return -ENODEV; |
| } |
| |
| ret = devm_gpio_request(&client->dev, bq->stat_gpio, "bq24251-stat"); |
| if (ret) { |
| dev_err(&client->dev, "Couldn't request STAT gpio\n"); |
| return ret; |
| } |
| |
| ret = gpio_direction_input(bq->stat_gpio); |
| if (ret) { |
| dev_err(&client->dev, "Couldn't set STAT gpio direction\n"); |
| return ret; |
| } |
| |
| mutex_lock(&bq->thread_mutex); |
| |
| ret = devm_request_threaded_irq(&client->dev, |
| gpio_to_irq(bq->stat_gpio), NULL, |
| bq24251_irq_handler, |
| IRQF_ONESHOT | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, |
| "bq24251-stat", client); |
| if (ret) { |
| dev_err(&client->dev, "Couldn't request irq for STAT gpio\n"); |
| goto probe_err_mutex_unlock; |
| } |
| |
| /* Get optional ADC and voltage divider resistor values */ |
| bq->chan = iio_channel_get(&client->dev, NULL); |
| if (!IS_ERR(bq->chan)) { |
| ret = of_property_read_u32(np, "divider-r1-ohm", &bq->r1_ohm); |
| if (ret) { |
| dev_err(&client->dev, "Couldn't get r1 property from DT\n"); |
| goto probe_err_iio_release; |
| } |
| |
| ret = of_property_read_u32(np, "divider-r2-ohm", &bq->r2_ohm); |
| if (ret) { |
| dev_err(&client->dev, "Couldn't get r2 property from DT\n"); |
| goto probe_err_iio_release; |
| } |
| |
| if (!bq->r1_ohm || !bq->r2_ohm) { |
| dev_err(&client->dev, "Divider values must be non-zero\n"); |
| ret = -EINVAL; |
| goto probe_err_iio_release; |
| } |
| |
| } else if (PTR_ERR(bq->chan) == -EPROBE_DEFER) { |
| /* If ADC is specified but not available, defer */ |
| return -EPROBE_DEFER; |
| |
| } else { |
| dev_info(&client->dev, "No associated ADC\n"); |
| |
| /* Omit support for POWER_SUPPLY_PROP_VOLTAGE_NOW if no ADC */ |
| bq->bat_psd.num_properties--; |
| bq->chan = 0; |
| } |
| |
| /* Add sysfs files */ |
| ret = sysfs_create_group(&client->dev.kobj, &bq24251_attr_group); |
| if (ret) { |
| dev_err(&client->dev, "Failed to create sysfs files\n"); |
| goto probe_err_mutex_unlock; |
| } |
| |
| #ifdef CONFIG_CHARGER_BQ24251_BATTERY_DISCONNECT_DETECTION |
| bq24251_wq = create_workqueue("bq24251_wq"); |
| if (bq24251_wq) { |
| INIT_DELAYED_WORK(&bq->work, bq24251_wq_function); |
| } else { |
| dev_err(&client->dev, "Failed to create workqueue\n"); |
| goto probe_err_sysfs_destroy; |
| } |
| #else |
| bq->online = true; |
| #endif |
| |
| bq24251_update_status(bq); |
| |
| /* Register as power supply */ |
| bq->bat_ps = power_supply_register(&client->dev, &bq->bat_psd, |
| &bq->bat_psc); |
| if (IS_ERR(bq->bat_ps)) { |
| ret = PTR_ERR(bq->bat_ps); |
| bq->bat_ps = NULL; |
| goto probe_err_workqueue_destroy; |
| } |
| |
| mutex_unlock(&bq->thread_mutex); |
| |
| /* Simulate external PS change to set input current */ |
| bq24251_bat_ext_changed(bq->bat_ps); |
| |
| /* Allow the device to wake us up */ |
| device_init_wakeup(&client->dev, 1); |
| |
| #ifdef CONFIG_CHARGER_BQ24251_POWER_OFF |
| /* Register a power off hook */ |
| if (pm_power_off != bq24251_power_off) { |
| power_off_dev = &client->dev; |
| pm_power_off = bq24251_power_off; |
| } |
| #endif |
| |
| return 0; |
| |
| probe_err_workqueue_destroy: |
| #ifdef CONFIG_CHARGER_BQ24251_BATTERY_DISCONNECT_DETECTION |
| cancel_delayed_work_sync(&bq->work); |
| destroy_workqueue(bq24251_wq); |
| |
| probe_err_sysfs_destroy: |
| #endif |
| sysfs_remove_group(&client->dev.kobj, &bq24251_attr_group); |
| |
| probe_err_mutex_unlock: |
| mutex_unlock(&bq->thread_mutex); |
| |
| probe_err_iio_release: |
| iio_channel_release(bq->chan); |
| return ret; |
| } |
| |
| |
| static int bq24251_remove(struct i2c_client *client) |
| { |
| struct bq24251_device *bq = i2c_get_clientdata(client); |
| |
| #ifdef CONFIG_CHARGER_BQ24251_BATTERY_DISCONNECT_DETECTION |
| cancel_delayed_work_sync(&bq->work); |
| destroy_workqueue(bq24251_wq); |
| #endif |
| |
| sysfs_remove_group(&client->dev.kobj, &bq24251_attr_group); |
| |
| power_supply_unregister(bq->bat_ps); |
| |
| if (bq->chan) |
| iio_channel_release(bq->chan); |
| |
| device_init_wakeup(&client->dev, 0); |
| |
| return 0; |
| } |
| |
| static int bq24251_suspend(struct device *dev) |
| { |
| struct i2c_client *client = container_of(dev, struct i2c_client, dev); |
| struct bq24251_device *bq = i2c_get_clientdata(client); |
| |
| if (device_may_wakeup(&client->dev)) |
| enable_irq_wake(gpio_to_irq(bq->stat_gpio)); |
| |
| return 0; |
| } |
| |
| static int bq24251_resume(struct device *dev) |
| { |
| struct i2c_client *client = container_of(dev, struct i2c_client, dev); |
| struct bq24251_device *bq = i2c_get_clientdata(client); |
| |
| if (device_may_wakeup(&client->dev)) |
| disable_irq_wake(gpio_to_irq(bq->stat_gpio)); |
| |
| return 0; |
| } |
| |
| static const struct i2c_device_id bq24251_id[] = { |
| { "bq24251-charger", 0 }, |
| { } |
| }; |
| |
| static const struct of_device_id bq24251_match[] = { |
| { .compatible = "ti,bq24251-charger", }, |
| { }, |
| }; |
| |
| static SIMPLE_DEV_PM_OPS(bq24251_pm_ops, bq24251_suspend, bq24251_resume); |
| |
| static struct i2c_driver bq24251_driver = { |
| .probe = bq24251_probe, |
| .remove = bq24251_remove, |
| .id_table = bq24251_id, |
| .driver = { |
| .name = "bq24251-charger", |
| .of_match_table = bq24251_match, |
| .pm = &bq24251_pm_ops, |
| }, |
| }; |
| module_i2c_driver(bq24251_driver); |
| |
| MODULE_AUTHOR("Tim Kryger <tkryger@nestlabs.com>"); |
| MODULE_DESCRIPTION("bq24251 charger driver"); |
| MODULE_LICENSE("GPL v2"); |