blob: 10eaf9f7c4da581584177c53ab93030ca49bca9a [file] [log] [blame]
/**
* linux/drivers/parrot/spi/p7-spim.c - Parrot7 SPI master controller driver
* implementation
*
* Copyright (C) 2012 Parrot S.A.
*
* @author: alvaro.moran@parrot.com
* @author: Gregor Boirie <gregor.boirie@parrot.com>
* @date: 2012-01-09
*
* This file is released under the GPL
*
* TODO:
* - remove unneeded timeout while polling controller status,
* - clean up debug / log / BUG_ON mess,
* - refactor with slave driver common parts,
* - document anatomy of a transfer (message / xfer / segments /rounds) in
* DMA, interrupt and polling modes,
* - implement full support for full duplex in PIO & DMA modes,
* - implement MPW2 instructions queueing ahead of time when possible,
* - review.
*
* Notes:
* ------
* In MPW1, FIFO TX Threshold interrupt is not generated if there is enough
* data in FIFO to finish the current instruction. That's why we push
* instructions only when we want to (en/de)queue more bytes than what was
* previously instructed.
* This forces us to maintain a count of bytes remaining to order for the
* current transfer (see ctrl->order). This SHOULD be fixed in MPW2.
*
* Currently, full duplex transfers are partially implemented: full duplex will
* work only for tx & rx buffers start @ aligned on the same boundary. Moreover,
* full duplex support in DMA mode cannot be implemented untill pl330 driver
* supports interleaved mode.
*
* In MPW1, if a given instruction/frame has an unaligned number of bytes
* (not multiple of 4) to be received, the last 32-bit word in the RX FIFO
* will be merged with the byte(s) of the next instruction/frame.
* This prevents us from queueing instructions ahead of time and introduces
* sub-optimal wait states between aligned and unaligned transfers (where we
* have to wait for all instructions completion before firing up a new round).
* This MUST be fixed in MPW2.
*
* MPW1 does not provide software with a way to flush instruction FIFOs. This
* forces driver to poll for all instructions completion when flushing (see
* p7spim_flush). This MUST be fixed in MPW2.
*
* MPW1 does not provide dsoftware with a way to de-select a slave peripheral
* without toggling clock, MISO or MOSI pins (see p7spim_flush).
* This SHOULD be fixed for MPW2.
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/time.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/sched.h>
#include <linux/dmaengine.h>
#include <asm/io.h>
#include "p7-spi.h"
#include "p7-spim.h"
#include "p7-spi_priv.h"
static bool noirq = false;
module_param(noirq, bool, 0644);
MODULE_PARM_DESC(noirq, "do not use interrupt handling");
static bool nodma = false;
module_param(nodma, bool, 0644);
MODULE_PARM_DESC(nodma, "do not use dma operations");
/* In P7 R1 and R2 the way to release the SS to high is different,
* we setup at init.*/
static u32 p7spim_SSEND = 0;
/*
* Master core instance internal state
*/
struct p7spim_core {
/* Common SPI core instance */
struct p7spi_core core;
/* Number of bytes left to instruct for current transfer */
size_t order;
/* State of slave select when transfers ends */
bool drop_ss;
/* Hardware mode control register word for current device. */
uint32_t mode;
/* Core input clock rate in Hz */
unsigned int base_hz;
/* Controller / core / master current SPI clock rate in Hz */
unsigned int curr_hz;
#ifdef CONFIG_PM_SLEEP
/* Saved control register accross suspend */
u32 saved_ctrl;
#endif
};
/*
* Return hardware divisor to reach SPI bus frequency passed in argument.
*
* Bus clock frequency is computed using the following formula:
* bus freq = parent clock freq / ((divisor + 1) × 2)
*/
static u32 p7spim_freq_div(struct p7spim_core const* ctrl, unsigned int hz)
{
u32 const div = DIV_ROUND_UP(ctrl->base_hz, 2 * hz);
u32 speed_max_div;
if (p7spi_kern.rev == SPI_REVISION_1)
speed_max_div = P7SPI_SPEED_MAXDIV_R1;
else
speed_max_div = P7SPI_SPEED_MAXDIV_R23;
if ((div - 1) & ~speed_max_div)
dev_dbg(p7spi_core_dev(&ctrl->core),
"unreacheable clock frequency requested (%uHz)\n",
hz);
return (div - 1) & speed_max_div;
}
/*
* Express delay passed in argument as number of bus clock ticks using the
* specified bus clock period (in nano seconds).
* Number of tick will be rounded to the closest upper one.
*/
static u32 p7spim_delay_ticks(struct p7spim_core const* ctrl,
unsigned int delay,
unsigned int ns)
{
u32 const ticks = DIV_ROUND_UP(delay, ns);
#define P7SPI_TIMING_MAXTICKS (0xfU)
if (ticks & ~P7SPI_TIMING_MAXTICKS)
dev_dbg(p7spi_core_dev(&ctrl->core),
"unreacheable delay requested (%uns)\n",
delay);
return ticks & P7SPI_TIMING_MAXTICKS;
}
/* Adjust timings given the bus clock frequency passed in argument. */
static void p7spim_adjust_clk(struct p7spim_core* ctrl,
struct p7spi_plat_data const* pdata,
struct p7spi_ctrl_data const* cdata,
unsigned int hz)
{
struct p7spi_core* const core = &ctrl->core;
u32 div_reg;
u32 tim_reg;
u32 ns;
/* Save uncorrected requested frequency. */
ctrl->curr_hz = hz;
/* Round requested frequency to possible limits. */
hz = max_t(unsigned int, hz, pdata->min_hz);
hz = min_t(unsigned int, hz, pdata->max_master_hz);
/* Compute divisor to reach maximum SPI master clock frequency. */
div_reg = p7spim_freq_div(ctrl, hz);
/* Compute timings according to above frequency. */
ns = ctrl->base_hz / ((div_reg + 1) * 2);
dev_info(p7spi_core_dev(core),
"bus clock frequency set to %uHz",
ns);
ns = NSEC_PER_SEC / ns;
tim_reg = (p7spim_delay_ticks(ctrl,
cdata->tsetup_ss_ns,
ns) << P7SPI_TSETUP_SHIFT) |
(p7spim_delay_ticks(ctrl,
cdata->thold_ss_ns,
ns) << P7SPI_THOLD_SHIFT) |
(p7spim_delay_ticks(ctrl,
cdata->toffclk_ns,
ns) << P7SPI_TOFFCLK_SHIFT) |
(p7spim_delay_ticks(ctrl,
cdata->toffspi_ns,
ns) << P7SPI_TOFFSPI_SHIFT) |
(p7spim_delay_ticks(ctrl,
cdata->tcapture_delay_ns +
(P7SPI_TCAPTURE_DELAY_MIN *
NSEC_PER_SEC / ctrl->base_hz),
NSEC_PER_SEC / ctrl->base_hz) <<
P7SPI_TCAPTURE_DELAY_SHIFT);
/* Setup master clock divisor. */
__raw_writel(div_reg, core->vaddr + P7SPI_SPEED);
/* Setup master timings. */
__raw_writel(tim_reg, core->vaddr + P7SPI_TIMING);
}
/*
* Check if pushed instructions (i.e. sequence of instructed transfers)
* are all completed.
*/
static int p7spim_instr_done(struct p7spi_core const* core, u32 stat)
{
if (! (stat & P7SPI_STATUS_END_TRANSM))
return -EAGAIN;
return 0;
}
/*
* Order a new transfer, i.e. push a new instruction to fire up a transfer
* round.
*/
static void p7spim_push_instr(struct p7spim_core const* ctrl,
size_t bytes,
bool dma,
bool drop_ss)
{
struct p7spi_core const* const core = &ctrl->core;
u32 cmd = ctrl->mode | bytes;
/*
* Build instruction word.
*/
if (core->tx_buff)
/* Write requested. */
cmd |= P7SPI_INSTR_RW_WRITE;
if (core->rx_buff)
/* Read requested. */
cmd |= P7SPI_INSTR_RW_READ;
if (drop_ss)
/* Slave de-select requested. */
cmd |= p7spim_SSEND;
if (dma)
/* DMA requested. */
cmd |= P7SPI_INSTR_DMAE_ENABLE;
#ifdef DEBUG
dev_dbg(p7spi_core_dev(core), "\t\t\t%uB instruction: 0x%08x\n", bytes, cmd);
BUG_ON(! ctrl);
BUG_ON(! bytes);
BUG_ON(bytes > P7SPI_INSTR_LEN_MASK);
BUG_ON(__raw_readl(core->vaddr + P7SPI_STATUS) &
P7SPI_STATUS_INSTR_FULL);
#endif
/*
* Push instruction word.
*/
__raw_writel(cmd, core->vaddr + P7SPI_INSTR);
}
/*
* Limitation on r1 and r2 using DMA in TX mode.
* The SPI notify DMA that it is ready to receive bytes when
* nb_bytes_in_instruction - TXCNT_VAL > DMA burst,
* and TXCNT_VAL is reset when the first byte of a new instruction is
* written in the FIFO. So when a first transfer completes, TXCNT_VAL
* is equal to the amount of bytes that have been transfered. Then, if
* another round tries to transfer the same amount by DMA, TXCNT_VAL
* is equal to nb_bytes and the DMA is not notified, so the DMA
* transfer can't start and TXCNT_VAL is not reset. Deadlock.
* The workaround is that before any DMA transfer are started in
* TX mode, wait for the previous instruction to complete
* and reset TXCNT_VAL
*/
static int p7spim_reset_txcnt(struct p7spim_core const* ctrl)
{
struct p7spi_core const* const core = &ctrl->core;
if (core->tx_buff) {
int err;
err = p7spi_poll_stat(core, p7spim_instr_done, 5 * HZ);
if (err) {
p7spi_dump_regs(core, "dma timeout");
return err;
}
__raw_writel(P7SPI_ITACK_TXCNT_ACK, core->vaddr + P7SPI_ITACK);
if (p7spi_kern.rev == SPI_REVISION_1) {
/* MPW1 needs 0 to be rewritten in the register */
writel(0, core->vaddr + P7SPI_ITACK);
}
}
return 0;
}
static bool p7spim_must_drop_ss(struct p7spim_core* ctrl, size_t rem_bytes)
{
struct p7spi_core* const core = &ctrl->core;
return (core->bytes == rem_bytes) && ctrl->drop_ss;
}
/*
* Launch a new transfer performing an alignment round.
* This will transfer enought bytes in polling mode to align overall transfer
* on a 32 bits boundary so that subsequent round may use either interrupts or
* DMA mode (FIFOs are 32bits wide).
*/
static int p7spim_start_xfer(struct p7spim_core* ctrl)
{
struct p7spi_core* const core = &ctrl->core;
char const* const buff = core->rx_buff ? : core->tx_buff;
size_t bytes = core->bytes;
bool ss_dropped;
if (! bytes)
/*
* No more bytes to transfer: this may happen when slave de-select is
* requested.
*/
return 0;
bytes = min(bytes, (size_t) (PTR_ALIGN(buff, sizeof(u32)) - buff));
if (bytes) {
/* Not aligned on word boundary: perform an alignment round. */
int err;
/*
* Instruct transfer ahead of time so that bytes are transmitted as soon
* as bytes are queued into FIFO. Controller will suspend transmission
* (i.e., stop toggling bus clock) if FIFOs are not ready (tx empty or
* rx full).
*
* On R1, if he have to deselect slave-select, it will be done in another
* function (_end_xfer). On R2 and R3, we do it in place.
*/
if ((p7spi_kern.rev != SPI_REVISION_1) &&
p7spim_must_drop_ss(ctrl, bytes)) {
p7spim_push_instr(ctrl, bytes, false, true);
ss_dropped = true;
}
else {
p7spim_push_instr(ctrl, bytes, false, false);
ss_dropped = false;
}
if (core->tx_buff)
/* Feed tx FIFO. */
p7spi_writeb_fifo(core, bytes);
dev_dbg(p7spi_core_dev(core),
"\t\t\t%uB align %swr[%p] rd[%p]\n",
bytes,
ss_dropped ? "cs " : "",
core->tx_buff ? (core->tx_buff - bytes) : NULL,
core->rx_buff);
/* Wait for completion of instruction. */
err = p7spi_poll_stat(core, p7spim_instr_done, 5 * HZ);
if (err) {
p7spi_dump_regs(core, "align round failed");
return err;
}
if (core->rx_buff)
/* Fetch data from rx FIFO. */
p7spi_readb_fifo(core, bytes);
/* Update count of completed bytes. */
core->bytes -= bytes;
}
/*
* Return bytes count left to transfer to inform caller wether or not it
* must run additional transfer rounds.
*/
return core->bytes;
}
/*
* Finalize a transfer. When processing terminal bytes of a transfer, take
* care of slave select line as described below.
*
* As of MPW1, controller hardware only allow software to de-select slave after
* each transfered bytes (i.e., software cannot request to de-select slave when
* transmitting last byte of a transfer). This is the reason why we have to
* build a separate transfer round to take care of slave select line.
*
* MPW2 will modify this behavior.
*/
static int p7spim_end_xfer(struct p7spim_core* ctrl, bool drop_ss)
{
int err;
struct p7spi_core* const core = &ctrl->core;
if (p7spi_kern.rev != SPI_REVISION_1)
/*
* On newer versions, CS is raised at the end of the
* instruction and there is no need to send a lonely byte
* to do so
*/
return 0;
if (! drop_ss)
/* Silently ignore if slave deselect is not required. */
return 0;
p7spim_push_instr(ctrl, 1U, false, true);
if (core->tx_buff)
p7spi_writeb_fifo(core, 1U);
dev_dbg(p7spi_core_dev(core),
"\t\t\t%uB cs wr[%p] rd[%p]\n",
1U,
core->tx_buff ? (core->tx_buff - 1U) : NULL,
core->rx_buff);
err = p7spi_poll_stat(core, p7spim_instr_done, 5 * HZ);
if (err) {
p7spi_dump_regs(core, "cs round failed");
return err;
}
if (core->rx_buff)
p7spi_readb_fifo(core, 1U);
return 0;
}
/*
* Launch the very first round of the first segment of a transfer,
* warming up the chaining of successive transfer rounds machinery
* (by preloading tx FIFO if required).
* This is used in polling and / or interrupt mode (also when DMA is enabled
* if transfer size is not long enought to run the DMA controller).
*
* At this time, we are guaranteed the transfer start @ is aligned on a 32 bits
* boundary (see p7spim_start_xfer).
*/
static ssize_t p7spim_start_sgm(struct p7spim_core* ctrl)
{
struct p7spi_core* const core = &ctrl->core;
size_t const bytes = min(core->bytes, core->fifo_sz);
bool ss_dropped;
#ifdef DEBUG
BUG_ON(! bytes);
#endif
if (core->bytes > core->fifo_sz) {
/*
* Compute maximum number of bytes to order
* (up to maximum segment size).
*/
size_t const order = min(core->bytes, core->sgm_sz);
/*
* Instruct to transfer a number of bytes rounded down to
* maximum transfer segment size so that a single instruction is needed
* per segment.
*/
if ((p7spi_kern.rev != SPI_REVISION_1) &&
p7spim_must_drop_ss(ctrl, order)) {
p7spim_push_instr(ctrl, order, false, true);
ss_dropped = true;
}
else {
p7spim_push_instr(ctrl, order, false, false);
ss_dropped = false;
}
/* Update count of bytes left to order to complete current transfer. */
ctrl->order = core->bytes - order;
}
else {
/* A single round is enought to complete this segment. */
if ((p7spi_kern.rev != SPI_REVISION_1) && ctrl->drop_ss) {
p7spim_push_instr(ctrl, bytes, false, true);
ss_dropped = true;
}
else {
p7spim_push_instr(ctrl, bytes, false, false);
ss_dropped = false;
}
}
if (core->tx_buff) {
/*
* Enqueue up to one FIFO depth bytes. In cases where we need multiple
* rounds, this allows us to preload tx FIFO with as many bytes as we
* can.
*/
p7spi_write_fifo(core, bytes);
dev_dbg(p7spi_core_dev(core),
"\t\t\t%uB %sproc wr[%p]\n",
bytes,
ss_dropped ? "cs " : "",
core->tx_buff - bytes);
}
/*
* Return bytes count left to process to inform caller wether or not it
* must run additional transfer rounds.
* This will return 0 as long as the number of bytes to transfer is <= FIFO
* depth.
*/
return core->bytes - bytes;
}
/*
* Complete a transfer round and initiate a new one if needed.
* Should be called after a hardware completion event happened
* (either FIFO threshold reached, last byte transferred or last
* instruction completed).
* Transfer rounds carried out by p7spim_process_round are always threshold
* bytes long.
* This is used in both polling and interrupt mode of operations.
*/
static int p7spim_process_round(struct p7spim_core* ctrl)
{
struct p7spi_core* const core = &ctrl->core;
size_t const thres = core->thres_sz;
size_t const sz = core->fifo_sz + thres;
size_t const bytes = core->bytes;
bool ss_dropped = false;
/*
* At this time:
* bytes left to order / instruct:
* ctrl->order
* bytes left to queue:
* core->bytes - core->fifo_sz
* bytes left to transfer (i.e., minus bytes still present within fifo) :
* core->bytes - (core->fifo_sz - core->thres_sz)
* bytes left to dequeue:
* core->bytes
*/
if (ctrl->order && (bytes < (ctrl->order + sz))) {
/*
* There are still some more bytes to order and the number of bytes left
* to queue at the end of this function
* (core->bytes - (core->fifo_sz + core->thres_sz)) is < ctrl->order,
* the current number of bytes ordered, i.e., at the
* end of this function we will have queued more bytes than the current
* number of ordered bytes.
* Order as many bytes as we can, i.e., up to maximum segment size...
*/
size_t const order = min(ctrl->order, core->sgm_sz);
if ((p7spi_kern.rev != SPI_REVISION_1) &&
(ctrl->order == order) &&
ctrl->drop_ss) {
p7spim_push_instr(ctrl, order, false, true);
ss_dropped = true;
}
else {
p7spim_push_instr(ctrl, order, false, false);
ss_dropped = false;
}
ctrl->order -= order;
}
if (bytes <= sz)
/*
* Remaining bytes to queue (core->bytes - core->fifo_sz) is <=
* threshold.
* we still have to perform an extra threshold sized fifo read to
* catch up with the one we should have done just below (see
* p7spim_end_sgm).
* Return 0 to inform caller there is no need to perform additional
* rounds.
*/
return 0;
if (core->rx_buff)
/* Fetch input bytes from receive FIFO. */
p7spi_readl_fifo(core, thres);
/* Update count of remaining bytes to dequeue. */
core->bytes -= thres;
/*
* Now start another round... At this time, remaining bytes to
* queue (core->bytes + core->thres_sz - core->fifo_sz) MUST
* be >= core->thres_sz since we perform threshold sized transfers
* only.
*/
if (core->tx_buff)
/* Feed transmit FIFO. */
p7spi_writel_fifo(core, thres);
dev_dbg(p7spi_core_dev(core),
"\t\t\t%uB %sproc wr[%p] rd[%p]\n",
thres,
ss_dropped ? "cs " : "",
core->tx_buff ? (core->tx_buff - thres) : NULL,
core->rx_buff ? (core->rx_buff - thres) : NULL);
/*
* Return count of bytes left to process to inform caller we need to perform
* additional rounds.
*/
return core->bytes;
}
/*
* Perform the last round of the last segment of a transfer taking care of last
* "residue" bytes.
*
* At this time, we are guaranteed the transfer start @ is aligned on a 32 bits
* boundary (see p7spim_process_round).
*/
static int p7spim_end_sgm(struct p7spim_core* ctrl)
{
struct p7spi_core* const core = &ctrl->core;
size_t const fifo = core->fifo_sz;
int err;
if (core->bytes > fifo) {
/*
* We had to perform more than a full FIFO bytes long transfer, meaning
* we are comming from p7spim_process_round and we still have to run an
* extra threshold sized fifo read.
*/
size_t const bytes = core->bytes - fifo;
size_t const thres = core->thres_sz;
#ifdef DEBUG
BUG_ON(core->bytes > (fifo + thres));
#endif
if (core->rx_buff) {
/* Fetch input bytes from receive FIFO. */
p7spi_readl_fifo(core, thres);
dev_dbg(p7spi_core_dev(core),
"\t\t\t%uB proc rd[%p]\n",
thres,
core->rx_buff - thres);
}
/* Update completed bytes counter. */
core->bytes -= thres;
if (bytes && core->tx_buff) {
/* Enqueue last bytes to send if any. */
p7spi_write_fifo(core, bytes);
dev_dbg(p7spi_core_dev(core),
"\t\t\t%uB end wr[%p]\n",
bytes,
core->tx_buff - bytes);
}
}
/* Ensure all instructions were completed. */
err = p7spi_poll_stat(core, p7spim_instr_done, 5 * HZ);
if (err) {
p7spi_dump_regs(core, "end round failed");
return err;
}
if (! (core->bytes && core->rx_buff))
/* No more bytes to receive. */
return 0;
/* Fetch bytes from input FIFO. */
p7spi_read_fifo(core, core->bytes);
dev_dbg(p7spi_core_dev(core),
"\t\t\t%uB end rd[%p]\n",
core->bytes,
core->rx_buff - core->bytes);
return 0;
}
/*
* Perform transfer in polling mode.
*/
static int p7spim_xfer_bypoll(struct p7spim_core* ctrl)
{
if (p7spim_start_sgm(ctrl) > 0) {
int err;
struct p7spi_core* const core = &ctrl->core;
do {
err = p7spi_poll_stat(core, p7spi_data_ready, 15 * HZ);
} while (! err && p7spim_process_round(ctrl));
if (err) {
p7spi_dump_regs(core, "proc round failed");
return err;
}
}
return p7spim_end_sgm(ctrl);
}
/*
* Master controller interrupt handler
* In interrupt mode, process threshold bytes long transfer rounds.
*/
static irqreturn_t p7spim_handle_irq(int irq, void* dev_id)
{
struct p7spim_core* const ctrl = (struct p7spim_core*) dev_id;
struct p7spi_core* const core = &ctrl->core;
core->stat = p7spi_poll_stat(core, p7spi_data_ready, 0);
switch (core->stat) {
case -EAGAIN:
/* Status reports nothing particular: spurious interrupt ?? */
return IRQ_NONE;
case 0:
if (p7spim_process_round(ctrl))
/*
* Round processed but there are still some more threshold bytes
* long rounds to perform (i.e., remaining bytes to complete >
* threshold).
*/
return IRQ_HANDLED;
}
/*
* Disable interrutps since remaining bytes will be transfered in polling
* mode.
*/
__raw_writel(0, core->vaddr + P7SPI_ITEN);
/* Notify worker we are done. */
complete(&core->done);
return IRQ_HANDLED;
}
/*
* Perform transfer in interrupt mode.
*/
static int p7spim_xfer_byirq(struct p7spim_core* ctrl)
{
struct p7spi_core* const core = &ctrl->core;
if (p7spi_has_irq(core)) {
/* Require being notified of FIFOs (over/under)flow (MPW2 only). */
u32 iten = P7SPI_ITEN_RXACCESS_ERROR |
P7SPI_ITEN_TXACCESS_ERROR;
if (p7spim_start_sgm(ctrl) > 0) {
/*
* Initialize interrupt mode completion token: we will be notified
* once "residue" bytes only are left to be processed.
*/
INIT_COMPLETION(core->done);
/*
* When transfering in rx & tx mode, we might catch 2 interrupts:
* activate rx-side interrupt only when rx & tx required.
*/
if (core->rx_buff)
/* rx and / or tx requested. */
iten |= P7SPI_ITEN_RX_TH_REACHED;
else if (core->tx_buff)
/* tx only. */
iten |= P7SPI_ITEN_TX_TH_REACHED;
__raw_writel(iten, core->vaddr + P7SPI_ITEN);
/* Wait for end of threshold bytes long rounds processing. */
if (! wait_for_completion_timeout(&core->done, 15 * HZ)) {
p7spi_dump_regs(core, "proc round failed");
/* Disable interrupts since interrupt handler could not do it. */
__raw_writel(0, core->vaddr + P7SPI_ITEN);
/*
* Return timeout error only if rounds processing did not
* encounter any.
*/
core->stat = core->stat ? : -ETIMEDOUT;
}
if (core->stat)
return core->stat;
}
return p7spim_end_sgm(ctrl);
}
return p7spim_xfer_bypoll(ctrl);
}
/*
* DMA task completion handler (run in tasklet context).
* Just notify xfer_msg function that it's done.
*/
static void p7spim_complete_dma(void* controller)
{
struct p7spim_core* const ctrl = controller;
struct p7spi_core* const core = &ctrl->core;
/* Notify worker we are done. */
complete(&core->done);
}
/*
* Transfer extra bytes after DMA rounds.
*/
static int p7spim_xfer_dma_extra(struct p7spim_core* ctrl, size_t bytes)
{
struct p7spi_core* const core = &ctrl->core;
int err;
#ifdef DEBUG
BUG_ON(bytes >= core->fifo_sz);
#endif
if (!bytes)
return 0;
if (core->tx_buff) {
err = p7spi_poll_stat(core, p7spi_data_ready, 5 * HZ);
if (err) {
p7spi_dump_regs(core, "xtra round failed");
return err;
}
p7spi_write_fifo(core, bytes);
dev_dbg(p7spi_core_dev(core),
"\t\t\t%uB xtra wr[%p]\n",
bytes, core->tx_buff - bytes);
}
err = p7spi_poll_stat(core, p7spim_instr_done, 5 * HZ);
if (err) {
p7spi_dump_regs(core, "xtra round failed");
return err;
}
if (core->rx_buff) {
p7spi_read_fifo(core, bytes);
dev_dbg(p7spi_core_dev(core),
"\t\t\t%uB extra rd[%p]\n",
bytes, core->rx_buff - bytes);
}
core->bytes -= bytes;
return 0;
}
/*
* Perform transfer in DMA mode.
* DMA transfers must satisfy following constraints:
* - start @ must be aligned on a cache line boundary,
* - size must be a multiple of cache line size,
* - DMA controller <-> SPI controller exchange burst size must be equal to
* SPI controller FIFO threshold to properly handle DMA peripheral interface
* handshakes (this means transfer size must be a multiple of burst size as
* well).
* When these condition cannot be satisfied, fall back to interrupt / polling
* mode.
*/
static int p7spim_xfer_bydma(struct p7spim_core* ctrl)
{
int err = 0;
bool ss_dropped;
struct p7spi_core* const core = &ctrl->core;
char const* const buff = core->rx_buff ? : core->tx_buff;
ssize_t bytes;
bytes = min_t(ssize_t,
core->bytes,
PTR_ALIGN(buff,dma_get_cache_alignment()) - buff);
if (! p7spi_has_dma(core) ||
((core->bytes - (size_t) bytes) < core->dma_min))
/*
* Cannot perform transfer using DMA because either DMA is disabled or
* transfer size and / or start @ don't comply with the above
* contraints. Fall back to interrupt / polling mode.
*/
return p7spim_xfer_byirq(ctrl);
if (bytes) {
/*
* Transfer enought byte to align start @ to cache line / burst size
* boundary if needed. core->bytes content is altered to fool
* interrupt / polling implementation so that it transfers required
* count of bytes only.
*/
size_t const sz = core->bytes - (size_t) bytes;
bool save_drop_ss = ctrl->drop_ss;
/* Request p7spim_xfer_byirq to transfer sz bytes only. */
core->bytes = (size_t) bytes;
ctrl->drop_ss = false;
err = p7spim_xfer_byirq(ctrl);
if (err)
return err;
/*
* Restore and update proper bytes count now that DMA operation is
* possible.
*/
core->bytes = sz;
ctrl->drop_ss = save_drop_ss;
}
/*
* Setup DMA operation properties common to all segments for this
* transfer.
*/
err = p7spi_config_dma(core);
if (err)
return err;
/* Setup SPI controller DMA peripheral interface. */
p7spi_cook_dmaif(core);
do {
size_t extra_bytes = 0;
/*
* map segment by segment.
*/
bytes = p7spi_map_dma(core);
if (bytes < 0)
goto unmap;
else if (core->bytes - bytes > 0 &&
!core->tx_buff &&
core->bytes - bytes < core->fifo_sz &&
core->bytes < core->sgm_sz) {
/*
* This is a little hack to use the same instruction
* to transfer remaining bytes in RX mode.
*/
extra_bytes = core->bytes - bytes;
bytes = core->bytes;
dev_dbg(p7spi_core_dev(core),
"\t\t\tusing dma instr to send %uB extra\n",
extra_bytes);
}
/* Submit DMA operation for current segment. */
err = p7spi_run_dma(core, p7spim_complete_dma);
if (err)
goto unmap;
if (p7spi_kern.rev == SPI_REVISION_1) {
/* we have to reet txcnt between two transfers only on R1 */
err = p7spim_reset_txcnt(ctrl);
if (err) {
p7spi_cancel_dma(core);
goto unmap;
}
}
/* Instruct SPI controller to start transfer. */
if ((p7spi_kern.rev != SPI_REVISION_1) &&
p7spim_must_drop_ss(ctrl, extra_bytes)) {
/*
* core->bytes is already decreased in p7spi_run_dma, so at this
* point, we check if this is the last round of bytes or not.
*/
p7spim_push_instr(ctrl, (size_t) bytes, true, true);
ss_dropped = true;
}
else {
p7spim_push_instr(ctrl, (size_t) bytes, true, false);
ss_dropped = false;
}
dev_dbg(p7spi_core_dev(core),
"\t\t\t%uB dma %swr[%p] rd[%p]\n",
(size_t) bytes,
ss_dropped ? "cs " : "",
core->tx_buff ? core->tx_buff - bytes + extra_bytes : NULL,
core->rx_buff ? core->rx_buff - bytes + extra_bytes : NULL);
/* Wait for segment completion. */
err = p7spi_wait_dma(core);
if (err) {
/* Cancel current DMA operations if still undergoing... */
p7spi_cancel_dma(core);
goto unmap;
}
core->stat = p7spi_check_dma(core);
if (core->stat) {
err = core->stat;
goto unmap;
}
p7spi_unmap_dma(core);
err = p7spim_xfer_dma_extra(ctrl, extra_bytes);
if (err) {
p7spi_uncook_dmaif(core);
return err;
}
} while (core->bytes >= core->dma_min);
unmap:
p7spi_uncook_dmaif(core);
if (err) {
p7spi_unmap_dma(core);
return err;
}
/*
* When DMA is completed in TX mode, we don't know how many bytes are laying
* in the FIFO.
* As xfer_byirq or xber_bypoll start by filling completely the FIFO, we
* need to ensure that there is room for these bytes, so we just wait for
* the "dma" bytes to be sent before continuing.
*/
if (core->tx_buff) {
err = p7spi_poll_stat(core, p7spim_instr_done, 5 * HZ);
if (err) {
p7spi_dump_regs(core, "dma wait timeout");
return err;
}
/*
* After a DMA TX xfer, an extra DMA request is going to be
* generated when the next instruction is queued and the "FIFO
* TX threshold reached" condition is true. It triggers a bug
* if the next instruction is RX DMA because in this case the
* DMA controller will start too soon and access an empty FIFO.
* The workaround is to send a "null" instruction and to flush
* the DMA request generated by this instruction to be in a
* clean state.
*/
__raw_writel(0, core->vaddr + P7SPI_INSTR);
writel(P7SPI_DMA_FLUSH, core->vaddr + P7SPI_FIFO_FLUSH);
}
if (! core->bytes)
return 0;
/*
* Finalize transfer by handling "residue" bytes in interrupt / polling mode.
*/
return p7spim_xfer_byirq(ctrl);
}
static inline int p7spim_do_xfer(struct p7spim_core* ctrl)
{
return p7spim_xfer_bydma(ctrl);
}
/*
* Called when tring to flush FIFOs on MPW1.
* Since MPW1 misses a way to flush instruction FIFOs, we need to ensure data
* FIFOs are flushed as long as all instructions are not completed.
*/
static int p7spim_empty_fifo(struct p7spi_core const* core, u32 stat)
{
/* Always flush receive FIFO if not empty. */
__raw_writel(P7SPI_FIFORX_FLUSH, core->vaddr + P7SPI_FIFO_FLUSH);
if ((stat & (P7SPI_STATUS_RXEMPTY | P7SPI_STATUS_END_TRANSM)) ==
(P7SPI_STATUS_RXEMPTY | P7SPI_STATUS_END_TRANSM))
/*
* No more instructions to process and receive FIFO is empty: flush
* succeeded.
*/
return 0;
if (core->tx_buff &&
! (stat & (P7SPI_STATUS_TXFULL | P7SPI_STATUS_END_TRANSM)))
/* Provide tx FIFO with fake data to consume instructions. */
__raw_writel(0, core->vaddr + P7SPI_DATA);
/* Tell caller we still need some more work to complete flush operation. */
return -EAGAIN;
}
static void p7spim_flush(struct p7spim_core const* ctrl)
{
struct p7spi_core const* const core = &ctrl->core;
if (p7spi_kern.rev == SPI_REVISION_1) {
/* This is MPW1: flush + slave de-select. */
int err = p7spi_poll_stat(core, p7spim_empty_fifo, 5 * HZ);
if (err)
dev_err(p7spi_core_dev(core),
"failed to flush FIFOs (%d)\n",
err);
/*
* At last, de-select slave...
* As of MPW1, there is no way to alter slave select signal without toggling
* clock / MISO / MOSI.
* Request controller to read one byte and de-select slave just after.
* This will change in MPW2 !
*/
__raw_writel(P7SPI_INSTR_RW_READ |
p7spim_SSEND |
ctrl->mode |
1U,
core->vaddr + P7SPI_INSTR);
err = p7spi_poll_stat(core, p7spim_instr_done, 5 *HZ);
if (err)
dev_err(p7spi_core_dev(core),
"failed to de-select slave (%d)\n",
err);
}
else {
/*
* MPW2 implementation: instructions FIFO is flushed at reset time, which
* is performed by clk implementation.
*/
clk_disable(core->clk);
clk_enable(core->clk);
}
__raw_writel(P7SPI_FIFOTX_FLUSH | P7SPI_FIFORX_FLUSH,
core->vaddr + P7SPI_FIFO_FLUSH);
}
/*
* Process generic SPI layer messages (run in workqueue / kthread context).
*/
static int p7spim_xfer_msg(struct spi_master* master, struct spi_message* msg)
{
struct p7spim_core* const ctrl = spi_master_get_devdata(master);
struct p7spi_core* const core = &ctrl->core;
struct spi_device const* const slave = msg->spi;
struct p7spi_plat_data const* const pdata = dev_get_platdata(p7spi_kern.dev);
struct p7spi_ctrl_data const* const cdata = slave->controller_data;
unsigned int const slave_hz = slave->max_speed_hz ? :
pdata->max_master_hz;
struct spi_transfer* xfer;
int err = -ENOMSG;
P7SPI_ASSERT_MSG(msg, ctrl, master, slave);
list_for_each_entry(xfer, &msg->transfers, transfer_list) {
/*
* When processing last transfer of current message, we are
* required to keep slave device selected between each message
* if cs_change is true.
* When processing other transfers of current message, we are
* required to deselect slave device between each transfer if
* cs_change is true.
*/
ctrl->drop_ss = list_is_last(&xfer->transfer_list,
&msg->transfers) ^ xfer->cs_change;
err = p7spi_init_xfer(core, xfer, msg, master);
if (err)
break;
if ((p7spi_kern.rev == SPI_REVISION_1) && ctrl->drop_ss) {
/*
* If we need to de-select slave at end of transfer, reduce size by
* one so that p7spim_end_sgm may handle it properly.
*/
core->bytes--;
}
/* Setup SPI bus clock rate if requested on a per-transfer basis. */
if (!xfer->speed_hz || xfer->speed_hz > slave_hz)
xfer->speed_hz = slave_hz;
if (xfer->speed_hz != ctrl->curr_hz)
p7spim_adjust_clk(ctrl, pdata, cdata, xfer->speed_hz);
/* Start transfer. */
err = p7spim_start_xfer(ctrl);
if (err < 0)
break;
if (err > 0) {
/*
* Run additional transfer rounds if some more bytes are left to
* transfer.
*/
err = p7spim_do_xfer(ctrl);
if (err)
break;
}
/* At last, take care of slave select output. */
err = p7spim_end_xfer(ctrl, ctrl->drop_ss);
if (err)
break;
msg->actual_length += xfer->len;
if (xfer->delay_usecs)
udelay(xfer->delay_usecs);
}
if (err) {
p7spim_flush(ctrl);
dev_err(p7spi_core_dev(core),
"failed to transfer message (%d)\n",
err);
}
msg->status = err;
spi_finalize_current_message(master);
return err;
}
static int p7spim_setup(struct spi_device* slave)
{
struct p7spi_plat_data const* const pdata = dev_get_platdata(p7spi_kern.dev);
struct p7spim_core* const ctrl = spi_master_get_devdata(slave->master);
struct p7spi_core* const core = &ctrl->core;
struct p7spi_ctrl_data const* const cdata = slave->controller_data;
u32 ctrl_reg = 0;
int err;
if (cdata->half_duplex)
slave->master->flags |= SPI_MASTER_HALF_DUPLEX;
if (! cdata->read)
slave->master->flags |= SPI_MASTER_NO_RX;
if (! cdata->write)
slave->master->flags |= SPI_MASTER_NO_TX;
if (! (cdata->half_duplex || (cdata->read ^ cdata->write))) {
dev_err(p7spi_core_dev(core),
"invalid transfer mode for device %s (%s duplex, %sread, %swrite)\n",
dev_name(&slave->dev),
cdata->half_duplex ? "half" : "full",
cdata->read ? "" : "no ",
cdata->write ? "" : "no ");
return -EINVAL;
}
err = p7spi_setup_core(core, slave, &ctrl_reg);
if (err == -EAGAIN)
return -EPROBE_DEFER;
else if (err)
return err;
/* Setup clock and timings. */
ctrl->base_hz = clk_get_rate(p7spi_kern.clk);
p7spim_adjust_clk(ctrl,
pdata,
cdata,
slave->max_speed_hz ? : pdata->max_master_hz);
/* Setup transfer mode. */
ctrl->mode = cdata->xfer_mode << P7SPI_INSTR_SPI_TMODE_SHIFT;
/* Start core in master mode. */
ctrl_reg |= P7SPI_CTRL_MSTR | P7SPI_CTRL_ENABLE;
p7spi_enable_core(core, slave, ctrl_reg);
dev_info(p7spi_core_dev(core),
"enabled device %s on master core:\n"
"\t%s duplex/%sread/%swrite\n"
"\tmode: %s\n"
"\tclock: polarity=%d phase=%d\n"
"\tfifo: %uB/%uB",
dev_name(&slave->dev),
cdata->half_duplex ? "half" : "full",
cdata->read ? "" : "no ",
cdata->write ? "" : "no ",
p7spi_mode_name(cdata->xfer_mode),
!! (slave->mode & SPI_CPOL),
!! (slave->mode & SPI_CPHA),
(unsigned int) core->thres_sz,
(unsigned int) core->fifo_sz);
return 0;
}
static struct p7spi_ops p7spim_ops __devinitdata = {
.setup = p7spim_setup,
.xfer = p7spim_xfer_msg,
.rt = false,
.dma_cyclic = false,
};
static int __devinit p7spim_probe(struct platform_device* pdev)
{
int ret;
ret = p7spi_create_ctrl(pdev, &p7spim_ops, struct p7spim_core);
if (! ret) {
struct p7spim_core* const ctrl =
(struct p7spim_core*) platform_get_drvdata(pdev);
ret = p7spi_register(pdev, &ctrl->core);
if (ret)
p7spi_abort_create(pdev);
}
return ret;
}
#ifdef CONFIG_PM_SLEEP
static int p7spim_suspend(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct p7spi_core* core = platform_get_drvdata(pdev);
struct p7spim_core* ctrl = (struct p7spim_core*) core;
int err;
err = spi_master_suspend(core->ctrl);
if (err)
return err;
ctrl->saved_ctrl = __raw_readl(core->vaddr + P7SPI_CTRL);
__raw_writel(0, core->vaddr + P7SPI_CTRL);
clk_disable_unprepare(core->clk);
return 0;
}
static int p7spim_resume(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct p7spi_core* core = platform_get_drvdata(pdev);
struct p7spim_core* ctrl = (struct p7spim_core*) core;
int err;
clk_prepare_enable(core->clk);
err = spi_master_resume(core->ctrl);
if (err)
return err;
ctrl->curr_hz = 0;
p7spi_enable_core(core, NULL, ctrl->saved_ctrl);
return 0;
}
#else
#define p7spim_suspend NULL
#define p7spim_resume NULL
#endif
static struct dev_pm_ops p7spim_dev_pm_ops = {
.suspend = p7spim_suspend,
.resume = p7spim_resume,
};
static struct platform_driver p7spim_driver = {
.driver = {
.name = P7SPIM_DRV_NAME,
.owner = THIS_MODULE,
.pm = &p7spim_dev_pm_ops,
},
.probe = p7spim_probe,
.remove = __devexit_p(p7spi_destroy_ctrl),
};
static int __init p7spim_init(void)
{
if (! noirq)
p7spim_ops.handle_irq = p7spim_handle_irq;
if (! nodma)
p7spim_ops.dma = true;
if (p7spi_kern.rev == SPI_REVISION_1)
p7spim_SSEND = P7SPI_INSTR_SS_END_BYTE_HIGH;
else
p7spim_SSEND = P7SPI_INSTR_SS_END_INSTR_HIGH;
return platform_driver_register(&p7spim_driver);
}
module_init(p7spim_init);
static void __exit p7spim_exit(void)
{
platform_driver_unregister(&p7spim_driver);
}
module_exit(p7spim_exit);
MODULE_AUTHOR("Alvaro Moran - Parrot S.A. <alvaro.moran@parrot.com>");
MODULE_DESCRIPTION("Parrot7 SPI master controller driver");
MODULE_LICENSE("GPL");