/* Version 1.2
 *   1.0 - initial release
 *   1.1 - fix initialization order
 *   1.2 - typo in gpioinitmask
 */

#include <linux/module.h>
#include <linux/pci.h>

#include <osl.h>

#include <bcmdevs.h>
#include <sbutils.h>
#include <sbconfig.h>
#include <sbchipc.h>

/* Use the existing Silicon Backplane handle (sbh)
 */
extern void *bcm947xx_sbh;
#define sbh bcm947xx_sbh

#define reset_gpio (1<<6)
static int dev_id;
static int polarity;
static int pressed=0;

static void gpio_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
	int status;

	/* check if it was the correct gpio
	 * Since any GPIO interrupt could have triggered, we have to check
	 * if it was ours by checking to see if the GPIO has changed.
	 */

	status = sb_gpioin(sbh) & reset_gpio;
	if (status == (polarity^reset_gpio)) {
		/* keep track of the button state
		 * Depending on the device, the polarity of the button may
		 * be reversed, so we can't use the polarity variable. 
		 */ 

		pressed ^= 1;
		printk("reset button: %s\n",pressed?"pressed":"released");

		/* flip the polarity
		 * This is used to ignore the interrupt until the GPIO
		 * changes again, creating an edge driven interrupt.
		 */
		polarity ^= reset_gpio;
		sb_gpiointpolarity(sbh,reset_gpio,polarity);
	}
}

static void __exit test_exit(void)
{
	/* remove the gpio from the interrupt mask
	 * We assume that we're the only ones monitoring this GPIO and
	 * since we're no longer interested, we don't need an interrupt
	 * every time the GPIO is triggered.
	 *
	 * We could also disable the GPIO interrupts by turning off
	 * CI_GPIO in the ccregs intmask, but that would break any
	 * other devices which may be using GPIO interrupts.
	 */
	sb_gpiointmask(sbh,reset_gpio,0);
	
	/* unregister our interrupt handler
	 */
	free_irq(3,&dev_id);
}

static int __init test_init(void)
{
	int ret=0, intmask;
	struct pci_dev *pdev;
	chipcregs_t *ccregs;

	/* access the chip common registers
	 * These are exported as a PCI device, so we need to find the PCI
	 * device and request access with ioremap.
	 */
	if (!(pdev = pci_find_device(VENDOR_BROADCOM, SB_CC, NULL))) {
		printk(KERN_ERR "Could not find chip common registers\n");
		ret=-ENODEV;
		goto out;
	}
	ccregs = (chipcregs_t *) ioremap_nocache(pci_resource_start(pdev, 0), pci_resource_len(pdev, 0));
	
	/* mark the gpio as an uncontrolled input
	 */
	sb_gpioouten(sbh,reset_gpio,0);
	sb_gpiocontrol(sbh,reset_gpio,0);

	/* get the initial state
	 * We read the value from the gpio to determine it's normal value
	 * which we then use as a polarity.
	 */
	polarity = sb_gpioin(sbh) & reset_gpio;
		
	/* register interrupt handler
	 * There is a single interrupt (3) that occurs for all gpios, so we
	 * have to share this interrupt (SA_SHIRQ) with any other drivers
	 * which may be trying to use the GPIOs as interrupts. We use
	 * a dev_id to uniquely identify our handler, so that we can remove
	 * it later.
	 */
	request_irq(3, gpio_interrupt, SA_SHIRQ, "gpio:reset", &dev_id);

	/* set the polarity and configure the GPIO as an interrupt
	 * It's important to set the polarity first to avoid accidential
	 * triggering of the interrupt if the GPIO interrupts are already
	 * enabled. The interrupt will continuously fire while the GPIO
	 * isn't at this polarity.
	 */
	sb_gpiointpolarity(sbh,reset_gpio,polarity);
	sb_gpiointmask(sbh,reset_gpio,reset_gpio);

	/* enable gpio interrupts
	 * The gpio interrupt may or may not have been enabled by another
	 * driver on the system, so we just blindly enable it here.
	 */
	intmask = readl(&ccregs->intmask);
	intmask |= CI_GPIO;
	writel(intmask, &ccregs->intmask);
out:
	return ret;
}

EXPORT_NO_SYMBOLS;

module_init(test_init);
module_exit(test_exit);

MODULE_AUTHOR("Mike Baker / OpenWrt.org");
MODULE_LICENSE("GPL");
