flycast/core/hw/naomi/touchscreen.cpp

178 lines
4.0 KiB
C++

/*
Copyright 2023 flyinghead
This file is part of Flycast.
Flycast is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Flycast 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
#include "touchscreen.h"
#include "hw/sh4/sh4_sched.h"
#include "hw/sh4/modules/modules.h"
#include "hw/maple/maple_cfg.h"
#include "hw/maple/maple_devs.h"
#include "input/gamepad.h"
#include "serialize.h"
#include <algorithm>
#include <deque>
#include <memory>
namespace touchscreen
{
//
// 837-14672 touchscreen sensor board
// used by Manic Panic Ghosts and Touch De Zunou
//
class TouchscreenPipe final : public SerialPort::Pipe
{
public:
TouchscreenPipe()
{
schedId = sh4_sched_register(0, schedCallback, this);
SCIFSerialPort::Instance().setPipe(this);
}
~TouchscreenPipe() override
{
SCIFSerialPort::Instance().setPipe(nullptr);
sh4_sched_unregister(schedId);
}
void write(u8 data) override
{
if (data == 0x39)
{
// status request
constexpr u8 status[] = { 0xaa, 0x39, 0 };
send(status, sizeof(status));
}
if (!schedulerStarted)
{
sh4_sched_request(schedId, FRAME_CYCLES);
schedulerStarted = true;
}
}
int available() override {
return toSend.size();
}
u8 read() override
{
u8 data = toSend.front();
toSend.pop_front();
return data;
}
void serialize(Serializer& ser)
{
ser << schedulerStarted;
sh4_sched_serialize(ser, schedId);
ser << (int)toSend.size();
for (u8 b : toSend)
ser << b;
}
void deserialize(Deserializer& deser)
{
deser >> schedulerStarted;
sh4_sched_deserialize(deser, schedId);
int size;
deser >> size;
toSend.resize(size);
for (int i = 0; i < size; i++)
deser >> toSend[i];
}
private:
void send(const u8 *msg, int size)
{
if (toSend.size() >= 32)
return;
toSend.insert(toSend.end(), &msg[0], &msg[size]);
toSend.push_back(calcChecksum(msg, size));
SCIFSerialPort::Instance().updateStatus();
}
u8 calcChecksum(const u8 *data, int size)
{
u8 c = 0;
for (int i = 0; i < size; i++)
c += data[i];
return 256 - c;
}
static int schedCallback(int tag, int cycles, int jitter, void *arg)
{
TouchscreenPipe *instance = (TouchscreenPipe *)arg;
u32 pack[2];
for (size_t i = 0; i < std::size(pack); i++)
{
int x = std::clamp(mapleInputState[i].absPos.x, 0, 1023);
int y = std::clamp(mapleInputState[i].absPos.y, 0, 1023);
#ifdef LIBRETRO
int hit = (mapleInputState[i].kcode & NAOMI_BTN0_KEY) == 0;
int charge = (mapleInputState[i].kcode & NAOMI_BTN1_KEY) == 0;
#else
int hit = (mapleInputState[i].kcode & DC_BTN_A) == 0;
int charge = (mapleInputState[i].kcode & DC_BTN_B) == 0;
#endif
// touches require bits 20, 21 and 22
// drag needs bit 22 off
// bit 23 is charge
pack[i] = (charge << 23) | (hit << 21) | (hit << 20) | (y << 10) | x;
if (!instance->touch[i])
pack[i] |= hit << 22;
instance->touch[i] = hit;
}
u8 msg[] = { 0xaa, 0x10,
u8(pack[0] >> 16), u8(pack[0] >> 8), u8(pack[0]),
u8(pack[1] >> 16), u8(pack[1] >> 8), u8(pack[1]) };
instance->send(msg, sizeof(msg));
return FRAME_CYCLES;
}
std::deque<u8> toSend;
int schedId = -1;
bool schedulerStarted = false;
bool touch[2] {};
static constexpr int FRAME_CYCLES = SH4_MAIN_CLOCK / 60;
};
std::unique_ptr<TouchscreenPipe> touchscreen;
void init() {
touchscreen = std::make_unique<TouchscreenPipe>();
}
void term() {
touchscreen.reset();
}
void serialize(Serializer& ser)
{
if (touchscreen)
touchscreen->serialize(ser);
}
void deserialize(Deserializer& deser)
{
if (touchscreen)
touchscreen->deserialize(deser);
}
}