forked from torvalds/linux
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathimon.c
2598 lines (2201 loc) · 70.1 KB
/
imon.c
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
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* imon.c: input and display driver for SoundGraph iMON IR/VFD/LCD
*
* Copyright(C) 2010 Jarod Wilson <[email protected]>
* Portions based on the original lirc_imon driver,
* Copyright(C) 2004 Venky Raju([email protected])
*
* Huge thanks to R. Geoff Newbury for invaluable debugging on the
* 0xffdc iMON devices, and for sending me one to hack on, without
* which the support for them wouldn't be nearly as good. Thanks
* also to the numerous 0xffdc device owners that tested auto-config
* support for me and provided debug dumps from their devices.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ":%s: " fmt, __func__
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/ktime.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/ratelimit.h>
#include <linux/input.h>
#include <linux/usb.h>
#include <linux/usb/input.h>
#include <media/rc-core.h>
#include <linux/timer.h>
#define MOD_AUTHOR "Jarod Wilson <[email protected]>"
#define MOD_DESC "Driver for SoundGraph iMON MultiMedia IR/Display"
#define MOD_NAME "imon"
#define MOD_VERSION "0.9.4"
#define DISPLAY_MINOR_BASE 144
#define DEVICE_NAME "lcd%d"
#define BUF_CHUNK_SIZE 8
#define BUF_SIZE 128
#define BIT_DURATION 250 /* each bit received is 250us */
#define IMON_CLOCK_ENABLE_PACKETS 2
/*** P R O T O T Y P E S ***/
/* USB Callback prototypes */
static int imon_probe(struct usb_interface *interface,
const struct usb_device_id *id);
static void imon_disconnect(struct usb_interface *interface);
static void usb_rx_callback_intf0(struct urb *urb);
static void usb_rx_callback_intf1(struct urb *urb);
static void usb_tx_callback(struct urb *urb);
/* suspend/resume support */
static int imon_resume(struct usb_interface *intf);
static int imon_suspend(struct usb_interface *intf, pm_message_t message);
/* Display file_operations function prototypes */
static int display_open(struct inode *inode, struct file *file);
static int display_close(struct inode *inode, struct file *file);
/* VFD write operation */
static ssize_t vfd_write(struct file *file, const char __user *buf,
size_t n_bytes, loff_t *pos);
/* LCD file_operations override function prototypes */
static ssize_t lcd_write(struct file *file, const char __user *buf,
size_t n_bytes, loff_t *pos);
/*** G L O B A L S ***/
struct imon_panel_key_table {
u64 hw_code;
u32 keycode;
};
struct imon_usb_dev_descr {
__u16 flags;
#define IMON_NO_FLAGS 0
#define IMON_NEED_20MS_PKT_DELAY 1
#define IMON_SUPPRESS_REPEATED_KEYS 2
struct imon_panel_key_table key_table[];
};
struct imon_context {
struct device *dev;
/* Newer devices have two interfaces */
struct usb_device *usbdev_intf0;
struct usb_device *usbdev_intf1;
bool display_supported; /* not all controllers do */
bool display_isopen; /* display port has been opened */
bool rf_device; /* true if iMON 2.4G LT/DT RF device */
bool rf_isassociating; /* RF remote associating */
bool dev_present_intf0; /* USB device presence, interface 0 */
bool dev_present_intf1; /* USB device presence, interface 1 */
struct mutex lock; /* to lock this object */
wait_queue_head_t remove_ok; /* For unexpected USB disconnects */
struct usb_endpoint_descriptor *rx_endpoint_intf0;
struct usb_endpoint_descriptor *rx_endpoint_intf1;
struct usb_endpoint_descriptor *tx_endpoint;
struct urb *rx_urb_intf0;
struct urb *rx_urb_intf1;
struct urb *tx_urb;
bool tx_control;
unsigned char usb_rx_buf[8];
unsigned char usb_tx_buf[8];
unsigned int send_packet_delay;
struct tx_t {
unsigned char data_buf[35]; /* user data buffer */
struct completion finished; /* wait for write to finish */
bool busy; /* write in progress */
int status; /* status of tx completion */
} tx;
u16 vendor; /* usb vendor ID */
u16 product; /* usb product ID */
struct rc_dev *rdev; /* rc-core device for remote */
struct input_dev *idev; /* input device for panel & IR mouse */
struct input_dev *touch; /* input device for touchscreen */
spinlock_t kc_lock; /* make sure we get keycodes right */
u32 kc; /* current input keycode */
u32 last_keycode; /* last reported input keycode */
u32 rc_scancode; /* the computed remote scancode */
u8 rc_toggle; /* the computed remote toggle bit */
u64 rc_proto; /* iMON or MCE (RC6) IR protocol? */
bool release_code; /* some keys send a release code */
u8 display_type; /* store the display type */
bool pad_mouse; /* toggle kbd(0)/mouse(1) mode */
char name_rdev[128]; /* rc input device name */
char phys_rdev[64]; /* rc input device phys path */
char name_idev[128]; /* input device name */
char phys_idev[64]; /* input device phys path */
char name_touch[128]; /* touch screen name */
char phys_touch[64]; /* touch screen phys path */
struct timer_list ttimer; /* touch screen timer */
int touch_x; /* x coordinate on touchscreen */
int touch_y; /* y coordinate on touchscreen */
const struct imon_usb_dev_descr *dev_descr;
/* device description with key */
/* table for front panels */
};
#define TOUCH_TIMEOUT (HZ/30)
/* vfd character device file operations */
static const struct file_operations vfd_fops = {
.owner = THIS_MODULE,
.open = &display_open,
.write = &vfd_write,
.release = &display_close,
.llseek = noop_llseek,
};
/* lcd character device file operations */
static const struct file_operations lcd_fops = {
.owner = THIS_MODULE,
.open = &display_open,
.write = &lcd_write,
.release = &display_close,
.llseek = noop_llseek,
};
enum {
IMON_DISPLAY_TYPE_AUTO = 0,
IMON_DISPLAY_TYPE_VFD = 1,
IMON_DISPLAY_TYPE_LCD = 2,
IMON_DISPLAY_TYPE_VGA = 3,
IMON_DISPLAY_TYPE_NONE = 4,
};
enum {
IMON_KEY_IMON = 0,
IMON_KEY_MCE = 1,
IMON_KEY_PANEL = 2,
};
static struct usb_class_driver imon_vfd_class = {
.name = DEVICE_NAME,
.fops = &vfd_fops,
.minor_base = DISPLAY_MINOR_BASE,
};
static struct usb_class_driver imon_lcd_class = {
.name = DEVICE_NAME,
.fops = &lcd_fops,
.minor_base = DISPLAY_MINOR_BASE,
};
/* imon receiver front panel/knob key table */
static const struct imon_usb_dev_descr imon_default_table = {
.flags = IMON_NO_FLAGS,
.key_table = {
{ 0x000000000f00ffeell, KEY_MEDIA }, /* Go */
{ 0x000000001200ffeell, KEY_UP },
{ 0x000000001300ffeell, KEY_DOWN },
{ 0x000000001400ffeell, KEY_LEFT },
{ 0x000000001500ffeell, KEY_RIGHT },
{ 0x000000001600ffeell, KEY_ENTER },
{ 0x000000001700ffeell, KEY_ESC },
{ 0x000000001f00ffeell, KEY_AUDIO },
{ 0x000000002000ffeell, KEY_VIDEO },
{ 0x000000002100ffeell, KEY_CAMERA },
{ 0x000000002700ffeell, KEY_DVD },
{ 0x000000002300ffeell, KEY_TV },
{ 0x000000002b00ffeell, KEY_EXIT },
{ 0x000000002c00ffeell, KEY_SELECT },
{ 0x000000002d00ffeell, KEY_MENU },
{ 0x000000000500ffeell, KEY_PREVIOUS },
{ 0x000000000700ffeell, KEY_REWIND },
{ 0x000000000400ffeell, KEY_STOP },
{ 0x000000003c00ffeell, KEY_PLAYPAUSE },
{ 0x000000000800ffeell, KEY_FASTFORWARD },
{ 0x000000000600ffeell, KEY_NEXT },
{ 0x000000010000ffeell, KEY_RIGHT },
{ 0x000001000000ffeell, KEY_LEFT },
{ 0x000000003d00ffeell, KEY_SELECT },
{ 0x000100000000ffeell, KEY_VOLUMEUP },
{ 0x010000000000ffeell, KEY_VOLUMEDOWN },
{ 0x000000000100ffeell, KEY_MUTE },
/* 0xffdc iMON MCE VFD */
{ 0x00010000ffffffeell, KEY_VOLUMEUP },
{ 0x01000000ffffffeell, KEY_VOLUMEDOWN },
{ 0x00000001ffffffeell, KEY_MUTE },
{ 0x0000000fffffffeell, KEY_MEDIA },
{ 0x00000012ffffffeell, KEY_UP },
{ 0x00000013ffffffeell, KEY_DOWN },
{ 0x00000014ffffffeell, KEY_LEFT },
{ 0x00000015ffffffeell, KEY_RIGHT },
{ 0x00000016ffffffeell, KEY_ENTER },
{ 0x00000017ffffffeell, KEY_ESC },
/* iMON Knob values */
{ 0x000100ffffffffeell, KEY_VOLUMEUP },
{ 0x010000ffffffffeell, KEY_VOLUMEDOWN },
{ 0x000008ffffffffeell, KEY_MUTE },
{ 0, KEY_RESERVED },
}
};
static const struct imon_usb_dev_descr imon_OEM_VFD = {
.flags = IMON_NEED_20MS_PKT_DELAY,
.key_table = {
{ 0x000000000f00ffeell, KEY_MEDIA }, /* Go */
{ 0x000000001200ffeell, KEY_UP },
{ 0x000000001300ffeell, KEY_DOWN },
{ 0x000000001400ffeell, KEY_LEFT },
{ 0x000000001500ffeell, KEY_RIGHT },
{ 0x000000001600ffeell, KEY_ENTER },
{ 0x000000001700ffeell, KEY_ESC },
{ 0x000000001f00ffeell, KEY_AUDIO },
{ 0x000000002b00ffeell, KEY_EXIT },
{ 0x000000002c00ffeell, KEY_SELECT },
{ 0x000000002d00ffeell, KEY_MENU },
{ 0x000000000500ffeell, KEY_PREVIOUS },
{ 0x000000000700ffeell, KEY_REWIND },
{ 0x000000000400ffeell, KEY_STOP },
{ 0x000000003c00ffeell, KEY_PLAYPAUSE },
{ 0x000000000800ffeell, KEY_FASTFORWARD },
{ 0x000000000600ffeell, KEY_NEXT },
{ 0x000000010000ffeell, KEY_RIGHT },
{ 0x000001000000ffeell, KEY_LEFT },
{ 0x000000003d00ffeell, KEY_SELECT },
{ 0x000100000000ffeell, KEY_VOLUMEUP },
{ 0x010000000000ffeell, KEY_VOLUMEDOWN },
{ 0x000000000100ffeell, KEY_MUTE },
/* 0xffdc iMON MCE VFD */
{ 0x00010000ffffffeell, KEY_VOLUMEUP },
{ 0x01000000ffffffeell, KEY_VOLUMEDOWN },
{ 0x00000001ffffffeell, KEY_MUTE },
{ 0x0000000fffffffeell, KEY_MEDIA },
{ 0x00000012ffffffeell, KEY_UP },
{ 0x00000013ffffffeell, KEY_DOWN },
{ 0x00000014ffffffeell, KEY_LEFT },
{ 0x00000015ffffffeell, KEY_RIGHT },
{ 0x00000016ffffffeell, KEY_ENTER },
{ 0x00000017ffffffeell, KEY_ESC },
/* iMON Knob values */
{ 0x000100ffffffffeell, KEY_VOLUMEUP },
{ 0x010000ffffffffeell, KEY_VOLUMEDOWN },
{ 0x000008ffffffffeell, KEY_MUTE },
{ 0, KEY_RESERVED },
}
};
/* imon receiver front panel/knob key table for DH102*/
static const struct imon_usb_dev_descr imon_DH102 = {
.flags = IMON_NO_FLAGS,
.key_table = {
{ 0x000100000000ffeell, KEY_VOLUMEUP },
{ 0x010000000000ffeell, KEY_VOLUMEDOWN },
{ 0x000000010000ffeell, KEY_MUTE },
{ 0x0000000f0000ffeell, KEY_MEDIA },
{ 0x000000120000ffeell, KEY_UP },
{ 0x000000130000ffeell, KEY_DOWN },
{ 0x000000140000ffeell, KEY_LEFT },
{ 0x000000150000ffeell, KEY_RIGHT },
{ 0x000000160000ffeell, KEY_ENTER },
{ 0x000000170000ffeell, KEY_ESC },
{ 0x0000002b0000ffeell, KEY_EXIT },
{ 0x0000002c0000ffeell, KEY_SELECT },
{ 0x0000002d0000ffeell, KEY_MENU },
{ 0, KEY_RESERVED }
}
};
/* imon ultrabay front panel key table */
static const struct imon_usb_dev_descr ultrabay_table = {
.flags = IMON_SUPPRESS_REPEATED_KEYS,
.key_table = {
{ 0x0000000f0000ffeell, KEY_MEDIA }, /* Go */
{ 0x000000000100ffeell, KEY_UP },
{ 0x000000000001ffeell, KEY_DOWN },
{ 0x000000160000ffeell, KEY_ENTER },
{ 0x0000001f0000ffeell, KEY_AUDIO }, /* Music */
{ 0x000000200000ffeell, KEY_VIDEO }, /* Movie */
{ 0x000000210000ffeell, KEY_CAMERA }, /* Photo */
{ 0x000000270000ffeell, KEY_DVD }, /* DVD */
{ 0x000000230000ffeell, KEY_TV }, /* TV */
{ 0x000000050000ffeell, KEY_PREVIOUS }, /* Previous */
{ 0x000000070000ffeell, KEY_REWIND },
{ 0x000000040000ffeell, KEY_STOP },
{ 0x000000020000ffeell, KEY_PLAYPAUSE },
{ 0x000000080000ffeell, KEY_FASTFORWARD },
{ 0x000000060000ffeell, KEY_NEXT }, /* Next */
{ 0x000100000000ffeell, KEY_VOLUMEUP },
{ 0x010000000000ffeell, KEY_VOLUMEDOWN },
{ 0x000000010000ffeell, KEY_MUTE },
{ 0, KEY_RESERVED },
}
};
/*
* USB Device ID for iMON USB Control Boards
*
* The Windows drivers contain 6 different inf files, more or less one for
* each new device until the 0x0034-0x0046 devices, which all use the same
* driver. Some of the devices in the 34-46 range haven't been definitively
* identified yet. Early devices have either a TriGem Computer, Inc. or a
* Samsung vendor ID (0x0aa8 and 0x04e8 respectively), while all later
* devices use the SoundGraph vendor ID (0x15c2). This driver only supports
* the ffdc and later devices, which do onboard decoding.
*/
static const struct usb_device_id imon_usb_id_table[] = {
/*
* Several devices with this same device ID, all use iMON_PAD.inf
* SoundGraph iMON PAD (IR & VFD)
* SoundGraph iMON PAD (IR & LCD)
* SoundGraph iMON Knob (IR only)
*/
{ USB_DEVICE(0x15c2, 0xffdc),
.driver_info = (unsigned long)&imon_default_table },
/*
* Newer devices, all driven by the latest iMON Windows driver, full
* list of device IDs extracted via 'strings Setup/data1.hdr |grep 15c2'
* Need user input to fill in details on unknown devices.
*/
/* SoundGraph iMON OEM Touch LCD (IR & 7" VGA LCD) */
{ USB_DEVICE(0x15c2, 0x0034),
.driver_info = (unsigned long)&imon_DH102 },
/* SoundGraph iMON OEM Touch LCD (IR & 4.3" VGA LCD) */
{ USB_DEVICE(0x15c2, 0x0035),
.driver_info = (unsigned long)&imon_default_table},
/* SoundGraph iMON OEM VFD (IR & VFD) */
{ USB_DEVICE(0x15c2, 0x0036),
.driver_info = (unsigned long)&imon_OEM_VFD },
/* device specifics unknown */
{ USB_DEVICE(0x15c2, 0x0037),
.driver_info = (unsigned long)&imon_default_table},
/* SoundGraph iMON OEM LCD (IR & LCD) */
{ USB_DEVICE(0x15c2, 0x0038),
.driver_info = (unsigned long)&imon_default_table},
/* SoundGraph iMON UltraBay (IR & LCD) */
{ USB_DEVICE(0x15c2, 0x0039),
.driver_info = (unsigned long)&imon_default_table},
/* device specifics unknown */
{ USB_DEVICE(0x15c2, 0x003a),
.driver_info = (unsigned long)&imon_default_table},
/* device specifics unknown */
{ USB_DEVICE(0x15c2, 0x003b),
.driver_info = (unsigned long)&imon_default_table},
/* SoundGraph iMON OEM Inside (IR only) */
{ USB_DEVICE(0x15c2, 0x003c),
.driver_info = (unsigned long)&imon_default_table},
/* device specifics unknown */
{ USB_DEVICE(0x15c2, 0x003d),
.driver_info = (unsigned long)&imon_default_table},
/* device specifics unknown */
{ USB_DEVICE(0x15c2, 0x003e),
.driver_info = (unsigned long)&imon_default_table},
/* device specifics unknown */
{ USB_DEVICE(0x15c2, 0x003f),
.driver_info = (unsigned long)&imon_default_table},
/* device specifics unknown */
{ USB_DEVICE(0x15c2, 0x0040),
.driver_info = (unsigned long)&imon_default_table},
/* SoundGraph iMON MINI (IR only) */
{ USB_DEVICE(0x15c2, 0x0041),
.driver_info = (unsigned long)&imon_default_table},
/* Antec Veris Multimedia Station EZ External (IR only) */
{ USB_DEVICE(0x15c2, 0x0042),
.driver_info = (unsigned long)&imon_default_table},
/* Antec Veris Multimedia Station Basic Internal (IR only) */
{ USB_DEVICE(0x15c2, 0x0043),
.driver_info = (unsigned long)&imon_default_table},
/* Antec Veris Multimedia Station Elite (IR & VFD) */
{ USB_DEVICE(0x15c2, 0x0044),
.driver_info = (unsigned long)&imon_default_table},
/* Antec Veris Multimedia Station Premiere (IR & LCD) */
{ USB_DEVICE(0x15c2, 0x0045),
.driver_info = (unsigned long)&imon_default_table},
/* device specifics unknown */
{ USB_DEVICE(0x15c2, 0x0046),
.driver_info = (unsigned long)&imon_default_table},
{}
};
/* USB Device data */
static struct usb_driver imon_driver = {
.name = MOD_NAME,
.probe = imon_probe,
.disconnect = imon_disconnect,
.suspend = imon_suspend,
.resume = imon_resume,
.id_table = imon_usb_id_table,
};
/* to prevent races between open() and disconnect(), probing, etc */
static DEFINE_MUTEX(driver_lock);
/* Module bookkeeping bits */
MODULE_AUTHOR(MOD_AUTHOR);
MODULE_DESCRIPTION(MOD_DESC);
MODULE_VERSION(MOD_VERSION);
MODULE_LICENSE("GPL");
MODULE_DEVICE_TABLE(usb, imon_usb_id_table);
static bool debug;
module_param(debug, bool, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(debug, "Debug messages: 0=no, 1=yes (default: no)");
/* lcd, vfd, vga or none? should be auto-detected, but can be overridden... */
static int display_type;
module_param(display_type, int, S_IRUGO);
MODULE_PARM_DESC(display_type, "Type of attached display. 0=autodetect, 1=vfd, 2=lcd, 3=vga, 4=none (default: autodetect)");
static int pad_stabilize = 1;
module_param(pad_stabilize, int, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(pad_stabilize, "Apply stabilization algorithm to iMON PAD presses in arrow key mode. 0=disable, 1=enable (default).");
/*
* In certain use cases, mouse mode isn't really helpful, and could actually
* cause confusion, so allow disabling it when the IR device is open.
*/
static bool nomouse;
module_param(nomouse, bool, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(nomouse, "Disable mouse input device mode when IR device is open. 0=don't disable, 1=disable. (default: don't disable)");
/* threshold at which a pad push registers as an arrow key in kbd mode */
static int pad_thresh;
module_param(pad_thresh, int, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(pad_thresh, "Threshold at which a pad push registers as an arrow key in kbd mode (default: 28)");
static void free_imon_context(struct imon_context *ictx)
{
struct device *dev = ictx->dev;
usb_free_urb(ictx->tx_urb);
usb_free_urb(ictx->rx_urb_intf0);
usb_free_urb(ictx->rx_urb_intf1);
kfree(ictx);
dev_dbg(dev, "%s: iMON context freed\n", __func__);
}
/*
* Called when the Display device (e.g. /dev/lcd0)
* is opened by the application.
*/
static int display_open(struct inode *inode, struct file *file)
{
struct usb_interface *interface;
struct imon_context *ictx = NULL;
int subminor;
int retval = 0;
/* prevent races with disconnect */
mutex_lock(&driver_lock);
subminor = iminor(inode);
interface = usb_find_interface(&imon_driver, subminor);
if (!interface) {
pr_err("could not find interface for minor %d\n", subminor);
retval = -ENODEV;
goto exit;
}
ictx = usb_get_intfdata(interface);
if (!ictx) {
pr_err("no context found for minor %d\n", subminor);
retval = -ENODEV;
goto exit;
}
mutex_lock(&ictx->lock);
if (!ictx->display_supported) {
pr_err("display not supported by device\n");
retval = -ENODEV;
} else if (ictx->display_isopen) {
pr_err("display port is already open\n");
retval = -EBUSY;
} else {
ictx->display_isopen = true;
file->private_data = ictx;
dev_dbg(ictx->dev, "display port opened\n");
}
mutex_unlock(&ictx->lock);
exit:
mutex_unlock(&driver_lock);
return retval;
}
/*
* Called when the display device (e.g. /dev/lcd0)
* is closed by the application.
*/
static int display_close(struct inode *inode, struct file *file)
{
struct imon_context *ictx = NULL;
int retval = 0;
ictx = file->private_data;
if (!ictx) {
pr_err("no context for device\n");
return -ENODEV;
}
mutex_lock(&ictx->lock);
if (!ictx->display_supported) {
pr_err("display not supported by device\n");
retval = -ENODEV;
} else if (!ictx->display_isopen) {
pr_err("display is not open\n");
retval = -EIO;
} else {
ictx->display_isopen = false;
dev_dbg(ictx->dev, "display port closed\n");
}
mutex_unlock(&ictx->lock);
return retval;
}
/*
* Sends a packet to the device -- this function must be called with
* ictx->lock held, or its unlock/lock sequence while waiting for tx
* to complete can/will lead to a deadlock.
*/
static int send_packet(struct imon_context *ictx)
{
unsigned int pipe;
unsigned long timeout;
int interval = 0;
int retval = 0;
struct usb_ctrlrequest *control_req = NULL;
/* Check if we need to use control or interrupt urb */
if (!ictx->tx_control) {
pipe = usb_sndintpipe(ictx->usbdev_intf0,
ictx->tx_endpoint->bEndpointAddress);
interval = ictx->tx_endpoint->bInterval;
usb_fill_int_urb(ictx->tx_urb, ictx->usbdev_intf0, pipe,
ictx->usb_tx_buf,
sizeof(ictx->usb_tx_buf),
usb_tx_callback, ictx, interval);
ictx->tx_urb->actual_length = 0;
} else {
/* fill request into kmalloc'ed space: */
control_req = kmalloc(sizeof(*control_req), GFP_KERNEL);
if (control_req == NULL)
return -ENOMEM;
/* setup packet is '21 09 0200 0001 0008' */
control_req->bRequestType = 0x21;
control_req->bRequest = 0x09;
control_req->wValue = cpu_to_le16(0x0200);
control_req->wIndex = cpu_to_le16(0x0001);
control_req->wLength = cpu_to_le16(0x0008);
/* control pipe is endpoint 0x00 */
pipe = usb_sndctrlpipe(ictx->usbdev_intf0, 0);
/* build the control urb */
usb_fill_control_urb(ictx->tx_urb, ictx->usbdev_intf0,
pipe, (unsigned char *)control_req,
ictx->usb_tx_buf,
sizeof(ictx->usb_tx_buf),
usb_tx_callback, ictx);
ictx->tx_urb->actual_length = 0;
}
reinit_completion(&ictx->tx.finished);
ictx->tx.busy = true;
smp_rmb(); /* ensure later readers know we're busy */
retval = usb_submit_urb(ictx->tx_urb, GFP_KERNEL);
if (retval) {
ictx->tx.busy = false;
smp_rmb(); /* ensure later readers know we're not busy */
pr_err_ratelimited("error submitting urb(%d)\n", retval);
} else {
/* Wait for transmission to complete (or abort) */
mutex_unlock(&ictx->lock);
retval = wait_for_completion_interruptible(
&ictx->tx.finished);
if (retval) {
usb_kill_urb(ictx->tx_urb);
pr_err_ratelimited("task interrupted\n");
}
mutex_lock(&ictx->lock);
retval = ictx->tx.status;
if (retval)
pr_err_ratelimited("packet tx failed (%d)\n", retval);
}
kfree(control_req);
/*
* Induce a mandatory delay before returning, as otherwise,
* send_packet can get called so rapidly as to overwhelm the device,
* particularly on faster systems and/or those with quirky usb.
*/
timeout = msecs_to_jiffies(ictx->send_packet_delay);
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout(timeout);
return retval;
}
/*
* Sends an associate packet to the iMON 2.4G.
*
* This might not be such a good idea, since it has an id collision with
* some versions of the "IR & VFD" combo. The only way to determine if it
* is an RF version is to look at the product description string. (Which
* we currently do not fetch).
*/
static int send_associate_24g(struct imon_context *ictx)
{
int retval;
const unsigned char packet[8] = { 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x20 };
if (!ictx) {
pr_err("no context for device\n");
return -ENODEV;
}
if (!ictx->dev_present_intf0) {
pr_err("no iMON device present\n");
return -ENODEV;
}
memcpy(ictx->usb_tx_buf, packet, sizeof(packet));
retval = send_packet(ictx);
return retval;
}
/*
* Sends packets to setup and show clock on iMON display
*
* Arguments: year - last 2 digits of year, month - 1..12,
* day - 1..31, dow - day of the week (0-Sun...6-Sat),
* hour - 0..23, minute - 0..59, second - 0..59
*/
static int send_set_imon_clock(struct imon_context *ictx,
unsigned int year, unsigned int month,
unsigned int day, unsigned int dow,
unsigned int hour, unsigned int minute,
unsigned int second)
{
unsigned char clock_enable_pkt[IMON_CLOCK_ENABLE_PACKETS][8];
int retval = 0;
int i;
if (!ictx) {
pr_err("no context for device\n");
return -ENODEV;
}
switch (ictx->display_type) {
case IMON_DISPLAY_TYPE_LCD:
clock_enable_pkt[0][0] = 0x80;
clock_enable_pkt[0][1] = year;
clock_enable_pkt[0][2] = month-1;
clock_enable_pkt[0][3] = day;
clock_enable_pkt[0][4] = hour;
clock_enable_pkt[0][5] = minute;
clock_enable_pkt[0][6] = second;
clock_enable_pkt[1][0] = 0x80;
clock_enable_pkt[1][1] = 0;
clock_enable_pkt[1][2] = 0;
clock_enable_pkt[1][3] = 0;
clock_enable_pkt[1][4] = 0;
clock_enable_pkt[1][5] = 0;
clock_enable_pkt[1][6] = 0;
if (ictx->product == 0xffdc) {
clock_enable_pkt[0][7] = 0x50;
clock_enable_pkt[1][7] = 0x51;
} else {
clock_enable_pkt[0][7] = 0x88;
clock_enable_pkt[1][7] = 0x8a;
}
break;
case IMON_DISPLAY_TYPE_VFD:
clock_enable_pkt[0][0] = year;
clock_enable_pkt[0][1] = month-1;
clock_enable_pkt[0][2] = day;
clock_enable_pkt[0][3] = dow;
clock_enable_pkt[0][4] = hour;
clock_enable_pkt[0][5] = minute;
clock_enable_pkt[0][6] = second;
clock_enable_pkt[0][7] = 0x40;
clock_enable_pkt[1][0] = 0;
clock_enable_pkt[1][1] = 0;
clock_enable_pkt[1][2] = 1;
clock_enable_pkt[1][3] = 0;
clock_enable_pkt[1][4] = 0;
clock_enable_pkt[1][5] = 0;
clock_enable_pkt[1][6] = 0;
clock_enable_pkt[1][7] = 0x42;
break;
default:
return -ENODEV;
}
for (i = 0; i < IMON_CLOCK_ENABLE_PACKETS; i++) {
memcpy(ictx->usb_tx_buf, clock_enable_pkt[i], 8);
retval = send_packet(ictx);
if (retval) {
pr_err("send_packet failed for packet %d\n", i);
break;
}
}
return retval;
}
/*
* These are the sysfs functions to handle the association on the iMON 2.4G LT.
*/
static ssize_t show_associate_remote(struct device *d,
struct device_attribute *attr,
char *buf)
{
struct imon_context *ictx = dev_get_drvdata(d);
if (!ictx)
return -ENODEV;
mutex_lock(&ictx->lock);
if (ictx->rf_isassociating)
strscpy(buf, "associating\n", PAGE_SIZE);
else
strscpy(buf, "closed\n", PAGE_SIZE);
dev_info(d, "Visit https://www.lirc.org/html/imon-24g.html for instructions on how to associate your iMON 2.4G DT/LT remote\n");
mutex_unlock(&ictx->lock);
return strlen(buf);
}
static ssize_t store_associate_remote(struct device *d,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct imon_context *ictx;
ictx = dev_get_drvdata(d);
if (!ictx)
return -ENODEV;
mutex_lock(&ictx->lock);
ictx->rf_isassociating = true;
send_associate_24g(ictx);
mutex_unlock(&ictx->lock);
return count;
}
/*
* sysfs functions to control internal imon clock
*/
static ssize_t show_imon_clock(struct device *d,
struct device_attribute *attr, char *buf)
{
struct imon_context *ictx = dev_get_drvdata(d);
size_t len;
if (!ictx)
return -ENODEV;
mutex_lock(&ictx->lock);
if (!ictx->display_supported) {
len = snprintf(buf, PAGE_SIZE, "Not supported.");
} else {
len = snprintf(buf, PAGE_SIZE,
"To set the clock on your iMON display:\n"
"# date \"+%%y %%m %%d %%w %%H %%M %%S\" > imon_clock\n"
"%s", ictx->display_isopen ?
"\nNOTE: imon device must be closed\n" : "");
}
mutex_unlock(&ictx->lock);
return len;
}
static ssize_t store_imon_clock(struct device *d,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct imon_context *ictx = dev_get_drvdata(d);
ssize_t retval;
unsigned int year, month, day, dow, hour, minute, second;
if (!ictx)
return -ENODEV;
mutex_lock(&ictx->lock);
if (!ictx->display_supported) {
retval = -ENODEV;
goto exit;
} else if (ictx->display_isopen) {
retval = -EBUSY;
goto exit;
}
if (sscanf(buf, "%u %u %u %u %u %u %u", &year, &month, &day, &dow,
&hour, &minute, &second) != 7) {
retval = -EINVAL;
goto exit;
}
if ((month < 1 || month > 12) ||
(day < 1 || day > 31) || (dow > 6) ||
(hour > 23) || (minute > 59) || (second > 59)) {
retval = -EINVAL;
goto exit;
}
retval = send_set_imon_clock(ictx, year, month, day, dow,
hour, minute, second);
if (retval)
goto exit;
retval = count;
exit:
mutex_unlock(&ictx->lock);
return retval;
}
static DEVICE_ATTR(imon_clock, S_IWUSR | S_IRUGO, show_imon_clock,
store_imon_clock);
static DEVICE_ATTR(associate_remote, S_IWUSR | S_IRUGO, show_associate_remote,
store_associate_remote);
static struct attribute *imon_display_sysfs_entries[] = {
&dev_attr_imon_clock.attr,
NULL
};
static const struct attribute_group imon_display_attr_group = {
.attrs = imon_display_sysfs_entries
};
static struct attribute *imon_rf_sysfs_entries[] = {
&dev_attr_associate_remote.attr,
NULL
};
static const struct attribute_group imon_rf_attr_group = {
.attrs = imon_rf_sysfs_entries
};
/*
* Writes data to the VFD. The iMON VFD is 2x16 characters
* and requires data in 5 consecutive USB interrupt packets,
* each packet but the last carrying 7 bytes.
*
* I don't know if the VFD board supports features such as
* scrolling, clearing rows, blanking, etc. so at
* the caller must provide a full screen of data. If fewer
* than 32 bytes are provided spaces will be appended to
* generate a full screen.
*/
static ssize_t vfd_write(struct file *file, const char __user *buf,
size_t n_bytes, loff_t *pos)
{
int i;
int offset;
int seq;
int retval = 0;
struct imon_context *ictx;
static const unsigned char vfd_packet6[] = {
0x01, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF };
ictx = file->private_data;
if (!ictx) {
pr_err_ratelimited("no context for device\n");
return -ENODEV;
}
mutex_lock(&ictx->lock);
if (!ictx->dev_present_intf0) {
pr_err_ratelimited("no iMON device present\n");
retval = -ENODEV;
goto exit;
}
if (n_bytes <= 0 || n_bytes > 32) {
pr_err_ratelimited("invalid payload size\n");
retval = -EINVAL;
goto exit;
}
if (copy_from_user(ictx->tx.data_buf, buf, n_bytes)) {
retval = -EFAULT;
goto exit;
}
/* Pad with spaces */
for (i = n_bytes; i < 32; ++i)
ictx->tx.data_buf[i] = ' ';
for (i = 32; i < 35; ++i)
ictx->tx.data_buf[i] = 0xFF;
offset = 0;
seq = 0;
do {
memcpy(ictx->usb_tx_buf, ictx->tx.data_buf + offset, 7);
ictx->usb_tx_buf[7] = (unsigned char) seq;
retval = send_packet(ictx);
if (retval) {
pr_err_ratelimited("send packet #%d failed\n", seq / 2);
goto exit;
} else {
seq += 2;
offset += 7;
}
} while (offset < 35);
/* Send packet #6 */
memcpy(ictx->usb_tx_buf, &vfd_packet6, sizeof(vfd_packet6));
ictx->usb_tx_buf[7] = (unsigned char) seq;
retval = send_packet(ictx);
if (retval)
pr_err_ratelimited("send packet #%d failed\n", seq / 2);