Sample Drivers for Instant Device Activation

FreeScale Media5200b sample minidriver

This sample is a minidriver for the Media5200b board. The driver initializes the serial port at a known baud rate and buffers the characters into the data area. Once the system is booted, a program can then read this data area and retrieve the buffered characters. For this implementation, we require:

The minidriver handler function

The prototype for the minidriver handler function is as follows:

int mdriver_handler(int state, void *data);

The state value informs the handler function where in the boot process it's being called from. See the mdriver_add() documentation in the API and Datatypes chapter of this guide. The data parameter is a virtual pointer to the minidriver's allocated data area. For this sample driver, the source code for the handler function would look like:

struct mserial_data
{
   psc_regs	*psc;
   psc_regs	*psc_k;
   uint16_t	intr_calls;
   uint16_t	ncalls;
   uint16_t	errors;
   uint16_t	last_count;
   uint16_t 	data_len;
};

/*
 * Hardware initialization function which only gets called 
 * the first time the mini-driver is run.
 * The Baud, Clk and port are hardcoded for this example
 *   baud = 14400
 *   clk = 132000000
 *   port = PSC1
 */
static psc_regs *
init_hw()
{
   /* hardcode to PSC1, baud and clk */
   int 		baud = 14400;
   int 		clk = 132000000;
   paddr64_t	psc_base = MGT5200_MBAR_BASE + 0x2000;
   psc_regs		*psc;
   gpio_std_regs	*gpio;

   if ((psc = (psc_regs *)(startup_memory_map(0xA0,psc_base, 
 	  PROT_READ|PROT_WRITE|PROT_NOCACHE))) == 0)
       return (0);
		
   if ((gpio = (gpio_std_regs*)(startup_memory_map(0x40,(MGT5200_MBAR_BASE + 0x0b00),
 	  PROT_READ|PROT_WRITE|PROT_NOCACHE))) == 0)
   {
      startup_memory_unmap((void *)psc);
      return (0);
   }

   baud = ( (clk / baud) + 16 ) / 32 ; /* calculate baud rate */
   psc->CR = PSC_CR_TXD | PSC_CR_RXD;
   psc->SICR = 0x00000000;         /* write to SICR: SIM2 = uart mode,dcd 
                                   does not affect rx */
   psc->SR_CSR = 0xdd00;           /* write to CSR: RX/TX baud rate 
                                   from timers */
   psc->CTUR = (baud >> 8) & 0xff; /* write to CTUR: divide counter 
                                   upper byte */
   psc->CTLR = baud & 0xff;        /* write to CTLR: divide counter 
                                   lower byte */
   psc->CR = 0x20;                 /* write to CR: reset RX and 
                                   RXFIFO */
   psc->CR = 0x30;                 /* write to CR: reset TX and 
                                   TXFIFO */
   psc->CR = 0x40;                 /* write to CR: reset 
                                   error status */
   psc->CR = 0x50;                 /* write to CR: reset 
                                   break change int */
   psc->CR = 0x10;                 /* write to CR: reset MR pointer */
   psc->IPCR_ACR = 0x00;           /* write to ACR: disable state 
                                   change CTS/ DCD interrupts */
   psc->ModeReg = 0x73;            /* write to MR1: 8bit data, 
                                   no parity */
   psc->ModeReg = 0x07;            /* write to MR2: normal 
                                   mode,1stop bit */
   psc->OP1 = 1;                   /* Write to OP1: enable 
                                   RTS to send */
   psc->ISR_IMR = 0x0000;          /* write to IMR: Mask RxRDY and 
                                   TxRDY from causing an interrupt */
   psc->RFALARM = 511;
   psc->TFALARM = 0xF8;
   psc->RFCNTL = 0x04;
	
   gpio->port_config = (gpio->port_config & GPIO_PSC1_MASK) | 
                       GPIO_PSC1_UART_ENABLE;
		
   /* Enable the transmitters and receivers */
   psc->CR = 0x05; /* write to CR: enable TX and RX.*/
   psc->ISR_IMR = PSC_ISR_RxRDY;

   startup_memory_unmap((void *)gpio);
	
   /* return mapped register pointer */
   return (psc);
}

