diff options
author | Shawn Guo <shawn.guo@freescale.com> | 2011-01-05 16:13:13 -0500 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2011-01-09 18:42:56 -0500 |
commit | b5680e0b591f2701c5ba7d5fc8f96b55414073c8 (patch) | |
tree | 393a02162339aba1c10e6c558b078d82bf6cef96 | |
parent | bcc67771ed8ee31cc1f2b1e033ae822b40c72ff9 (diff) |
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: Shawn Guo <shawn.guo@freescale.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r-- | drivers/net/Kconfig | 7 | ||||
-rw-r--r-- | drivers/net/fec.c | 148 | ||||
-rw-r--r-- | drivers/net/fec.h | 5 |
3 files changed, 139 insertions, 21 deletions
diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index 3fda24a28d2..5780dad6a3c 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig | |||
@@ -1944,18 +1944,19 @@ config 68360_ENET | |||
1944 | config FEC | 1944 | config FEC |
1945 | bool "FEC ethernet controller (of ColdFire and some i.MX CPUs)" | 1945 | bool "FEC ethernet controller (of ColdFire and some i.MX CPUs)" |
1946 | depends on M523x || M527x || M5272 || M528x || M520x || M532x || \ | 1946 | depends on M523x || M527x || M5272 || M528x || M520x || M532x || \ |
1947 | MACH_MX27 || ARCH_MX35 || ARCH_MX25 || ARCH_MX5 | 1947 | MACH_MX27 || ARCH_MX35 || ARCH_MX25 || ARCH_MX5 || SOC_IMX28 |
1948 | select PHYLIB | 1948 | select PHYLIB |
1949 | help | 1949 | help |
1950 | Say Y here if you want to use the built-in 10/100 Fast ethernet | 1950 | Say Y here if you want to use the built-in 10/100 Fast ethernet |
1951 | controller on some Motorola ColdFire and Freescale i.MX processors. | 1951 | controller on some Motorola ColdFire and Freescale i.MX processors. |
1952 | 1952 | ||
1953 | config FEC2 | 1953 | config FEC2 |
1954 | bool "Second FEC ethernet controller (on some ColdFire CPUs)" | 1954 | bool "Second FEC ethernet controller" |
1955 | depends on FEC | 1955 | depends on FEC |
1956 | help | 1956 | help |
1957 | Say Y here if you want to use the second built-in 10/100 Fast | 1957 | Say Y here if you want to use the second built-in 10/100 Fast |
1958 | ethernet controller on some Motorola ColdFire processors. | 1958 | ethernet controller on some Motorola ColdFire and Freescale |
1959 | i.MX processors. | ||
1959 | 1960 | ||
1960 | config FEC_MPC52xx | 1961 | config FEC_MPC52xx |
1961 | tristate "MPC52xx FEC driver" | 1962 | tristate "MPC52xx FEC driver" |
diff --git a/drivers/net/fec.c b/drivers/net/fec.c index 8a1c51f8641..2a71373719a 100644 --- a/drivers/net/fec.c +++ b/drivers/net/fec.c | |||
@@ -17,6 +17,8 @@ | |||
17 | * | 17 | * |
18 | * Bug fixes and cleanup by Philippe De Muyter (phdm@macqel.be) | 18 | * Bug fixes and cleanup by Philippe De Muyter (phdm@macqel.be) |
19 | * Copyright (c) 2004-2006 Macq Electronique SA. | 19 | * Copyright (c) 2004-2006 Macq Electronique SA. |
20 | * | ||
21 | * Copyright (C) 2010 Freescale Semiconductor, Inc. | ||
20 | */ | 22 | */ |
21 | 23 | ||
22 | #include <linux/module.h> | 24 | #include <linux/module.h> |
@@ -45,20 +47,36 @@ | |||
45 | 47 | ||
46 | #include <asm/cacheflush.h> | 48 | #include <asm/cacheflush.h> |
47 | 49 | ||
48 | #ifndef CONFIG_ARCH_MXC | 50 | #ifndef CONFIG_ARM |
49 | #include <asm/coldfire.h> | 51 | #include <asm/coldfire.h> |
50 | #include <asm/mcfsim.h> | 52 | #include <asm/mcfsim.h> |
51 | #endif | 53 | #endif |
52 | 54 | ||
53 | #include "fec.h" | 55 | #include "fec.h" |
54 | 56 | ||
55 | #ifdef CONFIG_ARCH_MXC | 57 | #if defined(CONFIG_ARCH_MXC) || defined(CONFIG_SOC_IMX28) |
56 | #include <mach/hardware.h> | ||
57 | #define FEC_ALIGNMENT 0xf | 58 | #define FEC_ALIGNMENT 0xf |
58 | #else | 59 | #else |
59 | #define FEC_ALIGNMENT 0x3 | 60 | #define FEC_ALIGNMENT 0x3 |
60 | #endif | 61 | #endif |
61 | 62 | ||
63 | #define DRIVER_NAME "fec" | ||
64 | |||
65 | /* Controller is ENET-MAC */ | ||
66 | #define FEC_QUIRK_ENET_MAC (1 << 0) | ||
67 | /* Controller needs driver to swap frame */ | ||
68 | #define FEC_QUIRK_SWAP_FRAME (1 << 1) | ||
69 | |||
70 | static struct platform_device_id fec_devtype[] = { | ||
71 | { | ||
72 | .name = DRIVER_NAME, | ||
73 | .driver_data = 0, | ||
74 | }, { | ||
75 | .name = "imx28-fec", | ||
76 | .driver_data = FEC_QUIRK_ENET_MAC | FEC_QUIRK_SWAP_FRAME, | ||
77 | } | ||
78 | }; | ||
79 | |||
62 | static unsigned char macaddr[ETH_ALEN]; | 80 | static unsigned char macaddr[ETH_ALEN]; |
63 | module_param_array(macaddr, byte, NULL, 0); | 81 | module_param_array(macaddr, byte, NULL, 0); |
64 | MODULE_PARM_DESC(macaddr, "FEC Ethernet MAC address"); | 82 | MODULE_PARM_DESC(macaddr, "FEC Ethernet MAC address"); |
@@ -129,7 +147,8 @@ MODULE_PARM_DESC(macaddr, "FEC Ethernet MAC address"); | |||
129 | * account when setting it. | 147 | * account when setting it. |
130 | */ | 148 | */ |
131 | #if defined(CONFIG_M523x) || defined(CONFIG_M527x) || defined(CONFIG_M528x) || \ | 149 | #if defined(CONFIG_M523x) || defined(CONFIG_M527x) || defined(CONFIG_M528x) || \ |
132 | defined(CONFIG_M520x) || defined(CONFIG_M532x) || defined(CONFIG_ARCH_MXC) | 150 | defined(CONFIG_M520x) || defined(CONFIG_M532x) || \ |
151 | defined(CONFIG_ARCH_MXC) || defined(CONFIG_SOC_IMX28) | ||
133 | #define OPT_FRAME_SIZE (PKT_MAXBUF_SIZE << 16) | 152 | #define OPT_FRAME_SIZE (PKT_MAXBUF_SIZE << 16) |
134 | #else | 153 | #else |
135 | #define OPT_FRAME_SIZE 0 | 154 | #define OPT_FRAME_SIZE 0 |
@@ -208,10 +227,23 @@ static void fec_stop(struct net_device *dev); | |||
208 | /* Transmitter timeout */ | 227 | /* Transmitter timeout */ |
209 | #define TX_TIMEOUT (2 * HZ) | 228 | #define TX_TIMEOUT (2 * HZ) |
210 | 229 | ||
230 | static void *swap_buffer(void *bufaddr, int len) | ||
231 | { | ||
232 | int i; | ||
233 | unsigned int *buf = bufaddr; | ||
234 | |||
235 | for (i = 0; i < (len + 3) / 4; i++, buf++) | ||
236 | *buf = cpu_to_be32(*buf); | ||
237 | |||
238 | return bufaddr; | ||
239 | } | ||
240 | |||
211 | static netdev_tx_t | 241 | static netdev_tx_t |
212 | fec_enet_start_xmit(struct sk_buff *skb, struct net_device *dev) | 242 | fec_enet_start_xmit(struct sk_buff *skb, struct net_device *dev) |
213 | { | 243 | { |
214 | struct fec_enet_private *fep = netdev_priv(dev); | 244 | struct fec_enet_private *fep = netdev_priv(dev); |
245 | const struct platform_device_id *id_entry = | ||
246 | platform_get_device_id(fep->pdev); | ||
215 | struct bufdesc *bdp; | 247 | struct bufdesc *bdp; |
216 | void *bufaddr; | 248 | void *bufaddr; |
217 | unsigned short status; | 249 | unsigned short status; |
@@ -256,6 +288,14 @@ fec_enet_start_xmit(struct sk_buff *skb, struct net_device *dev) | |||
256 | bufaddr = fep->tx_bounce[index]; | 288 | bufaddr = fep->tx_bounce[index]; |
257 | } | 289 | } |
258 | 290 | ||
291 | /* | ||
292 | * Some design made an incorrect assumption on endian mode of | ||
293 | * the system that it's running on. As the result, driver has to | ||
294 | * swap every frame going to and coming from the controller. | ||
295 | */ | ||
296 | if (id_entry->driver_data & FEC_QUIRK_SWAP_FRAME) | ||
297 | swap_buffer(bufaddr, skb->len); | ||
298 | |||
259 | /* Save skb pointer */ | 299 | /* Save skb pointer */ |
260 | fep->tx_skbuff[fep->skb_cur] = skb; | 300 | fep->tx_skbuff[fep->skb_cur] = skb; |
261 | 301 | ||
@@ -424,6 +464,8 @@ static void | |||
424 | fec_enet_rx(struct net_device *dev) | 464 | fec_enet_rx(struct net_device *dev) |
425 | { | 465 | { |
426 | struct fec_enet_private *fep = netdev_priv(dev); | 466 | struct fec_enet_private *fep = netdev_priv(dev); |
467 | const struct platform_device_id *id_entry = | ||
468 | platform_get_device_id(fep->pdev); | ||
427 | struct bufdesc *bdp; | 469 | struct bufdesc *bdp; |
428 | unsigned short status; | 470 | unsigned short status; |
429 | struct sk_buff *skb; | 471 | struct sk_buff *skb; |
@@ -487,6 +529,9 @@ fec_enet_rx(struct net_device *dev) | |||
487 | dma_unmap_single(NULL, bdp->cbd_bufaddr, bdp->cbd_datlen, | 529 | dma_unmap_single(NULL, bdp->cbd_bufaddr, bdp->cbd_datlen, |
488 | DMA_FROM_DEVICE); | 530 | DMA_FROM_DEVICE); |
489 | 531 | ||
532 | if (id_entry->driver_data & FEC_QUIRK_SWAP_FRAME) | ||
533 | swap_buffer(data, pkt_len); | ||
534 | |||
490 | /* This does 16 byte alignment, exactly what we need. | 535 | /* This does 16 byte alignment, exactly what we need. |
491 | * The packet length includes FCS, but we don't want to | 536 | * The packet length includes FCS, but we don't want to |
492 | * include that when passing upstream as it messes up | 537 | * include that when passing upstream as it messes up |
@@ -689,6 +734,7 @@ static int fec_enet_mii_probe(struct net_device *dev) | |||
689 | char mdio_bus_id[MII_BUS_ID_SIZE]; | 734 | char mdio_bus_id[MII_BUS_ID_SIZE]; |
690 | char phy_name[MII_BUS_ID_SIZE + 3]; | 735 | char phy_name[MII_BUS_ID_SIZE + 3]; |
691 | int phy_id; | 736 | int phy_id; |
737 | int dev_id = fep->pdev->id; | ||
692 | 738 | ||
693 | fep->phy_dev = NULL; | 739 | fep->phy_dev = NULL; |
694 | 740 | ||
@@ -700,6 +746,8 @@ static int fec_enet_mii_probe(struct net_device *dev) | |||
700 | continue; | 746 | continue; |
701 | if (fep->mii_bus->phy_map[phy_id]->phy_id == 0) | 747 | if (fep->mii_bus->phy_map[phy_id]->phy_id == 0) |
702 | continue; | 748 | continue; |
749 | if (dev_id--) | ||
750 | continue; | ||
703 | strncpy(mdio_bus_id, fep->mii_bus->id, MII_BUS_ID_SIZE); | 751 | strncpy(mdio_bus_id, fep->mii_bus->id, MII_BUS_ID_SIZE); |
704 | break; | 752 | break; |
705 | } | 753 | } |
@@ -737,10 +785,35 @@ static int fec_enet_mii_probe(struct net_device *dev) | |||
737 | 785 | ||
738 | static int fec_enet_mii_init(struct platform_device *pdev) | 786 | static int fec_enet_mii_init(struct platform_device *pdev) |
739 | { | 787 | { |
788 | static struct mii_bus *fec0_mii_bus; | ||
740 | struct net_device *dev = platform_get_drvdata(pdev); | 789 | struct net_device *dev = platform_get_drvdata(pdev); |
741 | struct fec_enet_private *fep = netdev_priv(dev); | 790 | struct fec_enet_private *fep = netdev_priv(dev); |
791 | const struct platform_device_id *id_entry = | ||
792 | platform_get_device_id(fep->pdev); | ||
742 | int err = -ENXIO, i; | 793 | int err = -ENXIO, i; |
743 | 794 | ||
795 | /* | ||
796 | * The dual fec interfaces are not equivalent with enet-mac. | ||
797 | * Here are the differences: | ||
798 | * | ||
799 | * - fec0 supports MII & RMII modes while fec1 only supports RMII | ||
800 | * - fec0 acts as the 1588 time master while fec1 is slave | ||
801 | * - external phys can only be configured by fec0 | ||
802 | * | ||
803 | * That is to say fec1 can not work independently. It only works | ||
804 | * when fec0 is working. The reason behind this design is that the | ||
805 | * second interface is added primarily for Switch mode. | ||
806 | * | ||
807 | * Because of the last point above, both phys are attached on fec0 | ||
808 | * mdio interface in board design, and need to be configured by | ||
809 | * fec0 mii_bus. | ||
810 | */ | ||
811 | if ((id_entry->driver_data & FEC_QUIRK_ENET_MAC) && pdev->id) { | ||
812 | /* fec1 uses fec0 mii_bus */ | ||
813 | fep->mii_bus = fec0_mii_bus; | ||
814 | return 0; | ||
815 | } | ||
816 | |||
744 | fep->mii_timeout = 0; | 817 | fep->mii_timeout = 0; |
745 | 818 | ||
746 | /* | 819 | /* |
@@ -777,6 +850,10 @@ static int fec_enet_mii_init(struct platform_device *pdev) | |||
777 | if (mdiobus_register(fep->mii_bus)) | 850 | if (mdiobus_register(fep->mii_bus)) |
778 | goto err_out_free_mdio_irq; | 851 | goto err_out_free_mdio_irq; |
779 | 852 | ||
853 | /* save fec0 mii_bus */ | ||
854 | if (id_entry->driver_data & FEC_QUIRK_ENET_MAC) | ||
855 | fec0_mii_bus = fep->mii_bus; | ||
856 | |||
780 | return 0; | 857 | return 0; |
781 | 858 | ||
782 | err_out_free_mdio_irq: | 859 | err_out_free_mdio_irq: |
@@ -1148,12 +1225,25 @@ static void | |||
1148 | fec_restart(struct net_device *dev, int duplex) | 1225 | fec_restart(struct net_device *dev, int duplex) |
1149 | { | 1226 | { |
1150 | struct fec_enet_private *fep = netdev_priv(dev); | 1227 | struct fec_enet_private *fep = netdev_priv(dev); |
1228 | const struct platform_device_id *id_entry = | ||
1229 | platform_get_device_id(fep->pdev); | ||
1151 | int i; | 1230 | int i; |
1231 | u32 val, temp_mac[2]; | ||
1152 | 1232 | ||
1153 | /* Whack a reset. We should wait for this. */ | 1233 | /* Whack a reset. We should wait for this. */ |
1154 | writel(1, fep->hwp + FEC_ECNTRL); | 1234 | writel(1, fep->hwp + FEC_ECNTRL); |
1155 | udelay(10); | 1235 | udelay(10); |
1156 | 1236 | ||
1237 | /* | ||
1238 | * enet-mac reset will reset mac address registers too, | ||
1239 | * so need to reconfigure it. | ||
1240 | */ | ||
1241 | if (id_entry->driver_data & FEC_QUIRK_ENET_MAC) { | ||
1242 | memcpy(&temp_mac, dev->dev_addr, ETH_ALEN); | ||
1243 | writel(cpu_to_be32(temp_mac[0]), fep->hwp + FEC_ADDR_LOW); | ||
1244 | writel(cpu_to_be32(temp_mac[1]), fep->hwp + FEC_ADDR_HIGH); | ||
1245 | } | ||
1246 | |||
1157 | /* Clear any outstanding interrupt. */ | 1247 | /* Clear any outstanding interrupt. */ |
1158 | writel(0xffc00000, fep->hwp + FEC_IEVENT); | 1248 | writel(0xffc00000, fep->hwp + FEC_IEVENT); |
1159 | 1249 | ||
@@ -1200,20 +1290,45 @@ fec_restart(struct net_device *dev, int duplex) | |||
1200 | /* Set MII speed */ | 1290 | /* Set MII speed */ |
1201 | writel(fep->phy_speed, fep->hwp + FEC_MII_SPEED); | 1291 | writel(fep->phy_speed, fep->hwp + FEC_MII_SPEED); |
1202 | 1292 | ||
1203 | #ifdef FEC_MIIGSK_ENR | 1293 | /* |
1204 | if (fep->phy_interface == PHY_INTERFACE_MODE_RMII) { | 1294 | * The phy interface and speed need to get configured |
1205 | /* disable the gasket and wait */ | 1295 | * differently on enet-mac. |
1206 | writel(0, fep->hwp + FEC_MIIGSK_ENR); | 1296 | */ |
1207 | while (readl(fep->hwp + FEC_MIIGSK_ENR) & 4) | 1297 | if (id_entry->driver_data & FEC_QUIRK_ENET_MAC) { |
1208 | udelay(1); | 1298 | val = readl(fep->hwp + FEC_R_CNTRL); |
1209 | 1299 | ||
1210 | /* configure the gasket: RMII, 50 MHz, no loopback, no echo */ | 1300 | /* MII or RMII */ |
1211 | writel(1, fep->hwp + FEC_MIIGSK_CFGR); | 1301 | if (fep->phy_interface == PHY_INTERFACE_MODE_RMII) |
1302 | val |= (1 << 8); | ||
1303 | else | ||
1304 | val &= ~(1 << 8); | ||
1212 | 1305 | ||
1213 | /* re-enable the gasket */ | 1306 | /* 10M or 100M */ |
1214 | writel(2, fep->hwp + FEC_MIIGSK_ENR); | 1307 | if (fep->phy_dev && fep->phy_dev->speed == SPEED_100) |
1215 | } | 1308 | val &= ~(1 << 9); |
1309 | else | ||
1310 | val |= (1 << 9); | ||
1311 | |||
1312 | writel(val, fep->hwp + FEC_R_CNTRL); | ||
1313 | } else { | ||
1314 | #ifdef FEC_MIIGSK_ENR | ||
1315 | if (fep->phy_interface == PHY_INTERFACE_MODE_RMII) { | ||
1316 | /* disable the gasket and wait */ | ||
1317 | writel(0, fep->hwp + FEC_MIIGSK_ENR); | ||
1318 | while (readl(fep->hwp + FEC_MIIGSK_ENR) & 4) | ||
1319 | udelay(1); | ||
1320 | |||
1321 | /* | ||
1322 | * configure the gasket: | ||
1323 | * RMII, 50 MHz, no loopback, no echo | ||
1324 | */ | ||
1325 | writel(1, fep->hwp + FEC_MIIGSK_CFGR); | ||
1326 | |||
1327 | /* re-enable the gasket */ | ||
1328 | writel(2, fep->hwp + FEC_MIIGSK_ENR); | ||
1329 | } | ||
1216 | #endif | 1330 | #endif |
1331 | } | ||
1217 | 1332 | ||
1218 | /* And last, enable the transmit and receive processing */ | 1333 | /* And last, enable the transmit and receive processing */ |
1219 | writel(2, fep->hwp + FEC_ECNTRL); | 1334 | writel(2, fep->hwp + FEC_ECNTRL); |
@@ -1410,12 +1525,13 @@ static const struct dev_pm_ops fec_pm_ops = { | |||
1410 | 1525 | ||
1411 | static struct platform_driver fec_driver = { | 1526 | static struct platform_driver fec_driver = { |
1412 | .driver = { | 1527 | .driver = { |
1413 | .name = "fec", | 1528 | .name = DRIVER_NAME, |
1414 | .owner = THIS_MODULE, | 1529 | .owner = THIS_MODULE, |
1415 | #ifdef CONFIG_PM | 1530 | #ifdef CONFIG_PM |
1416 | .pm = &fec_pm_ops, | 1531 | .pm = &fec_pm_ops, |
1417 | #endif | 1532 | #endif |
1418 | }, | 1533 | }, |
1534 | .id_table = fec_devtype, | ||
1419 | .probe = fec_probe, | 1535 | .probe = fec_probe, |
1420 | .remove = __devexit_p(fec_drv_remove), | 1536 | .remove = __devexit_p(fec_drv_remove), |
1421 | }; | 1537 | }; |
diff --git a/drivers/net/fec.h b/drivers/net/fec.h index 2c48b25668d..ace318df4c8 100644 --- a/drivers/net/fec.h +++ b/drivers/net/fec.h | |||
@@ -14,7 +14,8 @@ | |||
14 | /****************************************************************************/ | 14 | /****************************************************************************/ |
15 | 15 | ||
16 | #if defined(CONFIG_M523x) || defined(CONFIG_M527x) || defined(CONFIG_M528x) || \ | 16 | #if defined(CONFIG_M523x) || defined(CONFIG_M527x) || defined(CONFIG_M528x) || \ |
17 | defined(CONFIG_M520x) || defined(CONFIG_M532x) || defined(CONFIG_ARCH_MXC) | 17 | defined(CONFIG_M520x) || defined(CONFIG_M532x) || \ |
18 | defined(CONFIG_ARCH_MXC) || defined(CONFIG_SOC_IMX28) | ||
18 | /* | 19 | /* |
19 | * Just figures, Motorola would have to change the offsets for | 20 | * Just figures, Motorola would have to change the offsets for |
20 | * registers in the same peripheral device on different models | 21 | * registers in the same peripheral device on different models |
@@ -78,7 +79,7 @@ | |||
78 | /* | 79 | /* |
79 | * Define the buffer descriptor structure. | 80 | * Define the buffer descriptor structure. |
80 | */ | 81 | */ |
81 | #ifdef CONFIG_ARCH_MXC | 82 | #if defined(CONFIG_ARCH_MXC) || defined(CONFIG_SOC_IMX28) |
82 | struct bufdesc { | 83 | struct bufdesc { |
83 | unsigned short cbd_datlen; /* Data length */ | 84 | unsigned short cbd_datlen; /* Data length */ |
84 | unsigned short cbd_sc; /* Control and status info */ | 85 | unsigned short cbd_sc; /* Control and status info */ |