246 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			C
		
	
	
	
			
		
		
	
	
			246 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			C
		
	
	
	
| /*
 | |
|  * Opencore tiny_spi driver
 | |
|  *
 | |
|  * http://opencores.org/project,tiny_spi
 | |
|  *
 | |
|  * based on bfin_spi.c
 | |
|  * Copyright (c) 2005-2008 Analog Devices Inc.
 | |
|  * Copyright (C) 2010 Thomas Chou <thomas@wytron.com.tw>
 | |
|  *
 | |
|  * SPDX-License-Identifier:	GPL-2.0+
 | |
|  */
 | |
| 
 | |
| #include <common.h>
 | |
| #include <asm/io.h>
 | |
| #include <malloc.h>
 | |
| #include <spi.h>
 | |
| #include <asm/gpio.h>
 | |
| 
 | |
| #define TINY_SPI_STATUS_TXE 0x1
 | |
| #define TINY_SPI_STATUS_TXR 0x2
 | |
| 
 | |
| struct tiny_spi_regs {
 | |
| 	unsigned rxdata;	/* Rx data reg */
 | |
| 	unsigned txdata;	/* Tx data reg */
 | |
| 	unsigned status;	/* Status reg */
 | |
| 	unsigned control;	/* Control reg */
 | |
| 	unsigned baud;		/* Baud reg */
 | |
| };
 | |
| 
 | |
| struct tiny_spi_host {
 | |
| 	uint base;
 | |
| 	uint freq;
 | |
| 	uint baudwidth;
 | |
| };
 | |
| static const struct tiny_spi_host tiny_spi_host_list[] =
 | |
| 	CONFIG_SYS_TINY_SPI_LIST;
 | |
| 
 | |
| struct tiny_spi_slave {
 | |
| 	struct spi_slave slave;
 | |
| 	const struct tiny_spi_host *host;
 | |
| 	uint mode;
 | |
| 	uint baud;
 | |
| 	uint flg;
 | |
| };
 | |
| #define to_tiny_spi_slave(s) container_of(s, struct tiny_spi_slave, slave)
 | |
| 
 | |
| int spi_cs_is_valid(unsigned int bus, unsigned int cs)
 | |
| {
 | |
| 	return bus < ARRAY_SIZE(tiny_spi_host_list) && gpio_is_valid(cs);
 | |
| }
 | |
| 
 | |
| void spi_cs_activate(struct spi_slave *slave)
 | |
| {
 | |
| 	struct tiny_spi_slave *tiny_spi = to_tiny_spi_slave(slave);
 | |
| 	unsigned int cs = slave->cs;
 | |
| 
 | |
| 	gpio_set_value(cs, tiny_spi->flg);
 | |
| 	debug("%s: SPI_CS_GPIO:%x\n", __func__, gpio_get_value(cs));
 | |
| }
 | |
| 
 | |
| void spi_cs_deactivate(struct spi_slave *slave)
 | |
| {
 | |
| 	struct tiny_spi_slave *tiny_spi = to_tiny_spi_slave(slave);
 | |
| 	unsigned int cs = slave->cs;
 | |
| 
 | |
| 	gpio_set_value(cs, !tiny_spi->flg);
 | |
| 	debug("%s: SPI_CS_GPIO:%x\n", __func__, gpio_get_value(cs));
 | |
| }
 | |
| 
 | |
| void spi_set_speed(struct spi_slave *slave, uint hz)
 | |
| {
 | |
| 	struct tiny_spi_slave *tiny_spi = to_tiny_spi_slave(slave);
 | |
| 	const struct tiny_spi_host *host = tiny_spi->host;
 | |
| 
 | |
| 	tiny_spi->baud = min(DIV_ROUND_UP(host->freq, hz * 2),
 | |
| 			     (1 << host->baudwidth)) - 1;
 | |
| 	debug("%s: speed %u actual %u\n", __func__, hz,
 | |
| 	      host->freq / ((tiny_spi->baud + 1) * 2));
 | |
| }
 | |
| 
 | |
| void spi_init(void)
 | |
