summaryrefslogtreecommitdiff
path: root/drivers/spi/spi_s3c2443.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/spi/spi_s3c2443.c')
-rw-r--r--drivers/spi/spi_s3c2443.c1229
1 files changed, 1229 insertions, 0 deletions
diff --git a/drivers/spi/spi_s3c2443.c b/drivers/spi/spi_s3c2443.c
new file mode 100644
index 000000000000..7b0d087776dc
--- /dev/null
+++ b/drivers/spi/spi_s3c2443.c
@@ -0,0 +1,1229 @@
+/* -*- linux-c -*-
+ *
+ * linux/drivers/spi/spi_s3c2443.c
+ *
+ * Copyright (c) 2008 Digi International
+ *
+ * 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.
+ *
+ * !Revision: $Revision: 1.1.1.1 $
+ * !Author: Luis Galdos
+ * !Descr: High Speed SPI driver for the S3C2443
+ * !References: Based on the driver of the Samsung SMDK for the S3C2443
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+
+#include <linux/spi/spi.h>
+#include <linux/spi/spi_bitbang.h>
+
+#include <asm/io.h>
+#include <asm/dma.h>
+#include <mach/hardware.h>
+#include <asm/delay.h>
+
+#include <mach/gpio.h>
+#include <mach/regs-gpio.h>
+#include <mach/regs-gpioj.h>
+#include <asm/plat-s3c24xx/regs-spi.h>
+#include <mach/dma.h>
+#include <asm/plat-s3c24xx/spi.h>
+#include <mach/dma.h>
+#include <mach/regs-s3c2443-clock.h>
+
+
+/* This macro selects the virtual DMA-channel to use */
+#define SPI_TX_CHANNEL DMACH_SPI0
+#define SPI_RX_CHANNEL DMACH_SPI0_RX
+
+#define S3C2443_SPI_DMA_BUFFER (PAGE_SIZE)
+
+/* Supported modes */
+#define S3C2443_SPI_SUPPORTED_MODES (SPI_CPOL | SPI_CPHA)
+
+/* DMA transfer unit (byte). */
+#define S3C24XX_DMA_XFER_BYTE 1
+#define S3C24XX_DMA_XFER_HWORD 2
+#define S3C24XX_DMA_XFER_WORD 4
+
+/* Used for setting the CS on the different SPI-operations */
+#define SPI_CS_ACTIVE 1
+#define SPI_CS_INACTIVE 0
+
+#define printk_err(fmt, args...) printk(KERN_ERR "[ ERROR ] spi-s3c2443: " fmt, ## args)
+#define printk_info(fmt, args...) printk(KERN_INFO "spi-s3c2443: " fmt, ## args)
+
+#if 0
+#define S3C2443_SPI_DEBUG
+#endif
+
+#ifdef S3C2443_SPI_DEBUG
+# define printk_debug(fmt, args...) printk(KERN_DEBUG "spi-s3c2443: %s() " fmt, __FUNCTION__ , ## args)
+#else
+# define printk_debug(fmt, args...)
+#endif
+
+
+enum s3c2443_xfer_t {
+ S3C2443_DMA_TX = 0,
+ S3C2443_DMA_RX = 1,
+};
+
+
+struct s3c2443_spi {
+ void __iomem *regs;
+ int irq;
+ struct clk *clk;
+ struct resource *ioarea;
+ struct spi_master *master;
+ struct device *dev;
+ struct s3c2443_spi_info *pdata;
+
+ struct list_head xmit_queue;
+ spinlock_t xmit_lock;
+ struct spi_transfer *current_xfer;
+ long remaining_bytes;
+ unsigned long dma_xfer_len;
+ struct tasklet_struct xmit_tasklet;
+ void *tx_buf;
+ void *rx_buf;
+ dma_addr_t tx_dma;
+ dma_addr_t rx_dma;
+
+ dmach_t dma_ch;
+ struct s3c2410_dma_client dmach;
+ dmach_t dma_ch_rx;
+ struct s3c2410_dma_client dmach_rx;
+};
+
+
+/* Internal functions */
+static void s3c2443_spi_next_message(struct spi_master *master);
+static void s3c2443_spi_next_xfer(struct spi_master *master, struct spi_message *msg);
+
+
+static inline struct s3c2443_spi *spi_to_hw(struct spi_device *sdev)
+{
+ return spi_master_get_devdata(sdev->master);
+}
+
+static inline struct s3c2443_spi *master_to_hw(struct spi_master *master)
+{
+ return spi_master_get_devdata(master);
+}
+
+
+static void s3c2443_spi_chipsel(struct spi_device *spi, int active)
+{
+ /* unsigned int cspol; */
+ struct s3c2443_spi *hw;
+ void (* cs_callback)(struct spi_device *_spi, int _act);
+ unsigned long slavecfg;
+ struct s3c2443_spi_info *info;
+ struct s3c24xx_spi_gpio *cs;
+
+ hw = spi_to_hw(spi);
+ if (!hw) {
+ printk_err("HW null pointer found!\n");
+ return;
+ }
+
+ /*
+ * If the SPI-device has a pointer function for controlling the
+ * chip select, then we will call its passed function. Otherwise the
+ * CS will be controlled by the SPI-hardware
+ */
+ if (spi->controller_data) {
+ info = hw->pdata;
+ cs = info->cs;
+
+ printk_debug("Calling the controller data\n");
+
+ /*
+ * Since the SPI-device will control its own CS disable ours by
+ * configuring it as input, then we have enabled the pull-up, right?
+ */
+ if (active == SPI_CS_ACTIVE)
+ s3c2410_gpio_cfgpin(cs->nr, S3C2410_GPIO_INPUT);
+
+ cs_callback = spi->controller_data;
+ cs_callback(spi, !active);
+
+ /* Now reenable our CS, but before disable the last CS-activation! */
+ if (active == SPI_CS_INACTIVE) {
+ writel(S3C2443_SPI0_SLAVE_SIG_INACT,
+ hw->regs + S3C2443_SPI0_SLAVE_SEL);
+ s3c2410_gpio_cfgpin(cs->nr, cs->cfg);
+ }
+ }
+
+ /*
+ * Even an external interrupt is being used, we MUST enable the slave
+ * register for being able to shift the data to the bus. Otherwise the
+ * FIFO will not send the data to the bus, and an error from the DMA-engine
+ * will be generated: "... timeout ... load buffer ..."
+ */
+ printk_debug("Using the HW-CS (%i)\n", active);
+ slavecfg = S3C2443_SPI0_SLAVE_SIG_INACT;
+ if (active == SPI_CS_ACTIVE)
+ slavecfg = S3C2443_SPI0_SLAVE_SIG_ACT;
+
+ writel(slavecfg, hw->regs + S3C2443_SPI0_SLAVE_SEL);
+}
+
+/*
+ * Please reset the SPI-controller before starting a transfer. Otherwise the
+ * configuration of the last transfer will interfere with the new transfer
+ */
+static void s3c2443_spi_sw_reset(struct s3c2443_spi *spi)
+{
+ unsigned long cfg;
+
+ cfg = readl(spi->regs + S3C2443_SPI0_CH_CFG);
+ writel(cfg | S3C2443_SPI0_CH_SW_RST, spi->regs + S3C2443_SPI0_CH_CFG);
+ writel(cfg, spi->regs + S3C2443_SPI0_CH_CFG);
+}
+
+
+static int s3c2443_spi_hw_init(struct s3c2443_spi *hw)
+{
+ unsigned long misccr, clkcfg;
+ struct s3c2443_spi_info *info;
+ int cnt;
+ struct s3c24xx_spi_gpio *cs;
+
+ info = hw->pdata;
+
+ writel(readl(S3C2443_SCLKCON) | S3C2443_SCLKCON_HSSPICLK, S3C2443_SCLKCON);
+ writel(readl(S3C2443_PCLKCON) | S3C2443_PCLKCON_HSSPI, S3C2443_PCLKCON);
+
+ if (info->input_clk == S3C2443_HSSPI_INCLK_PCLK)
+ clkcfg = S3C2443_SPI0_CLKSEL_PCLK;
+ else
+ clkcfg = S3C2443_SPI0_CLKSEL_EPLL;
+ writel(clkcfg, hw->regs + S3C2443_SPI0_CLK_CFG);
+
+ misccr = __raw_readl(S3C24XX_MISCCR);
+ __raw_writel(misccr | (1 << 31), S3C24XX_MISCCR);
+
+ /* HSSPI software reset */
+ s3c2443_spi_sw_reset(hw);
+
+ /* Configure the chip selects depending on the passed platform data */
+ s3c2410_gpio_cfgpin(info->miso.nr, info->miso.cfg);
+ s3c2410_gpio_cfgpin(info->mosi.nr, info->mosi.cfg);
+ s3c2410_gpio_cfgpin(info->clk.nr, info->clk.cfg);
+ for (cnt = 0; cnt < info->num_chipselect; cnt++) {
+
+ /*
+ * We need to activate the pullup for being able to reconfigure the
+ * CS as an input IO
+ */
+ cs = info->cs + cnt;
+ s3c2410_gpio_cfgpin(cs->nr, cs->cfg);
+ s3c2410_gpio_pullup(cs->nr, 0);
+ }
+
+ return 0;
+}
+
+/*
+ * Configure the controller for the passed SPI-device. Additionally it will
+ * use the configuration values of the SPI-transfer if it's different than NULL
+ */
+static int s3c2443_spi_setupxfer(struct spi_device *spi,
+ struct spi_transfer *t)
+{
+ struct s3c2443_spi *hw = spi_to_hw(spi);
+ unsigned int bpw;
+ unsigned int hz, max_hz, min_hz;
+ unsigned int div;
+ unsigned long clkreg, chcfg, mode;
+
+ printk_debug("Setup for next xfer called\n");
+
+ /* If the bits per word is zero, then use the default value of the SPI-device */
+ bpw = t ? t->bits_per_word : spi->bits_per_word;
+ if (!bpw)
+ bpw = spi->bits_per_word;
+
+ /* We only support eigth bits per word */
+ if (bpw != 8) {
+ printk_err("Invalid bits-per-word (%d)\n", bpw);
+ return -EINVAL;
+ }
+
+ /*
+ * If the clock rate of the xfer is zero, then use the already configured
+ * rate of this SPI-device
+ */
+ hz = t ? t->speed_hz : spi->max_speed_hz;
+ if (!hz)
+ hz = spi->max_speed_hz;
+
+ /* The below calculation is coming from the SMDK */
+ max_hz = clk_get_rate(hw->clk) / 2;
+ min_hz = max_hz / 256;
+ if (hz > max_hz || hz < min_hz) {
+ printk_err("Speed %d Hz out of range (Min %i | Max %i)\n",
+ hz, min_hz, max_hz);
+ return -EINVAL;
+ }
+
+ div = clk_get_rate(hw->clk) / hz;
+ div /= 2;
+ div = (div) ? (div - 1) : (0);
+ if (div > S3C2443_SPI0_CLK_PRE_MASK) {
+ printk_err("Invalid clock divider %d (%i Hz)\n", div, hz);
+ return -EINVAL;
+ }
+
+ /* Only modify the prescaler */
+ printk_debug("Setting pre-scaler to %d (%d Hz)\n", div, hz);
+ clkreg = readl(hw->regs + S3C2443_SPI0_CLK_CFG) & ~(S3C2443_SPI0_CLK_PRE_MASK);
+ writel(clkreg | div, hw->regs + S3C2443_SPI0_CLK_CFG);
+
+ /*
+ * For some reasons the clock configuration isn't working correctly by
+ * the FIRST execution. For this reason let's make a sanity check and
+ * if required reset the hardware and reconfigure the clock again. This seems
+ * to solve the problem.
+ * Luis Galdos
+ */
+ clkreg = readl(hw->regs + S3C2443_SPI0_CLK_CFG);
+ if ((clkreg & S3C2443_SPI0_CLK_PRE_MASK) != div) {
+ printk_debug("Need to reconfigure the clock\n");
+ s3c2443_spi_hw_init(hw);
+ s3c2443_spi_sw_reset(hw);
+ writel(clkreg | div, hw->regs + S3C2443_SPI0_CLK_CFG);
+ }
+
+ /* Set the correct mode for the passed SPI-transfer */
+ mode = spi->mode & 0x03;
+ switch (mode) {
+ case SPI_MODE_2:
+ /* Sampled by falling edges with a high inactive clock (MODE_2) */
+ chcfg = S3C2443_SPI0_CH_FALLING | S3C2443_SPI0_CH_FORMAT_A;
+ break;
+
+ case SPI_MODE_1:
+ /* Sampled by falling edges with a low inactive clock (MODE_1) */
+ chcfg = S3C2443_SPI0_CH_RISING | S3C2443_SPI0_CH_FORMAT_B;
+ break;
+
+ case SPI_MODE_3:
+ /* Sampled by rising edges with a high inactive clock (MODE_3) */
+ chcfg = S3C2443_SPI0_CH_FALLING | S3C2443_SPI0_CH_FORMAT_B;
+ break;
+
+ case SPI_MODE_0:
+ default:
+ /* Sampled by rising edges with a low inactive clock (MODE_0) */
+ chcfg = S3C2443_SPI0_CH_RISING | S3C2443_SPI0_CH_FORMAT_A;
+ break;
+ }
+
+ printk_debug("Configuring the mode %lu\n", mode);
+ writel(chcfg , hw->regs + S3C2443_SPI0_CH_CFG);
+ return 0;
+}
+
+/*
+ * Configure the master-controller. This function is only called when the higher
+ * layer wants to MODIFY the current configuration of the controller.
+ */
+static int s3c2443_spi_setup(struct spi_device *spi)
+{
+ int ret;
+
+ if (!spi->bits_per_word)
+ spi->bits_per_word = 8;
+
+ if (spi->mode & ~S3C2443_SPI_SUPPORTED_MODES) {
+ printk_debug("Unsupported mode bits %x\n",
+ spi->mode & ~S3C2443_SPI_SUPPORTED_MODES);
+ return -EINVAL;
+ }
+
+ ret = s3c2443_spi_setupxfer(spi, NULL);
+ if (!ret)
+ printk_debug("Mode %d, %u bpw, %d HZ\n",
+ spi->mode, spi->bits_per_word,
+ spi->max_speed_hz);
+
+ return ret;
+}
+
+/*
+ * Tasklet function for checking the message queue. If there is another transfer
+ * pending, then this function will start the transfer, otherwise it calls the
+ * complete function of the SPI-message and reset the internal data for the
+ * next available message of the internal message queue and start it too
+ */
+static void s3c2443_spi_next_tasklet(unsigned long data)
+{
+ struct s3c2443_spi *hw;
+ struct spi_transfer *xfer;
+ struct spi_message *msg;
+ unsigned long stat;
+ int timeout, err;
+
+ hw = (struct s3c2443_spi *)data;
+ if (!hw)
+ return;
+
+ xfer = hw->current_xfer;
+ msg = list_entry(hw->xmit_queue.next, struct spi_message, queue);
+ if (!xfer || !msg)
+ return;
+
+ /*
+ * If we used the own internal DMA-buffer for the DMA-transfer then copy
+ * the data to the higher SPI-message. If not, then cause we have
+ * passed the transfer-buffer to the DMA-controller
+ */
+ if (!msg->is_dma_mapped && xfer->rx_buf && hw->dma_xfer_len) {
+ printk_debug("Copying %lu bytes to SPI-buffer %p\n",
+ hw->dma_xfer_len, xfer->rx_buf);
+ memcpy(xfer->rx_buf, hw->rx_buf, hw->dma_xfer_len);
+ }
+
+ /*
+ * We have a problem at this place, then we don't know if
+ * the shift-register is really empty. For this reason we
+ * use the below loop. Please note that by high configured
+ * clocks the shift register will be already empty at this point
+ * and by low clocks the usleep is not really relevant
+ * compared with the complete transmission time. The problem is
+ * that udelay will cause a higher CPU-load, but we don't
+ * want to sleep before calling the tasklet, and at this place
+ * is no more possible to take a siesta.
+ * Luis Galdos
+ */
+ err = (hw->remaining_bytes < 0) ? (hw->remaining_bytes) : 0;
+ if (!err && xfer->tx_buf) {
+ stat = readl(hw->regs + S3C2443_SPI0_STATUS);
+ timeout = (err) ? 0 : 0xffff;
+ while (!(stat & S3C2443_SPI0_STUS_TX_DONE) && timeout) {
+ udelay(2);
+ stat = readl(hw->regs + S3C2443_SPI0_STATUS);
+ timeout--;
+ }
+
+ if (!timeout)
+ err = -ETIME;
+ }
+
+ /*
+ * If NO more data is remaining then update the internal variables of the
+ * SPI-message and call the complete-function. The errors are passed to us
+ * with an negative value of remaining_bytes
+ */
+ if (hw->remaining_bytes == 0 || err) {
+
+ /* Disable the IRQs and FIFOs, then we are done */
+ writel(0x00, hw->regs + S3C2443_SPI0_CH_CFG);
+
+ msg->actual_length += hw->dma_xfer_len;
+
+ if (xfer->delay_usecs)
+ udelay(xfer->delay_usecs);
+
+ /* Check if the current SPI-transfer was the last of the message */
+ if (msg->transfers.prev == &xfer->transfer_list || err) {
+ msg->status = err;
+
+ /* Disable the slave selection (chip select inactive) */
+ s3c2443_spi_chipsel(msg->spi, SPI_CS_INACTIVE);
+
+ stat = readl(hw->regs + S3C2443_SPI0_STATUS);
+
+ printk_debug("Msg complete %i | stat 0x08%x)\n",
+ err, (unsigned int)stat);
+
+ list_del(&msg->queue);
+ msg->complete(msg->context);
+
+ /*
+ * Now, check for the next message of the internal xmit-queue.
+ * But first lock the code segment, then probably somebody is
+ * trying to start a new transfer at this moment
+ */
+ spin_lock(&hw->xmit_lock);
+ hw->current_xfer = NULL;
+ if (!list_empty(&hw->xmit_queue))
+ s3c2443_spi_next_message(hw->master);
+ spin_unlock(&hw->xmit_lock);
+
+ } else {
+ printk_debug("Calling the next XFER of the msg %p\n", msg);
+
+ /* Toggle the CS if requested */
+ if (xfer->cs_change) {
+ s3c2443_spi_chipsel(msg->spi, SPI_CS_INACTIVE);
+ udelay(1);
+ s3c2443_spi_chipsel(msg->spi, SPI_CS_ACTIVE);
+ }
+
+ s3c2443_spi_next_xfer(hw->master, msg);
+ }
+
+ } else {
+ /*
+ * Start the next write operation if there is remaining data of the
+ * last SPI-transfer
+ */
+ printk_debug("Need to send more data (%lu)\n", hw->remaining_bytes);
+ s3c2443_spi_next_xfer(hw->master, msg);
+ }
+
+}
+
+/*
+ * The DMA-subsytem calls this function from an interrupt context. For this
+ * reason we only update the remaining-bytes counter and check if can
+ * schedule the tasklet for the SPI-complete. BUT the called will be scheduled
+ * only if the FIFO has no more data to send, otherwise we will modify the interrupt
+ * registers for receiving an interrupt from the TX-FIFO later.
+ */
+static void s3c2443_spi_dma_callback(struct s3c2410_dma_chan *ch,
+ void *_hw, int size,
+ enum s3c2410_dma_buffresult result)
+{
+ struct s3c2443_spi *hw;
+ unsigned long mode, inten, stat;
+ int is_tx;
+
+ hw = (struct s3c2443_spi *)_hw;
+ if (!hw) {
+ printk_err("Got a NULL pointer from the DMA-callback!\n");
+ return;
+ }
+
+ /* Check if the callback corresponds to the RX or TX-channel */
+ is_tx = (ch->client == &hw->dmach) ? (1) : (0);
+ printk_debug("%s DMA callback | Ch %i\n", (is_tx) ? "TX" : "RX", ch->number);
+
+ /* If the transfer has a RX-channel running then need to wait for it */
+ if (is_tx && hw->current_xfer->rx_buf && result == S3C2410_RES_OK) {
+ printk_debug("Need to wait for the RX-DMA\n");
+ return;
+ }
+
+ /*
+ * If an error is detected pass the info to the tasklet too (place the error
+ * message into the status of the message).
+ * By aborts only set the number of remaining bytes to zero, so that the
+ * tasklet will complete the message
+ */
+ switch (result) {
+ case S3C2410_RES_OK:
+ printk_debug("Result OK\n");
+ hw->remaining_bytes -= size;
+ hw->dma_xfer_len += size;
+ break;
+ case S3C2410_RES_ERR:
+ printk_err("Result ERROR\n");
+ hw->dma_xfer_len = 0;
+ hw->remaining_bytes = -ERESTART;
+ break;
+ case S3C2410_RES_ABORT:
+ printk_debug("Result ABORT\n");
+ hw->dma_xfer_len += size;
+ hw->remaining_bytes = -ECONNABORTED;
+ break;
+ }
+
+ /*
+ * At this point, normally the TX-FIFO has still data to transfer to the
+ * bus. That means for us, we can enable the interrupt for the TX-FIFO
+ * which informs us about the TX-done. So, only enable
+ * the level IRQ, so that the IRQ-handler will call the tasklet later.
+ * BTW, we only enable the interrupt when we are transferring data using
+ * the DMA-controller, otherwise the DMA-subsystem calls this function when
+ * all the bytes were already read
+ */
+ if (likely(result == S3C2410_RES_OK && hw->current_xfer->tx_buf)) {
+ stat = readl(hw->regs + S3C2443_SPI0_STATUS);
+
+ printk_debug("Need to wait for the TX-FIFO | stat 0x%x\n",
+ (unsigned int)stat);
+
+ /* Disable the DMA-mode for the TX-FIFO */
+ mode = readl(hw->regs + S3C2443_SPI0_MODE_CFG);
+ mode &= ~S3C2443_SPI0_MODE_TXDMA_ON;
+ mode |= (1 << 5);
+ writel(mode, hw->regs + S3C2443_SPI0_MODE_CFG);
+
+ /* Enable the ready interrupt */
+ inten = readl(hw->regs + S3C2443_SPI0_INT_EN);
+ inten |= S3C2443_SPI0_INT_TX_UNDERRUN_EN |
+ S3C2443_SPI0_INT_TX_FIFORDY_EN;
+ writel(inten , hw->regs + S3C2443_SPI0_INT_EN);
+ } else {
+ printk_debug("Calling the tasklet from the DMA-callback\n");
+ tasklet_schedule(&hw->xmit_tasklet);
+ }
+}
+
+/*
+ * IMPORTANT: If the data transfer is smaller than four bytes, then the SPI-controller
+ * must be configured without the burst-mode! Otherwise the channel will run
+ * amok!
+ * Luis Galdos
+ */
+static int s3c2443_spi_dma_init(dmach_t dma_ch, enum s3c2443_xfer_t mode,
+ int length)
+{
+ int xmode;
+
+ printk_debug("DMA init for next %s\n", (mode == S3C2443_DMA_TX) ? "TX" : "RX");
+
+ if (mode == S3C2443_DMA_TX) {
+ /*
+ * For TX-transfers configure the destination with fixed address and
+ * inside the APB-bus. The source of transfers will be the
+ * system memory
+ */
+ s3c2410_dma_devconfig(dma_ch, S3C2410_DMASRC_MEM,
+ S3C2410_DISRCC_INC | S3C2410_DISRCC_APB,
+ S3C2443_SPI0_TX_DATA_PA);
+ } else if (mode == S3C2443_DMA_RX) {
+ /*
+ * By RX-transfer the source is the RX-FIFO of the SPI-controller.
+ * The controller is part of the APB and should not increment
+ * the address
+ */
+ s3c2410_dma_devconfig(dma_ch, S3C2410_DMASRC_HW,
+ S3C2410_DISRCC_INC | S3C2410_DISRCC_APB,
+ S3C2443_SPI0_RX_DATA_PA);
+ } else {
+ printk_err("Invalid DMA transfer mode (%i)\n", mode);
+ return -EINVAL;
+ }
+
+ /*
+ * Select the correct transfer mode depending on the number of bytes
+ * to transfer. Unfortunately the DMA-controller can transfer only data
+ * quantums depending on the number of the data size to be transferred
+ * (see register: DCON, field DSZ)
+ * Luis Galdos
+ */
+ if (!(length & 0x3))
+ xmode = S3C24XX_DMA_XFER_WORD;
+ else
+ xmode = S3C24XX_DMA_XFER_BYTE;
+
+ /* @XXX: Don't use the burst mode, then it's not working with this driver */
+ s3c2410_dma_config(dma_ch,
+ xmode,
+ S3C2410_DCON_HANDSHAKE |
+ S3C2410_DCON_SYNC_PCLK |
+ S3C2410_DCON_HWTRIG);
+
+ s3c2410_dma_setflags(dma_ch, S3C2410_DMAF_AUTOSTART);
+ return 0;
+}
+
+/*
+ * This function is used to handle the next transfer of a SPI-message.
+ * If the configuration of the transfer failed, then this function will
+ * set the error code and will schedule the tasklet for completing the
+ * SPI-message correctly.
+ */
+static void s3c2443_spi_next_xfer(struct spi_master *master,
+ struct spi_message *msg)
+{
+ struct spi_transfer *xfer;
+ struct s3c2443_spi *hw;
+ unsigned long len;
+ dma_addr_t rx_dma, tx_dma;
+ int err;
+ unsigned long clkcfg, chcfg, modecfg, spi_packet;
+
+ hw = master_to_hw(master);
+
+ /* Always reset the controller first */
+ s3c2443_spi_sw_reset(hw);
+
+ /*
+ * Check if we are going to start sending a complete new message, or if
+ * the passed message was already used for sending a SPI-transfer
+ */
+ xfer = hw->current_xfer;
+ if (!xfer || hw->remaining_bytes == 0) {
+ if (xfer)
+ xfer = list_entry(xfer->transfer_list.next,
+ struct spi_transfer, transfer_list);
+ else
+ xfer = list_entry(msg->transfers.next,
+ struct spi_transfer, transfer_list);
+
+ hw->current_xfer = xfer;
+ hw->remaining_bytes = xfer->len;
+ hw->dma_xfer_len = 0;
+ }
+
+ /* Set the requested configuration for this new SPI-transfer */
+ err = s3c2443_spi_setupxfer(msg->spi, xfer);
+ if (err) {
+ printk_err("Setting up the next SPI-transfer\n");
+ hw->remaining_bytes = err;
+ goto exit_err;
+ }
+
+ len = hw->remaining_bytes;
+
+ /* 2. Enable the output clock (but don't modify the clock prescaler) */
+ clkcfg = readl(hw->regs + S3C2443_SPI0_CLK_CFG);
+ clkcfg |= S3C2443_SPI0_ENCLK_ENABLE;
+ writel(clkcfg, hw->regs + S3C2443_SPI0_CLK_CFG);
+
+ /*
+ * Configure the transfer mode depending on the number of bytes to send
+ * @XXX: For some unknown reasons we can't enable the burst mode. Please be
+ * aware then the burst mode for the FIFOs will work ONLY if the
+ * DMA-channel is configured for the burst mode too (so far so good).
+ * Luis Galdos
+ */
+ modecfg = S3C2443_SPI0_MODE_CH_TSZ_BYTE;
+ if (!(len & 0x3))
+ modecfg = S3C2443_SPI0_MODE_CH_TSZ_WORD;
+
+ /*
+ * Enable the DMA-modes depending on the transfer type
+ */
+ if (xfer->tx_buf)
+ modecfg |= S3C2443_SPI0_MODE_TXDMA_ON;
+ if (xfer->rx_buf)
+ modecfg |= S3C2443_SPI0_MODE_RXDMA_ON;
+
+ writel(modecfg, hw->regs + S3C2443_SPI0_MODE_CFG);
+
+ /*
+ * 4. Set SPI INT_EN register
+ * Disable all the interrupts then we only use the DMA-controller for
+ * transferring the data from the RAM to the FIFO
+ */
+ writel(0x00, hw->regs + S3C2443_SPI0_INT_EN);
+
+ /* Clear the pending register */
+ writel(0x1f, hw->regs + S3C2443_SPI0_PENDING_CLR);
+
+ /*
+ * If the passed SPI-buffer isn't DMA-mapped, then use the internal
+ * DMA-buffers for sending the data to the DMA-layer. If we are using
+ * the internal buffer, then check that we can only send the maximal
+ * number of bytes. In the tasklet the transfer of the rest data can
+ * be triggered.
+ */
+ tx_dma = xfer->tx_dma;
+ rx_dma = xfer->rx_dma;
+ if (!msg->is_dma_mapped) {
+
+ printk_debug("SPI-message not DMA mapped\n");
+
+ rx_dma = hw->rx_dma;
+ if (len > S3C2443_SPI_DMA_BUFFER)
+ len = S3C2443_SPI_DMA_BUFFER;
+
+ if (xfer->tx_buf) {
+ tx_dma = hw->tx_dma;
+
+ if (len > S3C2443_SPI_DMA_BUFFER)
+ len = S3C2443_SPI_DMA_BUFFER;
+
+ /* IMPORTANT: Copy the TX-data from the correct place! */
+ memcpy(hw->tx_buf,
+ xfer->tx_buf + hw->dma_xfer_len, len);
+ }
+ }
+
+ /*
+ * 5. Set Packet Count configuration register
+ * For some unknown reasons the transfer of only ONE byte isn't
+ * working correctly. The data byte transferred from the DMA-controller
+ * isn't flushed from the TX-FIFO to the bus. But disabling the trailling
+ * count and reconfiguring the TX-FIFO helps to fix this problem.
+ * (Luis Galdos)
+ */
+ spi_packet = len | S3C2443_SPI0_PACKET_CNT_EN;
+ if (unlikely(len == 1)) {
+ spi_packet = 0;
+ s3c2443_spi_sw_reset(hw);
+ s3c2443_spi_setupxfer(msg->spi, xfer);
+ }
+ writel(spi_packet, hw->regs + S3C2443_SPI0_PACKET_CNT);
+
+ printk_debug("%s : %lu bytes | Rx %p | Tx %p\n", (xfer->tx_buf) ? "TX" : "RX",
+ len, xfer->rx_buf, xfer->tx_buf);
+
+ s3c2443_spi_chipsel(msg->spi, SPI_CS_ACTIVE);
+
+ /*
+ * Prepare the DMA-channel with the correct direction, and callback-function
+ * before sending the data to the DMA-subsystem
+ * If the TX-channel is being used, then only enable the TX-callback, otherwise
+ * use the RX-channel for the callback
+ */
+ s3c2410_dma_set_buffdone_fn(SPI_TX_CHANNEL, NULL);
+ s3c2410_dma_set_buffdone_fn(SPI_RX_CHANNEL, NULL);
+ if (xfer->tx_buf)
+ s3c2410_dma_set_buffdone_fn(SPI_TX_CHANNEL, s3c2443_spi_dma_callback);
+ if (xfer->rx_buf)
+ s3c2410_dma_set_buffdone_fn(SPI_RX_CHANNEL, s3c2443_spi_dma_callback);
+
+
+ if (xfer->rx_buf) {
+ s3c2443_spi_dma_init(hw->dma_ch_rx, S3C2443_DMA_RX, len);
+ err = s3c2410_dma_enqueue(hw->dma_ch_rx, hw, rx_dma, len);
+ if (err) {
+ printk_err("Starting the RX DMA-channel, %i\n", err);
+ s3c2410_dma_ctrl(hw->dma_ch_rx, S3C2410_DMAOP_FLUSH);
+ hw->remaining_bytes = -EBUSY;
+ goto exit_err;
+ }
+ }
+
+ if (xfer->tx_buf) {
+ s3c2443_spi_dma_init(hw->dma_ch, S3C2443_DMA_TX, len);
+ err = s3c2410_dma_enqueue(hw->dma_ch, hw, tx_dma, len);
+ if (err) {
+ printk_err("Starting the TX DMA-channel, %i\n", err);
+ hw->remaining_bytes = -EBUSY;
+ goto exit_err;
+ }
+ }
+
+ /* Init the FIFOs */
+ chcfg = readl(hw->regs + S3C2443_SPI0_CH_CFG);
+ chcfg &= ~(S3C2443_SPI0_CH_TXCH_ON | S3C2443_SPI0_CH_RXCH_ON);
+ if (xfer->tx_buf)
+ chcfg |= S3C2443_SPI0_CH_TXCH_ON;
+ if (xfer->rx_buf)
+ chcfg |= S3C2443_SPI0_CH_RXCH_ON;
+
+ writel(chcfg, hw->regs + S3C2443_SPI0_CH_CFG);
+
+ return;
+
+ /* By errors only schedule the tasklet */
+ exit_err:
+ tasklet_schedule(&hw->xmit_tasklet);
+}
+
+/* Get the head of the message queue and start the xmit */
+static void s3c2443_spi_next_message(struct spi_master *master)
+{
+ struct s3c2443_spi *hw;
+ struct spi_message *msg;
+
+ hw = master_to_hw(master);
+
+ msg = list_entry(hw->xmit_queue.next, struct spi_message, queue);
+
+ printk_debug("Starting to handle a new message %p\n", msg);
+
+ /* And start to send the new message now */
+ /* s3c2443_spi_chipsel(msg->spi, SPI_CS_ACTIVE); */
+ s3c2443_spi_next_xfer(master, msg);
+}
+
+/*
+ * This function will be called from the external SPI-layer for sending data
+ * to the bus. It will return inmediately, if another message is being
+ * transmitted, otherwise it will start sending the data to the bus
+ */
+static int s3c2443_spi_transfer(struct spi_device *spi, struct spi_message *msg)
+{
+ struct s3c2443_spi *hw = spi_to_hw(spi);
+ struct spi_transfer *xfer;
+
+ if (unlikely(list_empty(&msg->transfers)))
+ return -EINVAL;
+
+ list_for_each_entry(xfer, &msg->transfers, transfer_list) {
+ if (!xfer->tx_buf && !xfer->rx_buf) {
+ printk_err("Missing rx/tx buffer.\n");
+ return -EINVAL;
+ }
+ }
+
+ msg->status = -EINPROGRESS;
+ msg->actual_length = 0;
+
+ /*
+ * Add the new arrived message to the internal queue and start the
+ * transfer if the xmit-queue is IDLE
+ */
+ spin_lock(&hw->xmit_lock);
+ list_add_tail(&msg->queue, &hw->xmit_queue);
+ if (!hw->current_xfer)
+ s3c2443_spi_next_message(spi->master);
+ spin_unlock(&hw->xmit_lock);
+
+ return 0;
+}
+
+/*
+ * Normally the IRQs are not used for transferring data to the FIFOs, we use only
+ * the DMA-controller for this purpose.
+ *
+ * @FIXME: For some reasons we are not clearing all the interrupts correctly. With
+ * the activated debug prints you will see what I'm talking about.
+ * (Luis Galdos)
+ */
+static irqreturn_t s3c2443_spi_irq(int irq, void *_hw)
+{
+ struct s3c2443_spi *hw = _hw;
+ unsigned int spsta = readl(hw->regs + S3C2443_SPI0_STATUS);
+
+ spin_lock(&hw->xmit_lock);
+
+ /* Disable all the interrupts and clear the pending errors */
+ writel(0x00, hw->regs + S3C2443_SPI0_INT_EN);
+ writel(spsta, hw->regs + S3C2443_SPI0_STATUS);
+ writel(0x1f, hw->regs + S3C2443_SPI0_PENDING_CLR);
+
+ printk_debug("IRQ stat 0x%08x\n", spsta);
+
+ /*
+ * If we detect an error than force to the complete of the SPI-message by
+ * setting the number of remaining bytes to zero
+ */
+ if (spsta &
+ (S3C2443_SPI0_STUS_RX_OVERRUN_ERR | S3C2443_SPI0_STUS_RX_UNDERRUN_ERR)) {
+ hw->remaining_bytes = 0;
+ printk_err("RX error (0x%08x)\n", spsta);
+ }
+
+ if (spsta &
+ (S3C2443_SPI0_STUS_TX_OVERRUN_ERR | S3C2443_SPI0_STUS_TX_UNDERRUN_ERR)) {
+ hw->remaining_bytes = 0;
+ printk_err("TX error (0x%08x)\n", spsta);
+ }
+
+ /* Force the complete of the current transfer */
+ tasklet_schedule(&hw->xmit_tasklet);
+
+ spin_unlock(&hw->xmit_lock);
+ return IRQ_HANDLED;
+}
+
+static int __devinit s3c2443_spi_probe(struct platform_device *pdev)
+{
+ struct s3c2443_spi_info *pdata;
+ struct s3c2443_spi *hw;
+ struct spi_master *master;
+ struct resource *res;
+ int err = 0;
+
+ printk_info("Probing device with the ID %i\n", pdev->id);
+
+ master = spi_alloc_master(&pdev->dev, sizeof(struct s3c2443_spi));
+ if (master == NULL) {
+ dev_err(&pdev->dev, "No memory for spi_master\n");
+ err = -ENOMEM;
+ goto err_exit;
+ }
+
+ hw = spi_master_get_devdata(master);
+ memset(hw, 0, sizeof(struct s3c2443_spi));
+
+ hw->master = spi_master_get(master);
+ hw->pdata = pdev->dev.platform_data;
+ hw->dev = &pdev->dev;
+
+ pdata = pdev->dev.platform_data;
+ if (pdata == NULL) {
+ dev_err(&pdev->dev, "No platform data supplied\n");
+ err = -ENOENT;
+ goto err_put_spi;
+ }
+
+ /* Sanity checks for the passed platform data */
+ hw->pdata = pdata;
+ if (hw->pdata->num_chipselect > 1) {
+ printk_err("Only supports one chip select (%i passed)\n",
+ hw->pdata->num_chipselect);
+ goto err_put_spi;
+ }
+
+ platform_set_drvdata(pdev, hw);
+
+ /* find and map our resources */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (res == NULL) {
+ printk_err("Cannot get IORESOURCE_MEM\n");
+ err = -ENOENT;
+ goto err_put_spi;
+ }
+
+ hw->ioarea = request_mem_region(res->start, (res->end - res->start)+1,
+ pdev->name);
+ if (hw->ioarea == NULL) {
+ printk_err("Cannot reserve region\n");
+ err = -ENXIO;
+ goto err_put_spi;
+ }
+
+ hw->regs = ioremap(res->start, (res->end - res->start) + 1);
+ if (hw->regs == NULL) {
+ printk_err("Cannot map IO\n");
+ err = -ENXIO;
+ goto err_release_iomem;
+ }
+
+ hw->irq = platform_get_irq(pdev, 0);
+ if (hw->irq <= 0) {
+ printk_err("No IRQ specified? Aborting.\n");
+ err = -ENOENT;
+ goto err_unmap_iomem;
+ }
+
+ err = request_irq(hw->irq, s3c2443_spi_irq, 0, pdev->name, hw);
+ if (err) {
+ printk_err("Cannot claim IRQ\n");
+ goto err_unmap_iomem;
+ }
+
+ /* Use the passed input clock */
+ if (pdata->input_clk == S3C2443_HSSPI_INCLK_PCLK)
+ hw->clk = clk_get(&pdev->dev, "pclk");
+ else if (pdata->input_clk == S3C2443_HSSPI_INCLK_EPLL)
+ hw->clk = clk_get(&pdev->dev, "epll");
+ else {
+ printk_err("Invalid input clock passed (%i)\n", pdata->input_clk);
+ goto err_free_irq;
+ }
+
+ if (IS_ERR(hw->clk)) {
+ printk_err("No clock for device\n");
+ err = PTR_ERR(hw->clk);
+ goto err_free_irq;
+ }
+
+ /* @FIXME: For the moment, permanently enable the clock */
+ clk_enable(hw->clk);
+
+ printk_info("Input clock frequency: %lu Hz\n", clk_get_rate(hw->clk));
+
+ /* Init the internal data */
+ spin_lock_init(&hw->xmit_lock);
+ INIT_LIST_HEAD(&hw->xmit_queue);
+
+ /* Create the DMA-pool for DMA-unmapped message */
+ hw->tx_buf = dma_alloc_coherent(&pdev->dev, S3C2443_SPI_DMA_BUFFER,
+ &hw->tx_dma, GFP_DMA);
+ if (!hw->tx_buf) {
+ printk_err("Couldn't get the TX DMA-memory\n");
+ err = -ENOMEM;
+ goto err_put_clk;
+ }
+
+ hw->rx_buf = dma_alloc_coherent(&pdev->dev, S3C2443_SPI_DMA_BUFFER,
+ &hw->rx_dma, GFP_DMA);
+ if (!hw->rx_buf) {
+ printk_err("Couldn't get the RX DMA-memory\n");
+ err = -ENOMEM;
+ goto err_free_txdma;
+ }
+
+ /* We are safe, init the internal data for receiving the transfer requests */
+ tasklet_init(&hw->xmit_tasklet, s3c2443_spi_next_tasklet,
+ (unsigned long)hw);
+
+ /* Request the channel for the TX-transfers */
+ hw->dmach.name = (char *)pdev->name;
+ err = s3c2410_dma_request(SPI_TX_CHANNEL, &hw->dmach, hw);
+ if (err < 0) {
+ printk_err("TX DMA channel %i request failed\n.", SPI_TX_CHANNEL);
+ goto err_free_rxdma;
+ }
+ hw->dma_ch = SPI_TX_CHANNEL;
+ printk_debug("Got the TX DMA-channel %i (%i)\n", hw->dma_ch, SPI_TX_CHANNEL);
+
+ /* Request the channel for the RX-transfers */
+ hw->dmach_rx.name = (char *)pdev->name;
+ err = s3c2410_dma_request(SPI_RX_CHANNEL, &hw->dmach_rx, hw);
+ if (err < 0) {
+ printk_err("RX DMA channel %i request failed\n.", SPI_RX_CHANNEL);
+ goto err_free_chtx;
+ }
+ hw->dma_ch_rx = SPI_RX_CHANNEL;
+ printk_debug("Got the RX DMA-channel %i (%i)\n", hw->dma_ch_rx, SPI_RX_CHANNEL);
+
+ /* Set the callback function for the DMA-channel */
+ s3c2410_dma_set_buffdone_fn(SPI_TX_CHANNEL, NULL);
+ s3c2410_dma_set_opfn(SPI_TX_CHANNEL, NULL);
+ s3c2410_dma_set_buffdone_fn(SPI_RX_CHANNEL, NULL);
+ s3c2410_dma_set_opfn(SPI_RX_CHANNEL, NULL);
+
+ /* Setup and register the SPI master */
+ master->num_chipselect = pdata->num_chipselect;
+ master->setup = s3c2443_spi_setup;
+ master->transfer = s3c2443_spi_transfer;
+ master->bus_num = pdata->bus_num;
+ err = spi_register_master(hw->master);
+ if (err) {
+ printk_err("Failed to register the SPI master\n");
+ goto err_free_chrx;
+ }
+
+ /* Init the hardware (reset, enable clock, etc.) */
+ s3c2443_spi_hw_init(hw);
+
+ return 0;
+
+ err_free_chrx:
+ s3c2410_dma_free(SPI_RX_CHANNEL, &hw->dmach);
+
+ err_free_chtx:
+ s3c2410_dma_free(SPI_TX_CHANNEL, &hw->dmach);
+
+ err_free_rxdma:
+ dma_free_coherent(&pdev->dev, S3C2443_SPI_DMA_BUFFER, hw->rx_buf, hw->rx_dma);
+
+ err_free_txdma:
+ dma_free_coherent(&pdev->dev, S3C2443_SPI_DMA_BUFFER, hw->tx_buf, hw->tx_dma);
+
+ err_put_clk:
+ clk_disable(hw->clk);
+ clk_put(hw->clk);
+
+ err_free_irq:
+ free_irq(hw->irq, hw);
+
+ err_unmap_iomem:
+ iounmap(hw->regs);
+
+ err_release_iomem:
+ release_resource(hw->ioarea);
+ kfree(hw->ioarea);
+
+ err_put_spi:
+ spi_master_put(hw->master);;
+
+ err_exit:
+ platform_set_drvdata(pdev, NULL);
+ return err;
+}
+
+static int __devexit s3c2443_spi_remove(struct platform_device *pdev)
+{
+ struct s3c2443_spi *hw = platform_get_drvdata(pdev);
+
+ spi_unregister_master(hw->master);
+
+
+ tasklet_kill(&hw->xmit_tasklet);
+
+ clk_disable(hw->clk);
+ clk_put(hw->clk);
+
+ free_irq(hw->irq, hw);
+ iounmap(hw->regs);
+
+ release_resource(hw->ioarea);
+ kfree(hw->ioarea);
+
+ /* Free the allocated DMA-buffers */
+ dma_free_coherent(&pdev->dev, S3C2443_SPI_DMA_BUFFER, hw->tx_buf, hw->tx_dma);
+ dma_free_coherent(&pdev->dev, S3C2443_SPI_DMA_BUFFER, hw->rx_buf, hw->rx_dma);
+
+ /* Free the DMA-channels */
+ s3c2410_dma_free(SPI_TX_CHANNEL, &hw->dmach);
+ s3c2410_dma_free(SPI_RX_CHANNEL, &hw->dmach_rx);
+
+ /* Free the SPI-master */
+ spi_master_put(hw->master);
+
+ platform_set_drvdata(pdev, NULL);
+
+ return 0;
+}
+
+
+#ifdef CONFIG_PM
+static int s3c2443_spi_suspend(struct platform_device *pdev, pm_message_t msg)
+{
+ struct s3c2443_spi *hw = platform_get_drvdata(pdev);
+
+ clk_disable(hw->clk);
+ return 0;
+}
+
+static int s3c2443_spi_resume(struct platform_device *pdev)
+{
+ struct s3c2443_spi *hw;
+ int retval;
+
+ hw = platform_get_drvdata(pdev);
+
+ /* @FIXME: Why do we need to free the DMA-channels? */
+ s3c2410_dma_free(SPI_TX_CHANNEL, &hw->dmach);
+ s3c2410_dma_free(SPI_RX_CHANNEL, &hw->dmach_rx);
+
+ retval = s3c2410_dma_request(SPI_TX_CHANNEL, &hw->dmach, hw);
+ if (retval < 0)
+ goto exit_resume;
+
+ retval = s3c2410_dma_request(SPI_RX_CHANNEL, &hw->dmach_rx, hw);
+ if (retval < 0)
+ goto exit_free_tx;
+
+ clk_enable(hw->clk);
+ return 0;
+
+exit_free_tx:
+ s3c2410_dma_free(SPI_TX_CHANNEL, &hw->dmach);
+
+exit_resume:
+ return retval;
+}
+#else
+#define s3c2443_spi_suspend NULL
+#define s3c2443_spi_resume NULL
+#endif
+
+MODULE_ALIAS("platform:s3c2443-spi");
+
+static struct platform_driver s3c2443_spi_driver = {
+ .probe = s3c2443_spi_probe,
+ .remove = __devexit_p(s3c2443_spi_remove),
+ .suspend = s3c2443_spi_suspend,
+ .resume = s3c2443_spi_resume,
+ .driver = {
+ .name = "spi-s3c2443",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init s3c2443_spi_init(void)
+{
+ return platform_driver_register(&s3c2443_spi_driver);
+}
+
+static void __exit s3c2443_spi_exit(void)
+{
+ platform_driver_unregister(&s3c2443_spi_driver);
+}
+
+module_init(s3c2443_spi_init);
+module_exit(s3c2443_spi_exit);
+
+MODULE_DESCRIPTION("S3C2443 SPI Driver");
+MODULE_AUTHOR("Luis Galdos, <luis.galdos@digi.com>");
+MODULE_LICENSE("GPL");