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