This patch adds the silergy sy6410 single cell Li+ battery gas
gauge ic support,which is used to calculate the battery capacity.

Signed-off-by: Hong Peng <eli...@foxmail.com>

---
 drivers/power/supply/Kconfig          |   7 +
 drivers/power/supply/Makefile         |   1 +
 drivers/power/supply/sy6410_battery.c | 554 ++++++++++++++++++++++++++
 3 files changed, 562 insertions(+)
 create mode 100644 drivers/power/supply/sy6410_battery.c

diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index e901b9879e7e..7e37598f68c3 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -660,4 +660,11 @@ config FUEL_GAUGE_SC27XX
         Say Y here to enable support for fuel gauge with SC27XX
         PMIC chips.
 
+config SY6410_BATTERY
+       tristate "Silergy sy6410 single cell Li+ battery fuel gauge driver"
+       depends on I2C
+       help
+        Say Y here to enable support for fuel gauge with sy6410
+        PMIC chips,which is used to calculate the battery capacity
+
 endif # POWER_SUPPLY
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
index b731c2a9b695..330dbaa5319e 100644
--- a/drivers/power/supply/Makefile
+++ b/drivers/power/supply/Makefile
@@ -87,3 +87,4 @@ obj-$(CONFIG_AXP288_CHARGER)  += axp288_charger.o
 obj-$(CONFIG_CHARGER_CROS_USBPD)       += cros_usbpd-charger.o
 obj-$(CONFIG_CHARGER_SC2731)   += sc2731_charger.o
 obj-$(CONFIG_FUEL_GAUGE_SC27XX)        += sc27xx_fuel_gauge.o
