diff options
author | Vic (Chun-Ju) Yang <victoryang@chromium.org> | 2013-12-03 17:35:23 +0800 |
---|---|---|
committer | chrome-internal-fetch <chrome-internal-fetch@google.com> | 2013-12-06 05:21:41 +0000 |
commit | f9e00364ef84180e89e9c4bfb79e0123ee54923d (patch) | |
tree | 4ae5a4c4db864c4541c49c6010b79579b03bc6af | |
parent | 1762de9d19d2671cc56e5a479055379a346030d3 (diff) | |
download | chrome-ec-f9e00364ef84180e89e9c4bfb79e0123ee54923d.tar.gz |
mec1322: I2C driver
This adds the driver for MEC1322 I2C controller.
BUG=chrome-os-partner:24107
TEST=Hook up TSU6721 to eval board. Do the following tests:
- 'i2cscan' and see TSU6721.
- Read device ID register and get correct value.
- Add 3 tasks randomly doing I2C read and writes. Check there is
no error.
BRANCH=None
Change-Id: I465f73fe8177a8df6b56c57e594cd733caea37d4
Signed-off-by: Vic (Chun-Ju) Yang <victoryang@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/178591
Reviewed-by: Vincent Palatin <vpalatin@chromium.org>
Reviewed-by: Randall Spangler <rspangler@chromium.org>
-rw-r--r-- | board/mec1322_evb/board.c | 12 | ||||
-rw-r--r-- | chip/mec1322/build.mk | 1 | ||||
-rw-r--r-- | chip/mec1322/config_chip.h | 2 | ||||
-rw-r--r-- | chip/mec1322/i2c.c | 343 | ||||
-rw-r--r-- | chip/mec1322/registers.h | 33 |
5 files changed, 388 insertions, 3 deletions
diff --git a/board/mec1322_evb/board.c b/board/mec1322_evb/board.c index 73a2a19bb4..ced2c5ab27 100644 --- a/board/mec1322_evb/board.c +++ b/board/mec1322_evb/board.c @@ -6,6 +6,7 @@ #include "fan.h" #include "gpio.h" +#include "i2c.h" #include "registers.h" #include "util.h" @@ -23,9 +24,10 @@ BUILD_ASSERT(ARRAY_SIZE(gpio_list) == GPIO_COUNT); /* Pins with alternate functions */ const struct gpio_alt_func gpio_alt_funcs[] = { - {GPIO_PORT(16), 0x24, 1, MODULE_UART}, /* UART0 */ - {GPIO_PORT(3), (1 << 4), 3, MODULE_PWM_FAN}, + {GPIO_PORT(16), 0x24, 1, MODULE_UART}, /* UART0 */ + {GPIO_PORT(3), (1 << 4), 3, MODULE_PWM_FAN}, {GPIO_PORT(14), (1 << 0), 3, MODULE_PWM_FAN}, + {GPIO_PORT(1), 0x60, 2, MODULE_I2C}, /* I2C0 */ }; const int gpio_alt_funcs_count = ARRAY_SIZE(gpio_alt_funcs); @@ -40,3 +42,9 @@ const struct fan_t fans[] = { }, }; BUILD_ASSERT(ARRAY_SIZE(fans) == CONFIG_FANS); + +/* I2C ports */ +const struct i2c_port_t i2c_ports[] = { + {"port0", 0, 100}, +}; +const unsigned int i2c_ports_used = ARRAY_SIZE(i2c_ports); diff --git a/chip/mec1322/build.mk b/chip/mec1322/build.mk index 0f7262b8f9..bbc040a284 100644 --- a/chip/mec1322/build.mk +++ b/chip/mec1322/build.mk @@ -14,6 +14,7 @@ CFLAGS_CPU+=-march=armv7e-m -mcpu=cortex-m4 # Required chip modules chip-y=clock.o gpio.o hwtimer.o system.o uart.o jtag.o chip-$(CONFIG_FANS)+=fan.o +chip-$(CONFIG_I2C)+=i2c.o chip-$(CONFIG_LPC)+=lpc.o chip-$(CONFIG_PWM)+=pwm.o chip-$(CONFIG_WATCHDOG)+=watchdog.o diff --git a/chip/mec1322/config_chip.h b/chip/mec1322/config_chip.h index e49db95fed..e47f102759 100644 --- a/chip/mec1322/config_chip.h +++ b/chip/mec1322/config_chip.h @@ -81,11 +81,11 @@ /* Optional features present on this chip */ #if 0 #define CONFIG_ADC -#define CONFIG_I2C #define CONFIG_PECI #define CONFIG_SWITCH #define CONFIG_MPU #endif +#define CONFIG_I2C #define CONFIG_LPC #define CONFIG_FPU diff --git a/chip/mec1322/i2c.c b/chip/mec1322/i2c.c new file mode 100644 index 0000000000..98dd28e170 --- /dev/null +++ b/chip/mec1322/i2c.c @@ -0,0 +1,343 @@ +/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/* I2C port module for MEC1322 */ + +#include "common.h" +#include "console.h" +#include "gpio.h" +#include "hooks.h" +#include "i2c.h" +#include "registers.h" +#include "task.h" +#include "timer.h" +#include "util.h" + +#define CPUTS(outstr) cputs(CC_I2C, outstr) +#define CPRINTF(format, args...) cprintf(CC_I2C, format, ## args) + +#define I2C_CLOCK 16000000 /* 16 MHz */ + +/* Status */ +#define STS_NBB (1 << 0) /* Bus busy */ +#define STS_LAB (1 << 1) /* Arbitration lost */ +#define STS_LRB (1 << 3) /* Last received bit */ +#define STS_BER (1 << 4) /* Bus error */ +#define STS_PIN (1 << 7) /* Pending interrupt */ + +/* Control */ +#define CTRL_ACK (1 << 0) /* Acknowledge */ +#define CTRL_STO (1 << 1) /* STOP */ +#define CTRL_STA (1 << 2) /* START */ +#define CTRL_ENI (1 << 3) /* Enable interrupt */ +#define CTRL_ESO (1 << 6) /* Enable serial output */ +#define CTRL_PIN (1 << 7) /* Pending interrupt not */ + +static task_id_t task_waiting_on_port[I2C_PORT_COUNT]; + +static void configure_port_speed(int port, int kbps) +{ + int min_t_low, min_t_high; + int t_low, t_high; + const int period = I2C_CLOCK / 1000 / kbps; + + /* + * Refer to NXP UM10204 for minimum timing requirement of T_Low and + * T_High. + * http://www.nxp.com/documents/user_manual/UM10204.pdf + */ + + if (kbps > 400) { + /* Fast mode plus */ + min_t_low = I2C_CLOCK * 0.5 / 1000000 - 1; /* 0.5 us */ + min_t_high = I2C_CLOCK * 0.26 / 1000000 - 1; /* 0.26 us */ + MEC1322_I2C_DATA_TIM(port) = 0x06060601; + MEC1322_I2C_DATA_TIM_2(port) = 0x06; + } else if (kbps > 100) { + /* Fast mode */ + min_t_low = I2C_CLOCK * 1.3 / 1000000 - 1; /* 1.3 us */ + min_t_high = I2C_CLOCK * 0.6 / 1000000 - 1; /* 0.6 us */ + MEC1322_I2C_DATA_TIM(port) = 0x040a0a01; + MEC1322_I2C_DATA_TIM_2(port) = 0x0a; + } else { + /* Standard mode */ + min_t_low = I2C_CLOCK * 4.7 / 1000000 - 1; /* 4.7 us */ + min_t_high = I2C_CLOCK * 4.0 / 1000000 - 1; /* 4.0 us */ + MEC1322_I2C_DATA_TIM(port) = 0x0c4d5006; + MEC1322_I2C_DATA_TIM_2(port) = 0x4d; + } + + t_low = MAX(min_t_low + 1, period / 2); + t_high = MAX(min_t_high + 1, period - t_low); + + MEC1322_I2C_BUS_CLK(port) = ((t_high & 0xff) << 8) | + (t_low & 0xff); +} + +static void configure_port(int port, int kbps) +{ + MEC1322_I2C_CTRL(port) = CTRL_PIN; + MEC1322_I2C_OWN_ADDR(port) = 0x0; + configure_port_speed(port, kbps); + MEC1322_I2C_CTRL(port) = CTRL_PIN | CTRL_ESO | CTRL_ACK | CTRL_ENI; + MEC1322_I2C_CONFIG(port) |= 1 << 10; /* ENAB */ + + /* Enable interrupt */ + MEC1322_I2C_CONFIG(port) |= 1 << 29; /* ENIDI */ + MEC1322_INT_ENABLE(12) |= (1 << port); + MEC1322_INT_BLK_EN |= 1 << 12; + task_enable_irq(MEC1322_IRQ_I2C_0 + port); +} + +static void reset_port(int port) +{ + int i; + + MEC1322_I2C_CONFIG(port) |= 1 << 9; + udelay(100); + MEC1322_I2C_CONFIG(port) &= ~(1 << 9); + + for (i = 0; i < i2c_ports_used; ++i) + if (port == i2c_ports[i].port) { + configure_port(i2c_ports[i].port, i2c_ports[i].kbps); + break; + } +} + +static int wait_for_interrupt(int port, int *event) +{ + task_waiting_on_port[port] = task_get_current(); + task_enable_irq(MEC1322_IRQ_I2C_0 + port); + /* + * We want to wait here quietly until the I2C interrupt comes + * along, but we don't want to lose any pending events that + * will be needed by the task that started the I2C transaction + * in the first place. So we save them up and restore them when + * the I2C is either completed or timed out. Refer to the + * implementation of usleep() for a similar situation. + */ + *event |= (task_wait_event(SECOND) & ~TASK_EVENT_I2C_IDLE); + task_waiting_on_port[port] = TASK_ID_INVALID; + if (*event & TASK_EVENT_TIMER) { + /* Restore any events that we saw while waiting */ + task_set_event(task_get_current(), + (*event & ~TASK_EVENT_TIMER), 0); + return EC_ERROR_TIMEOUT; + } + return EC_SUCCESS; +} + +static int wait_idle(int port) +{ + uint8_t sts = MEC1322_I2C_STATUS(port); + int rv; + int event = 0; + + while (!(sts & STS_NBB)) { + rv = wait_for_interrupt(port, &event); + if (rv) + return rv; + sts = MEC1322_I2C_STATUS(port); + } + /* + * Restore any events that we saw while waiting. TASK_EVENT_TIMER isn't + * one, because we've handled it above. + */ + task_set_event(task_get_current(), event, 0); + + if (sts & (STS_BER | STS_LAB)) + return EC_ERROR_UNKNOWN; + return EC_SUCCESS; +} + +static int wait_byte_done(int port) +{ + uint8_t sts = MEC1322_I2C_STATUS(port); + int rv; + int event = 0; + + while (sts & STS_PIN) { + rv = wait_for_interrupt(port, &event); + if (rv) + return rv; + sts = MEC1322_I2C_STATUS(port); + } + /* + * Restore any events that we saw while waiting. TASK_EVENT_TIMER isn't + * one, because we've handled it above. + */ + task_set_event(task_get_current(), event, 0); + + return sts & STS_LRB; +} + +static inline void fill_in_buf(uint8_t *in, int id, uint8_t val) +{ + /* + * On MEC1322, first byte read is dummy read (slave addr). + * Throw it away. + */ + if (id != 0) + in[id - 1] = val; +} + +int i2c_xfer(int port, int slave_addr, const uint8_t *out, int out_size, + uint8_t *in, int in_size, int flags) +{ + int i; + int started = (flags & I2C_XFER_START) ? 0 : 1; + uint8_t reg_sts; + + if (out_size == 0 && in_size == 0) + return EC_SUCCESS; + + wait_idle(port); + + reg_sts = MEC1322_I2C_STATUS(port); + if (!started && + ((reg_sts & (STS_BER | STS_LAB)) || !(reg_sts & STS_NBB))) { + CPRINTF("[%T I2C%d bad status 0x%02x]\n", port, reg_sts); + + /* Bus error, bus busy, or arbitration lost. Reset port. */ + reset_port(port); + + /* + * We don't know what edges the slave saw, so sleep long enough + * that the slave will see the new start condition below. + */ + usleep(1000); + } + + if (out) { + MEC1322_I2C_DATA(port) = (uint8_t)slave_addr; + + /* + * Clock out the slave address. Send START bit if start flag is + * set. + */ + MEC1322_I2C_CTRL(port) = CTRL_PIN | CTRL_ESO | CTRL_ENI | + CTRL_ACK | (started ? 0 : CTRL_STA); + if (!started) + started = 1; + + for (i = 0; i < out_size; ++i) { + if (wait_byte_done(port)) + goto err_i2c_xfer; + MEC1322_I2C_DATA(port) = out[i]; + } + if (wait_byte_done(port)) + goto err_i2c_xfer; + + /* + * Send STOP bit if the stop flag is on, and caller + * doesn't expect to receive data. + */ + if ((flags & I2C_XFER_STOP) && in_size == 0) { + MEC1322_I2C_CTRL(port) = CTRL_PIN | CTRL_ESO | + CTRL_STO | CTRL_ACK; + } + } + + if (in_size) { + if (out_size) { + /* resend start bit when change direction */ + MEC1322_I2C_CTRL(port) = CTRL_ESO | CTRL_STA | + CTRL_ACK | CTRL_ENI; + } + + MEC1322_I2C_DATA(port) = (uint8_t)slave_addr | 0x01; + + if (!started) { + started = 1; + /* Clock out slave address with START bit */ + MEC1322_I2C_CTRL(port) = CTRL_PIN | CTRL_ESO | + CTRL_STA | CTRL_ACK | CTRL_ENI; + } + + /* On MEC1322, first byte read is dummy read (slave addr) */ + in_size++; + + for (i = 0; i < in_size - 2; ++i) { + if (wait_byte_done(port)) + goto err_i2c_xfer; + fill_in_buf(in, i, MEC1322_I2C_DATA(port)); + } + if (wait_byte_done(port)) + goto err_i2c_xfer; + + /* + * De-assert ACK bit before reading the next to last byte, + * so that the last byte is NACK'ed. + */ + MEC1322_I2C_CTRL(port) = CTRL_ESO | CTRL_ENI; + fill_in_buf(in, in_size - 2, MEC1322_I2C_DATA(port)); + if (wait_byte_done(port)) + goto err_i2c_xfer; + + /* Send STOP if stop flag is set */ + MEC1322_I2C_CTRL(port) = + CTRL_PIN | CTRL_ESO | CTRL_ACK | + ((flags & I2C_XFER_STOP) ? CTRL_STO : 0); + + /* Now read the last byte */ + fill_in_buf(in, in_size - 1, MEC1322_I2C_DATA(port)); + } + + /* Check for error conditions */ + if (MEC1322_I2C_STATUS(port) & (STS_LAB | STS_BER)) + return EC_ERROR_UNKNOWN; + + return EC_SUCCESS; +err_i2c_xfer: + /* Send STOP and return error */ + MEC1322_I2C_CTRL(port) = CTRL_PIN | CTRL_ESO | CTRL_STO | CTRL_ACK; + return EC_ERROR_UNKNOWN; +} + +int i2c_get_line_levels(int port) +{ + return (MEC1322_I2C_BB_CTRL(port) >> 5) & 0x3; +} + +static void i2c_init(void) +{ + int i; + + /* Configure GPIOs */ + gpio_config_module(MODULE_I2C, 1); + + for (i = 0; i < i2c_ports_used; ++i) + configure_port(i2c_ports[i].port, i2c_ports[i].kbps); +} +DECLARE_HOOK(HOOK_INIT, i2c_init, HOOK_PRIO_DEFAULT); + +static void handle_interrupt(int port) +{ + int id = task_waiting_on_port[port]; + + /* Clear the interrupt status */ + MEC1322_I2C_COMPLETE(port) |= 1 << 29; + + /* + * Write to control register interferes with I2C transaction. + * Instead, let's disable IRQ from the core until the next time + * we want to wait for STS_PIN/STS_NBB. + */ + task_disable_irq(MEC1322_IRQ_I2C_0 + port); + + /* Wake up the task which was waiting on the I2C interrupt, if any. */ + if (id != TASK_ID_INVALID) + task_set_event(id, TASK_EVENT_I2C_IDLE, 0); +} + +static void i2c0_interrupt(void) { handle_interrupt(0); } +static void i2c1_interrupt(void) { handle_interrupt(1); } +static void i2c2_interrupt(void) { handle_interrupt(2); } +static void i2c3_interrupt(void) { handle_interrupt(3); } + +DECLARE_IRQ(MEC1322_IRQ_I2C_0, i2c0_interrupt, 2); +DECLARE_IRQ(MEC1322_IRQ_I2C_1, i2c1_interrupt, 2); +DECLARE_IRQ(MEC1322_IRQ_I2C_2, i2c2_interrupt, 2); +DECLARE_IRQ(MEC1322_IRQ_I2C_3, i2c3_interrupt, 2); diff --git a/chip/mec1322/registers.h b/chip/mec1322/registers.h index e1827fbc61..3f9c350c5c 100644 --- a/chip/mec1322/registers.h +++ b/chip/mec1322/registers.h @@ -207,6 +207,39 @@ static inline uintptr_t gpio_port_base(int port_id) #define MEC1322_FAN_STATUS REG8(MEC1322_FAN_BASE + 0x11) +/* I2C */ +#define MEC1322_I2C0_BASE 0x40001800 +#define MEC1322_I2C1_BASE 0x4000ac00 +#define MEC1322_I2C2_BASE 0x4000b000 +#define MEC1322_I2C3_BASE 0x4000b400 +#define MEC1322_I2C_BASESEP 0x00000400 +#define MEC1322_I2C_ADDR(port, offset) \ + (offset + (port == 0 ? MEC1322_I2C0_BASE : \ + MEC1322_I2C1_BASE + MEC1322_I2C_BASESEP * (port - 1))) + +#define MEC1322_I2C_CTRL(port) REG8(MEC1322_I2C_ADDR(port, 0x0)) +#define MEC1322_I2C_STATUS(port) REG8(MEC1322_I2C_ADDR(port, 0x0)) +#define MEC1322_I2C_OWN_ADDR(port) REG16(MEC1322_I2C_ADDR(port, 0x4)) +#define MEC1322_I2C_DATA(port) REG8(MEC1322_I2C_ADDR(port, 0x8)) +#define MEC1322_I2C_MASTER_CMD(port) REG32(MEC1322_I2C_ADDR(port, 0xc)) +#define MEC1322_I2C_SLAVE_CMD(port) REG32(MEC1322_I2C_ADDR(port, 0x10)) +#define MEC1322_I2C_PEC(port) REG8(MEC1322_I2C_ADDR(port, 0x14)) +#define MEC1322_I2C_DATA_TIM_2(port) REG8(MEC1322_I2C_ADDR(port, 0x18)) +#define MEC1322_I2C_COMPLETE(port) REG32(MEC1322_I2C_ADDR(port, 0x20)) +#define MEC1322_I2C_IDLE_SCALE(port) REG32(MEC1322_I2C_ADDR(port, 0x24)) +#define MEC1322_I2C_CONFIG(port) REG32(MEC1322_I2C_ADDR(port, 0x28)) +#define MEC1322_I2C_BUS_CLK(port) REG16(MEC1322_I2C_ADDR(port, 0x2c)) +#define MEC1322_I2C_BLK_ID(port) REG8(MEC1322_I2C_ADDR(port, 0x30)) +#define MEC1322_I2C_REV(port) REG8(MEC1322_I2C_ADDR(port, 0x34)) +#define MEC1322_I2C_BB_CTRL(port) REG8(MEC1322_I2C_ADDR(port, 0x38)) +#define MEC1322_I2C_DATA_TIM(port) REG32(MEC1322_I2C_ADDR(port, 0x40)) +#define MEC1322_I2C_TOUT_SCALE(port) REG32(MEC1322_I2C_ADDR(port, 0x44)) +#define MEC1322_I2C_SLAVE_TX_BUF(port) REG8(MEC1322_I2C_ADDR(port, 0x48)) +#define MEC1322_I2C_SLAVE_RX_BUF(port) REG8(MEC1322_I2C_ADDR(port, 0x4c)) +#define MEC1322_I2C_MASTER_TX_BUF(port) REG8(MEC1322_I2C_ADDR(port, 0x50)) +#define MEC1322_I2C_MASTER_RX_BUF(port) REG8(MEC1322_I2C_ADDR(port, 0x54)) + + /* IRQ Numbers */ #define MEC1322_IRQ_I2C_0 0 #define MEC1322_IRQ_I2C_1 1 |