The Yepkit YKUSH <https://www.yepkit.com/products/ykush> is a three-port
switchable USB hub. In addition the to actual hub device, an HID device is
exported (VID 0x04D8 PID 0x0042). By sending specific commands to the HID
device, it is possible to power up/down the three ports separately or all ports
together. Commands 0x01-0x03 is used to power down ports 1-3, while 0x0A is used
to power down all ports. In order to power up a port (or all ports), the command
is OR'd with 0x10.

Signed-off-by: Kristian Evensen <[email protected]>
---
 drivers/hid/hid-core.c    |   1 +
 drivers/hid/hid-ids.h     |   1 +
 drivers/usb/misc/Kconfig  |  11 +++
 drivers/usb/misc/Makefile |   1 +
 drivers/usb/misc/ykush.c  | 176 ++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 190 insertions(+)
 create mode 100644 drivers/usb/misc/ykush.c

diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index 8b63879..77afffc 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -2328,6 +2328,7 @@ static const struct hid_device_id hid_ignore_list[] = {
        { HID_USB_DEVICE(USB_VENDOR_ID_MCC, USB_DEVICE_ID_MCC_PMD1208LS) },
        { HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_PICKIT1) },
        { HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_PICKIT2) },
+       { HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_PICK16F1454) },
        { HID_USB_DEVICE(USB_VENDOR_ID_NATIONAL_SEMICONDUCTOR, 
USB_DEVICE_ID_N_S_HARMONY) },
        { HID_USB_DEVICE(USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100) },
        { HID_USB_DEVICE(USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100 + 
20) },
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 9243359..8df4744 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -638,6 +638,7 @@
 #define USB_DEVICE_ID_PICKIT2          0x0033
 #define USB_DEVICE_ID_PICOLCD          0xc002
 #define USB_DEVICE_ID_PICOLCD_BOOTLOADER       0xf002
+#define USB_DEVICE_ID_PICK16F1454      0x0042
 
 #define USB_VENDOR_ID_MICROSOFT                0x045e
 #define USB_DEVICE_ID_SIDEWINDER_GV    0x003b
diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig
index 76d7720..4476d1d 100644
--- a/drivers/usb/misc/Kconfig
+++ b/drivers/usb/misc/Kconfig
@@ -255,3 +255,14 @@ config USB_LINK_LAYER_TEST
          This driver is for generating specific traffic for Super Speed Link
          Layer Test Device. Say Y only when you want to conduct USB Super Speed
          Link Layer Test for host controllers.
