This is my first input driver, and my first attempt to istart the process of
upstreaming something, (other than a small patch). 
I apologize in advance :)

The Uniwest EVI front panel has 9 usable buttons
which correspond to keyboard keys, as well as
horizontal and verical slidersr,
(the sliders are like touch based scroll bars)
and 9 indicator LEDs

This initial version of the driver just supports the buttons.

Signed-off-by: Joshua Clayton <[email protected]>
---
 drivers/input/misc/Kconfig     |  10 ++
 drivers/input/misc/Makefile    |   1 +
 drivers/input/misc/evifpanel.c | 265 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 276 insertions(+)
 create mode 100644 drivers/input/misc/evifpanel.c

diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index 23297ab..87ce0e6 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -222,6 +222,16 @@ config INPUT_APANEL
         To compile this driver as a module, choose M here: the module will
         be called apanel.
 
+config INPUT_EVIFPANEL
+       tristate "Uniwest EVI Front panel"
+       select SERIO
+       help
+         Say Y if you have a Uniwest EVI and want to enable the
+         sliders and buttons on the front.
+
+         To compile this driver as a module, choose M here: the
+         module will be called evifpanel.
+
 config INPUT_GP2A
        tristate "Sharp GP2AP002A00F I2C Proximity/Opto sensor driver"
        depends on I2C
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index 19c7603..5764467 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -28,6 +28,7 @@ obj-$(CONFIG_INPUT_DA9055_ONKEY)      += da9055_onkey.o
 obj-$(CONFIG_INPUT_DM355EVM)           += dm355evm_keys.o
 obj-$(CONFIG_INPUT_DRV260X_HAPTICS)    += drv260x.o
 obj-$(CONFIG_INPUT_DRV2667_HAPTICS)    += drv2667.o
+obj-$(CONFIG_INPUT_EVIFPANEL)          += evifpanel.o
 obj-$(CONFIG_INPUT_GP2A)               += gp2ap002a00f.o
 obj-$(CONFIG_INPUT_GPIO_BEEPER)                += gpio-beeper.o
 obj-$(CONFIG_INPUT_GPIO_TILT_POLLED)   += gpio_tilt_polled.o
