| /* | 
 |  * Servergy CTS-1000 Setup | 
 |  * | 
 |  * Maintained by Ben Collins <ben.c@servergy.com> | 
 |  * | 
 |  * Copyright 2012 by Servergy, 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/platform_device.h> | 
 | #include <linux/device.h> | 
 | #include <linux/module.h> | 
 | #include <linux/of_gpio.h> | 
 | #include <linux/of_irq.h> | 
 | #include <linux/workqueue.h> | 
 | #include <linux/reboot.h> | 
 | #include <linux/interrupt.h> | 
 |  | 
 | #include <asm/machdep.h> | 
 |  | 
 | static struct device_node *halt_node; | 
 |  | 
 | static const struct of_device_id child_match[] = { | 
 | 	{ | 
 | 		.compatible = "sgy,gpio-halt", | 
 | 	}, | 
 | 	{}, | 
 | }; | 
 |  | 
 | static void gpio_halt_wfn(struct work_struct *work) | 
 | { | 
 | 	/* Likely wont return */ | 
 | 	orderly_poweroff(true); | 
 | } | 
 | static DECLARE_WORK(gpio_halt_wq, gpio_halt_wfn); | 
 |  | 
 | static void gpio_halt_cb(void) | 
 | { | 
 | 	enum of_gpio_flags flags; | 
 | 	int trigger, gpio; | 
 |  | 
 | 	if (!halt_node) | 
 | 		return; | 
 |  | 
 | 	gpio = of_get_gpio_flags(halt_node, 0, &flags); | 
 |  | 
 | 	if (!gpio_is_valid(gpio)) | 
 | 		return; | 
 |  | 
 | 	trigger = (flags == OF_GPIO_ACTIVE_LOW); | 
 |  | 
 | 	printk(KERN_INFO "gpio-halt: triggering GPIO.\n"); | 
 |  | 
 | 	/* Probably wont return */ | 
 | 	gpio_set_value(gpio, trigger); | 
 | } | 
 |  | 
 | /* This IRQ means someone pressed the power button and it is waiting for us | 
 |  * to handle the shutdown/poweroff. */ | 
 | static irqreturn_t gpio_halt_irq(int irq, void *__data) | 
 | { | 
 | 	printk(KERN_INFO "gpio-halt: shutdown due to power button IRQ.\n"); | 
 | 	schedule_work(&gpio_halt_wq); | 
 |  | 
 |         return IRQ_HANDLED; | 
 | }; | 
 |  | 
 | static int gpio_halt_probe(struct platform_device *pdev) | 
 | { | 
 | 	enum of_gpio_flags flags; | 
 | 	struct device_node *node = pdev->dev.of_node; | 
 | 	int gpio, err, irq; | 
 | 	int trigger; | 
 |  | 
 | 	if (!node) | 
 | 		return -ENODEV; | 
 |  | 
 | 	/* If there's no matching child, this isn't really an error */ | 
 | 	halt_node = of_find_matching_node(node, child_match); | 
 | 	if (!halt_node) | 
 | 		return 0; | 
 |  | 
 | 	/* Technically we could just read the first one, but punish | 
 | 	 * DT writers for invalid form. */ | 
 | 	if (of_gpio_count(halt_node) != 1) | 
 | 		return -EINVAL; | 
 |  | 
 | 	/* Get the gpio number relative to the dynamic base. */ | 
 | 	gpio = of_get_gpio_flags(halt_node, 0, &flags); | 
 | 	if (!gpio_is_valid(gpio)) | 
 | 		return -EINVAL; | 
 |  | 
 | 	err = gpio_request(gpio, "gpio-halt"); | 
 | 	if (err) { | 
 | 		printk(KERN_ERR "gpio-halt: error requesting GPIO %d.\n", | 
 | 		       gpio); | 
 | 		halt_node = NULL; | 
 | 		return err; | 
 | 	} | 
 |  | 
 | 	trigger = (flags == OF_GPIO_ACTIVE_LOW); | 
 |  | 
 | 	gpio_direction_output(gpio, !trigger); | 
 |  | 
 | 	/* Now get the IRQ which tells us when the power button is hit */ | 
 | 	irq = irq_of_parse_and_map(halt_node, 0); | 
 | 	err = request_irq(irq, gpio_halt_irq, IRQF_TRIGGER_RISING | | 
 | 			  IRQF_TRIGGER_FALLING, "gpio-halt", halt_node); | 
 | 	if (err) { | 
 | 		printk(KERN_ERR "gpio-halt: error requesting IRQ %d for " | 
 | 		       "GPIO %d.\n", irq, gpio); | 
 | 		gpio_free(gpio); | 
 | 		halt_node = NULL; | 
 | 		return err; | 
 | 	} | 
 |  | 
 | 	/* Register our halt function */ | 
 | 	ppc_md.halt = gpio_halt_cb; | 
 | 	pm_power_off = gpio_halt_cb; | 
 |  | 
 | 	printk(KERN_INFO "gpio-halt: registered GPIO %d (%d trigger, %d" | 
 | 	       " irq).\n", gpio, trigger, irq); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int gpio_halt_remove(struct platform_device *pdev) | 
 | { | 
 | 	if (halt_node) { | 
 | 		int gpio = of_get_gpio(halt_node, 0); | 
 | 		int irq = irq_of_parse_and_map(halt_node, 0); | 
 |  | 
 | 		free_irq(irq, halt_node); | 
 |  | 
 | 		ppc_md.halt = NULL; | 
 | 		pm_power_off = NULL; | 
 |  | 
 | 		gpio_free(gpio); | 
 |  | 
 | 		halt_node = NULL; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static const struct of_device_id gpio_halt_match[] = { | 
 | 	/* We match on the gpio bus itself and scan the children since they | 
 | 	 * wont be matched against us. We know the bus wont match until it | 
 | 	 * has been registered too. */ | 
 | 	{ | 
 | 		.compatible = "fsl,qoriq-gpio", | 
 | 	}, | 
 | 	{}, | 
 | }; | 
 | MODULE_DEVICE_TABLE(of, gpio_halt_match); | 
 |  | 
 | static struct platform_driver gpio_halt_driver = { | 
 | 	.driver = { | 
 | 		.name		= "gpio-halt", | 
 | 		.of_match_table = gpio_halt_match, | 
 | 	}, | 
 | 	.probe		= gpio_halt_probe, | 
 | 	.remove		= gpio_halt_remove, | 
 | }; | 
 |  | 
 | module_platform_driver(gpio_halt_driver); | 
 |  | 
 | MODULE_DESCRIPTION("Driver to support GPIO triggered system halt for Servergy CTS-1000 Systems."); | 
 | MODULE_VERSION("1.0"); | 
 | MODULE_AUTHOR("Ben Collins <ben.c@servergy.com>"); | 
 | MODULE_LICENSE("GPL"); |