diff mbox

[v2,1/4] mfd: da9150: Add support for Fuel-Gauge

Message ID 11b930c87ee9728ce8911896aa673e2ad1dedf40.1435308209.git.Adam.Thomson.Opensource@diasemi.com (mailing list archive)
State Not Applicable, archived
Headers show

Commit Message

Adam Thomson June 26, 2015, 8:47 a.m. UTC
Signed-off-by: Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
---
 drivers/mfd/da9150-core.c       | 162 ++++++++++++++++++++++++++++++++++++++--
 include/linux/mfd/da9150/core.h |  19 +++++
 include/linux/mfd/da9150/fg.h   |  29 +++++++
 3 files changed, 202 insertions(+), 8 deletions(-)
 create mode 100644 include/linux/mfd/da9150/fg.h

--
1.9.3

--
To unsubscribe from this list: send the line "unsubscribe linux-pm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Comments

Lee Jones July 3, 2015, 3:16 p.m. UTC | #1
On Fri, 26 Jun 2015, Adam Thomson wrote:

> Signed-off-by: Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
> ---

Changelog v1 => v2?

>  drivers/mfd/da9150-core.c       | 162 ++++++++++++++++++++++++++++++++++++++--
>  include/linux/mfd/da9150/core.h |  19 +++++
>  include/linux/mfd/da9150/fg.h   |  29 +++++++
>  3 files changed, 202 insertions(+), 8 deletions(-)
>  create mode 100644 include/linux/mfd/da9150/fg.h
> 
> diff --git a/drivers/mfd/da9150-core.c b/drivers/mfd/da9150-core.c
> index 5549817..8feb75d 100644
> --- a/drivers/mfd/da9150-core.c
> +++ b/drivers/mfd/da9150-core.c
> @@ -21,8 +21,80 @@
>  #include <linux/interrupt.h>
>  #include <linux/mfd/core.h>
>  #include <linux/mfd/da9150/core.h>
> +#include <linux/mfd/da9150/fg.h>
>  #include <linux/mfd/da9150/registers.h>
> 
> +/* Raw device access, used for QIF */
> +static int da9150_i2c_read_device(struct i2c_client *client, u8 addr, int count,
> +				  u8 *buf)
> +{
> +	struct i2c_msg xfer;
> +	int ret;
> +
> +	/*
> +	 * Read is split into two transfers as device expects STOP/START rather
> +	 * than repeated start to carry out this kind of access.
> +	 */
> +
> +	/* Write address */
> +	xfer.addr = client->addr;
> +	xfer.flags = 0;
> +	xfer.len = 1;
> +	xfer.buf = &addr;
> +
> +	ret = i2c_transfer(client->adapter, &xfer, 1);
> +	if (ret != 1) {
> +		if (ret < 0)
> +			return ret;
> +		else
> +			return -EIO;
> +	}
> +
> +	/* Read data */
> +	xfer.addr = client->addr;
> +	xfer.flags = I2C_M_RD;
> +	xfer.len = count;
> +	xfer.buf = buf;
> +
> +	ret = i2c_transfer(client->adapter, &xfer, 1);
> +	if (ret == 1)
> +		return 0;
> +	else if (ret < 0)
> +		return ret;
> +	else
> +		return -EIO;
> +}
> +
> +static int da9150_i2c_write_device(struct i2c_client *client, u8 addr,
> +				   int count, const u8 *buf)
> +{
> +	struct i2c_msg xfer;
> +	u8 *reg_data;
> +	int ret;
> +
> +	reg_data = kzalloc(1 + count, GFP_KERNEL);
> +	if (!reg_data)
> +		return -ENOMEM;
> +
> +	reg_data[0] = addr;
> +	memcpy(&reg_data[1], buf, count);
> +
> +	/* Write address & data */
> +	xfer.addr = client->addr;
> +	xfer.flags = 0;
> +	xfer.len = 1 + count;
> +	xfer.buf = reg_data;
> +
> +	ret = i2c_transfer(client->adapter, &xfer, 1);
> +	kfree(reg_data);
> +	if (ret == 1)
> +		return 0;
> +	else if (ret < 0)
> +		return ret;
> +	else
> +		return -EIO;
> +}
> +
>  static bool da9150_volatile_reg(struct device *dev, unsigned int reg)
>  {
>  	switch (reg) {
> @@ -107,6 +179,28 @@ static const struct regmap_config da9150_regmap_config = {
>  	.volatile_reg = da9150_volatile_reg,
>  };
> 
> +void da9150_read_qif(struct da9150 *da9150, u8 addr, int count, u8 *buf)
> +{
> +	int ret;
> +
> +	ret = da9150->read_dev(da9150->core_qif, addr, count, buf);
> +	if (ret < 0)
> +		dev_err(da9150->dev, "Failed to read from QIF 0x%x: %d\n",
> +			addr, ret);
> +}
> +EXPORT_SYMBOL_GPL(da9150_read_qif);
> +
> +void da9150_write_qif(struct da9150 *da9150, u8 addr, int count, const u8 *buf)
> +{
> +	int ret;
> +
> +	ret = da9150->write_dev(da9150->core_qif, addr, count, buf);
> +	if (ret < 0)
> +		dev_err(da9150->dev, "Failed to write to QIF 0x%x: %d\n",
> +			addr, ret);
> +}
> +EXPORT_SYMBOL_GPL(da9150_write_qif);
> +
>  u8 da9150_reg_read(struct da9150 *da9150, u16 reg)
>  {
>  	int val, ret;
> @@ -297,6 +391,15 @@ static struct resource da9150_charger_resources[] = {
>  	},
>  };
> 
> +static struct resource da9150_fg_resources[] = {
> +	{
> +		.name = "FG",
> +		.start = DA9150_IRQ_FG,
> +		.end = DA9150_IRQ_FG,
> +		.flags = IORESOURCE_IRQ,

Can you use the DEFINE_RES_* helpers? 

> +	},
> +};
> +
>  static struct mfd_cell da9150_devs[] = {
>  	{
>  		.name = "da9150-gpadc",
> @@ -310,6 +413,12 @@ static struct mfd_cell da9150_devs[] = {
>  		.resources = da9150_charger_resources,
>  		.num_resources = ARRAY_SIZE(da9150_charger_resources),
>  	},
> +	{
> +		.name = "da9150-fuelgauge",
> +		.of_compatible = "dlg,da9150-fg",
> +		.resources = da9150_fg_resources,
> +		.num_resources = ARRAY_SIZE(da9150_fg_resources),
> +	},
>  };
> 
>  static int da9150_probe(struct i2c_client *client,
> @@ -317,11 +426,14 @@ static int da9150_probe(struct i2c_client *client,
>  {
>  	struct da9150 *da9150;
>  	struct da9150_pdata *pdata = dev_get_platdata(&client->dev);
> +	int qif_addr;
>  	int ret;
> 
>  	da9150 = devm_kzalloc(&client->dev, sizeof(*da9150), GFP_KERNEL);
> -	if (!da9150)
> -		return -ENOMEM;
> +	if (!da9150) {
> +		ret = -ENOMEM;
> +		goto fail;

Don't do this.  'fail' doesn't do anything, just return here.

> +	}
> 
>  	da9150->dev = &client->dev;
>  	da9150->irq = client->irq;
> @@ -332,19 +444,46 @@ static int da9150_probe(struct i2c_client *client,
>  		ret = PTR_ERR(da9150->regmap);
>  		dev_err(da9150->dev, "Failed to allocate register map: %d\n",
>  			ret);
> -		return ret;
> +		goto fail;

It was better before.

> +	}
> +
> +	/* Setup secondary I2C interface for QIF access */
> +	qif_addr = da9150_reg_read(da9150, DA9150_CORE2WIRE_CTRL_A);
> +	qif_addr = (qif_addr & DA9150_CORE_BASE_ADDR_MASK) >> 1;
> +	qif_addr |= DA9150_QIF_I2C_ADDR_LSB;
> +	da9150->core_qif = i2c_new_dummy(client->adapter, qif_addr);
> +	if (!da9150->core_qif) {
> +		dev_err(da9150->dev, "Failed to attach QIF client\n");
> +		ret = -ENODEV;
> +		goto fail;

Same.

>  	}
> 
> -	da9150->irq_base = pdata ? pdata->irq_base : -1;
> +	i2c_set_clientdata(da9150->core_qif, da9150);
> +	da9150->read_dev = (read_dev_t) da9150_i2c_read_device;
> +	da9150->write_dev = (write_dev_t) da9150_i2c_write_device;

Is it possible for read_dev to be set to anything other than
da9150_i2c_read_device?

> +	if (pdata) {
> +		da9150->irq_base = pdata->irq_base;
> +
> +		da9150_devs[2].platform_data = pdata->fg_pdata;
> +		da9150_devs[2].pdata_size = sizeof(struct da9150_fg_pdata);

This is fragile.

If another device gets inserted above the FG, this will break.

> +	} else {
> +		da9150->irq_base = -1;
> +	}
> 
>  	ret = regmap_add_irq_chip(da9150->regmap, da9150->irq,
>  				  IRQF_TRIGGER_LOW | IRQF_ONESHOT,
>  				  da9150->irq_base, &da9150_regmap_irq_chip,
>  				  &da9150->regmap_irq_data);
> -	if (ret)
> -		return ret;
> +	if (ret) {
> +		dev_err(da9150->dev, "Failed to add regmap irq chip: %d\n",
> +			ret);
> +		goto regmap_irq_fail;
> +	}
> +
> 
>  	da9150->irq_base = regmap_irq_chip_get_base(da9150->regmap_irq_data);
> +
>  	enable_irq_wake(da9150->irq);
> 
>  	ret = mfd_add_devices(da9150->dev, -1, da9150_devs,
> @@ -352,11 +491,17 @@ static int da9150_probe(struct i2c_client *client,
>  			      da9150->irq_base, NULL);
>  	if (ret) {
>  		dev_err(da9150->dev, "Failed to add child devices: %d\n", ret);
> -		regmap_del_irq_chip(da9150->irq, da9150->regmap_irq_data);
> -		return ret;
> +		goto mfd_fail;
>  	}
> 
>  	return 0;
> +
> +mfd_fail:
> +	regmap_del_irq_chip(da9150->irq, da9150->regmap_irq_data);
> +regmap_irq_fail:
> +	i2c_unregister_device(da9150->core_qif);
> +fail:
> +	return ret;
>  }
> 
>  static int da9150_remove(struct i2c_client *client)
> @@ -365,6 +510,7 @@ static int da9150_remove(struct i2c_client *client)
> 
>  	regmap_del_irq_chip(da9150->irq, da9150->regmap_irq_data);
>  	mfd_remove_devices(da9150->dev);
> +	i2c_unregister_device(da9150->core_qif);
> 
>  	return 0;
>  }
> diff --git a/include/linux/mfd/da9150/core.h b/include/linux/mfd/da9150/core.h
> index 76e6689..98ecfea 100644
> --- a/include/linux/mfd/da9150/core.h
> +++ b/include/linux/mfd/da9150/core.h
> @@ -15,9 +15,11 @@
>  #define __DA9150_CORE_H
> 
>  #include <linux/device.h>
> +#include <linux/i2c.h>
>  #include <linux/interrupt.h>
>  #include <linux/regmap.h>
> 
> +

Please remove this.

>  /* I2C address paging */
>  #define DA9150_REG_PAGE_SHIFT	8
>  #define DA9150_REG_PAGE_MASK	0xFF
> @@ -46,23 +48,40 @@
>  #define DA9150_IRQ_GPADC	19
>  #define DA9150_IRQ_WKUP		20
> 
> +/* Platform Data */

No need for this comment.  The pdata in the name give it away.

> +struct da9150_fg_pdata;

Why can't you just put the struct definition in here?

>  struct da9150_pdata {
>  	int irq_base;
> +	struct da9150_fg_pdata *fg_pdata;
>  };
> 
> +/* I/O function typedefs for raw access */
> +typedef int (*read_dev_t)(void *client, u8 addr, int count, u8 *buf);
> +typedef int (*write_dev_t)(void *client, u8 addr, int count, const u8 *buf);

No need to typedef, just pop them directly into the struct.  Although
I'm not convinced that need them at all really.

>  struct da9150 {
>  	struct device *dev;
>  	struct regmap *regmap;
> +	struct i2c_client *core_qif;
> +
> +	read_dev_t read_dev;
> +	write_dev_t write_dev;
> +
>  	struct regmap_irq_chip_data *regmap_irq_data;
>  	int irq;
>  	int irq_base;

Why are there 2 irq_bases?  That could get pretty confusing.

Why is it being stored?

Same with irq.

>  };
> 
>  /* Device I/O */
> +void da9150_read_qif(struct da9150 *da9150, u8 addr, int count, u8 *buf);
> +void da9150_write_qif(struct da9150 *da9150, u8 addr, int count, const u8 *buf);

What is qif?  The naming convention isn't very transparent.

>  u8 da9150_reg_read(struct da9150 *da9150, u16 reg);
>  void da9150_reg_write(struct da9150 *da9150, u16 reg, u8 val);
>  void da9150_set_bits(struct da9150 *da9150, u16 reg, u8 mask, u8 val);
> 
>  void da9150_bulk_read(struct da9150 *da9150, u16 reg, int count, u8 *buf);
>  void da9150_bulk_write(struct da9150 *da9150, u16 reg, int count, const u8 *buf);
> +
>  #endif /* __DA9150_CORE_H */
> diff --git a/include/linux/mfd/da9150/fg.h b/include/linux/mfd/da9150/fg.h
> new file mode 100644
> index 0000000..0ff52ab
> --- /dev/null
> +++ b/include/linux/mfd/da9150/fg.h
> @@ -0,0 +1,29 @@
> +/*
> + * DA9150 MFD Driver - Fuel Gauge Data

Does it really require it's very own header file?

> + * Copyright (c) 2015 Dialog Semiconductor
> + *
> + * Author: Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
> + *
> + * 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;  either version 2 of the  License, or (at your
> + * option) any later version.
> + */
> +
> +#ifndef __DA9150_FG_H
> +#define __DA9150_FG_H
> +
> +#include <linux/power_supply.h>
> +
> +/* I2C sub-device address */
> +#define DA9150_QIF_I2C_ADDR_LSB		0x5
> +
> +/* Platform data */
> +struct da9150_fg_pdata {
> +	u32 update_interval;	/* msecs */
> +	u8 warn_soc_lvl;	/* % value */
> +	u8 crit_soc_lvl;	/* % value */
> +};
> +
> +#endif /* __DA9150_FG_H */
Adam Thomson July 6, 2015, 2:03 p.m. UTC | #2
T24gSnVseSAzLCAyMDE1IDE2OjE2LCBMZWUgSm9uZXMgd3JvdGU6DQoNCj4gDQo+IENoYW5nZWxv
ZyB2MSA9PiB2Mj8NCg0KSXMgZGVzY3JpYmVkIGluIHRoZSBjb3ZlciBsZXR0ZXIsIGJ1dCBjYW4g
bW92ZSB0aGF0IGluZm9ybWF0aW9uIHRvIHRoZSBpbmRpdmlkdWFsDQpwYXRjaGVzIGluIHRoZSBm
dXR1cmUsIGlmIHRoYXQgaGVscHMuDQoNCj4gPg0KPiA+ICtzdGF0aWMgc3RydWN0IHJlc291cmNl
IGRhOTE1MF9mZ19yZXNvdXJjZXNbXSA9IHsNCj4gPiArCXsNCj4gPiArCQkubmFtZSA9ICJGRyIs
DQo+ID4gKwkJLnN0YXJ0ID0gREE5MTUwX0lSUV9GRywNCj4gPiArCQkuZW5kID0gREE5MTUwX0lS
UV9GRywNCj4gPiArCQkuZmxhZ3MgPSBJT1JFU09VUkNFX0lSUSwNCj4gDQo+IENhbiB5b3UgdXNl
IHRoZSBERUZJTkVfUkVTXyogaGVscGVycz8NCg0KSGFkbid0IHNwb3R0ZWQgdGhvc2UgYmVmb3Jl
LiBXaWxsIHVzZSB0aG9zZSBpbnN0ZWFkLg0KDQo+ID4gLQlpZiAoIWRhOTE1MCkNCj4gPiAtCQly
ZXR1cm4gLUVOT01FTTsNCj4gPiArCWlmICghZGE5MTUwKSB7DQo+ID4gKwkJcmV0ID0gLUVOT01F
TTsNCj4gPiArCQlnb3RvIGZhaWw7DQo+IA0KPiBEb24ndCBkbyB0aGlzLiAgJ2ZhaWwnIGRvZXNu
J3QgZG8gYW55dGhpbmcsIGp1c3QgcmV0dXJuIGhlcmUuDQoNCk9rLiBJIHdhcyBvcHRpbmcgZm9y
IGNob29zaW5nIG9uZSBjb25zaXN0ZW50IG1ldGhvZCBvZiBoYW5kbGluZyBmYWlsdXJlLCByYXRo
ZXINCnRoYW4gYSBjb21iaW5hdGlvbiBvZiBnb3RvcyBhbmQgaW1tZWRpYXRlIHJldHVybnMsIGJ1
dCB3aWxsIGNoYW5nZS4NCg0KPiANCj4gPiArCX0NCj4gPg0KPiA+ICAJZGE5MTUwLT5kZXYgPSAm
Y2xpZW50LT5kZXY7DQo+ID4gIAlkYTkxNTAtPmlycSA9IGNsaWVudC0+aXJxOw0KPiA+IEBAIC0z
MzIsMTkgKzQ0NCw0NiBAQCBzdGF0aWMgaW50IGRhOTE1MF9wcm9iZShzdHJ1Y3QgaTJjX2NsaWVu
dCAqY2xpZW50LA0KPiA+ICAJCXJldCA9IFBUUl9FUlIoZGE5MTUwLT5yZWdtYXApOw0KPiA+ICAJ
CWRldl9lcnIoZGE5MTUwLT5kZXYsICJGYWlsZWQgdG8gYWxsb2NhdGUgcmVnaXN0ZXIgbWFwOiAl
ZFxuIiwNCj4gPiAgCQkJcmV0KTsNCj4gPiAtCQlyZXR1cm4gcmV0Ow0KPiA+ICsJCWdvdG8gZmFp
bDsNCj4gDQo+IEl0IHdhcyBiZXR0ZXIgYmVmb3JlLg0KDQpPaw0KDQo+IA0KPiA+ICsJfQ0KPiA+
ICsNCj4gPiArCS8qIFNldHVwIHNlY29uZGFyeSBJMkMgaW50ZXJmYWNlIGZvciBRSUYgYWNjZXNz
ICovDQo+ID4gKwlxaWZfYWRkciA9IGRhOTE1MF9yZWdfcmVhZChkYTkxNTAsIERBOTE1MF9DT1JF
MldJUkVfQ1RSTF9BKTsNCj4gPiArCXFpZl9hZGRyID0gKHFpZl9hZGRyICYgREE5MTUwX0NPUkVf
QkFTRV9BRERSX01BU0spID4+IDE7DQo+ID4gKwlxaWZfYWRkciB8PSBEQTkxNTBfUUlGX0kyQ19B
RERSX0xTQjsNCj4gPiArCWRhOTE1MC0+Y29yZV9xaWYgPSBpMmNfbmV3X2R1bW15KGNsaWVudC0+
YWRhcHRlciwgcWlmX2FkZHIpOw0KPiA+ICsJaWYgKCFkYTkxNTAtPmNvcmVfcWlmKSB7DQo+ID4g
KwkJZGV2X2VycihkYTkxNTAtPmRldiwgIkZhaWxlZCB0byBhdHRhY2ggUUlGIGNsaWVudFxuIik7
DQo+ID4gKwkJcmV0ID0gLUVOT0RFVjsNCj4gPiArCQlnb3RvIGZhaWw7DQo+IA0KPiBTYW1lLg0K
DQpPaw0KDQo+IA0KPiA+ICAJfQ0KPiA+DQo+ID4gLQlkYTkxNTAtPmlycV9iYXNlID0gcGRhdGEg
PyBwZGF0YS0+aXJxX2Jhc2UgOiAtMTsNCj4gPiArCWkyY19zZXRfY2xpZW50ZGF0YShkYTkxNTAt
PmNvcmVfcWlmLCBkYTkxNTApOw0KPiA+ICsJZGE5MTUwLT5yZWFkX2RldiA9IChyZWFkX2Rldl90
KSBkYTkxNTBfaTJjX3JlYWRfZGV2aWNlOw0KPiA+ICsJZGE5MTUwLT53cml0ZV9kZXYgPSAod3Jp
dGVfZGV2X3QpIGRhOTE1MF9pMmNfd3JpdGVfZGV2aWNlOw0KPiANCj4gSXMgaXQgcG9zc2libGUg
Zm9yIHJlYWRfZGV2IHRvIGJlIHNldCB0byBhbnl0aGluZyBvdGhlciB0aGFuDQo+IGRhOTE1MF9p
MmNfcmVhZF9kZXZpY2U/DQoNClJpZ2h0IG5vdyB0aGVyZSBpcyBubyBTUEkgc3VwcG9ydCBpbiB0
aGUgZHJpdmVyIHNvIG5vLiBHaXZlbiB0aGUgY29tbWVudCBmdXJ0aGVyDQpkb3duLCBJJ2xsIHJl
bW92ZSB0aGVzZSBmdW5jdGlvbiBwb2ludGVycyBmb3Igbm93LCBhbmQgZGlyZWN0bHkgY2FsbCB0
aGUgSTJDDQpyZWxhdGVkIGZ1bmN0aW9ucy4NCg0KPiANCj4gPiArCWlmIChwZGF0YSkgew0KPiA+
ICsJCWRhOTE1MC0+aXJxX2Jhc2UgPSBwZGF0YS0+aXJxX2Jhc2U7DQo+ID4gKw0KPiA+ICsJCWRh
OTE1MF9kZXZzWzJdLnBsYXRmb3JtX2RhdGEgPSBwZGF0YS0+ZmdfcGRhdGE7DQo+ID4gKwkJZGE5
MTUwX2RldnNbMl0ucGRhdGFfc2l6ZSA9IHNpemVvZihzdHJ1Y3QgZGE5MTUwX2ZnX3BkYXRhKTsN
Cj4gDQo+IFRoaXMgaXMgZnJhZ2lsZS4NCj4gDQo+IElmIGFub3RoZXIgZGV2aWNlIGdldHMgaW5z
ZXJ0ZWQgYWJvdmUgdGhlIEZHLCB0aGlzIHdpbGwgYnJlYWsuDQo+IA0KDQpJdCBpcyB1bmxpa2Vs
eSwgYnV0IHlvdSBhcmUgcmlnaHQuIFdpbGwgZml4IHRoaXMuDQoNCj4gPiBkaWZmIC0tZ2l0IGEv
aW5jbHVkZS9saW51eC9tZmQvZGE5MTUwL2NvcmUuaCBiL2luY2x1ZGUvbGludXgvbWZkL2RhOTE1
MC9jb3JlLmgNCj4gPiBpbmRleCA3NmU2Njg5Li45OGVjZmVhIDEwMDY0NA0KPiA+IC0tLSBhL2lu
Y2x1ZGUvbGludXgvbWZkL2RhOTE1MC9jb3JlLmgNCj4gPiArKysgYi9pbmNsdWRlL2xpbnV4L21m
ZC9kYTkxNTAvY29yZS5oDQo+ID4gQEAgLTE1LDkgKzE1LDExIEBADQo+ID4gICNkZWZpbmUgX19E
QTkxNTBfQ09SRV9IDQo+ID4NCj4gPiAgI2luY2x1ZGUgPGxpbnV4L2RldmljZS5oPg0KPiA+ICsj
aW5jbHVkZSA8bGludXgvaTJjLmg+DQo+ID4gICNpbmNsdWRlIDxsaW51eC9pbnRlcnJ1cHQuaD4N
Cj4gPiAgI2luY2x1ZGUgPGxpbnV4L3JlZ21hcC5oPg0KPiA+DQo+ID4gKw0KPiANCj4gUGxlYXNl
IHJlbW92ZSB0aGlzLg0KDQpZZXAuDQoNCj4gDQo+ID4gIC8qIEkyQyBhZGRyZXNzIHBhZ2luZyAq
Lw0KPiA+ICAjZGVmaW5lIERBOTE1MF9SRUdfUEFHRV9TSElGVAk4DQo+ID4gICNkZWZpbmUgREE5
MTUwX1JFR19QQUdFX01BU0sJMHhGRg0KPiA+IEBAIC00NiwyMyArNDgsNDAgQEANCj4gPiAgI2Rl
ZmluZSBEQTkxNTBfSVJRX0dQQURDCTE5DQo+ID4gICNkZWZpbmUgREE5MTUwX0lSUV9XS1VQCQky
MA0KPiA+DQo+ID4gKy8qIFBsYXRmb3JtIERhdGEgKi8NCj4gDQo+IE5vIG5lZWQgZm9yIHRoaXMg
Y29tbWVudC4gIFRoZSBwZGF0YSBpbiB0aGUgbmFtZSBnaXZlIGl0IGF3YXkuDQoNCkZpbmUuDQoN
Cj4gDQo+ID4gK3N0cnVjdCBkYTkxNTBfZmdfcGRhdGE7DQo+IA0KPiBXaHkgY2FuJ3QgeW91IGp1
c3QgcHV0IHRoZSBzdHJ1Y3QgZGVmaW5pdGlvbiBpbiBoZXJlPw0KPiANCg0KV2FzIGEgZGVzaWdu
IGNob2ljZSBhcyBJIHdhcyBwcm92aWRpbmcgYSBoZWFkZXIgZm9yIEZHIHRvIGV4cG9zZSB0aGUg
dGVtcA0KcmVhZGluZyBjYWxsYmFjayBmdW5jdGlvbiBhbmQgZmlndXJlZCBpdCBtYWRlIHNlbnNl
IHRvIGtlZXAgb3RoZXIgRkcgcmVsYXRlZA0KaXRlbXMgaW4gdGhlcmUgYXMgd2VsbC4gQ2FuIGZv
bGQgdGhlbSBhbGwgaW50byB0aGUgY29yZS5oIHRob3VnaCBpZiB0aGF0J3MgdGhlDQpwcmVmZXJy
ZWQgbWV0aG9kLg0KDQo+ID4gIHN0cnVjdCBkYTkxNTBfcGRhdGEgew0KPiA+ICAJaW50IGlycV9i
YXNlOw0KPiA+ICsJc3RydWN0IGRhOTE1MF9mZ19wZGF0YSAqZmdfcGRhdGE7DQo+ID4gIH07DQo+
ID4NCj4gPiArLyogSS9PIGZ1bmN0aW9uIHR5cGVkZWZzIGZvciByYXcgYWNjZXNzICovDQo+ID4g
K3R5cGVkZWYgaW50ICgqcmVhZF9kZXZfdCkodm9pZCAqY2xpZW50LCB1OCBhZGRyLCBpbnQgY291
bnQsIHU4ICpidWYpOw0KPiA+ICt0eXBlZGVmIGludCAoKndyaXRlX2Rldl90KSh2b2lkICpjbGll
bnQsIHU4IGFkZHIsIGludCBjb3VudCwgY29uc3QgdTggKmJ1Zik7DQo+IA0KPiBObyBuZWVkIHRv
IHR5cGVkZWYsIGp1c3QgcG9wIHRoZW0gZGlyZWN0bHkgaW50byB0aGUgc3RydWN0LiAgQWx0aG91
Z2gNCj4gSSdtIG5vdCBjb252aW5jZWQgdGhhdCBuZWVkIHRoZW0gYXQgYWxsIHJlYWxseS4NCg0K
V2lsbCByZW1vdmUgdXNlIG9mIHRoaXMgZm9yIG5vdywgYXMgd2l0aG91dCBTUEkgc3VwcG9ydCwg
dGhleSBkb24ndCBhZGQNCmFueXRoaW5nLg0KDQo+IA0KPiA+ICBzdHJ1Y3QgZGE5MTUwIHsNCj4g
PiAgCXN0cnVjdCBkZXZpY2UgKmRldjsNCj4gPiAgCXN0cnVjdCByZWdtYXAgKnJlZ21hcDsNCj4g
PiArCXN0cnVjdCBpMmNfY2xpZW50ICpjb3JlX3FpZjsNCj4gPiArDQo+ID4gKwlyZWFkX2Rldl90
IHJlYWRfZGV2Ow0KPiA+ICsJd3JpdGVfZGV2X3Qgd3JpdGVfZGV2Ow0KPiA+ICsNCj4gPiAgCXN0
cnVjdCByZWdtYXBfaXJxX2NoaXBfZGF0YSAqcmVnbWFwX2lycV9kYXRhOw0KPiA+ICAJaW50IGly
cTsNCj4gPiAgCWludCBpcnFfYmFzZTsNCj4gDQo+IFdoeSBhcmUgdGhlcmUgMiBpcnFfYmFzZXM/
ICBUaGF0IGNvdWxkIGdldCBwcmV0dHkgY29uZnVzaW5nLg0KPiANCj4gV2h5IGlzIGl0IGJlaW5n
IHN0b3JlZD8NCj4gDQo+IFNhbWUgd2l0aCBpcnEuDQoNCk5vdCAyIElSUSBiYXNlcy4gJ2lycScg
aXMgdGhlIGNoaXAgSVJRLiBTYW1lIGFzIGlzIGRvbmUgaW4gYSBudW1iZXIgb2Ygb3RoZXINCk1G
RCBkcml2ZXJzLiBUZWNobmljYWxseSB0aG91Z2ggdGhleSBkb24ndCBuZWVkIHN0b3Jpbmcgd2l0
aGluIHRoZSBwcml2YXRlIGRhdGEsDQphbmQgd2FzIGRvbmUgYXMgYSBjb252ZW5pZW5jZS4gSSBj
YW4gYWRkIGEgZm9sbG93IHVwIHBhdGNoIHRvIHJlbW92ZSB0aGlzLCBpZg0KaXQncyB3YW50ZWQg
dGhvdWdoLg0KDQo+IA0KPiA+ICB9Ow0KPiA+DQo+ID4gIC8qIERldmljZSBJL08gKi8NCj4gPiAr
dm9pZCBkYTkxNTBfcmVhZF9xaWYoc3RydWN0IGRhOTE1MCAqZGE5MTUwLCB1OCBhZGRyLCBpbnQg
Y291bnQsIHU4ICpidWYpOw0KPiA+ICt2b2lkIGRhOTE1MF93cml0ZV9xaWYoc3RydWN0IGRhOTE1
MCAqZGE5MTUwLCB1OCBhZGRyLCBpbnQgY291bnQsIGNvbnN0IHU4ICpidWYpOw0KPiANCj4gV2hh
dCBpcyBxaWY/ICBUaGUgbmFtaW5nIGNvbnZlbnRpb24gaXNuJ3QgdmVyeSB0cmFuc3BhcmVudC4N
Cg0KVGhpcyBpcyBhbiBpbnRlcmZhY2Ugd2hpY2ggaXMgc3BlY2lmaWMgdG8gdGhlIGRldmljZSAo
UXVlcnkgSW50ZXJmYWNlKSwgYW5kIGlzDQp1c2VkIHRvIGFjY2VzcyBkYXRhIGZyb20gdGhlIGZ1
ZWwtZ2F1Z2UuIElzIGRvY3VtZW50ZWQgaW4gdGhlIGRldmljZQ0KZGF0YXNoZWV0LCBidXQgY2Fu
IGFkZCBjb21tZW50cyBoZXJlIGp1c3QgdG8gY2xhcmlmeS4gDQoNCj4gDQo+ID4gIHU4IGRhOTE1
MF9yZWdfcmVhZChzdHJ1Y3QgZGE5MTUwICpkYTkxNTAsIHUxNiByZWcpOw0KPiA+ICB2b2lkIGRh
OTE1MF9yZWdfd3JpdGUoc3RydWN0IGRhOTE1MCAqZGE5MTUwLCB1MTYgcmVnLCB1OCB2YWwpOw0K
PiA+ICB2b2lkIGRhOTE1MF9zZXRfYml0cyhzdHJ1Y3QgZGE5MTUwICpkYTkxNTAsIHUxNiByZWcs
IHU4IG1hc2ssIHU4IHZhbCk7DQo+ID4NCj4gPiAgdm9pZCBkYTkxNTBfYnVsa19yZWFkKHN0cnVj
dCBkYTkxNTAgKmRhOTE1MCwgdTE2IHJlZywgaW50IGNvdW50LCB1OCAqYnVmKTsNCj4gPiAgdm9p
ZCBkYTkxNTBfYnVsa193cml0ZShzdHJ1Y3QgZGE5MTUwICpkYTkxNTAsIHUxNiByZWcsIGludCBj
b3VudCwgY29uc3QgdTgNCj4gKmJ1Zik7DQo+ID4gKw0KPiA+ICAjZW5kaWYgLyogX19EQTkxNTBf
Q09SRV9IICovDQo+ID4gZGlmZiAtLWdpdCBhL2luY2x1ZGUvbGludXgvbWZkL2RhOTE1MC9mZy5o
IGIvaW5jbHVkZS9saW51eC9tZmQvZGE5MTUwL2ZnLmgNCj4gPiBuZXcgZmlsZSBtb2RlIDEwMDY0
NA0KPiA+IGluZGV4IDAwMDAwMDAuLjBmZjUyYWINCj4gPiAtLS0gL2Rldi9udWxsDQo+ID4gKysr
IGIvaW5jbHVkZS9saW51eC9tZmQvZGE5MTUwL2ZnLmgNCj4gPiBAQCAtMCwwICsxLDI5IEBADQo+
ID4gKy8qDQo+ID4gKyAqIERBOTE1MCBNRkQgRHJpdmVyIC0gRnVlbCBHYXVnZSBEYXRhDQo+IA0K
PiBEb2VzIGl0IHJlYWxseSByZXF1aXJlIGl0J3MgdmVyeSBvd24gaGVhZGVyIGZpbGU/DQoNCldp
bGwgZm9sZCB0aGlzIGludG8gdGhlIG1haW4gaGVhZGVyLCBpZiB5b3UgZG9uJ3Qgd2FudCB0byBz
ZXBhcmF0ZSBvdXQgdGhlIEZHDQpyZWxhdGVkIGRhdGEgYW5kIHRlbXAgY2FsbGJhY2sgcmVsYXRl
ZCBkZWZpbmVzLg0K
--
To unsubscribe from this list: send the line "unsubscribe linux-pm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/drivers/mfd/da9150-core.c b/drivers/mfd/da9150-core.c
index 5549817..8feb75d 100644
--- a/drivers/mfd/da9150-core.c
+++ b/drivers/mfd/da9150-core.c
@@ -21,8 +21,80 @@ 
 #include <linux/interrupt.h>
 #include <linux/mfd/core.h>
 #include <linux/mfd/da9150/core.h>
+#include <linux/mfd/da9150/fg.h>
 #include <linux/mfd/da9150/registers.h>

+/* Raw device access, used for QIF */
+static int da9150_i2c_read_device(struct i2c_client *client, u8 addr, int count,
+				  u8 *buf)
+{
+	struct i2c_msg xfer;
+	int ret;
+
+	/*
+	 * Read is split into two transfers as device expects STOP/START rather
+	 * than repeated start to carry out this kind of access.
+	 */
+
+	/* Write address */
+	xfer.addr = client->addr;
+	xfer.flags = 0;
+	xfer.len = 1;
+	xfer.buf = &addr;
+
+	ret = i2c_transfer(client->adapter, &xfer, 1);
+	if (ret != 1) {
+		if (ret < 0)
+			return ret;
+		else
+			return -EIO;
+	}
+
+	/* Read data */
+	xfer.addr = client->addr;
+	xfer.flags = I2C_M_RD;
+	xfer.len = count;
+	xfer.buf = buf;
+
+	ret = i2c_transfer(client->adapter, &xfer, 1);
+	if (ret == 1)
+		return 0;
+	else if (ret < 0)
+		return ret;
+	else
+		return -EIO;
+}
+
+static int da9150_i2c_write_device(struct i2c_client *client, u8 addr,
+				   int count, const u8 *buf)
+{
+	struct i2c_msg xfer;
+	u8 *reg_data;
+	int ret;
+
+	reg_data = kzalloc(1 + count, GFP_KERNEL);
+	if (!reg_data)
+		return -ENOMEM;
+
+	reg_data[0] = addr;
+	memcpy(&reg_data[1], buf, count);
+
+	/* Write address & data */
+	xfer.addr = client->addr;
+	xfer.flags = 0;
+	xfer.len = 1 + count;
+	xfer.buf = reg_data;
+
+	ret = i2c_transfer(client->adapter, &xfer, 1);
+	kfree(reg_data);
+	if (ret == 1)
+		return 0;
+	else if (ret < 0)
+		return ret;
+	else
+		return -EIO;
+}
+
 static bool da9150_volatile_reg(struct device *dev, unsigned int reg)
 {
 	switch (reg) {
@@ -107,6 +179,28 @@  static const struct regmap_config da9150_regmap_config = {
 	.volatile_reg = da9150_volatile_reg,
 };

+void da9150_read_qif(struct da9150 *da9150, u8 addr, int count, u8 *buf)
+{
+	int ret;
+
+	ret = da9150->read_dev(da9150->core_qif, addr, count, buf);
+	if (ret < 0)
+		dev_err(da9150->dev, "Failed to read from QIF 0x%x: %d\n",
+			addr, ret);
+}
+EXPORT_SYMBOL_GPL(da9150_read_qif);
+
+void da9150_write_qif(struct da9150 *da9150, u8 addr, int count, const u8 *buf)
+{
+	int ret;
+
+	ret = da9150->write_dev(da9150->core_qif, addr, count, buf);
+	if (ret < 0)
+		dev_err(da9150->dev, "Failed to write to QIF 0x%x: %d\n",
+			addr, ret);
+}
+EXPORT_SYMBOL_GPL(da9150_write_qif);
+
 u8 da9150_reg_read(struct da9150 *da9150, u16 reg)
 {
 	int val, ret;
@@ -297,6 +391,15 @@  static struct resource da9150_charger_resources[] = {
 	},
 };

+static struct resource da9150_fg_resources[] = {
+	{
+		.name = "FG",
+		.start = DA9150_IRQ_FG,
+		.end = DA9150_IRQ_FG,
+		.flags = IORESOURCE_IRQ,
+	},
+};
+
 static struct mfd_cell da9150_devs[] = {
 	{
 		.name = "da9150-gpadc",
@@ -310,6 +413,12 @@  static struct mfd_cell da9150_devs[] = {
 		.resources = da9150_charger_resources,
 		.num_resources = ARRAY_SIZE(da9150_charger_resources),
 	},
+	{
+		.name = "da9150-fuelgauge",
+		.of_compatible = "dlg,da9150-fg",
+		.resources = da9150_fg_resources,
+		.num_resources = ARRAY_SIZE(da9150_fg_resources),
+	},
 };

 static int da9150_probe(struct i2c_client *client,
@@ -317,11 +426,14 @@  static int da9150_probe(struct i2c_client *client,
 {
 	struct da9150 *da9150;
 	struct da9150_pdata *pdata = dev_get_platdata(&client->dev);
+	int qif_addr;
 	int ret;

 	da9150 = devm_kzalloc(&client->dev, sizeof(*da9150), GFP_KERNEL);
-	if (!da9150)
-		return -ENOMEM;
+	if (!da9150) {
+		ret = -ENOMEM;
+		goto fail;
+	}

 	da9150->dev = &client->dev;
 	da9150->irq = client->irq;
@@ -332,19 +444,46 @@  static int da9150_probe(struct i2c_client *client,
 		ret = PTR_ERR(da9150->regmap);
 		dev_err(da9150->dev, "Failed to allocate register map: %d\n",
 			ret);
-		return ret;
+		goto fail;
+	}
+
+	/* Setup secondary I2C interface for QIF access */
+	qif_addr = da9150_reg_read(da9150, DA9150_CORE2WIRE_CTRL_A);
+	qif_addr = (qif_addr & DA9150_CORE_BASE_ADDR_MASK) >> 1;
+	qif_addr |= DA9150_QIF_I2C_ADDR_LSB;
+	da9150->core_qif = i2c_new_dummy(client->adapter, qif_addr);
+	if (!da9150->core_qif) {
+		dev_err(da9150->dev, "Failed to attach QIF client\n");
+		ret = -ENODEV;
+		goto fail;
 	}

-	da9150->irq_base = pdata ? pdata->irq_base : -1;
+	i2c_set_clientdata(da9150->core_qif, da9150);
+	da9150->read_dev = (read_dev_t) da9150_i2c_read_device;
+	da9150->write_dev = (write_dev_t) da9150_i2c_write_device;
+
+	if (pdata) {
+		da9150->irq_base = pdata->irq_base;
+
+		da9150_devs[2].platform_data = pdata->fg_pdata;
+		da9150_devs[2].pdata_size = sizeof(struct da9150_fg_pdata);
+	} else {
+		da9150->irq_base = -1;
+	}

 	ret = regmap_add_irq_chip(da9150->regmap, da9150->irq,
 				  IRQF_TRIGGER_LOW | IRQF_ONESHOT,
 				  da9150->irq_base, &da9150_regmap_irq_chip,
 				  &da9150->regmap_irq_data);
-	if (ret)
-		return ret;
+	if (ret) {
+		dev_err(da9150->dev, "Failed to add regmap irq chip: %d\n",
+			ret);
+		goto regmap_irq_fail;
+	}
+

 	da9150->irq_base = regmap_irq_chip_get_base(da9150->regmap_irq_data);
+
 	enable_irq_wake(da9150->irq);

 	ret = mfd_add_devices(da9150->dev, -1, da9150_devs,
@@ -352,11 +491,17 @@  static int da9150_probe(struct i2c_client *client,
 			      da9150->irq_base, NULL);
 	if (ret) {
 		dev_err(da9150->dev, "Failed to add child devices: %d\n", ret);
-		regmap_del_irq_chip(da9150->irq, da9150->regmap_irq_data);
-		return ret;
+		goto mfd_fail;
 	}

 	return 0;
+
+mfd_fail:
+	regmap_del_irq_chip(da9150->irq, da9150->regmap_irq_data);
+regmap_irq_fail:
+	i2c_unregister_device(da9150->core_qif);
+fail:
+	return ret;
 }

 static int da9150_remove(struct i2c_client *client)
@@ -365,6 +510,7 @@  static int da9150_remove(struct i2c_client *client)

 	regmap_del_irq_chip(da9150->irq, da9150->regmap_irq_data);
 	mfd_remove_devices(da9150->dev);
+	i2c_unregister_device(da9150->core_qif);

 	return 0;
 }
diff --git a/include/linux/mfd/da9150/core.h b/include/linux/mfd/da9150/core.h
index 76e6689..98ecfea 100644
--- a/include/linux/mfd/da9150/core.h
+++ b/include/linux/mfd/da9150/core.h
@@ -15,9 +15,11 @@ 
 #define __DA9150_CORE_H

 #include <linux/device.h>
+#include <linux/i2c.h>
 #include <linux/interrupt.h>
 #include <linux/regmap.h>

+
 /* I2C address paging */
 #define DA9150_REG_PAGE_SHIFT	8
 #define DA9150_REG_PAGE_MASK	0xFF
@@ -46,23 +48,40 @@ 
 #define DA9150_IRQ_GPADC	19
 #define DA9150_IRQ_WKUP		20

+/* Platform Data */
+struct da9150_fg_pdata;
+
 struct da9150_pdata {
 	int irq_base;
+	struct da9150_fg_pdata *fg_pdata;
 };

+/* I/O function typedefs for raw access */
+typedef int (*read_dev_t)(void *client, u8 addr, int count, u8 *buf);
+typedef int (*write_dev_t)(void *client, u8 addr, int count, const u8 *buf);
+
 struct da9150 {
 	struct device *dev;
 	struct regmap *regmap;
+	struct i2c_client *core_qif;
+
+	read_dev_t read_dev;
+	write_dev_t write_dev;
+
 	struct regmap_irq_chip_data *regmap_irq_data;
 	int irq;
 	int irq_base;
 };

 /* Device I/O */
+void da9150_read_qif(struct da9150 *da9150, u8 addr, int count, u8 *buf);
+void da9150_write_qif(struct da9150 *da9150, u8 addr, int count, const u8 *buf);
+
 u8 da9150_reg_read(struct da9150 *da9150, u16 reg);
 void da9150_reg_write(struct da9150 *da9150, u16 reg, u8 val);
 void da9150_set_bits(struct da9150 *da9150, u16 reg, u8 mask, u8 val);

 void da9150_bulk_read(struct da9150 *da9150, u16 reg, int count, u8 *buf);
 void da9150_bulk_write(struct da9150 *da9150, u16 reg, int count, const u8 *buf);
+
 #endif /* __DA9150_CORE_H */
diff --git a/include/linux/mfd/da9150/fg.h b/include/linux/mfd/da9150/fg.h
new file mode 100644
index 0000000..0ff52ab
--- /dev/null
+++ b/include/linux/mfd/da9150/fg.h
@@ -0,0 +1,29 @@ 
+/*
+ * DA9150 MFD Driver - Fuel Gauge Data
+ *
+ * Copyright (c) 2015 Dialog Semiconductor
+ *
+ * Author: Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
+ *
+ * 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;  either version 2 of the  License, or (at your
+ * option) any later version.
+ */
+
+#ifndef __DA9150_FG_H
+#define __DA9150_FG_H
+
+#include <linux/power_supply.h>
+
+/* I2C sub-device address */
+#define DA9150_QIF_I2C_ADDR_LSB		0x5
+
+/* Platform data */
+struct da9150_fg_pdata {
+	u32 update_interval;	/* msecs */
+	u8 warn_soc_lvl;	/* % value */
+	u8 crit_soc_lvl;	/* % value */
+};
+
+#endif /* __DA9150_FG_H */