[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <20170717161914.5043-3-jiri@resnulli.us>
Date: Mon, 17 Jul 2017 18:19:14 +0200
From: Jiri Pirko <jiri@...nulli.us>
To: linux-kernel@...r.kernel.org
Cc: gregkh@...uxfoundation.org, davem@...emloft.net,
mchehab@...nel.org, jikos@...nel.org
Subject: [patch 2/2] actuator: introduce Linak USB2LIN cable support
Benefit from the previously introduced actuator infrastructure and
introduce the first actuator device driver. This driver controls Linak
table moving actuators over USB2LIN usb cable.
Signed-off-by: Jiri Pirko <jiri@...nulli.us>
---
drivers/actuator/Kconfig | 11 ++
drivers/actuator/Makefile | 1 +
drivers/actuator/usb2lin.c | 433 +++++++++++++++++++++++++++++++++++++++++++++
drivers/hid/hid-core.c | 1 +
drivers/hid/hid-ids.h | 3 +
5 files changed, 449 insertions(+)
create mode 100644 drivers/actuator/usb2lin.c
diff --git a/drivers/actuator/Kconfig b/drivers/actuator/Kconfig
index 6deac93..43150d8 100644
--- a/drivers/actuator/Kconfig
+++ b/drivers/actuator/Kconfig
@@ -12,4 +12,15 @@ menuconfig ACTUATOR
if ACTUATOR
+menuconfig ACTUATOR_USB2LIN
+ tristate "Linak USB2LIN cable support"
+ depends on USB
+ ---help---
+ This provides a support for Linak USB2LIN devices.
+
+ If you want this support, you should say Y here.
+
+ This support can also be built as a module. If so, the module
+ will be called usb2lin.
+
endif
diff --git a/drivers/actuator/Makefile b/drivers/actuator/Makefile
index c45af36..743dc18 100644
--- a/drivers/actuator/Makefile
+++ b/drivers/actuator/Makefile
@@ -1 +1,2 @@
obj-$(CONFIG_ACTUATOR) += actuator.o
+obj-$(CONFIG_ACTUATOR_USB2LIN) += usb2lin.o
diff --git a/drivers/actuator/usb2lin.c b/drivers/actuator/usb2lin.c
new file mode 100644
index 0000000..a39a994
--- /dev/null
+++ b/drivers/actuator/usb2lin.c
@@ -0,0 +1,433 @@
+/*
+ * drivers/actuator/usb2lin.c - Linak USB2LIN cable support
+ * Copyright (c) 2016-2017 Jiri Pirko <jiri@...nulli.us>
+ *
+ * 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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <linux/workqueue.h>
+#include <linux/usb.h>
+#include <linux/actuator.h>
+#include "../../drivers/hid/hid-ids.h"
+
+static const char u2l_driver_name[] = "usb2lin";
+
+static const struct usb_device_id u2l_id_table[] = {
+ { USB_DEVICE(USB_VENDOR_ID_LINAK, USB_DEVICE_ID_LINAK_USB2LIN) },
+ { },
+};
+
+struct u2l_status {
+ __le16 header;
+ __le16 move_status;
+#define U2L_HEIGHT_MULTIPLIER 100
+ __le16 height;
+#define U2L_STATUS_MOVE_FLAGS_BLOCKED BIT(1)
+#define U2L_STATUS_MOVE_FLAGS_DIR BIT(7)
+ u8 move_flags;
+ u8 move_magic; /* not 0 when moving :O */
+ u8 unknown1[12];
+#define U2L_STATUS_TARGET_HEIGHT_MAGIC_STOPPED 0x8001
+ __le16 target_height;
+ u8 unknown2[42];
+} __packed;
+
+struct u2l_move {
+ u8 header;
+#define U2L_MOVE_TARGET_HEIGHT_MAGIC_UP 0x8000
+#define U2L_MOVE_TARGET_HEIGHT_MAGIC_DOWN 0x7fff
+ __le16 target_height[4]; /* unaligned access */
+ u8 pad[59]; /* zero padding */
+} __packed;
+
+struct u2l_init {
+ __le32 header;
+ u8 pad[60]; /* zero padding */
+} __packed;
+
+struct u2l {
+ struct mutex lock;
+ struct actuator *act;
+ struct usb_device *udev;
+ struct u2l_status last_status;
+ struct u2l_init init;
+ struct delayed_work dw;
+ struct {
+ bool in_progress;
+ bool started;
+ unsigned long start_jiffies;
+ u16 target_height;
+ } move;
+ union {
+ struct u2l_move status;
+ struct u2l_move move;
+ } msg;
+};
+
+struct u2l_usbmsg_info {
+ u8 request;
+ u8 requesttype;
+ u16 value;
+};
+
+enum u2l_usbmsg_type {
+ U2L_USBMSG_HELLO,
+ U2L_USBMSG_INIT,
+ U2L_USBMSG_STATUS,
+ U2L_USBMSG_MOVE,
+};
+
+#define U2L_USBMSG_REQUEST_TYPE_IN 0xa1
+#define U2L_USBMSG_REQUEST_TYPE_OUT 0x21
+
+static const struct u2l_usbmsg_info u2l_ubsmsg_infos[] = {
+ [U2L_USBMSG_HELLO] = {
+ .request = 0x0a,
+ .requesttype = U2L_USBMSG_REQUEST_TYPE_OUT,
+ },
+ [U2L_USBMSG_INIT] = {
+ .request = 0x09,
+ .requesttype = U2L_USBMSG_REQUEST_TYPE_OUT,
+ .value = 0x0303,
+ },
+ [U2L_USBMSG_STATUS] = {
+ .request = 0x01,
+ .requesttype = U2L_USBMSG_REQUEST_TYPE_IN,
+ .value = 0x0304,
+ },
+ [U2L_USBMSG_MOVE] = {
+ .request = 0x09,
+ .requesttype = U2L_USBMSG_REQUEST_TYPE_OUT,
+ .value = 0x0305,
+ },
+};
+
+#define U2L_USB_CONTROL_MSG_TIMEOUT 300
+
+static int u2l_usb_control_msg(struct u2l *u2l, enum u2l_usbmsg_type type,
+ void *data, u16 size)
+{
+ const struct u2l_usbmsg_info *info = &u2l_ubsmsg_infos[type];
+ unsigned int pipe;
+ int err;
+
+ pipe = info->requesttype == U2L_USBMSG_REQUEST_TYPE_IN ?
+ usb_rcvctrlpipe(u2l->udev, 0) :
+ usb_sndctrlpipe(u2l->udev, 0);
+
+ err = usb_control_msg(u2l->udev, pipe, info->request, info->requesttype,
+ info->value, 0, data, size,
+ U2L_USB_CONTROL_MSG_TIMEOUT);
+ return err < 0 ? err : 0;
+}
+
+static int u2l_cmd_hello(struct u2l *u2l)
+{
+ return u2l_usb_control_msg(u2l, U2L_USBMSG_HELLO, NULL, 0);
+}
+
+#define U2L_USBMSG_INIT_HEADER_MAGIC 0xfb000403
+
+static int u2l_cmd_init(struct u2l *u2l)
+{
+ u2l->init.header = cpu_to_le32(U2L_USBMSG_INIT_HEADER_MAGIC);
+ return u2l_usb_control_msg(u2l, U2L_USBMSG_INIT,
+ &u2l->init, sizeof(u2l->init));
+}
+
+static int u2l_cmd_status(struct u2l *u2l, struct u2l_status *status)
+{
+ int err;
+
+ err = u2l_usb_control_msg(u2l, U2L_USBMSG_STATUS,
+ &u2l->msg.status, sizeof(*status));
+ if (err)
+ return err;
+ memcpy(status, &u2l->msg.status, sizeof(*status));
+ dev_dbg(&u2l->udev->dev, "move_status %04x, height %04x (%u), move_flags %02x (%s%s), move_magic %02x, target_height %04x (%u)\n",
+ status->move_status, status->height, status->height,
+ status->move_flags,
+ status->move_flags & U2L_STATUS_MOVE_FLAGS_BLOCKED ?
+ "blocked " : "",
+ status->move_flags & U2L_STATUS_MOVE_FLAGS_DIR ?
+ "down" : "up",
+ status->move_magic,
+ status->target_height, status->target_height);
+ return 0;
+}
+
+static void u2l_cmd_last_status_save(struct u2l *u2l, struct u2l_status *status)
+{
+ memcpy(&u2l->last_status, status, sizeof(u2l->last_status));
+}
+
+#define U2L_USBMSG_MOVE_HEADER_MAGIC 0x05
+
+static int u2l_cmd_move(struct u2l *u2l, u16 target_height)
+{
+ __le16 tmp;
+ int i;
+
+ u2l->msg.move.header = U2L_USBMSG_MOVE_HEADER_MAGIC;
+ tmp = cpu_to_le16(target_height);
+ for (i = 0; i < 4; i++)
+ memcpy(&u2l->msg.move.target_height[i], &tmp, sizeof(tmp));
+
+ return u2l_usb_control_msg(u2l, U2L_USBMSG_MOVE,
+ &u2l->msg.move, sizeof(u2l->move));
+}
+
+static void u2l_actuator_status_fill(struct u2l *u2l,
+ struct actuator_status *act_status,
+ struct u2l_status *status)
+{
+ act_status->value = le16_to_cpu(status->height) * U2L_HEIGHT_MULTIPLIER;
+ if (!status->move_magic) {
+ act_status->move_state = ACTUATOR_MOVE_STATE_STOPPED;
+ } else {
+ act_status->move_state =
+ status->move_flags & U2L_STATUS_MOVE_FLAGS_DIR ?
+ ACTUATOR_MOVE_STATE_NEGATIVE :
+ ACTUATOR_MOVE_STATE_POSITIVE;
+ }
+}
+
+static int u2l_actuator_status(struct actuator *act,
+ struct actuator_status *act_status)
+{
+ struct u2l *u2l = actuator_priv(act);
+ struct u2l_status status;
+ int err;
+
+ mutex_lock(&u2l->lock);
+ err = u2l_cmd_status(u2l, &status);
+ mutex_unlock(&u2l->lock);
+ if (err)
+ return err;
+ u2l_actuator_status_fill(u2l, act_status, &status);
+ return 0;
+}
+
+#define U2L_MOVE_START_DELAY 500 /* ms */
+
+static void u2l_check_move(struct u2l *u2l, struct u2l_status *status)
+{
+ if (!u2l->move.in_progress)
+ return;
+
+ if (!u2l->move.started) {
+ if (status->move_magic)
+ u2l->move.started = true;
+ else if (jiffies_to_msecs(jiffies - u2l->move.start_jiffies) >
+ U2L_MOVE_START_DELAY) {
+ goto finish; /* Move start timed-out */
+ }
+ } else if (!status->move_magic) {
+ goto finish; /* We are at the destination or limit */
+ }
+
+ u2l_cmd_move(u2l, u2l->move.target_height);
+ return;
+finish:
+ u2l->move.in_progress = false;
+}
+
+static void u2l_check_notify(struct u2l *u2l, struct u2l_status *status)
+{
+ struct actuator_status act_status;
+
+ if (!!status->move_magic != !!u2l->last_status.move_magic) {
+ u2l_actuator_status_fill(u2l, &act_status, status);
+ actuator_notify_status(u2l->act, &act_status);
+ }
+}
+
+#define U2L_WORK_DELAY 100 /* ms */
+
+static void u2l_schedule_work(struct u2l *u2l)
+{
+ schedule_delayed_work(&u2l->dw, U2L_WORK_DELAY);
+}
+
+static void u2l_work(struct work_struct *work)
+{
+ struct u2l_status status;
+ struct u2l *u2l;
+ int err;
+
+ u2l = container_of(work, struct u2l, dw.work);
+
+ mutex_lock(&u2l->lock);
+ err = u2l_cmd_status(u2l, &status);
+ if (err)
+ goto unlock;
+ u2l_check_move(u2l, &status);
+ u2l_check_notify(u2l, &status);
+ u2l_cmd_last_status_save(u2l, &status);
+ u2l_schedule_work(u2l);
+unlock:
+ mutex_unlock(&u2l->lock);
+}
+
+static int u2l_actuator_move(struct actuator *act, u32 value)
+{
+ struct u2l *u2l = actuator_priv(act);
+ int err;
+
+ value /= U2L_HEIGHT_MULTIPLIER;
+ if (value < 0 || value > 0xFFFF)
+ return -EINVAL;
+
+ mutex_lock(&u2l->lock);
+
+ u2l->move.in_progress = true;
+ u2l->move.started = false;
+ u2l->move.start_jiffies = jiffies;
+ u2l->move.target_height = value;
+
+ /* Do move cmd twice, first one would wake up the control in case
+ * it sleeps. Unfortunately there is no known way to find out.
+ */
+ err = u2l_cmd_move(u2l, u2l->move.target_height);
+ if (err)
+ goto unlock;
+ err = u2l_cmd_move(u2l, u2l->move.target_height);
+ if (err)
+ goto unlock;
+
+unlock:
+ mutex_unlock(&u2l->lock);
+ return err;
+}
+
+static int u2l_actuator_stop(struct actuator *act)
+{
+ struct u2l *u2l = actuator_priv(act);
+ struct u2l_status status;
+ u16 wakeup_target_height;
+ int err;
+
+ mutex_lock(&u2l->lock);
+
+ err = u2l_cmd_status(u2l, &status);
+ if (err)
+ goto unlock;
+ if (le16_to_cpu(status.target_height) ==
+ U2L_STATUS_TARGET_HEIGHT_MAGIC_STOPPED)
+ goto unlock;
+
+ wakeup_target_height =
+ status.move_flags & U2L_STATUS_MOVE_FLAGS_DIR ?
+ U2L_MOVE_TARGET_HEIGHT_MAGIC_UP :
+ U2L_MOVE_TARGET_HEIGHT_MAGIC_DOWN;
+
+ err = u2l_cmd_move(u2l, wakeup_target_height);
+
+ u2l->move.in_progress = false;
+
+unlock:
+ mutex_unlock(&u2l->lock);
+ return err;
+}
+
+static int u2l_init(struct u2l *u2l)
+{
+ struct u2l_status status;
+ u16 current_height;
+ int err;
+
+ err = u2l_cmd_hello(u2l);
+ if (err)
+ return err;
+
+ err = u2l_cmd_status(u2l, &status);
+ if (err)
+ return err;
+ current_height = le16_to_cpu(status.height);
+ u2l_cmd_last_status_save(u2l, &status);
+
+ err = u2l_cmd_init(u2l);
+ if (err)
+ return err;
+
+ return u2l_cmd_move(u2l, current_height);
+}
+
+static const struct actuator_ops u2l_actuator_ops = {
+ .priv_size = sizeof(struct u2l),
+ .driver_name = u2l_driver_name,
+ .type = ACTUATOR_TYPE_LINEAR,
+ .units = ACTUATOR_UNITS_UM,
+ .status = u2l_actuator_status,
+ .move = u2l_actuator_move,
+ .stop = u2l_actuator_stop,
+};
+
+static int u2l_probe(struct usb_interface *interface,
+ const struct usb_device_id *id)
+{
+ struct usb_device *udev = interface_to_usbdev(interface);
+ struct actuator *act;
+ struct u2l *u2l;
+ int err;
+
+ act = actuator_alloc(&u2l_actuator_ops);
+ if (!act)
+ return -ENOMEM;
+ u2l = actuator_priv(act);
+ u2l->act = act;
+ u2l->udev = udev;
+ mutex_init(&u2l->lock);
+ INIT_DELAYED_WORK(&u2l->dw, u2l_work);
+
+ err = u2l_init(u2l);
+ if (err) {
+ dev_err(&u2l->udev->dev, "Initialization failed\n");
+ goto err_init;
+ }
+
+ err = actuator_register(act, &udev->dev, 0);
+ if (err)
+ goto err_actuator_register;
+
+ usb_set_intfdata(interface, act);
+ u2l_schedule_work(u2l);
+ return 0;
+
+err_actuator_register:
+err_init:
+ actuator_free(act);
+ return err;
+}
+
+static void u2l_disconnect(struct usb_interface *interface)
+{
+ struct actuator *act = usb_get_intfdata(interface);
+ struct u2l *u2l = actuator_priv(act);
+
+ cancel_delayed_work_sync(&u2l->dw);
+ actuator_unregister(act);
+ actuator_free(act);
+}
+
+static struct usb_driver u2l_driver = {
+ .name = u2l_driver_name,
+ .probe = u2l_probe,
+ .disconnect = u2l_disconnect,
+ .id_table = u2l_id_table,
+};
+
+module_usb_driver(u2l_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Jiri Pirko <jiri@...nulli.us>");
+MODULE_DESCRIPTION("Linak USB2LIN cable support");
+MODULE_DEVICE_TABLE(usb, u2l_id_table);
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index 6fd01a6..3311a96 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -2740,6 +2740,7 @@ static const struct hid_device_id hid_ignore_list[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MCT) },
{ HID_USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_HYBRID) },
{ HID_USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_HEATCONTROL) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_LINAK, USB_DEVICE_ID_LINAK_USB2LIN) },
{ HID_USB_DEVICE(USB_VENDOR_ID_MADCATZ, USB_DEVICE_ID_MADCATZ_BEATPAD) },
{ HID_USB_DEVICE(USB_VENDOR_ID_MCC, USB_DEVICE_ID_MCC_PMD1024LS) },
{ HID_USB_DEVICE(USB_VENDOR_ID_MCC, USB_DEVICE_ID_MCC_PMD1208LS) },
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 3d911bf..0650fe9 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -663,6 +663,9 @@
#define USB_DEVICE_ID_LG_MULTITOUCH 0x0064
#define USB_DEVICE_ID_LG_MELFAS_MT 0x6007
+#define USB_VENDOR_ID_LINAK 0x12d3
+#define USB_DEVICE_ID_LINAK_USB2LIN 0x0002
+
#define USB_VENDOR_ID_LOGITECH 0x046d
#define USB_DEVICE_ID_LOGITECH_AUDIOHUB 0x0a0e
#define USB_DEVICE_ID_LOGITECH_T651 0xb00c
--
2.9.3
Powered by blists - more mailing lists