/*
 *  arch/ppc/platforms/pmac_feature.c
 *
 *  Copyright (C) 1996-2001 Paul Mackerras (paulus@cs.anu.edu.au)
 *                          Ben. Herrenschmidt (benh@kernel.crashing.org)
 *
 *  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.
 *
 *  TODO:
 *
 *   - Replace mdelay with some schedule loop if possible
 *   - Shorten some obfuscated delays on some routines (like modem
 *     power)
 *   - Refcount some clocks (see darwin)
 *   - Split split split...
 *
 */
#include <linux/config.h>
#include <linux/types.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/spinlock.h>
#include <linux/adb.h>
#include <linux/pmu.h>
#include <linux/ioport.h>
#include <linux/pci.h>
#include <asm/sections.h>
#include <asm/errno.h>
#include <asm/keylargo.h>
#include <asm/uninorth.h>
#include <asm/io.h>
#include <asm/prom.h>
#include <asm/machdep.h>
#include <asm/pmac_feature.h>
#include <asm/dbdma.h>
#include <asm/pci-bridge.h>
#include <asm/pmac_low_i2c.h>

#undef DEBUG_FEATURE

#ifdef DEBUG_FEATURE
#define DBG(fmt...) printk(KERN_DEBUG fmt)
#else
#define DBG(fmt...)
#endif

/*
 * We use a single global lock to protect accesses. Each driver has
 * to take care of its own locking
 */
static DEFINE_SPINLOCK(feature_lock  __pmacdata);

#define LOCK(flags)	spin_lock_irqsave(&feature_lock, flags);
#define UNLOCK(flags)	spin_unlock_irqrestore(&feature_lock, flags);


/*
 * Instance of some macio stuffs
 */
struct macio_chip macio_chips[MAX_MACIO_CHIPS]  __pmacdata;

struct macio_chip* __pmac macio_find(struct device_node* child, int type)
{
	while(child) {
		int	i;

		for (i=0; i < MAX_MACIO_CHIPS && macio_chips[i].of_node; i++)
			if (child == macio_chips[i].of_node &&
			    (!type || macio_chips[i].type == type))
				return &macio_chips[i];
		child = child->parent;
	}
	return NULL;
}
EXPORT_SYMBOL_GPL(macio_find);

static const char* macio_names[] __pmacdata =
{
	"Unknown",
	"Grand Central",
	"OHare",
	"OHareII",
	"Heathrow",
	"Gatwick",
	"Paddington",
	"Keylargo",
	"Pangea",
	"Intrepid",
	"K2"
};



/*
 * Uninorth reg. access. Note that Uni-N regs are big endian
 */

#define UN_REG(r)	(uninorth_base + ((r) >> 2))
#define UN_IN(r)	(in_be32(UN_REG(r)))
#define UN_OUT(r,v)	(out_be32(UN_REG(r), (v)))
#define UN_BIS(r,v)	(UN_OUT((r), UN_IN(r) | (v)))
#define UN_BIC(r,v)	(UN_OUT((r), UN_IN(r) & ~(v)))

static struct device_node* uninorth_node __pmacdata;
static u32* uninorth_base __pmacdata;
static u32 uninorth_rev __pmacdata;
static void *u3_ht;

extern struct device_node *k2_skiplist[2];

/*
 * For each motherboard family, we have a table of functions pointers
 * that handle the various features.
 */

typedef long (*feature_call)(struct device_node* node, long param, long value);

struct feature_table_entry {
	unsigned int	selector;
	feature_call	function;
};

struct pmac_mb_def
{
	const char*			model_string;
	const char*			model_name;
	int				model_id;
	struct feature_table_entry* 	features;
	unsigned long			board_flags;
};
static struct pmac_mb_def pmac_mb __pmacdata;

/*
 * Here are the chip specific feature functions
 */


static long __pmac g5_read_gpio(struct device_node* node, long param, long value)
{
	struct macio_chip* macio = &macio_chips[0];

	return MACIO_IN8(param);
}


static long __pmac g5_write_gpio(struct device_node* node, long param, long value)
{
	struct macio_chip* macio = &macio_chips[0];

	MACIO_OUT8(param, (u8)(value & 0xff));
	return 0;
}