int mini_serial(int state, void *data)
{
   uint8_t	   	*bp = 0;
   paddr64_t	psc_base = MGT5200_MBAR_BASE + 0x2000;
   uint16_t	count;
   uint8_t		*dptr;
   struct mserial_data	*mdata;
	
   mdata = (struct mserial_data *) data;
   dptr = (uint8_t *) (mdata + 1);

   if (state == MDRIVER_INTR_ATTACH)
   {
	  /* disable the serial interrupt */
	  mdata->psc->ISR_IMR = 0x0000;
	  * make sure there are no characters in the hardware fifo */
	  count = 0;
	  while (mdata->psc->SR_CSR & PSC_SR_RxRDY) 
	  {
	      bp = (uint8_t*) &mdata->psc->RB_TB;
	      dptr[mdata->data_len+count] = *bp;
	      count++;
	  }
	      mdata->last_count = count;
	      count = count + mdata->data_len;
 	      mdata->data_len = count;
	
	      /* reset any error conditions */    
	      if ((mdata->psc->SR_CSR & PSC_SR_ORERR) ||
	     	    (mdata->psc->SR_CSR & PSC_SR_RB) ||
	    	    (mdata->psc->SR_CSR & PSC_SR_FE) ||
		    (mdata->psc->SR_CSR & PSC_SR_PE))
	      {
		    mdata->psc->CR = PSC_CR_RESET_ERR;
		    count = mdata->errors + 1;
		    mdata->errors = count;
	      }
	
	      return (1);
    }
    else if (state == MDRIVER_INIT)
    {
       /* the first time called initialize the hardware and do data setup */
       mdata->psc = init_hw();
       if (mdata->psc == 0)
	  return (1);
       mdata->intr_calls = 0;
       mdata->ncalls = 0;
       mdata->errors = 0;
       mdata->data_len = 0;
    }
    else if (state == MDRIVER_STARTUP_PREPARE)
    {
       /* once we are out of startup use callout_io_map */
       if ((mdata->psc_k = (psc_regs *)(callout_memory_map(0xA0,psc_base,
    	   PROT_READ|PROT_WRITE|PROT_NOCACHE))) == 0)
       {
	   /* something bad so disable the driver */
	   mdata->psc->ISR_IMR = 0x0000;
	   return (1);
	   }
       }

    /* count the number of times the mini-driver is called */
    count = mdata->ncalls + 1;
    mdata->ncalls = count;
    if (state == MDRIVER_PROCESS)
    {
	/* called because of an interrupt */
	count = mdata->intr_calls + 1;
	mdata->intr_calls = count;
    }	
	
    count = 0;
    while (mdata->psc->SR_CSR & PSC_SR_RxRDY) 
    {
	bp = (uint8_t*) &mdata->psc->RB_TB;
    	dptr[mdata->data_len+count] = *bp;
	count++;
     }
    	
     mdata->last_count = count;
     count = count + mdata->data_len;
     mdata->data_len = count;

     /* reset any error conditions */    
     if ((mdata->psc->SR_CSR & PSC_SR_ORERR) ||
     	 (mdata->psc->SR_CSR & PSC_SR_RB) ||
    	 (mdata->psc->SR_CSR & PSC_SR_FE) ||
	 (mdata->psc->SR_CSR & PSC_SR_PE))
     {
	 mdata->psc->CR = PSC_CR_RESET_ERR;
	 count = mdata->errors + 1;
	 mdata->errors = count;
     }

     if (state == MDRIVER_STARTUP_FINI)
	mdata->psc = mdata->psc_k;	
	
     return (0);
}

In this example, the handler function stores call information, so a structure has been created to allow easier access to the data area. The data area is filled with the received characters.

Access to the serial port registers is necessary. At initialization time, the registers are mapped with startup_memory_map(), and the pointer is stored in the data area during the startup phase. The registers are mapped in with startup_memory_map(). Once the startup phase is complete, the handler must use callout_memory_map() to access the registers.

During the MDRIVER_INIT state, the data area is initialized, and the serial hardware is set up; this is called only once. At this time, the startup_memory_map() is called to map in the hardware registers, and the pointer is stored in the data area. This pointer is used for hardware access until the handler is called with a state of STARTUP_MDRIVER_PREPARE. At this time callout_memory_map() can be called and the pointer is stored in the data area, however the startup_memory_map() pointer should still be used. Once the handler is called with a state of MDRIVER_STARTUP_FINI, the handler starts using the pointer returned by callout_memory_map() for all hardware access. This pointer is then used for all further invocations of the minidriver handler.

