aboutsummaryrefslogtreecommitdiffstats
path: root/docs/results/yardstick-opnfv-ha.rst
blob: 4ee9de84796d23fe537dd7b0c3bd8c9994f60b6d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
.. This work is licensed under a Creative Commons Attribution 4.0 International
.. License.
.. http://creativecommons.org/licenses/by/4.0


===================================
Test Results for yardstick-opnfv-ha
===================================

.. toctree::
   :maxdepth: 2

Details
=======
There are two test cases, TC019 and TC025, for high availability (HA) test of
OPNFV platform, and both test cases were executed in CMCC's lab with 3+2 HA
deployment, where the installer is Arno SR1 release of fuel.


TC019
-----
This test case verifies the high availability of the openstack service, i.e.
"nova-api", on controller node.
There are one attacker, "kill-process" which kills all "nova-api" processes,
and two monitors, "openstack-cmd" monitoring "nova-api" service by openstack
command "nova image-list", while "process" monitor checks whether "nova-api"
process is running. Please see the test case description document for detail.

Overview of test results
------------------------
The service_outage_time of "nova image-list" is 0 seconds, while the
process_recover_time of "nova-api" is 300 seconds which equals the running time
of this test case, that means the "nova-api" service can't automatiocally
recover itself.

Detailed test results
---------------------
All "nova-api" process on the selected controller node was killed, and results
of two monitors were collected. Specifically, the results of "nova image-list"
request were collected from compute node and the status of "nova-api" process
were collected from the selected controller node.

Each monitor was running in a single process. The running time of each monitor
was about 300 seconds with no waiting time between twice monitor running. For
"nova image-list", the running times is 127, that's to say there is one
openstack command request every 2.36 seconds; while the running times is 141
for "nova-api" process checking, the accurancy is about 2.13 seconds.

The outage time of each monitor, which the name is "service_outage_time" for
"openstack-cmd" monitor and "process_recover_time" for "process" monitor, is
defined as the duration from the begin time of the first failure request to the
end time of the last failure request.

All "nova image-list" requestes were success, so the service_outage_time of
"nova image-list" is 0 second, while "nova-api" processes were not running for
all "process" checking, so the process_recover_time of "nova-api" is 300s.

Rationale for decisions
-----------------------
The service_outage_time is 0 second, that means the failover time of openstack
service is less than 2.36s, which is the period of each request. However, the
process_recover_time equals test case runing time, that means the process is
not automatically recovered, so this test case is fail.


TC025
-----
This test case verifies the high availability of controller node. When one of
the controller node abnormally shutdown, the service provided should be OK.
There are one attacker, "kill-process" which kills all "nova-api" processes,
and two "openstack-cmd" monitors, one monitoring openstack command
"nova image-list" and the other monitoring "neutron router-list".
Please see the test case description document for detail.

Overview of test results
------------------------
The both service_outage_time of "nova image-list" and "neutron router-list"
were 0 second.

Detailed test results
---------------------
A selected controller node was shutdown, and results of two monitors were
collected from compute node.

The return results of "nova image-list" and "neutron router-list" requests from
compute node were collected, then the failure requestion time were statistic
service_outage_time of corresponding service.

Each monitor was running in a single process. The running time of each monitor
was about 300 seconds with no waiting time between twice monitor running. For
"nova image-list", the running times is 49, that's to say there is one
openstack command request every 6.12 seconds; while the running times is 28 for
"neutron router-list", the accurancy is about 10.71 seconds.

The "service_outage_time" for two monitors is defined as the duration from the
begin time of the first failure request to the end time of the last failure
request.

All "nova image-list" and "neutron router-list" requestes were success, so the
service_outage_time of both two monitor were 0 second.

Rationale for decisions
-----------------------
As service_outage_time of all monitors are 0 second, that means there are none
failure request in this test case running time, this test case is passed.


Conclusions and recommendations
-------------------------------
The TC019 shows the killed process will be not automatically recovered, which
should be imporved.

There are several improvement points for HA test:
a) Running test cases in different enveriment deployed by different installers,
such as compass4nfv, apex and joid, with different versiones.
b) The period of each request is a little long, it needs more accurate test
 method.
c) More test cases with different faults and different monitors are needed.
588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724
/**************************************************************************
*
*    tlan.c -- Etherboot device driver for the Texas Instruments ThunderLAN
*    Written 2003-2003 by Timothy Legge <tlegge@rogers.com>
*
*    This program is free software; you can redistribute it and/or modify
*    it under the terms of the GNU General Public License as published by
*    the Free Software Foundation; either version 2 of the License, or
*    (at your option) any later version.
*
*    This program is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*    GNU General Public License for more details.
*
*    You should have received a copy of the GNU General Public License
*    along with this program; if not, write to the Free Software
*    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
*    02110-1301, USA.
*
*    Portions of this code based on:
*	lan.c: Linux ThunderLan Driver:
*
*	by James Banks
*
*  	(C) 1997-1998 Caldera, Inc.
*	(C) 1998 James Banks
*	(C) 1999-2001 Torben Mathiasen
*	(C) 2002 Samuel Chessman
*
*    REVISION HISTORY:
*    ================
*    v1.0	07-08-2003	timlegge	Initial not quite working version
*    v1.1	07-27-2003	timlegge	Sync 5.0 and 5.1 versions
*    v1.2	08-19-2003	timlegge	Implement Multicast Support
*    v1.3	08-23-2003	timlegge	Fix the transmit Function
*    v1.4	01-17-2004	timlegge	Initial driver output cleanup    
*    
*    Indent Options: indent -kr -i8
***************************************************************************/

FILE_LICENCE ( GPL2_OR_LATER );

#include "etherboot.h"
#include "nic.h"
#include <ipxe/pci.h>
#include <ipxe/ethernet.h>
#include <mii.h>
#include "tlan.h"

#define drv_version "v1.4"
#define drv_date "01-17-2004"

/* NIC specific static variables go here */
#define HZ 100
#define TX_TIME_OUT	  (6*HZ)

/* Condensed operations for readability. */
#define virt_to_le32desc(addr)  cpu_to_le32(virt_to_bus(addr))
#define le32desc_to_virt(addr)  bus_to_virt(le32_to_cpu(addr))

static void TLan_ResetLists(struct nic *nic __unused);
static void TLan_ResetAdapter(struct nic *nic __unused);
static void TLan_FinishReset(struct nic *nic __unused);

static void TLan_EeSendStart(u16);
static int TLan_EeSendByte(u16, u8, int);
static void TLan_EeReceiveByte(u16, u8 *, int);
static int TLan_EeReadByte(u16 io_base, u8, u8 *);

static void TLan_PhyDetect(struct nic *nic);
static void TLan_PhyPowerDown(struct nic *nic);
static void TLan_PhyPowerUp(struct nic *nic);


static void TLan_SetMac(struct nic *nic __unused, int areg, unsigned char *mac);

static void TLan_PhyReset(struct nic *nic);
static void TLan_PhyStartLink(struct nic *nic);
static void TLan_PhyFinishAutoNeg(struct nic *nic);

#ifdef MONITOR
static void TLan_PhyMonitor(struct nic *nic);
#endif


static void refill_rx(struct nic *nic __unused);

static int TLan_MiiReadReg(struct nic *nic __unused, u16, u16, u16 *);
static void TLan_MiiSendData(u16, u32, unsigned);
static void TLan_MiiSync(u16);
static void TLan_MiiWriteReg(struct nic *nic __unused, u16, u16, u16);


static const char *media[] = {
	"10BaseT-HD ", "10BaseT-FD ", "100baseTx-HD ",
	"100baseTx-FD", "100baseT4", NULL
};

/* This much match tlan_pci_tbl[]!  */
enum tlan_nics {
	NETEL10 = 0, NETEL100 = 1, NETFLEX3I = 2, THUNDER = 3, NETFLEX3B =
	    4, NETEL100PI = 5,
	NETEL100D = 6, NETEL100I = 7, OC2183 = 8, OC2325 = 9, OC2326 =
	    10, NETELLIGENT_10_100_WS_5100 = 11,
	NETELLIGENT_10_T2 = 12
};

struct pci_id_info {
	const char *name;
	int nic_id;
	struct match_info {
		u32 pci, pci_mask, subsystem, subsystem_mask;
		u32 revision, revision_mask;	/* Only 8 bits. */
	} id;
	u32 flags;
	u16 addrOfs;		/* Address Offset */
};

