#include "tn40.h"
#include "QT2025_phy.h"

#define LINK_LOOP_MAX   (10)

void QT2025_register_settings(struct bdx_priv *priv);
int QT2025_mdio_reset(struct bdx_priv *priv, int port, unsigned short phy);
u32 QT2025_link_changed(struct bdx_priv *priv);
void QT2025_leds(struct bdx_priv *priv, enum PHY_LEDS_OP op);

static int QT2025_get_link_speed(struct bdx_priv *priv)
{
	int speed;
	u16 module =
	    (bdx_mdio_read(priv, 3, priv->phy_mdio_port, 0xD70C)) & 0xFF;

	switch (module) {
	case 5:
	case 6:
	case 8:
	case 9:
		speed = SPEED_1000;
		break;

	default:
		speed = SPEED_10000;
		break;

	}

	return speed;

}

__init int QT2025_mdio_reset(struct bdx_priv *priv, int port,
			     unsigned short phy)
{
	struct device *dev = &priv->pdev->dev;
	u16 *phy_fw = QT2025_phy_firmware, j, fwVer01, fwVer2, fwVer3, module;
	int s_fw, i, a;
	int phy_id = 0, rev = 0;

	phy_id = bdx_mdio_read(priv, 1, port, 0xD001);

	switch (0xFF & (phy_id >> 8)) {
	case 0xb3:
		rev = 0xd;
		s_fw = sizeof(QT2025_phy_firmware) / sizeof(u16);
		break;

	default:
		dev_err(dev, "PHY ID =0x%x, returning\n",
			(0xFF & (phy_id >> 8)));
		return 1;
		break;
	}
	switch (rev) {
	default:
		dev_err(dev, "bdx: failed unknown PHY ID %x\n", phy_id);
		return 1;
		break;

	case 0xD:
		BDX_MDIO_WRITE(priv, 1, 0xC300, 0x0000);
		BDX_MDIO_WRITE(priv, 1, 0xC302, 0x4);
		BDX_MDIO_WRITE(priv, 1, 0xC319, 0x0038);

		BDX_MDIO_WRITE(priv, 1, 0xC31A, 0x0098);
		BDX_MDIO_WRITE(priv, 3, 0x0026, 0x0E00);

		BDX_MDIO_WRITE(priv, 3, 0x0027, 0x0893);	/*10G */

		BDX_MDIO_WRITE(priv, 3, 0x0028, 0xA528);
		BDX_MDIO_WRITE(priv, 3, 0x0029, 0x03);
		BDX_MDIO_WRITE(priv, 1, 0xC30A, 0x06E1);
		BDX_MDIO_WRITE(priv, 1, 0xC300, 0x0002);
		BDX_MDIO_WRITE(priv, 3, 0xE854, 0x00C0);

		/* Dump firmware starting at address 3.8000 */
		for (i = 0, j = 0x8000, a = 3; i < s_fw; i++, j++) {
			if (i == 0x4000) {
				a = 4;
				j = 0x8000;
			}
			if (phy_fw[i] < 0x100) {
				BDX_MDIO_WRITE(priv, a, j, phy_fw[i]);
			}
		}
		BDX_MDIO_WRITE(priv, 3, 0xE854, 0x0040);
		for (i = 60; i; i--) {
			msleep(50);
			j = bdx_mdio_read(priv, 3, port, 0xD7FD);
			if (!(j == 0x10 || j == 0)) {
				break;
			}
		}
		if (!i) {
			dev_err(dev, "PHY init error\n");
		}
		break;
	}
	fwVer01 = bdx_mdio_read(priv, 3, port, 0xD7F3);
	fwVer2 = bdx_mdio_read(priv, 3, port, 0xD7F4);
	fwVer3 = bdx_mdio_read(priv, 3, port, 0xD7F5);
	module = bdx_mdio_read(priv, 3, port, 0xD70C);

	dev_info(dev,
		 "QT2025 FW version %d.%d.%d.%d module type 0x%x\n",
		 ((fwVer01 >> 4) & 0xf), (fwVer01 & 0xf), (fwVer2 & 0xff),
		 (fwVer3 & 0xff), (u32) (module & 0xff));

	priv->link_speed = QT2025_get_link_speed(priv);
	bdx_speed_set(priv, priv->link_speed);

	return 0;

}

/*
 * Module types:
 *
		1 = 10GBASE-LRM
		2 = 10GBASE-SR
		3 = 10GBASE-LR
		4 = SFP+ passive copper direct attach cable
		5 = 1000BASE-SX
		6 = 1000BASE-LX
		7 = Invalid Module Type
		8 = 1000BASE-CX (1GE copper cable)
		9 = 1000BASE-T
		10 (0xA) = 10GBASE-ER
		11 (0xB) = SFP+ active copper direct attach cable
		>11 reserved for future use
*/

u32 QT2025_link_changed(struct bdx_priv *priv)
{
	u32 link = 0;
	struct net_device *ndev = priv->ndev;

	priv->link_speed = QT2025_get_link_speed(priv);
	link = READ_REG(priv, regMAC_LNK_STAT) & MAC_LINK_STAT;
	if (link) {
		netdev_dbg(ndev, "QT2025 link speed is %s\n",
			   (priv->link_speed == SPEED_10000) ? "10G" : "1G");
		link = priv->link_speed;
	} else {
		if (priv->link_loop_cnt++ > LINK_LOOP_MAX) {
			netdev_dbg
			    (ndev,
			     "QT2025 trying to recover link after %d tries\n",
			     LINK_LOOP_MAX);
			/* MAC reset */
			bdx_speed_set(priv, 0);
			bdx_speed_set(priv, priv->link_speed);
			priv->link_loop_cnt = 0;
		}
		netdev_dbg(ndev, "QT2025 no link, setting 1/5 sec timer\n");
		WRITE_REG(priv, 0x5150, 1000000);	/* 1/5 sec timeout */
	}

	return link;

}

void QT2025_leds(struct bdx_priv *priv, enum PHY_LEDS_OP op)
{
	switch (op) {
	case PHY_LEDS_SAVE:
		break;

	case PHY_LEDS_RESTORE:
		break;

	case PHY_LEDS_ON:
		WRITE_REG(priv, regBLNK_LED, 4);
		break;

	case PHY_LEDS_OFF:
		WRITE_REG(priv, regBLNK_LED, 0);
		break;

	default:
		netdev_dbg(priv->ndev, "QT2025_leds() unknown op 0x%x\n", op);
		break;

	}

}

__init enum PHY_TYPE QT2025_register(struct bdx_priv *priv)
{
	priv->phy_ops.mdio_reset = QT2025_mdio_reset;
	priv->phy_ops.link_changed = QT2025_link_changed;
	priv->phy_ops.ledset = QT2025_leds;
	priv->phy_ops.mdio_speed = MDIO_SPEED_6MHZ;
	QT2025_register_settings(priv);

	return PHY_TYPE_QT2025;

}
