diff options
author | Benjamin Collins <ben.c@servergy.com> | 2012-12-16 23:19:28 -0500 |
---|---|---|
committer | Benjamin Herrenschmidt <benh@kernel.crashing.org> | 2013-01-28 19:35:05 -0500 |
commit | 5611fe48c545a22e62273428ed74c9bfae4a9406 (patch) | |
tree | 79514228d518fe3adb823290879190157d299c1c /arch/powerpc/platforms | |
parent | 34f364fef475f5d960b74bf074d65392ece50235 (diff) |
powerpc: Add support for CTS-1000 GPIO controlled system poweroff
CTS-1000 is based on P4080. GPIO 27 is used to signal the FPGA to
switch off power, and also associates IRQ 8 with front-panel button
press (which we use to call orderly_poweroff()).
The relevant device-tree looks like this:
gpio0: gpio@130000 {
compatible = "fsl,qoriq-gpio";
reg = <0x130000 0x1000>;
interrupts = <55 2 0 0>;
#gpio-cells = <2>;
gpio-controller;
/* Allows powering off the system via GPIO signal. */
gpio-halt@27 {
compatible = "sgy,gpio-halt";
gpios = <&gpio0 27 0>;
interrupts = <8 1 0 0>;
};
};
Because the driver cannot match on sgy,gpio-halt (because the node is never
processed through of_platform), it matches on fsl,qoriq-gpio and then
checks child nodes for the matching sgy,gpio-halt. This also ensures that
the GPIO controller is detected prior to sgy_cts1000's probe callback,
since that node wont match via of_platform until the controller is
registered.
Also, because the GPIO handler for triggering system poweroff might sleep,
the IRQ uses a workqueue to call orderly_poweroff().
As a final note, this driver may be expanded for other features specific to
the CTS-1000.
Signed-off-by: Ben Collins <ben.c@servergy.com>
Cc: Jack Smith <jack.s@servergy.com>
Cc: Vihar Rai <vihar.r@servergy.com>
Cc: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Diffstat (limited to 'arch/powerpc/platforms')
-rw-r--r-- | arch/powerpc/platforms/85xx/Kconfig | 8 | ||||
-rw-r--r-- | arch/powerpc/platforms/85xx/Makefile | 1 | ||||
-rw-r--r-- | arch/powerpc/platforms/85xx/sgy_cts1000.c | 176 |
3 files changed, 185 insertions, 0 deletions
diff --git a/arch/powerpc/platforms/85xx/Kconfig b/arch/powerpc/platforms/85xx/Kconfig index 02d02a09942d..651788cbc6e6 100644 --- a/arch/powerpc/platforms/85xx/Kconfig +++ b/arch/powerpc/platforms/85xx/Kconfig | |||
@@ -245,6 +245,14 @@ config P4080_DS | |||
245 | help | 245 | help |
246 | This option enables support for the P4080 DS board | 246 | This option enables support for the P4080 DS board |
247 | 247 | ||
248 | config SGY_CTS1000 | ||
249 | tristate "Servergy CTS-1000 support" | ||
250 | select GPIOLIB | ||
251 | select OF_GPIO | ||
252 | depends on P4080_DS | ||
253 | help | ||
254 | Enable this to support functionality in Servergy's CTS-1000 systems. | ||
255 | |||
248 | endif # PPC32 | 256 | endif # PPC32 |
249 | 257 | ||
250 | config P5020_DS | 258 | config P5020_DS |
diff --git a/arch/powerpc/platforms/85xx/Makefile b/arch/powerpc/platforms/85xx/Makefile index 76f679cb04a0..9db31dcbd320 100644 --- a/arch/powerpc/platforms/85xx/Makefile +++ b/arch/powerpc/platforms/85xx/Makefile | |||
@@ -30,3 +30,4 @@ obj-$(CONFIG_KSI8560) += ksi8560.o | |||
30 | obj-$(CONFIG_XES_MPC85xx) += xes_mpc85xx.o | 30 | obj-$(CONFIG_XES_MPC85xx) += xes_mpc85xx.o |
31 | obj-$(CONFIG_GE_IMP3A) += ge_imp3a.o | 31 | obj-$(CONFIG_GE_IMP3A) += ge_imp3a.o |
32 | obj-$(CONFIG_PPC_QEMU_E500) += qemu_e500.o | 32 | obj-$(CONFIG_PPC_QEMU_E500) += qemu_e500.o |
33 | obj-$(CONFIG_SGY_CTS1000) += sgy_cts1000.o | ||
diff --git a/arch/powerpc/platforms/85xx/sgy_cts1000.c b/arch/powerpc/platforms/85xx/sgy_cts1000.c new file mode 100644 index 000000000000..611e92f291c4 --- /dev/null +++ b/arch/powerpc/platforms/85xx/sgy_cts1000.c | |||
@@ -0,0 +1,176 @@ | |||
1 | /* | ||
2 | * Servergy CTS-1000 Setup | ||
3 | * | ||
4 | * Maintained by Ben Collins <ben.c@servergy.com> | ||
5 | * | ||
6 | * Copyright 2012 by Servergy, Inc. | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify it | ||
9 | * under the terms of the GNU General Public License as published by the | ||
10 | * Free Software Foundation; either version 2 of the License, or (at your | ||
11 | * option) any later version. | ||
12 | */ | ||
13 | |||
14 | #include <linux/platform_device.h> | ||
15 | #include <linux/device.h> | ||
16 | #include <linux/module.h> | ||
17 | #include <linux/init.h> | ||
18 | #include <linux/of_gpio.h> | ||
19 | #include <linux/workqueue.h> | ||
20 | #include <linux/reboot.h> | ||
21 | #include <linux/interrupt.h> | ||
22 | |||
23 | #include <asm/machdep.h> | ||
24 | |||
25 | static struct device_node *halt_node; | ||
26 | |||
27 | static struct of_device_id child_match[] = { | ||
28 | { | ||
29 | .compatible = "sgy,gpio-halt", | ||
30 | }, | ||
31 | {}, | ||
32 | }; | ||
33 | |||
34 | static void gpio_halt_wfn(struct work_struct *work) | ||
35 | { | ||
36 | /* Likely wont return */ | ||
37 | orderly_poweroff(true); | ||
38 | } | ||
39 | static DECLARE_WORK(gpio_halt_wq, gpio_halt_wfn); | ||
40 | |||
41 | static void gpio_halt_cb(void) | ||
42 | { | ||
43 | enum of_gpio_flags flags; | ||
44 | int trigger, gpio; | ||
45 | |||
46 | if (!halt_node) | ||
47 | return; | ||
48 | |||
49 | gpio = of_get_gpio_flags(halt_node, 0, &flags); | ||
50 | |||
51 | if (!gpio_is_valid(gpio)) | ||
52 | return; | ||
53 | |||
54 | trigger = (flags == OF_GPIO_ACTIVE_LOW); | ||
55 | |||
56 | printk(KERN_INFO "gpio-halt: triggering GPIO.\n"); | ||
57 | |||
58 | /* Probably wont return */ | ||
59 | gpio_set_value(gpio, trigger); | ||
60 | } | ||
61 | |||
62 | /* This IRQ means someone pressed the power button and it is waiting for us | ||
63 | * to handle the shutdown/poweroff. */ | ||
64 | static irqreturn_t gpio_halt_irq(int irq, void *__data) | ||
65 | { | ||
66 | printk(KERN_INFO "gpio-halt: shutdown due to power button IRQ.\n"); | ||
67 | schedule_work(&gpio_halt_wq); | ||
68 | |||
69 | return IRQ_HANDLED; | ||
70 | }; | ||
71 | |||
72 | static int __devinit gpio_halt_probe(struct platform_device *pdev) | ||
73 | { | ||
74 | enum of_gpio_flags flags; | ||
75 | struct device_node *node = pdev->dev.of_node; | ||
76 | int gpio, err, irq; | ||
77 | int trigger; | ||
78 | |||
79 | if (!node) | ||
80 | return -ENODEV; | ||
81 | |||
82 | /* If there's no matching child, this isn't really an error */ | ||
83 | halt_node = of_find_matching_node(node, child_match); | ||
84 | if (!halt_node) | ||
85 | return 0; | ||
86 | |||
87 | /* Technically we could just read the first one, but punish | ||
88 | * DT writers for invalid form. */ | ||
89 | if (of_gpio_count(halt_node) != 1) | ||
90 | return -EINVAL; | ||
91 | |||
92 | /* Get the gpio number relative to the dynamic base. */ | ||
93 | gpio = of_get_gpio_flags(halt_node, 0, &flags); | ||
94 | if (!gpio_is_valid(gpio)) | ||
95 | return -EINVAL; | ||
96 | |||
97 | err = gpio_request(gpio, "gpio-halt"); | ||
98 | if (err) { | ||
99 | printk(KERN_ERR "gpio-halt: error requesting GPIO %d.\n", | ||
100 | gpio); | ||
101 | halt_node = NULL; | ||
102 | return err; | ||
103 | } | ||
104 | |||
105 | trigger = (flags == OF_GPIO_ACTIVE_LOW); | ||
106 | |||
107 | gpio_direction_output(gpio, !trigger); | ||
108 | |||
109 | /* Now get the IRQ which tells us when the power button is hit */ | ||
110 | irq = irq_of_parse_and_map(halt_node, 0); | ||
111 | err = request_irq(irq, gpio_halt_irq, IRQF_TRIGGER_RISING | | ||
112 | IRQF_TRIGGER_FALLING, "gpio-halt", halt_node); | ||
113 | if (err) { | ||
114 | printk(KERN_ERR "gpio-halt: error requesting IRQ %d for " | ||
115 | "GPIO %d.\n", irq, gpio); | ||
116 | gpio_free(gpio); | ||
117 | halt_node = NULL; | ||
118 | return err; | ||
119 | } | ||
120 | |||
121 | /* Register our halt function */ | ||
122 | ppc_md.halt = gpio_halt_cb; | ||
123 | ppc_md.power_off = gpio_halt_cb; | ||
124 | |||
125 | printk(KERN_INFO "gpio-halt: registered GPIO %d (%d trigger, %d" | ||
126 | " irq).\n", gpio, trigger, irq); | ||
127 | |||
128 | return 0; | ||
129 | } | ||
130 | |||
131 | static int __devexit gpio_halt_remove(struct platform_device *pdev) | ||
132 | { | ||
133 | if (halt_node) { | ||
134 | int gpio = of_get_gpio(halt_node, 0); | ||
135 | int irq = irq_of_parse_and_map(halt_node, 0); | ||
136 | |||
137 | free_irq(irq, halt_node); | ||
138 | |||
139 | ppc_md.halt = NULL; | ||
140 | ppc_md.power_off = NULL; | ||
141 | |||
142 | gpio_free(gpio); | ||
143 | |||
144 | halt_node = NULL; | ||
145 | } | ||
146 | |||
147 | return 0; | ||
148 | } | ||
149 | |||
150 | static struct of_device_id gpio_halt_match[] = { | ||
151 | /* We match on the gpio bus itself and scan the children since they | ||
152 | * wont be matched against us. We know the bus wont match until it | ||
153 | * has been registered too. */ | ||
154 | { | ||
155 | .compatible = "fsl,qoriq-gpio", | ||
156 | }, | ||
157 | {}, | ||
158 | }; | ||
159 | MODULE_DEVICE_TABLE(of, gpio_halt_match); | ||
160 | |||
161 | static struct platform_driver gpio_halt_driver = { | ||
162 | .driver = { | ||
163 | .name = "gpio-halt", | ||
164 | .owner = THIS_MODULE, | ||
165 | .of_match_table = gpio_halt_match, | ||
166 | }, | ||
167 | .probe = gpio_halt_probe, | ||
168 | .remove = __devexit_p(gpio_halt_remove), | ||
169 | }; | ||
170 | |||
171 | module_platform_driver(gpio_halt_driver); | ||
172 | |||
173 | MODULE_DESCRIPTION("Driver to support GPIO triggered system halt for Servergy CTS-1000 Systems."); | ||
174 | MODULE_VERSION("1.0"); | ||
175 | MODULE_AUTHOR("Ben Collins <ben.c@servergy.com>"); | ||
176 | MODULE_LICENSE("GPL"); | ||