331 lines
		
	
	
		
			8.2 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			331 lines
		
	
	
		
			8.2 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 <linux/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 u32 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;
 | |
| 	u32 button_state;
 | |
| };
 | |
| 
 | |
| 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 int seesaw_open(struct input_dev *input)
 | |
| {
 | |
| 	struct seesaw_gamepad *private = input_get_drvdata(input);
 | |
| 
 | |
| 	private->button_state = 0;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static void seesaw_poll(struct input_dev *input)
 | |
| {
 | |
| 	struct seesaw_gamepad *private = input_get_drvdata(input);
 | |
| 	struct seesaw_data data;
 | |
| 	unsigned long changed;
 | |
| 	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);
 | |
| 
 | |
| 	data.button_state &= SEESAW_BUTTON_MASK;
 | |
| 	changed = private->button_state ^ data.button_state;
 | |
| 	private->button_state = data.button_state;
 | |
| 
 | |
| 	for_each_set_bit(i, &changed, fls(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;
 | |
| 	seesaw->input_dev->open = seesaw_open;
 | |
| 	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");
 |