static const struct pci_id_info tlan_pci_tbl[] = {
	{"Compaq Netelligent 10 T PCI UTP", NETEL10,
	 {0xae340e11, 0xffffffff, 0, 0, 0, 0},
	 TLAN_ADAPTER_ACTIVITY_LED, 0x83},
	{"Compaq Netelligent 10/100 TX PCI UTP", NETEL100,
	 {0xae320e11, 0xffffffff, 0, 0, 0, 0},
	 TLAN_ADAPTER_ACTIVITY_LED, 0x83},
	{"Compaq Integrated NetFlex-3/P", NETFLEX3I,
	 {0xae350e11, 0xffffffff, 0, 0, 0, 0},
	 TLAN_ADAPTER_NONE, 0x83},
	{"Compaq NetFlex-3/P", THUNDER,
	 {0xf1300e11, 0xffffffff, 0, 0, 0, 0},
	 TLAN_ADAPTER_UNMANAGED_PHY | TLAN_ADAPTER_BIT_RATE_PHY, 0x83},
	{"Compaq NetFlex-3/P", NETFLEX3B,
	 {0xf1500e11, 0xffffffff, 0, 0, 0, 0},
	 TLAN_ADAPTER_NONE, 0x83},
	{"Compaq Netelligent Integrated 10/100 TX UTP", NETEL100PI,
	 {0xae430e11, 0xffffffff, 0, 0, 0, 0},
	 TLAN_ADAPTER_ACTIVITY_LED, 0x83},
	{"Compaq Netelligent Dual 10/100 TX PCI UTP", NETEL100D,
	 {0xae400e11, 0xffffffff, 0, 0, 0, 0},
	 TLAN_ADAPTER_NONE, 0x83},
	{"Compaq Netelligent 10/100 TX Embedded UTP", NETEL100I,
	 {0xb0110e11, 0xffffffff, 0, 0, 0, 0},
	 TLAN_ADAPTER_NONE, 0x83},
	{"Olicom OC-2183/2185", OC2183,
	 {0x0013108d, 0xffffffff, 0, 0, 0, 0},
	 TLAN_ADAPTER_USE_INTERN_10, 0x83},
	{"Olicom OC-2325", OC2325,
	 {0x0012108d, 0xffffffff, 0, 0, 0, 0},
	 TLAN_ADAPTER_UNMANAGED_PHY, 0xF8},
	{"Olicom OC-2326", OC2326,
	 {0x0014108d, 0xffffffff, 0, 0, 0, 0},
	 TLAN_ADAPTER_USE_INTERN_10, 0xF8},
	{"Compaq Netelligent 10/100 TX UTP", NETELLIGENT_10_100_WS_5100,
	 {0xb0300e11, 0xffffffff, 0, 0, 0, 0},
	 TLAN_ADAPTER_ACTIVITY_LED, 0x83},
	{"Compaq Netelligent 10 T/2 PCI UTP/Coax", NETELLIGENT_10_T2,
	 {0xb0120e11, 0xffffffff, 0, 0, 0, 0},
	 TLAN_ADAPTER_NONE, 0x83},
	{"Compaq NetFlex-3/E", 0,	/* EISA card */
	 {0, 0, 0, 0, 0, 0},
	 TLAN_ADAPTER_ACTIVITY_LED | TLAN_ADAPTER_UNMANAGED_PHY |
	 TLAN_ADAPTER_BIT_RATE_PHY, 0x83},
	{"Compaq NetFlex-3/E", 0,	/* EISA card */
	 {0, 0, 0, 0, 0, 0},
	 TLAN_ADAPTER_ACTIVITY_LED, 0x83},
	{NULL, 0,
	 {0, 0, 0, 0, 0, 0},
	 0, 0},
};

struct TLanList {
	u32 forward;
	u16 cStat;
	u16 frameSize;
	struct {
		u32 count;
		u32 address;
	} buffer[TLAN_BUFFERS_PER_LIST];
};

struct {
	struct TLanList tx_ring[TLAN_NUM_TX_LISTS];
	unsigned char txb[TLAN_MAX_FRAME_SIZE * TLAN_NUM_TX_LISTS];
	struct TLanList rx_ring[TLAN_NUM_RX_LISTS];
	unsigned char rxb[TLAN_MAX_FRAME_SIZE * TLAN_NUM_RX_LISTS];
} tlan_buffers __shared;
#define tx_ring tlan_buffers.tx_ring
#define txb tlan_buffers.txb
#define rx_ring tlan_buffers.rx_ring
#define rxb tlan_buffers.rxb

typedef u8 TLanBuffer[TLAN_MAX_FRAME_SIZE];

static int chip_idx;

/*****************************************************************
* TLAN Private Information Structure
*
****************************************************************/
static struct tlan_private {
	unsigned short vendor_id;	/* PCI Vendor code */
	unsigned short dev_id;	/* PCI Device code */
	const char *nic_name;
	unsigned int cur_rx, dirty_rx;	/* Producer/consumer ring indices */
	unsigned rx_buf_sz;	/* Based on mtu + Slack */
	struct TLanList *txList;
	u32 txHead;
	u32 txInProgress;
	u32 txTail;
	int eoc;
	u32 phyOnline;
	u32 aui;
	u32 duplex;
	u32 phy[2];
	u32 phyNum;
	u32 speed;
	u8 tlanRev;
	u8 tlanFullDuplex;
	u8 link;
	u8 neg_be_verbose;
} TLanPrivateInfo;

static struct tlan_private *priv;

static u32 BASE;

/***************************************************************
*	TLan_ResetLists
*
*	Returns:
*		Nothing
*	Parms:
*		dev	The device structure with the list
*			stuctures to be reset.
*
*	This routine sets the variables associated with managing
*	the TLAN lists to their initial values.
*
**************************************************************/

static void TLan_ResetLists(struct nic *nic __unused)
{

	int i;
	struct TLanList *list;
	priv->txHead = 0;
	priv->txTail = 0;

	for (i = 0; i < TLAN_NUM_TX_LISTS; i++) {
		list = &tx_ring[i];
		list->cStat = TLAN_CSTAT_UNUSED;
		list->buffer[0].address = virt_to_bus(txb + 
				(i * TLAN_MAX_FRAME_SIZE)); 
		list->buffer[2].count = 0;
		list->buffer[2].address = 0;
		list->buffer[9].address = 0;
	}

	priv->cur_rx = 0;
	priv->rx_buf_sz = (TLAN_MAX_FRAME_SIZE);
//	priv->rx_head_desc = &rx_ring[0];

	/* Initialize all the Rx descriptors */
	for (i = 0; i < TLAN_NUM_RX_LISTS; i++) {
		rx_ring[i].forward = virt_to_le32desc(&rx_ring[i + 1]);
		rx_ring[i].cStat = TLAN_CSTAT_READY;
		rx_ring[i].frameSize = TLAN_MAX_FRAME_SIZE;
		rx_ring[i].buffer[0].count =
		    TLAN_MAX_FRAME_SIZE | TLAN_LAST_BUFFER;
		rx_ring[i].buffer[0].address =
		    virt_to_le32desc(&rxb[i * TLAN_MAX_FRAME_SIZE]);
		rx_ring[i].buffer[1].count = 0;
		rx_ring[i].buffer[1].address = 0;
	}

	/* Mark the last entry as wrapping the ring */
	rx_ring[i - 1].forward = virt_to_le32desc(&rx_ring[0]);
	priv->dirty_rx = (unsigned int) (i - TLAN_NUM_RX_LISTS);

} /* TLan_ResetLists */

/***************************************************************
*	TLan_Reset
*
*	Returns:
*		0
*	Parms:
*		dev	Pointer to device structure of adapter
*			to be reset.
*
*	This function resets the adapter and it's physical
*	device.  See Chap. 3, pp. 9-10 of the "ThunderLAN
*	Programmer's Guide" for details.  The routine tries to
*	implement what is detailed there, though adjustments
*	have been made.
*
**************************************************************/

