/*
 *	Drivers for the IBM 20H2999 found in the IBM thinkpad series
 *	machines. 
 *
 *	This driver was done without documentation from IBM 
 *
 *              _
 *             { }
 *	       | |		All reverse engineering done 
 *	       | |		in accordance with 92/250/EEC
 *	    .-.! !.-.		and Copyright (Computer Programs)
 *	  .-!  ! !  !.-.	Regulations 1992 (S.I. 1992 No. 3233)
 *	  ! !       !  ;
 *         \	      ;	
 *	   \	     ;	
 *	    !       :
 *	    !       |
 *	    |       |
 *
 *
 *	Various other IBM's tried to obtain docs but failed. For that
 *	reason we only support warm not hot undocking at the moment.
 *
 *	Known bugs:
 *		Sometimes we hang with an IRQ storm. I don't know what
 *			deals with the IRQ disables yet. (Hot dock)
 *		Sometimes busmastering (and maybe IRQs) don't come back
 *		(Seems to be a buffering issue for hot dock)
 *
 *	Yet to do:
 *		ISA is not yet handled (oh god help us)
 *		Instead of saving/restoring pci devices we should
 *			re-enumerate that subtree so you can change devices
 *			(That also deals with stale save problems)
 *		We need to do a proper warm save/restore interface
 *		Bridged cards don't yet work
 *
 *	Usage:
 *		Load module
 *		Pray
 *
 */

#include <linux/config.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/kernel.h>
#include <linux/smp_lock.h>
#include <linux/completion.h>
#include <linux/delay.h>

#include "pci_hotplug.h"
#include "tp600.h"

static struct h2999_dev *testdev;

/**
 *	pci_save_slot	-	save slot PCI data
 *	@slot: slot to save
 *
 *	Save the slot data from a PCI device
 */
 
static void pci_save_slot(struct h2999_slot *s)
{
	int i, n;
	
	for(i=0;i<8;i++)
	{
		struct pci_dev *p = pci_find_slot(s->dev->hotplug_bus, PCI_DEVFN(s->slotid, i));
		s->pci[i] = p;
		if(p)
		{
			for(n = 0; n < 64; n++)
				pci_read_config_dword(p, n * 4, &s->save[i][n]);
//			printk("Saved %02X:%02X.%X\n",
//				s->dev->hotplug_bus, s->slotid, i);
		}
	}
}

static void pci_restore_slot(struct h2999_slot *s)
{
	int i,n;

	for(i = 0 ; i < 8; i++)
	{
		if(s->pci[i])
		{
			pci_set_power_state(s->pci[i], 0);

			for(n = 0; n < 54; n++)
				if(n!=1)
					pci_write_config_dword(s->pci[i], n * 4, s->save[i][n]);
			pci_write_config_dword(s->pci[i], 4, s->save[i][1]);
//			printk("Restored %02X:%02X.%X\n",
//				s->dev->hotplug_bus, s->slotid, i);
		}
	}
}
		
/**
 *	slot_enable	-	enable H2999 slot
 *	@slot: slot to enable
 *
 *	Enable a slot. Its not actually clear what this means with
 *	a hot dock. We can certainly 'discover' the PCI device in the
 *	slot when asked.
 */
 
static int slot_enable(struct hotplug_slot *slot)
{
	struct h2999_slot *s = slot->private;
	int i;
	pci_restore_slot(s);
	for(i=0; i < 8; i++)
	{
		if(s->pci[i] && (s->drivermap&(1<<i)))
			pci_announce_device_to_drivers(s->pci[i]);
	}
	return 0;
}

/**
 *	slot_disable	-	disable H2999 slot
 *	@slot: slot to disable
 *
 *	Disable a slot. Its not actually clear what to do here. We could
 *	report the device as having been removed when we are told to do
 *	this.
 */
 
static int slot_disable(struct hotplug_slot *slot)
{
	struct h2999_slot *s = slot->private;
	struct pci_dev *pdev;
	int i;

	for(i = 0; i < 8; i++)
	{
		pdev = s->pci[i];
		/* Hack for now */
		if (pdev && pdev->driver) {
			if (!pdev->driver->remove)
				return -EBUSY;
		}
	}
	
	s->drivermap = 0;
		
	for(i = 0; i < 8; i++)
	{
		pdev = s->pci[i];
		if(pdev)
		{
			if(pdev->driver)
			{
				s->drivermap|=(1<<i);
				pdev->driver->remove(pdev);
				pdev->driver = NULL;
			}
		}
	}
	return 0;
}

/**
 *	set_attention_status	-	set attention callback
 *	@slot: slot to set
 *	@value: on/off
 *
 *	Called when the hotplug layer wants to set the attention status of
 *	the hotplug slot. The H2999 doesn't have an attention control (at
 *	least not that we know of). So we ignore this.
 */
 
static int set_attention_status(struct hotplug_slot *slot, u8 value)
{
	return 0;
}

/**
 *	hardware_test		-	test hardware callback
 *	@slot: slot to test
 *	value: test to run
 *
 *	The H2999 does not support any hardware tests that we know of. 
 */
 
static int hardware_test(struct hotplug_slot *slot, u32 value)
{
	return 0;
}

/**
 *	get_power_status	-	power query callback
 *	@slot; slot to query
 *	@value: returned state
 *
 *	Called when the hotplug layer wants to ask us if the slot is
 *	powered. We work on the basis that all slots are powered when
 *	the unit is docked. This seems to be correct but I've not actually
 *	rammed a voltmeter into the slots to see if they are cleverer than
 *	that.
 */
 
static int get_power_status(struct hotplug_slot *slot, u8 *value)
{
	struct h2999_slot *s = slot->private;

	/* Slots are all powered when docked */
	if(s->dev->docked > 0)
		*value = 1;
	else
		*value = 0;
	return 0;
}

/**
 *	get_adapter_status	-	card presence query
 *	@slot: slot to query
 *	@value: returned state
 *
 *	If we are not docked, we know the "slot" is empty. If we are
 *	docked its a bit more complicated.
 */
 
static int get_adapter_status(struct hotplug_slot *slot, u8 *value)
{
	struct h2999_slot *s = slot->private;
	
	*value = 0;

	if(s->dev->docked)
		*value = 1;
	return 0;
}

static struct hotplug_slot_ops h2999_ops = {
	THIS_MODULE,
	slot_enable,
	slot_disable,
	set_attention_status,
	hardware_test,
	get_power_status,
	NULL,
	NULL,
	get_adapter_status
};

/**
 *	h2999_is_docked		-	check if docked
 *	@dev: h2999 device
 *
 *	Check if we are currently docked. The method we use at the moment
 *	relies on poking around behind the bridge. There is no doubt a
 *	correct way to do this. Maybe one day IBM will be decide to 
 *	actually provide documentation
 */
 
static int h2999_is_docked(struct h2999_dev *dev)
{
	struct pci_dev *pdev = pci_find_slot(dev->hotplug_bus, PCI_DEVFN(0,0));
	u32 status;
	
	if(pdev == NULL)
		return 0;	/* Shouldnt happen - must be undocked */
	
	if(pci_read_config_dword(pdev, PCI_VENDOR_ID, &status))
		return 0;	/* Config read failed - its missing */
	
	if(status == 0xFFFFFFFFUL)	/* Failed */
		return 0;

	/* Must be docked */		
	return 1;
}
/**
 *	h2999_reconfigure_dock	-	redock event handler
 *	@dev: h2999 device
 *
 *	A redocking event has occurred. There may also have been an undock
 *	before hand. If so then the unconfigure routine is called first.
 */
 
static void h2999_reconfigure_dock(struct h2999_dev *dev)
{
	int docked, i;
	
	docked = h2999_is_docked(dev);
			
	if(docked ^ dev->docked)
	{
		printk("h2999: Now %sdocked.\n",
			docked?"":"un");
		if(docked)
		{
			/* We should do the re-enumeration of the bus here
			   The current save/restore is a test hack */
			for(i=0; i < H2999_SLOTS; i++)
			{
				if(dev->slots[i].hotplug_slot.private != &dev->slots[i])
					BUG();
				slot_enable(&dev->slots[i].hotplug_slot);
			}
		}
		else
		{
			for(i=0; i < H2999_SLOTS; i++)
			{
				if(slot_disable(&dev->slots[i].hotplug_slot))
					printk(KERN_ERR "h2999: someone undocked while devices were in use, how rude!\n");
			}
		}
		dev->docked = docked;
	}
	/* Clear bits */
	pci_write_config_byte(dev->pdev, 0x1f, 0xf0);
}

/*
 *	h2999_attach	-	attach an H2999 bridge
 *	@pdev: PCI device
 *	@unused: unused 
 *
 *	Called when the PCI layer discovers an H2999 docking bridge is
 *	present in the system. We scan the bridge to obtain its current
 *	status and register it with the hot plug layer
 */
 
