xref: /openbmc/linux/drivers/mfd/tps65010.c (revision 9816d859)
174ba9207SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
287c13493SDavid Brownell /*
387c13493SDavid Brownell  * tps65010 - driver for tps6501x power management chips
487c13493SDavid Brownell  *
587c13493SDavid Brownell  * Copyright (C) 2004 Texas Instruments
687c13493SDavid Brownell  * Copyright (C) 2004-2005 David Brownell
787c13493SDavid Brownell  */
887c13493SDavid Brownell 
987c13493SDavid Brownell #include <linux/kernel.h>
1087c13493SDavid Brownell #include <linux/module.h>
1187c13493SDavid Brownell #include <linux/init.h>
1287c13493SDavid Brownell #include <linux/slab.h>
1387c13493SDavid Brownell #include <linux/interrupt.h>
1487c13493SDavid Brownell #include <linux/i2c.h>
1587c13493SDavid Brownell #include <linux/delay.h>
1687c13493SDavid Brownell #include <linux/workqueue.h>
1787c13493SDavid Brownell #include <linux/debugfs.h>
1887c13493SDavid Brownell #include <linux/seq_file.h>
1987c13493SDavid Brownell #include <linux/mutex.h>
2087c13493SDavid Brownell #include <linux/platform_device.h>
2187c13493SDavid Brownell 
229787076cSWolfram Sang #include <linux/mfd/tps65010.h>
2387c13493SDavid Brownell 
2422e5e747SLinus Walleij #include <linux/gpio/driver.h>
2587c13493SDavid Brownell 
2687c13493SDavid Brownell 
2787c13493SDavid Brownell /*-------------------------------------------------------------------------*/
2887c13493SDavid Brownell 
2987c13493SDavid Brownell #define	DRIVER_VERSION	"2 May 2005"
3087c13493SDavid Brownell #define	DRIVER_NAME	(tps65010_driver.driver.name)
3187c13493SDavid Brownell 
3287c13493SDavid Brownell MODULE_DESCRIPTION("TPS6501x Power Management Driver");
3387c13493SDavid Brownell MODULE_LICENSE("GPL");
3487c13493SDavid Brownell 
3587c13493SDavid Brownell static struct i2c_driver tps65010_driver;
3687c13493SDavid Brownell 
3787c13493SDavid Brownell /*-------------------------------------------------------------------------*/
3887c13493SDavid Brownell 
3987c13493SDavid Brownell /* This driver handles a family of multipurpose chips, which incorporate
4087c13493SDavid Brownell  * voltage regulators, lithium ion/polymer battery charging, GPIOs, LEDs,
4187c13493SDavid Brownell  * and other features often needed in portable devices like cell phones
4287c13493SDavid Brownell  * or digital cameras.
4387c13493SDavid Brownell  *
4487c13493SDavid Brownell  * The tps65011 and tps65013 have different voltage settings compared
4587c13493SDavid Brownell  * to tps65010 and tps65012.  The tps65013 has a NO_CHG status/irq.
4687c13493SDavid Brownell  * All except tps65010 have "wait" mode, possibly defaulted so that
4787c13493SDavid Brownell  * battery-insert != device-on.
4887c13493SDavid Brownell  *
4987c13493SDavid Brownell  * We could distinguish between some models by checking VDCDC1.UVLO or
5087c13493SDavid Brownell  * other registers, unless they've been changed already after powerup
5187c13493SDavid Brownell  * as part of board setup by a bootloader.
5287c13493SDavid Brownell  */
5387c13493SDavid Brownell enum tps_model {
5487c13493SDavid Brownell 	TPS65010,
5587c13493SDavid Brownell 	TPS65011,
5687c13493SDavid Brownell 	TPS65012,
5787c13493SDavid Brownell 	TPS65013,
5887c13493SDavid Brownell };
5987c13493SDavid Brownell 
6087c13493SDavid Brownell struct tps65010 {
6187c13493SDavid Brownell 	struct i2c_client	*client;
6287c13493SDavid Brownell 	struct mutex		lock;
6387c13493SDavid Brownell 	struct delayed_work	work;
6487c13493SDavid Brownell 	struct dentry		*file;
6587c13493SDavid Brownell 	unsigned		charging:1;
6687c13493SDavid Brownell 	unsigned		por:1;
6787c13493SDavid Brownell 	unsigned		model:8;
6887c13493SDavid Brownell 	u16			vbus;
6987c13493SDavid Brownell 	unsigned long		flags;
7087c13493SDavid Brownell #define	FLAG_VBUS_CHANGED	0
7187c13493SDavid Brownell #define	FLAG_IRQ_ENABLE		1
7287c13493SDavid Brownell 
7387c13493SDavid Brownell 	/* copies of last register state */
7487c13493SDavid Brownell 	u8			chgstatus, regstatus, chgconf;
7587c13493SDavid Brownell 	u8			nmask1, nmask2;
7687c13493SDavid Brownell 
7787c13493SDavid Brownell 	u8			outmask;
7887c13493SDavid Brownell 	struct gpio_chip	chip;
7987c13493SDavid Brownell 	struct platform_device	*leds;
8087c13493SDavid Brownell };
8187c13493SDavid Brownell 
8287c13493SDavid Brownell #define	POWER_POLL_DELAY	msecs_to_jiffies(5000)
8387c13493SDavid Brownell 
8487c13493SDavid Brownell /*-------------------------------------------------------------------------*/
8587c13493SDavid Brownell 
8687c13493SDavid Brownell #if	defined(DEBUG) || defined(CONFIG_DEBUG_FS)
8787c13493SDavid Brownell 
dbg_chgstat(char * buf,size_t len,u8 chgstatus)8887c13493SDavid Brownell static void dbg_chgstat(char *buf, size_t len, u8 chgstatus)
8987c13493SDavid Brownell {
9087c13493SDavid Brownell 	snprintf(buf, len, "%02x%s%s%s%s%s%s%s%s\n",
9187c13493SDavid Brownell 		chgstatus,
9287c13493SDavid Brownell 		(chgstatus & TPS_CHG_USB) ? " USB" : "",
9387c13493SDavid Brownell 		(chgstatus & TPS_CHG_AC) ? " AC" : "",
9487c13493SDavid Brownell 		(chgstatus & TPS_CHG_THERM) ? " therm" : "",
9587c13493SDavid Brownell 		(chgstatus & TPS_CHG_TERM) ? " done" :
9687c13493SDavid Brownell 			((chgstatus & (TPS_CHG_USB|TPS_CHG_AC))
9787c13493SDavid Brownell 				? " (charging)" : ""),
9887c13493SDavid Brownell 		(chgstatus & TPS_CHG_TAPER_TMO) ? " taper_tmo" : "",
9987c13493SDavid Brownell 		(chgstatus & TPS_CHG_CHG_TMO) ? " charge_tmo" : "",
10087c13493SDavid Brownell 		(chgstatus & TPS_CHG_PRECHG_TMO) ? " prechg_tmo" : "",
10187c13493SDavid Brownell 		(chgstatus & TPS_CHG_TEMP_ERR) ? " temp_err" : "");
10287c13493SDavid Brownell }
10387c13493SDavid Brownell 
dbg_regstat(char * buf,size_t len,u8 regstatus)10487c13493SDavid Brownell static void dbg_regstat(char *buf, size_t len, u8 regstatus)
10587c13493SDavid Brownell {
10687c13493SDavid Brownell 	snprintf(buf, len, "%02x %s%s%s%s%s%s%s%s\n",
10787c13493SDavid Brownell 		regstatus,
10887c13493SDavid Brownell 		(regstatus & TPS_REG_ONOFF) ? "off" : "(on)",
10987c13493SDavid Brownell 		(regstatus & TPS_REG_COVER) ? " uncover" : "",
11087c13493SDavid Brownell 		(regstatus & TPS_REG_UVLO) ? " UVLO" : "",
11187c13493SDavid Brownell 		(regstatus & TPS_REG_NO_CHG) ? " NO_CHG" : "",
11287c13493SDavid Brownell 		(regstatus & TPS_REG_PG_LD02) ? " ld02_bad" : "",
11387c13493SDavid Brownell 		(regstatus & TPS_REG_PG_LD01) ? " ld01_bad" : "",
11487c13493SDavid Brownell 		(regstatus & TPS_REG_PG_MAIN) ? " main_bad" : "",
11587c13493SDavid Brownell 		(regstatus & TPS_REG_PG_CORE) ? " core_bad" : "");
11687c13493SDavid Brownell }
11787c13493SDavid Brownell 
dbg_chgconf(int por,char * buf,size_t len,u8 chgconfig)11887c13493SDavid Brownell static void dbg_chgconf(int por, char *buf, size_t len, u8 chgconfig)
11987c13493SDavid Brownell {
12087c13493SDavid Brownell 	const char *hibit;
12187c13493SDavid Brownell 
12287c13493SDavid Brownell 	if (por)
12387c13493SDavid Brownell 		hibit = (chgconfig & TPS_CHARGE_POR)
12487c13493SDavid Brownell 				? "POR=69ms" : "POR=1sec";
12587c13493SDavid Brownell 	else
12687c13493SDavid Brownell 		hibit = (chgconfig & TPS65013_AUA) ? "AUA" : "";
12787c13493SDavid Brownell 
12887c13493SDavid Brownell 	snprintf(buf, len, "%02x %s%s%s AC=%d%% USB=%dmA %sCharge\n",
12987c13493SDavid Brownell 		chgconfig, hibit,
13087c13493SDavid Brownell 		(chgconfig & TPS_CHARGE_RESET) ? " reset" : "",
13187c13493SDavid Brownell 		(chgconfig & TPS_CHARGE_FAST) ? " fast" : "",
13287c13493SDavid Brownell 		({int p; switch ((chgconfig >> 3) & 3) {
13387c13493SDavid Brownell 		case 3:		p = 100; break;
13487c13493SDavid Brownell 		case 2:		p = 75; break;
13587c13493SDavid Brownell 		case 1:		p = 50; break;
13687c13493SDavid Brownell 		default:	p = 25; break;
13787c13493SDavid Brownell 		}; p; }),
13887c13493SDavid Brownell 		(chgconfig & TPS_VBUS_CHARGING)
13987c13493SDavid Brownell 			? ((chgconfig & TPS_VBUS_500MA) ? 500 : 100)
14087c13493SDavid Brownell 			: 0,
14187c13493SDavid Brownell 		(chgconfig & TPS_CHARGE_ENABLE) ? "" : "No");
14287c13493SDavid Brownell }
14387c13493SDavid Brownell 
14487c13493SDavid Brownell #endif
14587c13493SDavid Brownell 
14687c13493SDavid Brownell #ifdef	DEBUG
14787c13493SDavid Brownell 
show_chgstatus(const char * label,u8 chgstatus)14887c13493SDavid Brownell static void show_chgstatus(const char *label, u8 chgstatus)
14987c13493SDavid Brownell {
15087c13493SDavid Brownell 	char buf [100];
15187c13493SDavid Brownell 
15287c13493SDavid Brownell 	dbg_chgstat(buf, sizeof buf, chgstatus);
15387c13493SDavid Brownell 	pr_debug("%s: %s %s", DRIVER_NAME, label, buf);
15487c13493SDavid Brownell }
15587c13493SDavid Brownell 
show_regstatus(const char * label,u8 regstatus)15687c13493SDavid Brownell static void show_regstatus(const char *label, u8 regstatus)
15787c13493SDavid Brownell {
15887c13493SDavid Brownell 	char buf [100];
15987c13493SDavid Brownell 
16087c13493SDavid Brownell 	dbg_regstat(buf, sizeof buf, regstatus);
16187c13493SDavid Brownell 	pr_debug("%s: %s %s", DRIVER_NAME, label, buf);
16287c13493SDavid Brownell }
16387c13493SDavid Brownell 
show_chgconfig(int por,const char * label,u8 chgconfig)16487c13493SDavid Brownell static void show_chgconfig(int por, const char *label, u8 chgconfig)
16587c13493SDavid Brownell {
16687c13493SDavid Brownell 	char buf [100];
16787c13493SDavid Brownell 
16887c13493SDavid Brownell 	dbg_chgconf(por, buf, sizeof buf, chgconfig);
16987c13493SDavid Brownell 	pr_debug("%s: %s %s", DRIVER_NAME, label, buf);
17087c13493SDavid Brownell }
17187c13493SDavid Brownell 
17287c13493SDavid Brownell #else
17387c13493SDavid Brownell 
show_chgstatus(const char * label,u8 chgstatus)17487c13493SDavid Brownell static inline void show_chgstatus(const char *label, u8 chgstatus) { }
show_regstatus(const char * label,u8 chgstatus)17587c13493SDavid Brownell static inline void show_regstatus(const char *label, u8 chgstatus) { }
show_chgconfig(int por,const char * label,u8 chgconfig)17687c13493SDavid Brownell static inline void show_chgconfig(int por, const char *label, u8 chgconfig) { }
17787c13493SDavid Brownell 
17887c13493SDavid Brownell #endif
17987c13493SDavid Brownell 
18087c13493SDavid Brownell #ifdef	CONFIG_DEBUG_FS
18187c13493SDavid Brownell 
dbg_show(struct seq_file * s,void * _)18287c13493SDavid Brownell static int dbg_show(struct seq_file *s, void *_)
18387c13493SDavid Brownell {
18487c13493SDavid Brownell 	struct tps65010	*tps = s->private;
18587c13493SDavid Brownell 	u8		value, v2;
18687c13493SDavid Brownell 	unsigned	i;
18787c13493SDavid Brownell 	char		buf[100];
18887c13493SDavid Brownell 	const char	*chip;
18987c13493SDavid Brownell 
19087c13493SDavid Brownell 	switch (tps->model) {
19187c13493SDavid Brownell 	case TPS65010:	chip = "tps65010"; break;
19287c13493SDavid Brownell 	case TPS65011:	chip = "tps65011"; break;
19387c13493SDavid Brownell 	case TPS65012:	chip = "tps65012"; break;
19487c13493SDavid Brownell 	case TPS65013:	chip = "tps65013"; break;
19587c13493SDavid Brownell 	default:	chip = NULL; break;
19687c13493SDavid Brownell 	}
19787c13493SDavid Brownell 	seq_printf(s, "driver  %s\nversion %s\nchip    %s\n\n",
19887c13493SDavid Brownell 			DRIVER_NAME, DRIVER_VERSION, chip);
19987c13493SDavid Brownell 
20087c13493SDavid Brownell 	mutex_lock(&tps->lock);
20187c13493SDavid Brownell 
20287c13493SDavid Brownell 	/* FIXME how can we tell whether a battery is present?
20387c13493SDavid Brownell 	 * likely involves a charge gauging chip (like BQ26501).
20487c13493SDavid Brownell 	 */
20587c13493SDavid Brownell 
20687c13493SDavid Brownell 	seq_printf(s, "%scharging\n\n", tps->charging ? "" : "(not) ");
20787c13493SDavid Brownell 
20887c13493SDavid Brownell 
20987c13493SDavid Brownell 	/* registers for monitoring battery charging and status; note
21087c13493SDavid Brownell 	 * that reading chgstat and regstat may ack IRQs...
21187c13493SDavid Brownell 	 */
21287c13493SDavid Brownell 	value = i2c_smbus_read_byte_data(tps->client, TPS_CHGCONFIG);
21387c13493SDavid Brownell 	dbg_chgconf(tps->por, buf, sizeof buf, value);
21487c13493SDavid Brownell 	seq_printf(s, "chgconfig %s", buf);
21587c13493SDavid Brownell 
21687c13493SDavid Brownell 	value = i2c_smbus_read_byte_data(tps->client, TPS_CHGSTATUS);
21787c13493SDavid Brownell 	dbg_chgstat(buf, sizeof buf, value);
21887c13493SDavid Brownell 	seq_printf(s, "chgstat   %s", buf);
21987c13493SDavid Brownell 	value = i2c_smbus_read_byte_data(tps->client, TPS_MASK1);
22087c13493SDavid Brownell 	dbg_chgstat(buf, sizeof buf, value);
22187c13493SDavid Brownell 	seq_printf(s, "mask1     %s", buf);
22287c13493SDavid Brownell 	/* ignore ackint1 */
22387c13493SDavid Brownell 
22487c13493SDavid Brownell 	value = i2c_smbus_read_byte_data(tps->client, TPS_REGSTATUS);
22587c13493SDavid Brownell 	dbg_regstat(buf, sizeof buf, value);
22687c13493SDavid Brownell 	seq_printf(s, "regstat   %s", buf);
22787c13493SDavid Brownell 	value = i2c_smbus_read_byte_data(tps->client, TPS_MASK2);
22887c13493SDavid Brownell 	dbg_regstat(buf, sizeof buf, value);
22987c13493SDavid Brownell 	seq_printf(s, "mask2     %s\n", buf);
23087c13493SDavid Brownell 	/* ignore ackint2 */
23187c13493SDavid Brownell 
232bb3d5934SMark Brown 	queue_delayed_work(system_power_efficient_wq, &tps->work,
233bb3d5934SMark Brown 			   POWER_POLL_DELAY);
23487c13493SDavid Brownell 
23587c13493SDavid Brownell 	/* VMAIN voltage, enable lowpower, etc */
23687c13493SDavid Brownell 	value = i2c_smbus_read_byte_data(tps->client, TPS_VDCDC1);
23787c13493SDavid Brownell 	seq_printf(s, "vdcdc1    %02x\n", value);
23887c13493SDavid Brownell 
23987c13493SDavid Brownell 	/* VCORE voltage, vibrator on/off */
24087c13493SDavid Brownell 	value = i2c_smbus_read_byte_data(tps->client, TPS_VDCDC2);
24187c13493SDavid Brownell 	seq_printf(s, "vdcdc2    %02x\n", value);
24287c13493SDavid Brownell 
24387c13493SDavid Brownell 	/* both LD0s, and their lowpower behavior */
24487c13493SDavid Brownell 	value = i2c_smbus_read_byte_data(tps->client, TPS_VREGS1);
24587c13493SDavid Brownell 	seq_printf(s, "vregs1    %02x\n\n", value);
24687c13493SDavid Brownell 
24787c13493SDavid Brownell 
24887c13493SDavid Brownell 	/* LEDs and GPIOs */
24987c13493SDavid Brownell 	value = i2c_smbus_read_byte_data(tps->client, TPS_LED1_ON);
25087c13493SDavid Brownell 	v2 = i2c_smbus_read_byte_data(tps->client, TPS_LED1_PER);
25187c13493SDavid Brownell 	seq_printf(s, "led1 %s, on=%02x, per=%02x, %d/%d msec\n",
25287c13493SDavid Brownell 		(value & 0x80)
25387c13493SDavid Brownell 			? ((v2 & 0x80) ? "on" : "off")
25487c13493SDavid Brownell 			: ((v2 & 0x80) ? "blink" : "(nPG)"),
25587c13493SDavid Brownell 		value, v2,
25687c13493SDavid Brownell 		(value & 0x7f) * 10, (v2 & 0x7f) * 100);
25787c13493SDavid Brownell 
25887c13493SDavid Brownell 	value = i2c_smbus_read_byte_data(tps->client, TPS_LED2_ON);
25987c13493SDavid Brownell 	v2 = i2c_smbus_read_byte_data(tps->client, TPS_LED2_PER);
26087c13493SDavid Brownell 	seq_printf(s, "led2 %s, on=%02x, per=%02x, %d/%d msec\n",
26187c13493SDavid Brownell 		(value & 0x80)
26287c13493SDavid Brownell 			? ((v2 & 0x80) ? "on" : "off")
26387c13493SDavid Brownell 			: ((v2 & 0x80) ? "blink" : "off"),
26487c13493SDavid Brownell 		value, v2,
26587c13493SDavid Brownell 		(value & 0x7f) * 10, (v2 & 0x7f) * 100);
26687c13493SDavid Brownell 
26787c13493SDavid Brownell 	value = i2c_smbus_read_byte_data(tps->client, TPS_DEFGPIO);
26887c13493SDavid Brownell 	v2 = i2c_smbus_read_byte_data(tps->client, TPS_MASK3);
26987c13493SDavid Brownell 	seq_printf(s, "defgpio %02x mask3 %02x\n", value, v2);
27087c13493SDavid Brownell 
27187c13493SDavid Brownell 	for (i = 0; i < 4; i++) {
27287c13493SDavid Brownell 		if (value & (1 << (4 + i)))
27387c13493SDavid Brownell 			seq_printf(s, "  gpio%d-out %s\n", i + 1,
27487c13493SDavid Brownell 				(value & (1 << i)) ? "low" : "hi ");
27587c13493SDavid Brownell 		else
27687c13493SDavid Brownell 			seq_printf(s, "  gpio%d-in  %s %s %s\n", i + 1,
27787c13493SDavid Brownell 				(value & (1 << i)) ? "hi " : "low",
27887c13493SDavid Brownell 				(v2 & (1 << i)) ? "no-irq" : "irq",
27987c13493SDavid Brownell 				(v2 & (1 << (4 + i))) ? "rising" : "falling");
28087c13493SDavid Brownell 	}
28187c13493SDavid Brownell 
28287c13493SDavid Brownell 	mutex_unlock(&tps->lock);
28387c13493SDavid Brownell 	return 0;
28487c13493SDavid Brownell }
28587c13493SDavid Brownell 
dbg_tps_open(struct inode * inode,struct file * file)28687c13493SDavid Brownell static int dbg_tps_open(struct inode *inode, struct file *file)
28787c13493SDavid Brownell {
28887c13493SDavid Brownell 	return single_open(file, dbg_show, inode->i_private);
28987c13493SDavid Brownell }
29087c13493SDavid Brownell 
29187c13493SDavid Brownell static const struct file_operations debug_fops = {
29287c13493SDavid Brownell 	.open		= dbg_tps_open,
29387c13493SDavid Brownell 	.read		= seq_read,
29487c13493SDavid Brownell 	.llseek		= seq_lseek,
29587c13493SDavid Brownell 	.release	= single_release,
29687c13493SDavid Brownell };
29787c13493SDavid Brownell 
29887c13493SDavid Brownell #define	DEBUG_FOPS	&debug_fops
29987c13493SDavid Brownell 
30087c13493SDavid Brownell #else
30187c13493SDavid Brownell #define	DEBUG_FOPS	NULL
30287c13493SDavid Brownell #endif
30387c13493SDavid Brownell 
30487c13493SDavid Brownell /*-------------------------------------------------------------------------*/
30587c13493SDavid Brownell 
30687c13493SDavid Brownell /* handle IRQS in a task context, so we can use I2C calls */
tps65010_interrupt(struct tps65010 * tps)30787c13493SDavid Brownell static void tps65010_interrupt(struct tps65010 *tps)
30887c13493SDavid Brownell {
30987c13493SDavid Brownell 	u8 tmp = 0, mask, poll;
31087c13493SDavid Brownell 
31187c13493SDavid Brownell 	/* IRQs won't trigger for certain events, but we can get
31287c13493SDavid Brownell 	 * others by polling (normally, with external power applied).
31387c13493SDavid Brownell 	 */
31487c13493SDavid Brownell 	poll = 0;
31587c13493SDavid Brownell 
31687c13493SDavid Brownell 	/* regstatus irqs */
31787c13493SDavid Brownell 	if (tps->nmask2) {
31887c13493SDavid Brownell 		tmp = i2c_smbus_read_byte_data(tps->client, TPS_REGSTATUS);
31987c13493SDavid Brownell 		mask = tmp ^ tps->regstatus;
32087c13493SDavid Brownell 		tps->regstatus = tmp;
32187c13493SDavid Brownell 		mask &= tps->nmask2;
32287c13493SDavid Brownell 	} else
32387c13493SDavid Brownell 		mask = 0;
32487c13493SDavid Brownell 	if (mask) {
32587c13493SDavid Brownell 		tps->regstatus =  tmp;
32687c13493SDavid Brownell 		/* may need to shut something down ... */
32787c13493SDavid Brownell 
32887c13493SDavid Brownell 		/* "off" usually means deep sleep */
32987c13493SDavid Brownell 		if (tmp & TPS_REG_ONOFF) {
33087c13493SDavid Brownell 			pr_info("%s: power off button\n", DRIVER_NAME);
33187c13493SDavid Brownell #if 0
33287c13493SDavid Brownell 			/* REVISIT:  this might need its own workqueue
33387c13493SDavid Brownell 			 * plus tweaks including deadlock avoidance ...
33487c13493SDavid Brownell 			 * also needs to get error handling and probably
33587c13493SDavid Brownell 			 * an #ifdef CONFIG_HIBERNATION
33687c13493SDavid Brownell 			 */
33787c13493SDavid Brownell 			hibernate();
33887c13493SDavid Brownell #endif
33987c13493SDavid Brownell 			poll = 1;
34087c13493SDavid Brownell 		}
34187c13493SDavid Brownell 	}
34287c13493SDavid Brownell 
34387c13493SDavid Brownell 	/* chgstatus irqs */
34487c13493SDavid Brownell 	if (tps->nmask1) {
34587c13493SDavid Brownell 		tmp = i2c_smbus_read_byte_data(tps->client, TPS_CHGSTATUS);
34687c13493SDavid Brownell 		mask = tmp ^ tps->chgstatus;
34787c13493SDavid Brownell 		tps->chgstatus = tmp;
34887c13493SDavid Brownell 		mask &= tps->nmask1;
34987c13493SDavid Brownell 	} else
35087c13493SDavid Brownell 		mask = 0;
35187c13493SDavid Brownell 	if (mask) {
35287c13493SDavid Brownell 		unsigned	charging = 0;
35387c13493SDavid Brownell 
35487c13493SDavid Brownell 		show_chgstatus("chg/irq", tmp);
35587c13493SDavid Brownell 		if (tmp & (TPS_CHG_USB|TPS_CHG_AC))
35687c13493SDavid Brownell 			show_chgconfig(tps->por, "conf", tps->chgconf);
35787c13493SDavid Brownell 
35887c13493SDavid Brownell 		/* Unless it was turned off or disabled, we charge any
35987c13493SDavid Brownell 		 * battery whenever there's power available for it
36087c13493SDavid Brownell 		 * and the charger hasn't been disabled.
36187c13493SDavid Brownell 		 */
36287c13493SDavid Brownell 		if (!(tps->chgstatus & ~(TPS_CHG_USB|TPS_CHG_AC))
36387c13493SDavid Brownell 				&& (tps->chgstatus & (TPS_CHG_USB|TPS_CHG_AC))
36487c13493SDavid Brownell 				&& (tps->chgconf & TPS_CHARGE_ENABLE)
36587c13493SDavid Brownell 				) {
36687c13493SDavid Brownell 			if (tps->chgstatus & TPS_CHG_USB) {
36787c13493SDavid Brownell 				/* VBUS options are readonly until reconnect */
36887c13493SDavid Brownell 				if (mask & TPS_CHG_USB)
36987c13493SDavid Brownell 					set_bit(FLAG_VBUS_CHANGED, &tps->flags);
37087c13493SDavid Brownell 				charging = 1;
37187c13493SDavid Brownell 			} else if (tps->chgstatus & TPS_CHG_AC)
37287c13493SDavid Brownell 				charging = 1;
37387c13493SDavid Brownell 		}
37487c13493SDavid Brownell 		if (charging != tps->charging) {
37587c13493SDavid Brownell 			tps->charging = charging;
37687c13493SDavid Brownell 			pr_info("%s: battery %scharging\n",
37787c13493SDavid Brownell 				DRIVER_NAME, charging ? "" :
37887c13493SDavid Brownell 				((tps->chgstatus & (TPS_CHG_USB|TPS_CHG_AC))
37987c13493SDavid Brownell 					? "NOT " : "dis"));
38087c13493SDavid Brownell 		}
38187c13493SDavid Brownell 	}
38287c13493SDavid Brownell 
38387c13493SDavid Brownell 	/* always poll to detect (a) power removal, without tps65013
38487c13493SDavid Brownell 	 * NO_CHG IRQ; or (b) restart of charging after stop.
38587c13493SDavid Brownell 	 */
38687c13493SDavid Brownell 	if ((tps->model != TPS65013 || !tps->charging)
38787c13493SDavid Brownell 			&& (tps->chgstatus & (TPS_CHG_USB|TPS_CHG_AC)))
38887c13493SDavid Brownell 		poll = 1;
38987c13493SDavid Brownell 	if (poll)
390bb3d5934SMark Brown 		queue_delayed_work(system_power_efficient_wq, &tps->work,
391bb3d5934SMark Brown 				   POWER_POLL_DELAY);
39287c13493SDavid Brownell 
39387c13493SDavid Brownell 	/* also potentially gpio-in rise or fall */
39487c13493SDavid Brownell }
39587c13493SDavid Brownell 
39687c13493SDavid Brownell /* handle IRQs and polling using keventd for now */
tps65010_work(struct work_struct * work)39787c13493SDavid Brownell static void tps65010_work(struct work_struct *work)
39887c13493SDavid Brownell {
39987c13493SDavid Brownell 	struct tps65010		*tps;
40087c13493SDavid Brownell 
401afdb32f2STejun Heo 	tps = container_of(to_delayed_work(work), struct tps65010, work);
40287c13493SDavid Brownell 	mutex_lock(&tps->lock);
40387c13493SDavid Brownell 
40487c13493SDavid Brownell 	tps65010_interrupt(tps);
40587c13493SDavid Brownell 
40687c13493SDavid Brownell 	if (test_and_clear_bit(FLAG_VBUS_CHANGED, &tps->flags)) {
40787c13493SDavid Brownell 		u8	chgconfig, tmp;
40887c13493SDavid Brownell 
40987c13493SDavid Brownell 		chgconfig = i2c_smbus_read_byte_data(tps->client,
41087c13493SDavid Brownell 					TPS_CHGCONFIG);
41187c13493SDavid Brownell 		chgconfig &= ~(TPS_VBUS_500MA | TPS_VBUS_CHARGING);
41287c13493SDavid Brownell 		if (tps->vbus == 500)
41387c13493SDavid Brownell 			chgconfig |= TPS_VBUS_500MA | TPS_VBUS_CHARGING;
41487c13493SDavid Brownell 		else if (tps->vbus >= 100)
41587c13493SDavid Brownell 			chgconfig |= TPS_VBUS_CHARGING;
41687c13493SDavid Brownell 
4172fbd5834SLee Jones 		i2c_smbus_write_byte_data(tps->client,
41887c13493SDavid Brownell 					  TPS_CHGCONFIG, chgconfig);
41987c13493SDavid Brownell 
42087c13493SDavid Brownell 		/* vbus update fails unless VBUS is connected! */
42187c13493SDavid Brownell 		tmp = i2c_smbus_read_byte_data(tps->client, TPS_CHGCONFIG);
42287c13493SDavid Brownell 		tps->chgconf = tmp;
42387c13493SDavid Brownell 		show_chgconfig(tps->por, "update vbus", tmp);
42487c13493SDavid Brownell 	}
42587c13493SDavid Brownell 
42687c13493SDavid Brownell 	if (test_and_clear_bit(FLAG_IRQ_ENABLE, &tps->flags))
42787c13493SDavid Brownell 		enable_irq(tps->client->irq);
42887c13493SDavid Brownell 
42987c13493SDavid Brownell 	mutex_unlock(&tps->lock);
43087c13493SDavid Brownell }
43187c13493SDavid Brownell 
tps65010_irq(int irq,void * _tps)43287c13493SDavid Brownell static irqreturn_t tps65010_irq(int irq, void *_tps)
43387c13493SDavid Brownell {
43487c13493SDavid Brownell 	struct tps65010		*tps = _tps;
43587c13493SDavid Brownell 
43687c13493SDavid Brownell 	disable_irq_nosync(irq);
43787c13493SDavid Brownell 	set_bit(FLAG_IRQ_ENABLE, &tps->flags);
438bb3d5934SMark Brown 	queue_delayed_work(system_power_efficient_wq, &tps->work, 0);
43987c13493SDavid Brownell 	return IRQ_HANDLED;
44087c13493SDavid Brownell }
44187c13493SDavid Brownell 
44287c13493SDavid Brownell /*-------------------------------------------------------------------------*/
44387c13493SDavid Brownell 
44487c13493SDavid Brownell /* offsets 0..3 == GPIO1..GPIO4
44587c13493SDavid Brownell  * offsets 4..5 == LED1/nPG, LED2 (we set one of the non-BLINK modes)
44687c13493SDavid Brownell  * offset 6 == vibrator motor driver
44787c13493SDavid Brownell  */
44887c13493SDavid Brownell static void
tps65010_gpio_set(struct gpio_chip * chip,unsigned offset,int value)44987c13493SDavid Brownell tps65010_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
45087c13493SDavid Brownell {
45187c13493SDavid Brownell 	if (offset < 4)
45287c13493SDavid Brownell 		tps65010_set_gpio_out_value(offset + 1, value);
45387c13493SDavid Brownell 	else if (offset < 6)
45487c13493SDavid Brownell 		tps65010_set_led(offset - 3, value ? ON : OFF);
45587c13493SDavid Brownell 	else
45687c13493SDavid Brownell 		tps65010_set_vib(value);
45787c13493SDavid Brownell }
45887c13493SDavid Brownell 
45987c13493SDavid Brownell static int
tps65010_output(struct gpio_chip * chip,unsigned offset,int value)46087c13493SDavid Brownell tps65010_output(struct gpio_chip *chip, unsigned offset, int value)
46187c13493SDavid Brownell {
46287c13493SDavid Brownell 	/* GPIOs may be input-only */
46387c13493SDavid Brownell 	if (offset < 4) {
46487c13493SDavid Brownell 		struct tps65010		*tps;
46587c13493SDavid Brownell 
46622e5e747SLinus Walleij 		tps = gpiochip_get_data(chip);
46787c13493SDavid Brownell 		if (!(tps->outmask & (1 << offset)))
46887c13493SDavid Brownell 			return -EINVAL;
46987c13493SDavid Brownell 		tps65010_set_gpio_out_value(offset + 1, value);
47087c13493SDavid Brownell 	} else if (offset < 6)
47187c13493SDavid Brownell 		tps65010_set_led(offset - 3, value ? ON : OFF);
47287c13493SDavid Brownell 	else
47387c13493SDavid Brownell 		tps65010_set_vib(value);
47487c13493SDavid Brownell 
47587c13493SDavid Brownell 	return 0;
47687c13493SDavid Brownell }
47787c13493SDavid Brownell 
tps65010_gpio_get(struct gpio_chip * chip,unsigned offset)47887c13493SDavid Brownell static int tps65010_gpio_get(struct gpio_chip *chip, unsigned offset)
47987c13493SDavid Brownell {
48087c13493SDavid Brownell 	int			value;
48187c13493SDavid Brownell 	struct tps65010		*tps;
48287c13493SDavid Brownell 
48322e5e747SLinus Walleij 	tps = gpiochip_get_data(chip);
48487c13493SDavid Brownell 
48587c13493SDavid Brownell 	if (offset < 4) {
48687c13493SDavid Brownell 		value = i2c_smbus_read_byte_data(tps->client, TPS_DEFGPIO);
48787c13493SDavid Brownell 		if (value < 0)
488bf3de47fSLinus Walleij 			return value;
48987c13493SDavid Brownell 		if (value & (1 << (offset + 4)))	/* output */
49087c13493SDavid Brownell 			return !(value & (1 << offset));
49187c13493SDavid Brownell 		else					/* input */
492bf3de47fSLinus Walleij 			return !!(value & (1 << offset));
49387c13493SDavid Brownell 	}
49487c13493SDavid Brownell 
49587c13493SDavid Brownell 	/* REVISIT we *could* report LED1/nPG and LED2 state ... */
49687c13493SDavid Brownell 	return 0;
49787c13493SDavid Brownell }
49887c13493SDavid Brownell 
49987c13493SDavid Brownell 
50087c13493SDavid Brownell /*-------------------------------------------------------------------------*/
50187c13493SDavid Brownell 
50287c13493SDavid Brownell static struct tps65010 *the_tps;
50387c13493SDavid Brownell 
tps65010_remove(struct i2c_client * client)504ed5c2f5fSUwe Kleine-König static void tps65010_remove(struct i2c_client *client)
50587c13493SDavid Brownell {
50687c13493SDavid Brownell 	struct tps65010		*tps = i2c_get_clientdata(client);
507334a41ceSJingoo Han 	struct tps65010_board	*board = dev_get_platdata(&client->dev);
50887c13493SDavid Brownell 
50987c13493SDavid Brownell 	if (board && board->teardown)
51087c13493SDavid Brownell 		board->teardown(client, &tps->chip);
51187c13493SDavid Brownell 	if (client->irq > 0)
51287c13493SDavid Brownell 		free_irq(client->irq, tps);
51387c13493SDavid Brownell 	cancel_delayed_work_sync(&tps->work);
51487c13493SDavid Brownell 	debugfs_remove(tps->file);
51587c13493SDavid Brownell 	the_tps = NULL;
51687c13493SDavid Brownell }
517afdb32f2STejun Heo 
tps65010_probe(struct i2c_client * client)51887c13493SDavid Brownell static int tps65010_probe(struct i2c_client *client)
51987c13493SDavid Brownell {
52087c13493SDavid Brownell 	const struct i2c_device_id *id = i2c_client_get_device_id(client);
52187c13493SDavid Brownell 	struct tps65010		*tps;
52271b954d8SUwe Kleine-König 	int			status;
52387c13493SDavid Brownell 	struct tps65010_board	*board = dev_get_platdata(&client->dev);
52471b954d8SUwe Kleine-König 
52587c13493SDavid Brownell 	if (the_tps) {
52687c13493SDavid Brownell 		dev_dbg(&client->dev, "only one tps6501x chip allowed\n");
527334a41ceSJingoo Han 		return -ENODEV;
52887c13493SDavid Brownell 	}
52987c13493SDavid Brownell 
53087c13493SDavid Brownell 	if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
53187c13493SDavid Brownell 		return -EINVAL;
53287c13493SDavid Brownell 
53387c13493SDavid Brownell 	tps = devm_kzalloc(&client->dev, sizeof(*tps), GFP_KERNEL);
53487c13493SDavid Brownell 	if (!tps)
53587c13493SDavid Brownell 		return -ENOMEM;
53687c13493SDavid Brownell 
537eac1dcbdSJingoo Han 	mutex_init(&tps->lock);
53887c13493SDavid Brownell 	INIT_DELAYED_WORK(&tps->work, tps65010_work);
53987c13493SDavid Brownell 	tps->client = client;
54087c13493SDavid Brownell 	tps->model = id->driver_data;
54187c13493SDavid Brownell 
54287c13493SDavid Brownell 	/* the IRQ is active low, but many gpio lines can't support that
54387c13493SDavid Brownell 	 * so this driver uses falling-edge triggers instead.
54487c13493SDavid Brownell 	 */
54587c13493SDavid Brownell 	if (client->irq > 0) {
54687c13493SDavid Brownell 		status = request_irq(client->irq, tps65010_irq,
54787c13493SDavid Brownell 				     IRQF_TRIGGER_FALLING, DRIVER_NAME, tps);
54887c13493SDavid Brownell 		if (status < 0) {
54987c13493SDavid Brownell 			dev_dbg(&client->dev, "can't get IRQ %d, err %d\n",
55087c13493SDavid Brownell 					client->irq, status);
551212436c2STheodore Ts'o 			return status;
55287c13493SDavid Brownell 		}
55387c13493SDavid Brownell 		/* annoying race here, ideally we'd have an option
55487c13493SDavid Brownell 		 * to claim the irq now and enable it later.
555eac1dcbdSJingoo Han 		 * FIXME genirq IRQF_NOAUTOEN now solves that ...
55687c13493SDavid Brownell 		 */
55787c13493SDavid Brownell 		disable_irq(client->irq);
55887c13493SDavid Brownell 		set_bit(FLAG_IRQ_ENABLE, &tps->flags);
55987c13493SDavid Brownell 	} else
56087c13493SDavid Brownell 		dev_warn(&client->dev, "IRQ not configured!\n");
56187c13493SDavid Brownell 
56287c13493SDavid Brownell 
56387c13493SDavid Brownell 	switch (tps->model) {
56487c13493SDavid Brownell 	case TPS65010:
56587c13493SDavid Brownell 	case TPS65012:
56687c13493SDavid Brownell 		tps->por = 1;
56787c13493SDavid Brownell 		break;
56887c13493SDavid Brownell 	/* else CHGCONFIG.POR is replaced by AUA, enabling a WAIT mode */
56987c13493SDavid Brownell 	}
57087c13493SDavid Brownell 	tps->chgconf = i2c_smbus_read_byte_data(client, TPS_CHGCONFIG);
57187c13493SDavid Brownell 	show_chgconfig(tps->por, "conf/init", tps->chgconf);
57287c13493SDavid Brownell 
57387c13493SDavid Brownell 	show_chgstatus("chg/init",
57487c13493SDavid Brownell 		i2c_smbus_read_byte_data(client, TPS_CHGSTATUS));
57587c13493SDavid Brownell 	show_regstatus("reg/init",
57687c13493SDavid Brownell 		i2c_smbus_read_byte_data(client, TPS_REGSTATUS));
57787c13493SDavid Brownell 
57887c13493SDavid Brownell 	pr_debug("%s: vdcdc1 0x%02x, vdcdc2 %02x, vregs1 %02x\n", DRIVER_NAME,
57987c13493SDavid Brownell 		i2c_smbus_read_byte_data(client, TPS_VDCDC1),
58087c13493SDavid Brownell 		i2c_smbus_read_byte_data(client, TPS_VDCDC2),
58187c13493SDavid Brownell 		i2c_smbus_read_byte_data(client, TPS_VREGS1));
58287c13493SDavid Brownell 	pr_debug("%s: defgpio 0x%02x, mask3 0x%02x\n", DRIVER_NAME,
58387c13493SDavid Brownell 		i2c_smbus_read_byte_data(client, TPS_DEFGPIO),
58487c13493SDavid Brownell 		i2c_smbus_read_byte_data(client, TPS_MASK3));
58587c13493SDavid Brownell 
58687c13493SDavid Brownell 	i2c_set_clientdata(client, tps);
58787c13493SDavid Brownell 	the_tps = tps;
58887c13493SDavid Brownell 
58987c13493SDavid Brownell #if	defined(CONFIG_USB_GADGET) && !defined(CONFIG_USB_OTG)
59087c13493SDavid Brownell 	/* USB hosts can't draw VBUS.  OTG devices could, later
59187c13493SDavid Brownell 	 * when OTG infrastructure enables it.  USB peripherals
59287c13493SDavid Brownell 	 * could be relying on VBUS while booting, though.
59387c13493SDavid Brownell 	 */
59487c13493SDavid Brownell 	tps->vbus = 100;
59587c13493SDavid Brownell #endif
59687c13493SDavid Brownell 
59787c13493SDavid Brownell 	/* unmask the "interesting" irqs, then poll once to
59887c13493SDavid Brownell 	 * kickstart monitoring, initialize shadowed status
59987c13493SDavid Brownell 	 * registers, and maybe disable VBUS draw.
60087c13493SDavid Brownell 	 */
60187c13493SDavid Brownell 	tps->nmask1 = ~0;
60287c13493SDavid Brownell 	(void) i2c_smbus_write_byte_data(client, TPS_MASK1, ~tps->nmask1);
60387c13493SDavid Brownell 
60487c13493SDavid Brownell 	tps->nmask2 = TPS_REG_ONOFF;
60587c13493SDavid Brownell 	if (tps->model == TPS65013)
60687c13493SDavid Brownell 		tps->nmask2 |= TPS_REG_NO_CHG;
60787c13493SDavid Brownell 	(void) i2c_smbus_write_byte_data(client, TPS_MASK2, ~tps->nmask2);
60887c13493SDavid Brownell 
60987c13493SDavid Brownell 	(void) i2c_smbus_write_byte_data(client, TPS_MASK3, 0x0f
61087c13493SDavid Brownell 		| i2c_smbus_read_byte_data(client, TPS_MASK3));
61187c13493SDavid Brownell 
61287c13493SDavid Brownell 	tps65010_work(&tps->work.work);
61387c13493SDavid Brownell 
61487c13493SDavid Brownell 	tps->file = debugfs_create_file(DRIVER_NAME, S_IRUGO, NULL,
61587c13493SDavid Brownell 				tps, DEBUG_FOPS);
61687c13493SDavid Brownell 
61787c13493SDavid Brownell 	/* optionally register GPIOs */
61887c13493SDavid Brownell 	if (board) {
61987c13493SDavid Brownell 		tps->outmask = board->outmask;
62087c13493SDavid Brownell 
62187c13493SDavid Brownell 		tps->chip.label = client->name;
62284836992SBen Dooks 		tps->chip.parent = &client->dev;
62387c13493SDavid Brownell 		tps->chip.owner = THIS_MODULE;
62487c13493SDavid Brownell 
62587c13493SDavid Brownell 		tps->chip.set = tps65010_gpio_set;
62658383c78SLinus Walleij 		tps->chip.direction_output = tps65010_output;
62787c13493SDavid Brownell 
62887c13493SDavid Brownell 		/* NOTE:  only partial support for inputs; nyet IRQs */
62987c13493SDavid Brownell 		tps->chip.get = tps65010_gpio_get;
63087c13493SDavid Brownell 
63187c13493SDavid Brownell 		tps->chip.base = -1;
63287c13493SDavid Brownell 		tps->chip.ngpio = 7;
63387c13493SDavid Brownell 		tps->chip.can_sleep = 1;
63487c13493SDavid Brownell 
63587c13493SDavid Brownell 		status = gpiochip_add_data(&tps->chip, tps);
63687c13493SDavid Brownell 		if (status < 0)
63787c13493SDavid Brownell 			dev_err(&client->dev, "can't add gpiochip, err %d\n",
63887c13493SDavid Brownell 					status);
63922e5e747SLinus Walleij 		else if (board->setup) {
64087c13493SDavid Brownell 			status = board->setup(client, &tps->chip);
64187c13493SDavid Brownell 			if (status < 0) {
64287c13493SDavid Brownell 				dev_dbg(&client->dev,
64387c13493SDavid Brownell 					"board %s %s err %d\n",
64487c13493SDavid Brownell 					"setup", client->name, status);
64587c13493SDavid Brownell 				status = 0;
64687c13493SDavid Brownell 			}
64787c13493SDavid Brownell 		}
64887c13493SDavid Brownell 	}
64987c13493SDavid Brownell 
65087c13493SDavid Brownell 	return 0;
65187c13493SDavid Brownell }
65287c13493SDavid Brownell 
65387c13493SDavid Brownell static const struct i2c_device_id tps65010_id[] = {
65487c13493SDavid Brownell 	{ "tps65010", TPS65010 },
65587c13493SDavid Brownell 	{ "tps65011", TPS65011 },
65687c13493SDavid Brownell 	{ "tps65012", TPS65012 },
65787c13493SDavid Brownell 	{ "tps65013", TPS65013 },
65887c13493SDavid Brownell 	{ "tps65014", TPS65011 },	/* tps65011 charging at 6.5V max */
65987c13493SDavid Brownell 	{ }
66087c13493SDavid Brownell };
66187c13493SDavid Brownell MODULE_DEVICE_TABLE(i2c, tps65010_id);
66287c13493SDavid Brownell 
66387c13493SDavid Brownell static struct i2c_driver tps65010_driver = {
66487c13493SDavid Brownell 	.driver = {
66587c13493SDavid Brownell 		.name	= "tps65010",
66687c13493SDavid Brownell 	},
66787c13493SDavid Brownell 	.probe = tps65010_probe,
66887c13493SDavid Brownell 	.remove	= tps65010_remove,
66987c13493SDavid Brownell 	.id_table = tps65010_id,
67087c13493SDavid Brownell };
671*9816d859SUwe Kleine-König 
672bb733707SDmitry Torokhov /*-------------------------------------------------------------------------*/
67387c13493SDavid Brownell 
67487c13493SDavid Brownell /* Draw from VBUS:
67587c13493SDavid Brownell  *   0 mA -- DON'T DRAW (might supply power instead)
67687c13493SDavid Brownell  * 100 mA -- usb unit load (slowest charge rate)
67787c13493SDavid Brownell  * 500 mA -- usb high power (fast battery charge)
67887c13493SDavid Brownell  */
tps65010_set_vbus_draw(unsigned mA)67987c13493SDavid Brownell int tps65010_set_vbus_draw(unsigned mA)
68087c13493SDavid Brownell {
68187c13493SDavid Brownell 	unsigned long	flags;
68287c13493SDavid Brownell 
68387c13493SDavid Brownell 	if (!the_tps)
68487c13493SDavid Brownell 		return -ENODEV;
68587c13493SDavid Brownell 
68687c13493SDavid Brownell 	/* assumes non-SMP */
68787c13493SDavid Brownell 	local_irq_save(flags);
68887c13493SDavid Brownell 	if (mA >= 500)
68987c13493SDavid Brownell 		mA = 500;
69087c13493SDavid Brownell 	else if (mA >= 100)
69187c13493SDavid Brownell 		mA = 100;
69287c13493SDavid Brownell 	else
69387c13493SDavid Brownell 		mA = 0;
69487c13493SDavid Brownell 	the_tps->vbus = mA;
69587c13493SDavid Brownell 	if ((the_tps->chgstatus & TPS_CHG_USB)
69687c13493SDavid Brownell 			&& test_and_set_bit(
69787c13493SDavid Brownell 				FLAG_VBUS_CHANGED, &the_tps->flags)) {
69887c13493SDavid Brownell 		/* gadget drivers call this in_irq() */
69987c13493SDavid Brownell 		queue_delayed_work(system_power_efficient_wq, &the_tps->work,
70087c13493SDavid Brownell 				   0);
70187c13493SDavid Brownell 	}
70287c13493SDavid Brownell 	local_irq_restore(flags);
703bb3d5934SMark Brown 
704bb3d5934SMark Brown 	return 0;
70587c13493SDavid Brownell }
70687c13493SDavid Brownell EXPORT_SYMBOL(tps65010_set_vbus_draw);
70787c13493SDavid Brownell 
70887c13493SDavid Brownell /*-------------------------------------------------------------------------*/
70987c13493SDavid Brownell /* tps65010_set_gpio_out_value parameter:
71087c13493SDavid Brownell  * gpio:  GPIO1, GPIO2, GPIO3 or GPIO4
71187c13493SDavid Brownell  * value: LOW or HIGH
71287c13493SDavid Brownell  */
tps65010_set_gpio_out_value(unsigned gpio,unsigned value)71387c13493SDavid Brownell int tps65010_set_gpio_out_value(unsigned gpio, unsigned value)
71487c13493SDavid Brownell {
71587c13493SDavid Brownell 	int	 status;
71687c13493SDavid Brownell 	unsigned defgpio;
71787c13493SDavid Brownell 
71887c13493SDavid Brownell 	if (!the_tps)
71987c13493SDavid Brownell 		return -ENODEV;
72087c13493SDavid Brownell 	if ((gpio < GPIO1) || (gpio > GPIO4))
72187c13493SDavid Brownell 		return -EINVAL;
72287c13493SDavid Brownell 
72387c13493SDavid Brownell 	mutex_lock(&the_tps->lock);
72487c13493SDavid Brownell 
72587c13493SDavid Brownell 	defgpio = i2c_smbus_read_byte_data(the_tps->client, TPS_DEFGPIO);
72687c13493SDavid Brownell 
72787c13493SDavid Brownell 	/* Configure GPIO for output */
72887c13493SDavid Brownell 	defgpio |= 1 << (gpio + 3);
72987c13493SDavid Brownell 
73087c13493SDavid Brownell 	/* Writing 1 forces a logic 0 on that GPIO and vice versa */
73187c13493SDavid Brownell 	switch (value) {
73287c13493SDavid Brownell 	case LOW:
73387c13493SDavid Brownell 		defgpio |= 1 << (gpio - 1);    /* set GPIO low by writing 1 */
73487c13493SDavid Brownell 		break;
73587c13493SDavid Brownell 	/* case HIGH: */
73687c13493SDavid Brownell 	default:
73787c13493SDavid Brownell 		defgpio &= ~(1 << (gpio - 1)); /* set GPIO high by writing 0 */
73887c13493SDavid Brownell 		break;
73987c13493SDavid Brownell 	}
74087c13493SDavid Brownell 
74187c13493SDavid Brownell 	status = i2c_smbus_write_byte_data(the_tps->client,
74287c13493SDavid Brownell 		TPS_DEFGPIO, defgpio);
74387c13493SDavid Brownell 
74487c13493SDavid Brownell 	pr_debug("%s: gpio%dout = %s, defgpio 0x%02x\n", DRIVER_NAME,
74587c13493SDavid Brownell 		gpio, value ? "high" : "low",
74687c13493SDavid Brownell 		i2c_smbus_read_byte_data(the_tps->client, TPS_DEFGPIO));
74787c13493SDavid Brownell 
74887c13493SDavid Brownell 	mutex_unlock(&the_tps->lock);
74987c13493SDavid Brownell 	return status;
75087c13493SDavid Brownell }
75187c13493SDavid Brownell EXPORT_SYMBOL(tps65010_set_gpio_out_value);
75287c13493SDavid Brownell 
75387c13493SDavid Brownell /*-------------------------------------------------------------------------*/
75487c13493SDavid Brownell /* tps65010_set_led parameter:
75587c13493SDavid Brownell  * led:  LED1 or LED2
75687c13493SDavid Brownell  * mode: ON, OFF or BLINK
75787c13493SDavid Brownell  */
tps65010_set_led(unsigned led,unsigned mode)75887c13493SDavid Brownell int tps65010_set_led(unsigned led, unsigned mode)
75987c13493SDavid Brownell {
76087c13493SDavid Brownell 	int	 status;
76187c13493SDavid Brownell 	unsigned led_on, led_per, offs;
76287c13493SDavid Brownell 
76387c13493SDavid Brownell 	if (!the_tps)
76487c13493SDavid Brownell 		return -ENODEV;
76587c13493SDavid Brownell 
76687c13493SDavid Brownell 	if (led == LED1)
76787c13493SDavid Brownell 		offs = 0;
76887c13493SDavid Brownell 	else {
76987c13493SDavid Brownell 		offs = 2;
77087c13493SDavid Brownell 		led = LED2;
77187c13493SDavid Brownell 	}
77287c13493SDavid Brownell 
77387c13493SDavid Brownell 	mutex_lock(&the_tps->lock);
77487c13493SDavid Brownell 
77587c13493SDavid Brownell 	pr_debug("%s: led%i_on   0x%02x\n", DRIVER_NAME, led,
77687c13493SDavid Brownell 		i2c_smbus_read_byte_data(the_tps->client,
77787c13493SDavid Brownell 				TPS_LED1_ON + offs));
77887c13493SDavid Brownell 
77987c13493SDavid Brownell 	pr_debug("%s: led%i_per  0x%02x\n", DRIVER_NAME, led,
78087c13493SDavid Brownell 		i2c_smbus_read_byte_data(the_tps->client,
78187c13493SDavid Brownell 				TPS_LED1_PER + offs));
78287c13493SDavid Brownell 
78387c13493SDavid Brownell 	switch (mode) {
78487c13493SDavid Brownell 	case OFF:
78587c13493SDavid Brownell 		led_on  = 1 << 7;
78687c13493SDavid Brownell 		led_per = 0 << 7;
78787c13493SDavid Brownell 		break;
78887c13493SDavid Brownell 	case ON:
78987c13493SDavid Brownell 		led_on  = 1 << 7;
79087c13493SDavid Brownell 		led_per = 1 << 7;
79187c13493SDavid Brownell 		break;
79287c13493SDavid Brownell 	case BLINK:
79387c13493SDavid Brownell 		led_on  = 0x30 | (0 << 7);
79487c13493SDavid Brownell 		led_per = 0x08 | (1 << 7);
79587c13493SDavid Brownell 		break;
79687c13493SDavid Brownell 	default:
79787c13493SDavid Brownell 		printk(KERN_ERR "%s: Wrong mode parameter for set_led()\n",
79887c13493SDavid Brownell 		       DRIVER_NAME);
79987c13493SDavid Brownell 		mutex_unlock(&the_tps->lock);
80087c13493SDavid Brownell 		return -EINVAL;
80187c13493SDavid Brownell 	}
80287c13493SDavid Brownell 
80387c13493SDavid Brownell 	status = i2c_smbus_write_byte_data(the_tps->client,
80487c13493SDavid Brownell 			TPS_LED1_ON + offs, led_on);
80587c13493SDavid Brownell 
80687c13493SDavid Brownell 	if (status != 0) {
80787c13493SDavid Brownell 		printk(KERN_ERR "%s: Failed to write led%i_on register\n",
80887c13493SDavid Brownell 		       DRIVER_NAME, led);
80987c13493SDavid Brownell 		mutex_unlock(&the_tps->lock);
81087c13493SDavid Brownell 		return status;
81187c13493SDavid Brownell 	}
81287c13493SDavid Brownell 
81387c13493SDavid Brownell 	pr_debug("%s: led%i_on   0x%02x\n", DRIVER_NAME, led,
81487c13493SDavid Brownell 		i2c_smbus_read_byte_data(the_tps->client, TPS_LED1_ON + offs));
81587c13493SDavid Brownell 
81687c13493SDavid Brownell 	status = i2c_smbus_write_byte_data(the_tps->client,
81787c13493SDavid Brownell 			TPS_LED1_PER + offs, led_per);
81887c13493SDavid Brownell 
81987c13493SDavid Brownell 	if (status != 0) {
82087c13493SDavid Brownell 		printk(KERN_ERR "%s: Failed to write led%i_per register\n",
82187c13493SDavid Brownell 		       DRIVER_NAME, led);
82287c13493SDavid Brownell 		mutex_unlock(&the_tps->lock);
82387c13493SDavid Brownell 		return status;
82487c13493SDavid Brownell 	}
82587c13493SDavid Brownell 
82687c13493SDavid Brownell 	pr_debug("%s: led%i_per  0x%02x\n", DRIVER_NAME, led,
82787c13493SDavid Brownell 		i2c_smbus_read_byte_data(the_tps->client,
82887c13493SDavid Brownell 				TPS_LED1_PER + offs));
82987c13493SDavid Brownell 
83087c13493SDavid Brownell 	mutex_unlock(&the_tps->lock);
83187c13493SDavid Brownell 
83287c13493SDavid Brownell 	return status;
83387c13493SDavid Brownell }
83487c13493SDavid Brownell EXPORT_SYMBOL(tps65010_set_led);
83587c13493SDavid Brownell 
83687c13493SDavid Brownell /*-------------------------------------------------------------------------*/
83787c13493SDavid Brownell /* tps65010_set_vib parameter:
83887c13493SDavid Brownell  * value: ON or OFF
83987c13493SDavid Brownell  */
tps65010_set_vib(unsigned value)84087c13493SDavid Brownell int tps65010_set_vib(unsigned value)
84187c13493SDavid Brownell {
84287c13493SDavid Brownell 	int	 status;
84387c13493SDavid Brownell 	unsigned vdcdc2;
84487c13493SDavid Brownell 
84587c13493SDavid Brownell 	if (!the_tps)
84687c13493SDavid Brownell 		return -ENODEV;
84787c13493SDavid Brownell 
84887c13493SDavid Brownell 	mutex_lock(&the_tps->lock);
84987c13493SDavid Brownell 
85087c13493SDavid Brownell 	vdcdc2 = i2c_smbus_read_byte_data(the_tps->client, TPS_VDCDC2);
85187c13493SDavid Brownell 	vdcdc2 &= ~(1 << 1);
85287c13493SDavid Brownell 	if (value)
85387c13493SDavid Brownell 		vdcdc2 |= (1 << 1);
85487c13493SDavid Brownell 	status = i2c_smbus_write_byte_data(the_tps->client,
85587c13493SDavid Brownell 		TPS_VDCDC2, vdcdc2);
85687c13493SDavid Brownell 
85787c13493SDavid Brownell 	pr_debug("%s: vibrator %s\n", DRIVER_NAME, value ? "on" : "off");
85887c13493SDavid Brownell 
85987c13493SDavid Brownell 	mutex_unlock(&the_tps->lock);
86087c13493SDavid Brownell 	return status;
86187c13493SDavid Brownell }
86287c13493SDavid Brownell EXPORT_SYMBOL(tps65010_set_vib);
86387c13493SDavid Brownell 
86487c13493SDavid Brownell /*-------------------------------------------------------------------------*/
86587c13493SDavid Brownell /* tps65010_set_low_pwr parameter:
86687c13493SDavid Brownell  * mode: ON or OFF
86787c13493SDavid Brownell  */
tps65010_set_low_pwr(unsigned mode)86887c13493SDavid Brownell int tps65010_set_low_pwr(unsigned mode)
86987c13493SDavid Brownell {
87087c13493SDavid Brownell 	int	 status;
87187c13493SDavid Brownell 	unsigned vdcdc1;
87287c13493SDavid Brownell 
87387c13493SDavid Brownell 	if (!the_tps)
87487c13493SDavid Brownell 		return -ENODEV;
87587c13493SDavid Brownell 
87687c13493SDavid Brownell 	mutex_lock(&the_tps->lock);
87787c13493SDavid Brownell 
87887c13493SDavid Brownell 	pr_debug("%s: %s low_pwr, vdcdc1 0x%02x\n", DRIVER_NAME,
87987c13493SDavid Brownell 		mode ? "enable" : "disable",
88087c13493SDavid Brownell 		i2c_smbus_read_byte_data(the_tps->client, TPS_VDCDC1));
88187c13493SDavid Brownell 
88287c13493SDavid Brownell 	vdcdc1 = i2c_smbus_read_byte_data(the_tps->client, TPS_VDCDC1);
88387c13493SDavid Brownell 
88487c13493SDavid Brownell 	switch (mode) {
88587c13493SDavid Brownell 	case OFF:
88687c13493SDavid Brownell 		vdcdc1 &= ~TPS_ENABLE_LP; /* disable ENABLE_LP bit */
88787c13493SDavid Brownell 		break;
88887c13493SDavid Brownell 	/* case ON: */
88987c13493SDavid Brownell 	default:
89087c13493SDavid Brownell 		vdcdc1 |= TPS_ENABLE_LP;  /* enable ENABLE_LP bit */
89187c13493SDavid Brownell 		break;
89287c13493SDavid Brownell 	}
89387c13493SDavid Brownell 
89487c13493SDavid Brownell 	status = i2c_smbus_write_byte_data(the_tps->client,
89587c13493SDavid Brownell 			TPS_VDCDC1, vdcdc1);
89687c13493SDavid Brownell 
89787c13493SDavid Brownell 	if (status != 0)
89887c13493SDavid Brownell 		printk(KERN_ERR "%s: Failed to write vdcdc1 register\n",
89987c13493SDavid Brownell 			DRIVER_NAME);
90087c13493SDavid Brownell 	else
90187c13493SDavid Brownell 		pr_debug("%s: vdcdc1 0x%02x\n", DRIVER_NAME,
90287c13493SDavid Brownell 			i2c_smbus_read_byte_data(the_tps->client, TPS_VDCDC1));
90387c13493SDavid Brownell 
90487c13493SDavid Brownell 	mutex_unlock(&the_tps->lock);
90587c13493SDavid Brownell 
90687c13493SDavid Brownell 	return status;
90787c13493SDavid Brownell }
90887c13493SDavid Brownell EXPORT_SYMBOL(tps65010_set_low_pwr);
90987c13493SDavid Brownell 
91087c13493SDavid Brownell /*-------------------------------------------------------------------------*/
91187c13493SDavid Brownell /* tps65010_config_vregs1 parameter:
91287c13493SDavid Brownell  * value to be written to VREGS1 register
91387c13493SDavid Brownell  * Note: The complete register is written, set all bits you need
91487c13493SDavid Brownell  */
tps65010_config_vregs1(unsigned value)91587c13493SDavid Brownell int tps65010_config_vregs1(unsigned value)
91687c13493SDavid Brownell {
91787c13493SDavid Brownell 	int	 status;
91887c13493SDavid Brownell 
91987c13493SDavid Brownell 	if (!the_tps)
92087c13493SDavid Brownell 		return -ENODEV;
92187c13493SDavid Brownell 
92287c13493SDavid Brownell 	mutex_lock(&the_tps->lock);
92387c13493SDavid Brownell 
92487c13493SDavid Brownell 	pr_debug("%s: vregs1 0x%02x\n", DRIVER_NAME,
92587c13493SDavid Brownell 			i2c_smbus_read_byte_data(the_tps->client, TPS_VREGS1));
92687c13493SDavid Brownell 
92787c13493SDavid Brownell 	status = i2c_smbus_write_byte_data(the_tps->client,
92887c13493SDavid Brownell 			TPS_VREGS1, value);
92987c13493SDavid Brownell 
93087c13493SDavid Brownell 	if (status != 0)
93187c13493SDavid Brownell 		printk(KERN_ERR "%s: Failed to write vregs1 register\n",
93287c13493SDavid Brownell 			DRIVER_NAME);
93387c13493SDavid Brownell 	else
93487c13493SDavid Brownell 		pr_debug("%s: vregs1 0x%02x\n", DRIVER_NAME,
93587c13493SDavid Brownell 			i2c_smbus_read_byte_data(the_tps->client, TPS_VREGS1));
93687c13493SDavid Brownell 
93787c13493SDavid Brownell 	mutex_unlock(&the_tps->lock);
93887c13493SDavid Brownell 
93987c13493SDavid Brownell 	return status;
94087c13493SDavid Brownell }
94187c13493SDavid Brownell EXPORT_SYMBOL(tps65010_config_vregs1);
94287c13493SDavid Brownell 
tps65010_config_vdcdc2(unsigned value)94387c13493SDavid Brownell int tps65010_config_vdcdc2(unsigned value)
94487c13493SDavid Brownell {
94587c13493SDavid Brownell 	struct i2c_client *c;
94687c13493SDavid Brownell 	int	 status;
947b45440c3SBen Dooks 
948b45440c3SBen Dooks 	if (!the_tps)
949b45440c3SBen Dooks 		return -ENODEV;
950b45440c3SBen Dooks 
951b45440c3SBen Dooks 	c = the_tps->client;
952b45440c3SBen Dooks 	mutex_lock(&the_tps->lock);
953b45440c3SBen Dooks 
954b45440c3SBen Dooks 	pr_debug("%s: vdcdc2 0x%02x\n", DRIVER_NAME,
955b45440c3SBen Dooks 		 i2c_smbus_read_byte_data(c, TPS_VDCDC2));
956b45440c3SBen Dooks 
957b45440c3SBen Dooks 	status = i2c_smbus_write_byte_data(c, TPS_VDCDC2, value);
958b45440c3SBen Dooks 
959b45440c3SBen Dooks 	if (status != 0)
960b45440c3SBen Dooks 		printk(KERN_ERR "%s: Failed to write vdcdc2 register\n",
961b45440c3SBen Dooks 			DRIVER_NAME);
962b45440c3SBen Dooks 	else
963b45440c3SBen Dooks 		pr_debug("%s: vregs1 0x%02x\n", DRIVER_NAME,
964b45440c3SBen Dooks 			 i2c_smbus_read_byte_data(c, TPS_VDCDC2));
965b45440c3SBen Dooks 
966b45440c3SBen Dooks 	mutex_unlock(&the_tps->lock);
967b45440c3SBen Dooks 	return status;
968b45440c3SBen Dooks }
969b45440c3SBen Dooks EXPORT_SYMBOL(tps65010_config_vdcdc2);
970b45440c3SBen Dooks 
971b45440c3SBen Dooks /*-------------------------------------------------------------------------*/
972b45440c3SBen Dooks /* tps65013_set_low_pwr parameter:
973b45440c3SBen Dooks  * mode: ON or OFF
974b45440c3SBen Dooks  */
97587c13493SDavid Brownell 
97687c13493SDavid Brownell /* FIXME: Assumes AC or USB power is present. Setting AUA bit is not
97787c13493SDavid Brownell 	required if power supply is through a battery */
97887c13493SDavid Brownell 
tps65013_set_low_pwr(unsigned mode)97987c13493SDavid Brownell int tps65013_set_low_pwr(unsigned mode)
98087c13493SDavid Brownell {
98187c13493SDavid Brownell 	int	 status;
98287c13493SDavid Brownell 	unsigned vdcdc1, chgconfig;
98387c13493SDavid Brownell 
98487c13493SDavid Brownell 	if (!the_tps || the_tps->por)
98587c13493SDavid Brownell 		return -ENODEV;
98687c13493SDavid Brownell 
98787c13493SDavid Brownell 	mutex_lock(&the_tps->lock);
98887c13493SDavid Brownell 
98987c13493SDavid Brownell 	pr_debug("%s: %s low_pwr, chgconfig 0x%02x vdcdc1 0x%02x\n",
99087c13493SDavid Brownell 		DRIVER_NAME,
99187c13493SDavid Brownell 		mode ? "enable" : "disable",
99287c13493SDavid Brownell 		i2c_smbus_read_byte_data(the_tps->client, TPS_CHGCONFIG),
99387c13493SDavid Brownell 		i2c_smbus_read_byte_data(the_tps->client, TPS_VDCDC1));
99487c13493SDavid Brownell 
99587c13493SDavid Brownell 	chgconfig = i2c_smbus_read_byte_data(the_tps->client, TPS_CHGCONFIG);
99687c13493SDavid Brownell 	vdcdc1 = i2c_smbus_read_byte_data(the_tps->client, TPS_VDCDC1);
99787c13493SDavid Brownell 
99887c13493SDavid Brownell 	switch (mode) {
99987c13493SDavid Brownell 	case OFF:
100087c13493SDavid Brownell 		chgconfig &= ~TPS65013_AUA; /* disable AUA bit */
100187c13493SDavid Brownell 		vdcdc1 &= ~TPS_ENABLE_LP; /* disable ENABLE_LP bit */
100287c13493SDavid Brownell 		break;
100387c13493SDavid Brownell 	/* case ON: */
100487c13493SDavid Brownell 	default:
100587c13493SDavid Brownell 		chgconfig |= TPS65013_AUA;  /* enable AUA bit */
100687c13493SDavid Brownell 		vdcdc1 |= TPS_ENABLE_LP;  /* enable ENABLE_LP bit */
100787c13493SDavid Brownell 		break;
100887c13493SDavid Brownell 	}
100987c13493SDavid Brownell 
101087c13493SDavid Brownell 	status = i2c_smbus_write_byte_data(the_tps->client,
101187c13493SDavid Brownell 			TPS_CHGCONFIG, chgconfig);
101287c13493SDavid Brownell 	if (status != 0) {
101387c13493SDavid Brownell 		printk(KERN_ERR "%s: Failed to write chconfig register\n",
101487c13493SDavid Brownell 	 DRIVER_NAME);
101587c13493SDavid Brownell 		mutex_unlock(&the_tps->lock);
101687c13493SDavid Brownell 		return status;
101787c13493SDavid Brownell 	}
101887c13493SDavid Brownell 
101987c13493SDavid Brownell 	chgconfig = i2c_smbus_read_byte_data(the_tps->client, TPS_CHGCONFIG);
102087c13493SDavid Brownell 	the_tps->chgconf = chgconfig;
102187c13493SDavid Brownell 	show_chgconfig(0, "chgconf", chgconfig);
102287c13493SDavid Brownell 
102387c13493SDavid Brownell 	status = i2c_smbus_write_byte_data(the_tps->client,
102487c13493SDavid Brownell 			TPS_VDCDC1, vdcdc1);
102587c13493SDavid Brownell 
102687c13493SDavid Brownell 	if (status != 0)
102787c13493SDavid Brownell 		printk(KERN_ERR "%s: Failed to write vdcdc1 register\n",
102887c13493SDavid Brownell 	 DRIVER_NAME);
102987c13493SDavid Brownell 	else
103087c13493SDavid Brownell 		pr_debug("%s: vdcdc1 0x%02x\n", DRIVER_NAME,
103187c13493SDavid Brownell 			i2c_smbus_read_byte_data(the_tps->client, TPS_VDCDC1));
103287c13493SDavid Brownell 
103387c13493SDavid Brownell 	mutex_unlock(&the_tps->lock);
103487c13493SDavid Brownell 
103587c13493SDavid Brownell 	return status;
103687c13493SDavid Brownell }
103787c13493SDavid Brownell EXPORT_SYMBOL(tps65013_set_low_pwr);
103887c13493SDavid Brownell 
103987c13493SDavid Brownell /*-------------------------------------------------------------------------*/
104087c13493SDavid Brownell 
tps_init(void)104187c13493SDavid Brownell static int __init tps_init(void)
104287c13493SDavid Brownell {
104387c13493SDavid Brownell 	return i2c_add_driver(&tps65010_driver);
104487c13493SDavid Brownell }
104587c13493SDavid Brownell /* NOTE:  this MUST be initialized before the other parts of the system
104687c13493SDavid Brownell  * that rely on it ... but after the i2c bus on which this relies.
1047dbc352b9SAaro Koskinen  * That is, much earlier than on PC-type systems, which don't often use
104887c13493SDavid Brownell  * I2C as a core system bus.
104987c13493SDavid Brownell  */
105087c13493SDavid Brownell subsys_initcall(tps_init);
105187c13493SDavid Brownell 
tps_exit(void)105287c13493SDavid Brownell static void __exit tps_exit(void)
105387c13493SDavid Brownell {
105487c13493SDavid Brownell 	i2c_del_driver(&tps65010_driver);
105587c13493SDavid Brownell }
105687c13493SDavid Brownell module_exit(tps_exit);
105787c13493SDavid Brownell 
105887c13493SDavid Brownell