void TLan_ResetAdapter(struct nic *nic __unused)
{
	int i;
	u32 addr;
	u32 data;
	u8 data8;

	priv->tlanFullDuplex = FALSE;
	priv->phyOnline = 0;
/*  1.	Assert reset bit. */

	data = inl(BASE + TLAN_HOST_CMD);
	data |= TLAN_HC_AD_RST;
	outl(data, BASE + TLAN_HOST_CMD);

	udelay(1000);

/*  2.	Turn off interrupts. ( Probably isn't necessary ) */

	data = inl(BASE + TLAN_HOST_CMD);
	data |= TLAN_HC_INT_OFF;
	outl(data, BASE + TLAN_HOST_CMD);
/*  3.	Clear AREGs and HASHs. */

	for (i = TLAN_AREG_0; i <= TLAN_HASH_2; i += 4) {
		TLan_DioWrite32(BASE, (u16) i, 0);
	}

/*  4.	Setup NetConfig register. */

	data =
	    TLAN_NET_CFG_1FRAG | TLAN_NET_CFG_1CHAN | TLAN_NET_CFG_PHY_EN;
	TLan_DioWrite16(BASE, TLAN_NET_CONFIG, (u16) data);

/*  5.	Load Ld_Tmr and Ld_Thr in HOST_CMD. */

	outl(TLAN_HC_LD_TMR | 0x3f, BASE + TLAN_HOST_CMD);
	outl(TLAN_HC_LD_THR | 0x0, BASE + TLAN_HOST_CMD);

/*  6.	Unreset the MII by setting NMRST (in NetSio) to 1. */

	outw(TLAN_NET_SIO, BASE + TLAN_DIO_ADR);
	addr = BASE + TLAN_DIO_DATA + TLAN_NET_SIO;
	TLan_SetBit(TLAN_NET_SIO_NMRST, addr);

/*  7.	Setup the remaining registers. */

	if (priv->tlanRev >= 0x30) {
		data8 = TLAN_ID_TX_EOC | TLAN_ID_RX_EOC;
		TLan_DioWrite8(BASE, TLAN_INT_DIS, data8);
	}
	TLan_PhyDetect(nic);
	data = TLAN_NET_CFG_1FRAG | TLAN_NET_CFG_1CHAN;

	if (tlan_pci_tbl[chip_idx].flags & TLAN_ADAPTER_BIT_RATE_PHY) {
		data |= TLAN_NET_CFG_BIT;
		if (priv->aui == 1) {
			TLan_DioWrite8(BASE, TLAN_ACOMMIT, 0x0a);
		} else if (priv->duplex == TLAN_DUPLEX_FULL) {
			TLan_DioWrite8(BASE, TLAN_ACOMMIT, 0x00);
			priv->tlanFullDuplex = TRUE;
		} else {
			TLan_DioWrite8(BASE, TLAN_ACOMMIT, 0x08);
		}
	}

	if (priv->phyNum == 0) {
		data |= TLAN_NET_CFG_PHY_EN;
	}
	TLan_DioWrite16(BASE, TLAN_NET_CONFIG, (u16) data);

	if (tlan_pci_tbl[chip_idx].flags & TLAN_ADAPTER_UNMANAGED_PHY) {
		TLan_FinishReset(nic);
	} else {
		TLan_PhyPowerDown(nic);
	}

}	/* TLan_ResetAdapter */

void TLan_FinishReset(struct nic *nic)
{

	u8 data;
	u32 phy;
	u8 sio;
	u16 status;
	u16 partner;
	u16 tlphy_ctl;
	u16 tlphy_par;
	u16 tlphy_id1, tlphy_id2;
	int i;

	phy = priv->phy[priv->phyNum];

	data = TLAN_NET_CMD_NRESET | TLAN_NET_CMD_NWRAP;
	if (priv->tlanFullDuplex) {
		data |= TLAN_NET_CMD_DUPLEX;
	}
	TLan_DioWrite8(BASE, TLAN_NET_CMD, data);
	data = TLAN_NET_MASK_MASK4 | TLAN_NET_MASK_MASK5;
	if (priv->phyNum == 0) {
		data |= TLAN_NET_MASK_MASK7;
	}
	TLan_DioWrite8(BASE, TLAN_NET_MASK, data);
	TLan_DioWrite16(BASE, TLAN_MAX_RX, ((1536) + 7) & ~7);
	TLan_MiiReadReg(nic, phy, MII_PHYSID1, &tlphy_id1);
	TLan_MiiReadReg(nic, phy, MII_PHYSID2, &tlphy_id2);

	if ((tlan_pci_tbl[chip_idx].flags & TLAN_ADAPTER_UNMANAGED_PHY)
	    || (priv->aui)) {
		status = BMSR_LSTATUS;
		DBG ( "TLAN:  %s: Link forced.\n", priv->nic_name );
	} else {
		TLan_MiiReadReg(nic, phy, MII_BMSR, &status);
		udelay(1000);
		TLan_MiiReadReg(nic, phy, MII_BMSR, &status);
		if ((status & BMSR_LSTATUS) &&	/* We only support link info on Nat.Sem. PHY's */
		    (tlphy_id1 == NAT_SEM_ID1)
		    && (tlphy_id2 == NAT_SEM_ID2)) {
			TLan_MiiReadReg(nic, phy, MII_LPA, &partner);
			TLan_MiiReadReg(nic, phy, TLAN_TLPHY_PAR,
					&tlphy_par);

			DBG ( "TLAN: %s: Link active with ",
			       priv->nic_name );
			if (!(tlphy_par & TLAN_PHY_AN_EN_STAT)) {
				DBG ( "forced 10%sMbps %s-Duplex\n",
				       tlphy_par & TLAN_PHY_SPEED_100 ? ""
				       : "0",
				       tlphy_par & TLAN_PHY_DUPLEX_FULL ?
				       "Full" : "Half" );
			} else {
				DBG 
				    ( "AutoNegotiation enabled, at 10%sMbps %s-Duplex\n",
				     tlphy_par & TLAN_PHY_SPEED_100 ? "" :
				     "0",
				     tlphy_par & TLAN_PHY_DUPLEX_FULL ?
				     "Full" : "Half" );
				DBG ( "TLAN: Partner capability: " );
				for (i = 5; i <= 10; i++)
					if (partner & (1 << i)) {
						DBG ( "%s", media[i - 5] );
					}
				DBG ( "\n" );
			}

			TLan_DioWrite8(BASE, TLAN_LED_REG, TLAN_LED_LINK);
#ifdef MONITOR
			/* We have link beat..for now anyway */
			priv->link = 1;
			/*Enabling link beat monitoring */
			/* TLan_SetTimer( nic, (10*HZ), TLAN_TIMER_LINK_BEAT ); */
			mdelay(10000);
			TLan_PhyMonitor(nic);
#endif
		} else if (status & BMSR_LSTATUS) {
			DBG ( "TLAN: %s: Link active\n", priv->nic_name );
			TLan_DioWrite8(BASE, TLAN_LED_REG, TLAN_LED_LINK);
		}
	}

	if (priv->phyNum == 0) {
		TLan_MiiReadReg(nic, phy, TLAN_TLPHY_CTL, &tlphy_ctl);
		tlphy_ctl |= TLAN_TC_INTEN;
		TLan_MiiWriteReg(nic, phy, TLAN_TLPHY_CTL, tlphy_ctl);
		sio = TLan_DioRead8(BASE, TLAN_NET_SIO);
		sio |= TLAN_NET_SIO_MINTEN;
		TLan_DioWrite8(BASE, TLAN_NET_SIO, sio);
	}

	if (status & BMSR_LSTATUS) {
		TLan_SetMac(nic, 0, nic->node_addr);
		priv->phyOnline = 1;
		outb((TLAN_HC_INT_ON >> 8), BASE + TLAN_HOST_CMD + 1);
		outl(virt_to_bus(&rx_ring), BASE + TLAN_CH_PARM);
		outl(TLAN_HC_GO | TLAN_HC_RT, BASE + TLAN_HOST_CMD);
	} else {
		DBG 
		    ( "TLAN: %s: Link inactive, will retry in 10 secs...\n",
		     priv->nic_name );
		/* TLan_SetTimer( nic, (10*HZ), TLAN_TIMER_FINISH_RESET ); */
		mdelay(10000);
		TLan_FinishReset(nic);
		return;

	}

}	/* TLan_FinishReset */

/**************************************************************************
POLL - Wait for a frame
***************************************************************************/
static int tlan_poll(struct nic *nic, int retrieve)
{
	/* return true if there's an ethernet packet ready to read */
	/* nic->packet should contain data on return */
	/* nic->packetlen should contain length of data */
	u32 framesize;
	u32 host_cmd = 0;
	u32 ack = 1;
	int eoc = 0;
	int entry = priv->cur_rx % TLAN_NUM_RX_LISTS;
	u16 tmpCStat = le32_to_cpu(rx_ring[entry].cStat);
	u16 host_int = inw(BASE + TLAN_HOST_INT);

	if ((tmpCStat & TLAN_CSTAT_FRM_CMP) && !retrieve)
	  return 1;

	outw(host_int, BASE + TLAN_HOST_INT);

	if (!(tmpCStat & TLAN_CSTAT_FRM_CMP))
		return 0;

	/* printf("PI-1: 0x%hX\n", host_int); */
	if (tmpCStat & TLAN_CSTAT_EOC)
		eoc = 1;

	framesize = rx_ring[entry].frameSize;

	nic->packetlen = framesize;

	DBG ( ".%d.", (unsigned int) framesize ); 
     
	memcpy(nic->packet, rxb +
	       (priv->cur_rx * TLAN_MAX_FRAME_SIZE), nic->packetlen);

	rx_ring[entry].cStat = 0;

	DBG ( "%d", entry );  

	entry = (entry + 1) % TLAN_NUM_RX_LISTS;
	priv->cur_rx = entry;
	if (eoc) {
		if ((rx_ring[entry].cStat & TLAN_CSTAT_READY) ==
		    TLAN_CSTAT_READY) {
			ack |= TLAN_HC_GO | TLAN_HC_RT;
			host_cmd = TLAN_HC_ACK | ack | 0x001C0000;
			outl(host_cmd, BASE + TLAN_HOST_CMD);
		}
	} else {
		host_cmd = TLAN_HC_ACK | ack | (0x000C0000);
		outl(host_cmd, BASE + TLAN_HOST_CMD);
		
		DBG ( "AC: 0x%hX\n", inw(BASE + TLAN_CH_PARM) ); 
		DBG ( "PI-2: 0x%hX\n", inw(BASE + TLAN_HOST_INT) );
	}
	refill_rx(nic);
	return (1);		/* initially as this is called to flush the input */
}

