/*
 * Xilinx PCS/PMA Core phy driver
 *
 * Copyright (C) 2015 - 2016 Xilinx, Inc.
 *
 * SPDX-License-Identifier:	GPL-2.0+
 */

#include <config.h>
#include <common.h>
#include <phy.h>
#include <dm.h>

DECLARE_GLOBAL_DATA_PTR;

#define MII_PHY_STATUS_SPD_MASK		0x0C00
#define MII_PHY_STATUS_FULLDUPLEX	0x1000
#define MII_PHY_STATUS_1000		0x0800
#define MII_PHY_STATUS_100		0x0400
#define XPCSPMA_PHY_CTRL_ISOLATE_DISABLE 0xFBFF

/* Mask used for ID comparisons */
#define XILINX_PHY_ID_MASK		0xfffffff0

/* Known PHY IDs */
#define XILINX_PHY_ID			0x01740c00

/* struct phy_device dev_flags definitions */
#define XAE_PHY_TYPE_MII		0
#define XAE_PHY_TYPE_GMII		1
#define XAE_PHY_TYPE_RGMII_1_3		2
#define XAE_PHY_TYPE_RGMII_2_0		3
#define XAE_PHY_TYPE_SGMII		4
#define XAE_PHY_TYPE_1000BASE_X		5

static int xilinxphy_startup(struct phy_device *phydev)
{
	int err;
	int status = 0;

	debug("%s\n", __func__);
	/* Update the link, but return if there
	 * was an error
	 */
	err = genphy_update_link(phydev);
	if (err)
		return err;

	if (AUTONEG_ENABLE == phydev->autoneg) {
		status = phy_read(phydev, MDIO_DEVAD_NONE, MII_LPA);
		status = status & MII_PHY_STATUS_SPD_MASK;

		if (status & MII_PHY_STATUS_FULLDUPLEX)
			phydev->duplex = DUPLEX_FULL;
		else
			phydev->duplex = DUPLEX_HALF;

		switch (status) {
		case MII_PHY_STATUS_1000:
			phydev->speed = SPEED_1000;
			break;

		case MII_PHY_STATUS_100:
			phydev->speed = SPEED_100;
			break;

		default:
			phydev->speed = SPEED_10;
			break;
		}
	} else {
		int bmcr = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMCR);

		if (bmcr < 0)
			return bmcr;

		if (bmcr & BMCR_FULLDPLX)
			phydev->duplex = DUPLEX_FULL;
		else
			phydev->duplex = DUPLEX_HALF;

		if (bmcr & BMCR_SPEED1000)
			phydev->speed = SPEED_1000;
		else if (bmcr & BMCR_SPEED100)
			phydev->speed = SPEED_100;
		else
			phydev->speed = SPEED_10;
	}

	/*
	 * For 1000BASE-X Phy Mode the speed/duplex will always be
	 * 1000Mbps/fullduplex
	 */
	if (phydev->flags == XAE_PHY_TYPE_1000BASE_X) {
		phydev->duplex = DUPLEX_FULL;
		phydev->speed = SPEED_1000;
	}

	return 0;
}

static int xilinxphy_of_init(struct phy_device *phydev)
{
	u32 phytype;

	debug("%s\n", __func__);
	phytype = fdtdec_get_int(gd->fdt_blob, dev_of_offset(phydev->dev),
				 "phy-type", -1);
	if (phytype == XAE_PHY_TYPE_1000BASE_X)
		phydev->flags |= XAE_PHY_TYPE_1000BASE_X;

	return 0;
}

static int xilinxphy_config(struct phy_device *phydev)
{
	int temp;

	debug("%s\n", __func__);
	xilinxphy_of_init(phydev);
	temp = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMCR);
	temp &= XPCSPMA_PHY_CTRL_ISOLATE_DISABLE;
	phy_write(phydev, MDIO_DEVAD_NONE, MII_BMCR, temp);

	return 0;
}

static struct phy_driver xilinxphy_driver = {
	.uid = XILINX_PHY_ID,
	.mask = XILINX_PHY_ID_MASK,
	.name = "Xilinx PCS/PMA PHY",
	.features = PHY_GBIT_FEATURES,
	.config = &xilinxphy_config,
	.startup = &xilinxphy_startup,
	.shutdown = &genphy_shutdown,
};

int phy_xilinx_init(void)
{
	debug("%s\n", __func__);
	phy_register(&xilinxphy_driver);

	return 0;
}
