diff options
Diffstat (limited to 'gpxe/src/drivers')
147 files changed, 41446 insertions, 4706 deletions
diff --git a/gpxe/src/drivers/bitbash/bitbash.c b/gpxe/src/drivers/bitbash/bitbash.c index c6f93520..3e558d5c 100644 --- a/gpxe/src/drivers/bitbash/bitbash.c +++ b/gpxe/src/drivers/bitbash/bitbash.c @@ -16,6 +16,8 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +FILE_LICENCE ( GPL2_OR_LATER ); + #include <gpxe/bitbash.h> /** @file diff --git a/gpxe/src/drivers/bitbash/i2c_bit.c b/gpxe/src/drivers/bitbash/i2c_bit.c index b85057af..13197270 100644 --- a/gpxe/src/drivers/bitbash/i2c_bit.c +++ b/gpxe/src/drivers/bitbash/i2c_bit.c @@ -16,6 +16,8 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +FILE_LICENCE ( GPL2_OR_LATER ); + #include <stddef.h> #include <stdint.h> #include <errno.h> diff --git a/gpxe/src/drivers/bitbash/spi_bit.c b/gpxe/src/drivers/bitbash/spi_bit.c index ef87b5a2..8e703936 100644 --- a/gpxe/src/drivers/bitbash/spi_bit.c +++ b/gpxe/src/drivers/bitbash/spi_bit.c @@ -16,6 +16,8 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +FILE_LICENCE ( GPL2_OR_LATER ); + #include <stddef.h> #include <stdint.h> #include <string.h> @@ -58,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 ); @@ -94,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 */ @@ -111,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; } @@ -121,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 ); @@ -129,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 ); } } @@ -151,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, @@ -161,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 */ diff --git a/gpxe/src/drivers/block/ata.c b/gpxe/src/drivers/block/ata.c index c21d2f65..dc851d72 100644 --- a/gpxe/src/drivers/block/ata.c +++ b/gpxe/src/drivers/block/ata.c @@ -16,11 +16,15 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +FILE_LICENCE ( GPL2_OR_LATER ); + #include <stddef.h> #include <string.h> #include <assert.h> +#include <errno.h> #include <byteswap.h> #include <gpxe/blockdev.h> +#include <gpxe/process.h> #include <gpxe/ata.h> /** @file @@ -43,13 +47,34 @@ block_to_ata ( struct block_device *blockdev ) { */ static inline __attribute__ (( always_inline )) int ata_command ( struct ata_device *ata, struct ata_command *command ) { + int rc; + DBG ( "ATA cmd %02x dev %02x LBA%s %llx count %04x\n", command->cb.cmd_stat, command->cb.device, ( command->cb.lba48 ? "48" : "" ), ( unsigned long long ) command->cb.lba.native, command->cb.count.native ); - return ata->command ( ata, command ); + /* Flag command as in-progress */ + command->rc = -EINPROGRESS; + + /* Issue ATA command */ + if ( ( rc = ata->command ( ata, command ) ) != 0 ) { + /* Something went wrong with the issuing mechanism */ + DBG ( "ATA could not issue command: %s\n", strerror ( rc ) ); + return rc; + } + + /* Wait for command to complete */ + while ( command->rc == -EINPROGRESS ) + step(); + if ( ( rc = command->rc ) != 0 ) { + /* Something went wrong with the command execution */ + DBG ( "ATA command failed: %s\n", strerror ( rc ) ); + return rc; + } + + return 0; } /** diff --git a/gpxe/src/drivers/block/ramdisk.c b/gpxe/src/drivers/block/ramdisk.c index 50911994..4e6f1bca 100644 --- a/gpxe/src/drivers/block/ramdisk.c +++ b/gpxe/src/drivers/block/ramdisk.c @@ -16,6 +16,8 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +FILE_LICENCE ( GPL2_OR_LATER ); + #include <gpxe/blockdev.h> #include <gpxe/ramdisk.h> diff --git a/gpxe/src/drivers/block/scsi.c b/gpxe/src/drivers/block/scsi.c index b22bd20f..a51b3af6 100644 --- a/gpxe/src/drivers/block/scsi.c +++ b/gpxe/src/drivers/block/scsi.c @@ -16,11 +16,15 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +FILE_LICENCE ( GPL2_OR_LATER ); + #include <stddef.h> +#include <stdlib.h> #include <string.h> #include <byteswap.h> #include <errno.h> #include <gpxe/blockdev.h> +#include <gpxe/process.h> #include <gpxe/scsi.h> /** @file @@ -42,6 +46,18 @@ block_to_scsi ( struct block_device *blockdev ) { } /** + * Handle SCSI command with no backing device + * + * @v scsi SCSI device + * @v command SCSI command + * @ret rc Return status code + */ +int scsi_detached_command ( struct scsi_device *scsi __unused, + struct scsi_command *command __unused ) { + return -ENODEV; +} + +/** * Issue SCSI command * * @v scsi SCSI device @@ -52,24 +68,38 @@ static int scsi_command ( struct scsi_device *scsi, struct scsi_command *command ) { int rc; + DBGC2 ( scsi, "SCSI %p " SCSI_CDB_FORMAT "\n", + scsi, SCSI_CDB_DATA ( command->cdb ) ); + /* Clear sense response code before issuing command */ command->sense_response = 0; + /* Flag command as in-progress */ + command->rc = -EINPROGRESS; + /* Issue SCSI command */ if ( ( rc = scsi->command ( scsi, command ) ) != 0 ) { - /* Something went wrong with the issuing mechanism, - * (rather than with the command itself) - */ - DBG ( "SCSI %p " SCSI_CDB_FORMAT " err %s\n", - scsi, SCSI_CDB_DATA ( command->cdb ), strerror ( rc ) ); + /* Something went wrong with the issuing mechanism */ + DBGC ( scsi, "SCSI %p " SCSI_CDB_FORMAT " err %s\n", + scsi, SCSI_CDB_DATA ( command->cdb ), strerror ( rc ) ); + return rc; + } + + /* Wait for command to complete */ + while ( command->rc == -EINPROGRESS ) + step(); + if ( ( rc = command->rc ) != 0 ) { + /* Something went wrong with the command execution */ + DBGC ( scsi, "SCSI %p " SCSI_CDB_FORMAT " err %s\n", + scsi, SCSI_CDB_DATA ( command->cdb ), strerror ( rc ) ); return rc; } /* Check for SCSI errors */ if ( command->status != 0 ) { - DBG ( "SCSI %p " SCSI_CDB_FORMAT " status %02x sense %02x\n", - scsi, SCSI_CDB_DATA ( command->cdb ), - command->status, command->sense_response ); + DBGC ( scsi, "SCSI %p " SCSI_CDB_FORMAT " status %02x sense " + "%02x\n", scsi, SCSI_CDB_DATA ( command->cdb ), + command->status, command->sense_response ); return -EIO; } @@ -271,12 +301,17 @@ int init_scsidev ( struct scsi_device *scsi ) { for ( i = 0 ; i < SCSI_MAX_DUMMY_READ_CAP ; i++ ) { if ( ( rc = scsi_read_capacity_10 ( &scsi->blockdev ) ) == 0 ) break; + DBGC ( scsi, "SCSI %p ignoring start-of-day error (#%d)\n", + scsi, ( i + 1 ) ); } /* Try READ CAPACITY (10), which is a mandatory command, first. */ scsi->blockdev.op = &scsi_operations_10; - if ( ( rc = scsi_read_capacity_10 ( &scsi->blockdev ) ) != 0 ) + if ( ( rc = scsi_read_capacity_10 ( &scsi->blockdev ) ) != 0 ) { + DBGC ( scsi, "SCSI %p could not READ CAPACITY (10): %s\n", + scsi, strerror ( rc ) ); return rc; + } /* If capacity range was exceeded (i.e. capacity.lba was * 0xffffffff, meaning that blockdev->blocks is now zero), use @@ -285,8 +320,46 @@ int init_scsidev ( struct scsi_device *scsi ) { */ if ( scsi->blockdev.blocks == 0 ) { scsi->blockdev.op = &scsi_operations_16; - if ( ( rc = scsi_read_capacity_16 ( &scsi->blockdev ) ) != 0 ) + if ( ( rc = scsi_read_capacity_16 ( &scsi->blockdev ) ) != 0 ){ + DBGC ( scsi, "SCSI %p could not READ CAPACITY (16): " + "%s\n", scsi, strerror ( rc ) ); return rc; + } + } + + DBGC ( scsi, "SCSI %p using READ/WRITE (%d) commands\n", scsi, + ( ( scsi->blockdev.op == &scsi_operations_10 ) ? 10 : 16 ) ); + DBGC ( scsi, "SCSI %p capacity is %ld MB (%#llx blocks)\n", scsi, + ( ( unsigned long ) ( scsi->blockdev.blocks >> 11 ) ), + scsi->blockdev.blocks ); + + return 0; +} + +/** + * Parse SCSI LUN + * + * @v lun_string LUN string representation + * @v lun LUN to fill in + * @ret rc Return status code + */ +int scsi_parse_lun ( const char *lun_string, struct scsi_lun *lun ) { + char *p; + int i; + + memset ( lun, 0, sizeof ( *lun ) ); + if ( lun_string ) { + p = ( char * ) lun_string; + for ( i = 0 ; i < 4 ; i++ ) { + lun->u16[i] = htons ( strtoul ( p, &p, 16 ) ); + if ( *p == '\0' ) + break; + if ( *p != '-' ) + return -EINVAL; + p++; + } + if ( *p ) + return -EINVAL; } return 0; diff --git a/gpxe/src/drivers/block/srp.c b/gpxe/src/drivers/block/srp.c new file mode 100644 index 00000000..1d0799ab --- /dev/null +++ b/gpxe/src/drivers/block/srp.c @@ -0,0 +1,523 @@ +/* + * Copyright (C) 2009 Fen Systems Ltd <mbrown@fensystems.co.uk>. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +FILE_LICENCE ( BSD2 ); + +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <gpxe/scsi.h> +#include <gpxe/xfer.h> +#include <gpxe/features.h> +#include <gpxe/ib_srp.h> +#include <gpxe/srp.h> + +/** + * @file + * + * SCSI RDMA Protocol + * + */ + +FEATURE ( FEATURE_PROTOCOL, "SRP", DHCP_EB_FEATURE_SRP, 1 ); + +/** Tag to be used for next SRP IU */ +static unsigned int srp_tag = 0; + +static void srp_login ( struct srp_device *srp ); +static void srp_cmd ( struct srp_device *srp ); + +/** + * Mark SRP SCSI command as complete + * + * @v srp SRP device + * @v rc Status code + */ +static void srp_scsi_done ( struct srp_device *srp, int rc ) { + if ( srp->command ) + srp->command->rc = rc; + srp->command = NULL; +} + +/** + * Handle SRP session failure + * + * @v srp SRP device + * @v rc Reason for failure + */ +static void srp_fail ( struct srp_device *srp, int rc ) { + + /* Close underlying socket */ + xfer_close ( &srp->socket, rc ); + + /* Clear session state */ + srp->state = 0; + + /* If we have reached the retry limit, report the failure */ + if ( srp->retry_count >= SRP_MAX_RETRIES ) { + srp_scsi_done ( srp, rc ); + return; + } + + /* Otherwise, increment the retry count and try to reopen the + * connection + */ + srp->retry_count++; + srp_login ( srp ); +} + +/** + * Initiate SRP login + * + * @v srp SRP device + */ +static void srp_login ( struct srp_device *srp ) { + struct io_buffer *iobuf; + struct srp_login_req *login_req; + int rc; + + assert ( ! ( srp->state & SRP_STATE_SOCKET_OPEN ) ); + + /* Open underlying socket */ + if ( ( rc = srp->transport->connect ( srp ) ) != 0 ) { + DBGC ( srp, "SRP %p could not open socket: %s\n", + srp, strerror ( rc ) ); + goto err; + } + srp->state |= SRP_STATE_SOCKET_OPEN; + + /* Allocate I/O buffer */ + iobuf = xfer_alloc_iob ( &srp->socket, sizeof ( *login_req ) ); + if ( ! iobuf ) { + rc = -ENOMEM; + goto err; + } + + /* Construct login request IU */ + login_req = iob_put ( iobuf, sizeof ( *login_req ) ); + memset ( login_req, 0, sizeof ( *login_req ) ); + login_req->type = SRP_LOGIN_REQ; + login_req->tag.dwords[1] = htonl ( ++srp_tag ); + login_req->max_i_t_iu_len = htonl ( SRP_MAX_I_T_IU_LEN ); + login_req->required_buffer_formats = SRP_LOGIN_REQ_FMT_DDBD; + memcpy ( &login_req->port_ids, &srp->port_ids, + sizeof ( login_req->port_ids ) ); + + DBGC2 ( srp, "SRP %p TX login request tag %08x%08x\n", + srp, ntohl ( login_req->tag.dwords[0] ), + ntohl ( login_req->tag.dwords[1] ) ); + DBGC2_HDA ( srp, 0, iobuf->data, iob_len ( iobuf ) ); + + /* Send login request IU */ + if ( ( rc = xfer_deliver_iob ( &srp->socket, iobuf ) ) != 0 ) { + DBGC ( srp, "SRP %p could not send login request: %s\n", + srp, strerror ( rc ) ); + goto err; + } + + return; + + err: + srp_fail ( srp, rc ); +} + +/** + * Handle SRP login response + * + * @v srp SRP device + * @v iobuf I/O buffer + * @ret rc Return status code + */ +static int srp_login_rsp ( struct srp_device *srp, struct io_buffer *iobuf ) { + struct srp_login_rsp *login_rsp = iobuf->data; + int rc; + + DBGC2 ( srp, "SRP %p RX login response tag %08x%08x\n", + srp, ntohl ( login_rsp->tag.dwords[0] ), + ntohl ( login_rsp->tag.dwords[1] ) ); + + /* Sanity check */ + if ( iob_len ( iobuf ) < sizeof ( *login_rsp ) ) { + DBGC ( srp, "SRP %p RX login response too short (%zd bytes)\n", + srp, iob_len ( iobuf ) ); + rc = -EINVAL; + goto out; + } + + DBGC ( srp, "SRP %p logged in\n", srp ); + + /* Mark as logged in */ + srp->state |= SRP_STATE_LOGGED_IN; + + /* Reset error counter */ + srp->retry_count = 0; + + /* Issue pending command */ + srp_cmd ( srp ); + + rc = 0; + out: + free_iob ( iobuf ); + return rc; +} + +/** + * Handle SRP login rejection + * + * @v srp SRP device + * @v iobuf I/O buffer + * @ret rc Return status code + */ +static int srp_login_rej ( struct srp_device *srp, struct io_buffer *iobuf ) { + struct srp_login_rej *login_rej = iobuf->data; + int rc; + + DBGC2 ( srp, "SRP %p RX login rejection tag %08x%08x\n", + srp, ntohl ( login_rej->tag.dwords[0] ), + ntohl ( login_rej->tag.dwords[1] ) ); + + /* Sanity check */ + if ( iob_len ( iobuf ) < sizeof ( *login_rej ) ) { + DBGC ( srp, "SRP %p RX login rejection too short (%zd " + "bytes)\n", srp, iob_len ( iobuf ) ); + rc = -EINVAL; + goto out; + } + + /* Login rejection always indicates an error */ + DBGC ( srp, "SRP %p login rejected (reason %08x)\n", + srp, ntohl ( login_rej->reason ) ); + rc = -EPERM; + + out: + free_iob ( iobuf ); + return rc; +} + +/** + * Transmit SRP SCSI command + * + * @v srp SRP device + */ +static void srp_cmd ( struct srp_device *srp ) { + struct io_buffer *iobuf; + struct srp_cmd *cmd; + struct srp_memory_descriptor *data_out; + struct srp_memory_descriptor *data_in; + int rc; + + assert ( srp->state & SRP_STATE_LOGGED_IN ); + + /* Allocate I/O buffer */ + iobuf = xfer_alloc_iob ( &srp->socket, SRP_MAX_I_T_IU_LEN ); + if ( ! iobuf ) { + rc = -ENOMEM; + goto err; + } + + /* Construct base portion */ + cmd = iob_put ( iobuf, sizeof ( *cmd ) ); + memset ( cmd, 0, sizeof ( *cmd ) ); + cmd->type = SRP_CMD; + cmd->tag.dwords[1] = htonl ( ++srp_tag ); + cmd->lun = srp->lun; + memcpy ( &cmd->cdb, &srp->command->cdb, sizeof ( cmd->cdb ) ); + + /* Construct data-out descriptor, if present */ + if ( srp->command->data_out ) { + cmd->data_buffer_formats |= SRP_CMD_DO_FMT_DIRECT; + data_out = iob_put ( iobuf, sizeof ( *data_out ) ); + data_out->address = + cpu_to_be64 ( user_to_phys ( srp->command->data_out, 0 ) ); + data_out->handle = ntohl ( srp->memory_handle ); + data_out->len = ntohl ( srp->command->data_out_len ); + } + + /* Construct data-in descriptor, if present */ + if ( srp->command->data_in ) { + cmd->data_buffer_formats |= SRP_CMD_DI_FMT_DIRECT; + data_in = iob_put ( iobuf, sizeof ( *data_in ) ); + data_in->address = + cpu_to_be64 ( user_to_phys ( srp->command->data_in, 0 ) ); + data_in->handle = ntohl ( srp->memory_handle ); + data_in->len = ntohl ( srp->command->data_in_len ); + } + + DBGC2 ( srp, "SRP %p TX SCSI command tag %08x%08x\n", srp, + ntohl ( cmd->tag.dwords[0] ), ntohl ( cmd->tag.dwords[1] ) ); + DBGC2_HDA ( srp, 0, iobuf->data, iob_len ( iobuf ) ); + + /* Send IU */ + if ( ( rc = xfer_deliver_iob ( &srp->socket, iobuf ) ) != 0 ) { + DBGC ( srp, "SRP %p could not send command: %s\n", + srp, strerror ( rc ) ); + goto err; + } + + return; + + err: + srp_fail ( srp, rc ); +} + +/** + * Handle SRP SCSI response + * + * @v srp SRP device + * @v iobuf I/O buffer + * @ret rc Returns status code + */ +static int srp_rsp ( struct srp_device *srp, struct io_buffer *iobuf ) { + struct srp_rsp *rsp = iobuf->data; + int rc; + + DBGC2 ( srp, "SRP %p RX SCSI response tag %08x%08x\n", srp, + ntohl ( rsp->tag.dwords[0] ), ntohl ( rsp->tag.dwords[1] ) ); + + /* Sanity check */ + if ( iob_len ( iobuf ) < sizeof ( *rsp ) ) { + DBGC ( srp, "SRP %p RX SCSI response too short (%zd bytes)\n", + srp, iob_len ( iobuf ) ); + rc = -EINVAL; + goto out; + } + + /* Report SCSI errors */ + if ( rsp->status != 0 ) { + DBGC ( srp, "SRP %p response status %02x\n", + srp, rsp->status ); + if ( srp_rsp_sense_data ( rsp ) ) { + DBGC ( srp, "SRP %p sense data:\n", srp ); + DBGC_HDA ( srp, 0, srp_rsp_sense_data ( rsp ), + srp_rsp_sense_data_len ( rsp ) ); + } + } + if ( rsp->valid & ( SRP_RSP_VALID_DOUNDER | SRP_RSP_VALID_DOOVER ) ) { + DBGC ( srp, "SRP %p response data-out %srun by %#x bytes\n", + srp, ( ( rsp->valid & SRP_RSP_VALID_DOUNDER ) + ? "under" : "over" ), + ntohl ( rsp->data_out_residual_count ) ); + } + if ( rsp->valid & ( SRP_RSP_VALID_DIUNDER | SRP_RSP_VALID_DIOVER ) ) { + DBGC ( srp, "SRP %p response data-in %srun by %#x bytes\n", + srp, ( ( rsp->valid & SRP_RSP_VALID_DIUNDER ) + ? "under" : "over" ), + ntohl ( rsp->data_in_residual_count ) ); + } + srp->command->status = rsp->status; + + /* Mark SCSI command as complete */ + srp_scsi_done ( srp, 0 ); + + rc = 0; + out: + free_iob ( iobuf ); + return rc; +} + +/** + * Handle SRP unrecognised response + * + * @v srp SRP device + * @v iobuf I/O buffer + * @ret rc Returns status code + */ +static int srp_unrecognised ( struct srp_device *srp, + struct io_buffer *iobuf ) { + struct srp_common *common = iobuf->data; + + DBGC ( srp, "SRP %p RX unrecognised IU tag %08x%08x type %02x\n", + srp, ntohl ( common->tag.dwords[0] ), + ntohl ( common->tag.dwords[1] ), common->type ); + + free_iob ( iobuf ); + return -ENOTSUP; +} + +/** + * Receive data from underlying socket + * + * @v xfer Data transfer interface + * @v iobuf Datagram I/O buffer + * @v meta Data transfer metadata + * @ret rc Return status code + */ +static int srp_xfer_deliver_iob ( struct xfer_interface *xfer, + struct io_buffer *iobuf, + struct xfer_metadata *meta __unused ) { + struct srp_device *srp = + container_of ( xfer, struct srp_device, socket ); + struct srp_common *common = iobuf->data; + int ( * type ) ( struct srp_device *srp, struct io_buffer *iobuf ); + int rc; + + /* Determine IU type */ + switch ( common->type ) { + case SRP_LOGIN_RSP: + type = srp_login_rsp; + break; + case SRP_LOGIN_REJ: + type = srp_login_rej; + break; + case SRP_RSP: + type = srp_rsp; + break; + default: + type = srp_unrecognised; + break; + } + + /* Handle IU */ + if ( ( rc = type ( srp, iobuf ) ) != 0 ) + goto err; + + return 0; + + err: + srp_fail ( srp, rc ); + return rc; +} + +/** + * Underlying socket closed + * + * @v xfer Data transfer interface + * @v rc Reason for close + */ +static void srp_xfer_close ( struct xfer_interface *xfer, int rc ) { + struct srp_device *srp = + container_of ( xfer, struct srp_device, socket ); + + DBGC ( srp, "SRP %p socket closed: %s\n", srp, strerror ( rc ) ); + + srp_fail ( srp, rc ); +} + +/** SRP data transfer interface operations */ +static struct xfer_interface_operations srp_xfer_operations = { + .close = srp_xfer_close, + .vredirect = ignore_xfer_vredirect, + .window = unlimited_xfer_window, + .alloc_iob = default_xfer_alloc_iob, + .deliver_iob = srp_xfer_deliver_iob, + .deliver_raw = xfer_deliver_as_iob, +}; + +/** + * Issue SCSI command via SRP + * + * @v scsi SCSI device + * @v command SCSI command + * @ret rc Return status code + */ +static int srp_command ( struct scsi_device *scsi, + struct scsi_command *command ) { + struct srp_device *srp = + container_of ( scsi->backend, struct srp_device, refcnt ); + + /* Store SCSI command */ + if ( srp->command ) { + DBGC ( srp, "SRP %p cannot handle concurrent SCSI commands\n", + srp ); + return -EBUSY; + } + srp->command = command; + + /* Log in or issue command as appropriate */ + if ( ! ( srp->state & SRP_STATE_SOCKET_OPEN ) ) { + srp_login ( srp ); + } else if ( srp->state & SRP_STATE_LOGGED_IN ) { + srp_cmd ( srp ); + } else { + /* Still waiting for login; do nothing */ + } + + return 0; +} + +/** + * Attach SRP device + * + * @v scsi SCSI device + * @v root_path Root path + */ +int srp_attach ( struct scsi_device *scsi, const char *root_path ) { + struct srp_transport_type *transport; + struct srp_device *srp; + int rc; + + /* Hard-code an IB SRP back-end for now */ + transport = &ib_srp_transport; + + /* Allocate and initialise structure */ + srp = zalloc ( sizeof ( *srp ) + transport->priv_len ); + if ( ! srp ) { + rc = -ENOMEM; + goto err_alloc; + } + xfer_init ( &srp->socket, &srp_xfer_operations, &srp->refcnt ); + srp->transport = transport; + DBGC ( srp, "SRP %p using %s\n", srp, root_path ); + + /* Parse root path */ + if ( ( rc = transport->parse_root_path ( srp, root_path ) ) != 0 ) { + DBGC ( srp, "SRP %p could not parse root path: %s\n", + srp, strerror ( rc ) ); + goto err_parse_root_path; + } + + /* Attach parent interface, mortalise self, and return */ + scsi->backend = ref_get ( &srp->refcnt ); + scsi->command = srp_command; + ref_put ( &srp->refcnt ); + return 0; + + err_parse_root_path: + ref_put ( &srp->refcnt ); + err_alloc: + return rc; +} + +/** + * Detach SRP device + * + * @v scsi SCSI device + */ +void srp_detach ( struct scsi_device *scsi ) { + struct srp_device *srp = + container_of ( scsi->backend, struct srp_device, refcnt ); + + /* Close socket */ + xfer_nullify ( &srp->socket ); + xfer_close ( &srp->socket, 0 ); + scsi->command = scsi_detached_command; + ref_put ( scsi->backend ); + scsi->backend = NULL; +} diff --git a/gpxe/src/drivers/bus/eisa.c b/gpxe/src/drivers/bus/eisa.c index d9e42359..b533364b 100644 --- a/gpxe/src/drivers/bus/eisa.c +++ b/gpxe/src/drivers/bus/eisa.c @@ -7,10 +7,7 @@ #include <unistd.h> #include <gpxe/eisa.h> -static struct eisa_driver eisa_drivers[0] - __table_start ( struct eisa_driver, eisa_drivers ); -static struct eisa_driver eisa_drivers_end[0] - __table_end ( struct eisa_driver, eisa_drivers ); +FILE_LICENCE ( GPL2_OR_LATER ); static void eisabus_remove ( struct root_device *rootdev ); @@ -57,7 +54,7 @@ static int eisa_probe ( struct eisa_device *eisa ) { eisa->slot, eisa->vendor_id, eisa->prod_id, isa_id_string ( eisa->vendor_id, eisa->prod_id ), eisa->ioaddr ); - for ( driver = eisa_drivers; driver < eisa_drivers_end; driver++ ) { + for_each_table_entry ( driver, EISA_DRIVERS ) { for ( i = 0 ; i < driver->id_count ; i++ ) { id = &driver->ids[i]; if ( id->vendor_id != eisa->vendor_id ) diff --git a/gpxe/src/drivers/bus/isa.c b/gpxe/src/drivers/bus/isa.c index fa5def54..f458826d 100644 --- a/gpxe/src/drivers/bus/isa.c +++ b/gpxe/src/drivers/bus/isa.c @@ -6,6 +6,8 @@ #include <gpxe/io.h> #include <gpxe/isa.h> +FILE_LICENCE ( GPL2_OR_LATER ); + /* * isa.c implements a "classical" port-scanning method of ISA device * detection. The driver must provide a list of probe addresses @@ -48,11 +50,6 @@ static isa_probe_addr_t isa_extra_probe_addrs[] = { isa_extra_probe_addrs[ (ioidx) + ISA_EXTRA_PROBE_ADDR_COUNT ] : \ (driver)->probe_addrs[(ioidx)] ) -static struct isa_driver isa_drivers[0] - __table_start ( struct isa_driver, isa_drivers ); -static struct isa_driver isa_drivers_end[0] - __table_end ( struct isa_driver, isa_drivers ); - static void isabus_remove ( struct root_device *rootdev ); /** @@ -100,7 +97,7 @@ static int isabus_probe ( struct root_device *rootdev ) { int ioidx; int rc; - for ( driver = isa_drivers ; driver < isa_drivers_end ; driver++ ) { + for_each_table_entry ( driver, ISA_DRIVERS ) { for ( ioidx = ISA_IOIDX_MIN ( driver ) ; ioidx <= ISA_IOIDX_MAX ( driver ) ; ioidx++ ) { /* Allocate struct isa_device */ diff --git a/gpxe/src/drivers/bus/isapnp.c b/gpxe/src/drivers/bus/isapnp.c index 8f812df8..ccf6209f 100644 --- a/gpxe/src/drivers/bus/isapnp.c +++ b/gpxe/src/drivers/bus/isapnp.c @@ -55,6 +55,8 @@ * */ +FILE_LICENCE ( GPL2_OR_LATER ); + #include <stdint.h> #include <stdlib.h> #include <string.h> @@ -72,11 +74,6 @@ */ uint16_t isapnp_read_port; -static struct isapnp_driver isapnp_drivers[0] - __table_start ( struct isapnp_driver, isapnp_drivers ); -static struct isapnp_driver isapnp_drivers_end[0] - __table_end ( struct isapnp_driver, isapnp_drivers ); - static void isapnpbus_remove ( struct root_device *rootdev ); /* @@ -594,7 +591,7 @@ static int isapnp_probe ( struct isapnp_device *isapnp ) { isa_id_string ( isapnp->vendor_id, isapnp->prod_id ), isapnp->ioaddr, isapnp->irqno ); - for ( driver = isapnp_drivers; driver < isapnp_drivers_end; driver++ ){ + for_each_table_entry ( driver, ISAPNP_DRIVERS ) { for ( i = 0 ; i < driver->id_count ; i++ ) { id = &driver->ids[i]; if ( id->vendor_id != isapnp->vendor_id ) diff --git a/gpxe/src/drivers/bus/mca.c b/gpxe/src/drivers/bus/mca.c index e9233813..2815603e 100644 --- a/gpxe/src/drivers/bus/mca.c +++ b/gpxe/src/drivers/bus/mca.c @@ -5,6 +5,8 @@ * */ +FILE_LICENCE ( BSD2 ); + #include <stdint.h> #include <string.h> #include <stdlib.h> @@ -13,11 +15,6 @@ #include <gpxe/io.h> #include <gpxe/mca.h> -static struct mca_driver mca_drivers[0] - __table_start ( struct mca_driver, mca_drivers ); -static struct mca_driver mca_drivers_end[0] - __table_end ( struct mca_driver, mca_drivers ); - static void mcabus_remove ( struct root_device *rootdev ); /** @@ -41,7 +38,7 @@ static int mca_probe ( struct mca_device *mca ) { mca->pos[0], mca->pos[1], mca->pos[2], mca->pos[3], mca->pos[4], mca->pos[5], mca->pos[6], mca->pos[7] ); - for ( driver = mca_drivers; driver < mca_drivers_end; driver++ ){ + for_each_table_entry ( driver, MCA_DRIVERS ) { for ( i = 0 ; i < driver->id_count ; i++ ) { id = &driver->ids[i]; if ( id->id != MCA_ID ( mca ) ) diff --git a/gpxe/src/drivers/bus/pci.c b/gpxe/src/drivers/bus/pci.c index 2dc9d43a..8899e6e1 100644 --- a/gpxe/src/drivers/bus/pci.c +++ b/gpxe/src/drivers/bus/pci.c @@ -19,6 +19,8 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +FILE_LICENCE ( GPL2_OR_LATER ); + #include <stdint.h> #include <stdlib.h> #include <stdio.h> @@ -34,11 +36,6 @@ * */ -static struct pci_driver pci_drivers[0] - __table_start ( struct pci_driver, pci_drivers ); -static struct pci_driver pci_drivers_end[0] - __table_end ( struct pci_driver, pci_drivers ); - static void pcibus_remove ( struct root_device *rootdev ); /** @@ -188,7 +185,7 @@ static int pci_probe ( struct pci_device *pci ) { PCI_FUNC ( pci->devfn ), pci->vendor, pci->device, pci->membase, pci->ioaddr, pci->irq ); - for ( driver = pci_drivers ; driver < pci_drivers_end ; driver++ ) { + for_each_table_entry ( driver, PCI_DRIVERS ) { for ( i = 0 ; i < driver->id_count ; i++ ) { id = &driver->ids[i]; if ( ( id->vendor != PCI_ANY_ID ) && diff --git a/gpxe/src/drivers/bus/pcibackup.c b/gpxe/src/drivers/bus/pcibackup.c new file mode 100644 index 00000000..8f9994ec --- /dev/null +++ b/gpxe/src/drivers/bus/pcibackup.c @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2009 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. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include <stdint.h> +#include <gpxe/pci.h> +#include <gpxe/pcibackup.h> + +/** @file + * + * PCI configuration space backup and restoration + * + */ + +/** + * Check PCI configuration space offset against exclusion list + * + * @v pci PCI device + * @v offset Offset within PCI configuration space + * @v exclude PCI configuration space backup exclusion list, or NULL + */ +static int +pci_backup_excluded ( struct pci_device *pci, unsigned int offset, + const uint8_t *exclude ) { + + if ( ! exclude ) + return 0; + for ( ; *exclude != PCI_CONFIG_BACKUP_EXCLUDE_END ; exclude++ ) { + if ( offset == *exclude ) { + DBGC ( pci, "PCI %p skipping configuration offset " + "%02x\n", pci, offset ); + return 1; + } + } + return 0; +} + +/** + * Back up PCI configuration space + * + * @v pci PCI device + * @v backup PCI configuration space backup + * @v exclude PCI configuration space backup exclusion list, or NULL + */ +void pci_backup ( struct pci_device *pci, struct pci_config_backup *backup, + const uint8_t *exclude ) { + unsigned int offset; + uint32_t *dword; + + for ( offset = 0, dword = backup->dwords ; offset < 0x100 ; + offset += sizeof ( *dword ) , dword++ ) { + if ( ! pci_backup_excluded ( pci, offset, exclude ) ) + pci_read_config_dword ( pci, offset, dword ); + } +} + +/** + * Restore PCI configuration space + * + * @v pci PCI device + * @v backup PCI configuration space backup + * @v exclude PCI configuration space backup exclusion list, or NULL + */ +void pci_restore ( struct pci_device *pci, struct pci_config_backup *backup, + const uint8_t *exclude ) { + unsigned int offset; + uint32_t *dword; + + for ( offset = 0, dword = backup->dwords ; offset < 0x100 ; + offset += sizeof ( *dword ) , dword++ ) { + if ( ! pci_backup_excluded ( pci, offset, exclude ) ) + pci_write_config_dword ( pci, offset, *dword ); + } +} diff --git a/gpxe/src/drivers/bus/pciextra.c b/gpxe/src/drivers/bus/pciextra.c index 4603bcb9..74c40990 100644 --- a/gpxe/src/drivers/bus/pciextra.c +++ b/gpxe/src/drivers/bus/pciextra.c @@ -1,3 +1,5 @@ +FILE_LICENCE ( GPL2_OR_LATER ); + #include <stdint.h> #include <gpxe/pci.h> @@ -58,8 +60,11 @@ int pci_find_capability ( struct pci_device *pci, int cap ) { * function. */ unsigned long pci_bar_size ( struct pci_device *pci, unsigned int reg ) { + uint16_t cmd; uint32_t start, size; + /* Save the original command register */ + pci_read_config_word ( pci, PCI_COMMAND, &cmd ); /* Save the original bar */ pci_read_config_dword ( pci, reg, &start ); /* Compute which bits can be set */ @@ -68,6 +73,8 @@ unsigned long pci_bar_size ( struct pci_device *pci, unsigned int reg ) { /* Restore the original size */ pci_write_config_dword ( pci, reg, start ); /* Find the significant bits */ + /* Restore the original command register. This reenables decoding. */ + pci_write_config_word ( pci, PCI_COMMAND, cmd ); if ( start & PCI_BASE_ADDRESS_SPACE_IO ) { size &= PCI_BASE_ADDRESS_IO_MASK; } else { diff --git a/gpxe/src/drivers/infiniband/MT25218_PRM.h b/gpxe/src/drivers/infiniband/MT25218_PRM.h index 19ca92cd..f1b7c1ff 100644 --- a/gpxe/src/drivers/infiniband/MT25218_PRM.h +++ b/gpxe/src/drivers/infiniband/MT25218_PRM.h @@ -19,6 +19,8 @@ Copyright (c) 2004 Mellanox Technologies Ltd. All rights reserved. */ +FILE_LICENCE ( GPL2_ONLY ); + /*** *** This file was generated at "Tue Nov 22 15:21:23 2005" *** by: diff --git a/gpxe/src/drivers/infiniband/MT25408_PRM.h b/gpxe/src/drivers/infiniband/MT25408_PRM.h index 17882ed7..419e25ac 100644 --- a/gpxe/src/drivers/infiniband/MT25408_PRM.h +++ b/gpxe/src/drivers/infiniband/MT25408_PRM.h @@ -19,6 +19,8 @@ Copyright (c) 2004 Mellanox Technologies Ltd. All rights reserved. */ +FILE_LICENCE ( GPL2_ONLY ); + /*** *** This file was generated at "Mon Apr 16 23:22:02 2007" *** by: @@ -2069,7 +2071,11 @@ struct hermonprm_query_dev_cap_st { /* Little Endian */ pseudo_bit_t pkv[0x00001]; /* PKey Violation Counter Supported */ pseudo_bit_t qkv[0x00001]; /* QKey Violation Coutner Supported */ pseudo_bit_t vmm[0x00001]; /* Hermon New */ - pseudo_bit_t reserved27[0x00005]; + pseudo_bit_t fcoe[0x00001]; + pseudo_bit_t dpdp[0x00001]; /* Dual Port Different Protocols */ + pseudo_bit_t raw_ethertype[0x00001]; + pseudo_bit_t raw_ipv6[0x00001]; + pseudo_bit_t blh[0x00001]; pseudo_bit_t mw[0x00001]; /* Memory windows supported */ pseudo_bit_t apm[0x00001]; /* Automatic Path Migration Supported */ pseudo_bit_t atm[0x00001]; /* Atomic operations supported (atomicity is guaranteed between QPs on this HCA) */ diff --git a/gpxe/src/drivers/infiniband/arbel.c b/gpxe/src/drivers/infiniband/arbel.c index 1756a6e2..5bf35743 100644 --- a/gpxe/src/drivers/infiniband/arbel.c +++ b/gpxe/src/drivers/infiniband/arbel.c @@ -19,6 +19,8 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +FILE_LICENCE ( GPL2_OR_LATER ); + #include <stdint.h> #include <stdlib.h> #include <stdio.h> @@ -853,7 +855,6 @@ static int arbel_create_qp ( struct ib_device *ibdev, ( virt_to_bus ( arbel_qp->recv.wqe ) >> 6 ) ); MLX_FILL_1 ( &qpctx, 43, qpc_eec_data.rcv_db_record_index, arbel_qp->recv.doorbell_idx ); - MLX_FILL_1 ( &qpctx, 44, qpc_eec_data.q_key, qp->qkey ); if ( ( rc = arbel_cmd_rst2init_qpee ( arbel, qp->qpn, &qpctx )) != 0 ){ DBGC ( arbel, "Arbel %p RST2INIT_QPEE failed: %s\n", arbel, strerror ( rc ) ); @@ -906,24 +907,17 @@ static int arbel_create_qp ( struct ib_device *ibdev, * * @v ibdev Infiniband device * @v qp Queue pair - * @v mod_list Modification list * @ret rc Return status code */ static int arbel_modify_qp ( struct ib_device *ibdev, - struct ib_queue_pair *qp, - unsigned long mod_list ) { + struct ib_queue_pair *qp ) { struct arbel *arbel = ib_get_drvdata ( ibdev ); struct arbelprm_qp_ee_state_transitions qpctx; - unsigned long optparammask = 0; int rc; - /* Construct optparammask */ - if ( mod_list & IB_MODIFY_QKEY ) - optparammask |= ARBEL_QPEE_OPT_PARAM_QKEY; - /* Issue RTS2RTS_QP */ memset ( &qpctx, 0, sizeof ( qpctx ) ); - MLX_FILL_1 ( &qpctx, 0, opt_param_mask, optparammask ); + MLX_FILL_1 ( &qpctx, 0, opt_param_mask, ARBEL_QPEE_OPT_PARAM_QKEY ); MLX_FILL_1 ( &qpctx, 44, qpc_eec_data.q_key, qp->qkey ); if ( ( rc = arbel_cmd_rts2rts_qp ( arbel, qp->qpn, &qpctx ) ) != 0 ){ DBGC ( arbel, "Arbel %p RTS2RTS_QP failed: %s\n", @@ -2241,8 +2235,8 @@ static void arbel_remove ( struct pci_device *pci ) { } static struct pci_device_id arbel_nics[] = { - PCI_ROM ( 0x15b3, 0x6282, "mt25218", "MT25218 HCA driver" ), - PCI_ROM ( 0x15b3, 0x6274, "mt25204", "MT25204 HCA driver" ), + PCI_ROM ( 0x15b3, 0x6282, "mt25218", "MT25218 HCA driver", 0 ), + PCI_ROM ( 0x15b3, 0x6274, "mt25204", "MT25204 HCA driver", 0 ), }; struct pci_driver arbel_driver __pci_driver = { diff --git a/gpxe/src/drivers/infiniband/arbel.h b/gpxe/src/drivers/infiniband/arbel.h index 7d97b156..87f5933d 100644 --- a/gpxe/src/drivers/infiniband/arbel.h +++ b/gpxe/src/drivers/infiniband/arbel.h @@ -7,6 +7,8 @@ * */ +FILE_LICENCE ( GPL2_OR_LATER ); + #include <stdint.h> #include <gpxe/uaccess.h> #include "mlx_bitops.h" diff --git a/gpxe/src/drivers/infiniband/hermon.c b/gpxe/src/drivers/infiniband/hermon.c index 40add28a..b9c97f94 100644 --- a/gpxe/src/drivers/infiniband/hermon.c +++ b/gpxe/src/drivers/infiniband/hermon.c @@ -17,6 +17,8 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +FILE_LICENCE ( GPL2_OR_LATER ); + #include <stdint.h> #include <stdlib.h> #include <stdio.h> @@ -27,6 +29,7 @@ #include <byteswap.h> #include <gpxe/io.h> #include <gpxe/pci.h> +#include <gpxe/pcibackup.h> #include <gpxe/malloc.h> #include <gpxe/umalloc.h> #include <gpxe/iobuf.h> @@ -199,9 +202,10 @@ static int hermon_cmd ( struct hermon *hermon, unsigned long command, opcode_modifier, op_mod, go, 1, t, hermon->toggle ); - DBGC ( hermon, "Hermon %p issuing command:\n", hermon ); - DBGC_HDA ( hermon, virt_to_phys ( hermon->config + HERMON_HCR_BASE ), - &hcr, sizeof ( hcr ) ); + DBGC ( hermon, "Hermon %p issuing command %04x\n", + hermon, opcode ); + DBGC2_HDA ( hermon, virt_to_phys ( hermon->config + HERMON_HCR_BASE ), + &hcr, sizeof ( hcr ) ); if ( in_len && ( command & HERMON_HCR_IN_MBOX ) ) { DBGC2 ( hermon, "Input mailbox:\n" ); DBGC2_HDA ( hermon, virt_to_phys ( in_buffer ), in_buffer, @@ -416,6 +420,23 @@ hermon_cmd_2rst_qp ( struct hermon *hermon, unsigned long qpn ) { } static inline int +hermon_cmd_query_qp ( struct hermon *hermon, unsigned long qpn, + struct hermonprm_qp_ee_state_transitions *ctx ) { + return hermon_cmd ( hermon, + HERMON_HCR_OUT_CMD ( HERMON_HCR_QUERY_QP, + 1, sizeof ( *ctx ) ), + 0, NULL, qpn, ctx ); +} + +static inline int +hermon_cmd_conf_special_qp ( struct hermon *hermon, unsigned int internal_qps, + unsigned long base_qpn ) { + return hermon_cmd ( hermon, + HERMON_HCR_VOID_CMD ( HERMON_HCR_CONF_SPECIAL_QP ), + internal_qps, NULL, base_qpn, NULL ); +} + +static inline int hermon_cmd_mad_ifc ( struct hermon *hermon, unsigned int port, union hermonprm_mad *mad ) { return hermon_cmd ( hermon, @@ -521,6 +542,16 @@ hermon_cmd_map_fa ( struct hermon *hermon, 0, map, 1, NULL ); } +static inline int +hermon_cmd_sense_port ( struct hermon *hermon, unsigned int port, + struct hermonprm_sense_port *port_type ) { + return hermon_cmd ( hermon, + HERMON_HCR_OUT_CMD ( HERMON_HCR_SENSE_PORT, + 1, sizeof ( *port_type ) ), + 0, NULL, port, port_type ); +} + + /*************************************************************************** * * Memory translation table operations @@ -795,6 +826,124 @@ static void hermon_destroy_cq ( struct ib_device *ibdev, */ /** + * Assign queue pair number + * + * @v ibdev Infiniband device + * @v qp Queue pair + * @ret rc Return status code + */ +static int hermon_alloc_qpn ( struct ib_device *ibdev, + struct ib_queue_pair *qp ) { + struct hermon *hermon = ib_get_drvdata ( ibdev ); + unsigned int port_offset; + int qpn_offset; + + /* Calculate queue pair number */ + port_offset = ( ibdev->port - HERMON_PORT_BASE ); + + switch ( qp->type ) { + case IB_QPT_SMI: + qp->qpn = ( hermon->special_qpn_base + port_offset ); + return 0; + case IB_QPT_GSI: + qp->qpn = ( hermon->special_qpn_base + 2 + port_offset ); + return 0; + case IB_QPT_UD: + case IB_QPT_RC: + /* Find a free queue pair number */ + qpn_offset = hermon_bitmask_alloc ( hermon->qp_inuse, + HERMON_MAX_QPS, 1 ); + if ( qpn_offset < 0 ) { + DBGC ( hermon, "Hermon %p out of queue pairs\n", + hermon ); + return qpn_offset; + } + qp->qpn = ( ( random() & HERMON_QPN_RANDOM_MASK ) | + ( hermon->qpn_base + qpn_offset ) ); + return 0; + default: + DBGC ( hermon, "Hermon %p unsupported QP type %d\n", + hermon, qp->type ); + return -ENOTSUP; + } +} + +/** + * Free queue pair number + * + * @v ibdev Infiniband device + * @v qp Queue pair + */ +static void hermon_free_qpn ( struct ib_device *ibdev, + struct ib_queue_pair *qp ) { + struct hermon *hermon = ib_get_drvdata ( ibdev ); + int qpn_offset; + + qpn_offset = ( ( qp->qpn & ~HERMON_QPN_RANDOM_MASK ) + - hermon->qpn_base ); + if ( qpn_offset >= 0 ) + hermon_bitmask_free ( hermon->qp_inuse, qpn_offset, 1 ); +} + +/** + * Calculate transmission rate + * + * @v av Address vector + * @ret hermon_rate Hermon rate + */ +static unsigned int hermon_rate ( struct ib_address_vector *av ) { + return ( ( ( av->rate >= IB_RATE_2_5 ) && ( av->rate <= IB_RATE_120 ) ) + ? ( av->rate + 5 ) : 0 ); +} + +/** + * Calculate schedule queue + * + * @v ibdev Infiniband device + * @v qp Queue pair + * @ret sched_queue Schedule queue + */ +static unsigned int hermon_sched_queue ( struct ib_device *ibdev, + struct ib_queue_pair *qp ) { + return ( ( ( qp->type == IB_QPT_SMI ) ? + HERMON_SCHED_QP0 : HERMON_SCHED_DEFAULT ) | + ( ( ibdev->port - 1 ) << 6 ) ); +} + +/** Queue pair transport service type map */ +static uint8_t hermon_qp_st[] = { + [IB_QPT_SMI] = HERMON_ST_MLX, + [IB_QPT_GSI] = HERMON_ST_MLX, + [IB_QPT_UD] = HERMON_ST_UD, + [IB_QPT_RC] = HERMON_ST_RC, +}; + +/** + * Dump queue pair context (for debugging only) + * + * @v hermon Hermon device + * @v qp Queue pair + * @ret rc Return status code + */ +static inline int hermon_dump_qpctx ( struct hermon *hermon, + struct ib_queue_pair *qp ) { + struct hermonprm_qp_ee_state_transitions qpctx; + int rc; + + memset ( &qpctx, 0, sizeof ( qpctx ) ); + if ( ( rc = hermon_cmd_query_qp ( hermon, qp->qpn, &qpctx ) ) != 0 ) { + DBGC ( hermon, "Hermon %p QUERY_QP failed: %s\n", + hermon, strerror ( rc ) ); + return rc; + } + DBGC ( hermon, "Hermon %p QPN %lx context:\n", hermon, qp->qpn ); + DBGC_HDA ( hermon, 0, &qpctx.u.dwords[2], + ( sizeof ( qpctx ) - 8 ) ); + + return 0; +} + +/** * Create queue pair * * @v ibdev Infiniband device @@ -806,19 +955,11 @@ static int hermon_create_qp ( struct ib_device *ibdev, struct hermon *hermon = ib_get_drvdata ( ibdev ); struct hermon_queue_pair *hermon_qp; struct hermonprm_qp_ee_state_transitions qpctx; - int qpn_offset; int rc; - /* Find a free queue pair number */ - qpn_offset = hermon_bitmask_alloc ( hermon->qp_inuse, - HERMON_MAX_QPS, 1 ); - if ( qpn_offset < 0 ) { - DBGC ( hermon, "Hermon %p out of queue pairs\n", hermon ); - rc = qpn_offset; - goto err_qpn_offset; - } - qp->qpn = ( HERMON_QPN_BASE + hermon->cap.reserved_qps + - qpn_offset ); + /* Calculate queue pair number */ + if ( ( rc = hermon_alloc_qpn ( ibdev, qp ) ) != 0 ) + goto err_alloc_qpn; /* Allocate control structures */ hermon_qp = zalloc ( sizeof ( *hermon_qp ) ); @@ -864,8 +1005,8 @@ static int hermon_create_qp ( struct ib_device *ibdev, /* Transition queue to INIT state */ memset ( &qpctx, 0, sizeof ( qpctx ) ); MLX_FILL_2 ( &qpctx, 2, - qpc_eec_data.pm_state, 0x03 /* Always 0x03 for UD */, - qpc_eec_data.st, HERMON_ST_UD ); + qpc_eec_data.pm_state, HERMON_PM_STATE_MIGRATED, + qpc_eec_data.st, hermon_qp_st[qp->type] ); MLX_FILL_1 ( &qpctx, 3, qpc_eec_data.pd, HERMON_GLOBAL_PD ); MLX_FILL_4 ( &qpctx, 4, qpc_eec_data.log_rq_size, fls ( qp->recv.num_wqes - 1 ), @@ -878,12 +1019,15 @@ static int hermon_create_qp ( struct ib_device *ibdev, MLX_FILL_1 ( &qpctx, 5, qpc_eec_data.usr_page, HERMON_UAR_NON_EQ_PAGE ); MLX_FILL_1 ( &qpctx, 33, qpc_eec_data.cqn_snd, qp->send.cq->cqn ); - MLX_FILL_1 ( &qpctx, 38, qpc_eec_data.page_offset, + MLX_FILL_4 ( &qpctx, 38, + qpc_eec_data.rre, 1, + qpc_eec_data.rwe, 1, + qpc_eec_data.rae, 1, + qpc_eec_data.page_offset, ( hermon_qp->mtt.page_offset >> 6 ) ); MLX_FILL_1 ( &qpctx, 41, qpc_eec_data.cqn_rcv, qp->recv.cq->cqn ); MLX_FILL_1 ( &qpctx, 43, qpc_eec_data.db_record_addr_l, ( virt_to_phys ( &hermon_qp->recv.doorbell ) >> 2 ) ); - MLX_FILL_1 ( &qpctx, 44, qpc_eec_data.q_key, qp->qkey ); MLX_FILL_1 ( &qpctx, 53, qpc_eec_data.mtt_base_addr_l, ( hermon_qp->mtt.mtt_base_addr >> 3 ) ); if ( ( rc = hermon_cmd_rst2init_qp ( hermon, qp->qpn, @@ -892,28 +1036,7 @@ static int hermon_create_qp ( struct ib_device *ibdev, hermon, strerror ( rc ) ); goto err_rst2init_qp; } - - /* Transition queue to RTR state */ - memset ( &qpctx, 0, sizeof ( qpctx ) ); - MLX_FILL_2 ( &qpctx, 4, - qpc_eec_data.mtu, HERMON_MTU_2048, - qpc_eec_data.msg_max, 11 /* 2^11 = 2048 */ ); - MLX_FILL_1 ( &qpctx, 16, - qpc_eec_data.primary_address_path.sched_queue, - ( 0x83 /* default policy */ | - ( ( ibdev->port - 1 ) << 6 ) ) ); - if ( ( rc = hermon_cmd_init2rtr_qp ( hermon, qp->qpn, - &qpctx ) ) != 0 ) { - DBGC ( hermon, "Hermon %p INIT2RTR_QP failed: %s\n", - hermon, strerror ( rc ) ); - goto err_init2rtr_qp; - } - memset ( &qpctx, 0, sizeof ( qpctx ) ); - if ( ( rc = hermon_cmd_rtr2rts_qp ( hermon, qp->qpn, &qpctx ) ) != 0 ){ - DBGC ( hermon, "Hermon %p RTR2RTS_QP failed: %s\n", - hermon, strerror ( rc ) ); - goto err_rtr2rts_qp; - } + hermon_qp->state = HERMON_QP_ST_INIT; DBGC ( hermon, "Hermon %p QPN %#lx send ring at [%p,%p)\n", hermon, qp->qpn, hermon_qp->send.wqe, @@ -924,8 +1047,6 @@ static int hermon_create_qp ( struct ib_device *ibdev, ib_qp_set_drvdata ( qp, hermon_qp ); return 0; - err_rtr2rts_qp: - err_init2rtr_qp: hermon_cmd_2rst_qp ( hermon, qp->qpn ); err_rst2init_qp: hermon_free_mtt ( hermon, &hermon_qp->mtt ); @@ -934,8 +1055,8 @@ static int hermon_create_qp ( struct ib_device *ibdev, err_alloc_wqe: free ( hermon_qp ); err_hermon_qp: - hermon_bitmask_free ( hermon->qp_inuse, qpn_offset, 1 ); - err_qpn_offset: + hermon_free_qpn ( ibdev, qp ); + err_alloc_qpn: return rc; } @@ -944,24 +1065,68 @@ static int hermon_create_qp ( struct ib_device *ibdev, * * @v ibdev Infiniband device * @v qp Queue pair - * @v mod_list Modification list * @ret rc Return status code */ static int hermon_modify_qp ( struct ib_device *ibdev, - struct ib_queue_pair *qp, - unsigned long mod_list ) { + struct ib_queue_pair *qp ) { struct hermon *hermon = ib_get_drvdata ( ibdev ); + struct hermon_queue_pair *hermon_qp = ib_qp_get_drvdata ( qp ); struct hermonprm_qp_ee_state_transitions qpctx; - unsigned long optparammask = 0; int rc; - /* Construct optparammask */ - if ( mod_list & IB_MODIFY_QKEY ) - optparammask |= HERMON_QP_OPT_PARAM_QKEY; + /* Transition queue to RTR state, if applicable */ + if ( hermon_qp->state < HERMON_QP_ST_RTR ) { + memset ( &qpctx, 0, sizeof ( qpctx ) ); + MLX_FILL_2 ( &qpctx, 4, + qpc_eec_data.mtu, HERMON_MTU_2048, + qpc_eec_data.msg_max, 31 ); + MLX_FILL_1 ( &qpctx, 7, + qpc_eec_data.remote_qpn_een, qp->av.qpn ); + MLX_FILL_1 ( &qpctx, 9, + qpc_eec_data.primary_address_path.rlid, + qp->av.lid ); + MLX_FILL_1 ( &qpctx, 10, + qpc_eec_data.primary_address_path.max_stat_rate, + hermon_rate ( &qp->av ) ); + memcpy ( &qpctx.u.dwords[12], &qp->av.gid, + sizeof ( qp->av.gid ) ); + MLX_FILL_1 ( &qpctx, 16, + qpc_eec_data.primary_address_path.sched_queue, + hermon_sched_queue ( ibdev, qp ) ); + MLX_FILL_1 ( &qpctx, 39, + qpc_eec_data.next_rcv_psn, qp->recv.psn ); + if ( ( rc = hermon_cmd_init2rtr_qp ( hermon, qp->qpn, + &qpctx ) ) != 0 ) { + DBGC ( hermon, "Hermon %p INIT2RTR_QP failed: %s\n", + hermon, strerror ( rc ) ); + return rc; + } + hermon_qp->state = HERMON_QP_ST_RTR; + } + + /* Transition queue to RTS state */ + if ( hermon_qp->state < HERMON_QP_ST_RTS ) { + memset ( &qpctx, 0, sizeof ( qpctx ) ); + MLX_FILL_1 ( &qpctx, 10, + qpc_eec_data.primary_address_path.ack_timeout, + 14 /* 4.096us * 2^(14) = 67ms */ ); + MLX_FILL_2 ( &qpctx, 30, + qpc_eec_data.retry_count, HERMON_RETRY_MAX, + qpc_eec_data.rnr_retry, HERMON_RETRY_MAX ); + MLX_FILL_1 ( &qpctx, 32, + qpc_eec_data.next_send_psn, qp->send.psn ); + if ( ( rc = hermon_cmd_rtr2rts_qp ( hermon, qp->qpn, + &qpctx ) ) != 0 ) { + DBGC ( hermon, "Hermon %p RTR2RTS_QP failed: %s\n", + hermon, strerror ( rc ) ); + return rc; + } + hermon_qp->state = HERMON_QP_ST_RTS; + } - /* Issue RTS2RTS_QP */ + /* Update parameters in RTS state */ memset ( &qpctx, 0, sizeof ( qpctx ) ); - MLX_FILL_1 ( &qpctx, 0, opt_param_mask, optparammask ); + MLX_FILL_1 ( &qpctx, 0, opt_param_mask, HERMON_QP_OPT_PARAM_QKEY ); MLX_FILL_1 ( &qpctx, 44, qpc_eec_data.q_key, qp->qkey ); if ( ( rc = hermon_cmd_rts2rts_qp ( hermon, qp->qpn, &qpctx ) ) != 0 ){ DBGC ( hermon, "Hermon %p RTS2RTS_QP failed: %s\n", @@ -982,7 +1147,6 @@ static void hermon_destroy_qp ( struct ib_device *ibdev, struct ib_queue_pair *qp ) { struct hermon *hermon = ib_get_drvdata ( ibdev ); struct hermon_queue_pair *hermon_qp = ib_qp_get_drvdata ( qp ); - int qpn_offset; int rc; /* Take ownership back from hardware */ @@ -1001,9 +1165,7 @@ static void hermon_destroy_qp ( struct ib_device *ibdev, free ( hermon_qp ); /* Mark queue number as free */ - qpn_offset = ( qp->qpn - HERMON_QPN_BASE - - hermon->cap.reserved_qps ); - hermon_bitmask_free ( hermon->qp_inuse, qpn_offset, 1 ); + hermon_free_qpn ( ibdev, qp ); ib_qp_set_drvdata ( qp, NULL ); } @@ -1015,9 +1177,133 @@ static void hermon_destroy_qp ( struct ib_device *ibdev, *************************************************************************** */ -/** GID used for GID-less send work queue entries */ -static const struct ib_gid hermon_no_gid = { - { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } +/** + * Construct UD send work queue entry + * + * @v ibdev Infiniband device + * @v qp Queue pair + * @v av Address vector + * @v iobuf I/O buffer + * @v wqe Send work queue entry + * @ret opcode Control opcode + */ +static unsigned int +hermon_fill_ud_send_wqe ( struct ib_device *ibdev, + struct ib_queue_pair *qp __unused, + struct ib_address_vector *av, + struct io_buffer *iobuf, + union hermon_send_wqe *wqe ) { + struct hermon *hermon = ib_get_drvdata ( ibdev ); + + MLX_FILL_1 ( &wqe->ud.ctrl, 1, ds, + ( ( offsetof ( typeof ( wqe->ud ), data[1] ) / 16 ) ) ); + MLX_FILL_1 ( &wqe->ud.ctrl, 2, c, 0x03 /* generate completion */ ); + MLX_FILL_2 ( &wqe->ud.ud, 0, + ud_address_vector.pd, HERMON_GLOBAL_PD, + ud_address_vector.port_number, ibdev->port ); + MLX_FILL_2 ( &wqe->ud.ud, 1, + ud_address_vector.rlid, av->lid, + ud_address_vector.g, av->gid_present ); + MLX_FILL_1 ( &wqe->ud.ud, 2, + ud_address_vector.max_stat_rate, hermon_rate ( av ) ); + MLX_FILL_1 ( &wqe->ud.ud, 3, ud_address_vector.sl, av->sl ); + memcpy ( &wqe->ud.ud.u.dwords[4], &av->gid, sizeof ( av->gid ) ); + MLX_FILL_1 ( &wqe->ud.ud, 8, destination_qp, av->qpn ); + MLX_FILL_1 ( &wqe->ud.ud, 9, q_key, av->qkey ); + MLX_FILL_1 ( &wqe->ud.data[0], 0, byte_count, iob_len ( iobuf ) ); + MLX_FILL_1 ( &wqe->ud.data[0], 1, l_key, hermon->lkey ); + MLX_FILL_1 ( &wqe->ud.data[0], 3, + local_address_l, virt_to_bus ( iobuf->data ) ); + return HERMON_OPCODE_SEND; +} + +/** + * Construct MLX send work queue entry + * + * @v ibdev Infiniband device + * @v qp Queue pair + * @v av Address vector + * @v iobuf I/O buffer + * @v wqe Send work queue entry + * @ret opcode Control opcode + */ +static unsigned int +hermon_fill_mlx_send_wqe ( struct ib_device *ibdev, + struct ib_queue_pair *qp, + struct ib_address_vector *av, + struct io_buffer *iobuf, + union hermon_send_wqe *wqe ) { + struct hermon *hermon = ib_get_drvdata ( ibdev ); + struct io_buffer headers; + + /* Construct IB headers */ + iob_populate ( &headers, &wqe->mlx.headers, 0, + sizeof ( wqe->mlx.headers ) ); + iob_reserve ( &headers, sizeof ( wqe->mlx.headers ) ); + ib_push ( ibdev, &headers, qp, iob_len ( iobuf ), av ); + + /* Fill work queue entry */ + MLX_FILL_1 ( &wqe->mlx.ctrl, 1, ds, + ( ( offsetof ( typeof ( wqe->mlx ), data[2] ) / 16 ) ) ); + MLX_FILL_5 ( &wqe->mlx.ctrl, 2, + c, 0x03 /* generate completion */, + icrc, 0 /* generate ICRC */, + max_statrate, hermon_rate ( av ), + slr, 0, + v15, ( ( qp->ext_qpn == IB_QPN_SMI ) ? 1 : 0 ) ); + MLX_FILL_1 ( &wqe->mlx.ctrl, 3, rlid, av->lid ); + MLX_FILL_1 ( &wqe->mlx.data[0], 0, + byte_count, iob_len ( &headers ) ); + MLX_FILL_1 ( &wqe->mlx.data[0], 1, l_key, hermon->lkey ); + MLX_FILL_1 ( &wqe->mlx.data[0], 3, + local_address_l, virt_to_bus ( headers.data ) ); + MLX_FILL_1 ( &wqe->mlx.data[1], 0, + byte_count, ( iob_len ( iobuf ) + 4 /* ICRC */ ) ); + MLX_FILL_1 ( &wqe->mlx.data[1], 1, l_key, hermon->lkey ); + MLX_FILL_1 ( &wqe->mlx.data[1], 3, + local_address_l, virt_to_bus ( iobuf->data ) ); + return HERMON_OPCODE_SEND; +} + +/** + * Construct RC send work queue entry + * + * @v ibdev Infiniband device + * @v qp Queue pair + * @v av Address vector + * @v iobuf I/O buffer + * @v wqe Send work queue entry + * @ret opcode Control opcode + */ +static unsigned int +hermon_fill_rc_send_wqe ( struct ib_device *ibdev, + struct ib_queue_pair *qp __unused, + struct ib_address_vector *av __unused, + struct io_buffer *iobuf, + union hermon_send_wqe *wqe ) { + struct hermon *hermon = ib_get_drvdata ( ibdev ); + + MLX_FILL_1 ( &wqe->rc.ctrl, 1, ds, + ( ( offsetof ( typeof ( wqe->rc ), data[1] ) / 16 ) ) ); + MLX_FILL_1 ( &wqe->rc.ctrl, 2, c, 0x03 /* generate completion */ ); + MLX_FILL_1 ( &wqe->rc.data[0], 0, byte_count, iob_len ( iobuf ) ); + MLX_FILL_1 ( &wqe->rc.data[0], 1, l_key, hermon->lkey ); + MLX_FILL_1 ( &wqe->rc.data[0], 3, + local_address_l, virt_to_bus ( iobuf->data ) ); + return HERMON_OPCODE_SEND; +} + +/** Work queue entry constructors */ +static unsigned int +( * hermon_fill_send_wqe[] ) ( struct ib_device *ibdev, + struct ib_queue_pair *qp, + struct ib_address_vector *av, + struct io_buffer *iobuf, + union hermon_send_wqe *wqe ) = { + [IB_QPT_SMI] = hermon_fill_mlx_send_wqe, + [IB_QPT_GSI] = hermon_fill_mlx_send_wqe, + [IB_QPT_UD] = hermon_fill_ud_send_wqe, + [IB_QPT_RC] = hermon_fill_rc_send_wqe, }; /** @@ -1037,10 +1323,10 @@ static int hermon_post_send ( struct ib_device *ibdev, struct hermon_queue_pair *hermon_qp = ib_qp_get_drvdata ( qp ); struct ib_work_queue *wq = &qp->send; struct hermon_send_work_queue *hermon_send_wq = &hermon_qp->send; - struct hermonprm_ud_send_wqe *wqe; - const struct ib_gid *gid; + union hermon_send_wqe *wqe; union hermonprm_doorbell_register db_reg; unsigned int wqe_idx_mask; + unsigned int opcode; /* Allocate work queue entry */ wqe_idx_mask = ( wq->num_wqes - 1 ); @@ -1050,35 +1336,18 @@ static int hermon_post_send ( struct ib_device *ibdev, } wq->iobufs[wq->next_idx & wqe_idx_mask] = iobuf; wqe = &hermon_send_wq->wqe[ wq->next_idx & - ( hermon_send_wq->num_wqes - 1 ) ].ud; + ( hermon_send_wq->num_wqes - 1 ) ]; /* Construct work queue entry */ memset ( ( ( ( void * ) wqe ) + 4 /* avoid ctrl.owner */ ), 0, ( sizeof ( *wqe ) - 4 ) ); - MLX_FILL_1 ( &wqe->ctrl, 1, ds, ( sizeof ( *wqe ) / 16 ) ); - MLX_FILL_1 ( &wqe->ctrl, 2, c, 0x03 /* generate completion */ ); - MLX_FILL_2 ( &wqe->ud, 0, - ud_address_vector.pd, HERMON_GLOBAL_PD, - ud_address_vector.port_number, ibdev->port ); - MLX_FILL_2 ( &wqe->ud, 1, - ud_address_vector.rlid, av->lid, - ud_address_vector.g, av->gid_present ); - MLX_FILL_1 ( &wqe->ud, 2, - ud_address_vector.max_stat_rate, - ( ( ( av->rate < 2 ) || ( av->rate > 10 ) ) ? - 8 : ( av->rate + 5 ) ) ); - MLX_FILL_1 ( &wqe->ud, 3, ud_address_vector.sl, av->sl ); - gid = ( av->gid_present ? &av->gid : &hermon_no_gid ); - memcpy ( &wqe->ud.u.dwords[4], gid, sizeof ( *gid ) ); - MLX_FILL_1 ( &wqe->ud, 8, destination_qp, av->qpn ); - MLX_FILL_1 ( &wqe->ud, 9, q_key, av->qkey ); - MLX_FILL_1 ( &wqe->data[0], 0, byte_count, iob_len ( iobuf ) ); - MLX_FILL_1 ( &wqe->data[0], 1, l_key, hermon->reserved_lkey ); - MLX_FILL_1 ( &wqe->data[0], 3, - local_address_l, virt_to_bus ( iobuf->data ) ); + assert ( qp->type < ( sizeof ( hermon_fill_send_wqe ) / + sizeof ( hermon_fill_send_wqe[0] ) ) ); + assert ( hermon_fill_send_wqe[qp->type] != NULL ); + opcode = hermon_fill_send_wqe[qp->type] ( ibdev, qp, av, iobuf, wqe ); barrier(); MLX_FILL_2 ( &wqe->ctrl, 0, - opcode, HERMON_OPCODE_SEND, + opcode, opcode, owner, ( ( wq->next_idx & hermon_send_wq->num_wqes ) ? 1 : 0 ) ); DBGCP ( hermon, "Hermon %p posting send WQE:\n", hermon ); @@ -1126,7 +1395,7 @@ static int hermon_post_recv ( struct ib_device *ibdev, /* Construct work queue entry */ MLX_FILL_1 ( &wqe->data[0], 0, byte_count, iob_tailroom ( iobuf ) ); - MLX_FILL_1 ( &wqe->data[0], 1, l_key, hermon->reserved_lkey ); + MLX_FILL_1 ( &wqe->data[0], 1, l_key, hermon->lkey ); MLX_FILL_1 ( &wqe->data[0], 3, local_address_l, virt_to_bus ( iobuf->data ) ); @@ -1157,8 +1426,9 @@ static int hermon_complete ( struct ib_device *ibdev, struct ib_queue_pair *qp; struct hermon_queue_pair *hermon_qp; struct io_buffer *iobuf; - struct ib_address_vector av; + struct ib_address_vector recv_av; struct ib_global_route_header *grh; + struct ib_address_vector *av; unsigned int opcode; unsigned long qpn; int is_send; @@ -1196,7 +1466,7 @@ static int hermon_complete ( struct ib_device *ibdev, iobuf = wq->iobufs[wqe_idx]; if ( ! iobuf ) { DBGC ( hermon, "Hermon %p CQN %lx QPN %lx empty WQE %x\n", - hermon, cq->cqn, qpn, wqe_idx ); + hermon, cq->cqn, qp->qpn, wqe_idx ); return -EIO; } wq->iobufs[wqe_idx] = NULL; @@ -1209,18 +1479,31 @@ static int hermon_complete ( struct ib_device *ibdev, len = MLX_GET ( &cqe->normal, byte_cnt ); assert ( len <= iob_tailroom ( iobuf ) ); iob_put ( iobuf, len ); - assert ( iob_len ( iobuf ) >= sizeof ( *grh ) ); - grh = iobuf->data; - iob_pull ( iobuf, sizeof ( *grh ) ); - /* Construct address vector */ - memset ( &av, 0, sizeof ( av ) ); - av.qpn = MLX_GET ( &cqe->normal, srq_rqpn ); - av.lid = MLX_GET ( &cqe->normal, slid_smac47_32 ); - av.sl = MLX_GET ( &cqe->normal, sl ); - av.gid_present = MLX_GET ( &cqe->normal, g ); - memcpy ( &av.gid, &grh->sgid, sizeof ( av.gid ) ); + switch ( qp->type ) { + case IB_QPT_SMI: + case IB_QPT_GSI: + case IB_QPT_UD: + assert ( iob_len ( iobuf ) >= sizeof ( *grh ) ); + grh = iobuf->data; + iob_pull ( iobuf, sizeof ( *grh ) ); + /* Construct address vector */ + av = &recv_av; + memset ( av, 0, sizeof ( *av ) ); + av->qpn = MLX_GET ( &cqe->normal, srq_rqpn ); + av->lid = MLX_GET ( &cqe->normal, slid_smac47_32 ); + av->sl = MLX_GET ( &cqe->normal, sl ); + av->gid_present = MLX_GET ( &cqe->normal, g ); + memcpy ( &av->gid, &grh->sgid, sizeof ( av->gid ) ); + break; + case IB_QPT_RC: + av = &qp->av; + break; + default: + assert ( 0 ); + return -EINVAL; + } /* Hand off to completion handler */ - ib_complete_recv ( ibdev, qp, &av, iobuf, rc ); + ib_complete_recv ( ibdev, qp, av, iobuf, rc ); } return rc; @@ -1417,7 +1700,7 @@ static void hermon_event_port_state_change ( struct hermon *hermon, ( link_up ? "up" : "down" ) ); /* Sanity check */ - if ( port >= HERMON_NUM_PORTS ) { + if ( port >= hermon->cap.num_ports ) { DBGC ( hermon, "Hermon %p port %d does not exist!\n", hermon, ( port + 1 ) ); return; @@ -1489,6 +1772,36 @@ static void hermon_poll_eq ( struct ib_device *ibdev ) { */ /** + * Sense port type + * + * @v ibdev Infiniband device + * @ret port_type Port type, or negative error + */ +static int hermon_sense_port_type ( struct ib_device *ibdev ) { + struct hermon *hermon = ib_get_drvdata ( ibdev ); + struct hermonprm_sense_port sense_port; + int port_type; + int rc; + + /* If DPDP is not supported, always assume Infiniband */ + if ( ! hermon->cap.dpdp ) + return HERMON_PORT_TYPE_IB; + + /* Sense the port type */ + if ( ( rc = hermon_cmd_sense_port ( hermon, ibdev->port, + &sense_port ) ) != 0 ) { + DBGC ( hermon, "Hermon %p port %d sense failed: %s\n", + hermon, ibdev->port, strerror ( rc ) ); + return rc; + } + port_type = MLX_GET ( &sense_port, port_type ); + + DBGC ( hermon, "Hermon %p port %d type %d\n", + hermon, ibdev->port, port_type ); + return port_type; +} + +/** * Initialise Infiniband link * * @v ibdev Infiniband device @@ -1497,8 +1810,19 @@ static void hermon_poll_eq ( struct ib_device *ibdev ) { static int hermon_open ( struct ib_device *ibdev ) { struct hermon *hermon = ib_get_drvdata ( ibdev ); struct hermonprm_init_port init_port; + int port_type; int rc; + /* Check we are connected to an Infiniband network */ + if ( ( rc = port_type = hermon_sense_port_type ( ibdev ) ) < 0 ) + return rc; + if ( port_type != HERMON_PORT_TYPE_IB ) { + DBGC ( hermon, "Hermon %p port %d not connected to an " + "Infiniband network", hermon, ibdev->port ); + return -ENOTCONN; + } + + /* Init Port */ memset ( &init_port, 0, sizeof ( init_port ) ); MLX_FILL_2 ( &init_port, 0, port_width_cap, 3, @@ -1536,6 +1860,27 @@ static void hermon_close ( struct ib_device *ibdev ) { } } +/** + * Inform embedded subnet management agent of a received MAD + * + * @v ibdev Infiniband device + * @v mad MAD + * @ret rc Return status code + */ +static int hermon_inform_sma ( struct ib_device *ibdev, + union ib_mad *mad ) { + int rc; + + /* Send the MAD to the embedded SMA */ + if ( ( rc = hermon_mad ( ibdev, mad ) ) != 0 ) + return rc; + + /* Update parameters held in software */ + ib_smc_update ( ibdev, hermon_mad ); + + return 0; +} + /*************************************************************************** * * Multicast group operations @@ -1646,6 +1991,8 @@ static struct ib_device_operations hermon_ib_operations = { .close = hermon_close, .mcast_attach = hermon_mcast_attach, .mcast_detach = hermon_mcast_detach, + .set_port_info = hermon_inform_sma, + .set_pkey_table = hermon_inform_sma, }; /*************************************************************************** @@ -1676,6 +2023,12 @@ static int hermon_map_vpm ( struct hermon *hermon, assert ( ( pa & ( HERMON_PAGE_SIZE - 1 ) ) == 0 ); assert ( ( len & ( HERMON_PAGE_SIZE - 1 ) ) == 0 ); + /* These mappings tend to generate huge volumes of + * uninteresting debug data, which basically makes it + * impossible to use debugging otherwise. + */ + DBG_DISABLE ( DBGLVL_LOG | DBGLVL_EXTRA ); + while ( len ) { memset ( &mapping, 0, sizeof ( mapping ) ); MLX_FILL_1 ( &mapping, 0, va_h, ( va >> 32 ) ); @@ -1684,6 +2037,7 @@ static int hermon_map_vpm ( struct hermon *hermon, log2size, 0, pa_l, ( pa >> 12 ) ); if ( ( rc = map ( hermon, &mapping ) ) != 0 ) { + DBG_ENABLE ( DBGLVL_LOG | DBGLVL_EXTRA ); DBGC ( hermon, "Hermon %p could not map %llx => %lx: " "%s\n", hermon, va, pa, strerror ( rc ) ); return rc; @@ -1693,6 +2047,7 @@ static int hermon_map_vpm ( struct hermon *hermon, len -= HERMON_PAGE_SIZE; } + DBG_ENABLE ( DBGLVL_LOG | DBGLVL_EXTRA ); return 0; } @@ -1821,6 +2176,15 @@ static int hermon_get_cap ( struct hermon *hermon ) { ( 1 << MLX_GET ( &dev_cap, log2_rsvd_mrws ) ); hermon->cap.dmpt_entry_size = MLX_GET ( &dev_cap, d_mpt_entry_sz ); hermon->cap.reserved_uars = MLX_GET ( &dev_cap, num_rsvd_uars ); + hermon->cap.num_ports = MLX_GET ( &dev_cap, num_ports ); + hermon->cap.dpdp = MLX_GET ( &dev_cap, dpdp ); + + /* Sanity check */ + if ( hermon->cap.num_ports > HERMON_MAX_PORTS ) { + DBGC ( hermon, "Hermon %p has %d ports (only %d supported)\n", + hermon, hermon->cap.num_ports, HERMON_MAX_PORTS ); + hermon->cap.num_ports = HERMON_MAX_PORTS; + } return 0; } @@ -1868,7 +2232,8 @@ static int hermon_alloc_icm ( struct hermon *hermon, */ /* Calculate number of each object type within ICM */ - log_num_qps = fls ( hermon->cap.reserved_qps + HERMON_MAX_QPS - 1 ); + log_num_qps = fls ( hermon->cap.reserved_qps + + HERMON_RSVD_SPECIAL_QPS + HERMON_MAX_QPS - 1 ); log_num_srqs = fls ( hermon->cap.reserved_srqs - 1 ); log_num_cqs = fls ( hermon->cap.reserved_cqs + HERMON_MAX_CQS - 1 ); log_num_eqs = fls ( hermon->cap.reserved_eqs + HERMON_MAX_EQS - 1 ); @@ -2130,17 +2495,21 @@ static int hermon_setup_mpt ( struct hermon *hermon ) { /* Derive key */ key = ( hermon->cap.reserved_mrws | HERMON_MKEY_PREFIX ); - hermon->reserved_lkey = ( ( key << 8 ) | ( key >> 24 ) ); + hermon->lkey = ( ( key << 8 ) | ( key >> 24 ) ); /* Initialise memory protection table */ memset ( &mpt, 0, sizeof ( mpt ) ); - MLX_FILL_4 ( &mpt, 0, - r_w, 1, - pa, 1, + MLX_FILL_7 ( &mpt, 0, + atomic, 1, + rw, 1, + rr, 1, + lw, 1, lr, 1, - lw, 1 ); + pa, 1, + r_w, 1 ); MLX_FILL_1 ( &mpt, 2, mem_key, key ); - MLX_FILL_1 ( &mpt, 3, pd, HERMON_GLOBAL_PD ); + MLX_FILL_1 ( &mpt, 3, + pd, HERMON_GLOBAL_PD ); MLX_FILL_1 ( &mpt, 10, len64, 1 ); if ( ( rc = hermon_cmd_sw2hw_mpt ( hermon, hermon->cap.reserved_mrws, @@ -2154,6 +2523,54 @@ static int hermon_setup_mpt ( struct hermon *hermon ) { } /** + * Configure special queue pairs + * + * @v hermon Hermon device + * @ret rc Return status code + */ +static int hermon_configure_special_qps ( struct hermon *hermon ) { + int rc; + + /* Special QP block must be aligned on its own size */ + hermon->special_qpn_base = ( ( hermon->cap.reserved_qps + + HERMON_NUM_SPECIAL_QPS - 1 ) + & ~( HERMON_NUM_SPECIAL_QPS - 1 ) ); + hermon->qpn_base = ( hermon->special_qpn_base + + HERMON_NUM_SPECIAL_QPS ); + DBGC ( hermon, "Hermon %p special QPs at [%lx,%lx]\n", hermon, + hermon->special_qpn_base, ( hermon->qpn_base - 1 ) ); + + /* Issue command to configure special QPs */ + if ( ( rc = hermon_cmd_conf_special_qp ( hermon, 0x00, + hermon->special_qpn_base ) ) != 0 ) { + DBGC ( hermon, "Hermon %p could not configure special QPs: " + "%s\n", hermon, strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * Reset device + * + * @v hermon Hermon device + * @v pci PCI device + */ +static void hermon_reset ( struct hermon *hermon, + struct pci_device *pci ) { + struct pci_config_backup backup; + static const uint8_t backup_exclude[] = + PCI_CONFIG_BACKUP_EXCLUDE ( 0x58, 0x5c ); + + pci_backup ( pci, &backup, backup_exclude ); + writel ( HERMON_RESET_MAGIC, + ( hermon->config + HERMON_RESET_OFFSET ) ); + mdelay ( HERMON_RESET_WAIT_TIME_MS ); + pci_restore ( pci, &backup, backup_exclude ); +} + +/** * Probe PCI device * * @v pci PCI device @@ -2165,7 +2582,7 @@ static int hermon_probe ( struct pci_device *pci, struct hermon *hermon; struct ib_device *ibdev; struct hermonprm_init_hca init_hca; - int i; + unsigned int i; int rc; /* Allocate Hermon device */ @@ -2176,20 +2593,6 @@ static int hermon_probe ( struct pci_device *pci, } pci_set_drvdata ( pci, hermon ); - /* Allocate Infiniband devices */ - for ( i = 0 ; i < HERMON_NUM_PORTS ; i++ ) { - ibdev = alloc_ibdev ( 0 ); - if ( ! ibdev ) { - rc = -ENOMEM; - goto err_alloc_ibdev; - } - hermon->ibdev[i] = ibdev; - ibdev->op = &hermon_ib_operations; - ibdev->dev = &pci->dev; - ibdev->port = ( HERMON_PORT_BASE + i ); - ib_set_drvdata ( ibdev, hermon ); - } - /* Fix up PCI device */ adjust_pci_device ( pci ); @@ -2199,6 +2602,9 @@ static int hermon_probe ( struct pci_device *pci, hermon->uar = ioremap ( pci_bar_start ( pci, HERMON_PCI_UAR_BAR ), HERMON_UAR_NON_EQ_PAGE * HERMON_PAGE_SIZE ); + /* Reset device */ + hermon_reset ( hermon, pci ); + /* Allocate space for mailboxes */ hermon->mailbox_in = malloc_dma ( HERMON_MBOX_SIZE, HERMON_MBOX_ALIGN ); @@ -2221,6 +2627,20 @@ static int hermon_probe ( struct pci_device *pci, if ( ( rc = hermon_get_cap ( hermon ) ) != 0 ) goto err_get_cap; + /* Allocate Infiniband devices */ + for ( i = 0 ; i < hermon->cap.num_ports ; i++ ) { + ibdev = alloc_ibdev ( 0 ); + if ( ! ibdev ) { + rc = -ENOMEM; + goto err_alloc_ibdev; + } + hermon->ibdev[i] = ibdev; + ibdev->op = &hermon_ib_operations; + ibdev->dev = &pci->dev; + ibdev->port = ( HERMON_PORT_BASE + i ); + ib_set_drvdata ( ibdev, hermon ); + } + /* Allocate ICM */ memset ( &init_hca, 0, sizeof ( init_hca ) ); if ( ( rc = hermon_alloc_icm ( hermon, &init_hca ) ) != 0 ) @@ -2239,17 +2659,24 @@ static int hermon_probe ( struct pci_device *pci, /* Set up memory protection */ if ( ( rc = hermon_setup_mpt ( hermon ) ) != 0 ) goto err_setup_mpt; + for ( i = 0 ; i < hermon->cap.num_ports ; i++ ) + hermon->ibdev[i]->rdma_key = hermon->lkey; /* Set up event queue */ if ( ( rc = hermon_create_eq ( hermon ) ) != 0 ) goto err_create_eq; - /* Update MAD parameters */ - for ( i = 0 ; i < HERMON_NUM_PORTS ; i++ ) + /* Configure special QPs */ + if ( ( rc = hermon_configure_special_qps ( hermon ) ) != 0 ) + goto err_conf_special_qps; + + /* Update IPoIB MAC address */ + for ( i = 0 ; i < hermon->cap.num_ports ; i++ ) { ib_smc_update ( hermon->ibdev[i], hermon_mad ); + } /* Register Infiniband devices */ - for ( i = 0 ; i < HERMON_NUM_PORTS ; i++ ) { + for ( i = 0 ; i < hermon->cap.num_ports ; i++ ) { if ( ( rc = register_ibdev ( hermon->ibdev[i] ) ) != 0 ) { DBGC ( hermon, "Hermon %p could not register IB " "device: %s\n", hermon, strerror ( rc ) ); @@ -2259,10 +2686,11 @@ static int hermon_probe ( struct pci_device *pci, return 0; - i = HERMON_NUM_PORTS; + i = hermon->cap.num_ports; err_register_ibdev: - for ( i-- ; i >= 0 ; i-- ) + for ( i-- ; ( signed int ) i >= 0 ; i-- ) unregister_ibdev ( hermon->ibdev[i] ); + err_conf_special_qps: hermon_destroy_eq ( hermon ); err_create_eq: err_setup_mpt: @@ -2270,6 +2698,10 @@ static int hermon_probe ( struct pci_device *pci, err_init_hca: hermon_free_icm ( hermon ); err_alloc_icm: + i = hermon->cap.num_ports; + err_alloc_ibdev: + for ( i-- ; ( signed int ) i >= 0 ; i-- ) + ibdev_put ( hermon->ibdev[i] ); err_get_cap: hermon_stop_firmware ( hermon ); err_start_firmware: @@ -2277,10 +2709,6 @@ static int hermon_probe ( struct pci_device *pci, err_mailbox_out: free_dma ( hermon->mailbox_in, HERMON_MBOX_SIZE ); err_mailbox_in: - i = HERMON_NUM_PORTS; - err_alloc_ibdev: - for ( i-- ; i >= 0 ; i-- ) - ibdev_put ( hermon->ibdev[i] ); free ( hermon ); err_alloc_hermon: return rc; @@ -2295,7 +2723,7 @@ static void hermon_remove ( struct pci_device *pci ) { struct hermon *hermon = pci_get_drvdata ( pci ); int i; - for ( i = ( HERMON_NUM_PORTS - 1 ) ; i >= 0 ; i-- ) + for ( i = ( hermon->cap.num_ports - 1 ) ; i >= 0 ; i-- ) unregister_ibdev ( hermon->ibdev[i] ); hermon_destroy_eq ( hermon ); hermon_cmd_close_hca ( hermon ); @@ -2304,16 +2732,16 @@ static void hermon_remove ( struct pci_device *pci ) { hermon_stop_firmware ( hermon ); free_dma ( hermon->mailbox_out, HERMON_MBOX_SIZE ); free_dma ( hermon->mailbox_in, HERMON_MBOX_SIZE ); - for ( i = ( HERMON_NUM_PORTS - 1 ) ; i >= 0 ; i-- ) + for ( i = ( hermon->cap.num_ports - 1 ) ; i >= 0 ; i-- ) ibdev_put ( hermon->ibdev[i] ); free ( hermon ); } static struct pci_device_id hermon_nics[] = { - PCI_ROM ( 0x15b3, 0x6340, "mt25408", "MT25408 HCA driver" ), - PCI_ROM ( 0x15b3, 0x634a, "mt25418", "MT25418 HCA driver" ), - PCI_ROM ( 0x15b3, 0x6732, "mt26418", "MT26418 HCA driver" ), - PCI_ROM ( 0x15b3, 0x673c, "mt26428", "MT26428 HCA driver" ), + PCI_ROM ( 0x15b3, 0x6340, "mt25408", "MT25408 HCA driver", 0 ), + PCI_ROM ( 0x15b3, 0x634a, "mt25418", "MT25418 HCA driver", 0 ), + PCI_ROM ( 0x15b3, 0x6732, "mt26418", "MT26418 HCA driver", 0 ), + PCI_ROM ( 0x15b3, 0x673c, "mt26428", "MT26428 HCA driver", 0 ), }; struct pci_driver hermon_driver __pci_driver = { diff --git a/gpxe/src/drivers/infiniband/hermon.h b/gpxe/src/drivers/infiniband/hermon.h index ed39da69..c53f3da5 100644 --- a/gpxe/src/drivers/infiniband/hermon.h +++ b/gpxe/src/drivers/infiniband/hermon.h @@ -7,8 +7,11 @@ * */ +FILE_LICENCE ( GPL2_OR_LATER ); + #include <stdint.h> #include <gpxe/uaccess.h> +#include <gpxe/ib_packet.h> #include "mlx_bitops.h" #include "MT25408_PRM.h" @@ -18,7 +21,7 @@ */ /* Ports in existence */ -#define HERMON_NUM_PORTS 2 +#define HERMON_MAX_PORTS 2 #define HERMON_PORT_BASE 1 /* PCI BARs */ @@ -26,7 +29,13 @@ #define HERMON_PCI_CONFIG_BAR_SIZE 0x100000 #define HERMON_PCI_UAR_BAR PCI_BASE_ADDRESS_2 +/* Device reset */ +#define HERMON_RESET_OFFSET 0x0f0010 +#define HERMON_RESET_MAGIC 0x01000000UL +#define HERMON_RESET_WAIT_TIME_MS 1000 + /* Work queue entry and completion queue entry opcodes */ +#define HERMON_OPCODE_NOP 0x00 #define HERMON_OPCODE_SEND 0x0a #define HERMON_OPCODE_RECV_ERROR 0xfe #define HERMON_OPCODE_SEND_ERROR 0xff @@ -51,10 +60,13 @@ #define HERMON_HCR_RTR2RTS_QP 0x001b #define HERMON_HCR_RTS2RTS_QP 0x001c #define HERMON_HCR_2RST_QP 0x0021 +#define HERMON_HCR_QUERY_QP 0x0022 +#define HERMON_HCR_CONF_SPECIAL_QP 0x0023 #define HERMON_HCR_MAD_IFC 0x0024 #define HERMON_HCR_READ_MCG 0x0025 #define HERMON_HCR_WRITE_MCG 0x0026 #define HERMON_HCR_MGID_HASH 0x0027 +#define HERMON_HCR_SENSE_PORT 0x004d #define HERMON_HCR_RUN_FW 0x0ff6 #define HERMON_HCR_DISABLE_LAM 0x0ff7 #define HERMON_HCR_ENABLE_LAM 0x0ff8 @@ -67,7 +79,9 @@ #define HERMON_HCR_MAP_FA 0x0fff /* Service types */ +#define HERMON_ST_RC 0x00 #define HERMON_ST_UD 0x03 +#define HERMON_ST_MLX 0x07 /* MTUs */ #define HERMON_MTU_2048 0x04 @@ -80,13 +94,24 @@ #define HERMON_DB_EQ_OFFSET(_eqn) \ ( 0x800 + HERMON_PAGE_SIZE * ( (_eqn) / 4 ) + 0x08 * ( (_eqn) % 4 ) ) +#define HERMON_QP_OPT_PARAM_PM_STATE 0x00000400UL #define HERMON_QP_OPT_PARAM_QKEY 0x00000020UL +#define HERMON_QP_OPT_PARAM_ALT_PATH 0x00000001UL #define HERMON_MAP_EQ ( 0UL << 31 ) #define HERMON_UNMAP_EQ ( 1UL << 31 ) #define HERMON_EV_PORT_STATE_CHANGE 0x09 +#define HERMON_SCHED_QP0 0x3f +#define HERMON_SCHED_DEFAULT 0x83 + +#define HERMON_PM_STATE_ARMED 0x00 +#define HERMON_PM_STATE_REARM 0x01 +#define HERMON_PM_STATE_MIGRATED 0x03 + +#define HERMON_RETRY_MAX 0x07 + /* * Datatypes that seem to be missing from the autogenerated documentation * @@ -145,6 +170,14 @@ struct hermonprm_port_state_change_event_st { struct hermonprm_port_state_change_st data; } __attribute__ (( packed )); +/** Hermon sense port */ +struct hermonprm_sense_port_st { + pseudo_bit_t port_type[0x00020]; +/* -------------- */ + pseudo_bit_t reserved[0x00020]; +}; +#define HERMON_PORT_TYPE_IB 1 + /* * Wrapper structures for hardware datatypes * @@ -173,9 +206,11 @@ struct MLX_DECLARE_STRUCT ( hermonprm_query_dev_cap ); struct MLX_DECLARE_STRUCT ( hermonprm_query_fw ); struct MLX_DECLARE_STRUCT ( hermonprm_queue_pair_ee_context_entry ); struct MLX_DECLARE_STRUCT ( hermonprm_scalar_parameter ); +struct MLX_DECLARE_STRUCT ( hermonprm_sense_port ); struct MLX_DECLARE_STRUCT ( hermonprm_send_db_register ); struct MLX_DECLARE_STRUCT ( hermonprm_ud_address_vector ); struct MLX_DECLARE_STRUCT ( hermonprm_virtual_physical_mapping ); +struct MLX_DECLARE_STRUCT ( hermonprm_wqe_segment_ctrl_mlx ); struct MLX_DECLARE_STRUCT ( hermonprm_wqe_segment_ctrl_send ); struct MLX_DECLARE_STRUCT ( hermonprm_wqe_segment_data_ptr ); struct MLX_DECLARE_STRUCT ( hermonprm_wqe_segment_ud ); @@ -191,7 +226,7 @@ struct hermonprm_write_mtt { struct hermonprm_mtt mtt; } __attribute__ (( packed )); -#define HERMON_MAX_GATHER 1 +#define HERMON_MAX_GATHER 2 struct hermonprm_ud_send_wqe { struct hermonprm_wqe_segment_ctrl_send ctrl; @@ -199,6 +234,17 @@ struct hermonprm_ud_send_wqe { struct hermonprm_wqe_segment_data_ptr data[HERMON_MAX_GATHER]; } __attribute__ (( packed )); +struct hermonprm_mlx_send_wqe { + struct hermonprm_wqe_segment_ctrl_mlx ctrl; + struct hermonprm_wqe_segment_data_ptr data[HERMON_MAX_GATHER]; + uint8_t headers[IB_MAX_HEADER_SIZE]; +} __attribute__ (( packed )); + +struct hermonprm_rc_send_wqe { + struct hermonprm_wqe_segment_ctrl_send ctrl; + struct hermonprm_wqe_segment_data_ptr data[HERMON_MAX_GATHER]; +} __attribute__ (( packed )); + #define HERMON_MAX_SCATTER 1 struct hermonprm_recv_wqe { @@ -265,6 +311,10 @@ struct hermon_dev_cap { size_t dmpt_entry_size; /** Number of reserved UARs */ unsigned int reserved_uars; + /** Number of ports */ + unsigned int num_ports; + /** Dual-port different protocol */ + int dpdp; }; /** Number of cMPT entries of each type */ @@ -318,7 +368,10 @@ struct hermon_mtt { /** A Hermon send work queue entry */ union hermon_send_wqe { + struct hermonprm_wqe_segment_ctrl_send ctrl; struct hermonprm_ud_send_wqe ud; + struct hermonprm_mlx_send_wqe mlx; + struct hermonprm_rc_send_wqe rc; uint8_t force_align[HERMON_SEND_WQE_ALIGN]; } __attribute__ (( packed )); @@ -358,14 +411,32 @@ struct hermon_recv_work_queue { struct hermonprm_qp_db_record doorbell __attribute__ (( aligned (4) )); }; +/** Number of special queue pairs */ +#define HERMON_NUM_SPECIAL_QPS 8 + +/** Number of queue pairs reserved for the "special QP" block + * + * The special QPs must be within a contiguous block aligned on its + * own size. + */ +#define HERMON_RSVD_SPECIAL_QPS ( ( HERMON_NUM_SPECIAL_QPS << 1 ) - 1 ) + /** Maximum number of allocatable queue pairs * * This is a policy decision, not a device limit. */ #define HERMON_MAX_QPS 8 -/** Base queue pair number */ -#define HERMON_QPN_BASE 0x550000 +/** Queue pair number randomisation mask */ +#define HERMON_QPN_RANDOM_MASK 0xfff000 + +/** Hermon queue pair state */ +enum hermon_queue_pair_state { + HERMON_QP_ST_RST = 0, + HERMON_QP_ST_INIT, + HERMON_QP_ST_RTR, + HERMON_QP_ST_RTS, +}; /** A Hermon queue pair */ struct hermon_queue_pair { @@ -379,6 +450,8 @@ struct hermon_queue_pair { struct hermon_send_work_queue send; /** Receive work queue */ struct hermon_recv_work_queue recv; + /** Queue state */ + enum hermon_queue_pair_state state; }; /** Maximum number of allocatable completion queues @@ -458,11 +531,11 @@ struct hermon { /** Event queue */ struct hermon_event_queue eq; - /** Reserved LKey + /** Unrestricted LKey * * Used to get unrestricted memory access. */ - unsigned long reserved_lkey; + unsigned long lkey; /** Completion queue in-use bitmask */ hermon_bitmask_t cq_inuse[ HERMON_BITMASK_SIZE ( HERMON_MAX_CQS ) ]; @@ -473,9 +546,13 @@ struct hermon { /** Device capabilities */ struct hermon_dev_cap cap; + /** Special QPN base */ + unsigned long special_qpn_base; + /** QPN base */ + unsigned long qpn_base; /** Infiniband devices */ - struct ib_device *ibdev[HERMON_NUM_PORTS]; + struct ib_device *ibdev[HERMON_MAX_PORTS]; }; /** Global protection domain */ diff --git a/gpxe/src/drivers/infiniband/ib_packet.c b/gpxe/src/drivers/infiniband/ib_packet.c deleted file mode 100644 index 0f21617f..00000000 --- a/gpxe/src/drivers/infiniband/ib_packet.c +++ /dev/null @@ -1,234 +0,0 @@ -/* - * Copyright (C) 2008 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 <stdint.h> -#include <stdlib.h> -#include <string.h> -#include <errno.h> -#include <byteswap.h> -#include <gpxe/iobuf.h> -#include <gpxe/infiniband.h> -#include <gpxe/ib_packet.h> - -/** - * @file - * - * Infiniband Packet Formats - * - */ - -/** - * Add IB headers - * - * @v ibdev Infiniband device - * @v iobuf I/O buffer to contain headers - * @v qp Queue pair - * @v payload_len Payload length - * @v av Address vector - */ -int ib_push ( struct ib_device *ibdev, struct io_buffer *iobuf, - struct ib_queue_pair *qp, size_t payload_len, - const struct ib_address_vector *av ) { - struct ib_local_route_header *lrh; - struct ib_global_route_header *grh; - struct ib_base_transport_header *bth; - struct ib_datagram_extended_transport_header *deth; - size_t orig_iob_len = iob_len ( iobuf ); - size_t pad_len; - size_t lrh_len; - size_t grh_len; - unsigned int vl; - unsigned int lnh; - - DBGC2 ( ibdev, "IBDEV %p TX %04x:%08lx => %04x:%08lx (key %08lx)\n", - ibdev, ibdev->lid, qp->qpn, av->lid, av->qpn, av->qkey ); - - /* Calculate packet length */ - pad_len = ( (-payload_len) & 0x3 ); - payload_len += pad_len; - payload_len += 4; /* ICRC */ - - /* Reserve space for headers */ - orig_iob_len = iob_len ( iobuf ); - deth = iob_push ( iobuf, sizeof ( *deth ) ); - bth = iob_push ( iobuf, sizeof ( *bth ) ); - grh_len = ( payload_len + iob_len ( iobuf ) - orig_iob_len ); - grh = ( av->gid_present ? - iob_push ( iobuf, sizeof ( *grh ) ) : NULL ); - lrh = iob_push ( iobuf, sizeof ( *lrh ) ); - lrh_len = ( payload_len + iob_len ( iobuf ) - orig_iob_len ); - - /* Construct LRH */ - vl = ( ( av->qpn == IB_QPN_SMP ) ? IB_VL_SMP : IB_VL_DEFAULT ); - lrh->vl__lver = ( vl << 4 ); - lnh = ( grh ? IB_LNH_GRH : IB_LNH_BTH ); - lrh->sl__lnh = ( ( av->sl << 4 ) | lnh ); - lrh->dlid = htons ( av->lid ); - lrh->length = htons ( lrh_len >> 2 ); - lrh->slid = htons ( ibdev->lid ); - - /* Construct GRH, if required */ - if ( grh ) { - grh->ipver__tclass__flowlabel = - htonl ( IB_GRH_IPVER_IPv6 << 28 ); - grh->paylen = htons ( grh_len ); - grh->nxthdr = IB_GRH_NXTHDR_IBA; - grh->hoplmt = 0; - memcpy ( &grh->sgid, &ibdev->gid, sizeof ( grh->sgid ) ); - memcpy ( &grh->dgid, &av->gid, sizeof ( grh->dgid ) ); - } - - /* Construct BTH */ - bth->opcode = BTH_OPCODE_UD_SEND; - bth->se__m__padcnt__tver = ( pad_len << 4 ); - bth->pkey = htons ( ibdev->pkey ); - bth->dest_qp = htonl ( av->qpn ); - bth->ack__psn = htonl ( ( ibdev->psn++ ) & 0xffffffUL ); - - /* Construct DETH */ - deth->qkey = htonl ( av->qkey ); - deth->src_qp = htonl ( qp->qpn ); - - DBGCP_HDA ( ibdev, 0, iobuf->data, - ( iob_len ( iobuf ) - orig_iob_len ) ); - - return 0; -} - -/** - * Remove IB headers - * - * @v ibdev Infiniband device - * @v iobuf I/O buffer containing headers - * @v qp Queue pair to fill in, or NULL - * @v payload_len Payload length to fill in, or NULL - * @v av Address vector to fill in - */ -int ib_pull ( struct ib_device *ibdev, struct io_buffer *iobuf, - struct ib_queue_pair **qp, size_t *payload_len, - struct ib_address_vector *av ) { - struct ib_local_route_header *lrh; - struct ib_global_route_header *grh; - struct ib_base_transport_header *bth; - struct ib_datagram_extended_transport_header *deth; - size_t orig_iob_len = iob_len ( iobuf ); - unsigned int lnh; - size_t pad_len; - unsigned long qpn; - unsigned int lid; - - /* Clear return values */ - if ( qp ) - *qp = NULL; - if ( payload_len ) - *payload_len = 0; - memset ( av, 0, sizeof ( *av ) ); - - /* Extract LRH */ - if ( iob_len ( iobuf ) < sizeof ( *lrh ) ) { - DBGC ( ibdev, "IBDEV %p RX too short (%zd bytes) for LRH\n", - ibdev, iob_len ( iobuf ) ); - return -EINVAL; - } - lrh = iobuf->data; - iob_pull ( iobuf, sizeof ( *lrh ) ); - av->lid = ntohs ( lrh->slid ); - av->sl = ( lrh->sl__lnh >> 4 ); - lnh = ( lrh->sl__lnh & 0x3 ); - lid = ntohs ( lrh->dlid ); - - /* Reject unsupported packets */ - if ( ! ( ( lnh == IB_LNH_BTH ) || ( lnh == IB_LNH_GRH ) ) ) { - DBGC ( ibdev, "IBDEV %p RX unsupported LNH %x\n", - ibdev, lnh ); - return -ENOTSUP; - } - - /* Extract GRH, if present */ - if ( lnh == IB_LNH_GRH ) { - if ( iob_len ( iobuf ) < sizeof ( *grh ) ) { - DBGC ( ibdev, "IBDEV %p RX too short (%zd bytes) " - "for GRH\n", ibdev, iob_len ( iobuf ) ); - return -EINVAL; - } - grh = iobuf->data; - iob_pull ( iobuf, sizeof ( *grh ) ); - av->gid_present = 1; - memcpy ( &av->gid, &grh->sgid, sizeof ( av->gid ) ); - } else { - grh = NULL; - } - - /* Extract BTH */ - if ( iob_len ( iobuf ) < sizeof ( *bth ) ) { - DBGC ( ibdev, "IBDEV %p RX too short (%zd bytes) for BTH\n", - ibdev, iob_len ( iobuf ) ); - return -EINVAL; - } - bth = iobuf->data; - iob_pull ( iobuf, sizeof ( *bth ) ); - if ( bth->opcode != BTH_OPCODE_UD_SEND ) { - DBGC ( ibdev, "IBDEV %p unsupported BTH opcode %x\n", - ibdev, bth->opcode ); - return -ENOTSUP; - } - qpn = ntohl ( bth->dest_qp ); - - /* Extract DETH */ - if ( iob_len ( iobuf ) < sizeof ( *deth ) ) { - DBGC ( ibdev, "IBDEV %p RX too short (%zd bytes) for DETH\n", - ibdev, iob_len ( iobuf ) ); - return -EINVAL; - } - deth = iobuf->data; - iob_pull ( iobuf, sizeof ( *deth ) ); - av->qpn = ntohl ( deth->src_qp ); - av->qkey = ntohl ( deth->qkey ); - - /* Calculate payload length, if applicable */ - if ( payload_len ) { - pad_len = ( ( bth->se__m__padcnt__tver >> 4 ) & 0x3 ); - *payload_len = ( ( ntohs ( lrh->length ) << 2 ) - - ( orig_iob_len - iob_len ( iobuf ) ) - - pad_len - 4 /* ICRC */ ); - } - - /* Determine destination QP, if applicable */ - if ( qp ) { - if ( IB_LID_MULTICAST ( lid ) && grh ) { - *qp = ib_find_qp_mgid ( ibdev, &grh->dgid ); - } else { - *qp = ib_find_qp_qpn ( ibdev, qpn ); - } - if ( ! *qp ) { - DBGC ( ibdev, "IBDEV %p RX for nonexistent QP\n", - ibdev ); - return -ENODEV; - } - } - - DBGC2 ( ibdev, "IBDEV %p RX %04x:%08lx <= %04x:%08lx (key %08x)\n", - ibdev, lid, - ( IB_LID_MULTICAST( lid ) ? ( qp ? (*qp)->qpn : -1UL ) : qpn ), - av->lid, av->qpn, ntohl ( deth->qkey ) ); - DBGCP_HDA ( ibdev, 0, - ( iobuf->data - ( orig_iob_len - iob_len ( iobuf ) ) ), - ( orig_iob_len - iob_len ( iobuf ) ) ); - - return 0; -} diff --git a/gpxe/src/drivers/infiniband/ib_sma.c b/gpxe/src/drivers/infiniband/ib_sma.c deleted file mode 100644 index 2bd3a9e8..00000000 --- a/gpxe/src/drivers/infiniband/ib_sma.c +++ /dev/null @@ -1,553 +0,0 @@ -/* - * Copyright (C) 2008 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 <stdint.h> -#include <stdlib.h> -#include <string.h> -#include <errno.h> -#include <stdio.h> -#include <unistd.h> -#include <byteswap.h> -#include <gpxe/infiniband.h> -#include <gpxe/iobuf.h> -#include <gpxe/process.h> -#include <gpxe/ib_sma.h> - -/** - * @file - * - * Infiniband Subnet Management Agent - * - */ - -/** - * Get node information - * - * @v sma Subnet management agent - * @v get Attribute to get - */ -static void ib_sma_get_node_info ( struct ib_sma *sma, - union ib_smp_data *get ) { - struct ib_device *ibdev = sma->ibdev; - struct ib_node_info *node_info = &get->node_info; - struct ib_device *tmp; - - memset ( node_info, 0, sizeof ( *node_info ) ); - node_info->base_version = IB_MGMT_BASE_VERSION; - node_info->class_version = IB_SMP_CLASS_VERSION; - node_info->node_type = IB_NODE_TYPE_HCA; - /* Search for IB devices with the same physical device to - * identify port count and a suitable Node GUID. - */ - for_each_ibdev ( tmp ) { - if ( tmp->dev != ibdev->dev ) - continue; - if ( node_info->num_ports == 0 ) { - memcpy ( node_info->sys_guid, &tmp->gid.u.half[1], - sizeof ( node_info->sys_guid ) ); - memcpy ( node_info->node_guid, &tmp->gid.u.half[1], - sizeof ( node_info->node_guid ) ); - } - node_info->num_ports++; - } - memcpy ( node_info->port_guid, &ibdev->gid.u.half[1], - sizeof ( node_info->port_guid ) ); - node_info->partition_cap = htons ( 1 ); - node_info->local_port_num = ibdev->port; -} - -/** - * Get node description - * - * @v sma Subnet management agent - * @v get Attribute to get - */ -static void ib_sma_get_node_desc ( struct ib_sma *sma, - union ib_smp_data *get ) { - struct ib_device *ibdev = sma->ibdev; - struct ib_node_desc *node_desc = &get->node_desc; - struct ib_gid_half *guid = &ibdev->gid.u.half[1]; - - memset ( node_desc, 0, sizeof ( *node_desc ) ); - snprintf ( node_desc->node_string, sizeof ( node_desc->node_string ), - "gPXE %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x (%s)", - guid->bytes[0], guid->bytes[1], guid->bytes[2], - guid->bytes[3], guid->bytes[4], guid->bytes[5], - guid->bytes[6], guid->bytes[7], ibdev->dev->name ); -} - -/** - * Get GUID information - * - * @v sma Subnet management agent - * @v get Attribute to get - */ -static void ib_sma_get_guid_info ( struct ib_sma *sma, - union ib_smp_data *get ) { - struct ib_device *ibdev = sma->ibdev; - struct ib_guid_info *guid_info = &get->guid_info; - - memset ( guid_info, 0, sizeof ( *guid_info ) ); - memcpy ( guid_info->guid[0], &ibdev->gid.u.half[1], - sizeof ( guid_info->guid[0] ) ); -} - -/** - * Get port information - * - * @v sma Subnet management agent - * @v get Attribute to get - */ -static void ib_sma_get_port_info ( struct ib_sma *sma, - union ib_smp_data *get ) { - struct ib_device *ibdev = sma->ibdev; - struct ib_port_info *port_info = &get->port_info; - - memset ( port_info, 0, sizeof ( *port_info ) ); - memcpy ( port_info->gid_prefix, &ibdev->gid.u.half[0], - sizeof ( port_info->gid_prefix ) ); - port_info->lid = ntohs ( ibdev->lid ); - port_info->mastersm_lid = ntohs ( ibdev->sm_lid ); - port_info->local_port_num = ibdev->port; - port_info->link_width_enabled = ibdev->link_width; - port_info->link_width_supported = ibdev->link_width; - port_info->link_width_active = ibdev->link_width; - port_info->link_speed_supported__port_state = - ( ( ibdev->link_speed << 4 ) | ibdev->port_state ); - port_info->port_phys_state__link_down_def_state = - ( ( IB_PORT_PHYS_STATE_POLLING << 4 ) | - IB_PORT_PHYS_STATE_POLLING ); - port_info->link_speed_active__link_speed_enabled = - ( ( ibdev->link_speed << 4 ) | ibdev->link_speed ); - port_info->neighbour_mtu__mastersm_sl = - ( ( IB_MTU_2048 << 4 ) | ibdev->sm_sl ); - port_info->vl_cap__init_type = ( IB_VL_0 << 4 ); - port_info->init_type_reply__mtu_cap = IB_MTU_2048; - port_info->operational_vls__enforcement = ( IB_VL_0 << 4 ); - port_info->guid_cap = 1; -} - -/** - * Set port information - * - * @v sma Subnet management agent - * @v set Attribute to set - * @ret rc Return status code - */ -static int ib_sma_set_port_info ( struct ib_sma *sma, - const union ib_smp_data *set ) { - struct ib_device *ibdev = sma->ibdev; - const struct ib_port_info *port_info = &set->port_info; - - memcpy ( &ibdev->gid.u.half[0], port_info->gid_prefix, - sizeof ( ibdev->gid.u.half[0] ) ); - ibdev->lid = ntohs ( port_info->lid ); - ibdev->sm_lid = ntohs ( port_info->mastersm_lid ); - ibdev->sm_sl = ( port_info->neighbour_mtu__mastersm_sl & 0xf ); - - if ( ! sma->op->set_port_info ) { - /* Not an error; we just ignore all other settings */ - return 0; - } - - return sma->op->set_port_info ( ibdev, port_info ); -} - -/** - * Get partition key table - * - * @v sma Subnet management agent - * @v get Attribute to get - */ -static void ib_sma_get_pkey_table ( struct ib_sma *sma, - union ib_smp_data *get ) { - struct ib_device *ibdev = sma->ibdev; - struct ib_pkey_table *pkey_table = &get->pkey_table; - - memset ( pkey_table, 0, sizeof ( *pkey_table ) ); - pkey_table->pkey[0] = htons ( ibdev->pkey ); -} - -/** - * Set partition key table - * - * @v sma Subnet management agent - * @v set Attribute to set - */ -static int ib_sma_set_pkey_table ( struct ib_sma *sma, - const union ib_smp_data *get ) { - struct ib_device *ibdev = sma->ibdev; - const struct ib_pkey_table *pkey_table = &get->pkey_table; - - ibdev->pkey = ntohs ( pkey_table->pkey[0] ); - return 0; -} - -/** An attribute handler */ -struct ib_sma_handler { - /** Attribute (in network byte order) */ - uint16_t attr_id; - /** Get attribute - * - * @v sma Subnet management agent - * @v get Attribute to get - * @ret rc Return status code - */ - void ( * get ) ( struct ib_sma *sma, union ib_smp_data *get ); - /** Set attribute - * - * @v sma Subnet management agent - * @v set Attribute to set - * @ret rc Return status code - */ - int ( * set ) ( struct ib_sma *sma, const union ib_smp_data *set ); -}; - -/** List of attribute handlers */ -static struct ib_sma_handler ib_sma_handlers[] = { - { htons ( IB_SMP_ATTR_NODE_DESC ), - ib_sma_get_node_desc, NULL }, - { htons ( IB_SMP_ATTR_NODE_INFO ), - ib_sma_get_node_info, NULL }, - { htons ( IB_SMP_ATTR_GUID_INFO ), - ib_sma_get_guid_info, NULL }, - { htons ( IB_SMP_ATTR_PORT_INFO ), - ib_sma_get_port_info, ib_sma_set_port_info }, - { htons ( IB_SMP_ATTR_PKEY_TABLE ), - ib_sma_get_pkey_table, ib_sma_set_pkey_table }, -}; - -/** - * Identify attribute handler - * - * @v attr_id Attribute ID (in network byte order) - * @ret handler Attribute handler (or NULL) - */ -static struct ib_sma_handler * ib_sma_handler ( uint16_t attr_id ) { - struct ib_sma_handler *handler; - unsigned int i; - - for ( i = 0 ; i < ( sizeof ( ib_sma_handlers ) / - sizeof ( ib_sma_handlers[0] ) ) ; i++ ) { - handler = &ib_sma_handlers[i]; - if ( handler->attr_id == attr_id ) - return handler; - } - - return NULL; -} - -/** - * Respond to management datagram - * - * @v sma Subnet management agent - * @v mad Management datagram - * @ret rc Return status code - */ -static int ib_sma_mad ( struct ib_sma *sma, union ib_mad *mad ) { - struct ib_device *ibdev = sma->ibdev; - struct ib_mad_hdr *hdr = &mad->hdr; - struct ib_mad_smp *smp = &mad->smp; - struct ib_sma_handler *handler = NULL; - unsigned int hop_pointer; - unsigned int hop_count; - int rc; - - DBGC ( sma, "SMA %p received SMP with bv=%02x mc=%02x cv=%02x " - "meth=%02x attr=%04x mod=%08x\n", sma, hdr->base_version, - hdr->mgmt_class, hdr->class_version, hdr->method, - ntohs ( hdr->attr_id ), ntohl ( hdr->attr_mod ) ); - DBGC2_HDA ( sma, 0, mad, sizeof ( *mad ) ); - - /* Sanity checks */ - if ( hdr->base_version != IB_MGMT_BASE_VERSION ) { - DBGC ( sma, "SMA %p unsupported base version %x\n", - sma, hdr->base_version ); - return -ENOTSUP; - } - if ( ( hdr->mgmt_class != IB_MGMT_CLASS_SUBN_DIRECTED_ROUTE ) && - ( hdr->mgmt_class != IB_MGMT_CLASS_SUBN_LID_ROUTED ) ) { - DBGC ( sma, "SMA %p unsupported management class %x\n", - sma, hdr->mgmt_class ); - return -ENOTSUP; - } - if ( hdr->class_version != IB_SMP_CLASS_VERSION ) { - DBGC ( sma, "SMA %p unsupported class version %x\n", - sma, hdr->class_version ); - return -ENOTSUP; - } - if ( ( hdr->method != IB_MGMT_METHOD_GET ) && - ( hdr->method != IB_MGMT_METHOD_SET ) ) { - DBGC ( sma, "SMA %p unsupported method %x\n", - sma, hdr->method ); - return -ENOTSUP; - } - - /* Identify handler */ - if ( ! ( handler = ib_sma_handler ( hdr->attr_id ) ) ) { - DBGC ( sma, "SMA %p unsupported attribute %x\n", - sma, ntohs ( hdr->attr_id ) ); - hdr->status = htons ( IB_MGMT_STATUS_UNSUPPORTED_METHOD_ATTR ); - goto respond_without_data; - } - - /* Set attribute (if applicable) */ - if ( hdr->method != IB_MGMT_METHOD_SET ) { - hdr->status = htons ( IB_MGMT_STATUS_OK ); - goto respond; - } - if ( ! handler->set ) { - DBGC ( sma, "SMA %p attribute %x is unsettable\n", - sma, ntohs ( hdr->attr_id ) ); - hdr->status = htons ( IB_MGMT_STATUS_UNSUPPORTED_METHOD_ATTR ); - goto respond; - } - if ( ( rc = handler->set ( sma, &smp->smp_data ) ) != 0 ) { - DBGC ( sma, "SMA %p could not set attribute %x: %s\n", - sma, ntohs ( hdr->attr_id ), strerror ( rc ) ); - hdr->status = htons ( IB_MGMT_STATUS_UNSUPPORTED_METHOD_ATTR ); - goto respond; - } - - hdr->status = htons ( IB_MGMT_STATUS_OK ); - - respond: - /* Get attribute */ - handler->get ( sma, &smp->smp_data ); - - respond_without_data: - - /* Set method to "Get Response" */ - hdr->method = IB_MGMT_METHOD_GET_RESP; - - /* Set response fields for directed route SMPs */ - if ( hdr->mgmt_class == IB_MGMT_CLASS_SUBN_DIRECTED_ROUTE ) { - hdr->status |= htons ( IB_SMP_STATUS_D_INBOUND ); - hop_pointer = smp->mad_hdr.class_specific.smp.hop_pointer; - hop_count = smp->mad_hdr.class_specific.smp.hop_count; - assert ( hop_count == hop_pointer ); - if ( hop_pointer < ( sizeof ( smp->return_path.hops ) / - sizeof ( smp->return_path.hops[0] ) ) ) { - smp->return_path.hops[hop_pointer] = ibdev->port; - } else { - DBGC ( sma, "SMA %p invalid hop pointer %d\n", - sma, hop_pointer ); - return -EINVAL; - } - } - - DBGC ( sma, "SMA %p responding with status=%04x\n", - sma, ntohs ( hdr->status ) ); - DBGC2_HDA ( sma, 0, mad, sizeof ( *mad ) ); - - return 0; -} - -/** - * Refill SMA receive ring - * - * @v sma Subnet management agent - */ -static void ib_sma_refill_recv ( struct ib_sma *sma ) { - struct ib_device *ibdev = sma->ibdev; - struct io_buffer *iobuf; - int rc; - - while ( sma->qp->recv.fill < IB_SMA_NUM_RECV_WQES ) { - - /* Allocate I/O buffer */ - iobuf = alloc_iob ( IB_SMA_PAYLOAD_LEN ); - if ( ! iobuf ) { - /* Non-fatal; we will refill on next attempt */ - return; - } - - /* Post I/O buffer */ - if ( ( rc = ib_post_recv ( ibdev, sma->qp, iobuf ) ) != 0 ) { - DBGC ( sma, "SMA %p could not refill: %s\n", - sma, strerror ( rc ) ); - free_iob ( iobuf ); - /* Give up */ - return; - } - } -} - -/** - * Complete SMA send - * - * - * @v ibdev Infiniband device - * @v qp Queue pair - * @v iobuf I/O buffer - * @v rc Completion status code - */ -static void ib_sma_complete_send ( struct ib_device *ibdev __unused, - struct ib_queue_pair *qp, - struct io_buffer *iobuf, int rc ) { - struct ib_sma *sma = ib_qp_get_ownerdata ( qp ); - - if ( rc != 0 ) { - DBGC ( sma, "SMA %p send completion error: %s\n", - sma, strerror ( rc ) ); - } - free_iob ( iobuf ); -} - -/** - * Complete SMA receive - * - * - * @v ibdev Infiniband device - * @v qp Queue pair - * @v av Address vector - * @v iobuf I/O buffer - * @v rc Completion status code - */ -static void ib_sma_complete_recv ( struct ib_device *ibdev, - struct ib_queue_pair *qp, - struct ib_address_vector *av, - struct io_buffer *iobuf, int rc ) { - struct ib_sma *sma = ib_qp_get_ownerdata ( qp ); - union ib_mad *mad; - - /* Ignore errors */ - if ( rc != 0 ) { - DBGC ( sma, "SMA %p RX error: %s\n", sma, strerror ( rc ) ); - goto err; - } - - /* Sanity check */ - if ( iob_len ( iobuf ) != sizeof ( *mad ) ) { - DBGC ( sma, "SMA %p RX bad size (%zd bytes)\n", - sma, iob_len ( iobuf ) ); - goto err; - } - mad = iobuf->data; - - /* Construct MAD response */ - if ( ( rc = ib_sma_mad ( sma, mad ) ) != 0 ) { - DBGC ( sma, "SMA %p could not construct MAD response: %s\n", - sma, strerror ( rc ) ); - goto err; - } - - /* Send MAD response */ - if ( ( rc = ib_post_send ( ibdev, qp, av, iobuf ) ) != 0 ) { - DBGC ( sma, "SMA %p could not send MAD response: %s\n", - sma, strerror ( rc ) ); - goto err; - } - - return; - - err: - free_iob ( iobuf ); -} - -/** SMA completion operations */ -static struct ib_completion_queue_operations ib_sma_completion_ops = { - .complete_send = ib_sma_complete_send, - .complete_recv = ib_sma_complete_recv, -}; - -/** - * Poll SMA - * - * @v process Process - */ -static void ib_sma_step ( struct process *process ) { - struct ib_sma *sma = - container_of ( process, struct ib_sma, poll ); - struct ib_device *ibdev = sma->ibdev; - - /* Poll the kernel completion queue */ - ib_poll_cq ( ibdev, sma->cq ); - - /* Refill the receive ring */ - ib_sma_refill_recv ( sma ); -} - -/** - * Create SMA - * - * @v sma Subnet management agent - * @v ibdev Infiniband device - * @v op Subnet management operations - * @ret rc Return status code - */ -int ib_create_sma ( struct ib_sma *sma, struct ib_device *ibdev, - struct ib_sma_operations *op ) { - int rc; - - /* Initialise fields */ - memset ( sma, 0, sizeof ( *sma ) ); - sma->ibdev = ibdev; - sma->op = op; - process_init ( &sma->poll, ib_sma_step, &ibdev->refcnt ); - - /* Create completion queue */ - sma->cq = ib_create_cq ( ibdev, IB_SMA_NUM_CQES, - &ib_sma_completion_ops ); - if ( ! sma->cq ) { - rc = -ENOMEM; - goto err_create_cq; - } - - /* Create queue pair */ - sma->qp = ib_create_qp ( ibdev, IB_SMA_NUM_SEND_WQES, sma->cq, - IB_SMA_NUM_RECV_WQES, sma->cq, 0 ); - if ( ! sma->qp ) { - rc = -ENOMEM; - goto err_create_qp; - } - ib_qp_set_ownerdata ( sma->qp, sma ); - - /* If we don't get QP0, we can't function */ - if ( sma->qp->qpn != IB_QPN_SMP ) { - DBGC ( sma, "SMA %p on QPN %lx, needs to be on QPN 0\n", - sma, sma->qp->qpn ); - rc = -ENOTSUP; - goto err_not_qp0; - } - - /* Fill receive ring */ - ib_sma_refill_recv ( sma ); - return 0; - - err_not_qp0: - ib_destroy_qp ( ibdev, sma->qp ); - err_create_qp: - ib_destroy_cq ( ibdev, sma->cq ); - err_create_cq: - process_del ( &sma->poll ); - return rc; -} - -/** - * Destroy SMA - * - * @v sma Subnet management agent - */ -void ib_destroy_sma ( struct ib_sma *sma ) { - struct ib_device *ibdev = sma->ibdev; - - ib_destroy_qp ( ibdev, sma->qp ); - ib_destroy_cq ( ibdev, sma->cq ); - process_del ( &sma->poll ); -} diff --git a/gpxe/src/drivers/infiniband/ib_smc.c b/gpxe/src/drivers/infiniband/ib_smc.c deleted file mode 100644 index af0c4ab9..00000000 --- a/gpxe/src/drivers/infiniband/ib_smc.c +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright (C) 2008 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 <stdint.h> -#include <stdlib.h> -#include <string.h> -#include <errno.h> -#include <unistd.h> -#include <byteswap.h> -#include <gpxe/infiniband.h> -#include <gpxe/ib_smc.h> - -/** - * @file - * - * Infiniband Subnet Management Client - * - */ - -/** - * Get port information - * - * @v ibdev Infiniband device - * @v local_mad Method for issuing local MADs - * @v mad Management datagram to fill in - * @ret rc Return status code - */ -static int ib_smc_get_port_info ( struct ib_device *ibdev, - ib_local_mad_t local_mad, - union ib_mad *mad ) { - int rc; - - /* Construct MAD */ - memset ( mad, 0, sizeof ( *mad ) ); - mad->hdr.base_version = IB_MGMT_BASE_VERSION; - mad->hdr.mgmt_class = IB_MGMT_CLASS_SUBN_LID_ROUTED; - mad->hdr.class_version = 1; - mad->hdr.method = IB_MGMT_METHOD_GET; - mad->hdr.attr_id = htons ( IB_SMP_ATTR_PORT_INFO ); - mad->hdr.attr_mod = htonl ( ibdev->port ); - - if ( ( rc = local_mad ( ibdev, mad ) ) != 0 ) { - DBGC ( ibdev, "IBDEV %p could not get port info: %s\n", - ibdev, strerror ( rc ) ); - return rc; - } - return 0; -} - -/** - * Get GUID information - * - * @v ibdev Infiniband device - * @v local_mad Method for issuing local MADs - * @v mad Management datagram to fill in - * @ret rc Return status code - */ -static int ib_smc_get_guid_info ( struct ib_device *ibdev, - ib_local_mad_t local_mad, - union ib_mad *mad ) { - int rc; - - /* Construct MAD */ - memset ( mad, 0, sizeof ( *mad ) ); - mad->hdr.base_version = IB_MGMT_BASE_VERSION; - mad->hdr.mgmt_class = IB_MGMT_CLASS_SUBN_LID_ROUTED; - mad->hdr.class_version = 1; - mad->hdr.method = IB_MGMT_METHOD_GET; - mad->hdr.attr_id = htons ( IB_SMP_ATTR_GUID_INFO ); - - if ( ( rc = local_mad ( ibdev, mad ) ) != 0 ) { - DBGC ( ibdev, "IBDEV %p could not get GUID info: %s\n", - ibdev, strerror ( rc ) ); - return rc; - } - return 0; -} - -/** - * Get partition key table - * - * @v ibdev Infiniband device - * @v local_mad Method for issuing local MADs - * @v mad Management datagram to fill in - * @ret rc Return status code - */ -static int ib_smc_get_pkey_table ( struct ib_device *ibdev, - ib_local_mad_t local_mad, - union ib_mad *mad ) { - int rc; - - /* Construct MAD */ - memset ( mad, 0, sizeof ( *mad ) ); - mad->hdr.base_version = IB_MGMT_BASE_VERSION; - mad->hdr.mgmt_class = IB_MGMT_CLASS_SUBN_LID_ROUTED; - mad->hdr.class_version = 1; - mad->hdr.method = IB_MGMT_METHOD_GET; - mad->hdr.attr_id = htons ( IB_SMP_ATTR_PKEY_TABLE ); - - if ( ( rc = local_mad ( ibdev, mad ) ) != 0 ) { - DBGC ( ibdev, "IBDEV %p could not get pkey table: %s\n", - ibdev, strerror ( rc ) ); - return rc; - } - return 0; -} - -/** - * Get MAD parameters - * - * @v ibdev Infiniband device - * @v local_mad Method for issuing local MADs - * @ret rc Return status code - */ -int ib_smc_update ( struct ib_device *ibdev, ib_local_mad_t local_mad ) { - union ib_mad mad; - union ib_smp_data *smp = &mad.smp.smp_data; - int rc; - - /* Port info gives us the link state, the first half of the - * port GID and the SM LID. - */ - if ( ( rc = ib_smc_get_port_info ( ibdev, local_mad, &mad ) ) != 0 ) - return rc; - ibdev->port_state = - ( smp->port_info.link_speed_supported__port_state & 0x0f ); - memcpy ( &ibdev->gid.u.half[0], smp->port_info.gid_prefix, - sizeof ( ibdev->gid.u.half[0] ) ); - ibdev->lid = ntohs ( smp->port_info.lid ); - ibdev->sm_lid = ntohs ( smp->port_info.mastersm_lid ); - ibdev->sm_sl = ( smp->port_info.neighbour_mtu__mastersm_sl & 0xf ); - - /* GUID info gives us the second half of the port GID */ - if ( ( rc = ib_smc_get_guid_info ( ibdev, local_mad, &mad ) ) != 0 ) - return rc; - memcpy ( &ibdev->gid.u.half[1], smp->guid_info.guid[0], - sizeof ( ibdev->gid.u.half[1] ) ); - - /* Get partition key */ - if ( ( rc = ib_smc_get_pkey_table ( ibdev, local_mad, &mad ) ) != 0 ) - return rc; - ibdev->pkey = ntohs ( smp->pkey_table.pkey[0] ); - - DBGC ( ibdev, "IBDEV %p port GID is %08x:%08x:%08x:%08x\n", ibdev, - htonl ( ibdev->gid.u.dwords[0] ), - htonl ( ibdev->gid.u.dwords[1] ), - htonl ( ibdev->gid.u.dwords[2] ), - htonl ( ibdev->gid.u.dwords[3] ) ); - - return 0; -} diff --git a/gpxe/src/drivers/infiniband/linda.c b/gpxe/src/drivers/infiniband/linda.c index c5d13177..b9a7ba58 100644 --- a/gpxe/src/drivers/infiniband/linda.c +++ b/gpxe/src/drivers/infiniband/linda.c @@ -16,6 +16,8 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +FILE_LICENCE ( GPL2_OR_LATER ); + #include <stdint.h> #include <stdlib.h> #include <errno.h> @@ -28,7 +30,6 @@ #include <gpxe/bitbash.h> #include <gpxe/malloc.h> #include <gpxe/iobuf.h> -#include <gpxe/ib_sma.h> #include "linda.h" /** @@ -95,9 +96,6 @@ struct linda { struct i2c_bit_basher i2c; /** I2C serial EEPROM */ struct i2c_device eeprom; - - /** Subnet management agent */ - struct ib_sma sma; }; /*************************************************************************** @@ -233,22 +231,48 @@ static void linda_link_state_changed ( struct ib_device *ibdev ) { /* Notify Infiniband core of link state change */ ibdev->port_state = ( link_state + 1 ); - ibdev->link_width = + ibdev->link_width_active = ( link_width ? IB_LINK_WIDTH_4X : IB_LINK_WIDTH_1X ); - ibdev->link_speed = + ibdev->link_speed_active = ( link_speed ? IB_LINK_SPEED_DDR : IB_LINK_SPEED_SDR ); ib_link_state_changed ( ibdev ); } /** + * Wait for link state change to take effect + * + * @v linda Linda device + * @v new_link_state Expected link state + * @ret rc Return status code + */ +static int linda_link_state_check ( struct linda *linda, + unsigned int new_link_state ) { + struct QIB_7220_IBCStatus ibcstatus; + unsigned int link_state; + unsigned int i; + + for ( i = 0 ; i < LINDA_LINK_STATE_MAX_WAIT_US ; i++ ) { + linda_readq ( linda, &ibcstatus, QIB_7220_IBCStatus_offset ); + link_state = BIT_GET ( &ibcstatus, LinkState ); + if ( link_state == new_link_state ) + return 0; + udelay ( 1 ); + } + + DBGC ( linda, "Linda %p timed out waiting for link state %s\n", + linda, linda_link_state_text ( link_state ) ); + return -ETIMEDOUT; +} + +/** * Set port information * * @v ibdev Infiniband device - * @v port_info New port information + * @v mad Set port information MAD */ -static int linda_set_port_info ( struct ib_device *ibdev, - const struct ib_port_info *port_info ) { +static int linda_set_port_info ( struct ib_device *ibdev, union ib_mad *mad ) { struct linda *linda = ib_get_drvdata ( ibdev ); + struct ib_port_info *port_info = &mad->smp.smp_data.port_info; struct QIB_7220_IBCCtrl ibcctrl; unsigned int port_state; unsigned int link_state; @@ -262,6 +286,12 @@ static int linda_set_port_info ( struct ib_device *ibdev, linda_readq ( linda, &ibcctrl, QIB_7220_IBCCtrl_offset ); BIT_SET ( &ibcctrl, LinkCmd, link_state ); linda_writeq ( linda, &ibcctrl, QIB_7220_IBCCtrl_offset ); + + /* Wait for link state change to take effect. Ignore + * errors; the current link state will be returned via + * the GetResponse MAD. + */ + linda_link_state_check ( linda, link_state ); } /* Detect and report link state change */ @@ -270,10 +300,17 @@ static int linda_set_port_info ( struct ib_device *ibdev, return 0; } -/** Linda subnet management operations */ -static struct ib_sma_operations linda_sma_operations = { - .set_port_info = linda_set_port_info, -}; +/** + * Set partition key table + * + * @v ibdev Infiniband device + * @v mad Set partition key table MAD + */ +static int linda_set_pkey_table ( struct ib_device *ibdev __unused, + union ib_mad *mad __unused ) { + /* Nothing to do */ + return 0; +} /*************************************************************************** * @@ -859,12 +896,10 @@ static int linda_create_qp ( struct ib_device *ibdev, * * @v ibdev Infiniband device * @v qp Queue pair - * @v mod_list Modification list * @ret rc Return status code */ static int linda_modify_qp ( struct ib_device *ibdev, - struct ib_queue_pair *qp, - unsigned long mod_list __unused ) { + struct ib_queue_pair *qp ) { struct linda *linda = ib_get_drvdata ( ibdev ); /* Nothing to do; the hardware doesn't have a notion of queue @@ -1462,6 +1497,8 @@ static struct ib_device_operations linda_ib_operations = { .close = linda_close, .mcast_attach = linda_mcast_attach, .mcast_detach = linda_mcast_detach, + .set_port_info = linda_set_port_info, + .set_pkey_table = linda_set_pkey_table, }; /*************************************************************************** @@ -1601,15 +1638,15 @@ static int linda_read_eeprom ( struct linda *linda, /* Read GUID */ if ( ( rc = i2c->read ( i2c, &linda->eeprom, LINDA_EEPROM_GUID_OFFSET, - guid->bytes, sizeof ( *guid ) ) ) != 0 ) { + guid->u.bytes, sizeof ( *guid ) ) ) != 0 ) { DBGC ( linda, "Linda %p could not read GUID: %s\n", linda, strerror ( rc ) ); return rc; } DBGC2 ( linda, "Linda %p has GUID %02x:%02x:%02x:%02x:%02x:%02x:" - "%02x:%02x\n", linda, guid->bytes[0], guid->bytes[1], - guid->bytes[2], guid->bytes[3], guid->bytes[4], - guid->bytes[5], guid->bytes[6], guid->bytes[7] ); + "%02x:%02x\n", linda, guid->u.bytes[0], guid->u.bytes[1], + guid->u.bytes[2], guid->u.bytes[3], guid->u.bytes[4], + guid->u.bytes[5], guid->u.bytes[6], guid->u.bytes[7] ); /* Read serial number (debug only) */ if ( DBG_LOG ) { @@ -2219,7 +2256,7 @@ static int linda_init_ib_serdes ( struct linda *linda ) { linda_writeq ( linda, &ibcctrl, QIB_7220_IBCCtrl_offset ); /* Force SDR only to avoid needing all the DDR tuning, - * Mellanox compatibiltiy hacks etc. SDR is plenty for + * Mellanox compatibility hacks etc. SDR is plenty for * boot-time operation. */ linda_readq ( linda, &ibcddrctrl, QIB_7220_IBCDDRCtrl_offset ); @@ -2317,6 +2354,14 @@ static int linda_probe ( struct pci_device *pci, BIT_GET ( &revision, R_ChipRevMajor ), BIT_GET ( &revision, R_ChipRevMinor ) ); + /* Record link capabilities. Note that we force SDR only to + * avoid having to carry extra code for DDR tuning etc. + */ + ibdev->link_width_enabled = ibdev->link_width_supported = + ( IB_LINK_WIDTH_4X | IB_LINK_WIDTH_1X ); + ibdev->link_speed_enabled = ibdev->link_speed_supported = + IB_LINK_SPEED_SDR; + /* Initialise I2C subsystem */ if ( ( rc = linda_init_i2c ( linda ) ) != 0 ) goto err_init_i2c; @@ -2337,13 +2382,6 @@ static int linda_probe ( struct pci_device *pci, if ( ( rc = linda_init_ib_serdes ( linda ) ) != 0 ) goto err_init_ib_serdes; - /* Create the SMA */ - if ( ( rc = ib_create_sma ( &linda->sma, ibdev, - &linda_sma_operations ) ) != 0 ) - goto err_create_sma; - /* If the SMA doesn't get context 0, we're screwed */ - assert ( linda_qpn_to_ctx ( linda->sma.qp->qpn ) == 0 ); - /* Register Infiniband device */ if ( ( rc = register_ibdev ( ibdev ) ) != 0 ) { DBGC ( linda, "Linda %p could not register IB " @@ -2355,8 +2393,6 @@ static int linda_probe ( struct pci_device *pci, unregister_ibdev ( ibdev ); err_register_ibdev: - ib_destroy_sma ( &linda->sma ); - err_create_sma: linda_fini_recv ( linda ); err_init_recv: linda_fini_send ( linda ); @@ -2379,14 +2415,13 @@ static void linda_remove ( struct pci_device *pci ) { struct linda *linda = ib_get_drvdata ( ibdev ); unregister_ibdev ( ibdev ); - ib_destroy_sma ( &linda->sma ); linda_fini_recv ( linda ); linda_fini_send ( linda ); ibdev_put ( ibdev ); } static struct pci_device_id linda_nics[] = { - PCI_ROM ( 0x1077, 0x7220, "iba7220", "QLE7240/7280 HCA driver" ), + PCI_ROM ( 0x1077, 0x7220, "iba7220", "QLE7240/7280 HCA driver", 0 ), }; struct pci_driver linda_driver __pci_driver = { diff --git a/gpxe/src/drivers/infiniband/linda.h b/gpxe/src/drivers/infiniband/linda.h index dd1737a6..3068421b 100644 --- a/gpxe/src/drivers/infiniband/linda.h +++ b/gpxe/src/drivers/infiniband/linda.h @@ -19,6 +19,8 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +FILE_LICENCE ( GPL2_OR_LATER ); + /** * @file * @@ -268,4 +270,7 @@ enum linda_link_state { LINDA_LINK_STATE_ACT_DEFER = 4, }; +/** Maximum time to wait for link state changes, in us */ +#define LINDA_LINK_STATE_MAX_WAIT_US 20 + #endif /* _LINDA_H */ diff --git a/gpxe/src/drivers/infiniband/linda_fw.c b/gpxe/src/drivers/infiniband/linda_fw.c index fc5ea077..968a5f8d 100644 --- a/gpxe/src/drivers/infiniband/linda_fw.c +++ b/gpxe/src/drivers/infiniband/linda_fw.c @@ -30,6 +30,8 @@ * SOFTWARE. */ +FILE_LICENCE ( GPL2_ONLY ); + /* * This file contains the memory image from the vendor, to be copied into * the IB SERDES of the IBA7220 during initialization. diff --git a/gpxe/src/drivers/infiniband/mlx_bitops.h b/gpxe/src/drivers/infiniband/mlx_bitops.h index ec57d7b0..71a9bf1e 100644 --- a/gpxe/src/drivers/infiniband/mlx_bitops.h +++ b/gpxe/src/drivers/infiniband/mlx_bitops.h @@ -19,6 +19,8 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +FILE_LICENCE ( GPL2_OR_LATER ); + /** * @file * @@ -104,6 +106,10 @@ typedef unsigned char pseudo_bit_t; ( MLX_ASSEMBLE_1 ( _structure_st, _index, _field, _value ) | \ MLX_ASSEMBLE_5 ( _structure_st, _index, __VA_ARGS__ ) ) +#define MLX_ASSEMBLE_7( _structure_st, _index, _field, _value, ... ) \ + ( MLX_ASSEMBLE_1 ( _structure_st, _index, _field, _value ) | \ + MLX_ASSEMBLE_6 ( _structure_st, _index, __VA_ARGS__ ) ) + /* * Build native-endian (positive) dword bitmasks from named fields * @@ -133,6 +139,10 @@ typedef unsigned char pseudo_bit_t; ( MLX_MASK_1 ( _structure_st, _index, _field ) | \ MLX_MASK_5 ( _structure_st, _index, __VA_ARGS__ ) ) +#define MLX_MASK_7( _structure_st, _index, _field, ... ) \ + ( MLX_MASK_1 ( _structure_st, _index, _field ) | \ + MLX_MASK_6 ( _structure_st, _index, __VA_ARGS__ ) ) + /* * Populate big-endian dwords from named fields and values * @@ -169,6 +179,10 @@ typedef unsigned char pseudo_bit_t; MLX_FILL ( _ptr, _index, MLX_ASSEMBLE_6 ( MLX_PSEUDO_STRUCT ( _ptr ),\ _index, __VA_ARGS__ ) ) +#define MLX_FILL_7( _ptr, _index, ... ) \ + MLX_FILL ( _ptr, _index, MLX_ASSEMBLE_7 ( MLX_PSEUDO_STRUCT ( _ptr ),\ + _index, __VA_ARGS__ ) ) + /* * Modify big-endian dword using named field and value * diff --git a/gpxe/src/drivers/infiniband/qib_7220_regs.h b/gpxe/src/drivers/infiniband/qib_7220_regs.h index 0dd3c53d..0637ec80 100644 --- a/gpxe/src/drivers/infiniband/qib_7220_regs.h +++ b/gpxe/src/drivers/infiniband/qib_7220_regs.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008 QLogic Corporation. All rights reserved. + * Copyright (c) 2008, 2009 QLogic Corporation. All rights reserved. * * * This software is available to you under a choice of one of two @@ -30,12 +30,12 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * - * This file is mechanically generated. Any hand-edits will be lost. - * If not now, soon. */ +/* This file is mechanically generated from RTL. Any hand-edits will be lost! */ /* This file has been further processed by ./drivers/infiniband/qib_genbits.pl */ +FILE_LICENCE ( GPL2_ONLY ); #define QIB_7220_Revision_offset 0x00000000UL struct QIB_7220_Revision_pb { @@ -44,8 +44,8 @@ struct QIB_7220_Revision_pb { pseudo_bit_t R_Arch[8]; pseudo_bit_t R_SW[8]; pseudo_bit_t BoardID[8]; - pseudo_bit_t R_Palldium_Revcode[22]; - pseudo_bit_t R_Palladium[1]; + pseudo_bit_t R_Emulation_Revcode[22]; + pseudo_bit_t R_Emulation[1]; pseudo_bit_t R_Simulator[1]; }; struct QIB_7220_Revision { @@ -1250,7 +1250,6 @@ struct QIB_7220_SendDmaReqTagUsed_pb { pseudo_bit_t ReqTagUsed_7_0[8]; pseudo_bit_t _unused_0[8]; pseudo_bit_t Reserved[48]; - pseudo_bit_t _unused_1[8]; }; struct QIB_7220_SendDmaReqTagUsed { PSEUDO_BIT_STRUCT ( struct QIB_7220_SendDmaReqTagUsed_pb ); diff --git a/gpxe/src/drivers/infiniband/qib_genbits.pl b/gpxe/src/drivers/infiniband/qib_genbits.pl index 9eba4da5..1d5eeded 100644..100755 --- a/gpxe/src/drivers/infiniband/qib_genbits.pl +++ b/gpxe/src/drivers/infiniband/qib_genbits.pl @@ -20,6 +20,7 @@ use strict; use warnings; my $offsets = {}; +my $defaults = {}; my $structures = {}; my $structure = ""; @@ -28,8 +29,12 @@ while ( <> ) { if ( /^\#define (\S+)_OFFS (\S+)$/ ) { $structure = $1; $offsets->{$structure} = $2; + } elsif ( /^\#define ${structure}_DEF (\S+)$/ ) { + $defaults->{$structure} = $1; } elsif ( /^\#define ${structure}_(\S+)_LSB (\S+)$/ ) { $structures->{$structure}->{$1}->{LSB} = $2; + } elsif ( /^\#define ${structure}_(\S+)_MSB (\S+)$/ ) { + $structures->{$structure}->{$1}->{MSB} = $2; } elsif ( /^\#define ${structure}_(\S+)_RMASK (\S+)$/ ) { $structures->{$structure}->{$1}->{RMASK} = $2; } elsif ( /^\s*$/ ) { @@ -39,7 +44,8 @@ while ( <> ) { } } -my $data = [ map { { name => $_, offset => $offsets->{$_} }; } +my $data = [ map { { name => $_, offset => $offsets->{$_}, + default => $defaults->{$_} }; } sort { hex ( $offsets->{$a} ) <=> hex ( $offsets->{$b} ) } keys %$offsets ]; @@ -47,6 +53,7 @@ foreach my $datum ( @$data ) { next unless exists $structures->{$datum->{name}}; $structure = $structures->{$datum->{name}}; my $fields = [ map { { name => $_, lsb => $structure->{$_}->{LSB}, + msb => $structure->{$_}->{MSB}, rmask => $structure->{$_}->{RMASK} }; } sort { hex ( $structure->{$a}->{LSB} ) <=> hex ( $structure->{$b}->{LSB} ) } @@ -54,7 +61,8 @@ foreach my $datum ( @$data ) { $datum->{fields} = $fields; } -print "\n/* This file has been further processed by $0 */\n\n\n"; +print "\n/* This file has been further processed by $0 */\n\n"; +print "FILE_LICENCE ( GPL2_ONLY );\n\n"; foreach my $datum ( @$data ) { printf "#define %s_offset 0x%08xUL\n", @@ -65,11 +73,15 @@ foreach my $datum ( @$data ) { printf "struct %s_pb {\n", $datum->{name}; foreach my $field ( @{$datum->{fields}} ) { my $pad_width = ( hex ( $field->{lsb} ) - $lsb ); - die "Inconsistent LSB/RMASK in $datum->{name}\n" if $pad_width < 0; + die "Inconsistent LSB/RMASK in $datum->{name} before $field->{name}\n" + if $pad_width < 0; printf "\tpseudo_bit_t _unused_%u[%u];\n", $reserved_idx++, $pad_width if $pad_width; + $lsb += $pad_width; # Damn Perl can't cope with 64-bit hex constants my $width = 0; + die "Missing RMASK in $datum->{name}.$field->{name}\n" + unless defined $field->{rmask}; my $rmask = $field->{rmask}; while ( $rmask =~ /^(0x.+)f$/i ) { $width += 4; @@ -80,16 +92,25 @@ foreach my $datum ( @$data ) { $width++; $rmask >>= 1; } + if ( defined $field->{msb} ) { + my $msb_width = ( hex ( $field->{msb} ) - $lsb + 1 ); + $width ||= $msb_width; + die "Inconsistent LSB/MSB/RMASK in $datum->{name}.$field->{name}\n" + unless $width == $msb_width; + } printf "\tpseudo_bit_t %s[%u];\n", $field->{name}, $width; $lsb += $width; } my $pad_width = ( 64 - $lsb ); - die "Inconsistent LSB/RMASK in $datum->{name}\n" if $pad_width < 0; + die "Inconsistent LSB/RMASK in $datum->{name} final field\n" + if $pad_width < 0; printf "\tpseudo_bit_t _unused_%u[%u];\n", $reserved_idx++, $pad_width if $pad_width; printf "};\n"; printf "struct %s {\n\tPSEUDO_BIT_STRUCT ( struct %s_pb );\n};\n", $datum->{name}, $datum->{name}; } + printf "/* Default value: %s */\n", $datum->{default} + if defined $datum->{default}; print "\n"; } diff --git a/gpxe/src/drivers/net/3c509.c b/gpxe/src/drivers/net/3c509.c index ecfdec55..1c58f779 100644 --- a/gpxe/src/drivers/net/3c509.c +++ b/gpxe/src/drivers/net/3c509.c @@ -4,6 +4,8 @@ * */ +FILE_LICENCE ( BSD2 ); + #include <stdint.h> #include <stdlib.h> #include <string.h> diff --git a/gpxe/src/drivers/net/3c509.h b/gpxe/src/drivers/net/3c509.h index a06d91ea..f030d4ba 100644 --- a/gpxe/src/drivers/net/3c509.h +++ b/gpxe/src/drivers/net/3c509.h @@ -31,6 +31,8 @@ */ +FILE_LICENCE ( BSD3 ); + #include "nic.h" /* diff --git a/gpxe/src/drivers/net/3c515.c b/gpxe/src/drivers/net/3c515.c index dcfe66ba..eb9569fb 100644 --- a/gpxe/src/drivers/net/3c515.c +++ b/gpxe/src/drivers/net/3c515.c @@ -43,6 +43,7 @@ * Indent Options: indent -kr -i8 * *********************************************************/ +FILE_LICENCE ( GPL2_OR_LATER ); /* to get some global routines like printf */ #include "etherboot.h" diff --git a/gpxe/src/drivers/net/3c529.c b/gpxe/src/drivers/net/3c529.c index 31931048..42824644 100644 --- a/gpxe/src/drivers/net/3c529.c +++ b/gpxe/src/drivers/net/3c529.c @@ -3,6 +3,8 @@ * */ +FILE_LICENCE ( BSD2 ); + #include "etherboot.h" #include <gpxe/mca.h> #include <gpxe/isa.h> /* for ISA_ROM */ diff --git a/gpxe/src/drivers/net/3c595.c b/gpxe/src/drivers/net/3c595.c index 198e12e7..07c85d03 100644 --- a/gpxe/src/drivers/net/3c595.c +++ b/gpxe/src/drivers/net/3c595.c @@ -23,6 +23,8 @@ * timlegge 08-24-2003 Add Multicast Support */ +FILE_LICENCE ( BSD2 ); + /* #define EDEBUG */ #include "etherboot.h" @@ -521,20 +523,20 @@ static struct nic_operations t595_operations = { }; static struct pci_device_id t595_nics[] = { -PCI_ROM(0x10b7, 0x5900, "3c590", "3Com590"), /* Vortex 10Mbps */ -PCI_ROM(0x10b7, 0x5950, "3c595", "3Com595"), /* Vortex 100baseTx */ -PCI_ROM(0x10b7, 0x5951, "3c595-1", "3Com595"), /* Vortex 100baseT4 */ -PCI_ROM(0x10b7, 0x5952, "3c595-2", "3Com595"), /* Vortex 100base-MII */ -PCI_ROM(0x10b7, 0x9000, "3c900-tpo", "3Com900-TPO"), /* 10 Base TPO */ -PCI_ROM(0x10b7, 0x9001, "3c900-t4", "3Com900-Combo"), /* 10/100 T4 */ -PCI_ROM(0x10b7, 0x9004, "3c900b-tpo", "3Com900B-TPO"), /* 10 Base TPO */ -PCI_ROM(0x10b7, 0x9005, "3c900b-combo", "3Com900B-Combo"), /* 10 Base Combo */ -PCI_ROM(0x10b7, 0x9006, "3c900b-tpb2", "3Com900B-2/T"), /* 10 Base TP and Base2 */ -PCI_ROM(0x10b7, 0x900a, "3c900b-fl", "3Com900B-FL"), /* 10 Base F */ -PCI_ROM(0x10b7, 0x9800, "3c980-cyclone-1", "3Com980-Cyclone"), /* Cyclone */ -PCI_ROM(0x10b7, 0x9805, "3c9805-1", "3Com9805"), /* Dual Port Server Cyclone */ -PCI_ROM(0x10b7, 0x7646, "3csoho100-tx-1", "3CSOHO100-TX"), /* Hurricane */ -PCI_ROM(0x10b7, 0x4500, "3c450-1", "3Com450 HomePNA Tornado"), +PCI_ROM(0x10b7, 0x5900, "3c590", "3Com590", 0), /* Vortex 10Mbps */ +PCI_ROM(0x10b7, 0x5950, "3c595", "3Com595", 0), /* Vortex 100baseTx */ +PCI_ROM(0x10b7, 0x5951, "3c595-1", "3Com595", 0), /* Vortex 100baseT4 */ +PCI_ROM(0x10b7, 0x5952, "3c595-2", "3Com595", 0), /* Vortex 100base-MII */ +PCI_ROM(0x10b7, 0x9000, "3c900-tpo", "3Com900-TPO", 0), /* 10 Base TPO */ +PCI_ROM(0x10b7, 0x9001, "3c900-t4", "3Com900-Combo", 0), /* 10/100 T4 */ +PCI_ROM(0x10b7, 0x9004, "3c900b-tpo", "3Com900B-TPO", 0), /* 10 Base TPO */ +PCI_ROM(0x10b7, 0x9005, "3c900b-combo", "3Com900B-Combo", 0), /* 10 Base Combo */ +PCI_ROM(0x10b7, 0x9006, "3c900b-tpb2", "3Com900B-2/T", 0), /* 10 Base TP and Base2 */ +PCI_ROM(0x10b7, 0x900a, "3c900b-fl", "3Com900B-FL", 0), /* 10 Base F */ +PCI_ROM(0x10b7, 0x9800, "3c980-cyclone-1", "3Com980-Cyclone", 0), /* Cyclone */ +PCI_ROM(0x10b7, 0x9805, "3c9805-1", "3Com9805", 0), /* Dual Port Server Cyclone */ +PCI_ROM(0x10b7, 0x7646, "3csoho100-tx-1", "3CSOHO100-TX", 0), /* Hurricane */ +PCI_ROM(0x10b7, 0x4500, "3c450-1", "3Com450 HomePNA Tornado", 0), }; PCI_DRIVER ( t595_driver, t595_nics, PCI_NO_CLASS ); diff --git a/gpxe/src/drivers/net/3c595.h b/gpxe/src/drivers/net/3c595.h index 49d8d9b0..e27d204a 100644 --- a/gpxe/src/drivers/net/3c595.h +++ b/gpxe/src/drivers/net/3c595.h @@ -29,6 +29,8 @@ */ +FILE_LICENCE ( BSD3 ); + /* * Created from if_epreg.h by Fred Gray (fgray@rice.edu) to support the * 3c590 family. diff --git a/gpxe/src/drivers/net/3c5x9.c b/gpxe/src/drivers/net/3c5x9.c index 565044a1..87c9f29a 100644 --- a/gpxe/src/drivers/net/3c5x9.c +++ b/gpxe/src/drivers/net/3c5x9.c @@ -22,6 +22,8 @@ $Id$ ***************************************************************************/ +FILE_LICENCE ( BSD2 ); + /* #define EDEBUG */ #include <gpxe/ethernet.h> diff --git a/gpxe/src/drivers/net/3c90x.c b/gpxe/src/drivers/net/3c90x.c index a98e6628..9c1879bb 100644 --- a/gpxe/src/drivers/net/3c90x.c +++ b/gpxe/src/drivers/net/3c90x.c @@ -1,10 +1,19 @@ /* - * 3c90x.c -- This file implements the 3c90x driver for etherboot. Written - * by Greg Beeley, Greg.Beeley@LightSys.org. Modified by Steve Smith, - * Steve.Smith@Juno.Com. Alignment bug fix Neil Newell (nn@icenoir.net). + * 3c90x.c -- This file implements a gPXE API 3c90x driver * - * This program Copyright (C) 1999 LightSys Technology Services, Inc. - * Portions Copyright (C) 1999 Steve Smith + * Originally written for etherboot by: + * Greg Beeley, Greg.Beeley@LightSys.org + * Modified by Steve Smith, + * Steve.Smith@Juno.Com. Alignment bug fix Neil Newell (nn@icenoir.net). + * Almost totally Rewritten to use gPXE API, implementation of tx/rx ring support + * by Thomas Miletich, thomas.miletich@gmail.com + * Thanks to Marty Connor and Stefan Hajnoczi for their help and feedback, + * and to Daniel Verkamp for his help with testing. + * + * Copyright (c) 2009 Thomas Miletich + * + * Copyright (c) 1999 LightSys Technology Services, Inc. + * Portions Copyright (c) 1999 Steve Smith * * This program may be re-distributed in source or binary form, modified, * sold, or copied for any purpose, provided that the above copyright message @@ -15,1008 +24,966 @@ * PURPOSE or MERCHANTABILITY. Please read the associated documentation * "3c90x.txt" before compiling and using this driver. * - * -------- + * [ --mdc 20090313 The 3c90x.txt file is now at: + * http://etherboot.org/wiki/appnotes/3c90x_issues ] * - * Program written with the assistance of the 3com documentation for + * This program was written with the assistance of the 3com documentation for * the 3c905B-TX card, as well as with some assistance from the 3c59x * driver Donald Becker wrote for the Linux kernel, and with some assistance * from the remainder of the Etherboot distribution. * - * REVISION HISTORY: - * - * v0.10 1-26-1998 GRB Initial implementation. - * v0.90 1-27-1998 GRB System works. - * v1.00pre1 2-11-1998 GRB Got prom boot issue fixed. - * v2.0 9-24-1999 SCS Modified for 3c905 (from 3c905b code) - * Re-wrote poll and transmit for - * better error recovery and heavy - * network traffic operation - * v2.01 5-26-2003 NN Fixed driver alignment issue which - * caused system lockups if driver structures - * not 8-byte aligned. - * v2.02 11-28-2007 GSt Got polling working again by replacing - * "for(i=0;i<40000;i++);" with "mdelay(1);" - * + * Indented with unix 'indent' command: + * $ indent -kr -i8 3c90x.c */ -#include "etherboot.h" -#include "nic.h" -#include <gpxe/pci.h> +FILE_LICENCE ( BSD2 ); + +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <string.h> +#include <unistd.h> +#include <assert.h> +#include <byteswap.h> +#include <errno.h> #include <gpxe/ethernet.h> +#include <gpxe/if_ether.h> +#include <gpxe/io.h> +#include <gpxe/iobuf.h> +#include <gpxe/malloc.h> +#include <gpxe/netdevice.h> +#include <gpxe/pci.h> +#include <gpxe/timer.h> +#include <gpxe/nvs.h> -static struct nic_operations a3c90x_operations; - -#define XCVR_MAGIC (0x5A00) -/** any single transmission fails after 16 collisions or other errors - ** this is the number of times to retry the transmission -- this should - ** be plenty - **/ -#define XMIT_RETRIES 250 - -/*** Register definitions for the 3c905 ***/ -enum Registers - { - regPowerMgmtCtrl_w = 0x7c, /** 905B Revision Only **/ - regUpMaxBurst_w = 0x7a, /** 905B Revision Only **/ - regDnMaxBurst_w = 0x78, /** 905B Revision Only **/ - regDebugControl_w = 0x74, /** 905B Revision Only **/ - regDebugData_l = 0x70, /** 905B Revision Only **/ - regRealTimeCnt_l = 0x40, /** Universal **/ - regUpBurstThresh_b = 0x3e, /** 905B Revision Only **/ - regUpPoll_b = 0x3d, /** 905B Revision Only **/ - regUpPriorityThresh_b = 0x3c, /** 905B Revision Only **/ - regUpListPtr_l = 0x38, /** Universal **/ - regCountdown_w = 0x36, /** Universal **/ - regFreeTimer_w = 0x34, /** Universal **/ - regUpPktStatus_l = 0x30, /** Universal with Exception, pg 130 **/ - regTxFreeThresh_b = 0x2f, /** 90X Revision Only **/ - regDnPoll_b = 0x2d, /** 905B Revision Only **/ - regDnPriorityThresh_b = 0x2c, /** 905B Revision Only **/ - regDnBurstThresh_b = 0x2a, /** 905B Revision Only **/ - regDnListPtr_l = 0x24, /** Universal with Exception, pg 107 **/ - regDmaCtrl_l = 0x20, /** Universal with Exception, pg 106 **/ - /** **/ - regIntStatusAuto_w = 0x1e, /** 905B Revision Only **/ - regTxStatus_b = 0x1b, /** Universal with Exception, pg 113 **/ - regTimer_b = 0x1a, /** Universal **/ - regTxPktId_b = 0x18, /** 905B Revision Only **/ - regCommandIntStatus_w = 0x0e, /** Universal (Command Variations) **/ - }; - -/** following are windowed registers **/ -enum Registers7 - { - regPowerMgmtEvent_7_w = 0x0c, /** 905B Revision Only **/ - regVlanEtherType_7_w = 0x04, /** 905B Revision Only **/ - regVlanMask_7_w = 0x00, /** 905B Revision Only **/ - }; - -enum Registers6 - { - regBytesXmittedOk_6_w = 0x0c, /** Universal **/ - regBytesRcvdOk_6_w = 0x0a, /** Universal **/ - regUpperFramesOk_6_b = 0x09, /** Universal **/ - regFramesDeferred_6_b = 0x08, /** Universal **/ - regFramesRecdOk_6_b = 0x07, /** Universal with Exceptions, pg 142 **/ - regFramesXmittedOk_6_b = 0x06, /** Universal **/ - regRxOverruns_6_b = 0x05, /** Universal **/ - regLateCollisions_6_b = 0x04, /** Universal **/ - regSingleCollisions_6_b = 0x03, /** Universal **/ - regMultipleCollisions_6_b = 0x02, /** Universal **/ - regSqeErrors_6_b = 0x01, /** Universal **/ - regCarrierLost_6_b = 0x00, /** Universal **/ - }; - -enum Registers5 - { - regIndicationEnable_5_w = 0x0c, /** Universal **/ - regInterruptEnable_5_w = 0x0a, /** Universal **/ - regTxReclaimThresh_5_b = 0x09, /** 905B Revision Only **/ - regRxFilter_5_b = 0x08, /** Universal **/ - regRxEarlyThresh_5_w = 0x06, /** Universal **/ - regTxStartThresh_5_w = 0x00, /** Universal **/ - }; - -enum Registers4 - { - regUpperBytesOk_4_b = 0x0d, /** Universal **/ - regBadSSD_4_b = 0x0c, /** Universal **/ - regMediaStatus_4_w = 0x0a, /** Universal with Exceptions, pg 201 **/ - regPhysicalMgmt_4_w = 0x08, /** Universal **/ - regNetworkDiagnostic_4_w = 0x06, /** Universal with Exceptions, pg 203 **/ - regFifoDiagnostic_4_w = 0x04, /** Universal with Exceptions, pg 196 **/ - regVcoDiagnostic_4_w = 0x02, /** Undocumented? **/ - }; - -enum Registers3 - { - regTxFree_3_w = 0x0c, /** Universal **/ - regRxFree_3_w = 0x0a, /** Universal with Exceptions, pg 125 **/ - regResetMediaOptions_3_w = 0x08, /** Media Options on B Revision, **/ - /** Reset Options on Non-B Revision **/ - regMacControl_3_w = 0x06, /** Universal with Exceptions, pg 199 **/ - regMaxPktSize_3_w = 0x04, /** 905B Revision Only **/ - regInternalConfig_3_l = 0x00, /** Universal, different bit **/ - /** definitions, pg 59 **/ - }; - -enum Registers2 - { - regResetOptions_2_w = 0x0c, /** 905B Revision Only **/ - regStationMask_2_3w = 0x06, /** Universal with Exceptions, pg 127 **/ - regStationAddress_2_3w = 0x00, /** Universal with Exceptions, pg 127 **/ - }; - -enum Registers1 - { - regRxStatus_1_w = 0x0a, /** 90X Revision Only, Pg 126 **/ - }; - -enum Registers0 - { - regEepromData_0_w = 0x0c, /** Universal **/ - regEepromCommand_0_w = 0x0a, /** Universal **/ - regBiosRomData_0_b = 0x08, /** 905B Revision Only **/ - regBiosRomAddr_0_l = 0x04, /** 905B Revision Only **/ - }; - - -/*** The names for the eight register windows ***/ -enum Windows - { - winPowerVlan7 = 0x07, - winStatistics6 = 0x06, - winTxRxControl5 = 0x05, - winDiagnostics4 = 0x04, - winTxRxOptions3 = 0x03, - winAddressing2 = 0x02, - winUnused1 = 0x01, - winEepromBios0 = 0x00, - }; - - -/*** Command definitions for the 3c90X ***/ -enum Commands - { - cmdGlobalReset = 0x00, /** Universal with Exceptions, pg 151 **/ - cmdSelectRegisterWindow = 0x01, /** Universal **/ - cmdEnableDcConverter = 0x02, /** **/ - cmdRxDisable = 0x03, /** **/ - cmdRxEnable = 0x04, /** Universal **/ - cmdRxReset = 0x05, /** Universal **/ - cmdStallCtl = 0x06, /** Universal **/ - cmdTxEnable = 0x09, /** Universal **/ - cmdTxDisable = 0x0A, /** **/ - cmdTxReset = 0x0B, /** Universal **/ - cmdRequestInterrupt = 0x0C, /** **/ - cmdAcknowledgeInterrupt = 0x0D, /** Universal **/ - cmdSetInterruptEnable = 0x0E, /** Universal **/ - cmdSetIndicationEnable = 0x0F, /** Universal **/ - cmdSetRxFilter = 0x10, /** Universal **/ - cmdSetRxEarlyThresh = 0x11, /** **/ - cmdSetTxStartThresh = 0x13, /** **/ - cmdStatisticsEnable = 0x15, /** **/ - cmdStatisticsDisable = 0x16, /** **/ - cmdDisableDcConverter = 0x17, /** **/ - cmdSetTxReclaimThresh = 0x18, /** **/ - cmdSetHashFilterBit = 0x19, /** **/ - }; - - -/*** Values for int status register bitmask **/ -#define INT_INTERRUPTLATCH (1<<0) -#define INT_HOSTERROR (1<<1) -#define INT_TXCOMPLETE (1<<2) -#define INT_RXCOMPLETE (1<<4) -#define INT_RXEARLY (1<<5) -#define INT_INTREQUESTED (1<<6) -#define INT_UPDATESTATS (1<<7) -#define INT_LINKEVENT (1<<8) -#define INT_DNCOMPLETE (1<<9) -#define INT_UPCOMPLETE (1<<10) -#define INT_CMDINPROGRESS (1<<12) -#define INT_WINDOWNUMBER (7<<13) - - -/*** TX descriptor ***/ -typedef struct - { - unsigned int DnNextPtr; - unsigned int FrameStartHeader; - unsigned int HdrAddr; - unsigned int HdrLength; - unsigned int DataAddr; - unsigned int DataLength; - } - TXD __attribute__ ((aligned(8))); /* 64-bit aligned for bus mastering */ - -/*** RX descriptor ***/ -typedef struct - { - unsigned int UpNextPtr; - unsigned int UpPktStatus; - unsigned int DataAddr; - unsigned int DataLength; - } - RXD __attribute__ ((aligned(8))); /* 64-bit aligned for bus mastering */ - -/*** Global variables ***/ -static struct - { - unsigned int is3c556; - unsigned char isBrev; - unsigned char CurrentWindow; - unsigned int IOAddr; - unsigned char HWAddr[ETH_ALEN]; - TXD TransmitDPD; - RXD ReceiveUPD; - } - INF_3C90X; - - -/*** a3c90x_internal_IssueCommand: sends a command to the 3c90x card - ***/ -static int -a3c90x_internal_IssueCommand(int ioaddr, int cmd, int param) - { - unsigned int val; +#include "3c90x.h" + +/** + * a3c90x_internal_IssueCommand: sends a command to the 3c90x card + * and waits for it's completion + * + * @v ioaddr IOAddress of the NIC + * @v cmd Command to be issued + * @v param Command parameter + */ +static void a3c90x_internal_IssueCommand(int ioaddr, int cmd, int param) +{ + unsigned int val = (cmd << 11) | param; + int cnt = 0; - /** Build the cmd. **/ - val = cmd; - val <<= 11; - val |= param; + DBGP("a3c90x_internal_IssueCommand\n"); - /** Send the cmd to the cmd register **/ + /* Send the cmd to the cmd register */ outw(val, ioaddr + regCommandIntStatus_w); - /** Wait for the cmd to complete, if necessary **/ - while (inw(ioaddr + regCommandIntStatus_w) & INT_CMDINPROGRESS); + /* Wait for the cmd to complete */ + for (cnt = 0; cnt < 100000; cnt++) { + if (inw(ioaddr + regCommandIntStatus_w) & INT_CMDINPROGRESS) { + continue; + } else { + DBG2("Command 0x%04X finished in time. cnt = %d.\n", cmd, cnt); + return; + } + } - return 0; - } + DBG("Command 0x%04X DID NOT finish in time. cnt = %d.\n", cmd, cnt); +} +/** + * a3c90x_internal_SetWindow: selects a register window set. + * + * @v inf_3c90x private NIC data + * @v window window to be selected + */ +static void a3c90x_internal_SetWindow(struct INF_3C90X *inf_3c90x, int window) +{ + DBGP("a3c90x_internal_SetWindow\n"); + /* Window already as set? */ + if (inf_3c90x->CurrentWindow == window) + return; -/*** a3c90x_internal_SetWindow: selects a register window set. - ***/ -static int -a3c90x_internal_SetWindow(int ioaddr, int window) - { - - /** Window already as set? **/ - if (INF_3C90X.CurrentWindow == window) return 0; - - /** Issue the window command. **/ - a3c90x_internal_IssueCommand(ioaddr, cmdSelectRegisterWindow, window); - INF_3C90X.CurrentWindow = window; - - return 0; - } - - -/*** a3c90x_internal_ReadEeprom - read data from the serial eeprom. - ***/ -static unsigned short -a3c90x_internal_ReadEeprom(int ioaddr, int address) - { - unsigned short val; - - /** Select correct window **/ - a3c90x_internal_SetWindow(INF_3C90X.IOAddr, winEepromBios0); - - /** Make sure the eeprom isn't busy **/ - while((1<<15) & inw(ioaddr + regEepromCommand_0_w)); - - /** Read the value. **/ - if (INF_3C90X.is3c556) - { - outw(address + (0x230), ioaddr + regEepromCommand_0_w); - } - else - { - outw(address + ((0x02)<<6), ioaddr + regEepromCommand_0_w); - } - - while((1<<15) & inw(ioaddr + regEepromCommand_0_w)); - val = inw(ioaddr + regEepromData_0_w); - - return val; - } - - -#if 0 -/*** a3c90x_internal_WriteEepromWord - write a physical word of - *** data to the onboard serial eeprom (not the BIOS prom, but the - *** nvram in the card that stores, among other things, the MAC - *** address). - ***/ -static int -a3c90x_internal_WriteEepromWord(int ioaddr, int address, unsigned short value) - { - /** Select register window **/ - a3c90x_internal_SetWindow(ioaddr, winEepromBios0); - - /** Verify Eeprom not busy **/ - while((1<<15) & inw(ioaddr + regEepromCommand_0_w)); - - /** Issue WriteEnable, and wait for completion. **/ - outw(0x30, ioaddr + regEepromCommand_0_w); - while((1<<15) & inw(ioaddr + regEepromCommand_0_w)); - - /** Issue EraseRegister, and wait for completion. **/ - outw(address + ((0x03)<<6), ioaddr + regEepromCommand_0_w); - while((1<<15) & inw(ioaddr + regEepromCommand_0_w)); - - /** Send the new data to the eeprom, and wait for completion. **/ - outw(value, ioaddr + regEepromData_0_w); - outw(0x30, ioaddr + regEepromCommand_0_w); - while((1<<15) & inw(ioaddr + regEepromCommand_0_w)); - - /** Burn the new data into the eeprom, and wait for completion. **/ - outw(address + ((0x01)<<6), ioaddr + regEepromCommand_0_w); - while((1<<15) & inw(ioaddr + regEepromCommand_0_w)); - - return 0; - } -#endif - -#if 0 -/*** a3c90x_internal_WriteEeprom - write data to the serial eeprom, - *** and re-compute the eeprom checksum. - ***/ -static int -a3c90x_internal_WriteEeprom(int ioaddr, int address, unsigned short value) - { - int cksum = 0,v; - int i; - int maxAddress, cksumAddress; - - if (INF_3C90X.isBrev) - { - maxAddress=0x1f; - cksumAddress=0x20; - } - else - { - maxAddress=0x16; - cksumAddress=0x17; - } - - /** Write the value. **/ - if (a3c90x_internal_WriteEepromWord(ioaddr, address, value) == -1) - return -1; - - /** Recompute the checksum. **/ - for(i=0;i<=maxAddress;i++) - { - v = a3c90x_internal_ReadEeprom(ioaddr, i); - cksum ^= (v & 0xFF); - cksum ^= ((v>>8) & 0xFF); - } - /** Write the checksum to the location in the eeprom **/ - if (a3c90x_internal_WriteEepromWord(ioaddr, cksumAddress, cksum) == -1) - return -1; - - return 0; - } -#endif - -/*** a3c90x_reset: exported function that resets the card to its default - *** state. This is so the Linux driver can re-set the card up the way - *** it wants to. If CFG_3C90X_PRESERVE_XCVR is defined, then the reset will - *** not alter the selected transceiver that we used to download the boot - *** image. - ***/ -static void a3c90x_reset(void) - { -#ifdef CFG_3C90X_PRESERVE_XCVR - int cfg; - /** Read the current InternalConfig value. **/ - a3c90x_internal_SetWindow(INF_3C90X.IOAddr, winTxRxOptions3); - cfg = inl(INF_3C90X.IOAddr + regInternalConfig_3_l); -#endif - - /** Send the reset command to the card **/ - printf("Issuing RESET:\n"); - a3c90x_internal_IssueCommand(INF_3C90X.IOAddr, cmdGlobalReset, 0); - - /** wait for reset command to complete **/ - while (inw(INF_3C90X.IOAddr + regCommandIntStatus_w) & INT_CMDINPROGRESS); - - /** global reset command resets station mask, non-B revision cards - ** require explicit reset of values - **/ - a3c90x_internal_SetWindow(INF_3C90X.IOAddr, winAddressing2); - outw(0, INF_3C90X.IOAddr + regStationMask_2_3w+0); - outw(0, INF_3C90X.IOAddr + regStationMask_2_3w+2); - outw(0, INF_3C90X.IOAddr + regStationMask_2_3w+4); - -#ifdef CFG_3C90X_PRESERVE_XCVR - /** Re-set the original InternalConfig value from before reset **/ - a3c90x_internal_SetWindow(INF_3C90X.IOAddr, winTxRxOptions3); - outl(cfg, INF_3C90X.IOAddr + regInternalConfig_3_l); - - /** enable DC converter for 10-Base-T **/ - if ((cfg&0x0300) == 0x0300) - { - a3c90x_internal_IssueCommand(INF_3C90X.IOAddr, cmdEnableDcConverter, 0); - } -#endif - - /** Issue transmit reset, wait for command completion **/ - a3c90x_internal_IssueCommand(INF_3C90X.IOAddr, cmdTxReset, 0); - while (inw(INF_3C90X.IOAddr + regCommandIntStatus_w) & INT_CMDINPROGRESS) - ; - if (! INF_3C90X.isBrev) - outb(0x01, INF_3C90X.IOAddr + regTxFreeThresh_b); - a3c90x_internal_IssueCommand(INF_3C90X.IOAddr, cmdTxEnable, 0); - - /** - ** reset of the receiver on B-revision cards re-negotiates the link - ** takes several seconds (a computer eternity) - **/ - if (INF_3C90X.isBrev) - a3c90x_internal_IssueCommand(INF_3C90X.IOAddr, cmdRxReset, 0x04); - else - a3c90x_internal_IssueCommand(INF_3C90X.IOAddr, cmdRxReset, 0x00); - while (inw(INF_3C90X.IOAddr + regCommandIntStatus_w) & INT_CMDINPROGRESS); - ; - a3c90x_internal_IssueCommand(INF_3C90X.IOAddr, cmdRxEnable, 0); - - a3c90x_internal_IssueCommand(INF_3C90X.IOAddr, - cmdSetInterruptEnable, 0); - /** enable rxComplete and txComplete **/ - a3c90x_internal_IssueCommand(INF_3C90X.IOAddr, - cmdSetIndicationEnable, 0x0014); - /** acknowledge any pending status flags **/ - a3c90x_internal_IssueCommand(INF_3C90X.IOAddr, - cmdAcknowledgeInterrupt, 0x661); - - return; - } - - - -/*** a3c90x_transmit: exported function that transmits a packet. Does not - *** return any particular status. Parameters are: - *** d[6] - destination address, ethernet; - *** t - protocol type (ARP, IP, etc); - *** s - size of the non-header part of the packet that needs transmitted; - *** p - the pointer to the packet data itself. - ***/ -static void -a3c90x_transmit(struct nic *nic __unused, const char *d, unsigned int t, - unsigned int s, const char *p) - { - - struct eth_hdr - { - unsigned char dst_addr[ETH_ALEN]; - unsigned char src_addr[ETH_ALEN]; - unsigned short type; - } hdr; - - unsigned char status; - unsigned i, retries; - unsigned long ct; - - for (retries=0; retries < XMIT_RETRIES ; retries++) - { - /** Stall the download engine **/ - a3c90x_internal_IssueCommand(INF_3C90X.IOAddr, cmdStallCtl, 2); - - /** Make sure the card is not waiting on us **/ - inw(INF_3C90X.IOAddr + regCommandIntStatus_w); - inw(INF_3C90X.IOAddr + regCommandIntStatus_w); - - while (inw(INF_3C90X.IOAddr+regCommandIntStatus_w) & - INT_CMDINPROGRESS) - ; - - /** Set the ethernet packet type **/ - hdr.type = htons(t); - - /** Copy the destination address **/ - memcpy(hdr.dst_addr, d, ETH_ALEN); - - /** Copy our MAC address **/ - memcpy(hdr.src_addr, INF_3C90X.HWAddr, ETH_ALEN); - - /** Setup the DPD (download descriptor) **/ - INF_3C90X.TransmitDPD.DnNextPtr = 0; - /** set notification for transmission completion (bit 15) **/ - INF_3C90X.TransmitDPD.FrameStartHeader = (s + sizeof(hdr)) | 0x8000; - INF_3C90X.TransmitDPD.HdrAddr = virt_to_bus(&hdr); - INF_3C90X.TransmitDPD.HdrLength = sizeof(hdr); - INF_3C90X.TransmitDPD.DataAddr = virt_to_bus(p); - INF_3C90X.TransmitDPD.DataLength = s + (1<<31); - - /** Send the packet **/ - outl(virt_to_bus(&(INF_3C90X.TransmitDPD)), - INF_3C90X.IOAddr + regDnListPtr_l); - - /** End Stall and Wait for upload to complete. **/ - a3c90x_internal_IssueCommand(INF_3C90X.IOAddr, cmdStallCtl, 3); - while(inl(INF_3C90X.IOAddr + regDnListPtr_l) != 0) - ; - - /** Wait for NIC Transmit to Complete **/ - ct = currticks(); - - while (!(inw(INF_3C90X.IOAddr + regCommandIntStatus_w)&0x0004) && - ct + 10*1000 < currticks()); - ; - - if (!(inw(INF_3C90X.IOAddr + regCommandIntStatus_w)&0x0004)) - { - printf("3C90X: Tx Timeout\n"); - continue; - } - - status = inb(INF_3C90X.IOAddr + regTxStatus_b); - - /** acknowledge transmit interrupt by writing status **/ - outb(0x00, INF_3C90X.IOAddr + regTxStatus_b); - - /** successful completion (sans "interrupt Requested" bit) **/ - if ((status & 0xbf) == 0x80) - return; - - printf("3C90X: Status (%hhX)\n", status); - /** check error codes **/ - if (status & 0x02) - { - printf("3C90X: Tx Reclaim Error (%hhX)\n", status); - a3c90x_reset(); - } - else if (status & 0x04) - { - printf("3C90X: Tx Status Overflow (%hhX)\n", status); - for (i=0; i<32; i++) - outb(0x00, INF_3C90X.IOAddr + regTxStatus_b); - /** must re-enable after max collisions before re-issuing tx **/ - a3c90x_internal_IssueCommand(INF_3C90X.IOAddr, cmdTxEnable, 0); - } - else if (status & 0x08) - { - printf("3C90X: Tx Max Collisions (%hhX)\n", status); - /** must re-enable after max collisions before re-issuing tx **/ - a3c90x_internal_IssueCommand(INF_3C90X.IOAddr, cmdTxEnable, 0); - } - else if (status & 0x10) - { - printf("3C90X: Tx Underrun (%hhX)\n", status); - a3c90x_reset(); - } - else if (status & 0x20) - { - printf("3C90X: Tx Jabber (%hhX)\n", status); - a3c90x_reset(); - } - else if ((status & 0x80) != 0x80) - { - printf("3C90X: Internal Error - Incomplete Transmission (%hhX)\n", - status); - a3c90x_reset(); - } - } + /* Issue the window command. */ + a3c90x_internal_IssueCommand(inf_3c90x->IOAddr, + cmdSelectRegisterWindow, window); + inf_3c90x->CurrentWindow = window; - /** failed after RETRY attempts **/ - printf("Failed to send after %d retries\n", retries); - return; + return; +} - } +static void a3c90x_internal_WaitForEeprom(struct INF_3C90X *inf_3c90x) +{ + int cnt = 0; + DBGP("a3c90x_internal_WaitForEeprom\n"); + while (eepromBusy & inw(inf_3c90x->IOAddr + regEepromCommand_0_w)) { + if (cnt == EEPROM_TIMEOUT) { + DBG("Read from eeprom failed: timeout\n"); + return; + } + udelay(1); + cnt++; + } +} -/*** a3c90x_poll: exported routine that waits for a certain length of time - *** for a packet, and if it sees none, returns 0. This routine should - *** copy the packet to nic->packet if it gets a packet and set the size - *** in nic->packetlen. Return 1 if a packet was found. - ***/ +/** + * a3c90x_internal_ReadEeprom - nvs routine to read eeprom data + * We only support reading one word(2 byte). The nvs subsystem will make sure + * that the routine will never be called with len != 2. + * + * @v nvs nvs data. + * @v address eeprom address to read data from. + * @v data data is put here. + * @v len number of bytes to read. + */ static int -a3c90x_poll(struct nic *nic, int retrieve) - { - int errcode; +a3c90x_internal_ReadEeprom(struct nvs_device *nvs, unsigned int address, void *data, size_t len) +{ + unsigned short *dest = (unsigned short *) data; + struct INF_3C90X *inf_3c90x = + container_of(nvs, struct INF_3C90X, nvs); + + DBGP("a3c90x_internal_ReadEeprom\n"); + + /* we support reading 2 bytes only */ + assert(len == 2); + + /* Select correct window */ + a3c90x_internal_SetWindow(inf_3c90x, winEepromBios0); + + /* set eepromRead bits in command sent to NIC */ + address += (inf_3c90x->is3c556 ? eepromRead_556 : eepromRead); + + a3c90x_internal_WaitForEeprom(inf_3c90x); + /* send address to NIC */ + outw(address, inf_3c90x->IOAddr + regEepromCommand_0_w); + a3c90x_internal_WaitForEeprom(inf_3c90x); + + /* read value */ + *dest = inw(inf_3c90x->IOAddr + regEepromData_0_w); - if (!(inw(INF_3C90X.IOAddr + regCommandIntStatus_w)&0x0010)) - { return 0; +} + +/** + * a3c90x_internal_WriteEeprom - nvs routine to write eeprom data + * currently not implemented + * + * @v nvs nvs data. + * @v address eeprom address to read data from. + * @v data data is put here. + * @v len number of bytes to read. + */ +static int +a3c90x_internal_WriteEeprom(struct nvs_device *nvs __unused, + unsigned int address __unused, + const void *data __unused, size_t len __unused) +{ + return -ENOTSUP; +} + +static void a3c90x_internal_ReadEepromContents(struct INF_3C90X *inf_3c90x) +{ + int eeprom_size = (inf_3c90x->isBrev ? 0x20 : 0x17) * 2; + + DBGP("a3c90x_internal_ReadEepromContents\n"); + + nvs_read(&inf_3c90x->nvs, 0, inf_3c90x->eeprom, eeprom_size); +} + +/** + * a3c90x_reset: exported function that resets the card to its default + * state. This is so the Linux driver can re-set the card up the way + * it wants to. If CFG_3C90X_PRESERVE_XCVR is defined, then the reset will + * not alter the selected transceiver that we used to download the boot + * image. + * + * @v inf_3c90x Private NIC data + */ +static void a3c90x_reset(struct INF_3C90X *inf_3c90x) +{ + DBGP("a3c90x_reset\n"); + /* Send the reset command to the card */ + DBG("3c90x: Issuing RESET\n"); + a3c90x_internal_IssueCommand(inf_3c90x->IOAddr, cmdGlobalReset, 0); + + /* global reset command resets station mask, non-B revision cards + * require explicit reset of values + */ + a3c90x_internal_SetWindow(inf_3c90x, winAddressing2); + outw(0, inf_3c90x->IOAddr + regStationMask_2_3w + 0); + outw(0, inf_3c90x->IOAddr + regStationMask_2_3w + 2); + outw(0, inf_3c90x->IOAddr + regStationMask_2_3w + 4); + + /* Issue transmit reset, wait for command completion */ + a3c90x_internal_IssueCommand(inf_3c90x->IOAddr, cmdTxReset, 0); + + a3c90x_internal_IssueCommand(inf_3c90x->IOAddr, cmdTxEnable, 0); + + /* + * reset of the receiver on B-revision cards re-negotiates the link + * takes several seconds (a computer eternity) + */ + a3c90x_internal_IssueCommand(inf_3c90x->IOAddr, cmdRxReset, + inf_3c90x->isBrev ? 0x04 : 0x00); + + a3c90x_internal_IssueCommand(inf_3c90x->IOAddr, cmdRxEnable, 0); + + a3c90x_internal_IssueCommand(inf_3c90x->IOAddr, + cmdSetInterruptEnable, 0); + /* enable rxComplete and txComplete */ + a3c90x_internal_IssueCommand(inf_3c90x->IOAddr, + cmdSetIndicationEnable, + INT_TXCOMPLETE | INT_UPCOMPLETE); + /* acknowledge any pending status flags */ + a3c90x_internal_IssueCommand(inf_3c90x->IOAddr, + cmdAcknowledgeInterrupt, 0x661); + + return; +} + +/** + * a3c90x_setup_tx_ring - Allocates TX ring, initialize tx_desc values + * + * @v p Private NIC data + * + * @ret Returns 0 on success, negative on failure + */ +static int a3c90x_setup_tx_ring(struct INF_3C90X *p) +{ + DBGP("a3c90x_setup_tx_ring\n"); + p->tx_ring = + malloc_dma(TX_RING_SIZE * sizeof(struct TXD), TX_RING_ALIGN); + + if (!p->tx_ring) { + DBG("Could not allocate TX-ring\n"); + return -ENOMEM; } - if ( ! retrieve ) return 1; - - /** we don't need to acknowledge rxComplete -- the upload engine - ** does it for us. - **/ - - /** Build the up-load descriptor **/ - INF_3C90X.ReceiveUPD.UpNextPtr = 0; - INF_3C90X.ReceiveUPD.UpPktStatus = 0; - INF_3C90X.ReceiveUPD.DataAddr = virt_to_bus(nic->packet); - INF_3C90X.ReceiveUPD.DataLength = 1536 + (1<<31); - - /** Submit the upload descriptor to the NIC **/ - outl(virt_to_bus(&(INF_3C90X.ReceiveUPD)), - INF_3C90X.IOAddr + regUpListPtr_l); - - /** Wait for upload completion (upComplete(15) or upError (14)) **/ - mdelay(1); - while((INF_3C90X.ReceiveUPD.UpPktStatus & ((1<<14) | (1<<15))) == 0) - mdelay(1); - - /** Check for Error (else we have good packet) **/ - if (INF_3C90X.ReceiveUPD.UpPktStatus & (1<<14)) - { - errcode = INF_3C90X.ReceiveUPD.UpPktStatus; - if (errcode & (1<<16)) - printf("3C90X: Rx Overrun (%hX)\n",errcode>>16); - else if (errcode & (1<<17)) - printf("3C90X: Runt Frame (%hX)\n",errcode>>16); - else if (errcode & (1<<18)) - printf("3C90X: Alignment Error (%hX)\n",errcode>>16); - else if (errcode & (1<<19)) - printf("3C90X: CRC Error (%hX)\n",errcode>>16); - else if (errcode & (1<<20)) - printf("3C90X: Oversized Frame (%hX)\n",errcode>>16); - else - printf("3C90X: Packet error (%hX)\n",errcode>>16); + memset(p->tx_ring, 0, TX_RING_SIZE * sizeof(struct TXD)); + p->tx_cur = 0; + p->tx_cnt = 0; + p->tx_tail = 0; + return 0; - } +} - /** Ok, got packet. Set length in nic->packetlen. **/ - nic->packetlen = (INF_3C90X.ReceiveUPD.UpPktStatus & 0x1FFF); +/** + * a3c90x_process_tx_packets - Checks for successfully sent packets, + * reports them to gPXE with netdev_tx_complete(); + * + * @v netdev Network device info + */ +static void a3c90x_process_tx_packets(struct net_device *netdev) +{ + struct INF_3C90X *p = netdev_priv(netdev); + unsigned int downlist_ptr; - return 1; - } + DBGP("a3c90x_process_tx_packets\n"); + DBG(" tx_cnt: %d\n", p->tx_cnt); + while (p->tx_tail != p->tx_cur) { -/*** a3c90x_disable: exported routine to disable the card. What's this for? - *** the eepro100.c driver didn't have one, so I just left this one empty too. - *** Ideas anyone? - *** Must turn off receiver at least so stray packets will not corrupt memory - *** [Ken] - ***/ -static void -a3c90x_disable ( struct nic *nic __unused ) { - a3c90x_reset(); - /* Disable the receiver and transmitter. */ - outw(cmdRxDisable, INF_3C90X.IOAddr + regCommandIntStatus_w); - outw(cmdTxDisable, INF_3C90X.IOAddr + regCommandIntStatus_w); + downlist_ptr = inl(p->IOAddr + regDnListPtr_l); + + DBG(" downlist_ptr: %#08x\n", downlist_ptr); + DBG(" tx_tail: %d tx_cur: %d\n", p->tx_tail, p->tx_cur); + + /* NIC is currently working on this tx desc */ + if(downlist_ptr == virt_to_bus(p->tx_ring + p->tx_tail)) + return; + + netdev_tx_complete(netdev, p->tx_iobuf[p->tx_tail]); + + DBG("transmitted packet\n"); + DBG(" size: %zd\n", iob_len(p->tx_iobuf[p->tx_tail])); + + p->tx_tail = (p->tx_tail + 1) % TX_RING_SIZE; + p->tx_cnt--; + } } -static void a3c90x_irq(struct nic *nic __unused, irq_action_t action __unused) +static void a3c90x_free_tx_ring(struct INF_3C90X *p) { - switch ( action ) { - case DISABLE : - break; - case ENABLE : - break; - case FORCE : - break; - } + DBGP("a3c90x_free_tx_ring\n"); + + free_dma(p->tx_ring, TX_RING_SIZE * sizeof(struct TXD)); + p->tx_ring = NULL; + /* io_buffers are free()ed by netdev_tx_complete[,_err]() */ } -/*** a3c90x_probe: exported routine to probe for the 3c905 card and perform - *** initialization. If this routine is called, the pci functions did find the - *** card. We just have to init it here. - ***/ -static int a3c90x_probe ( struct nic *nic, struct pci_device *pci ) { - - int i, c; - unsigned short eeprom[0x21]; - unsigned int cfg; - unsigned int mopt; - unsigned int mstat; - unsigned short linktype; -#define HWADDR_OFFSET 10 - - if (pci->ioaddr == 0) - return 0; - - adjust_pci_device(pci); - - nic->ioaddr = pci->ioaddr; - nic->irqno = 0; - - INF_3C90X.is3c556 = (pci->device == 0x6055); - INF_3C90X.IOAddr = pci->ioaddr & ~3; - INF_3C90X.CurrentWindow = 255; - switch (a3c90x_internal_ReadEeprom(INF_3C90X.IOAddr, 0x03)) - { - case 0x9000: /** 10 Base TPO **/ - case 0x9001: /** 10/100 T4 **/ - case 0x9050: /** 10/100 TPO **/ - case 0x9051: /** 10 Base Combo **/ - INF_3C90X.isBrev = 0; - break; +/** + * a3c90x_transmit - Transmits a packet. + * + * @v netdev Network device info + * @v iob io_buffer containing the data to be send + * + * @ret Returns 0 on success, negative on failure + */ +static int a3c90x_transmit(struct net_device *netdev, + struct io_buffer *iob) +{ + struct INF_3C90X *inf_3c90x = netdev_priv(netdev); + struct TXD *tx_cur_desc; + struct TXD *tx_prev_desc; - case 0x9004: /** 10 Base TPO **/ - case 0x9005: /** 10 Base Combo **/ - case 0x9006: /** 10 Base TPO and Base2 **/ - case 0x900A: /** 10 Base FL **/ - case 0x9055: /** 10/100 TPO **/ - case 0x9056: /** 10/100 T4 **/ - case 0x905A: /** 10 Base FX **/ - default: - INF_3C90X.isBrev = 1; - break; + unsigned int len; + unsigned int downlist_ptr; + + DBGP("a3c90x_transmit\n"); + + if (inf_3c90x->tx_cnt == TX_RING_SIZE) { + DBG("TX-Ring overflow\n"); + return -ENOBUFS; } - /** Load the EEPROM contents **/ - if (INF_3C90X.isBrev) - { - for(i=0;i<=0x20;i++) - { - eeprom[i] = a3c90x_internal_ReadEeprom(INF_3C90X.IOAddr, i); - } - -#ifdef CFG_3C90X_BOOTROM_FIX - /** Set xcvrSelect in InternalConfig in eeprom. **/ - /* only necessary for 3c905b revision cards with boot PROM bug!!! */ - a3c90x_internal_WriteEeprom(INF_3C90X.IOAddr, 0x13, 0x0160); -#endif - -#ifdef CFG_3C90X_XCVR - if (CFG_3C90X_XCVR == 255) - { - /** Clear the LanWorks register **/ - a3c90x_internal_WriteEeprom(INF_3C90X.IOAddr, 0x16, 0); - } - else - { - /** Set the selected permanent-xcvrSelect in the - ** LanWorks register - **/ - a3c90x_internal_WriteEeprom(INF_3C90X.IOAddr, 0x16, - XCVR_MAGIC + ((CFG_3C90X_XCVR) & 0x000F)); - } -#endif + inf_3c90x->tx_iobuf[inf_3c90x->tx_cur] = iob; + tx_cur_desc = inf_3c90x->tx_ring + inf_3c90x->tx_cur; + + tx_prev_desc = inf_3c90x->tx_ring + + (((inf_3c90x->tx_cur + TX_RING_SIZE) - 1) % TX_RING_SIZE); + + len = iob_len(iob); + + /* Setup the DPD (download descriptor) */ + tx_cur_desc->DnNextPtr = 0; + + /* FrameStartHeader differs in 90x and >= 90xB + * It contains length in 90x and a round up boundary and packet ID for + * 90xB and 90xC. We can leave this to 0 for 90xB and 90xC. + */ + tx_cur_desc->FrameStartHeader = + fshTxIndicate | (inf_3c90x->isBrev ? 0x00 : len); + + tx_cur_desc->DataAddr = virt_to_bus(iob->data); + tx_cur_desc->DataLength = len | downLastFrag; + + /* We have to stall the download engine, so the NIC won't access the + * tx descriptor while we modify it. There is a way around this + * from revision B and upwards. To stay compatible with older revisions + * we don't use it here. + */ + a3c90x_internal_IssueCommand(inf_3c90x->IOAddr, cmdStallCtl, + dnStall); + + tx_prev_desc->DnNextPtr = virt_to_bus(tx_cur_desc); + + downlist_ptr = inl(inf_3c90x->IOAddr + regDnListPtr_l); + if (downlist_ptr == 0) { + /* currently no DownList, sending a new one */ + outl(virt_to_bus(tx_cur_desc), + inf_3c90x->IOAddr + regDnListPtr_l); } - else - { - for(i=0;i<=0x17;i++) - { - eeprom[i] = a3c90x_internal_ReadEeprom(INF_3C90X.IOAddr, i); - } + + /* End Stall */ + a3c90x_internal_IssueCommand(inf_3c90x->IOAddr, cmdStallCtl, + dnUnStall); + + inf_3c90x->tx_cur = (inf_3c90x->tx_cur + 1) % TX_RING_SIZE; + inf_3c90x->tx_cnt++; + + return 0; +} + +/** + * a3c90x_prepare_rx_desc - fills the rx desc with initial data + * + * @v p NIC private data + * @v index Index for rx_iobuf and rx_ring array + */ + +static void a3c90x_prepare_rx_desc(struct INF_3C90X *p, unsigned int index) +{ + DBGP("a3c90x_prepare_rx_desc\n"); + DBG("Populating rx_desc %d\n", index); + + /* We have to stall the upload engine, so the NIC won't access the + * rx descriptor while we modify it. There is a way around this + * from revision B and upwards. To stay compatible with older revisions + * we don't use it here. + */ + a3c90x_internal_IssueCommand(p->IOAddr, cmdStallCtl, upStall); + + p->rx_ring[index].DataAddr = virt_to_bus(p->rx_iobuf[index]->data); + p->rx_ring[index].DataLength = RX_BUF_SIZE | upLastFrag; + p->rx_ring[index].UpPktStatus = 0; + + /* unstall upload engine */ + a3c90x_internal_IssueCommand(p->IOAddr, cmdStallCtl, upUnStall); +} + +/** + * a3c90x_refill_rx_ring -checks every entry in the rx ring and reallocates + * them as necessary. Then it calls a3c90x_prepare_rx_desc to fill the rx desc + * with initial data. + * + * @v p NIC private data + */ +static void a3c90x_refill_rx_ring(struct INF_3C90X *p) +{ + int i; + unsigned int status; + struct RXD *rx_cur_desc; + + DBGP("a3c90x_refill_rx_ring\n"); + + for (i = 0; i < RX_RING_SIZE; i++) { + rx_cur_desc = p->rx_ring + i; + status = rx_cur_desc->UpPktStatus; + + /* only refill used descriptor */ + if (!(status & upComplete)) + continue; + + /* we still need to process this descriptor */ + if (p->rx_iobuf[i] != NULL) + continue; + + p->rx_iobuf[i] = alloc_iob(RX_BUF_SIZE); + if (p->rx_iobuf[i] == NULL) { + DBG("alloc_iob() failed\n"); + break; + } + + a3c90x_prepare_rx_desc(p, i); } +} - /** Print identification message **/ - printf("\n\n3C90X Driver 2.02 " - "Copyright 1999 LightSys Technology Services, Inc.\n" - "Portions Copyright 1999 Steve Smith\n"); - printf("Provided with ABSOLUTELY NO WARRANTY.\n"); -#ifdef CFG_3C90X_BOOTROM_FIX - if (INF_3C90X.isBrev) - { - printf("NOTE: 3c905b bootrom fix enabled; has side " - "effects. See 3c90x.txt for info.\n"); +/** + * a3c90x_setup_rx_ring - Allocates RX ring, initialize rx_desc values + * + * @v p Private NIC data + * + * @ret Returns 0 on success, negative on failure + */ +static int a3c90x_setup_rx_ring(struct INF_3C90X *p) +{ + int i; + + DBGP("a3c90x_setup_rx_ring\n"); + + p->rx_ring = + malloc_dma(RX_RING_SIZE * sizeof(struct RXD), RX_RING_ALIGN); + + if (!p->rx_ring) { + DBG("Could not allocate RX-ring\n"); + return -ENOMEM; } -#endif - printf("-------------------------------------------------------" - "------------------------\n"); - - /** Retrieve the Hardware address and print it on the screen. **/ - INF_3C90X.HWAddr[0] = eeprom[HWADDR_OFFSET + 0]>>8; - INF_3C90X.HWAddr[1] = eeprom[HWADDR_OFFSET + 0]&0xFF; - INF_3C90X.HWAddr[2] = eeprom[HWADDR_OFFSET + 1]>>8; - INF_3C90X.HWAddr[3] = eeprom[HWADDR_OFFSET + 1]&0xFF; - INF_3C90X.HWAddr[4] = eeprom[HWADDR_OFFSET + 2]>>8; - INF_3C90X.HWAddr[5] = eeprom[HWADDR_OFFSET + 2]&0xFF; - - DBG ( "MAC Address = %s\n", eth_ntoa ( INF_3C90X.HWAddr ) ); - - /** 3C556: Invert MII power **/ - if (INF_3C90X.is3c556) { - unsigned int tmp; - a3c90x_internal_SetWindow(INF_3C90X.IOAddr, winAddressing2); - tmp = inw(INF_3C90X.IOAddr + regResetOptions_2_w); - tmp |= 0x4000; - outw(tmp, INF_3C90X.IOAddr + regResetOptions_2_w); - } - - /* Test if the link is good, if not continue */ - a3c90x_internal_SetWindow(INF_3C90X.IOAddr, winDiagnostics4); - mstat = inw(INF_3C90X.IOAddr + regMediaStatus_4_w); - if((mstat & (1<<11)) == 0) { - printf("Valid link not established\n"); - return 0; - } - - /** Program the MAC address into the station address registers **/ - a3c90x_internal_SetWindow(INF_3C90X.IOAddr, winAddressing2); - outw(htons(eeprom[HWADDR_OFFSET + 0]), INF_3C90X.IOAddr + regStationAddress_2_3w); - outw(htons(eeprom[HWADDR_OFFSET + 1]), INF_3C90X.IOAddr + regStationAddress_2_3w+2); - outw(htons(eeprom[HWADDR_OFFSET + 2]), INF_3C90X.IOAddr + regStationAddress_2_3w+4); - outw(0, INF_3C90X.IOAddr + regStationMask_2_3w+0); - outw(0, INF_3C90X.IOAddr + regStationMask_2_3w+2); - outw(0, INF_3C90X.IOAddr + regStationMask_2_3w+4); - - /** Fill in our entry in the etherboot arp table **/ - for(i=0;i<ETH_ALEN;i++) - nic->node_addr[i] = (eeprom[HWADDR_OFFSET + i/2] >> (8*((i&1)^1))) & 0xff; - - /** Read the media options register, print a message and set default - ** xcvr. - ** - ** Uses Media Option command on B revision, Reset Option on non-B - ** revision cards -- same register address - **/ - a3c90x_internal_SetWindow(INF_3C90X.IOAddr, winTxRxOptions3); - mopt = inw(INF_3C90X.IOAddr + regResetMediaOptions_3_w); - - /** mask out VCO bit that is defined as 10baseFL bit on B-rev cards **/ - if (! INF_3C90X.isBrev) - { - mopt &= 0x7F; + + p->rx_cur = 0; + + for (i = 0; i < RX_RING_SIZE; i++) { + p->rx_ring[i].UpNextPtr = + virt_to_bus(p->rx_ring + (i + 1)); + + /* these are needed so refill_rx_ring initializes the ring */ + p->rx_ring[i].UpPktStatus = upComplete; + p->rx_iobuf[i] = NULL; } - printf("Connectors present: "); - c = 0; - linktype = 0x0008; - if (mopt & 0x01) - { - printf("%s100Base-T4",(c++)?", ":""); - linktype = 0x0006; + /* Loop the ring */ + p->rx_ring[i - 1].UpNextPtr = virt_to_bus(p->rx_ring); + + a3c90x_refill_rx_ring(p); + + return 0; +} + +static void a3c90x_free_rx_ring(struct INF_3C90X *p) +{ + DBGP("a3c90x_free_rx_ring\n"); + + free_dma(p->rx_ring, RX_RING_SIZE * sizeof(struct RXD)); + p->rx_ring = NULL; +} + +static void a3c90x_free_rx_iobuf(struct INF_3C90X *p) +{ + int i; + + DBGP("a3c90x_free_rx_iobuf\n"); + + for (i = 0; i < RX_RING_SIZE; i++) { + free_iob(p->rx_iobuf[i]); + p->rx_iobuf[i] = NULL; } - if (mopt & 0x04) - { - printf("%s100Base-FX",(c++)?", ":""); - linktype = 0x0005; +} + +/** + * a3c90x_process_rx_packets - Checks for received packets, + * reports them to gPXE with netdev_rx() or netdev_rx_err() if there was an + * error while receiving the packet + * + * @v netdev Network device info + */ +static void a3c90x_process_rx_packets(struct net_device *netdev) +{ + int i; + unsigned int rx_status; + struct INF_3C90X *p = netdev_priv(netdev); + struct RXD *rx_cur_desc; + + DBGP("a3c90x_process_rx_packets\n"); + + for (i = 0; i < RX_RING_SIZE; i++) { + rx_cur_desc = p->rx_ring + p->rx_cur; + rx_status = rx_cur_desc->UpPktStatus; + + if (!(rx_status & upComplete) && !(rx_status & upError)) + break; + + if (p->rx_iobuf[p->rx_cur] == NULL) + break; + + if (rx_status & upError) { + DBG("Corrupted packet received\n"); + netdev_rx_err(netdev, p->rx_iobuf[p->rx_cur], + -EINVAL); + } else { + /* if we're here, we've got good packet */ + int packet_len; + + packet_len = rx_status & 0x1FFF; + iob_put(p->rx_iobuf[p->rx_cur], packet_len); + + DBG("received packet\n"); + DBG(" size: %d\n", packet_len); + + netdev_rx(netdev, p->rx_iobuf[p->rx_cur]); + } + + p->rx_iobuf[p->rx_cur] = NULL; /* invalidate rx desc */ + p->rx_cur = (p->rx_cur + 1) % RX_RING_SIZE; } - if (mopt & 0x10) - { - printf("%s10Base-2",(c++)?", ":""); - linktype = 0x0003; + a3c90x_refill_rx_ring(p); + +} + +/** + * a3c90x_poll - Routine that gets called periodically. + * Here we hanle transmitted and received packets. + * We could also check the link status from time to time, which we + * currently don't do. + * + * @v netdev Network device info + */ +static void a3c90x_poll(struct net_device *netdev) +{ + struct INF_3C90X *p = netdev_priv(netdev); + uint16_t raw_status, int_status; + + DBGP("a3c90x_poll\n"); + + raw_status = inw(p->IOAddr + regCommandIntStatus_w); + int_status = (raw_status & 0x0FFF); + + if ( int_status == 0 ) + return; + + a3c90x_internal_IssueCommand(p->IOAddr, cmdAcknowledgeInterrupt, + int_status); + + if (int_status & INT_TXCOMPLETE) + outb(0x00, p->IOAddr + regTxStatus_b); + + DBG("poll: status = %#04x\n", raw_status); + + a3c90x_process_tx_packets(netdev); + + a3c90x_process_rx_packets(netdev); +} + + + +static void a3c90x_free_resources(struct INF_3C90X *p) +{ + DBGP("a3c90x_free_resources\n"); + + a3c90x_free_tx_ring(p); + a3c90x_free_rx_ring(p); + a3c90x_free_rx_iobuf(p); +} + +/** + * a3c90x_remove - Routine to remove the card. Unregisters + * the NIC from gPXE, disables RX/TX and resets the card. + * + * @v pci PCI device info + */ +static void a3c90x_remove(struct pci_device *pci) +{ + struct net_device *netdev = pci_get_drvdata(pci); + struct INF_3C90X *inf_3c90x = netdev_priv(netdev); + + DBGP("a3c90x_remove\n"); + + a3c90x_reset(inf_3c90x); + + /* Disable the receiver and transmitter. */ + outw(cmdRxDisable, inf_3c90x->IOAddr + regCommandIntStatus_w); + outw(cmdTxDisable, inf_3c90x->IOAddr + regCommandIntStatus_w); + + unregister_netdev(netdev); + netdev_nullify(netdev); + netdev_put(netdev); +} + +static void a3c90x_irq(struct net_device *netdev, int enable) +{ + struct INF_3C90X *p = netdev_priv(netdev); + + DBGP("a3c90x_irq\n"); + + if (enable == 0) { + /* disable interrupts */ + a3c90x_internal_IssueCommand(p->IOAddr, + cmdSetInterruptEnable, 0); + } else { + a3c90x_internal_IssueCommand(p->IOAddr, + cmdSetInterruptEnable, + INT_TXCOMPLETE | + INT_UPCOMPLETE); + a3c90x_internal_IssueCommand(p->IOAddr, + cmdAcknowledgeInterrupt, + 0x661); } - if (mopt & 0x20) - { - printf("%sAUI",(c++)?", ":""); - linktype = 0x0001; +} + +/** + * a3c90x_hw_start - Initialize hardware, copy MAC address + * to NIC registers, set default receiver + */ +static void a3c90x_hw_start(struct net_device *netdev) +{ + int i, c; + unsigned int cfg; + unsigned int mopt; + unsigned short linktype; + struct INF_3C90X *inf_3c90x = netdev_priv(netdev); + + DBGP("a3c90x_hw_start\n"); + + /* 3C556: Invert MII power */ + if (inf_3c90x->is3c556) { + unsigned int tmp; + a3c90x_internal_SetWindow(inf_3c90x, winAddressing2); + tmp = inw(inf_3c90x->IOAddr + regResetOptions_2_w); + tmp |= 0x4000; + outw(tmp, inf_3c90x->IOAddr + regResetOptions_2_w); } - if (mopt & 0x40) - { - printf("%sMII",(c++)?", ":""); - linktype = 0x0006; + + /* Copy MAC address into the NIC registers */ + a3c90x_internal_SetWindow(inf_3c90x, winAddressing2); + for (i = 0; i < ETH_ALEN; i++) + outb(netdev->ll_addr[i], + inf_3c90x->IOAddr + regStationAddress_2_3w + i); + for (i = 0; i < ETH_ALEN; i++) + outb(0, inf_3c90x->IOAddr + regStationMask_2_3w + i); + + /* Read the media options register, print a message and set default + * xcvr. + * + * Uses Media Option command on B revision, Reset Option on non-B + * revision cards -- same register address + */ + a3c90x_internal_SetWindow(inf_3c90x, winTxRxOptions3); + mopt = inw(inf_3c90x->IOAddr + regResetMediaOptions_3_w); + + /* mask out VCO bit that is defined as 10baseFL bit on B-rev cards */ + if (!inf_3c90x->isBrev) { + mopt &= 0x7F; } - if ((mopt & 0xA) == 0xA) - { - printf("%s10Base-T / 100Base-TX",(c++)?", ":""); + + DBG("Connectors present: "); + c = 0; linktype = 0x0008; + if (mopt & 0x01) { + DBG("%s100Base-T4", (c++) ? ", " : ""); + linktype = linkMII; } - else if ((mopt & 0xA) == 0x2) - { - printf("%s100Base-TX",(c++)?", ":""); - linktype = 0x0008; + if (mopt & 0x04) { + DBG("%s100Base-FX", (c++) ? ", " : ""); + linktype = link100BaseFX; } - else if ((mopt & 0xA) == 0x8) - { - printf("%s10Base-T",(c++)?", ":""); - linktype = 0x0008; + if (mopt & 0x10) { + DBG("%s10Base-2", (c++) ? ", " : ""); + linktype = link10Base2; } - printf(".\n"); - - /** Determine transceiver type to use, depending on value stored in - ** eeprom 0x16 - **/ - if (INF_3C90X.isBrev) - { - if ((eeprom[0x16] & 0xFF00) == XCVR_MAGIC) - { - /** User-defined **/ - linktype = eeprom[0x16] & 0x000F; - } + if (mopt & 0x20) { + DBG("%sAUI", (c++) ? ", " : ""); + linktype = linkAUI; } - else - { -#ifdef CFG_3C90X_XCVR - if (CFG_3C90X_XCVR != 255) - linktype = CFG_3C90X_XCVR; -#endif /* CFG_3C90X_XCVR */ - - /** I don't know what MII MAC only mode is!!! **/ - if (linktype == 0x0009) - { - if (INF_3C90X.isBrev) - printf("WARNING: MII External MAC Mode only supported on B-revision " - "cards!!!!\nFalling Back to MII Mode\n"); - linktype = 0x0006; + if (mopt & 0x40) { + DBG("%sMII", (c++) ? ", " : ""); + linktype = linkMII; + } + if ((mopt & 0xA) == 0xA) { + DBG("%s10Base-T / 100Base-TX", (c++) ? ", " : ""); + linktype = linkAutoneg; + } else if ((mopt & 0xA) == 0x2) { + DBG("%s100Base-TX", (c++) ? ", " : ""); + linktype = linkAutoneg; + } else if ((mopt & 0xA) == 0x8) { + DBG("%s10Base-T", (c++) ? ", " : ""); + linktype = linkAutoneg; + } + DBG(".\n"); + + /* Determine transceiver type to use, depending on value stored in + * eeprom 0x16 + */ + if (inf_3c90x->isBrev) { + if ((inf_3c90x->eeprom[0x16] & 0xFF00) == XCVR_MAGIC) { + /* User-defined */ + linktype = inf_3c90x->eeprom[0x16] & 0x000F; } + } else { + /* I don't know what MII MAC only mode is!!! */ + if (linktype == linkExternalMII) { + if (inf_3c90x->isBrev) + DBG("WARNING: MII External MAC Mode only supported on B-revision " "cards!!!!\nFalling Back to MII Mode\n"); + linktype = linkMII; + } + } + + /* enable DC converter for 10-Base-T */ + if (linktype == link10Base2) { + a3c90x_internal_IssueCommand(inf_3c90x->IOAddr, + cmdEnableDcConverter, 0); + } + + /* Set the link to the type we just determined. */ + a3c90x_internal_SetWindow(inf_3c90x, winTxRxOptions3); + cfg = inl(inf_3c90x->IOAddr + regInternalConfig_3_l); + cfg &= ~(0xF << 20); + cfg |= (linktype << 20); + + DBG("Setting internal cfg register: 0x%08X (linktype: 0x%02X)\n", + cfg, linktype); + + outl(cfg, inf_3c90x->IOAddr + regInternalConfig_3_l); + + /* Now that we set the xcvr type, reset the Tx and Rx */ + a3c90x_internal_IssueCommand(inf_3c90x->IOAddr, cmdTxReset, 0x00); + + if (!inf_3c90x->isBrev) + outb(0x01, inf_3c90x->IOAddr + regTxFreeThresh_b); + + /* Set the RX filter = receive only individual pkts & multicast & bcast. */ + a3c90x_internal_IssueCommand(inf_3c90x->IOAddr, cmdSetRxFilter, + 0x01 + 0x02 + 0x04); + + + /* + * set Indication and Interrupt flags , acknowledge any IRQ's + */ + a3c90x_internal_IssueCommand(inf_3c90x->IOAddr, + cmdSetInterruptEnable, + INT_TXCOMPLETE | INT_UPCOMPLETE); + a3c90x_internal_IssueCommand(inf_3c90x->IOAddr, + cmdSetIndicationEnable, + INT_TXCOMPLETE | INT_UPCOMPLETE); + a3c90x_internal_IssueCommand(inf_3c90x->IOAddr, + cmdAcknowledgeInterrupt, 0x661); +} + +/** + * a3c90x_open - Routine to initialize the card. Initialize hardware, + * allocate TX and RX ring, send RX ring address to the NIC. + * + * @v netdev Network device info + * + * @ret Returns 0 on success, negative on failure + */ +static int a3c90x_open(struct net_device *netdev) +{ + int rc; + struct INF_3C90X *inf_3c90x = netdev_priv(netdev); + + DBGP("a3c90x_open\n"); + + a3c90x_hw_start(netdev); + + rc = a3c90x_setup_tx_ring(inf_3c90x); + if (rc != 0) { + DBG("Error setting up TX Ring\n"); + goto error; } - /** enable DC converter for 10-Base-T **/ - if (linktype == 0x0003) - { - a3c90x_internal_IssueCommand(INF_3C90X.IOAddr, cmdEnableDcConverter, 0); + rc = a3c90x_setup_rx_ring(inf_3c90x); + if (rc != 0) { + DBG("Error setting up RX Ring\n"); + goto error; } - /** Set the link to the type we just determined. **/ - a3c90x_internal_SetWindow(INF_3C90X.IOAddr, winTxRxOptions3); - cfg = inl(INF_3C90X.IOAddr + regInternalConfig_3_l); - cfg &= ~(0xF<<20); - cfg |= (linktype<<20); - outl(cfg, INF_3C90X.IOAddr + regInternalConfig_3_l); - - /** Now that we set the xcvr type, reset the Tx and Rx, re-enable. **/ - a3c90x_internal_IssueCommand(INF_3C90X.IOAddr, cmdTxReset, 0x00); - while (inw(INF_3C90X.IOAddr + regCommandIntStatus_w) & INT_CMDINPROGRESS) - ; - - if (!INF_3C90X.isBrev) - outb(0x01, INF_3C90X.IOAddr + regTxFreeThresh_b); - - a3c90x_internal_IssueCommand(INF_3C90X.IOAddr, cmdTxEnable, 0); - - /** - ** reset of the receiver on B-revision cards re-negotiates the link - ** takes several seconds (a computer eternity) - **/ - if (INF_3C90X.isBrev) - a3c90x_internal_IssueCommand(INF_3C90X.IOAddr, cmdRxReset, 0x04); - else - a3c90x_internal_IssueCommand(INF_3C90X.IOAddr, cmdRxReset, 0x00); - while (inw(INF_3C90X.IOAddr + regCommandIntStatus_w) & INT_CMDINPROGRESS) - ; - - /** Set the RX filter = receive only individual pkts & multicast & bcast. **/ - a3c90x_internal_IssueCommand(INF_3C90X.IOAddr, cmdSetRxFilter, 0x01 + 0x02 + 0x04); - a3c90x_internal_IssueCommand(INF_3C90X.IOAddr, cmdRxEnable, 0); - - - /** - ** set Indication and Interrupt flags , acknowledge any IRQ's - **/ - a3c90x_internal_IssueCommand(INF_3C90X.IOAddr, cmdSetInterruptEnable, 0); - a3c90x_internal_IssueCommand(INF_3C90X.IOAddr, - cmdSetIndicationEnable, 0x0014); - a3c90x_internal_IssueCommand(INF_3C90X.IOAddr, - cmdAcknowledgeInterrupt, 0x661); - - /** Set our exported functions **/ - nic->nic_op = &a3c90x_operations; - return 1; + /* send rx_ring address to NIC */ + outl(virt_to_bus(inf_3c90x->rx_ring), + inf_3c90x->IOAddr + regUpListPtr_l); + + /* enable packet transmission and reception */ + a3c90x_internal_IssueCommand(inf_3c90x->IOAddr, cmdTxEnable, 0); + a3c90x_internal_IssueCommand(inf_3c90x->IOAddr, cmdRxEnable, 0); + + return 0; + + error: + a3c90x_free_resources(inf_3c90x); + a3c90x_reset(inf_3c90x); + return rc; } -static struct nic_operations a3c90x_operations = { - .connect = dummy_connect, - .poll = a3c90x_poll, - .transmit = a3c90x_transmit, - .irq = a3c90x_irq, +/** + * a3c90x_close - free()s TX and RX ring, disablex RX/TX, resets NIC + * + * @v netdev Network device info + */ +static void a3c90x_close(struct net_device *netdev) +{ + struct INF_3C90X *inf_3c90x = netdev_priv(netdev); + + DBGP("a3c90x_close\n"); + a3c90x_reset(inf_3c90x); + outw(cmdRxDisable, inf_3c90x->IOAddr + regCommandIntStatus_w); + outw(cmdTxDisable, inf_3c90x->IOAddr + regCommandIntStatus_w); + a3c90x_free_resources(inf_3c90x); +} + +static struct net_device_operations a3c90x_operations = { + .open = a3c90x_open, + .close = a3c90x_close, + .poll = a3c90x_poll, + .transmit = a3c90x_transmit, + .irq = a3c90x_irq, }; +/** + * a3c90x_probe: exported routine to probe for the 3c905 card. + * If this routine is called, the pci functions did find the + * card. We read the eeprom here and get the MAC address. + * Initialization is done in a3c90x_open(). + * + * @v pci PCI device info + * @ pci_id PCI device IDs + * + * @ret rc Returns 0 on success, negative on failure + */ +static int a3c90x_probe(struct pci_device *pci, + const struct pci_device_id *pci_id __unused) +{ + + struct net_device *netdev; + struct INF_3C90X *inf_3c90x; + unsigned char *HWAddr; + int rc; + + DBGP("a3c90x_probe\n"); + + if (pci->ioaddr == 0) + return -EINVAL; + + netdev = alloc_etherdev(sizeof(*inf_3c90x)); + if (!netdev) + return -ENOMEM; + + netdev_init(netdev, &a3c90x_operations); + pci_set_drvdata(pci, netdev); + netdev->dev = &pci->dev; + + inf_3c90x = netdev_priv(netdev); + memset(inf_3c90x, 0, sizeof(*inf_3c90x)); + + adjust_pci_device(pci); + + inf_3c90x->is3c556 = (pci->device == 0x6055); + inf_3c90x->IOAddr = pci->ioaddr; + inf_3c90x->CurrentWindow = winNone; + + inf_3c90x->isBrev = 1; + switch (pci->device) { + case 0x9000: /* 10 Base TPO */ + case 0x9001: /* 10/100 T4 */ + case 0x9050: /* 10/100 TPO */ + case 0x9051: /* 10 Base Combo */ + inf_3c90x->isBrev = 0; + break; + } + + DBG("[3c90x]: found NIC(0x%04X, 0x%04X), isBrev=%d, is3c556=%d\n", + pci->vendor, pci->device, inf_3c90x->isBrev, + inf_3c90x->is3c556); + + /* initialize nvs device */ + inf_3c90x->nvs.word_len_log2 = 1; /* word */ + inf_3c90x->nvs.size = (inf_3c90x->isBrev ? 0x20 : 0x17); + inf_3c90x->nvs.block_size = 1; + inf_3c90x->nvs.read = a3c90x_internal_ReadEeprom; + inf_3c90x->nvs.write = a3c90x_internal_WriteEeprom; + + /* reset NIC before accessing any data from it */ + a3c90x_reset(inf_3c90x); + + /* load eeprom contents to inf_3c90x->eeprom */ + a3c90x_internal_ReadEepromContents(inf_3c90x); + + HWAddr = netdev->hw_addr; + + /* Retrieve the Hardware address */ + HWAddr[0] = inf_3c90x->eeprom[eepromHwAddrOffset + 0] >> 8; + HWAddr[1] = inf_3c90x->eeprom[eepromHwAddrOffset + 0] & 0xFF; + HWAddr[2] = inf_3c90x->eeprom[eepromHwAddrOffset + 1] >> 8; + HWAddr[3] = inf_3c90x->eeprom[eepromHwAddrOffset + 1] & 0xFF; + HWAddr[4] = inf_3c90x->eeprom[eepromHwAddrOffset + 2] >> 8; + HWAddr[5] = inf_3c90x->eeprom[eepromHwAddrOffset + 2] & 0xFF; + + /* we don't handle linkstates yet, so we're always up */ + netdev_link_up(netdev); + + if ((rc = register_netdev(netdev)) != 0) { + DBG("3c90x: register_netdev() failed\n"); + netdev_put(netdev); + return rc; + } + + return 0; +} + static struct pci_device_id a3c90x_nics[] = { /* Original 90x revisions: */ -PCI_ROM(0x10b7, 0x6055, "3c556", "3C556"), /* Huricane */ -PCI_ROM(0x10b7, 0x9000, "3c905-tpo", "3Com900-TPO"), /* 10 Base TPO */ -PCI_ROM(0x10b7, 0x9001, "3c905-t4", "3Com900-Combo"), /* 10/100 T4 */ -PCI_ROM(0x10b7, 0x9050, "3c905-tpo100", "3Com905-TX"), /* 100 Base TX / 10/100 TPO */ -PCI_ROM(0x10b7, 0x9051, "3c905-combo", "3Com905-T4"), /* 100 Base T4 / 10 Base Combo */ + PCI_ROM(0x10b7, 0x6055, "3c556", "3C556", 0), /* Huricane */ + PCI_ROM(0x10b7, 0x9000, "3c905-tpo", "3Com900-TPO", 0), /* 10 Base TPO */ + PCI_ROM(0x10b7, 0x9001, "3c905-t4", "3Com900-Combo", 0), /* 10/100 T4 */ + PCI_ROM(0x10b7, 0x9050, "3c905-tpo100", "3Com905-TX", 0), /* 100 Base TX / 10/100 TPO */ + PCI_ROM(0x10b7, 0x9051, "3c905-combo", "3Com905-T4", 0), /* 100 Base T4 / 10 Base Combo */ /* Newer 90xB revisions: */ -PCI_ROM(0x10b7, 0x9004, "3c905b-tpo", "3Com900B-TPO"), /* 10 Base TPO */ -PCI_ROM(0x10b7, 0x9005, "3c905b-combo", "3Com900B-Combo"), /* 10 Base Combo */ -PCI_ROM(0x10b7, 0x9006, "3c905b-tpb2", "3Com900B-2/T"), /* 10 Base TP and Base2 */ -PCI_ROM(0x10b7, 0x900a, "3c905b-fl", "3Com900B-FL"), /* 10 Base FL */ -PCI_ROM(0x10b7, 0x9055, "3c905b-tpo100", "3Com905B-TX"), /* 10/100 TPO */ -PCI_ROM(0x10b7, 0x9056, "3c905b-t4", "3Com905B-T4"), /* 10/100 T4 */ -PCI_ROM(0x10b7, 0x9058, "3c905b-9058", "3Com905B-9058"), /* Cyclone 10/100/BNC */ -PCI_ROM(0x10b7, 0x905a, "3c905b-fx", "3Com905B-FL"), /* 100 Base FX / 10 Base FX */ + PCI_ROM(0x10b7, 0x9004, "3c905b-tpo", "3Com900B-TPO", 0), /* 10 Base TPO */ + PCI_ROM(0x10b7, 0x9005, "3c905b-combo", "3Com900B-Combo", 0), /* 10 Base Combo */ + PCI_ROM(0x10b7, 0x9006, "3c905b-tpb2", "3Com900B-2/T", 0), /* 10 Base TP and Base2 */ + PCI_ROM(0x10b7, 0x900a, "3c905b-fl", "3Com900B-FL", 0), /* 10 Base FL */ + PCI_ROM(0x10b7, 0x9055, "3c905b-tpo100", "3Com905B-TX", 0), /* 10/100 TPO */ + PCI_ROM(0x10b7, 0x9056, "3c905b-t4", "3Com905B-T4", 0), /* 10/100 T4 */ + PCI_ROM(0x10b7, 0x9058, "3c905b-9058", "3Com905B-9058", 0), /* Cyclone 10/100/BNC */ + PCI_ROM(0x10b7, 0x905a, "3c905b-fx", "3Com905B-FL", 0), /* 100 Base FX / 10 Base FX */ /* Newer 90xC revision: */ -PCI_ROM(0x10b7, 0x9200, "3c905c-tpo", "3Com905C-TXM"), /* 10/100 TPO (3C905C-TXM) */ -PCI_ROM(0x10b7, 0x9202, "3c920b-emb-ati", "3c920B-EMB-WNM (ATI Radeon 9100 IGP)"), /* 3c920B-EMB-WNM (ATI Radeon 9100 IGP) */ -PCI_ROM(0x10b7, 0x9210, "3c920b-emb-wnm","3Com20B-EMB WNM"), -PCI_ROM(0x10b7, 0x9800, "3c980", "3Com980-Cyclone"), /* Cyclone */ -PCI_ROM(0x10b7, 0x9805, "3c9805", "3Com9805"), /* Dual Port Server Cyclone */ -PCI_ROM(0x10b7, 0x7646, "3csoho100-tx", "3CSOHO100-TX"), /* Hurricane */ -PCI_ROM(0x10b7, 0x4500, "3c450", "3Com450 HomePNA Tornado"), -PCI_ROM(0x10b7, 0x1201, "3c982a", "3Com982A"), -PCI_ROM(0x10b7, 0x1202, "3c982b", "3Com982B"), + PCI_ROM(0x10b7, 0x9200, "3c905c-tpo", "3Com905C-TXM", 0), /* 10/100 TPO (3C905C-TXM) */ + PCI_ROM(0x10b7, 0x9202, "3c920b-emb-ati", "3c920B-EMB-WNM (ATI Radeon 9100 IGP)", 0), /* 3c920B-EMB-WNM (ATI Radeon 9100 IGP) */ + PCI_ROM(0x10b7, 0x9210, "3c920b-emb-wnm", "3Com20B-EMB WNM", 0), + PCI_ROM(0x10b7, 0x9800, "3c980", "3Com980-Cyclone", 0), /* Cyclone */ + PCI_ROM(0x10b7, 0x9805, "3c9805", "3Com9805", 0), /* Dual Port Server Cyclone */ + PCI_ROM(0x10b7, 0x7646, "3csoho100-tx", "3CSOHO100-TX", 0), /* Hurricane */ + PCI_ROM(0x10b7, 0x4500, "3c450", "3Com450 HomePNA Tornado", 0), + PCI_ROM(0x10b7, 0x1201, "3c982a", "3Com982A", 0), + PCI_ROM(0x10b7, 0x1202, "3c982b", "3Com982B", 0), }; -PCI_DRIVER ( a3c90x_driver, a3c90x_nics, PCI_NO_CLASS ); - -DRIVER ( "3C90X", nic_driver, pci_driver, a3c90x_driver, - a3c90x_probe, a3c90x_disable ); +struct pci_driver a3c90x_driver __pci_driver = { + .ids = a3c90x_nics, + .id_count = (sizeof(a3c90x_nics) / sizeof(a3c90x_nics[0])), + .probe = a3c90x_probe, + .remove = a3c90x_remove, +}; /* * Local variables: diff --git a/gpxe/src/drivers/net/3c90x.h b/gpxe/src/drivers/net/3c90x.h new file mode 100644 index 00000000..acbb29d2 --- /dev/null +++ b/gpxe/src/drivers/net/3c90x.h @@ -0,0 +1,302 @@ +/* + * 3c90x.c -- This file implements the 3c90x driver for etherboot. Written + * by Greg Beeley, Greg.Beeley@LightSys.org. Modified by Steve Smith, + * Steve.Smith@Juno.Com. Alignment bug fix Neil Newell (nn@icenoir.net). + * + * Port from etherboot to gPXE API, implementation of tx/rx ring support + * by Thomas Miletich, thomas.miletich@gmail.com + * Thanks to Marty Connor and Stefan Hajnoczi for their help and feedback. + * + * This program Copyright (C) 1999 LightSys Technology Services, Inc. + * Portions Copyright (C) 1999 Steve Smith + * + * This program may be re-distributed in source or binary form, modified, + * sold, or copied for any purpose, provided that the above copyright message + * and this text are included with all source copies or derivative works, and + * provided that the above copyright message and this text are included in the + * documentation of any binary-only distributions. This program is distributed + * WITHOUT ANY WARRANTY, without even the warranty of FITNESS FOR A PARTICULAR + * PURPOSE or MERCHANTABILITY. Please read the associated documentation + * "3c90x.txt" before compiling and using this driver. + * + * -------- + * + * Program written with the assistance of the 3com documentation for + * the 3c905B-TX card, as well as with some assistance from the 3c59x + * driver Donald Becker wrote for the Linux kernel, and with some assistance + * from the remainder of the Etherboot distribution. + * + * REVISION HISTORY: + * + * v0.10 1-26-1998 GRB Initial implementation. + * v0.90 1-27-1998 GRB System works. + * v1.00pre1 2-11-1998 GRB Got prom boot issue fixed. + * v2.0 9-24-1999 SCS Modified for 3c905 (from 3c905b code) + * Re-wrote poll and transmit for + * better error recovery and heavy + * network traffic operation + * v2.01 5-26-2003 NN Fixed driver alignment issue which + * caused system lockups if driver structures + * not 8-byte aligned. + * v2.02 11-28-2007 GSt Got polling working again by replacing + * "for(i=0;i<40000;i++);" with "mdelay(1);" + * + * + * indent options: indent -kr -i8 3c90x.c + */ + +FILE_LICENCE ( BSD2 ); + +#ifndef __3C90X_H_ +#define __3C90X_H_ + +static struct net_device_operations a3c90x_operations; + +#define XCVR_MAGIC (0x5A00) + +/* Register definitions for the 3c905 */ +enum Registers { + regPowerMgmtCtrl_w = 0x7c, /* 905B Revision Only */ + regUpMaxBurst_w = 0x7a, /* 905B Revision Only */ + regDnMaxBurst_w = 0x78, /* 905B Revision Only */ + regDebugControl_w = 0x74, /* 905B Revision Only */ + regDebugData_l = 0x70, /* 905B Revision Only */ + regRealTimeCnt_l = 0x40, /* Universal */ + regUpBurstThresh_b = 0x3e, /* 905B Revision Only */ + regUpPoll_b = 0x3d, /* 905B Revision Only */ + regUpPriorityThresh_b = 0x3c, /* 905B Revision Only */ + regUpListPtr_l = 0x38, /* Universal */ + regCountdown_w = 0x36, /* Universal */ + regFreeTimer_w = 0x34, /* Universal */ + regUpPktStatus_l = 0x30, /* Universal with Exception, pg 130 */ + regTxFreeThresh_b = 0x2f, /* 90X Revision Only */ + regDnPoll_b = 0x2d, /* 905B Revision Only */ + regDnPriorityThresh_b = 0x2c, /* 905B Revision Only */ + regDnBurstThresh_b = 0x2a, /* 905B Revision Only */ + regDnListPtr_l = 0x24, /* Universal with Exception, pg 107 */ + regDmaCtrl_l = 0x20, /* Universal with Exception, pg 106 */ + /* */ + regIntStatusAuto_w = 0x1e, /* 905B Revision Only */ + regTxStatus_b = 0x1b, /* Universal with Exception, pg 113 */ + regTimer_b = 0x1a, /* Universal */ + regTxPktId_b = 0x18, /* 905B Revision Only */ + regCommandIntStatus_w = 0x0e, /* Universal (Command Variations) */ +}; + +/* following are windowed registers */ +enum Registers7 { + regPowerMgmtEvent_7_w = 0x0c, /* 905B Revision Only */ + regVlanEtherType_7_w = 0x04, /* 905B Revision Only */ + regVlanMask_7_w = 0x00, /* 905B Revision Only */ +}; + +enum Registers6 { + regBytesXmittedOk_6_w = 0x0c, /* Universal */ + regBytesRcvdOk_6_w = 0x0a, /* Universal */ + regUpperFramesOk_6_b = 0x09, /* Universal */ + regFramesDeferred_6_b = 0x08, /* Universal */ + regFramesRecdOk_6_b = 0x07, /* Universal with Exceptions, pg 142 */ + regFramesXmittedOk_6_b = 0x06, /* Universal */ + regRxOverruns_6_b = 0x05, /* Universal */ + regLateCollisions_6_b = 0x04, /* Universal */ + regSingleCollisions_6_b = 0x03, /* Universal */ + regMultipleCollisions_6_b = 0x02, /* Universal */ + regSqeErrors_6_b = 0x01, /* Universal */ + regCarrierLost_6_b = 0x00, /* Universal */ +}; + +enum Registers5 { + regIndicationEnable_5_w = 0x0c, /* Universal */ + regInterruptEnable_5_w = 0x0a, /* Universal */ + regTxReclaimThresh_5_b = 0x09, /* 905B Revision Only */ + regRxFilter_5_b = 0x08, /* Universal */ + regRxEarlyThresh_5_w = 0x06, /* Universal */ + regTxStartThresh_5_w = 0x00, /* Universal */ +}; + +enum Registers4 { + regUpperBytesOk_4_b = 0x0d, /* Universal */ + regBadSSD_4_b = 0x0c, /* Universal */ + regMediaStatus_4_w = 0x0a, /* Universal with Exceptions, pg 201 */ + regPhysicalMgmt_4_w = 0x08, /* Universal */ + regNetworkDiagnostic_4_w = 0x06, /* Universal with Exceptions, pg 203 */ + regFifoDiagnostic_4_w = 0x04, /* Universal with Exceptions, pg 196 */ + regVcoDiagnostic_4_w = 0x02, /* Undocumented? */ +}; + +enum Registers3 { + regTxFree_3_w = 0x0c, /* Universal */ + regRxFree_3_w = 0x0a, /* Universal with Exceptions, pg 125 */ + regResetMediaOptions_3_w = 0x08, /* Media Options on B Revision, */ + /* Reset Options on Non-B Revision */ + regMacControl_3_w = 0x06, /* Universal with Exceptions, pg 199 */ + regMaxPktSize_3_w = 0x04, /* 905B Revision Only */ + regInternalConfig_3_l = 0x00, /* Universal, different bit */ + /* definitions, pg 59 */ +}; + +enum Registers2 { + regResetOptions_2_w = 0x0c, /* 905B Revision Only */ + regStationMask_2_3w = 0x06, /* Universal with Exceptions, pg 127 */ + regStationAddress_2_3w = 0x00, /* Universal with Exceptions, pg 127 */ +}; + +enum Registers1 { + regRxStatus_1_w = 0x0a, /* 90X Revision Only, Pg 126 */ +}; + +enum Registers0 { + regEepromData_0_w = 0x0c, /* Universal */ + regEepromCommand_0_w = 0x0a, /* Universal */ + regBiosRomData_0_b = 0x08, /* 905B Revision Only */ + regBiosRomAddr_0_l = 0x04, /* 905B Revision Only */ +}; + + +/* The names for the eight register windows */ +enum Windows { + winNone = 0xff, + winPowerVlan7 = 0x07, + winStatistics6 = 0x06, + winTxRxControl5 = 0x05, + winDiagnostics4 = 0x04, + winTxRxOptions3 = 0x03, + winAddressing2 = 0x02, + winUnused1 = 0x01, + winEepromBios0 = 0x00, +}; + + +/* Command definitions for the 3c90X */ +enum Commands { + cmdGlobalReset = 0x00, /* Universal with Exceptions, pg 151 */ + cmdSelectRegisterWindow = 0x01, /* Universal */ + cmdEnableDcConverter = 0x02, /* */ + cmdRxDisable = 0x03, /* */ + cmdRxEnable = 0x04, /* Universal */ + cmdRxReset = 0x05, /* Universal */ + cmdStallCtl = 0x06, /* Universal */ + cmdTxEnable = 0x09, /* Universal */ + cmdTxDisable = 0x0A, /* */ + cmdTxReset = 0x0B, /* Universal */ + cmdRequestInterrupt = 0x0C, /* */ + cmdAcknowledgeInterrupt = 0x0D, /* Universal */ + cmdSetInterruptEnable = 0x0E, /* Universal */ + cmdSetIndicationEnable = 0x0F, /* Universal */ + cmdSetRxFilter = 0x10, /* Universal */ + cmdSetRxEarlyThresh = 0x11, /* */ + cmdSetTxStartThresh = 0x13, /* */ + cmdStatisticsEnable = 0x15, /* */ + cmdStatisticsDisable = 0x16, /* */ + cmdDisableDcConverter = 0x17, /* */ + cmdSetTxReclaimThresh = 0x18, /* */ + cmdSetHashFilterBit = 0x19, /* */ +}; + +enum FrameStartHeader { + fshTxIndicate = 0x8000, + fshDnComplete = 0x10000, +}; + +enum UpDownDesc { + upLastFrag = (1 << 31), + downLastFrag = (1 << 31), +}; + +enum UpPktStatus { + upComplete = (1 << 15), + upError = (1 << 14), +}; + +enum Stalls { + upStall = 0x00, + upUnStall = 0x01, + + dnStall = 0x02, + dnUnStall = 0x03, +}; + +enum Resources { + resRxRing = 0x00, + resTxRing = 0x02, + resRxIOBuf = 0x04 +}; + +enum eeprom { + eepromBusy = (1 << 15), + eepromRead = ((0x02) << 6), + eepromRead_556 = 0x230, + eepromHwAddrOffset = 0x0a, +}; + +/* Bit 4 is only used in revison B and upwards */ +enum linktype { + link10BaseT = 0x00, + linkAUI = 0x01, + link10Base2 = 0x03, + link100BaseFX = 0x05, + linkMII = 0x06, + linkAutoneg = 0x08, + linkExternalMII = 0x09, +}; + +/* Values for int status register bitmask */ +#define INT_INTERRUPTLATCH (1<<0) +#define INT_HOSTERROR (1<<1) +#define INT_TXCOMPLETE (1<<2) +#define INT_RXCOMPLETE (1<<4) +#define INT_RXEARLY (1<<5) +#define INT_INTREQUESTED (1<<6) +#define INT_UPDATESTATS (1<<7) +#define INT_LINKEVENT (1<<8) +#define INT_DNCOMPLETE (1<<9) +#define INT_UPCOMPLETE (1<<10) +#define INT_CMDINPROGRESS (1<<12) +#define INT_WINDOWNUMBER (7<<13) + +/* Buffer sizes */ +#define TX_RING_SIZE 8 +#define RX_RING_SIZE 8 +#define TX_RING_ALIGN 16 +#define RX_RING_ALIGN 16 +#define RX_BUF_SIZE 1536 + +/* Timeouts for eeprom and command completion */ +/* Timeout 1 second, to be save */ +#define EEPROM_TIMEOUT 1 * 1000 * 1000 + +/* TX descriptor */ +struct TXD { + volatile unsigned int DnNextPtr; + volatile unsigned int FrameStartHeader; + volatile unsigned int DataAddr; + volatile unsigned int DataLength; +} __attribute__ ((aligned(8))); /* 64-bit aligned for bus mastering */ + +/* RX descriptor */ +struct RXD { + volatile unsigned int UpNextPtr; + volatile unsigned int UpPktStatus; + volatile unsigned int DataAddr; + volatile unsigned int DataLength; +} __attribute__ ((aligned(8))); /* 64-bit aligned for bus mastering */ + +/* Private NIC dats */ +struct INF_3C90X { + unsigned int is3c556; + unsigned char isBrev; + unsigned char CurrentWindow; + unsigned int IOAddr; + unsigned short eeprom[0x21]; + unsigned int tx_cur; /* current entry in tx_ring */ + unsigned int tx_cnt; /* current number of used tx descriptors */ + unsigned int tx_tail; /* entry of last finished packet */ + unsigned int rx_cur; + struct TXD *tx_ring; + struct RXD *rx_ring; + struct io_buffer *tx_iobuf[TX_RING_SIZE]; + struct io_buffer *rx_iobuf[RX_RING_SIZE]; + struct nvs_device nvs; +}; + +#endif diff --git a/gpxe/src/drivers/net/3c90x.txt b/gpxe/src/drivers/net/3c90x.txt deleted file mode 100644 index 3d6746c5..00000000 --- a/gpxe/src/drivers/net/3c90x.txt +++ /dev/null @@ -1,307 +0,0 @@ - - Instructions for use of the 3C90X driver for EtherBoot - - Original 3C905B support by: - Greg Beeley (Greg.Beeley@LightSys.org), - LightSys Technology Services, Inc. - February 11, 1999 - - Updates for 3C90X family by: - Steve Smith (steve.smith@juno.com) - October 1, 1999 - - Minor documentation updates by - Greg Beeley (Greg.Beeley@LightSys.org) - March 29, 2000 - -------------------------------------------------------------------------------- - -I OVERVIEW - - The 3c90X series ethernet cards are a group of high-performance busmaster - DMA cards from 3Com. This particular driver supports both the 3c90x and - the 3c90xB revision cards. 3C90xC family support has been tested to some - degree but not extensively. - - Here's the licensing information: - - This program Copyright (C) 1999 LightSys Technology Services, Inc. - Portions Copyright (C) 1999 Steve Smith. - - This program may be re-distributed in source or binary form, modified, - sold, or copied for any purpose, provided that the above copyright message - and this text are included with all source copies or derivative works, and - provided that the above copyright message and this text are included in the - documentation of any binary-only distributions. This program is - distributed WITHOUT ANY WARRANTY, without even the warranty of FITNESS FOR - A PARTICULAR PURPOSE or MERCHANTABILITY. Please read the associated - documentation "3c90x.txt" before compiling and using this driver. - - -II FLASH PROMS - - The 3c90xB cards, according to the 3Com documentation, only accept the - following flash memory chips: - - Atmel AT29C512 (64 kilobyte) - Atmel AT29C010 (128 kilobyte) - - The 3c90x cards, according to the 3Com documentation, accept the - following flash memory chips capacities: - - 64 kb (8 kB) - 128 kb (16 kB) - 256 kb (32 kB) and - 512 kb (64 kB) - - Atmel AT29C512 (64 kilobyte) chips are specifically listed for both - adapters, but flashing on the 3c905b cards would only be supported - through the Atmel parts. Any device, of the supported size, should - be supported when programmed by a dedicated PROM programmer (e.g. - not the card). - - To use this driver in such a PROM, visit Atmel's web site and download - their .PDF file containing a list of their distributors. Contact the - distributors for pricing information. The prices are quite reasonable - (about $3 US each for the 64 kB part), and are comparable to what one would - expect for similarly sized standard EPROMs. And, the flash chips are much - easier to work with, as they don't need to be UV-erased to be reprogrammed. - The 3C905B card actually provides a method to program the flash memory - while it is resident on board the card itself; if someone would like to - write a small DOS program to do the programming, I can provide the - information about the registers and so forth. - - A utility program, 3c90xutil, is provided with Etherboot in the 'contrib' - directory that allows for the on-board flashing of the ROM while Linux - is running. The program has been successfully used under Linux, but I - have heard problem reports of its use under FreeBSD. Anyone willing to - make it work under FreeBSD is more than welcome to do so! - - You also have the option of using EPROM chips - the 3C905B-TX-NM has been - successfully tested with 27C256 (32kB) and 27C512 (64kB) chips with a - specified access time of 100ns and faster. - - -III GENERAL USE - - Normally, the basic procedure for using this driver is as follows: - - 1. Run the 3c90xcfg program on the driver diskette to enable the - boot PROM and set it to 64k or 128k, as appropriate. - 2. Build the appropriate 3c90x.fd0 or 3c90x.fd0 floppy image with - possibly the value CFG_3C90X_XCVR defined to the transceiver type that - you want to use (i.e., 10/100 rj45, AUI, coax, MII). - 3. Run the floppy image on the PC to be network booted, to get - it configured, and to verify that it will boot properly. - 4. Build the 3c90x.rom or 3c90x.lzrom PROM image and program - it into the flash or EPROM memory chip. - 5. Put the PROM in the ethernet card, boot and enable 'boot from - network first' in the system BIOS, save and reboot. - - Here are some issues to be aware of: - - 1. If you experience crashes or different behaviour when using the - boot PROM, add the setting CFG_3C90X_BOOTROM_FIX and go through the - steps 2-5 above. This works around a bug in some 3c905B cards (see - below), but has some side-effects which may not be desirable. - Please note that you have to boot off a floppy (not PROM!) once for - this fix to take effect. - 2. The possible need to manually set the CFG_3C90X_XCVR value to - configure the transceiver type. Values are listed below. - 3. The possible need to define CFG_3C90X_PRESERVE_XCVR for use in - operating systems that don't intelligently determine the - transceiver type. - - Some things that are on the 'To-Do' list, perhaps for me, but perhaps - for any other volunteers out there: - - 1. Extend the driver to fully implement the auto-select - algorithm if the card has multiple media ports. - 2. Fix any bugs in the code <grin>.... - 3. Extend the driver to support the 3c905c revision cards - "officially". Right now, the support has been primarily empirical - and not based on 3c905C documentation. - - Now for the details.... - - This driver has been tested on roughly 300 systems. The main two - configuration issues to contend with are: - - 1. Ensure that PCI Busmastering is enabled for the adapter (configured - in the CMOS setup) - 2. Some systems don't work properly with the adapter when plug and - play OS is enabled; I always set it to "No" or "Disabled" -- this makes - it easier and really doesn't adversely affect anything. - - Roughly 95% of the systems worked when configured properly. A few - have issues with booting locally once the boot PROM has been installed - (this number has been less than 2%). Other configuration issues that - to check: - - 1. Newer BIOS's actually work correctly with the network boot order. - Set the network adapter first. Most older BIOS's automatically go to - the network boot PROM first. - 2. For systems where the adapter was already installed and is just - having the PROM installed, try setting the "reset configuration data" - to yes in the CMOS setup if the BIOS isn't seen at first. If your BIOS - doesn't have this option, remove the card, start the system, shut down, - install the card and restart (or switch to a different PCI slot). - 3. Make sure the CMOS security settings aren't preventing a boot. - - The 3c905B cards have a significant 'bug' that relates to the flash prom: - unless the card is set internally to the MII transceiver, it will only - read the first 8k of the PROM image. Don't ask why -- it seems really - obscure, but it has to do with the way they mux'd the address lines - from the PCI bus to the ROM. Unfortunately, most of us are not using - MII transceivers, and even the .lzrom image ends up being just a little - bit larger than 8k. Note that the workaround for this is disabled by - default, because the Windows NT 4.0 driver does not like it (no packets - are transmitted). - - So, the solution that I've used is to internally set the card's nvram - configuration to use MII when it boots. The 3c905b driver does this - automatically. This way, the 16k prom image can be loaded into memory, - and then the 3c905b driver can set the temporary configuration of the - card to an appropriate value, either configurable by the user or chosen - by the driver. - - To enable the 3c905B bugfix, which is necessary for these cards when - booting from the Flash ROM, define -DCFG_3C90X_BOOTROM_FIX when building, - create a floppy image and boot it once. - Thereafter, the card should accept the larger prom image. - - The driver should choose an appropriate transceiver on the card. However, - if it doesn't on your card or if you need to, for instance, set your - card to 10mbps when connected to an unmanaged 10/100 hub, you can specify - which transceiver you want to use. To do this, build the 3c905b.fd0 - image with -DCFG_3C90X_XCVR=x, where 'x' is one of the following - values: - - 0 10Base-T - 1 10mbps AUI - 3 10Base-2 (thinnet/coax) - 4 100Base-TX - 5 100Base-FX - 6 MII - 8 Auto-negotiation 10Base-T / 100Base-TX (usually the default) - 9 MII External MAC Mode - 255 Allow driver to choose an 'appropriate' media port. - - Then proceed from step 2 in the above 'general use' instructions. The - .rom image can be built with CFG_3C90X_XCVR set to a value, but you - normally don't want to do this, since it is easier to change the - transceiver type by rebuilding a new floppy, changing the BIOS to floppy - boot, booting, and then changing the BIOS back to network boot. If - CFG_3C90X_XCVR is not set in a particular build, it just uses the - current configuration (either its 'best guess' or whatever the stored - CFG_3C90X_XCVR value was from the last time it was set). - - [[ Note for the more technically inclined: The CFG_3C90X_XCVR value is - programmed into a register in the card's NVRAM that was reserved for - LanWorks PROM images to use. When the driver boots, the card comes - up in MII mode, and the driver checks the LanWorks register to find - out if the user specified a transceiver type. If it finds that - information, it uses that, otherwise it picks a transceiver that the - card has based on the 3c905b's MediaOptions register. This driver isn't - quite smart enough to always determine which media port is actually - _connected_; maybe someone else would like to take on that task (it - actually involves sending a self-directed packet and seeing if it - comes back. IF it does, that port is connected). ]] - - Another issue to keep in mind is that it is possible that some OS'es - might not be happy with the way I've handled the PROM-image hack with - setting MII mode on bootup. Linux 2.0.35 does not have this problem. - Behavior of other systems may vary. The 3com documentation specifically - says that, at least with the card that I have, the device driver in the - OS should auto-select the media port, so other drivers should work fine - with this 'hack'. However, if yours doesn't seem to, you can try defining - CFG_3C90X_PRESERVE_XCVR when building to cause Etherboot to keep the - working setting (that allowed the bootp/tftp process) across the eth_reset - operation. - - -IV FOR DEVELOPERS.... - - If you would like to fix/extend/etc. this driver, feel free to do so; just - be sure you can test the modified version on the 3c905B-TX cards that the - driver was originally designed for. This section of this document gives - some information that might be relevant to a programmer. - - A. Main Entry Point - - a3c90x_probe is the main entry point for this driver. It is referred - to in an array in 'config.c'. - - B. Other Important Functions - - The functions a3c90x_transmit, a3c90x_poll, a3c90x_reset, and - a3c90x_disable are static functions that EtherBoot finds out about - as a result of a3c90x_probe setting entries in the nic structure - for them. The EtherBoot framework does not use interrupts. It is - polled. All transmit and receive operations are initiated by the - etherboot framework, not by an interrupt or by the driver. - - C. Internal Functions - - The following functions are internal to the driver: - - a3c90x_internal_IssueCommand - sends a command to the 3c905b card. - a3c90x_internal_SetWindow - shifts between one of eight register - windows onboard the 3c90x. The bottom 16 bytes of the card's - I/O space are multiplexed among 128 bytes, only 16 of which are - visible at any one time. This SetWindow function selects one of - the eight sets. - a3c90x_internal_ReadEeprom - reads a word (16 bits) from the - card's onboard nvram. This is NOT the BIOS boot rom. This is - where the card stores such things as its hardware address. - a3c90x_internal_WriteEeprom - writes a word (16 bits) to the - card's nvram, and recomputes the eeprom checksum. - a3c90x_internal_WriteEepromWord - writes a word (16 bits) to the - card's nvram. Used by the above routine. - a3c90x_internal_WriteEepromWord - writes a word (16 bits) to the - card's nvram. Used by the above routine. - - D. Globals - - All global variables are inside a global structure named INF_3C90X. - So, wherever you see that structure referenced, you know the variable - is a global. Just keeps things a little neater. - - E. Enumerations - - There are quite a few enumerated type definitions for registers and - so forth, many for registers that I didn't even touch in the driver. - Register types start with 'reg', window numbers (for SetWindow) - start with 'win', and commands (for IssueCommand) start with 'cmd'. - Register offsets also include an indication in the name as to the - size of the register (_b = byte, _w = word, _l = long), and which - window the register is in, if it is windowed (0-7). - - F. Why the 'a3c90x' name? - - I had to come up with a letter at the beginning of all of the - identifiers, since 3com so conveniently had their name start with a - number. Another driver used 't' (for 'three'?); I chose 'a' for - no reason at all. - -Addendum by Jorge L. deLyra <delyra@latt.if.usp.br>, 22Nov2000 re -working around the 3C905 hardware bug mentioned above: - -Use this floppy to fix any 3COM model 3C905B PCI 10/100 Ethernet cards -that fail to load and run the boot program the first time around. If -they have a "Lucent" rather than a "Broadcom" chipset these cards have -a configuration bug that causes a hang when trying to load the boot -program from the PROM, if you try to use them right out of the box. - -The boot program in this floppy is the file named 3c905b-tpo100.rom -from Etherboot version 4.6.10, compiled with the bugfix parameter - - CFG_3C90X_BOOTROM_FIX - -You have to take the chip off the card and boot the system once using -this floppy. Once loaded from the floppy, the boot program will access -the card and change some setting in it, correcting the problem. After -that you may use either this boot program or the normal one, compiled -without this bugfix parameter, to boot the machine from the PROM chip. - -[Any recent Etherboot version should do, not just 4.6.10 - Ed.] diff --git a/gpxe/src/drivers/net/amd8111e.c b/gpxe/src/drivers/net/amd8111e.c index 1c41add1..1b1fdc19 100644 --- a/gpxe/src/drivers/net/amd8111e.c +++ b/gpxe/src/drivers/net/amd8111e.c @@ -28,6 +28,8 @@ * USA */ +FILE_LICENCE ( GPL2_OR_LATER ); + #include "etherboot.h" #include "nic.h" #include "mii.h" @@ -674,7 +676,7 @@ static int amd8111e_probe(struct nic *nic, struct pci_device *pdev) } static struct pci_device_id amd8111e_nics[] = { - PCI_ROM(0x1022, 0x7462, "amd8111e", "AMD8111E"), + PCI_ROM(0x1022, 0x7462, "amd8111e", "AMD8111E", 0), }; PCI_DRIVER ( amd8111e_driver, amd8111e_nics, PCI_NO_CLASS ); diff --git a/gpxe/src/drivers/net/amd8111e.h b/gpxe/src/drivers/net/amd8111e.h index 82b8f7a3..a402a63e 100644 --- a/gpxe/src/drivers/net/amd8111e.h +++ b/gpxe/src/drivers/net/amd8111e.h @@ -35,6 +35,8 @@ Revision History: 3.0.1 */ +FILE_LICENCE ( GPL2_OR_LATER ); + #ifndef _AMD811E_H #define _AMD811E_H diff --git a/gpxe/src/drivers/net/ath5k/ath5k.c b/gpxe/src/drivers/net/ath5k/ath5k.c new file mode 100644 index 00000000..37defce2 --- /dev/null +++ b/gpxe/src/drivers/net/ath5k/ath5k.c @@ -0,0 +1,1700 @@ +/* + * Copyright (c) 2002-2005 Sam Leffler, Errno Consulting + * Copyright (c) 2004-2005 Atheros Communications, Inc. + * Copyright (c) 2006 Devicescape Software, Inc. + * Copyright (c) 2007 Jiri Slaby <jirislaby@gmail.com> + * Copyright (c) 2007 Luis R. Rodriguez <mcgrof@winlab.rutgers.edu> + * + * Modified for gPXE, July 2009, by Joshua Oreman <oremanj@rwcr.net> + * Original from Linux kernel 2.6.30. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any + * redistribution must be conditioned upon including a substantially + * similar Disclaimer requirement for further binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * Alternatively, this software may be distributed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + * NO WARRANTY + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGES. + * + */ + +FILE_LICENCE ( BSD3 ); + +#include <stdlib.h> +#include <gpxe/malloc.h> +#include <gpxe/timer.h> +#include <gpxe/netdevice.h> +#include <gpxe/pci.h> +#include <gpxe/pci_io.h> + +#include "base.h" +#include "reg.h" + +#define ATH5K_CALIB_INTERVAL 10 /* Calibrate PHY every 10 seconds */ +#define ATH5K_RETRIES 4 /* Number of times to retry packet sends */ +#define ATH5K_DESC_ALIGN 16 /* Alignment for TX/RX descriptors */ + +/******************\ +* Internal defines * +\******************/ + +/* Known PCI ids */ +static struct pci_device_id ath5k_nics[] = { + PCI_ROM(0x168c, 0x0207, "ath5210e", "Atheros 5210 early", AR5K_AR5210), + PCI_ROM(0x168c, 0x0007, "ath5210", "Atheros 5210", AR5K_AR5210), + PCI_ROM(0x168c, 0x0011, "ath5311", "Atheros 5311 (AHB)", AR5K_AR5211), + PCI_ROM(0x168c, 0x0012, "ath5211", "Atheros 5211", AR5K_AR5211), + PCI_ROM(0x168c, 0x0013, "ath5212", "Atheros 5212", AR5K_AR5212), + PCI_ROM(0xa727, 0x0013, "ath5212c","3com Ath 5212", AR5K_AR5212), + PCI_ROM(0x10b7, 0x0013, "rdag675", "3com 3CRDAG675", AR5K_AR5212), + PCI_ROM(0x168c, 0x1014, "ath5212m", "Ath 5212 miniPCI", AR5K_AR5212), + PCI_ROM(0x168c, 0x0014, "ath5212x14", "Atheros 5212 x14", AR5K_AR5212), + PCI_ROM(0x168c, 0x0015, "ath5212x15", "Atheros 5212 x15", AR5K_AR5212), + PCI_ROM(0x168c, 0x0016, "ath5212x16", "Atheros 5212 x16", AR5K_AR5212), + PCI_ROM(0x168c, 0x0017, "ath5212x17", "Atheros 5212 x17", AR5K_AR5212), + PCI_ROM(0x168c, 0x0018, "ath5212x18", "Atheros 5212 x18", AR5K_AR5212), + PCI_ROM(0x168c, 0x0019, "ath5212x19", "Atheros 5212 x19", AR5K_AR5212), + PCI_ROM(0x168c, 0x001a, "ath2413", "Atheros 2413 Griffin", AR5K_AR5212), + PCI_ROM(0x168c, 0x001b, "ath5413", "Atheros 5413 Eagle", AR5K_AR5212), + PCI_ROM(0x168c, 0x001c, "ath5212e", "Atheros 5212 PCI-E", AR5K_AR5212), + PCI_ROM(0x168c, 0x001d, "ath2417", "Atheros 2417 Nala", AR5K_AR5212), +}; + +/* Known SREVs */ +static const struct ath5k_srev_name srev_names[] = { + { "5210", AR5K_VERSION_MAC, AR5K_SREV_AR5210 }, + { "5311", AR5K_VERSION_MAC, AR5K_SREV_AR5311 }, + { "5311A", AR5K_VERSION_MAC, AR5K_SREV_AR5311A }, + { "5311B", AR5K_VERSION_MAC, AR5K_SREV_AR5311B }, + { "5211", AR5K_VERSION_MAC, AR5K_SREV_AR5211 }, + { "5212", AR5K_VERSION_MAC, AR5K_SREV_AR5212 }, + { "5213", AR5K_VERSION_MAC, AR5K_SREV_AR5213 }, + { "5213A", AR5K_VERSION_MAC, AR5K_SREV_AR5213A }, + { "2413", AR5K_VERSION_MAC, AR5K_SREV_AR2413 }, + { "2414", AR5K_VERSION_MAC, AR5K_SREV_AR2414 }, + { "5424", AR5K_VERSION_MAC, AR5K_SREV_AR5424 }, + { "5413", AR5K_VERSION_MAC, AR5K_SREV_AR5413 }, + { "5414", AR5K_VERSION_MAC, AR5K_SREV_AR5414 }, + { "2415", AR5K_VERSION_MAC, AR5K_SREV_AR2415 }, + { "5416", AR5K_VERSION_MAC, AR5K_SREV_AR5416 }, + { "5418", AR5K_VERSION_MAC, AR5K_SREV_AR5418 }, + { "2425", AR5K_VERSION_MAC, AR5K_SREV_AR2425 }, + { "2417", AR5K_VERSION_MAC, AR5K_SREV_AR2417 }, + { "xxxxx", AR5K_VERSION_MAC, AR5K_SREV_UNKNOWN }, + { "5110", AR5K_VERSION_RAD, AR5K_SREV_RAD_5110 }, + { "5111", AR5K_VERSION_RAD, AR5K_SREV_RAD_5111 }, + { "5111A", AR5K_VERSION_RAD, AR5K_SREV_RAD_5111A }, + { "2111", AR5K_VERSION_RAD, AR5K_SREV_RAD_2111 }, + { "5112", AR5K_VERSION_RAD, AR5K_SREV_RAD_5112 }, + { "5112A", AR5K_VERSION_RAD, AR5K_SREV_RAD_5112A }, + { "5112B", AR5K_VERSION_RAD, AR5K_SREV_RAD_5112B }, + { "2112", AR5K_VERSION_RAD, AR5K_SREV_RAD_2112 }, + { "2112A", AR5K_VERSION_RAD, AR5K_SREV_RAD_2112A }, + { "2112B", AR5K_VERSION_RAD, AR5K_SREV_RAD_2112B }, + { "2413", AR5K_VERSION_RAD, AR5K_SREV_RAD_2413 }, + { "5413", AR5K_VERSION_RAD, AR5K_SREV_RAD_5413 }, + { "2316", AR5K_VERSION_RAD, AR5K_SREV_RAD_2316 }, + { "2317", AR5K_VERSION_RAD, AR5K_SREV_RAD_2317 }, + { "5424", AR5K_VERSION_RAD, AR5K_SREV_RAD_5424 }, + { "5133", AR5K_VERSION_RAD, AR5K_SREV_RAD_5133 }, + { "xxxxx", AR5K_VERSION_RAD, AR5K_SREV_UNKNOWN }, +}; + +#define ATH5K_SPMBL_NO 1 +#define ATH5K_SPMBL_YES 2 +#define ATH5K_SPMBL_BOTH 3 + +static const struct { + u16 bitrate; + u8 short_pmbl; + u8 hw_code; +} ath5k_rates[] = { + { 10, ATH5K_SPMBL_BOTH, ATH5K_RATE_CODE_1M }, + { 20, ATH5K_SPMBL_NO, ATH5K_RATE_CODE_2M }, + { 55, ATH5K_SPMBL_NO, ATH5K_RATE_CODE_5_5M }, + { 110, ATH5K_SPMBL_NO, ATH5K_RATE_CODE_11M }, + { 60, ATH5K_SPMBL_BOTH, ATH5K_RATE_CODE_6M }, + { 90, ATH5K_SPMBL_BOTH, ATH5K_RATE_CODE_9M }, + { 120, ATH5K_SPMBL_BOTH, ATH5K_RATE_CODE_12M }, + { 180, ATH5K_SPMBL_BOTH, ATH5K_RATE_CODE_18M }, + { 240, ATH5K_SPMBL_BOTH, ATH5K_RATE_CODE_24M }, + { 360, ATH5K_SPMBL_BOTH, ATH5K_RATE_CODE_36M }, + { 480, ATH5K_SPMBL_BOTH, ATH5K_RATE_CODE_48M }, + { 540, ATH5K_SPMBL_BOTH, ATH5K_RATE_CODE_54M }, + { 20, ATH5K_SPMBL_YES, ATH5K_RATE_CODE_2M | AR5K_SET_SHORT_PREAMBLE }, + { 55, ATH5K_SPMBL_YES, ATH5K_RATE_CODE_5_5M | AR5K_SET_SHORT_PREAMBLE }, + { 110, ATH5K_SPMBL_YES, ATH5K_RATE_CODE_11M | AR5K_SET_SHORT_PREAMBLE }, + { 0, 0, 0 }, +}; + +#define ATH5K_NR_RATES 15 + +/* + * Prototypes - PCI stack related functions + */ +static int ath5k_probe(struct pci_device *pdev, + const struct pci_device_id *id); +static void ath5k_remove(struct pci_device *pdev); + +struct pci_driver ath5k_pci_driver __pci_driver = { + .ids = ath5k_nics, + .id_count = sizeof(ath5k_nics) / sizeof(ath5k_nics[0]), + .probe = ath5k_probe, + .remove = ath5k_remove, +}; + + + +/* + * Prototypes - MAC 802.11 stack related functions + */ +static int ath5k_tx(struct net80211_device *dev, struct io_buffer *skb); +static int ath5k_reset(struct ath5k_softc *sc, struct net80211_channel *chan); +static int ath5k_reset_wake(struct ath5k_softc *sc); +static int ath5k_start(struct net80211_device *dev); +static void ath5k_stop(struct net80211_device *dev); +static int ath5k_config(struct net80211_device *dev, int changed); +static void ath5k_poll(struct net80211_device *dev); +static void ath5k_irq(struct net80211_device *dev, int enable); + +static struct net80211_device_operations ath5k_ops = { + .open = ath5k_start, + .close = ath5k_stop, + .transmit = ath5k_tx, + .poll = ath5k_poll, + .irq = ath5k_irq, + .config = ath5k_config, +}; + +/* + * Prototypes - Internal functions + */ +/* Attach detach */ +static int ath5k_attach(struct net80211_device *dev); +static void ath5k_detach(struct net80211_device *dev); +/* Channel/mode setup */ +static unsigned int ath5k_copy_channels(struct ath5k_hw *ah, + struct net80211_channel *channels, + unsigned int mode, + unsigned int max); +static int ath5k_setup_bands(struct net80211_device *dev); +static int ath5k_chan_set(struct ath5k_softc *sc, + struct net80211_channel *chan); +static void ath5k_setcurmode(struct ath5k_softc *sc, + unsigned int mode); +static void ath5k_mode_setup(struct ath5k_softc *sc); + +/* Descriptor setup */ +static int ath5k_desc_alloc(struct ath5k_softc *sc); +static void ath5k_desc_free(struct ath5k_softc *sc); +/* Buffers setup */ +static int ath5k_rxbuf_setup(struct ath5k_softc *sc, struct ath5k_buf *bf); +static int ath5k_txbuf_setup(struct ath5k_softc *sc, struct ath5k_buf *bf); + +static inline void ath5k_txbuf_free(struct ath5k_softc *sc, + struct ath5k_buf *bf) +{ + if (!bf->iob) + return; + + net80211_tx_complete(sc->dev, bf->iob, 0, ECANCELED); + bf->iob = NULL; +} + +static inline void ath5k_rxbuf_free(struct ath5k_softc *sc __unused, + struct ath5k_buf *bf) +{ + free_iob(bf->iob); + bf->iob = NULL; +} + +/* Queues setup */ +static int ath5k_txq_setup(struct ath5k_softc *sc, + int qtype, int subtype); +static void ath5k_txq_drainq(struct ath5k_softc *sc, + struct ath5k_txq *txq); +static void ath5k_txq_cleanup(struct ath5k_softc *sc); +static void ath5k_txq_release(struct ath5k_softc *sc); +/* Rx handling */ +static int ath5k_rx_start(struct ath5k_softc *sc); +static void ath5k_rx_stop(struct ath5k_softc *sc); +/* Tx handling */ +static void ath5k_tx_processq(struct ath5k_softc *sc, + struct ath5k_txq *txq); + +/* Interrupt handling */ +static int ath5k_init(struct ath5k_softc *sc); +static int ath5k_stop_hw(struct ath5k_softc *sc); + +static void ath5k_calibrate(struct ath5k_softc *sc); + +/* Filter */ +static void ath5k_configure_filter(struct ath5k_softc *sc); + +/********************\ +* PCI Initialization * +\********************/ + +#if DBGLVL_MAX +static const char * +ath5k_chip_name(enum ath5k_srev_type type, u16 val) +{ + const char *name = "xxxxx"; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(srev_names); i++) { + if (srev_names[i].sr_type != type) + continue; + + if ((val & 0xf0) == srev_names[i].sr_val) + name = srev_names[i].sr_name; + + if ((val & 0xff) == srev_names[i].sr_val) { + name = srev_names[i].sr_name; + break; + } + } + + return name; +} +#endif + +static int ath5k_probe(struct pci_device *pdev, + const struct pci_device_id *id) +{ + void *mem; + struct ath5k_softc *sc; + struct net80211_device *dev; + int ret; + u8 csz; + + adjust_pci_device(pdev); + + /* + * Cache line size is used to size and align various + * structures used to communicate with the hardware. + */ + pci_read_config_byte(pdev, PCI_CACHE_LINE_SIZE, &csz); + if (csz == 0) { + /* + * We must have this setup properly for rx buffer + * DMA to work so force a reasonable value here if it + * comes up zero. + */ + csz = 16; + pci_write_config_byte(pdev, PCI_CACHE_LINE_SIZE, csz); + } + /* + * The default setting of latency timer yields poor results, + * set it to the value used by other systems. It may be worth + * tweaking this setting more. + */ + pci_write_config_byte(pdev, PCI_LATENCY_TIMER, 0xa8); + + /* + * Disable the RETRY_TIMEOUT register (0x41) to keep + * PCI Tx retries from interfering with C3 CPU state. + */ + pci_write_config_byte(pdev, 0x41, 0); + + mem = ioremap(pdev->membase, 0x10000); + if (!mem) { + DBG("ath5k: cannot remap PCI memory region\n"); + ret = -EIO; + goto err; + } + + /* + * Allocate dev (net80211 main struct) + * and dev->priv (driver private data) + */ + dev = net80211_alloc(sizeof(*sc)); + if (!dev) { + DBG("ath5k: cannot allocate 802.11 device\n"); + ret = -ENOMEM; + goto err_map; + } + + /* Initialize driver private data */ + sc = dev->priv; + sc->dev = dev; + sc->pdev = pdev; + + sc->hwinfo = zalloc(sizeof(*sc->hwinfo)); + if (!sc->hwinfo) { + DBG("ath5k: cannot allocate 802.11 hardware info structure\n"); + ret = -ENOMEM; + goto err_free; + } + + sc->hwinfo->flags = NET80211_HW_RX_HAS_FCS; + sc->hwinfo->signal_type = NET80211_SIGNAL_DB; + sc->hwinfo->signal_max = 40; /* 35dB should give perfect 54Mbps */ + sc->hwinfo->channel_change_time = 5000; + + /* Avoid working with the device until setup is complete */ + sc->status |= ATH_STAT_INVALID; + + sc->iobase = mem; + sc->cachelsz = csz * 4; /* convert to bytes */ + + DBG("ath5k: register base at %p (%08lx)\n", sc->iobase, pdev->membase); + DBG("ath5k: cache line size %d\n", sc->cachelsz); + + /* Set private data */ + pci_set_drvdata(pdev, dev); + dev->netdev->dev = (struct device *)pdev; + + /* Initialize device */ + ret = ath5k_hw_attach(sc, id->driver_data, &sc->ah); + if (ret) + goto err_free_hwinfo; + + /* Finish private driver data initialization */ + ret = ath5k_attach(dev); + if (ret) + goto err_ah; + +#if DBGLVL_MAX + DBG("Atheros AR%s chip found (MAC: 0x%x, PHY: 0x%x)\n", + ath5k_chip_name(AR5K_VERSION_MAC, sc->ah->ah_mac_srev), + sc->ah->ah_mac_srev, sc->ah->ah_phy_revision); + + if (!sc->ah->ah_single_chip) { + /* Single chip radio (!RF5111) */ + if (sc->ah->ah_radio_5ghz_revision && + !sc->ah->ah_radio_2ghz_revision) { + /* No 5GHz support -> report 2GHz radio */ + if (!(sc->ah->ah_capabilities.cap_mode & AR5K_MODE_BIT_11A)) { + DBG("RF%s 2GHz radio found (0x%x)\n", + ath5k_chip_name(AR5K_VERSION_RAD, + sc->ah->ah_radio_5ghz_revision), + sc->ah->ah_radio_5ghz_revision); + /* No 2GHz support (5110 and some + * 5Ghz only cards) -> report 5Ghz radio */ + } else if (!(sc->ah->ah_capabilities.cap_mode & AR5K_MODE_BIT_11B)) { + DBG("RF%s 5GHz radio found (0x%x)\n", + ath5k_chip_name(AR5K_VERSION_RAD, + sc->ah->ah_radio_5ghz_revision), + sc->ah->ah_radio_5ghz_revision); + /* Multiband radio */ + } else { + DBG("RF%s multiband radio found (0x%x)\n", + ath5k_chip_name(AR5K_VERSION_RAD, + sc->ah->ah_radio_5ghz_revision), + sc->ah->ah_radio_5ghz_revision); + } + } + /* Multi chip radio (RF5111 - RF2111) -> + * report both 2GHz/5GHz radios */ + else if (sc->ah->ah_radio_5ghz_revision && + sc->ah->ah_radio_2ghz_revision) { + DBG("RF%s 5GHz radio found (0x%x)\n", + ath5k_chip_name(AR5K_VERSION_RAD, + sc->ah->ah_radio_5ghz_revision), + sc->ah->ah_radio_5ghz_revision); + DBG("RF%s 2GHz radio found (0x%x)\n", + ath5k_chip_name(AR5K_VERSION_RAD, + sc->ah->ah_radio_2ghz_revision), + sc->ah->ah_radio_2ghz_revision); + } + } +#endif + + /* Ready to go */ + sc->status &= ~ATH_STAT_INVALID; + + return 0; +err_ah: + ath5k_hw_detach(sc->ah); +err_free_hwinfo: + free(sc->hwinfo); +err_free: + net80211_free(dev); +err_map: + iounmap(mem); +err: + return ret; +} + +static void ath5k_remove(struct pci_device *pdev) +{ + struct net80211_device *dev = pci_get_drvdata(pdev); + struct ath5k_softc *sc = dev->priv; + + ath5k_detach(dev); + ath5k_hw_detach(sc->ah); + iounmap(sc->iobase); + free(sc->hwinfo); + net80211_free(dev); +} + + +/***********************\ +* Driver Initialization * +\***********************/ + +static int +ath5k_attach(struct net80211_device *dev) +{ + struct ath5k_softc *sc = dev->priv; + struct ath5k_hw *ah = sc->ah; + int ret; + + /* + * Collect the channel list. The 802.11 layer + * is resposible for filtering this list based + * on settings like the phy mode and regulatory + * domain restrictions. + */ + ret = ath5k_setup_bands(dev); + if (ret) { + DBG("ath5k: can't get channels\n"); + goto err; + } + + /* NB: setup here so ath5k_rate_update is happy */ + if (ah->ah_modes & AR5K_MODE_BIT_11A) + ath5k_setcurmode(sc, AR5K_MODE_11A); + else + ath5k_setcurmode(sc, AR5K_MODE_11B); + + /* + * Allocate tx+rx descriptors and populate the lists. + */ + ret = ath5k_desc_alloc(sc); + if (ret) { + DBG("ath5k: can't allocate descriptors\n"); + goto err; + } + + /* + * Allocate hardware transmit queues. Note that hw functions + * handle reseting these queues at the needed time. + */ + ret = ath5k_txq_setup(sc, AR5K_TX_QUEUE_DATA, AR5K_WME_AC_BE); + if (ret) { + DBG("ath5k: can't setup xmit queue\n"); + goto err_desc; + } + + sc->last_calib_ticks = currticks(); + + ret = ath5k_eeprom_read_mac(ah, sc->hwinfo->hwaddr); + if (ret) { + DBG("ath5k: unable to read address from EEPROM: 0x%04x\n", + sc->pdev->device); + goto err_queues; + } + + memset(sc->bssidmask, 0xff, ETH_ALEN); + ath5k_hw_set_bssid_mask(sc->ah, sc->bssidmask); + + ret = net80211_register(sc->dev, &ath5k_ops, sc->hwinfo); + if (ret) { + DBG("ath5k: can't register ieee80211 hw\n"); + goto err_queues; + } + + return 0; +err_queues: + ath5k_txq_release(sc); +err_desc: + ath5k_desc_free(sc); +err: + return ret; +} + +static void +ath5k_detach(struct net80211_device *dev) +{ + struct ath5k_softc *sc = dev->priv; + + net80211_unregister(dev); + ath5k_desc_free(sc); + ath5k_txq_release(sc); +} + + + + +/********************\ +* Channel/mode setup * +\********************/ + +/* + * Convert IEEE channel number to MHz frequency. + */ +static inline short +ath5k_ieee2mhz(short chan) +{ + if (chan < 14) + return 2407 + 5 * chan; + if (chan == 14) + return 2484; + if (chan < 27) + return 2212 + 20 * chan; + return 5000 + 5 * chan; +} + +static unsigned int +ath5k_copy_channels(struct ath5k_hw *ah, + struct net80211_channel *channels, + unsigned int mode, unsigned int max) +{ + unsigned int i, count, size, chfreq, freq, ch; + + if (!(ah->ah_modes & (1 << mode))) + return 0; + + switch (mode) { + case AR5K_MODE_11A: + case AR5K_MODE_11A_TURBO: + /* 1..220, but 2GHz frequencies are filtered by check_channel */ + size = 220; + chfreq = CHANNEL_5GHZ; + break; + case AR5K_MODE_11B: + case AR5K_MODE_11G: + case AR5K_MODE_11G_TURBO: + size = 26; + chfreq = CHANNEL_2GHZ; + break; + default: + return 0; + } + + for (i = 0, count = 0; i < size && max > 0; i++) { + ch = i + 1 ; + freq = ath5k_ieee2mhz(ch); + + /* Check if channel is supported by the chipset */ + if (!ath5k_channel_ok(ah, freq, chfreq)) + continue; + + /* Write channel info and increment counter */ + channels[count].center_freq = freq; + channels[count].maxpower = 0; /* use regulatory */ + channels[count].band = (chfreq == CHANNEL_2GHZ) ? + NET80211_BAND_2GHZ : NET80211_BAND_5GHZ; + switch (mode) { + case AR5K_MODE_11A: + case AR5K_MODE_11G: + channels[count].hw_value = chfreq | CHANNEL_OFDM; + break; + case AR5K_MODE_11A_TURBO: + case AR5K_MODE_11G_TURBO: + channels[count].hw_value = chfreq | + CHANNEL_OFDM | CHANNEL_TURBO; + break; + case AR5K_MODE_11B: + channels[count].hw_value = CHANNEL_B; + } + + count++; + max--; + } + + return count; +} + +static int +ath5k_setup_bands(struct net80211_device *dev) +{ + struct ath5k_softc *sc = dev->priv; + struct ath5k_hw *ah = sc->ah; + int max_c, count_c = 0; + int i; + int band; + + max_c = sizeof(sc->hwinfo->channels) / sizeof(sc->hwinfo->channels[0]); + + /* 2GHz band */ + if (sc->ah->ah_capabilities.cap_mode & AR5K_MODE_BIT_11G) { + /* G mode */ + band = NET80211_BAND_2GHZ; + sc->hwinfo->bands = NET80211_BAND_BIT_2GHZ; + sc->hwinfo->modes = (NET80211_MODE_G | NET80211_MODE_B); + + for (i = 0; i < 12; i++) + sc->hwinfo->rates[band][i] = ath5k_rates[i].bitrate; + sc->hwinfo->nr_rates[band] = 12; + + sc->hwinfo->nr_channels = + ath5k_copy_channels(ah, sc->hwinfo->channels, + AR5K_MODE_11G, max_c); + count_c = sc->hwinfo->nr_channels; + max_c -= count_c; + } else if (sc->ah->ah_capabilities.cap_mode & AR5K_MODE_BIT_11B) { + /* B mode */ + band = NET80211_BAND_2GHZ; + sc->hwinfo->bands = NET80211_BAND_BIT_2GHZ; + sc->hwinfo->modes = NET80211_MODE_B; + + for (i = 0; i < 4; i++) + sc->hwinfo->rates[band][i] = ath5k_rates[i].bitrate; + sc->hwinfo->nr_rates[band] = 4; + + sc->hwinfo->nr_channels = + ath5k_copy_channels(ah, sc->hwinfo->channels, + AR5K_MODE_11B, max_c); + count_c = sc->hwinfo->nr_channels; + max_c -= count_c; + } + + /* 5GHz band, A mode */ + if (sc->ah->ah_capabilities.cap_mode & AR5K_MODE_BIT_11A) { + band = NET80211_BAND_5GHZ; + sc->hwinfo->bands |= NET80211_BAND_BIT_5GHZ; + sc->hwinfo->modes |= NET80211_MODE_A; + + for (i = 0; i < 8; i++) + sc->hwinfo->rates[band][i] = ath5k_rates[i+4].bitrate; + sc->hwinfo->nr_rates[band] = 8; + + sc->hwinfo->nr_channels = + ath5k_copy_channels(ah, sc->hwinfo->channels, + AR5K_MODE_11B, max_c); + count_c = sc->hwinfo->nr_channels; + max_c -= count_c; + } + + return 0; +} + +/* + * Set/change channels. If the channel is really being changed, + * it's done by reseting the chip. To accomplish this we must + * first cleanup any pending DMA, then restart stuff after a la + * ath5k_init. + */ +static int +ath5k_chan_set(struct ath5k_softc *sc, struct net80211_channel *chan) +{ + if (chan->center_freq != sc->curchan->center_freq || + chan->hw_value != sc->curchan->hw_value) { + /* + * To switch channels clear any pending DMA operations; + * wait long enough for the RX fifo to drain, reset the + * hardware at the new frequency, and then re-enable + * the relevant bits of the h/w. + */ + DBG2("ath5k: resetting for channel change (%d -> %d MHz)\n", + sc->curchan->center_freq, chan->center_freq); + return ath5k_reset(sc, chan); + } + + return 0; +} + +static void +ath5k_setcurmode(struct ath5k_softc *sc, unsigned int mode) +{ + sc->curmode = mode; + + if (mode == AR5K_MODE_11A) { + sc->curband = NET80211_BAND_5GHZ; + } else { + sc->curband = NET80211_BAND_2GHZ; + } +} + +static void +ath5k_mode_setup(struct ath5k_softc *sc) +{ + struct ath5k_hw *ah = sc->ah; + u32 rfilt; + + /* configure rx filter */ + rfilt = sc->filter_flags; + ath5k_hw_set_rx_filter(ah, rfilt); + + if (ath5k_hw_hasbssidmask(ah)) + ath5k_hw_set_bssid_mask(ah, sc->bssidmask); + + /* configure operational mode */ + ath5k_hw_set_opmode(ah); + + ath5k_hw_set_mcast_filter(ah, 0, 0); +} + +static inline int +ath5k_hw_rix_to_bitrate(int hw_rix) +{ + int i; + + for (i = 0; i < ATH5K_NR_RATES; i++) { + if (ath5k_rates[i].hw_code == hw_rix) + return ath5k_rates[i].bitrate; + } + + DBG("ath5k: invalid rix %02x\n", hw_rix); + return 10; /* use lowest rate */ +} + +int ath5k_bitrate_to_hw_rix(int bitrate) +{ + int i; + + for (i = 0; i < ATH5K_NR_RATES; i++) { + if (ath5k_rates[i].bitrate == bitrate) + return ath5k_rates[i].hw_code; + } + + DBG("ath5k: invalid bitrate %d\n", bitrate); + return ATH5K_RATE_CODE_1M; /* use lowest rate */ +} + +/***************\ +* Buffers setup * +\***************/ + +static struct io_buffer * +ath5k_rx_iob_alloc(struct ath5k_softc *sc, u32 *iob_addr) +{ + struct io_buffer *iob; + unsigned int off; + + /* + * Allocate buffer with headroom_needed space for the + * fake physical layer header at the start. + */ + iob = alloc_iob(sc->rxbufsize + sc->cachelsz - 1); + + if (!iob) { + DBG("ath5k: can't alloc iobuf of size %d\n", + sc->rxbufsize + sc->cachelsz - 1); + return NULL; + } + + *iob_addr = virt_to_bus(iob->data); + + /* + * Cache-line-align. This is important (for the + * 5210 at least) as not doing so causes bogus data + * in rx'd frames. + */ + off = *iob_addr % sc->cachelsz; + if (off != 0) { + iob_reserve(iob, sc->cachelsz - off); + *iob_addr += sc->cachelsz - off; + } + + return iob; +} + +static int +ath5k_rxbuf_setup(struct ath5k_softc *sc, struct ath5k_buf *bf) +{ + struct ath5k_hw *ah = sc->ah; + struct io_buffer *iob = bf->iob; + struct ath5k_desc *ds; + + if (!iob) { + iob = ath5k_rx_iob_alloc(sc, &bf->iobaddr); + if (!iob) + return -ENOMEM; + bf->iob = iob; + } + + /* + * Setup descriptors. For receive we always terminate + * the descriptor list with a self-linked entry so we'll + * not get overrun under high load (as can happen with a + * 5212 when ANI processing enables PHY error frames). + * + * To insure the last descriptor is self-linked we create + * each descriptor as self-linked and add it to the end. As + * each additional descriptor is added the previous self-linked + * entry is ``fixed'' naturally. This should be safe even + * if DMA is happening. When processing RX interrupts we + * never remove/process the last, self-linked, entry on the + * descriptor list. This insures the hardware always has + * someplace to write a new frame. + */ + ds = bf->desc; + ds->ds_link = bf->daddr; /* link to self */ + ds->ds_data = bf->iobaddr; + if (ah->ah_setup_rx_desc(ah, ds, + iob_tailroom(iob), /* buffer size */ + 0) != 0) { + DBG("ath5k: error setting up RX descriptor for %d bytes\n", iob_tailroom(iob)); + return -EINVAL; + } + + if (sc->rxlink != NULL) + *sc->rxlink = bf->daddr; + sc->rxlink = &ds->ds_link; + return 0; +} + +static int +ath5k_txbuf_setup(struct ath5k_softc *sc, struct ath5k_buf *bf) +{ + struct ath5k_hw *ah = sc->ah; + struct ath5k_txq *txq = &sc->txq; + struct ath5k_desc *ds = bf->desc; + struct io_buffer *iob = bf->iob; + unsigned int pktlen, flags; + int ret; + u16 duration = 0; + u16 cts_rate = 0; + + flags = AR5K_TXDESC_INTREQ | AR5K_TXDESC_CLRDMASK; + bf->iobaddr = virt_to_bus(iob->data); + pktlen = iob_len(iob); + + /* FIXME: If we are in g mode and rate is a CCK rate + * subtract ah->ah_txpower.txp_cck_ofdm_pwr_delta + * from tx power (value is in dB units already) */ + if (sc->dev->phy_flags & NET80211_PHY_USE_PROTECTION) { + struct net80211_device *dev = sc->dev; + + flags |= AR5K_TXDESC_CTSENA; + cts_rate = sc->hw_rtscts_rate; + duration = net80211_cts_duration(dev, pktlen); + } + ret = ah->ah_setup_tx_desc(ah, ds, pktlen, + IEEE80211_TYP_FRAME_HEADER_LEN, + AR5K_PKT_TYPE_NORMAL, sc->power_level * 2, + sc->hw_rate, ATH5K_RETRIES, + AR5K_TXKEYIX_INVALID, 0, flags, + cts_rate, duration); + if (ret) + return ret; + + ds->ds_link = 0; + ds->ds_data = bf->iobaddr; + + list_add_tail(&bf->list, &txq->q); + if (txq->link == NULL) /* is this first packet? */ + ath5k_hw_set_txdp(ah, txq->qnum, bf->daddr); + else /* no, so only link it */ + *txq->link = bf->daddr; + + txq->link = &ds->ds_link; + ath5k_hw_start_tx_dma(ah, txq->qnum); + mb(); + + return 0; +} + +/*******************\ +* Descriptors setup * +\*******************/ + +static int +ath5k_desc_alloc(struct ath5k_softc *sc) +{ + struct ath5k_desc *ds; + struct ath5k_buf *bf; + u32 da; + unsigned int i; + int ret; + + /* allocate descriptors */ + sc->desc_len = sizeof(struct ath5k_desc) * (ATH_TXBUF + ATH_RXBUF + 1); + sc->desc = malloc_dma(sc->desc_len, ATH5K_DESC_ALIGN); + if (sc->desc == NULL) { + DBG("ath5k: can't allocate descriptors\n"); + ret = -ENOMEM; + goto err; + } + memset(sc->desc, 0, sc->desc_len); + sc->desc_daddr = virt_to_bus(sc->desc); + + ds = sc->desc; + da = sc->desc_daddr; + + bf = calloc(ATH_TXBUF + ATH_RXBUF + 1, sizeof(struct ath5k_buf)); + if (bf == NULL) { + DBG("ath5k: can't allocate buffer pointers\n"); + ret = -ENOMEM; + goto err_free; + } + sc->bufptr = bf; + + INIT_LIST_HEAD(&sc->rxbuf); + for (i = 0; i < ATH_RXBUF; i++, bf++, ds++, da += sizeof(*ds)) { + bf->desc = ds; + bf->daddr = da; + list_add_tail(&bf->list, &sc->rxbuf); + } + + INIT_LIST_HEAD(&sc->txbuf); + sc->txbuf_len = ATH_TXBUF; + for (i = 0; i < ATH_TXBUF; i++, bf++, ds++, da += sizeof(*ds)) { + bf->desc = ds; + bf->daddr = da; + list_add_tail(&bf->list, &sc->txbuf); + } + + return 0; + +err_free: + free_dma(sc->desc, sc->desc_len); +err: + sc->desc = NULL; + return ret; +} + +static void +ath5k_desc_free(struct ath5k_softc *sc) +{ + struct ath5k_buf *bf; + + list_for_each_entry(bf, &sc->txbuf, list) + ath5k_txbuf_free(sc, bf); + list_for_each_entry(bf, &sc->rxbuf, list) + ath5k_rxbuf_free(sc, bf); + + /* Free memory associated with all descriptors */ + free_dma(sc->desc, sc->desc_len); + + free(sc->bufptr); + sc->bufptr = NULL; +} + + + + + +/**************\ +* Queues setup * +\**************/ + +static int +ath5k_txq_setup(struct ath5k_softc *sc, int qtype, int subtype) +{ + struct ath5k_hw *ah = sc->ah; + struct ath5k_txq *txq; + struct ath5k_txq_info qi = { + .tqi_subtype = subtype, + .tqi_aifs = AR5K_TXQ_USEDEFAULT, + .tqi_cw_min = AR5K_TXQ_USEDEFAULT, + .tqi_cw_max = AR5K_TXQ_USEDEFAULT + }; + int qnum; + + /* + * Enable interrupts only for EOL and DESC conditions. + * We mark tx descriptors to receive a DESC interrupt + * when a tx queue gets deep; otherwise waiting for the + * EOL to reap descriptors. Note that this is done to + * reduce interrupt load and this only defers reaping + * descriptors, never transmitting frames. Aside from + * reducing interrupts this also permits more concurrency. + * The only potential downside is if the tx queue backs + * up in which case the top half of the kernel may backup + * due to a lack of tx descriptors. + */ + qi.tqi_flags = AR5K_TXQ_FLAG_TXEOLINT_ENABLE | + AR5K_TXQ_FLAG_TXDESCINT_ENABLE; + qnum = ath5k_hw_setup_tx_queue(ah, qtype, &qi); + if (qnum < 0) { + DBG("ath5k: can't set up a TX queue\n"); + return -EIO; + } + + txq = &sc->txq; + if (!txq->setup) { + txq->qnum = qnum; + txq->link = NULL; + INIT_LIST_HEAD(&txq->q); + txq->setup = 1; + } + return 0; +} + +static void +ath5k_txq_drainq(struct ath5k_softc *sc, struct ath5k_txq *txq) +{ + struct ath5k_buf *bf, *bf0; + + list_for_each_entry_safe(bf, bf0, &txq->q, list) { + ath5k_txbuf_free(sc, bf); + + list_del(&bf->list); + list_add_tail(&bf->list, &sc->txbuf); + sc->txbuf_len++; + } + txq->link = NULL; +} + +/* + * Drain the transmit queues and reclaim resources. + */ +static void +ath5k_txq_cleanup(struct ath5k_softc *sc) +{ + struct ath5k_hw *ah = sc->ah; + + if (!(sc->status & ATH_STAT_INVALID)) { + /* don't touch the hardware if marked invalid */ + if (sc->txq.setup) { + ath5k_hw_stop_tx_dma(ah, sc->txq.qnum); + DBG("ath5k: txq [%d] %x, link %p\n", + sc->txq.qnum, + ath5k_hw_get_txdp(ah, sc->txq.qnum), + sc->txq.link); + } + } + + if (sc->txq.setup) + ath5k_txq_drainq(sc, &sc->txq); +} + +static void +ath5k_txq_release(struct ath5k_softc *sc) +{ + if (sc->txq.setup) { + ath5k_hw_release_tx_queue(sc->ah); + sc->txq.setup = 0; + } +} + + + + +/*************\ +* RX Handling * +\*************/ + +/* + * Enable the receive h/w following a reset. + */ +static int +ath5k_rx_start(struct ath5k_softc *sc) +{ + struct ath5k_hw *ah = sc->ah; + struct ath5k_buf *bf; + int ret; + + sc->rxbufsize = IEEE80211_MAX_LEN; + if (sc->rxbufsize % sc->cachelsz != 0) + sc->rxbufsize += sc->cachelsz - (sc->rxbufsize % sc->cachelsz); + + sc->rxlink = NULL; + + list_for_each_entry(bf, &sc->rxbuf, list) { + ret = ath5k_rxbuf_setup(sc, bf); + if (ret != 0) + return ret; + } + + bf = list_entry(sc->rxbuf.next, struct ath5k_buf, list); + + ath5k_hw_set_rxdp(ah, bf->daddr); + ath5k_hw_start_rx_dma(ah); /* enable recv descriptors */ + ath5k_mode_setup(sc); /* set filters, etc. */ + ath5k_hw_start_rx_pcu(ah); /* re-enable PCU/DMA engine */ + + return 0; +} + +/* + * Disable the receive h/w in preparation for a reset. + */ +static void +ath5k_rx_stop(struct ath5k_softc *sc) +{ + struct ath5k_hw *ah = sc->ah; + + ath5k_hw_stop_rx_pcu(ah); /* disable PCU */ + ath5k_hw_set_rx_filter(ah, 0); /* clear recv filter */ + ath5k_hw_stop_rx_dma(ah); /* disable DMA engine */ + + sc->rxlink = NULL; /* just in case */ +} + +static void +ath5k_handle_rx(struct ath5k_softc *sc) +{ + struct ath5k_rx_status rs; + struct io_buffer *iob, *next_iob; + u32 next_iob_addr; + struct ath5k_buf *bf, *bf_last; + struct ath5k_desc *ds; + int ret; + + memset(&rs, 0, sizeof(rs)); + + if (list_empty(&sc->rxbuf)) { + DBG("ath5k: empty rx buf pool\n"); + return; + } + + bf_last = list_entry(sc->rxbuf.prev, struct ath5k_buf, list); + + do { + bf = list_entry(sc->rxbuf.next, struct ath5k_buf, list); + assert(bf->iob != NULL); + iob = bf->iob; + ds = bf->desc; + + /* + * last buffer must not be freed to ensure proper hardware + * function. When the hardware finishes also a packet next to + * it, we are sure, it doesn't use it anymore and we can go on. + */ + if (bf_last == bf) + bf->flags |= 1; + if (bf->flags) { + struct ath5k_buf *bf_next = list_entry(bf->list.next, + struct ath5k_buf, list); + ret = sc->ah->ah_proc_rx_desc(sc->ah, bf_next->desc, + &rs); + if (ret) + break; + bf->flags &= ~1; + /* skip the overwritten one (even status is martian) */ + goto next; + } + + ret = sc->ah->ah_proc_rx_desc(sc->ah, ds, &rs); + if (ret) { + if (ret != -EINPROGRESS) { + DBG("ath5k: error in processing rx desc: %s\n", + strerror(ret)); + net80211_rx_err(sc->dev, NULL, -ret); + } else { + /* normal return, reached end of + available descriptors */ + } + return; + } + + if (rs.rs_more) { + DBG("ath5k: unsupported fragmented rx\n"); + goto next; + } + + if (rs.rs_status) { + if (rs.rs_status & AR5K_RXERR_PHY) { + /* These are uncommon, and may indicate a real problem. */ + net80211_rx_err(sc->dev, NULL, EIO); + goto next; + } + if (rs.rs_status & AR5K_RXERR_CRC) { + /* These occur *all the time*. */ + goto next; + } + if (rs.rs_status & AR5K_RXERR_DECRYPT) { + /* + * Decrypt error. If the error occurred + * because there was no hardware key, then + * let the frame through so the upper layers + * can process it. This is necessary for 5210 + * parts which have no way to setup a ``clear'' + * key cache entry. + * + * XXX do key cache faulting + */ + if (rs.rs_keyix == AR5K_RXKEYIX_INVALID && + !(rs.rs_status & AR5K_RXERR_CRC)) + goto accept; + } + + /* any other error, unhandled */ + DBG("ath5k: packet rx status %x\n", rs.rs_status); + goto next; + } +accept: + next_iob = ath5k_rx_iob_alloc(sc, &next_iob_addr); + + /* + * If we can't replace bf->iob with a new iob under memory + * pressure, just skip this packet + */ + if (!next_iob) { + DBG("ath5k: dropping packet under memory pressure\n"); + goto next; + } + + iob_put(iob, rs.rs_datalen); + + /* The MAC header is padded to have 32-bit boundary if the + * packet payload is non-zero. However, gPXE only + * supports standard 802.11 packets with 24-byte + * header, so no padding correction should be needed. + */ + + DBG2("ath5k: rx %d bytes, signal %d\n", rs.rs_datalen, + rs.rs_rssi); + + net80211_rx(sc->dev, iob, rs.rs_rssi, + ath5k_hw_rix_to_bitrate(rs.rs_rate)); + + bf->iob = next_iob; + bf->iobaddr = next_iob_addr; +next: + list_del(&bf->list); + list_add_tail(&bf->list, &sc->rxbuf); + } while (ath5k_rxbuf_setup(sc, bf) == 0); +} + + + + +/*************\ +* TX Handling * +\*************/ + +static void +ath5k_tx_processq(struct ath5k_softc *sc, struct ath5k_txq *txq) +{ + struct ath5k_tx_status ts; + struct ath5k_buf *bf, *bf0; + struct ath5k_desc *ds; + struct io_buffer *iob; + int ret; + + memset(&ts, 0, sizeof(ts)); + + list_for_each_entry_safe(bf, bf0, &txq->q, list) { + ds = bf->desc; + + ret = sc->ah->ah_proc_tx_desc(sc->ah, ds, &ts); + if (ret) { + if (ret != -EINPROGRESS) { + DBG("ath5k: error in processing tx desc: %s\n", + strerror(ret)); + } else { + /* normal return, reached end of tx completions */ + } + break; + } + + iob = bf->iob; + bf->iob = NULL; + + DBG2("ath5k: tx %d bytes complete, %d retries\n", + iob_len(iob), ts.ts_retry[0]); + + net80211_tx_complete(sc->dev, iob, ts.ts_retry[0], + ts.ts_status ? EIO : 0); + + list_del(&bf->list); + list_add_tail(&bf->list, &sc->txbuf); + sc->txbuf_len++; + } + + if (list_empty(&txq->q)) + txq->link = NULL; +} + +static void +ath5k_handle_tx(struct ath5k_softc *sc) +{ + ath5k_tx_processq(sc, &sc->txq); +} + + +/********************\ +* Interrupt handling * +\********************/ + +static void +ath5k_irq(struct net80211_device *dev, int enable) +{ + struct ath5k_softc *sc = dev->priv; + struct ath5k_hw *ah = sc->ah; + + sc->irq_ena = enable; + ah->ah_ier = enable ? AR5K_IER_ENABLE : AR5K_IER_DISABLE; + + ath5k_hw_reg_write(ah, ah->ah_ier, AR5K_IER); + ath5k_hw_set_imr(ah, sc->imask); +} + +static int +ath5k_init(struct ath5k_softc *sc) +{ + struct ath5k_hw *ah = sc->ah; + int ret, i; + + /* + * Stop anything previously setup. This is safe + * no matter this is the first time through or not. + */ + ath5k_stop_hw(sc); + + /* + * The basic interface to setting the hardware in a good + * state is ``reset''. On return the hardware is known to + * be powered up and with interrupts disabled. This must + * be followed by initialization of the appropriate bits + * and then setup of the interrupt mask. + */ + sc->curchan = sc->dev->channels + sc->dev->channel; + sc->curband = sc->curchan->band; + sc->imask = AR5K_INT_RXOK | AR5K_INT_RXERR | AR5K_INT_RXEOL | + AR5K_INT_RXORN | AR5K_INT_TXDESC | AR5K_INT_TXEOL | + AR5K_INT_FATAL | AR5K_INT_GLOBAL; + ret = ath5k_reset(sc, NULL); + if (ret) + goto done; + + ath5k_rfkill_hw_start(ah); + + /* + * Reset the key cache since some parts do not reset the + * contents on initial power up or resume from suspend. + */ + for (i = 0; i < AR5K_KEYTABLE_SIZE; i++) + ath5k_hw_reset_key(ah, i); + + /* Set ack to be sent at low bit-rates */ + ath5k_hw_set_ack_bitrate_high(ah, 0); + + ret = 0; +done: + mb(); + return ret; +} + +static int +ath5k_stop_hw(struct ath5k_softc *sc) +{ + struct ath5k_hw *ah = sc->ah; + + /* + * Shutdown the hardware and driver: + * stop output from above + * disable interrupts + * turn off timers + * turn off the radio + * clear transmit machinery + * clear receive machinery + * drain and release tx queues + * reclaim beacon resources + * power down hardware + * + * Note that some of this work is not possible if the + * hardware is gone (invalid). + */ + + if (!(sc->status & ATH_STAT_INVALID)) { + ath5k_hw_set_imr(ah, 0); + } + ath5k_txq_cleanup(sc); + if (!(sc->status & ATH_STAT_INVALID)) { + ath5k_rx_stop(sc); + ath5k_hw_phy_disable(ah); + } else + sc->rxlink = NULL; + + ath5k_rfkill_hw_stop(sc->ah); + + return 0; +} + +static void +ath5k_poll(struct net80211_device *dev) +{ + struct ath5k_softc *sc = dev->priv; + struct ath5k_hw *ah = sc->ah; + enum ath5k_int status; + unsigned int counter = 1000; + + if (currticks() - sc->last_calib_ticks > + ATH5K_CALIB_INTERVAL * ticks_per_sec()) { + ath5k_calibrate(sc); + sc->last_calib_ticks = currticks(); + } + + if ((sc->status & ATH_STAT_INVALID) || + (sc->irq_ena && !ath5k_hw_is_intr_pending(ah))) + return; + + do { + ath5k_hw_get_isr(ah, &status); /* NB: clears IRQ too */ + DBGP("ath5k: status %#x/%#x\n", status, sc->imask); + if (status & AR5K_INT_FATAL) { + /* + * Fatal errors are unrecoverable. + * Typically these are caused by DMA errors. + */ + DBG("ath5k: fatal error, resetting\n"); + ath5k_reset_wake(sc); + } else if (status & AR5K_INT_RXORN) { + DBG("ath5k: rx overrun, resetting\n"); + ath5k_reset_wake(sc); + } else { + if (status & AR5K_INT_RXEOL) { + /* + * NB: the hardware should re-read the link when + * RXE bit is written, but it doesn't work at + * least on older hardware revs. + */ + DBG("ath5k: rx EOL\n"); + sc->rxlink = NULL; + } + if (status & AR5K_INT_TXURN) { + /* bump tx trigger level */ + DBG("ath5k: tx underrun\n"); + ath5k_hw_update_tx_triglevel(ah, 1); + } + if (status & (AR5K_INT_RXOK | AR5K_INT_RXERR)) + ath5k_handle_rx(sc); + if (status & (AR5K_INT_TXOK | AR5K_INT_TXDESC + | AR5K_INT_TXERR | AR5K_INT_TXEOL)) + ath5k_handle_tx(sc); + } + } while (ath5k_hw_is_intr_pending(ah) && counter-- > 0); + + if (!counter) + DBG("ath5k: too many interrupts, giving up for now\n"); +} + +/* + * Periodically recalibrate the PHY to account + * for temperature/environment changes. + */ +static void +ath5k_calibrate(struct ath5k_softc *sc) +{ + struct ath5k_hw *ah = sc->ah; + + if (ath5k_hw_gainf_calibrate(ah) == AR5K_RFGAIN_NEED_CHANGE) { + /* + * Rfgain is out of bounds, reset the chip + * to load new gain values. + */ + DBG("ath5k: resetting for calibration\n"); + ath5k_reset_wake(sc); + } + if (ath5k_hw_phy_calibrate(ah, sc->curchan)) + DBG("ath5k: calibration of channel %d failed\n", + sc->curchan->channel_nr); +} + + +/********************\ +* Net80211 functions * +\********************/ + +static int +ath5k_tx(struct net80211_device *dev, struct io_buffer *iob) +{ + struct ath5k_softc *sc = dev->priv; + struct ath5k_buf *bf; + int rc; + + /* + * The hardware expects the header padded to 4 byte boundaries. + * gPXE only ever sends 24-byte headers, so no action necessary. + */ + + if (list_empty(&sc->txbuf)) { + DBG("ath5k: dropping packet because no tx bufs available\n"); + return -ENOBUFS; + } + + bf = list_entry(sc->txbuf.next, struct ath5k_buf, list); + list_del(&bf->list); + sc->txbuf_len--; + + bf->iob = iob; + + if ((rc = ath5k_txbuf_setup(sc, bf)) != 0) { + bf->iob = NULL; + list_add_tail(&bf->list, &sc->txbuf); + sc->txbuf_len++; + return rc; + } + return 0; +} + +/* + * Reset the hardware. If chan is not NULL, then also pause rx/tx + * and change to the given channel. + */ +static int +ath5k_reset(struct ath5k_softc *sc, struct net80211_channel *chan) +{ + struct ath5k_hw *ah = sc->ah; + int ret; + + if (chan) { + ath5k_hw_set_imr(ah, 0); + ath5k_txq_cleanup(sc); + ath5k_rx_stop(sc); + + sc->curchan = chan; + sc->curband = chan->band; + } + + ret = ath5k_hw_reset(ah, sc->curchan, 1); + if (ret) { + DBG("ath5k: can't reset hardware: %s\n", strerror(ret)); + return ret; + } + + ret = ath5k_rx_start(sc); + if (ret) { + DBG("ath5k: can't start rx logic: %s\n", strerror(ret)); + return ret; + } + + /* + * Change channels and update the h/w rate map if we're switching; + * e.g. 11a to 11b/g. + * + * We may be doing a reset in response to an ioctl that changes the + * channel so update any state that might change as a result. + * + * XXX needed? + */ +/* ath5k_chan_change(sc, c); */ + + /* Reenable interrupts if necessary */ + ath5k_irq(sc->dev, sc->irq_ena); + + return 0; +} + +static int ath5k_reset_wake(struct ath5k_softc *sc) +{ + return ath5k_reset(sc, sc->curchan); +} + +static int ath5k_start(struct net80211_device *dev) +{ + struct ath5k_softc *sc = dev->priv; + int ret; + + if ((ret = ath5k_init(sc)) != 0) + return ret; + + sc->assoc = 0; + ath5k_configure_filter(sc); + ath5k_hw_set_lladdr(sc->ah, dev->netdev->ll_addr); + + return 0; +} + +static void ath5k_stop(struct net80211_device *dev) +{ + struct ath5k_softc *sc = dev->priv; + u8 mac[ETH_ALEN] = {}; + + ath5k_hw_set_lladdr(sc->ah, mac); + + ath5k_stop_hw(sc); +} + +static int +ath5k_config(struct net80211_device *dev, int changed) +{ + struct ath5k_softc *sc = dev->priv; + struct ath5k_hw *ah = sc->ah; + struct net80211_channel *chan = &dev->channels[dev->channel]; + int ret; + + if (changed & NET80211_CFG_CHANNEL) { + sc->power_level = chan->maxpower; + if ((ret = ath5k_chan_set(sc, chan)) != 0) + return ret; + } + + if ((changed & NET80211_CFG_RATE) || + (changed & NET80211_CFG_PHY_PARAMS)) { + int spmbl = ATH5K_SPMBL_NO; + u16 rate = dev->rates[dev->rate]; + u16 slowrate = dev->rates[dev->rtscts_rate]; + int i; + + if (dev->phy_flags & NET80211_PHY_USE_SHORT_PREAMBLE) + spmbl = ATH5K_SPMBL_YES; + + for (i = 0; i < ATH5K_NR_RATES; i++) { + if (ath5k_rates[i].bitrate == rate && + (ath5k_rates[i].short_pmbl & spmbl)) + sc->hw_rate = ath5k_rates[i].hw_code; + + if (ath5k_rates[i].bitrate == slowrate && + (ath5k_rates[i].short_pmbl & spmbl)) + sc->hw_rtscts_rate = ath5k_rates[i].hw_code; + } + } + + if (changed & NET80211_CFG_ASSOC) { + sc->assoc = !!(dev->state & NET80211_ASSOCIATED); + if (sc->assoc) { + memcpy(ah->ah_bssid, dev->bssid, ETH_ALEN); + } else { + memset(ah->ah_bssid, 0xff, ETH_ALEN); + } + ath5k_hw_set_associd(ah, ah->ah_bssid, 0); + } + + return 0; +} + +/* + * o always accept unicast, broadcast, and multicast traffic + * o multicast traffic for all BSSIDs will be enabled if mac80211 + * says it should be + * o maintain current state of phy ofdm or phy cck error reception. + * If the hardware detects any of these type of errors then + * ath5k_hw_get_rx_filter() will pass to us the respective + * hardware filters to be able to receive these type of frames. + * o probe request frames are accepted only when operating in + * hostap, adhoc, or monitor modes + * o enable promiscuous mode according to the interface state + * o accept beacons: + * - when operating in adhoc mode so the 802.11 layer creates + * node table entries for peers, + * - when operating in station mode for collecting rssi data when + * the station is otherwise quiet, or + * - when scanning + */ +static void ath5k_configure_filter(struct ath5k_softc *sc) +{ + struct ath5k_hw *ah = sc->ah; + u32 mfilt[2], rfilt; + + /* Enable all multicast */ + mfilt[0] = ~0; + mfilt[1] = ~0; + + /* Enable data frames and beacons */ + rfilt = (AR5K_RX_FILTER_UCAST | AR5K_RX_FILTER_BCAST | + AR5K_RX_FILTER_MCAST | AR5K_RX_FILTER_BEACON); + + /* Set filters */ + ath5k_hw_set_rx_filter(ah, rfilt); + + /* Set multicast bits */ + ath5k_hw_set_mcast_filter(ah, mfilt[0], mfilt[1]); + + /* Set the cached hw filter flags, this will alter actually + * be set in HW */ + sc->filter_flags = rfilt; +} diff --git a/gpxe/src/drivers/net/ath5k/ath5k.h b/gpxe/src/drivers/net/ath5k/ath5k.h new file mode 100644 index 00000000..e54433d7 --- /dev/null +++ b/gpxe/src/drivers/net/ath5k/ath5k.h @@ -0,0 +1,1279 @@ +/* + * Copyright (c) 2004-2007 Reyk Floeter <reyk@openbsd.org> + * Copyright (c) 2006-2007 Nick Kossifidis <mickflemm@gmail.com> + * + * Modified for gPXE, July 2009, by Joshua Oreman <oremanj@rwcr.net> + * Original from Linux kernel 2.6.30. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _ATH5K_H +#define _ATH5K_H + +FILE_LICENCE ( MIT ); + +#include <stddef.h> +#include <byteswap.h> +#include <gpxe/io.h> +#include <gpxe/netdevice.h> +#include <gpxe/net80211.h> +#include <errno.h> + +/* Keep all ath5k files under one errfile ID */ +#undef ERRFILE +#define ERRFILE ERRFILE_ath5k + +#define ARRAY_SIZE(a) (sizeof(a)/sizeof((a)[0])) + +/* RX/TX descriptor hw structs */ +#include "desc.h" + +/* EEPROM structs/offsets */ +#include "eeprom.h" + +/* PCI IDs */ +#define PCI_DEVICE_ID_ATHEROS_AR5210 0x0007 /* AR5210 */ +#define PCI_DEVICE_ID_ATHEROS_AR5311 0x0011 /* AR5311 */ +#define PCI_DEVICE_ID_ATHEROS_AR5211 0x0012 /* AR5211 */ +#define PCI_DEVICE_ID_ATHEROS_AR5212 0x0013 /* AR5212 */ +#define PCI_DEVICE_ID_3COM_3CRDAG675 0x0013 /* 3CRDAG675 (Atheros AR5212) */ +#define PCI_DEVICE_ID_3COM_2_3CRPAG175 0x0013 /* 3CRPAG175 (Atheros AR5212) */ +#define PCI_DEVICE_ID_ATHEROS_AR5210_AP 0x0207 /* AR5210 (Early) */ +#define PCI_DEVICE_ID_ATHEROS_AR5212_IBM 0x1014 /* AR5212 (IBM MiniPCI) */ +#define PCI_DEVICE_ID_ATHEROS_AR5210_DEFAULT 0x1107 /* AR5210 (no eeprom) */ +#define PCI_DEVICE_ID_ATHEROS_AR5212_DEFAULT 0x1113 /* AR5212 (no eeprom) */ +#define PCI_DEVICE_ID_ATHEROS_AR5211_DEFAULT 0x1112 /* AR5211 (no eeprom) */ +#define PCI_DEVICE_ID_ATHEROS_AR5212_FPGA 0xf013 /* AR5212 (emulation board) */ +#define PCI_DEVICE_ID_ATHEROS_AR5211_LEGACY 0xff12 /* AR5211 (emulation board) */ +#define PCI_DEVICE_ID_ATHEROS_AR5211_FPGA11B 0xf11b /* AR5211 (emulation board) */ +#define PCI_DEVICE_ID_ATHEROS_AR5312_REV2 0x0052 /* AR5312 WMAC (AP31) */ +#define PCI_DEVICE_ID_ATHEROS_AR5312_REV7 0x0057 /* AR5312 WMAC (AP30-040) */ +#define PCI_DEVICE_ID_ATHEROS_AR5312_REV8 0x0058 /* AR5312 WMAC (AP43-030) */ +#define PCI_DEVICE_ID_ATHEROS_AR5212_0014 0x0014 /* AR5212 compatible */ +#define PCI_DEVICE_ID_ATHEROS_AR5212_0015 0x0015 /* AR5212 compatible */ +#define PCI_DEVICE_ID_ATHEROS_AR5212_0016 0x0016 /* AR5212 compatible */ +#define PCI_DEVICE_ID_ATHEROS_AR5212_0017 0x0017 /* AR5212 compatible */ +#define PCI_DEVICE_ID_ATHEROS_AR5212_0018 0x0018 /* AR5212 compatible */ +#define PCI_DEVICE_ID_ATHEROS_AR5212_0019 0x0019 /* AR5212 compatible */ +#define PCI_DEVICE_ID_ATHEROS_AR2413 0x001a /* AR2413 (Griffin-lite) */ +#define PCI_DEVICE_ID_ATHEROS_AR5413 0x001b /* AR5413 (Eagle) */ +#define PCI_DEVICE_ID_ATHEROS_AR5424 0x001c /* AR5424 (Condor PCI-E) */ +#define PCI_DEVICE_ID_ATHEROS_AR5416 0x0023 /* AR5416 */ +#define PCI_DEVICE_ID_ATHEROS_AR5418 0x0024 /* AR5418 */ + +/****************************\ + GENERIC DRIVER DEFINITIONS +\****************************/ + +/* + * AR5K REGISTER ACCESS + */ + +/* Some macros to read/write fields */ + +/* First shift, then mask */ +#define AR5K_REG_SM(_val, _flags) \ + (((_val) << _flags##_S) & (_flags)) + +/* First mask, then shift */ +#define AR5K_REG_MS(_val, _flags) \ + (((_val) & (_flags)) >> _flags##_S) + +/* Some registers can hold multiple values of interest. For this + * reason when we want to write to these registers we must first + * retrieve the values which we do not want to clear (lets call this + * old_data) and then set the register with this and our new_value: + * ( old_data | new_value) */ +#define AR5K_REG_WRITE_BITS(ah, _reg, _flags, _val) \ + ath5k_hw_reg_write(ah, (ath5k_hw_reg_read(ah, _reg) & ~(_flags)) | \ + (((_val) << _flags##_S) & (_flags)), _reg) + +#define AR5K_REG_MASKED_BITS(ah, _reg, _flags, _mask) \ + ath5k_hw_reg_write(ah, (ath5k_hw_reg_read(ah, _reg) & \ + (_mask)) | (_flags), _reg) + +#define AR5K_REG_ENABLE_BITS(ah, _reg, _flags) \ + ath5k_hw_reg_write(ah, ath5k_hw_reg_read(ah, _reg) | (_flags), _reg) + +#define AR5K_REG_DISABLE_BITS(ah, _reg, _flags) \ + ath5k_hw_reg_write(ah, ath5k_hw_reg_read(ah, _reg) & ~(_flags), _reg) + +/* Access to PHY registers */ +#define AR5K_PHY_READ(ah, _reg) \ + ath5k_hw_reg_read(ah, (ah)->ah_phy + ((_reg) << 2)) + +#define AR5K_PHY_WRITE(ah, _reg, _val) \ + ath5k_hw_reg_write(ah, _val, (ah)->ah_phy + ((_reg) << 2)) + +/* Access QCU registers per queue */ +#define AR5K_REG_READ_Q(ah, _reg, _queue) \ + (ath5k_hw_reg_read(ah, _reg) & (1 << _queue)) \ + +#define AR5K_REG_WRITE_Q(ah, _reg, _queue) \ + ath5k_hw_reg_write(ah, (1 << _queue), _reg) + +#define AR5K_Q_ENABLE_BITS(_reg, _queue) do { \ + _reg |= 1 << _queue; \ +} while (0) + +#define AR5K_Q_DISABLE_BITS(_reg, _queue) do { \ + _reg &= ~(1 << _queue); \ +} while (0) + +/* Used while writing initvals */ +#define AR5K_REG_WAIT(_i) do { \ + if (_i % 64) \ + udelay(1); \ +} while (0) + +/* Register dumps are done per operation mode */ +#define AR5K_INI_RFGAIN_5GHZ 0 +#define AR5K_INI_RFGAIN_2GHZ 1 + +/* TODO: Clean this up */ +#define AR5K_INI_VAL_11A 0 +#define AR5K_INI_VAL_11A_TURBO 1 +#define AR5K_INI_VAL_11B 2 +#define AR5K_INI_VAL_11G 3 +#define AR5K_INI_VAL_11G_TURBO 4 +#define AR5K_INI_VAL_XR 0 +#define AR5K_INI_VAL_MAX 5 + +/* Used for BSSID etc manipulation */ +#define AR5K_LOW_ID(_a)( \ +(_a)[0] | (_a)[1] << 8 | (_a)[2] << 16 | (_a)[3] << 24 \ +) + +#define AR5K_HIGH_ID(_a) ((_a)[4] | (_a)[5] << 8) + +#define IEEE80211_MAX_LEN 2352 + +/* + * Some tuneable values (these should be changeable by the user) + */ +#define AR5K_TUNE_DMA_BEACON_RESP 2 +#define AR5K_TUNE_SW_BEACON_RESP 10 +#define AR5K_TUNE_ADDITIONAL_SWBA_BACKOFF 0 +#define AR5K_TUNE_RADAR_ALERT 0 +#define AR5K_TUNE_MIN_TX_FIFO_THRES 1 +#define AR5K_TUNE_MAX_TX_FIFO_THRES ((IEEE80211_MAX_LEN / 64) + 1) +#define AR5K_TUNE_REGISTER_TIMEOUT 20000 +/* Register for RSSI threshold has a mask of 0xff, so 255 seems to + * be the max value. */ +#define AR5K_TUNE_RSSI_THRES 129 +/* This must be set when setting the RSSI threshold otherwise it can + * prevent a reset. If AR5K_RSSI_THR is read after writing to it + * the BMISS_THRES will be seen as 0, seems harware doesn't keep + * track of it. Max value depends on harware. For AR5210 this is just 7. + * For AR5211+ this seems to be up to 255. */ +#define AR5K_TUNE_BMISS_THRES 7 +#define AR5K_TUNE_REGISTER_DWELL_TIME 20000 +#define AR5K_TUNE_BEACON_INTERVAL 100 +#define AR5K_TUNE_AIFS 2 +#define AR5K_TUNE_AIFS_11B 2 +#define AR5K_TUNE_AIFS_XR 0 +#define AR5K_TUNE_CWMIN 15 +#define AR5K_TUNE_CWMIN_11B 31 +#define AR5K_TUNE_CWMIN_XR 3 +#define AR5K_TUNE_CWMAX 1023 +#define AR5K_TUNE_CWMAX_11B 1023 +#define AR5K_TUNE_CWMAX_XR 7 +#define AR5K_TUNE_NOISE_FLOOR -72 +#define AR5K_TUNE_MAX_TXPOWER 63 +#define AR5K_TUNE_DEFAULT_TXPOWER 25 +#define AR5K_TUNE_TPC_TXPOWER 0 +#define AR5K_TUNE_ANT_DIVERSITY 1 +#define AR5K_TUNE_HWTXTRIES 4 + +#define AR5K_INIT_CARR_SENSE_EN 1 + +/*Swap RX/TX Descriptor for big endian archs*/ +#if __BYTE_ORDER == __BIG_ENDIAN +#define AR5K_INIT_CFG ( \ + AR5K_CFG_SWTD | AR5K_CFG_SWRD \ +) +#else +#define AR5K_INIT_CFG 0x00000000 +#endif + +/* Initial values */ +#define AR5K_INIT_CYCRSSI_THR1 2 +#define AR5K_INIT_TX_LATENCY 502 +#define AR5K_INIT_USEC 39 +#define AR5K_INIT_USEC_TURBO 79 +#define AR5K_INIT_USEC_32 31 +#define AR5K_INIT_SLOT_TIME 396 +#define AR5K_INIT_SLOT_TIME_TURBO 480 +#define AR5K_INIT_ACK_CTS_TIMEOUT 1024 +#define AR5K_INIT_ACK_CTS_TIMEOUT_TURBO 0x08000800 +#define AR5K_INIT_PROG_IFS 920 +#define AR5K_INIT_PROG_IFS_TURBO 960 +#define AR5K_INIT_EIFS 3440 +#define AR5K_INIT_EIFS_TURBO 6880 +#define AR5K_INIT_SIFS 560 +#define AR5K_INIT_SIFS_TURBO 480 +#define AR5K_INIT_SH_RETRY 10 +#define AR5K_INIT_LG_RETRY AR5K_INIT_SH_RETRY +#define AR5K_INIT_SSH_RETRY 32 +#define AR5K_INIT_SLG_RETRY AR5K_INIT_SSH_RETRY +#define AR5K_INIT_TX_RETRY 10 + +#define AR5K_INIT_TRANSMIT_LATENCY ( \ + (AR5K_INIT_TX_LATENCY << 14) | (AR5K_INIT_USEC_32 << 7) | \ + (AR5K_INIT_USEC) \ +) +#define AR5K_INIT_TRANSMIT_LATENCY_TURBO ( \ + (AR5K_INIT_TX_LATENCY << 14) | (AR5K_INIT_USEC_32 << 7) | \ + (AR5K_INIT_USEC_TURBO) \ +) +#define AR5K_INIT_PROTO_TIME_CNTRL ( \ + (AR5K_INIT_CARR_SENSE_EN << 26) | (AR5K_INIT_EIFS << 12) | \ + (AR5K_INIT_PROG_IFS) \ +) +#define AR5K_INIT_PROTO_TIME_CNTRL_TURBO ( \ + (AR5K_INIT_CARR_SENSE_EN << 26) | (AR5K_INIT_EIFS_TURBO << 12) | \ + (AR5K_INIT_PROG_IFS_TURBO) \ +) + +/* token to use for aifs, cwmin, cwmax in MadWiFi */ +#define AR5K_TXQ_USEDEFAULT ((u32) -1) + +/* GENERIC CHIPSET DEFINITIONS */ + +/* MAC Chips */ +enum ath5k_version { + AR5K_AR5210 = 0, + AR5K_AR5211 = 1, + AR5K_AR5212 = 2, +}; + +/* PHY Chips */ +enum ath5k_radio { + AR5K_RF5110 = 0, + AR5K_RF5111 = 1, + AR5K_RF5112 = 2, + AR5K_RF2413 = 3, + AR5K_RF5413 = 4, + AR5K_RF2316 = 5, + AR5K_RF2317 = 6, + AR5K_RF2425 = 7, +}; + +/* + * Common silicon revision/version values + */ + +enum ath5k_srev_type { + AR5K_VERSION_MAC, + AR5K_VERSION_RAD, +}; + +struct ath5k_srev_name { + const char *sr_name; + enum ath5k_srev_type sr_type; + unsigned sr_val; +}; + +#define AR5K_SREV_UNKNOWN 0xffff + +#define AR5K_SREV_AR5210 0x00 /* Crete */ +#define AR5K_SREV_AR5311 0x10 /* Maui 1 */ +#define AR5K_SREV_AR5311A 0x20 /* Maui 2 */ +#define AR5K_SREV_AR5311B 0x30 /* Spirit */ +#define AR5K_SREV_AR5211 0x40 /* Oahu */ +#define AR5K_SREV_AR5212 0x50 /* Venice */ +#define AR5K_SREV_AR5213 0x55 /* ??? */ +#define AR5K_SREV_AR5213A 0x59 /* Hainan */ +#define AR5K_SREV_AR2413 0x78 /* Griffin lite */ +#define AR5K_SREV_AR2414 0x70 /* Griffin */ +#define AR5K_SREV_AR5424 0x90 /* Condor */ +#define AR5K_SREV_AR5413 0xa4 /* Eagle lite */ +#define AR5K_SREV_AR5414 0xa0 /* Eagle */ +#define AR5K_SREV_AR2415 0xb0 /* Talon */ +#define AR5K_SREV_AR5416 0xc0 /* PCI-E */ +#define AR5K_SREV_AR5418 0xca /* PCI-E */ +#define AR5K_SREV_AR2425 0xe0 /* Swan */ +#define AR5K_SREV_AR2417 0xf0 /* Nala */ + +#define AR5K_SREV_RAD_5110 0x00 +#define AR5K_SREV_RAD_5111 0x10 +#define AR5K_SREV_RAD_5111A 0x15 +#define AR5K_SREV_RAD_2111 0x20 +#define AR5K_SREV_RAD_5112 0x30 +#define AR5K_SREV_RAD_5112A 0x35 +#define AR5K_SREV_RAD_5112B 0x36 +#define AR5K_SREV_RAD_2112 0x40 +#define AR5K_SREV_RAD_2112A 0x45 +#define AR5K_SREV_RAD_2112B 0x46 +#define AR5K_SREV_RAD_2413 0x50 +#define AR5K_SREV_RAD_5413 0x60 +#define AR5K_SREV_RAD_2316 0x70 /* Cobra SoC */ +#define AR5K_SREV_RAD_2317 0x80 +#define AR5K_SREV_RAD_5424 0xa0 /* Mostly same as 5413 */ +#define AR5K_SREV_RAD_2425 0xa2 +#define AR5K_SREV_RAD_5133 0xc0 + +#define AR5K_SREV_PHY_5211 0x30 +#define AR5K_SREV_PHY_5212 0x41 +#define AR5K_SREV_PHY_5212A 0x42 +#define AR5K_SREV_PHY_5212B 0x43 +#define AR5K_SREV_PHY_2413 0x45 +#define AR5K_SREV_PHY_5413 0x61 +#define AR5K_SREV_PHY_2425 0x70 + +/* + * Some of this information is based on Documentation from: + * + * http://madwifi.org/wiki/ChipsetFeatures/SuperAG + * + * Modulation for Atheros' eXtended Range - range enhancing extension that is + * supposed to double the distance an Atheros client device can keep a + * connection with an Atheros access point. This is achieved by increasing + * the receiver sensitivity up to, -105dBm, which is about 20dB above what + * the 802.11 specifications demand. In addition, new (proprietary) data rates + * are introduced: 3, 2, 1, 0.5 and 0.25 MBit/s. + * + * Please note that can you either use XR or TURBO but you cannot use both, + * they are exclusive. + * + */ +#define MODULATION_XR 0x00000200 + +/* + * Modulation for Atheros' Turbo G and Turbo A, its supposed to provide a + * throughput transmission speed up to 40Mbit/s-60Mbit/s at a 108Mbit/s + * signaling rate achieved through the bonding of two 54Mbit/s 802.11g + * channels. To use this feature your Access Point must also suport it. + * There is also a distinction between "static" and "dynamic" turbo modes: + * + * - Static: is the dumb version: devices set to this mode stick to it until + * the mode is turned off. + * - Dynamic: is the intelligent version, the network decides itself if it + * is ok to use turbo. As soon as traffic is detected on adjacent channels + * (which would get used in turbo mode), or when a non-turbo station joins + * the network, turbo mode won't be used until the situation changes again. + * Dynamic mode is achieved by Atheros' Adaptive Radio (AR) feature which + * monitors the used radio band in order to decide whether turbo mode may + * be used or not. + * + * This article claims Super G sticks to bonding of channels 5 and 6 for + * USA: + * + * http://www.pcworld.com/article/id,113428-page,1/article.html + * + * The channel bonding seems to be driver specific though. In addition to + * deciding what channels will be used, these "Turbo" modes are accomplished + * by also enabling the following features: + * + * - Bursting: allows multiple frames to be sent at once, rather than pausing + * after each frame. Bursting is a standards-compliant feature that can be + * used with any Access Point. + * - Fast frames: increases the amount of information that can be sent per + * frame, also resulting in a reduction of transmission overhead. It is a + * proprietary feature that needs to be supported by the Access Point. + * - Compression: data frames are compressed in real time using a Lempel Ziv + * algorithm. This is done transparently. Once this feature is enabled, + * compression and decompression takes place inside the chipset, without + * putting additional load on the host CPU. + * + */ +#define MODULATION_TURBO 0x00000080 + +enum ath5k_driver_mode { + AR5K_MODE_11A = 0, + AR5K_MODE_11A_TURBO = 1, + AR5K_MODE_11B = 2, + AR5K_MODE_11G = 3, + AR5K_MODE_11G_TURBO = 4, + AR5K_MODE_XR = 5, +}; + +enum { + AR5K_MODE_BIT_11A = (1 << AR5K_MODE_11A), + AR5K_MODE_BIT_11A_TURBO = (1 << AR5K_MODE_11A_TURBO), + AR5K_MODE_BIT_11B = (1 << AR5K_MODE_11B), + AR5K_MODE_BIT_11G = (1 << AR5K_MODE_11G), + AR5K_MODE_BIT_11G_TURBO = (1 << AR5K_MODE_11G_TURBO), + AR5K_MODE_BIT_XR = (1 << AR5K_MODE_XR), +}; + +/****************\ + TX DEFINITIONS +\****************/ + +/* + * TX Status descriptor + */ +struct ath5k_tx_status { + u16 ts_seqnum; + u16 ts_tstamp; + u8 ts_status; + u8 ts_rate[4]; + u8 ts_retry[4]; + u8 ts_final_idx; + s8 ts_rssi; + u8 ts_shortretry; + u8 ts_longretry; + u8 ts_virtcol; + u8 ts_antenna; +} __attribute__ ((packed)); + +#define AR5K_TXSTAT_ALTRATE 0x80 +#define AR5K_TXERR_XRETRY 0x01 +#define AR5K_TXERR_FILT 0x02 +#define AR5K_TXERR_FIFO 0x04 + +/** + * enum ath5k_tx_queue - Queue types used to classify tx queues. + * @AR5K_TX_QUEUE_INACTIVE: q is unused -- see ath5k_hw_release_tx_queue + * @AR5K_TX_QUEUE_DATA: A normal data queue + * @AR5K_TX_QUEUE_XR_DATA: An XR-data queue + * @AR5K_TX_QUEUE_BEACON: The beacon queue + * @AR5K_TX_QUEUE_CAB: The after-beacon queue + * @AR5K_TX_QUEUE_UAPSD: Unscheduled Automatic Power Save Delivery queue + */ +enum ath5k_tx_queue { + AR5K_TX_QUEUE_INACTIVE = 0, + AR5K_TX_QUEUE_DATA, + AR5K_TX_QUEUE_XR_DATA, + AR5K_TX_QUEUE_BEACON, + AR5K_TX_QUEUE_CAB, + AR5K_TX_QUEUE_UAPSD, +}; + +/* + * Queue syb-types to classify normal data queues. + * These are the 4 Access Categories as defined in + * WME spec. 0 is the lowest priority and 4 is the + * highest. Normal data that hasn't been classified + * goes to the Best Effort AC. + */ +enum ath5k_tx_queue_subtype { + AR5K_WME_AC_BK = 0, /*Background traffic*/ + AR5K_WME_AC_BE, /*Best-effort (normal) traffic)*/ + AR5K_WME_AC_VI, /*Video traffic*/ + AR5K_WME_AC_VO, /*Voice traffic*/ +}; + +/* + * Queue ID numbers as returned by the hw functions, each number + * represents a hw queue. If hw does not support hw queues + * (eg 5210) all data goes in one queue. These match + * d80211 definitions (net80211/MadWiFi don't use them). + */ +enum ath5k_tx_queue_id { + AR5K_TX_QUEUE_ID_NOQCU_DATA = 0, + AR5K_TX_QUEUE_ID_NOQCU_BEACON = 1, + AR5K_TX_QUEUE_ID_DATA_MIN = 0, /*IEEE80211_TX_QUEUE_DATA0*/ + AR5K_TX_QUEUE_ID_DATA_MAX = 4, /*IEEE80211_TX_QUEUE_DATA4*/ + AR5K_TX_QUEUE_ID_DATA_SVP = 5, /*IEEE80211_TX_QUEUE_SVP - Spectralink Voice Protocol*/ + AR5K_TX_QUEUE_ID_CAB = 6, /*IEEE80211_TX_QUEUE_AFTER_BEACON*/ + AR5K_TX_QUEUE_ID_BEACON = 7, /*IEEE80211_TX_QUEUE_BEACON*/ + AR5K_TX_QUEUE_ID_UAPSD = 8, + AR5K_TX_QUEUE_ID_XR_DATA = 9, +}; + +/* + * Flags to set hw queue's parameters... + */ +#define AR5K_TXQ_FLAG_TXOKINT_ENABLE 0x0001 /* Enable TXOK interrupt */ +#define AR5K_TXQ_FLAG_TXERRINT_ENABLE 0x0002 /* Enable TXERR interrupt */ +#define AR5K_TXQ_FLAG_TXEOLINT_ENABLE 0x0004 /* Enable TXEOL interrupt -not used- */ +#define AR5K_TXQ_FLAG_TXDESCINT_ENABLE 0x0008 /* Enable TXDESC interrupt -not used- */ +#define AR5K_TXQ_FLAG_TXURNINT_ENABLE 0x0010 /* Enable TXURN interrupt */ +#define AR5K_TXQ_FLAG_CBRORNINT_ENABLE 0x0020 /* Enable CBRORN interrupt */ +#define AR5K_TXQ_FLAG_CBRURNINT_ENABLE 0x0040 /* Enable CBRURN interrupt */ +#define AR5K_TXQ_FLAG_QTRIGINT_ENABLE 0x0080 /* Enable QTRIG interrupt */ +#define AR5K_TXQ_FLAG_TXNOFRMINT_ENABLE 0x0100 /* Enable TXNOFRM interrupt */ +#define AR5K_TXQ_FLAG_BACKOFF_DISABLE 0x0200 /* Disable random post-backoff */ +#define AR5K_TXQ_FLAG_RDYTIME_EXP_POLICY_ENABLE 0x0300 /* Enable ready time expiry policy (?)*/ +#define AR5K_TXQ_FLAG_FRAG_BURST_BACKOFF_ENABLE 0x0800 /* Enable backoff while bursting */ +#define AR5K_TXQ_FLAG_POST_FR_BKOFF_DIS 0x1000 /* Disable backoff while bursting */ +#define AR5K_TXQ_FLAG_COMPRESSION_ENABLE 0x2000 /* Enable hw compression -not implemented-*/ + +/* + * A struct to hold tx queue's parameters + */ +struct ath5k_txq_info { + enum ath5k_tx_queue tqi_type; + enum ath5k_tx_queue_subtype tqi_subtype; + u16 tqi_flags; /* Tx queue flags (see above) */ + u32 tqi_aifs; /* Arbitrated Interframe Space */ + s32 tqi_cw_min; /* Minimum Contention Window */ + s32 tqi_cw_max; /* Maximum Contention Window */ + u32 tqi_cbr_period; /* Constant bit rate period */ + u32 tqi_cbr_overflow_limit; + u32 tqi_burst_time; + u32 tqi_ready_time; /* Not used */ +}; + +/* + * Transmit packet types. + * used on tx control descriptor + * TODO: Use them inside base.c corectly + */ +enum ath5k_pkt_type { + AR5K_PKT_TYPE_NORMAL = 0, + AR5K_PKT_TYPE_ATIM = 1, + AR5K_PKT_TYPE_PSPOLL = 2, + AR5K_PKT_TYPE_BEACON = 3, + AR5K_PKT_TYPE_PROBE_RESP = 4, + AR5K_PKT_TYPE_PIFS = 5, +}; + +/* + * TX power and TPC settings + */ +#define AR5K_TXPOWER_OFDM(_r, _v) ( \ + ((0 & 1) << ((_v) + 6)) | \ + (((ah->ah_txpower.txp_rates_power_table[(_r)]) & 0x3f) << (_v)) \ +) + +#define AR5K_TXPOWER_CCK(_r, _v) ( \ + (ah->ah_txpower.txp_rates_power_table[(_r)] & 0x3f) << (_v) \ +) + +/* + * DMA size definitions (2^n+2) + */ +enum ath5k_dmasize { + AR5K_DMASIZE_4B = 0, + AR5K_DMASIZE_8B, + AR5K_DMASIZE_16B, + AR5K_DMASIZE_32B, + AR5K_DMASIZE_64B, + AR5K_DMASIZE_128B, + AR5K_DMASIZE_256B, + AR5K_DMASIZE_512B +}; + + +/****************\ + RX DEFINITIONS +\****************/ + +/* + * RX Status descriptor + */ +struct ath5k_rx_status { + u16 rs_datalen; + u16 rs_tstamp; + u8 rs_status; + u8 rs_phyerr; + s8 rs_rssi; + u8 rs_keyix; + u8 rs_rate; + u8 rs_antenna; + u8 rs_more; +}; + +#define AR5K_RXERR_CRC 0x01 +#define AR5K_RXERR_PHY 0x02 +#define AR5K_RXERR_FIFO 0x04 +#define AR5K_RXERR_DECRYPT 0x08 +#define AR5K_RXERR_MIC 0x10 +#define AR5K_RXKEYIX_INVALID ((u8) - 1) +#define AR5K_TXKEYIX_INVALID ((u32) - 1) + + +/* + * TSF to TU conversion: + * + * TSF is a 64bit value in usec (microseconds). + * TU is a 32bit value and defined by IEEE802.11 (page 6) as "A measurement of + * time equal to 1024 usec", so it's roughly milliseconds (usec / 1024). + */ +#define TSF_TO_TU(_tsf) (u32)((_tsf) >> 10) + + +/*******************************\ + GAIN OPTIMIZATION DEFINITIONS +\*******************************/ + +enum ath5k_rfgain { + AR5K_RFGAIN_INACTIVE = 0, + AR5K_RFGAIN_ACTIVE, + AR5K_RFGAIN_READ_REQUESTED, + AR5K_RFGAIN_NEED_CHANGE, +}; + +struct ath5k_gain { + u8 g_step_idx; + u8 g_current; + u8 g_target; + u8 g_low; + u8 g_high; + u8 g_f_corr; + u8 g_state; +}; + +/********************\ + COMMON DEFINITIONS +\********************/ + +#define AR5K_SLOT_TIME_9 396 +#define AR5K_SLOT_TIME_20 880 +#define AR5K_SLOT_TIME_MAX 0xffff + +/* channel_flags */ +#define CHANNEL_CW_INT 0x0008 /* Contention Window interference detected */ +#define CHANNEL_TURBO 0x0010 /* Turbo Channel */ +#define CHANNEL_CCK 0x0020 /* CCK channel */ +#define CHANNEL_OFDM 0x0040 /* OFDM channel */ +#define CHANNEL_2GHZ 0x0080 /* 2GHz channel. */ +#define CHANNEL_5GHZ 0x0100 /* 5GHz channel */ +#define CHANNEL_PASSIVE 0x0200 /* Only passive scan allowed */ +#define CHANNEL_DYN 0x0400 /* Dynamic CCK-OFDM channel (for g operation) */ +#define CHANNEL_XR 0x0800 /* XR channel */ + +#define CHANNEL_A (CHANNEL_5GHZ|CHANNEL_OFDM) +#define CHANNEL_B (CHANNEL_2GHZ|CHANNEL_CCK) +#define CHANNEL_G (CHANNEL_2GHZ|CHANNEL_OFDM) +#define CHANNEL_T (CHANNEL_5GHZ|CHANNEL_OFDM|CHANNEL_TURBO) +#define CHANNEL_TG (CHANNEL_2GHZ|CHANNEL_OFDM|CHANNEL_TURBO) +#define CHANNEL_108A CHANNEL_T +#define CHANNEL_108G CHANNEL_TG +#define CHANNEL_X (CHANNEL_5GHZ|CHANNEL_OFDM|CHANNEL_XR) + +#define CHANNEL_ALL (CHANNEL_OFDM|CHANNEL_CCK|CHANNEL_2GHZ|CHANNEL_5GHZ| \ + CHANNEL_TURBO) + +#define CHANNEL_ALL_NOTURBO (CHANNEL_ALL & ~CHANNEL_TURBO) +#define CHANNEL_MODES CHANNEL_ALL + +/* + * Used internaly for reset_tx_queue). + * Also see struct struct net80211_channel. + */ +#define IS_CHAN_XR(_c) ((_c->hw_value & CHANNEL_XR) != 0) +#define IS_CHAN_B(_c) ((_c->hw_value & CHANNEL_B) != 0) + +/* + * The following structure is used to map 2GHz channels to + * 5GHz Atheros channels. + * TODO: Clean up + */ +struct ath5k_athchan_2ghz { + u32 a2_flags; + u16 a2_athchan; +}; + + +/******************\ + RATE DEFINITIONS +\******************/ + +/** + * Seems the ar5xxx harware supports up to 32 rates, indexed by 1-32. + * + * The rate code is used to get the RX rate or set the TX rate on the + * hardware descriptors. It is also used for internal modulation control + * and settings. + * + * This is the hardware rate map we are aware of: + * + * rate_code 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 + * rate_kbps 3000 1000 ? ? ? 2000 500 48000 + * + * rate_code 0x09 0x0A 0x0B 0x0C 0x0D 0x0E 0x0F 0x10 + * rate_kbps 24000 12000 6000 54000 36000 18000 9000 ? + * + * rate_code 17 18 19 20 21 22 23 24 + * rate_kbps ? ? ? ? ? ? ? 11000 + * + * rate_code 25 26 27 28 29 30 31 32 + * rate_kbps 5500 2000 1000 11000S 5500S 2000S ? ? + * + * "S" indicates CCK rates with short preamble. + * + * AR5211 has different rate codes for CCK (802.11B) rates. It only uses the + * lowest 4 bits, so they are the same as below with a 0xF mask. + * (0xB, 0xA, 0x9 and 0x8 for 1M, 2M, 5.5M and 11M). + * We handle this in ath5k_setup_bands(). + */ +#define AR5K_MAX_RATES 32 + +/* B */ +#define ATH5K_RATE_CODE_1M 0x1B +#define ATH5K_RATE_CODE_2M 0x1A +#define ATH5K_RATE_CODE_5_5M 0x19 +#define ATH5K_RATE_CODE_11M 0x18 +/* A and G */ +#define ATH5K_RATE_CODE_6M 0x0B +#define ATH5K_RATE_CODE_9M 0x0F +#define ATH5K_RATE_CODE_12M 0x0A +#define ATH5K_RATE_CODE_18M 0x0E +#define ATH5K_RATE_CODE_24M 0x09 +#define ATH5K_RATE_CODE_36M 0x0D +#define ATH5K_RATE_CODE_48M 0x08 +#define ATH5K_RATE_CODE_54M 0x0C +/* XR */ +#define ATH5K_RATE_CODE_XR_500K 0x07 +#define ATH5K_RATE_CODE_XR_1M 0x02 +#define ATH5K_RATE_CODE_XR_2M 0x06 +#define ATH5K_RATE_CODE_XR_3M 0x01 + +/* adding this flag to rate_code enables short preamble */ +#define AR5K_SET_SHORT_PREAMBLE 0x04 + +/* + * Crypto definitions + */ + +#define AR5K_KEYCACHE_SIZE 8 + +/***********************\ + HW RELATED DEFINITIONS +\***********************/ + +/* + * Misc definitions + */ +#define AR5K_RSSI_EP_MULTIPLIER (1<<7) + +#define AR5K_ASSERT_ENTRY(_e, _s) do { \ + if (_e >= _s) \ + return 0; \ +} while (0) + +/* + * Hardware interrupt abstraction + */ + +/** + * enum ath5k_int - Hardware interrupt masks helpers + * + * @AR5K_INT_RX: mask to identify received frame interrupts, of type + * AR5K_ISR_RXOK or AR5K_ISR_RXERR + * @AR5K_INT_RXDESC: Request RX descriptor/Read RX descriptor (?) + * @AR5K_INT_RXNOFRM: No frame received (?) + * @AR5K_INT_RXEOL: received End Of List for VEOL (Virtual End Of List). The + * Queue Control Unit (QCU) signals an EOL interrupt only if a descriptor's + * LinkPtr is NULL. For more details, refer to: + * http://www.freepatentsonline.com/20030225739.html + * @AR5K_INT_RXORN: Indicates we got RX overrun (eg. no more descriptors). + * Note that Rx overrun is not always fatal, on some chips we can continue + * operation without reseting the card, that's why int_fatal is not + * common for all chips. + * @AR5K_INT_TX: mask to identify received frame interrupts, of type + * AR5K_ISR_TXOK or AR5K_ISR_TXERR + * @AR5K_INT_TXDESC: Request TX descriptor/Read TX status descriptor (?) + * @AR5K_INT_TXURN: received when we should increase the TX trigger threshold + * We currently do increments on interrupt by + * (AR5K_TUNE_MAX_TX_FIFO_THRES - current_trigger_level) / 2 + * @AR5K_INT_MIB: Indicates the Management Information Base counters should be + * checked. We should do this with ath5k_hw_update_mib_counters() but + * it seems we should also then do some noise immunity work. + * @AR5K_INT_RXPHY: RX PHY Error + * @AR5K_INT_RXKCM: RX Key cache miss + * @AR5K_INT_SWBA: SoftWare Beacon Alert - indicates its time to send a + * beacon that must be handled in software. The alternative is if you + * have VEOL support, in that case you let the hardware deal with things. + * @AR5K_INT_BMISS: If in STA mode this indicates we have stopped seeing + * beacons from the AP have associated with, we should probably try to + * reassociate. When in IBSS mode this might mean we have not received + * any beacons from any local stations. Note that every station in an + * IBSS schedules to send beacons at the Target Beacon Transmission Time + * (TBTT) with a random backoff. + * @AR5K_INT_BNR: Beacon Not Ready interrupt - ?? + * @AR5K_INT_GPIO: GPIO interrupt is used for RF Kill, disabled for now + * until properly handled + * @AR5K_INT_FATAL: Fatal errors were encountered, typically caused by DMA + * errors. These types of errors we can enable seem to be of type + * AR5K_SIMR2_MCABT, AR5K_SIMR2_SSERR and AR5K_SIMR2_DPERR. + * @AR5K_INT_GLOBAL: Used to clear and set the IER + * @AR5K_INT_NOCARD: signals the card has been removed + * @AR5K_INT_COMMON: common interrupts shared amogst MACs with the same + * bit value + * + * These are mapped to take advantage of some common bits + * between the MACs, to be able to set intr properties + * easier. Some of them are not used yet inside hw.c. Most map + * to the respective hw interrupt value as they are common amogst different + * MACs. + */ +enum ath5k_int { + AR5K_INT_RXOK = 0x00000001, + AR5K_INT_RXDESC = 0x00000002, + AR5K_INT_RXERR = 0x00000004, + AR5K_INT_RXNOFRM = 0x00000008, + AR5K_INT_RXEOL = 0x00000010, + AR5K_INT_RXORN = 0x00000020, + AR5K_INT_TXOK = 0x00000040, + AR5K_INT_TXDESC = 0x00000080, + AR5K_INT_TXERR = 0x00000100, + AR5K_INT_TXNOFRM = 0x00000200, + AR5K_INT_TXEOL = 0x00000400, + AR5K_INT_TXURN = 0x00000800, + AR5K_INT_MIB = 0x00001000, + AR5K_INT_SWI = 0x00002000, + AR5K_INT_RXPHY = 0x00004000, + AR5K_INT_RXKCM = 0x00008000, + AR5K_INT_SWBA = 0x00010000, + AR5K_INT_BRSSI = 0x00020000, + AR5K_INT_BMISS = 0x00040000, + AR5K_INT_FATAL = 0x00080000, /* Non common */ + AR5K_INT_BNR = 0x00100000, /* Non common */ + AR5K_INT_TIM = 0x00200000, /* Non common */ + AR5K_INT_DTIM = 0x00400000, /* Non common */ + AR5K_INT_DTIM_SYNC = 0x00800000, /* Non common */ + AR5K_INT_GPIO = 0x01000000, + AR5K_INT_BCN_TIMEOUT = 0x02000000, /* Non common */ + AR5K_INT_CAB_TIMEOUT = 0x04000000, /* Non common */ + AR5K_INT_RX_DOPPLER = 0x08000000, /* Non common */ + AR5K_INT_QCBRORN = 0x10000000, /* Non common */ + AR5K_INT_QCBRURN = 0x20000000, /* Non common */ + AR5K_INT_QTRIG = 0x40000000, /* Non common */ + AR5K_INT_GLOBAL = 0x80000000, + + AR5K_INT_COMMON = AR5K_INT_RXOK + | AR5K_INT_RXDESC + | AR5K_INT_RXERR + | AR5K_INT_RXNOFRM + | AR5K_INT_RXEOL + | AR5K_INT_RXORN + | AR5K_INT_TXOK + | AR5K_INT_TXDESC + | AR5K_INT_TXERR + | AR5K_INT_TXNOFRM + | AR5K_INT_TXEOL + | AR5K_INT_TXURN + | AR5K_INT_MIB + | AR5K_INT_SWI + | AR5K_INT_RXPHY + | AR5K_INT_RXKCM + | AR5K_INT_SWBA + | AR5K_INT_BRSSI + | AR5K_INT_BMISS + | AR5K_INT_GPIO + | AR5K_INT_GLOBAL, + + AR5K_INT_NOCARD = 0xffffffff +}; + +/* + * Power management + */ +enum ath5k_power_mode { + AR5K_PM_UNDEFINED = 0, + AR5K_PM_AUTO, + AR5K_PM_AWAKE, + AR5K_PM_FULL_SLEEP, + AR5K_PM_NETWORK_SLEEP, +}; + +/* GPIO-controlled software LED */ +#define AR5K_SOFTLED_PIN 0 +#define AR5K_SOFTLED_ON 0 +#define AR5K_SOFTLED_OFF 1 + +/* + * Chipset capabilities -see ath5k_hw_get_capability- + * get_capability function is not yet fully implemented + * in ath5k so most of these don't work yet... + * TODO: Implement these & merge with _TUNE_ stuff above + */ +enum ath5k_capability_type { + AR5K_CAP_REG_DMN = 0, /* Used to get current reg. domain id */ + AR5K_CAP_TKIP_MIC = 2, /* Can handle TKIP MIC in hardware */ + AR5K_CAP_TKIP_SPLIT = 3, /* TKIP uses split keys */ + AR5K_CAP_PHYCOUNTERS = 4, /* PHY error counters */ + AR5K_CAP_DIVERSITY = 5, /* Supports fast diversity */ + AR5K_CAP_NUM_TXQUEUES = 6, /* Used to get max number of hw txqueues */ + AR5K_CAP_VEOL = 7, /* Supports virtual EOL */ + AR5K_CAP_COMPRESSION = 8, /* Supports compression */ + AR5K_CAP_BURST = 9, /* Supports packet bursting */ + AR5K_CAP_FASTFRAME = 10, /* Supports fast frames */ + AR5K_CAP_TXPOW = 11, /* Used to get global tx power limit */ + AR5K_CAP_TPC = 12, /* Can do per-packet tx power control (needed for 802.11a) */ + AR5K_CAP_BSSIDMASK = 13, /* Supports bssid mask */ + AR5K_CAP_MCAST_KEYSRCH = 14, /* Supports multicast key search */ + AR5K_CAP_TSF_ADJUST = 15, /* Supports beacon tsf adjust */ + AR5K_CAP_XR = 16, /* Supports XR mode */ + AR5K_CAP_WME_TKIPMIC = 17, /* Supports TKIP MIC when using WMM */ + AR5K_CAP_CHAN_HALFRATE = 18, /* Supports half rate channels */ + AR5K_CAP_CHAN_QUARTERRATE = 19, /* Supports quarter rate channels */ + AR5K_CAP_RFSILENT = 20, /* Supports RFsilent */ +}; + + +/* XXX: we *may* move cap_range stuff to struct wiphy */ +struct ath5k_capabilities { + /* + * Supported PHY modes + * (ie. CHANNEL_A, CHANNEL_B, ...) + */ + u16 cap_mode; + + /* + * Frequency range (without regulation restrictions) + */ + struct { + u16 range_2ghz_min; + u16 range_2ghz_max; + u16 range_5ghz_min; + u16 range_5ghz_max; + } cap_range; + + /* + * Values stored in the EEPROM (some of them...) + */ + struct ath5k_eeprom_info cap_eeprom; + + /* + * Queue information + */ + struct { + u8 q_tx_num; + } cap_queues; +}; + + +/***************************************\ + HARDWARE ABSTRACTION LAYER STRUCTURE +\***************************************/ + +/* + * Misc defines + */ + +#define AR5K_MAX_GPIO 10 +#define AR5K_MAX_RF_BANKS 8 + +/* TODO: Clean up and merge with ath5k_softc */ +struct ath5k_hw { + struct ath5k_softc *ah_sc; + void *ah_iobase; + + enum ath5k_int ah_imr; + int ah_ier; + + struct net80211_channel *ah_current_channel; + int ah_turbo; + int ah_calibration; + int ah_running; + int ah_single_chip; + int ah_combined_mic; + + u32 ah_mac_srev; + u16 ah_mac_version; + u16 ah_mac_revision; + u16 ah_phy_revision; + u16 ah_radio_5ghz_revision; + u16 ah_radio_2ghz_revision; + + enum ath5k_version ah_version; + enum ath5k_radio ah_radio; + u32 ah_phy; + + int ah_5ghz; + int ah_2ghz; + +#define ah_regdomain ah_capabilities.cap_regdomain.reg_current +#define ah_regdomain_hw ah_capabilities.cap_regdomain.reg_hw +#define ah_modes ah_capabilities.cap_mode +#define ah_ee_version ah_capabilities.cap_eeprom.ee_version + + u32 ah_atim_window; + u32 ah_aifs; + u32 ah_cw_min; + u32 ah_cw_max; + int ah_software_retry; + u32 ah_limit_tx_retries; + + u32 ah_antenna[AR5K_EEPROM_N_MODES][AR5K_ANT_MAX]; + int ah_ant_diversity; + + u8 ah_sta_id[ETH_ALEN]; + + /* Current BSSID we are trying to assoc to / create. + * This is passed by mac80211 on config_interface() and cached here for + * use in resets */ + u8 ah_bssid[ETH_ALEN]; + u8 ah_bssid_mask[ETH_ALEN]; + + u32 ah_gpio[AR5K_MAX_GPIO]; + int ah_gpio_npins; + + struct ath5k_capabilities ah_capabilities; + + struct ath5k_txq_info ah_txq; + u32 ah_txq_status; + u32 ah_txq_imr_txok; + u32 ah_txq_imr_txerr; + u32 ah_txq_imr_txurn; + u32 ah_txq_imr_txdesc; + u32 ah_txq_imr_txeol; + u32 ah_txq_imr_cbrorn; + u32 ah_txq_imr_cbrurn; + u32 ah_txq_imr_qtrig; + u32 ah_txq_imr_nofrm; + u32 ah_txq_isr; + u32 *ah_rf_banks; + size_t ah_rf_banks_size; + size_t ah_rf_regs_count; + struct ath5k_gain ah_gain; + u8 ah_offset[AR5K_MAX_RF_BANKS]; + + + struct { + /* Temporary tables used for interpolation */ + u8 tmpL[AR5K_EEPROM_N_PD_GAINS] + [AR5K_EEPROM_POWER_TABLE_SIZE]; + u8 tmpR[AR5K_EEPROM_N_PD_GAINS] + [AR5K_EEPROM_POWER_TABLE_SIZE]; + u8 txp_pd_table[AR5K_EEPROM_POWER_TABLE_SIZE * 2]; + u16 txp_rates_power_table[AR5K_MAX_RATES]; + u8 txp_min_idx; + int txp_tpc; + /* Values in 0.25dB units */ + s16 txp_min_pwr; + s16 txp_max_pwr; + s16 txp_offset; + s16 txp_ofdm; + /* Values in dB units */ + s16 txp_cck_ofdm_pwr_delta; + s16 txp_cck_ofdm_gainf_delta; + } ah_txpower; + + /* noise floor from last periodic calibration */ + s32 ah_noise_floor; + + /* + * Function pointers + */ + int (*ah_setup_rx_desc)(struct ath5k_hw *ah, struct ath5k_desc *desc, + u32 size, unsigned int flags); + int (*ah_setup_tx_desc)(struct ath5k_hw *, struct ath5k_desc *, + unsigned int, unsigned int, enum ath5k_pkt_type, unsigned int, + unsigned int, unsigned int, unsigned int, unsigned int, + unsigned int, unsigned int, unsigned int); + int (*ah_proc_tx_desc)(struct ath5k_hw *, struct ath5k_desc *, + struct ath5k_tx_status *); + int (*ah_proc_rx_desc)(struct ath5k_hw *, struct ath5k_desc *, + struct ath5k_rx_status *); +}; + +/* + * Prototypes + */ + +extern int ath5k_bitrate_to_hw_rix(int bitrate); + +/* Attach/Detach Functions */ +extern int ath5k_hw_attach(struct ath5k_softc *sc, u8 mac_version, struct ath5k_hw **ah); +extern void ath5k_hw_detach(struct ath5k_hw *ah); + +/* LED functions */ +extern int ath5k_init_leds(struct ath5k_softc *sc); +extern void ath5k_led_enable(struct ath5k_softc *sc); +extern void ath5k_led_off(struct ath5k_softc *sc); +extern void ath5k_unregister_leds(struct ath5k_softc *sc); + +/* Reset Functions */ +extern int ath5k_hw_nic_wakeup(struct ath5k_hw *ah, int flags, int initial); +extern int ath5k_hw_reset(struct ath5k_hw *ah, struct net80211_channel *channel, int change_channel); +/* Power management functions */ +extern int ath5k_hw_set_power(struct ath5k_hw *ah, enum ath5k_power_mode mode, int set_chip, u16 sleep_duration); + +/* DMA Related Functions */ +extern void ath5k_hw_start_rx_dma(struct ath5k_hw *ah); +extern int ath5k_hw_stop_rx_dma(struct ath5k_hw *ah); +extern u32 ath5k_hw_get_rxdp(struct ath5k_hw *ah); +extern void ath5k_hw_set_rxdp(struct ath5k_hw *ah, u32 phys_addr); +extern int ath5k_hw_start_tx_dma(struct ath5k_hw *ah, unsigned int queue); +extern int ath5k_hw_stop_tx_dma(struct ath5k_hw *ah, unsigned int queue); +extern u32 ath5k_hw_get_txdp(struct ath5k_hw *ah, unsigned int queue); +extern int ath5k_hw_set_txdp(struct ath5k_hw *ah, unsigned int queue, + u32 phys_addr); +extern int ath5k_hw_update_tx_triglevel(struct ath5k_hw *ah, int increase); +/* Interrupt handling */ +extern int ath5k_hw_is_intr_pending(struct ath5k_hw *ah); +extern int ath5k_hw_get_isr(struct ath5k_hw *ah, enum ath5k_int *interrupt_mask); +extern enum ath5k_int ath5k_hw_set_imr(struct ath5k_hw *ah, enum ath5k_int new_mask); + +/* EEPROM access functions */ +extern int ath5k_eeprom_init(struct ath5k_hw *ah); +extern void ath5k_eeprom_detach(struct ath5k_hw *ah); +extern int ath5k_eeprom_read_mac(struct ath5k_hw *ah, u8 *mac); +extern int ath5k_eeprom_is_hb63(struct ath5k_hw *ah); + +/* Protocol Control Unit Functions */ +extern int ath5k_hw_set_opmode(struct ath5k_hw *ah); +/* BSSID Functions */ +extern void ath5k_hw_get_lladdr(struct ath5k_hw *ah, u8 *mac); +extern int ath5k_hw_set_lladdr(struct ath5k_hw *ah, const u8 *mac); +extern void ath5k_hw_set_associd(struct ath5k_hw *ah, const u8 *bssid, u16 assoc_id); +extern int ath5k_hw_set_bssid_mask(struct ath5k_hw *ah, const u8 *mask); +/* Receive start/stop functions */ +extern void ath5k_hw_start_rx_pcu(struct ath5k_hw *ah); +extern void ath5k_hw_stop_rx_pcu(struct ath5k_hw *ah); +/* RX Filter functions */ +extern void ath5k_hw_set_mcast_filter(struct ath5k_hw *ah, u32 filter0, u32 filter1); +extern u32 ath5k_hw_get_rx_filter(struct ath5k_hw *ah); +extern void ath5k_hw_set_rx_filter(struct ath5k_hw *ah, u32 filter); +/* ACK bit rate */ +void ath5k_hw_set_ack_bitrate_high(struct ath5k_hw *ah, int high); +/* ACK/CTS Timeouts */ +extern int ath5k_hw_set_ack_timeout(struct ath5k_hw *ah, unsigned int timeout); +extern unsigned int ath5k_hw_get_ack_timeout(struct ath5k_hw *ah); +extern int ath5k_hw_set_cts_timeout(struct ath5k_hw *ah, unsigned int timeout); +extern unsigned int ath5k_hw_get_cts_timeout(struct ath5k_hw *ah); +/* Key table (WEP) functions */ +extern int ath5k_hw_reset_key(struct ath5k_hw *ah, u16 entry); + +/* Queue Control Unit, DFS Control Unit Functions */ +extern int ath5k_hw_set_tx_queueprops(struct ath5k_hw *ah, const struct ath5k_txq_info *queue_info); +extern int ath5k_hw_setup_tx_queue(struct ath5k_hw *ah, + enum ath5k_tx_queue queue_type, + struct ath5k_txq_info *queue_info); +extern u32 ath5k_hw_num_tx_pending(struct ath5k_hw *ah); +extern void ath5k_hw_release_tx_queue(struct ath5k_hw *ah); +extern int ath5k_hw_reset_tx_queue(struct ath5k_hw *ah); +extern int ath5k_hw_set_slot_time(struct ath5k_hw *ah, unsigned int slot_time); + +/* Hardware Descriptor Functions */ +extern int ath5k_hw_init_desc_functions(struct ath5k_hw *ah); + +/* GPIO Functions */ +extern int ath5k_hw_set_gpio_input(struct ath5k_hw *ah, u32 gpio); +extern int ath5k_hw_set_gpio_output(struct ath5k_hw *ah, u32 gpio); +extern u32 ath5k_hw_get_gpio(struct ath5k_hw *ah, u32 gpio); +extern int ath5k_hw_set_gpio(struct ath5k_hw *ah, u32 gpio, u32 val); +extern void ath5k_hw_set_gpio_intr(struct ath5k_hw *ah, unsigned int gpio, u32 interrupt_level); + +/* rfkill Functions */ +extern void ath5k_rfkill_hw_start(struct ath5k_hw *ah); +extern void ath5k_rfkill_hw_stop(struct ath5k_hw *ah); + +/* Misc functions */ +int ath5k_hw_set_capabilities(struct ath5k_hw *ah); +extern int ath5k_hw_get_capability(struct ath5k_hw *ah, enum ath5k_capability_type cap_type, u32 capability, u32 *result); +extern int ath5k_hw_enable_pspoll(struct ath5k_hw *ah, u8 *bssid, u16 assoc_id); +extern int ath5k_hw_disable_pspoll(struct ath5k_hw *ah); + +/* Initial register settings functions */ +extern int ath5k_hw_write_initvals(struct ath5k_hw *ah, u8 mode, int change_channel); + +/* Initialize RF */ +extern int ath5k_hw_rfregs_init(struct ath5k_hw *ah, + struct net80211_channel *channel, + unsigned int mode); +extern int ath5k_hw_rfgain_init(struct ath5k_hw *ah, unsigned int freq); +extern enum ath5k_rfgain ath5k_hw_gainf_calibrate(struct ath5k_hw *ah); +extern int ath5k_hw_rfgain_opt_init(struct ath5k_hw *ah); +/* PHY/RF channel functions */ +extern int ath5k_channel_ok(struct ath5k_hw *ah, u16 freq, unsigned int flags); +extern int ath5k_hw_channel(struct ath5k_hw *ah, struct net80211_channel *channel); +/* PHY calibration */ +extern int ath5k_hw_phy_calibrate(struct ath5k_hw *ah, struct net80211_channel *channel); +extern int ath5k_hw_noise_floor_calibration(struct ath5k_hw *ah, short freq); +/* Misc PHY functions */ +extern u16 ath5k_hw_radio_revision(struct ath5k_hw *ah, unsigned int chan); +extern void ath5k_hw_set_def_antenna(struct ath5k_hw *ah, unsigned int ant); +extern unsigned int ath5k_hw_get_def_antenna(struct ath5k_hw *ah); +extern int ath5k_hw_phy_disable(struct ath5k_hw *ah); +/* TX power setup */ +extern int ath5k_hw_txpower(struct ath5k_hw *ah, struct net80211_channel *channel, u8 ee_mode, u8 txpower); +extern int ath5k_hw_set_txpower_limit(struct ath5k_hw *ah, u8 ee_mode, u8 txpower); + +/* + * Functions used internaly + */ + +/* + * Translate usec to hw clock units + * TODO: Half/quarter rate + */ +static inline unsigned int ath5k_hw_htoclock(unsigned int usec, int turbo) +{ + return turbo ? (usec * 80) : (usec * 40); +} + +/* + * Translate hw clock units to usec + * TODO: Half/quarter rate + */ +static inline unsigned int ath5k_hw_clocktoh(unsigned int clock, int turbo) +{ + return turbo ? (clock / 80) : (clock / 40); +} + +/* + * Read from a register + */ +static inline u32 ath5k_hw_reg_read(struct ath5k_hw *ah, u16 reg) +{ + return readl(ah->ah_iobase + reg); +} + +/* + * Write to a register + */ +static inline void ath5k_hw_reg_write(struct ath5k_hw *ah, u32 val, u16 reg) +{ + writel(val, ah->ah_iobase + reg); +} + +#if defined(_ATH5K_RESET) || defined(_ATH5K_PHY) +/* + * Check if a register write has been completed + */ +static int ath5k_hw_register_timeout(struct ath5k_hw *ah, u32 reg, u32 flag, + u32 val, int is_set) +{ + int i; + u32 data; + + for (i = AR5K_TUNE_REGISTER_TIMEOUT; i > 0; i--) { + data = ath5k_hw_reg_read(ah, reg); + if (is_set && (data & flag)) + break; + else if ((data & flag) == val) + break; + udelay(15); + } + + return (i <= 0) ? -EAGAIN : 0; +} + +/* + * Convert channel frequency to channel number + */ +static inline int ath5k_freq_to_channel(int freq) +{ + if (freq == 2484) + return 14; + + if (freq < 2484) + return (freq - 2407) / 5; + + return freq/5 - 1000; +} + +#endif + +static inline u32 ath5k_hw_bitswap(u32 val, unsigned int bits) +{ + u32 retval = 0, bit, i; + + for (i = 0; i < bits; i++) { + bit = (val >> i) & 1; + retval = (retval << 1) | bit; + } + + return retval; +} + +#endif diff --git a/gpxe/src/drivers/net/ath5k/ath5k_attach.c b/gpxe/src/drivers/net/ath5k/ath5k_attach.c new file mode 100644 index 00000000..36dc2439 --- /dev/null +++ b/gpxe/src/drivers/net/ath5k/ath5k_attach.c @@ -0,0 +1,340 @@ +/* + * Copyright (c) 2004-2008 Reyk Floeter <reyk@openbsd.org> + * Copyright (c) 2006-2008 Nick Kossifidis <mickflemm@gmail.com> + * + * Modified for gPXE, July 2009, by Joshua Oreman <oremanj@rwcr.net> + * Original from Linux kernel 2.6.30. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +FILE_LICENCE ( MIT ); + +/*************************************\ +* Attach/Detach Functions and helpers * +\*************************************/ + +#include <gpxe/pci.h> +#include <unistd.h> +#include <stdlib.h> +#include "ath5k.h" +#include "reg.h" +#include "base.h" + +/** + * ath5k_hw_post - Power On Self Test helper function + * + * @ah: The &struct ath5k_hw + */ +static int ath5k_hw_post(struct ath5k_hw *ah) +{ + + static const u32 static_pattern[4] = { + 0x55555555, 0xaaaaaaaa, + 0x66666666, 0x99999999 + }; + static const u16 regs[2] = { AR5K_STA_ID0, AR5K_PHY(8) }; + int i, c; + u16 cur_reg; + u32 var_pattern; + u32 init_val; + u32 cur_val; + + for (c = 0; c < 2; c++) { + + cur_reg = regs[c]; + + /* Save previous value */ + init_val = ath5k_hw_reg_read(ah, cur_reg); + + for (i = 0; i < 256; i++) { + var_pattern = i << 16 | i; + ath5k_hw_reg_write(ah, var_pattern, cur_reg); + cur_val = ath5k_hw_reg_read(ah, cur_reg); + + if (cur_val != var_pattern) { + DBG("ath5k: POST failed!\n"); + return -EAGAIN; + } + + /* Found on ndiswrapper dumps */ + var_pattern = 0x0039080f; + ath5k_hw_reg_write(ah, var_pattern, cur_reg); + } + + for (i = 0; i < 4; i++) { + var_pattern = static_pattern[i]; + ath5k_hw_reg_write(ah, var_pattern, cur_reg); + cur_val = ath5k_hw_reg_read(ah, cur_reg); + + if (cur_val != var_pattern) { + DBG("ath5k: POST failed!\n"); + return -EAGAIN; + } + + /* Found on ndiswrapper dumps */ + var_pattern = 0x003b080f; + ath5k_hw_reg_write(ah, var_pattern, cur_reg); + } + + /* Restore previous value */ + ath5k_hw_reg_write(ah, init_val, cur_reg); + + } + + return 0; + +} + +/** + * ath5k_hw_attach - Check if hw is supported and init the needed structs + * + * @sc: The &struct ath5k_softc we got from the driver's attach function + * @mac_version: The mac version id (check out ath5k.h) based on pci id + * @hw: Returned newly allocated hardware structure, on success + * + * Check if the device is supported, perform a POST and initialize the needed + * structs. Returns -ENOMEM if we don't have memory for the needed structs, + * -ENODEV if the device is not supported or prints an error msg if something + * else went wrong. + */ +int ath5k_hw_attach(struct ath5k_softc *sc, u8 mac_version, + struct ath5k_hw **hw) +{ + struct ath5k_hw *ah; + struct pci_device *pdev = sc->pdev; + int ret; + u32 srev; + + ah = zalloc(sizeof(struct ath5k_hw)); + if (ah == NULL) { + ret = -ENOMEM; + DBG("ath5k: out of memory\n"); + goto err; + } + + ah->ah_sc = sc; + ah->ah_iobase = sc->iobase; + + /* + * HW information + */ + ah->ah_turbo = 0; + ah->ah_txpower.txp_tpc = 0; + ah->ah_imr = 0; + ah->ah_atim_window = 0; + ah->ah_aifs = AR5K_TUNE_AIFS; + ah->ah_cw_min = AR5K_TUNE_CWMIN; + ah->ah_limit_tx_retries = AR5K_INIT_TX_RETRY; + ah->ah_software_retry = 0; + ah->ah_ant_diversity = AR5K_TUNE_ANT_DIVERSITY; + + /* + * Set the mac version based on the pci id + */ + ah->ah_version = mac_version; + + /*Fill the ath5k_hw struct with the needed functions*/ + ret = ath5k_hw_init_desc_functions(ah); + if (ret) + goto err_free; + + /* Bring device out of sleep and reset it's units */ + ret = ath5k_hw_nic_wakeup(ah, CHANNEL_B, 1); + if (ret) + goto err_free; + + /* Get MAC, PHY and RADIO revisions */ + srev = ath5k_hw_reg_read(ah, AR5K_SREV); + ah->ah_mac_srev = srev; + ah->ah_mac_version = AR5K_REG_MS(srev, AR5K_SREV_VER); + ah->ah_mac_revision = AR5K_REG_MS(srev, AR5K_SREV_REV); + ah->ah_phy_revision = ath5k_hw_reg_read(ah, AR5K_PHY_CHIP_ID); + ah->ah_radio_5ghz_revision = ath5k_hw_radio_revision(ah, CHANNEL_5GHZ); + ah->ah_phy = AR5K_PHY(0); + + /* Try to identify radio chip based on it's srev */ + switch (ah->ah_radio_5ghz_revision & 0xf0) { + case AR5K_SREV_RAD_5111: + ah->ah_radio = AR5K_RF5111; + ah->ah_single_chip = 0; + ah->ah_radio_2ghz_revision = ath5k_hw_radio_revision(ah, + CHANNEL_2GHZ); + break; + case AR5K_SREV_RAD_5112: + case AR5K_SREV_RAD_2112: + ah->ah_radio = AR5K_RF5112; + ah->ah_single_chip = 0; + ah->ah_radio_2ghz_revision = ath5k_hw_radio_revision(ah, + CHANNEL_2GHZ); + break; + case AR5K_SREV_RAD_2413: + ah->ah_radio = AR5K_RF2413; + ah->ah_single_chip = 1; + break; + case AR5K_SREV_RAD_5413: + ah->ah_radio = AR5K_RF5413; + ah->ah_single_chip = 1; + break; + case AR5K_SREV_RAD_2316: + ah->ah_radio = AR5K_RF2316; + ah->ah_single_chip = 1; + break; + case AR5K_SREV_RAD_2317: + ah->ah_radio = AR5K_RF2317; + ah->ah_single_chip = 1; + break; + case AR5K_SREV_RAD_5424: + if (ah->ah_mac_version == AR5K_SREV_AR2425 || + ah->ah_mac_version == AR5K_SREV_AR2417) { + ah->ah_radio = AR5K_RF2425; + } else { + ah->ah_radio = AR5K_RF5413; + } + ah->ah_single_chip = 1; + break; + default: + /* Identify radio based on mac/phy srev */ + if (ah->ah_version == AR5K_AR5210) { + ah->ah_radio = AR5K_RF5110; + ah->ah_single_chip = 0; + } else if (ah->ah_version == AR5K_AR5211) { + ah->ah_radio = AR5K_RF5111; + ah->ah_single_chip = 0; + ah->ah_radio_2ghz_revision = ath5k_hw_radio_revision(ah, + CHANNEL_2GHZ); + } else if (ah->ah_mac_version == (AR5K_SREV_AR2425 >> 4) || + ah->ah_mac_version == (AR5K_SREV_AR2417 >> 4) || + ah->ah_phy_revision == AR5K_SREV_PHY_2425) { + ah->ah_radio = AR5K_RF2425; + ah->ah_single_chip = 1; + ah->ah_radio_5ghz_revision = AR5K_SREV_RAD_2425; + } else if (srev == AR5K_SREV_AR5213A && + ah->ah_phy_revision == AR5K_SREV_PHY_5212B) { + ah->ah_radio = AR5K_RF5112; + ah->ah_single_chip = 0; + ah->ah_radio_5ghz_revision = AR5K_SREV_RAD_5112B; + } else if (ah->ah_mac_version == (AR5K_SREV_AR2415 >> 4)) { + ah->ah_radio = AR5K_RF2316; + ah->ah_single_chip = 1; + ah->ah_radio_5ghz_revision = AR5K_SREV_RAD_2316; + } else if (ah->ah_mac_version == (AR5K_SREV_AR5414 >> 4) || + ah->ah_phy_revision == AR5K_SREV_PHY_5413) { + ah->ah_radio = AR5K_RF5413; + ah->ah_single_chip = 1; + ah->ah_radio_5ghz_revision = AR5K_SREV_RAD_5413; + } else if (ah->ah_mac_version == (AR5K_SREV_AR2414 >> 4) || + ah->ah_phy_revision == AR5K_SREV_PHY_2413) { + ah->ah_radio = AR5K_RF2413; + ah->ah_single_chip = 1; + ah->ah_radio_5ghz_revision = AR5K_SREV_RAD_2413; + } else { + DBG("ath5k: Couldn't identify radio revision.\n"); + ret = -ENOTSUP; + goto err_free; + } + } + + /* Return on unsuported chips (unsupported eeprom etc) */ + if ((srev >= AR5K_SREV_AR5416) && + (srev < AR5K_SREV_AR2425)) { + DBG("ath5k: Device not yet supported.\n"); + ret = -ENOTSUP; + goto err_free; + } + + /* + * Write PCI-E power save settings + */ + if ((ah->ah_version == AR5K_AR5212) && + pci_find_capability(pdev, PCI_CAP_ID_EXP)) { + ath5k_hw_reg_write(ah, 0x9248fc00, AR5K_PCIE_SERDES); + ath5k_hw_reg_write(ah, 0x24924924, AR5K_PCIE_SERDES); + /* Shut off RX when elecidle is asserted */ + ath5k_hw_reg_write(ah, 0x28000039, AR5K_PCIE_SERDES); + ath5k_hw_reg_write(ah, 0x53160824, AR5K_PCIE_SERDES); + /* TODO: EEPROM work */ + ath5k_hw_reg_write(ah, 0xe5980579, AR5K_PCIE_SERDES); + /* Shut off PLL and CLKREQ active in L1 */ + ath5k_hw_reg_write(ah, 0x001defff, AR5K_PCIE_SERDES); + /* Preserce other settings */ + ath5k_hw_reg_write(ah, 0x1aaabe40, AR5K_PCIE_SERDES); + ath5k_hw_reg_write(ah, 0xbe105554, AR5K_PCIE_SERDES); + ath5k_hw_reg_write(ah, 0x000e3007, AR5K_PCIE_SERDES); + /* Reset SERDES to load new settings */ + ath5k_hw_reg_write(ah, 0x00000000, AR5K_PCIE_SERDES_RESET); + mdelay(1); + } + + /* + * POST + */ + ret = ath5k_hw_post(ah); + if (ret) + goto err_free; + + /* Enable pci core retry fix on Hainan (5213A) and later chips */ + if (srev >= AR5K_SREV_AR5213A) + ath5k_hw_reg_write(ah, AR5K_PCICFG_RETRY_FIX, AR5K_PCICFG); + + /* + * Get card capabilities, calibration values etc + * TODO: EEPROM work + */ + ret = ath5k_eeprom_init(ah); + if (ret) { + DBG("ath5k: unable to init EEPROM\n"); + goto err_free; + } + + /* Get misc capabilities */ + ret = ath5k_hw_set_capabilities(ah); + if (ret) { + DBG("ath5k: unable to get device capabilities: 0x%04x\n", + sc->pdev->device); + goto err_free; + } + + if (srev >= AR5K_SREV_AR2414) { + ah->ah_combined_mic = 1; + AR5K_REG_ENABLE_BITS(ah, AR5K_MISC_MODE, + AR5K_MISC_MODE_COMBINED_MIC); + } + + /* Set BSSID to bcast address: ff:ff:ff:ff:ff:ff for now */ + memset(ah->ah_bssid, 0xff, ETH_ALEN); + ath5k_hw_set_associd(ah, ah->ah_bssid, 0); + ath5k_hw_set_opmode(ah); + + ath5k_hw_rfgain_opt_init(ah); + + *hw = ah; + return 0; +err_free: + free(ah); +err: + return ret; +} + +/** + * ath5k_hw_detach - Free the ath5k_hw struct + * + * @ah: The &struct ath5k_hw + */ +void ath5k_hw_detach(struct ath5k_hw *ah) +{ + free(ah->ah_rf_banks); + ath5k_eeprom_detach(ah); + free(ah); +} diff --git a/gpxe/src/drivers/net/ath5k/ath5k_caps.c b/gpxe/src/drivers/net/ath5k/ath5k_caps.c new file mode 100644 index 00000000..1d60d744 --- /dev/null +++ b/gpxe/src/drivers/net/ath5k/ath5k_caps.c @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2004-2008 Reyk Floeter <reyk@openbsd.org> + * Copyright (c) 2006-2008 Nick Kossifidis <mickflemm@gmail.com> + * Copyright (c) 2007-2008 Jiri Slaby <jirislaby@gmail.com> + * + * Lightly modified for gPXE, July 2009, by Joshua Oreman <oremanj@rwcr.net>. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +FILE_LICENCE ( MIT ); + +/**************\ +* Capabilities * +\**************/ + +#include "ath5k.h" +#include "reg.h" +#include "base.h" + +/* + * Fill the capabilities struct + * TODO: Merge this with EEPROM code when we are done with it + */ +int ath5k_hw_set_capabilities(struct ath5k_hw *ah) +{ + u16 ee_header; + + /* Capabilities stored in the EEPROM */ + ee_header = ah->ah_capabilities.cap_eeprom.ee_header; + + if (ah->ah_version == AR5K_AR5210) { + /* + * Set radio capabilities + * (The AR5110 only supports the middle 5GHz band) + */ + ah->ah_capabilities.cap_range.range_5ghz_min = 5120; + ah->ah_capabilities.cap_range.range_5ghz_max = 5430; + ah->ah_capabilities.cap_range.range_2ghz_min = 0; + ah->ah_capabilities.cap_range.range_2ghz_max = 0; + + /* Set supported modes */ + ah->ah_capabilities.cap_mode |= AR5K_MODE_BIT_11A; + ah->ah_capabilities.cap_mode |= AR5K_MODE_BIT_11A_TURBO; + } else { + /* + * XXX The tranceiver supports frequencies from 4920 to 6100GHz + * XXX and from 2312 to 2732GHz. There are problems with the + * XXX current ieee80211 implementation because the IEEE + * XXX channel mapping does not support negative channel + * XXX numbers (2312MHz is channel -19). Of course, this + * XXX doesn't matter because these channels are out of range + * XXX but some regulation domains like MKK (Japan) will + * XXX support frequencies somewhere around 4.8GHz. + */ + + /* + * Set radio capabilities + */ + + if (AR5K_EEPROM_HDR_11A(ee_header)) { + /* 4920 */ + ah->ah_capabilities.cap_range.range_5ghz_min = 5005; + ah->ah_capabilities.cap_range.range_5ghz_max = 6100; + + /* Set supported modes */ + ah->ah_capabilities.cap_mode |= AR5K_MODE_BIT_11A; + ah->ah_capabilities.cap_mode |= AR5K_MODE_BIT_11A_TURBO; + if (ah->ah_version == AR5K_AR5212) + ah->ah_capabilities.cap_mode |= + AR5K_MODE_BIT_11G_TURBO; + } + + /* Enable 802.11b if a 2GHz capable radio (2111/5112) is + * connected */ + if (AR5K_EEPROM_HDR_11B(ee_header) || + (AR5K_EEPROM_HDR_11G(ee_header) && + ah->ah_version != AR5K_AR5211)) { + /* 2312 */ + ah->ah_capabilities.cap_range.range_2ghz_min = 2412; + ah->ah_capabilities.cap_range.range_2ghz_max = 2732; + + if (AR5K_EEPROM_HDR_11B(ee_header)) + ah->ah_capabilities.cap_mode |= + AR5K_MODE_BIT_11B; + + if (AR5K_EEPROM_HDR_11G(ee_header) && + ah->ah_version != AR5K_AR5211) + ah->ah_capabilities.cap_mode |= + AR5K_MODE_BIT_11G; + } + } + + /* GPIO */ + ah->ah_gpio_npins = AR5K_NUM_GPIO; + + /* Set number of supported TX queues */ + ah->ah_capabilities.cap_queues.q_tx_num = 1; + + return 0; +} + +/* Main function used by the driver part to check caps */ +int ath5k_hw_get_capability(struct ath5k_hw *ah, + enum ath5k_capability_type cap_type, + u32 capability __unused, u32 *result) +{ + switch (cap_type) { + case AR5K_CAP_NUM_TXQUEUES: + if (result) { + *result = 1; + goto yes; + } + case AR5K_CAP_VEOL: + goto yes; + case AR5K_CAP_COMPRESSION: + if (ah->ah_version == AR5K_AR5212) + goto yes; + else + goto no; + case AR5K_CAP_BURST: + goto yes; + case AR5K_CAP_TPC: + goto yes; + case AR5K_CAP_BSSIDMASK: + if (ah->ah_version == AR5K_AR5212) + goto yes; + else + goto no; + case AR5K_CAP_XR: + if (ah->ah_version == AR5K_AR5212) + goto yes; + else + goto no; + default: + goto no; + } + +no: + return -EINVAL; +yes: + return 0; +} diff --git a/gpxe/src/drivers/net/ath5k/ath5k_desc.c b/gpxe/src/drivers/net/ath5k/ath5k_desc.c new file mode 100644 index 00000000..76d0c1e4 --- /dev/null +++ b/gpxe/src/drivers/net/ath5k/ath5k_desc.c @@ -0,0 +1,544 @@ +/* + * Copyright (c) 2004-2008 Reyk Floeter <reyk@openbsd.org> + * Copyright (c) 2006-2008 Nick Kossifidis <mickflemm@gmail.com> + * Copyright (c) 2007-2008 Pavel Roskin <proski@gnu.org> + * + * Lightly modified for gPXE, July 2009, by Joshua Oreman <oremanj@rwcr.net>. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +FILE_LICENCE ( MIT ); + +/******************************\ + Hardware Descriptor Functions +\******************************/ + +#include "ath5k.h" +#include "reg.h" +#include "base.h" + +/* + * TX Descriptors + */ + +#define FCS_LEN 4 + +/* + * Initialize the 2-word tx control descriptor on 5210/5211 + */ +static int +ath5k_hw_setup_2word_tx_desc(struct ath5k_hw *ah, struct ath5k_desc *desc, + unsigned int pkt_len, unsigned int hdr_len, enum ath5k_pkt_type type, + unsigned int tx_power __unused, unsigned int tx_rate0, unsigned int tx_tries0, + unsigned int key_index __unused, unsigned int antenna_mode, unsigned int flags, + unsigned int rtscts_rate __unused, unsigned int rtscts_duration) +{ + u32 frame_type; + struct ath5k_hw_2w_tx_ctl *tx_ctl; + unsigned int frame_len; + + tx_ctl = &desc->ud.ds_tx5210.tx_ctl; + + /* + * Validate input + * - Zero retries don't make sense. + * - A zero rate will put the HW into a mode where it continously sends + * noise on the channel, so it is important to avoid this. + */ + if (tx_tries0 == 0) { + DBG("ath5k: zero retries\n"); + return -EINVAL; + } + if (tx_rate0 == 0) { + DBG("ath5k: zero rate\n"); + return -EINVAL; + } + + /* Clear descriptor */ + memset(&desc->ud.ds_tx5210, 0, sizeof(struct ath5k_hw_5210_tx_desc)); + + /* Setup control descriptor */ + + /* Verify and set frame length */ + + frame_len = pkt_len + FCS_LEN; + + if (frame_len & ~AR5K_2W_TX_DESC_CTL0_FRAME_LEN) + return -EINVAL; + + tx_ctl->tx_control_0 = frame_len & AR5K_2W_TX_DESC_CTL0_FRAME_LEN; + + /* Verify and set buffer length */ + + if (pkt_len & ~AR5K_2W_TX_DESC_CTL1_BUF_LEN) + return -EINVAL; + + tx_ctl->tx_control_1 = pkt_len & AR5K_2W_TX_DESC_CTL1_BUF_LEN; + + /* + * Verify and set header length + * XXX: I only found that on 5210 code, does it work on 5211 ? + */ + if (ah->ah_version == AR5K_AR5210) { + if (hdr_len & ~AR5K_2W_TX_DESC_CTL0_HEADER_LEN) + return -EINVAL; + tx_ctl->tx_control_0 |= + AR5K_REG_SM(hdr_len, AR5K_2W_TX_DESC_CTL0_HEADER_LEN); + } + + /*Diferences between 5210-5211*/ + if (ah->ah_version == AR5K_AR5210) { + switch (type) { + case AR5K_PKT_TYPE_BEACON: + case AR5K_PKT_TYPE_PROBE_RESP: + frame_type = AR5K_AR5210_TX_DESC_FRAME_TYPE_NO_DELAY; + case AR5K_PKT_TYPE_PIFS: + frame_type = AR5K_AR5210_TX_DESC_FRAME_TYPE_PIFS; + default: + frame_type = type /*<< 2 ?*/; + } + + tx_ctl->tx_control_0 |= + AR5K_REG_SM(frame_type, AR5K_2W_TX_DESC_CTL0_FRAME_TYPE) | + AR5K_REG_SM(tx_rate0, AR5K_2W_TX_DESC_CTL0_XMIT_RATE); + + } else { + tx_ctl->tx_control_0 |= + AR5K_REG_SM(tx_rate0, AR5K_2W_TX_DESC_CTL0_XMIT_RATE) | + AR5K_REG_SM(antenna_mode, + AR5K_2W_TX_DESC_CTL0_ANT_MODE_XMIT); + tx_ctl->tx_control_1 |= + AR5K_REG_SM(type, AR5K_2W_TX_DESC_CTL1_FRAME_TYPE); + } +#define _TX_FLAGS(_c, _flag) \ + if (flags & AR5K_TXDESC_##_flag) { \ + tx_ctl->tx_control_##_c |= \ + AR5K_2W_TX_DESC_CTL##_c##_##_flag; \ + } + + _TX_FLAGS(0, CLRDMASK); + _TX_FLAGS(0, VEOL); + _TX_FLAGS(0, INTREQ); + _TX_FLAGS(0, RTSENA); + _TX_FLAGS(1, NOACK); + +#undef _TX_FLAGS + + /* + * RTS/CTS Duration [5210 ?] + */ + if ((ah->ah_version == AR5K_AR5210) && + (flags & (AR5K_TXDESC_RTSENA | AR5K_TXDESC_CTSENA))) + tx_ctl->tx_control_1 |= rtscts_duration & + AR5K_2W_TX_DESC_CTL1_RTS_DURATION; + + return 0; +} + +/* + * Initialize the 4-word tx control descriptor on 5212 + */ +static int ath5k_hw_setup_4word_tx_desc(struct ath5k_hw *ah, + struct ath5k_desc *desc, unsigned int pkt_len, unsigned int hdr_len __unused, + enum ath5k_pkt_type type, unsigned int tx_power, unsigned int tx_rate0, + unsigned int tx_tries0, unsigned int key_index __unused, + unsigned int antenna_mode, unsigned int flags, + unsigned int rtscts_rate, + unsigned int rtscts_duration) +{ + struct ath5k_hw_4w_tx_ctl *tx_ctl; + unsigned int frame_len; + + tx_ctl = &desc->ud.ds_tx5212.tx_ctl; + + /* + * Validate input + * - Zero retries don't make sense. + * - A zero rate will put the HW into a mode where it continously sends + * noise on the channel, so it is important to avoid this. + */ + if (tx_tries0 == 0) { + DBG("ath5k: zero retries\n"); + return -EINVAL; + } + if (tx_rate0 == 0) { + DBG("ath5k: zero rate\n"); + return -EINVAL; + } + + tx_power += ah->ah_txpower.txp_offset; + if (tx_power > AR5K_TUNE_MAX_TXPOWER) + tx_power = AR5K_TUNE_MAX_TXPOWER; + + /* Clear descriptor */ + memset(&desc->ud.ds_tx5212, 0, sizeof(struct ath5k_hw_5212_tx_desc)); + + /* Setup control descriptor */ + + /* Verify and set frame length */ + + frame_len = pkt_len + FCS_LEN; + + if (frame_len & ~AR5K_4W_TX_DESC_CTL0_FRAME_LEN) + return -EINVAL; + + tx_ctl->tx_control_0 = frame_len & AR5K_4W_TX_DESC_CTL0_FRAME_LEN; + + /* Verify and set buffer length */ + + if (pkt_len & ~AR5K_4W_TX_DESC_CTL1_BUF_LEN) + return -EINVAL; + + tx_ctl->tx_control_1 = pkt_len & AR5K_4W_TX_DESC_CTL1_BUF_LEN; + + tx_ctl->tx_control_0 |= + AR5K_REG_SM(tx_power, AR5K_4W_TX_DESC_CTL0_XMIT_POWER) | + AR5K_REG_SM(antenna_mode, AR5K_4W_TX_DESC_CTL0_ANT_MODE_XMIT); + tx_ctl->tx_control_1 |= AR5K_REG_SM(type, + AR5K_4W_TX_DESC_CTL1_FRAME_TYPE); + tx_ctl->tx_control_2 = AR5K_REG_SM(tx_tries0 + AR5K_TUNE_HWTXTRIES, + AR5K_4W_TX_DESC_CTL2_XMIT_TRIES0); + tx_ctl->tx_control_3 = tx_rate0 & AR5K_4W_TX_DESC_CTL3_XMIT_RATE0; + +#define _TX_FLAGS(_c, _flag) \ + if (flags & AR5K_TXDESC_##_flag) { \ + tx_ctl->tx_control_##_c |= \ + AR5K_4W_TX_DESC_CTL##_c##_##_flag; \ + } + + _TX_FLAGS(0, CLRDMASK); + _TX_FLAGS(0, VEOL); + _TX_FLAGS(0, INTREQ); + _TX_FLAGS(0, RTSENA); + _TX_FLAGS(0, CTSENA); + _TX_FLAGS(1, NOACK); + +#undef _TX_FLAGS + + /* + * RTS/CTS + */ + if (flags & (AR5K_TXDESC_RTSENA | AR5K_TXDESC_CTSENA)) { + if ((flags & AR5K_TXDESC_RTSENA) && + (flags & AR5K_TXDESC_CTSENA)) + return -EINVAL; + tx_ctl->tx_control_2 |= rtscts_duration & + AR5K_4W_TX_DESC_CTL2_RTS_DURATION; + tx_ctl->tx_control_3 |= AR5K_REG_SM(rtscts_rate, + AR5K_4W_TX_DESC_CTL3_RTS_CTS_RATE); + } + + return 0; +} + +/* + * Proccess the tx status descriptor on 5210/5211 + */ +static int ath5k_hw_proc_2word_tx_status(struct ath5k_hw *ah __unused, + struct ath5k_desc *desc, struct ath5k_tx_status *ts) +{ + struct ath5k_hw_2w_tx_ctl *tx_ctl; + struct ath5k_hw_tx_status *tx_status; + + tx_ctl = &desc->ud.ds_tx5210.tx_ctl; + tx_status = &desc->ud.ds_tx5210.tx_stat; + + /* No frame has been send or error */ + if ((tx_status->tx_status_1 & AR5K_DESC_TX_STATUS1_DONE) == 0) + return -EINPROGRESS; + + /* + * Get descriptor status + */ + ts->ts_tstamp = AR5K_REG_MS(tx_status->tx_status_0, + AR5K_DESC_TX_STATUS0_SEND_TIMESTAMP); + ts->ts_shortretry = AR5K_REG_MS(tx_status->tx_status_0, + AR5K_DESC_TX_STATUS0_SHORT_RETRY_COUNT); + ts->ts_longretry = AR5K_REG_MS(tx_status->tx_status_0, + AR5K_DESC_TX_STATUS0_LONG_RETRY_COUNT); + /*TODO: ts->ts_virtcol + test*/ + ts->ts_seqnum = AR5K_REG_MS(tx_status->tx_status_1, + AR5K_DESC_TX_STATUS1_SEQ_NUM); + ts->ts_rssi = AR5K_REG_MS(tx_status->tx_status_1, + AR5K_DESC_TX_STATUS1_ACK_SIG_STRENGTH); + ts->ts_antenna = 1; + ts->ts_status = 0; + ts->ts_rate[0] = AR5K_REG_MS(tx_ctl->tx_control_0, + AR5K_2W_TX_DESC_CTL0_XMIT_RATE); + ts->ts_retry[0] = ts->ts_longretry; + ts->ts_final_idx = 0; + + if (!(tx_status->tx_status_0 & AR5K_DESC_TX_STATUS0_FRAME_XMIT_OK)) { + if (tx_status->tx_status_0 & + AR5K_DESC_TX_STATUS0_EXCESSIVE_RETRIES) + ts->ts_status |= AR5K_TXERR_XRETRY; + + if (tx_status->tx_status_0 & AR5K_DESC_TX_STATUS0_FIFO_UNDERRUN) + ts->ts_status |= AR5K_TXERR_FIFO; + + if (tx_status->tx_status_0 & AR5K_DESC_TX_STATUS0_FILTERED) + ts->ts_status |= AR5K_TXERR_FILT; + } + + return 0; +} + +/* + * Proccess a tx status descriptor on 5212 + */ +static int ath5k_hw_proc_4word_tx_status(struct ath5k_hw *ah __unused, + struct ath5k_desc *desc, struct ath5k_tx_status *ts) +{ + struct ath5k_hw_4w_tx_ctl *tx_ctl; + struct ath5k_hw_tx_status *tx_status; + + tx_ctl = &desc->ud.ds_tx5212.tx_ctl; + tx_status = &desc->ud.ds_tx5212.tx_stat; + + /* No frame has been send or error */ + if (!(tx_status->tx_status_1 & AR5K_DESC_TX_STATUS1_DONE)) + return -EINPROGRESS; + + /* + * Get descriptor status + */ + ts->ts_tstamp = AR5K_REG_MS(tx_status->tx_status_0, + AR5K_DESC_TX_STATUS0_SEND_TIMESTAMP); + ts->ts_shortretry = AR5K_REG_MS(tx_status->tx_status_0, + AR5K_DESC_TX_STATUS0_SHORT_RETRY_COUNT); + ts->ts_longretry = AR5K_REG_MS(tx_status->tx_status_0, + AR5K_DESC_TX_STATUS0_LONG_RETRY_COUNT); + ts->ts_seqnum = AR5K_REG_MS(tx_status->tx_status_1, + AR5K_DESC_TX_STATUS1_SEQ_NUM); + ts->ts_rssi = AR5K_REG_MS(tx_status->tx_status_1, + AR5K_DESC_TX_STATUS1_ACK_SIG_STRENGTH); + ts->ts_antenna = (tx_status->tx_status_1 & + AR5K_DESC_TX_STATUS1_XMIT_ANTENNA) ? 2 : 1; + ts->ts_status = 0; + + ts->ts_final_idx = AR5K_REG_MS(tx_status->tx_status_1, + AR5K_DESC_TX_STATUS1_FINAL_TS_INDEX); + + ts->ts_retry[0] = ts->ts_longretry; + ts->ts_rate[0] = tx_ctl->tx_control_3 & + AR5K_4W_TX_DESC_CTL3_XMIT_RATE0; + + /* TX error */ + if (!(tx_status->tx_status_0 & AR5K_DESC_TX_STATUS0_FRAME_XMIT_OK)) { + if (tx_status->tx_status_0 & + AR5K_DESC_TX_STATUS0_EXCESSIVE_RETRIES) + ts->ts_status |= AR5K_TXERR_XRETRY; + + if (tx_status->tx_status_0 & AR5K_DESC_TX_STATUS0_FIFO_UNDERRUN) + ts->ts_status |= AR5K_TXERR_FIFO; + + if (tx_status->tx_status_0 & AR5K_DESC_TX_STATUS0_FILTERED) + ts->ts_status |= AR5K_TXERR_FILT; + } + + return 0; +} + +/* + * RX Descriptors + */ + +/* + * Initialize an rx control descriptor + */ +static int ath5k_hw_setup_rx_desc(struct ath5k_hw *ah __unused, + struct ath5k_desc *desc, + u32 size, unsigned int flags) +{ + struct ath5k_hw_rx_ctl *rx_ctl; + + rx_ctl = &desc->ud.ds_rx.rx_ctl; + + /* + * Clear the descriptor + * If we don't clean the status descriptor, + * while scanning we get too many results, + * most of them virtual, after some secs + * of scanning system hangs. M.F. + */ + memset(&desc->ud.ds_rx, 0, sizeof(struct ath5k_hw_all_rx_desc)); + + /* Setup descriptor */ + rx_ctl->rx_control_1 = size & AR5K_DESC_RX_CTL1_BUF_LEN; + if (rx_ctl->rx_control_1 != size) + return -EINVAL; + + if (flags & AR5K_RXDESC_INTREQ) + rx_ctl->rx_control_1 |= AR5K_DESC_RX_CTL1_INTREQ; + + return 0; +} + +/* + * Proccess the rx status descriptor on 5210/5211 + */ +static int ath5k_hw_proc_5210_rx_status(struct ath5k_hw *ah __unused, + struct ath5k_desc *desc, struct ath5k_rx_status *rs) +{ + struct ath5k_hw_rx_status *rx_status; + + rx_status = &desc->ud.ds_rx.u.rx_stat; + + /* No frame received / not ready */ + if (!(rx_status->rx_status_1 & AR5K_5210_RX_DESC_STATUS1_DONE)) + return -EINPROGRESS; + + /* + * Frame receive status + */ + rs->rs_datalen = rx_status->rx_status_0 & + AR5K_5210_RX_DESC_STATUS0_DATA_LEN; + rs->rs_rssi = AR5K_REG_MS(rx_status->rx_status_0, + AR5K_5210_RX_DESC_STATUS0_RECEIVE_SIGNAL); + rs->rs_rate = AR5K_REG_MS(rx_status->rx_status_0, + AR5K_5210_RX_DESC_STATUS0_RECEIVE_RATE); + rs->rs_antenna = AR5K_REG_MS(rx_status->rx_status_0, + AR5K_5210_RX_DESC_STATUS0_RECEIVE_ANTENNA); + rs->rs_more = !!(rx_status->rx_status_0 & + AR5K_5210_RX_DESC_STATUS0_MORE); + /* TODO: this timestamp is 13 bit, later on we assume 15 bit */ + rs->rs_tstamp = AR5K_REG_MS(rx_status->rx_status_1, + AR5K_5210_RX_DESC_STATUS1_RECEIVE_TIMESTAMP); + rs->rs_status = 0; + rs->rs_phyerr = 0; + rs->rs_keyix = AR5K_RXKEYIX_INVALID; + + /* + * Receive/descriptor errors + */ + if (!(rx_status->rx_status_1 & + AR5K_5210_RX_DESC_STATUS1_FRAME_RECEIVE_OK)) { + if (rx_status->rx_status_1 & + AR5K_5210_RX_DESC_STATUS1_CRC_ERROR) + rs->rs_status |= AR5K_RXERR_CRC; + + if (rx_status->rx_status_1 & + AR5K_5210_RX_DESC_STATUS1_FIFO_OVERRUN) + rs->rs_status |= AR5K_RXERR_FIFO; + + if (rx_status->rx_status_1 & + AR5K_5210_RX_DESC_STATUS1_PHY_ERROR) { + rs->rs_status |= AR5K_RXERR_PHY; + rs->rs_phyerr |= AR5K_REG_MS(rx_status->rx_status_1, + AR5K_5210_RX_DESC_STATUS1_PHY_ERROR); + } + + if (rx_status->rx_status_1 & + AR5K_5210_RX_DESC_STATUS1_DECRYPT_CRC_ERROR) + rs->rs_status |= AR5K_RXERR_DECRYPT; + } + + return 0; +} + +/* + * Proccess the rx status descriptor on 5212 + */ +static int ath5k_hw_proc_5212_rx_status(struct ath5k_hw *ah __unused, + struct ath5k_desc *desc, struct ath5k_rx_status *rs) +{ + struct ath5k_hw_rx_status *rx_status; + struct ath5k_hw_rx_error *rx_err; + + rx_status = &desc->ud.ds_rx.u.rx_stat; + + /* Overlay on error */ + rx_err = &desc->ud.ds_rx.u.rx_err; + + /* No frame received / not ready */ + if (!(rx_status->rx_status_1 & AR5K_5212_RX_DESC_STATUS1_DONE)) + return -EINPROGRESS; + + /* + * Frame receive status + */ + rs->rs_datalen = rx_status->rx_status_0 & + AR5K_5212_RX_DESC_STATUS0_DATA_LEN; + rs->rs_rssi = AR5K_REG_MS(rx_status->rx_status_0, + AR5K_5212_RX_DESC_STATUS0_RECEIVE_SIGNAL); + rs->rs_rate = AR5K_REG_MS(rx_status->rx_status_0, + AR5K_5212_RX_DESC_STATUS0_RECEIVE_RATE); + rs->rs_antenna = AR5K_REG_MS(rx_status->rx_status_0, + AR5K_5212_RX_DESC_STATUS0_RECEIVE_ANTENNA); + rs->rs_more = !!(rx_status->rx_status_0 & + AR5K_5212_RX_DESC_STATUS0_MORE); + rs->rs_tstamp = AR5K_REG_MS(rx_status->rx_status_1, + AR5K_5212_RX_DESC_STATUS1_RECEIVE_TIMESTAMP); + rs->rs_status = 0; + rs->rs_phyerr = 0; + rs->rs_keyix = AR5K_RXKEYIX_INVALID; + + /* + * Receive/descriptor errors + */ + if (!(rx_status->rx_status_1 & + AR5K_5212_RX_DESC_STATUS1_FRAME_RECEIVE_OK)) { + if (rx_status->rx_status_1 & + AR5K_5212_RX_DESC_STATUS1_CRC_ERROR) + rs->rs_status |= AR5K_RXERR_CRC; + + if (rx_status->rx_status_1 & + AR5K_5212_RX_DESC_STATUS1_PHY_ERROR) { + rs->rs_status |= AR5K_RXERR_PHY; + rs->rs_phyerr |= AR5K_REG_MS(rx_err->rx_error_1, + AR5K_RX_DESC_ERROR1_PHY_ERROR_CODE); + } + + if (rx_status->rx_status_1 & + AR5K_5212_RX_DESC_STATUS1_DECRYPT_CRC_ERROR) + rs->rs_status |= AR5K_RXERR_DECRYPT; + + if (rx_status->rx_status_1 & + AR5K_5212_RX_DESC_STATUS1_MIC_ERROR) + rs->rs_status |= AR5K_RXERR_MIC; + } + + return 0; +} + +/* + * Init function pointers inside ath5k_hw struct + */ +int ath5k_hw_init_desc_functions(struct ath5k_hw *ah) +{ + + if (ah->ah_version != AR5K_AR5210 && + ah->ah_version != AR5K_AR5211 && + ah->ah_version != AR5K_AR5212) + return -ENOTSUP; + + if (ah->ah_version == AR5K_AR5212) { + ah->ah_setup_rx_desc = ath5k_hw_setup_rx_desc; + ah->ah_setup_tx_desc = ath5k_hw_setup_4word_tx_desc; + ah->ah_proc_tx_desc = ath5k_hw_proc_4word_tx_status; + } else { + ah->ah_setup_rx_desc = ath5k_hw_setup_rx_desc; + ah->ah_setup_tx_desc = ath5k_hw_setup_2word_tx_desc; + ah->ah_proc_tx_desc = ath5k_hw_proc_2word_tx_status; + } + + if (ah->ah_version == AR5K_AR5212) + ah->ah_proc_rx_desc = ath5k_hw_proc_5212_rx_status; + else if (ah->ah_version <= AR5K_AR5211) + ah->ah_proc_rx_desc = ath5k_hw_proc_5210_rx_status; + + return 0; +} + diff --git a/gpxe/src/drivers/net/ath5k/ath5k_dma.c b/gpxe/src/drivers/net/ath5k/ath5k_dma.c new file mode 100644 index 00000000..23c4cf91 --- /dev/null +++ b/gpxe/src/drivers/net/ath5k/ath5k_dma.c @@ -0,0 +1,631 @@ +/* + * Copyright (c) 2004-2008 Reyk Floeter <reyk@openbsd.org> + * Copyright (c) 2006-2008 Nick Kossifidis <mickflemm@gmail.com> + * + * Lightly modified for gPXE, July 2009, by Joshua Oreman <oremanj@rwcr.net>. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +FILE_LICENCE ( MIT ); + +/*************************************\ +* DMA and interrupt masking functions * +\*************************************/ + +/* + * dma.c - DMA and interrupt masking functions + * + * Here we setup descriptor pointers (rxdp/txdp) start/stop dma engine and + * handle queue setup for 5210 chipset (rest are handled on qcu.c). + * Also we setup interrupt mask register (IMR) and read the various iterrupt + * status registers (ISR). + * + * TODO: Handle SISR on 5211+ and introduce a function to return the queue + * number that resulted the interrupt. + */ + +#include <unistd.h> + +#include "ath5k.h" +#include "reg.h" +#include "base.h" + +/*********\ +* Receive * +\*********/ + +/** + * ath5k_hw_start_rx_dma - Start DMA receive + * + * @ah: The &struct ath5k_hw + */ +void ath5k_hw_start_rx_dma(struct ath5k_hw *ah) +{ + ath5k_hw_reg_write(ah, AR5K_CR_RXE, AR5K_CR); + ath5k_hw_reg_read(ah, AR5K_CR); +} + +/** + * ath5k_hw_stop_rx_dma - Stop DMA receive + * + * @ah: The &struct ath5k_hw + */ +int ath5k_hw_stop_rx_dma(struct ath5k_hw *ah) +{ + unsigned int i; + + ath5k_hw_reg_write(ah, AR5K_CR_RXD, AR5K_CR); + + /* + * It may take some time to disable the DMA receive unit + */ + for (i = 1000; i > 0 && + (ath5k_hw_reg_read(ah, AR5K_CR) & AR5K_CR_RXE) != 0; + i--) + udelay(10); + + return i ? 0 : -EBUSY; +} + +/** + * ath5k_hw_get_rxdp - Get RX Descriptor's address + * + * @ah: The &struct ath5k_hw + * + * XXX: Is RXDP read and clear ? + */ +u32 ath5k_hw_get_rxdp(struct ath5k_hw *ah) +{ + return ath5k_hw_reg_read(ah, AR5K_RXDP); +} + +/** + * ath5k_hw_set_rxdp - Set RX Descriptor's address + * + * @ah: The &struct ath5k_hw + * @phys_addr: RX descriptor address + * + * XXX: Should we check if rx is enabled before setting rxdp ? + */ +void ath5k_hw_set_rxdp(struct ath5k_hw *ah, u32 phys_addr) +{ + ath5k_hw_reg_write(ah, phys_addr, AR5K_RXDP); +} + + +/**********\ +* Transmit * +\**********/ + +/** + * ath5k_hw_start_tx_dma - Start DMA transmit for a specific queue + * + * @ah: The &struct ath5k_hw + * @queue: The hw queue number + * + * Start DMA transmit for a specific queue and since 5210 doesn't have + * QCU/DCU, set up queue parameters for 5210 here based on queue type (one + * queue for normal data and one queue for beacons). For queue setup + * on newer chips check out qcu.c. Returns -EINVAL if queue number is out + * of range or if queue is already disabled. + * + * NOTE: Must be called after setting up tx control descriptor for that + * queue (see below). + */ +int ath5k_hw_start_tx_dma(struct ath5k_hw *ah, unsigned int queue) +{ + u32 tx_queue; + + /* Return if queue is declared inactive */ + if (ah->ah_txq.tqi_type == AR5K_TX_QUEUE_INACTIVE) + return -EIO; + + if (ah->ah_version == AR5K_AR5210) { + tx_queue = ath5k_hw_reg_read(ah, AR5K_CR); + + /* Assume always a data queue */ + tx_queue |= AR5K_CR_TXE0 & ~AR5K_CR_TXD0; + + /* Start queue */ + ath5k_hw_reg_write(ah, tx_queue, AR5K_CR); + ath5k_hw_reg_read(ah, AR5K_CR); + } else { + /* Return if queue is disabled */ + if (AR5K_REG_READ_Q(ah, AR5K_QCU_TXD, queue)) + return -EIO; + + /* Start queue */ + AR5K_REG_WRITE_Q(ah, AR5K_QCU_TXE, queue); + } + + return 0; +} + +/** + * ath5k_hw_stop_tx_dma - Stop DMA transmit on a specific queue + * + * @ah: The &struct ath5k_hw + * @queue: The hw queue number + * + * Stop DMA transmit on a specific hw queue and drain queue so we don't + * have any pending frames. Returns -EBUSY if we still have pending frames, + * -EINVAL if queue number is out of range. + * + */ +int ath5k_hw_stop_tx_dma(struct ath5k_hw *ah, unsigned int queue) +{ + unsigned int i = 40; + u32 tx_queue, pending; + + /* Return if queue is declared inactive */ + if (ah->ah_txq.tqi_type == AR5K_TX_QUEUE_INACTIVE) + return -EIO; + + if (ah->ah_version == AR5K_AR5210) { + tx_queue = ath5k_hw_reg_read(ah, AR5K_CR); + + /* Assume a data queue */ + tx_queue |= AR5K_CR_TXD0 & ~AR5K_CR_TXE0; + + /* Stop queue */ + ath5k_hw_reg_write(ah, tx_queue, AR5K_CR); + ath5k_hw_reg_read(ah, AR5K_CR); + } else { + /* + * Schedule TX disable and wait until queue is empty + */ + AR5K_REG_WRITE_Q(ah, AR5K_QCU_TXD, queue); + + /*Check for pending frames*/ + do { + pending = ath5k_hw_reg_read(ah, + AR5K_QUEUE_STATUS(queue)) & + AR5K_QCU_STS_FRMPENDCNT; + udelay(100); + } while (--i && pending); + + /* For 2413+ order PCU to drop packets using + * QUIET mechanism */ + if (ah->ah_mac_version >= (AR5K_SREV_AR2414 >> 4) && pending) { + /* Set periodicity and duration */ + ath5k_hw_reg_write(ah, + AR5K_REG_SM(100, AR5K_QUIET_CTL2_QT_PER)| + AR5K_REG_SM(10, AR5K_QUIET_CTL2_QT_DUR), + AR5K_QUIET_CTL2); + + /* Enable quiet period for current TSF */ + ath5k_hw_reg_write(ah, + AR5K_QUIET_CTL1_QT_EN | + AR5K_REG_SM(ath5k_hw_reg_read(ah, + AR5K_TSF_L32_5211) >> 10, + AR5K_QUIET_CTL1_NEXT_QT_TSF), + AR5K_QUIET_CTL1); + + /* Force channel idle high */ + AR5K_REG_ENABLE_BITS(ah, AR5K_DIAG_SW_5211, + AR5K_DIAG_SW_CHANEL_IDLE_HIGH); + + /* Wait a while and disable mechanism */ + udelay(200); + AR5K_REG_DISABLE_BITS(ah, AR5K_QUIET_CTL1, + AR5K_QUIET_CTL1_QT_EN); + + /* Re-check for pending frames */ + i = 40; + do { + pending = ath5k_hw_reg_read(ah, + AR5K_QUEUE_STATUS(queue)) & + AR5K_QCU_STS_FRMPENDCNT; + udelay(100); + } while (--i && pending); + + AR5K_REG_DISABLE_BITS(ah, AR5K_DIAG_SW_5211, + AR5K_DIAG_SW_CHANEL_IDLE_HIGH); + } + + /* Clear register */ + ath5k_hw_reg_write(ah, 0, AR5K_QCU_TXD); + if (pending) + return -EBUSY; + } + + /* TODO: Check for success on 5210 else return error */ + return 0; +} + +/** + * ath5k_hw_get_txdp - Get TX Descriptor's address for a specific queue + * + * @ah: The &struct ath5k_hw + * @queue: The hw queue number + * + * Get TX descriptor's address for a specific queue. For 5210 we ignore + * the queue number and use tx queue type since we only have 2 queues. + * We use TXDP0 for normal data queue and TXDP1 for beacon queue. + * For newer chips with QCU/DCU we just read the corresponding TXDP register. + * + * XXX: Is TXDP read and clear ? + */ +u32 ath5k_hw_get_txdp(struct ath5k_hw *ah, unsigned int queue) +{ + u16 tx_reg; + + /* + * Get the transmit queue descriptor pointer from the selected queue + */ + /*5210 doesn't have QCU*/ + if (ah->ah_version == AR5K_AR5210) { + /* Assume a data queue */ + tx_reg = AR5K_NOQCU_TXDP0; + } else { + tx_reg = AR5K_QUEUE_TXDP(queue); + } + + return ath5k_hw_reg_read(ah, tx_reg); +} + +/** + * ath5k_hw_set_txdp - Set TX Descriptor's address for a specific queue + * + * @ah: The &struct ath5k_hw + * @queue: The hw queue number + * + * Set TX descriptor's address for a specific queue. For 5210 we ignore + * the queue number and we use tx queue type since we only have 2 queues + * so as above we use TXDP0 for normal data queue and TXDP1 for beacon queue. + * For newer chips with QCU/DCU we just set the corresponding TXDP register. + * Returns -EINVAL if queue type is invalid for 5210 and -EIO if queue is still + * active. + */ +int ath5k_hw_set_txdp(struct ath5k_hw *ah, unsigned int queue, u32 phys_addr) +{ + u16 tx_reg; + + /* + * Set the transmit queue descriptor pointer register by type + * on 5210 + */ + if (ah->ah_version == AR5K_AR5210) { + /* Assume a data queue */ + tx_reg = AR5K_NOQCU_TXDP0; + } else { + /* + * Set the transmit queue descriptor pointer for + * the selected queue on QCU for 5211+ + * (this won't work if the queue is still active) + */ + if (AR5K_REG_READ_Q(ah, AR5K_QCU_TXE, queue)) + return -EIO; + + tx_reg = AR5K_QUEUE_TXDP(queue); + } + + /* Set descriptor pointer */ + ath5k_hw_reg_write(ah, phys_addr, tx_reg); + + return 0; +} + +/** + * ath5k_hw_update_tx_triglevel - Update tx trigger level + * + * @ah: The &struct ath5k_hw + * @increase: Flag to force increase of trigger level + * + * This function increases/decreases the tx trigger level for the tx fifo + * buffer (aka FIFO threshold) that is used to indicate when PCU flushes + * the buffer and transmits it's data. Lowering this results sending small + * frames more quickly but can lead to tx underruns, raising it a lot can + * result other problems (i think bmiss is related). Right now we start with + * the lowest possible (64Bytes) and if we get tx underrun we increase it using + * the increase flag. Returns -EIO if we have have reached maximum/minimum. + * + * XXX: Link this with tx DMA size ? + * XXX: Use it to save interrupts ? + * TODO: Needs testing, i think it's related to bmiss... + */ +int ath5k_hw_update_tx_triglevel(struct ath5k_hw *ah, int increase) +{ + u32 trigger_level, imr; + int ret = -EIO; + + /* + * Disable interrupts by setting the mask + */ + imr = ath5k_hw_set_imr(ah, ah->ah_imr & ~AR5K_INT_GLOBAL); + + trigger_level = AR5K_REG_MS(ath5k_hw_reg_read(ah, AR5K_TXCFG), + AR5K_TXCFG_TXFULL); + + if (!increase) { + if (--trigger_level < AR5K_TUNE_MIN_TX_FIFO_THRES) + goto done; + } else + trigger_level += + ((AR5K_TUNE_MAX_TX_FIFO_THRES - trigger_level) / 2); + + /* + * Update trigger level on success + */ + if (ah->ah_version == AR5K_AR5210) + ath5k_hw_reg_write(ah, trigger_level, AR5K_TRIG_LVL); + else + AR5K_REG_WRITE_BITS(ah, AR5K_TXCFG, + AR5K_TXCFG_TXFULL, trigger_level); + + ret = 0; + +done: + /* + * Restore interrupt mask + */ + ath5k_hw_set_imr(ah, imr); + + return ret; +} + +/*******************\ +* Interrupt masking * +\*******************/ + +/** + * ath5k_hw_is_intr_pending - Check if we have pending interrupts + * + * @ah: The &struct ath5k_hw + * + * Check if we have pending interrupts to process. Returns 1 if we + * have pending interrupts and 0 if we haven't. + */ +int ath5k_hw_is_intr_pending(struct ath5k_hw *ah) +{ + return ath5k_hw_reg_read(ah, AR5K_INTPEND) == 1 ? 1 : 0; +} + +/** + * ath5k_hw_get_isr - Get interrupt status + * + * @ah: The @struct ath5k_hw + * @interrupt_mask: Driver's interrupt mask used to filter out + * interrupts in sw. + * + * This function is used inside our interrupt handler to determine the reason + * for the interrupt by reading Primary Interrupt Status Register. Returns an + * abstract interrupt status mask which is mostly ISR with some uncommon bits + * being mapped on some standard non hw-specific positions + * (check out &ath5k_int). + * + * NOTE: We use read-and-clear register, so after this function is called ISR + * is zeroed. + */ +int ath5k_hw_get_isr(struct ath5k_hw *ah, enum ath5k_int *interrupt_mask) +{ + u32 data; + + /* + * Read interrupt status from the Interrupt Status register + * on 5210 + */ + if (ah->ah_version == AR5K_AR5210) { + data = ath5k_hw_reg_read(ah, AR5K_ISR); + if (data == AR5K_INT_NOCARD) { + *interrupt_mask = data; + return -ENODEV; + } + } else { + /* + * Read interrupt status from Interrupt + * Status Register shadow copy (Read And Clear) + * + * Note: PISR/SISR Not available on 5210 + */ + data = ath5k_hw_reg_read(ah, AR5K_RAC_PISR); + if (data == AR5K_INT_NOCARD) { + *interrupt_mask = data; + return -ENODEV; + } + } + + /* + * Get abstract interrupt mask (driver-compatible) + */ + *interrupt_mask = (data & AR5K_INT_COMMON) & ah->ah_imr; + + if (ah->ah_version != AR5K_AR5210) { + u32 sisr2 = ath5k_hw_reg_read(ah, AR5K_RAC_SISR2); + + /*HIU = Host Interface Unit (PCI etc)*/ + if (data & (AR5K_ISR_HIUERR)) + *interrupt_mask |= AR5K_INT_FATAL; + + /*Beacon Not Ready*/ + if (data & (AR5K_ISR_BNR)) + *interrupt_mask |= AR5K_INT_BNR; + + if (sisr2 & (AR5K_SISR2_SSERR | AR5K_SISR2_DPERR | + AR5K_SISR2_MCABT)) + *interrupt_mask |= AR5K_INT_FATAL; + + if (data & AR5K_ISR_TIM) + *interrupt_mask |= AR5K_INT_TIM; + + if (data & AR5K_ISR_BCNMISC) { + if (sisr2 & AR5K_SISR2_TIM) + *interrupt_mask |= AR5K_INT_TIM; + if (sisr2 & AR5K_SISR2_DTIM) + *interrupt_mask |= AR5K_INT_DTIM; + if (sisr2 & AR5K_SISR2_DTIM_SYNC) + *interrupt_mask |= AR5K_INT_DTIM_SYNC; + if (sisr2 & AR5K_SISR2_BCN_TIMEOUT) + *interrupt_mask |= AR5K_INT_BCN_TIMEOUT; + if (sisr2 & AR5K_SISR2_CAB_TIMEOUT) + *interrupt_mask |= AR5K_INT_CAB_TIMEOUT; + } + + if (data & AR5K_ISR_RXDOPPLER) + *interrupt_mask |= AR5K_INT_RX_DOPPLER; + if (data & AR5K_ISR_QCBRORN) { + *interrupt_mask |= AR5K_INT_QCBRORN; + ah->ah_txq_isr |= AR5K_REG_MS( + ath5k_hw_reg_read(ah, AR5K_RAC_SISR3), + AR5K_SISR3_QCBRORN); + } + if (data & AR5K_ISR_QCBRURN) { + *interrupt_mask |= AR5K_INT_QCBRURN; + ah->ah_txq_isr |= AR5K_REG_MS( + ath5k_hw_reg_read(ah, AR5K_RAC_SISR3), + AR5K_SISR3_QCBRURN); + } + if (data & AR5K_ISR_QTRIG) { + *interrupt_mask |= AR5K_INT_QTRIG; + ah->ah_txq_isr |= AR5K_REG_MS( + ath5k_hw_reg_read(ah, AR5K_RAC_SISR4), + AR5K_SISR4_QTRIG); + } + + if (data & AR5K_ISR_TXOK) + ah->ah_txq_isr |= AR5K_REG_MS( + ath5k_hw_reg_read(ah, AR5K_RAC_SISR0), + AR5K_SISR0_QCU_TXOK); + + if (data & AR5K_ISR_TXDESC) + ah->ah_txq_isr |= AR5K_REG_MS( + ath5k_hw_reg_read(ah, AR5K_RAC_SISR0), + AR5K_SISR0_QCU_TXDESC); + + if (data & AR5K_ISR_TXERR) + ah->ah_txq_isr |= AR5K_REG_MS( + ath5k_hw_reg_read(ah, AR5K_RAC_SISR1), + AR5K_SISR1_QCU_TXERR); + + if (data & AR5K_ISR_TXEOL) + ah->ah_txq_isr |= AR5K_REG_MS( + ath5k_hw_reg_read(ah, AR5K_RAC_SISR1), + AR5K_SISR1_QCU_TXEOL); + + if (data & AR5K_ISR_TXURN) + ah->ah_txq_isr |= AR5K_REG_MS( + ath5k_hw_reg_read(ah, AR5K_RAC_SISR2), + AR5K_SISR2_QCU_TXURN); + } else { + if (data & (AR5K_ISR_SSERR | AR5K_ISR_MCABT | + AR5K_ISR_HIUERR | AR5K_ISR_DPERR)) + *interrupt_mask |= AR5K_INT_FATAL; + + /* + * XXX: BMISS interrupts may occur after association. + * I found this on 5210 code but it needs testing. If this is + * true we should disable them before assoc and re-enable them + * after a successful assoc + some jiffies. + interrupt_mask &= ~AR5K_INT_BMISS; + */ + } + + return 0; +} + +/** + * ath5k_hw_set_imr - Set interrupt mask + * + * @ah: The &struct ath5k_hw + * @new_mask: The new interrupt mask to be set + * + * Set the interrupt mask in hw to save interrupts. We do that by mapping + * ath5k_int bits to hw-specific bits to remove abstraction and writing + * Interrupt Mask Register. + */ +enum ath5k_int ath5k_hw_set_imr(struct ath5k_hw *ah, enum ath5k_int new_mask) +{ + enum ath5k_int old_mask, int_mask; + + old_mask = ah->ah_imr; + + /* + * Disable card interrupts to prevent any race conditions + * (they will be re-enabled afterwards if AR5K_INT GLOBAL + * is set again on the new mask). + */ + if (old_mask & AR5K_INT_GLOBAL) { + ath5k_hw_reg_write(ah, AR5K_IER_DISABLE, AR5K_IER); + ath5k_hw_reg_read(ah, AR5K_IER); + } + + /* + * Add additional, chipset-dependent interrupt mask flags + * and write them to the IMR (interrupt mask register). + */ + int_mask = new_mask & AR5K_INT_COMMON; + + if (ah->ah_version != AR5K_AR5210) { + /* Preserve per queue TXURN interrupt mask */ + u32 simr2 = ath5k_hw_reg_read(ah, AR5K_SIMR2) + & AR5K_SIMR2_QCU_TXURN; + + if (new_mask & AR5K_INT_FATAL) { + int_mask |= AR5K_IMR_HIUERR; + simr2 |= (AR5K_SIMR2_MCABT | AR5K_SIMR2_SSERR + | AR5K_SIMR2_DPERR); + } + + /*Beacon Not Ready*/ + if (new_mask & AR5K_INT_BNR) + int_mask |= AR5K_INT_BNR; + + if (new_mask & AR5K_INT_TIM) + int_mask |= AR5K_IMR_TIM; + + if (new_mask & AR5K_INT_TIM) + simr2 |= AR5K_SISR2_TIM; + if (new_mask & AR5K_INT_DTIM) + simr2 |= AR5K_SISR2_DTIM; + if (new_mask & AR5K_INT_DTIM_SYNC) + simr2 |= AR5K_SISR2_DTIM_SYNC; + if (new_mask & AR5K_INT_BCN_TIMEOUT) + simr2 |= AR5K_SISR2_BCN_TIMEOUT; + if (new_mask & AR5K_INT_CAB_TIMEOUT) + simr2 |= AR5K_SISR2_CAB_TIMEOUT; + + if (new_mask & AR5K_INT_RX_DOPPLER) + int_mask |= AR5K_IMR_RXDOPPLER; + + /* Note: Per queue interrupt masks + * are set via reset_tx_queue (qcu.c) */ + ath5k_hw_reg_write(ah, int_mask, AR5K_PIMR); + ath5k_hw_reg_write(ah, simr2, AR5K_SIMR2); + + } else { + if (new_mask & AR5K_INT_FATAL) + int_mask |= (AR5K_IMR_SSERR | AR5K_IMR_MCABT + | AR5K_IMR_HIUERR | AR5K_IMR_DPERR); + + ath5k_hw_reg_write(ah, int_mask, AR5K_IMR); + } + + /* If RXNOFRM interrupt is masked disable it + * by setting AR5K_RXNOFRM to zero */ + if (!(new_mask & AR5K_INT_RXNOFRM)) + ath5k_hw_reg_write(ah, 0, AR5K_RXNOFRM); + + /* Store new interrupt mask */ + ah->ah_imr = new_mask; + + /* ..re-enable interrupts if AR5K_INT_GLOBAL is set */ + if (new_mask & AR5K_INT_GLOBAL) { + ath5k_hw_reg_write(ah, ah->ah_ier, AR5K_IER); + ath5k_hw_reg_read(ah, AR5K_IER); + } + + return old_mask; +} + diff --git a/gpxe/src/drivers/net/ath5k/ath5k_eeprom.c b/gpxe/src/drivers/net/ath5k/ath5k_eeprom.c new file mode 100644 index 00000000..0f62c4c7 --- /dev/null +++ b/gpxe/src/drivers/net/ath5k/ath5k_eeprom.c @@ -0,0 +1,1760 @@ +/* + * Copyright (c) 2004-2008 Reyk Floeter <reyk@openbsd.org> + * Copyright (c) 2006-2009 Nick Kossifidis <mickflemm@gmail.com> + * Copyright (c) 2008-2009 Felix Fietkau <nbd@openwrt.org> + * + * Lightly modified for gPXE, July 2009, by Joshua Oreman <oremanj@rwcr.net>. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +FILE_LICENCE ( MIT ); + +/*************************************\ +* EEPROM access functions and helpers * +\*************************************/ + +#include <unistd.h> +#include <stdlib.h> + +#include "ath5k.h" +#include "reg.h" +#include "base.h" + +/* + * Read from eeprom + */ +static int ath5k_hw_eeprom_read(struct ath5k_hw *ah, u32 offset, u16 *data) +{ + u32 status, timeout; + + /* + * Initialize EEPROM access + */ + if (ah->ah_version == AR5K_AR5210) { + AR5K_REG_ENABLE_BITS(ah, AR5K_PCICFG, AR5K_PCICFG_EEAE); + (void)ath5k_hw_reg_read(ah, AR5K_EEPROM_BASE + (4 * offset)); + } else { + ath5k_hw_reg_write(ah, offset, AR5K_EEPROM_BASE); + AR5K_REG_ENABLE_BITS(ah, AR5K_EEPROM_CMD, + AR5K_EEPROM_CMD_READ); + } + + for (timeout = AR5K_TUNE_REGISTER_TIMEOUT; timeout > 0; timeout--) { + status = ath5k_hw_reg_read(ah, AR5K_EEPROM_STATUS); + if (status & AR5K_EEPROM_STAT_RDDONE) { + if (status & AR5K_EEPROM_STAT_RDERR) + return -EIO; + *data = (u16)(ath5k_hw_reg_read(ah, AR5K_EEPROM_DATA) & + 0xffff); + return 0; + } + udelay(15); + } + + return -ETIMEDOUT; +} + +/* + * Translate binary channel representation in EEPROM to frequency + */ +static u16 ath5k_eeprom_bin2freq(struct ath5k_eeprom_info *ee, u16 bin, + unsigned int mode) +{ + u16 val; + + if (bin == AR5K_EEPROM_CHANNEL_DIS) + return bin; + + if (mode == AR5K_EEPROM_MODE_11A) { + if (ee->ee_version > AR5K_EEPROM_VERSION_3_2) + val = (5 * bin) + 4800; + else + val = bin > 62 ? (10 * 62) + (5 * (bin - 62)) + 5100 : + (bin * 10) + 5100; + } else { + if (ee->ee_version > AR5K_EEPROM_VERSION_3_2) + val = bin + 2300; + else + val = bin + 2400; + } + + return val; +} + +/* + * Initialize eeprom & capabilities structs + */ +static int +ath5k_eeprom_init_header(struct ath5k_hw *ah) +{ + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + int ret; + u16 val; + + /* + * Read values from EEPROM and store them in the capability structure + */ + AR5K_EEPROM_READ_HDR(AR5K_EEPROM_MAGIC, ee_magic); + AR5K_EEPROM_READ_HDR(AR5K_EEPROM_PROTECT, ee_protect); + AR5K_EEPROM_READ_HDR(AR5K_EEPROM_REG_DOMAIN, ee_regdomain); + AR5K_EEPROM_READ_HDR(AR5K_EEPROM_VERSION, ee_version); + AR5K_EEPROM_READ_HDR(AR5K_EEPROM_HDR, ee_header); + + /* Return if we have an old EEPROM */ + if (ah->ah_ee_version < AR5K_EEPROM_VERSION_3_0) + return 0; + + AR5K_EEPROM_READ_HDR(AR5K_EEPROM_ANT_GAIN(ah->ah_ee_version), + ee_ant_gain); + + if (ah->ah_ee_version >= AR5K_EEPROM_VERSION_4_0) { + AR5K_EEPROM_READ_HDR(AR5K_EEPROM_MISC0, ee_misc0); + AR5K_EEPROM_READ_HDR(AR5K_EEPROM_MISC1, ee_misc1); + + /* XXX: Don't know which versions include these two */ + AR5K_EEPROM_READ_HDR(AR5K_EEPROM_MISC2, ee_misc2); + + if (ee->ee_version >= AR5K_EEPROM_VERSION_4_3) + AR5K_EEPROM_READ_HDR(AR5K_EEPROM_MISC3, ee_misc3); + + if (ee->ee_version >= AR5K_EEPROM_VERSION_5_0) { + AR5K_EEPROM_READ_HDR(AR5K_EEPROM_MISC4, ee_misc4); + AR5K_EEPROM_READ_HDR(AR5K_EEPROM_MISC5, ee_misc5); + AR5K_EEPROM_READ_HDR(AR5K_EEPROM_MISC6, ee_misc6); + } + } + + if (ah->ah_ee_version < AR5K_EEPROM_VERSION_3_3) { + AR5K_EEPROM_READ(AR5K_EEPROM_OBDB0_2GHZ, val); + ee->ee_ob[AR5K_EEPROM_MODE_11B][0] = val & 0x7; + ee->ee_db[AR5K_EEPROM_MODE_11B][0] = (val >> 3) & 0x7; + + AR5K_EEPROM_READ(AR5K_EEPROM_OBDB1_2GHZ, val); + ee->ee_ob[AR5K_EEPROM_MODE_11G][0] = val & 0x7; + ee->ee_db[AR5K_EEPROM_MODE_11G][0] = (val >> 3) & 0x7; + } + + AR5K_EEPROM_READ(AR5K_EEPROM_IS_HB63, val); + + if ((ah->ah_mac_version == (AR5K_SREV_AR2425 >> 4)) && val) + ee->ee_is_hb63 = 1; + else + ee->ee_is_hb63 = 0; + + AR5K_EEPROM_READ(AR5K_EEPROM_RFKILL, val); + ee->ee_rfkill_pin = (u8) AR5K_REG_MS(val, AR5K_EEPROM_RFKILL_GPIO_SEL); + ee->ee_rfkill_pol = val & AR5K_EEPROM_RFKILL_POLARITY ? 1 : 0; + + return 0; +} + + +/* + * Read antenna infos from eeprom + */ +static int ath5k_eeprom_read_ants(struct ath5k_hw *ah, u32 *offset, + unsigned int mode) +{ + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + u32 o = *offset; + u16 val; + int ret, i = 0; + + AR5K_EEPROM_READ(o++, val); + ee->ee_switch_settling[mode] = (val >> 8) & 0x7f; + ee->ee_atn_tx_rx[mode] = (val >> 2) & 0x3f; + ee->ee_ant_control[mode][i] = (val << 4) & 0x3f; + + AR5K_EEPROM_READ(o++, val); + ee->ee_ant_control[mode][i++] |= (val >> 12) & 0xf; + ee->ee_ant_control[mode][i++] = (val >> 6) & 0x3f; + ee->ee_ant_control[mode][i++] = val & 0x3f; + + AR5K_EEPROM_READ(o++, val); + ee->ee_ant_control[mode][i++] = (val >> 10) & 0x3f; + ee->ee_ant_control[mode][i++] = (val >> 4) & 0x3f; + ee->ee_ant_control[mode][i] = (val << 2) & 0x3f; + + AR5K_EEPROM_READ(o++, val); + ee->ee_ant_control[mode][i++] |= (val >> 14) & 0x3; + ee->ee_ant_control[mode][i++] = (val >> 8) & 0x3f; + ee->ee_ant_control[mode][i++] = (val >> 2) & 0x3f; + ee->ee_ant_control[mode][i] = (val << 4) & 0x3f; + + AR5K_EEPROM_READ(o++, val); + ee->ee_ant_control[mode][i++] |= (val >> 12) & 0xf; + ee->ee_ant_control[mode][i++] = (val >> 6) & 0x3f; + ee->ee_ant_control[mode][i++] = val & 0x3f; + + /* Get antenna modes */ + ah->ah_antenna[mode][0] = + (ee->ee_ant_control[mode][0] << 4); + ah->ah_antenna[mode][AR5K_ANT_FIXED_A] = + ee->ee_ant_control[mode][1] | + (ee->ee_ant_control[mode][2] << 6) | + (ee->ee_ant_control[mode][3] << 12) | + (ee->ee_ant_control[mode][4] << 18) | + (ee->ee_ant_control[mode][5] << 24); + ah->ah_antenna[mode][AR5K_ANT_FIXED_B] = + ee->ee_ant_control[mode][6] | + (ee->ee_ant_control[mode][7] << 6) | + (ee->ee_ant_control[mode][8] << 12) | + (ee->ee_ant_control[mode][9] << 18) | + (ee->ee_ant_control[mode][10] << 24); + + /* return new offset */ + *offset = o; + + return 0; +} + +/* + * Read supported modes and some mode-specific calibration data + * from eeprom + */ +static int ath5k_eeprom_read_modes(struct ath5k_hw *ah, u32 *offset, + unsigned int mode) +{ + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + u32 o = *offset; + u16 val; + int ret; + + ee->ee_n_piers[mode] = 0; + AR5K_EEPROM_READ(o++, val); + ee->ee_adc_desired_size[mode] = (s8)((val >> 8) & 0xff); + switch(mode) { + case AR5K_EEPROM_MODE_11A: + ee->ee_ob[mode][3] = (val >> 5) & 0x7; + ee->ee_db[mode][3] = (val >> 2) & 0x7; + ee->ee_ob[mode][2] = (val << 1) & 0x7; + + AR5K_EEPROM_READ(o++, val); + ee->ee_ob[mode][2] |= (val >> 15) & 0x1; + ee->ee_db[mode][2] = (val >> 12) & 0x7; + ee->ee_ob[mode][1] = (val >> 9) & 0x7; + ee->ee_db[mode][1] = (val >> 6) & 0x7; + ee->ee_ob[mode][0] = (val >> 3) & 0x7; + ee->ee_db[mode][0] = val & 0x7; + break; + case AR5K_EEPROM_MODE_11G: + case AR5K_EEPROM_MODE_11B: + ee->ee_ob[mode][1] = (val >> 4) & 0x7; + ee->ee_db[mode][1] = val & 0x7; + break; + } + + AR5K_EEPROM_READ(o++, val); + ee->ee_tx_end2xlna_enable[mode] = (val >> 8) & 0xff; + ee->ee_thr_62[mode] = val & 0xff; + + if (ah->ah_ee_version <= AR5K_EEPROM_VERSION_3_2) + ee->ee_thr_62[mode] = mode == AR5K_EEPROM_MODE_11A ? 15 : 28; + + AR5K_EEPROM_READ(o++, val); + ee->ee_tx_end2xpa_disable[mode] = (val >> 8) & 0xff; + ee->ee_tx_frm2xpa_enable[mode] = val & 0xff; + + AR5K_EEPROM_READ(o++, val); + ee->ee_pga_desired_size[mode] = (val >> 8) & 0xff; + + if ((val & 0xff) & 0x80) + ee->ee_noise_floor_thr[mode] = -((((val & 0xff) ^ 0xff)) + 1); + else + ee->ee_noise_floor_thr[mode] = val & 0xff; + + if (ah->ah_ee_version <= AR5K_EEPROM_VERSION_3_2) + ee->ee_noise_floor_thr[mode] = + mode == AR5K_EEPROM_MODE_11A ? -54 : -1; + + AR5K_EEPROM_READ(o++, val); + ee->ee_xlna_gain[mode] = (val >> 5) & 0xff; + ee->ee_x_gain[mode] = (val >> 1) & 0xf; + ee->ee_xpd[mode] = val & 0x1; + + if (ah->ah_ee_version >= AR5K_EEPROM_VERSION_4_0) + ee->ee_fixed_bias[mode] = (val >> 13) & 0x1; + + if (ah->ah_ee_version >= AR5K_EEPROM_VERSION_3_3) { + AR5K_EEPROM_READ(o++, val); + ee->ee_false_detect[mode] = (val >> 6) & 0x7f; + + if (mode == AR5K_EEPROM_MODE_11A) + ee->ee_xr_power[mode] = val & 0x3f; + else { + ee->ee_ob[mode][0] = val & 0x7; + ee->ee_db[mode][0] = (val >> 3) & 0x7; + } + } + + if (ah->ah_ee_version < AR5K_EEPROM_VERSION_3_4) { + ee->ee_i_gain[mode] = AR5K_EEPROM_I_GAIN; + ee->ee_cck_ofdm_power_delta = AR5K_EEPROM_CCK_OFDM_DELTA; + } else { + ee->ee_i_gain[mode] = (val >> 13) & 0x7; + + AR5K_EEPROM_READ(o++, val); + ee->ee_i_gain[mode] |= (val << 3) & 0x38; + + if (mode == AR5K_EEPROM_MODE_11G) { + ee->ee_cck_ofdm_power_delta = (val >> 3) & 0xff; + if (ah->ah_ee_version >= AR5K_EEPROM_VERSION_4_6) + ee->ee_scaled_cck_delta = (val >> 11) & 0x1f; + } + } + + if (ah->ah_ee_version >= AR5K_EEPROM_VERSION_4_0 && + mode == AR5K_EEPROM_MODE_11A) { + ee->ee_i_cal[mode] = (val >> 8) & 0x3f; + ee->ee_q_cal[mode] = (val >> 3) & 0x1f; + } + + if (ah->ah_ee_version < AR5K_EEPROM_VERSION_4_0) + goto done; + + /* Note: >= v5 have bg freq piers on another location + * so these freq piers are ignored for >= v5 (should be 0xff + * anyway) */ + switch(mode) { + case AR5K_EEPROM_MODE_11A: + if (ah->ah_ee_version < AR5K_EEPROM_VERSION_4_1) + break; + + AR5K_EEPROM_READ(o++, val); + ee->ee_margin_tx_rx[mode] = val & 0x3f; + break; + case AR5K_EEPROM_MODE_11B: + AR5K_EEPROM_READ(o++, val); + + ee->ee_pwr_cal_b[0].freq = + ath5k_eeprom_bin2freq(ee, val & 0xff, mode); + if (ee->ee_pwr_cal_b[0].freq != AR5K_EEPROM_CHANNEL_DIS) + ee->ee_n_piers[mode]++; + + ee->ee_pwr_cal_b[1].freq = + ath5k_eeprom_bin2freq(ee, (val >> 8) & 0xff, mode); + if (ee->ee_pwr_cal_b[1].freq != AR5K_EEPROM_CHANNEL_DIS) + ee->ee_n_piers[mode]++; + + AR5K_EEPROM_READ(o++, val); + ee->ee_pwr_cal_b[2].freq = + ath5k_eeprom_bin2freq(ee, val & 0xff, mode); + if (ee->ee_pwr_cal_b[2].freq != AR5K_EEPROM_CHANNEL_DIS) + ee->ee_n_piers[mode]++; + + if (ah->ah_ee_version >= AR5K_EEPROM_VERSION_4_1) + ee->ee_margin_tx_rx[mode] = (val >> 8) & 0x3f; + break; + case AR5K_EEPROM_MODE_11G: + AR5K_EEPROM_READ(o++, val); + + ee->ee_pwr_cal_g[0].freq = + ath5k_eeprom_bin2freq(ee, val & 0xff, mode); + if (ee->ee_pwr_cal_g[0].freq != AR5K_EEPROM_CHANNEL_DIS) + ee->ee_n_piers[mode]++; + + ee->ee_pwr_cal_g[1].freq = + ath5k_eeprom_bin2freq(ee, (val >> 8) & 0xff, mode); + if (ee->ee_pwr_cal_g[1].freq != AR5K_EEPROM_CHANNEL_DIS) + ee->ee_n_piers[mode]++; + + AR5K_EEPROM_READ(o++, val); + ee->ee_turbo_max_power[mode] = val & 0x7f; + ee->ee_xr_power[mode] = (val >> 7) & 0x3f; + + AR5K_EEPROM_READ(o++, val); + ee->ee_pwr_cal_g[2].freq = + ath5k_eeprom_bin2freq(ee, val & 0xff, mode); + if (ee->ee_pwr_cal_g[2].freq != AR5K_EEPROM_CHANNEL_DIS) + ee->ee_n_piers[mode]++; + + if (ah->ah_ee_version >= AR5K_EEPROM_VERSION_4_1) + ee->ee_margin_tx_rx[mode] = (val >> 8) & 0x3f; + + AR5K_EEPROM_READ(o++, val); + ee->ee_i_cal[mode] = (val >> 8) & 0x3f; + ee->ee_q_cal[mode] = (val >> 3) & 0x1f; + + if (ah->ah_ee_version >= AR5K_EEPROM_VERSION_4_2) { + AR5K_EEPROM_READ(o++, val); + ee->ee_cck_ofdm_gain_delta = val & 0xff; + } + break; + } + +done: + /* return new offset */ + *offset = o; + + return 0; +} + +/* + * Read turbo mode information on newer EEPROM versions + */ +static int +ath5k_eeprom_read_turbo_modes(struct ath5k_hw *ah, + u32 *offset, unsigned int mode) +{ + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + u32 o = *offset; + u16 val; + int ret; + + if (ee->ee_version < AR5K_EEPROM_VERSION_5_0) + return 0; + + switch (mode){ + case AR5K_EEPROM_MODE_11A: + ee->ee_switch_settling_turbo[mode] = (val >> 6) & 0x7f; + + ee->ee_atn_tx_rx_turbo[mode] = (val >> 13) & 0x7; + AR5K_EEPROM_READ(o++, val); + ee->ee_atn_tx_rx_turbo[mode] |= (val & 0x7) << 3; + ee->ee_margin_tx_rx_turbo[mode] = (val >> 3) & 0x3f; + + ee->ee_adc_desired_size_turbo[mode] = (val >> 9) & 0x7f; + AR5K_EEPROM_READ(o++, val); + ee->ee_adc_desired_size_turbo[mode] |= (val & 0x1) << 7; + ee->ee_pga_desired_size_turbo[mode] = (val >> 1) & 0xff; + + if (AR5K_EEPROM_EEMAP(ee->ee_misc0) >=2) + ee->ee_pd_gain_overlap = (val >> 9) & 0xf; + break; + case AR5K_EEPROM_MODE_11G: + ee->ee_switch_settling_turbo[mode] = (val >> 8) & 0x7f; + + ee->ee_atn_tx_rx_turbo[mode] = (val >> 15) & 0x7; + AR5K_EEPROM_READ(o++, val); + ee->ee_atn_tx_rx_turbo[mode] |= (val & 0x1f) << 1; + ee->ee_margin_tx_rx_turbo[mode] = (val >> 5) & 0x3f; + + ee->ee_adc_desired_size_turbo[mode] = (val >> 11) & 0x7f; + AR5K_EEPROM_READ(o++, val); + ee->ee_adc_desired_size_turbo[mode] |= (val & 0x7) << 5; + ee->ee_pga_desired_size_turbo[mode] = (val >> 3) & 0xff; + break; + } + + /* return new offset */ + *offset = o; + + return 0; +} + +/* Read mode-specific data (except power calibration data) */ +static int +ath5k_eeprom_init_modes(struct ath5k_hw *ah) +{ + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + u32 mode_offset[3]; + unsigned int mode; + u32 offset; + int ret; + + /* + * Get values for all modes + */ + mode_offset[AR5K_EEPROM_MODE_11A] = AR5K_EEPROM_MODES_11A(ah->ah_ee_version); + mode_offset[AR5K_EEPROM_MODE_11B] = AR5K_EEPROM_MODES_11B(ah->ah_ee_version); + mode_offset[AR5K_EEPROM_MODE_11G] = AR5K_EEPROM_MODES_11G(ah->ah_ee_version); + + ee->ee_turbo_max_power[AR5K_EEPROM_MODE_11A] = + AR5K_EEPROM_HDR_T_5GHZ_DBM(ee->ee_header); + + for (mode = AR5K_EEPROM_MODE_11A; mode <= AR5K_EEPROM_MODE_11G; mode++) { + offset = mode_offset[mode]; + + ret = ath5k_eeprom_read_ants(ah, &offset, mode); + if (ret) + return ret; + + ret = ath5k_eeprom_read_modes(ah, &offset, mode); + if (ret) + return ret; + + ret = ath5k_eeprom_read_turbo_modes(ah, &offset, mode); + if (ret) + return ret; + } + + /* override for older eeprom versions for better performance */ + if (ah->ah_ee_version <= AR5K_EEPROM_VERSION_3_2) { + ee->ee_thr_62[AR5K_EEPROM_MODE_11A] = 15; + ee->ee_thr_62[AR5K_EEPROM_MODE_11B] = 28; + ee->ee_thr_62[AR5K_EEPROM_MODE_11G] = 28; + } + + return 0; +} + +/* Read the frequency piers for each mode (mostly used on newer eeproms with 0xff + * frequency mask) */ +static inline int +ath5k_eeprom_read_freq_list(struct ath5k_hw *ah, int *offset, int max, + struct ath5k_chan_pcal_info *pc, unsigned int mode) +{ + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + int o = *offset; + int i = 0; + u8 freq1, freq2; + int ret; + u16 val; + + ee->ee_n_piers[mode] = 0; + while(i < max) { + AR5K_EEPROM_READ(o++, val); + + freq1 = val & 0xff; + if (!freq1) + break; + + pc[i++].freq = ath5k_eeprom_bin2freq(ee, + freq1, mode); + ee->ee_n_piers[mode]++; + + freq2 = (val >> 8) & 0xff; + if (!freq2) + break; + + pc[i++].freq = ath5k_eeprom_bin2freq(ee, + freq2, mode); + ee->ee_n_piers[mode]++; + } + + /* return new offset */ + *offset = o; + + return 0; +} + +/* Read frequency piers for 802.11a */ +static int +ath5k_eeprom_init_11a_pcal_freq(struct ath5k_hw *ah, int offset) +{ + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + struct ath5k_chan_pcal_info *pcal = ee->ee_pwr_cal_a; + int i, ret; + u16 val; + u8 mask; + + if (ee->ee_version >= AR5K_EEPROM_VERSION_3_3) { + ath5k_eeprom_read_freq_list(ah, &offset, + AR5K_EEPROM_N_5GHZ_CHAN, pcal, + AR5K_EEPROM_MODE_11A); + } else { + mask = AR5K_EEPROM_FREQ_M(ah->ah_ee_version); + + AR5K_EEPROM_READ(offset++, val); + pcal[0].freq = (val >> 9) & mask; + pcal[1].freq = (val >> 2) & mask; + pcal[2].freq = (val << 5) & mask; + + AR5K_EEPROM_READ(offset++, val); + pcal[2].freq |= (val >> 11) & 0x1f; + pcal[3].freq = (val >> 4) & mask; + pcal[4].freq = (val << 3) & mask; + + AR5K_EEPROM_READ(offset++, val); + pcal[4].freq |= (val >> 13) & 0x7; + pcal[5].freq = (val >> 6) & mask; + pcal[6].freq = (val << 1) & mask; + + AR5K_EEPROM_READ(offset++, val); + pcal[6].freq |= (val >> 15) & 0x1; + pcal[7].freq = (val >> 8) & mask; + pcal[8].freq = (val >> 1) & mask; + pcal[9].freq = (val << 6) & mask; + + AR5K_EEPROM_READ(offset++, val); + pcal[9].freq |= (val >> 10) & 0x3f; + + /* Fixed number of piers */ + ee->ee_n_piers[AR5K_EEPROM_MODE_11A] = 10; + + for (i = 0; i < AR5K_EEPROM_N_5GHZ_CHAN; i++) { + pcal[i].freq = ath5k_eeprom_bin2freq(ee, + pcal[i].freq, AR5K_EEPROM_MODE_11A); + } + } + + return 0; +} + +/* Read frequency piers for 802.11bg on eeprom versions >= 5 and eemap >= 2 */ +static inline int +ath5k_eeprom_init_11bg_2413(struct ath5k_hw *ah, unsigned int mode, int offset) +{ + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + struct ath5k_chan_pcal_info *pcal; + + switch(mode) { + case AR5K_EEPROM_MODE_11B: + pcal = ee->ee_pwr_cal_b; + break; + case AR5K_EEPROM_MODE_11G: + pcal = ee->ee_pwr_cal_g; + break; + default: + return -EINVAL; + } + + ath5k_eeprom_read_freq_list(ah, &offset, + AR5K_EEPROM_N_2GHZ_CHAN_2413, pcal, + mode); + + return 0; +} + +/* + * Read power calibration for RF5111 chips + * + * For RF5111 we have an XPD -eXternal Power Detector- curve + * for each calibrated channel. Each curve has 0,5dB Power steps + * on x axis and PCDAC steps (offsets) on y axis and looks like an + * exponential function. To recreate the curve we read 11 points + * here and interpolate later. + */ + +/* Used to match PCDAC steps with power values on RF5111 chips + * (eeprom versions < 4). For RF5111 we have 11 pre-defined PCDAC + * steps that match with the power values we read from eeprom. On + * older eeprom versions (< 3.2) these steps are equaly spaced at + * 10% of the pcdac curve -until the curve reaches it's maximum- + * (11 steps from 0 to 100%) but on newer eeprom versions (>= 3.2) + * these 11 steps are spaced in a different way. This function returns + * the pcdac steps based on eeprom version and curve min/max so that we + * can have pcdac/pwr points. + */ +static inline void +ath5k_get_pcdac_intercepts(struct ath5k_hw *ah, u8 min, u8 max, u8 *vp) +{ + static const u16 intercepts3[] = + { 0, 5, 10, 20, 30, 50, 70, 85, 90, 95, 100 }; + static const u16 intercepts3_2[] = + { 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 }; + const u16 *ip; + unsigned i; + + if (ah->ah_ee_version >= AR5K_EEPROM_VERSION_3_2) + ip = intercepts3_2; + else + ip = intercepts3; + + for (i = 0; i < ARRAY_SIZE(intercepts3); i++) + vp[i] = (ip[i] * max + (100 - ip[i]) * min) / 100; +} + +/* Convert RF5111 specific data to generic raw data + * used by interpolation code */ +static int +ath5k_eeprom_convert_pcal_info_5111(struct ath5k_hw *ah, int mode, + struct ath5k_chan_pcal_info *chinfo) +{ + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + struct ath5k_chan_pcal_info_rf5111 *pcinfo; + struct ath5k_pdgain_info *pd; + u8 pier, point, idx; + u8 *pdgain_idx = ee->ee_pdc_to_idx[mode]; + + /* Fill raw data for each calibration pier */ + for (pier = 0; pier < ee->ee_n_piers[mode]; pier++) { + + pcinfo = &chinfo[pier].rf5111_info; + + /* Allocate pd_curves for this cal pier */ + chinfo[pier].pd_curves = + calloc(AR5K_EEPROM_N_PD_CURVES, + sizeof(struct ath5k_pdgain_info)); + + if (!chinfo[pier].pd_curves) + return -ENOMEM; + + /* Only one curve for RF5111 + * find out which one and place + * in in pd_curves. + * Note: ee_x_gain is reversed here */ + for (idx = 0; idx < AR5K_EEPROM_N_PD_CURVES; idx++) { + + if (!((ee->ee_x_gain[mode] >> idx) & 0x1)) { + pdgain_idx[0] = idx; + break; + } + } + + ee->ee_pd_gains[mode] = 1; + + pd = &chinfo[pier].pd_curves[idx]; + + pd->pd_points = AR5K_EEPROM_N_PWR_POINTS_5111; + + /* Allocate pd points for this curve */ + pd->pd_step = calloc(AR5K_EEPROM_N_PWR_POINTS_5111, sizeof(u8)); + if (!pd->pd_step) + return -ENOMEM; + + pd->pd_pwr = calloc(AR5K_EEPROM_N_PWR_POINTS_5111, sizeof(s16)); + if (!pd->pd_pwr) + return -ENOMEM; + + /* Fill raw dataset + * (convert power to 0.25dB units + * for RF5112 combatibility) */ + for (point = 0; point < pd->pd_points; point++) { + + /* Absolute values */ + pd->pd_pwr[point] = 2 * pcinfo->pwr[point]; + + /* Already sorted */ + pd->pd_step[point] = pcinfo->pcdac[point]; + } + + /* Set min/max pwr */ + chinfo[pier].min_pwr = pd->pd_pwr[0]; + chinfo[pier].max_pwr = pd->pd_pwr[10]; + + } + + return 0; +} + +/* Parse EEPROM data */ +static int +ath5k_eeprom_read_pcal_info_5111(struct ath5k_hw *ah, int mode) +{ + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + struct ath5k_chan_pcal_info *pcal; + int offset, ret; + int i; + u16 val; + + offset = AR5K_EEPROM_GROUPS_START(ee->ee_version); + switch(mode) { + case AR5K_EEPROM_MODE_11A: + if (!AR5K_EEPROM_HDR_11A(ee->ee_header)) + return 0; + + ret = ath5k_eeprom_init_11a_pcal_freq(ah, + offset + AR5K_EEPROM_GROUP1_OFFSET); + if (ret < 0) + return ret; + + offset += AR5K_EEPROM_GROUP2_OFFSET; + pcal = ee->ee_pwr_cal_a; + break; + case AR5K_EEPROM_MODE_11B: + if (!AR5K_EEPROM_HDR_11B(ee->ee_header) && + !AR5K_EEPROM_HDR_11G(ee->ee_header)) + return 0; + + pcal = ee->ee_pwr_cal_b; + offset += AR5K_EEPROM_GROUP3_OFFSET; + + /* fixed piers */ + pcal[0].freq = 2412; + pcal[1].freq = 2447; + pcal[2].freq = 2484; + ee->ee_n_piers[mode] = 3; + break; + case AR5K_EEPROM_MODE_11G: + if (!AR5K_EEPROM_HDR_11G(ee->ee_header)) + return 0; + + pcal = ee->ee_pwr_cal_g; + offset += AR5K_EEPROM_GROUP4_OFFSET; + + /* fixed piers */ + pcal[0].freq = 2312; + pcal[1].freq = 2412; + pcal[2].freq = 2484; + ee->ee_n_piers[mode] = 3; + break; + default: + return -EINVAL; + } + + for (i = 0; i < ee->ee_n_piers[mode]; i++) { + struct ath5k_chan_pcal_info_rf5111 *cdata = + &pcal[i].rf5111_info; + + AR5K_EEPROM_READ(offset++, val); + cdata->pcdac_max = ((val >> 10) & AR5K_EEPROM_PCDAC_M); + cdata->pcdac_min = ((val >> 4) & AR5K_EEPROM_PCDAC_M); + cdata->pwr[0] = ((val << 2) & AR5K_EEPROM_POWER_M); + + AR5K_EEPROM_READ(offset++, val); + cdata->pwr[0] |= ((val >> 14) & 0x3); + cdata->pwr[1] = ((val >> 8) & AR5K_EEPROM_POWER_M); + cdata->pwr[2] = ((val >> 2) & AR5K_EEPROM_POWER_M); + cdata->pwr[3] = ((val << 4) & AR5K_EEPROM_POWER_M); + + AR5K_EEPROM_READ(offset++, val); + cdata->pwr[3] |= ((val >> 12) & 0xf); + cdata->pwr[4] = ((val >> 6) & AR5K_EEPROM_POWER_M); + cdata->pwr[5] = (val & AR5K_EEPROM_POWER_M); + + AR5K_EEPROM_READ(offset++, val); + cdata->pwr[6] = ((val >> 10) & AR5K_EEPROM_POWER_M); + cdata->pwr[7] = ((val >> 4) & AR5K_EEPROM_POWER_M); + cdata->pwr[8] = ((val << 2) & AR5K_EEPROM_POWER_M); + + AR5K_EEPROM_READ(offset++, val); + cdata->pwr[8] |= ((val >> 14) & 0x3); + cdata->pwr[9] = ((val >> 8) & AR5K_EEPROM_POWER_M); + cdata->pwr[10] = ((val >> 2) & AR5K_EEPROM_POWER_M); + + ath5k_get_pcdac_intercepts(ah, cdata->pcdac_min, + cdata->pcdac_max, cdata->pcdac); + } + + return ath5k_eeprom_convert_pcal_info_5111(ah, mode, pcal); +} + + +/* + * Read power calibration for RF5112 chips + * + * For RF5112 we have 4 XPD -eXternal Power Detector- curves + * for each calibrated channel on 0, -6, -12 and -18dbm but we only + * use the higher (3) and the lower (0) curves. Each curve has 0.5dB + * power steps on x axis and PCDAC steps on y axis and looks like a + * linear function. To recreate the curve and pass the power values + * on hw, we read 4 points for xpd 0 (lower gain -> max power) + * and 3 points for xpd 3 (higher gain -> lower power) here and + * interpolate later. + * + * Note: Many vendors just use xpd 0 so xpd 3 is zeroed. + */ + +/* Convert RF5112 specific data to generic raw data + * used by interpolation code */ +static int +ath5k_eeprom_convert_pcal_info_5112(struct ath5k_hw *ah, int mode, + struct ath5k_chan_pcal_info *chinfo) +{ + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + struct ath5k_chan_pcal_info_rf5112 *pcinfo; + u8 *pdgain_idx = ee->ee_pdc_to_idx[mode]; + unsigned int pier, pdg, point; + + /* Fill raw data for each calibration pier */ + for (pier = 0; pier < ee->ee_n_piers[mode]; pier++) { + + pcinfo = &chinfo[pier].rf5112_info; + + /* Allocate pd_curves for this cal pier */ + chinfo[pier].pd_curves = + calloc(AR5K_EEPROM_N_PD_CURVES, + sizeof(struct ath5k_pdgain_info)); + + if (!chinfo[pier].pd_curves) + return -ENOMEM; + + /* Fill pd_curves */ + for (pdg = 0; pdg < ee->ee_pd_gains[mode]; pdg++) { + + u8 idx = pdgain_idx[pdg]; + struct ath5k_pdgain_info *pd = + &chinfo[pier].pd_curves[idx]; + + /* Lowest gain curve (max power) */ + if (pdg == 0) { + /* One more point for better accuracy */ + pd->pd_points = AR5K_EEPROM_N_XPD0_POINTS; + + /* Allocate pd points for this curve */ + pd->pd_step = calloc(pd->pd_points, sizeof(u8)); + + if (!pd->pd_step) + return -ENOMEM; + + pd->pd_pwr = calloc(pd->pd_points, sizeof(s16)); + + if (!pd->pd_pwr) + return -ENOMEM; + + + /* Fill raw dataset + * (all power levels are in 0.25dB units) */ + pd->pd_step[0] = pcinfo->pcdac_x0[0]; + pd->pd_pwr[0] = pcinfo->pwr_x0[0]; + + for (point = 1; point < pd->pd_points; + point++) { + /* Absolute values */ + pd->pd_pwr[point] = + pcinfo->pwr_x0[point]; + + /* Deltas */ + pd->pd_step[point] = + pd->pd_step[point - 1] + + pcinfo->pcdac_x0[point]; + } + + /* Set min power for this frequency */ + chinfo[pier].min_pwr = pd->pd_pwr[0]; + + /* Highest gain curve (min power) */ + } else if (pdg == 1) { + + pd->pd_points = AR5K_EEPROM_N_XPD3_POINTS; + + /* Allocate pd points for this curve */ + pd->pd_step = calloc(pd->pd_points, sizeof(u8)); + + if (!pd->pd_step) + return -ENOMEM; + + pd->pd_pwr = calloc(pd->pd_points, sizeof(s16)); + + if (!pd->pd_pwr) + return -ENOMEM; + + /* Fill raw dataset + * (all power levels are in 0.25dB units) */ + for (point = 0; point < pd->pd_points; + point++) { + /* Absolute values */ + pd->pd_pwr[point] = + pcinfo->pwr_x3[point]; + + /* Fixed points */ + pd->pd_step[point] = + pcinfo->pcdac_x3[point]; + } + + /* Since we have a higher gain curve + * override min power */ + chinfo[pier].min_pwr = pd->pd_pwr[0]; + } + } + } + + return 0; +} + +/* Parse EEPROM data */ +static int +ath5k_eeprom_read_pcal_info_5112(struct ath5k_hw *ah, int mode) +{ + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + struct ath5k_chan_pcal_info_rf5112 *chan_pcal_info; + struct ath5k_chan_pcal_info *gen_chan_info; + u8 *pdgain_idx = ee->ee_pdc_to_idx[mode]; + u32 offset; + u8 i, c; + u16 val; + int ret; + u8 pd_gains = 0; + + /* Count how many curves we have and + * identify them (which one of the 4 + * available curves we have on each count). + * Curves are stored from lower (x0) to + * higher (x3) gain */ + for (i = 0; i < AR5K_EEPROM_N_PD_CURVES; i++) { + /* ee_x_gain[mode] is x gain mask */ + if ((ee->ee_x_gain[mode] >> i) & 0x1) + pdgain_idx[pd_gains++] = i; + } + ee->ee_pd_gains[mode] = pd_gains; + + if (pd_gains == 0 || pd_gains > 2) + return -EINVAL; + + switch (mode) { + case AR5K_EEPROM_MODE_11A: + /* + * Read 5GHz EEPROM channels + */ + offset = AR5K_EEPROM_GROUPS_START(ee->ee_version); + ath5k_eeprom_init_11a_pcal_freq(ah, offset); + + offset += AR5K_EEPROM_GROUP2_OFFSET; + gen_chan_info = ee->ee_pwr_cal_a; + break; + case AR5K_EEPROM_MODE_11B: + offset = AR5K_EEPROM_GROUPS_START(ee->ee_version); + if (AR5K_EEPROM_HDR_11A(ee->ee_header)) + offset += AR5K_EEPROM_GROUP3_OFFSET; + + /* NB: frequency piers parsed during mode init */ + gen_chan_info = ee->ee_pwr_cal_b; + break; + case AR5K_EEPROM_MODE_11G: + offset = AR5K_EEPROM_GROUPS_START(ee->ee_version); + if (AR5K_EEPROM_HDR_11A(ee->ee_header)) + offset += AR5K_EEPROM_GROUP4_OFFSET; + else if (AR5K_EEPROM_HDR_11B(ee->ee_header)) + offset += AR5K_EEPROM_GROUP2_OFFSET; + + /* NB: frequency piers parsed during mode init */ + gen_chan_info = ee->ee_pwr_cal_g; + break; + default: + return -EINVAL; + } + + for (i = 0; i < ee->ee_n_piers[mode]; i++) { + chan_pcal_info = &gen_chan_info[i].rf5112_info; + + /* Power values in quarter dB + * for the lower xpd gain curve + * (0 dBm -> higher output power) */ + for (c = 0; c < AR5K_EEPROM_N_XPD0_POINTS; c++) { + AR5K_EEPROM_READ(offset++, val); + chan_pcal_info->pwr_x0[c] = (s8) (val & 0xff); + chan_pcal_info->pwr_x0[++c] = (s8) ((val >> 8) & 0xff); + } + + /* PCDAC steps + * corresponding to the above power + * measurements */ + AR5K_EEPROM_READ(offset++, val); + chan_pcal_info->pcdac_x0[1] = (val & 0x1f); + chan_pcal_info->pcdac_x0[2] = ((val >> 5) & 0x1f); + chan_pcal_info->pcdac_x0[3] = ((val >> 10) & 0x1f); + + /* Power values in quarter dB + * for the higher xpd gain curve + * (18 dBm -> lower output power) */ + AR5K_EEPROM_READ(offset++, val); + chan_pcal_info->pwr_x3[0] = (s8) (val & 0xff); + chan_pcal_info->pwr_x3[1] = (s8) ((val >> 8) & 0xff); + + AR5K_EEPROM_READ(offset++, val); + chan_pcal_info->pwr_x3[2] = (val & 0xff); + + /* PCDAC steps + * corresponding to the above power + * measurements (fixed) */ + chan_pcal_info->pcdac_x3[0] = 20; + chan_pcal_info->pcdac_x3[1] = 35; + chan_pcal_info->pcdac_x3[2] = 63; + + if (ee->ee_version >= AR5K_EEPROM_VERSION_4_3) { + chan_pcal_info->pcdac_x0[0] = ((val >> 8) & 0x3f); + + /* Last xpd0 power level is also channel maximum */ + gen_chan_info[i].max_pwr = chan_pcal_info->pwr_x0[3]; + } else { + chan_pcal_info->pcdac_x0[0] = 1; + gen_chan_info[i].max_pwr = (s8) ((val >> 8) & 0xff); + } + + } + + return ath5k_eeprom_convert_pcal_info_5112(ah, mode, gen_chan_info); +} + + +/* + * Read power calibration for RF2413 chips + * + * For RF2413 we have a Power to PDDAC table (Power Detector) + * instead of a PCDAC and 4 pd gain curves for each calibrated channel. + * Each curve has power on x axis in 0.5 db steps and PDDADC steps on y + * axis and looks like an exponential function like the RF5111 curve. + * + * To recreate the curves we read here the points and interpolate + * later. Note that in most cases only 2 (higher and lower) curves are + * used (like RF5112) but vendors have the oportunity to include all + * 4 curves on eeprom. The final curve (higher power) has an extra + * point for better accuracy like RF5112. + */ + +/* For RF2413 power calibration data doesn't start on a fixed location and + * if a mode is not supported, it's section is missing -not zeroed-. + * So we need to calculate the starting offset for each section by using + * these two functions */ + +/* Return the size of each section based on the mode and the number of pd + * gains available (maximum 4). */ +static inline unsigned int +ath5k_pdgains_size_2413(struct ath5k_eeprom_info *ee, unsigned int mode) +{ + static const unsigned int pdgains_size[] = { 4, 6, 9, 12 }; + unsigned int sz; + + sz = pdgains_size[ee->ee_pd_gains[mode] - 1]; + sz *= ee->ee_n_piers[mode]; + + return sz; +} + +/* Return the starting offset for a section based on the modes supported + * and each section's size. */ +static unsigned int +ath5k_cal_data_offset_2413(struct ath5k_eeprom_info *ee, int mode) +{ + u32 offset = AR5K_EEPROM_CAL_DATA_START(ee->ee_misc4); + + switch(mode) { + case AR5K_EEPROM_MODE_11G: + if (AR5K_EEPROM_HDR_11B(ee->ee_header)) + offset += ath5k_pdgains_size_2413(ee, + AR5K_EEPROM_MODE_11B) + + AR5K_EEPROM_N_2GHZ_CHAN_2413 / 2; + /* fall through */ + case AR5K_EEPROM_MODE_11B: + if (AR5K_EEPROM_HDR_11A(ee->ee_header)) + offset += ath5k_pdgains_size_2413(ee, + AR5K_EEPROM_MODE_11A) + + AR5K_EEPROM_N_5GHZ_CHAN / 2; + /* fall through */ + case AR5K_EEPROM_MODE_11A: + break; + default: + break; + } + + return offset; +} + +/* Convert RF2413 specific data to generic raw data + * used by interpolation code */ +static int +ath5k_eeprom_convert_pcal_info_2413(struct ath5k_hw *ah, int mode, + struct ath5k_chan_pcal_info *chinfo) +{ + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + struct ath5k_chan_pcal_info_rf2413 *pcinfo; + u8 *pdgain_idx = ee->ee_pdc_to_idx[mode]; + unsigned int pier, point; + int pdg; + + /* Fill raw data for each calibration pier */ + for (pier = 0; pier < ee->ee_n_piers[mode]; pier++) { + + pcinfo = &chinfo[pier].rf2413_info; + + /* Allocate pd_curves for this cal pier */ + chinfo[pier].pd_curves = + calloc(AR5K_EEPROM_N_PD_CURVES, + sizeof(struct ath5k_pdgain_info)); + + if (!chinfo[pier].pd_curves) + return -ENOMEM; + + /* Fill pd_curves */ + for (pdg = 0; pdg < ee->ee_pd_gains[mode]; pdg++) { + + u8 idx = pdgain_idx[pdg]; + struct ath5k_pdgain_info *pd = + &chinfo[pier].pd_curves[idx]; + + /* One more point for the highest power + * curve (lowest gain) */ + if (pdg == ee->ee_pd_gains[mode] - 1) + pd->pd_points = AR5K_EEPROM_N_PD_POINTS; + else + pd->pd_points = AR5K_EEPROM_N_PD_POINTS - 1; + + /* Allocate pd points for this curve */ + pd->pd_step = calloc(pd->pd_points, sizeof(u8)); + + if (!pd->pd_step) + return -ENOMEM; + + pd->pd_pwr = calloc(pd->pd_points, sizeof(s16)); + + if (!pd->pd_pwr) + return -ENOMEM; + + /* Fill raw dataset + * convert all pwr levels to + * quarter dB for RF5112 combatibility */ + pd->pd_step[0] = pcinfo->pddac_i[pdg]; + pd->pd_pwr[0] = 4 * pcinfo->pwr_i[pdg]; + + for (point = 1; point < pd->pd_points; point++) { + + pd->pd_pwr[point] = pd->pd_pwr[point - 1] + + 2 * pcinfo->pwr[pdg][point - 1]; + + pd->pd_step[point] = pd->pd_step[point - 1] + + pcinfo->pddac[pdg][point - 1]; + + } + + /* Highest gain curve -> min power */ + if (pdg == 0) + chinfo[pier].min_pwr = pd->pd_pwr[0]; + + /* Lowest gain curve -> max power */ + if (pdg == ee->ee_pd_gains[mode] - 1) + chinfo[pier].max_pwr = + pd->pd_pwr[pd->pd_points - 1]; + } + } + + return 0; +} + +/* Parse EEPROM data */ +static int +ath5k_eeprom_read_pcal_info_2413(struct ath5k_hw *ah, int mode) +{ + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + struct ath5k_chan_pcal_info_rf2413 *pcinfo; + struct ath5k_chan_pcal_info *chinfo; + u8 *pdgain_idx = ee->ee_pdc_to_idx[mode]; + u32 offset; + int idx, i, ret; + u16 val; + u8 pd_gains = 0; + + /* Count how many curves we have and + * identify them (which one of the 4 + * available curves we have on each count). + * Curves are stored from higher to + * lower gain so we go backwards */ + for (idx = AR5K_EEPROM_N_PD_CURVES - 1; idx >= 0; idx--) { + /* ee_x_gain[mode] is x gain mask */ + if ((ee->ee_x_gain[mode] >> idx) & 0x1) + pdgain_idx[pd_gains++] = idx; + + } + ee->ee_pd_gains[mode] = pd_gains; + + if (pd_gains == 0) + return -EINVAL; + + offset = ath5k_cal_data_offset_2413(ee, mode); + switch (mode) { + case AR5K_EEPROM_MODE_11A: + if (!AR5K_EEPROM_HDR_11A(ee->ee_header)) + return 0; + + ath5k_eeprom_init_11a_pcal_freq(ah, offset); + offset += AR5K_EEPROM_N_5GHZ_CHAN / 2; + chinfo = ee->ee_pwr_cal_a; + break; + case AR5K_EEPROM_MODE_11B: + if (!AR5K_EEPROM_HDR_11B(ee->ee_header)) + return 0; + + ath5k_eeprom_init_11bg_2413(ah, mode, offset); + offset += AR5K_EEPROM_N_2GHZ_CHAN_2413 / 2; + chinfo = ee->ee_pwr_cal_b; + break; + case AR5K_EEPROM_MODE_11G: + if (!AR5K_EEPROM_HDR_11G(ee->ee_header)) + return 0; + + ath5k_eeprom_init_11bg_2413(ah, mode, offset); + offset += AR5K_EEPROM_N_2GHZ_CHAN_2413 / 2; + chinfo = ee->ee_pwr_cal_g; + break; + default: + return -EINVAL; + } + + for (i = 0; i < ee->ee_n_piers[mode]; i++) { + pcinfo = &chinfo[i].rf2413_info; + + /* + * Read pwr_i, pddac_i and the first + * 2 pd points (pwr, pddac) + */ + AR5K_EEPROM_READ(offset++, val); + pcinfo->pwr_i[0] = val & 0x1f; + pcinfo->pddac_i[0] = (val >> 5) & 0x7f; + pcinfo->pwr[0][0] = (val >> 12) & 0xf; + + AR5K_EEPROM_READ(offset++, val); + pcinfo->pddac[0][0] = val & 0x3f; + pcinfo->pwr[0][1] = (val >> 6) & 0xf; + pcinfo->pddac[0][1] = (val >> 10) & 0x3f; + + AR5K_EEPROM_READ(offset++, val); + pcinfo->pwr[0][2] = val & 0xf; + pcinfo->pddac[0][2] = (val >> 4) & 0x3f; + + pcinfo->pwr[0][3] = 0; + pcinfo->pddac[0][3] = 0; + + if (pd_gains > 1) { + /* + * Pd gain 0 is not the last pd gain + * so it only has 2 pd points. + * Continue wih pd gain 1. + */ + pcinfo->pwr_i[1] = (val >> 10) & 0x1f; + + pcinfo->pddac_i[1] = (val >> 15) & 0x1; + AR5K_EEPROM_READ(offset++, val); + pcinfo->pddac_i[1] |= (val & 0x3F) << 1; + + pcinfo->pwr[1][0] = (val >> 6) & 0xf; + pcinfo->pddac[1][0] = (val >> 10) & 0x3f; + + AR5K_EEPROM_READ(offset++, val); + pcinfo->pwr[1][1] = val & 0xf; + pcinfo->pddac[1][1] = (val >> 4) & 0x3f; + pcinfo->pwr[1][2] = (val >> 10) & 0xf; + + pcinfo->pddac[1][2] = (val >> 14) & 0x3; + AR5K_EEPROM_READ(offset++, val); + pcinfo->pddac[1][2] |= (val & 0xF) << 2; + + pcinfo->pwr[1][3] = 0; + pcinfo->pddac[1][3] = 0; + } else if (pd_gains == 1) { + /* + * Pd gain 0 is the last one so + * read the extra point. + */ + pcinfo->pwr[0][3] = (val >> 10) & 0xf; + + pcinfo->pddac[0][3] = (val >> 14) & 0x3; + AR5K_EEPROM_READ(offset++, val); + pcinfo->pddac[0][3] |= (val & 0xF) << 2; + } + + /* + * Proceed with the other pd_gains + * as above. + */ + if (pd_gains > 2) { + pcinfo->pwr_i[2] = (val >> 4) & 0x1f; + pcinfo->pddac_i[2] = (val >> 9) & 0x7f; + + AR5K_EEPROM_READ(offset++, val); + pcinfo->pwr[2][0] = (val >> 0) & 0xf; + pcinfo->pddac[2][0] = (val >> 4) & 0x3f; + pcinfo->pwr[2][1] = (val >> 10) & 0xf; + + pcinfo->pddac[2][1] = (val >> 14) & 0x3; + AR5K_EEPROM_READ(offset++, val); + pcinfo->pddac[2][1] |= (val & 0xF) << 2; + + pcinfo->pwr[2][2] = (val >> 4) & 0xf; + pcinfo->pddac[2][2] = (val >> 8) & 0x3f; + + pcinfo->pwr[2][3] = 0; + pcinfo->pddac[2][3] = 0; + } else if (pd_gains == 2) { + pcinfo->pwr[1][3] = (val >> 4) & 0xf; + pcinfo->pddac[1][3] = (val >> 8) & 0x3f; + } + + if (pd_gains > 3) { + pcinfo->pwr_i[3] = (val >> 14) & 0x3; + AR5K_EEPROM_READ(offset++, val); + pcinfo->pwr_i[3] |= ((val >> 0) & 0x7) << 2; + + pcinfo->pddac_i[3] = (val >> 3) & 0x7f; + pcinfo->pwr[3][0] = (val >> 10) & 0xf; + pcinfo->pddac[3][0] = (val >> 14) & 0x3; + + AR5K_EEPROM_READ(offset++, val); + pcinfo->pddac[3][0] |= (val & 0xF) << 2; + pcinfo->pwr[3][1] = (val >> 4) & 0xf; + pcinfo->pddac[3][1] = (val >> 8) & 0x3f; + + pcinfo->pwr[3][2] = (val >> 14) & 0x3; + AR5K_EEPROM_READ(offset++, val); + pcinfo->pwr[3][2] |= ((val >> 0) & 0x3) << 2; + + pcinfo->pddac[3][2] = (val >> 2) & 0x3f; + pcinfo->pwr[3][3] = (val >> 8) & 0xf; + + pcinfo->pddac[3][3] = (val >> 12) & 0xF; + AR5K_EEPROM_READ(offset++, val); + pcinfo->pddac[3][3] |= ((val >> 0) & 0x3) << 4; + } else if (pd_gains == 3) { + pcinfo->pwr[2][3] = (val >> 14) & 0x3; + AR5K_EEPROM_READ(offset++, val); + pcinfo->pwr[2][3] |= ((val >> 0) & 0x3) << 2; + + pcinfo->pddac[2][3] = (val >> 2) & 0x3f; + } + } + + return ath5k_eeprom_convert_pcal_info_2413(ah, mode, chinfo); +} + + +/* + * Read per rate target power (this is the maximum tx power + * supported by the card). This info is used when setting + * tx power, no matter the channel. + * + * This also works for v5 EEPROMs. + */ +static int +ath5k_eeprom_read_target_rate_pwr_info(struct ath5k_hw *ah, unsigned int mode) +{ + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + struct ath5k_rate_pcal_info *rate_pcal_info; + u8 *rate_target_pwr_num; + u32 offset; + u16 val; + int ret, i; + + offset = AR5K_EEPROM_TARGET_PWRSTART(ee->ee_misc1); + rate_target_pwr_num = &ee->ee_rate_target_pwr_num[mode]; + switch (mode) { + case AR5K_EEPROM_MODE_11A: + offset += AR5K_EEPROM_TARGET_PWR_OFF_11A(ee->ee_version); + rate_pcal_info = ee->ee_rate_tpwr_a; + ee->ee_rate_target_pwr_num[mode] = AR5K_EEPROM_N_5GHZ_CHAN; + break; + case AR5K_EEPROM_MODE_11B: + offset += AR5K_EEPROM_TARGET_PWR_OFF_11B(ee->ee_version); + rate_pcal_info = ee->ee_rate_tpwr_b; + ee->ee_rate_target_pwr_num[mode] = 2; /* 3rd is g mode's 1st */ + break; + case AR5K_EEPROM_MODE_11G: + offset += AR5K_EEPROM_TARGET_PWR_OFF_11G(ee->ee_version); + rate_pcal_info = ee->ee_rate_tpwr_g; + ee->ee_rate_target_pwr_num[mode] = AR5K_EEPROM_N_2GHZ_CHAN; + break; + default: + return -EINVAL; + } + + /* Different freq mask for older eeproms (<= v3.2) */ + if (ee->ee_version <= AR5K_EEPROM_VERSION_3_2) { + for (i = 0; i < (*rate_target_pwr_num); i++) { + AR5K_EEPROM_READ(offset++, val); + rate_pcal_info[i].freq = + ath5k_eeprom_bin2freq(ee, (val >> 9) & 0x7f, mode); + + rate_pcal_info[i].target_power_6to24 = ((val >> 3) & 0x3f); + rate_pcal_info[i].target_power_36 = (val << 3) & 0x3f; + + AR5K_EEPROM_READ(offset++, val); + + if (rate_pcal_info[i].freq == AR5K_EEPROM_CHANNEL_DIS || + val == 0) { + (*rate_target_pwr_num) = i; + break; + } + + rate_pcal_info[i].target_power_36 |= ((val >> 13) & 0x7); + rate_pcal_info[i].target_power_48 = ((val >> 7) & 0x3f); + rate_pcal_info[i].target_power_54 = ((val >> 1) & 0x3f); + } + } else { + for (i = 0; i < (*rate_target_pwr_num); i++) { + AR5K_EEPROM_READ(offset++, val); + rate_pcal_info[i].freq = + ath5k_eeprom_bin2freq(ee, (val >> 8) & 0xff, mode); + + rate_pcal_info[i].target_power_6to24 = ((val >> 2) & 0x3f); + rate_pcal_info[i].target_power_36 = (val << 4) & 0x3f; + + AR5K_EEPROM_READ(offset++, val); + + if (rate_pcal_info[i].freq == AR5K_EEPROM_CHANNEL_DIS || + val == 0) { + (*rate_target_pwr_num) = i; + break; + } + + rate_pcal_info[i].target_power_36 |= (val >> 12) & 0xf; + rate_pcal_info[i].target_power_48 = ((val >> 6) & 0x3f); + rate_pcal_info[i].target_power_54 = (val & 0x3f); + } + } + + return 0; +} + +/* + * Read per channel calibration info from EEPROM + * + * This info is used to calibrate the baseband power table. Imagine + * that for each channel there is a power curve that's hw specific + * (depends on amplifier etc) and we try to "correct" this curve using + * offests we pass on to phy chip (baseband -> before amplifier) so that + * it can use accurate power values when setting tx power (takes amplifier's + * performance on each channel into account). + * + * EEPROM provides us with the offsets for some pre-calibrated channels + * and we have to interpolate to create the full table for these channels and + * also the table for any channel. + */ +static int +ath5k_eeprom_read_pcal_info(struct ath5k_hw *ah) +{ + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + int (*read_pcal)(struct ath5k_hw *hw, int mode); + int mode; + int err; + + if ((ah->ah_ee_version >= AR5K_EEPROM_VERSION_4_0) && + (AR5K_EEPROM_EEMAP(ee->ee_misc0) == 1)) + read_pcal = ath5k_eeprom_read_pcal_info_5112; + else if ((ah->ah_ee_version >= AR5K_EEPROM_VERSION_5_0) && + (AR5K_EEPROM_EEMAP(ee->ee_misc0) == 2)) + read_pcal = ath5k_eeprom_read_pcal_info_2413; + else + read_pcal = ath5k_eeprom_read_pcal_info_5111; + + + for (mode = AR5K_EEPROM_MODE_11A; mode <= AR5K_EEPROM_MODE_11G; + mode++) { + err = read_pcal(ah, mode); + if (err) + return err; + + err = ath5k_eeprom_read_target_rate_pwr_info(ah, mode); + if (err < 0) + return err; + } + + return 0; +} + +static int +ath5k_eeprom_free_pcal_info(struct ath5k_hw *ah, int mode) +{ + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + struct ath5k_chan_pcal_info *chinfo; + u8 pier, pdg; + + switch (mode) { + case AR5K_EEPROM_MODE_11A: + if (!AR5K_EEPROM_HDR_11A(ee->ee_header)) + return 0; + chinfo = ee->ee_pwr_cal_a; + break; + case AR5K_EEPROM_MODE_11B: + if (!AR5K_EEPROM_HDR_11B(ee->ee_header)) + return 0; + chinfo = ee->ee_pwr_cal_b; + break; + case AR5K_EEPROM_MODE_11G: + if (!AR5K_EEPROM_HDR_11G(ee->ee_header)) + return 0; + chinfo = ee->ee_pwr_cal_g; + break; + default: + return -EINVAL; + } + + for (pier = 0; pier < ee->ee_n_piers[mode]; pier++) { + if (!chinfo[pier].pd_curves) + continue; + + for (pdg = 0; pdg < ee->ee_pd_gains[mode]; pdg++) { + struct ath5k_pdgain_info *pd = + &chinfo[pier].pd_curves[pdg]; + + if (pd != NULL) { + free(pd->pd_step); + free(pd->pd_pwr); + } + } + + free(chinfo[pier].pd_curves); + } + + return 0; +} + +void +ath5k_eeprom_detach(struct ath5k_hw *ah) +{ + u8 mode; + + for (mode = AR5K_EEPROM_MODE_11A; mode <= AR5K_EEPROM_MODE_11G; mode++) + ath5k_eeprom_free_pcal_info(ah, mode); +} + +/* Read conformance test limits used for regulatory control */ +static int +ath5k_eeprom_read_ctl_info(struct ath5k_hw *ah) +{ + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + struct ath5k_edge_power *rep; + unsigned int fmask, pmask; + unsigned int ctl_mode; + int ret, i, j; + u32 offset; + u16 val; + + pmask = AR5K_EEPROM_POWER_M; + fmask = AR5K_EEPROM_FREQ_M(ee->ee_version); + offset = AR5K_EEPROM_CTL(ee->ee_version); + ee->ee_ctls = AR5K_EEPROM_N_CTLS(ee->ee_version); + for (i = 0; i < ee->ee_ctls; i += 2) { + AR5K_EEPROM_READ(offset++, val); + ee->ee_ctl[i] = (val >> 8) & 0xff; + ee->ee_ctl[i + 1] = val & 0xff; + } + + offset = AR5K_EEPROM_GROUP8_OFFSET; + if (ee->ee_version >= AR5K_EEPROM_VERSION_4_0) + offset += AR5K_EEPROM_TARGET_PWRSTART(ee->ee_misc1) - + AR5K_EEPROM_GROUP5_OFFSET; + else + offset += AR5K_EEPROM_GROUPS_START(ee->ee_version); + + rep = ee->ee_ctl_pwr; + for(i = 0; i < ee->ee_ctls; i++) { + switch(ee->ee_ctl[i] & AR5K_CTL_MODE_M) { + case AR5K_CTL_11A: + case AR5K_CTL_TURBO: + ctl_mode = AR5K_EEPROM_MODE_11A; + break; + default: + ctl_mode = AR5K_EEPROM_MODE_11G; + break; + } + if (ee->ee_ctl[i] == 0) { + if (ee->ee_version >= AR5K_EEPROM_VERSION_3_3) + offset += 8; + else + offset += 7; + rep += AR5K_EEPROM_N_EDGES; + continue; + } + if (ee->ee_version >= AR5K_EEPROM_VERSION_3_3) { + for (j = 0; j < AR5K_EEPROM_N_EDGES; j += 2) { + AR5K_EEPROM_READ(offset++, val); + rep[j].freq = (val >> 8) & fmask; + rep[j + 1].freq = val & fmask; + } + for (j = 0; j < AR5K_EEPROM_N_EDGES; j += 2) { + AR5K_EEPROM_READ(offset++, val); + rep[j].edge = (val >> 8) & pmask; + rep[j].flag = (val >> 14) & 1; + rep[j + 1].edge = val & pmask; + rep[j + 1].flag = (val >> 6) & 1; + } + } else { + AR5K_EEPROM_READ(offset++, val); + rep[0].freq = (val >> 9) & fmask; + rep[1].freq = (val >> 2) & fmask; + rep[2].freq = (val << 5) & fmask; + + AR5K_EEPROM_READ(offset++, val); + rep[2].freq |= (val >> 11) & 0x1f; + rep[3].freq = (val >> 4) & fmask; + rep[4].freq = (val << 3) & fmask; + + AR5K_EEPROM_READ(offset++, val); + rep[4].freq |= (val >> 13) & 0x7; + rep[5].freq = (val >> 6) & fmask; + rep[6].freq = (val << 1) & fmask; + + AR5K_EEPROM_READ(offset++, val); + rep[6].freq |= (val >> 15) & 0x1; + rep[7].freq = (val >> 8) & fmask; + + rep[0].edge = (val >> 2) & pmask; + rep[1].edge = (val << 4) & pmask; + + AR5K_EEPROM_READ(offset++, val); + rep[1].edge |= (val >> 12) & 0xf; + rep[2].edge = (val >> 6) & pmask; + rep[3].edge = val & pmask; + + AR5K_EEPROM_READ(offset++, val); + rep[4].edge = (val >> 10) & pmask; + rep[5].edge = (val >> 4) & pmask; + rep[6].edge = (val << 2) & pmask; + + AR5K_EEPROM_READ(offset++, val); + rep[6].edge |= (val >> 14) & 0x3; + rep[7].edge = (val >> 8) & pmask; + } + for (j = 0; j < AR5K_EEPROM_N_EDGES; j++) { + rep[j].freq = ath5k_eeprom_bin2freq(ee, + rep[j].freq, ctl_mode); + } + rep += AR5K_EEPROM_N_EDGES; + } + + return 0; +} + + +/* + * Initialize eeprom power tables + */ +int +ath5k_eeprom_init(struct ath5k_hw *ah) +{ + int err; + + err = ath5k_eeprom_init_header(ah); + if (err < 0) + return err; + + err = ath5k_eeprom_init_modes(ah); + if (err < 0) + return err; + + err = ath5k_eeprom_read_pcal_info(ah); + if (err < 0) + return err; + + err = ath5k_eeprom_read_ctl_info(ah); + if (err < 0) + return err; + + return 0; +} + +/* + * Read the MAC address from eeprom + */ +int ath5k_eeprom_read_mac(struct ath5k_hw *ah, u8 *mac) +{ + u8 mac_d[ETH_ALEN] = {}; + u32 total, offset; + u16 data; + int octet, ret; + + ret = ath5k_hw_eeprom_read(ah, 0x20, &data); + if (ret) + return ret; + + for (offset = 0x1f, octet = 0, total = 0; offset >= 0x1d; offset--) { + ret = ath5k_hw_eeprom_read(ah, offset, &data); + if (ret) + return ret; + + total += data; + mac_d[octet + 1] = data & 0xff; + mac_d[octet] = data >> 8; + octet += 2; + } + + if (!total || total == 3 * 0xffff) + return -EINVAL; + + memcpy(mac, mac_d, ETH_ALEN); + + return 0; +} + +int ath5k_eeprom_is_hb63(struct ath5k_hw *ah) +{ + u16 data; + + ath5k_hw_eeprom_read(ah, AR5K_EEPROM_IS_HB63, &data); + + if ((ah->ah_mac_version == (AR5K_SREV_AR2425 >> 4)) && data) + return 1; + else + return 0; +} + diff --git a/gpxe/src/drivers/net/ath5k/ath5k_gpio.c b/gpxe/src/drivers/net/ath5k/ath5k_gpio.c new file mode 100644 index 00000000..0e8a3e68 --- /dev/null +++ b/gpxe/src/drivers/net/ath5k/ath5k_gpio.c @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2004-2008 Reyk Floeter <reyk@openbsd.org> + * Copyright (c) 2006-2008 Nick Kossifidis <mickflemm@gmail.com> + * + * Lightly modified for gPXE, July 2009, by Joshua Oreman <oremanj@rwcr.net>. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +FILE_LICENCE ( MIT ); + +/****************\ + GPIO Functions +\****************/ + +#include "ath5k.h" +#include "reg.h" +#include "base.h" + +/* + * Set GPIO inputs + */ +int ath5k_hw_set_gpio_input(struct ath5k_hw *ah, u32 gpio) +{ + if (gpio >= AR5K_NUM_GPIO) + return -EINVAL; + + ath5k_hw_reg_write(ah, + (ath5k_hw_reg_read(ah, AR5K_GPIOCR) & ~AR5K_GPIOCR_OUT(gpio)) + | AR5K_GPIOCR_IN(gpio), AR5K_GPIOCR); + + return 0; +} + +/* + * Set GPIO outputs + */ +int ath5k_hw_set_gpio_output(struct ath5k_hw *ah, u32 gpio) +{ + if (gpio >= AR5K_NUM_GPIO) + return -EINVAL; + + ath5k_hw_reg_write(ah, + (ath5k_hw_reg_read(ah, AR5K_GPIOCR) & ~AR5K_GPIOCR_OUT(gpio)) + | AR5K_GPIOCR_OUT(gpio), AR5K_GPIOCR); + + return 0; +} + +/* + * Get GPIO state + */ +u32 ath5k_hw_get_gpio(struct ath5k_hw *ah, u32 gpio) +{ + if (gpio >= AR5K_NUM_GPIO) + return 0xffffffff; + + /* GPIO input magic */ + return ((ath5k_hw_reg_read(ah, AR5K_GPIODI) & AR5K_GPIODI_M) >> gpio) & + 0x1; +} + +/* + * Set GPIO state + */ +int ath5k_hw_set_gpio(struct ath5k_hw *ah, u32 gpio, u32 val) +{ + u32 data; + + if (gpio >= AR5K_NUM_GPIO) + return -EINVAL; + + /* GPIO output magic */ + data = ath5k_hw_reg_read(ah, AR5K_GPIODO); + + data &= ~(1 << gpio); + data |= (val & 1) << gpio; + + ath5k_hw_reg_write(ah, data, AR5K_GPIODO); + + return 0; +} + +/* + * Initialize the GPIO interrupt (RFKill switch) + */ +void ath5k_hw_set_gpio_intr(struct ath5k_hw *ah, unsigned int gpio, + u32 interrupt_level) +{ + u32 data; + + if (gpio >= AR5K_NUM_GPIO) + return; + + /* + * Set the GPIO interrupt + */ + data = (ath5k_hw_reg_read(ah, AR5K_GPIOCR) & + ~(AR5K_GPIOCR_INT_SEL(gpio) | AR5K_GPIOCR_INT_SELH | + AR5K_GPIOCR_INT_ENA | AR5K_GPIOCR_OUT(gpio))) | + (AR5K_GPIOCR_INT_SEL(gpio) | AR5K_GPIOCR_INT_ENA); + + ath5k_hw_reg_write(ah, interrupt_level ? data : + (data | AR5K_GPIOCR_INT_SELH), AR5K_GPIOCR); + + ah->ah_imr |= AR5K_IMR_GPIO; + + /* Enable GPIO interrupts */ + AR5K_REG_ENABLE_BITS(ah, AR5K_PIMR, AR5K_IMR_GPIO); +} + diff --git a/gpxe/src/drivers/net/ath5k/ath5k_initvals.c b/gpxe/src/drivers/net/ath5k/ath5k_initvals.c new file mode 100644 index 00000000..92011c83 --- /dev/null +++ b/gpxe/src/drivers/net/ath5k/ath5k_initvals.c @@ -0,0 +1,1560 @@ +/* + * Initial register settings functions + * + * Copyright (c) 2004-2007 Reyk Floeter <reyk@openbsd.org> + * Copyright (c) 2006-2009 Nick Kossifidis <mickflemm@gmail.com> + * Copyright (c) 2007-2008 Jiri Slaby <jirislaby@gmail.com> + * + * Lightly modified for gPXE, July 2009, by Joshua Oreman <oremanj@rwcr.net>. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +FILE_LICENCE ( MIT ); + +#include <unistd.h> + +#include "ath5k.h" +#include "reg.h" +#include "base.h" + +/* + * Mode-independent initial register writes + */ + +struct ath5k_ini { + u16 ini_register; + u32 ini_value; + + enum { + AR5K_INI_WRITE = 0, /* Default */ + AR5K_INI_READ = 1, /* Cleared on read */ + } ini_mode; +}; + +/* + * Mode specific initial register values + */ + +struct ath5k_ini_mode { + u16 mode_register; + u32 mode_value[5]; +}; + +/* Initial register settings for AR5210 */ +static const struct ath5k_ini ar5210_ini[] = { + /* PCU and MAC registers */ + { AR5K_NOQCU_TXDP0, 0, AR5K_INI_WRITE }, + { AR5K_NOQCU_TXDP1, 0, AR5K_INI_WRITE }, + { AR5K_RXDP, 0, AR5K_INI_WRITE }, + { AR5K_CR, 0, AR5K_INI_WRITE }, + { AR5K_ISR, 0, AR5K_INI_READ }, + { AR5K_IMR, 0, AR5K_INI_WRITE }, + { AR5K_IER, AR5K_IER_DISABLE, AR5K_INI_WRITE }, + { AR5K_BSR, 0, AR5K_INI_READ }, + { AR5K_TXCFG, AR5K_DMASIZE_128B, AR5K_INI_WRITE }, + { AR5K_RXCFG, AR5K_DMASIZE_128B, AR5K_INI_WRITE }, + { AR5K_CFG, AR5K_INIT_CFG, AR5K_INI_WRITE }, + { AR5K_TOPS, 8, AR5K_INI_WRITE }, + { AR5K_RXNOFRM, 8, AR5K_INI_WRITE }, + { AR5K_RPGTO, 0, AR5K_INI_WRITE }, + { AR5K_TXNOFRM, 0, AR5K_INI_WRITE }, + { AR5K_SFR, 0, AR5K_INI_WRITE }, + { AR5K_MIBC, 0, AR5K_INI_WRITE }, + { AR5K_MISC, 0, AR5K_INI_WRITE }, + { AR5K_RX_FILTER_5210, 0, AR5K_INI_WRITE }, + { AR5K_MCAST_FILTER0_5210, 0, AR5K_INI_WRITE }, + { AR5K_MCAST_FILTER1_5210, 0, AR5K_INI_WRITE }, + { AR5K_TX_MASK0, 0, AR5K_INI_WRITE }, + { AR5K_TX_MASK1, 0, AR5K_INI_WRITE }, + { AR5K_CLR_TMASK, 0, AR5K_INI_WRITE }, + { AR5K_TRIG_LVL, AR5K_TUNE_MIN_TX_FIFO_THRES, AR5K_INI_WRITE }, + { AR5K_DIAG_SW_5210, 0, AR5K_INI_WRITE }, + { AR5K_RSSI_THR, AR5K_TUNE_RSSI_THRES, AR5K_INI_WRITE }, + { AR5K_TSF_L32_5210, 0, AR5K_INI_WRITE }, + { AR5K_TIMER0_5210, 0, AR5K_INI_WRITE }, + { AR5K_TIMER1_5210, 0xffffffff, AR5K_INI_WRITE }, + { AR5K_TIMER2_5210, 0xffffffff, AR5K_INI_WRITE }, + { AR5K_TIMER3_5210, 1, AR5K_INI_WRITE }, + { AR5K_CFP_DUR_5210, 0, AR5K_INI_WRITE }, + { AR5K_CFP_PERIOD_5210, 0, AR5K_INI_WRITE }, + /* PHY registers */ + { AR5K_PHY(0), 0x00000047, AR5K_INI_WRITE }, + { AR5K_PHY_AGC, 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHY(3), 0x09848ea6, AR5K_INI_WRITE }, + { AR5K_PHY(4), 0x3d32e000, AR5K_INI_WRITE }, + { AR5K_PHY(5), 0x0000076b, AR5K_INI_WRITE }, + { AR5K_PHY_ACT, AR5K_PHY_ACT_DISABLE, AR5K_INI_WRITE }, + { AR5K_PHY(8), 0x02020200, AR5K_INI_WRITE }, + { AR5K_PHY(9), 0x00000e0e, AR5K_INI_WRITE }, + { AR5K_PHY(10), 0x0a020201, AR5K_INI_WRITE }, + { AR5K_PHY(11), 0x00036ffc, AR5K_INI_WRITE }, + { AR5K_PHY(12), 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHY(13), 0x00000e0e, AR5K_INI_WRITE }, + { AR5K_PHY(14), 0x00000007, AR5K_INI_WRITE }, + { AR5K_PHY(15), 0x00020100, AR5K_INI_WRITE }, + { AR5K_PHY(16), 0x89630000, AR5K_INI_WRITE }, + { AR5K_PHY(17), 0x1372169c, AR5K_INI_WRITE }, + { AR5K_PHY(18), 0x0018b633, AR5K_INI_WRITE }, + { AR5K_PHY(19), 0x1284613c, AR5K_INI_WRITE }, + { AR5K_PHY(20), 0x0de8b8e0, AR5K_INI_WRITE }, + { AR5K_PHY(21), 0x00074859, AR5K_INI_WRITE }, + { AR5K_PHY(22), 0x7e80beba, AR5K_INI_WRITE }, + { AR5K_PHY(23), 0x313a665e, AR5K_INI_WRITE }, + { AR5K_PHY_AGCCTL, 0x00001d08, AR5K_INI_WRITE }, + { AR5K_PHY(25), 0x0001ce00, AR5K_INI_WRITE }, + { AR5K_PHY(26), 0x409a4190, AR5K_INI_WRITE }, + { AR5K_PHY(28), 0x0000000f, AR5K_INI_WRITE }, + { AR5K_PHY(29), 0x00000080, AR5K_INI_WRITE }, + { AR5K_PHY(30), 0x00000004, AR5K_INI_WRITE }, + { AR5K_PHY(31), 0x00000018, AR5K_INI_WRITE }, /* 0x987c */ + { AR5K_PHY(64), 0x00000000, AR5K_INI_WRITE }, /* 0x9900 */ + { AR5K_PHY(65), 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHY(66), 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHY(67), 0x00800000, AR5K_INI_WRITE }, + { AR5K_PHY(68), 0x00000003, AR5K_INI_WRITE }, + /* BB gain table (64bytes) */ + { AR5K_BB_GAIN(0), 0x00000000, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(1), 0x00000020, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(2), 0x00000010, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(3), 0x00000030, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(4), 0x00000008, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(5), 0x00000028, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(6), 0x00000028, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(7), 0x00000004, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(8), 0x00000024, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(9), 0x00000014, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(10), 0x00000034, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(11), 0x0000000c, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(12), 0x0000002c, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(13), 0x00000002, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(14), 0x00000022, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(15), 0x00000012, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(16), 0x00000032, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(17), 0x0000000a, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(18), 0x0000002a, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(19), 0x00000001, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(20), 0x00000021, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(21), 0x00000011, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(22), 0x00000031, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(23), 0x00000009, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(24), 0x00000029, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(25), 0x00000005, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(26), 0x00000025, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(27), 0x00000015, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(28), 0x00000035, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(29), 0x0000000d, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(30), 0x0000002d, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(31), 0x00000003, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(32), 0x00000023, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(33), 0x00000013, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(34), 0x00000033, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(35), 0x0000000b, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(36), 0x0000002b, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(37), 0x00000007, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(38), 0x00000027, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(39), 0x00000017, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(40), 0x00000037, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(41), 0x0000000f, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(42), 0x0000002f, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(43), 0x0000002f, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(44), 0x0000002f, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(45), 0x0000002f, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(46), 0x0000002f, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(47), 0x0000002f, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(48), 0x0000002f, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(49), 0x0000002f, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(50), 0x0000002f, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(51), 0x0000002f, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(52), 0x0000002f, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(53), 0x0000002f, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(54), 0x0000002f, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(55), 0x0000002f, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(56), 0x0000002f, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(57), 0x0000002f, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(58), 0x0000002f, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(59), 0x0000002f, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(60), 0x0000002f, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(61), 0x0000002f, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(62), 0x0000002f, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(63), 0x0000002f, AR5K_INI_WRITE }, + /* 5110 RF gain table (64btes) */ + { AR5K_RF_GAIN(0), 0x0000001d, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(1), 0x0000005d, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(2), 0x0000009d, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(3), 0x000000dd, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(4), 0x0000011d, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(5), 0x00000021, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(6), 0x00000061, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(7), 0x000000a1, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(8), 0x000000e1, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(9), 0x00000031, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(10), 0x00000071, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(11), 0x000000b1, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(12), 0x0000001c, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(13), 0x0000005c, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(14), 0x00000029, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(15), 0x00000069, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(16), 0x000000a9, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(17), 0x00000020, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(18), 0x00000019, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(19), 0x00000059, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(20), 0x00000099, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(21), 0x00000030, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(22), 0x00000005, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(23), 0x00000025, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(24), 0x00000065, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(25), 0x000000a5, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(26), 0x00000028, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(27), 0x00000068, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(28), 0x0000001f, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(29), 0x0000001e, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(30), 0x00000018, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(31), 0x00000058, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(32), 0x00000098, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(33), 0x00000003, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(34), 0x00000004, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(35), 0x00000044, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(36), 0x00000084, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(37), 0x00000013, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(38), 0x00000012, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(39), 0x00000052, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(40), 0x00000092, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(41), 0x000000d2, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(42), 0x0000002b, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(43), 0x0000002a, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(44), 0x0000006a, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(45), 0x000000aa, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(46), 0x0000001b, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(47), 0x0000001a, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(48), 0x0000005a, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(49), 0x0000009a, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(50), 0x000000da, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(51), 0x00000006, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(52), 0x00000006, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(53), 0x00000006, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(54), 0x00000006, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(55), 0x00000006, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(56), 0x00000006, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(57), 0x00000006, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(58), 0x00000006, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(59), 0x00000006, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(60), 0x00000006, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(61), 0x00000006, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(62), 0x00000006, AR5K_INI_WRITE }, + { AR5K_RF_GAIN(63), 0x00000006, AR5K_INI_WRITE }, + /* PHY activation */ + { AR5K_PHY(53), 0x00000020, AR5K_INI_WRITE }, + { AR5K_PHY(51), 0x00000004, AR5K_INI_WRITE }, + { AR5K_PHY(50), 0x00060106, AR5K_INI_WRITE }, + { AR5K_PHY(39), 0x0000006d, AR5K_INI_WRITE }, + { AR5K_PHY(48), 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHY(52), 0x00000014, AR5K_INI_WRITE }, + { AR5K_PHY_ACT, AR5K_PHY_ACT_ENABLE, AR5K_INI_WRITE }, +}; + +/* Initial register settings for AR5211 */ +static const struct ath5k_ini ar5211_ini[] = { + { AR5K_RXDP, 0x00000000, AR5K_INI_WRITE }, + { AR5K_RTSD0, 0x84849c9c, AR5K_INI_WRITE }, + { AR5K_RTSD1, 0x7c7c7c7c, AR5K_INI_WRITE }, + { AR5K_RXCFG, 0x00000005, AR5K_INI_WRITE }, + { AR5K_MIBC, 0x00000000, AR5K_INI_WRITE }, + { AR5K_TOPS, 0x00000008, AR5K_INI_WRITE }, + { AR5K_RXNOFRM, 0x00000008, AR5K_INI_WRITE }, + { AR5K_TXNOFRM, 0x00000010, AR5K_INI_WRITE }, + { AR5K_RPGTO, 0x00000000, AR5K_INI_WRITE }, + { AR5K_RFCNT, 0x0000001f, AR5K_INI_WRITE }, + { AR5K_QUEUE_TXDP(0), 0x00000000, AR5K_INI_WRITE }, + { AR5K_QUEUE_TXDP(1), 0x00000000, AR5K_INI_WRITE }, + { AR5K_QUEUE_TXDP(2), 0x00000000, AR5K_INI_WRITE }, + { AR5K_QUEUE_TXDP(3), 0x00000000, AR5K_INI_WRITE }, + { AR5K_QUEUE_TXDP(4), 0x00000000, AR5K_INI_WRITE }, + { AR5K_QUEUE_TXDP(5), 0x00000000, AR5K_INI_WRITE }, + { AR5K_QUEUE_TXDP(6), 0x00000000, AR5K_INI_WRITE }, + { AR5K_QUEUE_TXDP(7), 0x00000000, AR5K_INI_WRITE }, + { AR5K_QUEUE_TXDP(8), 0x00000000, AR5K_INI_WRITE }, + { AR5K_QUEUE_TXDP(9), 0x00000000, AR5K_INI_WRITE }, + { AR5K_DCU_FP, 0x00000000, AR5K_INI_WRITE }, + { AR5K_STA_ID1, 0x00000000, AR5K_INI_WRITE }, + { AR5K_BSS_ID0, 0x00000000, AR5K_INI_WRITE }, + { AR5K_BSS_ID1, 0x00000000, AR5K_INI_WRITE }, + { AR5K_RSSI_THR, 0x00000000, AR5K_INI_WRITE }, + { AR5K_CFP_PERIOD_5211, 0x00000000, AR5K_INI_WRITE }, + { AR5K_TIMER0_5211, 0x00000030, AR5K_INI_WRITE }, + { AR5K_TIMER1_5211, 0x0007ffff, AR5K_INI_WRITE }, + { AR5K_TIMER2_5211, 0x01ffffff, AR5K_INI_WRITE }, + { AR5K_TIMER3_5211, 0x00000031, AR5K_INI_WRITE }, + { AR5K_CFP_DUR_5211, 0x00000000, AR5K_INI_WRITE }, + { AR5K_RX_FILTER_5211, 0x00000000, AR5K_INI_WRITE }, + { AR5K_MCAST_FILTER0_5211, 0x00000000, AR5K_INI_WRITE }, + { AR5K_MCAST_FILTER1_5211, 0x00000002, AR5K_INI_WRITE }, + { AR5K_DIAG_SW_5211, 0x00000000, AR5K_INI_WRITE }, + { AR5K_ADDAC_TEST, 0x00000000, AR5K_INI_WRITE }, + { AR5K_DEFAULT_ANTENNA, 0x00000000, AR5K_INI_WRITE }, + /* PHY registers */ + { AR5K_PHY_AGC, 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHY(3), 0x2d849093, AR5K_INI_WRITE }, + { AR5K_PHY(4), 0x7d32e000, AR5K_INI_WRITE }, + { AR5K_PHY(5), 0x00000f6b, AR5K_INI_WRITE }, + { AR5K_PHY_ACT, 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHY(11), 0x00026ffe, AR5K_INI_WRITE }, + { AR5K_PHY(12), 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHY(15), 0x00020100, AR5K_INI_WRITE }, + { AR5K_PHY(16), 0x206a017a, AR5K_INI_WRITE }, + { AR5K_PHY(19), 0x1284613c, AR5K_INI_WRITE }, + { AR5K_PHY(21), 0x00000859, AR5K_INI_WRITE }, + { AR5K_PHY(26), 0x409a4190, AR5K_INI_WRITE }, /* 0x9868 */ + { AR5K_PHY(27), 0x050cb081, AR5K_INI_WRITE }, + { AR5K_PHY(28), 0x0000000f, AR5K_INI_WRITE }, + { AR5K_PHY(29), 0x00000080, AR5K_INI_WRITE }, + { AR5K_PHY(30), 0x0000000c, AR5K_INI_WRITE }, + { AR5K_PHY(64), 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHY(65), 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHY(66), 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHY(67), 0x00800000, AR5K_INI_WRITE }, + { AR5K_PHY(68), 0x00000001, AR5K_INI_WRITE }, + { AR5K_PHY(71), 0x0000092a, AR5K_INI_WRITE }, + { AR5K_PHY_IQ, 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHY(73), 0x00058a05, AR5K_INI_WRITE }, + { AR5K_PHY(74), 0x00000001, AR5K_INI_WRITE }, + { AR5K_PHY(75), 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHY_PAPD_PROBE, 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHY(77), 0x00000000, AR5K_INI_WRITE }, /* 0x9934 */ + { AR5K_PHY(78), 0x00000000, AR5K_INI_WRITE }, /* 0x9938 */ + { AR5K_PHY(79), 0x0000003f, AR5K_INI_WRITE }, /* 0x993c */ + { AR5K_PHY(80), 0x00000004, AR5K_INI_WRITE }, + { AR5K_PHY(82), 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHY(83), 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHY(84), 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHY_RADAR, 0x5d50f14c, AR5K_INI_WRITE }, + { AR5K_PHY(86), 0x00000018, AR5K_INI_WRITE }, + { AR5K_PHY(87), 0x004b6a8e, AR5K_INI_WRITE }, + /* Initial Power table (32bytes) + * common on all cards/modes. + * Note: Table is rewritten during + * txpower setup later using calibration + * data etc. so next write is non-common */ + { AR5K_PHY_PCDAC_TXPOWER(1), 0x06ff05ff, AR5K_INI_WRITE }, + { AR5K_PHY_PCDAC_TXPOWER(2), 0x07ff07ff, AR5K_INI_WRITE }, + { AR5K_PHY_PCDAC_TXPOWER(3), 0x08ff08ff, AR5K_INI_WRITE }, + { AR5K_PHY_PCDAC_TXPOWER(4), 0x09ff09ff, AR5K_INI_WRITE }, + { AR5K_PHY_PCDAC_TXPOWER(5), 0x0aff0aff, AR5K_INI_WRITE }, + { AR5K_PHY_PCDAC_TXPOWER(6), 0x0bff0bff, AR5K_INI_WRITE }, + { AR5K_PHY_PCDAC_TXPOWER(7), 0x0cff0cff, AR5K_INI_WRITE }, + { AR5K_PHY_PCDAC_TXPOWER(8), 0x0dff0dff, AR5K_INI_WRITE }, + { AR5K_PHY_PCDAC_TXPOWER(9), 0x0fff0eff, AR5K_INI_WRITE }, + { AR5K_PHY_PCDAC_TXPOWER(10), 0x12ff12ff, AR5K_INI_WRITE }, + { AR5K_PHY_PCDAC_TXPOWER(11), 0x14ff13ff, AR5K_INI_WRITE }, + { AR5K_PHY_PCDAC_TXPOWER(12), 0x16ff15ff, AR5K_INI_WRITE }, + { AR5K_PHY_PCDAC_TXPOWER(13), 0x19ff17ff, AR5K_INI_WRITE }, + { AR5K_PHY_PCDAC_TXPOWER(14), 0x1bff1aff, AR5K_INI_WRITE }, + { AR5K_PHY_PCDAC_TXPOWER(15), 0x1eff1dff, AR5K_INI_WRITE }, + { AR5K_PHY_PCDAC_TXPOWER(16), 0x23ff20ff, AR5K_INI_WRITE }, + { AR5K_PHY_PCDAC_TXPOWER(17), 0x27ff25ff, AR5K_INI_WRITE }, + { AR5K_PHY_PCDAC_TXPOWER(18), 0x2cff29ff, AR5K_INI_WRITE }, + { AR5K_PHY_PCDAC_TXPOWER(19), 0x31ff2fff, AR5K_INI_WRITE }, + { AR5K_PHY_PCDAC_TXPOWER(20), 0x37ff34ff, AR5K_INI_WRITE }, + { AR5K_PHY_PCDAC_TXPOWER(21), 0x3aff3aff, AR5K_INI_WRITE }, + { AR5K_PHY_PCDAC_TXPOWER(22), 0x3aff3aff, AR5K_INI_WRITE }, + { AR5K_PHY_PCDAC_TXPOWER(23), 0x3aff3aff, AR5K_INI_WRITE }, + { AR5K_PHY_PCDAC_TXPOWER(24), 0x3aff3aff, AR5K_INI_WRITE }, + { AR5K_PHY_PCDAC_TXPOWER(25), 0x3aff3aff, AR5K_INI_WRITE }, + { AR5K_PHY_PCDAC_TXPOWER(26), 0x3aff3aff, AR5K_INI_WRITE }, + { AR5K_PHY_PCDAC_TXPOWER(27), 0x3aff3aff, AR5K_INI_WRITE }, + { AR5K_PHY_PCDAC_TXPOWER(28), 0x3aff3aff, AR5K_INI_WRITE }, + { AR5K_PHY_PCDAC_TXPOWER(29), 0x3aff3aff, AR5K_INI_WRITE }, + { AR5K_PHY_PCDAC_TXPOWER(30), 0x3aff3aff, AR5K_INI_WRITE }, + { AR5K_PHY_PCDAC_TXPOWER(31), 0x3aff3aff, AR5K_INI_WRITE }, + { AR5K_PHY_CCKTXCTL, 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHY(642), 0x503e4646, AR5K_INI_WRITE }, + { AR5K_PHY_GAIN_2GHZ, 0x6480416c, AR5K_INI_WRITE }, + { AR5K_PHY(644), 0x0199a003, AR5K_INI_WRITE }, + { AR5K_PHY(645), 0x044cd610, AR5K_INI_WRITE }, + { AR5K_PHY(646), 0x13800040, AR5K_INI_WRITE }, + { AR5K_PHY(647), 0x1be00060, AR5K_INI_WRITE }, + { AR5K_PHY(648), 0x0c53800a, AR5K_INI_WRITE }, + { AR5K_PHY(649), 0x0014df3b, AR5K_INI_WRITE }, + { AR5K_PHY(650), 0x000001b5, AR5K_INI_WRITE }, + { AR5K_PHY(651), 0x00000020, AR5K_INI_WRITE }, +}; + +/* Initial mode-specific settings for AR5211 + * 5211 supports OFDM-only g (draft g) but we + * need to test it ! + */ +static const struct ath5k_ini_mode ar5211_ini_mode[] = { + { AR5K_TXCFG, + /* a aTurbo b g (OFDM) */ + { 0x00000015, 0x00000015, 0x0000001d, 0x00000015 } }, + { AR5K_QUEUE_DFS_LOCAL_IFS(0), + { 0x002ffc0f, 0x002ffc0f, 0x002ffc1f, 0x002ffc0f } }, + { AR5K_QUEUE_DFS_LOCAL_IFS(1), + { 0x002ffc0f, 0x002ffc0f, 0x002ffc1f, 0x002ffc0f } }, + { AR5K_QUEUE_DFS_LOCAL_IFS(2), + { 0x002ffc0f, 0x002ffc0f, 0x002ffc1f, 0x002ffc0f } }, + { AR5K_QUEUE_DFS_LOCAL_IFS(3), + { 0x002ffc0f, 0x002ffc0f, 0x002ffc1f, 0x002ffc0f } }, + { AR5K_QUEUE_DFS_LOCAL_IFS(4), + { 0x002ffc0f, 0x002ffc0f, 0x002ffc1f, 0x002ffc0f } }, + { AR5K_QUEUE_DFS_LOCAL_IFS(5), + { 0x002ffc0f, 0x002ffc0f, 0x002ffc1f, 0x002ffc0f } }, + { AR5K_QUEUE_DFS_LOCAL_IFS(6), + { 0x002ffc0f, 0x002ffc0f, 0x002ffc1f, 0x002ffc0f } }, + { AR5K_QUEUE_DFS_LOCAL_IFS(7), + { 0x002ffc0f, 0x002ffc0f, 0x002ffc1f, 0x002ffc0f } }, + { AR5K_QUEUE_DFS_LOCAL_IFS(8), + { 0x002ffc0f, 0x002ffc0f, 0x002ffc1f, 0x002ffc0f } }, + { AR5K_QUEUE_DFS_LOCAL_IFS(9), + { 0x002ffc0f, 0x002ffc0f, 0x002ffc1f, 0x002ffc0f } }, + { AR5K_DCU_GBL_IFS_SLOT, + { 0x00000168, 0x000001e0, 0x000001b8, 0x00000168 } }, + { AR5K_DCU_GBL_IFS_SIFS, + { 0x00000230, 0x000001e0, 0x000000b0, 0x00000230 } }, + { AR5K_DCU_GBL_IFS_EIFS, + { 0x00000d98, 0x00001180, 0x00001f48, 0x00000d98 } }, + { AR5K_DCU_GBL_IFS_MISC, + { 0x0000a0e0, 0x00014068, 0x00005880, 0x0000a0e0 } }, + { AR5K_TIME_OUT, + { 0x04000400, 0x08000800, 0x20003000, 0x04000400 } }, + { AR5K_USEC_5211, + { 0x0e8d8fa7, 0x0e8d8fcf, 0x01608f95, 0x0e8d8fa7 } }, + { AR5K_PHY_TURBO, + { 0x00000000, 0x00000003, 0x00000000, 0x00000000 } }, + { AR5K_PHY(8), + { 0x02020200, 0x02020200, 0x02010200, 0x02020200 } }, + { AR5K_PHY(9), + { 0x00000e0e, 0x00000e0e, 0x00000707, 0x00000e0e } }, + { AR5K_PHY(10), + { 0x0a020001, 0x0a020001, 0x05010000, 0x0a020001 } }, + { AR5K_PHY(13), + { 0x00000e0e, 0x00000e0e, 0x00000e0e, 0x00000e0e } }, + { AR5K_PHY(14), + { 0x00000007, 0x00000007, 0x0000000b, 0x0000000b } }, + { AR5K_PHY(17), + { 0x1372169c, 0x137216a5, 0x137216a8, 0x1372169c } }, + { AR5K_PHY(18), + { 0x0018ba67, 0x0018ba67, 0x0018ba69, 0x0018ba69 } }, + { AR5K_PHY(20), + { 0x0c28b4e0, 0x0c28b4e0, 0x0c28b4e0, 0x0c28b4e0 } }, + { AR5K_PHY_SIG, + { 0x7e800d2e, 0x7e800d2e, 0x7ec00d2e, 0x7e800d2e } }, + { AR5K_PHY_AGCCOARSE, + { 0x31375d5e, 0x31375d5e, 0x313a5d5e, 0x31375d5e } }, + { AR5K_PHY_AGCCTL, + { 0x0000bd10, 0x0000bd10, 0x0000bd38, 0x0000bd10 } }, + { AR5K_PHY_NF, + { 0x0001ce00, 0x0001ce00, 0x0001ce00, 0x0001ce00 } }, + { AR5K_PHY_RX_DELAY, + { 0x00002710, 0x00002710, 0x0000157c, 0x00002710 } }, + { AR5K_PHY(70), + { 0x00000190, 0x00000190, 0x00000084, 0x00000190 } }, + { AR5K_PHY_FRAME_CTL_5211, + { 0x6fe01020, 0x6fe01020, 0x6fe00920, 0x6fe01020 } }, + { AR5K_PHY_PCDAC_TXPOWER_BASE, + { 0x05ff14ff, 0x05ff14ff, 0x05ff14ff, 0x05ff19ff } }, + { AR5K_RF_BUFFER_CONTROL_4, + { 0x00000010, 0x00000014, 0x00000010, 0x00000010 } }, +}; + +/* Initial register settings for AR5212 */ +static const struct ath5k_ini ar5212_ini_common_start[] = { + { AR5K_RXDP, 0x00000000, AR5K_INI_WRITE }, + { AR5K_RXCFG, 0x00000005, AR5K_INI_WRITE }, + { AR5K_MIBC, 0x00000000, AR5K_INI_WRITE }, + { AR5K_TOPS, 0x00000008, AR5K_INI_WRITE }, + { AR5K_RXNOFRM, 0x00000008, AR5K_INI_WRITE }, + { AR5K_TXNOFRM, 0x00000010, AR5K_INI_WRITE }, + { AR5K_RPGTO, 0x00000000, AR5K_INI_WRITE }, + { AR5K_RFCNT, 0x0000001f, AR5K_INI_WRITE }, + { AR5K_QUEUE_TXDP(0), 0x00000000, AR5K_INI_WRITE }, + { AR5K_QUEUE_TXDP(1), 0x00000000, AR5K_INI_WRITE }, + { AR5K_QUEUE_TXDP(2), 0x00000000, AR5K_INI_WRITE }, + { AR5K_QUEUE_TXDP(3), 0x00000000, AR5K_INI_WRITE }, + { AR5K_QUEUE_TXDP(4), 0x00000000, AR5K_INI_WRITE }, + { AR5K_QUEUE_TXDP(5), 0x00000000, AR5K_INI_WRITE }, + { AR5K_QUEUE_TXDP(6), 0x00000000, AR5K_INI_WRITE }, + { AR5K_QUEUE_TXDP(7), 0x00000000, AR5K_INI_WRITE }, + { AR5K_QUEUE_TXDP(8), 0x00000000, AR5K_INI_WRITE }, + { AR5K_QUEUE_TXDP(9), 0x00000000, AR5K_INI_WRITE }, + { AR5K_DCU_FP, 0x00000000, AR5K_INI_WRITE }, + { AR5K_DCU_TXP, 0x00000000, AR5K_INI_WRITE }, + /* Tx filter table 0 (32 entries) */ + { AR5K_DCU_TX_FILTER_0(0), 0x00000000, AR5K_INI_WRITE }, /* DCU 0 */ + { AR5K_DCU_TX_FILTER_0(1), 0x00000000, AR5K_INI_WRITE }, + { AR5K_DCU_TX_FILTER_0(2), 0x00000000, AR5K_INI_WRITE }, + { AR5K_DCU_TX_FILTER_0(3), 0x00000000, AR5K_INI_WRITE }, + { AR5K_DCU_TX_FILTER_0(4), 0x00000000, AR5K_INI_WRITE }, /* DCU 1 */ + { AR5K_DCU_TX_FILTER_0(5), 0x00000000, AR5K_INI_WRITE }, + { AR5K_DCU_TX_FILTER_0(6), 0x00000000, AR5K_INI_WRITE }, + { AR5K_DCU_TX_FILTER_0(7), 0x00000000, AR5K_INI_WRITE }, + { AR5K_DCU_TX_FILTER_0(8), 0x00000000, AR5K_INI_WRITE }, /* DCU 2 */ + { AR5K_DCU_TX_FILTER_0(9), 0x00000000, AR5K_INI_WRITE }, + { AR5K_DCU_TX_FILTER_0(10), 0x00000000, AR5K_INI_WRITE }, + { AR5K_DCU_TX_FILTER_0(11), 0x00000000, AR5K_INI_WRITE }, + { AR5K_DCU_TX_FILTER_0(12), 0x00000000, AR5K_INI_WRITE }, /* DCU 3 */ + { AR5K_DCU_TX_FILTER_0(13), 0x00000000, AR5K_INI_WRITE }, + { AR5K_DCU_TX_FILTER_0(14), 0x00000000, AR5K_INI_WRITE }, + { AR5K_DCU_TX_FILTER_0(15), 0x00000000, AR5K_INI_WRITE }, + { AR5K_DCU_TX_FILTER_0(16), 0x00000000, AR5K_INI_WRITE }, /* DCU 4 */ + { AR5K_DCU_TX_FILTER_0(17), 0x00000000, AR5K_INI_WRITE }, + { AR5K_DCU_TX_FILTER_0(18), 0x00000000, AR5K_INI_WRITE }, + { AR5K_DCU_TX_FILTER_0(19), 0x00000000, AR5K_INI_WRITE }, + { AR5K_DCU_TX_FILTER_0(20), 0x00000000, AR5K_INI_WRITE }, /* DCU 5 */ + { AR5K_DCU_TX_FILTER_0(21), 0x00000000, AR5K_INI_WRITE }, + { AR5K_DCU_TX_FILTER_0(22), 0x00000000, AR5K_INI_WRITE }, + { AR5K_DCU_TX_FILTER_0(23), 0x00000000, AR5K_INI_WRITE }, + { AR5K_DCU_TX_FILTER_0(24), 0x00000000, AR5K_INI_WRITE }, /* DCU 6 */ + { AR5K_DCU_TX_FILTER_0(25), 0x00000000, AR5K_INI_WRITE }, + { AR5K_DCU_TX_FILTER_0(26), 0x00000000, AR5K_INI_WRITE }, + { AR5K_DCU_TX_FILTER_0(27), 0x00000000, AR5K_INI_WRITE }, + { AR5K_DCU_TX_FILTER_0(28), 0x00000000, AR5K_INI_WRITE }, /* DCU 7 */ + { AR5K_DCU_TX_FILTER_0(29), 0x00000000, AR5K_INI_WRITE }, + { AR5K_DCU_TX_FILTER_0(30), 0x00000000, AR5K_INI_WRITE }, + { AR5K_DCU_TX_FILTER_0(31), 0x00000000, AR5K_INI_WRITE }, + /* Tx filter table 1 (16 entries) */ + { AR5K_DCU_TX_FILTER_1(0), 0x00000000, AR5K_INI_WRITE }, + { AR5K_DCU_TX_FILTER_1(1), 0x00000000, AR5K_INI_WRITE }, + { AR5K_DCU_TX_FILTER_1(2), 0x00000000, AR5K_INI_WRITE }, + { AR5K_DCU_TX_FILTER_1(3), 0x00000000, AR5K_INI_WRITE }, + { AR5K_DCU_TX_FILTER_1(4), 0x00000000, AR5K_INI_WRITE }, + { AR5K_DCU_TX_FILTER_1(5), 0x00000000, AR5K_INI_WRITE }, + { AR5K_DCU_TX_FILTER_1(6), 0x00000000, AR5K_INI_WRITE }, + { AR5K_DCU_TX_FILTER_1(7), 0x00000000, AR5K_INI_WRITE }, + { AR5K_DCU_TX_FILTER_1(8), 0x00000000, AR5K_INI_WRITE }, + { AR5K_DCU_TX_FILTER_1(9), 0x00000000, AR5K_INI_WRITE }, + { AR5K_DCU_TX_FILTER_1(10), 0x00000000, AR5K_INI_WRITE }, + { AR5K_DCU_TX_FILTER_1(11), 0x00000000, AR5K_INI_WRITE }, + { AR5K_DCU_TX_FILTER_1(12), 0x00000000, AR5K_INI_WRITE }, + { AR5K_DCU_TX_FILTER_1(13), 0x00000000, AR5K_INI_WRITE }, + { AR5K_DCU_TX_FILTER_1(14), 0x00000000, AR5K_INI_WRITE }, + { AR5K_DCU_TX_FILTER_1(15), 0x00000000, AR5K_INI_WRITE }, + { AR5K_DCU_TX_FILTER_CLR, 0x00000000, AR5K_INI_WRITE }, + { AR5K_DCU_TX_FILTER_SET, 0x00000000, AR5K_INI_WRITE }, + { AR5K_DCU_TX_FILTER_CLR, 0x00000000, AR5K_INI_WRITE }, + { AR5K_DCU_TX_FILTER_SET, 0x00000000, AR5K_INI_WRITE }, + { AR5K_STA_ID1, 0x00000000, AR5K_INI_WRITE }, + { AR5K_BSS_ID0, 0x00000000, AR5K_INI_WRITE }, + { AR5K_BSS_ID1, 0x00000000, AR5K_INI_WRITE }, + { AR5K_BEACON_5211, 0x00000000, AR5K_INI_WRITE }, + { AR5K_CFP_PERIOD_5211, 0x00000000, AR5K_INI_WRITE }, + { AR5K_TIMER0_5211, 0x00000030, AR5K_INI_WRITE }, + { AR5K_TIMER1_5211, 0x0007ffff, AR5K_INI_WRITE }, + { AR5K_TIMER2_5211, 0x01ffffff, AR5K_INI_WRITE }, + { AR5K_TIMER3_5211, 0x00000031, AR5K_INI_WRITE }, + { AR5K_CFP_DUR_5211, 0x00000000, AR5K_INI_WRITE }, + { AR5K_RX_FILTER_5211, 0x00000000, AR5K_INI_WRITE }, + { AR5K_DIAG_SW_5211, 0x00000000, AR5K_INI_WRITE }, + { AR5K_ADDAC_TEST, 0x00000000, AR5K_INI_WRITE }, + { AR5K_DEFAULT_ANTENNA, 0x00000000, AR5K_INI_WRITE }, + { AR5K_FRAME_CTL_QOSM, 0x000fc78f, AR5K_INI_WRITE }, + { AR5K_XRMODE, 0x2a82301a, AR5K_INI_WRITE }, + { AR5K_XRDELAY, 0x05dc01e0, AR5K_INI_WRITE }, + { AR5K_XRTIMEOUT, 0x1f402710, AR5K_INI_WRITE }, + { AR5K_XRCHIRP, 0x01f40000, AR5K_INI_WRITE }, + { AR5K_XRSTOMP, 0x00001e1c, AR5K_INI_WRITE }, + { AR5K_SLEEP0, 0x0002aaaa, AR5K_INI_WRITE }, + { AR5K_SLEEP1, 0x02005555, AR5K_INI_WRITE }, + { AR5K_SLEEP2, 0x00000000, AR5K_INI_WRITE }, + { AR5K_BSS_IDM0, 0xffffffff, AR5K_INI_WRITE }, + { AR5K_BSS_IDM1, 0x0000ffff, AR5K_INI_WRITE }, + { AR5K_TXPC, 0x00000000, AR5K_INI_WRITE }, + { AR5K_PROFCNT_TX, 0x00000000, AR5K_INI_WRITE }, + { AR5K_PROFCNT_RX, 0x00000000, AR5K_INI_WRITE }, + { AR5K_PROFCNT_RXCLR, 0x00000000, AR5K_INI_WRITE }, + { AR5K_PROFCNT_CYCLE, 0x00000000, AR5K_INI_WRITE }, + { AR5K_QUIET_CTL1, 0x00000088, AR5K_INI_WRITE }, + /* Initial rate duration table (32 entries )*/ + { AR5K_RATE_DUR(0), 0x00000000, AR5K_INI_WRITE }, + { AR5K_RATE_DUR(1), 0x0000008c, AR5K_INI_WRITE }, + { AR5K_RATE_DUR(2), 0x000000e4, AR5K_INI_WRITE }, + { AR5K_RATE_DUR(3), 0x000002d5, AR5K_INI_WRITE }, + { AR5K_RATE_DUR(4), 0x00000000, AR5K_INI_WRITE }, + { AR5K_RATE_DUR(5), 0x00000000, AR5K_INI_WRITE }, + { AR5K_RATE_DUR(6), 0x000000a0, AR5K_INI_WRITE }, + { AR5K_RATE_DUR(7), 0x000001c9, AR5K_INI_WRITE }, + { AR5K_RATE_DUR(8), 0x0000002c, AR5K_INI_WRITE }, + { AR5K_RATE_DUR(9), 0x0000002c, AR5K_INI_WRITE }, + { AR5K_RATE_DUR(10), 0x00000030, AR5K_INI_WRITE }, + { AR5K_RATE_DUR(11), 0x0000003c, AR5K_INI_WRITE }, + { AR5K_RATE_DUR(12), 0x0000002c, AR5K_INI_WRITE }, + { AR5K_RATE_DUR(13), 0x0000002c, AR5K_INI_WRITE }, + { AR5K_RATE_DUR(14), 0x00000030, AR5K_INI_WRITE }, + { AR5K_RATE_DUR(15), 0x0000003c, AR5K_INI_WRITE }, + { AR5K_RATE_DUR(16), 0x00000000, AR5K_INI_WRITE }, + { AR5K_RATE_DUR(17), 0x00000000, AR5K_INI_WRITE }, + { AR5K_RATE_DUR(18), 0x00000000, AR5K_INI_WRITE }, + { AR5K_RATE_DUR(19), 0x00000000, AR5K_INI_WRITE }, + { AR5K_RATE_DUR(20), 0x00000000, AR5K_INI_WRITE }, + { AR5K_RATE_DUR(21), 0x00000000, AR5K_INI_WRITE }, + { AR5K_RATE_DUR(22), 0x00000000, AR5K_INI_WRITE }, + { AR5K_RATE_DUR(23), 0x00000000, AR5K_INI_WRITE }, + { AR5K_RATE_DUR(24), 0x000000d5, AR5K_INI_WRITE }, + { AR5K_RATE_DUR(25), 0x000000df, AR5K_INI_WRITE }, + { AR5K_RATE_DUR(26), 0x00000102, AR5K_INI_WRITE }, + { AR5K_RATE_DUR(27), 0x0000013a, AR5K_INI_WRITE }, + { AR5K_RATE_DUR(28), 0x00000075, AR5K_INI_WRITE }, + { AR5K_RATE_DUR(29), 0x0000007f, AR5K_INI_WRITE }, + { AR5K_RATE_DUR(30), 0x000000a2, AR5K_INI_WRITE }, + { AR5K_RATE_DUR(31), 0x00000000, AR5K_INI_WRITE }, + { AR5K_QUIET_CTL2, 0x00010002, AR5K_INI_WRITE }, + { AR5K_TSF_PARM, 0x00000001, AR5K_INI_WRITE }, + { AR5K_QOS_NOACK, 0x000000c0, AR5K_INI_WRITE }, + { AR5K_PHY_ERR_FIL, 0x00000000, AR5K_INI_WRITE }, + { AR5K_XRLAT_TX, 0x00000168, AR5K_INI_WRITE }, + { AR5K_ACKSIFS, 0x00000000, AR5K_INI_WRITE }, + /* Rate -> db table + * notice ...03<-02<-01<-00 ! */ + { AR5K_RATE2DB(0), 0x03020100, AR5K_INI_WRITE }, + { AR5K_RATE2DB(1), 0x07060504, AR5K_INI_WRITE }, + { AR5K_RATE2DB(2), 0x0b0a0908, AR5K_INI_WRITE }, + { AR5K_RATE2DB(3), 0x0f0e0d0c, AR5K_INI_WRITE }, + { AR5K_RATE2DB(4), 0x13121110, AR5K_INI_WRITE }, + { AR5K_RATE2DB(5), 0x17161514, AR5K_INI_WRITE }, + { AR5K_RATE2DB(6), 0x1b1a1918, AR5K_INI_WRITE }, + { AR5K_RATE2DB(7), 0x1f1e1d1c, AR5K_INI_WRITE }, + /* Db -> Rate table */ + { AR5K_DB2RATE(0), 0x03020100, AR5K_INI_WRITE }, + { AR5K_DB2RATE(1), 0x07060504, AR5K_INI_WRITE }, + { AR5K_DB2RATE(2), 0x0b0a0908, AR5K_INI_WRITE }, + { AR5K_DB2RATE(3), 0x0f0e0d0c, AR5K_INI_WRITE }, + { AR5K_DB2RATE(4), 0x13121110, AR5K_INI_WRITE }, + { AR5K_DB2RATE(5), 0x17161514, AR5K_INI_WRITE }, + { AR5K_DB2RATE(6), 0x1b1a1918, AR5K_INI_WRITE }, + { AR5K_DB2RATE(7), 0x1f1e1d1c, AR5K_INI_WRITE }, + /* PHY registers (Common settings + * for all chips/modes) */ + { AR5K_PHY(3), 0xad848e19, AR5K_INI_WRITE }, + { AR5K_PHY(4), 0x7d28e000, AR5K_INI_WRITE }, + { AR5K_PHY_TIMING_3, 0x9c0a9f6b, AR5K_INI_WRITE }, + { AR5K_PHY_ACT, 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHY(16), 0x206a017a, AR5K_INI_WRITE }, + { AR5K_PHY(21), 0x00000859, AR5K_INI_WRITE }, + { AR5K_PHY_BIN_MASK_1, 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHY_BIN_MASK_2, 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHY_BIN_MASK_3, 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHY_BIN_MASK_CTL, 0x00800000, AR5K_INI_WRITE }, + { AR5K_PHY_ANT_CTL, 0x00000001, AR5K_INI_WRITE }, + /*{ AR5K_PHY(71), 0x0000092a, AR5K_INI_WRITE },*/ /* Old value */ + { AR5K_PHY_MAX_RX_LEN, 0x00000c80, AR5K_INI_WRITE }, + { AR5K_PHY_IQ, 0x05100000, AR5K_INI_WRITE }, + { AR5K_PHY_WARM_RESET, 0x00000001, AR5K_INI_WRITE }, + { AR5K_PHY_CTL, 0x00000004, AR5K_INI_WRITE }, + { AR5K_PHY_TXPOWER_RATE1, 0x1e1f2022, AR5K_INI_WRITE }, + { AR5K_PHY_TXPOWER_RATE2, 0x0a0b0c0d, AR5K_INI_WRITE }, + { AR5K_PHY_TXPOWER_RATE_MAX, 0x0000003f, AR5K_INI_WRITE }, + { AR5K_PHY(82), 0x9280b212, AR5K_INI_WRITE }, + { AR5K_PHY_RADAR, 0x5d50e188, AR5K_INI_WRITE }, + /*{ AR5K_PHY(86), 0x000000ff, AR5K_INI_WRITE },*/ + { AR5K_PHY(87), 0x004b6a8e, AR5K_INI_WRITE }, + { AR5K_PHY_NFTHRES, 0x000003ce, AR5K_INI_WRITE }, + { AR5K_PHY_RESTART, 0x192fb515, AR5K_INI_WRITE }, + { AR5K_PHY(94), 0x00000001, AR5K_INI_WRITE }, + { AR5K_PHY_RFBUS_REQ, 0x00000000, AR5K_INI_WRITE }, + /*{ AR5K_PHY(644), 0x0080a333, AR5K_INI_WRITE },*/ /* Old value */ + /*{ AR5K_PHY(645), 0x00206c10, AR5K_INI_WRITE },*/ /* Old value */ + { AR5K_PHY(644), 0x00806333, AR5K_INI_WRITE }, + { AR5K_PHY(645), 0x00106c10, AR5K_INI_WRITE }, + { AR5K_PHY(646), 0x009c4060, AR5K_INI_WRITE }, + /* { AR5K_PHY(647), 0x1483800a, AR5K_INI_WRITE }, */ + /* { AR5K_PHY(648), 0x01831061, AR5K_INI_WRITE }, */ /* Old value */ + { AR5K_PHY(648), 0x018830c6, AR5K_INI_WRITE }, + { AR5K_PHY(649), 0x00000400, AR5K_INI_WRITE }, + /*{ AR5K_PHY(650), 0x000001b5, AR5K_INI_WRITE },*/ + { AR5K_PHY(651), 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHY_TXPOWER_RATE3, 0x20202020, AR5K_INI_WRITE }, + { AR5K_PHY_TXPOWER_RATE2, 0x20202020, AR5K_INI_WRITE }, + /*{ AR5K_PHY(655), 0x13c889af, AR5K_INI_WRITE },*/ + { AR5K_PHY(656), 0x38490a20, AR5K_INI_WRITE }, + { AR5K_PHY(657), 0x00007bb6, AR5K_INI_WRITE }, + { AR5K_PHY(658), 0x0fff3ffc, AR5K_INI_WRITE }, +}; + +/* Initial mode-specific settings for AR5212 (Written before ar5212_ini) */ +static const struct ath5k_ini_mode ar5212_ini_mode_start[] = { + { AR5K_QUEUE_DFS_LOCAL_IFS(0), + /* a/XR aTurbo b g (DYN) gTurbo */ + { 0x002ffc0f, 0x002ffc0f, 0x002ffc1f, 0x002ffc0f, 0x002ffc0f } }, + { AR5K_QUEUE_DFS_LOCAL_IFS(1), + { 0x002ffc0f, 0x002ffc0f, 0x002ffc1f, 0x002ffc0f, 0x002ffc0f } }, + { AR5K_QUEUE_DFS_LOCAL_IFS(2), + { 0x002ffc0f, 0x002ffc0f, 0x002ffc1f, 0x002ffc0f, 0x002ffc0f } }, + { AR5K_QUEUE_DFS_LOCAL_IFS(3), + { 0x002ffc0f, 0x002ffc0f, 0x002ffc1f, 0x002ffc0f, 0x002ffc0f } }, + { AR5K_QUEUE_DFS_LOCAL_IFS(4), + { 0x002ffc0f, 0x002ffc0f, 0x002ffc1f, 0x002ffc0f, 0x002ffc0f } }, + { AR5K_QUEUE_DFS_LOCAL_IFS(5), + { 0x002ffc0f, 0x002ffc0f, 0x002ffc1f, 0x002ffc0f, 0x002ffc0f } }, + { AR5K_QUEUE_DFS_LOCAL_IFS(6), + { 0x002ffc0f, 0x002ffc0f, 0x002ffc1f, 0x002ffc0f, 0x002ffc0f } }, + { AR5K_QUEUE_DFS_LOCAL_IFS(7), + { 0x002ffc0f, 0x002ffc0f, 0x002ffc1f, 0x002ffc0f, 0x002ffc0f } }, + { AR5K_QUEUE_DFS_LOCAL_IFS(8), + { 0x002ffc0f, 0x002ffc0f, 0x002ffc1f, 0x002ffc0f, 0x002ffc0f } }, + { AR5K_QUEUE_DFS_LOCAL_IFS(9), + { 0x002ffc0f, 0x002ffc0f, 0x002ffc1f, 0x002ffc0f, 0x002ffc0f } }, + { AR5K_DCU_GBL_IFS_SIFS, + { 0x00000230, 0x000001e0, 0x000000b0, 0x00000160, 0x000001e0 } }, + { AR5K_DCU_GBL_IFS_SLOT, + { 0x00000168, 0x000001e0, 0x000001b8, 0x0000018c, 0x000001e0 } }, + { AR5K_DCU_GBL_IFS_EIFS, + { 0x00000e60, 0x00001180, 0x00001f1c, 0x00003e38, 0x00001180 } }, + { AR5K_DCU_GBL_IFS_MISC, + { 0x0000a0e0, 0x00014068, 0x00005880, 0x0000b0e0, 0x00014068 } }, + { AR5K_TIME_OUT, + { 0x03e803e8, 0x06e006e0, 0x04200420, 0x08400840, 0x06e006e0 } }, + { AR5K_PHY_TURBO, + { 0x00000000, 0x00000003, 0x00000000, 0x00000000, 0x00000003 } }, + { AR5K_PHY(8), + { 0x02020200, 0x02020200, 0x02010200, 0x02020200, 0x02020200 } }, + { AR5K_PHY_RF_CTL2, + { 0x00000e0e, 0x00000e0e, 0x00000707, 0x00000e0e, 0x00000e0e } }, + { AR5K_PHY_SETTLING, + { 0x1372161c, 0x13721c25, 0x13721722, 0x137216a2, 0x13721c25 } }, + { AR5K_PHY_AGCCTL, + { 0x00009d10, 0x00009d10, 0x00009d18, 0x00009d18, 0x00009d10 } }, + { AR5K_PHY_NF, + { 0x0001ce00, 0x0001ce00, 0x0001ce00, 0x0001ce00, 0x0001ce00 } }, + { AR5K_PHY_WEAK_OFDM_HIGH_THR, + { 0x409a4190, 0x409a4190, 0x409a4190, 0x409a4190, 0x409a4190 } }, + { AR5K_PHY(70), + { 0x000001b8, 0x000001b8, 0x00000084, 0x00000108, 0x000001b8 } }, + { AR5K_PHY_OFDM_SELFCORR, + { 0x10058a05, 0x10058a05, 0x10058a05, 0x10058a05, 0x10058a05 } }, + { 0xa230, + { 0x00000000, 0x00000000, 0x00000000, 0x00000108, 0x00000000 } }, +}; + +/* Initial mode-specific settings for AR5212 + RF5111 (Written after ar5212_ini) */ +static const struct ath5k_ini_mode rf5111_ini_mode_end[] = { + { AR5K_TXCFG, + /* a/XR aTurbo b g (DYN) gTurbo */ + { 0x00008015, 0x00008015, 0x00008015, 0x00008015, 0x00008015 } }, + { AR5K_USEC_5211, + { 0x128d8fa7, 0x09880fcf, 0x04e00f95, 0x12e00fab, 0x09880fcf } }, + { AR5K_PHY_RF_CTL3, + { 0x0a020001, 0x0a020001, 0x05010100, 0x0a020001, 0x0a020001 } }, + { AR5K_PHY_RF_CTL4, + { 0x00000e0e, 0x00000e0e, 0x00000e0e, 0x00000e0e, 0x00000e0e } }, + { AR5K_PHY_PA_CTL, + { 0x00000007, 0x00000007, 0x0000000b, 0x0000000b, 0x0000000b } }, + { AR5K_PHY_GAIN, + { 0x0018da5a, 0x0018da5a, 0x0018ca69, 0x0018ca69, 0x0018ca69 } }, + { AR5K_PHY_DESIRED_SIZE, + { 0x0de8b4e0, 0x0de8b4e0, 0x0de8b4e0, 0x0de8b4e0, 0x0de8b4e0 } }, + { AR5K_PHY_SIG, + { 0x7e800d2e, 0x7e800d2e, 0x7ee84d2e, 0x7ee84d2e, 0x7e800d2e } }, + { AR5K_PHY_AGCCOARSE, + { 0x3137665e, 0x3137665e, 0x3137665e, 0x3137665e, 0x3137615e } }, + { AR5K_PHY_WEAK_OFDM_LOW_THR, + { 0x050cb081, 0x050cb081, 0x050cb081, 0x050cb080, 0x050cb080 } }, + { AR5K_PHY_RX_DELAY, + { 0x00002710, 0x00002710, 0x0000157c, 0x00002af8, 0x00002710 } }, + { AR5K_PHY_FRAME_CTL_5211, + { 0xf7b81020, 0xf7b81020, 0xf7b80d20, 0xf7b81020, 0xf7b81020 } }, + { AR5K_PHY_GAIN_2GHZ, + { 0x642c416a, 0x642c416a, 0x6440416a, 0x6440416a, 0x6440416a } }, + { AR5K_PHY_CCK_RX_CTL_4, + { 0x1883800a, 0x1883800a, 0x1873800a, 0x1883800a, 0x1883800a } }, +}; + +static const struct ath5k_ini rf5111_ini_common_end[] = { + { AR5K_DCU_FP, 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHY_AGC, 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHY_ADC_CTL, 0x00022ffe, AR5K_INI_WRITE }, + { 0x983c, 0x00020100, AR5K_INI_WRITE }, + { AR5K_PHY_GAIN_OFFSET, 0x1284613c, AR5K_INI_WRITE }, + { AR5K_PHY_PAPD_PROBE, 0x00004883, AR5K_INI_WRITE }, + { 0x9940, 0x00000004, AR5K_INI_WRITE }, + { 0x9958, 0x000000ff, AR5K_INI_WRITE }, + { 0x9974, 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHY_SPENDING, 0x00000018, AR5K_INI_WRITE }, + { AR5K_PHY_CCKTXCTL, 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHY_CCK_CROSSCORR, 0xd03e6788, AR5K_INI_WRITE }, + { AR5K_PHY_DAG_CCK_CTL, 0x000001b5, AR5K_INI_WRITE }, + { 0xa23c, 0x13c889af, AR5K_INI_WRITE }, +}; + +/* Initial mode-specific settings for AR5212 + RF5112 (Written after ar5212_ini) */ +static const struct ath5k_ini_mode rf5112_ini_mode_end[] = { + { AR5K_TXCFG, + /* a/XR aTurbo b g (DYN) gTurbo */ + { 0x00008015, 0x00008015, 0x00008015, 0x00008015, 0x00008015 } }, + { AR5K_USEC_5211, + { 0x128d93a7, 0x098813cf, 0x04e01395, 0x12e013ab, 0x098813cf } }, + { AR5K_PHY_RF_CTL3, + { 0x0a020001, 0x0a020001, 0x05020100, 0x0a020001, 0x0a020001 } }, + { AR5K_PHY_RF_CTL4, + { 0x00000e0e, 0x00000e0e, 0x00000e0e, 0x00000e0e, 0x00000e0e } }, + { AR5K_PHY_PA_CTL, + { 0x00000007, 0x00000007, 0x0000000b, 0x0000000b, 0x0000000b } }, + { AR5K_PHY_GAIN, + { 0x0018da6d, 0x0018da6d, 0x0018ca75, 0x0018ca75, 0x0018ca75 } }, + { AR5K_PHY_DESIRED_SIZE, + { 0x0de8b4e0, 0x0de8b4e0, 0x0de8b4e0, 0x0de8b4e0, 0x0de8b4e0 } }, + { AR5K_PHY_SIG, + { 0x7e800d2e, 0x7e800d2e, 0x7ee80d2e, 0x7ee80d2e, 0x7e800d2e } }, + { AR5K_PHY_AGCCOARSE, + { 0x3137665e, 0x3137665e, 0x3137665e, 0x3137665e, 0x3137665e } }, + { AR5K_PHY_WEAK_OFDM_LOW_THR, + { 0x050cb081, 0x050cb081, 0x050cb081, 0x050cb081, 0x050cb081 } }, + { AR5K_PHY_RX_DELAY, + { 0x000007d0, 0x000007d0, 0x0000044c, 0x00000898, 0x000007d0 } }, + { AR5K_PHY_FRAME_CTL_5211, + { 0xf7b81020, 0xf7b81020, 0xf7b80d10, 0xf7b81010, 0xf7b81010 } }, + { AR5K_PHY_CCKTXCTL, + { 0x00000000, 0x00000000, 0x00000008, 0x00000008, 0x00000008 } }, + { AR5K_PHY_CCK_CROSSCORR, + { 0xd6be6788, 0xd6be6788, 0xd03e6788, 0xd03e6788, 0xd03e6788 } }, + { AR5K_PHY_GAIN_2GHZ, + { 0x642c0140, 0x642c0140, 0x6442c160, 0x6442c160, 0x6442c160 } }, + { AR5K_PHY_CCK_RX_CTL_4, + { 0x1883800a, 0x1883800a, 0x1873800a, 0x1883800a, 0x1883800a } }, +}; + +static const struct ath5k_ini rf5112_ini_common_end[] = { + { AR5K_DCU_FP, 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHY_AGC, 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHY_ADC_CTL, 0x00022ffe, AR5K_INI_WRITE }, + { 0x983c, 0x00020100, AR5K_INI_WRITE }, + { AR5K_PHY_GAIN_OFFSET, 0x1284613c, AR5K_INI_WRITE }, + { AR5K_PHY_PAPD_PROBE, 0x00004882, AR5K_INI_WRITE }, + { 0x9940, 0x00000004, AR5K_INI_WRITE }, + { 0x9958, 0x000000ff, AR5K_INI_WRITE }, + { 0x9974, 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHY_DAG_CCK_CTL, 0x000001b5, AR5K_INI_WRITE }, + { 0xa23c, 0x13c889af, AR5K_INI_WRITE }, +}; + +/* Initial mode-specific settings for RF5413/5414 (Written after ar5212_ini) */ +static const struct ath5k_ini_mode rf5413_ini_mode_end[] = { + { AR5K_TXCFG, + /* a/XR aTurbo b g (DYN) gTurbo */ + { 0x00000015, 0x00000015, 0x00000015, 0x00000015, 0x00000015 } }, + { AR5K_USEC_5211, + { 0x128d93a7, 0x098813cf, 0x04e01395, 0x12e013ab, 0x098813cf } }, + { AR5K_PHY_RF_CTL3, + { 0x0a020001, 0x0a020001, 0x05020100, 0x0a020001, 0x0a020001 } }, + { AR5K_PHY_RF_CTL4, + { 0x00000e0e, 0x00000e0e, 0x00000e0e, 0x00000e0e, 0x00000e0e } }, + { AR5K_PHY_PA_CTL, + { 0x00000007, 0x00000007, 0x0000000b, 0x0000000b, 0x0000000b } }, + { AR5K_PHY_GAIN, + { 0x0018fa61, 0x0018fa61, 0x001a1a63, 0x001a1a63, 0x001a1a63 } }, + { AR5K_PHY_DESIRED_SIZE, + { 0x0c98b4e0, 0x0c98b4e0, 0x0c98b0da, 0x0c98b0da, 0x0c98b0da } }, + { AR5K_PHY_SIG, + { 0x7ec80d2e, 0x7ec80d2e, 0x7ec80d2e, 0x7ec80d2e, 0x7ec80d2e } }, + { AR5K_PHY_AGCCOARSE, + { 0x3139605e, 0x3139605e, 0x3139605e, 0x3139605e, 0x3139605e } }, + { AR5K_PHY_WEAK_OFDM_LOW_THR, + { 0x050cb081, 0x050cb081, 0x050cb081, 0x050cb081, 0x050cb081 } }, + { AR5K_PHY_RX_DELAY, + { 0x000007d0, 0x000007d0, 0x0000044c, 0x00000898, 0x000007d0 } }, + { AR5K_PHY_FRAME_CTL_5211, + { 0xf7b81000, 0xf7b81000, 0xf7b80d00, 0xf7b81000, 0xf7b81000 } }, + { AR5K_PHY_CCKTXCTL, + { 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000 } }, + { AR5K_PHY_CCK_CROSSCORR, + { 0xd6be6788, 0xd6be6788, 0xd03e6788, 0xd03e6788, 0xd03e6788 } }, + { AR5K_PHY_GAIN_2GHZ, + { 0x002ec1e0, 0x002ec1e0, 0x002ac120, 0x002ac120, 0x002ac120 } }, + { AR5K_PHY_CCK_RX_CTL_4, + { 0x1883800a, 0x1883800a, 0x1863800a, 0x1883800a, 0x1883800a } }, + { 0xa300, + { 0x18010000, 0x18010000, 0x18010000, 0x18010000, 0x18010000 } }, + { 0xa304, + { 0x30032602, 0x30032602, 0x30032602, 0x30032602, 0x30032602 } }, + { 0xa308, + { 0x48073e06, 0x48073e06, 0x48073e06, 0x48073e06, 0x48073e06 } }, + { 0xa30c, + { 0x560b4c0a, 0x560b4c0a, 0x560b4c0a, 0x560b4c0a, 0x560b4c0a } }, + { 0xa310, + { 0x641a600f, 0x641a600f, 0x641a600f, 0x641a600f, 0x641a600f } }, + { 0xa314, + { 0x784f6e1b, 0x784f6e1b, 0x784f6e1b, 0x784f6e1b, 0x784f6e1b } }, + { 0xa318, + { 0x868f7c5a, 0x868f7c5a, 0x868f7c5a, 0x868f7c5a, 0x868f7c5a } }, + { 0xa31c, + { 0x90cf865b, 0x90cf865b, 0x8ecf865b, 0x8ecf865b, 0x8ecf865b } }, + { 0xa320, + { 0x9d4f970f, 0x9d4f970f, 0x9b4f970f, 0x9b4f970f, 0x9b4f970f } }, + { 0xa324, + { 0xa7cfa38f, 0xa7cfa38f, 0xa3cf9f8f, 0xa3cf9f8f, 0xa3cf9f8f } }, + { 0xa328, + { 0xb55faf1f, 0xb55faf1f, 0xb35faf1f, 0xb35faf1f, 0xb35faf1f } }, + { 0xa32c, + { 0xbddfb99f, 0xbddfb99f, 0xbbdfb99f, 0xbbdfb99f, 0xbbdfb99f } }, + { 0xa330, + { 0xcb7fc53f, 0xcb7fc53f, 0xcb7fc73f, 0xcb7fc73f, 0xcb7fc73f } }, + { 0xa334, + { 0xd5ffd1bf, 0xd5ffd1bf, 0xd3ffd1bf, 0xd3ffd1bf, 0xd3ffd1bf } }, +}; + +static const struct ath5k_ini rf5413_ini_common_end[] = { + { AR5K_DCU_FP, 0x000003e0, AR5K_INI_WRITE }, + { AR5K_5414_CBCFG, 0x00000010, AR5K_INI_WRITE }, + { AR5K_SEQ_MASK, 0x0000000f, AR5K_INI_WRITE }, + { 0x809c, 0x00000000, AR5K_INI_WRITE }, + { 0x80a0, 0x00000000, AR5K_INI_WRITE }, + { AR5K_MIC_QOS_CTL, 0x00000000, AR5K_INI_WRITE }, + { AR5K_MIC_QOS_SEL, 0x00000000, AR5K_INI_WRITE }, + { AR5K_MISC_MODE, 0x00000000, AR5K_INI_WRITE }, + { AR5K_OFDM_FIL_CNT, 0x00000000, AR5K_INI_WRITE }, + { AR5K_CCK_FIL_CNT, 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHYERR_CNT1, 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHYERR_CNT1_MASK, 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHYERR_CNT2, 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHYERR_CNT2_MASK, 0x00000000, AR5K_INI_WRITE }, + { AR5K_TSF_THRES, 0x00000000, AR5K_INI_WRITE }, + { 0x8140, 0x800003f9, AR5K_INI_WRITE }, + { 0x8144, 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHY_AGC, 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHY_ADC_CTL, 0x0000a000, AR5K_INI_WRITE }, + { 0x983c, 0x00200400, AR5K_INI_WRITE }, + { AR5K_PHY_GAIN_OFFSET, 0x1284233c, AR5K_INI_WRITE }, + { AR5K_PHY_SCR, 0x0000001f, AR5K_INI_WRITE }, + { AR5K_PHY_SLMT, 0x00000080, AR5K_INI_WRITE }, + { AR5K_PHY_SCAL, 0x0000000e, AR5K_INI_WRITE }, + { 0x9958, 0x00081fff, AR5K_INI_WRITE }, + { AR5K_PHY_TIMING_7, 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHY_TIMING_8, 0x02800000, AR5K_INI_WRITE }, + { AR5K_PHY_TIMING_11, 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHY_HEAVY_CLIP_ENABLE, 0x00000000, AR5K_INI_WRITE }, + { 0x99e4, 0xaaaaaaaa, AR5K_INI_WRITE }, + { 0x99e8, 0x3c466478, AR5K_INI_WRITE }, + { 0x99ec, 0x000000aa, AR5K_INI_WRITE }, + { AR5K_PHY_SCLOCK, 0x0000000c, AR5K_INI_WRITE }, + { AR5K_PHY_SDELAY, 0x000000ff, AR5K_INI_WRITE }, + { AR5K_PHY_SPENDING, 0x00000014, AR5K_INI_WRITE }, + { AR5K_PHY_DAG_CCK_CTL, 0x000009b5, AR5K_INI_WRITE }, + { 0xa23c, 0x93c889af, AR5K_INI_WRITE }, + { AR5K_PHY_FAST_ADC, 0x00000001, AR5K_INI_WRITE }, + { 0xa250, 0x0000a000, AR5K_INI_WRITE }, + { AR5K_PHY_BLUETOOTH, 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHY_TPC_RG1, 0x0cc75380, AR5K_INI_WRITE }, + { 0xa25c, 0x0f0f0f01, AR5K_INI_WRITE }, + { 0xa260, 0x5f690f01, AR5K_INI_WRITE }, + { 0xa264, 0x00418a11, AR5K_INI_WRITE }, + { 0xa268, 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHY_TPC_RG5, 0x0c30c16a, AR5K_INI_WRITE }, + { 0xa270, 0x00820820, AR5K_INI_WRITE }, + { 0xa274, 0x081b7caa, AR5K_INI_WRITE }, + { 0xa278, 0x1ce739ce, AR5K_INI_WRITE }, + { 0xa27c, 0x051701ce, AR5K_INI_WRITE }, + { 0xa338, 0x00000000, AR5K_INI_WRITE }, + { 0xa33c, 0x00000000, AR5K_INI_WRITE }, + { 0xa340, 0x00000000, AR5K_INI_WRITE }, + { 0xa344, 0x00000000, AR5K_INI_WRITE }, + { 0xa348, 0x3fffffff, AR5K_INI_WRITE }, + { 0xa34c, 0x3fffffff, AR5K_INI_WRITE }, + { 0xa350, 0x3fffffff, AR5K_INI_WRITE }, + { 0xa354, 0x0003ffff, AR5K_INI_WRITE }, + { 0xa358, 0x79a8aa1f, AR5K_INI_WRITE }, + { 0xa35c, 0x066c420f, AR5K_INI_WRITE }, + { 0xa360, 0x0f282207, AR5K_INI_WRITE }, + { 0xa364, 0x17601685, AR5K_INI_WRITE }, + { 0xa368, 0x1f801104, AR5K_INI_WRITE }, + { 0xa36c, 0x37a00c03, AR5K_INI_WRITE }, + { 0xa370, 0x3fc40883, AR5K_INI_WRITE }, + { 0xa374, 0x57c00803, AR5K_INI_WRITE }, + { 0xa378, 0x5fd80682, AR5K_INI_WRITE }, + { 0xa37c, 0x7fe00482, AR5K_INI_WRITE }, + { 0xa380, 0x7f3c7bba, AR5K_INI_WRITE }, + { 0xa384, 0xf3307ff0, AR5K_INI_WRITE }, +}; + +/* Initial mode-specific settings for RF2413/2414 (Written after ar5212_ini) */ +/* XXX: a mode ? */ +static const struct ath5k_ini_mode rf2413_ini_mode_end[] = { + { AR5K_TXCFG, + /* a/XR aTurbo b g (DYN) gTurbo */ + { 0x00000015, 0x00000015, 0x00000015, 0x00000015, 0x00000015 } }, + { AR5K_USEC_5211, + { 0x128d93a7, 0x098813cf, 0x04e01395, 0x12e013ab, 0x098813cf } }, + { AR5K_PHY_RF_CTL3, + { 0x0a020001, 0x0a020001, 0x05020000, 0x0a020001, 0x0a020001 } }, + { AR5K_PHY_RF_CTL4, + { 0x00000e00, 0x00000e00, 0x00000e00, 0x00000e00, 0x00000e00 } }, + { AR5K_PHY_PA_CTL, + { 0x00000002, 0x00000002, 0x0000000a, 0x0000000a, 0x0000000a } }, + { AR5K_PHY_GAIN, + { 0x0018da6d, 0x0018da6d, 0x001a6a64, 0x001a6a64, 0x001a6a64 } }, + { AR5K_PHY_DESIRED_SIZE, + { 0x0de8b4e0, 0x0de8b4e0, 0x0de8b0da, 0x0c98b0da, 0x0de8b0da } }, + { AR5K_PHY_SIG, + { 0x7e800d2e, 0x7e800d2e, 0x7ee80d2e, 0x7ec80d2e, 0x7e800d2e } }, + { AR5K_PHY_AGCCOARSE, + { 0x3137665e, 0x3137665e, 0x3137665e, 0x3139605e, 0x3137665e } }, + { AR5K_PHY_WEAK_OFDM_LOW_THR, + { 0x050cb081, 0x050cb081, 0x050cb081, 0x050cb081, 0x050cb081 } }, + { AR5K_PHY_RX_DELAY, + { 0x000007d0, 0x000007d0, 0x0000044c, 0x00000898, 0x000007d0 } }, + { AR5K_PHY_FRAME_CTL_5211, + { 0xf7b81000, 0xf7b81000, 0xf7b80d00, 0xf7b81000, 0xf7b81000 } }, + { AR5K_PHY_CCKTXCTL, + { 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000 } }, + { AR5K_PHY_CCK_CROSSCORR, + { 0xd6be6788, 0xd6be6788, 0xd03e6788, 0xd03e6788, 0xd03e6788 } }, + { AR5K_PHY_GAIN_2GHZ, + { 0x002c0140, 0x002c0140, 0x0042c140, 0x0042c140, 0x0042c140 } }, + { AR5K_PHY_CCK_RX_CTL_4, + { 0x1883800a, 0x1883800a, 0x1863800a, 0x1883800a, 0x1883800a } }, +}; + +static const struct ath5k_ini rf2413_ini_common_end[] = { + { AR5K_DCU_FP, 0x000003e0, AR5K_INI_WRITE }, + { AR5K_SEQ_MASK, 0x0000000f, AR5K_INI_WRITE }, + { AR5K_MIC_QOS_CTL, 0x00000000, AR5K_INI_WRITE }, + { AR5K_MIC_QOS_SEL, 0x00000000, AR5K_INI_WRITE }, + { AR5K_MISC_MODE, 0x00000000, AR5K_INI_WRITE }, + { AR5K_OFDM_FIL_CNT, 0x00000000, AR5K_INI_WRITE }, + { AR5K_CCK_FIL_CNT, 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHYERR_CNT1, 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHYERR_CNT1_MASK, 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHYERR_CNT2, 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHYERR_CNT2_MASK, 0x00000000, AR5K_INI_WRITE }, + { AR5K_TSF_THRES, 0x00000000, AR5K_INI_WRITE }, + { 0x8140, 0x800000a8, AR5K_INI_WRITE }, + { 0x8144, 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHY_AGC, 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHY_ADC_CTL, 0x0000a000, AR5K_INI_WRITE }, + { 0x983c, 0x00200400, AR5K_INI_WRITE }, + { AR5K_PHY_GAIN_OFFSET, 0x1284233c, AR5K_INI_WRITE }, + { AR5K_PHY_SCR, 0x0000001f, AR5K_INI_WRITE }, + { AR5K_PHY_SLMT, 0x00000080, AR5K_INI_WRITE }, + { AR5K_PHY_SCAL, 0x0000000e, AR5K_INI_WRITE }, + { 0x9958, 0x000000ff, AR5K_INI_WRITE }, + { AR5K_PHY_TIMING_7, 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHY_TIMING_8, 0x02800000, AR5K_INI_WRITE }, + { AR5K_PHY_TIMING_11, 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHY_HEAVY_CLIP_ENABLE, 0x00000000, AR5K_INI_WRITE }, + { 0x99e4, 0xaaaaaaaa, AR5K_INI_WRITE }, + { 0x99e8, 0x3c466478, AR5K_INI_WRITE }, + { 0x99ec, 0x000000aa, AR5K_INI_WRITE }, + { AR5K_PHY_SCLOCK, 0x0000000c, AR5K_INI_WRITE }, + { AR5K_PHY_SDELAY, 0x000000ff, AR5K_INI_WRITE }, + { AR5K_PHY_SPENDING, 0x00000014, AR5K_INI_WRITE }, + { AR5K_PHY_DAG_CCK_CTL, 0x000009b5, AR5K_INI_WRITE }, + { 0xa23c, 0x93c889af, AR5K_INI_WRITE }, + { AR5K_PHY_FAST_ADC, 0x00000001, AR5K_INI_WRITE }, + { 0xa250, 0x0000a000, AR5K_INI_WRITE }, + { AR5K_PHY_BLUETOOTH, 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHY_TPC_RG1, 0x0cc75380, AR5K_INI_WRITE }, + { 0xa25c, 0x0f0f0f01, AR5K_INI_WRITE }, + { 0xa260, 0x5f690f01, AR5K_INI_WRITE }, + { 0xa264, 0x00418a11, AR5K_INI_WRITE }, + { 0xa268, 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHY_TPC_RG5, 0x0c30c16a, AR5K_INI_WRITE }, + { 0xa270, 0x00820820, AR5K_INI_WRITE }, + { 0xa274, 0x001b7caa, AR5K_INI_WRITE }, + { 0xa278, 0x1ce739ce, AR5K_INI_WRITE }, + { 0xa27c, 0x051701ce, AR5K_INI_WRITE }, + { 0xa300, 0x18010000, AR5K_INI_WRITE }, + { 0xa304, 0x30032602, AR5K_INI_WRITE }, + { 0xa308, 0x48073e06, AR5K_INI_WRITE }, + { 0xa30c, 0x560b4c0a, AR5K_INI_WRITE }, + { 0xa310, 0x641a600f, AR5K_INI_WRITE }, + { 0xa314, 0x784f6e1b, AR5K_INI_WRITE }, + { 0xa318, 0x868f7c5a, AR5K_INI_WRITE }, + { 0xa31c, 0x8ecf865b, AR5K_INI_WRITE }, + { 0xa320, 0x9d4f970f, AR5K_INI_WRITE }, + { 0xa324, 0xa5cfa18f, AR5K_INI_WRITE }, + { 0xa328, 0xb55faf1f, AR5K_INI_WRITE }, + { 0xa32c, 0xbddfb99f, AR5K_INI_WRITE }, + { 0xa330, 0xcd7fc73f, AR5K_INI_WRITE }, + { 0xa334, 0xd5ffd1bf, AR5K_INI_WRITE }, + { 0xa338, 0x00000000, AR5K_INI_WRITE }, + { 0xa33c, 0x00000000, AR5K_INI_WRITE }, + { 0xa340, 0x00000000, AR5K_INI_WRITE }, + { 0xa344, 0x00000000, AR5K_INI_WRITE }, + { 0xa348, 0x3fffffff, AR5K_INI_WRITE }, + { 0xa34c, 0x3fffffff, AR5K_INI_WRITE }, + { 0xa350, 0x3fffffff, AR5K_INI_WRITE }, + { 0xa354, 0x0003ffff, AR5K_INI_WRITE }, + { 0xa358, 0x79a8aa1f, AR5K_INI_WRITE }, + { 0xa35c, 0x066c420f, AR5K_INI_WRITE }, + { 0xa360, 0x0f282207, AR5K_INI_WRITE }, + { 0xa364, 0x17601685, AR5K_INI_WRITE }, + { 0xa368, 0x1f801104, AR5K_INI_WRITE }, + { 0xa36c, 0x37a00c03, AR5K_INI_WRITE }, + { 0xa370, 0x3fc40883, AR5K_INI_WRITE }, + { 0xa374, 0x57c00803, AR5K_INI_WRITE }, + { 0xa378, 0x5fd80682, AR5K_INI_WRITE }, + { 0xa37c, 0x7fe00482, AR5K_INI_WRITE }, + { 0xa380, 0x7f3c7bba, AR5K_INI_WRITE }, + { 0xa384, 0xf3307ff0, AR5K_INI_WRITE }, +}; + +/* Initial mode-specific settings for RF2425 (Written after ar5212_ini) */ +/* XXX: a mode ? */ +static const struct ath5k_ini_mode rf2425_ini_mode_end[] = { + { AR5K_TXCFG, + /* a/XR aTurbo b g (DYN) gTurbo */ + { 0x00000015, 0x00000015, 0x00000015, 0x00000015, 0x00000015 } }, + { AR5K_USEC_5211, + { 0x128d93a7, 0x098813cf, 0x04e01395, 0x12e013ab, 0x098813cf } }, + { AR5K_PHY_TURBO, + { 0x00000000, 0x00000001, 0x00000000, 0x00000000, 0x00000001 } }, + { AR5K_PHY_RF_CTL3, + { 0x0a020001, 0x0a020001, 0x05020100, 0x0a020001, 0x0a020001 } }, + { AR5K_PHY_RF_CTL4, + { 0x00000e0e, 0x00000e0e, 0x00000e0e, 0x00000e0e, 0x00000e0e } }, + { AR5K_PHY_PA_CTL, + { 0x00000003, 0x00000003, 0x0000000b, 0x0000000b, 0x0000000b } }, + { AR5K_PHY_SETTLING, + { 0x1372161c, 0x13721c25, 0x13721722, 0x13721422, 0x13721c25 } }, + { AR5K_PHY_GAIN, + { 0x0018fa61, 0x0018fa61, 0x00199a65, 0x00199a65, 0x00199a65 } }, + { AR5K_PHY_DESIRED_SIZE, + { 0x0c98b4e0, 0x0c98b4e0, 0x0c98b0da, 0x0c98b0da, 0x0c98b0da } }, + { AR5K_PHY_SIG, + { 0x7ec80d2e, 0x7ec80d2e, 0x7ec80d2e, 0x7ec80d2e, 0x7ec80d2e } }, + { AR5K_PHY_AGCCOARSE, + { 0x3139605e, 0x3139605e, 0x3139605e, 0x3139605e, 0x3139605e } }, + { AR5K_PHY_WEAK_OFDM_LOW_THR, + { 0x050cb081, 0x050cb081, 0x050cb081, 0x050cb081, 0x050cb081 } }, + { AR5K_PHY_RX_DELAY, + { 0x000007d0, 0x000007d0, 0x0000044c, 0x00000898, 0x000007d0 } }, + { AR5K_PHY_FRAME_CTL_5211, + { 0xf7b81000, 0xf7b81000, 0xf7b80d00, 0xf7b81000, 0xf7b81000 } }, + { AR5K_PHY_CCKTXCTL, + { 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000 } }, + { AR5K_PHY_CCK_CROSSCORR, + { 0xd6be6788, 0xd6be6788, 0xd03e6788, 0xd03e6788, 0xd03e6788 } }, + { AR5K_PHY_GAIN_2GHZ, + { 0x00000140, 0x00000140, 0x0052c140, 0x0052c140, 0x0052c140 } }, + { AR5K_PHY_CCK_RX_CTL_4, + { 0x1883800a, 0x1883800a, 0x1863800a, 0x1883800a, 0x1883800a } }, + { 0xa324, + { 0xa7cfa7cf, 0xa7cfa7cf, 0xa7cfa7cf, 0xa7cfa7cf, 0xa7cfa7cf } }, + { 0xa328, + { 0xa7cfa7cf, 0xa7cfa7cf, 0xa7cfa7cf, 0xa7cfa7cf, 0xa7cfa7cf } }, + { 0xa32c, + { 0xa7cfa7cf, 0xa7cfa7cf, 0xa7cfa7cf, 0xa7cfa7cf, 0xa7cfa7cf } }, + { 0xa330, + { 0xa7cfa7cf, 0xa7cfa7cf, 0xa7cfa7cf, 0xa7cfa7cf, 0xa7cfa7cf } }, + { 0xa334, + { 0xa7cfa7cf, 0xa7cfa7cf, 0xa7cfa7cf, 0xa7cfa7cf, 0xa7cfa7cf } }, +}; + +static const struct ath5k_ini rf2425_ini_common_end[] = { + { AR5K_DCU_FP, 0x000003e0, AR5K_INI_WRITE }, + { AR5K_SEQ_MASK, 0x0000000f, AR5K_INI_WRITE }, + { 0x809c, 0x00000000, AR5K_INI_WRITE }, + { 0x80a0, 0x00000000, AR5K_INI_WRITE }, + { AR5K_MIC_QOS_CTL, 0x00000000, AR5K_INI_WRITE }, + { AR5K_MIC_QOS_SEL, 0x00000000, AR5K_INI_WRITE }, + { AR5K_MISC_MODE, 0x00000000, AR5K_INI_WRITE }, + { AR5K_OFDM_FIL_CNT, 0x00000000, AR5K_INI_WRITE }, + { AR5K_CCK_FIL_CNT, 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHYERR_CNT1, 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHYERR_CNT1_MASK, 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHYERR_CNT2, 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHYERR_CNT2_MASK, 0x00000000, AR5K_INI_WRITE }, + { AR5K_TSF_THRES, 0x00000000, AR5K_INI_WRITE }, + { 0x8140, 0x800003f9, AR5K_INI_WRITE }, + { 0x8144, 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHY_AGC, 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHY_ADC_CTL, 0x0000a000, AR5K_INI_WRITE }, + { 0x983c, 0x00200400, AR5K_INI_WRITE }, + { AR5K_PHY_GAIN_OFFSET, 0x1284233c, AR5K_INI_WRITE }, + { AR5K_PHY_SCR, 0x0000001f, AR5K_INI_WRITE }, + { AR5K_PHY_SLMT, 0x00000080, AR5K_INI_WRITE }, + { AR5K_PHY_SCAL, 0x0000000e, AR5K_INI_WRITE }, + { 0x9958, 0x00081fff, AR5K_INI_WRITE }, + { AR5K_PHY_TIMING_7, 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHY_TIMING_8, 0x02800000, AR5K_INI_WRITE }, + { AR5K_PHY_TIMING_11, 0x00000000, AR5K_INI_WRITE }, + { 0x99dc, 0xfebadbe8, AR5K_INI_WRITE }, + { AR5K_PHY_HEAVY_CLIP_ENABLE, 0x00000000, AR5K_INI_WRITE }, + { 0x99e4, 0xaaaaaaaa, AR5K_INI_WRITE }, + { 0x99e8, 0x3c466478, AR5K_INI_WRITE }, + { 0x99ec, 0x000000aa, AR5K_INI_WRITE }, + { AR5K_PHY_SCLOCK, 0x0000000c, AR5K_INI_WRITE }, + { AR5K_PHY_SDELAY, 0x000000ff, AR5K_INI_WRITE }, + { AR5K_PHY_SPENDING, 0x00000014, AR5K_INI_WRITE }, + { AR5K_PHY_DAG_CCK_CTL, 0x000009b5, AR5K_INI_WRITE }, + { AR5K_PHY_TXPOWER_RATE3, 0x20202020, AR5K_INI_WRITE }, + { AR5K_PHY_TXPOWER_RATE4, 0x20202020, AR5K_INI_WRITE }, + { 0xa23c, 0x93c889af, AR5K_INI_WRITE }, + { AR5K_PHY_FAST_ADC, 0x00000001, AR5K_INI_WRITE }, + { 0xa250, 0x0000a000, AR5K_INI_WRITE }, + { AR5K_PHY_BLUETOOTH, 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHY_TPC_RG1, 0x0cc75380, AR5K_INI_WRITE }, + { 0xa25c, 0x0f0f0f01, AR5K_INI_WRITE }, + { 0xa260, 0x5f690f01, AR5K_INI_WRITE }, + { 0xa264, 0x00418a11, AR5K_INI_WRITE }, + { 0xa268, 0x00000000, AR5K_INI_WRITE }, + { AR5K_PHY_TPC_RG5, 0x0c30c166, AR5K_INI_WRITE }, + { 0xa270, 0x00820820, AR5K_INI_WRITE }, + { 0xa274, 0x081a3caa, AR5K_INI_WRITE }, + { 0xa278, 0x1ce739ce, AR5K_INI_WRITE }, + { 0xa27c, 0x051701ce, AR5K_INI_WRITE }, + { 0xa300, 0x16010000, AR5K_INI_WRITE }, + { 0xa304, 0x2c032402, AR5K_INI_WRITE }, + { 0xa308, 0x48433e42, AR5K_INI_WRITE }, + { 0xa30c, 0x5a0f500b, AR5K_INI_WRITE }, + { 0xa310, 0x6c4b624a, AR5K_INI_WRITE }, + { 0xa314, 0x7e8b748a, AR5K_INI_WRITE }, + { 0xa318, 0x96cf8ccb, AR5K_INI_WRITE }, + { 0xa31c, 0xa34f9d0f, AR5K_INI_WRITE }, + { 0xa320, 0xa7cfa58f, AR5K_INI_WRITE }, + { 0xa348, 0x3fffffff, AR5K_INI_WRITE }, + { 0xa34c, 0x3fffffff, AR5K_INI_WRITE }, + { 0xa350, 0x3fffffff, AR5K_INI_WRITE }, + { 0xa354, 0x0003ffff, AR5K_INI_WRITE }, + { 0xa358, 0x79a8aa1f, AR5K_INI_WRITE }, + { 0xa35c, 0x066c420f, AR5K_INI_WRITE }, + { 0xa360, 0x0f282207, AR5K_INI_WRITE }, + { 0xa364, 0x17601685, AR5K_INI_WRITE }, + { 0xa368, 0x1f801104, AR5K_INI_WRITE }, + { 0xa36c, 0x37a00c03, AR5K_INI_WRITE }, + { 0xa370, 0x3fc40883, AR5K_INI_WRITE }, + { 0xa374, 0x57c00803, AR5K_INI_WRITE }, + { 0xa378, 0x5fd80682, AR5K_INI_WRITE }, + { 0xa37c, 0x7fe00482, AR5K_INI_WRITE }, + { 0xa380, 0x7f3c7bba, AR5K_INI_WRITE }, + { 0xa384, 0xf3307ff0, AR5K_INI_WRITE }, +}; + +/* + * Initial BaseBand Gain settings for RF5111/5112 (AR5210 comes with + * RF5110 only so initial BB Gain settings are included in AR5K_AR5210_INI) + */ + +/* RF5111 Initial BaseBand Gain settings */ +static const struct ath5k_ini rf5111_ini_bbgain[] = { + { AR5K_BB_GAIN(0), 0x00000000, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(1), 0x00000020, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(2), 0x00000010, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(3), 0x00000030, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(4), 0x00000008, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(5), 0x00000028, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(6), 0x00000004, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(7), 0x00000024, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(8), 0x00000014, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(9), 0x00000034, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(10), 0x0000000c, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(11), 0x0000002c, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(12), 0x00000002, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(13), 0x00000022, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(14), 0x00000012, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(15), 0x00000032, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(16), 0x0000000a, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(17), 0x0000002a, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(18), 0x00000006, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(19), 0x00000026, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(20), 0x00000016, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(21), 0x00000036, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(22), 0x0000000e, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(23), 0x0000002e, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(24), 0x00000001, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(25), 0x00000021, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(26), 0x00000011, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(27), 0x00000031, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(28), 0x00000009, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(29), 0x00000029, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(30), 0x00000005, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(31), 0x00000025, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(32), 0x00000015, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(33), 0x00000035, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(34), 0x0000000d, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(35), 0x0000002d, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(36), 0x00000003, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(37), 0x00000023, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(38), 0x00000013, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(39), 0x00000033, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(40), 0x0000000b, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(41), 0x0000002b, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(42), 0x0000002b, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(43), 0x0000002b, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(44), 0x0000002b, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(45), 0x0000002b, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(46), 0x0000002b, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(47), 0x0000002b, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(48), 0x0000002b, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(49), 0x0000002b, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(50), 0x0000002b, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(51), 0x0000002b, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(52), 0x0000002b, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(53), 0x0000002b, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(54), 0x0000002b, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(55), 0x0000002b, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(56), 0x0000002b, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(57), 0x0000002b, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(58), 0x0000002b, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(59), 0x0000002b, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(60), 0x0000002b, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(61), 0x0000002b, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(62), 0x00000002, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(63), 0x00000016, AR5K_INI_WRITE }, +}; + +/* RF5112 Initial BaseBand Gain settings (Same for RF5413/5414+) */ +static const struct ath5k_ini rf5112_ini_bbgain[] = { + { AR5K_BB_GAIN(0), 0x00000000, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(1), 0x00000001, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(2), 0x00000002, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(3), 0x00000003, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(4), 0x00000004, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(5), 0x00000005, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(6), 0x00000008, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(7), 0x00000009, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(8), 0x0000000a, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(9), 0x0000000b, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(10), 0x0000000c, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(11), 0x0000000d, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(12), 0x00000010, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(13), 0x00000011, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(14), 0x00000012, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(15), 0x00000013, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(16), 0x00000014, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(17), 0x00000015, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(18), 0x00000018, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(19), 0x00000019, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(20), 0x0000001a, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(21), 0x0000001b, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(22), 0x0000001c, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(23), 0x0000001d, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(24), 0x00000020, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(25), 0x00000021, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(26), 0x00000022, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(27), 0x00000023, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(28), 0x00000024, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(29), 0x00000025, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(30), 0x00000028, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(31), 0x00000029, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(32), 0x0000002a, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(33), 0x0000002b, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(34), 0x0000002c, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(35), 0x0000002d, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(36), 0x00000030, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(37), 0x00000031, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(38), 0x00000032, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(39), 0x00000033, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(40), 0x00000034, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(41), 0x00000035, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(42), 0x00000035, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(43), 0x00000035, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(44), 0x00000035, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(45), 0x00000035, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(46), 0x00000035, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(47), 0x00000035, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(48), 0x00000035, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(49), 0x00000035, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(50), 0x00000035, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(51), 0x00000035, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(52), 0x00000035, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(53), 0x00000035, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(54), 0x00000035, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(55), 0x00000035, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(56), 0x00000035, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(57), 0x00000035, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(58), 0x00000035, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(59), 0x00000035, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(60), 0x00000035, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(61), 0x00000035, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(62), 0x00000010, AR5K_INI_WRITE }, + { AR5K_BB_GAIN(63), 0x0000001a, AR5K_INI_WRITE }, +}; + + +/* + * Write initial register dump + */ +static void ath5k_hw_ini_registers(struct ath5k_hw *ah, unsigned int size, + const struct ath5k_ini *ini_regs, int change_channel) +{ + unsigned int i; + + /* Write initial registers */ + for (i = 0; i < size; i++) { + /* On channel change there is + * no need to mess with PCU */ + if (change_channel && + ini_regs[i].ini_register >= AR5K_PCU_MIN && + ini_regs[i].ini_register <= AR5K_PCU_MAX) + continue; + + switch (ini_regs[i].ini_mode) { + case AR5K_INI_READ: + /* Cleared on read */ + ath5k_hw_reg_read(ah, ini_regs[i].ini_register); + break; + case AR5K_INI_WRITE: + default: + AR5K_REG_WAIT(i); + ath5k_hw_reg_write(ah, ini_regs[i].ini_value, + ini_regs[i].ini_register); + } + } +} + +static void ath5k_hw_ini_mode_registers(struct ath5k_hw *ah, + unsigned int size, const struct ath5k_ini_mode *ini_mode, + u8 mode) +{ + unsigned int i; + + for (i = 0; i < size; i++) { + AR5K_REG_WAIT(i); + ath5k_hw_reg_write(ah, ini_mode[i].mode_value[mode], + (u32)ini_mode[i].mode_register); + } +} + +int ath5k_hw_write_initvals(struct ath5k_hw *ah, u8 mode, int change_channel) +{ + /* + * Write initial register settings + */ + + /* For AR5212 and combatible */ + if (ah->ah_version == AR5K_AR5212) { + + /* First set of mode-specific settings */ + ath5k_hw_ini_mode_registers(ah, + ARRAY_SIZE(ar5212_ini_mode_start), + ar5212_ini_mode_start, mode); + + /* + * Write initial settings common for all modes + */ + ath5k_hw_ini_registers(ah, ARRAY_SIZE(ar5212_ini_common_start), + ar5212_ini_common_start, change_channel); + + /* Second set of mode-specific settings */ + switch (ah->ah_radio) { + case AR5K_RF5111: + + ath5k_hw_ini_mode_registers(ah, + ARRAY_SIZE(rf5111_ini_mode_end), + rf5111_ini_mode_end, mode); + + ath5k_hw_ini_registers(ah, + ARRAY_SIZE(rf5111_ini_common_end), + rf5111_ini_common_end, change_channel); + + /* Baseband gain table */ + ath5k_hw_ini_registers(ah, + ARRAY_SIZE(rf5111_ini_bbgain), + rf5111_ini_bbgain, change_channel); + + break; + case AR5K_RF5112: + + ath5k_hw_ini_mode_registers(ah, + ARRAY_SIZE(rf5112_ini_mode_end), + rf5112_ini_mode_end, mode); + + ath5k_hw_ini_registers(ah, + ARRAY_SIZE(rf5112_ini_common_end), + rf5112_ini_common_end, change_channel); + + ath5k_hw_ini_registers(ah, + ARRAY_SIZE(rf5112_ini_bbgain), + rf5112_ini_bbgain, change_channel); + + break; + case AR5K_RF5413: + + ath5k_hw_ini_mode_registers(ah, + ARRAY_SIZE(rf5413_ini_mode_end), + rf5413_ini_mode_end, mode); + + ath5k_hw_ini_registers(ah, + ARRAY_SIZE(rf5413_ini_common_end), + rf5413_ini_common_end, change_channel); + + ath5k_hw_ini_registers(ah, + ARRAY_SIZE(rf5112_ini_bbgain), + rf5112_ini_bbgain, change_channel); + + break; + case AR5K_RF2316: + case AR5K_RF2413: + + ath5k_hw_ini_mode_registers(ah, + ARRAY_SIZE(rf2413_ini_mode_end), + rf2413_ini_mode_end, mode); + + ath5k_hw_ini_registers(ah, + ARRAY_SIZE(rf2413_ini_common_end), + rf2413_ini_common_end, change_channel); + + /* Override settings from rf2413_ini_common_end */ + if (ah->ah_radio == AR5K_RF2316) { + ath5k_hw_reg_write(ah, 0x00004000, + AR5K_PHY_AGC); + ath5k_hw_reg_write(ah, 0x081b7caa, + 0xa274); + } + + ath5k_hw_ini_registers(ah, + ARRAY_SIZE(rf5112_ini_bbgain), + rf5112_ini_bbgain, change_channel); + break; + case AR5K_RF2317: + case AR5K_RF2425: + + ath5k_hw_ini_mode_registers(ah, + ARRAY_SIZE(rf2425_ini_mode_end), + rf2425_ini_mode_end, mode); + + ath5k_hw_ini_registers(ah, + ARRAY_SIZE(rf2425_ini_common_end), + rf2425_ini_common_end, change_channel); + + ath5k_hw_ini_registers(ah, + ARRAY_SIZE(rf5112_ini_bbgain), + rf5112_ini_bbgain, change_channel); + break; + default: + return -EINVAL; + + } + + /* For AR5211 */ + } else if (ah->ah_version == AR5K_AR5211) { + + /* AR5K_MODE_11B */ + if (mode > 2) { + DBG("ath5k: unsupported channel mode %d\n", mode); + return -EINVAL; + } + + /* Mode-specific settings */ + ath5k_hw_ini_mode_registers(ah, ARRAY_SIZE(ar5211_ini_mode), + ar5211_ini_mode, mode); + + /* + * Write initial settings common for all modes + */ + ath5k_hw_ini_registers(ah, ARRAY_SIZE(ar5211_ini), + ar5211_ini, change_channel); + + /* AR5211 only comes with 5111 */ + + /* Baseband gain table */ + ath5k_hw_ini_registers(ah, ARRAY_SIZE(rf5111_ini_bbgain), + rf5111_ini_bbgain, change_channel); + /* For AR5210 (for mode settings check out ath5k_hw_reset_tx_queue) */ + } else if (ah->ah_version == AR5K_AR5210) { + ath5k_hw_ini_registers(ah, ARRAY_SIZE(ar5210_ini), + ar5210_ini, change_channel); + } + + return 0; +} diff --git a/gpxe/src/drivers/net/ath5k/ath5k_pcu.c b/gpxe/src/drivers/net/ath5k/ath5k_pcu.c new file mode 100644 index 00000000..d3e144c4 --- /dev/null +++ b/gpxe/src/drivers/net/ath5k/ath5k_pcu.c @@ -0,0 +1,534 @@ +/* + * Copyright (c) 2004-2008 Reyk Floeter <reyk@openbsd.org> + * Copyright (c) 2006-2008 Nick Kossifidis <mickflemm@gmail.com> + * Copyright (c) 2007-2008 Matthew W. S. Bell <mentor@madwifi.org> + * Copyright (c) 2007-2008 Luis Rodriguez <mcgrof@winlab.rutgers.edu> + * Copyright (c) 2007-2008 Pavel Roskin <proski@gnu.org> + * Copyright (c) 2007-2008 Jiri Slaby <jirislaby@gmail.com> + * + * Lightly modified for gPXE, July 2009, by Joshua Oreman <oremanj@rwcr.net>. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +FILE_LICENCE ( MIT ); + +/*********************************\ +* Protocol Control Unit Functions * +\*********************************/ + +#include "ath5k.h" +#include "reg.h" +#include "base.h" + +/*******************\ +* Generic functions * +\*******************/ + +/** + * ath5k_hw_set_opmode - Set PCU operating mode + * + * @ah: The &struct ath5k_hw + * + * Initialize PCU for the various operating modes (AP/STA etc) + * + * For gPXE we always assume STA mode. + */ +int ath5k_hw_set_opmode(struct ath5k_hw *ah) +{ + u32 pcu_reg, beacon_reg, low_id, high_id; + + + /* Preserve rest settings */ + pcu_reg = ath5k_hw_reg_read(ah, AR5K_STA_ID1) & 0xffff0000; + pcu_reg &= ~(AR5K_STA_ID1_ADHOC | AR5K_STA_ID1_AP + | AR5K_STA_ID1_KEYSRCH_MODE + | (ah->ah_version == AR5K_AR5210 ? + (AR5K_STA_ID1_PWR_SV | AR5K_STA_ID1_NO_PSPOLL) : 0)); + + beacon_reg = 0; + + pcu_reg |= AR5K_STA_ID1_KEYSRCH_MODE + | (ah->ah_version == AR5K_AR5210 ? + AR5K_STA_ID1_PWR_SV : 0); + + /* + * Set PCU registers + */ + low_id = AR5K_LOW_ID(ah->ah_sta_id); + high_id = AR5K_HIGH_ID(ah->ah_sta_id); + ath5k_hw_reg_write(ah, low_id, AR5K_STA_ID0); + ath5k_hw_reg_write(ah, pcu_reg | high_id, AR5K_STA_ID1); + + /* + * Set Beacon Control Register on 5210 + */ + if (ah->ah_version == AR5K_AR5210) + ath5k_hw_reg_write(ah, beacon_reg, AR5K_BCR); + + return 0; +} + +/** + * ath5k_hw_set_ack_bitrate - set bitrate for ACKs + * + * @ah: The &struct ath5k_hw + * @high: Flag to determine if we want to use high transmition rate + * for ACKs or not + * + * If high flag is set, we tell hw to use a set of control rates based on + * the current transmition rate (check out control_rates array inside reset.c). + * If not hw just uses the lowest rate available for the current modulation + * scheme being used (1Mbit for CCK and 6Mbits for OFDM). + */ +void ath5k_hw_set_ack_bitrate_high(struct ath5k_hw *ah, int high) +{ + if (ah->ah_version != AR5K_AR5212) + return; + else { + u32 val = AR5K_STA_ID1_BASE_RATE_11B | AR5K_STA_ID1_ACKCTS_6MB; + if (high) + AR5K_REG_ENABLE_BITS(ah, AR5K_STA_ID1, val); + else + AR5K_REG_DISABLE_BITS(ah, AR5K_STA_ID1, val); + } +} + + +/******************\ +* ACK/CTS Timeouts * +\******************/ + +/** + * ath5k_hw_het_ack_timeout - Get ACK timeout from PCU in usec + * + * @ah: The &struct ath5k_hw + */ +unsigned int ath5k_hw_get_ack_timeout(struct ath5k_hw *ah) +{ + return ath5k_hw_clocktoh(AR5K_REG_MS(ath5k_hw_reg_read(ah, + AR5K_TIME_OUT), AR5K_TIME_OUT_ACK), ah->ah_turbo); +} + +/** + * ath5k_hw_set_ack_timeout - Set ACK timeout on PCU + * + * @ah: The &struct ath5k_hw + * @timeout: Timeout in usec + */ +int ath5k_hw_set_ack_timeout(struct ath5k_hw *ah, unsigned int timeout) +{ + if (ath5k_hw_clocktoh(AR5K_REG_MS(0xffffffff, AR5K_TIME_OUT_ACK), + ah->ah_turbo) <= timeout) + return -EINVAL; + + AR5K_REG_WRITE_BITS(ah, AR5K_TIME_OUT, AR5K_TIME_OUT_ACK, + ath5k_hw_htoclock(timeout, ah->ah_turbo)); + + return 0; +} + +/** + * ath5k_hw_get_cts_timeout - Get CTS timeout from PCU in usec + * + * @ah: The &struct ath5k_hw + */ +unsigned int ath5k_hw_get_cts_timeout(struct ath5k_hw *ah) +{ + return ath5k_hw_clocktoh(AR5K_REG_MS(ath5k_hw_reg_read(ah, + AR5K_TIME_OUT), AR5K_TIME_OUT_CTS), ah->ah_turbo); +} + +/** + * ath5k_hw_set_cts_timeout - Set CTS timeout on PCU + * + * @ah: The &struct ath5k_hw + * @timeout: Timeout in usec + */ +int ath5k_hw_set_cts_timeout(struct ath5k_hw *ah, unsigned int timeout) +{ + if (ath5k_hw_clocktoh(AR5K_REG_MS(0xffffffff, AR5K_TIME_OUT_CTS), + ah->ah_turbo) <= timeout) + return -EINVAL; + + AR5K_REG_WRITE_BITS(ah, AR5K_TIME_OUT, AR5K_TIME_OUT_CTS, + ath5k_hw_htoclock(timeout, ah->ah_turbo)); + + return 0; +} + + +/****************\ +* BSSID handling * +\****************/ + +/** + * ath5k_hw_get_lladdr - Get station id + * + * @ah: The &struct ath5k_hw + * @mac: The card's mac address + * + * Initialize ah->ah_sta_id using the mac address provided + * (just a memcpy). + * + * TODO: Remove it once we merge ath5k_softc and ath5k_hw + */ +void ath5k_hw_get_lladdr(struct ath5k_hw *ah, u8 *mac) +{ + memcpy(mac, ah->ah_sta_id, ETH_ALEN); +} + +/** + * ath5k_hw_set_lladdr - Set station id + * + * @ah: The &struct ath5k_hw + * @mac: The card's mac address + * + * Set station id on hw using the provided mac address + */ +int ath5k_hw_set_lladdr(struct ath5k_hw *ah, const u8 *mac) +{ + u32 low_id, high_id; + u32 pcu_reg; + + /* Set new station ID */ + memcpy(ah->ah_sta_id, mac, ETH_ALEN); + + pcu_reg = ath5k_hw_reg_read(ah, AR5K_STA_ID1) & 0xffff0000; + + low_id = AR5K_LOW_ID(mac); + high_id = AR5K_HIGH_ID(mac); + + ath5k_hw_reg_write(ah, low_id, AR5K_STA_ID0); + ath5k_hw_reg_write(ah, pcu_reg | high_id, AR5K_STA_ID1); + + return 0; +} + +/** + * ath5k_hw_set_associd - Set BSSID for association + * + * @ah: The &struct ath5k_hw + * @bssid: BSSID + * @assoc_id: Assoc id + * + * Sets the BSSID which trigers the "SME Join" operation + */ +void ath5k_hw_set_associd(struct ath5k_hw *ah, const u8 *bssid, u16 assoc_id) +{ + u32 low_id, high_id; + + /* + * Set simple BSSID mask on 5212 + */ + if (ah->ah_version == AR5K_AR5212) { + ath5k_hw_reg_write(ah, AR5K_LOW_ID(ah->ah_bssid_mask), + AR5K_BSS_IDM0); + ath5k_hw_reg_write(ah, AR5K_HIGH_ID(ah->ah_bssid_mask), + AR5K_BSS_IDM1); + } + + /* + * Set BSSID which triggers the "SME Join" operation + */ + low_id = AR5K_LOW_ID(bssid); + high_id = AR5K_HIGH_ID(bssid); + ath5k_hw_reg_write(ah, low_id, AR5K_BSS_ID0); + ath5k_hw_reg_write(ah, high_id | ((assoc_id & 0x3fff) << + AR5K_BSS_ID1_AID_S), AR5K_BSS_ID1); +} + +/** + * ath5k_hw_set_bssid_mask - filter out bssids we listen + * + * @ah: the &struct ath5k_hw + * @mask: the bssid_mask, a u8 array of size ETH_ALEN + * + * BSSID masking is a method used by AR5212 and newer hardware to inform PCU + * which bits of the interface's MAC address should be looked at when trying + * to decide which packets to ACK. In station mode and AP mode with a single + * BSS every bit matters since we lock to only one BSS. In AP mode with + * multiple BSSes (virtual interfaces) not every bit matters because hw must + * accept frames for all BSSes and so we tweak some bits of our mac address + * in order to have multiple BSSes. + * + * NOTE: This is a simple filter and does *not* filter out all + * relevant frames. Some frames that are not for us might get ACKed from us + * by PCU because they just match the mask. + * + * When handling multiple BSSes you can get the BSSID mask by computing the + * set of ~ ( MAC XOR BSSID ) for all bssids we handle. + * + * When you do this you are essentially computing the common bits of all your + * BSSes. Later it is assumed the harware will "and" (&) the BSSID mask with + * the MAC address to obtain the relevant bits and compare the result with + * (frame's BSSID & mask) to see if they match. + */ +/* + * Simple example: on your card you have have two BSSes you have created with + * BSSID-01 and BSSID-02. Lets assume BSSID-01 will not use the MAC address. + * There is another BSSID-03 but you are not part of it. For simplicity's sake, + * assuming only 4 bits for a mac address and for BSSIDs you can then have: + * + * \ + * MAC: 0001 | + * BSSID-01: 0100 | --> Belongs to us + * BSSID-02: 1001 | + * / + * ------------------- + * BSSID-03: 0110 | --> External + * ------------------- + * + * Our bssid_mask would then be: + * + * On loop iteration for BSSID-01: + * ~(0001 ^ 0100) -> ~(0101) + * -> 1010 + * bssid_mask = 1010 + * + * On loop iteration for BSSID-02: + * bssid_mask &= ~(0001 ^ 1001) + * bssid_mask = (1010) & ~(0001 ^ 1001) + * bssid_mask = (1010) & ~(1001) + * bssid_mask = (1010) & (0110) + * bssid_mask = 0010 + * + * A bssid_mask of 0010 means "only pay attention to the second least + * significant bit". This is because its the only bit common + * amongst the MAC and all BSSIDs we support. To findout what the real + * common bit is we can simply "&" the bssid_mask now with any BSSID we have + * or our MAC address (we assume the hardware uses the MAC address). + * + * Now, suppose there's an incoming frame for BSSID-03: + * + * IFRAME-01: 0110 + * + * An easy eye-inspeciton of this already should tell you that this frame + * will not pass our check. This is beacuse the bssid_mask tells the + * hardware to only look at the second least significant bit and the + * common bit amongst the MAC and BSSIDs is 0, this frame has the 2nd LSB + * as 1, which does not match 0. + * + * So with IFRAME-01 we *assume* the hardware will do: + * + * allow = (IFRAME-01 & bssid_mask) == (bssid_mask & MAC) ? 1 : 0; + * --> allow = (0110 & 0010) == (0010 & 0001) ? 1 : 0; + * --> allow = (0010) == 0000 ? 1 : 0; + * --> allow = 0 + * + * Lets now test a frame that should work: + * + * IFRAME-02: 0001 (we should allow) + * + * allow = (0001 & 1010) == 1010 + * + * allow = (IFRAME-02 & bssid_mask) == (bssid_mask & MAC) ? 1 : 0; + * --> allow = (0001 & 0010) == (0010 & 0001) ? 1 :0; + * --> allow = (0010) == (0010) + * --> allow = 1 + * + * Other examples: + * + * IFRAME-03: 0100 --> allowed + * IFRAME-04: 1001 --> allowed + * IFRAME-05: 1101 --> allowed but its not for us!!! + * + */ +int ath5k_hw_set_bssid_mask(struct ath5k_hw *ah, const u8 *mask) +{ + u32 low_id, high_id; + + /* Cache bssid mask so that we can restore it + * on reset */ + memcpy(ah->ah_bssid_mask, mask, ETH_ALEN); + if (ah->ah_version == AR5K_AR5212) { + low_id = AR5K_LOW_ID(mask); + high_id = AR5K_HIGH_ID(mask); + + ath5k_hw_reg_write(ah, low_id, AR5K_BSS_IDM0); + ath5k_hw_reg_write(ah, high_id, AR5K_BSS_IDM1); + + return 0; + } + + return -EIO; +} + + +/************\ +* RX Control * +\************/ + +/** + * ath5k_hw_start_rx_pcu - Start RX engine + * + * @ah: The &struct ath5k_hw + * + * Starts RX engine on PCU so that hw can process RXed frames + * (ACK etc). + * + * NOTE: RX DMA should be already enabled using ath5k_hw_start_rx_dma + * TODO: Init ANI here + */ +void ath5k_hw_start_rx_pcu(struct ath5k_hw *ah) +{ + AR5K_REG_DISABLE_BITS(ah, AR5K_DIAG_SW, AR5K_DIAG_SW_DIS_RX); +} + +/** + * at5k_hw_stop_rx_pcu - Stop RX engine + * + * @ah: The &struct ath5k_hw + * + * Stops RX engine on PCU + * + * TODO: Detach ANI here + */ +void ath5k_hw_stop_rx_pcu(struct ath5k_hw *ah) +{ + AR5K_REG_ENABLE_BITS(ah, AR5K_DIAG_SW, AR5K_DIAG_SW_DIS_RX); +} + +/* + * Set multicast filter + */ +void ath5k_hw_set_mcast_filter(struct ath5k_hw *ah, u32 filter0, u32 filter1) +{ + /* Set the multicat filter */ + ath5k_hw_reg_write(ah, filter0, AR5K_MCAST_FILTER0); + ath5k_hw_reg_write(ah, filter1, AR5K_MCAST_FILTER1); +} + +/** + * ath5k_hw_get_rx_filter - Get current rx filter + * + * @ah: The &struct ath5k_hw + * + * Returns the RX filter by reading rx filter and + * phy error filter registers. RX filter is used + * to set the allowed frame types that PCU will accept + * and pass to the driver. For a list of frame types + * check out reg.h. + */ +u32 ath5k_hw_get_rx_filter(struct ath5k_hw *ah) +{ + u32 data, filter = 0; + + filter = ath5k_hw_reg_read(ah, AR5K_RX_FILTER); + + /*Radar detection for 5212*/ + if (ah->ah_version == AR5K_AR5212) { + data = ath5k_hw_reg_read(ah, AR5K_PHY_ERR_FIL); + + if (data & AR5K_PHY_ERR_FIL_RADAR) + filter |= AR5K_RX_FILTER_RADARERR; + if (data & (AR5K_PHY_ERR_FIL_OFDM | AR5K_PHY_ERR_FIL_CCK)) + filter |= AR5K_RX_FILTER_PHYERR; + } + + return filter; +} + +/** + * ath5k_hw_set_rx_filter - Set rx filter + * + * @ah: The &struct ath5k_hw + * @filter: RX filter mask (see reg.h) + * + * Sets RX filter register and also handles PHY error filter + * register on 5212 and newer chips so that we have proper PHY + * error reporting. + */ +void ath5k_hw_set_rx_filter(struct ath5k_hw *ah, u32 filter) +{ + u32 data = 0; + + /* Set PHY error filter register on 5212*/ + if (ah->ah_version == AR5K_AR5212) { + if (filter & AR5K_RX_FILTER_RADARERR) + data |= AR5K_PHY_ERR_FIL_RADAR; + if (filter & AR5K_RX_FILTER_PHYERR) + data |= AR5K_PHY_ERR_FIL_OFDM | AR5K_PHY_ERR_FIL_CCK; + } + + /* + * The AR5210 uses promiscous mode to detect radar activity + */ + if (ah->ah_version == AR5K_AR5210 && + (filter & AR5K_RX_FILTER_RADARERR)) { + filter &= ~AR5K_RX_FILTER_RADARERR; + filter |= AR5K_RX_FILTER_PROM; + } + + /*Zero length DMA (phy error reporting) */ + if (data) + AR5K_REG_ENABLE_BITS(ah, AR5K_RXCFG, AR5K_RXCFG_ZLFDMA); + else + AR5K_REG_DISABLE_BITS(ah, AR5K_RXCFG, AR5K_RXCFG_ZLFDMA); + + /*Write RX Filter register*/ + ath5k_hw_reg_write(ah, filter & 0xff, AR5K_RX_FILTER); + + /*Write PHY error filter register on 5212*/ + if (ah->ah_version == AR5K_AR5212) + ath5k_hw_reg_write(ah, data, AR5K_PHY_ERR_FIL); + +} + +/*********************\ +* Key table functions * +\*********************/ + +/* + * Reset a key entry on the table + */ +int ath5k_hw_reset_key(struct ath5k_hw *ah, u16 entry) +{ + unsigned int i, type; + u16 micentry = entry + AR5K_KEYTABLE_MIC_OFFSET; + + type = ath5k_hw_reg_read(ah, AR5K_KEYTABLE_TYPE(entry)); + + for (i = 0; i < AR5K_KEYCACHE_SIZE; i++) + ath5k_hw_reg_write(ah, 0, AR5K_KEYTABLE_OFF(entry, i)); + + /* Reset associated MIC entry if TKIP + * is enabled located at offset (entry + 64) */ + if (type == AR5K_KEYTABLE_TYPE_TKIP) { + for (i = 0; i < AR5K_KEYCACHE_SIZE / 2 ; i++) + ath5k_hw_reg_write(ah, 0, + AR5K_KEYTABLE_OFF(micentry, i)); + } + + /* + * Set NULL encryption on AR5212+ + * + * Note: AR5K_KEYTABLE_TYPE -> AR5K_KEYTABLE_OFF(entry, 5) + * AR5K_KEYTABLE_TYPE_NULL -> 0x00000007 + * + * Note2: Windows driver (ndiswrapper) sets this to + * 0x00000714 instead of 0x00000007 + */ + if (ah->ah_version >= AR5K_AR5211) { + ath5k_hw_reg_write(ah, AR5K_KEYTABLE_TYPE_NULL, + AR5K_KEYTABLE_TYPE(entry)); + + if (type == AR5K_KEYTABLE_TYPE_TKIP) { + ath5k_hw_reg_write(ah, AR5K_KEYTABLE_TYPE_NULL, + AR5K_KEYTABLE_TYPE(micentry)); + } + } + + return 0; +} diff --git a/gpxe/src/drivers/net/ath5k/ath5k_phy.c b/gpxe/src/drivers/net/ath5k/ath5k_phy.c new file mode 100644 index 00000000..8856fa33 --- /dev/null +++ b/gpxe/src/drivers/net/ath5k/ath5k_phy.c @@ -0,0 +1,2586 @@ +/* + * PHY functions + * + * Copyright (c) 2004-2007 Reyk Floeter <reyk@openbsd.org> + * Copyright (c) 2006-2009 Nick Kossifidis <mickflemm@gmail.com> + * Copyright (c) 2007-2008 Jiri Slaby <jirislaby@gmail.com> + * Copyright (c) 2008-2009 Felix Fietkau <nbd@openwrt.org> + * + * Lightly modified for gPXE, July 2009, by Joshua Oreman <oremanj@rwcr.net>. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +FILE_LICENCE ( MIT ); + +#define _ATH5K_PHY + +#include <unistd.h> +#include <stdlib.h> + +#include "ath5k.h" +#include "reg.h" +#include "base.h" +#include "rfbuffer.h" +#include "rfgain.h" + +static inline int min(int x, int y) +{ + return (x < y) ? x : y; +} + +static inline int max(int x, int y) +{ + return (x > y) ? x : y; +} + +/* + * Used to modify RF Banks before writing them to AR5K_RF_BUFFER + */ +static unsigned int ath5k_hw_rfb_op(struct ath5k_hw *ah, + const struct ath5k_rf_reg *rf_regs, + u32 val, u8 reg_id, int set) +{ + const struct ath5k_rf_reg *rfreg = NULL; + u8 offset, bank, num_bits, col, position; + u16 entry; + u32 mask, data, last_bit, bits_shifted, first_bit; + u32 *rfb; + s32 bits_left; + unsigned i; + + data = 0; + rfb = ah->ah_rf_banks; + + for (i = 0; i < ah->ah_rf_regs_count; i++) { + if (rf_regs[i].index == reg_id) { + rfreg = &rf_regs[i]; + break; + } + } + + if (rfb == NULL || rfreg == NULL) { + DBG("ath5k: RF register not found!\n"); + /* should not happen */ + return 0; + } + + bank = rfreg->bank; + num_bits = rfreg->field.len; + first_bit = rfreg->field.pos; + col = rfreg->field.col; + + /* first_bit is an offset from bank's + * start. Since we have all banks on + * the same array, we use this offset + * to mark each bank's start */ + offset = ah->ah_offset[bank]; + + /* Boundary check */ + if (!(col <= 3 && num_bits <= 32 && first_bit + num_bits <= 319)) { + DBG("ath5k: RF invalid values at offset %d\n", offset); + return 0; + } + + entry = ((first_bit - 1) / 8) + offset; + position = (first_bit - 1) % 8; + + if (set) + data = ath5k_hw_bitswap(val, num_bits); + + for (bits_shifted = 0, bits_left = num_bits; bits_left > 0; + position = 0, entry++) { + + last_bit = (position + bits_left > 8) ? 8 : + position + bits_left; + + mask = (((1 << last_bit) - 1) ^ ((1 << position) - 1)) << + (col * 8); + + if (set) { + rfb[entry] &= ~mask; + rfb[entry] |= ((data << position) << (col * 8)) & mask; + data >>= (8 - position); + } else { + data |= (((rfb[entry] & mask) >> (col * 8)) >> position) + << bits_shifted; + bits_shifted += last_bit - position; + } + + bits_left -= 8 - position; + } + + data = set ? 1 : ath5k_hw_bitswap(data, num_bits); + + return data; +} + +/**********************\ +* RF Gain optimization * +\**********************/ + +/* + * This code is used to optimize rf gain on different environments + * (temprature mostly) based on feedback from a power detector. + * + * It's only used on RF5111 and RF5112, later RF chips seem to have + * auto adjustment on hw -notice they have a much smaller BANK 7 and + * no gain optimization ladder-. + * + * For more infos check out this patent doc + * http://www.freepatentsonline.com/7400691.html + * + * This paper describes power drops as seen on the receiver due to + * probe packets + * http://www.cnri.dit.ie/publications/ICT08%20-%20Practical%20Issues + * %20of%20Power%20Control.pdf + * + * And this is the MadWiFi bug entry related to the above + * http://madwifi-project.org/ticket/1659 + * with various measurements and diagrams + * + * TODO: Deal with power drops due to probes by setting an apropriate + * tx power on the probe packets ! Make this part of the calibration process. + */ + +/* Initialize ah_gain durring attach */ +int ath5k_hw_rfgain_opt_init(struct ath5k_hw *ah) +{ + /* Initialize the gain optimization values */ + switch (ah->ah_radio) { + case AR5K_RF5111: + ah->ah_gain.g_step_idx = rfgain_opt_5111.go_default; + ah->ah_gain.g_low = 20; + ah->ah_gain.g_high = 35; + ah->ah_gain.g_state = AR5K_RFGAIN_ACTIVE; + break; + case AR5K_RF5112: + ah->ah_gain.g_step_idx = rfgain_opt_5112.go_default; + ah->ah_gain.g_low = 20; + ah->ah_gain.g_high = 85; + ah->ah_gain.g_state = AR5K_RFGAIN_ACTIVE; + break; + default: + return -EINVAL; + } + + return 0; +} + +/* Schedule a gain probe check on the next transmited packet. + * That means our next packet is going to be sent with lower + * tx power and a Peak to Average Power Detector (PAPD) will try + * to measure the gain. + * + * TODO: Use propper tx power setting for the probe packet so + * that we don't observe a serious power drop on the receiver + * + * XXX: How about forcing a tx packet (bypassing PCU arbitrator etc) + * just after we enable the probe so that we don't mess with + * standard traffic ? Maybe it's time to use sw interrupts and + * a probe tasklet !!! + */ +static void ath5k_hw_request_rfgain_probe(struct ath5k_hw *ah) +{ + + /* Skip if gain calibration is inactive or + * we already handle a probe request */ + if (ah->ah_gain.g_state != AR5K_RFGAIN_ACTIVE) + return; + + /* Send the packet with 2dB below max power as + * patent doc suggest */ + ath5k_hw_reg_write(ah, AR5K_REG_SM(ah->ah_txpower.txp_max_pwr - 4, + AR5K_PHY_PAPD_PROBE_TXPOWER) | + AR5K_PHY_PAPD_PROBE_TX_NEXT, AR5K_PHY_PAPD_PROBE); + + ah->ah_gain.g_state = AR5K_RFGAIN_READ_REQUESTED; + +} + +/* Calculate gain_F measurement correction + * based on the current step for RF5112 rev. 2 */ +static u32 ath5k_hw_rf_gainf_corr(struct ath5k_hw *ah) +{ + u32 mix, step; + u32 *rf; + const struct ath5k_gain_opt *go; + const struct ath5k_gain_opt_step *g_step; + const struct ath5k_rf_reg *rf_regs; + + /* Only RF5112 Rev. 2 supports it */ + if ((ah->ah_radio != AR5K_RF5112) || + (ah->ah_radio_5ghz_revision <= AR5K_SREV_RAD_5112A)) + return 0; + + go = &rfgain_opt_5112; + rf_regs = rf_regs_5112a; + ah->ah_rf_regs_count = ARRAY_SIZE(rf_regs_5112a); + + g_step = &go->go_step[ah->ah_gain.g_step_idx]; + + if (ah->ah_rf_banks == NULL) + return 0; + + rf = ah->ah_rf_banks; + ah->ah_gain.g_f_corr = 0; + + /* No VGA (Variable Gain Amplifier) override, skip */ + if (ath5k_hw_rfb_op(ah, rf_regs, 0, AR5K_RF_MIXVGA_OVR, 0) != 1) + return 0; + + /* Mix gain stepping */ + step = ath5k_hw_rfb_op(ah, rf_regs, 0, AR5K_RF_MIXGAIN_STEP, 0); + + /* Mix gain override */ + mix = g_step->gos_param[0]; + + switch (mix) { + case 3: + ah->ah_gain.g_f_corr = step * 2; + break; + case 2: + ah->ah_gain.g_f_corr = (step - 5) * 2; + break; + case 1: + ah->ah_gain.g_f_corr = step; + break; + default: + ah->ah_gain.g_f_corr = 0; + break; + } + + return ah->ah_gain.g_f_corr; +} + +/* Check if current gain_F measurement is in the range of our + * power detector windows. If we get a measurement outside range + * we know it's not accurate (detectors can't measure anything outside + * their detection window) so we must ignore it */ +static int ath5k_hw_rf_check_gainf_readback(struct ath5k_hw *ah) +{ + const struct ath5k_rf_reg *rf_regs; + u32 step, mix_ovr, level[4]; + u32 *rf; + + if (ah->ah_rf_banks == NULL) + return 0; + + rf = ah->ah_rf_banks; + + if (ah->ah_radio == AR5K_RF5111) { + + rf_regs = rf_regs_5111; + ah->ah_rf_regs_count = ARRAY_SIZE(rf_regs_5111); + + step = ath5k_hw_rfb_op(ah, rf_regs, 0, AR5K_RF_RFGAIN_STEP, + 0); + + level[0] = 0; + level[1] = (step == 63) ? 50 : step + 4; + level[2] = (step != 63) ? 64 : level[0]; + level[3] = level[2] + 50 ; + + ah->ah_gain.g_high = level[3] - + (step == 63 ? AR5K_GAIN_DYN_ADJUST_HI_MARGIN : -5); + ah->ah_gain.g_low = level[0] + + (step == 63 ? AR5K_GAIN_DYN_ADJUST_LO_MARGIN : 0); + } else { + + rf_regs = rf_regs_5112; + ah->ah_rf_regs_count = ARRAY_SIZE(rf_regs_5112); + + mix_ovr = ath5k_hw_rfb_op(ah, rf_regs, 0, AR5K_RF_MIXVGA_OVR, + 0); + + level[0] = level[2] = 0; + + if (mix_ovr == 1) { + level[1] = level[3] = 83; + } else { + level[1] = level[3] = 107; + ah->ah_gain.g_high = 55; + } + } + + return (ah->ah_gain.g_current >= level[0] && + ah->ah_gain.g_current <= level[1]) || + (ah->ah_gain.g_current >= level[2] && + ah->ah_gain.g_current <= level[3]); +} + +/* Perform gain_F adjustment by choosing the right set + * of parameters from rf gain optimization ladder */ +static s8 ath5k_hw_rf_gainf_adjust(struct ath5k_hw *ah) +{ + const struct ath5k_gain_opt *go; + const struct ath5k_gain_opt_step *g_step; + int ret = 0; + + switch (ah->ah_radio) { + case AR5K_RF5111: + go = &rfgain_opt_5111; + break; + case AR5K_RF5112: + go = &rfgain_opt_5112; + break; + default: + return 0; + } + + g_step = &go->go_step[ah->ah_gain.g_step_idx]; + + if (ah->ah_gain.g_current >= ah->ah_gain.g_high) { + + /* Reached maximum */ + if (ah->ah_gain.g_step_idx == 0) + return -1; + + for (ah->ah_gain.g_target = ah->ah_gain.g_current; + ah->ah_gain.g_target >= ah->ah_gain.g_high && + ah->ah_gain.g_step_idx > 0; + g_step = &go->go_step[ah->ah_gain.g_step_idx]) + ah->ah_gain.g_target -= 2 * + (go->go_step[--(ah->ah_gain.g_step_idx)].gos_gain - + g_step->gos_gain); + + ret = 1; + goto done; + } + + if (ah->ah_gain.g_current <= ah->ah_gain.g_low) { + + /* Reached minimum */ + if (ah->ah_gain.g_step_idx == (go->go_steps_count - 1)) + return -2; + + for (ah->ah_gain.g_target = ah->ah_gain.g_current; + ah->ah_gain.g_target <= ah->ah_gain.g_low && + ah->ah_gain.g_step_idx < go->go_steps_count-1; + g_step = &go->go_step[ah->ah_gain.g_step_idx]) + ah->ah_gain.g_target -= 2 * + (go->go_step[++ah->ah_gain.g_step_idx].gos_gain - + g_step->gos_gain); + + ret = 2; + goto done; + } + +done: + DBG2("ath5k RF adjust: ret %d, gain step %d, current gain %d, " + "target gain %d\n", ret, ah->ah_gain.g_step_idx, + ah->ah_gain.g_current, ah->ah_gain.g_target); + + return ret; +} + +/* Main callback for thermal rf gain calibration engine + * Check for a new gain reading and schedule an adjustment + * if needed. + * + * TODO: Use sw interrupt to schedule reset if gain_F needs + * adjustment */ +enum ath5k_rfgain ath5k_hw_gainf_calibrate(struct ath5k_hw *ah) +{ + u32 data, type; + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + + if (ah->ah_rf_banks == NULL || + ah->ah_gain.g_state == AR5K_RFGAIN_INACTIVE) + return AR5K_RFGAIN_INACTIVE; + + /* No check requested, either engine is inactive + * or an adjustment is already requested */ + if (ah->ah_gain.g_state != AR5K_RFGAIN_READ_REQUESTED) + goto done; + + /* Read the PAPD (Peak to Average Power Detector) + * register */ + data = ath5k_hw_reg_read(ah, AR5K_PHY_PAPD_PROBE); + + /* No probe is scheduled, read gain_F measurement */ + if (!(data & AR5K_PHY_PAPD_PROBE_TX_NEXT)) { + ah->ah_gain.g_current = data >> AR5K_PHY_PAPD_PROBE_GAINF_S; + type = AR5K_REG_MS(data, AR5K_PHY_PAPD_PROBE_TYPE); + + /* If tx packet is CCK correct the gain_F measurement + * by cck ofdm gain delta */ + if (type == AR5K_PHY_PAPD_PROBE_TYPE_CCK) { + if (ah->ah_radio_5ghz_revision >= AR5K_SREV_RAD_5112A) + ah->ah_gain.g_current += + ee->ee_cck_ofdm_gain_delta; + else + ah->ah_gain.g_current += + AR5K_GAIN_CCK_PROBE_CORR; + } + + /* Further correct gain_F measurement for + * RF5112A radios */ + if (ah->ah_radio_5ghz_revision >= AR5K_SREV_RAD_5112A) { + ath5k_hw_rf_gainf_corr(ah); + ah->ah_gain.g_current = + ah->ah_gain.g_current >= ah->ah_gain.g_f_corr ? + (ah->ah_gain.g_current-ah->ah_gain.g_f_corr) : + 0; + } + + /* Check if measurement is ok and if we need + * to adjust gain, schedule a gain adjustment, + * else switch back to the acive state */ + if (ath5k_hw_rf_check_gainf_readback(ah) && + AR5K_GAIN_CHECK_ADJUST(&ah->ah_gain) && + ath5k_hw_rf_gainf_adjust(ah)) { + ah->ah_gain.g_state = AR5K_RFGAIN_NEED_CHANGE; + } else { + ah->ah_gain.g_state = AR5K_RFGAIN_ACTIVE; + } + } + +done: + return ah->ah_gain.g_state; +} + +/* Write initial rf gain table to set the RF sensitivity + * this one works on all RF chips and has nothing to do + * with gain_F calibration */ +int ath5k_hw_rfgain_init(struct ath5k_hw *ah, unsigned int freq) +{ + const struct ath5k_ini_rfgain *ath5k_rfg; + unsigned int i, size; + + switch (ah->ah_radio) { + case AR5K_RF5111: + ath5k_rfg = rfgain_5111; + size = ARRAY_SIZE(rfgain_5111); + break; + case AR5K_RF5112: + ath5k_rfg = rfgain_5112; + size = ARRAY_SIZE(rfgain_5112); + break; + case AR5K_RF2413: + ath5k_rfg = rfgain_2413; + size = ARRAY_SIZE(rfgain_2413); + break; + case AR5K_RF2316: + ath5k_rfg = rfgain_2316; + size = ARRAY_SIZE(rfgain_2316); + break; + case AR5K_RF5413: + ath5k_rfg = rfgain_5413; + size = ARRAY_SIZE(rfgain_5413); + break; + case AR5K_RF2317: + case AR5K_RF2425: + ath5k_rfg = rfgain_2425; + size = ARRAY_SIZE(rfgain_2425); + break; + default: + return -EINVAL; + } + + switch (freq) { + case AR5K_INI_RFGAIN_2GHZ: + case AR5K_INI_RFGAIN_5GHZ: + break; + default: + return -EINVAL; + } + + for (i = 0; i < size; i++) { + AR5K_REG_WAIT(i); + ath5k_hw_reg_write(ah, ath5k_rfg[i].rfg_value[freq], + (u32)ath5k_rfg[i].rfg_register); + } + + return 0; +} + + + +/********************\ +* RF Registers setup * +\********************/ + + +/* + * Setup RF registers by writing rf buffer on hw + */ +int ath5k_hw_rfregs_init(struct ath5k_hw *ah, struct net80211_channel *channel, + unsigned int mode) +{ + const struct ath5k_rf_reg *rf_regs; + const struct ath5k_ini_rfbuffer *ini_rfb; + const struct ath5k_gain_opt *go = NULL; + const struct ath5k_gain_opt_step *g_step; + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + u8 ee_mode = 0; + u32 *rfb; + int obdb = -1, bank = -1; + unsigned i; + + switch (ah->ah_radio) { + case AR5K_RF5111: + rf_regs = rf_regs_5111; + ah->ah_rf_regs_count = ARRAY_SIZE(rf_regs_5111); + ini_rfb = rfb_5111; + ah->ah_rf_banks_size = ARRAY_SIZE(rfb_5111); + go = &rfgain_opt_5111; + break; + case AR5K_RF5112: + if (ah->ah_radio_5ghz_revision >= AR5K_SREV_RAD_5112A) { + rf_regs = rf_regs_5112a; + ah->ah_rf_regs_count = ARRAY_SIZE(rf_regs_5112a); + ini_rfb = rfb_5112a; + ah->ah_rf_banks_size = ARRAY_SIZE(rfb_5112a); + } else { + rf_regs = rf_regs_5112; + ah->ah_rf_regs_count = ARRAY_SIZE(rf_regs_5112); + ini_rfb = rfb_5112; + ah->ah_rf_banks_size = ARRAY_SIZE(rfb_5112); + } + go = &rfgain_opt_5112; + break; + case AR5K_RF2413: + rf_regs = rf_regs_2413; + ah->ah_rf_regs_count = ARRAY_SIZE(rf_regs_2413); + ini_rfb = rfb_2413; + ah->ah_rf_banks_size = ARRAY_SIZE(rfb_2413); + break; + case AR5K_RF2316: + rf_regs = rf_regs_2316; + ah->ah_rf_regs_count = ARRAY_SIZE(rf_regs_2316); + ini_rfb = rfb_2316; + ah->ah_rf_banks_size = ARRAY_SIZE(rfb_2316); + break; + case AR5K_RF5413: + rf_regs = rf_regs_5413; + ah->ah_rf_regs_count = ARRAY_SIZE(rf_regs_5413); + ini_rfb = rfb_5413; + ah->ah_rf_banks_size = ARRAY_SIZE(rfb_5413); + break; + case AR5K_RF2317: + rf_regs = rf_regs_2425; + ah->ah_rf_regs_count = ARRAY_SIZE(rf_regs_2425); + ini_rfb = rfb_2317; + ah->ah_rf_banks_size = ARRAY_SIZE(rfb_2317); + break; + case AR5K_RF2425: + rf_regs = rf_regs_2425; + ah->ah_rf_regs_count = ARRAY_SIZE(rf_regs_2425); + if (ah->ah_mac_srev < AR5K_SREV_AR2417) { + ini_rfb = rfb_2425; + ah->ah_rf_banks_size = ARRAY_SIZE(rfb_2425); + } else { + ini_rfb = rfb_2417; + ah->ah_rf_banks_size = ARRAY_SIZE(rfb_2417); + } + break; + default: + return -EINVAL; + } + + /* If it's the first time we set rf buffer, allocate + * ah->ah_rf_banks based on ah->ah_rf_banks_size + * we set above */ + if (ah->ah_rf_banks == NULL) { + ah->ah_rf_banks = malloc(sizeof(u32) * ah->ah_rf_banks_size); + if (ah->ah_rf_banks == NULL) { + return -ENOMEM; + } + } + + /* Copy values to modify them */ + rfb = ah->ah_rf_banks; + + for (i = 0; i < ah->ah_rf_banks_size; i++) { + if (ini_rfb[i].rfb_bank >= AR5K_MAX_RF_BANKS) { + DBG("ath5k: invalid RF register bank\n"); + return -EINVAL; + } + + /* Bank changed, write down the offset */ + if (bank != ini_rfb[i].rfb_bank) { + bank = ini_rfb[i].rfb_bank; + ah->ah_offset[bank] = i; + } + + rfb[i] = ini_rfb[i].rfb_mode_data[mode]; + } + + /* Set Output and Driver bias current (OB/DB) */ + if (channel->hw_value & CHANNEL_2GHZ) { + + if (channel->hw_value & CHANNEL_CCK) + ee_mode = AR5K_EEPROM_MODE_11B; + else + ee_mode = AR5K_EEPROM_MODE_11G; + + /* For RF511X/RF211X combination we + * use b_OB and b_DB parameters stored + * in eeprom on ee->ee_ob[ee_mode][0] + * + * For all other chips we use OB/DB for 2Ghz + * stored in the b/g modal section just like + * 802.11a on ee->ee_ob[ee_mode][1] */ + if ((ah->ah_radio == AR5K_RF5111) || + (ah->ah_radio == AR5K_RF5112)) + obdb = 0; + else + obdb = 1; + + ath5k_hw_rfb_op(ah, rf_regs, ee->ee_ob[ee_mode][obdb], + AR5K_RF_OB_2GHZ, 1); + + ath5k_hw_rfb_op(ah, rf_regs, ee->ee_db[ee_mode][obdb], + AR5K_RF_DB_2GHZ, 1); + + /* RF5111 always needs OB/DB for 5GHz, even if we use 2GHz */ + } else if ((channel->hw_value & CHANNEL_5GHZ) || + (ah->ah_radio == AR5K_RF5111)) { + + /* For 11a, Turbo and XR we need to choose + * OB/DB based on frequency range */ + ee_mode = AR5K_EEPROM_MODE_11A; + obdb = channel->center_freq >= 5725 ? 3 : + (channel->center_freq >= 5500 ? 2 : + (channel->center_freq >= 5260 ? 1 : + (channel->center_freq > 4000 ? 0 : -1))); + + if (obdb < 0) + return -EINVAL; + + ath5k_hw_rfb_op(ah, rf_regs, ee->ee_ob[ee_mode][obdb], + AR5K_RF_OB_5GHZ, 1); + + ath5k_hw_rfb_op(ah, rf_regs, ee->ee_db[ee_mode][obdb], + AR5K_RF_DB_5GHZ, 1); + } + + g_step = &go->go_step[ah->ah_gain.g_step_idx]; + + /* Bank Modifications (chip-specific) */ + if (ah->ah_radio == AR5K_RF5111) { + + /* Set gain_F settings according to current step */ + if (channel->hw_value & CHANNEL_OFDM) { + + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_FRAME_CTL, + AR5K_PHY_FRAME_CTL_TX_CLIP, + g_step->gos_param[0]); + + ath5k_hw_rfb_op(ah, rf_regs, g_step->gos_param[1], + AR5K_RF_PWD_90, 1); + + ath5k_hw_rfb_op(ah, rf_regs, g_step->gos_param[2], + AR5K_RF_PWD_84, 1); + + ath5k_hw_rfb_op(ah, rf_regs, g_step->gos_param[3], + AR5K_RF_RFGAIN_SEL, 1); + + /* We programmed gain_F parameters, switch back + * to active state */ + ah->ah_gain.g_state = AR5K_RFGAIN_ACTIVE; + + } + + /* Bank 6/7 setup */ + + ath5k_hw_rfb_op(ah, rf_regs, !ee->ee_xpd[ee_mode], + AR5K_RF_PWD_XPD, 1); + + ath5k_hw_rfb_op(ah, rf_regs, ee->ee_x_gain[ee_mode], + AR5K_RF_XPD_GAIN, 1); + + ath5k_hw_rfb_op(ah, rf_regs, ee->ee_i_gain[ee_mode], + AR5K_RF_GAIN_I, 1); + + ath5k_hw_rfb_op(ah, rf_regs, ee->ee_xpd[ee_mode], + AR5K_RF_PLO_SEL, 1); + + /* TODO: Half/quarter channel support */ + } + + if (ah->ah_radio == AR5K_RF5112) { + + /* Set gain_F settings according to current step */ + if (channel->hw_value & CHANNEL_OFDM) { + + ath5k_hw_rfb_op(ah, rf_regs, g_step->gos_param[0], + AR5K_RF_MIXGAIN_OVR, 1); + + ath5k_hw_rfb_op(ah, rf_regs, g_step->gos_param[1], + AR5K_RF_PWD_138, 1); + + ath5k_hw_rfb_op(ah, rf_regs, g_step->gos_param[2], + AR5K_RF_PWD_137, 1); + + ath5k_hw_rfb_op(ah, rf_regs, g_step->gos_param[3], + AR5K_RF_PWD_136, 1); + + ath5k_hw_rfb_op(ah, rf_regs, g_step->gos_param[4], + AR5K_RF_PWD_132, 1); + + ath5k_hw_rfb_op(ah, rf_regs, g_step->gos_param[5], + AR5K_RF_PWD_131, 1); + + ath5k_hw_rfb_op(ah, rf_regs, g_step->gos_param[6], + AR5K_RF_PWD_130, 1); + + /* We programmed gain_F parameters, switch back + * to active state */ + ah->ah_gain.g_state = AR5K_RFGAIN_ACTIVE; + } + + /* Bank 6/7 setup */ + + ath5k_hw_rfb_op(ah, rf_regs, ee->ee_xpd[ee_mode], + AR5K_RF_XPD_SEL, 1); + + if (ah->ah_radio_5ghz_revision < AR5K_SREV_RAD_5112A) { + /* Rev. 1 supports only one xpd */ + ath5k_hw_rfb_op(ah, rf_regs, + ee->ee_x_gain[ee_mode], + AR5K_RF_XPD_GAIN, 1); + + } else { + /* TODO: Set high and low gain bits */ + ath5k_hw_rfb_op(ah, rf_regs, + ee->ee_x_gain[ee_mode], + AR5K_RF_PD_GAIN_LO, 1); + ath5k_hw_rfb_op(ah, rf_regs, + ee->ee_x_gain[ee_mode], + AR5K_RF_PD_GAIN_HI, 1); + + /* Lower synth voltage on Rev 2 */ + ath5k_hw_rfb_op(ah, rf_regs, 2, + AR5K_RF_HIGH_VC_CP, 1); + + ath5k_hw_rfb_op(ah, rf_regs, 2, + AR5K_RF_MID_VC_CP, 1); + + ath5k_hw_rfb_op(ah, rf_regs, 2, + AR5K_RF_LOW_VC_CP, 1); + + ath5k_hw_rfb_op(ah, rf_regs, 2, + AR5K_RF_PUSH_UP, 1); + + /* Decrease power consumption on 5213+ BaseBand */ + if (ah->ah_phy_revision >= AR5K_SREV_PHY_5212A) { + ath5k_hw_rfb_op(ah, rf_regs, 1, + AR5K_RF_PAD2GND, 1); + + ath5k_hw_rfb_op(ah, rf_regs, 1, + AR5K_RF_XB2_LVL, 1); + + ath5k_hw_rfb_op(ah, rf_regs, 1, + AR5K_RF_XB5_LVL, 1); + + ath5k_hw_rfb_op(ah, rf_regs, 1, + AR5K_RF_PWD_167, 1); + + ath5k_hw_rfb_op(ah, rf_regs, 1, + AR5K_RF_PWD_166, 1); + } + } + + ath5k_hw_rfb_op(ah, rf_regs, ee->ee_i_gain[ee_mode], + AR5K_RF_GAIN_I, 1); + + /* TODO: Half/quarter channel support */ + + } + + if (ah->ah_radio == AR5K_RF5413 && + channel->hw_value & CHANNEL_2GHZ) { + + ath5k_hw_rfb_op(ah, rf_regs, 1, AR5K_RF_DERBY_CHAN_SEL_MODE, + 1); + + /* Set optimum value for early revisions (on pci-e chips) */ + if (ah->ah_mac_srev >= AR5K_SREV_AR5424 && + ah->ah_mac_srev < AR5K_SREV_AR5413) + ath5k_hw_rfb_op(ah, rf_regs, ath5k_hw_bitswap(6, 3), + AR5K_RF_PWD_ICLOBUF_2G, 1); + + } + + /* Write RF banks on hw */ + for (i = 0; i < ah->ah_rf_banks_size; i++) { + AR5K_REG_WAIT(i); + ath5k_hw_reg_write(ah, rfb[i], ini_rfb[i].rfb_ctrl_register); + } + + return 0; +} + + +/**************************\ + PHY/RF channel functions +\**************************/ + +/* + * Check if a channel is supported + */ +int ath5k_channel_ok(struct ath5k_hw *ah, u16 freq, unsigned int flags) +{ + /* Check if the channel is in our supported range */ + if (flags & CHANNEL_2GHZ) { + if ((freq >= ah->ah_capabilities.cap_range.range_2ghz_min) && + (freq <= ah->ah_capabilities.cap_range.range_2ghz_max)) + return 1; + } else if (flags & CHANNEL_5GHZ) + if ((freq >= ah->ah_capabilities.cap_range.range_5ghz_min) && + (freq <= ah->ah_capabilities.cap_range.range_5ghz_max)) + return 1; + + return 0; +} + +/* + * Convertion needed for RF5110 + */ +static u32 ath5k_hw_rf5110_chan2athchan(struct net80211_channel *channel) +{ + u32 athchan; + + /* + * Convert IEEE channel/MHz to an internal channel value used + * by the AR5210 chipset. This has not been verified with + * newer chipsets like the AR5212A who have a completely + * different RF/PHY part. + */ + athchan = (ath5k_hw_bitswap((ath5k_freq_to_channel(channel->center_freq) + - 24) / 2, 5) << 1) + | (1 << 6) | 0x1; + return athchan; +} + +/* + * Set channel on RF5110 + */ +static int ath5k_hw_rf5110_channel(struct ath5k_hw *ah, + struct net80211_channel *channel) +{ + u32 data; + + /* + * Set the channel and wait + */ + data = ath5k_hw_rf5110_chan2athchan(channel); + ath5k_hw_reg_write(ah, data, AR5K_RF_BUFFER); + ath5k_hw_reg_write(ah, 0, AR5K_RF_BUFFER_CONTROL_0); + mdelay(1); + + return 0; +} + +/* + * Convertion needed for 5111 + */ +static int ath5k_hw_rf5111_chan2athchan(unsigned int ieee, + struct ath5k_athchan_2ghz *athchan) +{ + int channel; + + /* Cast this value to catch negative channel numbers (>= -19) */ + channel = (int)ieee; + + /* + * Map 2GHz IEEE channel to 5GHz Atheros channel + */ + if (channel <= 13) { + athchan->a2_athchan = 115 + channel; + athchan->a2_flags = 0x46; + } else if (channel == 14) { + athchan->a2_athchan = 124; + athchan->a2_flags = 0x44; + } else if (channel >= 15 && channel <= 26) { + athchan->a2_athchan = ((channel - 14) * 4) + 132; + athchan->a2_flags = 0x46; + } else + return -EINVAL; + + return 0; +} + +/* + * Set channel on 5111 + */ +static int ath5k_hw_rf5111_channel(struct ath5k_hw *ah, + struct net80211_channel *channel) +{ + struct ath5k_athchan_2ghz ath5k_channel_2ghz; + unsigned int ath5k_channel = ath5k_freq_to_channel(channel->center_freq); + u32 data0, data1, clock; + int ret; + + /* + * Set the channel on the RF5111 radio + */ + data0 = data1 = 0; + + if (channel->hw_value & CHANNEL_2GHZ) { + /* Map 2GHz channel to 5GHz Atheros channel ID */ + ret = ath5k_hw_rf5111_chan2athchan(ath5k_channel, + &ath5k_channel_2ghz); + if (ret) + return ret; + + ath5k_channel = ath5k_channel_2ghz.a2_athchan; + data0 = ((ath5k_hw_bitswap(ath5k_channel_2ghz.a2_flags, 8) & 0xff) + << 5) | (1 << 4); + } + + if (ath5k_channel < 145 || !(ath5k_channel & 1)) { + clock = 1; + data1 = ((ath5k_hw_bitswap(ath5k_channel - 24, 8) & 0xff) << 2) | + (clock << 1) | (1 << 10) | 1; + } else { + clock = 0; + data1 = ((ath5k_hw_bitswap((ath5k_channel - 24) / 2, 8) & 0xff) + << 2) | (clock << 1) | (1 << 10) | 1; + } + + ath5k_hw_reg_write(ah, (data1 & 0xff) | ((data0 & 0xff) << 8), + AR5K_RF_BUFFER); + ath5k_hw_reg_write(ah, ((data1 >> 8) & 0xff) | (data0 & 0xff00), + AR5K_RF_BUFFER_CONTROL_3); + + return 0; +} + +/* + * Set channel on 5112 and newer + */ +static int ath5k_hw_rf5112_channel(struct ath5k_hw *ah, + struct net80211_channel *channel) +{ + u32 data, data0, data1, data2; + u16 c; + + data = data0 = data1 = data2 = 0; + c = channel->center_freq; + + if (c < 4800) { + if (!((c - 2224) % 5)) { + data0 = ((2 * (c - 704)) - 3040) / 10; + data1 = 1; + } else if (!((c - 2192) % 5)) { + data0 = ((2 * (c - 672)) - 3040) / 10; + data1 = 0; + } else + return -EINVAL; + + data0 = ath5k_hw_bitswap((data0 << 2) & 0xff, 8); + } else if ((c - (c % 5)) != 2 || c > 5435) { + if (!(c % 20) && c >= 5120) { + data0 = ath5k_hw_bitswap(((c - 4800) / 20 << 2), 8); + data2 = ath5k_hw_bitswap(3, 2); + } else if (!(c % 10)) { + data0 = ath5k_hw_bitswap(((c - 4800) / 10 << 1), 8); + data2 = ath5k_hw_bitswap(2, 2); + } else if (!(c % 5)) { + data0 = ath5k_hw_bitswap((c - 4800) / 5, 8); + data2 = ath5k_hw_bitswap(1, 2); + } else + return -EINVAL; + } else { + data0 = ath5k_hw_bitswap((10 * (c - 2) - 4800) / 25 + 1, 8); + data2 = ath5k_hw_bitswap(0, 2); + } + + data = (data0 << 4) | (data1 << 1) | (data2 << 2) | 0x1001; + + ath5k_hw_reg_write(ah, data & 0xff, AR5K_RF_BUFFER); + ath5k_hw_reg_write(ah, (data >> 8) & 0x7f, AR5K_RF_BUFFER_CONTROL_5); + + return 0; +} + +/* + * Set the channel on the RF2425 + */ +static int ath5k_hw_rf2425_channel(struct ath5k_hw *ah, + struct net80211_channel *channel) +{ + u32 data, data0, data2; + u16 c; + + data = data0 = data2 = 0; + c = channel->center_freq; + + if (c < 4800) { + data0 = ath5k_hw_bitswap((c - 2272), 8); + data2 = 0; + /* ? 5GHz ? */ + } else if ((c - (c % 5)) != 2 || c > 5435) { + if (!(c % 20) && c < 5120) + data0 = ath5k_hw_bitswap(((c - 4800) / 20 << 2), 8); + else if (!(c % 10)) + data0 = ath5k_hw_bitswap(((c - 4800) / 10 << 1), 8); + else if (!(c % 5)) + data0 = ath5k_hw_bitswap((c - 4800) / 5, 8); + else + return -EINVAL; + data2 = ath5k_hw_bitswap(1, 2); + } else { + data0 = ath5k_hw_bitswap((10 * (c - 2) - 4800) / 25 + 1, 8); + data2 = ath5k_hw_bitswap(0, 2); + } + + data = (data0 << 4) | data2 << 2 | 0x1001; + + ath5k_hw_reg_write(ah, data & 0xff, AR5K_RF_BUFFER); + ath5k_hw_reg_write(ah, (data >> 8) & 0x7f, AR5K_RF_BUFFER_CONTROL_5); + + return 0; +} + +/* + * Set a channel on the radio chip + */ +int ath5k_hw_channel(struct ath5k_hw *ah, struct net80211_channel *channel) +{ + int ret; + /* + * Check bounds supported by the PHY (we don't care about regultory + * restrictions at this point). Note: hw_value already has the band + * (CHANNEL_2GHZ, or CHANNEL_5GHZ) so we inform ath5k_channel_ok() + * of the band by that */ + if (!ath5k_channel_ok(ah, channel->center_freq, channel->hw_value)) { + DBG("ath5k: channel frequency (%d MHz) out of supported " + "range\n", channel->center_freq); + return -EINVAL; + } + + /* + * Set the channel and wait + */ + switch (ah->ah_radio) { + case AR5K_RF5110: + ret = ath5k_hw_rf5110_channel(ah, channel); + break; + case AR5K_RF5111: + ret = ath5k_hw_rf5111_channel(ah, channel); + break; + case AR5K_RF2425: + ret = ath5k_hw_rf2425_channel(ah, channel); + break; + default: + ret = ath5k_hw_rf5112_channel(ah, channel); + break; + } + + if (ret) { + DBG("ath5k: setting channel failed: %s\n", strerror(ret)); + return ret; + } + + /* Set JAPAN setting for channel 14 */ + if (channel->center_freq == 2484) { + AR5K_REG_ENABLE_BITS(ah, AR5K_PHY_CCKTXCTL, + AR5K_PHY_CCKTXCTL_JAPAN); + } else { + AR5K_REG_ENABLE_BITS(ah, AR5K_PHY_CCKTXCTL, + AR5K_PHY_CCKTXCTL_WORLD); + } + + ah->ah_current_channel = channel; + ah->ah_turbo = (channel->hw_value == CHANNEL_T ? 1 : 0); + + return 0; +} + +/*****************\ + PHY calibration +\*****************/ + +/** + * ath5k_hw_noise_floor_calibration - perform PHY noise floor calibration + * + * @ah: struct ath5k_hw pointer we are operating on + * @freq: the channel frequency, just used for error logging + * + * This function performs a noise floor calibration of the PHY and waits for + * it to complete. Then the noise floor value is compared to some maximum + * noise floor we consider valid. + * + * Note that this is different from what the madwifi HAL does: it reads the + * noise floor and afterwards initiates the calibration. Since the noise floor + * calibration can take some time to finish, depending on the current channel + * use, that avoids the occasional timeout warnings we are seeing now. + * + * See the following link for an Atheros patent on noise floor calibration: + * http://patft.uspto.gov/netacgi/nph-Parser?Sect1=PTO1&Sect2=HITOFF&d=PALL \ + * &p=1&u=%2Fnetahtml%2FPTO%2Fsrchnum.htm&r=1&f=G&l=50&s1=7245893.PN.&OS=PN/7 + * + * XXX: Since during noise floor calibration antennas are detached according to + * the patent, we should stop tx queues here. + */ +int +ath5k_hw_noise_floor_calibration(struct ath5k_hw *ah, short freq) +{ + int ret; + unsigned int i; + s32 noise_floor; + + /* + * Enable noise floor calibration + */ + AR5K_REG_ENABLE_BITS(ah, AR5K_PHY_AGCCTL, + AR5K_PHY_AGCCTL_NF); + + ret = ath5k_hw_register_timeout(ah, AR5K_PHY_AGCCTL, + AR5K_PHY_AGCCTL_NF, 0, 0); + + if (ret) { + DBG("ath5k: noise floor calibration timeout (%d MHz)\n", freq); + return -EAGAIN; + } + + /* Wait until the noise floor is calibrated and read the value */ + for (i = 20; i > 0; i--) { + mdelay(1); + noise_floor = ath5k_hw_reg_read(ah, AR5K_PHY_NF); + noise_floor = AR5K_PHY_NF_RVAL(noise_floor); + if (noise_floor & AR5K_PHY_NF_ACTIVE) { + noise_floor = AR5K_PHY_NF_AVAL(noise_floor); + + if (noise_floor <= AR5K_TUNE_NOISE_FLOOR) + break; + } + } + + DBG2("ath5k: noise floor %d\n", noise_floor); + + if (noise_floor > AR5K_TUNE_NOISE_FLOOR) { + DBG("ath5k: noise floor calibration failed (%d MHz)\n", freq); + return -EAGAIN; + } + + ah->ah_noise_floor = noise_floor; + + return 0; +} + +/* + * Perform a PHY calibration on RF5110 + * -Fix BPSK/QAM Constellation (I/Q correction) + * -Calculate Noise Floor + */ +static int ath5k_hw_rf5110_calibrate(struct ath5k_hw *ah, + struct net80211_channel *channel) +{ + u32 phy_sig, phy_agc, phy_sat, beacon; + int ret; + + /* + * Disable beacons and RX/TX queues, wait + */ + AR5K_REG_ENABLE_BITS(ah, AR5K_DIAG_SW_5210, + AR5K_DIAG_SW_DIS_TX | AR5K_DIAG_SW_DIS_RX_5210); + beacon = ath5k_hw_reg_read(ah, AR5K_BEACON_5210); + ath5k_hw_reg_write(ah, beacon & ~AR5K_BEACON_ENABLE, AR5K_BEACON_5210); + + mdelay(2); + + /* + * Set the channel (with AGC turned off) + */ + AR5K_REG_ENABLE_BITS(ah, AR5K_PHY_AGC, AR5K_PHY_AGC_DISABLE); + udelay(10); + ret = ath5k_hw_channel(ah, channel); + + /* + * Activate PHY and wait + */ + ath5k_hw_reg_write(ah, AR5K_PHY_ACT_ENABLE, AR5K_PHY_ACT); + mdelay(1); + + AR5K_REG_DISABLE_BITS(ah, AR5K_PHY_AGC, AR5K_PHY_AGC_DISABLE); + + if (ret) + return ret; + + /* + * Calibrate the radio chip + */ + + /* Remember normal state */ + phy_sig = ath5k_hw_reg_read(ah, AR5K_PHY_SIG); + phy_agc = ath5k_hw_reg_read(ah, AR5K_PHY_AGCCOARSE); + phy_sat = ath5k_hw_reg_read(ah, AR5K_PHY_ADCSAT); + + /* Update radio registers */ + ath5k_hw_reg_write(ah, (phy_sig & ~(AR5K_PHY_SIG_FIRPWR)) | + AR5K_REG_SM(-1, AR5K_PHY_SIG_FIRPWR), AR5K_PHY_SIG); + + ath5k_hw_reg_write(ah, (phy_agc & ~(AR5K_PHY_AGCCOARSE_HI | + AR5K_PHY_AGCCOARSE_LO)) | + AR5K_REG_SM(-1, AR5K_PHY_AGCCOARSE_HI) | + AR5K_REG_SM(-127, AR5K_PHY_AGCCOARSE_LO), AR5K_PHY_AGCCOARSE); + + ath5k_hw_reg_write(ah, (phy_sat & ~(AR5K_PHY_ADCSAT_ICNT | + AR5K_PHY_ADCSAT_THR)) | + AR5K_REG_SM(2, AR5K_PHY_ADCSAT_ICNT) | + AR5K_REG_SM(12, AR5K_PHY_ADCSAT_THR), AR5K_PHY_ADCSAT); + + udelay(20); + + AR5K_REG_ENABLE_BITS(ah, AR5K_PHY_AGC, AR5K_PHY_AGC_DISABLE); + udelay(10); + ath5k_hw_reg_write(ah, AR5K_PHY_RFSTG_DISABLE, AR5K_PHY_RFSTG); + AR5K_REG_DISABLE_BITS(ah, AR5K_PHY_AGC, AR5K_PHY_AGC_DISABLE); + + mdelay(1); + + /* + * Enable calibration and wait until completion + */ + AR5K_REG_ENABLE_BITS(ah, AR5K_PHY_AGCCTL, AR5K_PHY_AGCCTL_CAL); + + ret = ath5k_hw_register_timeout(ah, AR5K_PHY_AGCCTL, + AR5K_PHY_AGCCTL_CAL, 0, 0); + + /* Reset to normal state */ + ath5k_hw_reg_write(ah, phy_sig, AR5K_PHY_SIG); + ath5k_hw_reg_write(ah, phy_agc, AR5K_PHY_AGCCOARSE); + ath5k_hw_reg_write(ah, phy_sat, AR5K_PHY_ADCSAT); + + if (ret) { + DBG("ath5k: calibration timeout (%d MHz)\n", + channel->center_freq); + return ret; + } + + ath5k_hw_noise_floor_calibration(ah, channel->center_freq); + + /* + * Re-enable RX/TX and beacons + */ + AR5K_REG_DISABLE_BITS(ah, AR5K_DIAG_SW_5210, + AR5K_DIAG_SW_DIS_TX | AR5K_DIAG_SW_DIS_RX_5210); + ath5k_hw_reg_write(ah, beacon, AR5K_BEACON_5210); + + return 0; +} + +/* + * Perform a PHY calibration on RF5111/5112 and newer chips + */ +static int ath5k_hw_rf511x_calibrate(struct ath5k_hw *ah, + struct net80211_channel *channel) +{ + u32 i_pwr, q_pwr; + s32 iq_corr, i_coff, i_coffd, q_coff, q_coffd; + int i; + + if (!ah->ah_calibration || + ath5k_hw_reg_read(ah, AR5K_PHY_IQ) & AR5K_PHY_IQ_RUN) + goto done; + + /* Calibration has finished, get the results and re-run */ + for (i = 0; i <= 10; i++) { + iq_corr = ath5k_hw_reg_read(ah, AR5K_PHY_IQRES_CAL_CORR); + i_pwr = ath5k_hw_reg_read(ah, AR5K_PHY_IQRES_CAL_PWR_I); + q_pwr = ath5k_hw_reg_read(ah, AR5K_PHY_IQRES_CAL_PWR_Q); + } + + i_coffd = ((i_pwr >> 1) + (q_pwr >> 1)) >> 7; + q_coffd = q_pwr >> 7; + + /* No correction */ + if (i_coffd == 0 || q_coffd == 0) + goto done; + + i_coff = ((-iq_corr) / i_coffd) & 0x3f; + + /* Boundary check */ + if (i_coff > 31) + i_coff = 31; + if (i_coff < -32) + i_coff = -32; + + q_coff = (((s32)i_pwr / q_coffd) - 128) & 0x1f; + + /* Boundary check */ + if (q_coff > 15) + q_coff = 15; + if (q_coff < -16) + q_coff = -16; + + /* Commit new I/Q value */ + AR5K_REG_ENABLE_BITS(ah, AR5K_PHY_IQ, AR5K_PHY_IQ_CORR_ENABLE | + ((u32)q_coff) | ((u32)i_coff << AR5K_PHY_IQ_CORR_Q_I_COFF_S)); + + /* Re-enable calibration -if we don't we'll commit + * the same values again and again */ + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_IQ, + AR5K_PHY_IQ_CAL_NUM_LOG_MAX, 15); + AR5K_REG_ENABLE_BITS(ah, AR5K_PHY_IQ, AR5K_PHY_IQ_RUN); + +done: + + /* TODO: Separate noise floor calibration from I/Q calibration + * since noise floor calibration interrupts rx path while I/Q + * calibration doesn't. We don't need to run noise floor calibration + * as often as I/Q calibration.*/ + ath5k_hw_noise_floor_calibration(ah, channel->center_freq); + + /* Initiate a gain_F calibration */ + ath5k_hw_request_rfgain_probe(ah); + + return 0; +} + +/* + * Perform a PHY calibration + */ +int ath5k_hw_phy_calibrate(struct ath5k_hw *ah, + struct net80211_channel *channel) +{ + int ret; + + if (ah->ah_radio == AR5K_RF5110) + ret = ath5k_hw_rf5110_calibrate(ah, channel); + else + ret = ath5k_hw_rf511x_calibrate(ah, channel); + + return ret; +} + +int ath5k_hw_phy_disable(struct ath5k_hw *ah) +{ + ath5k_hw_reg_write(ah, AR5K_PHY_ACT_DISABLE, AR5K_PHY_ACT); + + return 0; +} + +/********************\ + Misc PHY functions +\********************/ + +/* + * Get the PHY Chip revision + */ +u16 ath5k_hw_radio_revision(struct ath5k_hw *ah, unsigned int chan) +{ + unsigned int i; + u32 srev; + u16 ret; + + /* + * Set the radio chip access register + */ + switch (chan) { + case CHANNEL_2GHZ: + ath5k_hw_reg_write(ah, AR5K_PHY_SHIFT_2GHZ, AR5K_PHY(0)); + break; + case CHANNEL_5GHZ: + ath5k_hw_reg_write(ah, AR5K_PHY_SHIFT_5GHZ, AR5K_PHY(0)); + break; + default: + return 0; + } + + mdelay(2); + + /* ...wait until PHY is ready and read the selected radio revision */ + ath5k_hw_reg_write(ah, 0x00001c16, AR5K_PHY(0x34)); + + for (i = 0; i < 8; i++) + ath5k_hw_reg_write(ah, 0x00010000, AR5K_PHY(0x20)); + + if (ah->ah_version == AR5K_AR5210) { + srev = ath5k_hw_reg_read(ah, AR5K_PHY(256) >> 28) & 0xf; + ret = (u16)ath5k_hw_bitswap(srev, 4) + 1; + } else { + srev = (ath5k_hw_reg_read(ah, AR5K_PHY(0x100)) >> 24) & 0xff; + ret = (u16)ath5k_hw_bitswap(((srev & 0xf0) >> 4) | + ((srev & 0x0f) << 4), 8); + } + + /* Reset to the 5GHz mode */ + ath5k_hw_reg_write(ah, AR5K_PHY_SHIFT_5GHZ, AR5K_PHY(0)); + + return ret; +} + +void /*TODO:Boundary check*/ +ath5k_hw_set_def_antenna(struct ath5k_hw *ah, unsigned int ant) +{ + if (ah->ah_version != AR5K_AR5210) + ath5k_hw_reg_write(ah, ant, AR5K_DEFAULT_ANTENNA); +} + +unsigned int ath5k_hw_get_def_antenna(struct ath5k_hw *ah) +{ + if (ah->ah_version != AR5K_AR5210) + return ath5k_hw_reg_read(ah, AR5K_DEFAULT_ANTENNA); + + return 0; /*XXX: What do we return for 5210 ?*/ +} + + +/****************\ +* TX power setup * +\****************/ + +/* + * Helper functions + */ + +/* + * Do linear interpolation between two given (x, y) points + */ +static s16 +ath5k_get_interpolated_value(s16 target, s16 x_left, s16 x_right, + s16 y_left, s16 y_right) +{ + s16 ratio, result; + + /* Avoid divide by zero and skip interpolation + * if we have the same point */ + if ((x_left == x_right) || (y_left == y_right)) + return y_left; + + /* + * Since we use ints and not fps, we need to scale up in + * order to get a sane ratio value (or else we 'll eg. get + * always 1 instead of 1.25, 1.75 etc). We scale up by 100 + * to have some accuracy both for 0.5 and 0.25 steps. + */ + ratio = ((100 * y_right - 100 * y_left)/(x_right - x_left)); + + /* Now scale down to be in range */ + result = y_left + (ratio * (target - x_left) / 100); + + return result; +} + +/* + * Find vertical boundary (min pwr) for the linear PCDAC curve. + * + * Since we have the top of the curve and we draw the line below + * until we reach 1 (1 pcdac step) we need to know which point + * (x value) that is so that we don't go below y axis and have negative + * pcdac values when creating the curve, or fill the table with zeroes. + */ +static s16 +ath5k_get_linear_pcdac_min(const u8 *stepL, const u8 *stepR, + const s16 *pwrL, const s16 *pwrR) +{ + s8 tmp; + s16 min_pwrL, min_pwrR; + s16 pwr_i; + + if (pwrL[0] == pwrL[1]) + min_pwrL = pwrL[0]; + else { + pwr_i = pwrL[0]; + do { + pwr_i--; + tmp = (s8) ath5k_get_interpolated_value(pwr_i, + pwrL[0], pwrL[1], + stepL[0], stepL[1]); + } while (tmp > 1); + + min_pwrL = pwr_i; + } + + if (pwrR[0] == pwrR[1]) + min_pwrR = pwrR[0]; + else { + pwr_i = pwrR[0]; + do { + pwr_i--; + tmp = (s8) ath5k_get_interpolated_value(pwr_i, + pwrR[0], pwrR[1], + stepR[0], stepR[1]); + } while (tmp > 1); + + min_pwrR = pwr_i; + } + + /* Keep the right boundary so that it works for both curves */ + return max(min_pwrL, min_pwrR); +} + +/* + * Interpolate (pwr,vpd) points to create a Power to PDADC or a + * Power to PCDAC curve. + * + * Each curve has power on x axis (in 0.5dB units) and PCDAC/PDADC + * steps (offsets) on y axis. Power can go up to 31.5dB and max + * PCDAC/PDADC step for each curve is 64 but we can write more than + * one curves on hw so we can go up to 128 (which is the max step we + * can write on the final table). + * + * We write y values (PCDAC/PDADC steps) on hw. + */ +static void +ath5k_create_power_curve(s16 pmin, s16 pmax, + const s16 *pwr, const u8 *vpd, + u8 num_points, + u8 *vpd_table, u8 type) +{ + u8 idx[2] = { 0, 1 }; + s16 pwr_i = 2*pmin; + int i; + + if (num_points < 2) + return; + + /* We want the whole line, so adjust boundaries + * to cover the entire power range. Note that + * power values are already 0.25dB so no need + * to multiply pwr_i by 2 */ + if (type == AR5K_PWRTABLE_LINEAR_PCDAC) { + pwr_i = pmin; + pmin = 0; + pmax = 63; + } + + /* Find surrounding turning points (TPs) + * and interpolate between them */ + for (i = 0; (i <= (u16) (pmax - pmin)) && + (i < AR5K_EEPROM_POWER_TABLE_SIZE); i++) { + + /* We passed the right TP, move to the next set of TPs + * if we pass the last TP, extrapolate above using the last + * two TPs for ratio */ + if ((pwr_i > pwr[idx[1]]) && (idx[1] < num_points - 1)) { + idx[0]++; + idx[1]++; + } + + vpd_table[i] = (u8) ath5k_get_interpolated_value(pwr_i, + pwr[idx[0]], pwr[idx[1]], + vpd[idx[0]], vpd[idx[1]]); + + /* Increase by 0.5dB + * (0.25 dB units) */ + pwr_i += 2; + } +} + +/* + * Get the surrounding per-channel power calibration piers + * for a given frequency so that we can interpolate between + * them and come up with an apropriate dataset for our current + * channel. + */ +static void +ath5k_get_chan_pcal_surrounding_piers(struct ath5k_hw *ah, + struct net80211_channel *channel, + struct ath5k_chan_pcal_info **pcinfo_l, + struct ath5k_chan_pcal_info **pcinfo_r) +{ + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + struct ath5k_chan_pcal_info *pcinfo; + u8 idx_l, idx_r; + u8 mode, max, i; + u32 target = channel->center_freq; + + idx_l = 0; + idx_r = 0; + + if (!(channel->hw_value & CHANNEL_OFDM)) { + pcinfo = ee->ee_pwr_cal_b; + mode = AR5K_EEPROM_MODE_11B; + } else if (channel->hw_value & CHANNEL_2GHZ) { + pcinfo = ee->ee_pwr_cal_g; + mode = AR5K_EEPROM_MODE_11G; + } else { + pcinfo = ee->ee_pwr_cal_a; + mode = AR5K_EEPROM_MODE_11A; + } + max = ee->ee_n_piers[mode] - 1; + + /* Frequency is below our calibrated + * range. Use the lowest power curve + * we have */ + if (target < pcinfo[0].freq) { + idx_l = idx_r = 0; + goto done; + } + + /* Frequency is above our calibrated + * range. Use the highest power curve + * we have */ + if (target > pcinfo[max].freq) { + idx_l = idx_r = max; + goto done; + } + + /* Frequency is inside our calibrated + * channel range. Pick the surrounding + * calibration piers so that we can + * interpolate */ + for (i = 0; i <= max; i++) { + + /* Frequency matches one of our calibration + * piers, no need to interpolate, just use + * that calibration pier */ + if (pcinfo[i].freq == target) { + idx_l = idx_r = i; + goto done; + } + + /* We found a calibration pier that's above + * frequency, use this pier and the previous + * one to interpolate */ + if (target < pcinfo[i].freq) { + idx_r = i; + idx_l = idx_r - 1; + goto done; + } + } + +done: + *pcinfo_l = &pcinfo[idx_l]; + *pcinfo_r = &pcinfo[idx_r]; + + return; +} + +/* + * Get the surrounding per-rate power calibration data + * for a given frequency and interpolate between power + * values to set max target power supported by hw for + * each rate. + */ +static void +ath5k_get_rate_pcal_data(struct ath5k_hw *ah, + struct net80211_channel *channel, + struct ath5k_rate_pcal_info *rates) +{ + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + struct ath5k_rate_pcal_info *rpinfo; + u8 idx_l, idx_r; + u8 mode, max, i; + u32 target = channel->center_freq; + + idx_l = 0; + idx_r = 0; + + if (!(channel->hw_value & CHANNEL_OFDM)) { + rpinfo = ee->ee_rate_tpwr_b; + mode = AR5K_EEPROM_MODE_11B; + } else if (channel->hw_value & CHANNEL_2GHZ) { + rpinfo = ee->ee_rate_tpwr_g; + mode = AR5K_EEPROM_MODE_11G; + } else { + rpinfo = ee->ee_rate_tpwr_a; + mode = AR5K_EEPROM_MODE_11A; + } + max = ee->ee_rate_target_pwr_num[mode] - 1; + + /* Get the surrounding calibration + * piers - same as above */ + if (target < rpinfo[0].freq) { + idx_l = idx_r = 0; + goto done; + } + + if (target > rpinfo[max].freq) { + idx_l = idx_r = max; + goto done; + } + + for (i = 0; i <= max; i++) { + + if (rpinfo[i].freq == target) { + idx_l = idx_r = i; + goto done; + } + + if (target < rpinfo[i].freq) { + idx_r = i; + idx_l = idx_r - 1; + goto done; + } + } + +done: + /* Now interpolate power value, based on the frequency */ + rates->freq = target; + + rates->target_power_6to24 = + ath5k_get_interpolated_value(target, rpinfo[idx_l].freq, + rpinfo[idx_r].freq, + rpinfo[idx_l].target_power_6to24, + rpinfo[idx_r].target_power_6to24); + + rates->target_power_36 = + ath5k_get_interpolated_value(target, rpinfo[idx_l].freq, + rpinfo[idx_r].freq, + rpinfo[idx_l].target_power_36, + rpinfo[idx_r].target_power_36); + + rates->target_power_48 = + ath5k_get_interpolated_value(target, rpinfo[idx_l].freq, + rpinfo[idx_r].freq, + rpinfo[idx_l].target_power_48, + rpinfo[idx_r].target_power_48); + + rates->target_power_54 = + ath5k_get_interpolated_value(target, rpinfo[idx_l].freq, + rpinfo[idx_r].freq, + rpinfo[idx_l].target_power_54, + rpinfo[idx_r].target_power_54); +} + +/* + * Get the max edge power for this channel if + * we have such data from EEPROM's Conformance Test + * Limits (CTL), and limit max power if needed. + * + * FIXME: Only works for world regulatory domains + */ +static void +ath5k_get_max_ctl_power(struct ath5k_hw *ah, + struct net80211_channel *channel) +{ + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + struct ath5k_edge_power *rep = ee->ee_ctl_pwr; + u8 *ctl_val = ee->ee_ctl; + s16 max_chan_pwr = ah->ah_txpower.txp_max_pwr / 4; + s16 edge_pwr = 0; + u8 rep_idx; + u8 i, ctl_mode; + u8 ctl_idx = 0xFF; + u32 target = channel->center_freq; + + /* Find out a CTL for our mode that's not mapped + * on a specific reg domain. + * + * TODO: Map our current reg domain to one of the 3 available + * reg domain ids so that we can support more CTLs. */ + switch (channel->hw_value & CHANNEL_MODES) { + case CHANNEL_A: + ctl_mode = AR5K_CTL_11A | AR5K_CTL_NO_REGDOMAIN; + break; + case CHANNEL_G: + ctl_mode = AR5K_CTL_11G | AR5K_CTL_NO_REGDOMAIN; + break; + case CHANNEL_B: + ctl_mode = AR5K_CTL_11B | AR5K_CTL_NO_REGDOMAIN; + break; + case CHANNEL_T: + ctl_mode = AR5K_CTL_TURBO | AR5K_CTL_NO_REGDOMAIN; + break; + case CHANNEL_TG: + ctl_mode = AR5K_CTL_TURBOG | AR5K_CTL_NO_REGDOMAIN; + break; + case CHANNEL_XR: + /* Fall through */ + default: + return; + } + + for (i = 0; i < ee->ee_ctls; i++) { + if (ctl_val[i] == ctl_mode) { + ctl_idx = i; + break; + } + } + + /* If we have a CTL dataset available grab it and find the + * edge power for our frequency */ + if (ctl_idx == 0xFF) + return; + + /* Edge powers are sorted by frequency from lower + * to higher. Each CTL corresponds to 8 edge power + * measurements. */ + rep_idx = ctl_idx * AR5K_EEPROM_N_EDGES; + + /* Don't do boundaries check because we + * might have more that one bands defined + * for this mode */ + + /* Get the edge power that's closer to our + * frequency */ + for (i = 0; i < AR5K_EEPROM_N_EDGES; i++) { + rep_idx += i; + if (target <= rep[rep_idx].freq) + edge_pwr = (s16) rep[rep_idx].edge; + } + + if (edge_pwr) { + ah->ah_txpower.txp_max_pwr = 4*min(edge_pwr, max_chan_pwr); + } +} + + +/* + * Power to PCDAC table functions + */ + +/* + * Fill Power to PCDAC table on RF5111 + * + * No further processing is needed for RF5111, the only thing we have to + * do is fill the values below and above calibration range since eeprom data + * may not cover the entire PCDAC table. + */ +static void +ath5k_fill_pwr_to_pcdac_table(struct ath5k_hw *ah, s16* table_min, + s16 *table_max) +{ + u8 *pcdac_out = ah->ah_txpower.txp_pd_table; + u8 *pcdac_tmp = ah->ah_txpower.tmpL[0]; + u8 pcdac_0, pcdac_n, pcdac_i, pwr_idx, i; + s16 min_pwr, max_pwr; + + /* Get table boundaries */ + min_pwr = table_min[0]; + pcdac_0 = pcdac_tmp[0]; + + max_pwr = table_max[0]; + pcdac_n = pcdac_tmp[table_max[0] - table_min[0]]; + + /* Extrapolate below minimum using pcdac_0 */ + pcdac_i = 0; + for (i = 0; i < min_pwr; i++) + pcdac_out[pcdac_i++] = pcdac_0; + + /* Copy values from pcdac_tmp */ + pwr_idx = min_pwr; + for (i = 0 ; pwr_idx <= max_pwr && + pcdac_i < AR5K_EEPROM_POWER_TABLE_SIZE; i++) { + pcdac_out[pcdac_i++] = pcdac_tmp[i]; + pwr_idx++; + } + + /* Extrapolate above maximum */ + while (pcdac_i < AR5K_EEPROM_POWER_TABLE_SIZE) + pcdac_out[pcdac_i++] = pcdac_n; + +} + +/* + * Combine available XPD Curves and fill Linear Power to PCDAC table + * on RF5112 + * + * RFX112 can have up to 2 curves (one for low txpower range and one for + * higher txpower range). We need to put them both on pcdac_out and place + * them in the correct location. In case we only have one curve available + * just fit it on pcdac_out (it's supposed to cover the entire range of + * available pwr levels since it's always the higher power curve). Extrapolate + * below and above final table if needed. + */ +static void +ath5k_combine_linear_pcdac_curves(struct ath5k_hw *ah, s16* table_min, + s16 *table_max, u8 pdcurves) +{ + u8 *pcdac_out = ah->ah_txpower.txp_pd_table; + u8 *pcdac_low_pwr; + u8 *pcdac_high_pwr; + u8 *pcdac_tmp; + u8 pwr; + s16 max_pwr_idx; + s16 min_pwr_idx; + s16 mid_pwr_idx = 0; + /* Edge flag turs on the 7nth bit on the PCDAC + * to delcare the higher power curve (force values + * to be greater than 64). If we only have one curve + * we don't need to set this, if we have 2 curves and + * fill the table backwards this can also be used to + * switch from higher power curve to lower power curve */ + u8 edge_flag; + int i; + + /* When we have only one curve available + * that's the higher power curve. If we have + * two curves the first is the high power curve + * and the next is the low power curve. */ + if (pdcurves > 1) { + pcdac_low_pwr = ah->ah_txpower.tmpL[1]; + pcdac_high_pwr = ah->ah_txpower.tmpL[0]; + mid_pwr_idx = table_max[1] - table_min[1] - 1; + max_pwr_idx = (table_max[0] - table_min[0]) / 2; + + /* If table size goes beyond 31.5dB, keep the + * upper 31.5dB range when setting tx power. + * Note: 126 = 31.5 dB in quarter dB steps */ + if (table_max[0] - table_min[1] > 126) + min_pwr_idx = table_max[0] - 126; + else + min_pwr_idx = table_min[1]; + + /* Since we fill table backwards + * start from high power curve */ + pcdac_tmp = pcdac_high_pwr; + + edge_flag = 0x40; + } else { + pcdac_low_pwr = ah->ah_txpower.tmpL[1]; /* Zeroed */ + pcdac_high_pwr = ah->ah_txpower.tmpL[0]; + min_pwr_idx = table_min[0]; + max_pwr_idx = (table_max[0] - table_min[0]) / 2; + pcdac_tmp = pcdac_high_pwr; + edge_flag = 0; + } + + /* This is used when setting tx power*/ + ah->ah_txpower.txp_min_idx = min_pwr_idx/2; + + /* Fill Power to PCDAC table backwards */ + pwr = max_pwr_idx; + for (i = 63; i >= 0; i--) { + /* Entering lower power range, reset + * edge flag and set pcdac_tmp to lower + * power curve.*/ + if (edge_flag == 0x40 && + (2*pwr <= (table_max[1] - table_min[0]) || pwr == 0)) { + edge_flag = 0x00; + pcdac_tmp = pcdac_low_pwr; + pwr = mid_pwr_idx/2; + } + + /* Don't go below 1, extrapolate below if we have + * already swithced to the lower power curve -or + * we only have one curve and edge_flag is zero + * anyway */ + if (pcdac_tmp[pwr] < 1 && (edge_flag == 0x00)) { + while (i >= 0) { + pcdac_out[i] = pcdac_out[i + 1]; + i--; + } + break; + } + + pcdac_out[i] = pcdac_tmp[pwr] | edge_flag; + + /* Extrapolate above if pcdac is greater than + * 126 -this can happen because we OR pcdac_out + * value with edge_flag on high power curve */ + if (pcdac_out[i] > 126) + pcdac_out[i] = 126; + + /* Decrease by a 0.5dB step */ + pwr--; + } +} + +/* Write PCDAC values on hw */ +static void +ath5k_setup_pcdac_table(struct ath5k_hw *ah) +{ + u8 *pcdac_out = ah->ah_txpower.txp_pd_table; + int i; + + /* + * Write TX power values + */ + for (i = 0; i < (AR5K_EEPROM_POWER_TABLE_SIZE / 2); i++) { + ath5k_hw_reg_write(ah, + (((pcdac_out[2*i + 0] << 8 | 0xff) & 0xffff) << 0) | + (((pcdac_out[2*i + 1] << 8 | 0xff) & 0xffff) << 16), + AR5K_PHY_PCDAC_TXPOWER(i)); + } +} + + +/* + * Power to PDADC table functions + */ + +/* + * Set the gain boundaries and create final Power to PDADC table + * + * We can have up to 4 pd curves, we need to do a simmilar process + * as we do for RF5112. This time we don't have an edge_flag but we + * set the gain boundaries on a separate register. + */ +static void +ath5k_combine_pwr_to_pdadc_curves(struct ath5k_hw *ah, + s16 *pwr_min, s16 *pwr_max, u8 pdcurves) +{ + u8 gain_boundaries[AR5K_EEPROM_N_PD_GAINS]; + u8 *pdadc_out = ah->ah_txpower.txp_pd_table; + u8 *pdadc_tmp; + s16 pdadc_0; + u8 pdadc_i, pdadc_n, pwr_step, pdg, max_idx, table_size; + u8 pd_gain_overlap; + + /* Note: Register value is initialized on initvals + * there is no feedback from hw. + * XXX: What about pd_gain_overlap from EEPROM ? */ + pd_gain_overlap = (u8) ath5k_hw_reg_read(ah, AR5K_PHY_TPC_RG5) & + AR5K_PHY_TPC_RG5_PD_GAIN_OVERLAP; + + /* Create final PDADC table */ + for (pdg = 0, pdadc_i = 0; pdg < pdcurves; pdg++) { + pdadc_tmp = ah->ah_txpower.tmpL[pdg]; + + if (pdg == pdcurves - 1) + /* 2 dB boundary stretch for last + * (higher power) curve */ + gain_boundaries[pdg] = pwr_max[pdg] + 4; + else + /* Set gain boundary in the middle + * between this curve and the next one */ + gain_boundaries[pdg] = + (pwr_max[pdg] + pwr_min[pdg + 1]) / 2; + + /* Sanity check in case our 2 db stretch got out of + * range. */ + if (gain_boundaries[pdg] > AR5K_TUNE_MAX_TXPOWER) + gain_boundaries[pdg] = AR5K_TUNE_MAX_TXPOWER; + + /* For the first curve (lower power) + * start from 0 dB */ + if (pdg == 0) + pdadc_0 = 0; + else + /* For the other curves use the gain overlap */ + pdadc_0 = (gain_boundaries[pdg - 1] - pwr_min[pdg]) - + pd_gain_overlap; + + /* Force each power step to be at least 0.5 dB */ + if ((pdadc_tmp[1] - pdadc_tmp[0]) > 1) + pwr_step = pdadc_tmp[1] - pdadc_tmp[0]; + else + pwr_step = 1; + + /* If pdadc_0 is negative, we need to extrapolate + * below this pdgain by a number of pwr_steps */ + while ((pdadc_0 < 0) && (pdadc_i < 128)) { + s16 tmp = pdadc_tmp[0] + pdadc_0 * pwr_step; + pdadc_out[pdadc_i++] = (tmp < 0) ? 0 : (u8) tmp; + pdadc_0++; + } + + /* Set last pwr level, using gain boundaries */ + pdadc_n = gain_boundaries[pdg] + pd_gain_overlap - pwr_min[pdg]; + /* Limit it to be inside pwr range */ + table_size = pwr_max[pdg] - pwr_min[pdg]; + max_idx = (pdadc_n < table_size) ? pdadc_n : table_size; + + /* Fill pdadc_out table */ + while (pdadc_0 < max_idx) + pdadc_out[pdadc_i++] = pdadc_tmp[pdadc_0++]; + + /* Need to extrapolate above this pdgain? */ + if (pdadc_n <= max_idx) + continue; + + /* Force each power step to be at least 0.5 dB */ + if ((pdadc_tmp[table_size - 1] - pdadc_tmp[table_size - 2]) > 1) + pwr_step = pdadc_tmp[table_size - 1] - + pdadc_tmp[table_size - 2]; + else + pwr_step = 1; + + /* Extrapolate above */ + while ((pdadc_0 < (s16) pdadc_n) && + (pdadc_i < AR5K_EEPROM_POWER_TABLE_SIZE * 2)) { + s16 tmp = pdadc_tmp[table_size - 1] + + (pdadc_0 - max_idx) * pwr_step; + pdadc_out[pdadc_i++] = (tmp > 127) ? 127 : (u8) tmp; + pdadc_0++; + } + } + + while (pdg < AR5K_EEPROM_N_PD_GAINS) { + gain_boundaries[pdg] = gain_boundaries[pdg - 1]; + pdg++; + } + + while (pdadc_i < AR5K_EEPROM_POWER_TABLE_SIZE * 2) { + pdadc_out[pdadc_i] = pdadc_out[pdadc_i - 1]; + pdadc_i++; + } + + /* Set gain boundaries */ + ath5k_hw_reg_write(ah, + AR5K_REG_SM(pd_gain_overlap, + AR5K_PHY_TPC_RG5_PD_GAIN_OVERLAP) | + AR5K_REG_SM(gain_boundaries[0], + AR5K_PHY_TPC_RG5_PD_GAIN_BOUNDARY_1) | + AR5K_REG_SM(gain_boundaries[1], + AR5K_PHY_TPC_RG5_PD_GAIN_BOUNDARY_2) | + AR5K_REG_SM(gain_boundaries[2], + AR5K_PHY_TPC_RG5_PD_GAIN_BOUNDARY_3) | + AR5K_REG_SM(gain_boundaries[3], + AR5K_PHY_TPC_RG5_PD_GAIN_BOUNDARY_4), + AR5K_PHY_TPC_RG5); + + /* Used for setting rate power table */ + ah->ah_txpower.txp_min_idx = pwr_min[0]; + +} + +/* Write PDADC values on hw */ +static void +ath5k_setup_pwr_to_pdadc_table(struct ath5k_hw *ah, + u8 pdcurves, u8 *pdg_to_idx) +{ + u8 *pdadc_out = ah->ah_txpower.txp_pd_table; + u32 reg; + u8 i; + + /* Select the right pdgain curves */ + + /* Clear current settings */ + reg = ath5k_hw_reg_read(ah, AR5K_PHY_TPC_RG1); + reg &= ~(AR5K_PHY_TPC_RG1_PDGAIN_1 | + AR5K_PHY_TPC_RG1_PDGAIN_2 | + AR5K_PHY_TPC_RG1_PDGAIN_3 | + AR5K_PHY_TPC_RG1_NUM_PD_GAIN); + + /* + * Use pd_gains curve from eeprom + * + * This overrides the default setting from initvals + * in case some vendors (e.g. Zcomax) don't use the default + * curves. If we don't honor their settings we 'll get a + * 5dB (1 * gain overlap ?) drop. + */ + reg |= AR5K_REG_SM(pdcurves, AR5K_PHY_TPC_RG1_NUM_PD_GAIN); + + switch (pdcurves) { + case 3: + reg |= AR5K_REG_SM(pdg_to_idx[2], AR5K_PHY_TPC_RG1_PDGAIN_3); + /* Fall through */ + case 2: + reg |= AR5K_REG_SM(pdg_to_idx[1], AR5K_PHY_TPC_RG1_PDGAIN_2); + /* Fall through */ + case 1: + reg |= AR5K_REG_SM(pdg_to_idx[0], AR5K_PHY_TPC_RG1_PDGAIN_1); + break; + } + ath5k_hw_reg_write(ah, reg, AR5K_PHY_TPC_RG1); + + /* + * Write TX power values + */ + for (i = 0; i < (AR5K_EEPROM_POWER_TABLE_SIZE / 2); i++) { + ath5k_hw_reg_write(ah, + ((pdadc_out[4*i + 0] & 0xff) << 0) | + ((pdadc_out[4*i + 1] & 0xff) << 8) | + ((pdadc_out[4*i + 2] & 0xff) << 16) | + ((pdadc_out[4*i + 3] & 0xff) << 24), + AR5K_PHY_PDADC_TXPOWER(i)); + } +} + + +/* + * Common code for PCDAC/PDADC tables + */ + +/* + * This is the main function that uses all of the above + * to set PCDAC/PDADC table on hw for the current channel. + * This table is used for tx power calibration on the basband, + * without it we get weird tx power levels and in some cases + * distorted spectral mask + */ +static int +ath5k_setup_channel_powertable(struct ath5k_hw *ah, + struct net80211_channel *channel, + u8 ee_mode, u8 type) +{ + struct ath5k_pdgain_info *pdg_L, *pdg_R; + struct ath5k_chan_pcal_info *pcinfo_L; + struct ath5k_chan_pcal_info *pcinfo_R; + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + u8 *pdg_curve_to_idx = ee->ee_pdc_to_idx[ee_mode]; + s16 table_min[AR5K_EEPROM_N_PD_GAINS]; + s16 table_max[AR5K_EEPROM_N_PD_GAINS]; + u8 *tmpL; + u8 *tmpR; + u32 target = channel->center_freq; + int pdg, i; + + /* Get surounding freq piers for this channel */ + ath5k_get_chan_pcal_surrounding_piers(ah, channel, + &pcinfo_L, + &pcinfo_R); + + /* Loop over pd gain curves on + * surounding freq piers by index */ + for (pdg = 0; pdg < ee->ee_pd_gains[ee_mode]; pdg++) { + + /* Fill curves in reverse order + * from lower power (max gain) + * to higher power. Use curve -> idx + * backmaping we did on eeprom init */ + u8 idx = pdg_curve_to_idx[pdg]; + + /* Grab the needed curves by index */ + pdg_L = &pcinfo_L->pd_curves[idx]; + pdg_R = &pcinfo_R->pd_curves[idx]; + + /* Initialize the temp tables */ + tmpL = ah->ah_txpower.tmpL[pdg]; + tmpR = ah->ah_txpower.tmpR[pdg]; + + /* Set curve's x boundaries and create + * curves so that they cover the same + * range (if we don't do that one table + * will have values on some range and the + * other one won't have any so interpolation + * will fail) */ + table_min[pdg] = min(pdg_L->pd_pwr[0], + pdg_R->pd_pwr[0]) / 2; + + table_max[pdg] = max(pdg_L->pd_pwr[pdg_L->pd_points - 1], + pdg_R->pd_pwr[pdg_R->pd_points - 1]) / 2; + + /* Now create the curves on surrounding channels + * and interpolate if needed to get the final + * curve for this gain on this channel */ + switch (type) { + case AR5K_PWRTABLE_LINEAR_PCDAC: + /* Override min/max so that we don't loose + * accuracy (don't divide by 2) */ + table_min[pdg] = min(pdg_L->pd_pwr[0], + pdg_R->pd_pwr[0]); + + table_max[pdg] = + max(pdg_L->pd_pwr[pdg_L->pd_points - 1], + pdg_R->pd_pwr[pdg_R->pd_points - 1]); + + /* Override minimum so that we don't get + * out of bounds while extrapolating + * below. Don't do this when we have 2 + * curves and we are on the high power curve + * because table_min is ok in this case */ + if (!(ee->ee_pd_gains[ee_mode] > 1 && pdg == 0)) { + + table_min[pdg] = + ath5k_get_linear_pcdac_min(pdg_L->pd_step, + pdg_R->pd_step, + pdg_L->pd_pwr, + pdg_R->pd_pwr); + + /* Don't go too low because we will + * miss the upper part of the curve. + * Note: 126 = 31.5dB (max power supported) + * in 0.25dB units */ + if (table_max[pdg] - table_min[pdg] > 126) + table_min[pdg] = table_max[pdg] - 126; + } + + /* Fall through */ + case AR5K_PWRTABLE_PWR_TO_PCDAC: + case AR5K_PWRTABLE_PWR_TO_PDADC: + + ath5k_create_power_curve(table_min[pdg], + table_max[pdg], + pdg_L->pd_pwr, + pdg_L->pd_step, + pdg_L->pd_points, tmpL, type); + + /* We are in a calibration + * pier, no need to interpolate + * between freq piers */ + if (pcinfo_L == pcinfo_R) + continue; + + ath5k_create_power_curve(table_min[pdg], + table_max[pdg], + pdg_R->pd_pwr, + pdg_R->pd_step, + pdg_R->pd_points, tmpR, type); + break; + default: + return -EINVAL; + } + + /* Interpolate between curves + * of surounding freq piers to + * get the final curve for this + * pd gain. Re-use tmpL for interpolation + * output */ + for (i = 0; (i < (u16) (table_max[pdg] - table_min[pdg])) && + (i < AR5K_EEPROM_POWER_TABLE_SIZE); i++) { + tmpL[i] = (u8) ath5k_get_interpolated_value(target, + (s16) pcinfo_L->freq, + (s16) pcinfo_R->freq, + (s16) tmpL[i], + (s16) tmpR[i]); + } + } + + /* Now we have a set of curves for this + * channel on tmpL (x range is table_max - table_min + * and y values are tmpL[pdg][]) sorted in the same + * order as EEPROM (because we've used the backmaping). + * So for RF5112 it's from higher power to lower power + * and for RF2413 it's from lower power to higher power. + * For RF5111 we only have one curve. */ + + /* Fill min and max power levels for this + * channel by interpolating the values on + * surounding channels to complete the dataset */ + ah->ah_txpower.txp_min_pwr = ath5k_get_interpolated_value(target, + (s16) pcinfo_L->freq, + (s16) pcinfo_R->freq, + pcinfo_L->min_pwr, pcinfo_R->min_pwr); + + ah->ah_txpower.txp_max_pwr = |