1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
|
/* arch/arm/mach-msm/smd_tty.c
*
* Copyright (C) 2007 Google, Inc.
* Copyright (c) 2009-2012, The Linux Foundation. All rights reserved.
* Author: Brian Swetland <swetland@google.com>
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/wakelock.h>
#include <linux/platform_device.h>
#include <linux/sched.h>
#include <linux/pm_qos.h>
#include <linux/tty.h>
#include <linux/tty_driver.h>
#include <linux/tty_flip.h>
#include <mach/msm_smd.h>
#include <mach/peripheral-loader.h>
#include <mach/socinfo.h>
#include "smd_private.h"
#define MAX_SMD_TTYS 37
#define MAX_TTY_BUF_SIZE 2048
static DEFINE_MUTEX(smd_tty_lock);
static uint smd_tty_modem_wait;
module_param_named(modem_wait, smd_tty_modem_wait,
uint, S_IRUGO | S_IWUSR | S_IWGRP);
struct smd_tty_info {
smd_channel_t *ch;
struct tty_struct *tty;
struct wake_lock wake_lock;
int open_count;
struct tasklet_struct tty_tsklt;
struct timer_list buf_req_timer;
struct completion ch_allocated;
struct platform_driver driver;
void *pil;
int in_reset;
int in_reset_updated;
int is_open;
wait_queue_head_t ch_opened_wait_queue;
spinlock_t reset_lock;
struct smd_config *smd;
};
/**
* SMD port configuration.
*
* @tty_dev_index Index into smd_tty[]
* @port_name Name of the SMD port
* @dev_name Name of the TTY Device (if NULL, @port_name is used)
* @edge SMD edge
*/
struct smd_config {
uint32_t tty_dev_index;
const char *port_name;
const char *dev_name;
uint32_t edge;
};
static struct smd_config smd_configs[] = {
{0, "DS", NULL, SMD_APPS_MODEM},
{1, "APPS_FM", NULL, SMD_APPS_WCNSS},
{2, "APPS_RIVA_BT_ACL", NULL, SMD_APPS_WCNSS},
{3, "APPS_RIVA_BT_CMD", NULL, SMD_APPS_WCNSS},
{4, "MBALBRIDGE", NULL, SMD_APPS_MODEM},
{5, "APPS_RIVA_ANT_CMD", NULL, SMD_APPS_WCNSS},
{6, "APPS_RIVA_ANT_DATA", NULL, SMD_APPS_WCNSS},
{7, "DATA1", NULL, SMD_APPS_MODEM},
{11, "DATA11", NULL, SMD_APPS_MODEM},
{21, "DATA21", NULL, SMD_APPS_MODEM},
{27, "GPSNMEA", NULL, SMD_APPS_MODEM},
{36, "LOOPBACK", "LOOPBACK_TTY", SMD_APPS_MODEM},
};
#define DS_IDX 0
#define LOOPBACK_IDX 36
#define BT_ACL_IDX 2
#define BT_CMD_IDX 3
static struct delayed_work loopback_work;
static struct smd_tty_info smd_tty[MAX_SMD_TTYS];
static struct pm_qos_request smd_tty_qos_req;
static struct work_struct pm_qos_set_work;
static int is_in_reset(struct smd_tty_info *info)
{
return info->in_reset;
}
static void pm_qos_set_worker(struct work_struct *work)
{
/* keep the request for 500ms */
pm_qos_update_request_timeout(&smd_tty_qos_req,
0, jiffies_to_usecs(HZ / 2));
}
static void buf_req_retry(unsigned long param)
{
struct smd_tty_info *info = (struct smd_tty_info *)param;
unsigned long flags;
spin_lock_irqsave(&info->reset_lock, flags);
if (info->is_open) {
spin_unlock_irqrestore(&info->reset_lock, flags);
tasklet_hi_schedule(&info->tty_tsklt);
return;
}
spin_unlock_irqrestore(&info->reset_lock, flags);
}
static void smd_tty_read(unsigned long param)
{
unsigned char *ptr;
int avail;
struct smd_tty_info *info = (struct smd_tty_info *)param;
struct tty_struct *tty = info->tty;
if (!tty)
return;
for (;;) {
unsigned int n = info->tty->index;
if (is_in_reset(info)) {
if (n == BT_ACL_IDX || n == BT_CMD_IDX)
pr_err("%s: BT_IDX read in reset %d \n", __func__, n);
if ((n != BT_ACL_IDX) && (n != BT_CMD_IDX)) {
/* signal TTY clients using TTY_BREAK */
tty_insert_flip_char(tty, 0x00, TTY_BREAK);
tty_flip_buffer_push(tty);
break;
}
}
if (test_bit(TTY_THROTTLED, &tty->flags)) break;
avail = smd_read_avail(info->ch);
if (avail == 0)
break;
if (avail > MAX_TTY_BUF_SIZE)
avail = MAX_TTY_BUF_SIZE;
avail = tty_prepare_flip_string(tty, &ptr, avail);
if (avail <= 0) {
mod_timer(&info->buf_req_timer,
jiffies + msecs_to_jiffies(30));
return;
}
if (smd_read(info->ch, ptr, avail) != avail) {
/* shouldn't be possible since we're in interrupt
** context here and nobody else could 'steal' our
** characters.
*/
printk(KERN_ERR "OOPS - smd_tty_buffer mismatch?!");
}
#ifdef CONFIG_HAS_WAKELOCK
pr_debug("%s: lock wakelock %s\n", __func__, info->wake_lock.name);
#endif
wake_lock_timeout(&info->wake_lock, HZ / 2);
tty_flip_buffer_push(tty);
}
/* XXX only when writable and necessary */
tty_wakeup(tty);
}
static void smd_tty_notify(void *priv, unsigned event)
{
struct smd_tty_info *info = priv;
unsigned long flags;
unsigned char *ptr;
switch (event) {
case SMD_EVENT_DATA:
spin_lock_irqsave(&info->reset_lock, flags);
if (!info->is_open) {
spin_unlock_irqrestore(&info->reset_lock, flags);
break;
}
spin_unlock_irqrestore(&info->reset_lock, flags);
/* There may be clients (tty framework) that are blocked
* waiting for space to write data, so if a possible read
* interrupt came in wake anyone waiting and disable the
* interrupts
*/
if (smd_write_avail(info->ch)) {
smd_disable_read_intr(info->ch);
if (info->tty) {
unsigned int n = info->tty->index;
wake_up_interruptible(&info->tty->write_wait);
/* use pm_qos for BT performance */
if (n == BT_ACL_IDX || n == BT_CMD_IDX)
schedule_work(&pm_qos_set_work);
}
}
tasklet_hi_schedule(&info->tty_tsklt);
break;
case SMD_EVENT_OPEN:
if (is_in_reset(info)) {
unsigned int n = info->tty->index;
if (n == BT_CMD_IDX) {
pr_err("%s: BT_CMD_IDX Sending hardware error event to stack\n", __func__);
tty_prepare_flip_string(info->tty, &ptr, 0x03);
ptr[0] = 0x10;
ptr[1] = 0x01;
ptr[2] = 0x0A;
tty_flip_buffer_push(info->tty);
}
}
spin_lock_irqsave(&info->reset_lock, flags);
info->in_reset = 0;
info->in_reset_updated = 1;
info->is_open = 1;
wake_up_interruptible(&info->ch_opened_wait_queue);
spin_unlock_irqrestore(&info->reset_lock, flags);
break;
case SMD_EVENT_CLOSE:
spin_lock_irqsave(&info->reset_lock, flags);
info->in_reset = 1;
info->in_reset_updated = 1;
info->is_open = 0;
wake_up_interruptible(&info->ch_opened_wait_queue);
spin_unlock_irqrestore(&info->reset_lock, flags);
/* schedule task to send TTY_BREAK */
tasklet_hi_schedule(&info->tty_tsklt);
if (info->tty->index == LOOPBACK_IDX)
schedule_delayed_work(&loopback_work,
msecs_to_jiffies(1000));
break;
}
}
static uint32_t is_modem_smsm_inited(void)
{
uint32_t modem_state;
uint32_t ready_state = (SMSM_INIT | SMSM_SMDINIT);
modem_state = smsm_get_state(SMSM_MODEM_STATE);
return (modem_state & ready_state) == ready_state;
}
static int smd_tty_open(struct tty_struct *tty, struct file *f)
{
int res = 0;
unsigned int n = tty->index;
struct smd_tty_info *info;
const char *peripheral = NULL;
if (n >= MAX_SMD_TTYS || !smd_tty[n].smd)
return -ENODEV;
info = smd_tty + n;
mutex_lock(&smd_tty_lock);
tty->driver_data = info;
if (info->open_count++ == 0) {
peripheral = smd_edge_to_subsystem(smd_tty[n].smd->edge);
if (peripheral) {
info->pil = pil_get(peripheral);
if (IS_ERR(info->pil)) {
res = PTR_ERR(info->pil);
goto out;
}
/* Wait for the modem SMSM to be inited for the SMD
* Loopback channel to be allocated at the modem. Since
* the wait need to be done atmost once, using msleep
* doesn't degrade the performance.
*/
if (n == LOOPBACK_IDX) {
if (!is_modem_smsm_inited())
msleep(5000);
smsm_change_state(SMSM_APPS_STATE,
0, SMSM_SMD_LOOPBACK);
msleep(100);
}
/*
* Wait for a channel to be allocated so we know
* the modem is ready enough.
*/
if (smd_tty_modem_wait) {
res = wait_for_completion_interruptible_timeout(
&info->ch_allocated,
msecs_to_jiffies(smd_tty_modem_wait *
1000));
if (res == 0) {
pr_err("Timed out waiting for SMD"
" channel\n");
res = -ETIMEDOUT;
goto release_pil;
} else if (res < 0) {
pr_err("Error waiting for SMD channel:"
" %d\n",
res);
goto release_pil;
}
res = 0;
}
}
info->tty = tty;
tasklet_init(&info->tty_tsklt, smd_tty_read,
(unsigned long)info);
wake_lock_init(&info->wake_lock, WAKE_LOCK_SUSPEND,
smd_tty[n].smd->port_name);
if (!info->ch) {
res = smd_named_open_on_edge(smd_tty[n].smd->port_name,
smd_tty[n].smd->edge,
&info->ch, info,
smd_tty_notify);
if (res < 0) {
pr_err("%s: %s open failed %d\n", __func__,
smd_tty[n].smd->port_name, res);
goto release_pil;
}
res = wait_event_interruptible_timeout(
info->ch_opened_wait_queue,
info->is_open, (2 * HZ));
if (res == 0)
res = -ETIMEDOUT;
if (res < 0) {
pr_err("%s: wait for %s smd_open failed %d\n",
__func__, smd_tty[n].smd->port_name,
res);
goto release_pil;
}
res = 0;
}
}
release_pil:
if (res < 0)
pil_put(info->pil);
else
smd_disable_read_intr(info->ch);
out:
mutex_unlock(&smd_tty_lock);
return res;
}
static void smd_tty_close(struct tty_struct *tty, struct file *f)
{
struct smd_tty_info *info = tty->driver_data;
unsigned long flags;
if (info == 0)
return;
mutex_lock(&smd_tty_lock);
if (--info->open_count == 0) {
spin_lock_irqsave(&info->reset_lock, flags);
info->is_open = 0;
spin_unlock_irqrestore(&info->reset_lock, flags);
if (info->tty) {
tasklet_kill(&info->tty_tsklt);
wake_lock_destroy(&info->wake_lock);
info->tty = 0;
}
tty->driver_data = 0;
del_timer(&info->buf_req_timer);
if (info->ch) {
smd_close(info->ch);
info->ch = 0;
pil_put(info->pil);
}
}
mutex_unlock(&smd_tty_lock);
}
static int smd_tty_write(struct tty_struct *tty, const unsigned char *buf, int len)
{
struct smd_tty_info *info = tty->driver_data;
int avail;
/* if we're writing to a packet channel we will
** never be able to write more data than there
** is currently space for
*/
if (is_in_reset(info))
return -ENETRESET;
avail = smd_write_avail(info->ch);
/* if no space, we'll have to setup a notification later to wake up the
* tty framework when space becomes avaliable
*/
if (!avail) {
smd_enable_read_intr(info->ch);
return 0;
}
if (len > avail)
len = avail;
return smd_write(info->ch, buf, len);
}
static int smd_tty_write_room(struct tty_struct *tty)
{
struct smd_tty_info *info = tty->driver_data;
return smd_write_avail(info->ch);
}
static int smd_tty_chars_in_buffer(struct tty_struct *tty)
{
struct smd_tty_info *info = tty->driver_data;
return smd_read_avail(info->ch);
}
static void smd_tty_unthrottle(struct tty_struct *tty)
{
struct smd_tty_info *info = tty->driver_data;
unsigned long flags;
spin_lock_irqsave(&info->reset_lock, flags);
if (info->is_open) {
spin_unlock_irqrestore(&info->reset_lock, flags);
tasklet_hi_schedule(&info->tty_tsklt);
return;
}
spin_unlock_irqrestore(&info->reset_lock, flags);
}
/*
* Returns the current TIOCM status bits including:
* SMD Signals (DTR/DSR, CTS/RTS, CD, RI)
* TIOCM_OUT1 - reset state (1=in reset)
* TIOCM_OUT2 - reset state updated (1=updated)
*/
static int smd_tty_tiocmget(struct tty_struct *tty)
{
struct smd_tty_info *info = tty->driver_data;
unsigned long flags;
int tiocm;
tiocm = smd_tiocmget(info->ch);
spin_lock_irqsave(&info->reset_lock, flags);
tiocm |= (info->in_reset ? TIOCM_OUT1 : 0);
if (info->in_reset_updated) {
tiocm |= TIOCM_OUT2;
info->in_reset_updated = 0;
}
spin_unlock_irqrestore(&info->reset_lock, flags);
return tiocm;
}
static int smd_tty_tiocmset(struct tty_struct *tty,
unsigned int set, unsigned int clear)
{
struct smd_tty_info *info = tty->driver_data;
if (info->in_reset)
return -ENETRESET;
return smd_tiocmset(info->ch, set, clear);
}
static void loopback_probe_worker(struct work_struct *work)
{
/* wait for modem to restart before requesting loopback server */
if (!is_modem_smsm_inited())
schedule_delayed_work(&loopback_work, msecs_to_jiffies(1000));
else
smsm_change_state(SMSM_APPS_STATE,
0, SMSM_SMD_LOOPBACK);
}
static struct tty_operations smd_tty_ops = {
.open = smd_tty_open,
.close = smd_tty_close,
.write = smd_tty_write,
.write_room = smd_tty_write_room,
.chars_in_buffer = smd_tty_chars_in_buffer,
.unthrottle = smd_tty_unthrottle,
.tiocmget = smd_tty_tiocmget,
.tiocmset = smd_tty_tiocmset,
};
static int smd_tty_dummy_probe(struct platform_device *pdev)
{
int n;
int idx;
for (n = 0; n < ARRAY_SIZE(smd_configs); ++n) {
idx = smd_configs[n].tty_dev_index;
if (!smd_configs[n].dev_name)
continue;
if (pdev->id == smd_configs[n].edge &&
!strncmp(pdev->name, smd_configs[n].dev_name,
SMD_MAX_CH_NAME_LEN)) {
complete_all(&smd_tty[idx].ch_allocated);
return 0;
}
}
pr_err("%s: unknown device '%s'\n", __func__, pdev->name);
return -ENODEV;
}
static struct tty_driver *smd_tty_driver;
static int __init smd_tty_init(void)
{
int ret;
int n;
int idx;
smd_tty_driver = alloc_tty_driver(MAX_SMD_TTYS);
if (smd_tty_driver == 0)
return -ENOMEM;
smd_tty_driver->owner = THIS_MODULE;
smd_tty_driver->driver_name = "smd_tty_driver";
smd_tty_driver->name = "smd";
smd_tty_driver->major = 0;
smd_tty_driver->minor_start = 0;
smd_tty_driver->type = TTY_DRIVER_TYPE_SERIAL;
smd_tty_driver->subtype = SERIAL_TYPE_NORMAL;
smd_tty_driver->init_termios = tty_std_termios;
smd_tty_driver->init_termios.c_iflag = 0;
smd_tty_driver->init_termios.c_oflag = 0;
smd_tty_driver->init_termios.c_cflag = B38400 | CS8 | CREAD;
smd_tty_driver->init_termios.c_lflag = 0;
smd_tty_driver->flags = TTY_DRIVER_RESET_TERMIOS |
TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
tty_set_operations(smd_tty_driver, &smd_tty_ops);
ret = tty_register_driver(smd_tty_driver);
if (ret) {
put_tty_driver(smd_tty_driver);
pr_err("%s: driver registration failed %d\n", __func__, ret);
return ret;
}
for (n = 0; n < ARRAY_SIZE(smd_configs); ++n) {
idx = smd_configs[n].tty_dev_index;
if (smd_configs[n].dev_name == NULL)
smd_configs[n].dev_name = smd_configs[n].port_name;
if (idx == DS_IDX) {
/*
* DS port uses the kernel API starting with
* 8660 Fusion. Only register the userspace
* platform device for older targets.
*/
int legacy_ds = 0;
legacy_ds |= cpu_is_msm7x01() || cpu_is_msm7x25();
legacy_ds |= cpu_is_msm7x27() || cpu_is_msm7x30();
legacy_ds |= cpu_is_qsd8x50() || cpu_is_msm8x55();
/*
* use legacy mode for 8660 Standalone (subtype 0)
*/
legacy_ds |= cpu_is_msm8x60() &&
(socinfo_get_platform_subtype() == 0x0);
if (!legacy_ds)
continue;
}
tty_register_device(smd_tty_driver, idx, 0);
init_completion(&smd_tty[idx].ch_allocated);
/* register platform device */
smd_tty[idx].driver.probe = smd_tty_dummy_probe;
smd_tty[idx].driver.driver.name = smd_configs[n].dev_name;
smd_tty[idx].driver.driver.owner = THIS_MODULE;
spin_lock_init(&smd_tty[idx].reset_lock);
smd_tty[idx].is_open = 0;
setup_timer(&smd_tty[idx].buf_req_timer, buf_req_retry,
(unsigned long)&smd_tty[idx]);
init_waitqueue_head(&smd_tty[idx].ch_opened_wait_queue);
ret = platform_driver_register(&smd_tty[idx].driver);
if (ret) {
pr_err("%s: init failed %d (%d)\n", __func__, idx, ret);
smd_tty[idx].driver.probe = NULL;
goto out;
}
smd_tty[idx].smd = &smd_configs[n];
}
INIT_WORK(&pm_qos_set_work, pm_qos_set_worker);
INIT_DELAYED_WORK(&loopback_work, loopback_probe_worker);
pm_qos_add_request(&smd_tty_qos_req, PM_QOS_CPU_DMA_LATENCY,
PM_QOS_DEFAULT_VALUE);
return 0;
out:
/* unregister platform devices */
for (n = 0; n < ARRAY_SIZE(smd_configs); ++n) {
idx = smd_configs[n].tty_dev_index;
if (smd_tty[idx].driver.probe) {
platform_driver_unregister(&smd_tty[idx].driver);
tty_unregister_device(smd_tty_driver, idx);
}
}
tty_unregister_driver(smd_tty_driver);
put_tty_driver(smd_tty_driver);
return ret;
}
module_init(smd_tty_init);
|