⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 tps65010.c

📁 linux-2.6.15.6
💻 C
📖 第 1 页 / 共 2 页
字号:
/* * tps65010 - driver for tps6501x power management chips * * Copyright (C) 2004 Texas Instruments * Copyright (C) 2004-2005 David Brownell * * 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., 675 Mass Ave, Cambridge, MA 02139, USA. */#include <linux/config.h>#include <linux/kernel.h>#include <linux/module.h>#include <linux/init.h>#include <linux/slab.h>#include <linux/interrupt.h>#include <linux/device.h>#include <linux/i2c.h>#include <linux/delay.h>#include <linux/workqueue.h>#include <linux/suspend.h>#include <linux/debugfs.h>#include <linux/seq_file.h>#include <asm/irq.h>#include <asm/mach-types.h>#include <asm/arch/gpio.h>#include <asm/arch/mux.h>#include <asm/arch/tps65010.h>/*-------------------------------------------------------------------------*/#define	DRIVER_VERSION	"2 May 2005"#define	DRIVER_NAME	(tps65010_driver.name)MODULE_DESCRIPTION("TPS6501x Power Management Driver");MODULE_LICENSE("GPL");static unsigned short normal_i2c[] = { 0x48, /* 0x49, */ I2C_CLIENT_END };static unsigned short normal_i2c_range[] = { I2C_CLIENT_END };I2C_CLIENT_INSMOD;static struct i2c_driver tps65010_driver;/*-------------------------------------------------------------------------*//* This driver handles a family of multipurpose chips, which incorporate * voltage regulators, lithium ion/polymer battery charging, GPIOs, LEDs, * and other features often needed in portable devices like cell phones * or digital cameras. * * The tps65011 and tps65013 have different voltage settings compared * to tps65010 and tps65012.  The tps65013 has a NO_CHG status/irq. * All except tps65010 have "wait" mode, possibly defaulted so that * battery-insert != device-on. * * We could distinguish between some models by checking VDCDC1.UVLO or * other registers, unless they've been changed already after powerup * as part of board setup by a bootloader. */enum tps_model {	TPS_UNKNOWN = 0,	TPS65010,	TPS65011,	TPS65012,	TPS65013,};struct tps65010 {	struct i2c_client	client;	struct semaphore	lock;	int			irq;	struct work_struct	work;	struct dentry		*file;	unsigned		charging:1;	unsigned		por:1;	unsigned		model:8;	u16			vbus;	unsigned long		flags;#define	FLAG_VBUS_CHANGED	0#define	FLAG_IRQ_ENABLE		1	/* copies of last register state */	u8			chgstatus, regstatus, chgconf;	u8			nmask1, nmask2;	/* not currently tracking GPIO state */};#define	POWER_POLL_DELAY	msecs_to_jiffies(800)/*-------------------------------------------------------------------------*/#if	defined(DEBUG) || defined(CONFIG_DEBUG_FS)static void dbg_chgstat(char *buf, size_t len, u8 chgstatus){	snprintf(buf, len, "%02x%s%s%s%s%s%s%s%s\n",		chgstatus,		(chgstatus & TPS_CHG_USB) ? " USB" : "",		(chgstatus & TPS_CHG_AC) ? " AC" : "",		(chgstatus & TPS_CHG_THERM) ? " therm" : "",		(chgstatus & TPS_CHG_TERM) ? " done" :			((chgstatus & (TPS_CHG_USB|TPS_CHG_AC))				? " (charging)" : ""),		(chgstatus & TPS_CHG_TAPER_TMO) ? " taper_tmo" : "",		(chgstatus & TPS_CHG_CHG_TMO) ? " charge_tmo" : "",		(chgstatus & TPS_CHG_PRECHG_TMO) ? " prechg_tmo" : "",		(chgstatus & TPS_CHG_TEMP_ERR) ? " temp_err" : "");}static void dbg_regstat(char *buf, size_t len, u8 regstatus){	snprintf(buf, len, "%02x %s%s%s%s%s%s%s%s\n",		regstatus,		(regstatus & TPS_REG_ONOFF) ? "off" : "(on)",		(regstatus & TPS_REG_COVER) ? " uncover" : "",		(regstatus & TPS_REG_UVLO) ? " UVLO" : "",		(regstatus & TPS_REG_NO_CHG) ? " NO_CHG" : "",		(regstatus & TPS_REG_PG_LD02) ? " ld02_bad" : "",		(regstatus & TPS_REG_PG_LD01) ? " ld01_bad" : "",		(regstatus & TPS_REG_PG_MAIN) ? " main_bad" : "",		(regstatus & TPS_REG_PG_CORE) ? " core_bad" : "");}static void dbg_chgconf(int por, char *buf, size_t len, u8 chgconfig){	const char *hibit;	if (por)		hibit = (chgconfig & TPS_CHARGE_POR)				? "POR=69ms" : "POR=1sec";	else		hibit = (chgconfig & TPS65013_AUA) ? "AUA" : "";	snprintf(buf, len, "%02x %s%s%s AC=%d%% USB=%dmA %sCharge\n",		chgconfig, hibit,		(chgconfig & TPS_CHARGE_RESET) ? " reset" : "",		(chgconfig & TPS_CHARGE_FAST) ? " fast" : "",		({int p; switch ((chgconfig >> 3) & 3) {		case 3:		p = 100; break;		case 2:		p = 75; break;		case 1:		p = 50; break;		default:	p = 25; break;		}; p; }),		(chgconfig & TPS_VBUS_CHARGING)			? ((chgconfig & TPS_VBUS_500MA) ? 500 : 100)			: 0,		(chgconfig & TPS_CHARGE_ENABLE) ? "" : "No");}#endif#ifdef	DEBUGstatic void show_chgstatus(const char *label, u8 chgstatus){	char buf [100];	dbg_chgstat(buf, sizeof buf, chgstatus);	pr_debug("%s: %s %s", DRIVER_NAME, label, buf);}static void show_regstatus(const char *label, u8 regstatus){	char buf [100];	dbg_regstat(buf, sizeof buf, regstatus);	pr_debug("%s: %s %s", DRIVER_NAME, label, buf);}static void show_chgconfig(int por, const char *label, u8 chgconfig){	char buf [100];	dbg_chgconf(por, buf, sizeof buf, chgconfig);	pr_debug("%s: %s %s", DRIVER_NAME, label, buf);}#elsestatic inline void show_chgstatus(const char *label, u8 chgstatus) { }static inline void show_regstatus(const char *label, u8 chgstatus) { }static inline void show_chgconfig(int por, const char *label, u8 chgconfig) { }#endif#ifdef	CONFIG_DEBUG_FSstatic int dbg_show(struct seq_file *s, void *_){	struct tps65010	*tps = s->private;	u8		value, v2;	unsigned	i;	char		buf[100];	const char	*chip;	switch (tps->model) {	case TPS65010:	chip = "tps65010"; break;	case TPS65011:	chip = "tps65011"; break;	case TPS65012:	chip = "tps65012"; break;	case TPS65013:	chip = "tps65013"; break;	default:	chip = NULL; break;	}	seq_printf(s, "driver  %s\nversion %s\nchip    %s\n\n",			DRIVER_NAME, DRIVER_VERSION, chip);	down(&tps->lock);	/* FIXME how can we tell whether a battery is present?	 * likely involves a charge gauging chip (like BQ26501).	 */	seq_printf(s, "%scharging\n\n", tps->charging ? "" : "(not) ");	/* registers for monitoring battery charging and status; note	 * that reading chgstat and regstat may ack IRQs...	 */	value = i2c_smbus_read_byte_data(&tps->client, TPS_CHGCONFIG);	dbg_chgconf(tps->por, buf, sizeof buf, value);	seq_printf(s, "chgconfig %s", buf);	value = i2c_smbus_read_byte_data(&tps->client, TPS_CHGSTATUS);	dbg_chgstat(buf, sizeof buf, value);	seq_printf(s, "chgstat   %s", buf);	value = i2c_smbus_read_byte_data(&tps->client, TPS_MASK1);	dbg_chgstat(buf, sizeof buf, value);	seq_printf(s, "mask1     %s", buf);	/* ignore ackint1 */	value = i2c_smbus_read_byte_data(&tps->client, TPS_REGSTATUS);	dbg_regstat(buf, sizeof buf, value);	seq_printf(s, "regstat   %s", buf);	value = i2c_smbus_read_byte_data(&tps->client, TPS_MASK2);	dbg_regstat(buf, sizeof buf, value);	seq_printf(s, "mask2     %s\n", buf);	/* ignore ackint2 */	(void) schedule_delayed_work(&tps->work, POWER_POLL_DELAY);	/* VMAIN voltage, enable lowpower, etc */	value = i2c_smbus_read_byte_data(&tps->client, TPS_VDCDC1);	seq_printf(s, "vdcdc1    %02x\n", value);	/* VCORE voltage, vibrator on/off */	value = i2c_smbus_read_byte_data(&tps->client, TPS_VDCDC2);	seq_printf(s, "vdcdc2    %02x\n", value);	/* both LD0s, and their lowpower behavior */	value = i2c_smbus_read_byte_data(&tps->client, TPS_VREGS1);	seq_printf(s, "vregs1    %02x\n\n", value);	/* LEDs and GPIOs */	value = i2c_smbus_read_byte_data(&tps->client, TPS_LED1_ON);	v2 = i2c_smbus_read_byte_data(&tps->client, TPS_LED1_PER);	seq_printf(s, "led1 %s, on=%02x, per=%02x, %d/%d msec\n",		(value & 0x80)			? ((v2 & 0x80) ? "on" : "off")			: ((v2 & 0x80) ? "blink" : "(nPG)"),		value, v2,		(value & 0x7f) * 10, (v2 & 0x7f) * 100);	value = i2c_smbus_read_byte_data(&tps->client, TPS_LED2_ON);	v2 = i2c_smbus_read_byte_data(&tps->client, TPS_LED2_PER);	seq_printf(s, "led2 %s, on=%02x, per=%02x, %d/%d msec\n",		(value & 0x80)			? ((v2 & 0x80) ? "on" : "off")			: ((v2 & 0x80) ? "blink" : "off"),		value, v2,		(value & 0x7f) * 10, (v2 & 0x7f) * 100);	value = i2c_smbus_read_byte_data(&tps->client, TPS_DEFGPIO);	v2 = i2c_smbus_read_byte_data(&tps->client, TPS_MASK3);	seq_printf(s, "defgpio %02x mask3 %02x\n", value, v2);	for (i = 0; i < 4; i++) {		if (value & (1 << (4 + i)))			seq_printf(s, "  gpio%d-out %s\n", i + 1,				(value & (1 << i)) ? "low" : "hi ");		else			seq_printf(s, "  gpio%d-in  %s %s %s\n", i + 1,				(value & (1 << i)) ? "hi " : "low",				(v2 & (1 << i)) ? "no-irq" : "irq",				(v2 & (1 << (4 + i))) ? "rising" : "falling");	}	up(&tps->lock);	return 0;}static int dbg_tps_open(struct inode *inode, struct file *file){	return single_open(file, dbg_show, inode->u.generic_ip);}static struct file_operations debug_fops = {	.open		= dbg_tps_open,	.read		= seq_read,	.llseek		= seq_lseek,	.release	= single_release,};#define	DEBUG_FOPS	&debug_fops#else#define	DEBUG_FOPS	NULL#endif/*-------------------------------------------------------------------------*//* handle IRQS in a task context, so we can use I2C calls */static void tps65010_interrupt(struct tps65010 *tps){	u8 tmp = 0, mask, poll;	/* IRQs won't trigger irqs for certain events, but we can get	 * others by polling (normally, with external power applied).	 */	poll = 0;	/* regstatus irqs */	if (tps->nmask2) {		tmp = i2c_smbus_read_byte_data(&tps->client, TPS_REGSTATUS);		mask = tmp ^ tps->regstatus;		tps->regstatus = tmp;		mask &= tps->nmask2;	} else		mask = 0;	if (mask) {		tps->regstatus =  tmp;		/* may need to shut something down ... */		/* "off" usually means deep sleep */		if (tmp & TPS_REG_ONOFF) {			pr_info("%s: power off button\n", DRIVER_NAME);#if 0			/* REVISIT:  this might need its own workqueue			 * plus tweaks including deadlock avoidance ...			 */			software_suspend();#endif			poll = 1;		}	}	/* chgstatus irqs */	if (tps->nmask1) {		tmp = i2c_smbus_read_byte_data(&tps->client, TPS_CHGSTATUS);		mask = tmp ^ tps->chgstatus;		tps->chgstatus = tmp;		mask &= tps->nmask1;	} else		mask = 0;	if (mask) {		unsigned	charging = 0;		show_chgstatus("chg/irq", tmp);		if (tmp & (TPS_CHG_USB|TPS_CHG_AC))			show_chgconfig(tps->por, "conf", tps->chgconf);		/* Unless it was turned off or disabled, we charge any		 * battery whenever there's power available for it		 * and the charger hasn't been disabled.		 */		if (!(tps->chgstatus & ~(TPS_CHG_USB|TPS_CHG_AC))				&& (tps->chgstatus & (TPS_CHG_USB|TPS_CHG_AC))				&& (tps->chgconf & TPS_CHARGE_ENABLE)				) {			if (tps->chgstatus & TPS_CHG_USB) {				/* VBUS options are readonly until reconnect */				if (mask & TPS_CHG_USB)					set_bit(FLAG_VBUS_CHANGED, &tps->flags);				charging = 1;			} else if (tps->chgstatus & TPS_CHG_AC)				charging = 1;		}		if (charging != tps->charging) {			tps->charging = charging;			pr_info("%s: battery %scharging\n",				DRIVER_NAME, charging ? "" :				((tps->chgstatus & (TPS_CHG_USB|TPS_CHG_AC))					? "NOT " : "dis"));		}	}	/* always poll to detect (a) power removal, without tps65013	 * NO_CHG IRQ; or (b) restart of charging after stop.	 */	if ((tps->model != TPS65013 || !tps->charging)			&& (tps->chgstatus & (TPS_CHG_USB|TPS_CHG_AC)))		poll = 1;	if (poll)		(void) schedule_delayed_work(&tps->work, POWER_POLL_DELAY);	/* also potentially gpio-in rise or fall */}/* handle IRQs and polling using keventd for now */static void tps65010_work(void *_tps){	struct tps65010		*tps = _tps;	down(&tps->lock);	tps65010_interrupt(tps);	if (test_and_clear_bit(FLAG_VBUS_CHANGED, &tps->flags)) {		int	status;		u8	chgconfig, tmp;		chgconfig = i2c_smbus_read_byte_data(&tps->client,					TPS_CHGCONFIG);		chgconfig &= ~(TPS_VBUS_500MA | TPS_VBUS_CHARGING);		if (tps->vbus == 500)			chgconfig |= TPS_VBUS_500MA | TPS_VBUS_CHARGING;		else if (tps->vbus >= 100)			chgconfig |= TPS_VBUS_CHARGING;		status = i2c_smbus_write_byte_data(&tps->client,				TPS_CHGCONFIG, chgconfig);		/* vbus update fails unless VBUS is connected! */		tmp = i2c_smbus_read_byte_data(&tps->client, TPS_CHGCONFIG);		tps->chgconf = tmp;		show_chgconfig(tps->por, "update vbus", tmp);	}	if (test_and_clear_bit(FLAG_IRQ_ENABLE, &tps->flags))		enable_irq(tps->irq);	up(&tps->lock);}static irqreturn_t tps65010_irq(int irq, void *_tps, struct pt_regs *regs){	struct tps65010		*tps = _tps;	disable_irq_nosync(irq);	set_bit(FLAG_IRQ_ENABLE, &tps->flags);	(void) schedule_work(&tps->work);	return IRQ_HANDLED;}/*-------------------------------------------------------------------------*/static struct tps65010 *the_tps;static int __exit tps65010_detach_client(struct i2c_client *client){	struct tps65010		*tps;	tps = container_of(client, struct tps65010, client);#ifdef	CONFIG_ARM	if (machine_is_omap_h2())		omap_free_gpio(58);	if (machine_is_omap_osk())		omap_free_gpio(OMAP_MPUIO(1));#endif	free_irq(tps->irq, tps);	debugfs_remove(tps->file);	if (i2c_detach_client(client) == 0)		kfree(tps);	the_tps = NULL;	return 0;}static int tps65010_noscan(struct i2c_adapter *bus){	/* pure paranoia, in case someone adds another i2c bus	 * after our init section's gone...	 */	return -ENODEV;}/* no error returns, they'd just make bus scanning stop */static int __inittps65010_probe(struct i2c_adapter *bus, int address, int kind){	struct tps65010		*tps;	int			status;	if (the_tps) {		dev_dbg(&bus->dev, "only one %s for now\n", DRIVER_NAME);		return 0;	}	tps = kzalloc(sizeof *tps, GFP_KERNEL);	if (!tps)		return 0;	init_MUTEX(&tps->lock);	INIT_WORK(&tps->work, tps65010_work, tps);	tps->irq = -1;	tps->client.addr = address;	tps->client.adapter = bus;	tps->client.driver = &tps65010_driver;	strlcpy(tps->client.name, DRIVER_NAME, I2C_NAME_SIZE);	status = i2c_attach_client(&tps->client);	if (status < 0) {		dev_dbg(&bus->dev, "can't attach %s to device %d, err %d\n",				DRIVER_NAME, address, status);		goto fail1;	}#ifdef	CONFIG_ARM	if (machine_is_omap_h2()) {		tps->model = TPS65010;		omap_cfg_reg(W4_GPIO58);		tps->irq = OMAP_GPIO_IRQ(58);		omap_request_gpio(58);		omap_set_gpio_direction(58, 1);		set_irq_type(tps->irq, IRQT_FALLING);	}	if (machine_is_omap_osk()) {		tps->model = TPS65010;		// omap_cfg_reg(U19_1610_MPUIO1);		tps->irq = OMAP_GPIO_IRQ(OMAP_MPUIO(1));		omap_request_gpio(OMAP_MPUIO(1));

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -