update DI disc code
[libogc.git] / libogc / wiisd.c
blob98b41ac18bb26d5c920ce8d6b73ee740fb089893
1 /*
3 wiisd.c
5 Hardware routines for reading and writing to the Wii's internal
6 SD slot.
8 Copyright (c) 2008
9 Michael Wiedenbauer (shagkur)
10 Dave Murphy (WinterMute)
11 Sven Peter <svpe@gmx.net>
13 Redistribution and use in source and binary forms, with or without modification,
14 are permitted provided that the following conditions are met:
16 1. Redistributions of source code must retain the above copyright notice,
17 this list of conditions and the following disclaimer.
18 2. Redistributions in binary form must reproduce the above copyright notice,
19 this list of conditions and the following disclaimer in the documentation and/or
20 other materials provided with the distribution.
21 3. The name of the author may not be used to endorse or promote products derived
22 from this software without specific prior written permission.
24 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
25 WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
26 AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
27 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
29 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
30 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
31 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
32 EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 #if defined(HW_RVL)
36 #include <stdlib.h>
37 #include <string.h>
38 #include <malloc.h>
39 #include <time.h>
40 #include <gcutil.h>
41 #include <ogc/ipc.h>
42 #include <unistd.h>
43 #include <ogc/disc_io.h>
44 #include <sdcard/wiisd_io.h>
46 #include <asm.h>
47 #include <processor.h>
49 #define SDIO_HEAPSIZE 0x400
51 #define PAGE_SIZE512 512
53 #define SDIOHCR_RESPONSE 0x10
54 #define SDIOHCR_HOSTCONTROL 0x28
55 #define SDIOHCR_POWERCONTROL 0x29
56 #define SDIOHCR_CLOCKCONTROL 0x2c
57 #define SDIOHCR_TIMEOUTCONTROL 0x2e
58 #define SDIOHCR_SOFTWARERESET 0x2f
60 #define SDIOHCR_HOSTCONTROL_4BIT 0x02
62 #define SDIO_DEFAULT_TIMEOUT 0xe
64 #define IOCTL_SDIO_WRITEHCREG 0x01
65 #define IOCTL_SDIO_READHCREG 0x02
66 #define IOCTL_SDIO_READCREG 0x03
67 #define IOCTL_SDIO_RESETCARD 0x04
68 #define IOCTL_SDIO_WRITECREG 0x05
69 #define IOCTL_SDIO_SETCLK 0x06
70 #define IOCTL_SDIO_SENDCMD 0x07
71 #define IOCTL_SDIO_SETBUSWIDTH 0x08
72 #define IOCTL_SDIO_READMCREG 0x09
73 #define IOCTL_SDIO_WRITEMCREG 0x0A
74 #define IOCTL_SDIO_GETSTATUS 0x0B
75 #define IOCTL_SDIO_GETOCR 0x0C
76 #define IOCTL_SDIO_READDATA 0x0D
77 #define IOCTL_SDIO_WRITEDATA 0x0E
79 #define SDIOCMD_TYPE_BC 1
80 #define SDIOCMD_TYPE_BCR 2
81 #define SDIOCMD_TYPE_AC 3
82 #define SDIOCMD_TYPE_ADTC 4
84 #define SDIO_RESPONSE_NONE 0
85 #define SDIO_RESPONSE_R1 1
86 #define SDIO_RESPONSE_R1B 2
87 #define SDIO_RESPOSNE_R2 3
88 #define SDIO_RESPONSE_R3 4
89 #define SDIO_RESPONSE_R4 5
90 #define SDIO_RESPONSE_R5 6
91 #define SDIO_RESPONSE_R6 7
93 #define SDIO_CMD_GOIDLE 0x00
94 #define SDIO_CMD_ALL_SENDCID 0x02
95 #define SDIO_CMD_SENDRCA 0x03
96 #define SDIO_CMD_SELECT 0x07
97 #define SDIO_CMD_DESELECT 0x07
98 #define SDIO_CMD_SENDIFCOND 0x08
99 #define SDIO_CMD_SENDCSD 0x09
100 #define SDIO_CMD_SENDCID 0x0A
101 #define SDIO_CMD_SENDSTATUS 0x0D
102 #define SDIO_CMD_SETBLOCKLEN 0x10
103 #define SDIO_CMD_READBLOCK 0x11
104 #define SDIO_CMD_READMULTIBLOCK 0x12
105 #define SDIO_CMD_WRITEBLOCK 0x18
106 #define SDIO_CMD_WRITEMULTIBLOCK 0x19
107 #define SDIO_CMD_APPCMD 0x37
109 #define SDIO_ACMD_SETBUSWIDTH 0x06
110 #define SDIO_ACMD_SENDSCR 0x33
111 #define SDIO_ACMD_SENDOPCOND 0x29
113 #define SDIO_STATUS_CARD_INSERTED 0x1
114 #define SDIO_STATUS_CARD_INITIALIZED 0x10000
115 #define SDIO_STATUS_CARD_SDHC 0x100000
117 #define READ_BL_LEN ((u8)(__sd0_csd[5]&0x0f))
118 #define WRITE_BL_LEN ((u8)(((__sd0_csd[12]&0x03)<<2)|((__sd0_csd[13]>>6)&0x03)))
120 struct _sdiorequest
122 u32 cmd;
123 u32 cmd_type;
124 u32 rsp_type;
125 u32 arg;
126 u32 blk_cnt;
127 u32 blk_size;
128 void *dma_addr;
129 u32 isdma;
130 u32 pad0;
133 struct _sdioresponse
135 u32 rsp_fields[3];
136 u32 acmd12_response;
139 static s32 hId = -1;
141 static s32 __sd0_fd = -1;
142 static u16 __sd0_rca = 0;
143 static s32 __sd0_initialized = 0;
144 static s32 __sd0_sdhc = 0;
145 static u8 __sd0_csd[16];
146 static u8 __sd0_cid[16];
148 static s32 __sdio_initialized = 0;
150 static char _sd0_fs[] ATTRIBUTE_ALIGN(32) = "/dev/sdio/slot0";
152 static s32 __sdio_sendcommand(u32 cmd,u32 cmd_type,u32 rsp_type,u32 arg,u32 blk_cnt,u32 blk_size,void *buffer,void *reply,u32 rlen)
154 s32 ret;
155 STACK_ALIGN(ioctlv,iovec,3,32);
156 STACK_ALIGN(struct _sdiorequest,request,1,32);
157 STACK_ALIGN(struct _sdioresponse,response,1,32);
159 request->cmd = cmd;
160 request->cmd_type = cmd_type;
161 request->rsp_type = rsp_type;
162 request->arg = arg;
163 request->blk_cnt = blk_cnt;
164 request->blk_size = blk_size;
165 request->dma_addr = buffer;
166 request->isdma = ((buffer!=NULL)?1:0);
167 request->pad0 = 0;
169 if(request->isdma || __sd0_sdhc == 1) {
170 iovec[0].data = request;
171 iovec[0].len = sizeof(struct _sdiorequest);
172 iovec[1].data = buffer;
173 iovec[1].len = (blk_size*blk_cnt);
174 iovec[2].data = response;
175 iovec[2].len = sizeof(struct _sdioresponse);
176 ret = IOS_Ioctlv(__sd0_fd,IOCTL_SDIO_SENDCMD,2,1,iovec);
177 } else
178 ret = IOS_Ioctl(__sd0_fd,IOCTL_SDIO_SENDCMD,request,sizeof(struct _sdiorequest),response,sizeof(struct _sdioresponse));
180 if(reply && !(rlen>16)) memcpy(reply,response,rlen);
182 // printf(" cmd= %08x\n", cmd);
184 return ret;
187 static s32 __sdio_setclock(u32 set)
189 s32 ret;
190 STACK_ALIGN(u32,clock,1,32);
192 *clock = set;
193 ret = IOS_Ioctl(__sd0_fd,IOCTL_SDIO_SETCLK,clock,sizeof(u32),NULL,0);
195 return ret;
197 static s32 __sdio_getstatus()
199 s32 ret;
200 STACK_ALIGN(u32,status,1,32);
202 ret = IOS_Ioctl(__sd0_fd,IOCTL_SDIO_GETSTATUS,NULL,0,status,sizeof(u32));
203 if(ret<0) return ret;
205 return *status;
208 static s32 __sdio_resetcard()
210 s32 ret;
211 STACK_ALIGN(u32,status,1,32);
213 __sd0_rca = 0;
214 ret = IOS_Ioctl(__sd0_fd,IOCTL_SDIO_RESETCARD,NULL,0,status,sizeof(u32));
215 if(ret<0) return ret;
217 __sd0_rca = (u16)(*status>>16);
218 return (*status&0xffff);
221 static s32 __sdio_gethcr(u8 reg, u8 size, u32 *val)
223 s32 ret;
224 STACK_ALIGN(u32,hcr_value,1,32);
225 STACK_ALIGN(u32,hcr_query,6,32);
227 if(val==NULL) return IPC_EINVAL;
229 *hcr_value = 0;
230 *val = 0;
231 hcr_query[0] = reg;
232 hcr_query[1] = 0;
233 hcr_query[2] = 0;
234 hcr_query[3] = size;
235 hcr_query[4] = 0;
236 hcr_query[5] = 0;
237 ret = IOS_Ioctl(__sd0_fd,IOCTL_SDIO_READHCREG,(void*)hcr_query,24,hcr_value,sizeof(u32));
238 *val = *hcr_value;
241 return ret;
244 static s32 __sdio_sethcr(u8 reg, u8 size, u32 data)
246 s32 ret;
247 STACK_ALIGN(u32,hcr_query,6,32);
249 hcr_query[0] = reg;
250 hcr_query[1] = 0;
251 hcr_query[2] = 0;
252 hcr_query[3] = size;
253 hcr_query[4] = data;
254 hcr_query[5] = 0;
255 ret = IOS_Ioctl(__sd0_fd,IOCTL_SDIO_WRITEHCREG,(void*)hcr_query,24,NULL,0);
258 return ret;
261 static s32 __sdio_waithcr(u8 reg, u8 size, u8 unset, u32 mask)
263 u32 val;
264 s32 ret;
265 s32 tries = 10;
267 while(tries-- > 0)
269 ret = __sdio_gethcr(reg, size, &val);
270 if(ret < 0) return ret;
271 if((unset && !(val & mask)) || (!unset && (val & mask))) return 0;
272 usleep(10000);
275 return -1;
278 static s32 __sdio_setbuswidth(u32 bus_width)
280 s32 ret;
281 u32 hc_reg = 0;
283 ret = __sdio_gethcr(SDIOHCR_HOSTCONTROL, 1, &hc_reg);
284 if(ret<0) return ret;
286 hc_reg &= 0xff;
287 hc_reg &= ~SDIOHCR_HOSTCONTROL_4BIT;
288 if(bus_width==4) hc_reg |= SDIOHCR_HOSTCONTROL_4BIT;
290 return __sdio_sethcr(SDIOHCR_HOSTCONTROL, 1, hc_reg);
293 static s32 __sd0_getstatus()
295 s32 ret;
296 u32 status = 0;
298 ret = __sdio_sendcommand(SDIO_CMD_SENDSTATUS,SDIOCMD_TYPE_AC,SDIO_RESPONSE_R1,(__sd0_rca<<16),0,0,NULL,&status,sizeof(u32));
299 if(ret<0) return ret;
301 return status;
304 static s32 __sd0_getrca()
306 s32 ret;
307 u32 rca;
309 ret = __sdio_sendcommand(SDIO_CMD_SENDRCA,0,SDIO_RESPONSE_R5,0,0,0,NULL,&rca,sizeof(rca));
310 if(ret<0) return ret;
312 __sd0_rca = (u16)(rca>>16);
313 return (rca&0xffff);
316 static s32 __sd0_select()
318 s32 ret;
320 ret = __sdio_sendcommand(SDIO_CMD_SELECT,SDIOCMD_TYPE_AC,SDIO_RESPONSE_R1B,(__sd0_rca<<16),0,0,NULL,NULL,0);
322 return ret;
325 static s32 __sd0_deselect()
327 s32 ret;
329 ret = __sdio_sendcommand(SDIO_CMD_DESELECT,SDIOCMD_TYPE_AC,SDIO_RESPONSE_R1B,0,0,0,NULL,NULL,0);
331 return ret;
334 static s32 __sd0_setblocklength(u32 blk_len)
336 s32 ret;
338 ret = __sdio_sendcommand(SDIO_CMD_SETBLOCKLEN,SDIOCMD_TYPE_AC,SDIO_RESPONSE_R1,blk_len,0,0,NULL,NULL,0);
340 return ret;
343 static s32 __sd0_setbuswidth(u32 bus_width)
345 u16 val;
346 s32 ret;
348 val = 0x0000;
349 if(bus_width==4) val = 0x0002;
351 ret = __sdio_sendcommand(SDIO_CMD_APPCMD,SDIOCMD_TYPE_AC,SDIO_RESPONSE_R1,(__sd0_rca<<16),0,0,NULL,NULL,0);
352 if(ret<0) return ret;
354 ret = __sdio_sendcommand(SDIO_ACMD_SETBUSWIDTH,SDIOCMD_TYPE_AC,SDIO_RESPONSE_R1,val,0,0,NULL,NULL,0);
356 return ret;
359 static s32 __sd0_getcsd()
361 s32 ret;
363 ret = __sdio_sendcommand(SDIO_CMD_SENDCSD,SDIOCMD_TYPE_AC,SDIO_RESPOSNE_R2,(__sd0_rca<<16),0,0,NULL,__sd0_csd,16);
365 return ret;
368 static s32 __sd0_getcid()
370 s32 ret;
372 ret = __sdio_sendcommand(SDIO_CMD_ALL_SENDCID,0,SDIO_RESPOSNE_R2,(__sd0_rca<<16),0,0,NULL,__sd0_cid,16);
374 return ret;
378 static bool __sd0_initio()
380 s32 ret;
381 s32 tries;
382 u32 status;
383 struct _sdioresponse resp;
385 __sdio_resetcard();
386 status = __sdio_getstatus();
388 if(!(status & SDIO_STATUS_CARD_INSERTED))
389 return false;
391 if(!(status & SDIO_STATUS_CARD_INITIALIZED))
393 // IOS doesn't like this card, so we need to convice it to accept it.
395 // reopen the handle which makes IOS clean stuff up
396 IOS_Close(__sd0_fd);
397 __sd0_fd = IOS_Open(_sd0_fs,1);
399 // reset the host controller
400 if(__sdio_sethcr(SDIOHCR_SOFTWARERESET, 1, 7) < 0) goto fail;
401 if(__sdio_waithcr(SDIOHCR_SOFTWARERESET, 1, 1, 7) < 0) goto fail;
403 // initialize interrupts (sd_reset_card does this on success)
404 __sdio_sethcr(0x34, 4, 0x13f00c3);
405 __sdio_sethcr(0x38, 4, 0x13f00c3);
407 // enable power
408 __sd0_sdhc = 1;
409 ret = __sdio_sethcr(SDIOHCR_POWERCONTROL, 1, 0xe);
410 if(ret < 0) goto fail;
411 ret = __sdio_sethcr(SDIOHCR_POWERCONTROL, 1, 0xf);
412 if(ret < 0) goto fail;
414 // enable internal clock, wait until it gets stable and enable sd clock
415 ret = __sdio_sethcr(SDIOHCR_CLOCKCONTROL, 2, 0);
416 if(ret < 0) goto fail;
417 ret = __sdio_sethcr(SDIOHCR_CLOCKCONTROL, 2, 0x101);
418 if(ret < 0) goto fail;
419 ret = __sdio_waithcr(SDIOHCR_CLOCKCONTROL, 2, 0, 2);
420 if(ret < 0) goto fail;
421 ret = __sdio_sethcr(SDIOHCR_CLOCKCONTROL, 2, 0x107);
422 if(ret < 0) goto fail;
424 // setup timeout
425 ret = __sdio_sethcr(SDIOHCR_TIMEOUTCONTROL, 1, SDIO_DEFAULT_TIMEOUT);
426 if(ret < 0) goto fail;
428 // standard SDHC initialization process
429 ret = __sdio_sendcommand(SDIO_CMD_GOIDLE, 0, 0, 0, 0, 0, NULL, NULL, 0);
430 if(ret < 0) goto fail;
431 ret = __sdio_sendcommand(SDIO_CMD_SENDIFCOND, 0, SDIO_RESPONSE_R6, 0x1aa, 0, 0, NULL, &resp, sizeof(resp));
432 if(ret < 0) goto fail;
433 if((resp.rsp_fields[0] & 0xff) != 0xaa) goto fail;
435 tries = 10;
436 while(tries-- > 0)
438 ret = __sdio_sendcommand(SDIO_CMD_APPCMD, SDIOCMD_TYPE_AC,SDIO_RESPONSE_R1,0,0,0,NULL,NULL,0);
439 if(ret < 0) goto fail;
440 ret = __sdio_sendcommand(SDIO_ACMD_SENDOPCOND, 0, SDIO_RESPONSE_R3, 0x40300000, 0, 0, NULL, &resp, sizeof(resp));
441 if(ret < 0) goto fail;
442 if(resp.rsp_fields[0] & (1 << 31)) break;
444 usleep(10000);
446 if(tries < 0) goto fail;
448 // FIXME: SDv2 cards which are not high-capacity won't work :/
449 if(resp.rsp_fields[0] & (1 << 30))
450 __sd0_sdhc = 1;
451 else
452 __sd0_sdhc = 0;
454 ret = __sd0_getcid();
455 if(ret < 0) goto fail;
456 ret = __sd0_getrca();
457 if(ret < 0) goto fail;
459 else if(status&SDIO_STATUS_CARD_SDHC)
460 __sd0_sdhc = 1;
461 else
462 __sd0_sdhc = 0;
464 ret = __sdio_setbuswidth(4);
465 if(ret<0) return false;
467 ret = __sdio_setclock(1);
468 if(ret<0) return false;
470 ret = __sd0_select();
471 if(ret<0) return false;
473 ret = __sd0_setblocklength(PAGE_SIZE512);
474 if(ret<0) {
475 ret = __sd0_deselect();
476 return false;
479 ret = __sd0_setbuswidth(4);
480 if(ret<0) {
481 ret = __sd0_deselect();
482 return false;
484 __sd0_deselect();
486 __sd0_initialized = 1;
487 return true;
489 fail:
490 __sdio_sethcr(SDIOHCR_SOFTWARERESET, 1, 7);
491 __sdio_waithcr(SDIOHCR_SOFTWARERESET, 1, 1, 7);
492 IOS_Close(__sd0_fd);
493 __sd0_fd = IOS_Open(_sd0_fs,1);
494 return false;
497 bool sdio_Deinitialize()
499 if(__sd0_fd>=0)
500 IOS_Close(__sd0_fd);
502 __sd0_fd = -1;
503 __sdio_initialized = 0;
504 return true;
507 bool sdio_Startup()
509 if(__sdio_initialized==1) return true;
511 if(hId<0) {
512 hId = iosCreateHeap(SDIO_HEAPSIZE);
513 if(hId<0) return false;
516 __sd0_fd = IOS_Open(_sd0_fs,1);
518 if(__sd0_fd<0) {
519 sdio_Deinitialize();
520 return false;
523 if(__sd0_initio()==false) {
524 sdio_Deinitialize();
525 return false;
527 __sdio_initialized = 1;
528 return true;
533 bool sdio_Shutdown()
535 if(__sd0_initialized==0) return false;
537 sdio_Deinitialize();
539 __sd0_initialized = 0;
540 return true;
543 bool sdio_ReadSectors(sec_t sector, sec_t numSectors,void* buffer)
545 s32 ret;
546 u8 *rbuf,*ptr;
547 sec_t blk_off;
549 if(buffer==NULL) return false;
551 ret = __sd0_select();
552 if(ret<0) return false;
554 if((u32)buffer & 0x1F) {
556 rbuf = iosAlloc(hId,PAGE_SIZE512);
557 if(rbuf==NULL) {
558 __sd0_deselect();
559 return false;
562 ptr = (u8*)buffer;
563 while(numSectors>0) {
564 if(__sd0_sdhc == 0) blk_off = (sector*PAGE_SIZE512);
565 else blk_off = sector;
566 ret = __sdio_sendcommand(SDIO_CMD_READMULTIBLOCK,SDIOCMD_TYPE_AC,SDIO_RESPONSE_R1,blk_off,1,PAGE_SIZE512,rbuf,NULL,0);
567 if(ret>=0) {
568 memcpy(ptr,rbuf,PAGE_SIZE512);
569 ptr += PAGE_SIZE512;
570 sector++;
571 numSectors--;
572 } else
573 break;
575 iosFree(hId,rbuf);
576 } else {
577 if(__sd0_sdhc == 0) sector *= PAGE_SIZE512;
578 ret = __sdio_sendcommand(SDIO_CMD_READMULTIBLOCK,SDIOCMD_TYPE_AC,SDIO_RESPONSE_R1,sector,numSectors,PAGE_SIZE512,buffer,NULL,0);
581 __sd0_deselect();
583 return (ret>=0);
586 bool sdio_WriteSectors(sec_t sector, sec_t numSectors,const void* buffer)
588 s32 ret;
589 u8 *wbuf,*ptr;
590 u32 blk_off;
592 if(buffer==NULL) return false;
594 ret = __sd0_select();
595 if(ret<0) return false;
597 if((u32)buffer & 0x1F) {
598 wbuf = iosAlloc(hId,PAGE_SIZE512);
599 if(wbuf==NULL) {
600 __sd0_deselect();
601 return false;
604 ptr = (u8*)buffer;
605 while(numSectors>0) {
606 if(__sd0_sdhc == 0) blk_off = (sector*PAGE_SIZE512);
607 else blk_off = sector;
608 memcpy(wbuf,ptr,PAGE_SIZE512);
609 ret = __sdio_sendcommand(SDIO_CMD_WRITEMULTIBLOCK,SDIOCMD_TYPE_AC,SDIO_RESPONSE_R1,blk_off,1,PAGE_SIZE512,wbuf,NULL,0);
610 if(ret>=0) {
611 ptr += PAGE_SIZE512;
612 sector++;
613 numSectors--;
614 } else
615 break;
617 iosFree(hId,wbuf);
618 } else {
619 if(__sd0_sdhc == 0) sector *= PAGE_SIZE512;
620 ret = __sdio_sendcommand(SDIO_CMD_WRITEMULTIBLOCK,SDIOCMD_TYPE_AC,SDIO_RESPONSE_R1,sector,numSectors,PAGE_SIZE512,(char *)buffer,NULL,0);
623 __sd0_deselect();
625 return (ret>=0);
628 bool sdio_ClearStatus()
630 return true;
633 bool sdio_IsInserted()
635 return ((__sdio_getstatus() & SDIO_STATUS_CARD_INSERTED) ==
636 SDIO_STATUS_CARD_INSERTED);
639 bool sdio_IsInitialized()
641 return ((__sdio_getstatus() & SDIO_STATUS_CARD_INITIALIZED) ==
642 SDIO_STATUS_CARD_INITIALIZED);
645 const DISC_INTERFACE __io_wiisd = {
646 DEVICE_TYPE_WII_SD,
647 FEATURE_MEDIUM_CANREAD | FEATURE_MEDIUM_CANWRITE | FEATURE_WII_SD,
648 (FN_MEDIUM_STARTUP)&sdio_Startup,
649 (FN_MEDIUM_ISINSERTED)&sdio_IsInserted,
650 (FN_MEDIUM_READSECTORS)&sdio_ReadSectors,
651 (FN_MEDIUM_WRITESECTORS)&sdio_WriteSectors,
652 (FN_MEDIUM_CLEARSTATUS)&sdio_ClearStatus,
653 (FN_MEDIUM_SHUTDOWN)&sdio_Shutdown
656 #endif