|  | /* | 
|  | * 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 | 
|  |  | 
|  | #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 | 
|  | #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 | 
|  |  | 
|  | #define MII_M1111_HWCFG_MODE_MASK		0xf | 
|  | #define MII_M1111_HWCFG_MODE_COPPER_RGMII	0xb | 
|  | #define MII_M1111_HWCFG_MODE_FIBER_RGMII	0x3 | 
|  | #define MII_M1111_HWCFG_MODE_SGMII_NO_CLK	0x4 | 
|  | #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 | 
|  |  | 
|  | #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 | 
|  |  | 
|  | #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 | 
|  |  | 
|  |  | 
|  | 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; | 
|  |  | 
|  | if (phydev->interrupts == PHY_INTERRUPT_ENABLED) | 
|  | 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; | 
|  |  | 
|  | 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; | 
|  |  | 
|  | err = genphy_config_aneg(phydev); | 
|  |  | 
|  | return err; | 
|  | } | 
|  |  | 
|  | 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; | 
|  | } | 
|  |  | 
|  | static int m88e1111_config_init(struct phy_device *phydev) | 
|  | { | 
|  | int err; | 
|  | int temp; | 
|  |  | 
|  | /* Enable Fiber/Copper auto selection */ | 
|  | temp = phy_read(phydev, MII_M1111_PHY_EXT_SR); | 
|  | temp &= ~MII_M1111_HWCFG_FIBER_COPPER_AUTO; | 
|  | phy_write(phydev, MII_M1111_PHY_EXT_SR, temp); | 
|  |  | 
|  | temp = phy_read(phydev, MII_BMCR); | 
|  | temp |= BMCR_RESET; | 
|  | phy_write(phydev, MII_BMCR, temp); | 
|  |  | 
|  | if ((phydev->interface == PHY_INTERFACE_MODE_RGMII) || | 
|  | (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID) || | 
|  | (phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID) || | 
|  | (phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID)) { | 
|  |  | 
|  | temp = phy_read(phydev, MII_M1111_PHY_EXT_CR); | 
|  | if (temp < 0) | 
|  | return temp; | 
|  |  | 
|  | if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID) { | 
|  | temp |= (MII_M1111_RX_DELAY | MII_M1111_TX_DELAY); | 
|  | } 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; | 
|  | } | 
|  |  | 
|  | 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); | 
|  |  | 
|  | if (temp & MII_M1111_HWCFG_FIBER_COPPER_RES) | 
|  | temp |= MII_M1111_HWCFG_MODE_FIBER_RGMII; | 
|  | else | 
|  | temp |= MII_M1111_HWCFG_MODE_COPPER_RGMII; | 
|  |  | 
|  | err = phy_write(phydev, MII_M1111_PHY_EXT_SR, temp); | 
|  | if (err < 0) | 
|  | return err; | 
|  | } | 
|  |  | 
|  | 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; | 
|  |  | 
|  | err = phy_write(phydev, MII_M1111_PHY_EXT_SR, temp); | 
|  | if (err < 0) | 
|  | return err; | 
|  | } | 
|  |  | 
|  | err = phy_write(phydev, MII_BMCR, BMCR_RESET); | 
|  | if (err < 0) | 
|  | return err; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | 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; | 
|  | } | 
|  |  | 
|  | 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; | 
|  |  | 
|  | if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID) { | 
|  | 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; | 
|  | } | 
|  |  | 
|  | /* marvell_read_status | 
|  | * | 
|  | * Generic status code does not detect Fiber correctly! | 
|  | * Description: | 
|  | *   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; | 
|  | } | 
|  |  | 
|  | 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; | 
|  | } | 
|  |  | 
|  | 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, | 
|  | .driver = { .owner = THIS_MODULE }, | 
|  | }, | 
|  | { | 
|  | .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, | 
|  | .driver = { .owner = THIS_MODULE }, | 
|  | }, | 
|  | { | 
|  | .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, | 
|  | .read_status = &marvell_read_status, | 
|  | .ack_interrupt = &marvell_ack_interrupt, | 
|  | .config_intr = &marvell_config_intr, | 
|  | .driver = { .owner = THIS_MODULE }, | 
|  | }, | 
|  | { | 
|  | .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,}, | 
|  | }, | 
|  | { | 
|  | .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, | 
|  | .did_interrupt = &m88e1121_did_interrupt, | 
|  | .driver = { .owner = THIS_MODULE }, | 
|  | }, | 
|  | { | 
|  | .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, | 
|  | .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 }, | 
|  | }, | 
|  | }; | 
|  |  | 
|  | static int __init marvell_init(void) | 
|  | { | 
|  | int ret; | 
|  | int i; | 
|  |  | 
|  | for (i = 0; i < ARRAY_SIZE(marvell_drivers); i++) { | 
|  | ret = phy_driver_register(&marvell_drivers[i]); | 
|  |  | 
|  | if (ret) { | 
|  | while (i-- > 0) | 
|  | phy_driver_unregister(&marvell_drivers[i]); | 
|  | return ret; | 
|  | } | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void __exit marvell_exit(void) | 
|  | { | 
|  | int i; | 
|  |  | 
|  | for (i = 0; i < ARRAY_SIZE(marvell_drivers); i++) | 
|  | phy_driver_unregister(&marvell_drivers[i]); | 
|  | } | 
|  |  | 
|  | module_init(marvell_init); | 
|  | module_exit(marvell_exit); |