+
+config USB_YKUSH
+       tristate "USB Yepkit YKUSH driver"
+       help
+         Yepkit YKUSH is a three-port switchable USB hub (see
+         <https://www.yepkit.com/products/ykush>).  Say Y here if you want to
+         add support for the switching part of the hub.
+
+         To compile this driver as a module, choose M here: the
+         module will be called ykush.
+
diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile
index 65b0402..1dc17fe 100644
--- a/drivers/usb/misc/Makefile
+++ b/drivers/usb/misc/Makefile
@@ -28,3 +28,4 @@ obj-$(CONFIG_USB_HSIC_USB3503)                += usb3503.o
 
 obj-$(CONFIG_USB_SISUSBVGA)            += sisusbvga/
 obj-$(CONFIG_USB_LINK_LAYER_TEST)      += lvstest.o
+obj-$(CONFIG_USB_YKUSH)                        += ykush.o
diff --git a/drivers/usb/misc/ykush.c b/drivers/usb/misc/ykush.c
new file mode 100644
index 0000000..0278796
--- /dev/null
+++ b/drivers/usb/misc/ykush.c
@@ -0,0 +1,176 @@
+/*
+ * Yepkit YKUSH driver
+ *
+ * Copyright (C) 2015 Kristian Evensen ([email protected])
+ *
+ *     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, version 2.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+
+
+#define DRIVER_AUTHOR "Kristian Evensen, [email protected]"
+#define DRIVER_DESC "Yepkit YKUSH driver"
+
+#define CMD_PORT_1     0x01
+#define CMD_PORT_2     0x02
+#define CMD_PORT_3     0x03
+#define CMD_PORT_ALL   0x0A
+
+static const struct usb_device_id id_table[] = {
+       { USB_DEVICE(0x04d8, 0x0042) },
+       { },
+};
+MODULE_DEVICE_TABLE(usb, id_table);
+
+struct ykush_hub {
+       struct usb_device *udev;
+       u8 port1;
+       u8 port2;
+       u8 port3;
+       u8 all;
+};
+
+static void send_port_cmd(struct ykush_hub *hub, u8 cmd)
+{
+       int retval = 0, actlength;
+       u8 *buffer = kmalloc(6, GFP_KERNEL);
+
+       if (!buffer) {
+               dev_err(&hub->udev->dev, "out of memory\n");
+               return;
+       }
+
+       buffer[0] = cmd;
+       buffer[1] = cmd;
+
+       retval = usb_interrupt_msg(hub->udev,
+                                  usb_sndctrlpipe(hub->udev, 1),
+                                  buffer,
+                                  6,
+                                  &actlength,
+                                  5000);
+
+       if (retval)
+               pr_info("retval = %d\n", retval);
+
+       kfree(buffer);
+}
+
+#define show_set(value, cmd) \
+static ssize_t show_##value(struct device *dev, struct device_attribute *attr,\
+                           char *buf)                                  \
+{                                                                      \
+       struct usb_interface *intf = to_usb_interface(dev);             \
+       struct ykush_hub *hub = usb_get_intfdata(intf);                 \
+                                                                       \
+       return sprintf(buf, "%u\n", hub->value);                        \
+}                                                                      \
+static ssize_t set_##value(struct device *dev, struct device_attribute *attr,\
+                          const char *buf, size_t count)               \
+{                                                                      \
+       struct usb_interface *intf = to_usb_interface(dev);             \
+       struct ykush_hub *hub = usb_get_intfdata(intf);                 \
+       u8 enable = 0, cmd = CMD_##cmd;                                 \
+                                                                       \
+       if (kstrtou8(buf, 0, &enable) || (enable > 1))                  \
+               return -EINVAL;                                         \
+                                                                       \
+       hub->value = enable;                                            \
+                                                                       \
+       if (enable)                                                     \
+               cmd |= 0x10;                                            \
+                                                                       \
+       send_port_cmd(hub, cmd);                                        \
+       return count;                                                   \
+}                                                                      \
+static DEVICE_ATTR(value, S_IRUGO | S_IWUSR, show_##value, set_##value)
+show_set(port1, PORT_1);
+show_set(port2, PORT_2);
+show_set(port3, PORT_3);
+show_set(all, PORT_ALL);
+
+static int ykush_probe(struct usb_interface *interface,
+                      const struct usb_device_id *id)
+{
+       struct usb_device *udev = interface_to_usbdev(interface);
+       struct ykush_hub *dev = NULL;
+       int retval = -ENOMEM;
+
+       dev = kzalloc(sizeof(struct ykush_hub), GFP_KERNEL);
+
+       if (dev == NULL) {
+               dev_err(&interface->dev, "out of memory\n");
+               goto error_mem;
+       }
+
+       dev->udev = usb_get_dev(udev);
+       usb_set_intfdata(interface, dev);
+
+       retval = device_create_file(&interface->dev, &dev_attr_port1);
+       if (retval)
+               goto error;
+       retval = device_create_file(&interface->dev, &dev_attr_port2);
+       if (retval)
+               goto error;
+       retval = device_create_file(&interface->dev, &dev_attr_port3);
+       if (retval)
+               goto error;
+       retval = device_create_file(&interface->dev, &dev_attr_all);
+       if (retval)
+               goto error;
+
+       dev_info(&interface->dev, "Yepkit YKUSH hub now attached\n");
+       return 0;
+
+error:
+       device_remove_file(&interface->dev, &dev_attr_port1);
+       device_remove_file(&interface->dev, &dev_attr_port2);
+       device_remove_file(&interface->dev, &dev_attr_port3);
+       device_remove_file(&interface->dev, &dev_attr_all);
+       usb_set_intfdata(interface, NULL);
+       usb_put_dev(dev->udev);
+       kfree(dev);
+error_mem:
+       return retval;
+}
+
+static void ykush_disconnect(struct usb_interface *interface)
+{
+       struct ykush_hub *dev;
+
+       dev = usb_get_intfdata(interface);
+
+       device_remove_file(&interface->dev, &dev_attr_port1);
+       device_remove_file(&interface->dev, &dev_attr_port2);
+       device_remove_file(&interface->dev, &dev_attr_port3);
+       device_remove_file(&interface->dev, &dev_attr_all);
+
+       usb_set_intfdata(interface, NULL);
+
+       usb_put_dev(dev->udev);
+
+       kfree(dev);
+
+       dev_info(&interface->dev, "Yepkit YKUSH now disconnected\n");
+}
+
+static struct usb_driver ykush_driver = {
+       .name =         "ykush",
+       .probe =        ykush_probe,
+       .disconnect =   ykush_disconnect,
+       .id_table =     id_table,
+};
+
+module_usb_driver(ykush_driver);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
-- 
2.1.0

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

Reply via email to