| /* |
| * drivers/spi/spi_imx.c |
| * |
| * Copyright (C) 2006 SWAPP |
| * Andrea Paterniani <a.paterniani@swapp-eng.it> |
| * |
| * Initial version inspired by: |
| * linux-2.6.17-rc3-mm1/drivers/spi/pxa2xx_spi.c |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| */ |
| |
| #include <linux/init.h> |
| #include <linux/module.h> |
| #include <linux/device.h> |
| #include <linux/ioport.h> |
| #include <linux/errno.h> |
| #include <linux/interrupt.h> |
| #include <linux/platform_device.h> |
| #include <linux/dma-mapping.h> |
| #include <linux/spi/spi.h> |
| #include <linux/workqueue.h> |
| #include <linux/delay.h> |
| |
| #include <asm/io.h> |
| #include <asm/irq.h> |
| #include <asm/hardware.h> |
| #include <asm/delay.h> |
| |
| #include <asm/arch/hardware.h> |
| #include <asm/arch/imx-dma.h> |
| #include <asm/arch/spi_imx.h> |
| |
| /*-------------------------------------------------------------------------*/ |
| /* SPI Registers offsets from peripheral base address */ |
| #define SPI_RXDATA (0x00) |
| #define SPI_TXDATA (0x04) |
| #define SPI_CONTROL (0x08) |
| #define SPI_INT_STATUS (0x0C) |
| #define SPI_TEST (0x10) |
| #define SPI_PERIOD (0x14) |
| #define SPI_DMA (0x18) |
| #define SPI_RESET (0x1C) |
| |
| /* SPI Control Register Bit Fields & Masks */ |
| #define SPI_CONTROL_BITCOUNT_MASK (0xF) /* Bit Count Mask */ |
| #define SPI_CONTROL_BITCOUNT(n) (((n) - 1) & SPI_CONTROL_BITCOUNT_MASK) |
| #define SPI_CONTROL_POL (0x1 << 4) /* Clock Polarity Mask */ |
| #define SPI_CONTROL_POL_ACT_HIGH (0x0 << 4) /* Active high pol. (0=idle) */ |
| #define SPI_CONTROL_POL_ACT_LOW (0x1 << 4) /* Active low pol. (1=idle) */ |
| #define SPI_CONTROL_PHA (0x1 << 5) /* Clock Phase Mask */ |
| #define SPI_CONTROL_PHA_0 (0x0 << 5) /* Clock Phase 0 */ |
| #define SPI_CONTROL_PHA_1 (0x1 << 5) /* Clock Phase 1 */ |
| #define SPI_CONTROL_SSCTL (0x1 << 6) /* /SS Waveform Select Mask */ |
| #define SPI_CONTROL_SSCTL_0 (0x0 << 6) /* Master: /SS stays low between SPI burst |
| Slave: RXFIFO advanced by BIT_COUNT */ |
| #define SPI_CONTROL_SSCTL_1 (0x1 << 6) /* Master: /SS insert pulse between SPI burst |
| Slave: RXFIFO advanced by /SS rising edge */ |
| #define SPI_CONTROL_SSPOL (0x1 << 7) /* /SS Polarity Select Mask */ |
| #define SPI_CONTROL_SSPOL_ACT_LOW (0x0 << 7) /* /SS Active low */ |
| #define SPI_CONTROL_SSPOL_ACT_HIGH (0x1 << 7) /* /SS Active high */ |
| #define SPI_CONTROL_XCH (0x1 << 8) /* Exchange */ |
| #define SPI_CONTROL_SPIEN (0x1 << 9) /* SPI Module Enable */ |
| #define SPI_CONTROL_MODE (0x1 << 10) /* SPI Mode Select Mask */ |
| #define SPI_CONTROL_MODE_SLAVE (0x0 << 10) /* SPI Mode Slave */ |
| #define SPI_CONTROL_MODE_MASTER (0x1 << 10) /* SPI Mode Master */ |
| #define SPI_CONTROL_DRCTL (0x3 << 11) /* /SPI_RDY Control Mask */ |
| #define SPI_CONTROL_DRCTL_0 (0x0 << 11) /* Ignore /SPI_RDY */ |
| #define SPI_CONTROL_DRCTL_1 (0x1 << 11) /* /SPI_RDY falling edge triggers input */ |
| #define SPI_CONTROL_DRCTL_2 (0x2 << 11) /* /SPI_RDY active low level triggers input */ |
| #define SPI_CONTROL_DATARATE (0x7 << 13) /* Data Rate Mask */ |
| #define SPI_PERCLK2_DIV_MIN (0) /* PERCLK2:4 */ |
| #define SPI_PERCLK2_DIV_MAX (7) /* PERCLK2:512 */ |
| #define SPI_CONTROL_DATARATE_MIN (SPI_PERCLK2_DIV_MAX << 13) |
| #define SPI_CONTROL_DATARATE_MAX (SPI_PERCLK2_DIV_MIN << 13) |
| #define SPI_CONTROL_DATARATE_BAD (SPI_CONTROL_DATARATE_MIN + 1) |
| |
| /* SPI Interrupt/Status Register Bit Fields & Masks */ |
| #define SPI_STATUS_TE (0x1 << 0) /* TXFIFO Empty Status */ |
| #define SPI_STATUS_TH (0x1 << 1) /* TXFIFO Half Status */ |
| #define SPI_STATUS_TF (0x1 << 2) /* TXFIFO Full Status */ |
| #define SPI_STATUS_RR (0x1 << 3) /* RXFIFO Data Ready Status */ |
| #define SPI_STATUS_RH (0x1 << 4) /* RXFIFO Half Status */ |
| #define SPI_STATUS_RF (0x1 << 5) /* RXFIFO Full Status */ |
| #define SPI_STATUS_RO (0x1 << 6) /* RXFIFO Overflow */ |
| #define SPI_STATUS_BO (0x1 << 7) /* Bit Count Overflow */ |
| #define SPI_STATUS (0xFF) /* SPI Status Mask */ |
| #define SPI_INTEN_TE (0x1 << 8) /* TXFIFO Empty Interrupt Enable */ |
| #define SPI_INTEN_TH (0x1 << 9) /* TXFIFO Half Interrupt Enable */ |
| #define SPI_INTEN_TF (0x1 << 10) /* TXFIFO Full Interrupt Enable */ |
| #define SPI_INTEN_RE (0x1 << 11) /* RXFIFO Data Ready Interrupt Enable */ |
| #define SPI_INTEN_RH (0x1 << 12) /* RXFIFO Half Interrupt Enable */ |
| #define SPI_INTEN_RF (0x1 << 13) /* RXFIFO Full Interrupt Enable */ |
| #define SPI_INTEN_RO (0x1 << 14) /* RXFIFO Overflow Interrupt Enable */ |
| #define SPI_INTEN_BO (0x1 << 15) /* Bit Count Overflow Interrupt Enable */ |
| #define SPI_INTEN (0xFF << 8) /* SPI Interrupt Enable Mask */ |
| |
| /* SPI Test Register Bit Fields & Masks */ |
| #define SPI_TEST_TXCNT (0xF << 0) /* TXFIFO Counter */ |
| #define SPI_TEST_RXCNT_LSB (4) /* RXFIFO Counter LSB */ |
| #define SPI_TEST_RXCNT (0xF << 4) /* RXFIFO Counter */ |
| #define SPI_TEST_SSTATUS (0xF << 8) /* State Machine Status */ |
| #define SPI_TEST_LBC (0x1 << 14) /* Loop Back Control */ |
| |
| /* SPI Period Register Bit Fields & Masks */ |
| #define SPI_PERIOD_WAIT (0x7FFF << 0) /* Wait Between Transactions */ |
| #define SPI_PERIOD_MAX_WAIT (0x7FFF) /* Max Wait Between |
| Transactions */ |
| #define SPI_PERIOD_CSRC (0x1 << 15) /* Period Clock Source Mask */ |
| #define SPI_PERIOD_CSRC_BCLK (0x0 << 15) /* Period Clock Source is |
| Bit Clock */ |
| #define SPI_PERIOD_CSRC_32768 (0x1 << 15) /* Period Clock Source is |
| 32.768 KHz Clock */ |
| |
| /* SPI DMA Register Bit Fields & Masks */ |
| #define SPI_DMA_RHDMA (0x1 << 4) /* RXFIFO Half Status */ |
| #define SPI_DMA_RFDMA (0x1 << 5) /* RXFIFO Full Status */ |
| #define SPI_DMA_TEDMA (0x1 << 6) /* TXFIFO Empty Status */ |
| #define SPI_DMA_THDMA (0x1 << 7) /* TXFIFO Half Status */ |
| #define SPI_DMA_RHDEN (0x1 << 12) /* RXFIFO Half DMA Request Enable */ |
| #define SPI_DMA_RFDEN (0x1 << 13) /* RXFIFO Full DMA Request Enable */ |
| #define SPI_DMA_TEDEN (0x1 << 14) /* TXFIFO Empty DMA Request Enable */ |
| #define SPI_DMA_THDEN (0x1 << 15) /* TXFIFO Half DMA Request Enable */ |
| |
| /* SPI Soft Reset Register Bit Fields & Masks */ |
| #define SPI_RESET_START (0x1) /* Start */ |
| |
| /* Default SPI configuration values */ |
| #define SPI_DEFAULT_CONTROL \ |
| ( \ |
| SPI_CONTROL_BITCOUNT(16) | \ |
| SPI_CONTROL_POL_ACT_HIGH | \ |
| SPI_CONTROL_PHA_0 | \ |
| SPI_CONTROL_SPIEN | \ |
| SPI_CONTROL_SSCTL_1 | \ |
| SPI_CONTROL_MODE_MASTER | \ |
| SPI_CONTROL_DRCTL_0 | \ |
| SPI_CONTROL_DATARATE_MIN \ |
| ) |
| #define SPI_DEFAULT_ENABLE_LOOPBACK (0) |
| #define SPI_DEFAULT_ENABLE_DMA (0) |
| #define SPI_DEFAULT_PERIOD_WAIT (8) |
| /*-------------------------------------------------------------------------*/ |
| |
| |
| /*-------------------------------------------------------------------------*/ |
| /* TX/RX SPI FIFO size */ |
| #define SPI_FIFO_DEPTH (8) |
| #define SPI_FIFO_BYTE_WIDTH (2) |
| #define SPI_FIFO_OVERFLOW_MARGIN (2) |
| |
| /* DMA burst lenght for half full/empty request trigger */ |
| #define SPI_DMA_BLR (SPI_FIFO_DEPTH * SPI_FIFO_BYTE_WIDTH / 2) |
| |
| /* Dummy char output to achieve reads. |
| Choosing something different from all zeroes may help pattern recogition |
| for oscilloscope analysis, but may break some drivers. */ |
| #define SPI_DUMMY_u8 0 |
| #define SPI_DUMMY_u16 ((SPI_DUMMY_u8 << 8) | SPI_DUMMY_u8) |
| #define SPI_DUMMY_u32 ((SPI_DUMMY_u16 << 16) | SPI_DUMMY_u16) |
| |
| /** |
| * Macro to change a u32 field: |
| * @r : register to edit |
| * @m : bit mask |
| * @v : new value for the field correctly bit-alligned |
| */ |
| #define u32_EDIT(r, m, v) r = (r & ~(m)) | (v) |
| |
| /* Message state */ |
| #define START_STATE ((void*)0) |
| #define RUNNING_STATE ((void*)1) |
| #define DONE_STATE ((void*)2) |
| #define ERROR_STATE ((void*)-1) |
| |
| /* Queue state */ |
| #define QUEUE_RUNNING (0) |
| #define QUEUE_STOPPED (1) |
| |
| #define IS_DMA_ALIGNED(x) (((u32)(x) & 0x03) == 0) |
| /*-------------------------------------------------------------------------*/ |
| |
| |
| /*-------------------------------------------------------------------------*/ |
| /* Driver data structs */ |
| |
| /* Context */ |
| struct driver_data { |
| /* Driver model hookup */ |
| struct platform_device *pdev; |
| |
| /* SPI framework hookup */ |
| struct spi_master *master; |
| |
| /* IMX hookup */ |
| struct spi_imx_master *master_info; |
| |
| /* Memory resources and SPI regs virtual address */ |
| struct resource *ioarea; |
| void __iomem *regs; |
| |
| /* SPI RX_DATA physical address */ |
| dma_addr_t rd_data_phys; |
| |
| /* Driver message queue */ |
| struct workqueue_struct *workqueue; |
| struct work_struct work; |
| spinlock_t lock; |
| struct list_head queue; |
| int busy; |
| int run; |
| |
| /* Message Transfer pump */ |
| struct tasklet_struct pump_transfers; |
| |
| /* Current message, transfer and state */ |
| struct spi_message *cur_msg; |
| struct spi_transfer *cur_transfer; |
| struct chip_data *cur_chip; |
| |
| /* Rd / Wr buffers pointers */ |
| size_t len; |
| void *tx; |
| void *tx_end; |
| void *rx; |
| void *rx_end; |
| |
| u8 rd_only; |
| u8 n_bytes; |
| int cs_change; |
| |
| /* Function pointers */ |
| irqreturn_t (*transfer_handler)(struct driver_data *drv_data); |
| void (*cs_control)(u32 command); |
| |
| /* DMA setup */ |
| int rx_channel; |
| int tx_channel; |
| dma_addr_t rx_dma; |
| dma_addr_t tx_dma; |
| int rx_dma_needs_unmap; |
| int tx_dma_needs_unmap; |
| size_t tx_map_len; |
| u32 dummy_dma_buf ____cacheline_aligned; |
| }; |
| |
| /* Runtime state */ |
| struct chip_data { |
| u32 control; |
| u32 period; |
| u32 test; |
| |
| u8 enable_dma:1; |
| u8 bits_per_word; |
| u8 n_bytes; |
| u32 max_speed_hz; |
| |
| void (*cs_control)(u32 command); |
| }; |
| /*-------------------------------------------------------------------------*/ |
| |
| |
| static void pump_messages(struct work_struct *work); |
| |
| static int flush(struct driver_data *drv_data) |
| { |
| unsigned long limit = loops_per_jiffy << 1; |
| void __iomem *regs = drv_data->regs; |
| volatile u32 d; |
| |
| dev_dbg(&drv_data->pdev->dev, "flush\n"); |
| do { |
| while (readl(regs + SPI_INT_STATUS) & SPI_STATUS_RR) |
| d = readl(regs + SPI_RXDATA); |
| } while ((readl(regs + SPI_CONTROL) & SPI_CONTROL_XCH) && limit--); |
| |
| return limit; |
| } |
| |
| static void restore_state(struct driver_data *drv_data) |
| { |
| void __iomem *regs = drv_data->regs; |
| struct chip_data *chip = drv_data->cur_chip; |
| |
| /* Load chip registers */ |
| dev_dbg(&drv_data->pdev->dev, |
| "restore_state\n" |
| " test = 0x%08X\n" |
| " control = 0x%08X\n", |
| chip->test, |
| chip->control); |
| writel(chip->test, regs + SPI_TEST); |
| writel(chip->period, regs + SPI_PERIOD); |
| writel(0, regs + SPI_INT_STATUS); |
| writel(chip->control, regs + SPI_CONTROL); |
| } |
| |
| static void null_cs_control(u32 command) |
| { |
| } |
| |
| static inline u32 data_to_write(struct driver_data *drv_data) |
| { |
| return ((u32)(drv_data->tx_end - drv_data->tx)) / drv_data->n_bytes; |
| } |
| |
| static inline u32 data_to_read(struct driver_data *drv_data) |
| { |
| return ((u32)(drv_data->rx_end - drv_data->rx)) / drv_data->n_bytes; |
| } |
| |
| static int write(struct driver_data *drv_data) |
| { |
| void __iomem *regs = drv_data->regs; |
| void *tx = drv_data->tx; |
| void *tx_end = drv_data->tx_end; |
| u8 n_bytes = drv_data->n_bytes; |
| u32 remaining_writes; |
| u32 fifo_avail_space; |
| u32 n; |
| u16 d; |
| |
| /* Compute how many fifo writes to do */ |
| remaining_writes = (u32)(tx_end - tx) / n_bytes; |
| fifo_avail_space = SPI_FIFO_DEPTH - |
| (readl(regs + SPI_TEST) & SPI_TEST_TXCNT); |
| if (drv_data->rx && (fifo_avail_space > SPI_FIFO_OVERFLOW_MARGIN)) |
| /* Fix misunderstood receive overflow */ |
| fifo_avail_space -= SPI_FIFO_OVERFLOW_MARGIN; |
| n = min(remaining_writes, fifo_avail_space); |
| |
| dev_dbg(&drv_data->pdev->dev, |
| "write type %s\n" |
| " remaining writes = %d\n" |
| " fifo avail space = %d\n" |
| " fifo writes = %d\n", |
| (n_bytes == 1) ? "u8" : "u16", |
| remaining_writes, |
| fifo_avail_space, |
| n); |
| |
| if (n > 0) { |
| /* Fill SPI TXFIFO */ |
| if (drv_data->rd_only) { |
| tx += n * n_bytes; |
| while (n--) |
| writel(SPI_DUMMY_u16, regs + SPI_TXDATA); |
| } else { |
| if (n_bytes == 1) { |
| while (n--) { |
| d = *(u8*)tx; |
| writel(d, regs + SPI_TXDATA); |
| tx += 1; |
| } |
| } else { |
| while (n--) { |
| d = *(u16*)tx; |
| writel(d, regs + SPI_TXDATA); |
| tx += 2; |
| } |
| } |
| } |
| |
| /* Trigger transfer */ |
| writel(readl(regs + SPI_CONTROL) | SPI_CONTROL_XCH, |
| regs + SPI_CONTROL); |
| |
| /* Update tx pointer */ |
| drv_data->tx = tx; |
| } |
| |
| return (tx >= tx_end); |
| } |
| |
| static int read(struct driver_data *drv_data) |
| { |
| void __iomem *regs = drv_data->regs; |
| void *rx = drv_data->rx; |
| void *rx_end = drv_data->rx_end; |
| u8 n_bytes = drv_data->n_bytes; |
| u32 remaining_reads; |
| u32 fifo_rxcnt; |
| u32 n; |
| u16 d; |
| |
| /* Compute how many fifo reads to do */ |
| remaining_reads = (u32)(rx_end - rx) / n_bytes; |
| fifo_rxcnt = (readl(regs + SPI_TEST) & SPI_TEST_RXCNT) >> |
| SPI_TEST_RXCNT_LSB; |
| n = min(remaining_reads, fifo_rxcnt); |
| |
| dev_dbg(&drv_data->pdev->dev, |
| "read type %s\n" |
| " remaining reads = %d\n" |
| " fifo rx count = %d\n" |
| " fifo reads = %d\n", |
| (n_bytes == 1) ? "u8" : "u16", |
| remaining_reads, |
| fifo_rxcnt, |
| n); |
| |
| if (n > 0) { |
| /* Read SPI RXFIFO */ |
| if (n_bytes == 1) { |
| while (n--) { |
| d = readl(regs + SPI_RXDATA); |
| *((u8*)rx) = d; |
| rx += 1; |
| } |
| } else { |
| while (n--) { |
| d = readl(regs + SPI_RXDATA); |
| *((u16*)rx) = d; |
| rx += 2; |
| } |
| } |
| |
| /* Update rx pointer */ |
| drv_data->rx = rx; |
| } |
| |
| return (rx >= rx_end); |
| } |
| |
| static void *next_transfer(struct driver_data *drv_data) |
| { |
| struct spi_message *msg = drv_data->cur_msg; |
| struct spi_transfer *trans = drv_data->cur_transfer; |
| |
| /* Move to next transfer */ |
| if (trans->transfer_list.next != &msg->transfers) { |
| drv_data->cur_transfer = |
| list_entry(trans->transfer_list.next, |
| struct spi_transfer, |
| transfer_list); |
| return RUNNING_STATE; |
| } |
| |
| return DONE_STATE; |
| } |
| |
| static int map_dma_buffers(struct driver_data *drv_data) |
| { |
| struct spi_message *msg; |
| struct device *dev; |
| void *buf; |
| |
| drv_data->rx_dma_needs_unmap = 0; |
| drv_data->tx_dma_needs_unmap = 0; |
| |
| if (!drv_data->master_info->enable_dma || |
| !drv_data->cur_chip->enable_dma) |
| return -1; |
| |
| msg = drv_data->cur_msg; |
| dev = &msg->spi->dev; |
| if (msg->is_dma_mapped) { |
| if (drv_data->tx_dma) |
| /* The caller provided at least dma and cpu virtual |
| address for write; pump_transfers() will consider the |
| transfer as write only if cpu rx virtual address is |
| NULL */ |
| return 0; |
| |
| if (drv_data->rx_dma) { |
| /* The caller provided dma and cpu virtual address to |
| performe read only transfer --> |
| use drv_data->dummy_dma_buf for dummy writes to |
| achive reads */ |
| buf = &drv_data->dummy_dma_buf; |
| drv_data->tx_map_len = sizeof(drv_data->dummy_dma_buf); |
| drv_data->tx_dma = dma_map_single(dev, |
| buf, |
| drv_data->tx_map_len, |
| DMA_TO_DEVICE); |
| if (dma_mapping_error(drv_data->tx_dma)) |
| return -1; |
| |
| drv_data->tx_dma_needs_unmap = 1; |
| |
| /* Flags transfer as rd_only for pump_transfers() DMA |
| regs programming (should be redundant) */ |
| drv_data->tx = NULL; |
| |
| return 0; |
| } |
| } |
| |
| if (!IS_DMA_ALIGNED(drv_data->rx) || !IS_DMA_ALIGNED(drv_data->tx)) |
| return -1; |
| |
| /* NULL rx means write-only transfer and no map needed |
| since rx DMA will not be used */ |
| if (drv_data->rx) { |
| buf = drv_data->rx; |
| drv_data->rx_dma = dma_map_single( |
| dev, |
| buf, |
| drv_data->len, |
| DMA_FROM_DEVICE); |
| if (dma_mapping_error(drv_data->rx_dma)) |
| return -1; |
| drv_data->rx_dma_needs_unmap = 1; |
| } |
| |
| if (drv_data->tx == NULL) { |
| /* Read only message --> use drv_data->dummy_dma_buf for dummy |
| writes to achive reads */ |
| buf = &drv_data->dummy_dma_buf; |
| drv_data->tx_map_len = sizeof(drv_data->dummy_dma_buf); |
| } else { |
| buf = drv_data->tx; |
| drv_data->tx_map_len = drv_data->len; |
| } |
| drv_data->tx_dma = dma_map_single(dev, |
| buf, |
| drv_data->tx_map_len, |
| DMA_TO_DEVICE); |
| if (dma_mapping_error(drv_data->tx_dma)) { |
| if (drv_data->rx_dma) { |
| dma_unmap_single(dev, |
| drv_data->rx_dma, |
| drv_data->len, |
| DMA_FROM_DEVICE); |
| drv_data->rx_dma_needs_unmap = 0; |
| } |
| return -1; |
| } |
| drv_data->tx_dma_needs_unmap = 1; |
| |
| return 0; |
| } |
| |
| static void unmap_dma_buffers(struct driver_data *drv_data) |
| { |
| struct spi_message *msg = drv_data->cur_msg; |
| struct device *dev = &msg->spi->dev; |
| |
| if (drv_data->rx_dma_needs_unmap) { |
| dma_unmap_single(dev, |
| drv_data->rx_dma, |
| drv_data->len, |
| DMA_FROM_DEVICE); |
| drv_data->rx_dma_needs_unmap = 0; |
| } |
| if (drv_data->tx_dma_needs_unmap) { |
| dma_unmap_single(dev, |
| drv_data->tx_dma, |
| drv_data->tx_map_len, |
| DMA_TO_DEVICE); |
| drv_data->tx_dma_needs_unmap = 0; |
| } |
| } |
| |
| /* Caller already set message->status (dma is already blocked) */ |
| static void giveback(struct spi_message *message, struct driver_data *drv_data) |
| { |
| void __iomem *regs = drv_data->regs; |
| |
| /* Bring SPI to sleep; restore_state() and pump_transfer() |
| will do new setup */ |
| writel(0, regs + SPI_INT_STATUS); |
| writel(0, regs + SPI_DMA); |
| |
| drv_data->cs_control(SPI_CS_DEASSERT); |
| |
| message->state = NULL; |
| if (message->complete) |
| message->complete(message->context); |
| |
| drv_data->cur_msg = NULL; |
| drv_data->cur_transfer = NULL; |
| drv_data->cur_chip = NULL; |
| queue_work(drv_data->workqueue, &drv_data->work); |
| } |
| |
| static void dma_err_handler(int channel, void *data, int errcode) |
| { |
| struct driver_data *drv_data = data; |
| struct spi_message *msg = drv_data->cur_msg; |
| |
| dev_dbg(&drv_data->pdev->dev, "dma_err_handler\n"); |
| |
| /* Disable both rx and tx dma channels */ |
| imx_dma_disable(drv_data->rx_channel); |
| imx_dma_disable(drv_data->tx_channel); |
| |
| if (flush(drv_data) == 0) |
| dev_err(&drv_data->pdev->dev, |
| "dma_err_handler - flush failed\n"); |
| |
| unmap_dma_buffers(drv_data); |
| |
| msg->state = ERROR_STATE; |
| tasklet_schedule(&drv_data->pump_transfers); |
| } |
| |
| static void dma_tx_handler(int channel, void *data) |
| { |
| struct driver_data *drv_data = data; |
| |
| dev_dbg(&drv_data->pdev->dev, "dma_tx_handler\n"); |
| |
| imx_dma_disable(channel); |
| |
| /* Now waits for TX FIFO empty */ |
| writel(readl(drv_data->regs + SPI_INT_STATUS) | SPI_INTEN_TE, |
| drv_data->regs + SPI_INT_STATUS); |
| } |
| |
| static irqreturn_t dma_transfer(struct driver_data *drv_data) |
| { |
| u32 status; |
| struct spi_message *msg = drv_data->cur_msg; |
| void __iomem *regs = drv_data->regs; |
| unsigned long limit; |
| |
| status = readl(regs + SPI_INT_STATUS); |
| |
| if ((status & SPI_INTEN_RO) && (status & SPI_STATUS_RO)) { |
| writel(status & ~SPI_INTEN, regs + SPI_INT_STATUS); |
| |
| imx_dma_disable(drv_data->rx_channel); |
| unmap_dma_buffers(drv_data); |
| |
| if (flush(drv_data) == 0) |
| dev_err(&drv_data->pdev->dev, |
| "dma_transfer - flush failed\n"); |
| |
| dev_warn(&drv_data->pdev->dev, |
| "dma_transfer - fifo overun\n"); |
| |
| msg->state = ERROR_STATE; |
| tasklet_schedule(&drv_data->pump_transfers); |
| |
| return IRQ_HANDLED; |
| } |
| |
| if (status & SPI_STATUS_TE) { |
| writel(status & ~SPI_INTEN_TE, regs + SPI_INT_STATUS); |
| |
| if (drv_data->rx) { |
| /* Wait end of transfer before read trailing data */ |
| limit = loops_per_jiffy << 1; |
| while ((readl(regs + SPI_CONTROL) & SPI_CONTROL_XCH) && |
| limit--); |
| |
| if (limit == 0) |
| dev_err(&drv_data->pdev->dev, |
| "dma_transfer - end of tx failed\n"); |
| else |
| dev_dbg(&drv_data->pdev->dev, |
| "dma_transfer - end of tx\n"); |
| |
| imx_dma_disable(drv_data->rx_channel); |
| unmap_dma_buffers(drv_data); |
| |
| /* Calculate number of trailing data and read them */ |
| dev_dbg(&drv_data->pdev->dev, |
| "dma_transfer - test = 0x%08X\n", |
| readl(regs + SPI_TEST)); |
| drv_data->rx = drv_data->rx_end - |
| ((readl(regs + SPI_TEST) & |
| SPI_TEST_RXCNT) >> |
| SPI_TEST_RXCNT_LSB)*drv_data->n_bytes; |
| read(drv_data); |
| } else { |
| /* Write only transfer */ |
| unmap_dma_buffers(drv_data); |
| |
| if (flush(drv_data) == 0) |
| dev_err(&drv_data->pdev->dev, |
| "dma_transfer - flush failed\n"); |
| } |
| |
| /* End of transfer, update total byte transfered */ |
| msg->actual_length += drv_data->len; |
| |
| /* Release chip select if requested, transfer delays are |
| handled in pump_transfers() */ |
| if (drv_data->cs_change) |
| drv_data->cs_control(SPI_CS_DEASSERT); |
| |
| /* Move to next transfer */ |
| msg->state = next_transfer(drv_data); |
| |
| /* Schedule transfer tasklet */ |
| tasklet_schedule(&drv_data->pump_transfers); |
| |
| return IRQ_HANDLED; |
| } |
| |
| /* Opps problem detected */ |
| return IRQ_NONE; |
| } |
| |
| static irqreturn_t interrupt_wronly_transfer(struct driver_data *drv_data) |
| { |
| struct spi_message *msg = drv_data->cur_msg; |
| void __iomem *regs = drv_data->regs; |
| u32 status; |
| irqreturn_t handled = IRQ_NONE; |
| |
| status = readl(regs + SPI_INT_STATUS); |
| |
| while (status & SPI_STATUS_TH) { |
| dev_dbg(&drv_data->pdev->dev, |
| "interrupt_wronly_transfer - status = 0x%08X\n", status); |
| |
| /* Pump data */ |
| if (write(drv_data)) { |
| writel(readl(regs + SPI_INT_STATUS) & ~SPI_INTEN, |
| regs + SPI_INT_STATUS); |
| |
| dev_dbg(&drv_data->pdev->dev, |
| "interrupt_wronly_transfer - end of tx\n"); |
| |
| if (flush(drv_data) == 0) |
| dev_err(&drv_data->pdev->dev, |
| "interrupt_wronly_transfer - " |
| "flush failed\n"); |
| |
| /* End of transfer, update total byte transfered */ |
| msg->actual_length += drv_data->len; |
| |
| /* Release chip select if requested, transfer delays are |
| handled in pump_transfers */ |
| if (drv_data->cs_change) |
| drv_data->cs_control(SPI_CS_DEASSERT); |
| |
| /* Move to next transfer */ |
| msg->state = next_transfer(drv_data); |
| |
| /* Schedule transfer tasklet */ |
| tasklet_schedule(&drv_data->pump_transfers); |
| |
| return IRQ_HANDLED; |
| } |
| |
| status = readl(regs + SPI_INT_STATUS); |
| |
| /* We did something */ |
| handled = IRQ_HANDLED; |
| } |
| |
| return handled; |
| } |
| |
| static irqreturn_t interrupt_transfer(struct driver_data *drv_data) |
| { |
| struct spi_message *msg = drv_data->cur_msg; |
| void __iomem *regs = drv_data->regs; |
| u32 status; |
| irqreturn_t handled = IRQ_NONE; |
| unsigned long limit; |
| |
| status = readl(regs + SPI_INT_STATUS); |
| |
| while (status & (SPI_STATUS_TH | SPI_STATUS_RO)) { |
| dev_dbg(&drv_data->pdev->dev, |
| "interrupt_transfer - status = 0x%08X\n", status); |
| |
| if (status & SPI_STATUS_RO) { |
| writel(readl(regs + SPI_INT_STATUS) & ~SPI_INTEN, |
| regs + SPI_INT_STATUS); |
| |
| dev_warn(&drv_data->pdev->dev, |
| "interrupt_transfer - fifo overun\n" |
| " data not yet written = %d\n" |
| " data not yet read = %d\n", |
| data_to_write(drv_data), |
| data_to_read(drv_data)); |
| |
| if (flush(drv_data) == 0) |
| dev_err(&drv_data->pdev->dev, |
| "interrupt_transfer - flush failed\n"); |
| |
| msg->state = ERROR_STATE; |
| tasklet_schedule(&drv_data->pump_transfers); |
| |
| return IRQ_HANDLED; |
| } |
| |
| /* Pump data */ |
| read(drv_data); |
| if (write(drv_data)) { |
| writel(readl(regs + SPI_INT_STATUS) & ~SPI_INTEN, |
| regs + SPI_INT_STATUS); |
| |
| dev_dbg(&drv_data->pdev->dev, |
| "interrupt_transfer - end of tx\n"); |
| |
| /* Read trailing bytes */ |
| limit = loops_per_jiffy << 1; |
| while ((read(drv_data) == 0) && limit--); |
| |
| if (limit == 0) |
| dev_err(&drv_data->pdev->dev, |
| "interrupt_transfer - " |
| "trailing byte read failed\n"); |
| else |
| dev_dbg(&drv_data->pdev->dev, |
| "interrupt_transfer - end of rx\n"); |
| |
| /* End of transfer, update total byte transfered */ |
| msg->actual_length += drv_data->len; |
| |
| /* Release chip select if requested, transfer delays are |
| handled in pump_transfers */ |
| if (drv_data->cs_change) |
| drv_data->cs_control(SPI_CS_DEASSERT); |
| |
| /* Move to next transfer */ |
| msg->state = next_transfer(drv_data); |
| |
| /* Schedule transfer tasklet */ |
| tasklet_schedule(&drv_data->pump_transfers); |
| |
| return IRQ_HANDLED; |
| } |
| |
| status = readl(regs + SPI_INT_STATUS); |
| |
| /* We did something */ |
| handled = IRQ_HANDLED; |
| } |
| |
| return handled; |
| } |
| |
| static irqreturn_t spi_int(int irq, void *dev_id) |
| { |
| struct driver_data *drv_data = (struct driver_data *)dev_id; |
| |
| if (!drv_data->cur_msg) { |
| dev_err(&drv_data->pdev->dev, |
| "spi_int - bad message state\n"); |
| /* Never fail */ |
| return IRQ_HANDLED; |
| } |
| |
| return drv_data->transfer_handler(drv_data); |
| } |
| |
| static inline u32 spi_speed_hz(u32 data_rate) |
| { |
| return imx_get_perclk2() / (4 << ((data_rate) >> 13)); |
| } |
| |
| static u32 spi_data_rate(u32 speed_hz) |
| { |
| u32 div; |
| u32 quantized_hz = imx_get_perclk2() >> 2; |
| |
| for (div = SPI_PERCLK2_DIV_MIN; |
| div <= SPI_PERCLK2_DIV_MAX; |
| div++, quantized_hz >>= 1) { |
| if (quantized_hz <= speed_hz) |
| /* Max available speed LEQ required speed */ |
| return div << 13; |
| } |
| return SPI_CONTROL_DATARATE_BAD; |
| } |
| |
| static void pump_transfers(unsigned long data) |
| { |
| struct driver_data *drv_data = (struct driver_data *)data; |
| struct spi_message *message; |
| struct spi_transfer *transfer, *previous; |
| struct chip_data *chip; |
| void __iomem *regs; |
| u32 tmp, control; |
| |
| dev_dbg(&drv_data->pdev->dev, "pump_transfer\n"); |
| |
| message = drv_data->cur_msg; |
| |
| /* Handle for abort */ |
| if (message->state == ERROR_STATE) { |
| message->status = -EIO; |
| giveback(message, drv_data); |
| return; |
| } |
| |
| /* Handle end of message */ |
| if (message->state == DONE_STATE) { |
| message->status = 0; |
| giveback(message, drv_data); |
| return; |
| } |
| |
| chip = drv_data->cur_chip; |
| |
| /* Delay if requested at end of transfer*/ |
| transfer = drv_data->cur_transfer; |
| if (message->state == RUNNING_STATE) { |
| previous = list_entry(transfer->transfer_list.prev, |
| struct spi_transfer, |
| transfer_list); |
| if (previous->delay_usecs) |
| udelay(previous->delay_usecs); |
| } else { |
| /* START_STATE */ |
| message->state = RUNNING_STATE; |
| drv_data->cs_control = chip->cs_control; |
| } |
| |
| transfer = drv_data->cur_transfer; |
| drv_data->tx = (void *)transfer->tx_buf; |
| drv_data->tx_end = drv_data->tx + transfer->len; |
| drv_data->rx = transfer->rx_buf; |
| drv_data->rx_end = drv_data->rx + transfer->len; |
| drv_data->rx_dma = transfer->rx_dma; |
| drv_data->tx_dma = transfer->tx_dma; |
| drv_data->len = transfer->len; |
| drv_data->cs_change = transfer->cs_change; |
| drv_data->rd_only = (drv_data->tx == NULL); |
| |
| regs = drv_data->regs; |
| control = readl(regs + SPI_CONTROL); |
| |
| /* Bits per word setup */ |
| tmp = transfer->bits_per_word; |
| if (tmp == 0) { |
| /* Use device setup */ |
| tmp = chip->bits_per_word; |
| drv_data->n_bytes = chip->n_bytes; |
| } else |
| /* Use per-transfer setup */ |
| drv_data->n_bytes = (tmp <= 8) ? 1 : 2; |
| u32_EDIT(control, SPI_CONTROL_BITCOUNT_MASK, tmp - 1); |
| |
| /* Speed setup (surely valid because already checked) */ |
| tmp = transfer->speed_hz; |
| if (tmp == 0) |
| tmp = chip->max_speed_hz; |
| tmp = spi_data_rate(tmp); |
| u32_EDIT(control, SPI_CONTROL_DATARATE, tmp); |
| |
| writel(control, regs + SPI_CONTROL); |
| |
| /* Assert device chip-select */ |
| drv_data->cs_control(SPI_CS_ASSERT); |
| |
| /* DMA cannot read/write SPI FIFOs other than 16 bits at a time; hence |
| if bits_per_word is less or equal 8 PIO transfers are performed. |
| Moreover DMA is convinient for transfer length bigger than FIFOs |
| byte size. */ |
| if ((drv_data->n_bytes == 2) && |
| (drv_data->len > SPI_FIFO_DEPTH*SPI_FIFO_BYTE_WIDTH) && |
| (map_dma_buffers(drv_data) == 0)) { |
| dev_dbg(&drv_data->pdev->dev, |
| "pump dma transfer\n" |
| " tx = %p\n" |
| " tx_dma = %08X\n" |
| " rx = %p\n" |
| " rx_dma = %08X\n" |
| " len = %d\n", |
| drv_data->tx, |
| (unsigned int)drv_data->tx_dma, |
| drv_data->rx, |
| (unsigned int)drv_data->rx_dma, |
| drv_data->len); |
| |
| /* Ensure we have the correct interrupt handler */ |
| drv_data->transfer_handler = dma_transfer; |
| |
| /* Trigger transfer */ |
| writel(readl(regs + SPI_CONTROL) | SPI_CONTROL_XCH, |
| regs + SPI_CONTROL); |
| |
| /* Setup tx DMA */ |
| if (drv_data->tx) |
| /* Linear source address */ |
| CCR(drv_data->tx_channel) = |
| CCR_DMOD_FIFO | |
| CCR_SMOD_LINEAR | |
| CCR_SSIZ_32 | CCR_DSIZ_16 | |
| CCR_REN; |
| else |
| /* Read only transfer -> fixed source address for |
| dummy write to achive read */ |
| CCR(drv_data->tx_channel) = |
| CCR_DMOD_FIFO | |
| CCR_SMOD_FIFO | |
| CCR_SSIZ_32 | CCR_DSIZ_16 | |
| CCR_REN; |
| |
| imx_dma_setup_single( |
| drv_data->tx_channel, |
| drv_data->tx_dma, |
| drv_data->len, |
| drv_data->rd_data_phys + 4, |
| DMA_MODE_WRITE); |
| |
| if (drv_data->rx) { |
| /* Setup rx DMA for linear destination address */ |
| CCR(drv_data->rx_channel) = |
| CCR_DMOD_LINEAR | |
| CCR_SMOD_FIFO | |
| CCR_DSIZ_32 | CCR_SSIZ_16 | |
| CCR_REN; |
| imx_dma_setup_single( |
| drv_data->rx_channel, |
| drv_data->rx_dma, |
| drv_data->len, |
| drv_data->rd_data_phys, |
| DMA_MODE_READ); |
| imx_dma_enable(drv_data->rx_channel); |
| |
| /* Enable SPI interrupt */ |
| writel(SPI_INTEN_RO, regs + SPI_INT_STATUS); |
| |
| /* Set SPI to request DMA service on both |
| Rx and Tx half fifo watermark */ |
| writel(SPI_DMA_RHDEN | SPI_DMA_THDEN, regs + SPI_DMA); |
| } else |
| /* Write only access -> set SPI to request DMA |
| service on Tx half fifo watermark */ |
| writel(SPI_DMA_THDEN, regs + SPI_DMA); |
| |
| imx_dma_enable(drv_data->tx_channel); |
| } else { |
| dev_dbg(&drv_data->pdev->dev, |
| "pump pio transfer\n" |
| " tx = %p\n" |
| " rx = %p\n" |
| " len = %d\n", |
| drv_data->tx, |
| drv_data->rx, |
| drv_data->len); |
| |
| /* Ensure we have the correct interrupt handler */ |
| if (drv_data->rx) |
| drv_data->transfer_handler = interrupt_transfer; |
| else |
| drv_data->transfer_handler = interrupt_wronly_transfer; |
| |
| /* Enable SPI interrupt */ |
| if (drv_data->rx) |
| writel(SPI_INTEN_TH | SPI_INTEN_RO, |
| regs + SPI_INT_STATUS); |
| else |
| writel(SPI_INTEN_TH, regs + SPI_INT_STATUS); |
| } |
| } |
| |
| static void pump_messages(struct work_struct *work) |
| { |
| struct driver_data *drv_data = |
| container_of(work, struct driver_data, work); |
| unsigned long flags; |
| |
| /* Lock queue and check for queue work */ |
| spin_lock_irqsave(&drv_data->lock, flags); |
| if (list_empty(&drv_data->queue) || drv_data->run == QUEUE_STOPPED) { |
| drv_data->busy = 0; |
| spin_unlock_irqrestore(&drv_data->lock, flags); |
| return; |
| } |
| |
| /* Make sure we are not already running a message */ |
| if (drv_data->cur_msg) { |
| spin_unlock_irqrestore(&drv_data->lock, flags); |
| return; |
| } |
| |
| /* Extract head of queue */ |
| drv_data->cur_msg = list_entry(drv_data->queue.next, |
| struct spi_message, queue); |
| list_del_init(&drv_data->cur_msg->queue); |
| drv_data->busy = 1; |
| spin_unlock_irqrestore(&drv_data->lock, flags); |
| |
| /* Initial message state */ |
| drv_data->cur_msg->state = START_STATE; |
| drv_data->cur_transfer = list_entry(drv_data->cur_msg->transfers.next, |
| struct spi_transfer, |
| transfer_list); |
| |
| /* Setup the SPI using the per chip configuration */ |
| drv_data->cur_chip = spi_get_ctldata(drv_data->cur_msg->spi); |
| restore_state(drv_data); |
| |
| /* Mark as busy and launch transfers */ |
| tasklet_schedule(&drv_data->pump_transfers); |
| } |
| |
| static int transfer(struct spi_device *spi, struct spi_message *msg) |
| { |
| struct driver_data *drv_data = spi_master_get_devdata(spi->master); |
| u32 min_speed_hz, max_speed_hz, tmp; |
| struct spi_transfer *trans; |
| unsigned long flags; |
| |
| msg->actual_length = 0; |
| |
| /* Per transfer setup check */ |
| min_speed_hz = spi_speed_hz(SPI_CONTROL_DATARATE_MIN); |
| max_speed_hz = spi->max_speed_hz; |
| list_for_each_entry(trans, &msg->transfers, transfer_list) { |
| tmp = trans->bits_per_word; |
| if (tmp > 16) { |
| dev_err(&drv_data->pdev->dev, |
| "message rejected : " |
| "invalid transfer bits_per_word (%d bits)\n", |
| tmp); |
| goto msg_rejected; |
| } |
| tmp = trans->speed_hz; |
| if (tmp) { |
| if (tmp < min_speed_hz) { |
| dev_err(&drv_data->pdev->dev, |
| "message rejected : " |
| "device min speed (%d Hz) exceeds " |
| "required transfer speed (%d Hz)\n", |
| min_speed_hz, |
| tmp); |
| goto msg_rejected; |
| } else if (tmp > max_speed_hz) { |
| dev_err(&drv_data->pdev->dev, |
| "message rejected : " |
| "transfer speed (%d Hz) exceeds " |
| "device max speed (%d Hz)\n", |
| tmp, |
| max_speed_hz); |
| goto msg_rejected; |
| } |
| } |
| } |
| |
| /* Message accepted */ |
| msg->status = -EINPROGRESS; |
| msg->state = START_STATE; |
| |
| spin_lock_irqsave(&drv_data->lock, flags); |
| if (drv_data->run == QUEUE_STOPPED) { |
| spin_unlock_irqrestore(&drv_data->lock, flags); |
| return -ESHUTDOWN; |
| } |
| |
| list_add_tail(&msg->queue, &drv_data->queue); |
| if (drv_data->run == QUEUE_RUNNING && !drv_data->busy) |
| queue_work(drv_data->workqueue, &drv_data->work); |
| |
| spin_unlock_irqrestore(&drv_data->lock, flags); |
| return 0; |
| |
| msg_rejected: |
| /* Message rejected and not queued */ |
| msg->status = -EINVAL; |
| msg->state = ERROR_STATE; |
| if (msg->complete) |
| msg->complete(msg->context); |
| return -EINVAL; |
| } |
| |
| /* the spi->mode bits understood by this driver: */ |
| #define MODEBITS (SPI_CPOL | SPI_CPHA | SPI_CS_HIGH) |
| |
| /* On first setup bad values must free chip_data memory since will cause |
| spi_new_device to fail. Bad value setup from protocol driver are simply not |
| applied and notified to the calling driver. */ |
| static int setup(struct spi_device *spi) |
| { |
| struct spi_imx_chip *chip_info; |
| struct chip_data *chip; |
| int first_setup = 0; |
| u32 tmp; |
| int status = 0; |
| |
| if (spi->mode & ~MODEBITS) { |
| dev_dbg(&spi->dev, "setup: unsupported mode bits %x\n", |
| spi->mode & ~MODEBITS); |
| return -EINVAL; |
| } |
| |
| /* Get controller data */ |
| chip_info = spi->controller_data; |
| |
| /* Get controller_state */ |
| chip = spi_get_ctldata(spi); |
| if (chip == NULL) { |
| first_setup = 1; |
| |
| chip = kzalloc(sizeof(struct chip_data), GFP_KERNEL); |
| if (!chip) { |
| dev_err(&spi->dev, |
| "setup - cannot allocate controller state"); |
| return -ENOMEM; |
| } |
| chip->control = SPI_DEFAULT_CONTROL; |
| |
| if (chip_info == NULL) { |
| /* spi_board_info.controller_data not is supplied */ |
| chip_info = kzalloc(sizeof(struct spi_imx_chip), |
| GFP_KERNEL); |
| if (!chip_info) { |
| dev_err(&spi->dev, |
| "setup - " |
| "cannot allocate controller data"); |
| status = -ENOMEM; |
| goto err_first_setup; |
| } |
| /* Set controller data default value */ |
| chip_info->enable_loopback = |
| SPI_DEFAULT_ENABLE_LOOPBACK; |
| chip_info->enable_dma = SPI_DEFAULT_ENABLE_DMA; |
| chip_info->ins_ss_pulse = 1; |
| chip_info->bclk_wait = SPI_DEFAULT_PERIOD_WAIT; |
| chip_info->cs_control = null_cs_control; |
| } |
| } |
| |
| /* Now set controller state based on controller data */ |
| |
| if (first_setup) { |
| /* SPI loopback */ |
| if (chip_info->enable_loopback) |
| chip->test = SPI_TEST_LBC; |
| else |
| chip->test = 0; |
| |
| /* SPI dma driven */ |
| chip->enable_dma = chip_info->enable_dma; |
| |
| /* SPI /SS pulse between spi burst */ |
| if (chip_info->ins_ss_pulse) |
| u32_EDIT(chip->control, |
| SPI_CONTROL_SSCTL, SPI_CONTROL_SSCTL_1); |
| else |
| u32_EDIT(chip->control, |
| SPI_CONTROL_SSCTL, SPI_CONTROL_SSCTL_0); |
| |
| /* SPI bclk waits between each bits_per_word spi burst */ |
| if (chip_info->bclk_wait > SPI_PERIOD_MAX_WAIT) { |
| dev_err(&spi->dev, |
| "setup - " |
| "bclk_wait exceeds max allowed (%d)\n", |
| SPI_PERIOD_MAX_WAIT); |
| goto err_first_setup; |
| } |
| chip->period = SPI_PERIOD_CSRC_BCLK | |
| (chip_info->bclk_wait & SPI_PERIOD_WAIT); |
| } |
| |
| /* SPI mode */ |
| tmp = spi->mode; |
| if (tmp & SPI_CS_HIGH) { |
| u32_EDIT(chip->control, |
| SPI_CONTROL_SSPOL, SPI_CONTROL_SSPOL_ACT_HIGH); |
| } |
| switch (tmp & SPI_MODE_3) { |
| case SPI_MODE_0: |
| tmp = 0; |
| break; |
| case SPI_MODE_1: |
| tmp = SPI_CONTROL_PHA_1; |
| break; |
| case SPI_MODE_2: |
| tmp = SPI_CONTROL_POL_ACT_LOW; |
| break; |
| default: |
| /* SPI_MODE_3 */ |
| tmp = SPI_CONTROL_PHA_1 | SPI_CONTROL_POL_ACT_LOW; |
| break; |
| } |
| u32_EDIT(chip->control, SPI_CONTROL_POL | SPI_CONTROL_PHA, tmp); |
| |
| /* SPI word width */ |
| tmp = spi->bits_per_word; |
| if (tmp == 0) { |
| tmp = 8; |
| spi->bits_per_word = 8; |
| } else if (tmp > 16) { |
| status = -EINVAL; |
| dev_err(&spi->dev, |
| "setup - " |
| "invalid bits_per_word (%d)\n", |
| tmp); |
| if (first_setup) |
| goto err_first_setup; |
| else { |
| /* Undo setup using chip as backup copy */ |
| tmp = chip->bits_per_word; |
| spi->bits_per_word = tmp; |
| } |
| } |
| chip->bits_per_word = tmp; |
| u32_EDIT(chip->control, SPI_CONTROL_BITCOUNT_MASK, tmp - 1); |
| chip->n_bytes = (tmp <= 8) ? 1 : 2; |
| |
| /* SPI datarate */ |
| tmp = spi_data_rate(spi->max_speed_hz); |
| if (tmp == SPI_CONTROL_DATARATE_BAD) { |
| status = -EINVAL; |
| dev_err(&spi->dev, |
| "setup - " |
| "HW min speed (%d Hz) exceeds required " |
| "max speed (%d Hz)\n", |
| spi_speed_hz(SPI_CONTROL_DATARATE_MIN), |
| spi->max_speed_hz); |
| if (first_setup) |
| goto err_first_setup; |
| else |
| /* Undo setup using chip as backup copy */ |
| spi->max_speed_hz = chip->max_speed_hz; |
| } else { |
| u32_EDIT(chip->control, SPI_CONTROL_DATARATE, tmp); |
| /* Actual rounded max_speed_hz */ |
| tmp = spi_speed_hz(tmp); |
| spi->max_speed_hz = tmp; |
| chip->max_speed_hz = tmp; |
| } |
| |
| /* SPI chip-select management */ |
| if (chip_info->cs_control) |
| chip->cs_control = chip_info->cs_control; |
| else |
| chip->cs_control = null_cs_control; |
| |
| /* Save controller_state */ |
| spi_set_ctldata(spi, chip); |
| |
| /* Summary */ |
| dev_dbg(&spi->dev, |
| "setup succeded\n" |
| " loopback enable = %s\n" |
| " dma enable = %s\n" |
| " insert /ss pulse = %s\n" |
| " period wait = %d\n" |
| " mode = %d\n" |
| " bits per word = %d\n" |
| " min speed = %d Hz\n" |
| " rounded max speed = %d Hz\n", |
| chip->test & SPI_TEST_LBC ? "Yes" : "No", |
| chip->enable_dma ? "Yes" : "No", |
| chip->control & SPI_CONTROL_SSCTL ? "Yes" : "No", |
| chip->period & SPI_PERIOD_WAIT, |
| spi->mode, |
| spi->bits_per_word, |
| spi_speed_hz(SPI_CONTROL_DATARATE_MIN), |
| spi->max_speed_hz); |
| return status; |
| |
| err_first_setup: |
| kfree(chip); |
| return status; |
| } |
| |
| static void cleanup(struct spi_device *spi) |
| { |
| kfree(spi_get_ctldata(spi)); |
| } |
| |
| static int init_queue(struct driver_data *drv_data) |
| { |
| INIT_LIST_HEAD(&drv_data->queue); |
| spin_lock_init(&drv_data->lock); |
| |
| drv_data->run = QUEUE_STOPPED; |
| drv_data->busy = 0; |
| |
| tasklet_init(&drv_data->pump_transfers, |
| pump_transfers, (unsigned long)drv_data); |
| |
| INIT_WORK(&drv_data->work, pump_messages); |
| drv_data->workqueue = create_singlethread_workqueue( |
| drv_data->master->cdev.dev->bus_id); |
| if (drv_data->workqueue == NULL) |
| return -EBUSY; |
| |
| return 0; |
| } |
| |
| static int start_queue(struct driver_data *drv_data) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&drv_data->lock, flags); |
| |
| if (drv_data->run == QUEUE_RUNNING || drv_data->busy) { |
| spin_unlock_irqrestore(&drv_data->lock, flags); |
| return -EBUSY; |
| } |
| |
| drv_data->run = QUEUE_RUNNING; |
| drv_data->cur_msg = NULL; |
| drv_data->cur_transfer = NULL; |
| drv_data->cur_chip = NULL; |
| spin_unlock_irqrestore(&drv_data->lock, flags); |
| |
| queue_work(drv_data->workqueue, &drv_data->work); |
| |
| return 0; |
| } |
| |
| static int stop_queue(struct driver_data *drv_data) |
| { |
| unsigned long flags; |
| unsigned limit = 500; |
| int status = 0; |
| |
| spin_lock_irqsave(&drv_data->lock, flags); |
| |
| /* This is a bit lame, but is optimized for the common execution path. |
| * A wait_queue on the drv_data->busy could be used, but then the common |
| * execution path (pump_messages) would be required to call wake_up or |
| * friends on every SPI message. Do this instead */ |
| drv_data->run = QUEUE_STOPPED; |
| while (!list_empty(&drv_data->queue) && drv_data->busy && limit--) { |
| spin_unlock_irqrestore(&drv_data->lock, flags); |
| msleep(10); |
| spin_lock_irqsave(&drv_data->lock, flags); |
| } |
| |
| if (!list_empty(&drv_data->queue) || drv_data->busy) |
| status = -EBUSY; |
| |
| spin_unlock_irqrestore(&drv_data->lock, flags); |
| |
| return status; |
| } |
| |
| static int destroy_queue(struct driver_data *drv_data) |
| { |
| int status; |
| |
| status = stop_queue(drv_data); |
| if (status != 0) |
| return status; |
| |
| if (drv_data->workqueue) |
| destroy_workqueue(drv_data->workqueue); |
| |
| return 0; |
| } |
| |
| static int spi_imx_probe(struct platform_device *pdev) |
| { |
| struct device *dev = &pdev->dev; |
| struct spi_imx_master *platform_info; |
| struct spi_master *master; |
| struct driver_data *drv_data = NULL; |
| struct resource *res; |
| int irq, status = 0; |
| |
| platform_info = dev->platform_data; |
| if (platform_info == NULL) { |
| dev_err(&pdev->dev, "probe - no platform data supplied\n"); |
| status = -ENODEV; |
| goto err_no_pdata; |
| } |
| |
| /* Allocate master with space for drv_data */ |
| master = spi_alloc_master(dev, sizeof(struct driver_data)); |
| if (!master) { |
| dev_err(&pdev->dev, "probe - cannot alloc spi_master\n"); |
| status = -ENOMEM; |
| goto err_no_mem; |
| } |
| drv_data = spi_master_get_devdata(master); |
| drv_data->master = master; |
| drv_data->master_info = platform_info; |
| drv_data->pdev = pdev; |
| |
| master->bus_num = pdev->id; |
| master->num_chipselect = platform_info->num_chipselect; |
| master->cleanup = cleanup; |
| master->setup = setup; |
| master->transfer = transfer; |
| |
| drv_data->dummy_dma_buf = SPI_DUMMY_u32; |
| |
| /* Find and map resources */ |
| res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
| if (!res) { |
| dev_err(&pdev->dev, "probe - MEM resources not defined\n"); |
| status = -ENODEV; |
| goto err_no_iores; |
| } |
| drv_data->ioarea = request_mem_region(res->start, |
| res->end - res->start + 1, |
| pdev->name); |
| if (drv_data->ioarea == NULL) { |
| dev_err(&pdev->dev, "probe - cannot reserve region\n"); |
| status = -ENXIO; |
| goto err_no_iores; |
| } |
| drv_data->regs = ioremap(res->start, res->end - res->start + 1); |
| if (drv_data->regs == NULL) { |
| dev_err(&pdev->dev, "probe - cannot map IO\n"); |
| status = -ENXIO; |
| goto err_no_iomap; |
| } |
| drv_data->rd_data_phys = (dma_addr_t)res->start; |
| |
| /* Attach to IRQ */ |
| irq = platform_get_irq(pdev, 0); |
| if (irq < 0) { |
| dev_err(&pdev->dev, "probe - IRQ resource not defined\n"); |
| status = -ENODEV; |
| goto err_no_irqres; |
| } |
| status = request_irq(irq, spi_int, IRQF_DISABLED, dev->bus_id, drv_data); |
| if (status < 0) { |
| dev_err(&pdev->dev, "probe - cannot get IRQ (%d)\n", status); |
| goto err_no_irqres; |
| } |
| |
| /* Setup DMA if requested */ |
| drv_data->tx_channel = -1; |
| drv_data->rx_channel = -1; |
| if (platform_info->enable_dma) { |
| /* Get rx DMA channel */ |
| status = imx_dma_request_by_prio(&drv_data->rx_channel, |
| "spi_imx_rx", DMA_PRIO_HIGH); |
| if (status < 0) { |
| dev_err(dev, |
| "probe - problem (%d) requesting rx channel\n", |
| status); |
| goto err_no_rxdma; |
| } else |
| imx_dma_setup_handlers(drv_data->rx_channel, NULL, |
| dma_err_handler, drv_data); |
| |
| /* Get tx DMA channel */ |
| status = imx_dma_request_by_prio(&drv_data->tx_channel, |
| "spi_imx_tx", DMA_PRIO_MEDIUM); |
| if (status < 0) { |
| dev_err(dev, |
| "probe - problem (%d) requesting tx channel\n", |
| status); |
| imx_dma_free(drv_data->rx_channel); |
| goto err_no_txdma; |
| } else |
| imx_dma_setup_handlers(drv_data->tx_channel, |
| dma_tx_handler, dma_err_handler, |
| drv_data); |
| |
| /* Set request source and burst length for allocated channels */ |
| switch (drv_data->pdev->id) { |
| case 1: |
| /* Using SPI1 */ |
| RSSR(drv_data->rx_channel) = DMA_REQ_SPI1_R; |
| RSSR(drv_data->tx_channel) = DMA_REQ_SPI1_T; |
| break; |
| case 2: |
| /* Using SPI2 */ |
| RSSR(drv_data->rx_channel) = DMA_REQ_SPI2_R; |
| RSSR(drv_data->tx_channel) = DMA_REQ_SPI2_T; |
| break; |
| default: |
| dev_err(dev, "probe - bad SPI Id\n"); |
| imx_dma_free(drv_data->rx_channel); |
| imx_dma_free(drv_data->tx_channel); |
| status = -ENODEV; |
| goto err_no_devid; |
| } |
| BLR(drv_data->rx_channel) = SPI_DMA_BLR; |
| BLR(drv_data->tx_channel) = SPI_DMA_BLR; |
| } |
| |
| /* Load default SPI configuration */ |
| writel(SPI_RESET_START, drv_data->regs + SPI_RESET); |
| writel(0, drv_data->regs + SPI_RESET); |
| writel(SPI_DEFAULT_CONTROL, drv_data->regs + SPI_CONTROL); |
| |
| /* Initial and start queue */ |
| status = init_queue(drv_data); |
| if (status != 0) { |
| dev_err(&pdev->dev, "probe - problem initializing queue\n"); |
| goto err_init_queue; |
| } |
| status = start_queue(drv_data); |
| if (status != 0) { |
| dev_err(&pdev->dev, "probe - problem starting queue\n"); |
| goto err_start_queue; |
| } |
| |
| /* Register with the SPI framework */ |
| platform_set_drvdata(pdev, drv_data); |
| status = spi_register_master(master); |
| if (status != 0) { |
| dev_err(&pdev->dev, "probe - problem registering spi master\n"); |
| goto err_spi_register; |
| } |
| |
| dev_dbg(dev, "probe succeded\n"); |
| return 0; |
| |
| err_init_queue: |
| err_start_queue: |
| err_spi_register: |
| destroy_queue(drv_data); |
| |
| err_no_rxdma: |
| err_no_txdma: |
| err_no_devid: |
| free_irq(irq, drv_data); |
| |
| err_no_irqres: |
| iounmap(drv_data->regs); |
| |
| err_no_iomap: |
| release_resource(drv_data->ioarea); |
| kfree(drv_data->ioarea); |
| |
| err_no_iores: |
| spi_master_put(master); |
| |
| err_no_pdata: |
| err_no_mem: |
| return status; |
| } |
| |
| static int __devexit spi_imx_remove(struct platform_device *pdev) |
| { |
| struct driver_data *drv_data = platform_get_drvdata(pdev); |
| int irq; |
| int status = 0; |
| |
| if (!drv_data) |
| return 0; |
| |
| tasklet_kill(&drv_data->pump_transfers); |
| |
| /* Remove the queue */ |
| status = destroy_queue(drv_data); |
| if (status != 0) { |
| dev_err(&pdev->dev, "queue remove failed (%d)\n", status); |
| return status; |
| } |
| |
| /* Reset SPI */ |
| writel(SPI_RESET_START, drv_data->regs + SPI_RESET); |
| writel(0, drv_data->regs + SPI_RESET); |
| |
| /* Release DMA */ |
| if (drv_data->master_info->enable_dma) { |
| RSSR(drv_data->rx_channel) = 0; |
| RSSR(drv_data->tx_channel) = 0; |
| imx_dma_free(drv_data->tx_channel); |
| imx_dma_free(drv_data->rx_channel); |
| } |
| |
| /* Release IRQ */ |
| irq = platform_get_irq(pdev, 0); |
| if (irq >= 0) |
| free_irq(irq, drv_data); |
| |
| /* Release map resources */ |
| iounmap(drv_data->regs); |
| release_resource(drv_data->ioarea); |
| kfree(drv_data->ioarea); |
| |
| /* 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); |
| |
| dev_dbg(&pdev->dev, "remove succeded\n"); |
| |
| return 0; |
| } |
| |
| static void spi_imx_shutdown(struct platform_device *pdev) |
| { |
| struct driver_data *drv_data = platform_get_drvdata(pdev); |
| |
| /* Reset SPI */ |
| writel(SPI_RESET_START, drv_data->regs + SPI_RESET); |
| writel(0, drv_data->regs + SPI_RESET); |
| |
| dev_dbg(&pdev->dev, "shutdown succeded\n"); |
| } |
| |
| #ifdef CONFIG_PM |
| static int suspend_devices(struct device *dev, void *pm_message) |
| { |
| pm_message_t *state = pm_message; |
| |
| if (dev->power.power_state.event != state->event) { |
| dev_warn(dev, "pm state does not match request\n"); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| static int spi_imx_suspend(struct platform_device *pdev, pm_message_t state) |
| { |
| struct driver_data *drv_data = platform_get_drvdata(pdev); |
| int status = 0; |
| |
| status = stop_queue(drv_data); |
| if (status != 0) { |
| dev_warn(&pdev->dev, "suspend cannot stop queue\n"); |
| return status; |
| } |
| |
| dev_dbg(&pdev->dev, "suspended\n"); |
| |
| return 0; |
| } |
| |
| static int spi_imx_resume(struct platform_device *pdev) |
| { |
| struct driver_data *drv_data = platform_get_drvdata(pdev); |
| int status = 0; |
| |
| /* Start the queue running */ |
| status = start_queue(drv_data); |
| if (status != 0) |
| dev_err(&pdev->dev, "problem starting queue (%d)\n", status); |
| else |
| dev_dbg(&pdev->dev, "resumed\n"); |
| |
| return status; |
| } |
| #else |
| #define spi_imx_suspend NULL |
| #define spi_imx_resume NULL |
| #endif /* CONFIG_PM */ |
| |
| static struct platform_driver driver = { |
| .driver = { |
| .name = "imx-spi", |
| .bus = &platform_bus_type, |
| .owner = THIS_MODULE, |
| }, |
| .probe = spi_imx_probe, |
| .remove = __devexit_p(spi_imx_remove), |
| .shutdown = spi_imx_shutdown, |
| .suspend = spi_imx_suspend, |
| .resume = spi_imx_resume, |
| }; |
| |
| static int __init spi_imx_init(void) |
| { |
| return platform_driver_register(&driver); |
| } |
| module_init(spi_imx_init); |
| |
| static void __exit spi_imx_exit(void) |
| { |
| platform_driver_unregister(&driver); |
| } |
| module_exit(spi_imx_exit); |
| |
| MODULE_AUTHOR("Andrea Paterniani, <a.paterniani@swapp-eng.it>"); |
| MODULE_DESCRIPTION("iMX SPI Contoller Driver"); |
| MODULE_LICENSE("GPL"); |