Path: blob/master/firmware/keyspan_pda/xircom_pgs.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 PORTBCFG #0x7f94153#define PORTCCFG #0x7f95154#define OEA #0x7f9c155#define IN07IRQ #0x7fa9156#define OUT07IRQ #0x7faa157#define IN07IEN #0x7fac158#define OUT07IEN #0x7fad159#define USBIRQ #0x7fab160#define USBIEN #0x7fae161#define USBBAV #0x7faf162#define USBCS #0x7fd6163#define SUDPTRH #0x7fd4164#define SUDPTRL #0x7fd5165#define SETUPDAT #0x7fe8166167;; usb interrupt : enable is EIE.0 (0xe8), flag is EXIF.4 (0x91)168169.org 0170ljmp start171;; interrupt vectors172.org 23H173ljmp serial_int174.byte 0175176.org 43H177ljmp USB_Jump_Table178.byte 0 ; filled in by the USB core179180;;; local variables. These are not initialized properly: do it by hand.181.org 30H182rx_ring_in: .byte 0183rx_ring_out: .byte 0184tx_ring_in: .byte 0185tx_ring_out: .byte 0186tx_unthrottle_threshold: .byte 0187188.org 0x100H ; wants to be on a page boundary189USB_Jump_Table:190ljmp ISR_Sudav ; Setup Data Available191.byte 0192ljmp 0 ; Start of Frame193.byte 0194ljmp 0 ; Setup Data Loading195.byte 0196ljmp 0 ; Global Suspend197.byte 0198ljmp 0 ; USB Reset199.byte 0200ljmp 0 ; Reserved201.byte 0202ljmp 0 ; End Point 0 In203.byte 0204ljmp 0 ; End Point 0 Out205.byte 0206ljmp 0 ; End Point 1 In207.byte 0208ljmp 0 ; End Point 1 Out209.byte 0210ljmp ISR_Ep2in211.byte 0212ljmp ISR_Ep2out213.byte 0214215216.org 0x200217218start: mov SP,STACK-1 ; set stack219;; clear local variables220clr a221mov tx_ring_in, a222mov tx_ring_out, a223mov rx_ring_in, a224mov rx_ring_out, a225mov tx_unthrottle_threshold, a226clr TX_RUNNING227clr DO_TX_UNTHROTTLE228229;; clear fifo with "fe"230mov r1, 0231mov a, #0xfe232mov dptr, #tx_ring233clear_tx_ring_loop:234movx @dptr, a235inc dptr236djnz r1, clear_tx_ring_loop237238mov a, #0xfd239mov dptr, #rx_ring240clear_rx_ring_loop:241movx @dptr, a242inc dptr243djnz r1, clear_rx_ring_loop244245;;; turn on the RS-232 driver chip (bring the STANDBY pin low)246;;; on Xircom the STANDBY is wired to PB6 and PC4247mov dptr, PORTBCFG248mov a, #0xBf249movx @dptr, a250mov dptr, PORTCCFG251mov a, #0xef252movx @dptr, a253254;; set OEC.4255mov a, #0x10256mov dptr,OEC257movx @dptr,a258259;; clear PC4260mov a, #0x00261mov dptr,OUTC262movx @dptr,a263264;; set OEB.6265mov a, #0x40266mov dptr,OEB267movx @dptr,a268269;; clear PB6270mov a, #0x00271mov dptr,OUTB272movx @dptr,a273274;; set OEC.[17]275mov a, #0x82276mov dptr,OEC277movx @dptr,a278279280;; set PORTCCFG.[01] to route TxD0,RxD0 to serial port281mov dptr, PORTCCFG282mov a, #0x03283movx @dptr, a284285;; set up interrupts, autovectoring286;; set BKPT287mov dptr, USBBAV288movx a,@dptr289setb acc.0 ; AVEN bit to 0290movx @dptr, a291292mov a,#0x01 ; enable SUDAV: setup data available (for ep0)293mov dptr, USBIRQ294movx @dptr, a ; clear SUDAVI295mov dptr, USBIEN296movx @dptr, a297298mov dptr, IN07IEN299mov a,#0x04 ; enable IN2 int300movx @dptr, a301302mov dptr, OUT07IEN303mov a,#0x04 ; enable OUT2 int304movx @dptr, a305mov dptr, OUT2BC306movx @dptr, a ; arm OUT2307308;; mov a, #0x84 ; turn on RTS, DTR309;; mov dptr,OUTC310;; movx @dptr, a311312mov a, #0x7 ; turn on DTR313mov dptr,USBBAV314movx @dptr, a315316mov a, #0x20 ; turn on the RED led317mov dptr,OEA318movx @dptr, a319320mov a, #0x80 ; turn on RTS321mov dptr,OUTC322movx @dptr, a323324;; setup the serial port. 9600 8N1.325mov a,#0x53 ; mode 1, enable rx, clear int326mov SCON, a327;; using timer2, in 16-bit baud-rate-generator mode328;; (xtal 12MHz, internal fosc 24MHz)329;; RCAP2H,RCAP2L = 65536 - fosc/(32*baud)330;; 57600: 0xFFF2.F, say 0xFFF3331;; 9600: 0xFFB1.E, say 0xFFB2332;; 300: 0xF63C333#define BAUD 9600334#define BAUD_TIMEOUT(rate) (65536 - (24 * 1000 * 1000) / (32 * rate))335#define BAUD_HIGH(rate) HIGH(BAUD_TIMEOUT(rate))336#define BAUD_LOW(rate) LOW(BAUD_TIMEOUT(rate))337338mov T2CON, #030h ; rclk=1,tclk=1,cp=0,tr2=0(enable later)339mov r3, #5340acall set_baud341setb TR2342mov SCON, #050h343344#if 0345mov r1, #0x40346mov a, #0x41347send:348mov SBUF, a349inc a350anl a, #0x3F351orl a, #0x40352; xrl a, #0x02353wait1:354jnb TI, wait1355clr TI356djnz r1, send357;done: sjmp done358359#endif360361setb EUSB362setb EA363setb ES0364;acall dump_stat365366;; hey, what say we RENUMERATE! (TRM p.62)367mov a, #0368mov dps, a369mov dptr, USBCS370mov a, #0x02 ; DISCON=0, DISCOE=0, RENUM=1371movx @dptr, a372;; now presence pin is floating, simulating disconnect. wait 0.5s373mov r1, #46374renum_wait1:375mov r2, #0376renum_wait2:377mov r3, #0378renum_wait3:379djnz r3, renum_wait3380djnz r2, renum_wait2381djnz r1, renum_wait1 ; wait about n*(256^2) 6MHz clocks382mov a, #0x06 ; DISCON=0, DISCOE=1, RENUM=1383movx @dptr, a384;; we are back online. the host device will now re-query us385386387main: sjmp main388389390391ISR_Sudav:392push dps393push dpl394push dph395push dpl1396push dph1397push acc398mov a,EXIF399clr acc.4400mov EXIF,a ; clear INT2 first401mov dptr, USBIRQ ; clear USB int402mov a,#01h403movx @dptr,a404405;; get request type406mov dptr, SETUPDAT407movx a, @dptr408mov r1, a ; r1 = bmRequestType409inc dptr410movx a, @dptr411mov r2, a ; r2 = bRequest412inc dptr413movx a, @dptr414mov r3, a ; r3 = wValueL415inc dptr416movx a, @dptr417mov r4, a ; r4 = wValueH418419;; main switch on bmRequest.type: standard or vendor420mov a, r1421anl a, #0x60422cjne a, #0x00, setup_bmreq_type_not_standard423;; standard request: now main switch is on bRequest424ljmp setup_bmreq_is_standard425426setup_bmreq_type_not_standard:427;; a still has bmreq&0x60428cjne a, #0x40, setup_bmreq_type_not_vendor429;; Anchor reserves bRequest 0xa0-0xaf, we use small ones430;; switch on bRequest. bmRequest will always be 0x41 or 0xc1431cjne r2, #0x00, setup_ctrl_not_00432;; 00 is set baud, wValue[0] has baud rate index433lcall set_baud ; index in r3, carry set if error434jc setup_bmreq_type_not_standard__do_stall435ljmp setup_done_ack436setup_bmreq_type_not_standard__do_stall:437ljmp setup_stall438setup_ctrl_not_00:439cjne r2, #0x01, setup_ctrl_not_01440;; 01 is reserved for set bits (parity). TODO441ljmp setup_stall442setup_ctrl_not_01:443cjne r2, #0x02, setup_ctrl_not_02444;; 02 is set HW flow control. TODO445ljmp setup_stall446setup_ctrl_not_02:447cjne r2, #0x03, setup_ctrl_not_03448;; 03 is control pins (RTS, DTR).449ljmp control_pins ; will jump to setup_done_ack,450; or setup_return_one_byte451setup_ctrl_not_03:452cjne r2, #0x04, setup_ctrl_not_04453;; 04 is send break (really "turn break on/off"). TODO454cjne r3, #0x00, setup_ctrl_do_break_on455;; do break off: restore PORTCCFG.1 to reconnect TxD0 to serial port456mov dptr, PORTCCFG457movx a, @dptr458orl a, #0x02459movx @dptr, a460ljmp setup_done_ack461setup_ctrl_do_break_on:462;; do break on: clear PORTCCFG.0, set TxD high(?) (b1 low)463mov dptr, OUTC464movx a, @dptr465anl a, #0xfd ; ~0x02466movx @dptr, a467mov dptr, PORTCCFG468movx a, @dptr469anl a, #0xfd ; ~0x02470movx @dptr, a471ljmp setup_done_ack472setup_ctrl_not_04:473cjne r2, #0x05, setup_ctrl_not_05474;; 05 is set desired interrupt bitmap. TODO475ljmp setup_stall476setup_ctrl_not_05:477cjne r2, #0x06, setup_ctrl_not_06478;; 06 is query room479cjne r3, #0x00, setup_ctrl_06_not_00480;; 06, wValue[0]=0 is query write_room481mov a, tx_ring_out482setb c483subb a, tx_ring_in ; out-1-in = 255 - (in-out)484ljmp setup_return_one_byte485setup_ctrl_06_not_00:486cjne r3, #0x01, setup_ctrl_06_not_01487;; 06, wValue[0]=1 is query chars_in_buffer488mov a, tx_ring_in489clr c490subb a, tx_ring_out ; in-out491ljmp setup_return_one_byte492setup_ctrl_06_not_01:493ljmp setup_stall494setup_ctrl_not_06:495cjne r2, #0x07, setup_ctrl_not_07496;; 07 is request tx unthrottle interrupt497mov tx_unthrottle_threshold, r3; wValue[0] is threshold value498ljmp setup_done_ack499setup_ctrl_not_07:500ljmp setup_stall501502setup_bmreq_type_not_vendor:503ljmp setup_stall504505506setup_bmreq_is_standard:507cjne r2, #0x00, setup_breq_not_00508;; 00: Get_Status (sub-switch on bmRequestType: device, ep, int)509cjne r1, #0x80, setup_Get_Status_not_device510;; Get_Status(device)511;; are we self-powered? no. can we do remote wakeup? no512;; so return two zero bytes. This is reusable513setup_return_two_zero_bytes:514mov dptr, IN0BUF515clr a516movx @dptr, a517inc dptr518movx @dptr, a519mov dptr, IN0BC520mov a, #2521movx @dptr, a522ljmp setup_done_ack523setup_Get_Status_not_device:524cjne r1, #0x82, setup_Get_Status_not_endpoint525;; Get_Status(endpoint)526;; must get stall bit for ep[wIndexL], return two bytes, bit in lsb 0527;; for now: cheat. TODO528sjmp setup_return_two_zero_bytes529setup_Get_Status_not_endpoint:530cjne r1, #0x81, setup_Get_Status_not_interface531;; Get_Status(interface): return two zeros532sjmp setup_return_two_zero_bytes533setup_Get_Status_not_interface:534ljmp setup_stall535536setup_breq_not_00:537cjne r2, #0x01, setup_breq_not_01538;; 01: Clear_Feature (sub-switch on wValueL: stall, remote wakeup)539cjne r3, #0x00, setup_Clear_Feature_not_stall540;; Clear_Feature(stall). should clear a stall bit. TODO541ljmp setup_stall542setup_Clear_Feature_not_stall:543cjne r3, #0x01, setup_Clear_Feature_not_rwake544;; Clear_Feature(remote wakeup). ignored.545ljmp setup_done_ack546setup_Clear_Feature_not_rwake:547ljmp setup_stall548549setup_breq_not_01:550cjne r2, #0x03, setup_breq_not_03551;; 03: Set_Feature (sub-switch on wValueL: stall, remote wakeup)552cjne r3, #0x00, setup_Set_Feature_not_stall553;; Set_Feature(stall). Should set a stall bit. TODO554ljmp setup_stall555setup_Set_Feature_not_stall:556cjne r3, #0x01, setup_Set_Feature_not_rwake557;; Set_Feature(remote wakeup). ignored.558ljmp setup_done_ack559setup_Set_Feature_not_rwake:560ljmp setup_stall561562setup_breq_not_03:563cjne r2, #0x06, setup_breq_not_06564;; 06: Get_Descriptor (s-switch on wValueH: dev, config[n], string[n])565cjne r4, #0x01, setup_Get_Descriptor_not_device566;; Get_Descriptor(device)567mov dptr, SUDPTRH568mov a, #HIGH(desc_device)569movx @dptr, a570mov dptr, SUDPTRL571mov a, #LOW(desc_device)572movx @dptr, a573ljmp setup_done_ack574setup_Get_Descriptor_not_device:575cjne r4, #0x02, setup_Get_Descriptor_not_config576;; Get_Descriptor(config[n])577cjne r3, #0x00, setup_stall; only handle n==0578;; Get_Descriptor(config[0])579mov dptr, SUDPTRH580mov a, #HIGH(desc_config1)581movx @dptr, a582mov dptr, SUDPTRL583mov a, #LOW(desc_config1)584movx @dptr, a585ljmp setup_done_ack586setup_Get_Descriptor_not_config:587cjne r4, #0x03, setup_Get_Descriptor_not_string588;; Get_Descriptor(string[wValueL])589;; if (wValueL >= maxstrings) stall590mov a, #((desc_strings_end-desc_strings)/2)591clr c592subb a,r3 ; a=4, r3 = 0..3 . if a<=0 then stall593jc setup_stall594jz setup_stall595mov a, r3596add a, r3 ; a = 2*wValueL597mov dptr, #desc_strings598add a, dpl599mov dpl, a600mov a, #0601addc a, dph602mov dph, a ; dph = desc_strings[a]. big endian! (handy)603;; it looks like my adapter uses a revision of the EZUSB that604;; contains "rev D errata number 8", as hinted in the EzUSB example605;; code. I cannot find an actual errata description on the Cypress606;; web site, but from the example code it looks like this bug causes607;; the length of string descriptors to be read incorrectly, possibly608;; sending back more characters than the descriptor has. The workaround609;; is to manually send out all of the data. The consequence of not610;; using the workaround is that the strings gathered by the kernel611;; driver are too long and are filled with trailing garbage (including612;; leftover strings). Writing this out by hand is a nuisance, so for613;; now I will just live with the bug.614movx a, @dptr615mov r1, a616inc dptr617movx a, @dptr618mov r2, a619mov dptr, SUDPTRH620mov a, r1621movx @dptr, a622mov dptr, SUDPTRL623mov a, r2624movx @dptr, a625;; done626ljmp setup_done_ack627628setup_Get_Descriptor_not_string:629ljmp setup_stall630631setup_breq_not_06:632cjne r2, #0x08, setup_breq_not_08633;; Get_Configuration. always 1. return one byte.634;; this is reusable635mov a, #1636setup_return_one_byte:637mov dptr, IN0BUF638movx @dptr, a639mov a, #1640mov dptr, IN0BC641movx @dptr, a642ljmp setup_done_ack643setup_breq_not_08:644cjne r2, #0x09, setup_breq_not_09645;; 09: Set_Configuration. ignored.646ljmp setup_done_ack647setup_breq_not_09:648cjne r2, #0x0a, setup_breq_not_0a649;; 0a: Get_Interface. get the current altsetting for int[wIndexL]650;; since we only have one interface, ignore wIndexL, return a 0651mov a, #0652ljmp setup_return_one_byte653setup_breq_not_0a:654cjne r2, #0x0b, setup_breq_not_0b655;; 0b: Set_Interface. set altsetting for interface[wIndexL]. ignored656ljmp setup_done_ack657setup_breq_not_0b:658ljmp setup_stall659660661setup_done_ack:662;; now clear HSNAK663mov dptr, EP0CS664mov a, #0x02665movx @dptr, a666sjmp setup_done667setup_stall:668;; unhandled. STALL669;EP0CS |= bmEPSTALL670mov dptr, EP0CS671movx a, @dptr672orl a, EP0STALLbit673movx @dptr, a674sjmp setup_done675676setup_done:677pop acc678pop dph1679pop dpl1680pop dph681pop dpl682pop dps683reti684685;;; ==============================================================686687set_baud: ; baud index in r3688;; verify a < 10689mov a, r3690jb ACC.7, set_baud__badbaud691clr c692subb a, #10693jnc set_baud__badbaud694mov a, r3695rl a ; a = index*2696add a, #LOW(baud_table)697mov dpl, a698mov a, #HIGH(baud_table)699addc a, #0700mov dph, a701;; TODO: shut down xmit/receive702;; TODO: wait for current xmit char to leave703;; TODO: shut down timer to avoid partial-char glitch704movx a,@dptr ; BAUD_HIGH705mov RCAP2H, a706mov TH2, a707inc dptr708movx a,@dptr ; BAUD_LOW709mov RCAP2L, a710mov TL2, a711;; TODO: restart xmit/receive712;; TODO: reenable interrupts, resume tx if pending713clr c ; c=0: success714ret715set_baud__badbaud:716setb c ; c=1: failure717ret718719;;; ==================================================720control_pins:721cjne r1, #0x41, control_pins_in722control_pins_out:723;TODO BKPT is DTR724mov a, r3 ; wValue[0] holds new bits: b7 is new RTS725xrl a, #0xff ; 1 means active, 0V, +12V ?726anl a, #0x80727mov r3, a728mov dptr, OUTC729movx a, @dptr ; only change bit 7730anl a, #0x7F ; ~0x84731orl a, r3732movx @dptr, a ; other pins are inputs, bits ignored733ljmp setup_done_ack734control_pins_in:735mov dptr, PINSC736movx a, @dptr737xrl a, #0xff738ljmp setup_return_one_byte739740;;; ========================================741742ISR_Ep2in:743push dps744push dpl745push dph746push dpl1747push dph1748push acc749mov a,EXIF750clr acc.4751mov EXIF,a ; clear INT2 first752mov dptr, IN07IRQ ; clear USB int753mov a,#04h754movx @dptr,a755756mov a, #0x20 ; Turn off the green LED757mov dptr,OEA758movx @dptr, a759760761;; do stuff762lcall start_in763764mov a, #0x20 ; Turn off the green LED765mov dptr,OEA766movx @dptr, a767768769770pop acc771pop dph1772pop dpl1773pop dph774pop dpl775pop dps776reti777778ISR_Ep2out:779push dps780push dpl781push dph782push dpl1783push dph1784push acc785786mov a, #0x10 ; Turn the green LED787mov dptr,OEA788movx @dptr, a789790791792mov a,EXIF793clr acc.4794mov EXIF,a ; clear INT2 first795mov dptr, OUT07IRQ ; clear USB int796mov a,#04h797movx @dptr,a798799;; do stuff800801;; copy data into buffer. for now, assume we will have enough space802mov dptr, OUT2BC ; get byte count803movx a,@dptr804mov r1, a805clr a806mov dps, a807mov dptr, OUT2BUF ; load DPTR0 with source808mov dph1, #HIGH(tx_ring) ; load DPTR1 with target809mov dpl1, tx_ring_in810OUT_loop:811movx a,@dptr ; read812inc dps ; switch to DPTR1: target813inc dpl1 ; target = tx_ring_in+1814movx @dptr,a ; store815mov a,dpl1816cjne a, tx_ring_out, OUT_no_overflow817sjmp OUT_overflow818OUT_no_overflow:819inc tx_ring_in ; tx_ring_in++820inc dps ; switch to DPTR0: source821inc dptr822djnz r1, OUT_loop823sjmp OUT_done824OUT_overflow:825;; signal overflow826;; fall through827OUT_done:828;; ack829mov dptr,OUT2BC830movx @dptr,a831832;; start tx833acall maybe_start_tx834;acall dump_stat835836mov a, #0x20 ; Turn off the green LED837mov dptr,OEA838movx @dptr, a839840pop acc841pop dph1842pop dpl1843pop dph844pop dpl845pop dps846reti847848dump_stat:849;; fill in EP4in with a debugging message:850;; tx_ring_in, tx_ring_out, rx_ring_in, rx_ring_out851;; tx_active852;; tx_ring[0..15]853;; 0xfc854;; rx_ring[0..15]855clr a856mov dps, a857858mov dptr, IN4CS859movx a, @dptr860jb acc.1, dump_stat__done; busy: cannot dump, old one still pending861mov dptr, IN4BUF862863mov a, tx_ring_in864movx @dptr, a865inc dptr866mov a, tx_ring_out867movx @dptr, a868inc dptr869870mov a, rx_ring_in871movx @dptr, a872inc dptr873mov a, rx_ring_out874movx @dptr, a875inc dptr876877clr a878jnb TX_RUNNING, dump_stat__no_tx_running879inc a880dump_stat__no_tx_running:881movx @dptr, a882inc dptr883;; tx_ring[0..15]884inc dps885mov dptr, #tx_ring ; DPTR1: source886mov r1, #16887dump_stat__tx_ring_loop:888movx a, @dptr889inc dptr890inc dps891movx @dptr, a892inc dptr893inc dps894djnz r1, dump_stat__tx_ring_loop895inc dps896897mov a, #0xfc898movx @dptr, a899inc dptr900901;; rx_ring[0..15]902inc dps903mov dptr, #rx_ring ; DPTR1: source904mov r1, #16905dump_stat__rx_ring_loop:906movx a, @dptr907inc dptr908inc dps909movx @dptr, a910inc dptr911inc dps912djnz r1, dump_stat__rx_ring_loop913914;; now send it915clr a916mov dps, a917mov dptr, IN4BC918mov a, #38919movx @dptr, a920dump_stat__done:921ret922923;;; ============================================================924925maybe_start_tx:926;; make sure the tx process is running.927jb TX_RUNNING, start_tx_done928start_tx:929;; is there work to be done?930mov a, tx_ring_in931cjne a,tx_ring_out, start_tx__work932ret ; no work933start_tx__work:934;; tx was not running. send the first character, setup the TI int935inc tx_ring_out ; [++tx_ring_out]936mov dph, #HIGH(tx_ring)937mov dpl, tx_ring_out938movx a, @dptr939mov sbuf, a940setb TX_RUNNING941start_tx_done:942;; can we unthrottle the host tx process?943;; step 1: do we care?944mov a, #0945cjne a, tx_unthrottle_threshold, start_tx__maybe_unthrottle_tx946;; nope947start_tx_really_done:948ret949start_tx__maybe_unthrottle_tx:950;; step 2: is there now room?951mov a, tx_ring_out952setb c953subb a, tx_ring_in954;; a is now write_room. If thresh >= a, we can unthrottle955clr c956subb a, tx_unthrottle_threshold957jc start_tx_really_done ; nope958;; yes, we can unthrottle. remove the threshold and mark a request959mov tx_unthrottle_threshold, #0960setb DO_TX_UNTHROTTLE961;; prod rx, which will actually send the message when in2 becomes free962ljmp start_in963964965serial_int:966push dps967push dpl968push dph969push dpl1970push dph1971push acc972jnb TI, serial_int__not_tx973;; tx finished. send another character if we have one974clr TI ; clear int975clr TX_RUNNING976lcall start_tx977serial_int__not_tx:978jnb RI, serial_int__not_rx979lcall get_rx_char980clr RI ; clear int981serial_int__not_rx:982;; return983pop acc984pop dph1985pop dpl1986pop dph987pop dpl988pop dps989reti990991get_rx_char:992mov dph, #HIGH(rx_ring)993mov dpl, rx_ring_in994inc dpl ; target = rx_ring_in+1995mov a, sbuf996movx @dptr, a997;; check for overflow before incrementing rx_ring_in998mov a, dpl999cjne a, rx_ring_out, get_rx_char__no_overflow1000;; signal overflow1001ret1002get_rx_char__no_overflow:1003inc rx_ring_in1004;; kick off USB INpipe1005acall start_in1006ret10071008start_in:1009;; check if the inpipe is already running.1010mov a,#0x101011mov dptr, OEA1012movx @dptr,a10131014mov dptr, IN2CS1015movx a, @dptr1016jb acc.1, start_in__done; int will handle it1017jb DO_TX_UNTHROTTLE, start_in__do_tx_unthrottle1018;; see if there is any work to do. a serial interrupt might occur1019;; during this sequence?1020mov a, rx_ring_in1021cjne a, rx_ring_out, start_in__have_work1022ret ; nope1023start_in__have_work:1024;; now copy as much data as possible into the pipe. 63 bytes max.1025clr a1026mov dps, a1027mov dph, #HIGH(rx_ring) ; load DPTR0 with source1028inc dps1029mov dptr, IN2BUF ; load DPTR1 with target1030movx @dptr, a ; in[0] signals that rest of IN is rx data1031inc dptr1032inc dps1033;; loop until we run out of data, or we have copied 64 bytes1034mov r1, #1 ; INbuf size counter1035start_in__loop:1036mov a, rx_ring_in1037cjne a, rx_ring_out, start_inlocal_irq_enablell_copying1038sjmp start_in__kick1039start_inlocal_irq_enablell_copying:1040inc rx_ring_out1041mov dpl, rx_ring_out1042movx a, @dptr1043inc dps1044movx @dptr, a ; write into IN buffer1045inc dptr1046inc dps1047inc r11048cjne r1, #64, start_in__loop; loop1049start_in__kick:1050;; either we ran out of data, or we copied 64 bytes. r1 has byte count1051;; kick off IN1052mov a, #0x10 ; Turn the green LED1053mov dptr,OEA1054movx @dptr, a1055mov dptr, IN2BC1056mov a, r11057jz start_in__done1058movx @dptr, a1059;; done1060start_in__done:1061;acall dump_stat1062ret1063start_in__do_tx_unthrottle:1064;; special sequence: send a tx unthrottle message1065clr DO_TX_UNTHROTTLE1066clr a1067mov dps, a1068mov dptr, IN2BUF1069mov a, #11070movx @dptr, a1071inc dptr1072mov a, #21073movx @dptr, a1074mov dptr, IN2BC1075movx @dptr, a1076ret10771078putchar:1079clr TI1080mov SBUF, a1081putchar_wait:1082jnb TI, putchar_wait1083clr TI1084ret108510861087baud_table: ; baud_high, then baud_low1088;; baud[0]: 1101089.byte BAUD_HIGH(110)1090.byte BAUD_LOW(110)1091;; baud[1]: 3001092.byte BAUD_HIGH(300)1093.byte BAUD_LOW(300)1094;; baud[2]: 12001095.byte BAUD_HIGH(1200)1096.byte BAUD_LOW(1200)1097;; baud[3]: 24001098.byte BAUD_HIGH(2400)1099.byte BAUD_LOW(2400)1100;; baud[4]: 48001101.byte BAUD_HIGH(4800)1102.byte BAUD_LOW(4800)1103;; baud[5]: 96001104.byte BAUD_HIGH(9600)1105.byte BAUD_LOW(9600)1106;; baud[6]: 192001107.byte BAUD_HIGH(19200)1108.byte BAUD_LOW(19200)1109;; baud[7]: 384001110.byte BAUD_HIGH(38400)1111.byte BAUD_LOW(38400)1112;; baud[8]: 576001113.byte BAUD_HIGH(57600)1114.byte BAUD_LOW(57600)1115;; baud[9]: 1152001116.byte BAUD_HIGH(115200)1117.byte BAUD_LOW(115200)11181119desc_device:1120.byte 0x12, 0x01, 0x00, 0x01, 0xff, 0xff, 0xff, 0x401121.byte 0xcd, 0x06, 0x04, 0x01, 0x89, 0xab, 1, 2, 3, 0x011122;;; The "real" device id, which must match the host driver, is that1123;;; "0xcd 0x06 0x04 0x01" sequence, which is 0x06cd, 0x010411241125desc_config1:1126.byte 0x09, 0x02, 0x20, 0x00, 0x01, 0x01, 0x00, 0x80, 0x321127.byte 0x09, 0x04, 0x00, 0x00, 0x02, 0xff, 0xff, 0xff, 0x001128.byte 0x07, 0x05, 0x82, 0x03, 0x40, 0x00, 0x011129.byte 0x07, 0x05, 0x02, 0x02, 0x40, 0x00, 0x0011301131desc_strings:1132.word string_langids, string_mfg, string_product, string_serial1133desc_strings_end:11341135string_langids: .byte string_langids_end-string_langids1136.byte 31137.word 01138string_langids_end:11391140;; sigh. These strings are Unicode, meaning UTF16? 2 bytes each. Now1141;; *that* is a pain in the ass to encode. And they are little-endian1142;; too. Use this perl snippet to get the bytecodes:1143/* while (<>) {1144@c = split(//);1145foreach $c (@c) {1146printf("0x%02x, 0x00, ", ord($c));1147}1148}1149*/11501151string_mfg: .byte string_mfg_end-string_mfg1152.byte 31153; .byte "ACME usb widgets"1154.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, 0x001155string_mfg_end:11561157string_product: .byte string_product_end-string_product1158.byte 31159; .byte "ACME USB serial widget"1160.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, 0x001161string_product_end:11621163string_serial: .byte string_serial_end-string_serial1164.byte 31165; .byte "47"1166.byte 0x34, 0x00, 0x37, 0x001167string_serial_end:11681169;;; ring buffer memory1170;; tx_ring_in+1 is where the next input byte will go1171;; [tx_ring_out] has been sent1172;; if tx_ring_in == tx_ring_out, theres no work to do1173;; there are (tx_ring_in - tx_ring_out) chars to be written1174;; dont let _in lap _out1175;; cannot inc if tx_ring_in+1 == tx_ring_out1176;; write [tx_ring_in+1] then tx_ring_in++1177;; if (tx_ring_in+1 == tx_ring_out), overflow1178;; else tx_ring_in++1179;; read/send [tx_ring_out+1], then tx_ring_out++11801181;; rx_ring_in works the same way11821183.org 0x10001184tx_ring:1185.skip 0x100 ; 256 bytes1186rx_ring:1187.skip 0x100 ; 256 bytes118811891190.END1191119211931194