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.
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
--- /dev/null
+/*
+ * 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 <linux/dma-mapping.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/highmem.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/spi/spi.h>
+#include <linux/clk.h>
+
+
+#include "spi-rockchip-core.h"
+
+#ifdef CONFIG_DEBUG_FS
+#include <linux/debugfs.h>
+#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 <lw@rock-chips.com>");
+MODULE_DESCRIPTION("Driver for DesignWare SPI controller core");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+#ifndef DW_SPI_HEADER_H
+#define DW_SPI_HEADER_H
+
+#include <linux/io.h>
+#include <linux/scatterlist.h>
+
+
+#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 */
--- /dev/null
+/*
+ * 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 <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/workqueue.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/spi/spi.h>
+#include <linux/gpio.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/platform_data/spi-rockchip.h>
+
+
+#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
+
--- /dev/null
+/*drivers/serial/spi_test.c -spi test driver\r
+ *\r
+ *\r
+ * This program is distributed in the hope that it will be useful,\r
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
+ * GNU General Public License for more details.\r
+ */\r
+#include <linux/interrupt.h>\r
+#include <linux/slab.h>\r
+#include <linux/init.h>\r
+#include <linux/module.h>\r
+#include <linux/workqueue.h>\r
+#include <linux/interrupt.h>\r
+#include <linux/delay.h>\r
+#include <linux/clk.h>\r
+#include <linux/fs.h>\r
+#include <linux/dma-mapping.h>\r
+#include <linux/dmaengine.h>\r
+#include <linux/platform_device.h>\r
+#include <linux/pm_runtime.h>\r
+#include <linux/spi/spi.h>\r
+#include <linux/gpio.h>\r
+#include <linux/of.h>\r
+#include <linux/of_gpio.h>\r
+#include <linux/miscdevice.h>\r
+#include <linux/platform_data/spi-rockchip.h>\r
+#include <asm/uaccess.h>\r
+\r
+#include "spi-rockchip-core.h"\r
+\r
+\r
+#define MAX_SPI_BUS_NUM 2\r
+\r
+struct spi_test_data {\r
+ struct device *dev;\r
+ struct spi_device *spi; \r
+ char *rx_buf;\r
+ int rx_len; \r
+ char *tx_buf;\r
+ int tx_len; \r
+};\r
+static struct spi_test_data *g_spi_test_data[MAX_SPI_BUS_NUM];\r
+\r
+\r
+static struct dw_spi_chip spi_test_chip[] = {\r
+{\r
+ //.poll_mode = 1,\r
+ //.enable_dma = 1,\r
+},\r
+{\r
+ //.poll_mode = 1,\r
+ //.enable_dma = 1,\r
+},\r
+\r
+};\r
+\r
+ \r
+static struct spi_board_info board_spi_test_devices[] = { \r
+//#if defined(CONFIG_SPIM0_RK29)\r
+ {\r
+ .modalias = "spi_test_bus0",\r
+ .bus_num = 0, //0 or 1\r
+ .max_speed_hz = 12*1000*1000,\r
+ .chip_select = 0, \r
+ .mode = SPI_MODE_0,\r
+ .controller_data = &spi_test_chip[0],\r
+ },\r
+//#endif\r
+//#if defined(CONFIG_SPIM1_RK29)\r
+ {\r
+ .modalias = "spi_test_bus1",\r
+ .bus_num = 1, //0 or 1\r
+ .max_speed_hz = 12*1000*1000,\r
+ .chip_select = 0, \r
+ .mode = SPI_MODE_0,\r
+ .controller_data = &spi_test_chip[1],\r
+ }\r
+//#endif\r
+};\r
+\r
+static ssize_t spi_test_write(struct file *file, \r
+ const char __user *buf, size_t count, loff_t *offset)\r
+{\r
+ u8 nr_buf[8];\r
+ int nr = 0, ret;\r
+ int i = 0;\r
+ struct spi_device *spi = NULL;\r
+ char txbuf[256],rxbuf[256];\r
+\r
+ printk("%s:0:bus=0,cs=0; 1:bus=0,cs=1; 2:bus=1,cs=0; 3:bus=1,cs=1\n",__func__);\r
+\r
+ if(count > 3)\r
+ return -EFAULT;\r
+ \r
+ ret = copy_from_user(nr_buf, buf, count);\r
+ if(ret < 0)\r
+ return -EFAULT;\r
+\r
+ sscanf(nr_buf, "%d", &nr);\r
+ if(nr >= 4 || nr < 0)\r
+ {\r
+ printk("%s:cmd is error\n",__func__);\r
+ return -EFAULT;\r
+ }\r
+ \r
+ for(i=0; i<256; i++)\r
+ txbuf[i] = i;\r
+\r
+\r
+/*\r
+ if((nr == 0) || (nr == 1))\r
+ {\r
+ printk("%s:error SPIM0 need selected\n",__func__); \r
+ return -EFAULT;\r
+ }\r
+\r
+ if((nr == 2) || (nr == 3))\r
+ {\r
+ printk("%s:error SPIM1 need selected\n",__func__); \r
+ return -EFAULT;\r
+ }\r
+*/\r
+\r
+ switch(nr)\r
+ {\r
+ case 0: \r
+ if(!g_spi_test_data[0]->spi) \r
+ return -EFAULT;\r
+ spi = g_spi_test_data[0]->spi;\r
+ spi->chip_select = 0;\r
+ break;\r
+ case 1: \r
+ if(!g_spi_test_data[0]->spi) \r
+ return -EFAULT;\r
+ spi = g_spi_test_data[0]->spi;\r
+ spi->chip_select = 1;\r
+ break;\r
+ case 2: \r
+ if(!g_spi_test_data[1]->spi) \r
+ return -EFAULT;\r
+ spi = g_spi_test_data[1]->spi;\r
+ spi->chip_select = 0;\r
+ break;\r
+ case 3: \r
+ if(!g_spi_test_data[1]->spi) \r
+ return -EFAULT;\r
+ spi = g_spi_test_data[1]->spi;\r
+ spi->chip_select = 1;\r
+ break;\r
+ \r
+ default:\r
+ break;\r
+ }\r
+\r
+ for(i=0; i<1; i++)\r
+ {\r
+ ret = spi_write(spi, txbuf, 256);\r
+ ret = spi_read(spi, rxbuf, 256);\r
+ ret = spi_write_then_read(spi,txbuf,256,rxbuf,256); \r
+ ret = spi_write_and_read(spi,txbuf,rxbuf,256);\r
+ printk("%s:test %d times\n\n",__func__,i+1);\r
+ }\r
+ \r
+ if(!ret)\r
+ printk("%s:bus_num=%d,chip_select=%d,ok\n",__func__,spi->master->bus_num, spi->chip_select);\r
+ else\r
+ printk("%s:bus_num=%d,chip_select=%d,error\n",__func__,spi->master->bus_num, spi->chip_select);\r
+ \r
+ return count;\r
+}\r
+\r
+\r
+static const struct file_operations spi_test_fops = {\r
+ .write = spi_test_write,\r
+};\r
+\r
+static struct miscdevice spi_test_misc = {\r
+ .minor = MISC_DYNAMIC_MINOR,\r
+ .name = "spi_misc_test",\r
+ .fops = &spi_test_fops,\r
+};\r
+\r
+static int spi_test_probe(struct spi_device *spi)\r
+{ \r
+ struct spi_test_data *spi_test_data;\r
+ int ret;\r
+ int i =0;\r
+ char txbuf[256],rxbuf[256];\r
+ \r
+ if(!spi) \r
+ return -ENOMEM;\r
+\r
+ if((spi->master->bus_num >= MAX_SPI_BUS_NUM) || (spi->master->bus_num < 0))\r
+ {\r
+ printk("%s:error:bus_num=%d\n",__func__, spi->master->bus_num); \r
+ return -ENOMEM;\r
+ }\r
+ \r
+ spi_test_data = (struct spi_test_data *)kzalloc(sizeof(struct spi_test_data), GFP_KERNEL);\r
+ if(!spi_test_data){\r
+ dev_err(&spi->dev, "ERR: no memory for spi_test_data\n");\r
+ return -ENOMEM;
+ }\r
+\r
+ spi->bits_per_word = 8;\r
+ \r
+ spi_test_data->spi = spi;\r
+ spi_test_data->dev = &spi->dev;\r
+ ret = spi_setup(spi);\r
+ if (ret < 0){\r
+ dev_err(spi_test_data->dev, "ERR: fail to setup spi\n");\r
+ return -1;\r
+ } \r
+\r
+ g_spi_test_data[spi->master->bus_num] = spi_test_data;\r
+\r
+ printk("%s:bus_num=%d,ok\n",__func__,spi->master->bus_num);\r
+ return ret;\r
+\r
+}\r
+\r
+static const struct spi_device_id spi_test_id[] = { \r
+ {"spi_test_bus0", 0},\r
+ {"spi_test_bus1", 1},\r
+ {},\r
+};\r
+\r
+\r
+static struct spi_driver spi_test_driver = {\r
+ .driver = {\r
+ .name = "spi_test",\r
+ .bus = &spi_bus_type,\r
+ .owner = THIS_MODULE,\r
+ },\r
+ .id_table = spi_test_id,\r
+\r
+ .probe = spi_test_probe,\r
+};\r
+\r
+static int __init spi_test_init(void)\r
+{ \r
+ printk("%s\n",__func__);\r
+ spi_register_board_info(board_spi_test_devices, ARRAY_SIZE(board_spi_test_devices));\r
+ misc_register(&spi_test_misc);\r
+ return spi_register_driver(&spi_test_driver);\r
+}\r
+\r
+static void __exit spi_test_exit(void)\r
+{\r
+ misc_deregister(&spi_test_misc);\r
+ return spi_unregister_driver(&spi_test_driver);\r
+}\r
+module_init(spi_test_init);\r
+module_exit(spi_test_exit);\r
+\r
--- /dev/null
+/*
+ * 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 <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/workqueue.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/spi/spi.h>
+#include <linux/gpio.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/platform_data/spi-rockchip.h>
+
+#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 <lw@rock-chips.com>");
+MODULE_DESCRIPTION("ROCKCHIP SPI Controller Driver");
+MODULE_LICENSE("GPL");
--- /dev/null
+/* include/linux/platform_data/spi-rockchip.h
+ *
+ * Copyright (C) 2014 Rockchip Electronics Ltd.
+ * luowei <lw@rock-chips.com>
+ *
+ * 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 <linux/dmaengine.h>
+
+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 */