static void refill_rx(struct nic *nic __unused)
{
	int entry = 0;

	for (;
	     (priv->cur_rx - priv->dirty_rx +
	      TLAN_NUM_RX_LISTS) % TLAN_NUM_RX_LISTS > 0;
	     priv->dirty_rx = (priv->dirty_rx + 1) % TLAN_NUM_RX_LISTS) {
		entry = priv->dirty_rx % TLAN_NUM_TX_LISTS;
		rx_ring[entry].frameSize = TLAN_MAX_FRAME_SIZE;
		rx_ring[entry].cStat = TLAN_CSTAT_READY;
	}

}

/**************************************************************************
TRANSMIT - Transmit a frame
***************************************************************************/
static void tlan_transmit(struct nic *nic, const char *d,	/* Destination */
			  unsigned int t,	/* Type */
			  unsigned int s,	/* size */
			  const char *p)
{				/* Packet */
	u16 nstype;
	u32 to;
	struct TLanList *tail_list;
	struct TLanList *head_list;
	u8 *tail_buffer;
	u32 ack = 0;
	u32 host_cmd;
	int eoc = 0;
	u16 tmpCStat;
	u16 host_int = inw(BASE + TLAN_HOST_INT);

	int entry = 0;

	DBG ( "INT0-0x%hX\n", host_int );

	if (!priv->phyOnline) {
		printf("TRANSMIT:  %s PHY is not ready\n", priv->nic_name);
		return;
	}

	tail_list = priv->txList + priv->txTail;

	if (tail_list->cStat != TLAN_CSTAT_UNUSED) {
		printf("TRANSMIT: %s is busy (Head=%p Tail=%x)\n",
		       priv->nic_name, priv->txList, (unsigned int) priv->txTail);
		tx_ring[entry].cStat = TLAN_CSTAT_UNUSED;
//		priv->txBusyCount++;
		return;
	}

	tail_list->forward = 0;

	tail_buffer = txb + (priv->txTail * TLAN_MAX_FRAME_SIZE);

	/* send the packet to destination */
	memcpy(tail_buffer, d, ETH_ALEN);
	memcpy(tail_buffer + ETH_ALEN, nic->node_addr, ETH_ALEN);
	nstype = htons((u16) t);
	memcpy(tail_buffer + 2 * ETH_ALEN, (u8 *) & nstype, 2);
	memcpy(tail_buffer + ETH_HLEN, p, s);

	s += ETH_HLEN;
	s &= 0x0FFF;
	while (s < ETH_ZLEN)
		tail_buffer[s++] = '\0';

	/*=====================================================*/
	/* Receive
	 * 0000 0000 0001 1100
	 * 0000 0000 0000 1100
	 * 0000 0000 0000 0011 = 0x0003
	 *
	 * 0000 0000 0000 0000 0000 0000 0000 0011
	 * 0000 0000 0000 1100 0000 0000 0000 0000 = 0x000C0000
	 *
	 * Transmit
	 * 0000 0000 0001 1100
	 * 0000 0000 0000 0100
	 * 0000 0000 0000 0001 = 0x0001
	 *
	 * 0000 0000 0000 0000 0000 0000 0000 0001
	 * 0000 0000 0000 0100 0000 0000 0000 0000 = 0x00040000
	 * */

	/* Setup the transmit descriptor */
	tail_list->frameSize = (u16) s;
	tail_list->buffer[0].count = TLAN_LAST_BUFFER | (u32) s;
	tail_list->buffer[1].count = 0;
	tail_list->buffer[1].address = 0;

	tail_list->cStat = TLAN_CSTAT_READY;

	DBG ( "INT1-0x%hX\n", inw(BASE + TLAN_HOST_INT) );

	if (!priv->txInProgress) {
		priv->txInProgress = 1;
		outl(virt_to_le32desc(tail_list), BASE + TLAN_CH_PARM);
		outl(TLAN_HC_GO, BASE + TLAN_HOST_CMD);
	} else {
		if (priv->txTail == 0) {
			DBG ( "Out buffer\n" );
			(priv->txList + (TLAN_NUM_TX_LISTS - 1))->forward =
			    virt_to_le32desc(tail_list);
		} else {
			DBG ( "Fix this \n" );
			(priv->txList + (priv->txTail - 1))->forward =
			    virt_to_le32desc(tail_list);
		}
	}
	
	CIRC_INC(priv->txTail, TLAN_NUM_TX_LISTS);

	DBG ( "INT2-0x%hX\n", inw(BASE + TLAN_HOST_INT) );

	to = currticks() + TX_TIME_OUT;
	while ((tail_list->cStat == TLAN_CSTAT_READY) && currticks() < to);

	head_list = priv->txList + priv->txHead;
	while (((tmpCStat = head_list->cStat) & TLAN_CSTAT_FRM_CMP) 
			&& (ack < 255)) {
		ack++;
		if(tmpCStat & TLAN_CSTAT_EOC)
			eoc =1;
		head_list->cStat = TLAN_CSTAT_UNUSED;
		CIRC_INC(priv->txHead, TLAN_NUM_TX_LISTS);
		head_list = priv->txList + priv->txHead;
		
	}
	if(!ack)
		printf("Incomplete TX Frame\n");

	if(eoc) {
		head_list = priv->txList + priv->txHead;
		if ((head_list->cStat & TLAN_CSTAT_READY) == TLAN_CSTAT_READY) {
			outl(virt_to_le32desc(head_list), BASE + TLAN_CH_PARM);
			ack |= TLAN_HC_GO;
		} else {
			priv->txInProgress = 0;
		}
	}
	if(ack) {
		host_cmd = TLAN_HC_ACK | ack;
		outl(host_cmd, BASE + TLAN_HOST_CMD);
	}
	
	if(priv->tlanRev < 0x30 ) {
		ack = 1;
		head_list = priv->txList + priv->txHead;
		if ((head_list->cStat & TLAN_CSTAT_READY) == TLAN_CSTAT_READY) {
			outl(virt_to_le32desc(head_list), BASE + TLAN_CH_PARM);
			ack |= TLAN_HC_GO;
		} else {
			priv->txInProgress = 0;
		}
		host_cmd = TLAN_HC_ACK | ack | 0x00140000;
		outl(host_cmd, BASE + TLAN_HOST_CMD);
		
	}
			
	if (currticks() >= to) {
		printf("TX Time Out");
	}
}

/**************************************************************************
DISABLE - Turn off ethernet interface
***************************************************************************/
static void tlan_disable ( struct nic *nic __unused ) {
	/* put the card in its initial state */
	/* This function serves 3 purposes.
	 * This disables DMA and interrupts so we don't receive
	 *  unexpected packets or interrupts from the card after
	 *  etherboot has finished.
	 * This frees resources so etherboot may use
	 *  this driver on another interface
	 * This allows etherboot to reinitialize the interface
	 *  if something is something goes wrong.
	 *
	 */
	outl(TLAN_HC_AD_RST, BASE + TLAN_HOST_CMD);
}

/**************************************************************************
IRQ - Enable, Disable, or Force interrupts
***************************************************************************/
static void tlan_irq(struct nic *nic __unused, irq_action_t action __unused)
{
  switch ( action ) {
  case DISABLE :
    break;
  case ENABLE :
    break;
  case FORCE :
    break;
  }
}

static struct nic_operations tlan_operations = {
	.connect	= dummy_connect,
	.poll		= tlan_poll,
	.transmit	= tlan_transmit,
	.irq		= tlan_irq,

};

static void TLan_SetMulticastList(struct nic *nic) {
	int i;
	u8 tmp;

	/* !IFF_PROMISC */
	tmp = TLan_DioRead8(BASE, TLAN_NET_CMD);
	TLan_DioWrite8(BASE, TLAN_NET_CMD, tmp & ~TLAN_NET_CMD_CAF);

	/* IFF_ALLMULTI */
	for(i = 0; i< 3; i++)
		TLan_SetMac(nic, i + 1, NULL);
	TLan_DioWrite32(BASE, TLAN_HASH_1, 0xFFFFFFFF);
	TLan_DioWrite32(BASE, TLAN_HASH_2, 0xFFFFFFFF);

	
}
/**************************************************************************
PROBE - Look for an adapter, this routine's visible to the outside
***************************************************************************/

