| // SPDX-License-Identifier: GPL-2.0 | 
| /* | 
|  * BQ27xxx battery monitor I2C driver | 
|  * | 
|  * Copyright (C) 2015 Texas Instruments Incorporated - https://www.ti.com/ | 
|  *    Andrew F. Davis <afd@ti.com> | 
|  */ | 
|   | 
| #include <linux/i2c.h> | 
| #include <linux/interrupt.h> | 
| #include <linux/module.h> | 
| #include <asm/unaligned.h> | 
|   | 
| #include <linux/power/bq27xxx_battery.h> | 
|   | 
| static DEFINE_IDR(battery_id); | 
| static DEFINE_MUTEX(battery_mutex); | 
|   | 
| static irqreturn_t bq27xxx_battery_irq_handler_thread(int irq, void *data) | 
| { | 
|     struct bq27xxx_device_info *di = data; | 
|   | 
|     bq27xxx_battery_update(di); | 
|   | 
|     return IRQ_HANDLED; | 
| } | 
|   | 
| static int bq27xxx_battery_i2c_read(struct bq27xxx_device_info *di, u8 reg, | 
|                     bool single) | 
| { | 
|     struct i2c_client *client = to_i2c_client(di->dev); | 
|     struct i2c_msg msg[2]; | 
|     u8 data[2]; | 
|     int ret; | 
|   | 
|     if (!client->adapter) | 
|         return -ENODEV; | 
|   | 
|     msg[0].addr = client->addr; | 
|     msg[0].flags = 0; | 
|     msg[0].buf = ® | 
|     msg[0].len = sizeof(reg); | 
|     msg[1].addr = client->addr; | 
|     msg[1].flags = I2C_M_RD; | 
|     msg[1].buf = data; | 
|     if (single) | 
|         msg[1].len = 1; | 
|     else | 
|         msg[1].len = 2; | 
|   | 
|     ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); | 
|     if (ret < 0) | 
|         return ret; | 
|   | 
|     if (!single) | 
|         ret = get_unaligned_le16(data); | 
|     else | 
|         ret = data[0]; | 
|   | 
|     return ret; | 
| } | 
|   | 
| static int bq27xxx_battery_i2c_write(struct bq27xxx_device_info *di, u8 reg, | 
|                      int value, bool single) | 
| { | 
|     struct i2c_client *client = to_i2c_client(di->dev); | 
|     struct i2c_msg msg; | 
|     u8 data[4]; | 
|     int ret; | 
|   | 
|     if (!client->adapter) | 
|         return -ENODEV; | 
|   | 
|     data[0] = reg; | 
|     if (single) { | 
|         data[1] = (u8) value; | 
|         msg.len = 2; | 
|     } else { | 
|         put_unaligned_le16(value, &data[1]); | 
|         msg.len = 3; | 
|     } | 
|   | 
|     msg.buf = data; | 
|     msg.addr = client->addr; | 
|     msg.flags = 0; | 
|   | 
|     ret = i2c_transfer(client->adapter, &msg, 1); | 
|     if (ret < 0) | 
|         return ret; | 
|     if (ret != 1) | 
|         return -EINVAL; | 
|     return 0; | 
| } | 
|   | 
| static int bq27xxx_battery_i2c_bulk_read(struct bq27xxx_device_info *di, u8 reg, | 
|                      u8 *data, int len) | 
| { | 
|     struct i2c_client *client = to_i2c_client(di->dev); | 
|     int ret; | 
|   | 
|     if (!client->adapter) | 
|         return -ENODEV; | 
|   | 
|     ret = i2c_smbus_read_i2c_block_data(client, reg, len, data); | 
|     if (ret < 0) | 
|         return ret; | 
|     if (ret != len) | 
|         return -EINVAL; | 
|     return 0; | 
| } | 
|   | 
| static int bq27xxx_battery_i2c_bulk_write(struct bq27xxx_device_info *di, | 
|                       u8 reg, u8 *data, int len) | 
| { | 
|     struct i2c_client *client = to_i2c_client(di->dev); | 
|     struct i2c_msg msg; | 
|     u8 buf[33]; | 
|     int ret; | 
|   | 
|     if (!client->adapter) | 
|         return -ENODEV; | 
|   | 
|     buf[0] = reg; | 
|     memcpy(&buf[1], data, len); | 
|   | 
|     msg.buf = buf; | 
|     msg.addr = client->addr; | 
|     msg.flags = 0; | 
|     msg.len = len + 1; | 
|   | 
|     ret = i2c_transfer(client->adapter, &msg, 1); | 
|     if (ret < 0) | 
|         return ret; | 
|     if (ret != 1) | 
|         return -EINVAL; | 
|     return 0; | 
| } | 
|   | 
| static int bq27xxx_battery_i2c_probe(struct i2c_client *client, | 
|                      const struct i2c_device_id *id) | 
| { | 
|     struct bq27xxx_device_info *di; | 
|     int ret; | 
|     char *name; | 
|     int num; | 
|   | 
|     /* Get new ID for the new battery device */ | 
|     mutex_lock(&battery_mutex); | 
|     num = idr_alloc(&battery_id, client, 0, 0, GFP_KERNEL); | 
|     mutex_unlock(&battery_mutex); | 
|     if (num < 0) | 
|         return num; | 
|   | 
|     name = devm_kasprintf(&client->dev, GFP_KERNEL, "%s-%d", id->name, num); | 
|     if (!name) | 
|         goto err_mem; | 
|   | 
|     di = devm_kzalloc(&client->dev, sizeof(*di), GFP_KERNEL); | 
|     if (!di) | 
|         goto err_mem; | 
|   | 
|     di->id = num; | 
|     di->dev = &client->dev; | 
|     di->chip = id->driver_data; | 
|     di->name = name; | 
|   | 
|     di->bus.read = bq27xxx_battery_i2c_read; | 
|     di->bus.write = bq27xxx_battery_i2c_write; | 
|     di->bus.read_bulk = bq27xxx_battery_i2c_bulk_read; | 
|     di->bus.write_bulk = bq27xxx_battery_i2c_bulk_write; | 
|   | 
|     ret = bq27xxx_battery_setup(di); | 
|     if (ret) | 
|         goto err_failed; | 
|   | 
|     /* Schedule a polling after about 1 min */ | 
|     schedule_delayed_work(&di->work, 60 * HZ); | 
|   | 
|     i2c_set_clientdata(client, di); | 
|   | 
|     if (client->irq) { | 
|         ret = devm_request_threaded_irq(&client->dev, client->irq, | 
|                 NULL, bq27xxx_battery_irq_handler_thread, | 
|                 IRQF_ONESHOT, | 
|                 di->name, di); | 
|         if (ret) { | 
|             dev_err(&client->dev, | 
|                 "Unable to register IRQ %d error %d\n", | 
|                 client->irq, ret); | 
|             bq27xxx_battery_teardown(di); | 
|             goto err_failed; | 
|         } | 
|     } | 
|   | 
|     return 0; | 
|   | 
| err_mem: | 
|     ret = -ENOMEM; | 
|   | 
| err_failed: | 
|     mutex_lock(&battery_mutex); | 
|     idr_remove(&battery_id, num); | 
|     mutex_unlock(&battery_mutex); | 
|   | 
|     return ret; | 
| } | 
|   | 
| static int bq27xxx_battery_i2c_remove(struct i2c_client *client) | 
| { | 
|     struct bq27xxx_device_info *di = i2c_get_clientdata(client); | 
|   | 
|     bq27xxx_battery_teardown(di); | 
|   | 
|     mutex_lock(&battery_mutex); | 
|     idr_remove(&battery_id, di->id); | 
|     mutex_unlock(&battery_mutex); | 
|   | 
|     return 0; | 
| } | 
|   | 
| static const struct i2c_device_id bq27xxx_i2c_id_table[] = { | 
|     { "bq27200", BQ27000 }, | 
|     { "bq27210", BQ27010 }, | 
|     { "bq27500", BQ2750X }, | 
|     { "bq27510", BQ2751X }, | 
|     { "bq27520", BQ2752X }, | 
|     { "bq27500-1", BQ27500 }, | 
|     { "bq27510g1", BQ27510G1 }, | 
|     { "bq27510g2", BQ27510G2 }, | 
|     { "bq27510g3", BQ27510G3 }, | 
|     { "bq27520g1", BQ27520G1 }, | 
|     { "bq27520g2", BQ27520G2 }, | 
|     { "bq27520g3", BQ27520G3 }, | 
|     { "bq27520g4", BQ27520G4 }, | 
|     { "bq27521", BQ27521 }, | 
|     { "bq27530", BQ27530 }, | 
|     { "bq27531", BQ27531 }, | 
|     { "bq27541", BQ27541 }, | 
|     { "bq27542", BQ27542 }, | 
|     { "bq27546", BQ27546 }, | 
|     { "bq27742", BQ27742 }, | 
|     { "bq27545", BQ27545 }, | 
|     { "bq27411", BQ27411 }, | 
|     { "bq27421", BQ27421 }, | 
|     { "bq27425", BQ27425 }, | 
|     { "bq27426", BQ27426 }, | 
|     { "bq27441", BQ27441 }, | 
|     { "bq27621", BQ27621 }, | 
|     { "bq27z561", BQ27Z561 }, | 
|     { "bq28z610", BQ28Z610 }, | 
|     { "bq34z100", BQ34Z100 }, | 
|     {}, | 
| }; | 
| MODULE_DEVICE_TABLE(i2c, bq27xxx_i2c_id_table); | 
|   | 
| #ifdef CONFIG_OF | 
| static const struct of_device_id bq27xxx_battery_i2c_of_match_table[] = { | 
|     { .compatible = "ti,bq27200" }, | 
|     { .compatible = "ti,bq27210" }, | 
|     { .compatible = "ti,bq27500" }, | 
|     { .compatible = "ti,bq27510" }, | 
|     { .compatible = "ti,bq27520" }, | 
|     { .compatible = "ti,bq27500-1" }, | 
|     { .compatible = "ti,bq27510g1" }, | 
|     { .compatible = "ti,bq27510g2" }, | 
|     { .compatible = "ti,bq27510g3" }, | 
|     { .compatible = "ti,bq27520g1" }, | 
|     { .compatible = "ti,bq27520g2" }, | 
|     { .compatible = "ti,bq27520g3" }, | 
|     { .compatible = "ti,bq27520g4" }, | 
|     { .compatible = "ti,bq27521" }, | 
|     { .compatible = "ti,bq27530" }, | 
|     { .compatible = "ti,bq27531" }, | 
|     { .compatible = "ti,bq27541" }, | 
|     { .compatible = "ti,bq27542" }, | 
|     { .compatible = "ti,bq27546" }, | 
|     { .compatible = "ti,bq27742" }, | 
|     { .compatible = "ti,bq27545" }, | 
|     { .compatible = "ti,bq27411" }, | 
|     { .compatible = "ti,bq27421" }, | 
|     { .compatible = "ti,bq27425" }, | 
|     { .compatible = "ti,bq27426" }, | 
|     { .compatible = "ti,bq27441" }, | 
|     { .compatible = "ti,bq27621" }, | 
|     { .compatible = "ti,bq27z561" }, | 
|     { .compatible = "ti,bq28z610" }, | 
|     { .compatible = "ti,bq34z100" }, | 
|     {}, | 
| }; | 
| MODULE_DEVICE_TABLE(of, bq27xxx_battery_i2c_of_match_table); | 
| #endif | 
|   | 
| static struct i2c_driver bq27xxx_battery_i2c_driver = { | 
|     .driver = { | 
|         .name = "bq27xxx-battery", | 
|         .of_match_table = of_match_ptr(bq27xxx_battery_i2c_of_match_table), | 
|     }, | 
|     .probe = bq27xxx_battery_i2c_probe, | 
|     .remove = bq27xxx_battery_i2c_remove, | 
|     .id_table = bq27xxx_i2c_id_table, | 
| }; | 
| module_i2c_driver(bq27xxx_battery_i2c_driver); | 
|   | 
| MODULE_AUTHOR("Andrew F. Davis <afd@ti.com>"); | 
| MODULE_DESCRIPTION("BQ27xxx battery monitor i2c driver"); | 
| MODULE_LICENSE("GPL"); |