Windows CE Blog        Embedded Product Design Services

Apr/09

20

Sharing I2C bus between OAL RTC part and Driver part on Windows CE

Real Time Clock or RTC is an essential component in all embedded systems. Though all the modern day CPUs have inbuilt RTC, designers are comfortable with external RTC for some specific reasons such as to reduce the power burden, RTC alarms to wake-up the CPU etc. The power consumption of external RTC devices are extremely low and some of them offer additional battery-backed SRAM with tamper-detection etc. Designers interface these RTC chips through a common I2C bus shared by a lot of peripherals and sensors. Therefore a common I2C bus driver is needed to the developers.

Since an I2C interface is a shared bus, synchronization is needed between the devices to access the I2C bus. Windows CE supports synchronization objects like critical sections, semaphore and mutex etc.., to share the device or bus in driver level without conflict. But RTC related functionalities are implemented in the OAL.exe. Therefore it is not possible to use synchronization objects in the OAL part. This blog depicts - how to share I2C bus between OAL and driver without conflict.

RTC related OAL functions
Following functions are RTC related OAL functions.

a) BOOL OEMSetAlarmTime (LPSYSTEMTIME lpst );
b) BOOL OEMSetRealTime (LPSYSTEMTIME lpst);
c) BOOL OEMGetRealTime(LPSYSTEMTIME lpst);
d) Calling KernelIoControl with the IOCTL_HAL_INIT_RTC as a parameter.


These functions are called from the windows CE standard time functions. IOCTL_HAL_INIT_RTC resets the real-time clock by calling the OEMSetRealTime function during the booting. OEM is responsible for filling the stub inside these functions.

I2C Driver
I2C driver is a windows CE stream driver. Since the I2C bus is shared by the devices like camera sensor, temperature sensor, battery fuel gauge, Ambient light sensor etc. Therefore I2C bus driver is implemented with synchronization objects. For example, critical section is suitable for this case. This I2C driver will be utilized by other peripheral drivers. These techniques shall be effective to avoid races within the I2C driver. But the same I2C controller is shared between the OAL RTC code and the I2C driver code. This poses a different challenge.

Issue
Same I2C controller is shared between the OAL RTC and other peripheral drivers. Unfortunately, it is not possible to use synchronization objects between the OAL and the device drivers. Because “OAL.exe” is part of the Windows CE kernel. Device drivers are running on the top of the kernel. In order to synchronize the I2C bus access between OAL and driver, we need to maintain a sharable lock between these areas. The sharable lock can be achieved using the reserved memory region named DRVGLOB.

Solution
We can reserve a memory region with the known address in the config.bib file. A structure of sharable data between the OAL and the drivers can be maintained in the DRVGLOB reserved region. The following is the example of a config.bib that contains the DRVGLOB reserved region.

EBOOT                80000000               00100000           RESERVED
NK                        80100000               01F00000            RAMIMAGE
RAM                    82000000               0DF00000           RAM ;233MB
KITLUSB20      8FF00000               00010000            RESERVED ;For USB2.0 RNDIS Kitl DMA Buffer
LCDBOOTFRM 8FF10000                000D0000           RESERVED ;LCD FRAME BUFFER on EBOOT
DRVGLOB      8FFE0000            00010000           RESERVED ;Driver Globals

The following is a sample structure of DRVGLOB and it is declared in the DRVGLOB.H header file.

#ifndef __DRVGLOB_H
#define __DRVGLOB_H
typedef struct {
UINT32 Timeout; // RTCTime
UINT32 TouchWakeup; // Optional Wake up Source
.
.
.
UINT32 bI2CBusy; //Lock logic for sharing the I2C bus between OAL and Driver.
} DRV_GLOB, *P_DRV_GLOB;

#define DRVGLOB_BASE 0×8FFE0000
#define DRVGLOB_SIZE 0×10000

#endif

“bI2CBusy” is the variable used for maintaining the synchronization between the OAL and I2CDriver. We can access this reserved region in both the OAL and the driver. Following code snippet explains – how to access the structure in OAL and driver.

Initializing the DRVGLOB structure in OAL
volatile DRV_GLOB* g_DrvGlob;

g_DrvGlob = (volatile DRV_GLOB*) OALPAtoVA(DRVGLOB_BASE, FALSE);
if ( !g_DrvGlob )
{
RETAILMSG(1, (_T(”RTC::DRVGLOB Mem Alloc Failedrn”)));
return FALSE;
}

Initializing the DRVGLOB structure in I2C Driver
volatile DRV_GLOB* g_DrvGlob;
PHYSICAL_ADDRESS Drv_Glop_Base = {DRVGLOB_BASE};
g_DrvGlob = (volatile DRV_GLOB*) MmMapIoSpace(Drv_Glop_Base, sizeof(DRV_GLOB), FALSE);
if ( !g_DrvGlob )
{
RETAILMSG(1, (_T(”I2C::DRVGLOB Mem Alloc Failedrn”), GetLastError()));
return FALSE;
}
Synchronizing OAL RTC lib and I2C Driver

Following procedure explains the implementation.

I2C Read/Write Function in OAL RTC

a) Check I2C is busy with I2C driver. You can add some timeout, instead of waiting infinitely to avoid accidental hanging. If it is busy, wait in the loop until the bus is free. Following code snippet explains this.

while (g_DrvGlob->bI2CBusy)
{
Millisecond Delay function with 1 ms delay.
}
b) If it is free set the “bI2CBusy” as 1 to make it busy.

g_DrvGlob->bI2CBusy = 1;

c) Perform I2C bus access.

//I2C Read or Write operations
.
.
d) Clear the “bI2CBusy” to assure that the bus access is finished.

g_DrvGlob->bI2CBusy = 0;

I2C Read/Write Function in Driver

a) Enter Critical section to deny other peripheral driver accessing the I2C driver.

b) Check I2C is busy with OAL RTC. You can add some timeout, instead of waiting infinitely to avoid accidental hanging. If it is busy wait, in the loop until the bus is free. Following code snippet explains this.

while (g_DrvGlob->bI2CBusy)
{
Millisecond Delay function with 1 ms delay.
}
c) If it is free set the “bI2CBusy” as 1 to make it busy.

g_DrvGlob->bI2CBusy = 1;

d) Perform I2C bus access.

//I2C Read or Write operations
.
.
e) Clear the “bI2CBusy” to assure that the bus access is finished.

g_DrvGlob->bI2CBusy = 0;

f) Exit Critical section to allow other peripheral driver to access the I2C bus.

In this way, we can share the I2C bus with OAL part and the driver part.

RSS Feed

8 Comments for Sharing I2C bus between OAL RTC part and Driver part on Windows CE

Prashanth | July 26, 2009 at 11:20 pm

Vinoth,
Nice article.
But i have a small concern with this approach. Is this method foolproof? In the sense it could so happen that both OAL RTC and some other driver which is accessing I2C, executes “while (g_DrvGlob->bI2CBusy)” at the same time and both may get I2C access permission, before any one setting this field to 1.

This is the exact reason why CriticalSection/Mutex OS object which is an Atomic operation is used in Application space rather than using a global variable.

Author comment by Vinoth - Microsoft Certified Technology Specialist | July 27, 2009 at 5:50 am

Hi Prashanth,
Thank you for reading my article.

As I have already explained, CriticalSection / Mutex are not accessible from the OAL part. These objects can be accessed only on drivers and applications.

The purpose of this blog is to provide idea - How to share the device without any conflict between the OAL (the part where Critical section and mutex are restriced) and driver.

The code snippet which I have given here is a logic/outline of the implementation and not the actual implementation. Maintaining the atomicity is an implicit requirement. This I have taken care in my implementation.

There are APIs in windows CE that performs atomic operations. These functions can be used in OAL part as well as the driver and application part. Even the Critical Section and Mutex are developed using those API to maintain the atomicity.
Using those APIs we can maintain the atomicity of the while (g_DrvGlob->bI2CBusy), g_DrvGlob->bI2CBusy=1.

Gary (Cash Systemes Industrie) | September 10, 2009 at 4:36 am

Very interesting article, however I still don’t see how it could function. From within the OAL RTC, if the I2C driver is occupied with the I2C, the code block “while (g_DrvGlob->bI2CBusy)” will occupy the OAL entirely resulting in a deadlock. The OAL has priority over the I2C driver which has a resource required by the OAL. Unless you have some method of “sleeping” in the OAL RTC “while…” loop. If you do I am very interested, if you don’t then how can this code work?

Author comment by Vinoth - Microsoft Certified Technology Specialist | September 15, 2009 at 7:20 am

Hi Gary,
Thank you for your interest.
Yes. You are right, we have to introduce some kind of sleeping in the while loop of OAL RTC. See the following code snippet I have already given in the article.
while (g_DrvGlob->bI2CBusy)
{
Millisecond Delay function with 1 ms delay.
}
This 1 ms delay is the time, the OAL will sleep and you can increase your own delay. There is a Window Embedded CE API, which will allow the OAL RTC code to sleep.

Gary (Cash Systemes Industrie) | September 16, 2009 at 2:12 am

I saw that code and we agree. My question was closer to “how do you sleep in the OAL?” Simply saying “Millisecond Delay function with 1 ms delay.” or “This 1 ms delay is the time, the OAL will sleep” doesn’t explain how one can sleep in the OAL. I’m pretty familiar with the Windows CE API and the only routines which are similar to a sleep in the OAL are actually just loops that wait for a timeout. To my knowledge it is not possible to “relinquish” the processor for 1 millisecond from within the OAL. It seems that this is what is required here.

Author comment by Vinoth - Microsoft Certified Technology Specialist | September 17, 2009 at 12:58 am

Whatever i have given in the article is successfully implemented.
Since you are familiar with windows CE, I have given a clue to you. There are some functions exported by the kernel to OAL.

Gary | September 17, 2009 at 4:57 am

Very well. I can use those routines but would still be quite interested in discussing other aspects of your example - notably a usage of the virtual address in the call to OALPAtoVA. The “config.bib” file normally uses virtual addresses yet it is the same address used there that is passed to the OALPAtoVA routine. This routine normally accepts a physical address and returns the virtual address. I wouldn’t mention it but as I built my shared section based on your example, I lost a bit of time figuring out why OALPAtoVA wasn’t working (it was returning NULL). I wouldn’t blame you for not posting this so feel free to answer directly to my email address. In any case much thanks for your help.

Author comment by Vinoth - Microsoft Certified Technology Specialist | September 24, 2009 at 12:54 am

I was kind of occupied some other tasks and could not reply to your comments. I am not sure if you were able to figure out how Sleep was implemented in the kernel make use of the clue I have provided. Anyway I just thought I would share that information also here.

There is a structure NKGLOBAL, that defines functions and variables that the kernel exports. The OAL can only access kernel functions using this global structure. NKSleep () is the function, that provides sleeping facility in OAL.
while (g_DrvGlob->bI2CBusy)
{
NKSleep (Milliseconds);
}

To answer your next question, there are some processor maps the physical address of the RAM to 0×80000000. (Example PXA320). So the static cached virtual address range of windows CE is equals to the physical address of the RAM. The below Macro represent the physical address of the RAM.

#define DRVGLOB_BASE 0×8FFE0000 //Physical address of the RAM

OALPAtoVA() provides static cached virtual address as well as static uncached virtual address, based on the second parameter. See the function syntax VOID* OALPAtoVA( UINT32 pa, BOOL cached);

See the bellow given code snippet, I am passing the physical address of the RAM as a first parameter and FALSE as a second parameter to get the uncached virtual address.

g_DrvGlob = (volatile DRV_GLOB*) OALPAtoVA(DRVGLOB_BASE, FALSE);
g_DrvGlob will contain 0xAFFE0000 as a static uncached virtual address. For you case, you have to pass your RAM physical address.

I understand that you are looking out for implementing the same on your device. We already have this design implemented in our platform and this blog is just a conceptual design of such implementation. As you would be interested in the implementation of this, I have asked our techsupport to contact you directly.

Leave a comment!

<< Production ready Windows CE BSP

Workaround to reload the Power Manager activity timeout upon every booting in Hive based Registry >>

Find it!

 

e-con Systems is a leading Product Design services company drives a passion of Productizing ideas, e-con so far have developed Smart phones, PDAs, Industrial data loggers, Loyalty terminals, Point of Sale terminals and Reference platforms among others with OS focus on Windows CE 6.0, Windows Mobile and Linux 2.6.25.

e-con is popularly known for its Computer on Modules, Reference Designs and Camera Solutions among others...

Copyright © 2009 w w w . e - c o n s y s t e m s . c o m . | Download Comp any Profile | Sitemap  Windows Embedded Gold Partner