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.