All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] mfd: add bq2415x charger driver
@ 2011-12-05 22:35 Felipe Contreras
  2011-12-05 22:51 ` Felipe Contreras
                   ` (4 more replies)
  0 siblings, 5 replies; 28+ messages in thread
From: Felipe Contreras @ 2011-12-05 22:35 UTC (permalink / raw)
  To: linux-main
  Cc: linux-omap, Samuel Ortiz, Pali Rohár, Aliaksei Katovich,
	Vladimir Zapolskiy, Felipe Contreras

From: Aliaksei Katovich <aliaksei.katovich@nokia.com>

Hi,

The Nokia N900 has a bunch of stuff regarding the battery: bq27x00 to find out
the status, and isp1704 to detect what kind of charger is connected to the USB
port. However, that doesn't charge the battery.

That's the job of bq24150. Up until now, this has been done by the closed and
proprietary BME application from user-space, which sends i2c commands directly.

Several people have tried to decipher what it's actually doing, and there are
some simple user-space scripts that manage to achieve charging:

  http://enivax.net/jk/n900/charge21.sh.txt

Aliaksei Katovich already sent a patch series, but they seem to be doing
something completely different (wrong?).

This simple driver does the work for me. It's doing basically the same as the
user-space scripts, but a bit more aligned to the spec sheet[1], and a bit more
understadable. It's a proof of concept, as it doesn't really change the voltage
or amperage depending on the type of charger as it should, but it at least it
achieves charging.

I have never writen a driver, so don't hold on the comments :)

Some inspiration from the code of Aliaksei Katovich.

Cheers.

[1] http://www.ti.com/lit/ds/symlink/bq24150.pdf

Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>
---
 arch/arm/mach-omap2/board-rx51-peripherals.c |    3 +
 drivers/mfd/Kconfig                          |    7 +
 drivers/mfd/Makefile                         |    1 +
 drivers/mfd/bq2415x.c                        |  173 ++++++++++++++++++++++++++
 4 files changed, 184 insertions(+), 0 deletions(-)
 create mode 100644 drivers/mfd/bq2415x.c

diff --git a/arch/arm/mach-omap2/board-rx51-peripherals.c b/arch/arm/mach-omap2/board-rx51-peripherals.c
index 973b631..bf827f4 100644
--- a/arch/arm/mach-omap2/board-rx51-peripherals.c
+++ b/arch/arm/mach-omap2/board-rx51-peripherals.c
@@ -925,6 +925,9 @@ static struct i2c_board_info __initdata rx51_peripherals_i2c_board_info_2[] = {
 		I2C_BOARD_INFO("bq27200", 0x55),
 	},
 #endif
+	{
+		I2C_BOARD_INFO("bq24150", 0x6b),
+	},
 };
 
 static int __init rx51_i2c_init(void)
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index f1391c2..300aadb 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -772,6 +772,13 @@ config MFD_INTEL_MSIC
 	  Passage) chip. This chip embeds audio, battery, GPIO, etc.
 	  devices used in Intel Medfield platforms.
 
+config MFD_BQ2415X
+	tristate "TI bq2415x one-cell Li-Ion charger"
+	depends on I2C
+	help
+	  This driver supports TI bq2415x integrated switch-mode chargers
+	  attached to i2c.
+
 endmenu
 endif
 
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index b2292eb..ce715d2 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -104,3 +104,4 @@ obj-$(CONFIG_MFD_PM8XXX_IRQ) 	+= pm8xxx-irq.o
 obj-$(CONFIG_TPS65911_COMPARATOR)	+= tps65911-comparator.o
 obj-$(CONFIG_MFD_AAT2870_CORE)	+= aat2870-core.o
 obj-$(CONFIG_MFD_INTEL_MSIC)	+= intel_msic.o
+obj-$(CONFIG_MFD_BQ2415X)	+= bq2415x.o
diff --git a/drivers/mfd/bq2415x.c b/drivers/mfd/bq2415x.c
new file mode 100644
index 0000000..4382da6
--- /dev/null
+++ b/drivers/mfd/bq2415x.c
@@ -0,0 +1,173 @@
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+
+#define BQ2415X_TIMEOUT 9
+
+#define BQ2415X_STS_CTL 0x00
+#define BQ2415X_TMR_RST BIT(7)
+
+#define BQ2415X_GEN_CTL 0x01
+#define BQ2415X_BAT_CTL 0x02
+#define BQ2415X_CHG_CTL 0x04
+
+#define BQ2415X_IIN_LIMIT_500_MASK 0x40
+#define BQ2415X_IIN_LIMIT_800_MASK 0x80
+#define BQ2415X_IIN_LIMIT_UNLIM_MASK 0xc0
+
+#define BQ2415X_IIN_LIMIT_100 100000
+#define BQ2415X_IIN_LIMIT_500 500000
+#define BQ2415X_IIN_LIMIT_800 800000
+#define BQ2415X_IIN_LIMIT_UNLIM 1500000
+
+#define BQ2415X_MIN_VOLTAGE 3500000
+#define BQ2415X_VOLTAGE_SCALE 20000
+#define BQ2415X_BAT_VOL_MASK 0xfc
+
+enum bq2415x_chip { BQ24150, BQ24151, BQ24153, BQ24156, BQ24158 };
+
+struct bq2415x_device_info {
+	struct i2c_client *cli;
+	struct delayed_work work;
+};
+
+static inline int bq2415x_i2c_read(struct i2c_client *cli, u8 reg)
+{
+	return i2c_smbus_read_byte_data(cli, reg);
+}
+
+static inline int bq2415x_i2c_write(struct i2c_client *cli, u8 reg, u8 val)
+{
+	return i2c_smbus_write_byte_data(cli, reg, val);
+}
+
+static void bq2415x_timer(struct work_struct *work)
+{
+	struct bq2415x_device_info *di =
+		container_of(work, struct bq2415x_device_info, work.work);
+	struct i2c_client *cli = di->cli;
+	bq2415x_i2c_write(cli, BQ2415X_STS_CTL, BQ2415X_TMR_RST);
+	schedule_delayed_work(&di->work, BQ2415X_TIMEOUT * HZ);
+}
+
+static int bq2415x_set_current_limit(struct i2c_client *cli,
+		int min_uA, int max_uA)
+{
+	int res;
+
+	res = bq2415x_i2c_read(cli, BQ2415X_GEN_CTL);
+	if (res < 0)
+		return res;
+
+	res &= ~BQ2415X_IIN_LIMIT_UNLIM_MASK;
+
+	if (min_uA >= BQ2415X_IIN_LIMIT_100 && max_uA < BQ2415X_IIN_LIMIT_500)
+		;
+	else if (min_uA >= BQ2415X_IIN_LIMIT_500 && max_uA < BQ2415X_IIN_LIMIT_800)
+		res |= BQ2415X_IIN_LIMIT_500_MASK;
+	else if (min_uA >= BQ2415X_IIN_LIMIT_800 && max_uA < BQ2415X_IIN_LIMIT_UNLIM)
+		res |= BQ2415X_IIN_LIMIT_800_MASK;
+	else if (min_uA >= BQ2415X_IIN_LIMIT_UNLIM)
+		res |= BQ2415X_IIN_LIMIT_UNLIM_MASK;
+	else
+		return -EINVAL;
+
+	return bq2415x_i2c_write(cli, BQ2415X_GEN_CTL, res);
+}
+
+/* 3.5 - 4.44 (6 registers, pos 2) */
+static int bq2415x_set_voltage(struct i2c_client *cli,
+		int min_uV, int max_uV)
+{
+	int res;
+
+	if (max_uV >= 4400000)
+		return -EINVAL;
+
+	res = bq2415x_i2c_read(cli, BQ2415X_BAT_CTL);
+	if (res < 0)
+		return res;
+
+	res &= ~BQ2415X_BAT_VOL_MASK;
+	res |= ((max_uV - BQ2415X_MIN_VOLTAGE) / BQ2415X_VOLTAGE_SCALE) << 2;
+	return bq2415x_i2c_write(cli, BQ2415X_BAT_CTL, res);
+}
+
+static int bq2415x_probe(struct i2c_client *cli,
+		const struct i2c_device_id *id)
+{
+	struct bq2415x_device_info *di;
+
+	di = kzalloc(sizeof(*di), GFP_KERNEL);
+	if (!di) {
+		dev_err(&cli->dev, "failed to allocate device info data\n");
+		return -ENOMEM;
+	}
+
+	di->cli = cli;
+
+	i2c_set_clientdata(cli, di);
+
+	bq2415x_i2c_write(cli, BQ2415X_CHG_CTL, 0x42);
+
+	bq2415x_set_voltage(cli, 0, 4200000);
+
+	bq2415x_set_current_limit(cli, 1800000, 1800000);
+
+	/* seems we need to write something here to start charging */
+	bq2415x_i2c_write(cli, BQ2415X_STS_CTL, 0);
+
+	INIT_DELAYED_WORK(&di->work, bq2415x_timer);
+	schedule_delayed_work(&di->work, BQ2415X_TIMEOUT * HZ);
+
+	return 0;
+}
+
+static int bq2415x_remove(struct i2c_client *cli)
+{
+	struct bq2415x_device_info *di = i2c_get_clientdata(cli);
+	cancel_delayed_work_sync(&di->work);
+	kfree(di);
+	return 0;
+}
+
+static const struct i2c_device_id bq2415x_id[] = {
+	{ "bq24150", BQ24150 },
+	{ "bq24151", BQ24151 },
+	{ "bq24153", BQ24153 },
+	{ "bq24156", BQ24156 },
+	{ "bq24158", BQ24158 },
+	{},
+};
+MODULE_DEVICE_TABLE(i2c, bq2415x_id);
+
+static struct i2c_driver bq2415x_driver = {
+	.driver = {
+		.name = "bq2415x",
+		.owner = THIS_MODULE,
+	},
+	.probe = bq2415x_probe,
+	.remove = bq2415x_remove,
+	.id_table = bq2415x_id,
+};
+
+static int __init bq2415x_init(void)
+{
+	return i2c_add_driver(&bq2415x_driver);
+}
+
+static void __exit bq2415x_exit(void)
+{
+	i2c_del_driver(&bq2415x_driver);
+}
+
+module_init(bq2415x_init);
+module_exit(bq2415x_exit);
+
+MODULE_AUTHOR("Felipe Contreras <felipe.contreras@gmail.com>");
+MODULE_DESCRIPTION("TI bq2415x one-cell Li-Ion charger driver");
+MODULE_LICENSE("GPL");
-- 
1.7.8.rc3.17.gf56ef1


^ permalink raw reply related	[flat|nested] 28+ messages in thread

* Re: [PATCH] mfd: add bq2415x charger driver
  2011-12-05 22:35 [PATCH] mfd: add bq2415x charger driver Felipe Contreras
@ 2011-12-05 22:51 ` Felipe Contreras
  2011-12-05 23:05 ` Pali Rohár
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 28+ messages in thread
From: Felipe Contreras @ 2011-12-05 22:51 UTC (permalink / raw)
  To: Felipe Contreras
  Cc: linux-main, linux-omap, Samuel Ortiz, Pali Rohár,
	Aliaksei Katovich, Vladimir Zapolskiy

On Tue, Dec 6, 2011 at 12:35 AM, Felipe Contreras
<felipe.contreras@nokia.com> wrote:
> From: Aliaksei Katovich <aliaksei.katovich@nokia.com>

Er, this was supposed to be Felipe Contreras <felipe.contreras@gmail.com> :)

-- 
Felipe Contreras

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH] mfd: add bq2415x charger driver
  2011-12-05 22:35 [PATCH] mfd: add bq2415x charger driver Felipe Contreras
  2011-12-05 22:51 ` Felipe Contreras
@ 2011-12-05 23:05 ` Pali Rohár
  2011-12-06  0:12   ` Felipe Contreras
  2011-12-06  2:17 ` Sebastian Reichel
                   ` (2 subsequent siblings)
  4 siblings, 1 reply; 28+ messages in thread
From: Pali Rohár @ 2011-12-05 23:05 UTC (permalink / raw)
  To: Felipe Contreras
  Cc: linux-main, linux-omap, Samuel Ortiz, Aliaksei Katovich,
	Vladimir Zapolskiy, Felipe Contreras

[-- Attachment #1: Type: text/plain, Size: 1942 bytes --]

On Tuesday 06 December 2011 00:35:41 Felipe Contreras wrote:
> From: Aliaksei Katovich <aliaksei.katovich@nokia.com>
> 
> Hi,
> 
> The Nokia N900 has a bunch of stuff regarding the battery: bq27x00 to find
> out the status, and isp1704 to detect what kind of charger is connected to
> the USB port. However, that doesn't charge the battery.
> 
> That's the job of bq24150. Up until now, this has been done by the closed
> and proprietary BME application from user-space, which sends i2c commands
> directly.
> 
> Several people have tried to decipher what it's actually doing, and there
> are some simple user-space scripts that manage to achieve charging:
> 
>   http://enivax.net/jk/n900/charge21.sh.txt
> 
> Aliaksei Katovich already sent a patch series, but they seem to be doing
> something completely different (wrong?).
> 
> This simple driver does the work for me. It's doing basically the same as
> the user-space scripts, but a bit more aligned to the spec sheet[1], and a
> bit more understadable. It's a proof of concept, as it doesn't really
> change the voltage or amperage depending on the type of charger as it
> should, but it at least it achieves charging.
> 
> I have never writen a driver, so don't hold on the comments :)
> 
> Some inspiration from the code of Aliaksei Katovich.
> 
> Cheers.
> 
> [1] http://www.ti.com/lit/ds/symlink/bq24150.pdf
> 

Hello,

I started writing other implementaion of bq2415x charger driver, which should 
support also setting usb host mode. Code is still unfinished, but now is 
devided into 2 parts: one power_supply driver and one driver which cover all 
bq registers. See:

http://atrey.karlin.mff.cuni.cz/~pali/bq2415x/

Felipe Contreras, I think that my implementation is better - it will export 
all bq registers (which is needed for hostmode boost) and will also register 
regulator interface.

-- 
Pali Rohár
pali.rohar@gmail.com

[-- Attachment #2: This is a digitally signed message part. --]
[-- Type: application/pgp-signature, Size: 198 bytes --]

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH] mfd: add bq2415x charger driver
  2011-12-05 23:05 ` Pali Rohár
@ 2011-12-06  0:12   ` Felipe Contreras
  2011-12-06  7:25     ` Pali Rohár
  0 siblings, 1 reply; 28+ messages in thread
From: Felipe Contreras @ 2011-12-06  0:12 UTC (permalink / raw)
  To: Pali Rohár
  Cc: Felipe Contreras, linux-main, linux-omap, Samuel Ortiz,
	Aliaksei Katovich, Vladimir Zapolskiy

Hi,

On Tue, Dec 6, 2011 at 1:05 AM, Pali Rohár <pali.rohar@gmail.com> wrote:
> I started writing other implementaion of bq2415x charger driver, which should
> support also setting usb host mode. Code is still unfinished, but now is
> devided into 2 parts: one power_supply driver and one driver which cover all
> bq registers. See:
>
> http://atrey.karlin.mff.cuni.cz/~pali/bq2415x/
>
> Felipe Contreras, I think that my implementation is better - it will export
> all bq registers (which is needed for hostmode boost) and will also register
> regulator interface.

I took a look at your driver, and there's definitely good stuff in it.
However, I think there's a lot of unnecessary stuff, like the
miscdevice stuff (which was frowned upon for bq27x00), and a lot of
user-space interface. Moreover, it doesn't seem to do anything on its
own (it needs interaction from user-space).

IMO the first step should be to have a minimal driver that just works,
even if it doesn't achieve the absolute best charging performance.
More features could be added later on.

Also, I'm not familiar with the regulator interface, but it seems to
be meant for real regulators, which have consumers, and based on those
consumers's needs the real voltage changes. This battery charger on
the other hand doesn't have anything like that. There will be no
consumer, and some stuff like the weak battery voltage is not even
related to a voltage supply, but rather a threshold that can be
configured to change some behavior of the charger, but there's no
point in changing it dynamically (or maybe at all).

I guess the important one is the charge voltage, which is linked to a
real voltage, but what consumers would it have? I don't think there's
any.

Finally, I don't think user-space interaction should be needed at all.
The driver should start charging immediately when there power supply
available, and stop when there isn't any. Maybe at some point a
user-space interface will be useful later on (I don't see why), but I
don't think it should be *necessary*.

I'm not familiar with any of this stuff, so don't take my opinions too
seriously :)

Cheers.

-- 
Felipe Contreras

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH] mfd: add bq2415x charger driver
  2011-12-05 22:35 [PATCH] mfd: add bq2415x charger driver Felipe Contreras
  2011-12-05 22:51 ` Felipe Contreras
  2011-12-05 23:05 ` Pali Rohár
@ 2011-12-06  2:17 ` Sebastian Reichel
  2011-12-06  2:49     ` Felipe Contreras
  2011-12-06 11:31 ` Mark Brown
  2011-12-07 21:03 ` RFC: bq2415x_charger driver Pali Rohár
  4 siblings, 1 reply; 28+ messages in thread
From: Sebastian Reichel @ 2011-12-06  2:17 UTC (permalink / raw)
  To: Felipe Contreras
  Cc: linux-main, linux-omap, Samuel Ortiz, Pali Rohár,
	Aliaksei Katovich, Vladimir Zapolskiy, Felipe Contreras

[-- Attachment #1: Type: text/plain, Size: 956 bytes --]

Hi,

Good to hear somebody is working on this :) I planned to do this
myself, but didn't find any time for it. Here are some comments
from me:

[...]

> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig

I would expect the driver in drivers/power. But maybe MFD is
better, since the chip can also be used to power USB host?

[...]

> +#define BQ2415X_VOLTAGE_SCALE 20000

If I remember correctly (I read the specs some time ago), this can
be configured via external resistor. So this is board specific?

[...]

> +	bq2415x_i2c_write(cli, BQ2415X_CHG_CTL, 0x42);
> +	bq2415x_set_voltage(cli, 0, 4200000);
> +	bq2415x_set_current_limit(cli, 1800000, 1800000);

So it always charges with 1.8A? Shouldn't this be dependent on the
input source (e.g. 500mA from USB)? Also what's if no charger is
connected at all?

Apart from this I think those values should be exposed to sysfs
via /sys/class/power_supply.

-- Sebastian

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 836 bytes --]

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH] mfd: add bq2415x charger driver
  2011-12-06  2:17 ` Sebastian Reichel
@ 2011-12-06  2:49     ` Felipe Contreras
  0 siblings, 0 replies; 28+ messages in thread
From: Felipe Contreras @ 2011-12-06  2:49 UTC (permalink / raw)
  To: Sebastian Reichel
  Cc: Felipe Contreras, linux-main, linux-omap, Samuel Ortiz,
	Pali Rohár, Aliaksei Katovich, Vladimir Zapolskiy

Hi,

2011/12/6 Sebastian Reichel <sre@debian.org>:
> Good to hear somebody is working on this :) I planned to do this
> myself, but didn't find any time for it. Here are some comments
> from me:

:)

> [...]
>
>> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
>
> I would expect the driver in drivers/power. But maybe MFD is
> better, since the chip can also be used to power USB host?

Perhaps, at least I haven't tried that.

> [...]
>
>> +#define BQ2415X_VOLTAGE_SCALE 20000
>
> If I remember correctly (I read the specs some time ago), this can
> be configured via external resistor. So this is board specific?

Not really. There are 6 bits: 640, 320, 160, 80, 40, 20. So values
from 0 to 1260mV can be represented, at steps of 20mV. A value of 0
means 3.5V.

> [...]
>
>> +     bq2415x_i2c_write(cli, BQ2415X_CHG_CTL, 0x42);
>> +     bq2415x_set_voltage(cli, 0, 4200000);
>> +     bq2415x_set_current_limit(cli, 1800000, 1800000);
>
> So it always charges with 1.8A? Shouldn't this be dependent on the
> input source (e.g. 500mA from USB)? Also what's if no charger is
> connected at all?

No. I'm hard-coding those values, but I wrote the functions
(set_voltate, set_current_limit) so all values are supported.

What is missing is the code to connect this driver to the charger
detector driver (isp1704), so those values are determined dynamically.

> Apart from this I think those values should be exposed to sysfs
> via /sys/class/power_supply.

Perhaps. Although I don't see much of the power supply interface that
could be useful here, but if so, I guess this should be indeed in
'drivers/power'.

Cheers.

-- 
Felipe Contreras

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH] mfd: add bq2415x charger driver
@ 2011-12-06  2:49     ` Felipe Contreras
  0 siblings, 0 replies; 28+ messages in thread
From: Felipe Contreras @ 2011-12-06  2:49 UTC (permalink / raw)
  To: Sebastian Reichel
  Cc: Felipe Contreras, linux-main, linux-omap, Samuel Ortiz,
	Pali Rohár, Aliaksei Katovich, Vladimir Zapolskiy

Hi,

2011/12/6 Sebastian Reichel <sre@debian.org>:
> Good to hear somebody is working on this :) I planned to do this
> myself, but didn't find any time for it. Here are some comments
> from me:

:)

> [...]
>
>> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
>
> I would expect the driver in drivers/power. But maybe MFD is
> better, since the chip can also be used to power USB host?

Perhaps, at least I haven't tried that.

> [...]
>
>> +#define BQ2415X_VOLTAGE_SCALE 20000
>
> If I remember correctly (I read the specs some time ago), this can
> be configured via external resistor. So this is board specific?

Not really. There are 6 bits: 640, 320, 160, 80, 40, 20. So values
from 0 to 1260mV can be represented, at steps of 20mV. A value of 0
means 3.5V.

> [...]
>
>> +     bq2415x_i2c_write(cli, BQ2415X_CHG_CTL, 0x42);
>> +     bq2415x_set_voltage(cli, 0, 4200000);
>> +     bq2415x_set_current_limit(cli, 1800000, 1800000);
>
> So it always charges with 1.8A? Shouldn't this be dependent on the
> input source (e.g. 500mA from USB)? Also what's if no charger is
> connected at all?

No. I'm hard-coding those values, but I wrote the functions
(set_voltate, set_current_limit) so all values are supported.

What is missing is the code to connect this driver to the charger
detector driver (isp1704), so those values are determined dynamically.

> Apart from this I think those values should be exposed to sysfs
> via /sys/class/power_supply.

Perhaps. Although I don't see much of the power supply interface that
could be useful here, but if so, I guess this should be indeed in
'drivers/power'.

Cheers.

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

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH] mfd: add bq2415x charger driver
  2011-12-06  0:12   ` Felipe Contreras
@ 2011-12-06  7:25     ` Pali Rohár
  2011-12-06 10:58       ` Felipe Contreras
  2011-12-06 11:25       ` Mark Brown
  0 siblings, 2 replies; 28+ messages in thread
From: Pali Rohár @ 2011-12-06  7:25 UTC (permalink / raw)
  To: Felipe Contreras
  Cc: Felipe Contreras, linux-main, linux-omap, Samuel Ortiz,
	Aliaksei Katovich, Vladimir Zapolskiy

[-- Attachment #1: Type: text/plain, Size: 3400 bytes --]

Hi,

On Tuesday 06 December 2011 02:12:47 you wrote:
> Hi,
> 
> On Tue, Dec 6, 2011 at 1:05 AM, Pali Rohár <pali.rohar@gmail.com> wrote:
> > I started writing other implementaion of bq2415x charger driver, which
> > should support also setting usb host mode. Code is still unfinished,
> > but now is devided into 2 parts: one power_supply driver and one driver
> > which cover all bq registers. See:
> > 
> > http://atrey.karlin.mff.cuni.cz/~pali/bq2415x/
> > 
> > Felipe Contreras, I think that my implementation is better - it will
> > export all bq registers (which is needed for hostmode boost) and will
> > also register regulator interface.
> 
> I took a look at your driver, and there's definitely good stuff in it.
> However, I think there's a lot of unnecessary stuff, like the
> miscdevice stuff (which was frowned upon for bq27x00), and a lot of
> user-space interface. Moreover, it doesn't seem to do anything on its
> own (it needs interaction from user-space).

Ignore miscdevice, I will remove them from driver. I will add some debugfs 
interface for getting registers output (needed for debugging)

> 
> IMO the first step should be to have a minimal driver that just works,
> even if it doesn't achieve the absolute best charging performance.
> More features could be added later on.
> 
> Also, I'm not familiar with the regulator interface, but it seems to
> be meant for real regulators, which have consumers, and based on those
> consumers's needs the real voltage changes. This battery charger on
> the other hand doesn't have anything like that. There will be no
> consumer, and some stuff like the weak battery voltage is not even
> related to a voltage supply, but rather a threshold that can be
> configured to change some behavior of the charger, but there's no
> point in changing it dynamically (or maybe at all).

If regulator interface is not good, I can change it to some sysfs interface. 
But bq2415x chip driver is not only rx51 specified, so it should handle all 
chip capabilities.

> 
> I guess the important one is the charge voltage, which is linked to a
> real voltage, but what consumers would it have? I don't think there's
> any.
> 
> Finally, I don't think user-space interaction should be needed at all.
> The driver should start charging immediately when there power supply
> available, and stop when there isn't any. Maybe at some point a
> user-space interface will be useful later on (I don't see why), but I
> don't think it should be *necessary*.

Userspace interfaction is needed. We need to tell driver to boost - for usb 
host mode. But of course, battery charging should be automatic without 
userspace interfaction. 

> 
> I'm not familiar with any of this stuff, so don't take my opinions too
> seriously :)

Consider my code. We do not need two (or more) implementation of same driver 
in kernel. And also we do not need only rx51 specified code.

I separated bq2415x register access into one module (bq2415x.c - without any 
logic, only cover chip options) and real battery charging should be done in 
power_supply interface (bq2415x_charger.c)

My code has also prepaired boost support - for usb host mode, which must be 
done in driver.

If you do not agree with other parts, tell me about it - we can fix it.

> 
> Cheers.

-- 
Pali Rohár
pali.rohar@gmail.com

[-- Attachment #2: This is a digitally signed message part. --]
[-- Type: application/pgp-signature, Size: 198 bytes --]

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH] mfd: add bq2415x charger driver
  2011-12-06  7:25     ` Pali Rohár
@ 2011-12-06 10:58       ` Felipe Contreras
  2011-12-06 13:27         ` Pali Rohár
  2011-12-06 11:25       ` Mark Brown
  1 sibling, 1 reply; 28+ messages in thread
From: Felipe Contreras @ 2011-12-06 10:58 UTC (permalink / raw)
  To: Pali Rohár
  Cc: Felipe Contreras, linux-main, linux-omap, Samuel Ortiz,
	Aliaksei Katovich, Vladimir Zapolskiy

On Tue, Dec 6, 2011 at 9:25 AM, Pali Rohár <pali.rohar@gmail.com> wrote:
> On Tuesday 06 December 2011 02:12:47 you wrote:
>> On Tue, Dec 6, 2011 at 1:05 AM, Pali Rohár <pali.rohar@gmail.com> wrote:
>> > I started writing other implementaion of bq2415x charger driver, which
>> > should support also setting usb host mode. Code is still unfinished,
>> > but now is devided into 2 parts: one power_supply driver and one driver
>> > which cover all bq registers. See:
>> >
>> > http://atrey.karlin.mff.cuni.cz/~pali/bq2415x/
>> >
>> > Felipe Contreras, I think that my implementation is better - it will
>> > export all bq registers (which is needed for hostmode boost) and will
>> > also register regulator interface.
>>
>> I took a look at your driver, and there's definitely good stuff in it.
>> However, I think there's a lot of unnecessary stuff, like the
>> miscdevice stuff (which was frowned upon for bq27x00), and a lot of
>> user-space interface. Moreover, it doesn't seem to do anything on its
>> own (it needs interaction from user-space).
>
> Ignore miscdevice, I will remove them from driver. I will add some debugfs
> interface for getting registers output (needed for debugging)

Ok. A device with such debugfs would be nice, but I would start
without one, just something that works.

>> IMO the first step should be to have a minimal driver that just works,
>> even if it doesn't achieve the absolute best charging performance.
>> More features could be added later on.
>>
>> Also, I'm not familiar with the regulator interface, but it seems to
>> be meant for real regulators, which have consumers, and based on those
>> consumers's needs the real voltage changes. This battery charger on
>> the other hand doesn't have anything like that. There will be no
>> consumer, and some stuff like the weak battery voltage is not even
>> related to a voltage supply, but rather a threshold that can be
>> configured to change some behavior of the charger, but there's no
>> point in changing it dynamically (or maybe at all).
>
> If regulator interface is not good, I can change it to some sysfs interface.
> But bq2415x chip driver is not only rx51 specified, so it should handle all
> chip capabilities.

I don't know if the regulator interface makes sense, but I think not.
Anyway, I don't see how my code is specific to rx51, it should work
with all bq2415x models.

>> I guess the important one is the charge voltage, which is linked to a
>> real voltage, but what consumers would it have? I don't think there's
>> any.
>>
>> Finally, I don't think user-space interaction should be needed at all.
>> The driver should start charging immediately when there power supply
>> available, and stop when there isn't any. Maybe at some point a
>> user-space interface will be useful later on (I don't see why), but I
>> don't think it should be *necessary*.
>
> Userspace interfaction is needed. We need to tell driver to boost - for usb
> host mode. But of course, battery charging should be automatic without
> userspace interfaction.

Why do we need user-space for the boost mode?

>> I'm not familiar with any of this stuff, so don't take my opinions too
>> seriously :)
>
> Consider my code. We do not need two (or more) implementation of same driver
> in kernel. And also we do not need only rx51 specified code.

Of course, that's why I am discussing this :)

> I separated bq2415x register access into one module (bq2415x.c - without any
> logic, only cover chip options) and real battery charging should be done in
> power_supply interface (bq2415x_charger.c)

I don't see the point of having two drivers.

> My code has also prepaired boost support - for usb host mode, which must be
> done in driver.

Well, yeah, in my driver it can be added as well, however, I don't
think it's _needed_ right now.

First, I would like something that works by itself (without
user-space), which I already have. Next, I would like it to plug into
isp1704 to detect when a charger is connected, and select the correct
limits accordingly. I guess this hooks should be connected on the
board code. Once having that, I think the driver should be ready for
merging, the rest of the features can come later.

Cheers.

-- 
Felipe Contreras

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH] mfd: add bq2415x charger driver
  2011-12-06  7:25     ` Pali Rohár
  2011-12-06 10:58       ` Felipe Contreras
@ 2011-12-06 11:25       ` Mark Brown
  1 sibling, 0 replies; 28+ messages in thread
From: Mark Brown @ 2011-12-06 11:25 UTC (permalink / raw)
  To: Pali Roh?r
  Cc: Felipe Contreras, Felipe Contreras, linux-main, linux-omap,
	Samuel Ortiz, Aliaksei Katovich, Vladimir Zapolskiy

On Tue, Dec 06, 2011 at 08:25:49AM +0100, Pali Roh?r wrote:
> On Tuesday 06 December 2011 02:12:47 you wrote:

> > Also, I'm not familiar with the regulator interface, but it seems to
> > be meant for real regulators, which have consumers, and based on those
> > consumers's needs the real voltage changes. This battery charger on
> > the other hand doesn't have anything like that. There will be no
> > consumer, and some stuff like the weak battery voltage is not even
> > related to a voltage supply, but rather a threshold that can be
> > configured to change some behavior of the charger, but there's no
> > point in changing it dynamically (or maybe at all).

> If regulator interface is not good, I can change it to some sysfs interface. 
> But bq2415x chip driver is not only rx51 specified, so it should handle all 
> chip capabilities.

It's been used by a few charger drivers not for the voltage regulation
but for the current regulation interface - it does provide an
abstraction allowing the battery driver to vary the current going into
the battery independantly of the thing regulating that current.  This
does depend on what control the charger offers, some are designed to be
more autonomous than others.

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH] mfd: add bq2415x charger driver
  2011-12-05 22:35 [PATCH] mfd: add bq2415x charger driver Felipe Contreras
                   ` (2 preceding siblings ...)
  2011-12-06  2:17 ` Sebastian Reichel
@ 2011-12-06 11:31 ` Mark Brown
  2011-12-06 11:43     ` Felipe Contreras
  2011-12-07 21:03 ` RFC: bq2415x_charger driver Pali Rohár
  4 siblings, 1 reply; 28+ messages in thread
From: Mark Brown @ 2011-12-06 11:31 UTC (permalink / raw)
  To: Felipe Contreras
  Cc: linux-main, linux-omap, Samuel Ortiz, Pali Roh??r,
	Aliaksei Katovich, Vladimir Zapolskiy, Felipe Contreras

On Tue, Dec 06, 2011 at 12:35:41AM +0200, Felipe Contreras wrote:

> --- a/arch/arm/mach-omap2/board-rx51-peripherals.c
> +++ b/arch/arm/mach-omap2/board-rx51-peripherals.c
> @@ -925,6 +925,9 @@ static struct i2c_board_info __initdata rx51_peripherals_i2c_board_info_2[] = {
>  		I2C_BOARD_INFO("bq27200", 0x55),
>  	},
>  #endif
> +	{
> +		I2C_BOARD_INFO("bq24150", 0x6b),
> +	},

Clearly this is orthogonal.

> +static inline int bq2415x_i2c_read(struct i2c_client *cli, u8 reg)
> +{
> +	return i2c_smbus_read_byte_data(cli, reg);
> +}
> +
> +static inline int bq2415x_i2c_write(struct i2c_client *cli, u8 reg, u8 val)
> +{
> +	return i2c_smbus_write_byte_data(cli, reg, val);
> +}

regmap might be useful here (it's got an update bits operation and
cache).

> +static int bq2415x_set_current_limit(struct i2c_client *cli,
> +		int min_uA, int max_uA)
> +{
> +	int res;
> +
> +	res = bq2415x_i2c_read(cli, BQ2415X_GEN_CTL);
> +	if (res < 0)
> +		return res;
> +
> +	res &= ~BQ2415X_IIN_LIMIT_UNLIM_MASK;
> +
> +	if (min_uA >= BQ2415X_IIN_LIMIT_100 && max_uA < BQ2415X_IIN_LIMIT_500)
> +		;
> +	else if (min_uA >= BQ2415X_IIN_LIMIT_500 && max_uA < BQ2415X_IIN_LIMIT_800)
> +		res |= BQ2415X_IIN_LIMIT_500_MASK;
> +	else if (min_uA >= BQ2415X_IIN_LIMIT_800 && max_uA < BQ2415X_IIN_LIMIT_UNLIM)
> +		res |= BQ2415X_IIN_LIMIT_800_MASK;
> +	else if (min_uA >= BQ2415X_IIN_LIMIT_UNLIM)
> +		res |= BQ2415X_IIN_LIMIT_UNLIM_MASK;
> +	else
> +		return -EINVAL;
> +
> +	return bq2415x_i2c_write(cli, BQ2415X_GEN_CTL, res);
> +}

This is the sort of stuff that people were pushing through the regulator
API (and you have cloned the interface...) in order to allow a separate
bit of code to pick the current limits.  At the minute it looks like
you're hard coding the settings, the regulator API would at least let
you punt those to machines with fixed configurations and provides a hook
for anything which does want to play with the configuration at runtime.
Don't know if there's a better API, but it does seem like this is a
general thing for chargers and should therefore go through a generic
API.

On the other hand if you just set limits and let the charger get on with
its thing and run autonomously starting, stopping and fast charging by
itself then a power supply driver seems like a good fit - just provide
the upper limits as platform data or something and watch it go.

Either way it doesn't really seem like this device has multiple
functions...

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH] mfd: add bq2415x charger driver
  2011-12-06 11:31 ` Mark Brown
@ 2011-12-06 11:43     ` Felipe Contreras
  0 siblings, 0 replies; 28+ messages in thread
From: Felipe Contreras @ 2011-12-06 11:43 UTC (permalink / raw)
  To: Mark Brown
  Cc: Felipe Contreras, linux-main, linux-omap, Samuel Ortiz,
	Pali Roh??r, Aliaksei Katovich, Vladimir Zapolskiy

On Tue, Dec 6, 2011 at 1:31 PM, Mark Brown
<broonie@opensource.wolfsonmicro.com> wrote:
> On Tue, Dec 06, 2011 at 12:35:41AM +0200, Felipe Contreras wrote:
>
>> --- a/arch/arm/mach-omap2/board-rx51-peripherals.c
>> +++ b/arch/arm/mach-omap2/board-rx51-peripherals.c
>> @@ -925,6 +925,9 @@ static struct i2c_board_info __initdata rx51_peripherals_i2c_board_info_2[] = {
>>               I2C_BOARD_INFO("bq27200", 0x55),
>>       },
>>  #endif
>> +     {
>> +             I2C_BOARD_INFO("bq24150", 0x6b),
>> +     },
>
> Clearly this is orthogonal.

Yes, I added it just to demonstrate how to activate it.

>> +static inline int bq2415x_i2c_read(struct i2c_client *cli, u8 reg)
>> +{
>> +     return i2c_smbus_read_byte_data(cli, reg);
>> +}
>> +
>> +static inline int bq2415x_i2c_write(struct i2c_client *cli, u8 reg, u8 val)
>> +{
>> +     return i2c_smbus_write_byte_data(cli, reg, val);
>> +}
>
> regmap might be useful here (it's got an update bits operation and
> cache).

Will take a look.

>> +static int bq2415x_set_current_limit(struct i2c_client *cli,
>> +             int min_uA, int max_uA)
>> +{
>> +     int res;
>> +
>> +     res = bq2415x_i2c_read(cli, BQ2415X_GEN_CTL);
>> +     if (res < 0)
>> +             return res;
>> +
>> +     res &= ~BQ2415X_IIN_LIMIT_UNLIM_MASK;
>> +
>> +     if (min_uA >= BQ2415X_IIN_LIMIT_100 && max_uA < BQ2415X_IIN_LIMIT_500)
>> +             ;
>> +     else if (min_uA >= BQ2415X_IIN_LIMIT_500 && max_uA < BQ2415X_IIN_LIMIT_800)
>> +             res |= BQ2415X_IIN_LIMIT_500_MASK;
>> +     else if (min_uA >= BQ2415X_IIN_LIMIT_800 && max_uA < BQ2415X_IIN_LIMIT_UNLIM)
>> +             res |= BQ2415X_IIN_LIMIT_800_MASK;
>> +     else if (min_uA >= BQ2415X_IIN_LIMIT_UNLIM)
>> +             res |= BQ2415X_IIN_LIMIT_UNLIM_MASK;
>> +     else
>> +             return -EINVAL;
>> +
>> +     return bq2415x_i2c_write(cli, BQ2415X_GEN_CTL, res);
>> +}
>
> This is the sort of stuff that people were pushing through the regulator
> API (and you have cloned the interface...) in order to allow a separate
> bit of code to pick the current limits.  At the minute it looks like
> you're hard coding the settings, the regulator API would at least let
> you punt those to machines with fixed configurations and provides a hook
> for anything which does want to play with the configuration at runtime.
> Don't know if there's a better API, but it does seem like this is a
> general thing for chargers and should therefore go through a generic
> API.

Yes, for this particular case the regulator API might be useful, but I
don't see how external code will use this. Will they have to search
for the name of this regulator, and then try to change the
current_limit?

Anyway, there are other levels I'm not sure the regulator interface is
good for, like a "weak battery" voltage threshold, that most likely
would only make sense to handle internally in the driver.

> On the other hand if you just set limits and let the charger get on with
> its thing and run autonomously starting, stopping and fast charging by
> itself then a power supply driver seems like a good fit - just provide
> the upper limits as platform data or something and watch it go.

It would have to change its behavior depending on external events,
like charger plugged/unplugged, different types of chargers, and so
on. I'm thinking the rx51 board code could join some hooks from
isp1704 (which detects the events) into this driver.

Cheers.

-- 
Felipe Contreras

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH] mfd: add bq2415x charger driver
@ 2011-12-06 11:43     ` Felipe Contreras
  0 siblings, 0 replies; 28+ messages in thread
