xref: /openbmc/linux/drivers/pps/clients/pps-ldisc.c (revision baa7eb025ab14f3cba2e35c0a8648f9c9f01d24f)
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");
155