-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlapocket.c
More file actions
12784 lines (11717 loc) · 341 KB
/
Copy pathlapocket.c
File metadata and controls
12784 lines (11717 loc) · 341 KB
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
/*
* HP Jornada 545 emulator
* Copyright (C) 2026 Ernesto A. Fernández <ernesto.mnd.fernandez@gmail.com>
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <stdarg.h>
#ifdef HAVE_SDL
#define SDL_MAIN_USE_CALLBACKS 1
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#endif
#ifdef HAVE_PNG
#include <png.h>
#endif
static void eeprom_monitor_dump_all(void);
struct console_monitor;
static void console_monitor_dump(struct console_monitor *mon);
static void dump_all_monitors(void);
#define MONITOR_EEPROM_ENABLED (1U << 0)
#define MONITOR_NOTICE_ENABLED (1U << 1)
#define MONITOR_SERIAL_ENABLED (1U << 2)
#define MONITOR_XB3A_ENABLED (1U << 3)
#define MONITOR_PEN_ENABLED (1U << 4)
#define MONITOR_AUDIO_ENABLED (1U << 5)
#define MONITOR_IRDA_ENABLED (1U << 6)
/* The eeprom i2c monitor is not very interesting so it's off by default */
static uint8_t enabled_monitors = MONITOR_SERIAL_ENABLED | MONITOR_IRDA_ENABLED | MONITOR_XB3A_ENABLED;
static void dump_cpu(void);
static void print_backtrace(void);
static bool running = false;
static bool executing = false;
static bool panicked = false;
static bool kbinterrupted = false;
static bool refresh_time = false;
static int remaining_steps = -1;
static long long wait_end = 0;
static long long nanosecs = 0;
/* Keep track of the time spent inside the debugger */
static long long debugger_nanosecs = 0;
/* Last time the display got refreshed */
static long long last_refresh_nanosecs = 0;
static bool no_exception = true;
static void exception_check_prepare(void);
struct audio {
int a_freq; /* Samples per second */
int a_period; /* Length of a sample in nanoseconds */
long long a_pretime; /* Nanoseconds when dma started */
int a_count; /* Samples played in current dma transfer */
} audio = {
/*
* The actual frequency will get set later through a motherboard register,
* but I need to pick some default value just in case...
*/
.a_freq = 44100,
.a_period = 22675,
};
#ifdef HAVE_SDL
bool headless = false;
#else
bool headless = true;
#endif
#ifdef __unix__
#include <signal.h>
static void kbinterrupt_handler(int signum)
{
kbinterrupted = true;
}
static void set_signal_handlers(void)
{
struct sigaction act = { .sa_handler = kbinterrupt_handler };
sigaction(SIGINT, &act, NULL);
}
#else /* __unix__ */
static void set_signal_handlers(void)
{
}
#endif /* __unix__ */
#ifdef __GNUC__
__attribute__((format(printf, 1, 2)))
#endif
void notice(const char *format, ...)
{
va_list args;
if (!(enabled_monitors & MONITOR_NOTICE_ENABLED))
return;
dump_all_monitors();
va_start(args, format);
vprintf(format, args);
va_end(args);
}
/* Prints a notice only once to avoid a flood */
#define NOTICE_ONCE(message) \
do { \
static bool printed = false; \
\
if (!printed) { \
notice(message); \
printed = true; \
} \
} while (false)
#ifdef __GNUC__
[[gnu::format(printf, 1, 2)]]
#endif
[[nodiscard]] int panic(const char *format, ...)
{
va_list args;
if (panicked)
return 1;
/* Don't panic just because the debugger looked at something invalid */
if (!running)
return 1;
dump_all_monitors();
puts("");
puts("");
va_start(args, format);
printf("Panic! ");
vprintf(format, args);
va_end(args);
dump_cpu();
puts("Backtrace:");
print_backtrace();
puts("");
puts("");
#ifdef __EMSCRIPTEN__
/*
* Not much point in panicking when there is no debugger, and no real risk
* from out-of-bounds operations or the like. Just try to keep going and
* hope for the best.
*/
panicked = false;
return 0;
#else
panicked = true;
return 1;
#endif
}
/*
* This file represents the CompactFlash memory card. Note that ftell() returns
* a long, but cards at the time were small (mine was 256 MiB) and 32-bit issues
* are rare today, so I won't worry about this.
*/
FILE *card_file = NULL;
long card_size;
#ifdef __unix
#include <fcntl.h>
#include <unistd.h>
/* If provided, this file will be the serial interface */
static int serial_fd = -1;
/* And this will be the infrared interface */
static int irda_fd = -1;
#endif
/*
* The motherboard for the Jornada 545 reads "Hewlett Packard F1796-80004
* 0007EU206". I haven't found any documentation for it, but it seems to have
* a number of memory-mapped registers starting at address 0xb2000000 (which is
* actually in the P2 area mapped to physical address space so the top 3 bits
* are ignored). I've gleaned the following from looking at the firmware code:
*/
#define MBOARD_REGS_OFF 0x12000000
#define MBOARD_COMMANDS_OFF 0x12000004
#define MBOARD_STATUS_OFF 0x12000008 /* Status flags */
#define MBOARD_BLINKCNT_OFF 0x1200001C
#define MBOARD_SOUND_SAMPLE_OFF 0x1200007C
#define MBOARD_SOUND_FREQ_OFF 0x12000084
/* Flags for the motherboard's status register */
#define MBOARD_CARD_SLOT_EMPTY (1U << 0)
#define MBOARD_NOT_BLINKING (1U << 5)
#define MBOARD_BATTERY_CHARGING (1U << 8)
/* TODO: confirm what these two actually mean */
#define MBOARD_BATTERY_OUT (1U << 9)
#define MBOARD_AC_OUT (1U << 10)
/* Flags for the motherboard commands register */
#define MBOARD_START_BLINKING (1U << 4)
#define MBOARD_STOP_BLINKING (1U << 5)
struct motherboard {
uint16_t status;
uint16_t blinkcnt; /* Blinks remaining for the top light */
} motherboard = {
/*
* I'm not sure about the initial blinking status but this is more
* convenient for testing.
*
* TODO: add an input command for battery and charger to the debugger.
*/
.status = MBOARD_BATTERY_OUT | MBOARD_BATTERY_CHARGING | MBOARD_NOT_BLINKING,
};
static bool is_motherboard_word_address(uint32_t addr)
{
switch (addr) {
case MBOARD_COMMANDS_OFF:
case MBOARD_STATUS_OFF:
case MBOARD_BLINKCNT_OFF:
case MBOARD_SOUND_SAMPLE_OFF:
case 0x12000000:
case 0x12000014:
case 0x12000024:
case 0x12000030:
case 0x12000034:
case 0x12000044:
case 0x12000068:
case 0x1200006c:
case 0x12000074:
case 0x12000078:
case 0x12000080:
case 0x12000084:
case 0x12000098:
return true;
default:
return false;
}
}
/*
* The Jornada 545 that I opened comes with a battery labeled "1JP015057676". I
* couldn't find any info for that number, but the smaller front board reads
* "F1798-80003", and that does appear to identify a common battery for these
* devices. A seller has it listed as "PDA-10LI - HP F1798-80003 3.7v 1800 mAh
* LION".
*
* TODO: confirm that the values look similar on my device.
*/
struct battery {
/* This voltage times 4.17 gets reported as mV by <0x8003935C> */
unsigned int voltage;
/* This charge times 3300/1024 gets reported as % by <0x8003935C> */
unsigned int charge;
} battery = {
.voltage = 887, /* ~3.7v */
.charge = 31, /* ~100% */
};
/* The 545 has a resistive touchscreen */
struct touchscreen {
/*
* Coordinates of the pen, or (-1,-1) if there is no contact. These will be
* used directly as voltage values and fed to the A/D converter, which is
* not accurate, but I expect it to work after calibration. TODO: extract
* the calibration parameters from my device and use real voltage values.
*/
int x;
int y;
uint8_t state; /* SCP0DT:PE0DT:SCP1DT:PE1DT */
} touchscreen = {
.x = -1,
.y = -1,
};
#define TOUCH_STATE_PE1DT 0x01
#define TOUCH_STATE_SCP1DT 0x02
#define TOUCH_STATE_PE0DT 0x04
#define TOUCH_STATE_SCP0DT 0x08
/*
* I won't prentend to understand this at this point (TODO?), but these values
* are written to SCP0DT:PE0DT:SCP1DT:PE1DT before reading from each channel of
* the A/D converter.
*/
#define TOUCH_STATE_READ_X (TOUCH_STATE_PE0DT | TOUCH_STATE_SCP1DT)
#define TOUCH_STATE_READ_Y (TOUCH_STATE_SCP0DT | TOUCH_STATE_PE1DT)
#define TOUCH_STATE_NOREAD (TOUCH_STATE_SCP0DT)
/* The analog/digital converter */
struct adconv {
uint16_t ADDRAH; /* A/D data register A (high) */
uint8_t ADDRAL; /* A/D data register A (low) */
uint16_t ADDRBH; /* A/D data register B (high) */
uint8_t ADDRBL; /* A/D data register B (low) */
uint16_t ADDRCH; /* A/D data register C (high) */
uint8_t ADDRCL; /* A/D data register C (low) */
uint16_t ADDRDH; /* A/D data register D (high) */
uint8_t ADDRDL; /* A/D data register D (low) */
uint8_t ADCSR; /* A/D control/status register */
uint8_t ADCR; /* A/D control register */
} adconv = {
/* TODO: init all register structs like this */
.ADCR = 0x3F, /* The manual is actually inconsistent about this (TODO) */
};
#define ADCONV_ADDRAH_OFF 0x04000080
#define ADCONV_ADDRAL_OFF 0x04000082
#define ADCONV_ADDRBH_OFF 0x04000084
#define ADCONV_ADDRBL_OFF 0x04000086
#define ADCONV_ADDRCH_OFF 0x04000088
#define ADCONV_ADDRCL_OFF 0x0400008A
#define ADCONV_ADDRDH_OFF 0x0400008C
#define ADCONV_ADDRDL_OFF 0x0400008E
#define ADCONV_ADCSR_OFF 0x04000090
#define ADCONV_ADCR_OFF 0x04000092
/* Flags of the ADCSR register */
#define ADCSR_ADF (1U << 7) /* A/D End Flag */
#define ADCSR_ADIE (1U << 6) /* A/D Interrupt Enable */
#define ADCSR_ADST (1U << 5) /* A/D Start */
#define ADCSR_MULTI (1U << 4) /* Multi Mode */
#define ADCSR_CKS (1U << 3) /* Clock Select */
#define ADCSR_CH2 (1U << 2) /* Channel Select 2 */
#define ADCSR_CH1 (1U << 1) /* Channel Select 1 */
#define ADCSR_CH0 (1U << 0) /* Channel Select 0 */
#define ADCSR_CH_MASK (ADCSR_CH2 | ADCSR_CH1 | ADCSR_CH0)
/* These bits can be reset to zero on a write, but not set to 1 */
#define ADCSR_UNSETTABLE_MASK (ADCSR_ADF)
static bool is_adconv_byte_address(uint32_t addr)
{
/* TODO: apparently each ADDR register can also be read as a word */
switch (addr) {
case ADCONV_ADDRAH_OFF:
case ADCONV_ADDRAL_OFF:
case ADCONV_ADDRBH_OFF:
case ADCONV_ADDRBL_OFF:
case ADCONV_ADDRCH_OFF:
case ADCONV_ADDRCL_OFF:
case ADCONV_ADDRDH_OFF:
case ADCONV_ADDRDL_OFF:
case ADCONV_ADCSR_OFF:
case ADCONV_ADCR_OFF:
return true;
default:
return false;
}
}
static int adconv_write_byte_reg(uint32_t addr, uint8_t val)
{
uint8_t preserved_bits;
switch (addr) {
case ADCONV_ADCSR_OFF:
/* TODO: only clear ADF if it has been read */
preserved_bits = val & ADCSR_UNSETTABLE_MASK;
val = (val & ~preserved_bits) | (adconv.ADCSR & preserved_bits);
if (val & ADCSR_ADIE)
return panic("A/D interrupts not supported (0x%.2x)\n", val);
if (val & ADCSR_ADST) {
/* In the emulator, the digital data is available immediately */
val |= ADCSR_ADF;
}
if (val & ADCSR_MULTI)
return panic("A/D multi mode not supported (0x%.2x)\n", val);
if ((adconv.ADCSR ^ val) & ADCSR_CKS) {
if (adconv.ADCSR & ADCSR_ADST)
return panic("A/D clock switch during conversion\n");
notice("A/D conversion time set to %u states\n", val & ADCSR_CKS ? 134 : 266);
}
adconv.ADCSR = val;
return 0;
case ADCONV_ADCR_OFF:
if (val)
return panic("Unsupported ADCR configuration 0x%.4x\n", val);
adconv.ADCR = val;
return 0;
default:
return panic("Attempted write to unsupported A/D converter register at 0x%.8x\n", addr);
}
}
/*
* There seem to be two channels that report the charge level, with different
* calibration. I'm not at all confident about this... (TODO)
*/
static int charge_ch5_to_ch4(int ch5)
{
return ch5 * 33;
}
static int adconv_read_byte_reg(uint32_t addr, uint8_t *val_p)
{
unsigned int channel;
switch (addr) {
case ADCONV_ADCSR_OFF:
*val_p = adconv.ADCSR;
return 0;
/*
* The result of an A/D conversion is a 10-bit number. 'H' registers hold
* the top 8 bits; 'L' registers hold the bottom 2, in the top 2 positions.
*/
case ADCONV_ADDRAH_OFF:
if (!(adconv.ADCSR & ADCSR_ADST))
return panic("A/D data read attempt outside of conversion\n");
channel = adconv.ADCSR & ADCSR_CH_MASK;
if (channel == 4) {
*val_p = charge_ch5_to_ch4(battery.charge) >> 2;
return 0;
}
return panic("A/D ADDRA read attempt for wrong channel (%u)\n", channel);
case ADCONV_ADDRAL_OFF:
if (!(adconv.ADCSR & ADCSR_ADST))
return panic("A/D data read attempt outside of conversion\n");
channel = adconv.ADCSR & ADCSR_CH_MASK;
if (channel == 4) {
*val_p = charge_ch5_to_ch4(battery.charge) << 6;
return 0;
}
return panic("A/D ADDRA read attempt for wrong channel (%u)\n", channel);
case ADCONV_ADDRBH_OFF:
if (!(adconv.ADCSR & ADCSR_ADST))
return panic("A/D data read attempt outside of conversion\n");
channel = adconv.ADCSR & ADCSR_CH_MASK;
if (channel == 1) {
/* This is probably not accurate but it shouldn't matter... */
if (touchscreen.y < 0 || touchscreen.state != TOUCH_STATE_READ_Y) {
*val_p = 0;
return 0;
}
*val_p = touchscreen.y >> 2;
return 0;
}
if (channel == 5) {
*val_p = battery.charge >> 2;
return 0;
}
return panic("A/D ADDRB read attempt for wrong channel (%u)\n", channel);
case ADCONV_ADDRBL_OFF:
if (!(adconv.ADCSR & ADCSR_ADST))
return panic("A/D data read attempt outside of conversion\n");
channel = adconv.ADCSR & ADCSR_CH_MASK;
if (channel == 1) {
if (touchscreen.y < 0 || touchscreen.state != TOUCH_STATE_READ_Y) {
*val_p = 0;
return 0;
}
*val_p = touchscreen.y << 6;
return 0;
}
if (channel == 5) {
*val_p = battery.charge << 6;
return 0;
}
return panic("A/D data read attempt for wrong channel (%u != 1)\n", channel);
case ADCONV_ADDRCH_OFF:
if (!(adconv.ADCSR & ADCSR_ADST))
return panic("A/D data read attempt outside of conversion\n");
channel = adconv.ADCSR & ADCSR_CH_MASK;
if (channel != 2)
return panic("A/D data read attempt for wrong channel (%u != 2)\n", channel);
if (touchscreen.x < 0 || touchscreen.state != TOUCH_STATE_READ_X) {
*val_p = 0;
return 0;
}
*val_p = touchscreen.x >> 2;
return 0;
case ADCONV_ADDRCL_OFF:
if (!(adconv.ADCSR & ADCSR_ADST))
return panic("A/D data read attempt outside of conversion\n");
channel = adconv.ADCSR & ADCSR_CH_MASK;
if (channel != 2)
return panic("A/D data read attempt for wrong channel (%u != 2)\n", channel);
if (touchscreen.x < 0 || touchscreen.state != TOUCH_STATE_READ_X) {
*val_p = 0;
return 0;
}
*val_p = touchscreen.x << 6;
return 0;
case ADCONV_ADDRDH_OFF:
if (!(adconv.ADCSR & ADCSR_ADST))
return panic("A/D data read attempt outside of conversion\n");
channel = adconv.ADCSR & ADCSR_CH_MASK;
if (channel == 3) {
*val_p = battery.voltage >> 2;
return 0;
}
return panic("A/D ADDRD read attempt for wrong channel (%u)\n", channel);
case ADCONV_ADDRDL_OFF:
if (!(adconv.ADCSR & ADCSR_ADST))
return panic("A/D data read attempt outside of conversion\n");
channel = adconv.ADCSR & ADCSR_CH_MASK;
if (channel == 3) {
*val_p = battery.voltage << 6;
return 0;
}
return panic("A/D ADDRD read attempt for wrong channel (%u)\n", channel);
case ADCONV_ADCR_OFF:
*val_p = adconv.ADCR;
return 0;
default:
return panic("Attempted read of unsupported A/D converter register at 0x%.8x\n", addr);
}
}
/* The digital/analog converter */
struct daconv {
uint8_t DADR0; /* D/A data register 0 */
uint8_t DADR1; /* D/A data register 1 */
uint8_t DACR; /* D/A control register */
} daconv = {
.DACR = 0x1F,
};
#define DACONV_DADR0_OFF 0x040000A0
#define DACONV_DADR1_OFF 0x040000A2
#define DACONV_DACR_OFF 0x040000A4
/* Flags of the DACR register */
#define DACR_DAOE1 (1U << 7) /* D/A Output Enable 1 */
#define DACR_DAOE0 (1U << 6) /* D/A Output Enable 0 */
#define DACR_DAE (1U << 5) /* D/A Enable */
#define DACR_RSVD 0x1FU /* Reserved - always read as 1 */
static bool is_daconv_byte_address(uint32_t addr)
{
switch (addr) {
case DACONV_DADR0_OFF:
case DACONV_DADR1_OFF:
case DACONV_DACR_OFF:
return true;
default:
return false;
}
}
static int daconv_write_byte_reg(uint32_t addr, uint8_t val)
{
/*
* Contrast and brightness are set as a voltage between 0 and 256 on analog
* output pins DA0 and DA1 respectively. So the firmware converts the
* percentage into this range and writes it to these registers, then we
* convert it back to a percentage to report it. Integer division is
* involved in both directions so the result won't always be exactly the
* same.
*
* TODO: actually apply some transformation to the display contents.
*/
switch (addr) {
case DACONV_DADR0_OFF:
daconv.DADR0 = val;
if (daconv.DACR & DACR_DAE || daconv.DACR & DACR_DAOE0)
notice("Contrast set to %u%%\n", daconv.DADR0 * 100 >> 8);
return 0;
case DACONV_DADR1_OFF:
daconv.DADR1 = val;
if (daconv.DACR & DACR_DAE || daconv.DACR & DACR_DAOE1)
notice("Brightness set to %u%%\n", daconv.DADR1 * 100 >> 8);
return 0;
case DACONV_DACR_OFF:
daconv.DACR = val | DACR_RSVD;
return 0;
default:
return panic("BUG: nonexistent register for the D/A converter\n");
}
}
static int daconv_read_byte_reg(uint32_t addr, uint8_t *val_p)
{
switch (addr) {
case DACONV_DADR0_OFF:
*val_p = daconv.DADR0;
return 0;
case DACONV_DADR1_OFF:
*val_p = daconv.DADR1;
return 0;
case DACONV_DACR_OFF:
*val_p = daconv.DACR;
return 0;
default:
return panic("BUG: nonexistent register for the D/A converter\n");
}
}
enum button {
BUTTON_ONOFF = 0,
BUTTON_QL1 = 1,
BUTTON_QL2 = 2,
BUTTON_QL3 = 3,
BUTTON_QL4 = 4,
BUTTON_EXIT = 5,
BUTTON_RECORD = 6,
BUTTON_ENTER = 7,
BUTTON_DOWN = 8,
BUTTON_UP = 9,
/* Not a button... TODO: rethink this */
BUTTON_PEN = 10,
};
/* Set or unset a flag in a 32-bit register. TODO: reuse this function */
static void write_flag_to_long(uint32_t *reg, uint32_t flag, bool value)
{
if (value == true)
*reg |= flag;
else
*reg &= ~flag;
}
/* Set or unset a flag in an 8-bit register. TODO: reuse this function */
static void write_flag_to_byte(uint8_t *reg, uint8_t flag, bool value)
{
if (value == true)
*reg |= flag;
else
*reg &= ~flag;
}
#define BUTTON_ONOFF_PUSHED (1U << BUTTON_ONOFF)
#define BUTTON_QL1_PUSHED (1U << BUTTON_QL1)
#define BUTTON_QL2_PUSHED (1U << BUTTON_QL2)
#define BUTTON_QL3_PUSHED (1U << BUTTON_QL3)
#define BUTTON_QL4_PUSHED (1U << BUTTON_QL4)
#define BUTTON_QLX_MASK (BUTTON_QL1_PUSHED | BUTTON_QL2_PUSHED | BUTTON_QL3_PUSHED | BUTTON_QL4_PUSHED)
#define BUTTON_EXIT_PUSHED (1U << BUTTON_EXIT)
#define BUTTON_RECORD_PUSHED (1U << BUTTON_RECORD)
#define BUTTON_ENTER_PUSHED (1U << BUTTON_ENTER)
#define BUTTON_DOWN_PUSHED (1U << BUTTON_DOWN)
#define BUTTON_UP_PUSHED (1U << BUTTON_UP)
/*
* None of the buttons is pushed at first. Note that pins go low when the
* matching button gets pushed.
*/
static uint16_t button_state = 0;
/* TODO: the front buttons share a board and should have their own struct */
static bool front4_requested = false;
enum pin_sense_mode {
falling = 0x0000,
rising = 0x0001,
low = 0x0002,
high = 0x0003,
};
/*
* The Jornada 545 has a PHILIPS PDIUSBD12 usb interface device. I don't really
* want usb so I'm trying to implement as little as possible. Luckily a manual
* is available.
*/
#define USB_DATA_OFF 0x0BC00000
#define USB_COMMANDS_OFF 0x0BC00004
/* USB commands encountered so far */
/* Read last transaction status for each endpoint */
#define USB_CTRLOUT_STATUS 0x40 /* Control OUT */
#define USB_CTRLIN_STATUS 0x41 /* Control IN */
#define USB_END1OUT_STATUS 0x42 /* Endpoint 1 OUT */
#define USB_END1IN_STATUS 0x43 /* Endpoint 1 IN */
#define USB_END2OUT_STATUS 0x44 /* Endpoint 2 OUT */
#define USB_END2IN_STATUS 0x45 /* Endpoint 2 IN */
#define USB_SET_END_ENABLE 0xD8 /* Set endpoint enable */
#define USB_READ_INTR 0xF4 /* Read interrupt register */
#define USB_SET_DMA 0xFB
#define USB_SET_MODE 0xF3
/* The manual doesn't list an FF command, so I'll use that as a NULL */
#define USB_NO_COMMAND 0xFF
struct usb {
uint8_t command; /* Command in execution */
uint8_t buf_off;
uint8_t buf_end;
uint8_t buf[130];
} usb = {
.command = USB_NO_COMMAND,
};
static bool is_usb_byte_address(uint32_t addr)
{
switch (addr) {
case USB_DATA_OFF:
case USB_COMMANDS_OFF:
return true;
default:
return false;
}
}
static int usb_execute_command(void)
{
uint8_t byte;
switch (usb.command) {
case USB_SET_DMA:
byte = usb.buf[0];
if (byte & 0x3F)
return panic("USB DMA operation not supported\n");
if (byte & 0xC0)
notice("Interrupts enabled on USB endpoint buffer validation\n");
break;
case USB_SET_END_ENABLE:
byte = usb.buf[0];
if (byte)
return panic("USB generic/isochronous endpoints not supported\n");
break;
case USB_SET_MODE:
notice("USB mode set to 0x%.2x (clock division factor 0x%.2x)\n", usb.buf[0], usb.buf[1]);
break;
case USB_CTRLOUT_STATUS:
case USB_CTRLIN_STATUS:
case USB_END1OUT_STATUS:
case USB_END1IN_STATUS:
case USB_END2OUT_STATUS:
case USB_END2IN_STATUS:
/*
* These can stall or re-initialize the endpoints. I don't think I need
* to emulate anything right now. TODO: return the written status
* correctly on reads.
*/
break;
default:
return panic("Executing unsupported usb command 0x%.2x\n", usb.command);
}
usb.buf_off = 0;
usb.buf_end = 0;
usb.command = USB_NO_COMMAND;
return 0;
}
static uint8_t usb_command_to_trans_len(uint8_t command)
{
switch (command) {
case USB_CTRLOUT_STATUS:
case USB_CTRLIN_STATUS:
case USB_END1OUT_STATUS:
case USB_END1IN_STATUS:
case USB_END2OUT_STATUS:
case USB_END2IN_STATUS:
case USB_SET_END_ENABLE:
case USB_SET_DMA:
return 1;
case USB_READ_INTR:
case USB_SET_MODE:
case 0xfd:
return 2;
default:
(void)panic("BUG: accepting unsupported usb command 0x%.2x\n", command);
return 0;
}
}
static int usb_write_byte_reg(uint32_t addr, uint8_t val)
{
switch (addr) {
case USB_COMMANDS_OFF:
switch (val) {
case 0xfd:
notice("Unknown usb command 0x%.2x\n", val);
/* Fall through */
case USB_CTRLOUT_STATUS:
case USB_CTRLIN_STATUS:
case USB_END1OUT_STATUS:
case USB_END1IN_STATUS:
case USB_END2OUT_STATUS:
case USB_END2IN_STATUS:
case USB_SET_END_ENABLE:
case USB_READ_INTR:
case USB_SET_DMA:
case USB_SET_MODE:
usb.buf_off = 0;
usb.buf_end = usb_command_to_trans_len(val);
usb.command = val;
break;
default:
return panic("Unsupported usb command 0x%.2x\n", val);
}
return 0;
case USB_DATA_OFF:
if (usb.buf_off == usb.buf_end)
return panic("Input too long for usb command 0x%.2x\n", usb.command);
usb.buf[usb.buf_off++] = val;
if (usb.buf_off == usb.buf_end)
return usb_execute_command();
return 0;
default:
return panic("Attempted write of 0x%.2x to unsupported usb register at 0x%.8x\n", val, addr);
}
}
static int usb_read_byte_reg(uint32_t addr, uint8_t *val_p)
{
switch (addr) {
case USB_DATA_OFF:
if (usb.buf_off++ == usb.buf_end)
return panic("Too many reads for usb command 0x%.2x\n", usb.command);
/*
* Early on boot, the firmware runs status reads on all endpoints and
* reads the interrupt register. The values aren't checked, so I think
* the point is to clear interrupts. I don't even keep track of the usb
* interrupt register, so for now do nothing and just return zero.
*/
*val_p = 0;
return 0;
default:
return panic("Attempted read from unsupported usb register at 0x%.8x\n", addr);
}
}
enum monitor {
MONITOR_NONE,
MONITOR_SERIAL,
MONITOR_IRDA,
MONITOR_XB3A,
MONITOR_EEPROM,
MONITOR_AUDIO,
};
/* For monitoring xB3A and serial only for now. TODO: infrared. */
#define CONSOLE_MON_LINE_LIMIT 72
#define CONSOLE_MON_BUF_SIZE 80
struct console_monitor {
const char *cm_tag;
char cm_buf[CONSOLE_MON_BUF_SIZE];
int cm_len;
};
static struct console_monitor serial_monitor = { .cm_tag = "SERIAL" };
static struct console_monitor irda_monitor = { .cm_tag = "IRDA" };
static struct console_monitor xB3A_monitor = { .cm_tag = "XB3A" };
/* Not really a console... TODO: use a generic monitor buffer or something */
static struct console_monitor audio_monitor = { .cm_tag = "AUDIO" };
/*
* We don't want multiple monitors to buffer at the same time: that would make
* it hard to tell what happened first. Before saving to any monitor buffer,
* call this function to dump all the others.
*/
static void dump_monitors_except(enum monitor which)
{
if (which != MONITOR_SERIAL)
console_monitor_dump(&serial_monitor);
if (which != MONITOR_IRDA)
console_monitor_dump(&irda_monitor);
if (which != MONITOR_XB3A)
console_monitor_dump(&xB3A_monitor);
if (which != MONITOR_EEPROM)
eeprom_monitor_dump_all();
if (which != MONITOR_AUDIO)
console_monitor_dump(&audio_monitor);
}
static void dump_all_monitors(void)
{
return dump_monitors_except(MONITOR_NONE);
}
/*
* We have 16 MiB - 32 KiB of physical memory at address 0x8C008000, which is
* actually in the P2 area mapped to physical address space so the top 3 bits
* are ignored.
*/
#define MEMORY_SIZE (16 * 1024 * 1024)
uint8_t memory[MEMORY_SIZE] = {0};
#define MEMORY_OFF 0x0C000000
#define MEMORY_MASK (MEMORY_SIZE - 1)
/*
* To measure the size of the RAM, <0x8003023C> looks for a point when writes
* either wrap around or start getting ignored. I'll implement the first for
* now, but I haven't checked how the device actually works (TODO).
*/
#define MEMORY_SHADOW 0x0D000000
/*
* According to the display RAM self-tests, there are 512 KiB of memory at
* address 0xb4200000; the first 75 KiB are for the framebuffer. This is
* all in in the P2 area so the top 3 bits are ignored.
*/
#define DISPLAY_OFF 0x14000000
#define DISPLAY_FB_OFF 0x14200000
#define DISPLAY_FB_WIDTH 240
#define DISPLAY_FB_HEIGHT 320
#define DISPLAY_FB_SIZE (DISPLAY_FB_WIDTH * DISPLAY_FB_HEIGHT)
#define DISPLAY_RAM_SIZE 0x00080000
struct display {
/* Framebuffer and display ram. TODO: what is the ram for? Rename this? */
uint8_t fb[DISPLAY_RAM_SIZE];
uint8_t mode; /* Lots of guesswork here... */
/* The 256-color palette gets written one byte at a time */
uint8_t pal_idx; /* Palette entry to edit */
uint8_t pal_rgb; /* Color to edit (0-2) */
uint32_t pal[256]; /* The palette array */
/*
* The actual rgba screen contents after applying the palette. Only updated
* when it needs to get printed or displayed, and only if it changed since
* the last update.
*/
uint32_t output[DISPLAY_FB_SIZE];
} display = {0};
static bool display_dirty = true;
enum i2c_state {
I2C_STATE_STARTING,
I2C_STATE_RECEIVING_ADDRESS_ACK,
I2C_STATE_RECEIVING_DATA_ACK,
I2C_STATE_RECEIVING_DATA,
I2C_STATE_SENDING_DATA_ACK,
I2C_STATE_SENDING_ADDRESS,
I2C_STATE_SENDING_DATA,
I2C_STATE_STOPPED,
};
/* State of the i2c bus used to communicate with the EEPROM */
struct i2c {
uint8_t sda; /* Serial data line */
uint8_t scl; /* Serial clock line */
uint8_t bitcnt; /* Bits in frame so far */
uint8_t buffer; /* Byte being sent/received */
enum i2c_state state;
} i2c = {0};
static void eeprom_stop(void);
static int eeprom_start(void);
static int eeprom_receive_frame(uint8_t *frame_p);
static int eeprom_deliver_frame(uint8_t frame);
static void i2c_release_sda(void)
{
if (i2c.sda == 1)
return;
if (i2c.scl == 1) {
eeprom_stop();
i2c.state = I2C_STATE_STOPPED;
i2c.buffer = i2c.bitcnt = 0;
}
/* The "pullup resistor" does this */
i2c.sda = 1;
}
static void i2c_pull_down_sda(void)
{
if (i2c.sda == 0)
return;
if (i2c.scl == 1) {
i2c.state = I2C_STATE_STARTING;
i2c.buffer = i2c.bitcnt = 0;
}
i2c.sda = 0;
}
static int i2c_pull_up_scl(void)
{
if (i2c.scl == 1)
return 0;
/*
* We update the incoming sda values when the clock goes high, though in
* physical hardware it would have happened before... I think.
*
* TODO: what if the processor's port is not ready for reading? At least
* throw an error?