/* * Setup helpers for Two Wire Interface (TWI) (I²C) Allwinner CPUs * * Only functionality for I²C master is provided. * Largely based on the uboot-sunxi code. * * Copyright (C) 2012 Henrik Nordstrom * Copyright (C) 2013 Alexandru Gagniuc * Subject to the GNU GPL v2, or (at your option) any later version. */ #include "memmap.h" #include "twi.h" #include #include #include #define TWI_BASE(n) (A1X_TWI0_BASE + 0x400 * (n)) #define TWI_TIMEOUT (50 * 1000) static u8 is_busy(struct a1x_twi *twi) { return (read32(&twi->stat) != TWI_STAT_IDLE); } static enum cb_err wait_until_idle(struct a1x_twi *twi) { u32 i = TWI_TIMEOUT; while (i-- && is_busy((twi))) udelay(1); return i ? CB_SUCCESS : CB_ERR; } /* FIXME: This function is basic, and unintelligent */ static void configure_clock(struct a1x_twi *twi, u32 speed_hz) { /* FIXME: We assume clock is 24MHz, which may not be the case */ u32 apb_clk = 24000000, m, n; /* Pre-divide the clock by 8 */ n = 3; m = (apb_clk >> n) / speed_hz; write32(TWI_CLK_M(m) | TWI_CLK_N(n), &twi->clk); } void a1x_twi_init(u8 bus, u32 speed_hz) { u32 i = TWI_TIMEOUT; struct a1x_twi *twi = (void *)TWI_BASE(bus); configure_clock(twi, speed_hz); /* Enable the I²C bus */ write32(TWI_CTL_BUS_EN, &twi->ctl); /* Issue soft reset */ write32(1, &twi->reset); while (i-- && read32(&twi->reset)) udelay(1); } static void clear_interrupt_flag(struct a1x_twi *twi) { write32(read32(&twi->ctl) & ~TWI_CTL_INT_FLAG, &twi->ctl); } static void i2c_send_data(struct a1x_twi *twi, u8 data) { write32(data, &twi->data); clear_interrupt_flag(twi); } static enum twi_status wait_for_status(struct a1x_twi *twi) { u32 i = TWI_TIMEOUT; /* Wait until interrupt is asserted again */ while (i-- && !(read32(&twi->ctl) & TWI_CTL_INT_FLAG)) udelay(1); /* A timeout here most likely indicates a bus error */ return i ? read32(&twi->stat) : TWI_STAT_BUS_ERROR; } static void i2c_send_start(struct a1x_twi *twi) { u32 reg32, i; /* Send START condition */ reg32 = read32(&twi->ctl); reg32 &= ~TWI_CTL_INT_FLAG; reg32 |= TWI_CTL_M_START; write32(reg32, &twi->ctl); /* M_START is automatically cleared after condition is transmitted */ i = TWI_TIMEOUT; while (i-- && (read32(&twi->ctl) & TWI_CTL_M_START)) udelay(1); } static void i2c_send_stop(struct a1x_twi *twi) { u32 reg32; /* Send STOP condition */ reg32 = read32(&twi->ctl); reg32 &= ~TWI_CTL_INT_FLAG; reg32 |= TWI_CTL_M_STOP; write32(reg32, &twi->ctl); } static int i2c_read(unsigned bus, unsigned chip, unsigned addr, uint8_t *buf, unsigned len) { unsigned count = len; enum twi_status expected_status; struct a1x_twi *twi = (void *)TWI_BASE(bus); if (wait_until_idle(twi) != CB_SUCCESS) return CB_ERR; i2c_send_start(twi); if (wait_for_status(twi) != TWI_STAT_TX_START) return CB_ERR; /* Send chip address */ i2c_send_data(twi, chip << 1); if (wait_for_status(twi) != TWI_STAT_TX_AW_ACK) return CB_ERR; /* Send data address */ i2c_send_data(twi, addr); if (wait_for_status(twi) != TWI_STAT_TXD_ACK) return CB_ERR; /* Send restart for read */ i2c_send_start(twi); if (wait_for_status(twi) != TWI_STAT_TX_RSTART) return CB_ERR; /* Send chip address */ i2c_send_data(twi, chip << 1 | 1); if (wait_for_status(twi) != TWI_STAT_TX_AR_ACK) return CB_ERR; /* Start ACK-ing received data */ setbits_le32(&twi->ctl, TWI_CTL_A_ACK); expected_status = TWI_STAT_RXD_ACK; /* Read data */ while (count > 0) { if (count == 1) { /* Do not ACK the last byte */ clrbits_le32(&twi->ctl, TWI_CTL_A_ACK); expected_status = TWI_STAT_RXD_NAK; } clear_interrupt_flag(twi); if (wait_for_status(twi) != expected_status) return CB_ERR; *buf++ = read32(&twi->data); count--; } i2c_send_stop(twi); return len; } static int i2c_write(unsigned bus, unsigned chip, unsigned addr, const uint8_t *buf, unsigned len) { unsigned count = len; struct a1x_twi *twi = (void *)TWI_BASE(bus); if (wait_until_idle(twi) != CB_SUCCESS) return CB_ERR; i2c_send_start(twi); if (wait_for_status(twi) != TWI_STAT_TX_START) return CB_ERR; /* Send chip address */ i2c_send_data(twi, chip << 1); if (wait_for_status(twi) != TWI_STAT_TX_AW_ACK) return CB_ERR; /* Send data address */ i2c_send_data(twi, addr); if (wait_for_status(twi) != TWI_STAT_TXD_ACK) return CB_ERR; /* Send data */ while (count > 0) { i2c_send_data(twi, *buf++); if (wait_for_status(twi) != TWI_STAT_TXD_ACK) return CB_ERR; count--; } i2c_send_stop(twi); return len; } /* * This transfer function is not complete or correct, but it provides * the basic support that the above read and write functions previously * provided directly. It is extremely limited and not useful for * advanced drivers like TPM. * * TODO: Rewite the i2c_transfer and supporting functions * */ int i2c_transfer(unsigned bus, struct i2c_seg *segments, int count) { struct i2c_seg *seg = segments; if (seg->read) { /* Read has one buf for the addr and one for the data */ if (count != 2) return -1; if(i2c_read(bus, seg->chip, *seg->buf, seg[1].buf, seg[1].len) < 0) return -1; } else { /* Write buf has adder and data. */ if (count != 1) return -1; if(i2c_write(bus, seg->chip, *seg->buf, seg->buf+1, seg->len-1) < 0) return -1; } return 0; }