This topic describes how to create an LCD Extension.
The topic uses a reference board port named template_variant as an example implementation.
In the template reference board port, the .mmp file for the LCD Extension is ...\template_variant\lcdtemplate.mmp. This is one of the PRJ_MMPFILES referenced in the template variant's bld.inf file in the ...\template_variant\... directory, and means that the LCD Extension is built as part of the Variant.
The source for the driver is contained entirely within ...\template_variant\specific\lcd.cpp.
The driver is defined as a kernel extension and is loaded early in the boot sequence.
The driver functionality is almost entirely encapsulated by the DLcdPowerHandler class. This is a power handler class derived from DPowerHandler. An instance of DLcdPowerHandler is created when the extension is loaded.
DLcdPowerHandler is defined within the source file itself ...\template_variant\specific\lcd.cpp.
As the driver is a kernel extension, it must have a DECLARE_STANDARD_EXTENSION() statement. In the template port, this is implemented as follows:
DECLARE_STANDARD_EXTENSION() { __KTRACE_OPT(KPOWER,Kern::Printf("Starting LCD power manager")); // create LCD power handler TInt r=KErrNoMemory; DLcdPowerHandler* pH=new DLcdPowerHandler; if (pH) r=pH->Create(); __KTRACE_OPT(KPOWER,Kern::Printf("Returns %d",r)); return r; }
This simply creates an instance of the DLcdPowerHandler class and then calls its Create() function which implements the display setup. This function should do the following:
Map the video RAM
The frame buffer is a DPlatChunkHw object, and should be mapped as globally accessible, readable and writeable. It should not be mapped as writeback cached, it should be either not-cached or write-through. The advantage of write through is that it allows the use of the write buffer.
TInt DLcdPowerHandler::Create() { ... // map the video RAM TInt vSize = ((TemplateAssp*)Arch::TheAsic())->VideoRamSize(); ivRamPhys = TTemplate::VideoRamPhys(); // EXAMPLE ONLY: assume TTemplate interface class TInt r = DPlatChunkHw::New(iChunk,ivRamPhys,vSize,EMapAttrUserRw|EMapAttrBufferedC); if ® != KErrNone) return r; ...
If the frame buffer resides in main RAM and there is no restriction on which physical addresses may be used for it, physical RAM for the frame buffer should be reserved by using Epoc::AllocPhysicalRam().
If the frame buffer does not reside in main RAM, there is no problem about reserving it.
If the frame buffer must reside at a specific address in main RAM, there are two strategies available for reserving it:
If no conflicts are permitted between the frame buffer and memory allocations made during the kernel boot (for example, if the frame buffer must reside at the end of main memory), simply use Epoc::ClaimPhysicalRam(). This function just marks a region of physical RAM as allocated, returning an error if any part of the region has already been used.
The required physical RAM region can be reserved in the bootstrap. The correct place to do this is in the implementation of the boot table function BTF_Reserve when writing platform-specific source code for the bootstrap. See the Bootstrap Port Implementation Tutorial for more detail and look at ...\template_variant\bootstrap\template.s for a concrete example.
Note that all Symbian platform base ports currently create a second frame buffer for a secure screen. However, as platform security is not yet implemented, this is wasteful of RAM and should be omitted.
Set up the video information structure
The video information structure is used to define several aspects of the display including display size, bits per pixel and address of the frame buffer. This structure is the class TVideoInfoV01 defined in the header file ...\eka\include\videodriver.h and exported to ...\epoc32\include.
TInt DLcdPowerHandler::Create() { ... // setup the video info structure, this will be used to remember the video settings iVideoInfo.iDisplayMode = KConfigLcdInitialDisplayMode; iVideoInfo.iOffsetToFirstPixel = Lcd_Mode_Config[KConfigLcdInitialDisplayMode].iOffsetToFirstVideoBuffer; iVideoInfo.iIsPalettized = Lcd_Mode_Config[KConfigLcdInitialDisplayMode].iIsPalettized; iVideoInfo.iOffsetBetweenLines = Lcd_Mode_Config[KConfigLcdInitialDisplayMode].iOffsetBetweenLines; iVideoInfo.iBitsPerPixel = Lcd_Mode_Config[KConfigLcdInitialDisplayMode].iBitsPerPixel; iVideoInfo.iSizeInPixels.iWidth = KConfigLcdWidth; iVideoInfo.iSizeInPixels.iHeight = KConfigLcdHeight; iVideoInfo.iSizeInTwips.iWidth = KConfigLcdWidthInTwips; iVideoInfo.iSizeInTwips.iHeight = KConfigLcdHeightInTwips; iVideoInfo.iIsMono = KConfigLcdIsMono; iVideoInfo.iVideoAddress=(TInt)pV; iVideoInfo.iIsPixelOrderLandscape = KConfigLcdPixelOrderLandscape; iVideoInfo.iIsPixelOrderRGB = KConfigLcdPixelOrderRGB; ... }
Install the HAL handler
Control of the display is done by using the HAL, the Hardware Abstraction Layer.
The DLcdPowerHandler class provides the implementation for the HAL handler for the HAL function group EHalGroupDisplay and this needs to be registered with the kernel by calling Kern::AddHalEntry().
TInt DLcdPowerHandler::Create() { ... // install the HAL function r=Kern::AddHalEntry(EHalGroupDisplay, halFunction, this); if (r!=KErrNone) return r; ... }
See User-Side Hardware Abstraction for more detailed information on the HAL.
Install the power handler
A call must be made to the Add() function, which is supplied by the DPowerHandler base class of DLcdPowerHandler, to register the handler with the power manager.
TInt DLcdPowerHandler::Create() { ... // install the power handler // power up the screen Add(); ... }
Requests to get and set hardware attributes are made through calls to HAL::Get() and HAL::Set(). These two HAL functions take a value that identifies a hardware attribute, one of the HALData::TAttribute values.
For the LCD Extension, the relevant hardware attributes are: EDisplayMode, EDisplayBitsPerPixel, EDisplayIsPalettized, EDisplayIsMono, EDisplayMemoryAddress, EDisplayMemoryHandle, EDisplayOffsetToFirstPixel, EDisplayOffsetBetweenLines, EDisplayXPixels, EDisplayYPixels, EDisplayPaletteEntry and EDisplayOffsetBetweenLines.
The HAL handler is registered with the kernel as the handler for the THalFunctionGroup::EHalGroupDisplay group. The HAL handler itself takes a function ID, which is one of the TDisplayHalFunction enumerators.
A call to HAL::Get() and HAL::Set() that takes one of the hardware attributes relevant to the LCD Extension is ultimately routed to a call to this HAL handler function passing an appropriate function ID. The association between the hardware attribute and the function ID is the responsibility of the accessor functions.
See User-Side Hardware Abstraction for more information on the way this works in general.
The HAL handler is implemented as a case statement, switching on the function ID. For example, the following code fragment taken from DLcdPowerHandler::HalFunction() gets and sets the brightness:
TInt DLcdPowerHandler::HalFunction(TInt aFunction, TAny* a1, TAny* a2) { TInt r=KErrNone; switch(aFunction) { ... case EDisplayHalSetDisplayBrightness: if(!Kern::CurrentThreadHasCapability(ECapabilityWriteDeviceData, __PLATSEC_DIAGNOSTIC_STRING("Checked by Hal function EDisplayHalSetDisplayBrightness"))) return KErrPermissionDenied; r=SetBrightness(TInt(a1)); break; case EDisplayHalDisplayBrightness: kumemput32(a1,&iBrightness,sizeof(iBrightness)); break; ...
where SetBrightness() is implemented as:
TInt DLcdPowerHandler::SetBrightness(TInt aValue) { __KTRACE_OPT(KEXTENSION,Kern::Printf("SetBrightness(%d)", aValue)); if (aValue >= KConfigLcdMinDisplayBrightness && aValue <= KConfigLcdMaxDisplayBrightness) { iBrightness=aValue; // TO DO: (mandatory) // set the brightness // return KErrNone; } return KErrArgument; }
If an attribute does not have an implementation, the HAL handler function should return KErrNotSupported.
For platform security, the code only allows the attribute to be set if the current thread has been authorized to write system data. Otherwise, it returns KErrPermissionDenied.
Switch on and switch off operations
All of the HAL operations are seen to be synchronous by the user side. However there are some operations such as turning the display on and off which may need to be implemented asynchronously.
The display on/off code is implemented using synchronous kernel-side messages. There is only one message per thread and the thread always blocks while a message is outstanding. This means it is possible to make an asynchronous operation appear synchronous.
When turning on the screen the kernel-side message is queued and this thread is blocked until the message is completed, which happens when the display has been turned on.
If a display needs to be turned on and off truly asynchronously (for example, if millisecond timer waits are required during the process of turning on the display), the above functionality must be changed so that the complete occurs when the display is truly on.
Accessing the video information structure
When any part of the video information structure is read or written to, this must be done within a critical section to prevent potential collisions with other threads attempting to access the structure concurrently. A fast mutex is used to ensure that only one thread can access the video information at any one time, as the code segment below shows.
TInt DLcdPowerHandler::GetCurrentDisplayModeInfo(TVideoInfoV01& aInfo, TBool aSecure) { __KTRACE_OPT(KEXTENSION,Kern::Printf("GetCurrentDisplayModeInfo")); NKern::FMWait(&iLock); if (aSecure) aInfo = iSecureVideoInfo; else aInfo = iVideoInfo; NKern::FMSignal(&iLock); return KErrNone; }
The DPowerHandler class defines the interface that the driver must implement to provide power handling behaviour. For the template reference board, the LCD Extension defines and implements the DLcdPowerHandler class derived from DPowerHandler.
Note:
DPowerHandler::PowerDown() and DPowerHandler::PowerUp()
These functions are called in the context of the thread that initiates power down or power up, and synchronization is required, typically by means of power up and power down DFCs.
DPowerHandler::PowerUpLcd() and DPowerHandler::PowerDownLcd()
These functions generally queue DFCs which then call platform-specific functions to power the display up and down.
DPowerHandler::PowerUpDone() and DPowerHandler::PowerDownDone()
When power up or down is complete, the interface supplies a set of acknowledgment functions which must be called when the change of state has taken place.
Copyright ©2010 Nokia Corporation and/or its subsidiary(-ies).
All rights
reserved. Unless otherwise stated, these materials are provided under the terms of the Eclipse Public License
v1.0.