When the handler is called with a state of MDRIVER_INTR_ATTACH, it disables the device interrupt and returns a value of 1 requesting an exit of the handler.

Adding your minidriver to the system

Once you have written a handler function, you need to register it with startup and allocate the required system RAM for your data area. This can be accomplished with the following functions:

paddr_t alloc_ram(phys_addr, size, alignment);
int mdriver_add(name, interrupt, handler, data_paddr, data_size);

Since we are allocating memory and passing an interrupt these functions must be called after RAM is initialized by calling init_raminfo(), and after the interrupt information is added to the system page by calling init_intrinfo(). The main() function of our startup main.c will look like this:

	...
paddr_t		mdrvr_addr;
...

/*
* Collect information on all free RAM in the system.
*/
init_raminfo();
	
//
// In a virtual system, we need to initialize the page tables
//
if(shdr->flags1 & STARTUP_HDR_FLAGS1_VIRTUAL) init_mmu();

/*
* The following routines have hardware or system dependencies which
* may need to be changed.
*/
init_intrinfo();

mdrvr_addr = alloc_ram(~0L, 65536, 1); /* grab 64k */
mdriver_add("mini-serial",  0,  mini_serial, mdrvr_addr, 65536);
...

In this example, we have allocated 64 KB of memory and registered our minidriver handler function with the name mini-serial.

Build startup

The minidriver is complete now, and you must rebuild startup and boot image. In order to verify that the minidriver is running properly, you may want to add debug information into the handler function or write an application to read the minidriver data area and print the contents.

Testing your minidriver

The Media5200b board has a single serial port that this minidriver example uses to receive bytes on. In order to test this driver, you should build an OS image that sets up TCP/IP and lets you telnet to the board. Since you'll be using the serial port as a data source; don't run the serial driver and don't put debug printouts or verbosity on procnto in your image. Once you have an image, you should burn it onto the onboard flash where you can boot from it.

Connect a NULL-modem cable between your Media5200b board and a host machine. The minidriver is running at 14400 baud, with no flow control. You must make sure that the host machine's serial port is also configured in this manner. On the host machine, begin sending characters to the serial port. Sample code should look like:

...
uint8_t test_data[20] = {"012345678987654321"};
...

if ((fd = open("/dev/ser1", O_RDWR)) == -1)
{
   fprintf(stderr, "Unable to open device: %s (errno=%d\n", device, errno);
   return (-1);
}

for (;;)
{
write(fd, test_data + index, 1);
   index++;
   if (index == 19)
   index = 0;
}

This code sends a pattern of characters to the serial port so that the minidriver can capture data.

Now boot the Media5200b board and telnet to it.

Once connected, you can run the sample full driver, mdrvr-serpsc that does the following:

Since the mdrvr-serpsc application is a sample serial driver you don't need to run devc-serpsc driver.

Sample timings

These timings are based on sample boot image with the following properties:

mdriver_max Number of calls Average calling interval
1 KB 855 514 microseconds
4 KB 231 2.04 ms
16 KB 75 8.13 ms

Boot time First invocation time
453.2 ms 1 ms

Note: Boot time identifies the time from startup to the start of the first application process. The first invocation time is the time from IPL to the first invocation of the minidriver (MDRIVER_INIT).

Renesas Biscayne minidriver

This sample is a minidriver for the Biscayne board. The driver initializes the serial port at a known baud rate and buffers the characters into the data area. Once the system is booted, a program can then read this data area and retrieve the buffered characters.

For this implementation we require:

The minidriver handler function

The prototype for the minidriver handler function is as follows:

int mdriver_handler(int state, void *data);

The state value informs the handler function where in the boot process it's being called from. See the mdriver_add() documentation in the API and Datatypes chapter of this guide. The data parameter is a virtual pointer to the minidriver's allocated data area. For this sample driver, the source code for the handler function would look like this:

struct mserial_data
{
   uintptr_t	port;
   uintptr_t	port_k;
   uint16_t	intr_calls;
   uint16_t	ncalls;
   uint16_t	errors;
   uint16_t	err;
   uint16_t	last_count;
   uint16_t 	data_len;
};


void Delay(int n)
{
   unsigned int i;
   for(i=0;i<n;i++);
}