#define board_found 1
#define valid_link 0
static int tlan_probe ( struct nic *nic, struct pci_device *pci ) {

	u16 data = 0;
	int err;
	int i;

	if (pci->ioaddr == 0)
		return 0;

	nic->irqno  = 0;
	nic->ioaddr = pci->ioaddr;

	BASE = pci->ioaddr;

	/* Set nic as PCI bus master */
	adjust_pci_device(pci);
	
	/* Point to private storage */
	priv = &TLanPrivateInfo;

	/* Figure out which chip we're dealing with */
	i = 0;
	chip_idx = -1;
	while (tlan_pci_tbl[i].name) {
		if ((((u32) pci->device << 16) | pci->vendor) ==
		    (tlan_pci_tbl[i].id.pci & 0xffffffff)) {
			chip_idx = i;
			break;
		}
		i++;
	}

	priv->vendor_id = pci->vendor;
	priv->dev_id = pci->device;
	priv->nic_name = pci->id->name;
	priv->eoc = 0;

	err = 0;
	for (i = 0; i < 6; i++)
		err |= TLan_EeReadByte(BASE,
				       (u8) tlan_pci_tbl[chip_idx].
				       addrOfs + i,
				       (u8 *) & nic->node_addr[i]);
	if (err) {
  	    printf ( "TLAN: %s: Error reading MAC from eeprom: %d\n",
		    pci->id->name, err);
	} else {
	    DBG ( "%s: %s at ioaddr %#lX, ", 
		  pci->id->name, eth_ntoa ( nic->node_addr ), pci->ioaddr );
	}

	priv->tlanRev = TLan_DioRead8(BASE, TLAN_DEF_REVISION);
	printf("revision: 0x%hX\n", priv->tlanRev);

	TLan_ResetLists(nic);
	TLan_ResetAdapter(nic);

	data = inl(BASE + TLAN_HOST_CMD);
	data |= TLAN_HC_INT_OFF;
	outw(data, BASE + TLAN_HOST_CMD);

	TLan_SetMulticastList(nic);
	udelay(100); 
	priv->txList = tx_ring;

/*	if (board_found && valid_link)
	{*/
	/* point to NIC specific routines */
	nic->nic_op	= &tlan_operations;
	return 1;
}


/*****************************************************************************
******************************************************************************

	ThunderLAN Driver Eeprom routines

	The Compaq Netelligent 10 and 10/100 cards use a Microchip 24C02A
	EEPROM.  These functions are based on information in Microchip's
	data sheet.  I don't know how well this functions will work with
	other EEPROMs.

******************************************************************************
*****************************************************************************/


/***************************************************************
*	TLan_EeSendStart
*
*	Returns:
*		Nothing
*	Parms:
*		io_base		The IO port base address for the
*				TLAN device with the EEPROM to
*				use.
*
*	This function sends a start cycle to an EEPROM attached
*	to a TLAN chip.
*
**************************************************************/

void TLan_EeSendStart(u16 io_base)
{
	u16 sio;

	outw(TLAN_NET_SIO, io_base + TLAN_DIO_ADR);
	sio = io_base + TLAN_DIO_DATA + TLAN_NET_SIO;

	TLan_SetBit(TLAN_NET_SIO_ECLOK, sio);
	TLan_SetBit(TLAN_NET_SIO_EDATA, sio);
	TLan_SetBit(TLAN_NET_SIO_ETXEN, sio);
	TLan_ClearBit(TLAN_NET_SIO_EDATA, sio);
	TLan_ClearBit(TLAN_NET_SIO_ECLOK, sio);

}	/* TLan_EeSendStart */

/***************************************************************
*	TLan_EeSendByte
*
*	Returns:
*		If the correct ack was received, 0, otherwise 1
*	Parms:	io_base		The IO port base address for the
*				TLAN device with the EEPROM to
*				use.
*		data		The 8 bits of information to
*				send to the EEPROM.
*		stop		If TLAN_EEPROM_STOP is passed, a
*				stop cycle is sent after the
*				byte is sent after the ack is
*				read.
*
*	This function sends a byte on the serial EEPROM line,
*	driving the clock to send each bit. The function then
*	reverses transmission direction and reads an acknowledge
*	bit.
*
**************************************************************/

int TLan_EeSendByte(u16 io_base, u8 data, int stop)
{
	int err;
	u8 place;
	u16 sio;

	outw(TLAN_NET_SIO, io_base + TLAN_DIO_ADR);
	sio = io_base + TLAN_DIO_DATA + TLAN_NET_SIO;

	/* Assume clock is low, tx is enabled; */
	for (place = 0x80; place != 0; place >>= 1) {
		if (place & data)
			TLan_SetBit(TLAN_NET_SIO_EDATA, sio);
		else
			TLan_ClearBit(TLAN_NET_SIO_EDATA, sio);
		TLan_SetBit(TLAN_NET_SIO_ECLOK, sio);
		TLan_ClearBit(TLAN_NET_SIO_ECLOK, sio);
	}
	TLan_ClearBit(TLAN_NET_SIO_ETXEN, sio);
	TLan_SetBit(TLAN_NET_SIO_ECLOK, sio);
	err = TLan_GetBit(TLAN_NET_SIO_EDATA, sio);
	TLan_ClearBit(TLAN_NET_SIO_ECLOK, sio);
	TLan_SetBit(TLAN_NET_SIO_ETXEN, sio);

	if ((!err) && stop) {
		TLan_ClearBit(TLAN_NET_SIO_EDATA, sio);	/* STOP, raise data while clock is high */
		TLan_SetBit(TLAN_NET_SIO_ECLOK, sio);
		TLan_SetBit(TLAN_NET_SIO_EDATA, sio);
	}

	return (err);

}	/* TLan_EeSendByte */

/***************************************************************
*	TLan_EeReceiveByte
*
*	Returns:
*		Nothing
*	Parms:
*		io_base		The IO port base address for the
*				TLAN device with the EEPROM to
*				use.
*		data		An address to a char to hold the
*				data sent from the EEPROM.
*		stop		If TLAN_EEPROM_STOP is passed, a
*				stop cycle is sent after the
*				byte is received, and no ack is
*				sent.
*
*	This function receives 8 bits of data from the EEPROM
*	over the serial link.  It then sends and ack bit, or no
*	ack and a stop bit.  This function is used to retrieve
*	data after the address of a byte in the EEPROM has been
*	sent.
*
**************************************************************/

void TLan_EeReceiveByte(u16 io_base, u8 * data, int stop)
{
	u8 place;
	u16 sio;

	outw(TLAN_NET_SIO, io_base + TLAN_DIO_ADR);
	sio = io_base + TLAN_DIO_DATA + TLAN_NET_SIO;
	*data = 0;

	/* Assume clock is low, tx is enabled; */
	TLan_ClearBit(TLAN_NET_SIO_ETXEN, sio);
	for (place = 0x80; place; place >>= 1) {
		TLan_SetBit(TLAN_NET_SIO_ECLOK, sio);
		if (TLan_GetBit(TLAN_NET_SIO_EDATA, sio))
			*data |= place;
		TLan_ClearBit(TLAN_NET_SIO_ECLOK, sio);
	}

	TLan_SetBit(TLAN_NET_SIO_ETXEN, sio);
	if (!stop) {
		TLan_ClearBit(TLAN_NET_SIO_EDATA, sio);	/* Ack = 0 */
		TLan_SetBit(TLAN_NET_SIO_ECLOK, sio);
		TLan_ClearBit(TLAN_NET_SIO_ECLOK, sio);
	} else {
		TLan_SetBit(TLAN_NET_SIO_EDATA, sio);	/* No ack = 1 (?) */
		TLan_SetBit(TLAN_NET_SIO_ECLOK, sio);
		TLan_ClearBit(TLAN_NET_SIO_ECLOK, sio);
		TLan_ClearBit(TLAN_NET_SIO_EDATA, sio);	/* STOP, raise data while clock is high */
		TLan_SetBit(TLAN_NET_SIO_ECLOK, sio);
		TLan_SetBit(TLAN_NET_SIO_EDATA, sio);
	}

}	/* TLan_EeReceiveByte */

/***************************************************************
*	TLan_EeReadByte
*
*	Returns:
*		No error = 0, else, the stage at which the error
*		occurred.
*	Parms:
*		io_base		The IO port base address for the
*				TLAN device with the EEPROM to
*				use.
*		ee_addr		The address of the byte in the
*				EEPROM whose contents are to be
*				retrieved.
*		data		An address to a char to hold the
*				data obtained from the EEPROM.
*
*	This function reads a byte of information from an byte
*	cell in the EEPROM.
*
**************************************************************/

