| /* |
| * 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> |
| |
| #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 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 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 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 bat_ps; |
| struct i2c_client *client; |
| int stat_gpio; |
| int status; |
| int health; |
| 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 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 ssize_t bq24251_sysoff_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct i2c_client *client = container_of(dev, struct i2c_client, dev); |
| unsigned long sysoff; |
| int reg6_value; |
| |
| reg6_value = i2c_smbus_read_byte_data(client, REG6_ADDR); |
| if (reg6_value < 0) { |
| dev_err(dev, "Couldn't read Register 6\n"); |
| return -EIO; |
| } |
| |
| sysoff = (reg6_value & REG6_SYSOFF_MASK) ? 1 : 0; |
| |
| return sprintf(buf, "%lu\n", sysoff); |
| } |
| |
| static ssize_t bq24251_sysoff_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct i2c_client *client = container_of(dev, struct i2c_client, dev); |
| struct bq24251_device *bq = i2c_get_clientdata(client); |
| unsigned long sysoff; |
| int reg6_value; |
| int ret; |
| |
| ret = strict_strtoul(buf, 10, &sysoff); |
| |
| if (ret) |
| return ret; |
| |
| mutex_lock(&bq->thread_mutex); |
| |
| reg6_value = i2c_smbus_read_byte_data(client, REG6_ADDR); |
| |
| if (reg6_value < 0) { |
| dev_err(dev, "Couldn't read Register 6\n"); |
| ret = reg6_value; |
| } else { |
| if (sysoff) |
| reg6_value |= REG6_SYSOFF_MASK; |
| else |
| reg6_value &= ~REG6_SYSOFF_MASK; |
| |
| ret = i2c_smbus_write_byte_data(client, REG6_ADDR, reg6_value); |
| if (ret == 0) |
| ret = count; |
| } |
| |
| mutex_unlock(&bq->thread_mutex); |
| |
| return ret; |
| } |
| |
| static ssize_t bq24251_ce_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct i2c_client *client = container_of(dev, struct i2c_client, dev); |
| unsigned long ce; |
| int reg2_value; |
| |
| reg2_value = i2c_smbus_read_byte_data(client, REG2_ADDR); |
| if (reg2_value < 0) { |
| dev_err(dev, "Couldn't read Register 2\n"); |
| return -EIO; |
| } |
| |
| ce = (reg2_value & REG2_CE_MASK) ? 0 : 1; |
| |
| return sprintf(buf, "%lu\n", ce); |
| } |
| |
| static ssize_t bq24251_ce_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct i2c_client *client = container_of(dev, struct i2c_client, dev); |
| struct bq24251_device *bq = i2c_get_clientdata(client); |
| unsigned long ce; |
| int reg2_value; |
| int ret; |
| |
| ret = strict_strtoul(buf, 10, &ce); |
| |
| if (ret) |
| return ret; |
| |
| mutex_lock(&bq->thread_mutex); |
| |
| reg2_value = i2c_smbus_read_byte_data(client, REG2_ADDR); |
| |
| if (reg2_value < 0) { |
| dev_err(dev, "Couldn't read Register 2\n"); |
| ret = reg2_value; |
| } else { |
| /* Clear out the reset bit */ |
| reg2_value &= ~REG2_RESET_MASK; |
| |
| if (ce) |
| reg2_value &= ~REG2_CE_MASK; |
| else |
| reg2_value |= REG2_CE_MASK; |
| |
| ret = i2c_smbus_write_byte_data(client, REG2_ADDR, reg2_value); |
| if (ret == 0) |
| ret = count; |
| } |
| |
| mutex_unlock(&bq->thread_mutex); |
| |
| return ret; |
| } |
| |
| |
| |
| 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, struct bq24251_device, bat_ps); |
| 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: |
| /* Always online */ |
| val->intval = 1; |
| 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_update_status(struct bq24251_device *bq) |
| { |
| int reg1_value; |
| int reg1_read_count = 0; |
| |
| /* Read Register 1 until fault bits match status bits */ |
| do { |
| 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_dbg(&bq->client->dev, "Register 1: 0x%02X\n", reg1_value); |
| reg1_read_count++; |
| |
| } while ((reg1_value & REG1_STATUS_MASK) != REG1_STATUS_FAULT && |
| (reg1_value & REG1_FAULT_MASK) != REG1_FAULT_NORMAL && |
| reg1_read_count < REG1_READ_COUNT_MAX); |
| |
| /* 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: |
| case REG1_FAULT_THERMAL_SHUTDOWN: |
| bq->health = POWER_SUPPLY_HEALTH_OVERHEAT; |
| break; |
| |
| default: |
| bq->health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; |
| break; |
| } |
| |
| 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 = 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 = 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_ps is a supplicant of ext_ps */ |
| if (!ext_ps->name) |
| return 0; |
| |
| for (i = 0; i < bq->bat_ps.num_supplies; i++) |
| if (!strcmp(ext_ps->name, bq->bat_ps.supplied_from[i])) |
| found = 1; |
| |
| if (!found) |
| return 0; |
| |
| /* Get the latest max current property */ |
| if (ext_ps->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, struct bq24251_device, bat_ps); |
| |
| class_for_each_device(power_supply_class, NULL, bq, bq24251_bat_get_ext_psy_data); |
| } |
| |
| |
| static struct device_attribute bq24251_sysoff_attr = { |
| .attr = { |
| .name = "sysoff", |
| .mode = S_IRUSR | S_IWUSR, |
| }, |
| .show = bq24251_sysoff_show, |
| .store = bq24251_sysoff_store, |
| }; |
| |
| static struct device_attribute bq24251_ce_attr = { |
| .attr = { |
| .name = "charging_enabled", |
| .mode = S_IRUSR | S_IWUSR, |
| }, |
| .show = bq24251_ce_show, |
| .store = bq24251_ce_store, |
| }; |
| |
| #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_ps.name = "bq24251-battery"; |
| bq->bat_ps.type = POWER_SUPPLY_TYPE_BATTERY; |
| bq->bat_ps.get_property = bq24251_bat_get_property; |
| bq->bat_ps.properties = bq24251_bat_props; |
| bq->bat_ps.num_properties = ARRAY_SIZE(bq24251_bat_props); |
| bq->bat_ps.external_power_changed = bq24251_bat_ext_changed; |
| bq->bat_ps.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_ps.num_properties--; |
| bq->chan = 0; |
| } |
| |
| /* Add sysfs files */ |
| ret = device_create_file(&client->dev, &bq24251_sysoff_attr); |
| if (ret) { |
| dev_err(&client->dev, "Failed to create sysoff sysfs file\n"); |
| goto probe_err_mutex_unlock; |
| } |
| |
| ret = device_create_file(&client->dev, &bq24251_ce_attr); |
| if (ret) { |
| dev_err(&client->dev, "Failed to create ce sysfs file\n"); |
| goto probe_err_sysfs_destroy; |
| } |
| |
| bq24251_update_status(bq); |
| mutex_unlock(&bq->thread_mutex); |
| |
| /* Register as power supply */ |
| power_supply_register(&client->dev, &bq->bat_ps); |
| |
| /* 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_sysfs_destroy: |
| device_remove_file(&client->dev, &bq24251_sysoff_attr); |
| |
| 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); |
| |
| device_remove_file(&client->dev, &bq24251_ce_attr); |
| |
| device_remove_file(&client->dev, &bq24251_sysoff_attr); |
| |
| 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"); |