uintptr_t
init_hw()
{
   int 	cnt; 
   uint64_t	base = SH_SCIF_BASE;
   int		baud = 14400;
   int		clk = 33333333;
   unsigned port;
	
   if ((port = startup_io_map(120, base)) == NULL)
       return (NULL);
	
    cnt = clk / (baud * 64 / 2) - 1;  // assumes SCSMR1.CKS = 0

    //	set clock selection
    out16(port + SH_SCIF_SCSCR_OFF, 0x0);
    Delay(1);

    // Enable reset state of the FIFO register
    out16(port + SH_SCIF_SCFCR_OFF, SH_SCIF_SCFCR_TFRST | SH_SCIF_SCFCR_RFRST);

    // data transfer format
    // 8 bits no parity
    // CKS = 0
    out16(port + SH_SCIF_SCSMR_OFF,0);
    Delay(1);

    // baud rate
    out8(port + SH_SCIF_SCBRR_OFF,cnt);
    Delay(1);

    //Disable the reset state of the fifo
    set_port16(port + SH_SCIF_SCFCR_OFF, SH_SCIF_SCFCR_TFRST | SH_SCIF_SCFCR_RFRST, 0);

    // FIFO control
    out16(port + SH_SCIF_SCFCR_OFF + 4, 0x0);
    Delay(1);

    out8(port + SH_SCIF_SCFTDR_OFF, 0);
    Delay(1);

    out16(port + SH_SCIF_SCSPTR_OFF + 4, 0x41);

    out16(port + SH_SCIF_SCFCR_OFF, 0x30 | 0x200 | SH_SCIF_SCFCR_MCE);
	
    out16(port + SH_SCIF_SCSCR_OFF, SH_SCIF_SCSCR_RE | SH_SCIF_SCSCR_TE |
                                  SH_SCIF_SCSCR_TIE | SH_SCIF_SCSCR_RIE);
    Delay(1);	
    out16(port + SH7760_SCIF_SCSPTR_OFF, SH_SCIF_SCSPTR_RTSIO);

    out16(port + SH_SCIF_SCSCR_OFF, SH_SCIF_SCSCR_RE | SH_SCIF_SCSCR_RIE);

    return (port);
}


int 
mini_serial(int state, void *data)
{
   uint8_t	   	*bp = 0;
   uint16_t	count;
   uint8_t		*dptr, c;
   struct mserial_data	*mdata;
   int num, i;
   int          pending_interrupts;

   mdata = (struct mserial_data *) data;
   dptr = (uint8_t *) (mdata + 1);

   if (state == MDRIVER_INTR_ATTACH)
   {
      /* disable the serial interrupt */
      set_port16(mdata->port + SH_SCIF_SCSCR_OFF, SH_SCIF_SCSCR_RE | SH_SCIF_SCSCR_RIE, 0);
	    	return (1);
   }
   else if (state == MDRIVER_INIT)
   {
      /* the first time called initialize the hardware and do data setup */
      mdata->port = init_hw();
      if (mdata->port == 0)
	 return (1);
      mdata->intr_calls = 0;
      mdata->ncalls = 0;
      mdata->errors = 0;
      mdata->data_len = 0;
   }
   else if (state == MDRIVER_STARTUP_PREPARE)
   {
      /* once we are out of startup use callout_io_map */
      if ((mdata->port_k = callout_io_map(0x120, SH_SCIF_BASE)) == 0)
      {
	 /* something bad so disable the driver */
	 set_port16(mdata->port + SH_SCIF_SCSCR_OFF, SH_SCIF_SCSCR_RE | SH_SCIF_SCSCR_RIE, 0);
	 return (1);
       }
   }

   /* count the number of times the mini-driver is called */
   count = mdata->ncalls + 1;
   mdata->ncalls = count;
   if (state == MDRIVER_PROCESS)
   {
      /* called because of an interrupt */
      count = mdata->intr_calls + 1;
      mdata->intr_calls = count;
   }	

   /* get the data from the serial port and clear any errors */

   // Handle the error messages: Overrun, Framing, Parity
   pending_interrupts = in16(mdata->port + SH_SCIF_SCFSR_OFF);
   if (in8(mdata->port + SH7760_SCIF_SCLSR_OFF) & SH_SCIF_SCLSR_ORER) 
   {
      set_port16(mdata->port + SH7760_SCIF_SCLSR_OFF, SH_SCIF_SCLSR_ORER, 0);
   }
   if ((pending_interrupts & SH_SCIF_SCFSR_PER)) 
   {
      set_port16(mdata->port + SH_SCIF_SCFSR_OFF, SH_SCIF_SCFSR_PER | 
          SH_SCIF_SCFSR_ER | SH_SCIF_SCFSR_DR, 0);
   }
   if (pending_interrupts & SH_SCIF_SCFSR_FER) 
   {
      set_port16(mdata->port + SH_SCIF_SCFSR_OFF, 
          SH_SCIF_SCFSR_FER | SH_SCIF_SCFSR_ER | SH_SCIF_SCFSR_DR, 0);
   }
   if (pending_interrupts & SH_SCIF_SCFSR_BRK ) 
   {
      set_port16(mdata->port + SH_SCIF_SCFSR_OFF, SH_SCIF_SCFSR_BRK | SH_SCIF_SCFSR_DR, 0);
   }

   /* grab data */
   if (in16(mdata->port + SH7760_SCIF_SCRFDR_OFF)	)
   {
      c = in8(mdata->port + SH_SCIF_SCFRDR_OFF);
      set_port16(mdata->port + SH_SCIF_SCFSR_OFF, SH_SCIF_SCFSR_RDF, 0);
	    
      dptr[mdata->data_len] = c;
      mdata->data_len = mdata->data_len + 1;
   }
			        
   if (pending_interrupts & (SH_SCIF_SCFSR_TDFE|SH_SCIF_SCFSR_TEND) )
   {
      set_port16(mdata->port + SH_SCIF_SCSCR_OFF, SH_SCIF_SCSCR_TIE,0);
   }
		
   //Clear all the interrupts
   set_port16(mdata->port + SH7760_SCIF_SCLSR_OFF, SH_SCIF_SCLSR_ORER, 0);
   set_port16(mdata->port + SH_SCIF_SCFSR_OFF, 0xff, 0);

   if (state == MDRIVER_STARTUP_FINI)
      mdata->port = mdata->port_k;	
	
   return (0);
}

