/*
 * drivers/net/ibm_emac/ibm_emac_rgmii.c
 *
 * Driver for PowerPC 4xx on-chip ethernet controller, RGMII bridge support.
 *
 * Copyright (c) 2004, 2005 Zultys Technologies.
 * Eugene Surovegin <eugene.surovegin@zultys.com> or <ebs@ebshome.net>
 *
 * Based on original work by
 * 	Matt Porter <mporter@kernel.crashing.org>
 * 	Copyright 2004 MontaVista Software, Inc.
 * 
 * This program is free software; you can redistribute  it and/or modify it
 * under  the terms of  the GNU General  Public License as published by the
 * Free Software Foundation;  either version 2 of the  License, or (at your
 * option) any later version.
 *
 */
#include <linux/kernel.h>
#include <linux/ethtool.h>
#include <asm/io.h>

#include "ibm_emac_core.h"
#include "ibm_emac_debug.h"

/* RGMIIx_FER */
#define RGMII_FER_MASK(idx)	(0x7 << ((idx) * 4))
#define RGMII_FER_RTBI(idx)	(0x4 << ((idx) * 4))
#define RGMII_FER_RGMII(idx)	(0x5 << ((idx) * 4))
#define RGMII_FER_TBI(idx)	(0x6 << ((idx) * 4))
#define RGMII_FER_GMII(idx)	(0x7 << ((idx) * 4))

/* RGMIIx_SSR */
#define RGMII_SSR_MASK(idx)	(0x7 << ((idx) * 8))
#define RGMII_SSR_100(idx)	(0x2 << ((idx) * 8))
#define RGMII_SSR_1000(idx)	(0x4 << ((idx) * 8))

/* RGMII bridge supports only GMII/TBI and RGMII/RTBI PHYs */
static inline int rgmii_valid_mode(int phy_mode)
{
	return  phy_mode == PHY_MODE_GMII ||
		phy_mode == PHY_MODE_RGMII ||
		phy_mode == PHY_MODE_TBI ||
		phy_mode == PHY_MODE_RTBI;
}

static inline const char *rgmii_mode_name(int mode)
{
	switch (mode) {
	case PHY_MODE_RGMII:
		return "RGMII";
	case PHY_MODE_TBI:
		return "TBI";
	case PHY_MODE_GMII:
		return "GMII";
	case PHY_MODE_RTBI:
		return "RTBI";
	default:
		BUG();
	}
}

static inline u32 rgmii_mode_mask(int mode, int input)
{
	switch (mode) {
	case PHY_MODE_RGMII:
		return RGMII_FER_RGMII(input);
	case PHY_MODE_TBI:
		return RGMII_FER_TBI(input);
	case PHY_MODE_GMII:
		return RGMII_FER_GMII(input);
	case PHY_MODE_RTBI:
		return RGMII_FER_RTBI(input);
	default:
		BUG();
	}
}

static int __init rgmii_init(struct ocp_device *ocpdev, int input, int mode)
{
	struct ibm_ocp_rgmii *dev = ocp_get_drvdata(ocpdev);
	struct rgmii_regs *p;

	RGMII_DBG("%d: init(%d, %d)" NL, ocpdev->def->index, input, mode);

	if (!dev) {
		dev = kzalloc(sizeof(struct ibm_ocp_rgmii), GFP_KERNEL);
		if (!dev) {
			printk(KERN_ERR
			       "rgmii%d: couldn't allocate device structure!\n",
			       ocpdev->def->index);
			return -ENOMEM;
		}

		p = (struct rgmii_regs *)ioremap(ocpdev->def->paddr,
						 sizeof(struct rgmii_regs));
		if (!p) {
			printk(KERN_ERR
			       "rgmii%d: could not ioremap device registers!\n",
			       ocpdev->def->index);
			kfree(dev);
			return -ENOMEM;
		}

		dev->base = p;
		ocp_set_drvdata(ocpdev, dev);

		/* Disable all inputs by default */
		out_be32(&p->fer, 0);
	} else
		p = dev->base;

	/* Enable this input */
	out_be32(&p->fer, in_be32(&p->fer) | rgmii_mode_mask(mode, input));

	printk(KERN_NOTICE "rgmii%d: input %d in %s mode\n",
	       ocpdev->def->index, input, rgmii_mode_name(mode));

	++dev->users;
	return 0;
}

int __init rgmii_attach(void *emac)
{
	struct ocp_enet_private *dev = emac;
	struct ocp_func_emac_data *emacdata = dev->def->additions;

	/* Check if we need to attach to a RGMII */
	if (emacdata->rgmii_idx >= 0 && rgmii_valid_mode(emacdata->phy_mode)) {
		dev->rgmii_input = emacdata->rgmii_mux;
		dev->rgmii_dev =
		    ocp_find_device(OCP_VENDOR_IBM, OCP_FUNC_RGMII,
				    emacdata->rgmii_idx);
		if (!dev->rgmii_dev) {
			printk(KERN_ERR "emac%d: unknown rgmii%d!\n",
			       dev->def->index, emacdata->rgmii_idx);
			return -ENODEV;
		}
		if (rgmii_init
		    (dev->rgmii_dev, dev->rgmii_input, emacdata->phy_mode)) {
			printk(KERN_ERR
			       "emac%d: rgmii%d initialization failed!\n",
			       dev->def->index, emacdata->rgmii_idx);
			return -ENODEV;
		}
	}
	return 0;
}

void rgmii_set_speed(struct ocp_device *ocpdev, int input, int speed)
{
	struct ibm_ocp_rgmii *dev = ocp_get_drvdata(ocpdev);
	u32 ssr = in_be32(&dev->base->ssr) & ~RGMII_SSR_MASK(input);

	RGMII_DBG("%d: speed(%d, %d)" NL, ocpdev->def->index, input, speed);

	if (speed == SPEED_1000)
		ssr |= RGMII_SSR_1000(input);
	else if (speed == SPEED_100)
		ssr |= RGMII_SSR_100(input);

	out_be32(&dev->base->ssr, ssr);
}

void __rgmii_fini(struct ocp_device *ocpdev, int input)
{
	struct ibm_ocp_rgmii *dev = ocp_get_drvdata(ocpdev);
	BUG_ON(!dev || dev->users == 0);

	RGMII_DBG("%d: fini(%d)" NL, ocpdev->def->index, input);

	/* Disable this input */
	out_be32(&dev->base->fer,
		 in_be32(&dev->base->fer) & ~RGMII_FER_MASK(input));

	if (!--dev->users) {
		/* Free everything if this is the last user */
		ocp_set_drvdata(ocpdev, NULL);
		iounmap((void *)dev->base);
		kfree(dev);
	}
}

int __rgmii_get_regs_len(struct ocp_device *ocpdev)
{
	return sizeof(struct emac_ethtool_regs_subhdr) +
	    sizeof(struct rgmii_regs);
}

void *rgmii_dump_regs(struct ocp_device *ocpdev, void *buf)
{
	struct ibm_ocp_rgmii *dev = ocp_get_drvdata(ocpdev);
	struct emac_ethtool_regs_subhdr *hdr = buf;
	struct rgmii_regs *regs = (struct rgmii_regs *)(hdr + 1);

	hdr->version = 0;
	hdr->index = ocpdev->def->index;
	memcpy_fromio(regs, dev->base, sizeof(struct rgmii_regs));
	return regs + 1;
}
