bsptemplate/asspandvariant/template_variant/specific/lffsdev.cpp
author Tom Cosgrove <tom.cosgrove@nokia.com>
Fri, 28 May 2010 16:29:07 +0100
changeset 30 8aab599e3476
parent 0 a41df078684a
permissions -rw-r--r--
Fix for bug 2283 (RVCT 4.0 support is missing from PDK 3.0.h) Have multiple extension sections in the bld.inf, one for each version of the compiler. The RVCT version building the tools will build the runtime libraries for its version, but make sure we extract all the other versions from zip archives. Also add the archive for RVCT4.

// Copyright (c) 2004-2009 Nokia Corporation and/or its subsidiary(-ies).
// All rights reserved.
// This component and the accompanying materials are made available
// under the terms of the License "Eclipse Public License v1.0"
// which accompanies this distribution, and is available
// at the URL "http://www.eclipse.org/legal/epl-v10.html".
//
// Initial Contributors:
// Nokia Corporation - initial contribution.
//
// Contributors:
//
// Description:
// template\Template_Variant\Specific\lffsdev.cpp
// Implementation of a Logging Flash file system (LFFS) physical device driver 
// for a standard Common Flash Interface (CFI) based NOR flash chip.
// This file is part of the Template Base port
// N.B. This sample code assumes that:
// (1)	the device does not provide an interrupt i.e. it needs to be polled using a timer 
// to ascertain when an Erase/Write operation has completed.
// (2) the flash chip does not have 'read-while-write' support.
// 
//

#include "lffsdev.h"
#include "variant.h"

#ifdef _DEBUG
#define CHANGE_ERASE_STATE(x)	{TUint32 s=iEraseState; iEraseState=x; __KTRACE_OPT(KLOCDRV,Kern::Printf("ErSt: %d->%d",s,x));}
#else
#define CHANGE_ERASE_STATE(x)	iEraseState=x
#endif

//
// TO DO: (mandatory)
//
// Define the pyhsical base address of the NOR-Flash
// This is only example code... you will need to modify it for your hardware
const TPhysAddr KFlashPhysicalBaseAddress = 0x04000000;


/********************************************
 * Common Flash Interface (CFI) query stuff
 ********************************************/

/**
Read an 8-bit value from the device at the specified offset

@param	aOffset	the address in device words 
*/
TUint32 DMediaDriverFlashTemplate::ReadQueryData8(TUint32 aOffset)
	{
	volatile TUint8* pF=(volatile TUint8*)(iBase+FLASH_ADDRESS_IN_BYTES(aOffset));
	return pF[0];
	}

/**
Read a 16-bit value from the device at the specified offset

@param	aOffset	the address in device words 
*/
TUint32 DMediaDriverFlashTemplate::ReadQueryData16(TUint32 aOffset)
	{
	volatile TUint8* pF=(volatile TUint8*)(iBase);
	return 
		 pF[FLASH_ADDRESS_IN_BYTES(aOffset+0)] | 
		(pF[FLASH_ADDRESS_IN_BYTES(aOffset+1)] << 8);
	}

/**
 Put the device into query mode to read the flash parameters.
 */
void DMediaDriverFlashTemplate::ReadFlashParameters()
	{
	volatile TFLASHWORD* pF=(volatile TFLASHWORD*)iBase + KCmdReadQueryOffset;
	*pF=KCmdReadQuery;

	TUint32 qd=ReadQueryData16(KQueryOffsetQRY)|(ReadQueryData8(KQueryOffsetQRY+2)<<16);
	__KTRACE_OPT(KLOCDRV,Kern::Printf("Flash:Query QRY=%08x",qd));
	__ASSERT_ALWAYS(qd==0x595251,FLASH_FAULT());

	qd = FLASH_BUS_DEVICES << ReadQueryData8(KQueryOffsetSizePower);

	__KTRACE_OPT(KLOCDRV,Kern::Printf("Flash:Query Size=%08x",qd));
	iTotalSize=qd;

	qd = FLASH_BUS_DEVICES << ReadQueryData16(KQueryOffsetWriteBufferSizePower);
	__KTRACE_OPT(KLOCDRV,Kern::Printf("Flash:Query WBSize=%08x",qd));
	iWriteBufferSize=qd;

	qd = (ReadQueryData16(KQueryOffsetEraseBlockSize)) << (8 + FLASH_BUS_DEVICES-1);
	__KTRACE_OPT(KLOCDRV,Kern::Printf("Flash:Query EBSize=%08x",qd));
	iEraseBlockSize=qd;

	*pF=KCmdReadArray;
	}


