|
1 <?xml version="1.0" encoding="utf-8"?> |
|
2 <!-- Copyright (c) 2007-2010 Nokia Corporation and/or its subsidiary(-ies) All rights reserved. --> |
|
3 <!-- This component and the accompanying materials are made available under the terms of the License |
|
4 "Eclipse Public License v1.0" which accompanies this distribution, |
|
5 and is available at the URL "http://www.eclipse.org/legal/epl-v10.html". --> |
|
6 <!-- Initial Contributors: |
|
7 Nokia Corporation - initial contribution. |
|
8 Contributors: |
|
9 --> |
|
10 <!DOCTYPE concept |
|
11 PUBLIC "-//OASIS//DTD DITA Concept//EN" "concept.dtd"> |
|
12 <concept id="GUID-12A4418A-9BC6-4BEB-993D-B55E61240A15" xml:lang="en"><title>SDIO |
|
13 Client Interface Guide</title><shortdesc>Describes how to use the SDIO interface in a device driver.</shortdesc><prolog><metadata><keywords/></metadata></prolog><conbody> |
|
14 <p>This is a generic description of how to use SDIO in a kernel-side device |
|
15 driver. Specifics differ according to hardware and card function.</p> |
|
16 <section id="GUID-F6EC5141-075B-4B9A-8207-330FA8EC696A"><title>Socket, stack, |
|
17 card and function</title> <p>SDIO is an input/output protocol originally |
|
18 introduced to enable a device to communicate with SDIO (Secure Digital) cards. |
|
19 It can also be used as input/output for media such as Bluetooth adapters and |
|
20 GPS receivers and for input/output within a device (for instance to talk to |
|
21 an internal bus). These different uses of SDIO are called functions.</p><p>Symbian |
|
22 platform implements SDIO as part of a larger framework involving SD cards, |
|
23 which are a type of MMC (multimedia) card. For this reason, to use SDIO in |
|
24 a device driver you will need to use classes representing SD and MMC cards |
|
25 and the associated communications stack even if you only want the basic I/O |
|
26 functionality. These classes are:</p><ul> |
|
27 <li><p><xref href="GUID-45B97680-1756-3559-8A2D-2F2E851AD6A7.dita"><apiname>DMMCSocket</apiname></xref></p></li> |
|
28 <li><p><xref href="GUID-908B4DA8-8E1C-3B38-90FF-14EC52277B91.dita"><apiname>DSDIOStack</apiname></xref></p></li> |
|
29 <li><p><xref href="GUID-731522ED-8259-3CBA-9AD3-09DDAE23257D.dita"><apiname>TSDIOCard</apiname></xref></p></li> |
|
30 <li><p><xref href="GUID-1342EC36-363F-3E7D-9B5F-4AFD3BAC98C8.dita"><apiname>TSDIOFunction</apiname></xref></p></li> |
|
31 </ul><p>The work of data transfer is performed by reading to and writing from |
|
32 registers in response to interrupts, and sometimes the read and write operations |
|
33 are performed in sessions. The classes used are these:</p><ul> |
|
34 <li><p><xref href="GUID-CC5352E2-DB21-393F-A7A4-108AA3684460.dita"><apiname>DSDIORegInterface</apiname></xref></p></li> |
|
35 <li><p><xref href="GUID-5A5218D4-E8AD-322F-BE5B-597137623B3F.dita"><apiname>TSDIOInterrupt</apiname></xref></p></li> |
|
36 <li><p><xref href="GUID-36C5854E-6FD4-3630-9FF9-7DB3D578F9BD.dita"><apiname>DSDIOSession</apiname></xref></p></li> |
|
37 </ul><p>This document illustrates the use of these classes to create a driver, |
|
38 with example code fragments in which the driver being created is called <codeph>DSDIODriver</codeph>.</p> |
|
39 </section> |
|
40 <section id="GUID-6C842F3D-12E5-44A3-8284-4BD8C1770B08"><title>Creation and |
|
41 initialization</title><p>The first step in writing a driver using SDIO is |
|
42 thus to create socket, stack and card objects.</p><codeblock xml:space="preserve"> DMMCSocket* iSocket = static_cast<DMMCSocket>DPBusSocket::SocketFromId(KSocketNumber)); |
|
43 if (NULL == iSocket) |
|
44 return KErrNoMemory; |
|
45 |
|
46 DMMCStack* iStack = static_cast<DSDIOStack*>(iSocket->Stack(KStackNumber)); |
|
47 if (NULL == iStack) |
|
48 return KErrNoMemory; |
|
49 |
|
50 TSDIOCard* iCard = static_cast<TSDIOCard*>(iStack->CardP(KCardNumber)); |
|
51 if (NULL == iCard) |
|
52 return KErrNoMemory; |
|
53 |
|
54 </codeblock></section> |
|
55 <section id="GUID-6046097E-372A-4C06-941F-C37280668FDD"><title>Function types</title><p>The |
|
56 functions supported by SDIO are represented by the enumeration <xref href="GUID-14307AD8-424C-3AEC-964F-0488DE751CBA.dita"><apiname>TSdioFunctionType</apiname></xref><codeph/> which |
|
57 is declared in the <xref href="GUID-B05522D8-A05B-334F-AA92-05FDC2451000.dita"><apiname>TSdioFunctionCaps</apiname></xref> class. </p><codeblock xml:space="preserve">enum TSdioFunctionType |
|
58 { |
|
59 /** Not an SDIO standard interface */ |
|
60 ESdioFunctionTypeUnknown = 0, |
|
61 /** SDIO UART standard interface */ |
|
62 ESdioFunctionTypeUART = 1, |
|
63 /** SDIO 'thin' Bluetooth standard interface */ |
|
64 ESdioFunctionTypeThinBT = 2, |
|
65 /** SDIO 'complete' Bluetooth standard interface */ |
|
66 ESdioFunctionTypeFullBT = 3, |
|
67 /** SDIO GPS standard interface */ |
|
68 ESdioFunctionTypeGPS = 4, |
|
69 /** SDIO Camera standard interface */ |
|
70 ESdioFunctionTypeCamera = 5, |
|
71 /** SDIO PHS Radio standard interface */ |
|
72 ESdioFunctionTypePHS = 6, |
|
73 /** SDIO WLAN standard interface (Introduced in SDIO Rev. 1.10f) */ |
|
74 ESdioFunctionTypeWLAN = 7, |
|
75 /** Extended SDIO standard interface (Introduced in SDIO Rev. 1.10f) */ |
|
76 ESdioFunctionTypeExtended = 15, |
|
77 }; |
|
78 </codeblock></section> |
|
79 <section id="GUID-8B81F9BF-167E-4E94-A019-E90EA8281319"><title>Kernel polling</title><p>Kernel |
|
80 polling means the use of the <codeph>Kern::PollingWait()</codeph> function |
|
81 of the kernel to call a function repeatedly if it is likely to fail on a first |
|
82 try. You specify the number of attempts and the delay between them as parameters. |
|
83 It is a recommended technique in writing kernel drivers and is used more than |
|
84 once in the example code in this document. </p></section> |
|
85 <section id="GUID-1784014E-0352-408E-8BDD-EF27F53AF17C"><title>Initialization</title><p>To |
|
86 initialize the card you must power it up and test whether it is ready for |
|
87 use. The following code uses kernel polling to perform the test 10 times at |
|
88 100 millisecond intervals.</p><codeblock xml:space="preserve"> … |
|
89 iSocket->PowerUp(); |
|
90 Kern::PollingWait(CardReady, iCard, 100, 10); |
|
91 |
|
92 if (!iCard->IsReady()) |
|
93 return(KErrNotReady); |
|
94 … |
|
95 |
|
96 |
|
97 TBool DSDIODriver::CardReady(TAny* aSelfP) |
|
98 { |
|
99 TSDIOCard& card = *(TSDIOCard*)aSelfP; |
|
100 return card.IsReady(); |
|
101 } |
|
102 </codeblock><p>You may also want to test that the card is an SDIO card.</p><codeblock xml:space="preserve"> if (!pCard->IsIOCard()) |
|
103 return KErrNotReady; |
|
104 </codeblock><p>Next you locate and enable the function of the card (Bluetooth, |
|
105 UART etc.) which you want to use. A function has a location which differs |
|
106 from card to card and which is stored in a data structure called the CIS (Card |
|
107 Information Structure. To use a card's CIS you load it using the <xref href="GUID-98450844-1ED3-3696-B1A6-C1A97AED83A9.dita"><apiname>CheckCis()</apiname></xref> function |
|
108 of the <xref href="GUID-731522ED-8259-3CBA-9AD3-09DDAE23257D.dita"><apiname>TSDIOCard</apiname></xref> class.</p><codeblock xml:space="preserve"> TInt err = iCard->CheckCIS(); |
|
109 if (KErrNone != err) |
|
110 return err; |
|
111 </codeblock><p>The following code is a simple test to determine whether the |
|
112 passed in function type is present on the card.</p><codeblock xml:space="preserve">TSDIOFunction* DSdioDriver::FindFunction(TSdioFunctionType aFunctionType) |
|
113 { |
|
114 // We are going to try to match the function type |
|
115 TUint32 capsMatchMask = TSDIOFunctionCaps::EFunctionType; |
|
116 |
|
117 // Create the caps specification for a BT function |
|
118 TSDIOFunctionCaps functionCaps; |
|
119 functionCaps.iType = aFunctionType; |
|
120 |
|
121 // Request to find the function on the card |
|
122 TSDIOFunction* pFunction = iCard->FindFunction(functionCaps, capsMatchMask); |
|
123 |
|
124 // If the function cannot be found, pFunction will be NULL |
|
125 |
|
126 return pFunction; |
|
127 } |
|
128 </codeblock><p>Once you have the location of a function, you register the |
|
129 client driver with the stack and enable the function on the card.</p><codeblock xml:space="preserve"> TInt err = iFunction->RegisterClient(this); |
|
130 if (err != KErrNone) |
|
131 { |
|
132 return err; |
|
133 } |
|
134 |
|
135 // Enable the function |
|
136 err = iFunction->Enable(ETrue); |
|
137 |
|
138 if (KErrNone == err) |
|
139 { |
|
140 Kern::PollingWait(FunctionReady, this, 100, 10); |
|
141 } |
|
142 |
|
143 TBool isReady = EFalse; |
|
144 iFunction->IsReady(isReady); |
|
145 If (!isReady) |
|
146 return KErrNotReady; |
|
147 … |
|
148 |
|
149 |
|
150 TBool DSdioDriver::FunctionReady(TAny* aSelfP) |
|
151 { |
|
152 TSDIOFunction* pFunction = (TSDIOFunction*)aSelfP; |
|
153 TBool isReady = EFalse; |
|
154 pFunction->IsReady(isReady); |
|
155 return isReady; |
|
156 } |
|
157 </codeblock></section> |
|
158 <section id="GUID-44B9EA31-9BDD-4181-BF2F-FF80F5EDCE32"><title>Transferring |
|
159 data: registers and shared chunks</title><p>SDIO cards place data to be read |
|
160 or written in a register whose address depends on the function and is defined |
|
161 in the relevant specification available at <xref href="http://www.sdcard.org/home/" scope="external">www.sdcard.org</xref>. Registers are also used to send commands |
|
162 to the card and to enable or disable interrupts. You access the register for |
|
163 a function using the <xref href="GUID-6BD11B5F-2269-3317-A40D-6547042CA463.dita"><apiname>DSDIORegisterInterface</apiname></xref> class which |
|
164 is obtained from the function pointer like this.</p><p/><codeblock xml:space="preserve"> // Get the register interface |
|
165 DSDIORegisterInterface* pRegIfc = iFunction->RegisterInterface(this); |
|
166 </codeblock><p>Data can be transferred.</p><ul> |
|
167 <li><p>as individual bytes,</p></li> |
|
168 <li><p>as the contents of byte buffers (both directly and using shared chunks), |
|
169 and</p></li> |
|
170 <li><p>by setting bits in the register.</p></li> |
|
171 </ul><p>The following code uses the <xref href="GUID-53280C38-1734-332D-A432-0A56AB16CD04.dita"><apiname>Read8()</apiname></xref> and <xref href="GUID-BF2E636A-FA49-39F6-9A52-A09E4879F3FC.dita"><apiname>Write8()</apiname></xref> functions |
|
172 of the <xref href="GUID-6BD11B5F-2269-3317-A40D-6547042CA463.dita"><apiname>DSDIORegisterInterface</apiname></xref> class to read and write a |
|
173 single byte from and to the card function. You typically set registers on |
|
174 the card and enable interrupts by this method.</p><codeblock xml:space="preserve"> TUint8 readVal = 0; |
|
175 TInt ret = pRegIfc->Read8(KReadRegister, &readVal); |
|
176 if (KErrNone != ret) |
|
177 return ret; |
|
178 |
|
179 TUint8 writeVal = readVal + 1; |
|
180 ret = pRegIfc->Write8(KWriteRegister, writeVal); |
|
181 if (KErrNone != ret) |
|
182 return ret; |
|
183 </codeblock><p>This code demonstrates the use of the <xref href="GUID-2E0277CD-3CB8-3A8C-AAD3-8083E8BA7B60.dita"><apiname>ReadMultiple8()</apiname></xref> and <xref href="GUID-F29DFD6B-9CA4-3170-B829-F3881B152614.dita"><apiname>WriteMultiple8()</apiname></xref> functions |
|
184 to read to and write from byte buffers.</p><codeblock xml:space="preserve"> TUint8 client[6]; |
|
185 memcpy(client, “client”, 6); |
|
186 TInt ret = pRegIfc->WriteMultiple8(KWriteRegister, client, 6); |
|
187 if (KErrNone != ret) |
|
188 return ret; |
|
189 |
|
190 TUint8 server[6]; |
|
191 ret = pRegIfc->ReadMultiple8(KReadRegister, server, 6); |
|
192 if (KErrNone != ret) |
|
193 return ret; |
|
194 |
|
195 if (memcmp(server, “server”) != 0) |
|
196 return KErrNotFound; |
|
197 </codeblock><p>When large amounts of data are being transferred it is good |
|
198 practice to use a shared chunk. You call the same functions as before with |
|
199 the chunk as additional argument. The following example writes 1024 bytes |
|
200 with an offset of zero and reads 1024 bytes with an offset of 1024.</p><codeblock xml:space="preserve"> TInt ret = pRegIfc->WriteMultiple8(KWriteRegister, chunk, 0, 1024); |
|
201 if (KErrNone != ret) |
|
202 return ret; |
|
203 |
|
204 ret = pRegIfc->ReadMultiple8(KReadRegister, chunk, 1024, 1024); |
|
205 if (KErrNone != ret) |
|
206 return ret; |
|
207 </codeblock><p>The advantages of shared chunks are that they can be used from |
|
208 user space and reduce copying overhead.</p><p>The following code example shows |
|
209 how to set and clear bits in a register using the <xref href="GUID-312948B9-5338-3030-9130-821E9BDDCE62.dita"><apiname>Modify8()</apiname></xref> function |
|
210 of the <xref href="GUID-6BD11B5F-2269-3317-A40D-6547042CA463.dita"><apiname>DSDIORegisterInterface</apiname></xref> class</p><codeblock xml:space="preserve"> TUint8 setBits = 0x80; |
|
211 TUint8 clearBis = 0x02; |
|
212 |
|
213 ret = pRegIfc->Modify8(KModifyRegister, setBits, clearBits); |
|
214 if (KErrNone != ret) |
|
215 return ret; |
|
216 </codeblock><p>Another advantage of shared chunks is that they make it possible |
|
217 to send commands to the card while a multibyte data transfer is taking place. |
|
218 Not all cards support this functionality: you can check whether a particular |
|
219 card does so by reading the SDC bit in the Card Capability register. To do |
|
220 this, you need to create a second register interface to write the commands |
|
221 in the form of one byte transfers. This second interface (or 'second session') |
|
222 must run in a different thread from the interface performing the multibyte |
|
223 transfer and it is created in a different way, as in this code:</p><codeblock xml:space="preserve"> // Create a second session |
|
224 DSDIORegisterInterface* interfaceP = new DSDIORegisterInterface(cardP, functionP->FunctionNumber()); |
|
225 |
|
226 // Using the second interface run a direct command |
|
227 TUint8 readVal = 0; |
|
228 ret = interfaceP->Read8(KReadRegister, &readVal); |
|
229 |
|
230 … |
|
231 </codeblock></section> |
|
232 <section id="GUID-11562A70-49EF-49BE-9323-6E1E69433907"><title>Controlling |
|
233 data transfer: interrupts</title><p>Cards generate interrupts which control |
|
234 the process of data transfer: for instance the arrival of data on the function |
|
235 of a card generates an interrupt which serves as a signal that a read operation |
|
236 should take place. The are two levels of interrupts. A card level interrupt |
|
237 indicates which function has triggered it and a separate function level interrupt |
|
238 gives information specific to that function which is covered in the documentation |
|
239 for a particular card.</p><p>You must provide ISRs (interrupt service routines) |
|
240 to handle interrupts. ISRs typically queue DFCs to perform the required actions |
|
241 such as read and write operations on the card since ISRs must complete very |
|
242 quickly to maintain real-time guarantees whereas data transfer can wait for |
|
243 the current time slice to complete. The exact functionality of the DFCs will |
|
244 vary depending on the nature of the function and the hardware. Two things |
|
245 you must do in any driver implementation are enabling and disabling interrupts |
|
246 and binding the ISRs to the stack. Also, when an interrupt is triggered you |
|
247 must read the interrupt register to make sure that the interrupt is applicable |
|
248 and then clear the function level interrupt and re-enable interrupts.</p><p>You |
|
249 enable card level interrupts as in this example code:</p><codeblock xml:space="preserve"> // Enable SDIO card interrupt |
|
250 iFunction->Interrupt().Enable(); |
|
251 |
|
252 // Disable SDIO card interrupt |
|
253 iFunction->Interrupt().Disable(); |
|
254 </codeblock><p>How you enable function level interrupts is described in the |
|
255 documentation for a particular card.</p><p>You bind ISRs to the stack as in |
|
256 the following code fragment. </p><codeblock xml:space="preserve"> … |
|
257 // Bind to interrupt |
|
258 err = iFunction->Interrupt().Bind(DSDIODriver::Isr, this); |
|
259 if (KErrNone != err) |
|
260 return err; |
|
261 |
|
262 … |
|
263 |
|
264 void DSDIODriver::Isr(TAny* aParam) |
|
265 { |
|
266 DSDIODriver* pDriver = (DSDIODriver*)aParam; |
|
267 … |
|
268 } |
|
269 </codeblock></section> |
|
270 <section id="GUID-458DACCF-1FAD-4F2A-8D79-AD3B77BE75F0"><title>Notifications |
|
271 and powering down: callbacks</title><p>Register callbacks to be notified of |
|
272 events. This mechanism is standard for handling the removal of a card and |
|
273 powering down.</p><p>The SDIO stack can notify clients of events they must |
|
274 react to such as power down or the removal of the card. Events of this kind |
|
275 are listed in the enumeration <xref href="GUID-84D32429-8E04-39BA-A032-478976C7991C.dita"><apiname>TSDIOFunctionCallbackReason</apiname></xref>.</p><codeblock xml:space="preserve">enum TSDIOFunctionCallbackReason |
|
276 { |
|
277 /** Card has powered up */ |
|
278 ESdioNotifyPowerUp = 0, |
|
279 /** Card requests to power down */ |
|
280 ESdioNotifyPowerDownPending = 1, |
|
281 /** Card has powered down */ |
|
282 ESdioNotifyPowerDown = 2, |
|
283 /** Request to enter sleep mode */ |
|
284 ESdioNotifyPowerSleep = 3, |
|
285 /** Emergency power down */ |
|
286 ESdioNotifyEmergencyPowerDown = 4, |
|
287 /** PSU fault */ |
|
288 ESdioNotifyPsuFault = 5, |
|
289 /** Card has been removed */ |
|
290 ESdioNotifyCardRemoved = 6, |
|
291 /** Card has been inserted */ |
|
292 ESdioNotifyCardInserted = 7, |
|
293 }; |
|
294 </codeblock><p>You respond to notifications with callback functions which |
|
295 you write to provide appropriate functionality. Use the callback to initialize |
|
296 a <xref href="GUID-C07575EE-FCAA-3050-89F3-F0DF8EF8D122.dita"><apiname>TSDIOFunctionCallback</apiname></xref> object and then register that object |
|
297 with the socket. The following code uses an example callback <codeph>FnCallback()</codeph> and |
|
298 object <codeph>functionCallback</codeph>.</p><codeblock xml:space="preserve"> TSDIOFunctionCallback functionCallback(DSDIODriver::FnCallback, this); |
|
299 |
|
300 // Register the callback |
|
301 functionCallback.Register(iSocket); |
|
302 … |
|
303 |
|
304 TInt DSDIODriver::FnCallback(TAny* aParam, TSDIOFunctionCallbackReason aReason) |
|
305 { |
|
306 DSDIODriver* pDriver = (DSDIODriver*)aParam; |
|
307 |
|
308 switch(aReason) |
|
309 { |
|
310 case ESdioNotifyCardRemoved: |
|
311 … |
|
312 break; |
|
313 } |
|
314 … |
|
315 } |
|
316 </codeblock><p>You typically use notifications and callbacks to perform cleanup |
|
317 before a pending power down event. The socket class has functions to postpone |
|
318 power down at the start of cleanup and to resume power down when cleanup is |
|
319 finished. They are used like this.</p><codeblock xml:space="preserve"> iSocket->RequestAsyncPowerDown(); |
|
320 |
|
321 /* cleanup code here */ |
|
322 iSocket->PowerDownComplete();</codeblock></section> |
|
323 <section id="GUID-44A3E6CC-8EF1-45B5-92B9-3F440DC6791B"><title>Tutorials</title>The |
|
324 classes and commands used to create a device driver are discussed further |
|
325 in the tutorials.<ul> |
|
326 <li><p><xref href="GUID-2607CCFA-3D24-4716-A447-74304F861C44.dita">DSDIOREGInterface |
|
327 Class Tutorial</xref></p></li> |
|
328 <li><p><xref href="GUID-360DEB3A-3E38-413A-9941-6C758BA01ADE.dita">DSDIOSession |
|
329 Class Tutorial</xref></p></li> |
|
330 <li><p><xref href="GUID-E1277A18-7201-4856-8712-067022F92123.dita">DSDIOStack Class |
|
331 Tutorial</xref></p></li> |
|
332 <li><p><xref href="GUID-BA3B42A2-141C-49D9-B513-1571C246C19B.dita">TSDIOCard Class |
|
333 Tutorial</xref></p></li> |
|
334 <li><p><xref href="GUID-C57086D7-7672-4A52-8634-D28B37AC6290.dita">TSDIOFunction |
|
335 Class Tutorial</xref></p></li> |
|
336 <li><p><xref href="GUID-535C66C9-8B45-4DF3-8404-8ED03ABB4799-GENID-1-2-1-10-1-5-1-9-1-1-7-1-4-1-4-1.dita">TSDIOInterrupt |
|
337 Class Tutorial</xref></p></li> |
|
338 <li><p><xref href="GUID-BDF1F32B-796B-4D3D-9C91-43FF8E9DDAF9.dita">SDIO Commands |
|
339 Tutorial</xref></p></li> |
|
340 </ul><p>A reference implementation using Bluetooth is described in <xref href="GUID-53A6E46C-E961-47C7-A5FA-CB0B52266646.dita">SDIO |
|
341 BT Example</xref>.</p></section> |
|
342 </conbody></concept> |