mirror of
https://github.com/libretro/Lakka-LibreELEC.git
synced 2024-11-24 12:36:18 +00:00
4f8d2a57e9
* Initial fully buildable/bootable Odin Support * Rework NX-Boot to FAT32 Boot for shared usage between switch and odin * Move shared packages from switch/odin to main packages folder
912 lines
23 KiB
Diff
912 lines
23 KiB
Diff
From 6ccf93c7fa566cb2d8ea0370b7067aa3faab79e3 Mon Sep 17 00:00:00 2001
|
|
From: Teguh Sobirin <teguh@sobir.in>
|
|
Date: Fri, 1 Jul 2022 01:34:03 +0700
|
|
Subject: [PATCH 4/5] Input: add AYN Odin gamepad driver
|
|
|
|
---
|
|
drivers/input/joystick/Kconfig | 8 +
|
|
drivers/input/joystick/Makefile | 1 +
|
|
drivers/input/joystick/odin-gamepad.c | 859 ++++++++++++++++++++++++++
|
|
3 files changed, 868 insertions(+)
|
|
create mode 100644 drivers/input/joystick/odin-gamepad.c
|
|
|
|
diff --git a/drivers/input/joystick/Kconfig b/drivers/input/joystick/Kconfig
|
|
index 3b23078bc7b5..558db560bd65 100644
|
|
--- a/drivers/input/joystick/Kconfig
|
|
+++ b/drivers/input/joystick/Kconfig
|
|
@@ -343,6 +343,14 @@ config JOYSTICK_MAPLE
|
|
To compile this as a module choose M here: the module will be called
|
|
maplecontrol.
|
|
|
|
+config JOYSTICK_ODIN_GAMEPAD
|
|
+ tristate "AYN Odin Gamepad driver"
|
|
+ depends on ARCH_QCOM
|
|
+ help
|
|
+ Say Y here if you wish to enable AYN Odin internal gamepad unit.
|
|
+ To compile this as a module choose M here: the module will be called
|
|
+ odin-gamepad.
|
|
+
|
|
config JOYSTICK_PSXPAD_SPI
|
|
tristate "PlayStation 1/2 joypads via SPI interface"
|
|
depends on SPI
|
|
diff --git a/drivers/input/joystick/Makefile b/drivers/input/joystick/Makefile
|
|
index 5174b8aba2dd..97918839334c 100644
|
|
--- a/drivers/input/joystick/Makefile
|
|
+++ b/drivers/input/joystick/Makefile
|
|
@@ -25,6 +25,7 @@ obj-$(CONFIG_JOYSTICK_JOYDUMP) += joydump.o
|
|
obj-$(CONFIG_JOYSTICK_MAGELLAN) += magellan.o
|
|
obj-$(CONFIG_JOYSTICK_MAPLE) += maplecontrol.o
|
|
obj-$(CONFIG_JOYSTICK_N64) += n64joy.o
|
|
+obj-$(CONFIG_JOYSTICK_ODIN_GAMEPAD) += odin-gamepad.o
|
|
obj-$(CONFIG_JOYSTICK_PSXPAD_SPI) += psxpad-spi.o
|
|
obj-$(CONFIG_JOYSTICK_PXRC) += pxrc.o
|
|
obj-$(CONFIG_JOYSTICK_QWIIC) += qwiic-joystick.o
|
|
diff --git a/drivers/input/joystick/odin-gamepad.c b/drivers/input/joystick/odin-gamepad.c
|
|
new file mode 100644
|
|
index 000000000000..fd66ee9120fc
|
|
--- /dev/null
|
|
+++ b/drivers/input/joystick/odin-gamepad.c
|
|
@@ -0,0 +1,859 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * AYN Odin ADC joysticks and GPIO buttons driver.
|
|
+ * Copyright (c) 2022 Teguh Sobirin <teguh@sobir.in>
|
|
+ */
|
|
+
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/slab.h>
|
|
+#include <linux/input.h>
|
|
+#include <linux/ioport.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/gpio.h>
|
|
+#include <linux/gpio/consumer.h>
|
|
+#include <linux/gpio_keys.h>
|
|
+#include <linux/iio/consumer.h>
|
|
+#include <linux/iio/types.h>
|
|
+#include <linux/property.h>
|
|
+#include <linux/of_gpio.h>
|
|
+#include <linux/delay.h>
|
|
+
|
|
+#define DRIVER_NAME "odin-gamepad"
|
|
+
|
|
+enum adc_channel_id {
|
|
+ ADC_ABS_X = 0,
|
|
+ ADC_ABS_Y,
|
|
+ ADC_ABS_RX,
|
|
+ ADC_ABS_RY,
|
|
+ ADC_ABS_Z,
|
|
+ ADC_ABS_RZ,
|
|
+ ADC_CHAN_MAX
|
|
+};
|
|
+
|
|
+struct adc {
|
|
+ struct iio_channel *channel;
|
|
+ int value;
|
|
+ int report_type;
|
|
+ int max, min;
|
|
+ int cal;
|
|
+ bool invert;
|
|
+};
|
|
+
|
|
+struct btn {
|
|
+ const char *label;
|
|
+ int num;
|
|
+ int report_type;
|
|
+ int linux_code;
|
|
+ bool value;
|
|
+ bool active_level;
|
|
+};
|
|
+
|
|
+struct gamepad {
|
|
+ struct device *dev;
|
|
+ struct input_dev *input;
|
|
+
|
|
+ struct adc *adcs;
|
|
+ bool invert_x, invert_y;
|
|
+ bool invert_rx, invert_ry;
|
|
+ bool invert_z, invert_rz;
|
|
+ int adc_fuzz, adc_flat;
|
|
+ int adc_x_range, adc_y_range;
|
|
+ int adc_rx_range, adc_ry_range;
|
|
+ int adc_z_range, adc_rz_range;
|
|
+ int adc_deadzone;
|
|
+ int adc_count;
|
|
+
|
|
+ struct btn *btns;
|
|
+ int poll_interval;
|
|
+ int btn_count;
|
|
+ int auto_repeat;
|
|
+
|
|
+ bool enable;
|
|
+ struct mutex lock;
|
|
+};
|
|
+
|
|
+static unsigned int g_adc_x_range = 0;
|
|
+static unsigned int g_adc_y_range = 0;
|
|
+static unsigned int g_adc_rx_range = 0;
|
|
+static unsigned int g_adc_ry_range = 0;
|
|
+static unsigned int g_adc_z_range = 0;
|
|
+static unsigned int g_adc_rz_range = 0;
|
|
+static unsigned int g_adc_fuzz = 0;
|
|
+static unsigned int g_adc_flat = 0;
|
|
+static unsigned int g_adc_deadzone = 0;
|
|
+
|
|
+static int __init adcx_range_setup(char *str)
|
|
+{
|
|
+ if (!str)
|
|
+ return -EINVAL;
|
|
+
|
|
+ g_adc_x_range = simple_strtoul(str, NULL, 10);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+__setup("adc-x-range=", adcx_range_setup);
|
|
+
|
|
+static int __init adcy_range_setup(char *str)
|
|
+{
|
|
+ if (!str)
|
|
+ return -EINVAL;
|
|
+
|
|
+ g_adc_y_range = simple_strtoul(str, NULL, 10);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+__setup("adc-y-range=", adcy_range_setup);
|
|
+
|
|
+static int __init adcrx_range_setup(char *str)
|
|
+{
|
|
+ if (!str)
|
|
+ return -EINVAL;
|
|
+
|
|
+ g_adc_rx_range = simple_strtoul(str, NULL, 10);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+__setup("adc-rx-range=", adcrx_range_setup);
|
|
+
|
|
+static int __init adcry_range_setup(char *str)
|
|
+{
|
|
+ if (!str)
|
|
+ return -EINVAL;
|
|
+
|
|
+ g_adc_ry_range = simple_strtoul(str, NULL, 10);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+__setup("adc-ry-range=", adcry_range_setup);
|
|
+
|
|
+static int __init adcz_range_setup(char *str)
|
|
+{
|
|
+ if (!str)
|
|
+ return -EINVAL;
|
|
+
|
|
+ g_adc_z_range = simple_strtoul(str, NULL, 10);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+__setup("adc-z-range=", adcz_range_setup);
|
|
+
|
|
+static int __init adcrz_range_setup(char *str)
|
|
+{
|
|
+ if (!str)
|
|
+ return -EINVAL;
|
|
+
|
|
+ g_adc_rz_range = simple_strtoul(str, NULL, 10);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+__setup("adc-rz-range=", adcrz_range_setup);
|
|
+
|
|
+static int adc_fuzz(char *str)
|
|
+{
|
|
+ if (!str)
|
|
+ return -EINVAL;
|
|
+ g_adc_fuzz = simple_strtoul(str, NULL, 10);
|
|
+ return 0;
|
|
+}
|
|
+__setup("adc-fuzz=", adc_fuzz);
|
|
+
|
|
+static int adc_flat(char *str)
|
|
+{
|
|
+ if (!str)
|
|
+ return -EINVAL;
|
|
+ g_adc_flat = simple_strtoul(str, NULL, 10);
|
|
+ return 0;
|
|
+}
|
|
+__setup("adc-flat=", adc_flat);
|
|
+
|
|
+static int adc_deadzone(char *str)
|
|
+{
|
|
+ if (!str)
|
|
+ return -EINVAL;
|
|
+ g_adc_deadzone = simple_strtoul(str, NULL, 10);
|
|
+ return 0;
|
|
+}
|
|
+__setup("adc-deadzone=", adc_deadzone);
|
|
+
|
|
+static int gamepad_adc_read(struct adc *adc)
|
|
+{
|
|
+ int ret, value, value_mv;
|
|
+
|
|
+ ret=iio_read_channel_processed(adc->channel, &value_mv);
|
|
+ if (unlikely( ret < 0))
|
|
+ return 0;
|
|
+
|
|
+ value = value_mv / 100;
|
|
+
|
|
+ return (adc->invert ? (adc->max - value) : value);
|
|
+}
|
|
+
|
|
+static ssize_t gamepad_store_poll_interval(struct device *dev,
|
|
+ struct device_attribute *attr,
|
|
+ const char *buf,
|
|
+ size_t count)
|
|
+{
|
|
+ struct platform_device *pdev = to_platform_device(dev);
|
|
+ struct gamepad *gamepad = platform_get_drvdata(pdev);
|
|
+
|
|
+ mutex_lock(&gamepad->lock);
|
|
+ gamepad->poll_interval = simple_strtoul(buf, NULL, 10);
|
|
+ mutex_unlock(&gamepad->lock);
|
|
+
|
|
+ return count;
|
|
+}
|
|
+
|
|
+static ssize_t gamepad_show_poll_interval(struct device *dev,
|
|
+ struct device_attribute *attr,
|
|
+ char *buf)
|
|
+{
|
|
+ struct platform_device *pdev = to_platform_device(dev);
|
|
+ struct gamepad *gamepad = platform_get_drvdata(pdev);
|
|
+
|
|
+ return sprintf(buf, "%d\n", gamepad->poll_interval);
|
|
+}
|
|
+
|
|
+static ssize_t gamepad_store_enable(struct device *dev,
|
|
+ struct device_attribute *attr,
|
|
+ const char *buf,
|
|
+ size_t count)
|
|
+{
|
|
+ struct platform_device *pdev = to_platform_device(dev);
|
|
+ struct gamepad *gamepad = platform_get_drvdata(pdev);
|
|
+
|
|
+ mutex_lock(&gamepad->lock);
|
|
+ gamepad->enable = simple_strtoul(buf, NULL, 10);
|
|
+ mutex_unlock(&gamepad->lock);
|
|
+
|
|
+ return count;
|
|
+}
|
|
+
|
|
+static ssize_t gamepad_show_enable(struct device *dev,
|
|
+ struct device_attribute *attr,
|
|
+ char *buf)
|
|
+{
|
|
+ struct platform_device *pdev = to_platform_device(dev);
|
|
+ struct gamepad *gamepad = platform_get_drvdata(pdev);
|
|
+
|
|
+ return sprintf(buf, "%d\n", gamepad->enable);
|
|
+}
|
|
+
|
|
+static ssize_t gamepad_show_adc_fuzz(struct device *dev,
|
|
+ struct device_attribute *attr,
|
|
+ char *buf)
|
|
+{
|
|
+ struct platform_device *pdev = to_platform_device(dev);
|
|
+ struct gamepad *gamepad = platform_get_drvdata(pdev);
|
|
+
|
|
+ return sprintf(buf, "%d\n", gamepad->adc_fuzz);
|
|
+}
|
|
+
|
|
+static ssize_t gamepad_show_adc_flat(struct device *dev,
|
|
+ struct device_attribute *attr,
|
|
+ char *buf)
|
|
+{
|
|
+ struct platform_device *pdev = to_platform_device(dev);
|
|
+ struct gamepad *gamepad = platform_get_drvdata(pdev);
|
|
+
|
|
+ return sprintf(buf, "%d\n", gamepad->adc_flat);
|
|
+}
|
|
+
|
|
+static ssize_t gamepad_store_adc_cal(struct device *dev,
|
|
+ struct device_attribute *attr,
|
|
+ const char *buf,
|
|
+ size_t count)
|
|
+{
|
|
+ struct platform_device *pdev = to_platform_device(dev);
|
|
+ struct gamepad *gamepad = platform_get_drvdata(pdev);
|
|
+ bool calibration;
|
|
+
|
|
+ calibration = simple_strtoul(buf, NULL, 10);
|
|
+
|
|
+ if (calibration) {
|
|
+ int nbtn;
|
|
+
|
|
+ mutex_lock(&gamepad->lock);
|
|
+ for (nbtn = 0; nbtn < gamepad->adc_count; nbtn++) {
|
|
+ struct adc *adc = &gamepad->adcs[nbtn];
|
|
+
|
|
+ adc->cal = gamepad_adc_read(adc);
|
|
+ if (!adc->cal) {
|
|
+ dev_err(gamepad->dev, "%s : adc channels[%d]!\n",
|
|
+ __func__, nbtn);
|
|
+ continue;
|
|
+ }
|
|
+ adc->value = adc->cal;
|
|
+ }
|
|
+ mutex_unlock(&gamepad->lock);
|
|
+ }
|
|
+ return count;
|
|
+}
|
|
+
|
|
+static ssize_t gamepad_show_adc_cal(struct device *dev,
|
|
+ struct device_attribute *attr,
|
|
+ char *buf)
|
|
+{
|
|
+ struct platform_device *pdev = to_platform_device(dev);
|
|
+ struct gamepad *gamepad = platform_get_drvdata(pdev);
|
|
+ int nbtn;
|
|
+ ssize_t pos;
|
|
+
|
|
+ for (nbtn = 0, pos = 0; nbtn < gamepad->adc_count; nbtn++) {
|
|
+ struct adc *adc = &gamepad->adcs[nbtn];
|
|
+ pos += sprintf(&buf[pos], "adc[%d]->cal = %d ",
|
|
+ nbtn, adc->cal);
|
|
+ }
|
|
+ pos += sprintf(&buf[pos], "\n");
|
|
+ return pos;
|
|
+}
|
|
+
|
|
+static DEVICE_ATTR(poll_interval, S_IWUSR | S_IRUGO,
|
|
+ gamepad_show_poll_interval,
|
|
+ gamepad_store_poll_interval);
|
|
+
|
|
+static DEVICE_ATTR(enable, S_IWUSR | S_IRUGO,
|
|
+ gamepad_show_enable,
|
|
+ gamepad_store_enable);
|
|
+
|
|
+static DEVICE_ATTR(adc_fuzz, S_IWUSR | S_IRUGO,
|
|
+ gamepad_show_adc_fuzz,
|
|
+ NULL);
|
|
+
|
|
+static DEVICE_ATTR(adc_flat, S_IWUSR | S_IRUGO,
|
|
+ gamepad_show_adc_flat,
|
|
+ NULL);
|
|
+
|
|
+static DEVICE_ATTR(adc_cal, S_IWUSR | S_IRUGO,
|
|
+ gamepad_show_adc_cal,
|
|
+ gamepad_store_adc_cal);
|
|
+
|
|
+static struct attribute *gamepad_attrs[] = {
|
|
+ &dev_attr_poll_interval.attr,
|
|
+ &dev_attr_adc_fuzz.attr,
|
|
+ &dev_attr_adc_flat.attr,
|
|
+ &dev_attr_enable.attr,
|
|
+ &dev_attr_adc_cal.attr,
|
|
+ NULL,
|
|
+};
|
|
+
|
|
+static struct attribute_group gamepad_attr_group = {
|
|
+ .attrs = gamepad_attrs,
|
|
+};
|
|
+
|
|
+static void gamepad_get(struct input_dev *input)
|
|
+{
|
|
+ struct gamepad *gamepad = input_get_drvdata(input);
|
|
+ int nbtn, value;
|
|
+
|
|
+ for (nbtn = 0; nbtn < gamepad->adc_count; nbtn++) {
|
|
+ struct adc *adc = &gamepad->adcs[nbtn];
|
|
+
|
|
+ value = gamepad_adc_read(adc);
|
|
+ if (!value) {
|
|
+ dev_err(gamepad->dev, "%s : adc channels[%d]!\n",
|
|
+ __func__, nbtn);
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (gamepad->adc_deadzone) {
|
|
+ if ((value < adc->cal + gamepad->adc_deadzone) &&
|
|
+ (value > adc->cal - gamepad->adc_deadzone))
|
|
+ value = adc->cal;
|
|
+ }
|
|
+ value = value - adc->cal;
|
|
+ value = value > adc->max ? adc->max : value;
|
|
+ value = value < adc->min ? adc->min : value;
|
|
+
|
|
+ input_report_abs(input, adc->report_type, value);
|
|
+ adc->value = value;
|
|
+ }
|
|
+
|
|
+ for (nbtn = 0; nbtn < gamepad->btn_count; nbtn++) {
|
|
+ struct btn *btn = &gamepad->btns[nbtn];
|
|
+
|
|
+ if (gpio_get_value_cansleep(btn->num) < 0) {
|
|
+ dev_err(gamepad->dev, "failed to get gpio state\n");
|
|
+ continue;
|
|
+ }
|
|
+ value = gpio_get_value(btn->num);
|
|
+ if (value != btn->value) {
|
|
+ input_event(input,
|
|
+ btn->report_type,
|
|
+ btn->linux_code,
|
|
+ (value == btn->active_level) ? 1 : 0);
|
|
+ btn->value = value;
|
|
+ }
|
|
+ }
|
|
+ input_sync(input);
|
|
+}
|
|
+
|
|
+static void gamepad_poll(struct input_dev *input)
|
|
+{
|
|
+ struct gamepad *gamepad = input_get_drvdata(input);
|
|
+
|
|
+ if (gamepad->enable) {
|
|
+ gamepad_get(input);
|
|
+ }
|
|
+ if (input_get_poll_interval(input) != gamepad->poll_interval) {
|
|
+ mutex_lock(&gamepad->lock);
|
|
+ input_set_poll_interval(input, gamepad->poll_interval);
|
|
+ mutex_unlock(&gamepad->lock);
|
|
+ }
|
|
+}
|
|
+
|
|
+static int gamepad_open(struct input_dev *input)
|
|
+{
|
|
+ struct gamepad *gamepad = input_get_drvdata(input);
|
|
+ int nbtn;
|
|
+
|
|
+ for (nbtn = 0; nbtn < gamepad->adc_count; nbtn++) {
|
|
+ struct adc *adc = &gamepad->adcs[nbtn];
|
|
+
|
|
+ adc->value = gamepad_adc_read(adc);
|
|
+ if (!adc->value) {
|
|
+ dev_err(gamepad->dev, "%s : adc channels[%d]!\n",
|
|
+ __func__, nbtn);
|
|
+ continue;
|
|
+ }
|
|
+ adc->cal = adc->value;
|
|
+ dev_info(gamepad->dev, "%s : adc[%d] adc->cal = %d\n",
|
|
+ __func__, nbtn, adc->cal);
|
|
+ }
|
|
+ for (nbtn = 0; nbtn < gamepad->btn_count; nbtn++) {
|
|
+ struct btn *btn = &gamepad->btns[nbtn];
|
|
+ btn->value = btn->active_level ? 0 : 1;
|
|
+ }
|
|
+
|
|
+ gamepad_get(input);
|
|
+
|
|
+ mutex_lock(&gamepad->lock);
|
|
+ gamepad->enable = true;
|
|
+ mutex_unlock(&gamepad->lock);
|
|
+
|
|
+ dev_info(gamepad->dev, "%s : opened\n", __func__);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void gamepad_close(struct input_dev *input)
|
|
+{
|
|
+ struct gamepad *gamepad = input_get_drvdata(input);
|
|
+
|
|
+ mutex_lock(&gamepad->lock);
|
|
+ gamepad->enable = false;
|
|
+ mutex_unlock(&gamepad->lock);
|
|
+
|
|
+ dev_info(gamepad->dev, "%s : closed\n", __func__);
|
|
+}
|
|
+
|
|
+static int gamepad_adc_setup(struct device *dev, struct gamepad *gamepad)
|
|
+{
|
|
+ int nbtn = 0;
|
|
+
|
|
+ gamepad->adcs = devm_kzalloc(dev, gamepad->adc_count *
|
|
+ sizeof(struct adc), GFP_KERNEL);
|
|
+
|
|
+ if (!gamepad->adcs) {
|
|
+ dev_err(dev, "%s devm_kzmalloc error!", __func__);
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ for (nbtn = 0; nbtn < gamepad->adc_count; nbtn++) {
|
|
+ struct adc *adc = &gamepad->adcs[nbtn];
|
|
+ enum iio_chan_type type;
|
|
+
|
|
+ switch (nbtn) {
|
|
+ case ADC_ABS_X:
|
|
+ adc->channel =
|
|
+ devm_iio_channel_get(dev, "abs_x");
|
|
+ adc->report_type = ABS_X;
|
|
+ if (gamepad->invert_x)
|
|
+ adc->invert = true;
|
|
+
|
|
+ adc->max = (gamepad->adc_x_range / 2) - 1;
|
|
+ adc->min = -(gamepad->adc_x_range / 2);
|
|
+ break;
|
|
+ case ADC_ABS_Y:
|
|
+ adc->channel =
|
|
+ devm_iio_channel_get(dev, "abs_y");
|
|
+ adc->report_type = ABS_Y;
|
|
+ if (gamepad->invert_y)
|
|
+ adc->invert = true;
|
|
+
|
|
+ adc->max = (gamepad->adc_y_range / 2) - 1;
|
|
+ adc->min = -(gamepad->adc_y_range / 2);
|
|
+ break;
|
|
+ case ADC_ABS_RX:
|
|
+ adc->channel =
|
|
+ devm_iio_channel_get(dev, "abs_rx");
|
|
+ adc->report_type = ABS_RX;
|
|
+ if (gamepad->invert_rx)
|
|
+ adc->invert = true;
|
|
+
|
|
+ adc->max = (gamepad->adc_rx_range / 2) - 1;
|
|
+ adc->min = -(gamepad->adc_rx_range / 2);
|
|
+ break;
|
|
+ case ADC_ABS_RY:
|
|
+ adc->channel =
|
|
+ devm_iio_channel_get(dev, "abs_ry");
|
|
+ adc->report_type = ABS_RY;
|
|
+ if (gamepad->invert_ry)
|
|
+ adc->invert = true;
|
|
+
|
|
+ adc->max = (gamepad->adc_ry_range / 2) - 1;
|
|
+ adc->min = -(gamepad->adc_ry_range / 2);
|
|
+ break;
|
|
+ case ADC_ABS_Z:
|
|
+ adc->channel =
|
|
+ devm_iio_channel_get(dev, "abs_z");
|
|
+ adc->report_type = ABS_Z;
|
|
+ if (gamepad->invert_z)
|
|
+ adc->invert = true;
|
|
+
|
|
+ adc->max = (gamepad->adc_z_range / 2) - 1;
|
|
+ adc->min = -(gamepad->adc_z_range / 2);
|
|
+ break;
|
|
+ case ADC_ABS_RZ:
|
|
+ adc->channel =
|
|
+ devm_iio_channel_get(dev, "abs_rz");
|
|
+ adc->report_type = ABS_RZ;
|
|
+ if (gamepad->invert_rz)
|
|
+ adc->invert = true;
|
|
+
|
|
+ adc->max = (gamepad->adc_rz_range / 2) - 1;
|
|
+ adc->min = -(gamepad->adc_rz_range / 2);
|
|
+ break;
|
|
+ default:
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ if (IS_ERR(adc->channel)) {
|
|
+ dev_err(dev, "iio channel[%d] get error\n", nbtn);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ if (!adc->channel->indio_dev)
|
|
+ return -ENXIO;
|
|
+
|
|
+ if (iio_get_channel_type(adc->channel, &type))
|
|
+ return -EINVAL;
|
|
+
|
|
+ if (type != IIO_VOLTAGE) {
|
|
+ dev_err(dev, "Incompatible channel %d type %d\n",
|
|
+ nbtn, type);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ }
|
|
+ if (nbtn == 0)
|
|
+ return -EINVAL;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int gamepad_btn_setup(struct device *dev, struct gamepad *gamepad)
|
|
+{
|
|
+ struct device_node *node, *pp;
|
|
+ int nbtn;
|
|
+
|
|
+ node = dev->of_node;
|
|
+ if (!node)
|
|
+ return -ENODEV;
|
|
+
|
|
+ gamepad->btns = devm_kzalloc(dev, gamepad->btn_count *
|
|
+ sizeof(struct btn), GFP_KERNEL);
|
|
+
|
|
+ if (!gamepad->btns) {
|
|
+ dev_err(dev, "%s devm_kzmalloc error!", __func__);
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ nbtn = 0;
|
|
+ for_each_child_of_node(node, pp) {
|
|
+ enum of_gpio_flags flags;
|
|
+ struct btn *btn = &gamepad->btns[nbtn++];
|
|
+ int error;
|
|
+
|
|
+ btn->num = of_get_gpio_flags(pp, 0, &flags);
|
|
+ if (btn->num < 0) {
|
|
+ error = btn->num;
|
|
+ dev_err(dev, "Failed to get GPIO flags, error: %d\n",
|
|
+ error);
|
|
+ return error;
|
|
+ }
|
|
+
|
|
+ btn->active_level = (flags & OF_GPIO_ACTIVE_LOW) ? 0 : 1;
|
|
+ btn->label = of_get_property(pp, "label", NULL);
|
|
+
|
|
+ if (gpio_is_valid(btn->num)) {
|
|
+ error = devm_gpio_request_one(dev, btn->num,
|
|
+ GPIOF_IN, btn->label);
|
|
+ if (error < 0) {
|
|
+ dev_err(dev,
|
|
+ "Failed to request GPIO %d, error %d\n",
|
|
+ btn->num, error);
|
|
+ return error;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (of_property_read_u32(pp, "linux,code", &btn->linux_code)) {
|
|
+ dev_err(dev, "Button without keycode: 0x%x\n",
|
|
+ btn->num);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ btn->report_type = EV_KEY;
|
|
+ }
|
|
+ if (nbtn == 0)
|
|
+ return -EINVAL;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int gamepad_input_setup(struct device *dev, struct gamepad *gamepad)
|
|
+{
|
|
+ struct input_dev *input;
|
|
+ int nbtn, error;
|
|
+ u32 gamepad_vendor = 0;
|
|
+ u32 gamepad_product = 0;
|
|
+ u32 gamepad_revision = 0;
|
|
+
|
|
+ input = devm_input_allocate_device(dev);
|
|
+ if (!input) {
|
|
+ dev_err(dev, "failed to allocate input device\n");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ gamepad->input = input;
|
|
+ input_set_drvdata(input, gamepad);
|
|
+
|
|
+ input->open = gamepad_open;
|
|
+ input->close = gamepad_close;
|
|
+ input->phys = DRIVER_NAME"/input0";
|
|
+
|
|
+ device_property_read_string(dev, "gamepad-name", &input->name);
|
|
+
|
|
+ device_property_read_u32(dev, "gamepad-vendor", &gamepad_vendor);
|
|
+ device_property_read_u32(dev, "gamepad-product", &gamepad_product);
|
|
+ device_property_read_u32(dev, "gamepad-revision", &gamepad_revision);
|
|
+
|
|
+ input->id.bustype = BUS_HOST;
|
|
+ input->id.vendor = (u16)gamepad_vendor;
|
|
+ input->id.product = (u16)gamepad_product;
|
|
+ input->id.version = (u16)gamepad_revision;
|
|
+
|
|
+ __set_bit(EV_ABS, input->evbit);
|
|
+ for(nbtn = 0; nbtn < gamepad->adc_count; nbtn++) {
|
|
+ struct adc *adc = &gamepad->adcs[nbtn];
|
|
+ input_set_abs_params(input, adc->report_type,
|
|
+ adc->min, adc->max,
|
|
+ gamepad->adc_fuzz,
|
|
+ gamepad->adc_flat);
|
|
+ dev_info(dev,
|
|
+ "%s : ABS min = %d, max = %d,"
|
|
+ " fuzz = %d, flat = %d, deadzone = %d\n",
|
|
+ __func__, adc->min, adc->max,
|
|
+ gamepad->adc_fuzz, gamepad->adc_flat,
|
|
+ gamepad->adc_deadzone);
|
|
+ }
|
|
+
|
|
+ __set_bit(EV_KEY, input->evbit);
|
|
+ for(nbtn = 0; nbtn < gamepad->btn_count; nbtn++) {
|
|
+ struct btn *btn = &gamepad->btns[nbtn];
|
|
+ input_set_capability(input, btn->report_type,
|
|
+ btn->linux_code);
|
|
+ }
|
|
+
|
|
+ if (gamepad->auto_repeat)
|
|
+ __set_bit(EV_REP, input->evbit);
|
|
+
|
|
+ gamepad->dev = dev;
|
|
+
|
|
+ error = input_setup_polling(input, gamepad_poll);
|
|
+ if (error) {
|
|
+ dev_err(dev, "could not set up polling mode, %d\n", error);
|
|
+ return error;
|
|
+ }
|
|
+
|
|
+ input_set_poll_interval(input, gamepad->poll_interval);
|
|
+
|
|
+ error = input_register_device(input);
|
|
+ if (error) {
|
|
+ dev_err(dev, "unable to register polled device, err=%d\n",
|
|
+ error);
|
|
+ return error;
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void gamepad_setup_value(struct device *dev, struct gamepad *gamepad)
|
|
+{
|
|
+ if (g_adc_fuzz)
|
|
+ gamepad->adc_fuzz = g_adc_fuzz;
|
|
+ else
|
|
+ device_property_read_u32(dev, "adc-fuzz",
|
|
+ &gamepad->adc_fuzz);
|
|
+
|
|
+ if (g_adc_flat)
|
|
+ gamepad->adc_flat = g_adc_flat;
|
|
+ else
|
|
+ device_property_read_u32(dev, "adc-flat",
|
|
+ &gamepad->adc_flat);
|
|
+
|
|
+ if (g_adc_deadzone)
|
|
+ gamepad->adc_deadzone = g_adc_deadzone;
|
|
+ else
|
|
+ device_property_read_u32(dev, "adc-deadzone",
|
|
+ &gamepad->adc_deadzone);
|
|
+
|
|
+ if (g_adc_x_range)
|
|
+ gamepad->adc_x_range = g_adc_x_range;
|
|
+ else
|
|
+ device_property_read_u32(dev, "adc-x-range",
|
|
+ &gamepad->adc_x_range);
|
|
+
|
|
+ if (g_adc_y_range)
|
|
+ gamepad->adc_y_range = g_adc_y_range;
|
|
+ else
|
|
+ device_property_read_u32(dev, "adc-y-range",
|
|
+ &gamepad->adc_y_range);
|
|
+
|
|
+ if (g_adc_rx_range)
|
|
+ gamepad->adc_rx_range = g_adc_rx_range;
|
|
+ else
|
|
+ device_property_read_u32(dev, "adc-rx-range",
|
|
+ &gamepad->adc_rx_range);
|
|
+
|
|
+ if (g_adc_ry_range)
|
|
+ gamepad->adc_ry_range = g_adc_ry_range;
|
|
+ else
|
|
+ device_property_read_u32(dev, "adc-ry-range",
|
|
+ &gamepad->adc_ry_range);
|
|
+
|
|
+ if (g_adc_z_range)
|
|
+ gamepad->adc_z_range = g_adc_z_range;
|
|
+ else
|
|
+ device_property_read_u32(dev, "adc-z-range",
|
|
+ &gamepad->adc_z_range);
|
|
+
|
|
+ if (g_adc_rz_range)
|
|
+ gamepad->adc_rz_range = g_adc_rz_range;
|
|
+ else
|
|
+ device_property_read_u32(dev, "adc-rz-range",
|
|
+ &gamepad->adc_rz_range);
|
|
+}
|
|
+
|
|
+static int gamepad_parse_dt(struct device *dev, struct gamepad *gamepad)
|
|
+{
|
|
+ int error = 0;
|
|
+
|
|
+ gamepad_setup_value(dev, gamepad);
|
|
+
|
|
+ device_property_read_u32(dev, "poll-interval", &gamepad->poll_interval);
|
|
+
|
|
+ device_property_read_u32(dev, "adc-count", &gamepad->adc_count);
|
|
+
|
|
+ gamepad->invert_x = device_property_present(dev, "invert-x");
|
|
+ gamepad->invert_y = device_property_present(dev, "invert-y");
|
|
+ gamepad->invert_rx = device_property_present(dev, "invert-rx");
|
|
+ gamepad->invert_ry = device_property_present(dev, "invert-ry");
|
|
+ gamepad->invert_z = device_property_present(dev, "invert-z");
|
|
+ gamepad->invert_rz = device_property_present(dev, "invert-rz");
|
|
+ dev_info(dev,
|
|
+ "%s : invert-x = %d, invert-y = %d, "
|
|
+ "invert-rx = %d, invert-ry = %d, "
|
|
+ "invert-z = %d, invert-rz = %d\n",
|
|
+ __func__, gamepad->invert_x, gamepad->invert_y,
|
|
+ gamepad->invert_rx, gamepad->invert_ry,
|
|
+ gamepad->invert_z, gamepad->invert_rz);
|
|
+
|
|
+ gamepad->btn_count = device_get_child_node_count(dev);
|
|
+ gamepad->auto_repeat = device_property_present(dev, "autorepeat");
|
|
+
|
|
+ if ((gamepad->adc_count == 0) || (gamepad->btn_count == 0)) {
|
|
+ dev_err(dev, "adc = %d, btn = %d error!",
|
|
+ gamepad->adc_count, gamepad->btn_count);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ error = gamepad_adc_setup(dev, gamepad);
|
|
+ if (error)
|
|
+ return error;
|
|
+
|
|
+ error = gamepad_btn_setup(dev, gamepad);
|
|
+ if (error)
|
|
+ return error;
|
|
+
|
|
+ dev_info(dev, "%s : adcs cnt = %d btns cnt = %d\n",
|
|
+ __func__, gamepad->adc_count, gamepad->btn_count);
|
|
+
|
|
+ return error;
|
|
+}
|
|
+
|
|
+static int gamepad_probe(struct platform_device *pdev)
|
|
+{
|
|
+ struct gamepad *gamepad;
|
|
+ struct device *dev = &pdev->dev;
|
|
+ int error;
|
|
+
|
|
+ gamepad = devm_kzalloc(dev, sizeof(struct gamepad), GFP_KERNEL);
|
|
+ if (!gamepad) {
|
|
+ dev_err(dev, "gamepad devm_kzmalloc error!");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ error = gamepad_parse_dt(dev, gamepad);
|
|
+ if (error) {
|
|
+ dev_err(dev, "dt parse error!(err = %d)\n", error);
|
|
+ return error;
|
|
+ }
|
|
+
|
|
+ mutex_init(&gamepad->lock);
|
|
+ platform_set_drvdata(pdev, gamepad);
|
|
+
|
|
+ error = sysfs_create_group(&pdev->dev.kobj, &gamepad_attr_group);
|
|
+ if (error) {
|
|
+ dev_err(dev, "create sysfs group fail, error: %d\n",
|
|
+ error);
|
|
+ return error;
|
|
+ }
|
|
+
|
|
+ error = gamepad_input_setup(dev, gamepad);
|
|
+ if (error) {
|
|
+ dev_err(dev, "input setup failed!(err = %d)\n", error);
|
|
+ return error;
|
|
+ }
|
|
+ dev_info(dev, "%s : probe success\n", __func__);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct of_device_id gamepad_of_match[] = {
|
|
+ { .compatible = "odin-gamepad", },
|
|
+ { /* sentinel */ },
|
|
+};
|
|
+
|
|
+MODULE_DEVICE_TABLE(of, gamepad_of_match);
|
|
+
|
|
+static struct platform_driver gamepad_driver = {
|
|
+ .probe = gamepad_probe,
|
|
+ .driver = {
|
|
+ .name = DRIVER_NAME,
|
|
+ .of_match_table = of_match_ptr(gamepad_of_match),
|
|
+ },
|
|
+};
|
|
+
|
|
+static int __init gamepad_init(void)
|
|
+{
|
|
+ return platform_driver_register(&gamepad_driver);
|
|
+}
|
|
+
|
|
+static void __exit gamepad_exit(void)
|
|
+{
|
|
+ platform_driver_unregister(&gamepad_driver);
|
|
+}
|
|
+
|
|
+late_initcall(gamepad_init);
|
|
+module_exit(gamepad_exit);
|
|
+
|
|
+MODULE_DESCRIPTION("AYN Odin ADC joysticks and GPIO buttons driver");
|
|
+MODULE_AUTHOR("Teguh Sobirin <teguh@sobir.in>");
|
|
+MODULE_LICENSE("GPL");
|
|
+MODULE_ALIAS("platform:" DRIVER_NAME);
|
|
--
|
|
2.34.1
|
|
|