1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21/*
22 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
23 * Use is subject to license terms.
24 */
25
26#include "dmfe_impl.h"
27
28/*
29 * The bit-twiddling required by the MII interface makes the functions
30 * in this file relatively slow, so they should probably only be called
31 * from base/low-pri code.  However, there's nothing here that really
32 * won't work at hi-pri, AFAIK; and 'relatively slow' only means that
33 * they have microsecond busy-waits all over the place.
34 */
35
36static const int mii_reg_size = 16;			/* bits		*/
37
38/*
39 * ======== Low-level SROM access ========
40 */
41
42/*
43 * EEPROM access is here because it shares register functionality with MII.
44 * NB: <romaddr> is a byte address but must be 16-bit aligned.
45 *     <cnt> is a byte count, and must be a multiple of 2.
46 */
47void
48dmfe_read_eeprom(dmfe_t *dmfep, uint16_t raddr, uint8_t *ptr, int cnt)
49{
50	uint16_t value;
51	uint16_t bit;
52
53	/* only a whole number of words for now */
54	ASSERT((cnt % 2) == 0);
55	ASSERT((raddr % 2) == 0);
56	ASSERT(cnt > 0);
57	ASSERT(((raddr + cnt) / 2) < (HIGH_ADDRESS_BIT << 1));
58
59	raddr /= 2;	/* make it a word address */
60
61	/* loop over multiple words... rom access in 16-bit increments */
62	while (cnt > 0) {
63
64		/* select the eeprom */
65		dmfe_chip_put32(dmfep, ETHER_ROM_REG, READ_EEPROM);
66		drv_usecwait(1);
67		dmfe_chip_put32(dmfep, ETHER_ROM_REG, READ_EEPROM_CS);
68		drv_usecwait(1);
69		dmfe_chip_put32(dmfep, ETHER_ROM_REG, READ_EEPROM_CS | SEL_CLK);
70		drv_usecwait(1);
71		dmfe_chip_put32(dmfep, ETHER_ROM_REG, READ_EEPROM_CS);
72		drv_usecwait(1);
73
74		/* send 3 bit read command */
75		for (bit = HIGH_CMD_BIT; bit != 0; bit >>= 1) {
76
77			value = (bit & EEPROM_READ_CMD) ? DATA_IN : 0;
78
79			/* strobe the bit in */
80			dmfe_chip_put32(dmfep, ETHER_ROM_REG,
81			    READ_EEPROM_CS | value);
82			drv_usecwait(1);
83			dmfe_chip_put32(dmfep, ETHER_ROM_REG,
84			    READ_EEPROM_CS | SEL_CLK | value);
85			drv_usecwait(1);
86			dmfe_chip_put32(dmfep, ETHER_ROM_REG,
87			    READ_EEPROM_CS | value);
88			drv_usecwait(1);
89		}
90
91		/* send 6 bit address */
92		for (bit = HIGH_ADDRESS_BIT; bit != 0; bit >>= 1) {
93			value = (bit & raddr) ? DATA_IN : 0;
94
95			/* strobe the bit in */
96			dmfe_chip_put32(dmfep, ETHER_ROM_REG,
97			    READ_EEPROM_CS | value);
98			drv_usecwait(1);
99			dmfe_chip_put32(dmfep, ETHER_ROM_REG,
100			    READ_EEPROM_CS | SEL_CLK | value);
101			drv_usecwait(1);
102			dmfe_chip_put32(dmfep, ETHER_ROM_REG,
103			    READ_EEPROM_CS | value);
104			drv_usecwait(1);
105		}
106
107		/* shift out data */
108		value = 0;
109		for (bit = HIGH_DATA_BIT; bit != 0; bit >>= 1) {
110
111			dmfe_chip_put32(dmfep, ETHER_ROM_REG,
112			    READ_EEPROM_CS | SEL_CLK);
113			drv_usecwait(1);
114
115			if (dmfe_chip_get32(dmfep, ETHER_ROM_REG) & DATA_OUT)
116				value |= bit;
117			drv_usecwait(1);
118
119			dmfe_chip_put32(dmfep, ETHER_ROM_REG, READ_EEPROM_CS);
120			drv_usecwait(1);
121		}
122
123		/* turn off EEPROM access */
124		dmfe_chip_put32(dmfep, ETHER_ROM_REG, READ_EEPROM);
125		drv_usecwait(1);
126
127		/* this makes it endian neutral */
128		*ptr++ = value & 0xff;
129		*ptr++ = (value >> 8);
130
131		cnt -= 2;
132		raddr++;
133	}
134}
135
136/*
137 * ======== Lowest-level bit-twiddling to drive MII interface ========
138 */
139
140/*
141 * Poke <nbits> (up to 32) bits from <mii_data> along the MII control lines.
142 * Note: the data is taken starting with the MSB of <mii_data> and working
143 * down through progressively less significant bits.
144 */
145static void
146dmfe_poke_mii(dmfe_t *dmfep, uint32_t mii_data, uint_t nbits)
147{
148	uint32_t dbit;
149
150	ASSERT(mutex_owned(dmfep->milock));
151
152	for (; nbits > 0; mii_data <<= 1, --nbits) {
153		/*
154		 * Extract the MSB of <mii_data> and shift it to the
155		 * proper bit position in the MII-poking register
156		 */
157		dbit = mii_data >> 31;
158		dbit <<= MII_DATA_OUT_SHIFT;
159		ASSERT((dbit & ~MII_DATA_OUT) == 0);
160
161		/*
162		 * Drive the bit across the wire ...
163		 */
164		dmfe_chip_put32(dmfep, ETHER_ROM_REG,
165		    MII_WRITE | dbit);			/* Clock Low	*/
166		drv_usecwait(MII_DELAY);
167		dmfe_chip_put32(dmfep, ETHER_ROM_REG,
168		    MII_WRITE | MII_CLOCK | dbit);	/* Clock High	*/
169		drv_usecwait(MII_DELAY);
170	}
171
172	dmfe_chip_put32(dmfep, ETHER_ROM_REG,
173	    MII_WRITE | dbit);				/* Clock Low	*/
174	drv_usecwait(MII_DELAY);
175}
176
177/*
178 * Put the MDIO port in tri-state for the turn around bits
179 * in MII read and at end of MII management sequence.
180 */
181static void
182dmfe_tristate_mii(dmfe_t *dmfep)
183{
184	ASSERT(mutex_owned(dmfep->milock));
185
186	dmfe_chip_put32(dmfep, ETHER_ROM_REG, MII_TRISTATE);
187	drv_usecwait(MII_DELAY);
188	dmfe_chip_put32(dmfep, ETHER_ROM_REG, MII_TRISTATE | MII_CLOCK);
189	drv_usecwait(MII_DELAY);
190}
191
192
193/*
194 * ======== Next level: issue an MII access command/get a response ========
195 */
196
197static void
198dmfe_mii_command(dmfe_t *dmfep, uint32_t command_word, int nbits)
199{
200	ASSERT(mutex_owned(dmfep->milock));
201
202	/* Write Preamble & Command & return to tristate */
203	dmfe_poke_mii(dmfep, MII_PREAMBLE, 2*mii_reg_size);
204	dmfe_poke_mii(dmfep, command_word, nbits);
205	dmfe_tristate_mii(dmfep);
206}
207
208static uint16_t
209dmfe_mii_response(dmfe_t *dmfep)
210{
211	boolean_t ack;
212	uint16_t data;
213	uint32_t tmp;
214	int i;
215
216	/* Check that the PHY generated a zero bit on the 2nd clock */
217	tmp = dmfe_chip_get32(dmfep, ETHER_ROM_REG);
218	ack = (tmp & MII_DATA_IN) == 0;
219
220	/* read data WORD */
221	for (data = 0, i = 0; i < mii_reg_size; ++i) {
222		dmfe_chip_put32(dmfep, ETHER_ROM_REG, MII_READ);
223		drv_usecwait(MII_DELAY);
224		dmfe_chip_put32(dmfep, ETHER_ROM_REG, MII_READ | MII_CLOCK);
225		drv_usecwait(MII_DELAY);
226		tmp = dmfe_chip_get32(dmfep, ETHER_ROM_REG);
227		data <<= 1;
228		data |= (tmp >> MII_DATA_IN_SHIFT) & 1;
229	}
230
231	/* leave the interface tristated */
232	dmfe_tristate_mii(dmfep);
233
234	return (ack ? data : ~0);
235}
236
237/*
238 * ======== Next level: 16-bit PHY register access routines ========
239 */
240
241static void
242dmfe_mii_write(void *arg, uint8_t phy_num, uint8_t reg_num, uint16_t reg_dat)
243{
244	dmfe_t *dmfep = arg;
245	uint32_t command_word;
246
247	/* Issue MII command */
248	mutex_enter(dmfep->milock);
249	command_word = MII_WRITE_FRAME;
250	command_word |= phy_num << MII_PHY_ADDR_SHIFT;
251	command_word |= reg_num << MII_REG_ADDR_SHIFT;
252	command_word |= reg_dat;
253	dmfe_mii_command(dmfep, command_word, 2*mii_reg_size);
254	mutex_exit(dmfep->milock);
255}
256
257static uint16_t
258dmfe_mii_read(void *arg, uint8_t phy_num, uint8_t reg_num)
259{
260	dmfe_t *dmfep = arg;
261	uint32_t command_word;
262	uint16_t rv;
263
264	/* Issue MII command */
265	command_word = MII_READ_FRAME;
266	command_word |= phy_num << MII_PHY_ADDR_SHIFT;
267	command_word |= reg_num << MII_REG_ADDR_SHIFT;
268
269	mutex_enter(dmfep->milock);
270	dmfe_mii_command(dmfep, command_word, mii_reg_size-2);
271
272	rv = dmfe_mii_response(dmfep);
273	mutex_exit(dmfep->milock);
274	return (rv);
275}
276
277static void
278dmfe_mii_notify(void *arg, link_state_t link)
279{
280	dmfe_t *dmfep = arg;
281
282	if (link == LINK_STATE_UP) {
283		mutex_enter(dmfep->oplock);
284		/*
285		 * Configure DUPLEX setting on MAC.
286		 */
287		if (mii_get_duplex(dmfep->mii) == LINK_DUPLEX_FULL) {
288			dmfep->opmode |= FULL_DUPLEX;
289		} else {
290			dmfep->opmode &= ~FULL_DUPLEX;
291		}
292		dmfe_chip_put32(dmfep, OPN_MODE_REG, dmfep->opmode);
293		mutex_exit(dmfep->oplock);
294	}
295	mac_link_update(dmfep->mh, link);
296}
297
298
299/*
300 * PHY initialisation, called only once
301 */
302
303static mii_ops_t dmfe_mii_ops = {
304	MII_OPS_VERSION,
305	dmfe_mii_read,
306	dmfe_mii_write,
307	dmfe_mii_notify,
308	NULL,			/* mii_reset */
309};
310
311boolean_t
312dmfe_init_phy(dmfe_t *dmfep)
313{
314	dmfep->mii = mii_alloc(dmfep, dmfep->devinfo, &dmfe_mii_ops);
315	if (dmfep->mii == NULL) {
316		return (B_FALSE);
317	}
318	return (B_TRUE);
319}
320