Merge tag 'v3.10.94' into update
[GitHub/mt8127/android_kernel_alcatel_ttab.git] / drivers / char / dcc_tty.c
CommitLineData
6fa3eb70
S
1/* drivers/char/dcc_tty.c
2 *
3 * Copyright (C) 2007 Google, Inc.
4 *
5 * This software is licensed under the terms of the GNU General Public
6 * License version 2, as published by the Free Software Foundation, and
7 * may be copied, distributed, and modified under those terms.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 */
15
16#include <linux/module.h>
17#include <linux/platform_device.h>
18#include <linux/delay.h>
19#include <linux/console.h>
20#include <linux/hrtimer.h>
21#include <linux/tty.h>
22#include <linux/tty_driver.h>
23#include <linux/tty_flip.h>
24
25MODULE_DESCRIPTION("DCC TTY Driver");
26MODULE_LICENSE("GPL");
27MODULE_VERSION("1.0");
28
29static spinlock_t g_dcc_tty_lock = SPIN_LOCK_UNLOCKED;
30static struct hrtimer g_dcc_timer;
31static char g_dcc_buffer[16];
32static int g_dcc_buffer_head;
33static int g_dcc_buffer_count;
34static unsigned g_dcc_write_delay_usecs = 1;
35static struct tty_driver *g_dcc_tty_driver;
36static struct tty_struct *g_dcc_tty;
37static int g_dcc_tty_open_count;
38
39static void dcc_poll_locked(void)
40{
41 char ch;
42 int rch;
43 int written;
44
45 while (g_dcc_buffer_count) {
46 ch = g_dcc_buffer[g_dcc_buffer_head];
47 asm(
48 "mrc 14, 0, r15, c0, c1, 0\n"
49 "mcrcc 14, 0, %1, c0, c5, 0\n"
50 "movcc %0, #1\n"
51 "movcs %0, #0\n"
52 : "=r" (written)
53 : "r" (ch)
54 );
55 if (written) {
56 if (ch == '\n')
57 g_dcc_buffer[g_dcc_buffer_head] = '\r';
58 else {
59 g_dcc_buffer_head = (g_dcc_buffer_head + 1) % ARRAY_SIZE(g_dcc_buffer);
60 g_dcc_buffer_count--;
61 if (g_dcc_tty)
62 tty_wakeup(g_dcc_tty);
63 }
64 g_dcc_write_delay_usecs = 1;
65 } else {
66 if (g_dcc_write_delay_usecs > 0x100)
67 break;
68 g_dcc_write_delay_usecs <<= 1;
69 udelay(g_dcc_write_delay_usecs);
70 }
71 }
72
73 if (g_dcc_tty && !test_bit(TTY_THROTTLED, &g_dcc_tty->flags)) {
74 asm(
75 "mrc 14, 0, %0, c0, c1, 0\n"
76 "tst %0, #(1 << 30)\n"
77 "moveq %0, #-1\n"
78 "mrcne 14, 0, %0, c0, c5, 0\n"
79 : "=r" (rch)
80 );
81 if (rch >= 0) {
82 ch = rch;
83 tty_insert_flip_string(g_dcc_tty, &ch, 1);
84 tty_flip_buffer_push(g_dcc_tty);
85 }
86 }
87
88
89 if (g_dcc_buffer_count)
90 hrtimer_start(&g_dcc_timer, ktime_set(0, g_dcc_write_delay_usecs * NSEC_PER_USEC), HRTIMER_MODE_REL);
91 else
92 hrtimer_start(&g_dcc_timer, ktime_set(0, 20 * NSEC_PER_MSEC), HRTIMER_MODE_REL);
93}
94
95static int dcc_tty_open(struct tty_struct * tty, struct file * filp)
96{
97 int ret;
98 unsigned long irq_flags;
99
100 spin_lock_irqsave(&g_dcc_tty_lock, irq_flags);
101 if (g_dcc_tty == NULL || g_dcc_tty == tty) {
102 g_dcc_tty = tty;
103 g_dcc_tty_open_count++;
104 ret = 0;
105 } else
106 ret = -EBUSY;
107 spin_unlock_irqrestore(&g_dcc_tty_lock, irq_flags);
108
109 printk("dcc_tty_open, tty %p, f_flags %x, returned %d\n", tty, filp->f_flags, ret);
110
111 return ret;
112}
113
114static void dcc_tty_close(struct tty_struct * tty, struct file * filp)
115{
116 printk("dcc_tty_close, tty %p, f_flags %x\n", tty, filp->f_flags);
117 if (g_dcc_tty == tty) {
118 if (--g_dcc_tty_open_count == 0)
119 g_dcc_tty = NULL;
120 }
121}
122
123static int dcc_write(const unsigned char *buf_start, int count)
124{
125 const unsigned char *buf = buf_start;
126 unsigned long irq_flags;
127 int copy_len;
128 int space_left;
129 int tail;
130
131 if (count < 1)
132 return 0;
133
134 spin_lock_irqsave(&g_dcc_tty_lock, irq_flags);
135 do {
136 tail = (g_dcc_buffer_head + g_dcc_buffer_count) % ARRAY_SIZE(g_dcc_buffer);
137 copy_len = ARRAY_SIZE(g_dcc_buffer) - tail;
138 space_left = ARRAY_SIZE(g_dcc_buffer) - g_dcc_buffer_count;
139 if (copy_len > space_left)
140 copy_len = space_left;
141 if (copy_len > count)
142 copy_len = count;
143 memcpy(&g_dcc_buffer[tail], buf, copy_len);
144 g_dcc_buffer_count += copy_len;
145 buf += copy_len;
146 count -= copy_len;
147 if (copy_len < count && copy_len < space_left) {
148 space_left -= copy_len;
149 copy_len = count;
150 if (copy_len > space_left) {
151 copy_len = space_left;
152 }
153 memcpy(g_dcc_buffer, buf, copy_len);
154 buf += copy_len;
155 count -= copy_len;
156 g_dcc_buffer_count += copy_len;
157 }
158 dcc_poll_locked();
159 space_left = ARRAY_SIZE(g_dcc_buffer) - g_dcc_buffer_count;
160 } while(count && space_left);
161 spin_unlock_irqrestore(&g_dcc_tty_lock, irq_flags);
162 return buf - buf_start;
163}
164
165static int dcc_tty_write(struct tty_struct * tty, const unsigned char *buf, int count)
166{
167 int ret;
168 /* printk("dcc_tty_write %p, %d\n", buf, count); */
169 ret = dcc_write(buf, count);
170 if (ret != count)
171 printk("dcc_tty_write %p, %d, returned %d\n", buf, count, ret);
172 return ret;
173}
174
175static int dcc_tty_write_room(struct tty_struct *tty)
176{
177 int space_left;
178 unsigned long irq_flags;
179
180 spin_lock_irqsave(&g_dcc_tty_lock, irq_flags);
181 space_left = ARRAY_SIZE(g_dcc_buffer) - g_dcc_buffer_count;
182 spin_unlock_irqrestore(&g_dcc_tty_lock, irq_flags);
183 return space_left;
184}
185
186static int dcc_tty_chars_in_buffer(struct tty_struct *tty)
187{
188 int ret;
189 asm(
190 "mrc 14, 0, %0, c0, c1, 0\n"
191 "mov %0, %0, LSR #30\n"
192 "and %0, %0, #1\n"
193 : "=r" (ret)
194 );
195 return ret;
196}
197
198static void dcc_tty_unthrottle(struct tty_struct * tty)
199{
200 unsigned long irq_flags;
201
202 spin_lock_irqsave(&g_dcc_tty_lock, irq_flags);
203 dcc_poll_locked();
204 spin_unlock_irqrestore(&g_dcc_tty_lock, irq_flags);
205}
206
207static enum hrtimer_restart dcc_tty_timer_func(struct hrtimer *timer)
208{
209 unsigned long irq_flags;
210
211 spin_lock_irqsave(&g_dcc_tty_lock, irq_flags);
212 dcc_poll_locked();
213 spin_unlock_irqrestore(&g_dcc_tty_lock, irq_flags);
214 return HRTIMER_NORESTART;
215}
216
217void dcc_console_write(struct console *co, const char *b, unsigned count)
218{
219#if 1
220 dcc_write(b, count);
221#else
222 /* blocking printk */
223 while (count > 0) {
224 int written;
225 written = dcc_write(b, count);
226 if (written) {
227 b += written;
228 count -= written;
229 }
230 }
231#endif
232}
233
234static struct tty_driver *dcc_console_device(struct console *c, int *index)
235{
236 *index = 0;
237 return g_dcc_tty_driver;
238}
239
240static int __init dcc_console_setup(struct console *co, char *options)
241{
242 if (co->index != 0)
243 return -ENODEV;
244 return 0;
245}
246
247
248static struct console dcc_console =
249{
250 .name = "ttyDCC",
251 .write = dcc_console_write,
252 .device = dcc_console_device,
253 .setup = dcc_console_setup,
254 .flags = CON_PRINTBUFFER,
255 .index = -1,
256};
257
258static struct tty_operations dcc_tty_ops = {
259 .open = dcc_tty_open,
260 .close = dcc_tty_close,
261 .write = dcc_tty_write,
262 .write_room = dcc_tty_write_room,
263 .chars_in_buffer = dcc_tty_chars_in_buffer,
264 .unthrottle = dcc_tty_unthrottle,
265};
266
267static int __init dcc_tty_init(void)
268{
269 int ret;
270
271 hrtimer_init(&g_dcc_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
272 g_dcc_timer.function = dcc_tty_timer_func;
273
274 g_dcc_tty_driver = alloc_tty_driver(1);
275 if (!g_dcc_tty_driver) {
276 printk(KERN_ERR "dcc_tty_probe: alloc_tty_driver failed\n");
277 ret = -ENOMEM;
278 goto err_alloc_tty_driver_failed;
279 }
280 g_dcc_tty_driver->owner = THIS_MODULE;
281 g_dcc_tty_driver->driver_name = "dcc";
282 g_dcc_tty_driver->name = "ttyDCC";
283 g_dcc_tty_driver->major = 0; // auto assign
284 g_dcc_tty_driver->minor_start = 0;
285 g_dcc_tty_driver->type = TTY_DRIVER_TYPE_SERIAL;
286 g_dcc_tty_driver->subtype = SERIAL_TYPE_NORMAL;
287 g_dcc_tty_driver->init_termios = tty_std_termios;
288 g_dcc_tty_driver->flags = TTY_DRIVER_RESET_TERMIOS | TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
289 tty_set_operations(g_dcc_tty_driver, &dcc_tty_ops);
290 ret = tty_register_driver(g_dcc_tty_driver);
291 if (ret) {
292 printk(KERN_ERR "dcc_tty_probe: tty_register_driver failed, %d\n", ret);
293 goto err_tty_register_driver_failed;
294 }
295 tty_register_device(g_dcc_tty_driver, 0, NULL);
296
297 register_console(&dcc_console);
298 hrtimer_start(&g_dcc_timer, ktime_set(0, 0), HRTIMER_MODE_REL);
299
300 return 0;
301
302err_tty_register_driver_failed:
303 put_tty_driver(g_dcc_tty_driver);
304 g_dcc_tty_driver = NULL;
305err_alloc_tty_driver_failed:
306 return ret;
307}
308
309static void __exit dcc_tty_exit(void)
310{
311 int ret;
312
313 tty_unregister_device(g_dcc_tty_driver, 0);
314 ret = tty_unregister_driver(g_dcc_tty_driver);
315 if (ret < 0) {
316 printk(KERN_ERR "dcc_tty_remove: tty_unregister_driver failed, %d\n", ret);
317 } else {
318 put_tty_driver(g_dcc_tty_driver);
319 }
320 g_dcc_tty_driver = NULL;
321}
322
323module_init(dcc_tty_init);
324module_exit(dcc_tty_exit);
325
326