forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
305 lines
6.8 KiB
305 lines
6.8 KiB
// SPDX-License-Identifier: GPL-2.0 |
|
/* |
|
* BQ27xxx battery monitor I2C driver |
|
* |
|
* Copyright (C) 2015 Texas Instruments Incorporated - https://www.ti.com/ |
|
* Andrew F. Davis <[email protected]> |
|
*/ |
|
|
|
#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); |
|
return ret; |
|
} |
|
} |
|
|
|
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 <[email protected]>"); |
|
MODULE_DESCRIPTION("BQ27xxx battery monitor i2c driver"); |
|
MODULE_LICENSE("GPL");
|
|
|