BigW Consortium Gitlab

Commit 222ac6b2 by Forest Godfrey
parents 6d270014 258f589f
This diff is collapsed. Click to expand it.
Arduino sketch and evdev driver for Linux to implement a keyboard over I2C with an interrupt line.
# License
Copyright (C) 2018 Forest Godfrey
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*.ko
*.cmd
.tmp_versions
*.mod.c
.cache.mk
modules.order
Module.symvers
This kernel module is meant to work with the Arduino sketch in the directory above.
The Arduino sketch operates with a set of i2c registers and generates GPIO pin interrupts.
This module has only been tested with a Sierra Wireless WP7702 module, but should work fine
anywhere. It uses Platform Data instead of Device Tree Overlays. This is done because the
i2c bus used in the Sierra Wireless mangOH that this was tested with is off of an I2C switch
and they chose not to the Device Tree.
# License
Copyright (C) 2018 Forest Godfrey
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
/*
* This file is part of the Arduino I2C keyboard.
* Copyright (C) 2018 Forest Godfrey <fgodfrey@bigw.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _AI2C_H
#define _AI2C_H
typedef struct arduino_i2c_driver_data_s {
struct list_head driver_list;
struct work_struct intr_upper_work;
struct i2c_client *client;
struct input_dev *indev;
int irq_num;
int irq_pin;
int irq_count;
int numkeys;
int *keymap;
} arduino_i2c_driver_data_t;
#endif /* _AI2C_H */
/*
* This file is part of the Arduino I2C keyboard.
* Copyright (C) 2018 Forest Godfrey <fgodfrey@bigw.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _AI2C_PLATFORM_DATA_H
#define _AI2C_PLATFORM_DATA_H
typedef struct arduino_i2c_keyboard_platform_data {
int gpio_intr_pin;
int *keymap;
} arduino_i2c_keyboard_platform_data_t;
#endif /* _AI2C_PLATFORM_DATA_H */
/*
* Arduino I2C Keyboard
* This kernel module provides evdev events for keys controlled by an I2C device. An example
* compatible device is provided as an Arduino sketch in the directory above.
*
* Copyright (C) 2018 Forest Godfrey <fgodfrey@bigw.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/*
* Kernel Includes
*/
#include <linux/module.h>
#include <linux/gpio.h>
#include <linux/i2c.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
/*
* Project Includes
*/
#include "ai2c.h"
#include "ai2c_platform_data.h"
#define IRQ_GPIO_PIN 22
#define IRQ_GPIO_PIN 9
#define GPIO_IRQ_NAME "ai2c_keyboard_irq"
static int irq_num = -1;
static int count = 0;
const int default_keymap[] = {
0,
};
static LIST_HEAD(driver_list);
static DEFINE_MUTEX(driver_list_lock);
static void
ai2c_intr(struct work_struct *work) {
arduino_i2c_driver_data_t *drv_info = container_of(
work, arduino_i2c_driver_data_t, intr_upper_work);
BUG_ON(!drv_info->client);
dev_info(&drv_info->client->dev, "ai2c_intr: entered\n");
i2c_smbus_write_byte_data(drv_info->client, 50, 48);
input_report_key(drv_info->indev, BTN_0, 1);
input_sync(drv_info->indev);
input_report_key(drv_info->indev, BTN_0, 0);
input_sync(drv_info->indev);
return;
}
static irqreturn_t
ai2c_intr_lower(int irq, void *data) {
if (count++ < 10) {
arduino_i2c_driver_data_t *drv_info = (arduino_i2c_driver_data_t *)data;
BUG_ON(!drv_info);
if (drv_info->irq_count++ < 10) {
printk("ai2c_intr_lower: entered\n");
}
schedule_work(&drv_info->intr_upper_work);
return IRQ_HANDLED;
}
int
ai2c_init(void) {
int ret;
static int
ai2c_probe(struct i2c_client *client, const struct i2c_device_id *id) {
int ret = 0;
int flags = 0;
void *drv_info = NULL;
const arduino_i2c_keyboard_platform_data_t *pdata =
dev_get_platdata(&client->dev);
arduino_i2c_driver_data_t *drv_info = NULL;
printk(KERN_INFO "Arduino I2C Keyboard Starting\n");
ret = gpio_request(IRQ_GPIO_PIN, GPIO_IRQ_NAME);
dev_info(&client->dev, "Arduino I2C Keyboard Probing Device");
if (!pdata) {
dev_err(&client->dev, "No platform present - failing");
ret = -EINVAL;
goto out;
}
drv_info = (arduino_i2c_driver_data_t *)kmalloc(sizeof(arduino_i2c_driver_data_t), GFP_KERNEL);
if (!drv_info) {
dev_err(&client->dev, "Unable to allocate driver memory");
ret = -ENOMEM;
goto out;
}
memset(drv_info, 0, sizeof(arduino_i2c_driver_data_t));
ret = gpio_request(pdata->gpio_intr_pin, GPIO_IRQ_NAME);
if (ret < 0) {
printk(KERN_ERR "ai2c: IRQ pin reservation failed: error %d\n", ret);
return ret;
dev_err(&client->dev, "ai2c: IRQ pin reservation of %d failed: error %d", pdata->gpio_intr_pin, ret);
goto out_dealloc;
}
gpio_direction_input(IRQ_GPIO_PIN);
irq_num = gpio_to_irq(IRQ_GPIO_PIN);
gpio_direction_input(pdata->gpio_intr_pin);
gpio_set_debounce(pdata->gpio_intr_pin, 50); // 50ms
#ifndef CONFIG_SIERRA
/* GPIO exports are broken on Sierra Wireless Linux due to their weird gpiolib mods */
gpio_export(pdata->gpio_intr_pin, "arduino_i2c_intr_pin");
#endif /* CONFIG_SIERRA */
drv_info->irq_pin = pdata->gpio_intr_pin;
drv_info->irq_num = gpio_to_irq(pdata->gpio_intr_pin);
printk(KERN_INFO "ai2c: Reserved pin [%d]: irq = %d\n", IRQ_GPIO_PIN, irq_num);
dev_dbg(&client->dev, "ai2c: Reserved pin [%d]: irq = %d\n", pdata->gpio_intr_pin, drv_info->irq_num);
ret = request_irq(irq_num, ai2c_intr_lower, flags, "ai2c_keyboard", drv_info);
flags |= IRQF_TRIGGER_FALLING;
ret = request_irq(drv_info->irq_num, ai2c_intr_lower, flags, "ai2c_keyboard", drv_info);
if (ret < 0) {
printk(KERN_ERR "ai2c: request_irq failed: error %d\n", ret);
return ret;
dev_err(&client->dev, "ai2c: request_irq failed: error %d", ret);
goto out_cleanup;
}
irq_set_irq_type(irq_num, IRQ_TYPE_EDGE_FALLING);
drv_info->indev = devm_input_allocate_device(&client->dev);
if (!drv_info->indev) {
ret = -ENOMEM;
dev_err(&client->dev, "ai2c: unable to allocate input device");
goto out_cleanirq;
}
drv_info->indev->name = "arduino_i2c";
drv_info->indev->phys = "arduino_i2c/input0";
drv_info->indev->dev.parent = &client->dev;
drv_info->indev->id.bustype = BUS_I2C;
drv_info->indev->id.vendor = 0x4242;
drv_info->indev->id.product = 0x4242;
drv_info->indev->id.version = 0x0001;
drv_info->indev->keycodesize = sizeof(unsigned short);
drv_info->indev->keycodemax = BTN_0 + 1;
drv_info->indev->evbit[0] = BIT_MASK(EV_KEY);
drv_info->indev->keybit[BIT_WORD(BTN_0)] = BIT_MASK(BTN_0);
ret = input_register_device(drv_info->indev);
if (ret) {
dev_err(&client->dev, "ai2c: unable to register input device: %d", ret);
goto out_cleanindev;
}
irq_set_irq_type(drv_info->irq_num, IRQ_TYPE_EDGE_FALLING);
drv_info->client = client;
INIT_WORK(&drv_info->intr_upper_work, ai2c_intr);
dev_set_drvdata(&client->dev, drv_info);
INIT_LIST_HEAD(&drv_info->driver_list);
mutex_lock(&driver_list_lock);
list_add(&drv_info->driver_list, &driver_list);
mutex_unlock(&driver_list_lock);
return 0;
out_cleanindev:
input_free_device(drv_info->indev);
out_cleanirq:
free_irq(drv_info->irq_num, drv_info);
out_cleanup:
gpio_free(drv_info->irq_num);
out_dealloc:
kfree(drv_info);
out:
return ret;
}
static int
ai2c_remove(struct i2c_client *client) {
arduino_i2c_driver_data_t *drv_info = (arduino_i2c_driver_data_t *)dev_get_drvdata(&client->dev);
BUG_ON(!drv_info);
free_irq(drv_info->irq_num, drv_info);
flush_work(&drv_info->intr_upper_work);
gpio_free(drv_info->irq_num);
dev_set_drvdata(&drv_info->client->dev, NULL);
input_unregister_device(drv_info->indev);
mutex_lock(&driver_list_lock);
list_del(&drv_info->driver_list);
mutex_unlock(&driver_list_lock);
kfree(drv_info);
return 0;
}
/*
* Module initialization and parameters.
*/
static const struct i2c_device_id ai2c_id[] = {
{ "ai2c-keys", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, ai2c_id);
static struct i2c_driver ai2c_driver = {
.driver = {
.name = KBUILD_MODNAME,
},
.probe = ai2c_probe,
.remove = ai2c_remove,
.id_table = ai2c_id,
};
int
ai2c_init(void) {
int ret;
printk(KERN_INFO "Arduino I2C Keyboard Starting\n");
ret = i2c_add_driver(&ai2c_driver);
if (ret < 0) {
printk(KERN_ERR "Unable to register AI2C keyboard driver\n");
}
return ret;
}
void
ai2c_shutdown(void) {
printk(KERN_INFO "Arduino I2C Keyboard Shutting Down\n");
if (irq_num > 0) {
free_irq(irq_num, ai2c_intr_lower);
}
i2c_del_driver(&ai2c_driver);
}
module_init(ai2c_init);
module_exit(ai2c_shutdown);
MODULE_ALIAS("arduino_i2c");
MODULE_ALIAS("ai2c-keys");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Forest Godfrey <fgodfrey@bigw.org>");
MODULE_DESCRIPTION("evdev driver for i2c keyboard based on Arduino");
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment