/*
 *  Copyright (c) 2004, 2005 Zultys Technologies
 *  Eugene Surovegin <eugene.surovegin@zultys.com> or <ebs@ebshome.net>
 */
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>

#include "internal.h"

/* Ethtool get_regs complex data.
 * we want to get not just EMAC registers, but also MAL, ZMII, RGMII, TAH
 * when available.
 *
 * Returned BLOB consists of the ibm_emac_ethtool_regs_hdr,
 * MAL registers, EMAC registers and optional ZMII, RGMII, TAH registers.
 * Each register component is preceded with emac_ethtool_regs_subhdr.
 * Order of the optional headers follows their relative bit posititions
 * in emac_ethtool_regs_hdr.components
 */
#define EMAC_ETHTOOL_REGS_ZMII		0x00000001
#define EMAC_ETHTOOL_REGS_RGMII		0x00000002
#define EMAC_ETHTOOL_REGS_TAH		0x00000004

#define EMAC_VERSION			3
#define EMAC4_VERSION			4
#define EMAC4SYNC_VERSION		5

struct emac_ethtool_regs_hdr {
	u32 components;
};

struct emac_ethtool_regs_subhdr {
	u32 version;
	u32 index;
};

struct emac_regs {
	/* Common registers across all EMAC implementations. */
	u32 mr0;			/* Special 	*/
	u32 mr1;			/* Reset 	*/
	u32 tmr0;			/* Special 	*/
	u32 tmr1;			/* Special 	*/
	u32 rmr;			/* Reset 	*/
	u32 isr;			/* Always 	*/
	u32 iser;			/* Reset 	*/
	u32 iahr;			/* Reset, R, T 	*/
	u32 ialr;			/* Reset, R, T 	*/
	u32 vtpid;			/* Reset, R, T 	*/
	u32 vtci;			/* Reset, R, T 	*/
	u32 ptr;			/* Reset,    T 	*/
	union {
		/* Registers unique to EMAC4 implementations */
		struct {
			u32 iaht1;	/* Reset, R	*/
			u32 iaht2;	/* Reset, R	*/
			u32 iaht3;	/* Reset, R	*/
			u32 iaht4;	/* Reset, R	*/
			u32 gaht1;	/* Reset, R	*/
			u32 gaht2;	/* Reset, R	*/
			u32 gaht3;	/* Reset, R	*/
			u32 gaht4;	/* Reset, R	*/
		} emac4;
		/* Registers unique to EMAC4SYNC implementations */
		struct {
			u32 mahr;	/* Reset, R, T  */
			u32 malr;	/* Reset, R, T  */
			u32 mmahr;	/* Reset, R, T  */
			u32 mmalr;	/* Reset, R, T  */
			u32 rsvd0[4];
		} emac4sync;
	} u0;
	/* Common registers across all EMAC implementations. */
	u32 lsah;
	u32 lsal;
	u32 ipgvr;			/* Reset,    T 	*/
	u32 stacr;			/* Special 	*/
	u32 trtr;			/* Special 	*/
	u32 rwmr;			/* Reset 	*/
	u32 octx;
	u32 ocrx;
	union {
		/* Registers unique to EMAC4 implementations */
		struct {
			u32 ipcr;
		} emac4;
		/* Registers unique to EMAC4SYNC implementations */
		struct {
			u32 rsvd1;
			u32 revid;
			u32 rsvd2[2];
			u32 iaht1;	/* Reset, R     */
			u32 iaht2;	/* Reset, R     */
			u32 iaht3;	/* Reset, R     */
			u32 iaht4;	/* Reset, R     */
			u32 iaht5;	/* Reset, R     */
			u32 iaht6;	/* Reset, R     */
			u32 iaht7;	/* Reset, R     */
			u32 iaht8;	/* Reset, R     */
			u32 gaht1;	/* Reset, R     */
			u32 gaht2;	/* Reset, R     */
			u32 gaht3;	/* Reset, R     */
			u32 gaht4;	/* Reset, R     */
			u32 gaht5;	/* Reset, R     */
			u32 gaht6;	/* Reset, R     */
			u32 gaht7;	/* Reset, R     */
			u32 gaht8;	/* Reset, R     */
			u32 tpc;	/* Reset, T     */
		} emac4sync;
	} u1;
};

struct mal_regs {
	u32 tx_count;
	u32 rx_count;

