316 lines
7.8 KiB
C
316 lines
7.8 KiB
C
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||
|
/*
|
||
|
* Copyright (C) 2023 Anshul Dalal <anshulusr@gmail.com>
|
||
|
*
|
||
|
* Driver for Adafruit Mini I2C Gamepad
|
||
|
*
|
||
|
* Based on the work of:
|
||
|
* Oleh Kravchenko (Sparkfun Qwiic Joystick driver)
|
||
|
*
|
||
|
* Datasheet: https://cdn-learn.adafruit.com/downloads/pdf/gamepad-qt.pdf
|
||
|
* Product page: https://www.adafruit.com/product/5743
|
||
|
* Firmware and hardware sources: https://github.com/adafruit/Adafruit_Seesaw
|
||
|
*
|
||
|
* TODO:
|
||
|
* - Add interrupt support
|
||
|
*/
|
||
|
|
||
|
#include <asm/unaligned.h>
|
||
|
#include <linux/bits.h>
|
||
|
#include <linux/delay.h>
|
||
|
#include <linux/i2c.h>
|
||
|
#include <linux/input.h>
|
||
|
#include <linux/input/sparse-keymap.h>
|
||
|
#include <linux/kernel.h>
|
||
|
#include <linux/module.h>
|
||
|
|
||
|
#define SEESAW_DEVICE_NAME "seesaw-gamepad"
|
||
|
|
||
|
#define SEESAW_ADC_BASE 0x0900
|
||
|
|
||
|
#define SEESAW_GPIO_DIRCLR_BULK 0x0103
|
||
|
#define SEESAW_GPIO_BULK 0x0104
|
||
|
#define SEESAW_GPIO_BULK_SET 0x0105
|
||
|
#define SEESAW_GPIO_PULLENSET 0x010b
|
||
|
|
||
|
#define SEESAW_STATUS_HW_ID 0x0001
|
||
|
#define SEESAW_STATUS_SWRST 0x007f
|
||
|
|
||
|
#define SEESAW_ADC_OFFSET 0x07
|
||
|
|
||
|
#define SEESAW_BUTTON_A 0x05
|
||
|
#define SEESAW_BUTTON_B 0x01
|
||
|
#define SEESAW_BUTTON_X 0x06
|
||
|
#define SEESAW_BUTTON_Y 0x02
|
||
|
#define SEESAW_BUTTON_START 0x10
|
||
|
#define SEESAW_BUTTON_SELECT 0x00
|
||
|
|
||
|
#define SEESAW_ANALOG_X 0x0e
|
||
|
#define SEESAW_ANALOG_Y 0x0f
|
||
|
|
||
|
#define SEESAW_JOYSTICK_MAX_AXIS 1023
|
||
|
#define SEESAW_JOYSTICK_FUZZ 2
|
||
|
#define SEESAW_JOYSTICK_FLAT 4
|
||
|
|
||
|
#define SEESAW_GAMEPAD_POLL_INTERVAL_MS 16
|
||
|
#define SEESAW_GAMEPAD_POLL_MIN 8
|
||
|
#define SEESAW_GAMEPAD_POLL_MAX 32
|
||
|
|
||
|
static const unsigned long SEESAW_BUTTON_MASK =
|
||
|
BIT(SEESAW_BUTTON_A) | BIT(SEESAW_BUTTON_B) | BIT(SEESAW_BUTTON_X) |
|
||
|
BIT(SEESAW_BUTTON_Y) | BIT(SEESAW_BUTTON_START) |
|
||
|
BIT(SEESAW_BUTTON_SELECT);
|
||
|
|
||
|
struct seesaw_gamepad {
|
||
|
struct input_dev *input_dev;
|
||
|
struct i2c_client *i2c_client;
|
||
|
};
|
||
|
|
||
|
struct seesaw_data {
|
||
|
u16 x;
|
||
|
u16 y;
|
||
|
u32 button_state;
|
||
|
};
|
||
|
|
||
|
static const struct key_entry seesaw_buttons_new[] = {
|
||
|
{ KE_KEY, SEESAW_BUTTON_A, .keycode = BTN_SOUTH },
|
||
|
{ KE_KEY, SEESAW_BUTTON_B, .keycode = BTN_EAST },
|
||
|
{ KE_KEY, SEESAW_BUTTON_X, .keycode = BTN_NORTH },
|
||
|
{ KE_KEY, SEESAW_BUTTON_Y, .keycode = BTN_WEST },
|
||
|
{ KE_KEY, SEESAW_BUTTON_START, .keycode = BTN_START },
|
||
|
{ KE_KEY, SEESAW_BUTTON_SELECT, .keycode = BTN_SELECT },
|
||
|
{ KE_END, 0 }
|
||
|
};
|
||
|
|
||
|
static int seesaw_register_read(struct i2c_client *client, u16 reg, void *buf,
|
||
|
int count)
|
||
|
{
|
||
|
__be16 register_buf = cpu_to_be16(reg);
|
||
|
struct i2c_msg message_buf[2] = {
|
||
|
{
|
||
|
.addr = client->addr,
|
||
|
.flags = client->flags,
|
||
|
.len = sizeof(register_buf),
|
||
|
.buf = (u8 *)®ister_buf,
|
||
|
},
|
||
|
{
|
||
|
.addr = client->addr,
|
||
|
.flags = client->flags | I2C_M_RD,
|
||
|
.len = count,
|
||
|
.buf = (u8 *)buf,
|
||
|
},
|
||
|
};
|
||
|
int ret;
|
||
|
|
||
|
ret = i2c_transfer(client->adapter, message_buf,
|
||
|
ARRAY_SIZE(message_buf));
|
||
|
if (ret < 0)
|
||
|
return ret;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int seesaw_register_write_u8(struct i2c_client *client, u16 reg,
|
||
|
u8 value)
|
||
|
{
|
||
|
u8 write_buf[sizeof(reg) + sizeof(value)];
|
||
|
int ret;
|
||
|
|
||
|
put_unaligned_be16(reg, write_buf);
|
||
|
write_buf[sizeof(reg)] = value;
|
||
|
|
||
|
ret = i2c_master_send(client, write_buf, sizeof(write_buf));
|
||
|
if (ret < 0)
|
||
|
return ret;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int seesaw_register_write_u32(struct i2c_client *client, u16 reg,
|
||
|
u32 value)
|
||
|
{
|
||
|
u8 write_buf[sizeof(reg) + sizeof(value)];
|
||
|
int ret;
|
||
|
|
||
|
put_unaligned_be16(reg, write_buf);
|
||
|
put_unaligned_be32(value, write_buf + sizeof(reg));
|
||
|
ret = i2c_master_send(client, write_buf, sizeof(write_buf));
|
||
|
if (ret < 0)
|
||
|
return ret;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int seesaw_read_data(struct i2c_client *client, struct seesaw_data *data)
|
||
|
{
|
||
|
__be16 adc_data;
|
||
|
__be32 read_buf;
|
||
|
int err;
|
||
|
|
||
|
err = seesaw_register_read(client, SEESAW_GPIO_BULK,
|
||
|
&read_buf, sizeof(read_buf));
|
||
|
if (err)
|
||
|
return err;
|
||
|
|
||
|
data->button_state = ~be32_to_cpu(read_buf);
|
||
|
|
||
|
err = seesaw_register_read(client,
|
||
|
SEESAW_ADC_BASE |
|
||
|
(SEESAW_ADC_OFFSET + SEESAW_ANALOG_X),
|
||
|
&adc_data, sizeof(adc_data));
|
||
|
if (err)
|
||
|
return err;
|
||
|
/*
|
||
|
* ADC reads left as max and right as 0, must be reversed since kernel
|
||
|
* expects reports in opposite order.
|
||
|
*/
|
||
|
data->x = SEESAW_JOYSTICK_MAX_AXIS - be16_to_cpu(adc_data);
|
||
|
|
||
|
err = seesaw_register_read(client,
|
||
|
SEESAW_ADC_BASE |
|
||
|
(SEESAW_ADC_OFFSET + SEESAW_ANALOG_Y),
|
||
|
&adc_data, sizeof(adc_data));
|
||
|
if (err)
|
||
|
return err;
|
||
|
|
||
|
data->y = be16_to_cpu(adc_data);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void seesaw_poll(struct input_dev *input)
|
||
|
{
|
||
|
struct seesaw_gamepad *private = input_get_drvdata(input);
|
||
|
struct seesaw_data data;
|
||
|
int err, i;
|
||
|
|
||
|
err = seesaw_read_data(private->i2c_client, &data);
|
||
|
if (err) {
|
||
|
dev_err_ratelimited(&input->dev,
|
||
|
"failed to read joystick state: %d\n", err);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
input_report_abs(input, ABS_X, data.x);
|
||
|
input_report_abs(input, ABS_Y, data.y);
|
||
|
|
||
|
for_each_set_bit(i, &SEESAW_BUTTON_MASK,
|
||
|
BITS_PER_TYPE(SEESAW_BUTTON_MASK)) {
|
||
|
if (!sparse_keymap_report_event(input, i,
|
||
|
data.button_state & BIT(i),
|
||
|
false))
|
||
|
dev_err_ratelimited(&input->dev,
|
||
|
"failed to report keymap event");
|
||
|
}
|
||
|
|
||
|
input_sync(input);
|
||
|
}
|
||
|
|
||
|
static int seesaw_probe(struct i2c_client *client)
|
||
|
{
|
||
|
struct seesaw_gamepad *seesaw;
|
||
|
u8 hardware_id;
|
||
|
int err;
|
||
|
|
||
|
err = seesaw_register_write_u8(client, SEESAW_STATUS_SWRST, 0xFF);
|
||
|
if (err)
|
||
|
return err;
|
||
|
|
||
|
/* Wait for the registers to reset before proceeding */
|
||
|
usleep_range(10000, 15000);
|
||
|
|
||
|
seesaw = devm_kzalloc(&client->dev, sizeof(*seesaw), GFP_KERNEL);
|
||
|
if (!seesaw)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
err = seesaw_register_read(client, SEESAW_STATUS_HW_ID,
|
||
|
&hardware_id, sizeof(hardware_id));
|
||
|
if (err)
|
||
|
return err;
|
||
|
|
||
|
dev_dbg(&client->dev, "Adafruit Seesaw Gamepad, Hardware ID: %02x\n",
|
||
|
hardware_id);
|
||
|
|
||
|
/* Set Pin Mode to input and enable pull-up resistors */
|
||
|
err = seesaw_register_write_u32(client, SEESAW_GPIO_DIRCLR_BULK,
|
||
|
SEESAW_BUTTON_MASK);
|
||
|
if (err)
|
||
|
return err;
|
||
|
err = seesaw_register_write_u32(client, SEESAW_GPIO_PULLENSET,
|
||
|
SEESAW_BUTTON_MASK);
|
||
|
if (err)
|
||
|
return err;
|
||
|
err = seesaw_register_write_u32(client, SEESAW_GPIO_BULK_SET,
|
||
|
SEESAW_BUTTON_MASK);
|
||
|
if (err)
|
||
|
return err;
|
||
|
|
||
|
seesaw->i2c_client = client;
|
||
|
seesaw->input_dev = devm_input_allocate_device(&client->dev);
|
||
|
if (!seesaw->input_dev)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
seesaw->input_dev->id.bustype = BUS_I2C;
|
||
|
seesaw->input_dev->name = "Adafruit Seesaw Gamepad";
|
||
|
seesaw->input_dev->phys = "i2c/" SEESAW_DEVICE_NAME;
|
||
|
input_set_drvdata(seesaw->input_dev, seesaw);
|
||
|
input_set_abs_params(seesaw->input_dev, ABS_X,
|
||
|
0, SEESAW_JOYSTICK_MAX_AXIS,
|
||
|
SEESAW_JOYSTICK_FUZZ, SEESAW_JOYSTICK_FLAT);
|
||
|
input_set_abs_params(seesaw->input_dev, ABS_Y,
|
||
|
0, SEESAW_JOYSTICK_MAX_AXIS,
|
||
|
SEESAW_JOYSTICK_FUZZ, SEESAW_JOYSTICK_FLAT);
|
||
|
|
||
|
err = sparse_keymap_setup(seesaw->input_dev, seesaw_buttons_new, NULL);
|
||
|
if (err) {
|
||
|
dev_err(&client->dev,
|
||
|
"failed to set up input device keymap: %d\n", err);
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
err = input_setup_polling(seesaw->input_dev, seesaw_poll);
|
||
|
if (err) {
|
||
|
dev_err(&client->dev, "failed to set up polling: %d\n", err);
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
input_set_poll_interval(seesaw->input_dev,
|
||
|
SEESAW_GAMEPAD_POLL_INTERVAL_MS);
|
||
|
input_set_max_poll_interval(seesaw->input_dev, SEESAW_GAMEPAD_POLL_MAX);
|
||
|
input_set_min_poll_interval(seesaw->input_dev, SEESAW_GAMEPAD_POLL_MIN);
|
||
|
|
||
|
err = input_register_device(seesaw->input_dev);
|
||
|
if (err) {
|
||
|
dev_err(&client->dev, "failed to register joystick: %d\n", err);
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static const struct i2c_device_id seesaw_id_table[] = {
|
||
|
{ SEESAW_DEVICE_NAME },
|
||
|
{ /* Sentinel */ }
|
||
|
};
|
||
|
MODULE_DEVICE_TABLE(i2c, seesaw_id_table);
|
||
|
|
||
|
static const struct of_device_id seesaw_of_table[] = {
|
||
|
{ .compatible = "adafruit,seesaw-gamepad"},
|
||
|
{ /* Sentinel */ }
|
||
|
};
|
||
|
MODULE_DEVICE_TABLE(of, seesaw_of_table);
|
||
|
|
||
|
static struct i2c_driver seesaw_driver = {
|
||
|
.driver = {
|
||
|
.name = SEESAW_DEVICE_NAME,
|
||
|
.of_match_table = seesaw_of_table,
|
||
|
},
|
||
|
.id_table = seesaw_id_table,
|
||
|
.probe = seesaw_probe,
|
||
|
};
|
||
|
module_i2c_driver(seesaw_driver);
|
||
|
|
||
|
MODULE_AUTHOR("Anshul Dalal <anshulusr@gmail.com>");
|
||
|
MODULE_DESCRIPTION("Adafruit Mini I2C Gamepad driver");
|
||
|
MODULE_LICENSE("GPL");
|