Last active
September 30, 2024 02:49
-
-
Save jnewc/f8b668c41d7d4a68f6e46f46e8c559c2 to your computer and use it in GitHub Desktop.
i2c analog stick driver
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* i2c_test.c | |
*/ | |
#include <linux/slab.h> /* kzalloc */ | |
#include <linux/module.h> /* Needed by all modules */ | |
#include <linux/kernel.h> /* KERN_INFO */ | |
#include <linux/timer.h> /* timer_list */ | |
#include <linux/workqueue.h> /* schedule_work */ | |
#include <linux/input.h> | |
#include <linux/i2c.h> | |
#define REFRESH_RATE_MSECS 20 | |
MODULE_LICENSE("GPL"); | |
MODULE_AUTHOR("Jack Newcombe"); | |
MODULE_DESCRIPTION("Manages a single i2c analog stick using a pcf8591 ADC"); | |
MODULE_VERSION("0.1"); | |
struct pad_state { | |
signed int primary_x; | |
signed int primary_y; | |
}; | |
struct pad_data { | |
struct input_dev *device; | |
struct pad_state *pad_state; | |
}; | |
struct pad_data* pad_data; | |
/********************************************************************** | |
* i2c setup | |
**********************************************************************/ | |
struct i2c_adapter* i2c_dev; | |
struct i2c_client* i2c_client; | |
static struct timer_list i2c_timer; | |
static struct i2c_board_info __initdata board_info[] = { | |
{ | |
I2C_BOARD_INFO("pcf8591", 0x48), | |
} | |
}; | |
static s32 i2c_read_byte(s32 register_address) | |
{ | |
// Select channel to read from | |
i2c_smbus_write_byte(i2c_client, 0x40 | register_address); | |
i2c_smbus_read_byte(i2c_client); // Flush first byte | |
return i2c_smbus_read_byte(i2c_client); | |
} | |
static void i2c_work_handler(struct work_struct* work) | |
{ | |
//struct pad_state* new_pad_state; | |
//new_pad_state = { | |
//.primary_x = value, | |
//.primary_y = 0 | |
//}; | |
//pad_data->pad_state = new_pad_state | |
//printk(KERN_INFO "Got value %d", value); | |
input_report_abs(pad_data->device, ABS_X, i2c_read_byte(0x1)); | |
input_report_abs(pad_data->device, ABS_Y, i2c_read_byte(0x2)); | |
input_sync(pad_data->device); | |
} | |
DECLARE_WORK(i2c_work, i2c_work_handler); | |
static void i2c_timer_callback( unsigned long data ) | |
{ | |
schedule_work(&i2c_work); | |
mod_timer(&i2c_timer, jiffies + msecs_to_jiffies(REFRESH_RATE_MSECS) ); | |
} | |
/********************************************************************** | |
* Device setup | |
**********************************************************************/ | |
static void setup_device_axis(struct input_dev* device, int axis) | |
{ | |
input_set_abs_params(device, axis, 0, 255, 0, 0); | |
} | |
static int setup_device(void) | |
{ | |
int result = 0; | |
// Allocate memory to store our pad data | |
pad_data = kzalloc(sizeof (struct pad_data), GFP_KERNEL); | |
// Create the device | |
pad_data->device = input_allocate_device(); | |
if (!pad_data->device) { | |
printk(KERN_ERR "enough memory\n"); | |
return -1; | |
} | |
// Setup device description | |
pad_data->device->name = "PadPadPad"; | |
pad_data->device->phys = "PadPadPad_phys"; | |
pad_data->device->uniq = "PadPadPad_uniq"; | |
pad_data->device->evbit[0] = BIT_MASK(EV_ABS); | |
// Setup analog sticks | |
setup_device_axis(pad_data->device, ABS_X); // Primary X | |
setup_device_axis(pad_data->device, ABS_Y); // Primary Y | |
result = input_register_device(pad_data->device); | |
if (result < 0) { | |
printk(KERN_ERR "Failed to register device\n"); | |
} | |
return result; | |
} | |
/********************************************************************** | |
* Driver lifecycle | |
**********************************************************************/ | |
int i2c_test_init(void) | |
{ | |
s32 value; | |
int result = 0; | |
printk(KERN_INFO "loading i2c driver v0.1.\n"); | |
result = setup_device(); | |
if(result < 0) { | |
printk(KERN_INFO "FAIL: Could not setup device. Result: %d", result); | |
input_free_device(pad_data->device); | |
goto finish; | |
} | |
// Setup device | |
i2c_dev = i2c_get_adapter(1); | |
i2c_client = i2c_new_device(i2c_dev, board_info); | |
// Setup timer | |
setup_timer(&i2c_timer, i2c_timer_callback, 0); | |
result = mod_timer(&i2c_timer, jiffies + msecs_to_jiffies(REFRESH_RATE_MSECS) ); | |
if(result < 0) { | |
printk(KERN_INFO "FAIL: Timer not setup. Result: %d", result); | |
goto finish; | |
} | |
// Initialise with control call to set mode as output | |
value = i2c_smbus_write_byte_data(i2c_client, 0x40, 0); | |
printk(KERN_INFO "Write control, result: %d\n", value); | |
value = i2c_smbus_read_byte(i2c_client); // Flush first byte | |
printk(KERN_INFO "Flush value: %d\n", value); | |
/* | |
* A non 0 return means init_module failed; module can't be loaded. | |
*/ | |
finish: | |
return result; | |
} | |
void i2c_test_exit(void) | |
{ | |
printk(KERN_INFO "Goodbye world\n"); | |
input_unregister_device(pad_data->device); | |
input_free_device(pad_data->device); | |
i2c_unregister_device(i2c_client); | |
del_timer( &i2c_timer ); | |
kzfree(pad_data); | |
} | |
module_init(i2c_test_init); | |
module_exit(i2c_test_exit); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment