// SPDX-License-Identifier: GPL-2.0-only
/*****************************************************************************
* *
* File : subr . c *
* $ Revision : 1 . 27 $ *
* $ Date : 2005 / 06 / 22 01 : 08 : 36 $ *
* Description : *
* Various subroutines ( intr , pio , etc . ) used by Chelsio 10 G Ethernet driver . *
* part of the Chelsio 10 Gb Ethernet Driver . *
* *
* *
* http : //www.chelsio.com *
* *
* Copyright ( c ) 2003 - 2005 Chelsio Communications , Inc . *
* All rights reserved . *
* *
* Maintainers : maintainers @ chelsio . com *
* *
* Authors : Dimitrios Michailidis < dm @ chelsio . com > *
* Tina Yang < tainay @ chelsio . com > *
* Felix Marti < felix @ chelsio . com > *
* Scott Bardone < sbardone @ chelsio . com > *
* Kurt Ottaway < kottaway @ chelsio . com > *
* Frank DiMambro < frank @ chelsio . com > *
* *
* History : *
* *
****************************************************************************/
#include "common.h"
#include "elmer0.h"
#include "regs.h"
#include "gmac.h"
#include "cphy.h"
#include "sge.h"
#include "tp.h"
#include "espi.h"
/**
* t1_wait_op_done - wait until an operation is completed
* @ adapter : the adapter performing the operation
* @ reg : the register to check for completion
* @ mask : a single - bit field within @ reg that indicates completion
* @ polarity : the value of the field when the operation is completed
* @ attempts : number of check iterations
* @ delay : delay in usecs between iterations
*
* Wait until an operation is completed by checking a bit in a register
* up to @ attempts times . Returns % 0 if the operation completes and % 1
* otherwise .
*/
static int t1_wait_op_done(adapter_t *adapter, int reg, u32 mask, int polarity,
int attempts, int delay)
{
while (1 ) {
u32 val = readl(adapter->regs + reg) & mask;
if (!!val == polarity)
return 0 ;
if (--attempts == 0 )
return 1 ;
if (delay)
udelay(delay);
}
}
#define TPI_ATTEMPTS 50
/*
* Write a register over the TPI interface ( unlocked and locked versions ) .
*/
int __t1_tpi_write(adapter_t *adapter, u32 addr, u32 value)
{
int tpi_busy;
writel(addr, adapter->regs + A_TPI_ADDR);
writel(value, adapter->regs + A_TPI_WR_DATA);
writel(F_TPIWR, adapter->regs + A_TPI_CSR);
tpi_busy = t1_wait_op_done(adapter, A_TPI_CSR, F_TPIRDY, 1 ,
TPI_ATTEMPTS, 3 );
if (tpi_busy)
pr_alert("%s: TPI write to 0x%x failed\n" ,
adapter->name, addr);
return tpi_busy;
}
int t1_tpi_write(adapter_t *adapter, u32 addr, u32 value)
{
int ret;
spin_lock(&adapter->tpi_lock);
ret = __t1_tpi_write(adapter, addr, value);
spin_unlock(&adapter->tpi_lock);
return ret;
}
/*
* Read a register over the TPI interface ( unlocked and locked versions ) .
*/
int __t1_tpi_read(adapter_t *adapter, u32 addr, u32 *valp)
{
int tpi_busy;
writel(addr, adapter->regs + A_TPI_ADDR);
writel(0 , adapter->regs + A_TPI_CSR);
tpi_busy = t1_wait_op_done(adapter, A_TPI_CSR, F_TPIRDY, 1 ,
TPI_ATTEMPTS, 3 );
if (tpi_busy)
pr_alert("%s: TPI read from 0x%x failed\n" ,
adapter->name, addr);
else
*valp = readl(adapter->regs + A_TPI_RD_DATA);
return tpi_busy;
}
int t1_tpi_read(adapter_t *adapter, u32 addr, u32 *valp)
{
int ret;
spin_lock(&adapter->tpi_lock);
ret = __t1_tpi_read(adapter, addr, valp);
spin_unlock(&adapter->tpi_lock);
return ret;
}
/*
* Set a TPI parameter .
*/
static void t1_tpi_par(adapter_t *adapter, u32 value)
{
writel(V_TPIPAR(value), adapter->regs + A_TPI_PAR);
}
/*
* Called when a port ' s link settings change to propagate the new values to the
* associated PHY and MAC . After performing the common tasks it invokes an
* OS - specific handler .
*/
void t1_link_changed(adapter_t *adapter, int port_id)
{
int link_ok, speed, duplex, fc;
struct cphy *phy = adapter->port[port_id].phy;
struct link_config *lc = &adapter->port[port_id].link_config;
phy->ops->get_link_status(phy, &link_ok, &speed, &duplex, &fc);
lc->speed = speed < 0 ? SPEED_INVALID : speed;
lc->duplex = duplex < 0 ? DUPLEX_INVALID : duplex;
if (!(lc->requested_fc & PAUSE_AUTONEG))
fc = lc->requested_fc & (PAUSE_RX | PAUSE_TX);
if (link_ok && speed >= 0 && lc->autoneg == AUTONEG_ENABLE) {
/* Set MAC speed, duplex, and flow control to match PHY. */
struct cmac *mac = adapter->port[port_id].mac;
mac->ops->set_speed_duplex_fc(mac, speed, duplex, fc);
lc->fc = (unsigned char )fc;
}
t1_link_negotiated(adapter, port_id, link_ok, speed, duplex, fc);
}
static bool t1_pci_intr_handler(adapter_t *adapter)
{
u32 pcix_cause;
pci_read_config_dword(adapter->pdev, A_PCICFG_INTR_CAUSE, &pcix_cause);
if (pcix_cause) {
pci_write_config_dword(adapter->pdev, A_PCICFG_INTR_CAUSE,
pcix_cause);
/* PCI errors are fatal */
t1_interrupts_disable(adapter);
adapter->pending_thread_intr |= F_PL_INTR_SGE_ERR;
pr_alert("%s: PCI error encountered.\n" , adapter->name);
return true ;
}
return false ;
}
#ifdef CONFIG_CHELSIO_T1_1G
#include "fpga_defs.h"
/*
* PHY interrupt handler for FPGA boards .
*/
static int fpga_phy_intr_handler(adapter_t *adapter)
{
int p;
u32 cause = readl(adapter->regs + FPGA_GMAC_ADDR_INTERRUPT_CAUSE);
for_each_port(adapter, p)
if (cause & (1 << p)) {
struct cphy *phy = adapter->port[p].phy;
int phy_cause = phy->ops->interrupt_handler(phy);
if (phy_cause & cphy_cause_link_change)
t1_link_changed(adapter, p);
}
writel(cause, adapter->regs + FPGA_GMAC_ADDR_INTERRUPT_CAUSE);
return 0 ;
}
/*
* Slow path interrupt handler for FPGAs .
*/
static irqreturn_t fpga_slow_intr(adapter_t *adapter)
{
u32 cause = readl(adapter->regs + A_PL_CAUSE);
irqreturn_t ret = IRQ_NONE;
cause &= ~F_PL_INTR_SGE_DATA;
if (cause & F_PL_INTR_SGE_ERR) {
if (t1_sge_intr_error_handler(adapter->sge))
ret = IRQ_WAKE_THREAD;
}
if (cause & FPGA_PCIX_INTERRUPT_GMAC)
fpga_phy_intr_handler(adapter);
if (cause & FPGA_PCIX_INTERRUPT_TP) {
/*
* FPGA doesn ' t support MC4 interrupts and it requires
* this odd layer of indirection for MC5 .
*/
u32 tp_cause = readl(adapter->regs + FPGA_TP_ADDR_INTERRUPT_CAUSE);
/* Clear TP interrupt */
writel(tp_cause, adapter->regs + FPGA_TP_ADDR_INTERRUPT_CAUSE);
}
if (cause & FPGA_PCIX_INTERRUPT_PCIX) {
if (t1_pci_intr_handler(adapter))
ret = IRQ_WAKE_THREAD;
}
/* Clear the interrupts just processed. */
if (cause)
writel(cause, adapter->regs + A_PL_CAUSE);
if (ret != IRQ_NONE)
return ret;
return cause == 0 ? IRQ_NONE : IRQ_HANDLED;
}
#endif
/*
* Wait until Elmer ' s MI1 interface is ready for new operations .
*/
static int mi1_wait_until_ready(adapter_t *adapter, int mi1_reg)
{
int attempts = 100 , busy;
do {
u32 val;
__t1_tpi_read(adapter, mi1_reg, &val);
busy = val & F_MI1_OP_BUSY;
if (busy)
udelay(10 );
} while (busy && --attempts);
if (busy)
pr_alert("%s: MDIO operation timed out\n" , adapter->name);
return busy;
}
/*
* MI1 MDIO initialization .
*/
static void mi1_mdio_init(adapter_t *adapter, const struct board_info *bi)
{
u32 clkdiv = bi->clock_elmer0 / (2 * bi->mdio_mdc) - 1 ;
u32 val = F_MI1_PREAMBLE_ENABLE | V_MI1_MDI_INVERT(bi->mdio_mdiinv) |
V_MI1_MDI_ENABLE(bi->mdio_mdien) | V_MI1_CLK_DIV(clkdiv);
if (!(bi->caps & SUPPORTED_10000baseT_Full))
val |= V_MI1_SOF(1 );
t1_tpi_write(adapter, A_ELMER0_PORT0_MI1_CFG, val);
}
#if defined (CONFIG_CHELSIO_T1_1G)
/*
* Elmer MI1 MDIO read / write operations .
*/
static int mi1_mdio_read(struct net_device *dev, int phy_addr, int mmd_addr,
u16 reg_addr)
{
struct adapter *adapter = dev->ml_priv;
u32 addr = V_MI1_REG_ADDR(reg_addr) | V_MI1_PHY_ADDR(phy_addr);
unsigned int val;
spin_lock(&adapter->tpi_lock);
__t1_tpi_write(adapter, A_ELMER0_PORT0_MI1_ADDR, addr);
__t1_tpi_write(adapter,
A_ELMER0_PORT0_MI1_OP, MI1_OP_DIRECT_READ);
mi1_wait_until_ready(adapter, A_ELMER0_PORT0_MI1_OP);
__t1_tpi_read(adapter, A_ELMER0_PORT0_MI1_DATA, &val);
spin_unlock(&adapter->tpi_lock);
return val;
}
static int mi1_mdio_write(struct net_device *dev, int phy_addr, int mmd_addr,
u16 reg_addr, u16 val)
{
struct adapter *adapter = dev->ml_priv;
u32 addr = V_MI1_REG_ADDR(reg_addr) | V_MI1_PHY_ADDR(phy_addr);
spin_lock(&adapter->tpi_lock);
__t1_tpi_write(adapter, A_ELMER0_PORT0_MI1_ADDR, addr);
__t1_tpi_write(adapter, A_ELMER0_PORT0_MI1_DATA, val);
__t1_tpi_write(adapter,
A_ELMER0_PORT0_MI1_OP, MI1_OP_DIRECT_WRITE);
mi1_wait_until_ready(adapter, A_ELMER0_PORT0_MI1_OP);
spin_unlock(&adapter->tpi_lock);
return 0 ;
}
static const struct mdio_ops mi1_mdio_ops = {
.init = mi1_mdio_init,
.read = mi1_mdio_read,
.write = mi1_mdio_write,
.mode_support = MDIO_SUPPORTS_C22
};
#endif
static int mi1_mdio_ext_read(struct net_device *dev, int phy_addr, int mmd_addr,
u16 reg_addr)
{
struct adapter *adapter = dev->ml_priv;
u32 addr = V_MI1_REG_ADDR(mmd_addr) | V_MI1_PHY_ADDR(phy_addr);
unsigned int val;
spin_lock(&adapter->tpi_lock);
/* Write the address we want. */
__t1_tpi_write(adapter, A_ELMER0_PORT0_MI1_ADDR, addr);
__t1_tpi_write(adapter, A_ELMER0_PORT0_MI1_DATA, reg_addr);
__t1_tpi_write(adapter, A_ELMER0_PORT0_MI1_OP,
MI1_OP_INDIRECT_ADDRESS);
mi1_wait_until_ready(adapter, A_ELMER0_PORT0_MI1_OP);
/* Write the operation we want. */
__t1_tpi_write(adapter,
A_ELMER0_PORT0_MI1_OP, MI1_OP_INDIRECT_READ);
mi1_wait_until_ready(adapter, A_ELMER0_PORT0_MI1_OP);
/* Read the data. */
__t1_tpi_read(adapter, A_ELMER0_PORT0_MI1_DATA, &val);
spin_unlock(&adapter->tpi_lock);
return val;
}
static int mi1_mdio_ext_write(struct net_device *dev, int phy_addr,
int mmd_addr, u16 reg_addr, u16 val)
{
struct adapter *adapter = dev->ml_priv;
u32 addr = V_MI1_REG_ADDR(mmd_addr) | V_MI1_PHY_ADDR(phy_addr);
spin_lock(&adapter->tpi_lock);
/* Write the address we want. */
__t1_tpi_write(adapter, A_ELMER0_PORT0_MI1_ADDR, addr);
__t1_tpi_write(adapter, A_ELMER0_PORT0_MI1_DATA, reg_addr);
__t1_tpi_write(adapter, A_ELMER0_PORT0_MI1_OP,
MI1_OP_INDIRECT_ADDRESS);
mi1_wait_until_ready(adapter, A_ELMER0_PORT0_MI1_OP);
/* Write the data. */
__t1_tpi_write(adapter, A_ELMER0_PORT0_MI1_DATA, val);
__t1_tpi_write(adapter, A_ELMER0_PORT0_MI1_OP, MI1_OP_INDIRECT_WRITE);
mi1_wait_until_ready(adapter, A_ELMER0_PORT0_MI1_OP);
spin_unlock(&adapter->tpi_lock);
return 0 ;
}
static const struct mdio_ops mi1_mdio_ext_ops = {
.init = mi1_mdio_init,
.read = mi1_mdio_ext_read,
.write = mi1_mdio_ext_write,
.mode_support = MDIO_SUPPORTS_C45 | MDIO_EMULATE_C22
};
enum {
CH_BRD_T110_1CU,
CH_BRD_N110_1F,
CH_BRD_N210_1F,
CH_BRD_T210_1F,
CH_BRD_T210_1CU,
CH_BRD_N204_4CU,
};
static const struct board_info t1_board[] = {
{
.board = CHBT_BOARD_CHT110,
.port_number = 1 ,
.caps = SUPPORTED_10000baseT_Full,
.chip_term = CHBT_TERM_T1,
.chip_mac = CHBT_MAC_PM3393,
.chip_phy = CHBT_PHY_MY3126,
.clock_core = 125000000 ,
.clock_mc3 = 150000000 ,
.clock_mc4 = 125000000 ,
.espi_nports = 1 ,
.clock_elmer0 = 44 ,
.mdio_mdien = 1 ,
.mdio_mdiinv = 1 ,
.mdio_mdc = 1 ,
.mdio_phybaseaddr = 1 ,
.gmac = &t1_pm3393_ops,
.gphy = &t1_my3126_ops,
.mdio_ops = &mi1_mdio_ext_ops,
.desc = "Chelsio T110 1x10GBase-CX4 TOE" ,
},
{
.board = CHBT_BOARD_N110,
.port_number = 1 ,
.caps = SUPPORTED_10000baseT_Full | SUPPORTED_FIBRE,
.chip_term = CHBT_TERM_T1,
.chip_mac = CHBT_MAC_PM3393,
.chip_phy = CHBT_PHY_88X2010,
.clock_core = 125000000 ,
.espi_nports = 1 ,
.clock_elmer0 = 44 ,
.mdio_mdien = 0 ,
.mdio_mdiinv = 0 ,
.mdio_mdc = 1 ,
.mdio_phybaseaddr = 0 ,
.gmac = &t1_pm3393_ops,
.gphy = &t1_mv88x201x_ops,
.mdio_ops = &mi1_mdio_ext_ops,
.desc = "Chelsio N110 1x10GBaseX NIC" ,
},
{
.board = CHBT_BOARD_N210,
.port_number = 1 ,
.caps = SUPPORTED_10000baseT_Full | SUPPORTED_FIBRE,
.chip_term = CHBT_TERM_T2,
.chip_mac = CHBT_MAC_PM3393,
.chip_phy = CHBT_PHY_88X2010,
.clock_core = 125000000 ,
.espi_nports = 1 ,
.clock_elmer0 = 44 ,
.mdio_mdien = 0 ,
.mdio_mdiinv = 0 ,
.mdio_mdc = 1 ,
.mdio_phybaseaddr = 0 ,
.gmac = &t1_pm3393_ops,
.gphy = &t1_mv88x201x_ops,
.mdio_ops = &mi1_mdio_ext_ops,
.desc = "Chelsio N210 1x10GBaseX NIC" ,
},
{
.board = CHBT_BOARD_CHT210,
.port_number = 1 ,
.caps = SUPPORTED_10000baseT_Full,
.chip_term = CHBT_TERM_T2,
.chip_mac = CHBT_MAC_PM3393,
.chip_phy = CHBT_PHY_88X2010,
.clock_core = 125000000 ,
.clock_mc3 = 133000000 ,
.clock_mc4 = 125000000 ,
.espi_nports = 1 ,
.clock_elmer0 = 44 ,
.mdio_mdien = 0 ,
.mdio_mdiinv = 0 ,
.mdio_mdc = 1 ,
.mdio_phybaseaddr = 0 ,
.gmac = &t1_pm3393_ops,
.gphy = &t1_mv88x201x_ops,
.mdio_ops = &mi1_mdio_ext_ops,
.desc = "Chelsio T210 1x10GBaseX TOE" ,
},
{
.board = CHBT_BOARD_CHT210,
.port_number = 1 ,
.caps = SUPPORTED_10000baseT_Full,
.chip_term = CHBT_TERM_T2,
.chip_mac = CHBT_MAC_PM3393,
.chip_phy = CHBT_PHY_MY3126,
.clock_core = 125000000 ,
.clock_mc3 = 133000000 ,
.clock_mc4 = 125000000 ,
.espi_nports = 1 ,
.clock_elmer0 = 44 ,
.mdio_mdien = 1 ,
.mdio_mdiinv = 1 ,
.mdio_mdc = 1 ,
.mdio_phybaseaddr = 1 ,
.gmac = &t1_pm3393_ops,
.gphy = &t1_my3126_ops,
.mdio_ops = &mi1_mdio_ext_ops,
.desc = "Chelsio T210 1x10GBase-CX4 TOE" ,
},
#ifdef CONFIG_CHELSIO_T1_1G
{
.board = CHBT_BOARD_CHN204,
.port_number = 4 ,
.caps = SUPPORTED_10baseT_Half | SUPPORTED_10baseT_Full
| SUPPORTED_100baseT_Half | SUPPORTED_100baseT_Full
| SUPPORTED_1000baseT_Full | SUPPORTED_Autoneg |
SUPPORTED_PAUSE | SUPPORTED_TP,
.chip_term = CHBT_TERM_T2,
.chip_mac = CHBT_MAC_VSC7321,
.chip_phy = CHBT_PHY_88E1111,
.clock_core = 100000000 ,
.espi_nports = 4 ,
.clock_elmer0 = 44 ,
.mdio_mdien = 0 ,
.mdio_mdiinv = 0 ,
.mdio_mdc = 0 ,
.mdio_phybaseaddr = 4 ,
.gmac = &t1_vsc7326_ops,
.gphy = &t1_mv88e1xxx_ops,
.mdio_ops = &mi1_mdio_ops,
.desc = "Chelsio N204 4x100/1000BaseT NIC" ,
},
#endif
};
const struct pci_device_id t1_pci_tbl[] = {
CH_DEVICE(8 , 0 , CH_BRD_T110_1CU),
CH_DEVICE(8 , 1 , CH_BRD_T110_1CU),
CH_DEVICE(7 , 0 , CH_BRD_N110_1F),
CH_DEVICE(10 , 1 , CH_BRD_N210_1F),
CH_DEVICE(11 , 1 , CH_BRD_T210_1F),
CH_DEVICE(14 , 1 , CH_BRD_T210_1CU),
CH_DEVICE(16 , 1 , CH_BRD_N204_4CU),
{ 0 }
};
MODULE_DEVICE_TABLE(pci, t1_pci_tbl);
/*
* Return the board_info structure with a given index . Out - of - range indices
* return NULL .
*/
const struct board_info *t1_get_board_info(unsigned int board_id)
{
return board_id < ARRAY_SIZE(t1_board) ? &t1_board[board_id] : NULL;
}
struct chelsio_vpd_t {
u32 format_version;
u8 serial_number[16 ];
u8 mac_base_address[6 ];
u8 pad[2 ]; /* make multiple-of-4 size requirement explicit */
};
#define EEPROMSIZE (8 * 1024 )
#define EEPROM_MAX_POLL 4
/*
* Read SEEPROM . A zero is written to the flag register when the address is
* written to the Control register . The hardware device will set the flag to a
* one when 4 B have been transferred to the Data register .
*/
int t1_seeprom_read(adapter_t *adapter, u32 addr, __le32 *data)
{
int i = EEPROM_MAX_POLL;
u16 val;
u32 v;
if (addr >= EEPROMSIZE || (addr & 3 ))
return -EINVAL;
pci_write_config_word(adapter->pdev, A_PCICFG_VPD_ADDR, (u16)addr);
do {
udelay(50 );
pci_read_config_word(adapter->pdev, A_PCICFG_VPD_ADDR, &val);
} while (!(val & F_VPD_OP_FLAG) && --i);
if (!(val & F_VPD_OP_FLAG)) {
pr_err("%s: reading EEPROM address 0x%x failed\n" ,
adapter->name, addr);
return -EIO;
}
pci_read_config_dword(adapter->pdev, A_PCICFG_VPD_DATA, &v);
*data = cpu_to_le32(v);
return 0 ;
}
static int t1_eeprom_vpd_get(adapter_t *adapter, struct chelsio_vpd_t *vpd)
{
int addr, ret = 0 ;
for (addr = 0 ; !ret && addr < sizeof (*vpd); addr += sizeof (u32))
ret = t1_seeprom_read(adapter, addr,
(__le32 *)((u8 *)vpd + addr));
return ret;
}
/*
* Read a port ' s MAC address from the VPD ROM .
*/
static int vpd_macaddress_get(adapter_t *adapter, int index, u8 mac_addr[])
{
struct chelsio_vpd_t vpd;
if (t1_eeprom_vpd_get(adapter, &vpd))
return 1 ;
memcpy(mac_addr, vpd.mac_base_address, 5 );
mac_addr[5 ] = vpd.mac_base_address[5 ] + index;
return 0 ;
}
/*
* Set up the MAC / PHY according to the requested link settings .
*
* If the PHY can auto - negotiate first decide what to advertise , then
* enable / disable auto - negotiation as desired and reset .
*
* If the PHY does not auto - negotiate we just reset it .
*
* If auto - negotiation is off set the MAC to the proper speed / duplex / FC ,
* otherwise do it later based on the outcome of auto - negotiation .
*/
int t1_link_start(struct cphy *phy, struct cmac *mac, struct link_config *lc)
{
unsigned int fc = lc->requested_fc & (PAUSE_RX | PAUSE_TX);
if (lc->supported & SUPPORTED_Autoneg) {
lc->advertising &= ~(ADVERTISED_ASYM_PAUSE | ADVERTISED_PAUSE);
if (fc) {
if (fc == ((PAUSE_RX | PAUSE_TX) &
(mac->adapter->params.nports < 2 )))
lc->advertising |= ADVERTISED_PAUSE;
else {
lc->advertising |= ADVERTISED_ASYM_PAUSE;
if (fc == PAUSE_RX)
lc->advertising |= ADVERTISED_PAUSE;
}
}
phy->ops->advertise(phy, lc->advertising);
if (lc->autoneg == AUTONEG_DISABLE) {
lc->speed = lc->requested_speed;
lc->duplex = lc->requested_duplex;
lc->fc = (unsigned char )fc;
mac->ops->set_speed_duplex_fc(mac, lc->speed,
lc->duplex, fc);
/* Also disables autoneg */
phy->state = PHY_AUTONEG_RDY;
phy->ops->set_speed_duplex(phy, lc->speed, lc->duplex);
phy->ops->reset(phy, 0 );
} else {
phy->state = PHY_AUTONEG_EN;
phy->ops->autoneg_enable(phy); /* also resets PHY */
}
} else {
phy->state = PHY_AUTONEG_RDY;
mac->ops->set_speed_duplex_fc(mac, -1 , -1 , fc);
lc->fc = (unsigned char )fc;
phy->ops->reset(phy, 0 );
}
return 0 ;
}
/*
* External interrupt handler for boards using elmer0 .
*/
int t1_elmer0_ext_intr_handler(adapter_t *adapter)
{
struct cphy *phy;
int phy_cause;
u32 cause;
t1_tpi_read(adapter, A_ELMER0_INT_CAUSE, &cause);
switch (board_info(adapter)->board) {
#ifdef CONFIG_CHELSIO_T1_1G
case CHBT_BOARD_CHT204:
case CHBT_BOARD_CHT204E:
case CHBT_BOARD_CHN204:
case CHBT_BOARD_CHT204V: {
int i, port_bit;
for_each_port(adapter, i) {
port_bit = i + 1 ;
if (!(cause & (1 << port_bit)))
continue ;
phy = adapter->port[i].phy;
phy_cause = phy->ops->interrupt_handler(phy);
if (phy_cause & cphy_cause_link_change)
t1_link_changed(adapter, i);
}
break ;
}
case CHBT_BOARD_CHT101:
if (cause & ELMER0_GP_BIT1) { /* Marvell 88E1111 interrupt */
phy = adapter->port[0 ].phy;
phy_cause = phy->ops->interrupt_handler(phy);
if (phy_cause & cphy_cause_link_change)
t1_link_changed(adapter, 0 );
}
break ;
case CHBT_BOARD_7500: {
int p;
/*
* Elmer0 ' s interrupt cause isn ' t useful here because there is
* only one bit that can be set for all 4 ports . This means
* we are forced to check every PHY ' s interrupt status
* register to see who initiated the interrupt .
*/
for_each_port(adapter, p) {
phy = adapter->port[p].phy;
phy_cause = phy->ops->interrupt_handler(phy);
if (phy_cause & cphy_cause_link_change)
t1_link_changed(adapter, p);
}
break ;
}
#endif
case CHBT_BOARD_CHT210:
case CHBT_BOARD_N210:
case CHBT_BOARD_N110:
if (cause & ELMER0_GP_BIT6) { /* Marvell 88x2010 interrupt */
phy = adapter->port[0 ].phy;
phy_cause = phy->ops->interrupt_handler(phy);
if (phy_cause & cphy_cause_link_change)
t1_link_changed(adapter, 0 );
}
break ;
case CHBT_BOARD_8000:
case CHBT_BOARD_CHT110:
if (netif_msg_intr(adapter))
dev_dbg(&adapter->pdev->dev,
"External interrupt cause 0x%x\n" , cause);
if (cause & ELMER0_GP_BIT1) { /* PMC3393 INTB */
struct cmac *mac = adapter->port[0 ].mac;
mac->ops->interrupt_handler(mac);
}
if (cause & ELMER0_GP_BIT5) { /* XPAK MOD_DETECT */
u32 mod_detect;
t1_tpi_read(adapter,
A_ELMER0_GPI_STAT, &mod_detect);
if (netif_msg_link(adapter))
dev_info(&adapter->pdev->dev, "XPAK %s\n" ,
mod_detect ? "removed" : "inserted" );
}
break ;
}
t1_tpi_write(adapter, A_ELMER0_INT_CAUSE, cause);
return 0 ;
}
/* Enables all interrupts. */
void t1_interrupts_enable(adapter_t *adapter)
{
unsigned int i;
adapter->slow_intr_mask = F_PL_INTR_SGE_ERR | F_PL_INTR_TP;
t1_sge_intr_enable(adapter->sge);
t1_tp_intr_enable(adapter->tp);
if (adapter->espi) {
adapter->slow_intr_mask |= F_PL_INTR_ESPI;
t1_espi_intr_enable(adapter->espi);
}
/* Enable MAC/PHY interrupts for each port. */
for_each_port(adapter, i) {
adapter->port[i].mac->ops->interrupt_enable(adapter->port[i].mac);
adapter->port[i].phy->ops->interrupt_enable(adapter->port[i].phy);
}
/* Enable PCIX & external chip interrupts on ASIC boards. */
if (t1_is_asic(adapter)) {
u32 pl_intr = readl(adapter->regs + A_PL_ENABLE);
/* PCI-X interrupts */
pci_write_config_dword(adapter->pdev, A_PCICFG_INTR_ENABLE,
0 xffffffff);
adapter->slow_intr_mask |= F_PL_INTR_EXT | F_PL_INTR_PCIX;
pl_intr |= F_PL_INTR_EXT | F_PL_INTR_PCIX;
writel(pl_intr, adapter->regs + A_PL_ENABLE);
}
}
/* Disables all interrupts. */
void t1_interrupts_disable(adapter_t* adapter)
{
unsigned int i;
t1_sge_intr_disable(adapter->sge);
t1_tp_intr_disable(adapter->tp);
if (adapter->espi)
t1_espi_intr_disable(adapter->espi);
/* Disable MAC/PHY interrupts for each port. */
for_each_port(adapter, i) {
adapter->port[i].mac->ops->interrupt_disable(adapter->port[i].mac);
adapter->port[i].phy->ops->interrupt_disable(adapter->port[i].phy);
}
/* Disable PCIX & external chip interrupts. */
if (t1_is_asic(adapter))
writel(0 , adapter->regs + A_PL_ENABLE);
/* PCI-X interrupts */
pci_write_config_dword(adapter->pdev, A_PCICFG_INTR_ENABLE, 0 );
adapter->slow_intr_mask = 0 ;
}
/* Clears all interrupts */
void t1_interrupts_clear(adapter_t* adapter)
{
unsigned int i;
t1_sge_intr_clear(adapter->sge);
t1_tp_intr_clear(adapter->tp);
if (adapter->espi)
t1_espi_intr_clear(adapter->espi);
/* Clear MAC/PHY interrupts for each port. */
for_each_port(adapter, i) {
adapter->port[i].mac->ops->interrupt_clear(adapter->port[i].mac);
adapter->port[i].phy->ops->interrupt_clear(adapter->port[i].phy);
}
/* Enable interrupts for external devices. */
if (t1_is_asic(adapter)) {
u32 pl_intr = readl(adapter->regs + A_PL_CAUSE);
writel(pl_intr | F_PL_INTR_EXT | F_PL_INTR_PCIX,
adapter->regs + A_PL_CAUSE);
}
/* PCI-X interrupts */
pci_write_config_dword(adapter->pdev, A_PCICFG_INTR_CAUSE, 0 xffffffff);
}
/*
* Slow path interrupt handler for ASICs .
*/
static irqreturn_t asic_slow_intr(adapter_t *adapter)
{
u32 cause = readl(adapter->regs + A_PL_CAUSE);
irqreturn_t ret = IRQ_HANDLED;
cause &= adapter->slow_intr_mask;
if (!cause)
return IRQ_NONE;
if (cause & F_PL_INTR_SGE_ERR) {
if (t1_sge_intr_error_handler(adapter->sge))
ret = IRQ_WAKE_THREAD;
}
if (cause & F_PL_INTR_TP)
t1_tp_intr_handler(adapter->tp);
if (cause & F_PL_INTR_ESPI)
t1_espi_intr_handler(adapter->espi);
if (cause & F_PL_INTR_PCIX) {
if (t1_pci_intr_handler(adapter))
ret = IRQ_WAKE_THREAD;
}
if (cause & F_PL_INTR_EXT) {
/* Wake the threaded interrupt to handle external interrupts as
* we require a process context . We disable EXT interrupts in
* the interim and let the thread reenable them when it ' s done .
*/
adapter->pending_thread_intr |= F_PL_INTR_EXT;
adapter->slow_intr_mask &= ~F_PL_INTR_EXT;
writel(adapter->slow_intr_mask | F_PL_INTR_SGE_DATA,
adapter->regs + A_PL_ENABLE);
ret = IRQ_WAKE_THREAD;
}
/* Clear the interrupts just processed. */
writel(cause, adapter->regs + A_PL_CAUSE);
readl(adapter->regs + A_PL_CAUSE); /* flush writes */
return ret;
}
irqreturn_t t1_slow_intr_handler(adapter_t *adapter)
{
#ifdef CONFIG_CHELSIO_T1_1G
if (!t1_is_asic(adapter))
return fpga_slow_intr(adapter);
#endif
return asic_slow_intr(adapter);
}
/* Power sequencing is a work-around for Intel's XPAKs. */
static void power_sequence_xpak(adapter_t* adapter)
{
u32 mod_detect;
u32 gpo;
/* Check for XPAK */
t1_tpi_read(adapter, A_ELMER0_GPI_STAT, &mod_detect);
if (!(ELMER0_GP_BIT5 & mod_detect)) {
/* XPAK is present */
t1_tpi_read(adapter, A_ELMER0_GPO, &gpo);
gpo |= ELMER0_GP_BIT18;
t1_tpi_write(adapter, A_ELMER0_GPO, gpo);
}
}
int t1_get_board_rev(adapter_t *adapter, const struct board_info *bi,
struct adapter_params *p)
{
p->chip_version = bi->chip_term;
p->is_asic = (p->chip_version != CHBT_TERM_FPGA);
if (p->chip_version == CHBT_TERM_T1 ||
p->chip_version == CHBT_TERM_T2 ||
p->chip_version == CHBT_TERM_FPGA) {
u32 val = readl(adapter->regs + A_TP_PC_CONFIG);
val = G_TP_PC_REV(val);
if (val == 2 )
p->chip_revision = TERM_T1B;
else if (val == 3 )
p->chip_revision = TERM_T2;
else
return -1 ;
} else
return -1 ;
return 0 ;
}
/*
* Enable board components other than the Chelsio chip , such as external MAC
* and PHY .
*/
static int board_init(adapter_t *adapter, const struct board_info *bi)
{
switch (bi->board) {
case CHBT_BOARD_8000:
case CHBT_BOARD_N110:
case CHBT_BOARD_N210:
case CHBT_BOARD_CHT210:
t1_tpi_par(adapter, 0 xf);
t1_tpi_write(adapter, A_ELMER0_GPO, 0 x800);
break ;
case CHBT_BOARD_CHT110:
t1_tpi_par(adapter, 0 xf);
t1_tpi_write(adapter, A_ELMER0_GPO, 0 x1800);
/* TBD XXX Might not need. This fixes a problem
* described in the Intel SR XPAK errata .
*/
power_sequence_xpak(adapter);
break ;
#ifdef CONFIG_CHELSIO_T1_1G
case CHBT_BOARD_CHT204E:
/* add config space write here */
case CHBT_BOARD_CHT204:
case CHBT_BOARD_CHT204V:
case CHBT_BOARD_CHN204:
t1_tpi_par(adapter, 0 xf);
t1_tpi_write(adapter, A_ELMER0_GPO, 0 x804);
break ;
case CHBT_BOARD_CHT101:
case CHBT_BOARD_7500:
t1_tpi_par(adapter, 0 xf);
t1_tpi_write(adapter, A_ELMER0_GPO, 0 x1804);
break ;
#endif
}
return 0 ;
}
/*
* Initialize and configure the Terminator HW modules . Note that external
* MAC and PHYs are initialized separately .
*/
int t1_init_hw_modules(adapter_t *adapter)
{
int err = -EIO;
const struct board_info *bi = board_info(adapter);
if (!bi->clock_mc4) {
u32 val = readl(adapter->regs + A_MC4_CFG);
writel(val | F_READY | F_MC4_SLOW, adapter->regs + A_MC4_CFG);
writel(F_M_BUS_ENABLE | F_TCAM_RESET,
adapter->regs + A_MC5_CONFIG);
}
if (adapter->espi && t1_espi_init(adapter->espi, bi->chip_mac,
bi->espi_nports))
goto out_err;
if (t1_tp_reset(adapter->tp, &adapter->params.tp, bi->clock_core))
goto out_err;
err = t1_sge_configure(adapter->sge, &adapter->params.sge);
if (err)
goto out_err;
err = 0 ;
out_err:
return err;
}
/*
* Determine a card ' s PCI mode .
*/
static void get_pci_mode(adapter_t *adapter, struct chelsio_pci_params *p)
{
static const unsigned short speed_map[] = { 33 , 66 , 100 , 133 };
u32 pci_mode;
pci_read_config_dword(adapter->pdev, A_PCICFG_MODE, &pci_mode);
p->speed = speed_map[G_PCI_MODE_CLK(pci_mode)];
p->width = (pci_mode & F_PCI_MODE_64BIT) ? 64 : 32 ;
p->is_pcix = (pci_mode & F_PCI_MODE_PCIX) != 0 ;
}
/*
* Release the structures holding the SW per - Terminator - HW - module state .
*/
void t1_free_sw_modules(adapter_t *adapter)
{
unsigned int i;
for_each_port(adapter, i) {
struct cmac *mac = adapter->port[i].mac;
struct cphy *phy = adapter->port[i].phy;
if (mac)
mac->ops->destroy(mac);
if (phy)
phy->ops->destroy(phy);
}
if (adapter->sge)
t1_sge_destroy(adapter->sge);
if (adapter->tp)
t1_tp_destroy(adapter->tp);
if (adapter->espi)
t1_espi_destroy(adapter->espi);
}
static void init_link_config(struct link_config *lc,
const struct board_info *bi)
{
lc->supported = bi->caps;
lc->requested_speed = lc->speed = SPEED_INVALID;
lc->requested_duplex = lc->duplex = DUPLEX_INVALID;
lc->requested_fc = lc->fc = PAUSE_RX | PAUSE_TX;
if (lc->supported & SUPPORTED_Autoneg) {
lc->advertising = lc->supported;
lc->autoneg = AUTONEG_ENABLE;
lc->requested_fc |= PAUSE_AUTONEG;
} else {
lc->advertising = 0 ;
lc->autoneg = AUTONEG_DISABLE;
}
}
/*
* Allocate and initialize the data structures that hold the SW state of
* the Terminator HW modules .
*/
int t1_init_sw_modules(adapter_t *adapter, const struct board_info *bi)
{
unsigned int i;
adapter->params.brd_info = bi;
adapter->params.nports = bi->port_number;
adapter->params.stats_update_period = bi->gmac->stats_update_period;
adapter->sge = t1_sge_create(adapter, &adapter->params.sge);
if (!adapter->sge) {
pr_err("%s: SGE initialization failed\n" ,
adapter->name);
goto error;
}
if (bi->espi_nports && !(adapter->espi = t1_espi_create(adapter))) {
pr_err("%s: ESPI initialization failed\n" ,
adapter->name);
goto error;
}
adapter->tp = t1_tp_create(adapter, &adapter->params.tp);
if (!adapter->tp) {
pr_err("%s: TP initialization failed\n" ,
adapter->name);
goto error;
}
board_init(adapter, bi);
bi->mdio_ops->init(adapter, bi);
if (bi->gphy->reset)
bi->gphy->reset(adapter);
if (bi->gmac->reset)
bi->gmac->reset(adapter);
for_each_port(adapter, i) {
u8 hw_addr[6 ];
struct cmac *mac;
int phy_addr = bi->mdio_phybaseaddr + i;
adapter->port[i].phy = bi->gphy->create(adapter->port[i].dev,
phy_addr, bi->mdio_ops);
if (!adapter->port[i].phy) {
pr_err("%s: PHY %d initialization failed\n" ,
adapter->name, i);
goto error;
}
adapter->port[i].mac = mac = bi->gmac->create(adapter, i);
if (!mac) {
pr_err("%s: MAC %d initialization failed\n" ,
adapter->name, i);
goto error;
}
/*
* Get the port ' s MAC addresses either from the EEPROM if one
* exists or the one hardcoded in the MAC .
*/
if (!t1_is_asic(adapter) || bi->chip_mac == CHBT_MAC_DUMMY)
mac->ops->macaddress_get(mac, hw_addr);
else if (vpd_macaddress_get(adapter, i, hw_addr)) {
pr_err("%s: could not read MAC address from VPD ROM\n" ,
adapter->port[i].dev->name);
goto error;
}
eth_hw_addr_set(adapter->port[i].dev, hw_addr);
init_link_config(&adapter->port[i].link_config, bi);
}
get_pci_mode(adapter, &adapter->params.pci);
t1_interrupts_clear(adapter);
return 0 ;
error:
t1_free_sw_modules(adapter);
return -1 ;
}
Messung V0.5 in Prozent C=95 H=92 G=93
¤ Dauer der Verarbeitung: 0.19 Sekunden
(vorverarbeitet am 2026-10-02)
¤
*© Formatika GbR, Deutschland