/** @file s5933.c
 
   This module managed read / write access on a eeprom using the s5933 controller.
 
   @par CREATION  
   @author Krauth Julien
   @date   11.08.06
   
   @par VERSION
   @verbatim
   $LastChangedRevision: 204 $
   $LastChangedDate: 2006-06-29 11:02:47 +0200 (Thu, 29 Jun 2006) $
   @endverbatim   
   
   @par COMPILER
   @verbatim
   Compiler	: gcc
   Version	: 
   kernel	: 
   @endverbatim  
   
   @par UPDATES
   @verbatim
   Date			: 
   Author		: 
   Description	: 
   @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"


//-----------------------------------------------------------------------------

static inline int32_t i_ADDI_INPORTB(uint32_t ui_Address,unsigned char* pb_Value)
{
   *pb_Value = inb(ui_Address);   
   return(0);
}

static inline int32_t i_ADDI_OUTPORTB(uint32_t ui_Address,unsigned char b_Value)
{
   outb (b_Value, ui_Address);
   return(0);
}


static inline int32_t i_ADDI_INPORTW(uint32_t ui_Address,uint32_t* pui_Value)
{
   *pui_Value = inw(ui_Address);     
   return(0);
}

static inline int32_t i_ADDI_OUTPORTW(uint32_t ui_Address,uint32_t ui_Value)
{
   outw (ui_Value, ui_Address);     
   return(0);
}

static inline int32_t i_ADDI_INPORTDW(uint32_t ui_Address,uint32_t* pul_Value)
{
   *pul_Value = inl(ui_Address);    
   return(0);
}

static inline int32_t i_ADDI_OUTPORTDW(uint32_t ui_Address,uint32_t ul_Value)
{
   outl(ul_Value, ui_Address);   
   return(0);
}

static int32_t EepromWaitBusy (uint32_t	PCIBoardEepromAddress)
{
   unsigned char b_EepromBusy = 0;     
	unsigned long count = 0;
	
	while(1)
	{
	   /*************/
	   /* IMPORTANT */
	   /*************/

	   /************************************************************************/
	   /* An error has been written in the AMCC 5933 book at the page B-13     */
	   /* Ex: if you read a byte and look for the busy status EEPROM=0x80 and  */
	   /*      the operator register is AMCC_OP_REG_MCSR+3     		           */
	   /*      WORD read  EEPROM=0x8000 and AMCC_OP_REG_MCSR+2 		           */
	   /*      DWORD read  EEPROM=0x80000000 and AMCC_OP_REG_MCSR 		       */
	   /************************************************************************/

		i_ADDI_INPORTB (PCIBoardEepromAddress + AMCC_OP_REG_MCSR_S5933, &b_EepromBusy);
	   	b_EepromBusy = b_EepromBusy & EEPROM_BUSY_S5933;
		
		if(b_EepromBusy != EEPROM_BUSY_S5933)
			break;
	   
		udelay(100);
		count++;
		if ( count >= 10000 ) /* timeout: 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_s5933_read(int NbOfWordsToRead,
                           uint32_t PCIBoardEepromAddress,
                           uint16_t EepromStartAddress,
                           uint16_t *DataRead)	
{
   uint8_t b_Counter = 0;
   uint8_t b_ReadByte = 0;
   uint8_t b_ReadLowByte = 0;
   uint8_t b_ReadHighByte = 0;
   uint8_t b_SelectedAddressLow = 0;
   uint8_t b_SelectedAddressHigh = 0;
   uint16_t w_ReadWord = 0;    
   int32_t i_WordCounter = 0;
      
#ifdef CONFIG_APCI_EEPROM_HAS_MODULE   
   MOD_INC_USE_COUNT;
#endif
 
   for(i_WordCounter=0;i_WordCounter<NbOfWordsToRead;i_WordCounter++)
   {         
	   for (b_Counter=0; b_Counter<2; b_Counter++)
	      {
	      b_SelectedAddressLow  = (EepromStartAddress + b_Counter) % 256; //Read the low 8 bit part
	      b_SelectedAddressHigh = (EepromStartAddress + b_Counter) / 256; //Read the high 8 bit part

	      /* Select the load low address mode */
	      i_ADDI_OUTPORTB (PCIBoardEepromAddress + 0x3F, NVCMD_LOAD_LOW);

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

	      /* Load the low address */
	      i_ADDI_OUTPORTB (PCIBoardEepromAddress + 0x3E, b_SelectedAddressLow);

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

	      /* Select the load high address mode */
	      i_ADDI_OUTPORTB (PCIBoardEepromAddress + 0x3F, NVCMD_LOAD_HIGH);

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

	      /* Load the high address */
	      i_ADDI_OUTPORTB (PCIBoardEepromAddress + 0x3E, b_SelectedAddressHigh);

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

	      /* Select the READ mode */
	      i_ADDI_OUTPORTB (PCIBoardEepromAddress + 0x3F, NVCMD_BEGIN_READ);

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

	      /* Read data into the EEPROM */
	      i_ADDI_INPORTB (PCIBoardEepromAddress + 0x3E, &b_ReadByte);

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

	      /* Select the upper address part */
	      if(b_Counter==0)
	         b_ReadLowByte=b_ReadByte;
	      else
	         b_ReadHighByte=b_ReadByte;
	      }

	   w_ReadWord=(b_ReadLowByte | (((uint16_t) b_ReadHighByte) * 256));

	   DataRead[i_WordCounter]=w_ReadWord;
           
       /* To read the next word */
	   EepromStartAddress +=2;	   	                   
	   
   }
   
#ifdef CONFIG_APCI_EEPROM_HAS_MODULE   
   MOD_DEC_USE_COUNT;
#endif
   
   return (0);
}