From: Felipe Contreras @ 2011-12-06 11:43 UTC (permalink / raw)
  To: Mark Brown
  Cc: Felipe Contreras, linux-main, linux-omap, Samuel Ortiz,
	Pali Roh??r, Aliaksei Katovich, Vladimir Zapolskiy

On Tue, Dec 6, 2011 at 1:31 PM, Mark Brown
<broonie@opensource.wolfsonmicro.com> wrote:
> On Tue, Dec 06, 2011 at 12:35:41AM +0200, Felipe Contreras wrote:
>
>> --- a/arch/arm/mach-omap2/board-rx51-peripherals.c
>> +++ b/arch/arm/mach-omap2/board-rx51-peripherals.c
>> @@ -925,6 +925,9 @@ static struct i2c_board_info __initdata rx51_peripherals_i2c_board_info_2[] = {
>>               I2C_BOARD_INFO("bq27200", 0x55),
>>       },
>>  #endif
>> +     {
>> +             I2C_BOARD_INFO("bq24150", 0x6b),
>> +     },
>
> Clearly this is orthogonal.

Yes, I added it just to demonstrate how to activate it.

>> +static inline int bq2415x_i2c_read(struct i2c_client *cli, u8 reg)
>> +{
>> +     return i2c_smbus_read_byte_data(cli, reg);
>> +}
>> +
>> +static inline int bq2415x_i2c_write(struct i2c_client *cli, u8 reg, u8 val)
>> +{
>> +     return i2c_smbus_write_byte_data(cli, reg, val);
>> +}
>
> regmap might be useful here (it's got an update bits operation and
> cache).

Will take a look.

>> +static int bq2415x_set_current_limit(struct i2c_client *cli,
>> +             int min_uA, int max_uA)
>> +{
>> +     int res;
>> +
>> +     res = bq2415x_i2c_read(cli, BQ2415X_GEN_CTL);
>> +     if (res < 0)
>> +             return res;
>> +
>> +     res &= ~BQ2415X_IIN_LIMIT_UNLIM_MASK;
>> +
>> +     if (min_uA >= BQ2415X_IIN_LIMIT_100 && max_uA < BQ2415X_IIN_LIMIT_500)
>> +             ;
>> +     else if (min_uA >= BQ2415X_IIN_LIMIT_500 && max_uA < BQ2415X_IIN_LIMIT_800)
>> +             res |= BQ2415X_IIN_LIMIT_500_MASK;
>> +     else if (min_uA >= BQ2415X_IIN_LIMIT_800 && max_uA < BQ2415X_IIN_LIMIT_UNLIM)
>> +             res |= BQ2415X_IIN_LIMIT_800_MASK;
>> +     else if (min_uA >= BQ2415X_IIN_LIMIT_UNLIM)
>> +             res |= BQ2415X_IIN_LIMIT_UNLIM_MASK;
>> +     else
>> +             return -EINVAL;
>> +
>> +     return bq2415x_i2c_write(cli, BQ2415X_GEN_CTL, res);
>> +}
>
> This is the sort of stuff that people were pushing through the regulator
> API (and you have cloned the interface...) in order to allow a separate
> bit of code to pick the current limits.  At the minute it looks like
> you're hard coding the settings, the regulator API would at least let
> you punt those to machines with fixed configurations and provides a hook
> for anything which does want to play with the configuration at runtime.
> Don't know if there's a better API, but it does seem like this is a
> general thing for chargers and should therefore go through a generic
> API.

Yes, for this particular case the regulator API might be useful, but I
don't see how external code will use this. Will they have to search
for the name of this regulator, and then try to change the
current_limit?

Anyway, there are other levels I'm not sure the regulator interface is
good for, like a "weak battery" voltage threshold, that most likely
would only make sense to handle internally in the driver.

> On the other hand if you just set limits and let the charger get on with
> its thing and run autonomously starting, stopping and fast charging by
> itself then a power supply driver seems like a good fit - just provide
> the upper limits as platform data or something and watch it go.

It would have to change its behavior depending on external events,
like charger plugged/unplugged, different types of chargers, and so
on. I'm thinking the rx51 board code could join some hooks from
isp1704 (which detects the events) into this driver.

Cheers.

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

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH] mfd: add bq2415x charger driver
  2011-12-06 11:43     ` Felipe Contreras
  (?)
@ 2011-12-06 11:46     ` Mark Brown
  -1 siblings, 0 replies; 28+ messages in thread
From: Mark Brown @ 2011-12-06 11:46 UTC (permalink / raw)
  To: Felipe Contreras
  Cc: Felipe Contreras, linux-main, linux-omap, Samuel Ortiz,
	Pali Roh??r, Aliaksei Katovich, Vladimir Zapolskiy

On Tue, Dec 06, 2011 at 01:43:06PM +0200, Felipe Contreras wrote:

> Yes, for this particular case the regulator API might be useful, but I
> don't see how external code will use this. Will they have to search
> for the name of this regulator, and then try to change the
> current_limit?

No, they'd get hooked in via the regulator interface as consumers.  Like
I say it depends on how autonomous the chip is, if it needs a lot of TLC
then it's probably not a regulator thing.  If all you do the chip is
turn in on and off and set limits then the upper layer should really be
able to be independant of the chip.

> > its thing and run autonomously starting, stopping and fast charging by
> > itself then a power supply driver seems like a good fit - just provide
> > the upper limits as platform data or something and watch it go.

> It would have to change its behavior depending on external events,
> like charger plugged/unplugged, different types of chargers, and so
> on. I'm thinking the rx51 board code could join some hooks from
> isp1704 (which detects the events) into this driver.

That stuff definitely sounds like power supply material - there's been
several other people discussing extending the framework to make it
easier for different power supply chain elements to coordinate with each
other.  

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH] mfd: add bq2415x charger driver
  2011-12-06  2:49     ` Felipe Contreras
  (?)
@ 2011-12-06 13:21     ` Sebastian Reichel
  2011-12-06 13:50       ` Felipe Contreras
  -1 siblings, 1 reply; 28+ messages in thread
From: Sebastian Reichel @ 2011-12-06 13:21 UTC (permalink / raw)
  To: Felipe Contreras
  Cc: Felipe Contreras, linux-main, linux-omap, Samuel Ortiz,
	Pali Rohár, Aliaksei Katovich, Vladimir Zapolskiy

[-- Attachment #1: Type: text/plain, Size: 1983 bytes --]

On Tue, Dec 06, 2011 at 04:49:37AM +0200, Felipe Contreras wrote:
> Not really. There are 6 bits: 640, 320, 160, 80, 40, 20. So values
> from 0 to 1260mV can be represented, at steps of 20mV. A value of 0
> means 3.5V.

I just checked it. You'r driver just writes 0x42 to the register,
which makes use of the described platform specific resistor.

> bq2415x_i2c_write(cli, BQ2415X_CHG_CTL, 0x42);

I know that 42 is the "Answer to the Ultimate Question of Life, the
Universe, and Everything" [0]. But in this case 0x42 is platform
specific. Check page 30 in the specs, From Table 9 onwards.

> > [...]
> >
> >> +     bq2415x_i2c_write(cli, BQ2415X_CHG_CTL, 0x42);
> >> +     bq2415x_set_voltage(cli, 0, 4200000);
> >> +     bq2415x_set_current_limit(cli, 1800000, 1800000);
> >
> > So it always charges with 1.8A? Shouldn't this be dependent on the
> > input source (e.g. 500mA from USB)? Also what's if no charger is
> > connected at all?
> 
> No. I'm hard-coding those values, but I wrote the functions
> (set_voltate, set_current_limit) so all values are supported.
> 
> What is missing is the code to connect this driver to the charger
> detector driver (isp1704), so those values are determined dynamically.

ok, I was talking about the current state ;)

> > Apart from this I think those values should be exposed to sysfs
> > via /sys/class/power_supply.
> 
> Perhaps. Although I don't see much of the power supply interface that
> could be useful here, but if so, I guess this should be indeed in
> 'drivers/power'.

So it may need some new entries. Normal mobile userspace wants to
know at least charging status & speed. Also it's probably a good
idea to make current_limit sysfs entry writeable, so that advanced
users can overwrite a autodetected value (e.g. a dumb wallcharger
may be detected as 100mA, but can provide 1A).

[0] http://en.wikipedia.org/wiki/Phrases_from_The_Hitchhiker%27s_Guide_to_the_Galaxy

-- Sebasitan

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 836 bytes --]

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH] mfd: add bq2415x charger driver
  2011-12-06 10:58       ` Felipe Contreras
@ 2011-12-06 13:27         ` Pali Rohár
  2011-12-06 14:11           ` Felipe Contreras
  0 siblings, 1 reply; 28+ messages in thread
From: Pali Rohár @ 2011-12-06 13:27 UTC (permalink / raw)
  To: Felipe Contreras
  Cc: Felipe Contreras, linux-main, linux-omap, Samuel Ortiz,
	Aliaksei Katovich, Vladimir Zapolskiy

[-- Attachment #1: Type: text/plain, Size: 6702 bytes --]

Hello,

On Tuesday 06 December 2011 12:58:06 you wrote:
> On Tue, Dec 6, 2011 at 9:25 AM, Pali Rohár <pali.rohar@gmail.com> wrote:
> > On Tuesday 06 December 2011 02:12:47 you wrote:
> >> On Tue, Dec 6, 2011 at 1:05 AM, Pali Rohár <pali.rohar@gmail.com> wrote:
> >> > I started writing other implementaion of bq2415x charger driver,
> >> > which
> >> > should support also setting usb host mode. Code is still
> >> > unfinished,
> >> > but now is devided into 2 parts: one power_supply driver and one
> >> > driver which cover all bq registers. See:
> >> > 
> >> > http://atrey.karlin.mff.cuni.cz/~pali/bq2415x/
> >> > 
> >> > Felipe Contreras, I think that my implementation is better - it
> >> > will
> >> > export all bq registers (which is needed for hostmode boost) and
> >> > will
> >> > also register regulator interface.
> >> 
> >> I took a look at your driver, and there's definitely good stuff in it.
> >> However, I think there's a lot of unnecessary stuff, like the
> >> miscdevice stuff (which was frowned upon for bq27x00), and a lot of
> >> user-space interface. Moreover, it doesn't seem to do anything on its
> >> own (it needs interaction from user-space).
> > 
> > Ignore miscdevice, I will remove them from driver. I will add some
> > debugfs interface for getting registers output (needed for debugging)
> 
> Ok. A device with such debugfs would be nice, but I would start
> without one, just something that works.

I think we do not need in mainline kernel driver which "only works" without 
any debug or additional support.

> 
> >> IMO the first step should be to have a minimal driver that just works,
> >> even if it doesn't achieve the absolute best charging performance.
> >> More features could be added later on.
> >> 
> >> Also, I'm not familiar with the regulator interface, but it seems to
> >> be meant for real regulators, which have consumers, and based on those
> >> consumers's needs the real voltage changes. This battery charger on
> >> the other hand doesn't have anything like that. There will be no
> >> consumer, and some stuff like the weak battery voltage is not even
> >> related to a voltage supply, but rather a threshold that can be
> >> configured to change some behavior of the charger, but there's no
> >> point in changing it dynamically (or maybe at all).
> > 
> > If regulator interface is not good, I can change it to some sysfs
> > interface. But bq2415x chip driver is not only rx51 specified, so it
> > should handle all chip capabilities.
> 
> I don't know if the regulator interface makes sense, but I think not.
> Anyway, I don't see how my code is specific to rx51, it should work
> with all bq2415x models.

voltage and current values could be different for other boards. So each board 
(with bq2415x chip) should have defined default charge properties (in platform 
data structure or something else...). your interface does not support such as 
other changes.

> 
> >> I guess the important one is the charge voltage, which is linked to a
> >> real voltage, but what consumers would it have? I don't think there's
> >> any.
> >> 
> >> Finally, I don't think user-space interaction should be needed at all.
> >> The driver should start charging immediately when there power supply
> >> available, and stop when there isn't any. Maybe at some point a
> >> user-space interface will be useful later on (I don't see why), but I
> >> don't think it should be *necessary*.
> > 
> > Userspace interfaction is needed. We need to tell driver to boost - for
> > usb host mode. But of course, battery charging should be automatic
> > without userspace interfaction.
> 
> Why do we need user-space for the boost mode?

Because on n900 we *want* USB host mode. Without boost mode support (in kernel 
driver) again will need to rmmod driver (now we stopping BME) and start 
handling it in userspace.

> 
> >> I'm not familiar with any of this stuff, so don't take my opinions too
> >> seriously :)
> > 
> > Consider my code. We do not need two (or more) implementation of same
> > driver in kernel. And also we do not need only rx51 specified code.
> 
> Of course, that's why I am discussing this :)
> 
> > I separated bq2415x register access into one module (bq2415x.c - without
> > any logic, only cover chip options) and real battery charging should be
> > done in power_supply interface (bq2415x_charger.c)
> 
> I don't see the point of having two drivers.

Because proper charging on n900 needs interact with isp1704 driver. But this 
is specified for n900, not for all boards. bq2415x module should be general 
for all boards - so it should cover *only* bq2415x chip - nothing other.

In my opition bq2415x module should only export chip register access (to 
userspace via debugfs... and to kernel via some interface or symbols...)

> 
> > My code has also prepaired boost support - for usb host mode, which must
> > be done in driver.
> 
> Well, yeah, in my driver it can be added as well, however, I don't
> think it's _needed_ right now.

Of course, but I (and maybe some other people) do not need uncompleted chip 
driver.

> 
> First, I would like something that works by itself (without
> user-space), which I already have. Next, I would like it to plug into
> isp1704 to detect when a charger is connected, and select the correct
> limits accordingly. I guess this hooks should be connected on the
> board code. Once having that, I think the driver should be ready for
> merging, the rest of the features can come later.

Working without userspace is my primary goal. But also for debugging (and 
status apps/scripts) is needed direct register access. isp1704 interaction 
should not be in bq2415x chip driver, but in some rx51 specified code.

Charging should be done in power_supply interface.

See also api specification by Joerg Reisenweber (one of n900 usb hostmode 
support) on http://maemo.cloud-7.de/bq24150-sysnode.spec.txt
Similar interface is needed for proper usb host mode.

Also your driver does not handle errors, when charging and watchdog should be 
stopped. Charging is *very* crytical parts and it really should detect errors.

When in future I (or someone else) will want to add all missing features into 
bq2415x chip driver, it will be needed to rewrite it... (e.g. handling errors 
in boost mode)...

Why to very very quicky merge uncompleted (but working) driver to upstream? I 
think we should finish bq2415x chip driver and if all will be implemented, 
then to merge it. What other developers think about that?

> 
> Cheers.

-- 
Pali Rohár
pali.rohar@gmail.com

[-- Attachment #2: This is a digitally signed message part. --]
[-- Type: application/pgp-signature, Size: 198 bytes --]

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH] mfd: add bq2415x charger driver
  2011-12-06  2:49     ` Felipe Contreras
  (?)
  (?)
@ 2011-12-06 13:34     ` Pali Rohár
  -1 siblings, 0 replies; 28+ messages in thread
From: Pali Rohár @ 2011-12-06 13:34 UTC (permalink / raw)
  To: Felipe Contreras
  Cc: Sebastian Reichel, Felipe Contreras, linux-main, linux-omap,
	Samuel Ortiz, Aliaksei Katovich, Vladimir Zapolskiy

[-- Attachment #1: Type: text/plain, Size: 2209 bytes --]

Hi,

On Tuesday 06 December 2011 04:49:37 you wrote:
> Hi,
> 
> 2011/12/6 Sebastian Reichel <sre@debian.org>:
> > Good to hear somebody is working on this :) I planned to do this
> > myself, but didn't find any time for it. Here are some comments
> :
> > from me:
> :)
> :
> > [...]
> > 
> >> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> > 
> > I would expect the driver in drivers/power. But maybe MFD is
> > better, since the chip can also be used to power USB host?

Yes, chip support usb host mode and is working on rx51.

> 
> Perhaps, at least I haven't tried that.
> 
> > [...]
> > 
> >> +#define BQ2415X_VOLTAGE_SCALE 20000
> > 
> > If I remember correctly (I read the specs some time ago), this can
> > be configured via external resistor. So this is board specific?
> 
> Not really. There are 6 bits: 640, 320, 160, 80, 40, 20. So values
> from 0 to 1260mV can be represented, at steps of 20mV. A value of 0
> means 3.5V.
> 
> > [...]
> > 
> >> +     bq2415x_i2c_write(cli, BQ2415X_CHG_CTL, 0x42);
> >> +     bq2415x_set_voltage(cli, 0, 4200000);
> >> +     bq2415x_set_current_limit(cli, 1800000, 1800000);
> > 
> > So it always charges with 1.8A? Shouldn't this be dependent on the
> > input source (e.g. 500mA from USB)? Also what's if no charger is
> > connected at all?
> 
> No. I'm hard-coding those values, but I wrote the functions
> (set_voltate, set_current_limit) so all values are supported.

These hardcoded values should go to board specified section.

> 
> What is missing is the code to connect this driver to the charger
> detector driver (isp1704), so those values are determined dynamically.
> 
> > Apart from this I think those values should be exposed to sysfs
> > via /sys/class/power_supply.
> 
> Perhaps. Although I don't see much of the power supply interface that
> could be useful here, but if so, I guess this should be indeed in
> 'drivers/power'.

Power supply interface has pending patches for chargers. Patches will add 
interface for periodic calling functions - which is usefull for watchdog reset 
and charging (+ errors handling).

> 
> Cheers.

-- 
Pali Rohár
pali.rohar@gmail.com

[-- Attachment #2: This is a digitally signed message part. --]
[-- Type: application/pgp-signature, Size: 198 bytes --]

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH] mfd: add bq2415x charger driver
  2011-12-06 13:21     ` Sebastian Reichel
@ 2011-12-06 13:50       ` Felipe Contreras
  0 siblings, 0 replies; 28+ messages in thread
From: Felipe Contreras @ 2011-12-06 13:50 UTC (permalink / raw)
  To: Sebastian Reichel
  Cc: Felipe Contreras, linux-main, linux-omap, Samuel Ortiz,
	Pali Rohár, Aliaksei Katovich, Vladimir Zapolskiy

2011/12/6 Sebastian Reichel <sre@debian.org>:
> On Tue, Dec 06, 2011 at 04:49:37AM +0200, Felipe Contreras wrote:
>> Not really. There are 6 bits: 640, 320, 160, 80, 40, 20. So values
>> from 0 to 1260mV can be represented, at steps of 20mV. A value of 0
>> means 3.5V.
>
> I just checked it. You'r driver just writes 0x42 to the register,
> which makes use of the described platform specific resistor.
>
>> bq2415x_i2c_write(cli, BQ2415X_CHG_CTL, 0x42);
>
> I know that 42 is the "Answer to the Ultimate Question of Life, the
> Universe, and Everything" [0]. But in this case 0x42 is platform
> specific. Check page 30 in the specs, From Table 9 onwards.

Well, you were talking about BQ2415X_VOLTAGE_SCALE, which I used for
the voltage register calculation.

But yeah, you are right about BQ2415X_CHG_CTL. Maybe it would make
sense to make it a platform driver, or perhaps just expose functions
so the board can set the right values.

>> > Apart from this I think those values should be exposed to sysfs
>> > via /sys/class/power_supply.
>>
>> Perhaps. Although I don't see much of the power supply interface that
>> could be useful here, but if so, I guess this should be indeed in
>> 'drivers/power'.
>
> So it may need some new entries. Normal mobile userspace wants to
> know at least charging status & speed. Also it's probably a good
> idea to make current_limit sysfs entry writeable, so that advanced
> users can overwrite a autodetected value (e.g. a dumb wallcharger
> may be detected as 100mA, but can provide 1A).

True. drivers/power it is then :)

-- 
Felipe Contreras

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH] mfd: add bq2415x charger driver
  2011-12-06 13:27         ` Pali Rohár
@ 2011-12-06 14:11           ` Felipe Contreras
  2011-12-06 15:19             ` Pali Rohár
  0 siblings, 1 reply; 28+ messages in thread
From: Felipe Contreras @ 2011-12-06 14:11 UTC (permalink / raw)
  To: Pali Rohár
  Cc: Felipe Contreras, linux-main, linux-omap, Samuel Ortiz,
	Aliaksei Katovich, Vladimir Zapolskiy

On Tue, Dec 6, 2011 at 3:27 PM, Pali Rohár <pali.rohar@gmail.com> wrote:
> On Tuesday 06 December 2011 12:58:06 you wrote:
>> Ok. A device with such debugfs would be nice, but I would start
>> without one, just something that works.
>
> I think we do not need in mainline kernel driver which "only works" without
> any debug or additional support.

I think that's a good start, and that's what I will focus on.

>> I don't know if the regulator interface makes sense, but I think not.
>> Anyway, I don't see how my code is specific to rx51, it should work
>> with all bq2415x models.
>
> voltage and current values could be different for other boards. So each board
> (with bq2415x chip) should have defined default charge properties (in platform
> data structure or something else...). your interface does not support such as
> other changes.

No, they wouldn't, that depends on many things, like the type of
charger. As Sebastian pointed out, the current *sense* voltage, is
board specific, but that's about it.

>> Why do we need user-space for the boost mode?
>
> Because on n900 we *want* USB host mode. Without boost mode support (in kernel
> driver) again will need to rmmod driver (now we stopping BME) and start
> handling it in userspace.

But we can have boost mode *without* user-space. There's no reason why
it can't be handled by the kernel.

>> >> I'm not familiar with any of this stuff, so don't take my opinions too
>> >> seriously :)
>> >
>> > Consider my code. We do not need two (or more) implementation of same
>> > driver in kernel. And also we do not need only rx51 specified code.
>>
>> Of course, that's why I am discussing this :)
>>
>> > I separated bq2415x register access into one module (bq2415x.c - without
>> > any logic, only cover chip options) and real battery charging should be
>> > done in power_supply interface (bq2415x_charger.c)
>>
>> I don't see the point of having two drivers.
>
> Because proper charging on n900 needs interact with isp1704 driver. But this
> is specified for n900, not for all boards. bq2415x module should be general
> for all boards - so it should cover *only* bq2415x chip - nothing other.

Yes, and that be done with hooks. The bq2415x driver will have hooks,
and the rx51 board configuration will connect isp1704 to bq2415x, and
that's it. No need for yet another driver.

>> > My code has also prepaired boost support - for usb host mode, which must
>> > be done in driver.
>>
>> Well, yeah, in my driver it can be added as well, however, I don't
>> think it's _needed_ right now.
>
> Of course, but I (and maybe some other people) do not need uncompleted chip
> driver.

Anything is better than what we have now, which is nothing. Look for
example to how the bq27x00 battery driver evolved; it started very
simple.

>> First, I would like something that works by itself (without
>> user-space), which I already have. Next, I would like it to plug into
>> isp1704 to detect when a charger is connected, and select the correct
>> limits accordingly. I guess this hooks should be connected on the
>> board code. Once having that, I think the driver should be ready for
>> merging, the rest of the features can come later.
>
> Working without userspace is my primary goal. But also for debugging (and
> status apps/scripts) is needed direct register access. isp1704 interaction
> should not be in bq2415x chip driver, but in some rx51 specified code.

Yes.

> Charging should be done in power_supply interface.
>
> See also api specification by Joerg Reisenweber (one of n900 usb hostmode
> support) on http://maemo.cloud-7.de/bq24150-sysnode.spec.txt
> Similar interface is needed for proper usb host mode.

That is very interesting... Is there yet another module for this?
Again, I don't understand why interaction with user-space is *needed*
for host mode.

> Also your driver does not handle errors, when charging and watchdog should be
> stopped. Charging is *very* crytical parts and it really should detect errors.

Indeed, that's why this is RFC only.

> When in future I (or someone else) will want to add all missing features into
> bq2415x chip driver, it will be needed to rewrite it... (e.g. handling errors
> in boost mode)...

Perhaps, but I don't think so. Anyway, again, see the evolution of
bq27x00, or basically anything in the kernel. If something needs to be
refactored for new features, so be it.

But I think there is some consensus; the drivers should be in
drivers/power, and have a power supply interface, rx51 board info
should configure some sense voltage, and hook it up with
isp1704_charger somehow. Once this is done and driver is merged, I
don't expect that to change.

> Why to very very quicky merge uncompleted (but working) driver to upstream? I
> think we should finish bq2415x chip driver and if all will be implemented,
> then to merge it. What other developers think about that?

This version of the driver is not the one I am proposing to merge. The
one I'm proposed to merge should be a good basis for future work (or
what you are doing right now).

Cheers.

-- 
Felipe Contreras

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH] mfd: add bq2415x charger driver
  2011-12-06 14:11           ` Felipe Contreras
@ 2011-12-06 15:19             ` Pali Rohár
  0 siblings, 0 replies; 28+ messages in thread
From: Pali Rohár @ 2011-12-06 15:19 UTC (permalink / raw)
  To: Felipe Contreras
  Cc: Felipe Contreras, linux-main, linux-omap, Samuel Ortiz,
	Aliaksei Katovich, Vladimir Zapolskiy

[-- Attachment #1: Type: text/plain, Size: 6233 bytes --]

Hello,

On Tuesday 06 December 2011 16:11:33 Felipe Contreras wrote:
> On Tue, Dec 6, 2011 at 3:27 PM, Pali Rohár <pali.rohar@gmail.com> wrote:
> > On Tuesday 06 December 2011 12:58:06 you wrote:
> >> Ok. A device with such debugfs would be nice, but I would start
> >> without one, just something that works.
> > 
> > I think we do not need in mainline kernel driver which "only works"
> > without any debug or additional support.
> 
> I think that's a good start, and that's what I will focus on.
> 
> >> I don't know if the regulator interface makes sense, but I think not.
> >> Anyway, I don't see how my code is specific to rx51, it should work
> >> with all bq2415x models.
> > 
> > voltage and current values could be different for other boards. So each
> > board (with bq2415x chip) should have defined default charge properties
> > (in platform data structure or something else...). your interface does
> > not support such as other changes.
> 
> No, they wouldn't, that depends on many things, like the type of
> charger. As Sebastian pointed out, the current *sense* voltage, is
> board specific, but that's about it.
> 
> >> Why do we need user-space for the boost mode?
> > 
> > Because on n900 we *want* USB host mode. Without boost mode support (in
> > kernel driver) again will need to rmmod driver (now we stopping BME)
> > and start handling it in userspace.
> 
> But we can have boost mode *without* user-space. There's no reason why
> it can't be handled by the kernel.

I thought that: we need from userspace signal "now enable host mode" and "now 
disable host mode". So kernel driver should has sysfs for enable/disable host 
mode - only this, all other will be of course handled in kernel.

> 
> >> >> I'm not familiar with any of this stuff, so don't take my
> >> >> opinions too seriously :)
> >> > 
> >> > Consider my code. We do not need two (or more) implementation of
> >> > same
> >> > driver in kernel. And also we do not need only rx51 specified
> >> > code.
> >> 
> >> Of course, that's why I am discussing this :)
> >> 
> >> > I separated bq2415x register access into one module (bq2415x.c -
> >> > without any logic, only cover chip options) and real battery
> >> > charging should be done in power_supply interface
> >> > (bq2415x_charger.c)
> >> 
> >> I don't see the point of having two drivers.
> > 
> > Because proper charging on n900 needs interact with isp1704 driver. But
> > this is specified for n900, not for all boards. bq2415x module should
> > be general for all boards - so it should cover *only* bq2415x chip -
> > nothing other.
> Yes, and that be done with hooks. The bq2415x driver will have hooks,
> and the rx51 board configuration will connect isp1704 to bq2415x, and
> that's it. No need for yet another driver.

Ok, this sounds good.

> 
> >> > My code has also prepaired boost support - for usb host mode,
> >> > which must be done in driver.
> >> 
> >> Well, yeah, in my driver it can be added as well, however, I don't
> >> think it's _needed_ right now.
> > 
> > Of course, but I (and maybe some other people) do not need uncompleted
> > chip driver.
> 
> Anything is better than what we have now, which is nothing. Look for
> example to how the bq27x00 battery driver evolved; it started very
> simple.

I know, I sent more bq27x00_battery patches :-)

> 
> >> First, I would like something that works by itself (without
> >> user-space), which I already have. Next, I would like it to plug into
> >> isp1704 to detect when a charger is connected, and select the correct
> >> limits accordingly. I guess this hooks should be connected on the
> >> board code. Once having that, I think the driver should be ready for
> >> merging, the rest of the features can come later.
> > 
> > Working without userspace is my primary goal. But also for debugging
> > (and
> > status apps/scripts) is needed direct register access. isp1704
> > interaction should not be in bq2415x chip driver, but in some rx51
> > specified code.
> Yes.
> 
> > Charging should be done in power_supply interface.
> > 
> > See also api specification by Joerg Reisenweber (one of n900 usb
> > hostmode
> > support) on http://maemo.cloud-7.de/bq24150-sysnode.spec.txt
> > Similar interface is needed for proper usb host mode.
> 
> That is very interesting... Is there yet another module for this?
> Again, I don't understand why interaction with user-space is *needed*
> for host mode.

No other module exists - my is not finished :-)
Host mode only needs to kernel enable or disable it.

> 
> > Also your driver does not handle errors, when charging and watchdog
> > should be stopped. Charging is *very* crytical parts and it really
> > should detect errors.
> Indeed, that's why this is RFC only.
> 
> > When in future I (or someone else) will want to add all missing features
> > into bq2415x chip driver, it will be needed to rewrite it... (e.g.
> > handling errors in boost mode)...
> 
> Perhaps, but I don't think so. Anyway, again, see the evolution of
> bq27x00, or basically anything in the kernel. If something needs to be
> refactored for new features, so be it.
> 
> But I think there is some consensus; the drivers should be in
> drivers/power, and have a power supply interface, rx51 board info
> should configure some sense voltage, and hook it up with
> isp1704_charger somehow. Once this is done and driver is merged, I
> don't expect that to change.

Ok, I agree with this.

> 
> > Why to very very quicky merge uncompleted (but working) driver to
> > upstream? I think we should finish bq2415x chip driver and if all will
> > be implemented, then to merge it. What other developers think about
> > that?
> 
> This version of the driver is not the one I am proposing to merge. The
> one I'm proposed to merge should be a good basis for future work (or
> what you are doing right now).
> 
> Cheers.

I updated my last code at http://atrey.karlin.mff.cuni.cz/~pali/bq2415x/

Now my question is: Should I stop working on my implementation and wait until 
you finish yours? Or start merging code?


-- 
Pali Rohár
pali.rohar@gmail.com

[-- Attachment #2: This is a digitally signed message part. --]
[-- Type: application/pgp-signature, Size: 198 bytes --]

^ permalink raw reply	[flat|nested] 28+ messages in thread

* RFC: bq2415x_charger driver
  2011-12-05 22:35 [PATCH] mfd: add bq2415x charger driver Felipe Contreras
                   ` (3 preceding siblings ...)
  2011-12-06 11:31 ` Mark Brown
@ 2011-12-07 21:03 ` Pali Rohár
  2011-12-07 21:25   ` Vladimir Zapolskiy
  2012-01-27  2:40   ` RFC 2: " Pali Rohár
  4 siblings, 2 replies; 28+ messages in thread
From: Pali Rohár @ 2011-12-07 21:03 UTC (permalink / raw)
  To: linux-main
  Cc: linux-omap, Samuel Ortiz, Aliaksei Katovich, Vladimir Zapolskiy,
	Felipe Contreras, Anton Vorontsov, Joerg Reisenweber


