aboutsummaryrefslogtreecommitdiffstats
path: root/host/lib/usrp/cores/i2c_core_200.cpp
blob: 7b28573e9e1df0115e8297ff34bfd7bed52750ab (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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
//
// Copyright 2011-2012,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_200.hpp"
#include <uhd/exception.hpp>
#include <uhd/utils/log.hpp>
#include <boost/thread/thread.hpp> //sleep
#include <boost/thread/mutex.hpp>

#define REG_I2C_WR_PRESCALER_LO (1 << 3) | 0
#define REG_I2C_WR_PRESCALER_HI (1 << 3) | 1
#define REG_I2C_WR_CTRL         (1 << 3) | 2
#define REG_I2C_WR_DATA         (1 << 3) | 3
#define REG_I2C_WR_CMD          (1 << 3) | 4
#define REG_I2C_RD_DATA         (0 << 3) | 3
#define REG_I2C_RD_ST           (0 << 3) | 4

//
// 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_200::~i2c_core_200(void){
    /* NOP */
}

class i2c_core_200_impl : public i2c_core_200{
public:
    i2c_core_200_impl(wb_iface::sptr iface, const size_t base, const size_t readback):
        _iface(iface), _base(base), _readback(readback)
    {
        //init I2C FPGA interface.
        this->poke(REG_I2C_WR_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;
        this->poke(REG_I2C_WR_PRESCALER_LO, prescaler & 0xFF);
        this->poke(REG_I2C_WR_PRESCALER_HI, (prescaler >> 8) & 0xFF);
        this->poke(REG_I2C_WR_CTRL, I2C_CTRL_EN); //enable I2C core
    }

    void write_i2c(
        uint16_t addr,
        const byte_vector_t &bytes
    ){
        this->poke(REG_I2C_WR_DATA, (addr << 1) | 0); //addr and read bit (0)
        this->poke(REG_I2C_WR_CMD, I2C_CMD_WR | I2C_CMD_START | (bytes.size() == 0 ? I2C_CMD_STOP : 0));

        //wait for previous transfer to complete
        if (not wait_chk_ack()) {
            this->poke(REG_I2C_WR_CMD, I2C_CMD_STOP);
            return;
        }

        for (size_t i = 0; i < bytes.size(); i++) {
            this->poke(REG_I2C_WR_DATA, bytes[i]);
            this->poke(REG_I2C_WR_CMD, I2C_CMD_WR | ((i == (bytes.size() - 1)) ? I2C_CMD_STOP : 0));
            if(!wait_chk_ack()) {
                this->poke(REG_I2C_WR_CMD, 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 (this->peek(REG_I2C_RD_ST) & I2C_ST_BUSY){
            /* NOP */
        }

        this->poke(REG_I2C_WR_DATA, (addr << 1) | 1); //addr and read bit (1)
        this->poke(REG_I2C_WR_CMD, I2C_CMD_WR | I2C_CMD_START);
        //wait for previous transfer to complete
        if (not wait_chk_ack()) {
            this->poke(REG_I2C_WR_CMD, I2C_CMD_STOP);
        }
        for (size_t i = 0; i < num_bytes; i++) {
            this->poke(REG_I2C_WR_CMD, I2C_CMD_RD | ((num_bytes == i+1) ? (I2C_CMD_STOP | I2C_CMD_NACK) : 0));
            i2c_wait();
            bytes.push_back(this->peek(REG_I2C_RD_DATA));
        }
        return bytes;
    }

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

    bool wait_chk_ack(void){
        i2c_wait();
        return (this->peek(REG_I2C_RD_ST) & I2C_ST_RXACK) == 0;
    }

    void poke(const size_t what, const uint8_t cmd)
    {
        boost::mutex::scoped_lock lock(_mutex);
        _iface->poke32(_base, (what << 8) | cmd);
    }

    uint8_t peek(const size_t what)
    {
        boost::mutex::scoped_lock lock(_mutex);
        _iface->poke32(_base, what << 8);
        return uint8_t(_iface->peek32(_readback));
    }

    wb_iface::sptr _iface;
    const size_t _base;
    const size_t _readback;
    boost::mutex _mutex;
};

i2c_core_200::sptr i2c_core_200::make(wb_iface::sptr iface, const size_t base, const size_t readback){
    return sptr(new i2c_core_200_impl(iface, base, readback));
}