Skip to content

Commit

Permalink
spi/bfin_spi: utilize the SPI interrupt in PIO mode
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Yi Li authored and Mike Frysinger committed Oct 18, 2010
1 parent bb8beec commit f6a6d96
Showing 1 changed file with 185 additions and 50 deletions.
235 changes: 185 additions & 50 deletions drivers/spi/spi_bfin5xx.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 *);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);

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);
/* discard old RX data and clear RXS */
bfin_spi_dummy_read(drv_data);

drv_data->write(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->rx != NULL) {
/* read only half duplex */
dev_dbg(&drv_data->pdev->dev,
"IO read: cr is 0x%x\n", cr);
/* once TDBR is empty, interrupt is triggered */
enable_irq(drv_data->spi_irq);
return;
}

drv_data->read(drv_data);
if (drv_data->rx != drv_data->rx_end)
tranf_success = 0;
}
/* 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);
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 */
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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
Expand All @@ -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) {
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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);

Expand Down

0 comments on commit f6a6d96

Please sign in to comment.