/********************************************
 * Common Flash Interface (CFI) main code
 ********************************************/

/**
NOR flash LFFS constructor.

@param aMediaId            Media id number from ELOCD
*/
DMediaDriverFlashTemplate::DMediaDriverFlashTemplate(TInt aMediaId)
	:	DMediaDriverFlash(aMediaId),
		iHoldOffTimer(HoldOffTimerFn,this),
		iEventDfc(EventDfc,this,NULL,2)
	{
	// iWriteState = EWriteIdle;
	// iEraseState = EEraseIdle;
	}

/**
Device specific implementation of the NOR LFFS initialisation routine.

@see DMediaDriverFlash::Initialise
@return KErrNone unless the write data buffer couldn't be allocated or the
         timer interrupt could not be bound.
 */
TInt DMediaDriverFlashTemplate::Initialise()
	{
	iEventDfc.SetDfcQ(iPrimaryMedia->iDfcQ);
	iData=(TUint8*)Kern::Alloc(KDataBufSize);
	if (!iData)
		return KErrNoMemory;

	// Create temporary HW chunk to read FLASH device parameters (especially size)
	DPlatChunkHw* pC = NULL;
	TInt r = DPlatChunkHw::New(pC, KFlashPhysicalBaseAddress, 0x1000, EMapAttrSupRw|EMapAttrFullyBlocking);
	if (r!=KErrNone)
		return r;
	iBase = pC->LinearAddress();
	ReadFlashParameters();
	// close temporary chunk and open chunk with correct size
	pC->Close(NULL);
	r = DPlatChunkHw::New(iFlashChunk, KFlashPhysicalBaseAddress, iTotalSize, EMapAttrSupRw|EMapAttrFullyBlocking);
	if (r!=KErrNone)
		return r;
	iBase = iFlashChunk->LinearAddress();

	r=Interrupt::Bind(KIntIdTimer1, Isr, this);
	if (r!=KErrNone)
		{
		__KTRACE_OPT(KLOCDRV, Kern::Printf("Flash:Isr Bind failed"));
		return r;
		}

	// TO DO: (mandatory)
	// Write to the appropriate hardware register(s) to
	// configure (if necessary) and enable the timer hardware
	//

	// Enable the timer interrupt
	Interrupt::Enable(KIntIdTimer1);
	
	return KErrNone;
	}

/**
Used by the generic flash media driver code to get the erase block size in
bytes. 
 */
TUint32 DMediaDriverFlashTemplate::EraseBlockSize()
	{
	return iEraseBlockSize;
	}

/**
@return Return size of lffs in bytes
*/
TUint32 DMediaDriverFlashTemplate::TotalSize()
	{
	return iTotalSize;
	}

/**
Read at the location indicated by DMediaDriverFlash::iReadReq. 
Where Pos() is the read location

@return >0			Defer request to ELOCD. A write is in progress
@return KErrNone	Erase has been started
@return <0			An error has occured.
*/
TInt DMediaDriverFlashTemplate::DoRead()
	{
	if (iWriteReq)
		return KMediaDriverDeferRequest;	// write in progress so defer read
	__KTRACE_OPT(KLOCDRV,Kern::Printf("Flash:DoRead"));
	if (iEraseState==EEraseIdle || iEraseState==ESuspended)
		{
		// can do the read now
		TInt pos=(TInt)iReadReq->Pos();
		TInt len=(TInt)iReadReq->Length();

		__KTRACE_OPT(KLOCDRV,Kern::Printf("Flash:DoRead ibase: %x, pos: %x, len: %x",iBase,pos,len));

		// Issue a read array command
		// Porting note: Some devices may work without this step.
		// Ensure that the write is always dword aligned
		volatile TFLASHWORD* pF=(volatile TFLASHWORD*)((iBase+pos)&0xFFFFFFF0);
		*pF=KCmdReadArray;

		TPtrC8 des((const TUint8*)(iBase+pos),len);
		TInt r=iReadReq->WriteRemote(&des,0);
		Complete(EReqRead,r);

		// resume erase if necessary
		if (iEraseState==ESuspended)
			StartErase();
		}
	else if (iEraseState==EErase)
		{
		// erase in progress - suspend it
		SuspendErase();
		}
	else if (iEraseState==EEraseNoSuspend)
		CHANGE_ERASE_STATE(ESuspendPending);	// wait for suspend to complete
	
	
	return KErrNone;
	}