| {
 | |
| }
 | |
| 
 | |
| struct spi_slave *spi_setup_slave(unsigned int bus, unsigned int cs,
 | |
| 				  unsigned int hz, unsigned int mode)
 | |
| {
 | |
| 	struct tiny_spi_slave *tiny_spi;
 | |
| 
 | |
| 	if (!spi_cs_is_valid(bus, cs) || gpio_request(cs, "tiny_spi"))
 | |
| 		return NULL;
 | |
| 
 | |
| 	tiny_spi = spi_alloc_slave(struct tiny_spi_slave, bus, cs);
 | |
| 	if (!tiny_spi)
 | |
| 		return NULL;
 | |
| 
 | |
| 	tiny_spi->host = &tiny_spi_host_list[bus];
 | |
| 	tiny_spi->mode = mode & (SPI_CPOL | SPI_CPHA);
 | |
| 	tiny_spi->flg = mode & SPI_CS_HIGH ? 1 : 0;
 | |
| 	spi_set_speed(&tiny_spi->slave, hz);
 | |
| 
 | |
| 	debug("%s: bus:%i cs:%i base:%lx\n", __func__,
 | |
| 		bus, cs, tiny_spi->host->base);
 | |
| 	return &tiny_spi->slave;
 | |
| }
 | |
| 
 | |
| void spi_free_slave(struct spi_slave *slave)
 | |
| {
 | |
| 	struct tiny_spi_slave *tiny_spi = to_tiny_spi_slave(slave);
 | |
| 
 | |
| 	gpio_free(slave->cs);
 | |
| 	free(tiny_spi);
 | |
| }
 | |
| 
 | |
| int spi_claim_bus(struct spi_slave *slave)
 | |
