Commit b5680e0b authored by Shawn Guo's avatar Shawn Guo Committed by David S. Miller

net/fec: add dual fec support for mx28

This patch is to add mx28 dual fec support. Here are some key notes
for mx28 fec controller.

 - The mx28 fec controller naming ENET-MAC is a different IP from FEC
   used on other i.mx variants.  But they are basically compatible
   on software interface, so it's possible to share the same driver.
 - ENET-MAC design on mx28 made an improper assumption that it runs
   on a big-endian system. As the result, driver has to swap every
   frame going to and coming from the controller.
 - The external phys can only be configured by fec0, which means fec1
   can not work independently and both phys need to be configured by
   mii_bus attached on fec0.
 - ENET-MAC reset will get mac address registers reset too.
 - ENET-MAC MII/RMII mode and 10M/100M speed are configured
   differently FEC.
 - ETHER_EN bit must be set to get ENET-MAC interrupt work.
Signed-off-by: default avatarShawn Guo <shawn.guo@freescale.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent bcc67771
...@@ -1944,18 +1944,19 @@ config 68360_ENET ...@@ -1944,18 +1944,19 @@ config 68360_ENET
config FEC config FEC
bool "FEC ethernet controller (of ColdFire and some i.MX CPUs)" bool "FEC ethernet controller (of ColdFire and some i.MX CPUs)"
depends on M523x || M527x || M5272 || M528x || M520x || M532x || \ depends on M523x || M527x || M5272 || M528x || M520x || M532x || \
MACH_MX27 || ARCH_MX35 || ARCH_MX25 || ARCH_MX5 MACH_MX27 || ARCH_MX35 || ARCH_MX25 || ARCH_MX5 || SOC_IMX28
select PHYLIB select PHYLIB
help help
Say Y here if you want to use the built-in 10/100 Fast ethernet Say Y here if you want to use the built-in 10/100 Fast ethernet
controller on some Motorola ColdFire and Freescale i.MX processors. controller on some Motorola ColdFire and Freescale i.MX processors.
config FEC2 config FEC2
bool "Second FEC ethernet controller (on some ColdFire CPUs)" bool "Second FEC ethernet controller"
depends on FEC depends on FEC
help help
Say Y here if you want to use the second built-in 10/100 Fast Say Y here if you want to use the second built-in 10/100 Fast
ethernet controller on some Motorola ColdFire processors. ethernet controller on some Motorola ColdFire and Freescale
i.MX processors.
config FEC_MPC52xx config FEC_MPC52xx
tristate "MPC52xx FEC driver" tristate "MPC52xx FEC driver"
......
...@@ -17,6 +17,8 @@ ...@@ -17,6 +17,8 @@
* *
* Bug fixes and cleanup by Philippe De Muyter (phdm@macqel.be) * Bug fixes and cleanup by Philippe De Muyter (phdm@macqel.be)
* Copyright (c) 2004-2006 Macq Electronique SA. * Copyright (c) 2004-2006 Macq Electronique SA.
*
* Copyright (C) 2010 Freescale Semiconductor, Inc.
*/ */
#include <linux/module.h> #include <linux/module.h>
...@@ -45,20 +47,36 @@ ...@@ -45,20 +47,36 @@
#include <asm/cacheflush.h> #include <asm/cacheflush.h>
#ifndef CONFIG_ARCH_MXC #ifndef CONFIG_ARM
#include <asm/coldfire.h> #include <asm/coldfire.h>
#include <asm/mcfsim.h> #include <asm/mcfsim.h>
#endif #endif
#include "fec.h" #include "fec.h"
#ifdef CONFIG_ARCH_MXC #if defined(CONFIG_ARCH_MXC) || defined(CONFIG_SOC_IMX28)
#include <mach/hardware.h>
#define FEC_ALIGNMENT 0xf #define FEC_ALIGNMENT 0xf
#else #else
#define FEC_ALIGNMENT 0x3 #define FEC_ALIGNMENT 0x3
#endif #endif
#define DRIVER_NAME "fec"
/* Controller is ENET-MAC */
#define FEC_QUIRK_ENET_MAC (1 << 0)
/* Controller needs driver to swap frame */
#define FEC_QUIRK_SWAP_FRAME (1 << 1)
static struct platform_device_id fec_devtype[] = {
{
.name = DRIVER_NAME,
.driver_data = 0,
}, {
.name = "imx28-fec",
.driver_data = FEC_QUIRK_ENET_MAC | FEC_QUIRK_SWAP_FRAME,
}
};
static unsigned char macaddr[ETH_ALEN]; static unsigned char macaddr[ETH_ALEN];
module_param_array(macaddr, byte, NULL, 0); module_param_array(macaddr, byte, NULL, 0);
MODULE_PARM_DESC(macaddr, "FEC Ethernet MAC address"); MODULE_PARM_DESC(macaddr, "FEC Ethernet MAC address");
...@@ -129,7 +147,8 @@ MODULE_PARM_DESC(macaddr, "FEC Ethernet MAC address"); ...@@ -129,7 +147,8 @@ MODULE_PARM_DESC(macaddr, "FEC Ethernet MAC address");
* account when setting it. * account when setting it.
*/ */
#if defined(CONFIG_M523x) || defined(CONFIG_M527x) || defined(CONFIG_M528x) || \ #if defined(CONFIG_M523x) || defined(CONFIG_M527x) || defined(CONFIG_M528x) || \
defined(CONFIG_M520x) || defined(CONFIG_M532x) || defined(CONFIG_ARCH_MXC) defined(CONFIG_M520x) || defined(CONFIG_M532x) || \
defined(CONFIG_ARCH_MXC) || defined(CONFIG_SOC_IMX28)
#define OPT_FRAME_SIZE (PKT_MAXBUF_SIZE << 16) #define OPT_FRAME_SIZE (PKT_MAXBUF_SIZE << 16)
#else #else
#define OPT_FRAME_SIZE 0 #define OPT_FRAME_SIZE 0
...@@ -208,10 +227,23 @@ static void fec_stop(struct net_device *dev); ...@@ -208,10 +227,23 @@ static void fec_stop(struct net_device *dev);
/* Transmitter timeout */ /* Transmitter timeout */
#define TX_TIMEOUT (2 * HZ) #define TX_TIMEOUT (2 * HZ)
static void *swap_buffer(void *bufaddr, int len)
{
int i;
unsigned int *buf = bufaddr;
for (i = 0; i < (len + 3) / 4; i++, buf++)
*buf = cpu_to_be32(*buf);
return bufaddr;
}
static netdev_tx_t static netdev_tx_t
fec_enet_start_xmit(struct sk_buff *skb, struct net_device *dev) fec_enet_start_xmit(struct sk_buff *skb, struct net_device *dev)
{ {
struct fec_enet_private *fep = netdev_priv(dev); struct fec_enet_private *fep = netdev_priv(dev);
const struct platform_device_id *id_entry =
platform_get_device_id(fep->pdev);
struct bufdesc *bdp; struct bufdesc *bdp;
void *bufaddr; void *bufaddr;
unsigned short status; unsigned short status;
...@@ -256,6 +288,14 @@ fec_enet_start_xmit(struct sk_buff *skb, struct net_device *dev) ...@@ -256,6 +288,14 @@ fec_enet_start_xmit(struct sk_buff *skb, struct net_device *dev)
bufaddr = fep->tx_bounce[index]; bufaddr = fep->tx_bounce[index];
} }
/*
* Some design made an incorrect assumption on endian mode of
* the system that it's running on. As the result, driver has to
* swap every frame going to and coming from the controller.
*/
if (id_entry->driver_data & FEC_QUIRK_SWAP_FRAME)
swap_buffer(bufaddr, skb->len);
/* Save skb pointer */ /* Save skb pointer */
fep->tx_skbuff[fep->skb_cur] = skb; fep->tx_skbuff[fep->skb_cur] = skb;
...@@ -424,6 +464,8 @@ static void ...@@ -424,6 +464,8 @@ static void
fec_enet_rx(struct net_device *dev) fec_enet_rx(struct net_device *dev)
{ {
struct fec_enet_private *fep = netdev_priv(dev); struct fec_enet_private *fep = netdev_priv(dev);
const struct platform_device_id *id_entry =
platform_get_device_id(fep->pdev);
struct bufdesc *bdp; struct bufdesc *bdp;
unsigned short status; unsigned short status;
struct sk_buff *skb; struct sk_buff *skb;
...@@ -487,6 +529,9 @@ fec_enet_rx(struct net_device *dev) ...@@ -487,6 +529,9 @@ fec_enet_rx(struct net_device *dev)
dma_unmap_single(NULL, bdp->cbd_bufaddr, bdp->cbd_datlen, dma_unmap_single(NULL, bdp->cbd_bufaddr, bdp->cbd_datlen,
DMA_FROM_DEVICE); DMA_FROM_DEVICE);
if (id_entry->driver_data & FEC_QUIRK_SWAP_FRAME)
swap_buffer(data, pkt_len);
/* This does 16 byte alignment, exactly what we need. /* This does 16 byte alignment, exactly what we need.
* The packet length includes FCS, but we don't want to * The packet length includes FCS, but we don't want to
* include that when passing upstream as it messes up * include that when passing upstream as it messes up
...@@ -689,6 +734,7 @@ static int fec_enet_mii_probe(struct net_device *dev) ...@@ -689,6 +734,7 @@ static int fec_enet_mii_probe(struct net_device *dev)
char mdio_bus_id[MII_BUS_ID_SIZE]; char mdio_bus_id[MII_BUS_ID_SIZE];
char phy_name[MII_BUS_ID_SIZE + 3]; char phy_name[MII_BUS_ID_SIZE + 3];
int phy_id; int phy_id;
int dev_id = fep->pdev->id;
fep->phy_dev = NULL; fep->phy_dev = NULL;
...@@ -700,6 +746,8 @@ static int fec_enet_mii_probe(struct net_device *dev) ...@@ -700,6 +746,8 @@ static int fec_enet_mii_probe(struct net_device *dev)
continue; continue;
if (fep->mii_bus->phy_map[phy_id]->phy_id == 0) if (fep->mii_bus->phy_map[phy_id]->phy_id == 0)
continue; continue;
if (dev_id--)
continue;
strncpy(mdio_bus_id, fep->mii_bus->id, MII_BUS_ID_SIZE); strncpy(mdio_bus_id, fep->mii_bus->id, MII_BUS_ID_SIZE);
break; break;
} }
...@@ -737,10 +785,35 @@ static int fec_enet_mii_probe(struct net_device *dev) ...@@ -737,10 +785,35 @@ static int fec_enet_mii_probe(struct net_device *dev)
static int fec_enet_mii_init(struct platform_device *pdev) static int fec_enet_mii_init(struct platform_device *pdev)
{ {
static struct mii_bus *fec0_mii_bus;
struct net_device *dev = platform_get_drvdata(pdev); struct net_device *dev = platform_get_drvdata(pdev);
struct fec_enet_private *fep = netdev_priv(dev); struct fec_enet_private *fep = netdev_priv(dev);
const struct platform_device_id *id_entry =
platform_get_device_id(fep->pdev);
int err = -ENXIO, i; int err = -ENXIO, i;
/*
* The dual fec interfaces are not equivalent with enet-mac.
* Here are the differences:
*
* - fec0 supports MII & RMII modes while fec1 only supports RMII
* - fec0 acts as the 1588 time master while fec1 is slave
* - external phys can only be configured by fec0
*
* That is to say fec1 can not work independently. It only works
* when fec0 is working. The reason behind this design is that the
* second interface is added primarily for Switch mode.
*
* Because of the last point above, both phys are attached on fec0
* mdio interface in board design, and need to be configured by
* fec0 mii_bus.
*/
if ((id_entry->driver_data & FEC_QUIRK_ENET_MAC) && pdev->id) {
/* fec1 uses fec0 mii_bus */
fep->mii_bus = fec0_mii_bus;
return 0;
}
fep->mii_timeout = 0; fep->mii_timeout = 0;
/* /*
...@@ -777,6 +850,10 @@ static int fec_enet_mii_init(struct platform_device *pdev) ...@@ -777,6 +850,10 @@ static int fec_enet_mii_init(struct platform_device *pdev)
if (mdiobus_register(fep->mii_bus)) if (mdiobus_register(fep->mii_bus))
goto err_out_free_mdio_irq; goto err_out_free_mdio_irq;
/* save fec0 mii_bus */
if (id_entry->driver_data & FEC_QUIRK_ENET_MAC)
fec0_mii_bus = fep->mii_bus;
return 0; return 0;
err_out_free_mdio_irq: err_out_free_mdio_irq:
...@@ -1148,12 +1225,25 @@ static void ...@@ -1148,12 +1225,25 @@ static void
fec_restart(struct net_device *dev, int duplex) fec_restart(struct net_device *dev, int duplex)
{ {
struct fec_enet_private *fep = netdev_priv(dev); struct fec_enet_private *fep = netdev_priv(dev);
const struct platform_device_id *id_entry =
platform_get_device_id(fep->pdev);
int i; int i;
u32 val, temp_mac[2];
/* Whack a reset. We should wait for this. */ /* Whack a reset. We should wait for this. */
writel(1, fep->hwp + FEC_ECNTRL); writel(1, fep->hwp + FEC_ECNTRL);
udelay(10); udelay(10);
/*
* enet-mac reset will reset mac address registers too,
* so need to reconfigure it.
*/
if (id_entry->driver_data & FEC_QUIRK_ENET_MAC) {
memcpy(&temp_mac, dev->dev_addr, ETH_ALEN);
writel(cpu_to_be32(temp_mac[0]), fep->hwp + FEC_ADDR_LOW);
writel(cpu_to_be32(temp_mac[1]), fep->hwp + FEC_ADDR_HIGH);
}
/* Clear any outstanding interrupt. */ /* Clear any outstanding interrupt. */
writel(0xffc00000, fep->hwp + FEC_IEVENT); writel(0xffc00000, fep->hwp + FEC_IEVENT);
...@@ -1200,20 +1290,45 @@ fec_restart(struct net_device *dev, int duplex) ...@@ -1200,20 +1290,45 @@ fec_restart(struct net_device *dev, int duplex)
/* Set MII speed */ /* Set MII speed */
writel(fep->phy_speed, fep->hwp + FEC_MII_SPEED); writel(fep->phy_speed, fep->hwp + FEC_MII_SPEED);
#ifdef FEC_MIIGSK_ENR /*
if (fep->phy_interface == PHY_INTERFACE_MODE_RMII) { * The phy interface and speed need to get configured
/* disable the gasket and wait */ * differently on enet-mac.
writel(0, fep->hwp + FEC_MIIGSK_ENR); */
while (readl(fep->hwp + FEC_MIIGSK_ENR) & 4) if (id_entry->driver_data & FEC_QUIRK_ENET_MAC) {
udelay(1); val = readl(fep->hwp + FEC_R_CNTRL);
/* configure the gasket: RMII, 50 MHz, no loopback, no echo */ /* MII or RMII */
writel(1, fep->hwp + FEC_MIIGSK_CFGR); if (fep->phy_interface == PHY_INTERFACE_MODE_RMII)
val |= (1 << 8);
else
val &= ~(1 << 8);
/* re-enable the gasket */ /* 10M or 100M */
writel(2, fep->hwp + FEC_MIIGSK_ENR); if (fep->phy_dev && fep->phy_dev->speed == SPEED_100)
} val &= ~(1 << 9);
else
val |= (1 << 9);
writel(val, fep->hwp + FEC_R_CNTRL);
} else {
#ifdef FEC_MIIGSK_ENR
if (fep->phy_interface == PHY_INTERFACE_MODE_RMII) {
/* disable the gasket and wait */
writel(0, fep->hwp + FEC_MIIGSK_ENR);
while (readl(fep->hwp + FEC_MIIGSK_ENR) & 4)
udelay(1);
/*
* configure the gasket:
* RMII, 50 MHz, no loopback, no echo
*/
writel(1, fep->hwp + FEC_MIIGSK_CFGR);
/* re-enable the gasket */
writel(2, fep->hwp + FEC_MIIGSK_ENR);
}
#endif #endif
}
/* And last, enable the transmit and receive processing */ /* And last, enable the transmit and receive processing */
writel(2, fep->hwp + FEC_ECNTRL); writel(2, fep->hwp + FEC_ECNTRL);
...@@ -1410,12 +1525,13 @@ static const struct dev_pm_ops fec_pm_ops = { ...@@ -1410,12 +1525,13 @@ static const struct dev_pm_ops fec_pm_ops = {
static struct platform_driver fec_driver = { static struct platform_driver fec_driver = {
.driver = { .driver = {
.name = "fec", .name = DRIVER_NAME,
.owner = THIS_MODULE, .owner = THIS_MODULE,
#ifdef CONFIG_PM #ifdef CONFIG_PM
.pm = &fec_pm_ops, .pm = &fec_pm_ops,
#endif #endif
}, },
.id_table = fec_devtype,
.probe = fec_probe, .probe = fec_probe,
.remove = __devexit_p(fec_drv_remove), .remove = __devexit_p(fec_drv_remove),
}; };
......
...@@ -14,7 +14,8 @@ ...@@ -14,7 +14,8 @@
/****************************************************************************/ /****************************************************************************/
#if defined(CONFIG_M523x) || defined(CONFIG_M527x) || defined(CONFIG_M528x) || \ #if defined(CONFIG_M523x) || defined(CONFIG_M527x) || defined(CONFIG_M528x) || \
defined(CONFIG_M520x) || defined(CONFIG_M532x) || defined(CONFIG_ARCH_MXC) defined(CONFIG_M520x) || defined(CONFIG_M532x) || \
defined(CONFIG_ARCH_MXC) || defined(CONFIG_SOC_IMX28)
/* /*
* Just figures, Motorola would have to change the offsets for * Just figures, Motorola would have to change the offsets for
* registers in the same peripheral device on different models * registers in the same peripheral device on different models
...@@ -78,7 +79,7 @@ ...@@ -78,7 +79,7 @@
/* /*
* Define the buffer descriptor structure. * Define the buffer descriptor structure.
*/ */
#ifdef CONFIG_ARCH_MXC #if defined(CONFIG_ARCH_MXC) || defined(CONFIG_SOC_IMX28)
struct bufdesc { struct bufdesc {
unsigned short cbd_datlen; /* Data length */ unsigned short cbd_datlen; /* Data length */
unsigned short cbd_sc; /* Control and status info */ unsigned short cbd_sc; /* Control and status info */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment