diff options
Diffstat (limited to 'roms/u-boot-sam460ex/drivers/i2c/s3c24x0_i2c.c')
-rw-r--r-- | roms/u-boot-sam460ex/drivers/i2c/s3c24x0_i2c.c | 443 |
1 files changed, 443 insertions, 0 deletions
diff --git a/roms/u-boot-sam460ex/drivers/i2c/s3c24x0_i2c.c b/roms/u-boot-sam460ex/drivers/i2c/s3c24x0_i2c.c new file mode 100644 index 000000000..c8371cf73 --- /dev/null +++ b/roms/u-boot-sam460ex/drivers/i2c/s3c24x0_i2c.c @@ -0,0 +1,443 @@ +/* + * (C) Copyright 2002 + * David Mueller, ELSOFT AG, d.mueller@elsoft.ch + * + * See file CREDITS for list of people who contributed to this + * project. + * + * 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., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +/* This code should work for both the S3C2400 and the S3C2410 + * as they seem to have the same I2C controller inside. + * The different address mapping is handled by the s3c24xx.h files below. + */ + +#include <common.h> +#include <asm/arch/s3c24x0_cpu.h> + +#include <asm/io.h> +#include <i2c.h> + +#ifdef CONFIG_HARD_I2C + +#define I2C_WRITE 0 +#define I2C_READ 1 + +#define I2C_OK 0 +#define I2C_NOK 1 +#define I2C_NACK 2 +#define I2C_NOK_LA 3 /* Lost arbitration */ +#define I2C_NOK_TOUT 4 /* time out */ + +#define I2CSTAT_BSY 0x20 /* Busy bit */ +#define I2CSTAT_NACK 0x01 /* Nack bit */ +#define I2CCON_IRPND 0x10 /* Interrupt pending bit */ +#define I2C_MODE_MT 0xC0 /* Master Transmit Mode */ +#define I2C_MODE_MR 0x80 /* Master Receive Mode */ +#define I2C_START_STOP 0x20 /* START / STOP */ +#define I2C_TXRX_ENA 0x10 /* I2C Tx/Rx enable */ + +#define I2C_TIMEOUT 1 /* 1 second */ + +static int GetI2CSDA(void) +{ + struct s3c24x0_gpio *gpio = s3c24x0_get_base_gpio(); + +#ifdef CONFIG_S3C2410 + return (readl(&gpio->GPEDAT) & 0x8000) >> 15; +#endif +#ifdef CONFIG_S3C2400 + return (readl(&gpio->PGDAT) & 0x0020) >> 5; +#endif +} + +#if 0 +static void SetI2CSDA(int x) +{ + rGPEDAT = (rGPEDAT & ~0x8000) | (x & 1) << 15; +} +#endif + +static void SetI2CSCL(int x) +{ + struct s3c24x0_gpio *gpio = s3c24x0_get_base_gpio(); + +#ifdef CONFIG_S3C2410 + writel((readl(&gpio->GPEDAT) & ~0x4000) | (x & 1) << 14, &gpio->GPEDAT); +#endif +#ifdef CONFIG_S3C2400 + writel((readl(&gpio->PGDAT) & ~0x0040) | (x & 1) << 6, &gpio->PGDAT); +#endif +} + +static int WaitForXfer(void) +{ + struct s3c24x0_i2c *i2c = s3c24x0_get_base_i2c(); + int i; + + i = I2C_TIMEOUT * 10000; + while (!(readl(&i2c->IICCON) & I2CCON_IRPND) && (i > 0)) { + udelay(100); + i--; + } + + return (readl(&i2c->IICCON) & I2CCON_IRPND) ? I2C_OK : I2C_NOK_TOUT; +} + +static int IsACK(void) +{ + struct s3c24x0_i2c *i2c = s3c24x0_get_base_i2c(); + + return !(readl(&i2c->IICSTAT) & I2CSTAT_NACK); +} + +static void ReadWriteByte(void) +{ + struct s3c24x0_i2c *i2c = s3c24x0_get_base_i2c(); + + writel(readl(&i2c->IICCON) & ~I2CCON_IRPND, &i2c->IICCON); +} + +void i2c_init(int speed, int slaveadd) +{ + struct s3c24x0_i2c *i2c = s3c24x0_get_base_i2c(); + struct s3c24x0_gpio *gpio = s3c24x0_get_base_gpio(); + ulong freq, pres = 16, div; + int i; + + /* wait for some time to give previous transfer a chance to finish */ + + i = I2C_TIMEOUT * 1000; + while ((readl(&i2c->IICSTAT) && I2CSTAT_BSY) && (i > 0)) { + udelay(1000); + i--; + } + + if ((readl(&i2c->IICSTAT) & I2CSTAT_BSY) || GetI2CSDA() == 0) { +#ifdef CONFIG_S3C2410 + ulong old_gpecon = readl(&gpio->GPECON); +#endif +#ifdef CONFIG_S3C2400 + ulong old_gpecon = readl(&gpio->PGCON); +#endif + /* bus still busy probably by (most) previously interrupted + transfer */ + +#ifdef CONFIG_S3C2410 + /* set I2CSDA and I2CSCL (GPE15, GPE14) to GPIO */ + writel((readl(&gpio->GPECON) & ~0xF0000000) | 0x10000000, + &gpio->GPECON); +#endif +#ifdef CONFIG_S3C2400 + /* set I2CSDA and I2CSCL (PG5, PG6) to GPIO */ + writel((readl(&gpio->PGCON) & ~0x00003c00) | 0x00001000, + &gpio->PGCON); +#endif + + /* toggle I2CSCL until bus idle */ + SetI2CSCL(0); + udelay(1000); + i = 10; + while ((i > 0) && (GetI2CSDA() != 1)) { + SetI2CSCL(1); + udelay(1000); + SetI2CSCL(0); + udelay(1000); + i--; + } + SetI2CSCL(1); + udelay(1000); + + /* restore pin functions */ +#ifdef CONFIG_S3C2410 + writel(old_gpecon, &gpio->GPECON); +#endif +#ifdef CONFIG_S3C2400 + writel(old_gpecon, &gpio->PGCON); +#endif + } + + /* calculate prescaler and divisor values */ + freq = get_PCLK(); + if ((freq / pres / (16 + 1)) > speed) + /* set prescaler to 512 */ + pres = 512; + + div = 0; + while ((freq / pres / (div + 1)) > speed) + div++; + + /* set prescaler, divisor according to freq, also set + * ACKGEN, IRQ */ + writel((div & 0x0F) | 0xA0 | ((pres == 512) ? 0x40 : 0), &i2c->IICCON); + + /* init to SLAVE REVEIVE and set slaveaddr */ + writel(0, &i2c->IICSTAT); + writel(slaveadd, &i2c->IICADD); + /* program Master Transmit (and implicit STOP) */ + writel(I2C_MODE_MT | I2C_TXRX_ENA, &i2c->IICSTAT); + +} + +/* + * cmd_type is 0 for write, 1 for read. + * + * addr_len can take any value from 0-255, it is only limited + * by the char, we could make it larger if needed. If it is + * 0 we skip the address write cycle. + */ +static +int i2c_transfer(unsigned char cmd_type, + unsigned char chip, + unsigned char addr[], + unsigned char addr_len, + unsigned char data[], unsigned short data_len) +{ + struct s3c24x0_i2c *i2c = s3c24x0_get_base_i2c(); + int i, result; + + if (data == 0 || data_len == 0) { + /*Don't support data transfer of no length or to address 0 */ + printf("i2c_transfer: bad call\n"); + return I2C_NOK; + } + + /* Check I2C bus idle */ + i = I2C_TIMEOUT * 1000; + while ((readl(&i2c->IICSTAT) & I2CSTAT_BSY) && (i > 0)) { + udelay(1000); + i--; + } + + if (readl(&i2c->IICSTAT) & I2CSTAT_BSY) + return I2C_NOK_TOUT; + + writel(readl(&i2c->IICCON) | 0x80, &i2c->IICCON); + result = I2C_OK; + + switch (cmd_type) { + case I2C_WRITE: + if (addr && addr_len) { + writel(chip, &i2c->IICDS); + /* send START */ + writel(I2C_MODE_MT | I2C_TXRX_ENA | I2C_START_STOP, + &i2c->IICSTAT); + i = 0; + while ((i < addr_len) && (result == I2C_OK)) { + result = WaitForXfer(); + writel(addr[i], &i2c->IICDS); + ReadWriteByte(); + i++; + } + i = 0; + while ((i < data_len) && (result == I2C_OK)) { + result = WaitForXfer(); + writel(data[i], &i2c->IICDS); + ReadWriteByte(); + i++; + } + } else { + writel(chip, &i2c->IICDS); + /* send START */ + writel(I2C_MODE_MT | I2C_TXRX_ENA | I2C_START_STOP, + &i2c->IICSTAT); + i = 0; + while ((i < data_len) && (result = I2C_OK)) { + result = WaitForXfer(); + writel(data[i], &i2c->IICDS); + ReadWriteByte(); + i++; + } + } + + if (result == I2C_OK) + result = WaitForXfer(); + + /* send STOP */ + writel(I2C_MODE_MR | I2C_TXRX_ENA, &i2c->IICSTAT); + ReadWriteByte(); + break; + + case I2C_READ: + if (addr && addr_len) { + writel(I2C_MODE_MT | I2C_TXRX_ENA, &i2c->IICSTAT); + writel(chip, &i2c->IICDS); + /* send START */ + writel(readl(&i2c->IICSTAT) | I2C_START_STOP, + &i2c->IICSTAT); + result = WaitForXfer(); + if (IsACK()) { + i = 0; + while ((i < addr_len) && (result == I2C_OK)) { + writel(addr[i], &i2c->IICDS); + ReadWriteByte(); + result = WaitForXfer(); + i++; + } + + writel(chip, &i2c->IICDS); + /* resend START */ + writel(I2C_MODE_MR | I2C_TXRX_ENA | + I2C_START_STOP, &i2c->IICSTAT); + ReadWriteByte(); + result = WaitForXfer(); + i = 0; + while ((i < data_len) && (result == I2C_OK)) { + /* disable ACK for final READ */ + if (i == data_len - 1) + writel(readl(&i2c->IICCON) + & ~0x80, &i2c->IICCON); + ReadWriteByte(); + result = WaitForXfer(); + data[i] = readl(&i2c->IICDS); + i++; + } + } else { + result = I2C_NACK; + } + + } else { + writel(I2C_MODE_MR | I2C_TXRX_ENA, &i2c->IICSTAT); + writel(chip, &i2c->IICDS); + /* send START */ + writel(readl(&i2c->IICSTAT) | I2C_START_STOP, + &i2c->IICSTAT); + result = WaitForXfer(); + + if (IsACK()) { + i = 0; + while ((i < data_len) && (result == I2C_OK)) { + /* disable ACK for final READ */ + if (i == data_len - 1) + writel(readl(&i2c->IICCON) & + ~0x80, &i2c->IICCON); + ReadWriteByte(); + result = WaitForXfer(); + data[i] = readl(&i2c->IICDS); + i++; + } + } else { + result = I2C_NACK; + } + } + + /* send STOP */ + writel(I2C_MODE_MR | I2C_TXRX_ENA, &i2c->IICSTAT); + ReadWriteByte(); + break; + + default: + printf("i2c_transfer: bad call\n"); + result = I2C_NOK; + break; + } + + return (result); +} + +int i2c_probe(uchar chip) +{ + uchar buf[1]; + + buf[0] = 0; + + /* + * What is needed is to send the chip address and verify that the + * address was <ACK>ed (i.e. there was a chip at that address which + * drove the data line low). + */ + return i2c_transfer(I2C_READ, chip << 1, 0, 0, buf, 1) != I2C_OK; +} + +int i2c_read(uchar chip, uint addr, int alen, uchar *buffer, int len) +{ + uchar xaddr[4]; + int ret; + + if (alen > 4) { + printf("I2C read: addr len %d not supported\n", alen); + return 1; + } + + if (alen > 0) { + xaddr[0] = (addr >> 24) & 0xFF; + xaddr[1] = (addr >> 16) & 0xFF; + xaddr[2] = (addr >> 8) & 0xFF; + xaddr[3] = addr & 0xFF; + } + +#ifdef CONFIG_SYS_I2C_EEPROM_ADDR_OVERFLOW + /* + * EEPROM chips that implement "address overflow" are ones + * like Catalyst 24WC04/08/16 which has 9/10/11 bits of + * address and the extra bits end up in the "chip address" + * bit slots. This makes a 24WC08 (1Kbyte) chip look like + * four 256 byte chips. + * + * Note that we consider the length of the address field to + * still be one byte because the extra address bits are + * hidden in the chip address. + */ + if (alen > 0) + chip |= ((addr >> (alen * 8)) & + CONFIG_SYS_I2C_EEPROM_ADDR_OVERFLOW); +#endif + if ((ret = + i2c_transfer(I2C_READ, chip << 1, &xaddr[4 - alen], alen, + buffer, len)) != 0) { + printf("I2c read: failed %d\n", ret); + return 1; + } + return 0; +} + +int i2c_write(uchar chip, uint addr, int alen, uchar *buffer, int len) +{ + uchar xaddr[4]; + + if (alen > 4) { + printf("I2C write: addr len %d not supported\n", alen); + return 1; + } + + if (alen > 0) { + xaddr[0] = (addr >> 24) & 0xFF; + xaddr[1] = (addr >> 16) & 0xFF; + xaddr[2] = (addr >> 8) & 0xFF; + xaddr[3] = addr & 0xFF; + } +#ifdef CONFIG_SYS_I2C_EEPROM_ADDR_OVERFLOW + /* + * EEPROM chips that implement "address overflow" are ones + * like Catalyst 24WC04/08/16 which has 9/10/11 bits of + * address and the extra bits end up in the "chip address" + * bit slots. This makes a 24WC08 (1Kbyte) chip look like + * four 256 byte chips. + * + * Note that we consider the length of the address field to + * still be one byte because the extra address bits are + * hidden in the chip address. + */ + if (alen > 0) + chip |= ((addr >> (alen * 8)) & + CONFIG_SYS_I2C_EEPROM_ADDR_OVERFLOW); +#endif + return (i2c_transfer + (I2C_WRITE, chip << 1, &xaddr[4 - alen], alen, buffer, + len) != 0); +} +#endif /* CONFIG_HARD_I2C */ |