forked from recalbox/mk_arcade_joystick_rpi
-
Notifications
You must be signed in to change notification settings - Fork 8
/
mk_arcade_joystick_rpi_2019-08-02.c
1354 lines (1114 loc) · 52.8 KB
/
mk_arcade_joystick_rpi_2019-08-02.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
/*
* Arcade Joystick Driver for RaspberryPi
*
* Copyright (c) 2014 Matthieu Proucelle
*
* Based on the gamecon driver by Vojtech Pavlik, and Markus Hiienkari
*/
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/input.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/ioport.h>
#include <asm/io.h>
MODULE_AUTHOR("Matthieu Proucelle (edited for Freeplaytech by Ed Mandy)");
MODULE_DESCRIPTION("Freeplay GPIO Arcade Joystick Driver");
MODULE_LICENSE("GPL");
#define MK_MAX_DEVICES 2
//#define MK_MAX_BUTTONS 13
#define MK_MAX_BUTTONS 21
#ifdef RPI2
#define PERI_BASE 0x3F000000
#else
#define PERI_BASE 0x20000000
#endif
#define GPIO_BASE (PERI_BASE + 0x200000) /* GPIO controller */
#define INP_GPIO(g) *(gpio+((g)/10)) &= ~(7<<(((g)%10)*3))
#define OUT_GPIO(g) *(gpio+((g)/10)) |= (1<<(((g)%10)*3))
#define GPIO_READ(g) ((g < 32) ? (*(gpio + 13) &= (1<<(g))) : (*(gpio + 14) &= (1<<(g-32)))) //work >= 32
#define GET_GPIO(g) (*(gpio.addr + BCM2835_GPLEV0/4)&(1<<g)) // 0 if LOW, (1<<g) if HIGH
#define SET_GPIO_ALT(g,a) *(gpio+(((g)/10))) |= (((a)<=3?(a)+4:(a)==4?3:2)<<(((g)%10)*3))
#define GPIO_SET(g) ((g < 32) ? (*(gpio + 7) = (1<<(g))) : (*(gpio + 8) = (1<<(g-32)))) //work >= 32
#define GPIO_CLR(g) ((g < 32) ? (*(gpio + 10) = (1<<(g))) : (*(gpio + 11) = (1<<(g-32)))) //work >= 32
#define BSC1_BASE (PERI_BASE + 0x804000)
static volatile unsigned *gpio;
struct mk_config {
int args[MK_MAX_DEVICES];
unsigned int nargs;
};
static struct mk_config mk_cfg __initdata;
module_param_array_named(map, mk_cfg.args, int, &(mk_cfg.nargs), 0);
MODULE_PARM_DESC(map, "Enable or disable GPIO, TFT and Custom Arcade Joystick");
struct gpio_config {
int mk_arcade_gpio_maps_custom[MK_MAX_BUTTONS];
unsigned int nargs;
};
// for player 1
static struct gpio_config gpio_cfg __initdata;
module_param_array_named(gpio, gpio_cfg.mk_arcade_gpio_maps_custom, int, &(gpio_cfg.nargs), 0);
MODULE_PARM_DESC(gpio, "Numbers of custom GPIO for Arcade Joystick 1");
// for player 2
static struct gpio_config gpio_cfg2 __initdata;
// hotkey
unsigned char hk_state_prev = 0xFF;
//unsigned char hk_data_prev = 0;
unsigned char hk_pre_mode = 0;
int hotkey_combo_btn = -1;
unsigned char data[MK_MAX_BUTTONS]; //so we always keep the state of data
module_param_array_named(gpio2, gpio_cfg2.mk_arcade_gpio_maps_custom, int, &(gpio_cfg2.nargs), 0);
MODULE_PARM_DESC(gpio2, "Numbers of custom GPIO for Arcade Joystick 2");
#define HOTKEY_MODE_UNDEFINED 0
#define HOTKEY_MODE_NORMAL 1
#define HOTKEY_MODE_TOGGLE 2
struct hkmode_config {
int mode[1]; //HOTKEY_MODE_*
unsigned int nargs;
};
static struct hkmode_config hkmode_cfg __initdata;
module_param_array_named(hkmode, hkmode_cfg.mode, int, &(hkmode_cfg.nargs), 0);
MODULE_PARM_DESC(hkmode, "Hotkey Button Mode: 1=NORMAL, 2=TOGGLE");
struct i2cbus_config {
int busnum[1]; //HOTKEY_MODE_*
unsigned int nargs;
};
static struct i2cbus_config i2cbus_cfg __initdata;
module_param_array_named(i2cbus, i2cbus_cfg.busnum, int, &(i2cbus_cfg.nargs), 0);
MODULE_PARM_DESC(i2cbus, "I2C Bus Number /dev/i2c-# (typically 0 or 1)");
struct analog_config {
int address[1]; //i2c address of the adc
unsigned int nargs;
};
static struct analog_config analog_x1_cfg __initdata;
static struct analog_config analog_y1_cfg __initdata;
static struct analog_config analog_x2_cfg __initdata;
static struct analog_config analog_y2_cfg __initdata;
module_param_array_named(x1addr, analog_x1_cfg.address, int, &(analog_x1_cfg.nargs), 0);
MODULE_PARM_DESC(x1addr, "I2C address of X1 ADC MCP3021A chip");
module_param_array_named(y1addr, analog_y1_cfg.address, int, &(analog_y1_cfg.nargs), 0);
MODULE_PARM_DESC(y1addr, "I2C address of Y1 ADC MCP3021A chip");
module_param_array_named(x2addr, analog_x2_cfg.address, int, &(analog_x2_cfg.nargs), 0);
MODULE_PARM_DESC(x2addr, "I2C address of X2 ADC MCP3021A chip");
module_param_array_named(y2addr, analog_y2_cfg.address, int, &(analog_y2_cfg.nargs), 0);
MODULE_PARM_DESC(y2addr, "I2C address of Y2 ADC MCP3021A chip");
struct ads1015_config { //nns: add ads1015 support
int address[1]; //i2c address of the adc
unsigned int nargs;
};
static struct ads1015_config ads1015_cfg __initdata;
module_param_array_named(ads1015addr, ads1015_cfg.address, int, &(ads1015_cfg.nargs), 0);
MODULE_PARM_DESC(ads1015addr, "I2C address of ADC ADS1015 chip");
struct auto_center_config {
int auto_center[1];
unsigned int nargs;
};
static struct auto_center_config auto_center_cfg __initdata;
module_param_array_named(auto_center_analog, auto_center_cfg.auto_center, int, &(auto_center_cfg.nargs), 0);
MODULE_PARM_DESC(auto_center_analog, "Use auto centering for analog sticks (1=yes, 0=no)");
struct analog_direction_config { //nns: add analog direction
int dir[1];
unsigned int nargs;
};
static struct analog_direction_config analog_x1_direction_cfg __initdata;
static struct analog_direction_config analog_y1_direction_cfg __initdata;
static struct analog_direction_config analog_x2_direction_cfg __initdata;
static struct analog_direction_config analog_y2_direction_cfg __initdata;
module_param_array_named(x1dir, analog_x1_direction_cfg.dir, int, &(analog_x1_direction_cfg.nargs), 0);
MODULE_PARM_DESC(x1dir, "X1 analog direction");
module_param_array_named(y1dir, analog_y1_direction_cfg.dir, int, &(analog_y1_direction_cfg.nargs), 0);
MODULE_PARM_DESC(y1dir, "Y1 analog direction");
module_param_array_named(x2dir, analog_x2_direction_cfg.dir, int, &(analog_x2_direction_cfg.nargs), 0);
MODULE_PARM_DESC(x2dir, "X2 analog direction");
module_param_array_named(y2dir, analog_y2_direction_cfg.dir, int, &(analog_y2_direction_cfg.nargs), 0);
MODULE_PARM_DESC(y2dir, "Y2 analog direction");
//force feedback
struct ff_config { //nns: gpio force feedback support
int pins[2]; //gpio pin to use
unsigned int nargs;
};
static struct ff_config ff_cfg __initdata;
module_param_array_named(ff, ff_cfg.pins, int, &(ff_cfg.nargs), 0);
MODULE_PARM_DESC(ff, "Force feedback parameters (gpio pin for strong rumble, gpio pin for weak rumble)");
struct ffpwm_config { //nns: pwm force feedback support
int params[3]; //gpio pin to use
unsigned int nargs;
};
static struct ffpwm_config ffpwm_cfg __initdata;
module_param_array_named(ffpwm, ffpwm_cfg.params, int, &(ffpwm_cfg.nargs), 0);
MODULE_PARM_DESC(ffpwm, "Force feedback PWM parameters, require PCA9633 (PCA9633 adress, output for strong rumble, output for weak rumble)");
struct ffdir_config { //nns: gpio force feedback direction support
int pins[2]; //gpio pin to use
unsigned int nargs;
};
static struct ffdir_config ffdir_cfg __initdata;
module_param_array_named(ffdir, ffdir_cfg.pins, int, &(ffdir_cfg.nargs), 0);
MODULE_PARM_DESC(ffdir, "Force feedback direction parameters (gpio pin for rumble direction)");
bool ff_enable=false; //gpio force feedback enable
bool ff_pwm_enable=false; //pwm force feedback enable
bool ff_dir_enable=false; //force feedback direction enable
struct i2c_client* pca9633_client=NULL; //PCA9633
int ff_gpio_strong_pin=-1; //rumble strong pin
int ff_gpio_weak_pin=-1; //rumble weak pin
int ff_strong_pwm=-1; //rumble strong PCA9633 pwm output
int ff_weak_pwm=-1; //rumble weak PCA9633 pwm output
bool ff_strong_pwm_sent=true; //rumble strong pwm already sent via i2c
bool ff_weak_pwm_sent=true; //rumble weak pwm already sent via i2c
unsigned int ff_strong_pwm_value=0; //rumble strong PCA9633 pwm value
unsigned int ff_weak_pwm_value=0; //rumble weak PCA9633 pwm value
bool ff_effect_strong_running=false; //rumble strong running
bool ff_effect_weak_running=false; //rumble strong running
int ff_gpio_dir_pin=-1; //rumble direction pin
unsigned int ff_effect_dir=0; //rumble direction
struct debug_config { //nns: add debug
int debug[1];
unsigned int nargs;
};
static struct debug_config debug_config_cfg __initdata;
module_param_array_named(debug, debug_config_cfg.debug, int, &(debug_config_cfg.nargs), 0);
MODULE_PARM_DESC(debug, "Debug stuff");
bool debug_mode=false;
//i2c ADC
#define ABS_PARAMS_DEFAULT_X_MIN 374
#define ABS_PARAMS_DEFAULT_X_MAX 3418
#define ABS_PARAMS_DEFAULT_X_FUZZ 16
#define ABS_PARAMS_DEFAULT_X_FLAT 384
#define ABS_PARAMS_DEFAULT_Y_MIN 517
#define ABS_PARAMS_DEFAULT_Y_MAX 3378
#define ABS_PARAMS_DEFAULT_Y_FUZZ 16
#define ABS_PARAMS_DEFAULT_Y_FLAT 384
struct analog_abs_params_config { //add analog parameters
int abs_params[4];
unsigned int nargs;
};
static struct analog_abs_params_config analog_x1_abs_params_cfg __initdata;
static struct analog_abs_params_config analog_x2_abs_params_cfg __initdata;
static struct analog_abs_params_config analog_y1_abs_params_cfg __initdata;
static struct analog_abs_params_config analog_y2_abs_params_cfg __initdata;
module_param_array_named(x1params, analog_x1_abs_params_cfg.abs_params, int, &(analog_x1_abs_params_cfg.nargs), 0);
MODULE_PARM_DESC(x1params, "X1 ADC absolute parameters (min,max,fuzz,flat)");
module_param_array_named(y1params, analog_y1_abs_params_cfg.abs_params, int, &(analog_y1_abs_params_cfg.nargs), 0);
MODULE_PARM_DESC(y1params, "Y1 ADC absolute parameters (min,max,fuzz,flat)");
module_param_array_named(x2params, analog_x2_abs_params_cfg.abs_params, int, &(analog_x2_abs_params_cfg.nargs), 0);
MODULE_PARM_DESC(x2params, "X2 ADC absolute parameters (min,max,fuzz,flat)");
module_param_array_named(y2params, analog_y2_abs_params_cfg.abs_params, int, &(analog_y2_abs_params_cfg.nargs), 0);
MODULE_PARM_DESC(y2params, "Y2 ADC absolute parameters (min,max,fuzz,flat)");
struct delayed_work mk_delayed_work;
struct mk *g_mk = NULL;
static struct i2c_board_info __initdata board_info[] = {{I2C_BOARD_INFO("MCP3021X1", 0x48),}};
struct i2c_client *i2c_new_MCP3021(struct i2c_adapter *adapter, u16 address){
struct i2c_board_info info = {I2C_BOARD_INFO("MCP3021", address),};
return i2c_new_device(adapter, &info);
}
struct i2c_client *i2c_new_ADS1015(struct i2c_adapter *adapter, u16 address){ //nns: add ADS1015 support
struct i2c_board_info info = {I2C_BOARD_INFO("ADS1015", address),};
return i2c_new_device(adapter, &info);
}
struct i2c_client *i2c_new_PCA9633(struct i2c_adapter *adapter, u16 address){ //nns: add PCA9633 support
struct i2c_board_info info = {I2C_BOARD_INFO("PCA9633", address),};
return i2c_new_device(adapter, &info);
}
struct i2c_adapter* i2c_dev = NULL;
struct i2c_client* i2c_client_x1 = NULL;
struct i2c_client* i2c_client_y1 = NULL;
struct i2c_client* i2c_client_x2 = NULL;
struct i2c_client* i2c_client_y2 = NULL;
uint16_t x1_max = 0;
uint16_t x1_min = 0xFFFF;
int16_t x1_offset = 2048; //nns: use for analog center offcenter
uint16_t y1_max = 0;
uint16_t y1_min = 0xFFFF;
int16_t y1_offset = 2048; //nns: use for analog center offcenter
uint16_t x2_max = 0;
uint16_t x2_min = 0xFFFF;
int16_t x2_offset = 2048; //nns: use for analog center offcenter
uint16_t y2_max = 0;
uint16_t y2_min = 0xFFFF;
int16_t y2_offset = 2048; //nns: use for analog center offcenter
bool x1_reverse = false; //nns: x1 reverse direction
bool y1_reverse = false; //nns: y1 reverse direction
bool x2_reverse = false; //nns: x2 reverse direction
bool y2_reverse = false; //nns: y2 reverse direction
bool auto_center = false; //nns: analog center offcenter
bool x1_enable = false; //nns: x1 enabled?
bool y1_enable = false; //nns: y1 enabled?
bool x2_enable = false; //nns: x2 enabled?
bool y2_enable = false; //nns: y2 enabled?
bool ads1015_enable = false; //nns: ads1015 enabled?
int ads1015_lookup[] = {-1,-1,-1,-1}; //ads1015 ain x1,y1,x2,y2 lookup table
uint16_t ads1015_ain[] = {0x4000,0x5000,0x6000,0x7000}; //ain0,ain1,ain2,ain3 value for bitwise operation
struct analog_abs_params_struct {int min, max, fuzz, flat;} x1_analog_abs_params, x2_analog_abs_params, y1_analog_abs_params, y2_analog_abs_params;
enum mk_type {
MK_NONE = 0,
MK_ARCADE_GPIO,
MK_ARCADE_GPIO_BPLUS,
MK_ARCADE_GPIO_TFT,
MK_ARCADE_GPIO_CUSTOM,
MK_ARCADE_GPIO_CUSTOM2,
MK_MAX
};
#define MK_REFRESH_TIME HZ/100
struct mk_pad {
struct input_dev *dev;
enum mk_type type;
char phys[32];
int hotkey_mode;
int gpio_maps[MK_MAX_BUTTONS];
};
struct mk_nin_gpio {
unsigned pad_id;
unsigned cmd_setinputs;
unsigned cmd_setoutputs;
unsigned valid_bits;
unsigned request;
unsigned request_len;
unsigned response_len;
unsigned response_bufsize;
};
struct mk {
struct mk_pad pads[MK_MAX_DEVICES];
struct timer_list timer;
int used;
struct mutex mutex;
int total_pads;
};
struct mk_subdev {unsigned int idx;};
static struct mk *mk_base;
// Map of the gpios : up, down, left, right, start, select, a, b, tr, y, x, tl, hk
static const int mk_arcade_gpio_maps[] = {4, 17, 27, 22, 10, 9, 25, 24, 23, 18, 15, 14, 2, -1, -1, -1, -1};
// 2nd joystick on the b+ GPIOS up, down, left, right, start, select, a, b, tr, y, x, tl, hk
static const int mk_arcade_gpio_maps_bplus[] = {11, 5, 6, 13, 19, 26, 21, 20, 16, 12, 7, 8, 3, -1, -1, -1, -1};
// Map joystick on the b+ GPIOS with TFT up, down, left, right, start, select, a, b, tr, y, x, tl, hk
static const int mk_arcade_gpio_maps_tft[] = {21, 13, 26, 19, 5, 6, 22, 4, 20, 17, 27, 16, 12, -1, -1, -1, -1};
static const short mk_arcade_gpio_btn[] = {BTN_START, BTN_SELECT, BTN_A, BTN_B, BTN_TR, BTN_Y, BTN_X, BTN_TL, BTN_MODE /*this one can be special*/, BTN_TL2, BTN_TR2, BTN_C, BTN_Z, BTN_TOP, BTN_TOP2, BTN_BASE, BTN_BASE2};
static const char *mk_names[] = {NULL, "GPIO Controller 1", "GPIO Controller 2", "MCP23017 Controller", "GPIO Controller 1" , "GPIO Controller 1", "GPIO Controller 2"};
static int16_t ADC_OffsetCenter(uint16_t adc_resolution,uint16_t adc_value,uint16_t adc_min,uint16_t adc_max,int16_t adc_offset){
int16_t adc_center; int16_t range; int32_t ratio; int16_t corrected_value; //used variables
adc_center=adc_resolution/2; //center value, 2048 for 12bits
if(adc_value<(adc_center+adc_offset)){ //value under center offset
range=(adc_center+adc_offset)-adc_min;
if(range!=0){ //to avoid divide by 0
ratio=10000*adc_center/range; //float workaround
corrected_value=(adc_value-adc_min)*ratio/10000;
}else{corrected_value=adc_value;} //range=0, setting problems?
}else{ //value over center offset
range=adc_max-(adc_center+adc_offset);
if(range!=0){ //to avoid divide by 0
ratio=10000*adc_center/range; //float workaround
corrected_value=adc_center+(adc_value-(adc_center+adc_offset))*ratio/10000;
}else{corrected_value=adc_value;} //range=0, setting problems?
}
if(corrected_value<1){corrected_value=1;}else if(corrected_value>4094){corrected_value=4094;} //constrain computed value to 12bits value + fix for Reicast overflow
return corrected_value;
}
static int16_t ADC_Deadzone(uint16_t adc_value,uint16_t min,uint16_t max,uint16_t flat){
int16_t adc_center; //used variables
adc_center=(max+min)/2; //center value, 2048 for 12bits
if(adc_value>adc_center-flat&&adc_value<adc_center+flat){adc_value=adc_center;} //apply flat value to adc value
return adc_value;
}
static int16_t ADS1015_read(const struct i2c_client *client,uint16_t axis){ //based on https://github.com/torvalds/linux/blob/master/drivers/hwmon/ads1015.c
int16_t value=0; //used variables
int16_t ain=ads1015_lookup[axis]; //get ain id
if(ain<0||ain>3){return -1;} //fail: ain oob, return -1
i2c_smbus_write_word_swapped(client,0x01,0x83E3|ads1015_ain[ain]); //default config but +-4.096v FSR and 3300SPS
udelay(450); //wait 450us for conversion, 390us sould be enough in worst case but +60us add a extra security
value=i2c_smbus_read_word_swapped(client,0); //read value
if(value>=0){ //success
value=value>>4; //shift bits to get 12bits value
if(value<=0xFFF){ //valid 12bit range
return value; //return adc result
}else{return -1;} //invalid range
}else{return value;} //fail: return i2c error
}
/* GPIO UTILS */
static void setGpioPullUps(uint32_t pullUps, uint32_t pullUpsHigh){
*(gpio + 37) = 0x02;
udelay(10);
*(gpio + 38) = pullUps;
*(gpio + 39) = pullUpsHigh;
udelay(10);
*(gpio + 37) = 0x00;
*(gpio + 38) = 0x00;
*(gpio + 39) = 0x00;
}
static void setGpioAsInput(int gpioNum){INP_GPIO(gpioNum);} //gpio: set as input
static void setGpioAsOuput(int gpioNum){OUT_GPIO(gpioNum);} //gpio: set as output
static void GpioOuputSet(int gpioNum){GPIO_SET(gpioNum);} //gpio: set output high
static void GpioOuputClr(int gpioNum){GPIO_CLR(gpioNum);} //gpio: set output low
static int getPullUpMask(int gpioMap[], uint32_t *maskLow, uint32_t *maskHigh){
int i;
*maskLow = 0x0000000;
*maskHigh = 0x0000000;
for(i=0; i<MK_MAX_BUTTONS;i++){
if(gpioMap[i] > -1){ //to avoid unused pins
if(gpioMap[i] < 32){
*maskLow |= 1<<gpioMap[i];
}else if(gpioMap[i] < 64){
*maskHigh |= 1<<(gpioMap[i] - 32);
}
}
}
//printk("mask low=0x%08X high=0x%08X\n", *maskLow, *maskHigh);
return *maskLow;
}
static void mk_gpio_read_packet(struct mk_pad * pad, unsigned char *data){
int i;
for(i = 0; i < MK_MAX_BUTTONS; i++){
if(pad->gpio_maps[i] != -1){ // to avoid unused buttons
if((i==12) && (pad->hotkey_mode == HOTKEY_MODE_TOGGLE)){ //the hotkey
//we use the hotkey as a toggle (press to toggle data[i])
unsigned char hk_state;
int read;
if(pad->gpio_maps[i] < 0){
read = GPIO_READ(pad->gpio_maps[i] * -1); //invert this signal
if(read == 0){
hk_state = 0;
}else{
hk_state = 1; //pressed
}
}else{
read = GPIO_READ(pad->gpio_maps[i]);
if(read == 0){
hk_state = 1; //pressed
}else{
hk_state = 0;
}
}
if(hk_state != hk_state_prev){ //the hotkey changed
hk_state_prev = hk_state;
//if it changed and it's now a 1, we enter pre-hotkey mode
if(hk_state){
if(hk_pre_mode){
//the PWR btn itself is the hotkey
data[12] = 1; //turn on the hotkey
hotkey_combo_btn = i;
}else{
hk_pre_mode = 1;
hotkey_combo_btn = -1;
}
}else if(hotkey_combo_btn == i){ //the hotkey was just released, and the PWR btn itself is the hotkey
data[12] = 0; //turn off the hotkey
hk_pre_mode = 0;
hotkey_combo_btn = -1;
}
}
}else{
//all other (non-hotkey) buttons just report their state to data[i]
//except when we are in hk_state
unsigned char prev_data = data[i];
if(pad->gpio_maps[i] < 0){ //invert this signal
int read = GPIO_READ(pad->gpio_maps[i] * -1);
if(read == 0){
data[i] = 0;
}else{data[i] = 1;}
}else{
int read = GPIO_READ(pad->gpio_maps[i]);
if(read == 0){
data[i] = 1;
}else{data[i] = 0;}
}
if(prev_data != data[i]){ //the state of this button changed
if(hk_pre_mode){
if(data[i]){ //the button was just pressed
data[12] = 1; //turn on the hotkey
hotkey_combo_btn = i;
}else if(i == hotkey_combo_btn){ //the button was just released
data[12] = 0; //turn off the hotkey
hk_pre_mode = 0;
hotkey_combo_btn = -1;
}
}
}
}
}else{data[i] = 0;}
}
}
static void mk_input_report(struct mk_pad * pad, unsigned char * data){
struct input_dev * dev = pad->dev;
int j; //gpio maps loop
int16_t adc_val = 2048; //security if something goes wrong
if(x1_enable){ //if using analog
input_report_abs(dev, ABS_HAT0X, !data[2]-!data[3]); //DPAD is ABS_HAT0X
}else{input_report_abs(dev, ABS_X, !data[2]-!data[3]);} //DPAD is ABS_X
if(y1_enable){ //if using analog
input_report_abs(dev, ABS_HAT0Y, !data[0]-!data[1]); //DPAD is ABS_HAT0Y
}else{input_report_abs(dev, ABS_Y, !data[0]-!data[1]);} //DPAD is ABS_Y
if(x1_enable){
if(ads1015_enable){adc_val = ADS1015_read(i2c_client_x1,0); //ads1015
}else{adc_val = i2c_smbus_read_word_swapped(i2c_client_x1,0);} //mcp3021
if(adc_val>=0){
if(x1_reverse){adc_val = 4096 - adc_val;} //nns: reverse 12bits value
if(adc_val < x1_min){x1_min = adc_val;} //update x1 analog min value
if(adc_val > x1_max){x1_max = adc_val;} //update x1 analog max value
adc_val = ADC_OffsetCenter(4096,adc_val,x1_analog_abs_params.min,x1_analog_abs_params.max,x1_offset); //re-center adc value
adc_val = ADC_Deadzone(adc_val,0x000,0xFFF,x1_analog_abs_params.flat); //apply flat value to adc value
input_report_abs(dev, ABS_X, adc_val);
}else if(debug_mode){printk("mk_arcade_joystick_rpi: DEBUG : failed to read analog X1, returned %i\n",adc_val);} //nns: debug
}
if(y1_enable){
if(ads1015_enable){adc_val = ADS1015_read(i2c_client_x1,1); //ads1015
}else{adc_val = i2c_smbus_read_word_swapped(i2c_client_y1,0);} //mcp3021
if(adc_val>=0){
if(y1_reverse){adc_val = 4096 - adc_val;} //nns: reverse 12bits value
if(adc_val < y1_min){y1_min = adc_val;} //update y1 analog min value
if(adc_val > y1_max){y1_max = adc_val;} //update y1 analog max value
adc_val = ADC_OffsetCenter(4096,adc_val,y1_analog_abs_params.min,y1_analog_abs_params.max,y1_offset); //re-center adc value
adc_val = ADC_Deadzone(adc_val,0x000,0xFFF,y1_analog_abs_params.flat); //apply flat value to adc value
input_report_abs(dev, ABS_Y, adc_val);
}else if(debug_mode){printk("mk_arcade_joystick_rpi: DEBUG : failed to read analog Y1, returned %i\n",adc_val);} //nns: debug
}
if(x2_enable){
if(ads1015_enable){adc_val = ADS1015_read(i2c_client_x1,2); //ads1015
}else{adc_val = i2c_smbus_read_word_swapped(i2c_client_x2,0);} //mcp3021
if(adc_val>=0){
if(x2_reverse){adc_val = 4096 - adc_val;} //nns: reverse 12bits value
if(adc_val < x2_min){x2_min = adc_val;} //update x2 analog min value
if(adc_val > x2_max){x2_max = adc_val;} //update x2 analog max value
adc_val = ADC_OffsetCenter(4096,adc_val,x2_analog_abs_params.min,x2_analog_abs_params.max,x2_offset); //re-center adc value
adc_val = ADC_Deadzone(adc_val,0x000,0xFFF,x2_analog_abs_params.flat); //apply flat value to adc value
input_report_abs(dev, ABS_RX, adc_val);
}else if(debug_mode){printk("mk_arcade_joystick_rpi: DEBUG : failed to read analog X2, returned %i\n",adc_val);} //nns: debug
}
if(y2_enable){
if(ads1015_enable){adc_val = ADS1015_read(i2c_client_x1,3); //ads1015
}else{adc_val = i2c_smbus_read_word_swapped(i2c_client_y2,0);} //mcp3021
if(adc_val>=0){
if(y2_reverse){adc_val = 4096 - adc_val;} //nns: reverse 12bits value
if(adc_val < y2_min){y2_min = adc_val;} //update y2 analog min value
if(adc_val > y2_max){y2_max = adc_val;} //update y2 analog max value
adc_val = ADC_OffsetCenter(4096,adc_val,y2_analog_abs_params.min,y2_analog_abs_params.max,y2_offset); //re-center adc value
adc_val = ADC_Deadzone(adc_val,0x000,0xFFF,y2_analog_abs_params.flat); //apply flat value to adc value
input_report_abs(dev, ABS_RY, adc_val);
}else if(debug_mode){printk("mk_arcade_joystick_rpi: DEBUG : failed to read analog Y2, returned %i\n",adc_val);} //nns: debug
}
for (j = 4; j < MK_MAX_BUTTONS; j++){
if(pad->gpio_maps[j] != -1){input_report_key(dev, mk_arcade_gpio_btn[j - 4], data[j]);}
}
input_sync(dev);
//PWM force feedback, need to be here because i2c_smbus_write_byte_data mess with schedule_delayed_work
if(!ff_strong_pwm_sent){ //pwm i2c not already sent
if(debug_mode){printk("mk_arcade_joystick_rpi: DEBUG : Feedback effect : PWM Strong : %d\n",ff_strong_pwm_value);}
i2c_smbus_write_byte_data(pca9633_client,(uint8_t)(ff_strong_pwm+2),(uint8_t)(ff_strong_pwm_value)); //send pwm to i2c
ff_strong_pwm_sent=true; //reset
}
if(!ff_weak_pwm_sent){ //pwm i2c not already sent
if(debug_mode){printk("mk_arcade_joystick_rpi: DEBUG : Feedback effect : PWM Weak : %d\n",ff_weak_pwm_value);}
i2c_smbus_write_byte_data(pca9633_client,(uint8_t)(ff_weak_pwm+2),(uint8_t)(ff_weak_pwm_value)); //send pwm to i2c
ff_weak_pwm_sent=true; //reset
}
}
static void mk_process_packet(struct mk *mk){
struct mk_pad *pad;
int i;
for(i = 0; i < mk->total_pads; i++){
pad = &mk->pads[i];
mk_gpio_read_packet(pad, data); //data is now global
mk_input_report(pad, data);
}
}
static int mk_ff(struct input_dev *dev, void *data, struct ff_effect *effect){ //nns: handle force feedback effects
if(effect->type!=FF_RUMBLE){
if(debug_mode){printk("mk_arcade_joystick_rpi: DEBUG : Wrong force feedback effect\n");}
return 0;
}else{
if(effect->u.rumble.strong_magnitude!=0&&!ff_effect_strong_running){ //strong
if(debug_mode){printk("mk_arcade_joystick_rpi: DEBUG : Feedback effect : Strong : start\n");}
if(pca9633_client!=NULL&&ff_strong_pwm!=-1){ff_strong_pwm_value=effect->u.rumble.strong_magnitude/256; ff_strong_pwm_sent=false;} //pwm
if(ff_gpio_strong_pin!=-1){GpioOuputSet(ff_gpio_strong_pin);} //set gpio output high
ff_effect_strong_running=true; //running
}else{
if(debug_mode){printk("mk_arcade_joystick_rpi: DEBUG : Feedback effect : Strong : stop\n");}
if(pca9633_client!=NULL&&ff_strong_pwm!=-1){ff_strong_pwm_value=0; ff_strong_pwm_sent=false;} //pwm
if(ff_gpio_strong_pin!=-1){GpioOuputClr(ff_gpio_strong_pin);} //set gpio output low
ff_effect_strong_running=false; //reset
}
if(ff_gpio_weak_pin!=-1||ff_weak_pwm!=-1){
if(effect->u.rumble.weak_magnitude!=0&&!ff_effect_weak_running){ //weak
if(debug_mode){printk("mk_arcade_joystick_rpi: DEBUG : Feedback effect : Weak : start\n");}
if(pca9633_client!=NULL&&ff_weak_pwm!=-1){ff_weak_pwm_value=effect->u.rumble.weak_magnitude/256; ff_weak_pwm_sent=false;} //pwm
if(ff_gpio_weak_pin!=-1){GpioOuputSet(ff_gpio_weak_pin);} //set gpio output high
ff_effect_weak_running=true; //running
}else{
if(debug_mode){printk("mk_arcade_joystick_rpi: DEBUG : Feedback effect : Weak : stop\n");}
if(pca9633_client!=NULL&&ff_weak_pwm!=-1){ff_weak_pwm_value=0; ff_weak_pwm_sent=false;} //pwm
if(ff_gpio_weak_pin!=-1){GpioOuputClr(ff_gpio_weak_pin);} //set gpio output low
ff_effect_weak_running=false; //reset
}
}
if(ff_dir_enable){ //direction is set for both strong and week motor at the same time
ff_effect_dir=effect->direction; //https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/input.h#L443
if(ff_effect_dir<16384||ff_effect_dir>49152){ //assume value under left/over right as down
if(debug_mode){printk("mk_arcade_joystick_rpi: DEBUG : Feedback effect : Direction : Down (%d)\n",ff_effect_dir);}
GpioOuputSet(ff_gpio_dir_pin); //set gpio output high
}else{ //assume as up
if(debug_mode){printk("mk_arcade_joystick_rpi: DEBUG : Feedback effect : Direction : Up (%d)\n",ff_effect_dir);}
GpioOuputClr(ff_gpio_dir_pin); //set gpio output low
}
}
return 1;
}
}
static void mk_work_handler(struct work_struct* work){
struct mk *mk = g_mk;
mk_process_packet(mk);
schedule_delayed_work(&mk_delayed_work, MK_REFRESH_TIME);
}
static int mk_open(struct input_dev *dev){
struct mk *mk = input_get_drvdata(dev);
int err;
err = mutex_lock_interruptible(&mk->mutex);
if(err){return err;}
if(!mk->used++){schedule_delayed_work(&mk_delayed_work, MK_REFRESH_TIME);}
mutex_unlock(&mk->mutex);
return 0;
}
static void mk_close(struct input_dev *dev){
struct mk *mk = input_get_drvdata(dev);
mutex_lock(&mk->mutex);
if(!--mk->used){cancel_delayed_work_sync(&mk_delayed_work);}
mutex_unlock(&mk->mutex);
}
static int __init mk_setup_pad(struct mk *mk, int idx, int pad_type_arg){
struct mk_pad *pad = &mk->pads[idx];
struct input_dev *input_dev;
int i, pad_type;
int err;
pr_err("Freeplay Button Driver\n");
pr_err("pad type : %d\n",pad_type_arg);
pad_type = pad_type_arg;
if(pad_type < 1 || pad_type >= MK_MAX){
pr_err("Pad type %d unknown\n", pad_type);
return -EINVAL;
}
pad->hotkey_mode = hkmode_cfg.mode[0]; //for now, the hkmode parameter is "global" to all pads
if(pad_type == MK_ARCADE_GPIO_CUSTOM){ //if the device is custom, be sure to get correct pins
if(gpio_cfg.nargs < 1){
pr_err("Custom device needs gpio argument\n");
return -EINVAL;
}else if(gpio_cfg.nargs != MK_MAX_BUTTONS){
pr_err("Invalid gpio argument\n", pad_type);
return -EINVAL;
}
}
if(pad_type == MK_ARCADE_GPIO_CUSTOM2){ //if the device is custom, be sure to get correct pins
if(gpio_cfg2.nargs < 1){
pr_err("Custom device needs gpio argument\n");
return -EINVAL;
}else if(gpio_cfg2.nargs != MK_MAX_BUTTONS){
pr_err("Invalid gpio argument\n", pad_type);
return -EINVAL;
}
}
pr_err("pad type : %d\n",pad_type);
pad->dev = input_dev = input_allocate_device();
if(!input_dev){
pr_err("Not enough memory for input device\n");
return -ENOMEM;
}
pad->type = pad_type;
snprintf(pad->phys, sizeof (pad->phys), "input%d", idx);
if(debug_mode){pr_err("Running in Debug mode\n");}
input_dev->name = mk_names[pad_type];
input_dev->phys = pad->phys;
input_dev->id.bustype = BUS_PARPORT;
input_dev->id.vendor = 0x0001;
input_dev->id.product = pad_type;
input_dev->id.version = 0x0100;
input_set_drvdata(input_dev, mk);
input_dev->open = mk_open;
input_dev->close = mk_close;
input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
mk->total_pads++;
// asign gpio pins
switch (pad_type){
case MK_ARCADE_GPIO:
memcpy(pad->gpio_maps, mk_arcade_gpio_maps, MK_MAX_BUTTONS *sizeof(int));
break;
case MK_ARCADE_GPIO_BPLUS:
memcpy(pad->gpio_maps, mk_arcade_gpio_maps_bplus, MK_MAX_BUTTONS *sizeof(int));
break;
case MK_ARCADE_GPIO_TFT:
memcpy(pad->gpio_maps, mk_arcade_gpio_maps_tft, MK_MAX_BUTTONS *sizeof(int));
break;
case MK_ARCADE_GPIO_CUSTOM:
memcpy(pad->gpio_maps, gpio_cfg.mk_arcade_gpio_maps_custom, MK_MAX_BUTTONS *sizeof(int));
break;
case MK_ARCADE_GPIO_CUSTOM2:
memcpy(pad->gpio_maps, gpio_cfg2.mk_arcade_gpio_maps_custom, MK_MAX_BUTTONS *sizeof(int));
break;
}
if(x1_enable){ //if using analog, then DPAD is ABS_HAT0X
input_set_abs_params(input_dev, ABS_HAT0X, -1, 1, 0, 0);
}else{
input_set_abs_params(input_dev, ABS_X, -1, 1, 0, 0);
}
if(y1_enable){ //if using analog, then DPAD is ABS_HAT0Y
input_set_abs_params(input_dev, ABS_HAT0Y, -1, 1, 0, 0);
}else{
input_set_abs_params(input_dev, ABS_Y, -1, 1, 0, 0);
}
//i2c ADC
if(x1_enable){input_set_abs_params(input_dev, ABS_X, 0x000, 0xFFF, x1_analog_abs_params.fuzz, x1_analog_abs_params.flat);} //nns: parameters for center offcenter values
if(y1_enable){input_set_abs_params(input_dev, ABS_Y, 0x000, 0xFFF, y1_analog_abs_params.fuzz, y1_analog_abs_params.flat);} //nns: parameters for center offcenter values
if(x2_enable){input_set_abs_params(input_dev, ABS_RX, 0x000, 0xFFF, x2_analog_abs_params.fuzz, x2_analog_abs_params.flat);} //nns: parameters for center offcenter values
if(y2_enable){input_set_abs_params(input_dev, ABS_RY, 0x000, 0xFFF, y2_analog_abs_params.fuzz, y2_analog_abs_params.flat);} //nns: parameters for center offcenter values
for (i = 0; i < MK_MAX_BUTTONS - 4; i++){
if(pad->gpio_maps[i+4] != -1){__set_bit(mk_arcade_gpio_btn[i], input_dev->keybit);}
}
// initialize gpio
for (i = 0; i < MK_MAX_BUTTONS; i++){
if(pad->gpio_maps[i] != -1){ // to avoid unused buttons
if(pad->gpio_maps[i] < 0){
setGpioAsInput(pad->gpio_maps[i] * -1);
}else{
setGpioAsInput(pad->gpio_maps[i]);
}
}
}
uint32_t pullUpMaskLow, pullUpMaskHigh;
getPullUpMask(pad->gpio_maps, &pullUpMaskLow, &pullUpMaskHigh);
setGpioPullUps(pullUpMaskLow, pullUpMaskHigh);
printk("mk_arcade_joystick_rpi: GPIO configured for pad%d\n", idx);
if(ff_enable){ //nns: force feedback support
int ff_err;
input_set_capability(pad->dev, EV_FF, FF_RUMBLE);
ff_err=input_ff_create_memless(pad->dev, NULL, mk_ff);
if(ff_err){
printk("mk_arcade_joystick_rpi: Failed to create force feedback device : %d",ff_err);
}else{
printk("mk_arcade_joystick_rpi: Force feedback : Strong GPIO pin : %d\n", ff_gpio_strong_pin);
setGpioAsInput(ff_gpio_strong_pin); setGpioAsOuput(ff_gpio_strong_pin); //set pin as output, need to be set as input first
if(ff_gpio_weak_pin!=-1){
printk("mk_arcade_joystick_rpi: Force feedback : Weak GPIO pin : %d\n", ff_gpio_weak_pin);
setGpioAsInput(ff_gpio_weak_pin); setGpioAsOuput(ff_gpio_weak_pin); //set pin as output, need to be set as input first
}
if(ff_dir_enable){ //direction
printk("mk_arcade_joystick_rpi: Force feedback : Direction GPIO pin : %d\n", ff_gpio_dir_pin);
setGpioAsInput(ff_gpio_dir_pin); setGpioAsOuput(ff_gpio_dir_pin); //set pin as output, need to be set as input first
}
}
}
err = input_register_device(pad->dev);
if(err){goto err_free_dev;}
return 0;
err_free_dev: input_free_device(pad->dev); pad->dev = NULL; return err;
}
static struct mk __init *mk_probe(int *pads, int n_pads){
struct mk *mk;
int i;
int count = 0;
int err;
mk = kzalloc(sizeof (struct mk), GFP_KERNEL);
if(!mk){
pr_err("Not enough memory\n");
err = -ENOMEM;
goto err_out;
}
mutex_init(&mk->mutex);
//setup_timer(&mk->timer, mk_timer, (long) mk);
g_mk = mk;
INIT_DELAYED_WORK(&mk_delayed_work, mk_work_handler);
for(i = 0; i < n_pads && i < MK_MAX_DEVICES; i++){
if(!pads[i]){continue;}
err = mk_setup_pad(mk, i, pads[i]);
if(err){goto err_unreg_devs;}
count++;
}
if(count == 0){
pr_err("No valid devices specified\n");
err = -EINVAL;
goto err_free_mk;
}
return mk;
err_unreg_devs: while(--i >= 0){if(mk->pads[i].dev){input_unregister_device(mk->pads[i].dev);}}
err_free_mk: kfree(mk);
err_out: return ERR_PTR(err);
}
static void mk_remove(struct mk *mk){
int i;
for (i = 0; i < MK_MAX_DEVICES; i++){
if(mk->pads[i].dev){
input_unregister_device(mk->pads[i].dev);
}
}
kfree(mk);
}
static int __init mk_init(void){
/* Set up gpio pointer for direct register access */
if((gpio = ioremap(GPIO_BASE, 0xB0)) == NULL){
pr_err("io remap failed\n");
return -EBUSY;
}
if(debug_config_cfg.nargs > 0){ //if hkmode was not defined
if(debug_config_cfg.debug[0]>0){debug_mode=true;} //enable debug mode
}
if(hkmode_cfg.nargs == 0){ //if hkmode was not defined
hkmode_cfg.mode[0] = HOTKEY_MODE_TOGGLE; //default to HOTKEY_MODE_TOGGLE if not set
}
if(i2cbus_cfg.nargs == 0){ //if i2cbus addr was not defined
i2cbus_cfg.busnum[0] = -1; //default to not using i2c
}
if(ads1015_cfg.nargs == 0){ //nns: if ads1015 addr was not defined
ads1015_cfg.address[0] = 0; //default to not using it
}
if(analog_x1_cfg.nargs == 0){ //if analog input i2c addr was not defined
analog_x1_cfg.address[0] = -1; //default to not using it, nns: -1 to avoid using it if ads1015 used
}
if(analog_y1_cfg.nargs == 0){ //if analog input i2c addr was not defined
analog_y1_cfg.address[0] = -1; //default to not using it, nns: -1 to avoid using it if ads1015 used
}
if(analog_x2_cfg.nargs == 0){ //if analog input i2c addr was not defined
analog_x2_cfg.address[0] = -1; //default to not using it, nns: -1 to avoid using it if ads1015 used
}
if(analog_y2_cfg.nargs == 0){ //if analog input i2c addr was not defined
analog_y2_cfg.address[0] = -1; //default to not using it, nns: -1 to avoid using it if ads1015 used
}
if(auto_center_cfg.nargs > 0){ //if auto_center_analog set
if(auto_center_cfg.auto_center[0]>0){auto_center = true;} //nns: if value > 0, auto center enable
}
if(analog_x1_direction_cfg.nargs > 0){ //if x1dir set
if(analog_x1_direction_cfg.dir[0]<0){x1_reverse=true;} //nns:direction reversed
}
if(analog_y1_direction_cfg.nargs > 0){ //if y1dir set
if(analog_y1_direction_cfg.dir[0]<0){y1_reverse=true;} //nns:direction reversed
}
if(analog_x2_direction_cfg.nargs > 0){ //if x2dir set
if(analog_x2_direction_cfg.dir[0]<0){x2_reverse=true;} //nns:direction reversed
}
if(analog_y2_direction_cfg.nargs > 0){ //if y2dir set
if(analog_y2_direction_cfg.dir[0]<0){y2_reverse=true;} //nns:direction reversed
}
if(ff_cfg.nargs > 0){ //nns: force feedback support
ff_enable=true;
ff_gpio_strong_pin=abs(ff_cfg.pins[0]); //ff strong pin