1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Servergy CTS-1000 Setup
4 *
5 * Maintained by Ben Collins <ben.c@servergy.com>
6 *
7 * Copyright 2012 by Servergy, Inc.
8 */
9
10#include <linux/platform_device.h>
11#include <linux/device.h>
12#include <linux/module.h>
13#include <linux/of_gpio.h>
14#include <linux/of_irq.h>
15#include <linux/workqueue.h>
16#include <linux/reboot.h>
17#include <linux/interrupt.h>
18
19#include <asm/machdep.h>
20
21static struct device_node *halt_node;
22
23static const struct of_device_id child_match[] = {
24	{
25		.compatible = "sgy,gpio-halt",
26	},
27	{},
28};
29
30static void gpio_halt_wfn(struct work_struct *work)
31{
32	/* Likely wont return */
33	orderly_poweroff(true);
34}
35static DECLARE_WORK(gpio_halt_wq, gpio_halt_wfn);
36
37static void __noreturn gpio_halt_cb(void)
38{
39	enum of_gpio_flags flags;
40	int trigger, gpio;
41
42	if (!halt_node)
43		panic("No reset GPIO information was provided in DT\n");
44
45	gpio = of_get_gpio_flags(halt_node, 0, &flags);
46
47	if (!gpio_is_valid(gpio))
48		panic("Provided GPIO is invalid\n");
49
50	trigger = (flags == OF_GPIO_ACTIVE_LOW);
51
52	printk(KERN_INFO "gpio-halt: triggering GPIO.\n");
53
54	/* Probably wont return */
55	gpio_set_value(gpio, trigger);
56
57	panic("Halt failed\n");
58}
59
60/* This IRQ means someone pressed the power button and it is waiting for us
61 * to handle the shutdown/poweroff. */
62static irqreturn_t gpio_halt_irq(int irq, void *__data)
63{
64	printk(KERN_INFO "gpio-halt: shutdown due to power button IRQ.\n");
65	schedule_work(&gpio_halt_wq);
66
67        return IRQ_HANDLED;
68};
69
70static int gpio_halt_probe(struct platform_device *pdev)
71{
72	enum of_gpio_flags flags;
73	struct device_node *node = pdev->dev.of_node;
74	int gpio, err, irq;
75	int trigger;
76
77	if (!node)
78		return -ENODEV;
79
80	/* If there's no matching child, this isn't really an error */
81	halt_node = of_find_matching_node(node, child_match);
82	if (!halt_node)
83		return 0;
84
85	/* Technically we could just read the first one, but punish
86	 * DT writers for invalid form. */
87	if (of_gpio_count(halt_node) != 1)
88		return -EINVAL;
89
90	/* Get the gpio number relative to the dynamic base. */
91	gpio = of_get_gpio_flags(halt_node, 0, &flags);
92	if (!gpio_is_valid(gpio))
93		return -EINVAL;
94
95	err = gpio_request(gpio, "gpio-halt");
96	if (err) {
97		printk(KERN_ERR "gpio-halt: error requesting GPIO %d.\n",
98		       gpio);
99		halt_node = NULL;
100		return err;
101	}
102
103	trigger = (flags == OF_GPIO_ACTIVE_LOW);
104
105	gpio_direction_output(gpio, !trigger);
106
107	/* Now get the IRQ which tells us when the power button is hit */
108	irq = irq_of_parse_and_map(halt_node, 0);
109	err = request_irq(irq, gpio_halt_irq, IRQF_TRIGGER_RISING |
110			  IRQF_TRIGGER_FALLING, "gpio-halt", halt_node);
111	if (err) {
112		printk(KERN_ERR "gpio-halt: error requesting IRQ %d for "
113		       "GPIO %d.\n", irq, gpio);
114		gpio_free(gpio);
115		halt_node = NULL;
116		return err;
117	}
118
119	/* Register our halt function */
120	ppc_md.halt = gpio_halt_cb;
121	pm_power_off = gpio_halt_cb;
122
123	printk(KERN_INFO "gpio-halt: registered GPIO %d (%d trigger, %d"
124	       " irq).\n", gpio, trigger, irq);
125
126	return 0;
127}
128
129static int gpio_halt_remove(struct platform_device *pdev)
130{
131	if (halt_node) {
132		int gpio = of_get_gpio(halt_node, 0);
133		int irq = irq_of_parse_and_map(halt_node, 0);
134
135		free_irq(irq, halt_node);
136
137		ppc_md.halt = NULL;
138		pm_power_off = NULL;
139
140		gpio_free(gpio);
141
142		halt_node = NULL;
143	}
144
145	return 0;
146}
147
148static const struct of_device_id gpio_halt_match[] = {
149	/* We match on the gpio bus itself and scan the children since they
150	 * wont be matched against us. We know the bus wont match until it
151	 * has been registered too. */
152	{
153		.compatible = "fsl,qoriq-gpio",
154	},
155	{},
156};
157MODULE_DEVICE_TABLE(of, gpio_halt_match);
158
159static struct platform_driver gpio_halt_driver = {
160	.driver = {
161		.name		= "gpio-halt",
162		.of_match_table = gpio_halt_match,
163	},
164	.probe		= gpio_halt_probe,
165	.remove		= gpio_halt_remove,
166};
167
168module_platform_driver(gpio_halt_driver);
169
170MODULE_DESCRIPTION("Driver to support GPIO triggered system halt for Servergy CTS-1000 Systems.");
171MODULE_VERSION("1.0");
172MODULE_AUTHOR("Ben Collins <ben.c@servergy.com>");
173MODULE_LICENSE("GPL");
174