diff -r 578be2adaf3e -r 307f4279f433 Adaptation/GUID-12A4418A-9BC6-4BEB-993D-B55E61240A15.dita --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Adaptation/GUID-12A4418A-9BC6-4BEB-993D-B55E61240A15.dita Fri Oct 15 14:32:18 2010 +0100 @@ -0,0 +1,342 @@ + + + + + +SDIO +Client Interface GuideDescribes how to use the SDIO interface in a device driver. +

This is a generic description of how to use SDIO in a kernel-side device +driver. Specifics differ according to hardware and card function.

+
Socket, stack, +card and function

SDIO is an input/output protocol originally +introduced to enable a device to communicate with SDIO (Secure Digital) cards. +It can also be used as input/output for media such as Bluetooth adapters and +GPS receivers and for input/output within a device (for instance to talk to +an internal bus). These different uses of SDIO are called functions.

Symbian +platform implements SDIO as part of a larger framework involving SD cards, +which are a type of MMC (multimedia) card. For this reason, to use SDIO in +a device driver you will need to use classes representing SD and MMC cards +and the associated communications stack even if you only want the basic I/O +functionality. These classes are:

    +
  • DMMCSocket

  • +
  • DSDIOStack

  • +
  • TSDIOCard

  • +
  • TSDIOFunction

  • +

The work of data transfer is performed by reading to and writing from +registers in response to interrupts, and sometimes the read and write operations +are performed in sessions. The classes used are these:

    +
  • DSDIORegInterface

  • +
  • TSDIOInterrupt

  • +
  • DSDIOSession

  • +

This document illustrates the use of these classes to create a driver, +with example code fragments in which the driver being created is called DSDIODriver.

+
+
Creation and +initialization

The first step in writing a driver using SDIO is +thus to create socket, stack and card objects.

DMMCSocket* iSocket = static_cast<DMMCSocket>DPBusSocket::SocketFromId(KSocketNumber)); + if (NULL == iSocket) + return KErrNoMemory; + + DMMCStack* iStack = static_cast<DSDIOStack*>(iSocket->Stack(KStackNumber)); + if (NULL == iStack) + return KErrNoMemory; + + TSDIOCard* iCard = static_cast<TSDIOCard*>(iStack->CardP(KCardNumber)); + if (NULL == iCard) + return KErrNoMemory; + +
+
Function types

The +functions supported by SDIO are represented by the enumeration TSdioFunctionType which +is declared in the TSdioFunctionCaps class.

enum TSdioFunctionType + { + /** Not an SDIO standard interface */ + ESdioFunctionTypeUnknown = 0, + /** SDIO UART standard interface */ + ESdioFunctionTypeUART = 1, + /** SDIO 'thin' Bluetooth standard interface */ + ESdioFunctionTypeThinBT = 2, + /** SDIO 'complete' Bluetooth standard interface */ + ESdioFunctionTypeFullBT = 3, + /** SDIO GPS standard interface */ + ESdioFunctionTypeGPS = 4, + /** SDIO Camera standard interface */ + ESdioFunctionTypeCamera = 5, + /** SDIO PHS Radio standard interface */ + ESdioFunctionTypePHS = 6, + /** SDIO WLAN standard interface (Introduced in SDIO Rev. 1.10f) */ + ESdioFunctionTypeWLAN = 7, + /** Extended SDIO standard interface (Introduced in SDIO Rev. 1.10f) */ + ESdioFunctionTypeExtended = 15, + }; +
+
Kernel polling

Kernel +polling means the use of the Kern::PollingWait() function +of the kernel to call a function repeatedly if it is likely to fail on a first +try. You specify the number of attempts and the delay between them as parameters. +It is a recommended technique in writing kernel drivers and is used more than +once in the example code in this document.

+
Initialization

To +initialize the card you must power it up and test whether it is ready for +use. The following code uses kernel polling to perform the test 10 times at +100 millisecond intervals.

… + iSocket->PowerUp(); + Kern::PollingWait(CardReady, iCard, 100, 10); + + if (!iCard->IsReady()) + return(KErrNotReady); + … + + +TBool DSDIODriver::CardReady(TAny* aSelfP) + { + TSDIOCard& card = *(TSDIOCard*)aSelfP; + return card.IsReady(); + } +

You may also want to test that the card is an SDIO card.

if (!pCard->IsIOCard()) + return KErrNotReady; +

