mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-23 17:30:02 +00:00
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:
parent
bb8beecd98
commit
f6a6d96685
1 changed files with 186 additions and 51 deletions
|
@ -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);
|
||||
|
||||
|
|
Loading…
Reference in a new issue