/**
Write at the location indicated by DMediaDriverFlash::iWriteReq

@return >0			Defer request to ELOCD. A read is in progress
@return KErrNone	Erase has been started
@return <0			An error has occured.
 */
TInt DMediaDriverFlashTemplate::DoWrite()
	{
	if (iReadReq)
		return KMediaDriverDeferRequest;	// read in progress so defer write

	TInt pos=(TInt)iWriteReq->Pos();
	TInt len=(TInt)iWriteReq->Length();
	if (len==0)
		return KErrCompletion;
	TUint32 wb_mask=iWriteBufferSize-1;
	iWritePos=pos & ~wb_mask;	// round media position down to write buffer boundary
	TInt wb_off=pos & wb_mask;	// how many bytes of padding at beginning
	TInt start_len=Min(len,KDataBufSize-(TInt)wb_off);
	TInt write_len=(start_len+wb_off+wb_mask)&~wb_mask;
	memset(iData,0xff,iWriteBufferSize);
	memset(iData+write_len-iWriteBufferSize,0xff,iWriteBufferSize);
	TPtr8 des(iData+wb_off,0,start_len);
	TInt r=iWriteReq->ReadRemote(&des,0);
	if (r!=KErrNone)
		return r;
	iWriteReq->RemoteDesOffset()+=start_len;
	iWriteReq->Length()-=start_len;
	iDataBufPos=0;
	iDataBufRemain=write_len;
	iWriteError=KErrNone;

	__KTRACE_OPT(KLOCDRV,Kern::Printf("Flash:Write iWritePos=%08x iDataBufRemain=%x",iWritePos,iDataBufRemain));
	__KTRACE_OPT(KLOCDRV,Kern::Printf("Flash:Write Pos=%08x Length=%08x RemDesOff=%08x",
										(TInt)iWriteReq->Pos(),(TInt)iWriteReq->Length(),iWriteReq->RemoteDesOffset()));

	if (iEraseState==EEraseIdle || iEraseState==ESuspended)
		{
		// can start the write now
		iWriteState=EWriting;
		WriteStep();
		}
	else if (iEraseState==EErase)
		{
		// erase in progress - suspend it
		SuspendErase();
		}
	else if (iEraseState==EEraseNoSuspend)
		CHANGE_ERASE_STATE(ESuspendPending);	// wait for suspend to complete
	
	return KErrNone;
	}