	u32 cfg;
	u32 esr;
	u32 ier;
	u32 tx_casr;
	u32 tx_carr;
	u32 tx_eobisr;
	u32 tx_deir;
	u32 rx_casr;
	u32 rx_carr;
	u32 rx_eobisr;
	u32 rx_deir;
	u32 tx_ctpr[32];
	u32 rx_ctpr[32];
	u32 rcbs[32];
};

struct zmii_regs {
	u32 fer;
	u32 ssr;
	u32 smiisr;
};

struct rgmii_regs {
	u32 fer;
	u32 ssr;
};

struct tah_regs {
	u32 revid;
	u32 pad[3];
	u32 mr;
	u32 ssr0;
	u32 ssr1;
	u32 ssr2;
	u32 ssr3;
	u32 ssr4;
	u32 ssr5;
	u32 tsr;
};

static void *print_emac_regs(void *buf)
{
	struct emac_ethtool_regs_subhdr *hdr = buf;
	struct emac_regs *p = (struct emac_regs *)(hdr + 1);
	void *res = p + 1;

	if (!((hdr->version == EMAC_VERSION) ||
		(hdr->version == EMAC4_VERSION) ||
		(hdr->version == EMAC4SYNC_VERSION)))
	{
		printf("This driver version doesn't support information\n"
			" output for EMAC area, please update it or use older\n"
			" ethtool version\n");
		return res;
	}

	printf("EMAC%d Registers\n", hdr->index);
	printf("-----------------\n");
	printf("MR0   = 0x%08x MR1  = 0x%08x RMR = 0x%08x\n"
	       "ISR   = 0x%08x ISER = 0x%08x\n"
	       "TMR0  = 0x%08x TMR1 = 0x%08x\n"
	       "TRTR  = 0x%08x RWMR = 0x%08x\n"
	       "IAR   = %04x%08x\n"
	       "LSA   = %04x%08x\n"
	       "VTPID = 0x%04x VTCI = 0x%04x\n"
	       "IPGVR = 0x%04x STACR = 0x%08x\n"
	       "OCTX  = 0x%08x OCRX = 0x%08x\n",
	       p->mr0, p->mr1, p->rmr,
	       p->isr, p->iser,
	       p->tmr0, p->tmr1,
	       p->trtr, p->rwmr,
	       p->iahr, p->ialr,
	       p->lsah, p->lsal,
	       p->vtpid, p->vtci,
	       p->ipgvr, p->stacr, p->octx, p->ocrx);

	if (hdr->version == EMAC4SYNC_VERSION) {
		printf("MAHR  = 0x%08x MALR  = 0x%08x MMAHR = 0x%08x\n"
			"MMALR  = 0x%08x REVID  = 0x%08x\n",
			p->u0.emac4sync.mahr, p->u0.emac4sync.malr,
			p->u0.emac4sync.mmahr, p->u0.emac4sync.mmalr,
			p->u1.emac4sync.revid);

		printf("IAHT  = 0x%04x 0x%04x 0x%04x 0x%04x\n",
			p->u1.emac4sync.iaht1, p->u1.emac4sync.iaht2,
			p->u1.emac4sync.iaht3, p->u1.emac4sync.iaht4);
		printf("        0x%04x 0x%04x 0x%04x 0x%04x\n",
			p->u1.emac4sync.iaht5, p->u1.emac4sync.iaht6,
			p->u1.emac4sync.iaht7, p->u1.emac4sync.iaht8);


		printf("GAHT  = 0x%04x 0x%04x 0x%04x 0x%04x\n",
			p->u1.emac4sync.gaht1, p->u1.emac4sync.gaht2,
			p->u1.emac4sync.gaht3, p->u1.emac4sync.gaht4);
		printf("        0x%04x 0x%04x 0x%04x 0x%04x\n\n",
			p->u1.emac4sync.gaht5, p->u1.emac4sync.gaht6,
			p->u1.emac4sync.gaht7, p->u1.emac4sync.gaht8);

	} else if (hdr->version == EMAC4_VERSION) {
		printf("IAHT  = 0x%04x 0x%04x 0x%04x 0x%04x\n",
			p->u0.emac4.iaht1, p->u0.emac4.iaht2,
			p->u0.emac4.iaht3, p->u0.emac4.iaht4);

		printf("GAHT  = 0x%04x 0x%04x 0x%04x 0x%04x\n",
			p->u0.emac4.gaht1, p->u0.emac4.gaht2,
			p->u0.emac4.gaht3, p->u0.emac4.gaht4);

		printf(" IPCR = 0x%08x\n\n", p->u1.emac4.ipcr);
	} else if (hdr->version == EMAC_VERSION) {
		printf("IAHT  = 0x%04x 0x%04x 0x%04x 0x%04x\n",
			p->u0.emac4.iaht1, p->u0.emac4.iaht2,
			p->u0.emac4.iaht3, p->u0.emac4.iaht4);

		printf("GAHT  = 0x%04x 0x%04x 0x%04x 0x%04x\n",
			p->u0.emac4.gaht1, p->u0.emac4.gaht2,
			p->u0.emac4.gaht3, p->u0.emac4.gaht4);
	}
	return res;
}

