-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathS3ctl.c
More file actions
1350 lines (1204 loc) · 50.5 KB
/
Copy pathS3ctl.c
File metadata and controls
1350 lines (1204 loc) · 50.5 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
/*
* s3ctl.c - S3 ViRGE / Trio Runtime Control Utility
*
* Allows runtime control of S3 ViRGE and Trio chip parameters without
* requiring a BIOS flash. Changes are volatile and reset on reboot.
*
* Chip detection (tiered, in order):
* 1. --chip=NAME override
* 2. PCI BIOS scan (INT 1Ah, AX=B102h) for vendor 0x5333
* 3. CR-register probe (CR2D/CR2E)
* 4. SR1A sequencer probe (last resort; assumes ViRGE on match)
*
* Pedestal control register by family:
* ViRGE variants : SR1A bit 5 (verified)
* Trio 3D / 3D 2X : SR27 bit 3 (per bitsundbolts.com - UNTESTED)
* Legacy Trio : unsupported, use 'pedestal raw' to experiment
*
* Compile with Open Watcom (16-bit real mode DOS):
* wcl -bt=dos -ms s3ctl.c
*
* Usage:
* s3ctl [-q|-v] Show this help
* s3ctl [-q|-v] info Detect chip and display all parameters
* s3ctl [-q|-v] pedestal Show pedestal state (SR1A bit 5)
* s3ctl [-q|-v] pedestal off Clear SR1A bit 5 (true black; alias: 0)
* s3ctl [-q|-v] pedestal on Set SR1A bit 5 (raised; alias: 1)
* s3ctl [-q|-v] pedestal raw <n> Write full SR1A byte (advanced)
* s3ctl [-q|-v] mclk Show current MCLK frequency
* s3ctl [-q|-v] mclk <mhz> Set MCLK to target MHz (runtime only)
* (Note: there is no 'mclk reset' subcommand - reboot the machine
* to restore your OEM's factory MCLK value. The S3 reference of
* 45 MHz is not necessarily your machine's stock; e.g. the
* Toshiba Infinia ships its integrated ViRGE at ~60 MHz.)
*
* Flags:
* -q Quiet mode - suppress all output (useful in AUTOEXEC.BAT)
* -v Verbose mode - show extra diagnostic detail (VCO, raw regs,
* unlock/lock activity). Mutually exclusive with -q.
*
* Exit codes:
* 0 Success
* 1 S3 ViRGE not detected
* 2 Invalid argument
* 3 Operation failed
*
* Notes:
* - MCLK changes are runtime only and reset on reboot
* - Verify MCLK reads correctly before pushing higher
* - SR1A (pedestal) is an undocumented S3 register not in the datasheet
* - Only bit 5 of SR1A affects visible brightness (empirically verified
* on Toshiba Infinia ViRGE DX). Stock=0x3F, fixed=0x1F.
*
* Register reference (S3 ViRGE datasheet, August 1996):
* SR08 Unlock Extended Sequencer (write 0x06 to unlock SR09-SR1C)
* SR1A Pedestal control (undocumented)
* SR10 MCLK PLL N/R values (bits 4-0 = N, bits 6-5 = R encoded)
* SR11 MCLK PLL M value (bits 6-0)
* SR15 CLKSYN Control 2 (bit 0 = load new MCLK frequency)
* CR38 Register Lock 1 (write 0x48 to unlock CR30-CR3C)
* CR39 Register Lock 2 (write 0xA0 to unlock CR40-CR6D)
* CR2D Device ID High (0x56 for ViRGE)
* CR2E Device ID Low (0x31 for ViRGE/DX)
* CR2F Revision Level
* CR36 Configuration 1 (bits 7-5 = VRAM size)
* CR67 Extended Misc Control 2 (bits 7-4 = color mode)
*
* VCO formula (bitsundbolts.com S3 ViRGE BIOS Toolkit):
* VCO = ((M + 2) / (N + 2)) * 14.318 MHz
* MCLK = VCO / R
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <conio.h>
#include <math.h>
#include <i86.h> /* int86() for PCI BIOS calls */
/* ------------------------------------------------------------------ */
/* Global flags */
/* ------------------------------------------------------------------ */
static int quiet = 0; /* -q flag: suppress all output */
static int verbose = 0; /* -v flag: emit extra diagnostic detail */
#define qprintf if (!quiet) printf
#define vprintf_ if (verbose && !quiet) printf
/* ------------------------------------------------------------------ */
/* DOS convention: all user-facing tokens are case-insensitive. */
/* */
/* When adding new subcommands, flag values, or any other input the */
/* user types on the command line, compare with stricmp / strnicmp */
/* (Watcom string.h extensions) - NOT strcmp / strncmp. Hex prefixes */
/* should accept both "0x" and "0X". This matches the rest of DOS. */
/* ------------------------------------------------------------------ */
/* ------------------------------------------------------------------ */
/* Port addresses */
/* ------------------------------------------------------------------ */
#define SR_IDX 0x3C4 /* Sequencer index port */
#define SR_DATA 0x3C5 /* Sequencer data port */
#define CRTC_IDX 0x3D4 /* CRTC index port (color mode) */
#define CRTC_DATA 0x3D5 /* CRTC data port */
/* ------------------------------------------------------------------ */
/* Sequencer register indices */
/* ------------------------------------------------------------------ */
#define SR_UNLOCK 0x08 /* Unlock extended SR (write 0x06) */
#define SR_PLL_EXT_LO 0x0E /* PLL ext bits (Trio64V+ and later) */
#define SR_PLL_EXT_HI 0x0F /* PLL ext bits (Trio64V+ and later) */
#define SR_MCLK_LO 0x10 /* MCLK N/R values */
#define SR_MCLK_HI 0x11 /* MCLK M value */
#define SR_DCLK_LO 0x12 /* DCLK N/R values (pixel clock) */
#define SR_DCLK_HI 0x13 /* DCLK M value */
#define SR_CLKSYN2 0x15 /* CLKSYN control 2 */
#define SR_CLKSYN1 0x18 /* CLKSYN control 1 (clock src select) */
#define SR_PEDESTAL 0x1A /* ViRGE pedestal control (bit 5) */
#define SR_TRIO_PED 0x27 /* Trio 3D pedestal control (bit 3) */
/* ------------------------------------------------------------------ */
/* CRTC register indices */
/* ------------------------------------------------------------------ */
#define CR_LOCK1 0x38 /* Register lock 1 (write 0x48) */
#define CR_LOCK2 0x39 /* Register lock 2 (write 0xA0) */
#define CR_ID_HI 0x2D /* Chip ID high byte (0x56 = ViRGE) */
#define CR_ID_LO 0x2E /* Chip ID low byte (0x31 = ViRGE/DX) */
#define CR_REVISION 0x2F /* Revision level */
#define CR_CONFIG1 0x36 /* Configuration 1 (VRAM size) */
#define CR_COLORMOD 0x67 /* Color mode (bits 7-4) */
/* ------------------------------------------------------------------ */
/* Unlock keys */
/* ------------------------------------------------------------------ */
#define KEY_SR_UNLOCK 0x06 /* Unlocks SR09-SR1C */
#define KEY_CR_LOCK1 0x48 /* Unlocks CR30-CR3C */
#define KEY_CR_LOCK2 0xA0 /* Unlocks CR40-CR6D */
/* ------------------------------------------------------------------ */
/* PLL reference clock */
/* ------------------------------------------------------------------ */
#define FREF 14.318 /* VGA reference clock MHz */
/* ------------------------------------------------------------------ */
/* Safety limits for MCLK */
/* ------------------------------------------------------------------ */
#define MCLK_MIN 30 /* Minimum safe MCLK MHz */
#define MCLK_MAX 60 /* Maximum recommended MCLK MHz */
#define MCLK_REFERENCE 45 /* S3 reference BIOS MCLK MHz. */
/* NOT necessarily this OEM's factory */
/* value - e.g. Toshiba Infinia ships */
/* the integrated ViRGE at ~60 MHz. */
/* The actual per-machine stock can */
/* only be recovered by rebooting. */
/* ------------------------------------------------------------------ */
/* Pedestal bit positions (per chip family) */
/* ------------------------------------------------------------------ */
#define SR1A_PEDESTAL_BIT 0x20 /* ViRGE : SR1A bit 5 */
#define SR27_PEDESTAL_BIT 0x08 /* Trio3D : SR27 bit 3 */
/* ------------------------------------------------------------------ */
/* Chip family enumeration */
/* ------------------------------------------------------------------ */
/* Granularity: collapse variants that share pedestal control. */
/* CHIP_VIRGE - all ViRGE variants (DX/GX/VX/MX/GX2) use SR1A.5 */
/* CHIP_TRIO3D - Trio 3D / Trio 3D 2X use SR27.3 */
/* CHIP_TRIO_LEGACY - Trio32/64/64V+/64V2: detected, pedestal unknown */
/* CHIP_UNKNOWN - extended SR responds but family unidentified */
/* CHIP_NONE - no S3 detected */
enum chip_family {
CHIP_NONE = 0,
CHIP_UNKNOWN,
CHIP_VIRGE,
CHIP_TRIO_LEGACY,
CHIP_TRIO3D
};
enum detect_method {
DETECT_NONE = 0,
DETECT_PCI,
DETECT_CR,
DETECT_SR_PROBE,
DETECT_OVERRIDE
};
static enum chip_family detected_chip = CHIP_NONE;
static enum detect_method detect_via = DETECT_NONE;
static unsigned int pci_device_id = 0; /* PCI dev ID if found */
static enum chip_family chip_override = CHIP_NONE; /* --chip= value */
/* ------------------------------------------------------------------ */
/* PCI vendor / device IDs (from Linux s3fb.c) */
/* ------------------------------------------------------------------ */
#define S3_PCI_VENDOR 0x5333
/* Device IDs and the family each one maps to. */
/* Sub-variant identification (Trio32 vs Trio64, ViRGE/DX vs /GX, etc.) */
/* happens via CR-register reads after PCI tells us the family. */
#define PCI_DEV_TRIO_A 0x8810 /* Trio32/64/64V+ (legacy) */
#define PCI_DEV_TRIO_B 0x8811 /* Trio32/64/64V+ (legacy) */
#define PCI_DEV_VIRGE_VX 0x883D
#define PCI_DEV_TRIO3D 0x8904 /* Trio 3D and Trio 3D/2X */
#define PCI_DEV_VIRGE_DXGX 0x8A01
#define PCI_DEV_VIRGE_GX2 0x8A10
#define PCI_DEV_VIRGE_MX 0x8C01
/* ================================================================== */
/* Low level register access */
/* ================================================================== */
unsigned char sr_read(unsigned char index)
{
outp(SR_IDX, index);
return (unsigned char)inp(SR_DATA);
}
void sr_write(unsigned char index, unsigned char val)
{
outp(SR_IDX, index);
outp(SR_DATA, val);
}
unsigned char cr_read(unsigned char index)
{
outp(CRTC_IDX, index);
return (unsigned char)inp(CRTC_DATA);
}
void cr_write(unsigned char index, unsigned char val)
{
outp(CRTC_IDX, index);
outp(CRTC_DATA, val);
}
/*
* Returns 1 if CRTC extended registers appear accessible on this
* system, 0 if they read as the 0x80 sentinel value that some OEM
* integrated implementations (Toshiba Infinia) return for all
* extended CR reads.
*
* Probes CR2D and CR2E (chip ID, read-only). If BOTH come back as
* 0x80, the path is dead and downstream reads of CR36/CR67 etc.
* cannot be trusted. If at least one byte is anything other than
* 0x80, we treat the path as live.
*
* Caller must have run the CR unlock sequence already.
*/
static int cr_ext_readable(void)
{
unsigned char cr2d = cr_read(CR_ID_HI);
unsigned char cr2e = cr_read(CR_ID_LO);
if (cr2d == 0x80 && cr2e == 0x80) return 0;
return 1;
}
/* ================================================================== */
/* Unlock / lock routines */
/* ================================================================== */
void unlock_s3(void)
{
vprintf_("[v] unlock: SR08 <- 0x%02X\n", KEY_SR_UNLOCK);
sr_write(SR_UNLOCK, KEY_SR_UNLOCK); /* Unlock extended SR */
/* Note: CRTC extended register unlock (CR38/CR39) is not used */
/* as the CRTC extended registers are inaccessible on some OEM */
/* integrated implementations regardless of unlock key. */
}
void lock_s3(void)
{
/* SR08 auto-locks on next mode set - write 0x00 to be explicit */
vprintf_("[v] lock: SR08 <- 0x00\n");
sr_write(SR_UNLOCK, 0x00);
}
/* ================================================================== */
/* Detection */
/* ================================================================== */
/*
* Three-tier detection strategy:
*
* 1. --chip=X override (skips all probing)
* 2. PCI BIOS scan (INT 1Ah, AX=B102h) - most reliable
* Vendor 0x5333, dispatch on device ID.
* 3. CR-register probe (CR2D/CR2E/CR30/CR2F) - fallback when no
* PCI BIOS or no match
* 4. SR1A sequencer probe - last resort. CRTC extended regs are
* inaccessible on some OEM integrated implementations (Toshiba
* Infinia returns 0x80 for all CR2D/CR2E reads regardless of
* unlock key). If SR1A responds correctly we assume ViRGE,
* since SR1A bit 5 is the documented ViRGE pedestal bit and
* no Trio variant is known to be deployed on systems where
* CR-detection fails.
*
* Each tier populates detected_chip, pci_device_id (if known), and
* detect_via. detect_chip() returns 1 if anything S3-ish was found,
* 0 otherwise.
*/
/*
* PCI BIOS presence check (INT 1Ah, AX=B101h).
* Per PCI BIOS Specification 2.1:
* On entry: AH=B1h, AL=01h
* On return: EDX="PCI " (0x20494350), AH=0, CF=0 if present
* In 16-bit Watcom we only see DX (low word of EDX) = 0x4350 ("PC").
* Check AH=0 + CF clear + DX="PC" - three independent matches make
* a false positive extremely unlikely.
*/
static int pci_bios_present(void)
{
union REGS r;
r.w.ax = 0xB101;
int86(0x1A, &r, &r);
if ((r.w.cflag & 1) != 0) return 0;
if (r.h.ah != 0) return 0;
if (r.w.dx != 0x4350) return 0; /* "PC" low word of "PCI " */
return 1;
}
/*
* Find a PCI device by vendor + device ID (INT 1Ah, AX=B102h).
* On entry: AH=B1h, AL=02h, CX=device_id, DX=vendor_id, SI=index
* On return: BH=bus, BL=devfunc, AH=0 if found, CF clear
* Returns 1 if found (bus/devfunc filled), 0 otherwise.
*/
static int pci_find_device(unsigned int vendor, unsigned int device)
{
union REGS r;
r.w.ax = 0xB102;
r.w.cx = device;
r.w.dx = vendor;
r.w.si = 0;
int86(0x1A, &r, &r);
if ((r.w.cflag & 1) != 0) return 0;
if (r.h.ah != 0) return 0;
return 1;
}
/*
* PCI BIOS scan for any known S3 device.
* Populates detected_chip and pci_device_id on success.
* Returns 1 if a known S3 device is found, 0 otherwise.
*/
static int detect_via_pci(void)
{
struct {
unsigned int dev_id;
enum chip_family family;
const char *label;
} table[] = {
{ PCI_DEV_TRIO_A, CHIP_TRIO_LEGACY, "Trio (0x8810)" },
{ PCI_DEV_TRIO_B, CHIP_TRIO_LEGACY, "Trio (0x8811)" },
{ PCI_DEV_VIRGE_VX, CHIP_VIRGE, "ViRGE/VX" },
{ PCI_DEV_TRIO3D, CHIP_TRIO3D, "Trio 3D" },
{ PCI_DEV_VIRGE_DXGX, CHIP_VIRGE, "ViRGE/DX or GX" },
{ PCI_DEV_VIRGE_GX2, CHIP_VIRGE, "ViRGE/GX2" },
{ PCI_DEV_VIRGE_MX, CHIP_VIRGE, "ViRGE/MX" },
{ 0, CHIP_NONE, NULL }
};
int i;
if (!pci_bios_present()) {
vprintf_("[v] PCI BIOS not present, skipping PCI detection\n");
return 0;
}
vprintf_("[v] PCI BIOS present, scanning for vendor 0x%04X\n",
S3_PCI_VENDOR);
for (i = 0; table[i].label != NULL; i++) {
if (pci_find_device(S3_PCI_VENDOR, table[i].dev_id)) {
detected_chip = table[i].family;
pci_device_id = table[i].dev_id;
detect_via = DETECT_PCI;
vprintf_("[v] PCI match: vendor 0x5333 device 0x%04X (%s)\n",
table[i].dev_id, table[i].label);
return 1;
}
}
vprintf_("[v] PCI scan: no known S3 device found\n");
return 0;
}
/*
* CR-register probe. Reads CR2D/CR2E. Note: on the Toshiba Infinia
* these reads return 0x80 regardless of unlock, in which case this
* probe fails and we fall through to the SR probe.
*
* Known signatures (CR2D high byte):
* 0x88 - S3 Trio family
* 0x56 - S3 ViRGE family
*/
static int detect_via_cr(void)
{
unsigned char cr2d, cr2e;
/* Try the CR38/CR39 unlock keys in case this chip honors them. */
cr_write(CR_LOCK1, KEY_CR_LOCK1);
cr_write(CR_LOCK2, KEY_CR_LOCK2);
cr2d = cr_read(CR_ID_HI);
cr2e = cr_read(CR_ID_LO);
vprintf_("[v] CR probe: CR2D=0x%02X CR2E=0x%02X\n", cr2d, cr2e);
if (cr2d == 0x88) {
/* Trio family. CR2E disambiguates further but for pedestal */
/* purposes we only care Trio3D vs legacy Trio. */
/* CR2E=0x10 Trio32, 0x11 Trio64/64V+, others = newer variants */
if (cr2e >= 0x10 && cr2e <= 0x13) {
detected_chip = CHIP_TRIO_LEGACY;
} else {
/* Higher CR2E values map to Trio3D and successors */
detected_chip = CHIP_TRIO3D;
}
detect_via = DETECT_CR;
return 1;
}
if (cr2d == 0x56) {
detected_chip = CHIP_VIRGE;
detect_via = DETECT_CR;
return 1;
}
return 0;
}
/*
* SR1A sequencer probe. Last-resort detection.
* Writes 0x55 to SR1A, reads back. Match indicates S3 extended
* sequencer is present and responding. We assume ViRGE since SR1A
* bit 5 is the ViRGE pedestal bit and this path is what the Toshiba
* Infinia falls back to.
*/
static int detect_via_sr_probe(void)
{
unsigned char sr1a_orig, sr1a_readback;
sr_write(SR_UNLOCK, KEY_SR_UNLOCK);
sr1a_orig = sr_read(SR_PEDESTAL);
vprintf_("[v] SR probe: SR1A orig=0x%02X, probing with 0x55\n",
sr1a_orig);
sr_write(SR_PEDESTAL, 0x55);
sr1a_readback = sr_read(SR_PEDESTAL);
vprintf_("[v] SR probe: SR1A readback=0x%02X (%s)\n", sr1a_readback,
sr1a_readback == 0x55 ? "match" : "mismatch");
sr_write(SR_PEDESTAL, sr1a_orig);
if (sr1a_readback == 0x55) {
detected_chip = CHIP_VIRGE;
detect_via = DETECT_SR_PROBE;
return 1;
}
return 0;
}
/*
* Top-level chip detection. Runs the tiered strategy and populates
* detected_chip / detect_via / pci_device_id. Returns 1 if any S3
* chip was identified, 0 if nothing responded.
*/
int detect_chip(void)
{
/* Tier 1: explicit override */
if (chip_override != CHIP_NONE) {
detected_chip = chip_override;
detect_via = DETECT_OVERRIDE;
vprintf_("[v] using --chip override\n");
/* Still unlock so subsequent reg ops work */
sr_write(SR_UNLOCK, KEY_SR_UNLOCK);
return 1;
}
/* Tier 2: PCI BIOS */
if (detect_via_pci()) {
sr_write(SR_UNLOCK, KEY_SR_UNLOCK);
return 1;
}
/* Tier 3: CR registers */
if (detect_via_cr()) {
sr_write(SR_UNLOCK, KEY_SR_UNLOCK);
return 1;
}
/* Tier 4: SR1A probe (last resort) */
if (detect_via_sr_probe()) {
/* SR probe leaves SR08 unlocked already */
return 1;
}
detected_chip = CHIP_NONE;
detect_via = DETECT_NONE;
return 0;
}
/*
* Human-readable chip family label.
*/
static const char *chip_family_name(enum chip_family f)
{
switch (f) {
case CHIP_VIRGE: return "S3 ViRGE";
case CHIP_TRIO3D: return "S3 Trio 3D";
case CHIP_TRIO_LEGACY: return "S3 Trio (legacy)";
case CHIP_UNKNOWN: return "S3 (unknown variant)";
default: return "none";
}
}
static const char *detect_method_name(enum detect_method m)
{
switch (m) {
case DETECT_PCI: return "PCI BIOS";
case DETECT_CR: return "CR2D/CR2E";
case DETECT_SR_PROBE: return "SR1A probe";
case DETECT_OVERRIDE: return "--chip override";
default: return "none";
}
}
/*
* Translate PCI device ID into a specific chip label. For 0x8A01
* (ViRGE/DX or /GX) and 0x8904 (Trio 3D / Trio 3D 2X) the device ID
* alone is ambiguous; the caller can pass CR-derived hints if those
* registers were readable. Returns NULL if no PCI match.
*/
static const char *pci_id_label(unsigned int dev_id)
{
switch (dev_id) {
case PCI_DEV_TRIO_A: return "S3 Trio32/64/64V+ (legacy 0x8810)";
case PCI_DEV_TRIO_B: return "S3 Trio32/64/64V+ (legacy 0x8811)";
case PCI_DEV_VIRGE_VX: return "S3 ViRGE/VX";
case PCI_DEV_TRIO3D: return "S3 Trio 3D";
case PCI_DEV_VIRGE_DXGX: return "S3 ViRGE/DX or ViRGE/GX";
case PCI_DEV_VIRGE_GX2: return "S3 ViRGE/GX2";
case PCI_DEV_VIRGE_MX: return "S3 ViRGE/MX";
default: return NULL;
}
}
/* ================================================================== */
/* MCLK calculation */
/* ================================================================== */
/*
* R encoding in SR10 bits 6-5:
* 00 = divide by 1
* 01 = divide by 2
* 10 = divide by 4
* 11 = divide by 8
*/
static int r_decode(unsigned char r_bits)
{
switch (r_bits & 0x03) {
case 0: return 1;
case 1: return 2;
case 2: return 4;
case 3: return 8;
}
return 1;
}
static unsigned char r_encode(int r_val)
{
switch (r_val) {
case 1: return 0x00;
case 2: return 0x01;
case 4: return 0x02;
case 8: return 0x03;
}
return 0x00;
}
/*
* Decode a PLL byte pair (M-low/N-R, M-high) into M, N, R, and the
* resulting frequency in MHz. Used for both MCLK (SR10/SR11) and
* DCLK (SR12/SR13) which share the same encoding.
* Caller passes the two raw byte values; outputs are filled in.
*/
static void decode_pll(unsigned char lo, unsigned char hi,
int *M, int *N, int *R,
double *vco_out, double *freq_out)
{
*N = (int)(lo & 0x1F);
*R = r_decode((lo >> 5) & 0x03);
*M = (int)(hi & 0x7F);
*vco_out = ((double)(*M + 2) / (double)(*N + 2)) * FREF;
*freq_out = *vco_out / (double)*R;
}
/*
* Read current MCLK from SR10/SR11 and calculate frequency.
* Uses corrected VCO formula: VCO = ((M+2)/(N+2)) * 14.318
* MCLK = VCO / R
*/
double read_mclk(void)
{
unsigned char sr10, sr11;
int M, N, R;
double vco, mclk;
sr10 = sr_read(SR_MCLK_LO);
sr11 = sr_read(SR_MCLK_HI);
decode_pll(sr10, sr11, &M, &N, &R, &vco, &mclk);
return mclk;
}
/*
* Find the best M, N, R values to achieve a target MCLK frequency.
* Searches all valid combinations and picks the closest match.
* VCO must stay within 135-270 MHz (from bitsundbolts toolkit spec).
*
* Returns 1 on success, 0 if no valid combination found.
* Writes best M, N, R to output pointers.
*/
int find_pll_values(double target_mhz,
int *best_M, int *best_N, int *best_R,
double *best_freq)
{
int M, N, R;
double vco, freq, diff, best_diff;
int r_vals[4] = {1, 2, 4, 8};
int i;
best_diff = 1e9;
*best_M = 0;
*best_N = 0;
*best_R = 1;
*best_freq = 0.0;
for (i = 0; i < 4; i++) {
R = r_vals[i];
for (N = 0; N <= 31; N++) {
for (M = 0; M <= 127; M++) {
vco = ((double)(M + 2) / (double)(N + 2)) * FREF;
freq = vco / (double)R;
/* VCO must be within 135-270 MHz */
if (vco < 135.0 || vco > 270.0)
continue;
/* Frequency must be within safety limits */
if (freq < (double)MCLK_MIN || freq > (double)MCLK_MAX)
continue;
diff = freq - target_mhz;
if (diff < 0.0) diff = -diff;
if (diff < best_diff) {
best_diff = diff;
*best_M = M;
*best_N = N;
*best_R = R;
*best_freq = freq;
}
}
}
}
return (*best_freq > 0.0);
}
/*
* Write new MCLK PLL values to SR10/SR11 and trigger a load.
* Per datasheet SR15 bit 0 must be set then cleared.
*/
void write_mclk(int M, int N, int R)
{
unsigned char sr10_val, sr15_val;
sr10_val = (unsigned char)(N & 0x1F);
sr10_val |= (unsigned char)((r_encode(R) & 0x03) << 5);
sr_write(SR_MCLK_LO, sr10_val);
sr_write(SR_MCLK_HI, (unsigned char)(M & 0x7F));
/* Toggle bit 0 of SR15 to load new frequency */
sr15_val = sr_read(SR_CLKSYN2);
sr_write(SR_CLKSYN2, sr15_val | 0x01); /* Set load bit */
sr_write(SR_CLKSYN2, sr15_val & ~0x01); /* Clear load bit */
}
/* Note: vram_size_kb() and color_mode_str() are intentionally omitted.
* These decode CR36 and CR67 CRTC extended registers which are not
* accessible on OEM integrated ViRGE implementations. They are
* preserved in the handoff documentation for future reference if
* CRTC access becomes available on other hardware configurations. */
/* ================================================================== */
/* Command implementations */
/* ================================================================== */
/* Forward decl - defined just above cmd_pedestal */
static int pedestal_target(unsigned char *sr_index,
unsigned char *bitmask,
const char **reg_label,
int *bitno);
/*
* Decode CR36 VRAM size field (bits 7-5) into KB. Returns 0 if the
* field is one of the reserved values not in the datasheet table.
*/
static unsigned int vram_kb_from_cr36(unsigned char cr36)
{
switch ((cr36 >> 5) & 0x07) {
case 0: return 512;
case 1: return 1024;
case 2: return 2048;
case 3: return 4096;
default: return 0;
}
}
/*
* Decode CR67 color mode field (bits 7-4). Returns a static string.
*/
static const char *color_mode_from_cr67(unsigned char cr67)
{
switch ((cr67 >> 4) & 0x0F) {
case 0x0: return "8bpp palettized";
case 0x1: return "8bpp clock-doubled";
case 0x3: return "15bpp high-color";
case 0x5: return "16bpp high-color";
case 0xD: return "24bpp true-color";
default: return "reserved/unknown";
}
}
/*
* Build a sub-variant label that's as specific as the evidence allows.
* Combines PCI device ID + (if CR-ext is readable) CR2E/CR6F. The
* caller-supplied buffer must be at least 64 bytes.
*/
static const char *build_chip_label(char *out_buf,
int cr_live,
unsigned char cr2e,
unsigned char cr6f)
{
const char *base = pci_id_label(pci_device_id);
if (base == NULL) base = chip_family_name(detected_chip);
/* PCI 0x8A01 is ViRGE/DX or /GX; CR6F bit 0 distinguishes. */
if (pci_device_id == PCI_DEV_VIRGE_DXGX && cr_live) {
if ((cr6f & 0x01) == 0)
sprintf(out_buf, "S3 ViRGE/DX (86C375)");
else
sprintf(out_buf, "S3 ViRGE/GX (86C385)");
return out_buf;
}
/* PCI 0x8810/0x8811 lumps Trio32/64/64V+; CR2E disambiguates. */
if ((pci_device_id == PCI_DEV_TRIO_A ||
pci_device_id == PCI_DEV_TRIO_B) && cr_live) {
if (cr2e == 0x10) {
sprintf(out_buf, "S3 Trio32 (86C732)");
return out_buf;
}
if (cr2e == 0x11) {
sprintf(out_buf, "S3 Trio64 family (CR2E=0x11)");
return out_buf;
}
}
/* No further refinement - return the base label. */
sprintf(out_buf, "%s", base);
return out_buf;
}
/*
* cmd_info: detect chip and display all readable parameters
*/
int cmd_info(void)
{
double mclk;
unsigned char sr10, sr11;
int M, N, R;
unsigned char ped_idx = 0, ped_mask = 0;
const char *ped_reg = "";
int ped_bit = 0;
int has_ped;
/* Verbose clock-synth diagnostics */
unsigned char sr0e, sr0f, sr12, sr13, sr15, sr18;
int dM, dN, dR;
double dvco, dclk;
char label[64];
int cr_live;
unsigned char cr2d = 0, cr2e = 0, cr2f = 0, cr36 = 0, cr67 = 0, cr6f = 0;
unsigned int vram_kb = 0;
if (!detect_chip()) {
qprintf("Error: No S3 chip detected.\n");
qprintf(" PCI scan, CR registers, and SR1A probe all failed.\n");
qprintf(" Try --chip=virge or --chip=trio3d to override.\n");
return 1;
}
/* Try the CR extended unlock so chip-ID / VRAM / colormode reads */
/* succeed on chips that honor it. cr_ext_readable() then tells */
/* us whether the path actually works on this hardware. */
cr_write(CR_LOCK1, KEY_CR_LOCK1);
cr_write(CR_LOCK2, KEY_CR_LOCK2);
cr_live = cr_ext_readable();
if (cr_live) {
cr2d = cr_read(CR_ID_HI);
cr2e = cr_read(CR_ID_LO);
cr2f = cr_read(CR_REVISION);
cr36 = cr_read(CR_CONFIG1);
cr67 = cr_read(CR_COLORMOD);
cr6f = cr_read(0x6F);
vram_kb = vram_kb_from_cr36(cr36);
}
sr10 = sr_read(SR_MCLK_LO);
sr11 = sr_read(SR_MCLK_HI);
mclk = read_mclk();
N = sr10 & 0x1F;
R = r_decode((sr10 >> 5) & 0x03);
M = sr11 & 0x7F;
qprintf("\n");
qprintf("S3 ViRGE/Trio Runtime Control Utility\n");
qprintf("--------------------------------------\n");
qprintf("Chip: %s\n",
build_chip_label(label, cr_live, cr2e, cr6f));
qprintf("Family: %s\n", chip_family_name(detected_chip));
qprintf("Detected via: %s", detect_method_name(detect_via));
if (pci_device_id != 0)
qprintf(" (PCI 0x%04X:0x%04X)", S3_PCI_VENDOR, pci_device_id);
qprintf("\n");
if (cr_live) {
qprintf("Revision: CR2F=0x%02X\n", cr2f);
if (vram_kb)
qprintf("VRAM: %u KB (CR36=0x%02X, bits 7-5 = %d)\n",
vram_kb, cr36, (cr36 >> 5) & 0x07);
else
qprintf("VRAM: (reserved field) CR36=0x%02X\n", cr36);
qprintf("Color mode: %s (CR67=0x%02X, bits 7-4 = 0x%X)\n",
color_mode_from_cr67(cr67), cr67, (cr67 >> 4) & 0x0F);
if (verbose) {
printf("[v] CR2D=0x%02X CR2E=0x%02X CR2F=0x%02X CR36=0x%02X CR67=0x%02X CR6F=0x%02X\n",
cr2d, cr2e, cr2f, cr36, cr67, cr6f);
}
} else {
qprintf("Revision: n/a (CRTC extended regs unreadable on this system)\n");
qprintf("VRAM: n/a (same)\n");
qprintf("Color mode: n/a (same)\n");
if (verbose)
printf("[v] CR2D/CR2E both read as 0x80 - typical OEM-integrated case\n");
}
qprintf("MCLK: %.2f MHz (S3 reference: %d MHz)\n",
mclk, MCLK_REFERENCE);
if (mclk > (double)MCLK_REFERENCE + 5.0 ||
mclk < (double)MCLK_REFERENCE - 5.0) {
qprintf(" Note: this OEM may not use the S3 reference value.\n");
qprintf(" Reboot is the only way to restore factory MCLK.\n");
}
qprintf(" M=%d N=%d R=%d\n", M, N, R);
qprintf(" SR10=0x%02X SR11=0x%02X\n", sr10, sr11);
has_ped = pedestal_target(&ped_idx, &ped_mask, &ped_reg, &ped_bit);
if (has_ped) {
unsigned char pv = sr_read(ped_idx);
qprintf("Pedestal: %s=0x%02X bit %d = %d (%s)\n",
ped_reg, pv, ped_bit,
(pv & ped_mask) ? 1 : 0,
(pv & ped_mask) ? "on / raised" : "off / true black");
} else {
qprintf("Pedestal: not supported for this chip family\n");
}
if (verbose) {
double vco;
unsigned char r_bits;
decode_pll(sr10, sr11, &M, &N, &R, &vco, &mclk);
r_bits = (sr10 >> 5) & 0x03;
printf("[v] FREF: %.3f MHz (reference crystal)\n", FREF);
printf("[v] MCLK PLL: M=%d N=%d R=%d VCO=%.2f MCLK=%.2f MHz\n",
M, N, R, vco, mclk);
printf("[v] R bits: %d%d (SR10[6:5]) -> R=%d\n",
(r_bits >> 1) & 1, r_bits & 1, R);
/* Read the rest of the clock-synth state so we can sanity */
/* check whether SR10/SR11 are really MCLK on this chip and */
/* whether the BIOS programmed a non-stock value. */
sr0e = sr_read(SR_PLL_EXT_LO);
sr0f = sr_read(SR_PLL_EXT_HI);
sr12 = sr_read(SR_DCLK_LO);
sr13 = sr_read(SR_DCLK_HI);
sr15 = sr_read(SR_CLKSYN2);
sr18 = sr_read(SR_CLKSYN1);
decode_pll(sr12, sr13, &dM, &dN, &dR, &dvco, &dclk);
printf("[v] DCLK PLL: M=%d N=%d R=%d VCO=%.2f DCLK=%.2f MHz\n",
dM, dN, dR, dvco, dclk);
printf("[v] SR12=0x%02X SR13=0x%02X\n", sr12, sr13);
printf("[v] Clk synth: SR0E=0x%02X SR0F=0x%02X (PLL ext bits, Trio64V+ and later)\n",
sr0e, sr0f);
printf("[v] SR15=0x%02X (CLKSYN ctrl 2 - load bits)\n", sr15);
printf("[v] SR18=0x%02X (CLKSYN ctrl 1 - clock source select)\n", sr18);
printf("[v] Note: if DCLK matches your current display mode's pixel\n");
printf("[v] clock (25.18, 40, 65 MHz etc.) and MCLK reads ~60,\n");
printf("[v] then SR10/SR11 really are MCLK and the BIOS may have\n");
printf("[v] OEM-tuned this part above the 45 MHz reference value.\n");
}
qprintf("\n");
lock_s3();
return 0;
}
/*
* cmd_pedestal: read or write SR1A pedestal register
*/
/*
* SR1A pedestal control - bit 5 only.
*
* Experimentally confirmed on Toshiba Infinia S3 ViRGE DX:
* - Bit 5 set -> pedestal raised (grey blacks)
* - Bit 5 clear -> pedestal off (true black)
* - Other bits in SR1A do not affect visible brightness.
* Stock BIOS programs SR1A=0x3F; pedestal-fixed BIOS programs 0x1F
* (matches the +0x20 checksum delta documented in the handoff).
*
* The CLI exposes on/off (and 0/1 aliases) which read-modify-write
* bit 5 only, leaving the other bits as the BIOS set them. A 'raw'
* subcommand is provided for poking the full byte if needed.
*/
/*
* Pedestal register/bit dispatch by chip family.
* ViRGE : SR1A bit 5 (0x20) - empirically verified
* Trio3D : SR27 bit 3 (0x08) - per bitsundbolts article, UNTESTED
* Returns 1 if supported (sr_index/bitmask/label/bitno filled in),
* 0 if pedestal control is not known for this chip family.
*/
static int pedestal_target(unsigned char *sr_index,
unsigned char *bitmask,
const char **reg_label,
int *bitno)
{
switch (detected_chip) {
case CHIP_VIRGE:
*sr_index = SR_PEDESTAL;
*bitmask = SR1A_PEDESTAL_BIT;
*reg_label = "SR1A";
*bitno = 5;
return 1;
case CHIP_TRIO3D:
*sr_index = SR_TRIO_PED;
*bitmask = SR27_PEDESTAL_BIT;
*reg_label = "SR27";
*bitno = 3;
return 1;
default:
return 0;
}
}
int cmd_pedestal(int argc, char *argv[])
{
unsigned char current, new_val;
unsigned char sr_index = 0, bitmask = 0;
const char *reg_label = "";
int bitno = 0;
int want_on;
int is_raw = 0;
int parsed = 0;
if (!detect_chip()) {
qprintf("Error: No S3 chip detected.\n");
qprintf(" Try --chip=virge or --chip=trio3d to override.\n");
return 1;
}
if (!pedestal_target(&sr_index, &bitmask, ®_label, &bitno)) {
qprintf("Error: pedestal control is unknown for %s.\n",
chip_family_name(detected_chip));
qprintf(" Supported families: ViRGE (SR1A.5), Trio 3D (SR27.3).\n");
qprintf(" Use 'pedestal raw' on a known register if you want to experiment.\n");