Root/firmware/keyspan_pda/keyspan_pda.S

1/* $Id: loop.s,v 1.23 2000/03/20 09:49:06 warner Exp $
2 *
3 * Firmware for the Keyspan PDA Serial Adapter, a USB serial port based on
4 * the EzUSB microcontroller.
5 *
6 * (C) Copyright 2000 Brian Warner <warner@lothar.com>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * "Keyspan PDA Serial Adapter" is probably a copyright of Keyspan, the
14 * company.
15 *
16 * This serial adapter is basically an EzUSB chip and an RS-232 line driver
17 * in a little widget that has a DB-9 on one end and a USB plug on the other.
18 * It uses the EzUSB's internal UART0 (using the pins from Port C) and timer2
19 * as a baud-rate generator. The wiring is:
20 * PC0/RxD0 <- rxd (DB9 pin 2) PC4 <- dsr pin 6
21 * PC1/TxD0 -> txd pin 3 PC5 <- ri pin 9
22 * PC2 -> rts pin 7 PC6 <- dcd pin 1
23 * PC3 <- cts pin 8 PC7 -> dtr pin 4
24 * PB1 -> line driver standby
25 *
26 * The EzUSB register constants below come from their excellent documentation
27 * and sample code (which used to be available at www.anchorchips.com, but
28 * that has now been absorbed into Cypress' site and the CD-ROM contents
29 * don't appear to be available online anymore). If we get multiple
30 * EzUSB-based drivers into the kernel, it might be useful to pull them out
31 * into a separate .h file.
32 *
33 * THEORY OF OPERATION:
34 *
35 * There are two 256-byte ring buffers, one for tx, one for rx.
36 *
37 * EP2out is pure tx data. When it appears, the data is copied into the tx
38 * ring and serial transmission is started if it wasn't already running. The
39 * "tx buffer empty" interrupt may kick off another character if the ring
40 * still has data. If the host is tx-blocked because the ring filled up,
41 * it will request a "tx unthrottle" interrupt. If sending a serial character
42 * empties the ring below the desired threshold, we set a bit that will send
43 * up the tx unthrottle message as soon as the rx buffer becomes free.
44 *
45 * EP2in (interrupt) is used to send both rx chars and rx status messages
46 * (only "tx unthrottle" at this time) back up to the host. The first byte
47 * of the rx message indicates data (0) or status msg (1). Status messages
48 * are sent before any data.
49 *
50 * Incoming serial characters are put into the rx ring by the serial
51 * interrupt, and the EP2in buffer sent if it wasn't already in transit.
52 * When the EP2in buffer returns, the interrupt prompts us to send more
53 * rx chars (or status messages) if they are pending.
54 *
55 * Device control happens through "vendor specific" control messages on EP0.
56 * All messages are destined for the "Interface" (with the index always 0,
57 * so that if their two-port device might someday use similar firmware, we
58 * can use index=1 to refer to the second port). The messages defined are:
59 *
60 * bRequest = 0 : set baud/bits/parity
61 * 1 : unused
62 * 2 : reserved for setting HW flow control (CTSRTS)
63 * 3 : get/set "modem info" (pin states: DTR, RTS, DCD, RI, etc)
64 * 4 : set break (on/off)
65 * 5 : reserved for requesting interrupts on pin state change
66 * 6 : query buffer room or chars in tx buffer
67 * 7 : request tx unthrottle interrupt
68 *
69 * The host-side driver is set to recognize the device ID values stashed in
70 * serial EEPROM (0x06cd, 0x0103), program this firmware into place, then
71 * start it running. This firmware will use EzUSB's "renumeration" trick by
72 * simulating a bus disconnect, then reconnect with a different device ID
73 * (encoded in the desc_device descriptor below). The host driver then
74 * recognizes the new device ID and glues it to the real serial driver code.
75 *
76 * USEFUL DOCS:
77 * EzUSB Technical Reference Manual: <http://www.anchorchips.com>
78 * 8051 manuals: everywhere, but try www.dalsemi.com because the EzUSB is
79 * basically the Dallas enhanced 8051 code. Remember that the EzUSB IO ports
80 * use totally different registers!
81 * USB 1.1 spec: www.usb.org
82 *
83 * HOW TO BUILD:
84 * gcc -x assembler-with-cpp -P -E -o keyspan_pda.asm keyspan_pda.s
85 * as31 -l keyspan_pda.asm
86 * mv keyspan_pda.obj keyspan_pda.hex
87 * perl ezusb_convert.pl keyspan_pda < keyspan_pda.hex > keyspan_pda_fw.h
88 * Get as31 from <http://www.pjrc.com/tech/8051/index.html>, and hack on it
89 * a bit to make it build.
90 *
91 * THANKS:
92 * Greg Kroah-Hartman, for coordinating the whole usb-serial thing.
93 * AnchorChips, for making such an incredibly useful little microcontroller.
94 * KeySpan, for making a handy, cheap ($40) widget that was so easy to take
95 * apart and trace with an ohmmeter.
96 *
97 * TODO:
98 * lots. grep for TODO. Interrupt safety needs stress-testing. Better flow
99 * control. Interrupting host upon change in DCD, etc, counting transitions.
100 * Need to find a safe device id to use (the one used by the Keyspan firmware
101 * under Windows would be ideal.. can anyone figure out what it is?). Parity.
102 * More baud rates. Oh, and the string-descriptor-length silicon bug
103 * workaround should be implemented, but I'm lazy, and the consequence is
104 * that the device name strings that show up in your kernel log will have
105 * lots of trailing binary garbage in them (appears as ????). Device strings
106 * should be made more accurate.
107 *
108 * Questions, bugs, patches to Brian.
109 *
110 * -Brian Warner <warner@lothar.com>
111 *
112 */
113    
114#define HIGH(x) (((x) & 0xff00) / 256)
115#define LOW(x) ((x) & 0xff)
116
117#define dpl1 0x84
118#define dph1 0x85
119#define dps 0x86
120
121;;; our bit assignments
122#define TX_RUNNING 0
123#define DO_TX_UNTHROTTLE 1
124    
125    ;; stack from 0x60 to 0x7f: should really set SP to 0x60-1, not 0x60
126#define STACK #0x60-1
127
128#define EXIF 0x91
129#define EIE 0xe8
130    .flag EUSB, EIE.0
131    .flag ES0, IE.4
132
133#define EP0CS #0x7fb4
134#define EP0STALLbit #0x01
135#define IN0BUF #0x7f00
136#define IN0BC #0x7fb5
137#define OUT0BUF #0x7ec0
138#define OUT0BC #0x7fc5
139#define IN2BUF #0x7e00
140#define IN2BC #0x7fb9
141#define IN2CS #0x7fb8
142#define OUT2BC #0x7fc9
143#define OUT2CS #0x7fc8
144#define OUT2BUF #0x7dc0
145#define IN4BUF #0x7d00
146#define IN4BC #0x7fbd
147#define IN4CS #0x7fbc
148#define OEB #0x7f9d
149#define OUTB #0x7f97
150#define OEC #0x7f9e
151#define OUTC #0x7f98
152#define PINSC #0x7f9b
153#define PORTCCFG #0x7f95
154#define IN07IRQ #0x7fa9
155#define OUT07IRQ #0x7faa
156#define IN07IEN #0x7fac
157#define OUT07IEN #0x7fad
158#define USBIRQ #0x7fab
159#define USBIEN #0x7fae
160#define USBBAV #0x7faf
161#define USBCS #0x7fd6
162#define SUDPTRH #0x7fd4
163#define SUDPTRL #0x7fd5
164#define SETUPDAT #0x7fe8
165        
166    ;; usb interrupt : enable is EIE.0 (0xe8), flag is EXIF.4 (0x91)
167
168    .org 0
169    ljmp start
170    ;; interrupt vectors
171    .org 23H
172    ljmp serial_int
173    .byte 0
174    
175    .org 43H
176    ljmp USB_Jump_Table
177    .byte 0 ; filled in by the USB core
178
179;;; local variables. These are not initialized properly: do it by hand.
180    .org 30H
181rx_ring_in: .byte 0
182rx_ring_out: .byte 0
183tx_ring_in: .byte 0
184tx_ring_out: .byte 0
185tx_unthrottle_threshold: .byte 0
186        
187    .org 0x100H ; wants to be on a page boundary
188USB_Jump_Table:
189    ljmp ISR_Sudav ; Setup Data Available
190    .byte 0
191    ljmp 0 ; Start of Frame
192    .byte 0
193    ljmp 0 ; Setup Data Loading
194    .byte 0
195    ljmp 0 ; Global Suspend
196    .byte 0
197    ljmp 0 ; USB Reset
198    .byte 0
199    ljmp 0 ; Reserved
200    .byte 0
201    ljmp 0 ; End Point 0 In
202    .byte 0
203    ljmp 0 ; End Point 0 Out
204    .byte 0
205    ljmp 0 ; End Point 1 In
206    .byte 0
207    ljmp 0 ; End Point 1 Out
208    .byte 0
209    ljmp ISR_Ep2in
210    .byte 0
211    ljmp ISR_Ep2out
212    .byte 0
213
214
215    .org 0x200
216        
217start: mov SP,STACK-1 ; set stack
218    ;; clear local variables
219    clr a
220    mov tx_ring_in, a
221    mov tx_ring_out, a
222    mov rx_ring_in, a
223    mov rx_ring_out, a
224    mov tx_unthrottle_threshold, a
225    clr TX_RUNNING
226    clr DO_TX_UNTHROTTLE
227    
228    ;; clear fifo with "fe"
229    mov r1, 0
230    mov a, #0xfe
231    mov dptr, #tx_ring
232clear_tx_ring_loop:
233    movx @dptr, a
234    inc dptr
235    djnz r1, clear_tx_ring_loop
236
237    mov a, #0xfd
238    mov dptr, #rx_ring
239clear_rx_ring_loop:
240    movx @dptr, a
241    inc dptr
242    djnz r1, clear_rx_ring_loop
243
244;;; turn on the RS-232 driver chip (bring the STANDBY pin low)
245    ;; set OEB.1
246    mov a, #02H
247    mov dptr,OEB
248    movx @dptr,a
249    ;; clear PB1
250    mov a, #00H
251    mov dptr,OUTB
252    movx @dptr,a
253    ;; set OEC.[127]
254    mov a, #0x86
255    mov dptr,OEC
256    movx @dptr,a
257    ;; set PORTCCFG.[01] to route TxD0,RxD0 to serial port
258    mov dptr, PORTCCFG
259    mov a, #0x03
260    movx @dptr, a
261    
262    ;; set up interrupts, autovectoring
263    mov dptr, USBBAV
264    movx a,@dptr
265    setb acc.0 ; AVEN bit to 0
266    movx @dptr, a
267
268    mov a,#0x01 ; enable SUDAV: setup data available (for ep0)
269    mov dptr, USBIRQ
270    movx @dptr, a ; clear SUDAVI
271    mov dptr, USBIEN
272    movx @dptr, a
273    
274    mov dptr, IN07IEN
275    mov a,#0x04 ; enable IN2 int
276    movx @dptr, a
277    
278    mov dptr, OUT07IEN
279    mov a,#0x04 ; enable OUT2 int
280    movx @dptr, a
281    mov dptr, OUT2BC
282    movx @dptr, a ; arm OUT2
283
284    mov a, #0x84 ; turn on RTS, DTR
285    mov dptr,OUTC
286    movx @dptr, a
287    ;; setup the serial port. 9600 8N1.
288    mov a,#01010011 ; mode 1, enable rx, clear int
289    mov SCON, a
290    ;; using timer2, in 16-bit baud-rate-generator mode
291    ;; (xtal 12MHz, internal fosc 24MHz)
292    ;; RCAP2H,RCAP2L = 65536 - fosc/(32*baud)
293    ;; 57600: 0xFFF2.F, say 0xFFF3
294    ;; 9600: 0xFFB1.E, say 0xFFB2
295    ;; 300: 0xF63C
296#define BAUD 9600
297#define BAUD_TIMEOUT(rate) (65536 - (24 * 1000 * 1000) / (32 * rate))
298#define BAUD_HIGH(rate) HIGH(BAUD_TIMEOUT(rate))
299#define BAUD_LOW(rate) LOW(BAUD_TIMEOUT(rate))
300        
301    mov T2CON, #030h ; rclk=1,tclk=1,cp=0,tr2=0(enable later)
302    mov r3, #5
303    acall set_baud
304    setb TR2
305    mov SCON, #050h
306    
307#if 0
308    mov r1, #0x40
309    mov a, #0x41
310send:
311    mov SBUF, a
312    inc a
313    anl a, #0x3F
314    orl a, #0x40
315; xrl a, #0x02
316wait1:
317    jnb TI, wait1
318    clr TI
319    djnz r1, send
320;done: sjmp done
321
322#endif
323    
324    setb EUSB
325    setb EA
326    setb ES0
327    ;acall dump_stat
328
329    ;; hey, what say we RENUMERATE! (TRM p.62)
330    mov a, #0
331    mov dps, a
332    mov dptr, USBCS
333    mov a, #0x02 ; DISCON=0, DISCOE=0, RENUM=1
334    movx @dptr, a
335    ;; now presence pin is floating, simulating disconnect. wait 0.5s
336    mov r1, #46
337renum_wait1:
338    mov r2, #0
339renum_wait2:
340    mov r3, #0
341renum_wait3:
342    djnz r3, renum_wait3
343    djnz r2, renum_wait2
344    djnz r1, renum_wait1 ; wait about n*(256^2) 6MHz clocks
345    mov a, #0x06 ; DISCON=0, DISCOE=1, RENUM=1
346    movx @dptr, a
347    ;; we are back online. the host device will now re-query us
348    
349    
350main: sjmp main
351
352    
353
354ISR_Sudav:
355    push dps
356    push dpl
357    push dph
358    push dpl1
359    push dph1
360    push acc
361    mov a,EXIF
362    clr acc.4
363    mov EXIF,a ; clear INT2 first
364    mov dptr, USBIRQ ; clear USB int
365    mov a,#01h
366    movx @dptr,a
367
368    ;; get request type
369    mov dptr, SETUPDAT
370    movx a, @dptr
371    mov r1, a ; r1 = bmRequestType
372    inc dptr
373    movx a, @dptr
374    mov r2, a ; r2 = bRequest
375    inc dptr
376    movx a, @dptr
377    mov r3, a ; r3 = wValueL
378    inc dptr
379    movx a, @dptr
380    mov r4, a ; r4 = wValueH
381
382    ;; main switch on bmRequest.type: standard or vendor
383    mov a, r1
384    anl a, #0x60
385    cjne a, #0x00, setup_bmreq_type_not_standard
386    ;; standard request: now main switch is on bRequest
387    ljmp setup_bmreq_is_standard
388    
389setup_bmreq_type_not_standard:
390    ;; a still has bmreq&0x60
391    cjne a, #0x40, setup_bmreq_type_not_vendor
392    ;; Anchor reserves bRequest 0xa0-0xaf, we use small ones
393    ;; switch on bRequest. bmRequest will always be 0x41 or 0xc1
394    cjne r2, #0x00, setup_ctrl_not_00
395    ;; 00 is set baud, wValue[0] has baud rate index
396    lcall set_baud ; index in r3, carry set if error
397    jc setup_bmreq_type_not_standard__do_stall
398    ljmp setup_done_ack
399setup_bmreq_type_not_standard__do_stall:
400    ljmp setup_stall
401setup_ctrl_not_00:
402    cjne r2, #0x01, setup_ctrl_not_01
403    ;; 01 is reserved for set bits (parity). TODO
404    ljmp setup_stall
405setup_ctrl_not_01:
406    cjne r2, #0x02, setup_ctrl_not_02
407    ;; 02 is set HW flow control. TODO
408    ljmp setup_stall
409setup_ctrl_not_02:
410    cjne r2, #0x03, setup_ctrl_not_03
411    ;; 03 is control pins (RTS, DTR).
412    ljmp control_pins ; will jump to setup_done_ack,
413                ; or setup_return_one_byte
414setup_ctrl_not_03:
415    cjne r2, #0x04, setup_ctrl_not_04
416    ;; 04 is send break (really "turn break on/off"). TODO
417    cjne r3, #0x00, setup_ctrl_do_break_on
418    ;; do break off: restore PORTCCFG.1 to reconnect TxD0 to serial port
419    mov dptr, PORTCCFG
420    movx a, @dptr
421    orl a, #0x02
422    movx @dptr, a
423    ljmp setup_done_ack
424setup_ctrl_do_break_on:
425    ;; do break on: clear PORTCCFG.0, set TxD high(?) (b1 low)
426    mov dptr, OUTC
427    movx a, @dptr
428    anl a, #0xfd ; ~0x02
429    movx @dptr, a
430    mov dptr, PORTCCFG
431    movx a, @dptr
432    anl a, #0xfd ; ~0x02
433    movx @dptr, a
434    ljmp setup_done_ack
435setup_ctrl_not_04:
436    cjne r2, #0x05, setup_ctrl_not_05
437    ;; 05 is set desired interrupt bitmap. TODO
438    ljmp setup_stall
439setup_ctrl_not_05:
440    cjne r2, #0x06, setup_ctrl_not_06
441    ;; 06 is query room
442    cjne r3, #0x00, setup_ctrl_06_not_00
443    ;; 06, wValue[0]=0 is query write_room
444    mov a, tx_ring_out
445    setb c
446    subb a, tx_ring_in ; out-1-in = 255 - (in-out)
447    ljmp setup_return_one_byte
448setup_ctrl_06_not_00:
449    cjne r3, #0x01, setup_ctrl_06_not_01
450    ;; 06, wValue[0]=1 is query chars_in_buffer
451    mov a, tx_ring_in
452    clr c
453    subb a, tx_ring_out ; in-out
454    ljmp setup_return_one_byte
455setup_ctrl_06_not_01:
456    ljmp setup_stall
457setup_ctrl_not_06:
458    cjne r2, #0x07, setup_ctrl_not_07
459    ;; 07 is request tx unthrottle interrupt
460    mov tx_unthrottle_threshold, r3; wValue[0] is threshold value
461    ljmp setup_done_ack
462setup_ctrl_not_07:
463    ljmp setup_stall
464    
465setup_bmreq_type_not_vendor:
466    ljmp setup_stall
467
468
469setup_bmreq_is_standard:
470    cjne r2, #0x00, setup_breq_not_00
471    ;; 00: Get_Status (sub-switch on bmRequestType: device, ep, int)
472    cjne r1, #0x80, setup_Get_Status_not_device
473    ;; Get_Status(device)
474    ;; are we self-powered? no. can we do remote wakeup? no
475    ;; so return two zero bytes. This is reusable
476setup_return_two_zero_bytes:
477    mov dptr, IN0BUF
478    clr a
479    movx @dptr, a
480    inc dptr
481    movx @dptr, a
482    mov dptr, IN0BC
483    mov a, #2
484    movx @dptr, a
485    ljmp setup_done_ack
486setup_Get_Status_not_device:
487    cjne r1, #0x82, setup_Get_Status_not_endpoint
488    ;; Get_Status(endpoint)
489    ;; must get stall bit for ep[wIndexL], return two bytes, bit in lsb 0
490    ;; for now: cheat. TODO
491    sjmp setup_return_two_zero_bytes
492setup_Get_Status_not_endpoint:
493    cjne r1, #0x81, setup_Get_Status_not_interface
494    ;; Get_Status(interface): return two zeros
495    sjmp setup_return_two_zero_bytes
496setup_Get_Status_not_interface:
497    ljmp setup_stall
498    
499setup_breq_not_00:
500    cjne r2, #0x01, setup_breq_not_01
501    ;; 01: Clear_Feature (sub-switch on wValueL: stall, remote wakeup)
502    cjne r3, #0x00, setup_Clear_Feature_not_stall
503    ;; Clear_Feature(stall). should clear a stall bit. TODO
504    ljmp setup_stall
505setup_Clear_Feature_not_stall:
506    cjne r3, #0x01, setup_Clear_Feature_not_rwake
507    ;; Clear_Feature(remote wakeup). ignored.
508    ljmp setup_done_ack
509setup_Clear_Feature_not_rwake:
510    ljmp setup_stall
511    
512setup_breq_not_01:
513    cjne r2, #0x03, setup_breq_not_03
514    ;; 03: Set_Feature (sub-switch on wValueL: stall, remote wakeup)
515    cjne r3, #0x00, setup_Set_Feature_not_stall
516    ;; Set_Feature(stall). Should set a stall bit. TODO
517    ljmp setup_stall
518setup_Set_Feature_not_stall:
519    cjne r3, #0x01, setup_Set_Feature_not_rwake
520    ;; Set_Feature(remote wakeup). ignored.
521    ljmp setup_done_ack
522setup_Set_Feature_not_rwake:
523    ljmp setup_stall
524    
525setup_breq_not_03:
526    cjne r2, #0x06, setup_breq_not_06
527    ;; 06: Get_Descriptor (s-switch on wValueH: dev, config[n], string[n])
528    cjne r4, #0x01, setup_Get_Descriptor_not_device
529    ;; Get_Descriptor(device)
530    mov dptr, SUDPTRH
531    mov a, #HIGH(desc_device)
532    movx @dptr, a
533    mov dptr, SUDPTRL
534    mov a, #LOW(desc_device)
535    movx @dptr, a
536    ljmp setup_done_ack
537setup_Get_Descriptor_not_device:
538    cjne r4, #0x02, setup_Get_Descriptor_not_config
539    ;; Get_Descriptor(config[n])
540    cjne r3, #0x00, setup_stall; only handle n==0
541    ;; Get_Descriptor(config[0])
542    mov dptr, SUDPTRH
543    mov a, #HIGH(desc_config1)
544    movx @dptr, a
545    mov dptr, SUDPTRL
546    mov a, #LOW(desc_config1)
547    movx @dptr, a
548    ljmp setup_done_ack
549setup_Get_Descriptor_not_config:
550    cjne r4, #0x03, setup_Get_Descriptor_not_string
551    ;; Get_Descriptor(string[wValueL])
552    ;; if (wValueL >= maxstrings) stall
553    mov a, #((desc_strings_end-desc_strings)/2)
554    clr c
555    subb a,r3 ; a=4, r3 = 0..3 . if a<=0 then stall
556    jc setup_stall
557    jz setup_stall
558    mov a, r3
559    add a, r3 ; a = 2*wValueL
560    mov dptr, #desc_strings
561    add a, dpl
562    mov dpl, a
563    mov a, #0
564    addc a, dph
565    mov dph, a ; dph = desc_strings[a]. big endian! (handy)
566    ;; it looks like my adapter uses a revision of the EZUSB that
567    ;; contains "rev D errata number 8", as hinted in the EzUSB example
568    ;; code. I cannot find an actual errata description on the Cypress
569    ;; web site, but from the example code it looks like this bug causes
570    ;; the length of string descriptors to be read incorrectly, possibly
571    ;; sending back more characters than the descriptor has. The workaround
572    ;; is to manually send out all of the data. The consequence of not
573    ;; using the workaround is that the strings gathered by the kernel
574    ;; driver are too long and are filled with trailing garbage (including
575    ;; leftover strings). Writing this out by hand is a nuisance, so for
576    ;; now I will just live with the bug.
577    movx a, @dptr
578    mov r1, a
579    inc dptr
580    movx a, @dptr
581    mov r2, a
582    mov dptr, SUDPTRH
583    mov a, r1
584    movx @dptr, a
585    mov dptr, SUDPTRL
586    mov a, r2
587    movx @dptr, a
588    ;; done
589    ljmp setup_done_ack
590    
591setup_Get_Descriptor_not_string:
592    ljmp setup_stall
593    
594setup_breq_not_06:
595    cjne r2, #0x08, setup_breq_not_08
596    ;; Get_Configuration. always 1. return one byte.
597    ;; this is reusable
598    mov a, #1
599setup_return_one_byte:
600    mov dptr, IN0BUF
601    movx @dptr, a
602    mov a, #1
603    mov dptr, IN0BC
604    movx @dptr, a
605    ljmp setup_done_ack
606setup_breq_not_08:
607    cjne r2, #0x09, setup_breq_not_09
608    ;; 09: Set_Configuration. ignored.
609    ljmp setup_done_ack
610setup_breq_not_09:
611    cjne r2, #0x0a, setup_breq_not_0a
612    ;; 0a: Get_Interface. get the current altsetting for int[wIndexL]
613    ;; since we only have one interface, ignore wIndexL, return a 0
614    mov a, #0
615    ljmp setup_return_one_byte
616setup_breq_not_0a:
617    cjne r2, #0x0b, setup_breq_not_0b
618    ;; 0b: Set_Interface. set altsetting for interface[wIndexL]. ignored
619    ljmp setup_done_ack
620setup_breq_not_0b:
621    ljmp setup_stall
622
623        
624setup_done_ack:
625    ;; now clear HSNAK
626    mov dptr, EP0CS
627    mov a, #0x02
628    movx @dptr, a
629    sjmp setup_done
630setup_stall:
631    ;; unhandled. STALL
632    ;EP0CS |= bmEPSTALL
633    mov dptr, EP0CS
634    movx a, @dptr
635    orl a, EP0STALLbit
636    movx @dptr, a
637    sjmp setup_done
638    
639setup_done:
640    pop acc
641    pop dph1
642    pop dpl1
643    pop dph
644    pop dpl
645    pop dps
646    reti
647
648;;; ==============================================================
649    
650set_baud: ; baud index in r3
651    ;; verify a < 10
652    mov a, r3
653    jb ACC.7, set_baud__badbaud
654    clr c
655    subb a, #10
656    jnc set_baud__badbaud
657    mov a, r3
658    rl a ; a = index*2
659    add a, #LOW(baud_table)
660    mov dpl, a
661    mov a, #HIGH(baud_table)
662    addc a, #0
663    mov dph, a
664    ;; TODO: shut down xmit/receive
665    ;; TODO: wait for current xmit char to leave
666    ;; TODO: shut down timer to avoid partial-char glitch
667    movx a,@dptr ; BAUD_HIGH
668    mov RCAP2H, a
669    mov TH2, a
670    inc dptr
671    movx a,@dptr ; BAUD_LOW
672    mov RCAP2L, a
673    mov TL2, a
674    ;; TODO: restart xmit/receive
675    ;; TODO: reenable interrupts, resume tx if pending
676    clr c ; c=0: success
677    ret
678set_baud__badbaud:
679    setb c ; c=1: failure
680    ret
681    
682;;; ==================================================
683control_pins:
684    cjne r1, #0x41, control_pins_in
685control_pins_out:
686    mov a, r3 ; wValue[0] holds new bits: b7 is new DTR, b2 is new RTS
687    xrl a, #0xff ; 1 means active, 0V, +12V ?
688    anl a, #0x84
689    mov r3, a
690    mov dptr, OUTC
691    movx a, @dptr ; only change bits 7 and 2
692    anl a, #0x7b ; ~0x84
693    orl a, r3
694    movx @dptr, a ; other pins are inputs, bits ignored
695    ljmp setup_done_ack
696control_pins_in:
697    mov dptr, PINSC
698    movx a, @dptr
699    xrl a, #0xff
700    ljmp setup_return_one_byte
701
702;;; ========================================
703    
704ISR_Ep2in:
705    push dps
706    push dpl
707    push dph
708    push dpl1
709    push dph1
710    push acc
711    mov a,EXIF
712    clr acc.4
713    mov EXIF,a ; clear INT2 first
714    mov dptr, IN07IRQ ; clear USB int
715    mov a,#04h
716    movx @dptr,a
717
718    ;; do stuff
719    lcall start_in
720    
721    pop acc
722    pop dph1
723    pop dpl1
724    pop dph
725    pop dpl
726    pop dps
727    reti
728
729ISR_Ep2out:
730    push dps
731    push dpl
732    push dph
733    push dpl1
734    push dph1
735    push acc
736    mov a,EXIF
737    clr acc.4
738    mov EXIF,a ; clear INT2 first
739    mov dptr, OUT07IRQ ; clear USB int
740    mov a,#04h
741    movx @dptr,a
742
743    ;; do stuff
744
745    ;; copy data into buffer. for now, assume we will have enough space
746    mov dptr, OUT2BC ; get byte count
747    movx a,@dptr
748    mov r1, a
749    clr a
750    mov dps, a
751    mov dptr, OUT2BUF ; load DPTR0 with source
752    mov dph1, #HIGH(tx_ring) ; load DPTR1 with target
753    mov dpl1, tx_ring_in
754OUT_loop:
755    movx a,@dptr ; read
756    inc dps ; switch to DPTR1: target
757    inc dpl1 ; target = tx_ring_in+1
758    movx @dptr,a ; store
759    mov a,dpl1
760    cjne a, tx_ring_out, OUT_no_overflow
761    sjmp OUT_overflow
762OUT_no_overflow:
763    inc tx_ring_in ; tx_ring_in++
764    inc dps ; switch to DPTR0: source
765    inc dptr
766    djnz r1, OUT_loop
767    sjmp OUT_done
768OUT_overflow:
769    ;; signal overflow
770    ;; fall through
771OUT_done:
772    ;; ack
773    mov dptr,OUT2BC
774    movx @dptr,a
775
776    ;; start tx
777    acall maybe_start_tx
778    ;acall dump_stat
779    
780    pop acc
781    pop dph1
782    pop dpl1
783    pop dph
784    pop dpl
785    pop dps
786    reti
787
788dump_stat:
789    ;; fill in EP4in with a debugging message:
790    ;; tx_ring_in, tx_ring_out, rx_ring_in, rx_ring_out
791    ;; tx_active
792    ;; tx_ring[0..15]
793    ;; 0xfc
794    ;; rx_ring[0..15]
795    clr a
796    mov dps, a
797    
798    mov dptr, IN4CS
799    movx a, @dptr
800    jb acc.1, dump_stat__done; busy: cannot dump, old one still pending
801    mov dptr, IN4BUF
802    
803    mov a, tx_ring_in
804    movx @dptr, a
805    inc dptr
806    mov a, tx_ring_out
807    movx @dptr, a
808    inc dptr
809
810    mov a, rx_ring_in
811    movx @dptr, a
812    inc dptr
813    mov a, rx_ring_out
814    movx @dptr, a
815    inc dptr
816    
817    clr a
818    jnb TX_RUNNING, dump_stat__no_tx_running
819    inc a
820dump_stat__no_tx_running:
821    movx @dptr, a
822    inc dptr
823    ;; tx_ring[0..15]
824    inc dps
825    mov dptr, #tx_ring ; DPTR1: source
826    mov r1, #16
827dump_stat__tx_ring_loop:
828    movx a, @dptr
829    inc dptr
830    inc dps
831    movx @dptr, a
832    inc dptr
833    inc dps
834    djnz r1, dump_stat__tx_ring_loop
835    inc dps
836    
837    mov a, #0xfc
838    movx @dptr, a
839    inc dptr
840    
841    ;; rx_ring[0..15]
842    inc dps
843    mov dptr, #rx_ring ; DPTR1: source
844    mov r1, #16
845dump_stat__rx_ring_loop:
846    movx a, @dptr
847    inc dptr
848    inc dps
849    movx @dptr, a
850    inc dptr
851    inc dps
852    djnz r1, dump_stat__rx_ring_loop
853    
854    ;; now send it
855    clr a
856    mov dps, a
857    mov dptr, IN4BC
858    mov a, #38
859    movx @dptr, a
860dump_stat__done:
861    ret
862        
863;;; ============================================================
864    
865maybe_start_tx:
866    ;; make sure the tx process is running.
867    jb TX_RUNNING, start_tx_done
868start_tx:
869    ;; is there work to be done?
870    mov a, tx_ring_in
871    cjne a,tx_ring_out, start_tx__work
872    ret ; no work
873start_tx__work:
874    ;; tx was not running. send the first character, setup the TI int
875    inc tx_ring_out ; [++tx_ring_out]
876    mov dph, #HIGH(tx_ring)
877    mov dpl, tx_ring_out
878    movx a, @dptr
879    mov sbuf, a
880    setb TX_RUNNING
881start_tx_done:
882    ;; can we unthrottle the host tx process?
883    ;; step 1: do we care?
884    mov a, #0
885    cjne a, tx_unthrottle_threshold, start_tx__maybe_unthrottle_tx
886    ;; nope
887start_tx_really_done:
888    ret
889start_tx__maybe_unthrottle_tx:
890    ;; step 2: is there now room?
891    mov a, tx_ring_out
892    setb c
893    subb a, tx_ring_in
894    ;; a is now write_room. If thresh >= a, we can unthrottle
895    clr c
896    subb a, tx_unthrottle_threshold
897    jc start_tx_really_done ; nope
898    ;; yes, we can unthrottle. remove the threshold and mark a request
899    mov tx_unthrottle_threshold, #0
900    setb DO_TX_UNTHROTTLE
901    ;; prod rx, which will actually send the message when in2 becomes free
902    ljmp start_in
903    
904
905serial_int:
906    push dps
907    push dpl
908    push dph
909    push dpl1
910    push dph1
911    push acc
912    jnb TI, serial_int__not_tx
913    ;; tx finished. send another character if we have one
914    clr TI ; clear int
915    clr TX_RUNNING
916    lcall start_tx
917serial_int__not_tx:
918    jnb RI, serial_int__not_rx
919    lcall get_rx_char
920    clr RI ; clear int
921serial_int__not_rx:
922    ;; return
923    pop acc
924    pop dph1
925    pop dpl1
926    pop dph
927    pop dpl
928    pop dps
929    reti
930
931get_rx_char:
932    mov dph, #HIGH(rx_ring)
933    mov dpl, rx_ring_in
934    inc dpl ; target = rx_ring_in+1
935    mov a, sbuf
936    movx @dptr, a
937    ;; check for overflow before incrementing rx_ring_in
938    mov a, dpl
939    cjne a, rx_ring_out, get_rx_char__no_overflow
940    ;; signal overflow
941    ret
942get_rx_char__no_overflow:
943    inc rx_ring_in
944    ;; kick off USB INpipe
945    acall start_in
946    ret
947
948start_in:
949    ;; check if the inpipe is already running.
950    mov dptr, IN2CS
951    movx a, @dptr
952    jb acc.1, start_in__done; int will handle it
953    jb DO_TX_UNTHROTTLE, start_in__do_tx_unthrottle
954    ;; see if there is any work to do. a serial interrupt might occur
955    ;; during this sequence?
956    mov a, rx_ring_in
957    cjne a, rx_ring_out, start_in__have_work
958    ret ; nope
959start_in__have_work:
960    ;; now copy as much data as possible into the pipe. 63 bytes max.
961    clr a
962    mov dps, a
963    mov dph, #HIGH(rx_ring) ; load DPTR0 with source
964    inc dps
965    mov dptr, IN2BUF ; load DPTR1 with target
966    movx @dptr, a ; in[0] signals that rest of IN is rx data
967    inc dptr
968    inc dps
969    ;; loop until we run out of data, or we have copied 64 bytes
970    mov r1, #1 ; INbuf size counter
971start_in__loop:
972    mov a, rx_ring_in
973    cjne a, rx_ring_out, start_inlocal_irq_enablell_copying
974    sjmp start_in__kick
975start_inlocal_irq_enablell_copying:
976    inc rx_ring_out
977    mov dpl, rx_ring_out
978    movx a, @dptr
979    inc dps
980    movx @dptr, a ; write into IN buffer
981    inc dptr
982    inc dps
983    inc r1
984    cjne r1, #64, start_in__loop; loop
985start_in__kick:
986    ;; either we ran out of data, or we copied 64 bytes. r1 has byte count
987    ;; kick off IN
988    mov dptr, IN2BC
989    mov a, r1
990    jz start_in__done
991    movx @dptr, a
992    ;; done
993start_in__done:
994    ;acall dump_stat
995    ret
996start_in__do_tx_unthrottle:
997    ;; special sequence: send a tx unthrottle message
998    clr DO_TX_UNTHROTTLE
999    clr a
1000    mov dps, a
1001    mov dptr, IN2BUF
1002    mov a, #1
1003    movx @dptr, a
1004    inc dptr
1005    mov a, #2
1006    movx @dptr, a
1007    mov dptr, IN2BC
1008    movx @dptr, a
1009    ret
1010    
1011putchar:
1012    clr TI
1013    mov SBUF, a
1014putchar_wait:
1015    jnb TI, putchar_wait
1016    clr TI
1017    ret
1018
1019    
1020baud_table: ; baud_high, then baud_low
1021    ;; baud[0]: 110
1022    .byte BAUD_HIGH(110)
1023    .byte BAUD_LOW(110)
1024    ;; baud[1]: 300
1025    .byte BAUD_HIGH(300)
1026    .byte BAUD_LOW(300)
1027    ;; baud[2]: 1200
1028    .byte BAUD_HIGH(1200)
1029    .byte BAUD_LOW(1200)
1030    ;; baud[3]: 2400
1031    .byte BAUD_HIGH(2400)
1032    .byte BAUD_LOW(2400)
1033    ;; baud[4]: 4800
1034    .byte BAUD_HIGH(4800)
1035    .byte BAUD_LOW(4800)
1036    ;; baud[5]: 9600
1037    .byte BAUD_HIGH(9600)
1038    .byte BAUD_LOW(9600)
1039    ;; baud[6]: 19200
1040    .byte BAUD_HIGH(19200)
1041    .byte BAUD_LOW(19200)
1042    ;; baud[7]: 38400
1043    .byte BAUD_HIGH(38400)
1044    .byte BAUD_LOW(38400)
1045    ;; baud[8]: 57600
1046    .byte BAUD_HIGH(57600)
1047    .byte BAUD_LOW(57600)
1048    ;; baud[9]: 115200
1049    .byte BAUD_HIGH(115200)
1050    .byte BAUD_LOW(115200)
1051
1052desc_device:
1053    .byte 0x12, 0x01, 0x00, 0x01, 0xff, 0xff, 0xff, 0x40
1054    .byte 0xcd, 0x06, 0x04, 0x01, 0x89, 0xab, 1, 2, 3, 0x01
1055;;; The "real" device id, which must match the host driver, is that
1056;;; "0xcd 0x06 0x04 0x01" sequence, which is 0x06cd, 0x0104
1057    
1058desc_config1:
1059    .byte 0x09, 0x02, 0x20, 0x00, 0x01, 0x01, 0x00, 0x80, 0x32
1060    .byte 0x09, 0x04, 0x00, 0x00, 0x02, 0xff, 0xff, 0xff, 0x00
1061    .byte 0x07, 0x05, 0x82, 0x03, 0x40, 0x00, 0x01
1062    .byte 0x07, 0x05, 0x02, 0x02, 0x40, 0x00, 0x00
1063
1064desc_strings:
1065    .word string_langids, string_mfg, string_product, string_serial
1066desc_strings_end:
1067
1068string_langids: .byte string_langids_end-string_langids
1069    .byte 3
1070    .word 0
1071string_langids_end:
1072
1073    ;; sigh. These strings are Unicode, meaning UTF16? 2 bytes each. Now
1074    ;; *that* is a pain in the ass to encode. And they are little-endian
1075    ;; too. Use this perl snippet to get the bytecodes:
1076    /* while (<>) {
1077        @c = split(//);
1078        foreach $c (@c) {
1079         printf("0x%02x, 0x00, ", ord($c));
1080        }
1081       }
1082    */
1083
1084string_mfg: .byte string_mfg_end-string_mfg
1085    .byte 3
1086; .byte "ACME usb widgets"
1087    .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, 0x00
1088string_mfg_end:
1089    
1090string_product: .byte string_product_end-string_product
1091    .byte 3
1092; .byte "ACME USB serial widget"
1093    .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, 0x00
1094string_product_end:
1095    
1096string_serial: .byte string_serial_end-string_serial
1097    .byte 3
1098; .byte "47"
1099    .byte 0x34, 0x00, 0x37, 0x00
1100string_serial_end:
1101        
1102;;; ring buffer memory
1103    ;; tx_ring_in+1 is where the next input byte will go
1104    ;; [tx_ring_out] has been sent
1105    ;; if tx_ring_in == tx_ring_out, theres no work to do
1106    ;; there are (tx_ring_in - tx_ring_out) chars to be written
1107    ;; dont let _in lap _out
1108    ;; cannot inc if tx_ring_in+1 == tx_ring_out
1109    ;; write [tx_ring_in+1] then tx_ring_in++
1110    ;; if (tx_ring_in+1 == tx_ring_out), overflow
1111    ;; else tx_ring_in++
1112    ;; read/send [tx_ring_out+1], then tx_ring_out++
1113
1114    ;; rx_ring_in works the same way
1115    
1116    .org 0x1000
1117tx_ring:
1118    .skip 0x100 ; 256 bytes
1119rx_ring:
1120    .skip 0x100 ; 256 bytes
1121    
1122    
1123    .END
1124    
1125

Archive Download this file



interactive