spicc: initial add spicc driver
authorSunny Luo <sunny.luo@amlogic.com>
Fri, 31 Mar 2017 13:14:34 +0000 (21:14 +0800)
committerJianxin Pan <jianxin.pan@amlogic.com>
Sat, 1 Apr 2017 05:39:14 +0000 (13:39 +0800)
PD#138714: spicc: initial add spicc driver

Change-Id: I60aa176f7bd9d64bd6e9db56adc7f592bc856f50
Signed-off-by: Sunny Luo <sunny.luo@amlogic.com>
MAINTAINERS
arch/arm64/boot/dts/amlogic/gxl_p212_1g.dts
arch/arm64/boot/dts/amlogic/mesongxl.dtsi
arch/arm64/boot/dts/amlogic/mesongxm.dtsi
arch/arm64/configs/meson64_defconfig
drivers/amlogic/Kconfig
drivers/amlogic/Makefile
drivers/amlogic/spicc/Kconfig [new file with mode: 0644]
drivers/amlogic/spicc/Makefile [new file with mode: 0644]
drivers/amlogic/spicc/spicc.c [new file with mode: 0644]
drivers/amlogic/spicc/spicc.h [new file with mode: 0644]

index c6ad007..0e99584 100644 (file)
@@ -13751,3 +13751,10 @@ F: drivers/amlogic/media/video_processor/video_dev/Makefile
 F: drivers/amlogic/media/video_processor/video_dev/amlvideo.h
 F: drivers/amlogic/media/video_processor/video_dev/amlvideo.c
 F: drivers/amlogic/media/video_processor/video_dev/common/vfutil.c
+
+AMLOGIC SPICC DRIVER
+M: Sunny Luo <sunny.luo@amlogic.com>
+F: drivers/amlogic/spicc/spicc.c
+F: drivers/amlogic/spicc/spicc.h
+F: drivers/amlogic/spicc/Kconfig
+F: drivers/amlogic/spicc/Makefile
index 8ec31b4..bc3ef3f 100644 (file)
 &efuse {
        status = "ok";
 };
+
+&spicc{
+       status = "disabled";
+       pinctrl-names = "spicc_pulldown","spicc_pullup";
+       pinctrl-0 = <&spicc_pulldown_x8x9x11>;
+       pinctrl-1 = <&spicc_pullup_x8x9x11>;
+       num_chipselect = <1>;
+       cs-gpios = <&gpio GPIOX_10 GPIO_ACTIVE_HIGH>;
+       dma_en = <0>;
+       dma_tx_threshold = <3>;
+       dma_rx_threshold = <3>;
+       dma_num_per_read_burst = <3>;
+       dma_num_per_write_burst = <3>;
+       delay_control = <0x15>;
+       ssctl = <0>;
+};
index 7a319df..93ae8f4 100644 (file)
                /* 0: 100.0M    1: 166.7M    2: 200.0M    3: 250.0M */
                /* 4: 333.3M    5: 400.0M    6: 500.0M    7: 666.7M */
        };
+
+       spicc:@c1108d80{
+               compatible = "amlogic, spicc";
+               status = "disabled";
+               reg = <0x0 0xc1108d80 0x0 0x28>;
+               clocks = <&clkc CLKID_SPICC>;
+               clock-names = "spicc_clk";
+               interrupts = <0 81 1>;
+               device_id = <0>;
+       };
+
        i2c_ao: i2c@c8100500{ /*I2C-AO*/
                        compatible = "amlogic, meson-i2c";
                        dev_name = "i2c-AO";
                        };
                };
 
-               spicc_pins_z11z12z13: spicc_pins_z11z12z13 {
-                       mux {
-                               groups = "spi_sclk_0",
-                                       "spi_miso_0",
-                                       "spi_mosi_0";
-                               function = "spi";
-                       };
-               };
-
                spicc_pulldown_z11z12z13: spicc_pulldown_z11z12z13 {
                        mux {
                                groups = "spi_sclk_0",
                        };
                };
 
-               spicc_pins_x8x9x11: spicc_pins_x8x9x11 {
-                       mux {
-                               groups = "spi_sclk_1",
-                                       "spi_miso_1",
-                                       "spi_mosi_1";
-                               function = "spi";
-                       };
-               };
-
                spicc_pulldown_x8x9x11: spicc_pulldown_x8x9x11 {
                        mux {
                                groups = "spi_sclk_1",
        tbl = <&clk125_cfg &clk285_cfg &clk400_cfg
                   &clk500_cfg &clk666_cfg &clk750_cfg &clk750_cfg>;
 };
-
index fc5fe6f..64f5035 100644 (file)
                /* 0: 100.0M    1: 166.7M    2: 200.0M    3: 250.0M */
                /* 4: 333.3M    5: 400.0M    6: 500.0M    7: 666.7M */
        };
+
+       spicc:@c1108d80{
+               compatible = "amlogic, spicc";
+               status = "disabled";
+               reg = <0x0 0xc1108d80 0x0 0x28>;
+               clocks = <&clkc CLKID_SPICC>;
+               clock-names = "spicc_clk";
+               interrupts = <0 81 1>;
+               device_id = <0>;
+       };
+
        i2c_ao: i2c@c8100500{ /*I2C-AO*/
                        compatible = "amlogic, meson-i2c";
                        dev_name = "i2c-AO";
                        };
                };
 
-               spicc_pins_z11z12z13: spicc_pins_z11z12z13 {
-                       mux {
-                               groups = "spi_sclk_0",
-                                       "spi_miso_0",
-                                       "spi_mosi_0";
-                               function = "spi";
-                       };
-               };
-
                spicc_pulldown_z11z12z13: spicc_pulldown_z11z12z13 {
                        mux {
                                groups = "spi_sclk_0",
                        };
                };
 