In this example, the handler function stores call information, so a structure has been created to allow easier access to the data area. The data area is filled with the received characters.

During the MDRIVER_INIT state, the data area is initialized and the serial hardware is set up; this is called only once. At this time, the startup_io_map() is called to map in the hardware registers, and the pointer is stored in the data area.

This pointer is used for hardware access until the handler is called with a state of STARTUP_MDRIVER_PREPARE. At this time, callout_io_map() is called and the pointer is stored in the data area, however the startup_io_map() pointer should still be used. Once the handler is called with a state of MDRIVER_STARTUP_FINI, the handler starts using the pointer returned by callout_io_map() for all hardware access. This pointer is then used for all further invocations of the minidriver handler.

When the handler is called with a state of MDRIVER_INTR_ATTACH; it disables the device interrupt and returns a value of 1 requesting an exit of the handler.

Adding your minidriver to the system

Once you have written a handler function, you need to register it with startup and allocate the required system RAM for your data area. This is accomplished with the following functions:

paddr_t alloc_ram(phys_addr, size, alignment);
int mdriver_add(name, interrupt, handler, data_paddr, data_size);

Since you're allocating memory and passing an interrupt, these functions must be called after RAM is initialized by calling init_raminfo(), and after the interrupt information is added to the system page by calling init_intrinfo(). The main() function of our startup main.c looks like this:

...
paddr_t		mdrvr_addr;
...

/*
* Collect information on all free RAM in the system.
*/
init_raminfo();
	
//
// In a virtual system we need to initialize the page tables
//
if(shdr->flags1 & STARTUP_HDR_FLAGS1_VIRTUAL) init_mmu();

/*
* The following routines have hardware or system dependencies which
* may need to be changed.
*/
init_intrinfo();

mdrvr_addr = alloc_ram(~0L, 65536, 1); /* grab 64k */
mdriver_add("mini-serial",  0,  mini_serial, mdrvr_addr, 65536);
...

In this example, you have allocated 64 KB of memory and registered the minidriver handler function with the name mini-serial.

Build startup

The minidriver is now complete, and you should rebuild startup and the boot image. In order to verify that the minidriver is running properly, you may want to add debug information into the handler function or write an application to read the minidriver data area and print the contents.

Testing your minidriver

