|  | /* | 
|  | * 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"); |