/** @file s5920.c
 
   This module managed read / write access on a eeprom using the s5920 controller.
 
   @par CREATION  
   @author Krauth Julien
   @date   06.06.05
   
   @par VERSION
   @verbatim
   $LastChangedRevision$
   $LastChangedDate$
   @endverbatim   
   
   @par COMPILER
   @verbatim
   Compiler	: gcc
   Version	: gcc (GCC) 3.3.6 (Debian 1:3.3.6-5)   
   kernel	: 2.4.26
   @endverbatim  
   
   @par UPDATES
   @verbatim
   Date			: 15.06.05
   Author		: Krauth Julien
   Description	: Add timeout management.
   @endverbatim    
    
   @par LICENCE
   @verbatim
   Copyright (C) 2004,2005  ADDI-DATA GmbH for the source code of this module.
        
        ADDI-DATA GmbH 
        Dieselstrasse 3 
        D-77833 Ottersweier 
        Tel: +19(0)7223/9493-0 
        Fax: +49(0)7223/9493-92 
        http://www.addi-data-com 
        info@addi-data.com 
        
   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

   You shoud also find the complete GPL in the COPYING file 
   accompanying this source code.
   @endverbatim   
 */ 

#include <linux/module.h>
#include <linux/version.h>
#if LINUX_VERSION_CODE <= KERNEL_VERSION(4,14,0)
        #include <asm/uaccess.h>
#else
        #include <linux/uaccess.h>
#endif
#include <asm/io.h>
#include <linux/delay.h>

#include "s5920-s5933-private.h"

/** This function wait that the s5920 is ready to execute a new operation.
 * 
 * @param [in]	PCIBoardEepromAddress	Address of the controller.
 * 
 * @retval 0	No error.
 * @retval -1	Timeout.
 */
static int WaitOnBusy (uint32_t PCIBoardEepromAddress)
{
	uint32_t eeprom_busy = 0; 
	unsigned long count = 0;

	while(1) 
	{
		eeprom_busy = inl (PCIBoardEepromAddress + AMCC_OP_REG_MCSR_S5920);
    	eeprom_busy = eeprom_busy & EEPROM_BUSY_S5920;

		if ( eeprom_busy != EEPROM_BUSY_S5920 )
			break; 
    
    	udelay(100);
    	count ++;
		if (count > 10000 ) /* timeout in 1 second */
			return -1;
	}
        
    return 0;
}       

/** Read data.
 * 
 * @param [in]	NbOfWordsToRead			Number of Words to read.
 * @param [in]	PCIBoardEepromAddress	PCI eeprom address.
 * @param [in]	EepromStartAddress		Start reading address.
 * @param [out]	WordRead				Read data.
 * 
 * @retval	0	No error.
 * @retval	-1	Timeout.
 */
int addi_eeprom_s5920_read(int NbOfWordsToRead,
                           uint32_t PCIBoardEepromAddress,
                           uint16_t EepromStartAddress,
                           uint16_t *DataRead)		
{
	int i_Counter=0;	
	int i_WordCounter;
	int i;
	uint8_t pb_ReadByte[1];
	uint8_t b_ReadLowByte = 0;
	uint8_t b_ReadHighByte = 0;
	uint8_t b_SelectedAddressLow = 0;
	uint8_t b_SelectedAddressHigh = 0;
	uint16_t ReadWord = 0;

#ifdef CONFIG_APCI_EEPROM_HAS_MODULE   
	MOD_INC_USE_COUNT;
#endif
   
		for (i_WordCounter = 0;i_WordCounter<NbOfWordsToRead;i_WordCounter++) 
			{      
			/* Wait on busy */	
			if (WaitOnBusy (PCIBoardEepromAddress) == -1) return -1;

			for (i_Counter=0;i_Counter<2;i_Counter++) 
				{
				b_SelectedAddressLow=(EepromStartAddress+i_Counter) % 256; //Read the low 8 bit part
				b_SelectedAddressHigh=(EepromStartAddress+i_Counter)/256;  //Read the high 8 bit part

				/* Select the load low address mode */
				outb (NVCMD_LOAD_LOW, PCIBoardEepromAddress+AMCC_OP_REG_MCSR_S5920+3);

				/* Wait on busy */	
				if (WaitOnBusy (PCIBoardEepromAddress) == -1) return -1;

				/* Load the low address */
				outb(b_SelectedAddressLow, PCIBoardEepromAddress+AMCC_OP_REG_MCSR_S5920+2);

				/* Wait on busy */	
				if (WaitOnBusy (PCIBoardEepromAddress) == -1) return -1;

				/* Select the load high address mode */
				outb(NVCMD_LOAD_HIGH, PCIBoardEepromAddress+AMCC_OP_REG_MCSR_S5920+3);

				/* Wait on busy */	
				if (WaitOnBusy (PCIBoardEepromAddress) == -1) return -1;

				/* Load the high address */
				outb (b_SelectedAddressHigh, PCIBoardEepromAddress+AMCC_OP_REG_MCSR_S5920+2);

				/* Wait on busy */	
				if (WaitOnBusy (PCIBoardEepromAddress) == -1) return -1;

				/* Select the READ mode */
				outb (NVCMD_BEGIN_READ, PCIBoardEepromAddress+AMCC_OP_REG_MCSR_S5920+3);

				/* Wait on busy */	
				if (WaitOnBusy (PCIBoardEepromAddress) == -1) return -1;

				/* Read data into the EEPROM */
				*pb_ReadByte = inb (PCIBoardEepromAddress+AMCC_OP_REG_MCSR_S5920+2);

				/* Wait on busy */	
				if (WaitOnBusy (PCIBoardEepromAddress) == -1) return -1;

			    /* Select the upper address part */
				if (i_Counter==0)
					b_ReadLowByte=pb_ReadByte[0];
				else
					b_ReadHighByte=pb_ReadByte[0];
            
				for (i=0; i < 10000; i++);   
				}
			
			ReadWord=(b_ReadLowByte|( ( (unsigned short) b_ReadHighByte) *256) );
  
			DataRead[i_WordCounter]=ReadWord;

			/* To read the next word */
			EepromStartAddress +=2; 	   

		}

#ifdef CONFIG_APCI_EEPROM_HAS_MODULE   
	MOD_DEC_USE_COUNT;
#endif
   
		return (0);
}
