aboutsummaryrefslogtreecommitdiffstats
path: root/firmware/x300/lib/wb_i2c.c
blob: 611908e18fdd76d279e5f9b85e9dbea90618950e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// 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 <http://www.gnu.org/licenses/>.
 */

// 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 <wb_i2c.h>

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;
}