blob: 95d2288bf14580c86d4f155000517550e21f3108 [file] [log] [blame]
/*
* Driver for simulating ssp as spi device for pxa
*
* Copyright (c) 2009 Marvell Inc.
* Lei Wen <leiwen@marvell.com>
*
* Licensed under the GPL-2 or later.
*/
#include <common.h>
#include <malloc.h>
#include <spi.h>
#include <watchdog.h>
#include <asm/io.h>
#include <asm/arch/pxa2xx_spi.h>
#include <asm/gpio.h>
#include <asm/arch/pxa_dma.h>
#include <asm/arch/cpu.h>
#define to_pxa_spi_slave(s) container_of(s, struct pxa_spi_slave, slave)
int cs_continuous;
#ifndef CONFIG_PXA_SPI_ENABLE_AUTO_CS
void spi_cs_activate(struct spi_slave *slave)
{
struct pxa_spi_slave *pss = to_pxa_spi_slave(slave);
gpio_set_value(slave->cs, pss->gpio_cs_inverted);
}
void spi_cs_deactivate(struct spi_slave *slave)
{
struct pxa_spi_slave *pss = to_pxa_spi_slave(slave);
gpio_set_value(slave->cs, !pss->gpio_cs_inverted);
}
#endif
static int spi_pxa_write(struct pxa_spi_slave *pss, unsigned int bitlen)
{
int wait_timeout = SSP_FLUSH_NUM;
int bytes = bitlen / pss->slave.wordlen;
while (--wait_timeout && !(readl(&pss->spi_reg->sssr) & SSSR_TNF))
;
if (!wait_timeout) {
debug("%s: timeout error\n", __func__);
return -1;
}
while (bytes--) {
if (pss->tx != NULL) {
if (pss->slave.wordlen > 16) {
writel(*(u32 *)pss->tx, &pss->spi_reg->ssdr);
pss->tx += 4;
} else if (pss->slave.wordlen > 8) {
writel(*(u16 *)pss->tx, &pss->spi_reg->ssdr);
pss->tx += 2;
} else {
writel(*(u8 *)pss->tx, &pss->spi_reg->ssdr);
++pss->tx;
}
} else
writel(0, &pss->spi_reg->ssdr);
while ((readl(&pss->spi_reg->sssr)&0xF10) && wait_timeout--) {
nop();
nop();
}
if (!wait_timeout) {
debug("%s: timeout error\n", __func__);
return -1;
}
}
return 0;
}
static int spi_pxa_read(struct pxa_spi_slave *pss, unsigned int bitlen)
{
int wait_timeout = SSP_FLUSH_NUM;
int bytes = bitlen / pss->slave.wordlen;
while (--wait_timeout && !(readl(&pss->spi_reg->sssr) & SSSR_TNF))
;
if (!wait_timeout) {
debug("%s: timeout error\n", __func__);
return -1;
}
while (bytes--) {
wait_timeout = SSP_FLUSH_NUM;
writel(0xff, &pss->spi_reg->ssdr);
while ((readl(&pss->spi_reg->sssr)&0xF10) && wait_timeout--) {
nop();
nop();
}
if (!wait_timeout) {
printf("%s: timeout error, sssr:0x%x. byte:0x%x.\n",
__func__, readl(&pss->spi_reg->sssr), bytes);
return -1;
}
wait_timeout = SSP_FLUSH_NUM;
while (--wait_timeout && !(readl(&pss->spi_reg->sssr) & SSSR_RNE))
;
if (!wait_timeout) {
printf("%s: timeout error sssr:0x%x.\n", __func__,
readl(&pss->spi_reg->sssr));
return -1;
}
if (pss->rx != NULL) {
if (pss->slave.wordlen > 16) {
*(u32 *)pss->rx = readl(&pss->spi_reg->ssdr);
pss->rx += 4;
} else if (pss->slave.wordlen > 8) {
*(u16 *)pss->rx = readl(&pss->spi_reg->ssdr);
pss->rx += 2;
} else {
*(u8 *)pss->rx = readl(&pss->spi_reg->ssdr);
++pss->rx;
}
} else
readl(&pss->spi_reg->ssdr);
}
return 0;
}
static int spi_pxa_flush(struct pxa_spi_slave *pss)
{
unsigned long limit = SSP_FLUSH_NUM;
do {
while (readl(&pss->spi_reg->sssr) & SSSR_RNE)
readl(&pss->spi_reg->ssdr);
} while ((readl(&pss->spi_reg->sssr) & SSSR_BSY) && limit--);
writel(SSSR_ROR, &pss->spi_reg->sssr);
return limit;
}
struct spi_slave *spi_setup_slave(unsigned int bus, unsigned int cs,
unsigned int max_hz, unsigned int mode)
{
struct pxa_spi_slave *pss;
pss = spi_alloc_slave(struct pxa_spi_slave, bus, cs);
if (!pss)
return NULL;
pss->slave.bus = bus;
#ifndef CONFIG_PXA_SPI_ENABLE_AUTO_CS
pss->slave.cs = cs;
#endif
pss->spi_reg = (struct ssp_reg *)CONFIG_SYS_SSP_BASE;
#ifdef CONFIG_SPI_52M_ENABLE
pss->cr0 = SSCR0_MOTO | SSCR0_DATASIZE(DEFAULT_WORD_LEN)
| SSCR0_SSE | SSCR0_52MM;
#else
pss->cr0 = SSCR0_MOTO | SSCR0_DATASIZE(DEFAULT_WORD_LEN)
| SSCR0_SSE;
#endif
pss->cr1 = SSCR1_TTE | SSCR1_TTELP |
(SSCR1_RXTRESH(RX_THRESH_DEF) & SSCR1_RFT) |
(SSCR1_TXTRESH(TX_THRESH_DEF) & SSCR1_TFT);
pss->cr1 &= ~(SSCR1_SPO | SSCR1_SPH);
pss->cr1 |= (((mode & SPI_CPHA) != 0) ? SSCR1_SPH : 0)
| (((mode & SPI_CPOL) != 0) ? SSCR1_SPO : 0);
pss->int_cr1 = SSCR1_TIE | SSCR1_RIE | SSCR1_TINTE;
pss->clear_sr = SSSR_ROR | SSSR_TINT;
if (cs) {
#ifndef CONFIG_PXA_SPI_ENABLE_AUTO_CS
gpio_direction_output(cs, !pss->gpio_cs_inverted);
pss->gpio_cs_inverted = mode & SPI_CS_HIGH;
gpio_set_value(cs, !pss->gpio_cs_inverted);
#endif
}
cs_continuous = 0;
return &pss->slave;
}
void spi_free_slave(struct spi_slave *slave)
{
struct pxa_spi_slave *pss = to_pxa_spi_slave(slave);
free(pss);
}
#ifdef CONFIG_PXA_DMA
__attribute__ ((aligned(32))) unsigned int dummy[0x400];
#endif
void spi_init(void)
{
unsigned int reg;
int clk = SSP_26M; // default 26M
if (CONFIG_SYS_SSP_BASE == SSP0_BASE)
reg = APBC_SSP0_CLK_RST;
else if (CONFIG_SYS_SSP_BASE == SSP1_BASE)
reg = APBC_SSP1_CLK_RST;
else if (CONFIG_SYS_SSP_BASE == SSP2_BASE)
reg = APBC_SSP2_CLK_RST;
else {
printf("Fatal error: unsupportted ssp base: 0x%x.\n", CONFIG_SYS_SSP_BASE);
return;
}
#ifdef CONFIG_SPI_52M_ENABLE
clk = SSP_52M;
#else
#ifdef CONFIG_SPI_26M_ENABLE
clk = SSP_26M;
#else
#ifdef CONFIG_SPI_13M_ENABLE
clk = SSP_13M;
#else
#ifdef CONFIG_SPI_6P5M_ENABLE
clk = SSP_6P5M;
#endif
#endif
#endif
#endif
__raw_writel(APBC_SSP_BCLKEN | APBC_SSP_FNCLKEN |
(clk << APBC_SSP_FNCLKSEL_SHIFT), reg);
#ifdef CONFIG_PXA_DMA
memset(dummy, 0x0, 0x400*4);
#endif
/* Load default SSP configuration */
writel(0, CONFIG_SYS_SSP_BASE + SSCR0);
writel(SSCR1_RXTRESH(RX_THRESH_DEF) |
SSCR1_TXTRESH(TX_THRESH_DEF), CONFIG_SYS_SSP_BASE + SSCR1);
writel(SSCR0_MOTO | SSCR0_DATASIZE(DEFAULT_WORD_LEN)
, CONFIG_SYS_SSP_BASE + SSCR0);
writel(0, CONFIG_SYS_SSP_BASE + SSTO);
writel(0, CONFIG_SYS_SSP_BASE + SSPSP);
__raw_writel(DMA_CLK_AXICLK_EN | DMA_CLK_AXI_RST,
PMUA_DMA_CLK_RES_CTRL); /* enable DMA clock */
}
int spi_claim_bus(struct spi_slave *slave)
{
struct pxa_spi_slave *pss = to_pxa_spi_slave(slave);
slave->wordlen = 8; /* set default wordlen */
if (spi_pxa_flush(pss) == 0)
return -1;
return 0;
}
void spi_release_bus(struct spi_slave *slave)
{
}
int spi_xfer(struct spi_slave *slave, unsigned int bitlen, const void *dout,
void *din, unsigned long flags)
{
struct pxa_spi_slave *pss = to_pxa_spi_slave(slave);
int ret = 0;
if (slave->wordlen < 4 || slave->wordlen > 32) {
printf("pxa2xx_spi: invalid wordlen %d\n", slave->wordlen);
return -1;
}
if (bitlen % slave->wordlen)
return -1;
pss->rx = din;
pss->tx = dout;
writel(0, &pss->spi_reg->sscr0);
pss->cr0 &= ~(0xf);
if (slave->wordlen > 16)
pss->cr0 |= SSCR0_EDSS | (slave->wordlen - 1);
else
pss->cr0 |= 0xc00000 | (slave->wordlen - 1);
writel(pss->cr1, &pss->spi_reg->sscr1);
writel(TIMEOUT_DEF, &pss->spi_reg->ssto);
writel(pss->cr0, &pss->spi_reg->sscr0);
if (flags & SPI_XFER_BEGIN) {
#ifndef CONFIG_PXA_SPI_ENABLE_AUTO_CS
if (!cs_continuous)
spi_cs_activate(slave);
#endif
}
if (dout != NULL && din != NULL) {
ret = spi_pxa_write(pss, bitlen - slave->wordlen);
if(ret)
return -1;
ret = spi_pxa_read(pss, slave->wordlen);
if(ret)
return -1;
} else if (dout != NULL) {
ret = spi_pxa_write(pss, bitlen);
if(ret)
return -1;
} else if (din != NULL) {
ret = spi_pxa_read(pss, bitlen);
if(ret)
return -1;
}
if (flags & SPI_XFER_END) {
writel(pss->clear_sr, &pss->spi_reg->sssr);
writel(0, &pss->spi_reg->ssto);
writel(0, &pss->spi_reg->sscr0);
#ifndef CONFIG_PXA_SPI_ENABLE_AUTO_CS
if (!cs_continuous)
spi_cs_deactivate(slave);
#endif
}
return ret;
}
#ifdef CONFIG_PXA_DMA
static int spi_dma_read(void *din, unsigned int len)
{
int i, init = 0;
if (!init) {
if (cpu_is_pxa1826_z3() || cpu_is_pxa1826_a0()) {
dmac_map_device_to_channel(DMAC_SSP2_RX,
SSP_RX_CHANNEL);
dmac_map_device_to_channel(DMAC_SSP2_TX,
SSP_TX_CHANNEL);
} else {
dmac_map_device_to_channel(DMAC_SSP2_RX_Z12,
SSP_RX_CHANNEL);
dmac_map_device_to_channel(DMAC_SSP2_TX_Z12,
SSP_TX_CHANNEL);
}
dmac_user_aligment(SSP_RX_CHANNEL);
dmac_user_aligment(SSP_TX_CHANNEL);
init = 1;
}
pxa_dma_read((unsigned int)din, SSP_SSDR, len, SSP_RX_CHANNEL);
pxa_dma_write(SSP_SSDR, (unsigned int)dummy, len, SSP_TX_CHANNEL);
dmac_start_transfer(SSP_RX_CHANNEL);
dmac_start_transfer(SSP_TX_CHANNEL);
do {
for (i = 0; i < 500; i++)
nop();
if (dmac_read_dcsr(SSP_TX_CHANNEL) & DCSR_STOPSTATE)
break;
} while (1);
do {
if (dmac_read_dcsr(SSP_RX_CHANNEL) & DCSR_STOPSTATE)
break;
} while (1);
for (i = 0; i < len; i += 4)
swab32s(din+i);
if (!(dmac_read_dcsr(SSP_TX_CHANNEL) & DCSR_STOPSTATE)) {
printf("Error:DMA write operate timeout.\n");
return 1;
}
if (!(dmac_read_dcsr(SSP_RX_CHANNEL) & DCSR_STOPSTATE)) {
printf("Error:DMA read operate timeout.\n");
return 1;
}
return 0;
}
static int spi_dma_write(void *dout, unsigned int len)
{
int i, init = 0;
for (i = 0; i < len; i += 4)
swab32s(dout+i);
flush_dcache_all();
if (!init) {
if (cpu_is_pxa1826_z3() || cpu_is_pxa1826_a0())
dmac_map_device_to_channel(DMAC_SSP2_TX,
SSP_TX_CHANNEL);
else
dmac_map_device_to_channel(DMAC_SSP2_TX_Z12,
SSP_TX_CHANNEL);
dmac_user_aligment(SSP_TX_CHANNEL);
init = 1;
}
pxa_dma_write(SSP_SSDR, (unsigned int)dout, len, SSP_TX_CHANNEL);
dmac_start_transfer(SSP_TX_CHANNEL);
do {
for (i = 0; i < 500; i++)
nop();
if (dmac_read_dcsr(SSP_TX_CHANNEL) & DCSR_STOPSTATE)
break;
} while (1);
if (!(dmac_read_dcsr(SSP_TX_CHANNEL) & DCSR_STOPSTATE)) {
printf("Error:DMA write operate timeout.\n");
return 1;
}
return 0;
}
int spi_dma_xfer(struct spi_slave *slave, const u8 *cmd, size_t cmd_len,
const void *dout, const void *din, size_t data_len)
{
struct pxa_spi_slave *pss = to_pxa_spi_slave(slave);
int ret = 0;
#ifndef CONFIG_PXA_SPI_ENABLE_AUTO_CS
spi_cs_activate(slave);
#endif
if (cmd) {
cs_continuous = 1;
spi_xfer(slave, cmd_len*8, cmd, NULL,
SPI_XFER_BEGIN | SPI_XFER_END);
}
if (dout || din) {
writel(TIMEOUT_DEF, &pss->spi_reg->ssto);
#ifdef CONFIG_SPI_52M_ENABLE
writel(SSCR1_TTELP | SSCR1_TTE | SSCR1_RXTRESH(0x3)
| SSCR1_TRAIL | SSCR1_TSRE | SSCR1_RSRE |
SSCR1_TXTRESH(0x4), &pss->spi_reg->sscr1);
writel(SSCR0_52MM | SSCR0_SSE | SSCR0_EDSS |
SSCR0_DATASIZE(0x10), &pss->spi_reg->sscr0);
#else
writel(SSCR1_TTELP | SSCR1_TTE | SSCR1_RXTRESH(0x9)
| SSCR1_TRAIL | SSCR1_TSRE | SSCR1_RSRE |
SSCR1_TXTRESH(0x4), &pss->spi_reg->sscr1);
writel(SSCR0_SSE | SSCR0_EDSS |
SSCR0_DATASIZE(0x10), &pss->spi_reg->sscr0);
#endif
}
if (dout)
ret = spi_dma_write((void *)dout, data_len);
if (din)
ret = spi_dma_read((void *)din, data_len);
while (readl(&pss->spi_reg->sssr) & 0xF10)
;
if (dout || din) {
writel(pss->clear_sr, &pss->spi_reg->sssr);
clrbits_le32(&pss->spi_reg->sscr1, pss->int_cr1);
writel(0, &pss->spi_reg->sscr0);
#ifndef CONFIG_PXA_SPI_ENABLE_AUTO_CS
spi_cs_deactivate(slave);
#endif
}
cs_continuous = 0;
return ret;
}
#endif