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 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 23 24 #include <linux/module.h> 25 #include <linux/serial_core.h> 26 #include <linux/tty.h> 27 #include <linux/pps_kernel.h> 28 29 #define PPS_TTY_MAGIC 0x0001 30 31 static void pps_tty_dcd_change(struct tty_struct *tty, unsigned int status, 32 struct pps_event_time *ts) 33 { 34 struct pps_device *pps = (struct pps_device *)tty->disc_data; 35 36 BUG_ON(pps == NULL); 37 38 /* Now do the PPS event report */ 39 pps_event(pps, ts, status ? PPS_CAPTUREASSERT : 40 PPS_CAPTURECLEAR, NULL); 41 42 dev_dbg(pps->dev, "PPS %s at %lu\n", 43 status ? "assert" : "clear", jiffies); 44 } 45 46 static int (*alias_n_tty_open)(struct tty_struct *tty); 47 48 static int pps_tty_open(struct tty_struct *tty) 49 { 50 struct pps_source_info info; 51 struct tty_driver *drv = tty->driver; 52 int index = tty->index + drv->name_base; 53 struct pps_device *pps; 54 int ret; 55 56 info.owner = THIS_MODULE; 57 info.dev = NULL; 58 snprintf(info.name, PPS_MAX_NAME_LEN, "%s%d", drv->driver_name, index); 59 snprintf(info.path, PPS_MAX_NAME_LEN, "/dev/%s%d", drv->name, index); 60 info.mode = PPS_CAPTUREBOTH | \ 61 PPS_OFFSETASSERT | PPS_OFFSETCLEAR | \ 62 PPS_CANWAIT | PPS_TSFMT_TSPEC; 63 64 pps = pps_register_source(&info, PPS_CAPTUREBOTH | \ 65 PPS_OFFSETASSERT | PPS_OFFSETCLEAR); 66 if (pps == NULL) { 67 pr_err("cannot register PPS source \"%s\"\n", info.path); 68 return -ENOMEM; 69 } 70 tty->disc_data = pps; 71 72 /* Should open N_TTY ldisc too */ 73 ret = alias_n_tty_open(tty); 74 if (ret < 0) { 75 pr_err("cannot open tty ldisc \"%s\"\n", info.path); 76 goto err_unregister; 77 } 78 79 dev_info(pps->dev, "source \"%s\" added\n", info.path); 80 81 return 0; 82 83 err_unregister: 84 tty->disc_data = NULL; 85 pps_unregister_source(pps); 86 return ret; 87 } 88 89 static void (*alias_n_tty_close)(struct tty_struct *tty); 90 91 static void pps_tty_close(struct tty_struct *tty) 92 { 93 struct pps_device *pps = (struct pps_device *)tty->disc_data; 94 95 alias_n_tty_close(tty); 96 97 tty->disc_data = NULL; 98 dev_info(pps->dev, "removed\n"); 99 pps_unregister_source(pps); 100 } 101 102 static struct tty_ldisc_ops pps_ldisc_ops; 103 104 /* 105 * Module stuff 106 */ 107 108 static int __init pps_tty_init(void) 109 { 110 int err; 111 112 /* Inherit the N_TTY's ops */ 113 n_tty_inherit_ops(&pps_ldisc_ops); 114 115 /* Save N_TTY's open()/close() methods */ 116 alias_n_tty_open = pps_ldisc_ops.open; 117 alias_n_tty_close = pps_ldisc_ops.close; 118 119 /* Init PPS_TTY data */ 120 pps_ldisc_ops.owner = THIS_MODULE; 121 pps_ldisc_ops.magic = PPS_TTY_MAGIC; 122 pps_ldisc_ops.name = "pps_tty"; 123 pps_ldisc_ops.dcd_change = pps_tty_dcd_change; 124 pps_ldisc_ops.open = pps_tty_open; 125 pps_ldisc_ops.close = pps_tty_close; 126 127 err = tty_register_ldisc(N_PPS, &pps_ldisc_ops); 128 if (err) 129 pr_err("can't register PPS line discipline\n"); 130 else 131 pr_info("PPS line discipline registered\n"); 132 133 return err; 134 } 135 136 static void __exit pps_tty_cleanup(void) 137 { 138 int err; 139 140 err = tty_unregister_ldisc(N_PPS); 141 if (err) 142 pr_err("can't unregister PPS line discipline\n"); 143 else 144 pr_info("PPS line discipline removed\n"); 145 } 146 147 module_init(pps_tty_init); 148 module_exit(pps_tty_cleanup); 149 150 MODULE_ALIAS_LDISC(N_PPS); 151 MODULE_AUTHOR("Rodolfo Giometti <giometti@linux.it>"); 152 MODULE_DESCRIPTION("PPS TTY device driver"); 153 MODULE_LICENSE("GPL"); 154