/*
* 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 .
*/
#include
#include
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;
}