void DMediaDriverFlashTemplate::WriteStep()
	{
	__KTRACE_OPT(KLOCDRV,Kern::Printf("Flash:WriteStep @%08x",iWritePos));
	if (iDataBufRemain)
		{
		// still data left in buffer
		volatile TFLASHWORD* pF=(volatile TFLASHWORD*)(iBase+iWritePos);
		TInt i=KMaxWriteSetupAttempts;
		*pF=KCmdClearStatusRegister;
		TUint32 s=0;
		for (; i>0 && ((s&KStsReady)!=KStsReady); --i)
			{
			*pF=KCmdWriteToBuffer;		// send write command
			*pF=KCmdReadStatusRegister;	// send read status command
			s=*pF;						// read status reg
			}
		__KTRACE_OPT(KLOCDRV,Kern::Printf("i=%d, s=%08x",i,s));

		// calculate the buffer size in words -1 
		TFLASHWORD l = (FLASH_BYTES_TO_WORDS(iWriteBufferSize)) - 1;

#if FLASH_BUS_DEVICES == 2		// 2x16bit or 2x8bit devices
		l|= l<< BUS_WIDTH_PER_DEVICE;
#elif FLASH_BUS_DEVICES == 4	// 4x8bit device
		l|= (l<<BUS_WIDTH_PER_DEVICE) | (l<<BUS_WIDTH_PER_DEVICE*2) (l<<BUS_WIDTH_PER_DEVICE*3);
#endif

		// write the data length in words to the device(s)
		*pF=l;

		const TFLASHWORD* pS=(const TFLASHWORD*)(iData+iDataBufPos);

		// write the data
		TInt len;
		for (len = l; len>=0; len--)
			{
			*pF++=*pS++;
			}
	
		*(volatile TFLASHWORD *)(iBase+iWritePos) = KCmdConfirm; 

		// set up timer to poll for completion
		StartPollTimer(KFlashWriteTimerPeriod,KFlashWriteTimerRetries);

		iWritePos+=iWriteBufferSize;
		iDataBufPos+=iWriteBufferSize;
		iDataBufRemain-=iWriteBufferSize;
		if (!iDataBufRemain)
			{
			// refill buffer
			TInt len=(TInt)iWriteReq->Length();
			if (!len)
				return;	// all data has been written, complete request next time
			TUint32 wb_mask=iWriteBufferSize-1;
			TInt block_len=Min(len,KDataBufSize);
			TInt write_len=(block_len+wb_mask)&~wb_mask;
			memset(iData+write_len-iWriteBufferSize,0xff,iWriteBufferSize);
			TPtr8 des(iData,0,block_len);
			TInt r=iWriteReq->ReadRemote(&des,0);
			if (r!=KErrNone)
				{
				iWriteError=r;
				return;	// leave iDataBufRemain=0 so request is terminated when write completes
				}
			iWriteReq->RemoteDesOffset()+=block_len;
			iWriteReq->Length()-=block_len;
			iDataBufPos=0;
			iDataBufRemain=write_len;
			}
		}
	else
		{
		// write request should have completed, maybe with an error
		__ASSERT_ALWAYS(iWriteReq->Length()==0 || iWriteError,FLASH_FAULT());
		iWriteState=EWriteIdle;
		Complete(EReqWrite,iWriteError);
		if (iEraseState==ESuspended)
			StartErase();
		}
	}

/**
Erase at the location indicated by DMediaDriverFlash::iEraseReq

@return >0			Defer request to ELOCD. Read or a write is in progress
@return KErrNone	Erase has been started
@return <0			An error has occured.
 */
TInt DMediaDriverFlashTemplate::DoErase()
	{
	if (iReadReq || iWriteReq)
		return KMediaDriverDeferRequest;		// read or write in progress so defer this request
	TUint32 pos=(TUint32)iEraseReq->Pos();
	TUint32 len=(TUint32)iEraseReq->Length();
	__KTRACE_OPT(KLOCDRV,Kern::Printf("Flash:DoErase %d@%08x",len,pos));
	if (len!=iEraseBlockSize)
		return KErrArgument;	// only allow single-block erase
	if (pos & (iEraseBlockSize-1))
		return KErrArgument;	// start position must be on erase block boundary
	iErasePos=pos;
	__ASSERT_ALWAYS(iEraseState==EEraseIdle,FLASH_FAULT());
	StartErase();
	return KErrNone;
	}

void DMediaDriverFlashTemplate::StartHoldOffTimer()
	{
	// if this is a retry, don't allow suspends
	if (iEraseAttempt==0)
		iHoldOffTimer.OneShot(KEraseSuspendHoldOffTime);
	}

void DMediaDriverFlashTemplate::CancelHoldOffTimer()
	{
	iHoldOffTimer.Cancel();
	ClearEvents(EHoldOffEnd);
	}

void DMediaDriverFlashTemplate::ClearEvents(TUint32 aEvents)
	{
	__e32_atomic_and_ord32(&iEvents, ~aEvents);
	}

void DMediaDriverFlashTemplate::HoldOffTimerFn(TAny* aPtr)
	{
	DMediaDriverFlashTemplate* p=(DMediaDriverFlashTemplate*)aPtr;
	p->IPostEvents(EHoldOffEnd);
	}

void DMediaDriverFlashTemplate::StartPollTimer(TUint32 aPeriod, TUint32 aRetries)
	{
	__KTRACE_OPT(KLOCDRV,Kern::Printf("Flash:Tmr %d * %d",aPeriod,aRetries));

	ClearEvents(EPollTimer);
	iPollPeriod=aPeriod;
	iPollRetries=aRetries;
	StartPollTimer();
	}

