Alexandru Gagniuc | 5c4bde7 | 2013-12-28 21:50:54 -0500 | [diff] [blame] | 1 | /* |
| 2 | * Setup helpers for Two Wire Interface (TWI) (I²C) Allwinner CPUs |
| 3 | * |
| 4 | * Only functionality for I²C master is provided. |
| 5 | * Largely based on the uboot-sunxi code. |
| 6 | * |
| 7 | * Copyright (C) 2012 Henrik Nordstrom <henrik@henriknordstrom.net> |
| 8 | * Copyright (C) 2013 Alexandru Gagniuc <mr.nuke.me@gmail.com> |
| 9 | * Subject to the GNU GPL v2, or (at your option) any later version. |
| 10 | */ |
| 11 | |
| 12 | #include "memmap.h" |
| 13 | #include "twi.h" |
| 14 | |
| 15 | #include <arch/io.h> |
| 16 | #include <delay.h> |
| 17 | #include <device/i2c.h> |
| 18 | |
| 19 | #define TWI_BASE(n) (A1X_TWI0_BASE + 0x400 * (n)) |
| 20 | |
| 21 | #define TWI_TIMEOUT (50 * 1000) |
| 22 | |
| 23 | static u8 is_busy(struct a1x_twi *twi) |
| 24 | { |
| 25 | return (read32(&twi->stat) != TWI_STAT_IDLE); |
| 26 | } |
| 27 | |
| 28 | static enum cb_err wait_until_idle(struct a1x_twi *twi) |
| 29 | { |
| 30 | u32 i = TWI_TIMEOUT; |
| 31 | while (i-- && is_busy((twi))) |
| 32 | udelay(1); |
| 33 | return i ? CB_SUCCESS : CB_ERR; |
| 34 | } |
| 35 | |
| 36 | /* FIXME: This function is basic, and unintelligent */ |
| 37 | static void configure_clock(struct a1x_twi *twi, u32 speed_hz) |
| 38 | { |
| 39 | /* FIXME: We assume clock is 24MHz, which may not be the case */ |
| 40 | u32 apb_clk = 24000000, m, n; |
| 41 | |
| 42 | /* Pre-divide the clock by 8 */ |
| 43 | n = 3; |
| 44 | m = (apb_clk >> n) / speed_hz; |
Julius Werner | 2f37bd6 | 2015-02-19 14:51:15 -0800 | [diff] [blame] | 45 | write32(&twi->clk, TWI_CLK_M(m) | TWI_CLK_N(n)); |
Alexandru Gagniuc | 5c4bde7 | 2013-12-28 21:50:54 -0500 | [diff] [blame] | 46 | } |
| 47 | |
| 48 | void a1x_twi_init(u8 bus, u32 speed_hz) |
| 49 | { |
| 50 | u32 i = TWI_TIMEOUT; |
| 51 | struct a1x_twi *twi = (void *)TWI_BASE(bus); |
| 52 | |
| 53 | configure_clock(twi, speed_hz); |
| 54 | |
| 55 | /* Enable the I²C bus */ |
Julius Werner | 2f37bd6 | 2015-02-19 14:51:15 -0800 | [diff] [blame] | 56 | write32(&twi->ctl, TWI_CTL_BUS_EN); |
Alexandru Gagniuc | 5c4bde7 | 2013-12-28 21:50:54 -0500 | [diff] [blame] | 57 | /* Issue soft reset */ |
Julius Werner | 2f37bd6 | 2015-02-19 14:51:15 -0800 | [diff] [blame] | 58 | write32(&twi->reset, 1); |
Alexandru Gagniuc | 5c4bde7 | 2013-12-28 21:50:54 -0500 | [diff] [blame] | 59 | |
| 60 | while (i-- && read32(&twi->reset)) |
| 61 | udelay(1); |
| 62 | } |
| 63 | |
| 64 | static void clear_interrupt_flag(struct a1x_twi *twi) |
| 65 | { |
Julius Werner | 2f37bd6 | 2015-02-19 14:51:15 -0800 | [diff] [blame] | 66 | write32(&twi->ctl, read32(&twi->ctl) & ~TWI_CTL_INT_FLAG); |
Alexandru Gagniuc | 5c4bde7 | 2013-12-28 21:50:54 -0500 | [diff] [blame] | 67 | } |
| 68 | |
| 69 | static void i2c_send_data(struct a1x_twi *twi, u8 data) |
| 70 | { |
Julius Werner | 2f37bd6 | 2015-02-19 14:51:15 -0800 | [diff] [blame] | 71 | write32(&twi->data, data); |
Alexandru Gagniuc | 5c4bde7 | 2013-12-28 21:50:54 -0500 | [diff] [blame] | 72 | clear_interrupt_flag(twi); |
| 73 | } |
| 74 | |
| 75 | static enum twi_status wait_for_status(struct a1x_twi *twi) |
| 76 | { |
| 77 | u32 i = TWI_TIMEOUT; |
| 78 | /* Wait until interrupt is asserted again */ |
| 79 | while (i-- && !(read32(&twi->ctl) & TWI_CTL_INT_FLAG)) |
| 80 | udelay(1); |
| 81 | /* A timeout here most likely indicates a bus error */ |
| 82 | return i ? read32(&twi->stat) : TWI_STAT_BUS_ERROR; |
| 83 | } |
| 84 | |
| 85 | static void i2c_send_start(struct a1x_twi *twi) |
| 86 | { |
| 87 | u32 reg32, i; |
| 88 | |
| 89 | /* Send START condition */ |
| 90 | reg32 = read32(&twi->ctl); |
| 91 | reg32 &= ~TWI_CTL_INT_FLAG; |
| 92 | reg32 |= TWI_CTL_M_START; |
Julius Werner | 2f37bd6 | 2015-02-19 14:51:15 -0800 | [diff] [blame] | 93 | write32(&twi->ctl, reg32); |
Alexandru Gagniuc | 5c4bde7 | 2013-12-28 21:50:54 -0500 | [diff] [blame] | 94 | |
| 95 | /* M_START is automatically cleared after condition is transmitted */ |
| 96 | i = TWI_TIMEOUT; |
| 97 | while (i-- && (read32(&twi->ctl) & TWI_CTL_M_START)) |
| 98 | udelay(1); |
| 99 | } |
| 100 | |
| 101 | static void i2c_send_stop(struct a1x_twi *twi) |
| 102 | { |
| 103 | u32 reg32; |
| 104 | |
| 105 | /* Send STOP condition */ |
| 106 | reg32 = read32(&twi->ctl); |
| 107 | reg32 &= ~TWI_CTL_INT_FLAG; |
| 108 | reg32 |= TWI_CTL_M_STOP; |
Julius Werner | 2f37bd6 | 2015-02-19 14:51:15 -0800 | [diff] [blame] | 109 | write32(&twi->ctl, reg32); |
Alexandru Gagniuc | 5c4bde7 | 2013-12-28 21:50:54 -0500 | [diff] [blame] | 110 | } |
| 111 | |
Alexandru Gagniuc | e5ccbfd | 2015-02-13 16:30:30 -0600 | [diff] [blame] | 112 | static int i2c_read(struct a1x_twi *twi, uint8_t chip, |
| 113 | uint8_t *buf, size_t len) |
Alexandru Gagniuc | 5c4bde7 | 2013-12-28 21:50:54 -0500 | [diff] [blame] | 114 | { |
| 115 | unsigned count = len; |
| 116 | enum twi_status expected_status; |
Alexandru Gagniuc | 5c4bde7 | 2013-12-28 21:50:54 -0500 | [diff] [blame] | 117 | |
| 118 | /* Send restart for read */ |
| 119 | i2c_send_start(twi); |
| 120 | if (wait_for_status(twi) != TWI_STAT_TX_RSTART) |
| 121 | return CB_ERR; |
| 122 | |
| 123 | /* Send chip address */ |
| 124 | i2c_send_data(twi, chip << 1 | 1); |
| 125 | if (wait_for_status(twi) != TWI_STAT_TX_AR_ACK) |
| 126 | return CB_ERR; |
| 127 | |
| 128 | /* Start ACK-ing received data */ |
| 129 | setbits_le32(&twi->ctl, TWI_CTL_A_ACK); |
| 130 | expected_status = TWI_STAT_RXD_ACK; |
| 131 | |
| 132 | /* Read data */ |
| 133 | while (count > 0) { |
| 134 | if (count == 1) { |
| 135 | /* Do not ACK the last byte */ |
| 136 | clrbits_le32(&twi->ctl, TWI_CTL_A_ACK); |
| 137 | expected_status = TWI_STAT_RXD_NAK; |
| 138 | } |
| 139 | |
| 140 | clear_interrupt_flag(twi); |
| 141 | |
| 142 | if (wait_for_status(twi) != expected_status) |
| 143 | return CB_ERR; |
| 144 | |
| 145 | *buf++ = read32(&twi->data); |
| 146 | count--; |
| 147 | } |
| 148 | |
Alexandru Gagniuc | 5c4bde7 | 2013-12-28 21:50:54 -0500 | [diff] [blame] | 149 | return len; |
| 150 | } |
| 151 | |
Alexandru Gagniuc | e5ccbfd | 2015-02-13 16:30:30 -0600 | [diff] [blame] | 152 | static int i2c_write(struct a1x_twi *twi, uint8_t chip, |
| 153 | const uint8_t *buf, size_t len) |
Alexandru Gagniuc | 5c4bde7 | 2013-12-28 21:50:54 -0500 | [diff] [blame] | 154 | { |
Alexandru Gagniuc | e5ccbfd | 2015-02-13 16:30:30 -0600 | [diff] [blame] | 155 | size_t count = len; |
Alexandru Gagniuc | 5c4bde7 | 2013-12-28 21:50:54 -0500 | [diff] [blame] | 156 | |
| 157 | i2c_send_start(twi); |
| 158 | if (wait_for_status(twi) != TWI_STAT_TX_START) |
| 159 | return CB_ERR; |
| 160 | |
| 161 | /* Send chip address */ |
| 162 | i2c_send_data(twi, chip << 1); |
| 163 | if (wait_for_status(twi) != TWI_STAT_TX_AW_ACK) |
| 164 | return CB_ERR; |
| 165 | |
Alexandru Gagniuc | 5c4bde7 | 2013-12-28 21:50:54 -0500 | [diff] [blame] | 166 | /* Send data */ |
| 167 | while (count > 0) { |
| 168 | i2c_send_data(twi, *buf++); |
| 169 | if (wait_for_status(twi) != TWI_STAT_TXD_ACK) |
| 170 | return CB_ERR; |
| 171 | count--; |
| 172 | } |
| 173 | |
Alexandru Gagniuc | 5c4bde7 | 2013-12-28 21:50:54 -0500 | [diff] [blame] | 174 | return len; |
| 175 | } |
Gabe Black | cdb61a6 | 2014-04-07 18:45:14 -0700 | [diff] [blame] | 176 | |
Julius Werner | 37d7ac8 | 2014-05-05 18:03:46 -0700 | [diff] [blame] | 177 | int platform_i2c_transfer(unsigned bus, struct i2c_seg *segments, int count) |
Gabe Black | cdb61a6 | 2014-04-07 18:45:14 -0700 | [diff] [blame] | 178 | { |
Alexandru Gagniuc | e5ccbfd | 2015-02-13 16:30:30 -0600 | [diff] [blame] | 179 | int i, ret = CB_SUCCESS; |
Gabe Black | cdb61a6 | 2014-04-07 18:45:14 -0700 | [diff] [blame] | 180 | struct i2c_seg *seg = segments; |
Alexandru Gagniuc | e5ccbfd | 2015-02-13 16:30:30 -0600 | [diff] [blame] | 181 | struct a1x_twi *twi = (void *)TWI_BASE(bus); |
Gabe Black | cdb61a6 | 2014-04-07 18:45:14 -0700 | [diff] [blame] | 182 | |
Gabe Black | cdb61a6 | 2014-04-07 18:45:14 -0700 | [diff] [blame] | 183 | |
Alexandru Gagniuc | e5ccbfd | 2015-02-13 16:30:30 -0600 | [diff] [blame] | 184 | if (wait_until_idle(twi) != CB_SUCCESS) |
| 185 | return CB_ERR; |
Gabe Black | cdb61a6 | 2014-04-07 18:45:14 -0700 | [diff] [blame] | 186 | |
Alexandru Gagniuc | e5ccbfd | 2015-02-13 16:30:30 -0600 | [diff] [blame] | 187 | for (i = 0; i < count; i++) { |
| 188 | seg = segments + i; |
| 189 | |
| 190 | if (seg->read) { |
| 191 | ret = i2c_read(twi, seg->chip, seg->buf, seg->len); |
| 192 | if (ret < 0) |
| 193 | break; |
| 194 | } else { |
| 195 | ret = i2c_write(twi, seg->chip, seg->buf, seg->len); |
| 196 | if (ret < 0) |
| 197 | break; |
| 198 | } |
Gabe Black | cdb61a6 | 2014-04-07 18:45:14 -0700 | [diff] [blame] | 199 | } |
Alexandru Gagniuc | e5ccbfd | 2015-02-13 16:30:30 -0600 | [diff] [blame] | 200 | |
| 201 | /* Don't worry about the status. STOP is on a best-effort basis */ |
| 202 | i2c_send_stop(twi); |
| 203 | |
| 204 | return ret; |
Gabe Black | cdb61a6 | 2014-04-07 18:45:14 -0700 | [diff] [blame] | 205 | } |