static void *print_mal_regs(void *buf)
{
	struct emac_ethtool_regs_subhdr *hdr = buf;
	struct mal_regs *p = (struct mal_regs *)(hdr + 1);
	unsigned int i;

	printf("MAL%d Registers\n", hdr->index);
	printf("-----------------\n");
	printf("CFG = 0x%08x ESR = 0x%08x IER = 0x%08x\n"
	       "TX|CASR = 0x%08x CARR = 0x%08x EOBISR = 0x%08x DEIR = 0x%08x\n"
	       "RX|CASR = 0x%08x CARR = 0x%08x EOBISR = 0x%08x DEIR = 0x%08x\n",
	       p->cfg, p->esr, p->ier,
	       p->tx_casr, p->tx_carr, p->tx_eobisr, p->tx_deir,
	       p->rx_casr, p->rx_carr, p->rx_eobisr, p->rx_deir);

	printf("TX|");
	for (i = 0; i < p->tx_count; ++i) {
		if (i && !(i % 4))
			printf("\n   ");
		printf("CTP%d = 0x%08x ", i, p->tx_ctpr[i]);
	}
	printf("\nRX|");
	for (i = 0; i < p->rx_count; ++i) {
		if (i && !(i % 4))
			printf("\n   ");
		printf("CTP%d = 0x%08x ", i, p->rx_ctpr[i]);
	}
	printf("\n   ");
	for (i = 0; i < p->rx_count; ++i) {
		u32 r = p->rcbs[i];
		if (i && !(i % 3))
			printf("\n   ");
		printf("RCBS%d = 0x%08x (%d) ", i, r, r * 16);
	}
	printf("\n\n");
	return p + 1;
}

static void *print_zmii_regs(void *buf)
{
	struct emac_ethtool_regs_subhdr *hdr = buf;
	struct zmii_regs *p = (struct zmii_regs *)(hdr + 1);

	printf("ZMII%d Registers\n", hdr->index);
	printf("-----------------\n");
	printf("FER    = %08x SSR = %08x\n"
	       "SMIISR = %08x\n\n", p->fer, p->ssr, p->smiisr);

	return p + 1;
}

static void *print_rgmii_regs(void *buf)
{
	struct emac_ethtool_regs_subhdr *hdr = buf;
	struct rgmii_regs *p = (struct rgmii_regs *)(hdr + 1);

	printf("RGMII%d Registers\n", hdr->index);
	printf("-----------------\n");
	printf("FER    = %08x SSR = %08x\n\n", p->fer, p->ssr);

	return p + 1;
}

static void *print_tah_regs(void *buf)
{
	struct emac_ethtool_regs_subhdr *hdr = buf;
	struct tah_regs *p = (struct tah_regs *)(hdr + 1);

	printf("TAH%d Registers\n", hdr->index);
	printf("-----------------\n");

	printf("REVID = %08x MR = %08x TSR = %08x\n"
	       "SSR0  = %08x SSR1 = %08x SSR2 = %08x\n"
	       "SSR3  = %08x SSR4 = %08x SSR5 = %08x\n\n",
	       p->revid, p->mr, p->tsr,
	       p->ssr0, p->ssr1, p->ssr2, p->ssr3, p->ssr4, p->ssr5);

	return p + 1;
}

int ibm_emac_dump_regs(struct ethtool_drvinfo *info __maybe_unused,
		       struct ethtool_regs *regs)
{
	struct emac_ethtool_regs_hdr *hdr =
	    (struct emac_ethtool_regs_hdr *)regs->data;
	void *buf = hdr + 1;

	buf = print_mal_regs(buf);
	buf = print_emac_regs(buf);
	if (hdr->components & EMAC_ETHTOOL_REGS_ZMII)
		buf = print_zmii_regs(buf);
	if (hdr->components & EMAC_ETHTOOL_REGS_RGMII)
		buf = print_rgmii_regs(buf);
	if (hdr->components & EMAC_ETHTOOL_REGS_TAH)
		print_tah_regs(buf);

	return 0;
}