/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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_RGMII 0xb #define MII_M1111_HWCFG_MODE_SGMII_NO_CLK 0x4 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; if ((phydev->interface == PHY_INTERFACE_MODE_RGMII) || (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID)) { int temp; if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID) { 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); temp |= MII_M1111_HWCFG_MODE_RGMII; err = phy_write(phydev, MII_M1111_PHY_EXT_SR, temp); if (err < 0) return err; } if (phydev->interface == PHY_INTERFACE_MODE_SGMII) { int temp; 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 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; } 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 = &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);