// SPDX-License-Identifier: GPL-2.0-only /* * MPC52xx SPI bus driver. * * Copyright (C) 2008 Secret Lab Technologies Ltd. * * This is the driver for the MPC5200's dedicated SPI controller. * * Note: this driver does not support the MPC5200 PSC in SPI mode. For * that driver see drivers/spi/mpc52xx_psc_spi.c
*/
/* FSM state return values */ #define FSM_STOP 0 /* Nothing more for the state machine to */ /* do. If something interesting happens */ /* then an IRQ will be received */ #define FSM_POLL 1 /* need to poll for completion, an IRQ is */ /* not expected */ #define FSM_CONTINUE 2 /* Keep iterating the state machine */
/* Driver internal data */ struct mpc52xx_spi { struct spi_controller *host; void __iomem *regs; int irq0; /* MODF irq */ int irq1; /* SPIF irq */ unsignedint ipb_freq;
/* Statistics; not used now, but will be reintroduced for debugfs */ int msg_count; int wcol_count; int wcol_ticks;
u32 wcol_tx_timestamp; int modf_count; int byte_count;
/* Details of current transfer (length, and buffer pointers) */ struct spi_message *message; /* current message */ struct spi_transfer *transfer; /* current transfer */ int (*state)(int irq, struct mpc52xx_spi *ms, u8 status, u8 data); int len; int timestamp;
u8 *rx_buf; const u8 *tx_buf; int cs_change; int gpio_cs_count; struct gpio_desc **gpio_cs;
};
/* * CS control function
*/ staticvoid mpc52xx_spi_chipsel(struct mpc52xx_spi *ms, int value)
{ int cs;
/* * Start a new transfer. This is called both by the idle state * for the first transfer in a message, and by the wait state when the * previous transfer in a message is complete.
*/ staticvoid mpc52xx_spi_start_transfer(struct mpc52xx_spi *ms)
{
ms->rx_buf = ms->transfer->rx_buf;
ms->tx_buf = ms->transfer->tx_buf;
ms->len = ms->transfer->len;
/* Activate the chip select */ if (ms->cs_change)
mpc52xx_spi_chipsel(ms, 1);
ms->cs_change = ms->transfer->cs_change;
/* Write out the first byte */
ms->wcol_tx_timestamp = mftb(); if (ms->tx_buf)
out_8(ms->regs + SPI_DATA, *ms->tx_buf++); else
out_8(ms->regs + SPI_DATA, 0);
}
/* * IDLE state * * No transfers are in progress; if another transfer is pending then retrieve * it and kick it off. Otherwise, stop processing the state machine
*/ staticint
mpc52xx_spi_fsmstate_idle(int irq, struct mpc52xx_spi *ms, u8 status, u8 data)
{ struct spi_device *spi; int spr, sppr;
u8 ctrl1;
if (status && irq)
dev_err(&ms->host->dev, "spurious irq, status=0x%.2x\n",
status);
/* Check if there is another transfer waiting. */ if (list_empty(&ms->queue)) return FSM_STOP;
/* get the head of the queue */
ms->message = list_first_entry(&ms->queue, struct spi_message, queue);
list_del_init(&ms->message->queue);
/* * TRANSFER state * * In the middle of a transfer. If the SPI core has completed processing * a byte, then read out the received data and write out the next byte * (unless this transfer is finished; in which case go on to the wait * state)
*/ staticint mpc52xx_spi_fsmstate_transfer(int irq, struct mpc52xx_spi *ms,
u8 status, u8 data)
{ if (!status) return ms->irq0 ? FSM_STOP : FSM_POLL;
if (status & SPI_STATUS_WCOL) { /* The SPI controller is stoopid. At slower speeds, it may * raise the SPIF flag before the state machine is actually * finished, which causes a collision (internal to the state * machine only). The manual recommends inserting a delay * between receiving the interrupt and sending the next byte, * but it can also be worked around simply by retrying the
* transfer which is what we do here. */
ms->wcol_count++;
ms->wcol_ticks += mftb() - ms->wcol_tx_timestamp;
ms->wcol_tx_timestamp = mftb();
data = 0; if (ms->tx_buf)
data = *(ms->tx_buf - 1);
out_8(ms->regs + SPI_DATA, data); /* try again */ return FSM_CONTINUE;
} elseif (status & SPI_STATUS_MODF) {
ms->modf_count++;
dev_err(&ms->host->dev, "mode fault\n");
mpc52xx_spi_chipsel(ms, 0);
ms->message->status = -EIO; if (ms->message->complete)
ms->message->complete(ms->message->context);
ms->state = mpc52xx_spi_fsmstate_idle; return FSM_CONTINUE;
}
/* Read data out of the spi device */
ms->byte_count++; if (ms->rx_buf)
*ms->rx_buf++ = data;
/* Is the transfer complete? */
ms->len--; if (ms->len == 0) {
ms->timestamp = mftb(); if (ms->transfer->delay.unit == SPI_DELAY_UNIT_USECS)
ms->timestamp += ms->transfer->delay.value *
tb_ticks_per_usec;
ms->state = mpc52xx_spi_fsmstate_wait; return FSM_CONTINUE;
}
/* Write out the next byte */
ms->wcol_tx_timestamp = mftb(); if (ms->tx_buf)
out_8(ms->regs + SPI_DATA, *ms->tx_buf++); else
out_8(ms->regs + SPI_DATA, 0);
return FSM_CONTINUE;
}
/* * WAIT state * * A transfer has completed; need to wait for the delay period to complete * before starting the next transfer
*/ staticint
mpc52xx_spi_fsmstate_wait(int irq, struct mpc52xx_spi *ms, u8 status, u8 data)
{ if (status && irq)
dev_err(&ms->host->dev, "spurious irq, status=0x%.2x\n",
status);
if (((int)mftb()) - ms->timestamp < 0) return FSM_POLL;
ms->message->actual_length += ms->transfer->len;
/* Check if there is another transfer in this message. If there * aren't then deactivate CS, notify sender, and drop back to idle
* to start the next message. */ if (ms->transfer->transfer_list.next == &ms->message->transfers) {
ms->msg_count++;
mpc52xx_spi_chipsel(ms, 0);
ms->message->status = 0; if (ms->message->complete)
ms->message->complete(ms->message->context);
ms->state = mpc52xx_spi_fsmstate_idle; return FSM_CONTINUE;
}
/** * mpc52xx_spi_fsm_process - Finite State Machine iteration function * @irq: irq number that triggered the FSM or 0 for polling * @ms: pointer to mpc52xx_spi driver data
*/ staticvoid mpc52xx_spi_fsm_process(int irq, struct mpc52xx_spi *ms)
{ int rc = FSM_CONTINUE;
u8 status, data;
while (rc == FSM_CONTINUE) { /* Interrupt cleared by read of STATUS followed by
* read of DATA registers */
status = in_8(ms->regs + SPI_STATUS);
data = in_8(ms->regs + SPI_DATA);
rc = ms->state(irq, ms, status, data);
}
/* Clear the status register and re-read it to check for a MODF * failure. This driver cannot currently handle multiple hosts * on the SPI bus. This fault will also occur if the SPI signals
* are not connected to any pins (port_config setting) */
in_8(regs + SPI_STATUS);
out_8(regs + SPI_CTRL1, ctrl1);
for (i = 0; i < ms->gpio_cs_count; i++) {
gpio_cs = gpiod_get_index(&op->dev,
NULL, i, GPIOD_OUT_LOW);
rc = PTR_ERR_OR_ZERO(gpio_cs); if (rc) {
dev_err(&op->dev, "failed to get spi cs gpio #%d: %d\n",
i, rc); goto err_gpio;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.