1 module gbaid.gba.gpio;
2 
3 import std.conv : to;
4 import std.meta : AliasSeq;
5 
6 import gbaid.util;
7 
8 public enum uint GPIO_ROM_START_ADDRESS = 0xC4;
9 public enum uint GPIO_ROM_END_ADDRESS = 0xCA;
10 
11 public alias IoPinOut = bool delegate();
12 public alias IoPinIn = void delegate(bool pin);
13 
14 public struct GpioChip {
15     mixin declareFields!(IoPinOut, false, "readPin", null, 4);
16     mixin declareFields!(IoPinIn, false, "writePin", null, 4);
17 }
18 
19 public struct GpioPort {
20     public bool enabled = false;
21     private bool readable = false;
22     private ubyte directionFlags = 0b0000;
23     private short _valueAtCa = 0;
24     private GpioChip _chip;
25 
26     @property public void valueAtCa(short value) {
27         _valueAtCa = value;
28     }
29 
30     @property public void chip(GpioChip chip) {
31         _chip = chip;
32     }
33 
34     public T get(T)(uint address) {
35         // 32 bit reads must be aligned at 4 instead of 2
36         static if (is(T == int) || is(T == uint)) {
37             uint alignedAddress = address & ~0b11;
38         } else {
39             uint alignedAddress = address & ~0b1;
40         }
41         // Read the value from the register
42         short shortValue = void;
43         switch (alignedAddress) {
44             case 0xC4:
45                 shortValue = data;
46                 break;
47             case 0xC6:
48                 shortValue = direction;
49                 break;
50             case 0xC8:
51                 shortValue = control;
52                 break;
53             case 0xCA:
54                 shortValue = _valueAtCa;
55                 break;
56             default:
57                 throw new Exception("Invalid GPIO address: " ~ address.to!string);
58         }
59         // Convert the register value to the correct format
60         static if (is(T == byte) || is(T == ubyte)) {
61             return cast(byte) (shortValue >>> (address & 0b1) * 8);
62         } else static if (is(T == short) || is(T == ushort)) {
63             return shortValue;
64         } else static if (is(T == int) || is(T == uint)) {
65             // For ints, we must do a second read for the upper bits
66             return get!short(alignedAddress + 2) << 16 | shortValue & 0xFFFF;
67         } else {
68             static assert (0);
69         }
70     }
71 
72     public void set(T)(uint address, T value) {
73         // Convert the value to a short based on the address, and align the address to the correct register
74         static if (is(T == byte) || is(T == ubyte)) {
75             short shortValue = cast(short) ((value & 0xFF) << (address & 0b1) * 8);
76             address &= ~0b1;
77         } else static if (is(T == short) || is(T == ushort)) {
78             short shortValue = value;
79             address &= ~0b1;
80         } else static if (is(T == int) || is(T == uint)) {
81             short shortValue = cast(short) value;
82             address &= ~0b11;
83             // For ints, we must do a second write for the upper bits
84             set!short(address + 2, cast(short) (value >>> 16));
85         } else {
86             static assert (0);
87         }
88         // Write the value
89         switch (address) {
90             case 0xC4:
91                 data = shortValue;
92                 break;
93             case 0xC6:
94                 direction = shortValue;
95                 break;
96             case 0xC8:
97                 control = shortValue;
98                 break;
99             case 0xCA:
100                 break;
101             default:
102                 throw new Exception("Invalid GPIO address: " ~ address.to!string);
103         }
104     }
105 
106     @property private short control() {
107         if (!readable) {
108             return 0;
109         }
110         return cast(short) readable.checkBit(0);
111     }
112 
113     @property private void control(short value) {
114         readable = value.checkBit(0);
115     }
116 
117     @property private short direction() {
118         if (!readable) {
119             return 0;
120         }
121         return cast(short) directionFlags.getBits(0, 3);
122     }
123 
124     @property private void direction(short value) {
125         directionFlags = cast(ubyte) value.getBits(0, 3);
126     }
127 
128     @property private short data() {
129         if (!readable) {
130             return 0;
131         }
132         // Read from the input pins
133         int data = 0;
134         foreach (pin; AliasSeq!(0, 1, 2, 3)) {
135             if (!directionFlags.checkBit(pin)) {
136                 data.setBit(pin, _chip.readPin!pin());
137             }
138         }
139         return cast(short) data;
140     }
141 
142     @property private void data(short value) {
143         // Write to the output pins
144         foreach (pin; AliasSeq!(0, 1, 2, 3)) {
145             if (directionFlags.checkBit(pin)) {
146                 _chip.writePin!pin(value.checkBit(pin));
147             }
148         }
149     }
150 }