mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-12-25 12:33:41 +00:00
af15946aa0
This bug is the combination of dwc2 USB controller and lan78xx USB ethernet controller, which is the combination in use on the Raspberry Pi Model 3 B+. When the host attempts to receive a packet, but a packet has not arrived, the lan78xx controller responds by setting BIR (Bulk-In Empty Response) to NAK. Unfortunately, this hangs the USB controller and requires the USB controller to be reset. The fix proposed is to have the lan78xx controller respond by setting BIR to ZLP. Signed-off-by: Andrew Thomas <andrew.thomas@oracle.com> Tested-by: Peter Robinson <pbrobinson@gmail.com> Reviewed-by: Alexander Graf <agraf@suse.de>
476 lines
11 KiB
C
476 lines
11 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* Copyright (c) 2017 Microchip Technology Inc. All rights reserved.
|
|
*/
|
|
|
|
#include <dm.h>
|
|
#include <usb.h>
|
|
#include "usb_ether.h"
|
|
#include "lan7x.h"
|
|
|
|
/* LAN78xx specific register/bit defines */
|
|
#define LAN78XX_HW_CFG_LED1_EN BIT(21) /* Muxed with EEDO */
|
|
#define LAN78XX_HW_CFG_LED0_EN BIT(20) /* Muxed with EECLK */
|
|
|
|
#define LAN78XX_USB_CFG0 0x080
|
|
#define LAN78XX_USB_CFG0_BIR BIT(6)
|
|
|
|
#define LAN78XX_BURST_CAP 0x090
|
|
|
|
#define LAN78XX_BULK_IN_DLY 0x094
|
|
|
|
#define LAN78XX_RFE_CTL 0x0B0
|
|
|
|
#define LAN78XX_FCT_RX_CTL 0x0C0
|
|
|
|
#define LAN78XX_FCT_TX_CTL 0x0C4
|
|
|
|
#define LAN78XX_FCT_RX_FIFO_END 0x0C8
|
|
|
|
#define LAN78XX_FCT_TX_FIFO_END 0x0CC
|
|
|
|
#define LAN78XX_FCT_FLOW 0x0D0
|
|
|
|
#define LAN78XX_MAF_BASE 0x400
|
|
#define LAN78XX_MAF_HIX 0x00
|
|
#define LAN78XX_MAF_LOX 0x04
|
|
#define LAN78XX_MAF_HI_BEGIN (LAN78XX_MAF_BASE + LAN78XX_MAF_HIX)
|
|
#define LAN78XX_MAF_LO_BEGIN (LAN78XX_MAF_BASE + LAN78XX_MAF_LOX)
|
|
#define LAN78XX_MAF_HI(index) (LAN78XX_MAF_BASE + (8 * (index)) + \
|
|
LAN78XX_MAF_HIX)
|
|
#define LAN78XX_MAF_LO(index) (LAN78XX_MAF_BASE + (8 * (index)) + \
|
|
LAN78XX_MAF_LOX)
|
|
#define LAN78XX_MAF_HI_VALID BIT(31)
|
|
|
|
/* OTP registers */
|
|
#define LAN78XX_OTP_BASE_ADDR 0x00001000
|
|
|
|
#define LAN78XX_OTP_PWR_DN (LAN78XX_OTP_BASE_ADDR + 4 * 0x00)
|
|
#define LAN78XX_OTP_PWR_DN_PWRDN_N BIT(0)
|
|
|
|
#define LAN78XX_OTP_ADDR1 (LAN78XX_OTP_BASE_ADDR + 4 * 0x01)
|
|
#define LAN78XX_OTP_ADDR1_15_11 0x1F
|
|
|
|
#define LAN78XX_OTP_ADDR2 (LAN78XX_OTP_BASE_ADDR + 4 * 0x02)
|
|
#define LAN78XX_OTP_ADDR2_10_3 0xFF
|
|
|
|
#define LAN78XX_OTP_RD_DATA (LAN78XX_OTP_BASE_ADDR + 4 * 0x06)
|
|
|
|
#define LAN78XX_OTP_FUNC_CMD (LAN78XX_OTP_BASE_ADDR + 4 * 0x08)
|
|
#define LAN78XX_OTP_FUNC_CMD_READ BIT(0)
|
|
|
|
#define LAN78XX_OTP_CMD_GO (LAN78XX_OTP_BASE_ADDR + 4 * 0x0A)
|
|
#define LAN78XX_OTP_CMD_GO_GO BIT(0)
|
|
|
|
#define LAN78XX_OTP_STATUS (LAN78XX_OTP_BASE_ADDR + 4 * 0x0C)
|
|
#define LAN78XX_OTP_STATUS_BUSY BIT(0)
|
|
|
|
#define LAN78XX_OTP_INDICATOR_1 0xF3
|
|
#define LAN78XX_OTP_INDICATOR_2 0xF7
|
|
|
|
/*
|
|
* Lan78xx infrastructure commands
|
|
*/
|
|
static int lan78xx_read_raw_otp(struct usb_device *udev, u32 offset,
|
|
u32 length, u8 *data)
|
|
{
|
|
int i;
|
|
int ret;
|
|
u32 buf;
|
|
|
|
ret = lan7x_read_reg(udev, LAN78XX_OTP_PWR_DN, &buf);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (buf & LAN78XX_OTP_PWR_DN_PWRDN_N) {
|
|
/* clear it and wait to be cleared */
|
|
ret = lan7x_write_reg(udev, LAN78XX_OTP_PWR_DN, 0);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = lan7x_wait_for_bit(udev, "LAN78XX_OTP_PWR_DN_PWRDN_N",
|
|
LAN78XX_OTP_PWR_DN,
|
|
LAN78XX_OTP_PWR_DN_PWRDN_N,
|
|
false, 1000, 0);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
for (i = 0; i < length; i++) {
|
|
ret = lan7x_write_reg(udev, LAN78XX_OTP_ADDR1,
|
|
((offset + i) >> 8) &
|
|
LAN78XX_OTP_ADDR1_15_11);
|
|
if (ret)
|
|
return ret;
|
|
ret = lan7x_write_reg(udev, LAN78XX_OTP_ADDR2,
|
|
((offset + i) & LAN78XX_OTP_ADDR2_10_3));
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = lan7x_write_reg(udev, LAN78XX_OTP_FUNC_CMD,
|
|
LAN78XX_OTP_FUNC_CMD_READ);
|
|
if (ret)
|
|
return ret;
|
|
ret = lan7x_write_reg(udev, LAN78XX_OTP_CMD_GO,
|
|
LAN78XX_OTP_CMD_GO_GO);
|
|
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = lan7x_wait_for_bit(udev, "LAN78XX_OTP_STATUS_BUSY",
|
|
LAN78XX_OTP_STATUS,
|
|
LAN78XX_OTP_STATUS_BUSY,
|
|
false, 1000, 0);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = lan7x_read_reg(udev, LAN78XX_OTP_RD_DATA, &buf);
|
|
if (ret)
|
|
return ret;
|
|
|
|
data[i] = (u8)(buf & 0xFF);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int lan78xx_read_otp(struct usb_device *udev, u32 offset,
|
|
u32 length, u8 *data)
|
|
{
|
|
u8 sig;
|
|
int ret;
|
|
|
|
ret = lan78xx_read_raw_otp(udev, 0, 1, &sig);
|
|
|
|
if (!ret) {
|
|
if (sig == LAN78XX_OTP_INDICATOR_1)
|
|
offset = offset;
|
|
else if (sig == LAN78XX_OTP_INDICATOR_2)
|
|
offset += 0x100;
|
|
else
|
|
return -EINVAL;
|
|
ret = lan78xx_read_raw_otp(udev, offset, length, data);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
debug("LAN78x: MAC address from OTP = %pM\n", data);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int lan78xx_read_otp_mac(unsigned char *enetaddr,
|
|
struct usb_device *udev)
|
|
{
|
|
int ret;
|
|
|
|
memset(enetaddr, 0, 6);
|
|
|
|
ret = lan78xx_read_otp(udev,
|
|
EEPROM_MAC_OFFSET,
|
|
ETH_ALEN,
|
|
enetaddr);
|
|
if (!ret && is_valid_ethaddr(enetaddr)) {
|
|
/* eeprom values are valid so use them */
|
|
debug("MAC address read from OTP %pM\n", enetaddr);
|
|
return 0;
|
|
}
|
|
debug("MAC address read from OTP invalid %pM\n", enetaddr);
|
|
|
|
memset(enetaddr, 0, 6);
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int lan78xx_update_flowcontrol(struct usb_device *udev,
|
|
struct ueth_data *dev)
|
|
{
|
|
uint32_t flow = 0, fct_flow = 0;
|
|
int ret;
|
|
|
|
ret = lan7x_update_flowcontrol(udev, dev, &flow, &fct_flow);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = lan7x_write_reg(udev, LAN78XX_FCT_FLOW, fct_flow);
|
|
if (ret)
|
|
return ret;
|
|
return lan7x_write_reg(udev, FLOW, flow);
|
|
}
|
|
|
|
static int lan78xx_read_mac(unsigned char *enetaddr,
|
|
struct usb_device *udev,
|
|
struct lan7x_private *priv)
|
|
{
|
|
u32 val;
|
|
int ret;
|
|
int saved = 0, done = 0;
|
|
|
|
/*
|
|
* Depends on chip, some EEPROM pins are muxed with LED function.
|
|
* disable & restore LED function to access EEPROM.
|
|
*/
|
|
if ((priv->chipid == ID_REV_CHIP_ID_7800) ||
|
|
(priv->chipid == ID_REV_CHIP_ID_7850)) {
|
|
ret = lan7x_read_reg(udev, HW_CFG, &val);
|
|
if (ret)
|
|
return ret;
|
|
saved = val;
|
|
val &= ~(LAN78XX_HW_CFG_LED1_EN | LAN78XX_HW_CFG_LED0_EN);
|
|
ret = lan7x_write_reg(udev, HW_CFG, val);
|
|
if (ret)
|
|
goto restore;
|
|
}
|
|
|
|
/*
|
|
* Refer to the doc/README.enetaddr and doc/README.usb for
|
|
* the U-Boot MAC address policy
|
|
*/
|
|
/* try reading mac address from EEPROM, then from OTP */
|
|
ret = lan7x_read_eeprom_mac(enetaddr, udev);
|
|
if (!ret)
|
|
done = 1;
|
|
|
|
restore:
|
|
if ((priv->chipid == ID_REV_CHIP_ID_7800) ||
|
|
(priv->chipid == ID_REV_CHIP_ID_7850)) {
|
|
ret = lan7x_write_reg(udev, HW_CFG, saved);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
/* if the EEPROM mac address is good, then exit */
|
|
if (done)
|
|
return 0;
|
|
|
|
/* try reading mac address from OTP if the device is LAN78xx */
|
|
return lan78xx_read_otp_mac(enetaddr, udev);
|
|
}
|
|
|
|
static int lan78xx_set_receive_filter(struct usb_device *udev)
|
|
{
|
|
/* No multicast in u-boot for now */
|
|
return lan7x_write_reg(udev, LAN78XX_RFE_CTL,
|
|
RFE_CTL_BCAST_EN | RFE_CTL_DA_PERFECT);
|
|
}
|
|
|
|
/* starts the TX path */
|
|
static void lan78xx_start_tx_path(struct usb_device *udev)
|
|
{
|
|
/* Enable Tx at MAC */
|
|
lan7x_write_reg(udev, MAC_TX, MAC_TX_TXEN);
|
|
|
|
/* Enable Tx at SCSRs */
|
|
lan7x_write_reg(udev, LAN78XX_FCT_TX_CTL, FCT_TX_CTL_EN);
|
|
}
|
|
|
|
/* Starts the Receive path */
|
|
static void lan78xx_start_rx_path(struct usb_device *udev)
|
|
{
|
|
/* Enable Rx at MAC */
|
|
lan7x_write_reg(udev, MAC_RX,
|
|
LAN7X_MAC_RX_MAX_SIZE_DEFAULT |
|
|
MAC_RX_FCS_STRIP | MAC_RX_RXEN);
|
|
|
|
/* Enable Rx at SCSRs */
|
|
lan7x_write_reg(udev, LAN78XX_FCT_RX_CTL, FCT_RX_CTL_EN);
|
|
}
|
|
|
|
static int lan78xx_basic_reset(struct usb_device *udev,
|
|
struct ueth_data *dev,
|
|
struct lan7x_private *priv)
|
|
{
|
|
int ret;
|
|
u32 val;
|
|
|
|
ret = lan7x_basic_reset(udev, dev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Keep the chip ID */
|
|
ret = lan7x_read_reg(udev, ID_REV, &val);
|
|
if (ret)
|
|
return ret;
|
|
debug("LAN78xx ID_REV = 0x%08x\n", val);
|
|
|
|
priv->chipid = (val & ID_REV_CHIP_ID_MASK) >> 16;
|
|
|
|
/* Respond to the IN token with a NAK */
|
|
ret = lan7x_read_reg(udev, LAN78XX_USB_CFG0, &val);
|
|
if (ret)
|
|
return ret;
|
|
val &= ~LAN78XX_USB_CFG0_BIR;
|
|
return lan7x_write_reg(udev, LAN78XX_USB_CFG0, val);
|
|
}
|
|
|
|
int lan78xx_write_hwaddr(struct udevice *dev)
|
|
{
|
|
struct usb_device *udev = dev_get_parent_priv(dev);
|
|
struct eth_pdata *pdata = dev_get_platdata(dev);
|
|
unsigned char *enetaddr = pdata->enetaddr;
|
|
u32 addr_lo = get_unaligned_le32(&enetaddr[0]);
|
|
u32 addr_hi = (u32)get_unaligned_le16(&enetaddr[4]);
|
|
int ret;
|
|
|
|
/* set hardware address */
|
|
ret = lan7x_write_reg(udev, RX_ADDRL, addr_lo);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = lan7x_write_reg(udev, RX_ADDRH, addr_hi);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = lan7x_write_reg(udev, LAN78XX_MAF_LO(0), addr_lo);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = lan7x_write_reg(udev, LAN78XX_MAF_HI(0),
|
|
addr_hi | LAN78XX_MAF_HI_VALID);
|
|
if (ret)
|
|
return ret;
|
|
|
|
debug("MAC addr %pM written\n", enetaddr);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int lan78xx_eth_start(struct udevice *dev)
|
|
{
|
|
struct usb_device *udev = dev_get_parent_priv(dev);
|
|
struct lan7x_private *priv = dev_get_priv(dev);
|
|
|
|
int ret;
|
|
u32 write_buf;
|
|
|
|
/* Reset and read Mac addr were done in probe() */
|
|
ret = lan78xx_write_hwaddr(dev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = lan7x_write_reg(udev, LAN78XX_BURST_CAP, 0);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = lan7x_write_reg(udev, LAN78XX_BULK_IN_DLY, DEFAULT_BULK_IN_DELAY);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = lan7x_write_reg(udev, INT_STS, 0xFFFFFFFF);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* set FIFO sizes */
|
|
ret = lan7x_write_reg(udev, LAN78XX_FCT_RX_FIFO_END,
|
|
(MAX_RX_FIFO_SIZE - 512) / 512);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = lan7x_write_reg(udev, LAN78XX_FCT_TX_FIFO_END,
|
|
(MAX_TX_FIFO_SIZE - 512) / 512);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Init Tx */
|
|
ret = lan7x_write_reg(udev, FLOW, 0);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Init Rx. Set Vlan, keep default for VLAN on 78xx */
|
|
ret = lan78xx_set_receive_filter(udev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Init PHY, autonego, and link */
|
|
ret = lan7x_eth_phylib_connect(dev, &priv->ueth);
|
|
if (ret)
|
|
return ret;
|
|
ret = lan7x_eth_phylib_config_start(dev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/*
|
|
* MAC_CR has to be set after PHY init.
|
|
* MAC will auto detect the PHY speed.
|
|
*/
|
|
ret = lan7x_read_reg(udev, MAC_CR, &write_buf);
|
|
if (ret)
|
|
return ret;
|
|
write_buf |= MAC_CR_AUTO_DUPLEX | MAC_CR_AUTO_SPEED | MAC_CR_ADP;
|
|
ret = lan7x_write_reg(udev, MAC_CR, write_buf);
|
|
if (ret)
|
|
return ret;
|
|
|
|
lan78xx_start_tx_path(udev);
|
|
lan78xx_start_rx_path(udev);
|
|
|
|
return lan78xx_update_flowcontrol(udev, &priv->ueth);
|
|
}
|
|
|
|
int lan78xx_read_rom_hwaddr(struct udevice *dev)
|
|
{
|
|
struct usb_device *udev = dev_get_parent_priv(dev);
|
|
struct eth_pdata *pdata = dev_get_platdata(dev);
|
|
struct lan7x_private *priv = dev_get_priv(dev);
|
|
int ret;
|
|
|
|
ret = lan78xx_read_mac(pdata->enetaddr, udev, priv);
|
|
if (ret)
|
|
memset(pdata->enetaddr, 0, 6);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int lan78xx_eth_probe(struct udevice *dev)
|
|
{
|
|
struct usb_device *udev = dev_get_parent_priv(dev);
|
|
struct lan7x_private *priv = dev_get_priv(dev);
|
|
struct ueth_data *ueth = &priv->ueth;
|
|
struct eth_pdata *pdata = dev_get_platdata(dev);
|
|
int ret;
|
|
|
|
/* Do a reset in order to get the MAC address from HW */
|
|
if (lan78xx_basic_reset(udev, ueth, priv))
|
|
return 0;
|
|
|
|
/* Get the MAC address */
|
|
/*
|
|
* We must set the eth->enetaddr from HW because the upper layer
|
|
* will force to use the environmental var (usbethaddr) or random if
|
|
* there is no valid MAC address in eth->enetaddr.
|
|
*/
|
|
lan78xx_read_mac(pdata->enetaddr, udev, priv);
|
|
/* Do not return 0 for not finding MAC addr in HW */
|
|
|
|
ret = usb_ether_register(dev, ueth, RX_URB_SIZE);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Register phylib */
|
|
return lan7x_phylib_register(dev);
|
|
}
|
|
|
|
static const struct eth_ops lan78xx_eth_ops = {
|
|
.start = lan78xx_eth_start,
|
|
.send = lan7x_eth_send,
|
|
.recv = lan7x_eth_recv,
|
|
.free_pkt = lan7x_free_pkt,
|
|
.stop = lan7x_eth_stop,
|
|
.write_hwaddr = lan78xx_write_hwaddr,
|
|
.read_rom_hwaddr = lan78xx_read_rom_hwaddr,
|
|
};
|
|
|
|
U_BOOT_DRIVER(lan78xx_eth) = {
|
|
.name = "lan78xx_eth",
|
|
.id = UCLASS_ETH,
|
|
.probe = lan78xx_eth_probe,
|
|
.remove = lan7x_eth_remove,
|
|
.ops = &lan78xx_eth_ops,
|
|
.priv_auto_alloc_size = sizeof(struct lan7x_private),
|
|
.platdata_auto_alloc_size = sizeof(struct eth_pdata),
|
|
};
|
|
|
|
static const struct usb_device_id lan78xx_eth_id_table[] = {
|
|
{ USB_DEVICE(0x0424, 0x7800) }, /* LAN7800 USB Ethernet */
|
|
{ USB_DEVICE(0x0424, 0x7850) }, /* LAN7850 USB Ethernet */
|
|
{ } /* Terminating entry */
|
|
};
|
|
|
|
U_BOOT_USB_DEVICE(lan78xx_eth, lan78xx_eth_id_table);
|