/* * 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 #include #include #include #include #include #include #include #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 = master->cur_msg; struct wcxb_spi_transfer *t = master->cur_transfer; 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); 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; }