blob: a6b3d0cf127136b16905b147caa3545ae2514f5d [file] [log] [blame]
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001/* arch/arm/mach-msm/smd_tty.c
2 *
3 * Copyright (C) 2007 Google, Inc.
Duy Truong790f06d2013-02-13 16:38:12 -08004 * Copyright (c) 2009-2012, The Linux Foundation. All rights reserved.
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07005 * Author: Brian Swetland <swetland@google.com>
6 *
7 * This software is licensed under the terms of the GNU General Public
8 * License version 2, as published by the Free Software Foundation, and
9 * may be copied, distributed, and modified under those terms.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 */
17
18#include <linux/module.h>
19#include <linux/fs.h>
20#include <linux/cdev.h>
21#include <linux/device.h>
22#include <linux/interrupt.h>
23#include <linux/delay.h>
24#include <linux/wakelock.h>
25#include <linux/platform_device.h>
26#include <linux/sched.h>
27
28#include <linux/tty.h>
29#include <linux/tty_driver.h>
30#include <linux/tty_flip.h>
31
32#include <mach/msm_smd.h>
Stephen Boyd77db8bb2012-06-27 15:15:16 -070033#include <mach/subsystem_restart.h>
Jeff Hugo4c0ba6c2011-07-15 11:47:13 -060034#include <mach/socinfo.h>
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070035
36#include "smd_private.h"
37
38#define MAX_SMD_TTYS 37
39#define MAX_TTY_BUF_SIZE 2048
40
41static DEFINE_MUTEX(smd_tty_lock);
42
43static uint smd_tty_modem_wait;
44module_param_named(modem_wait, smd_tty_modem_wait,
45 uint, S_IRUGO | S_IWUSR | S_IWGRP);
46
47struct smd_tty_info {
48 smd_channel_t *ch;
49 struct tty_struct *tty;
50 struct wake_lock wake_lock;
51 int open_count;
52 struct tasklet_struct tty_tsklt;
53 struct timer_list buf_req_timer;
54 struct completion ch_allocated;
55 struct platform_driver driver;
56 void *pil;
57 int in_reset;
58 int in_reset_updated;
Karthikeyan Ramasubramaniand5e63af2011-07-29 11:30:59 -060059 int is_open;
60 wait_queue_head_t ch_opened_wait_queue;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070061 spinlock_t reset_lock;
Eric Holmberg513ad582011-12-14 16:27:13 -070062 struct smd_config *smd;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070063};
64
Eric Holmberg513ad582011-12-14 16:27:13 -070065/**
66 * SMD port configuration.
67 *
68 * @tty_dev_index Index into smd_tty[]
69 * @port_name Name of the SMD port
70 * @dev_name Name of the TTY Device (if NULL, @port_name is used)
71 * @edge SMD edge
72 */
73struct smd_config {
74 uint32_t tty_dev_index;
75 const char *port_name;
76 const char *dev_name;
77 uint32_t edge;
78};
79
80static struct smd_config smd_configs[] = {
81 {0, "DS", NULL, SMD_APPS_MODEM},
82 {1, "APPS_FM", NULL, SMD_APPS_WCNSS},
83 {2, "APPS_RIVA_BT_ACL", NULL, SMD_APPS_WCNSS},
84 {3, "APPS_RIVA_BT_CMD", NULL, SMD_APPS_WCNSS},
85 {4, "MBALBRIDGE", NULL, SMD_APPS_MODEM},
Jeff Hugodfb9c9a2012-04-10 14:22:47 -060086 {5, "APPS_RIVA_ANT_CMD", NULL, SMD_APPS_WCNSS},
87 {6, "APPS_RIVA_ANT_DATA", NULL, SMD_APPS_WCNSS},
Eric Holmberg513ad582011-12-14 16:27:13 -070088 {7, "DATA1", NULL, SMD_APPS_MODEM},
Eric Holmbergbe1dc5f2011-12-14 21:35:12 -070089 {11, "DATA11", NULL, SMD_APPS_MODEM},
Eric Holmberg513ad582011-12-14 16:27:13 -070090 {21, "DATA21", NULL, SMD_APPS_MODEM},
91 {27, "GPSNMEA", NULL, SMD_APPS_MODEM},
92 {36, "LOOPBACK", "LOOPBACK_TTY", SMD_APPS_MODEM},
93};
94#define DS_IDX 0
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070095#define LOOPBACK_IDX 36
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070096
97static struct delayed_work loopback_work;
98static struct smd_tty_info smd_tty[MAX_SMD_TTYS];
99
100static int is_in_reset(struct smd_tty_info *info)
101{
102 return info->in_reset;
103}
104
105static void buf_req_retry(unsigned long param)
106{
107 struct smd_tty_info *info = (struct smd_tty_info *)param;
Karthikeyan Ramasubramanianf8ad4132011-11-22 09:11:18 -0700108 unsigned long flags;
109
110 spin_lock_irqsave(&info->reset_lock, flags);
111 if (info->is_open) {
112 spin_unlock_irqrestore(&info->reset_lock, flags);
113 tasklet_hi_schedule(&info->tty_tsklt);
114 return;
115 }
116 spin_unlock_irqrestore(&info->reset_lock, flags);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700117}
118
119static void smd_tty_read(unsigned long param)
120{
121 unsigned char *ptr;
122 int avail;
123 struct smd_tty_info *info = (struct smd_tty_info *)param;
124 struct tty_struct *tty = info->tty;
125
126 if (!tty)
127 return;
128
129 for (;;) {
130 if (is_in_reset(info)) {
131 /* signal TTY clients using TTY_BREAK */
132 tty_insert_flip_char(tty, 0x00, TTY_BREAK);
133 tty_flip_buffer_push(tty);
134 break;
135 }
136
137 if (test_bit(TTY_THROTTLED, &tty->flags)) break;
138 avail = smd_read_avail(info->ch);
139 if (avail == 0)
140 break;
141
142 if (avail > MAX_TTY_BUF_SIZE)
143 avail = MAX_TTY_BUF_SIZE;
144
145 avail = tty_prepare_flip_string(tty, &ptr, avail);
146 if (avail <= 0) {
Stephen Boyd49a1e882012-07-02 17:28:13 -0700147 mod_timer(&info->buf_req_timer,
148 jiffies + msecs_to_jiffies(30));
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700149 return;
150 }
151
152 if (smd_read(info->ch, ptr, avail) != avail) {
153 /* shouldn't be possible since we're in interrupt
154 ** context here and nobody else could 'steal' our
155 ** characters.
156 */
157 printk(KERN_ERR "OOPS - smd_tty_buffer mismatch?!");
158 }
159
160 wake_lock_timeout(&info->wake_lock, HZ / 2);
161 tty_flip_buffer_push(tty);
162 }
163
164 /* XXX only when writable and necessary */
165 tty_wakeup(tty);
166}
167
168static void smd_tty_notify(void *priv, unsigned event)
169{
170 struct smd_tty_info *info = priv;
171 unsigned long flags;
172
173 switch (event) {
174 case SMD_EVENT_DATA:
Karthikeyan Ramasubramanianf8ad4132011-11-22 09:11:18 -0700175 spin_lock_irqsave(&info->reset_lock, flags);
176 if (!info->is_open) {
177 spin_unlock_irqrestore(&info->reset_lock, flags);
178 break;
179 }
180 spin_unlock_irqrestore(&info->reset_lock, flags);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700181 /* There may be clients (tty framework) that are blocked
182 * waiting for space to write data, so if a possible read
183 * interrupt came in wake anyone waiting and disable the
184 * interrupts
185 */
186 if (smd_write_avail(info->ch)) {
187 smd_disable_read_intr(info->ch);
188 if (info->tty)
189 wake_up_interruptible(&info->tty->write_wait);
190 }
191 tasklet_hi_schedule(&info->tty_tsklt);
192 break;
193
194 case SMD_EVENT_OPEN:
195 spin_lock_irqsave(&info->reset_lock, flags);
196 info->in_reset = 0;
197 info->in_reset_updated = 1;
Karthikeyan Ramasubramaniand5e63af2011-07-29 11:30:59 -0600198 info->is_open = 1;
199 wake_up_interruptible(&info->ch_opened_wait_queue);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700200 spin_unlock_irqrestore(&info->reset_lock, flags);
201 break;
202
203 case SMD_EVENT_CLOSE:
204 spin_lock_irqsave(&info->reset_lock, flags);
205 info->in_reset = 1;
206 info->in_reset_updated = 1;
Karthikeyan Ramasubramaniand5e63af2011-07-29 11:30:59 -0600207 info->is_open = 0;
208 wake_up_interruptible(&info->ch_opened_wait_queue);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700209 spin_unlock_irqrestore(&info->reset_lock, flags);
210 /* schedule task to send TTY_BREAK */
211 tasklet_hi_schedule(&info->tty_tsklt);
212
213 if (info->tty->index == LOOPBACK_IDX)
214 schedule_delayed_work(&loopback_work,
215 msecs_to_jiffies(1000));
216 break;
217 }
218}
219
220static uint32_t is_modem_smsm_inited(void)
221{
222 uint32_t modem_state;
223 uint32_t ready_state = (SMSM_INIT | SMSM_SMDINIT);
224
225 modem_state = smsm_get_state(SMSM_MODEM_STATE);
226 return (modem_state & ready_state) == ready_state;
227}
228
229static int smd_tty_open(struct tty_struct *tty, struct file *f)
230{
231 int res = 0;
Eric Holmberg513ad582011-12-14 16:27:13 -0700232 unsigned int n = tty->index;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700233 struct smd_tty_info *info;
Eric Holmberga53cf232012-02-28 13:41:44 -0700234 const char *peripheral = NULL;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700235
236
Eric Holmberg513ad582011-12-14 16:27:13 -0700237 if (n >= MAX_SMD_TTYS || !smd_tty[n].smd)
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700238 return -ENODEV;
239
240 info = smd_tty + n;
241
242 mutex_lock(&smd_tty_lock);
243 tty->driver_data = info;
244
245 if (info->open_count++ == 0) {
Eric Holmberga53cf232012-02-28 13:41:44 -0700246 peripheral = smd_edge_to_subsystem(smd_tty[n].smd->edge);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700247 if (peripheral) {
Stephen Boyd77db8bb2012-06-27 15:15:16 -0700248 info->pil = subsystem_get(peripheral);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700249 if (IS_ERR(info->pil)) {
250 res = PTR_ERR(info->pil);
251 goto out;
252 }
253
254 /* Wait for the modem SMSM to be inited for the SMD
255 * Loopback channel to be allocated at the modem. Since
256 * the wait need to be done atmost once, using msleep
257 * doesn't degrade the performance.
258 */
Eric Holmberg513ad582011-12-14 16:27:13 -0700259 if (n == LOOPBACK_IDX) {
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700260 if (!is_modem_smsm_inited())
261 msleep(5000);
262 smsm_change_state(SMSM_APPS_STATE,
263 0, SMSM_SMD_LOOPBACK);
264 msleep(100);
265 }
266
267
268 /*
269 * Wait for a channel to be allocated so we know
270 * the modem is ready enough.
271 */
272 if (smd_tty_modem_wait) {
273 res = wait_for_completion_interruptible_timeout(
274 &info->ch_allocated,
275 msecs_to_jiffies(smd_tty_modem_wait *
276 1000));
277
278 if (res == 0) {
279 pr_err("Timed out waiting for SMD"
280 " channel\n");
281 res = -ETIMEDOUT;
282 goto release_pil;
283 } else if (res < 0) {
284 pr_err("Error waiting for SMD channel:"
285 " %d\n",
286 res);
287 goto release_pil;
288 }
289
290 res = 0;
291 }
292 }
293
294
295 info->tty = tty;
296 tasklet_init(&info->tty_tsklt, smd_tty_read,
297 (unsigned long)info);
298 wake_lock_init(&info->wake_lock, WAKE_LOCK_SUSPEND,
Eric Holmberg513ad582011-12-14 16:27:13 -0700299 smd_tty[n].smd->port_name);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700300 if (!info->ch) {
Eric Holmberg513ad582011-12-14 16:27:13 -0700301 res = smd_named_open_on_edge(smd_tty[n].smd->port_name,
302 smd_tty[n].smd->edge,
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700303 &info->ch, info,
304 smd_tty_notify);
Karthikeyan Ramasubramaniand5e63af2011-07-29 11:30:59 -0600305 if (res < 0) {
306 pr_err("%s: %s open failed %d\n", __func__,
Eric Holmberg513ad582011-12-14 16:27:13 -0700307 smd_tty[n].smd->port_name, res);
Karthikeyan Ramasubramaniand5e63af2011-07-29 11:30:59 -0600308 goto release_pil;
309 }
310
311 res = wait_event_interruptible_timeout(
312 info->ch_opened_wait_queue,
313 info->is_open, (2 * HZ));
314 if (res == 0)
315 res = -ETIMEDOUT;
316 if (res < 0) {
317 pr_err("%s: wait for %s smd_open failed %d\n",
Eric Holmberg513ad582011-12-14 16:27:13 -0700318 __func__, smd_tty[n].smd->port_name,
319 res);
Karthikeyan Ramasubramaniand5e63af2011-07-29 11:30:59 -0600320 goto release_pil;
321 }
322 res = 0;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700323 }
324 }
325
326release_pil:
327 if (res < 0)
Stephen Boyd77db8bb2012-06-27 15:15:16 -0700328 subsystem_put(info->pil);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700329 else
330 smd_disable_read_intr(info->ch);
331out:
332 mutex_unlock(&smd_tty_lock);
333
334 return res;
335}
336
337static void smd_tty_close(struct tty_struct *tty, struct file *f)
338{
339 struct smd_tty_info *info = tty->driver_data;
Karthikeyan Ramasubramaniand563de52011-11-22 08:38:17 -0700340 unsigned long flags;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700341
342 if (info == 0)
343 return;
344
345 mutex_lock(&smd_tty_lock);
346 if (--info->open_count == 0) {
Karthikeyan Ramasubramaniand563de52011-11-22 08:38:17 -0700347 spin_lock_irqsave(&info->reset_lock, flags);
348 info->is_open = 0;
349 spin_unlock_irqrestore(&info->reset_lock, flags);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700350 if (info->tty) {
351 tasklet_kill(&info->tty_tsklt);
352 wake_lock_destroy(&info->wake_lock);
353 info->tty = 0;
354 }
355 tty->driver_data = 0;
356 del_timer(&info->buf_req_timer);
357 if (info->ch) {
358 smd_close(info->ch);
359 info->ch = 0;
Stephen Boyd77db8bb2012-06-27 15:15:16 -0700360 subsystem_put(info->pil);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700361 }
362 }
363 mutex_unlock(&smd_tty_lock);
364}
365
366static int smd_tty_write(struct tty_struct *tty, const unsigned char *buf, int len)
367{
368 struct smd_tty_info *info = tty->driver_data;
369 int avail;
370
371 /* if we're writing to a packet channel we will
372 ** never be able to write more data than there
373 ** is currently space for
374 */
375 if (is_in_reset(info))
376 return -ENETRESET;
377
378 avail = smd_write_avail(info->ch);
379 /* if no space, we'll have to setup a notification later to wake up the
380 * tty framework when space becomes avaliable
381 */
382 if (!avail) {
383 smd_enable_read_intr(info->ch);
384 return 0;
385 }
386 if (len > avail)
387 len = avail;
388
389 return smd_write(info->ch, buf, len);
390}
391
392static int smd_tty_write_room(struct tty_struct *tty)
393{
394 struct smd_tty_info *info = tty->driver_data;
395 return smd_write_avail(info->ch);
396}
397
398static int smd_tty_chars_in_buffer(struct tty_struct *tty)
399{
400 struct smd_tty_info *info = tty->driver_data;
401 return smd_read_avail(info->ch);
402}
403
404static void smd_tty_unthrottle(struct tty_struct *tty)
405{
406 struct smd_tty_info *info = tty->driver_data;
Karthikeyan Ramasubramanianf8ad4132011-11-22 09:11:18 -0700407 unsigned long flags;
408
409 spin_lock_irqsave(&info->reset_lock, flags);
410 if (info->is_open) {
411 spin_unlock_irqrestore(&info->reset_lock, flags);
412 tasklet_hi_schedule(&info->tty_tsklt);
413 return;
414 }
415 spin_unlock_irqrestore(&info->reset_lock, flags);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700416}
417
418/*
419 * Returns the current TIOCM status bits including:
420 * SMD Signals (DTR/DSR, CTS/RTS, CD, RI)
421 * TIOCM_OUT1 - reset state (1=in reset)
422 * TIOCM_OUT2 - reset state updated (1=updated)
423 */
424static int smd_tty_tiocmget(struct tty_struct *tty)
425{
426 struct smd_tty_info *info = tty->driver_data;
427 unsigned long flags;
428 int tiocm;
429
430 tiocm = smd_tiocmget(info->ch);
431
432 spin_lock_irqsave(&info->reset_lock, flags);
433 tiocm |= (info->in_reset ? TIOCM_OUT1 : 0);
434 if (info->in_reset_updated) {
435 tiocm |= TIOCM_OUT2;
436 info->in_reset_updated = 0;
437 }
438 spin_unlock_irqrestore(&info->reset_lock, flags);
439
440 return tiocm;
441}
442
443static int smd_tty_tiocmset(struct tty_struct *tty,
444 unsigned int set, unsigned int clear)
445{
446 struct smd_tty_info *info = tty->driver_data;
447
448 if (info->in_reset)
449 return -ENETRESET;
450
451 return smd_tiocmset(info->ch, set, clear);
452}
453
454static void loopback_probe_worker(struct work_struct *work)
455{
456 /* wait for modem to restart before requesting loopback server */
457 if (!is_modem_smsm_inited())
458 schedule_delayed_work(&loopback_work, msecs_to_jiffies(1000));
459 else
460 smsm_change_state(SMSM_APPS_STATE,
461 0, SMSM_SMD_LOOPBACK);
462}
463
464static struct tty_operations smd_tty_ops = {
465 .open = smd_tty_open,
466 .close = smd_tty_close,
467 .write = smd_tty_write,
468 .write_room = smd_tty_write_room,
469 .chars_in_buffer = smd_tty_chars_in_buffer,
470 .unthrottle = smd_tty_unthrottle,
471 .tiocmget = smd_tty_tiocmget,
472 .tiocmset = smd_tty_tiocmset,
473};
474
475static int smd_tty_dummy_probe(struct platform_device *pdev)
476{
Eric Holmberg513ad582011-12-14 16:27:13 -0700477 int n;
478 int idx;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700479
Eric Holmberg513ad582011-12-14 16:27:13 -0700480 for (n = 0; n < ARRAY_SIZE(smd_configs); ++n) {
481 idx = smd_configs[n].tty_dev_index;
482
483 if (!smd_configs[n].dev_name)
484 continue;
485
Eric Holmbergdaf36d12012-02-08 17:05:21 -0700486 if (pdev->id == smd_configs[n].edge &&
487 !strncmp(pdev->name, smd_configs[n].dev_name,
Eric Holmberg513ad582011-12-14 16:27:13 -0700488 SMD_MAX_CH_NAME_LEN)) {
489 complete_all(&smd_tty[idx].ch_allocated);
490 return 0;
491 }
492 }
493 pr_err("%s: unknown device '%s'\n", __func__, pdev->name);
494
495 return -ENODEV;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700496}
497
498static struct tty_driver *smd_tty_driver;
499
500static int __init smd_tty_init(void)
501{
502 int ret;
Eric Holmberg513ad582011-12-14 16:27:13 -0700503 int n;
504 int idx;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700505
506 smd_tty_driver = alloc_tty_driver(MAX_SMD_TTYS);
507 if (smd_tty_driver == 0)
508 return -ENOMEM;
509
510 smd_tty_driver->owner = THIS_MODULE;
511 smd_tty_driver->driver_name = "smd_tty_driver";
512 smd_tty_driver->name = "smd";
513 smd_tty_driver->major = 0;
514 smd_tty_driver->minor_start = 0;
515 smd_tty_driver->type = TTY_DRIVER_TYPE_SERIAL;
516 smd_tty_driver->subtype = SERIAL_TYPE_NORMAL;
517 smd_tty_driver->init_termios = tty_std_termios;
518 smd_tty_driver->init_termios.c_iflag = 0;
519 smd_tty_driver->init_termios.c_oflag = 0;
520 smd_tty_driver->init_termios.c_cflag = B38400 | CS8 | CREAD;
521 smd_tty_driver->init_termios.c_lflag = 0;
522 smd_tty_driver->flags = TTY_DRIVER_RESET_TERMIOS |
523 TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
524 tty_set_operations(smd_tty_driver, &smd_tty_ops);
525
526 ret = tty_register_driver(smd_tty_driver);
Eric Holmberg513ad582011-12-14 16:27:13 -0700527 if (ret) {
528 put_tty_driver(smd_tty_driver);
529 pr_err("%s: driver registration failed %d\n", __func__, ret);
530 return ret;
Jeff Hugo4c0ba6c2011-07-15 11:47:13 -0600531 }
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700532
Eric Holmberg513ad582011-12-14 16:27:13 -0700533 for (n = 0; n < ARRAY_SIZE(smd_configs); ++n) {
534 idx = smd_configs[n].tty_dev_index;
535
536 if (smd_configs[n].dev_name == NULL)
537 smd_configs[n].dev_name = smd_configs[n].port_name;
538
539 if (idx == DS_IDX) {
540 /*
541 * DS port uses the kernel API starting with
542 * 8660 Fusion. Only register the userspace
543 * platform device for older targets.
544 */
545 int legacy_ds = 0;
546
547 legacy_ds |= cpu_is_msm7x01() || cpu_is_msm7x25();
548 legacy_ds |= cpu_is_msm7x27() || cpu_is_msm7x30();
549 legacy_ds |= cpu_is_qsd8x50() || cpu_is_msm8x55();
Angshuman Sarkar4b931ed2012-01-05 13:38:36 +0530550 /*
551 * use legacy mode for 8660 Standalone (subtype 0)
552 */
Eric Holmberg513ad582011-12-14 16:27:13 -0700553 legacy_ds |= cpu_is_msm8x60() &&
Angshuman Sarkar4b931ed2012-01-05 13:38:36 +0530554 (socinfo_get_platform_subtype() == 0x0);
Eric Holmberg513ad582011-12-14 16:27:13 -0700555
556 if (!legacy_ds)
557 continue;
558 }
559
560 tty_register_device(smd_tty_driver, idx, 0);
561 init_completion(&smd_tty[idx].ch_allocated);
562
563 /* register platform device */
564 smd_tty[idx].driver.probe = smd_tty_dummy_probe;
565 smd_tty[idx].driver.driver.name = smd_configs[n].dev_name;
566 smd_tty[idx].driver.driver.owner = THIS_MODULE;
567 spin_lock_init(&smd_tty[idx].reset_lock);
568 smd_tty[idx].is_open = 0;
Stephen Boyd49a1e882012-07-02 17:28:13 -0700569 setup_timer(&smd_tty[idx].buf_req_timer, buf_req_retry,
570 (unsigned long)&smd_tty[idx]);
Eric Holmberg513ad582011-12-14 16:27:13 -0700571 init_waitqueue_head(&smd_tty[idx].ch_opened_wait_queue);
572 ret = platform_driver_register(&smd_tty[idx].driver);
573
574 if (ret) {
575 pr_err("%s: init failed %d (%d)\n", __func__, idx, ret);
576 smd_tty[idx].driver.probe = NULL;
577 goto out;
578 }
579 smd_tty[idx].smd = &smd_configs[n];
580 }
581 INIT_DELAYED_WORK(&loopback_work, loopback_probe_worker);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700582 return 0;
583
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700584out:
Eric Holmberg513ad582011-12-14 16:27:13 -0700585 /* unregister platform devices */
586 for (n = 0; n < ARRAY_SIZE(smd_configs); ++n) {
587 idx = smd_configs[n].tty_dev_index;
588
589 if (smd_tty[idx].driver.probe) {
590 platform_driver_unregister(&smd_tty[idx].driver);
591 tty_unregister_device(smd_tty_driver, idx);
592 }
593 }
594
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700595 tty_unregister_driver(smd_tty_driver);
596 put_tty_driver(smd_tty_driver);
597 return ret;
598}
599
600module_init(smd_tty_init);