Path: blob/master/firmware/keyspan_pda/keyspan_pda.S
10821 views
/* $Id: loop.s,v 1.23 2000/03/20 09:49:06 warner Exp $1*2* Firmware for the Keyspan PDA Serial Adapter, a USB serial port based on3* the EzUSB microcontroller.4*5* (C) Copyright 2000 Brian Warner <[email protected]>6*7* This program is free software; you can redistribute it and/or modify8* it under the terms of the GNU General Public License as published by9* the Free Software Foundation; either version 2 of the License, or10* (at your option) any later version.11*12* "Keyspan PDA Serial Adapter" is probably a copyright of Keyspan, the13* company.14*15* This serial adapter is basically an EzUSB chip and an RS-232 line driver16* in a little widget that has a DB-9 on one end and a USB plug on the other.17* It uses the EzUSB's internal UART0 (using the pins from Port C) and timer218* as a baud-rate generator. The wiring is:19* PC0/RxD0 <- rxd (DB9 pin 2) PC4 <- dsr pin 620* PC1/TxD0 -> txd pin 3 PC5 <- ri pin 921* PC2 -> rts pin 7 PC6 <- dcd pin 122* PC3 <- cts pin 8 PC7 -> dtr pin 423* PB1 -> line driver standby24*25* The EzUSB register constants below come from their excellent documentation26* and sample code (which used to be available at www.anchorchips.com, but27* that has now been absorbed into Cypress' site and the CD-ROM contents28* don't appear to be available online anymore). If we get multiple29* EzUSB-based drivers into the kernel, it might be useful to pull them out30* into a separate .h file.31*32* THEORY OF OPERATION:33*34* There are two 256-byte ring buffers, one for tx, one for rx.35*36* EP2out is pure tx data. When it appears, the data is copied into the tx37* ring and serial transmission is started if it wasn't already running. The38* "tx buffer empty" interrupt may kick off another character if the ring39* still has data. If the host is tx-blocked because the ring filled up,40* it will request a "tx unthrottle" interrupt. If sending a serial character41* empties the ring below the desired threshold, we set a bit that will send42* up the tx unthrottle message as soon as the rx buffer becomes free.43*44* EP2in (interrupt) is used to send both rx chars and rx status messages45* (only "tx unthrottle" at this time) back up to the host. The first byte46* of the rx message indicates data (0) or status msg (1). Status messages47* are sent before any data.48*49* Incoming serial characters are put into the rx ring by the serial50* interrupt, and the EP2in buffer sent if it wasn't already in transit.51* When the EP2in buffer returns, the interrupt prompts us to send more52* rx chars (or status messages) if they are pending.53*54* Device control happens through "vendor specific" control messages on EP0.55* All messages are destined for the "Interface" (with the index always 0,56* so that if their two-port device might someday use similar firmware, we57* can use index=1 to refer to the second port). The messages defined are:58*59* bRequest = 0 : set baud/bits/parity60* 1 : unused61* 2 : reserved for setting HW flow control (CTSRTS)62* 3 : get/set "modem info" (pin states: DTR, RTS, DCD, RI, etc)63* 4 : set break (on/off)64* 5 : reserved for requesting interrupts on pin state change65* 6 : query buffer room or chars in tx buffer66* 7 : request tx unthrottle interrupt67*68* The host-side driver is set to recognize the device ID values stashed in69* serial EEPROM (0x06cd, 0x0103), program this firmware into place, then70* start it running. This firmware will use EzUSB's "renumeration" trick by71* simulating a bus disconnect, then reconnect with a different device ID72* (encoded in the desc_device descriptor below). The host driver then73* recognizes the new device ID and glues it to the real serial driver code.74*75* USEFUL DOCS:76* EzUSB Technical Reference Manual: <http://www.cypress.com/>77* 8051 manuals: everywhere, but try www.dalsemi.com because the EzUSB is78* basically the Dallas enhanced 8051 code. Remember that the EzUSB IO ports79* use totally different registers!80* USB 1.1 spec: www.usb.org81*82* HOW TO BUILD:83* gcc -x assembler-with-cpp -P -E -o keyspan_pda.asm keyspan_pda.s84* as31 -l keyspan_pda.asm85* mv keyspan_pda.obj keyspan_pda.hex86* perl ezusb_convert.pl keyspan_pda < keyspan_pda.hex > keyspan_pda_fw.h87* Get as31 from <http://www.pjrc.com/tech/8051/index.html>, and hack on it88* a bit to make it build.89*90* THANKS:91* Greg Kroah-Hartman, for coordinating the whole usb-serial thing.92* AnchorChips, for making such an incredibly useful little microcontroller.93* KeySpan, for making a handy, cheap ($40) widget that was so easy to take94* apart and trace with an ohmmeter.95*96* TODO:97* lots. grep for TODO. Interrupt safety needs stress-testing. Better flow98* control. Interrupting host upon change in DCD, etc, counting transitions.99* Need to find a safe device id to use (the one used by the Keyspan firmware100* under Windows would be ideal.. can anyone figure out what it is?). Parity.101* More baud rates. Oh, and the string-descriptor-length silicon bug102* workaround should be implemented, but I'm lazy, and the consequence is103* that the device name strings that show up in your kernel log will have104* lots of trailing binary garbage in them (appears as ????). Device strings105* should be made more accurate.106*107* Questions, bugs, patches to Brian.108*109* -Brian Warner <[email protected]>110*111*/112113#define HIGH(x) (((x) & 0xff00) / 256)114#define LOW(x) ((x) & 0xff)115116#define dpl1 0x84117#define dph1 0x85118#define dps 0x86119120;;; our bit assignments121#define TX_RUNNING 0122#define DO_TX_UNTHROTTLE 1123124;; stack from 0x60 to 0x7f: should really set SP to 0x60-1, not 0x60125#define STACK #0x60-1126127#define EXIF 0x91128#define EIE 0xe8129.flag EUSB, EIE.0130.flag ES0, IE.4131132#define EP0CS #0x7fb4133#define EP0STALLbit #0x01134#define IN0BUF #0x7f00135#define IN0BC #0x7fb5136#define OUT0BUF #0x7ec0137#define OUT0BC #0x7fc5138#define IN2BUF #0x7e00139#define IN2BC #0x7fb9140#define IN2CS #0x7fb8141#define OUT2BC #0x7fc9142#define OUT2CS #0x7fc8143#define OUT2BUF #0x7dc0144#define IN4BUF #0x7d00145#define IN4BC #0x7fbd146#define IN4CS #0x7fbc147#define OEB #0x7f9d148#define OUTB #0x7f97149#define OEC #0x7f9e150#define OUTC #0x7f98151#define PINSC #0x7f9b152#define PORTCCFG #0x7f95153#define IN07IRQ #0x7fa9154#define OUT07IRQ #0x7faa155#define IN07IEN #0x7fac156#define OUT07IEN #0x7fad157#define USBIRQ #0x7fab158#define USBIEN #0x7fae159#define USBBAV #0x7faf160#define USBCS #0x7fd6161#define SUDPTRH #0x7fd4162#define SUDPTRL #0x7fd5163#define SETUPDAT #0x7fe8164165;; usb interrupt : enable is EIE.0 (0xe8), flag is EXIF.4 (0x91)166167.org 0168ljmp start169;; interrupt vectors170.org 23H171ljmp serial_int172.byte 0173174.org 43H175ljmp USB_Jump_Table176.byte 0 ; filled in by the USB core177178;;; local variables. These are not initialized properly: do it by hand.179.org 30H180rx_ring_in: .byte 0181rx_ring_out: .byte 0182tx_ring_in: .byte 0183tx_ring_out: .byte 0184tx_unthrottle_threshold: .byte 0185186.org 0x100H ; wants to be on a page boundary187USB_Jump_Table:188ljmp ISR_Sudav ; Setup Data Available189.byte 0190ljmp 0 ; Start of Frame191.byte 0192ljmp 0 ; Setup Data Loading193.byte 0194ljmp 0 ; Global Suspend195.byte 0196ljmp 0 ; USB Reset197.byte 0198ljmp 0 ; Reserved199.byte 0200ljmp 0 ; End Point 0 In201.byte 0202ljmp 0 ; End Point 0 Out203.byte 0204ljmp 0 ; End Point 1 In205.byte 0206ljmp 0 ; End Point 1 Out207.byte 0208ljmp ISR_Ep2in209.byte 0210ljmp ISR_Ep2out211.byte 0212213214.org 0x200215216start: mov SP,STACK-1 ; set stack217;; clear local variables218clr a219mov tx_ring_in, a220mov tx_ring_out, a221mov rx_ring_in, a222mov rx_ring_out, a223mov tx_unthrottle_threshold, a224clr TX_RUNNING225clr DO_TX_UNTHROTTLE226227;; clear fifo with "fe"228mov r1, 0229mov a, #0xfe230mov dptr, #tx_ring231clear_tx_ring_loop:232movx @dptr, a233inc dptr234djnz r1, clear_tx_ring_loop235236mov a, #0xfd237mov dptr, #rx_ring238clear_rx_ring_loop:239movx @dptr, a240inc dptr241djnz r1, clear_rx_ring_loop242243;;; turn on the RS-232 driver chip (bring the STANDBY pin low)244;; set OEB.1245mov a, #02H246mov dptr,OEB247movx @dptr,a248;; clear PB1249mov a, #00H250mov dptr,OUTB251movx @dptr,a252;; set OEC.[127]253mov a, #0x86254mov dptr,OEC255movx @dptr,a256;; set PORTCCFG.[01] to route TxD0,RxD0 to serial port257mov dptr, PORTCCFG258mov a, #0x03259movx @dptr, a260261;; set up interrupts, autovectoring262mov dptr, USBBAV263movx a,@dptr264setb acc.0 ; AVEN bit to 0265movx @dptr, a266267mov a,#0x01 ; enable SUDAV: setup data available (for ep0)268mov dptr, USBIRQ269movx @dptr, a ; clear SUDAVI270mov dptr, USBIEN271movx @dptr, a272273mov dptr, IN07IEN274mov a,#0x04 ; enable IN2 int275movx @dptr, a276277mov dptr, OUT07IEN278mov a,#0x04 ; enable OUT2 int279movx @dptr, a280mov dptr, OUT2BC281movx @dptr, a ; arm OUT2282283mov a, #0x84 ; turn on RTS, DTR284mov dptr,OUTC285movx @dptr, a286;; setup the serial port. 9600 8N1.287mov a,#01010011 ; mode 1, enable rx, clear int288mov SCON, a289;; using timer2, in 16-bit baud-rate-generator mode290;; (xtal 12MHz, internal fosc 24MHz)291;; RCAP2H,RCAP2L = 65536 - fosc/(32*baud)292;; 57600: 0xFFF2.F, say 0xFFF3293;; 9600: 0xFFB1.E, say 0xFFB2294;; 300: 0xF63C295#define BAUD 9600296#define BAUD_TIMEOUT(rate) (65536 - (24 * 1000 * 1000) / (32 * rate))297#define BAUD_HIGH(rate) HIGH(BAUD_TIMEOUT(rate))298#define BAUD_LOW(rate) LOW(BAUD_TIMEOUT(rate))299300mov T2CON, #030h ; rclk=1,tclk=1,cp=0,tr2=0(enable later)301mov r3, #5302acall set_baud303setb TR2304mov SCON, #050h305306#if 0307mov r1, #0x40308mov a, #0x41309send:310mov SBUF, a311inc a312anl a, #0x3F313orl a, #0x40314; xrl a, #0x02315wait1:316jnb TI, wait1317clr TI318djnz r1, send319;done: sjmp done320321#endif322323setb EUSB324setb EA325setb ES0326;acall dump_stat327328;; hey, what say we RENUMERATE! (TRM p.62)329mov a, #0330mov dps, a331mov dptr, USBCS332mov a, #0x02 ; DISCON=0, DISCOE=0, RENUM=1333movx @dptr, a334;; now presence pin is floating, simulating disconnect. wait 0.5s335mov r1, #46336renum_wait1:337mov r2, #0338renum_wait2:339mov r3, #0340renum_wait3:341djnz r3, renum_wait3342djnz r2, renum_wait2343djnz r1, renum_wait1 ; wait about n*(256^2) 6MHz clocks344mov a, #0x06 ; DISCON=0, DISCOE=1, RENUM=1345movx @dptr, a346;; we are back online. the host device will now re-query us347348349main: sjmp main350351352353ISR_Sudav:354push dps355push dpl356push dph357push dpl1358push dph1359push acc360mov a,EXIF361clr acc.4362mov EXIF,a ; clear INT2 first363mov dptr, USBIRQ ; clear USB int364mov a,#01h365movx @dptr,a366367;; get request type368mov dptr, SETUPDAT369movx a, @dptr370mov r1, a ; r1 = bmRequestType371inc dptr372movx a, @dptr373mov r2, a ; r2 = bRequest374inc dptr375movx a, @dptr376mov r3, a ; r3 = wValueL377inc dptr378movx a, @dptr379mov r4, a ; r4 = wValueH380381;; main switch on bmRequest.type: standard or vendor382mov a, r1383anl a, #0x60384cjne a, #0x00, setup_bmreq_type_not_standard385;; standard request: now main switch is on bRequest386ljmp setup_bmreq_is_standard387388setup_bmreq_type_not_standard:389;; a still has bmreq&0x60390cjne a, #0x40, setup_bmreq_type_not_vendor391;; Anchor reserves bRequest 0xa0-0xaf, we use small ones392;; switch on bRequest. bmRequest will always be 0x41 or 0xc1393cjne r2, #0x00, setup_ctrl_not_00394;; 00 is set baud, wValue[0] has baud rate index395lcall set_baud ; index in r3, carry set if error396jc setup_bmreq_type_not_standard__do_stall397ljmp setup_done_ack398setup_bmreq_type_not_standard__do_stall:399ljmp setup_stall400setup_ctrl_not_00:401cjne r2, #0x01, setup_ctrl_not_01402;; 01 is reserved for set bits (parity). TODO403ljmp setup_stall404setup_ctrl_not_01:405cjne r2, #0x02, setup_ctrl_not_02406;; 02 is set HW flow control. TODO407ljmp setup_stall408setup_ctrl_not_02:409cjne r2, #0x03, setup_ctrl_not_03410;; 03 is control pins (RTS, DTR).411ljmp control_pins ; will jump to setup_done_ack,412; or setup_return_one_byte413setup_ctrl_not_03:414cjne r2, #0x04, setup_ctrl_not_04415;; 04 is send break (really "turn break on/off"). TODO416cjne r3, #0x00, setup_ctrl_do_break_on417;; do break off: restore PORTCCFG.1 to reconnect TxD0 to serial port418mov dptr, PORTCCFG419movx a, @dptr420orl a, #0x02421movx @dptr, a422ljmp setup_done_ack423setup_ctrl_do_break_on:424;; do break on: clear PORTCCFG.0, set TxD high(?) (b1 low)425mov dptr, OUTC426movx a, @dptr427anl a, #0xfd ; ~0x02428movx @dptr, a429mov dptr, PORTCCFG430movx a, @dptr431anl a, #0xfd ; ~0x02432movx @dptr, a433ljmp setup_done_ack434setup_ctrl_not_04:435cjne r2, #0x05, setup_ctrl_not_05436;; 05 is set desired interrupt bitmap. TODO437ljmp setup_stall438setup_ctrl_not_05:439cjne r2, #0x06, setup_ctrl_not_06440;; 06 is query room441cjne r3, #0x00, setup_ctrl_06_not_00442;; 06, wValue[0]=0 is query write_room443mov a, tx_ring_out444setb c445subb a, tx_ring_in ; out-1-in = 255 - (in-out)446ljmp setup_return_one_byte447setup_ctrl_06_not_00:448cjne r3, #0x01, setup_ctrl_06_not_01449;; 06, wValue[0]=1 is query chars_in_buffer450mov a, tx_ring_in451clr c452subb a, tx_ring_out ; in-out453ljmp setup_return_one_byte454setup_ctrl_06_not_01:455ljmp setup_stall456setup_ctrl_not_06:457cjne r2, #0x07, setup_ctrl_not_07458;; 07 is request tx unthrottle interrupt459mov tx_unthrottle_threshold, r3; wValue[0] is threshold value460ljmp setup_done_ack461setup_ctrl_not_07:462ljmp setup_stall463464setup_bmreq_type_not_vendor:465ljmp setup_stall466467468setup_bmreq_is_standard:469cjne r2, #0x00, setup_breq_not_00470;; 00: Get_Status (sub-switch on bmRequestType: device, ep, int)471cjne r1, #0x80, setup_Get_Status_not_device472;; Get_Status(device)473;; are we self-powered? no. can we do remote wakeup? no474;; so return two zero bytes. This is reusable475setup_return_two_zero_bytes:476mov dptr, IN0BUF477clr a478movx @dptr, a479inc dptr480movx @dptr, a481mov dptr, IN0BC482mov a, #2483movx @dptr, a484ljmp setup_done_ack485setup_Get_Status_not_device:486cjne r1, #0x82, setup_Get_Status_not_endpoint487;; Get_Status(endpoint)488;; must get stall bit for ep[wIndexL], return two bytes, bit in lsb 0489;; for now: cheat. TODO490sjmp setup_return_two_zero_bytes491setup_Get_Status_not_endpoint:492cjne r1, #0x81, setup_Get_Status_not_interface493;; Get_Status(interface): return two zeros494sjmp setup_return_two_zero_bytes495setup_Get_Status_not_interface:496ljmp setup_stall497498setup_breq_not_00:499cjne r2, #0x01, setup_breq_not_01500;; 01: Clear_Feature (sub-switch on wValueL: stall, remote wakeup)501cjne r3, #0x00, setup_Clear_Feature_not_stall502;; Clear_Feature(stall). should clear a stall bit. TODO503ljmp setup_stall504setup_Clear_Feature_not_stall:505cjne r3, #0x01, setup_Clear_Feature_not_rwake506;; Clear_Feature(remote wakeup). ignored.507ljmp setup_done_ack508setup_Clear_Feature_not_rwake:509ljmp setup_stall510511setup_breq_not_01:512cjne r2, #0x03, setup_breq_not_03513;; 03: Set_Feature (sub-switch on wValueL: stall, remote wakeup)514cjne r3, #0x00, setup_Set_Feature_not_stall515;; Set_Feature(stall). Should set a stall bit. TODO516ljmp setup_stall517setup_Set_Feature_not_stall:518cjne r3, #0x01, setup_Set_Feature_not_rwake519;; Set_Feature(remote wakeup). ignored.520ljmp setup_done_ack521setup_Set_Feature_not_rwake:522ljmp setup_stall523524setup_breq_not_03:525cjne r2, #0x06, setup_breq_not_06526;; 06: Get_Descriptor (s-switch on wValueH: dev, config[n], string[n])527cjne r4, #0x01, setup_Get_Descriptor_not_device528;; Get_Descriptor(device)529mov dptr, SUDPTRH530mov a, #HIGH(desc_device)531movx @dptr, a532mov dptr, SUDPTRL533mov a, #LOW(desc_device)534movx @dptr, a535ljmp setup_done_ack536setup_Get_Descriptor_not_device:537cjne r4, #0x02, setup_Get_Descriptor_not_config538;; Get_Descriptor(config[n])539cjne r3, #0x00, setup_stall; only handle n==0540;; Get_Descriptor(config[0])541mov dptr, SUDPTRH542mov a, #HIGH(desc_config1)543movx @dptr, a544mov dptr, SUDPTRL545mov a, #LOW(desc_config1)546movx @dptr, a547ljmp setup_done_ack548setup_Get_Descriptor_not_config:549cjne r4, #0x03, setup_Get_Descriptor_not_string550;; Get_Descriptor(string[wValueL])551;; if (wValueL >= maxstrings) stall552mov a, #((desc_strings_end-desc_strings)/2)553clr c554subb a,r3 ; a=4, r3 = 0..3 . if a<=0 then stall555jc setup_stall556jz setup_stall557mov a, r3558add a, r3 ; a = 2*wValueL559mov dptr, #desc_strings560add a, dpl561mov dpl, a562mov a, #0563addc a, dph564mov dph, a ; dph = desc_strings[a]. big endian! (handy)565;; it looks like my adapter uses a revision of the EZUSB that566;; contains "rev D errata number 8", as hinted in the EzUSB example567;; code. I cannot find an actual errata description on the Cypress568;; web site, but from the example code it looks like this bug causes569;; the length of string descriptors to be read incorrectly, possibly570;; sending back more characters than the descriptor has. The workaround571;; is to manually send out all of the data. The consequence of not572;; using the workaround is that the strings gathered by the kernel573;; driver are too long and are filled with trailing garbage (including574;; leftover strings). Writing this out by hand is a nuisance, so for575;; now I will just live with the bug.576movx a, @dptr577mov r1, a578inc dptr579movx a, @dptr580mov r2, a581mov dptr, SUDPTRH582mov a, r1583movx @dptr, a584mov dptr, SUDPTRL585mov a, r2586movx @dptr, a587;; done588ljmp setup_done_ack589590setup_Get_Descriptor_not_string:591ljmp setup_stall592593setup_breq_not_06:594cjne r2, #0x08, setup_breq_not_08595;; Get_Configuration. always 1. return one byte.596;; this is reusable597mov a, #1598setup_return_one_byte:599mov dptr, IN0BUF600movx @dptr, a601mov a, #1602mov dptr, IN0BC603movx @dptr, a604ljmp setup_done_ack605setup_breq_not_08:606cjne r2, #0x09, setup_breq_not_09607;; 09: Set_Configuration. ignored.608ljmp setup_done_ack609setup_breq_not_09:610cjne r2, #0x0a, setup_breq_not_0a611;; 0a: Get_Interface. get the current altsetting for int[wIndexL]612;; since we only have one interface, ignore wIndexL, return a 0613mov a, #0614ljmp setup_return_one_byte615setup_breq_not_0a:616cjne r2, #0x0b, setup_breq_not_0b617;; 0b: Set_Interface. set altsetting for interface[wIndexL]. ignored618ljmp setup_done_ack619setup_breq_not_0b:620ljmp setup_stall621622623setup_done_ack:624;; now clear HSNAK625mov dptr, EP0CS626mov a, #0x02627movx @dptr, a628sjmp setup_done629setup_stall:630;; unhandled. STALL631;EP0CS |= bmEPSTALL632mov dptr, EP0CS633movx a, @dptr634orl a, EP0STALLbit635movx @dptr, a636sjmp setup_done637638setup_done:639pop acc640pop dph1641pop dpl1642pop dph643pop dpl644pop dps645reti646647;;; ==============================================================648649set_baud: ; baud index in r3650;; verify a < 10651mov a, r3652jb ACC.7, set_baud__badbaud653clr c654subb a, #10655jnc set_baud__badbaud656mov a, r3657rl a ; a = index*2658add a, #LOW(baud_table)659mov dpl, a660mov a, #HIGH(baud_table)661addc a, #0662mov dph, a663;; TODO: shut down xmit/receive664;; TODO: wait for current xmit char to leave665;; TODO: shut down timer to avoid partial-char glitch666movx a,@dptr ; BAUD_HIGH667mov RCAP2H, a668mov TH2, a669inc dptr670movx a,@dptr ; BAUD_LOW671mov RCAP2L, a672mov TL2, a673;; TODO: restart xmit/receive674;; TODO: reenable interrupts, resume tx if pending675clr c ; c=0: success676ret677set_baud__badbaud:678setb c ; c=1: failure679ret680681;;; ==================================================682control_pins:683cjne r1, #0x41, control_pins_in684control_pins_out:685mov a, r3 ; wValue[0] holds new bits: b7 is new DTR, b2 is new RTS686xrl a, #0xff ; 1 means active, 0V, +12V ?687anl a, #0x84688mov r3, a689mov dptr, OUTC690movx a, @dptr ; only change bits 7 and 2691anl a, #0x7b ; ~0x84692orl a, r3693movx @dptr, a ; other pins are inputs, bits ignored694ljmp setup_done_ack695control_pins_in:696mov dptr, PINSC697movx a, @dptr698xrl a, #0xff699ljmp setup_return_one_byte700701;;; ========================================702703ISR_Ep2in:704push dps705push dpl706push dph707push dpl1708push dph1709push acc710mov a,EXIF711clr acc.4712mov EXIF,a ; clear INT2 first713mov dptr, IN07IRQ ; clear USB int714mov a,#04h715movx @dptr,a716717;; do stuff718lcall start_in719720pop acc721pop dph1722pop dpl1723pop dph724pop dpl725pop dps726reti727728ISR_Ep2out:729push dps730push dpl731push dph732push dpl1733push dph1734push acc735mov a,EXIF736clr acc.4737mov EXIF,a ; clear INT2 first738mov dptr, OUT07IRQ ; clear USB int739mov a,#04h740movx @dptr,a741742;; do stuff743744;; copy data into buffer. for now, assume we will have enough space745mov dptr, OUT2BC ; get byte count746movx a,@dptr747mov r1, a748clr a749mov dps, a750mov dptr, OUT2BUF ; load DPTR0 with source751mov dph1, #HIGH(tx_ring) ; load DPTR1 with target752mov dpl1, tx_ring_in753OUT_loop:754movx a,@dptr ; read755inc dps ; switch to DPTR1: target756inc dpl1 ; target = tx_ring_in+1757movx @dptr,a ; store758mov a,dpl1759cjne a, tx_ring_out, OUT_no_overflow760sjmp OUT_overflow761OUT_no_overflow:762inc tx_ring_in ; tx_ring_in++763inc dps ; switch to DPTR0: source764inc dptr765djnz r1, OUT_loop766sjmp OUT_done767OUT_overflow:768;; signal overflow769;; fall through770OUT_done:771;; ack772mov dptr,OUT2BC773movx @dptr,a774775;; start tx776acall maybe_start_tx777;acall dump_stat778779pop acc780pop dph1781pop dpl1782pop dph783pop dpl784pop dps785reti786787dump_stat:788;; fill in EP4in with a debugging message:789;; tx_ring_in, tx_ring_out, rx_ring_in, rx_ring_out790;; tx_active791;; tx_ring[0..15]792;; 0xfc793;; rx_ring[0..15]794clr a795mov dps, a796797mov dptr, IN4CS798movx a, @dptr799jb acc.1, dump_stat__done; busy: cannot dump, old one still pending800mov dptr, IN4BUF801802mov a, tx_ring_in803movx @dptr, a804inc dptr805mov a, tx_ring_out806movx @dptr, a807inc dptr808809mov a, rx_ring_in810movx @dptr, a811inc dptr812mov a, rx_ring_out813movx @dptr, a814inc dptr815816clr a817jnb TX_RUNNING, dump_stat__no_tx_running818inc a819dump_stat__no_tx_running:820movx @dptr, a821inc dptr822;; tx_ring[0..15]823inc dps824mov dptr, #tx_ring ; DPTR1: source825mov r1, #16826dump_stat__tx_ring_loop:827movx a, @dptr828inc dptr829inc dps830movx @dptr, a831inc dptr832inc dps833djnz r1, dump_stat__tx_ring_loop834inc dps835836mov a, #0xfc837movx @dptr, a838inc dptr839840;; rx_ring[0..15]841inc dps842mov dptr, #rx_ring ; DPTR1: source843mov r1, #16844dump_stat__rx_ring_loop:845movx a, @dptr846inc dptr847inc dps848movx @dptr, a849inc dptr850inc dps851djnz r1, dump_stat__rx_ring_loop852853;; now send it854clr a855mov dps, a856mov dptr, IN4BC857mov a, #38858movx @dptr, a859dump_stat__done:860ret861862;;; ============================================================863864maybe_start_tx:865;; make sure the tx process is running.866jb TX_RUNNING, start_tx_done867start_tx:868;; is there work to be done?869mov a, tx_ring_in870cjne a,tx_ring_out, start_tx__work871ret ; no work872start_tx__work:873;; tx was not running. send the first character, setup the TI int874inc tx_ring_out ; [++tx_ring_out]875mov dph, #HIGH(tx_ring)876mov dpl, tx_ring_out877movx a, @dptr878mov sbuf, a879setb TX_RUNNING880start_tx_done:881;; can we unthrottle the host tx process?882;; step 1: do we care?883mov a, #0884cjne a, tx_unthrottle_threshold, start_tx__maybe_unthrottle_tx885;; nope886start_tx_really_done:887ret888start_tx__maybe_unthrottle_tx:889;; step 2: is there now room?890mov a, tx_ring_out891setb c892subb a, tx_ring_in893;; a is now write_room. If thresh >= a, we can unthrottle894clr c895subb a, tx_unthrottle_threshold896jc start_tx_really_done ; nope897;; yes, we can unthrottle. remove the threshold and mark a request898mov tx_unthrottle_threshold, #0899setb DO_TX_UNTHROTTLE900;; prod rx, which will actually send the message when in2 becomes free901ljmp start_in902903904serial_int:905push dps906push dpl907push dph908push dpl1909push dph1910push acc911jnb TI, serial_int__not_tx912;; tx finished. send another character if we have one913clr TI ; clear int914clr TX_RUNNING915lcall start_tx916serial_int__not_tx:917jnb RI, serial_int__not_rx918lcall get_rx_char919clr RI ; clear int920serial_int__not_rx:921;; return922pop acc923pop dph1924pop dpl1925pop dph926pop dpl927pop dps928reti929930get_rx_char:931mov dph, #HIGH(rx_ring)932mov dpl, rx_ring_in933inc dpl ; target = rx_ring_in+1934mov a, sbuf935movx @dptr, a936;; check for overflow before incrementing rx_ring_in937mov a, dpl938cjne a, rx_ring_out, get_rx_char__no_overflow939;; signal overflow940ret941get_rx_char__no_overflow:942inc rx_ring_in943;; kick off USB INpipe944acall start_in945ret946947start_in:948;; check if the inpipe is already running.949mov dptr, IN2CS950movx a, @dptr951jb acc.1, start_in__done; int will handle it952jb DO_TX_UNTHROTTLE, start_in__do_tx_unthrottle953;; see if there is any work to do. a serial interrupt might occur954;; during this sequence?955mov a, rx_ring_in956cjne a, rx_ring_out, start_in__have_work957ret ; nope958start_in__have_work:959;; now copy as much data as possible into the pipe. 63 bytes max.960clr a961mov dps, a962mov dph, #HIGH(rx_ring) ; load DPTR0 with source963inc dps964mov dptr, IN2BUF ; load DPTR1 with target965movx @dptr, a ; in[0] signals that rest of IN is rx data966inc dptr967inc dps968;; loop until we run out of data, or we have copied 64 bytes969mov r1, #1 ; INbuf size counter970start_in__loop:971mov a, rx_ring_in972cjne a, rx_ring_out, start_inlocal_irq_enablell_copying973sjmp start_in__kick974start_inlocal_irq_enablell_copying:975inc rx_ring_out976mov dpl, rx_ring_out977movx a, @dptr978inc dps979movx @dptr, a ; write into IN buffer980inc dptr981inc dps982inc r1983cjne r1, #64, start_in__loop; loop984start_in__kick:985;; either we ran out of data, or we copied 64 bytes. r1 has byte count986;; kick off IN987mov dptr, IN2BC988mov a, r1989jz start_in__done990movx @dptr, a991;; done992start_in__done:993;acall dump_stat994ret995start_in__do_tx_unthrottle:996;; special sequence: send a tx unthrottle message997clr DO_TX_UNTHROTTLE998clr a999mov dps, a1000mov dptr, IN2BUF1001mov a, #11002movx @dptr, a1003inc dptr1004mov a, #21005movx @dptr, a1006mov dptr, IN2BC1007movx @dptr, a1008ret10091010putchar:1011clr TI1012mov SBUF, a1013putchar_wait:1014jnb TI, putchar_wait1015clr TI1016ret101710181019baud_table: ; baud_high, then baud_low1020;; baud[0]: 1101021.byte BAUD_HIGH(110)1022.byte BAUD_LOW(110)1023;; baud[1]: 3001024.byte BAUD_HIGH(300)1025.byte BAUD_LOW(300)1026;; baud[2]: 12001027.byte BAUD_HIGH(1200)1028.byte BAUD_LOW(1200)1029;; baud[3]: 24001030.byte BAUD_HIGH(2400)1031.byte BAUD_LOW(2400)1032;; baud[4]: 48001033.byte BAUD_HIGH(4800)1034.byte BAUD_LOW(4800)1035;; baud[5]: 96001036.byte BAUD_HIGH(9600)1037.byte BAUD_LOW(9600)1038;; baud[6]: 192001039.byte BAUD_HIGH(19200)1040.byte BAUD_LOW(19200)1041;; baud[7]: 384001042.byte BAUD_HIGH(38400)1043.byte BAUD_LOW(38400)1044;; baud[8]: 576001045.byte BAUD_HIGH(57600)1046.byte BAUD_LOW(57600)1047;; baud[9]: 1152001048.byte BAUD_HIGH(115200)1049.byte BAUD_LOW(115200)10501051desc_device:1052.byte 0x12, 0x01, 0x00, 0x01, 0xff, 0xff, 0xff, 0x401053.byte 0xcd, 0x06, 0x04, 0x01, 0x89, 0xab, 1, 2, 3, 0x011054;;; The "real" device id, which must match the host driver, is that1055;;; "0xcd 0x06 0x04 0x01" sequence, which is 0x06cd, 0x010410561057desc_config1:1058.byte 0x09, 0x02, 0x20, 0x00, 0x01, 0x01, 0x00, 0x80, 0x321059.byte 0x09, 0x04, 0x00, 0x00, 0x02, 0xff, 0xff, 0xff, 0x001060.byte 0x07, 0x05, 0x82, 0x03, 0x40, 0x00, 0x011061.byte 0x07, 0x05, 0x02, 0x02, 0x40, 0x00, 0x0010621063desc_strings:1064.word string_langids, string_mfg, string_product, string_serial1065desc_strings_end:10661067string_langids: .byte string_langids_end-string_langids1068.byte 31069.word 01070string_langids_end:10711072;; sigh. These strings are Unicode, meaning UTF16? 2 bytes each. Now1073;; *that* is a pain in the ass to encode. And they are little-endian1074;; too. Use this perl snippet to get the bytecodes:1075/* while (<>) {1076@c = split(//);1077foreach $c (@c) {1078printf("0x%02x, 0x00, ", ord($c));1079}1080}1081*/10821083string_mfg: .byte string_mfg_end-string_mfg1084.byte 31085; .byte "ACME usb widgets"1086.byte 0x41, 0x00, 0x43, 0x00, 0x4d, 0x00, 0x45, 0x00, 0x20, 0x00, 0x75, 0x00, 0x73, 0x00, 0x62, 0x00, 0x20, 0x00, 0x77, 0x00, 0x69, 0x00, 0x64, 0x00, 0x67, 0x00, 0x65, 0x00, 0x74, 0x00, 0x73, 0x001087string_mfg_end:10881089string_product: .byte string_product_end-string_product1090.byte 31091; .byte "ACME USB serial widget"1092.byte 0x41, 0x00, 0x43, 0x00, 0x4d, 0x00, 0x45, 0x00, 0x20, 0x00, 0x55, 0x00, 0x53, 0x00, 0x42, 0x00, 0x20, 0x00, 0x73, 0x00, 0x65, 0x00, 0x72, 0x00, 0x69, 0x00, 0x61, 0x00, 0x6c, 0x00, 0x20, 0x00, 0x77, 0x00, 0x69, 0x00, 0x64, 0x00, 0x67, 0x00, 0x65, 0x00, 0x74, 0x001093string_product_end:10941095string_serial: .byte string_serial_end-string_serial1096.byte 31097; .byte "47"1098.byte 0x34, 0x00, 0x37, 0x001099string_serial_end:11001101;;; ring buffer memory1102;; tx_ring_in+1 is where the next input byte will go1103;; [tx_ring_out] has been sent1104;; if tx_ring_in == tx_ring_out, theres no work to do1105;; there are (tx_ring_in - tx_ring_out) chars to be written1106;; dont let _in lap _out1107;; cannot inc if tx_ring_in+1 == tx_ring_out1108;; write [tx_ring_in+1] then tx_ring_in++1109;; if (tx_ring_in+1 == tx_ring_out), overflow1110;; else tx_ring_in++1111;; read/send [tx_ring_out+1], then tx_ring_out++11121113;; rx_ring_in works the same way11141115.org 0x10001116tx_ring:1117.skip 0x100 ; 256 bytes1118rx_ring:1119.skip 0x100 ; 256 bytes112011211122.END1123112411251126