The Biscayne board has a debug serial port that this minidriver example uses to receive bytes on. In order to test this driver, you should build an OS image that sets up TCP/IP and lets you telnet to the board. Since you're using the serial port as a data source, don't run the serial driver and don't put debug printouts or verbosity on procnto in your image. Once you have an image, you should burn it onto the onboard flash where you can boot from it.

Connect a NULL-modem cable between your Biscayne board and a host machine. The minidriver is running at 14400 baud, so make sure the host machine's serial port is also configured in this manner. On the host machine, begin sending characters to the serial port. Sample code should look like this:

...
uint8_t test_data[20] = {"012345678987654321"};
...

if ((fd = open("/dev/ser1", O_RDWR)) == -1)
{
   fprintf(stderr, "Unable to open device: %s (errno=%d\n", device, errno);
   return (-1);
}

for (;;)
{
write(fd, test_data + index, 1);
   index++;
   if (index == 19)
   index = 0;
}

This code sends a pattern of characters to the serial port so that the minidriver can capture data.

Now boot the Biscayne board and telnet to it. Once connected, you can run the sample full driver, mdrvr-sescif that does the following:

Since the mdrvr-serscif application is a sample serial driver, you don't need to run the devc-sersci driver.

Sample Timings

These timings are based on sample boot image with the following properties:

mdriver_max Number of calls Average calling interval
1 KB 715 59 microseconds
4 KB 139 194 microseconds
16 KB 65 934 microseconds

Boot time first invocation time
636 ms 1 ms

Note: Boot time identifies the time from startup to the start of the first application process. The first invocation time is the time from IPL to the first invocation of the minidriver (MDRIVER_INIT).

OMAP minidriver

This sample is a minidriver for the OMAP 5912 board. The driver initializes the serial port at a known baud rate and buffers the characters into the data area. Once the system is booted, a program can then read this data area and retrieve the buffered characters. For this implementation we require:

The prototype for the minidriver handler function is as follows:

int mdriver_handler(int state, void *data);

The state value informs the handler function where in the boot process it is being called from. See the mdriver_add() documentation in the API and Datatypes chapter of this guide. The data parameter is a virtual pointer to the minidriver's allocated data area. For this sample driver, the source code for the handler function should look like this:

struct mserial_data
{
   uintptr_t	port;
   uintptr_t	port_k;
   uint16_t	intr_calls;
   uint16_t	ncalls;
   uint16_t	errors;
   uint16_t	err;
   uint16_t	last_count;
   uint16_t 	data_len;
};

#ifndef write_omap
#define	write_omap(__port,__val)	out8(__port,__val)
#endif

#ifndef read_omap
#define	read_omap(__port)	in8(__port)
#endif

/*
 * Initialize the UART hardware
 * base address = 0xfffb0000
 * baud rate = 14400
 * data = 8 n 1
 */
