| /* |
| * i2c.c - driver for ADI TWI/I2C |
| * |
| * Copyright (c) 2006-2014 Analog Devices Inc. |
| * |
| * Licensed under the GPL-2 or later. |
| */ |
| |
| #include <common.h> |
| #include <i2c.h> |
| |
| #include <asm/clock.h> |
| #include <asm/twi.h> |
| #include <asm/io.h> |
| |
| static struct twi_regs *i2c_get_base(struct i2c_adapter *adap); |
| |
| /* Every register is 32bit aligned, but only 16bits in size */ |
| #define ureg(name) u16 name; u16 __pad_##name; |
| struct twi_regs { |
| ureg(clkdiv); |
| ureg(control); |
| ureg(slave_ctl); |
| ureg(slave_stat); |
| ureg(slave_addr); |
| ureg(master_ctl); |
| ureg(master_stat); |
| ureg(master_addr); |
| ureg(int_stat); |
| ureg(int_mask); |
| ureg(fifo_ctl); |
| ureg(fifo_stat); |
| char __pad[0x50]; |
| ureg(xmt_data8); |
| ureg(xmt_data16); |
| ureg(rcv_data8); |
| ureg(rcv_data16); |
| }; |
| #undef ureg |
| |
| #ifdef TWI_CLKDIV |
| #define TWI0_CLKDIV TWI_CLKDIV |
| # ifdef CONFIG_SYS_MAX_I2C_BUS |
| # undef CONFIG_SYS_MAX_I2C_BUS |
| # endif |
| #define CONFIG_SYS_MAX_I2C_BUS 1 |
| #endif |
| |
| /* |
| * The way speed is changed into duty often results in integer truncation |
| * with 50% duty, so we'll force rounding up to the next duty by adding 1 |
| * to the max. In practice this will get us a speed of something like |
| * 385 KHz. The other limit is easy to handle as it is only 8 bits. |
| */ |
| #define I2C_SPEED_MAX 400000 |
| #define I2C_SPEED_TO_DUTY(speed) (5000000 / (speed)) |
| #define I2C_DUTY_MAX (I2C_SPEED_TO_DUTY(I2C_SPEED_MAX) + 1) |
| #define I2C_DUTY_MIN 0xff /* 8 bit limited */ |
| #define SYS_I2C_DUTY I2C_SPEED_TO_DUTY(CONFIG_SYS_I2C_SPEED) |
| /* Note: duty is inverse of speed, so the comparisons below are correct */ |
| #if SYS_I2C_DUTY < I2C_DUTY_MAX || SYS_I2C_DUTY > I2C_DUTY_MIN |
| # error "The I2C hardware can only operate 20KHz - 400KHz" |
| #endif |
| |
| /* All transfers are described by this data structure */ |
| struct i2c_msg { |
| u8 flags; |
| #define I2C_M_COMBO 0x4 |
| #define I2C_M_STOP 0x2 |
| #define I2C_M_READ 0x1 |
| int len; /* msg length */ |
| u8 *buf; /* pointer to msg data */ |
| int alen; /* addr length */ |
| u8 *abuf; /* addr buffer */ |
| }; |
| |
| /* Allow msec timeout per ~byte transfer */ |
| #define I2C_TIMEOUT 10 |
| |
| /** |
| * wait_for_completion - manage the actual i2c transfer |
| * @msg: the i2c msg |
| */ |
| static int wait_for_completion(struct twi_regs *twi, struct i2c_msg *msg) |
| { |
| u16 int_stat, ctl; |
| ulong timebase = get_timer(0); |
| |
| do { |
| int_stat = readw(&twi->int_stat); |
| |
| if (int_stat & XMTSERV) { |
| writew(XMTSERV, &twi->int_stat); |
| if (msg->alen) { |
| writew(*(msg->abuf++), &twi->xmt_data8); |
| --msg->alen; |
| } else if (!(msg->flags & I2C_M_COMBO) && msg->len) { |
| writew(*(msg->buf++), &twi->xmt_data8); |
| --msg->len; |
| } else { |
| ctl = readw(&twi->master_ctl); |
| if (msg->flags & I2C_M_COMBO) |
| writew(ctl | RSTART | MDIR, |
| &twi->master_ctl); |
| else |
| writew(ctl | STOP, &twi->master_ctl); |
| } |
| } |
| if (int_stat & RCVSERV) { |
| writew(RCVSERV, &twi->int_stat); |
| if (msg->len) { |
| *(msg->buf++) = readw(&twi->rcv_data8); |
| --msg->len; |
| } else if (msg->flags & I2C_M_STOP) { |
| ctl = readw(&twi->master_ctl); |
| writew(ctl | STOP, &twi->master_ctl); |
| } |
| } |
| if (int_stat & MERR) { |
| writew(MERR, &twi->int_stat); |
| return msg->len; |
| } |
| if (int_stat & MCOMP) { |
| writew(MCOMP, &twi->int_stat); |
| if (msg->flags & I2C_M_COMBO && msg->len) { |
| ctl = readw(&twi->master_ctl); |
| ctl = (ctl & ~RSTART) | |
| (min(msg->len, 0xff) << 6) | MEN | MDIR; |
| writew(ctl, &twi->master_ctl); |
| } else |
| break; |
| } |
| |
| /* If we were able to do something, reset timeout */ |
| if (int_stat) |
| timebase = get_timer(0); |
| |
| } while (get_timer(timebase) < I2C_TIMEOUT); |
| |
| return msg->len; |
| } |
| |
| static int i2c_transfer(struct i2c_adapter *adap, uint8_t chip, uint addr, |
| int alen, uint8_t *buffer, int len, uint8_t flags) |
| { |
| struct twi_regs *twi = i2c_get_base(adap); |
| int ret; |
| u16 ctl; |
| uchar addr_buffer[] = { |
| (addr >> 0), |
| (addr >> 8), |
| (addr >> 16), |
| }; |
| struct i2c_msg msg = { |
| .flags = flags | (len >= 0xff ? I2C_M_STOP : 0), |
| .buf = buffer, |
| .len = len, |
| .abuf = addr_buffer, |
| .alen = alen, |
| }; |
| |
| /* wait for things to settle */ |
| while (readw(&twi->master_stat) & BUSBUSY) |
| if (ctrlc()) |
| return 1; |
| |
| /* Set Transmit device address */ |
| writew(chip, &twi->master_addr); |
| |
| /* Clear the FIFO before starting things */ |
| writew(XMTFLUSH | RCVFLUSH, &twi->fifo_ctl); |
| writew(0, &twi->fifo_ctl); |
| |
| /* prime the pump */ |
| if (msg.alen) { |
| len = (msg.flags & I2C_M_COMBO) ? msg.alen : msg.alen + len; |
| writew(*(msg.abuf++), &twi->xmt_data8); |
| --msg.alen; |
| } else if (!(msg.flags & I2C_M_READ) && msg.len) { |
| writew(*(msg.buf++), &twi->xmt_data8); |
| --msg.len; |
| } |
| |
| /* clear int stat */ |
| writew(-1, &twi->master_stat); |
| writew(-1, &twi->int_stat); |
| writew(0, &twi->int_mask); |
| |
| /* Master enable */ |
| ctl = readw(&twi->master_ctl); |
| ctl = (ctl & FAST) | (min(len, 0xff) << 6) | MEN | |
| ((msg.flags & I2C_M_READ) ? MDIR : 0); |
| writew(ctl, &twi->master_ctl); |
| |
| /* process the rest */ |
| ret = wait_for_completion(twi, &msg); |
| |
| if (ret) { |
| ctl = readw(&twi->master_ctl) & ~MEN; |
| writew(ctl, &twi->master_ctl); |
| ctl = readw(&twi->control) & ~TWI_ENA; |
| writew(ctl, &twi->control); |
| ctl = readw(&twi->control) | TWI_ENA; |
| writew(ctl, &twi->control); |
| } |
| |
| return ret; |
| } |
| |
| static uint adi_i2c_setspeed(struct i2c_adapter *adap, uint speed) |
| { |
| struct twi_regs *twi = i2c_get_base(adap); |
| u16 clkdiv = I2C_SPEED_TO_DUTY(speed); |
| |
| /* Set TWI interface clock */ |
| if (clkdiv < I2C_DUTY_MAX || clkdiv > I2C_DUTY_MIN) |
| return -1; |
| clkdiv = (clkdiv << 8) | (clkdiv & 0xff); |
| writew(clkdiv, &twi->clkdiv); |
| |
| /* Don't turn it on */ |
| writew(speed > 100000 ? FAST : 0, &twi->master_ctl); |
| |
| return 0; |
| } |
| |
| static void adi_i2c_init(struct i2c_adapter *adap, int speed, int slaveaddr) |
| { |
| struct twi_regs *twi = i2c_get_base(adap); |
| u16 prescale = ((get_i2c_clk() / 1000 / 1000 + 5) / 10) & 0x7F; |
| |
| /* Set TWI internal clock as 10MHz */ |
| writew(prescale, &twi->control); |
| |
| /* Set TWI interface clock as specified */ |
| i2c_set_bus_speed(speed); |
| |
| /* Enable it */ |
| writew(TWI_ENA | prescale, &twi->control); |
| } |
| |
| static int adi_i2c_read(struct i2c_adapter *adap, uint8_t chip, |
| uint addr, int alen, uint8_t *buffer, int len) |
| { |
| return i2c_transfer(adap, chip, addr, alen, buffer, |
| len, alen ? I2C_M_COMBO : I2C_M_READ); |
| } |
| |
| static int adi_i2c_write(struct i2c_adapter *adap, uint8_t chip, |
| uint addr, int alen, uint8_t *buffer, int len) |
| { |
| return i2c_transfer(adap, chip, addr, alen, buffer, len, 0); |
| } |
| |
| static int adi_i2c_probe(struct i2c_adapter *adap, uint8_t chip) |
| { |
| u8 byte; |
| return adi_i2c_read(adap, chip, 0, 0, &byte, 1); |
| } |
| |
| static struct twi_regs *i2c_get_base(struct i2c_adapter *adap) |
| { |
| switch (adap->hwadapnr) { |
| #if CONFIG_SYS_MAX_I2C_BUS > 2 |
| case 2: |
| return (struct twi_regs *)TWI2_CLKDIV; |
| #endif |
| #if CONFIG_SYS_MAX_I2C_BUS > 1 |
| case 1: |
| return (struct twi_regs *)TWI1_CLKDIV; |
| #endif |
| case 0: |
| return (struct twi_regs *)TWI0_CLKDIV; |
| |
| default: |
| printf("wrong hwadapnr: %d\n", adap->hwadapnr); |
| } |
| |
| return NULL; |
| } |
| |
| U_BOOT_I2C_ADAP_COMPLETE(adi_i2c0, adi_i2c_init, adi_i2c_probe, |
| adi_i2c_read, adi_i2c_write, |
| adi_i2c_setspeed, |
| CONFIG_SYS_I2C_SPEED, |
| 0, |
| 0) |
| |
| #if CONFIG_SYS_MAX_I2C_BUS > 1 |
| U_BOOT_I2C_ADAP_COMPLETE(adi_i2c1, adi_i2c_init, adi_i2c_probe, |
| adi_i2c_read, adi_i2c_write, |
| adi_i2c_setspeed, |
| CONFIG_SYS_I2C_SPEED, |
| 0, |
| 1) |
| #endif |
| |
| #if CONFIG_SYS_MAX_I2C_BUS > 2 |
| U_BOOT_I2C_ADAP_COMPLETE(adi_i2c2, adi_i2c_init, adi_i2c_probe, |
| adi_i2c_read, adi_i2c_write, |
| adi_i2c_setspeed, |
| CONFIG_SYS_I2C_SPEED, |
| 0, |
| 2) |
| #endif |