Modified: trunk/drivers/spi/spi_bfin5xx.c (6552 => 6553)
--- trunk/drivers/spi/spi_bfin5xx.c 2009-06-03 09:45:45 UTC (rev 6552)
+++ trunk/drivers/spi/spi_bfin5xx.c 2009-06-03 09:46:22 UTC (rev 6553)
@@ -96,6 +96,8 @@
dma_addr_t rx_dma;
dma_addr_t tx_dma;
+ int irq_requested;
+
size_t rx_map_len;
size_t tx_map_len;
u8 n_bytes;
@@ -119,6 +121,7 @@
u16 cs_chg_udelay; /* Some devices require > 255usec delay */
u32 cs_gpio;
u16 idle_tx_val;
+ u8 pio_interrupt; /* use spi data irq */
void (*write) (struct driver_data *);
void (*read) (struct driver_data *);
void (*duplex) (struct driver_data *);
@@ -529,6 +532,79 @@
msg->complete(msg->context);
}
+/* spi data irq handler */
+static irqreturn_t bfin_spi_pio_irq_handler(int irq, void *dev_id)
+{
+ struct driver_data *drv_data = dev_id;
+ struct chip_data *chip = drv_data->cur_chip;
+ struct spi_message *msg = drv_data->cur_msg;
+ int n_bytes = drv_data->n_bytes;
+
+ /* wait until transfer finished. */
+ while (!(read_STAT(drv_data) & BIT_STAT_RXS))
+ cpu_relax();
+
+ if ((drv_data->tx && drv_data->tx >= drv_data->tx_end) ||
+ (drv_data->rx && drv_data->rx >= (drv_data->rx_end - n_bytes))) {
+ /* last read */
+ if (drv_data->rx) {
+ dev_dbg(&drv_data->pdev->dev, "last read\n");
+ if (n_bytes == 2)
+ *(u16 *) (drv_data->rx) = read_RDBR(drv_data);
+ else if (n_bytes == 1)
+ *(u8 *) (drv_data->rx) = read_RDBR(drv_data);
+ drv_data->rx += n_bytes;
+ }
+
+ msg->actual_length += drv_data->len_in_bytes;
+ if (drv_data->cs_change)
+ bfin_spi_cs_deactive(drv_data, chip);
+ /* Move to next transfer */
+ msg->state = bfin_spi_next_transfer(drv_data);
+
+ disable_irq(IRQ_SPI);
+
+ /* Schedule transfer tasklet */
+ tasklet_schedule(&drv_data->pump_transfers);
+ return IRQ_HANDLED;
+ }
+
+ if (drv_data->rx && drv_data->tx) {
+ /* duplex */
+ dev_dbg(&drv_data->pdev->dev, "duplex: write_TDBR\n");
+ if (drv_data->n_bytes == 2) {
+ *(u16 *) (drv_data->rx) = read_RDBR(drv_data);
+ write_TDBR(drv_data, (*(u16 *) (drv_data->tx)));
+ } else if (drv_data->n_bytes == 1) {
+ *(u8 *) (drv_data->rx) = read_RDBR(drv_data);
+ write_TDBR(drv_data, (*(u8 *) (drv_data->tx)));
+ }
+ } else if (drv_data->rx) {
+ /* read */
+ dev_dbg(&drv_data->pdev->dev, "read: write_TDBR\n");
+ if (drv_data->n_bytes == 2)
+ *(u16 *) (drv_data->rx) = read_RDBR(drv_data);
+ else if (drv_data->n_bytes == 1)
+ *(u8 *) (drv_data->rx) = read_RDBR(drv_data);
+ write_TDBR(drv_data, chip->idle_tx_val);
+ } else if (drv_data->tx) {
+ /* write */
+ dev_dbg(&drv_data->pdev->dev, "write: write_TDBR\n");
+ bfin_spi_dummy_read(drv_data);
+ if (drv_data->n_bytes == 2)
+ write_TDBR(drv_data, (*(u16 *) (drv_data->tx)));
+ else if (drv_data->n_bytes == 1)
+ write_TDBR(drv_data, (*(u8 *) (drv_data->tx)));
+ }
+
+ if (drv_data->tx)
+ drv_data->tx += n_bytes;
+ if (drv_data->rx)
+ drv_data->rx += n_bytes;
+
+ return IRQ_HANDLED;
+}
+
static irqreturn_t bfin_spi_dma_irq_handler(int irq, void *dev_id)
{
struct driver_data *drv_data = dev_id;
@@ -704,6 +780,7 @@
default:
/* No change, the same as default setting */
+ transfer->bits_per_word = chip->bits_per_word;
drv_data->n_bytes = chip->n_bytes;
width = chip->width;
drv_data->write = drv_data->tx ? chip->write : bfin_spi_null_writer;
@@ -846,60 +923,86 @@
dma_enable_irq(drv_data->dma_channel);
local_irq_restore(flags);
- } else {
- /* IO mode write then read */
- dev_dbg(&drv_data->pdev->dev, "doing IO transfer\n");
+ return;
+ }
- /* we always use SPI_WRITE mode. SPI_READ mode
- seems to have problems with setting up the
- output value in TDBR prior to the transfer. */
+ if (chip->pio_interrupt) {
+ /* use write mode. spi irq should have been disabled */
+ cr = (read_CTRL(drv_data) & (~BIT_CTL_TIMOD));
write_CTRL(drv_data, (cr | CFG_SPI_WRITE));
- if (full_duplex) {
- /* full duplex mode */
- BUG_ON((drv_data->tx_end - drv_data->tx) !=
- (drv_data->rx_end - drv_data->rx));
- dev_dbg(&drv_data->pdev->dev,
- "IO duplex: cr is 0x%x\n", cr);
+ /* discard old RX data and clear RXS */
+ bfin_spi_dummy_read(drv_data);
- drv_data->duplex(drv_data);
+ /* start transfer */
+ if (drv_data->tx == NULL)
+ write_TDBR(drv_data, chip->idle_tx_val);
+ else {
+ if (transfer->bits_per_word == 8)
+ write_TDBR(drv_data, (*(u8 *) (drv_data->tx)));
+ else if (transfer->bits_per_word == 16)
+ write_TDBR(drv_data, (*(u16 *) (drv_data->tx)));
+ drv_data->tx += drv_data->n_bytes;
+ }
- if (drv_data->tx != drv_data->tx_end)
- tranf_success = 0;
- } else if (drv_data->tx != NULL) {
- /* write only half duplex */
- dev_dbg(&drv_data->pdev->dev,
- "IO write: cr is 0x%x\n", cr);
+ /* once TDBR is empty, interrupt is triggered */
+ enable_irq(IRQ_SPI);
+ return;
+ }
- drv_data->write(drv_data);
+ /* IO mode */
+ dev_dbg(&drv_data->pdev->dev, "doing IO transfer\n");
- if (drv_data->tx != drv_data->tx_end)
- tranf_success = 0;
- } else if (drv_data->rx != NULL) {
- /* read only half duplex */
- dev_dbg(&drv_data->pdev->dev,
- "IO read: cr is 0x%x\n", cr);
+ /* we always use SPI_WRITE mode. SPI_READ mode
+ seems to have problems with setting up the
+ output value in TDBR prior to the transfer. */
+ write_CTRL(drv_data, (cr | CFG_SPI_WRITE));
- drv_data->read(drv_data);
- if (drv_data->rx != drv_data->rx_end)
- tranf_success = 0;
- }
+ if (full_duplex) {
+ /* full duplex mode */
+ BUG_ON((drv_data->tx_end - drv_data->tx) !=
+ (drv_data->rx_end - drv_data->rx));
+ dev_dbg(&drv_data->pdev->dev,
+ "IO duplex: cr is 0x%x\n", cr);
- if (!tranf_success) {
- dev_dbg(&drv_data->pdev->dev,
- "IO write error!\n");
- message->state = ERROR_STATE;
- } else {
- /* Update total byte transfered */
- message->actual_length += drv_data->len_in_bytes;
- /* Move to next transfer of this msg */
- message->state = bfin_spi_next_transfer(drv_data);
- if (drv_data->cs_change)
- bfin_spi_cs_deactive(drv_data, chip);
- }
- /* Schedule next transfer tasklet */
- tasklet_schedule(&drv_data->pump_transfers);
+ drv_data->duplex(drv_data);
+
+ if (drv_data->tx != drv_data->tx_end)
+ tranf_success = 0;
+ } else if (drv_data->tx != NULL) {
+ /* write only half duplex */
+ dev_dbg(&drv_data->pdev->dev,
+ "IO write: cr is 0x%x\n", cr);
+
+ drv_data->write(drv_data);
+
+ if (drv_data->tx != drv_data->tx_end)
+ tranf_success = 0;
+ } else if (drv_data->rx != NULL) {
+ /* read only half duplex */
+ dev_dbg(&drv_data->pdev->dev,
+ "IO read: cr is 0x%x\n", cr);
+
+ drv_data->read(drv_data);
+ if (drv_data->rx != drv_data->rx_end)
+ tranf_success = 0;
}
+
+ if (!tranf_success) {
+ dev_dbg(&drv_data->pdev->dev,
+ "IO write error!\n");
+ message->state = ERROR_STATE;
+ } else {
+ /* Update total byte transfered */
+ message->actual_length += drv_data->len_in_bytes;
+ /* Move to next transfer of this msg */
+ message->state = bfin_spi_next_transfer(drv_data);
+ if (drv_data->cs_change)
+ bfin_spi_cs_deactive(drv_data, chip);
+ }
+
+ /* Schedule next transfer tasklet */
+ tasklet_schedule(&drv_data->pump_transfers);
}
/* pop a msg from queue and kick off real transfer */
@@ -1130,6 +1233,7 @@
chip->cs_chg_udelay = chip_info->cs_chg_udelay;
chip->cs_gpio = chip_info->cs_gpio;
chip->idle_tx_val = chip_info->idle_tx_val;
+ chip->pio_interrupt = chip_info->pio_interrupt;
}
/* translate common spi framework into our register */
@@ -1179,6 +1283,11 @@
goto error;
}
+ if (chip->enable_dma && chip->pio_interrupt) {
+ dev_err(&spi->dev, "enable_dma is set, "
+ "do not set pio_interrupt\n");
+ goto error;
+ }
/*
* if any one SPI chip is registered and wants DMA, request the
* DMA channel for it
@@ -1202,6 +1311,18 @@
dma_disable_irq(drv_data->dma_channel);
}
+ if (chip->pio_interrupt && !drv_data->irq_requested) {
+ ret = request_irq(IRQ_SPI, bfin_spi_pio_irq_handler,
+ IRQF_DISABLED, "BFIN_SPI", drv_data);
+ if (ret) {
+ printk(KERN_NOTICE "Unable to register spi IRQ\n");
+ goto error;
+ }
+ drv_data->irq_requested = 1;
+ /* we use write mode, spi irq has to be disabled here */
+ disable_irq(IRQ_SPI);
+ }
+
if (chip->chip_select_num == 0) {
ret = gpio_request(chip->cs_gpio, spi->modalias);
if (ret)
@@ -1479,6 +1600,11 @@
free_dma(drv_data->dma_channel);
}
+ if (drv_data->irq_requested) {
+ free_irq(IRQ_SPI, drv_data);
+ drv_data->irq_requested = 0;
+ }
+
/* Disconnect from the SPI framework */
spi_unregister_master(drv_data->master);