Now it's time to get down to the real work - writing the major driver functions in the files sdc_driver.c. First, an overview of what the responsibilities of the driver are:
Before stepping through the driver file in detail, here are some important issues:
#define. If the 16 color server is supported, code specific to it can be enabled with the XF86VGA16
#define. In most cases it is sufficient to put the following near the top of the stub_driver.c file:
#ifdef XF86VGA16 #define MONOVGA #endif
If you are fortunate, the chipset vendor will include in the databook some tables of register settings for various BIOS modes. You can learn a lot about what manipulations you must do by looking at the various BIOS modes.
It is possible, and in fact desirable, to have a single driver support multiple chipsets from the same vendor. If there are multiple supported chipsets, then you would have a series of #define's for them, and a variable `SDCchipset', which would be used throughout the driver when distinctions must be made. See the Trident and PVGA1/WD drivers for examples (the Tseng ET3000 and ET4000 are counter-examples - these were implemented before the driver interface allowed for multiple chipsets, so this example should NOT be followed). Note that you should only distinguish versions when your driver needs to do things differently for them. For example, suppose the SDC driver supports the SDC-1a, SDC-1b, and SDC-2 chipsets. The -1a and -1b are essentially the same, but different from the -2 chipset. Your driver should support the -1 and -2 chipsets, and not distinguish between the -1a and -1b. This will simplify things for the end user.
In cases where you want to give the user control of driver behavior, or there are things that cannot be determined without user intervention, you should use ``option'' flags. Say that board vendors that use the SDC chipsets have the option of providing 8 or 16 clocks. There's no way you can determine this from the chipset probe, so you provide an option flag to let the user select the behavior from the XF86Config file. The option flags are defined in the file ``xf86_option.h''. You should look to see if there is already a flag that can be reused. If so, use it in your driver. If not, add a new #define, and define the string->symbol mapping in the table in that file. To see how option flags are used, look at the ET4000, PVGA1/WD, and Trident drivers.
Once you have an understanding of what is needed from the above description, it is time to fill in the driver data structures. First we will deal with the `vgaSDCRec' structure. This data structure is the driver-local structure that holds the SVGA state information. The first entry in this data structure is ALWAYS `vgaHWRec std'. This piece holds the generic VGA portion of the information. After that, you will have one `unsigned char' field for each register that will be manipulated by your driver. That's all there is to this data structure.
Next you must initialize the `SDC' structure (type `vgaVideoChipRec'). This is the global structure that identifies your driver to the server. Its name MUST be `SDC', in all caps - i.e. it must match the directory name for your driver. This is required so that the Link Kit reconfiguration can identify all of the requisite directories and global data structures.
The first section of this structure simply holds pointers to the driver functions.
Next, you must initialize the information about how your chipset does bank switching. The following fields must be filled in:
The Ident() function is a very simple function. The server will call this function repeatedly, until a NULL is returned, when printing out the list of configured drivers. The Ident() function should return a chipset name for a supported chipset. The function is passed a number which increments from 0 on each iteration.
The ClockSelect() function is used during clock probing (i.e. when no `Clocks' line is specified in the XF86Config file) to select the dot-clock indicated by the number passed in the parameter. The function should set the chipset's clock-select bits according to the passed-in number. Two dummy values will be passed in as well (CLK_REG_SAVE, CLK_SAVE_RESTORE). When CLK_REG_SAVE is passed, the function should save away copies of any registers that will be modified during clock selection. When CLK_REG_RESTORE is passed, the function should restore these registers. This ensure that the clock-probing cannot corrupt registers.
This function should return FALSE if the passed-in index value is invalid or if the clock can't be set for some reason.
The Probe() function is perhaps the most important, and perhaps the
least intuitive function in the driver. The Probe function is required
to identify the chipset independent of all other chipsets. If the user
has specified a `
Chipset' line in the XF86Config file, this is a simple
string comparison check. Otherwise, you must use some other technique
to figure out what chipset is installed. If you are lucky, the chipset
will have an identification mechanism (ident/version registers, etc), and
this will be documented in the databook. Otherwise, you will have to
determine some scheme, using the reference materials listed below.
The identification is often done by looking for particular patterns in register, or for the existence of certain extended registers. Or with some boards/chipsets, the requisite information can be obtained by reading the BIOS for certain signature strings. The best advise is to study the existing probe functions, and use the reference documentation. You must be certain that your probe is non-destructive - if you modify a register, it must be saved before, and restored after.
Once the chipset is successfully identified, the Probe() function must do some other initializations:
VideoRam' parameter in the XF86Config file, the amount of installed memory must be determined.
Clocks' parameter in the XF86Config file, the values for the available dot-clocks must be determined. This is done by calling the vgaGetClocks() function, and passing it the number of clocks available and a pointer to the ClockSelect() function.
The EnterLeave() function is called whenever the virtual console on which the server runs is entered or left (for OSs without virtual consoles, the function is called when the server starts and again when it exits). The purpose of this function is to enable and disable I/O permissions (for OSs where such is required), and to unlock and relock access to ``protected'' registers that the driver must manipulate. It is a fairly trivial function, and can be implemented by following the comments in the stub driver.
The Restore() function is used for restoring a saved video state. Note that `restore' is a bit of a misnomer - this function is used to both restore a saved state and to install a new one created by the server. The Restore() function must complete the following actions:
The Save() function is used to extract the initial video state information when the server starts. The Save() function must complete the following actions:
The Init() function is the second most important function in the driver (after the Probe() function). It is used to initialize a data structure for each of the defined display modes in the server. This function is required to initialize the entire `vgaSDCRec' data structure with the information needed to put the SVGA chipset into the required state. The generic VGA portion of the structure is initialized with a call to vgaHWInit() (also located in vgaHW.c).
Once the generic portion is initialized, the Init() function can override any of the generic register initialization, if necessary. All of the other fields are filled in with the correct initialization. The information about the particular mode being initialized is passed in the `mode' parameter, a pointer to a `DisplayModeRec' structure. This can be dereferenced to determine the needed parameters.
If you only know how to initialize certain bits of the register, do that here, and make sure that the Restore() function does a read/modify/write to only manipulate those bits. Again, refer to the existing drivers for examples of what happens in this function.
The Adjust() function is another fairly basic function. It is called whenever the server needs to adjust the start of the displayed part of the video memory, due to scrolling of the virtual screen or when changing the displayed resolution. All it does is set the starting address on the chipset to match the specified coordinate. Follow the comments in the stub driver for details on how to implement it.
The ValidMode() function is required. It is used to check for any chipset-dependent reasons why a graphics mode might not be valid. It gets called by higher levels of the code after the Probe() stage. In many cases no special checking will be required and this function will simply return TRUE always.
The SaveScreen() function is not needed by most chipsets. This function would only be required if the extended registers that your driver needs will be modified when a synchronous reset is performed on the SVGA chipset (your databook should tell you this). If you do NOT need this function, simply don't define it, and put `NoopDDA' in its place in the vgaVideoChipRec structure initialization (NoopDDA is a generic-use empty function).
If you DO need this function, it is fairly simple to do. It will be called twice - once before the reset, and again after. It will be passed a parameter of SS_START in the former case, and SS_FINISH in the latter. All that needs to be done is to save any registers that will be affected by the reset into static variables on the SS_START call, and then restore them on the SS_FINISH call.
The GetMode() function is not used as of XFree86 1.3; its place in the vgaVideoChipRec should be initialized to `NoopDDA'.
At some point in the future, this function will be used to enable the server and/or a standalone program using the server's driver libraries to do interactive video mode adjustments. This function will read the SVGA registers and fill in a DisplayModeRec structure with the current video mode.
The FbInit() function is required for drivers with accelerated graphics support. It is used to replace default cfb.banked functions with accelerated chip-specific versions. vga256LowlevFuncs is a struct containing a list of functions which can be replaced. This struct defined in vga256.h. Examples of FbInit() functions can be found in the et4000, pvga1 and cirrus drivers.
If you do NOT need this function, simply don't define it, and put `NoopDDA' in its place in the vgaVideoChipRec structure initialization.