/* spi-s3c2443.c * * Copyright (C) 2006 Samsung Electronics Co. Ltd. * * S3C2443 SPI Controller * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined (CONFIG_CPU_S3C2443) #include #elif defined (CONFIG_CPU_S3C2450) #include #elif defined (CONFIG_CPU_S3C2416) #include #else # error CPU Clock source is not defined. #endif #include #include #include #include #include "spi-dev.h" #include "hspi-s3c2443.h" #define CONFIG_AUTO_nSS 0 #define CONFIG_DO_SWRST 1 #define CONFIG_USE_PKTCNT 1 #define CONFIG_SPI_PRESCALER 1 #define CONFIG_TX_LVL 0x20 #define CONFIG_RX_LVL 0x10 #define CONFIG_TEST_EINT3 0 #define CONFIG_TEST_TXBUF_SIZE 0 #define CONFIG_TEST_RXBUF_SIZE 0 static int fgTxWait = 0; static int fgRxWait = 0; #define SPI_STUS_ERR_ALL (SPI_STUS_RX_OVERRUN_ERR \ | SPI_STUS_RX_UNDERRUN_ERR \ | SPI_STUS_TX_OVERRUN_ERR \ | SPI_STUS_TX_UNDERRUN_ERR \ ) #define SPI_STUS_RX_FIFO_LVL (0x7F << 13) #define SPI_STUS_TX_FIFO_LVL (0x7F << 6) static void s3c_spi_wifi_if_on(void); static void s3c_spi_wifi_if_off(void); //#undef debug //#define debug #ifdef debug #define DBG(x...) printk(x) #define DEBUG printk("%s :: %d\n",__FUNCTION__,__LINE__) void print_reg(struct s3c_spi *spi) { printk("CH_CFG = 0x%08x\n",readl(spi->regs + S3C_CH_CFG)); printk("CLK_CFG = 0x%08x\n",readl(spi->regs + S3C_CLK_CFG)); printk("MODE_CFG = 0x%08x\n",readl(spi->regs + S3C_MODE_CFG)); printk("SLAVE_CFG = 0x%08x\n",readl(spi->regs + S3C_SLAVE_SEL)); printk("INT_EN = 0x%08x\n",readl(spi->regs + S3C_SPI_INT_EN)); printk("SPI_STATUS = 0x%08x\n",readl(spi->regs + S3C_SPI_STATUS)); printk("PACKET_CNT = 0x%08x\n",readl(spi->regs + S3C_PACKET_CNT)); printk("PEND_CLR = 0x%08x\n",readl(spi->regs + S3C_PENDING_CLR)); printk("GPL_DAT = 0x%08x\n",readl(S3C2410_GPLDAT)); } #else #define DEBUG #define DBG(x...) do { } while (0) void print_reg(struct s3c_spi *spi) { } #endif static void s3c_spi_free(struct s3c_spi *spi) { //DEBUG; if (spi->clk != NULL && !IS_ERR(spi->clk)) { clk_disable(spi->clk); clk_put(spi->clk); spi->clk = NULL; } if (spi->regs != NULL) { iounmap(spi->regs); spi->regs = NULL; } if (spi->ioarea != NULL) { release_resource(spi->ioarea); kfree(spi->ioarea); spi->ioarea = NULL; } /* GeorgeKuo: */ if (spi->irq != NULL) { free_irq(spi->irq->start, spi); spi->irq = NULL; } } static void s3c_spi_wifi_if_on() { printk("\n[HIKO HSPI] s3c_spi_wifi_if_on()\n"); /* program defaults into the registers */ writel(readl(S3C2443_SCLKCON)|(1<<14), S3C2443_SCLKCON); writel(readl(S3C2443_PCLKCON)|(1<<6), S3C2443_PCLKCON); writel(readl(S3C24XX_MISCCR)|S3C24XX_MISCCR_SPISEL, S3C24XX_MISCCR); /* initialize the gpio */ //SPI DI s3c2410_gpio_cfgpin(S3C2410_GPE11, S3C2410_GPE11_SPIMISO0); /* GeorgeKuo: */ //s3c2410_gpio_pullup(S3C2410_GPE11, 2); /* pull-up enable */ //SPI DO s3c2410_gpio_cfgpin(S3C2410_GPE12, S3C2410_GPE12_SPIMOSI0); /* GeorgeKuo: */ //s3c2410_gpio_pullup(S3C2410_GPE12, 2); /* pull-up enable */ //SPI CLK s3c2410_gpio_cfgpin(S3C2410_GPE13, S3C2410_GPE13_SPICLK0); //Chip Select //s3c2410_gpio_cfgpin(S3C2410_GPL13, S3C2410_GPL13_OUTP); //s3c2410_gpio_setpin(S3C2410_GPL13, 1); s3c2410_gpio_cfgpin(S3C2410_GPL13, S3C2410_GPL13_SS0); /* GeorgeKuo: */ //s3c2410_gpio_pullup(S3C2410_GPL13, 2); /* pull-up enable */ //GPK5, PA 3.3V //writel(((readl(S3C2416_GPKCON) & ~(1<<11)) | (1<<10)), S3C2416_GPKCON); //writel((readl(S3C2416_GPKDAT) | (1<<5)), S3C2416_GPKDAT); //writel(((readl(S3C2416_GPKUDP) & (1<<11)) | ~(1<<10)), S3C2416_GPKUDP); //GPK6, PA 1.8V //writel(((readl(S3C2416_GPKCON) & ~(1<<13)) | (1<<12)), S3C2416_GPKCON); //writel((readl(S3C2416_GPKDAT) | (1<<6)), S3C2416_GPKDAT); //writel(((readl(S3C2416_GPKUDP) & (1<<13)) | ~(1<<12)), S3C2416_GPKUDP); /* GeorgeKuo: */ //EXT Reset //s3c2410_gpio_cfgpin(S3C2410_GPD8, S3C2410_GPD8_OUTP); //s3c2410_gpio_setpin(S3C2410_GPD8, 0); //mdelay(1); //s3c2410_gpio_setpin(S3C2410_GPD8, 1); //mdelay(20); } EXPORT_SYMBOL_GPL(s3c_spi_wifi_if_on); static void s3c_spi_wifi_if_off() { printk("\n[HIKO HSPI] s3c_spi_wifi_if_off()\n"); } EXPORT_SYMBOL_GPL(s3c_spi_wifi_if_off); static int s3c_spi_hw_init(struct s3c_spi *spi) { //printk("\n[HIKO HSPI] s3c_spi_hw_init()\n"); #if 0 /* program defaults into the registers */ writel(readl(S3C2443_SCLKCON)|(1<<14), S3C2443_SCLKCON); writel(readl(S3C2443_PCLKCON)|(1<<6), S3C2443_PCLKCON); writel(readl(S3C24XX_MISCCR)|S3C24XX_MISCCR_SPISEL, S3C24XX_MISCCR); /* initialize the gpio */ // s3c2410_gpio_cfgpin(S3C2410_GPL13, S3C2410_GPL13_SS0); //SPI DI s3c2410_gpio_cfgpin(S3C2410_GPE11, S3C2410_GPE11_SPIMISO0); //SPI DO s3c2410_gpio_cfgpin(S3C2410_GPE12, S3C2410_GPE12_SPIMOSI0); //SPI CLK s3c2410_gpio_cfgpin(S3C2410_GPE13, S3C2410_GPE13_SPICLK0); // s3c2410_gpio_cfgpin(S3C2410_GPL14, S3C2410_GPL14_SS1); // s3c2410_gpio_cfgpin(S3C2410_GPL11, S3C2410_GPL11_SPIMOSI1); // s3c2410_gpio_cfgpin(S3C2410_GPL12, S3C2410_GPL12_SPIMISO1); // s3c2410_gpio_cfgpin(S3C2410_GPL10, S3C2410_GPL10_SPICLK1); //Chip Select s3c2410_gpio_cfgpin(S3C2410_GPL13, S3C2410_GPL13_OUTP); s3c2410_gpio_setpin(S3C2410_GPL13, 1); //GPK6, PA 1.8V writel(((readl(S3C2416_GPKCON) & ~(1<<13)) | (1<<12)), S3C2416_GPKCON); writel((readl(S3C2416_GPKDAT) | (1<<6)), S3C2416_GPKDAT); writel(((readl(S3C2416_GPKUDP) & (1<<13)) | ~(1<<12)), S3C2416_GPKUDP); #endif //0 #ifdef CONFIG_SPICLK_PCLK /*Enable PCLK into the HS SPI*/ writel(readl(S3C2443_PCLKCON)|(1<<6), S3C2443_PCLKCON); clk_enable(spi->clk); #elif defined CONFIG_SPICLK_EPLL /* implemetation when use EPLL clock */ writel(0x800, S3C2443_LOCKCON1); writel( (readl( S3C2443_CLKSRC ) | (1 << 6) ), S3C2443_CLKSRC); // EPLL Output #if defined(CONFIG_CPU_S3C2443) writel((40<<16)|(1<<8)|(1<<0) ,S3C2443_EPLLCON);//96MHz #elif defined(CONFIG_CPU_S3C2450) || defined(CONFIG_CPU_S3C2416) writel((48<<16)|(3<<8)|(1<<0) ,S3C2443_EPLLCON);//96MHz #else # error Any CPU Type is not selected #endif writel( readl(S3C2443_EPLLCON)& (~(1<<24)) , S3C2443_EPLLCON ); //EPLL On writel(( readl(S3C2443_CLKDIV1) & (~(0x3<<24))) | (0x0 << 24) , S3C2443_CLKDIV1 ); // Epll Ratio is 1 writel(( readl(S3C24XX_MISCCR) & (~(0x7<<8))) | (0x1 << 8) , S3C24XX_MISCCR ); /* Epll, prescaler = 1 */ /* clock = ( clock source / (2 * ( prescaler + 1))) */ writel(0x501, spi->regs + S3C_CLK_CFG); //Use EPLL Clock and Clock On Prescaler 1 #else #error you must define correct confige file. #endif /* hspi software restet */ //writeb(readb(spi->regs + S3C_CH_CFG) | (1<<5), spi->regs + S3C_CH_CFG); //writeb(readb(spi->regs + S3C_CH_CFG) & (~(1<<5)), spi->regs + S3C_CH_CFG); /* GeorgeKuo: */ writel(readl(spi->regs + S3C_CH_CFG) | SPI_CH_SW_RST, spi->regs + S3C_CH_CFG); writel(readl(spi->regs + S3C_CH_CFG) & (~SPI_CH_SW_RST), spi->regs + S3C_CH_CFG); // DEBUG; return 0; } static int s3c_spi_dma_init(struct s3c_spi *spi, int mode) { // DEBUG; // TX if (mode == 0) { /* vivek, 2009-05-14 10:47 Notes: spi->dma changed to spi->dmaw*/ s3c2410_dma_devconfig(spi->dmaw, S3C2410_DMASRC_MEM, S3C_SPI_DMA_HWCFG, S3C_SPI_TX_DATA_REG); s3c2410_dma_config(spi->dmaw, S3C_DMA_XFER_BYTE, S3C_DCON_SPI1); // s3c2410_dma_setflags(spi->dmaw, S3C2410_DMAF_AUTOSTART); } // RX if (mode == 1) { /* vivek, 2009-05-14 10:47 Notes: spi->dma changed to spi->dmar*/ s3c2410_dma_devconfig(spi->dmar, S3C2410_DMASRC_HW, S3C_SPI_DMA_HWCFG, S3C_SPI_RX_DATA_REG); s3c2410_dma_config(spi->dmar, S3C_DMA_XFER_BYTE, S3C_DCON_SPI1); //s3c2410_dma_setflags(spi->dmar, S3C2410_DMAF_AUTOSTART); } /* vivek, 2009-05-14 10:48 Notes: moving it in above "if checks"*/ //s3c2410_dma_setflags(spi->dma, S3C2410_DMAF_AUTOSTART); return 0; } static inline void s3c_spi_write_fifo(struct s3c_spi *spi) { u32 wdata = 0; if (spi->msg->wbuf) { wdata = spi->msg->wbuf[spi->msg_ptr++]; } else { spi->msg_ptr++; wdata = 0xff; } DBG("wdata = %x\n",wdata); writel(wdata, spi->regs + S3C_SPI_TX_DATA); } /* s3c_spi_master_complete * * complete the message and wake up the caller, using the given return code, * or zero to mean ok. */ static inline void s3c_spi_master_complete(struct s3c_spi *spi, int ret) { // DEBUG; spi->msg_ptr = 0; spi->msg_rd_ptr = 0; spi->msg->flags = 0; spi->msg = NULL; spi->msg_idx ++; spi->msg_num = 0; writel(0xff, spi->regs + S3C_PENDING_CLR); if (ret) spi->msg_idx = ret; } #if 0 static int s3c_spi_done(struct s3c_spi *spi) { u32 spi_clkcfg; // DEBUG; spi_clkcfg = readl( spi->regs + S3C_CLK_CFG); /* GeorgeKuo: */ //spi_clkcfg &= SPI_ENCLK_DISABLE; spi_clkcfg &= ~SPI_ENCLK_ENABLE; writel( spi_clkcfg , spi->regs + S3C_CLK_CFG); return 0; } #endif static inline void s3c_spi_stop(struct s3c_spi *spi, int ret) { u32 spi_slavecfg; u32 spi_chcfg; #if CONFIG_AUTO_nSS #else /* GeorgeKuo: set nSS high to stop bus operation */ spi_slavecfg = readl(spi->regs + S3C_SLAVE_SEL); /* GeorgeKuo: clear to b0 to 0 to activate */ //spi_slavecfg &= SPI_SLAVE_SIG_ACT; spi_slavecfg |= SPI_SLAVE_SIG_INACT; writel(spi_slavecfg, spi->regs + S3C_SLAVE_SEL); #endif // DEBUG; writel(0x0, spi->regs + S3C_SPI_INT_EN); writel(0x1f, spi->regs + S3C_PENDING_CLR); /* GeoregKuo: clear TRXCH_ON bits */ //writel(0x0, spi->regs + S3C_CH_CFG); spi_chcfg = readl(spi->regs + S3C_CH_CFG); spi_chcfg &= ~(SPI_CH_TXCH_ON | SPI_CH_RXCH_ON); writel(spi_chcfg, spi->regs + S3C_CH_CFG); /* GeorgeKuo: skip disabling ENCLK in CLK_CFG */ //s3c_spi_done(spi); spi->state = STATE_IDLE; s3c_spi_master_complete(spi, ret); //print_reg(spi); /* GeorgeKuo:*/ //up(&spi->sem); complete(&spi->comp); } void s3c_spi_dma_cb(struct s3c2410_dma_chan *dma_ch, void *buf_id, int size, enum s3c2410_dma_buffresult result) { struct s3c_spi *spi = (struct s3c_spi *)buf_id; unsigned long status = 0; // DEBUG; status = readl(spi->regs + S3C_SPI_STATUS); pr_debug("DMA call back\n"); if (spi->msg->wbuf) while (!(readl(spi->regs +S3C_SPI_STATUS) & SPI_STUS_TX_DONE)) {} s3c_spi_stop(spi, status); } /* s3c_spi_message_start * * configure the spi controler and transmit start of a message onto the bus */ static void s3c_spi_message_start(struct s3c_spi *spi) { struct spi_msg *msg = spi->msg; u32 spi_chcfg = 0, spi_slavecfg, spi_inten= 0, spi_packet=0; // u8 prescaler = 0; // 44.435 Mhz // u8 prescaler = 1; // 22.2175 Mhz // u8 prescaler = 2; // 14.81 Mhz // u8 prescaler = 3; // 11.10875 Mhz // u8 prescaler = 4; // 8.887Mhz u32 spi_modecfg = 0; //u32 spi_clkcfg = 0; // DEBUG; #if CONFIG_DO_SWRST /* GeorgeKuo: do software reset */ writel(readl(spi->regs + S3C_CH_CFG) | SPI_CH_SW_RST, spi->regs + S3C_CH_CFG); writel(readl(spi->regs + S3C_CH_CFG) & (~SPI_CH_SW_RST), spi->regs + S3C_CH_CFG); #endif #if 0 /* GeorgeKuo: do hw init in probe() */ /* initialise the spi controller */ s3c_spi_hw_init(spi); #endif #if 0 /* GeorgeKuo: set CH_CFG in probe() */ /* 1. Set transfer type (CPOL & CPHA set) */ //spi_chcfg = SPI_CH_RISING | SPI_CH_FORMAT_A; /* GeorgeKuo: MT5921 uses CPOL=1, CPHA=1 */ spi_chcfg = SPI_CH_FALLING | SPI_CH_FORMAT_B; if (spi->msg->flags & SPI_M_MODE_MASTER) { spi_chcfg |= SPI_CH_MASTER; } else if(spi->msg->flags & SPI_M_MODE_SLAVE){ spi_chcfg |= SPI_CH_SLAVE; } writel( spi_chcfg , spi->regs + S3C_CH_CFG); #endif #if 0 /* GeorgeKuo: set CLK_CFG in probe() */ /* 2. Set clock configuration register */ spi_clkcfg = SPI_ENCLK_ENABLE; #if defined CONFIG_SPICLK_PCLK spi_clkcfg |= SPI_CLKSEL_PCLK; #elif defined CONFIG_SPICLK_EPLL spi_clkcfg |= SPI_CLKSEL_ECLK; #else #error you must define correct confige file. #endif writel( spi_clkcfg , spi->regs + S3C_CLK_CFG); spi_clkcfg = readl( spi->regs + S3C_CLK_CFG); /* SPI clockout = clock source / (2 * (prescaler +1)) */ spi_clkcfg |= prescaler; writel( spi_clkcfg , spi->regs + S3C_CLK_CFG); #endif /* 3. Set SPI MODE configuration register */ #ifdef CONFIG_WORD_TRANSIZE spi_modecfg = SPI_MODE_CH_TSZ_WORD; #else spi_modecfg = SPI_MODE_CH_TSZ_BYTE; #endif /* GeorgeKuo: 32-bit operation */ //spi_modecfg = SPI_MODE_CH_TSZ_WORD | SPI_MODE_BUS_TSZ_WORD; //spi_modecfg = SPI_MODE_CH_TSZ_BYTE | SPI_MODE_BUS_TSZ_WORD; /* GeorgeKuo: 8-bit operation */ spi_modecfg = SPI_MODE_CH_TSZ_BYTE | SPI_MODE_BUS_TSZ_BYTE; spi_modecfg |= SPI_MODE_TXDMA_OFF| SPI_MODE_SINGLE| SPI_MODE_RXDMA_OFF; if (msg->flags & SPI_M_DMA_MODE) { spi_modecfg |= SPI_MODE_TXDMA_ON| SPI_MODE_RXDMA_ON; } if (msg->wbuf){ /* GeorgeKuo: 32 bytes */ //spi_modecfg |= ( 0x3f << 5); /* Tx FIFO trigger level in INT mode */ spi_modecfg |= (CONFIG_TX_LVL << 5); /* Tx FIFO trigger level in INT mode */ } if (msg->rbuf) { /* GeorgeKuo: 16 bytes */ //spi_modecfg |= ( 0x3f << 11); /* Rx FIFO trigger level in INT mode */ spi_modecfg |= (CONFIG_RX_LVL << 11); /* Rx FIFO trigger level in INT mode */ } #if 0 /* GeorgeKuo: strange trailing count setting */ spi_modecfg |= ( 0x3ff << 19); #endif writel(spi_modecfg, spi->regs + S3C_MODE_CFG); /* 4. Set SPI INT_EN register */ if (msg->wbuf) spi_inten = SPI_INT_TX_FIFORDY_EN|SPI_INT_TX_UNDERRUN_EN|SPI_INT_TX_OVERRUN_EN; if (msg->rbuf) spi_inten = SPI_INT_RX_FIFORDY_EN|SPI_INT_RX_UNDERRUN_EN|SPI_INT_RX_OVERRUN_EN|SPI_INT_TRAILING_EN; writel(spi_inten, spi->regs + S3C_SPI_INT_EN); writel(0x1f, spi->regs + S3C_PENDING_CLR); #if CONFIG_USE_PKTCNT #if 1 if (msg->rbuf) { /* 5. Set Packet Count configuration register */ spi_packet = SPI_PACKET_CNT_EN | spi->msg->len; writel(spi_packet, spi->regs + S3C_PACKET_CNT); } #else /* 5. Set Packet Count configuration register */ spi_packet = SPI_PACKET_CNT_EN; spi_packet |= 0xffff; //spi_packet = SPI_PACKET_CNT_EN|spi->msg->len; writel(spi_packet, spi->regs + S3C_PACKET_CNT); #endif #endif /* vivek, 2009-04-10 11:25 Notes: swap_cfg */ /* spi_swapcfg = SPI_SWAP_RX_BYTE|SPI_SWAP_RX_EN; writel(spi_packet, spi->regs + S3C_SWAP_CFG); */ #if CONFIG_AUTO_nSS #else /* GeorgeKuo: assert nSS before TRXCH_ON */ spi_slavecfg = readl(spi->regs + S3C_SLAVE_SEL); spi_slavecfg &= ~SPI_SLAVE_SIG_INACT; writel(spi_slavecfg, spi->regs + S3C_SLAVE_SEL); #endif /* 6. Set Tx or Rx Channel on */ spi_chcfg = readl(spi->regs + S3C_CH_CFG); /* GeoregKuo: clear TRXCH_ON bits */ //spi_chcfg |= SPI_CH_TXCH_OFF | SPI_CH_RXCH_OFF; spi_chcfg &= ~(SPI_CH_TXCH_ON | SPI_CH_RXCH_ON); if (msg->wbuf) { spi_chcfg |= SPI_CH_TXCH_ON; } if (msg->rbuf) { spi_chcfg |= SPI_CH_RXCH_ON; } writel(spi_chcfg, spi->regs + S3C_CH_CFG); /* vivek, 2009-05-14 10:57 Notes: moving evrything in spi_probe()*/ /*if (msg->flags & SPI_M_DMA_MODE) { if (msg->wbuf) spi->dma = DMACH_SPI_TX; if (msg->rbuf) spi->dma = DMACH_SPI_RX; if (s3c2410_dma_request(spi->dma, &s3c2443spi_dma_client, NULL)) { printk(KERN_WARNING "unable to get DMA channel.\n" ); } s3c2410_dma_set_buffdone_fn(spi->dma, s3c_spi_dma_cb); s3c2410_dma_set_opfn(spi->dma, NULL); if (msg->wbuf) s3c_spi_dma_init(spi, 0); if (msg->rbuf) s3c_spi_dma_init(spi, 1); s3c2410_dma_enqueue(spi->dma, (void *) spi, spi->dmabuf_addr, spi->msg->len); }*/ //if(msg->wbuf) { if(msg->wbuf && spi->dmabuf_addr) { /* GeorgeKuo: check dmabuf_addr or DMA complains later */ pr_debug("spi->dmaw is %d\n",spi->dmaw); s3c2410_dma_setflags(spi->dmaw, S3C2410_DMAF_AUTOSTART); s3c2410_dma_enqueue(spi->dmaw, (void *) spi, spi->dmabuf_addr, spi->msg->len); } //if (msg->rbuf) { if (msg->rbuf && spi->dmabuf_addr) { /* GeorgeKuo: check dmabuf_addr or DMA complains later */ pr_debug("spi->dmar is %d\n",spi->dmar); s3c2410_dma_setflags(spi->dmar, S3C2410_DMAF_AUTOSTART); s3c2410_dma_enqueue(spi->dmar, (void *) spi, spi->dmabuf_addr, spi->msg->len); } #if 0 /* GeorgeKuo: */ /* 7. Set nSS low to start Tx or Rx operation */ spi_slavecfg = readl(spi->regs + S3C_SLAVE_SEL); spi_slavecfg &= SPI_SLAVE_SIG_ACT; writel(spi_slavecfg, spi->regs + S3C_SLAVE_SEL); #endif //print_reg(spi); } /* is_msgend * * returns TRUE if we reached the end of the current message */ static inline int tx_msgend(struct s3c_spi *spi) { return spi->msg_ptr >= spi->msg->len; } static inline int rx_msgend(struct s3c_spi *spi) { return spi->msg_rd_ptr >= spi->msg->len; } /* spi_s3c_irq_nextbyte * * process an interrupt and work out what to do */ static void spi_s3c_irq_nextbyte(struct s3c_spi *spi, unsigned long spsta) { /* GeorgeKuo: */ unsigned long fifo_lvl; DEBUG; DBG("[%ld] spi->state = %d (%s)\n", jiffies, spi->state, __FUNCTION__); DBG("[%ld] spi sta = 0x%lx (%s)\n", jiffies, spsta, __FUNCTION__); switch (spi->state) { case STATE_IDLE: DBG("%s: called in STATE_IDLE\n", __FUNCTION__); break; case STATE_STOP: /* GeorgeKuo: change to polling status instead of waiting */ if (spi->msg->wbuf) { do { fifo_lvl = readl(spi->regs + S3C_SPI_STATUS); } while((fifo_lvl & SPI_STUS_TX_FIFO_LVL) || !(fifo_lvl & SPI_STUS_TX_DONE)); } //udelay(200); s3c_spi_stop(spi, 0); DBG("%s: called in STATE_STOP\n", __FUNCTION__); break; case STATE_XFER_TX: DEBUG; //print_reg(spi); DBG("msg_ptr = 0x%x, len = 0x%x \n", spi->msg_ptr ,spi->msg->len); #if 1 /* GeorgeKuo: */ while(!(tx_msgend(spi))) { fifo_lvl = readl(spi->regs + S3C_SPI_STATUS); if (fifo_lvl & SPI_STUS_ERR_ALL) { printk(KERN_WARNING "hspi: STATE_XFER_TX spi status error: 0x%08lx\n", fifo_lvl); } fifo_lvl = (fifo_lvl >> 6) & 0x7F; if (fifo_lvl < 60) { s3c_spi_write_fifo(spi); } else { break; } } if (!(tx_msgend(spi))) { break; } #else while(!(tx_msgend(spi))) s3c_spi_write_fifo(spi); #endif DEBUG; //print_reg(spi); spi->state = STATE_STOP; break; case STATE_XFER_RX: DEBUG; //print_reg(spi); DBG("msg_rd_ptr = 0x%x, len = 0x%x \n", spi->msg_rd_ptr ,spi->msg->len); #if 1 /* GeorgeKuo: */ while(!(rx_msgend(spi))){ fifo_lvl = readl(spi->regs + S3C_SPI_STATUS); if (fifo_lvl & SPI_STUS_ERR_ALL) { printk(KERN_WARNING "hspi: STATE_XFER_RX spi status error: 0x%08lx\n", fifo_lvl); } fifo_lvl = (fifo_lvl >> 13) & 0x7F; if (fifo_lvl > 0) { spi->msg->rbuf[spi->msg_rd_ptr++] = readl(spi->regs + S3C_SPI_RX_DATA); DBG("msg_rd_ptr = 0x%x, len = 0x%x \n", spi->msg_rd_ptr ,spi->msg->len); DBG("msg_rbuf = 0x%x\n", spi->msg->rbuf[spi->msg_rd_ptr - 1]); } else { break; } } DBG("msg_rd_ptr = 0x%x, len = 0x%x \n", spi->msg_rd_ptr ,spi->msg->len); if (!(rx_msgend(spi))) { break; } #else while(!(rx_msgend(spi))){ spi->msg->rbuf[spi->msg_rd_ptr++] = readl(spi->regs + S3C_SPI_RX_DATA); DBG("msg_rd_ptr = 0x%x, len = 0x%x \n", spi->msg_rd_ptr ,spi->msg->len); DBG("msg_rbuf = 0x%x\n", spi->msg->rbuf[spi->msg_rd_ptr - 1]); } DBG("msg_rd_ptr = 0x%x, len = 0x%x \n", spi->msg_rd_ptr ,spi->msg->len); #endif DEBUG; //print_reg(spi); s3c_spi_stop(spi, 0); break; default: dev_err(spi->dev, "%s: called with Invalid option\n", __FUNCTION__); } return; } /* s3c_spi_irq * * top level IRQ servicing routine */ static irqreturn_t s3c_spi_irq(int irqno, void *dev_id) { struct s3c_spi *spi = dev_id; unsigned long spi_sts; spi_sts = readl(spi->regs + S3C_SPI_STATUS); if (spi_sts & SPI_STUS_RX_OVERRUN_ERR) { printk("hspi : Rx overrun error detected\n"); } if (spi_sts & SPI_STUS_RX_UNDERRUN_ERR) { printk("hspi : Rx underrun error detected\n"); } if (spi_sts & SPI_STUS_TX_OVERRUN_ERR) { printk("hspi : Tx overrun error detected\n"); } if (spi_sts & SPI_STUS_TX_UNDERRUN_ERR) { printk("hspi : Tx underrun error detected\n"); } /* pretty much this leaves us with the fact that we've * transmitted or received whatever byte we last sent */ spi_s3c_irq_nextbyte(spi, spi_sts); return IRQ_HANDLED; } static int s3c_spi_doxfer(struct s3c_spi *spi, struct spi_msg msgs[], int num) { int ret; /* GeorgeKuo: spi_dev->bus_lock is acquired by caller, do we have to do * protection again? */ //spin_lock_irq(&spi->lock); spi->msg = msgs; spi->msg_num = num; spi->msg_ptr = 0; spi->msg_rd_ptr = 0; spi->msg_idx = 0; /* vivek, 2009-04-15 17:40 Notes: comment and move it for buffer to decide */ /*if (spi->msg->flags & SPI_M_DMA_MODE) { spi->dmabuf_addr = spi->spidev.dmabuf; pr_debug("spi->dmabuf_addr = 0x%x\n",spi->dmabuf_addr); //printk("spi->dmabuf_addr = 0x%x\n",spi->dmabuf_addr); }*/ if (spi->msg->wbuf) { // DEBUG; spi->state = STATE_XFER_TX; /* vivek, 2009-04-15 17:40 Notes: write dmabufw to dmabuf_addr */ if (spi->msg->flags & SPI_M_DMA_MODE) { spi->dmabuf_addr = spi->spidev.dmabufw; pr_debug("spi->dmabuf_addr = 0x%x\n",spi->dmabuf_addr); } } else if (spi->msg->rbuf) { // DEBUG; spi->state = STATE_XFER_RX; /* vivek, 2009-04-15 17:41 Notes: write dmabufr to dmabuf_addr */ if (spi->msg->flags & SPI_M_DMA_MODE) { spi->dmabuf_addr = spi->spidev.dmabufr; pr_debug("spi->dmabuf_addr = 0x%x\n",spi->dmabuf_addr); } } else { dev_err(spi->dev,"Unknown functionality \n"); return -ESRCH; } /* GeorgeKuo: */ init_completion(&spi->comp); s3c_spi_message_start(spi); // DEBUG; /* GeorgeKuo: */ //spin_unlock_irq(&spi->lock); /* GeorgeKuo: replace semaphore with completion */ //if (down_interruptible(&spi->sem)) return -EINTR; if (wait_for_completion_interruptible(&spi->comp)) { return -EINTR; } // DEBUG; /* GeorgeKuo: */ //spin_unlock_irq(&spi->lock); // DEBUG; // ret = s3c2410_dma_free(spi->dma, &s3c2443spi_dma_client); ret = spi->msg_idx; return ret; } /* s3c_spi_xfer * * first port of call from the spi bus code when an message needs * transfering across the spi bus. */ static int s3c_spi_xfer(struct spi_dev *spi_dev, struct spi_msg msgs[], int num) { struct s3c_spi *spi = (struct s3c_spi *)spi_dev->algo_data; int retry; int ret; for (retry = 0; retry < spi_dev->retries; retry++) { ret = s3c_spi_doxfer(spi, msgs, num); //print_reg(spi); if (ret != -EAGAIN) return ret; printk("Retrying transmission (%d)\n", retry); udelay(100); } DEBUG; return -EREMOTEIO; } static int s3c_spi_close(struct spi_dev *spi_dev) { struct s3c_spi *spi = (struct s3c_spi *)spi_dev->algo_data; u32 spi_clkcfg; //DEBUG; spi_clkcfg = readl( spi->regs + S3C_CLK_CFG); /* GeorgeKuo: */ //spi_clkcfg &= SPI_ENCLK_DISABLE; spi_clkcfg &= ~SPI_ENCLK_ENABLE; writel( spi_clkcfg , spi->regs + S3C_CLK_CFG); /* GeorgeKuo: reset hw and CPOL, CPHA settings? */ /* Buffer Clear after finish xfer */ writel( 0x20, spi->regs + S3C_CH_CFG); writel( 0x0, spi->regs + S3C_CH_CFG); return 0; } /* spi bus registration info */ static struct spi_algorithm s3c_spi_algorithm = { .name = "S3C2443-spi-algorithm", .master_xfer = s3c_spi_xfer, .close = s3c_spi_close, }; static struct s3c_spi s3c_spi[2] = { [0] = { .lock = SPIN_LOCK_UNLOCKED, .spidev = { .algo = &s3c_spi_algorithm, .retries = 2, .timeout = 5, } }, [1] = { .lock = SPIN_LOCK_UNLOCKED, .spidev = { .algo = &s3c_spi_algorithm, .retries = 2, .timeout = 5, } }, }; #if CONFIG_TEST_EINT3 wait_queue_head_t rWlandWaitq; /* wland wait queue head */ unsigned long fgInt = 0; struct task_struct *prWland = NULL; static int glWlandThread ( void *data ) { int ret = 0; for (;;) { ret = wait_event_interruptible(rWlandWaitq, (kthread_should_stop() || (fgInt != 0))); if (kthread_should_stop()) { break; } fgInt = 0; printk(KERN_INFO "[%ld:%s enable_irq(IRQ_EINT3)\n", jiffies, __FUNCTION__); enable_irq(IRQ_EINT3); } printk(KERN_INFO "glWlandThread stops\n"); return 0; } irqreturn_t glSpiInterrupt (int i4Irq, void *pvDevID) { disable_irq(IRQ_EINT3); printk(KERN_INFO "[%ld:%s disable_irq(IRQ_EINT3)\n", jiffies, __FUNCTION__); fgInt = 1; wake_up_interruptible(&rWlandWaitq); return IRQ_HANDLED; } #endif /* s3c_spi_probe * * called by the bus driver when a suitable device is found */ static int s3c_spi_probe(struct platform_device *pdev) { struct s3c_spi *spi = &s3c_spi[pdev->id]; struct resource *res; int ret; /* find the clock and enable it */ sema_init(&spi->sem, 0); spi->nr = pdev->id; spi->dev = &pdev->dev; spi->clk = clk_get(&pdev->dev, "spi"); if (IS_ERR(spi->clk)) { dev_err(&pdev->dev, "cannot get clock\n"); ret = -ENOENT; goto out; } /* map the registers */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (res == NULL) { dev_err(&pdev->dev, "cannot find IO resource\n"); ret = -ENOENT; goto out; } spi->ioarea = request_mem_region(res->start, (res->end - res->start) + 1, pdev->name); if (spi->ioarea == NULL) { dev_err(&pdev->dev, "cannot request IO\n"); ret = -ENXIO; goto out; } printk(KERN_ALERT "resource start : %x\n",res->start); spi->regs = ioremap(res->start, (res->end - res->start) + 1); if (spi->regs == NULL) { dev_err(&pdev->dev, "cannot map IO\n"); ret = -ENXIO; goto out; } printk(KERN_ALERT "hspi registers %p (%p, %p)\n", spi->regs, spi->ioarea, res); /* setup info block for the spi core */ /* vivek, 2009-03-27 15:29 Notes: shifiting cpu/dma decision to gspi_io */ //spi->spidev.flags = SPI_M_DMA_MODE; spi->spidev.algo_data = spi; spi->spidev.dev.parent = &pdev->dev; spi->spidev.minor = spi->nr; init_MUTEX(&spi->spidev.bus_lock); /* find the IRQ for this unit (note, this relies on the init call to * ensure no current IRQs pending */ /* GeorgeKuo: */ //res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); spi->irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); /* GeorgeKuo: */ if (spi->irq == NULL) { /* if (res == NULL) { */ printk("hspi cannot find IRQ\n"); ret = -ENOENT; goto out; } /* GeorgeKuo: */ //ret = request_irq(res->start, s3c_spi_irq, SA_INTERRUPT, pdev->name, spi); ret = request_irq(spi->irq->start, s3c_spi_irq, SA_INTERRUPT, pdev->name, spi); if (ret != 0) { printk("hspi cannot claim IRQ\n"); goto out; } /* GeorgeKuo: Do initialization here instead of each read/write operation */ do { /* MT5921 uses CPOL=1, CPHA=1 */ u32 spi_chcfg = SPI_CH_MASTER | SPI_CH_FALLING | SPI_CH_FORMAT_B; //u32 spi_clkcfg = SPI_ENCLK_ENABLE | 1; /* prescaler = 1 */ u32 spi_clkcfg = SPI_ENCLK_ENABLE | CONFIG_SPI_PRESCALER; /* prescaler = 1 */ //0:44.435 Mhz, 1:22.2175 Mhz, 2:14.81 Mhz, 3:11.10875 Mhz, 4:8.887Mhz #if CONFIG_AUTO_nSS u32 spi_slavecfg = SPI_SLAVE_AUTO; #else u32 spi_slavecfg = SPI_SLAVE_MAN | SPI_SLAVE_SIG_INACT; #endif /* initialise the spi controller */ s3c_spi_hw_init(spi); /* 1. Set transfer type (CPOL & CPHA set) */ writel(spi_chcfg , spi->regs + S3C_CH_CFG); /* 2. Set clock configuration register */ #if defined CONFIG_SPICLK_PCLK spi_clkcfg |= SPI_CLKSEL_PCLK; #elif defined CONFIG_SPICLK_EPLL spi_clkcfg |= SPI_CLKSEL_ECLK; #else #error you must define correct confige file. #endif /* SPI clockout = clock source / (2 * (prescaler +1)) */ writel(spi_clkcfg , spi->regs + S3C_CLK_CFG); /* Config slave select */ writel(spi_slavecfg, spi->regs + S3C_SLAVE_SEL); } while (0); ret = spi_attach_spidev(&spi->spidev); if (ret < 0) { dev_err(&pdev->dev, "failed to add adapter to spi core\n"); goto out; } dev_set_drvdata(&pdev->dev, spi); dev_info(&pdev->dev, "%s: S3C SPI adapter\n", spi->dev->bus_id); printk("%s: S3C SPI adapter\n", spi->dev->bus_id); /* vivek, 2009-05-14 10:46 Notes: dma channel initialisation*/ /* vivek, 2009-05-14 10:51 Notes: for Write channel*/ spi->dmaw = DMACH_SPI_TX; if (s3c2410_dma_request(spi->dmaw, &s3c2443spi_dma_clientw, NULL)) { printk(KERN_WARNING "unable to get DMA channel.\n" ); } s3c2410_dma_set_buffdone_fn(spi->dmaw, s3c_spi_dma_cb); s3c2410_dma_set_opfn(spi->dmaw, NULL); s3c_spi_dma_init(spi, 0); printk("spi write channel is %d\n",spi->dmaw); /* vivek, 2009-05-14 10:54 Notes: for Read channel*/ spi->dmar = DMACH_SPI_RX; if (s3c2410_dma_request(spi->dmar, &s3c2443spi_dma_clientr, NULL)) { printk(KERN_WARNING "unable to get DMA channel.\n" ); } s3c2410_dma_set_buffdone_fn(spi->dmar, s3c_spi_dma_cb); s3c2410_dma_set_opfn(spi->dmar, NULL); s3c_spi_dma_init(spi, 1); printk("spi read channel is %d\n",spi->dmar); out: if (ret < 0) s3c_spi_free(spi); #if CONFIG_TEST_TXBUF_SIZE /* Bus write test */ do { u32 ucaBuf[CONFIG_TEST_TXBUF_SIZE] = {0}; u32 i = 0; struct spi_dev *prSpiDev = spi_dev_get_by_minor(0); for (i = 0; i < CONFIG_TEST_TXBUF_SIZE; ++i) { ucaBuf[i] = i; } ret = spi_master_send(prSpiDev, (char *)&ucaBuf[0], sizeof(u32)*CONFIG_TEST_TXBUF_SIZE); if (ret != sizeof(u32)*CONFIG_TEST_TXBUF_SIZE) { s3c2410_gpio_setpin(S3C2410_GPD8, 0); s3c2410_gpio_setpin(S3C2410_GPD8, 1); printk("error\n"); } ret = 0; } while (0); #endif #if CONFIG_TEST_RXBUF_SIZE /* Bus write test */ do { u32 ucaBuf[CONFIG_TEST_RXBUF_SIZE] = {0}; u32 i = 0; struct spi_dev *prSpiDev = spi_dev_get_by_minor(0); for (i = 0; i < CONFIG_TEST_RXBUF_SIZE; ++i) { ucaBuf[i] = i; } ret = spi_master_recv(prSpiDev, (char *)&ucaBuf[0], sizeof(u32)*CONFIG_TEST_RXBUF_SIZE); if (ret != sizeof(u32)*CONFIG_TEST_RXBUF_SIZE) { s3c2410_gpio_setpin(S3C2410_GPD8, 0); s3c2410_gpio_setpin(S3C2410_GPD8, 1); printk("error\n"); } ret = 0; } while (0); #endif #if 0 /* Bus write test */ do { u32 wdata[64] = {0x01234567, 0x89ABCDEF}; struct spi_dev *prSpiDev; int ret; int i = 0, j = 0; prSpiDev = spi_dev_get_by_minor(0); for (i = 0; i < 10; i++) { for (j = 0; j < 2; j++) { ret = spi_master_send(prSpiDev, (char *)&wdata[j], sizeof(u32)); } } } while (0); #endif #if 0 /* MT5921 Initial */ do { u32 u4InitCmd = 0x00040000UL; u32 u4RdDummy = 0xFFFFFFFFUL; u32 u4RVal; struct spi_dev *prSpiDev; int ret; int i; /* GeorgeKuo: */ s3c2410_gpio_setpin(S3C2410_GPD8, 0); mdelay(1); s3c2410_gpio_setpin(S3C2410_GPD8, 1); mdelay(20); prSpiDev = spi_dev_get_by_minor(0); #if 0 for (i = 0; i < 12; i++) { ret = spi_master_send(prSpiDev, (char *)&u4InitCmd, sizeof(u32)); } #endif #if 1 for (i = 0; i < 10; i++) { ret = spi_master_send(prSpiDev, (char *)&u4InitCmd, sizeof(u32)); ret = spi_master_recv(prSpiDev, (char *)&u4RdDummy, sizeof(u32)); ret = spi_master_recv(prSpiDev, (char *)&u4RVal, sizeof(u32)); printk(KERN_INFO "read 0x%x\n", u4RVal); } #endif //ret = spi_master_recv(prSpiDev, (char *)&u4RVal, sizeof(u32)); //printk(KERN_INFO "read 0x%x\n", u4RVal); } while (0); #endif #if CONFIG_TEST_EINT3 /* GPF3-EINT3 interrupt testing */ do { init_waitqueue_head(&rWlandWaitq); prWland = kthread_run(glWlandThread, NULL, "EINT3-testd"); s3c2410_gpio_cfgpin(S3C2410_GPF3, S3C2410_GPF3_EINT3); //s3c2410_gpio_pullup(S3C2410_GPF3, 0); /* pull-up/down disable */ s3c2410_gpio_pullup(S3C2410_GPF3, 2); /* pull-up enable */ //set_irq_type(WLAN_STA_IRQ, IRQ_TYPE_LEVEL_LOW); set_irq_type(IRQ_EINT3, IRQ_TYPE_EDGE_FALLING); mdelay(100); ret = request_irq(IRQ_EINT3, glSpiInterrupt, IRQF_TRIGGER_FALLING | IRQF_DISABLED, "EINT3-test", &rWlandWaitq); //ret = request_irq(IRQ_EINT3, glSpiInterrupt, SA_INTERRUPT, "EINT3-test", &rWlandWaitq); printk(KERN_INFO "request_irq(IRQ_EINT3) ret:%d\n", ret); if (ret) { disable_irq(IRQ_EINT3); free_irq(IRQ_EINT3, &rWlandWaitq); kthread_stop(prWland); } } while (0); #endif return ret; } /* s3c_spi_remove * * called when device is removed from the bus */ static int s3c_spi_remove(struct platform_device *pdev) { struct s3c_spi *spi = dev_get_drvdata(&pdev->dev); DEBUG; if (spi != NULL) { spi_detach_spidev(&spi->spidev); s3c_spi_free(spi); dev_set_drvdata(&pdev->dev, NULL); } #if CONFIG_TEST_EINT3 do { disable_irq(IRQ_EINT3); free_irq(IRQ_EINT3, &rWlandWaitq); kthread_stop(prWland); } while (0); #endif return 0; } #ifdef CONFIG_PM static int s3c_spi_suspend(struct platform_device *pdev, pm_message_t msg) { struct s3c_spi *hw = platform_get_drvdata(pdev); clk_disable(hw->clk); return 0; } static int s3c_spi_resume(struct platform_device *pdev) { struct s3c_spi *hw = platform_get_drvdata(pdev); clk_enable(hw->clk); s3c_spi_wifi_if_on(); return 0; } #else #define s3c_spi_suspend NULL #define s3c_spi_resume NULL #endif /* device driver for platform bus bits */ static struct platform_driver s3c_spi_driver = { .probe = s3c_spi_probe, .remove = s3c_spi_remove, #ifdef CONFIG_PM .suspend = s3c_spi_suspend, .resume = s3c_spi_resume, #endif .driver = { .name = "s3c2410-spi", .owner = THIS_MODULE, .bus = &platform_bus_type, }, }; static int __init s3c_spi_driver_init(void) { printk(KERN_INFO "S3C2443 HSPI Driver \n"); s3c_spi_wifi_if_on(); return platform_driver_register(&s3c_spi_driver); } static void __exit s3c_spi_driver_exit(void) { platform_driver_unregister(&s3c_spi_driver); } module_init(s3c_spi_driver_init); module_exit(s3c_spi_driver_exit); MODULE_DESCRIPTION("S3C2443 SPI Bus driver"); MODULE_AUTHOR("Ryu Euiyoul"); MODULE_LICENSE("GPL");