+obj-$(CONFIG_SY6410_BATTERY)    += sy6410_battery.o
diff --git a/drivers/power/supply/sy6410_battery.c 
b/drivers/power/supply/sy6410_battery.c
index 000000000000..537dd1cf3e7f
--- /dev/null
+++ b/drivers/power/supply/sy6410_battery.c
@@ -0,0 +1,554 @@
+/**
+ * I2C client/driver for the Silergy sy6410 single Cell Li+ Battery Fuel Gauge
+ *
+ * Author: elicec
+ *
+ * Date: 2017-12-12 15:15:05
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/swab.h>
+#include <linux/debugfs.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/power_supply.h>
+#include <linux/slab.h>
+#include <linux/qpnp/qpnp-adc.h>
+
+
+#define SY6410_VBAT_REG        0x02        /* The Voltage of Battery */
+#define SY6410_SOC_REG         0x04        /* The SOC of battery */
+#define SY6410_MODE_REG        0x06        /* mode register */
+#define SY6410_VERSION_REG     0x08        /* production version of this IC */
+#define SY6410_CONFIG_REG      0x0c
+#define SY6410_VRESET_REG      0x18        /* the reset voltage */
+#define SY6410_STATUS_REG      0x1a
+
+#define SY6410_STATUS_MASK     0x0001
+
+#define SY6410_DELAY           1000
+
+#define SY6410_DEBUG_REG(x)    {#x, x##_REG}
+
+struct debug_reg {
+       char *name;
+       u8 reg;
+};
+
+static struct debug_reg sy6410_debug_regs[] = {
+       SY6410_DEBUG_REG(SY6410_VBAT),
+       SY6410_DEBUG_REG(SY6410_SOC),
+       SY6410_DEBUG_REG(SY6410_MODE),
+       SY6410_DEBUG_REG(SY6410_VRESET),
+       SY6410_DEBUG_REG(SY6410_CONFIG),
+       SY6410_DEBUG_REG(SY6410_VERSION),
+       SY6410_DEBUG_REG(SY6410_STATUS),
+};
+
+struct sy6410_info;
+
+struct sy6410_battery_ops {
+       int (*get_battery_status)(struct sy6410_info *info, int *status);
+       int (*get_battery_voltage)(struct sy6410_info *info, int *voltage_uV);
+       int (*get_battery_capacity)(struct sy6410_info *info, int *capacity);
+       int (*get_battery_temp)(struct sy6410_info *info, int *temp);
+};
+
+#define to_sy6410_info(x) container_of(x, struct sy6410_info, battery)
+
+struct sy6410_info {
+       struct i2c_client       *client;
+       struct power_supply     battery;
+       struct sy6410_battery_ops  *ops;
+       struct delayed_work     bat_work;
+       struct dentry *debug_root;
+       struct qpnp_vadc_chip *vadc_dev;
+       s16                     version;
+       int                     capacity;
+       int                     status;         /* State Of Charge */
+};
+
+static struct sy6410_info *the_chip;
+
+static inline int sy6410_read_reg(struct sy6410_info *info, int reg, u8 *val)
+{
+       int ret;
+
+       ret = i2c_smbus_read_byte_data(info->client, reg);
+       if (ret < 0) {
+               dev_err(&info->client->dev, "register read failed\n");
+               return ret;
+       }
+
+       *val = ret;
+       return 0;
+}
+
+static inline int sy6410_write_reg16(struct sy6410_info *info, int reg_msb,
+                                   u16 val)
+{
+       int ret;
+
+       ret = i2c_smbus_write_word_data(info->client, reg_msb, val);
+       if (ret < 0)
+               dev_err(&info->client->dev, "register write failed\n");
+
+       return ret;
+}
+
+static inline int sy6410_read_reg16(struct sy6410_info *info, int reg_msb,
+                                   s16 *val)
+{
+       int ret;
+
+       ret = i2c_smbus_read_word_data(info->client, reg_msb);
+       if (ret < 0) {
+               dev_err(&info->client->dev, "register read failed\n");
+               return ret;
+       }
+
+       *val = swab16(ret);
+       return 0;
+}
+
+//TODO get temp
+static int sy6410_get_temp(struct sy6410_info *info, int *temp)
+{
+       struct qpnp_vadc_result result;
+       int ret;
+       int batt_temp = 25;
+
+       if (PTR_ERR(info->vadc_dev) == -EPROBE_DEFER) {
+               pr_err("vadc not found try again\n");
+               info->vadc_dev = qpnp_get_vadc(&info->client->dev,
+                                                                       
"batterytemp");
+       }
+       ret = qpnp_vadc_read(info->vadc_dev, LR_MUX1_BATT_THERM, &result);
+       if (ret)
+               pr_err("unable to read battery temp:ret=%d\n", ret);
+       else
+               batt_temp = (int)result.physical;
+
+       pr_info("read battery temp:raw=%lld\n", result.physical);
+       *temp = batt_temp;
+       *temp = 25;
+
+       return 0;
+}
+
+static int sy6410_get_voltage(struct sy6410_info *info, int *voltage_uV)
+{
+       s16 raw;
+       int err;
+
+       /*
+        * Voltage is measured in units of 0.61mv,offset 2.5v. The voltage is
+        * a 12-bit number plus sign, in the upper bits of a 16-bit register
+        */
+       err = sy6410_read_reg16(info, SY6410_VBAT_REG, &raw);
+       if (err)
+               return err;
+       *voltage_uV = raw * 610 + 2500000;
+       dev_dbg(&info->client->dev, "vbat reg(%02x) = %02x\n", SY6410_VBAT_REG,
+               raw);
+
+       return 0;
+}
+
+static int sy6410_get_capacity(struct sy6410_info *info, int *capacity)
+{
+       int err;
+       u16 raw;
+
+       err = sy6410_read_reg16(info, SY6410_SOC_REG, &raw);
+       dev_dbg(&info->client->dev, "capacity reg(%02x) = %04x\n",
+               SY6410_SOC_REG, raw);
+       if (err)
+               return err;
+
+       *capacity = raw * 100/65535;
+
+       return 0;
+}
+
+#define SLEEP_REG_MASK 0x2000
+static int sy6410_sleep_enable(struct sy6410_info *info, bool enable)
+{
+       int err;
+       s16 raw;
+
+       err = sy6410_read_reg16(info, SY6410_MODE_REG, &raw);
+       if (err) {
+               dev_dbg(&info->client->dev, "sleep enable err! read reg(%02x)
+                       error = %d\n", SY6410_MODE_REG, err);
+               return err;
+       }
+       /*the MSB 13bit is the sleep enable*/
+       raw &= ~SLEEP_REG_MASK;
+       raw |= (enable << 13) & SLEEP_REG_MASK;
+
+       err = sy6410_write_reg16(info, SY6410_MODE_REG, raw);
+       dev_dbg(&info->client->dev, "mode reg(%02x) = %02x\n", SY6410_MODE_REG,
+               raw);
+
+       return err;
+
+}
+
+static int sy6410_get_charge_status(struct sy6410_info *info, int *status)
+{
+       int err;
+       s16 raw;
+
+       err = sy6410_read_reg16(info, SY6410_STATUS_REG, &raw);
+       dev_dbg(&info->client->dev, "status reg(%02x) = %04x\n",
+               SY6410_STATUS_REG, raw);
+       if (err)
+               return err;
+       *status = raw & SY6410_STATUS_MASK;
+       return 0;
+}
+
+static int sy6410_get_status(struct sy6410_info *info, int *status)
+{
+       int err;
+       int state;
+       int capacity;
+
+       err = info->ops->get_battery_capacity(info, &capacity);
+       err = info->ops->get_battery_status(info, &state);
+       if (err)
+               return err;
+
+       info->capacity = capacity;
+       info->status = state;
+
+       if (capacity == 100)
+               *status = POWER_SUPPLY_STATUS_FULL;
+       else if (state == 1)
+               *status = POWER_SUPPLY_STATUS_CHARGING;
+       else if (state == 0)
+               *status = POWER_SUPPLY_STATUS_DISCHARGING;
+       else
+               *status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+
+       return 0;
+}
+
+static int sy6410_battery_get_property(struct power_supply *psy,
+                                      enum power_supply_property prop,
+                                      union power_supply_propval *val)
+{
+       struct sy6410_info *info = to_sy6410_info(psy);
+       int ret;
+
+       switch (prop) {
+       case POWER_SUPPLY_PROP_CAPACITY:
+               ret = info->ops->get_battery_capacity(info, &val->intval);
+               break;
+
+       case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+               ret = info->ops->get_battery_voltage(info, &val->intval);
+               break;
+
+       case POWER_SUPPLY_PROP_TEMP:
+               ret = info->ops->get_battery_temp(info, &val->intval);
+               break;
+
+       default:
+               ret = -EINVAL;
+       }
+
+       return ret;
+}
+
+static void sy6410_battery_update(struct sy6410_info *info)
+{
+       /*TODO add temp*/
+       int old_status = info->status;
+       int old_capacity = info->capacity;
+
+       sy6410_get_status(info, &info->status);
+
+       if ((old_status != info->status) || (old_capacity != info->capacity))
+               power_supply_changed(&info->battery);
+}
+
+static void sy6410_battery_work(struct work_struct *work)
+{
+       struct sy6410_info *info;
+
+       info = container_of(work, struct sy6410_info, bat_work.work);
+       sy6410_battery_update(info);
+
+       schedule_delayed_work(&info->bat_work, SY6410_DELAY);
+}
+
+static int show_config_regs(struct seq_file *m, void *data)
+{
+       struct sy6410_info *info = m->private;
+       int rc;
+       int n;
+       u16 reg;
+       u8 addrs[] = {0x02, 0x04, 0x06, 0x08, 0x0c, 0x18, 0x1a};

+       for (n = 0; n < ARRAY_SIZE(addrs); n++) {
+               rc = sy6410_read_reg16(info, addrs[n], &reg);
+               seq_printf(m, "0x%02x = 0x%04x\n", addrs[n], reg);
+       }
+       return 0;
+}
+
+static int cnfg_debugfs_open(struct inode *inode, struct file *file)
+{
+       struct sy6410_info *info = inode->i_private;

+       return single_open(file, show_config_regs, info);
+}
+
+static const struct file_operations cnfg_debugfs_ops = {
+       .owner = THIS_MODULE,
+       .open = cnfg_debugfs_open,
+       .read = seq_read,
+       .llseek = seq_lseek,
+       .release = single_release,
+};
+
+static int sy6410_set_reg(void *data, u64 val)
+{
+       u32 addr = (long) data;
+       int ret;
+
+       ret = sy6410_write_reg16(the_chip, addr, (u16) val);
+
+       return ret;
+}
+
+static int sy6410_get_reg(void *data, u64 *val)
+{
+       u32 addr = (long) data;
+       int ret;
+       u16 raw;
+
+       ret = sy6410_read_reg16(the_chip, addr, &raw);
+       if (ret < 0)
+               return ret;
+
+       *val = raw;
+
+       return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(reg_fops, sy6410_get_reg, sy6410_set_reg,
+               "0x%02llx\n");
+
+static int sy6410_create_debugfs_entries(struct sy6410_info *info)
+{
+       int i;
+
+       info->debug_root = debugfs_create_dir("SY6410", NULL);
+       if (!info->debug_root)
+               dev_err(&info->client->dev, "Couldn't create debug dir\n");
+       if (info->debug_root) {
+               struct dentry *ent;
+
+               ent = debugfs_create_file("registers", 0444,
+                               info->debug_root, info, &cnfg_debugfs_ops);
+               if (!ent)
+                       dev_err(&info->client->dev, "Couldn't create debug 
file\n");
+       }
+
+       for (i = 0; i < ARRAY_SIZE(sy6410_debug_regs); i++) {
+               char *name = sy6410_debug_regs[i].name;
+               u32 reg = sy6410_debug_regs[i].reg;
+               struct dentry *file;
+
+               file = debugfs_create_file(name, 0644,
+                               info->debug_root, (void *)(long)reg, &reg_fops);
+
+               if (IS_ERR(file)) {
+                       pr_err("debugfs_create_file %s failed.\n", name);
+                       return -EFAULT;
+               }
+       }
+
+       return 0;
+}
+
+static enum power_supply_property sy6410_battery_props[] = {
+       POWER_SUPPLY_PROP_CAPACITY,
+       POWER_SUPPLY_PROP_VOLTAGE_NOW,
+       POWER_SUPPLY_PROP_TEMP,
+};
+
+static void sy6410_power_supply_init(struct power_supply *battery)
+{
+       battery->name = "sy6410_battery";
+       battery->type                   = POWER_SUPPLY_TYPE_BATTERY;
+       battery->properties             = sy6410_battery_props;
+       battery->num_properties         = ARRAY_SIZE(sy6410_battery_props);
+       battery->get_property           = sy6410_battery_get_property;
+       battery->external_power_changed = NULL;
+}
+
+static int sy6410_battery_remove(struct i2c_client *client)
+{
+       struct sy6410_info *info = i2c_get_clientdata(client);
+
+       power_supply_unregister(&info->battery);
+       kfree(info->battery.name);
+
+       cancel_delayed_work(&info->bat_work);
+       the_chip = NULL;
+
+       kfree(info);
+       return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+
+static int sy6410_suspend(struct device *dev)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct sy6410_info *info = i2c_get_clientdata(client);
+
+       cancel_delayed_work(&info->bat_work);
+       return 0;
+}
+
+static int sy6410_resume(struct device *dev)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct sy6410_info *info = i2c_get_clientdata(client);
+
+       schedule_delayed_work(&info->bat_work, SY6410_DELAY);
+       return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(sy6410_battery_pm_ops, sy6410_suspend,
+               sy6410_resume);
+#define SY6410_BATTERY_PM_OPS (&sy6410_battery_pm_ops)
+
+#else
+#define SY6410_BATTERY_PM_OPS NULL
+#endif /* CONFIG_PM_SLEEP */
+
+enum sy6410_num_id {
+       SY6410 = 0,
+       SY641X,
+};
+
+static struct sy6410_battery_ops sy6410_ops[] = {
+       [SY6410] = {
+               .get_battery_status  = sy6410_get_charge_status,
+               .get_battery_voltage  = sy6410_get_voltage,
+               .get_battery_capacity = sy6410_get_capacity,
+               .get_battery_temp = sy6410_get_temp,
+       },
+       [SY641X] = {
+               .get_battery_status  = sy6410_get_charge_status,
+               .get_battery_voltage  = sy6410_get_voltage,
+               .get_battery_capacity = sy6410_get_capacity,
+               .get_battery_temp = sy6410_get_temp,
+       }
+};
+
+static int sy6410_battery_probe(struct i2c_client *client,
+                               const struct i2c_device_id *id)
+{
+       struct sy6410_info *info;
+       int ret;
+
+       info = kzalloc(sizeof(*info), GFP_KERNEL);
+       if (!info) {
+               ret = -ENOMEM;
+               goto fail_id;
+       }
+
+       info->client = client;
+       info->ops  = &sy6410_ops[0];
+       info->capacity = 75;
+       info->status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+       info->vadc_dev = qpnp_get_vadc(&client->dev, "batterytemp");
+       if (IS_ERR(info->vadc_dev)) {
+               ret = PTR_ERR(info->vadc_dev);
+               if (ret == -EPROBE_DEFER)
+                       dev_err(&client->dev, "vadc not found rc=%d\n", ret);
+               else
+                       dev_err(&client->dev, "vadc prop rc=%d\n", ret);
+       }
+       ret = sy6410_read_reg16(info, SY6410_VERSION_REG, &info->version);
+       if (ret) {
+               pr_err("unable to read sy6410 version, absent?ret=%d\n", ret);
+               return -ENODEV;
+       }
+
+       i2c_set_clientdata(client, info);
+       sy6410_power_supply_init(&info->battery);
+       INIT_DELAYED_WORK(&info->bat_work, sy6410_battery_work);
+
+       ret = power_supply_register(&client->dev, &info->battery);
+       if (ret) {
+               dev_err(&client->dev, "failed to register battery\n");
+               goto fail_register;
+       } else {
+               schedule_delayed_work(&info->bat_work, SY6410_DELAY);
+       }
+
+       sy6410_sleep_enable(info, false);
+       the_chip = info;
+
+       sy6410_create_debugfs_entries(info);
+
+       dev_info(&client->dev, "sy6410 HW version: 0x%X\n", info->version);
+
+       return 0;
+
+fail_register:
+       kfree(info->battery.name);
+fail_id:
+       return ret;
+}
+
+static const struct i2c_device_id sy6410_id[] = {
+       {"sy6410", SY6410},
+       {},
+};
+
+MODULE_DEVICE_TABLE(i2c, sy6410_id);
+
+static const struct of_device_id sy6410_match[] = {
+       { .compatible = "silergy,sy6410-battery", },
+       {  },
+};
+
+static struct i2c_driver sy6410_battery_driver = {
+       .driver = {
+               .name   = "sy6410-battery",
+               .pm     = SY6410_BATTERY_PM_OPS,
+               .of_match_table = of_match_ptr(sy6410_match),
+       },
+       .probe          = sy6410_battery_probe,
+       .remove         = sy6410_battery_remove,
+       .id_table       = sy6410_id,
+};
+module_i2c_driver(sy6410_battery_driver);
+
+static int __init sy6410_init(void)
+{
+       return i2c_add_driver(&sy6410_battery_driver);
+}
+module_init(sy6410_init);
+
+static void __exit sy6410_exit(void)
+{
+       return i2c_del_driver(&sy6410_battery_driver);
+}
+module_exit(sy6410_exit);
+
+MODULE_AUTHOR("elicec");
+MODULE_DESCRIPTION("Silergy SY6410 single cell Li+ Fuel Gauage IC driver");
+MODULE_LICENSE("GPL");
-- 
2.17.1

bÁC

Reply via email to