int TLan_EeReadByte(u16 io_base, u8 ee_addr, u8 * data)
{
	int err;
	int ret = 0;


	TLan_EeSendStart(io_base);
	err = TLan_EeSendByte(io_base, 0xA0, TLAN_EEPROM_ACK);
	if (err) {
		ret = 1;
		goto fail;
	}
	err = TLan_EeSendByte(io_base, ee_addr, TLAN_EEPROM_ACK);
	if (err) {
		ret = 2;
		goto fail;
	}
	TLan_EeSendStart(io_base);
	err = TLan_EeSendByte(io_base, 0xA1, TLAN_EEPROM_ACK);
	if (err) {
		ret = 3;
		goto fail;
	}
	TLan_EeReceiveByte(io_base, data, TLAN_EEPROM_STOP);
      fail:

	return ret;

}	/* TLan_EeReadByte */


/*****************************************************************************
******************************************************************************

ThunderLAN Driver MII Routines

These routines are based on the information in Chap. 2 of the
"ThunderLAN Programmer's Guide", pp. 15-24.

******************************************************************************
*****************************************************************************/


/***************************************************************
*	TLan_MiiReadReg
*
*	Returns:
*		0	if ack received ok
*		1	otherwise.
*
*	Parms:
*		dev		The device structure containing
*				The io address and interrupt count
*				for this device.
*		phy		The address of the PHY to be queried.
*		reg		The register whose contents are to be
*				retrieved.
*		val		A pointer to a variable to store the
*				retrieved value.
*
*	This function uses the TLAN's MII bus to retrieve the contents
*	of a given register on a PHY.  It sends the appropriate info
*	and then reads the 16-bit register value from the MII bus via
*	the TLAN SIO register.
*
**************************************************************/

int TLan_MiiReadReg(struct nic *nic __unused, u16 phy, u16 reg, u16 * val)
{
	u8 nack;
	u16 sio, tmp;
	u32 i;
	int err;
	int minten;

	err = FALSE;
	outw(TLAN_NET_SIO, BASE + TLAN_DIO_ADR);
	sio = BASE + TLAN_DIO_DATA + TLAN_NET_SIO;

	TLan_MiiSync(BASE);

	minten = TLan_GetBit(TLAN_NET_SIO_MINTEN, sio);
	if (minten)
		TLan_ClearBit(TLAN_NET_SIO_MINTEN, sio);

	TLan_MiiSendData(BASE, 0x1, 2);	/* Start ( 01b ) */
	TLan_MiiSendData(BASE, 0x2, 2);	/* Read  ( 10b ) */
	TLan_MiiSendData(BASE, phy, 5);	/* Device #      */
	TLan_MiiSendData(BASE, reg, 5);	/* Register #    */


	TLan_ClearBit(TLAN_NET_SIO_MTXEN, sio);	/* Change direction */

	TLan_ClearBit(TLAN_NET_SIO_MCLK, sio);	/* Clock Idle bit */
	TLan_SetBit(TLAN_NET_SIO_MCLK, sio);
	TLan_ClearBit(TLAN_NET_SIO_MCLK, sio);	/* Wait 300ns */

	nack = TLan_GetBit(TLAN_NET_SIO_MDATA, sio);	/* Check for ACK */
	TLan_SetBit(TLAN_NET_SIO_MCLK, sio);	/* Finish ACK */
	if (nack) {		/* No ACK, so fake it */
		for (i = 0; i < 16; i++) {
			TLan_ClearBit(TLAN_NET_SIO_MCLK, sio);
			TLan_SetBit(TLAN_NET_SIO_MCLK, sio);
		}
		tmp = 0xffff;
		err = TRUE;
	} else {		/* ACK, so read data */
		for (tmp = 0, i = 0x8000; i; i >>= 1) {
			TLan_ClearBit(TLAN_NET_SIO_MCLK, sio);
			if (TLan_GetBit(TLAN_NET_SIO_MDATA, sio))
				tmp |= i;
			TLan_SetBit(TLAN_NET_SIO_MCLK, sio);
		}
	}


	TLan_ClearBit(TLAN_NET_SIO_MCLK, sio);	/* Idle cycle */
	TLan_SetBit(TLAN_NET_SIO_MCLK, sio);

	if (minten)
		TLan_SetBit(TLAN_NET_SIO_MINTEN, sio);

	*val = tmp;

	return err;

}				/* TLan_MiiReadReg */

/***************************************************************
*	TLan_MiiSendData
*
*	Returns:
*		Nothing
*	Parms:
*		base_port	The base IO port of the adapter	in
*				question.
*		dev		The address of the PHY to be queried.
*		data		The value to be placed on the MII bus.
*		num_bits	The number of bits in data that are to
*				be placed on the MII bus.
*
*	This function sends on sequence of bits on the MII
*	configuration bus.
*
**************************************************************/

void TLan_MiiSendData(u16 base_port, u32 data, unsigned num_bits)
{
	u16 sio;
	u32 i;

	if (num_bits == 0)
		return;

	outw(TLAN_NET_SIO, base_port + TLAN_DIO_ADR);
	sio = base_port + TLAN_DIO_DATA + TLAN_NET_SIO;
	TLan_SetBit(TLAN_NET_SIO_MTXEN, sio);

	for (i = (0x1 << (num_bits - 1)); i; i >>= 1) {
		TLan_ClearBit(TLAN_NET_SIO_MCLK, sio);
		(void) TLan_GetBit(TLAN_NET_SIO_MCLK, sio);
		if (data & i)
			TLan_SetBit(TLAN_NET_SIO_MDATA, sio);
		else
			TLan_ClearBit(TLAN_NET_SIO_MDATA, sio);
		TLan_SetBit(TLAN_NET_SIO_MCLK, sio);
		(void) TLan_GetBit(TLAN_NET_SIO_MCLK, sio);
	}

}				/* TLan_MiiSendData */

/***************************************************************
*	TLan_MiiSync
*
*	Returns:
*		Nothing
*	Parms:
*		base_port	The base IO port of the adapter in
*				question.
*
*	This functions syncs all PHYs in terms of the MII configuration
*	bus.
*
**************************************************************/

void TLan_MiiSync(u16 base_port)
{
	int i;
	u16 sio;

	outw(TLAN_NET_SIO, base_port + TLAN_DIO_ADR);
	sio = base_port + TLAN_DIO_DATA + TLAN_NET_SIO;

	TLan_ClearBit(TLAN_NET_SIO_MTXEN, sio);
	for (i = 0; i < 32; i++) {
		TLan_ClearBit(TLAN_NET_SIO_MCLK, sio);
		TLan_SetBit(TLAN_NET_SIO_MCLK, sio);
	}

}				/* TLan_MiiSync */

/***************************************************************
*	TLan_MiiWriteReg
*
*	Returns:
*		Nothing
*	Parms:
*		dev		The device structure for the device
*				to write to.
*		phy		The address of the PHY to be written to.
*		reg		The register whose contents are to be
*				written.
*		val		The value to be written to the register.
*
*	This function uses the TLAN's MII bus to write the contents of a
*	given register on a PHY.  It sends the appropriate info and then
*	writes the 16-bit register value from the MII configuration bus
*	via the TLAN SIO register.
*
**************************************************************/

void TLan_MiiWriteReg(struct nic *nic __unused, u16 phy, u16 reg, u16 val)
{
	u16 sio;
	int minten;

	outw(TLAN_NET_SIO, BASE + TLAN_DIO_ADR);
	sio = BASE + TLAN_DIO_DATA + TLAN_NET_SIO;

	TLan_MiiSync(BASE);

	minten = TLan_GetBit(TLAN_NET_SIO_MINTEN, sio);
	if (minten)
		TLan_ClearBit(TLAN_NET_SIO_MINTEN, sio);

	TLan_MiiSendData(BASE, 0x1, 2);	/* Start ( 01b ) */
	TLan_MiiSendData(BASE, 0x1, 2);	/* Write ( 01b ) */
	TLan_MiiSendData(BASE, phy, 5);	/* Device #      */
	TLan_MiiSendData(BASE, reg, 5);	/* Register #    */

	TLan_MiiSendData(BASE, 0x2, 2);	/* Send ACK */
	TLan_MiiSendData(BASE, val, 16);	/* Send Data */

	TLan_ClearBit(TLAN_NET_SIO_MCLK, sio);	/* Idle cycle */
	TLan_SetBit(TLAN_NET_SIO_MCLK, sio);

	if (minten)
		TLan_SetBit(TLAN_NET_SIO_MINTEN, sio);


}				/* TLan_MiiWriteReg */

