2005-07-31 03:31:23 +04:00
/*
* drivers / net / phy / marvell . c
*
* Driver for Marvell PHYs
*
* Author : Andy Fleming
*
* Copyright ( c ) 2004 Freescale Semiconductor , Inc .
*
* 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 .
*
*/
# include <linux/kernel.h>
# include <linux/string.h>
# include <linux/errno.h>
# include <linux/unistd.h>
# include <linux/slab.h>
# include <linux/interrupt.h>
# include <linux/init.h>
# include <linux/delay.h>
# include <linux/netdevice.h>
# include <linux/etherdevice.h>
# include <linux/skbuff.h>
# include <linux/spinlock.h>
# include <linux/mm.h>
# include <linux/module.h>
# include <linux/mii.h>
# include <linux/ethtool.h>
# include <linux/phy.h>
# include <asm/io.h>
# include <asm/irq.h>
# include <asm/uaccess.h>
# define MII_M1011_IEVENT 0x13
# define MII_M1011_IEVENT_CLEAR 0x0000
# define MII_M1011_IMASK 0x12
# define MII_M1011_IMASK_INIT 0x6400
# define MII_M1011_IMASK_CLEAR 0x0000
2007-02-10 03:13:58 +03:00
# define MII_M1011_PHY_SCR 0x10
# define MII_M1011_PHY_SCR_AUTO_CROSS 0x0060
# define MII_M1145_PHY_EXT_CR 0x14
# define MII_M1145_RGMII_RX_DELAY 0x0080
# define MII_M1145_RGMII_TX_DELAY 0x0002
# define M1145_DEV_FLAGS_RESISTANCE 0x00000001
# define MII_M1111_PHY_LED_CONTROL 0x18
# define MII_M1111_PHY_LED_DIRECT 0x4100
# define MII_M1111_PHY_LED_COMBINE 0x411c
2007-06-05 14:46:47 +04:00
# define MII_M1111_PHY_EXT_CR 0x14
# define MII_M1111_RX_DELAY 0x80
# define MII_M1111_TX_DELAY 0x2
# define MII_M1111_PHY_EXT_SR 0x1b
2008-03-19 00:37:24 +03:00
# define MII_M1111_HWCFG_MODE_MASK 0xf
# define MII_M1111_HWCFG_MODE_COPPER_RGMII 0xb
# define MII_M1111_HWCFG_MODE_FIBER_RGMII 0x3
2007-05-12 03:25:18 +04:00
# define MII_M1111_HWCFG_MODE_SGMII_NO_CLK 0x4
2010-01-14 01:13:19 +03:00
# define MII_M1111_HWCFG_MODE_COPPER_RTBI 0x9
2008-03-19 00:37:24 +03:00
# define MII_M1111_HWCFG_FIBER_COPPER_AUTO 0x8000
# define MII_M1111_HWCFG_FIBER_COPPER_RES 0x2000
# define MII_M1111_COPPER 0
# define MII_M1111_FIBER 1
2009-04-07 06:01:41 +04:00
# define MII_88E1121_PHY_LED_CTRL 16
# define MII_88E1121_PHY_LED_PAGE 3
# define MII_88E1121_PHY_LED_DEF 0x0030
# define MII_88E1121_PHY_PAGE 22
2008-03-19 00:37:24 +03:00
# define MII_M1011_PHY_STATUS 0x11
# define MII_M1011_PHY_STATUS_1000 0x8000
# define MII_M1011_PHY_STATUS_100 0x4000
# define MII_M1011_PHY_STATUS_SPD_MASK 0xc000
# define MII_M1011_PHY_STATUS_FULLDUPLEX 0x2000
# define MII_M1011_PHY_STATUS_RESOLVED 0x0800
# define MII_M1011_PHY_STATUS_LINK 0x0400
2007-02-10 03:13:58 +03:00
2005-07-31 03:31:23 +04:00
MODULE_DESCRIPTION ( " Marvell PHY driver " ) ;
MODULE_AUTHOR ( " Andy Fleming " ) ;
MODULE_LICENSE ( " GPL " ) ;
static int marvell_ack_interrupt ( struct phy_device * phydev )
{
int err ;
/* Clear the interrupts by reading the reg */
err = phy_read ( phydev , MII_M1011_IEVENT ) ;
if ( err < 0 )
return err ;
return 0 ;
}
static int marvell_config_intr ( struct phy_device * phydev )
{
int err ;
2007-02-10 03:13:58 +03:00
if ( phydev - > interrupts = = PHY_INTERRUPT_ENABLED )
2005-07-31 03:31:23 +04:00
err = phy_write ( phydev , MII_M1011_IMASK , MII_M1011_IMASK_INIT ) ;
else
err = phy_write ( phydev , MII_M1011_IMASK , MII_M1011_IMASK_CLEAR ) ;
return err ;
}
static int marvell_config_aneg ( struct phy_device * phydev )
{
int err ;
/* The Marvell PHY has an errata which requires
* that certain registers get written in order
* to restart autonegotiation */
err = phy_write ( phydev , MII_BMCR , BMCR_RESET ) ;
if ( err < 0 )
return err ;
err = phy_write ( phydev , 0x1d , 0x1f ) ;
if ( err < 0 )
return err ;
err = phy_write ( phydev , 0x1e , 0x200c ) ;
if ( err < 0 )
return err ;
err = phy_write ( phydev , 0x1d , 0x5 ) ;
if ( err < 0 )
return err ;
err = phy_write ( phydev , 0x1e , 0 ) ;
if ( err < 0 )
return err ;
err = phy_write ( phydev , 0x1e , 0x100 ) ;
if ( err < 0 )
return err ;
2007-02-10 03:13:58 +03:00
err = phy_write ( phydev , MII_M1011_PHY_SCR ,
MII_M1011_PHY_SCR_AUTO_CROSS ) ;
if ( err < 0 )
return err ;
err = phy_write ( phydev , MII_M1111_PHY_LED_CONTROL ,
MII_M1111_PHY_LED_DIRECT ) ;
if ( err < 0 )
return err ;
2005-07-31 03:31:23 +04:00
err = genphy_config_aneg ( phydev ) ;
2009-09-09 20:01:30 +04:00
if ( err < 0 )
return err ;
2005-07-31 03:31:23 +04:00
2009-09-09 20:01:30 +04:00
if ( phydev - > autoneg ! = AUTONEG_ENABLE ) {
int bmcr ;
/*
* A write to speed / duplex bits ( that is performed by
* genphy_config_aneg ( ) call above ) must be followed by
* a software reset . Otherwise , the write has no effect .
*/
bmcr = phy_read ( phydev , MII_BMCR ) ;
if ( bmcr < 0 )
return bmcr ;
err = phy_write ( phydev , MII_BMCR , bmcr | BMCR_RESET ) ;
if ( err < 0 )
return err ;
}
return 0 ;
2005-07-31 03:31:23 +04:00
}
2009-04-07 06:01:41 +04:00
static int m88e1121_config_aneg ( struct phy_device * phydev )
{
int err , temp ;
err = phy_write ( phydev , MII_BMCR , BMCR_RESET ) ;
if ( err < 0 )
return err ;
err = phy_write ( phydev , MII_M1011_PHY_SCR ,
MII_M1011_PHY_SCR_AUTO_CROSS ) ;
if ( err < 0 )
return err ;
temp = phy_read ( phydev , MII_88E1121_PHY_PAGE ) ;
phy_write ( phydev , MII_88E1121_PHY_PAGE , MII_88E1121_PHY_LED_PAGE ) ;
phy_write ( phydev , MII_88E1121_PHY_LED_CTRL , MII_88E1121_PHY_LED_DEF ) ;
phy_write ( phydev , MII_88E1121_PHY_PAGE , temp ) ;
err = genphy_config_aneg ( phydev ) ;
return err ;
}
2007-06-05 14:46:47 +04:00
static int m88e1111_config_init ( struct phy_device * phydev )
{
int err ;
2008-03-19 00:37:24 +03:00
int temp ;
/* Enable Fiber/Copper auto selection */
temp = phy_read ( phydev , MII_M1111_PHY_EXT_SR ) ;
2008-07-16 17:46:17 +04:00
temp & = ~ MII_M1111_HWCFG_FIBER_COPPER_AUTO ;
2008-03-19 00:37:24 +03:00
phy_write ( phydev , MII_M1111_PHY_EXT_SR , temp ) ;
temp = phy_read ( phydev , MII_BMCR ) ;
temp | = BMCR_RESET ;
phy_write ( phydev , MII_BMCR , temp ) ;
2007-06-05 14:46:47 +04:00
if ( ( phydev - > interface = = PHY_INTERFACE_MODE_RGMII ) | |
2007-11-27 01:17:52 +03:00
( phydev - > interface = = PHY_INTERFACE_MODE_RGMII_ID ) | |
( phydev - > interface = = PHY_INTERFACE_MODE_RGMII_RXID ) | |
( phydev - > interface = = PHY_INTERFACE_MODE_RGMII_TXID ) ) {
2007-06-05 14:46:47 +04:00
2007-11-27 01:17:52 +03:00
temp = phy_read ( phydev , MII_M1111_PHY_EXT_CR ) ;
if ( temp < 0 )
return temp ;
2007-06-05 14:46:47 +04:00
2007-11-27 01:17:52 +03:00
if ( phydev - > interface = = PHY_INTERFACE_MODE_RGMII_ID ) {
2007-06-05 14:46:47 +04:00
temp | = ( MII_M1111_RX_DELAY | MII_M1111_TX_DELAY ) ;
2007-11-27 01:17:52 +03:00
} else if ( phydev - > interface = = PHY_INTERFACE_MODE_RGMII_RXID ) {
temp & = ~ MII_M1111_TX_DELAY ;
temp | = MII_M1111_RX_DELAY ;
} else if ( phydev - > interface = = PHY_INTERFACE_MODE_RGMII_TXID ) {
temp & = ~ MII_M1111_RX_DELAY ;
temp | = MII_M1111_TX_DELAY ;
2007-06-05 14:46:47 +04:00
}
2007-11-27 01:17:52 +03:00
err = phy_write ( phydev , MII_M1111_PHY_EXT_CR , temp ) ;
if ( err < 0 )
return err ;
2007-06-05 14:46:47 +04:00
temp = phy_read ( phydev , MII_M1111_PHY_EXT_SR ) ;
if ( temp < 0 )
return temp ;
temp & = ~ ( MII_M1111_HWCFG_MODE_MASK ) ;
2008-03-19 00:37:24 +03:00
2008-07-16 17:46:20 +04:00
if ( temp & MII_M1111_HWCFG_FIBER_COPPER_RES )
2008-03-19 00:37:24 +03:00
temp | = MII_M1111_HWCFG_MODE_FIBER_RGMII ;
else
temp | = MII_M1111_HWCFG_MODE_COPPER_RGMII ;
2007-06-05 14:46:47 +04:00
err = phy_write ( phydev , MII_M1111_PHY_EXT_SR , temp ) ;
if ( err < 0 )
return err ;
}
2007-05-12 03:25:18 +04:00
if ( phydev - > interface = = PHY_INTERFACE_MODE_SGMII ) {
temp = phy_read ( phydev , MII_M1111_PHY_EXT_SR ) ;
if ( temp < 0 )
return temp ;
temp & = ~ ( MII_M1111_HWCFG_MODE_MASK ) ;
temp | = MII_M1111_HWCFG_MODE_SGMII_NO_CLK ;
2009-06-02 08:04:13 +04:00
temp | = MII_M1111_HWCFG_FIBER_COPPER_AUTO ;
2007-05-12 03:25:18 +04:00
err = phy_write ( phydev , MII_M1111_PHY_EXT_SR , temp ) ;
if ( err < 0 )
return err ;
}
2010-01-14 01:13:19 +03:00
if ( phydev - > interface = = PHY_INTERFACE_MODE_RTBI ) {
temp = phy_read ( phydev , MII_M1111_PHY_EXT_CR ) ;
if ( temp < 0 )
return temp ;
temp | = ( MII_M1111_RX_DELAY | MII_M1111_TX_DELAY ) ;
err = phy_write ( phydev , MII_M1111_PHY_EXT_CR , temp ) ;
if ( err < 0 )
return err ;
temp = phy_read ( phydev , MII_M1111_PHY_EXT_SR ) ;
if ( temp < 0 )
return temp ;
temp & = ~ ( MII_M1111_HWCFG_MODE_MASK | MII_M1111_HWCFG_FIBER_COPPER_RES ) ;
temp | = 0x7 | MII_M1111_HWCFG_FIBER_COPPER_AUTO ;
err = phy_write ( phydev , MII_M1111_PHY_EXT_SR , temp ) ;
if ( err < 0 )
return err ;
/* soft reset */
err = phy_write ( phydev , MII_BMCR , BMCR_RESET ) ;
if ( err < 0 )
return err ;
do
temp = phy_read ( phydev , MII_BMCR ) ;
while ( temp & BMCR_RESET ) ;
temp = phy_read ( phydev , MII_M1111_PHY_EXT_SR ) ;
if ( temp < 0 )
return temp ;
temp & = ~ ( MII_M1111_HWCFG_MODE_MASK | MII_M1111_HWCFG_FIBER_COPPER_RES ) ;
temp | = MII_M1111_HWCFG_MODE_COPPER_RTBI | MII_M1111_HWCFG_FIBER_COPPER_AUTO ;
err = phy_write ( phydev , MII_M1111_PHY_EXT_SR , temp ) ;
if ( err < 0 )
return err ;
}
2007-06-05 14:46:47 +04:00
err = phy_write ( phydev , MII_BMCR , BMCR_RESET ) ;
if ( err < 0 )
return err ;
return 0 ;
}
2008-11-06 12:05:26 +03:00
static int m88e1118_config_aneg ( struct phy_device * phydev )
{
int err ;
err = phy_write ( phydev , MII_BMCR , BMCR_RESET ) ;
if ( err < 0 )
return err ;
err = phy_write ( phydev , MII_M1011_PHY_SCR ,
MII_M1011_PHY_SCR_AUTO_CROSS ) ;
if ( err < 0 )
return err ;
err = genphy_config_aneg ( phydev ) ;
return 0 ;
}
static int m88e1118_config_init ( struct phy_device * phydev )
{
int err ;
/* Change address */
err = phy_write ( phydev , 0x16 , 0x0002 ) ;
if ( err < 0 )
return err ;
/* Enable 1000 Mbit */
err = phy_write ( phydev , 0x15 , 0x1070 ) ;
if ( err < 0 )
return err ;
/* Change address */
err = phy_write ( phydev , 0x16 , 0x0003 ) ;
if ( err < 0 )
return err ;
/* Adjust LED Control */
err = phy_write ( phydev , 0x10 , 0x021e ) ;
if ( err < 0 )
return err ;
/* Reset address */
err = phy_write ( phydev , 0x16 , 0x0 ) ;
if ( err < 0 )
return err ;
err = phy_write ( phydev , MII_BMCR , BMCR_RESET ) ;
if ( err < 0 )
return err ;
return 0 ;
}
2007-02-10 03:13:58 +03:00
static int m88e1145_config_init ( struct phy_device * phydev )
{
int err ;
/* Take care of errata E0 & E1 */
err = phy_write ( phydev , 0x1d , 0x001b ) ;
if ( err < 0 )
return err ;
err = phy_write ( phydev , 0x1e , 0x418f ) ;
if ( err < 0 )
return err ;
err = phy_write ( phydev , 0x1d , 0x0016 ) ;
if ( err < 0 )
return err ;
err = phy_write ( phydev , 0x1e , 0xa2da ) ;
if ( err < 0 )
return err ;
2007-06-05 14:46:47 +04:00
if ( phydev - > interface = = PHY_INTERFACE_MODE_RGMII_ID ) {
2007-02-10 03:13:58 +03:00
int temp = phy_read ( phydev , MII_M1145_PHY_EXT_CR ) ;
if ( temp < 0 )
return temp ;
temp | = ( MII_M1145_RGMII_RX_DELAY | MII_M1145_RGMII_TX_DELAY ) ;
err = phy_write ( phydev , MII_M1145_PHY_EXT_CR , temp ) ;
if ( err < 0 )
return err ;
if ( phydev - > dev_flags & M1145_DEV_FLAGS_RESISTANCE ) {
err = phy_write ( phydev , 0x1d , 0x0012 ) ;
if ( err < 0 )
return err ;
temp = phy_read ( phydev , 0x1e ) ;
if ( temp < 0 )
return temp ;
temp & = 0xf03f ;
temp | = 2 < < 9 ; /* 36 ohm */
temp | = 2 < < 6 ; /* 39 ohm */
err = phy_write ( phydev , 0x1e , temp ) ;
if ( err < 0 )
return err ;
err = phy_write ( phydev , 0x1d , 0x3 ) ;
if ( err < 0 )
return err ;
err = phy_write ( phydev , 0x1e , 0x8000 ) ;
if ( err < 0 )
return err ;
}
}
return 0 ;
}
2005-07-31 03:31:23 +04:00
2008-03-19 00:37:24 +03:00
/* marvell_read_status
*
* Generic status code does not detect Fiber correctly !
2008-03-26 06:53:24 +03:00
* Description :
2008-03-19 00:37:24 +03:00
* Check the link , then figure out the current state
* by comparing what we advertise with what the link partner
* advertises . Start by checking the gigabit possibilities ,
* then move on to 10 / 100.
*/
static int marvell_read_status ( struct phy_device * phydev )
{
int adv ;
int err ;
int lpa ;
int status = 0 ;
/* Update the link, but return if there
* was an error */
err = genphy_update_link ( phydev ) ;
if ( err )
return err ;
if ( AUTONEG_ENABLE = = phydev - > autoneg ) {
status = phy_read ( phydev , MII_M1011_PHY_STATUS ) ;
if ( status < 0 )
return status ;
lpa = phy_read ( phydev , MII_LPA ) ;
if ( lpa < 0 )
return lpa ;
adv = phy_read ( phydev , MII_ADVERTISE ) ;
if ( adv < 0 )
return adv ;
lpa & = adv ;
if ( status & MII_M1011_PHY_STATUS_FULLDUPLEX )
phydev - > duplex = DUPLEX_FULL ;
else
phydev - > duplex = DUPLEX_HALF ;
status = status & MII_M1011_PHY_STATUS_SPD_MASK ;
phydev - > pause = phydev - > asym_pause = 0 ;
switch ( status ) {
case MII_M1011_PHY_STATUS_1000 :
phydev - > speed = SPEED_1000 ;
break ;
case MII_M1011_PHY_STATUS_100 :
phydev - > speed = SPEED_100 ;
break ;
default :
phydev - > speed = SPEED_10 ;
break ;
}
if ( phydev - > duplex = = DUPLEX_FULL ) {
phydev - > pause = lpa & LPA_PAUSE_CAP ? 1 : 0 ;
phydev - > asym_pause = lpa & LPA_PAUSE_ASYM ? 1 : 0 ;
}
} else {
int bmcr = phy_read ( phydev , MII_BMCR ) ;
if ( bmcr < 0 )
return bmcr ;
if ( bmcr & BMCR_FULLDPLX )
phydev - > duplex = DUPLEX_FULL ;
else
phydev - > duplex = DUPLEX_HALF ;
if ( bmcr & BMCR_SPEED1000 )
phydev - > speed = SPEED_1000 ;
else if ( bmcr & BMCR_SPEED100 )
phydev - > speed = SPEED_100 ;
else
phydev - > speed = SPEED_10 ;
phydev - > pause = phydev - > asym_pause = 0 ;
}
return 0 ;
}
2009-04-07 06:01:43 +04:00
static int m88e1121_did_interrupt ( struct phy_device * phydev )
{
int imask ;
imask = phy_read ( phydev , MII_M1011_IEVENT ) ;
if ( imask & MII_M1011_IMASK_INIT )
return 1 ;
return 0 ;
}
2007-07-04 01:23:46 +04:00
static struct phy_driver marvell_drivers [ ] = {
{
. phy_id = 0x01410c60 ,
. phy_id_mask = 0xfffffff0 ,
. name = " Marvell 88E1101 " ,
. features = PHY_GBIT_FEATURES ,
. flags = PHY_HAS_INTERRUPT ,
. config_aneg = & marvell_config_aneg ,
. read_status = & genphy_read_status ,
. ack_interrupt = & marvell_ack_interrupt ,
. config_intr = & marvell_config_intr ,
2007-11-05 01:08:51 +03:00
. driver = { . owner = THIS_MODULE } ,
2007-07-04 01:23:46 +04:00
} ,
2007-07-04 01:24:32 +04:00
{
. phy_id = 0x01410c90 ,
. phy_id_mask = 0xfffffff0 ,
. name = " Marvell 88E1112 " ,
. features = PHY_GBIT_FEATURES ,
. flags = PHY_HAS_INTERRUPT ,
. config_init = & m88e1111_config_init ,
. config_aneg = & marvell_config_aneg ,
. read_status = & genphy_read_status ,
. ack_interrupt = & marvell_ack_interrupt ,
. config_intr = & marvell_config_intr ,
2007-11-05 01:08:51 +03:00
. driver = { . owner = THIS_MODULE } ,
2007-07-04 01:24:32 +04:00
} ,
2007-07-04 01:23:46 +04:00
{
. phy_id = 0x01410cc0 ,
. phy_id_mask = 0xfffffff0 ,
. name = " Marvell 88E1111 " ,
. features = PHY_GBIT_FEATURES ,
. flags = PHY_HAS_INTERRUPT ,
. config_init = & m88e1111_config_init ,
. config_aneg = & marvell_config_aneg ,
2008-03-19 00:37:24 +03:00
. read_status = & marvell_read_status ,
2007-07-04 01:23:46 +04:00
. ack_interrupt = & marvell_ack_interrupt ,
. config_intr = & marvell_config_intr ,
2007-11-05 01:08:51 +03:00
. driver = { . owner = THIS_MODULE } ,
2007-07-04 01:23:46 +04:00
} ,
2008-11-06 12:05:26 +03:00
{
. phy_id = 0x01410e10 ,
. phy_id_mask = 0xfffffff0 ,
. name = " Marvell 88E1118 " ,
. features = PHY_GBIT_FEATURES ,
. flags = PHY_HAS_INTERRUPT ,
. config_init = & m88e1118_config_init ,
. config_aneg = & m88e1118_config_aneg ,
. read_status = & genphy_read_status ,
. ack_interrupt = & marvell_ack_interrupt ,
. config_intr = & marvell_config_intr ,
. driver = { . owner = THIS_MODULE , } ,
} ,
2009-04-07 06:01:41 +04:00
{
. phy_id = 0x01410cb0 ,
. phy_id_mask = 0xfffffff0 ,
. name = " Marvell 88E1121R " ,
. features = PHY_GBIT_FEATURES ,
. flags = PHY_HAS_INTERRUPT ,
. config_aneg = & m88e1121_config_aneg ,
. read_status = & marvell_read_status ,
. ack_interrupt = & marvell_ack_interrupt ,
. config_intr = & marvell_config_intr ,
2009-04-07 06:01:43 +04:00
. did_interrupt = & m88e1121_did_interrupt ,
2009-04-07 06:01:41 +04:00
. driver = { . owner = THIS_MODULE } ,
} ,
2007-07-04 01:23:46 +04:00
{
. phy_id = 0x01410cd0 ,
. phy_id_mask = 0xfffffff0 ,
. name = " Marvell 88E1145 " ,
. features = PHY_GBIT_FEATURES ,
. flags = PHY_HAS_INTERRUPT ,
. config_init = & m88e1145_config_init ,
. config_aneg = & marvell_config_aneg ,
. read_status = & genphy_read_status ,
. ack_interrupt = & marvell_ack_interrupt ,
. config_intr = & marvell_config_intr ,
2007-11-05 01:08:51 +03:00
. driver = { . owner = THIS_MODULE } ,
} ,
{
. phy_id = 0x01410e30 ,
. phy_id_mask = 0xfffffff0 ,
. name = " Marvell 88E1240 " ,
. features = PHY_GBIT_FEATURES ,
. flags = PHY_HAS_INTERRUPT ,
. config_init = & m88e1111_config_init ,
. config_aneg = & marvell_config_aneg ,
. read_status = & genphy_read_status ,
. ack_interrupt = & marvell_ack_interrupt ,
. config_intr = & marvell_config_intr ,
. driver = { . owner = THIS_MODULE } ,
} ,
2005-07-31 03:31:23 +04:00
} ;
static int __init marvell_init ( void )
{
2007-02-10 03:13:58 +03:00
int ret ;
2007-07-04 01:23:46 +04:00
int i ;
2007-02-10 03:13:58 +03:00
2007-07-04 01:23:46 +04:00
for ( i = 0 ; i < ARRAY_SIZE ( marvell_drivers ) ; i + + ) {
ret = phy_driver_register ( & marvell_drivers [ i ] ) ;
2007-02-10 03:13:58 +03:00
2007-07-04 01:23:46 +04:00
if ( ret ) {
while ( i - - > 0 )
phy_driver_unregister ( & marvell_drivers [ i ] ) ;
return ret ;
}
}
2007-02-10 03:13:58 +03:00
return 0 ;
2005-07-31 03:31:23 +04:00
}
static void __exit marvell_exit ( void )
{
2007-07-04 01:23:46 +04:00
int i ;
for ( i = 0 ; i < ARRAY_SIZE ( marvell_drivers ) ; i + + )
phy_driver_unregister ( & marvell_drivers [ i ] ) ;
2005-07-31 03:31:23 +04:00
}
module_init ( marvell_init ) ;
module_exit ( marvell_exit ) ;