From: luowei Date: Fri, 7 Mar 2014 11:01:09 +0000 (+0800) Subject: add spi driver support X-Git-Tag: firefly_0821_release~6165^2~7^2~2 X-Git-Url: http://demsky.eecs.uci.edu/git/?a=commitdiff_plain;h=b1d4d15af286b8fb98f8e59fa9ca222f8c7a06cf;p=firefly-linux-kernel-4.4.55.git add spi driver support --- diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig old mode 100644 new mode 100755 index 92a9345d7a6b..1eea3cff14c9 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -508,6 +508,23 @@ config SPI_DW_MMIO tristate "Memory-mapped io interface driver for DW SPI core" depends on SPI_DESIGNWARE && HAVE_CLK +config SPI_ROCKCHIP_CORE + tristate "ROCKCHIP SPI controller core support" + help + general driver for SPI controller core from ROCKCHIP + +config SPI_ROCKCHIP + tristate "ROCKCHIP SPI interface driver" + depends on SPI_ROCKCHIP_CORE + +config SPI_ROCKCHIP_DMA + bool "DMA support for ROCKCHIP SPI" + depends on SPI_ROCKCHIP + +config SPI_ROCKCHIP_TEST + bool "ROCKCHIP spi test code" + depends on SPI_ROCKCHIP + # # There are lots of SPI device types, with sensors and memory # being probably the most widely used ones. diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile old mode 100644 new mode 100755 index 33f9c09561e7..c4cbe7e5e2b8 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -74,3 +74,7 @@ obj-$(CONFIG_SPI_TOPCLIFF_PCH) += spi-topcliff-pch.o obj-$(CONFIG_SPI_TXX9) += spi-txx9.o obj-$(CONFIG_SPI_XCOMM) += spi-xcomm.o obj-$(CONFIG_SPI_XILINX) += spi-xilinx.o +obj-$(CONFIG_SPI_ROCKCHIP_CORE) += spi-rockchip-core.o +obj-$(CONFIG_SPI_ROCKCHIP_DMA) += spi-rockchip-dma.o +obj-$(CONFIG_SPI_ROCKCHIP) += spi-rockchip.o +obj-$(CONFIG_SPI_ROCKCHIP_TEST) += spi-rockchip-test.o \ No newline at end of file diff --git a/drivers/spi/spi-rockchip-core.c b/drivers/spi/spi-rockchip-core.c new file mode 100755 index 000000000000..05051a1fbc6d --- /dev/null +++ b/drivers/spi/spi-rockchip-core.c @@ -0,0 +1,1106 @@ +/* + * Designware SPI core controller driver (refer spi_dw.c) + * + * Copyright (c) 2009, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "spi-rockchip-core.h" + +#ifdef CONFIG_DEBUG_FS +#include +#endif + +#define START_STATE ((void *)0) +#define RUNNING_STATE ((void *)1) +#define DONE_STATE ((void *)2) +#define ERROR_STATE ((void *)-1) + +#define QUEUE_RUNNING 0 +#define QUEUE_STOPPED 1 + +#define MRST_SPI_DEASSERT 0 +#define MRST_SPI_ASSERT 1 + + +/* Slave spi_dev related */ +struct chip_data { + u16 cr0; + u8 cs; /* chip select pin */ + u8 n_bytes; /* current is a 1/2/4 byte op */ + u8 tmode; /* TR/TO/RO/EEPROM */ + u8 type; /* SPI/SSP/MicroWire */ + + u8 poll_mode; /* 1 means use poll mode */ + + u8 slave_enable; + u32 dma_width; + u32 rx_threshold; + u32 tx_threshold; + u8 enable_dma; + u8 bits_per_word; + u16 clk_div; /* baud rate divider */ + u32 speed_hz; /* baud rate */ + void (*cs_control)(struct dw_spi *dws, u32 cs, u8 flag); +}; + +#ifdef CONFIG_DEBUG_FS +#define SPI_REGS_BUFSIZE 1024 + +static ssize_t spi_write_proc_data(struct file *file, const char __user *buffer, + size_t count, loff_t *data) +{ + struct dw_spi *dws; + char *buf; + u32 len = 0; + ssize_t ret; + int reg = 0,value = 0; + + dws = file->private_data; + + buf = kzalloc(32, GFP_KERNEL); + if (!buf) + return 0; + + ret = copy_from_user(buf, buffer, count); + if (ret) + { + return ret; + } + + if((strstr(buf, "debug") != NULL) || (strstr(buf, "DEBUG") != NULL)) + { + atomic_set(&dws->debug_flag, 1); + kfree(buf); + printk("%s:open debug\n",__func__); + return count; + } + else if((strstr(buf, "stop") != NULL) || (strstr(buf, "STOP") != NULL)) + { + atomic_set(&dws->debug_flag, 0); + printk("%s:close debug\n",__func__); + } + else if((strstr(buf, "=") != NULL)) + { + printk("%s:invalid command\n",__func__); + return count; + } + + sscanf(buf, "0x%x=0x%x", ®, &value); + + if((reg >= SPIM_CTRLR0) && (reg <= SPIM_DMARDLR)) + { + dw_writew(dws, reg, value); + printk("%s:write data[0x%x] to reg[0x%x] succesfully\n",__func__, value, reg); + } + else + { + printk("%s:data[0x%x] or reg[0x%x] is out of range\n",__func__, value, reg); + } + + kfree(buf); + + return count; +} + +static ssize_t spi_show_regs(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct dw_spi *dws; + char *buf; + u32 len = 0; + ssize_t ret; + + dws = file->private_data; + + buf = kzalloc(SPI_REGS_BUFSIZE, GFP_KERNEL); + if (!buf) + return 0; + + len += snprintf(buf + len, SPI_REGS_BUFSIZE - len, + "MRST SPI0 registers:\n"); + len += snprintf(buf + len, SPI_REGS_BUFSIZE - len, + "=================================\n"); + len += snprintf(buf + len, SPI_REGS_BUFSIZE - len, + "CTRL0: \t\t0x%08x\n", dw_readl(dws, SPIM_CTRLR0)); + len += snprintf(buf + len, SPI_REGS_BUFSIZE - len, + "CTRL1: \t\t0x%08x\n", dw_readl(dws, SPIM_CTRLR1)); + len += snprintf(buf + len, SPI_REGS_BUFSIZE - len, + "SSIENR: \t0x%08x\n", dw_readl(dws, SPIM_SSIENR)); + len += snprintf(buf + len, SPI_REGS_BUFSIZE - len, + "SER: \t\t0x%08x\n", dw_readl(dws, SPIM_SER)); + len += snprintf(buf + len, SPI_REGS_BUFSIZE - len, + "BAUDR: \t\t0x%08x\n", dw_readl(dws, SPIM_BAUDR)); + len += snprintf(buf + len, SPI_REGS_BUFSIZE - len, + "TXFTLR: \t0x%08x\n", dw_readl(dws, SPIM_TXFTLR)); + len += snprintf(buf + len, SPI_REGS_BUFSIZE - len, + "RXFTLR: \t0x%08x\n", dw_readl(dws, SPIM_RXFTLR)); + len += snprintf(buf + len, SPI_REGS_BUFSIZE - len, + "TXFLR: \t\t0x%08x\n", dw_readl(dws, SPIM_TXFLR)); + len += snprintf(buf + len, SPI_REGS_BUFSIZE - len, + "RXFLR: \t\t0x%08x\n", dw_readl(dws, SPIM_RXFLR)); + len += snprintf(buf + len, SPI_REGS_BUFSIZE - len, + "SR: \t\t0x%08x\n", dw_readl(dws, SPIM_SR)); + len += snprintf(buf + len, SPI_REGS_BUFSIZE - len, + "IMR: \t\t0x%08x\n", dw_readl(dws, SPIM_IMR)); + len += snprintf(buf + len, SPI_REGS_BUFSIZE - len, + "ISR: \t\t0x%08x\n", dw_readl(dws, SPIM_ISR)); + len += snprintf(buf + len, SPI_REGS_BUFSIZE - len, + "DMACR: \t\t0x%08x\n", dw_readl(dws, SPIM_DMACR)); + len += snprintf(buf + len, SPI_REGS_BUFSIZE - len, + "DMATDLR: \t0x%08x\n", dw_readl(dws, SPIM_DMATDLR)); + len += snprintf(buf + len, SPI_REGS_BUFSIZE - len, + "DMARDLR: \t0x%08x\n", dw_readl(dws, SPIM_DMARDLR)); + len += snprintf(buf + len, SPI_REGS_BUFSIZE - len, + "=================================\n"); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, len); + kfree(buf); + return ret; +} + +static const struct file_operations spi_regs_ops = { + .owner = THIS_MODULE, + .open = simple_open, + .read = spi_show_regs, + .write = spi_write_proc_data, + .llseek = default_llseek, +}; + +static int spi_debugfs_init(struct dw_spi *dws) +{ + dws->debugfs = debugfs_create_dir("spi", NULL); + if (!dws->debugfs) + return -ENOMEM; + + debugfs_create_file("registers", S_IFREG | S_IRUGO, + dws->debugfs, (void *)dws, &spi_regs_ops); + return 0; +} + +static void spi_debugfs_remove(struct dw_spi *dws) +{ + if (dws->debugfs) + debugfs_remove_recursive(dws->debugfs); +} + +#else +static inline int spi_debugfs_init(struct dw_spi *dws) +{ + return 0; +} + +static inline void spi_debugfs_remove(struct dw_spi *dws) +{ +} +#endif /* CONFIG_DEBUG_FS */ + +static void wait_till_not_busy(struct dw_spi *dws) +{ + unsigned long end = jiffies + 1 + usecs_to_jiffies(1000); + //if spi was slave, it is SR_BUSY always. + if(dws->cur_chip) { + if(dws->cur_chip->slave_enable == 1) + return; + } + + while (time_before(jiffies, end)) { + if (!(dw_readw(dws, SPIM_SR) & SR_BUSY)) + return; + } + dev_err(&dws->master->dev, + "DW SPI: Status keeps busy for 1000us after a read/write!\n"); +} + + +static void flush(struct dw_spi *dws) +{ + while (!(dw_readw(dws, SPIM_SR) & SR_RF_EMPT)) + dw_readw(dws, SPIM_RXDR); + + wait_till_not_busy(dws); +} + + +/* Return the max entries we can fill into tx fifo */ +static inline u32 tx_max(struct dw_spi *dws) +{ + u32 tx_left, tx_room, rxtx_gap; + + tx_left = (dws->tx_end - dws->tx) / dws->n_bytes; + tx_room = dws->fifo_len - dw_readw(dws, SPIM_TXFLR); + + /* + * Another concern is about the tx/rx mismatch, we + * though to use (dws->fifo_len - rxflr - txflr) as + * one maximum value for tx, but it doesn't cover the + * data which is out of tx/rx fifo and inside the + * shift registers. So a control from sw point of + * view is taken. + */ + //rxtx_gap = ((dws->rx_end - dws->rx) - (dws->tx_end - dws->tx)) + // / dws->n_bytes; + + return min(tx_left, tx_room); +} + +/* Return the max entries we should read out of rx fifo */ +static inline u32 rx_max(struct dw_spi *dws) +{ + u32 rx_left = (dws->rx_end - dws->rx) / dws->n_bytes; + + return min(rx_left, (u32)dw_readw(dws, SPIM_RXFLR)); +} + +static void dw_writer(struct dw_spi *dws) +{ + u32 max = tx_max(dws); + u16 txw = 0; + + DBG_SPI("%dbyte tx:",dws->n_bytes); + while (max--) { + /* Set the tx word if the transfer's original "tx" is not null */ + if (dws->tx_end - dws->len) { + if (dws->n_bytes == 1) + { + txw = *(u8 *)(dws->tx); + DBG_SPI("0x%02x,", *(u8 *)(dws->tx)); + } + else + { + txw = *(u16 *)(dws->tx); + DBG_SPI("0x%02x,", *(u16 *)(dws->tx)); + } + } + dw_writew(dws, SPIM_TXDR, txw); + dws->tx += dws->n_bytes; + } + + //it is neccessary + wait_till_not_busy(dws); + + DBG_SPI("\n"); +} + +static void dw_reader(struct dw_spi *dws) +{ + u32 max = rx_max(dws); + u16 rxw; + + DBG_SPI("%dbyte rx:",dws->n_bytes); + + while (max--) { + rxw = dw_readw(dws, SPIM_RXDR); + /* Care rx only if the transfer's original "rx" is not null */ + if (dws->rx_end - dws->len) { + if (dws->n_bytes == 1) + { + *(u8 *)(dws->rx) = rxw; + DBG_SPI("0x%02x,", *(u8 *)(dws->rx)); + } + else + { + *(u16 *)(dws->rx) = rxw; + DBG_SPI("0x%02x,", *(u16 *)(dws->rx)); + } + } + + dws->rx += dws->n_bytes; + } + + DBG_SPI("\n"); +} + +static int reader_all(struct dw_spi *dws) +{ + u16 rxw; + while (!(dw_readw(dws, SPIM_SR) & SR_RF_EMPT) + && (dws->rx < dws->rx_end)) { + dw_reader(dws); + wait_till_not_busy(dws); + } + + return dws->rx == dws->rx_end; +} + + +static void *next_transfer(struct dw_spi *dws) +{ + struct spi_message *msg = dws->cur_msg; + struct spi_transfer *trans = dws->cur_transfer; + + /* Move to next transfer */ + if (trans->transfer_list.next != &msg->transfers) { + dws->cur_transfer = + list_entry(trans->transfer_list.next, + struct spi_transfer, + transfer_list); + return RUNNING_STATE; + } else + return DONE_STATE; +} + +/* + * Note: first step is the protocol driver prepares + * a dma-capable memory, and this func just need translate + * the virt addr to physical + */ +static int map_dma_buffers(struct dw_spi *dws) +{ + if (!dws->cur_msg->is_dma_mapped + || !dws->dma_inited + || !dws->cur_chip->enable_dma + || !dws->dma_ops) + return 0; + + if (dws->cur_transfer->tx_dma) + dws->tx_dma = dws->cur_transfer->tx_dma; + + if (dws->cur_transfer->rx_dma) + dws->rx_dma = dws->cur_transfer->rx_dma; + + DBG_SPI("%s:line=%d\n",__func__,__LINE__); + return 1; +} + +/* Caller already set message->status; dma and pio irqs are blocked */ +static void giveback(struct dw_spi *dws) +{ + struct spi_transfer *last_transfer; + unsigned long flags; + struct spi_message *msg; + struct spi_message *next_msg; + + spin_lock_irqsave(&dws->lock, flags); + msg = dws->cur_msg; + dws->cur_msg = NULL; + dws->cur_transfer = NULL; + dws->prev_chip = dws->cur_chip; + dws->cur_chip = NULL; + dws->dma_mapped = 0; + //queue_work(dws->workqueue, &dws->pump_messages); + + /*it is important to close intterrupt*/ + spi_mask_intr(dws, 0xff); + //rk29xx_writew(dws, SPIM_DMACR, 0); + + spin_unlock_irqrestore(&dws->lock, flags); + + last_transfer = list_entry(msg->transfers.prev, + struct spi_transfer, + transfer_list); + + if (!last_transfer->cs_change && dws->cs_control) + dws->cs_control(dws, msg->spi->chip_select, MRST_SPI_DEASSERT); + + msg->state = NULL; + + /* get a pointer to the next message, if any */ + next_msg = spi_get_next_queued_message(dws->master); + + /* see if the next and current messages point + * to the same chip + */ + if (next_msg && next_msg->spi != msg->spi) + next_msg = NULL; + + spi_finalize_current_message(dws->master); + dws->cur_chip = NULL; + + + DBG_SPI("%s:line=%d,tx_left=%d\n",__func__,__LINE__, (dws->tx_end - dws->tx) / dws->n_bytes); +} + + +static void int_error_stop(struct dw_spi *dws, const char *msg) +{ + /* Stop the hw */ + flush(dws); + spi_enable_chip(dws, 0); + + dev_err(&dws->master->dev, "%s\n", msg); + dws->cur_msg->state = ERROR_STATE; + tasklet_schedule(&dws->pump_transfers); + + DBG_SPI("%s:line=%d\n",__func__,__LINE__); +} + +void dw_spi_xfer_done(struct dw_spi *dws) +{ + /* Update total byte transferred return count actual bytes read */ + dws->cur_msg->actual_length += dws->len; + + /* Move to next transfer */ + dws->cur_msg->state = next_transfer(dws); + + /* Handle end of message */ + if (dws->cur_msg->state == DONE_STATE) { + dws->cur_msg->status = 0; + giveback(dws); + } else + tasklet_schedule(&dws->pump_transfers); + + DBG_SPI("%s:line=%d\n",__func__,__LINE__); +} +EXPORT_SYMBOL_GPL(dw_spi_xfer_done); + +static irqreturn_t interrupt_transfer(struct dw_spi *dws) +{ + u16 irq_status; + u32 int_level = dws->fifo_len / 2; + u32 left; + + + irq_status = dw_readw(dws, SPIM_ISR) & 0x1f; + + DBG_SPI("%s:line=%d,irq_status=0x%x\n",__func__,__LINE__,irq_status); + + /* Error handling */ + if (irq_status & (SPI_INT_TXOI | SPI_INT_RXOI | SPI_INT_RXUI)) { + dw_writew(dws, SPIM_ICR, SPI_CLEAR_INT_TXOI | SPI_CLEAR_INT_RXOI | SPI_CLEAR_INT_RXUI); + printk("%s:irq_status=0x%x\n",__func__,irq_status); + int_error_stop(dws, "interrupt_transfer: fifo overrun/underrun"); + return IRQ_HANDLED; + } + + if (irq_status & SPI_INT_TXEI) + { + spi_mask_intr(dws, SPI_INT_TXEI); + dw_writer(dws); + + if (dws->rx) { + reader_all(dws); + } + + /* Re-enable the IRQ if there is still data left to tx */ + if (dws->tx_end > dws->tx) + spi_umask_intr(dws, SPI_INT_TXEI); + else + dw_spi_xfer_done(dws); + } + + if (irq_status & SPI_INT_RXFI) { + spi_mask_intr(dws, SPI_INT_RXFI); + + reader_all(dws); + + /* Re-enable the IRQ if there is still data left to rx */ + if (dws->rx_end > dws->rx) { + left = ((dws->rx_end - dws->rx) / dws->n_bytes) - 1; + left = (left > int_level) ? int_level : left; + + dw_writew(dws, SPIM_RXFTLR, left); + spi_umask_intr(dws, SPI_INT_RXFI); + } + else { + dw_spi_xfer_done(dws); + } + + } + + return IRQ_HANDLED; +} + + +static irqreturn_t dw_spi_irq(int irq, void *dev_id) +{ + struct dw_spi *dws = dev_id; + u16 irq_status = dw_readw(dws, SPIM_ISR)&0x3f; + + if (!irq_status) + return IRQ_NONE; + + if (!dws->cur_msg) { + spi_mask_intr(dws, SPI_INT_TXEI); + return IRQ_HANDLED; + } + + return dws->transfer_handler(dws); +} + +/* Must be called inside pump_transfers() */ +static void poll_transfer(struct dw_spi *dws) +{ + DBG_SPI("%s:len=%d\n",__func__, dws->len); + + do { + dw_writer(dws); + dw_reader(dws); + cpu_relax(); + } while (dws->rx_end > dws->rx); + + dw_spi_xfer_done(dws); + +} + +static void pump_transfers(unsigned long data) +{ + struct dw_spi *dws = (struct dw_spi *)data; + struct spi_message *message = NULL; + struct spi_transfer *transfer = NULL; + struct spi_transfer *previous = NULL; + struct spi_device *spi = NULL; + struct chip_data *chip = NULL; + u8 bits = 0; + u8 imask = 0; + u8 cs_change = 0; + u16 txint_level = 0; + u16 rxint_level = 0; + u16 clk_div = 0; + u32 speed = 0; + u32 cr0 = 0; + + + /* Get current state information */ + message = dws->cur_msg; + transfer = dws->cur_transfer; + chip = dws->cur_chip; + spi = message->spi; + + if (unlikely(!chip->clk_div)) + chip->clk_div = dws->max_freq / chip->speed_hz; + + if (message->state == ERROR_STATE) { + message->status = -EIO; + goto early_exit; + } + + /* Handle end of message */ + if (message->state == DONE_STATE) { + message->status = 0; + goto early_exit; + } + + /* Delay if requested at end of 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); + } + + dws->n_bytes = chip->n_bytes; + dws->dma_width = chip->dma_width; + dws->cs_control = chip->cs_control; + + dws->rx_dma = transfer->rx_dma; + dws->tx_dma = transfer->tx_dma; + dws->tx = (void *)transfer->tx_buf; + dws->tx_end = dws->tx + transfer->len; + dws->rx = transfer->rx_buf; + dws->rx_end = dws->rx + transfer->len; + dws->cs_change = transfer->cs_change; + dws->len = dws->cur_transfer->len; + if (chip != dws->prev_chip) + cs_change = 1; + + cr0 = chip->cr0; + + + DBG_SPI("%s:len=%d\n",__func__,dws->len); + + /* Handle per transfer options for bpw and speed */ + if (transfer->speed_hz) { + speed = chip->speed_hz; + + if (transfer->speed_hz != speed) { + speed = transfer->speed_hz; + if (speed > dws->max_freq) { + printk(KERN_ERR "MRST SPI0: unsupported" + "freq: %dHz\n", speed); + message->status = -EIO; + goto early_exit; + } + + /* clk_div doesn't support odd number */ + clk_div = dws->max_freq / speed; + clk_div = (clk_div + 1) & 0xfffe; + + chip->speed_hz = speed; + chip->clk_div = clk_div; + } + } + if (transfer->bits_per_word) { + bits = transfer->bits_per_word; + + switch (bits) { + case 8: + case 16: + dws->n_bytes = dws->dma_width = bits >> 3; + break; + default: + printk(KERN_ERR "MRST SPI0: unsupported bits:" + "%db\n", bits); + message->status = -EIO; + goto early_exit; + } + + cr0 =((dws->n_bytes) << SPI_DFS_OFFSET) + | (SPI_HALF_WORLD_OFF << SPI_HALF_WORLD_TX_OFFSET) + | (SPI_SSN_DELAY_ONE << SPI_SSN_DELAY_OFFSET) + | (chip->type << SPI_FRF_OFFSET) + | (spi->mode << SPI_MODE_OFFSET) + | (chip->tmode << SPI_TMOD_OFFSET); + } + message->state = RUNNING_STATE; + + /* + * Adjust transfer mode if necessary. Requires platform dependent + * chipselect mechanism. + */ + if (dws->cs_control) { + if (dws->rx && dws->tx) + chip->tmode = SPI_TMOD_TR; + else if (dws->rx) + chip->tmode = SPI_TMOD_RO; + else + chip->tmode = SPI_TMOD_TO; + + //cr0 &= ~SPI_TMOD_MASK; + //cr0 |= (chip->tmode << SPI_TMOD_OFFSET); + + cr0 &= ~(0x3 << SPI_MODE_OFFSET); + cr0 &= ~(0x3 << SPI_TMOD_OFFSET); + cr0 &= ~(0x1 << SPI_OPMOD_OFFSET); + cr0 |= (spi->mode << SPI_MODE_OFFSET); + cr0 |= (chip->tmode << SPI_TMOD_OFFSET); + cr0 |= ((chip->slave_enable & 1) << SPI_OPMOD_OFFSET); + } + + /* Check if current transfer is a DMA transaction */ + dws->dma_mapped = map_dma_buffers(dws); + + /* + * Interrupt mode + * we only need set the TXEI IRQ, as TX/RX always happen syncronizely + */ + if (!dws->dma_mapped && !chip->poll_mode) { + int templen ; + + if (chip->tmode == SPI_TMOD_RO) { + templen = dws->len / dws->n_bytes - 1; + rxint_level = dws->fifo_len / 2; + rxint_level = (templen > rxint_level) ? rxint_level : templen; + imask |= SPI_INT_RXFI; + } + else { + templen = dws->len / dws->n_bytes; + txint_level = dws->fifo_len / 2; + txint_level = (templen > txint_level) ? txint_level : templen; + imask |= SPI_INT_TXEI | SPI_INT_TXOI; + } + dws->transfer_handler = interrupt_transfer; + } + + /* + * Reprogram registers only if + * 1. chip select changes + * 2. clk_div is changed + * 3. control value changes + */ + if (dw_readw(dws, SPIM_CTRLR0) != cr0 || cs_change || clk_div || imask) { + spi_enable_chip(dws, 0); + if (dw_readl(dws, SPIM_CTRLR0) != cr0) + dw_writel(dws, SPIM_CTRLR0, cr0); + + spi_set_clk(dws, clk_div ? clk_div : chip->clk_div); + spi_chip_sel(dws, spi->chip_select); + + dw_writew(dws, SPIM_CTRLR1, dws->len-1); + + if (txint_level) + dw_writew(dws, SPIM_TXFTLR, txint_level); + + if (rxint_level) + { + dw_writew(dws, SPIM_RXFTLR, rxint_level); + DBG_SPI("%s:rxint_level=%d\n",__func__,rxint_level); + } + /* Set the interrupt mask, for poll mode just diable all int */ + spi_mask_intr(dws, 0xff); + if (imask) + spi_umask_intr(dws, imask); + + spi_enable_chip(dws, 1); + + if (cs_change) + dws->prev_chip = chip; + } + + if (dws->dma_mapped) + dws->dma_ops->dma_transfer(dws, cs_change); + + if (chip->poll_mode) + poll_transfer(dws); + + return; + +early_exit: + giveback(dws); + return; +} + +static void pump_messages(struct work_struct *work) +{ + struct dw_spi *dws = + container_of(work, struct dw_spi, pump_messages); + unsigned long flags; + + /* Lock queue and check for queue work */ + spin_lock_irqsave(&dws->lock, flags); + if (list_empty(&dws->queue) || dws->run == QUEUE_STOPPED) { + dws->busy = 0; + spin_unlock_irqrestore(&dws->lock, flags); + return; + } + + /* Make sure we are not already running a message */ + if (dws->cur_msg) { + spin_unlock_irqrestore(&dws->lock, flags); + return; + } + + /* Extract head of queue */ + dws->cur_msg = list_entry(dws->queue.next, struct spi_message, queue); + list_del_init(&dws->cur_msg->queue); + + /* Initial message state*/ + dws->cur_msg->state = START_STATE; + dws->cur_transfer = list_entry(dws->cur_msg->transfers.next, + struct spi_transfer, + transfer_list); + dws->cur_chip = spi_get_ctldata(dws->cur_msg->spi); + + /* Mark as busy and launch transfers */ + tasklet_schedule(&dws->pump_transfers); + + dws->busy = 1; + spin_unlock_irqrestore(&dws->lock, flags); +} + + +static int dw_spi_transfer_one_message(struct spi_master *master, + struct spi_message *msg) +{ + struct dw_spi *dws = spi_master_get_devdata(master); + + dws->cur_msg = msg; + /* Initial message state*/ + dws->cur_msg->state = START_STATE; + dws->cur_transfer = list_entry(dws->cur_msg->transfers.next, + struct spi_transfer, + transfer_list); + + /* prepare to setup the SSP, in pump_transfers, using the per + * chip configuration */ + dws->cur_chip = spi_get_ctldata(dws->cur_msg->spi); + + /* Mark as busy and launch transfers */ + tasklet_schedule(&dws->pump_transfers); + + DBG_SPI("%s:line=%d\n",__func__,__LINE__); + return 0; +} + +static int dw_spi_prepare_transfer(struct spi_master *master) +{ + struct dw_spi *dws = spi_master_get_devdata(master); + + //pm_runtime_get_sync(&dws->pdev->dev); + + DBG_SPI("%s:line=%d\n",__func__,__LINE__); + return 0; +} + +static int dw_spi_unprepare_transfer(struct spi_master *master) +{ + struct dw_spi *dws = spi_master_get_devdata(master); + + /* Disable the SSP now */ + //write_SSCR0(read_SSCR0(dws->ioaddr) & ~SSCR0_SSE, + // dws->ioaddr); + + //pm_runtime_mark_last_busy(&dws->pdev->dev); + //pm_runtime_put_autosuspend(&dws->pdev->dev); + + DBG_SPI("%s:line=%d\n",__func__,__LINE__); + return 0; +} + +/* This may be called twice for each spi dev */ +static int dw_spi_setup(struct spi_device *spi) +{ + struct dw_spi_chip *chip_info = NULL; + struct chip_data *chip; + + if (spi->bits_per_word != 8 && spi->bits_per_word != 16) + return -EINVAL; + + /* Only alloc on first setup */ + chip = spi_get_ctldata(spi); + if (!chip) { + chip = kzalloc(sizeof(struct chip_data), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->cs_control = spi_cs_control; + chip->enable_dma = 0; + } + + /* + * Protocol drivers may change the chip settings, so... + * if chip_info exists, use it + */ + chip_info = spi->controller_data; + + /* chip_info doesn't always exist */ + if (chip_info) { + if (chip_info->cs_control) + chip->cs_control = chip_info->cs_control; + + chip->poll_mode = chip_info->poll_mode; + chip->type = chip_info->type; + + chip->rx_threshold = 0; + chip->tx_threshold = 0; + + chip->enable_dma = chip_info->enable_dma; + } + + if (spi->bits_per_word <= 8) { + chip->n_bytes = 1; + chip->dma_width = 1; + } else if (spi->bits_per_word <= 16) { + chip->n_bytes = 2; + chip->dma_width = 2; + } else { + /* Never take >16b case for MRST SPIC */ + dev_err(&spi->dev, "invalid wordsize\n"); + return -EINVAL; + } + chip->bits_per_word = spi->bits_per_word; + + if (!spi->max_speed_hz) { + dev_err(&spi->dev, "No max speed HZ parameter\n"); + return -EINVAL; + } + chip->speed_hz = spi->max_speed_hz; + + chip->tmode = 0; /* Tx & Rx */ + /* Default SPI mode is SCPOL = 0, SCPH = 0 */ + chip->cr0 = ((chip->n_bytes) << SPI_DFS_OFFSET) + | (SPI_HALF_WORLD_OFF << SPI_HALF_WORLD_TX_OFFSET) + | (SPI_SSN_DELAY_ONE << SPI_SSN_DELAY_OFFSET) + | (chip->type << SPI_FRF_OFFSET) + | (spi->mode << SPI_MODE_OFFSET) + | (chip->tmode << SPI_TMOD_OFFSET); + + spi_set_ctldata(spi, chip); + + //printk("%s:line=%d\n",__func__,__LINE__); + return 0; +} + +static void dw_spi_cleanup(struct spi_device *spi) +{ + struct chip_data *chip = spi_get_ctldata(spi); + kfree(chip); +} + + +/* Restart the controller, disable all interrupts, clean rx fifo */ +static void spi_hw_init(struct dw_spi *dws) +{ + spi_enable_chip(dws, 0); + spi_mask_intr(dws, 0xff); + + /* + * Try to detect the FIFO depth if not set by interface driver, + * the depth could be from 2 to 32 from HW spec + */ + if (!dws->fifo_len) { + u32 fifo; + for (fifo = 2; fifo <= 31; fifo++) { + dw_writew(dws, SPIM_TXFTLR, fifo); + if (fifo != dw_readw(dws, SPIM_TXFTLR)) + break; + } + + dws->fifo_len = (fifo == 31) ? 0 : fifo; + dw_writew(dws, SPIM_TXFTLR, 0); + } + + spi_enable_chip(dws, 1); + flush(dws); + DBG_SPI("%s:fifo_len=%d\n",__func__, dws->fifo_len); +} + +int dw_spi_add_host(struct dw_spi *dws) +{ + struct spi_master *master; + int ret; + + BUG_ON(dws == NULL); + + master = spi_alloc_master(dws->parent_dev, 0); + if (!master) { + ret = -ENOMEM; + goto exit; + } + + dws->master = master; + dws->type = SSI_MOTO_SPI; + dws->prev_chip = NULL; + dws->dma_inited = 0; + dws->dma_addr = (dma_addr_t)(dws->paddr + 0x60); + snprintf(dws->name, sizeof(dws->name), "dw_spi%d", + dws->bus_num); + + ret = request_irq(dws->irq, dw_spi_irq, IRQF_SHARED, + dws->name, dws); + if (ret < 0) { + dev_err(&master->dev, "can not get IRQ\n"); + goto err_free_master; + } + + master->mode_bits = SPI_CPOL | SPI_CPHA; + master->bus_num = dws->bus_num; + master->num_chipselect = dws->num_cs; + master->cleanup = dw_spi_cleanup; + master->setup = dw_spi_setup; + master->transfer_one_message = dw_spi_transfer_one_message; + master->prepare_transfer_hardware = dw_spi_prepare_transfer; + master->unprepare_transfer_hardware = dw_spi_unprepare_transfer; + + spin_lock_init(&dws->lock); + tasklet_init(&dws->pump_transfers, + pump_transfers, (unsigned long)dws); + + + /* Basic HW init */ + spi_hw_init(dws); + + if (dws->dma_ops && dws->dma_ops->dma_init) { + ret = dws->dma_ops->dma_init(dws); + if (ret) { + dev_warn(&master->dev, "DMA init failed\n"); + dws->dma_inited = 0; + } + } + + spi_master_set_devdata(master, dws); + ret = spi_register_master(master); + if (ret) { + dev_err(&master->dev, "problem registering spi master\n"); + goto err_queue_alloc; + } + + spi_debugfs_init(dws); + + + DBG_SPI("%s:bus_num=%d\n",__func__, dws->bus_num); + return 0; + +err_queue_alloc: + if (dws->dma_ops && dws->dma_ops->dma_exit) + dws->dma_ops->dma_exit(dws); +err_diable_hw: + spi_enable_chip(dws, 0); + free_irq(dws->irq, dws); +err_free_master: + spi_master_put(master); +exit: + return ret; +} +EXPORT_SYMBOL_GPL(dw_spi_add_host); + +void dw_spi_remove_host(struct dw_spi *dws) +{ + int status = 0; + + if (!dws) + return; + + spi_debugfs_remove(dws); + + if (dws->dma_ops && dws->dma_ops->dma_exit) + dws->dma_ops->dma_exit(dws); + + spi_enable_chip(dws, 0); + /* Disable clk */ + spi_set_clk(dws, 0); + free_irq(dws->irq, dws); + + /* Disconnect from the SPI framework */ + spi_unregister_master(dws->master); + + + DBG_SPI("%s:bus_num=%d\n",__func__, dws->bus_num); +} +EXPORT_SYMBOL_GPL(dw_spi_remove_host); + +int dw_spi_suspend_host(struct dw_spi *dws) +{ + int ret = 0; + + ret = spi_master_suspend(dws->master); + if (ret != 0) + return ret; + + spi_enable_chip(dws, 0); + spi_set_clk(dws, 0); + + clk_disable_unprepare(dws->clk_spi); + + DBG_SPI("%s:bus_num=%d\n",__func__, dws->bus_num); + return ret; +} +EXPORT_SYMBOL_GPL(dw_spi_suspend_host); + +int dw_spi_resume_host(struct dw_spi *dws) +{ + int ret; + + /* Enable the SPI clock */ + clk_prepare_enable(dws->clk_spi); + + spi_hw_init(dws); + + /* Start the queue running */ + ret = spi_master_resume(dws->master); + if (ret != 0) { + printk("%s:problem starting queue (%d)\n", __func__, ret); + return ret; + } + + DBG_SPI("%s:bus_num=%d\n",__func__, dws->bus_num); + return ret; +} +EXPORT_SYMBOL_GPL(dw_spi_resume_host); + +MODULE_AUTHOR("Luo Wei "); +MODULE_DESCRIPTION("Driver for DesignWare SPI controller core"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/spi/spi-rockchip-core.h b/drivers/spi/spi-rockchip-core.h new file mode 100755 index 000000000000..3225a98d6221 --- /dev/null +++ b/drivers/spi/spi-rockchip-core.h @@ -0,0 +1,355 @@ +#ifndef DW_SPI_HEADER_H +#define DW_SPI_HEADER_H + +#include +#include + + +#if 1 +#define DBG_SPI(x...) if(atomic_read(&dws->debug_flag) == 1) printk(x) +#else +#define DBG_SPI(x...) +#endif + +/* SPI register offsets */ +#define SPIM_CTRLR0 0x0000 +#define SPIM_CTRLR1 0x0004 +#define SPIM_SSIENR 0x0008 +#define SPIM_SER 0x000c +#define SPIM_BAUDR 0x0010 +#define SPIM_TXFTLR 0x0014 +#define SPIM_RXFTLR 0x0018 +#define SPIM_TXFLR 0x001c +#define SPIM_RXFLR 0x0020 +#define SPIM_SR 0x0024 +#define SPIM_IPR 0x0028 +#define SPIM_IMR 0x002c +#define SPIM_ISR 0x0030 +#define SPIM_RISR 0x0034 +#define SPIM_ICR 0x0038 +#define SPIM_DMACR 0x003c +#define SPIM_DMATDLR 0x0040 +#define SPIM_DMARDLR 0x0044 +#define SPIM_TXDR 0x0400 +#define SPIM_RXDR 0x0800 + +/* --------Bit fields in CTRLR0--------begin */ + +#define SPI_DFS_OFFSET 0 /* Data Frame Size */ +#define SPI_DFS_4BIT 0x00 +#define SPI_DFS_8BIT 0x01 +#define SPI_DFS_16BIT 0x02 +#define SPI_DFS_RESV 0x03 + +#define SPI_FRF_OFFSET 16 /* Frame Format */ +#define SPI_FRF_SPI 0x00 /* motorola spi */ +#define SPI_FRF_SSP 0x01 /* Texas Instruments SSP*/ +#define SPI_FRF_MICROWIRE 0x02 /* National Semiconductors Microwire */ +#define SPI_FRF_RESV 0x03 + +#define SPI_MODE_OFFSET 6 /* SCPH & SCOL */ + +#define SPI_SCPH_OFFSET 6 /* Serial Clock Phase */ +#define SPI_SCPH_TOGMID 0 /* Serial clock toggles in middle of first data bit */ +#define SPI_SCPH_TOGSTA 1 /* Serial clock toggles at start of first data bit */ + +#define SPI_SCOL_OFFSET 7 /* Serial Clock Polarity */ + +#define SPI_OPMOD_OFFSET 20 +#define SPI_OPMOD_MASTER 0 +#define SPI_OPMOD_SLAVE 1 + +#define SPI_TMOD_OFFSET 18 /* Transfer Mode */ +//#define SPI_TMOD_MASK (0x3 << SPI_TMOD_OFFSET) +#define SPI_TMOD_TR 0x00 /* xmit & recv */ +#define SPI_TMOD_TO 0x01 /* xmit only */ +#define SPI_TMOD_RO 0x02 /* recv only */ +#define SPI_TMOD_RESV 0x03 + +#define SPI_CFS_OFFSET 2 /* Control Frame Size */ + +#define SPI_CSM_OFFSET 8 /* Chip Select Mode */ +#define SPI_CSM_KEEP 0x00 /* ss_n keep low after every frame data is transferred */ +#define SPI_CSM_HALF 0x01 /* ss_n be high for half sclk_out cycles after every frame data is transferred */ +#define SPI_CSM_ONE 0x02 /* ss_n be high for one sclk_out cycle after every frame data is transferred */ + +#define SPI_SSN_DELAY_OFFSET 10 +#define SPI_SSN_DELAY_HALF 0x00 +#define SPI_SSN_DELAY_ONE 0x01 + +#define SPI_HALF_WORLD_TX_OFFSET 13 +#define SPI_HALF_WORLD_ON 0x00 +#define SPI_HALF_WORLD_OFF 0x01 + + +/* --------Bit fields in CTRLR0--------end */ + + +/* Bit fields in SR, 7 bits */ +#define SR_MASK 0x7f /* cover 7 bits */ +#define SR_BUSY (1 << 0) +#define SR_TF_FULL (1 << 1) +#define SR_TF_EMPT (1 << 2) +#define SR_RF_EMPT (1 << 3) +#define SR_RF_FULL (1 << 4) + +/* Bit fields in ISR, IMR, RISR, 7 bits */ +#define SPI_INT_TXEI (1 << 0) +#define SPI_INT_TXOI (1 << 1) +#define SPI_INT_RXUI (1 << 2) +#define SPI_INT_RXOI (1 << 3) +#define SPI_INT_RXFI (1 << 4) + +/* Bit fields in DMACR */ +#define SPI_DMACR_TX_ENABLE (1 << 1) +#define SPI_DMACR_RX_ENABLE (1 << 0) + +/* Bit fields in ICR */ +#define SPI_CLEAR_INT_ALL (1<< 0) +#define SPI_CLEAR_INT_RXUI (1 << 1) +#define SPI_CLEAR_INT_RXOI (1 << 2) +#define SPI_CLEAR_INT_TXOI (1 << 3) + + +#if 0 + + +/* Bit fields in CTRLR0 */ +#define SPI_DFS_OFFSET 0 + +#define SPI_FRF_OFFSET 4 +#define SPI_FRF_SPI 0x0 +#define SPI_FRF_SSP 0x1 +#define SPI_FRF_MICROWIRE 0x2 +#define SPI_FRF_RESV 0x3 + +#define SPI_MODE_OFFSET 6 +#define SPI_SCPH_OFFSET 6 +#define SPI_SCOL_OFFSET 7 + +#define SPI_TMOD_OFFSET 8 +#define SPI_TMOD_MASK (0x3 << SPI_TMOD_OFFSET) +#define SPI_TMOD_TR 0x0 /* xmit & recv */ +#define SPI_TMOD_TO 0x1 /* xmit only */ +#define SPI_TMOD_RO 0x2 /* recv only */ +#define SPI_TMOD_EPROMREAD 0x3 /* eeprom read mode */ + +#define SPI_SLVOE_OFFSET 10 +#define SPI_SRL_OFFSET 11 +#define SPI_CFS_OFFSET 12 + +/* Bit fields in SR, 7 bits */ +#define SR_MASK 0x7f /* cover 7 bits */ +#define SR_BUSY (1 << 0) +#define SR_TF_NOT_FULL (1 << 1) +#define SR_TF_EMPT (1 << 2) +#define SR_RF_NOT_EMPT (1 << 3) +#define SR_RF_FULL (1 << 4) +#define SR_TX_ERR (1 << 5) +#define SR_DCOL (1 << 6) + +/* Bit fields in ISR, IMR, RISR, 7 bits */ +#define SPI_INT_TXEI (1 << 0) +#define SPI_INT_TXOI (1 << 1) +#define SPI_INT_RXUI (1 << 2) +#define SPI_INT_RXOI (1 << 3) +#define SPI_INT_RXFI (1 << 4) +#define SPI_INT_MSTI (1 << 5) + +/* Bit fields in DMACR */ +#define SPI_DMACR_TX_ENABLE (1 << 1) +#define SPI_DMACR_RX_ENABLE (1 << 0) + +/* Bit fields in ICR */ +#define SPI_CLEAR_INT_ALL (1<< 0) +#define SPI_CLEAR_INT_RXUI (1 << 1) +#define SPI_CLEAR_INT_RXOI (1 << 2) +#define SPI_CLEAR_INT_TXOI (1 << 3) + + +/* TX RX interrupt level threshold, max can be 256 */ +#define SPI_INT_THRESHOLD 16 +#endif + +enum dw_ssi_type { + SSI_MOTO_SPI = 0, + SSI_TI_SSP, + SSI_NS_MICROWIRE, +}; + +struct dw_spi; +struct dw_spi_dma_ops { + int (*dma_init)(struct dw_spi *dws); + void (*dma_exit)(struct dw_spi *dws); + int (*dma_transfer)(struct dw_spi *dws, int cs_change); +}; + +struct dw_spi { + struct spi_master *master; + struct spi_device *cur_dev; + struct device *parent_dev; + enum dw_ssi_type type; + char name[16]; + + struct clk *clk_spi; + struct clk *pclk_spi; + + void __iomem *regs; + unsigned long paddr; + u32 iolen; + int irq; + u32 fifo_len; /* depth of the FIFO buffer */ + u32 max_freq; /* max bus freq supported */ + + u16 bus_num; + u16 num_cs; /* supported slave numbers */ + + /* Driver message queue */ + struct workqueue_struct *workqueue; + struct work_struct pump_messages; + spinlock_t lock; + struct list_head queue; + int busy; + int run; + + /* Message Transfer pump */ + struct tasklet_struct pump_transfers; + + /* Current message transfer state info */ + struct spi_message *cur_msg; + struct spi_transfer *cur_transfer; + struct chip_data *cur_chip; + struct chip_data *prev_chip; + size_t len; + void *tx; + void *tx_end; + void *rx; + void *rx_end; + int dma_mapped; + dma_addr_t rx_dma; + dma_addr_t tx_dma; + size_t rx_map_len; + size_t tx_map_len; + u8 n_bytes; /* current is a 1/2 bytes op */ + u8 max_bits_per_word; /* maxim is 16b */ + u32 dma_width; + int cs_change; + irqreturn_t (*transfer_handler)(struct dw_spi *dws); + void (*cs_control)(struct dw_spi *dws, u32 cs, u8 flag); + + /* Dma info */ + int dma_inited; + struct dma_chan *txchan; + struct scatterlist tx_sgl; + struct dma_chan *rxchan; + struct scatterlist rx_sgl; + int dma_chan_done; + struct device *dma_dev; + dma_addr_t dma_addr; /* phy address of the Data register */ + struct dw_spi_dma_ops *dma_ops; + void *dma_priv; /* platform relate info */ + + //struct pci_dev *dmac; + atomic_t debug_flag; + + /* Bus interface info */ + void *priv; +#ifdef CONFIG_DEBUG_FS + struct dentry *debugfs; +#endif +}; + +static inline u32 dw_readl(struct dw_spi *dws, u32 offset) +{ + return __raw_readl(dws->regs + offset); +} + +static inline void dw_writel(struct dw_spi *dws, u32 offset, u32 val) +{ + __raw_writel(val, dws->regs + offset); +} + +static inline u16 dw_readw(struct dw_spi *dws, u32 offset) +{ + return __raw_readw(dws->regs + offset); +} + +static inline void dw_writew(struct dw_spi *dws, u32 offset, u16 val) +{ + __raw_writew(val, dws->regs + offset); +} + +static inline void spi_enable_chip(struct dw_spi *dws, int enable) +{ + dw_writel(dws, SPIM_SSIENR, (enable ? 1 : 0)); +} + +static inline void spi_set_clk(struct dw_spi *dws, u16 div) +{ + dw_writel(dws, SPIM_BAUDR, div); +} + +static inline void spi_chip_sel(struct dw_spi *dws, u16 cs) +{ + if (cs > dws->num_cs) + return; + + if (dws->cs_control) + dws->cs_control(dws, cs, 1); + + dw_writel(dws, SPIM_SER, 1 << cs); + + DBG_SPI("%s:cs=%d\n",__func__,cs); +} + +static inline void spi_cs_control(struct dw_spi *dws, u32 cs, u8 flag) +{ + if (flag) + dw_writel(dws, SPIM_SER, 1 << cs); + else + dw_writel(dws, SPIM_SER, 0); + + return; +} + + +/* Disable IRQ bits */ +static inline void spi_mask_intr(struct dw_spi *dws, u32 mask) +{ + u32 new_mask; + + new_mask = dw_readl(dws, SPIM_IMR) & ~mask; + dw_writel(dws, SPIM_IMR, new_mask); +} + +/* Enable IRQ bits */ +static inline void spi_umask_intr(struct dw_spi *dws, u32 mask) +{ + u32 new_mask; + + new_mask = dw_readl(dws, SPIM_IMR) | mask; + dw_writel(dws, SPIM_IMR, new_mask); +} + +/* + * Each SPI slave device to work with dw_api controller should + * has such a structure claiming its working mode (PIO/DMA etc), + * which can be save in the "controller_data" member of the + * struct spi_device + */ +struct dw_spi_chip { + u8 poll_mode; /* 0 for contoller polling mode */ + u8 type; /* SPI/SSP/Micrwire */ + u8 enable_dma; + void (*cs_control)(u32 command); +}; + +extern int dw_spi_add_host(struct dw_spi *dws); +extern void dw_spi_remove_host(struct dw_spi *dws); +extern int dw_spi_suspend_host(struct dw_spi *dws); +extern int dw_spi_resume_host(struct dw_spi *dws); +extern void dw_spi_xfer_done(struct dw_spi *dws); + +/* platform related setup */ +extern int dw_spi_dma_init(struct dw_spi *dws); /* Intel MID platforms */ +#endif /* SPIM_HEADER_H */ diff --git a/drivers/spi/spi-rockchip-dma.c b/drivers/spi/spi-rockchip-dma.c new file mode 100755 index 000000000000..653396a52acc --- /dev/null +++ b/drivers/spi/spi-rockchip-dma.c @@ -0,0 +1,223 @@ +/* + * Special handling for DW core on Intel MID platform + * + * Copyright (c) 2009, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "spi-rockchip-core.h" + +#ifdef CONFIG_SPI_ROCKCHIP_DMA + +struct spi_dma_slave { + struct dma_chan *ch; + enum dma_transfer_direction direction; + unsigned int dmach; +}; + + +struct spi_dma { + struct spi_dma_slave dmas_tx; + struct spi_dma_slave dmas_rx; +}; + +static bool mid_spi_dma_chan_filter(struct dma_chan *chan, void *param) +{ + struct dw_spi *dws = param; + int ret = 0; + ret = dws->parent_dev && (&dws->parent_dev == chan->device->dev); + + printk("%s:ret=%d\n",__func__, ret); + return ret; +} + + +static int mid_spi_dma_init(struct dw_spi *dws) +{ + struct spi_dma *dw_dma = dws->dma_priv; + struct spi_dma_slave *rxs, *txs; + dma_cap_mask_t mask; + + /* + * Get pci device for DMA controller, currently it could only + * be the DMA controller of either Moorestown or Medfield + */ + //dws->dmac = pci_get_device(PCI_VENDOR_ID_INTEL, 0x0813, NULL); + //if (!dws->dmac) + // dws->dmac = pci_get_device(PCI_VENDOR_ID_INTEL, 0x0827, NULL); + + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + + /* 1. Init rx channel */ + dws->rxchan = dma_request_channel(mask, mid_spi_dma_chan_filter, dws); + if (!dws->rxchan) + goto err_exit; + rxs = &dw_dma->dmas_rx; + //rxs->hs_mode = LNW_DMA_HW_HS; + //rxs->cfg_mode = LNW_DMA_PER_TO_MEM; + dws->rxchan->private = rxs; + + /* 2. Init tx channel */ + dws->txchan = dma_request_channel(mask, mid_spi_dma_chan_filter, dws); + if (!dws->txchan) + goto free_rxchan; + txs = &dw_dma->dmas_tx; + //txs->hs_mode = LNW_DMA_HW_HS; + //txs->cfg_mode = LNW_DMA_MEM_TO_PER; + dws->txchan->private = txs; + + dws->dma_inited = 1; + return 0; + + free_rxchan: + dma_release_channel(dws->rxchan); + err_exit: + return -1; + +} + +static void mid_spi_dma_exit(struct dw_spi *dws) +{ + dma_release_channel(dws->txchan); + dma_release_channel(dws->rxchan); +} + +/* + * dws->dma_chan_done is cleared before the dma transfer starts, + * callback for rx/tx channel will each increment it by 1. + * Reaching 2 means the whole spi transaction is done. + */ +static void dw_spi_dma_done(void *arg) +{ + struct dw_spi *dws = arg; + + if (++dws->dma_chan_done != 2) + return; + dw_spi_xfer_done(dws); +} + +static int mid_spi_dma_transfer(struct dw_spi *dws, int cs_change) +{ + struct dma_async_tx_descriptor *txdesc = NULL, *rxdesc = NULL; + struct dma_chan *txchan, *rxchan; + struct dma_slave_config txconf, rxconf; + u16 dma_ctrl = 0; + + /* 1. setup DMA related registers */ + if (cs_change) { + spi_enable_chip(dws, 0); + dw_writew(dws, SPIM_DMARDLR, 0xf); + dw_writew(dws, SPIM_DMATDLR, 0x10); + if (dws->tx_dma) + dma_ctrl |= 0x2; + if (dws->rx_dma) + dma_ctrl |= 0x1; + dw_writew(dws, SPIM_DMACR, dma_ctrl); + spi_enable_chip(dws, 1); + } + + dws->dma_chan_done = 0; + txchan = dws->txchan; + rxchan = dws->rxchan; + + /* 2. Prepare the TX dma transfer */ + txconf.direction = DMA_MEM_TO_DEV; + txconf.dst_addr = dws->dma_addr; + txconf.dst_maxburst = 0x03;//LNW_DMA_MSIZE_16; + txconf.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + txconf.dst_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + txconf.device_fc = false; + + txchan->device->device_control(txchan, DMA_SLAVE_CONFIG, + (unsigned long) &txconf); + + memset(&dws->tx_sgl, 0, sizeof(dws->tx_sgl)); + dws->tx_sgl.dma_address = dws->tx_dma; + dws->tx_sgl.length = dws->len; + + txdesc = dmaengine_prep_slave_sg(txchan, + &dws->tx_sgl, + 1, + DMA_MEM_TO_DEV, + DMA_PREP_INTERRUPT | DMA_COMPL_SKIP_DEST_UNMAP); + txdesc->callback = dw_spi_dma_done; + txdesc->callback_param = dws; + + /* 3. Prepare the RX dma transfer */ + rxconf.direction = DMA_DEV_TO_MEM; + rxconf.src_addr = dws->dma_addr; + rxconf.src_maxburst = 0x03; //LNW_DMA_MSIZE_16; + rxconf.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + rxconf.src_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + rxconf.device_fc = false; + + rxchan->device->device_control(rxchan, DMA_SLAVE_CONFIG, + (unsigned long) &rxconf); + + memset(&dws->rx_sgl, 0, sizeof(dws->rx_sgl)); + dws->rx_sgl.dma_address = dws->rx_dma; + dws->rx_sgl.length = dws->len; + + rxdesc = dmaengine_prep_slave_sg(rxchan, + &dws->rx_sgl, + 1, + DMA_DEV_TO_MEM, + DMA_PREP_INTERRUPT | DMA_COMPL_SKIP_DEST_UNMAP); + rxdesc->callback = dw_spi_dma_done; + rxdesc->callback_param = dws; + + /* rx must be started before tx due to spi instinct */ + rxdesc->tx_submit(rxdesc); + txdesc->tx_submit(txdesc); + return 0; +} + +static struct dw_spi_dma_ops spi_dma_ops = { + .dma_init = mid_spi_dma_init, + .dma_exit = mid_spi_dma_exit, + .dma_transfer = mid_spi_dma_transfer, +}; + +int dw_spi_dma_init(struct dw_spi *dws) +{ + + dws->dma_priv = kzalloc(sizeof(struct spi_dma), GFP_KERNEL); + if (!dws->dma_priv) + return -ENOMEM; + dws->dma_ops = &spi_dma_ops; + return 0; +} +#endif + diff --git a/drivers/spi/spi-rockchip-test.c b/drivers/spi/spi-rockchip-test.c new file mode 100755 index 000000000000..a242e45ef239 --- /dev/null +++ b/drivers/spi/spi-rockchip-test.c @@ -0,0 +1,256 @@ +/*drivers/serial/spi_test.c -spi test driver + * + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "spi-rockchip-core.h" + + +#define MAX_SPI_BUS_NUM 2 + +struct spi_test_data { + struct device *dev; + struct spi_device *spi; + char *rx_buf; + int rx_len; + char *tx_buf; + int tx_len; +}; +static struct spi_test_data *g_spi_test_data[MAX_SPI_BUS_NUM]; + + +static struct dw_spi_chip spi_test_chip[] = { +{ + //.poll_mode = 1, + //.enable_dma = 1, +}, +{ + //.poll_mode = 1, + //.enable_dma = 1, +}, + +}; + + +static struct spi_board_info board_spi_test_devices[] = { +//#if defined(CONFIG_SPIM0_RK29) + { + .modalias = "spi_test_bus0", + .bus_num = 0, //0 or 1 + .max_speed_hz = 12*1000*1000, + .chip_select = 0, + .mode = SPI_MODE_0, + .controller_data = &spi_test_chip[0], + }, +//#endif +//#if defined(CONFIG_SPIM1_RK29) + { + .modalias = "spi_test_bus1", + .bus_num = 1, //0 or 1 + .max_speed_hz = 12*1000*1000, + .chip_select = 0, + .mode = SPI_MODE_0, + .controller_data = &spi_test_chip[1], + } +//#endif +}; + +static ssize_t spi_test_write(struct file *file, + const char __user *buf, size_t count, loff_t *offset) +{ + u8 nr_buf[8]; + int nr = 0, ret; + int i = 0; + struct spi_device *spi = NULL; + char txbuf[256],rxbuf[256]; + + printk("%s:0:bus=0,cs=0; 1:bus=0,cs=1; 2:bus=1,cs=0; 3:bus=1,cs=1\n",__func__); + + if(count > 3) + return -EFAULT; + + ret = copy_from_user(nr_buf, buf, count); + if(ret < 0) + return -EFAULT; + + sscanf(nr_buf, "%d", &nr); + if(nr >= 4 || nr < 0) + { + printk("%s:cmd is error\n",__func__); + return -EFAULT; + } + + for(i=0; i<256; i++) + txbuf[i] = i; + + +/* + if((nr == 0) || (nr == 1)) + { + printk("%s:error SPIM0 need selected\n",__func__); + return -EFAULT; + } + + if((nr == 2) || (nr == 3)) + { + printk("%s:error SPIM1 need selected\n",__func__); + return -EFAULT; + } +*/ + + switch(nr) + { + case 0: + if(!g_spi_test_data[0]->spi) + return -EFAULT; + spi = g_spi_test_data[0]->spi; + spi->chip_select = 0; + break; + case 1: + if(!g_spi_test_data[0]->spi) + return -EFAULT; + spi = g_spi_test_data[0]->spi; + spi->chip_select = 1; + break; + case 2: + if(!g_spi_test_data[1]->spi) + return -EFAULT; + spi = g_spi_test_data[1]->spi; + spi->chip_select = 0; + break; + case 3: + if(!g_spi_test_data[1]->spi) + return -EFAULT; + spi = g_spi_test_data[1]->spi; + spi->chip_select = 1; + break; + + default: + break; + } + + for(i=0; i<1; i++) + { + ret = spi_write(spi, txbuf, 256); + ret = spi_read(spi, rxbuf, 256); + ret = spi_write_then_read(spi,txbuf,256,rxbuf,256); + ret = spi_write_and_read(spi,txbuf,rxbuf,256); + printk("%s:test %d times\n\n",__func__,i+1); + } + + if(!ret) + printk("%s:bus_num=%d,chip_select=%d,ok\n",__func__,spi->master->bus_num, spi->chip_select); + else + printk("%s:bus_num=%d,chip_select=%d,error\n",__func__,spi->master->bus_num, spi->chip_select); + + return count; +} + + +static const struct file_operations spi_test_fops = { + .write = spi_test_write, +}; + +static struct miscdevice spi_test_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "spi_misc_test", + .fops = &spi_test_fops, +}; + +static int spi_test_probe(struct spi_device *spi) +{ + struct spi_test_data *spi_test_data; + int ret; + int i =0; + char txbuf[256],rxbuf[256]; + + if(!spi) + return -ENOMEM; + + if((spi->master->bus_num >= MAX_SPI_BUS_NUM) || (spi->master->bus_num < 0)) + { + printk("%s:error:bus_num=%d\n",__func__, spi->master->bus_num); + return -ENOMEM; + } + + spi_test_data = (struct spi_test_data *)kzalloc(sizeof(struct spi_test_data), GFP_KERNEL); + if(!spi_test_data){ + dev_err(&spi->dev, "ERR: no memory for spi_test_data\n"); + return -ENOMEM; + } + + spi->bits_per_word = 8; + + spi_test_data->spi = spi; + spi_test_data->dev = &spi->dev; + ret = spi_setup(spi); + if (ret < 0){ + dev_err(spi_test_data->dev, "ERR: fail to setup spi\n"); + return -1; + } + + g_spi_test_data[spi->master->bus_num] = spi_test_data; + + printk("%s:bus_num=%d,ok\n",__func__,spi->master->bus_num); + return ret; + +} + +static const struct spi_device_id spi_test_id[] = { + {"spi_test_bus0", 0}, + {"spi_test_bus1", 1}, + {}, +}; + + +static struct spi_driver spi_test_driver = { + .driver = { + .name = "spi_test", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .id_table = spi_test_id, + + .probe = spi_test_probe, +}; + +static int __init spi_test_init(void) +{ + printk("%s\n",__func__); + spi_register_board_info(board_spi_test_devices, ARRAY_SIZE(board_spi_test_devices)); + misc_register(&spi_test_misc); + return spi_register_driver(&spi_test_driver); +} + +static void __exit spi_test_exit(void) +{ + misc_deregister(&spi_test_misc); + return spi_unregister_driver(&spi_test_driver); +} +module_init(spi_test_init); +module_exit(spi_test_exit); + diff --git a/drivers/spi/spi-rockchip.c b/drivers/spi/spi-rockchip.c new file mode 100755 index 000000000000..95dec8e69e01 --- /dev/null +++ b/drivers/spi/spi-rockchip.c @@ -0,0 +1,349 @@ +/* + * rockchip spi interface driver for DW SPI Core + * + * Copyright (c) 2014, ROCKCHIP Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "spi-rockchip-core.h" + + +#define DRIVER_NAME "rockchip_spi_driver_data" +#define SPI_MAX_FREQUENCY 24000000 + +struct rockchip_spi_driver_data { + struct platform_device *pdev; + struct dw_spi dws; + struct rockchip_spi_info *info; + struct clk *clk_spi; + struct clk *pclk_spi; +}; + +#ifdef CONFIG_OF +static struct rockchip_spi_info *rockchip_spi_parse_dt(struct device *dev) +{ + struct rockchip_spi_info *info; + u32 temp; + + info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL); + if (!info) { + dev_err(dev, "memory allocation for spi_info failed\n"); + return ERR_PTR(-ENOMEM); + } + + if (of_property_read_u32(dev->of_node, "rockchip,spi-src-clk", &temp)) { + dev_warn(dev, "spi bus clock parent not specified, using clock at index 0 as parent\n"); + info->src_clk_nr = 0; + } else { + info->src_clk_nr = temp; + } +#if 0 + if (of_property_read_u32(dev->of_node, "bus-num", &temp)) { + dev_warn(dev, "number of bus not specified, assuming bus 0\n"); + info->bus_num= 0; + } else { + info->bus_num = temp; + } +#endif + if (of_property_read_u32(dev->of_node, "num-cs", &temp)) { + dev_warn(dev, "number of chip select lines not specified, assuming 1 chip select line\n"); + info->num_cs = 1; + } else { + info->num_cs = temp; + } + + if (of_property_read_u32(dev->of_node, "max-freq", &temp)) { + dev_warn(dev, "fail to get max-freq\n"); + info->spi_freq = SPI_MAX_FREQUENCY; + } else { + info->spi_freq = temp; + } + + //printk("%s:line=%d,src_clk_nr=%d,bus_num=%d,num_cs=%d\n",__func__, __LINE__,info->src_clk_nr,info->bus_num,info->num_cs); + + return info; +} +#else +static struct rockchip_spi_info *rockchip_spi_parse_dt(struct device *dev) +{ + return dev->platform_data; +} +#endif + + +static int rockchip_spi_probe(struct platform_device *pdev) +{ + struct resource *mem_res; + struct resource *res; + struct rockchip_spi_driver_data *sdd; + struct rockchip_spi_info *info = pdev->dev.platform_data; + struct dw_spi *dws; + struct spi_master *master; + int ret, irq; + char clk_name[16]; + + if (!info && pdev->dev.of_node) { + info = rockchip_spi_parse_dt(&pdev->dev); + if (IS_ERR(info)) + return PTR_ERR(info); + } + + if (!info) { + dev_err(&pdev->dev, "platform_data missing!\n"); + return -ENODEV; + } + + sdd = kzalloc(sizeof(struct rockchip_spi_driver_data), GFP_KERNEL); + if (!sdd) { + ret = -ENOMEM; + goto err_kfree; + } + + + sdd->pdev = pdev; + sdd->info = info; + dws = &sdd->dws; + + atomic_set(&dws->debug_flag, 0);//debug flag + + /* Get basic io resource and map it */ + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_warn(&pdev->dev, "Failed to get IRQ: %d\n", irq); + return irq; + } + + mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (mem_res == NULL) { + dev_err(&pdev->dev, "Unable to get SPI MEM resource\n"); + ret = -ENXIO; + goto err_unmap; + } + + dws->regs = ioremap(mem_res->start, (mem_res->end - mem_res->start) + 1); + if (!dws->regs){ + ret = -EBUSY; + goto err_unmap; + } + + dws->paddr = mem_res->start; + dws->iolen = (mem_res->end - mem_res->start) + 1; + + printk(KERN_INFO "dws->regs: %p\n", dws->regs); + + //get bus num + if (pdev->dev.of_node) { + ret = of_alias_get_id(pdev->dev.of_node, "spi"); + if (ret < 0) { + dev_err(&pdev->dev, "failed to get alias id, errno %d\n", + ret); + goto err_release_mem; + } + info->bus_num = ret; + } else { + info->bus_num = pdev->id; + } + + /* Setup clocks */ + sdd->clk_spi = devm_clk_get(&pdev->dev, "spi"); + if (IS_ERR(sdd->clk_spi)) { + dev_err(&pdev->dev, "Unable to acquire clock 'spi'\n"); + ret = PTR_ERR(sdd->clk_spi); + goto err_clk; + } + + if (clk_prepare_enable(sdd->clk_spi)) { + dev_err(&pdev->dev, "Couldn't enable clock 'spi'\n"); + ret = -EBUSY; + goto err_clk; + } + + sprintf(clk_name, "pclk_spi%d", info->src_clk_nr); + sdd->pclk_spi = devm_clk_get(&pdev->dev, clk_name); + if (IS_ERR(sdd->pclk_spi)) { + dev_err(&pdev->dev, + "Unable to acquire clock '%s'\n", clk_name); + ret = PTR_ERR(sdd->pclk_spi); + goto err_pclk; + } + + if (clk_prepare_enable(sdd->pclk_spi)) { + dev_err(&pdev->dev, "Couldn't enable clock '%s'\n", clk_name); + ret = -EBUSY; + goto err_pclk; + } + + clk_set_rate(sdd->clk_spi, info->spi_freq); + + dws->max_freq = clk_get_rate(sdd->clk_spi); + dws->parent_dev = &pdev->dev; + dws->bus_num = info->bus_num; + dws->num_cs = info->num_cs; + dws->irq = irq; + dws->clk_spi = sdd->clk_spi; + dws->pclk_spi = sdd->pclk_spi; + + /* + * handling for rockchip paltforms, like dma setup, + * clock rate, FIFO depth. + */ + +#ifdef CONFIG_SPI_ROCKCHIP_DMA + ret = dw_spi_dma_init(dws); + if (ret) + goto err_release_mem; +#endif + + ret = dw_spi_add_host(dws); + if (ret) + goto err_release_mem; + + platform_set_drvdata(pdev, sdd); + + printk("%s:num_cs=%d,irq=%d,freq=%d ok\n",__func__, info->num_cs, irq, dws->max_freq); + + return 0; +err_release_mem: + release_mem_region(mem_res->start, (mem_res->end - mem_res->start) + 1); +err_pclk: + clk_disable_unprepare(sdd->pclk_spi); +err_clk: + clk_disable_unprepare(sdd->clk_spi); +err_unmap: + iounmap(dws->regs); +err_kfree: + kfree(sdd); + return ret; +} + +static int rockchip_spi_remove(struct platform_device *pdev) +{ + struct rockchip_spi_driver_data *sdd = platform_get_drvdata(pdev); + + platform_set_drvdata(pdev, NULL); + dw_spi_remove_host(&sdd->dws); + iounmap(sdd->dws.regs); + kfree(sdd); +} + +#ifdef CONFIG_PM_SLEEP +static int rockchip_spi_suspend(struct device *dev) +{ + struct rockchip_spi_driver_data *sdd = dev_get_drvdata(dev); + int ret = 0; + + ret = dw_spi_suspend_host(&sdd->dws); + + return ret; +} + +static int rockchip_spi_resume(struct device *dev) +{ + struct rockchip_spi_driver_data *sdd = dev_get_drvdata(dev); + int ret = 0; + + ret = dw_spi_resume_host(&sdd->dws); + + return ret; +} +#endif /* CONFIG_PM_SLEEP */ + +#ifdef CONFIG_PM_RUNTIME +static int rockchip_spi_runtime_suspend(struct device *dev) +{ + struct rockchip_spi_driver_data *sdd = dev_get_drvdata(dev); + struct dw_spi *dws = &sdd->dws; + + clk_disable_unprepare(sdd->clk_spi); + clk_disable_unprepare(sdd->pclk_spi); + + + DBG_SPI("%s\n",__func__); + + return 0; +} + +static int rockchip_spi_runtime_resume(struct device *dev) +{ + struct rockchip_spi_driver_data *sdd = dev_get_drvdata(dev); + struct dw_spi *dws = &sdd->dws; + + clk_prepare_enable(sdd->pclk_spi); + clk_prepare_enable(sdd->clk_spi); + + DBG_SPI("%s\n",__func__); + return 0; +} +#endif /* CONFIG_PM_RUNTIME */ + +static const struct dev_pm_ops rockchip_spi_pm = { + SET_SYSTEM_SLEEP_PM_OPS(rockchip_spi_suspend, rockchip_spi_resume) + SET_RUNTIME_PM_OPS(rockchip_spi_runtime_suspend, + rockchip_spi_runtime_resume, NULL) +}; + +#ifdef CONFIG_OF +static const struct of_device_id rockchip_spi_dt_match[] = { + { .compatible = "rockchip,rockchip-spi", + }, + { }, +}; +MODULE_DEVICE_TABLE(of, rockchip_spi_dt_match); +#endif /* CONFIG_OF */ + +static struct platform_driver rockchip_spi_driver = { + .driver = { + .name = "rockchip-spi", + .owner = THIS_MODULE, + .pm = &rockchip_spi_pm, + .of_match_table = of_match_ptr(rockchip_spi_dt_match), + }, + .remove = rockchip_spi_remove, +}; +MODULE_ALIAS("platform:rockchip-spi"); + +static int __init rockchip_spi_init(void) +{ + return platform_driver_probe(&rockchip_spi_driver, rockchip_spi_probe); +} +subsys_initcall(rockchip_spi_init); + +static void __exit rockchip_spi_exit(void) +{ + platform_driver_unregister(&rockchip_spi_driver); +} +module_exit(rockchip_spi_exit); + +MODULE_AUTHOR("Luo Wei "); +MODULE_DESCRIPTION("ROCKCHIP SPI Controller Driver"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/platform_data/spi-rockchip.h b/include/linux/platform_data/spi-rockchip.h new file mode 100755 index 000000000000..4b588739b5b6 --- /dev/null +++ b/include/linux/platform_data/spi-rockchip.h @@ -0,0 +1,79 @@ +/* include/linux/platform_data/spi-rockchip.h + * + * Copyright (C) 2014 Rockchip Electronics Ltd. + * luowei + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __ROCKCHIP_PLAT_SPI_H +#define __ROCKCHIP_PLAT_SPI_H + +#include + +struct platform_device; + +/** + * struct rockchip_spi_csinfo - ChipSelect description + * @fb_delay: Slave specific feedback delay. + * Refer to FB_CLK_SEL register definition in SPI chapter. + * @line: Custom 'identity' of the CS line. + * + * This is per SPI-Slave Chipselect information. + * Allocate and initialize one in machine init code and make the + * spi_board_info.controller_data point to it. + */ +struct rockchip_spi_csinfo { + u8 fb_delay; + unsigned line; +}; + +/** + * struct rockchip_spi_info - SPI Controller defining structure + * @src_clk_nr: Clock source index for the CLK_CFG[SPI_CLKSEL] field. + * @num_cs: Number of CS this controller emulates. + * @cfg_gpio: Configure pins for this SPI controller. + */ +struct rockchip_spi_info { + int src_clk_nr; + int spi_freq; + int num_cs; + int bus_num; + int (*cfg_gpio)(void); + dma_filter_fn filter; + + u8 transfer_mode;/*full or half duplex*/ + u8 poll_mode; /* 0 for contoller polling mode */ + u8 type; /* SPI/SSP/Micrwire */ + u8 enable_dma; + u8 slave_enable; +}; + +/** + * rockchip_spi_set_platdata - SPI Controller configure callback by the board + * initialization code. + * @cfg_gpio: Pointer to gpio setup function. + * @src_clk_nr: Clock the SPI controller is to use to generate SPI clocks. + * @num_cs: Number of elements in the 'cs' array. + * + * Call this from machine init code for each SPI Controller that + * has some chips attached to it. + */ +extern void rockchip_spi0_set_platdata(int (*cfg_gpio)(void), int src_clk_nr, + int num_cs); +extern void rockchip_spi1_set_platdata(int (*cfg_gpio)(void), int src_clk_nr, + int num_cs); +extern void rockchip_spi2_set_platdata(int (*cfg_gpio)(void), int src_clk_nr, + int num_cs); + +/* defined by architecture to configure gpio */ +extern int rockchip_spi0_cfg_gpio(void); +extern int rockchip_spi1_cfg_gpio(void); +extern int rockchip_spi2_cfg_gpio(void); + +extern struct rockchip_spi_info rockchip_spi0_pdata; +extern struct rockchip_spi_info rockchip_spi1_pdata; +extern struct rockchip_spi_info rockchip_spi2_pdata; +#endif /* __ROCKCHIP_PLAT_SPI_H */