static long __pmac g5_gmac_enable(struct device_node* node, long param, long value)
{
	struct macio_chip* macio = &macio_chips[0];
	unsigned long flags;

	if (node == NULL)
		return -ENODEV;

	LOCK(flags);
	if (value) {
		MACIO_BIS(KEYLARGO_FCR1, K2_FCR1_GMAC_CLK_ENABLE);
		mb();
		k2_skiplist[0] = NULL;
	} else {
		k2_skiplist[0] = node;
		mb();
		MACIO_BIC(KEYLARGO_FCR1, K2_FCR1_GMAC_CLK_ENABLE);
	}
	
	UNLOCK(flags);
	mdelay(1);

	return 0;
}

static long __pmac g5_fw_enable(struct device_node* node, long param, long value)
{
	struct macio_chip* macio = &macio_chips[0];
	unsigned long flags;

	if (node == NULL)
		return -ENODEV;

	LOCK(flags);
	if (value) {
		MACIO_BIS(KEYLARGO_FCR1, K2_FCR1_FW_CLK_ENABLE);
		mb();
		k2_skiplist[1] = NULL;
	} else {
		k2_skiplist[1] = node;
		mb();
		MACIO_BIC(KEYLARGO_FCR1, K2_FCR1_FW_CLK_ENABLE);
	}
	
	UNLOCK(flags);
	mdelay(1);

	return 0;
}

static long __pmac g5_mpic_enable(struct device_node* node, long param, long value)
{
	unsigned long flags;

	if (node->parent == NULL || strcmp(node->parent->name, "u3"))
		return 0;

	LOCK(flags);
	UN_BIS(U3_TOGGLE_REG, U3_MPIC_RESET | U3_MPIC_OUTPUT_ENABLE);
	UNLOCK(flags);

	return 0;
}

static long __pmac g5_eth_phy_reset(struct device_node* node, long param, long value)
{
	struct macio_chip* macio = &macio_chips[0];
	struct device_node *phy;
	int need_reset;

	/*
	 * We must not reset the combo PHYs, only the BCM5221 found in
	 * the iMac G5.
	 */
	phy = of_get_next_child(node, NULL);
	if (!phy)
		return -ENODEV;
	need_reset = device_is_compatible(phy, "B5221");
	of_node_put(phy);
	if (!need_reset)
		return 0;

	/* PHY reset is GPIO 29, not in device-tree unfortunately */
	MACIO_OUT8(K2_GPIO_EXTINT_0 + 29,
		   KEYLARGO_GPIO_OUTPUT_ENABLE | KEYLARGO_GPIO_OUTOUT_DATA);
	/* Thankfully, this is now always called at a time when we can
	 * schedule by sungem.
	 */
	msleep(10);
	MACIO_OUT8(K2_GPIO_EXTINT_0 + 29, 0);

	return 0;
}

static long __pmac g5_i2s_enable(struct device_node *node, long param, long value)
{
	/* Very crude implementation for now */
	struct macio_chip* macio = &macio_chips[0];
	unsigned long flags;

	if (value == 0)
		return 0; /* don't disable yet */

	LOCK(flags);
	MACIO_BIS(KEYLARGO_FCR3, KL3_CLK45_ENABLE | KL3_CLK49_ENABLE |
		  KL3_I2S0_CLK18_ENABLE);
	udelay(10);
	MACIO_BIS(KEYLARGO_FCR1, K2_FCR1_I2S0_CELL_ENABLE |
		  K2_FCR1_I2S0_CLK_ENABLE_BIT | K2_FCR1_I2S0_ENABLE);
	udelay(10);
	MACIO_BIC(KEYLARGO_FCR1, K2_FCR1_I2S0_RESET);
	UNLOCK(flags);
	udelay(10);

	return 0;
}


