comparison io.c @ 2027:0f54a898db03

Implement Heartbeat Personal Trainer peripheral and add ROM DB entry for Outback Joey
author Michael Pavone <pavone@retrodev.com>
date Mon, 15 Feb 2021 11:24:06 -0800
parents e7a516f08cec
children e597572f45ce
comparison
equal deleted inserted replaced
2026:aa338eb0eded 2027:0f54a898db03
38 "Sega multi-tap", 38 "Sega multi-tap",
39 "EA 4-way Play cable A", 39 "EA 4-way Play cable A",
40 "EA 4-way Play cable B", 40 "EA 4-way Play cable B",
41 "Sega Parallel Transfer Board", 41 "Sega Parallel Transfer Board",
42 "Generic Device", 42 "Generic Device",
43 "Generic Serial" 43 "Generic Serial",
44 "Heartbeat Personal Trainer"
44 }; 45 };
45 46
46 #define GAMEPAD_TH0 0 47 #define GAMEPAD_TH0 0
47 #define GAMEPAD_TH1 1 48 #define GAMEPAD_TH1 1
48 #define GAMEPAD_EXTRA 2 49 #define GAMEPAD_EXTRA 2
55 enum { 56 enum {
56 IO_WRITE_PENDING, 57 IO_WRITE_PENDING,
57 IO_WRITTEN, 58 IO_WRITTEN,
58 IO_READ_PENDING, 59 IO_READ_PENDING,
59 IO_READ 60 IO_READ
61 };
62
63 enum {
64 HBPT_NEED_INIT,
65 HBPT_IDLE,
66 HBPT_CMD_PAYLOAD,
67 HBPT_REPLY
60 }; 68 };
61 69
62 typedef struct { 70 typedef struct {
63 uint8_t states[2], value; 71 uint8_t states[2], value;
64 } gp_button_def; 72 } gp_button_def;
85 { 93 {
86 io_port *port = io->ports + i; 94 io_port *port = io->ports + i;
87 if (port->device_type < IO_MOUSE && port->device.pad.gamepad_num == gamepad_num) { 95 if (port->device_type < IO_MOUSE && port->device.pad.gamepad_num == gamepad_num) {
88 return port; 96 return port;
89 } 97 }
98 if (port->device_type == IO_HEARTBEAT_TRAINER && port->device.heartbeat_trainer.device_num == gamepad_num) {
99 return port;
100 }
90 } 101 }
91 return NULL; 102 return NULL;
92 } 103 }
93 104
94 static io_port *find_mouse(sega_io *io, uint8_t mouse_num) 105 static io_port *find_mouse(sega_io *io, uint8_t mouse_num)
246 port->device_type = IO_GAMEPAD2; 257 port->device_type = IO_GAMEPAD2;
247 } else { 258 } else {
248 port->device_type = IO_GAMEPAD6; 259 port->device_type = IO_GAMEPAD6;
249 } 260 }
250 port->device.pad.gamepad_num = device_type[gamepad_len+2] - '0'; 261 port->device.pad.gamepad_num = device_type[gamepad_len+2] - '0';
262 } else if(startswith(device_type, "heartbeat_trainer.")) {
263 port->device_type = IO_HEARTBEAT_TRAINER;
264 port->device.heartbeat_trainer.nv_memory = NULL;
265 port->device.heartbeat_trainer.device_num = device_type[strlen("heartbeat_trainer.")] - '0';
251 } else if(startswith(device_type, "mouse")) { 266 } else if(startswith(device_type, "mouse")) {
252 if (port->device_type != IO_MOUSE) { 267 if (port->device_type != IO_MOUSE) {
253 port->device_type = IO_MOUSE; 268 port->device_type = IO_MOUSE;
254 port->device.mouse.mouse_num = device_type[strlen("mouse")+1] - '0'; 269 port->device.mouse.mouse_num = device_type[strlen("mouse")+1] - '0';
255 port->device.mouse.last_read_x = 0; 270 port->device.mouse.last_read_x = 0;
409 } 424 }
410 } else 425 } else
411 #endif 426 #endif
412 if (ports[i].device_type == IO_GAMEPAD3 || ports[i].device_type == IO_GAMEPAD6 || ports[i].device_type == IO_GAMEPAD2) { 427 if (ports[i].device_type == IO_GAMEPAD3 || ports[i].device_type == IO_GAMEPAD6 || ports[i].device_type == IO_GAMEPAD2) {
413 debug_message("IO port %s connected to gamepad #%d with type '%s'\n", io_name(i), ports[i].device.pad.gamepad_num, device_type_names[ports[i].device_type]); 428 debug_message("IO port %s connected to gamepad #%d with type '%s'\n", io_name(i), ports[i].device.pad.gamepad_num, device_type_names[ports[i].device_type]);
429 } else if (ports[i].device_type == IO_HEARTBEAT_TRAINER) {
430 debug_message("IO port %s connected to Heartbeat Personal Trainer #%d\n", io_name(i), ports[i].device.heartbeat_trainer.device_num);
431 if (rom->save_type == SAVE_HBPT) {
432 ports[i].device.heartbeat_trainer.nv_memory = rom->save_buffer;
433 uint32_t page_size = 16;
434 for (; page_size < 128; page_size *= 2)
435 {
436 if (rom->save_size / page_size < 256) {
437 break;
438 }
439 }
440 ports[i].device.heartbeat_trainer.nv_page_size = page_size;
441 uint32_t num_pages = rom->save_size / page_size;
442 ports[i].device.heartbeat_trainer.nv_pages = num_pages < 256 ? num_pages : 255;
443 } else {
444 ports[i].device.heartbeat_trainer.nv_page_size = 16;
445 ports[i].device.heartbeat_trainer.nv_pages = 32;
446 size_t bufsize =
447 ports[i].device.heartbeat_trainer.nv_page_size * ports[i].device.heartbeat_trainer.nv_pages
448 + 5 + 8;
449 ports[i].device.heartbeat_trainer.nv_memory = malloc(bufsize);
450 memset(ports[i].device.heartbeat_trainer.nv_memory, 0xFF, bufsize);
451 }
452 ports[i].device.heartbeat_trainer.state = HBPT_NEED_INIT;
414 } else { 453 } else {
415 debug_message("IO port %s connected to device '%s'\n", io_name(i), device_type_names[ports[i].device_type]); 454 debug_message("IO port %s connected to device '%s'\n", io_name(i), device_type_names[ports[i].device_type]);
416 } 455 }
417 } 456 }
418 } 457 }
639 } 678 }
640 } 679 }
641 } 680 }
642 #endif 681 #endif
643 682
683 enum {
684 HBPT_UNKNOWN1 = 1,
685 HBPT_POLL,
686 HBPT_READ_PAGE = 5,
687 HBPT_WRITE_PAGE,
688 HBPT_READ_RTC,
689 HBPT_SET_RTC,
690 HBPT_GET_STATUS,
691 HBPT_ERASE_NVMEM,
692 HBPT_NVMEM_PARAMS,
693 HBPT_INIT
694 };
695
696 static void start_reply(io_port *port, uint8_t bytes, const uint8_t *src)
697 {
698 port->device.heartbeat_trainer.remaining_bytes = bytes;
699 port->device.heartbeat_trainer.state = HBPT_REPLY;
700 port->device.heartbeat_trainer.cur_buffer = (uint8_t *)src;
701 }
702
703 static void simple_reply(io_port *port, uint8_t value)
704 {
705 port->device.heartbeat_trainer.param = value;
706 start_reply(port, 1, &port->device.heartbeat_trainer.param);
707 }
708
709 static void expect_payload(io_port *port, uint8_t bytes, uint8_t *dst)
710 {
711 port->device.heartbeat_trainer.remaining_bytes = bytes;
712 port->device.heartbeat_trainer.state = HBPT_CMD_PAYLOAD;
713 port->device.heartbeat_trainer.cur_buffer = dst;
714 }
715
716 void hbpt_check_init(io_port *port)
717 {
718 if (port->device.heartbeat_trainer.state == HBPT_NEED_INIT) {
719 port->device.heartbeat_trainer.rtc_base_timestamp = 0;
720 for (int i = 0; i < 8; i ++)
721 {
722 port->device.heartbeat_trainer.rtc_base_timestamp <<= 8;
723 port->device.heartbeat_trainer.rtc_base_timestamp |= port->device.heartbeat_trainer.nv_memory[i];
724 }
725 memcpy(port->device.heartbeat_trainer.rtc_base, port->device.heartbeat_trainer.nv_memory + 8, 5);
726 if (port->device.heartbeat_trainer.rtc_base_timestamp == UINT64_MAX) {
727 //uninitialized save, set the appropriate status bit
728 port->device.heartbeat_trainer.status |= 1;
729 }
730 port->device.heartbeat_trainer.bpm = 60;
731 port->device.heartbeat_trainer.state = HBPT_IDLE;
732 }
733 }
734
735 void hbpt_check_send_reply(io_port *port)
736 {
737 if (port->device.heartbeat_trainer.state == HBPT_REPLY && !port->receive_end) {
738 port->serial_receiving = *(port->device.heartbeat_trainer.cur_buffer++);
739 port->receive_end = port->serial_cycle + 10 * port->serial_divider;
740 if (!--port->device.heartbeat_trainer.remaining_bytes) {
741 port->device.heartbeat_trainer.state = HBPT_IDLE;
742 }
743 }
744 }
745
746 uint8_t is_leap_year(uint16_t year)
747 {
748 if (year & 3) {
749 return 0;
750 }
751 if (year % 100) {
752 return 1;
753 }
754 if (year % 400) {
755 return 0;
756 }
757 return 1;
758 }
759
760 uint8_t days_in_month(uint8_t month, uint16_t year)
761 {
762 static uint8_t days_per_month[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
763 if (month == 2 && is_leap_year(year)) {
764 return 29;
765 }
766 if (month > 12 || !month) {
767 return 30;
768 }
769 return days_per_month[month-1];
770 }
771
772 void hbpt_write_byte(io_port *port)
773 {
774 hbpt_check_init(port);
775 uint8_t reply;
776 switch (port->device.heartbeat_trainer.state)
777 {
778 case HBPT_IDLE:
779 port->device.heartbeat_trainer.cmd = port->serial_transmitting;
780 switch (port->device.heartbeat_trainer.cmd)
781 {
782 case HBPT_UNKNOWN1:
783 start_reply(port, 11, NULL);
784 break;
785 case HBPT_POLL:
786 start_reply(port, 3, &port->device.heartbeat_trainer.bpm);
787 if (port->serial_cycle - port->last_poll_cycle > MIN_POLL_INTERVAL) {
788 process_events();
789 port->last_poll_cycle = port->serial_cycle;
790 }
791 port->device.heartbeat_trainer.buttons = (port->input[GAMEPAD_TH0] << 2 & 0xC0) | (port->input[GAMEPAD_TH1] & 0x1F);
792 if (port->device.heartbeat_trainer.cadence && port->input[GAMEPAD_TH1] & 0x20) {
793 port->device.heartbeat_trainer.cadence--;
794 printf("Cadence: %d\n", port->device.heartbeat_trainer.cadence);
795 } else if (port->device.heartbeat_trainer.cadence < 255 && port->input[GAMEPAD_EXTRA] & 1) {
796 port->device.heartbeat_trainer.cadence++;
797 printf("Cadence: %d\n", port->device.heartbeat_trainer.cadence);
798 }
799 if (port->device.heartbeat_trainer.bpm && port->input[GAMEPAD_EXTRA] & 4) {
800 port->device.heartbeat_trainer.bpm--;
801 printf("Heart Rate: %d\n", port->device.heartbeat_trainer.bpm);
802 } else if (port->device.heartbeat_trainer.bpm < 255 && port->input[GAMEPAD_EXTRA] & 2) {
803 port->device.heartbeat_trainer.bpm++;
804 printf("Heart Rate: %d\n", port->device.heartbeat_trainer.bpm);
805 }
806
807 break;
808 case HBPT_READ_PAGE:
809 case HBPT_WRITE_PAGE:
810 //strictly speaking for the write case, we want 1 + page size here
811 //but the rest of the payload goes to a different destination
812 expect_payload(port, 1, &port->device.heartbeat_trainer.param);
813 break;
814 case HBPT_READ_RTC: {
815 uint8_t *rtc = port->device.heartbeat_trainer.rtc_base;
816 start_reply(port, 5, rtc);
817 uint64_t now = time(NULL);
818 uint64_t delta = (now - port->device.heartbeat_trainer.rtc_base_timestamp + 30) / 60;
819 rtc[4] += delta % 60;
820 if (rtc[4] > 59) {
821 rtc[4] -= 60;
822 rtc[3]++;
823 }
824 delta /= 60;
825 if (delta) {
826 rtc[3] += delta % 24;
827 delta /= 24;
828 if (rtc[3] > 23) {
829 rtc[3] -= 24;
830 delta++;
831 }
832 if (delta) {
833 uint16_t year = rtc[0] < 81 ? 2000 + rtc[0] : 1900 + rtc[0];
834 uint8_t days_cur_month = days_in_month(rtc[1], year);
835 while (delta + rtc[2] > days_cur_month) {
836 delta -= days_cur_month + 1 - rtc[2];
837 rtc[2] = 1;
838 if (++rtc[1] == 13) {
839 rtc[1] = 1;
840 year++;
841 }
842 days_cur_month = days_in_month(rtc[1], year);
843 }
844 rtc[1] += delta;
845 rtc[0] = year % 100;
846 }
847 }
848 printf("RTC %02d-%02d-%02d %02d:%02d\n", rtc[0], rtc[1], rtc[2], rtc[3], rtc[4]);
849 port->device.heartbeat_trainer.rtc_base_timestamp = now;
850 break;
851 }
852 case HBPT_SET_RTC:
853 port->device.heartbeat_trainer.rtc_base_timestamp = time(NULL);
854 expect_payload(port, 5, port->device.heartbeat_trainer.rtc_base);
855 break;
856 case HBPT_GET_STATUS:
857 simple_reply(port, port->device.heartbeat_trainer.status);
858 break;
859 case HBPT_ERASE_NVMEM:
860 expect_payload(port, 1, &port->device.heartbeat_trainer.param);
861 break;
862 case HBPT_NVMEM_PARAMS:
863 start_reply(port, 2, &port->device.heartbeat_trainer.nv_page_size);
864 break;
865 case HBPT_INIT:
866 expect_payload(port, 19, NULL);
867 break;
868 default:
869 // it's unclear what these commands do as they are unused by Outback Joey
870 // just return 0 to indicate failure
871 simple_reply(port, 0);
872 }
873 break;
874 case HBPT_CMD_PAYLOAD:
875 if (port->device.heartbeat_trainer.cur_buffer) {
876 *(port->device.heartbeat_trainer.cur_buffer++) = port->serial_transmitting;
877 }
878 if (!--port->device.heartbeat_trainer.remaining_bytes) {
879 switch (port->device.heartbeat_trainer.cmd)
880 {
881 case HBPT_READ_PAGE:
882 case HBPT_WRITE_PAGE:
883 if (
884 port->device.heartbeat_trainer.cmd == HBPT_WRITE_PAGE
885 && port->device.heartbeat_trainer.cur_buffer != &port->device.heartbeat_trainer.param + 1) {
886 simple_reply(port, 1);
887 break;
888 }
889 port->device.heartbeat_trainer.remaining_bytes = port->device.heartbeat_trainer.nv_page_size;
890 port->device.heartbeat_trainer.cur_buffer =
891 port->device.heartbeat_trainer.param < port->device.heartbeat_trainer.nv_pages
892 ? port->device.heartbeat_trainer.nv_memory + 5 + 8
893 + port->device.heartbeat_trainer.param * port->device.heartbeat_trainer.nv_page_size
894 : NULL;
895 if (port->device.heartbeat_trainer.cmd == HBPT_WRITE_PAGE) {
896 return;
897 }
898 port->device.heartbeat_trainer.state = HBPT_REPLY;
899 break;
900 case HBPT_SET_RTC:
901 //save RTC base values back to nv memory area so it's saved to disk on exit
902 for (int i = 0; i < 8; i++)
903 {
904 port->device.heartbeat_trainer.nv_memory[i] = port->device.heartbeat_trainer.rtc_base_timestamp >> (56 - i*8);
905 }
906 memcpy(port->device.heartbeat_trainer.nv_memory + 8, port->device.heartbeat_trainer.rtc_base, 5);
907 simple_reply(port, 1);
908 break;
909 case HBPT_ERASE_NVMEM:
910 memset(
911 port->device.heartbeat_trainer.nv_memory + 5 + 8,
912 port->device.heartbeat_trainer.param,
913 port->device.heartbeat_trainer.nv_pages * port->device.heartbeat_trainer.nv_page_size
914 );
915 simple_reply(port, 1);
916 break;
917 case HBPT_INIT: {
918 static const char reply[] = "(C) HEARTBEAT CORP";
919 start_reply(port, strlen(reply), reply);
920 break;
921 }
922 }
923 }
924 }
925 hbpt_check_send_reply(port);
926 }
927
928 void hbpt_read_byte(io_port *port)
929 {
930 hbpt_check_init(port);
931 hbpt_check_send_reply(port);
932 }
933
644 const int mouse_delays[] = {112*7, 120*7, 96*7, 132*7, 104*7, 96*7, 112*7, 96*7}; 934 const int mouse_delays[] = {112*7, 120*7, 96*7, 132*7, 104*7, 96*7, 112*7, 96*7};
645 935
646 enum { 936 enum {
647 KB_SETUP, 937 KB_SETUP,
648 KB_READ, 938 KB_READ,
665 port->transmit_end = 0; 955 port->transmit_end = 0;
666 956
667 if (port->serial_ctrl & SCTRL_BIT_TX_ENABLE) { 957 if (port->serial_ctrl & SCTRL_BIT_TX_ENABLE) {
668 switch (port->device_type) 958 switch (port->device_type)
669 { 959 {
960 case IO_HEARTBEAT_TRAINER:
961 hbpt_write_byte(port);
962 break;
670 #ifndef _WIN32 963 #ifndef _WIN32
671 case IO_GENERIC_SERIAL: 964 case IO_GENERIC_SERIAL:
672 write_serial_byte(port); 965 write_serial_byte(port);
673 break; 966 break;
674 #endif 967 #endif
691 port->receive_end = 0; 984 port->receive_end = 0;
692 } 985 }
693 if (!port->receive_end) { 986 if (!port->receive_end) {
694 switch(port->device_type) 987 switch(port->device_type)
695 { 988 {
989 case IO_HEARTBEAT_TRAINER:
990 hbpt_read_byte(port);
991 break;
696 #ifndef _WIN32 992 #ifndef _WIN32
697 case IO_GENERIC_SERIAL: 993 case IO_GENERIC_SERIAL:
698 read_serial_byte(port); 994 read_serial_byte(port);
699 break; 995 break;
700 #endif 996 #endif