]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[spi] Add address-length autodetection to the SPI bit-bashing code
authorMichael Brown <mcb30@etherboot.org>
Thu, 28 May 2009 13:45:32 +0000 (14:45 +0100)
committerMichael Brown <mcb30@etherboot.org>
Thu, 28 May 2009 18:32:03 +0000 (19:32 +0100)
Several SPI chips will respond to an SPI read command with a dummy
zero bit immediately prior to the first real data bit.  This can be
used to autodetect the address length, provided that the command
length and data length are already known, and that the MISO data line
is tied high.

Tested-by: Thomas Miletich <thomas.miletich@gmail.com>
Debugged-by: Thomas Miletich <thomas.miletich@gmail.com>
src/drivers/bitbash/spi_bit.c
src/drivers/nvs/threewire.c
src/include/gpxe/spi.h
src/include/gpxe/threewire.h

index 8b5060c19497a4a1f1ed7fb8cb0077be708d8e30..8e7039362ea6b85808eedfaed46187934da581b8 100644 (file)
@@ -60,8 +60,8 @@ static void spi_bit_set_slave_select ( struct spi_bit_basher *spibit,
        struct bit_basher *basher = &spibit->basher;
 
        state ^= ( spibit->bus.mode & SPI_MODE_SSPOL );
-       DBG ( "Setting slave %d select %s\n", slave,
-             ( state ? "high" : "low" ) );
+       DBGC2 ( spibit, "SPIBIT %p setting slave %d select %s\n",
+               spibit, slave, ( state ? "high" : "low" ) );
 
        spi_bit_delay();
        write_bit ( basher, SPI_BIT_SS ( slave ), state );
@@ -96,7 +96,8 @@ static void spi_bit_transfer ( struct spi_bit_basher *spibit,
        unsigned int bit;
        unsigned int step;
 
-       DBG ( "Transferring %d bits in mode %x\n", len, bus->mode );
+       DBGC2 ( spibit, "SPIBIT %p transferring %d bits in mode %#x\n",
+               spibit, len, bus->mode );
 
        for ( step = 0 ; step < ( len * 2 ) ; step++ ) {
                /* Calculate byte offset and byte mask */
@@ -113,6 +114,8 @@ static void spi_bit_transfer ( struct spi_bit_basher *spibit,
                        if ( data_out ) {
                                byte = ( data_out + byte_offset );
                                bit = ( *byte & byte_mask );
+                               DBGCP ( spibit, "SPIBIT %p wrote bit %d\n",
+                                       spibit, ( bit ? 1 : 0 ) );
                        } else {
                                bit = 0;
                        }
@@ -123,6 +126,8 @@ static void spi_bit_transfer ( struct spi_bit_basher *spibit,
                        /* Shift data in */
                        bit = read_bit ( basher, SPI_BIT_MISO );
                        if ( data_in ) {
+                               DBGCP ( spibit, "SPIBIT %p read bit %d\n",
+                                       spibit, ( bit ? 1 : 0 ) );
                                byte = ( data_in + byte_offset );
                                *byte &= ~byte_mask;
                                *byte |= ( bit & byte_mask );
@@ -131,7 +136,7 @@ static void spi_bit_transfer ( struct spi_bit_basher *spibit,
 
                /* Toggle clock line */
                spi_bit_delay();
-               sclk = ~sclk;
+               sclk ^= 1;
                write_bit ( basher, SPI_BIT_SCLK, sclk );
        }
 }
@@ -153,7 +158,9 @@ static int spi_bit_rw ( struct spi_bus *bus, struct spi_device *device,
                        const void *data_out, void *data_in, size_t len ) {
        struct spi_bit_basher *spibit
                = container_of ( bus, struct spi_bit_basher, bus );
-       uint32_t tmp;
+       uint32_t tmp_command;
+       uint32_t tmp_address;
+       uint32_t tmp_address_detect;
 
        /* Set clock line to idle state */
        write_bit ( &spibit->basher, SPI_BIT_SCLK, 
@@ -163,17 +170,37 @@ static int spi_bit_rw ( struct spi_bus *bus, struct spi_device *device,
        spi_bit_set_slave_select ( spibit, device->slave, SELECT_SLAVE );
 
        /* Transmit command */
-       assert ( device->command_len <= ( 8 * sizeof ( tmp ) ) );
-       tmp = cpu_to_le32 ( command );
-       spi_bit_transfer ( spibit, &tmp, NULL, device->command_len,
+       assert ( device->command_len <= ( 8 * sizeof ( tmp_command ) ) );
+       tmp_command = cpu_to_le32 ( command );
+       spi_bit_transfer ( spibit, &tmp_command, NULL, device->command_len,
                           SPI_BIT_BIG_ENDIAN );
 
        /* Transmit address, if present */
        if ( address >= 0 ) {
-               assert ( device->address_len <= ( 8 * sizeof ( tmp ) ) );
-               tmp = cpu_to_le32 ( address );
-               spi_bit_transfer ( spibit, &tmp, NULL, device->address_len,
-                                  SPI_BIT_BIG_ENDIAN );
+               assert ( device->address_len <= ( 8 * sizeof ( tmp_address )));
+               tmp_address = cpu_to_le32 ( address );
+               if ( device->address_len == SPI_AUTODETECT_ADDRESS_LEN ) {
+                       /* Autodetect address length.  This relies on
+                        * the device responding with a dummy zero
+                        * data bit before the first real data bit.
+                        */
+                       DBGC ( spibit, "SPIBIT %p autodetecting device "
+                              "address length\n", spibit );
+                       assert ( address == 0 );
+                       device->address_len = 0;
+                       do {
+                               spi_bit_transfer ( spibit, &tmp_address,
+                                                  &tmp_address_detect, 1,
+                                                  SPI_BIT_BIG_ENDIAN );
+                               device->address_len++;
+                       } while ( le32_to_cpu ( tmp_address_detect ) & 1 );
+                       DBGC ( spibit, "SPIBIT %p autodetected device address "
+                              "length %d\n", spibit, device->address_len );
+               } else {
+                       spi_bit_transfer ( spibit, &tmp_address, NULL,
+                                          device->address_len,
+                                          SPI_BIT_BIG_ENDIAN );
+               }
        }
 
        /* Transmit/receive data */
index f7a20bbef9e09b3e860b6e9b83b2cfcd2369ead7..8e521389349a9e573329ce9db7796ed074f71679 100644 (file)
@@ -19,6 +19,7 @@
 FILE_LICENCE ( GPL2_OR_LATER );
 
 #include <stddef.h>
+#include <string.h>
 #include <assert.h>
 #include <unistd.h>
 #include <gpxe/threewire.h>
@@ -42,13 +43,21 @@ int threewire_read ( struct nvs_device *nvs, unsigned int address,
                     void *data, size_t len ) {
        struct spi_device *device = nvs_to_spi ( nvs );
        struct spi_bus *bus = device->bus;
+       int rc;
 
        assert ( bus->mode == SPI_MODE_THREEWIRE );
 
-       DBG ( "3wire %p reading %zd bytes at %04x\n", device, len, address );
+       DBGC ( device, "3wire %p reading %zd bytes at %04x\n",
+              device, len, address );
+
+       if ( ( rc = bus->rw ( bus, device, THREEWIRE_READ, address,
+                             NULL, data, len ) ) != 0 ) {
+               DBGC ( device, "3wire %p could not read: %s\n",
+                      device, strerror ( rc ) );
+               return rc;
+       }
 
-       return bus->rw ( bus, device, THREEWIRE_READ, address,
-                        NULL, data, len );
+       return 0;
 }
 
 /**
@@ -68,17 +77,24 @@ int threewire_write ( struct nvs_device *nvs, unsigned int address,
 
        assert ( bus->mode == SPI_MODE_THREEWIRE );
 
-       DBG ( "3wire %p writing %zd bytes at %04x\n", device, len, address );
+       DBGC ( device, "3wire %p writing %zd bytes at %04x\n",
+              device, len, address );
 
        /* Enable device for writing */
        if ( ( rc = bus->rw ( bus, device, THREEWIRE_EWEN,
-                             THREEWIRE_EWEN_ADDRESS, NULL, NULL, 0 ) ) != 0 )
+                             THREEWIRE_EWEN_ADDRESS, NULL, NULL, 0 ) ) != 0 ){
+               DBGC ( device, "3wire %p could not enable writing: %s\n",
+                      device, strerror ( rc ) );
                return rc;
+       }
 
        /* Write data */
        if ( ( rc = bus->rw ( bus, device, THREEWIRE_WRITE, address,
-                             data, NULL, len ) ) != 0 )
+                             data, NULL, len ) ) != 0 ) {
+               DBGC ( device, "3wire %p could not write: %s\n",
+                      device, strerror ( rc ) );
                return rc;
+       }
 
        /* Our model of an SPI bus doesn't provide a mechanism for
         * "assert CS, wait for MISO to become high, so just wait for
@@ -88,3 +104,28 @@ int threewire_write ( struct nvs_device *nvs, unsigned int address,
 
        return 0;
 }
+
+/**
+ * Autodetect device address length
+ *
+ * @v device           SPI device
+ * @ret rc             Return status code
+ */
+int threewire_detect_address_len ( struct spi_device *device ) {
+       struct nvs_device *nvs = &device->nvs;
+       int rc;
+
+       DBGC ( device, "3wire %p autodetecting address length\n", device );
+
+       device->address_len = SPI_AUTODETECT_ADDRESS_LEN;
+       if ( ( rc = threewire_read ( nvs, 0, NULL,
+                                    ( 1 << nvs->word_len_log2 ) ) ) != 0 ) {
+               DBGC ( device, "3wire %p could not autodetect address "
+                      "length: %s\n", device, strerror ( rc ) );
+               return rc;
+       }
+
+       DBGC ( device, "3wire %p autodetected address length %d\n",
+              device, device->address_len );
+       return 0;
+}
index ebfc3226b73ecb8580f12966b53a07447f2cc129..8e4a6763b957a35fefe86e96e7fed33fdbdeba84 100644 (file)
@@ -104,6 +104,14 @@ struct spi_device {
        unsigned int munge_address : 1;
 };
 
+/**
+ * SPI magic autodetection address length
+ *
+ * Set @c spi_device::address_len to @c SPI_AUTODETECT_ADDRESS_LEN if
+ * the address length should be autodetected.
+ */
+#define SPI_AUTODETECT_ADDRESS_LEN 0
+
 static inline __attribute__ (( always_inline )) struct spi_device *
 nvs_to_spi ( struct nvs_device *nvs ) {
        return container_of ( nvs, struct spi_device, nvs );
index 4dc755c2ebaa0fb1dcda933b7530372ae97f22f9..2db6726038ea60f4cabe7e1e51e64238db9b0e75 100644 (file)
@@ -45,6 +45,7 @@ extern int threewire_read ( struct nvs_device *nvs, unsigned int address,
                            void *data, size_t len );
 extern int threewire_write ( struct nvs_device *nvs, unsigned int address,
                             const void *data, size_t len );
+extern int threewire_detect_address_len ( struct spi_device *device );
 
 /**
  * @defgroup tdevs Three-wire device types