#ifdef CONFIG_SMP
static long __pmac g5_reset_cpu(struct device_node* node, long param, long value)
{
	unsigned int reset_io = 0;
	unsigned long flags;
	struct macio_chip* macio;
	struct device_node* np;

	macio = &macio_chips[0];
	if (macio->type != macio_keylargo2)
		return -ENODEV;

	np = find_path_device("/cpus");
	if (np == NULL)
		return -ENODEV;
	for (np = np->child; np != NULL; np = np->sibling) {
		u32* num = (u32 *)get_property(np, "reg", NULL);
		u32* rst = (u32 *)get_property(np, "soft-reset", NULL);
		if (num == NULL || rst == NULL)
			continue;
		if (param == *num) {
			reset_io = *rst;
			break;
		}
	}
	if (np == NULL || reset_io == 0)
		return -ENODEV;

	LOCK(flags);
	MACIO_OUT8(reset_io, KEYLARGO_GPIO_OUTPUT_ENABLE);
	(void)MACIO_IN8(reset_io);
	udelay(1);
	MACIO_OUT8(reset_io, 0);
	(void)MACIO_IN8(reset_io);
	UNLOCK(flags);

	return 0;
}
#endif /* CONFIG_SMP */

/*
 * This can be called from pmac_smp so isn't static
 *
 * This takes the second CPU off the bus on dual CPU machines
 * running UP
 */
void __pmac g5_phy_disable_cpu1(void)
{
	UN_OUT(U3_API_PHY_CONFIG_1, 0);
}

static long __pmac generic_get_mb_info(struct device_node* node, long param, long value)
{
	switch(param) {
		case PMAC_MB_INFO_MODEL:
			return pmac_mb.model_id;
		case PMAC_MB_INFO_FLAGS:
			return pmac_mb.board_flags;
		case PMAC_MB_INFO_NAME:			
			/* hack hack hack... but should work */
			*((const char **)value) = pmac_mb.model_name;
			return 0;
	}
	return -EINVAL;
}


/*
 * Table definitions
 */

/* Used on any machine
 */
static struct feature_table_entry any_features[]  __pmacdata = {
	{ PMAC_FTR_GET_MB_INFO,		generic_get_mb_info },
	{ 0, NULL }
};

/* G5 features
 */
static struct feature_table_entry g5_features[]  __pmacdata = {
	{ PMAC_FTR_GMAC_ENABLE,		g5_gmac_enable },
	{ PMAC_FTR_1394_ENABLE,		g5_fw_enable },
	{ PMAC_FTR_ENABLE_MPIC,		g5_mpic_enable },
	{ PMAC_FTR_READ_GPIO,		g5_read_gpio },
	{ PMAC_FTR_WRITE_GPIO,		g5_write_gpio },
	{ PMAC_FTR_GMAC_PHY_RESET,	g5_eth_phy_reset },
	{ PMAC_FTR_SOUND_CHIP_ENABLE,	g5_i2s_enable },
#ifdef CONFIG_SMP
	{ PMAC_FTR_RESET_CPU,		g5_reset_cpu },
#endif /* CONFIG_SMP */
	{ 0, NULL }
};

static struct pmac_mb_def pmac_mb_defs[] __pmacdata = {
	{	"PowerMac7,2",			"PowerMac G5",
		PMAC_TYPE_POWERMAC_G5,		g5_features,
		0,
	},
	{	"PowerMac7,3",			"PowerMac G5",
		PMAC_TYPE_POWERMAC_G5,		g5_features,
		0,
	},
	{	"PowerMac8,1",			"iMac G5",
		PMAC_TYPE_IMAC_G5,		g5_features,
		0,
	},
	{	"PowerMac9,1",			"PowerMac G5",
		PMAC_TYPE_POWERMAC_G5_U3L,	g5_features,
		0,
	},
	{       "RackMac3,1",                   "XServe G5",
		PMAC_TYPE_XSERVE_G5,		g5_features,
		0,
	},
};

/*
 * The toplevel feature_call callback
 */