/***************************************************************
*	TLan_SetMac
*
*	Returns:
*		Nothing
*	Parms:
*		dev	Pointer to device structure of adapter
*			on which to change the AREG.
*		areg	The AREG to set the address in (0 - 3).
*		mac	A pointer to an array of chars.  Each
*			element stores one byte of the address.
*			IE, it isn't in ascii.
*
*	This function transfers a MAC address to one of the
*	TLAN AREGs (address registers).  The TLAN chip locks
*	the register on writing to offset 0 and unlocks the
*	register after writing to offset 5.  If NULL is passed
*	in mac, then the AREG is filled with 0's.
*
**************************************************************/

void TLan_SetMac(struct nic *nic __unused, int areg, unsigned char *mac)
{
	int i;

	areg *= 6;

	if (mac != NULL) {
		for (i = 0; i < 6; i++)
			TLan_DioWrite8(BASE, TLAN_AREG_0 + areg + i,
				       mac[i]);
	} else {
		for (i = 0; i < 6; i++)
			TLan_DioWrite8(BASE, TLAN_AREG_0 + areg + i, 0);
	}

}				/* TLan_SetMac */

/*********************************************************************
*	TLan_PhyDetect
*
*	Returns:
*		Nothing
*	Parms:
*		dev	A pointer to the device structure of the adapter
*			for which the PHY needs determined.
*
*	So far I've found that adapters which have external PHYs
*	may also use the internal PHY for part of the functionality.
*	(eg, AUI/Thinnet).  This function finds out if this TLAN
*	chip has an internal PHY, and then finds the first external
*	PHY (starting from address 0) if it exists).
*
********************************************************************/

void TLan_PhyDetect(struct nic *nic)
{
	u16 control;
	u16 hi;
	u16 lo;
	u32 phy;

	if (tlan_pci_tbl[chip_idx].flags & TLAN_ADAPTER_UNMANAGED_PHY) {
		priv->phyNum = 0xFFFF;
		return;
	}

	TLan_MiiReadReg(nic, TLAN_PHY_MAX_ADDR, MII_PHYSID1, &hi);

	if (hi != 0xFFFF) {
		priv->phy[0] = TLAN_PHY_MAX_ADDR;
	} else {
		priv->phy[0] = TLAN_PHY_NONE;
	}

	priv->phy[1] = TLAN_PHY_NONE;
	for (phy = 0; phy <= TLAN_PHY_MAX_ADDR; phy++) {
		TLan_MiiReadReg(nic, phy, MII_BMCR, &control);
		TLan_MiiReadReg(nic, phy, MII_PHYSID1, &hi);
		TLan_MiiReadReg(nic, phy, MII_PHYSID2, &lo);
		if ((control != 0xFFFF) || (hi != 0xFFFF)
		    || (lo != 0xFFFF)) {
			printf("PHY found at %hX %hX %hX %hX\n", 
			       (unsigned int) phy, control, hi, lo);
			if ((priv->phy[1] == TLAN_PHY_NONE)
			    && (phy != TLAN_PHY_MAX_ADDR)) {
				priv->phy[1] = phy;
			}
		}
	}

	if (priv->phy[1] != TLAN_PHY_NONE) {
		priv->phyNum = 1;
	} else if (priv->phy[0] != TLAN_PHY_NONE) {
		priv->phyNum = 0;
	} else {
		printf
		    ("TLAN:  Cannot initialize device, no PHY was found!\n");
	}

}				/* TLan_PhyDetect */

void TLan_PhyPowerDown(struct nic *nic)
{

	u16 value;
	DBG ( "%s: Powering down PHY(s).\n", priv->nic_name );
	value = BMCR_PDOWN | BMCR_LOOPBACK | BMCR_ISOLATE;
	TLan_MiiSync(BASE);
	TLan_MiiWriteReg(nic, priv->phy[priv->phyNum], MII_BMCR, value);
	if ((priv->phyNum == 0) && (priv->phy[1] != TLAN_PHY_NONE)
	    &&
	    (!(tlan_pci_tbl[chip_idx].
	       flags & TLAN_ADAPTER_USE_INTERN_10))) {
		TLan_MiiSync(BASE);
		TLan_MiiWriteReg(nic, priv->phy[1], MII_BMCR, value);
	}

	/* Wait for 50 ms and powerup
	 * This is abitrary.  It is intended to make sure the
	 * tranceiver settles.
	 */
	/* TLan_SetTimer( dev, (HZ/20), TLAN_TIMER_PHY_PUP ); */
	mdelay(50);
	TLan_PhyPowerUp(nic);

}				/* TLan_PhyPowerDown */


void TLan_PhyPowerUp(struct nic *nic)
{
	u16 value;

	DBG ( "%s: Powering up PHY.\n", priv->nic_name );
	TLan_MiiSync(BASE);
	value = BMCR_LOOPBACK;
	TLan_MiiWriteReg(nic, priv->phy[priv->phyNum], MII_BMCR, value);
	TLan_MiiSync(BASE);
	/* Wait for 500 ms and reset the
	 * tranceiver.  The TLAN docs say both 50 ms and
	 * 500 ms, so do the longer, just in case.
	 */
	mdelay(500);
	TLan_PhyReset(nic);
	/* TLan_SetTimer( dev, (HZ/20), TLAN_TIMER_PHY_RESET ); */

}				/* TLan_PhyPowerUp */

void TLan_PhyReset(struct nic *nic)
{
	u16 phy;
	u16 value;

	phy = priv->phy[priv->phyNum];

	DBG ( "%s: Reseting PHY.\n", priv->nic_name );
	TLan_MiiSync(BASE);
	value = BMCR_LOOPBACK | BMCR_RESET;
	TLan_MiiWriteReg(nic, phy, MII_BMCR, value);
	TLan_MiiReadReg(nic, phy, MII_BMCR, &value);
	while (value & BMCR_RESET) {
		TLan_MiiReadReg(nic, phy, MII_BMCR, &value);
	}

	/* Wait for 500 ms and initialize.
	 * I don't remember why I wait this long.
	 * I've changed this to 50ms, as it seems long enough.
	 */
	/* TLan_SetTimer( dev, (HZ/20), TLAN_TIMER_PHY_START_LINK ); */
	mdelay(50);
	TLan_PhyStartLink(nic);

}				/* TLan_PhyReset */


void TLan_PhyStartLink(struct nic *nic)
{

	u16 ability;
	u16 control;
	u16 data;
	u16 phy;
	u16 status;
	u16 tctl;

	phy = priv->phy[priv->phyNum];
	DBG ( "%s: Trying to activate link.\n", priv->nic_name );
	TLan_MiiReadReg(nic, phy, MII_BMSR, &status);
	TLan_MiiReadReg(nic, phy, MII_BMSR, &ability);

	if ((status & BMSR_ANEGCAPABLE) && (!priv->aui)) {
		ability = status >> 11;
		if (priv->speed == TLAN_SPEED_10 &&
		    priv->duplex == TLAN_DUPLEX_HALF) {
			TLan_MiiWriteReg(nic, phy, MII_BMCR, 0x0000);
		} else if (priv->speed == TLAN_SPEED_10 &&
			   priv->duplex == TLAN_DUPLEX_FULL) {
			priv->tlanFullDuplex = TRUE;
			TLan_MiiWriteReg(nic, phy, MII_BMCR, 0x0100);
		} else if (priv->speed == TLAN_SPEED_100 &&
			   priv->duplex == TLAN_DUPLEX_HALF) {
			TLan_MiiWriteReg(nic, phy, MII_BMCR, 0x2000);
		} else if (priv->speed == TLAN_SPEED_100 &&
			   priv->duplex == TLAN_DUPLEX_FULL) {
			priv->tlanFullDuplex = TRUE;
			TLan_MiiWriteReg(nic, phy, MII_BMCR, 0x2100);
		} else {

			/* Set Auto-Neg advertisement */
			TLan_MiiWriteReg(nic, phy, MII_ADVERTISE,
					 (ability << 5) | 1);
			/* Enablee Auto-Neg */
			TLan_MiiWriteReg(nic, phy, MII_BMCR, 0x1000);
			/* Restart Auto-Neg */
			TLan_MiiWriteReg(nic, phy, MII_BMCR, 0x1200);
			/* Wait for 4 sec for autonegotiation
			 * to complete.  The max spec time is less than this
			 * but the card need additional time to start AN.
			 * .5 sec should be plenty extra.
			 */
			DBG ( "TLAN: %s: Starting autonegotiation.\n",
			       priv->nic_name );
			mdelay(4000);
			TLan_PhyFinishAutoNeg(nic);
			/* TLan_SetTimer( dev, (2*HZ), TLAN_TIMER_PHY_FINISH_AN ); */
			return;
		}

	}

	if ((priv->aui) && (priv->phyNum != 0)) {
		priv->phyNum = 0;
		data =
		    TLAN_NET_CFG_1FRAG | TLAN_NET_CFG_1CHAN |
		    TLAN_NET_CFG_PHY_EN;
		TLan_DioWrite16(BASE, TLAN_NET_CONFIG, data);
		mdelay(50);
		/* TLan_SetTimer( dev, (40*HZ/1000), TLAN_TIMER_PHY_PDOWN ); */
		TLan_PhyPowerDown(nic);
		return;
	} else if (priv->phyNum == 0) {
		control = 0;
		TLan_MiiReadReg(nic, phy, TLAN_TLPHY_CTL, &tctl);
		if (priv->aui) {
			tctl |= TLAN_TC_AUISEL;
		} else {
			tctl &= ~TLAN_TC_AUISEL;
			if (priv->duplex == TLAN_DUPLEX_FULL) {
				control |= BMCR_FULLDPLX;
				priv->tlanFullDuplex = TRUE;
			}
			if (priv->speed == TLAN_SPEED_100) {
				control |= BMCR_SPEED100;
			}
		}
		TLan_MiiWriteReg(nic, phy, MII_BMCR, control);
		TLan_MiiWriteReg(nic, phy, TLAN_TLPHY_CTL, tctl);
	}

	/* Wait for 2 sec to give the tranceiver time
	 * to establish link.
	 */
	/* TLan_SetTimer( dev, (4*HZ), TLAN_TIMER_FINISH_RESET ); */
	mdelay(2000);
	TLan_FinishReset(nic);

}				/* TLan_PhyStartLink */

void TLan_PhyFinishAutoNeg(struct nic *nic)
{

	u16 an_adv;
	u16 an_lpa;
	u16 data;
	u16 mode;
	u16 phy;
	u16 status;

	phy = priv->phy[priv->phyNum];

	TLan_MiiReadReg(nic, phy, MII_BMSR, &status);
	udelay(1000);
	TLan_MiiReadReg(nic, phy, MII_BMSR, &status);

	if (!(status & BMSR_ANEGCOMPLETE)) {
		/* Wait for 8 sec to give the process
		 * more time.  Perhaps we should fail after a while.
		 */
		if (!priv->neg_be_verbose++) {
			printf
			    ("TLAN:  Giving autonegotiation more time.\n");
			printf
			    ("TLAN:  Please check that your adapter has\n");
			printf
			    ("TLAN:  been properly connected to a HUB or Switch.\n");
			printf
			    ("TLAN:  Trying to establish link in the background...\n");
		}
		mdelay(8000);
		TLan_PhyFinishAutoNeg(nic);
		/* TLan_SetTimer( dev, (8*HZ), TLAN_TIMER_PHY_FINISH_AN ); */
		return;
	}

	DBG ( "TLAN: %s: Autonegotiation complete.\n", priv->nic_name );
	TLan_MiiReadReg(nic, phy, MII_ADVERTISE, &an_adv);
	TLan_MiiReadReg(nic, phy, MII_LPA, &an_lpa);
	mode = an_adv & an_lpa & 0x03E0;
	if (mode & 0x0100) {
		printf("Full Duplex\n");
		priv->tlanFullDuplex = TRUE;
	} else if (!(mode & 0x0080) && (mode & 0x0040)) {
		priv->tlanFullDuplex = TRUE;
		printf("Full Duplex\n");
	}

	if ((!(mode & 0x0180))
	    && (tlan_pci_tbl[chip_idx].flags & TLAN_ADAPTER_USE_INTERN_10)
	    && (priv->phyNum != 0)) {
		priv->phyNum = 0;
		data =
		    TLAN_NET_CFG_1FRAG | TLAN_NET_CFG_1CHAN |
		    TLAN_NET_CFG_PHY_EN;
		TLan_DioWrite16(BASE, TLAN_NET_CONFIG, data);
		/* TLan_SetTimer( nic, (400*HZ/1000), TLAN_TIMER_PHY_PDOWN ); */
		mdelay(400);
		TLan_PhyPowerDown(nic);
		return;
	}

	if (priv->phyNum == 0) {
		if ((priv->duplex == TLAN_DUPLEX_FULL)
		    || (an_adv & an_lpa & 0x0040)) {
			TLan_MiiWriteReg(nic, phy, MII_BMCR,
					 BMCR_ANENABLE | BMCR_FULLDPLX);
			DBG 
			    ( "TLAN:  Starting internal PHY with FULL-DUPLEX\n" );
		} else {
			TLan_MiiWriteReg(nic, phy, MII_BMCR,
					 BMCR_ANENABLE);
			DBG 
			    ( "TLAN:  Starting internal PHY with HALF-DUPLEX\n" );
		}
	}

	/* Wait for 100 ms.  No reason in partiticular.
	 */
	/* TLan_SetTimer( dev, (HZ/10), TLAN_TIMER_FINISH_RESET ); */
	mdelay(100);
	TLan_FinishReset(nic);

}				/* TLan_PhyFinishAutoNeg */

#ifdef MONITOR

/*********************************************************************
*
*      TLan_phyMonitor
*
*      Returns:
*              None
*
*      Params:
*              dev             The device structure of this device.
*
*
*      This function monitors PHY condition by reading the status
*      register via the MII bus. This can be used to give info
*      about link changes (up/down), and possible switch to alternate
*      media.
*
********************************************************************/

void TLan_PhyMonitor(struct net_device *dev)
{
	TLanPrivateInfo *priv = dev->priv;
	u16 phy;
	u16 phy_status;

	phy = priv->phy[priv->phyNum];

	/* Get PHY status register */
	TLan_MiiReadReg(nic, phy, MII_BMSR, &phy_status);

	/* Check if link has been lost */
	if (!(phy_status & BMSR_LSTATUS)) {
		if (priv->link) {
			priv->link = 0;
			printf("TLAN: %s has lost link\n", priv->nic_name);
			priv->flags &= ~IFF_RUNNING;
			mdelay(2000);
			TLan_PhyMonitor(nic);
			/* TLan_SetTimer( dev, (2*HZ), TLAN_TIMER_LINK_BEAT ); */
			return;
		}
	}

	/* Link restablished? */
	if ((phy_status & BMSR_LSTATUS) && !priv->link) {
		priv->link = 1;
		printf("TLAN: %s has reestablished link\n",
		       priv->nic_name);
		priv->flags |= IFF_RUNNING;
	}

	/* Setup a new monitor */
	/* TLan_SetTimer( dev, (2*HZ), TLAN_TIMER_LINK_BEAT ); */
	mdelay(2000);
	TLan_PhyMonitor(nic);
}

#endif				/* MONITOR */

static struct pci_device_id tlan_nics[] = {
	PCI_ROM(0x0e11, 0xae34, "netel10", "Compaq Netelligent 10 T PCI UTP", 0),
	PCI_ROM(0x0e11, 0xae32, "netel100","Compaq Netelligent 10/100 TX PCI UTP", 0),
	PCI_ROM(0x0e11, 0xae35, "netflex3i", "Compaq Integrated NetFlex-3/P", 0),
	PCI_ROM(0x0e11, 0xf130, "thunder", "Compaq NetFlex-3/P", 0),
	PCI_ROM(0x0e11, 0xf150, "netflex3b", "Compaq NetFlex-3/P", 0),
	PCI_ROM(0x0e11, 0xae43, "netel100pi", "Compaq Netelligent Integrated 10/100 TX UTP", 0),
	PCI_ROM(0x0e11, 0xae40, "netel100d", "Compaq Netelligent Dual 10/100 TX PCI UTP", 0),
	PCI_ROM(0x0e11, 0xb011, "netel100i", "Compaq Netelligent 10/100 TX Embedded UTP", 0),
	PCI_ROM(0x108d, 0x0013, "oc2183", "Olicom OC-2183/2185", 0),
	PCI_ROM(0x108d, 0x0012, "oc2325", "Olicom OC-2325", 0),
	PCI_ROM(0x108d, 0x0014, "oc2326", "Olicom OC-2326", 0),
	PCI_ROM(0x0e11, 0xb030, "netelligent_10_100_ws_5100", "Compaq Netelligent 10/100 TX UTP", 0),
	PCI_ROM(0x0e11, 0xb012, "netelligent_10_t2", "Compaq Netelligent 10 T/2 PCI UTP/Coax", 0),
};

PCI_DRIVER ( tlan_driver, tlan_nics, PCI_NO_CLASS );

DRIVER ( "TLAN/PCI", nic_driver, pci_driver, tlan_driver,
	 tlan_probe, tlan_disable );

/*
 * Local variables:
 *  c-basic-offset: 8
 *  c-indent-level: 8
 *  tab-width: 8
 * End:
 */