Embed:
(wiki syntax)
Show/hide line numbers
USBBusInterface_LPC17_LPC23.c
00001 /* USBBusInterface_LPC17_LPC23.c */ 00002 /* USB Bus Interface for NXP LPC1768 and LPC2368 */ 00003 /* Copyright (c) 2011 ARM Limited. All rights reserved. */ 00004 00005 #ifdef TARGET_LPC1768 00006 00007 #include "USBBusInterface.h" 00008 #include "USBEvents.h" 00009 00010 00011 /* Get endpoint direction */ 00012 #define IN_EP(endpoint) ((endpoint) & 1U ? true : false) 00013 #define OUT_EP(endpoint) ((endpoint) & 1U ? false : true) 00014 00015 /* Convert physical endpoint number to register bit */ 00016 #define EP(endpoint) (1UL<<endpoint) 00017 00018 /* Power Control for Peripherals register */ 00019 #define PCUSB (1UL<<31) 00020 00021 /* USB Clock Control register */ 00022 #define DEV_CLK_EN (1UL<<1) 00023 #define AHB_CLK_EN (1UL<<4) 00024 00025 /* USB Clock Status register */ 00026 #define DEV_CLK_ON (1UL<<1) 00027 #define AHB_CLK_ON (1UL<<4) 00028 00029 /* USB Device Interupt registers */ 00030 #define FRAME (1UL<<0) 00031 #define EP_FAST (1UL<<1) 00032 #define EP_SLOW (1UL<<2) 00033 #define DEV_STAT (1UL<<3) 00034 #define CCEMPTY (1UL<<4) 00035 #define CDFULL (1UL<<5) 00036 #define RxENDPKT (1UL<<6) 00037 #define TxENDPKT (1UL<<7) 00038 #define EP_RLZED (1UL<<8) 00039 #define ERR_INT (1UL<<9) 00040 00041 /* USB Control register */ 00042 #define RD_EN (1<<0) 00043 #define WR_EN (1<<1) 00044 #define LOG_ENDPOINT(endpoint) ((endpoint>>1)<<2) 00045 00046 /* USB Receive Packet Length register */ 00047 #define DV (1UL<<10) 00048 #define PKT_RDY (1UL<<11) 00049 #define PKT_LNGTH_MASK (0x3ff) 00050 00051 /* Serial Interface Engine (SIE) */ 00052 #define SIE_WRITE (0x01) 00053 #define SIE_READ (0x02) 00054 #define SIE_COMMAND (0x05) 00055 #define SIE_CMD_CODE(phase, data) ((phase<<8)|(data<<16)) 00056 00057 /* SIE Command codes */ 00058 #define SIE_CMD_SET_ADDRESS (0xD0) 00059 #define SIE_CMD_CONFIGURE_DEVICE (0xD8) 00060 #define SIE_CMD_SET_MODE (0xF3) 00061 #define SIE_CMD_READ_FRAME_NUMBER (0xF5) 00062 #define SIE_CMD_READ_TEST_REGISTER (0xFD) 00063 #define SIE_CMD_SET_DEVICE_STATUS (0xFE) 00064 #define SIE_CMD_GET_DEVICE_STATUS (0xFE) 00065 #define SIE_CMD_GET_ERROR_CODE (0xFF) 00066 #define SIE_CMD_READ_ERROR_STATUS (0xFB) 00067 00068 #define SIE_CMD_SELECT_ENDPOINT(endpoint) (0x00+endpoint) 00069 #define SIE_CMD_SELECT_ENDPOINT_CLEAR_INTERRUPT(endpoint) (0x40+endpoint) 00070 #define SIE_CMD_SET_ENDPOINT_STATUS(endpoint) (0x40+endpoint) 00071 00072 #define SIE_CMD_CLEAR_BUFFER (0xF2) 00073 #define SIE_CMD_VALIDATE_BUFFER (0xFA) 00074 00075 /* SIE Device Status register */ 00076 #define SIE_DS_CON (1<<0) 00077 #define SIE_DS_CON_CH (1<<1) 00078 #define SIE_DS_SUS (1<<2) 00079 #define SIE_DS_SUS_CH (1<<3) 00080 #define SIE_DS_RST (1<<4) 00081 00082 /* SIE Device Set Address register */ 00083 #define SIE_DSA_DEV_EN (1<<7) 00084 00085 /* SIE Configue Device register */ 00086 #define SIE_CONF_DEVICE (1<<0) 00087 00088 /* Select Endpoint register */ 00089 #define SIE_SE_FE (1<<0) 00090 #define SIE_SE_ST (1<<1) 00091 #define SIE_SE_STP (1<<2) 00092 #define SIE_SE_PO (1<<3) 00093 #define SIE_SE_EPN (1<<4) 00094 #define SIE_SE_B_1_FULL (1<<5) 00095 #define SIE_SE_B_2_FULL (1<<6) 00096 00097 /* Set Endpoint Status command */ 00098 #define SIE_SES_ST (1<<0) 00099 #define SIE_SES_DA (1<<5) 00100 #define SIE_SES_RF_MO (1<<6) 00101 #define SIE_SES_CND_ST (1<<7) 00102 00103 static uint32_t endpointStallState; 00104 00105 static int epComplete = 0; 00106 /* 00107 * Serial Interface Engine commands 00108 */ 00109 00110 static void SIECommand(uint32_t command) 00111 { 00112 /* The command phase of a SIE transaction */ 00113 LPC_USB->USBDevIntClr = CCEMPTY; 00114 LPC_USB->USBCmdCode = SIE_CMD_CODE(SIE_COMMAND, command); 00115 while (!(LPC_USB->USBDevIntSt & CCEMPTY)); 00116 } 00117 00118 static void SIEWriteData(uint8_t data) 00119 { 00120 /* The data write phase of a SIE transaction */ 00121 LPC_USB->USBDevIntClr = CCEMPTY; 00122 LPC_USB->USBCmdCode = SIE_CMD_CODE(SIE_WRITE, data); 00123 while (!(LPC_USB->USBDevIntSt & CCEMPTY)); 00124 } 00125 00126 static uint8_t SIEReadData(uint32_t command) 00127 { 00128 /* The data read phase of a SIE transaction */ 00129 LPC_USB->USBDevIntClr = CDFULL; 00130 LPC_USB->USBCmdCode = SIE_CMD_CODE(SIE_READ, command); 00131 while (!(LPC_USB->USBDevIntSt & CDFULL)); 00132 return (uint8_t)LPC_USB->USBCmdData; 00133 } 00134 00135 static void SIEsetDeviceStatus(uint8_t status) 00136 { 00137 /* Write SIE device status register */ 00138 SIECommand(SIE_CMD_SET_DEVICE_STATUS); 00139 SIEWriteData(status); 00140 } 00141 00142 static uint8_t SIEgetDeviceStatus(void) 00143 { 00144 /* Read SIE device status register */ 00145 SIECommand(SIE_CMD_GET_DEVICE_STATUS); 00146 return SIEReadData(SIE_CMD_GET_DEVICE_STATUS); 00147 } 00148 00149 void SIEsetAddress(uint8_t address) 00150 { 00151 /* Write SIE device address register */ 00152 SIECommand(SIE_CMD_SET_ADDRESS); 00153 SIEWriteData((address & 0x7f) | SIE_DSA_DEV_EN); 00154 } 00155 00156 static uint8_t SIEselectEndpoint(uint8_t endpoint) 00157 { 00158 /* SIE select endpoint command */ 00159 SIECommand(SIE_CMD_SELECT_ENDPOINT(endpoint)); 00160 return SIEReadData(SIE_CMD_SELECT_ENDPOINT(endpoint)); 00161 } 00162 00163 static uint8_t SIEclearBuffer(void) 00164 { 00165 /* SIE clear buffer command */ 00166 SIECommand(SIE_CMD_CLEAR_BUFFER); 00167 return SIEReadData(SIE_CMD_CLEAR_BUFFER); 00168 } 00169 00170 static void SIEvalidateBuffer(void) 00171 { 00172 /* SIE validate buffer command */ 00173 SIECommand(SIE_CMD_VALIDATE_BUFFER); 00174 } 00175 00176 static void SIEsetEndpointStatus(uint8_t endpoint, uint8_t status) 00177 { 00178 /* SIE set endpoint status command */ 00179 SIECommand(SIE_CMD_SET_ENDPOINT_STATUS(endpoint)); 00180 SIEWriteData(status); 00181 } 00182 00183 static uint16_t SIEgetFrameNumber(void) __attribute__ ((unused)); 00184 static uint16_t SIEgetFrameNumber(void) 00185 { 00186 /* Read current frame number */ 00187 uint16_t lowByte; 00188 uint16_t highByte; 00189 00190 SIECommand(SIE_CMD_READ_FRAME_NUMBER); 00191 lowByte = SIEReadData(SIE_CMD_READ_FRAME_NUMBER); 00192 highByte = SIEReadData(SIE_CMD_READ_FRAME_NUMBER); 00193 00194 return (highByte << 8) | lowByte; 00195 } 00196 00197 static void SIEconfigureDevice(void) 00198 { 00199 /* SIE Configure device command */ 00200 SIECommand(SIE_CMD_CONFIGURE_DEVICE); 00201 SIEWriteData(SIE_CONF_DEVICE); 00202 } 00203 00204 static void SIEunconfigureDevice(void) 00205 { 00206 /* SIE Configure device command */ 00207 SIECommand(SIE_CMD_CONFIGURE_DEVICE); 00208 SIEWriteData(0); 00209 } 00210 00211 static void SIEconnect(void) 00212 { 00213 /* Connect USB device */ 00214 uint8_t status; 00215 00216 status = SIEgetDeviceStatus(); 00217 SIEsetDeviceStatus(status | SIE_DS_CON); 00218 } 00219 00220 00221 static void SIEdisconnect(void) 00222 { 00223 /* Disconnect USB device */ 00224 uint8_t status; 00225 00226 status = SIEgetDeviceStatus(); 00227 SIEsetDeviceStatus(status & ~SIE_DS_CON); 00228 } 00229 00230 /* 00231 * Endpoint commands 00232 */ 00233 00234 static uint8_t selectEndpointClearInterrupt(uint8_t endpoint) 00235 { 00236 /* Implemented using using EP_INT_CLR. */ 00237 LPC_USB->USBEpIntClr = EP(endpoint); 00238 while (!(LPC_USB->USBDevIntSt & CDFULL)); 00239 return (uint8_t)LPC_USB->USBCmdData; 00240 } 00241 00242 static void stallEndpoint(uint8_t endpoint) 00243 { 00244 /* Stall an endpoint */ 00245 if ( (endpoint==EP0IN) || (endpoint==EP0OUT) ) 00246 { 00247 /* Conditionally stall both control endpoints */ 00248 SIEsetEndpointStatus(EP0OUT, SIE_SES_CND_ST); 00249 } 00250 else 00251 { 00252 SIEsetEndpointStatus(endpoint, SIE_SES_ST); 00253 00254 /* Update stall state */ 00255 endpointStallState |= EP(endpoint); 00256 } 00257 } 00258 00259 static void unstallEndpoint(uint8_t endpoint) 00260 { 00261 /* Unstall an endpoint. The endpoint will also be reinitialised */ 00262 SIEsetEndpointStatus(endpoint, 0); 00263 00264 /* Update stall state */ 00265 endpointStallState &= ~EP(endpoint); 00266 } 00267 00268 static bool getEndpointStallState(uint8_t endpoint) 00269 { 00270 /* Returns true if endpoint stalled */ 00271 return endpointStallState & EP(endpoint); 00272 } 00273 00274 static void realiseEndpoint(uint8_t endpoint, uint32_t maxPacket) 00275 { 00276 /* Realise an endpoint */ 00277 LPC_USB->USBDevIntClr = EP_RLZED; 00278 LPC_USB->USBReEp |= EP(endpoint); 00279 LPC_USB->USBEpInd = endpoint; 00280 LPC_USB->USBMaxPSize = maxPacket; 00281 00282 while (!(LPC_USB->USBDevIntSt & EP_RLZED)); 00283 LPC_USB->USBDevIntClr = EP_RLZED; 00284 00285 /* Clear stall state */ 00286 endpointStallState &= ~EP(endpoint); 00287 } 00288 00289 static void enableEndpointEvent(uint8_t endpoint) 00290 { 00291 /* Enable an endpoint interrupt */ 00292 LPC_USB->USBEpIntEn |= EP(endpoint); 00293 } 00294 00295 static void disableEndpointEvent(uint8_t endpoint) __attribute__ ((unused)); 00296 static void disableEndpointEvent(uint8_t endpoint) 00297 { 00298 /* Disable an endpoint interrupt */ 00299 LPC_USB->USBEpIntEn &= ~EP(endpoint); 00300 } 00301 00302 static volatile uint32_t __attribute__((used)) dummyRead; 00303 00304 static uint32_t endpointRead(uint8_t endpoint, uint8_t *buffer) 00305 { 00306 /* Read from an OUT endpoint */ 00307 uint32_t size; 00308 uint32_t i; 00309 uint32_t data; 00310 uint8_t offset; 00311 00312 LPC_USB->USBCtrl = LOG_ENDPOINT(endpoint) | RD_EN; 00313 while (!(LPC_USB->USBRxPLen & PKT_RDY)); 00314 00315 size = LPC_USB->USBRxPLen & PKT_LNGTH_MASK; 00316 00317 offset = 0; 00318 00319 if (size > 0) 00320 { 00321 for (i=0; i<size; i++) 00322 { 00323 if (offset==0) 00324 { 00325 /* Fetch up to four bytes of data as a word */ 00326 data = LPC_USB->USBRxData; 00327 } 00328 00329 /* extract a byte */ 00330 *buffer++ = data>>offset; 00331 00332 /* move on to the next byte */ 00333 offset = (offset + 8) % 32; 00334 } 00335 } 00336 else 00337 { 00338 dummyRead = LPC_USB->USBRxData; 00339 } 00340 00341 SIEselectEndpoint(endpoint); 00342 SIEclearBuffer(); 00343 return size; 00344 } 00345 00346 static void endpointWrite(uint8_t endpoint, uint8_t *buffer, uint32_t size) 00347 { 00348 /* Write to an IN endpoint */ 00349 uint32_t temp, data; 00350 uint8_t offset; 00351 00352 LPC_USB->USBCtrl = LOG_ENDPOINT(endpoint) | WR_EN; 00353 00354 LPC_USB->USBTxPLen = size; 00355 offset = 0; 00356 data = 0; 00357 00358 if (size>0) 00359 { 00360 do { 00361 /* Fetch next data byte into a word-sized temporary variable */ 00362 temp = *buffer++; 00363 00364 /* Add to current data word */ 00365 temp = temp << offset; 00366 data = data | temp; 00367 00368 /* move on to the next byte */ 00369 offset = (offset + 8) % 32; 00370 size--; 00371 00372 if ((offset==0) || (size==0)) 00373 { 00374 /* Write the word to the endpoint */ 00375 LPC_USB->USBTxData = data; 00376 data = 0; 00377 } 00378 } while (size>0); 00379 } 00380 else 00381 { 00382 LPC_USB->USBTxData = 0; 00383 } 00384 00385 /* Clear WR_EN to cover zero length packet case */ 00386 LPC_USB->USBCtrl=0; 00387 00388 SIEselectEndpoint(endpoint); 00389 SIEvalidateBuffer(); 00390 } 00391 00392 /* 00393 * USBBusInterface API 00394 */ 00395 00396 bool USBBusInterface_init(void) 00397 { 00398 /* Disable IRQ */ 00399 NVIC_DisableIRQ(USB_IRQn); 00400 00401 /* Enable power to USB device controller */ 00402 LPC_SC->PCONP |= PCUSB; 00403 00404 /* Enable USB clocks */ 00405 LPC_USB->USBClkCtrl |= DEV_CLK_EN | AHB_CLK_EN; 00406 while (LPC_USB->USBClkSt != (DEV_CLK_ON | AHB_CLK_ON)); 00407 00408 /* Configure pins P0.29 and P0.30 to be USB D+ and USB D- */ 00409 LPC_PINCON->PINSEL1 &= 0xc3ffffff; 00410 LPC_PINCON->PINSEL1 |= 0x14000000; 00411 00412 /* Disconnect USB device */ 00413 SIEdisconnect(); 00414 00415 /* Configure pin P2.9 to be Connect */ 00416 LPC_PINCON->PINSEL4 &= 0xfffcffff; 00417 LPC_PINCON->PINSEL4 |= 0x00040000; 00418 00419 /* Connect must be low for at least 2.5uS */ 00420 wait(0.3); 00421 00422 /* Set the maximum packet size for the control endpoints */ 00423 realiseEndpoint(EP0IN, MAX_PACKET_SIZE_EP0); 00424 realiseEndpoint(EP0OUT, MAX_PACKET_SIZE_EP0); 00425 00426 /* Attach IRQ */ 00427 NVIC_EnableIRQ(USB_IRQn); 00428 00429 /* Enable interrupts for device events and EP0 */ 00430 LPC_USB->USBDevIntEn = EP_SLOW | DEV_STAT; 00431 enableEndpointEvent(EP0IN); 00432 enableEndpointEvent(EP0OUT); 00433 return true; 00434 } 00435 00436 void USBBusInterface_uninit(void) 00437 { 00438 /* Ensure device disconnected */ 00439 SIEdisconnect(); 00440 00441 /* Disable USB interrupts */ 00442 NVIC_DisableIRQ(USB_IRQn); 00443 } 00444 00445 void USBBusInterface_connect(void) 00446 { 00447 /* Connect USB device */ 00448 SIEconnect(); 00449 } 00450 00451 void USBBusInterface_disconnect(void) 00452 { 00453 /* Disconnect USB device */ 00454 SIEdisconnect(); 00455 } 00456 00457 void USBBusInterface_configureDevice(void) 00458 { 00459 SIEconfigureDevice(); 00460 } 00461 00462 void USBBusInterface_unconfigureDevice(void) 00463 { 00464 SIEunconfigureDevice(); 00465 } 00466 00467 void USBBusInterface_setAddress(uint8_t address) 00468 { 00469 SIEsetAddress(address); 00470 } 00471 00472 void USBBusInterface_EP0setup(uint8_t *buffer) 00473 { 00474 endpointRead(EP0OUT, buffer); 00475 } 00476 00477 void USBBusInterface_EP0read(void) 00478 { 00479 /* Not required */ 00480 } 00481 00482 uint32_t USBBusInterface_EP0getReadResult(uint8_t *buffer) 00483 { 00484 return endpointRead(EP0OUT, buffer); 00485 } 00486 00487 void USBBusInterface_EP0write(uint8_t *buffer, uint32_t size) 00488 { 00489 endpointWrite(EP0IN, buffer, size); 00490 } 00491 00492 void USBBusInterface_EP0getWriteResult(void) 00493 { 00494 /* Not required */ 00495 } 00496 00497 void USBBusInterface_EP0stall(void) 00498 { 00499 /* This will stall both control endpoints */ 00500 stallEndpoint(EP0OUT); 00501 } 00502 00503 EP_STATUS USBBusInterface_endpointRead(uint8_t endpoint, uint8_t *data, uint32_t maximumSize) 00504 { 00505 if (getEndpointStallState(endpoint)) 00506 { 00507 return EP_STALLED; 00508 } 00509 00510 return EP_PENDING; 00511 } 00512 00513 EP_STATUS USBBusInterface_endpointReadResult(uint8_t endpoint, uint32_t *bytesRead) 00514 { 00515 return EP_PENDING; 00516 } 00517 00518 EP_STATUS USBBusInterface_endpointWrite(uint8_t endpoint, uint8_t *data, uint32_t size) 00519 { 00520 if (getEndpointStallState(endpoint)) 00521 { 00522 return EP_STALLED; 00523 } 00524 00525 epComplete &= EP(endpoint); 00526 00527 endpointWrite(endpoint, data, size); 00528 return EP_PENDING; 00529 } 00530 00531 EP_STATUS USBBusInterface_endpointWriteResult(uint8_t endpoint) 00532 { 00533 if (epComplete & EP(endpoint)) 00534 { 00535 epComplete &= ~EP(endpoint); 00536 return EP_COMPLETED; 00537 } 00538 00539 return EP_PENDING; 00540 } 00541 00542 bool USBBusInterface_realiseEndpoint(uint8_t endpoint, uint32_t maxPacket, uint32_t flags) 00543 { 00544 realiseEndpoint(endpoint, maxPacket); 00545 enableEndpointEvent(endpoint); 00546 return true; 00547 } 00548 00549 void USBBusInterface_stallEndpoint(uint8_t endpoint) 00550 { 00551 stallEndpoint(endpoint); 00552 } 00553 00554 void USBBusInterface_unstallEndpoint(uint8_t endpoint) 00555 { 00556 unstallEndpoint(endpoint); 00557 } 00558 00559 bool USBBusInterface_getEndpointStallState(uint8_t endpoint) 00560 { 00561 return getEndpointStallState(endpoint); 00562 } 00563 00564 void USBBusInterface_remoteWakeup(void) 00565 { 00566 /* Remote wakeup */ 00567 uint8_t status; 00568 00569 /* Enable USB clocks */ 00570 LPC_USB->USBClkCtrl |= DEV_CLK_EN | AHB_CLK_EN; 00571 while (LPC_USB->USBClkSt != (DEV_CLK_ON | AHB_CLK_ON)); 00572 00573 status = SIEgetDeviceStatus(); 00574 SIEsetDeviceStatus(status & ~SIE_DS_SUS); 00575 } 00576 00577 /* 00578 * USB interrupt handler 00579 */ 00580 00581 extern "C" 00582 { 00583 void USB_IRQHandler(void) 00584 { 00585 uint8_t devStat; 00586 00587 if (LPC_USB->USBDevIntSt & FRAME) 00588 { 00589 /* Start of frame event */ 00590 USBDevice_SOF(SIEgetFrameNumber()); 00591 /* Clear interrupt status flag */ 00592 LPC_USB->USBDevIntClr = FRAME; 00593 } 00594 00595 if (LPC_USB->USBDevIntSt & DEV_STAT) 00596 { 00597 /* Device Status interrupt */ 00598 /* Must clear the interrupt status flag before reading the device status from the SIE */ 00599 LPC_USB->USBDevIntClr = DEV_STAT; 00600 00601 /* Read device status from SIE */ 00602 devStat = SIEgetDeviceStatus(); 00603 00604 if (devStat & SIE_DS_RST) 00605 { 00606 /* Bus reset */ 00607 USBDevice_busReset(); 00608 } 00609 } 00610 00611 if (LPC_USB->USBDevIntSt & EP_SLOW) 00612 { 00613 /* (Slow) Endpoint Interrupt */ 00614 00615 /* Process each endpoint interrupt */ 00616 if (LPC_USB->USBEpIntSt & EP(EP0OUT)) 00617 { 00618 if (selectEndpointClearInterrupt(EP0OUT) & SIE_SE_STP) 00619 { 00620 /* this is a setup packet */ 00621 USBDevice_EP0setup(); 00622 } 00623 else 00624 { 00625 USBDevice_EP0out(); 00626 } 00627 } 00628 00629 if (LPC_USB->USBEpIntSt & EP(EP0IN)) 00630 { 00631 selectEndpointClearInterrupt(EP0IN); 00632 USBDevice_EP0in(); 00633 } 00634 00635 /* TODO: This should cover all endpoints, not just EP1,2,3:*/ 00636 if (LPC_USB->USBEpIntSt & EP(EP1IN)) 00637 { 00638 selectEndpointClearInterrupt(EP1IN); 00639 epComplete |= EP(EP1IN); 00640 } 00641 00642 if (LPC_USB->USBEpIntSt & EP(EP1OUT)) 00643 { 00644 selectEndpointClearInterrupt(EP1OUT); 00645 epComplete |= EP(EP1OUT); 00646 } 00647 00648 if (LPC_USB->USBEpIntSt & EP(EP2IN)) 00649 { 00650 selectEndpointClearInterrupt(EP2IN); 00651 epComplete |= EP(EP2IN); 00652 } 00653 00654 if (LPC_USB->USBEpIntSt & EP(EP2OUT)) 00655 { 00656 selectEndpointClearInterrupt(EP2OUT); 00657 epComplete |= EP(EP2OUT); 00658 } 00659 00660 if (LPC_USB->USBEpIntSt & EP(EP3IN)) 00661 { 00662 selectEndpointClearInterrupt(EP3IN); 00663 epComplete |= EP(EP3IN); 00664 } 00665 00666 if (LPC_USB->USBEpIntSt & EP(EP3OUT)) 00667 { 00668 selectEndpointClearInterrupt(EP3OUT); 00669 epComplete |= EP(EP3OUT); 00670 } 00671 00672 /* Clear interrupt status flag */ 00673 /* EP_SLOW and EP_FAST interrupt bits should be cleared after the corresponding endpoint interrupts are cleared. */ 00674 LPC_USB->USBDevIntClr = EP_SLOW; 00675 } 00676 } 00677 } 00678 00679 #endif
Generated on Fri Jul 15 2022 02:22:27 by 1.7.2