diff options
author | David Hendricks <dhendrix@chromium.org> | 2012-12-27 14:06:05 -0800 |
---|---|---|
committer | Ronald G. Minnich <rminnich@gmail.com> | 2013-01-03 06:42:16 +0100 |
commit | 45256b3bfc18c2b1183238738d3f97474583b949 (patch) | |
tree | 56e418039a0bf2244ca2ca23ecdcf4bbca42bee1 | |
parent | d3c7530908463537c38d84d47d0c29a3bc5dac63 (diff) | |
download | coreboot-45256b3bfc18c2b1183238738d3f97474583b949.tar.xz |
Add (hacked-up) s3c24x0_i2c files
These are needed for communicating with the PMIC on Snow. We'll
tidy them up as we go along...
Change-Id: I197f59927eae0ad66191862d052de2a8873fb22f
Signed-off-by: David Hendricks <dhendrix@chromium.org>
Reviewed-on: http://review.coreboot.org/2078
Tested-by: build bot (Jenkins)
Reviewed-by: Hung-Te Lin <hungte@chromium.org>
-rw-r--r-- | src/cpu/samsung/s5p-common/Makefile.inc | 2 | ||||
-rw-r--r-- | src/cpu/samsung/s5p-common/s3c24x0_i2c.c | 640 | ||||
-rw-r--r-- | src/cpu/samsung/s5p-common/s3c24x0_i2c.h | 43 |
3 files changed, 685 insertions, 0 deletions
diff --git a/src/cpu/samsung/s5p-common/Makefile.inc b/src/cpu/samsung/s5p-common/Makefile.inc index 9747f0d1ab..7de3c285d2 100644 --- a/src/cpu/samsung/s5p-common/Makefile.inc +++ b/src/cpu/samsung/s5p-common/Makefile.inc @@ -2,6 +2,7 @@ romstage-y += cpu_info.c romstage-y += pwm.c # needed by timer.c romstage-y += s5p_gpio.c romstage-y += timer.c +romstage-y += s3c24x0_i2c.c #romstage-y += sromc.c #romstage-y += wdt.c @@ -10,3 +11,4 @@ ramstage-y += cpu_info.c ramstage-y += pwm.c # needed by timer.c ramstage-y += timer.c ramstage-y += s5p_gpio.c +ramstage-y += s3c24x0_i2c.c diff --git a/src/cpu/samsung/s5p-common/s3c24x0_i2c.c b/src/cpu/samsung/s5p-common/s3c24x0_i2c.c new file mode 100644 index 0000000000..0b6f7684b1 --- /dev/null +++ b/src/cpu/samsung/s5p-common/s3c24x0_i2c.c @@ -0,0 +1,640 @@ +/* + * (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 <arch/io.h> +#include "clk.h" +#include "cpu/samsung/exynos5-common/clk.h" +#include "cpu/samsung/exynos5250/cpu.h" +#include "gpio.h" +#include "cpu/samsung/exynos5250/gpio.h" +#include "cpu/samsung/exynos5250/pinmux.h" + +//#include <fdtdec.h> +#include "device/i2c.h" +#include "s3c24x0_i2c.h" + +/* for board_i2c_* */ +#include "cpu/samsung/exynos5-common/spl.h" + +#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_ACKGEN 0x80 /* Acknowledge generation */ +#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 */ + +/* The timeouts we live by */ +enum { + I2C_XFER_TIMEOUT_MS = 35, /* xfer to complete */ + I2C_INIT_TIMEOUT_MS = 1000, /* bus free on init */ + I2C_IDLE_TIMEOUT_MS = 100, /* waiting for bus idle */ + I2C_STOP_TIMEOUT_US = 200, /* waiting for stop events */ +}; + +/* We should not rely on any particular ordering of these IDs */ +#if 0 +#ifndef CONFIG_OF_CONTROL +static enum periph_id periph_for_dev[EXYNOS_I2C_MAX_CONTROLLERS] = { + PERIPH_ID_I2C0, + PERIPH_ID_I2C1, + PERIPH_ID_I2C2, + PERIPH_ID_I2C3, + PERIPH_ID_I2C4, + PERIPH_ID_I2C5, + PERIPH_ID_I2C6, + PERIPH_ID_I2C7, +}; +#endif +#endif + +static unsigned int g_current_bus __attribute__((section(".data"))); +static struct s3c24x0_i2c *g_early_i2c_config __attribute__((section(".data"))); + +static struct s3c24x0_i2c_bus i2c_bus[EXYNOS_I2C_MAX_CONTROLLERS] + __attribute__((section(".data"))); +static int i2c_busses __attribute__((section(".data"))); + +void i2c_set_early_reg(unsigned int base) +{ + g_early_i2c_config = (struct s3c24x0_i2c *)base; +} + +static struct s3c24x0_i2c_bus *get_bus(int bus_idx) +{ + /* If an early i2c config exists we just use that */ + if (g_early_i2c_config) { + /* FIXME: value not retained from i2c_set_early_reg()? (but then, how + * did if (!i2c) check pass earlier on? Corrupt value? */ + i2c_bus[0].regs = g_early_i2c_config; + return &i2c_bus[0]; + } + + if (bus_idx < i2c_busses) + return &i2c_bus[bus_idx]; + debug("Undefined bus: %d\n", bus_idx); + return NULL; +} + +static inline struct exynos5_gpio_part1 *exynos_get_base_gpio1(void) +{ + return (struct exynos5_gpio_part1 *)(EXYNOS5_GPIO_PART1_BASE); +} + +static int WaitForXfer(struct s3c24x0_i2c *i2c) +{ + int i; + + i = I2C_XFER_TIMEOUT_MS * 20; + while (!(readl(&i2c->iiccon) & I2CCON_IRPND)) { + if (i == 0) { + debug("%s: i2c xfer timeout\n", __func__); + return I2C_NOK_TOUT; + } + udelay(50); + i--; + } + + return I2C_OK; +} + +static int IsACK(struct s3c24x0_i2c *i2c) +{ + return !(readl(&i2c->iicstat) & I2CSTAT_NACK); +} + +static void ReadWriteByte(struct s3c24x0_i2c *i2c) +{ + uint32_t x; + + x = readl(&i2c->iiccon); + writel(x & ~I2CCON_IRPND, &i2c->iiccon); + /* FIXME(dhendrix): cannot use nested macro (compilation failure) */ +// writel(readl(&i2c->iiccon) & ~I2CCON_IRPND, &i2c->iiccon); +} + +static void i2c_ch_init(struct s3c24x0_i2c *i2c, int speed, int slaveadd) +{ + ulong freq, pres = 16, div; + + freq = clock_get_periph_rate(PERIPH_ID_I2C0); + /* calculate prescaler and divisor values */ + 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); +} + +/* TODO: determine if this is necessary to init board using FDT-provided info */ +#if 0 +void board_i2c_init(const void *blob) +{ + /* + * Turn off the early i2c configuration and init the i2c properly, + * this is done here to enable the use of i2c configs from FDT. + */ + i2c_set_early_reg(0); + +#ifdef CONFIG_OF_CONTROL + int node_list[EXYNOS_I2C_MAX_CONTROLLERS]; + int i, count; + + count = fdtdec_find_aliases_for_id(blob, "i2c", + COMPAT_SAMSUNG_S3C2440_I2C, node_list, + EXYNOS_I2C_MAX_CONTROLLERS); + + for (i = 0; i < count; i++) { + struct s3c24x0_i2c_bus *bus; + int node = node_list[i]; + + if (node < 0) + continue; + bus = &i2c_bus[i2c_busses]; + bus->regs = (struct s3c24x0_i2c *) + fdtdec_get_addr(blob, node, "reg"); + bus->id = (enum periph_id) + fdtdec_get_int(blob, node, "samsung,periph-id", -1); + bus->node = node; + bus->bus_num = i2c_busses++; + } +#else + int i; + + for (i = 0; i < EXYNOS_I2C_MAX_CONTROLLERS; i++) { + uintptr_t reg_addr = samsung_get_base_i2c() + + EXYNOS_I2C_SPACING * i; + + i2c_bus[i].regs = (struct s3c24x0_i2c_bus *)reg_addr; + i2c_bus[i].id = periph_for_dev[i]; + } + i2c_busses = EXYNOS_I2C_MAX_CONTROLLERS; +#endif +} +#endif + +/* + * MULTI BUS I2C support + */ +/* + * FIXME(dhendrix): not sure why this had to be guarded, but the code + * should probably go into an exynos5-specific .c file if it really is + * not generic. + */ +//#ifdef CONFIG_EXYNOS5 +static void i2c_bus_init(struct s3c24x0_i2c_bus *i2c, unsigned int bus) +{ + exynos_pinmux_config(i2c->id, 0); + + i2c_ch_init(i2c->regs, CONFIG_SYS_I2C_SPEED, CONFIG_SYS_I2C_SLAVE); +} +//#else +//#error "should not be here" +//static void i2c_bus_init(struct s3c24x0_i2c_bus *i2c, unsigned int bus) {} +//#endif + +#ifdef CONFIG_I2C_MULTI_BUS +int i2c_set_bus_num(unsigned int bus) +{ + struct s3c24x0_i2c_bus *i2c; + + i2c = get_bus(bus); + if (!i2c) + return -1; + g_current_bus = bus; + i2c_bus_init(i2c, g_current_bus); + + return 0; +} + +unsigned int i2c_get_bus_num(void) +{ + return g_current_bus; +} +#endif + +#ifdef CONFIG_OF_CONTROL +int i2c_get_bus_num_fdt(const void *blob, int node) +{ + enum fdt_compat_id compat; + fdt_addr_t reg; + int i; + + compat = fdtdec_lookup(blob, node); + if (compat != COMPAT_SAMSUNG_S3C2440_I2C) { + debug("%s: Not a supported I2C node\n", __func__); + return -1; + } + + reg = fdtdec_get_addr(blob, node, "reg"); + for (i = 0; i < i2c_busses; i++) + if (reg == (fdt_addr_t)(uintptr_t)i2c_bus[i].regs) + return i; + + debug("%s: Can't find any matched I2C bus\n", __func__); + return -1; +} + +int i2c_reset_port_fdt(const void *blob, int node) +{ + struct s3c24x0_i2c_bus *i2c; + + int bus; + + bus = i2c_get_bus_num_fdt(blob, node); + if (bus < 0) { + printf("could not get bus for node %d\n", node); + return -1; + } + i2c = get_bus(bus); + if (!i2c) { + printf("get_bus() failed for node node %d\n", node); + return -1; + } + + i2c_ch_init(i2c->regs, CONFIG_SYS_I2C_SPEED, CONFIG_SYS_I2C_SLAVE); + + return 0; +} +#endif + +/* + * Verify the whether I2C ACK was received or not + * + * @param i2c pointer to I2C register base + * @param buf array of data + * @param len length of data + * return I2C_OK when transmission done + * I2C_NACK otherwise + */ +static int i2c_send_verify(struct s3c24x0_i2c *i2c, unsigned char buf[], + unsigned char len) +{ + int i, result = I2C_OK; + + if (IsACK(i2c)) { + for (i = 0; (i < len) && (result == I2C_OK); i++) { + writel(buf[i], &i2c->iicds); + ReadWriteByte(i2c); + result = WaitForXfer(i2c); + if (result == I2C_OK && !IsACK(i2c)) + result = I2C_NACK; + } + } else { + result = I2C_NACK; + } + + return result; +} + +void i2c_init(int speed, int slaveadd) +{ + struct s3c24x0_i2c_bus *i2c; + struct exynos5_gpio_part1 *gpio; + int i; + uint32_t x; + + /* By default i2c channel 0 is the current bus */ + g_current_bus = 0; + + i2c = get_bus(g_current_bus); + if (!i2c) + return; + + i2c_bus_init(i2c, g_current_bus); + + /* wait for some time to give previous transfer a chance to finish */ + i = I2C_INIT_TIMEOUT_MS * 20; + while ((readl(&i2c->regs->iicstat) & I2CSTAT_BSY) && (i > 0)) { + udelay(50); + i--; + } + + gpio = exynos_get_base_gpio1(); + /* FIXME(dhendrix): cannot use nested macro (compilation failure) */ +// writel((readl(&gpio->b3.con) & ~0x00FF) | 0x0022, &gpio->b3.con); + x = readl(&gpio->b3.con); + writel((x & ~0x00FF) | 0x0022, &gpio->b3.con); + + i2c_ch_init(i2c->regs, speed, slaveadd); +} + +/* + * Send a STOP event and wait for it to have completed + * + * @param mode If it is a master transmitter or receiver + * @return I2C_OK if the line became idle before timeout I2C_NOK_TOUT otherwise + */ +static int i2c_send_stop(struct s3c24x0_i2c *i2c, int mode) +{ + int timeout; + + /* Setting the STOP event to fire */ + writel(mode | I2C_TXRX_ENA, &i2c->iicstat); + ReadWriteByte(i2c); + + /* Wait for the STOP to send and the bus to go idle */ + for (timeout = I2C_STOP_TIMEOUT_US; timeout > 0; timeout -= 5) { + if (!(readl(&i2c->iicstat) & I2CSTAT_BSY)) + return I2C_OK; + udelay(5); + } + + return I2C_NOK_TOUT; +} + +/* + * 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(struct s3c24x0_i2c *i2c, + unsigned char cmd_type, + unsigned char chip, + unsigned char addr[], + unsigned char addr_len, + unsigned char data[], + unsigned short data_len) +{ + int i, result, stop_bit_result; + uint32_t x; + + if (data == 0 || data_len == 0) { + /* Don't support data transfer of no length or to address 0 */ + debug("i2c_transfer: bad call\n"); + return I2C_NOK; + } + + /* Check I2C bus idle */ + i = I2C_IDLE_TIMEOUT_MS * 20; + while ((readl(&i2c->iicstat) & I2CSTAT_BSY) && (i > 0)) { + udelay(50); + i--; + } + + if (readl(&i2c->iicstat) & I2CSTAT_BSY) { + debug("%s: bus busy\n", __func__); + return I2C_NOK_TOUT; + } + + /* FIXME(dhendrix): cannot use nested macro (compilation failure) */ + //writel(readl(&i2c->iiccon) | I2CCON_ACKGEN, &i2c->iiccon); + x = readl(&i2c->iiccon); + writel(x | I2CCON_ACKGEN, &i2c->iiccon); + + if (addr && addr_len) { + writel(chip, &i2c->iicds); + /* send START */ + writel(I2C_MODE_MT | I2C_TXRX_ENA | I2C_START_STOP, + &i2c->iicstat); + if (WaitForXfer(i2c) == I2C_OK) + result = i2c_send_verify(i2c, addr, addr_len); + else + result = I2C_NACK; + } else + result = I2C_NACK; + + switch (cmd_type) { + case I2C_WRITE: + if (result == I2C_OK) + result = i2c_send_verify(i2c, data, data_len); + else { + writel(chip, &i2c->iicds); + /* send START */ + writel(I2C_MODE_MT | I2C_TXRX_ENA | I2C_START_STOP, + &i2c->iicstat); + if (WaitForXfer(i2c) == I2C_OK) + result = i2c_send_verify(i2c, data, data_len); + } + + if (result == I2C_OK) + result = WaitForXfer(i2c); + + stop_bit_result = i2c_send_stop(i2c, I2C_MODE_MT); + break; + + case I2C_READ: + { + int was_ok = (result == I2C_OK); + + writel(chip, &i2c->iicds); + /* resend START */ + writel(I2C_MODE_MR | I2C_TXRX_ENA | + I2C_START_STOP, &i2c->iicstat); + ReadWriteByte(i2c); + result = WaitForXfer(i2c); + + if (was_ok || IsACK(i2c)) { + i = 0; + while ((i < data_len) && (result == I2C_OK)) { + /* disable ACK for final READ */ + if (i == data_len - 1) { + /* FIXME(dhendrix): nested macro */ +#if 0 + writel(readl(&i2c->iiccon) & + ~I2CCON_ACKGEN, + &i2c->iiccon); +#endif + x = readl(&i2c->iiccon) & ~I2CCON_ACKGEN; + writel(x, &i2c->iiccon); + } + ReadWriteByte(i2c); + result = WaitForXfer(i2c); + data[i] = readl(&i2c->iicds); + i++; + } + } else { + result = I2C_NACK; + } + + stop_bit_result = i2c_send_stop(i2c, I2C_MODE_MR); + break; + } + + default: + debug("i2c_transfer: bad call\n"); + result = stop_bit_result = I2C_NOK; + break; + } + + /* + * If the transmission went fine, then only the stop bit was left to + * fail. Otherwise, the real failure we're interested in came before + * that, during the actual transmission. + */ + return (result == I2C_OK) ? stop_bit_result : result; +} + +int i2c_probe(uchar chip) +{ + struct s3c24x0_i2c_bus *i2c; + uchar buf[1]; + int ret; + + i2c = get_bus(g_current_bus); + if (!i2c) + return -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). + */ + if (board_i2c_claim_bus(i2c->node)) { + debug("I2C cannot claim bus %d\n", i2c->bus_num); + return -1; + } + ret = i2c_transfer(i2c->regs, I2C_READ, chip << 1, 0, 0, buf, 1); + board_i2c_release_bus(i2c->node); + + return ret != I2C_OK; +} + +int i2c_read(uchar chip, uint addr, int alen, uchar *buffer, int len) +{ + struct s3c24x0_i2c_bus *i2c; + uchar xaddr[4]; + int ret; + + if (alen > 4) { + debug("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 + i2c = get_bus(g_current_bus); + if (!i2c) + return -1; + if (board_i2c_claim_bus(i2c->node)) { + debug("I2C cannot claim bus %d\n", i2c->bus_num); + return -1; + } + ret = i2c_transfer(i2c->regs, I2C_READ, chip << 1, &xaddr[4 - alen], + alen, buffer, len); + board_i2c_release_bus(i2c->node); + if (ret) { + debug("I2c read: failed %d\n", ret); + return 1; + } + return 0; +} + +int i2c_write(uchar chip, uint addr, int alen, uchar *buffer, int len) +{ + struct s3c24x0_i2c_bus *i2c; + uchar xaddr[4]; + int ret; + + if (alen > 4) { + debug("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 + i2c = get_bus(g_current_bus); + if (!i2c) + return -1; + if (board_i2c_claim_bus(i2c->node)) { + debug("I2C cannot claim bus %d\n", i2c->bus_num); + return -1; + } + ret = i2c_transfer(i2c->regs, I2C_WRITE, chip << 1, &xaddr[4 - alen], + alen, buffer, len); + board_i2c_release_bus(i2c->node); + + return ret != 0; +} diff --git a/src/cpu/samsung/s5p-common/s3c24x0_i2c.h b/src/cpu/samsung/s5p-common/s3c24x0_i2c.h new file mode 100644 index 0000000000..eb68af78f0 --- /dev/null +++ b/src/cpu/samsung/s5p-common/s3c24x0_i2c.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2012 Samsung Electronics + * + * 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 + */ + +#ifndef _S3C24X0_I2C_H +#define _S3C24X0_I2C_H + +/* FIXME: gross hack */ +#include "cpu/samsung/exynos5250/periph.h" + +struct s3c24x0_i2c { + u32 iiccon; + u32 iicstat; + u32 iicadd; + u32 iicds; + u32 iiclc; +}; + +struct s3c24x0_i2c_bus { + int node; /* device tree node */ + int bus_num; /* i2c bus number */ + struct s3c24x0_i2c *regs; + enum periph_id id; +}; +#endif /* _S3C24X0_I2C_H */ |