commit: http://blackfin.uclinux.org/git/?p=linux-kernel;a=commitdiff;h=0a7f9952fbeb7d998f41db920118380a9043cc01 branch: http://blackfin.uclinux.org/git/?p=linux-kernel;a=shortlog;h=refs/heads/trunk
Signed-off-by: Scott Jiang <[email protected]> Signed-off-by: Bob Liu <[email protected]> --- arch/blackfin/include/asm/bfin6xx_spi.h | 3 + drivers/spi/spi-bfin6xx.c | 299 +++++++++++++++++++++++++------ 2 files changed, 248 insertions(+), 54 deletions(-) diff --git a/arch/blackfin/include/asm/bfin6xx_spi.h b/arch/blackfin/include/asm/bfin6xx_spi.h index 97d3c3c..89370b6 100644 --- a/arch/blackfin/include/asm/bfin6xx_spi.h +++ b/arch/blackfin/include/asm/bfin6xx_spi.h @@ -20,6 +20,8 @@ #ifndef _SPI_CHANNEL_H_ #define _SPI_CHANNEL_H_ +#include <linux/types.h> + /* SPI_CONTROL */ #define SPI_CTL_EN 0x00000001 /* Enable */ #define SPI_CTL_MSTR 0x00000002 /* Master/Slave */ @@ -250,6 +252,7 @@ struct bfin6xx_spi_chip { u32 control; u16 cs_chg_udelay; /* Some devices require 16-bit delays */ u32 tx_dummy_val; /* tx value for rx only transfer */ + bool enable_dma; }; #endif /* _SPI_CHANNEL_H_ */ diff --git a/drivers/spi/spi-bfin6xx.c b/drivers/spi/spi-bfin6xx.c index 8375db0..24335dd 100644 --- a/drivers/spi/spi-bfin6xx.c +++ b/drivers/spi/spi-bfin6xx.c @@ -19,6 +19,7 @@ #include <linux/delay.h> #include <linux/device.h> +#include <linux/dma-mapping.h> #include <linux/errno.h> #include <linux/init.h> #include <linux/interrupt.h> @@ -28,10 +29,12 @@ #include <linux/platform_device.h> #include <linux/slab.h> #include <linux/spi/spi.h> +#include <linux/types.h> #include <linux/workqueue.h> #include <asm/bfin6xx_spi.h> #include <asm/cacheflush.h> +#include <asm/dma.h> #include <asm/portmux.h> #define START_STATE ((void *)0) @@ -68,8 +71,8 @@ struct bfin_spi_master_data { struct work_struct pump_messages; spinlock_t lock; struct list_head queue; - int busy; - bool running; + bool busy; /* spi master is dealing with messages */ + bool running; /* spi master is ready */ /* Message Transfer pump */ struct tasklet_struct pump_transfers; @@ -78,18 +81,30 @@ struct bfin_spi_master_data { struct spi_message *cur_msg; struct spi_transfer *cur_transfer; struct bfin_spi_slave_data *cur_chip; - size_t len_in_bytes; + unsigned transfer_len; + unsigned cs_change; + + /* transfer buffer */ void *tx; void *tx_end; void *rx; void *rx_end; - u8 n_bytes; + /* dma info */ + unsigned int tx_dma; + unsigned int rx_dma; + dma_addr_t tx_dma_addr; + dma_addr_t rx_dma_addr; + unsigned long dummy_buffer; /* used in unidirectional transfer */ + unsigned long tx_dma_size; + unsigned long rx_dma_size; + int tx_num; + int rx_num; + /* store register value for suspend/resume */ u32 control; u32 ssel; - int cs_change; const struct bfin_spi_transfer_ops *ops; }; @@ -102,6 +117,7 @@ struct bfin_spi_slave_data { u16 cs_chg_udelay; /* Some devices require > 255usec delay */ u32 cs_gpio; u32 tx_dummy_val; /* tx value for rx only transfer */ + bool enable_dma; const struct bfin_spi_transfer_ops *ops; }; @@ -195,6 +211,7 @@ static void bfin_spi_restore_state(struct bfin_spi_master_data *drv_data) bfin_write(&drv_data->regs->clock, chip->clock); bfin_spi_enable(drv_data); + drv_data->tx_num = drv_data->rx_num = 0; /* we always choose tx transfer initiate */ bfin_write(&drv_data->regs->rx_control, SPI_RXCTL_REN); bfin_write(&drv_data->regs->tx_control, @@ -389,8 +406,8 @@ static void bfin_spi_pump_transfers(unsigned long data) struct bfin_spi_slave_data *chip = NULL; unsigned int bits_per_word; u32 cr, cr_width; - u32 tranf_success = 1; - u8 full_duplex = 0; + bool tranf_success = true; + bool full_duplex = false; /* Get current state information */ message = drv_data->cur_msg; @@ -435,7 +452,8 @@ static void bfin_spi_pump_transfers(unsigned long data) return; } - if (transfer->len == 0) { + if ((transfer->len == 0) || (transfer->tx_buf == NULL + && transfer->rx_buf == NULL)) { /* Move to next transfer of this msg */ message->state = bfin_spi_next_transfer(drv_data); /* Schedule next transfer tasklet */ @@ -453,7 +471,7 @@ static void bfin_spi_pump_transfers(unsigned long data) } if (transfer->rx_buf != NULL) { - full_duplex = transfer->tx_buf != NULL; + full_duplex = (transfer->tx_buf != NULL) ? true : false; drv_data->rx = transfer->rx_buf; drv_data->rx_end = drv_data->rx + transfer->len; dev_dbg(&drv_data->pdev->dev, "rx_buf is %p, rx_end is %p\n", @@ -462,13 +480,12 @@ static void bfin_spi_pump_transfers(unsigned long data) drv_data->rx = NULL; } - drv_data->len_in_bytes = transfer->len; + drv_data->transfer_len = transfer->len; drv_data->cs_change = transfer->cs_change; /* Bits per word setup */ bits_per_word = transfer->bits_per_word ? : message->spi->bits_per_word ? : 8; - drv_data->n_bytes = bits_per_word/8; switch (bits_per_word) { case 8: cr_width = SPI_CTL_SIZE08; @@ -507,26 +524,121 @@ static void bfin_spi_pump_transfers(unsigned long data) "now pumping a transfer: width is %d, len is %d\n", bits_per_word, transfer->len); + if (chip->enable_dma) { + u32 dma_config; + unsigned long word_count, word_size; + void *tx_buf, *rx_buf; + + switch (bits_per_word) { + case 8: + dma_config = WDSIZE_8 | PSIZE_8; + word_count = drv_data->transfer_len; + word_size = 1; + break; + case 16: + dma_config = WDSIZE_16 | PSIZE_16; + word_count = drv_data->transfer_len / 2; + word_size = 2; + break; + default: + dma_config = WDSIZE_32 | PSIZE_32; + word_count = drv_data->transfer_len / 4; + word_size = 4; + break; + } + + if (full_duplex) { + WARN_ON((drv_data->tx_end - drv_data->tx) + != (drv_data->rx_end - drv_data->rx)); + tx_buf = drv_data->tx; + rx_buf = drv_data->rx; + drv_data->tx_dma_size = drv_data->rx_dma_size + = drv_data->transfer_len; + set_dma_x_modify(drv_data->tx_dma, word_size); + set_dma_x_modify(drv_data->rx_dma, word_size); + } else if (drv_data->tx) { + tx_buf = drv_data->tx; + rx_buf = &drv_data->dummy_buffer; + drv_data->tx_dma_size = drv_data->transfer_len; + drv_data->rx_dma_size = sizeof(drv_data->dummy_buffer); + set_dma_x_modify(drv_data->tx_dma, word_size); + set_dma_x_modify(drv_data->rx_dma, 0); + } else { + drv_data->dummy_buffer = chip->tx_dummy_val; + tx_buf = &drv_data->dummy_buffer; + rx_buf = drv_data->rx; + drv_data->tx_dma_size = sizeof(drv_data->dummy_buffer); + drv_data->rx_dma_size = drv_data->transfer_len; + set_dma_x_modify(drv_data->tx_dma, 0); + set_dma_x_modify(drv_data->rx_dma, word_size); + } + + drv_data->tx_dma_addr = dma_map_single(&message->spi->dev, + (void *)tx_buf, + drv_data->tx_dma_size, + DMA_TO_DEVICE); + if (dma_mapping_error(&message->spi->dev, + drv_data->tx_dma_addr)) { + dev_dbg(&drv_data->pdev->dev, "Unable to map TX DMA\n"); + message->state = ERROR_STATE; + return; + } + + drv_data->rx_dma_addr = dma_map_single(&message->spi->dev, + (void *)rx_buf, + drv_data->rx_dma_size, + DMA_FROM_DEVICE); + if (dma_mapping_error(&message->spi->dev, + drv_data->rx_dma_addr)) { + dev_dbg(&drv_data->pdev->dev, "Unable to map RX DMA\n"); + message->state = ERROR_STATE; + dma_unmap_single(&message->spi->dev, + drv_data->tx_dma_addr, + drv_data->tx_dma_size, + DMA_TO_DEVICE); + return; + } + + dummy_read(drv_data); + set_dma_x_count(drv_data->tx_dma, word_count); + set_dma_x_count(drv_data->rx_dma, word_count); + set_dma_start_addr(drv_data->tx_dma, drv_data->tx_dma_addr); + set_dma_start_addr(drv_data->rx_dma, drv_data->rx_dma_addr); + dma_config |= DMAFLOW_STOP | RESTART | DI_EN; + set_dma_config(drv_data->tx_dma, dma_config); + set_dma_config(drv_data->rx_dma, dma_config | WNR); + enable_dma(drv_data->tx_dma); + enable_dma(drv_data->rx_dma); + SSYNC(); + + bfin_write(&drv_data->regs->rx_control, SPI_RXCTL_REN | SPI_RXCTL_RDR_NE); + SSYNC(); + bfin_write(&drv_data->regs->tx_control, + SPI_TXCTL_TEN | SPI_TXCTL_TTI | SPI_TXCTL_TDR_NF); + + return; + } + if (full_duplex) { /* full duplex mode */ - BUG_ON((drv_data->tx_end - drv_data->tx) != - (drv_data->rx_end - drv_data->rx)); + WARN_ON((drv_data->tx_end - drv_data->tx) + != (drv_data->rx_end - drv_data->rx)); drv_data->ops->duplex(drv_data); if (drv_data->tx != drv_data->tx_end) - tranf_success = 0; + tranf_success = false; } else if (drv_data->tx != NULL) { /* write only half duplex */ drv_data->ops->write(drv_data); if (drv_data->tx != drv_data->tx_end) - tranf_success = 0; - } else if (drv_data->rx != NULL) { + tranf_success = false; + } else { /* read only half duplex */ drv_data->ops->read(drv_data); if (drv_data->rx != drv_data->rx_end) - tranf_success = 0; + tranf_success = false; } if (!tranf_success) { @@ -534,7 +646,7 @@ static void bfin_spi_pump_transfers(unsigned long data) message->state = ERROR_STATE; } else { /* Update total byte transferred */ - message->actual_length += drv_data->len_in_bytes; + message->actual_length += drv_data->transfer_len; /* Move to next transfer of this msg */ message->state = bfin_spi_next_transfer(drv_data); if (drv_data->cs_change && message->state != DONE_STATE) { @@ -559,7 +671,7 @@ static void bfin_spi_pump_messages(struct work_struct *work) spin_lock_irqsave(&drv_data->lock, flags); if (list_empty(&drv_data->queue) || !drv_data->running) { /* pumper kicked off but no work to do */ - drv_data->busy = 0; + drv_data->busy = false; spin_unlock_irqrestore(&drv_data->lock, flags); return; } @@ -593,7 +705,7 @@ static void bfin_spi_pump_messages(struct work_struct *work) /* Mark as busy and launch transfers */ tasklet_schedule(&drv_data->pump_transfers); - drv_data->busy = 1; + drv_data->busy = true; spin_unlock_irqrestore(&drv_data->lock, flags); } @@ -680,6 +792,7 @@ static int bfin_spi_setup(struct spi_device *spi) chip->control = chip_info->control; chip->cs_chg_udelay = chip_info->cs_chg_udelay; chip->tx_dummy_val = chip_info->tx_dummy_val; + chip->enable_dma = chip_info->enable_dma; } else { /* force a default base state */ chip->control &= bfin_ctl_reg; @@ -712,9 +825,16 @@ static int bfin_spi_setup(struct spi_device *spi) else chip->cs_gpio = chip->chip_select_num - MAX_CTRL_CS; - if (chip->chip_select_num >= MAX_CTRL_CS) { - /* Only request on first setup */ - if (spi_get_ctldata(spi) == NULL) { + /* Only request on first setup */ + if (spi_get_ctldata(spi) == NULL) { + if (chip->chip_select_num < MAX_CTRL_CS) { + ret = peripheral_request(ssel[spi->master->bus_num] + [chip->chip_select_num-1], spi->modalias); + if (ret) { + dev_err(&spi->dev, "peripheral_request() error\n"); + goto pin_error; + } + } else { ret = gpio_request(chip->cs_gpio, spi->modalias); if (ret) { dev_err(&spi->dev, "gpio_request() error\n"); @@ -724,15 +844,6 @@ static int bfin_spi_setup(struct spi_device *spi) } } - if (chip->chip_select_num < MAX_CTRL_CS) { - ret = peripheral_request(ssel[spi->master->bus_num] - [chip->chip_select_num-1], spi->modalias); - if (ret) { - dev_err(&spi->dev, "peripheral_request() error\n"); - goto pin_error; - } - } - dev_dbg(&spi->dev, "setup spi chip %s, width is %d\n", spi->modalias, spi->bits_per_word); dev_dbg(&spi->dev, "control is 0x%x, ssel is 0x%x\n", @@ -746,13 +857,13 @@ static int bfin_spi_setup(struct spi_device *spi) return 0; - pin_error: - if (chip->chip_select_num >= MAX_CTRL_CS) - gpio_free(chip->cs_gpio); - else +pin_error: + if (chip->chip_select_num < MAX_CTRL_CS) peripheral_free(ssel[spi->master->bus_num] - [chip->chip_select_num - 1]); - error: + [chip->chip_select_num - 1]); + else + gpio_free(chip->cs_gpio); +error: if (chip) { kfree(chip); /* prevent free 'chip' twice */ @@ -792,7 +903,7 @@ static int bfin_spi_init_queue(struct bfin_spi_master_data *drv_data) spin_lock_init(&drv_data->lock); drv_data->running = false; - drv_data->busy = 0; + drv_data->busy = false; /* init transfer tasklet */ tasklet_init(&drv_data->pump_transfers, @@ -872,6 +983,51 @@ static int bfin_spi_destroy_queue(struct bfin_spi_master_data *drv_data) return 0; } +static irqreturn_t bfin_spi_tx_dma_isr(int irq, void *dev_id) +{ + struct bfin_spi_master_data *drv_data = dev_id; + u32 dma_stat = get_dma_curr_irqstat(drv_data->tx_dma); + + clear_dma_irqstat(drv_data->tx_dma); + if (dma_stat & DMA_DONE) + drv_data->tx_num++; + if (dma_stat & DMA_ERR) + dev_err(&drv_data->pdev->dev, + "spi tx dma error: %d\n", dma_stat); + bfin_write_and(&drv_data->regs->tx_control, ~SPI_TXCTL_TDR_NF); + return IRQ_HANDLED; +} + +static irqreturn_t bfin_spi_rx_dma_isr(int irq, void *dev_id) +{ + struct bfin_spi_master_data *drv_data = dev_id; + struct bfin_spi_slave_data *chip = drv_data->cur_chip; + struct spi_message *msg = drv_data->cur_msg; + u32 dma_stat = get_dma_curr_irqstat(drv_data->rx_dma); + + clear_dma_irqstat(drv_data->rx_dma); + if (dma_stat & DMA_DONE) + drv_data->rx_num++; + if (dma_stat & DMA_ERR) { + msg->state = ERROR_STATE; + dev_err(&drv_data->pdev->dev, + "spi rx dma error: %d\n", dma_stat); + } else { + msg->actual_length += drv_data->transfer_len; + if (drv_data->cs_change) + bfin_spi_cs_deactive(drv_data, chip); + msg->state = bfin_spi_next_transfer(drv_data); + } + bfin_write(&drv_data->regs->tx_control, 0); + bfin_write(&drv_data->regs->rx_control, 0); + if (drv_data->rx_num != drv_data->tx_num) + dev_dbg(&drv_data->pdev->dev, + "dma interrupt missing: tx=%d,rx=%d\n", + drv_data->tx_num, drv_data->rx_num); + tasklet_schedule(&drv_data->pump_transfers); + return IRQ_HANDLED; +} + static int __devinit bfin_spi_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -909,7 +1065,7 @@ static int __devinit bfin_spi_probe(struct platform_device *pdev) res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (res == NULL) { dev_err(dev, "Cannot get IORESOURCE_MEM\n"); - status = -ENOENT; + status = -ENODEV; goto err_put_master; } @@ -920,11 +1076,48 @@ static int __devinit bfin_spi_probe(struct platform_device *pdev) goto err_put_master; } + res = platform_get_resource(pdev, IORESOURCE_DMA, 0); + if (res == NULL) { + dev_err(dev, "Cannot get tx dma resource\n"); + status = -ENODEV; + goto err_iounmap; + } + drv_data->tx_dma = res->start; + + res = platform_get_resource(pdev, IORESOURCE_DMA, 1); + if (res == NULL) { + dev_err(dev, "Cannot get rx dma resource\n"); + status = -ENODEV; + goto err_iounmap; + } + drv_data->rx_dma = res->start; + + status = request_dma(drv_data->tx_dma, "SPI_TX_DMA"); + if (status) { + dev_err(dev, "Unable to request SPI TX DMA channel\n"); + goto err_iounmap; + } + set_dma_callback(drv_data->tx_dma, bfin_spi_tx_dma_isr, drv_data); + + status = request_dma(drv_data->rx_dma, "SPI_RX_DMA"); + if (status) { + dev_err(dev, "Unable to request SPI RX DMA channel\n"); + goto err_free_tx_dma; + } + set_dma_callback(drv_data->rx_dma, bfin_spi_rx_dma_isr, drv_data); + + /* request CLK, MOSI and MISO */ + status = peripheral_request_list(drv_data->pin_req, "bfin-spi"); + if (status != 0) { + dev_err(&pdev->dev, ": Requesting Peripherals failed\n"); + goto err_free_rx_dma; + } + /* Initial and start queue */ status = bfin_spi_init_queue(drv_data); if (status != 0) { dev_err(dev, "problem initializing queue\n"); - goto err_destroy_queue; + goto err_free_peripheral; } status = bfin_spi_start_queue(drv_data); @@ -933,12 +1126,6 @@ static int __devinit bfin_spi_probe(struct platform_device *pdev) goto err_destroy_queue; } - status = peripheral_request_list(drv_data->pin_req, "bfin-spi"); - if (status != 0) { - dev_err(&pdev->dev, ": Requesting Peripherals failed\n"); - goto err_destroy_queue; - } - bfin_write(&drv_data->regs->control, SPI_CTL_MSTR | SPI_CTL_CPHA); bfin_write(&drv_data->regs->ssel, 0x0000FE00); bfin_write(&drv_data->regs->delay, 0x0); @@ -954,10 +1141,15 @@ static int __devinit bfin_spi_probe(struct platform_device *pdev) dev_info(dev, "bfin-spi probe success\n"); return status; -err_free_peripheral: - peripheral_free_list(drv_data->pin_req); err_destroy_queue: bfin_spi_destroy_queue(drv_data); +err_free_peripheral: + peripheral_free_list(drv_data->pin_req); +err_free_rx_dma: + free_dma(drv_data->rx_dma); +err_free_tx_dma: + free_dma(drv_data->tx_dma); +err_iounmap: iounmap(drv_data->regs); err_put_master: spi_master_put(master); @@ -969,25 +1161,24 @@ err_put_master: static int __devexit bfin_spi_remove(struct platform_device *pdev) { struct bfin_spi_master_data *drv_data = platform_get_drvdata(pdev); - int status = 0; if (!drv_data) return 0; /* Remove the queue */ - status = bfin_spi_destroy_queue(drv_data); - if (status != 0) - return status; + bfin_spi_destroy_queue(drv_data); /* Disable the SSP at the peripheral and SOC level */ bfin_spi_disable(drv_data); peripheral_free_list(drv_data->pin_req); + free_dma(drv_data->rx_dma); + free_dma(drv_data->tx_dma); iounmap(drv_data->regs); /* Disconnect from the SPI framework */ spi_unregister_master(drv_data->master); - + spi_master_put(drv_data->master); /* Prevent double remove */ platform_set_drvdata(pdev, NULL);
_______________________________________________ Linux-kernel-commits mailing list [email protected] https://blackfin.uclinux.org/mailman/listinfo/linux-kernel-commits
