//------------------------------------------------------------------- // ohcipert.cpp // // Here we learn what steps are minimally required to transfer // an 80-character message to the Pertelian x2040 LCD display, // by direct hardware-level programming of an OHCI controller. // // Two USB control-transfers are issued: one verifies that the // Pertelian x2040 device is present at the address specified, // the other configures the device for subsequent transfers of // commands and character-codes to its 'OUT' endpoint. Delays // between transfers of commands and/or characters are needed, // as documented in the Pertelian's online programmer's guide. // // A user has the option here of requesting verbose output and // piping it to a file for later examination. (It illustrates // the sequence in which certain OHCI register-values and HCCA // memory-arguments get modified as SOF and WDH events occur.) // // to compile: $ g++ ohcipert.cpp -o ohcipert // to execute: $ ./ohcipert <-v> // // NOTE: Requires installing our 'ohci.c' device-driver module. // // programmer: ALLAN CRUSE // date begun: 24 JUN 2010 // completion: 08 JUL 2010 //------------------------------------------------------------------- #include // for printf(), perror() #include // for open() #include // for exit() #include // for lseek(), usleep() #include // for memset() #include // for signal() #include // for mmap() #include // for ioctl() #define OHCI_BASE 0x70000 // suitable address for register-map #define PAGE_SIZE 0x01000 // memory-granularity on x86 systems #define KMEM_BASE 0x71000 // address for a kernel memory-arena #define VENDOR_ID 0x0403 // Foresight Systems, LLC #define DEVICE_ID 0x6001 // Pertelian x2040 LCD Display typedef struct { unsigned short edFA : 7; unsigned short edEN : 4; unsigned short edD : 2; unsigned short edS : 1; unsigned short edK : 1; unsigned short edF : 1; unsigned short edMPS; unsigned int edTailPtr; unsigned int edHeadPtr; unsigned int edNextED; } OHCI_ED; // OpenHCI Endpoint Descriptor typedef struct { unsigned int tdUnused : 18; unsigned int tdR : 1; unsigned int tdDP : 2; unsigned int tdDI : 3; unsigned int tdT : 2; unsigned int tdEC : 2; unsigned int tdCC : 4; unsigned int tdBuffer; unsigned int tdNextTD; unsigned int tdBufEnd; } OHCI_TD; // OpenHCI Transfer Descriptor typedef struct { unsigned char bmReqType; unsigned char bRequest; unsigned short wValue; unsigned short wIndex; unsigned short wLength; } USB_RB; // USB Request Block typedef struct { unsigned char bLength; unsigned char bDescriptorType; unsigned short bcdUSB; unsigned char bDeviceClass; unsigned char bDeviceSubClass; unsigned char bDeviceProtocol; unsigned char bMaxPacketSize0; unsigned short idVendor; unsigned short idProduct; unsigned short bcdDevice; unsigned char iManufacturer; unsigned char iProduct; unsigned char iSerialNumber; unsigned char bNumConfigurations; } USB_DD; // USB Device Descriptor char devname[] = "/dev/ohci1"; const int sizeED = sizeof( OHCI_ED ); const int sizeTD = sizeof( OHCI_TD ); const int sizeRB = sizeof( USB_RB ); const int sizeDD = sizeof( USB_DD ); unsigned long kmem_phys, kmem_size; unsigned int *hcca = (unsigned int *)(KMEM_BASE + 0*PAGE_SIZE); OHCI_ED *ed = (OHCI_ED*)(KMEM_BASE + 1*PAGE_SIZE); OHCI_TD *td = (OHCI_TD*)(KMEM_BASE + 2*PAGE_SIZE); USB_RB *rb = (USB_RB *)(KMEM_BASE + 3*PAGE_SIZE); USB_DD *dd = (USB_DD *)(KMEM_BASE + 4*PAGE_SIZE); unsigned char *bf = (unsigned char*)(KMEM_BASE + 5*PAGE_SIZE); unsigned int *lp = (unsigned int *)OHCI_BASE; unsigned int hcca_phys, ed_phys, td_phys, rb_phys, dd_phys, bf_phys; unsigned int hub = 1, dev = 2, verbose = 0; // default values unsigned int HcControl, HcCommandStatus, HcInterruptStatus; unsigned int HcInterruptEnable, HcInterruptDisable, HcHCCA; unsigned int HcControlHeadED, HcControlCurrentED, HcDoneHead; void my_sigint_handler( int signo ) { exit(1); } void show_OHCI_registers( void ) { printf( "\n OHCI registers: " ); for (int i = 0; i < 24; i++) { if ( (i % 8) == 0 ) printf( "\n 0x%02X: ", i*4 ); printf( "%08X ", lp[ i ] ); } printf( "\n\n" ); } void show_descriptors( void ) { printf( " OHCI Transfer Descriptors: \n" ); for (int i = 0; i < 12; i++) { unsigned int *entry = (unsigned int *)&td[i]; printf( " TD%X ", i ); printf( "<0x%08X>: ", td_phys + i*sizeTD ); for (int j = 0; j < 4; j++) printf( "%08X ", entry[j] ); printf( "\n" ); } printf( " OHCI EndPoint Descriptors: \n" ); for (int i = 0; i < 2; i++) { unsigned int *entry = (unsigned int *)&ed[i]; printf( " ED%X ", i ); printf( "<0x%08X>: ", ed_phys + i*sizeED ); for (int j = 0; j < 4; j++) printf( "%08X ", entry[j] ); printf( "\n" ); } } void my_atexit( void ) { lp[ 0x14 >> 2 ] = 0xFFFFFFFF; // disable OHCI interrupts lp[ 0x30 >> 2 ] = HcDoneHead; lp[ 0x24 >> 2 ] = HcControlCurrentED; lp[ 0x04 >> 2 ] = HcControl; lp[ 0x08 >> 2 ] = HcCommandStatus; lp[ 0x18 >> 2 ] = HcHCCA; lp[ 0x20 >> 2 ] = HcControlHeadED; lp[ 0x0C >> 2 ] = 0xFFFFFFFF; // clear pending interrupts lp[ 0x10 >> 2 ] = HcInterruptEnable; } void await_Control_List_completion( void ) { // spin until the WDH event occurs ('Write Done Head') // optionally showing relevant OHCI controller values int c = 0; // iterations count int s = 0; // interrupt status do { s = lp[ 0x0C >> 2 ]; if ( verbose == 0 ) continue; printf( " #%-3d", ++c ); printf( " FrNm=%04X ", lp[ 0x3C >> 2 ] ); printf( " IntStat=%02X", s ); printf( " HeadED=%05X", lp[ 0x20 >> 2 ] ); printf( " CurrED=%05X", lp[ 0x24 >> 2 ] ); printf( " Done=%05X", lp[ 0x30 >> 2 ] ); printf( " Hcca=%08X ", hcca[ 0x21 ] ); printf( "\n" ); } while ( ( s & (1 << 1)) == 0 ); if ( verbose != 0 ) printf( "\n" ); } int main( int argc, char **argv ) { // allow the user to override our default-values // by supplying optional command-line arguments if ( argc > 1 ) dev = atoi( argv[1] )&0x7F; if ( argc > 2 ) hub = atoi( argv[2] )&0x03; if ( argc > 3 ) verbose = 1; sprintf( devname + 9, "%u", hub ); printf( "\n Using device-file \'%s\' ", devname ); printf( "and function-address %u \n", dev ); // open this device-file for reading and writing int fd = open( devname, O_RDWR ); if ( fd < 0 ) { perror( devname ); exit(1); } // setup the physical addresses for our DMA memory-regions if ( ioctl( fd, 0, &kmem_phys ) < 0 ) { perror( "ioctl" ); exit(1); } hcca_phys = kmem_phys + 0*PAGE_SIZE; ed_phys = kmem_phys + 1*PAGE_SIZE; td_phys = kmem_phys + 2*PAGE_SIZE; rb_phys = kmem_phys + 3*PAGE_SIZE; dd_phys = kmem_phys + 4*PAGE_SIZE; bf_phys = kmem_phys + 5*PAGE_SIZE; // map the controller's registers and our kernel memory arena int size = lseek( fd, 0, SEEK_END ); int prot = PROT_READ | PROT_WRITE; int flag = MAP_FIXED | MAP_SHARED; void *mm = (void*)OHCI_BASE; if ( mmap( mm, size, prot, flag, fd, 0 ) == MAP_FAILED ) { perror( "mmap" ); exit(1); } // initialize our private Host Controller Communication Area memset( hcca+0x00, 0x00, 32*4 ); // null periodic-list memset( hcca+0x20, 0xFF, 128 ); // non-null otherwise // preserve the values of OHCI registers we intend to modify HcControl = lp[ 0x04 >> 2 ]; HcCommandStatus = lp[ 0x08 >> 2 ]; HcInterruptStatus = lp[ 0x0C >> 2 ]; HcInterruptEnable = lp[ 0x10 >> 2 ]; HcInterruptDisable = lp[ 0x14 >> 2 ]; HcHCCA = lp[ 0x18 >> 2 ]; HcControlHeadED = lp[ 0x20 >> 2 ]; HcControlCurrentED = lp[ 0x24 >> 2 ]; HcDoneHead = lp[ 0x30 >> 2 ]; // exit if Host Controller Functional State is not 'OPERATIONAL' if ( ((HcControl >> 6) & 3) != 2 ) { printf( " Hardware state is not 'OPERATIONAL'\n\n", hub ); exit(1); } // otherwise insure registers will get restored upon quitting atexit( my_atexit ); signal( SIGINT, my_sigint_handler ); // setup a 'Get_Descriptor' Request Block rb[0].bmReqType = 0x80; rb[0].bRequest = 0x06; rb[0].wValue = 0x0100; rb[0].wIndex = 0x0000; rb[0].wLength = 0x0012; // setup an Endpoint Descriptor ed[0].edTailPtr = td_phys + 5*sizeTD; ed[0].edHeadPtr = td_phys; ed[0].edNextED = 0x00000000; // end of this ED list for now ed[0].edFA = dev; // device-address ed[0].edEN = 0; // endpoint-number ed[0].edD = 0; // get from TD ed[0].edS = 0; // Full-Speed ed[0].edK = 0; // do not skip ed[0].edF = 0; // Format (non-isochronous) ed[0].edMPS = 8; // Maximum Packet Size // setup a succession of Transfer Descriptors td[0].tdUnused = 0; td[0].tdR = 0; // Exact Buffer Length td[0].tdDP = 0; // Direction PID = 0 (SETUP) td[0].tdDI = 0; // Delay Interrupt td[0].tdT = 2; // Toggle is from here: DATA0 td[0].tdEC = 0; // Error Count td[0].tdCC = 0xF; // Completion Code td[0].tdBuffer = rb_phys + 0; td[0].tdNextTD = td_phys + 1*sizeTD; td[0].tdBufEnd = rb_phys + 7; td[1].tdUnused = 0; td[1].tdR = 0; // Exact Buffer Length td[1].tdDP = 2; // Direction PID = 2 (IN) td[1].tdDI = 0; // Delay Interrupt td[1].tdT = 3; // Toggle is from here: DATA1 td[1].tdEC = 0; // Error Count td[1].tdCC = 0xF; // Completion Code td[1].tdBuffer = dd_phys + 0; td[1].tdNextTD = td_phys + 2*sizeTD; td[1].tdBufEnd = dd_phys + 7; td[2].tdUnused = 0; td[2].tdR = 0; // Exact Buffer Length td[2].tdDP = 2; // Direction PID = 2 (IN) td[2].tdDI = 0; // Delay Interrupt td[2].tdT = 2; // Toggle is from here: DATA0 td[2].tdEC = 0; // Error Count td[2].tdCC = 0xF; // Completion Code td[2].tdBuffer = dd_phys + 8; td[2].tdNextTD = td_phys + 3*sizeTD; td[2].tdBufEnd = dd_phys + 15; td[3].tdUnused = 0; td[3].tdR = 0; // Exact Buffer Length td[3].tdDP = 2; // Direction PID = 2 (IN) td[3].tdDI = 0; // Delay Interrupt td[3].tdT = 3; // Toggle is from here: DATA1 td[3].tdEC = 0; // Error Count td[3].tdCC = 0xF; // Completion Code td[3].tdBuffer = dd_phys + 16; td[3].tdNextTD = td_phys + 4*sizeTD; td[3].tdBufEnd = dd_phys + 17; td[4].tdUnused = 0; td[4].tdR = 0; // Exact Buffer Length td[4].tdDP = 1; // Direction PID = 1 (OUT) td[4].tdDI = 0; // Delay Interrupt td[4].tdT = 3; // Toggle is from here: DATA1 td[4].tdEC = 0; // Error Count td[4].tdCC = 0xF; // Completion Code td[4].tdBuffer = 0x00000000; td[4].tdNextTD = td_phys + 5*sizeTD; //<--- Must not be NULL td[4].tdBufEnd = 0x00000000; // initialize space for another TD memset( &td[5], 0xFF, sizeTD ); // erase any prior contents of the device-descriptor buffer memset( dd, 0xFF, sizeDD ); // switch to our own HCCA and Control-List lp[ 0x14 >> 2 ] = 0xFFFFFFFF; // disable interrupts lp[ 0x18 >> 2 ] = hcca_phys; // HcHCCA lp[ 0x20 >> 2 ] = ed_phys; // HcControlHeadED lp[ 0x04 >> 2 ] |= (1 << 4); // HcCommand.CLE // activate our Control List lp[ 0x0C >> 2 ] = 0xFFFFFFFF; // clear pending interrupts lp[ 0x08 >> 2 ] |= (1 << 1); // set HcCommandStatus.CLF await_Control_List_completion(); // check that we got the Pertelian x2040 device-descriptor if ( ( dd[0].bDescriptorType == 1 ) &&( dd[0].idVendor == VENDOR_ID ) &&( dd[0].idProduct == DEVICE_ID ) ) ed[0].edK = 1; else { printf( "\n The Pertelian x2040 LCD " ); printf( "peripheral-device was not detected. \n\n" ); exit(1); } // setup a 'Set_Configuration' Request Block rb[1].bmReqType = 0x00; rb[1].bRequest = 0x09; rb[1].wValue = 0x0001; rb[1].wIndex = 0x0000; rb[1].wLength = 0x0000; // setup another succession of Transfer Descriptors td[5].tdUnused = 0; td[5].tdR = 0; // Exact Buffer Length td[5].tdDP = 0; // Direction PID = 0 (SETUP) td[5].tdDI = 0; // Delay Interrupt td[5].tdT = 2; // Toggle is from here: DATA0 td[5].tdEC = 0; // Error Count td[5].tdCC = 0xF; // Completion Code td[5].tdBuffer = rb_phys + 1*sizeRB; td[5].tdNextTD = td_phys + 6*sizeTD; td[5].tdBufEnd = rb_phys + 1*sizeRB + 7; td[6].tdUnused = 0; td[6].tdR = 0; // Exact Buffer Length td[6].tdDP = 2; // Direction PID = 2 (IN) td[6].tdDI = 0; // Delay Interrupt td[6].tdT = 3; // Toggle is from here: DATA1 td[6].tdEC = 0; // Error Count td[6].tdCC = 0xF; // Completion Code td[6].tdBuffer = 0x00000000; td[6].tdNextTD = td_phys + 7*sizeTD; //<-- Must not be null td[6].tdBufEnd = 0x00000000; // initialize space for another TD memset( &td[7], 0xFF, sizeTD ); // modify our EndPoint Descriptor ed[0].edTailPtr = td_phys + 7*sizeTD; ed[0].edK = 0; // clear ED's 'sKip' bit // reactivate our Control List lp[ 0x0C >> 2 ] = 0xFFFFFFFF; // clear pending interrupts lp[ 0x08 >> 2 ] |= (1 << 1); // set HcCommandStatus.CLF await_Control_List_completion(); //======================================================== //===== Execute the Pertelian x2040 initialization ===== //======================================================== // setup the Pertelian command-packet sequence unsigned char pertinit[10] = { 0xFE, 0x38, // Function-Set: 8-bit data, 2 lines, 5x7 dots 0xFE, 0x06, // Entry-mode Set: inc cursor, no auto-shift 0xFE, 0x10, // Cursor/Display Shift: cursor move 0xFE, 0x0C, // Display on; Cursor Off; Blink off 0xFE, 0x01 // Clear Display }; memcpy( bf, pertinit, sizeof( pertinit) ); // copy to DMA memory // setup an Endpoint Descriptor for Endpoint #2 (OUT) memset( &ed[2], 0, sizeED ); ed[1].edTailPtr = td_phys + 9*sizeTD; ed[1].edHeadPtr = td_phys + 8*sizeTD; ed[1].edNextED = 0x00000000; // end of our list of EDs ed[1].edFA = dev; // device-address ed[1].edEN = 2; // endpoint-number ed[1].edD = 1; // direction is OUT ed[1].edS = 0; // Full-Speed ed[1].edK = 1; // set the 'sKip' bit ed[1].edF = 0; // Format (non-isochronous) ed[1].edMPS = 64; // Maximum Packet Size // append this new EndPoint Descriptor ed[0].edNextED = ed_phys + 1*sizeED; // initialize the space for TD #9 memset( &td[9], 0xFF, sizeTD ); // loop to output the succession of command-packets for (int i = 0; i < 5; i++) { // setup the 'OUT' Transfer Descriptor td[8].tdUnused = 0; td[8].tdR = 0; // Exact Buffer Length td[8].tdDP = 1; // Direction PID = 1 (OUT) td[8].tdDI = 0; // Delay Interrupt td[8].tdT = 0; // Data-Toggle is from the ED td[8].tdEC = 0; // Error Count td[8].tdCC = 0xF; // Completion Code td[8].tdBuffer = bf_phys + i*2; td[8].tdNextTD = td_phys + 9*sizeTD; td[8].tdBufEnd = bf_phys + i*2 + 1; // refresh the ED's 'HeadPtr' field ed[1].edHeadPtr &= 0x2; // retain Toggle-Carry bit ed[1].edHeadPtr |= td_phys + 8*sizeTD; // clear the ED's sKip bit ed[1].edK = 0; // activate our Control List lp[ 0x0C >> 2 ] = 0xFFFFFFFF; lp[ 0x08 >> 2 ] |= (1 << 1); await_Control_List_completion(); // set the ED's sKip bit ed[1].edK = 1; // delay as specified in Pertelian documentation usleep( 1640 ); // 1.6 milliseconds } //======================================================== //===== Output the Pertelian x2040 welcome message ===== //======================================================== // setup the message-text char welcome[ 80 ]; sprintf( welcome + 0, "Welcome to Pertelian" ); sprintf( welcome + 20, " " ); sprintf( welcome + 40, " " ); sprintf( welcome + 60, "E0I0 " ); memcpy( bf+16, welcome, 80 ); // initialize space for TD #11 memset( &td[11], 0xFF, sizeTD ); // loop to output the succession of ASCII character-codes for (int i = 0; i < 80; i++) { // setup the Transfer Descriptor td[10].tdUnused = 0; td[10].tdR = 0; // Exact Buffer Length td[10].tdDP = 1; // Direction PID = 1 (OUT) td[10].tdDI = 0; // Delay Interrupt td[10].tdT = 0; // Toggle is from the ED td[10].tdEC = 0; // Error Count td[10].tdCC = 0xF; // Completion Code td[10].tdBuffer = bf_phys + 16 + i; td[10].tdNextTD = td_phys + 11*sizeTD; td[10].tdBufEnd = bf_phys + 16 + i; // refresh the ED's HeadPtr field ed[1].edTailPtr = td_phys + 11*sizeTD; ed[1].edHeadPtr &= 0x3; // retain Toggle-Carry ed[1].edHeadPtr |= td_phys + 10*sizeTD; // clear the ED's sKip bit ed[1].edK = 0; // activate our Control List lp[ 0x0C >> 2 ] = 0xFFFFFFFF; lp[ 0x08 >> 2 ] |= (1 << 1); await_Control_List_completion(); // set the ED's sKip bit ed[1].edK = 1; // delay as specified in Pertelian documentation usleep( 40 ); // 40 microseconds } show_OHCI_registers(); show_descriptors(); }