From a7c7130d916c1f7e0d27ad9b338912496ad53089 Mon Sep 17 00:00:00 2001 From: Alison Wang Date: Thu, 4 Aug 2011 09:59:46 +0800 Subject: [PATCH 25/52] Add I2C driver for MCF5445x/MCF547x/MCF548x. Add common I2C driver for MCF5445x/MCF547x/MCF548x and add I2C slave mode support for MCF5445x. Configure I2C adaptor as slave mode and Support I2C adaptor as a "eeprom-like" slave device. Signed-off-by: Alison Wang --- arch/m68k/include/asm/mcfi2c.h | 57 +++ drivers/i2c/busses/Kconfig | 24 ++ drivers/i2c/busses/Makefile | 2 + drivers/i2c/busses/i2c-algo-mcf.h | 23 ++ drivers/i2c/busses/i2c-mcf-slave.c | 358 ++++++++++++++++++ drivers/i2c/busses/i2c-mcf.c | 698 ++++++++++++++++++++++++++++++++++++ 6 files changed, 1162 insertions(+), 0 deletions(-) create mode 100644 arch/m68k/include/asm/mcfi2c.h create mode 100644 drivers/i2c/busses/i2c-algo-mcf.h create mode 100644 drivers/i2c/busses/i2c-mcf-slave.c create mode 100644 drivers/i2c/busses/i2c-mcf.c --- /dev/null +++ b/arch/m68k/include/asm/mcfi2c.h @@ -0,0 +1,57 @@ +/* + * mcfi2c.h -- ColdFire mcfv4/mcfv4e i2c controller support. + * Copyright (C) 2009-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ +#ifndef MCF_I2C_H +#define MCF_I2C_H + +/* Register read/write macros */ +#if defined(CONFIG_M547X_8X) +#define MCF_I2AR MCF_REG08(0x008F00) /* I2C Address */ +#define MCF_I2FDR MCF_REG08(0x008F04) /* I2C Frequency Divider */ +#define MCF_I2CR MCF_REG08(0x008F08) /* I2C Control */ +#define MCF_I2SR MCF_REG08(0x008F0C) /* I2C Status */ +#define MCF_I2DR MCF_REG08(0x008F10) /* I2C Data I/O */ +#define MCF_I2ICR MCF_REG08(0x008F20) /* I2C Interrupt Control */ +#elif defined(CONFIG_M5445X) || defined(CONFIG_M5441X) +#define MCF_I2AR (*(volatile u8 *)(0xFC058000)) /* I2C Address */ +/* I2C Frequency Divider */ +#define MCF_I2FDR (*(volatile u8 *)(0xFC058004)) +#define MCF_I2CR (*(volatile u8 *)(0xFC058008)) /* I2C Control */ +#define MCF_I2SR (*(volatile u8 *)(0xFC05800C)) /* I2C Status */ +#define MCF_I2DR (*(volatile u8 *)(0xFC058010)) /* I2C Data I/O */ +#endif + +/* Bit definitions and macros for MCF_I2C_I2AR */ +#define MCF_I2AR_ADR(x) (((x)&0x7F)<<1) + +/* Bit definitions and macros for MCF_I2C_I2FDR */ +#define MCF_I2FDR_IC(x) (((x)&0x3F)<<0) + +/* Bit definitions and macros for MCF_I2C_I2CR */ +#define MCF_I2CR_RSTA (0x04) +#define MCF_I2CR_TXAK (0x08) +#define MCF_I2CR_MTX (0x10) +#define MCF_I2CR_MSTA (0x20) +#define MCF_I2CR_IIEN (0x40) +#define MCF_I2CR_IEN (0x80) + +/* Bit definitions and macros for MCF_I2C_I2SR */ +#define MCF_I2SR_RXAK (0x01) +#define MCF_I2SR_IIF (0x02) +#define MCF_I2SR_SRW (0x04) +#define MCF_I2SR_IAL (0x10) +#define MCF_I2SR_IBB (0x20) +#define MCF_I2SR_IAAS (0x40) +#define MCF_I2SR_ICF (0x80) + +/* Bit definitions and macros for MCF_I2C_I2ICR */ +#if defined(CONFIG_M547X_8X) +#define MCF_I2ICR_IE (0x01) +#define MCF_I2ICR_RE (0x02) +#define MCF_I2ICR_TE (0x04) +#define MCF_I2ICR_BNBE (0x08) +#endif + +/********************************************************************/ +#endif --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -431,6 +431,30 @@ config I2C_IXP2000 This driver is deprecated and will be dropped soon. Use i2c-gpio instead. +config I2C_MCF + tristate "MCF ColdFire I2C Interface" + depends on I2C && COLDFIRE + help + If you say yes to this option, support will be included for the + I2C on most ColdFire CPUs + + This driver can also be built as a module. If so, the module + will be called i2c-mcf. + +config I2C_MCF_SLAVE + tristate "MCF ColdFire I2C Slave Interface" + depends on !(I2C_MCF) + default n + help + mcf i2c adapter slave mode, only supported on mcf5445x platform. + +config I2C_SLAVE_TEST + bool "I2C Slave Mode Test Configuration" + depends on I2C_MCF_SLAVE + default y + help + This configuration help to test I2C slave mode + config I2C_MPC tristate "MPC107/824x/85xx/512x/52xx/83xx/86xx" depends on PPC32 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -77,5 +77,7 @@ obj-$(CONFIG_I2C_SIBYTE) += i2c-sibyte.o obj-$(CONFIG_I2C_STUB) += i2c-stub.o obj-$(CONFIG_SCx200_ACB) += scx200_acb.o obj-$(CONFIG_SCx200_I2C) += scx200_i2c.o +obj-$(CONFIG_I2C_MCF) += i2c-mcf.o +obj-$(CONFIG_I2C_MCF_SLAVE) += i2c-mcf-slave.o ccflags-$(CONFIG_I2C_DEBUG_BUS) := -DDEBUG --- /dev/null +++ b/drivers/i2c/busses/i2c-algo-mcf.h @@ -0,0 +1,23 @@ +#ifndef I2C_ALGO_MCF_H +#define I2C_ALGO_MCF_H 1 + +/* --- Defines for pcf-adapters --------------------------------------- */ +#include + +struct i2c_algo_mcf_data { + void *data; /* private data for lolevel routines */ + void (*setmcf) (void *data, int ctl, int val); + int (*getmcf) (void *data, int ctl); + int (*getown) (void *data); + int (*getclock) (void *data); + void (*waitforpin) (void); + /* local settings */ + int udelay; + int mdelay; + int timeout; +}; + +int i2c_mcf_add_bus(struct i2c_adapter *); +int i2c_mcf_del_bus(struct i2c_adapter *); + +#endif /* I2C_ALGO_MCF_H */ --- /dev/null +++ b/drivers/i2c/busses/i2c-mcf-slave.c @@ -0,0 +1,358 @@ +/* + * i2c-mcf-slave.c - support adpater slave mode, now only support + * mcf5445x platform + * + * Copyright (C) 2009-2011 Freescale Semiconductor, Inc. All Rights Reserved. + * Lanttor Guo + * + * 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. +*/ + +#ifdef CONFIG_I2C_SLAVE_TEST +#define DEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(CONFIG_M5445X) +#include +#endif + +#define IRQ (64+30) +#define SLAVE_HANDLER_NAME "mcf-i2c slave handler" +#define I2C_BUFFER_SIZE 50 + +/* Structure for storing I2C transfer data */ +struct i2c_buffer { + int tx_index; /* TX index */ + int rx_index; /* RX index */ + u16 length; /* Length of the buffer in bytes */ + u8 buf[I2C_BUFFER_SIZE]; /* Data buffer */ +}; + +struct i2c_buffer i2c_tx_buffer; +struct i2c_buffer i2c_rx_buffer; + +u8 *tx_string = "abcdefghijklmnopqrstuvwxyz0123456789)!@#$%^&*([]."; + +/* + * I2C slave mode interrupt handler + * + */ +static irqreturn_t i2c_slave_handler(int this_irq, void *dev_id) +{ + u8 dummy_read; + int tmp_index; + +#ifdef DEBUG + printk(KERN_INFO "i2c adapter slave mode irq handler.\n"); +#endif + + /* Clear I2C interupt flag */ + MCF_I2SR = ~MCF_I2SR_IIF; + + /* Check if this device is in Master or Slave Mode. */ + if (MCF_I2CR & MCF_I2CR_MSTA) { + /* Master mode, do nothing here */ + printk(KERN_INFO "i2c master mode at %s(), do nothing!\n", + __func__); + return IRQ_NONE; + } else { + /* Slave Mode - Check if Arbitration Lost. */ + if (MCF_I2SR & MCF_I2SR_IAL) { + + #ifdef DEBUG + printk(KERN_INFO "Arbitration Lost.\n"); + #endif + + /* Clear IAL bit */ + MCF_I2SR &= ~MCF_I2SR_IAL; + + /* Arbitration Lost - + * Check if this device is being addressed as slave. + *(If not, nothing more needs to be done.) + */ + if (MCF_I2SR & MCF_I2SR_IAAS) { + /* Addressed as slave - + * Check if master was reading from slave or + * writing to slave. + */ + if (MCF_I2SR & MCF_I2SR_SRW) { + /* Set tx_index to 0 */ + if (i2c_tx_buffer.length == 0) { + i2c_tx_buffer.length = + I2C_BUFFER_SIZE; + i2c_tx_buffer.tx_index = 0; + } + + /* Master was reading from slave - + * Set Transmit Mode. + */ + MCF_I2CR |= MCF_I2CR_MTX; + + /* Write data to MBDR. */ + tmp_index = i2c_tx_buffer.tx_index++; + MCF_I2DR = i2c_tx_buffer.buf[tmp_index]; + i2c_tx_buffer.length--; + + #ifdef DEBUG + printk(KERN_INFO "Arbitration Lost: " + "Addressed as slave - " + "TX mode.\n"); + #endif + } else { + /* Set rx_index to 0 */ + i2c_rx_buffer.rx_index = 0; + + /* Master was writing to slave - + Set Receive Mode. */ + MCF_I2CR &= ~MCF_I2CR_MTX; + + /* Dummy read from MBDR, to clear + the ICF bit. */ + dummy_read = MCF_I2DR; + + #ifdef DEBUG + printk(KERN_INFO "Arbitration Lost: " + "Addressed as slave - " + "RX mode.\n"); + #endif + } + } + + } else { + /* Arbitration Not Lost - Check if data byte is this + devices's Slave Address byte. */ + if (MCF_I2SR & MCF_I2SR_IAAS) { + /* Data byte is Slave Address byte - + Check Slave Read/Write bit. */ + if (MCF_I2SR & MCF_I2SR_SRW) { + /* Set tx_index to 0 */ + if (i2c_tx_buffer.length == 0) { + i2c_tx_buffer.length = + I2C_BUFFER_SIZE; + i2c_tx_buffer.tx_index = 0; + } + + /* Master was reading from slave - + Set Transmit Mode. */ + MCF_I2CR |= MCF_I2CR_MTX; + + /* Write data to MBDR. */ + tmp_index = i2c_tx_buffer.tx_index++; + MCF_I2DR = i2c_tx_buffer.buf[tmp_index]; + i2c_tx_buffer.length--; + + #ifdef DEBUG + tmp_index = i2c_tx_buffer.tx_index - 1; + printk(KERN_INFO "Slave TX: First byte" + " - 0x%02X\n", + i2c_tx_buffer.buf[tmp_index]); + #endif + } else { + /* Master has specified Slave Receive + Mode. Set Receive Mode. (Writing to + MBCR clears IAAS.) */ + + /* Set rx_index to 0 */ + i2c_rx_buffer.rx_index = 0; + + MCF_I2CR &= ~MCF_I2CR_MTX; + + /* Dummy read from MBDR, to clear + the ICF bit. */ + dummy_read = MCF_I2DR; + + #ifdef DEBUG + printk(KERN_INFO "Slave RX: Receive " + "address.\n"); + #endif + } + } else { + /* Data byte received is not Slave Address byte + Check if this device is in Transmit or + Receive Mode. */ + if (MCF_I2CR & MCF_I2CR_MTX) { + /* Last byte received? */ + if (MCF_I2SR & MCF_I2SR_RXAK) { + MCF_I2CR &= ~MCF_I2CR_MTX; + dummy_read = MCF_I2DR; + + #ifdef DEBUG + printk(KERN_INFO "Slave TX: " + "Last byte has been " + "sent.\n"); + #endif + } else { + /* Write data to MBDR. */ + tmp_index = + i2c_tx_buffer.tx_index++; + MCF_I2DR = + i2c_tx_buffer.buf[tmp_index]; + i2c_tx_buffer.length--; + + if (i2c_tx_buffer.length == 0) { + i2c_tx_buffer.length = + I2C_BUFFER_SIZE; + i2c_tx_buffer.tx_index = + 0; + } + + } + } else { + /* Receive Mode - Read data from + MBDR and store it. */ + tmp_index = i2c_rx_buffer.rx_index++; + i2c_rx_buffer.buf[tmp_index] = MCF_I2DR; + i2c_rx_buffer.length++; + } + } + } + return IRQ_HANDLED; + } +} + +#ifdef CONFIG_PROC_FS + +/* + * Info exported via "/proc/driver/i2c". + */ + +static int gen_i2c_proc_output(char *buf) +{ + char *p; + + p = buf; + p += sprintf(p, + "I2CR: 0x%x\n" + "I2SR: 0x%x\n" + "I2DR: 0x%x\n", + MCF_I2CR, MCF_I2SR, MCF_I2DR); + + return p - buf; +} + +static int gen_i2c_read_proc(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + int len = gen_i2c_proc_output(page); + if (len <= off+count) + *eof = 1; + *start = page + off; + len -= off; + if (len > count) + len = count; + if (len < 0) + len = 0; + return len; +} + +static int __init gen_i2c_proc_init(void) +{ + struct proc_dir_entry *r; + + r = create_proc_read_entry("driver/i2c-adaptor-register", 0, NULL, + gen_i2c_read_proc, NULL); + if (!r) + return -ENOMEM; + return 0; +} +#else +static inline int gen_i2c_proc_init(void) { return 0; } +#endif /* CONFIG_PROC_FS */ + +/* + * Initalize I2C module + */ +static int __init i2c_coldfire_init(void) +{ + int retval; + u8 dummy_read; + +#ifdef DEBUG + printk(KERN_INFO "init i2c adaptor slave mode!\n"); +#endif + + /* Initialize the tx buffer */ + strcpy((char *)&i2c_tx_buffer.buf, (const char *)tx_string); + i2c_tx_buffer.length = I2C_BUFFER_SIZE; + +#if defined(CONFIG_M5445X) + /* + * Initialize the GPIOs for I2C + */ + MCF_GPIO_PAR_FECI2C |= (0 + | MCF_GPIO_PAR_FECI2C_PAR_SDA(3) + | MCF_GPIO_PAR_FECI2C_PAR_SCL(3)); +#endif + + /* Set transmission frequency 0x19 = ~100kHz */ + MCF_I2FDR = 0x19; + + /* set the I2C slave address */ + MCF_I2AR = 0x6A; + + /* Enable I2C module and if IBB is set, do the special initialzation */ + /* procedures as are documented */ + + if ((MCF_I2SR & MCF_I2SR_IBB) == 1) { + printk(KERN_INFO "%s - do special I2C init procedures\n", + __func__); + MCF_I2CR = 0x00; + MCF_I2CR = 0xA0; + dummy_read = MCF_I2DR; + MCF_I2SR = 0x00; + MCF_I2CR = 0x00; + } + + MCF_I2CR |= (MCF_I2CR_IEN | MCF_I2CR_IIEN); + + /* default I2C mode is - slave and receive */ + MCF_I2CR &= ~(MCF_I2CR_MSTA | MCF_I2CR_MTX); + + retval = request_irq(IRQ, i2c_slave_handler, IRQF_DISABLED, + SLAVE_HANDLER_NAME, NULL); + if (retval < 0) + printk(KERN_INFO "request_irq for i2c slave mode failed!\n"); + + retval = gen_i2c_proc_init(); + + if (retval < 0) + printk(KERN_INFO "gen /proc/i2c-adaptor-register for i2c slave mode failed!\n"); + + return retval; +}; + +/* + * I2C module exit function + */ + +static void __exit i2c_coldfire_exit(void) +{ + /* disable I2C and Interrupt */ + MCF_I2CR &= ~(MCF_I2CR_IEN | MCF_I2CR_IIEN); + free_irq(IRQ, NULL); + +}; + +MODULE_DESCRIPTION("MCF5445x I2C adaptor slave mode support"); +MODULE_LICENSE("GPL"); + +module_init(i2c_coldfire_init); +module_exit(i2c_coldfire_exit); --- /dev/null +++ b/drivers/i2c/busses/i2c-mcf.c @@ -0,0 +1,698 @@ +/* + * Copyright (C) 2009-2011 Freescale Semiconductor, Inc. All Rights Reserved. + * Lanttor.Guo@freescale.com + * + * I2C bus driver on mcfv4/mcfv4e platform + * + * 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. + */ +#include +#include "i2c-algo-mcf.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#if defined(CONFIG_M547X_8X) +#include +#elif defined(CONFIG_M5445X) || defined(CONFIG_M5441X) +#include +#endif + +#define get_clock(adap) (clock) +#define get_own(adap) (own) + +#if defined(CONFIG_M547X_8X) +static int clock = 0x3b; +#elif defined(CONFIG_M5445X) || defined(CONFIG_M5441X) +static int clock = 0x19; +#endif +module_param(clock, int, 0); +MODULE_PARM_DESC(clock, + "Set I2C clock in kHz: 400=fast mode (default == 100khz)"); + +static int own = 0x78; +module_param(own, int, 0); +MODULE_PARM_DESC(clock, "Set I2C Master controller address"); + +static struct i2c_algo_mcf_data i2c_mcf_board_data = { + .timeout = 10000, +}; + +static struct i2c_adapter i2c_mcf_board_adapter = { + .owner = THIS_MODULE, + .name = "mcf i2c adapter", + .algo_data = &i2c_mcf_board_data, + .class = I2C_CLASS_HWMON, + .timeout = 100, + .retries = 2 +}; +/* + * static void i2c_start() + * + * Generates START signal + */ +static void +i2c_start( + struct i2c_algo_mcf_data *adap +) { + MCF_I2CR |= MCF_I2CR_MSTA; +} + + +/* + * static void i2c_stop() + * + * Generates STOP signal + */ +static void +i2c_stop( + struct i2c_algo_mcf_data *adap +) { + MCF_I2CR &= ~MCF_I2CR_MSTA; +} + +static int +i2c_getack( + struct i2c_algo_mcf_data *adap +) { + return !(MCF_I2SR & MCF_I2SR_RXAK); +} + +/* + * static void wait_for_bb() + * + * Wait for bus idle state + */ +static int +wait_for_bb( + struct i2c_algo_mcf_data *adap +) { + int i; + for (i = 0; i < adap->timeout; i++) { + if (!(MCF_I2SR & MCF_I2SR_IBB)) + return 0; + udelay(100); + } + printk(KERN_ERR "%s: timeout", __func__); + return -ETIMEDOUT; +} + +/* + * static void wait_for_not_bb() + * + * Wait for bus busy state + */ +static int +wait_for_not_bb( + struct i2c_algo_mcf_data *adap +) { + int i; + for (i = 0; i < adap->timeout; i++) { + if (MCF_I2SR & MCF_I2SR_IBB) + return 0; + udelay(100); + } + printk(KERN_ERR "%s: timeout", __func__); + return -ETIMEDOUT; +} + +/* + * static void wait_xfer_done() + * + * Wait for transfer to complete + */ +static int +wait_xfer_done( + struct i2c_algo_mcf_data *adap +) { + int i; + + for (i = 0; i < adap->timeout; i++) { + if (MCF_I2SR & MCF_I2SR_IIF) { + MCF_I2SR &= ~MCF_I2SR_IIF; + return 0; + } + udelay(10); + } + printk(KERN_ERR "%s: timeout", __func__); + return -ETIMEDOUT; +} + + +/* + * static void i2c_set_addr() + * + * Sets slave address to communicate + */ +static int +i2c_set_addr( + struct i2c_algo_mcf_data *adap, + struct i2c_msg *msg, + int retries +) { + unsigned short flags = msg->flags; + unsigned char addr; + MCF_I2CR |= MCF_I2CR_MTX; + if ((flags & I2C_M_TEN)) { + /* 10 bit address not supported yet */ + return -EIO; + } else { + /* normal 7bit address */ + addr = (msg->addr << 1); + if (flags & I2C_M_RD) + addr |= 1; + if (flags & I2C_M_REV_DIR_ADDR) + addr ^= 1; + + MCF_I2DR = addr; + } + return 0; +} + + +/* + * static void mcf_i2c_init() + * + * Perform ColdFire i2c initialization + */ +static void +mcf_i2c_init(struct i2c_algo_mcf_data *adap) +{ + u8 dummy; + + /* Setup GPIO lines */ +#if defined(CONFIG_M547X_8X) + MCF_PAR_FECI2CIRQ |= MCF_PAR_SDA; + MCF_PAR_FECI2CIRQ |= MCF_PAR_SCL; +#elif defined(CONFIG_M5445X) + MCF_GPIO_PAR_FECI2C |= (0 + | MCF_GPIO_PAR_FECI2C_PAR_SDA(3) + | MCF_GPIO_PAR_FECI2C_PAR_SCL(3)); +#elif defined(CONFIG_M5441X) + MCF_GPIO_PAR_CANI2C = + (MCF_GPIO_PAR_CANI2C & MCF_GPIO_PAR_CANI2C_I2C0SCL_MASK) | + MCF_GPIO_PAR_CANI2C_I2C0SCL_I2C0SCL; + MCF_GPIO_PAR_CANI2C = + (MCF_GPIO_PAR_CANI2C & MCF_GPIO_PAR_CANI2C_I2C0SDA_MASK) | + MCF_GPIO_PAR_CANI2C_I2C0SDA_I2C0SDA; +#endif + + /* Ensure slaves are in idle state */ + if (MCF_I2SR & MCF_I2SR_IBB) { +#if defined(CONFIG_M547X_8X) + MCF_I2ICR = 0x00; + MCF_I2CR = 0x00; + MCF_I2CR = 0x0A; + dummy = MCF_I2DR; + MCF_I2SR = 0x00; + MCF_I2CR = 0x00; + MCF_I2ICR = 0x01; +#elif defined(CONFIG_M5445X) || defined(CONFIG_M5441X) + MCF_I2CR = 0x00; + MCF_I2CR = 0xA0; + dummy = MCF_I2DR; + MCF_I2SR = 0x00; + MCF_I2CR = 0x00; + MCF_I2CR = 0x80; +#endif + } + + /* setup SCL clock */ + MCF_I2FDR = get_clock(adap); + + /* set slave address */ + MCF_I2AR = get_own(adap); + + /* enable I2C module */ +#if defined(CONFIG_M5441X) + MCF_I2CR = (MCF_I2CR_IEN | MCF_I2CR_IIEN); +#else + MCF_I2CR = MCF_I2CR_IEN; +#endif +} + +static int i2c_outb( + struct i2c_adapter *i2c_adap, + char c +) { + + struct i2c_algo_mcf_data *adap = i2c_adap->algo_data; + int timeout; + /* Put data to be sent */ + MCF_I2DR = c; + /* Wait for xfer completed*/ + timeout = wait_xfer_done(adap); + if (timeout) { + i2c_stop(adap); + wait_for_bb(adap); + printk(KERN_ERR "i2c-algo-mcf: %s i2c_write: " + "error - timeout.\n", i2c_adap->name); + return -EREMOTEIO; /* got a better one ?? */ + } + + return 0; +} + + +/* + * static void mcf_sendbytes() + * + * Perform tx data transfer + */ +static int +mcf_sendbytes( + struct i2c_adapter *i2c_adap, + const char *buf, + int count, int last +) { + struct i2c_algo_mcf_data *adap = i2c_adap->algo_data; + int ret, i; + + /* Set master TX mode */ + MCF_I2CR |= MCF_I2CR_MTX; + + for (i = 0; i < count; ++i) { + printk(KERN_DEBUG "i2c-algo-mcf: %s i2c_write: writing %2.2X\n", + i2c_adap->name, buf[i]&0xff); + ret = i2c_outb(i2c_adap, buf[i]); + if (ret < 0) + return ret; + } + if (last) { + i2c_stop(adap); + wait_for_bb(adap); + } else { + /* i2c_repstart(adap);*/ + } + + return i; +} + + +/* + * static void mcf_readbytes() + * + * Perform rx data transfer + */ +static int +mcf_readbytes( + struct i2c_adapter *i2c_adap, + char *buf, + int count, int last +) { + int i; + struct i2c_algo_mcf_data *adap = i2c_adap->algo_data; + u8 dummy; + + /* Set master RX mode */ + MCF_I2CR &= ~MCF_I2CR_MTX; + MCF_I2CR &= ~MCF_I2CR_TXAK; + dummy = MCF_I2DR; + + for (i = 0; i < count-1; i++) { + if (wait_xfer_done(adap)) { + i2c_stop(adap); + wait_for_bb(adap); + printk(KERN_DEBUG + "i2c-algo-mcf: mcf_readbytes timed out.\n"); + return -1; + } + + /* store next data byte */ + buf[i] = MCF_I2DR; + } + + if (wait_xfer_done(adap)) { + i2c_stop(adap); + wait_for_bb(adap); + printk(KERN_DEBUG "i2c-algo-mcf: mcf_readbytes timed out.\n"); + return -1; + } + + /* Disable acknowlege (set I2CR.TXAK) */ + MCF_I2CR |= MCF_I2CR_TXAK; + buf[i] = MCF_I2DR; + if (wait_xfer_done(adap)) { + i2c_stop(adap); + wait_for_bb(adap); + printk(KERN_DEBUG "i2c-algo-mcf: mcf_readbytes timed out.\n"); + return -1; + } + + if (last) { + i2c_stop(adap); + wait_for_bb(adap); + } else { + /* i2c_repstart(adap);*/ + } + + return i+1; +} + + +/* + * static void mcf_xfer() + * + * Perform master data I/O transfer + */ +static int +mcf_xfer( + struct i2c_adapter *i2c_adap, + struct i2c_msg *msgs, + int num) +{ + struct i2c_algo_mcf_data *adap = i2c_adap->algo_data; + struct i2c_msg *pmsg; + int i; + int ret = 0, timeout; + + /* Skip own address */ + if (get_own(adap) == (msgs[0].addr << 1)) + return -EIO; + + /* Ensure slaves are in idle state */ + if (MCF_I2SR & MCF_I2SR_IBB) { +#if defined(CONFIG_M547X_8X) + MCF_I2ICR = 0x00; + MCF_I2CR = 0x00; + MCF_I2CR = 0x0A; + timeout = MCF_I2DR; + MCF_I2SR = 0x00; + MCF_I2CR = 0x00; + MCF_I2ICR = 0x01; +#elif defined(CONFIG_M5445X) || defined(CONFIG_M5441X) + MCF_I2CR = 0x00; + MCF_I2CR = 0xA0; + timeout = MCF_I2DR; + MCF_I2SR = 0x00; + MCF_I2CR = 0x00; + MCF_I2CR = 0x80; +#endif + } + + /* setup SCL clock */ + MCF_I2FDR = get_clock(adap); + /* set slave address */ + MCF_I2AR = get_own(adap); + /* enable I2C module */ +#if defined(CONFIG_M5441X) + MCF_I2CR = (MCF_I2CR_IEN | MCF_I2CR_IIEN); +#else + MCF_I2CR = MCF_I2CR_IEN; +#endif + MCF_I2CR |= MCF_I2CR_TXAK; + + /* Check for bus busy */ + wait_for_bb(adap); + + for (i = 0; ret >= 0 && i < num; i++) { + if (MCF_I2SR & MCF_I2SR_IBB) { +#if defined(CONFIG_M547X_8X) + MCF_I2ICR = 0x00; + MCF_I2CR = 0x00; + MCF_I2CR = 0x0A; + timeout = MCF_I2DR; + MCF_I2SR = 0x00; + MCF_I2CR = 0x00; + MCF_I2ICR = 0x01; +#elif defined(CONFIG_M5445X) || defined(CONFIG_M5441X) + MCF_I2CR = 0x00; + MCF_I2CR = 0xA0; + timeout = MCF_I2DR; + MCF_I2SR = 0x00; + MCF_I2CR = 0x00; + MCF_I2CR = 0x80; +#endif + } + /* setup SCL clock */ + MCF_I2FDR = get_clock(adap); + /* set slave address */ + MCF_I2AR = get_own(adap); + /* enable I2C module */ +#if defined(CONFIG_M5441X) + MCF_I2CR = (MCF_I2CR_IEN | MCF_I2CR_IIEN); +#else + MCF_I2CR = MCF_I2CR_IEN; +#endif + MCF_I2CR |= MCF_I2CR_TXAK; + + /* Check for bus busy */ + wait_for_bb(adap); + + pmsg = &msgs[i]; + + printk(KERN_DEBUG "i2c-algo-mcf: Doing %s %d bytes " + "to 0x%02x - %d of %d messages\n", + pmsg->flags & I2C_M_RD ? "read" : "write", + pmsg->len, pmsg->addr, i + 1, num); + + /* Send START */ + /*if (i == 0)*/ + i2c_start(adap); + + /* Wait for Bus Busy */ + wait_for_not_bb(adap); + + MCF_I2CR |= MCF_I2CR_MTX; + + ret = i2c_set_addr(adap, pmsg, i2c_adap->retries); + if (ret < 0) + return ret; + + /* Wait for address transfer completion */ + wait_xfer_done(adap); + + /* Check for ACK */ + if (!i2c_getack(adap)) { + i2c_stop(adap); + wait_for_bb(adap); + printk(KERN_DEBUG "i2c-algo-mcf: No ack after " + "send address in mcf_xfer\n"); + return -EREMOTEIO; + } + + printk(KERN_DEBUG "i2c-algo-mcf: Msg %d, " + "addr = 0x%x, flags = 0x%x, len = %d\n", + i, msgs[i].addr, msgs[i].flags, msgs[i].len); + /* Read */ + if (pmsg->flags & I2C_M_RD) { + /* read bytes into buffer*/ + ret = mcf_readbytes(i2c_adap, pmsg->buf, pmsg->len, + (i + 1 == num)); + + if (ret != pmsg->len) { + printk(KERN_DEBUG "i2c-algo-mcf: fail: " + "only read %d bytes.\n", ret); + } else { + printk(KERN_DEBUG "i2c-algo-mcf: " + "read %d bytes.\n", ret); + } + } else { + /* write bytes into buffer*/ + ret = mcf_sendbytes(i2c_adap, pmsg->buf, pmsg->len, + (i + 1 == num)); + if (ret != pmsg->len) { + printk(KERN_DEBUG "i2c-algo-mcf: fail: " + "only wrote %d bytes.\n", ret); + } else { + printk(KERN_DEBUG "i2c-algo-mcf: wrote" + "%d bytes.\n", ret); + } + } + MCF_I2CR = 0; + } + + /* Disable I2C module */ + MCF_I2CR = 0; + return i; +} + + +/* + * static void mcf_func() + * + * Return algorithm funtionality + */ +static u32 +mcf_func( + struct i2c_adapter *i2c_adap +) { + return I2C_FUNC_SMBUS_EMUL | I2C_FUNC_I2C; +} + +/* + * ColdFire bus algorithm callbacks + */ +static struct i2c_algorithm mcf_algo = { + .master_xfer = mcf_xfer, + .functionality = mcf_func, +}; + +/***********************************************************/ +struct coldfire_i2c { + void __iomem *base; + struct resource *irqarea; + struct resource *ioarea; + u32 irq; + struct i2c_adapter *adap; + u32 flags; +}; + +/* + * registering functions to load algorithms at runtime + */ +int i2c_mcf_add_bus(struct i2c_adapter *adap) +{ + struct i2c_algo_mcf_data *mcf_adap = adap->algo_data; + + /*adap->id |= mcf_algo.id;*/ + adap->algo = &mcf_algo; + adap->timeout = 100; + + mcf_i2c_init(mcf_adap); + + i2c_add_numbered_adapter(adap); + + return 0; +} + +static int mcf_i2c_probe(struct platform_device *pdev) +{ + struct coldfire_i2c *i2c; + int rc = 0; + + /************************************************************/ + i2c = kzalloc(sizeof(*i2c), GFP_KERNEL); + if (!i2c) { + printk(KERN_ERR "%s kzalloc coldfire_i2c faile\n", + __func__); + return -ENOMEM; + } + /****************************************************************/ + platform_set_drvdata(pdev, i2c); + + i2c->adap = &i2c_mcf_board_adapter; + i2c->adap->dev.parent = &pdev->dev; + i2c->adap->nr = pdev->id; + rc = i2c_mcf_add_bus(i2c->adap); + if (rc < 0) { + printk(KERN_ERR "%s - failed to add adapter\n", __func__); + rc = -ENODEV; + goto fail_add; + } + + printk(KERN_INFO "i2c-algo-mcf.o: I2C ColdFire algorithm" + " module is loaded.\n"); + return rc; + +fail_add: + kfree(i2c); + return rc; +}; + +static int mcf_i2c_remove(struct platform_device *pdev) +{ + struct coldfire_i2c *i2c = platform_get_drvdata(pdev); + + i2c_del_adapter(i2c->adap); + platform_set_drvdata(pdev, NULL); + iounmap(i2c->base); + kfree(i2c); + return 0; +}; + +/* Structure for a device driver */ +static struct platform_driver mcf_i2c_driver = { + .probe = mcf_i2c_probe, + .remove = mcf_i2c_remove, + .driver = { + .owner = THIS_MODULE, + .name = "mcf-i2c", + }, +}; + +#ifdef CONFIG_PROC_FS + +/* + * Info exported via "/proc/driver/i2c". + */ + +static int gen_i2c_proc_output(char *buf) +{ + char *p; + + p = buf; + p += sprintf(p, + "I2CR: 0x%x\n" + "I2SR: 0x%x\n" + "I2DR: 0x%x\n", + MCF_I2CR, MCF_I2SR, MCF_I2DR); + + return p - buf; +} + +static int gen_i2c_read_proc(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + int len = gen_i2c_proc_output(page); + if (len <= off+count) + *eof = 1; + *start = page + off; + len -= off; + if (len > count) + len = count; + if (len < 0) + len = 0; + return len; +} + +static int __init gen_i2c_proc_init(void) +{ + struct proc_dir_entry *r; + + r = create_proc_read_entry("driver/i2c-adaptor-register", 0, NULL, + gen_i2c_read_proc, NULL); + if (!r) + return -ENOMEM; + return 0; +} +#else +static inline int gen_i2c_proc_init(void) { return 0; } +#endif /* CONFIG_PROC_FS */ + +static int __init coldfire_i2c_init(void) +{ + int retval; + + retval = gen_i2c_proc_init(); + if (retval < 0) + printk(KERN_INFO "generate /proc/i2c-adaptor-register " + "for i2c master mode failed!\n"); + + return platform_driver_register(&mcf_i2c_driver); +} + +static void __exit coldfire_i2c_exit(void) +{ + platform_driver_unregister(&mcf_i2c_driver); +} + +module_init(coldfire_i2c_init); +module_exit(coldfire_i2c_exit); + +MODULE_AUTHOR("Adrian Cox "); +MODULE_DESCRIPTION("I2C-Bus adapter for MCFV4/MCFV4E processors"); +MODULE_LICENSE("GPL");