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.