| // SPDX-License-Identifier: GPL-2.0+ | |
| /* | |
| * Copyright 2014 Broadcom Corporation. | |
| */ | |
| #include <log.h> | |
| #include <malloc.h> | |
| #include <net.h> | |
| #include <config.h> | |
| #include <linux/delay.h> | |
| #include <linux/printk.h> | |
| #include <phy.h> | |
| #include <miiphy.h> | |
| #include <asm/io.h> | |
| #include <netdev.h> | |
| #include "bcm-sf2-eth.h" | |
| #if defined(CONFIG_BCM_SF2_ETH_GMAC) | |
| #include "bcm-sf2-eth-gmac.h" | |
| #else | |
| #error "bcm_sf2_eth: NEED to define a MAC!" | |
| #endif | |
| #define BCM_NET_MODULE_DESCRIPTION "Broadcom Starfighter2 Ethernet driver" | |
| #define BCM_NET_MODULE_VERSION "0.1" | |
| #define BCM_SF2_ETH_DEV_NAME "bcm_sf2" | |
| static const char banner[] = | |
| BCM_NET_MODULE_DESCRIPTION " " BCM_NET_MODULE_VERSION "\n"; | |
| static int bcm_sf2_eth_init(struct eth_device *dev) | |
| { | |
| struct eth_info *eth = (struct eth_info *)(dev->priv); | |
| struct eth_dma *dma = &(eth->dma); | |
| struct phy_device *phydev; | |
| int rc = 0; | |
| int i; | |
| rc = eth->mac_init(dev); | |
| if (rc) { | |
| pr_err("%s: Couldn't cofigure MAC!\n", __func__); | |
| return rc; | |
| } | |
| /* disable DMA */ | |
| dma->disable_dma(dma, MAC_DMA_RX); | |
| dma->disable_dma(dma, MAC_DMA_TX); | |
| eth->port_num = 0; | |
| debug("Connecting PHY 0...\n"); | |
| phydev = phy_connect(miiphy_get_dev_by_name(dev->name), | |
| -1, dev, eth->phy_interface); | |
| if (phydev != NULL) { | |
| eth->port[0] = phydev; | |
| eth->port_num += 1; | |
| } else { | |
| debug("No PHY found for port 0\n"); | |
| } | |
| for (i = 0; i < eth->port_num; i++) | |
| phy_config(eth->port[i]); | |
| return rc; | |
| } | |
| /* | |
| * u-boot net functions | |
| */ | |
| static int bcm_sf2_eth_send(struct eth_device *dev, void *packet, int length) | |
| { | |
| struct eth_dma *dma = &(((struct eth_info *)(dev->priv))->dma); | |
| uint8_t *buf = (uint8_t *)packet; | |
| int rc = 0; | |
| int i = 0; | |
| debug("%s enter\n", __func__); | |
| /* load buf and start transmit */ | |
| rc = dma->tx_packet(dma, buf, length); | |
| if (rc) { | |
| debug("ERROR - Tx failed\n"); | |
| return rc; | |
| } | |
| while (!(dma->check_tx_done(dma))) { | |
| udelay(100); | |
| debug("."); | |
| i++; | |
| if (i > 20) { | |
| pr_err("%s: Tx timeout: retried 20 times\n", __func__); | |
| rc = -1; | |
| break; | |
| } | |
| } | |
| debug("%s exit rc(0x%x)\n", __func__, rc); | |
| return rc; | |
| } | |
| static int bcm_sf2_eth_receive(struct eth_device *dev) | |
| { | |
| struct eth_dma *dma = &(((struct eth_info *)(dev->priv))->dma); | |
| uint8_t *buf = (uint8_t *)net_rx_packets[0]; | |
| int rcvlen; | |
| int rc = 0; | |
| int i = 0; | |
| while (1) { | |
| /* Poll Rx queue to get a packet */ | |
| rcvlen = dma->check_rx_done(dma, buf); | |
| if (rcvlen < 0) { | |
| /* No packet received */ | |
| rc = -1; | |
| debug("\nNO More Rx\n"); | |
| break; | |
| } else if ((rcvlen == 0) || (rcvlen > RX_BUF_SIZE)) { | |
| pr_err("%s: Wrong Ethernet packet size (%d B), skip!\n", | |
| __func__, rcvlen); | |
| break; | |
| } else { | |
| debug("recieved\n"); | |
| /* Forward received packet to uboot network handler */ | |
| net_process_received_packet(buf, rcvlen); | |
| if (++i >= PKTBUFSRX) | |
| i = 0; | |
| buf = net_rx_packets[i]; | |
| } | |
| } | |
| return rc; | |
| } | |
| static int bcm_sf2_eth_write_hwaddr(struct eth_device *dev) | |
| { | |
| struct eth_info *eth = (struct eth_info *)(dev->priv); | |
| printf(" ETH MAC: %02x:%02x:%02x:%02x:%02x:%02x\n", | |
| dev->enetaddr[0], dev->enetaddr[1], dev->enetaddr[2], | |
| dev->enetaddr[3], dev->enetaddr[4], dev->enetaddr[5]); | |
| return eth->set_mac_addr(dev->enetaddr); | |
| } | |
| static int bcm_sf2_eth_open(struct eth_device *dev, struct bd_info *bt) | |
| { | |
| struct eth_info *eth = (struct eth_info *)(dev->priv); | |
| struct eth_dma *dma = &(eth->dma); | |
| int i; | |
| debug("Enabling BCM SF2 Ethernet.\n"); | |
| eth->enable_mac(); | |
| /* enable tx and rx DMA */ | |
| dma->enable_dma(dma, MAC_DMA_RX); | |
| dma->enable_dma(dma, MAC_DMA_TX); | |
| /* | |
| * Need to start PHY here because link speed can change | |
| * before each ethernet operation | |
| */ | |
| for (i = 0; i < eth->port_num; i++) { | |
| if (phy_startup(eth->port[i])) { | |
| pr_err("%s: PHY %d startup failed!\n", __func__, i); | |
| if (i == CONFIG_BCM_SF2_ETH_DEFAULT_PORT) { | |
| pr_err("%s: No default port %d!\n", __func__, i); | |
| return -1; | |
| } | |
| } | |
| } | |
| /* Set MAC speed using default port */ | |
| i = CONFIG_BCM_SF2_ETH_DEFAULT_PORT; | |
| debug("PHY %d: speed:%d, duplex:%d, link:%d\n", i, | |
| eth->port[i]->speed, eth->port[i]->duplex, eth->port[i]->link); | |
| eth->set_mac_speed(eth->port[i]->speed, eth->port[i]->duplex); | |
| debug("Enable Ethernet Done.\n"); | |
| return 0; | |
| } | |
| static void bcm_sf2_eth_close(struct eth_device *dev) | |
| { | |
| struct eth_info *eth = (struct eth_info *)(dev->priv); | |
| struct eth_dma *dma = &(eth->dma); | |
| /* disable DMA */ | |
| dma->disable_dma(dma, MAC_DMA_RX); | |
| dma->disable_dma(dma, MAC_DMA_TX); | |
| eth->disable_mac(); | |
| } | |
| int bcm_sf2_eth_register(struct bd_info *bis, u8 dev_num) | |
| { | |
| struct eth_device *dev; | |
| struct eth_info *eth; | |
| int rc; | |
| dev = (struct eth_device *)malloc(sizeof(struct eth_device)); | |
| if (dev == NULL) { | |
| pr_err("%s: Not enough memory!\n", __func__); | |
| return -1; | |
| } | |
| eth = (struct eth_info *)malloc(sizeof(struct eth_info)); | |
| if (eth == NULL) { | |
| pr_err("%s: Not enough memory!\n", __func__); | |
| return -1; | |
| } | |
| printf(banner); | |
| memset(dev, 0, sizeof(*dev)); | |
| sprintf(dev->name, "%s_%s-%hu", BCM_SF2_ETH_DEV_NAME, | |
| BCM_SF2_ETH_MAC_NAME, dev_num); | |
| dev->priv = (void *)eth; | |
| dev->iobase = 0; | |
| dev->init = bcm_sf2_eth_open; | |
| dev->halt = bcm_sf2_eth_close; | |
| dev->send = bcm_sf2_eth_send; | |
| dev->recv = bcm_sf2_eth_receive; | |
| dev->write_hwaddr = bcm_sf2_eth_write_hwaddr; | |
| #ifdef CONFIG_BCM_SF2_ETH_GMAC | |
| if (gmac_add(dev)) { | |
| free(eth); | |
| free(dev); | |
| pr_err("%s: Adding GMAC failed!\n", __func__); | |
| return -1; | |
| } | |
| #else | |
| #error "bcm_sf2_eth: NEED to register a MAC!" | |
| #endif | |
| eth_register(dev); | |
| #ifdef CONFIG_CMD_MII | |
| int retval; | |
| struct mii_dev *mdiodev = mdio_alloc(); | |
| if (!mdiodev) | |
| return -ENOMEM; | |
| strlcpy(mdiodev->name, dev->name, MDIO_NAME_LEN); | |
| mdiodev->read = eth->miiphy_read; | |
| mdiodev->write = eth->miiphy_write; | |
| retval = mdio_register(mdiodev); | |
| if (retval < 0) | |
| return retval; | |
| #endif | |
| /* Initialization */ | |
| debug("Ethernet initialization ..."); | |
| rc = bcm_sf2_eth_init(dev); | |
| if (rc != 0) { | |
| pr_err("%s: configuration failed!\n", __func__); | |
| return -1; | |
| } | |
| printf("Basic ethernet functionality initialized\n"); | |
| return 0; | |
| } |