• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 PORTBCFG #0x7f94
154#define PORTCCFG #0x7f95
155#define OEA	#0x7f9c
156#define IN07IRQ #0x7fa9
157#define OUT07IRQ #0x7faa
158#define IN07IEN #0x7fac
159#define OUT07IEN #0x7fad
160#define USBIRQ #0x7fab
161#define USBIEN #0x7fae
162#define USBBAV #0x7faf
163#define USBCS #0x7fd6
164#define SUDPTRH #0x7fd4
165#define SUDPTRL #0x7fd5
166#define SETUPDAT #0x7fe8
167
168	;; usb interrupt : enable is EIE.0 (0xe8), flag is EXIF.4 (0x91)
169
170	.org 0
171	ljmp start
172	;; interrupt vectors
173	.org 23H
174	ljmp serial_int
175	.byte 0
176
177	.org 43H
178	ljmp USB_Jump_Table
179	.byte 0			; filled in by the USB core
180
181;;; local variables. These are not initialized properly: do it by hand.
182	.org 30H
183rx_ring_in:	.byte 0
184rx_ring_out:	.byte 0
185tx_ring_in:	.byte 0
186tx_ring_out:	.byte 0
187tx_unthrottle_threshold:	.byte 0
188
189	.org 0x100H		; wants to be on a page boundary
190USB_Jump_Table:
191	ljmp	ISR_Sudav	; Setup Data Available
192	.byte 0
193	ljmp	0		; Start of Frame
194	.byte 0
195	ljmp	0		; Setup Data Loading
196	.byte 0
197	ljmp	0		; Global Suspend
198	.byte 	0
199	ljmp	0		; USB Reset
200	.byte	0
201	ljmp	0		; Reserved
202	.byte	0
203	ljmp	0		; End Point 0 In
204	.byte	0
205	ljmp	0		; End Point 0 Out
206	.byte	0
207	ljmp	0		; End Point 1 In
208	.byte	0
209	ljmp	0		; End Point 1 Out
210	.byte	0
211	ljmp	ISR_Ep2in
212	.byte	0
213	ljmp	ISR_Ep2out
214	.byte	0
215
216
217	.org 0x200
218
219start:	mov SP,STACK-1 ; set stack
220	;; clear local variables
221	clr a
222	mov tx_ring_in, a
223	mov tx_ring_out, a
224	mov rx_ring_in, a
225	mov rx_ring_out, a
226	mov tx_unthrottle_threshold, a
227	clr TX_RUNNING
228	clr DO_TX_UNTHROTTLE
229
230	;; clear fifo with "fe"
231	mov r1, 0
232	mov a, #0xfe
233	mov dptr, #tx_ring
234clear_tx_ring_loop:
235	movx @dptr, a
236	inc dptr
237	djnz r1, clear_tx_ring_loop
238
239	mov a, #0xfd
240	mov dptr, #rx_ring
241clear_rx_ring_loop:
242	movx @dptr, a
243	inc dptr
244	djnz r1, clear_rx_ring_loop
245
246;;; turn on the RS-232 driver chip (bring the STANDBY pin low)
247;;; on Xircom the STANDBY is wired to PB6 and PC4
248	mov dptr, PORTBCFG
249        mov a, #0xBf
250        movx @dptr, a
251	mov dptr, PORTCCFG
252        mov a, #0xef
253        movx @dptr, a
254
255	;; set OEC.4
256        mov a, #0x10
257        mov dptr,OEC
258        movx @dptr,a
259
260        ;; clear PC4
261        mov a, #0x00
262        mov dptr,OUTC
263        movx @dptr,a
264
265	;; set OEB.6
266	mov a, #0x40
267	mov dptr,OEB
268	movx @dptr,a
269
270	;; clear PB6
271	mov a, #0x00
272	mov dptr,OUTB
273	movx @dptr,a
274
275	;; set OEC.[17]
276	mov a, #0x82
277	mov dptr,OEC
278	movx @dptr,a
279
280
281	;; set PORTCCFG.[01] to route TxD0,RxD0 to serial port
282	mov dptr, PORTCCFG
283	mov a, #0x03
284	movx @dptr, a
285
286	;; set up interrupts, autovectoring
287	;; set BKPT
288	mov dptr, USBBAV
289	movx a,@dptr
290	setb acc.0		; AVEN bit to 0
291	movx @dptr, a
292
293	mov a,#0x01		; enable SUDAV:	setup data available (for ep0)
294	mov dptr, USBIRQ
295	movx @dptr, a		; clear SUDAVI
296	mov dptr, USBIEN
297	movx @dptr, a
298
299	mov dptr, IN07IEN
300	mov a,#0x04		; enable IN2 int
301	movx @dptr, a
302
303	mov dptr, OUT07IEN
304	mov a,#0x04		; enable OUT2 int
305	movx @dptr, a
306	mov dptr, OUT2BC
307	movx @dptr, a		; arm OUT2
308
309;;	mov a, #0x84		; turn on RTS, DTR
310;;	mov dptr,OUTC
311;;	movx @dptr, a
312
313	mov a, #0x7             ; turn on  DTR
314        mov dptr,USBBAV
315        movx @dptr, a
316
317	mov a, #0x20             ; turn on the RED led
318        mov dptr,OEA
319        movx @dptr, a
320
321	mov a, #0x80            ; turn on  RTS
322        mov dptr,OUTC
323        movx @dptr, a
324
325	;; setup the serial port. 9600 8N1.
326	mov a,#0x53		; mode 1, enable rx, clear int
327	mov SCON, a
328	;;  using timer2, in 16-bit baud-rate-generator mode
329	;;   (xtal 12MHz, internal fosc 24MHz)
330	;;  RCAP2H,RCAP2L = 65536 - fosc/(32*baud)
331	;;  57600: 0xFFF2.F, say 0xFFF3
332	;;   9600: 0xFFB1.E, say 0xFFB2
333	;;    300: 0xF63C
334#define BAUD 9600
335#define BAUD_TIMEOUT(rate) (65536 - (24 * 1000 * 1000) / (32 * rate))
336#define BAUD_HIGH(rate) HIGH(BAUD_TIMEOUT(rate))
337#define BAUD_LOW(rate) LOW(BAUD_TIMEOUT(rate))
338
339	mov T2CON, #030h	; rclk=1,tclk=1,cp=0,tr2=0(enable later)
340	mov r3, #5
341	acall set_baud
342	setb TR2
343	mov SCON, #050h
344
345#if 0
346	mov r1, #0x40
347	mov a, #0x41
348send:
349	mov SBUF, a
350	inc a
351	anl a, #0x3F
352	orl a, #0x40
353;	xrl a, #0x02
354wait1:
355	jnb TI, wait1
356	clr TI
357	djnz r1, send
358;done:	sjmp done
359
360#endif
361
362	setb EUSB
363	setb EA
364	setb ES0
365	;acall dump_stat
366
367	;; hey, what say we RENUMERATE! (TRM p.62)
368	mov a, #0
369	mov dps, a
370	mov dptr, USBCS
371	mov a, #0x02		; DISCON=0, DISCOE=0, RENUM=1
372	movx @dptr, a
373	;; now presence pin is floating, simulating disconnect. wait 0.5s
374	mov r1, #46
375renum_wait1:
376	mov r2, #0
377renum_wait2:
378	mov r3, #0
379renum_wait3:
380	djnz r3, renum_wait3
381	djnz r2, renum_wait2
382	djnz r1, renum_wait1	; wait about n*(256^2) 6MHz clocks
383	mov a, #0x06		; DISCON=0, DISCOE=1, RENUM=1
384	movx @dptr, a
385	;; we are back online. the host device will now re-query us
386
387
388main:	sjmp main
389
390
391
392ISR_Sudav:
393	push dps
394	push dpl
395	push dph
396	push dpl1
397	push dph1
398	push acc
399	mov a,EXIF
400	clr acc.4
401	mov EXIF,a		; clear INT2 first
402	mov dptr, USBIRQ	; clear USB int
403	mov a,#01h
404	movx @dptr,a
405
406	;; get request type
407	mov dptr, SETUPDAT
408	movx a, @dptr
409	mov r1, a		; r1 = bmRequestType
410	inc dptr
411	movx a, @dptr
412	mov r2, a		; r2 = bRequest
413	inc dptr
414	movx a, @dptr
415	mov r3, a		; r3 = wValueL
416	inc dptr
417	movx a, @dptr
418	mov r4, a		; r4 = wValueH
419
420	;; main switch on bmRequest.type: standard or vendor
421	mov a, r1
422	anl a, #0x60
423	cjne a, #0x00, setup_bmreq_type_not_standard
424	;; standard request: now main switch is on bRequest
425	ljmp setup_bmreq_is_standard
426
427setup_bmreq_type_not_standard:
428	;; a still has bmreq&0x60
429	cjne a, #0x40, setup_bmreq_type_not_vendor
430	;; Anchor reserves bRequest 0xa0-0xaf, we use small ones
431	;; switch on bRequest. bmRequest will always be 0x41 or 0xc1
432	cjne r2, #0x00, setup_ctrl_not_00
433	;; 00 is set baud, wValue[0] has baud rate index
434	lcall set_baud		; index in r3, carry set if error
435	jc setup_bmreq_type_not_standard__do_stall
436	ljmp setup_done_ack
437setup_bmreq_type_not_standard__do_stall:
438	ljmp setup_stall
439setup_ctrl_not_00:
440	cjne r2, #0x01, setup_ctrl_not_01
441	;; 01 is reserved for set bits (parity). TODO
442	ljmp setup_stall
443setup_ctrl_not_01:
444	cjne r2, #0x02, setup_ctrl_not_02
445	;; 02 is set HW flow control. TODO
446	ljmp setup_stall
447setup_ctrl_not_02:
448	cjne r2, #0x03, setup_ctrl_not_03
449	;; 03 is control pins (RTS, DTR).
450	ljmp control_pins	; will jump to setup_done_ack,
451				;  or setup_return_one_byte
452setup_ctrl_not_03:
453	cjne r2, #0x04, setup_ctrl_not_04
454	;; 04 is send break (really "turn break on/off"). TODO
455	cjne r3, #0x00, setup_ctrl_do_break_on
456	;; do break off: restore PORTCCFG.1 to reconnect TxD0 to serial port
457	mov dptr, PORTCCFG
458	movx a, @dptr
459	orl a, #0x02
460	movx @dptr, a
461	ljmp setup_done_ack
462setup_ctrl_do_break_on:
463	;; do break on: clear PORTCCFG.0, set TxD high(?) (b1 low)
464	mov dptr, OUTC
465	movx a, @dptr
466	anl a, #0xfd		; ~0x02
467	movx @dptr, a
468	mov dptr, PORTCCFG
469	movx a, @dptr
470	anl a, #0xfd		; ~0x02
471	movx @dptr, a
472	ljmp setup_done_ack
473setup_ctrl_not_04:
474	cjne r2, #0x05, setup_ctrl_not_05
475	;; 05 is set desired interrupt bitmap. TODO
476	ljmp setup_stall
477setup_ctrl_not_05:
478	cjne r2, #0x06, setup_ctrl_not_06
479	;; 06 is query room
480	cjne r3, #0x00, setup_ctrl_06_not_00
481	;; 06, wValue[0]=0 is query write_room
482	mov a, tx_ring_out
483	setb c
484	subb a, tx_ring_in	; out-1-in = 255 - (in-out)
485	ljmp setup_return_one_byte
486setup_ctrl_06_not_00:
487	cjne r3, #0x01, setup_ctrl_06_not_01
488	;; 06, wValue[0]=1 is query chars_in_buffer
489	mov a, tx_ring_in
490	clr c
491	subb a, tx_ring_out	; in-out
492	ljmp setup_return_one_byte
493setup_ctrl_06_not_01:
494	ljmp setup_stall
495setup_ctrl_not_06:
496	cjne r2, #0x07, setup_ctrl_not_07
497	;; 07 is request tx unthrottle interrupt
498	mov tx_unthrottle_threshold, r3; wValue[0] is threshold value
499	ljmp setup_done_ack
500setup_ctrl_not_07:
501	ljmp setup_stall
502
503setup_bmreq_type_not_vendor:
504	ljmp setup_stall
505
506
507setup_bmreq_is_standard:
508	cjne r2, #0x00, setup_breq_not_00
509	;; 00:	Get_Status (sub-switch on bmRequestType: device, ep, int)
510	cjne r1, #0x80, setup_Get_Status_not_device
511	;; Get_Status(device)
512	;;  are we self-powered? no. can we do remote wakeup? no
513	;;   so return two zero bytes. This is reusable
514setup_return_two_zero_bytes:
515	mov dptr, IN0BUF
516	clr a
517	movx @dptr, a
518	inc dptr
519	movx @dptr, a
520	mov dptr, IN0BC
521	mov a, #2
522	movx @dptr, a
523	ljmp setup_done_ack
524setup_Get_Status_not_device:
525	cjne r1, #0x82, setup_Get_Status_not_endpoint
526	;; Get_Status(endpoint)
527	;;  must get stall bit for ep[wIndexL], return two bytes, bit in lsb 0
528	;; for now: cheat. TODO
529	sjmp setup_return_two_zero_bytes
530setup_Get_Status_not_endpoint:
531	cjne r1, #0x81, setup_Get_Status_not_interface
532	;; Get_Status(interface): return two zeros
533	sjmp setup_return_two_zero_bytes
534setup_Get_Status_not_interface:
535	ljmp setup_stall
536
537setup_breq_not_00:
538	cjne r2, #0x01, setup_breq_not_01
539	;; 01:	Clear_Feature (sub-switch on wValueL: stall, remote wakeup)
540	cjne r3, #0x00, setup_Clear_Feature_not_stall
541	;; Clear_Feature(stall). should clear a stall bit. TODO
542	ljmp setup_stall
543setup_Clear_Feature_not_stall:
544	cjne r3, #0x01, setup_Clear_Feature_not_rwake
545	;; Clear_Feature(remote wakeup). ignored.
546	ljmp setup_done_ack
547setup_Clear_Feature_not_rwake:
548	ljmp setup_stall
549
550setup_breq_not_01:
551	cjne r2, #0x03, setup_breq_not_03
552	;; 03:	Set_Feature (sub-switch on wValueL: stall, remote wakeup)
553	cjne r3, #0x00, setup_Set_Feature_not_stall
554	;; Set_Feature(stall). Should set a stall bit. TODO
555	ljmp setup_stall
556setup_Set_Feature_not_stall:
557	cjne r3, #0x01, setup_Set_Feature_not_rwake
558	;; Set_Feature(remote wakeup). ignored.
559	ljmp setup_done_ack
560setup_Set_Feature_not_rwake:
561	ljmp setup_stall
562
563setup_breq_not_03:
564	cjne r2, #0x06, setup_breq_not_06
565	;; 06:	Get_Descriptor (s-switch on wValueH: dev, config[n], string[n])
566	cjne r4, #0x01, setup_Get_Descriptor_not_device
567	;; Get_Descriptor(device)
568	mov dptr, SUDPTRH
569	mov a, #HIGH(desc_device)
570	movx @dptr, a
571	mov dptr, SUDPTRL
572	mov a, #LOW(desc_device)
573	movx @dptr, a
574	ljmp setup_done_ack
575setup_Get_Descriptor_not_device:
576	cjne r4, #0x02, setup_Get_Descriptor_not_config
577	;; Get_Descriptor(config[n])
578	cjne r3, #0x00, setup_stall; only handle n==0
579	;; Get_Descriptor(config[0])
580	mov dptr, SUDPTRH
581	mov a, #HIGH(desc_config1)
582	movx @dptr, a
583	mov dptr, SUDPTRL
584	mov a, #LOW(desc_config1)
585	movx @dptr, a
586	ljmp setup_done_ack
587setup_Get_Descriptor_not_config:
588	cjne r4, #0x03, setup_Get_Descriptor_not_string
589	;; Get_Descriptor(string[wValueL])
590	;;  if (wValueL >= maxstrings) stall
591	mov a, #((desc_strings_end-desc_strings)/2)
592	clr c
593	subb a,r3		; a=4, r3 = 0..3 . if a<=0 then stall
594	jc  setup_stall
595	jz  setup_stall
596	mov a, r3
597	add a, r3		; a = 2*wValueL
598	mov dptr, #desc_strings
599	add a, dpl
600	mov dpl, a
601	mov a, #0
602	addc a, dph
603	mov dph, a		; dph = desc_strings[a]. big endian! (handy)
604	;; it looks like my adapter uses a revision of the EZUSB that
605	;; contains "rev D errata number 8", as hinted in the EzUSB example
606	;; code. I cannot find an actual errata description on the Cypress
607	;; web site, but from the example code it looks like this bug causes
608	;; the length of string descriptors to be read incorrectly, possibly
609	;; sending back more characters than the descriptor has. The workaround
610	;; is to manually send out all of the data. The consequence of not
611	;; using the workaround is that the strings gathered by the kernel
612	;; driver are too long and are filled with trailing garbage (including
613	;; leftover strings). Writing this out by hand is a nuisance, so for
614	;; now I will just live with the bug.
615	movx a, @dptr
616	mov r1, a
617	inc dptr
618	movx a, @dptr
619	mov r2, a
620	mov dptr, SUDPTRH
621	mov a, r1
622	movx @dptr, a
623	mov dptr, SUDPTRL
624	mov a, r2
625	movx @dptr, a
626	;; done
627	ljmp setup_done_ack
628
629setup_Get_Descriptor_not_string:
630	ljmp setup_stall
631
632setup_breq_not_06:
633	cjne r2, #0x08, setup_breq_not_08
634	;; Get_Configuration. always 1. return one byte.
635	;; this is reusable
636	mov a, #1
637setup_return_one_byte:
638	mov dptr, IN0BUF
639	movx @dptr, a
640	mov a, #1
641	mov dptr, IN0BC
642	movx @dptr, a
643	ljmp setup_done_ack
644setup_breq_not_08:
645	cjne r2, #0x09, setup_breq_not_09
646	;; 09: Set_Configuration. ignored.
647	ljmp setup_done_ack
648setup_breq_not_09:
649	cjne r2, #0x0a, setup_breq_not_0a
650	;; 0a: Get_Interface. get the current altsetting for int[wIndexL]
651	;;  since we only have one interface, ignore wIndexL, return a 0
652	mov a, #0
653	ljmp setup_return_one_byte
654setup_breq_not_0a:
655	cjne r2, #0x0b, setup_breq_not_0b
656	;; 0b: Set_Interface. set altsetting for interface[wIndexL]. ignored
657	ljmp setup_done_ack
658setup_breq_not_0b:
659	ljmp setup_stall
660
661
662setup_done_ack:
663	;; now clear HSNAK
664	mov dptr, EP0CS
665	mov a, #0x02
666	movx @dptr, a
667	sjmp setup_done
668setup_stall:
669	;; unhandled. STALL
670	;EP0CS |= bmEPSTALL
671	mov dptr, EP0CS
672	movx a, @dptr
673	orl a, EP0STALLbit
674	movx @dptr, a
675	sjmp setup_done
676
677setup_done:
678	pop acc
679	pop dph1
680	pop dpl1
681	pop dph
682	pop dpl
683	pop dps
684	reti
685
686;;; ==============================================================
687
688set_baud:			; baud index in r3
689	;; verify a < 10
690	mov a, r3
691	jb ACC.7, set_baud__badbaud
692	clr c
693	subb a, #10
694	jnc set_baud__badbaud
695	mov a, r3
696	rl a			; a = index*2
697	add a, #LOW(baud_table)
698	mov dpl, a
699	mov a, #HIGH(baud_table)
700	addc a, #0
701	mov dph, a
702	;; TODO: shut down xmit/receive
703	;; TODO: wait for current xmit char to leave
704	;; TODO: shut down timer to avoid partial-char glitch
705	movx a,@dptr		; BAUD_HIGH
706	mov RCAP2H, a
707	mov TH2, a
708	inc dptr
709	movx a,@dptr		; BAUD_LOW
710	mov RCAP2L, a
711	mov TL2, a
712	;; TODO: restart xmit/receive
713	;; TODO: reenable interrupts, resume tx if pending
714	clr c			; c=0: success
715	ret
716set_baud__badbaud:
717	setb c			; c=1: failure
718	ret
719
720;;; ==================================================
721control_pins:
722	cjne r1, #0x41, control_pins_in
723control_pins_out:
724		;TODO BKPT is DTR
725	mov a, r3 ; wValue[0] holds new bits:	b7 is new RTS
726	xrl a, #0xff		; 1 means active, 0V, +12V ?
727	anl a, #0x80
728	mov r3, a
729	mov dptr, OUTC
730	movx a, @dptr		; only change bit 7
731	anl a, #0x7F		; ~0x84
732	orl a, r3
733	movx @dptr, a		; other pins are inputs, bits ignored
734	ljmp setup_done_ack
735control_pins_in:
736	mov dptr, PINSC
737	movx a, @dptr
738	xrl a, #0xff
739	ljmp setup_return_one_byte
740
741;;; ========================================
742
743ISR_Ep2in:
744	push dps
745	push dpl
746	push dph
747	push dpl1
748	push dph1
749	push acc
750	mov a,EXIF
751	clr acc.4
752	mov EXIF,a		; clear INT2 first
753	mov dptr, IN07IRQ	; clear USB int
754	mov a,#04h
755	movx @dptr,a
756
757	mov a, #0x20             ; Turn off the green LED
758        mov dptr,OEA
759        movx @dptr, a
760
761
762	;; do stuff
763	lcall start_in
764
765	mov a, #0x20             ; Turn off the green LED
766        mov dptr,OEA
767        movx @dptr, a
768
769
770
771	pop acc
772	pop dph1
773	pop dpl1
774	pop dph
775	pop dpl
776	pop dps
777	reti
778
779ISR_Ep2out:
780	push dps
781	push dpl
782	push dph
783	push dpl1
784	push dph1
785	push acc
786
787        mov a, #0x10             ; Turn the green LED
788        mov dptr,OEA
789        movx @dptr, a
790
791
792
793	mov a,EXIF
794	clr acc.4
795	mov EXIF,a		; clear INT2 first
796	mov dptr, OUT07IRQ	; clear USB int
797	mov a,#04h
798	movx @dptr,a
799
800	;; do stuff
801
802	;; copy data into buffer. for now, assume we will have enough space
803	mov dptr, OUT2BC	; get byte count
804	movx a,@dptr
805	mov r1, a
806	clr a
807	mov dps, a
808	mov dptr, OUT2BUF	; load DPTR0 with source
809	mov dph1, #HIGH(tx_ring)	; load DPTR1 with target
810	mov dpl1, tx_ring_in
811OUT_loop:
812	movx a,@dptr		; read
813	inc dps			; switch to DPTR1: target
814	inc dpl1		; target = tx_ring_in+1
815	movx @dptr,a		; store
816	mov a,dpl1
817	cjne a, tx_ring_out, OUT_no_overflow
818	sjmp OUT_overflow
819OUT_no_overflow:
820	inc tx_ring_in		; tx_ring_in++
821	inc dps			; switch to DPTR0: source
822	inc dptr
823	djnz r1, OUT_loop
824	sjmp OUT_done
825OUT_overflow:
826	;; signal overflow
827	;; fall through
828OUT_done:
829	;; ack
830	mov dptr,OUT2BC
831	movx @dptr,a
832
833	;; start tx
834	acall maybe_start_tx
835	;acall dump_stat
836
837        mov a, #0x20             ; Turn off the green LED
838        mov dptr,OEA
839        movx @dptr, a
840
841	pop acc
842	pop dph1
843	pop dpl1
844	pop dph
845	pop dpl
846	pop dps
847	reti
848
849dump_stat:
850	;; fill in EP4in with a debugging message:
851	;;   tx_ring_in, tx_ring_out, rx_ring_in, rx_ring_out
852	;;   tx_active
853	;;   tx_ring[0..15]
854	;;   0xfc
855	;;   rx_ring[0..15]
856	clr a
857	mov dps, a
858
859	mov dptr, IN4CS
860	movx a, @dptr
861	jb acc.1, dump_stat__done; busy: cannot dump, old one still pending
862	mov dptr, IN4BUF
863
864	mov a, tx_ring_in
865	movx @dptr, a
866	inc dptr
867	mov a, tx_ring_out
868	movx @dptr, a
869	inc dptr
870
871	mov a, rx_ring_in
872	movx @dptr, a
873	inc dptr
874	mov a, rx_ring_out
875	movx @dptr, a
876	inc dptr
877
878	clr a
879	jnb TX_RUNNING, dump_stat__no_tx_running
880	inc a
881dump_stat__no_tx_running:
882	movx @dptr, a
883	inc dptr
884	;; tx_ring[0..15]
885	inc dps
886	mov dptr, #tx_ring	; DPTR1: source
887	mov r1, #16
888dump_stat__tx_ring_loop:
889	movx a, @dptr
890	inc dptr
891	inc dps
892	movx @dptr, a
893	inc dptr
894	inc dps
895	djnz r1, dump_stat__tx_ring_loop
896	inc dps
897
898	mov a, #0xfc
899	movx @dptr, a
900	inc dptr
901
902	;; rx_ring[0..15]
903	inc dps
904	mov dptr, #rx_ring	; DPTR1: source
905	mov r1, #16
906dump_stat__rx_ring_loop:
907	movx a, @dptr
908	inc dptr
909	inc dps
910	movx @dptr, a
911	inc dptr
912	inc dps
913	djnz r1, dump_stat__rx_ring_loop
914
915	;; now send it
916	clr a
917	mov dps, a
918	mov dptr, IN4BC
919	mov a, #38
920	movx @dptr, a
921dump_stat__done:
922	ret
923
924;;; ============================================================
925
926maybe_start_tx:
927	;; make sure the tx process is running.
928	jb TX_RUNNING, start_tx_done
929start_tx:
930	;; is there work to be done?
931	mov a, tx_ring_in
932	cjne a,tx_ring_out, start_tx__work
933	ret			; no work
934start_tx__work:
935	;; tx was not running. send the first character, setup the TI int
936	inc tx_ring_out		; [++tx_ring_out]
937	mov dph, #HIGH(tx_ring)
938	mov dpl, tx_ring_out
939	movx a, @dptr
940	mov sbuf, a
941	setb TX_RUNNING
942start_tx_done:
943	;; can we unthrottle the host tx process?
944	;;  step 1: do we care?
945	mov a, #0
946	cjne a, tx_unthrottle_threshold, start_tx__maybe_unthrottle_tx
947	;; nope
948start_tx_really_done:
949	ret
950start_tx__maybe_unthrottle_tx:
951	;;  step 2: is there now room?
952	mov a, tx_ring_out
953	setb c
954	subb a, tx_ring_in
955	;; a is now write_room. If thresh >= a, we can unthrottle
956	clr c
957	subb a, tx_unthrottle_threshold
958	jc start_tx_really_done	; nope
959	;; yes, we can unthrottle. remove the threshold and mark a request
960	mov tx_unthrottle_threshold, #0
961	setb DO_TX_UNTHROTTLE
962	;; prod rx, which will actually send the message when in2 becomes free
963	ljmp start_in
964
965
966serial_int:
967	push dps
968	push dpl
969	push dph
970	push dpl1
971	push dph1
972	push acc
973	jnb TI, serial_int__not_tx
974	;; tx finished. send another character if we have one
975	clr TI			; clear int
976	clr TX_RUNNING
977	lcall start_tx
978serial_int__not_tx:
979	jnb RI, serial_int__not_rx
980	lcall get_rx_char
981	clr RI			; clear int
982serial_int__not_rx:
983	;; return
984	pop acc
985	pop dph1
986	pop dpl1
987	pop dph
988	pop dpl
989	pop dps
990	reti
991
992get_rx_char:
993	mov dph, #HIGH(rx_ring)
994	mov dpl, rx_ring_in
995	inc dpl			; target = rx_ring_in+1
996	mov a, sbuf
997	movx @dptr, a
998	;; check for overflow before incrementing rx_ring_in
999	mov a, dpl
1000	cjne a, rx_ring_out, get_rx_char__no_overflow
1001	;; signal overflow
1002	ret
1003get_rx_char__no_overflow:
1004	inc rx_ring_in
1005	;; kick off USB INpipe
1006	acall start_in
1007	ret
1008
1009start_in:
1010	;; check if the inpipe is already running.
1011	mov  a,#0x10
1012	mov dptr, OEA
1013	movx @dptr,a
1014
1015	mov dptr, IN2CS
1016	movx a, @dptr
1017	jb acc.1, start_in__done; int will handle it
1018	jb DO_TX_UNTHROTTLE, start_in__do_tx_unthrottle
1019	;; see if there is any work to do. a serial interrupt might occur
1020	;; during this sequence?
1021	mov a, rx_ring_in
1022	cjne a, rx_ring_out, start_in__have_work
1023	ret			; nope
1024start_in__have_work:
1025	;; now copy as much data as possible into the pipe. 63 bytes max.
1026	clr a
1027	mov dps, a
1028	mov dph, #HIGH(rx_ring)	; load DPTR0 with source
1029	inc dps
1030	mov dptr, IN2BUF	; load DPTR1 with target
1031	movx @dptr, a		; in[0] signals that rest of IN is rx data
1032	inc dptr
1033	inc dps
1034	;; loop until we run out of data, or we have copied 64 bytes
1035	mov r1, #1		; INbuf size counter
1036start_in__loop:
1037	mov a, rx_ring_in
1038	cjne a, rx_ring_out, start_inlocal_irq_enablell_copying
1039	sjmp start_in__kick
1040start_inlocal_irq_enablell_copying:
1041	inc rx_ring_out
1042	mov dpl, rx_ring_out
1043	movx a, @dptr
1044	inc dps
1045	movx @dptr, a		; write into IN buffer
1046	inc dptr
1047	inc dps
1048	inc r1
1049	cjne r1, #64, start_in__loop; loop
1050start_in__kick:
1051	;; either we ran out of data, or we copied 64 bytes. r1 has byte count
1052	;; kick off IN
1053	mov a, #0x10             ; Turn the green LED
1054        mov dptr,OEA
1055        movx @dptr, a
1056	mov dptr, IN2BC
1057	mov a, r1
1058	jz start_in__done
1059	movx @dptr, a
1060	;; done
1061start_in__done:
1062	;acall dump_stat
1063	ret
1064start_in__do_tx_unthrottle:
1065	;; special sequence: send a tx unthrottle message
1066	clr DO_TX_UNTHROTTLE
1067	clr a
1068	mov dps, a
1069	mov dptr, IN2BUF
1070	mov a, #1
1071	movx @dptr, a
1072	inc dptr
1073	mov a, #2
1074	movx @dptr, a
1075	mov dptr, IN2BC
1076	movx @dptr, a
1077	ret
1078
1079putchar:
1080	clr TI
1081	mov SBUF, a
1082putchar_wait:
1083	jnb TI, putchar_wait
1084	clr TI
1085	ret
1086
1087
1088baud_table:			; baud_high, then baud_low
1089	;; baud[0]: 110
1090	.byte BAUD_HIGH(110)
1091	.byte BAUD_LOW(110)
1092	;; baud[1]: 300
1093	.byte BAUD_HIGH(300)
1094	.byte BAUD_LOW(300)
1095	;; baud[2]: 1200
1096	.byte BAUD_HIGH(1200)
1097	.byte BAUD_LOW(1200)
1098	;; baud[3]: 2400
1099	.byte BAUD_HIGH(2400)
1100	.byte BAUD_LOW(2400)
1101	;; baud[4]: 4800
1102	.byte BAUD_HIGH(4800)
1103	.byte BAUD_LOW(4800)
1104	;; baud[5]: 9600
1105	.byte BAUD_HIGH(9600)
1106	.byte BAUD_LOW(9600)
1107	;; baud[6]: 19200
1108	.byte BAUD_HIGH(19200)
1109	.byte BAUD_LOW(19200)
1110	;; baud[7]: 38400
1111	.byte BAUD_HIGH(38400)
1112	.byte BAUD_LOW(38400)
1113	;; baud[8]: 57600
1114	.byte BAUD_HIGH(57600)
1115	.byte BAUD_LOW(57600)
1116	;; baud[9]: 115200
1117	.byte BAUD_HIGH(115200)
1118	.byte BAUD_LOW(115200)
1119
1120desc_device:
1121	.byte 0x12, 0x01, 0x00, 0x01, 0xff, 0xff, 0xff, 0x40
1122	.byte 0xcd, 0x06, 0x04, 0x01, 0x89, 0xab, 1, 2, 3, 0x01
1123;;; The "real" device id, which must match the host driver, is that
1124;;; "0xcd 0x06 0x04 0x01" sequence, which is 0x06cd, 0x0104
1125
1126desc_config1:
1127	.byte 0x09, 0x02, 0x20, 0x00, 0x01, 0x01, 0x00, 0x80, 0x32
1128	.byte 0x09, 0x04, 0x00, 0x00, 0x02, 0xff, 0xff, 0xff, 0x00
1129	.byte 0x07, 0x05, 0x82, 0x03, 0x40, 0x00, 0x01
1130	.byte 0x07, 0x05, 0x02, 0x02, 0x40, 0x00, 0x00
1131
1132desc_strings:
1133	.word string_langids, string_mfg, string_product, string_serial
1134desc_strings_end:
1135
1136string_langids:	.byte string_langids_end-string_langids
1137	.byte 3
1138	.word 0
1139string_langids_end:
1140
1141	;; sigh. These strings are Unicode, meaning UTF16? 2 bytes each. Now
1142	;; *that* is a pain in the ass to encode. And they are little-endian
1143	;; too. Use this perl snippet to get the bytecodes:
1144	/* while (<>) {
1145	    @c = split(//);
1146	    foreach $c (@c) {
1147	     printf("0x%02x, 0x00, ", ord($c));
1148	    }
1149	   }
1150	*/
1151
1152string_mfg:	.byte string_mfg_end-string_mfg
1153	.byte 3
1154;	.byte "ACME usb widgets"
1155	.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
1156string_mfg_end:
1157
1158string_product:	.byte string_product_end-string_product
1159	.byte 3
1160;	.byte "ACME USB serial widget"
1161	.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
1162string_product_end:
1163
1164string_serial:	.byte string_serial_end-string_serial
1165	.byte 3
1166;	.byte "47"
1167	.byte 0x34, 0x00, 0x37, 0x00
1168string_serial_end:
1169
1170;;; ring buffer memory
1171	;; tx_ring_in+1 is where the next input byte will go
1172	;; [tx_ring_out] has been sent
1173	;; if tx_ring_in == tx_ring_out, theres no work to do
1174	;; there are (tx_ring_in - tx_ring_out) chars to be written
1175	;; dont let _in lap _out
1176	;;   cannot inc if tx_ring_in+1 == tx_ring_out
1177	;;  write [tx_ring_in+1] then tx_ring_in++
1178	;;   if (tx_ring_in+1 == tx_ring_out), overflow
1179	;;   else tx_ring_in++
1180	;;  read/send [tx_ring_out+1], then tx_ring_out++
1181
1182	;; rx_ring_in works the same way
1183
1184	.org 0x1000
1185tx_ring:
1186	.skip 0x100		; 256 bytes
1187rx_ring:
1188	.skip 0x100		; 256 bytes
1189
1190
1191	.END
1192
1193