📄 i2c-keywest.c
字号:
/* i2c Support for Apple Keywest I2C Bus Controller Copyright (c) 2001 Benjamin Herrenschmidt <benh@kernel.crashing.org> Original work by Copyright (c) 2000 Philip Edelbrock <phil@stimpy.netroedge.com> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. Changes: 2001/12/13 BenH New implementation 2001/12/15 BenH Add support for "byte" and "quick" transfers. Add i2c_xfer routine. 2003/09/21 BenH Rework state machine with Paulus help 2004/01/21 BenH Merge in Greg KH changes, polled mode is back 2004/02/05 BenH Merge 64 bits fixes from the g5 ppc64 tree My understanding of the various modes supported by keywest are: - Dumb mode : not implemented, probably direct tweaking of lines - Standard mode : simple i2c transaction of type S Addr R/W A Data A Data ... T - Standard sub mode : combined 8 bit subaddr write with data read S Addr R/W A SubAddr A Data A Data ... T - Combined mode : Subaddress and Data sequences appended with no stop S Addr R/W A SubAddr S Addr R/W A Data A Data ... T Currently, this driver uses only Standard mode for i2c xfer, and smbus byte & quick transfers ; and uses StandardSub mode for other smbus transfers instead of combined as we need that for the sound driver to be happy*/#include <linux/config.h>#include <linux/module.h>#include <linux/kernel.h>#include <linux/ioport.h>#include <linux/pci.h>#include <linux/types.h>#include <linux/delay.h>#include <linux/i2c.h>#include <linux/init.h>#include <linux/mm.h>#include <linux/timer.h>#include <linux/spinlock.h>#include <linux/completion.h>#include <linux/interrupt.h>#include <asm/io.h>#include <asm/prom.h>#include <asm/machdep.h>#include <asm/pmac_feature.h>#include <asm/pmac_low_i2c.h>#include "i2c-keywest.h"#undef POLLED_MODE/* Some debug macros */#define WRONG_STATE(name) do {\ pr_debug("KW: wrong state. Got %s, state: %s (isr: %02x)\n", \ name, __kw_state_names[iface->state], isr); \ } while(0)#ifdef DEBUGstatic const char *__kw_state_names[] = { "state_idle", "state_addr", "state_read", "state_write", "state_stop", "state_dead"};#endif /* DEBUG */MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");MODULE_DESCRIPTION("I2C driver for Apple's Keywest");MODULE_LICENSE("GPL");MODULE_PARM(probe, "i");static int probe = 0;#ifdef POLLED_MODE/* Don't schedule, the g5 fan controller is too * timing sensitive */static u8wait_interrupt(struct keywest_iface* iface){ int i; u8 isr; for (i = 0; i < 200000; i++) { isr = read_reg(reg_isr) & KW_I2C_IRQ_MASK; if (isr != 0) return isr; udelay(10); } return isr;}#endif /* POLLED_MODE */static voiddo_stop(struct keywest_iface* iface, int result){ write_reg(reg_control, KW_I2C_CTL_STOP); iface->state = state_stop; iface->result = result;}/* Main state machine for standard & standard sub mode */static voidhandle_interrupt(struct keywest_iface *iface, u8 isr){ int ack; if (isr == 0) { if (iface->state != state_stop) { pr_debug("KW: Timeout !\n"); do_stop(iface, -EIO); } if (iface->state == state_stop) { ack = read_reg(reg_status); if (!(ack & KW_I2C_STAT_BUSY)) { iface->state = state_idle; write_reg(reg_ier, 0x00);#ifndef POLLED_MODE complete(&iface->complete);#endif /* POLLED_MODE */ } } return; } if (isr & KW_I2C_IRQ_ADDR) { ack = read_reg(reg_status); if (iface->state != state_addr) { write_reg(reg_isr, KW_I2C_IRQ_ADDR); WRONG_STATE("KW_I2C_IRQ_ADDR"); do_stop(iface, -EIO); return; } if ((ack & KW_I2C_STAT_LAST_AAK) == 0) { iface->state = state_stop; iface->result = -ENODEV; pr_debug("KW: NAK on address\n"); } else { /* Handle rw "quick" mode */ if (iface->datalen == 0) { do_stop(iface, 0); } else if (iface->read_write == I2C_SMBUS_READ) { iface->state = state_read; if (iface->datalen > 1) write_reg(reg_control, KW_I2C_CTL_AAK); } else { iface->state = state_write; write_reg(reg_data, *(iface->data++)); iface->datalen--; } } write_reg(reg_isr, KW_I2C_IRQ_ADDR); } if (isr & KW_I2C_IRQ_DATA) { if (iface->state == state_read) { *(iface->data++) = read_reg(reg_data); write_reg(reg_isr, KW_I2C_IRQ_DATA); iface->datalen--; if (iface->datalen == 0) iface->state = state_stop; else if (iface->datalen == 1) write_reg(reg_control, 0); } else if (iface->state == state_write) { /* Check ack status */ ack = read_reg(reg_status); if ((ack & KW_I2C_STAT_LAST_AAK) == 0) { pr_debug("KW: nack on data write (%x): %x\n", iface->data[-1], ack); do_stop(iface, -EIO); } else if (iface->datalen) { write_reg(reg_data, *(iface->data++)); iface->datalen--; } else { write_reg(reg_control, KW_I2C_CTL_STOP); iface->state = state_stop; iface->result = 0; } write_reg(reg_isr, KW_I2C_IRQ_DATA); } else { write_reg(reg_isr, KW_I2C_IRQ_DATA); WRONG_STATE("KW_I2C_IRQ_DATA"); if (iface->state != state_stop) do_stop(iface, -EIO); } } if (isr & KW_I2C_IRQ_STOP) { write_reg(reg_isr, KW_I2C_IRQ_STOP); if (iface->state != state_stop) { WRONG_STATE("KW_I2C_IRQ_STOP"); iface->result = -EIO; } iface->state = state_idle; write_reg(reg_ier, 0x00);#ifndef POLLED_MODE complete(&iface->complete);#endif /* POLLED_MODE */ } if (isr & KW_I2C_IRQ_START) write_reg(reg_isr, KW_I2C_IRQ_START);}#ifndef POLLED_MODE/* Interrupt handler */static irqreturn_tkeywest_irq(int irq, void *dev_id, struct pt_regs *regs){ struct keywest_iface *iface = (struct keywest_iface *)dev_id; unsigned long flags; spin_lock_irqsave(&iface->lock, flags); del_timer(&iface->timeout_timer); handle_interrupt(iface, read_reg(reg_isr)); if (iface->state != state_idle) { iface->timeout_timer.expires = jiffies + POLL_TIMEOUT; add_timer(&iface->timeout_timer); } spin_unlock_irqrestore(&iface->lock, flags); return IRQ_HANDLED;}static voidkeywest_timeout(unsigned long data){ struct keywest_iface *iface = (struct keywest_iface *)data; unsigned long flags; pr_debug("timeout !\n"); spin_lock_irqsave(&iface->lock, flags); handle_interrupt(iface, read_reg(reg_isr)); if (iface->state != state_idle) { iface->timeout_timer.expires = jiffies + POLL_TIMEOUT; add_timer(&iface->timeout_timer); } spin_unlock_irqrestore(&iface->lock, flags);}#endif /* POLLED_MODE *//* * SMBUS-type transfer entrypoint */static s32keywest_smbus_xfer( struct i2c_adapter* adap, u16 addr, unsigned short flags, char read_write, u8 command, int size, union i2c_smbus_data* data){ struct keywest_chan* chan = i2c_get_adapdata(adap); struct keywest_iface* iface = chan->iface; int len; u8* buffer; u16 cur_word; int rc = 0; if (iface->state == state_dead) return -ENXIO; /* Prepare datas & select mode */ iface->cur_mode &= ~KW_I2C_MODE_MODE_MASK; switch (size) { case I2C_SMBUS_QUICK: len = 0; buffer = NULL; iface->cur_mode |= KW_I2C_MODE_STANDARD; break; case I2C_SMBUS_BYTE: len = 1; buffer = &data->byte; iface->cur_mode |= KW_I2C_MODE_STANDARD; break; case I2C_SMBUS_BYTE_DATA: len = 1; buffer = &data->byte; iface->cur_mode |= KW_I2C_MODE_STANDARDSUB; break; case I2C_SMBUS_WORD_DATA: len = 2; cur_word = cpu_to_le16(data->word); buffer = (u8 *)&cur_word; iface->cur_mode |= KW_I2C_MODE_STANDARDSUB; break; case I2C_SMBUS_BLOCK_DATA: len = data->block[0]; buffer = &data->block[1]; iface->cur_mode |= KW_I2C_MODE_STANDARDSUB; break; default: return -1; } /* Turn a standardsub read into a combined mode access */ if (read_write == I2C_SMBUS_READ && (iface->cur_mode & KW_I2C_MODE_MODE_MASK) == KW_I2C_MODE_STANDARDSUB) { iface->cur_mode &= ~KW_I2C_MODE_MODE_MASK; iface->cur_mode |= KW_I2C_MODE_COMBINED; } /* Original driver had this limitation */ if (len > 32) len = 32; if (pmac_low_i2c_lock(iface->node)) return -ENXIO; pr_debug("chan: %d, addr: 0x%x, transfer len: %d, read: %d\n", chan->chan_no, addr, len, read_write == I2C_SMBUS_READ); iface->data = buffer; iface->datalen = len; iface->state = state_addr; iface->result = 0; iface->read_write = read_write; /* Setup channel & clear pending irqs */ write_reg(reg_isr, read_reg(reg_isr)); write_reg(reg_mode, iface->cur_mode | (chan->chan_no << 4)); write_reg(reg_status, 0); /* Set up address and r/w bit */ write_reg(reg_addr, (addr << 1) | ((read_write == I2C_SMBUS_READ) ? 0x01 : 0x00)); /* Set up the sub address */ if ((iface->cur_mode & KW_I2C_MODE_MODE_MASK) == KW_I2C_MODE_STANDARDSUB || (iface->cur_mode & KW_I2C_MODE_MODE_MASK) == KW_I2C_MODE_COMBINED) write_reg(reg_subaddr, command);#ifndef POLLED_MODE /* Arm timeout */ iface->timeout_timer.expires = jiffies + POLL_TIMEOUT; add_timer(&iface->timeout_timer);#endif /* Start sending address & enable interrupt*/ write_reg(reg_control, KW_I2C_CTL_XADDR); write_reg(reg_ier, KW_I2C_IRQ_MASK);#ifdef POLLED_MODE pr_debug("using polled mode...\n"); /* State machine, to turn into an interrupt handler */ while(iface->state != state_idle) { unsigned long flags; u8 isr = wait_interrupt(iface); spin_lock_irqsave(&iface->lock, flags); handle_interrupt(iface, isr); spin_unlock_irqrestore(&iface->lock, flags); }#else /* POLLED_MODE */ pr_debug("using interrupt mode...\n"); wait_for_completion(&iface->complete); #endif /* POLLED_MODE */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -