blob: 3d06d784a66f834a3775c1391bbacd089da3f9de [file] [log] [blame]
/*
* c2k_fast_spi.c
*
* Copyright (C) 2004,2005 Mindspeed 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <common.h>
#include <linux/byteorder/generic.h>
#include <linux/byteorder/little_endian.h>
#include <spi/fast_spi.h>
#include <mach/dma.h>
#include <common.h>
#include <asm/io.h>
#include <init.h>
#include <mach/comcerto_spi.h>
#include "c2k_dma.h"
#define CONFIG_DRIVER_FAST_SPI_DMA
#define SPI_FRAME_SIZE_MAX 16
#define SPI_FRAME_SIZE_MIN 4
#define SPI_CHIP_SELECT_MAX 15
#define SPI_BAUDR_MIN 2
#define SPI_BAUDR_MAX 0xFFFE
#define SPI_SPEED_MAX (4*1000*1000)
#define SPI_CTRLR0_SCPOL (1 << 7)
#define SPI_CTRLR0_SCPH (1 << 6)
#define COMCERTO_DEFAULTAXICLK 200000000 /* Hz */
//#define COMCERTO_SPI_DEBUG
#ifdef COMCERTO_SPI_DEBUG
#define spi_debug(fmt, arg...) printf(fmt, ##arg)
#else
#define spi_debug(fmt, arg...) ;
#endif
static int spi_udelay;
extern void dma_conf(U32 ch_no, U32 dst, U32 data_len, U32 src, struct dma_conf_param *dmaconf);
extern void dma_start(U32 chan);
extern retcode dma_ssi_xfer_cmplete_chk(U32 ch_no);
extern int do_eeprom_read(struct spi_adapter *, u8, u8 *, int *, u8 *, int *);
extern int do_write_read_transfer(struct spi_adapter *, u8, u8 *, int *, u8 *rbuf, int *);
extern int do_write_only_transfer8(struct spi_adapter *, u8 *, int *);
extern int do_write_only_transfer16(struct spi_adapter *, u16 *, int *);
extern int do_read_only_transfer8(struct spi_adapter *, u8 *, int *);
extern int do_read_only_transfer16(struct spi_adapter *, u16 *, int *);
void dma_out_of_reset(void);
//extern u32 HAL_get_axi_clk();
#ifdef CONFIG_DRIVER_FAST_SPI_DMA
static int read_dma_complete(U32 chan, U32 dst, U32 data_len, U32 src, struct dma_conf_param *dmaconf)
{
/* Program the DMA */
dma_conf(chan, (U32) dst, data_len, src, dmaconf);
/* verify we are not busy, done with last transaction */
while (readl(DW_DMA_SSI_SR) & BUSY) ;
/* Send 1 byte dummy data to start read*/
writel(0, DW_DMA_SSI_DR);
/* NOW start DMA */
dma_start(chan);
/* Wait for completion of DMA xfer (till channel is disabled by HW) */
if (RETCODE_OK != dma_ssi_xfer_cmplete_chk(chan))
return -1;
return 0;
}
static int write_dma_complete(U32 chan, U32 dst, U32 data_len, U32 src, struct dma_conf_param *dmaconf)
{
/* Program the DMA */
dma_conf(chan, dst, data_len, (U32) src, dmaconf);
/* verify we are not busy, done with last transaction */
while (readl(DW_DMA_SSI_SR) & BUSY) ;
/* NOW start DMA */
dma_start(chan);
/* Wait for completion of DMA xfer (till channel is disabled by HW) */
if (RETCODE_OK != dma_ssi_xfer_cmplete_chk(chan))
return -1;
return 0;
}
static int eeprom_writeread_dma_complete(U32 wr_ch, U32 rd_ch, U32 rbuf, U32 wbuf,
U32 data_len, U32 dr, struct dma_conf_param *wr_dmaconf, struct dma_conf_param *rd_dmaconf)
{
/* Program dma channel for write */
dma_conf(wr_ch, dr, data_len, wbuf, wr_dmaconf);
/* Program dma channel for read */
dma_conf(rd_ch, rbuf, data_len, dr, rd_dmaconf);
/* NOW start write DMA */
dma_start(wr_ch);
/* NOW start read DMA */
dma_start(rd_ch);
/* Wait for completion of DMA write (till channel is disabled by HW) */
if (RETCODE_OK != dma_ssi_xfer_cmplete_chk(wr_ch))
return -1;
/* Wait for completion of DMA read (till channel is disabled by HW) */
if (RETCODE_OK != dma_ssi_xfer_cmplete_chk(rd_ch))
return -1;
return 0;
}
static int eeprom_read_dma_complete(U32 chan, U32 dst, U32 data_len, U8 *wbuf, U32 src, struct dma_conf_param *dmaconf)
{
/* Program the DMA */
dma_conf(chan, (U32) dst, data_len, src, dmaconf);
/* verify we are not busy, done with last transaction */
while (readl(DW_DMA_SSI_SR) & BUSY) ;
/* ______________________________
* Write wbuf data which is in special format: |8b op|8b uppr adr|8b low adr |
* -------------------------------
* Need to check with specs whether these three bytes could be more or less
* If yes then wlen will be considered to write bytes to DR
*/
writel(*wbuf, DW_DMA_SSI_DR); //8 bit opcode
writel(*(wbuf+1), DW_DMA_SSI_DR); //8 bit upper address
writel(*(wbuf+2), DW_DMA_SSI_DR); //8 bit lower address
writel(*(wbuf+3), DW_DMA_SSI_DR); //8 bit lower address
/* NOW start DMA */
dma_start(chan);
/* Wait for completion of DMA xfer (till channel is disabled by HW) */
if (RETCODE_OK != dma_ssi_xfer_cmplete_chk(chan))
return -1 ;
/* disable slave to be safe in sharing the bus with other controllers */
writel(0, DW_DMA_SSI_SER);
return 0;
}
/**
* fast_spi_write_mem -
*
*
*/
static int fast_spi_write_mem(struct spi_device *spi, u8 *wbuf, int wlen)
{
struct spi_adapter *adapter = container_of(spi->master, struct spi_adapter, master);
int rc = 0;
U32 dma_len ;
U32 dr = adapter->membase + COMCERTO_SPI_DR;
struct dma_conf_param dmaconf;
dmaconf.dir = 0x1; /* Memory to peripheral with DW_ahb_dmac as flow controller */
dmaconf.hs_src = 0x1; /* S/W handshaking */
dmaconf.hs_dst = 0x0; /* H/W handshaking */
dmaconf.sinc = 0x0; /* Source Address Inc */
dmaconf.dinc = 0x2; /* No Destination Address Inc */
/* Enable the DMA TX FIFO and RX FIFO channel */
writel(((SSI_TDMAE << 1) | (SSI_RDMAE << 0)), adapter->membase + COMCERTO_SPI_DMACR);
/* DMA TX FIFO threshold level */
writel(SSI_DMATDL, adapter->membase + COMCERTO_SPI_DMATDLR);
/* DMA RX FIFO threshold level */
writel(SSI_DMARDL, adapter->membase + COMCERTO_SPI_DMARDLR);
while ( wlen ) {
if ( wlen > SPI_NDF )
dma_len = SPI_NDF;
else
dma_len = wlen;
/* Program SSI data Xfer length */
writel(dma_len-1, adapter->membase+COMCERTO_SPI_CTRLR1);
/* done, programming regs, enable the ssi */
writel(1, adapter->membase+COMCERTO_SPI_SSIENR);
/* write it in single DMA Req -- Single-block Transfer */
rc = write_dma_complete(DMA_CHANNEL_5, dr, dma_len, (U32) wbuf, &dmaconf);
wlen -= dma_len;
wbuf += dma_len;
}
/* deassert the chip select at least for this long */
udelay (spi_udelay);
return rc;
}
/**
* fast_spi_read_mem -
*
*
*/
static int fast_spi_read_mem(struct spi_device *spi, u8 *rbuf, int rlen)
{
struct spi_adapter *adapter = container_of(spi->master, struct spi_adapter, master);
int rc = 0;
U32 dma_len ;
U32 dr = adapter->membase + COMCERTO_SPI_DR;
struct dma_conf_param dmaconf;
dmaconf.dir = 0x2; /* Peripheral to memory with DW_ahb_dmac as flow controller */
dmaconf.hs_src = 0x0; /* H/W handshaking */
dmaconf.hs_dst = 0x1; /* S/W handshaking */
dmaconf.sinc = 0x2; /* No Source Address Inc */
dmaconf.dinc = 0x0; /* Destination Address Inc */
/* Enable the DMA TX FIFO and RX FIFO channel */
writel(((SSI_TDMAE << 1) | (SSI_RDMAE << 0)), adapter->membase + COMCERTO_SPI_DMACR);
/* DMA TX FIFO threshold level */
writel(SSI_DMATDL, adapter->membase + COMCERTO_SPI_DMATDLR);
/* DMA RX FIFO threshold level */
writel(SSI_DMARDL, adapter->membase + COMCERTO_SPI_DMARDLR);
while ( rlen ) {
if ( rlen > SPI_NDF )
dma_len = SPI_NDF;
else
dma_len = rlen;
/* Program SSI data Xfer length */
writel(dma_len-1, adapter->membase+COMCERTO_SPI_CTRLR1);
/* done, programming regs, enable the ssi */
writel(1, adapter->membase+COMCERTO_SPI_SSIENR);
/* read it in single DMA Req -- Single-block Transfer */
rc = read_dma_complete(DMA_CHANNEL_4, (U32) rbuf, dma_len, dr, &dmaconf);
rlen -= dma_len;
rbuf += dma_len;
}
/* deassert the chip select at least for this long */
udelay (spi_udelay);
return rc;
}
/**
* fast_spi_writeread_mem -
*
*
*/
static int fast_spi_writeread_mem(struct spi_device *spi, u8 *rbuf, int rlen, u8 *wbuf, int wlen)
{
struct spi_adapter *adapter = container_of(spi->master, struct spi_adapter, master);
int rc = 0;
U32 dma_len ;
U32 dr = adapter->membase + COMCERTO_SPI_DR;
struct dma_conf_param wr_dmaconf;
struct dma_conf_param rd_dmaconf;
wr_dmaconf.dir = 0x1; /* Memory to peripheral with DW_ahb_dmac as flow controller */
wr_dmaconf.hs_src = 0x1; /* H/W handshaking */
wr_dmaconf.hs_dst = 0x0; /* S/W handshaking */
wr_dmaconf.sinc = 0x0; /* Source Address Inc */
wr_dmaconf.dinc = 0x2; /* No Destination Address Inc */
rd_dmaconf.dir = 0x2; /* Peripheral to memory with DW_ahb_dmac as flow controller */
rd_dmaconf.hs_src = 0x0; /* H/W handshaking */
rd_dmaconf.hs_dst = 0x1; /* S/W handshaking */
rd_dmaconf.sinc = 0x2; /* No Source Address Inc */
rd_dmaconf.dinc = 0x0; /* Destination Address Inc */
/* Enable the DMA TX FIFO and RX FIFO channel */
writel(((SSI_TDMAE << 1) | (SSI_RDMAE << 0)), adapter->membase + COMCERTO_SPI_DMACR);
/* DMA TX FIFO threshold level */
writel(SSI_DMATDL, adapter->membase + COMCERTO_SPI_DMATDLR);
/* DMA RX FIFO threshold level */
writel(SSI_DMARDL, adapter->membase + COMCERTO_SPI_DMARDLR);
/* Looping only for number of bytes to be written because same number
* Number of bytes are read every time. We can not have unequal wlen
* and rlen because DW_ahb_ssi has one register ctrlr1 for the length
* to be transferred. If we write first and then read, there will be
* read data overwritten */
while ( wlen ) {
if ( wlen > SPI_NDF )
dma_len = SPI_NDF;
else
dma_len = wlen;
/* Program SSI data Xfer length */
writel(dma_len-1, adapter->membase+COMCERTO_SPI_CTRLR1);
/* done, programming regs, enable the ssi */
writel(1, adapter->membase+COMCERTO_SPI_SSIENR);
/* write it in single DMA Req -- Single-block Transfer */
rc = eeprom_writeread_dma_complete(DMA_CHANNEL_4, DMA_CHANNEL_5, (U32) rbuf, (U32)wbuf, \
dma_len, dr, &wr_dmaconf, &rd_dmaconf) ;
wlen -= dma_len;
wbuf += dma_len;
rbuf += dma_len;
}
/* deassert the chip select at least for this long */
udelay (spi_udelay);
return rc;
}
/**
* spi_eeprom_read
*
*
*/
static int fast_spi_eeprom_read(struct spi_device *spi, u8 *rbuf, int rlen, u8 *wbuf, int wlen)
{
struct spi_adapter *adapter = container_of(spi->master, struct spi_adapter, master);
int rc = 0;
U32 dr = adapter->membase + COMCERTO_SPI_DR;
struct dma_conf_param dmaconf;
/* These all DMA config params can go in a struct*/
dmaconf.dir = 0x2; /* Peripheral to memory with DW_ahb_dmac as flow controller */
dmaconf.hs_src = 0x0; /* H/W handshaking */
dmaconf.hs_dst = 0x1; /* S/W handshaking */
dmaconf.sinc = 0x2; /* No Source Address Inc */
dmaconf.dinc = 0x0; /* Destination Address Inc */
/* Enable the DMA TX FIFO and RX FIFO channel */
writel(((SSI_TDMAE << 1) | (SSI_RDMAE << 0)), adapter->membase + COMCERTO_SPI_DMACR);
/* DMA TX FIFO threshold level */
writel(SSI_DMATDL, adapter->membase + COMCERTO_SPI_DMATDLR);
/* DMA RX FIFO threshold level */
writel(SSI_DMARDL, adapter->membase + COMCERTO_SPI_DMARDLR);
/* done, programming regs, enable the ssi */
writel(1, adapter->membase+COMCERTO_SPI_SSIENR);
/* read it in single DMA Req -- Single-block Transfer */
rc = eeprom_read_dma_complete(DMA_CHANNEL_4, (U32) rbuf, rlen, wbuf, dr, &dmaconf) ;
/* deassert the chip select at least for this long */
udelay (spi_udelay);
return rc;
}
#endif
/**
* comcerto_spi_hw_init -
*
*
*/
static void comcerto_spi_hw_init(struct spi_adapter *adaptr)
{
/* disable SPI operation */
writel(0, adaptr->membase + COMCERTO_SPI_SSIENR);
/* mask all SPI irq's */
writel(0, adaptr->membase + COMCERTO_SPI_IMR);
}
/**
* comcerto_spi_hw_reset -
*
*
*/
#if 0
static void comcerto_spi_hw_reset(struct spi_adapter *adaptr)
{
/* disable SPI operation */
writel(0, adaptr->membase + COMCERTO_SPI_SSIENR);
/* mask all SPI irq's */
writel(0, adaptr->membase + COMCERTO_SPI_IMR);
}
#endif
static int c2k_fast_spi_setup(struct spi_device *spi)
{
u32 hz;
hz = COMCERTO_DEFAULTAXICLK; //HAL_get_axi_clk();
if (!spi->bits_per_word)
spi->bits_per_word = 8;
if(spi->bits_per_word < SPI_FRAME_SIZE_MIN || spi->bits_per_word > SPI_FRAME_SIZE_MAX)
return -1;
if (spi->max_speed_hz < (hz/SPI_BAUDR_MAX))
return -1;
if (spi->max_speed_hz > SPI_SPEED_MAX) {
printf("decreasing speed %u to max supported %u\n",
spi->max_speed_hz, SPI_SPEED_MAX);
spi->max_speed_hz = SPI_SPEED_MAX;
}
if (spi->chip_select > SPI_CHIP_SELECT_MAX) {
printf("chip select %u out of range 0..%u\n",
spi->max_speed_hz, SPI_CHIP_SELECT_MAX);
return -1;
}
spi_udelay = 1 + ((1000000 * spi->bits_per_word) / spi->max_speed_hz);
spi_debug("%s: bits_per_word: %d max_speed_hz: %d\n",
__func__, spi->bits_per_word, spi->max_speed_hz);
return 0;
}
static int c2k_fast_spi_transfer(struct spi_device *spi, struct spi_message *mesg)
{
struct spi_transfer *t = NULL;
unsigned int op = mesg->status & 0xff; /* what operation */
int ret = -1;
struct spi_adapter *adapter = container_of(spi->master, struct spi_adapter, master);
u32 ctrlr0 = 0;
u32 baudr = 0;
u32 ser = 0;
u32 hz;
if (spi->mode & SPI_CPOL)
ctrlr0 |= SPI_CTRLR0_SCPOL;
if (spi->mode & SPI_CPHA)
ctrlr0 |= SPI_CTRLR0_SCPH;
ctrlr0 |= (spi->bits_per_word - 1) & 0xf;
hz = COMCERTO_DEFAULTAXICLK; //Later on will use HAL_get_axi_clk() to get the current AXI freq
if (spi->max_speed_hz > 0) {
baudr = hz / spi->max_speed_hz;
if (baudr < SPI_BAUDR_MIN)
baudr = SPI_BAUDR_MIN;
if (baudr > SPI_BAUDR_MAX)
baudr = SPI_BAUDR_MAX;
}
else
{
baudr = 0;
printf("%s: Baud rate not set!!\n", __func__);
}
ser = (1 << spi->chip_select) ;
while(readl(adapter->membase + COMCERTO_SPI_SR) & BUSY);
writel(0, adapter->membase + COMCERTO_SPI_SSIENR);
spi_debug ("%s: ser = 0x%x baudr = 0x%x ctrlr0 = 0x%x\n",\
__func__, ser, baudr, ctrlr0);
list_for_each_entry (t, &mesg->transfers, transfer_list) {
u8 *txbuf = (u8*)t->tx_buf;
u8 *rxbuf = (u8*)t->rx_buf;
switch(op) {
case SPI_TRANSFER_MODE_WRITE_ONLY:
ctrlr0 |= (0x0001 << 8);
writel(ctrlr0, adapter->membase + COMCERTO_SPI_CTRLR0);
writel(baudr, adapter->membase + COMCERTO_SPI_BAUDR);
writel(ser, adapter->membase + COMCERTO_SPI_SER);
#ifndef CONFIG_DRIVER_FAST_SPI_DMA
writel(8, adapter->membase + COMCERTO_SPI_RXFTLR);
writel(0, adapter->membase + COMCERTO_SPI_TXFTLR);
writel(0, adapter->membase + COMCERTO_SPI_IMR);
writel(1, adapter->membase + COMCERTO_SPI_SSIENR);
if (spi->bits_per_word <= 8)
ret = do_write_only_transfer8(adapter, txbuf, &t->len);
else
ret = do_write_only_transfer16(adapter, (u16*)txbuf, &t->len);
#else
ret = fast_spi_write_mem(spi, txbuf, t->len);
#endif
break;
case SPI_TRANSFER_MODE_READ_ONLY:
ctrlr0 |= (0x0002 << 8);
writel(ctrlr0, adapter->membase + COMCERTO_SPI_CTRLR0);
writel(baudr, adapter->membase + COMCERTO_SPI_BAUDR);
writel(ser, adapter->membase + COMCERTO_SPI_SER);
#ifndef CONFIG_DRIVER_FAST_SPI_DMA
writel(8, adapter->membase + COMCERTO_SPI_RXFTLR);
writel(0, adapter->membase + COMCERTO_SPI_TXFTLR);
writel(0, adapter->membase + COMCERTO_SPI_IMR);
writel(1, adapter->membase + COMCERTO_SPI_SSIENR);
if (spi->bits_per_word <= 8)
ret = do_read_only_transfer8(adapter, rxbuf, &t->len);
else
ret = do_read_only_transfer16(adapter, (u16 *)rxbuf, &t->len);
#else
ret = fast_spi_read_mem(spi, rxbuf, t->len);
#endif
break;
case SPI_TRANSFER_MODE_WRITE_READ:
ctrlr0 |= (0x0000 << 8);
writel(ctrlr0, adapter->membase + COMCERTO_SPI_CTRLR0);
writel(baudr, adapter->membase + COMCERTO_SPI_BAUDR);
writel(ser, adapter->membase + COMCERTO_SPI_SER);
#ifndef CONFIG_DRIVER_FAST_SPI_DMA
writel(8, adapter->membase + COMCERTO_SPI_RXFTLR);
writel(0, adapter->membase + COMCERTO_SPI_TXFTLR);
writel(0, adapter->membase + COMCERTO_SPI_IMR);
writel(1, adapter->membase + COMCERTO_SPI_SSIENR);
int len = t->len;
int max = 8;
while(len > 0)
{
if(len < max)
max = len;
ret = do_write_read_transfer(adapter, spi->bits_per_word,\
txbuf, &max, rxbuf, &max);
len -= max;
txbuf += max;
rxbuf += max;
}
#else
ret = fast_spi_writeread_mem(spi, rxbuf, t->len, txbuf, t->len);
#endif
break;
case SPI_TRANSFER_MODE_EEPROM_READ:
ctrlr0 |= (0x0003 << 8);
writel(ctrlr0, adapter->membase + COMCERTO_SPI_CTRLR0);
writel(baudr, adapter->membase + COMCERTO_SPI_BAUDR);
writel(ser, adapter->membase + COMCERTO_SPI_SER);
writel(t->len-1, adapter->membase + COMCERTO_SPI_CTRLR1); //ndf
#ifndef CONFIG_DRIVER_FAST_SPI_DMA
writel(8, adapter->membase + COMCERTO_SPI_RXFTLR);
writel(0, adapter->membase + COMCERTO_SPI_TXFTLR);
writel(0, adapter->membase + COMCERTO_SPI_IMR);
writel(1, adapter->membase + COMCERTO_SPI_SSIENR);
ret = do_eeprom_read(adapter, spi->bits_per_word, txbuf, &t->len, rxbuf, &t->len);
#else
ret = fast_spi_eeprom_read(spi, rxbuf, t->len, txbuf, t->len);
#endif
break;
default:
printk ("Transfer mode not supported.\n");
return ret;
};
/* deassert the chip select at least for this long */
udelay (spi_udelay);
while(readl(adapter->membase + COMCERTO_SPI_SR) & BUSY);
}
return 0;
}
void dma_out_of_reset()
{
U32 val;
val = readl(AXI_RESET_1);
writel((val & ~DUS_AXI_RESET), AXI_RESET_1);
/* 6 uS delay for reset to happen */
udelay(6);
}
static int c2k_fast_spi_probe(struct device_d *dev)
{
struct spi_master *master;
struct spi_adapter *adapter;
struct c2k_spi_master *pdata = dev->platform_data;
spi_debug("%s: initializing....\n",__func__);
adapter = (struct spi_adapter *) xzalloc(sizeof(struct spi_adapter));
master = &adapter->master;
master->dev = dev;
master->setup = c2k_fast_spi_setup;
master->transfer = c2k_fast_spi_transfer;
master->bus_num = 1;
master->num_chipselect = pdata->num_chipselect;
adapter->membase = dev->map_base;
comcerto_spi_hw_init(adapter);
dma_out_of_reset();
spi_register_master(master);
return 0;
}
/**
* comcerto_spi_remove -
*
*
*/
#if 0
static int comcerto_fast_spi_remove(struct spi_adapter *spi)
{
int ret = 0;
ret = spi_unregister_master(spi->master);
comcerto_spi_hw_reset(spi);
return ret;
}
#endif
/* barebox framework */
static struct driver_d c2k_fast_spi_driver = {
.name = "c2k_fast_spi",
.probe = c2k_fast_spi_probe,
};
int c2k_fast_spi_init(void)
{
int ret = 0;
ret = register_driver(&c2k_fast_spi_driver);
return ret;
}
device_initcall(c2k_fast_spi_init);
/***/