// Copyright 2012 Ettus Research LLC
/*
* Copyright 2007 Free Software Foundation, Inc.
*
* 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 3 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, see .
*/
// NOTE: this driver left shifts a "7bit" I2C address by one bit and puts a R/W bit as bit 0.
// Some devices may specify there I2C address assuming this R/W bit is already in place.
#include
typedef struct {
volatile uint32_t prescaler_lo; // r/w
volatile uint32_t prescaler_hi; // r/w
volatile uint32_t ctrl; // r/w
volatile uint32_t data; // wr = transmit reg; rd = receive reg
volatile uint32_t cmd_status; // wr = command reg; rd = status reg
} i2c_regs_t;
#define i2c_regs ((i2c_regs_t *) base)
#define I2C_CTRL_EN (1 << 7) // core enable
#define I2C_CTRL_IE (1 << 6) // interrupt enable
//
// STA, STO, RD, WR, and IACK bits are cleared automatically
//
#define I2C_CMD_START (1 << 7) // generate (repeated) start condition
#define I2C_CMD_STOP (1 << 6) // generate stop condition
#define I2C_CMD_RD (1 << 5) // read from slave
#define I2C_CMD_WR (1 << 4) // write to slave
#define I2C_CMD_NACK (1 << 3) // when a rcvr, send ACK (ACK=0) or NACK (ACK=1)
#define I2C_CMD_RSVD_2 (1 << 2) // reserved
#define I2C_CMD_RSVD_1 (1 << 1) // reserved
#define I2C_CMD_IACK (1 << 0) // set to clear pending interrupt
#define I2C_ST_RXACK (1 << 7) // Received acknowledgement from slave (1 = NAK, 0 = ACK)
#define I2C_ST_BUSY (1 << 6) // 1 after START signal detected; 0 after STOP signal detected
#define I2C_ST_AL (1 << 5) // Arbitration lost. 1 when core lost arbitration
#define I2C_ST_RSVD_4 (1 << 4) // reserved
#define I2C_ST_RSVD_3 (1 << 3) // reserved
#define I2C_ST_RSVD_2 (1 << 2) // reserved
#define I2C_ST_TIP (1 << 1) // Transfer-in-progress
#define I2C_ST_IP (1 << 0) // Interrupt pending
void wb_i2c_init(const uint32_t base, const size_t clk_rate)
{
// prescaler divisor values for 100 kHz I2C [uses 5 * SCLK internally]
const uint16_t prescaler = (clk_rate/(5 * 400000)) - 1;
i2c_regs->prescaler_lo = prescaler & 0xff;
i2c_regs->prescaler_hi = (prescaler >> 8) & 0xff;
i2c_regs->ctrl = I2C_CTRL_EN; //| I2C_CTRL_IE; // enable core
}
static inline void
wait_for_xfer(const uint32_t base)
{
while (i2c_regs->cmd_status & I2C_ST_TIP) // wait for xfer to complete
;
}
static inline bool
wait_chk_ack(const uint32_t base)
{
wait_for_xfer(base);
if ((i2c_regs->cmd_status & I2C_ST_RXACK) != 0){ // target NAK'd
return false;
}
return true;
}
bool wb_i2c_read(const uint32_t base, const uint8_t i2c_addr, uint8_t *buf, size_t len)
{
if (len == 0) // reading zero bytes always works
return true;
while (i2c_regs->cmd_status & I2C_ST_BUSY)
;
i2c_regs->data = (i2c_addr << 1) | 1; // 7 bit address and read bit (1)
// generate START and write addr
i2c_regs->cmd_status = I2C_CMD_WR | I2C_CMD_START;
if (!wait_chk_ack(base))
goto fail;
for (; len > 0; buf++, len--){
i2c_regs->cmd_status = I2C_CMD_RD | (len == 1 ? (I2C_CMD_NACK | I2C_CMD_STOP) : 0);
wait_for_xfer(base);
*buf = i2c_regs->data;
}
return true;
fail:
i2c_regs->cmd_status = I2C_CMD_STOP; // generate STOP
return false;
}
bool wb_i2c_write(const uint32_t base, const uint8_t i2c_addr, const uint8_t *buf, size_t len)
{
while (i2c_regs->cmd_status & I2C_ST_BUSY)
;
i2c_regs->data = (i2c_addr << 1) | 0; // 7 bit address and write bit (0)
// generate START and write addr (and maybe STOP)
i2c_regs->cmd_status = I2C_CMD_WR | I2C_CMD_START | (len == 0 ? I2C_CMD_STOP : 0);
if (!wait_chk_ack(base))
goto fail;
for (; len > 0; buf++, len--){
i2c_regs->data = *buf;
i2c_regs->cmd_status = I2C_CMD_WR | (len == 1 ? I2C_CMD_STOP : 0);
if (!wait_chk_ack(base))
goto fail;
}
return true;
fail:
i2c_regs->cmd_status = I2C_CMD_STOP; // generate STOP
return false;
}