mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-12-12 22:33:18 +00:00
5f024d10bb
In lan78xx_read_otp() we want to know if sig is LAN78XX_OTP_INDICATOR_1 or LAN78XX_OTP_INDICATOR_2. In the case of matching the first one we set offset to itself, and clang warns about this. Rework the logic so that if sig is the second indicator we adjust the offset as today and if it does not match the first indicator we return -EINVAL Cc: Marek Vasut <marex@denx.de> Reviewed-by: Marek Vasut <marex@denx.de> Signed-off-by: Tom Rini <trini@konsulko.com>
477 lines
11 KiB
C
477 lines
11 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* Copyright (c) 2017 Microchip Technology Inc. All rights reserved.
|
|
*/
|
|
|
|
#include <dm.h>
|
|
#include <log.h>
|
|
#include <net.h>
|
|
#include <usb.h>
|
|
#include <linux/bitops.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_2)
|
|
offset += 0x100;
|
|
else if (sig != LAN78XX_OTP_INDICATOR_1)
|
|
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_plat(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_plat(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_plat(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 = sizeof(struct lan7x_private),
|
|
.plat_auto = 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);
|