long __pmac pmac_do_feature_call(unsigned int selector, ...)
{
	struct device_node* node;
	long param, value;
	int i;
	feature_call func = NULL;
	va_list args;

	if (pmac_mb.features)
		for (i=0; pmac_mb.features[i].function; i++)
			if (pmac_mb.features[i].selector == selector) {
				func = pmac_mb.features[i].function;
				break;
			}
	if (!func)
		for (i=0; any_features[i].function; i++)
			if (any_features[i].selector == selector) {
				func = any_features[i].function;
				break;
			}
	if (!func)
		return -ENODEV;

	va_start(args, selector);
	node = (struct device_node*)va_arg(args, void*);
	param = va_arg(args, long);
	value = va_arg(args, long);
	va_end(args);

	return func(node, param, value);
}

static int __init probe_motherboard(void)
{
	int i;
	struct macio_chip* macio = &macio_chips[0];
	const char* model = NULL;
	struct device_node *dt;

	/* Lookup known motherboard type in device-tree. First try an
	 * exact match on the "model" property, then try a "compatible"
	 * match is none is found.
	 */
	dt = find_devices("device-tree");
	if (dt != NULL)
		model = (const char *) get_property(dt, "model", NULL);
	for(i=0; model && i<(sizeof(pmac_mb_defs)/sizeof(struct pmac_mb_def)); i++) {
	    if (strcmp(model, pmac_mb_defs[i].model_string) == 0) {
		pmac_mb = pmac_mb_defs[i];
		goto found;
	    }
	}
	for(i=0; i<(sizeof(pmac_mb_defs)/sizeof(struct pmac_mb_def)); i++) {
	    if (machine_is_compatible(pmac_mb_defs[i].model_string)) {
		pmac_mb = pmac_mb_defs[i];
		goto found;
	    }
	}

	/* Fallback to selection depending on mac-io chip type */
	switch(macio->type) {
	case macio_keylargo2:
		pmac_mb.model_id = PMAC_TYPE_UNKNOWN_K2;
		pmac_mb.model_name = "Unknown K2-based";
	    	pmac_mb.features = g5_features;
		
	default:
	    	return -ENODEV;
	}
found:
	/* Check for "mobile" machine */
	if (model && (strncmp(model, "PowerBook", 9) == 0
		   || strncmp(model, "iBook", 5) == 0))
		pmac_mb.board_flags |= PMAC_MB_MOBILE;


	printk(KERN_INFO "PowerMac motherboard: %s\n", pmac_mb.model_name);
	return 0;
}

/* Initialize the Core99 UniNorth host bridge and memory controller
 */
static void __init probe_uninorth(void)
{
	uninorth_node = of_find_node_by_name(NULL, "u3");
	if (uninorth_node && uninorth_node->n_addrs > 0) {
		/* Small hack until I figure out if parsing in prom.c is correct. I should
		 * get rid of those pre-parsed junk anyway
		 */
		unsigned long address = uninorth_node->addrs[0].address;
		uninorth_base = ioremap(address, 0x40000);
		uninorth_rev = in_be32(UN_REG(UNI_N_VERSION));
		u3_ht = ioremap(address + U3_HT_CONFIG_BASE, 0x1000);
	} else
		uninorth_node = NULL;

	if (!uninorth_node)
		return;

	printk(KERN_INFO "Found U3 memory controller & host bridge, revision: %d\n",
	       uninorth_rev);
	printk(KERN_INFO "Mapped at 0x%08lx\n", (unsigned long)uninorth_base);

}