| {
 | |
| 	struct tiny_spi_slave *tiny_spi = to_tiny_spi_slave(slave);
 | |
| 	struct tiny_spi_regs *regs = (void *)tiny_spi->host->base;
 | |
| 
 | |
| 	debug("%s: bus:%i cs:%i\n", __func__, slave->bus, slave->cs);
 | |
| 	gpio_direction_output(slave->cs, !tiny_spi->flg);
 | |
| 	writel(tiny_spi->mode, ®s->control);
 | |
| 	writel(tiny_spi->baud, ®s->baud);
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| void spi_release_bus(struct spi_slave *slave)
 | |
| {
 | |
| 	debug("%s: bus:%i cs:%i\n", __func__, slave->bus, slave->cs);
 | |
| }
 | |
| 
 | |
| #ifndef CONFIG_TINY_SPI_IDLE_VAL
 | |
| # define CONFIG_TINY_SPI_IDLE_VAL 0xff
 | |
| #endif
 | |
| 
 | |
| int spi_xfer(struct spi_slave *slave, unsigned int bitlen, const void *dout,
 | |
| 	     void *din, unsigned long flags)
 | |
| {
 | |
| 	struct tiny_spi_slave *tiny_spi = to_tiny_spi_slave(slave);
 | |
| 	struct tiny_spi_regs *regs = (void *)tiny_spi->host->base;
 | |
| 	const u8 *txp = dout;
 | |
| 	u8 *rxp = din;
 | |
| 	uint bytes = bitlen / 8;
 | |
| 	uint i;
 | |
| 
 | |
| 	debug("%s: bus:%i cs:%i bitlen:%i bytes:%i flags:%lx\n", __func__,
 | |
| 		slave->bus, slave->cs, bitlen, bytes, flags);
 | |
| 	if (bitlen == 0)
 | |
| 		goto done;
 | |
| 
 | |
| 	/* assume to do 8 bits transfers */
 | |
| 	if (bitlen % 8) {
 | |
| 		flags |= SPI_XFER_END;
 | |
| 		goto done;
 | |
| 	}
 | |
| 
 | |
| 	if (flags & SPI_XFER_BEGIN)
 | |
| 		spi_cs_activate(slave);
 | |
| 
 | |
| 	/* we need to tighten the transfer loop */
 | |
| 	if (txp && rxp) {
 | |
| 		writeb(*txp++, ®s->txdata);
 | |
| 		if (bytes > 1) {
 | |
| 			writeb(*txp++, ®s->txdata);
 | |
| 			for (i = 2; i < bytes; i++) {
 | |
| 				u8 rx, tx = *txp++;
 | |
| 				while (!(readb(®s->status) &
 | |
| 					 TINY_SPI_STATUS_TXR))
 | |
| 					;
 | |
| 				rx = readb(®s->txdata);
 | |
| 				writeb(tx, ®s->txdata);
 | |
| 				*rxp++ = rx;
 | |
| 			}
 | |
| 			while (!(readb(®s->status) &
 | |
| 				 TINY_SPI_STATUS_TXR))
 | |
| 				;
 | |
| 			*rxp++ = readb(®s->txdata);
 | |
| 		}
 | |
| 		while (!(readb(®s->status) &
 | |
| 			 TINY_SPI_STATUS_TXE))
 | |
| 			;
 | |
| 		*rxp++ = readb(®s->rxdata);
 | |
| 	} else if (rxp) {
 | |
| 		writeb(CONFIG_TINY_SPI_IDLE_VAL, ®s->txdata);
 | |
| 		if (bytes > 1) {
 | |
| 			writeb(CONFIG_TINY_SPI_IDLE_VAL,
 | |
| 			       ®s->txdata);
 | |
| 			for (i = 2; i < bytes; i++) {
 | |
| 				u8 rx;
 | |
| 				while (!(readb(®s->status) &
 | |
| 					 TINY_SPI_STATUS_TXR))
 | |
| 					;
 | |
| 				rx = readb(®s->txdata);
 | |
| 				writeb(CONFIG_TINY_SPI_IDLE_VAL,
 | |
| 				       ®s->txdata);
 | |
| 				*rxp++ = rx;
 | |
| 			}
 | |
| 			while (!(readb(®s->status) &
 | |
| 				 TINY_SPI_STATUS_TXR))
 | |
| 				;
 | |
| 			*rxp++ = readb(®s->txdata);
 | |
| 		}
 | |
| 		while (!(readb(®s->status) &
 | |
| 			 TINY_SPI_STATUS_TXE))
 | |
| 			;
 | |
| 		*rxp++ = readb(®s->rxdata);
 | |
| 	} else if (txp) {
 | |
| 		writeb(*txp++, ®s->txdata);
 | |
| 		if (bytes > 1) {
 | |
| 			writeb(*txp++, ®s->txdata);
 | |
| 			for (i = 2; i < bytes; i++) {
 | |
| 				u8 tx = *txp++;
 | |
| 				while (!(readb(®s->status) &
 | |
| 					 TINY_SPI_STATUS_TXR))
 | |
| 					;
 | |
| 				writeb(tx, ®s->txdata);
 | |
| 			}
 | |
| 		}
 | |
| 		while (!(readb(®s->status) &
 | |
| 			 TINY_SPI_STATUS_TXE))
 | |
| 			;
 | |
| 	} else {
 | |
| 		writeb(CONFIG_TINY_SPI_IDLE_VAL, ®s->txdata);
 | |
| 		if (bytes > 1) {
 | |
| 			writeb(CONFIG_TINY_SPI_IDLE_VAL,
 | |
| 			       ®s->txdata);
 | |
| 			for (i = 2; i < bytes; i++) {
 | |
| 				while (!(readb(®s->status) &
 | |
| 					 TINY_SPI_STATUS_TXR))
 | |
| 					;
 | |
| 				writeb(CONFIG_TINY_SPI_IDLE_VAL,
 | |
| 				       ®s->txdata);
 | |
| 			}
 | |
| 		}
 | |
| 		while (!(readb(®s->status) &
 | |
| 			 TINY_SPI_STATUS_TXE))
 | |
| 			;
 | |
| 	}
 | |
| 
 | |
|  done:
 | |
| 	if (flags & SPI_XFER_END)
 | |
| 		spi_cs_deactivate(slave);
 | |
| 
 | |
| 	return 0;
 | |
| }
 |