uintptr_t
init_hw()
{
   unsigned		value = 0;
   unsigned 		msr, c;
   uintptr_t		port;
   uint64_t		base = 0xfffb0000;
   int	       		baud = 14400;
	
   if ((port = startup_io_map(OMAP_UART_SIZE, base)) == 0)
      return (0);

   // hit LCR with special byte to enable access to the Enhanced Feature Register (EFR)
   write_omap(port + OMAP_UART_LCR,0xbf);

   // turn off S/W flow control, enable writes to MCR[7:5], FCR[5:4], and IER[7:4]
   write_omap(port + OMAP_UART_EFR, 0x10); 

   write_omap(port + OMAP_UART_LCR,0);

   // set MCR bit 6 to enable access to TCR and TLR registers
   write_omap(port + OMAP_UART_MCR, 0x40);

   value = 0x0;
   // set TCR - RX FIFO - start Rx at 0 bytes, halt at rx fifo value
   write_omap(port + OMAP_UART_TCR,value >> 4);

   write_omap(port + OMAP_UART_TLR, value);

   // disable access to TCR and TLR
   write_omap(port + OMAP_UART_MCR, 0x00);
   write_omap(port + OMAP_UART_SCR, 0x00);

   write_omap(port + OMAP_UART_LCR,0xbf);

   // disable access to MCR[7:5], FCR[5:4], and IER[7:4], 
   // disable auto flow control and sw flow control
   write_omap(port + OMAP_UART_EFR, 0x00);

   // set Divisor Latch Enable - writing something other 
   // than 0xbf to LCR puts it back in "normal" mode
   write_omap(port + OMAP_UART_LCR, 0x80);

   // baud and clk
   value = 48000000 / (16 * baud);
   write_omap(port + OMAP_UART_DLL, value);
   write_omap(port + OMAP_UART_DLH, (value >> 8) & 0xff);
   write_omap(port + OMAP_UART_LCR, 0x13);

   // turn on DTR, RTS
   c = read_omap(port + OMAP_UART_MCR);
   write_omap(port + OMAP_UART_MCR, (c & ~OMAP_MCR_DTR|OMAP_MCR_RTS) | 
   OMAP_MCR_DTR|OMAP_MCR_RTS);
			
   // According to the National bug sheet you must wait for the transmit
   // holding register to be empty.
   do {
   } while((read_omap(port + OMAP_UART_LSR) & OMAP_LSR_TXRDY) == 0);

   // Clean the device so we get a level change on the intr line to the bus.
   // Enable out2 (gate intr to bus)
   c = read_omap(port + OMAP_UART_MCR);
   write_omap(port + OMAP_UART_MCR, (c & ~OMAP_MCR_OUT2) | OMAP_MCR_OUT2);

   write_omap(port + OMAP_UART_IER, 0);		// Disable all interrupts
   read_omap(port + OMAP_UART_LSR);			// Clear Line Status Interrupt
   read_omap(port + OMAP_UART_RHR);			// Clear RX Interrupt
   read_omap(port + OMAP_UART_THR);			// Clear TX Interrupt
   read_omap(port + OMAP_UART_MSR);			// Clear Modem Interrupt

   // Enable interrupt sources.
   write_omap(port + OMAP_UART_IER, 0x01);
	
   // get current MSR stat
   msr = read_omap(port + OMAP_UART_MSR);
	
   return (port);
}

int 
mini_serial(int state, void *data)
{
    uint16_t			count;
    uint8_t				*dptr, c;
    struct mserial_data	*mdata;
    unsigned			lsr, iir;

    mdata = (struct mserial_data *) data;
    dptr = (uint8_t *) (mdata + 1);

    if (state == MDRIVER_INTR_ATTACH)
    {
       /* disable the serial interrupt */
       write_omap(mdata->port + OMAP_UART_IER, 0x00);
       return (1);
    }
    else if (state == MDRIVER_INIT)
    {
       if ((mdata->port = init_hw()) == 0)
	  return (1);
			
	/* clear the data area counters */	
	mdata->intr_calls = 0;
	mdata->ncalls = 0;
	mdata->errors = 0;
	mdata->data_len = 0;
	}
	else if (state == MDRIVER_STARTUP_PREPARE)
	{
	   /* once we are out of startup use callout_io_map */
	   if ((mdata->port_k = callout_io_map(OMAP_UART_SIZE, 0xfffb0000)) == 0)
	   {
	      /* something bad so disable the driver */
	      write_omap(mdata->port + OMAP_UART_IER, 0x00);
	      return (1);
	   }
	}

	/* count the number of times the mini-driver is called */
	count = mdata->ncalls + 1;
	mdata->ncalls = count;
	if (state == MDRIVER_PROCESS)
	{
	   /* called because of an interrupt */
	   count = mdata->intr_calls + 1;
	   mdata->intr_calls = count;

	   iir = read_omap(mdata->port + OMAP_UART_IIR) & 0x07;
	   if (iir == OMAP_II_RX) /* receive interrupt */
	   {
	   do {
		 dptr[mdata->data_len] = read_omap(mdata->port + OMAP_UART_RHR) & 0xff;
		 mdata->data_len = mdata->data_len + 1;
				
		 lsr = read_omap(mdata->port + OMAP_UART_LSR);
		 /* check for errors */
		 if((lsr & (OMAP_LSR_RCV_FIFO | OMAP_LSR_BI|OMAP_LSR_OE|
                    OMAP_LSR_FE|OMAP_LSR_PE)) != 0)
		 {
		    // Read whatever input data happens to be in the buffer to "eat" the
		    // spurious data associated with break, parity error, etc.
		    c = read_omap(mdata->port + OMAP_UART_RHR);
		    c = c;
		 }
		    } while(lsr & OMAP_LSR_RXRDY);
	      }
	}
	else
	{
	   /* poll for data */
	   while (read_omap(mdata->port + OMAP_UART_LSR) & OMAP_LSR_RXRDY)
	   {
	       dptr[mdata->data_len] = read_omap(mdata->port + OMAP_UART_RHR) & 0xff;
	       mdata->data_len = mdata->data_len + 1;
	   }
	}
	
	if (state == MDRIVER_STARTUP_FINI)
	   mdata->port = mdata->port_k;	
	
	return (0);
}

