pps: serial clients support
[linux-3.10.git] / drivers / pps / clients / pps-ldisc.c
1 /*
2  * pps-ldisc.c -- PPS line discipline
3  *
4  *
5  * Copyright (C) 2008   Rodolfo Giometti <giometti@linux.it>
6  *
7  *   This program is free software; you can redistribute it and/or modify
8  *   it under the terms of the GNU General Public License as published by
9  *   the Free Software Foundation; either version 2 of the License, or
10  *   (at your option) any later version.
11  *
12  *   This program is distributed in the hope that it will be useful,
13  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *   GNU General Public License for more details.
16  *
17  *   You should have received a copy of the GNU General Public License
18  *   along with this program; if not, write to the Free Software
19  *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20  */
21
22 #include <linux/module.h>
23 #include <linux/serial_core.h>
24 #include <linux/tty.h>
25 #include <linux/pps_kernel.h>
26
27 #define PPS_TTY_MAGIC           0x0001
28
29 static void pps_tty_dcd_change(struct tty_struct *tty, unsigned int status,
30                                 struct timespec *ts)
31 {
32         int id = (long)tty->disc_data;
33         struct timespec __ts;
34         struct pps_ktime pps_ts;
35
36         /* First of all we get the time stamp... */
37         getnstimeofday(&__ts);
38
39         /* Does caller give us a timestamp? */
40         if (ts) {       /* Yes. Let's use it! */
41                 pps_ts.sec = ts->tv_sec;
42                 pps_ts.nsec = ts->tv_nsec;
43         } else {        /* No. Do it ourself! */
44                 pps_ts.sec = __ts.tv_sec;
45                 pps_ts.nsec = __ts.tv_nsec;
46         }
47
48         /* Now do the PPS event report */
49         pps_event(id, &pps_ts, status ? PPS_CAPTUREASSERT : PPS_CAPTURECLEAR,
50                         NULL);
51
52         pr_debug("PPS %s at %lu on source #%d\n",
53                         status ? "assert" : "clear", jiffies, id);
54 }
55
56 static int (*alias_n_tty_open)(struct tty_struct *tty);
57
58 static int pps_tty_open(struct tty_struct *tty)
59 {
60         struct pps_source_info info;
61         struct tty_driver *drv = tty->driver;
62         int index = tty->index + drv->name_base;
63         int ret;
64
65         info.owner = THIS_MODULE;
66         info.dev = NULL;
67         snprintf(info.name, PPS_MAX_NAME_LEN, "%s%d", drv->driver_name, index);
68         snprintf(info.path, PPS_MAX_NAME_LEN, "/dev/%s%d", drv->name, index);
69         info.mode = PPS_CAPTUREBOTH | \
70                         PPS_OFFSETASSERT | PPS_OFFSETCLEAR | \
71                         PPS_CANWAIT | PPS_TSFMT_TSPEC;
72
73         ret = pps_register_source(&info, PPS_CAPTUREBOTH | \
74                                 PPS_OFFSETASSERT | PPS_OFFSETCLEAR);
75         if (ret < 0) {
76                 pr_err("cannot register PPS source \"%s\"\n", info.path);
77                 return ret;
78         }
79         tty->disc_data = (void *)(long)ret;
80
81         /* Should open N_TTY ldisc too */
82         ret = alias_n_tty_open(tty);
83         if (ret < 0)
84                 pps_unregister_source((long)tty->disc_data);
85
86         pr_info("PPS source #%d \"%s\" added\n", ret, info.path);
87
88         return 0;
89 }
90
91 static void (*alias_n_tty_close)(struct tty_struct *tty);
92
93 static void pps_tty_close(struct tty_struct *tty)
94 {
95         int id = (long)tty->disc_data;
96
97         pps_unregister_source(id);
98         alias_n_tty_close(tty);
99
100         pr_info("PPS source #%d removed\n", id);
101 }
102
103 static struct tty_ldisc_ops pps_ldisc_ops;
104
105 /*
106  * Module stuff
107  */
108
109 static int __init pps_tty_init(void)
110 {
111         int err;
112
113         /* Inherit the N_TTY's ops */
114         n_tty_inherit_ops(&pps_ldisc_ops);
115
116         /* Save N_TTY's open()/close() methods */
117         alias_n_tty_open = pps_ldisc_ops.open;
118         alias_n_tty_close = pps_ldisc_ops.close;
119
120         /* Init PPS_TTY data */
121         pps_ldisc_ops.owner = THIS_MODULE;
122         pps_ldisc_ops.magic = PPS_TTY_MAGIC;
123         pps_ldisc_ops.name = "pps_tty";
124         pps_ldisc_ops.dcd_change = pps_tty_dcd_change;
125         pps_ldisc_ops.open = pps_tty_open;
126         pps_ldisc_ops.close = pps_tty_close;
127
128         err = tty_register_ldisc(N_PPS, &pps_ldisc_ops);
129         if (err)
130                 pr_err("can't register PPS line discipline\n");
131         else
132                 pr_info("PPS line discipline registered\n");
133
134         return err;
135 }
136
137 static void __exit pps_tty_cleanup(void)
138 {
139         int err;
140
141         err = tty_unregister_ldisc(N_PPS);
142         if (err)
143                 pr_err("can't unregister PPS line discipline\n");
144         else
145                 pr_info("PPS line discipline removed\n");
146 }
147
148 module_init(pps_tty_init);
149 module_exit(pps_tty_cleanup);
150
151 MODULE_ALIAS_LDISC(N_PPS);
152 MODULE_AUTHOR("Rodolfo Giometti <giometti@linux.it>");
153 MODULE_DESCRIPTION("PPS TTY device driver");
154 MODULE_LICENSE("GPL");