All of lore.kernel.org
 help / color / mirror / Atom feed
From: "Pali Rohár" <pali.rohar@gmail.com>
To: linux-main <linux-kernel@vger.kernel.org>
Cc: linux-omap <linux-omap@vger.kernel.org>,
	"Samuel Ortiz" <sameo@linux.intel.com>,
	"Aliaksei Katovich" <aliaksei.katovich@nokia.com>,
	"Vladimir Zapolskiy" <vz@mleia.com>,
	"Felipe Contreras" <felipe.contreras@gmail.com>,
	"Anton Vorontsov" <cbouatmailru@gmail.com>,
	"Joerg Reisenweber" <joerg@openmoko.org>,
	"Sebastian Reichel" <sre@debian.org>,
	"Ивайло Димитров" <freemangordon@abv.bg>
Subject: RFC 2: bq2415x_charger driver
Date: Fri, 27 Jan 2012 03:40:43 +0100	[thread overview]
Message-ID: <3531484.T8K8kTguQQ@pali> (raw)
In-Reply-To: <1677656.4d3iQHNZjX@pali-elitebook>


[-- 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 --]

  parent reply	other threads:[~2012-01-27  2:40 UTC|newest]

Thread overview: 28+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
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   ` Pali Rohár [this message]
2012-01-27 16:24     ` RFC 2: " Mark Brown
2012-01-27 18:33       ` Pali Rohár
2012-01-27 19:22         ` Mark Brown
2012-01-27 20:40     ` Sebastian Reichel

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=3531484.T8K8kTguQQ@pali \
    --to=pali.rohar@gmail.com \
    --cc=aliaksei.katovich@nokia.com \
    --cc=cbouatmailru@gmail.com \
    --cc=felipe.contreras@gmail.com \
    --cc=freemangordon@abv.bg \
    --cc=joerg@openmoko.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-omap@vger.kernel.org \
    --cc=sameo@linux.intel.com \
    --cc=sre@debian.org \
    --cc=vz@mleia.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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.