diff options
author | Michael Brown | 2006-06-14 02:22:50 +0200 |
---|---|---|
committer | Michael Brown | 2006-06-14 02:22:50 +0200 |
commit | 3b51c719d305d4bfe8e99003f28d7cd48e0d9f4e (patch) | |
tree | c366d04ad10c78c82309d1f1bcff601caf13f277 /src/drivers/bitbash | |
parent | Move per-transition delays from generic bit-bashing layer to i2c layer (diff) | |
download | ipxe-3b51c719d305d4bfe8e99003f28d7cd48e0d9f4e.tar.gz ipxe-3b51c719d305d4bfe8e99003f28d7cd48e0d9f4e.tar.xz ipxe-3b51c719d305d4bfe8e99003f28d7cd48e0d9f4e.zip |
Generalise three-wire interface to generic SPI interface.
Update rtl8139 driver to instantiate an SPI interface with a three-wire
device attached.
Diffstat (limited to 'src/drivers/bitbash')
-rw-r--r-- | src/drivers/bitbash/spi_bit.c | 165 |
1 files changed, 165 insertions, 0 deletions
diff --git a/src/drivers/bitbash/spi_bit.c b/src/drivers/bitbash/spi_bit.c new file mode 100644 index 00000000..20ad412c --- /dev/null +++ b/src/drivers/bitbash/spi_bit.c @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>. + * + * 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 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <stddef.h> +#include <stdint.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#include <timer.h> +#include <gpxe/bitbash.h> +#include <gpxe/spi.h> + +/** @file + * + * SPI bit-bashing interface + * + */ + +/** Delay between SCLK changes and around SS changes */ +static void spi_delay ( void ) { + udelay ( SPI_UDELAY ); +} + +/** + * Select/deselect slave + * + * @v spi SPI bit-bashing interface + * @v slave Slave number + * @v state Slave select state + * + * @c state must be set to zero to select the specified slave, or to + * @c SPI_MODE_SSPOL to deselect the slave. + */ +static void spi_bit_set_slave_select ( struct spi_bit_basher *spibit, + unsigned int slave, + unsigned int state ) { + struct bit_basher *basher = &spibit->basher; + + state ^= ( spibit->spi.mode & SPI_MODE_SSPOL ); + DBG ( "Setting slave %d select %s\n", slave, + ( state ? "high" : "low" ) ); + + spi_delay(); + write_bit ( basher, SPI_BIT_SS ( slave ), state ); + spi_delay(); +} + +/** + * Select slave + * + * @v spi SPI interface + * @v slave Slave number + */ +static void spi_bit_select_slave ( struct spi_interface *spi, + unsigned int slave ) { + struct spi_bit_basher *spibit + = container_of ( spi, struct spi_bit_basher, spi ); + + spibit->slave = slave; + spi_bit_set_slave_select ( spibit, slave, 0 ); +} + +/** + * Deselect slave + * + * @v spi SPI interface + */ +static void spi_bit_deselect_slave ( struct spi_interface *spi ) { + struct spi_bit_basher *spibit + = container_of ( spi, struct spi_bit_basher, spi ); + + spi_bit_set_slave_select ( spibit, spibit->slave, SPI_MODE_SSPOL ); +} + +/** + * Transfer bits over SPI bit-bashing interface + * + * @v spi SPI interface + * @v data_out TX data buffer (or NULL) + * @v data_in RX data buffer (or NULL) + * @v len Length of transfer (in @b bits) + * + * This issues @c len clock cycles on the SPI bus, shifting out data + * from the @c data_out buffer to the MOSI line and shifting in data + * from the MISO line to the @c data_in buffer. If @c data_out is + * NULL, then the data sent will be all zeroes. If @c data_in is + * NULL, then the incoming data will be discarded. + */ +static void spi_bit_transfer ( struct spi_interface *spi, const void *data_out, + void *data_in, unsigned int len ) { + struct spi_bit_basher *spibit + = container_of ( spi, struct spi_bit_basher, spi ); + struct bit_basher *basher = &spibit->basher; + unsigned int sclk = ( ( spi->mode & SPI_MODE_CPOL ) ? 1 : 0 ); + unsigned int cpha = ( ( spi->mode & SPI_MODE_CPHA ) ? 1 : 0 ); + unsigned int offset; + unsigned int mask; + unsigned int bit; + int step; + + DBG ( "Transferring %d bits in mode %x\n", len, spi->mode ); + + for ( step = ( ( len * 2 ) - 1 ) ; step >= 0 ; step-- ) { + /* Calculate byte offset within data and bit mask */ + offset = ( step / 16 ); + mask = ( 1 << ( ( step % 16 ) / 2 ) ); + + /* Shift data in or out */ + if ( sclk == cpha ) { + const uint8_t *byte; + + /* Shift data out */ + if ( data_out ) { + byte = ( data_out + offset ); + bit = ( *byte & mask ); + } else { + bit = 0; + } + write_bit ( basher, SPI_BIT_MOSI, bit ); + } else { + uint8_t *byte; + + /* Shift data in */ + bit = read_bit ( basher, SPI_BIT_MISO ); + if ( data_in ) { + byte = ( data_in + offset ); + *byte &= ~mask; + *byte |= ( bit & mask ); + } + } + + /* Toggle clock line */ + spi_delay(); + sclk = ~sclk; + write_bit ( basher, SPI_BIT_SCLK, sclk ); + } +} + +/** + * Initialise SPI bit-bashing interface + * + * @v spibit SPI bit-bashing interface + */ +void init_spi_bit_basher ( struct spi_bit_basher *spibit ) { + assert ( &spibit->basher.read != NULL ); + assert ( &spibit->basher.write != NULL ); + spibit->spi.select_slave = spi_bit_select_slave; + spibit->spi.deselect_slave = spi_bit_deselect_slave; + spibit->spi.transfer = spi_bit_transfer; +} |