-
Notifications
You must be signed in to change notification settings - Fork 59
Add support for a peripheral attached to the user port #355
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
7e746fb
de038af
e88430a
0f5a281
a2a07ff
306fbb6
5a5bd77
1a53e35
cd6b1a9
d0c6a02
81134a6
93e5de7
050b3e6
7f5efe3
2eb9604
a073871
6056aac
f015c22
e6f857d
41e42cd
efe08b5
f93da6b
7e8be97
f492c4a
ebbabaa
f5ffd64
78c2a92
0e0b09c
7bb9768
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| #pragma once | ||
|
|
||
| #ifdef _WIN32 | ||
| #include <windows.h> | ||
| #define LIBRARY_TYPE HMODULE | ||
| #define LOAD_LIBRARY(name) LoadLibrary(name) | ||
| #define GET_FUNCTION(lib, name) (void *)GetProcAddress(lib, name) | ||
| #define CLOSE_LIBRARY(lib) FreeLibrary(lib) | ||
| #define LIBRARY_ERROR() "[ unknown error ]" | ||
| #else | ||
| #include <dlfcn.h> | ||
| #define LIBRARY_TYPE void* | ||
| #define LOAD_LIBRARY(name) dlopen(name, RTLD_LAZY) | ||
| #define GET_FUNCTION(lib, name) dlsym(lib, name) | ||
| #define CLOSE_LIBRARY(lib) dlclose(lib) | ||
| #define LIBRARY_ERROR() dlerror() | ||
| #endif | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,121 @@ | ||
| #pragma once | ||
|
|
||
| // Include this header in a user peripheral library. | ||
| // | ||
| // A user peripheral library must export a [user_port_init_t x16_user_port_init] to be | ||
| // called at runtime by the emulator during initialization of via2. [x16_user_port_init] | ||
| // should return 0 on success, and -1 on error. | ||
|
|
||
| #include <stdint.h> | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not needed in here.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it is? user_pins_t is uint32_t. |
||
|
|
||
| // This must be assigned to the [api_version] field of [user_port_t] by the peripheral | ||
| // library so that version mismatches can be detected. | ||
| #define X16_USER_PORT_API_VERSION 1 | ||
|
|
||
| // Bit assignments for each 65C22 pin exposed to the user port, for use with | ||
| // [user_pin_t]. For convenience, all Port A pins are in the low byte, in bit order, and | ||
| // Port B pins are likewise in the second byte. | ||
|
|
||
| #define PA0_PIN (1 << 0) | ||
| #define PA1_PIN (1 << 1) | ||
| #define PA2_PIN (1 << 2) | ||
| #define PA3_PIN (1 << 3) | ||
| #define PA4_PIN (1 << 4) | ||
| #define PA5_PIN (1 << 5) | ||
| #define PA6_PIN (1 << 6) | ||
| #define PA7_PIN (1 << 7) | ||
| #define PB0_PIN (1 << 8) | ||
| #define PB1_PIN (1 << 9) | ||
| #define PB2_PIN (1 << 10) | ||
| #define PB3_PIN (1 << 11) | ||
| #define PB4_PIN (1 << 12) | ||
| #define PB5_PIN (1 << 13) | ||
| #define PB6_PIN (1 << 14) | ||
| #define PB7_PIN (1 << 15) | ||
| #define CA1_PIN (1 << 16) | ||
| #define CA2_PIN PB3_PIN | ||
| #define CB1_PIN PB6_PIN | ||
| #define CB2_PIN PB7_PIN | ||
|
|
||
| // USER_PINn macros map the 65C22 pins (above) to the exposed pins on the X16's user port. | ||
|
|
||
| // Left column | ||
| #define USER_PIN1 PB0_PIN | ||
| #define USER_PIN3 PA0_PIN | ||
| #define USER_PIN5 PA1_PIN | ||
| #define USER_PIN7 PA2_PIN | ||
| #define USER_PIN9 PA3_PIN | ||
| #define USER_PIN11 PA4_PIN | ||
| #define USER_PIN13 PA5_PIN | ||
| #define USER_PIN15 PA6_PIN | ||
| #define USER_PIN17 PA7_PIN | ||
| #define USER_PIN19 CA1_PIN | ||
| #define USER_PIN21 PB1_PIN | ||
| #define USER_PIN23 PB2_PIN | ||
| #define USER_PIN25 PB3_PIN | ||
|
|
||
| // Right Column | ||
| #define USER_PIN2 PB4_PIN | ||
| #define USER_PIN4 PB5_PIN | ||
| #define USER_PIN6 PB6_PIN | ||
| #define USER_PIN8 PB7_PIN | ||
| // 10-24 (even) are GND, 26 is VCC | ||
|
|
||
| // [user_pin_t] is a bitmask of pin values. Port A is the first byte, Port B is the second | ||
| // byte, and CA1 is bit 16. Bits above 16 must always be 0. The above *_PIN macros define | ||
| // the appropriate bits to use with [user_pin_t]. | ||
| // | ||
| // Note that in this implementation, all pins are either high (1) or low (0). If your | ||
| // device uses pull-up/down, you'll need to factor that into [read] or [step] pin values | ||
| // manually. However, unless the data direction of the pins on the via stays constant it | ||
| // will be quite difficult to keep the via's and peripheral's states in sync, since there | ||
| // is no signal that a via pin has switched from being actively driven to hi-Z, or vice | ||
| // versa. | ||
|
Comment on lines
+70
to
+73
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this relevant to implementers?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's easy enough to overlook that I think it's worth calling out, especially since the emulator does actually do I2C, but it's a special case. That said, I think I could add pull-up/pull-down masks to the user_port initialization, and treat output->input changes in the ddrs as a 'write' of the pulled value, and input->output as a write of the register value. |
||
| typedef uint32_t user_pin_t; | ||
|
|
||
| typedef struct user_port_t { | ||
| // Must be set to X16_USER_PORT_API_VERSION | ||
| int api_version; | ||
|
|
||
| // A mask of pins actually connected to the user peripheral. | ||
| user_pin_t connected_pins; | ||
|
|
||
| // Arbitrary user data which is passed to the various callbacks. | ||
| void *userdata; | ||
|
|
||
| // Return the values of the connected pins based on the peripheral's internal | ||
| // state. Any pin values not in [connected_pins] will be ignored. | ||
| user_pin_t (*read)(void *userdata); | ||
|
|
||
| // New pin values pushed from the via to the peripheral. Pins not in the | ||
| // [connected_pins] mask will be zeroes, but that does not imply those pins are low | ||
| void (*write)(user_pin_t pins, void *userdata); | ||
|
|
||
| // Step the state machine of the connected peripheral. [nanos] is the number of | ||
| // nanoseconds which has passed since the last step. Returns the pin state so that any | ||
| // interrupts based on CA1 and CB1 can be triggered. CA2 and CB2 are not presently | ||
| // implemented in the via code. | ||
| user_pin_t (*step)(double nanos, void *userdata); | ||
|
|
||
| // Called on reset and shutdown. is_poweroff is true for final shutdown, to | ||
| // differentiate from a reset | ||
| void (*cleanup)(bool is_poweroff, void *userdata); | ||
| } user_port_t; | ||
|
|
||
| // Extensible init args struct. Includes api_version so perhipheral libraries can abort or | ||
| // downgrade, if necessary. | ||
| typedef struct __attribute__((aligned(sizeof(void *)))) user_port_init_args_t { | ||
| int api_version; | ||
|
|
||
| // Callback for setting an error message from the plugin | ||
| // Errors longer than 256 chars (including null) will be truncated | ||
| void (*set_error)(char const *); | ||
| } user_port_init_args_t; | ||
|
|
||
| // Populates the provided [user_port_t *]. If any of [read], [write] or [step] is NULL, it | ||
| // will be ignored. | ||
|
Comment on lines
+115
to
+116
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This check isn't implemented.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is actually, via2_read, via2_write and via2_step check that user_port.whatever != NULL before invoking it. I figure it could be valid for a device not to need some subset of the functions. |
||
| // | ||
| // A peripheral library's exposed [user_port_init_t] function MUST be named "x16_user_port_init". | ||
| // | ||
| // Returns 0 on success and -1 on error. | ||
| typedef int (*user_port_init_t)(user_port_init_args_t *, user_port_t *); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GetLastError+FormatMessagewould be the way to get the error message; if you don't want to do theFormatMessagewrapping in this PR, I'd go with just printingGetLastError()and switching the format specifier between%uand%sdepending on the platform.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can add that but I can't test it. I just pulled these macros out of midi.c, there just wasn't one for errors.