aboutsummaryrefslogtreecommitdiffstats
path: root/firmware/usrp3/lib/wb_spi.c
blob: 04904feea5665e0fe8b902544a1ca2cdc1d10ee9 (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
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
/*
 * Copyright 2014 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 <wb_spi.h>
#include <trace.h>

typedef struct {
  volatile uint32_t data0;
  volatile uint32_t data1;
  volatile uint32_t data2;
  volatile uint32_t data3;
  volatile uint32_t ctrl_status;
  volatile uint32_t clkdiv;
  volatile uint32_t slavesel;
} wb_spi_regs_t;

#define WB_SPI_REGS(base) ((wb_spi_regs_t *) base)

// Masks for different parts of CTRL reg
#define WB_SPI_CTRL_AUTO_SS     (1 << 13)
#define WB_SPI_CTRL_IE          (1 << 12)
#define WB_SPI_CTRL_LSB         (1 << 11)
#define WB_SPI_CTRL_TXNEG       (1 << 10)
#define WB_SPI_CTRL_RXNEG       (1 << 9)
#define WB_SPI_CTRL_GO_BSY      (1 << 8)
#define WB_SPI_CTRL_LENGTH(x)   (x & 0x7F)

static inline uint32_t _wb_spi_get_flags(const wb_spi_slave_t* slave)
{
    uint32_t flags = 0;
    //If the SPI slave samples on the rising edge then shift
    //data out on the falling edge.
    if (slave->mosi_edge == RISING) flags |= WB_SPI_CTRL_TXNEG;
    //If the SPI slave drives on the rising edge then shift
    //data in on the falling edge.
    if (slave->miso_edge == RISING) flags |= WB_SPI_CTRL_RXNEG;
    if (slave->lsb_first)           flags |= WB_SPI_CTRL_LSB;
    return flags;
}

static inline void _wait_for_xfer(const wb_spi_slave_t* slave)
{
    while (WB_SPI_REGS(slave->base)->ctrl_status & WB_SPI_CTRL_GO_BSY) {
        /*NOP*/
    }
}

void wb_spi_init(const wb_spi_slave_t* slave)
{
    WB_SPI_REGS(slave->base)->clkdiv = slave->clk_div;
    WB_SPI_REGS(slave->base)->slavesel = 0;

    //Do a dummy transaction with no slave selected to prime the engine
    uint32_t ctrl = WB_SPI_CTRL_LENGTH(8) | _wb_spi_get_flags(slave);
    WB_SPI_REGS(slave->base)->ctrl_status = ctrl | WB_SPI_CTRL_GO_BSY;
    _wait_for_xfer(slave);
}