static int __devinit h2999_attach(struct pci_dev *pdev, const struct pci_device_id *unused)
{
	/* PCI core found a new H2999 */
	struct h2999_dev *dev;
	u8 bus;
	int i;
	
	dev = kmalloc(sizeof(*dev), GFP_KERNEL);
	if(dev == NULL)
		goto nomem;
		
	memset(dev, 0, sizeof(*dev));

	dev->pdev = pdev;

	pci_read_config_byte(pdev, PCI_SECONDARY_BUS, &bus);	
	dev->hotplug_bus = bus;

	/* Requires hotplug_bus and pdev are set */
	
	dev->docked = h2999_is_docked(dev);
	
	printk(KERN_INFO "Found IBM 20H2999. Status is %sdocked, docking bus is %d.\n",
		dev->docked?"":"un", dev->hotplug_bus);

	/*
	 *	Allow for 8 devices. On the TP600 at least we have
	 *	0-3 as the onboard devices, and 4-7 as the slots.
	 *	To add more fun there is an ISA bridge which we
	 *	don't really handle yet.
	 */
	 
	for(i = 0; i < H2999_SLOTS; i++)
	{
		struct h2999_slot *s = &dev->slots[i];
		int ret;
		
		s->hotplug_slot.info = &s->hotplug_info;
		s->hotplug_slot.private = s;
		s->slotid = i;
		s->dev = dev;
		s->live = 1;
		s->hotplug_slot.ops = &h2999_ops;
		s->hotplug_slot.name = s->name;
		s->hotplug_info.power_status = dev->docked;
		/* FIXME - should probe here 
		   In truth the hp_register ought to call thse as needed! */
		s->hotplug_info.adapter_status = 0;
		s->pdev = pci_find_slot(dev->hotplug_bus, PCI_DEVFN(i, 0));
		snprintf(s->name, SLOT_NAME_SIZE, "Dock%d.%d", dev->hotplug_bus, i);
		pci_save_slot(s);	
		ret = pci_hp_register(&s->hotplug_slot);
		if(ret)
		{
			printk(KERN_ERR "pci_hp_register failed for slot %d with error %d\n", i, ret);
			s->live = 0;
		}
	}
	pci_set_drvdata(pdev, dev);
	
	testdev = dev;
	return 0;
nomem:
	printk(KERN_ERR "h2999_attach: out of memory.\n");
	return -ENOMEM;
}

/**
 *	h2999_cleanup	-	free H2999 memory resources
 *	@dev: h2999 device
 *
 *	Unregister and free up all of our slots
 */

static int __devinit h2999_cleanup(struct h2999_dev *dev)
{
	struct h2999_slot *s;
	int slot;
	
	for(slot = 0; slot < H2999_SLOTS; slot++)
	{
		s = &dev->slots[slot];
		if(s->live)
			pci_hp_deregister(&s->hotplug_slot);
	}
	kfree(dev);
}

/**
 *	h2999_detach	-	an H2999 controller vanished
 *	@dev: device that vanished
 *
 *	Called when the PCI layer sees the bridge unplugged. At the moment
 *	this doesn't happen and since its currently unclear what to do
 *	in the hot plug layer if it does this may be a good thing 8)
 */
 
static void __devinit h2999_detach(struct pci_dev *pdev)
{
	struct h2999_dev *dev = pci_get_drvdata(pdev);
	h2999_cleanup(dev);
}


static struct pci_device_id h2999_id_tbl[] __devinitdata = {
	{ PCI_VENDOR_ID_IBM, 0x0095, PCI_ANY_ID, PCI_ANY_ID, },
	{ 0, }
};

MODULE_DEVICE_TABLE(pci, h2999_id_tbl);

static struct pci_driver h2999_driver = {
	name:		"h2999",
	id_table:	h2999_id_tbl,
	probe:		h2999_attach,
	remove:		__devexit_p(h2999_detach)
	/* FIXME - PM functions */
};

/*
 *	Test harness
 */
 
static struct completion thread_done;

static int h2999_thread(void *unused)
{
	lock_kernel();
	while(testdev != NULL)
	{
		set_current_state(TASK_INTERRUPTIBLE);
		if(signal_pending(current))
			break;
		schedule_timeout(HZ);
		h2999_reconfigure_dock(testdev);
	}
	unlock_kernel();
	complete_and_exit(&thread_done, 0);
}

static int __init h2999_init_module(void)
{
	int rc;
	printk(KERN_INFO "IBM 20H2999 PCI docking bridge driver v0.01\n");
	
	init_completion(&thread_done);
	
	rc = pci_module_init(&h2999_driver);
	if (rc == 0)
	{
		if( kernel_thread(h2999_thread, NULL, CLONE_SIGHAND) >= 0)
			return 0;
	}	
	complete(&thread_done);
	return rc;
}

static void __exit h2999_cleanup_module(void)
{
	pci_unregister_driver(&h2999_driver);
	wait_for_completion(&thread_done);
}

module_init(h2999_init_module);
module_exit(h2999_cleanup_module);

MODULE_AUTHOR("Alan Cox");
MODULE_DESCRIPTION("IBM 20H2999 Docking Bridge Driver");
MODULE_LICENSE("GPL");


