blob: 2ec7a1ce310c49fac121d4d7ab881c2e4dafa69e [file] [log] [blame]
#include <asm/types.h>
#include <asm/io.h>
#include <linux/types.h>
#include <linux/byteorder/generic.h>
#include <linux/byteorder/little_endian.h>
#define COMCERTO_AXI_APB_CFG_BASE 0x90400000
#define SPI_BASEADDR (COMCERTO_AXI_APB_CFG_BASE + 0x098000)
#define COMCERTO_SPI_CTRLR0 0x00
#define COMCERTO_SPI_CTRLR1 0x04
#define COMCERTO_SPI_SSIENR 0x08
#define COMCERTO_SPI_MWCR 0x0C
#define COMCERTO_SPI_SER 0x10
#define COMCERTO_SPI_BAUDR 0x14
#define COMCERTO_SPI_TXFTLR 0x18
#define COMCERTO_SPI_RXFTLR 0x1C
#define COMCERTO_SPI_TXFLR 0x20
#define COMCERTO_SPI_RXFLR 0x24
#define COMCERTO_SPI_SR 0x28
#define COMCERTO_SPI_IMR 0x2C
#define COMCERTO_SPI_ISR 0x30
#define COMCERTO_SPI_RISR 0x34
#define COMCERTO_SPI_TXOICR 0x38
#define COMCERTO_SPI_RXOICR 0x3C
#define COMCERTO_SPI_RXUICR 0x40
#define COMCERTO_SPI_MSTICR 0x44
#define COMCERTO_SPI_ICR 0x48
#define COMCERTO_SPI_DMACR 0x4C
#define COMCERTO_SPI_DMATDLR 0x50
#define COMCERTO_SPI_DMARDLR 0x54
#define COMCERTO_SPI_IDR 0x58
#define COMCERTO_SPI_DR 0x60
/* SR - status register bits */
#define BUSY (1<<0) /* SSI busy flag, serial transfer in progress */
#define TFNF (1<<1) /* Transmit FIFO not full */
#define TFE (1<<2) /* Transmit FIFO empty */
#define RFNE (1<<3) /* Receive FIFO not empty */
#define RFF (1<<4) /* Receive FIFO full */
#define TXE (1<<5) /* Transmission error */
#define DCOL (1<<6) /* Data collision error */
/* Interrupt status after being masked */
#define TXEIS (1<<0) /* Transmit FIFO empty interrupt status */
#define TXOIS (1<<1) /* Transmit FIFO overflow interrupt status */
#define RXUIS (1<<2) /* Receive FIFO underflow interrupt status */
#define RXOIS (1<<3) /* Receive FIFO overflow interrupt status */
#define RXFIS (1<<4) /* Receive FIFO full interrupt status */
#define MSTIS (1<<5) /* Multi-Master contention interrupt status */
/* Interrupt status before being masked */
#define TXEIR (1<<0) /* Transmit FIFO empty interrupt status */
#define TXOIR (1<<1) /* Transmit FIFO overflow interrupt status */
#define RXUIR (1<<2) /* Receive FIFO underflow interrupt status */
#define RXOIR (1<<3) /* Receive FIFO overflow interrupt status */
#define RXFIR (1<<4) /* Receive FIFO full interrupt status */
#define MSTIR (1<<5) /* Multi-Master contention interrupt status */
/* Interrupt mask register */
#define TXEIM (1<<0) /* Transmit FIFO empty interrupt status */
#define TXOIM (1<<1) /* Transmit FIFO overflow interrupt status */
#define RXUIM (1<<2) /* Receive FIFO underflow interrupt status */
#define RXOIM (1<<3) /* Receive FIFO overflow interrupt status */
#define RXFIM (1<<4) /* Receive FIFO full interrupt status */
#define MSTIM (1<<5) /* Multi-Master contention interrupt status */
#define SPI_TRANSFER_MODE_WRITE_ONLY 0x01
#define SPI_TRANSFER_MODE_READ_ONLY 0x02
#define SPI_TRANSFER_MODE_WRITE_READ 0x03
#define SPI_TRANSFER_MODE_EEPROM_READ 0x04
#define SPI_CTRLR0_SCPOL (1 << 7)
#define SPI_CTRLR0_SCPH (1 << 6)
#define COMCERTO_DEFAULTAXICLK 250000000 /* Hz */
unsigned int spi_base = SPI_BASEADDR;
struct spi_transfer {
const void *tx_buf;
void *rx_buf;
unsigned len;
int mode;
};
int do_eeprom_read(u8 fs, u8 *wbuf, u32 *wlen, u8 *rbuf, u32 *ndf)
{
u32 sr, dr;
u32 wlen_now = 0, rlen_now = 0;
int rc = 0;
while (wlen_now < *wlen) {
sr = readl(spi_base + COMCERTO_SPI_SR);
if (sr & TFNF) {
if (wlen_now < *wlen) {
writew(cpu_to_le16((u16) *wbuf), spi_base + COMCERTO_SPI_DR);
wbuf++;
wlen_now++;
}
}
}
while (rlen_now < *ndf) {
sr = readl(spi_base + COMCERTO_SPI_SR);
if (sr & (RFF | DCOL)) {
/* read overrun, data collision */
rc = -1;
goto out;
}
if (sr & RFNE) {
dr = readw(spi_base + COMCERTO_SPI_DR);
if (rlen_now < *ndf) {
*rbuf = (u8) (le16_to_cpu(dr) & 0xff);
rbuf++;
rlen_now++;
} else {
/* read overflow */
rc = -1;
goto out;
}
}
}
out:
*ndf = rlen_now;
*wlen = wlen_now;
return rc;
}
/**
* do_write_read_transfer -
*
*
*/
int do_write_read_transfer(u8 fs, u8 *wbuf, u32 *wlen, u8 *rbuf, u32 *rlen)
{
u32 sr, dr;
u32 wlen_now = 0, rlen_now = 0;
int rc = 0;
while (wlen_now < *wlen) {
sr = readl(spi_base + COMCERTO_SPI_SR);
if (sr & TFNF) {
if (wlen_now < *wlen) {
writew(cpu_to_le16((u16) *wbuf), spi_base + COMCERTO_SPI_DR);
wbuf++;
wlen_now++;
}
}
}
while (rlen_now < *rlen) {
sr = readl(spi_base + COMCERTO_SPI_SR);
if (sr & (RFF | DCOL)) {
/* read overrun, data collision */
printf ("%s: sr=0x%x: Read Overrun.\n", __func__, sr);
rc = -1;
goto out;
}
if (sr & RFNE) {
dr = readw(spi_base + COMCERTO_SPI_DR);
if (rlen_now < *rlen) {
*rbuf = (u8) (le16_to_cpu(dr) & 0xff);
rbuf++;
rlen_now++;
} else {
printf ("%s: Read Overflow.\n", __func__);
/* read overflow */
rc = -1;
goto out;
}
}
}
out:
*rlen = rlen_now;
*wlen = wlen_now;
return rc;
}
/**
* do_write_only_transfer8 -
*
*
*/
int do_write_only_transfer8(u8 *buf, u32 *len)
{
u32 len_now;
int rc = 0;
u32 tmp = *len;
u32 dr = spi_base + COMCERTO_SPI_DR;
u32 txflr = spi_base + COMCERTO_SPI_TXFLR;
while (tmp)
{
len_now = 8 - readl(txflr);
if (len_now > tmp)
len_now = tmp;
tmp -= len_now;
/* warm-up write fifo to avoid underruns */
while (len_now--)
{
writew(cpu_to_le16((u16) *buf++), dr);
}
}
*len -= tmp;
return rc;
}
/**
* do_write_only_transfer -
*
*
*/
int do_write_only_transfer16(u16 *buf, u32 *len)
{
u32 len_now;
int rc = 0;
u32 tmp = *len;
u32 dr = spi_base + COMCERTO_SPI_DR;
u32 txflr = spi_base + COMCERTO_SPI_TXFLR;
while (tmp)
{
len_now = 8 - readl(txflr);
if (len_now > tmp)
len_now = tmp;
tmp -= len_now;
/* warm-up write fifo to avoid underruns */
while (len_now--)
writew(cpu_to_le16(*buf++), dr);
}
*len -= tmp;
return rc;
}
/**
* do_read_only_transfer -
*
*
*/
int do_read_only_transfer8(u8 *buf, u32 *len)
{
u32 len_now;
int rc = 0;
u32 tmp = *len;
u32 dr = spi_base + COMCERTO_SPI_DR;
u32 rxflr = spi_base + COMCERTO_SPI_RXFLR;
/* start the serial clock */
writew(0, dr);
while (tmp)
{
len_now = readl(rxflr);
if (len_now > tmp)
len_now = tmp;
tmp -= len_now;
while (len_now--) {
*buf = (u8) (le16_to_cpu(readw(dr)) & 0xff);
buf++;
}
}
*len -= tmp;
return rc;
}
/**
* do_read_only_transfer -
*
*
*/
int do_read_only_transfer16(u16 *buf, u32 *len)
{
u32 len_now;
int rc = 0;
u32 tmp = *len;
u32 dr = spi_base + COMCERTO_SPI_DR;
u32 rxflr = spi_base + COMCERTO_SPI_RXFLR;
/* start the serial clock */
writew(0, dr);
while (tmp)
{
len_now = readl(rxflr);
if (len_now > tmp)
len_now = tmp;
tmp -= len_now;
while (len_now--) {
*buf = le16_to_cpu(readw(dr));
buf++;
}
}
*len -= tmp;
return rc;
}
static int c2k_spi_transfer(u8 chip_select,struct spi_transfer *t)
{
unsigned int op = t->mode & 0xff;
u32 ctrlr0 = 0;
u32 baudr = 0;
u32 ser = 0;
u8 bits_per_word = 8;
u32 max_speed_hz = 4000000;
u32 hz;
u8 *txbuf;
u8 *rxbuf;
int spi_udelay;
ctrlr0 |= SPI_CTRLR0_SCPOL;
ctrlr0 |= SPI_CTRLR0_SCPH;
ctrlr0 |= (bits_per_word - 1) & 0xf;
hz = COMCERTO_DEFAULTAXICLK;
baudr = hz / max_speed_hz;
ser = (1 << chip_select) ;
spi_udelay = 1 + ((1000000 * bits_per_word) / max_speed_hz);
while(readl(spi_base + COMCERTO_SPI_SR) & BUSY);
writel(0, spi_base + COMCERTO_SPI_SSIENR);
txbuf = t->tx_buf;
rxbuf = t->rx_buf;
switch(op) {
case SPI_TRANSFER_MODE_WRITE_ONLY:
ctrlr0 |= (0x0001 << 8);
writel(ctrlr0, spi_base + COMCERTO_SPI_CTRLR0);
writel(baudr, spi_base + COMCERTO_SPI_BAUDR);
writel(ser, spi_base + COMCERTO_SPI_SER);
writel(8, spi_base + COMCERTO_SPI_RXFTLR);
writel(0, spi_base + COMCERTO_SPI_TXFTLR);
writel(0, spi_base + COMCERTO_SPI_IMR);
writel(1, spi_base + COMCERTO_SPI_SSIENR);
if (bits_per_word <= 8)
do_write_only_transfer8(txbuf, &t->len);
else
do_write_only_transfer16((u16*)txbuf, &t->len);
break;
case SPI_TRANSFER_MODE_READ_ONLY:
ctrlr0 |= (0x0002 << 8);
writel(ctrlr0, spi_base + COMCERTO_SPI_CTRLR0);
writel(baudr, spi_base + COMCERTO_SPI_BAUDR);
writel(ser, spi_base + COMCERTO_SPI_SER);
writel(8, spi_base + COMCERTO_SPI_RXFTLR);
writel(0, spi_base + COMCERTO_SPI_TXFTLR);
writel(0, spi_base + COMCERTO_SPI_IMR);
writel(1, spi_base + COMCERTO_SPI_SSIENR);
if (bits_per_word <= 8)
do_read_only_transfer8(rxbuf, &t->len);
else
do_read_only_transfer16((u16 *)rxbuf, &t->len);
break;
case SPI_TRANSFER_MODE_WRITE_READ:
ctrlr0 |= (0x0000 << 8);
writel(ctrlr0, spi_base + COMCERTO_SPI_CTRLR0);
writel(baudr, spi_base + COMCERTO_SPI_BAUDR);
writel(ser, spi_base + COMCERTO_SPI_SER);
writel(8, spi_base + COMCERTO_SPI_RXFTLR);
writel(0, spi_base + COMCERTO_SPI_TXFTLR);
writel(0, spi_base + COMCERTO_SPI_IMR);
writel(1, spi_base + COMCERTO_SPI_SSIENR);
do_write_read_transfer(bits_per_word, txbuf, &t->len, rxbuf, &t->len);
break;
default:
printf ("Transfer mode not supported.\n");
return -1;
};
/* deassert the chip select at least for this long */
udelay (spi_udelay);
return 0;
}
int c2k_spi_write(u8 cs, const void *buf, size_t len)
{
struct spi_transfer t = {
.tx_buf = buf,
.len = len,
.mode = SPI_TRANSFER_MODE_WRITE_ONLY,
};
c2k_spi_transfer(cs, &t);
}
int c2k_spi_read(u8 cs, void *buf, size_t len)
{
struct spi_transfer t = {
.rx_buf = buf,
.len = len,
.mode = SPI_TRANSFER_MODE_READ_ONLY,
};
c2k_spi_transfer(cs, &t);
}