273 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			C
		
	
	
	
			
		
		
	
	
			273 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			C
		
	
	
	
/*
 | 
						|
 * SPI Driver for EP93xx
 | 
						|
 *
 | 
						|
 * Copyright (C) 2013 Sergey Kostanabev <sergey.kostanbaev <at> fairwaves.ru>
 | 
						|
 *
 | 
						|
 * Inspired form linux kernel driver and atmel uboot driver
 | 
						|
 *
 | 
						|
 * SPDX-License-Identifier:	GPL-2.0+
 | 
						|
 */
 | 
						|
 | 
						|
#include <common.h>
 | 
						|
#include <spi.h>
 | 
						|
#include <malloc.h>
 | 
						|
 | 
						|
#include <asm/io.h>
 | 
						|
 | 
						|
#include <asm/arch/ep93xx.h>
 | 
						|
 | 
						|
#define SSPBASE			SPI_BASE
 | 
						|
 | 
						|
#define SSPCR0			0x0000
 | 
						|
#define SSPCR0_MODE_SHIFT	6
 | 
						|
#define SSPCR0_SCR_SHIFT	8
 | 
						|
#define SSPCR0_SPH		BIT(7)
 | 
						|
#define SSPCR0_SPO		BIT(6)
 | 
						|
#define SSPCR0_FRF_SPI		0
 | 
						|
#define SSPCR0_DSS_8BIT		7
 | 
						|
 | 
						|
#define SSPCR1			0x0004
 | 
						|
#define SSPCR1_RIE		BIT(0)
 | 
						|
#define SSPCR1_TIE		BIT(1)
 | 
						|
#define SSPCR1_RORIE		BIT(2)
 | 
						|
#define SSPCR1_LBM		BIT(3)
 | 
						|
#define SSPCR1_SSE		BIT(4)
 | 
						|
#define SSPCR1_MS		BIT(5)
 | 
						|
#define SSPCR1_SOD		BIT(6)
 | 
						|
 | 
						|
#define SSPDR			0x0008
 | 
						|
 | 
						|
#define SSPSR			0x000c
 | 
						|
#define SSPSR_TFE		BIT(0)
 | 
						|
#define SSPSR_TNF		BIT(1)
 | 
						|
#define SSPSR_RNE		BIT(2)
 | 
						|
#define SSPSR_RFF		BIT(3)
 | 
						|
#define SSPSR_BSY		BIT(4)
 | 
						|
#define SSPCPSR			0x0010
 | 
						|
 | 
						|
#define SSPIIR			0x0014
 | 
						|
#define SSPIIR_RIS		BIT(0)
 | 
						|
#define SSPIIR_TIS		BIT(1)
 | 
						|
#define SSPIIR_RORIS		BIT(2)
 | 
						|
#define SSPICR			SSPIIR
 | 
						|
 | 
						|
#define SSPCLOCK		14745600
 | 
						|
#define SSP_MAX_RATE		(SSPCLOCK / 2)
 | 
						|
#define SSP_MIN_RATE		(SSPCLOCK / (254 * 256))
 | 
						|
 | 
						|
/* timeout in milliseconds */
 | 
						|
#define SPI_TIMEOUT		5
 | 
						|
/* maximum depth of RX/TX FIFO */
 | 
						|
#define SPI_FIFO_SIZE		8
 | 
						|
 | 
						|
struct ep93xx_spi_slave {
 | 
						|
	struct spi_slave slave;
 | 
						|
 | 
						|
	unsigned sspcr0;
 | 
						|
	unsigned sspcpsr;
 | 
						|
};
 | 
						|
 | 
						|
static inline struct ep93xx_spi_slave *to_ep93xx_spi(struct spi_slave *slave)
 | 
						|
{
 | 
						|
	return container_of(slave, struct ep93xx_spi_slave, slave);
 | 
						|
}
 | 
						|
 | 
						|
void spi_init()
 | 
						|
{
 | 
						|
}
 | 
						|
 | 
						|
static inline void ep93xx_spi_write_u8(u16 reg, u8 value)
 | 
						|
{
 | 
						|
	writel(value, (unsigned int *)(SSPBASE + reg));
 | 
						|
}
 | 
						|
 | 
						|
