diff options
Diffstat (limited to 'firmware/zpu/lib/i2c.c')
| -rw-r--r-- | firmware/zpu/lib/i2c.c | 129 | 
1 files changed, 129 insertions, 0 deletions
diff --git a/firmware/zpu/lib/i2c.c b/firmware/zpu/lib/i2c.c new file mode 100644 index 000000000..d230f462c --- /dev/null +++ b/firmware/zpu/lib/i2c.c @@ -0,0 +1,129 @@ +/* -*- c -*- */ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#include "i2c.h" +#include "memory_map.h" +#include "stdint.h" +#include <string.h> +#include "nonstdio.h" + +#define MAX_WB_DIV 4	// maximum wishbone divisor (from 100 MHz MASTER_CLK) + +// prescaler divisor values for 100 kHz I2C [uses 5 * SCLK internally] + +#define PRESCALER(wb_div) (((MASTER_CLK_RATE/(wb_div)) / (5 * 400000)) - 1) + +static uint16_t prescaler_values[MAX_WB_DIV+1] = { +  0xffff,	// 0: can't happen +  PRESCALER(1),	// 1: 100 MHz +  PRESCALER(2), // 2:  50 MHz +  PRESCALER(3), // 3:  33.333 MHz +  PRESCALER(4), // 4:  25 MHz +}; + +void +i2c_init(void) +{ +  i2c_regs->ctrl = 0;		// disable core +   +  // setup prescaler depending on wishbone divisor +  int wb_div = hwconfig_wishbone_divisor(); +  if (wb_div > MAX_WB_DIV) +    wb_div = MAX_WB_DIV; + +  i2c_regs->prescaler_lo = prescaler_values[wb_div] & 0xff; +  i2c_regs->prescaler_hi = (prescaler_values[wb_div] >> 8) & 0xff; + +  i2c_regs->ctrl = I2C_CTRL_EN; //| I2C_CTRL_IE;	// enable core + +  //now this is done separately to maintain common code for async and sync +  //pic_register_handler(IRQ_I2C, i2c_irq_handler); +} + +static inline void +wait_for_xfer(void) +{ +  while (i2c_regs->cmd_status & I2C_ST_TIP)	// wait for xfer to complete +    ; +} + +static inline bool +wait_chk_ack(void) +{ +  wait_for_xfer(); + +  if ((i2c_regs->cmd_status & I2C_ST_RXACK) != 0){	// target NAK'd +    return false; +  } +  return true; +} + +bool  +i2c_read (unsigned char i2c_addr, unsigned char *buf, unsigned int 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()) +    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(); +    *buf = i2c_regs->data; +  } +  return true; + + fail: +  i2c_regs->cmd_status = I2C_CMD_STOP;  // generate STOP +  return false; +} + + +bool  +i2c_write(unsigned char i2c_addr, const unsigned char *buf, unsigned int 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()) +    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()) +      goto fail; +  } +  return true; + + fail: +  i2c_regs->cmd_status = I2C_CMD_STOP;  // generate STOP +  return false; +} +  | 