[-- Attachment #1.1: Type: text/plain, Size: 821 bytes --]

Hello,

I'm sending preview of my implementation of bq2415x charger driver. Now all 
code is in one kernel module bq2415x_charger which has more interfaces 
(power_supply, sysfs, regulator). It is still unfinished, missing more bq2415x 
configuration like sysfs entires for boost, voltages or platform hooks (e.g. 
rx51 isp1704). This driver will have similar sysfs interface as in Joerg 
Reisenweber spec http://maemo.cloud-7.de/bq24150-sysnode.spec.txt . Driver 
should be replacemnt for Nokia N900 proprietary charging program BME. I was 
inspirated by driver bq27x00_battery and other bq2415x implementaton by 
Aliaksei Katovich and Felipe Contreras.

Please comment implementation and write what should be added or modified in 
driver (it is still not complete).

-- 
Pali Rohár
pali.rohar@gmail.com

[-- Attachment #1.2: bq2415x_charger.c --]
[-- Type: text/x-csrc, Size: 33248 bytes --]

/*
    bq2415x_charger.c - bq2415x charger driver
    Copyright (C) 2011  Pali Rohár <pali.rohar@gmail.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.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

/*
  Datasheets:
  http://www.ti.com/product/bq24150
  http://www.ti.com/product/bq24150a
  http://www.ti.com/product/bq24152
  http://www.ti.com/product/bq24153
  http://www.ti.com/product/bq24153a
  http://www.ti.com/product/bq24155
*/

#include <linux/version.h>
#include <linux/module.h>
#include <linux/param.h>
#include <linux/err.h>
#include <linux/workqueue.h>
#include <linux/sysfs.h>
#include <linux/platform_device.h>
#include <linux/regulator/driver.h>
#include <linux/power_supply.h>
#include <linux/idr.h>
#include <linux/leds.h>
#include <linux/i2c.h>
#include <linux/slab.h>

#include "bq2415x_charger.h"

#define BQ2415X_WATCHDOG_TIMEOUT	10

#define BQ2415X_REG_STATUS		0x00
#define BQ2415X_REG_CONTROL		0x01
#define BQ2415X_REG_VOLTAGE		0x02
#define BQ2415X_REG_VENDER		0x03
#define BQ2415X_REG_CURRENT		0x04

/* reset state for all registers */
#define BQ2415X_RESET_STATUS		BIT(6)
#define BQ2415X_RESET_CONTROL		(BIT(4)|BIT(5))
#define BQ2415X_RESET_VOLTAGE		(BIT(1)|BIT(3))
#define BQ2415X_RESET_CURRENT		(BIT(0)|BIT(3)|BIT(7))

/* status register */
#define BQ2415X_BIT_TMR_RST		7
#define BQ2415X_BIT_OTG			7
#define BQ2415X_BIT_EN_STAT		6
#define BQ2415X_MASK_STAT		(BIT(4)|BIT(5))
#define BQ2415X_SHIFT_STAT		4
#define BQ2415X_BIT_BOOST		3
#define BQ2415X_MASK_FAULT		(BIT(0)|BIT(1)|BIT(2))
#define BQ2415X_SHIFT_FAULT		0

/* control register */
#define BQ2415X_MASK_LIMIT		(BIT(6)|BIT(7))
#define BQ2415X_SHIFT_LIMIT		6
#define BQ2415X_MASK_VLOWV		(BIT(4)|BIT(5))
#define BQ2415X_SHIFT_VLOWV		4
#define BQ2415X_BIT_TE			3
#define BQ2415X_BIT_CE			2
#define BQ2415X_BIT_HZ_MODE		1
#define BQ2415X_BIT_OPA_MODE		0

/* voltage register */
#define BQ2415X_MASK_VO			(BIT(2)|BIT(3)|BIT(4)|BIT(5)|BIT(6)|BIT(7))
#define BQ2415X_SHIFT_VO		2
#define BQ2415X_BIT_OTG_PL		1
#define BQ2415X_BIT_OTG_EN		0

/* vender register */
#define BQ2415X_MASK_VENDER		(BIT(5)|BIT(6)|BIT(7))
#define BQ2415X_SHIFT_VENDER		5
#define BQ2415X_MASK_PN			(BIT(3)|BIT(4))
#define BQ2415X_SHIFT_PN		4
#define BQ2415X_MASK_REVISION		(BIT(0)|BIT(1)|BIT(2))
#define BQ2415X_SHIFT_REVISION		0

/* current register */
/* RESET				BIT(7) */
#define BQ2415X_MASK_VI_CHRG		(BIT(4)|BIT(5)|BIT(6))
#define BQ2415X_SHIFT_VI_CHRG		4
/* N/A					BIT(3) */
#define BQ2415X_MASK_VI_TERM		(BIT(0)|BIT(1)|BIT(2))
#define BQ2415X_SHIFT_VI_TERM		0

struct bq2415x_regulator {
	struct regulator_ops ops;
	struct regulator_desc desc;
	struct regulator_dev *dev;
};

struct bq2415x_device {
	struct device *dev;
	struct bq2415x_platform_data *platform_data;
	struct bq2415x_regulator current_limit;
	struct bq2415x_regulator weak_battery_voltage;
	struct bq2415x_regulator battery_regulation_voltage;
	struct bq2415x_regulator charge_current_sense_voltage;
	struct bq2415x_regulator termination_current_sense_voltage;
	struct led_classdev led;
	struct power_supply charger;
	struct delayed_work work;
	int watchdog;
	enum bq2415x_chip chip;
	char *name;
	int id;
};

static DEFINE_IDR(bq2415x_id);
static DEFINE_MUTEX(bq2415x_mutex);

/* i2c read functions */

static int bq2415x_i2c_read(struct bq2415x_device *bq, u8 reg)
{
	struct i2c_client *client = to_i2c_client(bq->dev);
	struct i2c_msg msg[2];
	u8 val;
	int ret;

	dev_info(bq->dev, "bq2415x_i2c_read reg=%#x\n", reg);

	if (!client->adapter)
		return -ENODEV;

	msg[0].addr = client->addr;
	msg[0].flags = 0;
	msg[0].buf = &reg;
	msg[0].len = sizeof(reg);
	msg[1].addr = client->addr;
	msg[1].flags = I2C_M_RD;
	msg[1].buf = &val;
	msg[1].len = sizeof(val);

	mutex_lock(&bq2415x_mutex);
	ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
	mutex_unlock(&bq2415x_mutex);

	if (ret < 0)
		return ret;

	return val;
}

static int bq2415x_i2c_read_mask(struct bq2415x_device *bq, u8 reg, u8 mask, u8 shift)
{
	int ret;

	dev_info(bq->dev, "bq2415x_i2c_read_mask reg=%#x mask=%#x shift=%#x\n", reg, mask, shift);

	if (shift > 8)
		return -EINVAL;

	ret = bq2415x_i2c_read(bq, reg);
	if (ret < 0)
		return ret;
	else
		return (ret & mask) >> shift;
}

static int bq2415x_i2c_read_bit(struct bq2415x_device *bq, u8 reg, u8 bit)
{
	if (bit > 8)
		return -EINVAL;
	else
		return bq2415x_i2c_read_mask(bq, reg, BIT(bit), bit);
}

/* i2c write functions */

static int bq2415x_i2c_write(struct bq2415x_device *bq, u8 reg, u8 val)
{
	struct i2c_client *client = to_i2c_client(bq->dev);
	struct i2c_msg msg[1];
	u8 data[2];
	int ret;

	dev_info(bq->dev, "bq2415x_i2c_write reg=%#x val=%#x\n", reg, val);

	data[0] = reg;
	data[1] = val;

	msg[0].addr = client->addr;
	msg[0].flags = 0;
	msg[0].buf = data;
	msg[0].len = ARRAY_SIZE(data);

	mutex_lock(&bq2415x_mutex);
	ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
	mutex_unlock(&bq2415x_mutex);

	/* i2c_transfer returns number of messages transferred */
	if (ret < 0)
		return ret;
	else if (ret != 1)
		return -EIO;

	return 0;
}

static int bq2415x_i2c_write_mask(struct bq2415x_device *bq, u8 reg, u8 val, u8 mask, u8 shift)
{
	int ret;

	dev_info(bq->dev, "bq2415x_i2c_write_mask reg=%#x val=%#x mask=%#x shift=%#x\n", reg, val, mask, shift);

	if (shift > 8)
		return -EINVAL;

	ret = bq2415x_i2c_read(bq, reg);
	if (ret < 0)
		return ret;

	ret &= ~mask;
	ret |= val << shift;

	return bq2415x_i2c_write(bq, reg, ret);
}

static int bq2415x_i2c_write_bit(struct bq2415x_device *bq, u8 reg, bool val, u8 bit)
{
	if (bit > 8)
		return -EINVAL;
	else
		return bq2415x_i2c_write_mask(bq, reg, val, BIT(bit), bit);
}

/* global detect chip */

enum bq2415x_chip bq2415x_detect_chip(struct bq2415x_device *bq)
{
	struct i2c_client *client = to_i2c_client(bq->dev);
	int ret = bq2415x_exec_command(bq, BQ2415X_PART_NUMBER);
	if (ret < 0)
		return ret;
	if (client->addr == 0x6b) {
		switch (ret) {
			case 0:
				if (bq->chip == BQ24151A)
					return bq->chip;
				else
					return BQ24151;
			case 1:
				if (bq->chip == BQ24150A || bq->chip == BQ24152 || bq->chip == BQ24155)
					return bq->chip;
				else
					return BQ24150;
			case 2:
				if (bq->chip == BQ24153A)
					return bq->chip;
				else
					return BQ24153;
			default:
				return BQUNKNOWN;
		}
	} else if (client->addr == 0x6a) {
		switch (ret) {
			case 0:
				if (bq->chip == BQ24156A)
					return bq->chip;
				else
					return BQ24156;
			case 2:
				return BQ24158;
			default:
				return BQUNKNOWN;
		}
	}
	return BQUNKNOWN;
}
EXPORT_SYMBOL_GPL(bq2415x_detect_chip);

/* global exec command function */

int bq2415x_exec_command(struct bq2415x_device *bq, enum bq2415x_command command)
{
	switch(command)
	{
		case BQ2415X_WATCHDOG_RESET:
			return bq2415x_i2c_write_bit(bq, BQ2415X_REG_STATUS, 1, BQ2415X_BIT_TMR_RST);
		case BQ2415X_OTG_STATUS:
			return bq2415x_i2c_read_bit(bq, BQ2415X_REG_STATUS, BQ2415X_BIT_OTG);
		case BQ2415X_STAT_PIN_STATUS:
			return bq2415x_i2c_read_bit(bq, BQ2415X_REG_STATUS, BQ2415X_BIT_EN_STAT);
		case BQ2415X_STAT_PIN_ENABLE:
			return bq2415x_i2c_write_bit(bq, BQ2415X_REG_STATUS, 1, BQ2415X_BIT_EN_STAT);
		case BQ2415X_STAT_PIN_DISABLE:
			return bq2415x_i2c_write_bit(bq, BQ2415X_REG_STATUS, 0, BQ2415X_BIT_EN_STAT);
		case BQ2415X_CHARGE_STATUS:
			return bq2415x_i2c_read_mask(bq, BQ2415X_REG_STATUS, BQ2415X_MASK_STAT, BQ2415X_SHIFT_STAT);
		case BQ2415X_BOOST_STATUS:
			return bq2415x_i2c_read_bit(bq, BQ2415X_REG_STATUS, BQ2415X_BIT_BOOST);
		case BQ2415X_FAULT_STATUS:
			return bq2415x_i2c_read_mask(bq, BQ2415X_REG_STATUS, BQ2415X_MASK_FAULT, BQ2415X_SHIFT_FAULT);

		case BQ2415X_CHARGE_CURRENT_TERMINATION_STATUS:
			return bq2415x_i2c_read_bit(bq, BQ2415X_REG_CONTROL, BQ2415X_BIT_TE);
		case BQ2415X_CHARGE_CURRENT_TERMINATION_ENABLE:
			return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, 1, BQ2415X_BIT_TE);
		case BQ2415X_CHARGE_CURRENT_TERMINATION_DISABLE:
			return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, 0, BQ2415X_BIT_TE);
		case BQ2415X_CHARGER_STATUS:
			return bq2415x_i2c_read_bit(bq, BQ2415X_REG_CONTROL, BQ2415X_BIT_CE);
		case BQ2415X_CHARGER_ENABLE:
			return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, 1, BQ2415X_BIT_CE);
		case BQ2415X_CHARGER_DISABLE:
			return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, 0, BQ2415X_BIT_CE);
		case BQ2415X_HIGH_IMPEDANCE_STATUS:
			return bq2415x_i2c_read_bit(bq, BQ2415X_REG_CONTROL, BQ2415X_BIT_HZ_MODE);
		case BQ2415X_HIGH_IMPEDANCE_ENABLE:
			return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, 1, BQ2415X_BIT_HZ_MODE);
		case BQ2415X_HIGH_IMPEDANCE_DISABLE:
			return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, 0, BQ2415X_BIT_HZ_MODE);
		case BQ2415X_MODE:
			return bq2415x_i2c_read_bit(bq, BQ2415X_REG_CONTROL, BQ2415X_BIT_OPA_MODE);
		case BQ2415X_SET_BOOST_MODE:
			return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, 1, BQ2415X_BIT_OPA_MODE);
		case BQ2415X_SET_CHARGER_MODE:
			return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, 0, BQ2415X_BIT_OPA_MODE);

		case BQ2415X_OTG_HIGH_STATUS:
			return bq2415x_i2c_read_bit(bq, BQ2415X_REG_VOLTAGE, BQ2415X_BIT_OTG_PL);
		case BQ2415X_OTG_HIGH_ACTIVATE:
			return bq2415x_i2c_write_bit(bq, BQ2415X_REG_VOLTAGE, 1, BQ2415X_BIT_OTG_PL);
		case BQ2415X_OTG_LOW_ACTIVATE:
			return bq2415x_i2c_write_bit(bq, BQ2415X_REG_VOLTAGE, 0, BQ2415X_BIT_OTG_PL);
		case BQ2415X_OTG_PIN_STATUS:
			return bq2415x_i2c_read_bit(bq, BQ2415X_REG_VOLTAGE, BQ2415X_BIT_OTG_EN);
		case BQ2415X_OTG_PIN_ENABLE:
			return bq2415x_i2c_write_bit(bq, BQ2415X_REG_VOLTAGE, 1, BQ2415X_BIT_OTG_EN);
		case BQ2415X_OTG_PIN_DISABLE:
			return bq2415x_i2c_write_bit(bq, BQ2415X_REG_VOLTAGE, 0, BQ2415X_BIT_OTG_EN);

		case BQ2415X_VENDER_CODE:
			return bq2415x_i2c_read_mask(bq, BQ2415X_REG_VENDER, BQ2415X_MASK_VENDER, BQ2415X_SHIFT_VENDER);
		case BQ2415X_PART_NUMBER:
			return bq2415x_i2c_read_mask(bq, BQ2415X_REG_VENDER, BQ2415X_MASK_PN, BQ2415X_SHIFT_PN);
		case BQ2415X_REVISION:
			return bq2415x_i2c_read_mask(bq, BQ2415X_REG_VENDER, BQ2415X_MASK_REVISION, BQ2415X_SHIFT_REVISION);

		default:
			return -EINVAL;
	}
}
EXPORT_SYMBOL_GPL(bq2415x_exec_command);

/* global other functions */

#define bq2415x_set_default_value(bq, value) \
	do { \
		int ret = 0; \
		if (bq->platform_data->value != -1) \
			ret = bq2415x_set_##value(bq, bq->platform_data->value); \
		if (ret < 0) \
			return ret; \
	} while (0)

int bq2415x_set_defaults(struct bq2415x_device *bq)
{
	bq2415x_exec_command(bq, BQ2415X_CHARGER_DISABLE);
	bq2415x_set_default_value(bq, current_limit);
	bq2415x_set_default_value(bq, weak_battery_voltage);
	bq2415x_set_default_value(bq, battery_regulation_voltage);
	bq2415x_set_default_value(bq, charge_current_sense_voltage);
	bq2415x_set_default_value(bq, termination_current_sense_voltage);
	bq2415x_exec_command(bq, BQ2415X_CHARGER_ENABLE);
	return 0;
}
EXPORT_SYMBOL_GPL(bq2415x_set_defaults);

#undef bq2415x_set_default_value

void bq2415x_reset_chip(struct bq2415x_device *bq)
{
	bq2415x_i2c_write(bq, BQ2415X_REG_CURRENT, BQ2415X_RESET_CURRENT);
	bq2415x_i2c_write(bq, BQ2415X_REG_VOLTAGE, BQ2415X_RESET_VOLTAGE);
	bq2415x_i2c_write(bq, BQ2415X_REG_CONTROL, BQ2415X_RESET_CONTROL);
	bq2415x_i2c_write(bq, BQ2415X_REG_STATUS, BQ2415X_RESET_STATUS);
}
EXPORT_SYMBOL_GPL(bq2415x_reset_chip);

int bq2415x_set_current_limit(struct bq2415x_device *bq, int mA)
{
	int val = (mA/100 + (mA%100 > 0 ? 1 : 0) - 1) / 4;

	if (val < 0)
		val = 0;

	if (val > 3)
		val = 3;

	return bq2415x_i2c_write_mask(bq, BQ2415X_REG_CONTROL, val, BQ2415X_MASK_LIMIT, BQ2415X_SHIFT_LIMIT);
}
EXPORT_SYMBOL_GPL(bq2415x_set_current_limit);

int bq2415x_get_current_limit(struct bq2415x_device *bq)
{
	int ret = bq2415x_i2c_read_mask(bq, BQ2415X_REG_CONTROL, BQ2415X_MASK_LIMIT, BQ2415X_SHIFT_LIMIT);
	if (ret < 0)
		return ret;
	else
		return 100 * (1 + 4*ret);
}
EXPORT_SYMBOL_GPL(bq2415x_get_current_limit);

int bq2415x_set_weak_battery_voltage(struct bq2415x_device *bq, int mV)
{
	int val = mV/100 + (mV%100 > 0 ? 1 : 0) - 34;

	if (val < 0)
		val = 0;

	if (val > 3)
		return -EINVAL;

	return bq2415x_i2c_write_mask(bq, BQ2415X_REG_CONTROL, val, BQ2415X_MASK_VLOWV, BQ2415X_SHIFT_VLOWV);
}
EXPORT_SYMBOL_GPL(bq2415x_set_weak_battery_voltage);

int bq2415x_get_weak_battery_voltage(struct bq2415x_device *bq)
{
	int ret = bq2415x_i2c_read_mask(bq, BQ2415X_REG_CONTROL, BQ2415X_MASK_VLOWV, BQ2415X_SHIFT_VLOWV);
	if (ret < 0)
		return ret;
	else
		return 100 * (34 + ret);
}
EXPORT_SYMBOL_GPL(bq2415x_get_weak_battery_voltage);

int bq2415x_set_battery_regulation_voltage(struct bq2415x_device *bq, int mV)
{
	int val = mV/10 + (mV%10 > 0 ? 1 : 0) - 350;

	if (val < 0)
		val = 0;

	if (val > 94) /* FIXME: Max is 94 or 122 ? */
		return -EINVAL;

	return bq2415x_i2c_write_mask(bq, BQ2415X_REG_VOLTAGE, val, BQ2415X_MASK_VO, BQ2415X_SHIFT_VO);
}
EXPORT_SYMBOL_GPL(bq2415x_set_battery_regulation_voltage);

int bq2415x_get_battery_regulation_voltage(struct bq2415x_device *bq)
{
	int ret = bq2415x_i2c_read_mask(bq, BQ2415X_REG_VOLTAGE, BQ2415X_MASK_VO, BQ2415X_SHIFT_VO);
	if (ret < 0)
		return ret;
	else
		return 10 * (350 + ret);
}
EXPORT_SYMBOL_GPL(bq2415x_get_battery_regulation_voltage);

int bq2415x_set_charge_current_sense_voltage(struct bq2415x_device *bq, int mV)
{
	/* TODO */
	return -1;
}
EXPORT_SYMBOL_GPL(bq2415x_set_charge_current_sense_voltage);

int bq2415x_get_charge_current_sense_voltage(struct bq2415x_device *bq)
{
	/* TODO */
	return -1;
}
EXPORT_SYMBOL_GPL(bq2415x_get_charge_current_sense_voltage);

int bq2415x_set_termination_current_sense_voltage(struct bq2415x_device *bq, int mV)
{
	/* TODO */
	return -1;
}
EXPORT_SYMBOL_GPL(bq2415x_set_termination_current_sense_voltage);

int bq2415x_get_termination_current_sense_voltage(struct bq2415x_device *bq)
{
	/* TODO */
	return -1;
}
EXPORT_SYMBOL_GPL(bq2415x_get_termination_current_sense_voltage);

/* current limit regulator */

static int bq2415x_regulator_set_current_limit(struct regulator_dev *rdev, int min_uA, int max_uA)
{
	struct bq2415x_device *bq = rdev_get_drvdata(rdev);
	return bq2415x_set_current_limit(bq, min_uA / 1000);
}

static int bq2415x_regulator_get_current_limit(struct regulator_dev *rdev)
{
	struct bq2415x_device *bq = rdev_get_drvdata(rdev);
	return bq2415x_get_current_limit(bq) * 1000;
}

struct regulator_ops bq2415x_regulator_current_limit = {
	.set_current_limit = bq2415x_regulator_set_current_limit,
	.get_current_limit = bq2415x_regulator_get_current_limit,
};

/* weak battery voltage regulator */

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)
static int bq2415x_regulator_list_weak_battery_voltage(struct regulator_dev *rdev, unsigned selector)
{
	if (selector > 3)
		return 0;
	else
		return 100000 * (34 + selector);
}
#endif

static int bq2415x_regulator_set_weak_battery_voltage(struct regulator_dev *rdev, int min_uV, int max_uV
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)
		, unsigned *selector
#endif
		)
{
	struct bq2415x_device *bq = rdev_get_drvdata(rdev);
	return bq2415x_set_weak_battery_voltage(bq, min_uV / 1000);
}

static int bq2415x_regulator_get_weak_battery_voltage(struct regulator_dev *rdev)
{
	struct bq2415x_device *bq = rdev_get_drvdata(rdev);
	return bq2415x_get_weak_battery_voltage(bq) * 1000;
}

static struct regulator_ops bq2415x_regulator_weak_battery_voltage = {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)
	.list_voltage = bq2415x_regulator_list_weak_battery_voltage,
#endif
	.set_voltage = bq2415x_regulator_set_weak_battery_voltage,
	.get_voltage = bq2415x_regulator_get_weak_battery_voltage,
};

/* battery regulation voltage regulator */

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)
static int bq2415x_regulator_list_battery_regulation_voltage(struct regulator_dev *rdev, unsigned selector)
{
	if (selector > 94) /* FIXME: Max is 94 or 122 ? */
		return 0;
	else
		return 10000 * (350 + selector);
}
#endif

static int bq2415x_regulator_set_battery_regulation_voltage(struct regulator_dev *rdev, int min_uV, int max_uV
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)
		, unsigned *selector
#endif
		)
{
	struct bq2415x_device *bq = rdev_get_drvdata(rdev);
	return bq2415x_set_battery_regulation_voltage(bq, min_uV / 1000);
}

static int bq2415x_regulator_get_battery_regulation_voltage(struct regulator_dev *rdev)
{
	struct bq2415x_device *bq = rdev_get_drvdata(rdev);
	return bq2415x_get_battery_regulation_voltage(bq) * 1000;
}

static struct regulator_ops bq2415x_regulator_battery_regulation_voltage = {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)
	.list_voltage = bq2415x_regulator_list_battery_regulation_voltage,
#endif
	.set_voltage = bq2415x_regulator_set_battery_regulation_voltage,
	.get_voltage = bq2415x_regulator_get_battery_regulation_voltage,
};

/* charge current sense voltage regulator */

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)
static int bq2415x_regulator_list_charge_current_sense_voltage(struct regulator_dev *rdev, unsigned selector)
{
	/* TODO */
	return 0;
}
#endif

static int bq2415x_regulator_set_charge_current_sense_voltage(struct regulator_dev *rdev, int min_uV, int max_uV
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)
		, unsigned *selector
#endif
		)
{
	struct bq2415x_device *bq = rdev_get_drvdata(rdev);
	return bq2415x_set_charge_current_sense_voltage(bq, min_uV / 1000);
}

static int bq2415x_regulator_get_charge_current_sense_voltage(struct regulator_dev *rdev)
{
	struct bq2415x_device *bq = rdev_get_drvdata(rdev);
	return bq2415x_get_charge_current_sense_voltage(bq) * 1000;
}

static struct regulator_ops bq2415x_regulator_charge_current_sense_voltage = {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)
	.list_voltage = bq2415x_regulator_list_charge_current_sense_voltage,
#endif
	.set_voltage = bq2415x_regulator_set_charge_current_sense_voltage,
	.get_voltage = bq2415x_regulator_get_charge_current_sense_voltage,
};

/* termination current sense voltage regulator */

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)
static int bq2415x_regulator_list_termination_current_sense_voltage(struct regulator_dev *rdev, unsigned selector)
{
	/* TODO */
	return 0;
}
#endif

static int bq2415x_regulator_set_termination_current_sense_voltage(struct regulator_dev *rdev, int min_uV, int max_uV
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)
		, unsigned *selector
#endif
		)
{
	struct bq2415x_device *bq = rdev_get_drvdata(rdev);
	return bq2415x_set_termination_current_sense_voltage(bq, min_uV / 1000);
}

static int bq2415x_regulator_get_termination_current_sense_voltage(struct regulator_dev *rdev)
{
	struct bq2415x_device *bq = rdev_get_drvdata(rdev);
	return bq2415x_get_termination_current_sense_voltage(bq) * 1000;
}

static int bq2415x_regulator_enable_termination_current_sense_voltage(struct regulator_dev *rdev)
{
	struct bq2415x_device *bq = rdev_get_drvdata(rdev);
	return bq2415x_exec_command(bq, BQ2415X_CHARGE_CURRENT_TERMINATION_ENABLE);
}

static int bq2415x_regulator_disable_termination_current_sense_voltage(struct regulator_dev *rdev)
{
	struct bq2415x_device *bq = rdev_get_drvdata(rdev);
	return bq2415x_exec_command(bq, BQ2415X_CHARGE_CURRENT_TERMINATION_DISABLE);
}

static int bq2415x_regulator_is_enabled_termination_current_sense_voltage(struct regulator_dev *rdev)
{
	struct bq2415x_device *bq = rdev_get_drvdata(rdev);
	return bq2415x_exec_command(bq, BQ2415X_CHARGE_CURRENT_TERMINATION_STATUS);
}

static struct regulator_ops bq2415x_regulator_termination_current_sense_voltage = {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)
	.list_voltage = bq2415x_regulator_list_termination_current_sense_voltage,
#endif
	.set_voltage = bq2415x_regulator_set_termination_current_sense_voltage,
	.get_voltage = bq2415x_regulator_get_termination_current_sense_voltage,
	.enable = bq2415x_regulator_enable_termination_current_sense_voltage,
	.disable = bq2415x_regulator_disable_termination_current_sense_voltage,
	.is_enabled = bq2415x_regulator_is_enabled_termination_current_sense_voltage,
};

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)
#define regulator_register2(desc, dev, init, data) regulator_register(desc, dev, init, data)
#else
#define regulator_register2(desc, dev, init, data) regulator_register(desc, dev, data)
#endif

#define bq2415x_regulator_register(bq, rname, rid, rtype) \
	do { \
		bq->rname.desc.name = __stringify(rname); \
		bq->rname.desc.id = rid; \
		bq->rname.desc.type = rtype; \
		bq->rname.desc.owner = THIS_MODULE; \
		bq->rname.desc.ops = &bq2415x_regulator_##rname; \
		bq->rname.dev = regulator_register2(&bq->rname.desc, bq->dev, NULL, bq); \
		if (IS_ERR(bq->rname.dev)) \
			return PTR_ERR(bq->rname.dev); \
	} while (0)

#define bq2415x_regulator_unregister(bq, rname) regulator_unregister(bq->rname.dev)

static int bq2415x_regulator_init(struct bq2415x_device *bq)
{
/*	bq2415x_regulator_register(bq, current_limit, 0, REGULATOR_CURRENT);
	bq2415x_regulator_register(bq, weak_battery_voltage, 1, REGULATOR_VOLTAGE);
	bq2415x_regulator_register(bq, battery_regulation_voltage, 2, REGULATOR_VOLTAGE);
	bq2415x_regulator_register(bq, charge_current_sense_voltage, 3, REGULATOR_VOLTAGE);
	bq2415x_regulator_register(bq, ermination_current_sense_voltage, 4, REGULATOR_VOLTAGE);*/
	return 0;
}

static void bq2415x_regulator_exit(struct bq2415x_device *bq)
{
/*	bq2415x_regulator_unregister(bq, current_limit);
	bq2415x_regulator_unregister(bq, weak_battery_voltage);
	bq2415x_regulator_unregister(bq, battery_regulation_voltage);
	bq2415x_regulator_unregister(bq, charge_current_sense_voltage);
	bq2415x_regulator_unregister(bq, termination_current_sense_voltage);*/
}

#undef bq2415x_regulator_register
#undef bq2415x_regulator_unregister

/* power supply */

static enum power_supply_property bq2415x_power_supply_props[] = {
	/* TODO */
	POWER_SUPPLY_PROP_STATUS,
};

static void bq2415x_power_supply_work(struct work_struct *work)
{
	struct bq2415x_device *bq = container_of(work, struct bq2415x_device, work.work);
	int ret;

	dev_info(bq->dev, "bq2415x_power_supply_work\n");

	if (bq->watchdog != 1)
		return;

	ret = bq2415x_exec_command(bq, BQ2415X_WATCHDOG_RESET);
	if (ret < 0)
		bq->watchdog = ret;

	if (bq->watchdog != 1)
		return;

	/* TODO charge */

	schedule_delayed_work(&bq->work, BQ2415X_WATCHDOG_TIMEOUT * HZ);
}

static void bq2415x_power_supply_update_watchdog(struct bq2415x_device *bq, int auto_enable)
{
	if (auto_enable && bq->watchdog == 1)
		return;

	if (!auto_enable && bq->watchdog == 0)
		return;

	if (auto_enable) {
		bq->watchdog = 1;
		schedule_delayed_work(&bq->work, BQ2415X_WATCHDOG_TIMEOUT * HZ);
		bq2415x_exec_command(bq, BQ2415X_WATCHDOG_RESET);
	} else {
		bq->watchdog = 0;
		cancel_delayed_work_sync(&bq->work);
	}
}

static int bq2415x_power_supply_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val)
{
	struct bq2415x_device *bq = container_of(psy, struct bq2415x_device, charger);
	/* TODO */
	return -EINVAL;
}

static int bq2415x_power_supply_init(struct bq2415x_device *bq)
{
	int ret;

	bq->charger.name = bq->name;
	bq->charger.type = POWER_SUPPLY_TYPE_USB;
	bq->charger.properties = bq2415x_power_supply_props;
	bq->charger.num_properties = ARRAY_SIZE(bq2415x_power_supply_props);
	bq->charger.get_property = bq2415x_power_supply_get_property;

	ret = power_supply_register(bq->dev, &bq->charger);
	if (ret)
		return ret;

	INIT_DELAYED_WORK(&bq->work, bq2415x_power_supply_work);

	bq2415x_power_supply_update_watchdog(bq, 1);
	return ret;
}

static void bq2415x_power_supply_exit(struct bq2415x_device *bq)
{
	bq->watchdog = 0;
	cancel_delayed_work_sync(&bq->work);
	power_supply_unregister(&bq->charger);
}

/* sysfs files */

static ssize_t bq2415x_sysfs_show_status(struct device *dev, struct device_attribute *attr, char *buf)
{
	struct power_supply *psy = dev_get_drvdata(dev);
	struct bq2415x_device *bq = container_of(psy, struct bq2415x_device, charger);
	enum bq2415x_command command;
	int ret;

	if (strcmp(attr->attr.name, "otg_status") == 0)
		command = BQ2415X_OTG_STATUS;
	else if (strcmp(attr->attr.name, "charge_status") == 0)
		command = BQ2415X_CHARGE_STATUS;
	else if (strcmp(attr->attr.name, "boost_status") == 0)
		command = BQ2415X_BOOST_STATUS;
	else if (strcmp(attr->attr.name, "fault_status") == 0)
		command = BQ2415X_FAULT_STATUS;
	else
		return -EINVAL;

	ret = bq2415x_exec_command(bq, command);
	if (ret < 0)
		return ret;
	else
		return sprintf(buf, "%d\n", ret);
}

static ssize_t bq2415x_sysfs_set_watchdog(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
	struct power_supply *psy = dev_get_drvdata(dev);
	struct bq2415x_device *bq = container_of(psy, struct bq2415x_device, charger);
	int ret = 0;

	if (strncmp(buf, "auto", 4) == 0)
		bq2415x_power_supply_update_watchdog(bq, 1);
	else if (strncmp(buf, "off", 3) == 0)
		bq2415x_power_supply_update_watchdog(bq, 0);
	else
		ret = bq2415x_exec_command(bq, BQ2415X_WATCHDOG_RESET);

	if (ret < 0)
		return ret;
	else
		return count;
}

static ssize_t bq2415x_sysfs_show_watchdog(struct device *dev, struct device_attribute *attr, char *buf)
{
	struct power_supply *psy = dev_get_drvdata(dev);
	struct bq2415x_device *bq = container_of(psy, struct bq2415x_device, charger);

	switch (bq->watchdog) {
		case 1: return sprintf(buf, "auto\n");
		case 0: return sprintf(buf, "off\n");
		default: return sprintf(buf, "error %d\n", bq->watchdog);
	}
}

/* TODO: Add other sysfs entries */

static DEVICE_ATTR(watchdog, S_IWUSR | S_IRUGO, bq2415x_sysfs_show_watchdog, bq2415x_sysfs_set_watchdog);
static DEVICE_ATTR(otg_status, S_IRUGO, bq2415x_sysfs_show_status, NULL);
static DEVICE_ATTR(charge_status, S_IRUGO, bq2415x_sysfs_show_status, NULL);
static DEVICE_ATTR(boost_status, S_IRUGO, bq2415x_sysfs_show_status, NULL);
static DEVICE_ATTR(fault_status, S_IRUGO, bq2415x_sysfs_show_status, NULL);

static struct attribute *bq2415x_sysfs_attributes[] = {
	&dev_attr_watchdog.attr,
	&dev_attr_otg_status.attr,
	&dev_attr_charge_status.attr,
	&dev_attr_boost_status.attr,
	&dev_attr_fault_status.attr,
	NULL,
};

static const struct attribute_group bq2415x_sysfs_attr_group = {
	.attrs = bq2415x_sysfs_attributes,
};

static int bq2415x_sysfs_init(struct bq2415x_device *bq)
{
	return sysfs_create_group(&bq->charger.dev->kobj, &bq2415x_sysfs_attr_group);
}

static void bq2415x_sysfs_exit(struct bq2415x_device *bq)
{
	sysfs_remove_group(&bq->charger.dev->kobj, &bq2415x_sysfs_attr_group);
}

/* charger led device on STAT_PIN */

static void bq2415x_led_set(struct led_classdev *led_cdev, enum led_brightness value)
{
	struct i2c_client *client = to_i2c_client(led_cdev->dev->parent);
	struct bq2415x_device *bq = i2c_get_clientdata(client);

	if (value)
		bq2415x_exec_command(bq, BQ2415X_STAT_PIN_ENABLE);
	else
		bq2415x_exec_command(bq, BQ2415X_STAT_PIN_DISABLE);
}

static enum led_brightness bq2415x_led_get(struct led_classdev *led_cdev)
{
	struct i2c_client *client = to_i2c_client(led_cdev->dev->parent);
	struct bq2415x_device *bq = i2c_get_clientdata(client);

	if (bq2415x_exec_command(bq, BQ2415X_STAT_PIN_STATUS) > 0)
		return LED_FULL;
	else
		return LED_OFF;
}

static int bq2415x_led_init(struct bq2415x_device *bq)
{
	bq->led.name = bq->name;
	bq->led.brightness_set = bq2415x_led_set;
	bq->led.brightness_get = bq2415x_led_get;
	return led_classdev_register(bq->dev, &bq->led);
}

static void bq2415x_led_exit(struct bq2415x_device *bq)
{
	led_classdev_unregister(&bq->led);
}

/* bq2415x register */

static int bq2415x_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	int ret;
	int num;
	char *name;
	struct bq2415x_device *bq;

	if (!client->dev.platform_data) {
		dev_err(&client->dev, "platform data not set\n");
		return -ENODEV;
	}

	/* Get new ID for the new device */
	ret = idr_pre_get(&bq2415x_id, GFP_KERNEL);
	if (ret == 0)
		return -ENOMEM;

	mutex_lock(&bq2415x_mutex);
	ret = idr_get_new(&bq2415x_id, client, &num);
	mutex_unlock(&bq2415x_mutex);

	if (ret < 0)
		return ret;

	name = kasprintf(GFP_KERNEL, "%s-%d", id->name, num);
	if (!name) {
		dev_err(&client->dev, "failed to allocate device name\n");
		ret = -ENOMEM;
		goto error_1;
	}

	bq = kzalloc(sizeof(*bq), GFP_KERNEL);
	if (!bq) {
		dev_err(&client->dev, "failed to allocate device data\n");
		ret = -ENOMEM;
		goto error_2;
	}

	i2c_set_clientdata(client, bq);

	bq->id = num;
	bq->dev = &client->dev;
	bq->chip = id->driver_data;
	bq->name = name;
	bq->platform_data = client->dev.platform_data;
	bq->watchdog = 0;

	bq2415x_reset_chip(bq);

	ret = bq2415x_regulator_init(bq);
	if (ret) {
		dev_err(bq->dev, "failed to register regulators: %d\n", ret);
		goto error_3;
	}

	ret = bq2415x_led_init(bq);
	if (ret) {
		dev_err(bq->dev, "failed to register led device: %d\n", ret);
		goto error_4;
	}

	ret = bq2415x_power_supply_init(bq);
	if (ret) {
		dev_err(bq->dev, "failed to register power supply: %d\n", ret);
		goto error_5;
	}

	ret = bq2415x_sysfs_init(bq);
	if (ret) {
		dev_err(bq->dev, "failed to create sysfs entries: %d\n", ret);
		goto error_6;
	}

	bq2415x_set_defaults(bq);

	dev_info(bq->dev, "driver registred\n");
	return 0;

/*error_7:*/
	bq2415x_sysfs_exit(bq);
error_6:
	bq2415x_power_supply_exit(bq);
error_5:
	bq2415x_led_exit(bq);
error_4:
	bq2415x_regulator_exit(bq);
error_3:
	kfree(bq);
error_2:
	kfree(name);
error_1:
	mutex_lock(&bq2415x_mutex);
	idr_remove(&bq2415x_id, num);
	mutex_unlock(&bq2415x_mutex);

	return ret;
}

/* bq2415x unregister */

static int bq2415x_remove(struct i2c_client *client)
{
	struct bq2415x_device *bq = i2c_get_clientdata(client);

	bq2415x_sysfs_exit(bq);
	bq2415x_power_supply_exit(bq);
	bq2415x_led_exit(bq);
	bq2415x_regulator_exit(bq);

	bq2415x_reset_chip(bq);

	mutex_lock(&bq2415x_mutex);
	idr_remove(&bq2415x_id, bq->id);
	mutex_unlock(&bq2415x_mutex);

	dev_info(bq->dev, "driver unregistred\n");

	kfree(bq->name);
	kfree(bq);

	return 0;
}

static const struct i2c_device_id bq2415x_i2c_id_table[] = {
	{ "bq2415x", BQUNKNOWN },
	{ "bq24150", BQ24150 },
	{ "bq24150a", BQ24150A },
	{ "bq24151", BQ24151 },
	{ "bq24151a", BQ24151A },
	{ "bq24152", BQ24152 },
	{ "bq24153", BQ24153 },
	{ "bq24153a", BQ24153A },
	{ "bq24155", BQ24155 },
	{ "bq24156", BQ24156 },
	{ "bq24156a", BQ24156A },
	{ "bq24158", BQ24158 },
	{},
};
MODULE_DEVICE_TABLE(i2c, bq2415x_i2c_id_table);

static struct i2c_driver bq2415x_driver = {
	.driver = {
		.name = "bq2415x-charger",
	},
	.probe = bq2415x_probe,
	.remove = bq2415x_remove,
	.id_table = bq2415x_i2c_id_table,
};

/* BEGIN: Temporary RX51 hack TODO: move to board-rx51.c */
static struct bq2415x_platform_data rx51_platform_data = {
	.current_limit = 100, /* mA */
	.weak_battery_voltage = 3400, /* mV */
	.battery_regulation_voltage = 4200, /* mV */
	.charge_current_sense_voltage = -1, /* TODO */
	.termination_current_sense_voltage = -1, /* TODO */
};
static struct i2c_board_info rx51_board_info = {
	I2C_BOARD_INFO("bq24150", 0x6b),
	.platform_data = &rx51_platform_data,
};
static struct i2c_client *client;
/* END */

static int __init bq2415x_init(void)
{
	/* BEGIN: Temporary RX51 hack TODO: move to board-rx51.c */
	client = i2c_new_device(i2c_get_adapter(2), &rx51_board_info);
	/* END */

	return i2c_add_driver(&bq2415x_driver);
}
module_init(bq2415x_init);

static void __exit bq2415x_exit(void)
{
	/* BEGIN: Temporary RX51 hack TODO: move to board-rx51.c */
	i2c_unregister_device(client);
	/* END */

	i2c_del_driver(&bq2415x_driver);
}
module_exit(bq2415x_exit);

MODULE_AUTHOR("Pali Rohár <pali.rohar@gmail.com>");
MODULE_DESCRIPTION("bq2415x charger driver");
MODULE_LICENSE("GPL");