static void __init probe_one_macio(const char* name, const char* compat, int type)
{
	struct device_node*	node;
	int			i;
	volatile u32*		base;
	u32*			revp;

	node = find_devices(name);
	if (!node || !node->n_addrs)
		return;
	if (compat)
		do {
			if (device_is_compatible(node, compat))
				break;
			node = node->next;
		} while (node);
	if (!node)
		return;
	for(i=0; i<MAX_MACIO_CHIPS; i++) {
		if (!macio_chips[i].of_node)
			break;
		if (macio_chips[i].of_node == node)
			return;
	}
	if (i >= MAX_MACIO_CHIPS) {
		printk(KERN_ERR "pmac_feature: Please increase MAX_MACIO_CHIPS !\n");
		printk(KERN_ERR "pmac_feature: %s skipped\n", node->full_name);
		return;
	}
	base = (volatile u32*)ioremap(node->addrs[0].address, node->addrs[0].size);
	if (!base) {
		printk(KERN_ERR "pmac_feature: Can't map mac-io chip !\n");
		return;
	}
	if (type == macio_keylargo) {
		u32* did = (u32 *)get_property(node, "device-id", NULL);
		if (*did == 0x00000025)
			type = macio_pangea;
		if (*did == 0x0000003e)
			type = macio_intrepid;
	}
	macio_chips[i].of_node	= node;
	macio_chips[i].type	= type;
	macio_chips[i].base	= base;
	macio_chips[i].flags	= MACIO_FLAG_SCCB_ON | MACIO_FLAG_SCCB_ON;
	macio_chips[i].name 	= macio_names[type];
	revp = (u32 *)get_property(node, "revision-id", NULL);
	if (revp)
		macio_chips[i].rev = *revp;
	printk(KERN_INFO "Found a %s mac-io controller, rev: %d, mapped at 0x%p\n",
		macio_names[type], macio_chips[i].rev, macio_chips[i].base);
}

static int __init
probe_macios(void)
{
	probe_one_macio("mac-io", "K2-Keylargo", macio_keylargo2);

	macio_chips[0].lbus.index = 0;
	macio_chips[1].lbus.index = 1;

	return (macio_chips[0].of_node == NULL) ? -ENODEV : 0;
}

static void __init
set_initial_features(void)
{
	struct device_node *np;

	if (macio_chips[0].type == macio_keylargo2) {
#ifndef CONFIG_SMP
		/* On SMP machines running UP, we have the second CPU eating
		 * bus cycles. We need to take it off the bus. This is done
		 * from pmac_smp for SMP kernels running on one CPU
		 */
		np = of_find_node_by_type(NULL, "cpu");
		if (np != NULL)
			np = of_find_node_by_type(np, "cpu");
		if (np != NULL) {
			g5_phy_disable_cpu1();
			of_node_put(np);
		}
#endif /* CONFIG_SMP */
		/* Enable GMAC for now for PCI probing. It will be disabled
		 * later on after PCI probe
		 */
		np = of_find_node_by_name(NULL, "ethernet");
		while(np) {
			if (device_is_compatible(np, "K2-GMAC"))
				g5_gmac_enable(np, 0, 1);
			np = of_find_node_by_name(np, "ethernet");
		}

		/* Enable FW before PCI probe. Will be disabled later on
		 * Note: We should have a batter way to check that we are
		 * dealing with uninorth internal cell and not a PCI cell
		 * on the external PCI. The code below works though.
		 */
		np = of_find_node_by_name(NULL, "firewire");
		while(np) {
			if (device_is_compatible(np, "pci106b,5811")) {
				macio_chips[0].flags |= MACIO_FLAG_FW_SUPPORTED;
				g5_fw_enable(np, 0, 1);
			}
			np = of_find_node_by_name(np, "firewire");
		}
	}
}

void __init
pmac_feature_init(void)
{
	/* Detect the UniNorth memory controller */
	probe_uninorth();

	/* Probe mac-io controllers */
	if (probe_macios()) {
		printk(KERN_WARNING "No mac-io chip found\n");
		return;
	}

	/* Setup low-level i2c stuffs */
	pmac_init_low_i2c();

	/* Probe machine type */
	if (probe_motherboard())
		printk(KERN_WARNING "Unknown PowerMac !\n");

	/* Set some initial features (turn off some chips that will
	 * be later turned on)
	 */
	set_initial_features();
}

int __init pmac_feature_late_init(void)
{
#if 0
	struct device_node* np;

	/* Request some resources late */
	if (uninorth_node)
		request_OF_resource(uninorth_node, 0, NULL);
	np = find_devices("hammerhead");
	if (np)
		request_OF_resource(np, 0, NULL);
	np = find_devices("interrupt-controller");
	if (np)
		request_OF_resource(np, 0, NULL);
#endif
	return 0;
}

device_initcall(pmac_feature_late_init);

