Commit dbcd6806 authored by Dmitry Bezrukov's avatar Dmitry Bezrukov Committed by David S. Miller
Browse files

net: aquantia: add support for Phy access



GPIO PIN control and access is done by direct phy manipulation.
Here we add an aq_phy module which is able to access phy registers
via MDIO access mailbox.

Access is controlled via HW semaphore.

Co-developed-by: default avatarNikita Danilov <nikita.danilov@aquantia.com>
Signed-off-by: default avatarNikita Danilov <nikita.danilov@aquantia.com>
Signed-off-by: default avatarDmitry Bezrukov <dmitry.bezrukov@aquantia.com>
Signed-off-by: default avatarIgor Russkikh <igor.russkikh@aquantia.com>
Reviewed-by: default avatarAndrew Lunn <andrew@lunn.ch>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 84989af0
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ atlantic-objs := aq_main.o \
	aq_drvinfo.o \
	aq_filters.o \
	aq_ptp.o \
	aq_phy.o \
	hw_atl/hw_atl_a0.o \
	hw_atl/hw_atl_b0.o \
	hw_atl/hw_atl_utils.o \
+1 −0
Original line number Diff line number Diff line
@@ -140,6 +140,7 @@ struct aq_hw_s {
	u32 rpc_tid;
	struct hw_atl_utils_fw_rpc rpc;
	s64 ptp_clk_offset;
	u16 phy_id;
};

struct aq_ring_s;
+6 −0
Original line number Diff line number Diff line
@@ -12,6 +12,7 @@
#include "aq_hw.h"
#include "aq_pci_func.h"
#include "aq_main.h"
#include "aq_phy.h"
#include "aq_ptp.h"
#include "aq_filters.h"

@@ -337,6 +338,11 @@ int aq_nic_init(struct aq_nic_s *self)
	if (err < 0)
		goto err_exit;

	if (self->aq_nic_cfg.aq_hw_caps->media_type == AQ_HW_MEDIA_TYPE_TP) {
		self->aq_hw->phy_id = HW_ATL_PHY_ID_MAX;
		err = aq_phy_init(self->aq_hw);
	}

	for (i = 0U, aq_vec = self->aq_vec[0];
		self->aq_vecs > i; ++i, aq_vec = self->aq_vec[i])
		aq_vec_init(aq_vec, self->aq_hw_ops, self->aq_hw);
+147 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-only
/* aQuantia Corporation Network Driver
 * Copyright (C) 2018-2019 aQuantia Corporation. All rights reserved
 */

#include "aq_phy.h"

bool aq_mdio_busy_wait(struct aq_hw_s *aq_hw)
{
	int err = 0;
	u32 val;

	err = readx_poll_timeout_atomic(hw_atl_mdio_busy_get, aq_hw,
					val, val == 0U, 10U, 100000U);

	if (err < 0)
		return false;

	return true;
}

u16 aq_mdio_read_word(struct aq_hw_s *aq_hw, u16 mmd, u16 addr)
{
	u16 phy_addr = aq_hw->phy_id << 5 | mmd;

	/* Set Address register. */
	hw_atl_glb_mdio_iface4_set(aq_hw, (addr & HW_ATL_MDIO_ADDRESS_MSK) <<
				   HW_ATL_MDIO_ADDRESS_SHIFT);
	/* Send Address command. */
	hw_atl_glb_mdio_iface2_set(aq_hw, HW_ATL_MDIO_EXECUTE_OPERATION_MSK |
				   (3 << HW_ATL_MDIO_OP_MODE_SHIFT) |
				   ((phy_addr & HW_ATL_MDIO_PHY_ADDRESS_MSK) <<
				    HW_ATL_MDIO_PHY_ADDRESS_SHIFT));

	aq_mdio_busy_wait(aq_hw);

	/* Send Read command. */
	hw_atl_glb_mdio_iface2_set(aq_hw, HW_ATL_MDIO_EXECUTE_OPERATION_MSK |
				   (1 << HW_ATL_MDIO_OP_MODE_SHIFT) |
				   ((phy_addr & HW_ATL_MDIO_PHY_ADDRESS_MSK) <<
				    HW_ATL_MDIO_PHY_ADDRESS_SHIFT));
	/* Read result. */
	aq_mdio_busy_wait(aq_hw);

	return (u16)hw_atl_glb_mdio_iface5_get(aq_hw);
}

