aboutsummaryrefslogtreecommitdiffstats
path: root/host/lib/usrp/cores/i2c_core_100.cpp
blob: e68dc2ea635c9a6dcb902e86e21d23b2ac066598 (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
138
139
140
141
142
143
144
//
// Copyright 2011,2014 Ettus Research LLC
//
// 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_core_100.hpp"
#include <uhd/exception.hpp>
#include <uhd/utils/log.hpp>
#include <boost/thread/thread.hpp> //sleep

#define REG_I2C_PRESCALER_LO _base + 0
#define REG_I2C_PRESCALER_HI _base + 2
#define REG_I2C_CTRL         _base + 4
#define REG_I2C_DATA         _base + 6
#define REG_I2C_CMD_STATUS   _base + 8

//
// STA, STO, RD, WR, and IACK bits are cleared automatically
//

#define	I2C_CTRL_EN	(1 << 7)	// core enable
#define	I2C_CTRL_IE	(1 << 6)	// interrupt enable

#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

using namespace uhd;

i2c_core_100::~i2c_core_100(void){
    /* NOP */
}

class i2c_core_100_impl : public i2c_core_100{
public:
    i2c_core_100_impl(wb_iface::sptr iface, const size_t base):
        _iface(iface), _base(base)
    {
        //init I2C FPGA interface.
        _iface->poke16(REG_I2C_CTRL, 0x0000);
        //set prescalers to operate at 400kHz: WB_CLK is 64MHz...
        static const uint32_t i2c_datarate = 400000;
        static const uint32_t wishbone_clk = 64000000; //FIXME should go somewhere else
        uint16_t prescaler = wishbone_clk / (i2c_datarate*5) - 1;
        _iface->poke16(REG_I2C_PRESCALER_LO, prescaler & 0xFF);
        _iface->poke16(REG_I2C_PRESCALER_HI, (prescaler >> 8) & 0xFF);
        _iface->poke16(REG_I2C_CTRL, I2C_CTRL_EN); //enable I2C core
    }

    void write_i2c(
        uint16_t addr,
        const byte_vector_t &bytes
    ){
        _iface->poke16(REG_I2C_DATA, (addr << 1) | 0); //addr and read bit (0)
        _iface->poke16(REG_I2C_CMD_STATUS, I2C_CMD_WR | I2C_CMD_START | (bytes.size() == 0 ? I2C_CMD_STOP : 0));

        //wait for previous transfer to complete
        if (not wait_chk_ack()) {
            _iface->poke16(REG_I2C_CMD_STATUS, I2C_CMD_STOP);
            return;
        }

        for (size_t i = 0; i < bytes.size(); i++) {
            _iface->poke16(REG_I2C_DATA, bytes[i]);
            _iface->poke16(REG_I2C_CMD_STATUS, I2C_CMD_WR | ((i == (bytes.size() - 1)) ? I2C_CMD_STOP : 0));
            if(!wait_chk_ack()) {
                _iface->poke16(REG_I2C_CMD_STATUS, I2C_CMD_STOP);
                return;
            }
        }
    }

    byte_vector_t read_i2c(
        uint16_t addr,
        size_t num_bytes
    ){
        byte_vector_t bytes;
        if (num_bytes == 0) return bytes;

        while (_iface->peek16(REG_I2C_CMD_STATUS) & I2C_ST_BUSY){
            /* NOP */
        }

        _iface->poke16(REG_I2C_DATA, (addr << 1) | 1); //addr and read bit (1)
        _iface->poke16(REG_I2C_CMD_STATUS, I2C_CMD_WR | I2C_CMD_START);
        //wait for previous transfer to complete
        if (not wait_chk_ack()) {
            _iface->poke16(REG_I2C_CMD_STATUS, I2C_CMD_STOP);
        }
        for (size_t i = 0; i < num_bytes; i++) {
            _iface->poke16(REG_I2C_CMD_STATUS, I2C_CMD_RD | ((num_bytes == i+1) ? (I2C_CMD_STOP | I2C_CMD_NACK) : 0));
            i2c_wait();
            bytes.push_back(uint8_t(_iface->peek16(REG_I2C_DATA)));
        }
        return bytes;
    }

private:
    void i2c_wait(void) {
        for (size_t i = 0; i < 100; i++){
            if ((_iface->peek16(REG_I2C_CMD_STATUS) & I2C_ST_TIP) == 0) return;
            boost::this_thread::sleep(boost::posix_time::milliseconds(1));
        }
        UHD_LOGGER_ERROR("CORES") << "i2c_core_100: i2c_wait timeout" ;
    }

    bool wait_chk_ack(void){
        i2c_wait();
        return (_iface->peek16(REG_I2C_CMD_STATUS) & I2C_ST_RXACK) == 0;
    }

    wb_iface::sptr _iface;
    const size_t _base;
};

i2c_core_100::sptr i2c_core_100::make(wb_iface::sptr iface, const size_t base){
    return sptr(new i2c_core_100_impl(iface, base));
}