mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-10-29 23:28:52 +01:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
219
drivers/net/phy/Kconfig
Normal file
219
drivers/net/phy/Kconfig
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
#
|
||||
# PHY Layer Configuration
|
||||
#
|
||||
|
||||
menuconfig PHYLIB
|
||||
tristate "PHY Device support and infrastructure"
|
||||
depends on NETDEVICES
|
||||
help
|
||||
Ethernet controllers are usually attached to PHY
|
||||
devices. This option provides infrastructure for
|
||||
managing PHY devices.
|
||||
|
||||
if PHYLIB
|
||||
|
||||
comment "MII PHY device drivers"
|
||||
|
||||
config AT803X_PHY
|
||||
tristate "Drivers for Atheros AT803X PHYs"
|
||||
---help---
|
||||
Currently supports the AT8030 and AT8035 model
|
||||
|
||||
config AMD_PHY
|
||||
tristate "Drivers for the AMD PHYs"
|
||||
---help---
|
||||
Currently supports the am79c874
|
||||
|
||||
config AMD_XGBE_PHY
|
||||
tristate "Driver for the AMD 10GbE (amd-xgbe) PHYs"
|
||||
depends on OF
|
||||
---help---
|
||||
Currently supports the AMD 10GbE PHY
|
||||
|
||||
config MARVELL_PHY
|
||||
tristate "Drivers for Marvell PHYs"
|
||||
---help---
|
||||
Currently has a driver for the 88E1011S
|
||||
|
||||
config DAVICOM_PHY
|
||||
tristate "Drivers for Davicom PHYs"
|
||||
---help---
|
||||
Currently supports dm9161e and dm9131
|
||||
|
||||
config QSEMI_PHY
|
||||
tristate "Drivers for Quality Semiconductor PHYs"
|
||||
---help---
|
||||
Currently supports the qs6612
|
||||
|
||||
config LXT_PHY
|
||||
tristate "Drivers for the Intel LXT PHYs"
|
||||
---help---
|
||||
Currently supports the lxt970, lxt971
|
||||
|
||||
config CICADA_PHY
|
||||
tristate "Drivers for the Cicada PHYs"
|
||||
---help---
|
||||
Currently supports the cis8204
|
||||
|
||||
config VITESSE_PHY
|
||||
tristate "Drivers for the Vitesse PHYs"
|
||||
---help---
|
||||
Currently supports the vsc8244
|
||||
|
||||
config SMSC_PHY
|
||||
tristate "Drivers for SMSC PHYs"
|
||||
---help---
|
||||
Currently supports the LAN83C185, LAN8187 and LAN8700 PHYs
|
||||
|
||||
config BROADCOM_PHY
|
||||
tristate "Drivers for Broadcom PHYs"
|
||||
---help---
|
||||
Currently supports the BCM5411, BCM5421, BCM5461, BCM5464, BCM5481
|
||||
and BCM5482 PHYs.
|
||||
|
||||
config BCM63XX_PHY
|
||||
tristate "Drivers for Broadcom 63xx SOCs internal PHY"
|
||||
depends on BCM63XX
|
||||
---help---
|
||||
Currently supports the 6348 and 6358 PHYs.
|
||||
|
||||
config BCM7XXX_PHY
|
||||
tristate "Drivers for Broadcom 7xxx SOCs internal PHYs"
|
||||
---help---
|
||||
Currently supports the BCM7366, BCM7439, BCM7445, and
|
||||
40nm and 65nm generation of BCM7xxx Set Top Box SoCs.
|
||||
|
||||
config BCM87XX_PHY
|
||||
tristate "Driver for Broadcom BCM8706 and BCM8727 PHYs"
|
||||
help
|
||||
Currently supports the BCM8706 and BCM8727 10G Ethernet PHYs.
|
||||
|
||||
config ICPLUS_PHY
|
||||
tristate "Drivers for ICPlus PHYs"
|
||||
---help---
|
||||
Currently supports the IP175C and IP1001 PHYs.
|
||||
|
||||
config REALTEK_PHY
|
||||
tristate "Drivers for Realtek PHYs"
|
||||
---help---
|
||||
Supports the Realtek 821x PHY.
|
||||
|
||||
config NATIONAL_PHY
|
||||
tristate "Drivers for National Semiconductor PHYs"
|
||||
---help---
|
||||
Currently supports the DP83865 PHY.
|
||||
|
||||
config STE10XP
|
||||
tristate "Driver for STMicroelectronics STe10Xp PHYs"
|
||||
---help---
|
||||
This is the driver for the STe100p and STe101p PHYs.
|
||||
|
||||
config LSI_ET1011C_PHY
|
||||
tristate "Driver for LSI ET1011C PHY"
|
||||
---help---
|
||||
Supports the LSI ET1011C PHY.
|
||||
|
||||
config MICREL_PHY
|
||||
tristate "Driver for Micrel PHYs"
|
||||
---help---
|
||||
Supports the KSZ9021, VSC8201, KS8001 PHYs.
|
||||
|
||||
config FIXED_PHY
|
||||
bool "Driver for MDIO Bus/PHY emulation with fixed speed/link PHYs"
|
||||
depends on PHYLIB=y
|
||||
---help---
|
||||
Adds the platform "fixed" MDIO Bus to cover the boards that use
|
||||
PHYs that are not connected to the real MDIO bus.
|
||||
|
||||
Currently tested with mpc866ads and mpc8349e-mitx.
|
||||
|
||||
config MDIO_BITBANG
|
||||
tristate "Support for bitbanged MDIO buses"
|
||||
help
|
||||
This module implements the MDIO bus protocol in software,
|
||||
for use by low level drivers that export the ability to
|
||||
drive the relevant pins.
|
||||
|
||||
If in doubt, say N.
|
||||
|
||||
config MDIO_GPIO
|
||||
tristate "Support for GPIO lib-based bitbanged MDIO buses"
|
||||
depends on MDIO_BITBANG && GPIOLIB
|
||||
---help---
|
||||
Supports GPIO lib-based MDIO busses.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called mdio-gpio.
|
||||
|
||||
config MDIO_OCTEON
|
||||
tristate "Support for MDIO buses on Octeon SOCs"
|
||||
depends on CAVIUM_OCTEON_SOC
|
||||
default y
|
||||
help
|
||||
|
||||
This module provides a driver for the Octeon MDIO busses.
|
||||
It is required by the Octeon Ethernet device drivers.
|
||||
|
||||
If in doubt, say Y.
|
||||
|
||||
config MDIO_SUN4I
|
||||
tristate "Allwinner sun4i MDIO interface support"
|
||||
depends on ARCH_SUNXI
|
||||
help
|
||||
This driver supports the MDIO interface found in the network
|
||||
interface units of the Allwinner SoC that have an EMAC (A10,
|
||||
A12, A10s, etc.)
|
||||
|
||||
config MDIO_MOXART
|
||||
tristate "MOXA ART MDIO interface support"
|
||||
depends on ARCH_MOXART
|
||||
help
|
||||
This driver supports the MDIO interface found in the network
|
||||
interface units of the MOXA ART SoC
|
||||
|
||||
config MDIO_BUS_MUX
|
||||
tristate
|
||||
depends on OF_MDIO
|
||||
help
|
||||
This module provides a driver framework for MDIO bus
|
||||
multiplexers which connect one of several child MDIO busses
|
||||
to a parent bus. Switching between child busses is done by
|
||||
device specific drivers.
|
||||
|
||||
config MDIO_BUS_MUX_GPIO
|
||||
tristate "Support for GPIO controlled MDIO bus multiplexers"
|
||||
depends on OF_GPIO && OF_MDIO
|
||||
select MDIO_BUS_MUX
|
||||
help
|
||||
This module provides a driver for MDIO bus multiplexers that
|
||||
are controlled via GPIO lines. The multiplexer connects one of
|
||||
several child MDIO busses to a parent bus. Child bus
|
||||
selection is under the control of GPIO lines.
|
||||
|
||||
config MDIO_BUS_MUX_MMIOREG
|
||||
tristate "Support for MMIO device-controlled MDIO bus multiplexers"
|
||||
depends on OF_MDIO
|
||||
select MDIO_BUS_MUX
|
||||
help
|
||||
This module provides a driver for MDIO bus multiplexers that
|
||||
are controlled via a simple memory-mapped device, like an FPGA.
|
||||
The multiplexer connects one of several child MDIO busses to a
|
||||
parent bus. Child bus selection is under the control of one of
|
||||
the FPGA's registers.
|
||||
|
||||
Currently, only 8-bit registers are supported.
|
||||
|
||||
config MDIO_BCM_UNIMAC
|
||||
tristate "Broadcom UniMAC MDIO bus controller"
|
||||
depends on HAS_IOMEM
|
||||
help
|
||||
This module provides a driver for the Broadcom UniMAC MDIO busses.
|
||||
This hardware can be found in the Broadcom GENET Ethernet MAC
|
||||
controllers as well as some Broadcom Ethernet switches such as the
|
||||
Starfighter 2 switches.
|
||||
|
||||
endif # PHYLIB
|
||||
|
||||
config MICREL_KS8995MA
|
||||
tristate "Micrel KS8995MA 5-ports 10/100 managed Ethernet switch"
|
||||
depends on SPI
|
||||
37
drivers/net/phy/Makefile
Normal file
37
drivers/net/phy/Makefile
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
# Makefile for Linux PHY drivers
|
||||
|
||||
libphy-objs := phy.o phy_device.o mdio_bus.o
|
||||
|
||||
obj-$(CONFIG_PHYLIB) += libphy.o
|
||||
obj-$(CONFIG_MARVELL_PHY) += marvell.o
|
||||
obj-$(CONFIG_DAVICOM_PHY) += davicom.o
|
||||
obj-$(CONFIG_CICADA_PHY) += cicada.o
|
||||
obj-$(CONFIG_LXT_PHY) += lxt.o
|
||||
obj-$(CONFIG_QSEMI_PHY) += qsemi.o
|
||||
obj-$(CONFIG_SMSC_PHY) += smsc.o
|
||||
obj-$(CONFIG_VITESSE_PHY) += vitesse.o
|
||||
obj-$(CONFIG_BROADCOM_PHY) += broadcom.o
|
||||
obj-$(CONFIG_BCM63XX_PHY) += bcm63xx.o
|
||||
obj-$(CONFIG_BCM7XXX_PHY) += bcm7xxx.o
|
||||
obj-$(CONFIG_BCM87XX_PHY) += bcm87xx.o
|
||||
obj-$(CONFIG_ICPLUS_PHY) += icplus.o
|
||||
obj-$(CONFIG_REALTEK_PHY) += realtek.o
|
||||
obj-$(CONFIG_LSI_ET1011C_PHY) += et1011c.o
|
||||
obj-$(CONFIG_FIXED_PHY) += fixed.o
|
||||
obj-$(CONFIG_MDIO_BITBANG) += mdio-bitbang.o
|
||||
obj-$(CONFIG_MDIO_GPIO) += mdio-gpio.o
|
||||
obj-$(CONFIG_NATIONAL_PHY) += national.o
|
||||
obj-$(CONFIG_DP83640_PHY) += dp83640.o
|
||||
obj-$(CONFIG_STE10XP) += ste10Xp.o
|
||||
obj-$(CONFIG_MICREL_PHY) += micrel.o
|
||||
obj-$(CONFIG_MDIO_OCTEON) += mdio-octeon.o
|
||||
obj-$(CONFIG_MICREL_KS8995MA) += spi_ks8995.o
|
||||
obj-$(CONFIG_AT803X_PHY) += at803x.o
|
||||
obj-$(CONFIG_AMD_PHY) += amd.o
|
||||
obj-$(CONFIG_MDIO_BUS_MUX) += mdio-mux.o
|
||||
obj-$(CONFIG_MDIO_BUS_MUX_GPIO) += mdio-mux-gpio.o
|
||||
obj-$(CONFIG_MDIO_BUS_MUX_MMIOREG) += mdio-mux-mmioreg.o
|
||||
obj-$(CONFIG_MDIO_SUN4I) += mdio-sun4i.o
|
||||
obj-$(CONFIG_MDIO_MOXART) += mdio-moxart.o
|
||||
obj-$(CONFIG_AMD_XGBE_PHY) += amd-xgbe-phy.o
|
||||
obj-$(CONFIG_MDIO_BCM_UNIMAC) += mdio-bcm-unimac.o
|
||||
1489
drivers/net/phy/amd-xgbe-phy.c
Normal file
1489
drivers/net/phy/amd-xgbe-phy.c
Normal file
File diff suppressed because it is too large
Load diff
96
drivers/net/phy/amd.c
Normal file
96
drivers/net/phy/amd.c
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* Driver for AMD am79c PHYs
|
||||
*
|
||||
* Author: Heiko Schocher <hs@denx.de>
|
||||
*
|
||||
* Copyright (c) 2011 DENX Software Engineering GmbH
|
||||
*
|
||||
* 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/errno.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mii.h>
|
||||
#include <linux/phy.h>
|
||||
|
||||
#define PHY_ID_AM79C874 0x0022561b
|
||||
|
||||
#define MII_AM79C_IR 17 /* Interrupt Status/Control Register */
|
||||
#define MII_AM79C_IR_EN_LINK 0x0400 /* IR enable Linkstate */
|
||||
#define MII_AM79C_IR_EN_ANEG 0x0100 /* IR enable Aneg Complete */
|
||||
#define MII_AM79C_IR_IMASK_INIT (MII_AM79C_IR_EN_LINK | MII_AM79C_IR_EN_ANEG)
|
||||
|
||||
MODULE_DESCRIPTION("AMD PHY driver");
|
||||
MODULE_AUTHOR("Heiko Schocher <hs@denx.de>");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
static int am79c_ack_interrupt(struct phy_device *phydev)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = phy_read(phydev, MII_BMSR);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = phy_read(phydev, MII_AM79C_IR);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int am79c_config_init(struct phy_device *phydev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int am79c_config_intr(struct phy_device *phydev)
|
||||
{
|
||||
int err;
|
||||
|
||||
if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
|
||||
err = phy_write(phydev, MII_AM79C_IR, MII_AM79C_IR_IMASK_INIT);
|
||||
else
|
||||
err = phy_write(phydev, MII_AM79C_IR, 0);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static struct phy_driver am79c_driver = {
|
||||
.phy_id = PHY_ID_AM79C874,
|
||||
.name = "AM79C874",
|
||||
.phy_id_mask = 0xfffffff0,
|
||||
.features = PHY_BASIC_FEATURES,
|
||||
.flags = PHY_HAS_INTERRUPT,
|
||||
.config_init = am79c_config_init,
|
||||
.config_aneg = genphy_config_aneg,
|
||||
.read_status = genphy_read_status,
|
||||
.ack_interrupt = am79c_ack_interrupt,
|
||||
.config_intr = am79c_config_intr,
|
||||
.driver = { .owner = THIS_MODULE,},
|
||||
};
|
||||
|
||||
static int __init am79c_init(void)
|
||||
{
|
||||
return phy_driver_register(&am79c_driver);
|
||||
}
|
||||
|
||||
static void __exit am79c_exit(void)
|
||||
{
|
||||
phy_driver_unregister(&am79c_driver);
|
||||
}
|
||||
|
||||
module_init(am79c_init);
|
||||
module_exit(am79c_exit);
|
||||
|
||||
static struct mdio_device_id __maybe_unused amd_tbl[] = {
|
||||
{ PHY_ID_AM79C874, 0xfffffff0 },
|
||||
{ }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(mdio, amd_tbl);
|
||||
376
drivers/net/phy/at803x.c
Normal file
376
drivers/net/phy/at803x.c
Normal file
|
|
@ -0,0 +1,376 @@
|
|||
/*
|
||||
* drivers/net/phy/at803x.c
|
||||
*
|
||||
* Driver for Atheros 803x PHY
|
||||
*
|
||||
* Author: Matus Ujhelyi <ujhelyi.m@gmail.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.
|
||||
*/
|
||||
|
||||
#include <linux/phy.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/netdevice.h>
|
||||
#include <linux/etherdevice.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
|
||||
#define AT803X_INTR_ENABLE 0x12
|
||||
#define AT803X_INTR_STATUS 0x13
|
||||
#define AT803X_SMART_SPEED 0x14
|
||||
#define AT803X_LED_CONTROL 0x18
|
||||
#define AT803X_WOL_ENABLE 0x01
|
||||
#define AT803X_DEVICE_ADDR 0x03
|
||||
#define AT803X_LOC_MAC_ADDR_0_15_OFFSET 0x804C
|
||||
#define AT803X_LOC_MAC_ADDR_16_31_OFFSET 0x804B
|
||||
#define AT803X_LOC_MAC_ADDR_32_47_OFFSET 0x804A
|
||||
#define AT803X_MMD_ACCESS_CONTROL 0x0D
|
||||
#define AT803X_MMD_ACCESS_CONTROL_DATA 0x0E
|
||||
#define AT803X_FUNC_DATA 0x4003
|
||||
#define AT803X_INER 0x0012
|
||||
#define AT803X_INER_INIT 0xec00
|
||||
#define AT803X_INSR 0x0013
|
||||
#define AT803X_DEBUG_ADDR 0x1D
|
||||
#define AT803X_DEBUG_DATA 0x1E
|
||||
#define AT803X_DEBUG_SYSTEM_MODE_CTRL 0x05
|
||||
#define AT803X_DEBUG_RGMII_TX_CLK_DLY BIT(8)
|
||||
|
||||
#define ATH8030_PHY_ID 0x004dd076
|
||||
#define ATH8031_PHY_ID 0x004dd074
|
||||
#define ATH8035_PHY_ID 0x004dd072
|
||||
|
||||
MODULE_DESCRIPTION("Atheros 803x PHY driver");
|
||||
MODULE_AUTHOR("Matus Ujhelyi");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
struct at803x_priv {
|
||||
bool phy_reset:1;
|
||||
struct gpio_desc *gpiod_reset;
|
||||
};
|
||||
|
||||
struct at803x_context {
|
||||
u16 bmcr;
|
||||
u16 advertise;
|
||||
u16 control1000;
|
||||
u16 int_enable;
|
||||
u16 smart_speed;
|
||||
u16 led_control;
|
||||
};
|
||||
|
||||
/* save relevant PHY registers to private copy */
|
||||
static void at803x_context_save(struct phy_device *phydev,
|
||||
struct at803x_context *context)
|
||||
{
|
||||
context->bmcr = phy_read(phydev, MII_BMCR);
|
||||
context->advertise = phy_read(phydev, MII_ADVERTISE);
|
||||
context->control1000 = phy_read(phydev, MII_CTRL1000);
|
||||
context->int_enable = phy_read(phydev, AT803X_INTR_ENABLE);
|
||||
context->smart_speed = phy_read(phydev, AT803X_SMART_SPEED);
|
||||
context->led_control = phy_read(phydev, AT803X_LED_CONTROL);
|
||||
}
|
||||
|
||||
/* restore relevant PHY registers from private copy */
|
||||
static void at803x_context_restore(struct phy_device *phydev,
|
||||
const struct at803x_context *context)
|
||||
{
|
||||
phy_write(phydev, MII_BMCR, context->bmcr);
|
||||
phy_write(phydev, MII_ADVERTISE, context->advertise);
|
||||
phy_write(phydev, MII_CTRL1000, context->control1000);
|
||||
phy_write(phydev, AT803X_INTR_ENABLE, context->int_enable);
|
||||
phy_write(phydev, AT803X_SMART_SPEED, context->smart_speed);
|
||||
phy_write(phydev, AT803X_LED_CONTROL, context->led_control);
|
||||
}
|
||||
|
||||
static int at803x_set_wol(struct phy_device *phydev,
|
||||
struct ethtool_wolinfo *wol)
|
||||
{
|
||||
struct net_device *ndev = phydev->attached_dev;
|
||||
const u8 *mac;
|
||||
int ret;
|
||||
u32 value;
|
||||
unsigned int i, offsets[] = {
|
||||
AT803X_LOC_MAC_ADDR_32_47_OFFSET,
|
||||
AT803X_LOC_MAC_ADDR_16_31_OFFSET,
|
||||
AT803X_LOC_MAC_ADDR_0_15_OFFSET,
|
||||
};
|
||||
|
||||
if (!ndev)
|
||||
return -ENODEV;
|
||||
|
||||
if (wol->wolopts & WAKE_MAGIC) {
|
||||
mac = (const u8 *) ndev->dev_addr;
|
||||
|
||||
if (!is_valid_ether_addr(mac))
|
||||
return -EFAULT;
|
||||
|
||||
for (i = 0; i < 3; i++) {
|
||||
phy_write(phydev, AT803X_MMD_ACCESS_CONTROL,
|
||||
AT803X_DEVICE_ADDR);
|
||||
phy_write(phydev, AT803X_MMD_ACCESS_CONTROL_DATA,
|
||||
offsets[i]);
|
||||
phy_write(phydev, AT803X_MMD_ACCESS_CONTROL,
|
||||
AT803X_FUNC_DATA);
|
||||
phy_write(phydev, AT803X_MMD_ACCESS_CONTROL_DATA,
|
||||
mac[(i * 2) + 1] | (mac[(i * 2)] << 8));
|
||||
}
|
||||
|
||||
value = phy_read(phydev, AT803X_INTR_ENABLE);
|
||||
value |= AT803X_WOL_ENABLE;
|
||||
ret = phy_write(phydev, AT803X_INTR_ENABLE, value);
|
||||
if (ret)
|
||||
return ret;
|
||||
value = phy_read(phydev, AT803X_INTR_STATUS);
|
||||
} else {
|
||||
value = phy_read(phydev, AT803X_INTR_ENABLE);
|
||||
value &= (~AT803X_WOL_ENABLE);
|
||||
ret = phy_write(phydev, AT803X_INTR_ENABLE, value);
|
||||
if (ret)
|
||||
return ret;
|
||||
value = phy_read(phydev, AT803X_INTR_STATUS);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void at803x_get_wol(struct phy_device *phydev,
|
||||
struct ethtool_wolinfo *wol)
|
||||
{
|
||||
u32 value;
|
||||
|
||||
wol->supported = WAKE_MAGIC;
|
||||
wol->wolopts = 0;
|
||||
|
||||
value = phy_read(phydev, AT803X_INTR_ENABLE);
|
||||
if (value & AT803X_WOL_ENABLE)
|
||||
wol->wolopts |= WAKE_MAGIC;
|
||||
}
|
||||
|
||||
static int at803x_suspend(struct phy_device *phydev)
|
||||
{
|
||||
int value;
|
||||
int wol_enabled;
|
||||
|
||||
mutex_lock(&phydev->lock);
|
||||
|
||||
value = phy_read(phydev, AT803X_INTR_ENABLE);
|
||||
wol_enabled = value & AT803X_WOL_ENABLE;
|
||||
|
||||
value = phy_read(phydev, MII_BMCR);
|
||||
|
||||
if (wol_enabled)
|
||||
value |= BMCR_ISOLATE;
|
||||
else
|
||||
value |= BMCR_PDOWN;
|
||||
|
||||
phy_write(phydev, MII_BMCR, value);
|
||||
|
||||
mutex_unlock(&phydev->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int at803x_resume(struct phy_device *phydev)
|
||||
{
|
||||
int value;
|
||||
|
||||
mutex_lock(&phydev->lock);
|
||||
|
||||
value = phy_read(phydev, MII_BMCR);
|
||||
value &= ~(BMCR_PDOWN | BMCR_ISOLATE);
|
||||
phy_write(phydev, MII_BMCR, value);
|
||||
|
||||
mutex_unlock(&phydev->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int at803x_probe(struct phy_device *phydev)
|
||||
{
|
||||
struct device *dev = &phydev->dev;
|
||||
struct at803x_priv *priv;
|
||||
|
||||
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
priv->gpiod_reset = devm_gpiod_get(dev, "reset");
|
||||
if (IS_ERR(priv->gpiod_reset))
|
||||
priv->gpiod_reset = NULL;
|
||||
else
|
||||
gpiod_direction_output(priv->gpiod_reset, 1);
|
||||
|
||||
phydev->priv = priv;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int at803x_config_init(struct phy_device *phydev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = genphy_config_init(phydev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID) {
|
||||
ret = phy_write(phydev, AT803X_DEBUG_ADDR,
|
||||
AT803X_DEBUG_SYSTEM_MODE_CTRL);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = phy_write(phydev, AT803X_DEBUG_DATA,
|
||||
AT803X_DEBUG_RGMII_TX_CLK_DLY);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int at803x_ack_interrupt(struct phy_device *phydev)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = phy_read(phydev, AT803X_INSR);
|
||||
|
||||
return (err < 0) ? err : 0;
|
||||
}
|
||||
|
||||
static int at803x_config_intr(struct phy_device *phydev)
|
||||
{
|
||||
int err;
|
||||
int value;
|
||||
|
||||
value = phy_read(phydev, AT803X_INER);
|
||||
|
||||
if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
|
||||
err = phy_write(phydev, AT803X_INER,
|
||||
value | AT803X_INER_INIT);
|
||||
else
|
||||
err = phy_write(phydev, AT803X_INER, 0);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static void at803x_link_change_notify(struct phy_device *phydev)
|
||||
{
|
||||
struct at803x_priv *priv = phydev->priv;
|
||||
|
||||
/*
|
||||
* Conduct a hardware reset for AT8030 every time a link loss is
|
||||
* signalled. This is necessary to circumvent a hardware bug that
|
||||
* occurs when the cable is unplugged while TX packets are pending
|
||||
* in the FIFO. In such cases, the FIFO enters an error mode it
|
||||
* cannot recover from by software.
|
||||
*/
|
||||
if (phydev->drv->phy_id == ATH8030_PHY_ID) {
|
||||
if (phydev->state == PHY_NOLINK) {
|
||||
if (priv->gpiod_reset && !priv->phy_reset) {
|
||||
struct at803x_context context;
|
||||
|
||||
at803x_context_save(phydev, &context);
|
||||
|
||||
gpiod_set_value(priv->gpiod_reset, 0);
|
||||
msleep(1);
|
||||
gpiod_set_value(priv->gpiod_reset, 1);
|
||||
msleep(1);
|
||||
|
||||
at803x_context_restore(phydev, &context);
|
||||
|
||||
dev_dbg(&phydev->dev, "%s(): phy was reset\n",
|
||||
__func__);
|
||||
priv->phy_reset = true;
|
||||
}
|
||||
} else {
|
||||
priv->phy_reset = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static struct phy_driver at803x_driver[] = {
|
||||
{
|
||||
/* ATHEROS 8035 */
|
||||
.phy_id = ATH8035_PHY_ID,
|
||||
.name = "Atheros 8035 ethernet",
|
||||
.phy_id_mask = 0xffffffef,
|
||||
.probe = at803x_probe,
|
||||
.config_init = at803x_config_init,
|
||||
.link_change_notify = at803x_link_change_notify,
|
||||
.set_wol = at803x_set_wol,
|
||||
.get_wol = at803x_get_wol,
|
||||
.suspend = at803x_suspend,
|
||||
.resume = at803x_resume,
|
||||
.features = PHY_GBIT_FEATURES,
|
||||
.flags = PHY_HAS_INTERRUPT,
|
||||
.config_aneg = genphy_config_aneg,
|
||||
.read_status = genphy_read_status,
|
||||
.driver = {
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
}, {
|
||||
/* ATHEROS 8030 */
|
||||
.phy_id = ATH8030_PHY_ID,
|
||||
.name = "Atheros 8030 ethernet",
|
||||
.phy_id_mask = 0xffffffef,
|
||||
.probe = at803x_probe,
|
||||
.config_init = at803x_config_init,
|
||||
.link_change_notify = at803x_link_change_notify,
|
||||
.set_wol = at803x_set_wol,
|
||||
.get_wol = at803x_get_wol,
|
||||
.suspend = at803x_suspend,
|
||||
.resume = at803x_resume,
|
||||
.features = PHY_GBIT_FEATURES,
|
||||
.flags = PHY_HAS_INTERRUPT,
|
||||
.config_aneg = genphy_config_aneg,
|
||||
.read_status = genphy_read_status,
|
||||
.driver = {
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
}, {
|
||||
/* ATHEROS 8031 */
|
||||
.phy_id = ATH8031_PHY_ID,
|
||||
.name = "Atheros 8031 ethernet",
|
||||
.phy_id_mask = 0xffffffef,
|
||||
.probe = at803x_probe,
|
||||
.config_init = at803x_config_init,
|
||||
.link_change_notify = at803x_link_change_notify,
|
||||
.set_wol = at803x_set_wol,
|
||||
.get_wol = at803x_get_wol,
|
||||
.suspend = at803x_suspend,
|
||||
.resume = at803x_resume,
|
||||
.features = PHY_GBIT_FEATURES,
|
||||
.flags = PHY_HAS_INTERRUPT,
|
||||
.config_aneg = genphy_config_aneg,
|
||||
.read_status = genphy_read_status,
|
||||
.ack_interrupt = &at803x_ack_interrupt,
|
||||
.config_intr = &at803x_config_intr,
|
||||
.driver = {
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
} };
|
||||
|
||||
static int __init atheros_init(void)
|
||||
{
|
||||
return phy_drivers_register(at803x_driver,
|
||||
ARRAY_SIZE(at803x_driver));
|
||||
}
|
||||
|
||||
static void __exit atheros_exit(void)
|
||||
{
|
||||
phy_drivers_unregister(at803x_driver, ARRAY_SIZE(at803x_driver));
|
||||
}
|
||||
|
||||
module_init(atheros_init);
|
||||
module_exit(atheros_exit);
|
||||
|
||||
static struct mdio_device_id __maybe_unused atheros_tbl[] = {
|
||||
{ ATH8030_PHY_ID, 0xffffffef },
|
||||
{ ATH8031_PHY_ID, 0xffffffef },
|
||||
{ ATH8035_PHY_ID, 0xffffffef },
|
||||
{ }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(mdio, atheros_tbl);
|
||||
124
drivers/net/phy/bcm63xx.c
Normal file
124
drivers/net/phy/bcm63xx.c
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
* Driver for Broadcom 63xx SOCs integrated PHYs
|
||||
*
|
||||
* 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/module.h>
|
||||
#include <linux/phy.h>
|
||||
|
||||
#define MII_BCM63XX_IR 0x1a /* interrupt register */
|
||||
#define MII_BCM63XX_IR_EN 0x4000 /* global interrupt enable */
|
||||
#define MII_BCM63XX_IR_DUPLEX 0x0800 /* duplex changed */
|
||||
#define MII_BCM63XX_IR_SPEED 0x0400 /* speed changed */
|
||||
#define MII_BCM63XX_IR_LINK 0x0200 /* link changed */
|
||||
#define MII_BCM63XX_IR_GMASK 0x0100 /* global interrupt mask */
|
||||
|
||||
MODULE_DESCRIPTION("Broadcom 63xx internal PHY driver");
|
||||
MODULE_AUTHOR("Maxime Bizon <mbizon@freebox.fr>");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
static int bcm63xx_config_init(struct phy_device *phydev)
|
||||
{
|
||||
int reg, err;
|
||||
|
||||
reg = phy_read(phydev, MII_BCM63XX_IR);
|
||||
if (reg < 0)
|
||||
return reg;
|
||||
|
||||
/* Mask interrupts globally. */
|
||||
reg |= MII_BCM63XX_IR_GMASK;
|
||||
err = phy_write(phydev, MII_BCM63XX_IR, reg);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
/* Unmask events we are interested in */
|
||||
reg = ~(MII_BCM63XX_IR_DUPLEX |
|
||||
MII_BCM63XX_IR_SPEED |
|
||||
MII_BCM63XX_IR_LINK) |
|
||||
MII_BCM63XX_IR_EN;
|
||||
return phy_write(phydev, MII_BCM63XX_IR, reg);
|
||||
}
|
||||
|
||||
static int bcm63xx_ack_interrupt(struct phy_device *phydev)
|
||||
{
|
||||
int reg;
|
||||
|
||||
/* Clear pending interrupts. */
|
||||
reg = phy_read(phydev, MII_BCM63XX_IR);
|
||||
if (reg < 0)
|
||||
return reg;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bcm63xx_config_intr(struct phy_device *phydev)
|
||||
{
|
||||
int reg, err;
|
||||
|
||||
reg = phy_read(phydev, MII_BCM63XX_IR);
|
||||
if (reg < 0)
|
||||
return reg;
|
||||
|
||||
if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
|
||||
reg &= ~MII_BCM63XX_IR_GMASK;
|
||||
else
|
||||
reg |= MII_BCM63XX_IR_GMASK;
|
||||
|
||||
err = phy_write(phydev, MII_BCM63XX_IR, reg);
|
||||
return err;
|
||||
}
|
||||
|
||||
static struct phy_driver bcm63xx_driver[] = {
|
||||
{
|
||||
.phy_id = 0x00406000,
|
||||
.phy_id_mask = 0xfffffc00,
|
||||
.name = "Broadcom BCM63XX (1)",
|
||||
/* ASYM_PAUSE bit is marked RO in datasheet, so don't cheat */
|
||||
.features = (PHY_BASIC_FEATURES | SUPPORTED_Pause),
|
||||
.flags = PHY_HAS_INTERRUPT | PHY_IS_INTERNAL,
|
||||
.config_init = bcm63xx_config_init,
|
||||
.config_aneg = genphy_config_aneg,
|
||||
.read_status = genphy_read_status,
|
||||
.ack_interrupt = bcm63xx_ack_interrupt,
|
||||
.config_intr = bcm63xx_config_intr,
|
||||
.driver = { .owner = THIS_MODULE },
|
||||
}, {
|
||||
/* same phy as above, with just a different OUI */
|
||||
.phy_id = 0x002bdc00,
|
||||
.phy_id_mask = 0xfffffc00,
|
||||
.name = "Broadcom BCM63XX (2)",
|
||||
.features = (PHY_BASIC_FEATURES | SUPPORTED_Pause),
|
||||
.flags = PHY_HAS_INTERRUPT | PHY_IS_INTERNAL,
|
||||
.config_init = bcm63xx_config_init,
|
||||
.config_aneg = genphy_config_aneg,
|
||||
.read_status = genphy_read_status,
|
||||
.ack_interrupt = bcm63xx_ack_interrupt,
|
||||
.config_intr = bcm63xx_config_intr,
|
||||
.driver = { .owner = THIS_MODULE },
|
||||
} };
|
||||
|
||||
static int __init bcm63xx_phy_init(void)
|
||||
{
|
||||
return phy_drivers_register(bcm63xx_driver,
|
||||
ARRAY_SIZE(bcm63xx_driver));
|
||||
}
|
||||
|
||||
static void __exit bcm63xx_phy_exit(void)
|
||||
{
|
||||
phy_drivers_unregister(bcm63xx_driver,
|
||||
ARRAY_SIZE(bcm63xx_driver));
|
||||
}
|
||||
|
||||
module_init(bcm63xx_phy_init);
|
||||
module_exit(bcm63xx_phy_exit);
|
||||
|
||||
static struct mdio_device_id __maybe_unused bcm63xx_tbl[] = {
|
||||
{ 0x00406000, 0xfffffc00 },
|
||||
{ 0x002bdc00, 0xfffffc00 },
|
||||
{ }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(mdio, bcm63xx_tbl);
|
||||
438
drivers/net/phy/bcm7xxx.c
Normal file
438
drivers/net/phy/bcm7xxx.c
Normal file
|
|
@ -0,0 +1,438 @@
|
|||
/*
|
||||
* Broadcom BCM7xxx internal transceivers support.
|
||||
*
|
||||
* Copyright (C) 2014, Broadcom Corporation
|
||||
*
|
||||
* 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/module.h>
|
||||
#include <linux/phy.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/brcmphy.h>
|
||||
#include <linux/mdio.h>
|
||||
|
||||
/* Broadcom BCM7xxx internal PHY registers */
|
||||
#define MII_BCM7XXX_CHANNEL_WIDTH 0x2000
|
||||
|
||||
/* 40nm only register definitions */
|
||||
#define MII_BCM7XXX_100TX_AUX_CTL 0x10
|
||||
#define MII_BCM7XXX_100TX_FALSE_CAR 0x13
|
||||
#define MII_BCM7XXX_100TX_DISC 0x14
|
||||
#define MII_BCM7XXX_AUX_MODE 0x1d
|
||||
#define MII_BCM7XX_64CLK_MDIO BIT(12)
|
||||
#define MII_BCM7XXX_CORE_BASE1E 0x1e
|
||||
#define MII_BCM7XXX_TEST 0x1f
|
||||
#define MII_BCM7XXX_SHD_MODE_2 BIT(2)
|
||||
|
||||
/* 28nm only register definitions */
|
||||
#define MISC_ADDR(base, channel) base, channel
|
||||
|
||||
#define DSP_TAP10 MISC_ADDR(0x0a, 0)
|
||||
#define PLL_PLLCTRL_1 MISC_ADDR(0x32, 1)
|
||||
#define PLL_PLLCTRL_2 MISC_ADDR(0x32, 2)
|
||||
#define PLL_PLLCTRL_4 MISC_ADDR(0x33, 0)
|
||||
|
||||
#define AFE_RXCONFIG_0 MISC_ADDR(0x38, 0)
|
||||
#define AFE_RXCONFIG_1 MISC_ADDR(0x38, 1)
|
||||
#define AFE_RX_LP_COUNTER MISC_ADDR(0x38, 3)
|
||||
#define AFE_TX_CONFIG MISC_ADDR(0x39, 0)
|
||||
#define AFE_HPF_TRIM_OTHERS MISC_ADDR(0x3a, 0)
|
||||
|
||||
#define CORE_EXPB0 0xb0
|
||||
|
||||
static int bcm7445_config_init(struct phy_device *phydev)
|
||||
{
|
||||
int ret;
|
||||
const struct bcm7445_regs {
|
||||
int reg;
|
||||
u16 value;
|
||||
} bcm7445_regs_cfg[] = {
|
||||
/* increases ADC latency by 24ns */
|
||||
{ MII_BCM54XX_EXP_SEL, 0x0038 },
|
||||
{ MII_BCM54XX_EXP_DATA, 0xAB95 },
|
||||
/* increases internal 1V LDO voltage by 5% */
|
||||
{ MII_BCM54XX_EXP_SEL, 0x2038 },
|
||||
{ MII_BCM54XX_EXP_DATA, 0xBB22 },
|
||||
/* reduce RX low pass filter corner frequency */
|
||||
{ MII_BCM54XX_EXP_SEL, 0x6038 },
|
||||
{ MII_BCM54XX_EXP_DATA, 0xFFC5 },
|
||||
/* reduce RX high pass filter corner frequency */
|
||||
{ MII_BCM54XX_EXP_SEL, 0x003a },
|
||||
{ MII_BCM54XX_EXP_DATA, 0x2002 },
|
||||
};
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(bcm7445_regs_cfg); i++) {
|
||||
ret = phy_write(phydev,
|
||||
bcm7445_regs_cfg[i].reg,
|
||||
bcm7445_regs_cfg[i].value);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void phy_write_exp(struct phy_device *phydev,
|
||||
u16 reg, u16 value)
|
||||
{
|
||||
phy_write(phydev, MII_BCM54XX_EXP_SEL, MII_BCM54XX_EXP_SEL_ER | reg);
|
||||
phy_write(phydev, MII_BCM54XX_EXP_DATA, value);
|
||||
}
|
||||
|
||||
static void phy_write_misc(struct phy_device *phydev,
|
||||
u16 reg, u16 chl, u16 value)
|
||||
{
|
||||
int tmp;
|
||||
|
||||
phy_write(phydev, MII_BCM54XX_AUX_CTL, MII_BCM54XX_AUXCTL_SHDWSEL_MISC);
|
||||
|
||||
tmp = phy_read(phydev, MII_BCM54XX_AUX_CTL);
|
||||
tmp |= MII_BCM54XX_AUXCTL_ACTL_SMDSP_ENA;
|
||||
phy_write(phydev, MII_BCM54XX_AUX_CTL, tmp);
|
||||
|
||||
tmp = (chl * MII_BCM7XXX_CHANNEL_WIDTH) | reg;
|
||||
phy_write(phydev, MII_BCM54XX_EXP_SEL, tmp);
|
||||
|
||||
phy_write(phydev, MII_BCM54XX_EXP_DATA, value);
|
||||
}
|
||||
|
||||
static int bcm7xxx_28nm_afe_config_init(struct phy_device *phydev)
|
||||
{
|
||||
/* Increase VCO range to prevent unlocking problem of PLL at low
|
||||
* temp
|
||||
*/
|
||||
phy_write_misc(phydev, PLL_PLLCTRL_1, 0x0048);
|
||||
|
||||
/* Change Ki to 011 */
|
||||
phy_write_misc(phydev, PLL_PLLCTRL_2, 0x021b);
|
||||
|
||||
/* Disable loading of TVCO buffer to bandgap, set bandgap trim
|
||||
* to 111
|
||||
*/
|
||||
phy_write_misc(phydev, PLL_PLLCTRL_4, 0x0e20);
|
||||
|
||||
/* Adjust bias current trim by -3 */
|
||||
phy_write_misc(phydev, DSP_TAP10, 0x690b);
|
||||
|
||||
/* Switch to CORE_BASE1E */
|
||||
phy_write(phydev, MII_BCM7XXX_CORE_BASE1E, 0xd);
|
||||
|
||||
/* Reset R_CAL/RC_CAL Engine */
|
||||
phy_write_exp(phydev, CORE_EXPB0, 0x0010);
|
||||
|
||||
/* Disable Reset R_CAL/RC_CAL Engine */
|
||||
phy_write_exp(phydev, CORE_EXPB0, 0x0000);
|
||||
|
||||
/* write AFE_RXCONFIG_0 */
|
||||
phy_write_misc(phydev, AFE_RXCONFIG_0, 0xeb19);
|
||||
|
||||
/* write AFE_RXCONFIG_1 */
|
||||
phy_write_misc(phydev, AFE_RXCONFIG_1, 0x9a3f);
|
||||
|
||||
/* write AFE_RX_LP_COUNTER */
|
||||
phy_write_misc(phydev, AFE_RX_LP_COUNTER, 0x7fc0);
|
||||
|
||||
/* write AFE_HPF_TRIM_OTHERS */
|
||||
phy_write_misc(phydev, AFE_HPF_TRIM_OTHERS, 0x000b);
|
||||
|
||||
/* write AFTE_TX_CONFIG */
|
||||
phy_write_misc(phydev, AFE_TX_CONFIG, 0x0800);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bcm7xxx_apd_enable(struct phy_device *phydev)
|
||||
{
|
||||
int val;
|
||||
|
||||
/* Enable powering down of the DLL during auto-power down */
|
||||
val = bcm54xx_shadow_read(phydev, BCM54XX_SHD_SCR3);
|
||||
if (val < 0)
|
||||
return val;
|
||||
|
||||
val |= BCM54XX_SHD_SCR3_DLLAPD_DIS;
|
||||
bcm54xx_shadow_write(phydev, BCM54XX_SHD_SCR3, val);
|
||||
|
||||
/* Enable auto-power down */
|
||||
val = bcm54xx_shadow_read(phydev, BCM54XX_SHD_APD);
|
||||
if (val < 0)
|
||||
return val;
|
||||
|
||||
val |= BCM54XX_SHD_APD_EN;
|
||||
return bcm54xx_shadow_write(phydev, BCM54XX_SHD_APD, val);
|
||||
}
|
||||
|
||||
static int bcm7xxx_eee_enable(struct phy_device *phydev)
|
||||
{
|
||||
int val;
|
||||
|
||||
val = phy_read_mmd_indirect(phydev, BRCM_CL45VEN_EEE_CONTROL,
|
||||
MDIO_MMD_AN, phydev->addr);
|
||||
if (val < 0)
|
||||
return val;
|
||||
|
||||
/* Enable general EEE feature at the PHY level */
|
||||
val |= LPI_FEATURE_EN | LPI_FEATURE_EN_DIG1000X;
|
||||
|
||||
phy_write_mmd_indirect(phydev, BRCM_CL45VEN_EEE_CONTROL,
|
||||
MDIO_MMD_AN, phydev->addr, val);
|
||||
|
||||
/* Advertise supported modes */
|
||||
val = phy_read_mmd_indirect(phydev, MDIO_AN_EEE_ADV,
|
||||
MDIO_MMD_AN, phydev->addr);
|
||||
|
||||
val |= (MDIO_AN_EEE_ADV_100TX | MDIO_AN_EEE_ADV_1000T);
|
||||
phy_write_mmd_indirect(phydev, MDIO_AN_EEE_ADV,
|
||||
MDIO_MMD_AN, phydev->addr, val);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bcm7xxx_28nm_config_init(struct phy_device *phydev)
|
||||
{
|
||||
u8 rev = PHY_BRCM_7XXX_REV(phydev->dev_flags);
|
||||
u8 patch = PHY_BRCM_7XXX_PATCH(phydev->dev_flags);
|
||||
int ret = 0;
|
||||
|
||||
dev_info(&phydev->dev, "PHY revision: 0x%02x, patch: %d\n", rev, patch);
|
||||
|
||||
switch (rev) {
|
||||
case 0xa0:
|
||||
case 0xb0:
|
||||
ret = bcm7445_config_init(phydev);
|
||||
break;
|
||||
default:
|
||||
ret = bcm7xxx_28nm_afe_config_init(phydev);
|
||||
break;
|
||||
}
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = bcm7xxx_eee_enable(phydev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return bcm7xxx_apd_enable(phydev);
|
||||
}
|
||||
|
||||
static int bcm7xxx_28nm_resume(struct phy_device *phydev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* Re-apply workarounds coming out suspend/resume */
|
||||
ret = bcm7xxx_28nm_config_init(phydev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* 28nm Gigabit PHYs come out of reset without any half-duplex
|
||||
* or "hub" compliant advertised mode, fix that. This does not
|
||||
* cause any problems with the PHY library since genphy_config_aneg()
|
||||
* gracefully handles auto-negotiated and forced modes.
|
||||
*/
|
||||
return genphy_config_aneg(phydev);
|
||||
}
|
||||
|
||||
static int phy_set_clr_bits(struct phy_device *dev, int location,
|
||||
int set_mask, int clr_mask)
|
||||
{
|
||||
int v, ret;
|
||||
|
||||
v = phy_read(dev, location);
|
||||
if (v < 0)
|
||||
return v;
|
||||
|
||||
v &= ~clr_mask;
|
||||
v |= set_mask;
|
||||
|
||||
ret = phy_write(dev, location, v);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
static int bcm7xxx_config_init(struct phy_device *phydev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* Enable 64 clock MDIO */
|
||||
phy_write(phydev, MII_BCM7XXX_AUX_MODE, MII_BCM7XX_64CLK_MDIO);
|
||||
phy_read(phydev, MII_BCM7XXX_AUX_MODE);
|
||||
|
||||
/* Workaround only required for 100Mbits/sec capable PHYs */
|
||||
if (phydev->supported & PHY_GBIT_FEATURES)
|
||||
return 0;
|
||||
|
||||
/* set shadow mode 2 */
|
||||
ret = phy_set_clr_bits(phydev, MII_BCM7XXX_TEST,
|
||||
MII_BCM7XXX_SHD_MODE_2, MII_BCM7XXX_SHD_MODE_2);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* set iddq_clkbias */
|
||||
phy_write(phydev, MII_BCM7XXX_100TX_DISC, 0x0F00);
|
||||
udelay(10);
|
||||
|
||||
/* reset iddq_clkbias */
|
||||
phy_write(phydev, MII_BCM7XXX_100TX_DISC, 0x0C00);
|
||||
|
||||
phy_write(phydev, MII_BCM7XXX_100TX_FALSE_CAR, 0x7555);
|
||||
|
||||
/* reset shadow mode 2 */
|
||||
ret = phy_set_clr_bits(phydev, MII_BCM7XXX_TEST, MII_BCM7XXX_SHD_MODE_2, 0);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Workaround for putting the PHY in IDDQ mode, required
|
||||
* for all BCM7XXX 40nm and 65nm PHYs
|
||||
*/
|
||||
static int bcm7xxx_suspend(struct phy_device *phydev)
|
||||
{
|
||||
int ret;
|
||||
const struct bcm7xxx_regs {
|
||||
int reg;
|
||||
u16 value;
|
||||
} bcm7xxx_suspend_cfg[] = {
|
||||
{ MII_BCM7XXX_TEST, 0x008b },
|
||||
{ MII_BCM7XXX_100TX_AUX_CTL, 0x01c0 },
|
||||
{ MII_BCM7XXX_100TX_DISC, 0x7000 },
|
||||
{ MII_BCM7XXX_TEST, 0x000f },
|
||||
{ MII_BCM7XXX_100TX_AUX_CTL, 0x20d0 },
|
||||
{ MII_BCM7XXX_TEST, 0x000b },
|
||||
};
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(bcm7xxx_suspend_cfg); i++) {
|
||||
ret = phy_write(phydev,
|
||||
bcm7xxx_suspend_cfg[i].reg,
|
||||
bcm7xxx_suspend_cfg[i].value);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bcm7xxx_dummy_config_init(struct phy_device *phydev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define BCM7XXX_28NM_GPHY(_oui, _name) \
|
||||
{ \
|
||||
.phy_id = (_oui), \
|
||||
.phy_id_mask = 0xfffffff0, \
|
||||
.name = _name, \
|
||||
.features = PHY_GBIT_FEATURES | \
|
||||
SUPPORTED_Pause | SUPPORTED_Asym_Pause, \
|
||||
.flags = PHY_IS_INTERNAL, \
|
||||
.config_init = bcm7xxx_28nm_afe_config_init, \
|
||||
.config_aneg = genphy_config_aneg, \
|
||||
.read_status = genphy_read_status, \
|
||||
.resume = bcm7xxx_28nm_resume, \
|
||||
.driver = { .owner = THIS_MODULE }, \
|
||||
}
|
||||
|
||||
static struct phy_driver bcm7xxx_driver[] = {
|
||||
BCM7XXX_28NM_GPHY(PHY_ID_BCM7250, "Broadcom BCM7250"),
|
||||
BCM7XXX_28NM_GPHY(PHY_ID_BCM7364, "Broadcom BCM7364"),
|
||||
BCM7XXX_28NM_GPHY(PHY_ID_BCM7366, "Broadcom BCM7366"),
|
||||
BCM7XXX_28NM_GPHY(PHY_ID_BCM7439, "Broadcom BCM7439"),
|
||||
BCM7XXX_28NM_GPHY(PHY_ID_BCM7445, "Broadcom BCM7445"),
|
||||
{
|
||||
.phy_id = PHY_ID_BCM7425,
|
||||
.phy_id_mask = 0xfffffff0,
|
||||
.name = "Broadcom BCM7425",
|
||||
.features = PHY_GBIT_FEATURES |
|
||||
SUPPORTED_Pause | SUPPORTED_Asym_Pause,
|
||||
.flags = 0,
|
||||
.config_init = bcm7xxx_config_init,
|
||||
.config_aneg = genphy_config_aneg,
|
||||
.read_status = genphy_read_status,
|
||||
.suspend = bcm7xxx_suspend,
|
||||
.resume = bcm7xxx_config_init,
|
||||
.driver = { .owner = THIS_MODULE },
|
||||
}, {
|
||||
.phy_id = PHY_ID_BCM7429,
|
||||
.phy_id_mask = 0xfffffff0,
|
||||
.name = "Broadcom BCM7429",
|
||||
.features = PHY_GBIT_FEATURES |
|
||||
SUPPORTED_Pause | SUPPORTED_Asym_Pause,
|
||||
.flags = PHY_IS_INTERNAL,
|
||||
.config_init = bcm7xxx_config_init,
|
||||
.config_aneg = genphy_config_aneg,
|
||||
.read_status = genphy_read_status,
|
||||
.suspend = bcm7xxx_suspend,
|
||||
.resume = bcm7xxx_config_init,
|
||||
.driver = { .owner = THIS_MODULE },
|
||||
}, {
|
||||
.phy_id = PHY_BCM_OUI_4,
|
||||
.phy_id_mask = 0xffff0000,
|
||||
.name = "Broadcom BCM7XXX 40nm",
|
||||
.features = PHY_GBIT_FEATURES |
|
||||
SUPPORTED_Pause | SUPPORTED_Asym_Pause,
|
||||
.flags = PHY_IS_INTERNAL,
|
||||
.config_init = bcm7xxx_config_init,
|
||||
.config_aneg = genphy_config_aneg,
|
||||
.read_status = genphy_read_status,
|
||||
.suspend = bcm7xxx_suspend,
|
||||
.resume = bcm7xxx_config_init,
|
||||
.driver = { .owner = THIS_MODULE },
|
||||
}, {
|
||||
.phy_id = PHY_BCM_OUI_5,
|
||||
.phy_id_mask = 0xffffff00,
|
||||
.name = "Broadcom BCM7XXX 65nm",
|
||||
.features = PHY_BASIC_FEATURES |
|
||||
SUPPORTED_Pause | SUPPORTED_Asym_Pause,
|
||||
.flags = PHY_IS_INTERNAL,
|
||||
.config_init = bcm7xxx_dummy_config_init,
|
||||
.config_aneg = genphy_config_aneg,
|
||||
.read_status = genphy_read_status,
|
||||
.suspend = bcm7xxx_suspend,
|
||||
.resume = bcm7xxx_config_init,
|
||||
.driver = { .owner = THIS_MODULE },
|
||||
} };
|
||||
|
||||
static struct mdio_device_id __maybe_unused bcm7xxx_tbl[] = {
|
||||
{ PHY_ID_BCM7250, 0xfffffff0, },
|
||||
{ PHY_ID_BCM7364, 0xfffffff0, },
|
||||
{ PHY_ID_BCM7366, 0xfffffff0, },
|
||||
{ PHY_ID_BCM7425, 0xfffffff0, },
|
||||
{ PHY_ID_BCM7429, 0xfffffff0, },
|
||||
{ PHY_ID_BCM7439, 0xfffffff0, },
|
||||
{ PHY_ID_BCM7445, 0xfffffff0, },
|
||||
{ PHY_BCM_OUI_4, 0xffff0000 },
|
||||
{ PHY_BCM_OUI_5, 0xffffff00 },
|
||||
{ }
|
||||
};
|
||||
|
||||
static int __init bcm7xxx_phy_init(void)
|
||||
{
|
||||
return phy_drivers_register(bcm7xxx_driver,
|
||||
ARRAY_SIZE(bcm7xxx_driver));
|
||||
}
|
||||
|
||||
static void __exit bcm7xxx_phy_exit(void)
|
||||
{
|
||||
phy_drivers_unregister(bcm7xxx_driver,
|
||||
ARRAY_SIZE(bcm7xxx_driver));
|
||||
}
|
||||
|
||||
module_init(bcm7xxx_phy_init);
|
||||
module_exit(bcm7xxx_phy_exit);
|
||||
|
||||
MODULE_DEVICE_TABLE(mdio, bcm7xxx_tbl);
|
||||
|
||||
MODULE_DESCRIPTION("Broadcom BCM7xxx internal PHY driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Broadcom Corporation");
|
||||
233
drivers/net/phy/bcm87xx.c
Normal file
233
drivers/net/phy/bcm87xx.c
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
/*
|
||||
* This file is subject to the terms and conditions of the GNU General Public
|
||||
* License. See the file "COPYING" in the main directory of this archive
|
||||
* for more details.
|
||||
*
|
||||
* Copyright (C) 2011 - 2012 Cavium, Inc.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/phy.h>
|
||||
#include <linux/of.h>
|
||||
|
||||
#define PHY_ID_BCM8706 0x0143bdc1
|
||||
#define PHY_ID_BCM8727 0x0143bff0
|
||||
|
||||
#define BCM87XX_PMD_RX_SIGNAL_DETECT (MII_ADDR_C45 | 0x1000a)
|
||||
#define BCM87XX_10GBASER_PCS_STATUS (MII_ADDR_C45 | 0x30020)
|
||||
#define BCM87XX_XGXS_LANE_STATUS (MII_ADDR_C45 | 0x40018)
|
||||
|
||||
#define BCM87XX_LASI_CONTROL (MII_ADDR_C45 | 0x39002)
|
||||
#define BCM87XX_LASI_STATUS (MII_ADDR_C45 | 0x39005)
|
||||
|
||||
#if IS_ENABLED(CONFIG_OF_MDIO)
|
||||
/* Set and/or override some configuration registers based on the
|
||||
* broadcom,c45-reg-init property stored in the of_node for the phydev.
|
||||
*
|
||||
* broadcom,c45-reg-init = <devid reg mask value>,...;
|
||||
*
|
||||
* There may be one or more sets of <devid reg mask value>:
|
||||
*
|
||||
* devid: which sub-device to use.
|
||||
* reg: the register.
|
||||
* mask: if non-zero, ANDed with existing register value.
|
||||
* value: ORed with the masked value and written to the regiser.
|
||||
*
|
||||
*/
|
||||
static int bcm87xx_of_reg_init(struct phy_device *phydev)
|
||||
{
|
||||
const __be32 *paddr;
|
||||
const __be32 *paddr_end;
|
||||
int len, ret;
|
||||
|
||||
if (!phydev->dev.of_node)
|
||||
return 0;
|
||||
|
||||
paddr = of_get_property(phydev->dev.of_node,
|
||||
"broadcom,c45-reg-init", &len);
|
||||
if (!paddr)
|
||||
return 0;
|
||||
|
||||
paddr_end = paddr + (len /= sizeof(*paddr));
|
||||
|
||||
ret = 0;
|
||||
|
||||
while (paddr + 3 < paddr_end) {
|
||||
u16 devid = be32_to_cpup(paddr++);
|
||||
u16 reg = be32_to_cpup(paddr++);
|
||||
u16 mask = be32_to_cpup(paddr++);
|
||||
u16 val_bits = be32_to_cpup(paddr++);
|
||||
int val;
|
||||
u32 regnum = MII_ADDR_C45 | (devid << 16) | reg;
|
||||
val = 0;
|
||||
if (mask) {
|
||||
val = phy_read(phydev, regnum);
|
||||
if (val < 0) {
|
||||
ret = val;
|
||||
goto err;
|
||||
}
|
||||
val &= mask;
|
||||
}
|
||||
val |= val_bits;
|
||||
|
||||
ret = phy_write(phydev, regnum, val);
|
||||
if (ret < 0)
|
||||
goto err;
|
||||
}
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
#else
|
||||
static int bcm87xx_of_reg_init(struct phy_device *phydev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif /* CONFIG_OF_MDIO */
|
||||
|
||||
static int bcm87xx_config_init(struct phy_device *phydev)
|
||||
{
|
||||
phydev->supported = SUPPORTED_10000baseR_FEC;
|
||||
phydev->advertising = ADVERTISED_10000baseR_FEC;
|
||||
phydev->state = PHY_NOLINK;
|
||||
phydev->autoneg = AUTONEG_DISABLE;
|
||||
|
||||
bcm87xx_of_reg_init(phydev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bcm87xx_config_aneg(struct phy_device *phydev)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int bcm87xx_read_status(struct phy_device *phydev)
|
||||
{
|
||||
int rx_signal_detect;
|
||||
int pcs_status;
|
||||
int xgxs_lane_status;
|
||||
|
||||
rx_signal_detect = phy_read(phydev, BCM87XX_PMD_RX_SIGNAL_DETECT);
|
||||
if (rx_signal_detect < 0)
|
||||
return rx_signal_detect;
|
||||
|
||||
if ((rx_signal_detect & 1) == 0)
|
||||
goto no_link;
|
||||
|
||||
pcs_status = phy_read(phydev, BCM87XX_10GBASER_PCS_STATUS);
|
||||
if (pcs_status < 0)
|
||||
return pcs_status;
|
||||
|
||||
if ((pcs_status & 1) == 0)
|
||||
goto no_link;
|
||||
|
||||
xgxs_lane_status = phy_read(phydev, BCM87XX_XGXS_LANE_STATUS);
|
||||
if (xgxs_lane_status < 0)
|
||||
return xgxs_lane_status;
|
||||
|
||||
if ((xgxs_lane_status & 0x1000) == 0)
|
||||
goto no_link;
|
||||
|
||||
phydev->speed = 10000;
|
||||
phydev->link = 1;
|
||||
phydev->duplex = 1;
|
||||
return 0;
|
||||
|
||||
no_link:
|
||||
phydev->link = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bcm87xx_config_intr(struct phy_device *phydev)
|
||||
{
|
||||
int reg, err;
|
||||
|
||||
reg = phy_read(phydev, BCM87XX_LASI_CONTROL);
|
||||
|
||||
if (reg < 0)
|
||||
return reg;
|
||||
|
||||
if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
|
||||
reg |= 1;
|
||||
else
|
||||
reg &= ~1;
|
||||
|
||||
err = phy_write(phydev, BCM87XX_LASI_CONTROL, reg);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int bcm87xx_did_interrupt(struct phy_device *phydev)
|
||||
{
|
||||
int reg;
|
||||
|
||||
reg = phy_read(phydev, BCM87XX_LASI_STATUS);
|
||||
|
||||
if (reg < 0) {
|
||||
dev_err(&phydev->dev,
|
||||
"Error: Read of BCM87XX_LASI_STATUS failed: %d\n", reg);
|
||||
return 0;
|
||||
}
|
||||
return (reg & 1) != 0;
|
||||
}
|
||||
|
||||
static int bcm87xx_ack_interrupt(struct phy_device *phydev)
|
||||
{
|
||||
/* Reading the LASI status clears it. */
|
||||
bcm87xx_did_interrupt(phydev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bcm8706_match_phy_device(struct phy_device *phydev)
|
||||
{
|
||||
return phydev->c45_ids.device_ids[4] == PHY_ID_BCM8706;
|
||||
}
|
||||
|
||||
static int bcm8727_match_phy_device(struct phy_device *phydev)
|
||||
{
|
||||
return phydev->c45_ids.device_ids[4] == PHY_ID_BCM8727;
|
||||
}
|
||||
|
||||
static struct phy_driver bcm87xx_driver[] = {
|
||||
{
|
||||
.phy_id = PHY_ID_BCM8706,
|
||||
.phy_id_mask = 0xffffffff,
|
||||
.name = "Broadcom BCM8706",
|
||||
.flags = PHY_HAS_INTERRUPT,
|
||||
.config_init = bcm87xx_config_init,
|
||||
.config_aneg = bcm87xx_config_aneg,
|
||||
.read_status = bcm87xx_read_status,
|
||||
.ack_interrupt = bcm87xx_ack_interrupt,
|
||||
.config_intr = bcm87xx_config_intr,
|
||||
.did_interrupt = bcm87xx_did_interrupt,
|
||||
.match_phy_device = bcm8706_match_phy_device,
|
||||
.driver = { .owner = THIS_MODULE },
|
||||
}, {
|
||||
.phy_id = PHY_ID_BCM8727,
|
||||
.phy_id_mask = 0xffffffff,
|
||||
.name = "Broadcom BCM8727",
|
||||
.flags = PHY_HAS_INTERRUPT,
|
||||
.config_init = bcm87xx_config_init,
|
||||
.config_aneg = bcm87xx_config_aneg,
|
||||
.read_status = bcm87xx_read_status,
|
||||
.ack_interrupt = bcm87xx_ack_interrupt,
|
||||
.config_intr = bcm87xx_config_intr,
|
||||
.did_interrupt = bcm87xx_did_interrupt,
|
||||
.match_phy_device = bcm8727_match_phy_device,
|
||||
.driver = { .owner = THIS_MODULE },
|
||||
} };
|
||||
|
||||
static int __init bcm87xx_init(void)
|
||||
{
|
||||
return phy_drivers_register(bcm87xx_driver,
|
||||
ARRAY_SIZE(bcm87xx_driver));
|
||||
}
|
||||
module_init(bcm87xx_init);
|
||||
|
||||
static void __exit bcm87xx_exit(void)
|
||||
{
|
||||
phy_drivers_unregister(bcm87xx_driver,
|
||||
ARRAY_SIZE(bcm87xx_driver));
|
||||
}
|
||||
module_exit(bcm87xx_exit);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
687
drivers/net/phy/broadcom.c
Normal file
687
drivers/net/phy/broadcom.c
Normal file
|
|
@ -0,0 +1,687 @@
|
|||
/*
|
||||
* drivers/net/phy/broadcom.c
|
||||
*
|
||||
* Broadcom BCM5411, BCM5421 and BCM5461 Gigabit Ethernet
|
||||
* transceivers.
|
||||
*
|
||||
* Copyright (c) 2006 Maciej W. Rozycki
|
||||
*
|
||||
* Inspired by code written by Amy Fong.
|
||||
*
|
||||
* 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/module.h>
|
||||
#include <linux/phy.h>
|
||||
#include <linux/brcmphy.h>
|
||||
|
||||
|
||||
#define BRCM_PHY_MODEL(phydev) \
|
||||
((phydev)->drv->phy_id & (phydev)->drv->phy_id_mask)
|
||||
|
||||
#define BRCM_PHY_REV(phydev) \
|
||||
((phydev)->drv->phy_id & ~((phydev)->drv->phy_id_mask))
|
||||
|
||||
MODULE_DESCRIPTION("Broadcom PHY driver");
|
||||
MODULE_AUTHOR("Maciej W. Rozycki");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
/* Indirect register access functions for the Expansion Registers */
|
||||
static int bcm54xx_exp_read(struct phy_device *phydev, u16 regnum)
|
||||
{
|
||||
int val;
|
||||
|
||||
val = phy_write(phydev, MII_BCM54XX_EXP_SEL, regnum);
|
||||
if (val < 0)
|
||||
return val;
|
||||
|
||||
val = phy_read(phydev, MII_BCM54XX_EXP_DATA);
|
||||
|
||||
/* Restore default value. It's O.K. if this write fails. */
|
||||
phy_write(phydev, MII_BCM54XX_EXP_SEL, 0);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static int bcm54xx_exp_write(struct phy_device *phydev, u16 regnum, u16 val)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = phy_write(phydev, MII_BCM54XX_EXP_SEL, regnum);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = phy_write(phydev, MII_BCM54XX_EXP_DATA, val);
|
||||
|
||||
/* Restore default value. It's O.K. if this write fails. */
|
||||
phy_write(phydev, MII_BCM54XX_EXP_SEL, 0);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int bcm54xx_auxctl_write(struct phy_device *phydev, u16 regnum, u16 val)
|
||||
{
|
||||
return phy_write(phydev, MII_BCM54XX_AUX_CTL, regnum | val);
|
||||
}
|
||||
|
||||
/* Needs SMDSP clock enabled via bcm54xx_phydsp_config() */
|
||||
static int bcm50610_a0_workaround(struct phy_device *phydev)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = bcm54xx_exp_write(phydev, MII_BCM54XX_EXP_AADJ1CH0,
|
||||
MII_BCM54XX_EXP_AADJ1CH0_SWP_ABCD_OEN |
|
||||
MII_BCM54XX_EXP_AADJ1CH0_SWSEL_THPF);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = bcm54xx_exp_write(phydev, MII_BCM54XX_EXP_AADJ1CH3,
|
||||
MII_BCM54XX_EXP_AADJ1CH3_ADCCKADJ);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = bcm54xx_exp_write(phydev, MII_BCM54XX_EXP_EXP75,
|
||||
MII_BCM54XX_EXP_EXP75_VDACCTRL);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = bcm54xx_exp_write(phydev, MII_BCM54XX_EXP_EXP96,
|
||||
MII_BCM54XX_EXP_EXP96_MYST);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = bcm54xx_exp_write(phydev, MII_BCM54XX_EXP_EXP97,
|
||||
MII_BCM54XX_EXP_EXP97_MYST);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int bcm54xx_phydsp_config(struct phy_device *phydev)
|
||||
{
|
||||
int err, err2;
|
||||
|
||||
/* Enable the SMDSP clock */
|
||||
err = bcm54xx_auxctl_write(phydev,
|
||||
MII_BCM54XX_AUXCTL_SHDWSEL_AUXCTL,
|
||||
MII_BCM54XX_AUXCTL_ACTL_SMDSP_ENA |
|
||||
MII_BCM54XX_AUXCTL_ACTL_TX_6DB);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
if (BRCM_PHY_MODEL(phydev) == PHY_ID_BCM50610 ||
|
||||
BRCM_PHY_MODEL(phydev) == PHY_ID_BCM50610M) {
|
||||
/* Clear bit 9 to fix a phy interop issue. */
|
||||
err = bcm54xx_exp_write(phydev, MII_BCM54XX_EXP_EXP08,
|
||||
MII_BCM54XX_EXP_EXP08_RJCT_2MHZ);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
if (phydev->drv->phy_id == PHY_ID_BCM50610) {
|
||||
err = bcm50610_a0_workaround(phydev);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
if (BRCM_PHY_MODEL(phydev) == PHY_ID_BCM57780) {
|
||||
int val;
|
||||
|
||||
val = bcm54xx_exp_read(phydev, MII_BCM54XX_EXP_EXP75);
|
||||
if (val < 0)
|
||||
goto error;
|
||||
|
||||
val |= MII_BCM54XX_EXP_EXP75_CM_OSC;
|
||||
err = bcm54xx_exp_write(phydev, MII_BCM54XX_EXP_EXP75, val);
|
||||
}
|
||||
|
||||
error:
|
||||
/* Disable the SMDSP clock */
|
||||
err2 = bcm54xx_auxctl_write(phydev,
|
||||
MII_BCM54XX_AUXCTL_SHDWSEL_AUXCTL,
|
||||
MII_BCM54XX_AUXCTL_ACTL_TX_6DB);
|
||||
|
||||
/* Return the first error reported. */
|
||||
return err ? err : err2;
|
||||
}
|
||||
|
||||
static void bcm54xx_adjust_rxrefclk(struct phy_device *phydev)
|
||||
{
|
||||
u32 orig;
|
||||
int val;
|
||||
bool clk125en = true;
|
||||
|
||||
/* Abort if we are using an untested phy. */
|
||||
if (BRCM_PHY_MODEL(phydev) != PHY_ID_BCM57780 &&
|
||||
BRCM_PHY_MODEL(phydev) != PHY_ID_BCM50610 &&
|
||||
BRCM_PHY_MODEL(phydev) != PHY_ID_BCM50610M)
|
||||
return;
|
||||
|
||||
val = bcm54xx_shadow_read(phydev, BCM54XX_SHD_SCR3);
|
||||
if (val < 0)
|
||||
return;
|
||||
|
||||
orig = val;
|
||||
|
||||
if ((BRCM_PHY_MODEL(phydev) == PHY_ID_BCM50610 ||
|
||||
BRCM_PHY_MODEL(phydev) == PHY_ID_BCM50610M) &&
|
||||
BRCM_PHY_REV(phydev) >= 0x3) {
|
||||
/*
|
||||
* Here, bit 0 _disables_ CLK125 when set.
|
||||
* This bit is set by default.
|
||||
*/
|
||||
clk125en = false;
|
||||
} else {
|
||||
if (phydev->dev_flags & PHY_BRCM_RX_REFCLK_UNUSED) {
|
||||
/* Here, bit 0 _enables_ CLK125 when set */
|
||||
val &= ~BCM54XX_SHD_SCR3_DEF_CLK125;
|
||||
clk125en = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!clk125en || (phydev->dev_flags & PHY_BRCM_AUTO_PWRDWN_ENABLE))
|
||||
val &= ~BCM54XX_SHD_SCR3_DLLAPD_DIS;
|
||||
else
|
||||
val |= BCM54XX_SHD_SCR3_DLLAPD_DIS;
|
||||
|
||||
if (phydev->dev_flags & PHY_BRCM_DIS_TXCRXC_NOENRGY)
|
||||
val |= BCM54XX_SHD_SCR3_TRDDAPD;
|
||||
|
||||
if (orig != val)
|
||||
bcm54xx_shadow_write(phydev, BCM54XX_SHD_SCR3, val);
|
||||
|
||||
val = bcm54xx_shadow_read(phydev, BCM54XX_SHD_APD);
|
||||
if (val < 0)
|
||||
return;
|
||||
|
||||
orig = val;
|
||||
|
||||
if (!clk125en || (phydev->dev_flags & PHY_BRCM_AUTO_PWRDWN_ENABLE))
|
||||
val |= BCM54XX_SHD_APD_EN;
|
||||
else
|
||||
val &= ~BCM54XX_SHD_APD_EN;
|
||||
|
||||
if (orig != val)
|
||||
bcm54xx_shadow_write(phydev, BCM54XX_SHD_APD, val);
|
||||
}
|
||||
|
||||
static int bcm54xx_config_init(struct phy_device *phydev)
|
||||
{
|
||||
int reg, err;
|
||||
|
||||
reg = phy_read(phydev, MII_BCM54XX_ECR);
|
||||
if (reg < 0)
|
||||
return reg;
|
||||
|
||||
/* Mask interrupts globally. */
|
||||
reg |= MII_BCM54XX_ECR_IM;
|
||||
err = phy_write(phydev, MII_BCM54XX_ECR, reg);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
/* Unmask events we are interested in. */
|
||||
reg = ~(MII_BCM54XX_INT_DUPLEX |
|
||||
MII_BCM54XX_INT_SPEED |
|
||||
MII_BCM54XX_INT_LINK);
|
||||
err = phy_write(phydev, MII_BCM54XX_IMR, reg);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
if ((BRCM_PHY_MODEL(phydev) == PHY_ID_BCM50610 ||
|
||||
BRCM_PHY_MODEL(phydev) == PHY_ID_BCM50610M) &&
|
||||
(phydev->dev_flags & PHY_BRCM_CLEAR_RGMII_MODE))
|
||||
bcm54xx_shadow_write(phydev, BCM54XX_SHD_RGMII_MODE, 0);
|
||||
|
||||
if ((phydev->dev_flags & PHY_BRCM_RX_REFCLK_UNUSED) ||
|
||||
(phydev->dev_flags & PHY_BRCM_DIS_TXCRXC_NOENRGY) ||
|
||||
(phydev->dev_flags & PHY_BRCM_AUTO_PWRDWN_ENABLE))
|
||||
bcm54xx_adjust_rxrefclk(phydev);
|
||||
|
||||
bcm54xx_phydsp_config(phydev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bcm5482_config_init(struct phy_device *phydev)
|
||||
{
|
||||
int err, reg;
|
||||
|
||||
err = bcm54xx_config_init(phydev);
|
||||
|
||||
if (phydev->dev_flags & PHY_BCM_FLAGS_MODE_1000BX) {
|
||||
/*
|
||||
* Enable secondary SerDes and its use as an LED source
|
||||
*/
|
||||
reg = bcm54xx_shadow_read(phydev, BCM5482_SHD_SSD);
|
||||
bcm54xx_shadow_write(phydev, BCM5482_SHD_SSD,
|
||||
reg |
|
||||
BCM5482_SHD_SSD_LEDM |
|
||||
BCM5482_SHD_SSD_EN);
|
||||
|
||||
/*
|
||||
* Enable SGMII slave mode and auto-detection
|
||||
*/
|
||||
reg = BCM5482_SSD_SGMII_SLAVE | MII_BCM54XX_EXP_SEL_SSD;
|
||||
err = bcm54xx_exp_read(phydev, reg);
|
||||
if (err < 0)
|
||||
return err;
|
||||
err = bcm54xx_exp_write(phydev, reg, err |
|
||||
BCM5482_SSD_SGMII_SLAVE_EN |
|
||||
BCM5482_SSD_SGMII_SLAVE_AD);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
/*
|
||||
* Disable secondary SerDes powerdown
|
||||
*/
|
||||
reg = BCM5482_SSD_1000BX_CTL | MII_BCM54XX_EXP_SEL_SSD;
|
||||
err = bcm54xx_exp_read(phydev, reg);
|
||||
if (err < 0)
|
||||
return err;
|
||||
err = bcm54xx_exp_write(phydev, reg,
|
||||
err & ~BCM5482_SSD_1000BX_CTL_PWRDOWN);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
/*
|
||||
* Select 1000BASE-X register set (primary SerDes)
|
||||
*/
|
||||
reg = bcm54xx_shadow_read(phydev, BCM5482_SHD_MODE);
|
||||
bcm54xx_shadow_write(phydev, BCM5482_SHD_MODE,
|
||||
reg | BCM5482_SHD_MODE_1000BX);
|
||||
|
||||
/*
|
||||
* LED1=ACTIVITYLED, LED3=LINKSPD[2]
|
||||
* (Use LED1 as secondary SerDes ACTIVITY LED)
|
||||
*/
|
||||
bcm54xx_shadow_write(phydev, BCM5482_SHD_LEDS1,
|
||||
BCM5482_SHD_LEDS1_LED1(BCM_LED_SRC_ACTIVITYLED) |
|
||||
BCM5482_SHD_LEDS1_LED3(BCM_LED_SRC_LINKSPD2));
|
||||
|
||||
/*
|
||||
* Auto-negotiation doesn't seem to work quite right
|
||||
* in this mode, so we disable it and force it to the
|
||||
* right speed/duplex setting. Only 'link status'
|
||||
* is important.
|
||||
*/
|
||||
phydev->autoneg = AUTONEG_DISABLE;
|
||||
phydev->speed = SPEED_1000;
|
||||
phydev->duplex = DUPLEX_FULL;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int bcm5482_read_status(struct phy_device *phydev)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = genphy_read_status(phydev);
|
||||
|
||||
if (phydev->dev_flags & PHY_BCM_FLAGS_MODE_1000BX) {
|
||||
/*
|
||||
* Only link status matters for 1000Base-X mode, so force
|
||||
* 1000 Mbit/s full-duplex status
|
||||
*/
|
||||
if (phydev->link) {
|
||||
phydev->speed = SPEED_1000;
|
||||
phydev->duplex = DUPLEX_FULL;
|
||||
}
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int bcm54xx_ack_interrupt(struct phy_device *phydev)
|
||||
{
|
||||
int reg;
|
||||
|
||||
/* Clear pending interrupts. */
|
||||
reg = phy_read(phydev, MII_BCM54XX_ISR);
|
||||
if (reg < 0)
|
||||
return reg;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bcm54xx_config_intr(struct phy_device *phydev)
|
||||
{
|
||||
int reg, err;
|
||||
|
||||
reg = phy_read(phydev, MII_BCM54XX_ECR);
|
||||
if (reg < 0)
|
||||
return reg;
|
||||
|
||||
if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
|
||||
reg &= ~MII_BCM54XX_ECR_IM;
|
||||
else
|
||||
reg |= MII_BCM54XX_ECR_IM;
|
||||
|
||||
err = phy_write(phydev, MII_BCM54XX_ECR, reg);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int bcm5481_config_aneg(struct phy_device *phydev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* Aneg firsly. */
|
||||
ret = genphy_config_aneg(phydev);
|
||||
|
||||
/* Then we can set up the delay. */
|
||||
if (phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID) {
|
||||
u16 reg;
|
||||
|
||||
/*
|
||||
* There is no BCM5481 specification available, so down
|
||||
* here is everything we know about "register 0x18". This
|
||||
* at least helps BCM5481 to successfully receive packets
|
||||
* on MPC8360E-RDK board. Peter Barada <peterb@logicpd.com>
|
||||
* says: "This sets delay between the RXD and RXC signals
|
||||
* instead of using trace lengths to achieve timing".
|
||||
*/
|
||||
|
||||
/* Set RDX clk delay. */
|
||||
reg = 0x7 | (0x7 << 12);
|
||||
phy_write(phydev, 0x18, reg);
|
||||
|
||||
reg = phy_read(phydev, 0x18);
|
||||
/* Set RDX-RXC skew. */
|
||||
reg |= (1 << 8);
|
||||
/* Write bits 14:0. */
|
||||
reg |= (1 << 15);
|
||||
phy_write(phydev, 0x18, reg);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int brcm_phy_setbits(struct phy_device *phydev, int reg, int set)
|
||||
{
|
||||
int val;
|
||||
|
||||
val = phy_read(phydev, reg);
|
||||
if (val < 0)
|
||||
return val;
|
||||
|
||||
return phy_write(phydev, reg, val | set);
|
||||
}
|
||||
|
||||
static int brcm_fet_config_init(struct phy_device *phydev)
|
||||
{
|
||||
int reg, err, err2, brcmtest;
|
||||
|
||||
/* Reset the PHY to bring it to a known state. */
|
||||
err = phy_write(phydev, MII_BMCR, BMCR_RESET);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
reg = phy_read(phydev, MII_BRCM_FET_INTREG);
|
||||
if (reg < 0)
|
||||
return reg;
|
||||
|
||||
/* Unmask events we are interested in and mask interrupts globally. */
|
||||
reg = MII_BRCM_FET_IR_DUPLEX_EN |
|
||||
MII_BRCM_FET_IR_SPEED_EN |
|
||||
MII_BRCM_FET_IR_LINK_EN |
|
||||
MII_BRCM_FET_IR_ENABLE |
|
||||
MII_BRCM_FET_IR_MASK;
|
||||
|
||||
err = phy_write(phydev, MII_BRCM_FET_INTREG, reg);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
/* Enable shadow register access */
|
||||
brcmtest = phy_read(phydev, MII_BRCM_FET_BRCMTEST);
|
||||
if (brcmtest < 0)
|
||||
return brcmtest;
|
||||
|
||||
reg = brcmtest | MII_BRCM_FET_BT_SRE;
|
||||
|
||||
err = phy_write(phydev, MII_BRCM_FET_BRCMTEST, reg);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
/* Set the LED mode */
|
||||
reg = phy_read(phydev, MII_BRCM_FET_SHDW_AUXMODE4);
|
||||
if (reg < 0) {
|
||||
err = reg;
|
||||
goto done;
|
||||
}
|
||||
|
||||
reg &= ~MII_BRCM_FET_SHDW_AM4_LED_MASK;
|
||||
reg |= MII_BRCM_FET_SHDW_AM4_LED_MODE1;
|
||||
|
||||
err = phy_write(phydev, MII_BRCM_FET_SHDW_AUXMODE4, reg);
|
||||
if (err < 0)
|
||||
goto done;
|
||||
|
||||
/* Enable auto MDIX */
|
||||
err = brcm_phy_setbits(phydev, MII_BRCM_FET_SHDW_MISCCTRL,
|
||||
MII_BRCM_FET_SHDW_MC_FAME);
|
||||
if (err < 0)
|
||||
goto done;
|
||||
|
||||
if (phydev->dev_flags & PHY_BRCM_AUTO_PWRDWN_ENABLE) {
|
||||
/* Enable auto power down */
|
||||
err = brcm_phy_setbits(phydev, MII_BRCM_FET_SHDW_AUXSTAT2,
|
||||
MII_BRCM_FET_SHDW_AS2_APDE);
|
||||
}
|
||||
|
||||
done:
|
||||
/* Disable shadow register access */
|
||||
err2 = phy_write(phydev, MII_BRCM_FET_BRCMTEST, brcmtest);
|
||||
if (!err)
|
||||
err = err2;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int brcm_fet_ack_interrupt(struct phy_device *phydev)
|
||||
{
|
||||
int reg;
|
||||
|
||||
/* Clear pending interrupts. */
|
||||
reg = phy_read(phydev, MII_BRCM_FET_INTREG);
|
||||
if (reg < 0)
|
||||
return reg;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int brcm_fet_config_intr(struct phy_device *phydev)
|
||||
{
|
||||
int reg, err;
|
||||
|
||||
reg = phy_read(phydev, MII_BRCM_FET_INTREG);
|
||||
if (reg < 0)
|
||||
return reg;
|
||||
|
||||
if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
|
||||
reg &= ~MII_BRCM_FET_IR_MASK;
|
||||
else
|
||||
reg |= MII_BRCM_FET_IR_MASK;
|
||||
|
||||
err = phy_write(phydev, MII_BRCM_FET_INTREG, reg);
|
||||
return err;
|
||||
}
|
||||
|
||||
static struct phy_driver broadcom_drivers[] = {
|
||||
{
|
||||
.phy_id = PHY_ID_BCM5411,
|
||||
.phy_id_mask = 0xfffffff0,
|
||||
.name = "Broadcom BCM5411",
|
||||
.features = PHY_GBIT_FEATURES |
|
||||
SUPPORTED_Pause | SUPPORTED_Asym_Pause,
|
||||
.flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT,
|
||||
.config_init = bcm54xx_config_init,
|
||||
.config_aneg = genphy_config_aneg,
|
||||
.read_status = genphy_read_status,
|
||||
.ack_interrupt = bcm54xx_ack_interrupt,
|
||||
.config_intr = bcm54xx_config_intr,
|
||||
.driver = { .owner = THIS_MODULE },
|
||||
}, {
|
||||
.phy_id = PHY_ID_BCM5421,
|
||||
.phy_id_mask = 0xfffffff0,
|
||||
.name = "Broadcom BCM5421",
|
||||
.features = PHY_GBIT_FEATURES |
|
||||
SUPPORTED_Pause | SUPPORTED_Asym_Pause,
|
||||
.flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT,
|
||||
.config_init = bcm54xx_config_init,
|
||||
.config_aneg = genphy_config_aneg,
|
||||
.read_status = genphy_read_status,
|
||||
.ack_interrupt = bcm54xx_ack_interrupt,
|
||||
.config_intr = bcm54xx_config_intr,
|
||||
.driver = { .owner = THIS_MODULE },
|
||||
}, {
|
||||
.phy_id = PHY_ID_BCM5461,
|
||||
.phy_id_mask = 0xfffffff0,
|
||||
.name = "Broadcom BCM5461",
|
||||
.features = PHY_GBIT_FEATURES |
|
||||
SUPPORTED_Pause | SUPPORTED_Asym_Pause,
|
||||
.flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT,
|
||||
.config_init = bcm54xx_config_init,
|
||||
.config_aneg = genphy_config_aneg,
|
||||
.read_status = genphy_read_status,
|
||||
.ack_interrupt = bcm54xx_ack_interrupt,
|
||||
.config_intr = bcm54xx_config_intr,
|
||||
.driver = { .owner = THIS_MODULE },
|
||||
}, {
|
||||
.phy_id = PHY_ID_BCM5464,
|
||||
.phy_id_mask = 0xfffffff0,
|
||||
.name = "Broadcom BCM5464",
|
||||
.features = PHY_GBIT_FEATURES |
|
||||
SUPPORTED_Pause | SUPPORTED_Asym_Pause,
|
||||
.flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT,
|
||||
.config_init = bcm54xx_config_init,
|
||||
.config_aneg = genphy_config_aneg,
|
||||
.read_status = genphy_read_status,
|
||||
.ack_interrupt = bcm54xx_ack_interrupt,
|
||||
.config_intr = bcm54xx_config_intr,
|
||||
.driver = { .owner = THIS_MODULE },
|
||||
}, {
|
||||
.phy_id = PHY_ID_BCM5481,
|
||||
.phy_id_mask = 0xfffffff0,
|
||||
.name = "Broadcom BCM5481",
|
||||
.features = PHY_GBIT_FEATURES |
|
||||
SUPPORTED_Pause | SUPPORTED_Asym_Pause,
|
||||
.flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT,
|
||||
.config_init = bcm54xx_config_init,
|
||||
.config_aneg = bcm5481_config_aneg,
|
||||
.read_status = genphy_read_status,
|
||||
.ack_interrupt = bcm54xx_ack_interrupt,
|
||||
.config_intr = bcm54xx_config_intr,
|
||||
.driver = { .owner = THIS_MODULE },
|
||||
}, {
|
||||
.phy_id = PHY_ID_BCM5482,
|
||||
.phy_id_mask = 0xfffffff0,
|
||||
.name = "Broadcom BCM5482",
|
||||
.features = PHY_GBIT_FEATURES |
|
||||
SUPPORTED_Pause | SUPPORTED_Asym_Pause,
|
||||
.flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT,
|
||||
.config_init = bcm5482_config_init,
|
||||
.config_aneg = genphy_config_aneg,
|
||||
.read_status = bcm5482_read_status,
|
||||
.ack_interrupt = bcm54xx_ack_interrupt,
|
||||
.config_intr = bcm54xx_config_intr,
|
||||
.driver = { .owner = THIS_MODULE },
|
||||
}, {
|
||||
.phy_id = PHY_ID_BCM50610,
|
||||
.phy_id_mask = 0xfffffff0,
|
||||
.name = "Broadcom BCM50610",
|
||||
.features = PHY_GBIT_FEATURES |
|
||||
SUPPORTED_Pause | SUPPORTED_Asym_Pause,
|
||||
.flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT,
|
||||
.config_init = bcm54xx_config_init,
|
||||
.config_aneg = genphy_config_aneg,
|
||||
.read_status = genphy_read_status,
|
||||
.ack_interrupt = bcm54xx_ack_interrupt,
|
||||
.config_intr = bcm54xx_config_intr,
|
||||
.driver = { .owner = THIS_MODULE },
|
||||
}, {
|
||||
.phy_id = PHY_ID_BCM50610M,
|
||||
.phy_id_mask = 0xfffffff0,
|
||||
.name = "Broadcom BCM50610M",
|
||||
.features = PHY_GBIT_FEATURES |
|
||||
SUPPORTED_Pause | SUPPORTED_Asym_Pause,
|
||||
.flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT,
|
||||
.config_init = bcm54xx_config_init,
|
||||
.config_aneg = genphy_config_aneg,
|
||||
.read_status = genphy_read_status,
|
||||
.ack_interrupt = bcm54xx_ack_interrupt,
|
||||
.config_intr = bcm54xx_config_intr,
|
||||
.driver = { .owner = THIS_MODULE },
|
||||
}, {
|
||||
.phy_id = PHY_ID_BCM57780,
|
||||
.phy_id_mask = 0xfffffff0,
|
||||
.name = "Broadcom BCM57780",
|
||||
.features = PHY_GBIT_FEATURES |
|
||||
SUPPORTED_Pause | SUPPORTED_Asym_Pause,
|
||||
.flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT,
|
||||
.config_init = bcm54xx_config_init,
|
||||
.config_aneg = genphy_config_aneg,
|
||||
.read_status = genphy_read_status,
|
||||
.ack_interrupt = bcm54xx_ack_interrupt,
|
||||
.config_intr = bcm54xx_config_intr,
|
||||
.driver = { .owner = THIS_MODULE },
|
||||
}, {
|
||||
.phy_id = PHY_ID_BCMAC131,
|
||||
.phy_id_mask = 0xfffffff0,
|
||||
.name = "Broadcom BCMAC131",
|
||||
.features = PHY_BASIC_FEATURES |
|
||||
SUPPORTED_Pause | SUPPORTED_Asym_Pause,
|
||||
.flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT,
|
||||
.config_init = brcm_fet_config_init,
|
||||
.config_aneg = genphy_config_aneg,
|
||||
.read_status = genphy_read_status,
|
||||
.ack_interrupt = brcm_fet_ack_interrupt,
|
||||
.config_intr = brcm_fet_config_intr,
|
||||
.driver = { .owner = THIS_MODULE },
|
||||
}, {
|
||||
.phy_id = PHY_ID_BCM5241,
|
||||
.phy_id_mask = 0xfffffff0,
|
||||
.name = "Broadcom BCM5241",
|
||||
.features = PHY_BASIC_FEATURES |
|
||||
SUPPORTED_Pause | SUPPORTED_Asym_Pause,
|
||||
.flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT,
|
||||
.config_init = brcm_fet_config_init,
|
||||
.config_aneg = genphy_config_aneg,
|
||||
.read_status = genphy_read_status,
|
||||
.ack_interrupt = brcm_fet_ack_interrupt,
|
||||
.config_intr = brcm_fet_config_intr,
|
||||
.driver = { .owner = THIS_MODULE },
|
||||
} };
|
||||
|
||||
static int __init broadcom_init(void)
|
||||
{
|
||||
return phy_drivers_register(broadcom_drivers,
|
||||
ARRAY_SIZE(broadcom_drivers));
|
||||
}
|
||||
|
||||
static void __exit broadcom_exit(void)
|
||||
{
|
||||
phy_drivers_unregister(broadcom_drivers,
|
||||
ARRAY_SIZE(broadcom_drivers));
|
||||
}
|
||||
|
||||
module_init(broadcom_init);
|
||||
module_exit(broadcom_exit);
|
||||
|
||||
static struct mdio_device_id __maybe_unused broadcom_tbl[] = {
|
||||
{ PHY_ID_BCM5411, 0xfffffff0 },
|
||||
{ PHY_ID_BCM5421, 0xfffffff0 },
|
||||
{ PHY_ID_BCM5461, 0xfffffff0 },
|
||||
{ PHY_ID_BCM5464, 0xfffffff0 },
|
||||
{ PHY_ID_BCM5482, 0xfffffff0 },
|
||||
{ PHY_ID_BCM5482, 0xfffffff0 },
|
||||
{ PHY_ID_BCM50610, 0xfffffff0 },
|
||||
{ PHY_ID_BCM50610M, 0xfffffff0 },
|
||||
{ PHY_ID_BCM57780, 0xfffffff0 },
|
||||
{ PHY_ID_BCMAC131, 0xfffffff0 },
|
||||
{ PHY_ID_BCM5241, 0xfffffff0 },
|
||||
{ }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(mdio, broadcom_tbl);
|
||||
153
drivers/net/phy/cicada.c
Normal file
153
drivers/net/phy/cicada.c
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
* drivers/net/phy/cicada.c
|
||||
*
|
||||
* Driver for Cicada 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/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 <linux/io.h>
|
||||
#include <asm/irq.h>
|
||||
#include <linux/uaccess.h>
|
||||
|
||||
/* Cicada Extended Control Register 1 */
|
||||
#define MII_CIS8201_EXT_CON1 0x17
|
||||
#define MII_CIS8201_EXTCON1_INIT 0x0000
|
||||
|
||||
/* Cicada Interrupt Mask Register */
|
||||
#define MII_CIS8201_IMASK 0x19
|
||||
#define MII_CIS8201_IMASK_IEN 0x8000
|
||||
#define MII_CIS8201_IMASK_SPEED 0x4000
|
||||
#define MII_CIS8201_IMASK_LINK 0x2000
|
||||
#define MII_CIS8201_IMASK_DUPLEX 0x1000
|
||||
#define MII_CIS8201_IMASK_MASK 0xf000
|
||||
|
||||
/* Cicada Interrupt Status Register */
|
||||
#define MII_CIS8201_ISTAT 0x1a
|
||||
#define MII_CIS8201_ISTAT_STATUS 0x8000
|
||||
#define MII_CIS8201_ISTAT_SPEED 0x4000
|
||||
#define MII_CIS8201_ISTAT_LINK 0x2000
|
||||
#define MII_CIS8201_ISTAT_DUPLEX 0x1000
|
||||
|
||||
/* Cicada Auxiliary Control/Status Register */
|
||||
#define MII_CIS8201_AUX_CONSTAT 0x1c
|
||||
#define MII_CIS8201_AUXCONSTAT_INIT 0x0004
|
||||
#define MII_CIS8201_AUXCONSTAT_DUPLEX 0x0020
|
||||
#define MII_CIS8201_AUXCONSTAT_SPEED 0x0018
|
||||
#define MII_CIS8201_AUXCONSTAT_GBIT 0x0010
|
||||
#define MII_CIS8201_AUXCONSTAT_100 0x0008
|
||||
|
||||
MODULE_DESCRIPTION("Cicadia PHY driver");
|
||||
MODULE_AUTHOR("Andy Fleming");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
static int cis820x_config_init(struct phy_device *phydev)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = phy_write(phydev, MII_CIS8201_AUX_CONSTAT,
|
||||
MII_CIS8201_AUXCONSTAT_INIT);
|
||||
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = phy_write(phydev, MII_CIS8201_EXT_CON1,
|
||||
MII_CIS8201_EXTCON1_INIT);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int cis820x_ack_interrupt(struct phy_device *phydev)
|
||||
{
|
||||
int err = phy_read(phydev, MII_CIS8201_ISTAT);
|
||||
|
||||
return (err < 0) ? err : 0;
|
||||
}
|
||||
|
||||
static int cis820x_config_intr(struct phy_device *phydev)
|
||||
{
|
||||
int err;
|
||||
|
||||
if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
|
||||
err = phy_write(phydev, MII_CIS8201_IMASK,
|
||||
MII_CIS8201_IMASK_MASK);
|
||||
else
|
||||
err = phy_write(phydev, MII_CIS8201_IMASK, 0);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Cicada 8201, a.k.a Vitesse VSC8201 */
|
||||
static struct phy_driver cis820x_driver[] = {
|
||||
{
|
||||
.phy_id = 0x000fc410,
|
||||
.name = "Cicada Cis8201",
|
||||
.phy_id_mask = 0x000ffff0,
|
||||
.features = PHY_GBIT_FEATURES,
|
||||
.flags = PHY_HAS_INTERRUPT,
|
||||
.config_init = &cis820x_config_init,
|
||||
.config_aneg = &genphy_config_aneg,
|
||||
.read_status = &genphy_read_status,
|
||||
.ack_interrupt = &cis820x_ack_interrupt,
|
||||
.config_intr = &cis820x_config_intr,
|
||||
.driver = { .owner = THIS_MODULE,},
|
||||
}, {
|
||||
.phy_id = 0x000fc440,
|
||||
.name = "Cicada Cis8204",
|
||||
.phy_id_mask = 0x000fffc0,
|
||||
.features = PHY_GBIT_FEATURES,
|
||||
.flags = PHY_HAS_INTERRUPT,
|
||||
.config_init = &cis820x_config_init,
|
||||
.config_aneg = &genphy_config_aneg,
|
||||
.read_status = &genphy_read_status,
|
||||
.ack_interrupt = &cis820x_ack_interrupt,
|
||||
.config_intr = &cis820x_config_intr,
|
||||
.driver = { .owner = THIS_MODULE,},
|
||||
} };
|
||||
|
||||
static int __init cicada_init(void)
|
||||
{
|
||||
return phy_drivers_register(cis820x_driver,
|
||||
ARRAY_SIZE(cis820x_driver));
|
||||
}
|
||||
|
||||
static void __exit cicada_exit(void)
|
||||
{
|
||||
phy_drivers_unregister(cis820x_driver,
|
||||
ARRAY_SIZE(cis820x_driver));
|
||||
}
|
||||
|
||||
module_init(cicada_init);
|
||||
module_exit(cicada_exit);
|
||||
|
||||
static struct mdio_device_id __maybe_unused cicada_tbl[] = {
|
||||
{ 0x000fc410, 0x000ffff0 },
|
||||
{ 0x000fc440, 0x000fffc0 },
|
||||
{ }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(mdio, cicada_tbl);
|
||||
207
drivers/net/phy/davicom.c
Normal file
207
drivers/net/phy/davicom.c
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
/*
|
||||
* drivers/net/phy/davicom.c
|
||||
*
|
||||
* Driver for Davicom 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/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_DM9161_SCR 0x10
|
||||
#define MII_DM9161_SCR_INIT 0x0610
|
||||
#define MII_DM9161_SCR_RMII 0x0100
|
||||
|
||||
/* DM9161 Interrupt Register */
|
||||
#define MII_DM9161_INTR 0x15
|
||||
#define MII_DM9161_INTR_PEND 0x8000
|
||||
#define MII_DM9161_INTR_DPLX_MASK 0x0800
|
||||
#define MII_DM9161_INTR_SPD_MASK 0x0400
|
||||
#define MII_DM9161_INTR_LINK_MASK 0x0200
|
||||
#define MII_DM9161_INTR_MASK 0x0100
|
||||
#define MII_DM9161_INTR_DPLX_CHANGE 0x0010
|
||||
#define MII_DM9161_INTR_SPD_CHANGE 0x0008
|
||||
#define MII_DM9161_INTR_LINK_CHANGE 0x0004
|
||||
#define MII_DM9161_INTR_INIT 0x0000
|
||||
#define MII_DM9161_INTR_STOP \
|
||||
(MII_DM9161_INTR_DPLX_MASK | MII_DM9161_INTR_SPD_MASK \
|
||||
| MII_DM9161_INTR_LINK_MASK | MII_DM9161_INTR_MASK)
|
||||
|
||||
/* DM9161 10BT Configuration/Status */
|
||||
#define MII_DM9161_10BTCSR 0x12
|
||||
#define MII_DM9161_10BTCSR_INIT 0x7800
|
||||
|
||||
MODULE_DESCRIPTION("Davicom PHY driver");
|
||||
MODULE_AUTHOR("Andy Fleming");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
|
||||
#define DM9161_DELAY 1
|
||||
static int dm9161_config_intr(struct phy_device *phydev)
|
||||
{
|
||||
int temp;
|
||||
|
||||
temp = phy_read(phydev, MII_DM9161_INTR);
|
||||
|
||||
if (temp < 0)
|
||||
return temp;
|
||||
|
||||
if (PHY_INTERRUPT_ENABLED == phydev->interrupts)
|
||||
temp &= ~(MII_DM9161_INTR_STOP);
|
||||
else
|
||||
temp |= MII_DM9161_INTR_STOP;
|
||||
|
||||
temp = phy_write(phydev, MII_DM9161_INTR, temp);
|
||||
|
||||
return temp;
|
||||
}
|
||||
|
||||
static int dm9161_config_aneg(struct phy_device *phydev)
|
||||
{
|
||||
int err;
|
||||
|
||||
/* Isolate the PHY */
|
||||
err = phy_write(phydev, MII_BMCR, BMCR_ISOLATE);
|
||||
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
/* Configure the new settings */
|
||||
err = genphy_config_aneg(phydev);
|
||||
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dm9161_config_init(struct phy_device *phydev)
|
||||
{
|
||||
int err, temp;
|
||||
|
||||
/* Isolate the PHY */
|
||||
err = phy_write(phydev, MII_BMCR, BMCR_ISOLATE);
|
||||
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
switch (phydev->interface) {
|
||||
case PHY_INTERFACE_MODE_MII:
|
||||
temp = MII_DM9161_SCR_INIT;
|
||||
break;
|
||||
case PHY_INTERFACE_MODE_RMII:
|
||||
temp = MII_DM9161_SCR_INIT | MII_DM9161_SCR_RMII;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Do not bypass the scrambler/descrambler */
|
||||
err = phy_write(phydev, MII_DM9161_SCR, temp);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
/* Clear 10BTCSR to default */
|
||||
err = phy_write(phydev, MII_DM9161_10BTCSR, MII_DM9161_10BTCSR_INIT);
|
||||
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
/* Reconnect the PHY, and enable Autonegotiation */
|
||||
return phy_write(phydev, MII_BMCR, BMCR_ANENABLE);
|
||||
}
|
||||
|
||||
static int dm9161_ack_interrupt(struct phy_device *phydev)
|
||||
{
|
||||
int err = phy_read(phydev, MII_DM9161_INTR);
|
||||
|
||||
return (err < 0) ? err : 0;
|
||||
}
|
||||
|
||||
static struct phy_driver dm91xx_driver[] = {
|
||||
{
|
||||
.phy_id = 0x0181b880,
|
||||
.name = "Davicom DM9161E",
|
||||
.phy_id_mask = 0x0ffffff0,
|
||||
.features = PHY_BASIC_FEATURES,
|
||||
.flags = PHY_HAS_INTERRUPT,
|
||||
.config_init = dm9161_config_init,
|
||||
.config_aneg = dm9161_config_aneg,
|
||||
.read_status = genphy_read_status,
|
||||
.ack_interrupt = dm9161_ack_interrupt,
|
||||
.config_intr = dm9161_config_intr,
|
||||
.driver = { .owner = THIS_MODULE,},
|
||||
}, {
|
||||
.phy_id = 0x0181b8a0,
|
||||
.name = "Davicom DM9161A",
|
||||
.phy_id_mask = 0x0ffffff0,
|
||||
.features = PHY_BASIC_FEATURES,
|
||||
.flags = PHY_HAS_INTERRUPT,
|
||||
.config_init = dm9161_config_init,
|
||||
.config_aneg = dm9161_config_aneg,
|
||||
.read_status = genphy_read_status,
|
||||
.ack_interrupt = dm9161_ack_interrupt,
|
||||
.config_intr = dm9161_config_intr,
|
||||
.driver = { .owner = THIS_MODULE,},
|
||||
}, {
|
||||
.phy_id = 0x00181b80,
|
||||
.name = "Davicom DM9131",
|
||||
.phy_id_mask = 0x0ffffff0,
|
||||
.features = PHY_BASIC_FEATURES,
|
||||
.flags = PHY_HAS_INTERRUPT,
|
||||
.config_aneg = genphy_config_aneg,
|
||||
.read_status = genphy_read_status,
|
||||
.ack_interrupt = dm9161_ack_interrupt,
|
||||
.config_intr = dm9161_config_intr,
|
||||
.driver = { .owner = THIS_MODULE,},
|
||||
} };
|
||||
|
||||
static int __init davicom_init(void)
|
||||
{
|
||||
return phy_drivers_register(dm91xx_driver,
|
||||
ARRAY_SIZE(dm91xx_driver));
|
||||
}
|
||||
|
||||
static void __exit davicom_exit(void)
|
||||
{
|
||||
phy_drivers_unregister(dm91xx_driver,
|
||||
ARRAY_SIZE(dm91xx_driver));
|
||||
}
|
||||
|
||||
module_init(davicom_init);
|
||||
module_exit(davicom_exit);
|
||||
|
||||
static struct mdio_device_id __maybe_unused davicom_tbl[] = {
|
||||
{ 0x0181b880, 0x0ffffff0 },
|
||||
{ 0x0181b8a0, 0x0ffffff0 },
|
||||
{ 0x00181b80, 0x0ffffff0 },
|
||||
{ }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(mdio, davicom_tbl);
|
||||
1493
drivers/net/phy/dp83640.c
Normal file
1493
drivers/net/phy/dp83640.c
Normal file
File diff suppressed because it is too large
Load diff
267
drivers/net/phy/dp83640_reg.h
Normal file
267
drivers/net/phy/dp83640_reg.h
Normal file
|
|
@ -0,0 +1,267 @@
|
|||
/* dp83640_reg.h
|
||||
* Generated by regen.tcl on Thu Feb 17 10:02:48 AM CET 2011
|
||||
*/
|
||||
#ifndef HAVE_DP83640_REGISTERS
|
||||
#define HAVE_DP83640_REGISTERS
|
||||
|
||||
#define PAGE0 0x0000
|
||||
#define PHYCR2 0x001c /* PHY Control Register 2 */
|
||||
|
||||
#define PAGE4 0x0004
|
||||
#define PTP_CTL 0x0014 /* PTP Control Register */
|
||||
#define PTP_TDR 0x0015 /* PTP Time Data Register */
|
||||
#define PTP_STS 0x0016 /* PTP Status Register */
|
||||
#define PTP_TSTS 0x0017 /* PTP Trigger Status Register */
|
||||
#define PTP_RATEL 0x0018 /* PTP Rate Low Register */
|
||||
#define PTP_RATEH 0x0019 /* PTP Rate High Register */
|
||||
#define PTP_RDCKSUM 0x001a /* PTP Read Checksum */
|
||||
#define PTP_WRCKSUM 0x001b /* PTP Write Checksum */
|
||||
#define PTP_TXTS 0x001c /* PTP Transmit Timestamp Register, in four 16-bit reads */
|
||||
#define PTP_RXTS 0x001d /* PTP Receive Timestamp Register, in six? 16-bit reads */
|
||||
#define PTP_ESTS 0x001e /* PTP Event Status Register */
|
||||
#define PTP_EDATA 0x001f /* PTP Event Data Register */
|
||||
|
||||
#define PAGE5 0x0005
|
||||
#define PTP_TRIG 0x0014 /* PTP Trigger Configuration Register */
|
||||
#define PTP_EVNT 0x0015 /* PTP Event Configuration Register */
|
||||
#define PTP_TXCFG0 0x0016 /* PTP Transmit Configuration Register 0 */
|
||||
#define PTP_TXCFG1 0x0017 /* PTP Transmit Configuration Register 1 */
|
||||
#define PSF_CFG0 0x0018 /* PHY Status Frame Configuration Register 0 */
|
||||
#define PTP_RXCFG0 0x0019 /* PTP Receive Configuration Register 0 */
|
||||
#define PTP_RXCFG1 0x001a /* PTP Receive Configuration Register 1 */
|
||||
#define PTP_RXCFG2 0x001b /* PTP Receive Configuration Register 2 */
|
||||
#define PTP_RXCFG3 0x001c /* PTP Receive Configuration Register 3 */
|
||||
#define PTP_RXCFG4 0x001d /* PTP Receive Configuration Register 4 */
|
||||
#define PTP_TRDL 0x001e /* PTP Temporary Rate Duration Low Register */
|
||||
#define PTP_TRDH 0x001f /* PTP Temporary Rate Duration High Register */
|
||||
|
||||
#define PAGE6 0x0006
|
||||
#define PTP_COC 0x0014 /* PTP Clock Output Control Register */
|
||||
#define PSF_CFG1 0x0015 /* PHY Status Frame Configuration Register 1 */
|
||||
#define PSF_CFG2 0x0016 /* PHY Status Frame Configuration Register 2 */
|
||||
#define PSF_CFG3 0x0017 /* PHY Status Frame Configuration Register 3 */
|
||||
#define PSF_CFG4 0x0018 /* PHY Status Frame Configuration Register 4 */
|
||||
#define PTP_SFDCFG 0x0019 /* PTP SFD Configuration Register */
|
||||
#define PTP_INTCTL 0x001a /* PTP Interrupt Control Register */
|
||||
#define PTP_CLKSRC 0x001b /* PTP Clock Source Register */
|
||||
#define PTP_ETR 0x001c /* PTP Ethernet Type Register */
|
||||
#define PTP_OFF 0x001d /* PTP Offset Register */
|
||||
#define PTP_GPIOMON 0x001e /* PTP GPIO Monitor Register */
|
||||
#define PTP_RXHASH 0x001f /* PTP Receive Hash Register */
|
||||
|
||||
/* Bit definitions for the PHYCR2 register */
|
||||
#define BC_WRITE (1<<11) /* Broadcast Write Enable */
|
||||
|
||||
/* Bit definitions for the PTP_CTL register */
|
||||
#define TRIG_SEL_SHIFT (10) /* PTP Trigger Select */
|
||||
#define TRIG_SEL_MASK (0x7)
|
||||
#define TRIG_DIS (1<<9) /* Disable PTP Trigger */
|
||||
#define TRIG_EN (1<<8) /* Enable PTP Trigger */
|
||||
#define TRIG_READ (1<<7) /* Read PTP Trigger */
|
||||
#define TRIG_LOAD (1<<6) /* Load PTP Trigger */
|
||||
#define PTP_RD_CLK (1<<5) /* Read PTP Clock */
|
||||
#define PTP_LOAD_CLK (1<<4) /* Load PTP Clock */
|
||||
#define PTP_STEP_CLK (1<<3) /* Step PTP Clock */
|
||||
#define PTP_ENABLE (1<<2) /* Enable PTP Clock */
|
||||
#define PTP_DISABLE (1<<1) /* Disable PTP Clock */
|
||||
#define PTP_RESET (1<<0) /* Reset PTP Clock */
|
||||
|
||||
/* Bit definitions for the PTP_STS register */
|
||||
#define TXTS_RDY (1<<11) /* Transmit Timestamp Ready */
|
||||
#define RXTS_RDY (1<<10) /* Receive Timestamp Ready */
|
||||
#define TRIG_DONE (1<<9) /* PTP Trigger Done */
|
||||
#define EVENT_RDY (1<<8) /* PTP Event Timestamp Ready */
|
||||
#define TXTS_IE (1<<3) /* Transmit Timestamp Interrupt Enable */
|
||||
#define RXTS_IE (1<<2) /* Receive Timestamp Interrupt Enable */
|
||||
#define TRIG_IE (1<<1) /* Trigger Interrupt Enable */
|
||||
#define EVENT_IE (1<<0) /* Event Interrupt Enable */
|
||||
|
||||
/* Bit definitions for the PTP_TSTS register */
|
||||
#define TRIG7_ERROR (1<<15) /* Trigger 7 Error */
|
||||
#define TRIG7_ACTIVE (1<<14) /* Trigger 7 Active */
|
||||
#define TRIG6_ERROR (1<<13) /* Trigger 6 Error */
|
||||
#define TRIG6_ACTIVE (1<<12) /* Trigger 6 Active */
|
||||
#define TRIG5_ERROR (1<<11) /* Trigger 5 Error */
|
||||
#define TRIG5_ACTIVE (1<<10) /* Trigger 5 Active */
|
||||
#define TRIG4_ERROR (1<<9) /* Trigger 4 Error */
|
||||
#define TRIG4_ACTIVE (1<<8) /* Trigger 4 Active */
|
||||
#define TRIG3_ERROR (1<<7) /* Trigger 3 Error */
|
||||
#define TRIG3_ACTIVE (1<<6) /* Trigger 3 Active */
|
||||
#define TRIG2_ERROR (1<<5) /* Trigger 2 Error */
|
||||
#define TRIG2_ACTIVE (1<<4) /* Trigger 2 Active */
|
||||
#define TRIG1_ERROR (1<<3) /* Trigger 1 Error */
|
||||
#define TRIG1_ACTIVE (1<<2) /* Trigger 1 Active */
|
||||
#define TRIG0_ERROR (1<<1) /* Trigger 0 Error */
|
||||
#define TRIG0_ACTIVE (1<<0) /* Trigger 0 Active */
|
||||
|
||||
/* Bit definitions for the PTP_RATEH register */
|
||||
#define PTP_RATE_DIR (1<<15) /* PTP Rate Direction */
|
||||
#define PTP_TMP_RATE (1<<14) /* PTP Temporary Rate */
|
||||
#define PTP_RATE_HI_SHIFT (0) /* PTP Rate High 10-bits */
|
||||
#define PTP_RATE_HI_MASK (0x3ff)
|
||||
|
||||
/* Bit definitions for the PTP_ESTS register */
|
||||
#define EVNTS_MISSED_SHIFT (8) /* Indicates number of events missed */
|
||||
#define EVNTS_MISSED_MASK (0x7)
|
||||
#define EVNT_TS_LEN_SHIFT (6) /* Indicates length of the Timestamp field in 16-bit words minus 1 */
|
||||
#define EVNT_TS_LEN_MASK (0x3)
|
||||
#define EVNT_RF (1<<5) /* Indicates whether the event is a rise or falling event */
|
||||
#define EVNT_NUM_SHIFT (2) /* Indicates Event Timestamp Unit which detected an event */
|
||||
#define EVNT_NUM_MASK (0x7)
|
||||
#define MULT_EVNT (1<<1) /* Indicates multiple events were detected at the same time */
|
||||
#define EVENT_DET (1<<0) /* PTP Event Detected */
|
||||
|
||||
/* Bit definitions for the PTP_EDATA register */
|
||||
#define E7_RISE (1<<15) /* Indicates direction of Event 7 */
|
||||
#define E7_DET (1<<14) /* Indicates Event 7 detected */
|
||||
#define E6_RISE (1<<13) /* Indicates direction of Event 6 */
|
||||
#define E6_DET (1<<12) /* Indicates Event 6 detected */
|
||||
#define E5_RISE (1<<11) /* Indicates direction of Event 5 */
|
||||
#define E5_DET (1<<10) /* Indicates Event 5 detected */
|
||||
#define E4_RISE (1<<9) /* Indicates direction of Event 4 */
|
||||
#define E4_DET (1<<8) /* Indicates Event 4 detected */
|
||||
#define E3_RISE (1<<7) /* Indicates direction of Event 3 */
|
||||
#define E3_DET (1<<6) /* Indicates Event 3 detected */
|
||||
#define E2_RISE (1<<5) /* Indicates direction of Event 2 */
|
||||
#define E2_DET (1<<4) /* Indicates Event 2 detected */
|
||||
#define E1_RISE (1<<3) /* Indicates direction of Event 1 */
|
||||
#define E1_DET (1<<2) /* Indicates Event 1 detected */
|
||||
#define E0_RISE (1<<1) /* Indicates direction of Event 0 */
|
||||
#define E0_DET (1<<0) /* Indicates Event 0 detected */
|
||||
|
||||
/* Bit definitions for the PTP_TRIG register */
|
||||
#define TRIG_PULSE (1<<15) /* generate a Pulse rather than a single edge */
|
||||
#define TRIG_PER (1<<14) /* generate a periodic signal */
|
||||
#define TRIG_IF_LATE (1<<13) /* trigger immediately if already past */
|
||||
#define TRIG_NOTIFY (1<<12) /* Trigger Notification Enable */
|
||||
#define TRIG_GPIO_SHIFT (8) /* Trigger GPIO Connection, value 1-12 */
|
||||
#define TRIG_GPIO_MASK (0xf)
|
||||
#define TRIG_TOGGLE (1<<7) /* Trigger Toggle Mode Enable */
|
||||
#define TRIG_CSEL_SHIFT (1) /* Trigger Configuration Select */
|
||||
#define TRIG_CSEL_MASK (0x7)
|
||||
#define TRIG_WR (1<<0) /* Trigger Configuration Write */
|
||||
|
||||
/* Bit definitions for the PTP_EVNT register */
|
||||
#define EVNT_RISE (1<<14) /* Event Rise Detect Enable */
|
||||
#define EVNT_FALL (1<<13) /* Event Fall Detect Enable */
|
||||
#define EVNT_SINGLE (1<<12) /* enable single event capture operation */
|
||||
#define EVNT_GPIO_SHIFT (8) /* Event GPIO Connection, value 1-12 */
|
||||
#define EVNT_GPIO_MASK (0xf)
|
||||
#define EVNT_SEL_SHIFT (1) /* Event Select */
|
||||
#define EVNT_SEL_MASK (0x7)
|
||||
#define EVNT_WR (1<<0) /* Event Configuration Write */
|
||||
|
||||
/* Bit definitions for the PTP_TXCFG0 register */
|
||||
#define SYNC_1STEP (1<<15) /* insert timestamp into transmit Sync Messages */
|
||||
#define DR_INSERT (1<<13) /* Insert Delay_Req Timestamp in Delay_Resp (dangerous) */
|
||||
#define NTP_TS_EN (1<<12) /* Enable Timestamping of NTP Packets */
|
||||
#define IGNORE_2STEP (1<<11) /* Ignore Two_Step flag for One-Step operation */
|
||||
#define CRC_1STEP (1<<10) /* Disable checking of CRC for One-Step operation */
|
||||
#define CHK_1STEP (1<<9) /* Enable UDP Checksum correction for One-Step Operation */
|
||||
#define IP1588_EN (1<<8) /* Enable IEEE 1588 defined IP address filter */
|
||||
#define TX_L2_EN (1<<7) /* Layer2 Timestamp Enable */
|
||||
#define TX_IPV6_EN (1<<6) /* IPv6 Timestamp Enable */
|
||||
#define TX_IPV4_EN (1<<5) /* IPv4 Timestamp Enable */
|
||||
#define TX_PTP_VER_SHIFT (1) /* Enable Timestamp capture for IEEE 1588 version X */
|
||||
#define TX_PTP_VER_MASK (0xf)
|
||||
#define TX_TS_EN (1<<0) /* Transmit Timestamp Enable */
|
||||
|
||||
/* Bit definitions for the PTP_TXCFG1 register */
|
||||
#define BYTE0_MASK_SHIFT (8) /* Bit mask to be used for matching Byte0 of the PTP Message */
|
||||
#define BYTE0_MASK_MASK (0xff)
|
||||
#define BYTE0_DATA_SHIFT (0) /* Data to be used for matching Byte0 of the PTP Message */
|
||||
#define BYTE0_DATA_MASK (0xff)
|
||||
|
||||
/* Bit definitions for the PSF_CFG0 register */
|
||||
#define MAC_SRC_ADD_SHIFT (11) /* Status Frame Mac Source Address */
|
||||
#define MAC_SRC_ADD_MASK (0x3)
|
||||
#define MIN_PRE_SHIFT (8) /* Status Frame Minimum Preamble */
|
||||
#define MIN_PRE_MASK (0x7)
|
||||
#define PSF_ENDIAN (1<<7) /* Status Frame Endian Control */
|
||||
#define PSF_IPV4 (1<<6) /* Status Frame IPv4 Enable */
|
||||
#define PSF_PCF_RD (1<<5) /* Control Frame Read PHY Status Frame Enable */
|
||||
#define PSF_ERR_EN (1<<4) /* Error PHY Status Frame Enable */
|
||||
#define PSF_TXTS_EN (1<<3) /* Transmit Timestamp PHY Status Frame Enable */
|
||||
#define PSF_RXTS_EN (1<<2) /* Receive Timestamp PHY Status Frame Enable */
|
||||
#define PSF_TRIG_EN (1<<1) /* Trigger PHY Status Frame Enable */
|
||||
#define PSF_EVNT_EN (1<<0) /* Event PHY Status Frame Enable */
|
||||
|
||||
/* Bit definitions for the PTP_RXCFG0 register */
|
||||
#define DOMAIN_EN (1<<15) /* Domain Match Enable */
|
||||
#define ALT_MAST_DIS (1<<14) /* Alternate Master Timestamp Disable */
|
||||
#define USER_IP_SEL (1<<13) /* Selects portion of IP address accessible thru PTP_RXCFG2 */
|
||||
#define USER_IP_EN (1<<12) /* Enable User-programmed IP address filter */
|
||||
#define RX_SLAVE (1<<11) /* Receive Slave Only */
|
||||
#define IP1588_EN_SHIFT (8) /* Enable IEEE 1588 defined IP address filters */
|
||||
#define IP1588_EN_MASK (0xf)
|
||||
#define RX_L2_EN (1<<7) /* Layer2 Timestamp Enable */
|
||||
#define RX_IPV6_EN (1<<6) /* IPv6 Timestamp Enable */
|
||||
#define RX_IPV4_EN (1<<5) /* IPv4 Timestamp Enable */
|
||||
#define RX_PTP_VER_SHIFT (1) /* Enable Timestamp capture for IEEE 1588 version X */
|
||||
#define RX_PTP_VER_MASK (0xf)
|
||||
#define RX_TS_EN (1<<0) /* Receive Timestamp Enable */
|
||||
|
||||
/* Bit definitions for the PTP_RXCFG1 register */
|
||||
#define BYTE0_MASK_SHIFT (8) /* Bit mask to be used for matching Byte0 of the PTP Message */
|
||||
#define BYTE0_MASK_MASK (0xff)
|
||||
#define BYTE0_DATA_SHIFT (0) /* Data to be used for matching Byte0 of the PTP Message */
|
||||
#define BYTE0_DATA_MASK (0xff)
|
||||
|
||||
/* Bit definitions for the PTP_RXCFG3 register */
|
||||
#define TS_MIN_IFG_SHIFT (12) /* Minimum Inter-frame Gap */
|
||||
#define TS_MIN_IFG_MASK (0xf)
|
||||
#define ACC_UDP (1<<11) /* Record Timestamp if UDP Checksum Error */
|
||||
#define ACC_CRC (1<<10) /* Record Timestamp if CRC Error */
|
||||
#define TS_APPEND (1<<9) /* Append Timestamp for L2 */
|
||||
#define TS_INSERT (1<<8) /* Enable Timestamp Insertion */
|
||||
#define PTP_DOMAIN_SHIFT (0) /* PTP Message domainNumber field */
|
||||
#define PTP_DOMAIN_MASK (0xff)
|
||||
|
||||
/* Bit definitions for the PTP_RXCFG4 register */
|
||||
#define IPV4_UDP_MOD (1<<15) /* Enable IPV4 UDP Modification */
|
||||
#define TS_SEC_EN (1<<14) /* Enable Timestamp Seconds */
|
||||
#define TS_SEC_LEN_SHIFT (12) /* Inserted Timestamp Seconds Length */
|
||||
#define TS_SEC_LEN_MASK (0x3)
|
||||
#define RXTS_NS_OFF_SHIFT (6) /* Receive Timestamp Nanoseconds offset */
|
||||
#define RXTS_NS_OFF_MASK (0x3f)
|
||||
#define RXTS_SEC_OFF_SHIFT (0) /* Receive Timestamp Seconds offset */
|
||||
#define RXTS_SEC_OFF_MASK (0x3f)
|
||||
|
||||
/* Bit definitions for the PTP_COC register */
|
||||
#define PTP_CLKOUT_EN (1<<15) /* PTP Clock Output Enable */
|
||||
#define PTP_CLKOUT_SEL (1<<14) /* PTP Clock Output Source Select */
|
||||
#define PTP_CLKOUT_SPEEDSEL (1<<13) /* PTP Clock Output I/O Speed Select */
|
||||
#define PTP_CLKDIV_SHIFT (0) /* PTP Clock Divide-by Value */
|
||||
#define PTP_CLKDIV_MASK (0xff)
|
||||
|
||||
/* Bit definitions for the PSF_CFG1 register */
|
||||
#define PTPRESERVED_SHIFT (12) /* PTP v2 reserved field */
|
||||
#define PTPRESERVED_MASK (0xf)
|
||||
#define VERSIONPTP_SHIFT (8) /* PTP v2 versionPTP field */
|
||||
#define VERSIONPTP_MASK (0xf)
|
||||
#define TRANSPORT_SPECIFIC_SHIFT (4) /* PTP v2 Header transportSpecific field */
|
||||
#define TRANSPORT_SPECIFIC_MASK (0xf)
|
||||
#define MESSAGETYPE_SHIFT (0) /* PTP v2 messageType field */
|
||||
#define MESSAGETYPE_MASK (0xf)
|
||||
|
||||
/* Bit definitions for the PTP_SFDCFG register */
|
||||
#define TX_SFD_GPIO_SHIFT (4) /* TX SFD GPIO Select, value 1-12 */
|
||||
#define TX_SFD_GPIO_MASK (0xf)
|
||||
#define RX_SFD_GPIO_SHIFT (0) /* RX SFD GPIO Select, value 1-12 */
|
||||
#define RX_SFD_GPIO_MASK (0xf)
|
||||
|
||||
/* Bit definitions for the PTP_INTCTL register */
|
||||
#define PTP_INT_GPIO_SHIFT (0) /* PTP Interrupt GPIO Select */
|
||||
#define PTP_INT_GPIO_MASK (0xf)
|
||||
|
||||
/* Bit definitions for the PTP_CLKSRC register */
|
||||
#define CLK_SRC_SHIFT (14) /* PTP Clock Source Select */
|
||||
#define CLK_SRC_MASK (0x3)
|
||||
#define CLK_SRC_PER_SHIFT (0) /* PTP Clock Source Period */
|
||||
#define CLK_SRC_PER_MASK (0x7f)
|
||||
|
||||
/* Bit definitions for the PTP_OFF register */
|
||||
#define PTP_OFFSET_SHIFT (0) /* PTP Message offset from preceding header */
|
||||
#define PTP_OFFSET_MASK (0xff)
|
||||
|
||||
#endif
|
||||
119
drivers/net/phy/et1011c.c
Normal file
119
drivers/net/phy/et1011c.c
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* drivers/net/phy/et1011c.c
|
||||
*
|
||||
* Driver for LSI ET1011C PHYs
|
||||
*
|
||||
* Author: Chaithrika U S
|
||||
*
|
||||
* Copyright (c) 2008 Texas Instruments
|
||||
*
|
||||
* 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/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 <linux/io.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <asm/irq.h>
|
||||
|
||||
#define ET1011C_STATUS_REG (0x1A)
|
||||
#define ET1011C_CONFIG_REG (0x16)
|
||||
#define ET1011C_SPEED_MASK (0x0300)
|
||||
#define ET1011C_GIGABIT_SPEED (0x0200)
|
||||
#define ET1011C_TX_FIFO_MASK (0x3000)
|
||||
#define ET1011C_TX_FIFO_DEPTH_8 (0x0000)
|
||||
#define ET1011C_TX_FIFO_DEPTH_16 (0x1000)
|
||||
#define ET1011C_INTERFACE_MASK (0x0007)
|
||||
#define ET1011C_GMII_INTERFACE (0x0002)
|
||||
#define ET1011C_SYS_CLK_EN (0x01 << 4)
|
||||
|
||||
|
||||
MODULE_DESCRIPTION("LSI ET1011C PHY driver");
|
||||
MODULE_AUTHOR("Chaithrika U S");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
static int et1011c_config_aneg(struct phy_device *phydev)
|
||||
{
|
||||
int ctl = 0;
|
||||
ctl = phy_read(phydev, MII_BMCR);
|
||||
if (ctl < 0)
|
||||
return ctl;
|
||||
ctl &= ~(BMCR_FULLDPLX | BMCR_SPEED100 | BMCR_SPEED1000 |
|
||||
BMCR_ANENABLE);
|
||||
/* First clear the PHY */
|
||||
phy_write(phydev, MII_BMCR, ctl | BMCR_RESET);
|
||||
|
||||
return genphy_config_aneg(phydev);
|
||||
}
|
||||
|
||||
static int et1011c_read_status(struct phy_device *phydev)
|
||||
{
|
||||
int ret;
|
||||
u32 val;
|
||||
static int speed;
|
||||
ret = genphy_read_status(phydev);
|
||||
|
||||
if (speed != phydev->speed) {
|
||||
speed = phydev->speed;
|
||||
val = phy_read(phydev, ET1011C_STATUS_REG);
|
||||
if ((val & ET1011C_SPEED_MASK) ==
|
||||
ET1011C_GIGABIT_SPEED) {
|
||||
val = phy_read(phydev, ET1011C_CONFIG_REG);
|
||||
val &= ~ET1011C_TX_FIFO_MASK;
|
||||
phy_write(phydev, ET1011C_CONFIG_REG, val\
|
||||
| ET1011C_GMII_INTERFACE\
|
||||
| ET1011C_SYS_CLK_EN\
|
||||
| ET1011C_TX_FIFO_DEPTH_16);
|
||||
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct phy_driver et1011c_driver = {
|
||||
.phy_id = 0x0282f014,
|
||||
.name = "ET1011C",
|
||||
.phy_id_mask = 0xfffffff0,
|
||||
.features = (PHY_BASIC_FEATURES | SUPPORTED_1000baseT_Full),
|
||||
.flags = PHY_POLL,
|
||||
.config_aneg = et1011c_config_aneg,
|
||||
.read_status = et1011c_read_status,
|
||||
.driver = { .owner = THIS_MODULE,},
|
||||
};
|
||||
|
||||
static int __init et1011c_init(void)
|
||||
{
|
||||
return phy_driver_register(&et1011c_driver);
|
||||
}
|
||||
|
||||
static void __exit et1011c_exit(void)
|
||||
{
|
||||
phy_driver_unregister(&et1011c_driver);
|
||||
}
|
||||
|
||||
module_init(et1011c_init);
|
||||
module_exit(et1011c_exit);
|
||||
|
||||
static struct mdio_device_id __maybe_unused et1011c_tbl[] = {
|
||||
{ 0x0282f014, 0xfffffff0 },
|
||||
{ }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(mdio, et1011c_tbl);
|
||||
336
drivers/net/phy/fixed.c
Normal file
336
drivers/net/phy/fixed.c
Normal file
|
|
@ -0,0 +1,336 @@
|
|||
/*
|
||||
* Fixed MDIO bus (MDIO bus emulation with fixed PHYs)
|
||||
*
|
||||
* Author: Vitaly Bordug <vbordug@ru.mvista.com>
|
||||
* Anton Vorontsov <avorontsov@ru.mvista.com>
|
||||
*
|
||||
* Copyright (c) 2006-2007 MontaVista Software, 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/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/mii.h>
|
||||
#include <linux/phy.h>
|
||||
#include <linux/phy_fixed.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/of.h>
|
||||
|
||||
#define MII_REGS_NUM 29
|
||||
|
||||
struct fixed_mdio_bus {
|
||||
int irqs[PHY_MAX_ADDR];
|
||||
struct mii_bus *mii_bus;
|
||||
struct list_head phys;
|
||||
};
|
||||
|
||||
struct fixed_phy {
|
||||
int addr;
|
||||
u16 regs[MII_REGS_NUM];
|
||||
struct phy_device *phydev;
|
||||
struct fixed_phy_status status;
|
||||
int (*link_update)(struct net_device *, struct fixed_phy_status *);
|
||||
struct list_head node;
|
||||
};
|
||||
|
||||
static struct platform_device *pdev;
|
||||
static struct fixed_mdio_bus platform_fmb = {
|
||||
.phys = LIST_HEAD_INIT(platform_fmb.phys),
|
||||
};
|
||||
|
||||
static int fixed_phy_update_regs(struct fixed_phy *fp)
|
||||
{
|
||||
u16 bmsr = BMSR_ANEGCAPABLE;
|
||||
u16 bmcr = 0;
|
||||
u16 lpagb = 0;
|
||||
u16 lpa = 0;
|
||||
|
||||
if (fp->status.duplex) {
|
||||
bmcr |= BMCR_FULLDPLX;
|
||||
|
||||
switch (fp->status.speed) {
|
||||
case 1000:
|
||||
bmsr |= BMSR_ESTATEN;
|
||||
bmcr |= BMCR_SPEED1000;
|
||||
lpagb |= LPA_1000FULL;
|
||||
break;
|
||||
case 100:
|
||||
bmsr |= BMSR_100FULL;
|
||||
bmcr |= BMCR_SPEED100;
|
||||
lpa |= LPA_100FULL;
|
||||
break;
|
||||
case 10:
|
||||
bmsr |= BMSR_10FULL;
|
||||
lpa |= LPA_10FULL;
|
||||
break;
|
||||
default:
|
||||
pr_warn("fixed phy: unknown speed\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
} else {
|
||||
switch (fp->status.speed) {
|
||||
case 1000:
|
||||
bmsr |= BMSR_ESTATEN;
|
||||
bmcr |= BMCR_SPEED1000;
|
||||
lpagb |= LPA_1000HALF;
|
||||
break;
|
||||
case 100:
|
||||
bmsr |= BMSR_100HALF;
|
||||
bmcr |= BMCR_SPEED100;
|
||||
lpa |= LPA_100HALF;
|
||||
break;
|
||||
case 10:
|
||||
bmsr |= BMSR_10HALF;
|
||||
lpa |= LPA_10HALF;
|
||||
break;
|
||||
default:
|
||||
pr_warn("fixed phy: unknown speed\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
if (fp->status.link)
|
||||
bmsr |= BMSR_LSTATUS | BMSR_ANEGCOMPLETE;
|
||||
|
||||
if (fp->status.pause)
|
||||
lpa |= LPA_PAUSE_CAP;
|
||||
|
||||
if (fp->status.asym_pause)
|
||||
lpa |= LPA_PAUSE_ASYM;
|
||||
|
||||
fp->regs[MII_PHYSID1] = 0;
|
||||
fp->regs[MII_PHYSID2] = 0;
|
||||
|
||||
fp->regs[MII_BMSR] = bmsr;
|
||||
fp->regs[MII_BMCR] = bmcr;
|
||||
fp->regs[MII_LPA] = lpa;
|
||||
fp->regs[MII_STAT1000] = lpagb;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fixed_mdio_read(struct mii_bus *bus, int phy_addr, int reg_num)
|
||||
{
|
||||
struct fixed_mdio_bus *fmb = bus->priv;
|
||||
struct fixed_phy *fp;
|
||||
|
||||
if (reg_num >= MII_REGS_NUM)
|
||||
return -1;
|
||||
|
||||
/* We do not support emulating Clause 45 over Clause 22 register reads
|
||||
* return an error instead of bogus data.
|
||||
*/
|
||||
switch (reg_num) {
|
||||
case MII_MMD_CTRL:
|
||||
case MII_MMD_DATA:
|
||||
return -1;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
list_for_each_entry(fp, &fmb->phys, node) {
|
||||
if (fp->addr == phy_addr) {
|
||||
/* Issue callback if user registered it. */
|
||||
if (fp->link_update) {
|
||||
fp->link_update(fp->phydev->attached_dev,
|
||||
&fp->status);
|
||||
fixed_phy_update_regs(fp);
|
||||
}
|
||||
return fp->regs[reg_num];
|
||||
}
|
||||
}
|
||||
|
||||
return 0xFFFF;
|
||||
}
|
||||
|
||||
static int fixed_mdio_write(struct mii_bus *bus, int phy_addr, int reg_num,
|
||||
u16 val)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* If something weird is required to be done with link/speed,
|
||||
* network driver is able to assign a function to implement this.
|
||||
* May be useful for PHY's that need to be software-driven.
|
||||
*/
|
||||
int fixed_phy_set_link_update(struct phy_device *phydev,
|
||||
int (*link_update)(struct net_device *,
|
||||
struct fixed_phy_status *))
|
||||
{
|
||||
struct fixed_mdio_bus *fmb = &platform_fmb;
|
||||
struct fixed_phy *fp;
|
||||
|
||||
if (!link_update || !phydev || !phydev->bus)
|
||||
return -EINVAL;
|
||||
|
||||
list_for_each_entry(fp, &fmb->phys, node) {
|
||||
if (fp->addr == phydev->addr) {
|
||||
fp->link_update = link_update;
|
||||
fp->phydev = phydev;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -ENOENT;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(fixed_phy_set_link_update);
|
||||
|
||||
int fixed_phy_add(unsigned int irq, int phy_addr,
|
||||
struct fixed_phy_status *status)
|
||||
{
|
||||
int ret;
|
||||
struct fixed_mdio_bus *fmb = &platform_fmb;
|
||||
struct fixed_phy *fp;
|
||||
|
||||
fp = kzalloc(sizeof(*fp), GFP_KERNEL);
|
||||
if (!fp)
|
||||
return -ENOMEM;
|
||||
|
||||
memset(fp->regs, 0xFF, sizeof(fp->regs[0]) * MII_REGS_NUM);
|
||||
|
||||
fmb->irqs[phy_addr] = irq;
|
||||
|
||||
fp->addr = phy_addr;
|
||||
fp->status = *status;
|
||||
|
||||
ret = fixed_phy_update_regs(fp);
|
||||
if (ret)
|
||||
goto err_regs;
|
||||
|
||||
list_add_tail(&fp->node, &fmb->phys);
|
||||
|
||||
return 0;
|
||||
|
||||
err_regs:
|
||||
kfree(fp);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(fixed_phy_add);
|
||||
|
||||
void fixed_phy_del(int phy_addr)
|
||||
{
|
||||
struct fixed_mdio_bus *fmb = &platform_fmb;
|
||||
struct fixed_phy *fp, *tmp;
|
||||
|
||||
list_for_each_entry_safe(fp, tmp, &fmb->phys, node) {
|
||||
if (fp->addr == phy_addr) {
|
||||
list_del(&fp->node);
|
||||
kfree(fp);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(fixed_phy_del);
|
||||
|
||||
static int phy_fixed_addr;
|
||||
static DEFINE_SPINLOCK(phy_fixed_addr_lock);
|
||||
|
||||
struct phy_device *fixed_phy_register(unsigned int irq,
|
||||
struct fixed_phy_status *status,
|
||||
struct device_node *np)
|
||||
{
|
||||
struct fixed_mdio_bus *fmb = &platform_fmb;
|
||||
struct phy_device *phy;
|
||||
int phy_addr;
|
||||
int ret;
|
||||
|
||||
/* Get the next available PHY address, up to PHY_MAX_ADDR */
|
||||
spin_lock(&phy_fixed_addr_lock);
|
||||
if (phy_fixed_addr == PHY_MAX_ADDR) {
|
||||
spin_unlock(&phy_fixed_addr_lock);
|
||||
return ERR_PTR(-ENOSPC);
|
||||
}
|
||||
phy_addr = phy_fixed_addr++;
|
||||
spin_unlock(&phy_fixed_addr_lock);
|
||||
|
||||
ret = fixed_phy_add(PHY_POLL, phy_addr, status);
|
||||
if (ret < 0)
|
||||
return ERR_PTR(ret);
|
||||
|
||||
phy = get_phy_device(fmb->mii_bus, phy_addr, false);
|
||||
if (!phy || IS_ERR(phy)) {
|
||||
fixed_phy_del(phy_addr);
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
of_node_get(np);
|
||||
phy->dev.of_node = np;
|
||||
|
||||
ret = phy_device_register(phy);
|
||||
if (ret) {
|
||||
phy_device_free(phy);
|
||||
of_node_put(np);
|
||||
fixed_phy_del(phy_addr);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
return phy;
|
||||
}
|
||||
|
||||
static int __init fixed_mdio_bus_init(void)
|
||||
{
|
||||
struct fixed_mdio_bus *fmb = &platform_fmb;
|
||||
int ret;
|
||||
|
||||
pdev = platform_device_register_simple("Fixed MDIO bus", 0, NULL, 0);
|
||||
if (IS_ERR(pdev)) {
|
||||
ret = PTR_ERR(pdev);
|
||||
goto err_pdev;
|
||||
}
|
||||
|
||||
fmb->mii_bus = mdiobus_alloc();
|
||||
if (fmb->mii_bus == NULL) {
|
||||
ret = -ENOMEM;
|
||||
goto err_mdiobus_reg;
|
||||
}
|
||||
|
||||
snprintf(fmb->mii_bus->id, MII_BUS_ID_SIZE, "fixed-0");
|
||||
fmb->mii_bus->name = "Fixed MDIO Bus";
|
||||
fmb->mii_bus->priv = fmb;
|
||||
fmb->mii_bus->parent = &pdev->dev;
|
||||
fmb->mii_bus->read = &fixed_mdio_read;
|
||||
fmb->mii_bus->write = &fixed_mdio_write;
|
||||
fmb->mii_bus->irq = fmb->irqs;
|
||||
|
||||
ret = mdiobus_register(fmb->mii_bus);
|
||||
if (ret)
|
||||
goto err_mdiobus_alloc;
|
||||
|
||||
return 0;
|
||||
|
||||
err_mdiobus_alloc:
|
||||
mdiobus_free(fmb->mii_bus);
|
||||
err_mdiobus_reg:
|
||||
platform_device_unregister(pdev);
|
||||
err_pdev:
|
||||
return ret;
|
||||
}
|
||||
module_init(fixed_mdio_bus_init);
|
||||
|
||||
static void __exit fixed_mdio_bus_exit(void)
|
||||
{
|
||||
struct fixed_mdio_bus *fmb = &platform_fmb;
|
||||
struct fixed_phy *fp, *tmp;
|
||||
|
||||
mdiobus_unregister(fmb->mii_bus);
|
||||
mdiobus_free(fmb->mii_bus);
|
||||
platform_device_unregister(pdev);
|
||||
|
||||
list_for_each_entry_safe(fp, tmp, &fmb->phys, node) {
|
||||
list_del(&fp->node);
|
||||
kfree(fp);
|
||||
}
|
||||
}
|
||||
module_exit(fixed_mdio_bus_exit);
|
||||
|
||||
MODULE_DESCRIPTION("Fixed MDIO bus (MDIO bus emulation with fixed PHYs)");
|
||||
MODULE_AUTHOR("Vitaly Bordug");
|
||||
MODULE_LICENSE("GPL");
|
||||
278
drivers/net/phy/icplus.c
Normal file
278
drivers/net/phy/icplus.c
Normal file
|
|
@ -0,0 +1,278 @@
|
|||
/*
|
||||
* Driver for ICPlus PHYs
|
||||
*
|
||||
* Copyright (c) 2007 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/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>
|
||||
|
||||
MODULE_DESCRIPTION("ICPlus IP175C/IP101A/IP101G/IC1001 PHY drivers");
|
||||
MODULE_AUTHOR("Michael Barkowski");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
/* IP101A/G - IP1001 */
|
||||
#define IP10XX_SPEC_CTRL_STATUS 16 /* Spec. Control Register */
|
||||
#define IP1001_RXPHASE_SEL (1<<0) /* Add delay on RX_CLK */
|
||||
#define IP1001_TXPHASE_SEL (1<<1) /* Add delay on TX_CLK */
|
||||
#define IP1001_SPEC_CTRL_STATUS_2 20 /* IP1001 Spec. Control Reg 2 */
|
||||
#define IP1001_APS_ON 11 /* IP1001 APS Mode bit */
|
||||
#define IP101A_G_APS_ON 2 /* IP101A/G APS Mode bit */
|
||||
#define IP101A_G_IRQ_CONF_STATUS 0x11 /* Conf Info IRQ & Status Reg */
|
||||
#define IP101A_G_IRQ_PIN_USED (1<<15) /* INTR pin used */
|
||||
#define IP101A_G_IRQ_DEFAULT IP101A_G_IRQ_PIN_USED
|
||||
|
||||
static int ip175c_config_init(struct phy_device *phydev)
|
||||
{
|
||||
int err, i;
|
||||
static int full_reset_performed;
|
||||
|
||||
if (full_reset_performed == 0) {
|
||||
|
||||
/* master reset */
|
||||
err = mdiobus_write(phydev->bus, 30, 0, 0x175c);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
/* ensure no bus delays overlap reset period */
|
||||
err = mdiobus_read(phydev->bus, 30, 0);
|
||||
|
||||
/* data sheet specifies reset period is 2 msec */
|
||||
mdelay(2);
|
||||
|
||||
/* enable IP175C mode */
|
||||
err = mdiobus_write(phydev->bus, 29, 31, 0x175c);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
/* Set MII0 speed and duplex (in PHY mode) */
|
||||
err = mdiobus_write(phydev->bus, 29, 22, 0x420);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
/* reset switch ports */
|
||||
for (i = 0; i < 5; i++) {
|
||||
err = mdiobus_write(phydev->bus, i,
|
||||
MII_BMCR, BMCR_RESET);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
|
||||
for (i = 0; i < 5; i++)
|
||||
err = mdiobus_read(phydev->bus, i, MII_BMCR);
|
||||
|
||||
mdelay(2);
|
||||
|
||||
full_reset_performed = 1;
|
||||
}
|
||||
|
||||
if (phydev->addr != 4) {
|
||||
phydev->state = PHY_RUNNING;
|
||||
phydev->speed = SPEED_100;
|
||||
phydev->duplex = DUPLEX_FULL;
|
||||
phydev->link = 1;
|
||||
netif_carrier_on(phydev->attached_dev);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ip1xx_reset(struct phy_device *phydev)
|
||||
{
|
||||
int bmcr;
|
||||
|
||||
/* Software Reset PHY */
|
||||
bmcr = phy_read(phydev, MII_BMCR);
|
||||
if (bmcr < 0)
|
||||
return bmcr;
|
||||
bmcr |= BMCR_RESET;
|
||||
bmcr = phy_write(phydev, MII_BMCR, bmcr);
|
||||
if (bmcr < 0)
|
||||
return bmcr;
|
||||
|
||||
do {
|
||||
bmcr = phy_read(phydev, MII_BMCR);
|
||||
if (bmcr < 0)
|
||||
return bmcr;
|
||||
} while (bmcr & BMCR_RESET);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ip1001_config_init(struct phy_device *phydev)
|
||||
{
|
||||
int c;
|
||||
|
||||
c = ip1xx_reset(phydev);
|
||||
if (c < 0)
|
||||
return c;
|
||||
|
||||
/* Enable Auto Power Saving mode */
|
||||
c = phy_read(phydev, IP1001_SPEC_CTRL_STATUS_2);
|
||||
if (c < 0)
|
||||
return c;
|
||||
c |= IP1001_APS_ON;
|
||||
c = phy_write(phydev, IP1001_SPEC_CTRL_STATUS_2, c);
|
||||
if (c < 0)
|
||||
return c;
|
||||
|
||||
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)) {
|
||||
|
||||
c = phy_read(phydev, IP10XX_SPEC_CTRL_STATUS);
|
||||
if (c < 0)
|
||||
return c;
|
||||
|
||||
c &= ~(IP1001_RXPHASE_SEL | IP1001_TXPHASE_SEL);
|
||||
|
||||
if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID)
|
||||
c |= (IP1001_RXPHASE_SEL | IP1001_TXPHASE_SEL);
|
||||
else if (phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID)
|
||||
c |= IP1001_RXPHASE_SEL;
|
||||
else if (phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID)
|
||||
c |= IP1001_TXPHASE_SEL;
|
||||
|
||||
c = phy_write(phydev, IP10XX_SPEC_CTRL_STATUS, c);
|
||||
if (c < 0)
|
||||
return c;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ip101a_g_config_init(struct phy_device *phydev)
|
||||
{
|
||||
int c;
|
||||
|
||||
c = ip1xx_reset(phydev);
|
||||
if (c < 0)
|
||||
return c;
|
||||
|
||||
/* INTR pin used: speed/link/duplex will cause an interrupt */
|
||||
c = phy_write(phydev, IP101A_G_IRQ_CONF_STATUS, IP101A_G_IRQ_DEFAULT);
|
||||
if (c < 0)
|
||||
return c;
|
||||
|
||||
/* Enable Auto Power Saving mode */
|
||||
c = phy_read(phydev, IP10XX_SPEC_CTRL_STATUS);
|
||||
c |= IP101A_G_APS_ON;
|
||||
|
||||
return phy_write(phydev, IP10XX_SPEC_CTRL_STATUS, c);
|
||||
}
|
||||
|
||||
static int ip175c_read_status(struct phy_device *phydev)
|
||||
{
|
||||
if (phydev->addr == 4) /* WAN port */
|
||||
genphy_read_status(phydev);
|
||||
else
|
||||
/* Don't need to read status for switch ports */
|
||||
phydev->irq = PHY_IGNORE_INTERRUPT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ip175c_config_aneg(struct phy_device *phydev)
|
||||
{
|
||||
if (phydev->addr == 4) /* WAN port */
|
||||
genphy_config_aneg(phydev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ip101a_g_ack_interrupt(struct phy_device *phydev)
|
||||
{
|
||||
int err = phy_read(phydev, IP101A_G_IRQ_CONF_STATUS);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct phy_driver icplus_driver[] = {
|
||||
{
|
||||
.phy_id = 0x02430d80,
|
||||
.name = "ICPlus IP175C",
|
||||
.phy_id_mask = 0x0ffffff0,
|
||||
.features = PHY_BASIC_FEATURES,
|
||||
.config_init = &ip175c_config_init,
|
||||
.config_aneg = &ip175c_config_aneg,
|
||||
.read_status = &ip175c_read_status,
|
||||
.suspend = genphy_suspend,
|
||||
.resume = genphy_resume,
|
||||
.driver = { .owner = THIS_MODULE,},
|
||||
}, {
|
||||
.phy_id = 0x02430d90,
|
||||
.name = "ICPlus IP1001",
|
||||
.phy_id_mask = 0x0ffffff0,
|
||||
.features = PHY_GBIT_FEATURES | SUPPORTED_Pause |
|
||||
SUPPORTED_Asym_Pause,
|
||||
.config_init = &ip1001_config_init,
|
||||
.config_aneg = &genphy_config_aneg,
|
||||
.read_status = &genphy_read_status,
|
||||
.suspend = genphy_suspend,
|
||||
.resume = genphy_resume,
|
||||
.driver = { .owner = THIS_MODULE,},
|
||||
}, {
|
||||
.phy_id = 0x02430c54,
|
||||
.name = "ICPlus IP101A/G",
|
||||
.phy_id_mask = 0x0ffffff0,
|
||||
.features = PHY_BASIC_FEATURES | SUPPORTED_Pause |
|
||||
SUPPORTED_Asym_Pause,
|
||||
.flags = PHY_HAS_INTERRUPT,
|
||||
.ack_interrupt = ip101a_g_ack_interrupt,
|
||||
.config_init = &ip101a_g_config_init,
|
||||
.config_aneg = &genphy_config_aneg,
|
||||
.read_status = &genphy_read_status,
|
||||
.suspend = genphy_suspend,
|
||||
.resume = genphy_resume,
|
||||
.driver = { .owner = THIS_MODULE,},
|
||||
} };
|
||||
|
||||
static int __init icplus_init(void)
|
||||
{
|
||||
return phy_drivers_register(icplus_driver,
|
||||
ARRAY_SIZE(icplus_driver));
|
||||
}
|
||||
|
||||
static void __exit icplus_exit(void)
|
||||
{
|
||||
phy_drivers_unregister(icplus_driver,
|
||||
ARRAY_SIZE(icplus_driver));
|
||||
}
|
||||
|
||||
module_init(icplus_init);
|
||||
module_exit(icplus_exit);
|
||||
|
||||
static struct mdio_device_id __maybe_unused icplus_tbl[] = {
|
||||
{ 0x02430d80, 0x0ffffff0 },
|
||||
{ 0x02430d90, 0x0ffffff0 },
|
||||
{ 0x02430c54, 0x0ffffff0 },
|
||||
{ }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(mdio, icplus_tbl);
|
||||
337
drivers/net/phy/lxt.c
Normal file
337
drivers/net/phy/lxt.c
Normal file
|
|
@ -0,0 +1,337 @@
|
|||
/*
|
||||
* drivers/net/phy/lxt.c
|
||||
*
|
||||
* Driver for Intel LXT 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/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>
|
||||
|
||||
/* The Level one LXT970 is used by many boards */
|
||||
|
||||
#define MII_LXT970_IER 17 /* Interrupt Enable Register */
|
||||
|
||||
#define MII_LXT970_IER_IEN 0x0002
|
||||
|
||||
#define MII_LXT970_ISR 18 /* Interrupt Status Register */
|
||||
|
||||
#define MII_LXT970_CONFIG 19 /* Configuration Register */
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* The Level one LXT971 is used on some of my custom boards */
|
||||
|
||||
/* register definitions for the 971 */
|
||||
#define MII_LXT971_IER 18 /* Interrupt Enable Register */
|
||||
#define MII_LXT971_IER_IEN 0x00f2
|
||||
|
||||
#define MII_LXT971_ISR 19 /* Interrupt Status Register */
|
||||
|
||||
/* register definitions for the 973 */
|
||||
#define MII_LXT973_PCR 16 /* Port Configuration Register */
|
||||
#define PCR_FIBER_SELECT 1
|
||||
|
||||
MODULE_DESCRIPTION("Intel LXT PHY driver");
|
||||
MODULE_AUTHOR("Andy Fleming");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
static int lxt970_ack_interrupt(struct phy_device *phydev)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = phy_read(phydev, MII_BMSR);
|
||||
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = phy_read(phydev, MII_LXT970_ISR);
|
||||
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lxt970_config_intr(struct phy_device *phydev)
|
||||
{
|
||||
int err;
|
||||
|
||||
if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
|
||||
err = phy_write(phydev, MII_LXT970_IER, MII_LXT970_IER_IEN);
|
||||
else
|
||||
err = phy_write(phydev, MII_LXT970_IER, 0);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int lxt970_config_init(struct phy_device *phydev)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = phy_write(phydev, MII_LXT970_CONFIG, 0);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
static int lxt971_ack_interrupt(struct phy_device *phydev)
|
||||
{
|
||||
int err = phy_read(phydev, MII_LXT971_ISR);
|
||||
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lxt971_config_intr(struct phy_device *phydev)
|
||||
{
|
||||
int err;
|
||||
|
||||
if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
|
||||
err = phy_write(phydev, MII_LXT971_IER, MII_LXT971_IER_IEN);
|
||||
else
|
||||
err = phy_write(phydev, MII_LXT971_IER, 0);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* A2 version of LXT973 chip has an ERRATA: it randomly return the contents
|
||||
* of the previous even register when you read a odd register regularly
|
||||
*/
|
||||
|
||||
static int lxt973a2_update_link(struct phy_device *phydev)
|
||||
{
|
||||
int status;
|
||||
int control;
|
||||
int retry = 8; /* we try 8 times */
|
||||
|
||||
/* Do a fake read */
|
||||
status = phy_read(phydev, MII_BMSR);
|
||||
|
||||
if (status < 0)
|
||||
return status;
|
||||
|
||||
control = phy_read(phydev, MII_BMCR);
|
||||
if (control < 0)
|
||||
return control;
|
||||
|
||||
do {
|
||||
/* Read link and autonegotiation status */
|
||||
status = phy_read(phydev, MII_BMSR);
|
||||
} while (status >= 0 && retry-- && status == control);
|
||||
|
||||
if (status < 0)
|
||||
return status;
|
||||
|
||||
if ((status & BMSR_LSTATUS) == 0)
|
||||
phydev->link = 0;
|
||||
else
|
||||
phydev->link = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lxt973a2_read_status(struct phy_device *phydev)
|
||||
{
|
||||
int adv;
|
||||
int err;
|
||||
int lpa;
|
||||
int lpagb = 0;
|
||||
|
||||
/* Update the link, but return if there was an error */
|
||||
err = lxt973a2_update_link(phydev);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (AUTONEG_ENABLE == phydev->autoneg) {
|
||||
int retry = 1;
|
||||
|
||||
adv = phy_read(phydev, MII_ADVERTISE);
|
||||
|
||||
if (adv < 0)
|
||||
return adv;
|
||||
|
||||
do {
|
||||
lpa = phy_read(phydev, MII_LPA);
|
||||
|
||||
if (lpa < 0)
|
||||
return lpa;
|
||||
|
||||
/* If both registers are equal, it is suspect but not
|
||||
* impossible, hence a new try
|
||||
*/
|
||||
} while (lpa == adv && retry--);
|
||||
|
||||
lpa &= adv;
|
||||
|
||||
phydev->speed = SPEED_10;
|
||||
phydev->duplex = DUPLEX_HALF;
|
||||
phydev->pause = phydev->asym_pause = 0;
|
||||
|
||||
if (lpagb & (LPA_1000FULL | LPA_1000HALF)) {
|
||||
phydev->speed = SPEED_1000;
|
||||
|
||||
if (lpagb & LPA_1000FULL)
|
||||
phydev->duplex = DUPLEX_FULL;
|
||||
} else if (lpa & (LPA_100FULL | LPA_100HALF)) {
|
||||
phydev->speed = SPEED_100;
|
||||
|
||||
if (lpa & LPA_100FULL)
|
||||
phydev->duplex = DUPLEX_FULL;
|
||||
} else {
|
||||
if (lpa & LPA_10FULL)
|
||||
phydev->duplex = DUPLEX_FULL;
|
||||
}
|
||||
|
||||
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 lxt973_probe(struct phy_device *phydev)
|
||||
{
|
||||
int val = phy_read(phydev, MII_LXT973_PCR);
|
||||
|
||||
if (val & PCR_FIBER_SELECT) {
|
||||
/*
|
||||
* If fiber is selected, then the only correct setting
|
||||
* is 100Mbps, full duplex, and auto negotiation off.
|
||||
*/
|
||||
val = phy_read(phydev, MII_BMCR);
|
||||
val |= (BMCR_SPEED100 | BMCR_FULLDPLX);
|
||||
val &= ~BMCR_ANENABLE;
|
||||
phy_write(phydev, MII_BMCR, val);
|
||||
/* Remember that the port is in fiber mode. */
|
||||
phydev->priv = lxt973_probe;
|
||||
} else {
|
||||
phydev->priv = NULL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lxt973_config_aneg(struct phy_device *phydev)
|
||||
{
|
||||
/* Do nothing if port is in fiber mode. */
|
||||
return phydev->priv ? 0 : genphy_config_aneg(phydev);
|
||||
}
|
||||
|
||||
static struct phy_driver lxt97x_driver[] = {
|
||||
{
|
||||
.phy_id = 0x78100000,
|
||||
.name = "LXT970",
|
||||
.phy_id_mask = 0xfffffff0,
|
||||
.features = PHY_BASIC_FEATURES,
|
||||
.flags = PHY_HAS_INTERRUPT,
|
||||
.config_init = lxt970_config_init,
|
||||
.config_aneg = genphy_config_aneg,
|
||||
.read_status = genphy_read_status,
|
||||
.ack_interrupt = lxt970_ack_interrupt,
|
||||
.config_intr = lxt970_config_intr,
|
||||
.driver = { .owner = THIS_MODULE,},
|
||||
}, {
|
||||
.phy_id = 0x001378e0,
|
||||
.name = "LXT971",
|
||||
.phy_id_mask = 0xfffffff0,
|
||||
.features = PHY_BASIC_FEATURES,
|
||||
.flags = PHY_HAS_INTERRUPT,
|
||||
.config_aneg = genphy_config_aneg,
|
||||
.read_status = genphy_read_status,
|
||||
.ack_interrupt = lxt971_ack_interrupt,
|
||||
.config_intr = lxt971_config_intr,
|
||||
.driver = { .owner = THIS_MODULE,},
|
||||
}, {
|
||||
.phy_id = 0x00137a10,
|
||||
.name = "LXT973-A2",
|
||||
.phy_id_mask = 0xffffffff,
|
||||
.features = PHY_BASIC_FEATURES,
|
||||
.flags = 0,
|
||||
.probe = lxt973_probe,
|
||||
.config_aneg = lxt973_config_aneg,
|
||||
.read_status = lxt973a2_read_status,
|
||||
.driver = { .owner = THIS_MODULE,},
|
||||
}, {
|
||||
.phy_id = 0x00137a10,
|
||||
.name = "LXT973",
|
||||
.phy_id_mask = 0xfffffff0,
|
||||
.features = PHY_BASIC_FEATURES,
|
||||
.flags = 0,
|
||||
.probe = lxt973_probe,
|
||||
.config_aneg = lxt973_config_aneg,
|
||||
.read_status = genphy_read_status,
|
||||
.driver = { .owner = THIS_MODULE,},
|
||||
} };
|
||||
|
||||
static int __init lxt_init(void)
|
||||
{
|
||||
return phy_drivers_register(lxt97x_driver,
|
||||
ARRAY_SIZE(lxt97x_driver));
|
||||
}
|
||||
|
||||
static void __exit lxt_exit(void)
|
||||
{
|
||||
phy_drivers_unregister(lxt97x_driver,
|
||||
ARRAY_SIZE(lxt97x_driver));
|
||||
}
|
||||
|
||||
module_init(lxt_init);
|
||||
module_exit(lxt_exit);
|
||||
|
||||
static struct mdio_device_id __maybe_unused lxt_tbl[] = {
|
||||
{ 0x78100000, 0xfffffff0 },
|
||||
{ 0x001378e0, 0xfffffff0 },
|
||||
{ 0x00137a10, 0xfffffff0 },
|
||||
{ }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(mdio, lxt_tbl);
|
||||
1104
drivers/net/phy/marvell.c
Normal file
1104
drivers/net/phy/marvell.c
Normal file
File diff suppressed because it is too large
Load diff
213
drivers/net/phy/mdio-bcm-unimac.c
Normal file
213
drivers/net/phy/mdio-bcm-unimac.c
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
/*
|
||||
* Broadcom UniMAC MDIO bus controller driver
|
||||
*
|
||||
* Copyright (C) 2014, Broadcom Corporation
|
||||
*
|
||||
* 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/phy.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/delay.h>
|
||||
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/of_mdio.h>
|
||||
|
||||
#define MDIO_CMD 0x00
|
||||
#define MDIO_START_BUSY (1 << 29)
|
||||
#define MDIO_READ_FAIL (1 << 28)
|
||||
#define MDIO_RD (2 << 26)
|
||||
#define MDIO_WR (1 << 26)
|
||||
#define MDIO_PMD_SHIFT 21
|
||||
#define MDIO_PMD_MASK 0x1F
|
||||
#define MDIO_REG_SHIFT 16
|
||||
#define MDIO_REG_MASK 0x1F
|
||||
|
||||
#define MDIO_CFG 0x04
|
||||
#define MDIO_C22 (1 << 0)
|
||||
#define MDIO_C45 0
|
||||
#define MDIO_CLK_DIV_SHIFT 4
|
||||
#define MDIO_CLK_DIV_MASK 0x3F
|
||||
#define MDIO_SUPP_PREAMBLE (1 << 12)
|
||||
|
||||
struct unimac_mdio_priv {
|
||||
struct mii_bus *mii_bus;
|
||||
void __iomem *base;
|
||||
};
|
||||
|
||||
static inline void unimac_mdio_start(struct unimac_mdio_priv *priv)
|
||||
{
|
||||
u32 reg;
|
||||
|
||||
reg = __raw_readl(priv->base + MDIO_CMD);
|
||||
reg |= MDIO_START_BUSY;
|
||||
__raw_writel(reg, priv->base + MDIO_CMD);
|
||||
}
|
||||
|
||||
static inline unsigned int unimac_mdio_busy(struct unimac_mdio_priv *priv)
|
||||
{
|
||||
return __raw_readl(priv->base + MDIO_CMD) & MDIO_START_BUSY;
|
||||
}
|
||||
|
||||
static int unimac_mdio_read(struct mii_bus *bus, int phy_id, int reg)
|
||||
{
|
||||
struct unimac_mdio_priv *priv = bus->priv;
|
||||
unsigned int timeout = 1000;
|
||||
u32 cmd;
|
||||
|
||||
/* Prepare the read operation */
|
||||
cmd = MDIO_RD | (phy_id << MDIO_PMD_SHIFT) | (reg << MDIO_REG_SHIFT);
|
||||
__raw_writel(cmd, priv->base + MDIO_CMD);
|
||||
|
||||
/* Start MDIO transaction */
|
||||
unimac_mdio_start(priv);
|
||||
|
||||
do {
|
||||
if (!unimac_mdio_busy(priv))
|
||||
break;
|
||||
|
||||
usleep_range(1000, 2000);
|
||||
} while (timeout--);
|
||||
|
||||
if (!timeout)
|
||||
return -ETIMEDOUT;
|
||||
|
||||
cmd = __raw_readl(priv->base + MDIO_CMD);
|
||||
if (cmd & MDIO_READ_FAIL)
|
||||
return -EIO;
|
||||
|
||||
return cmd & 0xffff;
|
||||
}
|
||||
|
||||
static int unimac_mdio_write(struct mii_bus *bus, int phy_id,
|
||||
int reg, u16 val)
|
||||
{
|
||||
struct unimac_mdio_priv *priv = bus->priv;
|
||||
unsigned int timeout = 1000;
|
||||
u32 cmd;
|
||||
|
||||
/* Prepare the write operation */
|
||||
cmd = MDIO_WR | (phy_id << MDIO_PMD_SHIFT) |
|
||||
(reg << MDIO_REG_SHIFT) | (0xffff & val);
|
||||
__raw_writel(cmd, priv->base + MDIO_CMD);
|
||||
|
||||
unimac_mdio_start(priv);
|
||||
|
||||
do {
|
||||
if (!unimac_mdio_busy(priv))
|
||||
break;
|
||||
|
||||
usleep_range(1000, 2000);
|
||||
} while (timeout--);
|
||||
|
||||
if (!timeout)
|
||||
return -ETIMEDOUT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int unimac_mdio_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct unimac_mdio_priv *priv;
|
||||
struct device_node *np;
|
||||
struct mii_bus *bus;
|
||||
struct resource *r;
|
||||
int ret;
|
||||
|
||||
np = pdev->dev.of_node;
|
||||
|
||||
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
|
||||
/* Just ioremap, as this MDIO block is usually integrated into an
|
||||
* Ethernet MAC controller register range
|
||||
*/
|
||||
priv->base = devm_ioremap(&pdev->dev, r->start, resource_size(r));
|
||||
if (!priv->base) {
|
||||
dev_err(&pdev->dev, "failed to remap register\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
priv->mii_bus = mdiobus_alloc();
|
||||
if (!priv->mii_bus)
|
||||
return -ENOMEM;
|
||||
|
||||
bus = priv->mii_bus;
|
||||
bus->priv = priv;
|
||||
bus->name = "unimac MII bus";
|
||||
bus->parent = &pdev->dev;
|
||||
bus->read = unimac_mdio_read;
|
||||
bus->write = unimac_mdio_write;
|
||||
snprintf(bus->id, MII_BUS_ID_SIZE, "%s", pdev->name);
|
||||
|
||||
bus->irq = kcalloc(PHY_MAX_ADDR, sizeof(int), GFP_KERNEL);
|
||||
if (!bus->irq) {
|
||||
ret = -ENOMEM;
|
||||
goto out_mdio_free;
|
||||
}
|
||||
|
||||
ret = of_mdiobus_register(bus, np);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "MDIO bus registration failed\n");
|
||||
goto out_mdio_irq;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, priv);
|
||||
|
||||
dev_info(&pdev->dev, "Broadcom UniMAC MDIO bus at 0x%p\n", priv->base);
|
||||
|
||||
return 0;
|
||||
|
||||
out_mdio_irq:
|
||||
kfree(bus->irq);
|
||||
out_mdio_free:
|
||||
mdiobus_free(bus);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int unimac_mdio_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct unimac_mdio_priv *priv = platform_get_drvdata(pdev);
|
||||
|
||||
mdiobus_unregister(priv->mii_bus);
|
||||
kfree(priv->mii_bus->irq);
|
||||
mdiobus_free(priv->mii_bus);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct of_device_id unimac_mdio_ids[] = {
|
||||
{ .compatible = "brcm,genet-mdio-v4", },
|
||||
{ .compatible = "brcm,genet-mdio-v3", },
|
||||
{ .compatible = "brcm,genet-mdio-v2", },
|
||||
{ .compatible = "brcm,genet-mdio-v1", },
|
||||
{ .compatible = "brcm,unimac-mdio", },
|
||||
{ /* sentinel */ },
|
||||
};
|
||||
|
||||
static struct platform_driver unimac_mdio_driver = {
|
||||
.driver = {
|
||||
.name = "unimac-mdio",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = unimac_mdio_ids,
|
||||
},
|
||||
.probe = unimac_mdio_probe,
|
||||
.remove = unimac_mdio_remove,
|
||||
};
|
||||
module_platform_driver(unimac_mdio_driver);
|
||||
|
||||
MODULE_AUTHOR("Broadcom Corporation");
|
||||
MODULE_DESCRIPTION("Broadcom UniMAC MDIO bus controller");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:unimac-mdio");
|
||||
241
drivers/net/phy/mdio-bitbang.c
Normal file
241
drivers/net/phy/mdio-bitbang.c
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
/*
|
||||
* Bitbanged MDIO support.
|
||||
*
|
||||
* Author: Scott Wood <scottwood@freescale.com>
|
||||
* Copyright (c) 2007 Freescale Semiconductor
|
||||
*
|
||||
* Based on CPM2 MDIO code which is:
|
||||
*
|
||||
* Copyright (c) 2003 Intracom S.A.
|
||||
* by Pantelis Antoniou <panto@intracom.gr>
|
||||
*
|
||||
* 2005 (c) MontaVista Software, Inc.
|
||||
* Vitaly Bordug <vbordug@ru.mvista.com>
|
||||
*
|
||||
* This file is licensed under the terms of the GNU General Public License
|
||||
* version 2. This program is licensed "as is" without any warranty of any
|
||||
* kind, whether express or implied.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/mdio-bitbang.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/delay.h>
|
||||
|
||||
#define MDIO_READ 2
|
||||
#define MDIO_WRITE 1
|
||||
|
||||
#define MDIO_C45 (1<<15)
|
||||
#define MDIO_C45_ADDR (MDIO_C45 | 0)
|
||||
#define MDIO_C45_READ (MDIO_C45 | 3)
|
||||
#define MDIO_C45_WRITE (MDIO_C45 | 1)
|
||||
|
||||
#define MDIO_SETUP_TIME 10
|
||||
#define MDIO_HOLD_TIME 10
|
||||
|
||||
/* Minimum MDC period is 400 ns, plus some margin for error. MDIO_DELAY
|
||||
* is done twice per period.
|
||||
*/
|
||||
#define MDIO_DELAY 250
|
||||
|
||||
/* The PHY may take up to 300 ns to produce data, plus some margin
|
||||
* for error.
|
||||
*/
|
||||
#define MDIO_READ_DELAY 350
|
||||
|
||||
/* MDIO must already be configured as output. */
|
||||
static void mdiobb_send_bit(struct mdiobb_ctrl *ctrl, int val)
|
||||
{
|
||||
const struct mdiobb_ops *ops = ctrl->ops;
|
||||
|
||||
ops->set_mdio_data(ctrl, val);
|
||||
ndelay(MDIO_DELAY);
|
||||
ops->set_mdc(ctrl, 1);
|
||||
ndelay(MDIO_DELAY);
|
||||
ops->set_mdc(ctrl, 0);
|
||||
}
|
||||
|
||||
/* MDIO must already be configured as input. */
|
||||
static int mdiobb_get_bit(struct mdiobb_ctrl *ctrl)
|
||||
{
|
||||
const struct mdiobb_ops *ops = ctrl->ops;
|
||||
|
||||
ndelay(MDIO_DELAY);
|
||||
ops->set_mdc(ctrl, 1);
|
||||
ndelay(MDIO_READ_DELAY);
|
||||
ops->set_mdc(ctrl, 0);
|
||||
|
||||
return ops->get_mdio_data(ctrl);
|
||||
}
|
||||
|
||||
/* MDIO must already be configured as output. */
|
||||
static void mdiobb_send_num(struct mdiobb_ctrl *ctrl, u16 val, int bits)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = bits - 1; i >= 0; i--)
|
||||
mdiobb_send_bit(ctrl, (val >> i) & 1);
|
||||
}
|
||||
|
||||
/* MDIO must already be configured as input. */
|
||||
static u16 mdiobb_get_num(struct mdiobb_ctrl *ctrl, int bits)
|
||||
{
|
||||
int i;
|
||||
u16 ret = 0;
|
||||
|
||||
for (i = bits - 1; i >= 0; i--) {
|
||||
ret <<= 1;
|
||||
ret |= mdiobb_get_bit(ctrl);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Utility to send the preamble, address, and
|
||||
* register (common to read and write).
|
||||
*/
|
||||
static void mdiobb_cmd(struct mdiobb_ctrl *ctrl, int op, u8 phy, u8 reg)
|
||||
{
|
||||
const struct mdiobb_ops *ops = ctrl->ops;
|
||||
int i;
|
||||
|
||||
ops->set_mdio_dir(ctrl, 1);
|
||||
|
||||
/*
|
||||
* Send a 32 bit preamble ('1's) with an extra '1' bit for good
|
||||
* measure. The IEEE spec says this is a PHY optional
|
||||
* requirement. The AMD 79C874 requires one after power up and
|
||||
* one after a MII communications error. This means that we are
|
||||
* doing more preambles than we need, but it is safer and will be
|
||||
* much more robust.
|
||||
*/
|
||||
|
||||
for (i = 0; i < 32; i++)
|
||||
mdiobb_send_bit(ctrl, 1);
|
||||
|
||||
/* send the start bit (01) and the read opcode (10) or write (10).
|
||||
Clause 45 operation uses 00 for the start and 11, 10 for
|
||||
read/write */
|
||||
mdiobb_send_bit(ctrl, 0);
|
||||
if (op & MDIO_C45)
|
||||
mdiobb_send_bit(ctrl, 0);
|
||||
else
|
||||
mdiobb_send_bit(ctrl, 1);
|
||||
mdiobb_send_bit(ctrl, (op >> 1) & 1);
|
||||
mdiobb_send_bit(ctrl, (op >> 0) & 1);
|
||||
|
||||
mdiobb_send_num(ctrl, phy, 5);
|
||||
mdiobb_send_num(ctrl, reg, 5);
|
||||
}
|
||||
|
||||
/* In clause 45 mode all commands are prefixed by MDIO_ADDR to specify the
|
||||
lower 16 bits of the 21 bit address. This transfer is done identically to a
|
||||
MDIO_WRITE except for a different code. To enable clause 45 mode or
|
||||
MII_ADDR_C45 into the address. Theoretically clause 45 and normal devices
|
||||
can exist on the same bus. Normal devices should ignore the MDIO_ADDR
|
||||
phase. */
|
||||
static int mdiobb_cmd_addr(struct mdiobb_ctrl *ctrl, int phy, u32 addr)
|
||||
{
|
||||
unsigned int dev_addr = (addr >> 16) & 0x1F;
|
||||
unsigned int reg = addr & 0xFFFF;
|
||||
mdiobb_cmd(ctrl, MDIO_C45_ADDR, phy, dev_addr);
|
||||
|
||||
/* send the turnaround (10) */
|
||||
mdiobb_send_bit(ctrl, 1);
|
||||
mdiobb_send_bit(ctrl, 0);
|
||||
|
||||
mdiobb_send_num(ctrl, reg, 16);
|
||||
|
||||
ctrl->ops->set_mdio_dir(ctrl, 0);
|
||||
mdiobb_get_bit(ctrl);
|
||||
|
||||
return dev_addr;
|
||||
}
|
||||
|
||||
static int mdiobb_read(struct mii_bus *bus, int phy, int reg)
|
||||
{
|
||||
struct mdiobb_ctrl *ctrl = bus->priv;
|
||||
int ret, i;
|
||||
|
||||
if (reg & MII_ADDR_C45) {
|
||||
reg = mdiobb_cmd_addr(ctrl, phy, reg);
|
||||
mdiobb_cmd(ctrl, MDIO_C45_READ, phy, reg);
|
||||
} else
|
||||
mdiobb_cmd(ctrl, MDIO_READ, phy, reg);
|
||||
|
||||
ctrl->ops->set_mdio_dir(ctrl, 0);
|
||||
|
||||
/* check the turnaround bit: the PHY should be driving it to zero */
|
||||
if (mdiobb_get_bit(ctrl) != 0) {
|
||||
/* PHY didn't drive TA low -- flush any bits it
|
||||
* may be trying to send.
|
||||
*/
|
||||
for (i = 0; i < 32; i++)
|
||||
mdiobb_get_bit(ctrl);
|
||||
|
||||
return 0xffff;
|
||||
}
|
||||
|
||||
ret = mdiobb_get_num(ctrl, 16);
|
||||
mdiobb_get_bit(ctrl);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mdiobb_write(struct mii_bus *bus, int phy, int reg, u16 val)
|
||||
{
|
||||
struct mdiobb_ctrl *ctrl = bus->priv;
|
||||
|
||||
if (reg & MII_ADDR_C45) {
|
||||
reg = mdiobb_cmd_addr(ctrl, phy, reg);
|
||||
mdiobb_cmd(ctrl, MDIO_C45_WRITE, phy, reg);
|
||||
} else
|
||||
mdiobb_cmd(ctrl, MDIO_WRITE, phy, reg);
|
||||
|
||||
/* send the turnaround (10) */
|
||||
mdiobb_send_bit(ctrl, 1);
|
||||
mdiobb_send_bit(ctrl, 0);
|
||||
|
||||
mdiobb_send_num(ctrl, val, 16);
|
||||
|
||||
ctrl->ops->set_mdio_dir(ctrl, 0);
|
||||
mdiobb_get_bit(ctrl);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mdiobb_reset(struct mii_bus *bus)
|
||||
{
|
||||
struct mdiobb_ctrl *ctrl = bus->priv;
|
||||
if (ctrl->reset)
|
||||
ctrl->reset(bus);
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct mii_bus *alloc_mdio_bitbang(struct mdiobb_ctrl *ctrl)
|
||||
{
|
||||
struct mii_bus *bus;
|
||||
|
||||
bus = mdiobus_alloc();
|
||||
if (!bus)
|
||||
return NULL;
|
||||
|
||||
__module_get(ctrl->ops->owner);
|
||||
|
||||
bus->read = mdiobb_read;
|
||||
bus->write = mdiobb_write;
|
||||
bus->reset = mdiobb_reset;
|
||||
bus->priv = ctrl;
|
||||
|
||||
return bus;
|
||||
}
|
||||
EXPORT_SYMBOL(alloc_mdio_bitbang);
|
||||
|
||||
void free_mdio_bitbang(struct mii_bus *bus)
|
||||
{
|
||||
struct mdiobb_ctrl *ctrl = bus->priv;
|
||||
|
||||
module_put(ctrl->ops->owner);
|
||||
mdiobus_free(bus);
|
||||
}
|
||||
EXPORT_SYMBOL(free_mdio_bitbang);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
272
drivers/net/phy/mdio-gpio.c
Normal file
272
drivers/net/phy/mdio-gpio.c
Normal file
|
|
@ -0,0 +1,272 @@
|
|||
/*
|
||||
* GPIO based MDIO bitbang driver.
|
||||
* Supports OpenFirmware.
|
||||
*
|
||||
* Copyright (c) 2008 CSE Semaphore Belgium.
|
||||
* by Laurent Pinchart <laurentp@cse-semaphore.com>
|
||||
*
|
||||
* Copyright (C) 2008, Paulius Zaleckas <paulius.zaleckas@teltonika.lt>
|
||||
*
|
||||
* Based on earlier work by
|
||||
*
|
||||
* Copyright (c) 2003 Intracom S.A.
|
||||
* by Pantelis Antoniou <panto@intracom.gr>
|
||||
*
|
||||
* 2005 (c) MontaVista Software, Inc.
|
||||
* Vitaly Bordug <vbordug@ru.mvista.com>
|
||||
*
|
||||
* This file is licensed under the terms of the GNU General Public License
|
||||
* version 2. This program is licensed "as is" without any warranty of any
|
||||
* kind, whether express or implied.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/mdio-gpio.h>
|
||||
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/of_mdio.h>
|
||||
|
||||
struct mdio_gpio_info {
|
||||
struct mdiobb_ctrl ctrl;
|
||||
int mdc, mdio, mdo;
|
||||
int mdc_active_low, mdio_active_low, mdo_active_low;
|
||||
};
|
||||
|
||||
static void *mdio_gpio_of_get_data(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
struct mdio_gpio_platform_data *pdata;
|
||||
enum of_gpio_flags flags;
|
||||
int ret;
|
||||
|
||||
pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
|
||||
if (!pdata)
|
||||
return NULL;
|
||||
|
||||
ret = of_get_gpio_flags(np, 0, &flags);
|
||||
if (ret < 0)
|
||||
return NULL;
|
||||
|
||||
pdata->mdc = ret;
|
||||
pdata->mdc_active_low = flags & OF_GPIO_ACTIVE_LOW;
|
||||
|
||||
ret = of_get_gpio_flags(np, 1, &flags);
|
||||
if (ret < 0)
|
||||
return NULL;
|
||||
pdata->mdio = ret;
|
||||
pdata->mdio_active_low = flags & OF_GPIO_ACTIVE_LOW;
|
||||
|
||||
ret = of_get_gpio_flags(np, 2, &flags);
|
||||
if (ret > 0) {
|
||||
pdata->mdo = ret;
|
||||
pdata->mdo_active_low = flags & OF_GPIO_ACTIVE_LOW;
|
||||
}
|
||||
|
||||
return pdata;
|
||||
}
|
||||
|
||||
static void mdio_dir(struct mdiobb_ctrl *ctrl, int dir)
|
||||
{
|
||||
struct mdio_gpio_info *bitbang =
|
||||
container_of(ctrl, struct mdio_gpio_info, ctrl);
|
||||
|
||||
if (bitbang->mdo) {
|
||||
/* Separate output pin. Always set its value to high
|
||||
* when changing direction. If direction is input,
|
||||
* assume the pin serves as pull-up. If direction is
|
||||
* output, the default value is high.
|
||||
*/
|
||||
gpio_set_value(bitbang->mdo, 1 ^ bitbang->mdo_active_low);
|
||||
return;
|
||||
}
|
||||
|
||||
if (dir)
|
||||
gpio_direction_output(bitbang->mdio,
|
||||
1 ^ bitbang->mdio_active_low);
|
||||
else
|
||||
gpio_direction_input(bitbang->mdio);
|
||||
}
|
||||
|
||||
static int mdio_get(struct mdiobb_ctrl *ctrl)
|
||||
{
|
||||
struct mdio_gpio_info *bitbang =
|
||||
container_of(ctrl, struct mdio_gpio_info, ctrl);
|
||||
|
||||
return gpio_get_value(bitbang->mdio) ^ bitbang->mdio_active_low;
|
||||
}
|
||||
|
||||
static void mdio_set(struct mdiobb_ctrl *ctrl, int what)
|
||||
{
|
||||
struct mdio_gpio_info *bitbang =
|
||||
container_of(ctrl, struct mdio_gpio_info, ctrl);
|
||||
|
||||
if (bitbang->mdo)
|
||||
gpio_set_value(bitbang->mdo, what ^ bitbang->mdo_active_low);
|
||||
else
|
||||
gpio_set_value(bitbang->mdio, what ^ bitbang->mdio_active_low);
|
||||
}
|
||||
|
||||
static void mdc_set(struct mdiobb_ctrl *ctrl, int what)
|
||||
{
|
||||
struct mdio_gpio_info *bitbang =
|
||||
container_of(ctrl, struct mdio_gpio_info, ctrl);
|
||||
|
||||
gpio_set_value(bitbang->mdc, what ^ bitbang->mdc_active_low);
|
||||
}
|
||||
|
||||
static struct mdiobb_ops mdio_gpio_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.set_mdc = mdc_set,
|
||||
.set_mdio_dir = mdio_dir,
|
||||
.set_mdio_data = mdio_set,
|
||||
.get_mdio_data = mdio_get,
|
||||
};
|
||||
|
||||
static struct mii_bus *mdio_gpio_bus_init(struct device *dev,
|
||||
struct mdio_gpio_platform_data *pdata,
|
||||
int bus_id)
|
||||
{
|
||||
struct mii_bus *new_bus;
|
||||
struct mdio_gpio_info *bitbang;
|
||||
int i;
|
||||
|
||||
bitbang = devm_kzalloc(dev, sizeof(*bitbang), GFP_KERNEL);
|
||||
if (!bitbang)
|
||||
goto out;
|
||||
|
||||
bitbang->ctrl.ops = &mdio_gpio_ops;
|
||||
bitbang->ctrl.reset = pdata->reset;
|
||||
bitbang->mdc = pdata->mdc;
|
||||
bitbang->mdc_active_low = pdata->mdc_active_low;
|
||||
bitbang->mdio = pdata->mdio;
|
||||
bitbang->mdio_active_low = pdata->mdio_active_low;
|
||||
bitbang->mdo = pdata->mdo;
|
||||
bitbang->mdo_active_low = pdata->mdo_active_low;
|
||||
|
||||
new_bus = alloc_mdio_bitbang(&bitbang->ctrl);
|
||||
if (!new_bus)
|
||||
goto out;
|
||||
|
||||
new_bus->name = "GPIO Bitbanged MDIO",
|
||||
|
||||
new_bus->phy_mask = pdata->phy_mask;
|
||||
new_bus->irq = pdata->irqs;
|
||||
new_bus->parent = dev;
|
||||
|
||||
if (new_bus->phy_mask == ~0)
|
||||
goto out_free_bus;
|
||||
|
||||
for (i = 0; i < PHY_MAX_ADDR; i++)
|
||||
if (!new_bus->irq[i])
|
||||
new_bus->irq[i] = PHY_POLL;
|
||||
|
||||
snprintf(new_bus->id, MII_BUS_ID_SIZE, "gpio-%x", bus_id);
|
||||
|
||||
if (devm_gpio_request(dev, bitbang->mdc, "mdc"))
|
||||
goto out_free_bus;
|
||||
|
||||
if (devm_gpio_request(dev, bitbang->mdio, "mdio"))
|
||||
goto out_free_bus;
|
||||
|
||||
if (bitbang->mdo) {
|
||||
if (devm_gpio_request(dev, bitbang->mdo, "mdo"))
|
||||
goto out_free_bus;
|
||||
gpio_direction_output(bitbang->mdo, 1);
|
||||
gpio_direction_input(bitbang->mdio);
|
||||
}
|
||||
|
||||
gpio_direction_output(bitbang->mdc, 0);
|
||||
|
||||
dev_set_drvdata(dev, new_bus);
|
||||
|
||||
return new_bus;
|
||||
|
||||
out_free_bus:
|
||||
free_mdio_bitbang(new_bus);
|
||||
out:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void mdio_gpio_bus_deinit(struct device *dev)
|
||||
{
|
||||
struct mii_bus *bus = dev_get_drvdata(dev);
|
||||
|
||||
free_mdio_bitbang(bus);
|
||||
}
|
||||
|
||||
static void mdio_gpio_bus_destroy(struct device *dev)
|
||||
{
|
||||
struct mii_bus *bus = dev_get_drvdata(dev);
|
||||
|
||||
mdiobus_unregister(bus);
|
||||
mdio_gpio_bus_deinit(dev);
|
||||
}
|
||||
|
||||
static int mdio_gpio_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct mdio_gpio_platform_data *pdata;
|
||||
struct mii_bus *new_bus;
|
||||
int ret, bus_id;
|
||||
|
||||
if (pdev->dev.of_node) {
|
||||
pdata = mdio_gpio_of_get_data(pdev);
|
||||
bus_id = of_alias_get_id(pdev->dev.of_node, "mdio-gpio");
|
||||
if (bus_id < 0) {
|
||||
dev_warn(&pdev->dev, "failed to get alias id\n");
|
||||
bus_id = 0;
|
||||
}
|
||||
} else {
|
||||
pdata = dev_get_platdata(&pdev->dev);
|
||||
bus_id = pdev->id;
|
||||
}
|
||||
|
||||
if (!pdata)
|
||||
return -ENODEV;
|
||||
|
||||
new_bus = mdio_gpio_bus_init(&pdev->dev, pdata, bus_id);
|
||||
if (!new_bus)
|
||||
return -ENODEV;
|
||||
|
||||
if (pdev->dev.of_node)
|
||||
ret = of_mdiobus_register(new_bus, pdev->dev.of_node);
|
||||
else
|
||||
ret = mdiobus_register(new_bus);
|
||||
|
||||
if (ret)
|
||||
mdio_gpio_bus_deinit(&pdev->dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mdio_gpio_remove(struct platform_device *pdev)
|
||||
{
|
||||
mdio_gpio_bus_destroy(&pdev->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct of_device_id mdio_gpio_of_match[] = {
|
||||
{ .compatible = "virtual,mdio-gpio", },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
|
||||
static struct platform_driver mdio_gpio_driver = {
|
||||
.probe = mdio_gpio_probe,
|
||||
.remove = mdio_gpio_remove,
|
||||
.driver = {
|
||||
.name = "mdio-gpio",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = mdio_gpio_of_match,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(mdio_gpio_driver);
|
||||
|
||||
MODULE_ALIAS("platform:mdio-gpio");
|
||||
MODULE_AUTHOR("Laurent Pinchart, Paulius Zaleckas");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION("Generic driver for MDIO bus emulation using GPIO");
|
||||
200
drivers/net/phy/mdio-moxart.c
Normal file
200
drivers/net/phy/mdio-moxart.c
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
/* MOXA ART Ethernet (RTL8201CP) MDIO interface driver
|
||||
*
|
||||
* Copyright (C) 2013 Jonas Jensen <jonas.jensen@gmail.com>
|
||||
*
|
||||
* This file is licensed under the terms of the GNU General Public
|
||||
* License version 2. This program is licensed "as is" without any
|
||||
* warranty of any kind, whether express or implied.
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_mdio.h>
|
||||
#include <linux/phy.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
|
||||
#define REG_PHY_CTRL 0
|
||||
#define REG_PHY_WRITE_DATA 4
|
||||
|
||||
/* REG_PHY_CTRL */
|
||||
#define MIIWR BIT(27) /* init write sequence (auto cleared)*/
|
||||
#define MIIRD BIT(26)
|
||||
#define REGAD_MASK 0x3e00000
|
||||
#define PHYAD_MASK 0x1f0000
|
||||
#define MIIRDATA_MASK 0xffff
|
||||
|
||||
/* REG_PHY_WRITE_DATA */
|
||||
#define MIIWDATA_MASK 0xffff
|
||||
|
||||
struct moxart_mdio_data {
|
||||
void __iomem *base;
|
||||
};
|
||||
|
||||
static int moxart_mdio_read(struct mii_bus *bus, int mii_id, int regnum)
|
||||
{
|
||||
struct moxart_mdio_data *data = bus->priv;
|
||||
u32 ctrl = 0;
|
||||
unsigned int count = 5;
|
||||
|
||||
dev_dbg(&bus->dev, "%s\n", __func__);
|
||||
|
||||
ctrl |= MIIRD | ((mii_id << 16) & PHYAD_MASK) |
|
||||
((regnum << 21) & REGAD_MASK);
|
||||
|
||||
writel(ctrl, data->base + REG_PHY_CTRL);
|
||||
|
||||
do {
|
||||
ctrl = readl(data->base + REG_PHY_CTRL);
|
||||
|
||||
if (!(ctrl & MIIRD))
|
||||
return ctrl & MIIRDATA_MASK;
|
||||
|
||||
mdelay(10);
|
||||
count--;
|
||||
} while (count > 0);
|
||||
|
||||
dev_dbg(&bus->dev, "%s timed out\n", __func__);
|
||||
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
static int moxart_mdio_write(struct mii_bus *bus, int mii_id,
|
||||
int regnum, u16 value)
|
||||
{
|
||||
struct moxart_mdio_data *data = bus->priv;
|
||||
u32 ctrl = 0;
|
||||
unsigned int count = 5;
|
||||
|
||||
dev_dbg(&bus->dev, "%s\n", __func__);
|
||||
|
||||
ctrl |= MIIWR | ((mii_id << 16) & PHYAD_MASK) |
|
||||
((regnum << 21) & REGAD_MASK);
|
||||
|
||||
value &= MIIWDATA_MASK;
|
||||
|
||||
writel(value, data->base + REG_PHY_WRITE_DATA);
|
||||
writel(ctrl, data->base + REG_PHY_CTRL);
|
||||
|
||||
do {
|
||||
ctrl = readl(data->base + REG_PHY_CTRL);
|
||||
|
||||
if (!(ctrl & MIIWR))
|
||||
return 0;
|
||||
|
||||
mdelay(10);
|
||||
count--;
|
||||
} while (count > 0);
|
||||
|
||||
dev_dbg(&bus->dev, "%s timed out\n", __func__);
|
||||
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
static int moxart_mdio_reset(struct mii_bus *bus)
|
||||
{
|
||||
int data, i;
|
||||
|
||||
for (i = 0; i < PHY_MAX_ADDR; i++) {
|
||||
data = moxart_mdio_read(bus, i, MII_BMCR);
|
||||
if (data < 0)
|
||||
continue;
|
||||
|
||||
data |= BMCR_RESET;
|
||||
if (moxart_mdio_write(bus, i, MII_BMCR, data) < 0)
|
||||
continue;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int moxart_mdio_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
struct mii_bus *bus;
|
||||
struct moxart_mdio_data *data;
|
||||
struct resource *res;
|
||||
int ret, i;
|
||||
|
||||
bus = mdiobus_alloc_size(sizeof(*data));
|
||||
if (!bus)
|
||||
return -ENOMEM;
|
||||
|
||||
bus->name = "MOXA ART Ethernet MII";
|
||||
bus->read = &moxart_mdio_read;
|
||||
bus->write = &moxart_mdio_write;
|
||||
bus->reset = &moxart_mdio_reset;
|
||||
snprintf(bus->id, MII_BUS_ID_SIZE, "%s-%d-mii", pdev->name, pdev->id);
|
||||
bus->parent = &pdev->dev;
|
||||
|
||||
bus->irq = devm_kzalloc(&pdev->dev, sizeof(int) * PHY_MAX_ADDR,
|
||||
GFP_KERNEL);
|
||||
if (!bus->irq) {
|
||||
ret = -ENOMEM;
|
||||
goto err_out_free_mdiobus;
|
||||
}
|
||||
|
||||
/* Setting PHY_IGNORE_INTERRUPT here even if it has no effect,
|
||||
* of_mdiobus_register() sets these PHY_POLL.
|
||||
* Ideally, the interrupt from MAC controller could be used to
|
||||
* detect link state changes, not polling, i.e. if there was
|
||||
* a way phy_driver could set PHY_HAS_INTERRUPT but have that
|
||||
* interrupt handled in ethernet drivercode.
|
||||
*/
|
||||
for (i = 0; i < PHY_MAX_ADDR; i++)
|
||||
bus->irq[i] = PHY_IGNORE_INTERRUPT;
|
||||
|
||||
data = bus->priv;
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
data->base = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(data->base)) {
|
||||
ret = PTR_ERR(data->base);
|
||||
goto err_out_free_mdiobus;
|
||||
}
|
||||
|
||||
ret = of_mdiobus_register(bus, np);
|
||||
if (ret < 0)
|
||||
goto err_out_free_mdiobus;
|
||||
|
||||
platform_set_drvdata(pdev, bus);
|
||||
|
||||
return 0;
|
||||
|
||||
err_out_free_mdiobus:
|
||||
mdiobus_free(bus);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int moxart_mdio_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct mii_bus *bus = platform_get_drvdata(pdev);
|
||||
|
||||
mdiobus_unregister(bus);
|
||||
mdiobus_free(bus);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id moxart_mdio_dt_ids[] = {
|
||||
{ .compatible = "moxa,moxart-mdio" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, moxart_mdio_dt_ids);
|
||||
|
||||
static struct platform_driver moxart_mdio_driver = {
|
||||
.probe = moxart_mdio_probe,
|
||||
.remove = moxart_mdio_remove,
|
||||
.driver = {
|
||||
.name = "moxart-mdio",
|
||||
.of_match_table = moxart_mdio_dt_ids,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(moxart_mdio_driver);
|
||||
|
||||
MODULE_DESCRIPTION("MOXA ART MDIO interface driver");
|
||||
MODULE_AUTHOR("Jonas Jensen <jonas.jensen@gmail.com>");
|
||||
MODULE_LICENSE("GPL");
|
||||
140
drivers/net/phy/mdio-mux-gpio.c
Normal file
140
drivers/net/phy/mdio-mux-gpio.c
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* This file is subject to the terms and conditions of the GNU General Public
|
||||
* License. See the file "COPYING" in the main directory of this archive
|
||||
* for more details.
|
||||
*
|
||||
* Copyright (C) 2011, 2012 Cavium, Inc.
|
||||
*/
|
||||
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/of_mdio.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/phy.h>
|
||||
#include <linux/mdio-mux.h>
|
||||
#include <linux/of_gpio.h>
|
||||
|
||||
#define DRV_VERSION "1.0"
|
||||
#define DRV_DESCRIPTION "GPIO controlled MDIO bus multiplexer driver"
|
||||
|
||||
#define MDIO_MUX_GPIO_MAX_BITS 8
|
||||
|
||||
struct mdio_mux_gpio_state {
|
||||
int gpio[MDIO_MUX_GPIO_MAX_BITS];
|
||||
unsigned int num_gpios;
|
||||
void *mux_handle;
|
||||
};
|
||||
|
||||
static int mdio_mux_gpio_switch_fn(int current_child, int desired_child,
|
||||
void *data)
|
||||
{
|
||||
int change;
|
||||
unsigned int n;
|
||||
struct mdio_mux_gpio_state *s = data;
|
||||
|
||||
if (current_child == desired_child)
|
||||
return 0;
|
||||
|
||||
change = current_child == -1 ? -1 : current_child ^ desired_child;
|
||||
|
||||
for (n = 0; n < s->num_gpios; n++) {
|
||||
if (change & 1)
|
||||
gpio_set_value_cansleep(s->gpio[n],
|
||||
(desired_child & 1) != 0);
|
||||
change >>= 1;
|
||||
desired_child >>= 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mdio_mux_gpio_probe(struct platform_device *pdev)
|
||||
{
|
||||
enum of_gpio_flags f;
|
||||
struct mdio_mux_gpio_state *s;
|
||||
int num_gpios;
|
||||
unsigned int n;
|
||||
int r;
|
||||
|
||||
if (!pdev->dev.of_node)
|
||||
return -ENODEV;
|
||||
|
||||
num_gpios = of_gpio_count(pdev->dev.of_node);
|
||||
if (num_gpios <= 0 || num_gpios > MDIO_MUX_GPIO_MAX_BITS)
|
||||
return -ENODEV;
|
||||
|
||||
s = devm_kzalloc(&pdev->dev, sizeof(*s), GFP_KERNEL);
|
||||
if (!s)
|
||||
return -ENOMEM;
|
||||
|
||||
s->num_gpios = num_gpios;
|
||||
|
||||
for (n = 0; n < num_gpios; ) {
|
||||
int gpio = of_get_gpio_flags(pdev->dev.of_node, n, &f);
|
||||
if (gpio < 0) {
|
||||
r = (gpio == -ENODEV) ? -EPROBE_DEFER : gpio;
|
||||
goto err;
|
||||
}
|
||||
s->gpio[n] = gpio;
|
||||
|
||||
n++;
|
||||
|
||||
r = gpio_request(gpio, "mdio_mux_gpio");
|
||||
if (r)
|
||||
goto err;
|
||||
|
||||
r = gpio_direction_output(gpio, 0);
|
||||
if (r)
|
||||
goto err;
|
||||
}
|
||||
|
||||
r = mdio_mux_init(&pdev->dev,
|
||||
mdio_mux_gpio_switch_fn, &s->mux_handle, s);
|
||||
|
||||
if (r == 0) {
|
||||
pdev->dev.platform_data = s;
|
||||
return 0;
|
||||
}
|
||||
err:
|
||||
while (n) {
|
||||
n--;
|
||||
gpio_free(s->gpio[n]);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
static int mdio_mux_gpio_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct mdio_mux_gpio_state *s = dev_get_platdata(&pdev->dev);
|
||||
mdio_mux_uninit(s->mux_handle);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct of_device_id mdio_mux_gpio_match[] = {
|
||||
{
|
||||
.compatible = "mdio-mux-gpio",
|
||||
},
|
||||
{
|
||||
/* Legacy compatible property. */
|
||||
.compatible = "cavium,mdio-mux-sn74cbtlv3253",
|
||||
},
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, mdio_mux_gpio_match);
|
||||
|
||||
static struct platform_driver mdio_mux_gpio_driver = {
|
||||
.driver = {
|
||||
.name = "mdio-mux-gpio",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = mdio_mux_gpio_match,
|
||||
},
|
||||
.probe = mdio_mux_gpio_probe,
|
||||
.remove = mdio_mux_gpio_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(mdio_mux_gpio_driver);
|
||||
|
||||
MODULE_DESCRIPTION(DRV_DESCRIPTION);
|
||||
MODULE_VERSION(DRV_VERSION);
|
||||
MODULE_AUTHOR("David Daney");
|
||||
MODULE_LICENSE("GPL");
|
||||
170
drivers/net/phy/mdio-mux-mmioreg.c
Normal file
170
drivers/net/phy/mdio-mux-mmioreg.c
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
/*
|
||||
* Simple memory-mapped device MDIO MUX driver
|
||||
*
|
||||
* Author: Timur Tabi <timur@freescale.com>
|
||||
*
|
||||
* Copyright 2012 Freescale Semiconductor, Inc.
|
||||
*
|
||||
* This file is licensed under the terms of the GNU General Public License
|
||||
* version 2. This program is licensed "as is" without any warranty of any
|
||||
* kind, whether express or implied.
|
||||
*/
|
||||
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_mdio.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/phy.h>
|
||||
#include <linux/mdio-mux.h>
|
||||
|
||||
struct mdio_mux_mmioreg_state {
|
||||
void *mux_handle;
|
||||
phys_addr_t phys;
|
||||
uint8_t mask;
|
||||
};
|
||||
|
||||
/*
|
||||
* MDIO multiplexing switch function
|
||||
*
|
||||
* This function is called by the mdio-mux layer when it thinks the mdio bus
|
||||
* multiplexer needs to switch.
|
||||
*
|
||||
* 'current_child' is the current value of the mux register (masked via
|
||||
* s->mask).
|
||||
*
|
||||
* 'desired_child' is the value of the 'reg' property of the target child MDIO
|
||||
* node.
|
||||
*
|
||||
* The first time this function is called, current_child == -1.
|
||||
*
|
||||
* If current_child == desired_child, then the mux is already set to the
|
||||
* correct bus.
|
||||
*/
|
||||
static int mdio_mux_mmioreg_switch_fn(int current_child, int desired_child,
|
||||
void *data)
|
||||
{
|
||||
struct mdio_mux_mmioreg_state *s = data;
|
||||
|
||||
if (current_child ^ desired_child) {
|
||||
void __iomem *p = ioremap(s->phys, 1);
|
||||
uint8_t x, y;
|
||||
|
||||
if (!p)
|
||||
return -ENOMEM;
|
||||
|
||||
x = ioread8(p);
|
||||
y = (x & ~s->mask) | desired_child;
|
||||
if (x != y) {
|
||||
iowrite8((x & ~s->mask) | desired_child, p);
|
||||
pr_debug("%s: %02x -> %02x\n", __func__, x, y);
|
||||
}
|
||||
|
||||
iounmap(p);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mdio_mux_mmioreg_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *np2, *np = pdev->dev.of_node;
|
||||
struct mdio_mux_mmioreg_state *s;
|
||||
struct resource res;
|
||||
const __be32 *iprop;
|
||||
int len, ret;
|
||||
|
||||
dev_dbg(&pdev->dev, "probing node %s\n", np->full_name);
|
||||
|
||||
s = devm_kzalloc(&pdev->dev, sizeof(*s), GFP_KERNEL);
|
||||
if (!s)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = of_address_to_resource(np, 0, &res);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "could not obtain memory map for node %s\n",
|
||||
np->full_name);
|
||||
return ret;
|
||||
}
|
||||
s->phys = res.start;
|
||||
|
||||
if (resource_size(&res) != sizeof(uint8_t)) {
|
||||
dev_err(&pdev->dev, "only 8-bit registers are supported\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
iprop = of_get_property(np, "mux-mask", &len);
|
||||
if (!iprop || len != sizeof(uint32_t)) {
|
||||
dev_err(&pdev->dev, "missing or invalid mux-mask property\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
if (be32_to_cpup(iprop) > 255) {
|
||||
dev_err(&pdev->dev, "only 8-bit registers are supported\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
s->mask = be32_to_cpup(iprop);
|
||||
|
||||
/*
|
||||
* Verify that the 'reg' property of each child MDIO bus does not
|
||||
* set any bits outside of the 'mask'.
|
||||
*/
|
||||
for_each_available_child_of_node(np, np2) {
|
||||
iprop = of_get_property(np2, "reg", &len);
|
||||
if (!iprop || len != sizeof(uint32_t)) {
|
||||
dev_err(&pdev->dev, "mdio-mux child node %s is "
|
||||
"missing a 'reg' property\n", np2->full_name);
|
||||
return -ENODEV;
|
||||
}
|
||||
if (be32_to_cpup(iprop) & ~s->mask) {
|
||||
dev_err(&pdev->dev, "mdio-mux child node %s has "
|
||||
"a 'reg' value with unmasked bits\n",
|
||||
np2->full_name);
|
||||
return -ENODEV;
|
||||
}
|
||||
}
|
||||
|
||||
ret = mdio_mux_init(&pdev->dev, mdio_mux_mmioreg_switch_fn,
|
||||
&s->mux_handle, s);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to register mdio-mux bus %s\n",
|
||||
np->full_name);
|
||||
return ret;
|
||||
}
|
||||
|
||||
pdev->dev.platform_data = s;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mdio_mux_mmioreg_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct mdio_mux_mmioreg_state *s = dev_get_platdata(&pdev->dev);
|
||||
|
||||
mdio_mux_uninit(s->mux_handle);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct of_device_id mdio_mux_mmioreg_match[] = {
|
||||
{
|
||||
.compatible = "mdio-mux-mmioreg",
|
||||
},
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, mdio_mux_mmioreg_match);
|
||||
|
||||
static struct platform_driver mdio_mux_mmioreg_driver = {
|
||||
.driver = {
|
||||
.name = "mdio-mux-mmioreg",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = mdio_mux_mmioreg_match,
|
||||
},
|
||||
.probe = mdio_mux_mmioreg_probe,
|
||||
.remove = mdio_mux_mmioreg_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(mdio_mux_mmioreg_driver);
|
||||
|
||||
MODULE_AUTHOR("Timur Tabi <timur@freescale.com>");
|
||||
MODULE_DESCRIPTION("Memory-mapped device MDIO MUX driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
198
drivers/net/phy/mdio-mux.c
Normal file
198
drivers/net/phy/mdio-mux.c
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
/*
|
||||
* This file is subject to the terms and conditions of the GNU General Public
|
||||
* License. See the file "COPYING" in the main directory of this archive
|
||||
* for more details.
|
||||
*
|
||||
* Copyright (C) 2011, 2012 Cavium, Inc.
|
||||
*/
|
||||
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/mdio-mux.h>
|
||||
#include <linux/of_mdio.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/phy.h>
|
||||
|
||||
#define DRV_VERSION "1.0"
|
||||
#define DRV_DESCRIPTION "MDIO bus multiplexer driver"
|
||||
|
||||
struct mdio_mux_child_bus;
|
||||
|
||||
struct mdio_mux_parent_bus {
|
||||
struct mii_bus *mii_bus;
|
||||
int current_child;
|
||||
int parent_id;
|
||||
void *switch_data;
|
||||
int (*switch_fn)(int current_child, int desired_child, void *data);
|
||||
|
||||
/* List of our children linked through their next fields. */
|
||||
struct mdio_mux_child_bus *children;
|
||||
};
|
||||
|
||||
struct mdio_mux_child_bus {
|
||||
struct mii_bus *mii_bus;
|
||||
struct mdio_mux_parent_bus *parent;
|
||||
struct mdio_mux_child_bus *next;
|
||||
int bus_number;
|
||||
int phy_irq[PHY_MAX_ADDR];
|
||||
};
|
||||
|
||||
/*
|
||||
* The parent bus' lock is used to order access to the switch_fn.
|
||||
*/
|
||||
static int mdio_mux_read(struct mii_bus *bus, int phy_id, int regnum)
|
||||
{
|
||||
struct mdio_mux_child_bus *cb = bus->priv;
|
||||
struct mdio_mux_parent_bus *pb = cb->parent;
|
||||
int r;
|
||||
|
||||
/* In theory multiple mdio_mux could be stacked, thus creating
|
||||
* more than a single level of nesting. But in practice,
|
||||
* SINGLE_DEPTH_NESTING will cover the vast majority of use
|
||||
* cases. We use it, instead of trying to handle the general
|
||||
* case.
|
||||
*/
|
||||
mutex_lock_nested(&pb->mii_bus->mdio_lock, SINGLE_DEPTH_NESTING);
|
||||
r = pb->switch_fn(pb->current_child, cb->bus_number, pb->switch_data);
|
||||
if (r)
|
||||
goto out;
|
||||
|
||||
pb->current_child = cb->bus_number;
|
||||
|
||||
r = pb->mii_bus->read(pb->mii_bus, phy_id, regnum);
|
||||
out:
|
||||
mutex_unlock(&pb->mii_bus->mdio_lock);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
/*
|
||||
* The parent bus' lock is used to order access to the switch_fn.
|
||||
*/
|
||||
static int mdio_mux_write(struct mii_bus *bus, int phy_id,
|
||||
int regnum, u16 val)
|
||||
{
|
||||
struct mdio_mux_child_bus *cb = bus->priv;
|
||||
struct mdio_mux_parent_bus *pb = cb->parent;
|
||||
|
||||
int r;
|
||||
|
||||
mutex_lock_nested(&pb->mii_bus->mdio_lock, SINGLE_DEPTH_NESTING);
|
||||
r = pb->switch_fn(pb->current_child, cb->bus_number, pb->switch_data);
|
||||
if (r)
|
||||
goto out;
|
||||
|
||||
pb->current_child = cb->bus_number;
|
||||
|
||||
r = pb->mii_bus->write(pb->mii_bus, phy_id, regnum, val);
|
||||
out:
|
||||
mutex_unlock(&pb->mii_bus->mdio_lock);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
static int parent_count;
|
||||
|
||||
int mdio_mux_init(struct device *dev,
|
||||
int (*switch_fn)(int cur, int desired, void *data),
|
||||
void **mux_handle,
|
||||
void *data)
|
||||
{
|
||||
struct device_node *parent_bus_node;
|
||||
struct device_node *child_bus_node;
|
||||
int r, ret_val;
|
||||
struct mii_bus *parent_bus;
|
||||
struct mdio_mux_parent_bus *pb;
|
||||
struct mdio_mux_child_bus *cb;
|
||||
|
||||
if (!dev->of_node)
|
||||
return -ENODEV;
|
||||
|
||||
parent_bus_node = of_parse_phandle(dev->of_node, "mdio-parent-bus", 0);
|
||||
|
||||
if (!parent_bus_node)
|
||||
return -ENODEV;
|
||||
|
||||
parent_bus = of_mdio_find_bus(parent_bus_node);
|
||||
if (parent_bus == NULL) {
|
||||
ret_val = -EPROBE_DEFER;
|
||||
goto err_parent_bus;
|
||||
}
|
||||
|
||||
pb = devm_kzalloc(dev, sizeof(*pb), GFP_KERNEL);
|
||||
if (pb == NULL) {
|
||||
ret_val = -ENOMEM;
|
||||
goto err_parent_bus;
|
||||
}
|
||||
|
||||
pb->switch_data = data;
|
||||
pb->switch_fn = switch_fn;
|
||||
pb->current_child = -1;
|
||||
pb->parent_id = parent_count++;
|
||||
pb->mii_bus = parent_bus;
|
||||
|
||||
ret_val = -ENODEV;
|
||||
for_each_available_child_of_node(dev->of_node, child_bus_node) {
|
||||
u32 v;
|
||||
|
||||
r = of_property_read_u32(child_bus_node, "reg", &v);
|
||||
if (r)
|
||||
continue;
|
||||
|
||||
cb = devm_kzalloc(dev, sizeof(*cb), GFP_KERNEL);
|
||||
if (cb == NULL) {
|
||||
dev_err(dev,
|
||||
"Error: Failed to allocate memory for child\n");
|
||||
ret_val = -ENOMEM;
|
||||
break;
|
||||
}
|
||||
cb->bus_number = v;
|
||||
cb->parent = pb;
|
||||
cb->mii_bus = mdiobus_alloc();
|
||||
cb->mii_bus->priv = cb;
|
||||
|
||||
cb->mii_bus->irq = cb->phy_irq;
|
||||
cb->mii_bus->name = "mdio_mux";
|
||||
snprintf(cb->mii_bus->id, MII_BUS_ID_SIZE, "%x.%x",
|
||||
pb->parent_id, v);
|
||||
cb->mii_bus->parent = dev;
|
||||
cb->mii_bus->read = mdio_mux_read;
|
||||
cb->mii_bus->write = mdio_mux_write;
|
||||
r = of_mdiobus_register(cb->mii_bus, child_bus_node);
|
||||
if (r) {
|
||||
mdiobus_free(cb->mii_bus);
|
||||
devm_kfree(dev, cb);
|
||||
} else {
|
||||
of_node_get(child_bus_node);
|
||||
cb->next = pb->children;
|
||||
pb->children = cb;
|
||||
}
|
||||
}
|
||||
if (pb->children) {
|
||||
*mux_handle = pb;
|
||||
dev_info(dev, "Version " DRV_VERSION "\n");
|
||||
return 0;
|
||||
}
|
||||
err_parent_bus:
|
||||
of_node_put(parent_bus_node);
|
||||
return ret_val;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mdio_mux_init);
|
||||
|
||||
void mdio_mux_uninit(void *mux_handle)
|
||||
{
|
||||
struct mdio_mux_parent_bus *pb = mux_handle;
|
||||
struct mdio_mux_child_bus *cb = pb->children;
|
||||
|
||||
while (cb) {
|
||||
mdiobus_unregister(cb->mii_bus);
|
||||
mdiobus_free(cb->mii_bus);
|
||||
cb = cb->next;
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mdio_mux_uninit);
|
||||
|
||||
MODULE_DESCRIPTION(DRV_DESCRIPTION);
|
||||
MODULE_VERSION(DRV_VERSION);
|
||||
MODULE_AUTHOR("David Daney");
|
||||
MODULE_LICENSE("GPL");
|
||||
284
drivers/net/phy/mdio-octeon.c
Normal file
284
drivers/net/phy/mdio-octeon.c
Normal file
|
|
@ -0,0 +1,284 @@
|
|||
/*
|
||||
* This file is subject to the terms and conditions of the GNU General Public
|
||||
* License. See the file "COPYING" in the main directory of this archive
|
||||
* for more details.
|
||||
*
|
||||
* Copyright (C) 2009-2012 Cavium, Inc.
|
||||
*/
|
||||
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/of_mdio.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/gfp.h>
|
||||
#include <linux/phy.h>
|
||||
#include <linux/io.h>
|
||||
|
||||
#include <asm/octeon/octeon.h>
|
||||
#include <asm/octeon/cvmx-smix-defs.h>
|
||||
|
||||
#define DRV_VERSION "1.0"
|
||||
#define DRV_DESCRIPTION "Cavium Networks Octeon SMI/MDIO driver"
|
||||
|
||||
#define SMI_CMD 0x0
|
||||
#define SMI_WR_DAT 0x8
|
||||
#define SMI_RD_DAT 0x10
|
||||
#define SMI_CLK 0x18
|
||||
#define SMI_EN 0x20
|
||||
|
||||
enum octeon_mdiobus_mode {
|
||||
UNINIT = 0,
|
||||
C22,
|
||||
C45
|
||||
};
|
||||
|
||||
struct octeon_mdiobus {
|
||||
struct mii_bus *mii_bus;
|
||||
u64 register_base;
|
||||
resource_size_t mdio_phys;
|
||||
resource_size_t regsize;
|
||||
enum octeon_mdiobus_mode mode;
|
||||
int phy_irq[PHY_MAX_ADDR];
|
||||
};
|
||||
|
||||
static void octeon_mdiobus_set_mode(struct octeon_mdiobus *p,
|
||||
enum octeon_mdiobus_mode m)
|
||||
{
|
||||
union cvmx_smix_clk smi_clk;
|
||||
|
||||
if (m == p->mode)
|
||||
return;
|
||||
|
||||
smi_clk.u64 = cvmx_read_csr(p->register_base + SMI_CLK);
|
||||
smi_clk.s.mode = (m == C45) ? 1 : 0;
|
||||
smi_clk.s.preamble = 1;
|
||||
cvmx_write_csr(p->register_base + SMI_CLK, smi_clk.u64);
|
||||
p->mode = m;
|
||||
}
|
||||
|
||||
static int octeon_mdiobus_c45_addr(struct octeon_mdiobus *p,
|
||||
int phy_id, int regnum)
|
||||
{
|
||||
union cvmx_smix_cmd smi_cmd;
|
||||
union cvmx_smix_wr_dat smi_wr;
|
||||
int timeout = 1000;
|
||||
|
||||
octeon_mdiobus_set_mode(p, C45);
|
||||
|
||||
smi_wr.u64 = 0;
|
||||
smi_wr.s.dat = regnum & 0xffff;
|
||||
cvmx_write_csr(p->register_base + SMI_WR_DAT, smi_wr.u64);
|
||||
|
||||
regnum = (regnum >> 16) & 0x1f;
|
||||
|
||||
smi_cmd.u64 = 0;
|
||||
smi_cmd.s.phy_op = 0; /* MDIO_CLAUSE_45_ADDRESS */
|
||||
smi_cmd.s.phy_adr = phy_id;
|
||||
smi_cmd.s.reg_adr = regnum;
|
||||
cvmx_write_csr(p->register_base + SMI_CMD, smi_cmd.u64);
|
||||
|
||||
do {
|
||||
/* Wait 1000 clocks so we don't saturate the RSL bus
|
||||
* doing reads.
|
||||
*/
|
||||
__delay(1000);
|
||||
smi_wr.u64 = cvmx_read_csr(p->register_base + SMI_WR_DAT);
|
||||
} while (smi_wr.s.pending && --timeout);
|
||||
|
||||
if (timeout <= 0)
|
||||
return -EIO;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int octeon_mdiobus_read(struct mii_bus *bus, int phy_id, int regnum)
|
||||
{
|
||||
struct octeon_mdiobus *p = bus->priv;
|
||||
union cvmx_smix_cmd smi_cmd;
|
||||
union cvmx_smix_rd_dat smi_rd;
|
||||
unsigned int op = 1; /* MDIO_CLAUSE_22_READ */
|
||||
int timeout = 1000;
|
||||
|
||||
if (regnum & MII_ADDR_C45) {
|
||||
int r = octeon_mdiobus_c45_addr(p, phy_id, regnum);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
regnum = (regnum >> 16) & 0x1f;
|
||||
op = 3; /* MDIO_CLAUSE_45_READ */
|
||||
} else {
|
||||
octeon_mdiobus_set_mode(p, C22);
|
||||
}
|
||||
|
||||
|
||||
smi_cmd.u64 = 0;
|
||||
smi_cmd.s.phy_op = op;
|
||||
smi_cmd.s.phy_adr = phy_id;
|
||||
smi_cmd.s.reg_adr = regnum;
|
||||
cvmx_write_csr(p->register_base + SMI_CMD, smi_cmd.u64);
|
||||
|
||||
do {
|
||||
/* Wait 1000 clocks so we don't saturate the RSL bus
|
||||
* doing reads.
|
||||
*/
|
||||
__delay(1000);
|
||||
smi_rd.u64 = cvmx_read_csr(p->register_base + SMI_RD_DAT);
|
||||
} while (smi_rd.s.pending && --timeout);
|
||||
|
||||
if (smi_rd.s.val)
|
||||
return smi_rd.s.dat;
|
||||
else
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
static int octeon_mdiobus_write(struct mii_bus *bus, int phy_id,
|
||||
int regnum, u16 val)
|
||||
{
|
||||
struct octeon_mdiobus *p = bus->priv;
|
||||
union cvmx_smix_cmd smi_cmd;
|
||||
union cvmx_smix_wr_dat smi_wr;
|
||||
unsigned int op = 0; /* MDIO_CLAUSE_22_WRITE */
|
||||
int timeout = 1000;
|
||||
|
||||
|
||||
if (regnum & MII_ADDR_C45) {
|
||||
int r = octeon_mdiobus_c45_addr(p, phy_id, regnum);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
regnum = (regnum >> 16) & 0x1f;
|
||||
op = 1; /* MDIO_CLAUSE_45_WRITE */
|
||||
} else {
|
||||
octeon_mdiobus_set_mode(p, C22);
|
||||
}
|
||||
|
||||
smi_wr.u64 = 0;
|
||||
smi_wr.s.dat = val;
|
||||
cvmx_write_csr(p->register_base + SMI_WR_DAT, smi_wr.u64);
|
||||
|
||||
smi_cmd.u64 = 0;
|
||||
smi_cmd.s.phy_op = op;
|
||||
smi_cmd.s.phy_adr = phy_id;
|
||||
smi_cmd.s.reg_adr = regnum;
|
||||
cvmx_write_csr(p->register_base + SMI_CMD, smi_cmd.u64);
|
||||
|
||||
do {
|
||||
/* Wait 1000 clocks so we don't saturate the RSL bus
|
||||
* doing reads.
|
||||
*/
|
||||
__delay(1000);
|
||||
smi_wr.u64 = cvmx_read_csr(p->register_base + SMI_WR_DAT);
|
||||
} while (smi_wr.s.pending && --timeout);
|
||||
|
||||
if (timeout <= 0)
|
||||
return -EIO;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int octeon_mdiobus_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct octeon_mdiobus *bus;
|
||||
struct resource *res_mem;
|
||||
union cvmx_smix_en smi_en;
|
||||
int err = -ENOENT;
|
||||
|
||||
bus = devm_kzalloc(&pdev->dev, sizeof(*bus), GFP_KERNEL);
|
||||
if (!bus)
|
||||
return -ENOMEM;
|
||||
|
||||
res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
|
||||
if (res_mem == NULL) {
|
||||
dev_err(&pdev->dev, "found no memory resource\n");
|
||||
err = -ENXIO;
|
||||
goto fail;
|
||||
}
|
||||
bus->mdio_phys = res_mem->start;
|
||||
bus->regsize = resource_size(res_mem);
|
||||
if (!devm_request_mem_region(&pdev->dev, bus->mdio_phys, bus->regsize,
|
||||
res_mem->name)) {
|
||||
dev_err(&pdev->dev, "request_mem_region failed\n");
|
||||
goto fail;
|
||||
}
|
||||
bus->register_base =
|
||||
(u64)devm_ioremap(&pdev->dev, bus->mdio_phys, bus->regsize);
|
||||
|
||||
bus->mii_bus = mdiobus_alloc();
|
||||
|
||||
if (!bus->mii_bus)
|
||||
goto fail;
|
||||
|
||||
smi_en.u64 = 0;
|
||||
smi_en.s.en = 1;
|
||||
cvmx_write_csr(bus->register_base + SMI_EN, smi_en.u64);
|
||||
|
||||
bus->mii_bus->priv = bus;
|
||||
bus->mii_bus->irq = bus->phy_irq;
|
||||
bus->mii_bus->name = "mdio-octeon";
|
||||
snprintf(bus->mii_bus->id, MII_BUS_ID_SIZE, "%llx", bus->register_base);
|
||||
bus->mii_bus->parent = &pdev->dev;
|
||||
|
||||
bus->mii_bus->read = octeon_mdiobus_read;
|
||||
bus->mii_bus->write = octeon_mdiobus_write;
|
||||
|
||||
platform_set_drvdata(pdev, bus);
|
||||
|
||||
err = of_mdiobus_register(bus->mii_bus, pdev->dev.of_node);
|
||||
if (err)
|
||||
goto fail_register;
|
||||
|
||||
dev_info(&pdev->dev, "Version " DRV_VERSION "\n");
|
||||
|
||||
return 0;
|
||||
fail_register:
|
||||
mdiobus_free(bus->mii_bus);
|
||||
fail:
|
||||
smi_en.u64 = 0;
|
||||
cvmx_write_csr(bus->register_base + SMI_EN, smi_en.u64);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int octeon_mdiobus_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct octeon_mdiobus *bus;
|
||||
union cvmx_smix_en smi_en;
|
||||
|
||||
bus = platform_get_drvdata(pdev);
|
||||
|
||||
mdiobus_unregister(bus->mii_bus);
|
||||
mdiobus_free(bus->mii_bus);
|
||||
smi_en.u64 = 0;
|
||||
cvmx_write_csr(bus->register_base + SMI_EN, smi_en.u64);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct of_device_id octeon_mdiobus_match[] = {
|
||||
{
|
||||
.compatible = "cavium,octeon-3860-mdio",
|
||||
},
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, octeon_mdiobus_match);
|
||||
|
||||
static struct platform_driver octeon_mdiobus_driver = {
|
||||
.driver = {
|
||||
.name = "mdio-octeon",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = octeon_mdiobus_match,
|
||||
},
|
||||
.probe = octeon_mdiobus_probe,
|
||||
.remove = octeon_mdiobus_remove,
|
||||
};
|
||||
|
||||
void octeon_mdiobus_force_mod_depencency(void)
|
||||
{
|
||||
/* Let ethernet drivers force us to be loaded. */
|
||||
}
|
||||
EXPORT_SYMBOL(octeon_mdiobus_force_mod_depencency);
|
||||
|
||||
module_platform_driver(octeon_mdiobus_driver);
|
||||
|
||||
MODULE_DESCRIPTION(DRV_DESCRIPTION);
|
||||
MODULE_VERSION(DRV_VERSION);
|
||||
MODULE_AUTHOR("David Daney");
|
||||
MODULE_LICENSE("GPL");
|
||||
188
drivers/net/phy/mdio-sun4i.c
Normal file
188
drivers/net/phy/mdio-sun4i.c
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
/*
|
||||
* Allwinner EMAC MDIO interface driver
|
||||
*
|
||||
* Copyright 2012-2013 Stefan Roese <sr@denx.de>
|
||||
* Copyright 2013 Maxime Ripard <maxime.ripard@free-electrons.com>
|
||||
*
|
||||
* Based on the Linux driver provided by Allwinner:
|
||||
* Copyright (C) 1997 Sten Wang
|
||||
*
|
||||
* This file is licensed under the terms of the GNU General Public
|
||||
* License version 2. This program is licensed "as is" without any
|
||||
* warranty of any kind, whether express or implied.
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_mdio.h>
|
||||
#include <linux/phy.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
|
||||
#define EMAC_MAC_MCMD_REG (0x00)
|
||||
#define EMAC_MAC_MADR_REG (0x04)
|
||||
#define EMAC_MAC_MWTD_REG (0x08)
|
||||
#define EMAC_MAC_MRDD_REG (0x0c)
|
||||
#define EMAC_MAC_MIND_REG (0x10)
|
||||
#define EMAC_MAC_SSRR_REG (0x14)
|
||||
|
||||
#define MDIO_TIMEOUT (msecs_to_jiffies(100))
|
||||
|
||||
struct sun4i_mdio_data {
|
||||
void __iomem *membase;
|
||||
struct regulator *regulator;
|
||||
};
|
||||
|
||||
static int sun4i_mdio_read(struct mii_bus *bus, int mii_id, int regnum)
|
||||
{
|
||||
struct sun4i_mdio_data *data = bus->priv;
|
||||
unsigned long timeout_jiffies;
|
||||
int value;
|
||||
|
||||
/* issue the phy address and reg */
|
||||
writel((mii_id << 8) | regnum, data->membase + EMAC_MAC_MADR_REG);
|
||||
/* pull up the phy io line */
|
||||
writel(0x1, data->membase + EMAC_MAC_MCMD_REG);
|
||||
|
||||
/* Wait read complete */
|
||||
timeout_jiffies = jiffies + MDIO_TIMEOUT;
|
||||
while (readl(data->membase + EMAC_MAC_MIND_REG) & 0x1) {
|
||||
if (time_is_before_jiffies(timeout_jiffies))
|
||||
return -ETIMEDOUT;
|
||||
msleep(1);
|
||||
}
|
||||
|
||||
/* push down the phy io line */
|
||||
writel(0x0, data->membase + EMAC_MAC_MCMD_REG);
|
||||
/* and read data */
|
||||
value = readl(data->membase + EMAC_MAC_MRDD_REG);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
static int sun4i_mdio_write(struct mii_bus *bus, int mii_id, int regnum,
|
||||
u16 value)
|
||||
{
|
||||
struct sun4i_mdio_data *data = bus->priv;
|
||||
unsigned long timeout_jiffies;
|
||||
|
||||
/* issue the phy address and reg */
|
||||
writel((mii_id << 8) | regnum, data->membase + EMAC_MAC_MADR_REG);
|
||||
/* pull up the phy io line */
|
||||
writel(0x1, data->membase + EMAC_MAC_MCMD_REG);
|
||||
|
||||
/* Wait read complete */
|
||||
timeout_jiffies = jiffies + MDIO_TIMEOUT;
|
||||
while (readl(data->membase + EMAC_MAC_MIND_REG) & 0x1) {
|
||||
if (time_is_before_jiffies(timeout_jiffies))
|
||||
return -ETIMEDOUT;
|
||||
msleep(1);
|
||||
}
|
||||
|
||||
/* push down the phy io line */
|
||||
writel(0x0, data->membase + EMAC_MAC_MCMD_REG);
|
||||
/* and write data */
|
||||
writel(value, data->membase + EMAC_MAC_MWTD_REG);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sun4i_mdio_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
struct mii_bus *bus;
|
||||
struct sun4i_mdio_data *data;
|
||||
struct resource *res;
|
||||
int ret, i;
|
||||
|
||||
bus = mdiobus_alloc_size(sizeof(*data));
|
||||
if (!bus)
|
||||
return -ENOMEM;
|
||||
|
||||
bus->name = "sun4i_mii_bus";
|
||||
bus->read = &sun4i_mdio_read;
|
||||
bus->write = &sun4i_mdio_write;
|
||||
snprintf(bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(&pdev->dev));
|
||||
bus->parent = &pdev->dev;
|
||||
|
||||
bus->irq = devm_kzalloc(&pdev->dev, sizeof(int) * PHY_MAX_ADDR,
|
||||
GFP_KERNEL);
|
||||
if (!bus->irq) {
|
||||
ret = -ENOMEM;
|
||||
goto err_out_free_mdiobus;
|
||||
}
|
||||
|
||||
for (i = 0; i < PHY_MAX_ADDR; i++)
|
||||
bus->irq[i] = PHY_POLL;
|
||||
|
||||
data = bus->priv;
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
data->membase = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(data->membase)) {
|
||||
ret = PTR_ERR(data->membase);
|
||||
goto err_out_free_mdiobus;
|
||||
}
|
||||
|
||||
data->regulator = devm_regulator_get(&pdev->dev, "phy");
|
||||
if (IS_ERR(data->regulator)) {
|
||||
if (PTR_ERR(data->regulator) == -EPROBE_DEFER)
|
||||
return -EPROBE_DEFER;
|
||||
|
||||
dev_info(&pdev->dev, "no regulator found\n");
|
||||
} else {
|
||||
ret = regulator_enable(data->regulator);
|
||||
if (ret)
|
||||
goto err_out_free_mdiobus;
|
||||
}
|
||||
|
||||
ret = of_mdiobus_register(bus, np);
|
||||
if (ret < 0)
|
||||
goto err_out_disable_regulator;
|
||||
|
||||
platform_set_drvdata(pdev, bus);
|
||||
|
||||
return 0;
|
||||
|
||||
err_out_disable_regulator:
|
||||
regulator_disable(data->regulator);
|
||||
err_out_free_mdiobus:
|
||||
mdiobus_free(bus);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sun4i_mdio_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct mii_bus *bus = platform_get_drvdata(pdev);
|
||||
|
||||
mdiobus_unregister(bus);
|
||||
mdiobus_free(bus);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id sun4i_mdio_dt_ids[] = {
|
||||
{ .compatible = "allwinner,sun4i-a10-mdio" },
|
||||
|
||||
/* Deprecated */
|
||||
{ .compatible = "allwinner,sun4i-mdio" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, sun4i_mdio_dt_ids);
|
||||
|
||||
static struct platform_driver sun4i_mdio_driver = {
|
||||
.probe = sun4i_mdio_probe,
|
||||
.remove = sun4i_mdio_remove,
|
||||
.driver = {
|
||||
.name = "sun4i-mdio",
|
||||
.of_match_table = sun4i_mdio_dt_ids,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(sun4i_mdio_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Allwinner EMAC MDIO interface driver");
|
||||
MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>");
|
||||
MODULE_LICENSE("GPL");
|
||||
610
drivers/net/phy/mdio_bus.c
Normal file
610
drivers/net/phy/mdio_bus.c
Normal file
|
|
@ -0,0 +1,610 @@
|
|||
/* MDIO Bus interface
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#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/device.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/of_mdio.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 <linux/io.h>
|
||||
#include <linux/uaccess.h>
|
||||
|
||||
#include <asm/irq.h>
|
||||
|
||||
/**
|
||||
* mdiobus_alloc_size - allocate a mii_bus structure
|
||||
* @size: extra amount of memory to allocate for private storage.
|
||||
* If non-zero, then bus->priv is points to that memory.
|
||||
*
|
||||
* Description: called by a bus driver to allocate an mii_bus
|
||||
* structure to fill in.
|
||||
*/
|
||||
struct mii_bus *mdiobus_alloc_size(size_t size)
|
||||
{
|
||||
struct mii_bus *bus;
|
||||
size_t aligned_size = ALIGN(sizeof(*bus), NETDEV_ALIGN);
|
||||
size_t alloc_size;
|
||||
|
||||
/* If we alloc extra space, it should be aligned */
|
||||
if (size)
|
||||
alloc_size = aligned_size + size;
|
||||
else
|
||||
alloc_size = sizeof(*bus);
|
||||
|
||||
bus = kzalloc(alloc_size, GFP_KERNEL);
|
||||
if (bus) {
|
||||
bus->state = MDIOBUS_ALLOCATED;
|
||||
if (size)
|
||||
bus->priv = (void *)bus + aligned_size;
|
||||
}
|
||||
|
||||
return bus;
|
||||
}
|
||||
EXPORT_SYMBOL(mdiobus_alloc_size);
|
||||
|
||||
static void _devm_mdiobus_free(struct device *dev, void *res)
|
||||
{
|
||||
mdiobus_free(*(struct mii_bus **)res);
|
||||
}
|
||||
|
||||
static int devm_mdiobus_match(struct device *dev, void *res, void *data)
|
||||
{
|
||||
struct mii_bus **r = res;
|
||||
|
||||
if (WARN_ON(!r || !*r))
|
||||
return 0;
|
||||
|
||||
return *r == data;
|
||||
}
|
||||
|
||||
/**
|
||||
* devm_mdiobus_alloc_size - Resource-managed mdiobus_alloc_size()
|
||||
* @dev: Device to allocate mii_bus for
|
||||
* @sizeof_priv: Space to allocate for private structure.
|
||||
*
|
||||
* Managed mdiobus_alloc_size. mii_bus allocated with this function is
|
||||
* automatically freed on driver detach.
|
||||
*
|
||||
* If an mii_bus allocated with this function needs to be freed separately,
|
||||
* devm_mdiobus_free() must be used.
|
||||
*
|
||||
* RETURNS:
|
||||
* Pointer to allocated mii_bus on success, NULL on failure.
|
||||
*/
|
||||
struct mii_bus *devm_mdiobus_alloc_size(struct device *dev, int sizeof_priv)
|
||||
{
|
||||
struct mii_bus **ptr, *bus;
|
||||
|
||||
ptr = devres_alloc(_devm_mdiobus_free, sizeof(*ptr), GFP_KERNEL);
|
||||
if (!ptr)
|
||||
return NULL;
|
||||
|
||||
/* use raw alloc_dr for kmalloc caller tracing */
|
||||
bus = mdiobus_alloc_size(sizeof_priv);
|
||||
if (bus) {
|
||||
*ptr = bus;
|
||||
devres_add(dev, ptr);
|
||||
} else {
|
||||
devres_free(ptr);
|
||||
}
|
||||
|
||||
return bus;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(devm_mdiobus_alloc_size);
|
||||
|
||||
/**
|
||||
* devm_mdiobus_free - Resource-managed mdiobus_free()
|
||||
* @dev: Device this mii_bus belongs to
|
||||
* @bus: the mii_bus associated with the device
|
||||
*
|
||||
* Free mii_bus allocated with devm_mdiobus_alloc_size().
|
||||
*/
|
||||
void devm_mdiobus_free(struct device *dev, struct mii_bus *bus)
|
||||
{
|
||||
int rc;
|
||||
|
||||
rc = devres_release(dev, _devm_mdiobus_free,
|
||||
devm_mdiobus_match, bus);
|
||||
WARN_ON(rc);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(devm_mdiobus_free);
|
||||
|
||||
/**
|
||||
* mdiobus_release - mii_bus device release callback
|
||||
* @d: the target struct device that contains the mii_bus
|
||||
*
|
||||
* Description: called when the last reference to an mii_bus is
|
||||
* dropped, to free the underlying memory.
|
||||
*/
|
||||
static void mdiobus_release(struct device *d)
|
||||
{
|
||||
struct mii_bus *bus = to_mii_bus(d);
|
||||
BUG_ON(bus->state != MDIOBUS_RELEASED &&
|
||||
/* for compatibility with error handling in drivers */
|
||||
bus->state != MDIOBUS_ALLOCATED);
|
||||
kfree(bus);
|
||||
}
|
||||
|
||||
static struct class mdio_bus_class = {
|
||||
.name = "mdio_bus",
|
||||
.dev_release = mdiobus_release,
|
||||
};
|
||||
|
||||
#if IS_ENABLED(CONFIG_OF_MDIO)
|
||||
/* Helper function for of_mdio_find_bus */
|
||||
static int of_mdio_bus_match(struct device *dev, const void *mdio_bus_np)
|
||||
{
|
||||
return dev->of_node == mdio_bus_np;
|
||||
}
|
||||
/**
|
||||
* of_mdio_find_bus - Given an mii_bus node, find the mii_bus.
|
||||
* @mdio_bus_np: Pointer to the mii_bus.
|
||||
*
|
||||
* Returns a pointer to the mii_bus, or NULL if none found.
|
||||
*
|
||||
* Because the association of a device_node and mii_bus is made via
|
||||
* of_mdiobus_register(), the mii_bus cannot be found before it is
|
||||
* registered with of_mdiobus_register().
|
||||
*
|
||||
*/
|
||||
struct mii_bus *of_mdio_find_bus(struct device_node *mdio_bus_np)
|
||||
{
|
||||
struct device *d;
|
||||
|
||||
if (!mdio_bus_np)
|
||||
return NULL;
|
||||
|
||||
d = class_find_device(&mdio_bus_class, NULL, mdio_bus_np,
|
||||
of_mdio_bus_match);
|
||||
|
||||
return d ? to_mii_bus(d) : NULL;
|
||||
}
|
||||
EXPORT_SYMBOL(of_mdio_find_bus);
|
||||
|
||||
/* Walk the list of subnodes of a mdio bus and look for a node that matches the
|
||||
* phy's address with its 'reg' property. If found, set the of_node pointer for
|
||||
* the phy. This allows auto-probed pyh devices to be supplied with information
|
||||
* passed in via DT.
|
||||
*/
|
||||
static void of_mdiobus_link_phydev(struct mii_bus *mdio,
|
||||
struct phy_device *phydev)
|
||||
{
|
||||
struct device *dev = &phydev->dev;
|
||||
struct device_node *child;
|
||||
|
||||
if (dev->of_node || !mdio->dev.of_node)
|
||||
return;
|
||||
|
||||
for_each_available_child_of_node(mdio->dev.of_node, child) {
|
||||
int addr;
|
||||
int ret;
|
||||
|
||||
ret = of_property_read_u32(child, "reg", &addr);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "%s has invalid PHY address\n",
|
||||
child->full_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* A PHY must have a reg property in the range [0-31] */
|
||||
if (addr >= PHY_MAX_ADDR) {
|
||||
dev_err(dev, "%s PHY address %i is too large\n",
|
||||
child->full_name, addr);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (addr == phydev->addr) {
|
||||
dev->of_node = child;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
#else /* !IS_ENABLED(CONFIG_OF_MDIO) */
|
||||
static inline void of_mdiobus_link_phydev(struct mii_bus *mdio,
|
||||
struct phy_device *phydev)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* mdiobus_register - bring up all the PHYs on a given bus and attach them to bus
|
||||
* @bus: target mii_bus
|
||||
*
|
||||
* Description: Called by a bus driver to bring up all the PHYs
|
||||
* on a given bus, and attach them to the bus.
|
||||
*
|
||||
* Returns 0 on success or < 0 on error.
|
||||
*/
|
||||
int mdiobus_register(struct mii_bus *bus)
|
||||
{
|
||||
int i, err;
|
||||
|
||||
if (NULL == bus || NULL == bus->name ||
|
||||
NULL == bus->read || NULL == bus->write)
|
||||
return -EINVAL;
|
||||
|
||||
BUG_ON(bus->state != MDIOBUS_ALLOCATED &&
|
||||
bus->state != MDIOBUS_UNREGISTERED);
|
||||
|
||||
bus->dev.parent = bus->parent;
|
||||
bus->dev.class = &mdio_bus_class;
|
||||
bus->dev.groups = NULL;
|
||||
dev_set_name(&bus->dev, "%s", bus->id);
|
||||
|
||||
err = device_register(&bus->dev);
|
||||
if (err) {
|
||||
pr_err("mii_bus %s failed to register\n", bus->id);
|
||||
put_device(&bus->dev);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mutex_init(&bus->mdio_lock);
|
||||
|
||||
if (bus->reset)
|
||||
bus->reset(bus);
|
||||
|
||||
for (i = 0; i < PHY_MAX_ADDR; i++) {
|
||||
if ((bus->phy_mask & (1 << i)) == 0) {
|
||||
struct phy_device *phydev;
|
||||
|
||||
phydev = mdiobus_scan(bus, i);
|
||||
if (IS_ERR(phydev)) {
|
||||
err = PTR_ERR(phydev);
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bus->state = MDIOBUS_REGISTERED;
|
||||
pr_info("%s: probed\n", bus->name);
|
||||
return 0;
|
||||
|
||||
error:
|
||||
while (--i >= 0) {
|
||||
if (bus->phy_map[i])
|
||||
device_unregister(&bus->phy_map[i]->dev);
|
||||
}
|
||||
device_del(&bus->dev);
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL(mdiobus_register);
|
||||
|
||||
void mdiobus_unregister(struct mii_bus *bus)
|
||||
{
|
||||
int i;
|
||||
|
||||
BUG_ON(bus->state != MDIOBUS_REGISTERED);
|
||||
bus->state = MDIOBUS_UNREGISTERED;
|
||||
|
||||
device_del(&bus->dev);
|
||||
for (i = 0; i < PHY_MAX_ADDR; i++) {
|
||||
if (bus->phy_map[i])
|
||||
device_unregister(&bus->phy_map[i]->dev);
|
||||
bus->phy_map[i] = NULL;
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(mdiobus_unregister);
|
||||
|
||||
/**
|
||||
* mdiobus_free - free a struct mii_bus
|
||||
* @bus: mii_bus to free
|
||||
*
|
||||
* This function releases the reference to the underlying device
|
||||
* object in the mii_bus. If this is the last reference, the mii_bus
|
||||
* will be freed.
|
||||
*/
|
||||
void mdiobus_free(struct mii_bus *bus)
|
||||
{
|
||||
/* For compatibility with error handling in drivers. */
|
||||
if (bus->state == MDIOBUS_ALLOCATED) {
|
||||
kfree(bus);
|
||||
return;
|
||||
}
|
||||
|
||||
BUG_ON(bus->state != MDIOBUS_UNREGISTERED);
|
||||
bus->state = MDIOBUS_RELEASED;
|
||||
|
||||
put_device(&bus->dev);
|
||||
}
|
||||
EXPORT_SYMBOL(mdiobus_free);
|
||||
|
||||
struct phy_device *mdiobus_scan(struct mii_bus *bus, int addr)
|
||||
{
|
||||
struct phy_device *phydev;
|
||||
int err;
|
||||
|
||||
phydev = get_phy_device(bus, addr, false);
|
||||
if (IS_ERR(phydev) || phydev == NULL)
|
||||
return phydev;
|
||||
|
||||
/*
|
||||
* For DT, see if the auto-probed phy has a correspoding child
|
||||
* in the bus node, and set the of_node pointer in this case.
|
||||
*/
|
||||
of_mdiobus_link_phydev(bus, phydev);
|
||||
|
||||
err = phy_device_register(phydev);
|
||||
if (err) {
|
||||
phy_device_free(phydev);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return phydev;
|
||||
}
|
||||
EXPORT_SYMBOL(mdiobus_scan);
|
||||
|
||||
/**
|
||||
* mdiobus_read - Convenience function for reading a given MII mgmt register
|
||||
* @bus: the mii_bus struct
|
||||
* @addr: the phy address
|
||||
* @regnum: register number to read
|
||||
*
|
||||
* NOTE: MUST NOT be called from interrupt context,
|
||||
* because the bus read/write functions may wait for an interrupt
|
||||
* to conclude the operation.
|
||||
*/
|
||||
int mdiobus_read(struct mii_bus *bus, int addr, u32 regnum)
|
||||
{
|
||||
int retval;
|
||||
|
||||
BUG_ON(in_interrupt());
|
||||
|
||||
mutex_lock(&bus->mdio_lock);
|
||||
retval = bus->read(bus, addr, regnum);
|
||||
mutex_unlock(&bus->mdio_lock);
|
||||
|
||||
return retval;
|
||||
}
|
||||
EXPORT_SYMBOL(mdiobus_read);
|
||||
|
||||
/**
|
||||
* mdiobus_write - Convenience function for writing a given MII mgmt register
|
||||
* @bus: the mii_bus struct
|
||||
* @addr: the phy address
|
||||
* @regnum: register number to write
|
||||
* @val: value to write to @regnum
|
||||
*
|
||||
* NOTE: MUST NOT be called from interrupt context,
|
||||
* because the bus read/write functions may wait for an interrupt
|
||||
* to conclude the operation.
|
||||
*/
|
||||
int mdiobus_write(struct mii_bus *bus, int addr, u32 regnum, u16 val)
|
||||
{
|
||||
int err;
|
||||
|
||||
BUG_ON(in_interrupt());
|
||||
|
||||
mutex_lock(&bus->mdio_lock);
|
||||
err = bus->write(bus, addr, regnum, val);
|
||||
mutex_unlock(&bus->mdio_lock);
|
||||
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL(mdiobus_write);
|
||||
|
||||
/**
|
||||
* mdio_bus_match - determine if given PHY driver supports the given PHY device
|
||||
* @dev: target PHY device
|
||||
* @drv: given PHY driver
|
||||
*
|
||||
* Description: Given a PHY device, and a PHY driver, return 1 if
|
||||
* the driver supports the device. Otherwise, return 0.
|
||||
*/
|
||||
static int mdio_bus_match(struct device *dev, struct device_driver *drv)
|
||||
{
|
||||
struct phy_device *phydev = to_phy_device(dev);
|
||||
struct phy_driver *phydrv = to_phy_driver(drv);
|
||||
|
||||
if (of_driver_match_device(dev, drv))
|
||||
return 1;
|
||||
|
||||
if (phydrv->match_phy_device)
|
||||
return phydrv->match_phy_device(phydev);
|
||||
|
||||
return (phydrv->phy_id & phydrv->phy_id_mask) ==
|
||||
(phydev->phy_id & phydrv->phy_id_mask);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
|
||||
static bool mdio_bus_phy_may_suspend(struct phy_device *phydev)
|
||||
{
|
||||
struct device_driver *drv = phydev->dev.driver;
|
||||
struct phy_driver *phydrv = to_phy_driver(drv);
|
||||
struct net_device *netdev = phydev->attached_dev;
|
||||
|
||||
if (!drv || !phydrv->suspend)
|
||||
return false;
|
||||
|
||||
/* PHY not attached? May suspend. */
|
||||
if (!netdev)
|
||||
return true;
|
||||
|
||||
/* Don't suspend PHY if the attched netdev parent may wakeup.
|
||||
* The parent may point to a PCI device, as in tg3 driver.
|
||||
*/
|
||||
if (netdev->dev.parent && device_may_wakeup(netdev->dev.parent))
|
||||
return false;
|
||||
|
||||
/* Also don't suspend PHY if the netdev itself may wakeup. This
|
||||
* is the case for devices w/o underlaying pwr. mgmt. aware bus,
|
||||
* e.g. SoC devices.
|
||||
*/
|
||||
if (device_may_wakeup(&netdev->dev))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int mdio_bus_suspend(struct device *dev)
|
||||
{
|
||||
struct phy_driver *phydrv = to_phy_driver(dev->driver);
|
||||
struct phy_device *phydev = to_phy_device(dev);
|
||||
|
||||
/* We must stop the state machine manually, otherwise it stops out of
|
||||
* control, possibly with the phydev->lock held. Upon resume, netdev
|
||||
* may call phy routines that try to grab the same lock, and that may
|
||||
* lead to a deadlock.
|
||||
*/
|
||||
if (phydev->attached_dev && phydev->adjust_link)
|
||||
phy_stop_machine(phydev);
|
||||
|
||||
if (!mdio_bus_phy_may_suspend(phydev))
|
||||
return 0;
|
||||
|
||||
return phydrv->suspend(phydev);
|
||||
}
|
||||
|
||||
static int mdio_bus_resume(struct device *dev)
|
||||
{
|
||||
struct phy_driver *phydrv = to_phy_driver(dev->driver);
|
||||
struct phy_device *phydev = to_phy_device(dev);
|
||||
int ret;
|
||||
|
||||
if (!mdio_bus_phy_may_suspend(phydev))
|
||||
goto no_resume;
|
||||
|
||||
ret = phydrv->resume(phydev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
no_resume:
|
||||
if (phydev->attached_dev && phydev->adjust_link)
|
||||
phy_start_machine(phydev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mdio_bus_restore(struct device *dev)
|
||||
{
|
||||
struct phy_device *phydev = to_phy_device(dev);
|
||||
struct net_device *netdev = phydev->attached_dev;
|
||||
int ret;
|
||||
|
||||
if (!netdev)
|
||||
return 0;
|
||||
|
||||
ret = phy_init_hw(phydev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* The PHY needs to renegotiate. */
|
||||
phydev->link = 0;
|
||||
phydev->state = PHY_UP;
|
||||
|
||||
phy_start_machine(phydev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops mdio_bus_pm_ops = {
|
||||
.suspend = mdio_bus_suspend,
|
||||
.resume = mdio_bus_resume,
|
||||
.freeze = mdio_bus_suspend,
|
||||
.thaw = mdio_bus_resume,
|
||||
.restore = mdio_bus_restore,
|
||||
};
|
||||
|
||||
#define MDIO_BUS_PM_OPS (&mdio_bus_pm_ops)
|
||||
|
||||
#else
|
||||
|
||||
#define MDIO_BUS_PM_OPS NULL
|
||||
|
||||
#endif /* CONFIG_PM */
|
||||
|
||||
static ssize_t
|
||||
phy_id_show(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct phy_device *phydev = to_phy_device(dev);
|
||||
|
||||
return sprintf(buf, "0x%.8lx\n", (unsigned long)phydev->phy_id);
|
||||
}
|
||||
static DEVICE_ATTR_RO(phy_id);
|
||||
|
||||
static ssize_t
|
||||
phy_interface_show(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct phy_device *phydev = to_phy_device(dev);
|
||||
const char *mode = NULL;
|
||||
|
||||
if (phy_is_internal(phydev))
|
||||
mode = "internal";
|
||||
else
|
||||
mode = phy_modes(phydev->interface);
|
||||
|
||||
return sprintf(buf, "%s\n", mode);
|
||||
}
|
||||
static DEVICE_ATTR_RO(phy_interface);
|
||||
|
||||
static ssize_t
|
||||
phy_has_fixups_show(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct phy_device *phydev = to_phy_device(dev);
|
||||
|
||||
return sprintf(buf, "%d\n", phydev->has_fixups);
|
||||
}
|
||||
static DEVICE_ATTR_RO(phy_has_fixups);
|
||||
|
||||
static struct attribute *mdio_dev_attrs[] = {
|
||||
&dev_attr_phy_id.attr,
|
||||
&dev_attr_phy_interface.attr,
|
||||
&dev_attr_phy_has_fixups.attr,
|
||||
NULL,
|
||||
};
|
||||
ATTRIBUTE_GROUPS(mdio_dev);
|
||||
|
||||
struct bus_type mdio_bus_type = {
|
||||
.name = "mdio_bus",
|
||||
.match = mdio_bus_match,
|
||||
.pm = MDIO_BUS_PM_OPS,
|
||||
.dev_groups = mdio_dev_groups,
|
||||
};
|
||||
EXPORT_SYMBOL(mdio_bus_type);
|
||||
|
||||
int __init mdio_bus_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = class_register(&mdio_bus_class);
|
||||
if (!ret) {
|
||||
ret = bus_register(&mdio_bus_type);
|
||||
if (ret)
|
||||
class_unregister(&mdio_bus_class);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void mdio_bus_exit(void)
|
||||
{
|
||||
class_unregister(&mdio_bus_class);
|
||||
bus_unregister(&mdio_bus_type);
|
||||
}
|
||||
695
drivers/net/phy/micrel.c
Normal file
695
drivers/net/phy/micrel.c
Normal file
|
|
@ -0,0 +1,695 @@
|
|||
/*
|
||||
* drivers/net/phy/micrel.c
|
||||
*
|
||||
* Driver for Micrel PHYs
|
||||
*
|
||||
* Author: David J. Choi
|
||||
*
|
||||
* Copyright (c) 2010-2013 Micrel, 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.
|
||||
*
|
||||
* Support : Micrel Phys:
|
||||
* Giga phys: ksz9021, ksz9031
|
||||
* 100/10 Phys : ksz8001, ksz8721, ksz8737, ksz8041
|
||||
* ksz8021, ksz8031, ksz8051,
|
||||
* ksz8081, ksz8091,
|
||||
* ksz8061,
|
||||
* Switch : ksz8873, ksz886x
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/phy.h>
|
||||
#include <linux/micrel_phy.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/clk.h>
|
||||
|
||||
/* Operation Mode Strap Override */
|
||||
#define MII_KSZPHY_OMSO 0x16
|
||||
#define KSZPHY_OMSO_B_CAST_OFF (1 << 9)
|
||||
#define KSZPHY_OMSO_RMII_OVERRIDE (1 << 1)
|
||||
#define KSZPHY_OMSO_MII_OVERRIDE (1 << 0)
|
||||
|
||||
/* general Interrupt control/status reg in vendor specific block. */
|
||||
#define MII_KSZPHY_INTCS 0x1B
|
||||
#define KSZPHY_INTCS_JABBER (1 << 15)
|
||||
#define KSZPHY_INTCS_RECEIVE_ERR (1 << 14)
|
||||
#define KSZPHY_INTCS_PAGE_RECEIVE (1 << 13)
|
||||
#define KSZPHY_INTCS_PARELLEL (1 << 12)
|
||||
#define KSZPHY_INTCS_LINK_PARTNER_ACK (1 << 11)
|
||||
#define KSZPHY_INTCS_LINK_DOWN (1 << 10)
|
||||
#define KSZPHY_INTCS_REMOTE_FAULT (1 << 9)
|
||||
#define KSZPHY_INTCS_LINK_UP (1 << 8)
|
||||
#define KSZPHY_INTCS_ALL (KSZPHY_INTCS_LINK_UP |\
|
||||
KSZPHY_INTCS_LINK_DOWN)
|
||||
|
||||
/* general PHY control reg in vendor specific block. */
|
||||
#define MII_KSZPHY_CTRL 0x1F
|
||||
/* bitmap of PHY register to set interrupt mode */
|
||||
#define KSZPHY_CTRL_INT_ACTIVE_HIGH (1 << 9)
|
||||
#define KSZ9021_CTRL_INT_ACTIVE_HIGH (1 << 14)
|
||||
#define KS8737_CTRL_INT_ACTIVE_HIGH (1 << 14)
|
||||
#define KSZ8051_RMII_50MHZ_CLK (1 << 7)
|
||||
|
||||
/* Write/read to/from extended registers */
|
||||
#define MII_KSZPHY_EXTREG 0x0b
|
||||
#define KSZPHY_EXTREG_WRITE 0x8000
|
||||
|
||||
#define MII_KSZPHY_EXTREG_WRITE 0x0c
|
||||
#define MII_KSZPHY_EXTREG_READ 0x0d
|
||||
|
||||
/* Extended registers */
|
||||
#define MII_KSZPHY_CLK_CONTROL_PAD_SKEW 0x104
|
||||
#define MII_KSZPHY_RX_DATA_PAD_SKEW 0x105
|
||||
#define MII_KSZPHY_TX_DATA_PAD_SKEW 0x106
|
||||
|
||||
#define PS_TO_REG 200
|
||||
|
||||
static int ksz_config_flags(struct phy_device *phydev)
|
||||
{
|
||||
int regval;
|
||||
|
||||
if (phydev->dev_flags & (MICREL_PHY_50MHZ_CLK | MICREL_PHY_25MHZ_CLK)) {
|
||||
regval = phy_read(phydev, MII_KSZPHY_CTRL);
|
||||
if (phydev->dev_flags & MICREL_PHY_50MHZ_CLK)
|
||||
regval |= KSZ8051_RMII_50MHZ_CLK;
|
||||
else
|
||||
regval &= ~KSZ8051_RMII_50MHZ_CLK;
|
||||
return phy_write(phydev, MII_KSZPHY_CTRL, regval);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kszphy_extended_write(struct phy_device *phydev,
|
||||
u32 regnum, u16 val)
|
||||
{
|
||||
phy_write(phydev, MII_KSZPHY_EXTREG, KSZPHY_EXTREG_WRITE | regnum);
|
||||
return phy_write(phydev, MII_KSZPHY_EXTREG_WRITE, val);
|
||||
}
|
||||
|
||||
static int kszphy_extended_read(struct phy_device *phydev,
|
||||
u32 regnum)
|
||||
{
|
||||
phy_write(phydev, MII_KSZPHY_EXTREG, regnum);
|
||||
return phy_read(phydev, MII_KSZPHY_EXTREG_READ);
|
||||
}
|
||||
|
||||
static int kszphy_ack_interrupt(struct phy_device *phydev)
|
||||
{
|
||||
/* bit[7..0] int status, which is a read and clear register. */
|
||||
int rc;
|
||||
|
||||
rc = phy_read(phydev, MII_KSZPHY_INTCS);
|
||||
|
||||
return (rc < 0) ? rc : 0;
|
||||
}
|
||||
|
||||
static int kszphy_set_interrupt(struct phy_device *phydev)
|
||||
{
|
||||
int temp;
|
||||
temp = (PHY_INTERRUPT_ENABLED == phydev->interrupts) ?
|
||||
KSZPHY_INTCS_ALL : 0;
|
||||
return phy_write(phydev, MII_KSZPHY_INTCS, temp);
|
||||
}
|
||||
|
||||
static int kszphy_config_intr(struct phy_device *phydev)
|
||||
{
|
||||
int temp, rc;
|
||||
|
||||
/* set the interrupt pin active low */
|
||||
temp = phy_read(phydev, MII_KSZPHY_CTRL);
|
||||
temp &= ~KSZPHY_CTRL_INT_ACTIVE_HIGH;
|
||||
phy_write(phydev, MII_KSZPHY_CTRL, temp);
|
||||
rc = kszphy_set_interrupt(phydev);
|
||||
return rc < 0 ? rc : 0;
|
||||
}
|
||||
|
||||
static int ksz9021_config_intr(struct phy_device *phydev)
|
||||
{
|
||||
int temp, rc;
|
||||
|
||||
/* set the interrupt pin active low */
|
||||
temp = phy_read(phydev, MII_KSZPHY_CTRL);
|
||||
temp &= ~KSZ9021_CTRL_INT_ACTIVE_HIGH;
|
||||
phy_write(phydev, MII_KSZPHY_CTRL, temp);
|
||||
rc = kszphy_set_interrupt(phydev);
|
||||
return rc < 0 ? rc : 0;
|
||||
}
|
||||
|
||||
static int ks8737_config_intr(struct phy_device *phydev)
|
||||
{
|
||||
int temp, rc;
|
||||
|
||||
/* set the interrupt pin active low */
|
||||
temp = phy_read(phydev, MII_KSZPHY_CTRL);
|
||||
temp &= ~KS8737_CTRL_INT_ACTIVE_HIGH;
|
||||
phy_write(phydev, MII_KSZPHY_CTRL, temp);
|
||||
rc = kszphy_set_interrupt(phydev);
|
||||
return rc < 0 ? rc : 0;
|
||||
}
|
||||
|
||||
static int kszphy_setup_led(struct phy_device *phydev,
|
||||
unsigned int reg, unsigned int shift)
|
||||
{
|
||||
|
||||
struct device *dev = &phydev->dev;
|
||||
struct device_node *of_node = dev->of_node;
|
||||
int rc, temp;
|
||||
u32 val;
|
||||
|
||||
if (!of_node && dev->parent->of_node)
|
||||
of_node = dev->parent->of_node;
|
||||
|
||||
if (of_property_read_u32(of_node, "micrel,led-mode", &val))
|
||||
return 0;
|
||||
|
||||
temp = phy_read(phydev, reg);
|
||||
if (temp < 0)
|
||||
return temp;
|
||||
|
||||
temp &= ~(3 << shift);
|
||||
temp |= val << shift;
|
||||
rc = phy_write(phydev, reg, temp);
|
||||
|
||||
return rc < 0 ? rc : 0;
|
||||
}
|
||||
|
||||
static int kszphy_config_init(struct phy_device *phydev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kszphy_config_init_led8041(struct phy_device *phydev)
|
||||
{
|
||||
/* single led control, register 0x1e bits 15..14 */
|
||||
return kszphy_setup_led(phydev, 0x1e, 14);
|
||||
}
|
||||
|
||||
static int ksz8021_config_init(struct phy_device *phydev)
|
||||
{
|
||||
const u16 val = KSZPHY_OMSO_B_CAST_OFF | KSZPHY_OMSO_RMII_OVERRIDE;
|
||||
int rc;
|
||||
|
||||
rc = kszphy_setup_led(phydev, 0x1f, 4);
|
||||
if (rc)
|
||||
dev_err(&phydev->dev, "failed to set led mode\n");
|
||||
|
||||
rc = ksz_config_flags(phydev);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
rc = phy_write(phydev, MII_KSZPHY_OMSO, val);
|
||||
return rc < 0 ? rc : 0;
|
||||
}
|
||||
|
||||
static int ks8051_config_init(struct phy_device *phydev)
|
||||
{
|
||||
int rc;
|
||||
|
||||
rc = kszphy_setup_led(phydev, 0x1f, 4);
|
||||
if (rc)
|
||||
dev_err(&phydev->dev, "failed to set led mode\n");
|
||||
|
||||
rc = ksz_config_flags(phydev);
|
||||
return rc < 0 ? rc : 0;
|
||||
}
|
||||
|
||||
static int ksz9021_load_values_from_of(struct phy_device *phydev,
|
||||
struct device_node *of_node, u16 reg,
|
||||
char *field1, char *field2,
|
||||
char *field3, char *field4)
|
||||
{
|
||||
int val1 = -1;
|
||||
int val2 = -2;
|
||||
int val3 = -3;
|
||||
int val4 = -4;
|
||||
int newval;
|
||||
int matches = 0;
|
||||
|
||||
if (!of_property_read_u32(of_node, field1, &val1))
|
||||
matches++;
|
||||
|
||||
if (!of_property_read_u32(of_node, field2, &val2))
|
||||
matches++;
|
||||
|
||||
if (!of_property_read_u32(of_node, field3, &val3))
|
||||
matches++;
|
||||
|
||||
if (!of_property_read_u32(of_node, field4, &val4))
|
||||
matches++;
|
||||
|
||||
if (!matches)
|
||||
return 0;
|
||||
|
||||
if (matches < 4)
|
||||
newval = kszphy_extended_read(phydev, reg);
|
||||
else
|
||||
newval = 0;
|
||||
|
||||
if (val1 != -1)
|
||||
newval = ((newval & 0xfff0) | ((val1 / PS_TO_REG) & 0xf) << 0);
|
||||
|
||||
if (val2 != -2)
|
||||
newval = ((newval & 0xff0f) | ((val2 / PS_TO_REG) & 0xf) << 4);
|
||||
|
||||
if (val3 != -3)
|
||||
newval = ((newval & 0xf0ff) | ((val3 / PS_TO_REG) & 0xf) << 8);
|
||||
|
||||
if (val4 != -4)
|
||||
newval = ((newval & 0x0fff) | ((val4 / PS_TO_REG) & 0xf) << 12);
|
||||
|
||||
return kszphy_extended_write(phydev, reg, newval);
|
||||
}
|
||||
|
||||
static int ksz9021_config_init(struct phy_device *phydev)
|
||||
{
|
||||
struct device *dev = &phydev->dev;
|
||||
struct device_node *of_node = dev->of_node;
|
||||
|
||||
if (!of_node && dev->parent->of_node)
|
||||
of_node = dev->parent->of_node;
|
||||
|
||||
if (of_node) {
|
||||
ksz9021_load_values_from_of(phydev, of_node,
|
||||
MII_KSZPHY_CLK_CONTROL_PAD_SKEW,
|
||||
"txen-skew-ps", "txc-skew-ps",
|
||||
"rxdv-skew-ps", "rxc-skew-ps");
|
||||
ksz9021_load_values_from_of(phydev, of_node,
|
||||
MII_KSZPHY_RX_DATA_PAD_SKEW,
|
||||
"rxd0-skew-ps", "rxd1-skew-ps",
|
||||
"rxd2-skew-ps", "rxd3-skew-ps");
|
||||
ksz9021_load_values_from_of(phydev, of_node,
|
||||
MII_KSZPHY_TX_DATA_PAD_SKEW,
|
||||
"txd0-skew-ps", "txd1-skew-ps",
|
||||
"txd2-skew-ps", "txd3-skew-ps");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define MII_KSZ9031RN_MMD_CTRL_REG 0x0d
|
||||
#define MII_KSZ9031RN_MMD_REGDATA_REG 0x0e
|
||||
#define OP_DATA 1
|
||||
#define KSZ9031_PS_TO_REG 60
|
||||
|
||||
/* Extended registers */
|
||||
#define MII_KSZ9031RN_CONTROL_PAD_SKEW 4
|
||||
#define MII_KSZ9031RN_RX_DATA_PAD_SKEW 5
|
||||
#define MII_KSZ9031RN_TX_DATA_PAD_SKEW 6
|
||||
#define MII_KSZ9031RN_CLK_PAD_SKEW 8
|
||||
|
||||
static int ksz9031_extended_write(struct phy_device *phydev,
|
||||
u8 mode, u32 dev_addr, u32 regnum, u16 val)
|
||||
{
|
||||
phy_write(phydev, MII_KSZ9031RN_MMD_CTRL_REG, dev_addr);
|
||||
phy_write(phydev, MII_KSZ9031RN_MMD_REGDATA_REG, regnum);
|
||||
phy_write(phydev, MII_KSZ9031RN_MMD_CTRL_REG, (mode << 14) | dev_addr);
|
||||
return phy_write(phydev, MII_KSZ9031RN_MMD_REGDATA_REG, val);
|
||||
}
|
||||
|
||||
static int ksz9031_extended_read(struct phy_device *phydev,
|
||||
u8 mode, u32 dev_addr, u32 regnum)
|
||||
{
|
||||
phy_write(phydev, MII_KSZ9031RN_MMD_CTRL_REG, dev_addr);
|
||||
phy_write(phydev, MII_KSZ9031RN_MMD_REGDATA_REG, regnum);
|
||||
phy_write(phydev, MII_KSZ9031RN_MMD_CTRL_REG, (mode << 14) | dev_addr);
|
||||
return phy_read(phydev, MII_KSZ9031RN_MMD_REGDATA_REG);
|
||||
}
|
||||
|
||||
static int ksz9031_of_load_skew_values(struct phy_device *phydev,
|
||||
struct device_node *of_node,
|
||||
u16 reg, size_t field_sz,
|
||||
char *field[], u8 numfields)
|
||||
{
|
||||
int val[4] = {-1, -2, -3, -4};
|
||||
int matches = 0;
|
||||
u16 mask;
|
||||
u16 maxval;
|
||||
u16 newval;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < numfields; i++)
|
||||
if (!of_property_read_u32(of_node, field[i], val + i))
|
||||
matches++;
|
||||
|
||||
if (!matches)
|
||||
return 0;
|
||||
|
||||
if (matches < numfields)
|
||||
newval = ksz9031_extended_read(phydev, OP_DATA, 2, reg);
|
||||
else
|
||||
newval = 0;
|
||||
|
||||
maxval = (field_sz == 4) ? 0xf : 0x1f;
|
||||
for (i = 0; i < numfields; i++)
|
||||
if (val[i] != -(i + 1)) {
|
||||
mask = 0xffff;
|
||||
mask ^= maxval << (field_sz * i);
|
||||
newval = (newval & mask) |
|
||||
(((val[i] / KSZ9031_PS_TO_REG) & maxval)
|
||||
<< (field_sz * i));
|
||||
}
|
||||
|
||||
return ksz9031_extended_write(phydev, OP_DATA, 2, reg, newval);
|
||||
}
|
||||
|
||||
static int ksz9031_config_init(struct phy_device *phydev)
|
||||
{
|
||||
struct device *dev = &phydev->dev;
|
||||
struct device_node *of_node = dev->of_node;
|
||||
char *clk_skews[2] = {"rxc-skew-ps", "txc-skew-ps"};
|
||||
char *rx_data_skews[4] = {
|
||||
"rxd0-skew-ps", "rxd1-skew-ps",
|
||||
"rxd2-skew-ps", "rxd3-skew-ps"
|
||||
};
|
||||
char *tx_data_skews[4] = {
|
||||
"txd0-skew-ps", "txd1-skew-ps",
|
||||
"txd2-skew-ps", "txd3-skew-ps"
|
||||
};
|
||||
char *control_skews[2] = {"txen-skew-ps", "rxdv-skew-ps"};
|
||||
|
||||
if (!of_node && dev->parent->of_node)
|
||||
of_node = dev->parent->of_node;
|
||||
|
||||
if (of_node) {
|
||||
ksz9031_of_load_skew_values(phydev, of_node,
|
||||
MII_KSZ9031RN_CLK_PAD_SKEW, 5,
|
||||
clk_skews, 2);
|
||||
|
||||
ksz9031_of_load_skew_values(phydev, of_node,
|
||||
MII_KSZ9031RN_CONTROL_PAD_SKEW, 4,
|
||||
control_skews, 2);
|
||||
|
||||
ksz9031_of_load_skew_values(phydev, of_node,
|
||||
MII_KSZ9031RN_RX_DATA_PAD_SKEW, 4,
|
||||
rx_data_skews, 4);
|
||||
|
||||
ksz9031_of_load_skew_values(phydev, of_node,
|
||||
MII_KSZ9031RN_TX_DATA_PAD_SKEW, 4,
|
||||
tx_data_skews, 4);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define KSZ8873MLL_GLOBAL_CONTROL_4 0x06
|
||||
#define KSZ8873MLL_GLOBAL_CONTROL_4_DUPLEX (1 << 6)
|
||||
#define KSZ8873MLL_GLOBAL_CONTROL_4_SPEED (1 << 4)
|
||||
static int ksz8873mll_read_status(struct phy_device *phydev)
|
||||
{
|
||||
int regval;
|
||||
|
||||
/* dummy read */
|
||||
regval = phy_read(phydev, KSZ8873MLL_GLOBAL_CONTROL_4);
|
||||
|
||||
regval = phy_read(phydev, KSZ8873MLL_GLOBAL_CONTROL_4);
|
||||
|
||||
if (regval & KSZ8873MLL_GLOBAL_CONTROL_4_DUPLEX)
|
||||
phydev->duplex = DUPLEX_HALF;
|
||||
else
|
||||
phydev->duplex = DUPLEX_FULL;
|
||||
|
||||
if (regval & KSZ8873MLL_GLOBAL_CONTROL_4_SPEED)
|
||||
phydev->speed = SPEED_10;
|
||||
else
|
||||
phydev->speed = SPEED_100;
|
||||
|
||||
phydev->link = 1;
|
||||
phydev->pause = phydev->asym_pause = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ksz8873mll_config_aneg(struct phy_device *phydev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* This routine returns -1 as an indication to the caller that the
|
||||
* Micrel ksz9021 10/100/1000 PHY does not support standard IEEE
|
||||
* MMD extended PHY registers.
|
||||
*/
|
||||
static int
|
||||
ksz9021_rd_mmd_phyreg(struct phy_device *phydev, int ptrad, int devnum,
|
||||
int regnum)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* This routine does nothing since the Micrel ksz9021 does not support
|
||||
* standard IEEE MMD extended PHY registers.
|
||||
*/
|
||||
static void
|
||||
ksz9021_wr_mmd_phyreg(struct phy_device *phydev, int ptrad, int devnum,
|
||||
int regnum, u32 val)
|
||||
{
|
||||
}
|
||||
|
||||
static int ksz8021_probe(struct phy_device *phydev)
|
||||
{
|
||||
struct clk *clk;
|
||||
|
||||
clk = devm_clk_get(&phydev->dev, "rmii-ref");
|
||||
if (!IS_ERR(clk)) {
|
||||
unsigned long rate = clk_get_rate(clk);
|
||||
|
||||
if (rate > 24500000 && rate < 25500000) {
|
||||
phydev->dev_flags |= MICREL_PHY_25MHZ_CLK;
|
||||
} else if (rate > 49500000 && rate < 50500000) {
|
||||
phydev->dev_flags |= MICREL_PHY_50MHZ_CLK;
|
||||
} else {
|
||||
dev_err(&phydev->dev, "Clock rate out of range: %ld\n", rate);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct phy_driver ksphy_driver[] = {
|
||||
{
|
||||
.phy_id = PHY_ID_KS8737,
|
||||
.phy_id_mask = 0x00fffff0,
|
||||
.name = "Micrel KS8737",
|
||||
.features = (PHY_BASIC_FEATURES | SUPPORTED_Pause),
|
||||
.flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT,
|
||||
.config_init = kszphy_config_init,
|
||||
.config_aneg = genphy_config_aneg,
|
||||
.read_status = genphy_read_status,
|
||||
.ack_interrupt = kszphy_ack_interrupt,
|
||||
.config_intr = ks8737_config_intr,
|
||||
.suspend = genphy_suspend,
|
||||
.resume = genphy_resume,
|
||||
.driver = { .owner = THIS_MODULE,},
|
||||
}, {
|
||||
.phy_id = PHY_ID_KSZ8021,
|
||||
.phy_id_mask = 0x00ffffff,
|
||||
.name = "Micrel KSZ8021 or KSZ8031",
|
||||
.features = (PHY_BASIC_FEATURES | SUPPORTED_Pause |
|
||||
SUPPORTED_Asym_Pause),
|
||||
.flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT,
|
||||
.probe = ksz8021_probe,
|
||||
.config_init = ksz8021_config_init,
|
||||
.config_aneg = genphy_config_aneg,
|
||||
.read_status = genphy_read_status,
|
||||
.ack_interrupt = kszphy_ack_interrupt,
|
||||
.config_intr = kszphy_config_intr,
|
||||
.suspend = genphy_suspend,
|
||||
.resume = genphy_resume,
|
||||
.driver = { .owner = THIS_MODULE,},
|
||||
}, {
|
||||
.phy_id = PHY_ID_KSZ8031,
|
||||
.phy_id_mask = 0x00ffffff,
|
||||
.name = "Micrel KSZ8031",
|
||||
.features = (PHY_BASIC_FEATURES | SUPPORTED_Pause |
|
||||
SUPPORTED_Asym_Pause),
|
||||
.flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT,
|
||||
.probe = ksz8021_probe,
|
||||
.config_init = ksz8021_config_init,
|
||||
.config_aneg = genphy_config_aneg,
|
||||
.read_status = genphy_read_status,
|
||||
.ack_interrupt = kszphy_ack_interrupt,
|
||||
.config_intr = kszphy_config_intr,
|
||||
.suspend = genphy_suspend,
|
||||
.resume = genphy_resume,
|
||||
.driver = { .owner = THIS_MODULE,},
|
||||
}, {
|
||||
.phy_id = PHY_ID_KSZ8041,
|
||||
.phy_id_mask = 0x00fffff0,
|
||||
.name = "Micrel KSZ8041",
|
||||
.features = (PHY_BASIC_FEATURES | SUPPORTED_Pause
|
||||
| SUPPORTED_Asym_Pause),
|
||||
.flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT,
|
||||
.config_init = kszphy_config_init_led8041,
|
||||
.config_aneg = genphy_config_aneg,
|
||||
.read_status = genphy_read_status,
|
||||
.ack_interrupt = kszphy_ack_interrupt,
|
||||
.config_intr = kszphy_config_intr,
|
||||
.suspend = genphy_suspend,
|
||||
.resume = genphy_resume,
|
||||
.driver = { .owner = THIS_MODULE,},
|
||||
}, {
|
||||
.phy_id = PHY_ID_KSZ8041RNLI,
|
||||
.phy_id_mask = 0x00fffff0,
|
||||
.name = "Micrel KSZ8041RNLI",
|
||||
.features = PHY_BASIC_FEATURES |
|
||||
SUPPORTED_Pause | SUPPORTED_Asym_Pause,
|
||||
.flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT,
|
||||
.config_init = kszphy_config_init_led8041,
|
||||
.config_aneg = genphy_config_aneg,
|
||||
.read_status = genphy_read_status,
|
||||
.ack_interrupt = kszphy_ack_interrupt,
|
||||
.config_intr = kszphy_config_intr,
|
||||
.suspend = genphy_suspend,
|
||||
.resume = genphy_resume,
|
||||
.driver = { .owner = THIS_MODULE,},
|
||||
}, {
|
||||
.phy_id = PHY_ID_KSZ8051,
|
||||
.phy_id_mask = 0x00fffff0,
|
||||
.name = "Micrel KSZ8051",
|
||||
.features = (PHY_BASIC_FEATURES | SUPPORTED_Pause
|
||||
| SUPPORTED_Asym_Pause),
|
||||
.flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT,
|
||||
.config_init = ks8051_config_init,
|
||||
.config_aneg = genphy_config_aneg,
|
||||
.read_status = genphy_read_status,
|
||||
.ack_interrupt = kszphy_ack_interrupt,
|
||||
.config_intr = kszphy_config_intr,
|
||||
.suspend = genphy_suspend,
|
||||
.resume = genphy_resume,
|
||||
.driver = { .owner = THIS_MODULE,},
|
||||
}, {
|
||||
.phy_id = PHY_ID_KSZ8001,
|
||||
.name = "Micrel KSZ8001 or KS8721",
|
||||
.phy_id_mask = 0x00ffffff,
|
||||
.features = (PHY_BASIC_FEATURES | SUPPORTED_Pause),
|
||||
.flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT,
|
||||
.config_init = kszphy_config_init_led8041,
|
||||
.config_aneg = genphy_config_aneg,
|
||||
.read_status = genphy_read_status,
|
||||
.ack_interrupt = kszphy_ack_interrupt,
|
||||
.config_intr = kszphy_config_intr,
|
||||
.suspend = genphy_suspend,
|
||||
.resume = genphy_resume,
|
||||
.driver = { .owner = THIS_MODULE,},
|
||||
}, {
|
||||
.phy_id = PHY_ID_KSZ8081,
|
||||
.name = "Micrel KSZ8081 or KSZ8091",
|
||||
.phy_id_mask = 0x00fffff0,
|
||||
.features = (PHY_BASIC_FEATURES | SUPPORTED_Pause),
|
||||
.flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT,
|
||||
.config_init = kszphy_config_init,
|
||||
.config_aneg = genphy_config_aneg,
|
||||
.read_status = genphy_read_status,
|
||||
.ack_interrupt = kszphy_ack_interrupt,
|
||||
.config_intr = kszphy_config_intr,
|
||||
.suspend = genphy_suspend,
|
||||
.resume = genphy_resume,
|
||||
.driver = { .owner = THIS_MODULE,},
|
||||
}, {
|
||||
.phy_id = PHY_ID_KSZ8061,
|
||||
.name = "Micrel KSZ8061",
|
||||
.phy_id_mask = 0x00fffff0,
|
||||
.features = (PHY_BASIC_FEATURES | SUPPORTED_Pause),
|
||||
.flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT,
|
||||
.config_init = kszphy_config_init,
|
||||
.config_aneg = genphy_config_aneg,
|
||||
.read_status = genphy_read_status,
|
||||
.ack_interrupt = kszphy_ack_interrupt,
|
||||
.config_intr = kszphy_config_intr,
|
||||
.suspend = genphy_suspend,
|
||||
.resume = genphy_resume,
|
||||
.driver = { .owner = THIS_MODULE,},
|
||||
}, {
|
||||
.phy_id = PHY_ID_KSZ9021,
|
||||
.phy_id_mask = 0x000ffffe,
|
||||
.name = "Micrel KSZ9021 Gigabit PHY",
|
||||
.features = (PHY_GBIT_FEATURES | SUPPORTED_Pause),
|
||||
.flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT,
|
||||
.config_init = ksz9021_config_init,
|
||||
.config_aneg = genphy_config_aneg,
|
||||
.read_status = genphy_read_status,
|
||||
.ack_interrupt = kszphy_ack_interrupt,
|
||||
.config_intr = ksz9021_config_intr,
|
||||
.suspend = genphy_suspend,
|
||||
.resume = genphy_resume,
|
||||
.read_mmd_indirect = ksz9021_rd_mmd_phyreg,
|
||||
.write_mmd_indirect = ksz9021_wr_mmd_phyreg,
|
||||
.driver = { .owner = THIS_MODULE, },
|
||||
}, {
|
||||
.phy_id = PHY_ID_KSZ9031,
|
||||
.phy_id_mask = 0x00fffff0,
|
||||
.name = "Micrel KSZ9031 Gigabit PHY",
|
||||
.features = (PHY_GBIT_FEATURES | SUPPORTED_Pause),
|
||||
.flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT,
|
||||
.config_init = ksz9031_config_init,
|
||||
.config_aneg = genphy_config_aneg,
|
||||
.read_status = genphy_read_status,
|
||||
.ack_interrupt = kszphy_ack_interrupt,
|
||||
.config_intr = ksz9021_config_intr,
|
||||
.suspend = genphy_suspend,
|
||||
.resume = genphy_resume,
|
||||
.driver = { .owner = THIS_MODULE, },
|
||||
}, {
|
||||
.phy_id = PHY_ID_KSZ8873MLL,
|
||||
.phy_id_mask = 0x00fffff0,
|
||||
.name = "Micrel KSZ8873MLL Switch",
|
||||
.features = (SUPPORTED_Pause | SUPPORTED_Asym_Pause),
|
||||
.flags = PHY_HAS_MAGICANEG,
|
||||
.config_init = kszphy_config_init,
|
||||
.config_aneg = ksz8873mll_config_aneg,
|
||||
.read_status = ksz8873mll_read_status,
|
||||
.suspend = genphy_suspend,
|
||||
.resume = genphy_resume,
|
||||
.driver = { .owner = THIS_MODULE, },
|
||||
}, {
|
||||
.phy_id = PHY_ID_KSZ886X,
|
||||
.phy_id_mask = 0x00fffff0,
|
||||
.name = "Micrel KSZ886X Switch",
|
||||
.features = (PHY_BASIC_FEATURES | SUPPORTED_Pause),
|
||||
.flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT,
|
||||
.config_init = kszphy_config_init,
|
||||
.config_aneg = genphy_config_aneg,
|
||||
.read_status = genphy_read_status,
|
||||
.suspend = genphy_suspend,
|
||||
.resume = genphy_resume,
|
||||
.driver = { .owner = THIS_MODULE, },
|
||||
} };
|
||||
|
||||
static int __init ksphy_init(void)
|
||||
{
|
||||
return phy_drivers_register(ksphy_driver,
|
||||
ARRAY_SIZE(ksphy_driver));
|
||||
}
|
||||
|
||||
static void __exit ksphy_exit(void)
|
||||
{
|
||||
phy_drivers_unregister(ksphy_driver,
|
||||
ARRAY_SIZE(ksphy_driver));
|
||||
}
|
||||
|
||||
module_init(ksphy_init);
|
||||
module_exit(ksphy_exit);
|
||||
|
||||
MODULE_DESCRIPTION("Micrel PHY driver");
|
||||
MODULE_AUTHOR("David J. Choi");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
static struct mdio_device_id __maybe_unused micrel_tbl[] = {
|
||||
{ PHY_ID_KSZ9021, 0x000ffffe },
|
||||
{ PHY_ID_KSZ9031, 0x00fffff0 },
|
||||
{ PHY_ID_KSZ8001, 0x00ffffff },
|
||||
{ PHY_ID_KS8737, 0x00fffff0 },
|
||||
{ PHY_ID_KSZ8021, 0x00ffffff },
|
||||
{ PHY_ID_KSZ8031, 0x00ffffff },
|
||||
{ PHY_ID_KSZ8041, 0x00fffff0 },
|
||||
{ PHY_ID_KSZ8051, 0x00fffff0 },
|
||||
{ PHY_ID_KSZ8061, 0x00fffff0 },
|
||||
{ PHY_ID_KSZ8081, 0x00fffff0 },
|
||||
{ PHY_ID_KSZ8873MLL, 0x00fffff0 },
|
||||
{ PHY_ID_KSZ886X, 0x00fffff0 },
|
||||
{ }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(mdio, micrel_tbl);
|
||||
168
drivers/net/phy/national.c
Normal file
168
drivers/net/phy/national.c
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
/*
|
||||
* drivers/net/phy/national.c
|
||||
*
|
||||
* Driver for National Semiconductor PHYs
|
||||
*
|
||||
* Author: Stuart Menefy <stuart.menefy@st.com>
|
||||
* Maintainer: Giuseppe Cavallaro <peppe.cavallaro@st.com>
|
||||
*
|
||||
* Copyright (c) 2008 STMicroelectronics Limited
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mii.h>
|
||||
#include <linux/ethtool.h>
|
||||
#include <linux/phy.h>
|
||||
#include <linux/netdevice.h>
|
||||
|
||||
#define DEBUG
|
||||
|
||||
/* DP83865 phy identifier values */
|
||||
#define DP83865_PHY_ID 0x20005c7a
|
||||
|
||||
#define DP83865_INT_STATUS 0x14
|
||||
#define DP83865_INT_MASK 0x15
|
||||
#define DP83865_INT_CLEAR 0x17
|
||||
|
||||
#define DP83865_INT_REMOTE_FAULT 0x0008
|
||||
#define DP83865_INT_ANE_COMPLETED 0x0010
|
||||
#define DP83865_INT_LINK_CHANGE 0xe000
|
||||
#define DP83865_INT_MASK_DEFAULT (DP83865_INT_REMOTE_FAULT | \
|
||||
DP83865_INT_ANE_COMPLETED | \
|
||||
DP83865_INT_LINK_CHANGE)
|
||||
|
||||
/* Advanced proprietary configuration */
|
||||
#define NS_EXP_MEM_CTL 0x16
|
||||
#define NS_EXP_MEM_DATA 0x1d
|
||||
#define NS_EXP_MEM_ADD 0x1e
|
||||
|
||||
#define LED_CTRL_REG 0x13
|
||||
#define AN_FALLBACK_AN 0x0001
|
||||
#define AN_FALLBACK_CRC 0x0002
|
||||
#define AN_FALLBACK_IE 0x0004
|
||||
#define ALL_FALLBACK_ON (AN_FALLBACK_AN | AN_FALLBACK_CRC | AN_FALLBACK_IE)
|
||||
|
||||
enum hdx_loopback {
|
||||
hdx_loopback_on = 0,
|
||||
hdx_loopback_off = 1,
|
||||
};
|
||||
|
||||
static u8 ns_exp_read(struct phy_device *phydev, u16 reg)
|
||||
{
|
||||
phy_write(phydev, NS_EXP_MEM_ADD, reg);
|
||||
return phy_read(phydev, NS_EXP_MEM_DATA);
|
||||
}
|
||||
|
||||
static void ns_exp_write(struct phy_device *phydev, u16 reg, u8 data)
|
||||
{
|
||||
phy_write(phydev, NS_EXP_MEM_ADD, reg);
|
||||
phy_write(phydev, NS_EXP_MEM_DATA, data);
|
||||
}
|
||||
|
||||
static int ns_config_intr(struct phy_device *phydev)
|
||||
{
|
||||
int err;
|
||||
|
||||
if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
|
||||
err = phy_write(phydev, DP83865_INT_MASK,
|
||||
DP83865_INT_MASK_DEFAULT);
|
||||
else
|
||||
err = phy_write(phydev, DP83865_INT_MASK, 0);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int ns_ack_interrupt(struct phy_device *phydev)
|
||||
{
|
||||
int ret = phy_read(phydev, DP83865_INT_STATUS);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* Clear the interrupt status bit by writing a “1”
|
||||
* to the corresponding bit in INT_CLEAR (2:0 are reserved) */
|
||||
ret = phy_write(phydev, DP83865_INT_CLEAR, ret & ~0x7);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void ns_giga_speed_fallback(struct phy_device *phydev, int mode)
|
||||
{
|
||||
int bmcr = phy_read(phydev, MII_BMCR);
|
||||
|
||||
phy_write(phydev, MII_BMCR, (bmcr | BMCR_PDOWN));
|
||||
|
||||
/* Enable 8 bit expended memory read/write (no auto increment) */
|
||||
phy_write(phydev, NS_EXP_MEM_CTL, 0);
|
||||
phy_write(phydev, NS_EXP_MEM_ADD, 0x1C0);
|
||||
phy_write(phydev, NS_EXP_MEM_DATA, 0x0008);
|
||||
phy_write(phydev, MII_BMCR, (bmcr & ~BMCR_PDOWN));
|
||||
phy_write(phydev, LED_CTRL_REG, mode);
|
||||
}
|
||||
|
||||
static void ns_10_base_t_hdx_loopack(struct phy_device *phydev, int disable)
|
||||
{
|
||||
if (disable)
|
||||
ns_exp_write(phydev, 0x1c0, ns_exp_read(phydev, 0x1c0) | 1);
|
||||
else
|
||||
ns_exp_write(phydev, 0x1c0,
|
||||
ns_exp_read(phydev, 0x1c0) & 0xfffe);
|
||||
|
||||
pr_debug("10BASE-T HDX loopback %s\n",
|
||||
(ns_exp_read(phydev, 0x1c0) & 0x0001) ? "off" : "on");
|
||||
}
|
||||
|
||||
static int ns_config_init(struct phy_device *phydev)
|
||||
{
|
||||
ns_giga_speed_fallback(phydev, ALL_FALLBACK_ON);
|
||||
/* In the latest MAC or switches design, the 10 Mbps loopback
|
||||
is desired to be turned off. */
|
||||
ns_10_base_t_hdx_loopack(phydev, hdx_loopback_off);
|
||||
return ns_ack_interrupt(phydev);
|
||||
}
|
||||
|
||||
static struct phy_driver dp83865_driver = {
|
||||
.phy_id = DP83865_PHY_ID,
|
||||
.phy_id_mask = 0xfffffff0,
|
||||
.name = "NatSemi DP83865",
|
||||
.features = PHY_GBIT_FEATURES | SUPPORTED_Pause | SUPPORTED_Asym_Pause,
|
||||
.flags = PHY_HAS_INTERRUPT,
|
||||
.config_init = ns_config_init,
|
||||
.config_aneg = genphy_config_aneg,
|
||||
.read_status = genphy_read_status,
|
||||
.ack_interrupt = ns_ack_interrupt,
|
||||
.config_intr = ns_config_intr,
|
||||
.driver = {.owner = THIS_MODULE,}
|
||||
};
|
||||
|
||||
static int __init ns_init(void)
|
||||
{
|
||||
return phy_driver_register(&dp83865_driver);
|
||||
}
|
||||
|
||||
static void __exit ns_exit(void)
|
||||
{
|
||||
phy_driver_unregister(&dp83865_driver);
|
||||
}
|
||||
|
||||
MODULE_DESCRIPTION("NatSemi PHY driver");
|
||||
MODULE_AUTHOR("Stuart Menefy");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
module_init(ns_init);
|
||||
module_exit(ns_exit);
|
||||
|
||||
static struct mdio_device_id __maybe_unused ns_tbl[] = {
|
||||
{ DP83865_PHY_ID, 0xfffffff0 },
|
||||
{ }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(mdio, ns_tbl);
|
||||
1204
drivers/net/phy/phy.c
Normal file
1204
drivers/net/phy/phy.c
Normal file
File diff suppressed because it is too large
Load diff
1392
drivers/net/phy/phy_device.c
Normal file
1392
drivers/net/phy/phy_device.c
Normal file
File diff suppressed because it is too large
Load diff
146
drivers/net/phy/qsemi.c
Normal file
146
drivers/net/phy/qsemi.c
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
* drivers/net/phy/qsemi.c
|
||||
*
|
||||
* Driver for Quality Semiconductor 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/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>
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* The Quality Semiconductor QS6612 is used on the RPX CLLF */
|
||||
|
||||
/* register definitions */
|
||||
|
||||
#define MII_QS6612_MCR 17 /* Mode Control Register */
|
||||
#define MII_QS6612_FTR 27 /* Factory Test Register */
|
||||
#define MII_QS6612_MCO 28 /* Misc. Control Register */
|
||||
#define MII_QS6612_ISR 29 /* Interrupt Source Register */
|
||||
#define MII_QS6612_IMR 30 /* Interrupt Mask Register */
|
||||
#define MII_QS6612_IMR_INIT 0x003a
|
||||
#define MII_QS6612_PCR 31 /* 100BaseTx PHY Control Reg. */
|
||||
|
||||
#define QS6612_PCR_AN_COMPLETE 0x1000
|
||||
#define QS6612_PCR_RLBEN 0x0200
|
||||
#define QS6612_PCR_DCREN 0x0100
|
||||
#define QS6612_PCR_4B5BEN 0x0040
|
||||
#define QS6612_PCR_TX_ISOLATE 0x0020
|
||||
#define QS6612_PCR_MLT3_DIS 0x0002
|
||||
#define QS6612_PCR_SCRM_DESCRM 0x0001
|
||||
|
||||
MODULE_DESCRIPTION("Quality Semiconductor PHY driver");
|
||||
MODULE_AUTHOR("Andy Fleming");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
/* Returns 0, unless there's a write error */
|
||||
static int qs6612_config_init(struct phy_device *phydev)
|
||||
{
|
||||
/* The PHY powers up isolated on the RPX,
|
||||
* so send a command to allow operation.
|
||||
* XXX - My docs indicate this should be 0x0940
|
||||
* ...or something. The current value sets three
|
||||
* reserved bits, bit 11, which specifies it should be
|
||||
* set to one, bit 10, which specifies it should be set
|
||||
* to 0, and bit 7, which doesn't specify. However, my
|
||||
* docs are preliminary, and I will leave it like this
|
||||
* until someone more knowledgable corrects me or it.
|
||||
* -- Andy Fleming
|
||||
*/
|
||||
return phy_write(phydev, MII_QS6612_PCR, 0x0dc0);
|
||||
}
|
||||
|
||||
static int qs6612_ack_interrupt(struct phy_device *phydev)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = phy_read(phydev, MII_QS6612_ISR);
|
||||
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = phy_read(phydev, MII_BMSR);
|
||||
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = phy_read(phydev, MII_EXPANSION);
|
||||
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int qs6612_config_intr(struct phy_device *phydev)
|
||||
{
|
||||
int err;
|
||||
if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
|
||||
err = phy_write(phydev, MII_QS6612_IMR,
|
||||
MII_QS6612_IMR_INIT);
|
||||
else
|
||||
err = phy_write(phydev, MII_QS6612_IMR, 0);
|
||||
|
||||
return err;
|
||||
|
||||
}
|
||||
|
||||
static struct phy_driver qs6612_driver = {
|
||||
.phy_id = 0x00181440,
|
||||
.name = "QS6612",
|
||||
.phy_id_mask = 0xfffffff0,
|
||||
.features = PHY_BASIC_FEATURES,
|
||||
.flags = PHY_HAS_INTERRUPT,
|
||||
.config_init = qs6612_config_init,
|
||||
.config_aneg = genphy_config_aneg,
|
||||
.read_status = genphy_read_status,
|
||||
.ack_interrupt = qs6612_ack_interrupt,
|
||||
.config_intr = qs6612_config_intr,
|
||||
.driver = { .owner = THIS_MODULE,},
|
||||
};
|
||||
|
||||
static int __init qs6612_init(void)
|
||||
{
|
||||
return phy_driver_register(&qs6612_driver);
|
||||
}
|
||||
|
||||
static void __exit qs6612_exit(void)
|
||||
{
|
||||
phy_driver_unregister(&qs6612_driver);
|
||||
}
|
||||
|
||||
module_init(qs6612_init);
|
||||
module_exit(qs6612_exit);
|
||||
|
||||
static struct mdio_device_id __maybe_unused qs6612_tbl[] = {
|
||||
{ 0x00181440, 0xfffffff0 },
|
||||
{ }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(mdio, qs6612_tbl);
|
||||
123
drivers/net/phy/realtek.c
Normal file
123
drivers/net/phy/realtek.c
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* drivers/net/phy/realtek.c
|
||||
*
|
||||
* Driver for Realtek PHYs
|
||||
*
|
||||
* Author: Johnson Leung <r58129@freescale.com>
|
||||
*
|
||||
* 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/phy.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
#define RTL821x_PHYSR 0x11
|
||||
#define RTL821x_PHYSR_DUPLEX 0x2000
|
||||
#define RTL821x_PHYSR_SPEED 0xc000
|
||||
#define RTL821x_INER 0x12
|
||||
#define RTL821x_INER_INIT 0x6400
|
||||
#define RTL821x_INSR 0x13
|
||||
|
||||
#define RTL8211E_INER_LINK_STATUS 0x400
|
||||
|
||||
MODULE_DESCRIPTION("Realtek PHY driver");
|
||||
MODULE_AUTHOR("Johnson Leung");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
static int rtl821x_ack_interrupt(struct phy_device *phydev)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = phy_read(phydev, RTL821x_INSR);
|
||||
|
||||
return (err < 0) ? err : 0;
|
||||
}
|
||||
|
||||
static int rtl8211b_config_intr(struct phy_device *phydev)
|
||||
{
|
||||
int err;
|
||||
|
||||
if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
|
||||
err = phy_write(phydev, RTL821x_INER,
|
||||
RTL821x_INER_INIT);
|
||||
else
|
||||
err = phy_write(phydev, RTL821x_INER, 0);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int rtl8211e_config_intr(struct phy_device *phydev)
|
||||
{
|
||||
int err;
|
||||
|
||||
if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
|
||||
err = phy_write(phydev, RTL821x_INER,
|
||||
RTL8211E_INER_LINK_STATUS);
|
||||
else
|
||||
err = phy_write(phydev, RTL821x_INER, 0);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static struct phy_driver realtek_drvs[] = {
|
||||
{
|
||||
.phy_id = 0x00008201,
|
||||
.name = "RTL8201CP Ethernet",
|
||||
.phy_id_mask = 0x0000ffff,
|
||||
.features = PHY_BASIC_FEATURES,
|
||||
.flags = PHY_HAS_INTERRUPT,
|
||||
.config_aneg = &genphy_config_aneg,
|
||||
.read_status = &genphy_read_status,
|
||||
.driver = { .owner = THIS_MODULE,},
|
||||
}, {
|
||||
.phy_id = 0x001cc912,
|
||||
.name = "RTL8211B Gigabit Ethernet",
|
||||
.phy_id_mask = 0x001fffff,
|
||||
.features = PHY_GBIT_FEATURES,
|
||||
.flags = PHY_HAS_INTERRUPT,
|
||||
.config_aneg = &genphy_config_aneg,
|
||||
.read_status = &genphy_read_status,
|
||||
.ack_interrupt = &rtl821x_ack_interrupt,
|
||||
.config_intr = &rtl8211b_config_intr,
|
||||
.driver = { .owner = THIS_MODULE,},
|
||||
}, {
|
||||
.phy_id = 0x001cc915,
|
||||
.name = "RTL8211E Gigabit Ethernet",
|
||||
.phy_id_mask = 0x001fffff,
|
||||
.features = PHY_GBIT_FEATURES,
|
||||
.flags = PHY_HAS_INTERRUPT,
|
||||
.config_aneg = &genphy_config_aneg,
|
||||
.read_status = &genphy_read_status,
|
||||
.ack_interrupt = &rtl821x_ack_interrupt,
|
||||
.config_intr = &rtl8211e_config_intr,
|
||||
.suspend = genphy_suspend,
|
||||
.resume = genphy_resume,
|
||||
.driver = { .owner = THIS_MODULE,},
|
||||
},
|
||||
};
|
||||
|
||||
static int __init realtek_init(void)
|
||||
{
|
||||
return phy_drivers_register(realtek_drvs, ARRAY_SIZE(realtek_drvs));
|
||||
}
|
||||
|
||||
static void __exit realtek_exit(void)
|
||||
{
|
||||
phy_drivers_unregister(realtek_drvs, ARRAY_SIZE(realtek_drvs));
|
||||
}
|
||||
|
||||
module_init(realtek_init);
|
||||
module_exit(realtek_exit);
|
||||
|
||||
static struct mdio_device_id __maybe_unused realtek_tbl[] = {
|
||||
{ 0x001cc912, 0x001fffff },
|
||||
{ 0x001cc915, 0x001fffff },
|
||||
{ }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(mdio, realtek_tbl);
|
||||
280
drivers/net/phy/smsc.c
Normal file
280
drivers/net/phy/smsc.c
Normal file
|
|
@ -0,0 +1,280 @@
|
|||
/*
|
||||
* drivers/net/phy/smsc.c
|
||||
*
|
||||
* Driver for SMSC PHYs
|
||||
*
|
||||
* Author: Herbert Valerio Riedel
|
||||
*
|
||||
* Copyright (c) 2006 Herbert Valerio Riedel <hvr@gnu.org>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Support added for SMSC LAN8187 and LAN8700 by steve.glendinning@shawell.net
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mii.h>
|
||||
#include <linux/ethtool.h>
|
||||
#include <linux/phy.h>
|
||||
#include <linux/netdevice.h>
|
||||
#include <linux/smscphy.h>
|
||||
|
||||
static int smsc_phy_config_intr(struct phy_device *phydev)
|
||||
{
|
||||
int rc = phy_write (phydev, MII_LAN83C185_IM,
|
||||
((PHY_INTERRUPT_ENABLED == phydev->interrupts)
|
||||
? MII_LAN83C185_ISF_INT_PHYLIB_EVENTS
|
||||
: 0));
|
||||
|
||||
return rc < 0 ? rc : 0;
|
||||
}
|
||||
|
||||
static int smsc_phy_ack_interrupt(struct phy_device *phydev)
|
||||
{
|
||||
int rc = phy_read (phydev, MII_LAN83C185_ISF);
|
||||
|
||||
return rc < 0 ? rc : 0;
|
||||
}
|
||||
|
||||
static int smsc_phy_config_init(struct phy_device *phydev)
|
||||
{
|
||||
int rc = phy_read(phydev, MII_LAN83C185_CTRL_STATUS);
|
||||
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
/* Enable energy detect mode for this SMSC Transceivers */
|
||||
rc = phy_write(phydev, MII_LAN83C185_CTRL_STATUS,
|
||||
rc | MII_LAN83C185_EDPWRDOWN);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
return smsc_phy_ack_interrupt(phydev);
|
||||
}
|
||||
|
||||
static int smsc_phy_reset(struct phy_device *phydev)
|
||||
{
|
||||
int rc = phy_read(phydev, MII_LAN83C185_SPECIAL_MODES);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
/* If the SMSC PHY is in power down mode, then set it
|
||||
* in all capable mode before using it.
|
||||
*/
|
||||
if ((rc & MII_LAN83C185_MODE_MASK) == MII_LAN83C185_MODE_POWERDOWN) {
|
||||
int timeout = 50000;
|
||||
|
||||
/* set "all capable" mode and reset the phy */
|
||||
rc |= MII_LAN83C185_MODE_ALL;
|
||||
phy_write(phydev, MII_LAN83C185_SPECIAL_MODES, rc);
|
||||
phy_write(phydev, MII_BMCR, BMCR_RESET);
|
||||
|
||||
/* wait end of reset (max 500 ms) */
|
||||
do {
|
||||
udelay(10);
|
||||
if (timeout-- == 0)
|
||||
return -1;
|
||||
rc = phy_read(phydev, MII_BMCR);
|
||||
} while (rc & BMCR_RESET);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lan911x_config_init(struct phy_device *phydev)
|
||||
{
|
||||
return smsc_phy_ack_interrupt(phydev);
|
||||
}
|
||||
|
||||
/*
|
||||
* The LAN8710/LAN8720 requires a minimum of 2 link pulses within 64ms of each
|
||||
* other in order to set the ENERGYON bit and exit EDPD mode. If a link partner
|
||||
* does send the pulses within this interval, the PHY will remained powered
|
||||
* down.
|
||||
*
|
||||
* This workaround will manually toggle the PHY on/off upon calls to read_status
|
||||
* in order to generate link test pulses if the link is down. If a link partner
|
||||
* is present, it will respond to the pulses, which will cause the ENERGYON bit
|
||||
* to be set and will cause the EDPD mode to be exited.
|
||||
*/
|
||||
static int lan87xx_read_status(struct phy_device *phydev)
|
||||
{
|
||||
int err = genphy_read_status(phydev);
|
||||
|
||||
if (!phydev->link) {
|
||||
/* Disable EDPD to wake up PHY */
|
||||
int rc = phy_read(phydev, MII_LAN83C185_CTRL_STATUS);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
rc = phy_write(phydev, MII_LAN83C185_CTRL_STATUS,
|
||||
rc & ~MII_LAN83C185_EDPWRDOWN);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
/* Sleep 64 ms to allow ~5 link test pulses to be sent */
|
||||
msleep(64);
|
||||
|
||||
/* Re-enable EDPD */
|
||||
rc = phy_read(phydev, MII_LAN83C185_CTRL_STATUS);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
rc = phy_write(phydev, MII_LAN83C185_CTRL_STATUS,
|
||||
rc | MII_LAN83C185_EDPWRDOWN);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static struct phy_driver smsc_phy_driver[] = {
|
||||
{
|
||||
.phy_id = 0x0007c0a0, /* OUI=0x00800f, Model#=0x0a */
|
||||
.phy_id_mask = 0xfffffff0,
|
||||
.name = "SMSC LAN83C185",
|
||||
|
||||
.features = (PHY_BASIC_FEATURES | SUPPORTED_Pause
|
||||
| SUPPORTED_Asym_Pause),
|
||||
.flags = PHY_HAS_INTERRUPT | PHY_HAS_MAGICANEG,
|
||||
|
||||
/* basic functions */
|
||||
.config_aneg = genphy_config_aneg,
|
||||
.read_status = genphy_read_status,
|
||||
.config_init = smsc_phy_config_init,
|
||||
.soft_reset = smsc_phy_reset,
|
||||
|
||||
/* IRQ related */
|
||||
.ack_interrupt = smsc_phy_ack_interrupt,
|
||||
.config_intr = smsc_phy_config_intr,
|
||||
|
||||
.suspend = genphy_suspend,
|
||||
.resume = genphy_resume,
|
||||
|
||||
.driver = { .owner = THIS_MODULE, }
|
||||
}, {
|
||||
.phy_id = 0x0007c0b0, /* OUI=0x00800f, Model#=0x0b */
|
||||
.phy_id_mask = 0xfffffff0,
|
||||
.name = "SMSC LAN8187",
|
||||
|
||||
.features = (PHY_BASIC_FEATURES | SUPPORTED_Pause
|
||||
| SUPPORTED_Asym_Pause),
|
||||
.flags = PHY_HAS_INTERRUPT | PHY_HAS_MAGICANEG,
|
||||
|
||||
/* basic functions */
|
||||
.config_aneg = genphy_config_aneg,
|
||||
.read_status = genphy_read_status,
|
||||
.config_init = smsc_phy_config_init,
|
||||
.soft_reset = smsc_phy_reset,
|
||||
|
||||
/* IRQ related */
|
||||
.ack_interrupt = smsc_phy_ack_interrupt,
|
||||
.config_intr = smsc_phy_config_intr,
|
||||
|
||||
.suspend = genphy_suspend,
|
||||
.resume = genphy_resume,
|
||||
|
||||
.driver = { .owner = THIS_MODULE, }
|
||||
}, {
|
||||
.phy_id = 0x0007c0c0, /* OUI=0x00800f, Model#=0x0c */
|
||||
.phy_id_mask = 0xfffffff0,
|
||||
.name = "SMSC LAN8700",
|
||||
|
||||
.features = (PHY_BASIC_FEATURES | SUPPORTED_Pause
|
||||
| SUPPORTED_Asym_Pause),
|
||||
.flags = PHY_HAS_INTERRUPT | PHY_HAS_MAGICANEG,
|
||||
|
||||
/* basic functions */
|
||||
.config_aneg = genphy_config_aneg,
|
||||
.read_status = genphy_read_status,
|
||||
.config_init = smsc_phy_config_init,
|
||||
.soft_reset = smsc_phy_reset,
|
||||
|
||||
/* IRQ related */
|
||||
.ack_interrupt = smsc_phy_ack_interrupt,
|
||||
.config_intr = smsc_phy_config_intr,
|
||||
|
||||
.suspend = genphy_suspend,
|
||||
.resume = genphy_resume,
|
||||
|
||||
.driver = { .owner = THIS_MODULE, }
|
||||
}, {
|
||||
.phy_id = 0x0007c0d0, /* OUI=0x00800f, Model#=0x0d */
|
||||
.phy_id_mask = 0xfffffff0,
|
||||
.name = "SMSC LAN911x Internal PHY",
|
||||
|
||||
.features = (PHY_BASIC_FEATURES | SUPPORTED_Pause
|
||||
| SUPPORTED_Asym_Pause),
|
||||
.flags = PHY_HAS_INTERRUPT | PHY_HAS_MAGICANEG,
|
||||
|
||||
/* basic functions */
|
||||
.config_aneg = genphy_config_aneg,
|
||||
.read_status = genphy_read_status,
|
||||
.config_init = lan911x_config_init,
|
||||
|
||||
/* IRQ related */
|
||||
.ack_interrupt = smsc_phy_ack_interrupt,
|
||||
.config_intr = smsc_phy_config_intr,
|
||||
|
||||
.suspend = genphy_suspend,
|
||||
.resume = genphy_resume,
|
||||
|
||||
.driver = { .owner = THIS_MODULE, }
|
||||
}, {
|
||||
.phy_id = 0x0007c0f0, /* OUI=0x00800f, Model#=0x0f */
|
||||
.phy_id_mask = 0xfffffff0,
|
||||
.name = "SMSC LAN8710/LAN8720",
|
||||
|
||||
.features = (PHY_BASIC_FEATURES | SUPPORTED_Pause
|
||||
| SUPPORTED_Asym_Pause),
|
||||
.flags = PHY_HAS_INTERRUPT | PHY_HAS_MAGICANEG,
|
||||
|
||||
/* basic functions */
|
||||
.config_aneg = genphy_config_aneg,
|
||||
.read_status = lan87xx_read_status,
|
||||
.config_init = smsc_phy_config_init,
|
||||
.soft_reset = smsc_phy_reset,
|
||||
|
||||
/* IRQ related */
|
||||
.ack_interrupt = smsc_phy_ack_interrupt,
|
||||
.config_intr = smsc_phy_config_intr,
|
||||
|
||||
.suspend = genphy_suspend,
|
||||
.resume = genphy_resume,
|
||||
|
||||
.driver = { .owner = THIS_MODULE, }
|
||||
} };
|
||||
|
||||
static int __init smsc_init(void)
|
||||
{
|
||||
return phy_drivers_register(smsc_phy_driver,
|
||||
ARRAY_SIZE(smsc_phy_driver));
|
||||
}
|
||||
|
||||
static void __exit smsc_exit(void)
|
||||
{
|
||||
phy_drivers_unregister(smsc_phy_driver, ARRAY_SIZE(smsc_phy_driver));
|
||||
}
|
||||
|
||||
MODULE_DESCRIPTION("SMSC PHY driver");
|
||||
MODULE_AUTHOR("Herbert Valerio Riedel");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
module_init(smsc_init);
|
||||
module_exit(smsc_exit);
|
||||
|
||||
static struct mdio_device_id __maybe_unused smsc_tbl[] = {
|
||||
{ 0x0007c0a0, 0xfffffff0 },
|
||||
{ 0x0007c0b0, 0xfffffff0 },
|
||||
{ 0x0007c0c0, 0xfffffff0 },
|
||||
{ 0x0007c0d0, 0xfffffff0 },
|
||||
{ 0x0007c0f0, 0xfffffff0 },
|
||||
{ }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(mdio, smsc_tbl);
|
||||
377
drivers/net/phy/spi_ks8995.c
Normal file
377
drivers/net/phy/spi_ks8995.c
Normal file
|
|
@ -0,0 +1,377 @@
|
|||
/*
|
||||
* SPI driver for Micrel/Kendin KS8995M and KSZ8864RMN ethernet switches
|
||||
*
|
||||
* Copyright (C) 2008 Gabor Juhos <juhosg at openwrt.org>
|
||||
*
|
||||
* This file was based on: drivers/spi/at25.c
|
||||
* Copyright (C) 2006 David Brownell
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published
|
||||
* by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
|
||||
#include <linux/spi/spi.h>
|
||||
|
||||
#define DRV_VERSION "0.1.1"
|
||||
#define DRV_DESC "Micrel KS8995 Ethernet switch SPI driver"
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
#define KS8995_REG_ID0 0x00 /* Chip ID0 */
|
||||
#define KS8995_REG_ID1 0x01 /* Chip ID1 */
|
||||
|
||||
#define KS8995_REG_GC0 0x02 /* Global Control 0 */
|
||||
#define KS8995_REG_GC1 0x03 /* Global Control 1 */
|
||||
#define KS8995_REG_GC2 0x04 /* Global Control 2 */
|
||||
#define KS8995_REG_GC3 0x05 /* Global Control 3 */
|
||||
#define KS8995_REG_GC4 0x06 /* Global Control 4 */
|
||||
#define KS8995_REG_GC5 0x07 /* Global Control 5 */
|
||||
#define KS8995_REG_GC6 0x08 /* Global Control 6 */
|
||||
#define KS8995_REG_GC7 0x09 /* Global Control 7 */
|
||||
#define KS8995_REG_GC8 0x0a /* Global Control 8 */
|
||||
#define KS8995_REG_GC9 0x0b /* Global Control 9 */
|
||||
|
||||
#define KS8995_REG_PC(p, r) ((0x10 * p) + r) /* Port Control */
|
||||
#define KS8995_REG_PS(p, r) ((0x10 * p) + r + 0xe) /* Port Status */
|
||||
|
||||
#define KS8995_REG_TPC0 0x60 /* TOS Priority Control 0 */
|
||||
#define KS8995_REG_TPC1 0x61 /* TOS Priority Control 1 */
|
||||
#define KS8995_REG_TPC2 0x62 /* TOS Priority Control 2 */
|
||||
#define KS8995_REG_TPC3 0x63 /* TOS Priority Control 3 */
|
||||
#define KS8995_REG_TPC4 0x64 /* TOS Priority Control 4 */
|
||||
#define KS8995_REG_TPC5 0x65 /* TOS Priority Control 5 */
|
||||
#define KS8995_REG_TPC6 0x66 /* TOS Priority Control 6 */
|
||||
#define KS8995_REG_TPC7 0x67 /* TOS Priority Control 7 */
|
||||
|
||||
#define KS8995_REG_MAC0 0x68 /* MAC address 0 */
|
||||
#define KS8995_REG_MAC1 0x69 /* MAC address 1 */
|
||||
#define KS8995_REG_MAC2 0x6a /* MAC address 2 */
|
||||
#define KS8995_REG_MAC3 0x6b /* MAC address 3 */
|
||||
#define KS8995_REG_MAC4 0x6c /* MAC address 4 */
|
||||
#define KS8995_REG_MAC5 0x6d /* MAC address 5 */
|
||||
|
||||
#define KS8995_REG_IAC0 0x6e /* Indirect Access Control 0 */
|
||||
#define KS8995_REG_IAC1 0x6f /* Indirect Access Control 0 */
|
||||
#define KS8995_REG_IAD7 0x70 /* Indirect Access Data 7 */
|
||||
#define KS8995_REG_IAD6 0x71 /* Indirect Access Data 6 */
|
||||
#define KS8995_REG_IAD5 0x72 /* Indirect Access Data 5 */
|
||||
#define KS8995_REG_IAD4 0x73 /* Indirect Access Data 4 */
|
||||
#define KS8995_REG_IAD3 0x74 /* Indirect Access Data 3 */
|
||||
#define KS8995_REG_IAD2 0x75 /* Indirect Access Data 2 */
|
||||
#define KS8995_REG_IAD1 0x76 /* Indirect Access Data 1 */
|
||||
#define KS8995_REG_IAD0 0x77 /* Indirect Access Data 0 */
|
||||
|
||||
#define KSZ8864_REG_ID1 0xfe /* Chip ID in bit 7 */
|
||||
|
||||
#define KS8995_REGS_SIZE 0x80
|
||||
#define KSZ8864_REGS_SIZE 0x100
|
||||
|
||||
#define ID1_CHIPID_M 0xf
|
||||
#define ID1_CHIPID_S 4
|
||||
#define ID1_REVISION_M 0x7
|
||||
#define ID1_REVISION_S 1
|
||||
#define ID1_START_SW 1 /* start the switch */
|
||||
|
||||
#define FAMILY_KS8995 0x95
|
||||
#define CHIPID_M 0
|
||||
|
||||
#define KS8995_CMD_WRITE 0x02U
|
||||
#define KS8995_CMD_READ 0x03U
|
||||
|
||||
#define KS8995_RESET_DELAY 10 /* usec */
|
||||
|
||||
struct ks8995_pdata {
|
||||
/* not yet implemented */
|
||||
};
|
||||
|
||||
struct ks8995_switch {
|
||||
struct spi_device *spi;
|
||||
struct mutex lock;
|
||||
struct ks8995_pdata *pdata;
|
||||
struct bin_attribute regs_attr;
|
||||
};
|
||||
|
||||
static inline u8 get_chip_id(u8 val)
|
||||
{
|
||||
return (val >> ID1_CHIPID_S) & ID1_CHIPID_M;
|
||||
}
|
||||
|
||||
static inline u8 get_chip_rev(u8 val)
|
||||
{
|
||||
return (val >> ID1_REVISION_S) & ID1_REVISION_M;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
static int ks8995_read(struct ks8995_switch *ks, char *buf,
|
||||
unsigned offset, size_t count)
|
||||
{
|
||||
u8 cmd[2];
|
||||
struct spi_transfer t[2];
|
||||
struct spi_message m;
|
||||
int err;
|
||||
|
||||
spi_message_init(&m);
|
||||
|
||||
memset(&t, 0, sizeof(t));
|
||||
|
||||
t[0].tx_buf = cmd;
|
||||
t[0].len = sizeof(cmd);
|
||||
spi_message_add_tail(&t[0], &m);
|
||||
|
||||
t[1].rx_buf = buf;
|
||||
t[1].len = count;
|
||||
spi_message_add_tail(&t[1], &m);
|
||||
|
||||
cmd[0] = KS8995_CMD_READ;
|
||||
cmd[1] = offset;
|
||||
|
||||
mutex_lock(&ks->lock);
|
||||
err = spi_sync(ks->spi, &m);
|
||||
mutex_unlock(&ks->lock);
|
||||
|
||||
return err ? err : count;
|
||||
}
|
||||
|
||||
|
||||
static int ks8995_write(struct ks8995_switch *ks, char *buf,
|
||||
unsigned offset, size_t count)
|
||||
{
|
||||
u8 cmd[2];
|
||||
struct spi_transfer t[2];
|
||||
struct spi_message m;
|
||||
int err;
|
||||
|
||||
spi_message_init(&m);
|
||||
|
||||
memset(&t, 0, sizeof(t));
|
||||
|
||||
t[0].tx_buf = cmd;
|
||||
t[0].len = sizeof(cmd);
|
||||
spi_message_add_tail(&t[0], &m);
|
||||
|
||||
t[1].tx_buf = buf;
|
||||
t[1].len = count;
|
||||
spi_message_add_tail(&t[1], &m);
|
||||
|
||||
cmd[0] = KS8995_CMD_WRITE;
|
||||
cmd[1] = offset;
|
||||
|
||||
mutex_lock(&ks->lock);
|
||||
err = spi_sync(ks->spi, &m);
|
||||
mutex_unlock(&ks->lock);
|
||||
|
||||
return err ? err : count;
|
||||
}
|
||||
|
||||
static inline int ks8995_read_reg(struct ks8995_switch *ks, u8 addr, u8 *buf)
|
||||
{
|
||||
return ks8995_read(ks, buf, addr, 1) != 1;
|
||||
}
|
||||
|
||||
static inline int ks8995_write_reg(struct ks8995_switch *ks, u8 addr, u8 val)
|
||||
{
|
||||
char buf = val;
|
||||
|
||||
return ks8995_write(ks, &buf, addr, 1) != 1;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
static int ks8995_stop(struct ks8995_switch *ks)
|
||||
{
|
||||
return ks8995_write_reg(ks, KS8995_REG_ID1, 0);
|
||||
}
|
||||
|
||||
static int ks8995_start(struct ks8995_switch *ks)
|
||||
{
|
||||
return ks8995_write_reg(ks, KS8995_REG_ID1, 1);
|
||||
}
|
||||
|
||||
static int ks8995_reset(struct ks8995_switch *ks)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = ks8995_stop(ks);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
udelay(KS8995_RESET_DELAY);
|
||||
|
||||
return ks8995_start(ks);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
static ssize_t ks8995_registers_read(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr, char *buf, loff_t off, size_t count)
|
||||
{
|
||||
struct device *dev;
|
||||
struct ks8995_switch *ks8995;
|
||||
|
||||
dev = container_of(kobj, struct device, kobj);
|
||||
ks8995 = dev_get_drvdata(dev);
|
||||
|
||||
if (unlikely(off > ks8995->regs_attr.size))
|
||||
return 0;
|
||||
|
||||
if ((off + count) > ks8995->regs_attr.size)
|
||||
count = ks8995->regs_attr.size - off;
|
||||
|
||||
if (unlikely(!count))
|
||||
return count;
|
||||
|
||||
return ks8995_read(ks8995, buf, off, count);
|
||||
}
|
||||
|
||||
|
||||
static ssize_t ks8995_registers_write(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr, char *buf, loff_t off, size_t count)
|
||||
{
|
||||
struct device *dev;
|
||||
struct ks8995_switch *ks8995;
|
||||
|
||||
dev = container_of(kobj, struct device, kobj);
|
||||
ks8995 = dev_get_drvdata(dev);
|
||||
|
||||
if (unlikely(off >= ks8995->regs_attr.size))
|
||||
return -EFBIG;
|
||||
|
||||
if ((off + count) > ks8995->regs_attr.size)
|
||||
count = ks8995->regs_attr.size - off;
|
||||
|
||||
if (unlikely(!count))
|
||||
return count;
|
||||
|
||||
return ks8995_write(ks8995, buf, off, count);
|
||||
}
|
||||
|
||||
|
||||
static const struct bin_attribute ks8995_registers_attr = {
|
||||
.attr = {
|
||||
.name = "registers",
|
||||
.mode = S_IRUSR | S_IWUSR,
|
||||
},
|
||||
.size = KS8995_REGS_SIZE,
|
||||
.read = ks8995_registers_read,
|
||||
.write = ks8995_registers_write,
|
||||
};
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
static int ks8995_probe(struct spi_device *spi)
|
||||
{
|
||||
struct ks8995_switch *ks;
|
||||
struct ks8995_pdata *pdata;
|
||||
u8 ids[2];
|
||||
int err;
|
||||
|
||||
/* Chip description */
|
||||
pdata = spi->dev.platform_data;
|
||||
|
||||
ks = devm_kzalloc(&spi->dev, sizeof(*ks), GFP_KERNEL);
|
||||
if (!ks)
|
||||
return -ENOMEM;
|
||||
|
||||
mutex_init(&ks->lock);
|
||||
ks->pdata = pdata;
|
||||
ks->spi = spi_dev_get(spi);
|
||||
spi_set_drvdata(spi, ks);
|
||||
|
||||
spi->mode = SPI_MODE_0;
|
||||
spi->bits_per_word = 8;
|
||||
err = spi_setup(spi);
|
||||
if (err) {
|
||||
dev_err(&spi->dev, "spi_setup failed, err=%d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = ks8995_read(ks, ids, KS8995_REG_ID0, sizeof(ids));
|
||||
if (err < 0) {
|
||||
dev_err(&spi->dev, "unable to read id registers, err=%d\n",
|
||||
err);
|
||||
return err;
|
||||
}
|
||||
|
||||
switch (ids[0]) {
|
||||
case FAMILY_KS8995:
|
||||
break;
|
||||
default:
|
||||
dev_err(&spi->dev, "unknown family id:%02x\n", ids[0]);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
memcpy(&ks->regs_attr, &ks8995_registers_attr, sizeof(ks->regs_attr));
|
||||
if (get_chip_id(ids[1]) != CHIPID_M) {
|
||||
u8 val;
|
||||
|
||||
/* Check if this is a KSZ8864RMN */
|
||||
err = ks8995_read(ks, &val, KSZ8864_REG_ID1, sizeof(val));
|
||||
if (err < 0) {
|
||||
dev_err(&spi->dev,
|
||||
"unable to read chip id register, err=%d\n",
|
||||
err);
|
||||
return err;
|
||||
}
|
||||
if ((val & 0x80) == 0) {
|
||||
dev_err(&spi->dev, "unknown chip:%02x,0\n", ids[1]);
|
||||
return err;
|
||||
}
|
||||
ks->regs_attr.size = KSZ8864_REGS_SIZE;
|
||||
}
|
||||
|
||||
err = ks8995_reset(ks);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
err = sysfs_create_bin_file(&spi->dev.kobj, &ks->regs_attr);
|
||||
if (err) {
|
||||
dev_err(&spi->dev, "unable to create sysfs file, err=%d\n",
|
||||
err);
|
||||
return err;
|
||||
}
|
||||
|
||||
if (get_chip_id(ids[1]) == CHIPID_M) {
|
||||
dev_info(&spi->dev,
|
||||
"KS8995 device found, Chip ID:%x, Revision:%x\n",
|
||||
get_chip_id(ids[1]), get_chip_rev(ids[1]));
|
||||
} else {
|
||||
dev_info(&spi->dev, "KSZ8864 device found, Revision:%x\n",
|
||||
get_chip_rev(ids[1]));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ks8995_remove(struct spi_device *spi)
|
||||
{
|
||||
sysfs_remove_bin_file(&spi->dev.kobj, &ks8995_registers_attr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
static struct spi_driver ks8995_driver = {
|
||||
.driver = {
|
||||
.name = "spi-ks8995",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = ks8995_probe,
|
||||
.remove = ks8995_remove,
|
||||
};
|
||||
|
||||
module_spi_driver(ks8995_driver);
|
||||
|
||||
MODULE_DESCRIPTION(DRV_DESC);
|
||||
MODULE_VERSION(DRV_VERSION);
|
||||
MODULE_AUTHOR("Gabor Juhos <juhosg at openwrt.org>");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
140
drivers/net/phy/ste10Xp.c
Normal file
140
drivers/net/phy/ste10Xp.c
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* drivers/net/phy/ste10Xp.c
|
||||
*
|
||||
* Driver for STMicroelectronics STe10Xp PHYs
|
||||
*
|
||||
* Author: Giuseppe Cavallaro <peppe.cavallaro@st.com>
|
||||
*
|
||||
* Copyright (c) 2008 STMicroelectronics Limited
|
||||
*
|
||||
* 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/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/netdevice.h>
|
||||
#include <linux/ethtool.h>
|
||||
#include <linux/mii.h>
|
||||
#include <linux/phy.h>
|
||||
|
||||
#define MII_XCIIS 0x11 /* Configuration Info IRQ & Status Reg */
|
||||
#define MII_XIE 0x12 /* Interrupt Enable Register */
|
||||
#define MII_XIE_DEFAULT_MASK 0x0070 /* ANE complete, Remote Fault, Link Down */
|
||||
|
||||
#define STE101P_PHY_ID 0x00061c50
|
||||
#define STE100P_PHY_ID 0x1c040011
|
||||
|
||||
static int ste10Xp_config_init(struct phy_device *phydev)
|
||||
{
|
||||
int value, err;
|
||||
|
||||
/* Software Reset PHY */
|
||||
value = phy_read(phydev, MII_BMCR);
|
||||
if (value < 0)
|
||||
return value;
|
||||
|
||||
value |= BMCR_RESET;
|
||||
err = phy_write(phydev, MII_BMCR, value);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
do {
|
||||
value = phy_read(phydev, MII_BMCR);
|
||||
} while (value & BMCR_RESET);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ste10Xp_config_intr(struct phy_device *phydev)
|
||||
{
|
||||
int err, value;
|
||||
|
||||
if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
|
||||
/* Enable all STe101P interrupts (PR12) */
|
||||
err = phy_write(phydev, MII_XIE, MII_XIE_DEFAULT_MASK);
|
||||
/* clear any pending interrupts */
|
||||
if (err == 0) {
|
||||
value = phy_read(phydev, MII_XCIIS);
|
||||
if (value < 0)
|
||||
err = value;
|
||||
}
|
||||
} else
|
||||
err = phy_write(phydev, MII_XIE, 0);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int ste10Xp_ack_interrupt(struct phy_device *phydev)
|
||||
{
|
||||
int err = phy_read(phydev, MII_XCIIS);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct phy_driver ste10xp_pdriver[] = {
|
||||
{
|
||||
.phy_id = STE101P_PHY_ID,
|
||||
.phy_id_mask = 0xfffffff0,
|
||||
.name = "STe101p",
|
||||
.features = PHY_BASIC_FEATURES | SUPPORTED_Pause,
|
||||
.flags = PHY_HAS_INTERRUPT,
|
||||
.config_init = ste10Xp_config_init,
|
||||
.config_aneg = genphy_config_aneg,
|
||||
.read_status = genphy_read_status,
|
||||
.ack_interrupt = ste10Xp_ack_interrupt,
|
||||
.config_intr = ste10Xp_config_intr,
|
||||
.suspend = genphy_suspend,
|
||||
.resume = genphy_resume,
|
||||
.driver = {.owner = THIS_MODULE,}
|
||||
}, {
|
||||
.phy_id = STE100P_PHY_ID,
|
||||
.phy_id_mask = 0xffffffff,
|
||||
.name = "STe100p",
|
||||
.features = PHY_BASIC_FEATURES | SUPPORTED_Pause,
|
||||
.flags = PHY_HAS_INTERRUPT,
|
||||
.config_init = ste10Xp_config_init,
|
||||
.config_aneg = genphy_config_aneg,
|
||||
.read_status = genphy_read_status,
|
||||
.ack_interrupt = ste10Xp_ack_interrupt,
|
||||
.config_intr = ste10Xp_config_intr,
|
||||
.suspend = genphy_suspend,
|
||||
.resume = genphy_resume,
|
||||
.driver = {.owner = THIS_MODULE,}
|
||||
} };
|
||||
|
||||
static int __init ste10Xp_init(void)
|
||||
{
|
||||
return phy_drivers_register(ste10xp_pdriver,
|
||||
ARRAY_SIZE(ste10xp_pdriver));
|
||||
}
|
||||
|
||||
static void __exit ste10Xp_exit(void)
|
||||
{
|
||||
phy_drivers_unregister(ste10xp_pdriver,
|
||||
ARRAY_SIZE(ste10xp_pdriver));
|
||||
}
|
||||
|
||||
module_init(ste10Xp_init);
|
||||
module_exit(ste10Xp_exit);
|
||||
|
||||
static struct mdio_device_id __maybe_unused ste10Xp_tbl[] = {
|
||||
{ STE101P_PHY_ID, 0xfffffff0 },
|
||||
{ STE100P_PHY_ID, 0xffffffff },
|
||||
{ }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(mdio, ste10Xp_tbl);
|
||||
|
||||
MODULE_DESCRIPTION("STMicroelectronics STe10Xp PHY driver");
|
||||
MODULE_AUTHOR("Giuseppe Cavallaro <peppe.cavallaro@st.com>");
|
||||
MODULE_LICENSE("GPL");
|
||||
339
drivers/net/phy/vitesse.c
Normal file
339
drivers/net/phy/vitesse.c
Normal file
|
|
@ -0,0 +1,339 @@
|
|||
/*
|
||||
* Driver for Vitesse PHYs
|
||||
*
|
||||
* Author: Kriston Carson
|
||||
*
|
||||
* Copyright (c) 2005, 2009, 2011 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/module.h>
|
||||
#include <linux/mii.h>
|
||||
#include <linux/ethtool.h>
|
||||
#include <linux/phy.h>
|
||||
|
||||
/* Vitesse Extended Page Magic Register(s) */
|
||||
#define MII_VSC82X4_EXT_PAGE_16E 0x10
|
||||
#define MII_VSC82X4_EXT_PAGE_17E 0x11
|
||||
#define MII_VSC82X4_EXT_PAGE_18E 0x12
|
||||
|
||||
/* Vitesse Extended Control Register 1 */
|
||||
#define MII_VSC8244_EXT_CON1 0x17
|
||||
#define MII_VSC8244_EXTCON1_INIT 0x0000
|
||||
#define MII_VSC8244_EXTCON1_TX_SKEW_MASK 0x0c00
|
||||
#define MII_VSC8244_EXTCON1_RX_SKEW_MASK 0x0300
|
||||
#define MII_VSC8244_EXTCON1_TX_SKEW 0x0800
|
||||
#define MII_VSC8244_EXTCON1_RX_SKEW 0x0200
|
||||
|
||||
/* Vitesse Interrupt Mask Register */
|
||||
#define MII_VSC8244_IMASK 0x19
|
||||
#define MII_VSC8244_IMASK_IEN 0x8000
|
||||
#define MII_VSC8244_IMASK_SPEED 0x4000
|
||||
#define MII_VSC8244_IMASK_LINK 0x2000
|
||||
#define MII_VSC8244_IMASK_DUPLEX 0x1000
|
||||
#define MII_VSC8244_IMASK_MASK 0xf000
|
||||
|
||||
#define MII_VSC8221_IMASK_MASK 0xa000
|
||||
|
||||
/* Vitesse Interrupt Status Register */
|
||||
#define MII_VSC8244_ISTAT 0x1a
|
||||
#define MII_VSC8244_ISTAT_STATUS 0x8000
|
||||
#define MII_VSC8244_ISTAT_SPEED 0x4000
|
||||
#define MII_VSC8244_ISTAT_LINK 0x2000
|
||||
#define MII_VSC8244_ISTAT_DUPLEX 0x1000
|
||||
|
||||
/* Vitesse Auxiliary Control/Status Register */
|
||||
#define MII_VSC8244_AUX_CONSTAT 0x1c
|
||||
#define MII_VSC8244_AUXCONSTAT_INIT 0x0000
|
||||
#define MII_VSC8244_AUXCONSTAT_DUPLEX 0x0020
|
||||
#define MII_VSC8244_AUXCONSTAT_SPEED 0x0018
|
||||
#define MII_VSC8244_AUXCONSTAT_GBIT 0x0010
|
||||
#define MII_VSC8244_AUXCONSTAT_100 0x0008
|
||||
|
||||
#define MII_VSC8221_AUXCONSTAT_INIT 0x0004 /* need to set this bit? */
|
||||
#define MII_VSC8221_AUXCONSTAT_RESERVED 0x0004
|
||||
|
||||
/* Vitesse Extended Page Access Register */
|
||||
#define MII_VSC82X4_EXT_PAGE_ACCESS 0x1f
|
||||
|
||||
#define PHY_ID_VSC8234 0x000fc620
|
||||
#define PHY_ID_VSC8244 0x000fc6c0
|
||||
#define PHY_ID_VSC8514 0x00070670
|
||||
#define PHY_ID_VSC8574 0x000704a0
|
||||
#define PHY_ID_VSC8662 0x00070660
|
||||
#define PHY_ID_VSC8221 0x000fc550
|
||||
#define PHY_ID_VSC8211 0x000fc4b0
|
||||
|
||||
MODULE_DESCRIPTION("Vitesse PHY driver");
|
||||
MODULE_AUTHOR("Kriston Carson");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
static int vsc824x_add_skew(struct phy_device *phydev)
|
||||
{
|
||||
int err;
|
||||
int extcon;
|
||||
|
||||
extcon = phy_read(phydev, MII_VSC8244_EXT_CON1);
|
||||
|
||||
if (extcon < 0)
|
||||
return extcon;
|
||||
|
||||
extcon &= ~(MII_VSC8244_EXTCON1_TX_SKEW_MASK |
|
||||
MII_VSC8244_EXTCON1_RX_SKEW_MASK);
|
||||
|
||||
extcon |= (MII_VSC8244_EXTCON1_TX_SKEW |
|
||||
MII_VSC8244_EXTCON1_RX_SKEW);
|
||||
|
||||
err = phy_write(phydev, MII_VSC8244_EXT_CON1, extcon);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int vsc824x_config_init(struct phy_device *phydev)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = phy_write(phydev, MII_VSC8244_AUX_CONSTAT,
|
||||
MII_VSC8244_AUXCONSTAT_INIT);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID)
|
||||
err = vsc824x_add_skew(phydev);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int vsc824x_ack_interrupt(struct phy_device *phydev)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
/* Don't bother to ACK the interrupts if interrupts
|
||||
* are disabled. The 824x cannot clear the interrupts
|
||||
* if they are disabled.
|
||||
*/
|
||||
if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
|
||||
err = phy_read(phydev, MII_VSC8244_ISTAT);
|
||||
|
||||
return (err < 0) ? err : 0;
|
||||
}
|
||||
|
||||
static int vsc82xx_config_intr(struct phy_device *phydev)
|
||||
{
|
||||
int err;
|
||||
|
||||
if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
|
||||
err = phy_write(phydev, MII_VSC8244_IMASK,
|
||||
(phydev->drv->phy_id == PHY_ID_VSC8234 ||
|
||||
phydev->drv->phy_id == PHY_ID_VSC8244 ||
|
||||
phydev->drv->phy_id == PHY_ID_VSC8514 ||
|
||||
phydev->drv->phy_id == PHY_ID_VSC8574) ?
|
||||
MII_VSC8244_IMASK_MASK :
|
||||
MII_VSC8221_IMASK_MASK);
|
||||
else {
|
||||
/* The Vitesse PHY cannot clear the interrupt
|
||||
* once it has disabled them, so we clear them first
|
||||
*/
|
||||
err = phy_read(phydev, MII_VSC8244_ISTAT);
|
||||
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = phy_write(phydev, MII_VSC8244_IMASK, 0);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int vsc8221_config_init(struct phy_device *phydev)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = phy_write(phydev, MII_VSC8244_AUX_CONSTAT,
|
||||
MII_VSC8221_AUXCONSTAT_INIT);
|
||||
return err;
|
||||
|
||||
/* Perhaps we should set EXT_CON1 based on the interface?
|
||||
* Options are 802.3Z SerDes or SGMII
|
||||
*/
|
||||
}
|
||||
|
||||
/* vsc82x4_config_autocross_enable - Enable auto MDI/MDI-X for forced links
|
||||
* @phydev: target phy_device struct
|
||||
*
|
||||
* Enable auto MDI/MDI-X when in 10/100 forced link speeds by writing
|
||||
* special values in the VSC8234/VSC8244 extended reserved registers
|
||||
*/
|
||||
static int vsc82x4_config_autocross_enable(struct phy_device *phydev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (phydev->autoneg == AUTONEG_ENABLE || phydev->speed > SPEED_100)
|
||||
return 0;
|
||||
|
||||
/* map extended registers set 0x10 - 0x1e */
|
||||
ret = phy_write(phydev, MII_VSC82X4_EXT_PAGE_ACCESS, 0x52b5);
|
||||
if (ret >= 0)
|
||||
ret = phy_write(phydev, MII_VSC82X4_EXT_PAGE_18E, 0x0012);
|
||||
if (ret >= 0)
|
||||
ret = phy_write(phydev, MII_VSC82X4_EXT_PAGE_17E, 0x2803);
|
||||
if (ret >= 0)
|
||||
ret = phy_write(phydev, MII_VSC82X4_EXT_PAGE_16E, 0x87fa);
|
||||
/* map standard registers set 0x10 - 0x1e */
|
||||
if (ret >= 0)
|
||||
ret = phy_write(phydev, MII_VSC82X4_EXT_PAGE_ACCESS, 0x0000);
|
||||
else
|
||||
phy_write(phydev, MII_VSC82X4_EXT_PAGE_ACCESS, 0x0000);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* vsc82x4_config_aneg - restart auto-negotiation or write BMCR
|
||||
* @phydev: target phy_device struct
|
||||
*
|
||||
* Description: If auto-negotiation is enabled, we configure the
|
||||
* advertising, and then restart auto-negotiation. If it is not
|
||||
* enabled, then we write the BMCR and also start the auto
|
||||
* MDI/MDI-X feature
|
||||
*/
|
||||
static int vsc82x4_config_aneg(struct phy_device *phydev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* Enable auto MDI/MDI-X when in 10/100 forced link speeds by
|
||||
* writing special values in the VSC8234 extended reserved registers
|
||||
*/
|
||||
if (phydev->autoneg != AUTONEG_ENABLE && phydev->speed <= SPEED_100) {
|
||||
ret = genphy_setup_forced(phydev);
|
||||
|
||||
if (ret < 0) /* error */
|
||||
return ret;
|
||||
|
||||
return vsc82x4_config_autocross_enable(phydev);
|
||||
}
|
||||
|
||||
return genphy_config_aneg(phydev);
|
||||
}
|
||||
|
||||
/* Vitesse 82xx */
|
||||
static struct phy_driver vsc82xx_driver[] = {
|
||||
{
|
||||
.phy_id = PHY_ID_VSC8234,
|
||||
.name = "Vitesse VSC8234",
|
||||
.phy_id_mask = 0x000ffff0,
|
||||
.features = PHY_GBIT_FEATURES,
|
||||
.flags = PHY_HAS_INTERRUPT,
|
||||
.config_init = &vsc824x_config_init,
|
||||
.config_aneg = &vsc82x4_config_aneg,
|
||||
.read_status = &genphy_read_status,
|
||||
.ack_interrupt = &vsc824x_ack_interrupt,
|
||||
.config_intr = &vsc82xx_config_intr,
|
||||
.driver = { .owner = THIS_MODULE,},
|
||||
}, {
|
||||
.phy_id = PHY_ID_VSC8244,
|
||||
.name = "Vitesse VSC8244",
|
||||
.phy_id_mask = 0x000fffc0,
|
||||
.features = PHY_GBIT_FEATURES,
|
||||
.flags = PHY_HAS_INTERRUPT,
|
||||
.config_init = &vsc824x_config_init,
|
||||
.config_aneg = &vsc82x4_config_aneg,
|
||||
.read_status = &genphy_read_status,
|
||||
.ack_interrupt = &vsc824x_ack_interrupt,
|
||||
.config_intr = &vsc82xx_config_intr,
|
||||
.driver = { .owner = THIS_MODULE,},
|
||||
}, {
|
||||
.phy_id = PHY_ID_VSC8514,
|
||||
.name = "Vitesse VSC8514",
|
||||
.phy_id_mask = 0x000ffff0,
|
||||
.features = PHY_GBIT_FEATURES,
|
||||
.flags = PHY_HAS_INTERRUPT,
|
||||
.config_init = &vsc824x_config_init,
|
||||
.config_aneg = &vsc82x4_config_aneg,
|
||||
.read_status = &genphy_read_status,
|
||||
.ack_interrupt = &vsc824x_ack_interrupt,
|
||||
.config_intr = &vsc82xx_config_intr,
|
||||
.driver = { .owner = THIS_MODULE,},
|
||||
}, {
|
||||
.phy_id = PHY_ID_VSC8574,
|
||||
.name = "Vitesse VSC8574",
|
||||
.phy_id_mask = 0x000ffff0,
|
||||
.features = PHY_GBIT_FEATURES,
|
||||
.flags = PHY_HAS_INTERRUPT,
|
||||
.config_init = &vsc824x_config_init,
|
||||
.config_aneg = &vsc82x4_config_aneg,
|
||||
.read_status = &genphy_read_status,
|
||||
.ack_interrupt = &vsc824x_ack_interrupt,
|
||||
.config_intr = &vsc82xx_config_intr,
|
||||
.driver = { .owner = THIS_MODULE,},
|
||||
}, {
|
||||
.phy_id = PHY_ID_VSC8662,
|
||||
.name = "Vitesse VSC8662",
|
||||
.phy_id_mask = 0x000ffff0,
|
||||
.features = PHY_GBIT_FEATURES,
|
||||
.flags = PHY_HAS_INTERRUPT,
|
||||
.config_init = &vsc824x_config_init,
|
||||
.config_aneg = &vsc82x4_config_aneg,
|
||||
.read_status = &genphy_read_status,
|
||||
.ack_interrupt = &vsc824x_ack_interrupt,
|
||||
.config_intr = &vsc82xx_config_intr,
|
||||
.driver = { .owner = THIS_MODULE,},
|
||||
}, {
|
||||
/* Vitesse 8221 */
|
||||
.phy_id = PHY_ID_VSC8221,
|
||||
.phy_id_mask = 0x000ffff0,
|
||||
.name = "Vitesse VSC8221",
|
||||
.features = PHY_GBIT_FEATURES,
|
||||
.flags = PHY_HAS_INTERRUPT,
|
||||
.config_init = &vsc8221_config_init,
|
||||
.config_aneg = &genphy_config_aneg,
|
||||
.read_status = &genphy_read_status,
|
||||
.ack_interrupt = &vsc824x_ack_interrupt,
|
||||
.config_intr = &vsc82xx_config_intr,
|
||||
.driver = { .owner = THIS_MODULE,},
|
||||
}, {
|
||||
/* Vitesse 8211 */
|
||||
.phy_id = PHY_ID_VSC8211,
|
||||
.phy_id_mask = 0x000ffff0,
|
||||
.name = "Vitesse VSC8211",
|
||||
.features = PHY_GBIT_FEATURES,
|
||||
.flags = PHY_HAS_INTERRUPT,
|
||||
.config_init = &vsc8221_config_init,
|
||||
.config_aneg = &genphy_config_aneg,
|
||||
.read_status = &genphy_read_status,
|
||||
.ack_interrupt = &vsc824x_ack_interrupt,
|
||||
.config_intr = &vsc82xx_config_intr,
|
||||
.driver = { .owner = THIS_MODULE,},
|
||||
} };
|
||||
|
||||
static int __init vsc82xx_init(void)
|
||||
{
|
||||
return phy_drivers_register(vsc82xx_driver,
|
||||
ARRAY_SIZE(vsc82xx_driver));
|
||||
}
|
||||
|
||||
static void __exit vsc82xx_exit(void)
|
||||
{
|
||||
phy_drivers_unregister(vsc82xx_driver, ARRAY_SIZE(vsc82xx_driver));
|
||||
}
|
||||
|
||||
module_init(vsc82xx_init);
|
||||
module_exit(vsc82xx_exit);
|
||||
|
||||
static struct mdio_device_id __maybe_unused vitesse_tbl[] = {
|
||||
{ PHY_ID_VSC8234, 0x000ffff0 },
|
||||
{ PHY_ID_VSC8244, 0x000fffc0 },
|
||||
{ PHY_ID_VSC8514, 0x000ffff0 },
|
||||
{ PHY_ID_VSC8574, 0x000ffff0 },
|
||||
{ PHY_ID_VSC8662, 0x000ffff0 },
|
||||
{ PHY_ID_VSC8221, 0x000ffff0 },
|
||||
{ PHY_ID_VSC8211, 0x000ffff0 },
|
||||
{ }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(mdio, vitesse_tbl);
|
||||
Loading…
Add table
Add a link
Reference in a new issue