diff options
Diffstat (limited to 'drivers/spi/wmt_spi_1.c')
-rwxr-xr-x | drivers/spi/wmt_spi_1.c | 1363 |
1 files changed, 1363 insertions, 0 deletions
diff --git a/drivers/spi/wmt_spi_1.c b/drivers/spi/wmt_spi_1.c new file mode 100755 index 00000000..82398071 --- /dev/null +++ b/drivers/spi/wmt_spi_1.c @@ -0,0 +1,1363 @@ +/*++ + drivers/spi/wmt_spi_1.c + + Copyright (c) 2008 WonderMedia Technologies, Inc. + + 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, see <http://www.gnu.org/licenses/>. + + WonderMedia Technologies, Inc. + 10F, 529, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C. +--*/ +#include <linux/init.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/spinlock.h> +#include <linux/workqueue.h> +#include <linux/ioport.h> +#include <linux/dma-mapping.h> +#include <linux/sched.h> + +#include <mach/wmt_env.h> + +#include <mach/hardware.h> +#include <mach/wmt_gpio.h> + +//#define DEBUG 1 /* debug open */ +#include <linux/platform_device.h> +#include <linux/spi/spi.h> +#include <mach/wmt-spi.h> + +#ifdef DEBUG +#define spi_dbg(fmt, args...) printk(KERN_ERR "[%s]_%d: " fmt, __func__ , __LINE__, ## args) +#define spi_trace() printk(KERN_ERR "trace in %s %d\n", __func__, __LINE__); +#else +#define spi_dbg(fmt, args...) +#define spi_trace() +#endif + +#define GPIO_SPI1_MISO BIT2 +#define GPIO_SPI1_MOSI BIT3 +#define GPIO_SPI1_CLK BIT6 +#define GPIO_SPI1_SS0 BIT7 + +#define GPIO_SPI1_SS1 BIT7 +#define GPIO_SPI1_SS2 BIT0 +#define GPIO_SPI1_SS3 BIT1 +#define MASTER 1 + +static unsigned int pllb_input_freq = 83333; +static bool g_use_ss0 = true; +static bool g_use_ss1 = false; +static bool g_use_ss2 = false; +static bool g_use_ss3 = false; + +/* + * spi_set_reg32 - write a u32 value to spi register + * @spi: spi controller's driver data + * @reg_offset: register's offset address + * @val: value register will be set + */ +static inline void +spi_set_reg32(struct wmt_spi *spi, u32 reg_offset, u32 val) +{ + iowrite32(val, spi->regs_base + reg_offset); +} + +/* + * spi_get_reg32 - read a u32 value from spi register + * @spi: spi controller's driver data + * @reg_offset: register's offset address + */ +static inline unsigned int +spi_get_reg32(struct wmt_spi *spi, int reg_offset) +{ + return ioread32(spi->regs_base + reg_offset); +} + +/* + * spi_setbit: write bit1 to related register's bit + * @spi: spi controller's driver data + * @offset: register's offset address + * @mask: bit setting mask + */ +static void +spi_setbit(struct wmt_spi *spi, u32 reg_offset, u32 mask) +{ + u32 tmp; + tmp = spi_get_reg32(spi, reg_offset); + tmp |= mask; + spi_set_reg32(spi, reg_offset, tmp); +} + +/* + * spi_clrbit: write bit0 to related register's bit + * @spi: spi controller's driver data + * @offset: register's offset address + * @mask: bit setting mask + */ +static void +spi_clrbit(struct wmt_spi *spi, u32 reg_offset, u32 mask) +{ + u32 tmp; + tmp = spi_get_reg32(spi, reg_offset); + tmp &= ~mask; + spi_set_reg32(spi, reg_offset, tmp); +} + +/* + * spi_write_fifo: write a u8 value to spi tx fifo + * @spi: spi controller's driver data + * @fifo_reg: spi tx fifo register offset + * @val: value writen to spi tx fifo + */ +static inline void +spi_write_fifo(struct wmt_spi *spi, u32 fifo_reg, const u8 val) +{ + iowrite8(val, spi->regs_base + fifo_reg); +} + +/* + * spi_read_fifo: read a u8 value from spi rx fifo + * @spi: spi controller's driver data + * @fifo_reg: spi rx fifo register offset + */ +static inline u8 +spi_read_fifo(struct wmt_spi *spi, u32 fifo_reg) +{ + return ioread8(spi->regs_base + fifo_reg); +} + +/* + * wmt_spi_clock_enable: set GPIO to SPI mode, ten enable spi + * clock from Power management module and set default spi clock + * dividor to 0x04 as default (93Mhz) + */ +static inline void wmt_spi_clock_enable(void) +{ + int timeout = POLLING_SPI_REG_TIMEOUT; + + /*Enable SPI function and disable GPIO function for related SPI pin*/ + GPIO_CTRL_GP18_UART_BYTE_VAL &= ~(GPIO_SPI1_CLK | GPIO_SPI1_MISO | + GPIO_SPI1_MOSI | GPIO_SPI1_SS0); + + /* miso/mosi : pull up + * clk : pull down + */ + PULL_EN_GP18_UART_BYTE_VAL |= (GPIO_SPI1_CLK | GPIO_SPI1_MISO | GPIO_SPI1_MOSI); + PULL_CTRL_GP18_UART_BYTE_VAL |= (GPIO_SPI1_MISO |GPIO_SPI1_MOSI); + PULL_CTRL_GP18_UART_BYTE_VAL &= ~(GPIO_SPI1_CLK); + + if (g_use_ss0) { + GPIO_CTRL_GP18_UART_BYTE_VAL &= ~GPIO_SPI1_SS0; + PULL_EN_GP18_UART_BYTE_VAL |= GPIO_SPI1_SS0; + PULL_CTRL_GP18_UART_BYTE_VAL |= GPIO_SPI1_SS0; + } + +#if 0 + if (g_use_ss1) { + GPIO_CTRL_GP0_BYTE_VAL &= ~GPIO_SPI1_SS1; + GPIO_PULL_EN_GP0_BYTE_VAL |= GPIO_SPI1_SS1; + GPIO_PULL_CTRL_GP0_BYTE_VAL |= GPIO_SPI1_SS1; + } + if (g_use_ss2) { + GPIO_CTRL_GP1_BYTE_VAL &= ~GPIO_SPI1_SS2; + GPIO_PULL_EN_GP1_BYTE_VAL |= GPIO_SPI1_SS2; + GPIO_PULL_CTRL_GP1_BYTE_VAL |= GPIO_SPI1_SS2; + } + if (g_use_ss3) { + GPIO_CTRL_GP1_BYTE_VAL &= ~GPIO_SPI1_SS3; + GPIO_PULL_EN_GP1_BYTE_VAL |= GPIO_SPI1_SS3; + GPIO_PULL_CTRL_GP1_BYTE_VAL |= GPIO_SPI1_SS3; + } +#endif + + PIN_SHARING_SEL_4BYTE_VAL |= BIT10; + + /* clock open */ + auto_pll_divisor(DEV_SPI1, CLK_ENABLE, 0, 0); + pllb_input_freq = auto_pll_divisor(DEV_SPI1, SET_DIV, 1, 100000); + pllb_input_freq /= 1000; + + /* check if config successful */ + while (timeout--) { + if (!(REG8_VAL(PMC_REG_BASE) & 0x08000000)) + return ; + udelay(1); + } + printk(KERN_ERR "Enable SPI1 clock from PMC module failed\n!"); +} + +/* + * wmt_spi_clock_disable: reset GPIO and disable spi clock from + * Power management module + */ +static inline void wmt_spi_clock_disable(void) +{ +#if 0 + GPIO_CTRL_GP11_SPI_BYTE_VAL |= (GPIO_SPI0_CLK | + GPIO_SPI0_MOSI| + GPIO_SPI0_SS| + GPIO_SPI0_MISO| + GPIO_SPI0_SSB); +#endif +} + +/* + * spi_is_busy: check spi controller(master) is busy or not + * @spi: spi controller's driver data + */ +static int spi_is_busy(struct wmt_spi *spi) +{ + unsigned int timeout = POLLING_SPI_REG_TIMEOUT; + while (timeout--) { + if ((spi_get_reg32(spi, SPI_SR) & SPI_SR_BUSY_MASK) == 0) + return 0; + } + return -EAGAIN; +} + +/* + * spi_enable: enable spi module + * @spi: spi controller's driver data + */ +static void spi_enable(struct wmt_spi *spi) +{ + spi_setbit(spi, SPI_CR, SPI_CR_ME_MASK); +} + +/* + * spi_disable: disable spi module + * @spi: spi controller's driver data + */ +static void spi_disable(struct wmt_spi *spi) +{ + if (spi_is_busy(spi)) { + dev_warn(&spi->master->dev, "Disable spi controller failed\n"); + } + spi_clrbit(spi, SPI_CR, SPI_CR_ME_MASK); +} + +/* + * spi_tx_is_finish: polling if data in tx fifo has been sent out + * @spi: spi controller's driver data + */ +static int spi_tx_is_finish(struct wmt_spi *spi) +{ + unsigned int timeout = POLLING_SPI_REG_TIMEOUT; + while (timeout--) { + if (spi_get_reg32(spi, SPI_SR) | SPI_SR_TFEI_MASK) + return 0; + } + return -EAGAIN; +} + +/* + * spi_rx_is_finish: polling if data in rx fifo has been read out + * @spi: spi controller's driver data + */ +static int spi_rx_is_finish(struct wmt_spi *spi) +{ + unsigned int timeout = POLLING_SPI_REG_TIMEOUT; + while (timeout--) { + if ((spi_get_reg32(spi, SPI_SR) | SPI_SR_RFEI_MASK)) + return 0; + } + return -EAGAIN; +} + +/* + * wmt_spi_cs_active: enable chip select signal. SSN is driven + * low to active if spi controller use SSN_CTRL_PROGRAM mode + * @spi: spi controller's driver data + */ +static inline void wmt_spi_cs_active(struct spi_device *spi_dev, struct wmt_spi *spi) +{ + struct wmt_spi_hw *hw_info = spi->spi_hw_info; + + /* enable SSN */ + if (hw_info->ssn_ctrl == SSN_CTRL_PROGRAM) + spi_clrbit(spi, SPI_DFCR, SPI_DFCR_DSV_MASK | SPI_DFCR_DSE_MASK); +} + +/* + * wmt_spi_cs_inactive: disable chip select signal. A SSN is driven + * high to inactive if spi controller use SSN_CTRL_PROGRAM mode + * @spi: spi controller's driver data + */ +static inline void wmt_spi_cs_inactive(struct spi_device *spi_dev, struct wmt_spi *spi) +{ + struct wmt_spi_hw *hw_info = spi->spi_hw_info; + + if (hw_info->ssn_ctrl == SSN_CTRL_PROGRAM) + spi_setbit(spi, SPI_DFCR, SPI_DFCR_DSV_MASK | SPI_DFCR_DSE_MASK); +} + +/* + * get_spi_input_freq: get spi original input frequence from power + * management module + */ +static unsigned int get_spi_input_freq(void) +{ + return pllb_input_freq; +} + +/* + * spi_set_clock_div: set clock divider in spi control register, + * if the divisor is too low or too high, set spi working + * frequency 5Mhz as default + * @spi: spi controller's driver data + * @speed_hz: spi working clock frequency, unit is hz + */ +static void spi_set_clock_div(struct wmt_spi *spi, int speed_hz) +{ + unsigned int divisor; + unsigned int hw_freq = get_spi_input_freq()*1000; /* KHz to Hz */ + + spi_clrbit(spi, SPI_CR, SPI_CR_TCD_MASK); + if (!speed_hz) + goto err; + + divisor = hw_freq/(2*speed_hz); + if (divisor < 0 || divisor > 0x7ff) /* spi cr bit(21,31), max:0x7ff */ + goto err; + + spi_setbit(spi, SPI_CR, divisor << SPI_CR_TCD_SHIFT); + return ; + +err: + divisor = hw_freq/(2*SPI_DFLT_FREQ); + spi_setbit(spi, SPI_CR, divisor << SPI_CR_TCD_SHIFT); + dev_err(&spi->master->dev, "SPI frequency %dhz not support, " \ + "set %dhz as default\n", speed_hz, SPI_DFLT_FREQ); + return ; +} + +/* + * spi_set_bit_order: set spi redeive/transmit significant bit order + * @spi: spi controller's driver data + * @mode: spi device working mode + */ +static void spi_set_bit_order(struct wmt_spi *spi, u8 mode) +{ + if (mode & SPI_LSB_FIRST) + spi_setbit(spi, SPI_DFCR, SPI_DFCR_RSBO_MASK | SPI_DFCR_TSBO_MASK); +} + +/* + * spi_set_clock_mode: set spi clock polarity and phase (spi clock mode) + * @spi: spi controller's driver data + * @clk_mode: spi clock mode + */ +static void spi_set_clock_mode(struct wmt_spi *spi, u8 clk_mode) +{ + spi_clrbit(spi, SPI_CR, SPI_CR_CPHS_MASK); + if (clk_mode > SPI_CLK_MODE3) + goto err; + spi_setbit(spi, SPI_CR, clk_mode << SPI_CR_CPHS_SHIFT); + return ; +err: + spi_setbit(spi, SPI_CR, SPI_CLK_MODE3 << SPI_CR_CPHS_SHIFT); + dev_err(&spi->master->dev, "clock mode err, set clock mode 3 as default\n"); + return ; +} + +/* + * spi_as_master: spi master/slave select + * @spi: spi controller's driver data + * @is_master: if spi configured as master, is_master = 1, else is_master = 0 + */ +static void spi_as_master(struct wmt_spi *spi, u8 is_master) +{ + if (is_master) + spi_clrbit(spi, SPI_CR, SPI_CR_MSMS_MASK); + else + spi_setbit(spi, SPI_CR, SPI_CR_MSMS_MASK); +} + +/* + * spi_reset_tx_fifo: reset spi transmit fifo + * @spi: spi controller's driver data + */ +static int spi_reset_tx_fifo(struct wmt_spi *spi) +{ + unsigned int timeout = POLLING_SPI_REG_TIMEOUT; + spi_setbit(spi, SPI_CR, SPI_CR_TFR_MASK); + while (timeout--) { + if ((spi_get_reg32(spi, SPI_CR) & SPI_CR_TFR_MASK) == 0) + return 0; + } + return -1; +} + +/* + * spi_reset_rx_fifo: reset spi receive fifo + * @spi: spi controller's driver data + */ +static int spi_reset_rx_fifo(struct wmt_spi *spi) +{ + unsigned int timeout = POLLING_SPI_REG_TIMEOUT; + spi_setbit(spi, SPI_CR, SPI_CR_RFR_MASK); + while (timeout--) { + if ((spi_get_reg32(spi, SPI_CR) & SPI_CR_RFR_MASK) == 0) + return 0; + } + return -EAGAIN; +} + +/* + * spi_reset_fifo: reset both spi transmit fifo and receive fifo + * @spi: spi controller's driver data + */ +static int spi_reset_fifo(struct wmt_spi *spi) +{ + if (spi_reset_tx_fifo(spi)) + return -EAGAIN; + if (spi_reset_rx_fifo(spi)) + return -EAGAIN; + return 0; +} + +/* + * spi_reset: reset spi status register and reset spi tx and rx fifo + * @spi: spi controller's driver data + */ +static int spi_reset(struct wmt_spi *spi) +{ + spi_set_reg32(spi, SPI_SR, ~0UL); + return spi_reset_fifo(spi); +} + +/* + * spi_reset_transfer_speed: reset spi work frequency according + * to spi_transfer + * @spi: spi controller's driver data + * @t: spi transfer + */ +static void +spi_reset_transfer_speed(struct wmt_spi *spi, struct spi_transfer *t) +{ + if (t && t->speed_hz) + spi_set_clock_div(spi, t->speed_hz); +} + +/* + * wmt_spi_regs_config: config spi registers according to the + * information get from spi_device + @spi_dev: Master side proxy for an SPI slave device + */ +static int wmt_spi_regs_config(struct spi_device *spi_dev) +{ + int ret = 0; + u8 clk_mode; + struct wmt_spi *wmt_spi; + struct wmt_spi_slave *slave_info; + struct wmt_spi_hw *hw_info; + + wmt_spi = spi_master_get_devdata(spi_dev->master); + slave_info = spi_dev->controller_data; + hw_info = wmt_spi->spi_hw_info; + + /* clear spi control register */ + spi_set_reg32(wmt_spi, SPI_CR, 0x00UL); + /* clear spi status register */ + spi_set_reg32(wmt_spi, SPI_SR, ~0UL); + /* spi cre register */ + spi_set_reg32(wmt_spi, SPI_CRE, 0x20); + + /* reset tx and rx fifo */ + ret = spi_reset_fifo(wmt_spi); + if (ret) + goto err; + /* setting spi controller register */ + /* 1. set spi clock divider */ + spi_set_clock_div(wmt_spi, spi_dev->max_speed_hz); + /* 2 set clock mode */ + clk_mode = (spi_dev->mode & (BIT0 | BIT1)); + spi_set_clock_mode(wmt_spi, clk_mode); + /* 3. spi as master */ + spi_as_master(wmt_spi, 1); + /* 4. slave selection */ + /* + spi_setbit(wmt_spi, SPI_CR, spi_dev->chip_select << SPI_CR_SS_SHIFT); + */ + /*for wm3465 A0*/ + spi_setbit(wmt_spi, SPI_CR, 0 << SPI_CR_SS_SHIFT); + + /* setting spi data format control register */ + /* 1. port mode setting */ + if (PORT_MODE_PTP == hw_info->port_mode) + spi_setbit(wmt_spi, SPI_DFCR, SPI_DFCR_SPM_MASK); + /* 2. spi tx/rx significant bit order */ + spi_set_bit_order(wmt_spi, spi_dev->mode); + /* 3. ssn control setting */ + if (SSN_CTRL_PROGRAM == hw_info->ssn_ctrl) { + if ((clk_mode & SPI_CR_CPHS_MASK) == 0) { + dev_warn(&spi_dev->dev, "SSN_ctrl conflict with clock mode\n"); + /* do not abort now, the conflict is not a serious problem, + driver can handle this well, so we work on */ + goto err; + } + spi_setbit(wmt_spi, SPI_DFCR, SPI_DFCR_SC_MASK); + } + return 0; + +err: + dev_err(&wmt_spi->pdev->dev, "SPI config register error\n"); + return ret; +} + +/* + * wmt_spi_fifo_tx: transmit data by spi TX_FIFO. + * @spi: spi controller's driver data + * @tx_buf: data transmited by spi tx fifo + * @len: tx buffers length. Note: len is not checked in this function, + * and wmt spi fifo size is 32bytes, so caller must be sure + * len <= 32bytes + */ +static void +wmt_spi_fifo_tx(struct wmt_spi *spi, const u8 *tx_buf, int len) +{ + int i; + /* load data to tx fifo */ + if (tx_buf) { + for (i = 0; i < len; i++) + spi_write_fifo(spi, SPI_TXFIFO, tx_buf[i]); + } /* load idle data to tx fifo */else { + for (i = 0; i < len; i++) + spi_write_fifo(spi, SPI_TXFIFO, 0x00); + } +} + +/* + * wmt_spi_write_read: spi transfer routine + * @spi_dev: Master side proxy for an SPI slave device + * @t: spi transfer + * wmt spi transmit fifo is SPI_FIFO_SIZE(32bytes), + * so if transmit data more than 32bytes, we need transmit + * those data more than one time + **/ +static int +wmt_spi_write_read(struct spi_device *spi, struct spi_transfer *t) +{ + int i = 0; + const u8 *tx = t->tx_buf; + u8 *rx = t->rx_buf; + unsigned int cnt = t->len; + struct wmt_spi *wmt_spi = spi_master_get_devdata(spi->master); + + /* reset spi first */ + if (spi_reset(wmt_spi)) + goto out; + /* if data length more than SPI_FIFO_SIZE(32bytes) */ + while (cnt >= SPI_FIFO_SIZE) { + /* load data form tx_buf to tx_fifo */ + wmt_spi_fifo_tx(wmt_spi, tx, SPI_FIFO_SIZE); + /* send data */ + spi_enable(wmt_spi); + /* waitting for send finish */ + if (spi_tx_is_finish(wmt_spi)) + goto out; + /* data send has finished */ + spi_disable(wmt_spi); + /* read data and stroe in rx_buf */ + if (rx) { + for (i = 0; i < SPI_FIFO_SIZE; i++) + *rx++ = spi_read_fifo(wmt_spi, SPI_RXFIFO); + } + /* check if rx finish */ + if (spi_rx_is_finish(wmt_spi)) + goto out; + if (tx) + tx += SPI_FIFO_SIZE; + cnt -= SPI_FIFO_SIZE; + if (t->speed_hz < 5*1000*1000) + udelay(2000); + /* reset for the next transfer */ + if (spi_reset(wmt_spi)) + goto out; + } + /* remain data transfer */ + if (cnt && cnt < SPI_FIFO_SIZE) { + wmt_spi_fifo_tx(wmt_spi, tx, cnt); + spi_enable(wmt_spi); + if (spi_tx_is_finish(wmt_spi)) + goto out; + spi_disable(wmt_spi); + if (rx) { + for (; cnt > 0; cnt--) + *rx++ = spi_read_fifo(wmt_spi, SPI_RXFIFO); + } else { + for (; cnt > 0; cnt--) + spi_read_fifo(wmt_spi, SPI_RXFIFO); + } + } +out: + return (t->len - cnt); +} + +/* + * spi_dsr_w: spi dma transmit callback function + * @arg: point to wmt_spi + **/ +static void spi_dsr_w(void *arg) +{ + struct wmt_spi *spi = (struct wmt_spi *)arg; + struct wmt_spi_dma *spi_dma = spi->spi_dma_info; + spi_dma->tx_ack = 1; + wake_up_interruptible(&spi_dma->tx_event); +} + +/* + * spi_dsr_r: spi dma receive callback function + * @arg: point to wmt_spi + **/ +static void spi_dsr_r(void *arg) +{ + struct wmt_spi *spi = (struct wmt_spi *)arg; + struct wmt_spi_dma *spi_dma = spi->spi_dma_info; + spi_dma->rx_ack = 1; + wake_up_interruptible(&spi_dma->rx_event); +} + +/* + * wmt_spi_dma_write_read: spi transfer (use DMA mode) + * @spi_dev: Master side proxy for an SPI slave device + * @t: spi transfer1 + **/ +static int +wmt_spi_dma_write_read(struct spi_device *spi_dev, struct spi_transfer *t) +{ + struct wmt_spi *wmt_spi = spi_master_get_devdata(spi_dev->master); + struct wmt_spi_dma *spi_dma = wmt_spi->spi_dma_info; + wait_queue_head_t *event = &spi_dma->tx_event; + volatile int *ack = &spi_dma->tx_ack; + unsigned int transfered_cnt = 0; + u32 ctrl; + + /* spi dma transfer need cs inactive first*/ + wmt_spi_cs_inactive(spi_dev, wmt_spi); + if (t->speed_hz > 15000000) + spi_set_clock_div(wmt_spi, 15000000); + ctrl = spi_get_reg32(wmt_spi, SPI_CR); + ctrl |= SPI_CR_DRC_MASK | SPI_CR_RFTS_MASK | SPI_CR_TFTS_MASK; + spi_set_reg32(wmt_spi, SPI_CR, ctrl); + /* reset spi fifo */ + if (spi_reset(wmt_spi)) + goto out; + /* tx dma buffer prepare */ + if (t->tx_buf) + memcpy(spi_dma->io_waddr, t->tx_buf, t->len); + /* tx dma request */ + if (wmt_request_dma(&spi_dma->tx_ch, "wmt_spi_tx", + spi_dma->tx_config.DeviceReqType, + spi_dsr_w, wmt_spi)) { + dev_err(&spi_dev->dev, "SPI request TX DMA failed\n"); + goto out; + } + /* rx dma request and start */ + if (t->rx_buf) { + if (wmt_request_dma(&spi_dma->rx_ch, "wmt_spi_rx", + spi_dma->rx_config.DeviceReqType, + spi_dsr_r, wmt_spi)) { + dev_err(&spi_dev->dev, "SPI request RX DMA failed\n"); + goto free_tx_dma; + } + wmt_setup_dma(spi_dma->rx_ch, spi_dma->rx_config); + wmt_start_dma(spi_dma->rx_ch, spi_dma->phys_raddr, 0x00, t->len); + event = &spi_dma->rx_event; + ack = &spi_dma->rx_ack; + } + /* transmit dma setup and start */ + wmt_setup_dma(spi_dma->tx_ch, spi_dma->tx_config); + wmt_start_dma(spi_dma->tx_ch, spi_dma->phys_waddr, 0x00, t->len + 7); + /* enable spi and active chipselect signal */ + msleep(2); + spi_enable(wmt_spi); + /* waitting for transmit finish */ + msleep(2); + wmt_spi_cs_active(spi_dev, wmt_spi); + /* waitting transfer finish */ + if (!wait_event_interruptible_timeout(*event, *ack, 100)) { + dev_err(&spi_dev->dev, "SPI DMA transfer failed\n"); + goto out; + } + transfered_cnt = t->len; + /* if RX buf is not empty, copy received data from dma to t->rx_buf */ + if (t->rx_buf) { + memcpy(t->rx_buf, spi_dma->io_raddr, t->len); + wmt_free_dma(spi_dma->rx_ch); + memset(spi_dma->io_raddr, 0x00, t->len); + } +free_tx_dma: + wmt_free_dma(spi_dma->tx_ch); + memset(spi_dma->io_waddr, 0x00, t->len); +out: + spi_disable(wmt_spi); + spi_dma->rx_ack = spi_dma->tx_ack = 0; + return transfered_cnt; +} + +/* + * wmt_spi_work: wmt spi controller work queue routine function. + * Check every message which queued in workqueue (wmt_spi->queue), + * if all things correct, message dequeued and begin transfer routine + * @work: work which queued in spi controller's work queue + */ +static void wmt_spi_work(struct work_struct *work) +{ + struct wmt_spi *wmt_spi; + struct wmt_spi_slave *spi_ctrl; + + wmt_spi = container_of(work, struct wmt_spi, work); + spin_lock_irq(&wmt_spi->spinlock); + + while (!list_empty(&wmt_spi->queue)) { + int status = 0; + u8 cs_active = 0; + struct spi_message *m = NULL; + struct spi_transfer *t = NULL; + struct spi_device *spi; + + m = container_of(wmt_spi->queue.next, struct spi_message, queue); + spi_ctrl = m->spi->controller_data; + + list_del_init(&m->queue); + spin_unlock_irq(&wmt_spi->spinlock); + + spi = m->spi; + status = wmt_spi_regs_config(m->spi); + if (status < 0) + goto msg_done; + /* check every transfer which queued in message, + * if all things right, begin data transfer */ + list_for_each_entry(t, &m->transfers, transfer_list) { + spi_reset_transfer_speed(wmt_spi, t); + + if (!cs_active) { + wmt_spi_cs_active(spi, wmt_spi); + cs_active = 1; + } + + /* data transfer begins here */ + if (t->len) { + if (wmt_spi->spi_hw_info->dma_support && m->is_dma_mapped + && spi_ctrl->dma_en && t->len > SPI_FIFO_SIZE) { + m->actual_length += wmt_spi_dma_write_read(m->spi, t); + } else { + m->actual_length += wmt_spi_write_read(m->spi, t); + } + } + /* some device need this feature support */ + if (t->delay_usecs) + udelay(t->delay_usecs); + + /* if cs need change in next transfer, just inactive it */ + if (t->cs_change) { + wmt_spi_cs_inactive(spi, wmt_spi); + cs_active = 0; + } + } + +msg_done: + if (status < 0) + dev_err(&wmt_spi->master->dev, "SPI transfer error!\n"); + if (cs_active) + wmt_spi_cs_inactive(spi, wmt_spi); + m->status = status; + if (m->complete && m->context) + m->complete(m->context); + spin_lock_irq(&wmt_spi->spinlock); + } + spin_unlock(&wmt_spi->spinlock); +} + +/* + * bits_per_word_is_support: check if spi controller can support + * this bits_per_word type or not + * @spi: spi controller's driver data + * @bit_per_word: bits_per_word type will be check + */ +static int +bits_per_word_is_support(struct wmt_spi *spi, u8 bit_per_word) +{ + int ret = 0; + struct wmt_spi_hw *hw_info = spi->spi_hw_info; + + if ((bit_per_word != 8) && (bit_per_word != 16 )) { + ret = -EINVAL; + goto err; + } + + if ((bit_per_word == 8) && + !(hw_info->bits_per_word_en & BITS8_PER_WORD_EN)) { + ret = -EINVAL; + goto err; + } + + if ((bit_per_word == 16) && + !(hw_info->bits_per_word_en & BITS16_PER_WORD_EN)) { + ret = -EINVAL; + goto err; + } +err: + return ret; +} + +/* + * speed_hz_is_support: check spi controller can support this + * frequency or not + * @spi: spi controller's driver data + * @speed_hz: frequency spi controller will working with + */ +static int speed_hz_is_support(struct wmt_spi *spi, u32 speed_hz) +{ + int ret = 0; + struct wmt_spi_hw *hw_info = spi->spi_hw_info; + + if (speed_hz < hw_info->min_freq_hz) { + ret = -EINVAL; + goto err; + } + + if (speed_hz > hw_info->max_freq_hz) { + ret = -EINVAL; + } + +err: + return ret; +} + +/* + * wmt_spi_transfer: check transfers queued in spi message, + * then queued spi message in workqueue + *@spi_dev: Master side proxy for an SPI slave device + * @m: spi message which will be added to the queue + */ +/* bidirectional bulk transfers + * + * + The transfer() method may not sleep; its main role is + * just to add the message to the queue. + * + For now there's no remove-from-queue operation, or + * any other request management + * + To a given spi_device, message queueing is pure fifo + * + * + The master's main job is to process its message queue, + * selecting a chip then transferring data + * + If there are multiple spi_device children, the i/o queue + * arbitration algorithm is unspecified (round robin, fifo, + * priority, reservations, preemption, etc) + * + * + Chipselect stays active during the entire message + * (unless modified by spi_transfer.cs_change != 0). + * + The message transfers use clock and SPI mode parameters + * previously established by setup() for this device + */ +static int +wmt_spi_transfer(struct spi_device *spi_dev, struct spi_message *m) +{ + struct wmt_spi *wmt_spi = spi_master_get_devdata(spi_dev->master); + struct spi_transfer *t = NULL; + unsigned long flags = 0; + + m->actual_length = 0; + m->status = 0; + /* reject invalid messages and transfers */ + if (list_empty(&m->transfers) || !m->complete) { + //if (list_empty(&m->transfers)) { // for none sleep + dev_err(&spi_dev->dev, "msg rejected: invalid message of transfer\n"); + goto msg_reject; + } + /* transfers members bits_per_word and speed_hz checking and setting */ + list_for_each_entry(t, &m->transfers, transfer_list) { + if ((!t->tx_buf) && (!t->rx_buf) && t->len) { + dev_err(&spi_dev->dev, "msg rejected: invalid message data\n"); + goto msg_reject; + } + + /* checking transfer length */ + if (t->len > wmt_spi->spi_hw_info->max_transfer_length) { + dev_err(&spi_dev->dev, "msg rejected: transfer lenth can not be"\ + "bigger than %d, please split it into smaller chunks"\ + "and submit them separately\n", + wmt_spi->spi_hw_info->max_transfer_length); + goto msg_reject; + } + + /* checking and resetting transfer bits_per_word */ + if (!t->bits_per_word + || bits_per_word_is_support(wmt_spi, t->bits_per_word)) + t->bits_per_word = spi_dev->bits_per_word; + + /* checking and resetting transfer speed_hz */ + if (!t->speed_hz || speed_hz_is_support(wmt_spi, t->speed_hz)) + t->speed_hz = spi_dev->max_speed_hz; + } + spin_lock_irqsave(&wmt_spi->spinlock, flags); + /* add this message into spi's queue */ + list_add_tail(&m->queue, &wmt_spi->queue); + /* doing message transfer routine in spi work */ + queue_work(wmt_spi->workqueue, &wmt_spi->work); + spin_unlock_irqrestore(&wmt_spi->spinlock, flags); + return 0; + +msg_reject: + m->status = -EINVAL; + if (m->complete) + m->complete(m->context); + return -EINVAL; +} + +/* + * wmt_spi_setup: updates the device mode, bits_per_word and clocking + * records used by a device's SPI controller; protocol code may call + * this. This must fail if an unrecognized or unsupported mode is + * requested.It's always safe to call this unless transfers are pending + * on the device whose settings are being modified. + * @spi_dev: Master side proxy for an SPI slave device + */ +#define MODEBITS (SPI_CPOL | SPI_CPHA | SPI_LOOP | SPI_LSB_FIRST) +static int wmt_spi_setup(struct spi_device *spi_dev) +{ + int ret = 0; + struct wmt_spi *wmt_spi; + struct wmt_spi_slave *slave_info; + struct wmt_spi_hw *hw_info; + + wmt_spi = spi_master_get_devdata(spi_dev->master); + slave_info = spi_dev->controller_data; + hw_info = wmt_spi->spi_hw_info; + + /* mode checking */ + if (spi_dev->mode & ~MODEBITS) { + dev_err(&spi_dev->dev, "SPI unsupported this mode 0x%08x\n", + spi_dev->mode & ~MODEBITS); + ret = -EINVAL; + goto err; + } + + /* bits_per_word checking */ + if (!spi_dev->bits_per_word) + spi_dev->bits_per_word = slave_info->bits_per_word; + + if (spi_dev->bits_per_word) { + if (bits_per_word_is_support(wmt_spi, spi_dev->bits_per_word)) { + if (bits_per_word_is_support(wmt_spi, + slave_info->bits_per_word)) { + dev_err(&spi_dev->dev, "SPI unsupport %d and %dbits_per_word\n", + spi_dev->bits_per_word, slave_info->bits_per_word); + ret = -EINVAL; + goto err; + } + spi_dev->bits_per_word = slave_info->bits_per_word; + } + } + + /* max_speed_hz checking */ + if ((spi_dev->max_speed_hz == 0) || + (spi_dev->max_speed_hz > hw_info->max_freq_hz)) + spi_dev->max_speed_hz = hw_info->max_freq_hz; + + if (spi_dev->max_speed_hz < hw_info->min_freq_hz) { + dev_err(&spi_dev->dev, "SPI unspport speed lower than %dhz\n", + hw_info->min_freq_hz); + ret = -EINVAL; + goto err; + } + return 0; + +err: + dev_err(&spi_dev->dev, "SPI setup spi device failed!\n"); + return ret; +} + +/* + * wmt_spi_cleanup: called on module_exit to free memory + * provided by spi_master + * @wmt_spi: spi controller driver data + */ +static void wmt_spi_cleanup(struct wmt_spi *wmt_spi) +{ + if (wmt_spi->spi_hw_info) + kfree(wmt_spi->spi_hw_info); + wmt_spi->spi_hw_info = NULL; + + if (wmt_spi->spi_dma_info) + kfree(wmt_spi->spi_dma_info); + wmt_spi->spi_dma_info = NULL; +} + +/* + * spi_get_hw_info: get spi controller's configuration infomation + * form platform device + * @pdev: spi platform device structure + */ +static inline void *spi_get_hw_info(struct platform_device *pdev) +{ + return pdev->dev.platform_data; +} + +/* + * wmt_spi_dma_init: request spi dma memory and initialize spi dma info + if dma supported + * @spi: spi controller's driver data + */ +static int wmt_spi_dma_init(struct wmt_spi *spi) +{ + int ret = 0; + unsigned int dma_size; + + if (!spi->spi_hw_info) + goto out; + if (!spi->spi_hw_info->dma_support) + goto out; + spi->spi_dma_info = kmalloc(sizeof(struct wmt_spi_dma), GFP_KERNEL); + if (!spi->spi_dma_info) { + ret = -ENOMEM; + dev_err(&spi->pdev->dev, "SPI allocating dma info memory failed\n"); + goto out; + } + dma_size = spi->spi_hw_info->max_transfer_length; + /* dma read config */ + spi->spi_dma_info->rx_ch = ~0UL; + spi->spi_dma_info->rx_config.ChunkSize = SPI_DMA_CHUNK_SIZE; + spi->spi_dma_info->rx_config.DefaultCCR = SPI_RX_DMA_CFG; + spi->spi_dma_info->rx_config.DeviceReqType = SPI0_DMA_RX_REQ; + spi->spi_dma_info->rx_config.MIF1addr = SPI0_BASE_ADDR + SPI_RXFIFO; + spi->spi_dma_info->io_raddr = dma_alloc_coherent(&spi->pdev->dev, + dma_size, + &spi->spi_dma_info->phys_raddr, + GFP_KERNEL | GFP_DMA); + if (!spi->spi_dma_info->io_raddr) { + ret = -ENOMEM; + dev_err(&spi->pdev->dev, "SPI allocate rdma failed\n"); + goto out; + } + memset(spi->spi_dma_info->io_raddr, 0x00, dma_size); + spi->spi_dma_info->rx_config.MIF0addr = (ulong)spi->spi_dma_info->io_raddr; + init_waitqueue_head(&spi->spi_dma_info->rx_event); + spi->spi_dma_info->rx_ack = 0; + /* dma write config */ + spi->spi_dma_info->tx_ch = ~0UL; + spi->spi_dma_info->tx_config.ChunkSize = SPI_DMA_CHUNK_SIZE; + spi->spi_dma_info->tx_config.DefaultCCR = SPI_TX_DMA_CFG; + spi->spi_dma_info->tx_config.DeviceReqType = SPI0_DMA_TX_REQ; + spi->spi_dma_info->tx_config.MIF1addr = SPI0_BASE_ADDR + SPI_TXFIFO; + spi->spi_dma_info->io_waddr = dma_alloc_coherent(&spi->pdev->dev, + dma_size + 7, + &spi->spi_dma_info->phys_waddr, + GFP_KERNEL | GFP_DMA); + if (!spi->spi_dma_info->io_waddr) { + ret = -ENOMEM; + dev_err(&spi->pdev->dev, "SPI allocate wdma failed\n"); + goto free_spi_rx_dma; + } + memset(spi->spi_dma_info->io_waddr, 0x00, dma_size + 7); + spi->spi_dma_info->tx_config.MIF0addr = (ulong)spi->spi_dma_info->io_waddr; + init_waitqueue_head(&spi->spi_dma_info->tx_event); + spi->spi_dma_info->tx_ack = 0; + return 0; + +free_spi_rx_dma: + dma_free_coherent(&spi->pdev->dev,dma_size,spi->spi_dma_info->io_raddr, + spi->spi_dma_info->phys_raddr); +out: + return ret; +} + +/* + * wmt_spi_dma_release: release spi dma memory if dma supported + * @spi: spi controller's driver data + */ +static void wmt_spi_dma_release(struct wmt_spi *spi) +{ + if (!spi->spi_hw_info) + goto out; + if (!spi->spi_hw_info->dma_support) + goto out; + + dma_free_coherent(&spi->pdev->dev, + spi->spi_hw_info->max_transfer_length, + spi->spi_dma_info->io_raddr, + spi->spi_dma_info->phys_raddr); + dma_free_coherent(&spi->pdev->dev, + spi->spi_hw_info->max_transfer_length + 7, + spi->spi_dma_info->io_waddr, + spi->spi_dma_info->phys_waddr); +out: + return ; +} + +static int __devinit wmt_spi_probe(struct platform_device *pdev) +{ + int ret; + struct device *dev = &pdev->dev; + struct spi_master *master; + struct wmt_spi *wmt_spi; + struct wmt_spi_hw *wmt_dev_info; + struct resource *res; + + /* SPI master allocation */ + master = spi_alloc_master(dev, sizeof(struct wmt_spi)); + if (!master) { + ret = -ENOMEM; + dev_err(dev, "SPI master allocation failed!\n"); + goto the_end; + } + + /* spi controller structure initialization */ + wmt_spi = (struct wmt_spi *)spi_master_get_devdata(master); + memset(wmt_spi, 0x00, sizeof(struct wmt_spi)); + wmt_spi->master = spi_master_get(master); + wmt_spi->pdev = pdev; + wmt_dev_info = spi_get_hw_info(pdev); + wmt_spi->spi_hw_info = kmalloc(sizeof(struct wmt_spi_hw), GFP_KERNEL); + if (!wmt_spi->spi_hw_info) { + dev_err(dev, "SPI allocating hardware info memory failed\n"); + ret = -ENOMEM; + goto release_master; + } + memset(wmt_spi->spi_hw_info, 0x00, sizeof(struct wmt_spi_hw)); + memcpy(wmt_spi->spi_hw_info, wmt_dev_info, + sizeof(struct wmt_spi_hw)); + + /* spi master initialization */ + master->bus_num = pdev->id; + master->num_chipselect = wmt_dev_info->num_chipselect; + master->setup = wmt_spi_setup; + master->transfer = wmt_spi_transfer; + + /* the spi->mode bits understood by this driver: */ + master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH; + + platform_set_drvdata(pdev, wmt_spi); + + /* device resource request */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + ret = -ENXIO; + dev_err(dev, "SPI getting platform resource failed\n"); + goto release_hw_info; + } +#if 0 + if (!request_mem_region(res->start, res->end-res->start+1, pdev->name)) { + ret = -ENXIO; + dev_err(dev, "SPI request mem region failed\n"); + goto release_hw_info; + } + wmt_spi->regs_base = ioremap(res->start, res->end - res->start + 1); +#endif + wmt_spi->regs_base = (void __iomem *)SPI1_BASE_ADDR; + if (!wmt_spi->regs_base) { + ret = -ENXIO; + dev_err(dev, "SPI ioremap failed!\n"); + goto release_region; + } + + if (wmt_dev_info->dma_support) { + if (wmt_spi_dma_init(wmt_spi)) { + ret = -ENXIO; + wmt_dev_info->dma_support = 0; + dev_err(dev, "SPI dma init failed\n"); + goto release_ioremap; + } + } + + /* work queue create */ + spin_lock_init(&wmt_spi->spinlock); + INIT_LIST_HEAD(&wmt_spi->queue); + INIT_WORK(&wmt_spi->work, wmt_spi_work); + init_waitqueue_head(&wmt_spi->waitq); + wmt_spi->workqueue = create_singlethread_workqueue("wmt_spi"); + if (NULL == wmt_spi->workqueue) { + ret = -ENXIO; + dev_err(dev, "SPI create workqueue failed!\n"); + goto release_dma; + } + /* power and clock open */ + wmt_spi_clock_enable(); + + /* registe spi master at last */ + ret = spi_register_master(master); + if (ret) { + dev_err(dev, "SPI register master failed\n"); + goto release_workqueue; + } + + printk(KERN_INFO "WMT EVB SPI Controlor Driver OK!\n"); + return 0; + +release_workqueue: + wmt_spi_clock_disable(); + destroy_workqueue(wmt_spi->workqueue); +release_dma: + wmt_spi_dma_release(wmt_spi); +release_ioremap: + iounmap(wmt_spi->regs_base); +release_region: + release_mem_region(res->start, res->end - res->start + 1); +release_hw_info: + kfree(wmt_spi->spi_hw_info); + platform_set_drvdata(pdev, NULL); +release_master: + spi_master_put(master); +the_end: + dev_err(dev, "WMT EVB SPI Controlor Probe Failed!\n"); + return ret; +} + +static int wmt_spi_stop_queue(struct wmt_spi *spi) +{ + unsigned long flags; + unsigned limit = 500; + int status = 0; + + spin_lock_irqsave(&spi->spinlock, flags); + + while (!list_empty(&spi->queue) && limit--) { + spin_unlock_irqrestore(&spi->spinlock, flags); + msleep(10); + spin_lock_irqsave(&spi->spinlock, flags); + } + + if (!list_empty(&spi->queue)) + status = -EBUSY; + + spin_unlock_irqrestore(&spi->spinlock, flags); + + return status; +} + +static int __devexit wmt_spi_remove(struct platform_device *pdev) +{ + struct wmt_spi *spi; + struct wmt_spi_hw *spi_hw_info; + struct resource *res; + + spi = (struct wmt_spi *)platform_get_drvdata(pdev); + spi_hw_info = spi_get_hw_info(pdev); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + /* stop spi message queue */ + wmt_spi_stop_queue(spi); + /* SSN inactive */ + /* + wmt_spi_cs_inactive(spi); + */ + spi_disable(spi); + /* clock and power off */ + wmt_spi_clock_disable(); + /* work queue flush and destory */ + flush_workqueue(spi->workqueue); + destroy_workqueue(spi->workqueue); + /* dma free if dma support and enable */ + wmt_spi_dma_release(spi); + /* irq free */ + if (spi->irq) + free_irq(spi->irq, spi); + /* release requested resource */ + iounmap(spi->regs_base); + release_mem_region(res->start, res->end - res->start + 1); + /* driver data entry reset */ + platform_set_drvdata(pdev, NULL); + /* free memory provide by spi master */ + wmt_spi_cleanup(spi); + /* spi master unregister and free */ + spi_unregister_master(spi->master); + spi_master_put(spi->master); + + return 0; +} + +#ifdef CONFIG_PM +static int +wmt_spi_suspend(struct platform_device *pdev, pm_message_t state) +{ + int ret = 0; + struct wmt_spi *spi = platform_get_drvdata(pdev); + state = state; + + spi_dbg("Enter\n"); + ret = wmt_spi_stop_queue(spi); + if (ret) { + dev_warn(&pdev->dev, "suspend wmt spi failed\n"); + return ret; + } + /* + wmt_spi_cs_inactive(spi); + */ + spi_disable(spi); + + spi_dbg("Exit\n"); + return 0; +} + +static int wmt_spi_resume(struct platform_device *pdev) +{ + struct wmt_spi *spi = platform_get_drvdata(pdev); + + spi_dbg("Enter\n"); + /* hardware reset */ + wmt_spi_clock_enable(); + /* start msg queue */ + queue_work(spi->workqueue, &spi->work); + + spi_dbg("Exit\n"); + return 0; +} +#else +#define wmt_spi_suspend NULL +#define wmt_spi_resume NULL +#endif /* CONFIG_PM */ + +static struct platform_driver wmt_spi_driver = { + .driver = { + .name = "wmt_spi_1", + .owner = THIS_MODULE, + }, + .suspend = wmt_spi_suspend, + .resume = wmt_spi_resume, + .remove = __devexit_p(wmt_spi_remove), +}; + +static int parse_spi1_param(void) +{ + char buf[64]; + size_t l = sizeof(buf); + int uart_spi_sel = 0; + + if (wmt_getsyspara("wmt.spi1.param", buf, &l) == 0) { + sscanf(buf, "%d", &uart_spi_sel); + } + + pr_info("spi1 %s.\n", uart_spi_sel == MASTER ? "available" : "invalid"); + return (uart_spi_sel == MASTER); +} + +static int __init wmt_spi_init(void) +{ + return (parse_spi1_param()) ? + platform_driver_probe(&wmt_spi_driver, wmt_spi_probe) : 0; +} +module_init(wmt_spi_init); + +static void __exit wmt_spi_exit(void) +{ + platform_driver_unregister(&wmt_spi_driver); +} +module_exit(wmt_spi_exit); + +MODULE_AUTHOR("WonderMedia Technologies, Inc."); +MODULE_DESCRIPTION("WMT SPI Controller Driver"); +MODULE_LICENSE("GPL"); |