Next you locate and enable the function of the card (Bluetooth, +UART etc.) which you want to use. A function has a location which differs +from card to card and which is stored in a data structure called the CIS (Card +Information Structure. To use a card's CIS you load it using the CheckCis() function +of the TSDIOCard class.

TInt err = iCard->CheckCIS(); + if (KErrNone != err) + return err; +

The following code is a simple test to determine whether the +passed in function type is present on the card.

TSDIOFunction* DSdioDriver::FindFunction(TSdioFunctionType aFunctionType) + { + // We are going to try to match the function type + TUint32 capsMatchMask = TSDIOFunctionCaps::EFunctionType; + + // Create the caps specification for a BT function + TSDIOFunctionCaps functionCaps; + functionCaps.iType = aFunctionType; + + // Request to find the function on the card + TSDIOFunction* pFunction = iCard->FindFunction(functionCaps, capsMatchMask); + + // If the function cannot be found, pFunction will be NULL + + return pFunction; + } +

Once you have the location of a function, you register the +client driver with the stack and enable the function on the card.

TInt err = iFunction->RegisterClient(this); + if (err != KErrNone) + { + return err; + } + + // Enable the function + err = iFunction->Enable(ETrue); + + if (KErrNone == err) + { + Kern::PollingWait(FunctionReady, this, 100, 10); + } + + TBool isReady = EFalse; + iFunction->IsReady(isReady); + If (!isReady) + return KErrNotReady; + … + + +TBool DSdioDriver::FunctionReady(TAny* aSelfP) + { + TSDIOFunction* pFunction = (TSDIOFunction*)aSelfP; + TBool isReady = EFalse; + pFunction->IsReady(isReady); + return isReady; + } +
+
Transferring +data: registers and shared chunks

SDIO cards place data to be read +or written in a register whose address depends on the function and is defined +in the relevant specification available at www.sdcard.org. Registers are also used to send commands +to the card and to enable or disable interrupts. You access the register for +a function using the DSDIORegisterInterface class which +is obtained from the function pointer like this.

// Get the register interface + DSDIORegisterInterface* pRegIfc = iFunction->RegisterInterface(this); +

Data can be transferred.

    +
  • as individual bytes,

  • +
  • as the contents of byte buffers (both directly and using shared chunks), +and

  • +
  • by setting bits in the register.

  • +

The following code uses the Read8() and Write8() functions +of the DSDIORegisterInterface class to read and write a +single byte from and to the card function. You typically set registers on +the card and enable interrupts by this method.

TUint8 readVal = 0; + TInt ret = pRegIfc->Read8(KReadRegister, &readVal); + if (KErrNone != ret) + return ret; + + TUint8 writeVal = readVal + 1; + ret = pRegIfc->Write8(KWriteRegister, writeVal); + if (KErrNone != ret) + return ret; +

This code demonstrates the use of the ReadMultiple8() and WriteMultiple8() functions +to read to and write from byte buffers.

TUint8 client[6]; + memcpy(client, “client”, 6); + TInt ret = pRegIfc->WriteMultiple8(KWriteRegister, client, 6); + if (KErrNone != ret) + return ret; + + TUint8 server[6]; + ret = pRegIfc->ReadMultiple8(KReadRegister, server, 6); + if (KErrNone != ret) + return ret; + + if (memcmp(server, “server”) != 0) + return KErrNotFound; +

When large amounts of data are being transferred it is good +practice to use a shared chunk. You call the same functions as before with +the chunk as additional argument. The following example writes 1024 bytes +with an offset of zero and reads 1024 bytes with an offset of 1024.

TInt ret = pRegIfc->WriteMultiple8(KWriteRegister, chunk, 0, 1024); + if (KErrNone != ret) + return ret; + + ret = pRegIfc->ReadMultiple8(KReadRegister, chunk, 1024, 1024); + if (KErrNone != ret) + return ret; +

The advantages of shared chunks are that they can be used from +user space and reduce copying overhead.

The following code example shows +how to set and clear bits in a register using the Modify8() function +of the DSDIORegisterInterface class

TUint8 setBits = 0x80; + TUint8 clearBis = 0x02; + + ret = pRegIfc->Modify8(KModifyRegister, setBits, clearBits); + if (KErrNone != ret) + return ret; +

Another advantage of shared chunks is that they make it possible +to send commands to the card while a multibyte data transfer is taking place. +Not all cards support this functionality: you can check whether a particular +card does so by reading the SDC bit in the Card Capability register. To do +this, you need to create a second register interface to write the commands +in the form of one byte transfers. This second interface (or 'second session') +must run in a different thread from the interface performing the multibyte +transfer and it is created in a different way, as in this code:

// Create a second session + DSDIORegisterInterface* interfaceP = new DSDIORegisterInterface(cardP, functionP->FunctionNumber()); + + // Using the second interface run a direct command + TUint8 readVal = 0; + ret = interfaceP->Read8(KReadRegister, &readVal); + + … +
+
Controlling +data transfer: interrupts

Cards generate interrupts which control +the process of data transfer: for instance the arrival of data on the function +of a card generates an interrupt which serves as a signal that a read operation +should take place. The are two levels of interrupts. A card level interrupt +indicates which function has triggered it and a separate function level interrupt +gives information specific to that function which is covered in the documentation +for a particular card.

You must provide ISRs (interrupt service routines) +to handle interrupts. ISRs typically queue DFCs to perform the required actions +such as read and write operations on the card since ISRs must complete very +quickly to maintain real-time guarantees whereas data transfer can wait for +the current time slice to complete. The exact functionality of the DFCs will +vary depending on the nature of the function and the hardware. Two things +you must do in any driver implementation are enabling and disabling interrupts +and binding the ISRs to the stack. Also, when an interrupt is triggered you +must read the interrupt register to make sure that the interrupt is applicable +and then clear the function level interrupt and re-enable interrupts.

You +enable card level interrupts as in this example code:

// Enable SDIO card interrupt + iFunction->Interrupt().Enable(); + + // Disable SDIO card interrupt + iFunction->Interrupt().Disable(); +

How you enable function level interrupts is described in the +documentation for a particular card.

You bind ISRs to the stack as in +the following code fragment.

… + // Bind to interrupt + err = iFunction->Interrupt().Bind(DSDIODriver::Isr, this); + if (KErrNone != err) + return err; + + … + +void DSDIODriver::Isr(TAny* aParam) + { + DSDIODriver* pDriver = (DSDIODriver*)aParam; + … + } +
+
Notifications +and powering down: callbacks

Register callbacks to be notified of +events. This mechanism is standard for handling the removal of a card and +powering down.

The SDIO stack can notify clients of events they must +react to such as power down or the removal of the card. Events of this kind +are listed in the enumeration TSDIOFunctionCallbackReason.

enum TSDIOFunctionCallbackReason + { + /** Card has powered up */ + ESdioNotifyPowerUp = 0, + /** Card requests to power down */ + ESdioNotifyPowerDownPending = 1, + /** Card has powered down */ + ESdioNotifyPowerDown = 2, + /** Request to enter sleep mode */ + ESdioNotifyPowerSleep = 3, + /** Emergency power down */ + ESdioNotifyEmergencyPowerDown = 4, + /** PSU fault */ + ESdioNotifyPsuFault = 5, + /** Card has been removed */ + ESdioNotifyCardRemoved = 6, + /** Card has been inserted */ + ESdioNotifyCardInserted = 7, + }; +

You respond to notifications with callback functions which +you write to provide appropriate functionality. Use the callback to initialize +a TSDIOFunctionCallback object and then register that object +with the socket. The following code uses an example callback FnCallback() and +object functionCallback.

TSDIOFunctionCallback functionCallback(DSDIODriver::FnCallback, this); + + // Register the callback + functionCallback.Register(iSocket); + … + +TInt DSDIODriver::FnCallback(TAny* aParam, TSDIOFunctionCallbackReason aReason) + { + DSDIODriver* pDriver = (DSDIODriver*)aParam; + + switch(aReason) + { + case ESdioNotifyCardRemoved: + … + break; + } + … + } +

You typically use notifications and callbacks to perform cleanup +before a pending power down event. The socket class has functions to postpone +power down at the start of cleanup and to resume power down when cleanup is +finished. They are used like this.

iSocket->RequestAsyncPowerDown(); + +/* cleanup code here */ + iSocket->PowerDownComplete();
+
TutorialsThe +classes and commands used to create a device driver are discussed further +in the tutorials.
    +
  • DSDIOREGInterface +Class Tutorial

  • +
  • DSDIOSession +Class Tutorial

  • +
  • DSDIOStack Class +Tutorial

  • +
  • TSDIOCard Class +Tutorial

  • +
  • TSDIOFunction +Class Tutorial

  • +
  • TSDIOInterrupt +Class Tutorial

  • +
  • SDIO Commands +Tutorial

  • +

A reference implementation using Bluetooth is described in SDIO +BT Example.

+
\ No newline at end of file