summaryrefslogtreecommitdiff
path: root/arch/powerpc/platforms/85xx/sgy_cts1000.c
blob: 21d6aaa5c3e4aedced415e9409591049a63cc7cc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
/*
 * 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 __noreturn gpio_halt_cb(void)
{
	enum of_gpio_flags flags;
	int trigger, gpio;

	if (!halt_node)
		panic("No reset GPIO information was provided in DT\n");

	gpio = of_get_gpio_flags(halt_node, 0, &flags);

	if (!gpio_is_valid(gpio))
		panic("Provided GPIO is invalid\n");

	trigger = (flags == OF_GPIO_ACTIVE_LOW);

	printk(KERN_INFO "gpio-halt: triggering GPIO.\n");

	/* Probably wont return */
	gpio_set_value(gpio, trigger);

	panic("Halt failed\n");
}

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