-               spicc_pins_x8x9x11: spicc_pins_x8x9x11 {
-                       mux {
-                               groups = "spi_sclk_1",
-                                       "spi_miso_1",
-                                       "spi_mosi_1";
-                               function = "spi";
-                       };
-               };
-
                spicc_pulldown_x8x9x11: spicc_pulldown_x8x9x11 {
                        mux {
                                groups = "spi_sclk_1",
index 8c6e3af..aba75d8 100644 (file)
@@ -170,6 +170,7 @@ CONFIG_AMLOGIC_USB2PHY=y
 CONFIG_AMLOGIC_USB3PHY=y
 CONFIG_AMLOGIC_I2C=y
 CONFIG_AMLOGIC_I2C_MASTER=y
+CONFIG_AMLOGIC_SPICC_MASTER=y
 CONFIG_AMLOGIC_SEC=y
 CONFIG_AMLOGIC_CPU_VERSION=y
 CONFIG_AMLOGIC_MESON64_VERSION=y
@@ -289,6 +290,10 @@ CONFIG_INPUT_UINPUT=y
 # CONFIG_DEVMEM is not set
 # CONFIG_DEVKMEM is not set
 CONFIG_HW_RANDOM=y
+CONFIG_SPI=y
+CONFIG_SPI_DEBUG=y
+CONFIG_SPI_GPIO=y
+CONFIG_SPI_SPIDEV=y
 CONFIG_THERMAL=y
 CONFIG_THERMAL_WRITABLE_TRIPS=y
 CONFIG_THERMAL_DEFAULT_GOV_POWER_ALLOCATOR=y
index 966cfb1..25aca80 100644 (file)
@@ -21,6 +21,8 @@ source "drivers/amlogic/usb/Kconfig"
 
 source "drivers/amlogic/i2c/Kconfig"
 
+source "drivers/amlogic/spicc/Kconfig"
+
 source "drivers/amlogic/secmon/Kconfig"
 
 source "drivers/amlogic/cpu_version/Kconfig"
index 92ed400..cc07d57 100644 (file)
@@ -21,6 +21,8 @@ obj-$(CONFIG_AMLOGIC_CPU_VERSION)       += cpu_version/
 
 obj-$(CONFIG_AMLOGIC_I2C)              += i2c/
 
+obj-$(CONFIG_AMLOGIC_SPICC_MASTER) += spicc/
+
 obj-$(CONFIG_AMLOGIC_CPU_INFO)          += cpu_info/
 
 obj-$(CONFIG_AMLOGIC_MHU_MBOX)         += mailbox/
diff --git a/drivers/amlogic/spicc/Kconfig b/drivers/amlogic/spicc/Kconfig
new file mode 100644 (file)
index 0000000..1099cbc
--- /dev/null
@@ -0,0 +1,15 @@
+#
+# Sensor device configuration
+#
+
+menu "AMLOGIC SPI Hardware bus support"
+
+
+config AMLOGIC_SPICC_MASTER
+       tristate "AMLOGIC SPICC MASTER SUPPORT"
+       default n
+       help
+               This is amlogic SPICC hardware bus master.
+               This driver can also be built as a module.
+
+endmenu
diff --git a/drivers/amlogic/spicc/Makefile b/drivers/amlogic/spicc/Makefile
new file mode 100644 (file)
index 0000000..f622507
--- /dev/null
@@ -0,0 +1,5 @@
+#
+# Makefile for the amlogic SPI bus drivers.
+#
+
+obj-$(CONFIG_AMLOGIC_SPICC_MASTER)     += spicc.o
diff --git a/drivers/amlogic/spicc/spicc.c b/drivers/amlogic/spicc/spicc.c
new file mode 100644 (file)
index 0000000..281d28c
--- /dev/null
@@ -0,0 +1,1122 @@
+/*
+ * drivers/amlogic/spicc/spicc.c
+ *
+ * Copyright (C) 2017 Amlogic, 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.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/spi/spi.h>
+#include <linux/of_address.h>
+#include <linux/amlogic/aml_gpio_consumer.h>
+#include <linux/of.h>
+#include <linux/reset.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
+#include <linux/of_irq.h>
+#include "spicc.h"
+
+/* #define CONFIG_SPICC_LOG */
+#ifdef CONFIG_SPICC_LOG
+struct my_log {
+       struct timeval tv;
+       unsigned int irq_data;
+       unsigned int reg_val[13];
+       unsigned int param[8];
+       unsigned int param_count;
+       const char *comment;
+};
+#endif
+
+/**
+ * struct spicc
+ * @lock: spinlock for SPICC controller.
+ * @msg_queue: link with the spi message list.
+ * @wq: work queque
+ * @work: work
+ * @master: spi master alloc for this driver.
+ * @spi: spi device on working.
+ * @regs: the start register address of this SPICC controller.
+ */
+struct spicc {
+       spinlock_t lock;
+       struct list_head msg_queue;
+       struct workqueue_struct *wq;
+       struct work_struct work;
+       struct spi_master *master;
+       struct spi_device *spi;
+       struct class cls;
+
+       int device_id;
+       struct reset_control *rst;
+       struct clk *clk;
+       void __iomem *regs;
+       struct pinctrl *pinctrl;
+       struct pinctrl_state *pullup;
+       struct pinctrl_state *pulldown;
+       int bits_per_word;
+       int mode;
+       int speed;
+       unsigned int dma_tx_threshold;
+       unsigned int dma_rx_threshold;
+       unsigned int dma_num_per_read_burst;
+       unsigned int dma_num_per_write_burst;
+       int irq;
+       struct completion completion;
+#define FLAG_DMA_EN 0
+#define FLAG_TEST_DATA_AUTO_INC 1
+#define FLAG_SSCTL 2
+#define FLAG_ENHANCE 3
+       unsigned int flags;
+       u8 test_data;
+       unsigned int delay_control;
+       unsigned int cs_delay;
+       int remain;
+       u8 *txp;
+       u8 *rxp;
+       int burst_len;
+#ifdef CONFIG_SPICC_LOG
+       struct my_log *log;
+       int log_size;
+       int log_count;
+#endif
+};
+
+#ifdef CONFIG_SPICC_LOG
+enum {
+       PROBE_BEGIN,
+       PROBE_END,
+       REQUEST_IRQ,
+       HAND_MSG_BEGIN,
+       HAND_MSG_END,
+       XFER_COMP_ISR,
+       XFER_COMP_POLLING,
+       DMA_BEGIN,
+       DMA_END,
+       PIO_BEGIN,
+       PIO_END,
+};
+
+static const char * const log_comment[] = {
+       "probe begin",
+       "probe end",
+       "request irq",
+       "handle msg begin",
+       "handle msg end",
+       "xfer complete isr",
+       "xfer complete polling",
+       "dma begin",
+       "dma end",
+       "pio begin",
+       "pio end"
+};
+
+static void spicc_log_init(struct spicc *spicc)
+{
+       spicc->log = 0;
+       spicc->log_count = 0;
+       if (spicc->log_size)
+               spicc->log = kzalloc(spicc->log_size *
+                       sizeof(struct my_log), GFP_KERNEL);
+       pr_info("spicc: log_size=%d, log=%p",
+                       spicc->log_size, spicc->log);
+}
+
+static void spicc_log_exit(struct spicc *spicc)
+{
+       spicc->log = 0;
+       spicc->log_count = 0;
+       spicc->log_size = 0;
+       kfree(spicc->log);
+}
+
+static void spicc_log(
+       struct spicc *spicc,
+       unsigned int *param,
+       int param_count,
+       int comment_id)
+{
+       struct my_log *log;
+       struct irq_desc *desc;
+       int i;
+
+       if (IS_ERR_OR_NULL(spicc->log))
+               return;
+       log = &spicc->log[spicc->log_count];
+       log->tv = ktime_to_timeval(ktime_get());
+       if (spicc->irq) {
+               desc = irq_to_desc(spicc->irq);
+               log->irq_data = desc->irq_data.state_use_accessors;
+       }
+       for (i = 0; i < ARRAY_SIZE(log->reg_val); i++)
+               log->reg_val[i] = readl(spicc->regs + ((i+2)<<2));
+
+       if (param) {
+               if (param_count > ARRAY_SIZE(log->param))
+                       param_count = ARRAY_SIZE(log->param);
+               for (i = 0; i < param_count; i++)
+                       log->param[i] = param[i];
+               log->param_count = param_count;
+       }
+       log->comment = log_comment[comment_id];
+
+       if (++spicc->log_count > spicc->log_size)
+               spicc->log_count = 0;
+}
+
+static void spicc_log_print(struct spicc *spicc)
+{
+       struct my_log *log;
+       int i, j;
+       unsigned int *p;
+
+       if (IS_ERR_OR_NULL(spicc->log))
+               return;
+       pr_info("log total:%d\n", spicc->log_count);
+       for (i = 0; i < spicc->log_count; i++) {
+               log = &spicc->log[i];
+               pr_info("[%6u.%6u]spicc-%d: %s\n",
+                               (unsigned int)log->tv.tv_sec,
+                               (unsigned int)log->tv.tv_usec,
+                               i, log->comment);
+               pr_info("irq_data=0x%x\n", log->irq_data);
+               p = log->reg_val;
+               for (j = 0; j < ARRAY_SIZE(log->reg_val); j++)
+                       if (*p)
+                               pr_info("reg[%2d]=0x%x\n", j+2, *p++);
+               p = log->param;
+               for (j = 0; j < log->param_count; j++)
+                       pr_info("param[%d]=0x%x\n", j, *p++);
+       }
+}
+#else
+#define spicc_log_init(spicc)
+#define spicc_log_exit(spicc)
+#define spicc_log(spicc, param, param_count, comment_id)
+#define spicc_log_print(spicc)
+#endif
+
+/* Note this is chip_select enable or disable */
+void spicc_chip_select(struct spi_device *spi, bool select)
+{
+       struct spicc *spicc;
+       unsigned int level;
+
+       spicc = spi_master_get_devdata(spi->master);
+       level = (spi->mode & SPI_CS_HIGH) ? select : !select;
+
+       if (spi->mode & SPI_NO_CS)
+               return;
+       if (spi->cs_gpio >= 0) {
+               gpio_direction_output(spi->cs_gpio, level);
+       } else if (select) {
+               setb(spicc->regs, CON_CHIP_SELECT, spi->chip_select);
+               setb(spicc->regs, CON_SS_POL, level);
+       }
+}
+EXPORT_SYMBOL(spicc_chip_select);
+
+static inline bool spicc_get_flag(struct spicc *spicc, unsigned int flag)
+{
+       bool ret;
+
+       ret = (spicc->flags >> flag) & 1;
+       return ret;
+}
+
+static inline void spicc_set_flag(
+               struct spicc *spicc,
+               unsigned int flag,
+               bool value)
+{
+       if (value)
+               spicc->flags |= 1<<flag;
+       else
+               spicc->flags &= ~(1<<flag);
+}
+
+static inline void spicc_set_bit_width(struct spicc *spicc, u8 bw)
+{
+       setb(spicc->regs, CON_BITS_PER_WORD, bw-1);
+       spicc->bits_per_word = bw;
+}
+
+static void spicc_set_mode(struct spicc *spicc, u8 mode)
+{
+       bool cpol = (mode & SPI_CPOL) ? 1:0;
+       bool cpha = (mode & SPI_CPHA) ? 1:0;
+
+       if (mode == spicc->mode)
+               return;
+
+       spicc->mode = mode;
+       if (!spicc_get_flag(spicc, FLAG_ENHANCE)) {
+               if (cpol && !IS_ERR_OR_NULL(spicc->pullup))
+                       pinctrl_select_state(spicc->pinctrl, spicc->pullup);
+               else if (!cpol && !IS_ERR_OR_NULL(spicc->pulldown))
+                       pinctrl_select_state(spicc->pinctrl, spicc->pulldown);
+       }
+       setb(spicc->regs, CON_CLK_PHA, cpha);
+       setb(spicc->regs, CON_CLK_POL, cpol);
+}
+
+static void spicc_set_clk(struct spicc *spicc, int speed)
+{
+       unsigned int sys_clk_rate;
+       unsigned int div, mid_speed;
+
+       if (!speed)
+               clk_disable_unprepare(spicc->clk);
+       else
+               clk_prepare_enable(spicc->clk);
+       if (!speed || (speed == spicc->speed))
+               return;
+
+       spicc->speed = speed;
+       sys_clk_rate = clk_get_rate(spicc->clk);
+
+       if (spicc_get_flag(spicc, FLAG_ENHANCE)) {
+               div = (sys_clk_rate/speed)-1;
+               setb(spicc->regs, ENHANCE_CLK_DIV, div);
+               setb(spicc->regs, ENHANCE_CLK_DIV_SELECT, 1);
+       } else {
+               /* speed = sys_clk_rate / 2^(conreg.data_rate_div+2) */
+               mid_speed = (sys_clk_rate * 3) >> 4;
+               for (div = 0; div < 7; div++) {
+                       if (speed >= mid_speed)
+                               break;
+                       mid_speed >>= 1;
+               }
+               setb(spicc->regs, CON_DATA_RATE_DIV, div);
+       }
+}
+
+static inline void spicc_set_txfifo(struct spicc *spicc, u32 dat)
+{
+       writel(dat, spicc->regs + SPICC_REG_TXDATA);
+}
+
+static inline u32 spicc_get_rxfifo(struct spicc *spicc)
+{
+       return readl(spicc->regs + SPICC_REG_RXDATA);
+}
+
+static inline void spicc_enable(struct spicc *spicc, bool en)
+{
+       setb(spicc->regs, CON_ENABLE, en);
+}
+
+static void dma_one_burst(struct spicc *spicc)
+{
+       void __iomem *mem_base = spicc->regs;
+
+       setb(mem_base, STA_XFER_COM, 1);
+       if (spicc->remain > 0) {
+               spicc->burst_len = min_t(size_t, spicc->remain, BURST_LEN_MAX);
+               setb(mem_base, CON_BURST_LEN, spicc->burst_len - 1);
+               setb(mem_base, CON_XCH, 1);
+               spicc->remain -= spicc->burst_len;
+       }
+}
+
+static void pio_one_burst_recv(struct spicc *spicc)
+{
+       int bytes_per_word;
+       unsigned int dat;
+       int i, j;
+
+       bytes_per_word = ((spicc->bits_per_word - 1)>>3) + 1;
+       for (i = 0; i < spicc->burst_len; i++) {
+               dat = spicc_get_rxfifo(spicc);
+               if (spicc->rxp) {
+                       for (j = 0; j < bytes_per_word; j++) {
+                               *spicc->rxp++ = dat & 0xff;
+                               dat >>= 8;
+                       }
+               }
+       }
+}
+
+static void pio_one_burst_send(struct spicc *spicc)
+{
+       void __iomem *mem_base = spicc->regs;
+       int bytes_per_word;
+       unsigned int dat;
+       int i, j;
+
+       setb(mem_base, STA_XFER_COM, 1);
+       bytes_per_word = ((spicc->bits_per_word - 1)>>3) + 1;
+       if (spicc->remain > 0) {
+               spicc->burst_len = min_t(size_t, spicc->remain,
+                               SPICC_FIFO_SIZE);
+               for (i = 0; i < spicc->burst_len; i++) {
+                       dat = 0;
+                       if (spicc->txp) {
+                               for (j = 0; j < bytes_per_word; j++) {
+                                       dat <<= 8;
+                                       dat += *spicc->txp++;
+                               }
+                       }
+                       spicc_set_txfifo(spicc, dat);
+               }
+               setb(mem_base, CON_BURST_LEN, spicc->burst_len - 1);
+               setb(mem_base, CON_XCH, 1);
+               spicc->remain -= spicc->burst_len;
+       }
+}
+
+/**
+ * Return: same with wait_for_completion_interruptible_timeout
+ *   =0: timed out,
+ *   >0: completed, number of usec left till timeout.
+ */
+static int spicc_wait_complete(struct spicc *spicc, int us)
+{
+       void __iomem *mem_base = spicc->regs;
+       int i;
+
+       for (i = 0; i < us; i++) {
+               udelay(1);
+               if (getb(mem_base, STA_XFER_COM)) {
+                       setb(spicc->regs, STA_XFER_COM, 1); /* set 1 to clear */
+                       break;
+               }
+       }
+       return us - i;
+}
+
+static irqreturn_t spicc_xfer_complete_isr(int irq, void *dev_id)
+{
+       struct spicc *spicc = (struct spicc *)dev_id;
+       unsigned long flags;
+
+       spin_lock_irqsave(&spicc->lock, flags);
+       spicc_wait_complete(spicc, 100);
+       spicc_log(spicc, &spicc->remain, 1, XFER_COMP_ISR);
+       if (!spicc_get_flag(spicc, FLAG_DMA_EN))
+               pio_one_burst_recv(spicc);
+       if (!spicc->remain)
+               complete(&spicc->completion);
+       else if (spicc_get_flag(spicc, FLAG_DMA_EN))
+               dma_one_burst(spicc);
+       else
+               pio_one_burst_send(spicc);
+       spin_unlock_irqrestore(&spicc->lock, flags);
+       return IRQ_HANDLED;
+}
+
+#define INVALID_DMA_ADDRESS 0xffffffff
+/*
+ * For DMA, tx_buf/tx_dma have the same relationship as rx_buf/rx_dma:
+ *  - The buffer is either valid for CPU access, else NULL
+ *  - If the buffer is valid, so is its DMA address
+ *
+ * This driver manages the dma address unless message->is_dma_mapped.
+ */
+static int spicc_dma_map(struct spicc *spicc, struct spi_transfer *t)
+{
+       struct device *dev = spicc->master->dev.parent;
+       u8 buf[8], *p = (u8 *)t->tx_buf;
+       int i, len = 0;
+
+       t->tx_dma = t->rx_dma = INVALID_DMA_ADDRESS;
+       if (t->tx_buf) {
+               while (len < t->len) {
+                       memcpy(buf, p, 8);
+                       for (i = 0; i < 8; i++)
+                               *p++ = buf[7-i];
+                       len += 8;
+               }
+               t->tx_dma = dma_map_single(dev,
+                               (void *)t->tx_buf, t->len, DMA_TO_DEVICE);
+               if (dma_mapping_error(dev, t->tx_dma)) {
+                       dev_err(dev, "tx_dma map failed\n");
+                       return -ENOMEM;
+               }
+       }
+       if (t->rx_buf) {
+               t->rx_dma = dma_map_single(dev,
+                               t->rx_buf, t->len, DMA_FROM_DEVICE);
+               if (dma_mapping_error(dev, t->rx_dma)) {
+                       if (t->tx_buf) {
+                               dev_err(dev, "rx_dma map failed\n");
+                               dma_unmap_single(dev, t->tx_dma, t->len,
+                                               DMA_TO_DEVICE);
+                       }
+                       return -ENOMEM;
+               }
+       }
+       return 0;
+}
+
+static void spicc_dma_unmap(struct spicc *spicc, struct spi_transfer *t)
+{
+       struct device *dev = spicc->master->dev.parent;
+       u8 buf[8], *p = (u8 *)t->rx_buf;
+       int i, len = 0;
+
+       if (t->tx_dma != INVALID_DMA_ADDRESS)
+               dma_unmap_single(dev, t->tx_dma, t->len, DMA_TO_DEVICE);
+       if (t->rx_dma != INVALID_DMA_ADDRESS) {
+               dma_unmap_single(dev, t->rx_dma, t->len, DMA_FROM_DEVICE);
+               while (len < t->len) {
+                       memcpy(buf, p, 8);
+                       for (i = 0; i < 8; i++)
+                               *p++ = buf[7-i];
+                       len += 8;
+               }
+       }
+}
+
+/**
+ * Return:
+ *   =0: xfer success,
+ *   -ETIMEDOUT: timeout.
+ */
+static int spicc_dma_xfer(struct spicc *spicc, struct spi_transfer *t)
+{
+       void __iomem *mem_base = spicc->regs;
+       int ret;
+
+       setb(mem_base, RX_FIFO_RESET, 1);
+       setb(mem_base, TX_FIFO_RESET, 1);
+       setb(mem_base, CON_XCH, 0);
+       spicc_set_bit_width(spicc, 64);
+       if (t->tx_dma != INVALID_DMA_ADDRESS)
+               writel(t->tx_dma, mem_base + SPICC_REG_DRADDR);
+       if (t->rx_dma != INVALID_DMA_ADDRESS)
+               writel(t->rx_dma, mem_base + SPICC_REG_DWADDR);
+       setb(mem_base, DMA_EN, 1);
+       spicc->remain = ((t->len - 1) >> 3) + 1;
+       spicc->burst_len = 0;
+       spicc_log(spicc, &spicc->remain, 1, DMA_BEGIN);
+       if (spicc->irq) {
+               setb(mem_base, INT_XFER_COM_EN, 1);
+               enable_irq(spicc->irq);
+               dma_one_burst(spicc);
+               ret = wait_for_completion_interruptible_timeout(
+                       &spicc->completion, msecs_to_jiffies(2000));
+               disable_irq_nosync(spicc->irq);
+               setb(mem_base, INT_XFER_COM_EN, 0);
+       } else {
+               while (spicc->remain) {
+                       dma_one_burst(spicc);
+                       ret = spicc_wait_complete(spicc,
+                                       spicc->burst_len << 13);
+                       spicc_log(spicc, &spicc->remain, 1, XFER_COMP_POLLING);
+                       if (!ret)
+                               break;
+               }
+       }
+       setb(mem_base, DMA_EN, 0);
+       ret = ret ? 0 : -ETIMEDOUT;
+       spicc_log(spicc, &ret, 1, DMA_END);
+       return ret;
+}
+
+/**
+ * Return:
+ *   =0: xfer success,
+ *   -ETIMEDOUT: timeout.
+ */
+static int spicc_hw_xfer(struct spicc *spicc, u8 *txp, u8 *rxp, int len)
+{
+       void __iomem *mem_base = spicc->regs;
+       int bytes_per_word;
+       int ret;
+
+       setb(mem_base, RX_FIFO_RESET, 1);
+       setb(mem_base, TX_FIFO_RESET, 1);
+       setb(mem_base, CON_XCH, 0);
+       bytes_per_word = ((spicc->bits_per_word - 1)>>3) + 1;
+       spicc->remain = len / bytes_per_word;
+       spicc->txp = txp;
+       spicc->rxp = rxp;
+       spicc_log(spicc, &spicc->remain, 1, PIO_BEGIN);
+       if (spicc->irq) {
+               setb(mem_base, INT_XFER_COM_EN, 1);
+               enable_irq(spicc->irq);
+               pio_one_burst_send(spicc);
+               ret = wait_for_completion_interruptible_timeout(
+                       &spicc->completion, msecs_to_jiffies(2000));
+               disable_irq_nosync(spicc->irq);
+               setb(mem_base, INT_XFER_COM_EN, 0);
+       } else {
+               while (spicc->remain) {
+                       pio_one_burst_send(spicc);
+                       ret = spicc_wait_complete(spicc,
+                                       spicc->burst_len << 10);
+                       spicc_log(spicc, &spicc->remain, 1, XFER_COMP_POLLING);
+                       if (!ret)
+                               break;
+                       pio_one_burst_recv(spicc);
+               }
+       }
+       ret = ret ? 0 : -ETIMEDOUT;
+       spicc_log(spicc, &ret, 1, PIO_END);
+       return ret;
+}
+
+static void spicc_hw_init(struct spicc *spicc)
+{
+       void __iomem *mem_base = spicc->regs;
+
+       spicc->bits_per_word = -1;
+       spicc->mode = -1;
+       spicc->speed = -1;
+       spicc_enable(spicc, 0);
+       setb(mem_base, CLK_FREE_EN, 1);
+       setb(mem_base, CON_MODE, 1); /* 0-slave, 1-master */
+       setb(mem_base, CON_XCH, 0);
+       setb(mem_base, CON_SMC, 0);
+       setb(mem_base, CON_SS_CTL, spicc_get_flag(spicc, FLAG_SSCTL));
+       setb(mem_base, CON_DRCTL, 0);
+       spicc_enable(spicc, 1);
+       setb(mem_base, DELAY_CONTROL, spicc->delay_control);
+       writel((0x00<<26 |
+                                       0x00<<20 |
+                                       0x0<<19 |
+                                       spicc->dma_num_per_write_burst<<15 |
+                                       spicc->dma_num_per_read_burst<<11 |
+                                       spicc->dma_rx_threshold<<6 |
+                                       spicc->dma_tx_threshold<<1 |
+                                       0x0<<0),
+                                       mem_base + SPICC_REG_DMA);
+       spicc_set_bit_width(spicc, SPICC_DEFAULT_BIT_WIDTH);
+       spicc_set_mode(spicc, SPI_MODE_0);
+       spicc_set_clk(spicc, SPICC_DEFAULT_SPEED_HZ);
+       if (spicc_get_flag(spicc, FLAG_ENHANCE)) {
+               setb(spicc->regs, MOSI_OEN, 1);
+               setb(spicc->regs, CLK_OEN, 1);
+               setb(mem_base, CS_OEN, 1);
+               setb(mem_base, CS_DELAY, spicc->cs_delay);
+               setb(mem_base, CS_DELAY_EN, 1);
+       }
+       /* spicc_enable(spicc, 0); */
+}
+
+
+int dirspi_xfer(struct spi_device *spi, u8 *tx_buf, u8 *rx_buf, int len)
+{
+       struct spicc *spicc;
+
+       spicc = spi_master_get_devdata(spi->master);
+       return spicc_hw_xfer(spicc, tx_buf, rx_buf, len);
+}
+EXPORT_SYMBOL(dirspi_xfer);
+
+int dirspi_write(struct spi_device *spi, u8 *buf, int len)
+{
+       struct spicc *spicc;
+
+       spicc = spi_master_get_devdata(spi->master);
+       return spicc_hw_xfer(spicc, buf, 0, len);
+}
+EXPORT_SYMBOL(dirspi_write);
+
+int dirspi_read(struct spi_device *spi, u8 *buf, int len)
+{
+       struct spicc *spicc;
+
+       spicc = spi_master_get_devdata(spi->master);
+       return spicc_hw_xfer(spicc, 0, buf, len);
+}
+EXPORT_SYMBOL(dirspi_read);
+
+void dirspi_start(struct spi_device *spi)
+{
+       spicc_chip_select(spi, 1);
+}
+EXPORT_SYMBOL(dirspi_start);
+
+void dirspi_stop(struct spi_device *spi)
+{
+       spicc_chip_select(spi, 0);
+}
+EXPORT_SYMBOL(dirspi_stop);
+
+
+/* setting clock and pinmux here */
+static int spicc_setup(struct spi_device *spi)
+{
+       struct spicc *spicc;
+
+       spicc = spi_master_get_devdata(spi->master);
+       if (spi->cs_gpio >= 0)
+               gpio_request(spi->cs_gpio, "spicc");
+       dev_info(&spi->dev, "chip_select=%d cs_gpio=%d mode=%d speed=%d\n",
+                       spi->chip_select, spi->cs_gpio,
+                       spi->mode, spi->max_speed_hz);
+       spicc_chip_select(spi, 0);
+       return 0;
+}
+
+static void spicc_cleanup(struct spi_device *spi)
+{
+       spicc_chip_select(spi, 0);
+       if (spi->cs_gpio >= 0)
+               gpio_free(spi->cs_gpio);
+}
+
+static void spicc_handle_one_msg(struct spicc *spicc, struct spi_message *m)
+{
+       struct spi_device *spi = m->spi;
+       struct spi_transfer *t;
+       int ret = 0;
+       bool cs_changed = 0;
+
+       spicc_log(spicc, 0, 0, HAND_MSG_BEGIN);
+       /* spicc_enable(spicc, 1); */
+       if (spi) {
+               spicc_set_bit_width(spicc,
+                               spi->bits_per_word ? : SPICC_DEFAULT_BIT_WIDTH);
+               spicc_set_clk(spicc,
+                               spi->max_speed_hz ? : SPICC_DEFAULT_SPEED_HZ);
+               spicc_set_mode(spicc, spi->mode);
+               spicc_chip_select(spi, 1);
+       }
+       list_for_each_entry(t, &m->transfers, transfer_list) {
+               if (t->bits_per_word)
+                       spicc_set_bit_width(spicc, t->bits_per_word);
+               if (t->speed_hz)
+                       spicc_set_clk(spicc, t->speed_hz);
+               if (spi && cs_changed) {
+                       spicc_chip_select(spi, 1);
+                       cs_changed = 0;
+               }
+               if (t->delay_usecs >> 10)
+                       udelay(t->delay_usecs >> 10);
+
+               if (spicc_get_flag(spicc, FLAG_DMA_EN)) {
+                       if (!m->is_dma_mapped)
+                               spicc_dma_map(spicc, t);
+                       ret = spicc_dma_xfer(spicc, t);
+                       if (!m->is_dma_mapped)
+                               spicc_dma_unmap(spicc, t);
+               } else {
+                       ret = spicc_hw_xfer(spicc, (u8 *)t->tx_buf,
+                               (u8 *)t->rx_buf, t->len);
+               }
+               if (ret)
+                       goto spicc_handle_end;
+               m->actual_length += t->len;
+
+               /*
+                * to delay after this transfer before
+                * (optionally) changing the chipselect status.
+                */
+               if (t->delay_usecs & 0x3ff)
+                       udelay(t->delay_usecs & 0x3ff);
+               if (spi && t->cs_change) {
+                       spicc_chip_select(spi, 0);
+                       cs_changed = 1;
+               }
+       }
+
+spicc_handle_end:
+       if (spi)
+               spicc_chip_select(spi, 0);
+       /* spicc_enable(spicc, 0); */
+       m->status = ret;
+       if (m->context)
+               m->complete(m->context);
+       spicc_log(spicc, 0, 0, HAND_MSG_END);
+}
+
+static int spicc_transfer(struct spi_device *spi, struct spi_message *m)
+{
+       struct spicc *spicc = spi_master_get_devdata(spi->master);
+       unsigned long flags;
+
+       m->actual_length = 0;
+       m->status = -EINPROGRESS;
+
+       spin_lock_irqsave(&spicc->lock, flags);
+       list_add_tail(&m->queue, &spicc->msg_queue);
+       queue_work(spicc->wq, &spicc->work);
+       spin_unlock_irqrestore(&spicc->lock, flags);
+
+       return 0;
+}
+
+static void spicc_work(struct work_struct *work)
+{
+       struct spicc *spicc = container_of(work, struct spicc, work);
+       struct spi_message *m;
+       unsigned long flags;
+
+       spin_lock_irqsave(&spicc->lock, flags);
+       while (!list_empty(&spicc->msg_queue)) {
+               m = container_of(spicc->msg_queue.next,
+                               struct spi_message, queue);
+               list_del_init(&m->queue);
+               spin_unlock_irqrestore(&spicc->lock, flags);
+               spicc_handle_one_msg(spicc, m);
+               spin_lock_irqsave(&spicc->lock, flags);
+       }
+       spin_unlock_irqrestore(&spicc->lock, flags);
+}
+
+static ssize_t show_setting(struct class *class,
+       struct class_attribute *attr, char *buf)
+{
+       int ret = 0;
+       struct spicc *spicc = container_of(class, struct spicc, cls);
+
+       if (!strcmp(attr->attr.name, "speed"))
+               ret = sprintf(buf, "speed=%d\n", spicc->speed);
+       else if (!strcmp(attr->attr.name, "mode"))
+               ret = sprintf(buf, "mode=%d\n", spicc->mode);
+       else if (!strcmp(attr->attr.name, "bit_width"))
+               ret = sprintf(buf, "bit_width=%d\n", spicc->bits_per_word);
+       else if (!strcmp(attr->attr.name, "flags"))
+               ret = sprintf(buf, "flags=0x%x\n", spicc->flags);
+       else if (!strcmp(attr->attr.name, "test_data"))
+               ret = sprintf(buf, "test_data=0x%x\n", spicc->test_data);
+       else if (!strcmp(attr->attr.name, "help")) {
+               pr_info("SPI device test help\n");
+               pr_info("echo cs_gpio speed mode bits_per_word num");
+               pr_info("[wbyte1 wbyte2...] >test\n");
+       }
+#ifdef CONFIG_SPICC_LOG
+       else if (!strcmp(attr->attr.name, "log")) {
+               spicc_log_print(spicc);
+               spicc->log_count = 0;
+       }
+#endif
+       return ret;
+}
+
+
+static ssize_t store_setting(
+               struct class *class,
+               struct class_attribute *attr,
+               const char *buf, size_t count)
+{
+       long value;
+       struct spicc *spicc = container_of(class, struct spicc, cls);
+
+       if (kstrtol(buf, 0, &value))
+               return -EINVAL;
+       if (!strcmp(attr->attr.name, "speed"))
+               spicc_set_clk(spicc, value);
+       else if (!strcmp(attr->attr.name, "mode"))
+               spicc_set_mode(spicc, value);
+       else if (!strcmp(attr->attr.name, "bit_width"))
+               spicc_set_bit_width(spicc, value);
+       else if (!strcmp(attr->attr.name, "flags"))
+               spicc->flags = value;
+       else if (!strcmp(attr->attr.name, "test_data"))
+               spicc->test_data = (u8)(value & 0xff);
+       return count;
+}
+
+static ssize_t store_test(
+               struct class *class,
+               struct class_attribute *attr,
+               const char *buf, size_t count)
+{
+       struct spicc *spicc = container_of(class, struct spicc, cls);
+       struct device *dev = spicc->master->dev.parent;
+       unsigned int cs_gpio, speed, mode, bits_per_word, num;
+       u8 *tx_buf, *rx_buf;
+       unsigned long value;
+       char *kstr, *str_temp, *token;
+       int i;
+       struct spi_transfer t;
+       struct spi_message m;
+
+       if (sscanf(buf, "%d%d%d%d%d", &cs_gpio, &speed,
+                       &mode, &bits_per_word, &num) != 5) {
+               dev_err(dev, "error test data\n");
+               return count;
+       }
+       kstr = kstrdup(buf, GFP_KERNEL);
+       tx_buf = kzalloc(num, GFP_KERNEL | GFP_DMA);
+       rx_buf = kzalloc(num, GFP_KERNEL | GFP_DMA);
+       if (IS_ERR_OR_NULL(kstr) ||
+                       IS_ERR_OR_NULL(tx_buf) ||
+                       IS_ERR_OR_NULL(rx_buf)) {
+               dev_err(dev, "failed to alloc tx rx buffer\n");
+               goto test_end;
+       }
+
+       str_temp = kstr;
+       /* skip pass over "cs_gpio speed mode bits_per_word num" */
+       for (i = 0; i < 5; i++)
+               strsep(&str_temp, ", ");
+       for (i = 0; i < num; i++) {
+               token = strsep(&str_temp, ", ");
+               if (!token || kstrtoul(token, 16, &value))
+                       break;
+               tx_buf[i] = (u8)(value & 0xff);
+       }
+       for (; i < num; i++) {
+               tx_buf[i] = spicc->test_data;
+               if (spicc_get_flag(spicc, FLAG_TEST_DATA_AUTO_INC))
+                       spicc->test_data++;
+       }
+
+       spi_message_init(&m);
+       m.spi = spi_alloc_device(spicc->master);
+       if (cs_gpio < 1000)
+               m.spi->cs_gpio = cs_gpio;
+       else {
+               m.spi->cs_gpio = -ENOENT;
+               m.spi->chip_select = cs_gpio - 1000;
+       }
+       m.spi->max_speed_hz = speed;
+       m.spi->mode = mode;
+       m.spi->bits_per_word = bits_per_word;
+       spicc_setup(m.spi);
+       memset(&t, 0, sizeof(t));
+       t.tx_buf = (void *)tx_buf;
+       t.rx_buf = (void *)rx_buf;
+       t.len = num;
+       spi_message_add_tail(&t, &m);
+       spicc_handle_one_msg(spicc, &m);
+       spi_dev_put(m.spi);
+
+       dev_info(dev, "read back data ok\n");
+       for (i = 0; i < min_t(size_t, 32, num); i++)
+               dev_info(dev, "[%d]: 0x%2x, 0x%2x\n", i, tx_buf[i], rx_buf[i]);
+test_end:
+       kfree(kstr);
+       kfree(tx_buf);
+       kfree(rx_buf);
+       return count;
+}
+
+static struct class_attribute spicc_class_attrs[] = {
+               __ATTR(test, 0200, NULL, store_test),
+               __ATTR(test_data, 0644, show_setting, store_setting),
+               __ATTR(speed, 0644, show_setting, store_setting),
+               __ATTR(mode, 0644, show_setting, store_setting),
+               __ATTR(bit_width, 0644, show_setting, store_setting),
+               __ATTR(flags, 0644, show_setting, store_setting),
+               __ATTR(help, 0444, show_setting, NULL),
+#ifdef CONFIG_SPICC_LOG
+               __ATTR(log, 0444, show_setting, NULL),
+#endif
+               __ATTR_NULL
+};
+
+
+static int of_spicc_get_data(
+               struct spicc *spicc,
+               struct platform_device *pdev)
+{
+       struct device_node *np = pdev->dev.of_node;
+       struct resource *res;
+       int err;
+       unsigned int value;
+
+       err = of_property_read_u32(np, "device_id", &spicc->device_id);
+       if (err) {
+               dev_err(&pdev->dev, "get device_id fail\n");
+               return err;
+       }
+
+       err = of_property_read_u32(np, "dma_en", &value);
+       spicc_set_flag(spicc, FLAG_DMA_EN, err ? 0 : (!!value));
+       dev_info(&pdev->dev, "dma_en=%d\n", spicc_get_flag(spicc, FLAG_DMA_EN));
+       err = of_property_read_u32(np, "dma_tx_threshold", &value);
+       spicc->dma_tx_threshold = err ? 3 : value;
+       err = of_property_read_u32(np, "dma_rx_threshold", &value);
+       spicc->dma_rx_threshold = err ? 3 : value;
+       err = of_property_read_u32(np, "dma_num_per_read_burst", &value);
+       spicc->dma_num_per_read_burst = err ? 3 : value;
+       err = of_property_read_u32(np, "dma_num_per_write_burst", &value);
+       spicc->dma_num_per_write_burst = err ? 3 : value;
+       spicc->irq = irq_of_parse_and_map(np, 0);
+       dev_info(&pdev->dev, "irq = 0x%x\n", spicc->irq);
+       err = of_property_read_u32(np, "delay_control", &value);
+       spicc->delay_control = err ? 0x15 : value;
+       dev_info(&pdev->dev, "delay_control=%d\n", spicc->delay_control);
+       err = of_property_read_u32(np, "enhance", &value);
+       spicc_set_flag(spicc, FLAG_ENHANCE, err ? 0 : (!!value));
+       dev_info(&pdev->dev, "enhance=%d\n",
+                       spicc_get_flag(spicc, FLAG_ENHANCE));
+       err = of_property_read_u32(np, "cs_delay", &value);
+       spicc->cs_delay = err ? 0 : value;
+       err = of_property_read_u32(np, "ssctl", &value);
+       spicc_set_flag(spicc, FLAG_SSCTL, err ? 0 : (!!value));
+#ifdef CONFIG_SPICC_LOG
+       err = of_property_read_u32(np, "log_size", &value);
+       spicc->log_size = err ? 0 : value;
+#endif
+
+       spicc->pinctrl = devm_pinctrl_get(&pdev->dev);
+       if (IS_ERR_OR_NULL(spicc->pinctrl)) {
+               dev_err(&pdev->dev, "get pinctrl fail\n");
+               return PTR_ERR(spicc->pinctrl);
+       }
+       spicc->pullup = pinctrl_lookup_state(spicc->pinctrl, "default");
+       if (!IS_ERR_OR_NULL(spicc->pullup))
+               pinctrl_select_state(spicc->pinctrl, spicc->pullup);
+       spicc->pullup = pinctrl_lookup_state(
+                       spicc->pinctrl, "spicc_pullup");
+       spicc->pulldown = pinctrl_lookup_state(
+                       spicc->pinctrl, "spicc_pulldown");
+
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       spicc->regs = devm_ioremap_resource(&pdev->dev, res);
+       if (IS_ERR_OR_NULL(spicc->regs)) {
+               dev_err(&pdev->dev, "get resource fail\n");
+               return PTR_ERR(spicc->regs);
+       }
+
+       spicc->clk = devm_clk_get(&pdev->dev, "spicc_clk");
+       if (IS_ERR_OR_NULL(spicc->clk)) {
+               dev_err(&pdev->dev, "get clk fail\n");
+               return PTR_ERR(spicc->clk);
+       }
+       return 0;
+}
+
+static int spicc_probe(struct platform_device *pdev)
+{
+       struct spi_master *master;
+       struct spicc *spicc;
+       int i, ret;
+
+       WARN_ON(!pdev->dev.of_node);
+       master = spi_alloc_master(&pdev->dev, sizeof(*spicc));
+       if (IS_ERR_OR_NULL(master)) {
+               dev_err(&pdev->dev, "allocate spi master failed!\n");
+               return -ENOMEM;
+       }
+       spicc = spi_master_get_devdata(master);
+       spicc->master = master;
+       dev_set_drvdata(&pdev->dev, spicc);
+       ret = of_spicc_get_data(spicc, pdev);
+       if (ret) {
+               dev_err(&pdev->dev, "of error=%d\n", ret);
+               goto err;
+       }
+       spicc_hw_init(spicc);
+       spicc_log_init(spicc);
+       spicc_log(spicc, 0, 0, PROBE_BEGIN);
+
+       master->dev.of_node = pdev->dev.of_node;
+       master->bus_num = spicc->device_id;
+       master->setup = spicc_setup;
+       master->transfer = spicc_transfer;
+       master->cleanup = spicc_cleanup;
+       master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;
+       ret = spi_register_master(master);
+       if (ret < 0) {
+               dev_err(&pdev->dev, "register spi master failed! (%d)\n", ret);
+               goto err;
+       }
+
+       INIT_WORK(&spicc->work, spicc_work);
+       spicc->wq = create_singlethread_workqueue(dev_name(master->dev.parent));
+       if (spicc->wq == NULL) {
+               ret = -EBUSY;
+               goto err;
+       }
+       spin_lock_init(&spicc->lock);
+       INIT_LIST_HEAD(&spicc->msg_queue);
+
+       init_completion(&spicc->completion);
+       if (spicc->irq) {
+               if (request_irq(spicc->irq, spicc_xfer_complete_isr,
+                               IRQF_SHARED, "spicc", spicc)) {
+                       dev_err(&pdev->dev, "master %d request irq(%d) failed!\n",
+                                       master->bus_num, spicc->irq);
+                       spicc->irq = 0;
+               } else
+                       disable_irq_nosync(spicc->irq);
+       }
+       spicc_log(spicc, &spicc->irq, 1, REQUEST_IRQ);
+
+       /*setup class*/
+       spicc->cls.name = kzalloc(10, GFP_KERNEL);
+       sprintf((char *)spicc->cls.name, "spicc%d", master->bus_num);
+       spicc->cls.class_attrs = spicc_class_attrs;
+       ret = class_register(&spicc->cls);
+       if (ret < 0)
+               dev_err(&pdev->dev, "register class failed! (%d)\n", ret);
+
+       dev_info(&pdev->dev, "cs_num=%d\n", master->num_chipselect);
+       if (master->cs_gpios) {
+               for (i = 0; i < master->num_chipselect; i++)
+                       dev_info(&pdev->dev, "cs[%d]=%d\n", i,
+                                       master->cs_gpios[i]);
+       }
+       spicc_log(spicc, 0, 0, PROBE_END);
+       return ret;
+err:
+       spi_master_put(master);
+       return ret;
+}
+
+static int spicc_remove(struct platform_device *pdev)
+{
+       struct spicc *spicc;
+
+       spicc = (struct spicc *)dev_get_drvdata(&pdev->dev);
+       spicc_log_exit(spicc);
+       spi_unregister_master(spicc->master);
+       destroy_workqueue(spicc->wq);
+       if (spicc->pinctrl)
+               devm_pinctrl_put(spicc->pinctrl);
+       return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id spicc_of_match[] = {
+       {       .compatible = "amlogic, spicc", },
+       {},
+};
+#else
+#define spicc_of_match NULL
+#endif
+
+static struct platform_driver spicc_driver = {
+       .probe = spicc_probe,
+       .remove = spicc_remove,
+       .driver = {
+                       .name = "spicc",
+                       .of_match_table = spicc_of_match,
+                       .owner = THIS_MODULE,
+               },
+};
+
+static int __init spicc_init(void)
+{
+       return platform_driver_register(&spicc_driver);
+}
+
+static void __exit spicc_exit(void)
+{
+       platform_driver_unregister(&spicc_driver);
+}
+
+subsys_initcall(spicc_init);
+module_exit(spicc_exit);
+
+MODULE_DESCRIPTION("Amlogic SPICC driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/amlogic/spicc/spicc.h b/drivers/amlogic/spicc/spicc.h
new file mode 100644 (file)
index 0000000..51e43d1
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+ * drivers/amlogic/spicc/spicc.h
+ *
+ * Copyright (C) 2017 Amlogic, 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.
+ *
+ */
+
+#ifndef __SPICC_H__
+#define __SPICC_H__
+
+#include <linux/amlogic/saradc.h>
+
+#define SPICC_FIFO_SIZE 16
+#define SPICC_DEFAULT_BIT_WIDTH 8
+#define SPICC_DEFAULT_SPEED_HZ 3000000
+
+#define SPICC_REG_RXDATA (0<<2)
+#define SPICC_REG_TXDATA (1<<2)
+#define SPICC_REG_CON    (2<<2)
+#define SPICC_REG_INT    (3<<2)
+#define SPICC_REG_DMA    (4<<2)
+#define SPICC_REG_STA    (5<<2)
+#define SPICC_REG_PERIOD (6<<2)
+#define SPICC_REG_TEST   (7<<2)
+#define SPICC_REG_DRADDR (8<<2)
+#define SPICC_REG_DWADDR (9<<2)
+#define SPICC_REG_LD_CNTL0 (10<<2)
+#define SPICC_REG_LD_CNTL1 (11<<2)
+#define SPICC_REG_LD_RADDR (12<<2)
+#define SPICC_REG_LD_WADDR (13<<2)
+#define SPICC_REG_ENHANCE_CNTL (14<<2)
+
+#define CON_ENABLE bits_desc(SPICC_REG_CON, 0, 1)
+#define CON_MODE bits_desc(SPICC_REG_CON, 1, 1)
+#define CON_XCH bits_desc(SPICC_REG_CON, 2, 1)
+#define CON_SMC bits_desc(SPICC_REG_CON, 3, 1)
+#define CON_CLK_POL bits_desc(SPICC_REG_CON, 4, 1)
+#define CON_CLK_PHA bits_desc(SPICC_REG_CON, 5, 1)
+#define CON_SS_CTL bits_desc(SPICC_REG_CON, 6, 1)
+#define CON_SS_POL bits_desc(SPICC_REG_CON, 7, 1)
+#define CON_DRCTL bits_desc(SPICC_REG_CON, 8, 2)
+#define CON_CHIP_SELECT bits_desc(SPICC_REG_CON, 12, 2)
+#define CON_DATA_RATE_DIV bits_desc(SPICC_REG_CON, 16, 3)
+#define CON_BITS_PER_WORD bits_desc(SPICC_REG_CON, 19, 6)
+#define CON_BURST_LEN bits_desc(SPICC_REG_CON, 25, 7)
+#define BURST_LEN_MAX 128
+
+#define INT_TX_EMPTY_EN bits_desc(SPICC_REG_INT, 0, 1)
+#define INT_TX_HALF_EN bits_desc(SPICC_REG_INT, 1, 1)
+#define INT_TX_FULL_EN bits_desc(SPICC_REG_INT, 2, 1)
+#define INT_RX_READY_EN bits_desc(SPICC_REG_INT, 3, 1)
+#define INT_RX_HALF_EN bits_desc(SPICC_REG_INT, 4, 1)
+#define INT_RX_FULL_EN bits_desc(SPICC_REG_INT, 5, 1)
+#define INT_RX_OF_EN bits_desc(SPICC_REG_INT, 6, 1)
+#define INT_XFER_COM_EN bits_desc(SPICC_REG_INT, 7, 1)
+
+#define DMA_EN bits_desc(SPICC_REG_DMA, 0, 1)
+#define DMA_TX_FIFO_TH bits_desc(SPICC_REG_DMA, 1, 5)
+#define DMA_RX_FIFO_TH bits_desc(SPICC_REG_DMA, 6, 5)
+#define DMA_NUM_RD_BURST bits_desc(SPICC_REG_DMA, 11, 4)
+#define DMA_NUM_WR_BURST bits_desc(SPICC_REG_DMA, 15, 4)
+#define DMA_URGENT bits_desc(SPICC_REG_DMA, 19, 1)
+#define DMA_THREAD_ID bits_desc(SPICC_REG_DMA, 20, 6)
+#define DMA_BURST_NUM bits_desc(SPICC_REG_DMA, 26, 6)
+
+#define STA_TX_EMPTY bits_desc(SPICC_REG_STA, 0, 1)
+#define STA_TX_HALF bits_desc(SPICC_REG_STA, 1, 1)
+#define STA_TX_FULL bits_desc(SPICC_REG_STA, 2, 1)
+#define STA_RX_READY bits_desc(SPICC_REG_STA, 3, 1)
+#define STA_RX_HALF bits_desc(SPICC_REG_STA, 4, 1)
+#define STA_RX_FULL bits_desc(SPICC_REG_STA, 5, 1)
+#define STA_RX_OF bits_desc(SPICC_REG_STA, 6, 1)
+#define STA_XFER_COM bits_desc(SPICC_REG_STA, 7, 1)
+
+#define TX_COUNT bits_desc(SPICC_REG_TEST, 0, 5)
+#define RX_COUNT bits_desc(SPICC_REG_TEST, 5, 5)
+#define DELAY_CONTROL bits_desc(SPICC_REG_TEST, 16, 6)
+#define RX_FIFO_RESET bits_desc(SPICC_REG_TEST, 22, 1)
+#define TX_FIFO_RESET bits_desc(SPICC_REG_TEST, 23, 1)
+#define CLK_FREE_EN bits_desc(SPICC_REG_TEST, 24, 1)
+
+#define CS_DELAY bits_desc(SPICC_REG_ENHANCE_CNTL, 0, 16)
+#define ENHANCE_CLK_DIV bits_desc(SPICC_REG_ENHANCE_CNTL, 16, 8)
+#define ENHANCE_CLK_DIV_SELECT bits_desc(SPICC_REG_ENHANCE_CNTL, 24, 1)
+#define MOSI_OEN bits_desc(SPICC_REG_ENHANCE_CNTL, 25, 1)
+#define CLK_OEN bits_desc(SPICC_REG_ENHANCE_CNTL, 26, 1)
+#define CS_OEN bits_desc(SPICC_REG_ENHANCE_CNTL, 27, 1)
+#define CS_DELAY_EN bits_desc(SPICC_REG_ENHANCE_CNTL, 28, 1)
+#define MAIN_CLK_AO bits_desc(SPICC_REG_ENHANCE_CNTL, 29, 1)
+
+
+struct spicc_platform_data {
+       int device_id;
+       struct spicc_regs __iomem *regs;
+       struct pinctrl *pinctrl;
+       struct clk *clk;
+       int num_chipselect;
+       int *cs_gpios;
+};
+
+#endif
+