void DMediaDriverFlashTemplate::StartPollTimer()
	{
	// TO DO: (mandatory)
	// Configure the hardware timer to expire after iPollPeriod ticks
	// and start the timer
	
	}

void DMediaDriverFlashTemplate::EventDfc(TAny* aPtr)
	{
	DMediaDriverFlashTemplate* p=(DMediaDriverFlashTemplate*)aPtr;
	TUint32 e = __e32_atomic_swp_ord32(&p->iEvents, 0);
	if (e)
		p->HandleEvents(e);
	}

void DMediaDriverFlashTemplate::HandleEvents(TUint32 aEvents)
	{
	__KTRACE_OPT(KLOCDRV,Kern::Printf("Flash:Events %x",aEvents));
	if (aEvents & EHoldOffEnd)
		{
		if (iEraseState==ESuspendPending)
			{
			SuspendErase();
			}
		else if (iEraseState==EEraseNoSuspend)
			{
			CHANGE_ERASE_STATE(EErase);	// can now be suspended
			}
		else
			{
			__KTRACE_OPT(KPANIC,Kern::Printf("iEraseState=%d",iEraseState));
			FLASH_FAULT();
			}
		}
	if (aEvents & EPollTimer)
		{
		volatile TFLASHWORD* pF=(volatile TFLASHWORD*)iBase;
		*pF=KCmdReadStatusRegister;
		if ((*pF & KStsReady)!=KStsReady)
			{
			// not ready yet
			if (--iPollRetries)
				{
				// try again
				StartPollTimer();
				}
			else
				// timed out
				aEvents|=ETimeout;
			}
		else
			{
			// ready
			TFLASHWORD s=*pF;	// read full status value
			*pF=KCmdClearStatusRegister;
			DoFlashReady(s);
			}
		}
	if (aEvents & ETimeout)
		{
		DoFlashTimeout();
		}
	}

void DMediaDriverFlashTemplate::StartErase()
	{
	TFLASHWORD s=KStsReady;
	TInt i;
	volatile TFLASHWORD* pF=(volatile TFLASHWORD*)(iBase+iErasePos);
	__KTRACE_OPT(KLOCDRV,Kern::Printf("Flash:StartErase %08x",pF));
	switch (iEraseState)
		{
		case EEraseIdle:	// first attempt to erase
			iEraseAttempt=-1;
			// coverity[fallthrough]
			// fallthrough after attempt
		case EErase:	// retry after verify failed
		case EEraseNoSuspend:
			++iEraseAttempt;
			*pF=KCmdBlockErase;
			*pF=KCmdConfirm;
			CHANGE_ERASE_STATE(EEraseNoSuspend);
			iEraseError=0;
			StartHoldOffTimer();
			break;
		case ESuspended:
			*pF=KCmdClearStatusRegister;
			*pF=KCmdEraseResume;
			CHANGE_ERASE_STATE(EEraseNoSuspend);
			i=KMaxEraseResumeAttempts;
			for (; i>0 && ((s&KStsReady)!=0); --i)
				{
				*pF=KCmdReadStatusRegister;	// send read status command
				s=*pF;						// read status reg
				s=*pF;						// read status reg
				}
			__KTRACE_OPT(KLOCDRV,Kern::Printf("RESUME: i=%d, s=%08x",i,s));
			StartHoldOffTimer();
			break;
		default:
			__KTRACE_OPT(KPANIC,Kern::Printf("iEraseState=%d",iEraseState));
			FLASH_FAULT();
		}
	StartPollTimer(KFlashEraseTimerPeriod,KFlashEraseTimerRetries);
	}

void DMediaDriverFlashTemplate::SuspendErase()
	{
	__ASSERT_ALWAYS(iEraseState==EErase || iEraseState==ESuspendPending,FLASH_FAULT());
	volatile TFLASHWORD* pF=(volatile TFLASHWORD*)(iBase+iErasePos);
	__KTRACE_OPT(KLOCDRV,Kern::Printf("Flash:SuspendErase %08x",pF));
	*pF=KCmdEraseSuspend;
	CHANGE_ERASE_STATE(ESuspending);
	StartPollTimer(KFlashSuspendTimerPeriod,KFlashSuspendTimerRetries);
	}

