1/* 2 * Copyright (C) 2007 Google, Inc. 3 * Copyright (c) 2011, Code Aurora Forum. All rights reserved. 4 * Author: Brian Swetland <swetland@google.com> 5 * 6 * This software is licensed under the terms of the GNU General Public 7 * License version 2, as published by the Free Software Foundation, and 8 * may be copied, distributed, and modified under those terms. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 */ 16 17#include <linux/module.h> 18#include <linux/fs.h> 19#include <linux/cdev.h> 20#include <linux/device.h> 21#include <linux/wait.h> 22 23#include <linux/tty.h> 24#include <linux/tty_driver.h> 25#include <linux/tty_flip.h> 26 27#include <mach/msm_smd.h> 28 29#define MAX_SMD_TTYS 32 30 31struct smd_tty_info { 32 struct tty_port port; 33 smd_channel_t *ch; 34}; 35 36struct smd_tty_channel_desc { 37 int id; 38 const char *name; 39}; 40 41static struct smd_tty_info smd_tty[MAX_SMD_TTYS]; 42 43static const struct smd_tty_channel_desc smd_default_tty_channels[] = { 44 { .id = 0, .name = "SMD_DS" }, 45 { .id = 27, .name = "SMD_GPSNMEA" }, 46}; 47 48static const struct smd_tty_channel_desc *smd_tty_channels = 49 smd_default_tty_channels; 50static int smd_tty_channels_len = ARRAY_SIZE(smd_default_tty_channels); 51 52static void smd_tty_notify(void *priv, unsigned event) 53{ 54 unsigned char *ptr; 55 int avail; 56 struct smd_tty_info *info = priv; 57 struct tty_struct *tty; 58 59 if (event != SMD_EVENT_DATA) 60 return; 61 62 tty = tty_port_tty_get(&info->port); 63 if (!tty) 64 return; 65 66 for (;;) { 67 if (test_bit(TTY_THROTTLED, &tty->flags)) 68 break; 69 avail = smd_read_avail(info->ch); 70 if (avail == 0) 71 break; 72 73 avail = tty_prepare_flip_string(tty, &ptr, avail); 74 75 if (smd_read(info->ch, ptr, avail) != avail) { 76 /* shouldn't be possible since we're in interrupt 77 ** context here and nobody else could 'steal' our 78 ** characters. 79 */ 80 pr_err("OOPS - smd_tty_buffer mismatch?!"); 81 } 82 83 tty_flip_buffer_push(tty); 84 } 85 86 /* XXX only when writable and necessary */ 87 tty_wakeup(tty); 88 tty_kref_put(tty); 89} 90 91static int smd_tty_port_activate(struct tty_port *tport, struct tty_struct *tty) 92{ 93 int i, res = 0; 94 int n = tty->index; 95 const char *name = NULL; 96 struct smd_tty_info *info = smd_tty + n; 97 98 for (i = 0; i < smd_tty_channels_len; i++) { 99 if (smd_tty_channels[i].id == n) { 100 name = smd_tty_channels[i].name; 101 break; 102 } 103 } 104 if (!name) 105 return -ENODEV; 106 107 if (info->ch) 108 smd_kick(info->ch); 109 else 110 res = smd_open(name, &info->ch, info, smd_tty_notify); 111 112 if (!res) 113 tty->driver_data = info; 114 115 return res; 116} 117 118static void smd_tty_port_shutdown(struct tty_port *tport) 119{ 120 struct smd_tty_info *info; 121 struct tty_struct *tty = tty_port_tty_get(tport); 122 123 info = tty->driver_data; 124 if (info->ch) { 125 smd_close(info->ch); 126 info->ch = 0; 127 } 128 129 tty->driver_data = 0; 130 tty_kref_put(tty); 131} 132 133static int smd_tty_open(struct tty_struct *tty, struct file *f) 134{ 135 struct smd_tty_info *info = smd_tty + tty->index; 136 137 return tty_port_open(&info->port, tty, f); 138} 139 140static void smd_tty_close(struct tty_struct *tty, struct file *f) 141{ 142 struct smd_tty_info *info = tty->driver_data; 143 144 tty_port_close(&info->port, tty, f); 145} 146 147static int smd_tty_write(struct tty_struct *tty, 148 const unsigned char *buf, int len) 149{ 150 struct smd_tty_info *info = tty->driver_data; 151 int avail; 152 153 /* if we're writing to a packet channel we will 154 ** never be able to write more data than there 155 ** is currently space for 156 */ 157 avail = smd_write_avail(info->ch); 158 if (len > avail) 159 len = avail; 160 161 return smd_write(info->ch, buf, len); 162} 163 164static int smd_tty_write_room(struct tty_struct *tty) 165{ 166 struct smd_tty_info *info = tty->driver_data; 167 return smd_write_avail(info->ch); 168} 169 170static int smd_tty_chars_in_buffer(struct tty_struct *tty) 171{ 172 struct smd_tty_info *info = tty->driver_data; 173 return smd_read_avail(info->ch); 174} 175 176static void smd_tty_unthrottle(struct tty_struct *tty) 177{ 178 struct smd_tty_info *info = tty->driver_data; 179 smd_kick(info->ch); 180} 181 182static const struct tty_port_operations smd_tty_port_ops = { 183 .shutdown = smd_tty_port_shutdown, 184 .activate = smd_tty_port_activate, 185}; 186 187static const struct tty_operations smd_tty_ops = { 188 .open = smd_tty_open, 189 .close = smd_tty_close, 190 .write = smd_tty_write, 191 .write_room = smd_tty_write_room, 192 .chars_in_buffer = smd_tty_chars_in_buffer, 193 .unthrottle = smd_tty_unthrottle, 194}; 195 196static struct tty_driver *smd_tty_driver; 197 198static int __init smd_tty_init(void) 199{ 200 int ret, i; 201 202 smd_tty_driver = alloc_tty_driver(MAX_SMD_TTYS); 203 if (smd_tty_driver == 0) 204 return -ENOMEM; 205 206 smd_tty_driver->driver_name = "smd_tty_driver"; 207 smd_tty_driver->name = "smd"; 208 smd_tty_driver->major = 0; 209 smd_tty_driver->minor_start = 0; 210 smd_tty_driver->type = TTY_DRIVER_TYPE_SERIAL; 211 smd_tty_driver->subtype = SERIAL_TYPE_NORMAL; 212 smd_tty_driver->init_termios = tty_std_termios; 213 smd_tty_driver->init_termios.c_iflag = 0; 214 smd_tty_driver->init_termios.c_oflag = 0; 215 smd_tty_driver->init_termios.c_cflag = B38400 | CS8 | CREAD; 216 smd_tty_driver->init_termios.c_lflag = 0; 217 smd_tty_driver->flags = TTY_DRIVER_RESET_TERMIOS | 218 TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV; 219 tty_set_operations(smd_tty_driver, &smd_tty_ops); 220 221 ret = tty_register_driver(smd_tty_driver); 222 if (ret) 223 return ret; 224 225 for (i = 0; i < smd_tty_channels_len; i++) { 226 tty_port_init(&smd_tty[smd_tty_channels[i].id].port); 227 smd_tty[smd_tty_channels[i].id].port.ops = &smd_tty_port_ops; 228 tty_register_device(smd_tty_driver, smd_tty_channels[i].id, 0); 229 } 230 231 return 0; 232} 233 234module_init(smd_tty_init); 235