In this example, our handler function stores call information, so a structure is created to allow easier access to the data area. The data area is filled with the received characters.

During the MDRIVER_INIT state, the data area is initialized and the serial hardware is set up. This is called only once. At this time, startup_io_map() is called to map in the hardware registers, and the pointer is stored in the data area.

This pointer is used for hardware access until the handler is called with a state of STARTUP_MDRIVER_PREPARE. At this time, callout_io_map() is called, and the pointer is stored in the data area, however the startup_io_map() pointer should still be used. Once the handler is called with a state of MDRIVER_STARTUP_FINI, the handler starts using the pointer returned by callout_io_map() for all hardware access. This pointer is then used for all further invocations of the minidriver handler. When the handler is called with a state of MDRIVER_INTR_ATTACH, it disables the device interrupt and returns a value of 1 requesting an exit of the handler.

Adding your minidriver to the system

Once you have written a handler function, you need to register it with startup and allocate the required system RAM for your data area. This is accomplished with the following functions:

paddr_t alloc_ram(phys_addr, size, alignment);
int mdriver_add(name, interrupt, handler, data_paddr, data_size);

Since you're allocating memory and passing an interrupt, these functions must be called after RAM is initialized by calling init_raminfo(), and after the interrupt information is added to the system page by calling init_intrinfo(). The main() function of the startup main.c should look like this:

...
paddr_t		mdrvr_addr;
...
init_intrinfo();
   init_qtime();
	
   /* allocate 64k of ram for the minidriver's use */
   mdrvr_addr = alloc_ram(~0L, 64*1024, 1);
   /* code to add a sample minidriver which merely does data collection */	
   mdriver_add("mini-data", 0, mini_data, mdrvr_addr, 64*1024);
   /* code to add a sample minidriver for a serial port */
   /* mdriver_add("mini-serial", 46, mini_serial, mdrvr_addr, 64*1024); */
...

In this example, you have allocated 64 KB of memory and registered the minidriver handler function with the name mini-serial>

Build startup

Our minidriver is complete, and you should now rebuild startup and the boot image. In order to verify that the minidriver is running properly, you may want to add debug information to the handler function or write an application to read the minidriver data area and print the contents.

Testing your minidriver

The OMAP 5912 board has a debug serial port that this minidriver example uses to receive bytes on. In order to test this driver, you should build an OS image that sets up TCP/IP and lets you telnet to the board. Since you'll be using the serial port as a data source, don't run the serial driver and don't put debug printouts or verbosity on procnto in your image. Once you have an image, you should burn it onto the onboard flash where you can boot from it.

Connect a NULL-modem cable between your OMAP board and a host machine. The minidriver is running at 14400 baud, so make sure the host machine's serial port is also configured in this manner. On the host machine, begin sending characters to the serial port. Sample code should look like this:

...
uint8_t test_data[20] = {"012345678987654321"};
...

if ((fd = open("/dev/ser1", O_RDWR)) == -1)
{
   fprintf(stderr, "Unable to open device: %s (errno=%d\n", device, errno);
   return (-1);
}

for (;;)
{
write(fd, test_data + index, 1);
   index++;
   if (index == 19)
      index = 0;
}

This code sends a pattern of characters to the serial port so that the minidriver can capture data.

Now boot the OMAP 5912 board and telnet to it. Once connected, you can run the sample full driver, mdrvr-seromap, which does the following:

Since the mdrvr-seromap application is a sample serial driver, you don't need to run the devc-seromap driver.

Sample timings

These timings are based on a sample boot image with the following properties:

mdriver_max Number of calls Average calling interval
1 KB 782 93 microseconds
4 KB 229 352 microseconds
16 KB 91 1.38 ms
Boot time First invocation time
170 ms 1 ms

Note: Boot time identifies the time from startup to the start of the first application process. The first invocation time is the time from IPL to the first invocation of the minidriver (MDRIVER_INIT).