void DMediaDriverFlashTemplate::StartPendingRW()
	{
	// start any pending read or write requests
	if (iReadReq)
		DoRead();
	if (iWriteReq)
		{
		// can start the write now
		iWriteState=EWriting;
		WriteStep();
		}
	}

void DMediaDriverFlashTemplate::DoFlashReady(TUint32 aStatus)
	{
	// could be write completion, erase completion or suspend completion
	if (iWriteState==EWriting)
		{
		// write completion
		__KTRACE_OPT(KLOCDRV,Kern::Printf("Flash:WriteComplete %08x",aStatus));
		TUint32 err=aStatus & (KStsWriteError|KStsVppLow|KStsLocked);
		if (err)
			{
			iWriteState=EWriteIdle;
			Complete(EReqWrite,KErrGeneral);
			if (iEraseState==ESuspended)
				StartErase();
			}
		else
			WriteStep();
		return;
		}

	// put the FLASH back into read mode
	volatile TFLASHWORD* pF=(volatile TFLASHWORD*)(iBase+iErasePos);
	*pF=KCmdReadArray;

	if (iEraseState==ESuspending)
		{
		// erase suspend completion
		__KTRACE_OPT(KLOCDRV,Kern::Printf("Flash:SuspendComplete %08x",aStatus));

		// accumulate errors during erase
		iEraseError|=(aStatus & (KStsEraseError|KStsVppLow|KStsLocked));

		if (aStatus & KStsSuspended)
			{
			// at least one of the two FLASH devices has suspended
			CHANGE_ERASE_STATE(ESuspended);

			// start any pending read or write requests
			StartPendingRW();
			return;					// in case erase has been resumed by DoRead()
			}

		// erase completed before we suspended it
		CHANGE_ERASE_STATE(EErase);
		}
	if (iEraseState==EErase || iEraseState==EEraseNoSuspend)
		{
		// erase completion
		__KTRACE_OPT(KLOCDRV,Kern::Printf("Flash:EraseComplete %08x",aStatus));
		CancelHoldOffTimer();

		// accumulate errors during erase
		iEraseError|=(aStatus & (KStsEraseError|KStsVppLow|KStsLocked));

		TFLASHWORD x = FLASH_ERASE_WORD_VALUE;

		// if no device error, verify that erase was successful
		if (!iEraseError)
			{
			volatile TFLASHWORD* p=pF;
			volatile TFLASHWORD* pE=p + FLASH_BYTES_TO_WORDS(iEraseBlockSize);
			while(p<pE)
				x&=*p++;
			}
		else
			{
			}
		if (x == FLASH_ERASE_WORD_VALUE)
			{
			// erase OK
			__KTRACE_OPT(KLOCDRV,Kern::Printf("Flash:VerifyErase OK"));
			CHANGE_ERASE_STATE(EEraseIdle);

			// complete the erase request
			TInt r=iEraseError?KErrGeneral:KErrNone;
			Complete(EReqErase,r);

			// start any pending read or write requests
			StartPendingRW();
			}
		else
			{
			// erase failed, so retry
			__KTRACE_OPT(KLOCDRV,Kern::Printf("Flash:VerifyErase BAD"));
			StartErase();
			}
		}
	}

void DMediaDriverFlashTemplate::DoFlashTimeout()
	{
	// TO DO: (optional)
	// Take appropriate action to handle a timeout.
	FLASH_FAULT();	// // EXAMPLE ONLY:
	}

DMediaDriverFlash* DMediaDriverFlash::New(TInt aMediaId)
	{
	return new DMediaDriverFlashTemplate(aMediaId);
	}

void DMediaDriverFlashTemplate::Isr(TAny* aPtr)
	{
	DMediaDriverFlashTemplate& d=*(DMediaDriverFlashTemplate*)aPtr;

	
	// TO DO: (mandatory)
	// Write to the timer hardware register(s) to
	// clear the timer interrupt
	//
	
	d.IPostEvents(EPollTimer);
	}

void DMediaDriverFlashTemplate::IPostEvents(TUint32 aEvents)
	{
	iEvents|=aEvents;
	iEventDfc.Add();
	}