spi/bfin_spi: utilize the SPI interrupt in PIO mode

The current behavior in PIO mode is to poll the SPI status registers which
can obviously lead to higher latencies when doing a lot of SPI traffic.
There is a SPI interrupt which can be used instead to signal individual
completion of transactions.

Signed-off-by: Yi Li <yi.li@analog.com>
Signed-off-by: Mike Frysinger <vapier@gentoo.org>
This commit is contained in:
Yi Li 2009-06-03 09:46:22 +00:00 committed by Mike Frysinger
parent bb8beecd98
commit f6a6d96685
1 changed files with 186 additions and 51 deletions

View File

@ -92,6 +92,9 @@ struct driver_data {
dma_addr_t rx_dma;
dma_addr_t tx_dma;
int irq_requested;
int spi_irq;
size_t rx_map_len;
size_t tx_map_len;
u8 n_bytes;
@ -115,6 +118,7 @@ struct chip_data {
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 *);
@ -525,6 +529,79 @@ static void bfin_spi_giveback(struct driver_data *drv_data)
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(drv_data->spi_irq);
/* 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;
@ -700,6 +777,7 @@ static void bfin_spi_pump_transfers(unsigned long data)
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;
@ -842,60 +920,86 @@ static void bfin_spi_pump_transfers(unsigned long data)
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);
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;
/* 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 (!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);
/* once TDBR is empty, interrupt is triggered */
enable_irq(drv_data->spi_irq);
return;
}
/* IO mode */
dev_dbg(&drv_data->pdev->dev, "doing IO transfer\n");
/* 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));
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);
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 */
@ -1047,6 +1151,7 @@ static int bfin_spi_setup(struct spi_device *spi)
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 */
@ -1096,6 +1201,11 @@ static int bfin_spi_setup(struct spi_device *spi)
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
@ -1119,6 +1229,18 @@ static int bfin_spi_setup(struct spi_device *spi)
dma_disable_irq(drv_data->dma_channel);
}
if (chip->pio_interrupt && !drv_data->irq_requested) {
ret = request_irq(drv_data->spi_irq, bfin_spi_pio_irq_handler,
IRQF_DISABLED, "BFIN_SPI", drv_data);
if (ret) {
dev_err(&spi->dev, "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(drv_data->spi_irq);
}
if (chip->chip_select_num == 0) {
ret = gpio_request(chip->cs_gpio, spi->modalias);
if (ret) {
@ -1328,11 +1450,19 @@ static int __init bfin_spi_probe(struct platform_device *pdev)
goto out_error_ioremap;
}
drv_data->dma_channel = platform_get_irq(pdev, 0);
if (drv_data->dma_channel < 0) {
res = platform_get_resource(pdev, IORESOURCE_DMA, 0);
if (res == NULL) {
dev_err(dev, "No DMA channel specified\n");
status = -ENOENT;
goto out_error_no_dma_ch;
goto out_error_free_io;
}
drv_data->dma_channel = res->start;
drv_data->spi_irq = platform_get_irq(pdev, 0);
if (drv_data->spi_irq < 0) {
dev_err(dev, "No spi pio irq specified\n");
status = -ENOENT;
goto out_error_free_io;
}
/* Initial and start queue */
@ -1375,7 +1505,7 @@ static int __init bfin_spi_probe(struct platform_device *pdev)
out_error_queue_alloc:
bfin_spi_destroy_queue(drv_data);
out_error_no_dma_ch:
out_error_free_io:
iounmap((void *) drv_data->regs_base);
out_error_ioremap:
out_error_get_res:
@ -1407,6 +1537,11 @@ static int __devexit bfin_spi_remove(struct platform_device *pdev)
free_dma(drv_data->dma_channel);
}
if (drv_data->irq_requested) {
free_irq(drv_data->spi_irq, drv_data);
drv_data->irq_requested = 0;
}
/* Disconnect from the SPI framework */
spi_unregister_master(drv_data->master);