0faac26dde
The spi master cur_transfer and cur_msg should only be changed under the spin_lock for the master. The result is that if running user space tools, like fxstest, that check registers on the modules, it's possible to have a message that was not yet complete flagged as completed which would result in a bad read. This does not affect "normal" operation of the wcaxx driver since interrupts are not enabled during module detection, and during normal operation all access to the resgisters is done in the context of the interrupt handler. This would only be an issue if the interrupt handler was running and register accesses are tried in user space context on an SMP system. Signed-off-by: Shaun Ruffell <sruffell@digium.com>
387 lines
10 KiB
C
387 lines
10 KiB
C
/*
|
|
* wcxb SPI library
|
|
*
|
|
* Copyright (C) 2013 Digium, Inc.
|
|
*
|
|
* All rights reserved.
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* See http://www.asterisk.org for more information about
|
|
* the Asterisk project. Please do not directly contact
|
|
* any of the maintainers of this project for assistance;
|
|
* the project provides a web site, mailing lists and IRC
|
|
* channels for your use.
|
|
*
|
|
* This program is free software, distributed under the terms of
|
|
* the GNU General Public License Version 2 as published by the
|
|
* Free Software Foundation. See the LICENSE file included with
|
|
* this program for more details.
|
|
*/
|
|
|
|
#define DEBUG
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/list.h>
|
|
#include <linux/device.h>
|
|
#include <linux/completion.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/delay.h>
|
|
|
|
#include <linux/io.h>
|
|
|
|
#include <dahdi/kernel.h>
|
|
|
|
#include "wcxb_spi.h"
|
|
|
|
#define BBIT(n) (1UL << (31UL - (n)))
|
|
|
|
#define SPISRR 0x40
|
|
#define SPISRR_RESET 0x0000000a /* Resets Device */
|
|
|
|
#define SPICR 0x60
|
|
#define SPICR_LSB_FIRST BBIT(22) /* LSB First. 0=MSB first transfer */
|
|
#define SPICR_MASTER_INHIBIT BBIT(23) /* Master Transaction Inhibit */
|
|
#define SPICR_SLAVE_SELECT BBIT(24) /* Manual Slave Select Assert Enable */
|
|
#define SPICR_RX_FIFO_RESET BBIT(25) /* Receive FIFO Reset */
|
|
#define SPICR_TX_FIFO_RESET BBIT(26) /* Transmit FIFO Reset */
|
|
#define SPICR_CPHA BBIT(27) /* Clock Phase */
|
|
#define SPICR_CPOL BBIT(28) /* Clock Polarity 0=Active High */
|
|
#define SPICR_MASTER BBIT(29) /* Master Enable */
|
|
#define SPICR_SPE BBIT(30) /* SPI System Enable */
|
|
#define SPICR_LOOP BBIT(31) /* Local Loopback Mode */
|
|
|
|
#define SPICR_START_TRANSFER (SPICR_CPHA | SPICR_CPOL | \
|
|
SPICR_MASTER | SPICR_SPE)
|
|
#define SPICR_READY_TRANSFER (SPICR_MASTER_INHIBIT | SPICR_START_TRANSFER)
|
|
|
|
#define SPISR 0x64 /* SPI Status Register */
|
|
#define SPISR_SLAVE_MODE_SEL BBIT(26) /* Slave Mode Select Flag */
|
|
#define SPISR_MODF BBIT(27) /* Mode-Fault Error Flag */
|
|
#define SPISR_TX_FULL BBIT(28) /* Transmit FIFO Full */
|
|
#define SPISR_TX_EMPTY BBIT(29) /* Transmit FIFO Empty */
|
|
#define SPISR_RX_FULL BBIT(30) /* Receive FIFO Full */
|
|
#define SPISR_RX_EMPTY BBIT(31) /* Receive FIFO Empty */
|
|
|
|
#define SPIDTR 0x68 /* SPI Data Transmit Register */
|
|
#define SPIDRR 0x6c /* SPI Data Receive Register */
|
|
|
|
#define SPISSR 0x70 /* SPI Slave Select Register */
|
|
|
|
#undef SUCCESS
|
|
#define SUCCESS 0
|
|
|
|
struct wcxb_spi_master {
|
|
struct device *parent;
|
|
struct list_head message_queue;
|
|
spinlock_t lock;
|
|
void __iomem *base;
|
|
struct wcxb_spi_message *cur_msg;
|
|
struct wcxb_spi_transfer *cur_transfer;
|
|
u16 bytes_left;
|
|
u16 bytes_in_fifo;
|
|
const u8 *cur_tx_byte;
|
|
u8 *cur_rx_byte;
|
|
u16 auto_cs:1;
|
|
};
|
|
|
|
static inline void _wcxb_assert_chip_select(struct wcxb_spi_master *master,
|
|
unsigned int cs)
|
|
{
|
|
const int cs_mask = ~(1UL << cs);
|
|
iowrite32be(cs_mask, master->base + SPISSR);
|
|
ioread32be(master->base + SPISSR);
|
|
}
|
|
|
|
static inline void _wcxb_clear_chip_select(struct wcxb_spi_master *master)
|
|
{
|
|
iowrite32be(~(0), master->base + SPISSR);
|
|
ioread32(master->base + SPISSR);
|
|
}
|
|
|
|
static inline void wcxb_spi_reset_controller(struct wcxb_spi_master *master)
|
|
{
|
|
u32 spicr = SPICR_READY_TRANSFER;
|
|
spicr |= (master->auto_cs) ? 0 : SPICR_SLAVE_SELECT;
|
|
iowrite32be(SPISRR_RESET, master->base + SPISRR);
|
|
iowrite32be(spicr, master->base + SPICR);
|
|
iowrite32be(0xffffffff, master->base + SPISSR);
|
|
}
|
|
|
|
struct wcxb_spi_master *wcxb_spi_master_create(struct device *parent,
|
|
void __iomem *membase,
|
|
bool auto_cs)
|
|
{
|
|
struct wcxb_spi_master *master = NULL;
|
|
master = kzalloc(sizeof(struct wcxb_spi_master), GFP_KERNEL);
|
|
if (!master)
|
|
goto error_exit;
|
|
|
|
spin_lock_init(&master->lock);
|
|
INIT_LIST_HEAD(&master->message_queue);
|
|
master->base = membase;
|
|
master->parent = parent;
|
|
|
|
master->auto_cs = (auto_cs) ? 1 : 0;
|
|
wcxb_spi_reset_controller(master);
|
|
return master;
|
|
|
|
error_exit:
|
|
kfree(master);
|
|
return NULL;
|
|
}
|
|
|
|
void wcxb_spi_master_destroy(struct wcxb_spi_master *master)
|
|
{
|
|
struct wcxb_spi_message *m;
|
|
if (!master)
|
|
return;
|
|
while (!list_empty(&master->message_queue)) {
|
|
m = list_first_entry(&master->message_queue,
|
|
struct wcxb_spi_message, node);
|
|
list_del(&m->node);
|
|
if (m->complete)
|
|
m->complete(m->arg);
|
|
}
|
|
kfree(master);
|
|
return;
|
|
}
|
|
|
|
static inline bool is_txfifo_empty(const struct wcxb_spi_master *master)
|
|
{
|
|
return ((ioread32(master->base + SPISR) &
|
|
cpu_to_be32(SPISR_TX_EMPTY)) > 0);
|
|
}
|
|
|
|
static const u8 DUMMY_TX = 0xff;
|
|
static u8 DUMMY_RX;
|
|
|
|
static void _wcxb_spi_transfer_to_fifo(struct wcxb_spi_master *master)
|
|
{
|
|
const unsigned int FIFO_SIZE = 16;
|
|
u32 spicr;
|
|
while (master->bytes_left && master->bytes_in_fifo < FIFO_SIZE) {
|
|
iowrite32be(*master->cur_tx_byte, master->base + SPIDTR);
|
|
master->bytes_in_fifo++;
|
|
master->bytes_left--;
|
|
if (&DUMMY_TX != master->cur_tx_byte)
|
|
master->cur_tx_byte++;
|
|
}
|
|
|
|
spicr = (master->auto_cs) ? SPICR_START_TRANSFER :
|
|
SPICR_START_TRANSFER | SPICR_SLAVE_SELECT;
|
|
iowrite32be(spicr, master->base + SPICR);
|
|
}
|
|
|
|
static void _wcxb_spi_transfer_from_fifo(struct wcxb_spi_master *master)
|
|
{
|
|
u32 spicr;
|
|
while (master->bytes_in_fifo) {
|
|
*master->cur_rx_byte = ioread32be(master->base + SPIDRR);
|
|
if (&DUMMY_RX != master->cur_rx_byte)
|
|
master->cur_rx_byte++;
|
|
--master->bytes_in_fifo;
|
|
}
|
|
|
|
spicr = SPICR_START_TRANSFER;
|
|
spicr |= (master->auto_cs) ? 0 : SPICR_SLAVE_SELECT;
|
|
iowrite32be(spicr | SPICR_MASTER_INHIBIT, master->base + SPICR);
|
|
}
|
|
|
|
static void _wcxb_spi_start_transfer(struct wcxb_spi_master *master,
|
|
struct wcxb_spi_transfer *t)
|
|
{
|
|
#ifdef DEBUG
|
|
if (!t || !master || (!t->tx_buf && !t->rx_buf) ||
|
|
master->cur_transfer) {
|
|
WARN_ON(1);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
master->cur_transfer = t;
|
|
master->bytes_left = t->len;
|
|
master->cur_tx_byte = (t->tx_buf) ?: &DUMMY_TX;
|
|
master->cur_rx_byte = (t->rx_buf) ?: &DUMMY_RX;
|
|
|
|
_wcxb_spi_transfer_to_fifo(master);
|
|
}
|
|
|
|
/**
|
|
* _wcxb_spi_start_message - Start a new message transferring.
|
|
*
|
|
* Must be called with master->lock held.
|
|
*
|
|
*/
|
|
static int _wcxb_spi_start_message(struct wcxb_spi_master *master,
|
|
struct wcxb_spi_message *message)
|
|
{
|
|
struct wcxb_spi_transfer *t;
|
|
|
|
if (master->cur_msg) {
|
|
/* There is already a message in progress. Queue for later. */
|
|
list_add_tail(&message->node, &master->message_queue);
|
|
return 0;
|
|
}
|
|
|
|
if (!message->spi) {
|
|
dev_dbg(master->parent,
|
|
"Queueing message without SPI device specified?\n");
|
|
return -EINVAL;
|
|
};
|
|
|
|
master->cur_msg = message;
|
|
|
|
_wcxb_assert_chip_select(master, message->spi->chip_select);
|
|
t = list_first_entry(&message->transfers,
|
|
struct wcxb_spi_transfer, node);
|
|
_wcxb_spi_start_transfer(master, t);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* wcxb_spi_complete_message - Complete the current message.
|
|
*
|
|
* Called after all transfers in current message have been completed. This will
|
|
* complete the current message and start the next queued message if there are
|
|
* any.
|
|
*
|
|
* Must be called with the master->lock held.
|
|
*
|
|
*/
|
|
static void _wcxb_spi_complete_cur_msg(struct wcxb_spi_master *master)
|
|
{
|
|
struct wcxb_spi_message *message;
|
|
if (!master->cur_msg)
|
|
return;
|
|
message = master->cur_msg;
|
|
message->status = SUCCESS;
|
|
_wcxb_clear_chip_select(master);
|
|
master->cur_msg = NULL;
|
|
if (!list_empty(&master->message_queue)) {
|
|
message = list_first_entry(&master->message_queue,
|
|
struct wcxb_spi_message, node);
|
|
list_del(&message->node);
|
|
_wcxb_spi_start_message(master, message);
|
|
}
|
|
return;
|
|
}
|
|
|
|
static inline bool
|
|
_wcxb_spi_is_last_transfer(const struct wcxb_spi_transfer *t,
|
|
const struct wcxb_spi_message *message)
|
|
{
|
|
return t->node.next == &message->transfers;
|
|
}
|
|
|
|
static inline struct wcxb_spi_transfer *
|
|
_wcxb_spi_next_transfer(struct wcxb_spi_transfer *t)
|
|
{
|
|
return list_entry(t->node.next, struct wcxb_spi_transfer, node);
|
|
}
|
|
|
|
/**
|
|
* wcxb_spi_handle_interrupt - Drives the transfers forward.
|
|
*
|
|
* Doesn't necessarily need to be called in the context of a real interrupt, but
|
|
* should be called with interrupts disabled on the local CPU.
|
|
*
|
|
*/
|
|
void wcxb_spi_handle_interrupt(struct wcxb_spi_master *master)
|
|
{
|
|
struct wcxb_spi_message *msg;
|
|
struct wcxb_spi_transfer *t;
|
|
void (*complete)(void *arg) = NULL;
|
|
unsigned long flags;
|
|
|
|
/* Check if we're not in the middle of a transfer, or not finished with
|
|
* a part of one. */
|
|
spin_lock_irqsave(&master->lock, flags);
|
|
|
|
t = master->cur_transfer;
|
|
msg = master->cur_msg;
|
|
|
|
if (!msg || !is_txfifo_empty(master))
|
|
goto done;
|
|
|
|
#ifdef DEBUG
|
|
if (!t) {
|
|
dev_dbg(master->parent,
|
|
"No current transfer in %s\n", __func__);
|
|
goto done;
|
|
}
|
|
#endif
|
|
|
|
/* First read any data out of the receive FIFO into the current
|
|
* transfer. */
|
|
_wcxb_spi_transfer_from_fifo(master);
|
|
if (master->bytes_left) {
|
|
/* The current transfer isn't finished. */
|
|
_wcxb_spi_transfer_to_fifo(master);
|
|
goto done;
|
|
}
|
|
|
|
/* The current transfer is finished. Check for another transfer in this
|
|
* message or complete it and look for another message to start. */
|
|
master->cur_transfer = NULL;
|
|
|
|
if (_wcxb_spi_is_last_transfer(t, msg)) {
|
|
complete = msg->complete;
|
|
_wcxb_spi_complete_cur_msg(master);
|
|
} else {
|
|
t = _wcxb_spi_next_transfer(t);
|
|
_wcxb_spi_start_transfer(master, t);
|
|
}
|
|
done:
|
|
spin_unlock_irqrestore(&master->lock, flags);
|
|
/* Do not call the complete call back under the bus lock. */
|
|
if (complete)
|
|
complete(msg->arg);
|
|
return;
|
|
}
|
|
|
|
int wcxb_spi_async(struct wcxb_spi_device *spi,
|
|
struct wcxb_spi_message *message)
|
|
{
|
|
int res;
|
|
unsigned long flags;
|
|
WARN_ON(!spi || !message || !spi->master);
|
|
|
|
if (list_empty(&message->transfers)) {
|
|
/* No transfers in this message? */
|
|
if (message->complete)
|
|
message->complete(message->arg);
|
|
message->status = -EINVAL;
|
|
return 0;
|
|
}
|
|
message->status = -EINPROGRESS;
|
|
message->spi = spi;
|
|
spin_lock_irqsave(&spi->master->lock, flags);
|
|
res = _wcxb_spi_start_message(spi->master, message);
|
|
spin_unlock_irqrestore(&spi->master->lock, flags);
|
|
return res;
|
|
}
|
|
|
|
static void wcxb_spi_complete_message(void *arg)
|
|
{
|
|
complete((struct completion *)arg);
|
|
}
|
|
|
|
int wcxb_spi_sync(struct wcxb_spi_device *spi, struct wcxb_spi_message *message)
|
|
{
|
|
DECLARE_COMPLETION_ONSTACK(done);
|
|
WARN_ON(!spi || !spi->master);
|
|
message->complete = wcxb_spi_complete_message;
|
|
message->arg = &done;
|
|
wcxb_spi_async(spi, message);
|
|
|
|
/* TODO: There has got to be a better way to do this. */
|
|
while (!try_wait_for_completion(&done)) {
|
|
wcxb_spi_handle_interrupt(spi->master);
|
|
cpu_relax();
|
|
}
|
|
return message->status;
|
|
}
|