Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/dtor/input
[firefly-linux-kernel-4.4.55.git] / drivers / power / bq20z75.c
index 492da27e1a47ed8960bd76e7626225e7fe0510f8..506585e31a5bee12ff53e3d7b3604f11d9d70902 100644 (file)
 #include <linux/power_supply.h>
 #include <linux/i2c.h>
 #include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/gpio.h>
+
+#include <linux/power/bq20z75.h>
 
 enum {
        REG_MANUFACTURER_DATA,
@@ -38,11 +42,22 @@ enum {
        REG_CYCLE_COUNT,
        REG_SERIAL_NUMBER,
        REG_REMAINING_CAPACITY,
+       REG_REMAINING_CAPACITY_CHARGE,
        REG_FULL_CHARGE_CAPACITY,
+       REG_FULL_CHARGE_CAPACITY_CHARGE,
        REG_DESIGN_CAPACITY,
+       REG_DESIGN_CAPACITY_CHARGE,
        REG_DESIGN_VOLTAGE,
 };
 
+/* Battery Mode defines */
+#define BATTERY_MODE_OFFSET            0x03
+#define BATTERY_MODE_MASK              0x8000
+enum bq20z75_battery_mode {
+       BATTERY_MODE_AMPS,
+       BATTERY_MODE_WATTS
+};
+
 /* manufacturer access defines */
 #define MANUFACTURER_ACCESS_STATUS     0x0006
 #define MANUFACTURER_ACCESS_SLEEP      0x0011
@@ -78,8 +93,12 @@ static const struct bq20z75_device_data {
                BQ20Z75_DATA(POWER_SUPPLY_PROP_CAPACITY, 0x0E, 0, 100),
        [REG_REMAINING_CAPACITY] =
                BQ20Z75_DATA(POWER_SUPPLY_PROP_ENERGY_NOW, 0x0F, 0, 65535),
+       [REG_REMAINING_CAPACITY_CHARGE] =
+               BQ20Z75_DATA(POWER_SUPPLY_PROP_CHARGE_NOW, 0x0F, 0, 65535),
        [REG_FULL_CHARGE_CAPACITY] =
                BQ20Z75_DATA(POWER_SUPPLY_PROP_ENERGY_FULL, 0x10, 0, 65535),
+       [REG_FULL_CHARGE_CAPACITY_CHARGE] =
+               BQ20Z75_DATA(POWER_SUPPLY_PROP_CHARGE_FULL, 0x10, 0, 65535),
        [REG_TIME_TO_EMPTY] =
                BQ20Z75_DATA(POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, 0x12, 0,
                        65535),
@@ -93,6 +112,9 @@ static const struct bq20z75_device_data {
        [REG_DESIGN_CAPACITY] =
                BQ20Z75_DATA(POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, 0x18, 0,
                        65535),
+       [REG_DESIGN_CAPACITY_CHARGE] =
+               BQ20Z75_DATA(POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, 0x18, 0,
+                       65535),
        [REG_DESIGN_VOLTAGE] =
                BQ20Z75_DATA(POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, 0x19, 0,
                        65535),
@@ -117,39 +139,72 @@ static enum power_supply_property bq20z75_properties[] = {
        POWER_SUPPLY_PROP_ENERGY_NOW,
        POWER_SUPPLY_PROP_ENERGY_FULL,
        POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN,
+       POWER_SUPPLY_PROP_CHARGE_NOW,
+       POWER_SUPPLY_PROP_CHARGE_FULL,
+       POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
 };
 
 struct bq20z75_info {
-       struct i2c_client       *client;
-       struct power_supply     power_supply;
+       struct i2c_client               *client;
+       struct power_supply             power_supply;
+       struct bq20z75_platform_data    *pdata;
+       bool                            is_present;
+       bool                            gpio_detect;
+       bool                            enable_detection;
+       int                             irq;
 };
 
 static int bq20z75_read_word_data(struct i2c_client *client, u8 address)
 {
-       s32 ret;
+       struct bq20z75_info *bq20z75_device = i2c_get_clientdata(client);
+       s32 ret = 0;
+       int retries = 1;
+
+       if (bq20z75_device->pdata)
+               retries = max(bq20z75_device->pdata->i2c_retry_count + 1, 1);
+
+       while (retries > 0) {
+               ret = i2c_smbus_read_word_data(client, address);
+               if (ret >= 0)
+                       break;
+               retries--;
+       }
 
-       ret = i2c_smbus_read_word_data(client, address);
        if (ret < 0) {
-               dev_err(&client->dev,
+               dev_dbg(&client->dev,
                        "%s: i2c read at address 0x%x failed\n",
                        __func__, address);
                return ret;
        }
+
        return le16_to_cpu(ret);
 }
 
 static int bq20z75_write_word_data(struct i2c_client *client, u8 address,
        u16 value)
 {
-       s32 ret;
+       struct bq20z75_info *bq20z75_device = i2c_get_clientdata(client);
+       s32 ret = 0;
+       int retries = 1;
+
+       if (bq20z75_device->pdata)
+               retries = max(bq20z75_device->pdata->i2c_retry_count + 1, 1);
+
+       while (retries > 0) {
+               ret = i2c_smbus_write_word_data(client, address,
+                       le16_to_cpu(value));
+               if (ret >= 0)
+                       break;
+               retries--;
+       }
 
-       ret = i2c_smbus_write_word_data(client, address, le16_to_cpu(value));
        if (ret < 0) {
-               dev_err(&client->dev,
+               dev_dbg(&client->dev,
                        "%s: i2c write to address 0x%x failed\n",
                        __func__, address);
                return ret;
        }
+
        return 0;
 }
 
@@ -158,6 +213,19 @@ static int bq20z75_get_battery_presence_and_health(
        union power_supply_propval *val)
 {
        s32 ret;
+       struct bq20z75_info *bq20z75_device = i2c_get_clientdata(client);
+
+       if (psp == POWER_SUPPLY_PROP_PRESENT &&
+               bq20z75_device->gpio_detect) {
+               ret = gpio_get_value(
+                       bq20z75_device->pdata->battery_detect);
+               if (ret == bq20z75_device->pdata->battery_detect_present)
+                       val->intval = 1;
+               else
+                       val->intval = 0;
+               bq20z75_device->is_present = val->intval;
+               return ret;
+       }
 
        /* Write to ManufacturerAccess with
         * ManufacturerAccess command and then
@@ -165,9 +233,11 @@ static int bq20z75_get_battery_presence_and_health(
        ret = bq20z75_write_word_data(client,
                bq20z75_data[REG_MANUFACTURER_DATA].addr,
                MANUFACTURER_ACCESS_STATUS);
-       if (ret < 0)
+       if (ret < 0) {
+               if (psp == POWER_SUPPLY_PROP_PRESENT)
+                       val->intval = 0; /* battery removed */
                return ret;
-
+       }
 
        ret = bq20z75_read_word_data(client,
                bq20z75_data[REG_MANUFACTURER_DATA].addr);
@@ -248,30 +318,39 @@ static void  bq20z75_unit_adjustment(struct i2c_client *client,
 {
 #define BASE_UNIT_CONVERSION           1000
 #define BATTERY_MODE_CAP_MULT_WATT     (10 * BASE_UNIT_CONVERSION)
-#define TIME_UNIT_CONVERSION           600
-#define TEMP_KELVIN_TO_CELCIUS         2731
+#define TIME_UNIT_CONVERSION           60
+#define TEMP_KELVIN_TO_CELSIUS         2731
        switch (psp) {
        case POWER_SUPPLY_PROP_ENERGY_NOW:
        case POWER_SUPPLY_PROP_ENERGY_FULL:
        case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN:
+               /* bq20z75 provides energy in units of 10mWh.
+                * Convert to µWh
+                */
                val->intval *= BATTERY_MODE_CAP_MULT_WATT;
                break;
 
        case POWER_SUPPLY_PROP_VOLTAGE_NOW:
        case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
        case POWER_SUPPLY_PROP_CURRENT_NOW:
+       case POWER_SUPPLY_PROP_CHARGE_NOW:
+       case POWER_SUPPLY_PROP_CHARGE_FULL:
+       case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
                val->intval *= BASE_UNIT_CONVERSION;
                break;
 
        case POWER_SUPPLY_PROP_TEMP:
-               /* bq20z75 provides battery tempreture in 0.1°K
-                * so convert it to 0.1°C */
-               val->intval -= TEMP_KELVIN_TO_CELCIUS;
-               val->intval *= 10;
+               /* bq20z75 provides battery temperature in 0.1K
+                * so convert it to 0.1°C
+                */
+               val->intval -= TEMP_KELVIN_TO_CELSIUS;
                break;
 
        case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG:
        case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG:
+               /* bq20z75 provides time to empty and time to full in minutes.
+                * Convert to seconds
+                */
                val->intval *= TIME_UNIT_CONVERSION;
                break;
 
@@ -281,11 +360,44 @@ static void  bq20z75_unit_adjustment(struct i2c_client *client,
        }
 }
 
+static enum bq20z75_battery_mode
+bq20z75_set_battery_mode(struct i2c_client *client,
+       enum bq20z75_battery_mode mode)
+{
+       int ret, original_val;
+
+       original_val = bq20z75_read_word_data(client, BATTERY_MODE_OFFSET);
+       if (original_val < 0)
+               return original_val;
+
+       if ((original_val & BATTERY_MODE_MASK) == mode)
+               return mode;
+
+       if (mode == BATTERY_MODE_AMPS)
+               ret = original_val & ~BATTERY_MODE_MASK;
+       else
+               ret = original_val | BATTERY_MODE_MASK;
+
+       ret = bq20z75_write_word_data(client, BATTERY_MODE_OFFSET, ret);
+       if (ret < 0)
+               return ret;
+
+       return original_val & BATTERY_MODE_MASK;
+}
+
 static int bq20z75_get_battery_capacity(struct i2c_client *client,
        int reg_offset, enum power_supply_property psp,
        union power_supply_propval *val)
 {
        s32 ret;
+       enum bq20z75_battery_mode mode = BATTERY_MODE_WATTS;
+
+       if (power_supply_is_amp_property(psp))
+               mode = BATTERY_MODE_AMPS;
+
+       mode = bq20z75_set_battery_mode(client, mode);
+       if (mode < 0)
+               return mode;
 
        ret = bq20z75_read_word_data(client, bq20z75_data[reg_offset].addr);
        if (ret < 0)
@@ -298,6 +410,10 @@ static int bq20z75_get_battery_capacity(struct i2c_client *client,
        } else
                val->intval = ret;
 
+       ret = bq20z75_set_battery_mode(client, mode);
+       if (ret < 0)
+               return ret;
+
        return 0;
 }
 
@@ -318,12 +434,25 @@ static int bq20z75_get_battery_serial_number(struct i2c_client *client,
        return 0;
 }
 
+static int bq20z75_get_property_index(struct i2c_client *client,
+       enum power_supply_property psp)
+{
+       int count;
+       for (count = 0; count < ARRAY_SIZE(bq20z75_data); count++)
+               if (psp == bq20z75_data[count].psp)
+                       return count;
+
+       dev_warn(&client->dev,
+               "%s: Invalid Property - %d\n", __func__, psp);
+
+       return -EINVAL;
+}
+
 static int bq20z75_get_property(struct power_supply *psy,
        enum power_supply_property psp,
        union power_supply_propval *val)
 {
-       int count;
-       int ret;
+       int ret = 0;
        struct bq20z75_info *bq20z75_device = container_of(psy,
                                struct bq20z75_info, power_supply);
        struct i2c_client *client = bq20z75_device->client;
@@ -332,8 +461,8 @@ static int bq20z75_get_property(struct power_supply *psy,
        case POWER_SUPPLY_PROP_PRESENT:
        case POWER_SUPPLY_PROP_HEALTH:
                ret = bq20z75_get_battery_presence_and_health(client, psp, val);
-               if (ret)
-                       return ret;
+               if (psp == POWER_SUPPLY_PROP_PRESENT)
+                       return 0;
                break;
 
        case POWER_SUPPLY_PROP_TECHNOLOGY:
@@ -343,22 +472,19 @@ static int bq20z75_get_property(struct power_supply *psy,
        case POWER_SUPPLY_PROP_ENERGY_NOW:
        case POWER_SUPPLY_PROP_ENERGY_FULL:
        case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN:
+       case POWER_SUPPLY_PROP_CHARGE_NOW:
+       case POWER_SUPPLY_PROP_CHARGE_FULL:
+       case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
        case POWER_SUPPLY_PROP_CAPACITY:
-               for (count = 0; count < ARRAY_SIZE(bq20z75_data); count++) {
-                       if (psp == bq20z75_data[count].psp)
-                               break;
-               }
-
-               ret = bq20z75_get_battery_capacity(client, count, psp, val);
-               if (ret)
-                       return ret;
+               ret = bq20z75_get_property_index(client, psp);
+               if (ret < 0)
+                       break;
 
+               ret = bq20z75_get_battery_capacity(client, ret, psp, val);
                break;
 
        case POWER_SUPPLY_PROP_SERIAL_NUMBER:
                ret = bq20z75_get_battery_serial_number(client, val);
-               if (ret)
-                       return ret;
                break;
 
        case POWER_SUPPLY_PROP_STATUS:
@@ -369,15 +495,11 @@ static int bq20z75_get_property(struct power_supply *psy,
        case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG:
        case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG:
        case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
-               for (count = 0; count < ARRAY_SIZE(bq20z75_data); count++) {
-                       if (psp == bq20z75_data[count].psp)
-                               break;
-               }
-
-               ret = bq20z75_get_battery_property(client, count, psp, val);
-               if (ret)
-                       return ret;
+               ret = bq20z75_get_property_index(client, psp);
+               if (ret < 0)
+                       break;
 
+               ret = bq20z75_get_battery_property(client, ret, psp, val);
                break;
 
        default:
@@ -386,26 +508,58 @@ static int bq20z75_get_property(struct power_supply *psy,
                return -EINVAL;
        }
 
-       /* Convert units to match requirements for power supply class */
-       bq20z75_unit_adjustment(client, psp, val);
+       if (!bq20z75_device->enable_detection)
+               goto done;
+
+       if (!bq20z75_device->gpio_detect &&
+               bq20z75_device->is_present != (ret >= 0)) {
+               bq20z75_device->is_present = (ret >= 0);
+               power_supply_changed(&bq20z75_device->power_supply);
+       }
+
+done:
+       if (!ret) {
+               /* Convert units to match requirements for power supply class */
+               bq20z75_unit_adjustment(client, psp, val);
+       }
 
        dev_dbg(&client->dev,
-               "%s: property = %d, value = %d\n", __func__, psp, val->intval);
+               "%s: property = %d, value = %x\n", __func__, psp, val->intval);
+
+       if (ret && bq20z75_device->is_present)
+               return ret;
+
+       /* battery not present, so return NODATA for properties */
+       if (ret)
+               return -ENODATA;
 
        return 0;
 }
 
-static int bq20z75_probe(struct i2c_client *client,
+static irqreturn_t bq20z75_irq(int irq, void *devid)
+{
+       struct power_supply *battery = devid;
+
+       power_supply_changed(battery);
+
+       return IRQ_HANDLED;
+}
+
+static int __devinit bq20z75_probe(struct i2c_client *client,
        const struct i2c_device_id *id)
 {
        struct bq20z75_info *bq20z75_device;
+       struct bq20z75_platform_data *pdata = client->dev.platform_data;
        int rc;
+       int irq;
 
        bq20z75_device = kzalloc(sizeof(struct bq20z75_info), GFP_KERNEL);
        if (!bq20z75_device)
                return -ENOMEM;
 
        bq20z75_device->client = client;
+       bq20z75_device->enable_detection = false;
+       bq20z75_device->gpio_detect = false;
        bq20z75_device->power_supply.name = "battery";
        bq20z75_device->power_supply.type = POWER_SUPPLY_TYPE_BATTERY;
        bq20z75_device->power_supply.properties = bq20z75_properties;
@@ -413,26 +567,86 @@ static int bq20z75_probe(struct i2c_client *client,
                ARRAY_SIZE(bq20z75_properties);
        bq20z75_device->power_supply.get_property = bq20z75_get_property;
 
+       if (pdata) {
+               bq20z75_device->gpio_detect =
+                       gpio_is_valid(pdata->battery_detect);
+               bq20z75_device->pdata = pdata;
+       }
+
        i2c_set_clientdata(client, bq20z75_device);
 
+       if (!bq20z75_device->gpio_detect)
+               goto skip_gpio;
+
+       rc = gpio_request(pdata->battery_detect, dev_name(&client->dev));
+       if (rc) {
+               dev_warn(&client->dev, "Failed to request gpio: %d\n", rc);
+               bq20z75_device->gpio_detect = false;
+               goto skip_gpio;
+       }
+
+       rc = gpio_direction_input(pdata->battery_detect);
+       if (rc) {
+               dev_warn(&client->dev, "Failed to get gpio as input: %d\n", rc);
+               gpio_free(pdata->battery_detect);
+               bq20z75_device->gpio_detect = false;
+               goto skip_gpio;
+       }
+
+       irq = gpio_to_irq(pdata->battery_detect);
+       if (irq <= 0) {
+               dev_warn(&client->dev, "Failed to get gpio as irq: %d\n", irq);
+               gpio_free(pdata->battery_detect);
+               bq20z75_device->gpio_detect = false;
+               goto skip_gpio;
+       }
+
+       rc = request_irq(irq, bq20z75_irq,
+               IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+               dev_name(&client->dev), &bq20z75_device->power_supply);
+       if (rc) {
+               dev_warn(&client->dev, "Failed to request irq: %d\n", rc);
+               gpio_free(pdata->battery_detect);
+               bq20z75_device->gpio_detect = false;
+               goto skip_gpio;
+       }
+
+       bq20z75_device->irq = irq;
+
+skip_gpio:
+
        rc = power_supply_register(&client->dev, &bq20z75_device->power_supply);
        if (rc) {
                dev_err(&client->dev,
                        "%s: Failed to register power supply\n", __func__);
-               kfree(bq20z75_device);
-               return rc;
+               goto exit_psupply;
        }
 
        dev_info(&client->dev,
                "%s: battery gas gauge device registered\n", client->name);
 
        return 0;
+
+exit_psupply:
+       if (bq20z75_device->irq)
+               free_irq(bq20z75_device->irq, &bq20z75_device->power_supply);
+       if (bq20z75_device->gpio_detect)
+               gpio_free(pdata->battery_detect);
+
+       kfree(bq20z75_device);
+
+       return rc;
 }
 
-static int bq20z75_remove(struct i2c_client *client)
+static int __devexit bq20z75_remove(struct i2c_client *client)
 {
        struct bq20z75_info *bq20z75_device = i2c_get_clientdata(client);
 
+       if (bq20z75_device->irq)
+               free_irq(bq20z75_device->irq, &bq20z75_device->power_supply);
+       if (bq20z75_device->gpio_detect)
+               gpio_free(bq20z75_device->pdata->battery_detect);
+
        power_supply_unregister(&bq20z75_device->power_supply);
        kfree(bq20z75_device);
        bq20z75_device = NULL;
@@ -444,13 +658,14 @@ static int bq20z75_remove(struct i2c_client *client)
 static int bq20z75_suspend(struct i2c_client *client,
        pm_message_t state)
 {
+       struct bq20z75_info *bq20z75_device = i2c_get_clientdata(client);
        s32 ret;
 
        /* write to manufacturer access with sleep command */
        ret = bq20z75_write_word_data(client,
                bq20z75_data[REG_MANUFACTURER_DATA].addr,
                MANUFACTURER_ACCESS_SLEEP);
-       if (ret < 0)
+       if (bq20z75_device->is_present && ret < 0)
                return ret;
 
        return 0;
@@ -465,10 +680,11 @@ static const struct i2c_device_id bq20z75_id[] = {
        { "bq20z75", 0 },
        {}
 };
+MODULE_DEVICE_TABLE(i2c, bq20z75_id);
 
 static struct i2c_driver bq20z75_battery_driver = {
        .probe          = bq20z75_probe,
-       .remove         = bq20z75_remove,
+       .remove         = __devexit_p(bq20z75_remove),
        .suspend        = bq20z75_suspend,
        .resume         = bq20z75_resume,
        .id_table       = bq20z75_id,