| /* | 
 |  * 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_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 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 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 = 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); |