#if 0
static void dump_HT_speeds(char *name, u32 cfg, u32 frq)
{
	int	freqs[16] = { 200,300,400,500,600,800,1000,0,0,0,0,0,0,0,0,0 };
	int	bits[8] = { 8,16,0,32,2,4,0,0 };
	int	freq = (frq >> 8) & 0xf;

	if (freqs[freq] == 0)
		printk("%s: Unknown HT link frequency %x\n", name, freq);
	else
		printk("%s: %d MHz on main link, (%d in / %d out) bits width\n",
		       name, freqs[freq],
		       bits[(cfg >> 28) & 0x7], bits[(cfg >> 24) & 0x7]);
}
#endif

void __init pmac_check_ht_link(void)
{
#if 0 /* Disabled for now */
	u32	ufreq, freq, ucfg, cfg;
	struct device_node *pcix_node;
	u8  	px_bus, px_devfn;
	struct pci_controller *px_hose;

	(void)in_be32(u3_ht + U3_HT_LINK_COMMAND);
	ucfg = cfg = in_be32(u3_ht + U3_HT_LINK_CONFIG);
	ufreq = freq = in_be32(u3_ht + U3_HT_LINK_FREQ);
	dump_HT_speeds("U3 HyperTransport", cfg, freq);

	pcix_node = of_find_compatible_node(NULL, "pci", "pci-x");
	if (pcix_node == NULL) {
		printk("No PCI-X bridge found\n");
		return;
	}
	px_hose = pcix_node->phb;
	px_bus = pcix_node->busno;
	px_devfn = pcix_node->devfn;
	
	early_read_config_dword(px_hose, px_bus, px_devfn, 0xc4, &cfg);
	early_read_config_dword(px_hose, px_bus, px_devfn, 0xcc, &freq);
	dump_HT_speeds("PCI-X HT Uplink", cfg, freq);
	early_read_config_dword(px_hose, px_bus, px_devfn, 0xc8, &cfg);
	early_read_config_dword(px_hose, px_bus, px_devfn, 0xd0, &freq);
	dump_HT_speeds("PCI-X HT Downlink", cfg, freq);
#endif
}

/*
 * Early video resume hook
 */

static void (*pmac_early_vresume_proc)(void *data) __pmacdata;
static void *pmac_early_vresume_data __pmacdata;

void pmac_set_early_video_resume(void (*proc)(void *data), void *data)
{
	if (_machine != _MACH_Pmac)
		return;
	preempt_disable();
	pmac_early_vresume_proc = proc;
	pmac_early_vresume_data = data;
	preempt_enable();
}
EXPORT_SYMBOL(pmac_set_early_video_resume);


/*
 * AGP related suspend/resume code
 */

static struct pci_dev *pmac_agp_bridge __pmacdata;
static int (*pmac_agp_suspend)(struct pci_dev *bridge) __pmacdata;
static int (*pmac_agp_resume)(struct pci_dev *bridge) __pmacdata;

void __pmac pmac_register_agp_pm(struct pci_dev *bridge,
				 int (*suspend)(struct pci_dev *bridge),
				 int (*resume)(struct pci_dev *bridge))
{
	if (suspend || resume) {
		pmac_agp_bridge = bridge;
		pmac_agp_suspend = suspend;
		pmac_agp_resume = resume;
		return;
	}
	if (bridge != pmac_agp_bridge)
		return;
	pmac_agp_suspend = pmac_agp_resume = NULL;
	return;
}
EXPORT_SYMBOL(pmac_register_agp_pm);

void __pmac pmac_suspend_agp_for_card(struct pci_dev *dev)
{
	if (pmac_agp_bridge == NULL || pmac_agp_suspend == NULL)
		return;
	if (pmac_agp_bridge->bus != dev->bus)
		return;
	pmac_agp_suspend(pmac_agp_bridge);
}
EXPORT_SYMBOL(pmac_suspend_agp_for_card);

void __pmac pmac_resume_agp_for_card(struct pci_dev *dev)
{
	if (pmac_agp_bridge == NULL || pmac_agp_resume == NULL)
		return;
	if (pmac_agp_bridge->bus != dev->bus)
		return;
	pmac_agp_resume(pmac_agp_bridge);
}
EXPORT_SYMBOL(pmac_resume_agp_for_card);