[-- Attachment #1.3: bq2415x_charger.h --]
[-- Type: text/x-chdr, Size: 3110 bytes --]

/*
    bq2415x_charger.h - bq2415x charger driver
    Copyright (C) 2011  Pali Rohár <pali.rohar@gmail.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.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#ifndef BQ2415X_CHARGER_H
#define BQ2415X_CHARGER_H

struct bq2415x_device;

struct bq2415x_platform_data {
	int current_limit;
	int weak_battery_voltage;
	int battery_regulation_voltage;
	int charge_current_sense_voltage;
	int termination_current_sense_voltage;
};

enum bq2415x_command {
	BQ2415X_WATCHDOG_RESET,
	BQ2415X_OTG_STATUS,
	BQ2415X_STAT_PIN_STATUS,
	BQ2415X_STAT_PIN_ENABLE,
	BQ2415X_STAT_PIN_DISABLE,
	BQ2415X_CHARGE_STATUS,
	BQ2415X_BOOST_STATUS,
	BQ2415X_FAULT_STATUS,

	BQ2415X_CHARGE_CURRENT_TERMINATION_STATUS,
	BQ2415X_CHARGE_CURRENT_TERMINATION_ENABLE,
	BQ2415X_CHARGE_CURRENT_TERMINATION_DISABLE,
	BQ2415X_CHARGER_STATUS,
	BQ2415X_CHARGER_ENABLE,
	BQ2415X_CHARGER_DISABLE,
	BQ2415X_HIGH_IMPEDANCE_STATUS,
	BQ2415X_HIGH_IMPEDANCE_ENABLE,
	BQ2415X_HIGH_IMPEDANCE_DISABLE,
	BQ2415X_MODE,
	BQ2415X_SET_BOOST_MODE,
	BQ2415X_SET_CHARGER_MODE,

	BQ2415X_OTG_HIGH_STATUS,
	BQ2415X_OTG_HIGH_ACTIVATE,
	BQ2415X_OTG_LOW_ACTIVATE,
	BQ2415X_OTG_PIN_STATUS,
	BQ2415X_OTG_PIN_ENABLE,
	BQ2415X_OTG_PIN_DISABLE,

	BQ2415X_VENDER_CODE,
	BQ2415X_PART_NUMBER,
	BQ2415X_REVISION,
};

enum bq2415x_chip {
	BQUNKNOWN,
	BQ24150,
	BQ24150A,
	BQ24151,
	BQ24151A,
	BQ24152,
	BQ24153,
	BQ24153A,
	BQ24155,
	BQ24156,
	BQ24156A,
	BQ24158,
};

enum bq2415x_chip bq2415x_detect_chip(struct bq2415x_device *bq);
int bq2415x_exec_command(struct bq2415x_device *bq, enum bq2415x_command command);
int bq2415x_set_defaults(struct bq2415x_device *bq);
int bq2415x_set_current_limit(struct bq2415x_device *bq, int uA);
int bq2415x_get_current_limit(struct bq2415x_device *bq);
int bq2415x_set_weak_battery_voltage(struct bq2415x_device *bq, int mV);
int bq2415x_get_weak_battery_voltage(struct bq2415x_device *bq);
int bq2415x_set_battery_regulation_voltage(struct bq2415x_device *bq, int mV);
int bq2415x_get_battery_regulation_voltage(struct bq2415x_device *bq);
int bq2415x_set_charge_current_sense_voltage(struct bq2415x_device *bq, int mV);
int bq2415x_get_charge_current_sense_voltage(struct bq2415x_device *bq);
int bq2415x_set_termination_current_sense_voltage(struct bq2415x_device *bq, int mV);
int bq2415x_get_termination_current_sense_voltage(struct bq2415x_device *bq);

#endif

[-- Attachment #2: This is a digitally signed message part. --]
[-- Type: application/pgp-signature, Size: 198 bytes --]

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: RFC: bq2415x_charger driver
  2011-12-07 21:03 ` RFC: bq2415x_charger driver Pali Rohár
@ 2011-12-07 21:25   ` Vladimir Zapolskiy
  2011-12-07 21:40     ` Pali Rohár
  2012-01-27  2:40   ` RFC 2: " Pali Rohár
  1 sibling, 1 reply; 28+ messages in thread
From: Vladimir Zapolskiy @ 2011-12-07 21:25 UTC (permalink / raw)
  To: Pali Rohár
  Cc: linux-main, linux-omap, Samuel Ortiz, Aliaksei Katovich,
	Felipe Contreras, Anton Vorontsov, Joerg Reisenweber

Hi Pali,

On 07.12.2011 23:03, Pali Rohár wrote:
> Hello,
>
> I'm sending preview of my implementation of bq2415x charger driver. Now all
> code is in one kernel module bq2415x_charger which has more interfaces
> (power_supply, sysfs, regulator). It is still unfinished, missing more bq2415x
> configuration like sysfs entires for boost, voltages or platform hooks (e.g.
> rx51 isp1704). This driver will have similar sysfs interface as in Joerg
> Reisenweber spec http://maemo.cloud-7.de/bq24150-sysnode.spec.txt . Driver
> should be replacemnt for Nokia N900 proprietary charging program BME. I was
> inspirated by driver bq27x00_battery and other bq2415x implementaton by
> Aliaksei Katovich and Felipe Contreras.
>
> Please comment implementation and write what should be added or modified in
> driver (it is still not complete).
>

There are well defined policies regarding patch review in the Linux 
kernel mailing lists. I encourage you to take a look at 
http://felipec.wordpress.com/2009/10/25/git-send-email-tricks/

-- 
With best wishes,
Vladimir

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: RFC: bq2415x_charger driver
  2011-12-07 21:25   ` Vladimir Zapolskiy
@ 2011-12-07 21:40     ` Pali Rohár
  0 siblings, 0 replies; 28+ messages in thread
From: Pali Rohár @ 2011-12-07 21:40 UTC (permalink / raw)
  To: Vladimir Zapolskiy
  Cc: linux-main, linux-omap, Samuel Ortiz, Aliaksei Katovich,
	Felipe Contreras, Anton Vorontsov, Joerg Reisenweber

[-- Attachment #1: Type: text/plain, Size: 39248 bytes --]

On Wednesday 07 December 2011 23:25:12 Vladimir Zapolskiy wrote:
> Hi Pali,
> 
> On 07.12.2011 23:03, Pali Rohár wrote:
> > Hello,
> > 
> > I'm sending preview of my implementation of bq2415x charger driver. Now
> > all code is in one kernel module bq2415x_charger which has more
> > interfaces (power_supply, sysfs, regulator). It is still unfinished,
> > missing more bq2415x configuration like sysfs entires for boost,
> > voltages or platform hooks (e.g. rx51 isp1704). This driver will have
> > similar sysfs interface as in Joerg Reisenweber spec
> > http://maemo.cloud-7.de/bq24150-sysnode.spec.txt . Driver should be
> > replacemnt for Nokia N900 proprietary charging program BME. I was
> > inspirated by driver bq27x00_battery and other bq2415x implementaton by
> > Aliaksei Katovich and Felipe Contreras.
> > 
> > Please comment implementation and write what should be added or modified
> > in driver (it is still not complete).
> 
> There are well defined policies regarding patch review in the Linux
> kernel mailing lists. I encourage you to take a look at
> http://felipec.wordpress.com/2009/10/25/git-send-email-tricks/

Ok, I do not know where to put this driver, so I only send files. Here is diff

diff --git a/drivers/power/bq2415x_charger.c b/drivers/power/bq2415x_charger.c
new file mode 100644
index 0000000..eb9957a
--- /dev/null
+++ b/drivers/power/bq2415x_charger.c
@@ -0,0 +1,1115 @@
+/*
+    bq2415x_charger.c - bq2415x charger driver
+    Copyright (C) 2011  Pali Rohár <pali.rohar@gmail.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.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+/*
+  Datasheets:
+  http://www.ti.com/product/bq24150
+  http://www.ti.com/product/bq24150a
+  http://www.ti.com/product/bq24152
+  http://www.ti.com/product/bq24153
+  http://www.ti.com/product/bq24153a
+  http://www.ti.com/product/bq24155
+*/
+
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/param.h>
+#include <linux/err.h>
+#include <linux/workqueue.h>
+#include <linux/sysfs.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/driver.h>
+#include <linux/power_supply.h>
+#include <linux/idr.h>
+#include <linux/leds.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+
+#include "bq2415x_charger.h"
+
+#define BQ2415X_WATCHDOG_TIMEOUT	10
+
+#define BQ2415X_REG_STATUS		0x00
+#define BQ2415X_REG_CONTROL		0x01
+#define BQ2415X_REG_VOLTAGE		0x02
+#define BQ2415X_REG_VENDER		0x03
+#define BQ2415X_REG_CURRENT		0x04
+
+/* reset state for all registers */
+#define BQ2415X_RESET_STATUS		BIT(6)
+#define BQ2415X_RESET_CONTROL		(BIT(4)|BIT(5))
+#define BQ2415X_RESET_VOLTAGE		(BIT(1)|BIT(3))
+#define BQ2415X_RESET_CURRENT		(BIT(0)|BIT(3)|BIT(7))
+
+/* status register */
+#define BQ2415X_BIT_TMR_RST		7
+#define BQ2415X_BIT_OTG			7
+#define BQ2415X_BIT_EN_STAT		6
+#define BQ2415X_MASK_STAT		(BIT(4)|BIT(5))
+#define BQ2415X_SHIFT_STAT		4
+#define BQ2415X_BIT_BOOST		3
+#define BQ2415X_MASK_FAULT		(BIT(0)|BIT(1)|BIT(2))
+#define BQ2415X_SHIFT_FAULT		0
+
+/* control register */
+#define BQ2415X_MASK_LIMIT		(BIT(6)|BIT(7))
+#define BQ2415X_SHIFT_LIMIT		6
+#define BQ2415X_MASK_VLOWV		(BIT(4)|BIT(5))
+#define BQ2415X_SHIFT_VLOWV		4
+#define BQ2415X_BIT_TE			3
+#define BQ2415X_BIT_CE			2
+#define BQ2415X_BIT_HZ_MODE		1
+#define BQ2415X_BIT_OPA_MODE		0
+
+/* voltage register */
+#define BQ2415X_MASK_VO			(BIT(2)|BIT(3)|BIT(4)|BIT(5)|BIT(6)|BIT(7))
+#define BQ2415X_SHIFT_VO		2
+#define BQ2415X_BIT_OTG_PL		1
+#define BQ2415X_BIT_OTG_EN		0
+
+/* vender register */
+#define BQ2415X_MASK_VENDER		(BIT(5)|BIT(6)|BIT(7))
+#define BQ2415X_SHIFT_VENDER		5
+#define BQ2415X_MASK_PN			(BIT(3)|BIT(4))
+#define BQ2415X_SHIFT_PN		4
+#define BQ2415X_MASK_REVISION		(BIT(0)|BIT(1)|BIT(2))
+#define BQ2415X_SHIFT_REVISION		0
+
+/* current register */
+/* RESET				BIT(7) */
+#define BQ2415X_MASK_VI_CHRG		(BIT(4)|BIT(5)|BIT(6))
+#define BQ2415X_SHIFT_VI_CHRG		4
+/* N/A					BIT(3) */
+#define BQ2415X_MASK_VI_TERM		(BIT(0)|BIT(1)|BIT(2))
+#define BQ2415X_SHIFT_VI_TERM		0
+
+struct bq2415x_regulator {
+	struct regulator_ops ops;
+	struct regulator_desc desc;
+	struct regulator_dev *dev;
+};
+
+struct bq2415x_device {
+	struct device *dev;
+	struct bq2415x_platform_data *platform_data;
+	struct bq2415x_regulator current_limit;
+	struct bq2415x_regulator weak_battery_voltage;
+	struct bq2415x_regulator battery_regulation_voltage;
+	struct bq2415x_regulator charge_current_sense_voltage;
+	struct bq2415x_regulator termination_current_sense_voltage;
+	struct led_classdev led;
+	struct power_supply charger;
+	struct delayed_work work;
+	int watchdog;
+	enum bq2415x_chip chip;
+	char *name;
+	int id;
+};
+
+static DEFINE_IDR(bq2415x_id);
+static DEFINE_MUTEX(bq2415x_mutex);
+
+/* i2c read functions */
+
+static int bq2415x_i2c_read(struct bq2415x_device *bq, u8 reg)
+{
+	struct i2c_client *client = to_i2c_client(bq->dev);
+	struct i2c_msg msg[2];
+	u8 val;
+	int ret;
+
+	dev_info(bq->dev, "bq2415x_i2c_read reg=%#x\n", reg);
+
+	if (!client->adapter)
+		return -ENODEV;
+
+	msg[0].addr = client->addr;
+	msg[0].flags = 0;
+	msg[0].buf = &reg;
+	msg[0].len = sizeof(reg);
+	msg[1].addr = client->addr;
+	msg[1].flags = I2C_M_RD;
+	msg[1].buf = &val;
+	msg[1].len = sizeof(val);
+
+	mutex_lock(&bq2415x_mutex);
+	ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
+	mutex_unlock(&bq2415x_mutex);
+
+	if (ret < 0)
+		return ret;
+
+	return val;
+}
+
+static int bq2415x_i2c_read_mask(struct bq2415x_device *bq, u8 reg, u8 mask, u8 shift)
+{
+	int ret;
+
+	dev_info(bq->dev, "bq2415x_i2c_read_mask reg=%#x mask=%#x shift=%#x\n", reg, mask, shift);
+
+	if (shift > 8)
+		return -EINVAL;
+
+	ret = bq2415x_i2c_read(bq, reg);
+	if (ret < 0)
+		return ret;
+	else
+		return (ret & mask) >> shift;
+}
+
+static int bq2415x_i2c_read_bit(struct bq2415x_device *bq, u8 reg, u8 bit)
+{
+	if (bit > 8)
+		return -EINVAL;
+	else
+		return bq2415x_i2c_read_mask(bq, reg, BIT(bit), bit);
+}
+
+/* i2c write functions */
+
+static int bq2415x_i2c_write(struct bq2415x_device *bq, u8 reg, u8 val)
+{
+	struct i2c_client *client = to_i2c_client(bq->dev);
+	struct i2c_msg msg[1];
+	u8 data[2];
+	int ret;
+
+	dev_info(bq->dev, "bq2415x_i2c_write reg=%#x val=%#x\n", reg, val);
+
+	data[0] = reg;
+	data[1] = val;
+
+	msg[0].addr = client->addr;
+	msg[0].flags = 0;
+	msg[0].buf = data;
+	msg[0].len = ARRAY_SIZE(data);
+
+	mutex_lock(&bq2415x_mutex);
+	ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
+	mutex_unlock(&bq2415x_mutex);
+
+	/* i2c_transfer returns number of messages transferred */
+	if (ret < 0)
+		return ret;
+	else if (ret != 1)
+		return -EIO;
+
+	return 0;
+}
+
+static int bq2415x_i2c_write_mask(struct bq2415x_device *bq, u8 reg, u8 val, u8 mask, u8 shift)
+{
+	int ret;
+
+	dev_info(bq->dev, "bq2415x_i2c_write_mask reg=%#x val=%#x mask=%#x shift=%#x\n", reg, val, mask, shift);
+
+	if (shift > 8)
+		return -EINVAL;
+
+	ret = bq2415x_i2c_read(bq, reg);
+	if (ret < 0)
+		return ret;
+
+	ret &= ~mask;
+	ret |= val << shift;
+
+	return bq2415x_i2c_write(bq, reg, ret);
+}
+
+static int bq2415x_i2c_write_bit(struct bq2415x_device *bq, u8 reg, bool val, u8 bit)
+{
+	if (bit > 8)
+		return -EINVAL;
+	else
+		return bq2415x_i2c_write_mask(bq, reg, val, BIT(bit), bit);
+}
+
+/* global detect chip */
+
+enum bq2415x_chip bq2415x_detect_chip(struct bq2415x_device *bq)
+{
+	struct i2c_client *client = to_i2c_client(bq->dev);
+	int ret = bq2415x_exec_command(bq, BQ2415X_PART_NUMBER);
+	if (ret < 0)
+		return ret;
+	if (client->addr == 0x6b) {
+		switch (ret) {
+			case 0:
+				if (bq->chip == BQ24151A)
+					return bq->chip;
+				else
+					return BQ24151;
+			case 1:
+				if (bq->chip == BQ24150A || bq->chip == BQ24152 || bq->chip == BQ24155)
+					return bq->chip;
+				else
+					return BQ24150;
+			case 2:
+				if (bq->chip == BQ24153A)
+					return bq->chip;
+				else
+					return BQ24153;
+			default:
+				return BQUNKNOWN;
+		}
+	} else if (client->addr == 0x6a) {
+		switch (ret) {
+			case 0:
+				if (bq->chip == BQ24156A)
+					return bq->chip;
+				else
+					return BQ24156;
+			case 2:
+				return BQ24158;
+			default:
+				return BQUNKNOWN;
+		}
+	}
+	return BQUNKNOWN;
+}
+EXPORT_SYMBOL_GPL(bq2415x_detect_chip);
+
+/* global exec command function */
+
+int bq2415x_exec_command(struct bq2415x_device *bq, enum bq2415x_command command)
+{
+	switch(command)
+	{
+		case BQ2415X_WATCHDOG_RESET:
+			return bq2415x_i2c_write_bit(bq, BQ2415X_REG_STATUS, 1, BQ2415X_BIT_TMR_RST);
+		case BQ2415X_OTG_STATUS:
+			return bq2415x_i2c_read_bit(bq, BQ2415X_REG_STATUS, BQ2415X_BIT_OTG);
+		case BQ2415X_STAT_PIN_STATUS:
+			return bq2415x_i2c_read_bit(bq, BQ2415X_REG_STATUS, BQ2415X_BIT_EN_STAT);
+		case BQ2415X_STAT_PIN_ENABLE:
+			return bq2415x_i2c_write_bit(bq, BQ2415X_REG_STATUS, 1, BQ2415X_BIT_EN_STAT);
+		case BQ2415X_STAT_PIN_DISABLE:
+			return bq2415x_i2c_write_bit(bq, BQ2415X_REG_STATUS, 0, BQ2415X_BIT_EN_STAT);
+		case BQ2415X_CHARGE_STATUS:
+			return bq2415x_i2c_read_mask(bq, BQ2415X_REG_STATUS, BQ2415X_MASK_STAT, BQ2415X_SHIFT_STAT);
+		case BQ2415X_BOOST_STATUS:
+			return bq2415x_i2c_read_bit(bq, BQ2415X_REG_STATUS, BQ2415X_BIT_BOOST);
+		case BQ2415X_FAULT_STATUS:
+			return bq2415x_i2c_read_mask(bq, BQ2415X_REG_STATUS, BQ2415X_MASK_FAULT, BQ2415X_SHIFT_FAULT);
+
+		case BQ2415X_CHARGE_CURRENT_TERMINATION_STATUS:
+			return bq2415x_i2c_read_bit(bq, BQ2415X_REG_CONTROL, BQ2415X_BIT_TE);
+		case BQ2415X_CHARGE_CURRENT_TERMINATION_ENABLE:
+			return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, 1, BQ2415X_BIT_TE);
+		case BQ2415X_CHARGE_CURRENT_TERMINATION_DISABLE:
+			return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, 0, BQ2415X_BIT_TE);
+		case BQ2415X_CHARGER_STATUS:
+			return bq2415x_i2c_read_bit(bq, BQ2415X_REG_CONTROL, BQ2415X_BIT_CE);
+		case BQ2415X_CHARGER_ENABLE:
+			return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, 1, BQ2415X_BIT_CE);
+		case BQ2415X_CHARGER_DISABLE:
+			return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, 0, BQ2415X_BIT_CE);
+		case BQ2415X_HIGH_IMPEDANCE_STATUS:
+			return bq2415x_i2c_read_bit(bq, BQ2415X_REG_CONTROL, BQ2415X_BIT_HZ_MODE);
+		case BQ2415X_HIGH_IMPEDANCE_ENABLE:
+			return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, 1, BQ2415X_BIT_HZ_MODE);
+		case BQ2415X_HIGH_IMPEDANCE_DISABLE:
+			return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, 0, BQ2415X_BIT_HZ_MODE);
+		case BQ2415X_MODE:
+			return bq2415x_i2c_read_bit(bq, BQ2415X_REG_CONTROL, BQ2415X_BIT_OPA_MODE);
+		case BQ2415X_SET_BOOST_MODE:
+			return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, 1, BQ2415X_BIT_OPA_MODE);
+		case BQ2415X_SET_CHARGER_MODE:
+			return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, 0, BQ2415X_BIT_OPA_MODE);
+
+		case BQ2415X_OTG_HIGH_STATUS:
+			return bq2415x_i2c_read_bit(bq, BQ2415X_REG_VOLTAGE, BQ2415X_BIT_OTG_PL);
+		case BQ2415X_OTG_HIGH_ACTIVATE:
+			return bq2415x_i2c_write_bit(bq, BQ2415X_REG_VOLTAGE, 1, BQ2415X_BIT_OTG_PL);
+		case BQ2415X_OTG_LOW_ACTIVATE:
+			return bq2415x_i2c_write_bit(bq, BQ2415X_REG_VOLTAGE, 0, BQ2415X_BIT_OTG_PL);
+		case BQ2415X_OTG_PIN_STATUS:
+			return bq2415x_i2c_read_bit(bq, BQ2415X_REG_VOLTAGE, BQ2415X_BIT_OTG_EN);
+		case BQ2415X_OTG_PIN_ENABLE:
+			return bq2415x_i2c_write_bit(bq, BQ2415X_REG_VOLTAGE, 1, BQ2415X_BIT_OTG_EN);
+		case BQ2415X_OTG_PIN_DISABLE:
+			return bq2415x_i2c_write_bit(bq, BQ2415X_REG_VOLTAGE, 0, BQ2415X_BIT_OTG_EN);
+
+		case BQ2415X_VENDER_CODE:
+			return bq2415x_i2c_read_mask(bq, BQ2415X_REG_VENDER, BQ2415X_MASK_VENDER, BQ2415X_SHIFT_VENDER);
+		case BQ2415X_PART_NUMBER:
+			return bq2415x_i2c_read_mask(bq, BQ2415X_REG_VENDER, BQ2415X_MASK_PN, BQ2415X_SHIFT_PN);
+		case BQ2415X_REVISION:
+			return bq2415x_i2c_read_mask(bq, BQ2415X_REG_VENDER, BQ2415X_MASK_REVISION, BQ2415X_SHIFT_REVISION);
+
+		default:
+			return -EINVAL;
+	}
+}
+EXPORT_SYMBOL_GPL(bq2415x_exec_command);
+
+/* global other functions */
+
+#define bq2415x_set_default_value(bq, value) \
+	do { \
+		int ret = 0; \
+		if (bq->platform_data->value != -1) \
+			ret = bq2415x_set_##value(bq, bq->platform_data->value); \
+		if (ret < 0) \
+			return ret; \
+	} while (0)
+
+int bq2415x_set_defaults(struct bq2415x_device *bq)
+{
+	bq2415x_exec_command(bq, BQ2415X_CHARGER_DISABLE);
+	bq2415x_set_default_value(bq, current_limit);
+	bq2415x_set_default_value(bq, weak_battery_voltage);
+	bq2415x_set_default_value(bq, battery_regulation_voltage);
+	bq2415x_set_default_value(bq, charge_current_sense_voltage);
+	bq2415x_set_default_value(bq, termination_current_sense_voltage);
+	bq2415x_exec_command(bq, BQ2415X_CHARGER_ENABLE);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(bq2415x_set_defaults);
+
+#undef bq2415x_set_default_value
+
+void bq2415x_reset_chip(struct bq2415x_device *bq)
+{
+	bq2415x_i2c_write(bq, BQ2415X_REG_CURRENT, BQ2415X_RESET_CURRENT);
+	bq2415x_i2c_write(bq, BQ2415X_REG_VOLTAGE, BQ2415X_RESET_VOLTAGE);
+	bq2415x_i2c_write(bq, BQ2415X_REG_CONTROL, BQ2415X_RESET_CONTROL);
+	bq2415x_i2c_write(bq, BQ2415X_REG_STATUS, BQ2415X_RESET_STATUS);
+}
+EXPORT_SYMBOL_GPL(bq2415x_reset_chip);
+
+int bq2415x_set_current_limit(struct bq2415x_device *bq, int mA)
+{
+	int val = (mA/100 + (mA%100 > 0 ? 1 : 0) - 1) / 4;
+
+	if (val < 0)
+		val = 0;
+
+	if (val > 3)
+		val = 3;
+
+	return bq2415x_i2c_write_mask(bq, BQ2415X_REG_CONTROL, val, BQ2415X_MASK_LIMIT, BQ2415X_SHIFT_LIMIT);
+}
+EXPORT_SYMBOL_GPL(bq2415x_set_current_limit);
+
+int bq2415x_get_current_limit(struct bq2415x_device *bq)
+{
+	int ret = bq2415x_i2c_read_mask(bq, BQ2415X_REG_CONTROL, BQ2415X_MASK_LIMIT, BQ2415X_SHIFT_LIMIT);
+	if (ret < 0)
+		return ret;
+	else
+		return 100 * (1 + 4*ret);
+}
+EXPORT_SYMBOL_GPL(bq2415x_get_current_limit);
+
+int bq2415x_set_weak_battery_voltage(struct bq2415x_device *bq, int mV)
+{
+	int val = mV/100 + (mV%100 > 0 ? 1 : 0) - 34;
+
+	if (val < 0)
+		val = 0;
+
+	if (val > 3)
+		return -EINVAL;
+
+	return bq2415x_i2c_write_mask(bq, BQ2415X_REG_CONTROL, val, BQ2415X_MASK_VLOWV, BQ2415X_SHIFT_VLOWV);
+}
+EXPORT_SYMBOL_GPL(bq2415x_set_weak_battery_voltage);
+
+int bq2415x_get_weak_battery_voltage(struct bq2415x_device *bq)
+{
+	int ret = bq2415x_i2c_read_mask(bq, BQ2415X_REG_CONTROL, BQ2415X_MASK_VLOWV, BQ2415X_SHIFT_VLOWV);
+	if (ret < 0)
+		return ret;
+	else
+		return 100 * (34 + ret);
+}
+EXPORT_SYMBOL_GPL(bq2415x_get_weak_battery_voltage);
+
+int bq2415x_set_battery_regulation_voltage(struct bq2415x_device *bq, int mV)
+{
+	int val = mV/10 + (mV%10 > 0 ? 1 : 0) - 350;
+
+	if (val < 0)
+		val = 0;
+
+	if (val > 94) /* FIXME: Max is 94 or 122 ? */
+		return -EINVAL;
+
+	return bq2415x_i2c_write_mask(bq, BQ2415X_REG_VOLTAGE, val, BQ2415X_MASK_VO, BQ2415X_SHIFT_VO);
+}
+EXPORT_SYMBOL_GPL(bq2415x_set_battery_regulation_voltage);
+
+int bq2415x_get_battery_regulation_voltage(struct bq2415x_device *bq)
+{
+	int ret = bq2415x_i2c_read_mask(bq, BQ2415X_REG_VOLTAGE, BQ2415X_MASK_VO, BQ2415X_SHIFT_VO);
+	if (ret < 0)
+		return ret;
+	else
+		return 10 * (350 + ret);
+}
+EXPORT_SYMBOL_GPL(bq2415x_get_battery_regulation_voltage);
+
+int bq2415x_set_charge_current_sense_voltage(struct bq2415x_device *bq, int mV)
+{
+	/* TODO */
+	return -1;
+}
+EXPORT_SYMBOL_GPL(bq2415x_set_charge_current_sense_voltage);
+
+int bq2415x_get_charge_current_sense_voltage(struct bq2415x_device *bq)
+{
+	/* TODO */
+	return -1;
+}
+EXPORT_SYMBOL_GPL(bq2415x_get_charge_current_sense_voltage);
+
+int bq2415x_set_termination_current_sense_voltage(struct bq2415x_device *bq, int mV)
+{
+	/* TODO */
+	return -1;
+}
+EXPORT_SYMBOL_GPL(bq2415x_set_termination_current_sense_voltage);
+
+int bq2415x_get_termination_current_sense_voltage(struct bq2415x_device *bq)
+{
+	/* TODO */
+	return -1;
+}
+EXPORT_SYMBOL_GPL(bq2415x_get_termination_current_sense_voltage);
+
+/* current limit regulator */
+
+static int bq2415x_regulator_set_current_limit(struct regulator_dev *rdev, int min_uA, int max_uA)
+{
+	struct bq2415x_device *bq = rdev_get_drvdata(rdev);
+	return bq2415x_set_current_limit(bq, min_uA / 1000);
+}
+
+static int bq2415x_regulator_get_current_limit(struct regulator_dev *rdev)
+{
+	struct bq2415x_device *bq = rdev_get_drvdata(rdev);
+	return bq2415x_get_current_limit(bq) * 1000;
+}
+
+struct regulator_ops bq2415x_regulator_current_limit = {
+	.set_current_limit = bq2415x_regulator_set_current_limit,
+	.get_current_limit = bq2415x_regulator_get_current_limit,
+};
+
+/* weak battery voltage regulator */
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)
+static int bq2415x_regulator_list_weak_battery_voltage(struct regulator_dev *rdev, unsigned selector)
+{
+	if (selector > 3)
+		return 0;
+	else
+		return 100000 * (34 + selector);
+}
+#endif
+
+static int bq2415x_regulator_set_weak_battery_voltage(struct regulator_dev *rdev, int min_uV, int max_uV
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)
+		, unsigned *selector
+#endif
+		)
+{
+	struct bq2415x_device *bq = rdev_get_drvdata(rdev);
+	return bq2415x_set_weak_battery_voltage(bq, min_uV / 1000);
+}
+
+static int bq2415x_regulator_get_weak_battery_voltage(struct regulator_dev *rdev)
+{
+	struct bq2415x_device *bq = rdev_get_drvdata(rdev);
+	return bq2415x_get_weak_battery_voltage(bq) * 1000;
+}
+
+static struct regulator_ops bq2415x_regulator_weak_battery_voltage = {
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)
+	.list_voltage = bq2415x_regulator_list_weak_battery_voltage,
+#endif
+	.set_voltage = bq2415x_regulator_set_weak_battery_voltage,
+	.get_voltage = bq2415x_regulator_get_weak_battery_voltage,
+};
+
+/* battery regulation voltage regulator */
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)
+static int bq2415x_regulator_list_battery_regulation_voltage(struct regulator_dev *rdev, unsigned selector)
+{
+	if (selector > 94) /* FIXME: Max is 94 or 122 ? */
+		return 0;
+	else
+		return 10000 * (350 + selector);
+}
+#endif
+
+static int bq2415x_regulator_set_battery_regulation_voltage(struct regulator_dev *rdev, int min_uV, int max_uV
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)
+		, unsigned *selector
+#endif
+		)
+{
+	struct bq2415x_device *bq = rdev_get_drvdata(rdev);
+	return bq2415x_set_battery_regulation_voltage(bq, min_uV / 1000);
+}
+
+static int bq2415x_regulator_get_battery_regulation_voltage(struct regulator_dev *rdev)
+{
+	struct bq2415x_device *bq = rdev_get_drvdata(rdev);
+	return bq2415x_get_battery_regulation_voltage(bq) * 1000;
+}
+
+static struct regulator_ops bq2415x_regulator_battery_regulation_voltage = {
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)
+	.list_voltage = bq2415x_regulator_list_battery_regulation_voltage,
+#endif
+	.set_voltage = bq2415x_regulator_set_battery_regulation_voltage,
+	.get_voltage = bq2415x_regulator_get_battery_regulation_voltage,
+};
+
+/* charge current sense voltage regulator */
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)
+static int bq2415x_regulator_list_charge_current_sense_voltage(struct regulator_dev *rdev, unsigned selector)
+{
+	/* TODO */
+	return 0;
+}
+#endif
+
+static int bq2415x_regulator_set_charge_current_sense_voltage(struct regulator_dev *rdev, int min_uV, int max_uV
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)
+		, unsigned *selector
+#endif
+		)
+{
+	struct bq2415x_device *bq = rdev_get_drvdata(rdev);
+	return bq2415x_set_charge_current_sense_voltage(bq, min_uV / 1000);
+}
+
+static int bq2415x_regulator_get_charge_current_sense_voltage(struct regulator_dev *rdev)
+{
+	struct bq2415x_device *bq = rdev_get_drvdata(rdev);
+	return bq2415x_get_charge_current_sense_voltage(bq) * 1000;
+}
+
+static struct regulator_ops bq2415x_regulator_charge_current_sense_voltage = {
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)
+	.list_voltage = bq2415x_regulator_list_charge_current_sense_voltage,
+#endif
+	.set_voltage = bq2415x_regulator_set_charge_current_sense_voltage,
+	.get_voltage = bq2415x_regulator_get_charge_current_sense_voltage,
+};
+
+/* termination current sense voltage regulator */
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)
+static int bq2415x_regulator_list_termination_current_sense_voltage(struct regulator_dev *rdev, unsigned selector)
+{
+	/* TODO */
+	return 0;
+}
+#endif
+
+static int bq2415x_regulator_set_termination_current_sense_voltage(struct regulator_dev *rdev, int min_uV, int max_uV
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)
+		, unsigned *selector
+#endif
+		)
+{
+	struct bq2415x_device *bq = rdev_get_drvdata(rdev);
+	return bq2415x_set_termination_current_sense_voltage(bq, min_uV / 1000);
+}
+
+static int bq2415x_regulator_get_termination_current_sense_voltage(struct regulator_dev *rdev)
+{
+	struct bq2415x_device *bq = rdev_get_drvdata(rdev);
+	return bq2415x_get_termination_current_sense_voltage(bq) * 1000;
+}
+
+static int bq2415x_regulator_enable_termination_current_sense_voltage(struct regulator_dev *rdev)
+{
+	struct bq2415x_device *bq = rdev_get_drvdata(rdev);
+	return bq2415x_exec_command(bq, BQ2415X_CHARGE_CURRENT_TERMINATION_ENABLE);
+}
+
+static int bq2415x_regulator_disable_termination_current_sense_voltage(struct regulator_dev *rdev)
+{
+	struct bq2415x_device *bq = rdev_get_drvdata(rdev);
+	return bq2415x_exec_command(bq, BQ2415X_CHARGE_CURRENT_TERMINATION_DISABLE);
+}
+
+static int bq2415x_regulator_is_enabled_termination_current_sense_voltage(struct regulator_dev *rdev)
+{
+	struct bq2415x_device *bq = rdev_get_drvdata(rdev);
+	return bq2415x_exec_command(bq, BQ2415X_CHARGE_CURRENT_TERMINATION_STATUS);
+}
+
+static struct regulator_ops bq2415x_regulator_termination_current_sense_voltage = {
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)
+	.list_voltage = bq2415x_regulator_list_termination_current_sense_voltage,
+#endif
+	.set_voltage = bq2415x_regulator_set_termination_current_sense_voltage,
+	.get_voltage = bq2415x_regulator_get_termination_current_sense_voltage,
+	.enable = bq2415x_regulator_enable_termination_current_sense_voltage,
+	.disable = bq2415x_regulator_disable_termination_current_sense_voltage,
+	.is_enabled = bq2415x_regulator_is_enabled_termination_current_sense_voltage,
+};
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)
+#define regulator_register2(desc, dev, init, data) regulator_register(desc, dev, init, data)
+#else
+#define regulator_register2(desc, dev, init, data) regulator_register(desc, dev, data)
+#endif
+
+#define bq2415x_regulator_register(bq, rname, rid, rtype) \
+	do { \
+		bq->rname.desc.name = __stringify(rname); \
+		bq->rname.desc.id = rid; \
+		bq->rname.desc.type = rtype; \
+		bq->rname.desc.owner = THIS_MODULE; \
+		bq->rname.desc.ops = &bq2415x_regulator_##rname; \
+		bq->rname.dev = regulator_register2(&bq->rname.desc, bq->dev, NULL, bq); \
+		if (IS_ERR(bq->rname.dev)) \
+			return PTR_ERR(bq->rname.dev); \
+	} while (0)
+
+#define bq2415x_regulator_unregister(bq, rname) regulator_unregister(bq->rname.dev)
+
+static int bq2415x_regulator_init(struct bq2415x_device *bq)
+{
+/*	bq2415x_regulator_register(bq, current_limit, 0, REGULATOR_CURRENT);
+	bq2415x_regulator_register(bq, weak_battery_voltage, 1, REGULATOR_VOLTAGE);
+	bq2415x_regulator_register(bq, battery_regulation_voltage, 2, REGULATOR_VOLTAGE);
+	bq2415x_regulator_register(bq, charge_current_sense_voltage, 3, REGULATOR_VOLTAGE);
+	bq2415x_regulator_register(bq, ermination_current_sense_voltage, 4, REGULATOR_VOLTAGE);*/
+	return 0;
+}
+
+static void bq2415x_regulator_exit(struct bq2415x_device *bq)
+{
+/*	bq2415x_regulator_unregister(bq, current_limit);
+	bq2415x_regulator_unregister(bq, weak_battery_voltage);
+	bq2415x_regulator_unregister(bq, battery_regulation_voltage);
+	bq2415x_regulator_unregister(bq, charge_current_sense_voltage);
+	bq2415x_regulator_unregister(bq, termination_current_sense_voltage);*/
+}
+
+#undef bq2415x_regulator_register
+#undef bq2415x_regulator_unregister
+
+/* power supply */
+
+static enum power_supply_property bq2415x_power_supply_props[] = {
+	/* TODO */
+	POWER_SUPPLY_PROP_STATUS,
+};
+
+static void bq2415x_power_supply_work(struct work_struct *work)
+{
+	struct bq2415x_device *bq = container_of(work, struct bq2415x_device, work.work);
+	int ret;
+
+	dev_info(bq->dev, "bq2415x_power_supply_work\n");
+
+	if (bq->watchdog != 1)
+		return;
+
+	ret = bq2415x_exec_command(bq, BQ2415X_WATCHDOG_RESET);
+	if (ret < 0)
+		bq->watchdog = ret;
+
+	if (bq->watchdog != 1)
+		return;
+
+	/* TODO charge */
+
+	schedule_delayed_work(&bq->work, BQ2415X_WATCHDOG_TIMEOUT * HZ);
+}
+
+static void bq2415x_power_supply_update_watchdog(struct bq2415x_device *bq, int auto_enable)
+{
+	if (auto_enable && bq->watchdog == 1)
+		return;
+
+	if (!auto_enable && bq->watchdog == 0)
+		return;
+
+	if (auto_enable) {
+		bq->watchdog = 1;
+		schedule_delayed_work(&bq->work, BQ2415X_WATCHDOG_TIMEOUT * HZ);
+		bq2415x_exec_command(bq, BQ2415X_WATCHDOG_RESET);
+	} else {
+		bq->watchdog = 0;
+		cancel_delayed_work_sync(&bq->work);
+	}
+}
+
+static int bq2415x_power_supply_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val)
+{
+	struct bq2415x_device *bq = container_of(psy, struct bq2415x_device, charger);
+	/* TODO */
+	return -EINVAL;
+}
+
+static int bq2415x_power_supply_init(struct bq2415x_device *bq)
+{
+	int ret;
+
+	bq->charger.name = bq->name;
+	bq->charger.type = POWER_SUPPLY_TYPE_USB;
+	bq->charger.properties = bq2415x_power_supply_props;
+	bq->charger.num_properties = ARRAY_SIZE(bq2415x_power_supply_props);
+	bq->charger.get_property = bq2415x_power_supply_get_property;
+
+	ret = power_supply_register(bq->dev, &bq->charger);
+	if (ret)
+		return ret;
+
+	INIT_DELAYED_WORK(&bq->work, bq2415x_power_supply_work);
+
+	bq2415x_power_supply_update_watchdog(bq, 1);
+	return ret;
+}
+
+static void bq2415x_power_supply_exit(struct bq2415x_device *bq)
+{
+	bq->watchdog = 0;
+	cancel_delayed_work_sync(&bq->work);
+	power_supply_unregister(&bq->charger);
+}
+
+/* sysfs files */
+
+static ssize_t bq2415x_sysfs_show_status(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct power_supply *psy = dev_get_drvdata(dev);
+	struct bq2415x_device *bq = container_of(psy, struct bq2415x_device, charger);
+	enum bq2415x_command command;
+	int ret;
+
+	if (strcmp(attr->attr.name, "otg_status") == 0)
+		command = BQ2415X_OTG_STATUS;
+	else if (strcmp(attr->attr.name, "charge_status") == 0)
+		command = BQ2415X_CHARGE_STATUS;
+	else if (strcmp(attr->attr.name, "boost_status") == 0)
+		command = BQ2415X_BOOST_STATUS;
+	else if (strcmp(attr->attr.name, "fault_status") == 0)
+		command = BQ2415X_FAULT_STATUS;
+	else
+		return -EINVAL;
+
+	ret = bq2415x_exec_command(bq, command);
+	if (ret < 0)
+		return ret;
+	else
+		return sprintf(buf, "%d\n", ret);
+}
+
+static ssize_t bq2415x_sysfs_set_watchdog(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct power_supply *psy = dev_get_drvdata(dev);
+	struct bq2415x_device *bq = container_of(psy, struct bq2415x_device, charger);
+	int ret = 0;
+
+	if (strncmp(buf, "auto", 4) == 0)
+		bq2415x_power_supply_update_watchdog(bq, 1);
+	else if (strncmp(buf, "off", 3) == 0)
+		bq2415x_power_supply_update_watchdog(bq, 0);
+	else
+		ret = bq2415x_exec_command(bq, BQ2415X_WATCHDOG_RESET);
+
+	if (ret < 0)
+		return ret;
+	else
+		return count;
+}
+
+static ssize_t bq2415x_sysfs_show_watchdog(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct power_supply *psy = dev_get_drvdata(dev);
+	struct bq2415x_device *bq = container_of(psy, struct bq2415x_device, charger);
+
+	switch (bq->watchdog) {
+		case 1: return sprintf(buf, "auto\n");
+		case 0: return sprintf(buf, "off\n");
+		default: return sprintf(buf, "error %d\n", bq->watchdog);
+	}
+}
+
+/* TODO: Add other sysfs entries */
+
+static DEVICE_ATTR(watchdog, S_IWUSR | S_IRUGO, bq2415x_sysfs_show_watchdog, bq2415x_sysfs_set_watchdog);
+static DEVICE_ATTR(otg_status, S_IRUGO, bq2415x_sysfs_show_status, NULL);
+static DEVICE_ATTR(charge_status, S_IRUGO, bq2415x_sysfs_show_status, NULL);
+static DEVICE_ATTR(boost_status, S_IRUGO, bq2415x_sysfs_show_status, NULL);
+static DEVICE_ATTR(fault_status, S_IRUGO, bq2415x_sysfs_show_status, NULL);
+
+static struct attribute *bq2415x_sysfs_attributes[] = {
+	&dev_attr_watchdog.attr,
+	&dev_attr_otg_status.attr,
+	&dev_attr_charge_status.attr,
+	&dev_attr_boost_status.attr,
+	&dev_attr_fault_status.attr,
+	NULL,
+};
+
+static const struct attribute_group bq2415x_sysfs_attr_group = {
+	.attrs = bq2415x_sysfs_attributes,
+};
+
+static int bq2415x_sysfs_init(struct bq2415x_device *bq)
+{
+	return sysfs_create_group(&bq->charger.dev->kobj, &bq2415x_sysfs_attr_group);
+}
+
+static void bq2415x_sysfs_exit(struct bq2415x_device *bq)
+{
+	sysfs_remove_group(&bq->charger.dev->kobj, &bq2415x_sysfs_attr_group);
+}
+
+/* charger led device on STAT_PIN */
+
+static void bq2415x_led_set(struct led_classdev *led_cdev, enum led_brightness value)
+{
+	struct i2c_client *client = to_i2c_client(led_cdev->dev->parent);
+	struct bq2415x_device *bq = i2c_get_clientdata(client);
+
+	if (value)
+		bq2415x_exec_command(bq, BQ2415X_STAT_PIN_ENABLE);
+	else
+		bq2415x_exec_command(bq, BQ2415X_STAT_PIN_DISABLE);
+}
+
+static enum led_brightness bq2415x_led_get(struct led_classdev *led_cdev)
+{
+	struct i2c_client *client = to_i2c_client(led_cdev->dev->parent);
+	struct bq2415x_device *bq = i2c_get_clientdata(client);
+
+	if (bq2415x_exec_command(bq, BQ2415X_STAT_PIN_STATUS) > 0)
+		return LED_FULL;
+	else
+		return LED_OFF;
+}
+
+static int bq2415x_led_init(struct bq2415x_device *bq)
+{
+	bq->led.name = bq->name;
+	bq->led.brightness_set = bq2415x_led_set;
+	bq->led.brightness_get = bq2415x_led_get;
+	return led_classdev_register(bq->dev, &bq->led);
+}
+
+static void bq2415x_led_exit(struct bq2415x_device *bq)
+{
+	led_classdev_unregister(&bq->led);
+}
+
+/* bq2415x register */
+
+static int bq2415x_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+	int ret;
+	int num;
+	char *name;
+	struct bq2415x_device *bq;
+
+	if (!client->dev.platform_data) {
+		dev_err(&client->dev, "platform data not set\n");
+		return -ENODEV;
+	}
+
+	/* Get new ID for the new device */
+	ret = idr_pre_get(&bq2415x_id, GFP_KERNEL);
+	if (ret == 0)
+		return -ENOMEM;
+
+	mutex_lock(&bq2415x_mutex);
+	ret = idr_get_new(&bq2415x_id, client, &num);
+	mutex_unlock(&bq2415x_mutex);
+
+	if (ret < 0)
+		return ret;
+
+	name = kasprintf(GFP_KERNEL, "%s-%d", id->name, num);
+	if (!name) {
+		dev_err(&client->dev, "failed to allocate device name\n");
+		ret = -ENOMEM;
+		goto error_1;
+	}
+
+	bq = kzalloc(sizeof(*bq), GFP_KERNEL);
+	if (!bq) {
+		dev_err(&client->dev, "failed to allocate device data\n");
+		ret = -ENOMEM;
+		goto error_2;
+	}
+
+	i2c_set_clientdata(client, bq);
+
+	bq->id = num;
+	bq->dev = &client->dev;
+	bq->chip = id->driver_data;
+	bq->name = name;
+	bq->platform_data = client->dev.platform_data;
+	bq->watchdog = 0;
+
+	bq2415x_reset_chip(bq);
+
+	ret = bq2415x_regulator_init(bq);
+	if (ret) {
+		dev_err(bq->dev, "failed to register regulators: %d\n", ret);
+		goto error_3;
+	}
+
+	ret = bq2415x_led_init(bq);
+	if (ret) {
+		dev_err(bq->dev, "failed to register led device: %d\n", ret);
+		goto error_4;
+	}
+
+	ret = bq2415x_power_supply_init(bq);
+	if (ret) {
+		dev_err(bq->dev, "failed to register power supply: %d\n", ret);
+		goto error_5;
+	}
+
+	ret = bq2415x_sysfs_init(bq);
+	if (ret) {
+		dev_err(bq->dev, "failed to create sysfs entries: %d\n", ret);
+		goto error_6;
+	}
+
+	bq2415x_set_defaults(bq);
+
+	dev_info(bq->dev, "driver registred\n");
+	return 0;
+
+/*error_7:*/
+	bq2415x_sysfs_exit(bq);
+error_6:
+	bq2415x_power_supply_exit(bq);
+error_5:
+	bq2415x_led_exit(bq);
+error_4:
+	bq2415x_regulator_exit(bq);
+error_3:
+	kfree(bq);
+error_2:
+	kfree(name);
+error_1:
+	mutex_lock(&bq2415x_mutex);
+	idr_remove(&bq2415x_id, num);
+	mutex_unlock(&bq2415x_mutex);
+
+	return ret;
+}
+
+/* bq2415x unregister */
+
+static int bq2415x_remove(struct i2c_client *client)
+{
+	struct bq2415x_device *bq = i2c_get_clientdata(client);
+
+	bq2415x_sysfs_exit(bq);
+	bq2415x_power_supply_exit(bq);
+	bq2415x_led_exit(bq);
+	bq2415x_regulator_exit(bq);
+
+	bq2415x_reset_chip(bq);
+
+	mutex_lock(&bq2415x_mutex);
+	idr_remove(&bq2415x_id, bq->id);
+	mutex_unlock(&bq2415x_mutex);
+
+	dev_info(bq->dev, "driver unregistred\n");
+
+	kfree(bq->name);
+	kfree(bq);
+
+	return 0;
+}
+
+static const struct i2c_device_id bq2415x_i2c_id_table[] = {
+	{ "bq2415x", BQUNKNOWN },
+	{ "bq24150", BQ24150 },
+	{ "bq24150a", BQ24150A },
+	{ "bq24151", BQ24151 },
+	{ "bq24151a", BQ24151A },
+	{ "bq24152", BQ24152 },
+	{ "bq24153", BQ24153 },
+	{ "bq24153a", BQ24153A },
+	{ "bq24155", BQ24155 },
+	{ "bq24156", BQ24156 },
+	{ "bq24156a", BQ24156A },
+	{ "bq24158", BQ24158 },
+	{},
+};
+MODULE_DEVICE_TABLE(i2c, bq2415x_i2c_id_table);
+
+static struct i2c_driver bq2415x_driver = {
+	.driver = {
+		.name = "bq2415x-charger",
+	},
+	.probe = bq2415x_probe,
+	.remove = bq2415x_remove,
+	.id_table = bq2415x_i2c_id_table,
+};
+
+/* BEGIN: Temporary RX51 hack TODO: move to board-rx51.c */
+static struct bq2415x_platform_data rx51_platform_data = {
+	.current_limit = 100, /* mA */
+	.weak_battery_voltage = 3400, /* mV */
+	.battery_regulation_voltage = 4200, /* mV */
+	.charge_current_sense_voltage = -1, /* TODO */
+	.termination_current_sense_voltage = -1, /* TODO */
+};
+static struct i2c_board_info rx51_board_info = {
+	I2C_BOARD_INFO("bq24150", 0x6b),
+	.platform_data = &rx51_platform_data,
+};
+static struct i2c_client *client;
+/* END */
+
+static int __init bq2415x_init(void)
+{
+	/* BEGIN: Temporary RX51 hack TODO: move to board-rx51.c */
+	client = i2c_new_device(i2c_get_adapter(2), &rx51_board_info);
+	/* END */
+
+	return i2c_add_driver(&bq2415x_driver);
+}
+module_init(bq2415x_init);
+
+static void __exit bq2415x_exit(void)
+{
+	/* BEGIN: Temporary RX51 hack TODO: move to board-rx51.c */
+	i2c_unregister_device(client);
+	/* END */
+
+	i2c_del_driver(&bq2415x_driver);
+}
+module_exit(bq2415x_exit);
+
+MODULE_AUTHOR("Pali Rohár <pali.rohar@gmail.com>");
+MODULE_DESCRIPTION("bq2415x charger driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/bq2415x_charger.h b/drivers/power/bq2415x_charger.h
new file mode 100644
index 0000000..de0ec02
--- /dev/null
+++ b/drivers/power/bq2415x_charger.h
@@ -0,0 +1,97 @@
+/*
+    bq2415x_charger.h - bq2415x charger driver
+    Copyright (C) 2011  Pali Rohár <pali.rohar@gmail.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.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#ifndef BQ2415X_CHARGER_H
+#define BQ2415X_CHARGER_H
+
+struct bq2415x_device;
+
+struct bq2415x_platform_data {
+	int current_limit;
+	int weak_battery_voltage;
+	int battery_regulation_voltage;
+	int charge_current_sense_voltage;
+	int termination_current_sense_voltage;
+};
+
+enum bq2415x_command {
+	BQ2415X_WATCHDOG_RESET,
+	BQ2415X_OTG_STATUS,
+	BQ2415X_STAT_PIN_STATUS,
+	BQ2415X_STAT_PIN_ENABLE,
+	BQ2415X_STAT_PIN_DISABLE,
+	BQ2415X_CHARGE_STATUS,
+	BQ2415X_BOOST_STATUS,
+	BQ2415X_FAULT_STATUS,
+
+	BQ2415X_CHARGE_CURRENT_TERMINATION_STATUS,
+	BQ2415X_CHARGE_CURRENT_TERMINATION_ENABLE,
+	BQ2415X_CHARGE_CURRENT_TERMINATION_DISABLE,
+	BQ2415X_CHARGER_STATUS,
+	BQ2415X_CHARGER_ENABLE,
+	BQ2415X_CHARGER_DISABLE,
+	BQ2415X_HIGH_IMPEDANCE_STATUS,
+	BQ2415X_HIGH_IMPEDANCE_ENABLE,
+	BQ2415X_HIGH_IMPEDANCE_DISABLE,
+	BQ2415X_MODE,
+	BQ2415X_SET_BOOST_MODE,
+	BQ2415X_SET_CHARGER_MODE,
+
+	BQ2415X_OTG_HIGH_STATUS,
+	BQ2415X_OTG_HIGH_ACTIVATE,
+	BQ2415X_OTG_LOW_ACTIVATE,
+	BQ2415X_OTG_PIN_STATUS,
+	BQ2415X_OTG_PIN_ENABLE,
+	BQ2415X_OTG_PIN_DISABLE,
+
+	BQ2415X_VENDER_CODE,
+	BQ2415X_PART_NUMBER,
+	BQ2415X_REVISION,
+};
+
+enum bq2415x_chip {
+	BQUNKNOWN,
+	BQ24150,
+	BQ24150A,
+	BQ24151,
+	BQ24151A,
+	BQ24152,
+	BQ24153,
+	BQ24153A,
+	BQ24155,
+	BQ24156,
+	BQ24156A,
+	BQ24158,
+};
+
+enum bq2415x_chip bq2415x_detect_chip(struct bq2415x_device *bq);
+int bq2415x_exec_command(struct bq2415x_device *bq, enum bq2415x_command command);
+int bq2415x_set_defaults(struct bq2415x_device *bq);
+int bq2415x_set_current_limit(struct bq2415x_device *bq, int uA);
+int bq2415x_get_current_limit(struct bq2415x_device *bq);
+int bq2415x_set_weak_battery_voltage(struct bq2415x_device *bq, int mV);
+int bq2415x_get_weak_battery_voltage(struct bq2415x_device *bq);
+int bq2415x_set_battery_regulation_voltage(struct bq2415x_device *bq, int mV);
+int bq2415x_get_battery_regulation_voltage(struct bq2415x_device *bq);
+int bq2415x_set_charge_current_sense_voltage(struct bq2415x_device *bq, int mV);
+int bq2415x_get_charge_current_sense_voltage(struct bq2415x_device *bq);
+int bq2415x_set_termination_current_sense_voltage(struct bq2415x_device *bq, int mV);
+int bq2415x_get_termination_current_sense_voltage(struct bq2415x_device *bq);
+
+#endif


-- 
Pali Rohár
pali.rohar@gmail.com

[-- Attachment #2: This is a digitally signed message part. --]
[-- Type: application/pgp-signature, Size: 198 bytes --]

^ permalink raw reply related	[flat|nested] 28+ messages in thread

* RFC 2: bq2415x_charger driver
  2011-12-07 21:03 ` RFC: bq2415x_charger driver Pali Rohár
  2011-12-07 21:25   ` Vladimir Zapolskiy
@ 2012-01-27  2:40   ` Pali Rohár
  2012-01-27 16:24     ` Mark Brown
  2012-01-27 20:40     ` Sebastian Reichel
  1 sibling, 2 replies; 28+ messages in thread
From: Pali Rohár @ 2012-01-27  2:40 UTC (permalink / raw)
  To: linux-main
  Cc: linux-omap, Samuel Ortiz, Aliaksei Katovich, Vladimir Zapolskiy,
	Felipe Contreras, Anton Vorontsov, Joerg Reisenweber,
	Sebastian Reichel,
	Ивайло
	Димитров


[-- Attachment #1.1: Type: text/plain, Size: 1955 bytes --]

Hello,

I'm sending second RFC kernel driver which can charge battery in Nokia N900. 
Now all code for bq2415x chip is in one kernel driver bq2415x_charger. This 
version working fine on Nokia N900 with kernel 2.6.28 (but should work with 
new versions too). I tested only charging (but boost mode is implemented too).

Driver has power_supply interface (without new charger interface - due to 
compatibility with n900 kernel 2.6.28) and additional sysfs entires for 
configuring bq chip.

I created this sysfs files integrated into power_supply subdir:

(rw) battery_regulation_voltage (int)
(r)  boost_status (int)
(rw) current_limit (int)
(r)  fault_status (int)
(rw) high_impedance_enable (int)
(rw) charge_current_sense_voltage (int)
(rw) charge_current_termination_enable (int)
(r)  charge_status (int)
(rw) mode (string)
(rw) otg_pin_enable (int)
(r)  otg_status (int)
(r)  regdump (string)
(rw) stat_pin_enable (int)
(rw) termination_current_sense_voltage (int)
(rw) timer (string)
(rw) weak_battery_voltage (int)

most important sysfs entry is mode:
it will configure mode (charge or boost) and current_limit. possible values:
* auto
* none (100mA)
* host (USB Host/HUB charger - 500mA)
* dedicated (USB Dedicated charger - unlimited mA)
* boost (boost mode for usb host mode - charging disabled)

auto mode depends on external platform data (now not working) - in future can 
be configured with isp1704_charger driver, which support autodetection of 
connected charger.

how to use this module on n900 to charge battery:
1. Turn off BME
2. load module
3. connect charger and set correct charger type:
 $ echo host > /sys/class/power_supply/bq24150-0/mode
 or
 $ echo dedicated > /sys/class/power_supply/bq24150-0/mode

This RFC driver has not implemented sysfs entries for configuring charge 
current sense voltage and termination current sense voltage.

-- 
Pali Rohár
pali.rohar@gmail.com

[-- Attachment #1.2: bq2415x_charger.patch --]
[-- Type: text/x-patch, Size: 43346 bytes --]

--- /dev/null	2012-01-26 12:32:59.572022923 +0100
+++ bq2415x_charger.c	2012-01-27 02:58:56.026089998 +0100
@@ -0,0 +1,1438 @@
+/*
+    bq2415x_charger.c - bq2415x charger driver
+    Copyright (C) 2011-2012  Pali Rohár <pali.rohar@gmail.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.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+/*
+  Datasheets:
+  http://www.ti.com/product/bq24150
+  http://www.ti.com/product/bq24150a
+  http://www.ti.com/product/bq24152
+  http://www.ti.com/product/bq24153
+  http://www.ti.com/product/bq24153a
+  http://www.ti.com/product/bq24155
+*/
+
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/param.h>
+#include <linux/err.h>
+#include <linux/workqueue.h>
+#include <linux/sysfs.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/idr.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+
+/*#include "isp1704_charger.h"*/
+#include "bq2415x_charger.h"
+
+#define BQ2415X_TIMER_TIMEOUT	10
+
+#define BQ2415X_REG_STATUS		0x00
+#define BQ2415X_REG_CONTROL		0x01
+#define BQ2415X_REG_VOLTAGE		0x02
+#define BQ2415X_REG_VENDER		0x03
+#define BQ2415X_REG_CURRENT		0x04
+
+/* reset state for all registers */
+#define BQ2415X_RESET_STATUS		BIT(6)
+#define BQ2415X_RESET_CONTROL		(BIT(4)|BIT(5))
+#define BQ2415X_RESET_VOLTAGE		(BIT(1)|BIT(3))
+#define BQ2415X_RESET_CURRENT		(BIT(0)|BIT(3)|BIT(7))
+
+/* status register */
+#define BQ2415X_BIT_TMR_RST		7
+#define BQ2415X_BIT_OTG			7
+#define BQ2415X_BIT_EN_STAT		6
+#define BQ2415X_MASK_STAT		(BIT(4)|BIT(5))
+#define BQ2415X_SHIFT_STAT		4
+#define BQ2415X_BIT_BOOST		3
+#define BQ2415X_MASK_FAULT		(BIT(0)|BIT(1)|BIT(2))
+#define BQ2415X_SHIFT_FAULT		0
+
+/* control register */
+#define BQ2415X_MASK_LIMIT		(BIT(6)|BIT(7))
+#define BQ2415X_SHIFT_LIMIT		6
+#define BQ2415X_MASK_VLOWV		(BIT(4)|BIT(5))
+#define BQ2415X_SHIFT_VLOWV		4
+#define BQ2415X_BIT_TE			3
+#define BQ2415X_BIT_CE			2
+#define BQ2415X_BIT_HZ_MODE		1
+#define BQ2415X_BIT_OPA_MODE		0
+
+/* voltage register */
+#define BQ2415X_MASK_VO			(BIT(2)|BIT(3)|BIT(4)|BIT(5)|BIT(6)|BIT(7))
+#define BQ2415X_SHIFT_VO		2
+#define BQ2415X_BIT_OTG_PL		1
+#define BQ2415X_BIT_OTG_EN		0
+
+/* vender register */
+#define BQ2415X_MASK_VENDER		(BIT(5)|BIT(6)|BIT(7))
+#define BQ2415X_SHIFT_VENDER		5
+#define BQ2415X_MASK_PN			(BIT(3)|BIT(4))
+#define BQ2415X_SHIFT_PN		3
+#define BQ2415X_MASK_REVISION		(BIT(0)|BIT(1)|BIT(2))
+#define BQ2415X_SHIFT_REVISION		0
+
+/* current register */
+/* RESET				BIT(7) */
+#define BQ2415X_MASK_VI_CHRG		(BIT(4)|BIT(5)|BIT(6))
+#define BQ2415X_SHIFT_VI_CHRG		4
+/* N/A					BIT(3) */
+#define BQ2415X_MASK_VI_TERM		(BIT(0)|BIT(1)|BIT(2))
+#define BQ2415X_SHIFT_VI_TERM		0
+
+
+enum bq2415x_command {
+	BQ2415X_TIMER_RESET,
+	BQ2415X_OTG_STATUS,
+	BQ2415X_STAT_PIN_STATUS,
+	BQ2415X_STAT_PIN_ENABLE,
+	BQ2415X_STAT_PIN_DISABLE,
+	BQ2415X_CHARGE_STATUS,
+	BQ2415X_BOOST_STATUS,
+	BQ2415X_FAULT_STATUS,
+
+	BQ2415X_CHARGE_CURRENT_TERMINATION_STATUS,
+	BQ2415X_CHARGE_CURRENT_TERMINATION_ENABLE,
+	BQ2415X_CHARGE_CURRENT_TERMINATION_DISABLE,
+	BQ2415X_CHARGER_STATUS,
+	BQ2415X_CHARGER_ENABLE,
+	BQ2415X_CHARGER_DISABLE,
+	BQ2415X_HIGH_IMPEDANCE_STATUS,
+	BQ2415X_HIGH_IMPEDANCE_ENABLE,
+	BQ2415X_HIGH_IMPEDANCE_DISABLE,
+	BQ2415X_BOOST_MODE_STATUS,
+	BQ2415X_BOOST_MODE_ENABLE,
+	BQ2415X_BOOST_MODE_DISABLE,
+
+	BQ2415X_OTG_LEVEL,
+	BQ2415X_OTG_ACTIVATE_HIGH,
+	BQ2415X_OTG_ACTIVATE_LOW,
+	BQ2415X_OTG_PIN_STATUS,
+	BQ2415X_OTG_PIN_ENABLE,
+	BQ2415X_OTG_PIN_DISABLE,
+
+	BQ2415X_VENDER_CODE,
+	BQ2415X_PART_NUMBER,
+	BQ2415X_REVISION,
+};
+
+enum bq2415x_chip {
+	BQUNKNOWN,
+	BQ24150,
+	BQ24150A,
+	BQ24151,
+	BQ24151A,
+	BQ24152,
+	BQ24153,
+	BQ24153A,
+	BQ24155,
+	BQ24156,
+	BQ24156A,
+	BQ24158,
+};
+
+enum bq2415x_mode {
+	BQ2415X_MODE_NONE,
+	BQ2415X_MODE_HOST_CHARGER,
+	BQ2415X_MODE_DEDICATED_CHARGER,
+	BQ2415X_MODE_BOOST,
+};
+
+static char *bq2415x_chip_name[] = {
+	"unknown",
+	"bq24150",
+	"bq24150a",
+	"bq24151",
+	"bq24151a",
+	"bq24152",
+	"bq24153",
+	"bq24153a",
+	"bq24155",
+	"bq24156",
+	"bq24156a",
+	"bq24158",
+};
+
+static char *bq2415x_rev_name[] = {
+	"1.0",
+	"1.1",
+	"1.2",
+	"1.3",
+	"1.4",
+	"1.5",
+	"1.6",
+	"1.7",
+	"unknown",
+};
+
+struct bq2415x_device {
+	struct device *dev;
+	struct bq2415x_platform_data *platform_data;
+	struct power_supply charger;
+	struct delayed_work work;
+	enum bq2415x_mode charger_mode;
+	enum bq2415x_mode mode;
+	enum bq2415x_chip chip;
+	char *model;
+	char *name;
+	int autotimer;
+	int automode;
+	int id;
+};
+
+static DEFINE_IDR(bq2415x_id);
+
+static DEFINE_MUTEX(bq2415x_id_mutex);
+static DEFINE_MUTEX(bq2415x_timer_mutex);
+static DEFINE_MUTEX(bq2415x_type_mutex);
+static DEFINE_MUTEX(bq2415x_i2c_mutex);
+
+/* i2c read functions */
+
+static int bq2415x_i2c_read(struct bq2415x_device *bq, u8 reg)
+{
+	struct i2c_client *client = to_i2c_client(bq->dev);
+	struct i2c_msg msg[2];
+	u8 val;
+	int ret;
+
+	if (!client->adapter)
+		return -ENODEV;
+
+	msg[0].addr = client->addr;
+	msg[0].flags = 0;
+	msg[0].buf = &reg;
+	msg[0].len = sizeof(reg);
+	msg[1].addr = client->addr;
+	msg[1].flags = I2C_M_RD;
+	msg[1].buf = &val;
+	msg[1].len = sizeof(val);
+
+	mutex_lock(&bq2415x_i2c_mutex);
+	ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
+	mutex_unlock(&bq2415x_i2c_mutex);
+
+	if (ret < 0)
+		return ret;
+
+	return val;
+}
+
+static int bq2415x_i2c_read_mask(struct bq2415x_device *bq, u8 reg, u8 mask, u8 shift)
+{
+	int ret;
+
+	if (shift > 8)
+		return -EINVAL;
+
+	ret = bq2415x_i2c_read(bq, reg);
+	if (ret < 0)
+		return ret;
+	else
+		return (ret & mask) >> shift;
+}
+
+static int bq2415x_i2c_read_bit(struct bq2415x_device *bq, u8 reg, u8 bit)
+{
+	if (bit > 8)
+		return -EINVAL;
+	else
+		return bq2415x_i2c_read_mask(bq, reg, BIT(bit), bit);
+}
+
+/* i2c write functions */
+
+static int bq2415x_i2c_write(struct bq2415x_device *bq, u8 reg, u8 val)
+{
+	struct i2c_client *client = to_i2c_client(bq->dev);
+	struct i2c_msg msg[1];
+	u8 data[2];
+	int ret;
+
+	data[0] = reg;
+	data[1] = val;
+
+	msg[0].addr = client->addr;
+	msg[0].flags = 0;
+	msg[0].buf = data;
+	msg[0].len = ARRAY_SIZE(data);
+
+	mutex_lock(&bq2415x_i2c_mutex);
+	ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
+	mutex_unlock(&bq2415x_i2c_mutex);
+
+	/* i2c_transfer returns number of messages transferred */
+	if (ret < 0)
+		return ret;
+	else if (ret != 1)
+		return -EIO;
+
+	return 0;
+}
+
+static int bq2415x_i2c_write_mask(struct bq2415x_device *bq, u8 reg, u8 val, u8 mask, u8 shift)
+{
+	int ret;
+
+	if (shift > 8)
+		return -EINVAL;
+
+	ret = bq2415x_i2c_read(bq, reg);
+	if (ret < 0)
+		return ret;
+
+	ret &= ~mask;
+	ret |= val << shift;
+
+	return bq2415x_i2c_write(bq, reg, ret);
+}
+
+static int bq2415x_i2c_write_bit(struct bq2415x_device *bq, u8 reg, bool val, u8 bit)
+{
+	if (bit > 8)
+		return -EINVAL;
+	else
+		return bq2415x_i2c_write_mask(bq, reg, val, BIT(bit), bit);
+}
+
+/* global exec command function */
+
+static int bq2415x_exec_command(struct bq2415x_device *bq, enum bq2415x_command command)
+{
+	int ret;
+	switch(command)
+	{
+		case BQ2415X_TIMER_RESET:
+			return bq2415x_i2c_write_bit(bq, BQ2415X_REG_STATUS, 1, BQ2415X_BIT_TMR_RST);
+		case BQ2415X_OTG_STATUS:
+			return bq2415x_i2c_read_bit(bq, BQ2415X_REG_STATUS, BQ2415X_BIT_OTG);
+		case BQ2415X_STAT_PIN_STATUS:
+			return bq2415x_i2c_read_bit(bq, BQ2415X_REG_STATUS, BQ2415X_BIT_EN_STAT);
+		case BQ2415X_STAT_PIN_ENABLE:
+			return bq2415x_i2c_write_bit(bq, BQ2415X_REG_STATUS, 1, BQ2415X_BIT_EN_STAT);
+		case BQ2415X_STAT_PIN_DISABLE:
+			return bq2415x_i2c_write_bit(bq, BQ2415X_REG_STATUS, 0, BQ2415X_BIT_EN_STAT);
+		case BQ2415X_CHARGE_STATUS:
+			return bq2415x_i2c_read_mask(bq, BQ2415X_REG_STATUS, BQ2415X_MASK_STAT, BQ2415X_SHIFT_STAT);
+		case BQ2415X_BOOST_STATUS:
+			return bq2415x_i2c_read_bit(bq, BQ2415X_REG_STATUS, BQ2415X_BIT_BOOST);
+		case BQ2415X_FAULT_STATUS:
+			return bq2415x_i2c_read_mask(bq, BQ2415X_REG_STATUS, BQ2415X_MASK_FAULT, BQ2415X_SHIFT_FAULT);
+
+		case BQ2415X_CHARGE_CURRENT_TERMINATION_STATUS:
+			return bq2415x_i2c_read_bit(bq, BQ2415X_REG_CONTROL, BQ2415X_BIT_TE);
+		case BQ2415X_CHARGE_CURRENT_TERMINATION_ENABLE:
+			return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, 1, BQ2415X_BIT_TE);
+		case BQ2415X_CHARGE_CURRENT_TERMINATION_DISABLE:
+			return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, 0, BQ2415X_BIT_TE);
+		case BQ2415X_CHARGER_STATUS:
+			ret = bq2415x_i2c_read_bit(bq, BQ2415X_REG_CONTROL, BQ2415X_BIT_CE);
+			if (ret < 0)
+				return ret;
+			else
+				return ret > 0 ? 0 : 1;
+		case BQ2415X_CHARGER_ENABLE:
+			return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, 0, BQ2415X_BIT_CE);
+		case BQ2415X_CHARGER_DISABLE:
+			return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, 1, BQ2415X_BIT_CE);
+		case BQ2415X_HIGH_IMPEDANCE_STATUS:
+			return bq2415x_i2c_read_bit(bq, BQ2415X_REG_CONTROL, BQ2415X_BIT_HZ_MODE);
+		case BQ2415X_HIGH_IMPEDANCE_ENABLE:
+			return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, 1, BQ2415X_BIT_HZ_MODE);
+		case BQ2415X_HIGH_IMPEDANCE_DISABLE:
+			return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, 0, BQ2415X_BIT_HZ_MODE);
+		case BQ2415X_BOOST_MODE_STATUS:
+			return bq2415x_i2c_read_bit(bq, BQ2415X_REG_CONTROL, BQ2415X_BIT_OPA_MODE);
+		case BQ2415X_BOOST_MODE_ENABLE:
+			return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, 1, BQ2415X_BIT_OPA_MODE);
+		case BQ2415X_BOOST_MODE_DISABLE:
+			return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, 0, BQ2415X_BIT_OPA_MODE);
+
+		case BQ2415X_OTG_LEVEL:
+			return bq2415x_i2c_read_bit(bq, BQ2415X_REG_VOLTAGE, BQ2415X_BIT_OTG_PL);
+		case BQ2415X_OTG_ACTIVATE_HIGH:
+			return bq2415x_i2c_write_bit(bq, BQ2415X_REG_VOLTAGE, 1, BQ2415X_BIT_OTG_PL);
+		case BQ2415X_OTG_ACTIVATE_LOW:
+			return bq2415x_i2c_write_bit(bq, BQ2415X_REG_VOLTAGE, 0, BQ2415X_BIT_OTG_PL);
+		case BQ2415X_OTG_PIN_STATUS:
+			return bq2415x_i2c_read_bit(bq, BQ2415X_REG_VOLTAGE, BQ2415X_BIT_OTG_EN);
+		case BQ2415X_OTG_PIN_ENABLE:
+			return bq2415x_i2c_write_bit(bq, BQ2415X_REG_VOLTAGE, 1, BQ2415X_BIT_OTG_EN);
+		case BQ2415X_OTG_PIN_DISABLE:
+			return bq2415x_i2c_write_bit(bq, BQ2415X_REG_VOLTAGE, 0, BQ2415X_BIT_OTG_EN);
+
+		case BQ2415X_VENDER_CODE:
+			return bq2415x_i2c_read_mask(bq, BQ2415X_REG_VENDER, BQ2415X_MASK_VENDER, BQ2415X_SHIFT_VENDER);
+		case BQ2415X_PART_NUMBER:
+			return bq2415x_i2c_read_mask(bq, BQ2415X_REG_VENDER, BQ2415X_MASK_PN, BQ2415X_SHIFT_PN);
+		case BQ2415X_REVISION:
+			return bq2415x_i2c_read_mask(bq, BQ2415X_REG_VENDER, BQ2415X_MASK_REVISION, BQ2415X_SHIFT_REVISION);
+
+		default:
+			return -EINVAL;
+	}
+}
+
+/* global detect chip */
+
+static enum bq2415x_chip bq2415x_detect_chip(struct bq2415x_device *bq)
+{
+	struct i2c_client *client = to_i2c_client(bq->dev);
+	int ret = bq2415x_exec_command(bq, BQ2415X_PART_NUMBER);
+	if (ret < 0)
+		return ret;
+	if (client->addr == 0x6b) {
+		switch (ret) {
+			case 0:
+				if (bq->chip == BQ24151A)
+					return bq->chip;
+				else
+					return BQ24151;
+			case 1:
+				if (bq->chip == BQ24150A || bq->chip == BQ24152 || bq->chip == BQ24155)
+					return bq->chip;
+				else
+					return BQ24150;
+			case 2:
+				if (bq->chip == BQ24153A)
+					return bq->chip;
+				else
+					return BQ24153;
+			default:
+				return BQUNKNOWN;
+		}
+	} else if (client->addr == 0x6a) {
+		switch (ret) {
+			case 0:
+				if (bq->chip == BQ24156A)
+					return bq->chip;
+				else
+					return BQ24156;
+			case 2:
+				return BQ24158;
+			default:
+				return BQUNKNOWN;
+		}
+	}
+	return BQUNKNOWN;
+}
+
+static char * bq2415x_detect_revision(struct bq2415x_device *bq)
+{
+	int ret = bq2415x_exec_command(bq, BQ2415X_REVISION);
+	int chip = bq2415x_detect_chip(bq);
+	if (ret < 0 || chip < 0)
+		return bq2415x_rev_name[8];
+
+	switch (chip) {
+		case BQUNKNOWN:
+			return bq2415x_rev_name[8];
+
+		case BQ24150:
+		case BQ24150A:
+		case BQ24151:
+		case BQ24151A:
+		case BQ24152:
+			if (ret >= 0 && ret <= 3)
+				return bq2415x_rev_name[ret];
+			else
+				return bq2415x_rev_name[8];
+
+		case BQ24153:
+		case BQ24153A:
+		case BQ24156:
+		case BQ24156A:
+		case BQ24158:
+			if (ret == 3)
+				return bq2415x_rev_name[0];
+			else if (ret == 1)
+				return bq2415x_rev_name[1];
+			else
+				return bq2415x_rev_name[8];
+
+		case BQ24155:
+			if (ret == 3)
+				return bq2415x_rev_name[3];
+			else
+				return bq2415x_rev_name[8];
+	}
+
+	return bq2415x_rev_name[8];
+}
+
+static int bq2415x_vender_code(struct bq2415x_device *bq)
+{
+	int ret = bq2415x_exec_command(bq, BQ2415X_VENDER_CODE);
+	if (ret < 0)
+		return 0;
+	else /* convert to binary */
+		return (ret & 0x1) + ((ret >> 1) & 0x1) * 10 + ((ret >> 2) & 0x1) * 100;
+}
+
+/* global other functions */
+
+static void bq2415x_reset_chip(struct bq2415x_device *bq)
+{
+	bq2415x_i2c_write(bq, BQ2415X_REG_CURRENT, BQ2415X_RESET_CURRENT);
+	bq2415x_i2c_write(bq, BQ2415X_REG_VOLTAGE, BQ2415X_RESET_VOLTAGE);
+	bq2415x_i2c_write(bq, BQ2415X_REG_CONTROL, BQ2415X_RESET_CONTROL);
+	bq2415x_i2c_write(bq, BQ2415X_REG_STATUS, BQ2415X_RESET_STATUS);
+}
+
+static int bq2415x_set_current_limit(struct bq2415x_device *bq, int mA)
+{
+	int val;
+	if (mA <= 100)
+		val = 0;
+	else if (mA <= 500)
+		val = 1;
+	else if (mA <= 800)
+		val = 2;
+	else
+		val = 3;
+	return bq2415x_i2c_write_mask(bq, BQ2415X_REG_CONTROL, val, BQ2415X_MASK_LIMIT, BQ2415X_SHIFT_LIMIT);
+}
+
+static int bq2415x_get_current_limit(struct bq2415x_device *bq)
+{
+	int ret = bq2415x_i2c_read_mask(bq, BQ2415X_REG_CONTROL, BQ2415X_MASK_LIMIT, BQ2415X_SHIFT_LIMIT);
+	if (ret < 0)
+		return ret;
+	else if (ret == 0)
+		return 100;
+	else if (ret == 1)
+		return 500;
+	else if (ret == 2)
+		return 800;
+	else
+		return 1800;
+}
+
+static int bq2415x_set_weak_battery_voltage(struct bq2415x_device *bq, int mV)
+{
+	int val = mV/100 + (mV%100 > 0 ? 1 : 0) - 34;
+
+	if (val < 0)
+		val = 0;
+	else if (val > 3)
+		val = 3;
+
+	return bq2415x_i2c_write_mask(bq, BQ2415X_REG_CONTROL, val, BQ2415X_MASK_VLOWV, BQ2415X_SHIFT_VLOWV);
+}
+
+static int bq2415x_get_weak_battery_voltage(struct bq2415x_device *bq)
+{
+	int ret = bq2415x_i2c_read_mask(bq, BQ2415X_REG_CONTROL, BQ2415X_MASK_VLOWV, BQ2415X_SHIFT_VLOWV);
+	if (ret < 0)
+		return ret;
+	else
+		return 100 * (34 + ret);
+}
+
+static int bq2415x_set_battery_regulation_voltage(struct bq2415x_device *bq, int mV)
+{
+	int val = (mV/10 + (mV%10 > 0 ? 1 : 0) - 350) / 2;
+
+	if (val < 0)
+		val = 0;
+	else if (val > 94) /* FIXME: Max is 94 or 122 ? */
+		return -EINVAL;
+
+	return bq2415x_i2c_write_mask(bq, BQ2415X_REG_VOLTAGE, val, BQ2415X_MASK_VO, BQ2415X_SHIFT_VO);
+}
+
+static int bq2415x_get_battery_regulation_voltage(struct bq2415x_device *bq)
+{
+	int ret = bq2415x_i2c_read_mask(bq, BQ2415X_REG_VOLTAGE, BQ2415X_MASK_VO, BQ2415X_SHIFT_VO);
+	if (ret < 0)
+		return ret;
+	else
+		return 10 * (350 + 2*ret);
+}
+
+static int bq2415x_set_charge_current_sense_voltage(struct bq2415x_device *bq, int mV)
+{
+	/* TODO */
+	return -ENOSYS;
+}
+
+static int bq2415x_get_charge_current_sense_voltage(struct bq2415x_device *bq)
+{
+	/* TODO */
+	return -ENOSYS;
+}
+
+static int bq2415x_set_termination_current_sense_voltage(struct bq2415x_device *bq, int mV)
+{
+	/* TODO */
+	return -ENOSYS;
+}
+
+static int bq2415x_get_termination_current_sense_voltage(struct bq2415x_device *bq)
+{
+	/* TODO */
+	return -ENOSYS;
+}
+
+#define bq2415x_set_default_value(bq, value) \
+	do { \
+		int ret = 0; \
+		if (bq->platform_data->value != -1) \
+			ret = bq2415x_set_##value(bq, bq->platform_data->value); \
+		if (ret < 0) \
+			return ret; \
+	} while (0)
+
+static int bq2415x_set_defaults(struct bq2415x_device *bq)
+{
+	bq2415x_exec_command(bq, BQ2415X_BOOST_MODE_DISABLE);
+	bq2415x_exec_command(bq, BQ2415X_CHARGER_DISABLE);
+	bq2415x_set_default_value(bq, current_limit);
+	bq2415x_set_default_value(bq, weak_battery_voltage);
+	bq2415x_set_default_value(bq, battery_regulation_voltage);
+	bq2415x_set_default_value(bq, charge_current_sense_voltage);
+	bq2415x_set_default_value(bq, termination_current_sense_voltage);
+	bq2415x_exec_command(bq, BQ2415X_CHARGE_CURRENT_TERMINATION_ENABLE);
+	bq2415x_exec_command(bq, BQ2415X_CHARGER_ENABLE);
+	return 0;
+}
+
+#undef bq2415x_set_default_value
+
+/* power supply */
+
+static enum power_supply_property bq2415x_power_supply_props[] = {
+	/* TODO */
+	POWER_SUPPLY_PROP_STATUS,
+	POWER_SUPPLY_PROP_MODEL_NAME,
+};
+
+static void bq2415x_power_supply_set_autotimer(struct bq2415x_device *bq, int state)
+{
+	mutex_lock(&bq2415x_timer_mutex);
+
+	if (bq->autotimer == state) {
+		mutex_unlock(&bq2415x_timer_mutex);
+		return;
+	}
+
+	bq->autotimer = state;
+
+	if (state) {
+		schedule_delayed_work(&bq->work, BQ2415X_TIMER_TIMEOUT * HZ);
+		bq2415x_exec_command(bq, BQ2415X_TIMER_RESET);
+	} else {
+		cancel_delayed_work_sync(&bq->work);
+	}
+
+	mutex_unlock(&bq2415x_timer_mutex);
+}
+
+static int bq2415x_power_supply_set_mode(struct bq2415x_device *bq, enum bq2415x_mode mode)
+{
+	int ret = 0;
+
+	switch (mode) {
+
+		case BQ2415X_MODE_NONE: /* N/A */
+		case BQ2415X_MODE_HOST_CHARGER: /* Host/HUB charger */
+		case BQ2415X_MODE_DEDICATED_CHARGER: /* Dedicated charger */
+
+			ret = bq2415x_exec_command(bq, BQ2415X_BOOST_MODE_DISABLE);
+			if (ret < 0)
+				break;
+
+			if (mode == BQ2415X_MODE_NONE) {
+				dev_info(bq->dev, "mode: N/A\n");
+				ret = bq2415x_set_current_limit(bq, 100);
+			} else if (mode == BQ2415X_MODE_HOST_CHARGER) {
+				dev_info(bq->dev, "mode: Host/HUB charger\n");
+				ret = bq2415x_set_current_limit(bq, 500);
+			} else {
+				dev_info(bq->dev, "mode: Dedicated charger\n");
+				ret = bq2415x_set_current_limit(bq, 1800);
+			}
+
+			if (ret < 0)
+				break;
+
+			ret = bq2415x_exec_command(bq, BQ2415X_CHARGER_ENABLE);
+			if (ret < 0)
+				break;
+
+			break;
+
+		case BQ2415X_MODE_BOOST: /* Boost mode */
+			dev_info(bq->dev, "mode: Boost\n");
+
+			ret = bq2415x_exec_command(bq, BQ2415X_CHARGER_DISABLE);
+			if (ret < 0)
+				break;
+
+			ret = bq2415x_set_current_limit(bq, 100);
+			if (ret < 0)
+				break;
+
+			ret = bq2415x_exec_command(bq, BQ2415X_BOOST_MODE_ENABLE);
+			if (ret < 0)
+				break;
+
+			break;
+
+	}
+
+	if (ret >= 0) {
+		bq->mode = mode;
+		ret = 0;
+	}
+
+	return ret;
+
+}
+
+static void bq2415x_power_supply_set_charger_type(int type, void *data)
+{
+	struct bq2415x_device *bq = data;
+
+	if (!bq)
+		return;
+
+	if (type == 0)
+		bq->charger_mode = BQ2415X_MODE_NONE;
+	else if (type == 1)
+		bq->charger_mode = BQ2415X_MODE_HOST_CHARGER;
+	else if (type == 2)
+		bq->charger_mode = BQ2415X_MODE_DEDICATED_CHARGER;
+	else
+		return;
+
+	if (!bq->automode)
+		return;
+
+	/* TODO: Detect USB Host mode */
+
+	bq2415x_power_supply_set_mode(bq, bq->charger_mode);
+
+}
+
+static void bq2415x_power_supply_error(struct bq2415x_device *bq, const char *msg)
+{
+	dev_err(bq->dev, "%s\n", msg);
+	bq->automode = 0;
+	bq2415x_power_supply_set_mode(bq, BQ2415X_MODE_NONE);
+	bq2415x_power_supply_set_autotimer(bq, 0);
+}
+
+static void bq2415x_power_supply_work(struct work_struct *work)
+{
+	struct bq2415x_device *bq = container_of(work, struct bq2415x_device, work.work);
+	int ret, error, boost;
+
+	if (!bq->autotimer)
+		return;
+
+	ret = bq2415x_exec_command(bq, BQ2415X_TIMER_RESET);
+	if (ret < 0) {
+		bq2415x_power_supply_error(bq, "Reseting timer failed");
+		return;
+	}
+
+	boost = bq2415x_exec_command(bq, BQ2415X_BOOST_MODE_STATUS);
+	if (boost < 0) {
+		bq2415x_power_supply_error(bq, "Unknow error");
+		return;
+	}
+
+	error = bq2415x_exec_command(bq, BQ2415X_FAULT_STATUS);
+	if (error < 0) {
+		bq2415x_power_supply_error(bq, "Unknow error");
+		return;
+	}
+
+	if (boost) {
+
+		switch (error) {
+			case 0: /* No error */
+				break;
+			case 6: /* Timer expired */
+				dev_info(bq->dev, "Timer expired\n");
+				break;
+			case 3: /* Battery voltage too low */
+				dev_info(bq->dev, "Battery voltage to low\n");
+				break;
+
+			case 1: /* Overvoltage protection (chip fried) */
+				bq2415x_power_supply_error(bq, "Overvolatge protection (chip fried)");
+				return;
+			case 2: /* Overload */
+				bq2415x_power_supply_error(bq, "Overload");
+				return;
+			case 4: /* Battery overvoltage protection */
+				bq2415x_power_supply_error(bq, "Battery overvoltage protection");
+				return;
+			case 5: /* Termal shutdown (too hot) */
+				bq2415x_power_supply_error(bq, "Termal shutdown (too hot)");
+				return;
+			case 7: /* N/A */
+				bq2415x_power_supply_error(bq, "Unknow error");
+				return;
+		}
+
+	} else {
+
+		switch (error) {
+			case 0: /* No error */
+				break;
+			case 2: /* Sleep mode */
+				dev_info(bq->dev, "Sleep mode\n");
+				break;
+			case 3: /* Poor input source */
+				dev_info(bq->dev, "Poor input source\n");
+				break;
+			case 6: /* Timer expired */
+				dev_info(bq->dev, "Timer expired\n");
+				break;
+			case 7: /* No battery */
+				dev_info(bq->dev, "No battery\n");
+				break;
+
+			case 1: /* Overvoltage protection (chip fried) */
+				bq2415x_power_supply_error(bq, "Overvolatge protection (chip fried)");
+				return;
+			case 4: /* Battery overvoltage protection */
+				bq2415x_power_supply_error(bq, "Battery overvoltage protection");
+				return;
+			case 5: /* Termal shutdown (too hot) */
+				bq2415x_power_supply_error(bq, "Termal shutdown (too hot)");
+				return;
+		}
+
+	}
+
+	schedule_delayed_work(&bq->work, BQ2415X_TIMER_TIMEOUT * HZ);
+}
+
+static int bq2415x_power_supply_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val)
+{
+	struct bq2415x_device *bq = container_of(psy, struct bq2415x_device, charger);
+	int ret;
+
+	switch (psp) {
+		case POWER_SUPPLY_PROP_STATUS:
+			ret = bq2415x_exec_command(bq, BQ2415X_CHARGE_STATUS);
+			if (ret < 0)
+				return ret;
+			else if (ret == 0) /* Ready */
+				val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+			else if (ret == 1) /* Charge in progress */
+				val->intval = POWER_SUPPLY_STATUS_CHARGING;
+			else if (ret == 2) /* Charge done */
+				val->intval = POWER_SUPPLY_STATUS_FULL;
+			else
+				val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
+			break;
+		case POWER_SUPPLY_PROP_MODEL_NAME:
+			val->strval = bq->model;
+			break;
+		default:
+			return -EINVAL;
+	}
+	return 0;
+}
+
+static int bq2415x_power_supply_init(struct bq2415x_device *bq)
+{
+	int ret;
+
+	bq->charger.name = bq->name;
+	bq->charger.type = POWER_SUPPLY_TYPE_USB;
+	bq->charger.properties = bq2415x_power_supply_props;
+	bq->charger.num_properties = ARRAY_SIZE(bq2415x_power_supply_props);
+	bq->charger.get_property = bq2415x_power_supply_get_property;
+
+	ret = bq2415x_detect_chip(bq);
+	if (ret < 0)
+		ret = BQUNKNOWN;
+
+	bq->model = kasprintf(GFP_KERNEL, "chip %s, revision %s, vender code %.3d", bq2415x_chip_name[ret], bq2415x_detect_revision(bq), bq2415x_vender_code(bq));
+	if (!bq->model) {
+		dev_err(bq->dev, "failed to allocate model name\n");
+		return -ENOMEM;
+	}
+
+	ret = power_supply_register(bq->dev, &bq->charger);
+	if (ret) {
+		kfree(bq->model);
+		return ret;
+	}
+
+	INIT_DELAYED_WORK(&bq->work, bq2415x_power_supply_work);
+
+	bq2415x_power_supply_set_autotimer(bq, 1);
+	return 0;
+}
+
+static void bq2415x_power_supply_exit(struct bq2415x_device *bq)
+{
+	bq->autotimer = 0;
+	bq->automode = 0;
+	cancel_delayed_work_sync(&bq->work);
+	power_supply_unregister(&bq->charger);
+	kfree(bq->model);
+}
+
+/* sysfs files */
+
+static ssize_t bq2415x_sysfs_show_status(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct power_supply *psy = dev_get_drvdata(dev);
+	struct bq2415x_device *bq = container_of(psy, struct bq2415x_device, charger);
+	enum bq2415x_command command;
+	int ret;
+
+	if (strcmp(attr->attr.name, "otg_status") == 0)
+		command = BQ2415X_OTG_STATUS;
+	else if (strcmp(attr->attr.name, "charge_status") == 0)
+		command = BQ2415X_CHARGE_STATUS;
+	else if (strcmp(attr->attr.name, "boost_status") == 0)
+		command = BQ2415X_BOOST_STATUS;
+	else if (strcmp(attr->attr.name, "fault_status") == 0)
+		command = BQ2415X_FAULT_STATUS;
+	else
+		return -EINVAL;
+
+	ret = bq2415x_exec_command(bq, command);
+	if (ret < 0)
+		return ret;
+	else
+		return sprintf(buf, "%d\n", ret);
+}
+
+static ssize_t bq2415x_sysfs_set_timer(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct power_supply *psy = dev_get_drvdata(dev);
+	struct bq2415x_device *bq = container_of(psy, struct bq2415x_device, charger);
+	int ret = 0;
+
+	if (strncmp(buf, "auto", 4) == 0)
+		bq2415x_power_supply_set_autotimer(bq, 1);
+	else if (strncmp(buf, "off", 3) == 0)
+		bq2415x_power_supply_set_autotimer(bq, 0);
+	else
+		ret = bq2415x_exec_command(bq, BQ2415X_TIMER_RESET);
+
+	if (ret < 0)
+		return ret;
+	else
+		return count;
+}
+
+static ssize_t bq2415x_sysfs_show_timer(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct power_supply *psy = dev_get_drvdata(dev);
+	struct bq2415x_device *bq = container_of(psy, struct bq2415x_device, charger);
+
+	if (bq->autotimer)
+		return sprintf(buf, "auto\n");
+	else
+		return sprintf(buf, "off\n");
+}
+
+static ssize_t bq2415x_sysfs_set_mode(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct power_supply *psy = dev_get_drvdata(dev);
+	struct bq2415x_device *bq = container_of(psy, struct bq2415x_device, charger);
+	enum bq2415x_mode mode;
+	int ret = 0;
+
+	dev_info(bq->dev, "Calling sysfs_set_mode: %s\n", buf);
+
+	if (strncmp(buf, "auto", 4) == 0) {
+		bq->automode = 1;
+		mode = bq->charger_mode;
+	} else if (strncmp(buf, "none", 4) == 0) {
+		bq->automode = 0;
+		mode = BQ2415X_MODE_NONE;
+	} else if (strncmp(buf, "host", 4) == 0) {
+		bq->automode = 0;
+		mode = BQ2415X_MODE_HOST_CHARGER;
+	} else if (strncmp(buf, "dedicated", 9) == 0) {
+		bq->automode = 0;
+		mode = BQ2415X_MODE_DEDICATED_CHARGER;
+	} else if (strncmp(buf, "boost", 5) == 0) {
+		bq->automode = 0;
+		mode = BQ2415X_MODE_BOOST;
+	} else if (strncmp(buf, "reset", 5) == 0) {
+		bq2415x_reset_chip(bq);
+		bq2415x_set_defaults(bq);
+		bq->automode = 1;
+		return count;
+	} else
+		return -EINVAL;
+
+	ret = bq2415x_power_supply_set_mode(bq, mode);
+	if (ret < 0)
+		return ret;
+	else
+		return count;
+}
+
+static ssize_t bq2415x_sysfs_show_mode(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct power_supply *psy = dev_get_drvdata(dev);
+	struct bq2415x_device *bq = container_of(psy, struct bq2415x_device, charger);
+	ssize_t ret = 0;
+
+	if (bq->automode)
+		ret += sprintf(buf+ret, "auto (");
+
+	switch (bq->mode) {
+		case BQ2415X_MODE_NONE:
+			ret += sprintf(buf+ret, "none");
+			break;
+		case BQ2415X_MODE_HOST_CHARGER:
+			ret += sprintf(buf+ret, "host");
+			break;
+		case BQ2415X_MODE_DEDICATED_CHARGER:
+			ret += sprintf(buf+ret, "dedicated");
+			break;
+		case BQ2415X_MODE_BOOST:
+			ret += sprintf(buf+ret, "boost");
+			break;
+	}
+
+	if (bq->automode)
+		ret += sprintf(buf+ret, ")");
+
+	ret += sprintf(buf+ret, "\n");
+	return ret;
+}
+
+static ssize_t bq2415x_sysfs_print_reg(struct bq2415x_device *bq, u8 reg, char *buf)
+{
+	int ret = bq2415x_i2c_read(bq, reg);
+	if (ret < 0)
+		return sprintf(buf, "%#.2x=error\n", reg);
+	else
+		return sprintf(buf, "%#.2x=%#.2x\n", reg, ret);
+}
+
+static ssize_t bq2415x_sysfs_show_regdump(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct power_supply *psy = dev_get_drvdata(dev);
+	struct bq2415x_device *bq = container_of(psy, struct bq2415x_device, charger);
+	ssize_t ret = 0;
+
+	ret += bq2415x_sysfs_print_reg(bq, BQ2415X_REG_STATUS, buf+ret);
+	ret += bq2415x_sysfs_print_reg(bq, BQ2415X_REG_CONTROL, buf+ret);
+	ret += bq2415x_sysfs_print_reg(bq, BQ2415X_REG_VOLTAGE, buf+ret);
+	ret += bq2415x_sysfs_print_reg(bq, BQ2415X_REG_VENDER, buf+ret);
+	ret += bq2415x_sysfs_print_reg(bq, BQ2415X_REG_CURRENT, buf+ret);
+	return ret;
+}
+
+/* Current & Volatage settings */
+
+static ssize_t bq2415x_sysfs_set_limit(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct power_supply *psy = dev_get_drvdata(dev);
+	struct bq2415x_device *bq = container_of(psy, struct bq2415x_device, charger);
+	int ret;
+	long val;
+
+	if (strict_strtol(buf, 10, &val) < 0)
+		return -EINVAL;
+
+	if (strcmp(attr->attr.name, "current_limit") == 0)
+		ret = bq2415x_set_current_limit(bq, val);
+	else if (strcmp(attr->attr.name, "weak_battery_voltage") == 0)
+		ret = bq2415x_set_weak_battery_voltage(bq, val);
+	else if (strcmp(attr->attr.name, "battery_regulation_voltage") == 0)
+		ret = bq2415x_set_battery_regulation_voltage(bq, val);
+	else if (strcmp(attr->attr.name, "charge_current_sense_voltage") == 0)
+		ret = bq2415x_set_charge_current_sense_voltage(bq, val);
+	else if (strcmp(attr->attr.name, "termination_current_sense_voltage") == 0)
+		ret = bq2415x_set_termination_current_sense_voltage(bq, val);
+	else
+		return -EINVAL;
+
+	if (ret < 0)
+		return ret;
+	else
+		return count;
+}
+
+static ssize_t bq2415x_sysfs_show_limit(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct power_supply *psy = dev_get_drvdata(dev);
+	struct bq2415x_device *bq = container_of(psy, struct bq2415x_device, charger);
+	int ret;
+
+	if (strcmp(attr->attr.name, "current_limit") == 0)
+		ret = bq2415x_get_current_limit(bq);
+	else if (strcmp(attr->attr.name, "weak_battery_voltage") == 0)
+		ret = bq2415x_get_weak_battery_voltage(bq);
+	else if (strcmp(attr->attr.name, "battery_regulation_voltage") == 0)
+		ret = bq2415x_get_battery_regulation_voltage(bq);
+	else if (strcmp(attr->attr.name, "charge_current_sense_voltage") == 0)
+		ret = bq2415x_get_charge_current_sense_voltage(bq);
+	else if (strcmp(attr->attr.name, "charge_current_sense_voltage") == 0)
+		ret = bq2415x_get_termination_current_sense_voltage(bq);
+	else
+		return -EINVAL;
+
+	if (ret < 0)
+		return ret;
+	else
+		return sprintf(buf, "%d\n", ret);
+}
+
+static ssize_t bq2415x_sysfs_set_enable(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct power_supply *psy = dev_get_drvdata(dev);
+	struct bq2415x_device *bq = container_of(psy, struct bq2415x_device, charger);
+	enum bq2415x_command command;
+	long val;
+	int ret;
+
+	if (strict_strtol(buf, 10, &val) < 0)
+		return -EINVAL;
+
+	if (strcmp(attr->attr.name, "charge_current_termination_enable") == 0)
+		command = val ? BQ2415X_CHARGE_CURRENT_TERMINATION_ENABLE : BQ2415X_CHARGE_CURRENT_TERMINATION_DISABLE;
+	else if (strcmp(attr->attr.name, "high_impedance_enable") == 0)
+		command = val ? BQ2415X_HIGH_IMPEDANCE_ENABLE : BQ2415X_HIGH_IMPEDANCE_DISABLE;
+	else if (strcmp(attr->attr.name, "otg_pin_enable") == 0)
+		command = val ? BQ2415X_OTG_PIN_ENABLE : BQ2415X_OTG_PIN_DISABLE;
+	else if (strcmp(attr->attr.name, "stat_pin_enable") == 0)
+		command = val ? BQ2415X_STAT_PIN_ENABLE : BQ2415X_STAT_PIN_DISABLE;
+	else
+		return -EINVAL;
+
+	ret = bq2415x_exec_command(bq, command);
+	if (ret < 0)
+		return ret;
+	else
+		return count;
+}
+
+static ssize_t bq2415x_sysfs_show_enable(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct power_supply *psy = dev_get_drvdata(dev);
+	struct bq2415x_device *bq = container_of(psy, struct bq2415x_device, charger);
+	enum bq2415x_command command;
+	int ret;
+
+	if (strcmp(attr->attr.name, "charge_current_termination_enable") == 0)
+		command = BQ2415X_CHARGE_CURRENT_TERMINATION_STATUS;
+	else if (strcmp(attr->attr.name, "high_impedance_enable") == 0)
+		command = BQ2415X_HIGH_IMPEDANCE_STATUS;
+	else if (strcmp(attr->attr.name, "otg_pin_enable") == 0)
+		command = BQ2415X_OTG_PIN_STATUS;
+	else if (strcmp(attr->attr.name, "stat_pin_enable") == 0)
+		command = BQ2415X_STAT_PIN_STATUS;
+	else
+		return -EINVAL;
+
+	ret = bq2415x_exec_command(bq, command);
+	if (ret < 0)
+		return ret;
+	else
+		return sprintf(buf, "%d\n", ret);
+}
+
+/* TODO: Add other sysfs entries */
+
+static DEVICE_ATTR(current_limit, S_IWUSR | S_IRUGO, bq2415x_sysfs_show_limit, bq2415x_sysfs_set_limit);
+static DEVICE_ATTR(weak_battery_voltage, S_IWUSR | S_IRUGO, bq2415x_sysfs_show_limit, bq2415x_sysfs_set_limit);
+static DEVICE_ATTR(battery_regulation_voltage, S_IWUSR | S_IRUGO, bq2415x_sysfs_show_limit, bq2415x_sysfs_set_limit);
+static DEVICE_ATTR(charge_current_sense_voltage, S_IWUSR | S_IRUGO, bq2415x_sysfs_show_limit, bq2415x_sysfs_set_limit);
+static DEVICE_ATTR(termination_current_sense_voltage, S_IWUSR | S_IRUGO, bq2415x_sysfs_show_limit, bq2415x_sysfs_set_limit);
+
+static DEVICE_ATTR(charge_current_termination_enable, S_IWUSR | S_IRUGO, bq2415x_sysfs_show_enable, bq2415x_sysfs_set_enable);
+static DEVICE_ATTR(high_impedance_enable, S_IWUSR | S_IRUGO, bq2415x_sysfs_show_enable, bq2415x_sysfs_set_enable);
+static DEVICE_ATTR(otg_pin_enable, S_IWUSR | S_IRUGO, bq2415x_sysfs_show_enable, bq2415x_sysfs_set_enable);
+static DEVICE_ATTR(stat_pin_enable, S_IWUSR | S_IRUGO, bq2415x_sysfs_show_enable, bq2415x_sysfs_set_enable);
+
+static DEVICE_ATTR(mode, S_IWUSR | S_IRUGO, bq2415x_sysfs_show_mode, bq2415x_sysfs_set_mode);
+static DEVICE_ATTR(timer, S_IWUSR | S_IRUGO, bq2415x_sysfs_show_timer, bq2415x_sysfs_set_timer);
+
+static DEVICE_ATTR(regdump, S_IRUGO, bq2415x_sysfs_show_regdump, NULL);
+
+static DEVICE_ATTR(otg_status, S_IRUGO, bq2415x_sysfs_show_status, NULL);
+static DEVICE_ATTR(charge_status, S_IRUGO, bq2415x_sysfs_show_status, NULL);
+static DEVICE_ATTR(boost_status, S_IRUGO, bq2415x_sysfs_show_status, NULL);
+static DEVICE_ATTR(fault_status, S_IRUGO, bq2415x_sysfs_show_status, NULL);
+
+static struct attribute *bq2415x_sysfs_attributes[] = {
+	&dev_attr_current_limit.attr,
+	&dev_attr_weak_battery_voltage.attr,
+	&dev_attr_battery_regulation_voltage.attr,
+	&dev_attr_charge_current_sense_voltage.attr,
+	&dev_attr_termination_current_sense_voltage.attr,
+
+	&dev_attr_charge_current_termination_enable.attr,
+	&dev_attr_high_impedance_enable.attr,
+	&dev_attr_otg_pin_enable.attr,
+	&dev_attr_stat_pin_enable.attr,
+
+	&dev_attr_mode.attr,
+	&dev_attr_timer.attr,
+
+	&dev_attr_regdump.attr,
+
+	&dev_attr_otg_status.attr,
+	&dev_attr_charge_status.attr,
+	&dev_attr_boost_status.attr,
+	&dev_attr_fault_status.attr,
+	NULL,
+};
+
+static const struct attribute_group bq2415x_sysfs_attr_group = {
+	.attrs = bq2415x_sysfs_attributes,
+};
+
+static int bq2415x_sysfs_init(struct bq2415x_device *bq)
+{
+	return sysfs_create_group(&bq->charger.dev->kobj, &bq2415x_sysfs_attr_group);
+}
+
+static void bq2415x_sysfs_exit(struct bq2415x_device *bq)
+{
+	sysfs_remove_group(&bq->charger.dev->kobj, &bq2415x_sysfs_attr_group);
+}
+
+/* bq2415x register */
+
+static int bq2415x_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+	int ret;
+	int num;
+	char *name;
+	struct bq2415x_device *bq;
+
+	if (!client->dev.platform_data) {
+		dev_err(&client->dev, "platform data not set\n");
+		return -ENODEV;
+	}
+
+	/* Get new ID for the new device */
+	ret = idr_pre_get(&bq2415x_id, GFP_KERNEL);
+	if (ret == 0)
+		return -ENOMEM;
+
+	mutex_lock(&bq2415x_id_mutex);
+	ret = idr_get_new(&bq2415x_id, client, &num);
+	mutex_unlock(&bq2415x_id_mutex);
+
+	if (ret < 0)
+		return ret;
+
+	name = kasprintf(GFP_KERNEL, "%s-%d", id->name, num);
+	if (!name) {
+		dev_err(&client->dev, "failed to allocate device name\n");
+		ret = -ENOMEM;
+		goto error_1;
+	}
+
+	bq = kzalloc(sizeof(*bq), GFP_KERNEL);
+	if (!bq) {
+		dev_err(&client->dev, "failed to allocate device data\n");
+		ret = -ENOMEM;
+		goto error_2;
+	}
+
+	i2c_set_clientdata(client, bq);
+
+	bq->id = num;
+	bq->dev = &client->dev;
+	bq->chip = id->driver_data;
+	bq->name = name;
+	bq->platform_data = client->dev.platform_data;
+	bq->mode = BQ2415X_MODE_NONE;
+	bq->charger_mode = BQ2415X_MODE_NONE;
+	bq->autotimer = 0;
+	bq->automode = 1;
+
+	bq2415x_reset_chip(bq);
+
+	ret = bq2415x_power_supply_init(bq);
+	if (ret) {
+		dev_err(bq->dev, "failed to register power supply: %d\n", ret);
+		goto error_3;
+	}
+
+	ret = bq2415x_sysfs_init(bq);
+	if (ret) {
+		dev_err(bq->dev, "failed to create sysfs entries: %d\n", ret);
+		goto error_4;
+	}
+
+	ret = bq2415x_set_defaults(bq);
+	if (ret) {
+		dev_err(bq->dev, "failed to set default values: %d\n", ret);
+		goto error_5;
+	}
+
+	if (bq->platform_data->set_charger_type_hook)
+		bq->platform_data->set_charger_type_hook(bq2415x_power_supply_set_charger_type, bq);
+	else
+		bq->automode = 0;
+
+	dev_info(bq->dev, "driver registred\n");
+	return 0;
+
+error_5:
+	bq2415x_sysfs_exit(bq);
+error_4:
+	bq2415x_power_supply_exit(bq);
+error_3:
+	kfree(bq);
+error_2:
+	kfree(name);
+error_1:
+	mutex_lock(&bq2415x_id_mutex);
+	idr_remove(&bq2415x_id, num);
+	mutex_unlock(&bq2415x_id_mutex);
+
+	return ret;
+}
+
+/* bq2415x unregister */
+
+static int bq2415x_remove(struct i2c_client *client)
+{
+	struct bq2415x_device *bq = i2c_get_clientdata(client);
+
+	if (bq->platform_data->set_charger_type_hook)
+		bq->platform_data->set_charger_type_hook(NULL, NULL);
+
+	bq2415x_sysfs_exit(bq);
+	bq2415x_power_supply_exit(bq);
+
+	bq2415x_reset_chip(bq);
+
+	mutex_lock(&bq2415x_id_mutex);
+	idr_remove(&bq2415x_id, bq->id);
+	mutex_unlock(&bq2415x_id_mutex);
+
+	dev_info(bq->dev, "driver unregistred\n");
+
+	kfree(bq->name);
+	kfree(bq);
+
+	return 0;
+}
+
+static const struct i2c_device_id bq2415x_i2c_id_table[] = {
+	{ "bq2415x", BQUNKNOWN },
+	{ "bq24150", BQ24150 },
+	{ "bq24150a", BQ24150A },
+	{ "bq24151", BQ24151 },
+	{ "bq24151a", BQ24151A },
+	{ "bq24152", BQ24152 },
+	{ "bq24153", BQ24153 },
+	{ "bq24153a", BQ24153A },
+	{ "bq24155", BQ24155 },
+	{ "bq24156", BQ24156 },
+	{ "bq24156a", BQ24156A },
+	{ "bq24158", BQ24158 },
+	{},
+};
+MODULE_DEVICE_TABLE(i2c, bq2415x_i2c_id_table);
+
+static struct i2c_driver bq2415x_driver = {
+	.driver = {
+		.name = "bq2415x-charger",
+	},
+	.probe = bq2415x_probe,
+	.remove = bq2415x_remove,
+	.id_table = bq2415x_i2c_id_table,
+};
+
+/* BEGIN: Temporary RX51 hack TODO: move to board-rx51.c */
+static void *rx51_charger_hook_data = NULL;
+static void (*rx51_charger_hook)(int type, void *data) = NULL;
+static void rx51_charger_set_hook(void *hook, void *data)
+{
+	rx51_charger_hook = hook;
+	rx51_charger_hook_data = data;
+}
+/*static void rx51_charger_set_power(bool on)
+{*/
+	/* gpio_set_value(RX51_USB_TRANSCEIVER_RST_GPIO, on); */
+/*}
+static void rx51_charger_set_type(int type)
+{
+	if (rx51_charger_hook)
+		rx51_charger_hook(type, rx51_charger_hook_data);
+}
+static struct isp1704_charger_data rx51_charger_data = {
+	.set_power	= rx51_charger_set_power,
+	.set_type	= rx51_charger_set_type,
+};
+static struct platform_device rx51_charger_device = {
+	.name	= "isp1704_charger",
+	.dev	= {
+		.platform_data = &rx51_charger_data,
+	},
+};*/
+
+static struct bq2415x_platform_data rx51_platform_data = {
+	.current_limit = 100, /* mA */
+	.weak_battery_voltage = 3400, /* mV */
+	.battery_regulation_voltage = 4200, /* mV */
+	.charge_current_sense_voltage = -1, /* TODO */
+	.termination_current_sense_voltage = -1, /* TODO */
+	.set_charger_type_hook = &rx51_charger_set_hook,
+};
+static struct i2c_board_info rx51_board_info = {
+	I2C_BOARD_INFO("bq24150", 0x6b),
+	.platform_data = &rx51_platform_data,
+};
+static struct i2c_client *client;
+/* END */
+
+static int __init bq2415x_init(void)
+{
+	/* BEGIN: Temporary RX51 hack TODO: move to board-rx51.c */
+	/*platform_device_register(&rx51_charger_device);*/
+	client = i2c_new_device(i2c_get_adapter(2), &rx51_board_info);
+	/* END */
+
+	return i2c_add_driver(&bq2415x_driver);
+}
+module_init(bq2415x_init);
+
+static void __exit bq2415x_exit(void)
+{
+	/* BEGIN: Temporary RX51 hack TODO: move to board-rx51.c */
+	/*platform_device_unregister(&rx51_charger_device);*/
+	i2c_unregister_device(client);
+	/* END */
+
+	i2c_del_driver(&bq2415x_driver);
+}
+module_exit(bq2415x_exit);
+
+MODULE_AUTHOR("Pali Rohár <pali.rohar@gmail.com>");
+MODULE_DESCRIPTION("bq2415x charger driver");
+MODULE_LICENSE("GPL");
--- /dev/null	2012-01-26 12:32:59.572022923 +0100
+++ bq2415x_charger.h	2011-12-09 22:41:06.130971667 +0100
@@ -0,0 +1,32 @@
+/*
+    bq2415x_charger.h - bq2415x charger driver
+    Copyright (C) 2011  Pali Rohár <pali.rohar@gmail.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.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#ifndef BQ2415X_CHARGER_H
+#define BQ2415X_CHARGER_H
+
+struct bq2415x_platform_data {
+	int current_limit;
+	int weak_battery_voltage;
+	int battery_regulation_voltage;
+	int charge_current_sense_voltage;
+	int termination_current_sense_voltage;
+	void (*set_charger_type_hook)(void *hook, void *data);
+};
+
+#endif

[-- Attachment #2: This is a digitally signed message part. --]
[-- Type: application/pgp-signature, Size: 198 bytes --]

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: RFC 2: bq2415x_charger driver
  2012-01-27  2:40   ` RFC 2: " Pali Rohár
@ 2012-01-27 16:24     ` Mark Brown
  2012-01-27 18:33       ` Pali Rohár
  2012-01-27 20:40     ` Sebastian Reichel
  1 sibling, 1 reply; 28+ messages in thread
From: Mark Brown @ 2012-01-27 16:24 UTC (permalink / raw)
  To: Pali Roh?r
  Cc: linux-main, linux-omap, Samuel Ortiz, Aliaksei Katovich,
	Vladimir Zapolskiy, Felipe Contreras, Anton Vorontsov,
	Joerg Reisenweber, Sebastian Reichel,
	???????????? ????????????????

On Fri, Jan 27, 2012 at 03:40:43AM +0100, Pali Roh?r wrote:

> +static char *bq2415x_rev_name[] = {
> +	"1.0",
> +	"1.1",
> +	"1.2",
> +	"1.3",
> +	"1.4",
> +	"1.5",
> +	"1.6",
> +	"1.7",

Looks like you can just store this as a number?

> +struct bq2415x_device {
> +	struct device *dev;
> +	struct bq2415x_platform_data *platform_data;

You should take a copy of the platform data so it can be marked
initdata.

> +static DEFINE_MUTEX(bq2415x_id_mutex);
> +static DEFINE_MUTEX(bq2415x_timer_mutex);
> +static DEFINE_MUTEX(bq2415x_type_mutex);
> +static DEFINE_MUTEX(bq2415x_i2c_mutex);

Why are these global?

> +/* i2c read functions */
> +
> +static int bq2415x_i2c_read(struct bq2415x_device *bq, u8 reg)
> +{
> +	struct i2c_client *client = to_i2c_client(bq->dev);

You could save a bunch of code by moving all this I2C register I/O over
to regmap.

> +		case BQ2415X_BOOST_MODE_ENABLE:
> +			return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, 1, BQ2415X_BIT_OPA_MODE);

