Skip to content

Instantly share code, notes, and snippets.

@jnewc
Last active May 8, 2021 03:17
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save jnewc/f8b668c41d7d4a68f6e46f46e8c559c2 to your computer and use it in GitHub Desktop.
Save jnewc/f8b668c41d7d4a68f6e46f46e8c559c2 to your computer and use it in GitHub Desktop.
i2c analog stick driver
/*
* 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