diff options
Diffstat (limited to 'drivers/spi/omap3_spi.c')
| -rw-r--r-- | drivers/spi/omap3_spi.c | 352 | 
1 files changed, 352 insertions, 0 deletions
| diff --git a/drivers/spi/omap3_spi.c b/drivers/spi/omap3_spi.c new file mode 100644 index 000000000..af12c0e59 --- /dev/null +++ b/drivers/spi/omap3_spi.c @@ -0,0 +1,352 @@ +/* + * Copyright (C) 2010 Dirk Behme <dirk.behme@googlemail.com> + * + * Driver for McSPI controller on OMAP3. Based on davinci_spi.c + * Copyright (C) 2009 Texas Instruments Incorporated - http://www.ti.com/ + * + * Copyright (C) 2007 Atmel Corporation + * + * Parts taken from linux/drivers/spi/omap2_mcspi.c + * Copyright (C) 2005, 2006 Nokia Corporation + * + * Modified by Ruslan Araslanov <ruslan.araslanov@vitecmm.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 <spi.h> +#include <malloc.h> +#include <asm/io.h> +#include "omap3_spi.h" + +#define WORD_LEN	8 +#define SPI_WAIT_TIMEOUT 3000000; + +static void spi_reset(struct omap3_spi_slave *ds) +{ +	unsigned int tmp; + +	writel(OMAP3_MCSPI_SYSCONFIG_SOFTRESET, &ds->regs->sysconfig); +	do { +		tmp = readl(&ds->regs->sysstatus); +	} while (!(tmp & OMAP3_MCSPI_SYSSTATUS_RESETDONE)); + +	writel(OMAP3_MCSPI_SYSCONFIG_AUTOIDLE | +				 OMAP3_MCSPI_SYSCONFIG_ENAWAKEUP | +				 OMAP3_MCSPI_SYSCONFIG_SMARTIDLE, +				 &ds->regs->sysconfig); + +	writel(OMAP3_MCSPI_WAKEUPENABLE_WKEN, &ds->regs->wakeupenable); +} + +void spi_init() +{ +	/* do nothing */ +} + +struct spi_slave *spi_setup_slave(unsigned int bus, unsigned int cs, +				  unsigned int max_hz, unsigned int mode) +{ +	struct omap3_spi_slave	*ds; + +	ds = malloc(sizeof(struct omap3_spi_slave)); +	if (!ds) { +		printf("SPI error: malloc of SPI structure failed\n"); +		return NULL; +	} + +	/* +	 * OMAP3 McSPI (MultiChannel SPI) has 4 busses (modules) +	 * with different number of chip selects (CS, channels): +	 * McSPI1 has 4 CS (bus 0, cs 0 - 3) +	 * McSPI2 has 2 CS (bus 1, cs 0 - 1) +	 * McSPI3 has 2 CS (bus 2, cs 0 - 1) +	 * McSPI4 has 1 CS (bus 3, cs 0) +	 */ + +	switch (bus) { +	case 0: +		ds->regs = (struct mcspi *)OMAP3_MCSPI1_BASE; +		break; +	case 1: +		ds->regs = (struct mcspi *)OMAP3_MCSPI2_BASE; +		break; +	case 2: +		ds->regs = (struct mcspi *)OMAP3_MCSPI3_BASE; +		break; +	case 3: +		ds->regs = (struct mcspi *)OMAP3_MCSPI4_BASE; +		break; +	default: +		printf("SPI error: unsupported bus %i. \ +			Supported busses 0 - 3\n", bus); +		return NULL; +	} +	ds->slave.bus = bus; + +	if (((bus == 0) && (cs > 3)) || +			((bus == 1) && (cs > 1)) || +			((bus == 2) && (cs > 1)) || +			((bus == 3) && (cs > 0))) { +		printf("SPI error: unsupported chip select %i \ +			on bus %i\n", cs, bus); +		return NULL; +	} +	ds->slave.cs = cs; + +	if (max_hz > OMAP3_MCSPI_MAX_FREQ) { +		printf("SPI error: unsupported frequency %i Hz. \ +			Max frequency is 48 Mhz\n", max_hz); +		return NULL; +	} +	ds->freq = max_hz; + +	if (mode > SPI_MODE_3) { +		printf("SPI error: unsupported SPI mode %i\n", mode); +		return NULL; +	} +	ds->mode = mode; + +	return &ds->slave; +} + +void spi_free_slave(struct spi_slave *slave) +{ +	struct omap3_spi_slave *ds = to_omap3_spi(slave); + +	free(ds); +} + +int spi_claim_bus(struct spi_slave *slave) +{ +	struct omap3_spi_slave *ds = to_omap3_spi(slave); +	unsigned int conf, div = 0; + +	/* McSPI global module configuration */ + +	/* +	 * setup when switching from (reset default) slave mode +	 * to single-channel master mode +	 */ +	spi_reset(ds); +	conf = readl(&ds->regs->modulctrl); +	conf &= ~(OMAP3_MCSPI_MODULCTRL_STEST | OMAP3_MCSPI_MODULCTRL_MS); +	conf |= OMAP3_MCSPI_MODULCTRL_SINGLE; +	writel(conf, &ds->regs->modulctrl); + +	/* McSPI individual channel configuration */ + +	/* Calculate clock divisor. Valid range: 0x0 - 0xC ( /1 - /4096 ) */ +	if (ds->freq) { +		while (div <= 0xC && (OMAP3_MCSPI_MAX_FREQ / (1 << div)) +					 > ds->freq) +			div++; +	} else +		div = 0xC; + +	conf = readl(&ds->regs->channel[ds->slave.cs].chconf); + +	/* standard 4-wire master mode:	SCK, MOSI/out, MISO/in, nCS +	 * REVISIT: this controller could support SPI_3WIRE mode. +	 */ +	conf &= ~(OMAP3_MCSPI_CHCONF_IS|OMAP3_MCSPI_CHCONF_DPE1); +	conf |= OMAP3_MCSPI_CHCONF_DPE0; + +	/* wordlength */ +	conf &= ~OMAP3_MCSPI_CHCONF_WL_MASK; +	conf |= (WORD_LEN - 1) << 7; + +	/* set chipselect polarity; manage with FORCE */ +	if (!(ds->mode & SPI_CS_HIGH)) +		conf |= OMAP3_MCSPI_CHCONF_EPOL; /* active-low; normal */ +	else +		conf &= ~OMAP3_MCSPI_CHCONF_EPOL; + +	/* set clock divisor */ +	conf &= ~OMAP3_MCSPI_CHCONF_CLKD_MASK; +	conf |= div << 2; + +	/* set SPI mode 0..3 */ +	if (ds->mode & SPI_CPOL) +		conf |= OMAP3_MCSPI_CHCONF_POL; +	else +		conf &= ~OMAP3_MCSPI_CHCONF_POL; +	if (ds->mode & SPI_CPHA) +		conf |= OMAP3_MCSPI_CHCONF_PHA; +	else +		conf &= ~OMAP3_MCSPI_CHCONF_PHA; + +	/* Transmit & receive mode */ +	conf &= ~OMAP3_MCSPI_CHCONF_TRM_MASK; + +	writel(conf, &ds->regs->channel[ds->slave.cs].chconf); + +	return 0; +} + +void spi_release_bus(struct spi_slave *slave) +{ +	struct omap3_spi_slave *ds = to_omap3_spi(slave); + +	/* Reset the SPI hardware */ +	spi_reset(ds); +} + +int omap3_spi_write(struct spi_slave *slave, unsigned int len, const u8 *txp, +		    unsigned long flags) +{ +	struct omap3_spi_slave *ds = to_omap3_spi(slave); +	int i; +	int timeout = SPI_WAIT_TIMEOUT; +	int chconf = readl(&ds->regs->channel[ds->slave.cs].chconf); + +	if (flags & SPI_XFER_BEGIN) +		writel(OMAP3_MCSPI_CHCTRL_EN, +		       &ds->regs->channel[ds->slave.cs].chctrl); + +	chconf &= ~OMAP3_MCSPI_CHCONF_TRM_MASK; +	chconf |= OMAP3_MCSPI_CHCONF_TRM_TX_ONLY; +	chconf |= OMAP3_MCSPI_CHCONF_FORCE; +	writel(chconf, &ds->regs->channel[ds->slave.cs].chconf); + +	for (i = 0; i < len; i++) { +		/* wait till TX register is empty (TXS == 1) */ +		while (!(readl(&ds->regs->channel[ds->slave.cs].chstat) & +			 OMAP3_MCSPI_CHSTAT_TXS)) { +			if (--timeout <= 0) { +				printf("SPI TXS timed out, status=0x%08x\n", +				       readl(&ds->regs->channel[ds->slave.cs].chstat)); +				return -1; +			} +		} +		/* Write the data */ +		writel(txp[i], &ds->regs->channel[ds->slave.cs].tx); +	} + +	if (flags & SPI_XFER_END) { +		/* wait to finish of transfer */ +		while (!(readl(&ds->regs->channel[ds->slave.cs].chstat) & +			 OMAP3_MCSPI_CHSTAT_EOT)); + +		chconf &= ~OMAP3_MCSPI_CHCONF_FORCE; +		writel(chconf, &ds->regs->channel[ds->slave.cs].chconf); + +		writel(0, &ds->regs->channel[ds->slave.cs].chctrl); +	} +	return 0; +} + +int omap3_spi_read(struct spi_slave *slave, unsigned int len, u8 *rxp, +		   unsigned long flags) +{ +	struct omap3_spi_slave *ds = to_omap3_spi(slave); +	int i; +	int timeout = SPI_WAIT_TIMEOUT; +	int chconf = readl(&ds->regs->channel[ds->slave.cs].chconf); + +	if (flags & SPI_XFER_BEGIN) +		writel(OMAP3_MCSPI_CHCTRL_EN, +		       &ds->regs->channel[ds->slave.cs].chctrl); + +	chconf &= ~OMAP3_MCSPI_CHCONF_TRM_MASK; +	chconf |= OMAP3_MCSPI_CHCONF_TRM_RX_ONLY; +	chconf |= OMAP3_MCSPI_CHCONF_FORCE; +	writel(chconf, &ds->regs->channel[ds->slave.cs].chconf); + +	writel(0, &ds->regs->channel[ds->slave.cs].tx); + +	for (i = 0; i < len; i++) { +		/* Wait till RX register contains data (RXS == 1) */ +		while (!(readl(&ds->regs->channel[ds->slave.cs].chstat) & +			 OMAP3_MCSPI_CHSTAT_RXS)) { +			if (--timeout <= 0) { +				printf("SPI RXS timed out, status=0x%08x\n", +				       readl(&ds->regs->channel[ds->slave.cs].chstat)); +				return -1; +			} +		} +		/* Read the data */ +		rxp[i] = readl(&ds->regs->channel[ds->slave.cs].rx); +	} + +	if (flags & SPI_XFER_END) { +		chconf &= ~OMAP3_MCSPI_CHCONF_FORCE; +		writel(chconf, &ds->regs->channel[ds->slave.cs].chconf); + +		writel(0, &ds->regs->channel[ds->slave.cs].chctrl); +	} + +	return 0; +} + +int spi_xfer(struct spi_slave *slave, unsigned int bitlen, +	     const void *dout, void *din, unsigned long flags) +{ +	struct omap3_spi_slave *ds = to_omap3_spi(slave); +	unsigned int	len; +	const u8	*txp = dout; +	u8		*rxp = din; +	int ret = -1; + +	if (bitlen % 8) +		return -1; + +	len = bitlen / 8; + +	if (bitlen == 0) {	 /* only change CS */ +		int chconf = readl(&ds->regs->channel[ds->slave.cs].chconf); + +		if (flags & SPI_XFER_BEGIN) { +			writel(OMAP3_MCSPI_CHCTRL_EN, +			       &ds->regs->channel[ds->slave.cs].chctrl); +			chconf |= OMAP3_MCSPI_CHCONF_FORCE; +			writel(chconf, +			       &ds->regs->channel[ds->slave.cs].chconf); +		} +		if (flags & SPI_XFER_END) { +			chconf &= ~OMAP3_MCSPI_CHCONF_FORCE; +			writel(chconf, +			       &ds->regs->channel[ds->slave.cs].chconf); +			writel(0, &ds->regs->channel[ds->slave.cs].chctrl); +		} +		ret = 0; +	} else { +		if (dout != NULL) +			ret = omap3_spi_write(slave, len, txp, flags); + +		if (din != NULL) +			ret = omap3_spi_read(slave, len, rxp, flags); +	} +	return ret; +} + +int spi_cs_is_valid(unsigned int bus, unsigned int cs) +{ +	return 1; +} + +void spi_cs_activate(struct spi_slave *slave) +{ +} + +void spi_cs_deactivate(struct spi_slave *slave) +{ +} |