SDL: Initial PS2_Joystick implementation

From 7fd46ec581d45ec3fea3fcfe226bbc2ae824fc7c Mon Sep 17 00:00:00 2001
From: Francisco Javier Trujillo Mata <[EMAIL REDACTED]>
Date: Sat, 18 Jun 2022 17:40:53 +0200
Subject: [PATCH] Initial PS2_Joystick implementation

---
 CMakeLists.txt                      |   8 +-
 include/SDL_config.h.cmake          |   1 +
 src/joystick/SDL_gamecontrollerdb.h |   3 +
 src/joystick/SDL_joystick.c         |   3 +
 src/joystick/SDL_sysjoystick.h      |   3 +-
 src/joystick/ps2/SDL_sysjoystick.c  | 348 ++++++++++++++++++++++++++++
 6 files changed, 364 insertions(+), 2 deletions(-)
 create mode 100644 src/joystick/ps2/SDL_sysjoystick.c

diff --git a/CMakeLists.txt b/CMakeLists.txt
index b82f735506c..1f42cf5f7c5 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2539,7 +2539,7 @@ elseif(PSP)
   endif(NOT SDL2_DISABLE_SDL2MAIN)
 
 elseif(PS2)
-  list(APPEND EXTRA_CFLAGS "-DPS2" "-D__PS2__" "-I${PS2SDK}/ports/include")
+  list(APPEND EXTRA_CFLAGS "-DPS2" "-D__PS2__" "-I$ENV{PS2SDK}/ports/include")
 
   file(GLOB PS2_MAIN_SOURCES ${SDL2_SOURCE_DIR}/src/main/ps2/*.c)
   set(SDLMAIN_SOURCES ${SDLMAIN_SOURCES} ${PS2_MAIN_SOURCES})
@@ -2550,6 +2550,12 @@ elseif(PS2)
     list(APPEND SOURCE_FILES ${PS2_FILESYSTEM_SOURCES})
     set(HAVE_SDL_FILESYSTEM TRUE)
   endif()
+  if(SDL_JOYSTICK)
+    set(SDL_JOYSTICK_PS2 1)
+    file(GLOB PS2_JOYSTICK_SOURCES ${SDL2_SOURCE_DIR}/src/joystick/ps2/*.c)
+    list(APPEND SOURCE_FILES ${PS2_JOYSTICK_SOURCES})
+    set(HAVE_SDL_JOYSTICK TRUE)
+  endif()
   if(SDL_THREADS)
     set(SDL_THREAD_PS2 1)
     file(GLOB PS2_THREAD_SOURCES ${SDL2_SOURCE_DIR}/src/thread/generic/SDL_systls.c ${SDL2_SOURCE_DIR}/src/thread/generic/SDL_sysmutex.c ${SDL2_SOURCE_DIR}/src/thread/ps2/*.c)
diff --git a/include/SDL_config.h.cmake b/include/SDL_config.h.cmake
index 9cc18ab95c1..129e808d2ab 100644
--- a/include/SDL_config.h.cmake
+++ b/include/SDL_config.h.cmake
@@ -348,6 +348,7 @@
 #cmakedefine SDL_JOYSTICK_VIRTUAL @SDL_JOYSTICK_VIRTUAL@
 #cmakedefine SDL_JOYSTICK_VITA @SDL_JOYSTICK_VITA@
 #cmakedefine SDL_JOYSTICK_PSP @SDL_JOYSTICK_PSP@
+#cmakedefine SDL_JOYSTICK_PS2 @SDL_JOYSTICK_PS2@
 #cmakedefine SDL_HAPTIC_DUMMY @SDL_HAPTIC_DUMMY@
 #cmakedefine SDL_HAPTIC_LINUX @SDL_HAPTIC_LINUX@
 #cmakedefine SDL_HAPTIC_IOKIT @SDL_HAPTIC_IOKIT@
diff --git a/src/joystick/SDL_gamecontrollerdb.h b/src/joystick/SDL_gamecontrollerdb.h
index 9266c5165fc..aa9d35780de 100644
--- a/src/joystick/SDL_gamecontrollerdb.h
+++ b/src/joystick/SDL_gamecontrollerdb.h
@@ -913,6 +913,9 @@ static const char *s_ControllerMappings [] =
 #endif
 #if defined(SDL_JOYSTICK_PSP)
     "505350206275696c74696e206a6f7970,PSP builtin joypad,a:b2,b:b1,back:b10,dpdown:b6,dpleft:b7,dpright:b9,dpup:b8,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,rightx:a2,righty:a3,start:b11,x:b3,y:b0,",
+#endif
+#if defined(SDL_JOYSTICK_PS2)
+    "50533220436f6e74726f6c6c65720000,PS2 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,leftshoulder:b10,leftx:a0,lefty:a1,rightshoulder:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,leftstick:b1,rightstick:b2,",
 #endif
     "hidapi,*,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,",
     NULL
diff --git a/src/joystick/SDL_joystick.c b/src/joystick/SDL_joystick.c
index 32f03fffb76..bfb20464239 100644
--- a/src/joystick/SDL_joystick.c
+++ b/src/joystick/SDL_joystick.c
@@ -89,6 +89,9 @@ static SDL_JoystickDriver *SDL_joystick_drivers[] = {
 #ifdef SDL_JOYSTICK_OS2
     &SDL_OS2_JoystickDriver,
 #endif
+#ifdef SDL_JOYSTICK_PS2
+    &SDL_PS2_JoystickDriver,
+#endif
 #ifdef SDL_JOYSTICK_PSP
     &SDL_PSP_JoystickDriver,
 #endif
diff --git a/src/joystick/SDL_sysjoystick.h b/src/joystick/SDL_sysjoystick.h
index 6fd82bdce14..4215f4a514a 100644
--- a/src/joystick/SDL_sysjoystick.h
+++ b/src/joystick/SDL_sysjoystick.h
@@ -155,7 +155,7 @@ typedef struct _SDL_JoystickDriver
     /* Function to get the player index of a joystick */
     int (*GetDevicePlayerIndex)(int device_index);
 
-    /* Function to get the player index of a joystick */
+    /* Function to set the player index of a joystick */
     void (*SetDevicePlayerIndex)(int device_index, int player_index);
 
     /* Function to return the stable GUID for a plugged in device */
@@ -226,6 +226,7 @@ extern SDL_JoystickDriver SDL_WGI_JoystickDriver;
 extern SDL_JoystickDriver SDL_WINDOWS_JoystickDriver;
 extern SDL_JoystickDriver SDL_WINMM_JoystickDriver;
 extern SDL_JoystickDriver SDL_OS2_JoystickDriver;
+extern SDL_JoystickDriver SDL_PS2_JoystickDriver;
 extern SDL_JoystickDriver SDL_PSP_JoystickDriver;
 extern SDL_JoystickDriver SDL_VITA_JoystickDriver;
 
diff --git a/src/joystick/ps2/SDL_sysjoystick.c b/src/joystick/ps2/SDL_sysjoystick.c
new file mode 100644
index 00000000000..d30b3c1b1bb
--- /dev/null
+++ b/src/joystick/ps2/SDL_sysjoystick.c
@@ -0,0 +1,348 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+#include "../../SDL_internal.h"
+
+#if SDL_JOYSTICK_PS2
+
+/* This is the PS2 implementation of the SDL joystick API */
+#include <libmtap.h>
+#include <libpad.h>
+#include <ps2_joystick_driver.h>
+
+#include <stdio.h>      /* For the definition of NULL */
+#include <stdlib.h>
+#include <stdbool.h>
+
+#include "../SDL_sysjoystick.h"
+#include "../SDL_joystick_c.h"
+
+#include "SDL_events.h"
+#include "SDL_error.h"
+
+#define PS2_MAX_PORT 2 /* each ps2 has 2 ports */
+#define PS2_MAX_SLOT 4 /* maximum - 4 slots in one multitap */
+#define MAX_CONTROLLERS (PS2_MAX_PORT * PS2_MAX_SLOT)
+#define PS2_ANALOG_STICKS 2
+#define PS2_ANALOG_AXIS 2
+#define PS2_BUTTONS 16
+#define PS2_TOTAL_AXIS (PS2_ANALOG_STICKS * PS2_ANALOG_AXIS)
+
+struct JoyInfo {
+    uint8_t padBuf[256];
+    uint16_t btns;
+    uint8_t analog_state[PS2_TOTAL_AXIS];
+    uint8_t port;
+    uint8_t slot;
+    int8_t rumble_ready;
+} __attribute__ ((aligned (64)));
+
+static uint8_t enabled_pads = 0;
+static struct JoyInfo joyInfo[MAX_CONTROLLERS];
+
+static inline int16_t convert_u8_to_s16(uint8_t val)
+{
+   if (val == 0)
+      return -0x7fff;
+   return val * 0x0101 - 0x8000;
+}
+
+static inline uint8_t rumble_status(uint8_t index) 
+{
+    char actAlign[6];
+    int res;
+    struct JoyInfo *info = &joyInfo[index];
+
+    if (info->rumble_ready == 0) {
+        actAlign[0] = 0;
+        actAlign[1] = 1;
+        actAlign[2] = 0xff;
+        actAlign[3] = 0xff;
+        actAlign[4] = 0xff;
+        actAlign[5] = 0xff;
+        
+        res = padSetActAlign(info->port, info->slot, actAlign);
+        info->rumble_ready = res <= 0 ? -1 : 1;
+    }
+
+    return info->rumble_ready == 1;
+}
+
+/* Function to scan the system for joysticks.
+*  Joystick 0 should be the system default joystick.
+*  This function should return 0, or -1 on an unrecoverable error.
+*/
+static int PS2_JoystickInit(void)
+{
+    uint32_t port = 0;
+    uint32_t slot = 0;
+
+    if(init_joystick_driver(true) < 0)
+        return -1;
+
+   for (port = 0; port < PS2_MAX_PORT; port++)
+        mtapPortOpen(port);
+    /* it can fail - we dont care, we will check it more strictly when padPortOpen */
+
+    for (slot = 0; slot < PS2_MAX_SLOT; slot++) {
+        for (port = 0; port < PS2_MAX_PORT; port++) {
+            /* 2 main controller ports acts the same with and without multitap
+            Port 0,0 -> Connector 1 - the same as Port 0
+            Port 1,0 -> Connector 2 - the same as Port 1
+            Port 0,1 -> Connector 3
+            Port 1,1 -> Connector 4
+            Port 0,2 -> Connector 5
+            Port 1,2 -> Connector 6
+            Port 0,3 -> Connector 7
+            Port 1,3 -> Connector 8
+            */
+
+            struct JoyInfo *info = &joyInfo[enabled_pads];
+            if(padPortOpen(port, slot, (void *)info->padBuf) > 0) {
+                info->port = (uint8_t)port;
+                info->slot = (uint8_t)slot;
+                enabled_pads++;
+            }
+        }
+    }
+
+    return enabled_pads > 0 ? 0 : -1;
+}
+
+/* Function to return the number of joystick devices plugged in right now */
+static int PS2_JoystickGetCount()
+{
+    return (int)enabled_pads;
+}
+
+/* Function to cause any queued joystick insertions to be processed */
+static void PS2_JoystickDetect()
+{
+}
+
+/* Function to get the device-dependent name of a joystick */
+static const char *PS2_JoystickGetDeviceName(int index)
+{
+    if (index >= 0 && index < enabled_pads)
+        return "PS2 Controller";
+
+    SDL_SetError("No joystick available with that index");
+    return NULL;
+}
+
+/* Function to get the device-dependent path of a joystick */
+static const char *PS2_JoystickGetDevicePath(int index)
+{
+    return NULL;
+}
+
+/* Function to get the player index of a joystick */
+static int PS2_JoystickGetDevicePlayerIndex(int device_index)
+{
+    return -1;
+}
+
+/* Function to set the player index of a joystick */
+static void PS2_JoystickSetDevicePlayerIndex(int device_index, int player_index)
+{
+}
+
+/* Function to return the stable GUID for a plugged in device */
+static SDL_JoystickGUID PS2_JoystickGetDeviceGUID( int device_index )
+{
+    SDL_JoystickGUID guid;
+    /* the GUID is just the first 16 chars of the name for now */
+    const char *name = PS2_JoystickGetDeviceName(device_index);
+    SDL_zero(guid);
+    SDL_memcpy(&guid, name, SDL_min(sizeof(guid), SDL_strlen(name)));
+    return guid;
+}
+
+/* Function to get the current instance id of the joystick located at device_index */
+static SDL_JoystickID PS2_JoystickGetDeviceInstanceID(int device_index)
+{
+    return device_index;
+}
+
+/*  Function to open a joystick for use.
+    The joystick to open is specified by the device index.
+    This should fill the nbuttons and naxes fields of the joystick structure.
+    It returns 0, or -1 if there is an error.
+*/
+static int PS2_JoystickOpen(SDL_Joystick *joystick, int device_index)
+{
+    joystick->nbuttons = PS2_BUTTONS;
+    joystick->naxes = PS2_TOTAL_AXIS;
+    joystick->nhats = 0;
+    joystick->instance_id = device_index;
+    
+    return 0;
+}
+
+/* Rumble functionality */
+static int PS2_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
+{
+    char actAlign[6];
+    int res;
+    int index = joystick->instance_id;
+    struct JoyInfo *info = &joyInfo[index];
+
+    if (!rumble_status(index)) {
+        return -1;
+    }
+
+    // Initial value
+    actAlign[0] = low_frequency_rumble >> 8;   // Enable small engine
+    actAlign[1] = high_frequency_rumble >> 8;   // Enable big engine
+    actAlign[2] = 0xff;
+    actAlign[3] = 0xff;
+    actAlign[4] = 0xff;
+    actAlign[5] = 0xff;
+    
+    res = padSetActDirect(info->port, info->slot, actAlign);
+    return res == 1 ? 0 : -1;
+}
+
+/* Rumble functionality */
+static int PS2_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left, Uint16 right)
+{
+    return -1;
+}
+
+/* Capability detection */
+static Uint32 PS2_JoystickGetCapabilities(SDL_Joystick *joystick)
+{
+    return  SDL_JOYCAP_RUMBLE;
+}
+
+/* LED functionality */
+static int PS2_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
+{
+    return -1;
+}
+
+/* General effects */
+static int PS2_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
+{
+    return -1;
+}
+
+/* Sensor functionality */
+static int PS2_JoystickSetSensorsEnabled(SDL_Joystick *joystick, SDL_bool enabled)
+{
+    return -1;
+}
+
+/*  Function to update the state of a joystick - called as a device poll.
+*   This function shouldn't update the joystick structure directly,
+*   but instead should call SDL_PrivateJoystick*() to deliver events
+*   and update joystick device state.
+*/
+static void PS2_JoystickUpdate(SDL_Joystick *joystick)
+{
+    uint8_t i;
+    uint8_t previous_axis, current_axis;
+    uint16_t mask, previous, current;
+    struct padButtonStatus buttons;
+    uint8_t all_axis[PS2_TOTAL_AXIS];
+    int index = joystick->instance_id;
+    struct JoyInfo *info = &joyInfo[index];
+    int state = padGetState(info->port, info->slot);
+
+    if (state != PAD_STATE_DISCONN || state != PAD_STATE_EXECCMD || state != PAD_STATE_ERROR) {
+        int ret = padRead(info->port, info->slot, &buttons); /* port, slot, buttons */
+        if (ret != 0) {
+            /* Buttons */
+            int32_t pressed_buttons = 0xffff ^ buttons.btns;;
+            if (info->btns != pressed_buttons) {
+                for (i = 0; i < PS2_BUTTONS; i++) {
+                    mask = (1 << i);
+                    previous = info->btns & mask;
+                    current = pressed_buttons & mask;
+                    if (previous != current)
+                        SDL_PrivateJoystickButton(joystick, i, current ? SDL_PRESSED : SDL_RELEASED);
+                }
+            }
+            info->btns = pressed_buttons;
+
+            /* Analog */
+            all_axis[0] = buttons.ljoy_h;
+            all_axis[1] = buttons.ljoy_v;
+            all_axis[2] = buttons.rjoy_h;
+            all_axis[3] = buttons.rjoy_v;
+
+            for (i = 0; i < PS2_TOTAL_AXIS; i++) {
+                previous_axis = info->analog_state[i];
+                current_axis = all_axis[i];
+                if (previous_axis != current_axis)
+                    SDL_PrivateJoystickAxis(joystick, i, convert_u8_to_s16(current_axis));
+                
+                info->analog_state[i] = current_axis;
+            }
+        }
+    }
+}
+
+/* Function to close a joystick after use */
+static void PS2_JoystickClose(SDL_Joystick *joystick)
+{
+    int index = joystick->instance_id;
+    struct JoyInfo *info = &joyInfo[index];
+    padPortClose(info->port, info->slot);
+}
+
+/* Function to perform any system-specific joystick related cleanup */
+static void PS2_JoystickQuit(void)
+{
+    deinit_joystick_driver(true);
+}
+
+static SDL_bool PS2_GetGamepadMapping(int device_index, SDL_GamepadMapping * out)
+{
+    return SDL_FALSE;
+}
+
+SDL_JoystickDriver SDL_PS2_JoystickDriver =
+{
+    PS2_JoystickInit,
+    PS2_JoystickGetCount,
+    PS2_JoystickDetect,
+    PS2_JoystickGetDeviceName,
+    PS2_JoystickGetDevicePath,
+    PS2_JoystickGetDevicePlayerIndex,
+    PS2_JoystickSetDevicePlayerIndex,
+    PS2_JoystickGetDeviceGUID,
+    PS2_JoystickGetDeviceInstanceID,
+    PS2_JoystickOpen,
+    PS2_JoystickRumble,
+    PS2_JoystickRumbleTriggers,
+    PS2_JoystickGetCapabilities,
+    PS2_JoystickSetLED,
+    PS2_JoystickSendEffect,
+    PS2_JoystickSetSensorsEnabled,
+    PS2_JoystickUpdate,
+    PS2_JoystickClose,
+    PS2_JoystickQuit,
+    PS2_GetGamepadMapping,
+};
+
+#endif /* SDL_JOYSTICK_PS2 */
+
+/* vi: set ts=4 sw=4 expandtab: */