📄 low_i2c.c
字号:
/* * 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 + -