aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/pps/clients
diff options
context:
space:
mode:
authorJames Nuss <jamesnuss@nanometrics.ca>2011-11-02 16:39:38 -0400
committerLinus Torvalds <torvalds@linux-foundation.org>2011-11-02 19:07:02 -0400
commit161520451dfacd0eb79d501933f47d3fb7464938 (patch)
tree6a3f613d005d32cdf7aeb725bac00ed55c45b929 /drivers/pps/clients
parent437c53418616973071fd2d7c87497780944d8fdb (diff)
pps: new client driver using GPIO
This client driver allows you to use a GPIO pin as a source for PPS signals. Platform data [1] are used to specify the GPIO pin number, label, assert event edge type, and whether clear events are captured. This driver is based on the work by Ricardo Martins who submitted an initial implementation [2] of a PPS IRQ client driver to the linuxpps mailing-list on Dec 3 2010. [1] include/linux/pps-gpio.h [2] http://ml.enneenne.com/pipermail/linuxpps/2010-December/004155.html [akpm@linux-foundation.org: remove unneeded cast of void*] Signed-off-by: James Nuss <jamesnuss@nanometrics.ca> Cc: Ricardo Martins <rasm@fe.up.pt> Acked-by: Rodolfo Giometti <giometti@linux.it> Signed-off-by: Ricardo Martins <rasm@fe.up.pt> Cc: Alexander Gordeev <lasaine@lvk.cs.msu.su> Cc: Igor Plyatov <plyatov@gmail.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'drivers/pps/clients')
-rw-r--r--drivers/pps/clients/Kconfig9
-rw-r--r--drivers/pps/clients/Makefile1
-rw-r--r--drivers/pps/clients/pps-gpio.c227
3 files changed, 237 insertions, 0 deletions
diff --git a/drivers/pps/clients/Kconfig b/drivers/pps/clients/Kconfig
index 8520a7f4dd62..c2e0f1e97bcd 100644
--- a/drivers/pps/clients/Kconfig
+++ b/drivers/pps/clients/Kconfig
@@ -29,4 +29,13 @@ config PPS_CLIENT_PARPORT
29 If you say yes here you get support for a PPS source connected 29 If you say yes here you get support for a PPS source connected
30 with the interrupt pin of your parallel port. 30 with the interrupt pin of your parallel port.
31 31
32config PPS_CLIENT_GPIO
33 tristate "PPS client using GPIO"
34 depends on PPS
35 help
36 If you say yes here you get support for a PPS source using
37 GPIO. To be useful you must also register a platform device
38 specifying the GPIO pin and other options, usually in your board
39 setup.
40
32endif 41endif
diff --git a/drivers/pps/clients/Makefile b/drivers/pps/clients/Makefile
index 4feb7e9e71ee..a461d15f4a2e 100644
--- a/drivers/pps/clients/Makefile
+++ b/drivers/pps/clients/Makefile
@@ -5,5 +5,6 @@
5obj-$(CONFIG_PPS_CLIENT_KTIMER) += pps-ktimer.o 5obj-$(CONFIG_PPS_CLIENT_KTIMER) += pps-ktimer.o
6obj-$(CONFIG_PPS_CLIENT_LDISC) += pps-ldisc.o 6obj-$(CONFIG_PPS_CLIENT_LDISC) += pps-ldisc.o
7obj-$(CONFIG_PPS_CLIENT_PARPORT) += pps_parport.o 7obj-$(CONFIG_PPS_CLIENT_PARPORT) += pps_parport.o
8obj-$(CONFIG_PPS_CLIENT_GPIO) += pps-gpio.o
8 9
9ccflags-$(CONFIG_PPS_DEBUG) := -DDEBUG 10ccflags-$(CONFIG_PPS_DEBUG) := -DDEBUG
diff --git a/drivers/pps/clients/pps-gpio.c b/drivers/pps/clients/pps-gpio.c
new file mode 100644
index 000000000000..655055545479
--- /dev/null
+++ b/drivers/pps/clients/pps-gpio.c
@@ -0,0 +1,227 @@
1/*
2 * pps-gpio.c -- PPS client driver using GPIO
3 *
4 *
5 * Copyright (C) 2010 Ricardo Martins <rasm@fe.up.pt>
6 * Copyright (C) 2011 James Nuss <jamesnuss@nanometrics.ca>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 */
22
23#define PPS_GPIO_NAME "pps-gpio"
24#define pr_fmt(fmt) PPS_GPIO_NAME ": " fmt
25
26#include <linux/init.h>
27#include <linux/kernel.h>
28#include <linux/interrupt.h>
29#include <linux/module.h>
30#include <linux/platform_device.h>
31#include <linux/slab.h>
32#include <linux/pps_kernel.h>
33#include <linux/pps-gpio.h>
34#include <linux/gpio.h>
35#include <linux/list.h>
36
37/* Info for each registered platform device */
38struct pps_gpio_device_data {
39 int irq; /* IRQ used as PPS source */
40 struct pps_device *pps; /* PPS source device */
41 struct pps_source_info info; /* PPS source information */
42 const struct pps_gpio_platform_data *pdata;
43};
44
45/*
46 * Report the PPS event
47 */
48
49static irqreturn_t pps_gpio_irq_handler(int irq, void *data)
50{
51 const struct pps_gpio_device_data *info;
52 struct pps_event_time ts;
53 int rising_edge;
54
55 /* Get the time stamp first */
56 pps_get_ts(&ts);
57
58 info = data;
59
60 rising_edge = gpio_get_value(info->pdata->gpio_pin);
61 if ((rising_edge && !info->pdata->assert_falling_edge) ||
62 (!rising_edge && info->pdata->assert_falling_edge))
63 pps_event(info->pps, &ts, PPS_CAPTUREASSERT, NULL);
64 else if (info->pdata->capture_clear &&
65 ((rising_edge && info->pdata->assert_falling_edge) ||
66 (!rising_edge && !info->pdata->assert_falling_edge)))
67 pps_event(info->pps, &ts, PPS_CAPTURECLEAR, NULL);
68
69 return IRQ_HANDLED;
70}
71
72static int pps_gpio_setup(struct platform_device *pdev)
73{
74 int ret;
75 const struct pps_gpio_platform_data *pdata = pdev->dev.platform_data;
76
77 ret = gpio_request(pdata->gpio_pin, pdata->gpio_label);
78 if (ret) {
79 pr_warning("failed to request GPIO %u\n", pdata->gpio_pin);
80 return -EINVAL;
81 }
82
83 ret = gpio_direction_input(pdata->gpio_pin);
84 if (ret) {
85 pr_warning("failed to set pin direction\n");
86 gpio_free(pdata->gpio_pin);
87 return -EINVAL;
88 }
89
90 return 0;
91}
92
93static unsigned long
94get_irqf_trigger_flags(const struct pps_gpio_platform_data *pdata)
95{
96 unsigned long flags = pdata->assert_falling_edge ?
97 IRQF_TRIGGER_FALLING : IRQF_TRIGGER_RISING;
98
99 if (pdata->capture_clear) {
100 flags |= ((flags & IRQF_TRIGGER_RISING) ?
101 IRQF_TRIGGER_FALLING : IRQF_TRIGGER_RISING);
102 }
103
104 return flags;
105}
106
107static int pps_gpio_probe(struct platform_device *pdev)
108{
109 struct pps_gpio_device_data *data;
110 int irq;
111 int ret;
112 int err;
113 int pps_default_params;
114 const struct pps_gpio_platform_data *pdata = pdev->dev.platform_data;
115
116
117 /* GPIO setup */
118 ret = pps_gpio_setup(pdev);
119 if (ret)
120 return -EINVAL;
121
122 /* IRQ setup */
123 irq = gpio_to_irq(pdata->gpio_pin);
124 if (irq < 0) {
125 pr_err("failed to map GPIO to IRQ: %d\n", irq);
126 err = -EINVAL;
127 goto return_error;
128 }
129
130 /* allocate space for device info */
131 data = kzalloc(sizeof(struct pps_gpio_device_data), GFP_KERNEL);
132 if (data == NULL) {
133 err = -ENOMEM;
134 goto return_error;
135 }
136
137 /* initialize PPS specific parts of the bookkeeping data structure. */
138 data->info.mode = PPS_CAPTUREASSERT | PPS_OFFSETASSERT |
139 PPS_ECHOASSERT | PPS_CANWAIT | PPS_TSFMT_TSPEC;
140 if (pdata->capture_clear)
141 data->info.mode |= PPS_CAPTURECLEAR | PPS_OFFSETCLEAR |
142 PPS_ECHOCLEAR;
143 data->info.owner = THIS_MODULE;
144 snprintf(data->info.name, PPS_MAX_NAME_LEN - 1, "%s.%d",
145 pdev->name, pdev->id);
146
147 /* register PPS source */
148 pps_default_params = PPS_CAPTUREASSERT | PPS_OFFSETASSERT;
149 if (pdata->capture_clear)
150 pps_default_params |= PPS_CAPTURECLEAR | PPS_OFFSETCLEAR;
151 data->pps = pps_register_source(&data->info, pps_default_params);
152 if (data->pps == NULL) {
153 kfree(data);
154 pr_err("failed to register IRQ %d as PPS source\n", irq);
155 err = -EINVAL;
156 goto return_error;
157 }
158
159 data->irq = irq;
160 data->pdata = pdata;
161
162 /* register IRQ interrupt handler */
163 ret = request_irq(irq, pps_gpio_irq_handler,
164 get_irqf_trigger_flags(pdata), data->info.name, data);
165 if (ret) {
166 pps_unregister_source(data->pps);
167 kfree(data);
168 pr_err("failed to acquire IRQ %d\n", irq);
169 err = -EINVAL;
170 goto return_error;
171 }
172
173 platform_set_drvdata(pdev, data);
174 dev_info(data->pps->dev, "Registered IRQ %d as PPS source\n", irq);
175
176 return 0;
177
178return_error:
179 gpio_free(pdata->gpio_pin);
180 return err;
181}
182
183static int pps_gpio_remove(struct platform_device *pdev)
184{
185 struct pps_gpio_device_data *data = platform_get_drvdata(pdev);
186 const struct pps_gpio_platform_data *pdata = data->pdata;
187
188 platform_set_drvdata(pdev, NULL);
189 free_irq(data->irq, data);
190 gpio_free(pdata->gpio_pin);
191 pps_unregister_source(data->pps);
192 pr_info("removed IRQ %d as PPS source\n", data->irq);
193 kfree(data);
194 return 0;
195}
196
197static struct platform_driver pps_gpio_driver = {
198 .probe = pps_gpio_probe,
199 .remove = __devexit_p(pps_gpio_remove),
200 .driver = {
201 .name = PPS_GPIO_NAME,
202 .owner = THIS_MODULE
203 },
204};
205
206static int __init pps_gpio_init(void)
207{
208 int ret = platform_driver_register(&pps_gpio_driver);
209 if (ret < 0)
210 pr_err("failed to register platform driver\n");
211 return ret;
212}
213
214static void __exit pps_gpio_exit(void)
215{
216 platform_driver_unregister(&pps_gpio_driver);
217 pr_debug("unregistered platform driver\n");
218}
219
220module_init(pps_gpio_init);
221module_exit(pps_gpio_exit);
222
223MODULE_AUTHOR("Ricardo Martins <rasm@fe.up.pt>");
224MODULE_AUTHOR("James Nuss <jamesnuss@nanometrics.ca>");
225MODULE_DESCRIPTION("Use GPIO pin as PPS source");
226MODULE_LICENSE("GPL");
227MODULE_VERSION("1.0.0");