15181Sgd78059 /*
25181Sgd78059 * CDDL HEADER START
35181Sgd78059 *
45181Sgd78059 * The contents of this file are subject to the terms of the
55181Sgd78059 * Common Development and Distribution License (the "License").
65181Sgd78059 * You may not use this file except in compliance with the License.
75181Sgd78059 *
85181Sgd78059 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
95181Sgd78059 * or http://www.opensolaris.org/os/licensing.
105181Sgd78059 * See the License for the specific language governing permissions
115181Sgd78059 * and limitations under the License.
125181Sgd78059 *
135181Sgd78059 * When distributing Covered Code, include this CDDL HEADER in each
145181Sgd78059 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
155181Sgd78059 * If applicable, add the following below this CDDL HEADER, with the
165181Sgd78059 * fields enclosed by brackets "[]" replaced with your own identifying
175181Sgd78059 * information: Portions Copyright [yyyy] [name of copyright owner]
185181Sgd78059 *
195181Sgd78059 * CDDL HEADER END
205181Sgd78059 */
215181Sgd78059 /*
22*9860Sgdamore@opensolaris.org * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
235181Sgd78059 * Use is subject to license terms.
245181Sgd78059 */
255181Sgd78059
265181Sgd78059 #include "dmfe_impl.h"
275181Sgd78059
285181Sgd78059 /*
295181Sgd78059 * The bit-twiddling required by the MII interface makes the functions
305181Sgd78059 * in this file relatively slow, so they should probably only be called
315181Sgd78059 * from base/low-pri code. However, there's nothing here that really
325181Sgd78059 * won't work at hi-pri, AFAIK; and 'relatively slow' only means that
335181Sgd78059 * they have microsecond busy-waits all over the place.
345181Sgd78059 */
355181Sgd78059
365181Sgd78059 static const int mii_reg_size = 16; /* bits */
375181Sgd78059
385181Sgd78059 /*
395181Sgd78059 * ======== Low-level SROM access ========
405181Sgd78059 */
415181Sgd78059
425181Sgd78059 /*
435181Sgd78059 * EEPROM access is here because it shares register functionality with MII.
445181Sgd78059 * NB: <romaddr> is a byte address but must be 16-bit aligned.
455181Sgd78059 * <cnt> is a byte count, and must be a multiple of 2.
465181Sgd78059 */
475181Sgd78059 void
dmfe_read_eeprom(dmfe_t * dmfep,uint16_t raddr,uint8_t * ptr,int cnt)485181Sgd78059 dmfe_read_eeprom(dmfe_t *dmfep, uint16_t raddr, uint8_t *ptr, int cnt)
495181Sgd78059 {
505181Sgd78059 uint16_t value;
515181Sgd78059 uint16_t bit;
525181Sgd78059
535181Sgd78059 /* only a whole number of words for now */
545181Sgd78059 ASSERT((cnt % 2) == 0);
555181Sgd78059 ASSERT((raddr % 2) == 0);
565181Sgd78059 ASSERT(cnt > 0);
575181Sgd78059 ASSERT(((raddr + cnt) / 2) < (HIGH_ADDRESS_BIT << 1));
585181Sgd78059
595181Sgd78059 raddr /= 2; /* make it a word address */
605181Sgd78059
615181Sgd78059 /* loop over multiple words... rom access in 16-bit increments */
625181Sgd78059 while (cnt > 0) {
635181Sgd78059
645181Sgd78059 /* select the eeprom */
655181Sgd78059 dmfe_chip_put32(dmfep, ETHER_ROM_REG, READ_EEPROM);
665181Sgd78059 drv_usecwait(1);
675181Sgd78059 dmfe_chip_put32(dmfep, ETHER_ROM_REG, READ_EEPROM_CS);
685181Sgd78059 drv_usecwait(1);
695181Sgd78059 dmfe_chip_put32(dmfep, ETHER_ROM_REG, READ_EEPROM_CS | SEL_CLK);
705181Sgd78059 drv_usecwait(1);
715181Sgd78059 dmfe_chip_put32(dmfep, ETHER_ROM_REG, READ_EEPROM_CS);
725181Sgd78059 drv_usecwait(1);
735181Sgd78059
745181Sgd78059 /* send 3 bit read command */
755181Sgd78059 for (bit = HIGH_CMD_BIT; bit != 0; bit >>= 1) {
765181Sgd78059
775181Sgd78059 value = (bit & EEPROM_READ_CMD) ? DATA_IN : 0;
785181Sgd78059
795181Sgd78059 /* strobe the bit in */
805181Sgd78059 dmfe_chip_put32(dmfep, ETHER_ROM_REG,
815181Sgd78059 READ_EEPROM_CS | value);
825181Sgd78059 drv_usecwait(1);
835181Sgd78059 dmfe_chip_put32(dmfep, ETHER_ROM_REG,
845181Sgd78059 READ_EEPROM_CS | SEL_CLK | value);
855181Sgd78059 drv_usecwait(1);
865181Sgd78059 dmfe_chip_put32(dmfep, ETHER_ROM_REG,
875181Sgd78059 READ_EEPROM_CS | value);
885181Sgd78059 drv_usecwait(1);
895181Sgd78059 }
905181Sgd78059
915181Sgd78059 /* send 6 bit address */
925181Sgd78059 for (bit = HIGH_ADDRESS_BIT; bit != 0; bit >>= 1) {
935181Sgd78059 value = (bit & raddr) ? DATA_IN : 0;
945181Sgd78059
955181Sgd78059 /* strobe the bit in */
965181Sgd78059 dmfe_chip_put32(dmfep, ETHER_ROM_REG,
975181Sgd78059 READ_EEPROM_CS | value);
985181Sgd78059 drv_usecwait(1);
995181Sgd78059 dmfe_chip_put32(dmfep, ETHER_ROM_REG,
1005181Sgd78059 READ_EEPROM_CS | SEL_CLK | value);
1015181Sgd78059 drv_usecwait(1);
1025181Sgd78059 dmfe_chip_put32(dmfep, ETHER_ROM_REG,
1035181Sgd78059 READ_EEPROM_CS | value);
1045181Sgd78059 drv_usecwait(1);
1055181Sgd78059 }
1065181Sgd78059
1075181Sgd78059 /* shift out data */
1085181Sgd78059 value = 0;
1095181Sgd78059 for (bit = HIGH_DATA_BIT; bit != 0; bit >>= 1) {
1105181Sgd78059
1115181Sgd78059 dmfe_chip_put32(dmfep, ETHER_ROM_REG,
1125181Sgd78059 READ_EEPROM_CS | SEL_CLK);
1135181Sgd78059 drv_usecwait(1);
1145181Sgd78059
1155181Sgd78059 if (dmfe_chip_get32(dmfep, ETHER_ROM_REG) & DATA_OUT)
1165181Sgd78059 value |= bit;
1175181Sgd78059 drv_usecwait(1);
1185181Sgd78059
1195181Sgd78059 dmfe_chip_put32(dmfep, ETHER_ROM_REG, READ_EEPROM_CS);
1205181Sgd78059 drv_usecwait(1);
1215181Sgd78059 }
1225181Sgd78059
1235181Sgd78059 /* turn off EEPROM access */
1245181Sgd78059 dmfe_chip_put32(dmfep, ETHER_ROM_REG, READ_EEPROM);
1255181Sgd78059 drv_usecwait(1);
1265181Sgd78059
1275181Sgd78059 /* this makes it endian neutral */
1285181Sgd78059 *ptr++ = value & 0xff;
1295181Sgd78059 *ptr++ = (value >> 8);
1305181Sgd78059
1315181Sgd78059 cnt -= 2;
1325181Sgd78059 raddr++;
1335181Sgd78059 }
1345181Sgd78059 }
1355181Sgd78059
1365181Sgd78059 /*
1375181Sgd78059 * ======== Lowest-level bit-twiddling to drive MII interface ========
1385181Sgd78059 */
1395181Sgd78059
1405181Sgd78059 /*
1415181Sgd78059 * Poke <nbits> (up to 32) bits from <mii_data> along the MII control lines.
1425181Sgd78059 * Note: the data is taken starting with the MSB of <mii_data> and working
1435181Sgd78059 * down through progressively less significant bits.
1445181Sgd78059 */
1455181Sgd78059 static void
dmfe_poke_mii(dmfe_t * dmfep,uint32_t mii_data,uint_t nbits)1465181Sgd78059 dmfe_poke_mii(dmfe_t *dmfep, uint32_t mii_data, uint_t nbits)
1475181Sgd78059 {
1485181Sgd78059 uint32_t dbit;
1495181Sgd78059
1505181Sgd78059 ASSERT(mutex_owned(dmfep->milock));
1515181Sgd78059
1525181Sgd78059 for (; nbits > 0; mii_data <<= 1, --nbits) {
1535181Sgd78059 /*
1545181Sgd78059 * Extract the MSB of <mii_data> and shift it to the
1555181Sgd78059 * proper bit position in the MII-poking register
1565181Sgd78059 */
1575181Sgd78059 dbit = mii_data >> 31;
1585181Sgd78059 dbit <<= MII_DATA_OUT_SHIFT;
1595181Sgd78059 ASSERT((dbit & ~MII_DATA_OUT) == 0);
1605181Sgd78059
1615181Sgd78059 /*
1625181Sgd78059 * Drive the bit across the wire ...
1635181Sgd78059 */
1645181Sgd78059 dmfe_chip_put32(dmfep, ETHER_ROM_REG,
1655181Sgd78059 MII_WRITE | dbit); /* Clock Low */
1665181Sgd78059 drv_usecwait(MII_DELAY);
1675181Sgd78059 dmfe_chip_put32(dmfep, ETHER_ROM_REG,
1685181Sgd78059 MII_WRITE | MII_CLOCK | dbit); /* Clock High */
1695181Sgd78059 drv_usecwait(MII_DELAY);
1705181Sgd78059 }
1715181Sgd78059
1725181Sgd78059 dmfe_chip_put32(dmfep, ETHER_ROM_REG,
1735181Sgd78059 MII_WRITE | dbit); /* Clock Low */
1745181Sgd78059 drv_usecwait(MII_DELAY);
1755181Sgd78059 }
1765181Sgd78059
1775181Sgd78059 /*
1785181Sgd78059 * Put the MDIO port in tri-state for the turn around bits
1795181Sgd78059 * in MII read and at end of MII management sequence.
1805181Sgd78059 */
1815181Sgd78059 static void
dmfe_tristate_mii(dmfe_t * dmfep)1825181Sgd78059 dmfe_tristate_mii(dmfe_t *dmfep)
1835181Sgd78059 {
1845181Sgd78059 ASSERT(mutex_owned(dmfep->milock));
1855181Sgd78059
1865181Sgd78059 dmfe_chip_put32(dmfep, ETHER_ROM_REG, MII_TRISTATE);
1875181Sgd78059 drv_usecwait(MII_DELAY);
1885181Sgd78059 dmfe_chip_put32(dmfep, ETHER_ROM_REG, MII_TRISTATE | MII_CLOCK);
1895181Sgd78059 drv_usecwait(MII_DELAY);
1905181Sgd78059 }
1915181Sgd78059
1925181Sgd78059
1935181Sgd78059 /*
1945181Sgd78059 * ======== Next level: issue an MII access command/get a response ========
1955181Sgd78059 */
1965181Sgd78059
1975181Sgd78059 static void
dmfe_mii_command(dmfe_t * dmfep,uint32_t command_word,int nbits)1985181Sgd78059 dmfe_mii_command(dmfe_t *dmfep, uint32_t command_word, int nbits)
1995181Sgd78059 {
2005181Sgd78059 ASSERT(mutex_owned(dmfep->milock));
2015181Sgd78059
2025181Sgd78059 /* Write Preamble & Command & return to tristate */
2035181Sgd78059 dmfe_poke_mii(dmfep, MII_PREAMBLE, 2*mii_reg_size);
2045181Sgd78059 dmfe_poke_mii(dmfep, command_word, nbits);
2055181Sgd78059 dmfe_tristate_mii(dmfep);
2065181Sgd78059 }
2075181Sgd78059
2085181Sgd78059 static uint16_t
dmfe_mii_response(dmfe_t * dmfep)2095181Sgd78059 dmfe_mii_response(dmfe_t *dmfep)
2105181Sgd78059 {
2115181Sgd78059 boolean_t ack;
2126990Sgd78059 uint16_t data;
2135181Sgd78059 uint32_t tmp;
2145181Sgd78059 int i;
2155181Sgd78059
2165181Sgd78059 /* Check that the PHY generated a zero bit on the 2nd clock */
2175181Sgd78059 tmp = dmfe_chip_get32(dmfep, ETHER_ROM_REG);
2185181Sgd78059 ack = (tmp & MII_DATA_IN) == 0;
2195181Sgd78059
2205181Sgd78059 /* read data WORD */
2215181Sgd78059 for (data = 0, i = 0; i < mii_reg_size; ++i) {
2225181Sgd78059 dmfe_chip_put32(dmfep, ETHER_ROM_REG, MII_READ);
2235181Sgd78059 drv_usecwait(MII_DELAY);
2245181Sgd78059 dmfe_chip_put32(dmfep, ETHER_ROM_REG, MII_READ | MII_CLOCK);
2255181Sgd78059 drv_usecwait(MII_DELAY);
2265181Sgd78059 tmp = dmfe_chip_get32(dmfep, ETHER_ROM_REG);
2275181Sgd78059 data <<= 1;
2285181Sgd78059 data |= (tmp >> MII_DATA_IN_SHIFT) & 1;
2295181Sgd78059 }
2305181Sgd78059
2315181Sgd78059 /* leave the interface tristated */
2325181Sgd78059 dmfe_tristate_mii(dmfep);
2335181Sgd78059
2345181Sgd78059 return (ack ? data : ~0);
2355181Sgd78059 }
2365181Sgd78059
2375181Sgd78059 /*
2385181Sgd78059 * ======== Next level: 16-bit PHY register access routines ========
2395181Sgd78059 */
2405181Sgd78059
2415181Sgd78059 static void
dmfe_mii_write(void * arg,uint8_t phy_num,uint8_t reg_num,uint16_t reg_dat)242*9860Sgdamore@opensolaris.org dmfe_mii_write(void *arg, uint8_t phy_num, uint8_t reg_num, uint16_t reg_dat)
2435181Sgd78059 {
244*9860Sgdamore@opensolaris.org dmfe_t *dmfep = arg;
2455181Sgd78059 uint32_t command_word;
2465181Sgd78059
2475181Sgd78059 /* Issue MII command */
248*9860Sgdamore@opensolaris.org mutex_enter(dmfep->milock);
2495181Sgd78059 command_word = MII_WRITE_FRAME;
250*9860Sgdamore@opensolaris.org command_word |= phy_num << MII_PHY_ADDR_SHIFT;
2515181Sgd78059 command_word |= reg_num << MII_REG_ADDR_SHIFT;
2525181Sgd78059 command_word |= reg_dat;
2535181Sgd78059 dmfe_mii_command(dmfep, command_word, 2*mii_reg_size);
254*9860Sgdamore@opensolaris.org mutex_exit(dmfep->milock);
2555181Sgd78059 }
2565181Sgd78059
2575181Sgd78059 static uint16_t
dmfe_mii_read(void * arg,uint8_t phy_num,uint8_t reg_num)258*9860Sgdamore@opensolaris.org dmfe_mii_read(void *arg, uint8_t phy_num, uint8_t reg_num)
2595181Sgd78059 {
260*9860Sgdamore@opensolaris.org dmfe_t *dmfep = arg;
2615181Sgd78059 uint32_t command_word;
262*9860Sgdamore@opensolaris.org uint16_t rv;
2635181Sgd78059
2645181Sgd78059 /* Issue MII command */
2655181Sgd78059 command_word = MII_READ_FRAME;
266*9860Sgdamore@opensolaris.org command_word |= phy_num << MII_PHY_ADDR_SHIFT;
2675181Sgd78059 command_word |= reg_num << MII_REG_ADDR_SHIFT;
268*9860Sgdamore@opensolaris.org
269*9860Sgdamore@opensolaris.org mutex_enter(dmfep->milock);
2705181Sgd78059 dmfe_mii_command(dmfep, command_word, mii_reg_size-2);
2715181Sgd78059
272*9860Sgdamore@opensolaris.org rv = dmfe_mii_response(dmfep);
273*9860Sgdamore@opensolaris.org mutex_exit(dmfep->milock);
274*9860Sgdamore@opensolaris.org return (rv);
2755181Sgd78059 }
2765181Sgd78059
277*9860Sgdamore@opensolaris.org static void
dmfe_mii_notify(void * arg,link_state_t link)278*9860Sgdamore@opensolaris.org dmfe_mii_notify(void *arg, link_state_t link)
2795181Sgd78059 {
280*9860Sgdamore@opensolaris.org dmfe_t *dmfep = arg;
2815181Sgd78059
282*9860Sgdamore@opensolaris.org if (link == LINK_STATE_UP) {
283*9860Sgdamore@opensolaris.org mutex_enter(dmfep->oplock);
2845181Sgd78059 /*
285*9860Sgdamore@opensolaris.org * Configure DUPLEX setting on MAC.
2865181Sgd78059 */
287*9860Sgdamore@opensolaris.org if (mii_get_duplex(dmfep->mii) == LINK_DUPLEX_FULL) {
288*9860Sgdamore@opensolaris.org dmfep->opmode |= FULL_DUPLEX;
289*9860Sgdamore@opensolaris.org } else {
290*9860Sgdamore@opensolaris.org dmfep->opmode &= ~FULL_DUPLEX;
291*9860Sgdamore@opensolaris.org }
292*9860Sgdamore@opensolaris.org dmfe_chip_put32(dmfep, OPN_MODE_REG, dmfep->opmode);
293*9860Sgdamore@opensolaris.org mutex_exit(dmfep->oplock);
2945181Sgd78059 }
295*9860Sgdamore@opensolaris.org mac_link_update(dmfep->mh, link);
2965181Sgd78059 }
2975181Sgd78059
2985181Sgd78059
2995181Sgd78059 /*
3005181Sgd78059 * PHY initialisation, called only once
3015181Sgd78059 */
302*9860Sgdamore@opensolaris.org
303*9860Sgdamore@opensolaris.org static mii_ops_t dmfe_mii_ops = {
304*9860Sgdamore@opensolaris.org MII_OPS_VERSION,
305*9860Sgdamore@opensolaris.org dmfe_mii_read,
306*9860Sgdamore@opensolaris.org dmfe_mii_write,
307*9860Sgdamore@opensolaris.org dmfe_mii_notify,
308*9860Sgdamore@opensolaris.org NULL, /* mii_reset */
309*9860Sgdamore@opensolaris.org };
310*9860Sgdamore@opensolaris.org
3115181Sgd78059 boolean_t
dmfe_init_phy(dmfe_t * dmfep)3125181Sgd78059 dmfe_init_phy(dmfe_t *dmfep)
3135181Sgd78059 {
314*9860Sgdamore@opensolaris.org dmfep->mii = mii_alloc(dmfep, dmfep->devinfo, &dmfe_mii_ops);
315*9860Sgdamore@opensolaris.org if (dmfep->mii == NULL) {
316*9860Sgdamore@opensolaris.org return (B_FALSE);
3175181Sgd78059 }
318*9860Sgdamore@opensolaris.org return (B_TRUE);
3195181Sgd78059 }
320