Keep things under 80 columns - see CodingStyle.

> +	if (client->addr == 0x6b) {

> +	} else if (client->addr == 0x6a) {

This looks like a switch statement.

> +	switch (chip) {
> +		case BQUNKNOWN:
> +			return bq2415x_rev_name[8];

Magic numbers ahoy...

> +static int bq2415x_vender_code(struct bq2415x_device *bq)
> +{
> +	int ret = bq2415x_exec_command(bq, BQ2415X_VENDER_CODE);
> +	if (ret < 0)
> +		return 0;
> +	else /* convert to binary */
> +		return (ret & 0x1) + ((ret >> 1) & 0x1) * 10 + ((ret >> 2) & 0x1) * 100;

Just print it as hex?

> +static int bq2415x_set_weak_battery_voltage(struct bq2415x_device *bq, int mV)
> +{
> +	int val = mV/100 + (mV%100 > 0 ? 1 : 0) - 34;

This could probably be written more clearly?

> +static int bq2415x_get_charge_current_sense_voltage(struct bq2415x_device *bq)
> +{
> +	/* TODO */
> +	return -ENOSYS;
> +}

Just don't provide things that aren't there, the frameworks should do
appropriate handling.

> +static enum power_supply_property bq2415x_power_supply_props[] = {
> +	/* TODO */
> +	POWER_SUPPLY_PROP_STATUS,
> +	POWER_SUPPLY_PROP_MODEL_NAME,

TODO?

> +static int bq2415x_power_supply_set_mode(struct bq2415x_device *bq, enum bq2415x_mode mode)
> +{
> +	int ret = 0;
> +
> +	switch (mode) {
> +
> +		case BQ2415X_MODE_NONE: /* N/A */

CodingStyle for the indentation.

> +			if (mode == BQ2415X_MODE_NONE) {
> +				dev_info(bq->dev, "mode: N/A\n");
> +				ret = bq2415x_set_current_limit(bq, 100);
> +			} else if (mode == BQ2415X_MODE_HOST_CHARGER) {
> +				dev_info(bq->dev, "mode: Host/HUB charger\n");
> +				ret = bq2415x_set_current_limit(bq, 500);
> +			} else {
> +				dev_info(bq->dev, "mode: Dedicated charger\n");
> +				ret = bq2415x_set_current_limit(bq, 1800);
> +			}

This should be a switch statement.

> +		case BQ2415X_MODE_BOOST: /* Boost mode */
> +			dev_info(bq->dev, "mode: Boost\n");

Is dev_info() really appropriate for this stuff?

> +			break;
> +
> +	}

No default case?

> +static void bq2415x_power_supply_set_charger_type(int type, void *data)

> +	if (type == 0)
> +		bq->charger_mode = BQ2415X_MODE_NONE;
> +	else if (type == 1)
> +		bq->charger_mode = BQ2415X_MODE_HOST_CHARGER;
> +	else if (type == 2)
> +		bq->charger_mode = BQ2415X_MODE_DEDICATED_CHARGER;
> +	else
> +		return;

Switch statement.  I never understood why this is such a common idiom...

> +		switch (error) {
> +			case 0: /* No error */
> +				break;
> +			case 6: /* Timer expired */
> +				dev_info(bq->dev, "Timer expired\n");
> +				break;

Should we really be logging this at anything more than dev_dbg()?

> +			case 7: /* N/A */
> +				bq2415x_power_supply_error(bq, "Unknow error");
> +				return;

Typo: unknown.

> +			case 5: /* Termal shutdown (too hot) */
> +				bq2415x_power_supply_error(bq, "Termal shutdown (too hot)");

Typo: thermal.

> +static int bq2415x_power_supply_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val)

CodingStyle - wrapping (lots of this in the driver.

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: RFC 2: bq2415x_charger driver
  2012-01-27 16:24     ` Mark Brown
@ 2012-01-27 18:33       ` Pali Rohár
  2012-01-27 19:22         ` Mark Brown
  0 siblings, 1 reply; 28+ messages in thread
From: Pali Rohár @ 2012-01-27 18:33 UTC (permalink / raw)
  To: Mark Brown
  Cc: linux-main, linux-omap, Samuel Ortiz, Aliaksei Katovich,
	Vladimir Zapolskiy, Felipe Contreras, Anton Vorontsov,
	Joerg Reisenweber, Sebastian Reichel,
	???????????? ????????????????


[-- Attachment #1.1: Type: text/plain, Size: 5820 bytes --]

On Friday 27 January 2012 16:24:55 Mark Brown wrote:
> On Fri, Jan 27, 2012 at 03:40:43AM +0100, Pali Roh?r wrote:
> > +static char *bq2415x_rev_name[] = {
> > +	"1.0",
> > +	"1.1",
> > +	"1.2",
> > +	"1.3",
> > +	"1.4",
> > +	"1.5",
> > +	"1.6",
> > +	"1.7",
> 
> Looks like you can just store this as a number?
> 

array removed, value stored as number

> > +struct bq2415x_device {
> > +	struct device *dev;
> > +	struct bq2415x_platform_data *platform_data;
> 
> You should take a copy of the platform data so it can be marked
> initdata.

now driver copy platform_data and store it in bq2415x_device->init_data

> 
> > +static DEFINE_MUTEX(bq2415x_id_mutex);
> > +static DEFINE_MUTEX(bq2415x_timer_mutex);
> > +static DEFINE_MUTEX(bq2415x_type_mutex);
> > +static DEFINE_MUTEX(bq2415x_i2c_mutex);
> 
> Why are these global?

type_mutex was removed
id_mutex is needed for incrementing chip id (driver support more bq devices)
timer_mutex is used for periodicaly (every 10s) reseting chip timer
i2c_mutex is for locking i2c read/write functions in bq driver

> 
> > +/* i2c read functions */
> > +
> > +static int bq2415x_i2c_read(struct bq2415x_device *bq, u8 reg)
> > +{
> > +	struct i2c_client *client = to_i2c_client(bq->dev);
> 
> You could save a bunch of code by moving all this I2C register I/O over
> to regmap.

regmap is not available in 2.6.28, so I cannot use it.

> 
> > +		case BQ2415X_BOOST_MODE_ENABLE:
> > +			return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, 1,
> > BQ2415X_BIT_OPA_MODE);
> Keep things under 80 columns - see CodingStyle.

Fixed in whole file.

> 
> > +	if (client->addr == 0x6b) {
> > 
> > +	} else if (client->addr == 0x6a) {
> 
> This looks like a switch statement.

Fixed.

> 
> > +	switch (chip) {
> > +		case BQUNKNOWN:
> > +			return bq2415x_rev_name[8];
> 
> Magic numbers ahoy...
> 
> > +static int bq2415x_vender_code(struct bq2415x_device *bq)
> > +{
> > +	int ret = bq2415x_exec_command(bq, BQ2415X_VENDER_CODE);
> > +	if (ret < 0)
> > +		return 0;
> > +	else /* convert to binary */
> > +		return (ret & 0x1) + ((ret >> 1) & 0x1) * 10 + ((ret >> 2) & 0x1) *
> > 100;
> 
> Just print it as hex?

In datasheet is in binary and number has only 3 bits.

> 
> > +static int bq2415x_set_weak_battery_voltage(struct bq2415x_device *bq,
> > int mV) +{
> > +	int val = mV/100 + (mV%100 > 0 ? 1 : 0) - 34;
> 
> This could probably be written more clearly?

How?

> 
> > +static int bq2415x_get_charge_current_sense_voltage(struct bq2415x_device
> > *bq) +{
> > +	/* TODO */
> > +	return -ENOSYS;
> > +}
> 
> Just don't provide things that aren't there, the frameworks should do
> appropriate handling.

This is RFC driver and not all options was implemened yet. But new version has 
this now implemented.

> 
> > +static enum power_supply_property bq2415x_power_supply_props[] = {
> > +	/* TODO */
> > +	POWER_SUPPLY_PROP_STATUS,
> > +	POWER_SUPPLY_PROP_MODEL_NAME,
> 
> TODO?

No idea if power_supply needs more properties (I added info to TODO comment).

> 
> > +static int bq2415x_power_supply_set_mode(struct bq2415x_device *bq, enum
> > bq2415x_mode mode) +{
> > +	int ret = 0;
> > +
> > +	switch (mode) {
> > +
> > +		case BQ2415X_MODE_NONE: /* N/A */
> 
> CodingStyle for the indentation.

Fixed.

> 
> > +			if (mode == BQ2415X_MODE_NONE) {
> > +				dev_info(bq->dev, "mode: N/A\n");
> > +				ret = bq2415x_set_current_limit(bq, 100);
> > +			} else if (mode == BQ2415X_MODE_HOST_CHARGER) {
> > +				dev_info(bq->dev, "mode: Host/HUB charger\n");
> > +				ret = bq2415x_set_current_limit(bq, 500);
> > +			} else {
> > +				dev_info(bq->dev, "mode: Dedicated charger\n");
> > +				ret = bq2415x_set_current_limit(bq, 1800);
> > +			}
> 
> This should be a switch statement.

Changed to switch.

> 
> > +		case BQ2415X_MODE_BOOST: /* Boost mode */
> > +			dev_info(bq->dev, "mode: Boost\n");
> 
> Is dev_info() really appropriate for this stuff?
> 

How can driver write info that user (or other driver) changed chip mode?
This should be reported in dmesg!

> > +			break;
> > +
> > +	}
> 
> No default case?

Added -EINVAL.

> 
> > +static void bq2415x_power_supply_set_charger_type(int type, void *data)
> > 
> > +	if (type == 0)
> > +		bq->charger_mode = BQ2415X_MODE_NONE;
> > +	else if (type == 1)
> > +		bq->charger_mode = BQ2415X_MODE_HOST_CHARGER;
> > +	else if (type == 2)
> > +		bq->charger_mode = BQ2415X_MODE_DEDICATED_CHARGER;
> > +	else
> > +		return;
> 
> Switch statement.  I never understood why this is such a common idiom...
> 
> > +		switch (error) {
> > +			case 0: /* No error */
> > +				break;
> > +			case 6: /* Timer expired */
> > +				dev_info(bq->dev, "Timer expired\n");
> > +				break;
> 
> Should we really be logging this at anything more than dev_dbg()?

Changed to dev_err(). This is error message and should be visible.

> 
> > +			case 7: /* N/A */
> > +				bq2415x_power_supply_error(bq, "Unknow error");
> > +				return;
> 
> Typo: unknown.

Fixed

> 
> > +			case 5: /* Termal shutdown (too hot) */
> > +				bq2415x_power_supply_error(bq, "Termal shutdown (too 
hot)");
> 
> Typo: thermal.

Fixed

> 
> > +static int bq2415x_power_supply_get_property(struct power_supply *psy,
> > enum power_supply_property psp, union power_supply_propval *val)
> CodingStyle - wrapping (lots of this in the driver.

Wrapping and switches are fixed. Now I'm sending fixed version. This version 
has now implemented configuration for charge_current and termination_current.

PS: Ignore code at end of file - it is platform code for nokia n900. It will 
be moved to rx51 board section.

-- 
Pali Rohár
pali.rohar@gmail.com

[-- Attachment #1.2: bq2415x_charger.patch --]
[-- Type: text/x-patch, Size: 44154 bytes --]

--- /dev/null	2012-01-27 11:15:13.971717681 +0100
+++ bq2415x_charger.c	2012-01-27 19:14:36.301222584 +0100
@@ -0,0 +1,1633 @@
+/*
+    bq2415x_charger.c - bq2415x charger driver
+    Copyright (C) 2011-2012  Pali Rohár <pali.rohar@gmail.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.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+/*
+  Datasheets:
+  http://www.ti.com/product/bq24150
+  http://www.ti.com/product/bq24150a
+  http://www.ti.com/product/bq24152
+  http://www.ti.com/product/bq24153
+  http://www.ti.com/product/bq24153a
+  http://www.ti.com/product/bq24155
+*/
+
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/param.h>
+#include <linux/err.h>
+#include <linux/workqueue.h>
+#include <linux/sysfs.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/idr.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+
+#include "bq2415x_charger.h"
+
+#define BQ2415X_TIMER_TIMEOUT	10
+
+#define BQ2415X_REG_STATUS		0x00
+#define BQ2415X_REG_CONTROL		0x01
+#define BQ2415X_REG_VOLTAGE		0x02
+#define BQ2415X_REG_VENDER		0x03
+#define BQ2415X_REG_CURRENT		0x04
+
+/* reset state for all registers */
+#define BQ2415X_RESET_STATUS		BIT(6)
+#define BQ2415X_RESET_CONTROL		(BIT(4)|BIT(5))
+#define BQ2415X_RESET_VOLTAGE		(BIT(1)|BIT(3))
+#define BQ2415X_RESET_CURRENT		(BIT(0)|BIT(3)|BIT(7))
+
+/* status register */
+#define BQ2415X_BIT_TMR_RST		7
+#define BQ2415X_BIT_OTG			7
+#define BQ2415X_BIT_EN_STAT		6
+#define BQ2415X_MASK_STAT		(BIT(4)|BIT(5))
+#define BQ2415X_SHIFT_STAT		4
+#define BQ2415X_BIT_BOOST		3
+#define BQ2415X_MASK_FAULT		(BIT(0)|BIT(1)|BIT(2))
+#define BQ2415X_SHIFT_FAULT		0
+
+/* control register */
+#define BQ2415X_MASK_LIMIT		(BIT(6)|BIT(7))
+#define BQ2415X_SHIFT_LIMIT		6
+#define BQ2415X_MASK_VLOWV		(BIT(4)|BIT(5))
+#define BQ2415X_SHIFT_VLOWV		4
+#define BQ2415X_BIT_TE			3
+#define BQ2415X_BIT_CE			2
+#define BQ2415X_BIT_HZ_MODE		1
+#define BQ2415X_BIT_OPA_MODE		0
+
+/* voltage register */
+#define BQ2415X_MASK_VO		(BIT(2)|BIT(3)|BIT(4)|BIT(5)|BIT(6)|BIT(7))
+#define BQ2415X_SHIFT_VO		2
+#define BQ2415X_BIT_OTG_PL		1
+#define BQ2415X_BIT_OTG_EN		0
+
+/* vender register */
+#define BQ2415X_MASK_VENDER		(BIT(5)|BIT(6)|BIT(7))
+#define BQ2415X_SHIFT_VENDER		5
+#define BQ2415X_MASK_PN			(BIT(3)|BIT(4))
+#define BQ2415X_SHIFT_PN		3
+#define BQ2415X_MASK_REVISION		(BIT(0)|BIT(1)|BIT(2))
+#define BQ2415X_SHIFT_REVISION		0
+
+/* current register */
+/* RESET				BIT(7) */
+#define BQ2415X_MASK_VI_CHRG		(BIT(4)|BIT(5)|BIT(6))
+#define BQ2415X_SHIFT_VI_CHRG		4
+/* N/A					BIT(3) */
+#define BQ2415X_MASK_VI_TERM		(BIT(0)|BIT(1)|BIT(2))
+#define BQ2415X_SHIFT_VI_TERM		0
+
+
+enum bq2415x_command {
+	BQ2415X_TIMER_RESET,
+	BQ2415X_OTG_STATUS,
+	BQ2415X_STAT_PIN_STATUS,
+	BQ2415X_STAT_PIN_ENABLE,
+	BQ2415X_STAT_PIN_DISABLE,
+	BQ2415X_CHARGE_STATUS,
+	BQ2415X_BOOST_STATUS,
+	BQ2415X_FAULT_STATUS,
+
+	BQ2415X_CHARGE_TERMINATION_STATUS,
+	BQ2415X_CHARGE_TERMINATION_ENABLE,
+	BQ2415X_CHARGE_TERMINATION_DISABLE,
+	BQ2415X_CHARGER_STATUS,
+	BQ2415X_CHARGER_ENABLE,
+	BQ2415X_CHARGER_DISABLE,
+	BQ2415X_HIGH_IMPEDANCE_STATUS,
+	BQ2415X_HIGH_IMPEDANCE_ENABLE,
+	BQ2415X_HIGH_IMPEDANCE_DISABLE,
+	BQ2415X_BOOST_MODE_STATUS,
+	BQ2415X_BOOST_MODE_ENABLE,
+	BQ2415X_BOOST_MODE_DISABLE,
+
+	BQ2415X_OTG_LEVEL,
+	BQ2415X_OTG_ACTIVATE_HIGH,
+	BQ2415X_OTG_ACTIVATE_LOW,
+	BQ2415X_OTG_PIN_STATUS,
+	BQ2415X_OTG_PIN_ENABLE,
+	BQ2415X_OTG_PIN_DISABLE,
+
+	BQ2415X_VENDER_CODE,
+	BQ2415X_PART_NUMBER,
+	BQ2415X_REVISION,
+};
+
+enum bq2415x_chip {
+	BQUNKNOWN,
+	BQ24150,
+	BQ24150A,
+	BQ24151,
+	BQ24151A,
+	BQ24152,
+	BQ24153,
+	BQ24153A,
+	BQ24155,
+	BQ24156,
+	BQ24156A,
+	BQ24158,
+};
+
+enum bq2415x_mode {
+	BQ2415X_MODE_NONE,
+	BQ2415X_MODE_HOST_CHARGER,
+	BQ2415X_MODE_DEDICATED_CHARGER,
+	BQ2415X_MODE_BOOST,
+};
+
+static char *bq2415x_chip_name[] = {
+	"unknown",
+	"bq24150",
+	"bq24150a",
+	"bq24151",
+	"bq24151a",
+	"bq24152",
+	"bq24153",
+	"bq24153a",
+	"bq24155",
+	"bq24156",
+	"bq24156a",
+	"bq24158",
+};
+
+struct bq2415x_device {
+	struct device *dev;
+	struct bq2415x_platform_data init_data;
+	struct power_supply charger;
+	struct delayed_work work;
+	enum bq2415x_mode charger_mode;	/* mode reported by hook function */
+	enum bq2415x_mode mode;		/* actual setted mode */
+	enum bq2415x_chip chip;
+	char *model;
+	char *name;
+	int autotimer;	/* 1 - if driver automatically reset timer, 0 - not */
+	int automode;	/* 1 - enabled, 0 - disabled; -1 - not supported */
+	int id;
+};
+
+static DEFINE_IDR(bq2415x_id);
+
+static DEFINE_MUTEX(bq2415x_id_mutex);
+static DEFINE_MUTEX(bq2415x_timer_mutex);
+static DEFINE_MUTEX(bq2415x_i2c_mutex);
+
+/* i2c read functions */
+
+static int bq2415x_i2c_read(struct bq2415x_device *bq, u8 reg)
+{
+	struct i2c_client *client = to_i2c_client(bq->dev);
+	struct i2c_msg msg[2];
+	u8 val;
+	int ret;
+
+	if (!client->adapter)
+		return -ENODEV;
+
+	msg[0].addr = client->addr;
+	msg[0].flags = 0;
+	msg[0].buf = &reg;
+	msg[0].len = sizeof(reg);
+	msg[1].addr = client->addr;
+	msg[1].flags = I2C_M_RD;
+	msg[1].buf = &val;
+	msg[1].len = sizeof(val);
+
+	mutex_lock(&bq2415x_i2c_mutex);
+	ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
+	mutex_unlock(&bq2415x_i2c_mutex);
+
+	if (ret < 0)
+		return ret;
+
+	return val;
+}
+
+static int bq2415x_i2c_read_mask(struct bq2415x_device *bq, u8 reg,
+				u8 mask, u8 shift)
+{
+	int ret;
+
+	if (shift > 8)
+		return -EINVAL;
+
+	ret = bq2415x_i2c_read(bq, reg);
+	if (ret < 0)
+		return ret;
+	else
+		return (ret & mask) >> shift;
+}
+
+static int bq2415x_i2c_read_bit(struct bq2415x_device *bq, u8 reg, u8 bit)
+{
+	if (bit > 8)
+		return -EINVAL;
+	else
+		return bq2415x_i2c_read_mask(bq, reg, BIT(bit), bit);
+}
+
+/* i2c write functions */
+
+static int bq2415x_i2c_write(struct bq2415x_device *bq, u8 reg, u8 val)
+{
+	struct i2c_client *client = to_i2c_client(bq->dev);
+	struct i2c_msg msg[1];
+	u8 data[2];
+	int ret;
+
+	data[0] = reg;
+	data[1] = val;
+
+	msg[0].addr = client->addr;
+	msg[0].flags = 0;
+	msg[0].buf = data;
+	msg[0].len = ARRAY_SIZE(data);
+
+	mutex_lock(&bq2415x_i2c_mutex);
+	ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
+	mutex_unlock(&bq2415x_i2c_mutex);
+
+	/* i2c_transfer returns number of messages transferred */
+	if (ret < 0)
+		return ret;
+	else if (ret != 1)
+		return -EIO;
+
+	return 0;
+}
+
+static int bq2415x_i2c_write_mask(struct bq2415x_device *bq, u8 reg, u8 val,
+				u8 mask, u8 shift)
+{
+	int ret;
+
+	if (shift > 8)
+		return -EINVAL;
+
+	ret = bq2415x_i2c_read(bq, reg);
+	if (ret < 0)
+		return ret;
+
+	ret &= ~mask;
+	ret |= val << shift;
+
+	return bq2415x_i2c_write(bq, reg, ret);
+}
+
+static int bq2415x_i2c_write_bit(struct bq2415x_device *bq, u8 reg,
+				bool val, u8 bit)
+{
+	if (bit > 8)
+		return -EINVAL;
+	else
+		return bq2415x_i2c_write_mask(bq, reg, val, BIT(bit), bit);
+}
+
+/* global exec command function */
+
+static int bq2415x_exec_command(struct bq2415x_device *bq,
+				enum bq2415x_command command)
+{
+	int ret;
+	switch (command) {
+	case BQ2415X_TIMER_RESET:
+		return bq2415x_i2c_write_bit(bq, BQ2415X_REG_STATUS,
+				1, BQ2415X_BIT_TMR_RST);
+	case BQ2415X_OTG_STATUS:
+		return bq2415x_i2c_read_bit(bq, BQ2415X_REG_STATUS,
+				BQ2415X_BIT_OTG);
+	case BQ2415X_STAT_PIN_STATUS:
+		return bq2415x_i2c_read_bit(bq, BQ2415X_REG_STATUS,
+				BQ2415X_BIT_EN_STAT);
+	case BQ2415X_STAT_PIN_ENABLE:
+		return bq2415x_i2c_write_bit(bq, BQ2415X_REG_STATUS, 1,
+				BQ2415X_BIT_EN_STAT);
+	case BQ2415X_STAT_PIN_DISABLE:
+		return bq2415x_i2c_write_bit(bq, BQ2415X_REG_STATUS, 0,
+				BQ2415X_BIT_EN_STAT);
+	case BQ2415X_CHARGE_STATUS:
+		return bq2415x_i2c_read_mask(bq, BQ2415X_REG_STATUS,
+				BQ2415X_MASK_STAT, BQ2415X_SHIFT_STAT);
+	case BQ2415X_BOOST_STATUS:
+		return bq2415x_i2c_read_bit(bq, BQ2415X_REG_STATUS,
+				BQ2415X_BIT_BOOST);
+	case BQ2415X_FAULT_STATUS:
+		return bq2415x_i2c_read_mask(bq, BQ2415X_REG_STATUS,
+			BQ2415X_MASK_FAULT, BQ2415X_SHIFT_FAULT);
+
+	case BQ2415X_CHARGE_TERMINATION_STATUS:
+		return bq2415x_i2c_read_bit(bq, BQ2415X_REG_CONTROL,
+				BQ2415X_BIT_TE);
+	case BQ2415X_CHARGE_TERMINATION_ENABLE:
+		return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL,
+				1, BQ2415X_BIT_TE);
+	case BQ2415X_CHARGE_TERMINATION_DISABLE:
+		return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL,
+				0, BQ2415X_BIT_TE);
+	case BQ2415X_CHARGER_STATUS:
+		ret = bq2415x_i2c_read_bit(bq, BQ2415X_REG_CONTROL,
+			BQ2415X_BIT_CE);
+		if (ret < 0)
+			return ret;
+		else
+			return ret > 0 ? 0 : 1;
+	case BQ2415X_CHARGER_ENABLE:
+		return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL,
+				0, BQ2415X_BIT_CE);
+	case BQ2415X_CHARGER_DISABLE:
+		return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL,
+				1, BQ2415X_BIT_CE);
+	case BQ2415X_HIGH_IMPEDANCE_STATUS:
+		return bq2415x_i2c_read_bit(bq, BQ2415X_REG_CONTROL,
+				BQ2415X_BIT_HZ_MODE);
+	case BQ2415X_HIGH_IMPEDANCE_ENABLE:
+		return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL,
+				1, BQ2415X_BIT_HZ_MODE);
+	case BQ2415X_HIGH_IMPEDANCE_DISABLE:
+		return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL,
+				0, BQ2415X_BIT_HZ_MODE);
+	case BQ2415X_BOOST_MODE_STATUS:
+		return bq2415x_i2c_read_bit(bq, BQ2415X_REG_CONTROL,
+				BQ2415X_BIT_OPA_MODE);
+	case BQ2415X_BOOST_MODE_ENABLE:
+		return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL,
+				1, BQ2415X_BIT_OPA_MODE);
+	case BQ2415X_BOOST_MODE_DISABLE:
+		return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL,
+				0, BQ2415X_BIT_OPA_MODE);
+
+	case BQ2415X_OTG_LEVEL:
+		return bq2415x_i2c_read_bit(bq, BQ2415X_REG_VOLTAGE,
+				BQ2415X_BIT_OTG_PL);
+	case BQ2415X_OTG_ACTIVATE_HIGH:
+		return bq2415x_i2c_write_bit(bq, BQ2415X_REG_VOLTAGE,
+				1, BQ2415X_BIT_OTG_PL);
+	case BQ2415X_OTG_ACTIVATE_LOW:
+		return bq2415x_i2c_write_bit(bq, BQ2415X_REG_VOLTAGE,
+				0, BQ2415X_BIT_OTG_PL);
+	case BQ2415X_OTG_PIN_STATUS:
+		return bq2415x_i2c_read_bit(bq, BQ2415X_REG_VOLTAGE,
+				BQ2415X_BIT_OTG_EN);
+	case BQ2415X_OTG_PIN_ENABLE:
+		return bq2415x_i2c_write_bit(bq, BQ2415X_REG_VOLTAGE,
+				1, BQ2415X_BIT_OTG_EN);
+	case BQ2415X_OTG_PIN_DISABLE:
+		return bq2415x_i2c_write_bit(bq, BQ2415X_REG_VOLTAGE,
+				0, BQ2415X_BIT_OTG_EN);
+
+	case BQ2415X_VENDER_CODE:
+		return bq2415x_i2c_read_mask(bq, BQ2415X_REG_VENDER,
+			BQ2415X_MASK_VENDER, BQ2415X_SHIFT_VENDER);
+	case BQ2415X_PART_NUMBER:
+		return bq2415x_i2c_read_mask(bq, BQ2415X_REG_VENDER,
+				BQ2415X_MASK_PN, BQ2415X_SHIFT_PN);
+	case BQ2415X_REVISION:
+		return bq2415x_i2c_read_mask(bq, BQ2415X_REG_VENDER,
+			BQ2415X_MASK_REVISION, BQ2415X_SHIFT_REVISION);
+
+	default:
+		return -EINVAL;
+	}
+}
+
+/* global detect chip */
+
+static enum bq2415x_chip bq2415x_detect_chip(struct bq2415x_device *bq)
+{
+	struct i2c_client *client = to_i2c_client(bq->dev);
+	int ret = bq2415x_exec_command(bq, BQ2415X_PART_NUMBER);
+
+	if (ret < 0)
+		return ret;
+
+	switch (client->addr) {
+	case 0x6b:
+		switch (ret) {
+		case 0:
+			if (bq->chip == BQ24151A)
+				return bq->chip;
+			else
+				return BQ24151;
+		case 1:
+			if (bq->chip == BQ24150A ||
+				bq->chip == BQ24152 ||
+				bq->chip == BQ24155)
+				return bq->chip;
+			else
+				return BQ24150;
+		case 2:
+			if (bq->chip == BQ24153A)
+				return bq->chip;
+			else
+				return BQ24153;
+		default:
+			return BQUNKNOWN;
+		}
+		break;
+
+	case 0x6a:
+		switch (ret) {
+		case 0:
+			if (bq->chip == BQ24156A)
+				return bq->chip;
+			else
+				return BQ24156;
+		case 2:
+			return BQ24158;
+		default:
+			return BQUNKNOWN;
+		}
+		break;
+	}
+
+	return BQUNKNOWN;
+}
+
+static int bq2415x_detect_revision(struct bq2415x_device *bq)
+{
+	int ret = bq2415x_exec_command(bq, BQ2415X_REVISION);
+	int chip = bq2415x_detect_chip(bq);
+	if (ret < 0 || chip < 0)
+		return -1;
+
+	switch (chip) {
+	case BQ24150:
+	case BQ24150A:
+	case BQ24151:
+	case BQ24151A:
+	case BQ24152:
+		if (ret >= 0 && ret <= 3)
+			return ret;
+		else
+			return -1;
+
+	case BQ24153:
+	case BQ24153A:
+	case BQ24156:
+	case BQ24156A:
+	case BQ24158:
+		if (ret == 3)
+			return 0;
+		else if (ret == 1)
+			return 1;
+		else
+			return -1;
+
+	case BQ24155:
+		if (ret == 3)
+			return 3;
+		else
+			return -1;
+
+	case BQUNKNOWN:
+		return -1;
+	}
+
+	return -1;
+}
+
+static int bq2415x_get_vender_code(struct bq2415x_device *bq)
+{
+	int ret = bq2415x_exec_command(bq, BQ2415X_VENDER_CODE);
+	if (ret < 0)
+		return 0;
+	else /* convert to binary */
+		return (ret & 0x1) +
+			((ret >> 1) & 0x1) * 10 +
+			((ret >> 2) & 0x1) * 100;
+}
+
+/* global other functions */
+
+static void bq2415x_reset_chip(struct bq2415x_device *bq)
+{
+	bq2415x_i2c_write(bq, BQ2415X_REG_CURRENT, BQ2415X_RESET_CURRENT);
+	bq2415x_i2c_write(bq, BQ2415X_REG_VOLTAGE, BQ2415X_RESET_VOLTAGE);
+	bq2415x_i2c_write(bq, BQ2415X_REG_CONTROL, BQ2415X_RESET_CONTROL);
+	bq2415x_i2c_write(bq, BQ2415X_REG_STATUS, BQ2415X_RESET_STATUS);
+}
+
+static int bq2415x_set_current_limit(struct bq2415x_device *bq, int mA)
+{
+	int val;
+	if (mA <= 100)
+		val = 0;
+	else if (mA <= 500)
+		val = 1;
+	else if (mA <= 800)
+		val = 2;
+	else
+		val = 3;
+	return bq2415x_i2c_write_mask(bq, BQ2415X_REG_CONTROL, val,
+			BQ2415X_MASK_LIMIT, BQ2415X_SHIFT_LIMIT);
+}
+
+static int bq2415x_get_current_limit(struct bq2415x_device *bq)
+{
+	int ret = bq2415x_i2c_read_mask(bq, BQ2415X_REG_CONTROL,
+			BQ2415X_MASK_LIMIT, BQ2415X_SHIFT_LIMIT);
+	if (ret < 0)
+		return ret;
+	else if (ret == 0)
+		return 100;
+	else if (ret == 1)
+		return 500;
+	else if (ret == 2)
+		return 800;
+	else if (ret == 3)
+		return 1800;
+	else
+		return -EINVAL;
+}
+
+static int bq2415x_set_weak_battery_voltage(struct bq2415x_device *bq, int mV)
+{
+	int val = mV/100 + (mV%100 > 0 ? 1 : 0) - 34;
+
+	if (val < 0)
+		val = 0;
+	else if (val > 3)
+		val = 3;
+
+	return bq2415x_i2c_write_mask(bq, BQ2415X_REG_CONTROL, val,
+			BQ2415X_MASK_VLOWV, BQ2415X_SHIFT_VLOWV);
+}
+
+static int bq2415x_get_weak_battery_voltage(struct bq2415x_device *bq)
+{
+	int ret = bq2415x_i2c_read_mask(bq, BQ2415X_REG_CONTROL,
+			BQ2415X_MASK_VLOWV, BQ2415X_SHIFT_VLOWV);
+	if (ret < 0)
+		return ret;
+	else
+		return 100 * (34 + ret);
+}
+
+static int bq2415x_set_battery_regulation_voltage(struct bq2415x_device *bq,
+						int mV)
+{
+	int val = (mV/10 + (mV%10 > 0 ? 1 : 0) - 350) / 2;
+
+	if (val < 0)
+		val = 0;
+	else if (val > 94) /* FIXME: Max is 94 or 122 ? */
+		return -EINVAL;
+
+	return bq2415x_i2c_write_mask(bq, BQ2415X_REG_VOLTAGE, val,
+			BQ2415X_MASK_VO, BQ2415X_SHIFT_VO);
+}
+
+static int bq2415x_get_battery_regulation_voltage(struct bq2415x_device *bq)
+{
+	int ret = bq2415x_i2c_read_mask(bq, BQ2415X_REG_VOLTAGE,
+			BQ2415X_MASK_VO, BQ2415X_SHIFT_VO);
+	if (ret < 0)
+		return ret;
+	else
+		return 10 * (350 + 2*ret);
+}
+
+static int bq2415x_set_charge_current(struct bq2415x_device *bq, int mA)
+{
+	int val;
+	if (bq->init_data.resistor_sense <= 0)
+		return -ENOSYS;
+
+	val = (mA * bq->init_data.resistor_sense - 37400);
+	val = val/6800 + (val%6800 > 0 ? 1 : 0);
+
+	if (val < 0)
+		val = 0;
+	else if (val > 7)
+		val = 7;
+
+	return bq2415x_i2c_write_mask(bq, BQ2415X_REG_CURRENT, val,
+			BQ2415X_MASK_VI_CHRG, BQ2415X_SHIFT_VI_CHRG);
+}
+
+static int bq2415x_get_charge_current(struct bq2415x_device *bq)
+{
+	int ret;
+	if (bq->init_data.resistor_sense <= 0)
+		return -ENOSYS;
+
+	ret = bq2415x_i2c_read_mask(bq, BQ2415X_REG_CURRENT,
+			BQ2415X_MASK_VI_CHRG, BQ2415X_SHIFT_VI_CHRG);
+	if (ret < 0)
+		return ret;
+	else
+		return (37400 + 6800*ret) / bq->init_data.resistor_sense;
+}
+
+static int bq2415x_set_termination_current(struct bq2415x_device *bq, int mA)
+{
+	int val;
+	if (bq->init_data.resistor_sense <= 0)
+		return -ENOSYS;
+
+	val = (mA * bq->init_data.resistor_sense - 3400);
+	val = val/3400 + (val%3400 > 0 ? 1 : 0);
+
+	if (val < 0)
+		val = 0;
+	else if (val > 7)
+		val = 7;
+
+	return bq2415x_i2c_write_mask(bq, BQ2415X_REG_CURRENT, val,
+			BQ2415X_MASK_VI_TERM, BQ2415X_SHIFT_VI_TERM);
+}
+
+static int bq2415x_get_termination_current(struct bq2415x_device *bq)
+{
+	int ret;
+	if (bq->init_data.resistor_sense <= 0)
+		return -ENOSYS;
+
+	ret = bq2415x_i2c_read_mask(bq, BQ2415X_REG_CURRENT,
+			BQ2415X_MASK_VI_TERM, BQ2415X_SHIFT_VI_TERM);
+	if (ret < 0)
+		return ret;
+	else
+		return (3400 + 3400*ret) / bq->init_data.resistor_sense;
+}
+
+#define bq2415x_set_default_value(bq, value) \
+	do { \
+		int ret = 0; \
+		if (bq->init_data.value != -1) \
+			ret = bq2415x_set_##value(bq, bq->init_data.value); \
+		if (ret < 0) \
+			return ret; \
+	} while (0)
+
+static int bq2415x_set_defaults(struct bq2415x_device *bq)
+{
+	bq2415x_exec_command(bq, BQ2415X_BOOST_MODE_DISABLE);
+	bq2415x_exec_command(bq, BQ2415X_CHARGER_DISABLE);
+	bq2415x_set_default_value(bq, current_limit);
+	bq2415x_set_default_value(bq, weak_battery_voltage);
+	bq2415x_set_default_value(bq, battery_regulation_voltage);
+	if (bq->init_data.resistor_sense > 0) {
+		bq2415x_set_default_value(bq, charge_current);
+		bq2415x_set_default_value(bq, termination_current);
+		bq2415x_exec_command(bq, BQ2415X_CHARGE_TERMINATION_ENABLE);
+	}
+	bq2415x_exec_command(bq, BQ2415X_CHARGER_ENABLE);
+	return 0;
+}
+
+#undef bq2415x_set_default_value
+
+/* charger mode functions */
+
+static int bq2415x_set_mode(struct bq2415x_device *bq, enum bq2415x_mode mode)
+{
+	int ret = 0;
+	int charger = 0;
+
+	if (mode == BQ2415X_MODE_NONE ||
+		mode == BQ2415X_MODE_HOST_CHARGER ||
+		mode == BQ2415X_MODE_DEDICATED_CHARGER)
+			charger = 1;
+
+	if (charger)
+		ret = bq2415x_exec_command(bq, BQ2415X_BOOST_MODE_DISABLE);
+	else
+		ret = bq2415x_exec_command(bq, BQ2415X_CHARGER_DISABLE);
+
+	if (ret < 0)
+		return ret;
+
+	switch (mode) {
+	case BQ2415X_MODE_NONE:
+		dev_info(bq->dev, "mode: N/A\n");
+		ret = bq2415x_set_current_limit(bq, 100);
+		break;
+	case BQ2415X_MODE_HOST_CHARGER:
+		dev_info(bq->dev, "mode: Host/HUB charger\n");
+		ret = bq2415x_set_current_limit(bq, 500);
+		break;
+	case BQ2415X_MODE_DEDICATED_CHARGER:
+		dev_info(bq->dev, "mode: Dedicated charger\n");
+		ret = bq2415x_set_current_limit(bq, 1800);
+		break;
+	case BQ2415X_MODE_BOOST: /* Boost mode */
+		dev_info(bq->dev, "mode: Boost\n");
+		ret = bq2415x_set_current_limit(bq, 100);
+		break;
+	}
+
+	if (ret < 0)
+		return ret;
+
+	if (charger)
+		ret = bq2415x_exec_command(bq, BQ2415X_CHARGER_ENABLE);
+	else
+		ret = bq2415x_exec_command(bq, BQ2415X_BOOST_MODE_ENABLE);
+
+	if (ret < 0)
+		return ret;
+
+	bq->mode = mode;
+	return 0;
+
+}
+
+static void bq2415x_set_charger_type(int type, void *data)
+{
+	struct bq2415x_device *bq = data;
+
+	if (!bq)
+		return;
+
+	switch (type) {
+	case 0:
+		bq->charger_mode = BQ2415X_MODE_NONE;
+		break;
+	case 1:
+		bq->charger_mode = BQ2415X_MODE_HOST_CHARGER;
+		break;
+	case 2:
+		bq->charger_mode = BQ2415X_MODE_DEDICATED_CHARGER;
+		break;
+	default:
+		return;
+	}
+
+	if (bq->automode < 1)
+		return;
+
+	/* TODO: Detect USB Host mode */
+
+	bq2415x_set_mode(bq, bq->charger_mode);
+
+}
+
+/* timer functions */
+
+static void bq2415x_set_autotimer(struct bq2415x_device *bq, int state)
+{
+	mutex_lock(&bq2415x_timer_mutex);
+
+	if (bq->autotimer == state) {
+		mutex_unlock(&bq2415x_timer_mutex);
+		return;
+	}
+
+	bq->autotimer = state;
+
+	if (state) {
+		schedule_delayed_work(&bq->work, BQ2415X_TIMER_TIMEOUT * HZ);
+		bq2415x_exec_command(bq, BQ2415X_TIMER_RESET);
+	} else {
+		cancel_delayed_work_sync(&bq->work);
+	}
+
+	mutex_unlock(&bq2415x_timer_mutex);
+}
+
+static void bq2415x_timer_error(struct bq2415x_device *bq, const char *msg)
+{
+	dev_err(bq->dev, "%s\n", msg);
+	if (bq->automode > 0)
+		bq->automode = 0;
+	bq2415x_set_mode(bq, BQ2415X_MODE_NONE);
+	bq2415x_set_autotimer(bq, 0);
+}
+
+static void bq2415x_timer_work(struct work_struct *work)
+{
+	struct bq2415x_device *bq = container_of(work, struct bq2415x_device,
+						work.work);
+	int ret, error, boost;
+
+	if (!bq->autotimer)
+		return;
+
+	ret = bq2415x_exec_command(bq, BQ2415X_TIMER_RESET);
+	if (ret < 0) {
+		bq2415x_timer_error(bq, "Reseting timer failed");
+		return;
+	}
+
+	boost = bq2415x_exec_command(bq, BQ2415X_BOOST_MODE_STATUS);
+	if (boost < 0) {
+		bq2415x_timer_error(bq, "Unknow error");
+		return;
+	}
+
+	error = bq2415x_exec_command(bq, BQ2415X_FAULT_STATUS);
+	if (error < 0) {
+		bq2415x_timer_error(bq, "Unknow error");
+		return;
+	}
+
+	if (boost) {
+		switch (error) {
+		case 0: /* No error */
+			break;
+		case 6: /* Timer expired */
+			dev_err(bq->dev, "Timer expired\n");
+			break;
+		case 3: /* Battery voltage too low */
+			dev_err(bq->dev, "Battery voltage to low\n");
+			break;
+
+		case 1: /* Overvoltage protection (chip fried) */
+			bq2415x_timer_error(bq,
+				"Overvolatge protection (chip fried)");
+			return;
+		case 2: /* Overload */
+			bq2415x_timer_error(bq, "Overload");
+			return;
+		case 4: /* Battery overvoltage protection */
+			bq2415x_timer_error(bq,
+				"Battery overvoltage protection");
+			return;
+		case 5: /* Thermal shutdown (too hot) */
+			bq2415x_timer_error(bq,
+					"Thermal shutdown (too hot)");
+			return;
+		case 7: /* N/A */
+			bq2415x_timer_error(bq, "Unknown error");
+			return;
+		}
+	} else {
+		switch (error) {
+		case 0: /* No error */
+			break;
+		case 2: /* Sleep mode */
+			dev_err(bq->dev, "Sleep mode\n");
+			break;
+		case 3: /* Poor input source */
+			dev_err(bq->dev, "Poor input source\n");
+			break;
+		case 6: /* Timer expired */
+			dev_err(bq->dev, "Timer expired\n");
+			break;
+		case 7: /* No battery */
+			dev_err(bq->dev, "No battery\n");
+			break;
+
+		case 1: /* Overvoltage protection (chip fried) */
+			bq2415x_timer_error(bq,
+				"Overvolatge protection (chip fried)");
+			return;
+		case 4: /* Battery overvoltage protection */
+			bq2415x_timer_error(bq,
+				"Battery overvoltage protection");
+			return;
+		case 5: /* Thermal shutdown (too hot) */
+			bq2415x_timer_error(bq,
+				"Thermal shutdown (too hot)");
+			return;
+		}
+	}
+
+	schedule_delayed_work(&bq->work, BQ2415X_TIMER_TIMEOUT * HZ);
+}
+
+/* power supply */
+
+static enum power_supply_property bq2415x_power_supply_props[] = {
+	/* TODO: more power supply properties */
+	POWER_SUPPLY_PROP_STATUS,
+	POWER_SUPPLY_PROP_MODEL_NAME,
+};
+
+static int bq2415x_power_supply_get_property(struct power_supply *psy,
+	enum power_supply_property psp, union power_supply_propval *val)
+{
+	struct bq2415x_device *bq = container_of(psy, struct bq2415x_device,
+						charger);
+	int ret;
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_STATUS:
+		ret = bq2415x_exec_command(bq, BQ2415X_CHARGE_STATUS);
+		if (ret < 0)
+			return ret;
+		else if (ret == 0) /* Ready */
+			val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+		else if (ret == 1) /* Charge in progress */
+			val->intval = POWER_SUPPLY_STATUS_CHARGING;
+		else if (ret == 2) /* Charge done */
+			val->intval = POWER_SUPPLY_STATUS_FULL;
+		else
+			val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
+		break;
+	case POWER_SUPPLY_PROP_MODEL_NAME:
+		val->strval = bq->model;
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int bq2415x_power_supply_init(struct bq2415x_device *bq)
+{
+	int ret;
+	int chip;
+	char revstr[8];
+
+	bq->charger.name = bq->name;
+	bq->charger.type = POWER_SUPPLY_TYPE_USB;
+	bq->charger.properties = bq2415x_power_supply_props;
+	bq->charger.num_properties = ARRAY_SIZE(bq2415x_power_supply_props);
+	bq->charger.get_property = bq2415x_power_supply_get_property;
+
+	ret = bq2415x_detect_chip(bq);
+	if (ret < 0)
+		chip = BQUNKNOWN;
+	else
+		chip = ret;
+
+	ret = bq2415x_detect_revision(bq);
+	if (ret < 0)
+		strcpy(revstr, "unknown");
+	else
+		sprintf(revstr, "1.%d", ret);
+
+	bq->model = kasprintf(GFP_KERNEL,
+				"chip %s, revision %s, vender code %.3d",
+				bq2415x_chip_name[chip], revstr,
+				bq2415x_get_vender_code(bq));
+	if (!bq->model) {
+		dev_err(bq->dev, "failed to allocate model name\n");
+		return -ENOMEM;
+	}
+
+	ret = power_supply_register(bq->dev, &bq->charger);
+	if (ret) {
+		kfree(bq->model);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void bq2415x_power_supply_exit(struct bq2415x_device *bq)
+{
+	bq->autotimer = 0;
+	if (bq->automode > 0)
+		bq->automode = 0;
+	cancel_delayed_work_sync(&bq->work);
+	power_supply_unregister(&bq->charger);
+	kfree(bq->model);
+}
+
+/* sysfs files */
+
+static ssize_t bq2415x_sysfs_show_status(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct power_supply *psy = dev_get_drvdata(dev);
+	struct bq2415x_device *bq = container_of(psy, struct bq2415x_device,
+						charger);
+	enum bq2415x_command command;
+	int ret;
+
+	if (strcmp(attr->attr.name, "otg_status") == 0)
+		command = BQ2415X_OTG_STATUS;
+	else if (strcmp(attr->attr.name, "charge_status") == 0)
+		command = BQ2415X_CHARGE_STATUS;
+	else if (strcmp(attr->attr.name, "boost_status") == 0)
+		command = BQ2415X_BOOST_STATUS;
+	else if (strcmp(attr->attr.name, "fault_status") == 0)
+		command = BQ2415X_FAULT_STATUS;
+	else
+		return -EINVAL;
+
+	ret = bq2415x_exec_command(bq, command);
+	if (ret < 0)
+		return ret;
+	else
+		return sprintf(buf, "%d\n", ret);
+}
+
+static ssize_t bq2415x_sysfs_set_timer(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct power_supply *psy = dev_get_drvdata(dev);
+	struct bq2415x_device *bq = container_of(psy, struct bq2415x_device,
+						charger);
+	int ret = 0;
+
+	if (strncmp(buf, "auto", 4) == 0)
+		bq2415x_set_autotimer(bq, 1);
+	else if (strncmp(buf, "off", 3) == 0)
+		bq2415x_set_autotimer(bq, 0);
+	else
+		ret = bq2415x_exec_command(bq, BQ2415X_TIMER_RESET);
+
+	if (ret < 0)
+		return ret;
+	else
+		return count;
+}
+
+static ssize_t bq2415x_sysfs_show_timer(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct power_supply *psy = dev_get_drvdata(dev);
+	struct bq2415x_device *bq = container_of(psy, struct bq2415x_device,
+						charger);
+
+	if (bq->autotimer)
+		return sprintf(buf, "auto\n");
+	else
+		return sprintf(buf, "off\n");
+}
+
+static ssize_t bq2415x_sysfs_set_mode(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct power_supply *psy = dev_get_drvdata(dev);
+	struct bq2415x_device *bq = container_of(psy, struct bq2415x_device,
+						charger);
+	enum bq2415x_mode mode;
+	int ret = 0;
+
+	if (strncmp(buf, "auto", 4) == 0) {
+		if (bq->automode < 0)
+			return -ENOSYS;
+		bq->automode = 1;
+		mode = bq->charger_mode;
+	} else if (strncmp(buf, "none", 4) == 0) {
+		if (bq->automode > 0)
+			bq->automode = 0;
+		mode = BQ2415X_MODE_NONE;
+	} else if (strncmp(buf, "host", 4) == 0) {
+		if (bq->automode > 0)
+			bq->automode = 0;
+		mode = BQ2415X_MODE_HOST_CHARGER;
+	} else if (strncmp(buf, "dedicated", 9) == 0) {
+		if (bq->automode > 0)
+			bq->automode = 0;
+		mode = BQ2415X_MODE_DEDICATED_CHARGER;
+	} else if (strncmp(buf, "boost", 5) == 0) {
+		if (bq->automode > 0)
+			bq->automode = 0;
+		mode = BQ2415X_MODE_BOOST;
+	} else if (strncmp(buf, "reset", 5) == 0) {
+		bq2415x_reset_chip(bq);
+		bq2415x_set_defaults(bq);
+		if (bq->automode > 0)
+			bq->automode = 1;
+		return count;
+	} else
+		return -EINVAL;
+
+	ret = bq2415x_set_mode(bq, mode);
+	if (ret < 0)
+		return ret;
+	else
+		return count;
+}
+
+static ssize_t bq2415x_sysfs_show_mode(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct power_supply *psy = dev_get_drvdata(dev);
+	struct bq2415x_device *bq = container_of(psy, struct bq2415x_device,
+						charger);
+	ssize_t ret = 0;
+
+	if (bq->automode > 0)
+		ret += sprintf(buf+ret, "auto (");
+
+	switch (bq->mode) {
+	case BQ2415X_MODE_NONE:
+		ret += sprintf(buf+ret, "none");
+		break;
+	case BQ2415X_MODE_HOST_CHARGER:
+		ret += sprintf(buf+ret, "host");
+		break;
+	case BQ2415X_MODE_DEDICATED_CHARGER:
+		ret += sprintf(buf+ret, "dedicated");
+		break;
+	case BQ2415X_MODE_BOOST:
+		ret += sprintf(buf+ret, "boost");
+		break;
+	}
+
+	if (bq->automode > 0)
+		ret += sprintf(buf+ret, ")");
+
+	ret += sprintf(buf+ret, "\n");
+	return ret;
+}
+
+static ssize_t bq2415x_sysfs_set_registers(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct power_supply *psy = dev_get_drvdata(dev);
+	struct bq2415x_device *bq = container_of(psy, struct bq2415x_device,
+						charger);
+	ssize_t ret = 0;
+	char *end;
+	int reg;
+	int val;
+
+	reg = simple_strtol(buf, &end, 16);
+	if (reg < 0 || reg > 4)
+		return -EINVAL;
+
+	val = simple_strtol(end+1, NULL, 16);
+	if (val < 0 || val > 255)
+		return -EINVAL;
+
+	ret = bq2415x_i2c_write(bq, reg, val);
+	if (ret < 0)
+		return ret;
+	else
+		return count;
+}
+
+static ssize_t bq2415x_sysfs_print_reg(struct bq2415x_device *bq,
+					u8 reg, char *buf)
+{
+	int ret = bq2415x_i2c_read(bq, reg);
+	if (ret < 0)
+		return sprintf(buf, "%#.2x=error %d\n", reg, ret);
+	else
+		return sprintf(buf, "%#.2x=%#.2x\n", reg, ret);
+}
+
+static ssize_t bq2415x_sysfs_show_registers(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct power_supply *psy = dev_get_drvdata(dev);
+	struct bq2415x_device *bq = container_of(psy, struct bq2415x_device,
+						charger);
+	ssize_t ret = 0;
+
+	ret += bq2415x_sysfs_print_reg(bq, BQ2415X_REG_STATUS, buf+ret);
+	ret += bq2415x_sysfs_print_reg(bq, BQ2415X_REG_CONTROL, buf+ret);
+	ret += bq2415x_sysfs_print_reg(bq, BQ2415X_REG_VOLTAGE, buf+ret);
+	ret += bq2415x_sysfs_print_reg(bq, BQ2415X_REG_VENDER, buf+ret);
+	ret += bq2415x_sysfs_print_reg(bq, BQ2415X_REG_CURRENT, buf+ret);
+	return ret;
+}
+
+/* Current & Volatage settings */
+
+static ssize_t bq2415x_sysfs_set_limit(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct power_supply *psy = dev_get_drvdata(dev);
+	struct bq2415x_device *bq = container_of(psy, struct bq2415x_device,
+						charger);
+	long val;
+	int ret;
+
+	if (strict_strtol(buf, 10, &val) < 0)
+		return -EINVAL;
+
+	if (strcmp(attr->attr.name, "current_limit") == 0)
+		ret = bq2415x_set_current_limit(bq, val);
+	else if (strcmp(attr->attr.name, "weak_battery_voltage") == 0)
+		ret = bq2415x_set_weak_battery_voltage(bq, val);
+	else if (strcmp(attr->attr.name, "battery_regulation_voltage") == 0)
+		ret = bq2415x_set_battery_regulation_voltage(bq, val);
+	else if (strcmp(attr->attr.name, "charge_current") == 0)
+		ret = bq2415x_set_charge_current(bq, val);
+	else if (strcmp(attr->attr.name, "termination_current") == 0)
+		ret = bq2415x_set_termination_current(bq, val);
+	else
+		return -EINVAL;
+
+	if (ret < 0)
+		return ret;
+	else
+		return count;
+}
+
+static ssize_t bq2415x_sysfs_show_limit(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct power_supply *psy = dev_get_drvdata(dev);
+	struct bq2415x_device *bq = container_of(psy, struct bq2415x_device,
+						charger);
+	int ret;
+
+	if (strcmp(attr->attr.name, "current_limit") == 0)
+		ret = bq2415x_get_current_limit(bq);
+	else if (strcmp(attr->attr.name, "weak_battery_voltage") == 0)
+		ret = bq2415x_get_weak_battery_voltage(bq);
+	else if (strcmp(attr->attr.name, "battery_regulation_voltage") == 0)
+		ret = bq2415x_get_battery_regulation_voltage(bq);
+	else if (strcmp(attr->attr.name, "charge_current") == 0)
+		ret = bq2415x_get_charge_current(bq);
+	else if (strcmp(attr->attr.name, "termination_current") == 0)
+		ret = bq2415x_get_termination_current(bq);
+	else
+		return -EINVAL;
+
+	if (ret < 0)
+		return ret;
+	else
+		return sprintf(buf, "%d\n", ret);
+}
+
+static ssize_t bq2415x_sysfs_set_enable(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct power_supply *psy = dev_get_drvdata(dev);
+	struct bq2415x_device *bq = container_of(psy, struct bq2415x_device,
+						charger);
+	enum bq2415x_command command;
+	long val;
+	int ret;
+
+	if (strict_strtol(buf, 10, &val) < 0)
+		return -EINVAL;
+
+	if (strcmp(attr->attr.name, "charge_termination_enable") == 0)
+		command = val ? BQ2415X_CHARGE_TERMINATION_ENABLE :
+			BQ2415X_CHARGE_TERMINATION_DISABLE;
+	else if (strcmp(attr->attr.name, "high_impedance_enable") == 0)
+		command = val ? BQ2415X_HIGH_IMPEDANCE_ENABLE :
+			BQ2415X_HIGH_IMPEDANCE_DISABLE;
+	else if (strcmp(attr->attr.name, "otg_pin_enable") == 0)
+		command = val ? BQ2415X_OTG_PIN_ENABLE :
+			BQ2415X_OTG_PIN_DISABLE;
+	else if (strcmp(attr->attr.name, "stat_pin_enable") == 0)
+		command = val ? BQ2415X_STAT_PIN_ENABLE :
+			BQ2415X_STAT_PIN_DISABLE;
+	else
+		return -EINVAL;
+
+	ret = bq2415x_exec_command(bq, command);
+	if (ret < 0)
+		return ret;
+	else
+		return count;
+}
+
+static ssize_t bq2415x_sysfs_show_enable(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct power_supply *psy = dev_get_drvdata(dev);
+	struct bq2415x_device *bq = container_of(psy, struct bq2415x_device,
+						charger);
+	enum bq2415x_command command;
+	int ret;
+
+	if (strcmp(attr->attr.name, "charge_termination_enable") == 0)
+		command = BQ2415X_CHARGE_TERMINATION_STATUS;
+	else if (strcmp(attr->attr.name, "high_impedance_enable") == 0)
+		command = BQ2415X_HIGH_IMPEDANCE_STATUS;
+	else if (strcmp(attr->attr.name, "otg_pin_enable") == 0)
+		command = BQ2415X_OTG_PIN_STATUS;
+	else if (strcmp(attr->attr.name, "stat_pin_enable") == 0)
+		command = BQ2415X_STAT_PIN_STATUS;
+	else
+		return -EINVAL;
+
+	ret = bq2415x_exec_command(bq, command);
+	if (ret < 0)
+		return ret;
+	else
+		return sprintf(buf, "%d\n", ret);
+}
+
+static DEVICE_ATTR(current_limit, S_IWUSR | S_IRUGO,
+		bq2415x_sysfs_show_limit, bq2415x_sysfs_set_limit);
+static DEVICE_ATTR(weak_battery_voltage, S_IWUSR | S_IRUGO,
+		bq2415x_sysfs_show_limit, bq2415x_sysfs_set_limit);
+static DEVICE_ATTR(battery_regulation_voltage, S_IWUSR | S_IRUGO,
+		bq2415x_sysfs_show_limit, bq2415x_sysfs_set_limit);
+static DEVICE_ATTR(charge_current, S_IWUSR | S_IRUGO,
+		bq2415x_sysfs_show_limit, bq2415x_sysfs_set_limit);
+static DEVICE_ATTR(termination_current, S_IWUSR | S_IRUGO,
+		bq2415x_sysfs_show_limit, bq2415x_sysfs_set_limit);
+
+static DEVICE_ATTR(charge_termination_enable, S_IWUSR | S_IRUGO,
+		bq2415x_sysfs_show_enable, bq2415x_sysfs_set_enable);
+static DEVICE_ATTR(high_impedance_enable, S_IWUSR | S_IRUGO,
+		bq2415x_sysfs_show_enable, bq2415x_sysfs_set_enable);
+static DEVICE_ATTR(otg_pin_enable, S_IWUSR | S_IRUGO,
+		bq2415x_sysfs_show_enable, bq2415x_sysfs_set_enable);
+static DEVICE_ATTR(stat_pin_enable, S_IWUSR | S_IRUGO,
+		bq2415x_sysfs_show_enable, bq2415x_sysfs_set_enable);
+
+static DEVICE_ATTR(mode, S_IWUSR | S_IRUGO,
+		bq2415x_sysfs_show_mode, bq2415x_sysfs_set_mode);
+static DEVICE_ATTR(timer, S_IWUSR | S_IRUGO,
+		bq2415x_sysfs_show_timer, bq2415x_sysfs_set_timer);
+
+static DEVICE_ATTR(registers, S_IWUSR | S_IRUGO,
+		bq2415x_sysfs_show_registers, bq2415x_sysfs_set_registers);
+
+static DEVICE_ATTR(otg_status, S_IRUGO, bq2415x_sysfs_show_status, NULL);
+static DEVICE_ATTR(charge_status, S_IRUGO, bq2415x_sysfs_show_status, NULL);
+static DEVICE_ATTR(boost_status, S_IRUGO, bq2415x_sysfs_show_status, NULL);
+static DEVICE_ATTR(fault_status, S_IRUGO, bq2415x_sysfs_show_status, NULL);
+
+static struct attribute *bq2415x_sysfs_attributes[] = {
+	&dev_attr_current_limit.attr,
+	&dev_attr_weak_battery_voltage.attr,
+	&dev_attr_battery_regulation_voltage.attr,
+	&dev_attr_charge_current.attr,
+	&dev_attr_termination_current.attr,
+
+	&dev_attr_charge_termination_enable.attr,
+	&dev_attr_high_impedance_enable.attr,
+	&dev_attr_otg_pin_enable.attr,
+	&dev_attr_stat_pin_enable.attr,
+
+	&dev_attr_mode.attr,
+	&dev_attr_timer.attr,
+
+	&dev_attr_registers.attr,
+
+	&dev_attr_otg_status.attr,
+	&dev_attr_charge_status.attr,
+	&dev_attr_boost_status.attr,
+	&dev_attr_fault_status.attr,
+	NULL,
+};
+
+static const struct attribute_group bq2415x_sysfs_attr_group = {
+	.attrs = bq2415x_sysfs_attributes,
+};
+
+static int bq2415x_sysfs_init(struct bq2415x_device *bq)
+{
+	return sysfs_create_group(&bq->charger.dev->kobj,
+			&bq2415x_sysfs_attr_group);
+}
+
+static void bq2415x_sysfs_exit(struct bq2415x_device *bq)
+{
+	sysfs_remove_group(&bq->charger.dev->kobj, &bq2415x_sysfs_attr_group);
+}
+
+/* bq2415x register */
+
+static int bq2415x_probe(struct i2c_client *client,
+		const struct i2c_device_id *id)
+{
+	int ret;
+	int num;
+	char *name;
+	struct bq2415x_device *bq;
+
+	if (!client->dev.platform_data) {
+		dev_err(&client->dev, "platform data not set\n");
+		return -ENODEV;
+	}
+
+	/* Get new ID for the new device */
+	ret = idr_pre_get(&bq2415x_id, GFP_KERNEL);
+	if (ret == 0)
+		return -ENOMEM;
+
+	mutex_lock(&bq2415x_id_mutex);
+	ret = idr_get_new(&bq2415x_id, client, &num);
+	mutex_unlock(&bq2415x_id_mutex);
+
+	if (ret < 0)
+		return ret;
+
+	name = kasprintf(GFP_KERNEL, "%s-%d", id->name, num);
+	if (!name) {
+		dev_err(&client->dev, "failed to allocate device name\n");
+		ret = -ENOMEM;
+		goto error_1;
+	}
+
+	bq = kzalloc(sizeof(*bq), GFP_KERNEL);
+	if (!bq) {
+		dev_err(&client->dev, "failed to allocate device data\n");
+		ret = -ENOMEM;
+		goto error_2;
+	}
+
+	i2c_set_clientdata(client, bq);
+
+	bq->id = num;
+	bq->dev = &client->dev;
+	bq->chip = id->driver_data;
+	bq->name = name;
+	bq->mode = BQ2415X_MODE_NONE;
+	bq->charger_mode = BQ2415X_MODE_NONE;
+	bq->autotimer = 0;
+	bq->automode = 0;
+
+	memcpy(&bq->init_data, client->dev.platform_data,
+			sizeof(bq->init_data));
+
+	bq2415x_reset_chip(bq);
+
+	ret = bq2415x_power_supply_init(bq);
+	if (ret) {
+		dev_err(bq->dev, "failed to register power supply: %d\n", ret);
+		goto error_3;
+	}
+
+	ret = bq2415x_sysfs_init(bq);
+	if (ret) {
+		dev_err(bq->dev, "failed to create sysfs entries: %d\n", ret);
+		goto error_4;
+	}
+
+	ret = bq2415x_set_defaults(bq);
+	if (ret) {
+		dev_err(bq->dev, "failed to set default values: %d\n", ret);
+		goto error_5;
+	}
+
+	if (bq->init_data.set_charger_type_hook) {
+		if (bq->init_data.set_charger_type_hook(
+				bq2415x_set_charger_type, bq)) {
+			bq->automode = 1;
+			dev_info(bq->dev, "automode enabled\n");
+		} else {
+			bq->automode = -1;
+			dev_info(bq->dev, "automode not supported\n");
+		}
+	} else {
+		bq->automode = -1;
+		dev_info(bq->dev, "automode not supported\n");
+	}
+
+	INIT_DELAYED_WORK(&bq->work, bq2415x_timer_work);
+	bq2415x_set_autotimer(bq, 1);
+
+	dev_info(bq->dev, "driver registred\n");
+	return 0;
+
+error_5:
+	bq2415x_sysfs_exit(bq);
+error_4:
+	bq2415x_power_supply_exit(bq);
+error_3:
+	kfree(bq);
+error_2:
+	kfree(name);
+error_1:
+	mutex_lock(&bq2415x_id_mutex);
+	idr_remove(&bq2415x_id, num);
+	mutex_unlock(&bq2415x_id_mutex);
+
+	return ret;
+}
+
+/* bq2415x unregister */
+
+static int bq2415x_remove(struct i2c_client *client)
+{
+	struct bq2415x_device *bq = i2c_get_clientdata(client);
+
+	if (bq->init_data.set_charger_type_hook)
+		bq->init_data.set_charger_type_hook(NULL, NULL);
+
+	bq2415x_sysfs_exit(bq);
+	bq2415x_power_supply_exit(bq);
+
+	bq2415x_reset_chip(bq);
+
+	mutex_lock(&bq2415x_id_mutex);
+	idr_remove(&bq2415x_id, bq->id);
+	mutex_unlock(&bq2415x_id_mutex);
+
+	dev_info(bq->dev, "driver unregistred\n");
+
+	kfree(bq->name);
+	kfree(bq);
+
+	return 0;
+}
+
+static const struct i2c_device_id bq2415x_i2c_id_table[] = {
+	{ "bq2415x", BQUNKNOWN },
+	{ "bq24150", BQ24150 },
+	{ "bq24150a", BQ24150A },
+	{ "bq24151", BQ24151 },
+	{ "bq24151a", BQ24151A },
+	{ "bq24152", BQ24152 },
+	{ "bq24153", BQ24153 },
+	{ "bq24153a", BQ24153A },
+	{ "bq24155", BQ24155 },
+	{ "bq24156", BQ24156 },
+	{ "bq24156a", BQ24156A },
+	{ "bq24158", BQ24158 },
+	{},
+};
+MODULE_DEVICE_TABLE(i2c, bq2415x_i2c_id_table);
+
+static struct i2c_driver bq2415x_driver = {
+	.driver = {
+		.name = "bq2415x-charger",
+	},
+	.probe = bq2415x_probe,
+	.remove = bq2415x_remove,
+	.id_table = bq2415x_i2c_id_table,
+};
+
+/* BEGIN: RX-51 platform code: TODO: move to board-rx51.c */
+static void rx51_init(void);
+static void rx51_exit(void);
+/* END */
+
+static int __init bq2415x_init(void)
+{
+/* BEGIN: RX-51 platform code: TODO: move to board-rx51.c */
+	rx51_init();
+/* END */
+	return i2c_add_driver(&bq2415x_driver);
+}
+module_init(bq2415x_init);
+
+static void __exit bq2415x_exit(void)
+{
+/* BEGIN: RX-51 platform code: TODO: move to board-rx51.c */
+	rx51_exit();
+/* END */
+	i2c_del_driver(&bq2415x_driver);
+}
+module_exit(bq2415x_exit);
+
+MODULE_AUTHOR("Pali Rohár <pali.rohar@gmail.com>");
+MODULE_DESCRIPTION("bq2415x charger driver");
+MODULE_LICENSE("GPL");
+
+
+/* BEGIN: RX-51 platform code: TODO: move to board-rx51.c */
+
+/*#include "isp1704_charger.h"*/
+
+/*static void *rx51_charger_hook_data;
+static void (*rx51_charger_hook)(int type, void *data);*/
+
+static int rx51_charger_set_hook(void (*hook)(int type, void *data), void *data)
+{
+/*	rx51_charger_hook = hook;
+	rx51_charger_hook_data = data;
+	return 1;*/
+	return 0; /* - error - no automode yet, need to use isp1704 driver */
+}
+
+/*static void rx51_charger_set_power(bool on)
+{
+	gpio_set_value(RX51_USB_TRANSCEIVER_RST_GPIO, on);
+}
+
+static void rx51_charger_set_type(int type)
+{
+	if (rx51_charger_hook)
+		rx51_charger_hook(type, rx51_charger_hook_data);
+}*/
+
+static struct bq2415x_platform_data rx51_platform_data = {
+	.current_limit = 100,			/* mA */
+	.weak_battery_voltage = 3400,		/* mV */
+	.battery_regulation_voltage = 4200,	/* mV */
+	.charge_current = 950, /*1200*/		/* mA */
+	.termination_current = 150, /*400*/	/* mA */
+	.resistor_sense = 68,			/* m ohm */
+	.set_charger_type_hook = &rx51_charger_set_hook,
+};
+
+static struct i2c_board_info rx51_board_info = {
+	I2C_BOARD_INFO("bq24150", 0x6b),
+	.platform_data = &rx51_platform_data,
+};
+
+static struct i2c_client *client;
+
+static void rx51_init(void)
+{
+	/*platform_device_register(&rx51_charger_device);*/
+	client = i2c_new_device(i2c_get_adapter(2), &rx51_board_info);
+}
+
+static void rx51_exit(void)
+{
+	/*platform_device_unregister(&rx51_charger_device);*/
+	i2c_unregister_device(client);
+}
+
+/* END */

[-- Attachment #2: This is a digitally signed message part. --]
[-- Type: application/pgp-signature, Size: 198 bytes --]

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: RFC 2: bq2415x_charger driver
  2012-01-27 18:33       ` Pali Rohár
@ 2012-01-27 19:22         ` Mark Brown
  0 siblings, 0 replies; 28+ messages in thread
From: Mark Brown @ 2012-01-27 19:22 UTC (permalink / raw)
  To: Pali Rohár
  Cc: linux-main, linux-omap, Samuel Ortiz, Aliaksei Katovich,
	Vladimir Zapolskiy, Felipe Contreras, Anton Vorontsov,
	Joerg Reisenweber, Sebastian Reichel,
	???????????? ????????????????

[-- Attachment #1: Type: text/plain, Size: 1226 bytes --]

On Fri, Jan 27, 2012 at 07:33:59PM +0100, Pali Rohár wrote:
> On Friday 27 January 2012 16:24:55 Mark Brown wrote:
> > On Fri, Jan 27, 2012 at 03:40:43AM +0100, Pali Roh?r wrote:

> > > +	struct i2c_client *client = to_i2c_client(bq->dev);

> > You could save a bunch of code by moving all this I2C register I/O over
> > to regmap.

> regmap is not available in 2.6.28, so I cannot use it.

You're submitting to mainline here...

In any case, it's trivial to backport - just copy the directory and
header back.

> > > +static int bq2415x_set_weak_battery_voltage(struct bq2415x_device *bq,
> > > int mV) +{
> > > +	int val = mV/100 + (mV%100 > 0 ? 1 : 0) - 34;

> > This could probably be written more clearly?

> How?

Without the ternery operator and with more spaces, probably.

> > > +		case BQ2415X_MODE_BOOST: /* Boost mode */
> > > +			dev_info(bq->dev, "mode: Boost\n");

> > Is dev_info() really appropriate for this stuff?

> How can driver write info that user (or other driver) changed chip mode?
> This should be reported in dmesg!

Why does the user care?  There's an interface for querying if they want
to know and you can generate power supply events when the mode changes.

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 836 bytes --]

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: RFC 2: bq2415x_charger driver
  2012-01-27  2:40   ` RFC 2: " Pali Rohár
  2012-01-27 16:24     ` Mark Brown
@ 2012-01-27 20:40     ` Sebastian Reichel
  1 sibling, 0 replies; 28+ messages in thread
From: Sebastian Reichel @ 2012-01-27 20:40 UTC (permalink / raw)
  To: Pali Rohár
  Cc: linux-main, linux-omap, Samuel Ortiz, Aliaksei Katovich,
	Vladimir Zapolskiy, Felipe Contreras, Anton Vorontsov,
	Joerg Reisenweber,
	Ивайло
	Димитров

[-- Attachment #1: Type: text/plain, Size: 533 bytes --]

On Fri, Jan 27, 2012 at 03:40:43AM +0100, Pali Rohár wrote:
> Driver has power_supply interface (without new charger interface -
> due to compatibility with n900 kernel 2.6.28) and additional sysfs
> entires for configuring bq chip.

IMHO the new charger interface [0] should be used. It has been
written exactly for devices like the N900. The aim is to get a
proper driver in the mainline kernel and not a driver for some
deprecated kernel fork.

[0] http://permalink.gmane.org/gmane.linux.kernel/1237755

-- Sebastian

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 836 bytes --]

^ permalink raw reply	[flat|nested] 28+ messages in thread

end of thread, other threads:[~2012-01-27 21:12 UTC | newest]

Thread overview: 28+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2011-12-05 22:35 [PATCH] mfd: add bq2415x charger driver Felipe Contreras
2011-12-05 22:51 ` Felipe Contreras
2011-12-05 23:05 ` Pali Rohár
2011-12-06  0:12   ` Felipe Contreras
2011-12-06  7:25     ` Pali Rohár
2011-12-06 10:58       ` Felipe Contreras
2011-12-06 13:27         ` Pali Rohár
2011-12-06 14:11           ` Felipe Contreras
2011-12-06 15:19             ` Pali Rohár
2011-12-06 11:25       ` Mark Brown
2011-12-06  2:17 ` Sebastian Reichel
2011-12-06  2:49   ` Felipe Contreras
2011-12-06  2:49     ` Felipe Contreras
2011-12-06 13:21     ` Sebastian Reichel
2011-12-06 13:50       ` Felipe Contreras
2011-12-06 13:34     ` Pali Rohár
2011-12-06 11:31 ` Mark Brown
2011-12-06 11:43   ` Felipe Contreras
2011-12-06 11:43     ` Felipe Contreras
2011-12-06 11:46     ` Mark Brown
2011-12-07 21:03 ` RFC: bq2415x_charger driver Pali Rohár
2011-12-07 21:25   ` Vladimir Zapolskiy
2011-12-07 21:40     ` Pali Rohár
2012-01-27  2:40   ` RFC 2: " Pali Rohár
2012-01-27 16:24     ` Mark Brown
2012-01-27 18:33       ` Pali Rohár
2012-01-27 19:22         ` Mark Brown
2012-01-27 20:40     ` Sebastian Reichel

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.