diff --git a/drivers/input/misc/evifpanel.c b/drivers/input/misc/evifpanel.c
new file mode 100644
index 0000000..aa28e6c
--- /dev/null
+++ b/drivers/input/misc/evifpanel.c
@@ -0,0 +1,265 @@
+/*
+ * Uniwest EVI Front Panel driver
+ *
+ * Copyright 2015 United Western Technologies
+ *
+ * Joshua Clayton <[email protected]>
+ *
+ * This file is subject to the terms and conditions of the GNU General
+ * Public License. See the file "COPYING" in the main directory of this
+ * archive for more details.
+ */
+#include <linux/input.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/serio.h>
+#include <linux/slab.h>
+
+#define DRIVER_DESC "Uniwest EVI Frontpanel input driver"
+MODULE_AUTHOR("Joshua Clayton <[email protected]>");
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+
+struct evifpanel {
+       struct input_dev *dev;
+       struct serio *serio;
+       unsigned int bytes;
+       char name[64];
+       char phys[32];
+       unsigned char buf[8];
+};
+
+struct key_map {
+       u16 type;
+       u16 code;
+       s32 value;
+       unsigned int byte;
+       unsigned int offset;
+};
+
+static struct key_map btns[] = {
+       { EV_KEY, KEY_F1, 1, 6, 0 },
+       { EV_KEY, KEY_D, 1, 6, 1 },
+       { EV_KEY, KEY_N, 1, 7, 0 },
+       { EV_KEY, KEY_BACKSPACE, 1, 7, 1 },
+       { EV_KEY, KEY_ENTER, 1, 7, 2 },
+       { EV_KEY, KEY_ESC, 1, 7, 3 },
+       { EV_KEY, KEY_F4, 1, 7, 4 },
+       { EV_KEY, KEY_F3, 1, 7, 5 },
+       { EV_KEY, KEY_F2, 1, 7, 6 },
+       { },
+};
+
+static void fp_check_key(struct evifpanel *fp, struct key_map *key)
+{
+       s32 value = fp->buf[key->byte] & BIT(key->offset);
+
+       input_report_key(fp->dev, key->code, value);
+}
+
+/*
+ * Check buttons against array of key_map
+ */
+static void fp_check_btns(struct evifpanel *fp, struct key_map *key)
+{
+       while (key->type) {
+               switch (key->type) {
+               case EV_KEY:
+                       fp_check_key(fp, key);
+                       break;
+               default:
+                       break; /* ignore unknown types */
+               }
+               key++;
+       }
+
+       input_sync(fp->dev);
+}
+
+/*
+ * Set the firmware version coming from the fp in an ascii file
+ */
+static void fp_set_fw_ver(struct evifpanel *fp)
+{
+       scnprintf(fp->serio->firmware_id, sizeof(fp->serio->firmware_id),
+                       "evifpanel v%3.3u.%3.3u.%3.3u.%3.3u", fp->buf[4],
+                       fp->buf[5], fp->buf[6], fp->buf[7]);
+
+       dev_info(&fp->serio->dev, "firmware found: %s\n",
+                       fp->serio->firmware_id);
+}
+
+/*
+ * Request firmware version info from the device
+ */
+static void fp_request_fw_ver(struct evifpanel *fp)
+{
+       serio_write(fp->serio, '\xC0');
+       serio_write(fp->serio, '\x00');
+       serio_write(fp->serio, '\x00');
+       serio_write(fp->serio, '\x09');
+       serio_write(fp->serio, '\x00');
+       serio_write(fp->serio, '\x01');
+       serio_write(fp->serio, '\x00');
+       serio_write(fp->serio, '\x00');
+}
+
+/*
+ * Send zero or more input events based on the state of the fp
+ * Call this when you have a full packet.
+ */
+static int fp_parse_buf(struct evifpanel *fp)
+{
+       switch (fp->buf[1]) {
+       case '\x03':
+               fp_check_btns(fp, btns);
+               break;
+       case '\x09':
+               if (fp->buf[4] || fp->buf[5])
+                       fp_set_fw_ver(fp);
+               break;
+       default:
+               dev_err(&fp->dev->dev, "Bad cmd id %X in sequence %llX\n",
+                               fp->buf[1], *(u64 *)(fp->buf));
+
+               return -EIO;
+       }
+
+       return 0;
+}
+
+static void fp_add_byte(struct evifpanel *fp, unsigned char c)
+{
+       if (c != '\xC0' && !fp->bytes) {
+               dev_err(&fp->dev->dev, "drop %X. looking for check byte\n", c);
+               return;
+       }
+
+       if (c == '\xC0' && fp->bytes) {
+               /* msg check byte should not be found in the middle of a set */
+               dev_warn(&fp->dev->dev, "discarding %d bytes from %llX\n",
+                        fp->bytes, *(u64 *)(fp->buf));
+               fp->bytes = 0;
+       }
+
+       fp->buf[fp->bytes] = c;
+       ++fp->bytes;
+}
+
+
+static irqreturn_t fp_interrupt(struct serio *serio, unsigned char data,
+               unsigned int flags)
+{
+       struct evifpanel *fp = serio_get_drvdata(serio);
+
+       fp_add_byte(fp, data);
+       if (fp->bytes == 8) {
+               fp_parse_buf(fp);
+               fp->bytes = 0;
+       }
+
+       return IRQ_HANDLED;
+}
+
+static void fp_set_device_attrs(struct evifpanel *fp)
+{
+       snprintf(fp->name, sizeof(fp->name),
+                       "EVI Frontpanel keypad and sliders");
+       snprintf(fp->phys, sizeof(fp->phys),
+                       "%s/input0", fp->serio->phys);
+
+       fp->dev->name = fp->name;
+       fp->dev->phys = fp->phys;
+       fp->dev->id.bustype = BUS_RS232;
+       fp->dev->dev.parent = &fp->serio->dev;
+
+       fp->dev->evbit[0] = BIT_MASK(EV_KEY);
+       __set_bit(KEY_D, fp->dev->keybit);
+       __set_bit(KEY_N, fp->dev->keybit);
+       __set_bit(KEY_BACKSPACE, fp->dev->keybit);
+       __set_bit(KEY_ENTER, fp->dev->keybit);
+       __set_bit(KEY_F1, fp->dev->keybit);
+       __set_bit(KEY_F2, fp->dev->keybit);
+       __set_bit(KEY_F3, fp->dev->keybit);
+       __set_bit(KEY_F4, fp->dev->keybit);
+       __set_bit(KEY_ESC, fp->dev->keybit);
+}
+
+static int fp_connect(struct serio *serio, struct serio_driver *drv)
+{
+       struct evifpanel *fp;
+       struct input_dev *input_dev;
+       int error = -ENOMEM;
+
+       fp = kzalloc(sizeof(struct evifpanel), GFP_KERNEL);
+
+       input_dev = input_allocate_device();
+       if (!input_dev || !fp) {
+               pr_err("evifpanel: failed to get memory\n");
+               goto fail1;
+       }
+
+       fp->dev = input_dev;
+       fp->serio = serio;
+       fp_set_device_attrs(fp);
+       serio_set_drvdata(serio, fp);
+
+       error = serio_open(serio, drv);
+       if (error) {
+               pr_err("evifpanel: failed to open serio\n");
+               goto fail2;
+       }
+       fp_request_fw_ver(fp);
+
+       error = input_register_device(input_dev);
+       if (error) {
+               pr_err("evifpanel: failed to register input device\n");
+               goto fail3;
+       }
+
+       return 0;
+
+fail3:
+       serio_close(serio);
+fail2:
+       serio_set_drvdata(serio, NULL);
+fail1:
+       input_free_device(input_dev);
+       kfree(fp);
+       pr_err("fp_connect: FAILED\n");
+
+       return error;
+}
+
+static void fp_disconnect(struct serio *serio)
+{
+       struct evifpanel *fp = serio_get_drvdata(serio);
+
+       input_unregister_device(fp->dev);
+       serio_close(serio);
+       serio_set_drvdata(serio, NULL);
+       kfree(fp);
+};
+
+static struct serio_device_id fp_ids[] = {
+       {
+               .type  = SERIO_RS232,
+               .proto = SERIO_ANY,
+               .id    = SERIO_ANY,
+               .extra = SERIO_ANY,
+       },
+       { 0 }
+};
+
+static struct serio_driver fp_drv = {
+       .driver = {
+               .name = "evifpanel",
+       },
+       .description = DRIVER_DESC,
+       .id_table    = fp_ids,
+       .connect     = fp_connect,
+       .interrupt   = fp_interrupt,
+       .disconnect  = fp_disconnect,
+};
+
+module_serio_driver(fp_drv);
-- 
2.1.4

--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to