void aq_mdio_write_word(struct aq_hw_s *aq_hw, u16 mmd, u16 addr, u16 data)
{
	u16 phy_addr = aq_hw->phy_id << 5 | mmd;

	/* Set Address register. */
	hw_atl_glb_mdio_iface4_set(aq_hw, (addr & HW_ATL_MDIO_ADDRESS_MSK) <<
				   HW_ATL_MDIO_ADDRESS_SHIFT);
	/* Send Address command. */
	hw_atl_glb_mdio_iface2_set(aq_hw, HW_ATL_MDIO_EXECUTE_OPERATION_MSK |
				   (3 << HW_ATL_MDIO_OP_MODE_SHIFT) |
				   ((phy_addr & HW_ATL_MDIO_PHY_ADDRESS_MSK) <<
				    HW_ATL_MDIO_PHY_ADDRESS_SHIFT));

	aq_mdio_busy_wait(aq_hw);

	hw_atl_glb_mdio_iface3_set(aq_hw, (data & HW_ATL_MDIO_WRITE_DATA_MSK) <<
				   HW_ATL_MDIO_WRITE_DATA_SHIFT);
	/* Send Write command. */
	hw_atl_glb_mdio_iface2_set(aq_hw, HW_ATL_MDIO_EXECUTE_OPERATION_MSK |
				   (2 << HW_ATL_MDIO_OP_MODE_SHIFT) |
				   ((phy_addr & HW_ATL_MDIO_PHY_ADDRESS_MSK) <<
				    HW_ATL_MDIO_PHY_ADDRESS_SHIFT));

	aq_mdio_busy_wait(aq_hw);
}

u16 aq_phy_read_reg(struct aq_hw_s *aq_hw, u16 mmd, u16 address)
{
	int err = 0;
	u32 val;

	err = readx_poll_timeout_atomic(hw_atl_sem_mdio_get, aq_hw,
					val, val == 1U, 10U, 100000U);

	if (err < 0) {
		err = 0xffff;
		goto err_exit;
	}

	err = aq_mdio_read_word(aq_hw, mmd, address);

	hw_atl_reg_glb_cpu_sem_set(aq_hw, 1U, HW_ATL_FW_SM_MDIO);

err_exit:
	return err;
}

void aq_phy_write_reg(struct aq_hw_s *aq_hw, u16 mmd, u16 address, u16 data)
{
	int err = 0;
	u32 val;

	err = readx_poll_timeout_atomic(hw_atl_sem_mdio_get, aq_hw,
					val, val == 1U, 10U, 100000U);
	if (err < 0)
		return;

	aq_mdio_write_word(aq_hw, mmd, address, data);
	hw_atl_reg_glb_cpu_sem_set(aq_hw, 1U, HW_ATL_FW_SM_MDIO);
}

bool aq_phy_init_phy_id(struct aq_hw_s *aq_hw)
{
	u16 val;

	for (aq_hw->phy_id = 0; aq_hw->phy_id < HW_ATL_PHY_ID_MAX;
	     ++aq_hw->phy_id) {
		/* PMA Standard Device Identifier 2: Address 1.3 */
		val = aq_phy_read_reg(aq_hw, MDIO_MMD_PMAPMD, 3);

		if (val != 0xffff)
			return true;
	}

	return false;
}

bool aq_phy_init(struct aq_hw_s *aq_hw)
{
	u32 dev_id;

	if (aq_hw->phy_id == HW_ATL_PHY_ID_MAX)
		if (!aq_phy_init_phy_id(aq_hw))
			return false;

	/* PMA Standard Device Identifier:
	 * Address 1.2 = MSW,
	 * Address 1.3 = LSW
	 */
	dev_id = aq_phy_read_reg(aq_hw, MDIO_MMD_PMAPMD, 2);
	dev_id <<= 16;
	dev_id |= aq_phy_read_reg(aq_hw, MDIO_MMD_PMAPMD, 3);

	if (dev_id == 0xffffffff) {
		aq_hw->phy_id = HW_ATL_PHY_ID_MAX;
		return false;
	}

	return true;
}
+32 −0
Original line number Diff line number Diff line
/* SPDX-License-Identifier: GPL-2.0-only */
/* aQuantia Corporation Network Driver
 * Copyright (C) 2018-2019 aQuantia Corporation. All rights reserved
 */

#ifndef AQ_PHY_H
#define AQ_PHY_H

#include <linux/mdio.h>

#include "hw_atl/hw_atl_llh.h"
#include "hw_atl/hw_atl_llh_internal.h"
#include "aq_hw_utils.h"
#include "aq_hw.h"

#define HW_ATL_PHY_ID_MAX 32U

bool aq_mdio_busy_wait(struct aq_hw_s *aq_hw);

u16 aq_mdio_read_word(struct aq_hw_s *aq_hw, u16 mmd, u16 addr);

void aq_mdio_write_word(struct aq_hw_s *aq_hw, u16 mmd, u16 addr, u16 data);

u16 aq_phy_read_reg(struct aq_hw_s *aq_hw, u16 mmd, u16 address);

void aq_phy_write_reg(struct aq_hw_s *aq_hw, u16 mmd, u16 address, u16 data);

bool aq_phy_init_phy_id(struct aq_hw_s *aq_hw);

bool aq_phy_init(struct aq_hw_s *aq_hw);

#endif /* AQ_PHY_H */
Loading