static inline u8 ep93xx_spi_read_u8(u16 reg)
 | 
						|
{
 | 
						|
	return readl((unsigned int *)(SSPBASE + reg));
 | 
						|
}
 | 
						|
 | 
						|
static inline void ep93xx_spi_write_u16(u16 reg, u16 value)
 | 
						|
{
 | 
						|
	writel(value, (unsigned int *)(SSPBASE + reg));
 | 
						|
}
 | 
						|
 | 
						|
static inline u16 ep93xx_spi_read_u16(u16 reg)
 | 
						|
{
 | 
						|
	return (u16)readl((unsigned int *)(SSPBASE + reg));
 | 
						|
}
 | 
						|
 | 
						|
static int ep93xx_spi_init_hw(unsigned int rate, unsigned int mode,
 | 
						|
				struct ep93xx_spi_slave *slave)
 | 
						|
{
 | 
						|
	unsigned cpsr, scr;
 | 
						|
 | 
						|
	if (rate > SSP_MAX_RATE)
 | 
						|
		rate = SSP_MAX_RATE;
 | 
						|
 | 
						|
	if (rate < SSP_MIN_RATE)
 | 
						|
		return -1;
 | 
						|
 | 
						|
	/* Calculate divisors so that we can get speed according the
 | 
						|
	 * following formula:
 | 
						|
	 *	rate = spi_clock_rate / (cpsr * (1 + scr))
 | 
						|
	 *
 | 
						|
	 * cpsr must be even number and starts from 2, scr can be any number
 | 
						|
	 * between 0 and 255.
 | 
						|
	 */
 | 
						|
	for (cpsr = 2; cpsr <= 254; cpsr += 2) {
 | 
						|
		for (scr = 0; scr <= 255; scr++) {
 | 
						|
			if ((SSPCLOCK / (cpsr * (scr + 1))) <= rate) {
 | 
						|
				/* Set CHPA and CPOL, SPI format and 8bit */
 | 
						|
				unsigned sspcr0 = (scr << SSPCR0_SCR_SHIFT) |
 | 
						|
					SSPCR0_FRF_SPI | SSPCR0_DSS_8BIT;
 | 
						|
				if (mode & SPI_CPHA)
 | 
						|
					sspcr0 |= SSPCR0_SPH;
 | 
						|
				if (mode & SPI_CPOL)
 | 
						|
					sspcr0 |= SSPCR0_SPO;
 | 
						|
 | 
						|
				slave->sspcr0 = sspcr0;
 | 
						|
				slave->sspcpsr = cpsr;
 | 
						|
				return 0;
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return -1;
 | 
						|
}
 | 
						|
 | 
						|
void spi_set_speed(struct spi_slave *slave, unsigned int hz)
 | 
						|
{
 | 
						|
	struct ep93xx_spi_slave *as = to_ep93xx_spi(slave);
 | 
						|
 | 
						|
	unsigned int mode = 0;
 | 
						|
	if (as->sspcr0 & SSPCR0_SPH)
 | 
						|
		mode |= SPI_CPHA;
 | 
						|
	if (as->sspcr0 & SSPCR0_SPO)
 | 
						|
		mode |= SPI_CPOL;
 | 
						|
 | 
						|
	ep93xx_spi_init_hw(hz, mode, as);
 | 
						|
}
 | 
						|
 | 
						|
struct spi_slave *spi_setup_slave(unsigned int bus, unsigned int cs,
 | 
						|
			unsigned int max_hz, unsigned int mode)
 | 
						|
{
 | 
						|
	struct ep93xx_spi_slave	*as;
 | 
						|
 | 
						|
	if (!spi_cs_is_valid(bus, cs))
 | 
						|
		return NULL;
 | 
						|
 | 
						|
	as = spi_alloc_slave(struct ep93xx_spi_slave, bus, cs);
 | 
						|
	if (!as)
 | 
						|
		return NULL;
 | 
						|
 | 
						|
	if (ep93xx_spi_init_hw(max_hz, mode, as)) {
 | 
						|
		free(as);
 | 
						|
		return NULL;
 | 
						|
	}
 | 
						|
 | 
						|
	return &as->slave;
 | 
						|
}
 | 
						|
 | 
						|
void spi_free_slave(struct spi_slave *slave)
 | 
						|
{
 | 
						|
	struct ep93xx_spi_slave *as = to_ep93xx_spi(slave);
 | 
						|
 | 
						|
	free(as);
 | 
						|
}
 | 
						|
 | 
						|
int spi_claim_bus(struct spi_slave *slave)
 | 
						|
{
 | 
						|
	struct ep93xx_spi_slave *as = to_ep93xx_spi(slave);
 | 
						|
 | 
						|
	/* Enable the SPI hardware */
 | 
						|
	ep93xx_spi_write_u8(SSPCR1, SSPCR1_SSE);
 | 
						|
 | 
						|
 | 
						|
	ep93xx_spi_write_u8(SSPCPSR, as->sspcpsr);
 | 
						|
	ep93xx_spi_write_u16(SSPCR0, as->sspcr0);
 | 
						|
 | 
						|
	debug("Select CS:%d SSPCPSR=%02x SSPCR0=%04x\n",
 | 
						|
	      slave->cs, as->sspcpsr, as->sspcr0);
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
void spi_release_bus(struct spi_slave *slave)
 | 
						|
{
 | 
						|
	/* Disable the SPI hardware */
 | 
						|
	ep93xx_spi_write_u8(SSPCR1, 0);
 | 
						|
}
 | 
						|
 | 
						|
int spi_xfer(struct spi_slave *slave, unsigned int bitlen,
 | 
						|
		const void *dout, void *din, unsigned long flags)
 | 
						|
{
 | 
						|
	unsigned int	len_tx;
 | 
						|
	unsigned int	len_rx;
 | 
						|
	unsigned int	len;
 | 
						|
	u32		status;
 | 
						|
	const u8	*txp = dout;
 | 
						|
	u8		*rxp = din;
 | 
						|
	u8		value;
 | 
						|
 | 
						|
	debug("spi_xfer: slave %u:%u dout %p din %p bitlen %u\n",
 | 
						|
	      slave->bus, slave->cs, (uint *)dout, (uint *)din, bitlen);
 | 
						|
 | 
						|
 | 
						|
	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;
 | 
						|
 | 
						|
 | 
						|
	if (flags & SPI_XFER_BEGIN) {
 | 
						|
		/* Empty RX FIFO */
 | 
						|
		while ((ep93xx_spi_read_u8(SSPSR) & SSPSR_RNE))
 | 
						|
			ep93xx_spi_read_u8(SSPDR);
 | 
						|
 | 
						|
		spi_cs_activate(slave);
 | 
						|
	}
 | 
						|
 | 
						|
	for (len_tx = 0, len_rx = 0; len_rx < len; ) {
 | 
						|
		status = ep93xx_spi_read_u8(SSPSR);
 | 
						|
 | 
						|
		if ((len_tx < len) && (status & SSPSR_TNF)) {
 | 
						|
			if (txp)
 | 
						|
				value = *txp++;
 | 
						|
			else
 | 
						|
				value = 0xff;
 | 
						|
 | 
						|
			ep93xx_spi_write_u8(SSPDR, value);
 | 
						|
			len_tx++;
 | 
						|
		}
 | 
						|
 | 
						|
		if (status & SSPSR_RNE) {
 | 
						|
			value = ep93xx_spi_read_u8(SSPDR);
 | 
						|
 | 
						|
			if (rxp)
 | 
						|
				*rxp++ = value;
 | 
						|
			len_rx++;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
out:
 | 
						|
	if (flags & SPI_XFER_END) {
 | 
						|
		/*
 | 
						|
		 * Wait until the transfer is completely done before
 | 
						|
		 * we deactivate CS.
 | 
						|
		 */
 | 
						|
		do {
 | 
						|
			status = ep93xx_spi_read_u8(SSPSR);
 | 
						|
		} while (status & SSPSR_BSY);
 | 
						|
 | 
						|
		spi_cs_deactivate(slave);
 | 
						|
	}
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 |