1 /* ************************************************************************* */
2 /* SauthPalm.c                                                               */
3 /* DS Software Authorization Alpha01 API for the HandSpring Visor            */
4 /* 04/03/2000                                                                */
5 /*                                                                           */
6 /* Copyright (C) 2000 Dallas Semiconductor Corporation.                      */
7 /* All rights Reserved. Printed in U.S.A.                                    */
8 /* This software is protected by copyright laws of                           */
9 /* the United States and of foreign countries.                               */
10 /* This software is furnished under a license agreement and/or a             */
11 /* nondisclosure agreement and may only be used or copied in accordance      */
12 /* with the terms of those agreements.                                       */
13 /* The mere transfer of this software does not imply any licenses            */
14 /* of trade secrets, proprietary technology, copyrights, patents,            */
15 /* trademarks, maskwork rights, or any other form of intellectual            */
16 /* property whatsoever. Dallas Semiconductor retains all ownership rights.   */
17 /*                                                                           */
18 /* Additional Notes:                                                         */
19 /*   This code was compiled using CodeWarrior 6 for Palm Computing Platform. */
20 /*   This code was originally derived from SAuth400 in DS1410k and was       */
21 /*     modified to support the C-API for the Java Powered iButton.           */
22 /*   Functions that begin with iB (iBFirst(), ...) can be optimized by       */
23 /*     removing the call overhead of the non-iB versions of these functions  */
24 /*     (first(),...)                                                         */
25 /*                                                                           */
26 /* ************************************************************************* */
27 
28 #include <SystemMgr.h>
29 #include "ownet.h"
30 #include "SauthPalm.h"
31 
32 asm void Sleep6us();
33 asm unsigned long RawInByte ():__D0;
34 asm void RawOutByte (unsigned long p_DataByte:__D0);
35 static uchar Inbyte(void);
36 static void OutByte(uchar p_DataByte);
37 static uchar ToggleOverdrive(void);
38 static void TogglePassthru(void);
39 static void TogglePassthru(void);
40 static uchar EnterPassthru(void);
41 static uchar ExitPassthru(void);
42 static uchar RomSearch(void);
43 uchar DOWReset(void);
44 static uchar DOWBit(uchar tbit);
45 uchar DOWByte(uchar bts);
46 static uchar DS1481Present(void);
47 static uchar CheckOD(void);
48 static uchar DS1481Comm(uchar DRegByte);
49 static uchar CheckBusy();
50 void FastSleep(long Delay);
51 //
52 asm void Sleep6us();
53 asm void SpinUSec(long uSecs:__D0);
54 //
55 static  uchar  SetupOk = FALSE;
56 static  uchar  Passthru = TRUE;
57 static  uchar  FailNext;
58 static  uchar  AccessGood;
59 static  uchar  TimeOut;
60 static  uchar  l0 = 0;
61 static  uchar  RomDta[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
62 static  ushort pa_vals[] = {PALM_EXTERNAL_DS1481, PALM_INTERNAL_DS1481};
63 static  ushort bpa = PALM_EXTERNAL_DS1481;
64 //
65 static uchar g_OverdriveOn = FALSE;
66 static uchar g_PowerOn = FALSE;
67 //
68 int gType = PARALLEL_DS1481;  /* default to the parallel port */
69 //
70 /***********************************************************************
71  *
72  *   read in a byte at visor's CS1 address
73  *
74  ***********************************************************************/
Sleep6us()75 asm void Sleep6us()
76 {
77   rts;
78 }
79 //
80 // These assembly routines gave me a little bit of trouble.
81 // Move.b works fine for RawOutByte but not for RawInByte.
82 // I kept getting the next-to-least significant byte in
83 // RawInByte. I found that Move.l gave me the correct
84 // results for this routine.
85 //
86 /***********************************************************************
87  *
88  *   read in a byte at visor's CS1 address
89  *
90  ***********************************************************************/
RawInByte()91 asm unsigned long RawInByte ():__D0
92 {
93   movea.l  #0x29000000, A0
94   move.l   (A0), d0
95   // result is in d0
96   rts;
97 }
98 /***********************************************************************
99  *
100  *   read in a byte at visor's CS1 address
101  *
102  ***********************************************************************/
InByte(void)103 static uchar InByte (void)
104 {
105   ulong l_Temp = 0;
106 /*
107   if(!g_PowerOn)
108   {
109     RawOutByte(0x080);
110     g_PowerOn = TRUE;
111   }
112 */
113   l_Temp = RawInByte();
114   return (uchar)(bpa? (l_Temp>>4): l_Temp);
115 }
116 /***********************************************************************
117  *
118  *   write in a byte at visor's CS1 address
119  *
120  ***********************************************************************/
121 asm void RawOutByte (unsigned long p_DataByte:__D0)
122 {
123   movea.l  #0x29000000, A0
124   move.b   d0, (A0)
125   rts;
126 }
127 /***********************************************************************
128  *
129  *   write in a byte at visor's CS1 address
130  *
131  ***********************************************************************/
OutByte(uchar p_DataByte)132 static void OutByte(uchar p_DataByte)
133 {
134   if(!g_PowerOn)
135   {
136     RawOutByte(0x080);
137     g_PowerOn = TRUE;
138   }
139 
140   RawOutByte((ulong)((bpa? p_DataByte<<4: p_DataByte)|0x80));
141 }
142 ////////////////////////////////////////////////////////////////////////
143 ////////////////////////////////////////////////////////////////////////
FastSleep(long Delay)144 void FastSleep(long Delay)
145 {
146   Sleep(Delay);
147 }
148 ////////////////////////////////////////////////////////////////////////
Sleep(long p_Milliseconds)149 void Sleep(long p_Milliseconds)
150 {
151   long l_Centiseconds = (p_Milliseconds + 5) / 10;
152 
153   if(l_Centiseconds == 0)
154     l_Centiseconds++;
155 
156   SysTaskDelay(l_Centiseconds);
157 }
158 ////////////////////////////////////////////////////////////////////////
AdapterPowerManagement(uchar p_PowerOn)159 void AdapterPowerManagement(uchar p_PowerOn)
160 {
161   g_PowerOn = (uchar)(p_PowerOn? TRUE: FALSE);
162   RawOutByte((ulong)(p_PowerOn? 0x80: 0));
163 }
164 ////////////////////////////////////////////////////////////////////////
iBKeyOpen(void)165 uchar iBKeyOpen(void)
166 {
167   return keyopen();
168 }
keyopen(void)169 uchar far pascal keyopen(void)
170 {
171   ExitPassthru();
172 
173   if(CheckOD())
174     OverdriveOff();
175 
176   return TRUE;
177 }
178 ////////////////////////////////////////////////////////////////////////
iBKeyClose(void)179 uchar iBKeyClose(void)
180 {
181   return keyclose();
182 }
keyclose(void)183 uchar far pascal keyclose(void)
184 {
185   EnterPassthru();
186   return TRUE;
187 }
188 ////////////////////////////////////////////////////////////////////////
189 //
190 //    No device driver is required for Palm operation, so dowcheck will
191 // simply return TRUE.
192 //
dowcheck(void)193 uchar far pascal dowcheck(void)
194 {
195   return TRUE;
196 }
197 ////////////////////////////////////////////////////////////////////////
TogglePassthru(void)198 static void TogglePassthru(void)
199 {
200   uchar i = 0;
201 
202   for(i = 0; i < 4; i++)
203     ToggleOverdrive();
204 
205   Sleep(20);
206 }
207 ////////////////////////////////////////////////////////////////////////
EnterPassthru(void)208 static uchar EnterPassthru(void)
209 {
210   Passthru = (uchar)!CheckBusy();
211 
212   if(!Passthru)
213   {
214     TogglePassthru();
215     Passthru = (uchar)!CheckBusy();
216   }
217 
218   return Passthru;
219 }
220 ////////////////////////////////////////////////////////////////////////
ExitPassthru(void)221 static uchar ExitPassthru(void)
222 {
223   Passthru = (uchar)!CheckBusy();
224 
225   if(Passthru)
226   {
227     TogglePassthru();
228     Passthru = (uchar)!CheckBusy();
229   }
230 
231   return (uchar)(!Passthru);
232 }
233 ////////////////////////////////////////////////////////////////////////
234 //
235 //    This function sets the base port address.
236 //
iBSetup(uchar pn)237 uchar iBSetup(uchar pn)
238 {
239   return setup(pn);
240 }
setup(uchar pn)241 uchar far pascal setup(uchar pn)
242 {
243 
244   // Initialize global flags
245   SetupOk = FailNext = AccessGood = FALSE;
246 
247   // Reset RomSearch (first, next) algorithm
248   FailNext = FALSE;
249   l0 = 0;
250 
251   // Make sure port number is valid
252   if(pn > 0 && pn < 3)
253   {
254     // This allows all other functions to execute
255     SetupOk = TRUE;
256     // Set base port address
257     bpa = pa_vals[pn - 1];
258   }
259   else
260     bpa = pa_vals[0];  // Set to default in case caller ignores FALSE return
261 
262   // Return result of setup function
263   return SetupOk;
264 }
265 ////////////////////////////////////////////////////////////////////////
266 //
267 //    Find next DOW part on selected LPT port
268 //
iBNext(void)269 uchar iBNext(void)
270 {
271   return next();
272 }
next(void)273 uchar far pascal next(void)
274 {
275   uchar tr;
276 
277   if(SetupOk)
278   {
279     // See if last search found last button
280     if(FailNext)
281     {
282       FailNext = FALSE;
283       // Reset next function
284       l0 = 0;
285     }
286     else while((tr = RomSearch()) != 0)
287     {
288       // See if we should force failure
289       if(tr == 2)
290          FailNext = 1;
291 
292       // Detect short circuit
293       if(!RomDta[0])
294       {
295 
296         return FALSE;
297       }
298 
299       AccessGood = TRUE;
300       return TRUE;
301     }
302   }
303 
304   return FALSE;
305 }
306 ////////////////////////////////////////////////////////////////////////
307 //
308 //    Find first DOW part on specified port
309 //
iBFirst(void)310 uchar iBFirst(void)
311 {
312   return first();
313 }
first(void)314 uchar far pascal first(void)
315 {
316   // Don't force a failure here
317   FailNext = FALSE;
318   // Point Rom Search algorithm to the top
319   l0 = 0;
320 
321   // Go look for the first DOW part on the bus
322   return next();
323 }
324 ////////////////////////////////////////////////////////////////////////
325 //
326 //    Strong access
327 //
iBAccess(void)328 uchar iBAccess(void)
329 {
330   return access();
331 }
access(void)332 uchar far pascal access(void)
333 {
334   uchar i, j;
335 
336   // Assume failure
337   AccessGood = FALSE;
338 
339   // Send reset pulse
340   if(DOWReset())
341   {
342     // ROM search command byte
343     DOWByte(0xF0);
344 
345     // Byte loop
346     for(i = 0; i < 8; i++)
347     {
348       // Bit loop
349       for(j = 0; j < 8; j++)
350       {
351         if(((DOWBit(TRUE) << 1) | DOWBit(TRUE)) == 3)
352           return FALSE;
353 
354         // Send write time slot
355         DOWBit((uchar) ((RomDta[i] >> j) & 1));
356       }
357     }
358 
359     // Success if we made it through all the bits
360     AccessGood = TRUE;
361    }
362 
363    return AccessGood;
364 }
365 ////////////////////////////////////////////////////////////////////////
366 //
367 //    Transmit data to buttons on the selected LPT port
368 //
iBDataByte(uchar data)369 uchar iBDataByte(uchar data)
370 {
371   return databyte(data);
372 }
databyte(uchar br)373 uchar far pascal databyte(uchar br)
374 {
375   return (SetupOk && AccessGood) ? DOWByte(br) : br;
376 }
377 ////////////////////////////////////////////////////////////////////////
gndtest(void)378 uchar far pascal gndtest(void)
379 {
380   return SetupOk;
381 }
382 ////////////////////////////////////////////////////////////////////////
iBROMData(void)383 uchar *iBROMData(void)
384 {
385   return romdata();
386 }
romdata(void)387 uchar far * far pascal romdata(void)
388 {
389   // Return pointer to ROM Data buffer
390   return RomDta;
391 }
392 ////////////////////////////////////////////////////////////////////////
393 //
394 //     This function performs a ROM search and finds one part on the DOW bus
395 // per call.
396 //
397 //      Return values : 0 => No parts on bus or bus error
398 //                      1 => A part was found and more are out there
399 //                      2 => A part was found and it was last one on the bus
400 //
RomSearch(void)401 static uchar RomSearch(void)
402 {
403   uchar i = 0,
404         x = 0,
405         ld = l0;
406   uchar RomBit,
407         Mask;
408 
409   // Reset DOW bus
410   if(DOWReset())
411     DOWByte(0xF0);  // Send search command
412   else
413   {
414     return FALSE;   // No DOW parts were found on bus
415   }
416 
417   // While not done and not bus error
418   while((i++ < 64) && (x < 3))
419   {
420     // Get bit mask
421     Mask = (uchar)(1 << ((i - 1) % 8)) ;
422 
423     // Get last pass bit
424     RomBit = (uchar)(RomDta[(i - 1) >> 3] & Mask ? TRUE : FALSE);
425 
426     // Send read time slots
427     x = (uchar)(DOWBit(TRUE) << 1);
428     x |= DOWBit(TRUE);
429 
430     // Is there a disagreement in this bit position
431     if(!x)
432     {
433       // Stay on old path or pick a new one ?
434       if(i >= ld)
435         RomBit = (uchar)(i == ld);         // Send write 1 if at position of ld
436       // Save this value as temp last disagreement
437       if(!RomBit)
438         l0 = i;
439     }
440     else
441       RomBit = (uchar)((x & 1) ^ 1);          // Get lsb of x and flip it
442 
443     if(RomBit)
444       RomDta[(i - 1) >> 3] |= Mask;          // Set bit in Rom Data byte
445     else
446       RomDta[(i - 1) >> 3] &= (uchar)((Mask ^ 0xFF)); // Clear bit in Rom Data byte
447 
448     // Send write time slot
449     DOWBit(RomBit);
450   }
451 
452   return (uchar)((x == 3) ? 0 : 1 + (ld == l0));
453 }
454 ////////////////////////////////////////////////////////////////////////
DOWReset(void)455 uchar DOWReset(void)
456 {
457   // Sampling a low => presence detected => success
458   return (uchar)(DS1481Comm(RESET) ^ 1);
459 }
460 ////////////////////////////////////////////////////////////////////////
iBDataBit(uchar bit)461 uchar iBDataBit(uchar bit)
462 {
463   return DOWBit(bit);
464 }
DOWBit(uchar tbit)465 static uchar DOWBit(uchar tbit)
466 {
467    // Return sampled value
468   return DS1481Comm((uchar)(tbit ? RWBIT : W0BIT));
469 }
470 ////////////////////////////////////////////////////////////////////////
DOWByte(uchar bts)471 uchar DOWByte(uchar bts)
472 {
473   uchar i,
474         brecv = 0;
475 
476   // Call DOWBit 8 times to transfer entire byte
477   for(i = 0; i < 8; i++)
478   {
479     brecv >>= 1;
480     // Smush bit into byte
481     brecv |= (uchar)(DOWBit((uchar) (bts & 1)) ? 0x80 : 0);
482     bts >>= 1;
483   }
484 
485   // Return result sampled on 1-wire bus
486   return brecv;
487 }
488 ////////////////////////////////////////////////////////////////////////
iBOverdriveOn(void)489 uchar iBOverdriveOn(void)
490 {
491   return OverdriveOn();
492 }
OverdriveOn(void)493 uchar far pascal OverdriveOn(void)
494 {
495 
496   // Comment out code so that it is not done twice...jpe
497 //  if(!DOWReset())
498 //  {
499     //Sleep(500);
500 
501 //    return FALSE;
502 //  }
503 
504   // Put all parts in overdrive mode
505 // DOWByte(0x3C);
506 
507   if(!(g_OverdriveOn = CheckOD()))
508   {
509     ToggleOverdrive();
510     //g_OverdriveOn = CheckOD();
511   }
512 
513   Sleep(20);
514   return g_OverdriveOn;
515 }
516 ////////////////////////////////////////////////////////////////////////
iBOverdriveOff(void)517 uchar iBOverdriveOff(void)
518 {
519   return OverdriveOff();
520 }
OverdriveOff(void)521 uchar far pascal OverdriveOff(void)
522 {
523   // Turn overdrive off
524   if((g_OverdriveOn = CheckOD()))
525   {
526     ToggleOverdrive();
527     //g_OverdriveOn = CheckOD();
528   }
529 
530   Sleep(20);
531   return (uchar)(g_OverdriveOn? FALSE: TRUE);
532 }
533 ////////////////////////////////////////////////////////////////////////
534 //
535 //    Change state of overdrive ff in all 1481s on LPT port.
536 //
ToggleOverdrive(void)537 static uchar ToggleOverdrive(void)
538 {
539 
540   // Disable interrupts
541   DisableInterrupts();
542 
543   // Set initial state
544   OutByte(0xff);
545 
546   // Set initial state  - DClk, Res low
547   OutByte(0xf9);
548 
549   // Drive chip select low
550   OutByte(0xf8);
551 
552   // Drive pin 14 back high again !!!
553   OutByte(0xff);
554 
555   // Allow interrupts back in
556   EnableInterrupts();
557 
558   g_OverdriveOn = (uchar)~g_OverdriveOn;
559   return g_OverdriveOn;
560 }
561 ////////////////////////////////////////////////////////////////////////
DS1481Present(void)562 static uchar DS1481Present(void)
563 {
564   return (uchar)(CheckBusy() == TRUE);
565 }
566 ////////////////////////////////////////////////////////////////////////
DS1481Comm(uchar DRegByte)567 static uchar DS1481Comm(uchar DRegByte)
568 {
569   uchar  DOWRes;
570   ushort MDelay;
571 
572   DisableInterrupts();
573 
574   OutByte(0xff);
575   Sleep6us();
576 
577   // Set initial state of data lines
578   OutByte((uchar)(DRegByte | 1));
579   Sleep6us();
580 
581   // Drive chip select low
582   OutByte((uchar)(DRegByte & 0xfe));
583 
584   // Wait for DS1481 to issue busy signal
585   MDelay = 0;
586 
587   while((InByte() & 1) && (MDelay++ < MAX_W))
588   {}
589 
590   EnableInterrupts()
591 
592    // Now we can reset the state of the data lines
593    OutByte(0xfe);
594 
595    MDelay = 0;
596 
597    while(!(InByte() & 1) && (MDelay++ < MAX_W))
598    {}
599 
600    // Drive clk line low to get our bit
601    OutByte(0xfc);
602 
603    // Get bit result
604    DOWRes = (uchar)((InByte() & 1) ? TRUE : FALSE);
605 
606 
607    // Drive ENI back high again !!!
608    OutByte(0xff);
609    return DOWRes;
610 }
611 ////////////////////////////////////////////////////////////////////////
612 // This function sends a WRITE1 and looks for the busy lines to go low.
613 // If a low is detected the timeslot is aborted and TRUE is returned.
614 //
615 // If the 1481 is in passthrough mode the busy lines will never go low.
616 //
617 // ASSUMPTIONS are all based empirically on Palm Handheld timing.
618 //
CheckBusy()619 static uchar CheckBusy()
620 {
621   uchar Busy;
622   int   i;
623 
624   DisableInterrupts();
625 
626   OutByte(0xff);
627 
628   // Set initial state of data lines
629   OutByte((uchar)(0xf7));
630 
631   // Drive chip select low
632   OutByte((uchar)(0xf6));
633 
634   // Wait for DS1481 to issue busy signal
635   for(i = 0; i < 10; i++)
636   {
637     Busy = (uchar)((InByte() & 1)? FALSE: TRUE);
638 
639     if(Busy)
640       break;
641   }
642 
643   OutByte(0xf4);
644 
645   EnableInterrupts()
646 
647   // Drive pin 14 back high again !!!
648   OutByte(0xff);
649 
650   return Busy;
651 }
652 ////////////////////////////////////////////////////////////////////////
653 // This function sends a WRITE1 and looks for the busy lines to go low.
654 // If a low is detected the timeslot is aborted
655 //
656 // If the 1481 is in overdrive the WRITE1 is short and the second read
657 // should miss the busy signal.
658 //
659 // ASSUMPTIONS are all based empirically on Palm Handheld timing.
660 //
CheckOD()661 static uchar CheckOD()
662 {
663   uchar l_OverDrive;
664   int   i;
665   ushort MDelay;
666 
667   DisableInterrupts();
668 
669   OutByte(0xff);
670 
671   // set initial state of data lines for write 1
672   OutByte((uchar)(0xf7));
673 
674   // drive chip select low
675   OutByte((uchar)(0xf6));
676 
677   // wait for DS1481 to issue busy signal
678   MDelay = 0;
679 
680   while((InByte() & 1) && (MDelay++ < MAX_W))
681   {}
682 
683   // wait for DS1481 to release busy signal
684   // timeslot is on the order of 6 us for OD
685   //  and 60 us for regular speed
686   for(i = 0; i < 2; i++)
687   {
688     Sleep6us();
689     l_OverDrive = (uchar)((InByte() & 1)? TRUE: FALSE);
690 
691     if(l_OverDrive)
692       break;
693   }
694 
695   EnableInterrupts()
696 
697   // Drive pin 14 back high again !!!
698   OutByte(0xff);
699 
700   return l_OverDrive;
701 }
702 ////////////////////////////////////////////////////////////////////////
703 /******************************************************************************
704 /*
705 /*    Block I/O command. Transmit data in blocks of size PAR_BLOCK_SIZE.
706 /* Return received bytes in the same buffer.
707 /*
708 /******************************************************************************/
iBDataBlock(uchar * data,int count)709 uchar iBDataBlock(uchar *data, int count)
710 {
711    int i;
712 
713    /* Byte-bang the thing */
714    for(i = 0; i < count; i++)
715      data[i] = iBDataByte(data[i]);
716 
717    return TRUE;
718 }
719 ////////////////////////////////////////////////////////////////////////
SetAdapterType(uchar Type,char * type)720 uchar SetAdapterType(uchar Type, char *type)
721 {
722   if(Type != DS1410)
723     return FALSE;
724 
725   gType = DS1410;
726   return TRUE;
727 }
728 ////////////////////////////////////////////////////////////////////////
SetAdapterSpeed(ulong speed)729 uchar SetAdapterSpeed(ulong speed)
730 {
731   return TRUE;
732 }
733 ////////////////////////////////////////////////////////////////////////
iBStream(uchar * arr,int length)734 uchar iBStream(uchar *arr,int length)
735 {
736   int i;
737   int AccessGood = FALSE;
738   uchar accessarray[9];
739 
740   if (DOWReset())
741   {
742     AccessGood = TRUE;
743 
744     /* Set up block for transmit */
745     accessarray[0] = 0x55;
746     for (i = 1;i < 9;i++)
747       accessarray[i] = RomDta[i-1];
748     /* Transmit block. */
749     iBDataBlock(accessarray,9);
750     iBDataBlock(arr,length);
751   }
752 
753   return (uchar)AccessGood;
754 }
755 ////////////////////////////////////////////////////////////////////////
iBFastAccess(void)756 uchar iBFastAccess(void)
757 {
758   return iBAccess();
759 }
760 ////////////////////////////////////////////////////////////////////////
iBStrongAccess(void)761 uchar iBStrongAccess(void)
762 {
763    uchar i, j;
764    uchar mask = 0x80;
765    //uchar byte;
766    //uchar bit;
767    //uchar bytesend[24];
768 
769    /* Assume failure  */
770    AccessGood = FALSE;
771 
772    /* Send reset pulse */
773    if (DOWReset())
774    {
775 
776       /* ROM search command byte */
777       DOWByte(0xF0);
778 
779 
780       /* Byte loop */
781       for (i = 0; i < 8; i++)
782       {
783         /* Bit loop */
784         for (j = 0; j < 8; j++)
785         {
786           /* Send two read time slots */
787           if (((DOWBit(TRUE) << 1) | DOWBit(TRUE)) == 3)
788             return FALSE;
789 
790           /* Send write time slot */
791           DOWBit((uchar) ((RomDta[i] >> j) & 1));
792         }
793       }
794 
795       /* Success if we made it through all bits */
796       AccessGood = TRUE;
797    }
798 
799    return AccessGood;
800 }
801 ////////////////////////////////////////////////////////////////////////
SetAdapter5VTime(uchar time)802 uchar SetAdapter5VTime(uchar time)
803 {
804   return TRUE;
805 }
806 ////////////////////////////////////////////////////////////////////////
Adapter5VPrime(void)807 uchar Adapter5VPrime(void)
808 {
809   return TRUE;
810 }
811 ////////////////////////////////////////////////////////////////////////
Adapter5VCancel(void)812 uchar Adapter5VCancel(void)
813 {
814   return TRUE;
815 }
816 ////////////////////////////////////////////////////////////////////////
817 ////////////////////////////////////////////////////////////////////////
818 
819