void _wb_spi_transact_buf(
    const wb_spi_slave_t* slave, wb_spi_rw_mode_t rw_mode,
    const void* mosi_buf, void* miso_buf, uint32_t length,
    bool auto_slave_sel)
{
    if (length == 0) return;

    //Wait for previous transaction to finish
    _wait_for_xfer(slave);

    //Write SPI data register(s)
    if (mosi_buf) {
        uint8_t* mosi_bytes = (uint8_t*) mosi_buf;
        uint8_t bits_left = length;
        for (uint32_t reg_index = 0; reg_index < 4; reg_index++) {
            uint32_t word = 0;
            if (bits_left < 32) {
                if (bits_left <= 8) {
                    word = (uint32_t) mosi_bytes[0];
                } else if (bits_left <= 16) {
                    word = (((uint32_t) mosi_bytes[1]) << 0) |
                           (((uint32_t) mosi_bytes[0]) << 8);
                } else if (bits_left <= 24) {
                    word = (((uint32_t) mosi_bytes[2]) << 0) |
                           (((uint32_t) mosi_bytes[1]) << 8) |
                           (((uint32_t) mosi_bytes[0]) << 16);
                } else {
                    word = *((uint32_t*) mosi_bytes);
                }
                bits_left = 0;
            } else {
                word = *((uint32_t*) mosi_bytes);
                mosi_bytes += 4;
                bits_left -= 32;
            }

            switch (reg_index) {
                case 0: WB_SPI_REGS(slave->base)->data0 = word; break;
                case 1: WB_SPI_REGS(slave->base)->data1 = word; break;
                case 2: WB_SPI_REGS(slave->base)->data2 = word; break;
                case 3: WB_SPI_REGS(slave->base)->data3 = word; break;
            }

            if (bits_left == 0) break;
        }
    }

    //Compute flags for slave and write control register
    uint32_t ctrl = WB_SPI_CTRL_LENGTH(length) | _wb_spi_get_flags(slave);
    if (auto_slave_sel) ctrl |= WB_SPI_CTRL_AUTO_SS;
    WB_SPI_REGS(slave->base)->ctrl_status = ctrl;

    // Tell it which SPI slave device to access
    WB_SPI_REGS(slave->base)->slavesel    = slave->slave_sel;

    //Go go go!
    WB_SPI_REGS(slave->base)->ctrl_status = ctrl | WB_SPI_CTRL_GO_BSY;

    if (rw_mode == WRITE_READ) {
        //Wait for SPI read operation to complete
        _wait_for_xfer(slave);

        if (miso_buf) {
            //Read SPI data registers
            uint8_t* miso_bytes = (uint8_t*) miso_buf;
            uint8_t bits_left = length;
            for (uint32_t reg_index = 0; reg_index < 4; reg_index++) {
                uint32_t word = 0;
                switch (reg_index) {
                    case 0: word = WB_SPI_REGS(slave->base)->data0; break;
                    case 1: word = WB_SPI_REGS(slave->base)->data1; break;
                    case 2: word = WB_SPI_REGS(slave->base)->data2; break;
                    case 3: word = WB_SPI_REGS(slave->base)->data3; break;
                }

                if (bits_left < 32) {
                    if (bits_left <= 8) {
                        miso_bytes[0] = word & 0xFF;
                    } else if (bits_left <= 16) {
                        miso_bytes[1] = word & 0xFF;
                        miso_bytes[0] = (word >> 8) & 0xFF;
                    } else if (bits_left <= 24) {
                        miso_bytes[2] = word & 0xFF;
                        miso_bytes[1] = (word >> 8) & 0xFF;
                        miso_bytes[0] = (word >> 16) & 0xFF;
                    } else {
                        *((uint32_t*) miso_bytes) = word;
                    }
                    bits_left = 0;
                } else {
                    *((uint32_t*) miso_bytes) = word;
                    miso_bytes += 4;
                    bits_left -= 32;
                }

                if (bits_left == 0) break;
            }
        }
    }
}

void wb_spi_transact(
    const wb_spi_slave_t* slave, wb_spi_rw_mode_t rw_mode,
    const void* mosi_buf, void* miso_buf, uint32_t length)
{
    return _wb_spi_transact_buf(slave, rw_mode, mosi_buf, miso_buf, length, true);
}

void wb_spi_transact_man_ss(
    const wb_spi_slave_t* slave, wb_spi_rw_mode_t rw_mode,
    const void* mosi_buf, void* miso_buf, uint32_t length)
{
    return _wb_spi_transact_buf(slave, rw_mode, mosi_buf, miso_buf, length, false);
}

void wb_spi_slave_select(const wb_spi_slave_t* slave)
{
    //Wait for previous transactions to finish
    _wait_for_xfer(slave);
    //Disable auto slave select
    WB_SPI_REGS(slave->base)->ctrl_status = _wb_spi_get_flags(slave);
    //Manually select slave
    WB_SPI_REGS(slave->base)->slavesel    = slave->slave_sel;
}

void wb_spi_slave_deselect(const wb_spi_slave_t* slave)
{
    //Wait for previous transactions to finish
    _wait_for_xfer(slave);
    //Disable auto slave select
    WB_SPI_REGS(slave->base)->ctrl_status = _wb_spi_get_flags(slave);
    //Manually deselect slave
    WB_SPI_REGS(slave->base)->slavesel    = 0;
}