/******************************************************************************* Copyright(c) 1999 - 2004 Intel Corporation. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. The full GNU General Public License is included in this distribution in the file called LICENSE. Contact Information: Linux NICS Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 *******************************************************************************/ /********************************************************************** * * * INTEL CORPORATION * * * * This software is supplied under the terms of the license included * * above. All use of this driver must be in accordance with the terms * * of that license. * * * * Module Name: e100_eeprom.c * * * * Abstract: This module contains routines to read and write to a * * serial EEPROM * * * * Environment: This file is intended to be specific to the Linux * * operating system. * * * **********************************************************************/ #include "e100.h" #define CSR_EEPROM_CONTROL_FIELD(bdp) ((bdp)->scb->scb_eprm_cntrl) #define CSR_GENERAL_CONTROL2_FIELD(bdp) \ ((bdp)->scb->scb_ext.d102_scb.scb_gen_ctrl2) #define EEPROM_STALL_TIME 4 #define EEPROM_CHECKSUM ((u16) 0xBABA) #define EEPROM_MAX_WORD_SIZE 256 void e100_eeprom_cleanup(struct e100_private *adapter); u16 e100_eeprom_calculate_chksum(struct e100_private *adapter); static void e100_eeprom_write_word(struct e100_private *adapter, u16 reg, u16 data); void e100_eeprom_write_block(struct e100_private *adapter, u16 start, u16 *data, u16 size); u16 e100_eeprom_size(struct e100_private *adapter); u16 e100_eeprom_read(struct e100_private *adapter, u16 reg); static void shift_out_bits(struct e100_private *adapter, u16 data, u16 count); static u16 shift_in_bits(struct e100_private *adapter); static void raise_clock(struct e100_private *adapter, u16 *x); static void lower_clock(struct e100_private *adapter, u16 *x); static u16 eeprom_wait_cmd_done(struct e100_private *adapter); static void eeprom_stand_by(struct e100_private *adapter); //---------------------------------------------------------------------------------------- // Procedure: eeprom_set_semaphore // // Description: This function set (write 1) Gamla EEPROM semaphore bit (bit 23 word 0x1C in the CSR). // // Arguments: // Adapter - Adapter context // // Returns: true if success // else return false // //---------------------------------------------------------------------------------------- inline u8 eeprom_set_semaphore(struct e100_private *adapter) { u16 data = 0; unsigned long expiration_time = jiffies + HZ / 100 + 1; do { // Get current value of General Control 2 data = readb(&CSR_GENERAL_CONTROL2_FIELD(adapter)); // Set bit 23 word 0x1C in the CSR. data |= SCB_GCR2_EEPROM_ACCESS_SEMAPHORE; writeb(data, &CSR_GENERAL_CONTROL2_FIELD(adapter)); // Check to see if this bit set or not. data = readb(&CSR_GENERAL_CONTROL2_FIELD(adapter)); if (data & SCB_GCR2_EEPROM_ACCESS_SEMAPHORE) { return true; } if (time_before(jiffies, expiration_time)) yield(); else return false; } while (true); } //---------------------------------------------------------------------------------------- // Procedure: eeprom_reset_semaphore // // Description: This function reset (write 0) Gamla EEPROM semaphore bit // (bit 23 word 0x1C in the CSR). // // Arguments: struct e100_private * adapter - Adapter context //---------------------------------------------------------------------------------------- inline void eeprom_reset_semaphore(struct e100_private *adapter) { u16 data = 0; data = readb(&CSR_GENERAL_CONTROL2_FIELD(adapter)); data &= ~(SCB_GCR2_EEPROM_ACCESS_SEMAPHORE); writeb(data, &CSR_GENERAL_CONTROL2_FIELD(adapter)); } //---------------------------------------------------------------------------------------- // Procedure: e100_eeprom_size // // Description: This routine determines the size of the EEPROM. This value should be // checked for validity - ie. is it too big or too small. The size returned // is then passed to the read/write functions. // // Returns: // Size of the eeprom, or zero if an error occurred //---------------------------------------------------------------------------------------- u16 e100_eeprom_size(struct e100_private *adapter) { u16 x, size = 1; // must be one to accumulate a product // if we've already stored this data, read from memory if (adapter->eeprom_size) { return adapter->eeprom_size; } // otherwise, read from the eeprom // Set EEPROM semaphore. if (adapter->rev_id >= D102_REV_ID) { if (!eeprom_set_semaphore(adapter)) return 0; } // enable the eeprom by setting EECS. x = readw(&CSR_EEPROM_CONTROL_FIELD(adapter)); x &= ~(EEDI | EEDO | EESK); x |= EECS; writew(x, &CSR_EEPROM_CONTROL_FIELD(adapter)); // write the read opcode shift_out_bits(adapter, EEPROM_READ_OPCODE, 3); // experiment to discover the size of the eeprom. request register zero // and wait for the eeprom to tell us it has accepted the entire address. x = readw(&CSR_EEPROM_CONTROL_FIELD(adapter)); do { size *= 2; // each bit of address doubles eeprom size x |= EEDO; // set bit to detect "dummy zero" x &= ~EEDI; // address consists of all zeros writew(x, &CSR_EEPROM_CONTROL_FIELD(adapter)); readw(&(adapter->scb->scb_status)); udelay(EEPROM_STALL_TIME); raise_clock(adapter, &x); lower_clock(adapter, &x); // check for "dummy zero" x = readw(&CSR_EEPROM_CONTROL_FIELD(adapter)); if (size > EEPROM_MAX_WORD_SIZE) { size = 0; break; } } while (x & EEDO); // read in the value requested (void) shift_in_bits(adapter); e100_eeprom_cleanup(adapter); // Clear EEPROM Semaphore. if (adapter->rev_id >= D102_REV_ID) { eeprom_reset_semaphore(adapter); } return size; } //---------------------------------------------------------------------------------------- // Procedure: eeprom_address_size // // Description: determines the number of bits in an address for the eeprom acceptable // values are 64, 128, and 256 // Arguments: size of the eeprom // Returns: bits in an address for that size eeprom //---------------------------------------------------------------------------------------- static inline int eeprom_address_size(u16 size) { int isize = size; return (ffs(isize) - 1); } //---------------------------------------------------------------------------------------- // Procedure: e100_eeprom_read // // Description: This routine serially reads one word out of the EEPROM. // // Arguments: // adapter - our adapter context // reg - EEPROM word to read. // // Returns: // Contents of EEPROM word (reg). //---------------------------------------------------------------------------------------- u16 e100_eeprom_read(struct e100_private *adapter, u16 reg) { u16 x, data, bits; // Set EEPROM semaphore. if (adapter->rev_id >= D102_REV_ID) { if (!eeprom_set_semaphore(adapter)) return 0; } // eeprom size is initialized to zero if (!adapter->eeprom_size) adapter->eeprom_size = e100_eeprom_size(adapter); bits = eeprom_address_size(adapter->eeprom_size); // select EEPROM, reset bits, set EECS x = readw(&CSR_EEPROM_CONTROL_FIELD(adapter)); x &= ~(EEDI | EEDO | EESK); x |= EECS; writew(x, &CSR_EEPROM_CONTROL_FIELD(adapter)); // write the read opcode and register number in that order // The opcode is 3bits in length, reg is 'bits' bits long shift_out_bits(adapter, EEPROM_READ_OPCODE, 3); shift_out_bits(adapter, reg, bits); // Now read the data (16 bits) in from the selected EEPROM word data = shift_in_bits(adapter); e100_eeprom_cleanup(adapter); // Clear EEPROM Semaphore. if (adapter->rev_id >= D102_REV_ID) { eeprom_reset_semaphore(adapter); } return data; } //---------------------------------------------------------------------------------------- // Procedure: shift_out_bits // // Description: This routine shifts data bits out to the EEPROM. // // Arguments: // data - data to send to the EEPROM. // count - number of data bits to shift out. // // Returns: (none) //---------------------------------------------------------------------------------------- static void shift_out_bits(struct e100_private *adapter, u16 data, u16 count) { u16 x, mask; mask = 1 << (count - 1); x = readw(&CSR_EEPROM_CONTROL_FIELD(adapter)); x &= ~(EEDO | EEDI); do { x &= ~EEDI; if (data & mask) x |= EEDI; writew(x, &CSR_EEPROM_CONTROL_FIELD(adapter)); readw(&(adapter->scb->scb_status)); /* flush command to card */ udelay(EEPROM_STALL_TIME); raise_clock(adapter, &x); lower_clock(adapter, &x); mask = mask >> 1; } while (mask); x &= ~EEDI; writew(x, &CSR_EEPROM_CONTROL_FIELD(adapter)); } //---------------------------------------------------------------------------------------- // Procedure: raise_clock // // Description: This routine raises the EEPROM's clock input (EESK) // // Arguments: // x - Ptr to the EEPROM control register's current value // // Returns: (none) //---------------------------------------------------------------------------------------- void raise_clock(struct e100_private *adapter, u16 *x) { *x = *x | EESK; writew(*x, &CSR_EEPROM_CONTROL_FIELD(adapter)); readw(&(adapter->scb->scb_status)); /* flush command to card */ udelay(EEPROM_STALL_TIME); } //---------------------------------------------------------------------------------------- // Procedure: lower_clock // // Description: This routine lower's the EEPROM's clock input (EESK) // // Arguments: // x - Ptr to the EEPROM control register's current value // // Returns: (none) //---------------------------------------------------------------------------------------- void lower_clock(struct e100_private *adapter, u16 *x) { *x = *x & ~EESK; writew(*x, &CSR_EEPROM_CONTROL_FIELD(adapter)); readw(&(adapter->scb->scb_status)); /* flush command to card */ udelay(EEPROM_STALL_TIME); } //---------------------------------------------------------------------------------------- // Procedure: shift_in_bits // // Description: This routine shifts data bits in from the EEPROM. // // Arguments: // // Returns: // The contents of that particular EEPROM word //---------------------------------------------------------------------------------------- static u16 shift_in_bits(struct e100_private *adapter) { u16 x, d, i; x = readw(&CSR_EEPROM_CONTROL_FIELD(adapter)); x &= ~(EEDO | EEDI); d = 0; for (i = 0; i < 16; i++) { d <<= 1; raise_clock(adapter, &x); x = readw(&CSR_EEPROM_CONTROL_FIELD(adapter)); x &= ~EEDI; if (x & EEDO) d |= 1; lower_clock(adapter, &x); } return d; } //---------------------------------------------------------------------------------------- // Procedure: e100_eeprom_cleanup // // Description: This routine returns the EEPROM to an idle state //---------------------------------------------------------------------------------------- void e100_eeprom_cleanup(struct e100_private *adapter) { u16 x; x = readw(&CSR_EEPROM_CONTROL_FIELD(adapter)); x &= ~(EECS | EEDI); writew(x, &CSR_EEPROM_CONTROL_FIELD(adapter)); raise_clock(adapter, &x); lower_clock(adapter, &x); } //********************************************************************************** // Procedure: e100_eeprom_update_chksum // // Description: Calculates the checksum and writes it to the EEProm. // It calculates the checksum accroding to the formula: // Checksum = 0xBABA - (sum of first 63 words). // //----------------------------------------------------------------------------------- u16 e100_eeprom_calculate_chksum(struct e100_private *adapter) { u16 idx, xsum_index, checksum = 0; // eeprom size is initialized to zero if (!adapter->eeprom_size) adapter->eeprom_size = e100_eeprom_size(adapter); xsum_index = adapter->eeprom_size - 1; for (idx = 0; idx < xsum_index; idx++) checksum += e100_eeprom_read(adapter, idx); checksum = EEPROM_CHECKSUM - checksum; return checksum; } //---------------------------------------------------------------------------------------- // Procedure: e100_eeprom_write_word // // Description: This routine writes a word to a specific EEPROM location without. // taking EEPROM semaphore and updating checksum. // Use e100_eeprom_write_block for the EEPROM update // Arguments: reg - The EEPROM word that we are going to write to. // data - The data (word) that we are going to write to the EEPROM. //---------------------------------------------------------------------------------------- static void e100_eeprom_write_word(struct e100_private *adapter, u16 reg, u16 data) { u16 x; u16 bits; bits = eeprom_address_size(adapter->eeprom_size); /* select EEPROM, mask off ASIC and reset bits, set EECS */ x = readw(&CSR_EEPROM_CONTROL_FIELD(adapter)); x &= ~(EEDI | EEDO | EESK); writew(x, &CSR_EEPROM_CONTROL_FIELD(adapter)); readw(&(adapter->scb->scb_status)); /* flush command to card */ udelay(EEPROM_STALL_TIME); x |= EECS; writew(x, &CSR_EEPROM_CONTROL_FIELD(adapter)); shift_out_bits(adapter, EEPROM_EWEN_OPCODE, 5); shift_out_bits(adapter, reg, (u16) (bits - 2)); if (!eeprom_wait_cmd_done(adapter)) return; /* write the new word to the EEPROM & send the write opcode the EEPORM */ shift_out_bits(adapter, EEPROM_WRITE_OPCODE, 3); /* select which word in the EEPROM that we are writing to */ shift_out_bits(adapter, reg, bits); /* write the data to the selected EEPROM word */ shift_out_bits(adapter, data, 16); if (!eeprom_wait_cmd_done(adapter)) return; shift_out_bits(adapter, EEPROM_EWDS_OPCODE, 5); shift_out_bits(adapter, reg, (u16) (bits - 2)); if (!eeprom_wait_cmd_done(adapter)) return; e100_eeprom_cleanup(adapter); } //---------------------------------------------------------------------------------------- // Procedure: e100_eeprom_write_block // // Description: This routine writes a block of words starting from specified EEPROM // location and updates checksum // Arguments: reg - The EEPROM word that we are going to write to. // data - The data (word) that we are going to write to the EEPROM. //---------------------------------------------------------------------------------------- void e100_eeprom_write_block(struct e100_private *adapter, u16 start, u16 *data, u16 size) { u16 checksum; u16 i; if (!adapter->eeprom_size) adapter->eeprom_size = e100_eeprom_size(adapter); // Set EEPROM semaphore. if (adapter->rev_id >= D102_REV_ID) { if (!eeprom_set_semaphore(adapter)) return; } for (i = 0; i < size; i++) { e100_eeprom_write_word(adapter, start + i, data[i]); } //Update checksum checksum = e100_eeprom_calculate_chksum(adapter); e100_eeprom_write_word(adapter, (adapter->eeprom_size - 1), checksum); // Clear EEPROM Semaphore. if (adapter->rev_id >= D102_REV_ID) { eeprom_reset_semaphore(adapter); } } //---------------------------------------------------------------------------------------- // Procedure: eeprom_wait_cmd_done // // Description: This routine waits for the the EEPROM to finish its command. // Specifically, it waits for EEDO (data out) to go high. // Returns: true - If the command finished // false - If the command never finished (EEDO stayed low) //---------------------------------------------------------------------------------------- static u16 eeprom_wait_cmd_done(struct e100_private *adapter) { u16 x; unsigned long expiration_time = jiffies + HZ / 100 + 1; eeprom_stand_by(adapter); do { rmb(); x = readw(&CSR_EEPROM_CONTROL_FIELD(adapter)); if (x & EEDO) return true; if (time_before(jiffies, expiration_time)) yield(); else return false; } while (true); } //---------------------------------------------------------------------------------------- // Procedure: eeprom_stand_by // // Description: This routine lowers the EEPROM chip select (EECS) for a few microseconds. //---------------------------------------------------------------------------------------- static void eeprom_stand_by(struct e100_private *adapter) { u16 x; x = readw(&CSR_EEPROM_CONTROL_FIELD(adapter)); x &= ~(EECS | EESK); writew(x, &CSR_EEPROM_CONTROL_FIELD(adapter)); readw(&(adapter->scb->scb_status)); /* flush command to card */ udelay(EEPROM_STALL_TIME); x |= EECS; writew(x, &CSR_EEPROM_CONTROL_FIELD(adapter)); readw(&(adapter->scb->scb_status)); /* flush command to card */ udelay(EEPROM_STALL_TIME); }