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

📄 low_i2c.c

📁 linux内核源码
💻 C
📖 第 1 页 / 共 3 页
字号:
/* * arch/powerpc/platforms/powermac/low_i2c.c * *  Copyright (C) 2003-2005 Ben. Herrenschmidt (benh@kernel.crashing.org) * *  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. * * The linux i2c layer isn't completely suitable for our needs for various * reasons ranging from too late initialisation to semantics not perfectly * matching some requirements of the apple platform functions etc... * * This file thus provides a simple low level unified i2c interface for * powermac that covers the various types of i2c busses used in Apple machines. * For now, keywest, PMU and SMU, though we could add Cuda, or other bit * banging busses found on older chipstes in earlier machines if we ever need * one of them. * * The drivers in this file are synchronous/blocking. In addition, the * keywest one is fairly slow due to the use of msleep instead of interrupts * as the interrupt is currently used by i2c-keywest. In the long run, we * might want to get rid of those high-level interfaces to linux i2c layer * either completely (converting all drivers) or replacing them all with a * single stub driver on top of this one. Once done, the interrupt will be * available for our use. */#undef DEBUG#undef DEBUG_LOW#include <linux/types.h>#include <linux/sched.h>#include <linux/init.h>#include <linux/module.h>#include <linux/adb.h>#include <linux/pmu.h>#include <linux/delay.h>#include <linux/completion.h>#include <linux/platform_device.h>#include <linux/interrupt.h>#include <linux/timer.h>#include <linux/mutex.h>#include <asm/keylargo.h>#include <asm/uninorth.h>#include <asm/io.h>#include <asm/prom.h>#include <asm/machdep.h>#include <asm/smu.h>#include <asm/pmac_pfunc.h>#include <asm/pmac_low_i2c.h>#ifdef DEBUG#define DBG(x...) do {\		printk(KERN_DEBUG "low_i2c:" x);	\	} while(0)#else#define DBG(x...)#endif#ifdef DEBUG_LOW#define DBG_LOW(x...) do {\		printk(KERN_DEBUG "low_i2c:" x);	\	} while(0)#else#define DBG_LOW(x...)#endifstatic int pmac_i2c_force_poll = 1;/* * A bus structure. Each bus in the system has such a structure associated. */struct pmac_i2c_bus{	struct list_head	link;	struct device_node	*controller;	struct device_node	*busnode;	int			type;	int			flags;	struct i2c_adapter	*adapter;	void			*hostdata;	int			channel;	/* some hosts have multiple */	int			mode;		/* current mode */	struct mutex		mutex;	int			opened;	int			polled;		/* open mode */	struct platform_device	*platform_dev;	/* ops */	int (*open)(struct pmac_i2c_bus *bus);	void (*close)(struct pmac_i2c_bus *bus);	int (*xfer)(struct pmac_i2c_bus *bus, u8 addrdir, int subsize,		    u32 subaddr, u8 *data, int len);};static LIST_HEAD(pmac_i2c_busses);/* * Keywest implementation */struct pmac_i2c_host_kw{	struct mutex		mutex;		/* Access mutex for use by						 * i2c-keywest */	void __iomem		*base;		/* register base address */	int			bsteps;		/* register stepping */	int			speed;		/* speed */	int			irq;	u8			*data;	unsigned		len;	int			state;	int			rw;	int			polled;	int			result;	struct completion	complete;	spinlock_t		lock;	struct timer_list	timeout_timer;};/* Register indices */typedef enum {	reg_mode = 0,	reg_control,	reg_status,	reg_isr,	reg_ier,	reg_addr,	reg_subaddr,	reg_data} reg_t;/* The Tumbler audio equalizer can be really slow sometimes */#define KW_POLL_TIMEOUT		(2*HZ)/* Mode register */#define KW_I2C_MODE_100KHZ	0x00#define KW_I2C_MODE_50KHZ	0x01#define KW_I2C_MODE_25KHZ	0x02#define KW_I2C_MODE_DUMB	0x00#define KW_I2C_MODE_STANDARD	0x04#define KW_I2C_MODE_STANDARDSUB	0x08#define KW_I2C_MODE_COMBINED	0x0C#define KW_I2C_MODE_MODE_MASK	0x0C#define KW_I2C_MODE_CHAN_MASK	0xF0/* Control register */#define KW_I2C_CTL_AAK		0x01#define KW_I2C_CTL_XADDR	0x02#define KW_I2C_CTL_STOP		0x04#define KW_I2C_CTL_START	0x08/* Status register */#define KW_I2C_STAT_BUSY	0x01#define KW_I2C_STAT_LAST_AAK	0x02#define KW_I2C_STAT_LAST_RW	0x04#define KW_I2C_STAT_SDA		0x08#define KW_I2C_STAT_SCL		0x10/* IER & ISR registers */#define KW_I2C_IRQ_DATA		0x01#define KW_I2C_IRQ_ADDR		0x02#define KW_I2C_IRQ_STOP		0x04#define KW_I2C_IRQ_START	0x08#define KW_I2C_IRQ_MASK		0x0F/* State machine states */enum {	state_idle,	state_addr,	state_read,	state_write,	state_stop,	state_dead};#define WRONG_STATE(name) do {\		printk(KERN_DEBUG "KW: wrong state. Got %s, state: %s " \		       "(isr: %02x)\n",	\		       name, __kw_state_names[host->state], isr); \	} while(0)static const char *__kw_state_names[] = {	"state_idle",	"state_addr",	"state_read",	"state_write",	"state_stop",	"state_dead"};static inline u8 __kw_read_reg(struct pmac_i2c_host_kw *host, reg_t reg){	return readb(host->base + (((unsigned int)reg) << host->bsteps));}static inline void __kw_write_reg(struct pmac_i2c_host_kw *host,				  reg_t reg, u8 val){	writeb(val, host->base + (((unsigned)reg) << host->bsteps));	(void)__kw_read_reg(host, reg_subaddr);}#define kw_write_reg(reg, val)	__kw_write_reg(host, reg, val)#define kw_read_reg(reg)	__kw_read_reg(host, reg)static u8 kw_i2c_wait_interrupt(struct pmac_i2c_host_kw *host){	int i, j;	u8 isr;		for (i = 0; i < 1000; i++) {		isr = kw_read_reg(reg_isr) & KW_I2C_IRQ_MASK;		if (isr != 0)			return isr;		/* This code is used with the timebase frozen, we cannot rely		 * on udelay nor schedule when in polled mode !		 * For now, just use a bogus loop....		 */		if (host->polled) {			for (j = 1; j < 100000; j++)				mb();		} else			msleep(1);	}	return isr;}static void kw_i2c_do_stop(struct pmac_i2c_host_kw *host, int result){	kw_write_reg(reg_control, KW_I2C_CTL_STOP);	host->state = state_stop;	host->result = result;}static void kw_i2c_handle_interrupt(struct pmac_i2c_host_kw *host, u8 isr){	u8 ack;	DBG_LOW("kw_handle_interrupt(%s, isr: %x)\n",		__kw_state_names[host->state], isr);	if (host->state == state_idle) {		printk(KERN_WARNING "low_i2c: Keywest got an out of state"		       " interrupt, ignoring\n");		kw_write_reg(reg_isr, isr);		return;	}	if (isr == 0) {		printk(KERN_WARNING "low_i2c: Timeout in i2c transfer"		       " on keywest !\n");		if (host->state != state_stop) {			kw_i2c_do_stop(host, -EIO);			return;		}		ack = kw_read_reg(reg_status);		if (ack & KW_I2C_STAT_BUSY)			kw_write_reg(reg_status, 0);		host->state = state_idle;		kw_write_reg(reg_ier, 0x00);		if (!host->polled)			complete(&host->complete);		return;	}	if (isr & KW_I2C_IRQ_ADDR) {		ack = kw_read_reg(reg_status);		if (host->state != state_addr) {			WRONG_STATE("KW_I2C_IRQ_ADDR"); 			kw_i2c_do_stop(host, -EIO);		}		if ((ack & KW_I2C_STAT_LAST_AAK) == 0) {			host->result = -ENXIO;			host->state = state_stop;			DBG_LOW("KW: NAK on address\n");		} else {			if (host->len == 0)				kw_i2c_do_stop(host, 0);			else if (host->rw) {				host->state = state_read;				if (host->len > 1)					kw_write_reg(reg_control,						     KW_I2C_CTL_AAK);			} else {				host->state = state_write;				kw_write_reg(reg_data, *(host->data++));				host->len--;			}		}		kw_write_reg(reg_isr, KW_I2C_IRQ_ADDR);	}	if (isr & KW_I2C_IRQ_DATA) {		if (host->state == state_read) {			*(host->data++) = kw_read_reg(reg_data);			host->len--;			kw_write_reg(reg_isr, KW_I2C_IRQ_DATA);			if (host->len == 0)				host->state = state_stop;			else if (host->len == 1)				kw_write_reg(reg_control, 0);		} else if (host->state == state_write) {			ack = kw_read_reg(reg_status);			if ((ack & KW_I2C_STAT_LAST_AAK) == 0) {				DBG_LOW("KW: nack on data write\n");				host->result = -EFBIG;				host->state = state_stop;			} else if (host->len) {				kw_write_reg(reg_data, *(host->data++));				host->len--;			} else				kw_i2c_do_stop(host, 0);		} else {			WRONG_STATE("KW_I2C_IRQ_DATA"); 			if (host->state != state_stop)				kw_i2c_do_stop(host, -EIO);		}		kw_write_reg(reg_isr, KW_I2C_IRQ_DATA);	}	if (isr & KW_I2C_IRQ_STOP) {		kw_write_reg(reg_isr, KW_I2C_IRQ_STOP);		if (host->state != state_stop) {			WRONG_STATE("KW_I2C_IRQ_STOP");			host->result = -EIO;		}		host->state = state_idle;		if (!host->polled)			complete(&host->complete);	}	/* Below should only happen in manual mode which we don't use ... */	if (isr & KW_I2C_IRQ_START)		kw_write_reg(reg_isr, KW_I2C_IRQ_START);}/* Interrupt handler */static irqreturn_t kw_i2c_irq(int irq, void *dev_id){	struct pmac_i2c_host_kw *host = dev_id;	unsigned long flags;	spin_lock_irqsave(&host->lock, flags);	del_timer(&host->timeout_timer);	kw_i2c_handle_interrupt(host, kw_read_reg(reg_isr));	if (host->state != state_idle) {		host->timeout_timer.expires = jiffies + KW_POLL_TIMEOUT;		add_timer(&host->timeout_timer);	}	spin_unlock_irqrestore(&host->lock, flags);	return IRQ_HANDLED;}static void kw_i2c_timeout(unsigned long data){	struct pmac_i2c_host_kw *host = (struct pmac_i2c_host_kw *)data;	unsigned long flags;	spin_lock_irqsave(&host->lock, flags);	kw_i2c_handle_interrupt(host, kw_read_reg(reg_isr));	if (host->state != state_idle) {		host->timeout_timer.expires = jiffies + KW_POLL_TIMEOUT;		add_timer(&host->timeout_timer);	}	spin_unlock_irqrestore(&host->lock, flags);}static int kw_i2c_open(struct pmac_i2c_bus *bus){	struct pmac_i2c_host_kw *host = bus->hostdata;	mutex_lock(&host->mutex);	return 0;}static void kw_i2c_close(struct pmac_i2c_bus *bus){	struct pmac_i2c_host_kw *host = bus->hostdata;	mutex_unlock(&host->mutex);}static int kw_i2c_xfer(struct pmac_i2c_bus *bus, u8 addrdir, int subsize,		       u32 subaddr, u8 *data, int len){	struct pmac_i2c_host_kw *host = bus->hostdata;	u8 mode_reg = host->speed;	int use_irq = host->irq != NO_IRQ && !bus->polled;	/* Setup mode & subaddress if any */	switch(bus->mode) {	case pmac_i2c_mode_dumb:		return -EINVAL;	case pmac_i2c_mode_std:		mode_reg |= KW_I2C_MODE_STANDARD;		if (subsize != 0)			return -EINVAL;		break;	case pmac_i2c_mode_stdsub:		mode_reg |= KW_I2C_MODE_STANDARDSUB;		if (subsize != 1)			return -EINVAL;		break;	case pmac_i2c_mode_combined:		mode_reg |= KW_I2C_MODE_COMBINED;		if (subsize != 1)			return -EINVAL;		break;	}	/* Setup channel & clear pending irqs */	kw_write_reg(reg_isr, kw_read_reg(reg_isr));	kw_write_reg(reg_mode, mode_reg | (bus->channel << 4));	kw_write_reg(reg_status, 0);	/* Set up address and r/w bit, strip possible stale bus number from	 * address top bits	 */	kw_write_reg(reg_addr, addrdir & 0xff);	/* Set up the sub address */	if ((mode_reg & KW_I2C_MODE_MODE_MASK) == KW_I2C_MODE_STANDARDSUB	    || (mode_reg & KW_I2C_MODE_MODE_MASK) == KW_I2C_MODE_COMBINED)		kw_write_reg(reg_subaddr, subaddr);	/* Prepare for async operations */	host->data = data;	host->len = len;	host->state = state_addr;	host->result = 0;	host->rw = (addrdir & 1);	host->polled = bus->polled;	/* Enable interrupt if not using polled mode and interrupt is	 * available	 */	if (use_irq) {		/* Clear completion */		INIT_COMPLETION(host->complete);		/* Ack stale interrupts */		kw_write_reg(reg_isr, kw_read_reg(reg_isr));		/* Arm timeout */		host->timeout_timer.expires = jiffies + KW_POLL_TIMEOUT;		add_timer(&host->timeout_timer);		/* Enable emission */		kw_write_reg(reg_ier, KW_I2C_IRQ_MASK);	}	/* Start sending address */	kw_write_reg(reg_control, KW_I2C_CTL_XADDR);	/* Wait for completion */	if (use_irq)		wait_for_completion(&host->complete);	else {		while(host->state != state_idle) {			unsigned long flags;			u8 isr = kw_i2c_wait_interrupt(host);			spin_lock_irqsave(&host->lock, flags);			kw_i2c_handle_interrupt(host, isr);			spin_unlock_irqrestore(&host->lock, flags);		}	}	/* Disable emission */	kw_write_reg(reg_ier, 0);	return host->result;}static struct pmac_i2c_host_kw *__init kw_i2c_host_init(struct device_node *np){	struct pmac_i2c_host_kw *host;	const u32		*psteps, *prate, *addrp;	u32			steps;	host = kzalloc(sizeof(struct pmac_i2c_host_kw), GFP_KERNEL);	if (host == NULL) {		printk(KERN_ERR "low_i2c: Can't allocate host for %s\n",		       np->full_name);		return NULL;	}	/* Apple is kind enough to provide a valid AAPL,address property	 * on all i2c keywest nodes so far ... we would have to fallback	 * to macio parsing if that wasn't the case	 */	addrp = of_get_property(np, "AAPL,address", NULL);	if (addrp == NULL) {		printk(KERN_ERR "low_i2c: Can't find address for %s\n",		       np->full_name);		kfree(host);		return NULL;	}	mutex_init(&host->mutex);	init_completion(&host->complete);	spin_lock_init(&host->lock);	init_timer(&host->timeout_timer);	host->timeout_timer.function = kw_i2c_timeout;	host->timeout_timer.data = (unsigned long)host;

⌨️ 快捷键说明

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