|  | /* | 
|  | * Driver of Andes SPI Controller | 
|  | * | 
|  | * (C) Copyright 2011 Andes Technology | 
|  | * Macpaul Lin <macpaul@andestech.com> | 
|  | * | 
|  | * See file CREDITS for list of people who contributed to this | 
|  | * project. | 
|  | * | 
|  | * 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 <malloc.h> | 
|  | #include <spi.h> | 
|  |  | 
|  | #include <asm/io.h> | 
|  | #include "andes_spi.h" | 
|  |  | 
|  | void spi_init(void) | 
|  | { | 
|  | /* do nothing */ | 
|  | } | 
|  |  | 
|  | static void andes_spi_spit_en(struct andes_spi_slave *ds) | 
|  | { | 
|  | unsigned int dcr = readl(&ds->regs->dcr); | 
|  |  | 
|  | debug("%s: dcr: %x, write value: %x\n", | 
|  | __func__, dcr, (dcr | ANDES_SPI_DCR_SPIT)); | 
|  |  | 
|  | writel((dcr | ANDES_SPI_DCR_SPIT), &ds->regs->dcr); | 
|  | } | 
|  |  | 
|  | struct spi_slave *spi_setup_slave(unsigned int bus, unsigned int cs, | 
|  | unsigned int max_hz, unsigned int mode) | 
|  | { | 
|  | struct andes_spi_slave	*ds; | 
|  |  | 
|  | if (!spi_cs_is_valid(bus, cs)) | 
|  | return NULL; | 
|  |  | 
|  | ds = malloc(sizeof(*ds)); | 
|  | if (!ds) | 
|  | return NULL; | 
|  |  | 
|  | ds->slave.bus = bus; | 
|  | ds->slave.cs = cs; | 
|  | ds->regs = (struct andes_spi_regs *)CONFIG_SYS_SPI_BASE; | 
|  |  | 
|  | /* | 
|  | * The hardware of andes_spi will set its frequency according | 
|  | * to APB/AHB bus clock. Hence the hardware doesn't allow changing of | 
|  | * requency and so the user requested speed is always ignored. | 
|  | */ | 
|  | ds->freq = max_hz; | 
|  |  | 
|  | return &ds->slave; | 
|  | } | 
|  |  | 
|  | void spi_free_slave(struct spi_slave *slave) | 
|  | { | 
|  | struct andes_spi_slave *ds = to_andes_spi(slave); | 
|  |  | 
|  | free(ds); | 
|  | } | 
|  |  | 
|  | int spi_claim_bus(struct spi_slave *slave) | 
|  | { | 
|  | struct andes_spi_slave *ds = to_andes_spi(slave); | 
|  | unsigned int apb; | 
|  | unsigned int baud; | 
|  |  | 
|  | /* Enable the SPI hardware */ | 
|  | writel(ANDES_SPI_CR_SPIRST, &ds->regs->cr); | 
|  | udelay(1000); | 
|  |  | 
|  | /* setup format */ | 
|  | baud = ((CONFIG_SYS_CLK_FREQ / CONFIG_SYS_SPI_CLK / 2) - 1) & 0xFF; | 
|  |  | 
|  | /* | 
|  | * SPI_CLK = AHB bus clock / ((BAUD + 1)*2) | 
|  | * BAUD = AHB bus clock / SPI_CLK / 2) - 1 | 
|  | */ | 
|  | apb = (readl(&ds->regs->apb) & 0xffffff00) | baud; | 
|  | writel(apb, &ds->regs->apb); | 
|  |  | 
|  | /* no interrupts */ | 
|  | writel(0, &ds->regs->ie); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | void spi_release_bus(struct spi_slave *slave) | 
|  | { | 
|  | struct andes_spi_slave *ds = to_andes_spi(slave); | 
|  |  | 
|  | /* Disable the SPI hardware */ | 
|  | writel(ANDES_SPI_CR_SPIRST, &ds->regs->cr); | 
|  | } | 
|  |  | 
|  | static int andes_spi_read(struct spi_slave *slave, unsigned int len, | 
|  | u8 *rxp, unsigned long flags) | 
|  | { | 
|  | struct andes_spi_slave *ds = to_andes_spi(slave); | 
|  | unsigned int i, left; | 
|  | unsigned int data; | 
|  |  | 
|  | debug("%s: slave: %x, len: %d, rxp: %x, flags: %d\n", | 
|  | __func__, slave, len, rxp, flags); | 
|  |  | 
|  | debug("%s: data: ", __func__); | 
|  | while (len > 0) { | 
|  | left = min(len, 4); | 
|  | data = readl(&ds->regs->data); | 
|  |  | 
|  | debug(" "); | 
|  | for (i = 0; i < left; i++) { | 
|  | debug("%02x ", data & 0xff); | 
|  | *rxp++ = data; | 
|  | data >>= 8; | 
|  | len--; | 
|  | } | 
|  | } | 
|  | debug("\n"); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int andes_spi_write(struct spi_slave *slave, unsigned int wlen, | 
|  | unsigned int rlen, const u8 *txp, unsigned long flags) | 
|  | { | 
|  | struct andes_spi_slave *ds = to_andes_spi(slave); | 
|  | unsigned int data; | 
|  | unsigned int i, left; | 
|  | unsigned int spit_enabled = 0; | 
|  |  | 
|  | debug("%s: slave: %x, wlen: %d, rlen: %d, txp: %x, flags: %x\n", | 
|  | __func__, slave, wlen, rlen, txp, flags); | 
|  |  | 
|  | /* The value of wlen and rlen wrote to register must minus 1 */ | 
|  | if (rlen == 0)					/* write only */ | 
|  | writel(ANDES_SPI_DCR_MODE_WO | ANDES_SPI_DCR_WCNT(wlen-1) | | 
|  | ANDES_SPI_DCR_RCNT(0), &ds->regs->dcr); | 
|  | else						/* write then read */ | 
|  | writel(ANDES_SPI_DCR_MODE_WR | ANDES_SPI_DCR_WCNT(wlen-1) | | 
|  | ANDES_SPI_DCR_RCNT(rlen-1), &ds->regs->dcr); | 
|  |  | 
|  | /* wait till SPIBSY is cleared */ | 
|  | while (readl(&ds->regs->st) & ANDES_SPI_ST_SPIBSY) | 
|  | ; | 
|  |  | 
|  | /* data write process */ | 
|  | debug("%s: txp: ", __func__); | 
|  | while (wlen > 0) { | 
|  | /* clear the data */ | 
|  | data = 0; | 
|  |  | 
|  | /* data are usually be read 32bits once a time */ | 
|  | left = min(wlen, 4); | 
|  |  | 
|  | for (i = 0; i < left; i++) { | 
|  | debug("%x ", *txp); | 
|  | data |= *txp++ << (i * 8); | 
|  | wlen--; | 
|  | } | 
|  | debug("\n"); | 
|  |  | 
|  | debug("data: %08x\n", data); | 
|  | debug("streg before write: %08x\n", readl(&ds->regs->st)); | 
|  | /* wait till TXFULL is deasserted */ | 
|  | while (readl(&ds->regs->st) & ANDES_SPI_ST_TXFEL) | 
|  | ; | 
|  | writel(data, &ds->regs->data); | 
|  | debug("streg after write: %08x\n", readl(&ds->regs->st)); | 
|  |  | 
|  |  | 
|  | if (spit_enabled == 0) { | 
|  | /* enable SPIT bit -  trigger the tx and rx progress */ | 
|  | andes_spi_spit_en(ds); | 
|  | spit_enabled = 1; | 
|  | } | 
|  |  | 
|  | } | 
|  | debug("\n"); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * spi_xfer: | 
|  | *	Since andes_spi doesn't support independent command transaction, | 
|  | *	that is, write and than read must be operated in continuous | 
|  | *	execution, there is no need to set dcr and trigger spit again in | 
|  | *	RX process. | 
|  | */ | 
|  | int spi_xfer(struct spi_slave *slave, unsigned int bitlen, | 
|  | const void *dout, void *din, unsigned long flags) | 
|  | { | 
|  | unsigned int len; | 
|  | static int op_nextime; | 
|  | static u8 tmp_cmd[5]; | 
|  | static int tmp_wlen; | 
|  | unsigned int i; | 
|  |  | 
|  | if (bitlen == 0) | 
|  | /* Finish any previously submitted transfers */ | 
|  | goto out; | 
|  |  | 
|  | if (bitlen % 8) { | 
|  | /* Errors always terminate an ongoing transfer */ | 
|  | flags |= SPI_XFER_END; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | len = bitlen / 8; | 
|  |  | 
|  | debug("%s: slave: %08x, bitlen: %d, dout: " | 
|  | "%08x, din: %08x, flags: %d, len: %d\n", | 
|  | __func__, slave, bitlen, dout, din, flags, len); | 
|  |  | 
|  | /* | 
|  | * Important: | 
|  | *	andes_spi's hardware doesn't support 2 data channel. The read | 
|  | *	and write cmd/data share the same register (data register). | 
|  | * | 
|  | *	If a command has write and read transaction, you cannot do write | 
|  | *	this time and then do read on next time. | 
|  | * | 
|  | *	A command writes first with a read response must indicating | 
|  | *	the read length in write operation. Hence the write action must | 
|  | *	be stored temporary and wait until the next read action has been | 
|  | *	arrived. Then we flush the write and read action out together. | 
|  | */ | 
|  | if (!dout) { | 
|  | if (op_nextime == 1) { | 
|  | /* flags should be SPI_XFER_END, value is 2 */ | 
|  | op_nextime = 0; | 
|  | andes_spi_write(slave, tmp_wlen, len, tmp_cmd, flags); | 
|  | } | 
|  | return andes_spi_read(slave, len, din, flags); | 
|  | } else if (!din) { | 
|  | if (flags == SPI_XFER_BEGIN) { | 
|  | /* store the write command and do operation next time */ | 
|  | op_nextime = 1; | 
|  | memset(tmp_cmd, 0, sizeof(tmp_cmd)); | 
|  | memcpy(tmp_cmd, dout, len); | 
|  |  | 
|  | debug("%s: tmp_cmd: ", __func__); | 
|  | for (i = 0; i < len; i++) | 
|  | debug("%x ", *(tmp_cmd + i)); | 
|  | debug("\n"); | 
|  |  | 
|  | tmp_wlen = len; | 
|  | } else { | 
|  | /* | 
|  | * flags should be (SPI_XFER_BEGIN | SPI_XFER_END), | 
|  | * the value is 3. | 
|  | */ | 
|  | if (op_nextime == 1) { | 
|  | /* flags should be SPI_XFER_END, value is 2 */ | 
|  | op_nextime = 0; | 
|  | /* flags 3 implies write only */ | 
|  | andes_spi_write(slave, tmp_wlen, 0, tmp_cmd, 3); | 
|  | } | 
|  |  | 
|  | debug("flags: %x\n", flags); | 
|  | return andes_spi_write(slave, len, 0, dout, flags); | 
|  | } | 
|  | } | 
|  |  | 
|  | out: | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int spi_cs_is_valid(unsigned int bus, unsigned int cs) | 
|  | { | 
|  | return bus == 0 && cs == 0; | 
|  | } | 
|  |  | 
|  | void spi_cs_activate(struct spi_slave *slave) | 
|  | { | 
|  | /* do nothing */ | 
|  | } | 
|  |  | 
|  | void spi_cs_deactivate(struct spi_slave *slave) | 
|  | { | 
|  | /* do nothing */ | 
|  | } |