📄 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. 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/module.h>#include <linux/config.h>#include <linux/version.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 <asm/io.h>#include <asm/prom.h>#include <asm/machdep.h>#include <asm/pmac_feature.h>#include "i2c-keywest.h"#undef POLLED_MODE#define DBG(x...) do {\ if (debug > 0) \ printk(KERN_DEBUG "KW:" x); \ } while(0)MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");MODULE_DESCRIPTION("I2C driver for Apple's Keywest");MODULE_LICENSE("GPL");MODULE_PARM(probe, "i");MODULE_PARM(debug, "i");EXPORT_NO_SYMBOLS;int probe = 0;int debug = 0;static struct keywest_iface *ifaces = NULL;#ifdef POLLED_MODE/* This isn't fast, but will go once I implement interrupt with * proper timeout */static u8wait_interrupt(struct keywest_iface* iface){ int i; u8 isr; for (i = 0; i < POLL_TIMEOUT; i++) { isr = read_reg(reg_isr) & KW_I2C_IRQ_MASK; if (isr != 0) return isr; current->state = TASK_UNINTERRUPTIBLE; schedule_timeout(1); } return isr;}#endif /* POLLED_MODE */static voiddo_stop(struct keywest_iface* iface, int result){ write_reg(reg_control, read_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; DBG("handle_interrupt(), got: %x, status: %x, state: %d\n", isr, read_reg(reg_status), iface->state); if (isr == 0 && iface->state != state_stop) { do_stop(iface, -1); return; } if (isr & KW_I2C_IRQ_STOP && iface->state != state_stop) { iface->result = -1; iface->state = state_stop; } switch(iface->state) { case state_addr: if (!(isr & KW_I2C_IRQ_ADDR)) { do_stop(iface, -1); break; } ack = read_reg(reg_status); DBG("ack on set address: %x\n", ack); if ((ack & KW_I2C_STAT_LAST_AAK) == 0) { do_stop(iface, -1); break; } /* 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, read_reg(reg_control) | KW_I2C_CTL_AAK); } else { iface->state = state_write; DBG("write byte: %x\n", *(iface->data)); write_reg(reg_data, *(iface->data++)); iface->datalen--; } break; case state_read: if (!(isr & KW_I2C_IRQ_DATA)) { do_stop(iface, -1); break; } *(iface->data++) = read_reg(reg_data); DBG("read byte: %x\n", *(iface->data-1)); iface->datalen--; if (iface->datalen == 0) iface->state = state_stop; else write_reg(reg_control, 0); break; case state_write: if (!(isr & KW_I2C_IRQ_DATA)) { do_stop(iface, -1); break; } /* Check ack status */ ack = read_reg(reg_status); DBG("ack on data write: %x\n", ack); if ((ack & KW_I2C_STAT_LAST_AAK) == 0) { do_stop(iface, -1); break; } if (iface->datalen) { DBG("write byte: %x\n", *(iface->data)); write_reg(reg_data, *(iface->data++)); iface->datalen--; } else do_stop(iface, 0); break; case state_stop: if (!(isr & KW_I2C_IRQ_STOP) && (++iface->stopretry) < 10) do_stop(iface, -1); else { iface->state = state_idle; write_reg(reg_control, 0x00); write_reg(reg_ier, 0x00);#ifndef POLLED_MODE complete(&iface->complete);#endif /* POLLED_MODE */ } break; } write_reg(reg_isr, isr);}#ifndef POLLED_MODE/* Interrupt handler */static voidkeywest_irq(int irq, void *dev_id, struct pt_regs *regs){ struct keywest_iface *iface = (struct keywest_iface *)dev_id; spin_lock(&iface->lock); 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(&iface->lock);}static voidkeywest_timeout(unsigned long data){ struct keywest_iface *iface = (struct keywest_iface *)data; DBG("timeout !\n"); spin_lock_irq(&iface->lock); 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(&iface->lock);}#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 = (struct keywest_chan*)adap->data; struct keywest_iface* iface = chan->iface; int len; u8* buffer; u16 cur_word; int rc = 0; if (iface->state == state_dead) return -1; /* 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; } /* Original driver had this limitation */ if (len > 32) len = 32; down(&iface->sem); DBG("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->stopretry = 0; iface->read_write = read_write; /* Setup channel & clear pending irqs */ write_reg(reg_mode, iface->cur_mode | (chan->chan_no << 4)); write_reg(reg_isr, read_reg(reg_isr)); 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); /* Arm timeout */ iface->timeout_timer.expires = jiffies + POLL_TIMEOUT; add_timer(&iface->timeout_timer); /* Start sending address & enable interrupt*/ write_reg(reg_control, read_reg(reg_control) | KW_I2C_CTL_XADDR); write_reg(reg_ier, KW_I2C_IRQ_MASK);#ifdef POLLED_MODE DBG("using polled mode...\n");
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -