xref: /onnv-gate/usr/src/uts/common/io/sdcard/impl/sda_init.c (revision 8289:591a483170d9)
17302Sgdamore@opensolaris.org /*
27302Sgdamore@opensolaris.org  * CDDL HEADER START
37302Sgdamore@opensolaris.org  *
47302Sgdamore@opensolaris.org  * The contents of this file are subject to the terms of the
57302Sgdamore@opensolaris.org  * Common Development and Distribution License (the "License").
67302Sgdamore@opensolaris.org  * You may not use this file except in compliance with the License.
77302Sgdamore@opensolaris.org  *
87302Sgdamore@opensolaris.org  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
97302Sgdamore@opensolaris.org  * or http://www.opensolaris.org/os/licensing.
107302Sgdamore@opensolaris.org  * See the License for the specific language governing permissions
117302Sgdamore@opensolaris.org  * and limitations under the License.
127302Sgdamore@opensolaris.org  *
137302Sgdamore@opensolaris.org  * When distributing Covered Code, include this CDDL HEADER in each
147302Sgdamore@opensolaris.org  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
157302Sgdamore@opensolaris.org  * If applicable, add the following below this CDDL HEADER, with the
167302Sgdamore@opensolaris.org  * fields enclosed by brackets "[]" replaced with your own identifying
177302Sgdamore@opensolaris.org  * information: Portions Copyright [yyyy] [name of copyright owner]
187302Sgdamore@opensolaris.org  *
197302Sgdamore@opensolaris.org  * CDDL HEADER END
207302Sgdamore@opensolaris.org  */
217302Sgdamore@opensolaris.org /*
227302Sgdamore@opensolaris.org  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
237302Sgdamore@opensolaris.org  * Use is subject to license terms.
247302Sgdamore@opensolaris.org  */
257302Sgdamore@opensolaris.org 
267302Sgdamore@opensolaris.org /*
277302Sgdamore@opensolaris.org  * SD card initialization support.
287302Sgdamore@opensolaris.org  */
297302Sgdamore@opensolaris.org 
307302Sgdamore@opensolaris.org #include <sys/types.h>
317302Sgdamore@opensolaris.org #include <sys/ddi.h>
327302Sgdamore@opensolaris.org #include <sys/sunddi.h>
337302Sgdamore@opensolaris.org #include <sys/sdcard/sda.h>
347302Sgdamore@opensolaris.org #include <sys/sdcard/sda_impl.h>
357302Sgdamore@opensolaris.org 
367302Sgdamore@opensolaris.org 
377302Sgdamore@opensolaris.org /*
387302Sgdamore@opensolaris.org  * Local Prototypes.
397302Sgdamore@opensolaris.org  */
407302Sgdamore@opensolaris.org 
417302Sgdamore@opensolaris.org static sda_err_t sda_init_mmc(sda_slot_t *);
427302Sgdamore@opensolaris.org static sda_err_t sda_init_sdio(sda_slot_t *);
437302Sgdamore@opensolaris.org static sda_err_t sda_init_sdmem(sda_slot_t *);
447302Sgdamore@opensolaris.org static sda_err_t sda_init_cmd(sda_slot_t *, sda_index_t, uint32_t,
457302Sgdamore@opensolaris.org     sda_rtype_t, uint32_t *);
467302Sgdamore@opensolaris.org static sda_err_t sda_init_acmd(sda_slot_t *, sda_index_t, uint32_t,
477302Sgdamore@opensolaris.org     sda_rtype_t, uint32_t *);
487302Sgdamore@opensolaris.org static sda_err_t sda_init_blocklen(sda_slot_t *);
497302Sgdamore@opensolaris.org static sda_err_t sda_init_width(sda_slot_t *);
507302Sgdamore@opensolaris.org static sda_err_t sda_init_rca(sda_slot_t *);
517302Sgdamore@opensolaris.org static sda_err_t sda_init_ifcond(sda_slot_t *);
527302Sgdamore@opensolaris.org static sda_err_t sda_init_highspeed(sda_slot_t *);
537302Sgdamore@opensolaris.org static sda_err_t sda_init_switch(sda_slot_t *, uint8_t, uint8_t, uint8_t,
547302Sgdamore@opensolaris.org     uint8_t *);
557302Sgdamore@opensolaris.org static void sda_init_clock(sda_slot_t *, uint32_t);
567302Sgdamore@opensolaris.org 
577302Sgdamore@opensolaris.org /*
587302Sgdamore@opensolaris.org  * Implementation.
597302Sgdamore@opensolaris.org  */
607302Sgdamore@opensolaris.org sda_err_t
sda_init_cmd(sda_slot_t * slot,sda_index_t cmd,uint32_t arg,sda_rtype_t rtype,uint32_t * resp)617302Sgdamore@opensolaris.org sda_init_cmd(sda_slot_t *slot, sda_index_t cmd, uint32_t arg,
627302Sgdamore@opensolaris.org     sda_rtype_t rtype, uint32_t *resp)
637302Sgdamore@opensolaris.org {
647302Sgdamore@opensolaris.org 	sda_cmd_t	*cmdp;
657302Sgdamore@opensolaris.org 	sda_err_t	errno;
667302Sgdamore@opensolaris.org 
677302Sgdamore@opensolaris.org 	cmdp = sda_cmd_alloc(slot, cmd, arg, rtype, NULL, KM_SLEEP);
687302Sgdamore@opensolaris.org 
697302Sgdamore@opensolaris.org 	cmdp->sc_flags |= SDA_CMDF_INIT;
707302Sgdamore@opensolaris.org 
717302Sgdamore@opensolaris.org 	errno = sda_cmd_exec(slot, cmdp, resp);
727302Sgdamore@opensolaris.org 
737302Sgdamore@opensolaris.org 	sda_cmd_free(cmdp);
747302Sgdamore@opensolaris.org 
757302Sgdamore@opensolaris.org 	return (errno);
767302Sgdamore@opensolaris.org }
777302Sgdamore@opensolaris.org 
787302Sgdamore@opensolaris.org sda_err_t
sda_init_acmd(sda_slot_t * slot,sda_index_t cmd,uint32_t arg,sda_rtype_t rtype,uint32_t * resp)797302Sgdamore@opensolaris.org sda_init_acmd(sda_slot_t *slot, sda_index_t cmd, uint32_t arg,
807302Sgdamore@opensolaris.org     sda_rtype_t rtype, uint32_t *resp)
817302Sgdamore@opensolaris.org {
827302Sgdamore@opensolaris.org 	sda_cmd_t	*cmdp;
837302Sgdamore@opensolaris.org 	sda_err_t	errno;
847302Sgdamore@opensolaris.org 
857302Sgdamore@opensolaris.org 	cmdp = sda_cmd_alloc_acmd(slot, cmd, arg, rtype, NULL, KM_SLEEP);
867302Sgdamore@opensolaris.org 
877302Sgdamore@opensolaris.org 	cmdp->sc_flags |= SDA_CMDF_INIT;
887302Sgdamore@opensolaris.org 
897302Sgdamore@opensolaris.org 	errno = sda_cmd_exec(slot, cmdp, resp);
907302Sgdamore@opensolaris.org 
917302Sgdamore@opensolaris.org 	sda_cmd_free(cmdp);
927302Sgdamore@opensolaris.org 
937302Sgdamore@opensolaris.org 	return (errno);
947302Sgdamore@opensolaris.org }
957302Sgdamore@opensolaris.org 
967302Sgdamore@opensolaris.org sda_err_t
sda_init_sdio(sda_slot_t * slot)977302Sgdamore@opensolaris.org sda_init_sdio(sda_slot_t *slot)
987302Sgdamore@opensolaris.org {
997302Sgdamore@opensolaris.org 	slot->s_num_io = 0;
1007302Sgdamore@opensolaris.org 
1017302Sgdamore@opensolaris.org 	/*
1027302Sgdamore@opensolaris.org 	 * TODO: SDIO: We need to initialize the SDIO OCR register using
1037302Sgdamore@opensolaris.org 	 * the special CMD_IO_SEND_OCR (CMD5) command.
1047302Sgdamore@opensolaris.org 	 */
1057302Sgdamore@opensolaris.org 	return (SDA_EOK);
1067302Sgdamore@opensolaris.org }
1077302Sgdamore@opensolaris.org 
1087302Sgdamore@opensolaris.org sda_err_t
sda_init_sdmem(sda_slot_t * slot)1097302Sgdamore@opensolaris.org sda_init_sdmem(sda_slot_t *slot)
1107302Sgdamore@opensolaris.org {
1117302Sgdamore@opensolaris.org 	uint32_t	ocr;
1127302Sgdamore@opensolaris.org 	int		count;
1137302Sgdamore@opensolaris.org 
1147302Sgdamore@opensolaris.org 	slot->s_flags &= ~SLOTF_SDMEM;
1157302Sgdamore@opensolaris.org 
1167302Sgdamore@opensolaris.org 	/*
1177302Sgdamore@opensolaris.org 	 * Try sending the ACMD41 to query the OCR (Op Cond Register).
1187302Sgdamore@opensolaris.org 	 */
1197302Sgdamore@opensolaris.org 	if (sda_init_acmd(slot, ACMD_SD_SEND_OCR, 0, R3, &ocr) != SDA_EOK) {
1207302Sgdamore@opensolaris.org 		/*
1217302Sgdamore@opensolaris.org 		 * Card failed to respond to query, not an SD card?
1227302Sgdamore@opensolaris.org 		 * We send GO_IDLE to clear any error status on the
1237302Sgdamore@opensolaris.org 		 * card.
1247302Sgdamore@opensolaris.org 		 */
1257302Sgdamore@opensolaris.org 		(void) sda_init_cmd(slot, CMD_GO_IDLE, 0, R0, NULL);
1267302Sgdamore@opensolaris.org 		return (SDA_EOK);
1277302Sgdamore@opensolaris.org 	}
1287302Sgdamore@opensolaris.org 
1297302Sgdamore@opensolaris.org 	/*
1307302Sgdamore@opensolaris.org 	 * Now we have to send our OCR value, along with the HCS (High
1317302Sgdamore@opensolaris.org 	 * Capacity Support) bit.  The HCS bit is required, to
1327302Sgdamore@opensolaris.org 	 * activate high capacity cards.  We only set the HCS bit if
1337302Sgdamore@opensolaris.org 	 * the card responded to CMD8 (SEND_IFCOND), indicating that
1347302Sgdamore@opensolaris.org 	 * it supports the new protocol.
1357302Sgdamore@opensolaris.org 	 *
1367302Sgdamore@opensolaris.org 	 * Note that the HCS bit occupies the same location as the CCS bit
1377302Sgdamore@opensolaris.org 	 * in the response.
1387302Sgdamore@opensolaris.org 	 */
1397302Sgdamore@opensolaris.org 	if ((ocr & slot->s_cur_ocr) == 0) {
1407302Sgdamore@opensolaris.org 		sda_slot_err(slot, "SD card not compatible with host");
1417302Sgdamore@opensolaris.org 		return (SDA_ENOTSUP);
1427302Sgdamore@opensolaris.org 	}
1437302Sgdamore@opensolaris.org 	/* set the HCS bit if its a ver 2.00 card */
1447302Sgdamore@opensolaris.org 	if (slot->s_flags & SLOTF_IFCOND) {
1457302Sgdamore@opensolaris.org 		ocr |= OCR_CCS;
1467302Sgdamore@opensolaris.org 	}
1477302Sgdamore@opensolaris.org 
1487302Sgdamore@opensolaris.org 	/* make sure card is powered up */
1497302Sgdamore@opensolaris.org 	for (count = 1000000; count != 0; count -= 10000) {
1507302Sgdamore@opensolaris.org 		uint32_t	r3;
1517302Sgdamore@opensolaris.org 
1527302Sgdamore@opensolaris.org 		if (sda_init_acmd(slot, ACMD_SD_SEND_OCR, ocr, R3, &r3) != 0) {
1537302Sgdamore@opensolaris.org 			sda_slot_err(slot, "SD card failed to power up");
1547302Sgdamore@opensolaris.org 			return (SDA_ENOTSUP);
1557302Sgdamore@opensolaris.org 		}
1567302Sgdamore@opensolaris.org 
1577302Sgdamore@opensolaris.org 		/* Now check the busy bit */
1587302Sgdamore@opensolaris.org 		if (r3 & OCR_POWER_UP) {
1597302Sgdamore@opensolaris.org 			slot->s_flags |= SLOTF_SDMEM;
1607302Sgdamore@opensolaris.org 			if ((slot->s_flags & SLOTF_IFCOND) &&
1617302Sgdamore@opensolaris.org 			    (r3 & OCR_CCS)) {
1627302Sgdamore@opensolaris.org 				slot->s_flags |= SLOTF_SDHC;
1637302Sgdamore@opensolaris.org 			} else {
1647302Sgdamore@opensolaris.org 				slot->s_flags &= ~SLOTF_SDHC;
1657302Sgdamore@opensolaris.org 			}
1667302Sgdamore@opensolaris.org 			return (0);
1677302Sgdamore@opensolaris.org 		}
1687302Sgdamore@opensolaris.org 
1697302Sgdamore@opensolaris.org 		drv_usecwait(10000);
1707302Sgdamore@opensolaris.org 	}
1717302Sgdamore@opensolaris.org 
1727302Sgdamore@opensolaris.org 	sda_slot_err(slot, "SD card timed out during power up");
1737302Sgdamore@opensolaris.org 	return (SDA_ETIME);
1747302Sgdamore@opensolaris.org }
1757302Sgdamore@opensolaris.org 
1767302Sgdamore@opensolaris.org sda_err_t
sda_init_mmc(sda_slot_t * slot)1777302Sgdamore@opensolaris.org sda_init_mmc(sda_slot_t *slot)
1787302Sgdamore@opensolaris.org {
1797302Sgdamore@opensolaris.org 	uint32_t	ocr;
1807302Sgdamore@opensolaris.org 	int		count;
1817302Sgdamore@opensolaris.org 
1827302Sgdamore@opensolaris.org 	slot->s_flags &= ~SLOTF_MMC;
1837302Sgdamore@opensolaris.org 
1847302Sgdamore@opensolaris.org 	/*
1857302Sgdamore@opensolaris.org 	 * If the card has already been identified as an SD card, then
1867302Sgdamore@opensolaris.org 	 * cannot also be an MMC card, so don't probe it as such.
1877302Sgdamore@opensolaris.org 	 */
1887302Sgdamore@opensolaris.org 	if (slot->s_flags & SLOTF_SD) {
1897302Sgdamore@opensolaris.org 		return (SDA_EOK);
1907302Sgdamore@opensolaris.org 	}
1917302Sgdamore@opensolaris.org 
1927302Sgdamore@opensolaris.org 	/*
1937302Sgdamore@opensolaris.org 	 * Try sending the CMD1 to query the OCR.
1947302Sgdamore@opensolaris.org 	 */
1957302Sgdamore@opensolaris.org 	if (sda_init_cmd(slot, CMD_SEND_OCR, 0, R3, &ocr) != 0) {
1967302Sgdamore@opensolaris.org 		/*
1977302Sgdamore@opensolaris.org 		 * Card failed to respond to query, not an MMC card?
1987302Sgdamore@opensolaris.org 		 * We send GO_IDLE to clear any error status on the
1997302Sgdamore@opensolaris.org 		 * card.
2007302Sgdamore@opensolaris.org 		 */
2017302Sgdamore@opensolaris.org 		(void) sda_init_cmd(slot, CMD_GO_IDLE, 0, R0, NULL);
2027302Sgdamore@opensolaris.org 		return (SDA_EOK);
2037302Sgdamore@opensolaris.org 	}
2047302Sgdamore@opensolaris.org 
2057302Sgdamore@opensolaris.org 	if ((ocr & slot->s_cur_ocr) == 0) {
2067302Sgdamore@opensolaris.org 		sda_slot_err(slot, "MMC card not compatible with host");
2077302Sgdamore@opensolaris.org 		return (SDA_ENOTSUP);
2087302Sgdamore@opensolaris.org 	}
2097302Sgdamore@opensolaris.org 
2107302Sgdamore@opensolaris.org 	/* make sure card is powered up */
2117302Sgdamore@opensolaris.org 	for (count = 1000000; count != 0; count -= 10000) {
2127302Sgdamore@opensolaris.org 		uint32_t	r3;
2137302Sgdamore@opensolaris.org 
2147302Sgdamore@opensolaris.org 		if (sda_init_cmd(slot, CMD_SEND_OCR, ocr, R3, &r3) != 0) {
2157302Sgdamore@opensolaris.org 			sda_slot_err(slot, "MMC card failed to power up");
2167302Sgdamore@opensolaris.org 			return (SDA_ENOTSUP);
2177302Sgdamore@opensolaris.org 		}
2187302Sgdamore@opensolaris.org 
2197302Sgdamore@opensolaris.org 		/* Now check the busy bit */
2207302Sgdamore@opensolaris.org 		if (r3 & OCR_POWER_UP) {
2217302Sgdamore@opensolaris.org 			slot->s_flags |= SLOTF_MMC;
2227302Sgdamore@opensolaris.org 			return (SDA_EOK);
2237302Sgdamore@opensolaris.org 		}
2247302Sgdamore@opensolaris.org 
2257302Sgdamore@opensolaris.org 		drv_usecwait(10000);
2267302Sgdamore@opensolaris.org 	}
2277302Sgdamore@opensolaris.org 
2287302Sgdamore@opensolaris.org 	sda_slot_err(slot, "MMC card timed out during power up");
2297302Sgdamore@opensolaris.org 	return (SDA_ETIME);
2307302Sgdamore@opensolaris.org }
2317302Sgdamore@opensolaris.org 
2327302Sgdamore@opensolaris.org sda_err_t
sda_init_card(sda_slot_t * slot)2337302Sgdamore@opensolaris.org sda_init_card(sda_slot_t *slot)
2347302Sgdamore@opensolaris.org {
2357302Sgdamore@opensolaris.org 	int		rv;
2367302Sgdamore@opensolaris.org 	uint32_t	resp;
2377302Sgdamore@opensolaris.org 	uint32_t	val;
2387302Sgdamore@opensolaris.org 
2397302Sgdamore@opensolaris.org 	/*
2407302Sgdamore@opensolaris.org 	 * Power off slot/card initially.
2417302Sgdamore@opensolaris.org 	 */
2427302Sgdamore@opensolaris.org 	sda_slot_power_off(slot);
2437302Sgdamore@opensolaris.org 
2447302Sgdamore@opensolaris.org 	/*
2457302Sgdamore@opensolaris.org 	 * Apply initial power to the slot.
2467302Sgdamore@opensolaris.org 	 */
2477302Sgdamore@opensolaris.org 	if ((rv = sda_slot_power_on(slot)) != 0) {
2487302Sgdamore@opensolaris.org 		return (rv);
2497302Sgdamore@opensolaris.org 	}
2507302Sgdamore@opensolaris.org 
2517302Sgdamore@opensolaris.org 	/*
2527302Sgdamore@opensolaris.org 	 * First enable the clock, but only at 400 kHz.  All cards are
2537302Sgdamore@opensolaris.org 	 * supposed to be able to operate between this speed and 100
2547302Sgdamore@opensolaris.org 	 * kHz, and all hosts must be able to pick a speed between 100
2557302Sgdamore@opensolaris.org 	 * kHz and 400 kHz.
2567302Sgdamore@opensolaris.org 	 *
2577302Sgdamore@opensolaris.org 	 * Once we know what the device can support, then we speed up.
2587302Sgdamore@opensolaris.org 	 */
2597302Sgdamore@opensolaris.org 	sda_init_clock(slot, 400000);
2607302Sgdamore@opensolaris.org 
2617302Sgdamore@opensolaris.org 	if ((rv = sda_init_ifcond(slot)) != SDA_EOK) {
2627302Sgdamore@opensolaris.org 		goto done;
2637302Sgdamore@opensolaris.org 	}
2647302Sgdamore@opensolaris.org 
2657302Sgdamore@opensolaris.org 	if (((rv = sda_init_sdio(slot)) != SDA_EOK) ||
2667302Sgdamore@opensolaris.org 	    ((rv = sda_init_sdmem(slot)) != SDA_EOK) ||
2677302Sgdamore@opensolaris.org 	    ((rv = sda_init_mmc(slot)) != SDA_EOK)) {
2687302Sgdamore@opensolaris.org 
2697302Sgdamore@opensolaris.org 		/* message will already have been logged */
2707302Sgdamore@opensolaris.org 		goto done;
2717302Sgdamore@opensolaris.org 	}
2727302Sgdamore@opensolaris.org 
2737302Sgdamore@opensolaris.org 	if ((slot->s_flags & (SLOTF_MEMORY | SLOTF_SDIO)) == 0) {
2747302Sgdamore@opensolaris.org 		sda_slot_err(slot, "Unidentified card type");
2757302Sgdamore@opensolaris.org 		rv = SDA_ENOTSUP;
2767302Sgdamore@opensolaris.org 		goto done;
2777302Sgdamore@opensolaris.org 	}
2787302Sgdamore@opensolaris.org 
2797302Sgdamore@opensolaris.org 	/*
2807302Sgdamore@opensolaris.org 	 * Memory cards need to obtain their CID before getting their RCA.
2817302Sgdamore@opensolaris.org 	 * This is a requirement for the state transitions... they go thru
2827302Sgdamore@opensolaris.org 	 * the ident state, unlike SDIO cards.
2837302Sgdamore@opensolaris.org 	 */
2847302Sgdamore@opensolaris.org 	if (slot->s_flags & SLOTF_MEMORY) {
2857302Sgdamore@opensolaris.org 		rv = sda_init_cmd(slot, CMD_BCAST_CID, 0, R2, slot->s_rcid);
2867302Sgdamore@opensolaris.org 		if (rv != SDA_EOK) {
2877302Sgdamore@opensolaris.org 			sda_slot_err(slot, "Failed getting card CID (%d)", rv);
2887302Sgdamore@opensolaris.org 			goto done;
2897302Sgdamore@opensolaris.org 		}
2907302Sgdamore@opensolaris.org 	}
2917302Sgdamore@opensolaris.org 
2927302Sgdamore@opensolaris.org 	if ((rv = sda_init_rca(slot)) != SDA_EOK) {
2937302Sgdamore@opensolaris.org 		goto done;
2947302Sgdamore@opensolaris.org 	}
2957302Sgdamore@opensolaris.org 
2967302Sgdamore@opensolaris.org 	slot->s_maxclk = 0xffffffffU;	/* special sentinel */
2977302Sgdamore@opensolaris.org 
2987302Sgdamore@opensolaris.org 	/*
2997302Sgdamore@opensolaris.org 	 * Figure out card supported bus width and speed.
3007302Sgdamore@opensolaris.org 	 *
3017302Sgdamore@opensolaris.org 	 * TODO: SDIO: For IO cards, we have to check what speed the card
3027302Sgdamore@opensolaris.org 	 * supports by looking in the CCCR_CAPAB register.  (SDIO cards
3037302Sgdamore@opensolaris.org 	 * can go low-speed only, full-speed, or high-speed.)
3047302Sgdamore@opensolaris.org 	 */
3057302Sgdamore@opensolaris.org 	if (slot->s_flags & SLOTF_MEMORY) {
3067302Sgdamore@opensolaris.org 
3077302Sgdamore@opensolaris.org 		/*
3087302Sgdamore@opensolaris.org 		 * We need to obtain the CSD.
3097302Sgdamore@opensolaris.org 		 */
3107302Sgdamore@opensolaris.org 		rv = sda_init_cmd(slot, CMD_SEND_CSD, slot->s_rca << 16, R2,
3117302Sgdamore@opensolaris.org 		    slot->s_rcsd);
3127302Sgdamore@opensolaris.org 		if (rv != 0) {
3137302Sgdamore@opensolaris.org 			sda_slot_err(slot, "Failed getting card CSD (%d)", rv);
3147302Sgdamore@opensolaris.org 			goto done;
3157302Sgdamore@opensolaris.org 		}
3167302Sgdamore@opensolaris.org 
3177302Sgdamore@opensolaris.org 		/*
3187302Sgdamore@opensolaris.org 		 * Calculate the maxclock.
3197302Sgdamore@opensolaris.org 		 */
3207302Sgdamore@opensolaris.org 		slot->s_maxclk = sda_mem_maxclk(slot);
3217302Sgdamore@opensolaris.org 	}
3227302Sgdamore@opensolaris.org 	if (((slot->s_flags & SLOTF_SDMEM) != 0) &&
3237302Sgdamore@opensolaris.org 	    ((slot->s_caps & SLOT_CAP_4BITS) != 0)) {
3247302Sgdamore@opensolaris.org 		slot->s_flags |= SLOTF_4BITS;
3257302Sgdamore@opensolaris.org 	}
3267302Sgdamore@opensolaris.org 	if (slot->s_flags & SLOTF_SDIO) {
3277302Sgdamore@opensolaris.org 		sda_slot_debug(slot, "Wide SDIO bus not yet supported");
3287302Sgdamore@opensolaris.org 		slot->s_flags &= ~SLOTF_4BITS;
3297302Sgdamore@opensolaris.org 	}
3307302Sgdamore@opensolaris.org 
3317302Sgdamore@opensolaris.org 	/*
3327302Sgdamore@opensolaris.org 	 * Now select the card.
3337302Sgdamore@opensolaris.org 	 */
3347302Sgdamore@opensolaris.org 	if ((rv = sda_init_cmd(slot, CMD_SELECT_CARD, slot->s_rca << 16,
3357302Sgdamore@opensolaris.org 	    R1b, &resp)) != SDA_EOK) {
3367302Sgdamore@opensolaris.org 		sda_slot_err(slot, "Failed selecting card (%d, %x)", rv, resp);
3377302Sgdamore@opensolaris.org 		goto done;
3387302Sgdamore@opensolaris.org 	}
3397302Sgdamore@opensolaris.org 
3407302Sgdamore@opensolaris.org 	if ((rv = sda_init_highspeed(slot)) != SDA_EOK) {
3417302Sgdamore@opensolaris.org 		goto done;
3427302Sgdamore@opensolaris.org 	}
3437302Sgdamore@opensolaris.org 
3447302Sgdamore@opensolaris.org 	sda_init_clock(slot, slot->s_maxclk);
3457302Sgdamore@opensolaris.org 
3467302Sgdamore@opensolaris.org 	/*
3477302Sgdamore@opensolaris.org 	 * Lets go to 4-bit bus mode, if possible.
3487302Sgdamore@opensolaris.org 	 */
3497302Sgdamore@opensolaris.org 	if ((rv = sda_init_width(slot)) != SDA_EOK) {
3507302Sgdamore@opensolaris.org 		goto done;
3517302Sgdamore@opensolaris.org 	}
3527302Sgdamore@opensolaris.org 
3537302Sgdamore@opensolaris.org 	if ((rv = sda_init_blocklen(slot)) != SDA_EOK) {
3547302Sgdamore@opensolaris.org 		goto done;
3557302Sgdamore@opensolaris.org 	}
3567302Sgdamore@opensolaris.org 
3577302Sgdamore@opensolaris.org 	/* note if a card is writable */
3587302Sgdamore@opensolaris.org 	if ((sda_getprop(slot, SDA_PROP_WPROTECT, &val) == SDA_EOK) &&
3597302Sgdamore@opensolaris.org 	    (val == 0)) {
3607302Sgdamore@opensolaris.org 		slot->s_flags |= SLOTF_WRITABLE;
3617302Sgdamore@opensolaris.org 	}
3627302Sgdamore@opensolaris.org 
3637302Sgdamore@opensolaris.org 	rv = SDA_EOK;
3647302Sgdamore@opensolaris.org 
3657302Sgdamore@opensolaris.org done:
3667302Sgdamore@opensolaris.org 
3677302Sgdamore@opensolaris.org 	sda_slot_enter(slot);
3687302Sgdamore@opensolaris.org 	slot->s_init = B_FALSE;
3697302Sgdamore@opensolaris.org 	sda_slot_exit(slot);
3707302Sgdamore@opensolaris.org 
3717302Sgdamore@opensolaris.org 	sda_slot_wakeup(slot);
3727302Sgdamore@opensolaris.org 
3737302Sgdamore@opensolaris.org 	return (rv);
3747302Sgdamore@opensolaris.org }
3757302Sgdamore@opensolaris.org 
3767302Sgdamore@opensolaris.org sda_err_t
sda_init_blocklen(sda_slot_t * slot)3777302Sgdamore@opensolaris.org sda_init_blocklen(sda_slot_t *slot)
3787302Sgdamore@opensolaris.org {
3797302Sgdamore@opensolaris.org 	int		rv;
3807302Sgdamore@opensolaris.org 	uint32_t	resp;
3817302Sgdamore@opensolaris.org 
3827302Sgdamore@opensolaris.org 	if ((slot->s_flags & SLOTF_MEMORY) == 0)  {
3837302Sgdamore@opensolaris.org 		return (SDA_EOK);
3847302Sgdamore@opensolaris.org 	}
3857302Sgdamore@opensolaris.org 
3867302Sgdamore@opensolaris.org 	/*
3877302Sgdamore@opensolaris.org 	 * All memory cards support block sizes of 512.  Full stop.
3887302Sgdamore@opensolaris.org 	 */
3897302Sgdamore@opensolaris.org 	rv = sda_init_cmd(slot, CMD_SET_BLOCKLEN, 512, R1, &resp);
3907302Sgdamore@opensolaris.org 	if (rv != SDA_EOK) {
3917302Sgdamore@opensolaris.org 		sda_slot_err(slot, "Unable to set block length (%d, %x)",
3927302Sgdamore@opensolaris.org 		    rv, resp);
3937302Sgdamore@opensolaris.org 	}
3947302Sgdamore@opensolaris.org 	return (rv);
3957302Sgdamore@opensolaris.org }
3967302Sgdamore@opensolaris.org 
3977302Sgdamore@opensolaris.org void
sda_init_clock(sda_slot_t * slot,uint32_t hz)3987302Sgdamore@opensolaris.org sda_init_clock(sda_slot_t *slot, uint32_t hz)
3997302Sgdamore@opensolaris.org {
4007302Sgdamore@opensolaris.org 	int		rv;
4017302Sgdamore@opensolaris.org 	uint32_t	act;
4027302Sgdamore@opensolaris.org 
4037302Sgdamore@opensolaris.org 	/*
4047302Sgdamore@opensolaris.org 	 * Note that at no time is a failure programming the clock
4057302Sgdamore@opensolaris.org 	 * itself necessarily a fatal error.  Although if the clock
4067302Sgdamore@opensolaris.org 	 * wasn't programmed, other things will probably not work during
4077302Sgdamore@opensolaris.org 	 * initialization.
4087302Sgdamore@opensolaris.org 	 */
4097302Sgdamore@opensolaris.org 
4107302Sgdamore@opensolaris.org 	if ((rv = sda_setprop(slot, SDA_PROP_CLOCK, hz)) != SDA_EOK) {
4117302Sgdamore@opensolaris.org 		sda_slot_err(slot, "Failed setting clock to %u Hz (%d)",
4127302Sgdamore@opensolaris.org 		    hz, rv);
4137302Sgdamore@opensolaris.org 		/* XXX: FMA fail the slot */
4147302Sgdamore@opensolaris.org 		return;
4157302Sgdamore@opensolaris.org 	}
4167302Sgdamore@opensolaris.org 
417*8289Sgdamore@opensolaris.org 	rv = sda_getprop(slot, SDA_PROP_CLOCK, &act);
418*8289Sgdamore@opensolaris.org 	sda_slot_debug(slot,
419*8289Sgdamore@opensolaris.org 	    rv == SDA_EOK ? "Clock set to %u Hz (requested %u Hz)" :
420*8289Sgdamore@opensolaris.org 	    "Clock frequency unknown (good luck).", act, hz);
4217302Sgdamore@opensolaris.org 
4227302Sgdamore@opensolaris.org 	/*
4237302Sgdamore@opensolaris.org 	 * For now, just wait 10msec for clocks to stabilize to the
4247302Sgdamore@opensolaris.org 	 * card.  (Is this really necessary?)
4257302Sgdamore@opensolaris.org 	 */
4267302Sgdamore@opensolaris.org 	delay(drv_usectohz(10000));
4277302Sgdamore@opensolaris.org }
4287302Sgdamore@opensolaris.org 
4297302Sgdamore@opensolaris.org sda_err_t
sda_init_width(sda_slot_t * slot)4307302Sgdamore@opensolaris.org sda_init_width(sda_slot_t *slot)
4317302Sgdamore@opensolaris.org {
4327302Sgdamore@opensolaris.org 	int		rv;
4337302Sgdamore@opensolaris.org 	uint32_t	resp;
4347302Sgdamore@opensolaris.org 
4357302Sgdamore@opensolaris.org 	/*
4367302Sgdamore@opensolaris.org 	 * Spec says we should command the card first.
4377302Sgdamore@opensolaris.org 	 */
4387302Sgdamore@opensolaris.org 
4397302Sgdamore@opensolaris.org 	rv = sda_setprop(slot, SDA_PROP_BUSWIDTH, 1);
4407302Sgdamore@opensolaris.org 	if (rv != SDA_EOK) {
4417302Sgdamore@opensolaris.org 		sda_slot_err(slot, "Unable to set slot 1-bit mode (%d)", rv);
4427302Sgdamore@opensolaris.org 		return (rv);
4437302Sgdamore@opensolaris.org 	}
4447302Sgdamore@opensolaris.org 
4457302Sgdamore@opensolaris.org 	if ((slot->s_flags & SLOTF_4BITS) == 0) {
4467302Sgdamore@opensolaris.org 		return (SDA_EOK);
4477302Sgdamore@opensolaris.org 	}
4487302Sgdamore@opensolaris.org 
4497302Sgdamore@opensolaris.org 	/*
4507302Sgdamore@opensolaris.org 	 * TODO: SDIO: SDIO cards set the CCCR_BUS_WIDTH
4517302Sgdamore@opensolaris.org 	 * and CCCR_CD_DISABLE bits here.
4527302Sgdamore@opensolaris.org 	 */
4537302Sgdamore@opensolaris.org 
4547302Sgdamore@opensolaris.org 	/*
4557302Sgdamore@opensolaris.org 	 * If we're going to use all 4 pins, we really need to disconnect
4567302Sgdamore@opensolaris.org 	 * the card pullup resistor.   A consquence of this, is that hosts
4577302Sgdamore@opensolaris.org 	 * which use that resistor for detection must not claim to support
4587302Sgdamore@opensolaris.org 	 * 4-bit bus mode.  This is a limitation of our implementation.
4597302Sgdamore@opensolaris.org 	 */
4607302Sgdamore@opensolaris.org 	rv = sda_init_acmd(slot, ACMD_SET_CLR_CARD_DETECT, 1, R1, &resp);
4617302Sgdamore@opensolaris.org 	if (rv != SDA_EOK) {
4627302Sgdamore@opensolaris.org 		sda_slot_err(slot,
4637302Sgdamore@opensolaris.org 		    "Unable disconnect DAT3 resistor on card (%d, %x)",
4647302Sgdamore@opensolaris.org 		    rv, resp);
4657302Sgdamore@opensolaris.org 		/* non-fatal error, muddle on */
4667302Sgdamore@opensolaris.org 		return (SDA_EOK);
4677302Sgdamore@opensolaris.org 	}
4687302Sgdamore@opensolaris.org 
4697302Sgdamore@opensolaris.org 	rv = sda_init_acmd(slot, ACMD_SET_BUS_WIDTH, 2, R1, &resp);
4707302Sgdamore@opensolaris.org 	if (rv != SDA_EOK) {
4717302Sgdamore@opensolaris.org 		sda_slot_err(slot, "Unable to set card 4-bit mode (%d, %x)",
4727302Sgdamore@opensolaris.org 		    rv, resp);
4737302Sgdamore@opensolaris.org 		/* non-fatal error, muddle on */
4747302Sgdamore@opensolaris.org 		return (SDA_EOK);
4757302Sgdamore@opensolaris.org 	}
4767302Sgdamore@opensolaris.org 
4777302Sgdamore@opensolaris.org 	rv = sda_setprop(slot, SDA_PROP_BUSWIDTH, 4);
4787302Sgdamore@opensolaris.org 	if (rv != SDA_EOK) {
4797302Sgdamore@opensolaris.org 		/*
4807302Sgdamore@opensolaris.org 		 * This is bad news.  We've already asked for the card to
4817302Sgdamore@opensolaris.org 		 * to use 4-bit mode, but the host is not complying.  It
4827302Sgdamore@opensolaris.org 		 * shouldn't ever happen, so we just error out.
4837302Sgdamore@opensolaris.org 		 */
4847302Sgdamore@opensolaris.org 		sda_slot_err(slot, "Unable to set slot 4-bit mode (%d)", rv);
4857302Sgdamore@opensolaris.org 	}
4867302Sgdamore@opensolaris.org 
4877302Sgdamore@opensolaris.org 	return (rv);
4887302Sgdamore@opensolaris.org }
4897302Sgdamore@opensolaris.org 
4907302Sgdamore@opensolaris.org sda_err_t
sda_init_ifcond(sda_slot_t * slot)4917302Sgdamore@opensolaris.org sda_init_ifcond(sda_slot_t *slot)
4927302Sgdamore@opensolaris.org {
4937302Sgdamore@opensolaris.org 	int		rv;
4947302Sgdamore@opensolaris.org 	int		tries;
4957302Sgdamore@opensolaris.org 	uint32_t	vchk;
4967302Sgdamore@opensolaris.org 	uint32_t	resp;
4977302Sgdamore@opensolaris.org 
4987302Sgdamore@opensolaris.org 	/*
4997302Sgdamore@opensolaris.org 	 * Try SEND_IF_COND.  Note that this assumes that the host is
5007302Sgdamore@opensolaris.org 	 * supplying 2.7 - 3.6 voltage range.  The standard is not
5017302Sgdamore@opensolaris.org 	 * defined for any other ranges.
5027302Sgdamore@opensolaris.org 	 */
5037302Sgdamore@opensolaris.org 	vchk = R7_VHS_27_36V | R7_PATTERN;
5047302Sgdamore@opensolaris.org 
5057302Sgdamore@opensolaris.org 	/* we try this a few times, just to be sure */
5067302Sgdamore@opensolaris.org 	for (tries = 0; tries < 5; tries++) {
5077302Sgdamore@opensolaris.org 		rv = sda_init_cmd(slot, CMD_GO_IDLE, 0, R0, NULL);
5087302Sgdamore@opensolaris.org 		if (rv != SDA_EOK) {
5097302Sgdamore@opensolaris.org 			sda_slot_err(slot, "Failed to IDLE card");
5107302Sgdamore@opensolaris.org 			return (rv);
5117302Sgdamore@opensolaris.org 		}
5127302Sgdamore@opensolaris.org 
5137302Sgdamore@opensolaris.org 		rv = sda_init_cmd(slot, CMD_SEND_IF_COND, vchk, R7, &resp);
5147302Sgdamore@opensolaris.org 		if (rv == SDA_EOK) {
5157302Sgdamore@opensolaris.org 			break;
5167302Sgdamore@opensolaris.org 		}
5177302Sgdamore@opensolaris.org 		delay(drv_usectohz(10000));
5187302Sgdamore@opensolaris.org 	}
5197302Sgdamore@opensolaris.org 
5207302Sgdamore@opensolaris.org 	if (rv != SDA_EOK) {
5217302Sgdamore@opensolaris.org 		(void) sda_init_cmd(slot, CMD_GO_IDLE, 0, R0, NULL);
5227302Sgdamore@opensolaris.org 		slot->s_flags &= ~SLOTF_IFCOND;
5237302Sgdamore@opensolaris.org 
5247302Sgdamore@opensolaris.org 	} else if (resp != vchk) {
5257302Sgdamore@opensolaris.org 		sda_slot_err(slot, "Card voltages incompatible! (%x)", resp);
5267302Sgdamore@opensolaris.org 		return (SDA_ENOTSUP);
5277302Sgdamore@opensolaris.org 
5287302Sgdamore@opensolaris.org 	} else {
5297302Sgdamore@opensolaris.org 		/* SDHC compliant */
5307302Sgdamore@opensolaris.org 		slot->s_flags |= SLOTF_IFCOND;
5317302Sgdamore@opensolaris.org 	}
5327302Sgdamore@opensolaris.org 
5337302Sgdamore@opensolaris.org 	return (SDA_EOK);
5347302Sgdamore@opensolaris.org }
5357302Sgdamore@opensolaris.org 
5367302Sgdamore@opensolaris.org sda_err_t
sda_init_rca(sda_slot_t * slot)5377302Sgdamore@opensolaris.org sda_init_rca(sda_slot_t *slot)
5387302Sgdamore@opensolaris.org {
5397302Sgdamore@opensolaris.org 	int		rv;
5407302Sgdamore@opensolaris.org 	int		tries;
5417302Sgdamore@opensolaris.org 	uint32_t	resp;
5427302Sgdamore@opensolaris.org 
5437302Sgdamore@opensolaris.org 	/*
5447302Sgdamore@opensolaris.org 	 * Program the RCA.  Note that MMC has a different mechanism
5457302Sgdamore@opensolaris.org 	 * for this.
5467302Sgdamore@opensolaris.org 	 */
5477302Sgdamore@opensolaris.org 	for (tries = 0; tries < 10; tries++) {
5487302Sgdamore@opensolaris.org 
5497302Sgdamore@opensolaris.org 		if (slot->s_flags & SLOTF_MMC) {
5507302Sgdamore@opensolaris.org 			/*
5517302Sgdamore@opensolaris.org 			 * For MMC, we push the RCA to the MMC.  We
5527302Sgdamore@opensolaris.org 			 * arbitrarily start at 0x100, and add from
5537302Sgdamore@opensolaris.org 			 * there.
5547302Sgdamore@opensolaris.org 			 */
5557302Sgdamore@opensolaris.org 			rv = sda_init_cmd(slot, CMD_SEND_RCA,
5567302Sgdamore@opensolaris.org 			    (0x100 + tries) << 16, R1, NULL);
5577302Sgdamore@opensolaris.org 			if (rv == SDA_EOK)
5587302Sgdamore@opensolaris.org 				slot->s_rca = 0x100 + tries;
5597302Sgdamore@opensolaris.org 		} else {
5607302Sgdamore@opensolaris.org 			/*
5617302Sgdamore@opensolaris.org 			 * For SDcard, we are basically asking the
5627302Sgdamore@opensolaris.org 			 * card to propose a value.  It *may* propose
5637302Sgdamore@opensolaris.org 			 * a value of zero, in which case we will have
5647302Sgdamore@opensolaris.org 			 * to ask again.
5657302Sgdamore@opensolaris.org 			 */
5667302Sgdamore@opensolaris.org 			rv = sda_init_cmd(slot, CMD_SEND_RCA, 0, R6, &resp);
5677302Sgdamore@opensolaris.org 			if (rv == SDA_EOK)
5687302Sgdamore@opensolaris.org 				slot->s_rca = resp >> 16;
5697302Sgdamore@opensolaris.org 		}
5707302Sgdamore@opensolaris.org 		if ((rv == SDA_EOK) && (slot->s_rca != 0)) {
5717302Sgdamore@opensolaris.org 			sda_slot_debug(slot, "Relative address (RCA) = %d",
5727302Sgdamore@opensolaris.org 			    slot->s_rca);
5737302Sgdamore@opensolaris.org 			return (SDA_EOK);
5747302Sgdamore@opensolaris.org 		}
5757302Sgdamore@opensolaris.org 	}
5767302Sgdamore@opensolaris.org 
5777302Sgdamore@opensolaris.org 	sda_slot_err(slot, "Unable to negotiate a suitable RCA (%d)", rv);
5787302Sgdamore@opensolaris.org 	return ((rv != SDA_EOK) ? rv : SDA_EINVAL);
5797302Sgdamore@opensolaris.org }
5807302Sgdamore@opensolaris.org 
5817302Sgdamore@opensolaris.org sda_err_t
sda_init_switch(sda_slot_t * slot,uint8_t mode,uint8_t grp,uint8_t val,uint8_t * data)5827302Sgdamore@opensolaris.org sda_init_switch(sda_slot_t *slot, uint8_t mode, uint8_t grp, uint8_t val,
5837302Sgdamore@opensolaris.org     uint8_t *data)
5847302Sgdamore@opensolaris.org {
5857302Sgdamore@opensolaris.org 	sda_cmd_t	*cmdp;
5867302Sgdamore@opensolaris.org 	sda_err_t	errno;
5877302Sgdamore@opensolaris.org 	uint32_t	arg;
5887302Sgdamore@opensolaris.org 
5897302Sgdamore@opensolaris.org 	/*
5907302Sgdamore@opensolaris.org 	 * The spec says we should leave unselected groups set to 0xf,
5917302Sgdamore@opensolaris.org 	 * to prevent inadvertent changes.
5927302Sgdamore@opensolaris.org 	 */
5937302Sgdamore@opensolaris.org 	arg = (mode << 31) | 0xffffff;
5947302Sgdamore@opensolaris.org 	arg &= ~(0xf << (grp << 2));
5957302Sgdamore@opensolaris.org 	arg |= (val << (grp << 2));
5967302Sgdamore@opensolaris.org 
5977302Sgdamore@opensolaris.org 	cmdp = sda_cmd_alloc(slot, CMD_SWITCH_FUNC, arg, R1, NULL, KM_SLEEP);
5987302Sgdamore@opensolaris.org 
5997302Sgdamore@opensolaris.org 	cmdp->sc_flags |= SDA_CMDF_INIT | SDA_CMDF_DAT | SDA_CMDF_READ;
6007302Sgdamore@opensolaris.org 	cmdp->sc_blksz = 64;
6017302Sgdamore@opensolaris.org 	cmdp->sc_nblks = 1;
6027302Sgdamore@opensolaris.org 	cmdp->sc_kvaddr = (void *)data;
6037302Sgdamore@opensolaris.org 
6047302Sgdamore@opensolaris.org 	errno = sda_cmd_exec(slot, cmdp, NULL);
6057302Sgdamore@opensolaris.org 
6067302Sgdamore@opensolaris.org 	sda_cmd_free(cmdp);
6077302Sgdamore@opensolaris.org 
6087302Sgdamore@opensolaris.org 	return (errno);
6097302Sgdamore@opensolaris.org 
6107302Sgdamore@opensolaris.org }
6117302Sgdamore@opensolaris.org 
6127302Sgdamore@opensolaris.org sda_err_t
sda_init_highspeed(sda_slot_t * slot)6137302Sgdamore@opensolaris.org sda_init_highspeed(sda_slot_t *slot)
6147302Sgdamore@opensolaris.org {
6157302Sgdamore@opensolaris.org 	uint32_t	ccc;
6167302Sgdamore@opensolaris.org 	uint8_t		data[64];
6177302Sgdamore@opensolaris.org 	sda_err_t	rv;
6187302Sgdamore@opensolaris.org 
6197302Sgdamore@opensolaris.org 	if ((slot->s_caps & SLOT_CAP_HISPEED) == 0) {
6207302Sgdamore@opensolaris.org 		return (SDA_EOK);
6217302Sgdamore@opensolaris.org 	}
6227302Sgdamore@opensolaris.org 	if ((slot->s_flags & SLOTF_SDMEM) == 0) {
6237302Sgdamore@opensolaris.org 		return (SDA_EOK);
6247302Sgdamore@opensolaris.org 	}
6257302Sgdamore@opensolaris.org 	ccc = sda_mem_getbits(slot->s_rcsd, 95, 12);
6267302Sgdamore@opensolaris.org 	if ((ccc & (1 << 10)) == 0) {
6277302Sgdamore@opensolaris.org 		return (SDA_EOK);
6287302Sgdamore@opensolaris.org 	}
6297302Sgdamore@opensolaris.org 
6307302Sgdamore@opensolaris.org 	rv = sda_init_switch(slot, 0, 0, 1, data);
6317302Sgdamore@opensolaris.org 
6327302Sgdamore@opensolaris.org 	/* these are big-endian bits, bit 401 */
6337302Sgdamore@opensolaris.org 	if ((rv != SDA_EOK) || ((data[13] & (1 << 1)) == 0)) {
6347302Sgdamore@opensolaris.org 		return (SDA_EOK);
6357302Sgdamore@opensolaris.org 	}
6367302Sgdamore@opensolaris.org 
6377302Sgdamore@opensolaris.org 	rv = sda_init_switch(slot, 1, 0, 1, data);
6387302Sgdamore@opensolaris.org 	if (rv != SDA_EOK) {
6397302Sgdamore@opensolaris.org 		return (SDA_EOK);
6407302Sgdamore@opensolaris.org 	}
6417302Sgdamore@opensolaris.org 
6427302Sgdamore@opensolaris.org 	/* now program the card */
6437302Sgdamore@opensolaris.org 	rv = sda_setprop(slot, SDA_PROP_HISPEED, 1);
6447302Sgdamore@opensolaris.org 	if (rv != SDA_EOK) {
6457302Sgdamore@opensolaris.org 		sda_slot_err(slot, "Failed setting slot to high speed mode");
6467302Sgdamore@opensolaris.org 	} else {
6477302Sgdamore@opensolaris.org 		/* the card should now support 50 MHz */
6487302Sgdamore@opensolaris.org 		slot->s_maxclk = 50000000;
6497302Sgdamore@opensolaris.org 	}
6507302Sgdamore@opensolaris.org 
6517302Sgdamore@opensolaris.org 	return (rv);
6527302Sgdamore@opensolaris.org }
653