| // SPDX-License-Identifier: GPL-2.0-or-later | 
 | /* | 
 |  * Copyright(c) 2007 Atheros Corporation. All rights reserved. | 
 |  * | 
 |  * Derived from Intel e1000 driver | 
 |  * Copyright(c) 1999 - 2005 Intel Corporation. All rights reserved. | 
 |  */ | 
 | #include <linux/pci.h> | 
 | #include <linux/delay.h> | 
 | #include <linux/mii.h> | 
 | #include <linux/crc32.h> | 
 |  | 
 | #include "atl1c.h" | 
 |  | 
 | /* | 
 |  * check_eeprom_exist | 
 |  * return 1 if eeprom exist | 
 |  */ | 
 | int atl1c_check_eeprom_exist(struct atl1c_hw *hw) | 
 | { | 
 | 	u32 data; | 
 |  | 
 | 	AT_READ_REG(hw, REG_TWSI_DEBUG, &data); | 
 | 	if (data & TWSI_DEBUG_DEV_EXIST) | 
 | 		return 1; | 
 |  | 
 | 	AT_READ_REG(hw, REG_MASTER_CTRL, &data); | 
 | 	if (data & MASTER_CTRL_OTP_SEL) | 
 | 		return 1; | 
 | 	return 0; | 
 | } | 
 |  | 
 | void atl1c_hw_set_mac_addr(struct atl1c_hw *hw, u8 *mac_addr) | 
 | { | 
 | 	u32 value; | 
 | 	/* | 
 | 	 * 00-0B-6A-F6-00-DC | 
 | 	 * 0:  6AF600DC 1: 000B | 
 | 	 * low dword | 
 | 	 */ | 
 | 	value = mac_addr[2] << 24 | | 
 | 		mac_addr[3] << 16 | | 
 | 		mac_addr[4] << 8  | | 
 | 		mac_addr[5]; | 
 | 	AT_WRITE_REG_ARRAY(hw, REG_MAC_STA_ADDR, 0, value); | 
 | 	/* hight dword */ | 
 | 	value = mac_addr[0] << 8 | | 
 | 		mac_addr[1]; | 
 | 	AT_WRITE_REG_ARRAY(hw, REG_MAC_STA_ADDR, 1, value); | 
 | } | 
 |  | 
 | /* read mac address from hardware register */ | 
 | static bool atl1c_read_current_addr(struct atl1c_hw *hw, u8 *eth_addr) | 
 | { | 
 | 	u32 addr[2]; | 
 |  | 
 | 	AT_READ_REG(hw, REG_MAC_STA_ADDR, &addr[0]); | 
 | 	AT_READ_REG(hw, REG_MAC_STA_ADDR + 4, &addr[1]); | 
 |  | 
 | 	*(u32 *) ð_addr[2] = htonl(addr[0]); | 
 | 	*(u16 *) ð_addr[0] = htons((u16)addr[1]); | 
 |  | 
 | 	return is_valid_ether_addr(eth_addr); | 
 | } | 
 |  | 
 | /* | 
 |  * atl1c_get_permanent_address | 
 |  * return 0 if get valid mac address, | 
 |  */ | 
 | static int atl1c_get_permanent_address(struct atl1c_hw *hw) | 
 | { | 
 | 	u32 i; | 
 | 	u32 otp_ctrl_data; | 
 | 	u32 twsi_ctrl_data; | 
 | 	u16 phy_data; | 
 | 	bool raise_vol = false; | 
 |  | 
 | 	/* MAC-address from BIOS is the 1st priority */ | 
 | 	if (atl1c_read_current_addr(hw, hw->perm_mac_addr)) | 
 | 		return 0; | 
 |  | 
 | 	/* init */ | 
 | 	AT_READ_REG(hw, REG_OTP_CTRL, &otp_ctrl_data); | 
 | 	if (atl1c_check_eeprom_exist(hw)) { | 
 | 		if (hw->nic_type == athr_l1c || hw->nic_type == athr_l2c) { | 
 | 			/* Enable OTP CLK */ | 
 | 			if (!(otp_ctrl_data & OTP_CTRL_CLK_EN)) { | 
 | 				otp_ctrl_data |= OTP_CTRL_CLK_EN; | 
 | 				AT_WRITE_REG(hw, REG_OTP_CTRL, otp_ctrl_data); | 
 | 				AT_WRITE_FLUSH(hw); | 
 | 				msleep(1); | 
 | 			} | 
 | 		} | 
 | 		/* raise voltage temporally for l2cb */ | 
 | 		if (hw->nic_type == athr_l2c_b || hw->nic_type == athr_l2c_b2) { | 
 | 			atl1c_read_phy_dbg(hw, MIIDBG_ANACTRL, &phy_data); | 
 | 			phy_data &= ~ANACTRL_HB_EN; | 
 | 			atl1c_write_phy_dbg(hw, MIIDBG_ANACTRL, phy_data); | 
 | 			atl1c_read_phy_dbg(hw, MIIDBG_VOLT_CTRL, &phy_data); | 
 | 			phy_data |= VOLT_CTRL_SWLOWEST; | 
 | 			atl1c_write_phy_dbg(hw, MIIDBG_VOLT_CTRL, phy_data); | 
 | 			udelay(20); | 
 | 			raise_vol = true; | 
 | 		} | 
 |  | 
 | 		AT_READ_REG(hw, REG_TWSI_CTRL, &twsi_ctrl_data); | 
 | 		twsi_ctrl_data |= TWSI_CTRL_SW_LDSTART; | 
 | 		AT_WRITE_REG(hw, REG_TWSI_CTRL, twsi_ctrl_data); | 
 | 		for (i = 0; i < AT_TWSI_EEPROM_TIMEOUT; i++) { | 
 | 			msleep(10); | 
 | 			AT_READ_REG(hw, REG_TWSI_CTRL, &twsi_ctrl_data); | 
 | 			if ((twsi_ctrl_data & TWSI_CTRL_SW_LDSTART) == 0) | 
 | 				break; | 
 | 		} | 
 | 		if (i >= AT_TWSI_EEPROM_TIMEOUT) | 
 | 			return -1; | 
 | 	} | 
 | 	/* Disable OTP_CLK */ | 
 | 	if ((hw->nic_type == athr_l1c || hw->nic_type == athr_l2c)) { | 
 | 		otp_ctrl_data &= ~OTP_CTRL_CLK_EN; | 
 | 		AT_WRITE_REG(hw, REG_OTP_CTRL, otp_ctrl_data); | 
 | 		msleep(1); | 
 | 	} | 
 | 	if (raise_vol) { | 
 | 		atl1c_read_phy_dbg(hw, MIIDBG_ANACTRL, &phy_data); | 
 | 		phy_data |= ANACTRL_HB_EN; | 
 | 		atl1c_write_phy_dbg(hw, MIIDBG_ANACTRL, phy_data); | 
 | 		atl1c_read_phy_dbg(hw, MIIDBG_VOLT_CTRL, &phy_data); | 
 | 		phy_data &= ~VOLT_CTRL_SWLOWEST; | 
 | 		atl1c_write_phy_dbg(hw, MIIDBG_VOLT_CTRL, phy_data); | 
 | 		udelay(20); | 
 | 	} | 
 |  | 
 | 	if (atl1c_read_current_addr(hw, hw->perm_mac_addr)) | 
 | 		return 0; | 
 |  | 
 | 	return -1; | 
 | } | 
 |  | 
 | bool atl1c_read_eeprom(struct atl1c_hw *hw, u32 offset, u32 *p_value) | 
 | { | 
 | 	int i; | 
 | 	bool ret = false; | 
 | 	u32 otp_ctrl_data; | 
 | 	u32 control; | 
 | 	u32 data; | 
 |  | 
 | 	if (offset & 3) | 
 | 		return ret; /* address do not align */ | 
 |  | 
 | 	AT_READ_REG(hw, REG_OTP_CTRL, &otp_ctrl_data); | 
 | 	if (!(otp_ctrl_data & OTP_CTRL_CLK_EN)) | 
 | 		AT_WRITE_REG(hw, REG_OTP_CTRL, | 
 | 				(otp_ctrl_data | OTP_CTRL_CLK_EN)); | 
 |  | 
 | 	AT_WRITE_REG(hw, REG_EEPROM_DATA_LO, 0); | 
 | 	control = (offset & EEPROM_CTRL_ADDR_MASK) << EEPROM_CTRL_ADDR_SHIFT; | 
 | 	AT_WRITE_REG(hw, REG_EEPROM_CTRL, control); | 
 |  | 
 | 	for (i = 0; i < 10; i++) { | 
 | 		udelay(100); | 
 | 		AT_READ_REG(hw, REG_EEPROM_CTRL, &control); | 
 | 		if (control & EEPROM_CTRL_RW) | 
 | 			break; | 
 | 	} | 
 | 	if (control & EEPROM_CTRL_RW) { | 
 | 		AT_READ_REG(hw, REG_EEPROM_CTRL, &data); | 
 | 		AT_READ_REG(hw, REG_EEPROM_DATA_LO, p_value); | 
 | 		data = data & 0xFFFF; | 
 | 		*p_value = swab32((data << 16) | (*p_value >> 16)); | 
 | 		ret = true; | 
 | 	} | 
 | 	if (!(otp_ctrl_data & OTP_CTRL_CLK_EN)) | 
 | 		AT_WRITE_REG(hw, REG_OTP_CTRL, otp_ctrl_data); | 
 |  | 
 | 	return ret; | 
 | } | 
 | /* | 
 |  * Reads the adapter's MAC address from the EEPROM | 
 |  * | 
 |  * hw - Struct containing variables accessed by shared code | 
 |  */ | 
 | int atl1c_read_mac_addr(struct atl1c_hw *hw) | 
 | { | 
 | 	int err = 0; | 
 |  | 
 | 	err = atl1c_get_permanent_address(hw); | 
 | 	if (err) | 
 | 		eth_random_addr(hw->perm_mac_addr); | 
 |  | 
 | 	memcpy(hw->mac_addr, hw->perm_mac_addr, sizeof(hw->perm_mac_addr)); | 
 | 	return err; | 
 | } | 
 |  | 
 | /* | 
 |  * atl1c_hash_mc_addr | 
 |  *  purpose | 
 |  *      set hash value for a multicast address | 
 |  *      hash calcu processing : | 
 |  *          1. calcu 32bit CRC for multicast address | 
 |  *          2. reverse crc with MSB to LSB | 
 |  */ | 
 | u32 atl1c_hash_mc_addr(struct atl1c_hw *hw, u8 *mc_addr) | 
 | { | 
 | 	u32 crc32; | 
 | 	u32 value = 0; | 
 | 	int i; | 
 |  | 
 | 	crc32 = ether_crc_le(6, mc_addr); | 
 | 	for (i = 0; i < 32; i++) | 
 | 		value |= (((crc32 >> i) & 1) << (31 - i)); | 
 |  | 
 | 	return value; | 
 | } | 
 |  | 
 | /* | 
 |  * Sets the bit in the multicast table corresponding to the hash value. | 
 |  * hw - Struct containing variables accessed by shared code | 
 |  * hash_value - Multicast address hash value | 
 |  */ | 
 | void atl1c_hash_set(struct atl1c_hw *hw, u32 hash_value) | 
 | { | 
 | 	u32 hash_bit, hash_reg; | 
 | 	u32 mta; | 
 |  | 
 | 	/* | 
 | 	 * The HASH Table  is a register array of 2 32-bit registers. | 
 | 	 * It is treated like an array of 64 bits.  We want to set | 
 | 	 * bit BitArray[hash_value]. So we figure out what register | 
 | 	 * the bit is in, read it, OR in the new bit, then write | 
 | 	 * back the new value.  The register is determined by the | 
 | 	 * upper bit of the hash value and the bit within that | 
 | 	 * register are determined by the lower 5 bits of the value. | 
 | 	 */ | 
 | 	hash_reg = (hash_value >> 31) & 0x1; | 
 | 	hash_bit = (hash_value >> 26) & 0x1F; | 
 |  | 
 | 	mta = AT_READ_REG_ARRAY(hw, REG_RX_HASH_TABLE, hash_reg); | 
 |  | 
 | 	mta |= (1 << hash_bit); | 
 |  | 
 | 	AT_WRITE_REG_ARRAY(hw, REG_RX_HASH_TABLE, hash_reg, mta); | 
 | } | 
 |  | 
 | /* | 
 |  * wait mdio module be idle | 
 |  * return true: idle | 
 |  *        false: still busy | 
 |  */ | 
 | bool atl1c_wait_mdio_idle(struct atl1c_hw *hw) | 
 | { | 
 | 	u32 val; | 
 | 	int i; | 
 |  | 
 | 	for (i = 0; i < MDIO_MAX_AC_TO; i++) { | 
 | 		AT_READ_REG(hw, REG_MDIO_CTRL, &val); | 
 | 		if (!(val & (MDIO_CTRL_BUSY | MDIO_CTRL_START))) | 
 | 			break; | 
 | 		udelay(10); | 
 | 	} | 
 |  | 
 | 	return i != MDIO_MAX_AC_TO; | 
 | } | 
 |  | 
 | void atl1c_stop_phy_polling(struct atl1c_hw *hw) | 
 | { | 
 | 	if (!(hw->ctrl_flags & ATL1C_FPGA_VERSION)) | 
 | 		return; | 
 |  | 
 | 	AT_WRITE_REG(hw, REG_MDIO_CTRL, 0); | 
 | 	atl1c_wait_mdio_idle(hw); | 
 | } | 
 |  | 
 | void atl1c_start_phy_polling(struct atl1c_hw *hw, u16 clk_sel) | 
 | { | 
 | 	u32 val; | 
 |  | 
 | 	if (!(hw->ctrl_flags & ATL1C_FPGA_VERSION)) | 
 | 		return; | 
 |  | 
 | 	val = MDIO_CTRL_SPRES_PRMBL | | 
 | 		FIELDX(MDIO_CTRL_CLK_SEL, clk_sel) | | 
 | 		FIELDX(MDIO_CTRL_REG, 1) | | 
 | 		MDIO_CTRL_START | | 
 | 		MDIO_CTRL_OP_READ; | 
 | 	AT_WRITE_REG(hw, REG_MDIO_CTRL, val); | 
 | 	atl1c_wait_mdio_idle(hw); | 
 | 	val |= MDIO_CTRL_AP_EN; | 
 | 	val &= ~MDIO_CTRL_START; | 
 | 	AT_WRITE_REG(hw, REG_MDIO_CTRL, val); | 
 | 	udelay(30); | 
 | } | 
 |  | 
 |  | 
 | /* | 
 |  * atl1c_read_phy_core | 
 |  * core function to read register in PHY via MDIO control register. | 
 |  * ext: extension register (see IEEE 802.3) | 
 |  * dev: device address (see IEEE 802.3 DEVAD, PRTAD is fixed to 0) | 
 |  * reg: reg to read | 
 |  */ | 
 | int atl1c_read_phy_core(struct atl1c_hw *hw, bool ext, u8 dev, | 
 | 			u16 reg, u16 *phy_data) | 
 | { | 
 | 	u32 val; | 
 | 	u16 clk_sel = MDIO_CTRL_CLK_25_4; | 
 |  | 
 | 	atl1c_stop_phy_polling(hw); | 
 |  | 
 | 	*phy_data = 0; | 
 |  | 
 | 	/* only l2c_b2 & l1d_2 could use slow clock */ | 
 | 	if ((hw->nic_type == athr_l2c_b2 || hw->nic_type == athr_l1d_2) && | 
 | 		hw->hibernate) | 
 | 		clk_sel = MDIO_CTRL_CLK_25_128; | 
 | 	if (ext) { | 
 | 		val = FIELDX(MDIO_EXTN_DEVAD, dev) | FIELDX(MDIO_EXTN_REG, reg); | 
 | 		AT_WRITE_REG(hw, REG_MDIO_EXTN, val); | 
 | 		val = MDIO_CTRL_SPRES_PRMBL | | 
 | 			FIELDX(MDIO_CTRL_CLK_SEL, clk_sel) | | 
 | 			MDIO_CTRL_START | | 
 | 			MDIO_CTRL_MODE_EXT | | 
 | 			MDIO_CTRL_OP_READ; | 
 | 	} else { | 
 | 		val = MDIO_CTRL_SPRES_PRMBL | | 
 | 			FIELDX(MDIO_CTRL_CLK_SEL, clk_sel) | | 
 | 			FIELDX(MDIO_CTRL_REG, reg) | | 
 | 			MDIO_CTRL_START | | 
 | 			MDIO_CTRL_OP_READ; | 
 | 	} | 
 | 	AT_WRITE_REG(hw, REG_MDIO_CTRL, val); | 
 |  | 
 | 	if (!atl1c_wait_mdio_idle(hw)) | 
 | 		return -1; | 
 |  | 
 | 	AT_READ_REG(hw, REG_MDIO_CTRL, &val); | 
 | 	*phy_data = (u16)FIELD_GETX(val, MDIO_CTRL_DATA); | 
 |  | 
 | 	atl1c_start_phy_polling(hw, clk_sel); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | /* | 
 |  * atl1c_write_phy_core | 
 |  * core function to write to register in PHY via MDIO control register. | 
 |  * ext: extension register (see IEEE 802.3) | 
 |  * dev: device address (see IEEE 802.3 DEVAD, PRTAD is fixed to 0) | 
 |  * reg: reg to write | 
 |  */ | 
 | int atl1c_write_phy_core(struct atl1c_hw *hw, bool ext, u8 dev, | 
 | 			u16 reg, u16 phy_data) | 
 | { | 
 | 	u32 val; | 
 | 	u16 clk_sel = MDIO_CTRL_CLK_25_4; | 
 |  | 
 | 	atl1c_stop_phy_polling(hw); | 
 |  | 
 |  | 
 | 	/* only l2c_b2 & l1d_2 could use slow clock */ | 
 | 	if ((hw->nic_type == athr_l2c_b2 || hw->nic_type == athr_l1d_2) && | 
 | 		hw->hibernate) | 
 | 		clk_sel = MDIO_CTRL_CLK_25_128; | 
 |  | 
 | 	if (ext) { | 
 | 		val = FIELDX(MDIO_EXTN_DEVAD, dev) | FIELDX(MDIO_EXTN_REG, reg); | 
 | 		AT_WRITE_REG(hw, REG_MDIO_EXTN, val); | 
 | 		val = MDIO_CTRL_SPRES_PRMBL | | 
 | 			FIELDX(MDIO_CTRL_CLK_SEL, clk_sel) | | 
 | 			FIELDX(MDIO_CTRL_DATA, phy_data) | | 
 | 			MDIO_CTRL_START | | 
 | 			MDIO_CTRL_MODE_EXT; | 
 | 	} else { | 
 | 		val = MDIO_CTRL_SPRES_PRMBL | | 
 | 			FIELDX(MDIO_CTRL_CLK_SEL, clk_sel) | | 
 | 			FIELDX(MDIO_CTRL_DATA, phy_data) | | 
 | 			FIELDX(MDIO_CTRL_REG, reg) | | 
 | 			MDIO_CTRL_START; | 
 | 	} | 
 | 	AT_WRITE_REG(hw, REG_MDIO_CTRL, val); | 
 |  | 
 | 	if (!atl1c_wait_mdio_idle(hw)) | 
 | 		return -1; | 
 |  | 
 | 	atl1c_start_phy_polling(hw, clk_sel); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | /* | 
 |  * Reads the value from a PHY register | 
 |  * hw - Struct containing variables accessed by shared code | 
 |  * reg_addr - address of the PHY register to read | 
 |  */ | 
 | int atl1c_read_phy_reg(struct atl1c_hw *hw, u16 reg_addr, u16 *phy_data) | 
 | { | 
 | 	return atl1c_read_phy_core(hw, false, 0, reg_addr, phy_data); | 
 | } | 
 |  | 
 | /* | 
 |  * Writes a value to a PHY register | 
 |  * hw - Struct containing variables accessed by shared code | 
 |  * reg_addr - address of the PHY register to write | 
 |  * data - data to write to the PHY | 
 |  */ | 
 | int atl1c_write_phy_reg(struct atl1c_hw *hw, u32 reg_addr, u16 phy_data) | 
 | { | 
 | 	return atl1c_write_phy_core(hw, false, 0, reg_addr, phy_data); | 
 | } | 
 |  | 
 | /* read from PHY extension register */ | 
 | int atl1c_read_phy_ext(struct atl1c_hw *hw, u8 dev_addr, | 
 | 			u16 reg_addr, u16 *phy_data) | 
 | { | 
 | 	return atl1c_read_phy_core(hw, true, dev_addr, reg_addr, phy_data); | 
 | } | 
 |  | 
 | /* write to PHY extension register */ | 
 | int atl1c_write_phy_ext(struct atl1c_hw *hw, u8 dev_addr, | 
 | 			u16 reg_addr, u16 phy_data) | 
 | { | 
 | 	return atl1c_write_phy_core(hw, true, dev_addr, reg_addr, phy_data); | 
 | } | 
 |  | 
 | int atl1c_read_phy_dbg(struct atl1c_hw *hw, u16 reg_addr, u16 *phy_data) | 
 | { | 
 | 	int err; | 
 |  | 
 | 	err = atl1c_write_phy_reg(hw, MII_DBG_ADDR, reg_addr); | 
 | 	if (unlikely(err)) | 
 | 		return err; | 
 | 	else | 
 | 		err = atl1c_read_phy_reg(hw, MII_DBG_DATA, phy_data); | 
 |  | 
 | 	return err; | 
 | } | 
 |  | 
 | int atl1c_write_phy_dbg(struct atl1c_hw *hw, u16 reg_addr, u16 phy_data) | 
 | { | 
 | 	int err; | 
 |  | 
 | 	err = atl1c_write_phy_reg(hw, MII_DBG_ADDR, reg_addr); | 
 | 	if (unlikely(err)) | 
 | 		return err; | 
 | 	else | 
 | 		err = atl1c_write_phy_reg(hw, MII_DBG_DATA, phy_data); | 
 |  | 
 | 	return err; | 
 | } | 
 |  | 
 | /* | 
 |  * Configures PHY autoneg and flow control advertisement settings | 
 |  * | 
 |  * hw - Struct containing variables accessed by shared code | 
 |  */ | 
 | static int atl1c_phy_setup_adv(struct atl1c_hw *hw) | 
 | { | 
 | 	u16 mii_adv_data = ADVERTISE_DEFAULT_CAP & ~ADVERTISE_ALL; | 
 | 	u16 mii_giga_ctrl_data = GIGA_CR_1000T_DEFAULT_CAP & | 
 | 				~GIGA_CR_1000T_SPEED_MASK; | 
 |  | 
 | 	if (hw->autoneg_advertised & ADVERTISED_10baseT_Half) | 
 | 		mii_adv_data |= ADVERTISE_10HALF; | 
 | 	if (hw->autoneg_advertised & ADVERTISED_10baseT_Full) | 
 | 		mii_adv_data |= ADVERTISE_10FULL; | 
 | 	if (hw->autoneg_advertised & ADVERTISED_100baseT_Half) | 
 | 		mii_adv_data |= ADVERTISE_100HALF; | 
 | 	if (hw->autoneg_advertised & ADVERTISED_100baseT_Full) | 
 | 		mii_adv_data |= ADVERTISE_100FULL; | 
 |  | 
 | 	if (hw->autoneg_advertised & ADVERTISED_Autoneg) | 
 | 		mii_adv_data |= ADVERTISE_10HALF  | ADVERTISE_10FULL | | 
 | 				ADVERTISE_100HALF | ADVERTISE_100FULL; | 
 |  | 
 | 	if (hw->link_cap_flags & ATL1C_LINK_CAP_1000M) { | 
 | 		if (hw->autoneg_advertised & ADVERTISED_1000baseT_Half) | 
 | 			mii_giga_ctrl_data |= ADVERTISE_1000HALF; | 
 | 		if (hw->autoneg_advertised & ADVERTISED_1000baseT_Full) | 
 | 			mii_giga_ctrl_data |= ADVERTISE_1000FULL; | 
 | 		if (hw->autoneg_advertised & ADVERTISED_Autoneg) | 
 | 			mii_giga_ctrl_data |= ADVERTISE_1000HALF | | 
 | 					ADVERTISE_1000FULL; | 
 | 	} | 
 |  | 
 | 	if (atl1c_write_phy_reg(hw, MII_ADVERTISE, mii_adv_data) != 0 || | 
 | 	    atl1c_write_phy_reg(hw, MII_CTRL1000, mii_giga_ctrl_data) != 0) | 
 | 		return -1; | 
 | 	return 0; | 
 | } | 
 |  | 
 | void atl1c_phy_disable(struct atl1c_hw *hw) | 
 | { | 
 | 	atl1c_power_saving(hw, 0); | 
 | } | 
 |  | 
 |  | 
 | int atl1c_phy_reset(struct atl1c_hw *hw) | 
 | { | 
 | 	struct atl1c_adapter *adapter = hw->adapter; | 
 | 	struct pci_dev *pdev = adapter->pdev; | 
 | 	u16 phy_data; | 
 | 	u32 phy_ctrl_data, lpi_ctrl; | 
 | 	int err; | 
 |  | 
 | 	/* reset PHY core */ | 
 | 	AT_READ_REG(hw, REG_GPHY_CTRL, &phy_ctrl_data); | 
 | 	phy_ctrl_data &= ~(GPHY_CTRL_EXT_RESET | GPHY_CTRL_PHY_IDDQ | | 
 | 		GPHY_CTRL_GATE_25M_EN | GPHY_CTRL_PWDOWN_HW | GPHY_CTRL_CLS); | 
 | 	phy_ctrl_data |= GPHY_CTRL_SEL_ANA_RST; | 
 | 	if (!(hw->ctrl_flags & ATL1C_HIB_DISABLE)) | 
 | 		phy_ctrl_data |= (GPHY_CTRL_HIB_EN | GPHY_CTRL_HIB_PULSE); | 
 | 	else | 
 | 		phy_ctrl_data &= ~(GPHY_CTRL_HIB_EN | GPHY_CTRL_HIB_PULSE); | 
 | 	AT_WRITE_REG(hw, REG_GPHY_CTRL, phy_ctrl_data); | 
 | 	AT_WRITE_FLUSH(hw); | 
 | 	udelay(10); | 
 | 	AT_WRITE_REG(hw, REG_GPHY_CTRL, phy_ctrl_data | GPHY_CTRL_EXT_RESET); | 
 | 	AT_WRITE_FLUSH(hw); | 
 | 	udelay(10 * GPHY_CTRL_EXT_RST_TO);	/* delay 800us */ | 
 |  | 
 | 	/* switch clock */ | 
 | 	if (hw->nic_type == athr_l2c_b) { | 
 | 		atl1c_read_phy_dbg(hw, MIIDBG_CFGLPSPD, &phy_data); | 
 | 		atl1c_write_phy_dbg(hw, MIIDBG_CFGLPSPD, | 
 | 			phy_data & ~CFGLPSPD_RSTCNT_CLK125SW); | 
 | 	} | 
 |  | 
 | 	/* tx-half amplitude issue fix */ | 
 | 	if (hw->nic_type == athr_l2c_b || hw->nic_type == athr_l2c_b2) { | 
 | 		atl1c_read_phy_dbg(hw, MIIDBG_CABLE1TH_DET, &phy_data); | 
 | 		phy_data |= CABLE1TH_DET_EN; | 
 | 		atl1c_write_phy_dbg(hw, MIIDBG_CABLE1TH_DET, phy_data); | 
 | 	} | 
 |  | 
 | 	/* clear bit3 of dbgport 3B to lower voltage */ | 
 | 	if (!(hw->ctrl_flags & ATL1C_HIB_DISABLE)) { | 
 | 		if (hw->nic_type == athr_l2c_b || hw->nic_type == athr_l2c_b2) { | 
 | 			atl1c_read_phy_dbg(hw, MIIDBG_VOLT_CTRL, &phy_data); | 
 | 			phy_data &= ~VOLT_CTRL_SWLOWEST; | 
 | 			atl1c_write_phy_dbg(hw, MIIDBG_VOLT_CTRL, phy_data); | 
 | 		} | 
 | 		/* power saving config */ | 
 | 		phy_data = | 
 | 			hw->nic_type == athr_l1d || hw->nic_type == athr_l1d_2 ? | 
 | 			L1D_LEGCYPS_DEF : L1C_LEGCYPS_DEF; | 
 | 		atl1c_write_phy_dbg(hw, MIIDBG_LEGCYPS, phy_data); | 
 | 		/* hib */ | 
 | 		atl1c_write_phy_dbg(hw, MIIDBG_SYSMODCTRL, | 
 | 			SYSMODCTRL_IECHOADJ_DEF); | 
 | 	} else { | 
 | 		/* disable pws */ | 
 | 		atl1c_read_phy_dbg(hw, MIIDBG_LEGCYPS, &phy_data); | 
 | 		atl1c_write_phy_dbg(hw, MIIDBG_LEGCYPS, | 
 | 			phy_data & ~LEGCYPS_EN); | 
 | 		/* disable hibernate */ | 
 | 		atl1c_read_phy_dbg(hw, MIIDBG_HIBNEG, &phy_data); | 
 | 		atl1c_write_phy_dbg(hw, MIIDBG_HIBNEG, | 
 | 			phy_data & HIBNEG_PSHIB_EN); | 
 | 	} | 
 | 	/* disable AZ(EEE) by default */ | 
 | 	if (hw->nic_type == athr_l1d || hw->nic_type == athr_l1d_2 || | 
 | 	    hw->nic_type == athr_l2c_b2) { | 
 | 		AT_READ_REG(hw, REG_LPI_CTRL, &lpi_ctrl); | 
 | 		AT_WRITE_REG(hw, REG_LPI_CTRL, lpi_ctrl & ~LPI_CTRL_EN); | 
 | 		atl1c_write_phy_ext(hw, MIIEXT_ANEG, MIIEXT_LOCAL_EEEADV, 0); | 
 | 		atl1c_write_phy_ext(hw, MIIEXT_PCS, MIIEXT_CLDCTRL3, | 
 | 			L2CB_CLDCTRL3); | 
 | 	} | 
 |  | 
 | 	/* other debug port to set */ | 
 | 	atl1c_write_phy_dbg(hw, MIIDBG_ANACTRL, ANACTRL_DEF); | 
 | 	atl1c_write_phy_dbg(hw, MIIDBG_SRDSYSMOD, SRDSYSMOD_DEF); | 
 | 	atl1c_write_phy_dbg(hw, MIIDBG_TST10BTCFG, TST10BTCFG_DEF); | 
 | 	/* UNH-IOL test issue, set bit7 */ | 
 | 	atl1c_write_phy_dbg(hw, MIIDBG_TST100BTCFG, | 
 | 		TST100BTCFG_DEF | TST100BTCFG_LITCH_EN); | 
 |  | 
 | 	/* set phy interrupt mask */ | 
 | 	phy_data = IER_LINK_UP | IER_LINK_DOWN; | 
 | 	err = atl1c_write_phy_reg(hw, MII_IER, phy_data); | 
 | 	if (err) { | 
 | 		if (netif_msg_hw(adapter)) | 
 | 			dev_err(&pdev->dev, | 
 | 				"Error enable PHY linkChange Interrupt\n"); | 
 | 		return err; | 
 | 	} | 
 | 	return 0; | 
 | } | 
 |  | 
 | int atl1c_phy_init(struct atl1c_hw *hw) | 
 | { | 
 | 	struct atl1c_adapter *adapter = hw->adapter; | 
 | 	struct pci_dev *pdev = adapter->pdev; | 
 | 	int ret_val; | 
 | 	u16 mii_bmcr_data = BMCR_RESET; | 
 |  | 
 | 	if (hw->nic_type == athr_mt) { | 
 | 		hw->phy_configured = true; | 
 | 		return 0; | 
 | 	} | 
 |  | 
 | 	if ((atl1c_read_phy_reg(hw, MII_PHYSID1, &hw->phy_id1) != 0) || | 
 | 		(atl1c_read_phy_reg(hw, MII_PHYSID2, &hw->phy_id2) != 0)) { | 
 | 		dev_err(&pdev->dev, "Error get phy ID\n"); | 
 | 		return -1; | 
 | 	} | 
 | 	switch (hw->media_type) { | 
 | 	case MEDIA_TYPE_AUTO_SENSOR: | 
 | 		ret_val = atl1c_phy_setup_adv(hw); | 
 | 		if (ret_val) { | 
 | 			if (netif_msg_link(adapter)) | 
 | 				dev_err(&pdev->dev, | 
 | 					"Error Setting up Auto-Negotiation\n"); | 
 | 			return ret_val; | 
 | 		} | 
 | 		mii_bmcr_data |= BMCR_ANENABLE | BMCR_ANRESTART; | 
 | 		break; | 
 | 	case MEDIA_TYPE_100M_FULL: | 
 | 		mii_bmcr_data |= BMCR_SPEED100 | BMCR_FULLDPLX; | 
 | 		break; | 
 | 	case MEDIA_TYPE_100M_HALF: | 
 | 		mii_bmcr_data |= BMCR_SPEED100; | 
 | 		break; | 
 | 	case MEDIA_TYPE_10M_FULL: | 
 | 		mii_bmcr_data |= BMCR_FULLDPLX; | 
 | 		break; | 
 | 	case MEDIA_TYPE_10M_HALF: | 
 | 		break; | 
 | 	default: | 
 | 		if (netif_msg_link(adapter)) | 
 | 			dev_err(&pdev->dev, "Wrong Media type %d\n", | 
 | 				hw->media_type); | 
 | 		return -1; | 
 | 	} | 
 |  | 
 | 	ret_val = atl1c_write_phy_reg(hw, MII_BMCR, mii_bmcr_data); | 
 | 	if (ret_val) | 
 | 		return ret_val; | 
 | 	hw->phy_configured = true; | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | bool atl1c_get_link_status(struct atl1c_hw *hw) | 
 | { | 
 | 	u16 phy_data; | 
 |  | 
 | 	if (hw->nic_type == athr_mt) { | 
 | 		u32 spd; | 
 |  | 
 | 		AT_READ_REG(hw, REG_MT_SPEED, &spd); | 
 | 		return !!spd; | 
 | 	} | 
 |  | 
 | 	/* MII_BMSR must be read twice */ | 
 | 	atl1c_read_phy_reg(hw, MII_BMSR, &phy_data); | 
 | 	atl1c_read_phy_reg(hw, MII_BMSR, &phy_data); | 
 | 	return !!(phy_data & BMSR_LSTATUS); | 
 | } | 
 |  | 
 | /* | 
 |  * Detects the current speed and duplex settings of the hardware. | 
 |  * | 
 |  * hw - Struct containing variables accessed by shared code | 
 |  * speed - Speed of the connection | 
 |  * duplex - Duplex setting of the connection | 
 |  */ | 
 | int atl1c_get_speed_and_duplex(struct atl1c_hw *hw, u16 *speed, u16 *duplex) | 
 | { | 
 | 	int err; | 
 | 	u16 phy_data; | 
 |  | 
 | 	if (hw->nic_type == athr_mt) { | 
 | 		u32 spd; | 
 |  | 
 | 		AT_READ_REG(hw, REG_MT_SPEED, &spd); | 
 | 		*speed = spd; | 
 | 		*duplex = FULL_DUPLEX; | 
 | 		return 0; | 
 | 	} | 
 |  | 
 | 	/* Read   PHY Specific Status Register (17) */ | 
 | 	err = atl1c_read_phy_reg(hw, MII_GIGA_PSSR, &phy_data); | 
 | 	if (err) | 
 | 		return err; | 
 |  | 
 | 	if (!(phy_data & GIGA_PSSR_SPD_DPLX_RESOLVED)) | 
 | 		return -1; | 
 |  | 
 | 	switch (phy_data & GIGA_PSSR_SPEED) { | 
 | 	case GIGA_PSSR_1000MBS: | 
 | 		*speed = SPEED_1000; | 
 | 		break; | 
 | 	case GIGA_PSSR_100MBS: | 
 | 		*speed = SPEED_100; | 
 | 		break; | 
 | 	case  GIGA_PSSR_10MBS: | 
 | 		*speed = SPEED_10; | 
 | 		break; | 
 | 	default: | 
 | 		return -1; | 
 | 	} | 
 |  | 
 | 	if (phy_data & GIGA_PSSR_DPLX) | 
 | 		*duplex = FULL_DUPLEX; | 
 | 	else | 
 | 		*duplex = HALF_DUPLEX; | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | /* select one link mode to get lower power consumption */ | 
 | int atl1c_phy_to_ps_link(struct atl1c_hw *hw) | 
 | { | 
 | 	struct atl1c_adapter *adapter = hw->adapter; | 
 | 	struct pci_dev *pdev = adapter->pdev; | 
 | 	int ret = 0; | 
 | 	u16 autoneg_advertised = ADVERTISED_10baseT_Half; | 
 | 	u16 save_autoneg_advertised; | 
 | 	u16 mii_lpa_data; | 
 | 	u16 speed = SPEED_0; | 
 | 	u16 duplex = FULL_DUPLEX; | 
 | 	int i; | 
 |  | 
 | 	if (atl1c_get_link_status(hw)) { | 
 | 		atl1c_read_phy_reg(hw, MII_LPA, &mii_lpa_data); | 
 | 		if (mii_lpa_data & LPA_10FULL) | 
 | 			autoneg_advertised = ADVERTISED_10baseT_Full; | 
 | 		else if (mii_lpa_data & LPA_10HALF) | 
 | 			autoneg_advertised = ADVERTISED_10baseT_Half; | 
 | 		else if (mii_lpa_data & LPA_100HALF) | 
 | 			autoneg_advertised = ADVERTISED_100baseT_Half; | 
 | 		else if (mii_lpa_data & LPA_100FULL) | 
 | 			autoneg_advertised = ADVERTISED_100baseT_Full; | 
 |  | 
 | 		save_autoneg_advertised = hw->autoneg_advertised; | 
 | 		hw->phy_configured = false; | 
 | 		hw->autoneg_advertised = autoneg_advertised; | 
 | 		if (atl1c_restart_autoneg(hw) != 0) { | 
 | 			dev_dbg(&pdev->dev, "phy autoneg failed\n"); | 
 | 			ret = -1; | 
 | 		} | 
 | 		hw->autoneg_advertised = save_autoneg_advertised; | 
 |  | 
 | 		if (mii_lpa_data) { | 
 | 			for (i = 0; i < AT_SUSPEND_LINK_TIMEOUT; i++) { | 
 | 				mdelay(100); | 
 | 				if (atl1c_get_link_status(hw)) { | 
 | 					if (atl1c_get_speed_and_duplex(hw, &speed, | 
 | 									&duplex) != 0) | 
 | 						dev_dbg(&pdev->dev, | 
 | 							"get speed and duplex failed\n"); | 
 | 					break; | 
 | 				} | 
 | 			} | 
 | 		} | 
 | 	} else { | 
 | 		speed = SPEED_10; | 
 | 		duplex = HALF_DUPLEX; | 
 | 	} | 
 | 	adapter->link_speed = speed; | 
 | 	adapter->link_duplex = duplex; | 
 |  | 
 | 	return ret; | 
 | } | 
 |  | 
 | int atl1c_restart_autoneg(struct atl1c_hw *hw) | 
 | { | 
 | 	int err = 0; | 
 | 	u16 mii_bmcr_data = BMCR_RESET; | 
 |  | 
 | 	err = atl1c_phy_setup_adv(hw); | 
 | 	if (err) | 
 | 		return err; | 
 | 	mii_bmcr_data |= BMCR_ANENABLE | BMCR_ANRESTART; | 
 |  | 
 | 	return atl1c_write_phy_reg(hw, MII_BMCR, mii_bmcr_data); | 
 | } | 
 |  | 
 | int atl1c_power_saving(struct atl1c_hw *hw, u32 wufc) | 
 | { | 
 | 	struct atl1c_adapter *adapter = hw->adapter; | 
 | 	struct pci_dev *pdev = adapter->pdev; | 
 | 	u32 master_ctrl, mac_ctrl, phy_ctrl; | 
 | 	u32 wol_ctrl, speed; | 
 | 	u16 phy_data; | 
 |  | 
 | 	wol_ctrl = 0; | 
 | 	speed = adapter->link_speed == SPEED_1000 ? | 
 | 		MAC_CTRL_SPEED_1000 : MAC_CTRL_SPEED_10_100; | 
 |  | 
 | 	AT_READ_REG(hw, REG_MASTER_CTRL, &master_ctrl); | 
 | 	AT_READ_REG(hw, REG_MAC_CTRL, &mac_ctrl); | 
 | 	AT_READ_REG(hw, REG_GPHY_CTRL, &phy_ctrl); | 
 |  | 
 | 	master_ctrl &= ~MASTER_CTRL_CLK_SEL_DIS; | 
 | 	mac_ctrl = FIELD_SETX(mac_ctrl, MAC_CTRL_SPEED, speed); | 
 | 	mac_ctrl &= ~(MAC_CTRL_DUPLX | MAC_CTRL_RX_EN | MAC_CTRL_TX_EN); | 
 | 	if (adapter->link_duplex == FULL_DUPLEX) | 
 | 		mac_ctrl |= MAC_CTRL_DUPLX; | 
 | 	phy_ctrl &= ~(GPHY_CTRL_EXT_RESET | GPHY_CTRL_CLS); | 
 | 	phy_ctrl |= GPHY_CTRL_SEL_ANA_RST | GPHY_CTRL_HIB_PULSE | | 
 | 		GPHY_CTRL_HIB_EN; | 
 | 	if (!wufc) { /* without WoL */ | 
 | 		master_ctrl |= MASTER_CTRL_CLK_SEL_DIS; | 
 | 		phy_ctrl |= GPHY_CTRL_PHY_IDDQ | GPHY_CTRL_PWDOWN_HW; | 
 | 		AT_WRITE_REG(hw, REG_MASTER_CTRL, master_ctrl); | 
 | 		AT_WRITE_REG(hw, REG_MAC_CTRL, mac_ctrl); | 
 | 		AT_WRITE_REG(hw, REG_GPHY_CTRL, phy_ctrl); | 
 | 		AT_WRITE_REG(hw, REG_WOL_CTRL, 0); | 
 | 		hw->phy_configured = false; /* re-init PHY when resume */ | 
 | 		return 0; | 
 | 	} | 
 | 	phy_ctrl |= GPHY_CTRL_EXT_RESET; | 
 | 	if (wufc & AT_WUFC_MAG) { | 
 | 		mac_ctrl |= MAC_CTRL_RX_EN | MAC_CTRL_BC_EN; | 
 | 		wol_ctrl |= WOL_MAGIC_EN | WOL_MAGIC_PME_EN; | 
 | 		if (hw->nic_type == athr_l2c_b && hw->revision_id == L2CB_V11) | 
 | 			wol_ctrl |= WOL_PATTERN_EN | WOL_PATTERN_PME_EN; | 
 | 	} | 
 | 	if (wufc & AT_WUFC_LNKC) { | 
 | 		wol_ctrl |= WOL_LINK_CHG_EN | WOL_LINK_CHG_PME_EN; | 
 | 		if (atl1c_write_phy_reg(hw, MII_IER, IER_LINK_UP) != 0) { | 
 | 			dev_dbg(&pdev->dev, "%s: write phy MII_IER failed.\n", | 
 | 				atl1c_driver_name); | 
 | 		} | 
 | 	} | 
 | 	/* clear PHY interrupt */ | 
 | 	atl1c_read_phy_reg(hw, MII_ISR, &phy_data); | 
 |  | 
 | 	dev_dbg(&pdev->dev, "%s: suspend MAC=%x,MASTER=%x,PHY=0x%x,WOL=%x\n", | 
 | 		atl1c_driver_name, mac_ctrl, master_ctrl, phy_ctrl, wol_ctrl); | 
 | 	AT_WRITE_REG(hw, REG_MASTER_CTRL, master_ctrl); | 
 | 	AT_WRITE_REG(hw, REG_MAC_CTRL, mac_ctrl); | 
 | 	AT_WRITE_REG(hw, REG_GPHY_CTRL, phy_ctrl); | 
 | 	AT_WRITE_REG(hw, REG_WOL_CTRL, wol_ctrl); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 |  | 
 | /* configure phy after Link change Event */ | 
 | void atl1c_post_phy_linkchg(struct atl1c_hw *hw, u16 link_speed) | 
 | { | 
 | 	u16 phy_val; | 
 | 	bool adj_thresh = false; | 
 |  | 
 | 	if (hw->nic_type == athr_l2c_b || hw->nic_type == athr_l2c_b2 || | 
 | 	    hw->nic_type == athr_l1d || hw->nic_type == athr_l1d_2) | 
 | 		adj_thresh = true; | 
 |  | 
 | 	if (link_speed != SPEED_0) { /* link up */ | 
 | 		/* az with brcm, half-amp */ | 
 | 		if (hw->nic_type == athr_l1d_2) { | 
 | 			atl1c_read_phy_ext(hw, MIIEXT_PCS, MIIEXT_CLDCTRL6, | 
 | 				&phy_val); | 
 | 			phy_val = FIELD_GETX(phy_val, CLDCTRL6_CAB_LEN); | 
 | 			phy_val = phy_val > CLDCTRL6_CAB_LEN_SHORT ? | 
 | 				AZ_ANADECT_LONG : AZ_ANADECT_DEF; | 
 | 			atl1c_write_phy_dbg(hw, MIIDBG_AZ_ANADECT, phy_val); | 
 | 		} | 
 | 		/* threshold adjust */ | 
 | 		if (adj_thresh && link_speed == SPEED_100 && hw->msi_lnkpatch) { | 
 | 			atl1c_write_phy_dbg(hw, MIIDBG_MSE16DB, L1D_MSE16DB_UP); | 
 | 			atl1c_write_phy_dbg(hw, MIIDBG_SYSMODCTRL, | 
 | 				L1D_SYSMODCTRL_IECHOADJ_DEF); | 
 | 		} | 
 | 	} else { /* link down */ | 
 | 		if (adj_thresh && hw->msi_lnkpatch) { | 
 | 			atl1c_write_phy_dbg(hw, MIIDBG_SYSMODCTRL, | 
 | 				SYSMODCTRL_IECHOADJ_DEF); | 
 | 			atl1c_write_phy_dbg(hw, MIIDBG_MSE16DB, | 
 | 				L1D_MSE16DB_DOWN); | 
 | 		} | 
 | 	} | 
 | } |