C:/nxpdrv/LPC1700CMSIS/Drivers/source/lpc17xx_ssp.c
Go to the documentation of this file.00001 00020 /* Peripheral group ----------------------------------------------------------- */ 00025 /* Includes ------------------------------------------------------------------- */ 00026 #include "lpc17xx_ssp.h" 00027 #include "lpc17xx_clkpwr.h" 00028 00029 00030 /* If this source file built with example, the LPC17xx FW library configuration 00031 * file in each example directory ("lpc17xx_libcfg.h") must be included, 00032 * otherwise the default FW library configuration file must be included instead 00033 */ 00034 #ifdef __BUILD_WITH_EXAMPLE__ 00035 #include "lpc17xx_libcfg.h" 00036 #else 00037 #include "lpc17xx_libcfg_default.h" 00038 #endif /* __BUILD_WITH_EXAMPLE__ */ 00039 00040 00041 #ifdef _SSP 00042 00043 /* Private Types -------------------------------------------------------------- */ 00049 typedef struct 00050 { 00051 int32_t dataword; /* Current data word: 0 - 8 bit; 1 - 16 bit */ 00052 uint32_t txrx_setup; /* Transmission setup */ 00053 void (*inthandler)(LPC_SSP_TypeDef *SSPx); /* Transmission interrupt handler */ 00054 } SSP_CFG_T; 00055 00060 /* Private Variables ---------------------------------------------------------- */ 00061 /* SSP configuration data */ 00062 static SSP_CFG_T sspdat[2]; 00063 00064 00065 /* Private Functions ---------------------------------------------------------- */ 00073 static int32_t SSP_getNum(LPC_SSP_TypeDef *SSPx){ 00074 if (SSPx == LPC_SSP0) { 00075 return (0); 00076 } else if (SSPx == LPC_SSP1) { 00077 return (1); 00078 } 00079 return (-1); 00080 } 00081 00082 00083 /*********************************************************************/ 00089 void SSP_IntHandler(LPC_SSP_TypeDef *SSPx) 00090 { 00091 SSP_DATA_SETUP_Type *xf_setup; 00092 uint16_t tmp; 00093 int32_t sspnum; 00094 00095 // Disable interrupt 00096 SSPx->IMSC = 0; 00097 00098 sspnum = SSP_getNum(SSPx); 00099 xf_setup = (SSP_DATA_SETUP_Type *)sspdat[sspnum].txrx_setup; 00100 00101 // save status 00102 tmp = SSPx->RIS; 00103 xf_setup->status = tmp; 00104 00105 // Check overrun error 00106 if (tmp & SSP_RIS_ROR){ 00107 // Clear interrupt 00108 SSPx->ICR = SSP_RIS_ROR; 00109 // update status 00110 xf_setup->status |= SSP_STAT_ERROR; 00111 // Callback 00112 if (xf_setup->callback != NULL){ 00113 xf_setup->callback(); 00114 } 00115 return; 00116 } 00117 00118 if ((xf_setup->tx_cnt != xf_setup->length) || (xf_setup->rx_cnt != xf_setup->length)){ 00119 /* check if RX FIFO contains data */ 00120 while ((SSPx->SR & SSP_SR_RNE) && (xf_setup->rx_cnt != xf_setup->length)){ 00121 // Read data from SSP data 00122 tmp = SSP_ReceiveData(SSPx); 00123 00124 // Store data to destination 00125 if (xf_setup->rx_data != NULL) 00126 { 00127 if (sspdat[sspnum].dataword == 0){ 00128 *(uint8_t *)((uint32_t)xf_setup->rx_data + xf_setup->rx_cnt) = (uint8_t) tmp; 00129 } else { 00130 *(uint16_t *)((uint32_t)xf_setup->rx_data + xf_setup->rx_cnt) = (uint16_t) tmp; 00131 } 00132 } 00133 // Increase counter 00134 if (sspdat[sspnum].dataword == 0){ 00135 xf_setup->rx_cnt++; 00136 } else { 00137 xf_setup->rx_cnt += 2; 00138 } 00139 } 00140 00141 while ((SSPx->SR & SSP_SR_TNF) && (xf_setup->tx_cnt != xf_setup->length)){ 00142 // Write data to buffer 00143 if(xf_setup->tx_data == NULL){ 00144 if (sspdat[sspnum].dataword == 0){ 00145 SSP_SendData(SSPx, 0xFF); 00146 xf_setup->tx_cnt++; 00147 } else { 00148 SSP_SendData(SSPx, 0xFFFF); 00149 xf_setup->tx_cnt += 2; 00150 } 00151 } else { 00152 if (sspdat[sspnum].dataword == 0){ 00153 SSP_SendData(SSPx, (*(uint8_t *)((uint32_t)xf_setup->tx_data + xf_setup->tx_cnt))); 00154 xf_setup->tx_cnt++; 00155 } else { 00156 SSP_SendData(SSPx, (*(uint16_t *)((uint32_t)xf_setup->tx_data + xf_setup->tx_cnt))); 00157 xf_setup->tx_cnt += 2; 00158 } 00159 } 00160 00161 // Check overrun error 00162 if ((tmp = SSPx->RIS) & SSP_RIS_ROR){ 00163 // update status 00164 xf_setup->status |= SSP_STAT_ERROR; 00165 // Callback 00166 if (xf_setup->callback != NULL){ 00167 xf_setup->callback(); 00168 } 00169 return; 00170 } 00171 00172 // Check for any data available in RX FIFO 00173 while ((SSPx->SR & SSP_SR_RNE) && (xf_setup->rx_cnt != xf_setup->length)){ 00174 // Read data from SSP data 00175 tmp = SSP_ReceiveData(SSPx); 00176 00177 // Store data to destination 00178 if (xf_setup->rx_data != NULL) 00179 { 00180 if (sspdat[sspnum].dataword == 0){ 00181 *(uint8_t *)((uint32_t)xf_setup->rx_data + xf_setup->rx_cnt) = (uint8_t) tmp; 00182 } else { 00183 *(uint16_t *)((uint32_t)xf_setup->rx_data + xf_setup->rx_cnt) = (uint16_t) tmp; 00184 } 00185 } 00186 // Increase counter 00187 if (sspdat[sspnum].dataword == 0){ 00188 xf_setup->rx_cnt++; 00189 } else { 00190 xf_setup->rx_cnt += 2; 00191 } 00192 } 00193 } 00194 } 00195 00196 // If there more data to sent or receive 00197 if ((xf_setup->rx_cnt != xf_setup->length) || (xf_setup->tx_cnt != xf_setup->length)){ 00198 // Enable all interrupt 00199 SSPx->IMSC = SSP_IMSC_BITMASK; 00200 } else { 00201 // Save status 00202 xf_setup->status = SSP_STAT_DONE; 00203 // Callback 00204 if (xf_setup->callback != NULL){ 00205 xf_setup->callback(); 00206 } 00207 } 00208 } 00209 00215 /* Public Functions ----------------------------------------------------------- */ 00220 /*********************************************************************/ 00227 void SSP_SetClock (LPC_SSP_TypeDef *SSPx, uint32_t target_clock) 00228 { 00229 uint32_t prescale, cr0_div, cmp_clk, ssp_clk; 00230 00231 CHECK_PARAM(PARAM_SSPx(SSPx)); 00232 00233 /* The SSP clock is derived from the (main system oscillator / 2), 00234 so compute the best divider from that clock */ 00235 if (SSPx == LPC_SSP0){ 00236 ssp_clk = CLKPWR_GetPCLK (CLKPWR_PCLKSEL_SSP0); 00237 } else if (SSPx == LPC_SSP1) { 00238 ssp_clk = CLKPWR_GetPCLK (CLKPWR_PCLKSEL_SSP1); 00239 } else { 00240 return; 00241 } 00242 00243 /* Find closest divider to get at or under the target frequency. 00244 Use smallest prescale possible and rely on the divider to get 00245 the closest target frequency */ 00246 cr0_div = 0; 00247 cmp_clk = 0xFFFFFFFF; 00248 prescale = 2; 00249 while (cmp_clk > target_clock) 00250 { 00251 cmp_clk = ssp_clk / ((cr0_div + 1) * prescale); 00252 if (cmp_clk > target_clock) 00253 { 00254 cr0_div++; 00255 if (cr0_div > 0xFF) 00256 { 00257 cr0_div = 0; 00258 prescale += 2; 00259 } 00260 } 00261 } 00262 00263 /* Write computed prescaler and divider back to register */ 00264 SSPx->CR0 &= (~SSP_CR0_SCR(0xFF)) & SSP_CR0_BITMASK; 00265 SSPx->CR0 |= (SSP_CR0_SCR(cr0_div)) & SSP_CR0_BITMASK; 00266 SSPx->CPSR = prescale & SSP_CPSR_BITMASK; 00267 } 00268 00269 00270 /*********************************************************************/ 00276 void SSP_DeInit(LPC_SSP_TypeDef* SSPx) 00277 { 00278 CHECK_PARAM(PARAM_SSPx(SSPx)); 00279 00280 if (SSPx == LPC_SSP0){ 00281 /* Set up clock and power for SSP0 module */ 00282 CLKPWR_ConfigPPWR (CLKPWR_PCONP_PCSSP0, DISABLE); 00283 } else if (SSPx == LPC_SSP1) { 00284 /* Set up clock and power for SSP1 module */ 00285 CLKPWR_ConfigPPWR (CLKPWR_PCONP_PCSSP1, DISABLE); 00286 } 00287 } 00288 00289 00290 00291 /********************************************************************/ 00300 void SSP_Init(LPC_SSP_TypeDef *SSPx, SSP_CFG_Type *SSP_ConfigStruct) 00301 { 00302 uint32_t tmp; 00303 00304 CHECK_PARAM(PARAM_SSPx(SSPx)); 00305 00306 if(SSPx == LPC_SSP0) { 00307 /* Set up clock and power for SSP0 module */ 00308 CLKPWR_ConfigPPWR (CLKPWR_PCONP_PCSSP0, ENABLE); 00309 } else if(SSPx == LPC_SSP1) { 00310 /* Set up clock and power for SSP1 module */ 00311 CLKPWR_ConfigPPWR (CLKPWR_PCONP_PCSSP1, ENABLE); 00312 } else { 00313 return; 00314 } 00315 00316 /* Configure SSP, interrupt is disable, LoopBack mode is disable, 00317 * SSP is disable, Slave output is disable as default 00318 */ 00319 tmp = ((SSP_ConfigStruct->CPHA) | (SSP_ConfigStruct->CPOL) \ 00320 | (SSP_ConfigStruct->FrameFormat) | (SSP_ConfigStruct->Databit)) 00321 & SSP_CR0_BITMASK; 00322 // write back to SSP control register 00323 SSPx->CR0 = tmp; 00324 tmp = SSP_getNum(SSPx); 00325 if (SSP_ConfigStruct->Databit > SSP_DATABIT_8){ 00326 sspdat[tmp].dataword = 1; 00327 } else { 00328 sspdat[tmp].dataword = 0; 00329 } 00330 00331 tmp = SSP_ConfigStruct->Mode & SSP_CR1_BITMASK; 00332 // Write back to CR1 00333 SSPx->CR1 = tmp; 00334 00335 // Set clock rate for SSP peripheral 00336 SSP_SetClock(SSPx, SSP_ConfigStruct->ClockRate); 00337 } 00338 00339 00340 00341 /*****************************************************************************/ 00353 void SSP_ConfigStructInit(SSP_CFG_Type *SSP_InitStruct) 00354 { 00355 SSP_InitStruct->CPHA = SSP_CPHA_FIRST; 00356 SSP_InitStruct->CPOL = SSP_CPOL_HI; 00357 SSP_InitStruct->ClockRate = 1000000; 00358 SSP_InitStruct->Databit = SSP_DATABIT_8; 00359 SSP_InitStruct->Mode = SSP_MASTER_MODE; 00360 SSP_InitStruct->FrameFormat = SSP_FRAME_SPI; 00361 } 00362 00363 00364 /*********************************************************************/ 00370 void SSP_Cmd(LPC_SSP_TypeDef* SSPx, FunctionalState NewState) 00371 { 00372 CHECK_PARAM(PARAM_SSPx(SSPx)); 00373 CHECK_PARAM(PARAM_FUNCTIONALSTATE(NewState)); 00374 00375 if (NewState == ENABLE) 00376 { 00377 SSPx->CR1 |= SSP_CR1_SSP_EN; 00378 } 00379 else 00380 { 00381 SSPx->CR1 &= (~SSP_CR1_SSP_EN) & SSP_CR1_BITMASK; 00382 } 00383 } 00384 00385 00386 00387 /*********************************************************************/ 00395 void SSP_LoopBackCmd(LPC_SSP_TypeDef* SSPx, FunctionalState NewState) 00396 { 00397 CHECK_PARAM(PARAM_SSPx(SSPx)); 00398 CHECK_PARAM(PARAM_FUNCTIONALSTATE(NewState)); 00399 00400 if (NewState == ENABLE) 00401 { 00402 SSPx->CR1 |= SSP_CR1_LBM_EN; 00403 } 00404 else 00405 { 00406 SSPx->CR1 &= (~SSP_CR1_LBM_EN) & SSP_CR1_BITMASK; 00407 } 00408 } 00409 00410 00411 00412 /*********************************************************************/ 00423 void SSP_SlaveOutputCmd(LPC_SSP_TypeDef* SSPx, FunctionalState NewState) 00424 { 00425 CHECK_PARAM(PARAM_SSPx(SSPx)); 00426 CHECK_PARAM(PARAM_FUNCTIONALSTATE(NewState)); 00427 00428 if (NewState == ENABLE) 00429 { 00430 SSPx->CR1 &= (~SSP_CR1_SO_DISABLE) & SSP_CR1_BITMASK; 00431 } 00432 else 00433 { 00434 SSPx->CR1 |= SSP_CR1_SO_DISABLE; 00435 } 00436 } 00437 00438 00439 00440 /*********************************************************************/ 00447 void SSP_SendData(LPC_SSP_TypeDef* SSPx, uint16_t Data) 00448 { 00449 CHECK_PARAM(PARAM_SSPx(SSPx)); 00450 00451 SSPx->DR = SSP_DR_BITMASK(Data); 00452 } 00453 00454 00455 00456 /*********************************************************************/ 00461 uint16_t SSP_ReceiveData(LPC_SSP_TypeDef* SSPx) 00462 { 00463 CHECK_PARAM(PARAM_SSPx(SSPx)); 00464 00465 return ((uint16_t) (SSP_DR_BITMASK(SSPx->DR))); 00466 } 00467 00468 /*********************************************************************/ 00482 int32_t SSP_ReadWrite (LPC_SSP_TypeDef *SSPx, SSP_DATA_SETUP_Type *dataCfg, \ 00483 SSP_TRANSFER_Type xfType) 00484 { 00485 uint8_t *rdata8; 00486 uint8_t *wdata8; 00487 uint16_t *rdata16; 00488 uint16_t *wdata16; 00489 uint32_t stat; 00490 uint32_t tmp; 00491 int32_t sspnum; 00492 int32_t dataword; 00493 00494 dataCfg->rx_cnt = 0; 00495 dataCfg->tx_cnt = 0; 00496 dataCfg->status = 0; 00497 00498 00499 /* Clear all remaining data in RX FIFO */ 00500 while (SSPx->SR & SSP_SR_RNE){ 00501 tmp = (uint32_t) SSP_ReceiveData(SSPx); 00502 } 00503 00504 // Clear status 00505 SSPx->ICR = SSP_ICR_BITMASK; 00506 00507 sspnum = SSP_getNum(SSPx); 00508 dataword = sspdat[sspnum].dataword; 00509 00510 // Polling mode ---------------------------------------------------------------------- 00511 if (xfType == SSP_TRANSFER_POLLING){ 00512 if (dataword == 0){ 00513 rdata8 = (uint8_t *)dataCfg->rx_data; 00514 wdata8 = (uint8_t *)dataCfg->tx_data; 00515 } else { 00516 rdata16 = (uint16_t *)dataCfg->rx_data; 00517 wdata16 = (uint16_t *)dataCfg->tx_data; 00518 } 00519 while ((dataCfg->tx_cnt != dataCfg->length) || (dataCfg->rx_cnt != dataCfg->length)){ 00520 if ((SSPx->SR & SSP_SR_TNF) && (dataCfg->tx_cnt != dataCfg->length)){ 00521 // Write data to buffer 00522 if(dataCfg->tx_data == NULL){ 00523 if (dataword == 0){ 00524 SSP_SendData(SSPx, 0xFF); 00525 dataCfg->tx_cnt++; 00526 } else { 00527 SSP_SendData(SSPx, 0xFFFF); 00528 dataCfg->tx_cnt += 2; 00529 } 00530 } else { 00531 if (dataword == 0){ 00532 SSP_SendData(SSPx, *wdata8); 00533 wdata8++; 00534 dataCfg->tx_cnt++; 00535 } else { 00536 SSP_SendData(SSPx, *wdata16); 00537 wdata16++; 00538 dataCfg->tx_cnt += 2; 00539 } 00540 } 00541 } 00542 00543 // Check overrun error 00544 if ((stat = SSPx->RIS) & SSP_RIS_ROR){ 00545 // save status and return 00546 dataCfg->status = stat | SSP_STAT_ERROR; 00547 return (-1); 00548 } 00549 00550 // Check for any data available in RX FIFO 00551 while ((SSPx->SR & SSP_SR_RNE) && (dataCfg->rx_cnt != dataCfg->length)){ 00552 // Read data from SSP data 00553 tmp = SSP_ReceiveData(SSPx); 00554 00555 // Store data to destination 00556 if (dataCfg->rx_data != NULL) 00557 { 00558 if (dataword == 0){ 00559 *(rdata8) = (uint8_t) tmp; 00560 rdata8++; 00561 } else { 00562 *(rdata16) = (uint16_t) tmp; 00563 rdata16++; 00564 } 00565 } 00566 // Increase counter 00567 if (dataword == 0){ 00568 dataCfg->rx_cnt++; 00569 } else { 00570 dataCfg->rx_cnt += 2; 00571 } 00572 } 00573 } 00574 00575 // save status 00576 dataCfg->status = SSP_STAT_DONE; 00577 00578 if (dataCfg->tx_data != NULL){ 00579 return dataCfg->tx_cnt; 00580 } else if (dataCfg->rx_data != NULL){ 00581 return dataCfg->rx_cnt; 00582 } else { 00583 return (0); 00584 } 00585 } 00586 00587 // Interrupt mode ---------------------------------------------------------------------- 00588 else if (xfType == SSP_TRANSFER_INTERRUPT){ 00589 sspdat[sspnum].inthandler = SSP_IntHandler; 00590 sspdat[sspnum].txrx_setup = (uint32_t)dataCfg; 00591 00592 while ((SSPx->SR & SSP_SR_TNF) && (dataCfg->tx_cnt != dataCfg->length)){ 00593 // Write data to buffer 00594 if(dataCfg->tx_data == NULL){ 00595 if (sspdat[sspnum].dataword == 0){ 00596 SSP_SendData(SSPx, 0xFF); 00597 dataCfg->tx_cnt++; 00598 } else { 00599 SSP_SendData(SSPx, 0xFFFF); 00600 dataCfg->tx_cnt += 2; 00601 } 00602 } else { 00603 if (sspdat[sspnum].dataword == 0){ 00604 SSP_SendData(SSPx, (*(uint8_t *)((uint32_t)dataCfg->tx_data + dataCfg->tx_cnt))); 00605 dataCfg->tx_cnt++; 00606 } else { 00607 SSP_SendData(SSPx, (*(uint16_t *)((uint32_t)dataCfg->tx_data + dataCfg->tx_cnt))); 00608 dataCfg->tx_cnt += 2; 00609 } 00610 } 00611 00612 // Check error 00613 if ((stat = SSPx->RIS) & SSP_RIS_ROR){ 00614 // save status and return 00615 dataCfg->status = stat | SSP_STAT_ERROR; 00616 return (-1); 00617 } 00618 00619 // Check for any data available in RX FIFO 00620 while ((SSPx->SR & SSP_SR_RNE) && (dataCfg->rx_cnt != dataCfg->length)){ 00621 // Read data from SSP data 00622 tmp = SSP_ReceiveData(SSPx); 00623 00624 // Store data to destination 00625 if (dataCfg->rx_data != NULL) 00626 { 00627 if (sspdat[sspnum].dataword == 0){ 00628 *(uint8_t *)((uint32_t)dataCfg->rx_data + dataCfg->rx_cnt) = (uint8_t) tmp; 00629 } else { 00630 *(uint16_t *)((uint32_t)dataCfg->rx_data + dataCfg->rx_cnt) = (uint16_t) tmp; 00631 } 00632 } 00633 // Increase counter 00634 if (sspdat[sspnum].dataword == 0){ 00635 dataCfg->rx_cnt++; 00636 } else { 00637 dataCfg->rx_cnt += 2; 00638 } 00639 } 00640 } 00641 00642 // If there more data to sent or receive 00643 if ((dataCfg->rx_cnt != dataCfg->length) || (dataCfg->tx_cnt != dataCfg->length)){ 00644 // Enable all interrupt 00645 SSPx->IMSC = SSP_IMSC_BITMASK; 00646 } else { 00647 // Save status 00648 dataCfg->status = SSP_STAT_DONE; 00649 } 00650 return (0); 00651 } 00652 00653 return (-1); 00654 } 00655 00656 /*********************************************************************/ 00668 FlagStatus SSP_GetStatus(LPC_SSP_TypeDef* SSPx, uint32_t FlagType) 00669 { 00670 CHECK_PARAM(PARAM_SSPx(SSPx)); 00671 CHECK_PARAM(PARAM_SSP_STAT(FlagType)); 00672 00673 return ((SSPx->SR & FlagType) ? SET : RESET); 00674 } 00675 00676 00677 00678 /*********************************************************************/ 00691 void SSP_IntConfig(LPC_SSP_TypeDef *SSPx, uint32_t IntType, FunctionalState NewState) 00692 { 00693 CHECK_PARAM(PARAM_SSPx(SSPx)); 00694 CHECK_PARAM(PARAM_SSP_INTCFG(IntType)); 00695 00696 if (NewState == ENABLE) 00697 { 00698 SSPx->IMSC |= IntType; 00699 } 00700 else 00701 { 00702 SSPx->IMSC &= (~IntType) & SSP_IMSC_BITMASK; 00703 } 00704 } 00705 00706 00707 /*********************************************************************/ 00720 IntStatus SSP_GetRawIntStatus(LPC_SSP_TypeDef *SSPx, uint32_t RawIntType) 00721 { 00722 CHECK_PARAM(PARAM_SSPx(SSPx)); 00723 CHECK_PARAM(PARAM_SSP_INTSTAT_RAW(RawIntType)); 00724 00725 return ((SSPx->RIS & RawIntType) ? SET : RESET); 00726 } 00727 00728 00729 /*********************************************************************/ 00742 IntStatus SSP_GetIntStatus (LPC_SSP_TypeDef *SSPx, uint32_t IntType) 00743 { 00744 CHECK_PARAM(PARAM_SSPx(SSPx)); 00745 CHECK_PARAM(PARAM_SSP_INTSTAT(IntType)); 00746 00747 return ((SSPx->MIS & IntType) ? SET :RESET); 00748 } 00749 00750 00751 00752 /*********************************************************************/ 00762 void SSP_ClearIntPending(LPC_SSP_TypeDef *SSPx, uint32_t IntType) 00763 { 00764 CHECK_PARAM(PARAM_SSPx(SSPx)); 00765 CHECK_PARAM(PARAM_SSP_INTCLR(IntType)); 00766 00767 SSPx->ICR = IntType; 00768 } 00769 00770 /*********************************************************************/ 00782 void SSP_DMACmd(LPC_SSP_TypeDef *SSPx, uint32_t DMAMode, FunctionalState NewState) 00783 { 00784 CHECK_PARAM(PARAM_SSPx(SSPx)); 00785 CHECK_PARAM(PARAM_SSP_DMA(DMAMode)); 00786 CHECK_PARAM(PARAM_FUNCTIONALSTATE(NewState)); 00787 00788 if (NewState == ENABLE) 00789 { 00790 SSPx->DMACR |= DMAMode; 00791 } 00792 else 00793 { 00794 SSPx->DMACR &= (~DMAMode) & SSP_DMA_BITMASK; 00795 } 00796 } 00797 00803 void SSP0_StdIntHandler(void) 00804 { 00805 // Call relevant handler 00806 sspdat[0].inthandler(LPC_SSP0); 00807 } 00808 00814 void SSP1_StdIntHandler(void) 00815 { 00816 // Call relevant handler 00817 sspdat[1].inthandler(LPC_SSP1); 00818 } 00819 00824 #endif /* _SSP */ 00825 00830 /* --------------------------------- End Of File ------------------------------ */ 00831
Generated on Mon Feb 8 10:01:38 2010 for LPC1700CMSIS Standard Peripheral Firmware Library by
