From 50350fb3200ea1ec96669f21650bb7f841ed431f Mon Sep 17 00:00:00 2001 From: =?utf8?q?=E6=9E=97=E8=BE=89=E8=BE=89?= Date: Tue, 11 May 2010 07:34:46 +0000 Subject: [PATCH] add spi driver --- arch/arm/mach-rk2818/board-midsdk.c | 29 +- arch/arm/mach-rk2818/clock.c | 2 +- arch/arm/mach-rk2818/devices.c | 22 + arch/arm/mach-rk2818/devices.h | 5 +- arch/arm/mach-rk2818/include/mach/dma.h | 3 +- arch/arm/mach-rk2818/include/mach/iomux.h | 2 +- drivers/spi/Kconfig | 7 + drivers/spi/Makefile | 1 + drivers/spi/rk2818_spim.c | 1039 +++++++++++++++++++++ drivers/spi/rk2818_spim.h | 223 +++++ 10 files changed, 1328 insertions(+), 5 deletions(-) create mode 100644 drivers/spi/rk2818_spim.c create mode 100644 drivers/spi/rk2818_spim.h diff --git a/arch/arm/mach-rk2818/board-midsdk.c b/arch/arm/mach-rk2818/board-midsdk.c index 49104bb40c34..f2707735db3d 100644 --- a/arch/arm/mach-rk2818/board-midsdk.c +++ b/arch/arm/mach-rk2818/board-midsdk.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -136,11 +137,34 @@ static struct map_desc rk2818_io_desc[] __initdata = { .length = 0xa0000, ///apb bus i2s i2c spi no map in this .type = MT_DEVICE }, +}; + +/***************************************************************************************** + * SPI devices + *author: lhh + *****************************************************************************************/ +static struct spi_board_info board_spi_devices[] = { + { /* net chip */ + .modalias = "enc28j60", + .chip_select = 1, + .max_speed_hz = 12 * 1000 * 1000, + .bus_num = 0, + .mode = SPI_MODE_0, + }, + + { + .modalias = "xpt2046_ts", + .chip_select = 0, + .max_speed_hz = 1000000, + .bus_num = 0, + .mode = SPI_MODE_0, + }, }; static struct platform_device *devices[] __initdata = { &rk2818_device_uart1, + &rk2818_device_spim, }; extern struct sys_timer rk2818_timer; @@ -155,7 +179,10 @@ static void __init machine_rk2818_init_irq(void) static void __init machine_rk2818_board_init(void) { platform_add_devices(devices, ARRAY_SIZE(devices)); - + spi_register_board_info(board_spi_devices, ARRAY_SIZE(board_spi_devices)); + rk2818_mux_api_set(GPIOB4_SPI0CS0_MMC0D4_NAME,IOMUXA_GPIO0_B4); //IOMUXA_SPI0_CSN0);//use for gpio SPI CS0 + rk2818_mux_api_set(GPIOB0_SPI0CSN1_MMC1PCA_NAME,IOMUXA_GPIO0_B0); //IOMUXA_SPI0_CSN1);//use for gpio SPI CS1 + rk2818_mux_api_set(GPIOB_SPI0_MMC0_NAME,IOMUXA_SPI0);//use for SPI CLK SDI SDO } static void __init machine_rk2818_mapio(void) diff --git a/arch/arm/mach-rk2818/clock.c b/arch/arm/mach-rk2818/clock.c index 228c52ef5af3..d7cd503d821f 100644 --- a/arch/arm/mach-rk2818/clock.c +++ b/arch/arm/mach-rk2818/clock.c @@ -915,7 +915,7 @@ static struct clk_lookup clks[] = { CLK("rk2818_serial.1", "uart", &uart1_clk), CLK1(i2c0), CLK1(i2c1), - CLK1(spi0), + CLK("rk2818_spim.0", "spi", &spi0_clk), CLK1(spi1), CLK1(pwm), CLK1(timer), diff --git a/arch/arm/mach-rk2818/devices.c b/arch/arm/mach-rk2818/devices.c index 95359a4e70c1..fc429d92595d 100644 --- a/arch/arm/mach-rk2818/devices.c +++ b/arch/arm/mach-rk2818/devices.c @@ -100,3 +100,25 @@ struct platform_device rk2818_device_uart3 = { .resource = resources_uart3, }; +/* + * rk2818 spi master device + */ +static struct resource resources_spim[] = { + { + .start = IRQ_NR_SPIM, + .end = IRQ_NR_SPIM, + .flags = IORESOURCE_IRQ, + }, + { + .start = RK2818_SPIMASTER_PHYS, + .end = RK2818_SPIMASTER_PHYS + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, +}; +struct platform_device rk2818_device_spim = { + .name = "rk2818_spim", + .id = 0, + .num_resources = ARRAY_SIZE(resources_spim), + .resource = resources_spim, +}; + diff --git a/arch/arm/mach-rk2818/devices.h b/arch/arm/mach-rk2818/devices.h index 9228e5398aaa..15766a01d5e6 100644 --- a/arch/arm/mach-rk2818/devices.h +++ b/arch/arm/mach-rk2818/devices.h @@ -16,7 +16,10 @@ #ifndef __ARCH_ARM_MACH_RK2818_DEVICES_H #define __ARCH_ARM_MACH_RK2818_DEVICES_H +extern struct platform_device rk2818_device_uart0; extern struct platform_device rk2818_device_uart1; - +extern struct platform_device rk2818_device_uart2; +extern struct platform_device rk2818_device_uart3; +extern struct platform_device rk2818_device_spim; #endif diff --git a/arch/arm/mach-rk2818/include/mach/dma.h b/arch/arm/mach-rk2818/include/mach/dma.h index 22416d0b7abc..0ac2ea8c6f06 100644 --- a/arch/arm/mach-rk2818/include/mach/dma.h +++ b/arch/arm/mach-rk2818/include/mach/dma.h @@ -1,4 +1,5 @@ -/* arch/arm/mach-rk2818/dma.h +/* + * arch/arm/mach-rk2818/include/mach/dma.h * * Copyright (C) 2010 ROCKCHIP, Inc. * diff --git a/arch/arm/mach-rk2818/include/mach/iomux.h b/arch/arm/mach-rk2818/include/mach/iomux.h index d64a2e5de641..f37c9e1a6793 100644 --- a/arch/arm/mach-rk2818/include/mach/iomux.h +++ b/arch/arm/mach-rk2818/include/mach/iomux.h @@ -37,7 +37,7 @@ #define IOMUXA_FLASH_CS67 (2) #define IOMUXA_GPIO1_A12 (0) #define IOMUXA_CLKIN_SSINN (1) -#define IOMUXA_FLASH_CS45 (2) (1) +#define IOMUXA_FLASH_CS45 (2) #define IOMUXA_GPIO0_B0 (0) #define IOMUXA_SPI0_CSN1 (1) #define IOMUXA_SDMMC1_PWR_EN (2) diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 4b6f7cba3b3d..60f6a186e640 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -248,6 +248,13 @@ config SPI_XILINX # Add new SPI master controllers in alphabetical order above this line # +config SPIM_RK2818 + tristate "RK2818 SPI master controller core support" + depends on ARCH_RK2818 && SPI_MASTER + help + general driver for SPI controller core from DesignWare + + # # There are lots of SPI device types, with sensors and memory # being probably the most widely used ones. diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 21a118269cac..ad604874029c 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -16,6 +16,7 @@ obj-$(CONFIG_SPI_BFIN) += spi_bfin5xx.o obj-$(CONFIG_SPI_BITBANG) += spi_bitbang.o obj-$(CONFIG_SPI_AU1550) += au1550_spi.o obj-$(CONFIG_SPI_BUTTERFLY) += spi_butterfly.o +obj-$(CONFIG_SPIM_RK2818) += rk2818_spim.o obj-$(CONFIG_SPI_GPIO) += spi_gpio.o obj-$(CONFIG_SPI_IMX) += spi_imx.o obj-$(CONFIG_SPI_LM70_LLP) += spi_lm70llp.o diff --git a/drivers/spi/rk2818_spim.c b/drivers/spi/rk2818_spim.c new file mode 100644 index 000000000000..cd6a2fbc6fd2 --- /dev/null +++ b/drivers/spi/rk2818_spim.c @@ -0,0 +1,1039 @@ +/*drivers/serial/rk2818_spim.c - driver for rk2818 spim device + * + * Copyright (C) 2010 ROCKCHIP, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rk2818_spim.h" +#include + +#ifdef CONFIG_DEBUG_FS +#include +#endif + +#define START_STATE ((void *)0) +#define RUNNING_STATE ((void *)1) +#define DONE_STATE ((void *)2) +#define ERROR_STATE ((void *)-1) + +#define QUEUE_RUNNING 0 +#define QUEUE_STOPPED 1 + +#define MRST_SPI_DEASSERT 0 +#define MRST_SPI_ASSERT 1 ///CS0 +#define MRST_SPI_ASSERT1 2 ///CS1 + +/* Slave spi_dev related */ +struct chip_data { + u16 cr0; + u8 cs; /* chip select pin */ + u8 n_bytes; /* current is a 1/2/4 byte op */ + u8 tmode; /* TR/TO/RO/EEPROM */ + u8 type; /* SPI/SSP/MicroWire */ + + u8 poll_mode; /* 1 means use poll mode */ + + u32 dma_width; + u32 rx_threshold; + u32 tx_threshold; + u8 enable_dma; + u8 bits_per_word; + u16 clk_div; /* baud rate divider */ + u32 speed_hz; /* baud rate */ + int (*write)(struct rk2818_spi *dws); + int (*read)(struct rk2818_spi *dws); + void (*cs_control)(u32 command); +}; + +#ifdef CONFIG_DEBUG_FS +static int spi_show_regs_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +#define SPI_REGS_BUFSIZE 1024 +static ssize_t spi_show_regs(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct rk2818_spi *dws; + char *buf; + u32 len = 0; + ssize_t ret; + + dws = file->private_data; + + buf = kzalloc(SPI_REGS_BUFSIZE, GFP_KERNEL); + if (!buf) + return 0; + + len += printk(buf + len, SPI_REGS_BUFSIZE - len, + "MRST SPI0 registers:\n"); + len += printk(buf + len, SPI_REGS_BUFSIZE - len, + "=================================\n"); + len += printk(buf + len, SPI_REGS_BUFSIZE - len, + "CTRL0: \t\t0x%08x\n", rk2818_readl(dws, SPIM_CTRLR0)); + len += printk(buf + len, SPI_REGS_BUFSIZE - len, + "CTRL1: \t\t0x%08x\n", rk2818_readl(dws, SPIM_CTRLR1)); + len += printk(buf + len, SPI_REGS_BUFSIZE - len, + "SSIENR: \t0x%08x\n", rk2818_readl(dws, SPIM_SPIENR)); + len += printk(buf + len, SPI_REGS_BUFSIZE - len, + "SER: \t\t0x%08x\n", rk2818_readl(dws, SPIM_SER)); + len += printk(buf + len, SPI_REGS_BUFSIZE - len, + "BAUDR: \t\t0x%08x\n", rk2818_readl(dws, SPIM_BAUDR)); + len += printk(buf + len, SPI_REGS_BUFSIZE - len, + "TXFTLR: \t0x%08x\n", rk2818_readl(dws, SPIM_TXFTLR)); + len += printk(buf + len, SPI_REGS_BUFSIZE - len, + "RXFTLR: \t0x%08x\n", rk2818_readl(dws, SPIM_RXFTLR)); + len += printk(buf + len, SPI_REGS_BUFSIZE - len, + "TXFLR: \t\t0x%08x\n", rk2818_readl(dws, SPIM_TXFLR)); + len += printk(buf + len, SPI_REGS_BUFSIZE - len, + "RXFLR: \t\t0x%08x\n", rk2818_readl(dws, SPIM_RXFLR)); + len += printk(buf + len, SPI_REGS_BUFSIZE - len, + "SR: \t\t0x%08x\n", rk2818_readl(dws, SPIM_SR)); + len += printk(buf + len, SPI_REGS_BUFSIZE - len, + "IMR: \t\t0x%08x\n", rk2818_readl(dws, SPIM_IMR)); + len += printk(buf + len, SPI_REGS_BUFSIZE - len, + "ISR: \t\t0x%08x\n", rk2818_readl(dws, SPIM_ISR)); + len += printk(buf + len, SPI_REGS_BUFSIZE - len, + "DMACR: \t\t0x%08x\n", rk2818_readl(dws, SPIM_DMACR)); + len += printk(buf + len, SPI_REGS_BUFSIZE - len, + "DMATDLR: \t0x%08x\n", rk2818_readl(dws, SPIM_DMATDLR)); + len += printk(buf + len, SPI_REGS_BUFSIZE - len, + "DMARDLR: \t0x%08x\n", rk2818_readl(dws, SPIM_DMARDLR)); + len += printk(buf + len, SPI_REGS_BUFSIZE - len, + "=================================\n"); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, len); + kfree(buf); + return ret; +} + +static const struct file_operations mrst_spi_regs_ops = { + .owner = THIS_MODULE, + .open = spi_show_regs_open, + .read = spi_show_regs, +}; + +static int mrst_spi_debugfs_init(struct rk2818_spi *dws) +{ + dws->debugfs = debugfs_create_dir("mrst_spi", NULL); + if (!dws->debugfs) + return -ENOMEM; + + debugfs_create_file("registers", S_IFREG | S_IRUGO, + dws->debugfs, (void *)dws, &mrst_spi_regs_ops); + return 0; +} + +static void mrst_spi_debugfs_remove(struct rk2818_spi *dws) +{ + if (dws->debugfs) + debugfs_remove_recursive(dws->debugfs); +} + +#else +static inline int mrst_spi_debugfs_init(struct rk2818_spi *dws) +{ + return 0; +} + +static inline void mrst_spi_debugfs_remove(struct rk2818_spi *dws) +{ +} +#endif /* CONFIG_DEBUG_FS */ + +static void wait_till_not_busy(struct rk2818_spi *dws) +{ + unsigned long end = jiffies + 1 + usecs_to_jiffies(1000); + + while (time_before(jiffies, end)) { + if (!(rk2818_readw(dws, SPIM_SR) & SR_BUSY)) + return; + } + dev_err(&dws->master->dev, + "DW SPI: Status keeps busy for 1000us after a read/write!\n"); +} + +static void flush(struct rk2818_spi *dws) +{ + while (rk2818_readw(dws, SPIM_SR) & SR_RF_NOT_EMPT) + rk2818_readw(dws, SPIM_DR0); + + wait_till_not_busy(dws); +} + +static void null_cs_control(u32 command) +{ + if(command == 2) + gpio_direction_output(RK2818_PIN_PB0,GPIO_LOW); + else if(command == 1) + gpio_direction_output(RK2818_PIN_PB4,GPIO_LOW); + else{ + gpio_direction_output(RK2818_PIN_PB0,GPIO_HIGH); + gpio_direction_output(RK2818_PIN_PB4,GPIO_HIGH); + } +} + +static int null_writer(struct rk2818_spi *dws) +{ + u8 n_bytes = dws->n_bytes; + + if (!(rk2818_readw(dws, SPIM_SR) & SR_TF_NOT_FULL) + || (dws->tx == dws->tx_end)) + return 0; + rk2818_writew(dws, SPIM_DR0, 0); + dws->tx += n_bytes; + + wait_till_not_busy(dws); + return 1; +} + +static int null_reader(struct rk2818_spi *dws) +{ + u8 n_bytes = dws->n_bytes; + while ((rk2818_readw(dws, SPIM_SR) & SR_RF_NOT_EMPT) + && (dws->rx < dws->rx_end)) { + rk2818_readw(dws, SPIM_DR0); + dws->rx += n_bytes; + } + wait_till_not_busy(dws); + return dws->rx == dws->rx_end; +} + +static int u8_writer(struct rk2818_spi *dws) +{ + if (!(rk2818_readw(dws, SPIM_SR) & SR_TF_NOT_FULL) + || (dws->tx == dws->tx_end)) + return 0; + rk2818_writew(dws, SPIM_DR0, *(u8 *)(dws->tx)); + ++dws->tx; + + wait_till_not_busy(dws); + return 1; +} + +static int u8_reader(struct rk2818_spi *dws) +{ + while ((rk2818_readw(dws, SPIM_SR) & SR_RF_NOT_EMPT) + && (dws->rx < dws->rx_end)) { + *(u8 *)(dws->rx) = rk2818_readw(dws, SPIM_DR0); + ++dws->rx; + } + + wait_till_not_busy(dws); + return dws->rx == dws->rx_end; +} + +static int u16_writer(struct rk2818_spi *dws) +{ + if (!(rk2818_readw(dws, SPIM_SR) & SR_TF_NOT_FULL) + || (dws->tx == dws->tx_end)) + return 0; + + rk2818_writew(dws, SPIM_DR0, *(u16 *)(dws->tx)); + dws->tx += 2; + + wait_till_not_busy(dws); + return 1; +} + +static int u16_reader(struct rk2818_spi *dws) +{ + u16 temp; + + while ((rk2818_readw(dws, SPIM_SR) & SR_RF_NOT_EMPT) + && (dws->rx < dws->rx_end)) { + temp = rk2818_readw(dws, SPIM_DR0); + *(u16 *)(dws->rx) = temp; + dws->rx += 2; + } + + wait_till_not_busy(dws); + return dws->rx == dws->rx_end; +} + +static void *next_transfer(struct rk2818_spi *dws) +{ + struct spi_message *msg = dws->cur_msg; + struct spi_transfer *trans = dws->cur_transfer; + + /* Move to next transfer */ + if (trans->transfer_list.next != &msg->transfers) { + dws->cur_transfer = + list_entry(trans->transfer_list.next, + struct spi_transfer, + transfer_list); + return RUNNING_STATE; + } else + return DONE_STATE; +} + +/* + * Note: first step is the protocol driver prepares + * a dma-capable memory, and this func just need translate + * the virt addr to physical + */ +static int map_dma_buffers(struct rk2818_spi *dws) +{ + if (!dws->cur_msg->is_dma_mapped || !dws->dma_inited + || !dws->cur_chip->enable_dma) + return 0; + + if (dws->cur_transfer->tx_dma) + dws->tx_dma = dws->cur_transfer->tx_dma; + + if (dws->cur_transfer->rx_dma) + dws->rx_dma = dws->cur_transfer->rx_dma; + + return 1; +} + +/* Caller already set message->status; dma and pio irqs are blocked */ +static void giveback(struct rk2818_spi *dws) +{ + struct spi_transfer *last_transfer; + unsigned long flags; + struct spi_message *msg; + + spin_lock_irqsave(&dws->lock, flags); + msg = dws->cur_msg; + dws->cur_msg = NULL; + dws->cur_transfer = NULL; + dws->prev_chip = dws->cur_chip; + dws->cur_chip = NULL; + dws->dma_mapped = 0; + queue_work(dws->workqueue, &dws->pump_messages); + spin_unlock_irqrestore(&dws->lock, flags); + + last_transfer = list_entry(msg->transfers.prev, + struct spi_transfer, + transfer_list); + + if (!last_transfer->cs_change) + dws->cs_control(MRST_SPI_DEASSERT); + + msg->state = NULL; + if (msg->complete) + msg->complete(msg->context); +} + +static void int_error_stop(struct rk2818_spi *dws, const char *msg) +{ + /* Stop and reset hw */ + flush(dws); + spi_enable_chip(dws, 0); + + dev_err(&dws->master->dev, "%s\n", msg); + dws->cur_msg->state = ERROR_STATE; + tasklet_schedule(&dws->pump_transfers); +} + +static void transfer_complete(struct rk2818_spi *dws) +{ + /* Update total byte transfered return count actual bytes read */ + dws->cur_msg->actual_length += dws->len; + + /* Move to next transfer */ + dws->cur_msg->state = next_transfer(dws); + + /* Handle end of message */ + if (dws->cur_msg->state == DONE_STATE) { + dws->cur_msg->status = 0; + giveback(dws); + } else + tasklet_schedule(&dws->pump_transfers); +} + +static irqreturn_t interrupt_transfer(struct rk2818_spi *dws) +{ + u16 irq_status, irq_mask = 0x3f; + u32 int_level = dws->fifo_len / 2; + u32 left; + + irq_status = rk2818_readw(dws, SPIM_ISR) & irq_mask; + /* Error handling */ + if (irq_status & (SPI_INT_TXOI | SPI_INT_RXOI | SPI_INT_RXUI)) { + rk2818_readw(dws, SPIM_TXOICR); + rk2818_readw(dws, SPIM_RXOICR); + rk2818_readw(dws, SPIM_RXUICR); + int_error_stop(dws, "interrupt_transfer: fifo overrun"); + return IRQ_HANDLED; + } + + if (irq_status & SPI_INT_TXEI) { + spi_mask_intr(dws, SPI_INT_TXEI); + + left = (dws->tx_end - dws->tx) / dws->n_bytes; + left = (left > int_level) ? int_level : left; + + while (left--) + dws->write(dws); + dws->read(dws); + + /* Re-enable the IRQ if there is still data left to tx */ + if (dws->tx_end > dws->tx) + spi_umask_intr(dws, SPI_INT_TXEI); + else + transfer_complete(dws); + } + + return IRQ_HANDLED; +} + +static irqreturn_t rk2818_spi_irq(int irq, void *dev_id) +{ + struct rk2818_spi *dws = dev_id; + + if (!dws->cur_msg) { + spi_mask_intr(dws, SPI_INT_TXEI); + /* Never fail */ + return IRQ_HANDLED; + } + + return dws->transfer_handler(dws); +} + +/* Must be called inside pump_transfers() */ +static void poll_transfer(struct rk2818_spi *dws) +{ + while (dws->write(dws)) + dws->read(dws); + + transfer_complete(dws); +} + +static void dma_transfer(struct rk2818_spi *dws, struct spi_transfer *xfer) //int cs_change) +{ + +} + +static void pump_transfers(unsigned long data) +{ + struct rk2818_spi *dws = (struct rk2818_spi *)data; + struct spi_message *message = NULL; + struct spi_transfer *transfer = NULL; + struct spi_transfer *previous = NULL; + struct spi_device *spi = NULL; + struct chip_data *chip = NULL; + u8 bits = 0; + u8 imask = 0; + u8 cs_change = 0; + u16 txint_level = 0; + u16 clk_div = 0; + u32 speed = 0; + u32 cr0 = 0; + + /* Get current state information */ + message = dws->cur_msg; + transfer = dws->cur_transfer; + chip = dws->cur_chip; + spi = message->spi; + if (unlikely(!chip->clk_div)) + chip->clk_div = clk_get_rate(dws->clock_spim) / chip->speed_hz; + if (message->state == ERROR_STATE) { + message->status = -EIO; + goto early_exit; + } + + /* Handle end of message */ + if (message->state == DONE_STATE) { + message->status = 0; + goto early_exit; + } + + /* Delay if requested at end of transfer*/ + if (message->state == RUNNING_STATE) { + previous = list_entry(transfer->transfer_list.prev, + struct spi_transfer, + transfer_list); + if (previous->delay_usecs) + udelay(previous->delay_usecs); + } + + dws->n_bytes = chip->n_bytes; + dws->dma_width = chip->dma_width; + dws->cs_control = chip->cs_control; + + dws->rx_dma = transfer->rx_dma; + dws->tx_dma = transfer->tx_dma; + dws->tx = (void *)transfer->tx_buf; + dws->tx_end = dws->tx + transfer->len; + dws->rx = transfer->rx_buf; + dws->rx_end = dws->rx + transfer->len; + dws->write = dws->tx ? chip->write : null_writer; + dws->read = dws->rx ? chip->read : null_reader; + dws->cs_change = transfer->cs_change; + dws->len = dws->cur_transfer->len; + if (chip != dws->prev_chip) + cs_change = 1; + + cr0 = chip->cr0; + + /* Handle per transfer options for bpw and speed */ + if (transfer->speed_hz) { + speed = chip->speed_hz; + + if (transfer->speed_hz != speed) { + speed = transfer->speed_hz; + if (speed > clk_get_rate(dws->clock_spim)) { + printk(KERN_ERR "MRST SPI0: unsupported" + "freq: %dHz\n", speed); + message->status = -EIO; + goto early_exit; + } + + /* clk_div doesn't support odd number */ + clk_div = clk_get_rate(dws->clock_spim) / speed; + clk_div = (clk_div + 1) & 0xfffe; + + chip->speed_hz = speed; + chip->clk_div = clk_div; + } + } + if (transfer->bits_per_word) { + bits = transfer->bits_per_word; + + switch (bits) { + case 8: + dws->n_bytes = 1; + dws->dma_width = 1; + dws->read = (dws->read != null_reader) ? + u8_reader : null_reader; + dws->write = (dws->write != null_writer) ? + u8_writer : null_writer; + break; + case 16: + dws->n_bytes = 2; + dws->dma_width = 2; + dws->read = (dws->read != null_reader) ? + u16_reader : null_reader; + dws->write = (dws->write != null_writer) ? + u16_writer : null_writer; + break; + default: + printk(KERN_ERR "MRST SPI0: unsupported bits:" + "%db\n", bits); + message->status = -EIO; + goto early_exit; + } + + cr0 = (bits - 1) + | (chip->type << SPI_FRF_OFFSET) + | (spi->mode << SPI_MODE_OFFSET) + | (chip->tmode << SPI_TMOD_OFFSET); + } + message->state = RUNNING_STATE; + + /* + * Adjust transfer mode if necessary. Requires platform dependent + * chipselect mechanism. + */ + if (dws->cs_control) { + if (dws->rx && dws->tx) + chip->tmode = 0x00; + else if (dws->rx) + chip->tmode = 0x02; + else + chip->tmode = 0x01; + + cr0 &= ~(0x3 << SPI_MODE_OFFSET); + cr0 |= (chip->tmode << SPI_TMOD_OFFSET); + } + /* Check if current transfer is a DMA transaction */ + dws->dma_mapped = map_dma_buffers(dws); + + /* + * Interrupt mode + * we only need set the TXEI IRQ, as TX/RX always happen syncronizely + */ + if (!dws->dma_mapped && !chip->poll_mode) { + int templen = dws->len / dws->n_bytes; + txint_level = dws->fifo_len / 2; + txint_level = (templen > txint_level) ? txint_level : templen; + + imask |= SPI_INT_TXEI; + dws->transfer_handler = interrupt_transfer; + } + + /* + * Reprogram registers only if + * 1. chip select changes + * 2. clk_div is changed + * 3. control value changes + */ + if (rk2818_readw(dws, SPIM_CTRLR0) != cr0 || cs_change || clk_div || imask) { + spi_enable_chip(dws, 0); + if (rk2818_readw(dws, SPIM_CTRLR0) != cr0) + rk2818_writew(dws, SPIM_CTRLR0, cr0); + + spi_set_clk(dws, clk_div ? clk_div : chip->clk_div); + spi_chip_sel(dws, spi->chip_select); + /* Set the interrupt mask, for poll mode just diable all int */ + spi_mask_intr(dws, 0xff); + if (imask) + spi_umask_intr(dws, imask); + if (txint_level) + rk2818_writew(dws, SPIM_TXFTLR, txint_level); + + spi_enable_chip(dws, 1); + if (cs_change) + dws->prev_chip = chip; + } + + if (dws->dma_mapped) + dma_transfer(dws, transfer); ///cs_change); + + if (chip->poll_mode) + poll_transfer(dws); + + return; + +early_exit: + giveback(dws); + return; +} + +static void pump_messages(struct work_struct *work) +{ + struct rk2818_spi *dws = + container_of(work, struct rk2818_spi, pump_messages); + unsigned long flags; + + /* Lock queue and check for queue work */ + spin_lock_irqsave(&dws->lock, flags); + if (list_empty(&dws->queue) || dws->run == QUEUE_STOPPED) { + dws->busy = 0; + spin_unlock_irqrestore(&dws->lock, flags); + return; + } + + /* Make sure we are not already running a message */ + if (dws->cur_msg) { + spin_unlock_irqrestore(&dws->lock, flags); + return; + } + + /* Extract head of queue */ + dws->cur_msg = list_entry(dws->queue.next, struct spi_message, queue); + list_del_init(&dws->cur_msg->queue); + + /* Initial message state*/ + dws->cur_msg->state = START_STATE; + dws->cur_transfer = list_entry(dws->cur_msg->transfers.next, + struct spi_transfer, + transfer_list); + dws->cur_chip = spi_get_ctldata(dws->cur_msg->spi); + + /* Mark as busy and launch transfers */ + tasklet_schedule(&dws->pump_transfers); + + dws->busy = 1; + spin_unlock_irqrestore(&dws->lock, flags); +} + +/* spi_device use this to queue in their spi_msg */ +static int rk2818_spi_transfer(struct spi_device *spi, struct spi_message *msg) +{ + struct rk2818_spi *dws = spi_master_get_devdata(spi->master); + unsigned long flags; + + spin_lock_irqsave(&dws->lock, flags); + + if (dws->run == QUEUE_STOPPED) { + spin_unlock_irqrestore(&dws->lock, flags); + return -ESHUTDOWN; + } + + msg->actual_length = 0; + msg->status = -EINPROGRESS; + msg->state = START_STATE; + + list_add_tail(&msg->queue, &dws->queue); + + if (dws->run == QUEUE_RUNNING && !dws->busy) { + + if (dws->cur_transfer || dws->cur_msg) + queue_work(dws->workqueue, + &dws->pump_messages); + else { + /* If no other data transaction in air, just go */ + spin_unlock_irqrestore(&dws->lock, flags); + pump_messages(&dws->pump_messages); + return 0; + } + } + + spin_unlock_irqrestore(&dws->lock, flags); + + return 0; +} + +/* This may be called twice for each spi dev */ +static int rk2818_spi_setup(struct spi_device *spi) +{ + struct rk2818_spi_chip *chip_info = NULL; + struct chip_data *chip; + + if (spi->bits_per_word != 8 && spi->bits_per_word != 16) + return -EINVAL; + + /* Only alloc on first setup */ + chip = spi_get_ctldata(spi); + if (!chip) { + chip = kzalloc(sizeof(struct chip_data), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->cs_control = null_cs_control; + chip->enable_dma = 1; //0; + } + + /* + * Protocol drivers may change the chip settings, so... + * if chip_info exists, use it + */ + chip_info = spi->controller_data; + + /* chip_info doesn't always exist */ + if (chip_info) { + if (chip_info->cs_control) + chip->cs_control = chip_info->cs_control; + + chip->poll_mode = chip_info->poll_mode; + chip->type = chip_info->type; + + chip->rx_threshold = 0; + chip->tx_threshold = 0; + + chip->enable_dma = chip_info->enable_dma; + } + + if (spi->bits_per_word <= 8) { + chip->n_bytes = 1; + chip->dma_width = 1; + chip->read = u8_reader; + chip->write = u8_writer; + } else if (spi->bits_per_word <= 16) { + chip->n_bytes = 2; + chip->dma_width = 2; + chip->read = u16_reader; + chip->write = u16_writer; + } else { + /* Never take >16b case for MRST SPIC */ + dev_err(&spi->dev, "invalid wordsize\n"); + return -EINVAL; + } + chip->bits_per_word = spi->bits_per_word; + + if (!spi->max_speed_hz) { + dev_err(&spi->dev, "No max speed HZ parameter\n"); + return -EINVAL; + } + chip->speed_hz = spi->max_speed_hz; + + chip->tmode = 0; /* Tx & Rx */ + /* Default SPI mode is SCPOL = 0, SCPH = 0 */ + chip->cr0 = (chip->bits_per_word - 1) + | (chip->type << SPI_FRF_OFFSET) + | (spi->mode << SPI_MODE_OFFSET) + | (chip->tmode << SPI_TMOD_OFFSET); + + spi_set_ctldata(spi, chip); + return 0; +} + +static void rk2818_spi_cleanup(struct spi_device *spi) +{ + struct chip_data *chip = spi_get_ctldata(spi); + kfree(chip); +} + +static int __devinit init_queue(struct rk2818_spi *dws) +{ + INIT_LIST_HEAD(&dws->queue); + spin_lock_init(&dws->lock); + + dws->run = QUEUE_STOPPED; + dws->busy = 0; + + tasklet_init(&dws->pump_transfers, + pump_transfers, (unsigned long)dws); + + INIT_WORK(&dws->pump_messages, pump_messages); + dws->workqueue = create_singlethread_workqueue( + dev_name(dws->master->dev.parent)); + if (dws->workqueue == NULL) + return -EBUSY; + + return 0; +} + +static int start_queue(struct rk2818_spi *dws) +{ + unsigned long flags; + + spin_lock_irqsave(&dws->lock, flags); + + if (dws->run == QUEUE_RUNNING || dws->busy) { + spin_unlock_irqrestore(&dws->lock, flags); + return -EBUSY; + } + + dws->run = QUEUE_RUNNING; + dws->cur_msg = NULL; + dws->cur_transfer = NULL; + dws->cur_chip = NULL; + dws->prev_chip = NULL; + spin_unlock_irqrestore(&dws->lock, flags); + + queue_work(dws->workqueue, &dws->pump_messages); + + return 0; +} + +static int stop_queue(struct rk2818_spi *dws) +{ + unsigned long flags; + unsigned limit = 50; + int status = 0; + + spin_lock_irqsave(&dws->lock, flags); + dws->run = QUEUE_STOPPED; + while (!list_empty(&dws->queue) && dws->busy && limit--) { + spin_unlock_irqrestore(&dws->lock, flags); + msleep(10); + spin_lock_irqsave(&dws->lock, flags); + } + + if (!list_empty(&dws->queue) || dws->busy) + status = -EBUSY; + spin_unlock_irqrestore(&dws->lock, flags); + + return status; +} + +static int destroy_queue(struct rk2818_spi *dws) +{ + int status; + + status = stop_queue(dws); + if (status != 0) + return status; + destroy_workqueue(dws->workqueue); + return 0; +} + +/* Restart the controller, disable all interrupts, clean rx fifo */ +static void spi_hw_init(struct rk2818_spi *dws) +{ + spi_enable_chip(dws, 0); + spi_mask_intr(dws, 0xff); + spi_enable_chip(dws, 1); + flush(dws); + + /* + * Try to detect the FIFO depth if not set by interface driver, + * the depth could be from 2 to 32 from HW spec + */ + if (!dws->fifo_len) { + u32 fifo; + for (fifo = 2; fifo <= 31; fifo++) { + rk2818_writew(dws, SPIM_TXFTLR, fifo); + if (fifo != rk2818_readw(dws, SPIM_TXFTLR)) + break; + } + + dws->fifo_len = (fifo == 31) ? 0 : fifo; + rk2818_writew(dws, SPIM_TXFTLR, 0); + } +} + +static int __init rk2818_spim_probe(struct platform_device *pdev) +{ + struct resource *regs; + struct rk2818_spi *dws; + struct spi_master *master; + int irq; + int ret; + + gpio_request(RK2818_PIN_PB0, "rk2818_spim"); + gpio_request(RK2818_PIN_PB4, "rk2818_spim"); + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!regs) + return -ENXIO; + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + /* setup spi core then atmel-specific driver state */ + ret = -ENOMEM; + master = spi_alloc_master(&pdev->dev, sizeof *dws); + if (!master) { + ret = -ENOMEM; + goto exit; + } + platform_set_drvdata(pdev, master); + dws = spi_master_get_devdata(master); + dws->clock_spim = clk_get(&pdev->dev, "spi"); + clk_enable(dws->clock_spim); + if (IS_ERR(dws->clock_spim)) + return PTR_ERR(dws->clock_spim); + dws->regs = ioremap(regs->start, (regs->end - regs->start) + 1); + if (!dws->regs) + goto exit; + dws->irq = irq; + dws->master = master; + dws->type = SSI_MOTO_SPI; + dws->prev_chip = NULL; + dws->dma_inited = 1; ///0; + ///dws->dma_addr = (dma_addr_t)(dws->paddr + 0x60); + ret = request_irq(dws->irq, rk2818_spi_irq, 0, + "rk2818_spim", dws); + if (ret < 0) { + dev_err(&master->dev, "can not get IRQ\n"); + goto err_free_master; + } + master->mode_bits = SPI_CPOL | SPI_CPHA; + master->bus_num = pdev->id; + master->num_chipselect = 2; + master->cleanup = rk2818_spi_cleanup; + master->setup = rk2818_spi_setup; + master->transfer = rk2818_spi_transfer; + dws->pdev = pdev; + /* Basic HW init */ + spi_hw_init(dws); + /* Initial and start queue */ + ret = init_queue(dws); + if (ret) { + dev_err(&master->dev, "problem initializing queue\n"); + goto err_diable_hw; + } + ret = start_queue(dws); + if (ret) { + dev_err(&master->dev, "problem starting queue\n"); + goto err_diable_hw; + } + spi_master_set_devdata(master, dws); + ret = spi_register_master(master); + if (ret) { + dev_err(&master->dev, "problem registering spi master\n"); + goto err_queue_alloc; + } + printk(KERN_INFO "rk2818_spim: driver initialized\n"); + mrst_spi_debugfs_init(dws); + return 0; + +err_queue_alloc: + destroy_queue(dws); +err_diable_hw: + spi_enable_chip(dws, 0); + free_irq(dws->irq, dws); +err_free_master: + spi_master_put(master); + iounmap(dws->regs); +exit: + return ret; +} + +static void __exit rk2818_spim_remove(struct platform_device *pdev) +{ + struct rk2818_spi *dws = platform_get_drvdata(pdev); + int status = 0; + + if (!dws) + return; + mrst_spi_debugfs_remove(dws); + + /* Remove the queue */ + status = destroy_queue(dws); + if (status != 0) + dev_err(&dws->master->dev, "rk2818_spi_remove: workqueue will not " + "complete, message memory not freed\n"); + clk_put(dws->clock_spim); + clk_disable(dws->clock_spim); + spi_enable_chip(dws, 0); + /* Disable clk */ + spi_set_clk(dws, 0); + free_irq(dws->irq, dws); + + /* Disconnect from the SPI framework */ + spi_unregister_master(dws->master); + iounmap(dws->regs); +} + + +#ifdef CONFIG_PM + +static int rk2818_spim_suspend(struct platform_device *pdev, pm_message_t mesg) +{ + struct spi_master *master = platform_get_drvdata(pdev); + struct rk2818_spi *dws = spi_master_get_devdata(master); + clk_disable(dws->clock_spim); + return 0; +} + +static int rk2818_spim_resume(struct platform_device *pdev) +{ + struct rk2818_spi *dws = platform_get_drvdata(pdev); + int ret; + + clk_enable(dws->clock_spim); + spi_hw_init(dws); + ret = start_queue(dws); + if (ret) + dev_err(&dws->master->dev, "fail to start queue (%d)\n", ret); + return ret; +} + +#else +#define rk2818_spim_suspend NULL +#define rk2818_spim_resume NULL +#endif + +static struct platform_driver rk2818_platform_spim_driver = { + .remove = __exit_p(rk2818_spim_remove), + .driver = { + .name = "rk2818_spim", + .owner = THIS_MODULE, + }, + .suspend = rk2818_spim_suspend, + .resume = rk2818_spim_resume, +}; + +static int __init rk2818_spim_init(void) +{ + int ret; + ret = platform_driver_probe(&rk2818_platform_spim_driver, rk2818_spim_probe); + return ret; +} + +static void __exit rk2818_spim_exit(void) +{ + platform_driver_unregister(&rk2818_platform_spim_driver); +} + +module_init(rk2818_spim_init); +module_exit(rk2818_spim_exit); + +MODULE_AUTHOR("lhh lhh@rock-chips.com"); +MODULE_DESCRIPTION("Rockchip RK2818 spim port driver"); +MODULE_LICENSE("GPL");; diff --git a/drivers/spi/rk2818_spim.h b/drivers/spi/rk2818_spim.h new file mode 100644 index 000000000000..1a91ef4c2de7 --- /dev/null +++ b/drivers/spi/rk2818_spim.h @@ -0,0 +1,223 @@ +/* drivers/spi/rk2818_spim.h + * + * Copyright (C) 2010 ROCKCHIP, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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 __DRIVERS_SPIM_RK2818_HEADER_H +#define __DRIVERS_SPIM_RK2818_HEADER_H +#include + +/* Bit fields in CTRLR0 */ +#define SPI_DFS_OFFSET 0 + +#define SPI_FRF_OFFSET 4 +#define SPI_FRF_SPI 0x0 +#define SPI_FRF_SSP 0x1 +#define SPI_FRF_MICROWIRE 0x2 +#define SPI_FRF_RESV 0x3 + +#define SPI_MODE_OFFSET 6 +#define SPI_SCPH_OFFSET 6 +#define SPI_SCOL_OFFSET 7 +#define SPI_TMOD_OFFSET 8 +#define SPI_TMOD_TR 0x0 /* xmit & recv */ +#define SPI_TMOD_TO 0x1 /* xmit only */ +#define SPI_TMOD_RO 0x2 /* recv only */ +#define SPI_TMOD_EPROMREAD 0x3 /* eeprom read mode */ + +#define SPI_SLVOE_OFFSET 10 +#define SPI_SRL_OFFSET 11 +#define SPI_CFS_OFFSET 12 + +/* Bit fields in SR, 7 bits */ +#define SR_MASK 0x7f /* cover 7 bits */ +#define SR_BUSY (1 << 0) +#define SR_TF_NOT_FULL (1 << 1) +#define SR_TF_EMPT (1 << 2) +#define SR_RF_NOT_EMPT (1 << 3) +#define SR_RF_FULL (1 << 4) +#define SR_TX_ERR (1 << 5) +#define SR_DCOL (1 << 6) + +/* Bit fields in ISR, IMR, RISR, 7 bits */ +#define SPI_INT_TXEI (1 << 0) +#define SPI_INT_TXOI (1 << 1) +#define SPI_INT_RXUI (1 << 2) +#define SPI_INT_RXOI (1 << 3) +#define SPI_INT_RXFI (1 << 4) +#define SPI_INT_MSTI (1 << 5) + +/* TX RX interrupt level threshhold, max can be 256 */ +#define SPI_INT_THRESHOLD 32 + +#define SPIM_CTRLR0 0x0000 +#define SPIM_CTRLR1 0x0004 +#define SPIM_SPIENR 0x0008 +#define SPIM_MWCR 0x000c +#define SPIM_SER 0x0010 +#define SPIM_BAUDR 0x0014 +#define SPIM_TXFTLR 0x0018 +#define SPIM_RXFTLR 0x001c +#define SPIM_TXFLR 0x0020 +#define SPIM_RXFLR 0x0024 +#define SPIM_SR 0x0028 +#define SPIM_IMR 0x002c +#define SPIM_ISR 0x0030 +#define SPIM_RISR 0x0034 +#define SPIM_TXOICR 0x0038 +#define SPIM_RXOICR 0x003c +#define SPIM_RXUICR 0x0040 +#define SPIM_MSTICR 0x0044 +#define SPIM_ICR 0x0048 +#define SPIM_DMACR 0x004c +#define SPIM_DMATDLR 0x0050 +#define SPIM_DMARDLR 0x0054 +#define SPIM_IDR0 0x0058 +#define SPIM_COMP_VERSION 0x005c +#define SPIM_DR0 0x0060 + +enum rk2818_ssi_type { + SSI_MOTO_SPI = 0, + SSI_TI_SSP, + SSI_NS_MICROWIRE, +}; + +struct rk2818_spi { + struct spi_master *master; + struct spi_device *cur_dev; + enum rk2818_ssi_type type; + + void __iomem *regs; + unsigned long paddr; + u32 iolen; + int irq; + u32 fifo_len; /* depth of the FIFO buffer */ + struct clk *clock_spim; /* clk apb */ + struct platform_device *pdev; + + /* Driver message queue */ + struct workqueue_struct *workqueue; + struct work_struct pump_messages; + spinlock_t lock; + struct list_head queue; + int busy; + int run; + + /* Message Transfer pump */ + struct tasklet_struct pump_transfers; + + /* Current message transfer state info */ + struct spi_message *cur_msg; + struct spi_transfer *cur_transfer; + struct chip_data *cur_chip; + struct chip_data *prev_chip; + size_t len; + void *tx; + void *tx_end; + void *rx; + void *rx_end; + int dma_mapped; + dma_addr_t rx_dma; + dma_addr_t tx_dma; + size_t rx_map_len; + size_t tx_map_len; + u8 n_bytes; /* current is a 1/2 bytes op */ + u8 max_bits_per_word; /* maxim is 16b */ + u32 dma_width; + int cs_change; + int (*write)(struct rk2818_spi *dws); + int (*read)(struct rk2818_spi *dws); + irqreturn_t (*transfer_handler)(struct rk2818_spi *dws); + void (*cs_control)(u32 command); + + /* Dma info */ + int dma_inited; + struct dma_chan *txchan; + struct dma_chan *rxchan; + int txdma_done; + int rxdma_done; + u64 tx_param; + u64 rx_param; + struct device *dma_dev; + dma_addr_t dma_addr; + + /* Bus interface info */ + void *priv; +#ifdef CONFIG_DEBUG_FS + struct dentry *debugfs; +#endif +}; + +#define rk2818_readl(dw, off) \ + __raw_readl(dw->regs + off) +#define rk2818_writel(dw,off,val) \ + __raw_writel(val, dw->regs + off) +#define rk2818_readw(dw, off) \ + __raw_readw(dw->regs + off) +#define rk2818_writew(dw,off,val) \ + __raw_writel(val, dw->regs + off) + +static inline void spi_enable_chip(struct rk2818_spi *dws, int enable) +{ + rk2818_writel(dws, SPIM_SPIENR, (enable ? 1 : 0)); +} + +static inline void spi_set_clk(struct rk2818_spi *dws, u16 div) +{ + rk2818_writel(dws, SPIM_BAUDR, div); +} + +static inline void spi_chip_sel(struct rk2818_spi *dws, u16 cs) +{ + if (cs > 1) + return; + + if (dws->cs_control){ + if(cs == 1) + dws->cs_control(2); + else + dws->cs_control(1); + } + rk2818_writel(dws, SPIM_SER, 1 << cs); +} + +/* Disable IRQ bits */ +static inline void spi_mask_intr(struct rk2818_spi *dws, u32 mask) +{ + u32 new_mask; + + new_mask = rk2818_readl(dws, SPIM_IMR) & ~mask; + rk2818_writel(dws, SPIM_IMR, new_mask); +} + +/* Enable IRQ bits */ +static inline void spi_umask_intr(struct rk2818_spi *dws, u32 mask) +{ + u32 new_mask; + + new_mask = rk2818_readl(dws, SPIM_IMR) | mask; + rk2818_writel(dws, SPIM_IMR, new_mask); +} + +/* + * Each SPI slave device to work with rk2818_api controller should + * has such a structure claiming its working mode (PIO/DMA etc), + * which can be save in the "controller_data" member of the + * struct spi_device + */ +struct rk2818_spi_chip { + u8 poll_mode; /* 0 for contoller polling mode */ + u8 type; /* SPI/SSP/Micrwire */ + u8 enable_dma; + void (*cs_control)(u32 command); +}; + +#endif /* __DRIVERS_SPIM_RK2818_HEADER_H */ -- 2.34.1