Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Fork of USBDevice by
USBHAL_KL25Z.cpp
00001 /* Copyright (c) 2010-2011 mbed.org, MIT License 00002 * 00003 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 00004 * and associated documentation files (the "Software"), to deal in the Software without 00005 * restriction, including without limitation the rights to use, copy, modify, merge, publish, 00006 * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 00007 * Software is furnished to do so, subject to the following conditions: 00008 * 00009 * The above copyright notice and this permission notice shall be included in all copies or 00010 * substantial portions of the Software. 00011 * 00012 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 00013 * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 00014 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 00015 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 00016 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 00017 */ 00018 00019 #if defined(TARGET_KL25Z) | defined(TARGET_KL46Z) | defined(TARGET_K20D5M) | defined(TARGET_K64F) 00020 00021 #include "USBHAL.h" 00022 00023 USBHAL * USBHAL::instance; 00024 00025 static volatile int epComplete = 0; 00026 00027 // Convert physical endpoint number to register bit 00028 #define EP(endpoint) (1<<(endpoint)) 00029 00030 // Convert physical to logical 00031 #define PHY_TO_LOG(endpoint) ((endpoint)>>1) 00032 00033 // Get endpoint direction 00034 #define IN_EP(endpoint) ((endpoint) & 1U ? true : false) 00035 #define OUT_EP(endpoint) ((endpoint) & 1U ? false : true) 00036 00037 #define BD_OWN_MASK (1<<7) 00038 #define BD_DATA01_MASK (1<<6) 00039 #define BD_KEEP_MASK (1<<5) 00040 #define BD_NINC_MASK (1<<4) 00041 #define BD_DTS_MASK (1<<3) 00042 #define BD_STALL_MASK (1<<2) 00043 00044 #define TX 1 00045 #define RX 0 00046 #define ODD 0 00047 #define EVEN 1 00048 // this macro waits a physical endpoint number 00049 #define EP_BDT_IDX(ep, dir, odd) (((ep * 4) + (2 * dir) + (1 * odd))) 00050 00051 #define SETUP_TOKEN 0x0D 00052 #define IN_TOKEN 0x09 00053 #define OUT_TOKEN 0x01 00054 #define TOK_PID(idx) ((bdt[idx].info >> 2) & 0x0F) 00055 00056 // for each endpt: 8 bytes 00057 typedef struct BDT { 00058 uint8_t info; // BD[0:7] 00059 uint8_t dummy; // RSVD: BD[8:15] 00060 uint16_t byte_count; // BD[16:32] 00061 uint32_t address; // Addr 00062 } BDT; 00063 00064 00065 // there are: 00066 // * 16 bidirectionnal endpt -> 32 physical endpt 00067 // * as there are ODD and EVEN buffer -> 32*2 bdt 00068 __attribute__((__aligned__(512))) BDT bdt[NUMBER_OF_PHYSICAL_ENDPOINTS * 2]; 00069 uint8_t * endpoint_buffer[(NUMBER_OF_PHYSICAL_ENDPOINTS - 2) * 2]; 00070 uint8_t * endpoint_buffer_iso[2*2]; 00071 00072 static uint8_t set_addr = 0; 00073 static uint8_t addr = 0; 00074 00075 static uint32_t Data1 = 0x55555555; 00076 00077 static uint32_t frameNumber() { 00078 return((USB0->FRMNUML | (USB0->FRMNUMH << 8)) & 0x07FF); 00079 } 00080 00081 uint32_t USBHAL::endpointReadcore(uint8_t endpoint, uint8_t *buffer) { 00082 return 0; 00083 } 00084 00085 USBHAL::USBHAL(void) { 00086 // Disable IRQ 00087 NVIC_DisableIRQ(USB0_IRQn); 00088 00089 #if defined(TARGET_K64F) 00090 MPU->CESR=0; 00091 #endif 00092 // fill in callback array 00093 epCallback[0] = &USBHAL::EP1_OUT_callback; 00094 epCallback[1] = &USBHAL::EP1_IN_callback; 00095 epCallback[2] = &USBHAL::EP2_OUT_callback; 00096 epCallback[3] = &USBHAL::EP2_IN_callback; 00097 epCallback[4] = &USBHAL::EP3_OUT_callback; 00098 epCallback[5] = &USBHAL::EP3_IN_callback; 00099 epCallback[6] = &USBHAL::EP4_OUT_callback; 00100 epCallback[7] = &USBHAL::EP4_IN_callback; 00101 epCallback[8] = &USBHAL::EP5_OUT_callback; 00102 epCallback[9] = &USBHAL::EP5_IN_callback; 00103 epCallback[10] = &USBHAL::EP6_OUT_callback; 00104 epCallback[11] = &USBHAL::EP6_IN_callback; 00105 epCallback[12] = &USBHAL::EP7_OUT_callback; 00106 epCallback[13] = &USBHAL::EP7_IN_callback; 00107 epCallback[14] = &USBHAL::EP8_OUT_callback; 00108 epCallback[15] = &USBHAL::EP8_IN_callback; 00109 epCallback[16] = &USBHAL::EP9_OUT_callback; 00110 epCallback[17] = &USBHAL::EP9_IN_callback; 00111 epCallback[18] = &USBHAL::EP10_OUT_callback; 00112 epCallback[19] = &USBHAL::EP10_IN_callback; 00113 epCallback[20] = &USBHAL::EP11_OUT_callback; 00114 epCallback[21] = &USBHAL::EP11_IN_callback; 00115 epCallback[22] = &USBHAL::EP12_OUT_callback; 00116 epCallback[23] = &USBHAL::EP12_IN_callback; 00117 epCallback[24] = &USBHAL::EP13_OUT_callback; 00118 epCallback[25] = &USBHAL::EP13_IN_callback; 00119 epCallback[26] = &USBHAL::EP14_OUT_callback; 00120 epCallback[27] = &USBHAL::EP14_IN_callback; 00121 epCallback[28] = &USBHAL::EP15_OUT_callback; 00122 epCallback[29] = &USBHAL::EP15_IN_callback; 00123 00124 00125 // choose usb src as PLL 00126 SIM->SOPT2 |= (SIM_SOPT2_USBSRC_MASK | SIM_SOPT2_PLLFLLSEL_MASK); 00127 00128 // enable OTG clock 00129 SIM->SCGC4 |= SIM_SCGC4_USBOTG_MASK; 00130 00131 // Attach IRQ 00132 instance = this; 00133 NVIC_SetVector(USB0_IRQn, (uint32_t)&_usbisr); 00134 NVIC_EnableIRQ(USB0_IRQn); 00135 00136 // USB Module Configuration 00137 // Reset USB Module 00138 USB0->USBTRC0 |= USB_USBTRC0_USBRESET_MASK; 00139 while(USB0->USBTRC0 & USB_USBTRC0_USBRESET_MASK); 00140 00141 // Set BDT Base Register 00142 USB0->BDTPAGE1 = (uint8_t)((uint32_t)bdt>>8); 00143 USB0->BDTPAGE2 = (uint8_t)((uint32_t)bdt>>16); 00144 USB0->BDTPAGE3 = (uint8_t)((uint32_t)bdt>>24); 00145 00146 // Clear interrupt flag 00147 USB0->ISTAT = 0xff; 00148 00149 // USB Interrupt Enablers 00150 USB0->INTEN |= USB_INTEN_TOKDNEEN_MASK | 00151 USB_INTEN_SOFTOKEN_MASK | 00152 USB_INTEN_ERROREN_MASK | 00153 USB_INTEN_USBRSTEN_MASK; 00154 00155 // Disable weak pull downs 00156 USB0->USBCTRL &= ~(USB_USBCTRL_PDE_MASK | USB_USBCTRL_SUSP_MASK); 00157 00158 USB0->USBTRC0 |= 0x40; 00159 } 00160 00161 USBHAL::~USBHAL(void) { } 00162 00163 void USBHAL::connect(void) { 00164 // enable USB 00165 USB0->CTL |= USB_CTL_USBENSOFEN_MASK; 00166 // Pull up enable 00167 USB0->CONTROL |= USB_CONTROL_DPPULLUPNONOTG_MASK; 00168 } 00169 00170 void USBHAL::disconnect(void) { 00171 // disable USB 00172 USB0->CTL &= ~USB_CTL_USBENSOFEN_MASK; 00173 // Pull up disable 00174 USB0->CONTROL &= ~USB_CONTROL_DPPULLUPNONOTG_MASK; 00175 00176 //Free buffers if required: 00177 for (int i = 0; i<(NUMBER_OF_PHYSICAL_ENDPOINTS - 2) * 2; i++) { 00178 free(endpoint_buffer[i]); 00179 endpoint_buffer[i] = NULL; 00180 } 00181 free(endpoint_buffer_iso[2]); 00182 endpoint_buffer_iso[2] = NULL; 00183 free(endpoint_buffer_iso[0]); 00184 endpoint_buffer_iso[0] = NULL; 00185 } 00186 00187 void USBHAL::configureDevice(void) { 00188 // not needed 00189 } 00190 00191 void USBHAL::unconfigureDevice(void) { 00192 // not needed 00193 } 00194 00195 void USBHAL::setAddress(uint8_t address) { 00196 // we don't set the address now otherwise the usb controller does not ack 00197 // we set a flag instead 00198 // see usbisr when an IN token is received 00199 set_addr = 1; 00200 addr = address; 00201 } 00202 00203 bool USBHAL::realiseEndpoint(uint8_t endpoint, uint32_t maxPacket, uint32_t flags) { 00204 uint32_t handshake_flag = 0; 00205 uint8_t * buf; 00206 00207 if (endpoint > NUMBER_OF_PHYSICAL_ENDPOINTS - 1) { 00208 return false; 00209 } 00210 00211 uint32_t log_endpoint = PHY_TO_LOG(endpoint); 00212 00213 if ((flags & ISOCHRONOUS) == 0) { 00214 handshake_flag = USB_ENDPT_EPHSHK_MASK; 00215 if (IN_EP(endpoint)) { 00216 if (endpoint_buffer[EP_BDT_IDX(log_endpoint, TX, ODD)] == NULL) 00217 endpoint_buffer[EP_BDT_IDX(log_endpoint, TX, ODD)] = (uint8_t *) malloc (64*2); 00218 buf = &endpoint_buffer[EP_BDT_IDX(log_endpoint, TX, ODD)][0]; 00219 } else { 00220 if (endpoint_buffer[EP_BDT_IDX(log_endpoint, RX, ODD)] == NULL) 00221 endpoint_buffer[EP_BDT_IDX(log_endpoint, RX, ODD)] = (uint8_t *) malloc (64*2); 00222 buf = &endpoint_buffer[EP_BDT_IDX(log_endpoint, RX, ODD)][0]; 00223 } 00224 } else { 00225 if (IN_EP(endpoint)) { 00226 if (endpoint_buffer_iso[2] == NULL) 00227 endpoint_buffer_iso[2] = (uint8_t *) malloc (1023*2); 00228 buf = &endpoint_buffer_iso[2][0]; 00229 } else { 00230 if (endpoint_buffer_iso[0] == NULL) 00231 endpoint_buffer_iso[0] = (uint8_t *) malloc (1023*2); 00232 buf = &endpoint_buffer_iso[0][0]; 00233 } 00234 } 00235 00236 // IN endpt -> device to host (TX) 00237 if (IN_EP(endpoint)) { 00238 USB0->ENDPOINT[log_endpoint].ENDPT |= handshake_flag | // ep handshaking (not if iso endpoint) 00239 USB_ENDPT_EPTXEN_MASK; // en TX (IN) tran 00240 bdt[EP_BDT_IDX(log_endpoint, TX, ODD )].address = (uint32_t) buf; 00241 bdt[EP_BDT_IDX(log_endpoint, TX, EVEN)].address = 0; 00242 } 00243 // OUT endpt -> host to device (RX) 00244 else { 00245 USB0->ENDPOINT[log_endpoint].ENDPT |= handshake_flag | // ep handshaking (not if iso endpoint) 00246 USB_ENDPT_EPRXEN_MASK; // en RX (OUT) tran. 00247 bdt[EP_BDT_IDX(log_endpoint, RX, ODD )].byte_count = maxPacket; 00248 bdt[EP_BDT_IDX(log_endpoint, RX, ODD )].address = (uint32_t) buf; 00249 bdt[EP_BDT_IDX(log_endpoint, RX, ODD )].info = BD_OWN_MASK | BD_DTS_MASK; 00250 bdt[EP_BDT_IDX(log_endpoint, RX, EVEN)].info = 0; 00251 } 00252 00253 Data1 |= (1 << endpoint); 00254 00255 return true; 00256 } 00257 00258 // read setup packet 00259 void USBHAL::EP0setup(uint8_t *buffer) { 00260 uint32_t sz; 00261 endpointReadResult(EP0OUT, buffer, &sz); 00262 } 00263 00264 void USBHAL::EP0readStage(void) { 00265 Data1 &= ~1UL; // set DATA0 00266 bdt[0].info = (BD_DTS_MASK | BD_OWN_MASK); 00267 } 00268 00269 void USBHAL::EP0read(void) { 00270 uint32_t idx = EP_BDT_IDX(PHY_TO_LOG(EP0OUT), RX, 0); 00271 bdt[idx].byte_count = MAX_PACKET_SIZE_EP0; 00272 } 00273 00274 uint32_t USBHAL::EP0getReadResult(uint8_t *buffer) { 00275 uint32_t sz; 00276 endpointReadResult(EP0OUT, buffer, &sz); 00277 return sz; 00278 } 00279 00280 void USBHAL::EP0write(uint8_t *buffer, uint32_t size) { 00281 endpointWrite(EP0IN, buffer, size); 00282 } 00283 00284 void USBHAL::EP0getWriteResult(void) { 00285 } 00286 00287 void USBHAL::EP0stall(void) { 00288 stallEndpoint(EP0OUT); 00289 } 00290 00291 EP_STATUS USBHAL::endpointRead(uint8_t endpoint, uint32_t maximumSize) { 00292 endpoint = PHY_TO_LOG(endpoint); 00293 uint32_t idx = EP_BDT_IDX(endpoint, RX, 0); 00294 bdt[idx].byte_count = maximumSize; 00295 return EP_PENDING; 00296 } 00297 00298 EP_STATUS USBHAL::endpointReadResult(uint8_t endpoint, uint8_t * buffer, uint32_t *bytesRead) { 00299 uint32_t n, sz, idx, setup = 0; 00300 uint8_t not_iso; 00301 uint8_t * ep_buf; 00302 00303 uint32_t log_endpoint = PHY_TO_LOG(endpoint); 00304 00305 if (endpoint > NUMBER_OF_PHYSICAL_ENDPOINTS - 1) { 00306 return EP_INVALID; 00307 } 00308 00309 // if read on a IN endpoint -> error 00310 if (IN_EP(endpoint)) { 00311 return EP_INVALID; 00312 } 00313 00314 idx = EP_BDT_IDX(log_endpoint, RX, 0); 00315 sz = bdt[idx].byte_count; 00316 not_iso = USB0->ENDPOINT[log_endpoint].ENDPT & USB_ENDPT_EPHSHK_MASK; 00317 00318 //for isochronous endpoint, we don't wait an interrupt 00319 if ((log_endpoint != 0) && not_iso && !(epComplete & EP(endpoint))) { 00320 return EP_PENDING; 00321 } 00322 00323 if ((log_endpoint == 0) && (TOK_PID(idx) == SETUP_TOKEN)) { 00324 setup = 1; 00325 } 00326 00327 // non iso endpoint 00328 if (not_iso) { 00329 ep_buf = endpoint_buffer[idx]; 00330 } else { 00331 ep_buf = endpoint_buffer_iso[0]; 00332 } 00333 00334 for (n = 0; n < sz; n++) { 00335 buffer[n] = ep_buf[n]; 00336 } 00337 00338 if (((Data1 >> endpoint) & 1) == ((bdt[idx].info >> 6) & 1)) { 00339 if (setup && (buffer[6] == 0)) // if no setup data stage, 00340 Data1 &= ~1UL; // set DATA0 00341 else 00342 Data1 ^= (1 << endpoint); 00343 } 00344 00345 if (((Data1 >> endpoint) & 1)) { 00346 bdt[idx].info = BD_DTS_MASK | BD_DATA01_MASK | BD_OWN_MASK; 00347 } 00348 else { 00349 bdt[idx].info = BD_DTS_MASK | BD_OWN_MASK; 00350 } 00351 00352 USB0->CTL &= ~USB_CTL_TXSUSPENDTOKENBUSY_MASK; 00353 *bytesRead = sz; 00354 00355 epComplete &= ~EP(endpoint); 00356 return EP_COMPLETED; 00357 } 00358 00359 EP_STATUS USBHAL::endpointWrite(uint8_t endpoint, uint8_t *data, uint32_t size) { 00360 uint32_t idx, n; 00361 uint8_t * ep_buf; 00362 00363 if (endpoint > NUMBER_OF_PHYSICAL_ENDPOINTS - 1) { 00364 return EP_INVALID; 00365 } 00366 00367 // if write on a OUT endpoint -> error 00368 if (OUT_EP(endpoint)) { 00369 return EP_INVALID; 00370 } 00371 00372 idx = EP_BDT_IDX(PHY_TO_LOG(endpoint), TX, 0); 00373 bdt[idx].byte_count = size; 00374 00375 00376 // non iso endpoint 00377 if (USB0->ENDPOINT[PHY_TO_LOG(endpoint)].ENDPT & USB_ENDPT_EPHSHK_MASK) { 00378 ep_buf = endpoint_buffer[idx]; 00379 } else { 00380 ep_buf = endpoint_buffer_iso[2]; 00381 } 00382 00383 for (n = 0; n < size; n++) { 00384 ep_buf[n] = data[n]; 00385 } 00386 00387 if ((Data1 >> endpoint) & 1) { 00388 bdt[idx].info = BD_OWN_MASK | BD_DTS_MASK; 00389 } else { 00390 bdt[idx].info = BD_OWN_MASK | BD_DTS_MASK | BD_DATA01_MASK; 00391 } 00392 00393 Data1 ^= (1 << endpoint); 00394 00395 return EP_PENDING; 00396 } 00397 00398 EP_STATUS USBHAL::endpointWriteResult(uint8_t endpoint) { 00399 if (epComplete & EP(endpoint)) { 00400 epComplete &= ~EP(endpoint); 00401 return EP_COMPLETED; 00402 } 00403 00404 return EP_PENDING; 00405 } 00406 00407 void USBHAL::stallEndpoint(uint8_t endpoint) { 00408 USB0->ENDPOINT[PHY_TO_LOG(endpoint)].ENDPT |= USB_ENDPT_EPSTALL_MASK; 00409 } 00410 00411 void USBHAL::unstallEndpoint(uint8_t endpoint) { 00412 USB0->ENDPOINT[PHY_TO_LOG(endpoint)].ENDPT &= ~USB_ENDPT_EPSTALL_MASK; 00413 } 00414 00415 bool USBHAL::getEndpointStallState(uint8_t endpoint) { 00416 uint8_t stall = (USB0->ENDPOINT[PHY_TO_LOG(endpoint)].ENDPT & USB_ENDPT_EPSTALL_MASK); 00417 return (stall) ? true : false; 00418 } 00419 00420 void USBHAL::remoteWakeup(void) { 00421 // [TODO] 00422 } 00423 00424 00425 void USBHAL::_usbisr(void) { 00426 instance->usbisr(); 00427 } 00428 00429 00430 void USBHAL::usbisr(void) { 00431 uint8_t i; 00432 uint8_t istat = USB0->ISTAT; 00433 00434 // reset interrupt 00435 if (istat & USB_ISTAT_USBRST_MASK) { 00436 // disable all endpt 00437 for(i = 0; i < 16; i++) { 00438 USB0->ENDPOINT[i].ENDPT = 0x00; 00439 } 00440 00441 // enable control endpoint 00442 realiseEndpoint(EP0OUT, MAX_PACKET_SIZE_EP0, 0); 00443 realiseEndpoint(EP0IN, MAX_PACKET_SIZE_EP0, 0); 00444 00445 Data1 = 0x55555555; 00446 USB0->CTL |= USB_CTL_ODDRST_MASK; 00447 00448 USB0->ISTAT = 0xFF; // clear all interrupt status flags 00449 USB0->ERRSTAT = 0xFF; // clear all error flags 00450 USB0->ERREN = 0xFF; // enable error interrupt sources 00451 USB0->ADDR = 0x00; // set default address 00452 00453 return; 00454 } 00455 00456 // resume interrupt 00457 if (istat & USB_ISTAT_RESUME_MASK) { 00458 USB0->ISTAT = USB_ISTAT_RESUME_MASK; 00459 } 00460 00461 // SOF interrupt 00462 if (istat & USB_ISTAT_SOFTOK_MASK) { 00463 USB0->ISTAT = USB_ISTAT_SOFTOK_MASK; 00464 // SOF event, read frame number 00465 SOF(frameNumber()); 00466 } 00467 00468 // stall interrupt 00469 if (istat & 1<<7) { 00470 if (USB0->ENDPOINT[0].ENDPT & USB_ENDPT_EPSTALL_MASK) 00471 USB0->ENDPOINT[0].ENDPT &= ~USB_ENDPT_EPSTALL_MASK; 00472 USB0->ISTAT |= USB_ISTAT_STALL_MASK; 00473 } 00474 00475 // token interrupt 00476 if (istat & 1<<3) { 00477 uint32_t num = (USB0->STAT >> 4) & 0x0F; 00478 uint32_t dir = (USB0->STAT >> 3) & 0x01; 00479 uint32_t ev_odd = (USB0->STAT >> 2) & 0x01; 00480 00481 // setup packet 00482 if ((num == 0) && (TOK_PID((EP_BDT_IDX(num, dir, ev_odd))) == SETUP_TOKEN)) { 00483 Data1 &= ~0x02; 00484 bdt[EP_BDT_IDX(0, TX, EVEN)].info &= ~BD_OWN_MASK; 00485 bdt[EP_BDT_IDX(0, TX, ODD)].info &= ~BD_OWN_MASK; 00486 00487 // EP0 SETUP event (SETUP data received) 00488 EP0setupCallback(); 00489 00490 } else { 00491 // OUT packet 00492 if (TOK_PID((EP_BDT_IDX(num, dir, ev_odd))) == OUT_TOKEN) { 00493 if (num == 0) 00494 EP0out(); 00495 else { 00496 epComplete |= (1 << EP(num)); 00497 if ((instance->*(epCallback[EP(num) - 2]))()) { 00498 epComplete &= ~(1 << EP(num)); 00499 } 00500 } 00501 } 00502 00503 // IN packet 00504 if (TOK_PID((EP_BDT_IDX(num, dir, ev_odd))) == IN_TOKEN) { 00505 if (num == 0) { 00506 EP0in(); 00507 if (set_addr == 1) { 00508 USB0->ADDR = addr & 0x7F; 00509 set_addr = 0; 00510 } 00511 } 00512 else { 00513 epComplete |= (1 << (EP(num) + 1)); 00514 if ((instance->*(epCallback[EP(num) + 1 - 2]))()) { 00515 epComplete &= ~(1 << (EP(num) + 1)); 00516 } 00517 } 00518 } 00519 } 00520 00521 USB0->ISTAT = USB_ISTAT_TOKDNE_MASK; 00522 } 00523 00524 // sleep interrupt 00525 if (istat & 1<<4) { 00526 USB0->ISTAT |= USB_ISTAT_SLEEP_MASK; 00527 } 00528 00529 // error interrupt 00530 if (istat & USB_ISTAT_ERROR_MASK) { 00531 USB0->ERRSTAT = 0xFF; 00532 USB0->ISTAT |= USB_ISTAT_ERROR_MASK; 00533 } 00534 } 00535 00536 00537 #endif
Generated on Tue Jul 12 2022 20:01:24 by
1.7.2
