xref: /illumos-gate/usr/src/uts/common/io/dmfe/dmfe_mii.c (revision bdb9230a)
15c1d0199Sgd78059 /*
25c1d0199Sgd78059  * CDDL HEADER START
35c1d0199Sgd78059  *
45c1d0199Sgd78059  * The contents of this file are subject to the terms of the
55c1d0199Sgd78059  * Common Development and Distribution License (the "License").
65c1d0199Sgd78059  * You may not use this file except in compliance with the License.
75c1d0199Sgd78059  *
85c1d0199Sgd78059  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
95c1d0199Sgd78059  * or http://www.opensolaris.org/os/licensing.
105c1d0199Sgd78059  * See the License for the specific language governing permissions
115c1d0199Sgd78059  * and limitations under the License.
125c1d0199Sgd78059  *
135c1d0199Sgd78059  * When distributing Covered Code, include this CDDL HEADER in each
145c1d0199Sgd78059  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
155c1d0199Sgd78059  * If applicable, add the following below this CDDL HEADER, with the
165c1d0199Sgd78059  * fields enclosed by brackets "[]" replaced with your own identifying
175c1d0199Sgd78059  * information: Portions Copyright [yyyy] [name of copyright owner]
185c1d0199Sgd78059  *
195c1d0199Sgd78059  * CDDL HEADER END
205c1d0199Sgd78059  */
215c1d0199Sgd78059 /*
22*bdb9230aSGarrett D'Amore  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
235c1d0199Sgd78059  * Use is subject to license terms.
245c1d0199Sgd78059  */
255c1d0199Sgd78059 
265c1d0199Sgd78059 #include "dmfe_impl.h"
275c1d0199Sgd78059 
285c1d0199Sgd78059 /*
295c1d0199Sgd78059  * The bit-twiddling required by the MII interface makes the functions
305c1d0199Sgd78059  * in this file relatively slow, so they should probably only be called
315c1d0199Sgd78059  * from base/low-pri code.  However, there's nothing here that really
325c1d0199Sgd78059  * won't work at hi-pri, AFAIK; and 'relatively slow' only means that
335c1d0199Sgd78059  * they have microsecond busy-waits all over the place.
345c1d0199Sgd78059  */
355c1d0199Sgd78059 
365c1d0199Sgd78059 static const int mii_reg_size = 16;			/* bits		*/
375c1d0199Sgd78059 
385c1d0199Sgd78059 /*
395c1d0199Sgd78059  * ======== Low-level SROM access ========
405c1d0199Sgd78059  */
415c1d0199Sgd78059 
425c1d0199Sgd78059 /*
435c1d0199Sgd78059  * EEPROM access is here because it shares register functionality with MII.
445c1d0199Sgd78059  * NB: <romaddr> is a byte address but must be 16-bit aligned.
455c1d0199Sgd78059  *     <cnt> is a byte count, and must be a multiple of 2.
465c1d0199Sgd78059  */
475c1d0199Sgd78059 void
dmfe_read_eeprom(dmfe_t * dmfep,uint16_t raddr,uint8_t * ptr,int cnt)485c1d0199Sgd78059 dmfe_read_eeprom(dmfe_t *dmfep, uint16_t raddr, uint8_t *ptr, int cnt)
495c1d0199Sgd78059 {
505c1d0199Sgd78059 	uint16_t value;
515c1d0199Sgd78059 	uint16_t bit;
525c1d0199Sgd78059 
535c1d0199Sgd78059 	/* only a whole number of words for now */
545c1d0199Sgd78059 	ASSERT((cnt % 2) == 0);
555c1d0199Sgd78059 	ASSERT((raddr % 2) == 0);
565c1d0199Sgd78059 	ASSERT(cnt > 0);
575c1d0199Sgd78059 	ASSERT(((raddr + cnt) / 2) < (HIGH_ADDRESS_BIT << 1));
585c1d0199Sgd78059 
595c1d0199Sgd78059 	raddr /= 2;	/* make it a word address */
605c1d0199Sgd78059 
615c1d0199Sgd78059 	/* loop over multiple words... rom access in 16-bit increments */
625c1d0199Sgd78059 	while (cnt > 0) {
635c1d0199Sgd78059 
645c1d0199Sgd78059 		/* select the eeprom */
655c1d0199Sgd78059 		dmfe_chip_put32(dmfep, ETHER_ROM_REG, READ_EEPROM);
665c1d0199Sgd78059 		drv_usecwait(1);
675c1d0199Sgd78059 		dmfe_chip_put32(dmfep, ETHER_ROM_REG, READ_EEPROM_CS);
685c1d0199Sgd78059 		drv_usecwait(1);
695c1d0199Sgd78059 		dmfe_chip_put32(dmfep, ETHER_ROM_REG, READ_EEPROM_CS | SEL_CLK);
705c1d0199Sgd78059 		drv_usecwait(1);
715c1d0199Sgd78059 		dmfe_chip_put32(dmfep, ETHER_ROM_REG, READ_EEPROM_CS);
725c1d0199Sgd78059 		drv_usecwait(1);
735c1d0199Sgd78059 
745c1d0199Sgd78059 		/* send 3 bit read command */
755c1d0199Sgd78059 		for (bit = HIGH_CMD_BIT; bit != 0; bit >>= 1) {
765c1d0199Sgd78059 
775c1d0199Sgd78059 			value = (bit & EEPROM_READ_CMD) ? DATA_IN : 0;
785c1d0199Sgd78059 
795c1d0199Sgd78059 			/* strobe the bit in */
805c1d0199Sgd78059 			dmfe_chip_put32(dmfep, ETHER_ROM_REG,
815c1d0199Sgd78059 			    READ_EEPROM_CS | value);
825c1d0199Sgd78059 			drv_usecwait(1);
835c1d0199Sgd78059 			dmfe_chip_put32(dmfep, ETHER_ROM_REG,
845c1d0199Sgd78059 			    READ_EEPROM_CS | SEL_CLK | value);
855c1d0199Sgd78059 			drv_usecwait(1);
865c1d0199Sgd78059 			dmfe_chip_put32(dmfep, ETHER_ROM_REG,
875c1d0199Sgd78059 			    READ_EEPROM_CS | value);
885c1d0199Sgd78059 			drv_usecwait(1);
895c1d0199Sgd78059 		}
905c1d0199Sgd78059 
915c1d0199Sgd78059 		/* send 6 bit address */
925c1d0199Sgd78059 		for (bit = HIGH_ADDRESS_BIT; bit != 0; bit >>= 1) {
935c1d0199Sgd78059 			value = (bit & raddr) ? DATA_IN : 0;
945c1d0199Sgd78059 
955c1d0199Sgd78059 			/* strobe the bit in */
965c1d0199Sgd78059 			dmfe_chip_put32(dmfep, ETHER_ROM_REG,
975c1d0199Sgd78059 			    READ_EEPROM_CS | value);
985c1d0199Sgd78059 			drv_usecwait(1);
995c1d0199Sgd78059 			dmfe_chip_put32(dmfep, ETHER_ROM_REG,
1005c1d0199Sgd78059 			    READ_EEPROM_CS | SEL_CLK | value);
1015c1d0199Sgd78059 			drv_usecwait(1);
1025c1d0199Sgd78059 			dmfe_chip_put32(dmfep, ETHER_ROM_REG,
1035c1d0199Sgd78059 			    READ_EEPROM_CS | value);
1045c1d0199Sgd78059 			drv_usecwait(1);
1055c1d0199Sgd78059 		}
1065c1d0199Sgd78059 
1075c1d0199Sgd78059 		/* shift out data */
1085c1d0199Sgd78059 		value = 0;
1095c1d0199Sgd78059 		for (bit = HIGH_DATA_BIT; bit != 0; bit >>= 1) {
1105c1d0199Sgd78059 
1115c1d0199Sgd78059 			dmfe_chip_put32(dmfep, ETHER_ROM_REG,
1125c1d0199Sgd78059 			    READ_EEPROM_CS | SEL_CLK);
1135c1d0199Sgd78059 			drv_usecwait(1);
1145c1d0199Sgd78059 
1155c1d0199Sgd78059 			if (dmfe_chip_get32(dmfep, ETHER_ROM_REG) & DATA_OUT)
1165c1d0199Sgd78059 				value |= bit;
1175c1d0199Sgd78059 			drv_usecwait(1);
1185c1d0199Sgd78059 
1195c1d0199Sgd78059 			dmfe_chip_put32(dmfep, ETHER_ROM_REG, READ_EEPROM_CS);
1205c1d0199Sgd78059 			drv_usecwait(1);
1215c1d0199Sgd78059 		}
1225c1d0199Sgd78059 
1235c1d0199Sgd78059 		/* turn off EEPROM access */
1245c1d0199Sgd78059 		dmfe_chip_put32(dmfep, ETHER_ROM_REG, READ_EEPROM);
1255c1d0199Sgd78059 		drv_usecwait(1);
1265c1d0199Sgd78059 
1275c1d0199Sgd78059 		/* this makes it endian neutral */
1285c1d0199Sgd78059 		*ptr++ = value & 0xff;
1295c1d0199Sgd78059 		*ptr++ = (value >> 8);
1305c1d0199Sgd78059 
1315c1d0199Sgd78059 		cnt -= 2;
1325c1d0199Sgd78059 		raddr++;
1335c1d0199Sgd78059 	}
1345c1d0199Sgd78059 }
1355c1d0199Sgd78059 
1365c1d0199Sgd78059 /*
1375c1d0199Sgd78059  * ======== Lowest-level bit-twiddling to drive MII interface ========
1385c1d0199Sgd78059  */
1395c1d0199Sgd78059 
1405c1d0199Sgd78059 /*
1415c1d0199Sgd78059  * Poke <nbits> (up to 32) bits from <mii_data> along the MII control lines.
1425c1d0199Sgd78059  * Note: the data is taken starting with the MSB of <mii_data> and working
1435c1d0199Sgd78059  * down through progressively less significant bits.
1445c1d0199Sgd78059  */
1455c1d0199Sgd78059 static void
dmfe_poke_mii(dmfe_t * dmfep,uint32_t mii_data,uint_t nbits)1465c1d0199Sgd78059 dmfe_poke_mii(dmfe_t *dmfep, uint32_t mii_data, uint_t nbits)
1475c1d0199Sgd78059 {
1485c1d0199Sgd78059 	uint32_t dbit;
1495c1d0199Sgd78059 
1505c1d0199Sgd78059 	ASSERT(mutex_owned(dmfep->milock));
1515c1d0199Sgd78059 
1525c1d0199Sgd78059 	for (; nbits > 0; mii_data <<= 1, --nbits) {
1535c1d0199Sgd78059 		/*
1545c1d0199Sgd78059 		 * Extract the MSB of <mii_data> and shift it to the
1555c1d0199Sgd78059 		 * proper bit position in the MII-poking register
1565c1d0199Sgd78059 		 */
1575c1d0199Sgd78059 		dbit = mii_data >> 31;
1585c1d0199Sgd78059 		dbit <<= MII_DATA_OUT_SHIFT;
1595c1d0199Sgd78059 		ASSERT((dbit & ~MII_DATA_OUT) == 0);
1605c1d0199Sgd78059 
1615c1d0199Sgd78059 		/*
1625c1d0199Sgd78059 		 * Drive the bit across the wire ...
1635c1d0199Sgd78059 		 */
1645c1d0199Sgd78059 		dmfe_chip_put32(dmfep, ETHER_ROM_REG,
1655c1d0199Sgd78059 		    MII_WRITE | dbit);			/* Clock Low	*/
1665c1d0199Sgd78059 		drv_usecwait(MII_DELAY);
1675c1d0199Sgd78059 		dmfe_chip_put32(dmfep, ETHER_ROM_REG,
1685c1d0199Sgd78059 		    MII_WRITE | MII_CLOCK | dbit);	/* Clock High	*/
1695c1d0199Sgd78059 		drv_usecwait(MII_DELAY);
1705c1d0199Sgd78059 	}
1715c1d0199Sgd78059 
1725c1d0199Sgd78059 	dmfe_chip_put32(dmfep, ETHER_ROM_REG,
1735c1d0199Sgd78059 	    MII_WRITE | dbit);				/* Clock Low	*/
1745c1d0199Sgd78059 	drv_usecwait(MII_DELAY);
1755c1d0199Sgd78059 }
1765c1d0199Sgd78059 
1775c1d0199Sgd78059 /*
1785c1d0199Sgd78059  * Put the MDIO port in tri-state for the turn around bits
1795c1d0199Sgd78059  * in MII read and at end of MII management sequence.
1805c1d0199Sgd78059  */
1815c1d0199Sgd78059 static void
dmfe_tristate_mii(dmfe_t * dmfep)1825c1d0199Sgd78059 dmfe_tristate_mii(dmfe_t *dmfep)
1835c1d0199Sgd78059 {
1845c1d0199Sgd78059 	ASSERT(mutex_owned(dmfep->milock));
1855c1d0199Sgd78059 
1865c1d0199Sgd78059 	dmfe_chip_put32(dmfep, ETHER_ROM_REG, MII_TRISTATE);
1875c1d0199Sgd78059 	drv_usecwait(MII_DELAY);
1885c1d0199Sgd78059 	dmfe_chip_put32(dmfep, ETHER_ROM_REG, MII_TRISTATE | MII_CLOCK);
1895c1d0199Sgd78059 	drv_usecwait(MII_DELAY);
1905c1d0199Sgd78059 }
1915c1d0199Sgd78059 
1925c1d0199Sgd78059 
1935c1d0199Sgd78059 /*
1945c1d0199Sgd78059  * ======== Next level: issue an MII access command/get a response ========
1955c1d0199Sgd78059  */
1965c1d0199Sgd78059 
1975c1d0199Sgd78059 static void
dmfe_mii_command(dmfe_t * dmfep,uint32_t command_word,int nbits)1985c1d0199Sgd78059 dmfe_mii_command(dmfe_t *dmfep, uint32_t command_word, int nbits)
1995c1d0199Sgd78059 {
2005c1d0199Sgd78059 	ASSERT(mutex_owned(dmfep->milock));
2015c1d0199Sgd78059 
2025c1d0199Sgd78059 	/* Write Preamble & Command & return to tristate */
2035c1d0199Sgd78059 	dmfe_poke_mii(dmfep, MII_PREAMBLE, 2*mii_reg_size);
2045c1d0199Sgd78059 	dmfe_poke_mii(dmfep, command_word, nbits);
2055c1d0199Sgd78059 	dmfe_tristate_mii(dmfep);
2065c1d0199Sgd78059 }
2075c1d0199Sgd78059 
2085c1d0199Sgd78059 static uint16_t
dmfe_mii_response(dmfe_t * dmfep)2095c1d0199Sgd78059 dmfe_mii_response(dmfe_t *dmfep)
2105c1d0199Sgd78059 {
2115c1d0199Sgd78059 	boolean_t ack;
21222eb7cb5Sgd78059 	uint16_t data;
2135c1d0199Sgd78059 	uint32_t tmp;
2145c1d0199Sgd78059 	int i;
2155c1d0199Sgd78059 
2165c1d0199Sgd78059 	/* Check that the PHY generated a zero bit on the 2nd clock */
2175c1d0199Sgd78059 	tmp = dmfe_chip_get32(dmfep, ETHER_ROM_REG);
2185c1d0199Sgd78059 	ack = (tmp & MII_DATA_IN) == 0;
2195c1d0199Sgd78059 
2205c1d0199Sgd78059 	/* read data WORD */
2215c1d0199Sgd78059 	for (data = 0, i = 0; i < mii_reg_size; ++i) {
2225c1d0199Sgd78059 		dmfe_chip_put32(dmfep, ETHER_ROM_REG, MII_READ);
2235c1d0199Sgd78059 		drv_usecwait(MII_DELAY);
2245c1d0199Sgd78059 		dmfe_chip_put32(dmfep, ETHER_ROM_REG, MII_READ | MII_CLOCK);
2255c1d0199Sgd78059 		drv_usecwait(MII_DELAY);
2265c1d0199Sgd78059 		tmp = dmfe_chip_get32(dmfep, ETHER_ROM_REG);
2275c1d0199Sgd78059 		data <<= 1;
2285c1d0199Sgd78059 		data |= (tmp >> MII_DATA_IN_SHIFT) & 1;
2295c1d0199Sgd78059 	}
2305c1d0199Sgd78059 
2315c1d0199Sgd78059 	/* leave the interface tristated */
2325c1d0199Sgd78059 	dmfe_tristate_mii(dmfep);
2335c1d0199Sgd78059 
2345c1d0199Sgd78059 	return (ack ? data : ~0);
2355c1d0199Sgd78059 }
2365c1d0199Sgd78059 
2375c1d0199Sgd78059 /*
2385c1d0199Sgd78059  * ======== Next level: 16-bit PHY register access routines ========
2395c1d0199Sgd78059  */
2405c1d0199Sgd78059 
2415c1d0199Sgd78059 static void
dmfe_mii_write(void * arg,uint8_t phy_num,uint8_t reg_num,uint16_t reg_dat)242*bdb9230aSGarrett D'Amore dmfe_mii_write(void *arg, uint8_t phy_num, uint8_t reg_num, uint16_t reg_dat)
2435c1d0199Sgd78059 {
244*bdb9230aSGarrett D'Amore 	dmfe_t *dmfep = arg;
2455c1d0199Sgd78059 	uint32_t command_word;
2465c1d0199Sgd78059 
2475c1d0199Sgd78059 	/* Issue MII command */
248*bdb9230aSGarrett D'Amore 	mutex_enter(dmfep->milock);
2495c1d0199Sgd78059 	command_word = MII_WRITE_FRAME;
250*bdb9230aSGarrett D'Amore 	command_word |= phy_num << MII_PHY_ADDR_SHIFT;
2515c1d0199Sgd78059 	command_word |= reg_num << MII_REG_ADDR_SHIFT;
2525c1d0199Sgd78059 	command_word |= reg_dat;
2535c1d0199Sgd78059 	dmfe_mii_command(dmfep, command_word, 2*mii_reg_size);
254*bdb9230aSGarrett D'Amore 	mutex_exit(dmfep->milock);
2555c1d0199Sgd78059 }
2565c1d0199Sgd78059 
2575c1d0199Sgd78059 static uint16_t
dmfe_mii_read(void * arg,uint8_t phy_num,uint8_t reg_num)258*bdb9230aSGarrett D'Amore dmfe_mii_read(void *arg, uint8_t phy_num, uint8_t reg_num)
2595c1d0199Sgd78059 {
260*bdb9230aSGarrett D'Amore 	dmfe_t *dmfep = arg;
2615c1d0199Sgd78059 	uint32_t command_word;
262*bdb9230aSGarrett D'Amore 	uint16_t rv;
2635c1d0199Sgd78059 
2645c1d0199Sgd78059 	/* Issue MII command */
2655c1d0199Sgd78059 	command_word = MII_READ_FRAME;
266*bdb9230aSGarrett D'Amore 	command_word |= phy_num << MII_PHY_ADDR_SHIFT;
2675c1d0199Sgd78059 	command_word |= reg_num << MII_REG_ADDR_SHIFT;
268*bdb9230aSGarrett D'Amore 
269*bdb9230aSGarrett D'Amore 	mutex_enter(dmfep->milock);
2705c1d0199Sgd78059 	dmfe_mii_command(dmfep, command_word, mii_reg_size-2);
2715c1d0199Sgd78059 
272*bdb9230aSGarrett D'Amore 	rv = dmfe_mii_response(dmfep);
273*bdb9230aSGarrett D'Amore 	mutex_exit(dmfep->milock);
274*bdb9230aSGarrett D'Amore 	return (rv);
2755c1d0199Sgd78059 }
2765c1d0199Sgd78059 
2775c1d0199Sgd78059 static void
dmfe_mii_notify(void * arg,link_state_t link)278*bdb9230aSGarrett D'Amore dmfe_mii_notify(void *arg, link_state_t link)
2795c1d0199Sgd78059 {
280*bdb9230aSGarrett D'Amore 	dmfe_t *dmfep = arg;
2815c1d0199Sgd78059 
282*bdb9230aSGarrett D'Amore 	if (link == LINK_STATE_UP) {
283*bdb9230aSGarrett D'Amore 		mutex_enter(dmfep->oplock);
284*bdb9230aSGarrett D'Amore 		/*
285*bdb9230aSGarrett D'Amore 		 * Configure DUPLEX setting on MAC.
286*bdb9230aSGarrett D'Amore 		 */
287*bdb9230aSGarrett D'Amore 		if (mii_get_duplex(dmfep->mii) == LINK_DUPLEX_FULL) {
288*bdb9230aSGarrett D'Amore 			dmfep->opmode |= FULL_DUPLEX;
289*bdb9230aSGarrett D'Amore 		} else {
290*bdb9230aSGarrett D'Amore 			dmfep->opmode &= ~FULL_DUPLEX;
2915c1d0199Sgd78059 		}
292*bdb9230aSGarrett D'Amore 		dmfe_chip_put32(dmfep, OPN_MODE_REG, dmfep->opmode);
293*bdb9230aSGarrett D'Amore 		mutex_exit(dmfep->oplock);
2945c1d0199Sgd78059 	}
295*bdb9230aSGarrett D'Amore 	mac_link_update(dmfep->mh, link);
2965c1d0199Sgd78059 }
2975c1d0199Sgd78059 
2985c1d0199Sgd78059 
2995c1d0199Sgd78059 /*
3005c1d0199Sgd78059  * PHY initialisation, called only once
3015c1d0199Sgd78059  */
302*bdb9230aSGarrett D'Amore 
303*bdb9230aSGarrett D'Amore static mii_ops_t dmfe_mii_ops = {
304*bdb9230aSGarrett D'Amore 	MII_OPS_VERSION,
305*bdb9230aSGarrett D'Amore 	dmfe_mii_read,
306*bdb9230aSGarrett D'Amore 	dmfe_mii_write,
307*bdb9230aSGarrett D'Amore 	dmfe_mii_notify,
308*bdb9230aSGarrett D'Amore 	NULL,			/* mii_reset */
309*bdb9230aSGarrett D'Amore };
310*bdb9230aSGarrett D'Amore 
3115c1d0199Sgd78059 boolean_t
dmfe_init_phy(dmfe_t * dmfep)3125c1d0199Sgd78059 dmfe_init_phy(dmfe_t *dmfep)
3135c1d0199Sgd78059 {
314*bdb9230aSGarrett D'Amore 	dmfep->mii = mii_alloc(dmfep, dmfep->devinfo, &dmfe_mii_ops);
315*bdb9230aSGarrett D'Amore 	if (dmfep->mii == NULL) {
3165c1d0199Sgd78059 		return (B_FALSE);
3175c1d0199Sgd78059 	}
3185c1d0199Sgd78059 	return (B_TRUE);
3195c1d0199Sgd78059 }
320