kernel/eka/drivers/ecomm/uart16550.cpp
changeset 0 a41df078684a
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kernel/eka/drivers/ecomm/uart16550.cpp	Mon Oct 19 15:55:17 2009 +0100
@@ -0,0 +1,502 @@
+// Copyright (c) 1998-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:
+// e32\drivers\ecomm\uart16550.cpp
+// PDD for 16550 UART
+// 
+//
+
+#include <drivers/comm.h>
+#include <assp.h>
+#include <var_defs.h>
+#include <uart16550.h>
+#include <e32hal.h>
+
+_LIT(KPddName,"Comm.16550");
+
+#define __COMMS_MACHINE_CODED__
+#ifdef __COMMS_MACHINE_CODED__
+#define DBASE_VPTR_OFFSET	4
+#define RX_ISR_VT_OFFSET	0x24
+#define CHK_TXB_VT_OFFSET	0x28
+#define STATE_ISR_VT_OFFSET	0x2C
+#endif
+
+// needs ldd version..
+const TInt KMinimumLddMajorVersion=1;
+const TInt KMinimumLddMinorVersion=1;
+const TInt KMinimumLddBuild=122;
+
+// configuration data
+static const TUint16 BaudRateDivisor[19] =
+	{
+	2304,	1536,	1047,	860,	768,	384,	192,	96,
+	64,		58,		48,		32,		24,		16,		12,		6,
+	3,		2,		1
+	};
+
+class DDriverComm : public DPhysicalDevice
+	{
+public:
+	DDriverComm();
+	virtual TInt Install();
+	virtual void GetCaps(TDes8 &aDes) const;
+	virtual TInt Create(DBase*& aChannel, TInt aUnit, const TDesC8* anInfo, const TVersion &aVer);
+	virtual TInt Validate(TInt aUnit, const TDesC8* anInfo, const TVersion &aVer);
+public:
+	TDynamicDfcQue* iDfcQ;
+	};
+
+class DComm16550 : public DComm
+	{
+public:
+	DComm16550();
+	~DComm16550();
+	TInt DoCreate(TInt aUnit, const TDesC8* anInfo);
+public:
+	virtual TInt Start();
+	virtual void Stop(TStopMode aMode);
+	virtual void Break(TBool aState);
+	virtual void EnableTransmit();
+	virtual TUint Signals() const;
+	virtual void SetSignals(TUint aSetMask,TUint aClearMask);
+	virtual TInt ValidateConfig(const TCommConfigV01 &aConfig) const;
+	virtual void Configure(TCommConfigV01 &aConfig);
+	virtual void Caps(TDes8 &aCaps) const;
+	virtual TInt DisableIrqs();
+	virtual void RestoreIrqs(TInt aIrq);
+	virtual TDfcQue* DfcQ(TInt aUnit);
+	virtual void CheckConfig(TCommConfigV01& aConfig);
+public:
+	static void Isr(TAny* aPtr);
+public:
+	TInt iInterruptId;
+	TInt iUnit;
+	T16550Uart* iUart;
+	};
+
+
+DDriverComm::DDriverComm()
+//
+// Constructor
+//
+	{
+	iUnitsMask=~(0xffffffffu<<KNum16550Uarts);
+	iVersion=TVersion(KCommsMajorVersionNumber,KCommsMinorVersionNumber,KCommsBuildVersionNumber);
+	}
+
+
+TInt DDriverComm::Install()
+//
+// Install the driver
+//
+	{
+	// Allocate a kernel thread to run the DFC 
+	TInt r = Kern::DynamicDfcQCreate(iDfcQ, KUart16550ThreadPriority, KUar16550tDriverThread);
+	if (r == KErrNone)
+		{
+		iDfcQ->SetRealtimeState(ERealtimeStateOff);  
+		r = SetName(&KPddName);
+		}
+	return r;
+	}
+
+/**
+  Destructor
+*/
+DDriverComm::~DDriverComm()
+	{
+	if (iDfcQ)
+		iDfcQ->Destroy();
+	}
+
+void Get16550CommsCaps(TDes8& aCaps, TInt aUnit)
+	{
+	TCommCaps3 capsBuf;
+	TCommCapsV03 &c=capsBuf();
+	c.iRate=KCapsBps110|KCapsBps150|KCapsBps300|KCapsBps600\
+		|KCapsBps1200|KCapsBps2400|KCapsBps4800|KCapsBps9600\
+		|KCapsBps19200|KCapsBps38400|KCapsBps57600|KCapsBps115200;
+	c.iDataBits=KCapsData5|KCapsData6|KCapsData7|KCapsData8;
+	c.iStopBits=KCapsStop1|KCapsStop2;
+	c.iParity=KCapsParityNone|KCapsParityEven|KCapsParityOdd|KCapsParityMark|KCapsParitySpace;
+	c.iHandshake=KCapsObeyXoffSupported|KCapsSendXoffSupported|
+					KCapsObeyCTSSupported|KCapsFailCTSSupported|
+					KCapsObeyDSRSupported|KCapsFailDSRSupported|
+					KCapsObeyDCDSupported|KCapsFailDCDSupported|
+					KCapsFreeRTSSupported|KCapsFreeDTRSupported;
+	c.iSIR=0;
+	c.iSignals=KCapsSignalCTSSupported|KCapsSignalRTSSupported|KCapsSignalDTRSupported|
+						KCapsSignalDSRSupported|KCapsSignalDCDSupported|KCapsSignalRNGSupported;
+	c.iFifo=KCapsHasFifo;
+	c.iNotificationCaps=KNotifyDataAvailableSupported|KNotifySignalsChangeSupported;
+	c.iRoleCaps=0;
+	c.iFlowControlCaps=0;
+	c.iBreakSupported=ETrue;
+	aCaps.FillZ(aCaps.MaxLength());
+	aCaps=capsBuf.Left(Min(capsBuf.Length(),aCaps.MaxLength()));
+	}
+
+void DDriverComm::GetCaps(TDes8 &aDes) const
+//
+// Return the drivers capabilities
+//
+	{
+	Get16550CommsCaps(aDes, 0);
+	}
+
+TInt DDriverComm::Create(DBase*& aChannel, TInt aUnit, const TDesC8* anInfo, const TVersion& aVer)
+//
+// Create a driver
+//
+	{
+	DComm16550* pD=new DComm16550;
+	aChannel=pD;
+	TInt r=KErrNoMemory;
+	if (pD)
+		r=pD->DoCreate(aUnit,anInfo);
+	return r;
+	}
+
+TInt DDriverComm::Validate(TInt aUnit, const TDesC8* /*anInfo*/, const TVersion& aVer)
+//
+//	Validate the requested configuration
+//
+	{
+	if ((!Kern::QueryVersionSupported(iVersion,aVer)) || (!Kern::QueryVersionSupported(aVer,TVersion(KMinimumLddMajorVersion,KMinimumLddMinorVersion,KMinimumLddBuild))))
+		return KErrNotSupported;
+	if (aUnit<0 || aUnit>=KNum16550Uarts)
+		return KErrNotSupported;
+	return KErrNone;
+	}
+
+DComm16550::DComm16550()
+//
+// Constructor
+//
+	{
+//	iTransmitting=EFalse;
+	iInterruptId=-1;		// -1 means not bound
+	}
+
+DComm16550::~DComm16550()
+//
+// Destructor
+//
+	{
+	if (iInterruptId>=0)
+		Interrupt::Unbind(iInterruptId);
+	}
+
+TInt DComm16550::DoCreate(TInt aUnit, const TDesC8* /*anInfo*/)
+//
+// Sets up the PDD
+//
+	{
+	iUnit=aUnit;
+	TInt irq=IrqFromUnit(aUnit);
+
+	// bind to UART interrupt
+	TInt r=Interrupt::Bind(irq,Isr,this);
+	if (r==KErrNone)
+		{
+		iInterruptId=irq;
+		iUart=UartFromUnit(aUnit);
+		iUart->SetIER(0);
+		iUart->SetLCR(0);
+		iUart->SetFCR(0);
+		iUart->SetMCR(0);
+		}
+	return r;
+	}
+
+TDfcQue* DComm16550::DfcQ(TInt /*aUnit*/)
+//
+// Return the DFC queue to be used for this device
+// For PC cards, use the PC card controller thread for the socket in question.
+//
+
+	{
+	return iDfcQ;
+	}
+
+TInt DComm16550::Start()
+//
+// Start receiving characters
+//
+	{
+	// if EnableTransmit() called before Start()
+	iTransmitting=EFalse;
+	iUart->SetIER(K16550IER_RDAI|K16550IER_RLSI|K16550IER_MSI);
+	iLdd->UpdateSignals(Signals());
+	Interrupt::Enable(iInterruptId);
+	return KErrNone;
+	}
+
+TBool FinishedTransmitting(TAny* aPtr)
+	{
+	DComm16550& d=*(DComm16550*)aPtr;
+	return d.iUart->TestLSR(K16550LSR_TxIdle);
+	}
+
+void DComm16550::Stop(TStopMode aMode)
+//
+// Stop receiving characters
+//
+	{
+	switch (aMode)
+		{
+		case EStopNormal:
+		case EStopPwrDown:
+			iUart->SetIER(0);
+			Interrupt::Disable(iInterruptId);
+			iTransmitting=EFalse;
+
+			// wait for uart to stop tranmitting
+			Kern::PollingWait(FinishedTransmitting,this,3,100);
+			break;
+		case  EStopEmergency:
+			iUart->SetIER(0);
+			Interrupt::Disable(iInterruptId);
+			iTransmitting=EFalse;
+			break;
+		}
+	}
+
+void DComm16550::Break(TBool aState)
+//
+// Start or stop the uart breaking
+//
+	{
+	if (aState)
+		iUart->ModifyLCR(0,K16550LCR_TxBreak);
+	else
+		iUart->ModifyLCR(K16550LCR_TxBreak,0);
+	}
+
+void DComm16550::EnableTransmit()
+//
+// Start sending characters.
+//
+	{
+	TBool tx = (TBool)__e32_atomic_swp_ord32(&iTransmitting, 1);
+	if (tx)
+		return;
+	iUart->ModifyIER(0,K16550IER_THREI);
+	}
+
+TUint DComm16550::Signals() const
+//
+// Read and translate the modem lines
+//
+	{
+	TUint msr=iUart->MSR();
+	msr=((msr>>4)&0x0f);			// true input signals
+	TUint sig=msr & 3;				// CTS,DSR OK
+	if (msr & 4)
+		sig|=KSignalRNG;			// swap DCD,RNG
+	if (msr & 8)
+		sig|=KSignalDCD;
+	return sig;
+	}
+
+void DComm16550::SetSignals(TUint aSetMask, TUint aClearMask)
+//
+// Set signals.
+//
+	{
+	TUint set=0;
+	TUint clear=0;
+	if (aSetMask & KSignalRTS)
+		set|=K16550MCR_RTS;
+	if (aSetMask & KSignalDTR)
+		set|=K16550MCR_DTR;
+	if (aClearMask & KSignalRTS)
+		clear|=K16550MCR_RTS;
+	if (aClearMask & KSignalDTR)
+		clear|=K16550MCR_DTR;
+	iUart->ModifyMCR(clear,set);
+	}
+
+TInt DComm16550::ValidateConfig(const TCommConfigV01 &aConfig) const
+//
+// Check a config structure.
+//
+	{
+	if (aConfig.iSIREnable==ESIREnable)
+		return KErrNotSupported;
+	switch (aConfig.iParity)
+		{
+		case EParityNone:
+		case EParityOdd:
+		case EParityEven:
+		case EParityMark:
+		case EParitySpace:
+			break;
+		default:
+			return KErrNotSupported;
+		}
+	if (TUint(aConfig.iRate)>TUint(EBps115200))
+		return KErrNotSupported;
+	return KErrNone;
+	}
+
+void DComm16550::CheckConfig(TCommConfigV01& aConfig)
+	{
+	// do nothing
+	}
+
+TInt DComm16550::DisableIrqs()
+//
+// Disable normal interrupts
+//
+	{
+	
+	return NKern::DisableInterrupts(1);
+	}
+
+void DComm16550::RestoreIrqs(TInt aLevel)
+//
+// Restore normal interrupts
+//
+	{
+	
+	NKern::RestoreInterrupts(aLevel);
+	}
+
+void DComm16550::Configure(TCommConfigV01 &aConfig)
+//
+// Set up the Uart
+//
+	{
+	// wait for uart to stop tranmitting
+	Kern::PollingWait(FinishedTransmitting,this,3,100);
+
+	TUint lcr=0;
+	switch (aConfig.iDataBits)
+		{
+		case EData5: lcr=K16550LCR_Data5; break;
+		case EData6: lcr=K16550LCR_Data6; break;
+		case EData7: lcr=K16550LCR_Data7; break;
+		case EData8: lcr=K16550LCR_Data8; break;
+		}
+	switch (aConfig.iStopBits)
+		{
+		case EStop1: break;
+		case EStop2: lcr|=K16550LCR_Stop2; break;
+		}
+	switch (aConfig.iParity)
+		{
+		case EParityNone: break;
+		case EParityEven: lcr|=K16550LCR_ParityEnable|K16550LCR_ParityEven; break;
+		case EParityOdd: lcr|=K16550LCR_ParityEnable; break;
+		case EParityMark: lcr|=K16550LCR_ParityEnable|K16550LCR_ParityMark; break;
+		case EParitySpace: lcr|=K16550LCR_ParityEnable|K16550LCR_ParitySpace; break;
+		}
+	iUart->SetLCR(lcr|K16550LCR_DLAB);
+	iUart->SetBaudRateDivisor(BaudRateDivisor[(TInt)aConfig.iRate]);
+	iUart->SetLCR(lcr);
+	iUart->SetFCR(K16550FCR_Enable|K16550FCR_RxReset|K16550FCR_TxReset|K16550FCR_RxTrig8);
+	}
+
+void DComm16550::Caps(TDes8 &aCaps) const
+//
+// return our caps
+//
+	{
+	Get16550CommsCaps(aCaps,iUnit);
+	}
+
+void DComm16550::Isr(TAny* aPtr)
+//
+// Service the UART interrupt
+//
+	{
+	DComm16550& d=*(DComm16550*)aPtr;
+	T16550Uart& u=*d.iUart;
+	TUint rx[32];
+	TUint xon=d.iLdd->iRxXonChar;
+	TUint xoff=d.iLdd->iRxXoffChar;
+
+	TUint isr=u.ISR();
+	if (isr & K16550ISR_NotPending)
+		return;
+	isr&=K16550ISR_IntIdMask;
+
+	// if receive data available or line status interrupt
+	if (isr==K16550ISR_RDAI || isr==K16550ISR_RLSI)
+		{
+		TInt rxi=0;
+		TInt x=0;
+		while(u.TestLSR(K16550LSR_RxReady|K16550LSR_RxParityErr|K16550LSR_RxOverrun|K16550LSR_RxFrameErr|K16550LSR_RxBreak) && Kern::PowerGood())
+			{
+			TUint lsr=0;
+			// checks for EIF flag
+			if (isr==K16550ISR_RLSI)
+				lsr=u.LSR()&(K16550LSR_RxParityErr|K16550LSR_RxOverrun|K16550LSR_RxFrameErr);
+			TUint ch=u.RxData();
+			// if error in this character
+			if(lsr)
+				{
+				if (lsr & K16550LSR_RxParityErr)
+					ch|=KReceiveIsrParityError;
+				if (lsr & K16550LSR_RxBreak)
+					ch|=KReceiveIsrBreakError;
+				if (lsr & K16550LSR_RxFrameErr)
+					ch|=KReceiveIsrFrameError;
+				if (lsr & K16550LSR_RxOverrun)
+					ch|=KReceiveIsrOverrunError;
+				}
+			if (ch==xon)
+				x=1;
+			else if (ch==xoff)
+				x=-1;
+			else
+				rx[rxi++]=ch;
+			}
+		d.ReceiveIsr(rx,rxi,x);
+		return;
+		}
+	// if TFS flag and TIE
+	if (isr==K16550ISR_THREI)
+		{
+		TInt n;
+		for (n=0; n<16; ++n)
+			{
+			TInt r=d.TransmitIsr();
+			if(r<0)
+				{
+				//no more to send
+				// Disable the TX interrupt
+				u.ModifyIER(K16550IER_THREI,0);
+				d.iTransmitting=EFalse;
+				break;
+				}
+			u.SetTxData(r);
+			}
+		d.CheckTxBuffer();
+		return;
+		}
+	// must be signal change
+	d.StateIsr(d.Signals());
+	}
+
+
+const TInt KUart16550ThreadPriority = 27;
+_LIT(KUar16550tDriverThread,"UART16550_Thread");
+
+DECLARE_STANDARD_PDD()
+	{
+	return new DDriverComm;
+	}
+