mirror of
https://github.com/brain-hackers/u-boot-brain
synced 2024-08-17 15:38:51 +09:00
![Paul Burton](/assets/img/avatar_default.png)
On MIPS systems DMA isn't coherent with the CPU caches unless an IOCU is present. When there is no IOCU we need to writeback or invalidate the data caches at appropriate points. Perform this cache maintenance in the pch_gbe driver which is used on the MIPS Boston development board. Signed-off-by: Paul Burton <paul.burton@imgtec.com> Reviewed-by: Bin Meng <bmeng.cn@gmail.com> Tested-by: Bin Meng <bmeng.cn@gmail.com> Signed-off-by: Daniel Schwierzeck <daniel.schwierzeck@gmail.com> Reviewed-by: Simon Glass <sjg@chromium.org> Acked-by: Joe Hershberger <joe.hershberger@ni.com>
505 lines
13 KiB
C
505 lines
13 KiB
C
/*
|
|
* Copyright (C) 2015, Bin Meng <bmeng.cn@gmail.com>
|
|
*
|
|
* Intel Platform Controller Hub EG20T (codename Topcliff) GMAC Driver
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0+
|
|
*/
|
|
|
|
#include <common.h>
|
|
#include <dm.h>
|
|
#include <errno.h>
|
|
#include <asm/io.h>
|
|
#include <pci.h>
|
|
#include <miiphy.h>
|
|
#include "pch_gbe.h"
|
|
|
|
#if !defined(CONFIG_PHYLIB)
|
|
# error "PCH Gigabit Ethernet driver requires PHYLIB - missing CONFIG_PHYLIB"
|
|
#endif
|
|
|
|
static struct pci_device_id supported[] = {
|
|
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_TCF_GBE) },
|
|
{ }
|
|
};
|
|
|
|
static void pch_gbe_mac_read(struct pch_gbe_regs *mac_regs, u8 *addr)
|
|
{
|
|
u32 macid_hi, macid_lo;
|
|
|
|
macid_hi = readl(&mac_regs->mac_adr[0].high);
|
|
macid_lo = readl(&mac_regs->mac_adr[0].low) & 0xffff;
|
|
debug("pch_gbe: macid_hi %#x macid_lo %#x\n", macid_hi, macid_lo);
|
|
|
|
addr[0] = (u8)(macid_hi & 0xff);
|
|
addr[1] = (u8)((macid_hi >> 8) & 0xff);
|
|
addr[2] = (u8)((macid_hi >> 16) & 0xff);
|
|
addr[3] = (u8)((macid_hi >> 24) & 0xff);
|
|
addr[4] = (u8)(macid_lo & 0xff);
|
|
addr[5] = (u8)((macid_lo >> 8) & 0xff);
|
|
}
|
|
|
|
static int pch_gbe_mac_write(struct pch_gbe_regs *mac_regs, u8 *addr)
|
|
{
|
|
u32 macid_hi, macid_lo;
|
|
ulong start;
|
|
|
|
macid_hi = addr[0] + (addr[1] << 8) + (addr[2] << 16) + (addr[3] << 24);
|
|
macid_lo = addr[4] + (addr[5] << 8);
|
|
|
|
writel(macid_hi, &mac_regs->mac_adr[0].high);
|
|
writel(macid_lo, &mac_regs->mac_adr[0].low);
|
|
writel(0xfffe, &mac_regs->addr_mask);
|
|
|
|
start = get_timer(0);
|
|
while (get_timer(start) < PCH_GBE_TIMEOUT) {
|
|
if (!(readl(&mac_regs->addr_mask) & PCH_GBE_BUSY))
|
|
return 0;
|
|
|
|
udelay(10);
|
|
}
|
|
|
|
return -ETIME;
|
|
}
|
|
|
|
static int pch_gbe_reset(struct udevice *dev)
|
|
{
|
|
struct pch_gbe_priv *priv = dev_get_priv(dev);
|
|
struct eth_pdata *plat = dev_get_platdata(dev);
|
|
struct pch_gbe_regs *mac_regs = priv->mac_regs;
|
|
ulong start;
|
|
|
|
priv->rx_idx = 0;
|
|
priv->tx_idx = 0;
|
|
|
|
writel(PCH_GBE_ALL_RST, &mac_regs->reset);
|
|
|
|
/*
|
|
* Configure the MAC to RGMII mode after reset
|
|
*
|
|
* For some unknown reason, we must do the configuration here right
|
|
* after resetting the whole MAC, otherwise the reset bit in the RESET
|
|
* register will never be cleared by the hardware. And there is another
|
|
* way of having the same magic, that is to configure the MODE register
|
|
* to have the MAC work in MII/GMII mode, which is how current Linux
|
|
* pch_gbe driver does. Since anyway we need program the MAC to RGMII
|
|
* mode in the driver, we just do it here.
|
|
*
|
|
* Note: this behavior is not documented in the hardware manual.
|
|
*/
|
|
writel(PCH_GBE_RGMII_MODE_RGMII | PCH_GBE_CHIP_TYPE_INTERNAL,
|
|
&mac_regs->rgmii_ctrl);
|
|
|
|
start = get_timer(0);
|
|
while (get_timer(start) < PCH_GBE_TIMEOUT) {
|
|
if (!(readl(&mac_regs->reset) & PCH_GBE_ALL_RST)) {
|
|
/*
|
|
* Soft reset clears hardware MAC address registers,
|
|
* so we have to reload MAC address here in order to
|
|
* make linux pch_gbe driver happy.
|
|
*/
|
|
return pch_gbe_mac_write(mac_regs, plat->enetaddr);
|
|
}
|
|
|
|
udelay(10);
|
|
}
|
|
|
|
debug("pch_gbe: reset timeout\n");
|
|
return -ETIME;
|
|
}
|
|
|
|
static void pch_gbe_rx_descs_init(struct udevice *dev)
|
|
{
|
|
struct pch_gbe_priv *priv = dev_get_priv(dev);
|
|
struct pch_gbe_regs *mac_regs = priv->mac_regs;
|
|
struct pch_gbe_rx_desc *rx_desc = &priv->rx_desc[0];
|
|
int i;
|
|
|
|
memset(rx_desc, 0, sizeof(struct pch_gbe_rx_desc) * PCH_GBE_DESC_NUM);
|
|
for (i = 0; i < PCH_GBE_DESC_NUM; i++)
|
|
rx_desc[i].buffer_addr = dm_pci_virt_to_mem(priv->dev,
|
|
priv->rx_buff[i]);
|
|
|
|
flush_dcache_range((ulong)rx_desc, (ulong)&rx_desc[PCH_GBE_DESC_NUM]);
|
|
|
|
writel(dm_pci_virt_to_mem(priv->dev, rx_desc),
|
|
&mac_regs->rx_dsc_base);
|
|
writel(sizeof(struct pch_gbe_rx_desc) * (PCH_GBE_DESC_NUM - 1),
|
|
&mac_regs->rx_dsc_size);
|
|
|
|
writel(dm_pci_virt_to_mem(priv->dev, rx_desc + 1),
|
|
&mac_regs->rx_dsc_sw_p);
|
|
}
|
|
|
|
static void pch_gbe_tx_descs_init(struct udevice *dev)
|
|
{
|
|
struct pch_gbe_priv *priv = dev_get_priv(dev);
|
|
struct pch_gbe_regs *mac_regs = priv->mac_regs;
|
|
struct pch_gbe_tx_desc *tx_desc = &priv->tx_desc[0];
|
|
|
|
memset(tx_desc, 0, sizeof(struct pch_gbe_tx_desc) * PCH_GBE_DESC_NUM);
|
|
|
|
flush_dcache_range((ulong)tx_desc, (ulong)&tx_desc[PCH_GBE_DESC_NUM]);
|
|
|
|
writel(dm_pci_virt_to_mem(priv->dev, tx_desc),
|
|
&mac_regs->tx_dsc_base);
|
|
writel(sizeof(struct pch_gbe_tx_desc) * (PCH_GBE_DESC_NUM - 1),
|
|
&mac_regs->tx_dsc_size);
|
|
writel(dm_pci_virt_to_mem(priv->dev, tx_desc + 1),
|
|
&mac_regs->tx_dsc_sw_p);
|
|
}
|
|
|
|
static void pch_gbe_adjust_link(struct pch_gbe_regs *mac_regs,
|
|
struct phy_device *phydev)
|
|
{
|
|
if (!phydev->link) {
|
|
printf("%s: No link.\n", phydev->dev->name);
|
|
return;
|
|
}
|
|
|
|
clrbits_le32(&mac_regs->rgmii_ctrl,
|
|
PCH_GBE_RGMII_RATE_2_5M | PCH_GBE_CRS_SEL);
|
|
clrbits_le32(&mac_regs->mode,
|
|
PCH_GBE_MODE_GMII_ETHER | PCH_GBE_MODE_FULL_DUPLEX);
|
|
|
|
switch (phydev->speed) {
|
|
case 1000:
|
|
setbits_le32(&mac_regs->rgmii_ctrl, PCH_GBE_RGMII_RATE_125M);
|
|
setbits_le32(&mac_regs->mode, PCH_GBE_MODE_GMII_ETHER);
|
|
break;
|
|
case 100:
|
|
setbits_le32(&mac_regs->rgmii_ctrl, PCH_GBE_RGMII_RATE_25M);
|
|
setbits_le32(&mac_regs->mode, PCH_GBE_MODE_MII_ETHER);
|
|
break;
|
|
case 10:
|
|
setbits_le32(&mac_regs->rgmii_ctrl, PCH_GBE_RGMII_RATE_2_5M);
|
|
setbits_le32(&mac_regs->mode, PCH_GBE_MODE_MII_ETHER);
|
|
break;
|
|
}
|
|
|
|
if (phydev->duplex) {
|
|
setbits_le32(&mac_regs->rgmii_ctrl, PCH_GBE_CRS_SEL);
|
|
setbits_le32(&mac_regs->mode, PCH_GBE_MODE_FULL_DUPLEX);
|
|
}
|
|
|
|
printf("Speed: %d, %s duplex\n", phydev->speed,
|
|
(phydev->duplex) ? "full" : "half");
|
|
|
|
return;
|
|
}
|
|
|
|
static int pch_gbe_start(struct udevice *dev)
|
|
{
|
|
struct pch_gbe_priv *priv = dev_get_priv(dev);
|
|
struct pch_gbe_regs *mac_regs = priv->mac_regs;
|
|
|
|
if (pch_gbe_reset(dev))
|
|
return -1;
|
|
|
|
pch_gbe_rx_descs_init(dev);
|
|
pch_gbe_tx_descs_init(dev);
|
|
|
|
/* Enable frame bursting */
|
|
writel(PCH_GBE_MODE_FR_BST, &mac_regs->mode);
|
|
/* Disable TCP/IP accelerator */
|
|
writel(PCH_GBE_RX_TCPIPACC_OFF, &mac_regs->tcpip_acc);
|
|
/* Disable RX flow control */
|
|
writel(0, &mac_regs->rx_fctrl);
|
|
/* Configure RX/TX mode */
|
|
writel(PCH_GBE_RH_ALM_EMP_16 | PCH_GBE_RH_ALM_FULL_16 |
|
|
PCH_GBE_RH_RD_TRG_32, &mac_regs->rx_mode);
|
|
writel(PCH_GBE_TM_TH_TX_STRT_32 | PCH_GBE_TM_TH_ALM_EMP_16 |
|
|
PCH_GBE_TM_TH_ALM_FULL_32 | PCH_GBE_TM_ST_AND_FD |
|
|
PCH_GBE_TM_SHORT_PKT, &mac_regs->tx_mode);
|
|
|
|
/* Start up the PHY */
|
|
if (phy_startup(priv->phydev)) {
|
|
printf("Could not initialize PHY %s\n",
|
|
priv->phydev->dev->name);
|
|
return -1;
|
|
}
|
|
|
|
pch_gbe_adjust_link(mac_regs, priv->phydev);
|
|
|
|
if (!priv->phydev->link)
|
|
return -1;
|
|
|
|
/* Enable TX & RX */
|
|
writel(PCH_GBE_RX_DMA_EN | PCH_GBE_TX_DMA_EN, &mac_regs->dma_ctrl);
|
|
writel(PCH_GBE_MRE_MAC_RX_EN, &mac_regs->mac_rx_en);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void pch_gbe_stop(struct udevice *dev)
|
|
{
|
|
struct pch_gbe_priv *priv = dev_get_priv(dev);
|
|
|
|
pch_gbe_reset(dev);
|
|
|
|
phy_shutdown(priv->phydev);
|
|
}
|
|
|
|
static int pch_gbe_send(struct udevice *dev, void *packet, int length)
|
|
{
|
|
struct pch_gbe_priv *priv = dev_get_priv(dev);
|
|
struct pch_gbe_regs *mac_regs = priv->mac_regs;
|
|
struct pch_gbe_tx_desc *tx_head, *tx_desc;
|
|
u16 frame_ctrl = 0;
|
|
u32 int_st;
|
|
ulong start;
|
|
|
|
flush_dcache_range((ulong)packet, (ulong)packet + length);
|
|
|
|
tx_head = &priv->tx_desc[0];
|
|
tx_desc = &priv->tx_desc[priv->tx_idx];
|
|
|
|
if (length < 64)
|
|
frame_ctrl |= PCH_GBE_TXD_CTRL_APAD;
|
|
|
|
tx_desc->buffer_addr = dm_pci_virt_to_mem(priv->dev, packet);
|
|
tx_desc->length = length;
|
|
tx_desc->tx_words_eob = length + 3;
|
|
tx_desc->tx_frame_ctrl = frame_ctrl;
|
|
tx_desc->dma_status = 0;
|
|
tx_desc->gbec_status = 0;
|
|
|
|
flush_dcache_range((ulong)tx_desc, (ulong)&tx_desc[1]);
|
|
|
|
/* Test the wrap-around condition */
|
|
if (++priv->tx_idx >= PCH_GBE_DESC_NUM)
|
|
priv->tx_idx = 0;
|
|
|
|
writel(dm_pci_virt_to_mem(priv->dev, tx_head + priv->tx_idx),
|
|
&mac_regs->tx_dsc_sw_p);
|
|
|
|
start = get_timer(0);
|
|
while (get_timer(start) < PCH_GBE_TIMEOUT) {
|
|
int_st = readl(&mac_regs->int_st);
|
|
if (int_st & PCH_GBE_INT_TX_CMPLT)
|
|
return 0;
|
|
|
|
udelay(10);
|
|
}
|
|
|
|
debug("pch_gbe: sent failed\n");
|
|
return -ETIME;
|
|
}
|
|
|
|
static int pch_gbe_recv(struct udevice *dev, int flags, uchar **packetp)
|
|
{
|
|
struct pch_gbe_priv *priv = dev_get_priv(dev);
|
|
struct pch_gbe_regs *mac_regs = priv->mac_regs;
|
|
struct pch_gbe_rx_desc *rx_desc;
|
|
ulong hw_desc, length;
|
|
void *buffer;
|
|
|
|
rx_desc = &priv->rx_desc[priv->rx_idx];
|
|
|
|
readl(&mac_regs->int_st);
|
|
hw_desc = readl(&mac_regs->rx_dsc_hw_p_hld);
|
|
|
|
/* Just return if not receiving any packet */
|
|
if (virt_to_phys(rx_desc) == hw_desc)
|
|
return -EAGAIN;
|
|
|
|
/* Invalidate the descriptor */
|
|
invalidate_dcache_range((ulong)rx_desc, (ulong)&rx_desc[1]);
|
|
|
|
length = rx_desc->rx_words_eob - 3 - ETH_FCS_LEN;
|
|
buffer = dm_pci_mem_to_virt(priv->dev, rx_desc->buffer_addr, length, 0);
|
|
invalidate_dcache_range((ulong)buffer, (ulong)buffer + length);
|
|
*packetp = (uchar *)buffer;
|
|
|
|
return length;
|
|
}
|
|
|
|
static int pch_gbe_free_pkt(struct udevice *dev, uchar *packet, int length)
|
|
{
|
|
struct pch_gbe_priv *priv = dev_get_priv(dev);
|
|
struct pch_gbe_regs *mac_regs = priv->mac_regs;
|
|
struct pch_gbe_rx_desc *rx_head = &priv->rx_desc[0];
|
|
int rx_swp;
|
|
|
|
/* Test the wrap-around condition */
|
|
if (++priv->rx_idx >= PCH_GBE_DESC_NUM)
|
|
priv->rx_idx = 0;
|
|
rx_swp = priv->rx_idx;
|
|
if (++rx_swp >= PCH_GBE_DESC_NUM)
|
|
rx_swp = 0;
|
|
|
|
writel(dm_pci_virt_to_mem(priv->dev, rx_head + rx_swp),
|
|
&mac_regs->rx_dsc_sw_p);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pch_gbe_mdio_ready(struct pch_gbe_regs *mac_regs)
|
|
{
|
|
ulong start = get_timer(0);
|
|
|
|
while (get_timer(start) < PCH_GBE_TIMEOUT) {
|
|
if (readl(&mac_regs->miim) & PCH_GBE_MIIM_OPER_READY)
|
|
return 0;
|
|
|
|
udelay(10);
|
|
}
|
|
|
|
return -ETIME;
|
|
}
|
|
|
|
static int pch_gbe_mdio_read(struct mii_dev *bus, int addr, int devad, int reg)
|
|
{
|
|
struct pch_gbe_regs *mac_regs = bus->priv;
|
|
u32 miim;
|
|
|
|
if (pch_gbe_mdio_ready(mac_regs))
|
|
return -ETIME;
|
|
|
|
miim = (addr << PCH_GBE_MIIM_PHY_ADDR_SHIFT) |
|
|
(reg << PCH_GBE_MIIM_REG_ADDR_SHIFT) |
|
|
PCH_GBE_MIIM_OPER_READ;
|
|
writel(miim, &mac_regs->miim);
|
|
|
|
if (pch_gbe_mdio_ready(mac_regs))
|
|
return -ETIME;
|
|
|
|
return readl(&mac_regs->miim) & 0xffff;
|
|
}
|
|
|
|
static int pch_gbe_mdio_write(struct mii_dev *bus, int addr, int devad,
|
|
int reg, u16 val)
|
|
{
|
|
struct pch_gbe_regs *mac_regs = bus->priv;
|
|
u32 miim;
|
|
|
|
if (pch_gbe_mdio_ready(mac_regs))
|
|
return -ETIME;
|
|
|
|
miim = (addr << PCH_GBE_MIIM_PHY_ADDR_SHIFT) |
|
|
(reg << PCH_GBE_MIIM_REG_ADDR_SHIFT) |
|
|
PCH_GBE_MIIM_OPER_WRITE | val;
|
|
writel(miim, &mac_regs->miim);
|
|
|
|
if (pch_gbe_mdio_ready(mac_regs))
|
|
return -ETIME;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static int pch_gbe_mdio_init(const char *name, struct pch_gbe_regs *mac_regs)
|
|
{
|
|
struct mii_dev *bus;
|
|
|
|
bus = mdio_alloc();
|
|
if (!bus) {
|
|
debug("pch_gbe: failed to allocate MDIO bus\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
bus->read = pch_gbe_mdio_read;
|
|
bus->write = pch_gbe_mdio_write;
|
|
strcpy(bus->name, name);
|
|
|
|
bus->priv = (void *)mac_regs;
|
|
|
|
return mdio_register(bus);
|
|
}
|
|
|
|
static int pch_gbe_phy_init(struct udevice *dev)
|
|
{
|
|
struct pch_gbe_priv *priv = dev_get_priv(dev);
|
|
struct eth_pdata *plat = dev_get_platdata(dev);
|
|
struct phy_device *phydev;
|
|
int mask = 0xffffffff;
|
|
|
|
phydev = phy_find_by_mask(priv->bus, mask, plat->phy_interface);
|
|
if (!phydev) {
|
|
printf("pch_gbe: cannot find the phy\n");
|
|
return -1;
|
|
}
|
|
|
|
phy_connect_dev(phydev, dev);
|
|
|
|
phydev->supported &= PHY_GBIT_FEATURES;
|
|
phydev->advertising = phydev->supported;
|
|
|
|
priv->phydev = phydev;
|
|
phy_config(phydev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int pch_gbe_probe(struct udevice *dev)
|
|
{
|
|
struct pch_gbe_priv *priv;
|
|
struct eth_pdata *plat = dev_get_platdata(dev);
|
|
void *iobase;
|
|
int err;
|
|
|
|
/*
|
|
* The priv structure contains the descriptors and frame buffers which
|
|
* need a strict buswidth alignment (64 bytes). This is guaranteed by
|
|
* DM_FLAG_ALLOC_PRIV_DMA flag in the U_BOOT_DRIVER.
|
|
*/
|
|
priv = dev_get_priv(dev);
|
|
|
|
priv->dev = dev;
|
|
|
|
iobase = dm_pci_map_bar(dev, PCI_BASE_ADDRESS_1, PCI_REGION_MEM);
|
|
|
|
plat->iobase = (ulong)iobase;
|
|
priv->mac_regs = (struct pch_gbe_regs *)iobase;
|
|
|
|
/* Read MAC address from SROM and initialize dev->enetaddr with it */
|
|
pch_gbe_mac_read(priv->mac_regs, plat->enetaddr);
|
|
|
|
plat->phy_interface = PHY_INTERFACE_MODE_RGMII;
|
|
pch_gbe_mdio_init(dev->name, priv->mac_regs);
|
|
priv->bus = miiphy_get_dev_by_name(dev->name);
|
|
|
|
err = pch_gbe_reset(dev);
|
|
if (err)
|
|
return err;
|
|
|
|
return pch_gbe_phy_init(dev);
|
|
}
|
|
|
|
int pch_gbe_remove(struct udevice *dev)
|
|
{
|
|
struct pch_gbe_priv *priv = dev_get_priv(dev);
|
|
|
|
free(priv->phydev);
|
|
mdio_unregister(priv->bus);
|
|
mdio_free(priv->bus);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct eth_ops pch_gbe_ops = {
|
|
.start = pch_gbe_start,
|
|
.send = pch_gbe_send,
|
|
.recv = pch_gbe_recv,
|
|
.free_pkt = pch_gbe_free_pkt,
|
|
.stop = pch_gbe_stop,
|
|
};
|
|
|
|
static const struct udevice_id pch_gbe_ids[] = {
|
|
{ .compatible = "intel,pch-gbe" },
|
|
{ }
|
|
};
|
|
|
|
U_BOOT_DRIVER(eth_pch_gbe) = {
|
|
.name = "pch_gbe",
|
|
.id = UCLASS_ETH,
|
|
.of_match = pch_gbe_ids,
|
|
.probe = pch_gbe_probe,
|
|
.remove = pch_gbe_remove,
|
|
.ops = &pch_gbe_ops,
|
|
.priv_auto_alloc_size = sizeof(struct pch_gbe_priv),
|
|
.platdata_auto_alloc_size = sizeof(struct eth_pdata),
|
|
.flags = DM_FLAG_ALLOC_PRIV_DMA,
|
|
};
|
|
|
|
U_BOOT_PCI_DEVICE(eth_pch_gbe, supported);
|