summaryrefslogtreecommitdiff
path: root/drivers/spi
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/spi')
-rw-r--r--drivers/spi/Kconfig12
-rw-r--r--drivers/spi/Makefile2
-rw-r--r--drivers/spi/mxc_spi.c1316
-rw-r--r--drivers/spi/spi_mxs.c750
-rw-r--r--drivers/spi/spi_mxs.h54
-rw-r--r--drivers/spi/spi_sam.c1161
6 files changed, 3295 insertions, 0 deletions
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 91c2f4f3af10..7248cb14cb5f 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -337,6 +337,18 @@ config SPI_NUC900
help
SPI driver for Nuvoton NUC900 series ARM SoCs
+config SPI_MXC
+ tristate "Freescale MXC CSPI controller"
+ depends on ARCH_MXC && SPI_MASTER
+ help
+ SPI driver for Freescale MXC CSPI interface
+
+config SPI_MXS
+ tristate "Freescale MXS SPI/SSP controller"
+ depends on ARCH_MXS && SPI_MASTER
+ help
+ SPI driver for Freescale MXS SoC SSP interface
+
#
# Add new SPI master controllers in alphabetical order above this line
#
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index e9cbd18217a0..ac1fb109a48c 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -47,6 +47,8 @@ obj-$(CONFIG_SPI_SH_SCI) += spi_sh_sci.o
obj-$(CONFIG_SPI_SH_MSIOF) += spi_sh_msiof.o
obj-$(CONFIG_SPI_STMP3XXX) += spi_stmp.o
obj-$(CONFIG_SPI_NUC900) += spi_nuc900.o
+obj-$(CONFIG_SPI_MXS) += spi_mxs.o
+obj-$(CONFIG_SPI_MXC) += mxc_spi.o
# special build for s3c24xx spi driver with fiq support
spi_s3c24xx_hw-y := spi_s3c24xx.o
diff --git a/drivers/spi/mxc_spi.c b/drivers/spi/mxc_spi.c
new file mode 100644
index 000000000000..8e255f48bd7a
--- /dev/null
+++ b/drivers/spi/mxc_spi.c
@@ -0,0 +1,1316 @@
+/*
+ * Copyright (C) 2004-2010 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-licensisr_locke.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+/*!
+ * @defgroup SPI Configurable Serial Peripheral Interface (CSPI) Driver
+ */
+
+/*!
+ * @file mxc_spi.c
+ * @brief This file contains the implementation of the SPI master controller services
+ *
+ *
+ * @ingroup SPI
+ */
+
+#include <linux/slab.h>
+#include <linux/completion.h>
+#include <linux/platform_device.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/types.h>
+#include <linux/clk.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/spi_bitbang.h>
+#include <linux/fsl_devices.h>
+
+#define MXC_CSPIRXDATA 0x00
+#define MXC_CSPITXDATA 0x04
+#define MXC_CSPICTRL 0x08
+#define MXC_CSPICONFIG 0x08
+#define MXC_CSPIINT 0x0C
+
+#define MXC_CSPICTRL_DISABLE 0x0
+#define MXC_CSPICTRL_SLAVE 0x0
+#define MXC_CSPICTRL_CSMASK 0x3
+#define MXC_CSPICTRL_SMC (1 << 3)
+
+#define MXC_CSPIINT_TEEN_SHIFT 0
+#define MXC_CSPIINT_THEN_SHIFT 1
+#define MXC_CSPIINT_TFEN_SHIFT 2
+#define MXC_CSPIINT_RREN_SHIFT 3
+#define MXC_CSPIINT_RHEN_SHIFT 4
+#define MXC_CSPIINT_RFEN_SHIFT 5
+#define MXC_CSPIINT_ROEN_SHIFT 6
+
+#define MXC_HIGHPOL 0x0
+#define MXC_NOPHA 0x0
+#define MXC_LOWSSPOL 0x0
+
+#define MXC_CSPISTAT_TE 0
+#define MXC_CSPISTAT_TH 1
+#define MXC_CSPISTAT_TF 2
+#define MXC_CSPISTAT_RR 3
+#define MXC_CSPISTAT_RH 4
+#define MXC_CSPISTAT_RF 5
+#define MXC_CSPISTAT_RO 6
+
+#define MXC_CSPIPERIOD_32KHZ (1 << 15)
+
+/*!
+ * @struct mxc_spi_unique_def
+ * @brief This structure contains information that differs with
+ * SPI master controller hardware version
+ */
+struct mxc_spi_unique_def {
+ /* Width of valid bits in MXC_CSPIINT */
+ unsigned int intr_bit_shift;
+ /* Chip Select shift */
+ unsigned int cs_shift;
+ /* Bit count shift */
+ unsigned int bc_shift;
+ /* Bit count mask */
+ unsigned int bc_mask;
+ /* Data Control shift */
+ unsigned int drctrl_shift;
+ /* Transfer Complete shift */
+ unsigned int xfer_complete;
+ /* Bit counnter overflow shift */
+ unsigned int bc_overflow;
+ /* FIFO Size */
+ unsigned int fifo_size;
+ /* Control reg address */
+ unsigned int ctrl_reg_addr;
+ /* Status reg address */
+ unsigned int stat_reg_addr;
+ /* Period reg address */
+ unsigned int period_reg_addr;
+ /* Test reg address */
+ unsigned int test_reg_addr;
+ /* Reset reg address */
+ unsigned int reset_reg_addr;
+ /* SPI mode mask */
+ unsigned int mode_mask;
+ /* SPI enable */
+ unsigned int spi_enable;
+ /* XCH bit */
+ unsigned int xch;
+ /* Spi mode shift */
+ unsigned int mode_shift;
+ /* Spi master mode enable */
+ unsigned int master_enable;
+ /* TX interrupt enable diff */
+ unsigned int tx_inten_dif;
+ /* RX interrupt enable bit diff */
+ unsigned int rx_inten_dif;
+ /* Interrupt status diff */
+ unsigned int int_status_dif;
+ /* Low pol shift */
+ unsigned int low_pol_shift;
+ /* Phase shift */
+ unsigned int pha_shift;
+ /* SS control shift */
+ unsigned int ss_ctrl_shift;
+ /* SS pol shift */
+ unsigned int ss_pol_shift;
+ /* Maximum data rate */
+ unsigned int max_data_rate;
+ /* Data mask */
+ unsigned int data_mask;
+ /* Data shift */
+ unsigned int data_shift;
+ /* Loopback control */
+ unsigned int lbc;
+ /* RX count off */
+ unsigned int rx_cnt_off;
+ /* RX count mask */
+ unsigned int rx_cnt_mask;
+ /* Reset start */
+ unsigned int reset_start;
+ /* SCLK control inactive state shift */
+ unsigned int sclk_ctl_shift;
+};
+
+struct mxc_spi;
+
+/*!
+ * Structure to group together all the data buffers and functions
+ * used in data transfers.
+ */
+struct mxc_spi_xfer {
+ /* Transmit buffer */
+ const void *tx_buf;
+ /* Receive buffer */
+ void *rx_buf;
+ /* Data transfered count */
+ unsigned int count;
+ /* Data received count, descending sequence, zero means no more data to
+ be received */
+ unsigned int rx_count;
+ /* Function to read the FIFO data to rx_buf */
+ void (*rx_get) (struct mxc_spi *, u32 val);
+ /* Function to get the data to be written to FIFO */
+ u32(*tx_get) (struct mxc_spi *);
+};
+
+/*!
+ * This structure is a way for the low level driver to define their own
+ * \b spi_master structure. This structure includes the core \b spi_master
+ * structure that is provided by Linux SPI Framework/driver as an
+ * element and has other elements that are specifically required by this
+ * low-level driver.
+ */
+struct mxc_spi {
+ /* SPI Master and a simple I/O queue runner */
+ struct spi_bitbang mxc_bitbang;
+ /* Completion flags used in data transfers */
+ struct completion xfer_done;
+ /* Data transfer structure */
+ struct mxc_spi_xfer transfer;
+ /* Resource structure, which will maintain base addresses and IRQs */
+ struct resource *res;
+ /* Base address of CSPI, used in readl and writel */
+ void *base;
+ /* CSPI IRQ number */
+ int irq;
+ /* CSPI Clock id */
+ struct clk *clk;
+ /* CSPI input clock SCLK */
+ unsigned long spi_ipg_clk;
+ /* CSPI registers' bit pattern */
+ struct mxc_spi_unique_def *spi_ver_def;
+ /* Control reg address */
+ void *ctrl_addr;
+ /* Status reg address */
+ void *stat_addr;
+ /* Period reg address */
+ void *period_addr;
+ /* Test reg address */
+ void *test_addr;
+ /* Reset reg address */
+ void *reset_addr;
+ /* Chipselect active function */
+ void (*chipselect_active) (int cspi_mode, int status, int chipselect);
+ /* Chipselect inactive function */
+ void (*chipselect_inactive) (int cspi_mode, int status, int chipselect);
+};
+
+#ifdef CONFIG_SPI_MXC_TEST_LOOPBACK
+struct spi_chip_info {
+ int lb_enable;
+};
+
+static struct spi_chip_info lb_chip_info = {
+ .lb_enable = 1,
+};
+
+static struct spi_board_info loopback_info[] = {
+#ifdef CONFIG_SPI_MXC_SELECT1
+ {
+ .modalias = "spidev",
+ .controller_data = &lb_chip_info,
+ .irq = 0,
+ .max_speed_hz = 4000000,
+ .bus_num = 1,
+ .chip_select = 4,
+ },
+#endif
+#ifdef CONFIG_SPI_MXC_SELECT2
+ {
+ .modalias = "spidev",
+ .controller_data = &lb_chip_info,
+ .irq = 0,
+ .max_speed_hz = 4000000,
+ .bus_num = 2,
+ .chip_select = 4,
+ },
+#endif
+#ifdef CONFIG_SPI_MXC_SELECT3
+ {
+ .modalias = "spidev",
+ .controller_data = &lb_chip_info,
+ .irq = 0,
+ .max_speed_hz = 4000000,
+ .bus_num = 3,
+ .chip_select = 4,
+ },
+#endif
+};
+#endif
+
+static struct mxc_spi_unique_def spi_ver_2_3 = {
+ .intr_bit_shift = 8,
+ .cs_shift = 18,
+ .bc_shift = 20,
+ .bc_mask = 0xFFF,
+ .drctrl_shift = 16,
+ .xfer_complete = (1 << 7),
+ .bc_overflow = 0,
+ .fifo_size = 64,
+ .ctrl_reg_addr = 4,
+ .stat_reg_addr = 0x18,
+ .period_reg_addr = 0x1C,
+ .test_reg_addr = 0x20,
+ .reset_reg_addr = 0x8,
+ .mode_mask = 0xF,
+ .spi_enable = 0x1,
+ .xch = (1 << 2),
+ .mode_shift = 4,
+ .master_enable = 0,
+ .tx_inten_dif = 0,
+ .rx_inten_dif = 0,
+ .int_status_dif = 0,
+ .low_pol_shift = 4,
+ .pha_shift = 0,
+ .ss_ctrl_shift = 8,
+ .ss_pol_shift = 12,
+ .max_data_rate = 0xF,
+ .data_mask = 0xFF,
+ .data_shift = 8,
+ .lbc = (1 << 31),
+ .rx_cnt_off = 8,
+ .rx_cnt_mask = (0x7F << 8),
+ .reset_start = 0,
+ .sclk_ctl_shift = 20,
+};
+
+static struct mxc_spi_unique_def spi_ver_0_7 = {
+ .intr_bit_shift = 8,
+ .cs_shift = 12,
+ .bc_shift = 20,
+ .bc_mask = 0xFFF,
+ .drctrl_shift = 8,
+ .xfer_complete = (1 << 7),
+ .bc_overflow = 0,
+ .fifo_size = 8,
+ .ctrl_reg_addr = 0,
+ .stat_reg_addr = 0x14,
+ .period_reg_addr = 0x18,
+ .test_reg_addr = 0x1C,
+ .reset_reg_addr = 0x0,
+ .mode_mask = 0x1,
+ .spi_enable = 0x1,
+ .xch = (1 << 2),
+ .mode_shift = 1,
+ .master_enable = 1 << 1,
+ .tx_inten_dif = 0,
+ .rx_inten_dif = 0,
+ .int_status_dif = 0,
+ .low_pol_shift = 4,
+ .pha_shift = 5,
+ .ss_ctrl_shift = 6,
+ .ss_pol_shift = 7,
+ .max_data_rate = 0x7,
+ .data_mask = 0x7,
+ .data_shift = 16,
+ .lbc = (1 << 14),
+ .rx_cnt_off = 4,
+ .rx_cnt_mask = (0xF << 4),
+ .reset_start = 1,
+};
+
+static struct mxc_spi_unique_def spi_ver_0_5 = {
+ .intr_bit_shift = 9,
+ .cs_shift = 12,
+ .bc_shift = 20,
+ .bc_mask = 0xFFF,
+ .drctrl_shift = 8,
+ .xfer_complete = (1 << 8),
+ .bc_overflow = (1 << 7),
+ .fifo_size = 8,
+ .ctrl_reg_addr = 0,
+ .stat_reg_addr = 0x14,
+ .period_reg_addr = 0x18,
+ .test_reg_addr = 0x1C,
+ .reset_reg_addr = 0x0,
+ .mode_mask = 0x1,
+ .spi_enable = 0x1,
+ .xch = (1 << 2),
+ .mode_shift = 1,
+ .master_enable = 1 << 1,
+ .tx_inten_dif = 0,
+ .rx_inten_dif = 0,
+ .int_status_dif = 0,
+ .low_pol_shift = 4,
+ .pha_shift = 5,
+ .ss_ctrl_shift = 6,
+ .ss_pol_shift = 7,
+ .max_data_rate = 0x7,
+ .data_mask = 0x7,
+ .data_shift = 16,
+ .lbc = (1 << 14),
+ .rx_cnt_off = 4,
+ .rx_cnt_mask = (0xF << 4),
+ .reset_start = 1,
+};
+
+static struct mxc_spi_unique_def spi_ver_0_4 = {
+ .intr_bit_shift = 9,
+ .cs_shift = 24,
+ .bc_shift = 8,
+ .bc_mask = 0x1F,
+ .drctrl_shift = 20,
+ .xfer_complete = (1 << 8),
+ .bc_overflow = (1 << 7),
+ .fifo_size = 8,
+ .ctrl_reg_addr = 0,
+ .stat_reg_addr = 0x14,
+ .period_reg_addr = 0x18,
+ .test_reg_addr = 0x1C,
+ .reset_reg_addr = 0x0,
+ .mode_mask = 0x1,
+ .spi_enable = 0x1,
+ .xch = (1 << 2),
+ .mode_shift = 1,
+ .master_enable = 1 << 1,
+ .tx_inten_dif = 0,
+ .rx_inten_dif = 0,
+ .int_status_dif = 0,
+ .low_pol_shift = 4,
+ .pha_shift = 5,
+ .ss_ctrl_shift = 6,
+ .ss_pol_shift = 7,
+ .max_data_rate = 0x7,
+ .data_mask = 0x7,
+ .data_shift = 16,
+ .lbc = (1 << 14),
+ .rx_cnt_off = 4,
+ .rx_cnt_mask = (0xF << 4),
+ .reset_start = 1,
+};
+
+static struct mxc_spi_unique_def spi_ver_0_0 = {
+ .intr_bit_shift = 18,
+ .cs_shift = 19,
+ .bc_shift = 0,
+ .bc_mask = 0x1F,
+ .drctrl_shift = 12,
+ .xfer_complete = (1 << 3),
+ .bc_overflow = (1 << 8),
+ .fifo_size = 8,
+ .ctrl_reg_addr = 0,
+ .stat_reg_addr = 0x0C,
+ .period_reg_addr = 0x14,
+ .test_reg_addr = 0x10,
+ .reset_reg_addr = 0x1C,
+ .mode_mask = 0x1,
+ .spi_enable = (1 << 10),
+ .xch = (1 << 9),
+ .mode_shift = 11,
+ .master_enable = 1 << 11,
+ .tx_inten_dif = 9,
+ .rx_inten_dif = 10,
+ .int_status_dif = 1,
+ .low_pol_shift = 5,
+ .pha_shift = 6,
+ .ss_ctrl_shift = 7,
+ .ss_pol_shift = 8,
+ .max_data_rate = 0x10,
+ .data_mask = 0x1F,
+ .data_shift = 14,
+ .lbc = (1 << 14),
+ .rx_cnt_off = 4,
+ .rx_cnt_mask = (0xF << 4),
+ .reset_start = 1,
+};
+
+extern void gpio_spi_active(int cspi_mod);
+extern void gpio_spi_inactive(int cspi_mod);
+
+#define MXC_SPI_BUF_RX(type) \
+void mxc_spi_buf_rx_##type(struct mxc_spi *master_drv_data, u32 val)\
+{\
+ type *rx = master_drv_data->transfer.rx_buf;\
+ *rx++ = (type)val;\
+ master_drv_data->transfer.rx_buf = rx;\
+}
+
+#define MXC_SPI_BUF_TX(type) \
+u32 mxc_spi_buf_tx_##type(struct mxc_spi *master_drv_data)\
+{\
+ u32 val;\
+ const type *tx = master_drv_data->transfer.tx_buf;\
+ val = *tx++;\
+ master_drv_data->transfer.tx_buf = tx;\
+ return val;\
+}
+
+MXC_SPI_BUF_RX(u8)
+ MXC_SPI_BUF_TX(u8)
+ MXC_SPI_BUF_RX(u16)
+ MXC_SPI_BUF_TX(u16)
+ MXC_SPI_BUF_RX(u32)
+ MXC_SPI_BUF_TX(u32)
+
+/*!
+ * This function enables CSPI interrupt(s)
+ *
+ * @param master_data the pointer to mxc_spi structure
+ * @param irqs the irq(s) to set (can be a combination)
+ *
+ * @return This function returns 0 if successful, -1 otherwise.
+ */
+static int spi_enable_interrupt(struct mxc_spi *master_data, unsigned int irqs)
+{
+ if (irqs & ~((1 << master_data->spi_ver_def->intr_bit_shift) - 1)) {
+ return -1;
+ }
+
+ __raw_writel((irqs | __raw_readl(MXC_CSPIINT + master_data->ctrl_addr)),
+ MXC_CSPIINT + master_data->ctrl_addr);
+ return 0;
+}
+
+/*!
+ * This function disables CSPI interrupt(s)
+ *
+ * @param master_data the pointer to mxc_spi structure
+ * @param irqs the irq(s) to reset (can be a combination)
+ *
+ * @return This function returns 0 if successful, -1 otherwise.
+ */
+static int spi_disable_interrupt(struct mxc_spi *master_data, unsigned int irqs)
+{
+ if (irqs & ~((1 << master_data->spi_ver_def->intr_bit_shift) - 1)) {
+ return -1;
+ }
+
+ __raw_writel((~irqs &
+ __raw_readl(MXC_CSPIINT + master_data->ctrl_addr)),
+ MXC_CSPIINT + master_data->ctrl_addr);
+ return 0;
+}
+
+/*!
+ * This function sets the baud rate for the SPI module.
+ *
+ * @param master_data the pointer to mxc_spi structure
+ * @param baud the baud rate
+ *
+ * @return This function returns the baud rate divisor.
+ */
+static unsigned int spi_find_baudrate(struct mxc_spi *master_data,
+ unsigned int baud)
+{
+ unsigned int divisor;
+ unsigned int shift = 0;
+
+ /* Calculate required divisor (rounded) */
+ divisor = (master_data->spi_ipg_clk + baud / 2) / baud;
+ while (divisor >>= 1)
+ shift++;
+
+ if (master_data->spi_ver_def == &spi_ver_0_0) {
+ shift = (shift - 1) * 2;
+ } else if (master_data->spi_ver_def == &spi_ver_2_3) {
+ shift = shift;
+ } else {
+ shift -= 2;
+ }
+
+ if (shift > master_data->spi_ver_def->max_data_rate)
+ shift = master_data->spi_ver_def->max_data_rate;
+
+ return shift << master_data->spi_ver_def->data_shift;
+}
+
+/*!
+ * This function loads the transmit fifo.
+ *
+ * @param base the CSPI base address
+ * @param count number of words to put in the TxFIFO
+ * @param master_drv_data spi master structure
+ */
+static void spi_put_tx_data(void *base, unsigned int count,
+ struct mxc_spi *master_drv_data)
+{
+ unsigned int ctrl_reg;
+ unsigned int data;
+ int i = 0;
+
+ /* Perform Tx transaction */
+ for (i = 0; i < count; i++) {
+ data = master_drv_data->transfer.tx_get(master_drv_data);
+ __raw_writel(data, base + MXC_CSPITXDATA);
+ }
+
+ ctrl_reg = __raw_readl(base + MXC_CSPICTRL);
+
+ ctrl_reg |= master_drv_data->spi_ver_def->xch;
+
+ __raw_writel(ctrl_reg, base + MXC_CSPICTRL);
+
+ return;
+}
+
+/*!
+ * This function configures the hardware CSPI for the current SPI device.
+ * It sets the word size, transfer mode, data rate for this device.
+ *
+ * @param spi the current SPI device
+ * @param is_active indicates whether to active/deactivate the current device
+ */
+void mxc_spi_chipselect(struct spi_device *spi, int is_active)
+{
+ struct mxc_spi *master_drv_data;
+ struct mxc_spi_xfer *ptransfer;
+ struct mxc_spi_unique_def *spi_ver_def;
+ unsigned int ctrl_reg = 0;
+ unsigned int config_reg = 0;
+ unsigned int xfer_len;
+ unsigned int cs_value;
+
+ if (is_active == BITBANG_CS_INACTIVE) {
+ /*Need to deselect the slave */
+ return;
+ }
+
+ /* Get the master controller driver data from spi device's master */
+
+ master_drv_data = spi_master_get_devdata(spi->master);
+ clk_enable(master_drv_data->clk);
+ spi_ver_def = master_drv_data->spi_ver_def;
+
+ xfer_len = spi->bits_per_word;
+
+ if (spi_ver_def == &spi_ver_2_3) {
+ /* Control Register Settings for transfer to this slave */
+ ctrl_reg = master_drv_data->spi_ver_def->spi_enable;
+ ctrl_reg |=
+ ((spi->chip_select & MXC_CSPICTRL_CSMASK) << spi_ver_def->
+ cs_shift);
+ ctrl_reg |=
+ (((1 << (spi->chip_select & MXC_CSPICTRL_CSMASK)) &
+ spi_ver_def->mode_mask) << spi_ver_def->mode_shift);
+ ctrl_reg |=
+ spi_find_baudrate(master_drv_data, spi->max_speed_hz);
+ ctrl_reg |=
+ (((xfer_len -
+ 1) & spi_ver_def->bc_mask) << spi_ver_def->bc_shift);
+
+ if (spi->mode & SPI_CPHA)
+ config_reg |=
+ (((1 << (spi->chip_select & MXC_CSPICTRL_CSMASK)) &
+ spi_ver_def->mode_mask) <<
+ spi_ver_def->pha_shift);
+
+ if ((spi->mode & SPI_CPOL)) {
+ config_reg |=
+ (((1 << (spi->chip_select & MXC_CSPICTRL_CSMASK)) &
+ spi_ver_def->mode_mask) <<
+ spi_ver_def->low_pol_shift);
+ config_reg |=
+ (((1 << (spi->chip_select & MXC_CSPICTRL_CSMASK)) &
+ spi_ver_def->mode_mask) <<
+ spi_ver_def->sclk_ctl_shift);
+ }
+ cs_value = (__raw_readl(MXC_CSPICONFIG +
+ master_drv_data->ctrl_addr) >>
+ spi_ver_def->ss_pol_shift) & spi_ver_def->mode_mask;
+ if (spi->mode & SPI_CS_HIGH) {
+ config_reg |=
+ ((((1 << (spi->chip_select & MXC_CSPICTRL_CSMASK)) &
+ spi_ver_def->mode_mask) | cs_value) <<
+ spi_ver_def->ss_pol_shift);
+ } else
+ config_reg |=
+ ((~((1 << (spi->chip_select &
+ MXC_CSPICTRL_CSMASK)) &
+ spi_ver_def->mode_mask) & cs_value) <<
+ spi_ver_def->ss_pol_shift);
+ config_reg |=
+ (((1 << (spi->chip_select & MXC_CSPICTRL_CSMASK)) &
+ spi_ver_def->mode_mask) << spi_ver_def->ss_ctrl_shift);
+ __raw_writel(0, master_drv_data->base + MXC_CSPICTRL);
+ __raw_writel(ctrl_reg, master_drv_data->base + MXC_CSPICTRL);
+ __raw_writel(config_reg,
+ MXC_CSPICONFIG + master_drv_data->ctrl_addr);
+ } else {
+ /* Control Register Settings for transfer to this slave */
+ ctrl_reg = master_drv_data->spi_ver_def->spi_enable;
+ ctrl_reg |=
+ (((spi->chip_select & MXC_CSPICTRL_CSMASK) << spi_ver_def->
+ cs_shift) | spi_ver_def->mode_mask <<
+ spi_ver_def->mode_shift);
+ ctrl_reg |=
+ spi_find_baudrate(master_drv_data, spi->max_speed_hz);
+ ctrl_reg |=
+ (((xfer_len -
+ 1) & spi_ver_def->bc_mask) << spi_ver_def->bc_shift);
+ if (spi->mode & SPI_CPHA)
+ ctrl_reg |=
+ spi_ver_def->mode_mask << spi_ver_def->pha_shift;
+ if (spi->mode & SPI_CPOL)
+ ctrl_reg |=
+ spi_ver_def->mode_mask << spi_ver_def->
+ low_pol_shift;
+ if (spi->mode & SPI_CS_HIGH)
+ ctrl_reg |=
+ spi_ver_def->mode_mask << spi_ver_def->ss_pol_shift;
+ if (spi_ver_def == &spi_ver_0_7)
+ ctrl_reg |=
+ spi_ver_def->mode_mask << spi_ver_def->
+ ss_ctrl_shift;
+
+ __raw_writel(ctrl_reg, master_drv_data->base + MXC_CSPICTRL);
+ }
+
+ /* Initialize the functions for transfer */
+ ptransfer = &master_drv_data->transfer;
+ if (xfer_len <= 8) {
+ ptransfer->rx_get = mxc_spi_buf_rx_u8;
+ ptransfer->tx_get = mxc_spi_buf_tx_u8;
+ } else if (xfer_len <= 16) {
+ ptransfer->rx_get = mxc_spi_buf_rx_u16;
+ ptransfer->tx_get = mxc_spi_buf_tx_u16;
+ } else {
+ ptransfer->rx_get = mxc_spi_buf_rx_u32;
+ ptransfer->tx_get = mxc_spi_buf_tx_u32;
+ }
+#ifdef CONFIG_SPI_MXC_TEST_LOOPBACK
+ {
+ struct spi_chip_info *lb_chip =
+ (struct spi_chip_info *)spi->controller_data;
+ if (!lb_chip)
+ __raw_writel(0, master_drv_data->test_addr);
+ else if (lb_chip->lb_enable)
+ __raw_writel(spi_ver_def->lbc,
+ master_drv_data->test_addr);
+ }
+#endif
+ clk_disable(master_drv_data->clk);
+ return;
+}
+
+/*!
+ * This function is called when an interrupt occurs on the SPI modules.
+ * It is the interrupt handler for the SPI modules.
+ *
+ * @param irq the irq number
+ * @param dev_id the pointer on the device
+ *
+ * @return The function returns IRQ_HANDLED when handled.
+ */
+static irqreturn_t mxc_spi_isr(int irq, void *dev_id)
+{
+ struct mxc_spi *master_drv_data = dev_id;
+ irqreturn_t ret = IRQ_NONE;
+ unsigned int status;
+ int fifo_size;
+ unsigned int pass_counter;
+
+ fifo_size = master_drv_data->spi_ver_def->fifo_size;
+ pass_counter = fifo_size;
+
+ /* Read the interrupt status register to determine the source */
+ status = __raw_readl(master_drv_data->stat_addr);
+ do {
+ u32 rx_tmp =
+ __raw_readl(master_drv_data->base + MXC_CSPIRXDATA);
+
+ if (master_drv_data->transfer.rx_buf)
+ master_drv_data->transfer.rx_get(master_drv_data,
+ rx_tmp);
+ (master_drv_data->transfer.count)--;
+ (master_drv_data->transfer.rx_count)--;
+ ret = IRQ_HANDLED;
+ if (pass_counter-- == 0) {
+ break;
+ }
+ status = __raw_readl(master_drv_data->stat_addr);
+ } while (status &
+ (1 <<
+ (MXC_CSPISTAT_RR +
+ master_drv_data->spi_ver_def->int_status_dif)));
+
+ if (master_drv_data->transfer.rx_count)
+ return ret;
+
+ if (master_drv_data->transfer.count) {
+ if (master_drv_data->transfer.tx_buf) {
+ u32 count = (master_drv_data->transfer.count >
+ fifo_size) ? fifo_size :
+ master_drv_data->transfer.count;
+ master_drv_data->transfer.rx_count = count;
+ spi_put_tx_data(master_drv_data->base, count,
+ master_drv_data);
+ }
+ } else {
+ complete(&master_drv_data->xfer_done);
+ }
+
+ return ret;
+}
+
+/*!
+ * This function initialize the current SPI device.
+ *
+ * @param spi the current SPI device.
+ *
+ */
+int mxc_spi_setup(struct spi_device *spi)
+{
+ if (spi->max_speed_hz < 0) {
+ return -EINVAL;
+ }
+
+ if (!spi->bits_per_word)
+ spi->bits_per_word = 8;
+
+ pr_debug("%s: mode %d, %u bpw, %d hz\n", __FUNCTION__,
+ spi->mode, spi->bits_per_word, spi->max_speed_hz);
+
+ return 0;
+}
+
+static int mxc_spi_setup_transfer(struct spi_device *spi, struct spi_transfer *t)
+{
+ return 0;
+}
+
+/*!
+ * This function is called when the data has to transfer from/to the
+ * current SPI device in poll mode
+ *
+ * @param spi the current spi device
+ * @param t the transfer request - read/write buffer pairs
+ *
+ * @return Returns 0 on success.
+ */
+int mxc_spi_poll_transfer(struct spi_device *spi, struct spi_transfer *t)
+{
+ struct mxc_spi *master_drv_data = NULL;
+ int count, i;
+ volatile unsigned int status;
+ u32 rx_tmp;
+ u32 fifo_size;
+ int chipselect_status;
+
+ mxc_spi_chipselect(spi, BITBANG_CS_ACTIVE);
+
+ /* Get the master controller driver data from spi device's master */
+ master_drv_data = spi_master_get_devdata(spi->master);
+
+ chipselect_status = __raw_readl(MXC_CSPICONFIG +
+ master_drv_data->ctrl_addr);
+ chipselect_status >>= master_drv_data->spi_ver_def->ss_pol_shift &
+ master_drv_data->spi_ver_def->mode_mask;
+ if (master_drv_data->chipselect_active)
+ master_drv_data->chipselect_active(spi->master->bus_num,
+ chipselect_status,
+ (spi->chip_select &
+ MXC_CSPICTRL_CSMASK) + 1);
+
+ clk_enable(master_drv_data->clk);
+
+ /* Modify the Tx, Rx, Count */
+ master_drv_data->transfer.tx_buf = t->tx_buf;
+ master_drv_data->transfer.rx_buf = t->rx_buf;
+ master_drv_data->transfer.count = t->len;
+ fifo_size = master_drv_data->spi_ver_def->fifo_size;
+
+ count = (t->len > fifo_size) ? fifo_size : t->len;
+ spi_put_tx_data(master_drv_data->base, count, master_drv_data);
+
+ while ((((status = __raw_readl(master_drv_data->test_addr)) &
+ master_drv_data->spi_ver_def->rx_cnt_mask) >> master_drv_data->
+ spi_ver_def->rx_cnt_off) != count)
+ ;
+
+ for (i = 0; i < count; i++) {
+ rx_tmp = __raw_readl(master_drv_data->base + MXC_CSPIRXDATA);
+ master_drv_data->transfer.rx_get(master_drv_data, rx_tmp);
+ }
+
+ clk_disable(master_drv_data->clk);
+ if (master_drv_data->chipselect_inactive)
+ master_drv_data->chipselect_inactive(spi->master->bus_num,
+ chipselect_status,
+ (spi->chip_select &
+ MXC_CSPICTRL_CSMASK) + 1);
+ return 0;
+}
+
+/*!
+ * This function is called when the data has to transfer from/to the
+ * current SPI device. It enables the Rx interrupt, initiates the transfer.
+ * When Rx interrupt occurs, the completion flag is set. It then disables
+ * the Rx interrupt.
+ *
+ * @param spi the current spi device
+ * @param t the transfer request - read/write buffer pairs
+ *
+ * @return Returns 0 on success -1 on failure.
+ */
+int mxc_spi_transfer(struct spi_device *spi, struct spi_transfer *t)
+{
+ struct mxc_spi *master_drv_data = NULL;
+ int count;
+ int chipselect_status;
+ u32 fifo_size;
+
+ /* Get the master controller driver data from spi device's master */
+
+ master_drv_data = spi_master_get_devdata(spi->master);
+
+ chipselect_status = __raw_readl(MXC_CSPICONFIG +
+ master_drv_data->ctrl_addr);
+ chipselect_status >>= master_drv_data->spi_ver_def->ss_pol_shift &
+ master_drv_data->spi_ver_def->mode_mask;
+ if (master_drv_data->chipselect_active)
+ master_drv_data->chipselect_active(spi->master->bus_num,
+ chipselect_status,
+ (spi->chip_select &
+ MXC_CSPICTRL_CSMASK) + 1);
+
+ clk_enable(master_drv_data->clk);
+ /* Modify the Tx, Rx, Count */
+ master_drv_data->transfer.tx_buf = t->tx_buf;
+ master_drv_data->transfer.rx_buf = t->rx_buf;
+ master_drv_data->transfer.count = t->len;
+ fifo_size = master_drv_data->spi_ver_def->fifo_size;
+ INIT_COMPLETION(master_drv_data->xfer_done);
+
+ /* Enable the Rx Interrupts */
+
+ spi_enable_interrupt(master_drv_data,
+ 1 << (MXC_CSPIINT_RREN_SHIFT +
+ master_drv_data->spi_ver_def->rx_inten_dif));
+ count = (t->len > fifo_size) ? fifo_size : t->len;
+
+ /* Perform Tx transaction */
+ master_drv_data->transfer.rx_count = count;
+ spi_put_tx_data(master_drv_data->base, count, master_drv_data);
+
+ /* Wait for transfer completion */
+ wait_for_completion(&master_drv_data->xfer_done);
+
+ /* Disable the Rx Interrupts */
+
+ spi_disable_interrupt(master_drv_data,
+ 1 << (MXC_CSPIINT_RREN_SHIFT +
+ master_drv_data->spi_ver_def->
+ rx_inten_dif));
+
+ clk_disable(master_drv_data->clk);
+ if (master_drv_data->chipselect_inactive)
+ master_drv_data->chipselect_inactive(spi->master->bus_num,
+ chipselect_status,
+ (spi->chip_select &
+ MXC_CSPICTRL_CSMASK) + 1);
+ return t->len - master_drv_data->transfer.count;
+}
+
+/*!
+ * This function releases the current SPI device's resources.
+ *
+ * @param spi the current SPI device.
+ *
+ */
+void mxc_spi_cleanup(struct spi_device *spi)
+{
+}
+
+/*!
+ * This function is called during the driver binding process. Based on the CSPI
+ * hardware module that is being probed this function adds the appropriate SPI module
+ * structure in the SPI core driver.
+ *
+ * @param pdev the device structure used to store device specific
+ * information that is used by the suspend, resume and remove
+ * functions.
+ *
+ * @return The function returns 0 on successful registration and initialization
+ * of CSPI module. Otherwise returns specific error code.
+ */
+static int mxc_spi_probe(struct platform_device *pdev)
+{
+ struct mxc_spi_master *mxc_platform_info;
+ struct spi_master *master;
+ struct mxc_spi *master_drv_data = NULL;
+ unsigned int spi_ver;
+ int ret = -ENODEV;
+
+ /* Get the platform specific data for this master device */
+
+ mxc_platform_info = (struct mxc_spi_master *)pdev->dev.platform_data;
+ if (!mxc_platform_info) {
+ dev_err(&pdev->dev, "can't get the platform data for CSPI\n");
+ return -EINVAL;
+ }
+
+ /* Allocate SPI master controller */
+
+ master = spi_alloc_master(&pdev->dev, sizeof(struct mxc_spi));
+ if (!master) {
+ dev_err(&pdev->dev, "can't alloc for spi_master\n");
+ return -ENOMEM;
+ }
+
+ /* Set this device's driver data to master */
+
+ platform_set_drvdata(pdev, master);
+
+ /* Set this master's data from platform_info */
+
+ master->bus_num = pdev->id + 1;
+ master->num_chipselect = mxc_platform_info->maxchipselect;
+ master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;
+#ifdef CONFIG_SPI_MXC_TEST_LOOPBACK
+ master->num_chipselect += 1;
+#endif
+ /* Set the master controller driver data for this master */
+
+ master_drv_data = spi_master_get_devdata(master);
+ master_drv_data->mxc_bitbang.master = spi_master_get(master);
+ if (mxc_platform_info->chipselect_active)
+ master_drv_data->chipselect_active =
+ mxc_platform_info->chipselect_active;
+ if (mxc_platform_info->chipselect_inactive)
+ master_drv_data->chipselect_inactive =
+ mxc_platform_info->chipselect_inactive;
+
+ /* Identify SPI version */
+
+ spi_ver = mxc_platform_info->spi_version;
+ if (spi_ver == 7) {
+ master_drv_data->spi_ver_def = &spi_ver_0_7;
+ } else if (spi_ver == 5) {
+ master_drv_data->spi_ver_def = &spi_ver_0_5;
+ } else if (spi_ver == 4) {
+ master_drv_data->spi_ver_def = &spi_ver_0_4;
+ } else if (spi_ver == 0) {
+ master_drv_data->spi_ver_def = &spi_ver_0_0;
+ } else if (spi_ver == 23) {
+ master_drv_data->spi_ver_def = &spi_ver_2_3;
+ }
+
+ dev_dbg(&pdev->dev, "SPI_REV 0.%d\n", spi_ver);
+
+ /* Set the master bitbang data */
+
+ master_drv_data->mxc_bitbang.chipselect = mxc_spi_chipselect;
+ master_drv_data->mxc_bitbang.txrx_bufs = mxc_spi_transfer;
+ master_drv_data->mxc_bitbang.master->setup = mxc_spi_setup;
+ master_drv_data->mxc_bitbang.master->cleanup = mxc_spi_cleanup;
+ master_drv_data->mxc_bitbang.setup_transfer = mxc_spi_setup_transfer;
+
+ /* Initialize the completion object */
+
+ init_completion(&master_drv_data->xfer_done);
+
+ /* Set the master controller register addresses and irqs */
+
+ master_drv_data->res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!master_drv_data->res) {
+ dev_err(&pdev->dev, "can't get platform resource for CSPI%d\n",
+ master->bus_num);
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ if (!request_mem_region(master_drv_data->res->start,
+ master_drv_data->res->end -
+ master_drv_data->res->start + 1, pdev->name)) {
+ dev_err(&pdev->dev, "request_mem_region failed for CSPI%d\n",
+ master->bus_num);
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ master_drv_data->base = ioremap(master_drv_data->res->start,
+ master_drv_data->res->end - master_drv_data->res->start + 1);
+ if (!master_drv_data->base) {
+ dev_err(&pdev->dev, "invalid base address for CSPI%d\n",
+ master->bus_num);
+ ret = -EINVAL;
+ goto err1;
+ }
+
+ master_drv_data->irq = platform_get_irq(pdev, 0);
+ if (master_drv_data->irq < 0) {
+ dev_err(&pdev->dev, "can't get IRQ for CSPI%d\n",
+ master->bus_num);
+ ret = -EINVAL;
+ goto err1;
+ }
+
+ /* Register for SPI Interrupt */
+
+ ret = request_irq(master_drv_data->irq, mxc_spi_isr,
+ 0, "CSPI_IRQ", master_drv_data);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "request_irq failed for CSPI%d\n",
+ master->bus_num);
+ goto err1;
+ }
+
+ /* Setup any GPIO active */
+
+ gpio_spi_active(master->bus_num - 1);
+
+ /* Enable the CSPI Clock, CSPI Module, set as a master */
+
+ master_drv_data->ctrl_addr =
+ master_drv_data->base + master_drv_data->spi_ver_def->ctrl_reg_addr;
+ master_drv_data->stat_addr =
+ master_drv_data->base + master_drv_data->spi_ver_def->stat_reg_addr;
+ master_drv_data->period_addr =
+ master_drv_data->base +
+ master_drv_data->spi_ver_def->period_reg_addr;
+ master_drv_data->test_addr =
+ master_drv_data->base + master_drv_data->spi_ver_def->test_reg_addr;
+ master_drv_data->reset_addr =
+ master_drv_data->base +
+ master_drv_data->spi_ver_def->reset_reg_addr;
+
+ master_drv_data->clk = clk_get(&pdev->dev, "cspi_clk");
+ clk_enable(master_drv_data->clk);
+ master_drv_data->spi_ipg_clk = clk_get_rate(master_drv_data->clk);
+
+ __raw_writel(master_drv_data->spi_ver_def->reset_start,
+ master_drv_data->reset_addr);
+ udelay(1);
+ __raw_writel((master_drv_data->spi_ver_def->spi_enable +
+ master_drv_data->spi_ver_def->master_enable),
+ master_drv_data->base + MXC_CSPICTRL);
+ __raw_writel(MXC_CSPIPERIOD_32KHZ, master_drv_data->period_addr);
+ __raw_writel(0, MXC_CSPIINT + master_drv_data->ctrl_addr);
+
+ /* Start the SPI Master Controller driver */
+
+ ret = spi_bitbang_start(&master_drv_data->mxc_bitbang);
+
+ if (ret != 0)
+ goto err2;
+
+ printk(KERN_INFO "CSPI: %s-%d probed\n", pdev->name, pdev->id);
+
+#ifdef CONFIG_SPI_MXC_TEST_LOOPBACK
+ {
+ int i;
+ struct spi_board_info *bi = &loopback_info[0];
+ for (i = 0; i < ARRAY_SIZE(loopback_info); i++, bi++) {
+ if (bi->bus_num != master->bus_num)
+ continue;
+
+ dev_info(&pdev->dev,
+ "registering loopback device '%s'\n",
+ bi->modalias);
+
+ spi_new_device(master, bi);
+ }
+ }
+#endif
+ clk_disable(master_drv_data->clk);
+ return ret;
+
+ err2:
+ gpio_spi_inactive(master->bus_num - 1);
+ clk_disable(master_drv_data->clk);
+ clk_put(master_drv_data->clk);
+ free_irq(master_drv_data->irq, master_drv_data);
+ err1:
+ iounmap(master_drv_data->base);
+ release_mem_region(pdev->resource[0].start,
+ pdev->resource[0].end - pdev->resource[0].start + 1);
+ err:
+ spi_master_put(master);
+ kfree(master);
+ platform_set_drvdata(pdev, NULL);
+ return ret;
+}
+
+/*!
+ * Dissociates the driver from the SPI master controller. Disables the CSPI module.
+ * It handles the release of SPI resources like IRQ, memory,..etc.
+ *
+ * @param pdev the device structure used to give information on which SPI
+ * to remove
+ *
+ * @return The function always returns 0.
+ */
+static int mxc_spi_remove(struct platform_device *pdev)
+{
+ struct spi_master *master = platform_get_drvdata(pdev);
+
+ if (master) {
+ struct mxc_spi *master_drv_data =
+ spi_master_get_devdata(master);
+
+ gpio_spi_inactive(master->bus_num - 1);
+
+ /* Disable the CSPI module */
+ clk_enable(master_drv_data->clk);
+ __raw_writel(MXC_CSPICTRL_DISABLE,
+ master_drv_data->base + MXC_CSPICTRL);
+ clk_disable(master_drv_data->clk);
+ /* Unregister for SPI Interrupt */
+
+ free_irq(master_drv_data->irq, master_drv_data);
+
+ iounmap(master_drv_data->base);
+ release_mem_region(master_drv_data->res->start,
+ master_drv_data->res->end -
+ master_drv_data->res->start + 1);
+
+ /* Stop the SPI Master Controller driver */
+
+ spi_bitbang_stop(&master_drv_data->mxc_bitbang);
+
+ spi_master_put(master);
+ }
+
+ printk(KERN_INFO "CSPI: %s-%d removed\n", pdev->name, pdev->id);
+ platform_set_drvdata(pdev, NULL);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int spi_bitbang_suspend(struct spi_bitbang *bitbang)
+{
+ unsigned long flags;
+ unsigned limit = 500;
+
+ spin_lock_irqsave(&bitbang->lock, flags);
+ while (!list_empty(&bitbang->queue) && limit--) {
+ spin_unlock_irqrestore(&bitbang->lock, flags);
+
+ dev_dbg(&bitbang->master->dev, "wait for queue\n");
+ msleep(10);
+
+ spin_lock_irqsave(&bitbang->lock, flags);
+ }
+ if (!list_empty(&bitbang->queue)) {
+ dev_err(&bitbang->master->dev, "queue didn't empty\n");
+ spin_unlock_irqrestore(&bitbang->lock, flags);
+ return -EBUSY;
+ }
+ spin_unlock_irqrestore(&bitbang->lock, flags);
+
+ return 0;
+}
+
+static void spi_bitbang_resume(struct spi_bitbang *bitbang)
+{
+ spin_lock_init(&bitbang->lock);
+ INIT_LIST_HEAD(&bitbang->queue);
+
+ bitbang->busy = 0;
+}
+
+/*!
+ * This function puts the SPI master controller in low-power mode/state.
+ *
+ * @param pdev the device structure used to give information on which SDHC
+ * to suspend
+ * @param state the power state the device is entering
+ *
+ * @return The function always returns 0.
+ */
+static int mxc_spi_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct spi_master *master = platform_get_drvdata(pdev);
+ struct mxc_spi *master_drv_data = spi_master_get_devdata(master);
+ int ret = 0;
+
+ spi_bitbang_suspend(&master_drv_data->mxc_bitbang);
+ clk_enable(master_drv_data->clk);
+ __raw_writel(MXC_CSPICTRL_DISABLE,
+ master_drv_data->base + MXC_CSPICTRL);
+ clk_disable(master_drv_data->clk);
+ gpio_spi_inactive(master->bus_num - 1);
+
+ return ret;
+}
+
+/*!
+ * This function brings the SPI master controller back from low-power state.
+ *
+ * @param pdev the device structure used to give information on which SDHC
+ * to resume
+ *
+ * @return The function always returns 0.
+ */
+static int mxc_spi_resume(struct platform_device *pdev)
+{
+ struct spi_master *master = platform_get_drvdata(pdev);
+ struct mxc_spi *master_drv_data = spi_master_get_devdata(master);
+
+ gpio_spi_active(master->bus_num - 1);
+
+ spi_bitbang_resume(&master_drv_data->mxc_bitbang);
+ clk_enable(master_drv_data->clk);
+ __raw_writel((master_drv_data->spi_ver_def->spi_enable +
+ master_drv_data->spi_ver_def->master_enable),
+ master_drv_data->base + MXC_CSPICTRL);
+ clk_disable(master_drv_data->clk);
+
+ return 0;
+}
+#else
+#define mxc_spi_suspend NULL
+#define mxc_spi_resume NULL
+#endif /* CONFIG_PM */
+
+/*!
+ * This structure contains pointers to the power management callback functions.
+ */
+static struct platform_driver mxc_spi_driver = {
+ .driver = {
+ .name = "mxc_spi",
+ .owner = THIS_MODULE,
+ },
+ .probe = mxc_spi_probe,
+ .remove = mxc_spi_remove,
+ .suspend = mxc_spi_suspend,
+ .resume = mxc_spi_resume,
+};
+
+/*!
+ * This function implements the init function of the SPI device.
+ * It is called when the module is loaded. It enables the required
+ * clocks to CSPI module(if any) and activates necessary GPIO pins.
+ *
+ * @return This function returns 0.
+ */
+static int __init mxc_spi_init(void)
+{
+ pr_debug("Registering the SPI Controller Driver\n");
+ return platform_driver_register(&mxc_spi_driver);
+}
+
+/*!
+ * This function implements the exit function of the SPI device.
+ * It is called when the module is unloaded. It deactivates the
+ * the GPIO pin associated with CSPI hardware modules.
+ *
+ */
+static void __exit mxc_spi_exit(void)
+{
+ pr_debug("Unregistering the SPI Controller Driver\n");
+ platform_driver_unregister(&mxc_spi_driver);
+}
+
+subsys_initcall(mxc_spi_init);
+module_exit(mxc_spi_exit);
+
+MODULE_DESCRIPTION("SPI Master Controller driver");
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_LICENSE("GPL");
diff --git a/drivers/spi/spi_mxs.c b/drivers/spi/spi_mxs.c
new file mode 100644
index 000000000000..c6e66fd9a711
--- /dev/null
+++ b/drivers/spi/spi_mxs.c
@@ -0,0 +1,750 @@
+/*
+ * Freescale MXS SPI master driver
+ *
+ * Author: dmitry pervushin <dimka@embeddedalley.com>
+ *
+ * Copyright 2008-2010 Freescale Semiconductor, Inc.
+ * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * 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 Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/dma-mapping.h>
+#include <linux/errno.h>
+#include <asm/dma.h>
+
+#include <mach/regs-ssp.h>
+#include <mach/dmaengine.h>
+#include <mach/device.h>
+#include <mach/system.h>
+#include <mach/hardware.h>
+
+#include "spi_mxs.h"
+
+#ifndef BF_SSP_CTRL0_XFER_COUNT
+#define BF_SSP_CTRL0_XFER_COUNT(len) HW_SSP_VERSION
+#endif
+#ifndef BM_SSP_CTRL0_XFER_COUNT
+#define BM_SSP_CTRL0_XFER_COUNT HW_SSP_VERSION
+#endif
+
+#ifndef BF_SSP_XFER_SIZE_XFER_COUNT
+#define BF_SSP_XFER_SIZE_XFER_COUNT(len) HW_SSP_VERSION
+#endif
+#ifndef BM_SSP_XFER_SIZE_XFER_COUNT
+#define BM_SSP_XFER_SIZE_XFER_COUNT HW_SSP_VERSION
+#endif
+
+#ifndef HW_SSP_XFER_SIZE
+#define HW_SSP_XFER_SIZE HW_SSP_VERSION
+#endif
+
+/* 0 means DMA modei(recommended, default), !0 - PIO mode */
+static int pio /* = 0 */ ;
+static int debug;
+
+/**
+ * mxs_spi_init_hw
+ *
+ * Initialize the SSP port
+ */
+static int mxs_spi_init_hw(struct mxs_spi *ss)
+{
+ struct mxs_spi_platform_data *pdata;
+ int err;
+
+ pdata = ss->master_dev->platform_data;
+
+ if (!pdata->clk) {
+ dev_err(ss->master_dev, "unknown clock\n");
+ err = -EINVAL;
+ goto out;
+ }
+ ss->clk = clk_get(ss->master_dev, pdata->clk);
+ if (IS_ERR(ss->clk)) {
+ err = PTR_ERR(ss->clk);
+ goto out;
+ }
+ clk_enable(ss->clk);
+
+ mxs_reset_block((void *)ss->regs, 0);
+ mxs_dma_reset(ss->dma);
+
+ return 0;
+
+out:
+ return err;
+}
+
+static void mxs_spi_release_hw(struct mxs_spi *ss)
+{
+ if (ss->clk && !IS_ERR(ss->clk)) {
+ clk_disable(ss->clk);
+ clk_put(ss->clk);
+ }
+}
+
+static int mxs_spi_setup_transfer(struct spi_device *spi,
+ struct spi_transfer *t)
+{
+ u8 bits_per_word;
+ u32 hz;
+ struct mxs_spi *ss /* = spi_master_get_devdata(spi->master) */ ;
+ u16 rate;
+
+ ss = spi_master_get_devdata(spi->master);
+
+ bits_per_word = spi->bits_per_word;
+ if (t && t->bits_per_word)
+ bits_per_word = t->bits_per_word;
+
+ /*
+ Calculate speed:
+ - by default, use maximum speed from ssp clk
+ - if device overrides it, use it
+ - if transfer specifies other speed, use transfer's one
+ */
+ hz = 1000 * ss->speed_khz / ss->divider;
+ if (spi->max_speed_hz)
+ hz = min(hz, spi->max_speed_hz);
+ if (t && t->speed_hz)
+ hz = min(hz, t->speed_hz);
+
+ if (hz == 0) {
+ dev_err(&spi->dev, "Cannot continue with zero clock\n");
+ return -EINVAL;
+ }
+
+ if (bits_per_word != 8) {
+ dev_err(&spi->dev, "%s, unsupported bits_per_word=%d\n",
+ __func__, bits_per_word);
+ return -EINVAL;
+ }
+
+ dev_dbg(&spi->dev, "Requested clk rate = %uHz, max = %ukHz/%d = %uHz\n",
+ hz, ss->speed_khz, ss->divider,
+ ss->speed_khz * 1000 / ss->divider);
+
+ if (ss->speed_khz * 1000 / ss->divider < hz) {
+ dev_err(&spi->dev, "%s, unsupported clock rate %uHz\n",
+ __func__, hz);
+ return -EINVAL;
+ }
+
+ rate = 1000 * ss->speed_khz / ss->divider / hz;
+
+ __raw_writel(BF_SSP_TIMING_CLOCK_DIVIDE(ss->divider) |
+ BF_SSP_TIMING_CLOCK_RATE(rate - 1),
+ ss->regs + HW_SSP_TIMING);
+
+ __raw_writel(BF_SSP_CTRL1_SSP_MODE(BV_SSP_CTRL1_SSP_MODE__SPI) |
+ BF_SSP_CTRL1_WORD_LENGTH
+ (BV_SSP_CTRL1_WORD_LENGTH__EIGHT_BITS) |
+ ((spi->mode & SPI_CPOL) ? BM_SSP_CTRL1_POLARITY : 0) |
+ ((spi->mode & SPI_CPHA) ? BM_SSP_CTRL1_PHASE : 0) |
+ (pio ? 0 : BM_SSP_CTRL1_DMA_ENABLE),
+ ss->regs + HW_SSP_CTRL1);
+
+ __raw_writel(0x00, ss->regs + HW_SSP_CMD0_SET);
+
+ return 0;
+}
+
+static void mxs_spi_cleanup(struct spi_device *spi)
+{
+ return;
+}
+
+/* the spi->mode bits understood by this driver: */
+#define MODEBITS (SPI_CPOL | SPI_CPHA)
+static int mxs_spi_setup(struct spi_device *spi)
+{
+ struct mxs_spi *ss;
+ int err = 0;
+
+ ss = spi_master_get_devdata(spi->master);
+
+ if (!spi->bits_per_word)
+ spi->bits_per_word = 8;
+
+ if (spi->mode & ~MODEBITS) {
+ dev_err(&spi->dev, "%s: unsupported mode bits %x\n",
+ __func__, spi->mode & ~MODEBITS);
+ err = -EINVAL;
+ goto out;
+ }
+
+ dev_dbg(&spi->dev, "%s, mode %d, %u bits/w\n",
+ __func__, spi->mode & MODEBITS, spi->bits_per_word);
+
+ err = mxs_spi_setup_transfer(spi, NULL);
+ if (err)
+ goto out;
+ return 0;
+
+out:
+ dev_err(&spi->dev, "Failed to setup transfer, error = %d\n", err);
+ return err;
+}
+
+static inline u32 mxs_spi_cs(unsigned cs)
+{
+ return ((cs & 1) ? BM_SSP_CTRL0_WAIT_FOR_CMD : 0) |
+ ((cs & 2) ? BM_SSP_CTRL0_WAIT_FOR_IRQ : 0);
+}
+
+static int mxs_spi_txrx_dma(struct mxs_spi *ss, int cs,
+ unsigned char *buf, dma_addr_t dma_buf, int len,
+ int *first, int *last, int write)
+{
+ u32 c0 = 0, xfer_size = 0;
+ dma_addr_t spi_buf_dma = dma_buf;
+ int count, status = 0;
+ enum dma_data_direction dir = write ? DMA_TO_DEVICE : DMA_FROM_DEVICE;
+
+ c0 |= (*first ? BM_SSP_CTRL0_LOCK_CS : 0);
+ c0 |= (*last ? BM_SSP_CTRL0_IGNORE_CRC : 0);
+ c0 |= (write ? 0 : BM_SSP_CTRL0_READ);
+ c0 |= BM_SSP_CTRL0_DATA_XFER;
+
+ c0 |= mxs_spi_cs(cs);
+
+ if (ss->ver_major > 3) {
+ xfer_size = BF_SSP_XFER_SIZE_XFER_COUNT(len);
+ __raw_writel(xfer_size, ss->regs + HW_SSP_XFER_SIZE);
+ } else {
+ c0 |= BF_SSP_CTRL0_XFER_COUNT(len);
+ }
+
+ if (!dma_buf)
+ spi_buf_dma = dma_map_single(ss->master_dev, buf, len, dir);
+
+ ss->pdesc->cmd.cmd.bits.bytes = len;
+ ss->pdesc->cmd.cmd.bits.pio_words = 1;
+ ss->pdesc->cmd.cmd.bits.wait4end = 1;
+ ss->pdesc->cmd.cmd.bits.dec_sem = 1;
+ ss->pdesc->cmd.cmd.bits.irq = 1;
+ ss->pdesc->cmd.cmd.bits.command = write ? DMA_READ : DMA_WRITE;
+ ss->pdesc->cmd.address = spi_buf_dma;
+ ss->pdesc->cmd.pio_words[0] = c0;
+ mxs_dma_desc_append(ss->dma, ss->pdesc);
+
+ mxs_dma_reset(ss->dma);
+ mxs_dma_ack_irq(ss->dma);
+ mxs_dma_enable_irq(ss->dma, 1);
+ init_completion(&ss->done);
+ mxs_dma_enable(ss->dma);
+ wait_for_completion(&ss->done);
+ count = 10000;
+ while ((__raw_readl(ss->regs + HW_SSP_CTRL0) & BM_SSP_CTRL0_RUN)
+ && count--)
+ continue;
+ if (count <= 0) {
+ printk(KERN_ERR "%c: timeout on line %s:%d\n",
+ write ? 'W' : 'C', __func__, __LINE__);
+ status = -ETIMEDOUT;
+ }
+
+ if (!dma_buf)
+ dma_unmap_single(ss->master_dev, spi_buf_dma, len, dir);
+
+ return status;
+}
+
+static inline void mxs_spi_enable(struct mxs_spi *ss)
+{
+ __raw_writel(BM_SSP_CTRL0_LOCK_CS, ss->regs + HW_SSP_CTRL0_SET);
+ __raw_writel(BM_SSP_CTRL0_IGNORE_CRC, ss->regs + HW_SSP_CTRL0_CLR);
+}
+
+static inline void mxs_spi_disable(struct mxs_spi *ss)
+{
+ __raw_writel(BM_SSP_CTRL0_LOCK_CS, ss->regs + HW_SSP_CTRL0_CLR);
+ __raw_writel(BM_SSP_CTRL0_IGNORE_CRC, ss->regs + HW_SSP_CTRL0_SET);
+}
+
+static int mxs_spi_txrx_pio(struct mxs_spi *ss, int cs,
+ unsigned char *buf, int len,
+ int *first, int *last, int write)
+{
+ int count;
+
+ if (*first) {
+ mxs_spi_enable(ss);
+ *first = 0;
+ }
+
+ __raw_writel(mxs_spi_cs(cs), ss->regs + HW_SSP_CTRL0_SET);
+
+ while (len--) {
+ if (*last && len == 0) {
+ mxs_spi_disable(ss);
+ *last = 0;
+ }
+
+ /* byte-by-byte */
+ if (ss->ver_major > 3) {
+ __raw_writel(1, ss->regs + HW_SSP_XFER_SIZE);
+ } else {
+ __raw_writel(BM_SSP_CTRL0_XFER_COUNT,
+ ss->regs + HW_SSP_CTRL0_CLR);
+ __raw_writel(1, ss->regs + HW_SSP_CTRL0_SET);
+ }
+
+ if (write)
+ __raw_writel(BM_SSP_CTRL0_READ,
+ ss->regs + HW_SSP_CTRL0_CLR);
+ else
+ __raw_writel(BM_SSP_CTRL0_READ,
+ ss->regs + HW_SSP_CTRL0_SET);
+
+ /* Run! */
+ __raw_writel(BM_SSP_CTRL0_RUN, ss->regs + HW_SSP_CTRL0_SET);
+ count = 10000;
+ while (((__raw_readl(ss->regs + HW_SSP_CTRL0) &
+ BM_SSP_CTRL0_RUN) == 0) && count--)
+ continue;
+ if (count <= 0) {
+ printk(KERN_ERR "%c: timeout on line %s:%d\n",
+ write ? 'W' : 'C', __func__, __LINE__);
+ break;
+ }
+
+ if (write)
+ __raw_writel(*buf, ss->regs + HW_SSP_DATA);
+
+ /* Set TRANSFER */
+ __raw_writel(BM_SSP_CTRL0_DATA_XFER,
+ ss->regs + HW_SSP_CTRL0_SET);
+
+ if (!write) {
+ count = 10000;
+ while (count-- &&
+ (__raw_readl(ss->regs + HW_SSP_STATUS) &
+ BM_SSP_STATUS_FIFO_EMPTY))
+ continue;
+ if (count <= 0) {
+ printk(KERN_ERR "%c: timeout on line %s:%d\n",
+ write ? 'W' : 'C', __func__, __LINE__);
+ break;
+ }
+ *buf = (__raw_readl(ss->regs + HW_SSP_DATA) & 0xFF);
+ }
+
+ count = 10000;
+ while ((__raw_readl(ss->regs + HW_SSP_CTRL0) & BM_SSP_CTRL0_RUN)
+ && count--)
+ continue;
+ if (count <= 0) {
+ printk(KERN_ERR "%c: timeout on line %s:%d\n",
+ write ? 'W' : 'C', __func__, __LINE__);
+ break;
+ }
+
+ /* advance to the next byte */
+ buf++;
+ }
+ return len < 0 ? 0 : -ETIMEDOUT;
+}
+
+static int mxs_spi_handle_message(struct mxs_spi *ss, struct spi_message *m)
+{
+ int first, last;
+ struct spi_transfer *t, *tmp_t;
+ int status = 0;
+ int cs;
+
+ first = last = 0;
+
+ cs = m->spi->chip_select;
+
+ list_for_each_entry_safe(t, tmp_t, &m->transfers, transfer_list) {
+
+ mxs_spi_setup_transfer(m->spi, t);
+
+ if (&t->transfer_list == m->transfers.next)
+ first = !0;
+ if (&t->transfer_list == m->transfers.prev)
+ last = !0;
+ if (t->rx_buf && t->tx_buf) {
+ pr_debug("%s: cannot send and receive simultaneously\n",
+ __func__);
+ return -EINVAL;
+ }
+
+ /*
+ REVISIT:
+ here driver completely ignores setting of t->cs_change
+ */
+ if (t->tx_buf) {
+ status = pio ?
+ mxs_spi_txrx_pio(ss, cs, (void *)t->tx_buf,
+ t->len, &first, &last, 1) :
+ mxs_spi_txrx_dma(ss, cs, (void *)t->tx_buf,
+ t->tx_dma, t->len, &first, &last,
+ 1);
+ if (debug) {
+ if (t->len < 0x10)
+ print_hex_dump_bytes("Tx ",
+ DUMP_PREFIX_OFFSET,
+ t->tx_buf, t->len);
+ else
+ pr_debug("Tx: %d bytes\n", t->len);
+ }
+ }
+ if (t->rx_buf) {
+ status = pio ?
+ mxs_spi_txrx_pio(ss, cs, t->rx_buf,
+ t->len, &first, &last, 0) :
+ mxs_spi_txrx_dma(ss, cs, t->rx_buf,
+ t->rx_dma, t->len, &first, &last,
+ 0);
+ if (debug) {
+ if (t->len < 0x10)
+ print_hex_dump_bytes("Rx ",
+ DUMP_PREFIX_OFFSET,
+ t->rx_buf, t->len);
+ else
+ pr_debug("Rx: %d bytes\n", t->len);
+ }
+ }
+
+ m->actual_length += t->len;
+ if (status)
+ break;
+
+ first = last = 0;
+
+ }
+ return status;
+}
+
+/**
+ * mxs_spi_handle
+ *
+ * The workhorse of the driver - it handles messages from the list
+ *
+ **/
+static void mxs_spi_handle(struct work_struct *w)
+{
+ struct mxs_spi *ss = container_of(w, struct mxs_spi, work);
+ unsigned long flags;
+ struct spi_message *m;
+
+ BUG_ON(w == NULL);
+
+ spin_lock_irqsave(&ss->lock, flags);
+ while (!list_empty(&ss->queue)) {
+ m = list_entry(ss->queue.next, struct spi_message, queue);
+ list_del_init(&m->queue);
+ spin_unlock_irqrestore(&ss->lock, flags);
+
+ m->status = mxs_spi_handle_message(ss, m);
+ if (m->complete)
+ m->complete(m->context);
+
+ spin_lock_irqsave(&ss->lock, flags);
+ }
+ spin_unlock_irqrestore(&ss->lock, flags);
+
+ return;
+}
+
+/**
+ * mxs_spi_transfer
+ *
+ * Called indirectly from spi_async, queues all the messages to
+ * spi_handle_message
+ *
+ * @spi: spi device
+ * @m: message to be queued
+**/
+static int mxs_spi_transfer(struct spi_device *spi, struct spi_message *m)
+{
+ struct mxs_spi *ss = spi_master_get_devdata(spi->master);
+ unsigned long flags;
+
+ m->actual_length = 0;
+ m->status = -EINPROGRESS;
+ spin_lock_irqsave(&ss->lock, flags);
+ list_add_tail(&m->queue, &ss->queue);
+ queue_work(ss->workqueue, &ss->work);
+ spin_unlock_irqrestore(&ss->lock, flags);
+ return 0;
+}
+
+static irqreturn_t mxs_spi_irq_dma(int irq, void *dev_id)
+{
+ struct mxs_spi *ss = dev_id;
+
+ mxs_dma_ack_irq(ss->dma);
+ mxs_dma_cooked(ss->dma, NULL);
+ complete(&ss->done);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t mxs_spi_irq_err(int irq, void *dev_id)
+{
+ struct mxs_spi *ss = dev_id;
+ u32 c1, st;
+
+ c1 = __raw_readl(ss->regs + HW_SSP_CTRL1);
+ st = __raw_readl(ss->regs + HW_SSP_STATUS);
+ printk(KERN_ERR "IRQ - ERROR!, status = 0x%08X, c1 = 0x%08X\n", st, c1);
+ __raw_writel(c1 & 0xCCCC0000, ss->regs + HW_SSP_CTRL1_CLR);
+
+ return IRQ_HANDLED;
+}
+
+static int __init mxs_spi_probe(struct platform_device *dev)
+{
+ struct mxs_spi_platform_data *pdata;
+ int err = 0;
+ struct spi_master *master;
+ struct mxs_spi *ss;
+ struct resource *r;
+ u32 mem;
+
+ pdata = dev->dev.platform_data;
+
+ if (pdata && pdata->hw_pin_init) {
+ err = pdata->hw_pin_init();
+ if (err)
+ goto out0;
+ }
+
+ /* Get resources(memory, IRQ) associated with the device */
+ master = spi_alloc_master(&dev->dev, sizeof(struct mxs_spi));
+
+ if (master == NULL) {
+ err = -ENOMEM;
+ goto out0;
+ }
+
+ platform_set_drvdata(dev, master);
+
+ r = platform_get_resource(dev, IORESOURCE_MEM, 0);
+ if (r == NULL) {
+ err = -ENODEV;
+ goto out_put_master;
+ }
+
+ ss = spi_master_get_devdata(master);
+ ss->master_dev = &dev->dev;
+
+ INIT_WORK(&ss->work, mxs_spi_handle);
+ INIT_LIST_HEAD(&ss->queue);
+ spin_lock_init(&ss->lock);
+ ss->workqueue = create_singlethread_workqueue(dev_name(&dev->dev));
+ master->transfer = mxs_spi_transfer;
+ master->setup = mxs_spi_setup;
+ master->cleanup = mxs_spi_cleanup;
+
+ if (!request_mem_region(r->start,
+ resource_size(r), dev_name(&dev->dev))) {
+ err = -ENXIO;
+ goto out_put_master;
+ }
+ mem = r->start;
+
+ ss->regs = IO_ADDRESS(r->start);
+
+ ss->irq_dma = platform_get_irq(dev, 0);
+ if (ss->irq_dma < 0) {
+ err = -ENXIO;
+ goto out_put_master;
+ }
+ ss->irq_err = platform_get_irq(dev, 1);
+ if (ss->irq_err < 0) {
+ err = -ENXIO;
+ goto out_put_master;
+ }
+
+ r = platform_get_resource(dev, IORESOURCE_DMA, 0);
+ if (r == NULL) {
+ err = -ENODEV;
+ goto out_put_master;
+ }
+
+ ss->dma = r->start;
+ err = mxs_dma_request(ss->dma, &dev->dev, (char *)dev_name(&dev->dev));
+ if (err)
+ goto out_put_master;
+
+ ss->pdesc = mxs_dma_alloc_desc();
+ if (ss->pdesc == NULL || IS_ERR(ss->pdesc)) {
+ err = -ENOMEM;
+ goto out_free_dma;
+ }
+
+ master->bus_num = dev->id + 1;
+ master->num_chipselect = 1;
+
+ /* SPI controller initializations */
+ err = mxs_spi_init_hw(ss);
+ if (err) {
+ dev_dbg(&dev->dev, "cannot initialize hardware\n");
+ goto out_free_dma_desc;
+ }
+
+ clk_set_rate(ss->clk, 120 * 1000 * 1000);
+ ss->speed_khz = clk_get_rate(ss->clk) / 1000;
+ ss->divider = 2;
+ dev_info(&dev->dev, "Max possible speed %d = %ld/%d kHz\n",
+ ss->speed_khz, clk_get_rate(ss->clk), ss->divider);
+
+ ss->ver_major = __raw_readl(ss->regs + HW_SSP_VERSION) >> 24;
+
+ /* Register for SPI Interrupt */
+ err = request_irq(ss->irq_dma, mxs_spi_irq_dma, 0,
+ dev_name(&dev->dev), ss);
+ if (err) {
+ dev_dbg(&dev->dev, "request_irq failed, %d\n", err);
+ goto out_release_hw;
+ }
+ err = request_irq(ss->irq_err, mxs_spi_irq_err, IRQF_SHARED,
+ dev_name(&dev->dev), ss);
+ if (err) {
+ dev_dbg(&dev->dev, "request_irq(error) failed, %d\n", err);
+ goto out_free_irq;
+ }
+
+ err = spi_register_master(master);
+ if (err) {
+ dev_dbg(&dev->dev, "cannot register spi master, %d\n", err);
+ goto out_free_irq_2;
+ }
+ dev_info(&dev->dev, "at 0x%08X mapped to 0x%08X, irq=%d, bus %d, %s ver_major %d\n",
+ mem, (u32) ss->regs, ss->irq_dma,
+ master->bus_num, pio ? "PIO" : "DMA", ss->ver_major);
+ return 0;
+
+out_free_irq_2:
+ free_irq(ss->irq_err, ss);
+out_free_irq:
+ free_irq(ss->irq_dma, ss);
+out_free_dma_desc:
+ mxs_dma_free_desc(ss->pdesc);
+out_free_dma:
+ mxs_dma_release(ss->dma, &dev->dev);
+out_release_hw:
+ mxs_spi_release_hw(ss);
+out_put_master:
+ spi_master_put(master);
+out0:
+ return err;
+}
+
+static int __devexit mxs_spi_remove(struct platform_device *dev)
+{
+ struct mxs_spi *ss;
+ struct spi_master *master;
+ struct mxs_spi_platform_data *pdata = dev->dev.platform_data;
+
+ if (pdata && pdata->hw_pin_release)
+ pdata->hw_pin_release();
+
+ master = platform_get_drvdata(dev);
+ if (master == NULL)
+ goto out0;
+ ss = spi_master_get_devdata(master);
+ if (ss == NULL)
+ goto out1;
+ free_irq(ss->irq_err, ss);
+ free_irq(ss->irq_dma, ss);
+ if (ss->workqueue)
+ destroy_workqueue(ss->workqueue);
+ mxs_dma_free_desc(ss->pdesc);
+ mxs_dma_release(ss->dma, &dev->dev);
+ mxs_spi_release_hw(ss);
+ platform_set_drvdata(dev, 0);
+out1:
+ spi_master_put(master);
+out0:
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int mxs_spi_suspend(struct platform_device *pdev, pm_message_t pmsg)
+{
+ struct mxs_spi *ss;
+ struct spi_master *master;
+
+ master = platform_get_drvdata(pdev);
+ ss = spi_master_get_devdata(master);
+
+ ss->saved_timings = __raw_readl(ss->regs + HW_SSP_TIMING);
+ clk_disable(ss->clk);
+
+ return 0;
+}
+
+static int mxs_spi_resume(struct platform_device *pdev)
+{
+ struct mxs_spi *ss;
+ struct spi_master *master;
+
+ master = platform_get_drvdata(pdev);
+ ss = spi_master_get_devdata(master);
+
+ clk_enable(ss->clk);
+ __raw_writel(BM_SSP_CTRL0_SFTRST | BM_SSP_CTRL0_CLKGATE,
+ ss->regs + HW_SSP_CTRL0_CLR);
+ __raw_writel(ss->saved_timings, ss->regs + HW_SSP_TIMING);
+
+ return 0;
+}
+
+#else
+#define mxs_spi_suspend NULL
+#define mxs_spi_resume NULL
+#endif
+
+static struct platform_driver mxs_spi_driver = {
+ .probe = mxs_spi_probe,
+ .remove = __devexit_p(mxs_spi_remove),
+ .driver = {
+ .name = "mxs-spi",
+ .owner = THIS_MODULE,
+ },
+ .suspend = mxs_spi_suspend,
+ .resume = mxs_spi_resume,
+};
+
+static int __init mxs_spi_init(void)
+{
+ return platform_driver_register(&mxs_spi_driver);
+}
+
+static void __exit mxs_spi_exit(void)
+{
+ platform_driver_unregister(&mxs_spi_driver);
+}
+
+module_init(mxs_spi_init);
+module_exit(mxs_spi_exit);
+module_param(pio, int, S_IRUGO);
+module_param(debug, int, S_IRUGO);
+MODULE_AUTHOR("dmitry pervushin <dimka@embeddedalley.com>");
+MODULE_DESCRIPTION("MXS SPI/SSP");
+MODULE_LICENSE("GPL");
diff --git a/drivers/spi/spi_mxs.h b/drivers/spi/spi_mxs.h
new file mode 100644
index 000000000000..c9e271e92f8b
--- /dev/null
+++ b/drivers/spi/spi_mxs.h
@@ -0,0 +1,54 @@
+/*
+ * Freescale MXS SPI master driver
+ *
+ * Author: dmitry pervushin <dimka@embeddedalley.com>
+ *
+ * Copyright 2008-2010 Freescale Semiconductor, Inc.
+ * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * 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 Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef __SPI_STMP_H
+#define __SPI_STMP_H
+
+#include <mach/dma.h>
+
+struct mxs_spi {
+ void __iomem *regs; /* vaddr of the control registers */
+
+ u32 irq_dma;
+ u32 irq_err;
+ u32 dma;
+ struct mxs_dma_desc *pdesc;
+
+ u32 speed_khz;
+ u32 saved_timings;
+ u32 divider;
+
+ struct clk *clk;
+ struct device *master_dev;
+
+ struct work_struct work;
+ struct workqueue_struct *workqueue;
+ spinlock_t lock;
+ struct list_head queue;
+
+ u32 ver_major;
+
+ struct completion done;
+};
+
+#endif /* __SPI_STMP_H */
diff --git a/drivers/spi/spi_sam.c b/drivers/spi/spi_sam.c
new file mode 100644
index 000000000000..a67a01368010
--- /dev/null
+++ b/drivers/spi/spi_sam.c
@@ -0,0 +1,1161 @@
+/*
+ * spi_sam.c - Samsung SOC SPI controller driver.
+ * By -- Jaswinder Singh <jassi.brar@samsung.com>
+ *
+ * Copyright (C) 2009 Samsung Electronics Ltd.
+ */
+
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/sched.h>
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
+#include <linux/spi/spi.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/dma-mapping.h>
+#include <asm/gpio.h>
+#include <asm/dma.h>
+
+#include "spi_sam.h"
+
+//#define DEBUGSPI
+
+#ifdef DEBUGSPI
+
+#define dbg_printk(x...) printk(x)
+
+static void dump_regs(struct samspi_bus *sspi)
+{
+ u32 val;
+
+ val = readl(sspi->regs + SAMSPI_CH_CFG);
+ printk("CHN-%x\t", val);
+ val = readl(sspi->regs + SAMSPI_CLK_CFG);
+ printk("CLK-%x\t", val);
+ val = readl(sspi->regs + SAMSPI_MODE_CFG);
+ printk("MOD-%x\t", val);
+ val = readl(sspi->regs + SAMSPI_SLAVE_SEL);
+ printk("SLVSEL-%x\t", val);
+ val = readl(sspi->regs + SAMSPI_SPI_STATUS);
+ if(val & SPI_STUS_TX_DONE)
+ printk("TX_done\t");
+ if(val & SPI_STUS_TRAILCNT_ZERO)
+ printk("TrailZ\t");
+ if(val & SPI_STUS_RX_OVERRUN_ERR)
+ printk("RX_Ovrn\t");
+ if(val & SPI_STUS_RX_UNDERRUN_ERR)
+ printk("Rx_Unrn\t");
+ if(val & SPI_STUS_TX_OVERRUN_ERR)
+ printk("Tx_Ovrn\t");
+ if(val & SPI_STUS_TX_UNDERRUN_ERR)
+ printk("Tx_Unrn\t");
+ if(val & SPI_STUS_RX_FIFORDY)
+ printk("Rx_Rdy\t");
+ if(val & SPI_STUS_TX_FIFORDY)
+ printk("Tx_Rdy\t");
+ printk("Rx/TxLvl=%d,%d\n", (val>>13)&0x7f, (val>>6)&0x7f);
+}
+
+static void dump_spidevice_info(struct spi_device *spi)
+{
+ dbg_printk("Modalias = %s\n", spi->modalias);
+ dbg_printk("Slave-%d on Bus-%d\n", spi->chip_select, spi->master->bus_num);
+ dbg_printk("max_speed_hz = %d\n", spi->max_speed_hz);
+ dbg_printk("bits_per_word = %d\n", spi->bits_per_word);
+ dbg_printk("irq = %d\n", spi->irq);
+ dbg_printk("Clk Phs = %d\n", spi->mode & SPI_CPHA);
+ dbg_printk("Clk Pol = %d\n", spi->mode & SPI_CPOL);
+ dbg_printk("ActiveCS = %s\n", (spi->mode & (1<<2)) ? "high" : "low" );
+ dbg_printk("Our Mode = %s\n", (spi->mode & SPI_SLAVE) ? "Slave" : "Master");
+}
+
+#else
+
+#define dbg_printk(x...) /**/
+#define dump_regs(sspi) /**/
+#define dump_spidevice_info(spi) /**/
+
+#endif
+
+static void dump_spi_regs(struct samspi_bus *sspi)
+{
+ printk(KERN_CRIT "Reg Info \n");
+ printk(KERN_CRIT "CH_CFG = 0x%8.8x\n", readl(sspi->regs + SAMSPI_CH_CFG));
+ printk(KERN_CRIT "CLK_CFG = 0x%8.8x\n", readl(sspi->regs + SAMSPI_CLK_CFG));
+ printk(KERN_CRIT "MODE_CFG = 0x%8.8x\n", readl(sspi->regs + SAMSPI_MODE_CFG));
+ printk(KERN_CRIT "CS_REG = 0x%8.8x\n", readl(sspi->regs + SAMSPI_SLAVE_SEL));
+ printk(KERN_CRIT "SPI_INT_EN = 0x%8.8x\n", readl(sspi->regs + SAMSPI_SPI_INT_EN));
+ printk(KERN_CRIT "SPI_STATUS = 0x%8.8x\n", readl(sspi->regs + SAMSPI_SPI_STATUS));
+// printk(KERN_CRIT "SAMSPI_SPI_RX_DATA = 0x%8.8x\n", readl(sspi->regs + SAMSPI_SPI_RX_DATA));
+// printk(KERN_CRIT "SAMSPI_SPI_TX_DATA = 0x%8.8x\n", readl(sspi->regs + SAMSPI_SPI_TX_DATA));
+}
+
+static struct s3c2410_dma_client samspi_dma_client = {
+ .name = "samspi-dma",
+};
+
+static int sspi_getclcks(struct samspi_bus *sspi)
+{
+ struct clk *cspi, *cp, *cm, *cf;
+
+ cp = NULL;
+ cm = NULL;
+ cf = NULL;
+ cspi = sspi->clk;
+
+ if(cspi == NULL){
+ cspi = clk_get(&sspi->pdev->dev, "spi");
+ if(IS_ERR(cspi)){
+ printk("Unable to get spi!\n");
+ return -EBUSY;
+ }
+ }
+ dbg_printk("%s:%d Got clk=spi\n", __func__, __LINE__);
+
+#if defined(CONFIG_SPICLK_SRC_SCLK48M) || defined(CONFIG_SPICLK_SRC_EPLL) || defined(CONFIG_SPICLK_SRC_SPIEXT)
+ cp = clk_get(&sspi->pdev->dev, spiclk_src);
+ if(IS_ERR(cp)){
+ printk("Unable to get parent clock(%s)!\n", spiclk_src);
+ if(sspi->clk == NULL){
+ clk_disable(cspi);
+ clk_put(cspi);
+ }
+ return -EBUSY;
+ }
+ dbg_printk("%s:%d Got clk=%s\n", __func__, __LINE__, spiclk_src);
+
+#if defined(CONFIG_SPICLK_SRC_EPLL) || defined(CONFIG_SPICLK_SRC_SPIEXT)
+ cm = clk_get(&sspi->pdev->dev, spisclk_src);
+ if(IS_ERR(cm)){
+ printk("Unable to get %s\n", spisclk_src);
+ clk_put(cp);
+ return -EBUSY;
+ }
+ dbg_printk("%s:%d Got clk=%s\n", __func__, __LINE__, spisclk_src);
+ if(clk_set_parent(cp, cm)){
+ printk("failed to set %s as the parent of %s\n", spisclk_src, spiclk_src);
+ clk_put(cm);
+ clk_put(cp);
+ return -EBUSY;
+ }
+ dbg_printk("Set %s as the parent of %s\n", spisclk_src, spiclk_src);
+
+#if defined(CONFIG_SPICLK_EPLL_MOUTEPLL) /* MOUTepll through EPLL */
+ cf = clk_get(&sspi->pdev->dev, "fout_epll");
+ if(IS_ERR(cf)){
+ printk("Unable to get fout_epll\n");
+ clk_put(cm);
+ clk_put(cp);
+ return -EBUSY;
+ }
+ dbg_printk("Got fout_epll\n");
+ if(clk_set_parent(cm, cf)){
+ printk("failed to set FOUTepll as parent of %s\n", spisclk_src);
+ clk_put(cf);
+ clk_put(cm);
+ clk_put(cp);
+ return -EBUSY;
+ }
+ dbg_printk("Set FOUTepll as parent of %s\n", spisclk_src);
+ clk_put(cf);
+#endif
+ clk_put(cm);
+#endif
+
+ sspi->prnt_clk = cp;
+#endif
+
+ sspi->clk = cspi;
+ return 0;
+}
+
+static void sspi_putclcks(struct samspi_bus *sspi)
+{
+ if(sspi->prnt_clk != NULL)
+ clk_put(sspi->prnt_clk);
+
+ clk_put(sspi->clk);
+}
+
+static int sspi_enclcks(struct samspi_bus *sspi)
+{
+ if(sspi->prnt_clk != NULL)
+ clk_enable(sspi->prnt_clk);
+
+ return clk_enable(sspi->clk);
+}
+
+static void sspi_disclcks(struct samspi_bus *sspi)
+{
+ if(sspi->prnt_clk != NULL)
+ clk_disable(sspi->prnt_clk);
+
+ clk_disable(sspi->clk);
+}
+
+static unsigned long sspi_getrate(struct samspi_bus *sspi)
+{
+ if(sspi->prnt_clk != NULL)
+ return clk_get_rate(sspi->prnt_clk);
+ else
+ return clk_get_rate(sspi->clk);
+}
+
+static int sspi_setrate(struct samspi_bus *sspi, unsigned long r)
+{
+ /* We don't take charge of the Src Clock, yet */
+ return 0;
+}
+
+static inline void enable_spidma(struct samspi_bus *sspi, struct spi_transfer *xfer)
+{
+ u32 val;
+
+ val = readl(sspi->regs + SAMSPI_MODE_CFG);
+ val &= ~(SPI_MODE_TXDMA_ON | SPI_MODE_RXDMA_ON);
+ if(xfer->tx_buf != NULL)
+ val |= SPI_MODE_TXDMA_ON;
+ if(xfer->rx_buf != NULL)
+ val |= SPI_MODE_RXDMA_ON;
+ writel(val, sspi->regs + SAMSPI_MODE_CFG);
+}
+
+static inline void flush_dma(struct samspi_bus *sspi, struct spi_transfer *xfer)
+{
+ if(xfer->tx_buf != NULL)
+ s3c2410_dma_ctrl(sspi->tx_dmach, S3C2410_DMAOP_FLUSH);
+ if(xfer->rx_buf != NULL)
+ s3c2410_dma_ctrl(sspi->rx_dmach, S3C2410_DMAOP_FLUSH);
+}
+
+static inline void flush_spi(struct samspi_bus *sspi)
+{
+ u32 val;
+
+ val = readl(sspi->regs + SAMSPI_CH_CFG);
+ val |= SPI_CH_SW_RST;
+ val &= ~SPI_CH_HS_EN;
+ if((sspi->cur_speed > 30000000UL) && !(sspi->cur_mode & SPI_SLAVE)) /* TODO ??? */
+ val |= SPI_CH_HS_EN;
+ writel(val, sspi->regs + SAMSPI_CH_CFG);
+
+ /* Flush TxFIFO*/
+ do{
+ val = readl(sspi->regs + SAMSPI_SPI_STATUS);
+ val = (val>>6) & 0x7f;
+ }while(val);
+
+ /* Flush RxFIFO*/
+ val = readl(sspi->regs + SAMSPI_SPI_STATUS);
+ val = (val>>13) & 0x7f;
+ while(val){
+ readl(sspi->regs + SAMSPI_SPI_RX_DATA);
+ val = readl(sspi->regs + SAMSPI_SPI_STATUS);
+ val = (val>>13) & 0x7f;
+ }
+
+ val = readl(sspi->regs + SAMSPI_CH_CFG);
+ val &= ~SPI_CH_SW_RST;
+ writel(val, sspi->regs + SAMSPI_CH_CFG);
+}
+
+static inline void enable_spichan(struct samspi_bus *sspi, struct spi_transfer *xfer)
+{
+ u32 val;
+
+ //printk(KERN_CRIT "@@@@@@@@ enable_spichan() \n");
+
+ val = readl(sspi->regs + SAMSPI_CH_CFG);
+ val &= ~(SPI_CH_RXCH_ON | SPI_CH_TXCH_ON);
+ if(xfer->tx_buf != NULL){
+ val |= SPI_CH_TXCH_ON;
+ }
+ if(xfer->rx_buf != NULL){
+ if(!(sspi->cur_mode & SPI_SLAVE)){
+ writel((xfer->len & 0xffff) | SPI_PACKET_CNT_EN,
+ sspi->regs + SAMSPI_PACKET_CNT); /* XXX TODO Bytes or number of SPI-Words? */
+ }
+ val |= SPI_CH_RXCH_ON;
+ }
+ writel(val, sspi->regs + SAMSPI_CH_CFG);
+}
+
+static inline void enable_spiintr(struct samspi_bus *sspi, struct spi_transfer *xfer)
+{
+ u32 val = 0;
+
+ if(xfer->tx_buf != NULL){
+ val |= SPI_INT_TX_OVERRUN_EN;
+ if(!(sspi->cur_mode & SPI_SLAVE))
+ val |= SPI_INT_TX_UNDERRUN_EN;
+ }
+ if(xfer->rx_buf != NULL){
+ val |= (SPI_INT_RX_UNDERRUN_EN | SPI_INT_RX_OVERRUN_EN | SPI_INT_TRAILING_EN);
+ }
+ writel(val, sspi->regs + SAMSPI_SPI_INT_EN);
+}
+
+static inline void enable_spienqueue(struct samspi_bus *sspi, struct spi_transfer *xfer)
+{
+ if(xfer->rx_buf != NULL){
+ sspi->rx_done = BUSY;
+ s3c2410_dma_config(sspi->rx_dmach, sspi->cur_bpw/8, 0);
+ s3c2410_dma_enqueue(sspi->rx_dmach, (void *)sspi, xfer->rx_dma, xfer->len);
+ }
+ if(xfer->tx_buf != NULL){
+ sspi->tx_done = BUSY;
+ s3c2410_dma_config(sspi->tx_dmach, sspi->cur_bpw/8, 0);
+ s3c2410_dma_enqueue(sspi->tx_dmach, (void *)sspi, xfer->tx_dma, xfer->len);
+ }
+}
+
+static inline void enable_cs(struct samspi_bus *sspi, struct spi_device *spi)
+{
+ u32 val;
+ struct sam_spi_pdata *spd = (struct sam_spi_pdata *)spi->controller_data;
+
+ val = readl(sspi->regs + SAMSPI_SLAVE_SEL);
+
+ if(sspi->cur_mode & SPI_SLAVE){
+ val |= SPI_SLAVE_AUTO; /* Auto Mode */
+ val |= SPI_SLAVE_SIG_INACT;
+ }else{ /* Master Mode */
+ val &= ~SPI_SLAVE_AUTO; /* Manual Mode */
+ val &= ~SPI_SLAVE_SIG_INACT; /* Activate CS */
+ if(spi->mode & SPI_CS_HIGH){ // spd->cs_act_high){
+ spd->cs_set(spd->cs_pin, CS_HIGH);
+ spd->cs_level = CS_HIGH;
+ }else{
+ spd->cs_set(spd->cs_pin, CS_LOW);
+ spd->cs_level = CS_LOW;
+ }
+ }
+
+ writel(val, sspi->regs + SAMSPI_SLAVE_SEL);
+}
+
+static inline void disable_cs(struct samspi_bus *sspi, struct spi_device *spi)
+{
+ u32 val;
+ struct sam_spi_pdata *spd = (struct sam_spi_pdata *)spi->controller_data;
+
+ if(!(spi->mode & SPI_CS_HIGH) && spd->cs_act_high){
+ dbg_printk("%s:%s:%d Slave supports SPI_CS_HIGH, but not requested by Master!\n", __FILE__, __func__, __LINE__);
+ }
+
+ val = readl(sspi->regs + SAMSPI_SLAVE_SEL);
+
+ if(sspi->cur_mode & SPI_SLAVE){
+ val |= SPI_SLAVE_AUTO; /* Auto Mode */
+ }else{ /* Master Mode */
+ val &= ~SPI_SLAVE_AUTO; /* Manual Mode */
+ val |= SPI_SLAVE_SIG_INACT; /* DeActivate CS */
+ if(spi->mode & SPI_CS_HIGH){ // spd->cs_act_high){
+ spd->cs_set(spd->cs_pin, CS_LOW);
+ spd->cs_level = CS_LOW;
+ }else{
+ spd->cs_set(spd->cs_pin, CS_HIGH);
+ spd->cs_level = CS_HIGH;
+ }
+ }
+
+ writel(val, sspi->regs + SAMSPI_SLAVE_SEL);
+}
+
+static inline void set_polarity(struct samspi_bus *sspi)
+{
+ u32 val;
+
+ val = readl(sspi->regs + SAMSPI_CH_CFG);
+ val &= ~(SPI_CH_SLAVE | SPI_CPOL_L | SPI_CPHA_B);
+ if(sspi->cur_mode & SPI_SLAVE)
+ val |= SPI_CH_SLAVE;
+ if(!(sspi->cur_mode & SPI_CPOL))
+ val |= SPI_CPOL_L;
+ if(sspi->cur_mode & SPI_CPHA)
+ val |= SPI_CPHA_B;
+ writel(val, sspi->regs + SAMSPI_CH_CFG);
+}
+
+static inline void set_clock(struct samspi_bus *sspi)
+{
+ u32 val;
+
+ val = readl(sspi->regs + SAMSPI_CLK_CFG);
+ val &= ~(SPI_CLKSEL_SRCMSK | SPI_ENCLK_ENABLE | 0xff);
+ val |= SPI_CLKSEL_SRC;
+ if(!(sspi->cur_mode & SPI_SLAVE)){
+ val |= ((sspi_getrate(sspi) / sspi->cur_speed / 2 - 1) << 0); // PCLK and PSR
+ val |= SPI_ENCLK_ENABLE;
+ }
+ writel(val, sspi->regs + SAMSPI_CLK_CFG);
+}
+
+static inline void set_dmachan(struct samspi_bus *sspi)
+{
+ u32 val;
+
+ val = readl(sspi->regs + SAMSPI_MODE_CFG);
+ val &= ~((0x3<<17) | (0x3<<29));
+ if(sspi->cur_bpw == 8){
+ val |= SPI_MODE_CH_TSZ_BYTE;
+ val |= SPI_MODE_BUS_TSZ_BYTE;
+ }else if(sspi->cur_bpw == 16){
+ val |= SPI_MODE_CH_TSZ_HALFWORD;
+ val |= SPI_MODE_BUS_TSZ_HALFWORD;
+ }else if(sspi->cur_bpw == 32){
+ val |= SPI_MODE_CH_TSZ_WORD;
+ val |= SPI_MODE_BUS_TSZ_WORD;
+ }else{
+ printk("Invalid Bits/Word!\n");
+ }
+ val &= ~(SPI_MODE_4BURST | SPI_MODE_TXDMA_ON | SPI_MODE_RXDMA_ON);
+ writel(val, sspi->regs + SAMSPI_MODE_CFG);
+}
+
+static void config_sspi(struct samspi_bus *sspi)
+{
+ /* Set Polarity and Phase */
+ set_polarity(sspi);
+
+ /* Set Channel & DMA Mode */
+ set_dmachan(sspi);
+}
+
+static void samspi_hwinit(struct samspi_bus *sspi, int channel)
+{
+ unsigned int val;
+
+ writel(SPI_SLAVE_SIG_INACT, sspi->regs + SAMSPI_SLAVE_SEL);
+
+ /* Disable Interrupts */
+ writel(0, sspi->regs + SAMSPI_SPI_INT_EN);
+
+#ifdef CONFIG_CPU_S3C6410
+ writel((readl(S3C64XX_SPC_BASE) & ~(3<<28)) | (3<<28), S3C64XX_SPC_BASE);
+ writel((readl(S3C64XX_SPC_BASE) & ~(3<<18)) | (3<<18), S3C64XX_SPC_BASE);
+#elif defined (CONFIG_CPU_S5P6440)
+ writel((readl(S5P64XX_SPC_BASE) & ~(3<<28)) | (3<<28), S5P64XX_SPC_BASE);
+ writel((readl(S5P64XX_SPC_BASE) & ~(3<<18)) | (3<<18), S5P64XX_SPC_BASE);
+#elif defined (CONFIG_CPU_S5P6440)
+ /* How to control drive strength, if we must? */
+#endif
+
+ writel(SPI_CLKSEL_SRC, sspi->regs + SAMSPI_CLK_CFG);
+ writel(0, sspi->regs + SAMSPI_MODE_CFG);
+ writel(SPI_SLAVE_SIG_INACT, sspi->regs + SAMSPI_SLAVE_SEL);
+ writel(0, sspi->regs + SAMSPI_PACKET_CNT);
+ writel(readl(sspi->regs + SAMSPI_PENDING_CLR), sspi->regs + SAMSPI_PENDING_CLR);
+ writel(SPI_FBCLK_0NS, sspi->regs + SAMSPI_FB_CLK);
+
+ flush_spi(sspi);
+
+ writel(0, sspi->regs + SAMSPI_SWAP_CFG);
+ writel(SPI_FBCLK_9NS, sspi->regs + SAMSPI_FB_CLK);
+
+ val = readl(sspi->regs + SAMSPI_MODE_CFG);
+ val &= ~(SPI_MAX_TRAILCNT << SPI_TRAILCNT_OFF);
+ if(channel == 0)
+ SET_MODECFG(val, 0);
+ else
+ SET_MODECFG(val, 1);
+ val |= (SPI_TRAILCNT << SPI_TRAILCNT_OFF);
+ writel(val, sspi->regs + SAMSPI_MODE_CFG);
+}
+
+static irqreturn_t samspi_interrupt(int irq, void *dev_id)
+{
+ u32 val;
+ struct samspi_bus *sspi = (struct samspi_bus *)dev_id;
+
+ dump_regs(sspi);
+ val = readl(sspi->regs + SAMSPI_PENDING_CLR);
+ dbg_printk("PENDING=%x\n", val);
+ writel(val, sspi->regs + SAMSPI_PENDING_CLR);
+
+ /* We get interrupted only for bad news */
+ if(sspi->tx_done != PASS){
+ printk(KERN_CRIT "TX FAILED \n");
+ sspi->tx_done = FAIL;
+ }
+ if(sspi->rx_done != PASS){
+ printk(KERN_CRIT "RX FAILED \n");
+ sspi->rx_done = FAIL;
+ }
+ sspi->state = STOPPED;
+ complete(&sspi->xfer_completion);
+
+ return IRQ_HANDLED;
+}
+
+void samspi_dma_rxcb(struct s3c2410_dma_chan *chan, void *buf_id, int size, enum s3c2410_dma_buffresult res)
+{
+ struct samspi_bus *sspi = (struct samspi_bus *)buf_id;
+
+ if(res == S3C2410_RES_OK){
+ sspi->rx_done = PASS;
+ dbg_printk("DmaRx-%d ", size);
+ }else{
+ sspi->rx_done = FAIL;
+ dbg_printk("DmaAbrtRx-%d ", size);
+ }
+
+ if(sspi->tx_done != BUSY && sspi->state != STOPPED) /* If other done and all OK */
+ complete(&sspi->xfer_completion);
+}
+
+void samspi_dma_txcb(struct s3c2410_dma_chan *chan, void *buf_id, int size, enum s3c2410_dma_buffresult res)
+{
+ struct samspi_bus *sspi = (struct samspi_bus *)buf_id;
+
+ if(res == S3C2410_RES_OK){
+ sspi->tx_done = PASS;
+ dbg_printk("DmaTx-%d ", size);
+ }else{
+ sspi->tx_done = FAIL;
+ dbg_printk("DmaAbrtTx-%d ", size);
+ }
+
+ if(sspi->rx_done != BUSY && sspi->state != STOPPED) /* If other done and all OK */
+ complete(&sspi->xfer_completion);
+}
+
+static int wait_for_txshiftout(struct samspi_bus *sspi, unsigned long t)
+{
+ unsigned long timeout;
+
+ timeout = jiffies + t;
+ while((__raw_readl(sspi->regs + SAMSPI_SPI_STATUS) >> 6) & 0x7f){
+ if(time_after(jiffies, timeout))
+ return -1;
+ cpu_relax();
+ }
+ return 0;
+}
+
+static int wait_for_xfer(struct samspi_bus *sspi, struct spi_transfer *xfer)
+{
+ int status;
+ u32 val;
+
+ val = msecs_to_jiffies(xfer->len / (sspi->min_speed / 8 / 1000)); /* time to xfer data at min. speed */
+ if(sspi->cur_mode & SPI_SLAVE)
+ val += msecs_to_jiffies(5000); /* 5secs to switch on the Master */
+ else
+ val += msecs_to_jiffies(10); /* just some more */
+ status = wait_for_completion_interruptible_timeout(&sspi->xfer_completion, val);
+
+ //printk(KERN_CRIT "Return from waitforcompletion = %d \n", status);
+
+ if(status == 0)
+ status = -ETIMEDOUT;
+ else if(status == -ERESTARTSYS)
+ status = -EINTR;
+ else if((sspi->tx_done != PASS) || (sspi->rx_done != PASS)) /* Some Xfer failed */
+ status = -EIO;
+ else
+ status = 0; /* All OK */
+
+ /* When TxLen <= SPI-FifoLen in Slave mode, DMA returns naively */
+ if(!status && (sspi->cur_mode & SPI_SLAVE) && (xfer->tx_buf != NULL)){
+ val = msecs_to_jiffies(xfer->len / (sspi->min_speed / 8 / 1000)); /* Be lenient */
+ val += msecs_to_jiffies(5000); /* 5secs to switch on the Master */
+ status = wait_for_txshiftout(sspi, val);
+ if(status == -1)
+ status = -ETIMEDOUT;
+ else
+ status = 0;
+ }
+
+ return status;
+}
+
+#define INVALID_DMA_ADDRESS 0xffffffff
+/* First, try to map buf onto phys addr as such.
+ * If xfer->r/tx_buf was not on contiguous memory,
+ * allocate from our preallocated DMA buffer.
+ */
+static int samspi_map_xfer(struct samspi_bus *sspi, struct spi_transfer *xfer)
+{
+ struct device *dev = &sspi->pdev->dev;
+
+ sspi->rx_tmp = NULL;
+ sspi->tx_tmp = NULL;
+
+ xfer->tx_dma = xfer->rx_dma = INVALID_DMA_ADDRESS;
+ if(xfer->tx_buf != NULL){
+ xfer->tx_dma = dma_map_single(dev,
+ (void *) xfer->tx_buf, xfer->len,
+ DMA_TO_DEVICE);
+ if(dma_mapping_error(dev, xfer->tx_dma))
+ goto alloc_from_buffer;
+ }
+ if(xfer->rx_buf != NULL){
+ xfer->rx_dma = dma_map_single(dev,
+ xfer->rx_buf, xfer->len,
+ DMA_FROM_DEVICE);
+ if(dma_mapping_error(dev, xfer->rx_dma)){
+ if(xfer->tx_buf)
+ dma_unmap_single(dev,
+ xfer->tx_dma, xfer->len,
+ DMA_TO_DEVICE);
+ goto alloc_from_buffer;
+ }
+ }
+ return 0;
+
+alloc_from_buffer: /* If the xfer->[r/t]x_buf was not on contiguous memory */
+
+ printk(KERN_CRIT "############ Allocating from buffer...\n");
+
+ if(xfer->len <= SAMSPI_DMABUF_LEN){
+ if(xfer->rx_buf != NULL){
+ xfer->rx_dma = sspi->rx_dma_phys;
+ sspi->rx_tmp = (void *)sspi->rx_dma_cpu;
+ }
+ if(xfer->tx_buf != NULL){
+ xfer->tx_dma = sspi->tx_dma_phys;
+ sspi->tx_tmp = (void *)sspi->tx_dma_cpu;
+ }
+ }else{
+ dbg_printk("If you plan to use this Xfer size often, increase SAMSPI_DMABUF_LEN\n");
+ if(xfer->rx_buf != NULL){
+ sspi->rx_tmp = dma_alloc_coherent(&sspi->pdev->dev, SAMSPI_DMABUF_LEN,
+ &xfer->rx_dma, GFP_KERNEL | GFP_DMA);
+ if(sspi->rx_tmp == NULL)
+ return -ENOMEM;
+ }
+ if(xfer->tx_buf != NULL){
+ sspi->tx_tmp = dma_alloc_coherent(&sspi->pdev->dev,
+ SAMSPI_DMABUF_LEN, &xfer->tx_dma, GFP_KERNEL | GFP_DMA);
+ if(sspi->tx_tmp == NULL){
+ if(xfer->rx_buf != NULL)
+ dma_free_coherent(&sspi->pdev->dev,
+ SAMSPI_DMABUF_LEN, sspi->rx_tmp, xfer->rx_dma);
+ return -ENOMEM;
+ }
+ }
+ }
+
+ if(xfer->tx_buf != NULL)
+ memcpy(sspi->tx_tmp, xfer->tx_buf, xfer->len);
+
+ return 0;
+}
+
+static void samspi_unmap_xfer(struct samspi_bus *sspi, struct spi_transfer *xfer)
+{
+ if((sspi->rx_tmp == NULL) && (sspi->tx_tmp == NULL)) /* if map_single'd */
+ return;
+
+ if((xfer->rx_buf != NULL) && (sspi->rx_tmp != NULL))
+ memcpy(xfer->rx_buf, sspi->rx_tmp, xfer->len);
+
+ if(xfer->len > SAMSPI_DMABUF_LEN){
+ if(xfer->rx_buf != NULL)
+ dma_free_coherent(&sspi->pdev->dev, SAMSPI_DMABUF_LEN, sspi->rx_tmp, xfer->rx_dma);
+ if(xfer->tx_buf != NULL)
+ dma_free_coherent(&sspi->pdev->dev, SAMSPI_DMABUF_LEN, sspi->tx_tmp, xfer->tx_dma);
+ }else{
+ sspi->rx_tmp = NULL;
+ sspi->tx_tmp = NULL;
+ }
+}
+
+static void handle_msg(struct samspi_bus *sspi, struct spi_message *msg)
+{
+ u8 bpw;
+ u32 speed, val;
+ int status = 0;
+ struct spi_transfer *xfer;
+ struct spi_device *spi = msg->spi;
+
+ config_sspi(sspi);
+
+ list_for_each_entry (xfer, &msg->transfers, transfer_list) {
+
+ if(!msg->is_dma_mapped && samspi_map_xfer(sspi, xfer)){
+ dev_err(&spi->dev, "Xfer: Unable to allocate DMA buffer!\n");
+ status = -ENOMEM;
+ goto out;
+ }
+
+ INIT_COMPLETION(sspi->xfer_completion);
+
+ /* Only BPW and Speed may change across transfers */
+ bpw = xfer->bits_per_word ? : spi->bits_per_word;
+ speed = xfer->speed_hz ? : spi->max_speed_hz;
+
+ if(sspi->cur_bpw != bpw || sspi->cur_speed != speed){
+ sspi->cur_bpw = bpw;
+ sspi->cur_speed = speed;
+ config_sspi(sspi);
+ }
+
+ /* Pending only which is to be done */
+ sspi->rx_done = PASS;
+ sspi->tx_done = PASS;
+ sspi->state = RUNNING;
+
+ /* Configure Clock */
+ set_clock(sspi);
+
+ /* Enable Interrupts */
+ enable_spiintr(sspi, xfer);
+
+ if(!(sspi->cur_mode & SPI_SLAVE))
+ flush_spi(sspi);
+
+ /* Enqueue data on DMA */
+ enable_spienqueue(sspi, xfer);
+
+ /* Enable DMA */
+ enable_spidma(sspi, xfer);
+
+ /* Enable TX/RX */
+ enable_spichan(sspi, xfer);
+
+ /* Slave Select */
+ enable_cs(sspi, spi);
+
+ status = wait_for_xfer(sspi, xfer);
+
+ /**************
+ * Block Here *
+ **************/
+
+ if(status == -ETIMEDOUT){
+ dev_err(&spi->dev, "Xfer: Timeout!\n");
+ dump_regs(sspi);
+ sspi->state = STOPPED;
+ /* DMA Disable*/
+ val = readl(sspi->regs + SAMSPI_MODE_CFG);
+ val &= ~(SPI_MODE_TXDMA_ON | SPI_MODE_RXDMA_ON);
+ writel(val, sspi->regs + SAMSPI_MODE_CFG);
+ flush_dma(sspi, xfer);
+ flush_spi(sspi);
+ if(!msg->is_dma_mapped)
+ samspi_unmap_xfer(sspi, xfer);
+ goto out;
+ }
+ if(status == -EINTR){
+ dev_err(&spi->dev, "Xfer: Interrupted!\n");
+ dump_regs(sspi);
+ sspi->state = STOPPED;
+ /* DMA Disable*/
+ val = readl(sspi->regs + SAMSPI_MODE_CFG);
+ val &= ~(SPI_MODE_TXDMA_ON | SPI_MODE_RXDMA_ON);
+ writel(val, sspi->regs + SAMSPI_MODE_CFG);
+ flush_dma(sspi, xfer);
+ flush_spi(sspi);
+ if(!msg->is_dma_mapped)
+ samspi_unmap_xfer(sspi, xfer);
+ goto out;
+ }
+ if(status == -EIO){ /* Some Xfer failed */
+ dev_err(&spi->dev, "Xfer: Failed!\n");
+ dump_regs(sspi);
+ sspi->state = STOPPED;
+ /* DMA Disable*/
+ val = readl(sspi->regs + SAMSPI_MODE_CFG);
+ val &= ~(SPI_MODE_TXDMA_ON | SPI_MODE_RXDMA_ON);
+ writel(val, sspi->regs + SAMSPI_MODE_CFG);
+ flush_dma(sspi, xfer);
+ flush_spi(sspi);
+ if(!msg->is_dma_mapped)
+ samspi_unmap_xfer(sspi, xfer);
+ goto out;
+ }
+
+ if(xfer->delay_usecs){
+ udelay(xfer->delay_usecs);
+ dbg_printk("%s:%s:%d Unverified Control Flow Path!\n", __FILE__, __func__, __LINE__);
+ }
+
+ if(xfer->cs_change && !(sspi->cur_mode & SPI_SLAVE)){
+ disable_cs(sspi, spi);
+ dbg_printk("%s:%s:%d Unverified Control Flow Path!\n", __FILE__, __func__, __LINE__);
+ }
+
+ msg->actual_length += xfer->len;
+
+ if(!msg->is_dma_mapped)
+ samspi_unmap_xfer(sspi, xfer);
+
+ }
+
+out:
+ /* Slave Deselect */
+ if(!(sspi->cur_mode & SPI_SLAVE))
+ disable_cs(sspi, spi);
+
+ /* Disable Interrupts */
+ writel(0, sspi->regs + SAMSPI_SPI_INT_EN);
+
+ /* Tx/Rx Disable */
+ val = readl(sspi->regs + SAMSPI_CH_CFG);
+ val &= ~(SPI_CH_RXCH_ON | SPI_CH_TXCH_ON);
+ writel(val, sspi->regs + SAMSPI_CH_CFG);
+
+ /* DMA Disable*/
+ val = readl(sspi->regs + SAMSPI_MODE_CFG);
+ val &= ~(SPI_MODE_TXDMA_ON | SPI_MODE_RXDMA_ON);
+ writel(val, sspi->regs + SAMSPI_MODE_CFG);
+
+ msg->status = status;
+ if(msg->complete)
+ msg->complete(msg->context);
+}
+
+static void samspi_work(struct work_struct *work)
+{
+ struct samspi_bus *sspi = container_of(work, struct samspi_bus, work);
+ unsigned long flags;
+
+ spin_lock_irqsave(&sspi->lock, flags);
+ while (!list_empty(&sspi->queue)) {
+ struct spi_message *msg;
+
+ msg = container_of(sspi->queue.next, struct spi_message, queue);
+ list_del_init(&msg->queue);
+ spin_unlock_irqrestore(&sspi->lock, flags);
+
+ handle_msg(sspi, msg);
+
+ spin_lock_irqsave(&sspi->lock, flags);
+ }
+ spin_unlock_irqrestore(&sspi->lock, flags);
+}
+
+static void samspi_cleanup(struct spi_device *spi)
+{
+ dbg_printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
+}
+
+static int samspi_transfer(struct spi_device *spi, struct spi_message *msg)
+{
+ struct spi_master *master = spi->master;
+ struct samspi_bus *sspi = spi_master_get_devdata(master);
+ unsigned long flags;
+
+ spin_lock_irqsave(&sspi->lock, flags);
+ msg->actual_length = 0;
+ list_add_tail(&msg->queue, &sspi->queue);
+ queue_work(sspi->workqueue, &sspi->work);
+ spin_unlock_irqrestore(&sspi->lock, flags);
+
+ return 0;
+}
+
+/* the spi->mode bits understood by this driver: */
+#define MODEBITS (SPI_CPOL | SPI_CPHA | SPI_SLAVE | (spd->cs_act_high ? SPI_CS_HIGH : 0) )
+/*
+ * Here we only check the validity of requested configuration and
+ * save the configuration in a local data-structure.
+ * The controller is actually configured only just before
+ * we get a message to transfer _and_ if no other message is pending(already configured).
+ */
+static int samspi_setup(struct spi_device *spi)
+{
+ unsigned long flags;
+ unsigned int psr;
+ struct samspi_bus *sspi = spi_master_get_devdata(spi->master);
+ struct sam_spi_pdata *spd = (struct sam_spi_pdata *)spi->controller_data;
+
+ spin_lock_irqsave(&sspi->lock, flags);
+ if(!list_empty(&sspi->queue)){ /* Any pending message? */
+ spin_unlock_irqrestore(&sspi->lock, flags);
+ dev_dbg(&spi->dev, "setup: attempt while messages in queue!\n");
+ return -EBUSY;
+ }
+ spin_unlock_irqrestore(&sspi->lock, flags);
+
+ if (spi->chip_select > spi->master->num_chipselect) {
+ dev_dbg(&spi->dev, "setup: invalid chipselect %u (%u defined)\n",
+ spi->chip_select, spi->master->num_chipselect);
+ return -EINVAL;
+ }
+
+ spi->bits_per_word = spi->bits_per_word ? : 8;
+
+ if((spi->bits_per_word != 8) &&
+ (spi->bits_per_word != 16) &&
+ (spi->bits_per_word != 32)){
+ dev_err(&spi->dev, "setup: %dbits/wrd not supported!\n", spi->bits_per_word);
+ return -EINVAL;
+ }
+
+ spi->max_speed_hz = spi->max_speed_hz ? : sspi->max_speed;
+
+ /* Round-off max_speed_hz */
+ psr = sspi_getrate(sspi) / spi->max_speed_hz / 2 - 1;
+ psr &= 0xff;
+ if(spi->max_speed_hz < sspi_getrate(sspi) / 2 / (psr + 1))
+ psr = (psr+1) & 0xff;
+
+ spi->max_speed_hz = sspi_getrate(sspi) / 2 / (psr + 1);
+
+ if (spi->max_speed_hz > sspi->max_speed
+ || spi->max_speed_hz < sspi->min_speed){
+ dev_err(&spi->dev, "setup: req speed(%u) out of range[%u-%u]\n",
+ spi->max_speed_hz, sspi->min_speed, sspi->max_speed);
+ return -EINVAL;
+ }
+
+ if (spi->mode & ~MODEBITS) {
+ dev_dbg(&spi->dev, "setup: unsupported mode bits %x\n", spi->mode & ~MODEBITS);
+ return -EINVAL;
+ }
+
+ if(!(spi->mode & SPI_SLAVE) && (spd->cs_level == CS_FLOAT)){
+ spd->cs_config(spd->cs_pin, spd->cs_mode, (spi->mode & SPI_CS_HIGH)/*spd->cs_act_high*/ ? CS_LOW : CS_HIGH);
+ disable_cs(sspi, spi);
+ }
+
+ if((sspi->cur_bpw == spi->bits_per_word) &&
+ (sspi->cur_speed == spi->max_speed_hz) &&
+ (sspi->cur_mode == spi->mode)) /* If no change in configuration, do nothing */
+ return 0;
+
+ sspi->cur_bpw = spi->bits_per_word;
+ sspi->cur_speed = spi->max_speed_hz;
+ sspi->cur_mode = spi->mode;
+
+ SAM_SETGPIOPULL(sspi);
+ return 0;
+}
+
+static int __init samspi_probe(struct platform_device *pdev)
+{
+ struct spi_master *master;
+ struct samspi_bus *sspi;
+ int ret = -ENODEV;
+
+ dbg_printk("%s:%s:%d ID=%d\n", __FILE__, __func__, __LINE__, pdev->id);
+ master = spi_alloc_master(&pdev->dev, sizeof(struct samspi_bus)); /* Allocate contiguous SPI controller */
+ if (master == NULL)
+ return ret;
+ sspi = spi_master_get_devdata(master);
+ sspi->pdev = pdev;
+ sspi->spi_mstinfo = (struct sam_spi_mstr_info *)pdev->dev.platform_data;
+ sspi->master = master;
+ platform_set_drvdata(pdev, master);
+
+ INIT_WORK(&sspi->work, samspi_work);
+ spin_lock_init(&sspi->lock);
+ INIT_LIST_HEAD(&sspi->queue);
+ init_completion(&sspi->xfer_completion);
+
+ ret = sspi_getclcks(sspi);
+ if(ret){
+ dev_err(&pdev->dev, "cannot acquire clock \n");
+ ret = -EBUSY;
+ goto lb1;
+ }
+ ret = sspi_enclcks(sspi);
+ if(ret){
+ dev_err(&pdev->dev, "cannot enable clock \n");
+ ret = -EBUSY;
+ goto lb2;
+ }
+
+ sspi->max_speed = sspi_getrate(sspi) / 2 / (0x0 + 1);
+ sspi->min_speed = sspi_getrate(sspi) / 2 / (0xff + 1);
+
+ sspi->cur_bpw = 8;
+ sspi->cur_mode = SPI_SLAVE; /* Start in Slave mode */
+ sspi->cur_speed = sspi->min_speed;
+
+ /* Get and Map Resources */
+ sspi->iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (sspi->iores == NULL) {
+ dev_err(&pdev->dev, "cannot find IO resource\n");
+ ret = -ENOENT;
+ goto lb3;
+ }
+
+ sspi->ioarea = request_mem_region(sspi->iores->start, sspi->iores->end - sspi->iores->start + 1, pdev->name);
+ if (sspi->ioarea == NULL) {
+ dev_err(&pdev->dev, "cannot request IO\n");
+ ret = -ENXIO;
+ goto lb4;
+ }
+
+ sspi->regs = ioremap(sspi->iores->start, sspi->iores->end - sspi->iores->start + 1);
+ if (sspi->regs == NULL) {
+ dev_err(&pdev->dev, "cannot map IO\n");
+ ret = -ENXIO;
+ goto lb5;
+ }
+
+ sspi->tx_dma_cpu = dma_alloc_coherent(&pdev->dev, SAMSPI_DMABUF_LEN, &sspi->tx_dma_phys, GFP_KERNEL | GFP_DMA);
+ if(sspi->tx_dma_cpu == NULL){
+ dev_err(&pdev->dev, "Unable to allocate TX DMA buffers\n");
+ ret = -ENOMEM;
+ goto lb6;
+ }
+
+ sspi->rx_dma_cpu = dma_alloc_coherent(&pdev->dev, SAMSPI_DMABUF_LEN, &sspi->rx_dma_phys, GFP_KERNEL | GFP_DMA);
+ if(sspi->rx_dma_cpu == NULL){
+ dev_err(&pdev->dev, "Unable to allocate RX DMA buffers\n");
+ ret = -ENOMEM;
+ goto lb7;
+ }
+
+ sspi->irqres = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+ if(sspi->irqres == NULL){
+ dev_err(&pdev->dev, "cannot find IRQ\n");
+ ret = -ENOENT;
+ goto lb8;
+ }
+
+ ret = request_irq(sspi->irqres->start, samspi_interrupt, IRQF_DISABLED,
+ pdev->name, sspi);
+ if(ret){
+ dev_err(&pdev->dev, "cannot acquire IRQ\n");
+ ret = -EBUSY;
+ goto lb9;
+ }
+
+ sspi->workqueue = create_singlethread_workqueue(master->dev.parent->bus_id);
+ if(!sspi->workqueue){
+ dev_err(&pdev->dev, "cannot create workqueue\n");
+ ret = -EBUSY;
+ goto lb10;
+ }
+
+ master->bus_num = pdev->id;
+ master->setup = samspi_setup;
+ master->transfer = samspi_transfer;
+ master->cleanup = samspi_cleanup;
+ master->num_chipselect = sspi->spi_mstinfo->num_slaves;
+
+ if(spi_register_master(master)){
+ dev_err(&pdev->dev, "cannot register SPI master\n");
+ ret = -EBUSY;
+ goto lb11;
+ }
+
+ /* Configure GPIOs */
+ if(pdev->id == 0)
+ SETUP_SPI(sspi, 0);
+ else if(pdev->id == 1)
+ SETUP_SPI(sspi, 1);
+ SAM_SETGPIOPULL(sspi);
+
+ if(s3c2410_dma_request(sspi->rx_dmach, &samspi_dma_client, NULL)){
+ dev_err(&pdev->dev, "cannot get RxDMA\n");
+ ret = -EBUSY;
+ goto lb12;
+ }
+ s3c2410_dma_set_buffdone_fn(sspi->rx_dmach, samspi_dma_rxcb);
+ s3c2410_dma_devconfig(sspi->rx_dmach, S3C2410_DMASRC_HW, 0, sspi->sfr_phyaddr + SAMSPI_SPI_RX_DATA);
+ s3c2410_dma_config(sspi->rx_dmach, sspi->cur_bpw/8, 0);
+ s3c2410_dma_setflags(sspi->rx_dmach, S3C2410_DMAF_AUTOSTART);
+
+ if(s3c2410_dma_request(sspi->tx_dmach, &samspi_dma_client, NULL)){
+ dev_err(&pdev->dev, "cannot get TxDMA\n");
+ ret = -EBUSY;
+ goto lb13;
+ }
+ s3c2410_dma_set_buffdone_fn(sspi->tx_dmach, samspi_dma_txcb);
+ s3c2410_dma_devconfig(sspi->tx_dmach, S3C2410_DMASRC_MEM, 0, sspi->sfr_phyaddr + SAMSPI_SPI_TX_DATA);
+ s3c2410_dma_config(sspi->tx_dmach, sspi->cur_bpw/8, 0);
+ s3c2410_dma_setflags(sspi->tx_dmach, S3C2410_DMAF_AUTOSTART);
+
+ /* Setup Deufult Mode */
+ samspi_hwinit(sspi, pdev->id);
+
+ printk("Samsung SoC SPI Driver loaded for Bus SPI-%d with %d Slaves attached\n", pdev->id, master->num_chipselect);
+ printk("\tMax,Min-Speed [%d, %d]Hz\n", sspi->max_speed, sspi->min_speed);
+ printk("\tIrq=%d\tIOmem=[0x%x-0x%x]\tDMA=[Rx-%d, Tx-%d]\n",
+ sspi->irqres->start,
+ sspi->iores->end, sspi->iores->start,
+ sspi->rx_dmach, sspi->tx_dmach);
+
+ return 0;
+
+lb13:
+ s3c2410_dma_free(sspi->rx_dmach, &samspi_dma_client);
+lb12:
+ spi_unregister_master(master);
+lb11:
+ destroy_workqueue(sspi->workqueue);
+lb10:
+ free_irq(sspi->irqres->start, sspi);
+lb9:
+lb8:
+ dma_free_coherent(&pdev->dev, SAMSPI_DMABUF_LEN, sspi->rx_dma_cpu, sspi->rx_dma_phys);
+lb7:
+ dma_free_coherent(&pdev->dev, SAMSPI_DMABUF_LEN, sspi->tx_dma_cpu, sspi->tx_dma_phys);
+lb6:
+ iounmap((void *) sspi->regs);
+lb5:
+ release_mem_region(sspi->iores->start, sspi->iores->end - sspi->iores->start + 1);
+lb4:
+lb3:
+ sspi_disclcks(sspi);
+lb2:
+ sspi_putclcks(sspi);
+lb1:
+ platform_set_drvdata(pdev, NULL);
+ spi_master_put(master);
+
+ return ret;
+}
+
+static int __exit samspi_remove(struct platform_device *pdev)
+{
+ struct spi_master *master = spi_master_get(platform_get_drvdata(pdev));
+ struct samspi_bus *sspi = spi_master_get_devdata(master);
+
+ s3c2410_dma_free(sspi->tx_dmach, &samspi_dma_client);
+ s3c2410_dma_free(sspi->rx_dmach, &samspi_dma_client);
+ spi_unregister_master(master);
+ destroy_workqueue(sspi->workqueue);
+ free_irq(sspi->irqres->start, sspi);
+ dma_free_coherent(&pdev->dev, SAMSPI_DMABUF_LEN, sspi->rx_dma_cpu, sspi->rx_dma_phys);
+ dma_free_coherent(&pdev->dev, SAMSPI_DMABUF_LEN, sspi->tx_dma_cpu, sspi->tx_dma_phys);
+ iounmap((void *) sspi->regs);
+ release_mem_region(sspi->iores->start, sspi->iores->end - sspi->iores->start + 1);
+ sspi_disclcks(sspi);
+ sspi_putclcks(sspi);
+ platform_set_drvdata(pdev, NULL);
+ spi_master_put(master);
+
+ return 0;
+}
+
+static struct platform_driver sam_spi_driver = {
+ .driver = {
+ .name = "sam-spi",
+ .owner = THIS_MODULE,
+ .bus = &platform_bus_type,
+ },
+// .remove = sam_spi_remove,
+// .shutdown = sam_spi_shutdown,
+// .suspend = sam_spi_suspend,
+// .resume = sam_spi_resume,
+};
+
+static int __init sam_spi_init(void)
+{
+ dbg_printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
+ return platform_driver_probe(&sam_spi_driver, samspi_probe);
+}
+//module_init(sam_spi_init);
+subsys_initcall(sam_spi_init);
+
+static void __exit sam_spi_exit(void)
+{
+ platform_driver_unregister(&sam_spi_driver);
+}
+module_exit(sam_spi_exit);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jaswinder Singh Brar <jassi.brar@samsung.com>");
+MODULE_DESCRIPTION("Samsung SOC SPI Controller");