@@ -12066,6 +12066,12 @@ F: drivers/phy/samsung/phy-s5pv210-usb2.c
F: drivers/phy/samsung/phy-samsung-usb2.c
F: drivers/phy/samsung/phy-samsung-usb2.h
+SASEM REMOTE CONTROLLER
+M: Sean Young <sean@mess.org>
+L: linux-media@vger.kernel.org
+S: Maintained
+F: drivers/media/rc/sasem_ir.c
+
SC1200 WDT DRIVER
M: Zwane Mwaikambo <zwanem@gmail.com>
S: Maintained
@@ -457,6 +457,22 @@ config IR_SERIAL
To compile this driver as a module, choose M here: the module will
be called serial-ir.
+config IR_SASEM
+ tristate "Sasem Remote Controller"
+ depends on USB_ARCH_HAS_HCD
+ depends on RC_CORE
+ select USB
+ select CHARLCD
+ ---help---
+ Driver for the Sasem OnAir Remocon-V or Dign HV5 HTPC IR/VFD Module
+
+ The LCD can be controlled via the charlcd driver. Unfortunately the
+ device does not seem to provide a method for accessing the
+ character generator ram.
+
+ To compile this driver as a module, choose M here: the module will
+ be called sesam_ir.
+
config IR_SERIAL_TRANSMITTER
bool "Serial Port Transmitter"
default y
@@ -41,6 +41,7 @@ obj-$(CONFIG_IR_TTUSBIR) += ttusbir.o
obj-$(CONFIG_RC_ST) += st_rc.o
obj-$(CONFIG_IR_SUNXI) += sunxi-cir.o
obj-$(CONFIG_IR_IMG) += img-ir/
+obj-$(CONFIG_IR_SASEM) += sasem_ir.o
obj-$(CONFIG_IR_SERIAL) += serial_ir.o
obj-$(CONFIG_IR_SIR) += sir_ir.o
obj-$(CONFIG_IR_MTK) += mtk-cir.o
new file mode 100644
@@ -0,0 +1,297 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// Copyright (C) 2018 Sean Young <sean@mess.org>
+
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/usb/input.h>
+#include <misc/charlcd.h>
+
+#include <media/rc-core.h>
+
+struct sasem {
+ struct device *dev;
+ struct urb *ir_urb;
+ struct urb *vfd_urb;
+ struct rc_dev *rcdev;
+ struct completion completion;
+ u8 ir_buf[8];
+ u8 vfd_buf[8];
+ unsigned int offset;
+ char phys[64];
+};
+
+static void sasem_ir_rx(struct urb *urb)
+{
+ struct sasem *sasem = urb->context;
+ enum rc_proto proto;
+ u32 code;
+ int ret;
+
+ switch (urb->status) {
+ case 0:
+ dev_dbg(sasem->dev, "data: %*ph", 8, sasem->ir_buf);
+ /*
+ * Note that sanyo and jvc protocols are also supported,
+ * but the scancode seems garbled. More testing needed.
+ */
+ switch (sasem->ir_buf[0]) {
+ case 0xc:
+ code = ir_nec_bytes_to_scancode(sasem->ir_buf[1],
+ sasem->ir_buf[2], sasem->ir_buf[3],
+ sasem->ir_buf[4], &proto);
+ rc_keydown(sasem->rcdev, proto, code, 0);
+ break;
+ case 0x8:
+ rc_repeat(sasem->rcdev);
+ break;
+ }
+ break;
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ usb_unlink_urb(urb);
+ return;
+ case -EPIPE:
+ default:
+ dev_dbg(sasem->dev, "error: urb status = %d", urb->status);
+ break;
+ }
+
+ ret = usb_submit_urb(urb, GFP_ATOMIC);
+ if (ret && ret != -ENODEV)
+ dev_warn(sasem->dev, "failed to resubmit urb: %d", ret);
+}
+
+static void sasem_vfd_complete(struct urb *urb)
+{
+ struct sasem *sasem = urb->context;
+
+ if (urb->status)
+ dev_info(sasem->dev, "error: vfd urb status = %d", urb->status);
+
+ complete(&sasem->completion);
+}
+
+static int sasem_vfd_send(struct sasem *sasem)
+{
+ int ret;
+
+ reinit_completion(&sasem->completion);
+
+ ret = usb_submit_urb(sasem->vfd_urb, GFP_KERNEL);
+ if (ret)
+ return ret;
+
+ if (wait_for_completion_timeout(&sasem->completion, 1000) == 0)
+ return -ETIMEDOUT;
+
+ return 0;
+}
+
+static void sasem_lcd_flush(struct charlcd *lcd)
+{
+ struct sasem *sasem = lcd->drvdata;
+
+ if (sasem->offset > 0) {
+ while (sasem->offset < 8)
+ sasem->vfd_buf[sasem->offset++] = 0;
+
+ sasem_vfd_send(sasem);
+
+ sasem->offset = 0;
+ }
+}
+
+static void sasem_lcd_write_cmd(struct charlcd *lcd, int cmd)
+{
+ struct sasem *sasem = lcd->drvdata;
+
+ dev_dbg(sasem->dev, "lcd_write_cmd: %02x", cmd);
+
+ if (sasem->offset >= 6)
+ sasem_lcd_flush(lcd);
+
+ if (cmd & 0x80) {
+ sasem->vfd_buf[sasem->offset++] = 0x09;
+ sasem->vfd_buf[sasem->offset++] = ((cmd & 0x7f) + 1) ^ 0x40;
+ } else {
+ sasem->vfd_buf[sasem->offset++] = 0x07;
+
+ /* The interface between Sasem and NEC µPD16314 is 4-bit */
+ if ((cmd & 0xe0) == 0x20)
+ cmd &= ~0x10;
+ sasem->vfd_buf[sasem->offset++] = cmd;
+ }
+}
+
+static void sasem_lcd_write_data(struct charlcd *lcd, int data)
+{
+ struct sasem *sasem = lcd->drvdata;
+
+ dev_dbg(sasem->dev, "lcd_write_data: %02x", data);
+
+ if (sasem->offset >= 7)
+ sasem_lcd_flush(lcd);
+
+ sasem->vfd_buf[sasem->offset++] = data;
+}
+
+static const struct charlcd_ops sasem_lcd_ops = {
+ .write_cmd = sasem_lcd_write_cmd,
+ .write_data = sasem_lcd_write_data,
+ .flush = sasem_lcd_flush
+};
+
+static int sasem_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ struct usb_endpoint_descriptor *ir_ep = NULL;
+ struct usb_endpoint_descriptor *vfd_ep = NULL;
+ struct usb_host_interface *idesc;
+ struct usb_device *udev;
+ struct rc_dev *rcdev;
+ struct charlcd *lcd;
+ struct sasem *sasem;
+ int i, ret;
+
+ udev = interface_to_usbdev(intf);
+ idesc = intf->cur_altsetting;
+
+ for (i = 0; i < idesc->desc.bNumEndpoints; i++) {
+ struct usb_endpoint_descriptor *ep = &idesc->endpoint[i].desc;
+
+ if (usb_endpoint_is_int_in(ep) && !ir_ep)
+ ir_ep = ep;
+ else if (usb_endpoint_is_int_out(ep) && !vfd_ep)
+ vfd_ep = ep;
+ }
+
+ if (!ir_ep || !vfd_ep) {
+ dev_err(&intf->dev, "usb endpoint missing");
+ return -ENODEV;
+ }
+
+ lcd = charlcd_alloc(sizeof(*sasem));
+ if (!lcd)
+ return -ENOMEM;
+
+ sasem = lcd->drvdata;
+
+ sasem->ir_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!sasem->ir_urb) {
+ ret = -ENOMEM;
+ goto out_free;
+ }
+
+ sasem->dev = &intf->dev;
+ usb_fill_int_urb(sasem->ir_urb, udev,
+ usb_rcvintpipe(udev, ir_ep->bEndpointAddress),
+ sasem->ir_buf, sizeof(sasem->ir_buf),
+ sasem_ir_rx, sasem, ir_ep->bInterval);
+
+ rcdev = devm_rc_allocate_device(&intf->dev, RC_DRIVER_SCANCODE);
+ if (!rcdev) {
+ ret = -ENOMEM;
+ goto out_free;
+ }
+
+ usb_make_path(udev, sasem->phys, sizeof(sasem->phys));
+
+ rcdev->device_name = "Sasem Remote Controller";
+ rcdev->driver_name = KBUILD_MODNAME;
+ rcdev->input_phys = sasem->phys;
+ usb_to_input_id(udev, &rcdev->input_id);
+ rcdev->dev.parent = &intf->dev;
+ rcdev->allowed_protocols = RC_PROTO_BIT_NEC;
+ rcdev->map_name = RC_MAP_DIGN;
+ rcdev->priv = sasem;
+
+ ret = devm_rc_register_device(&intf->dev, rcdev);
+ if (ret)
+ goto out_free;
+
+ sasem->rcdev = rcdev;
+
+ lcd->height = 2;
+ lcd->width = 16;
+ lcd->bwidth = 16;
+ lcd->ops = &sasem_lcd_ops;
+
+ init_completion(&sasem->completion);
+
+ sasem->vfd_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!sasem->vfd_urb) {
+ ret = -ENOMEM;
+ goto out_free;
+ }
+
+ usb_fill_int_urb(sasem->vfd_urb, udev,
+ usb_sndintpipe(udev, vfd_ep->bEndpointAddress),
+ sasem->vfd_buf, sizeof(sasem->vfd_buf),
+ sasem_vfd_complete, sasem, vfd_ep->bInterval);
+
+ /*
+ * After usb is plugged in, the device programs the LCD with
+ * 'Welcome DIGN Home Theatre PC'. Wait for this to complete,
+ * else our commands will be mixed up with these commands.
+ */
+ msleep(20);
+
+ ret = charlcd_register(lcd);
+ if (ret)
+ goto out_free;
+
+ ret = usb_submit_urb(sasem->ir_urb, GFP_KERNEL);
+ if (ret)
+ goto out_lcd;
+
+ usb_set_intfdata(intf, lcd);
+
+ return 0;
+
+out_lcd:
+ charlcd_unregister(lcd);
+out_free:
+ usb_free_urb(sasem->ir_urb);
+ usb_free_urb(sasem->vfd_urb);
+ kfree(lcd);
+
+ return ret;
+}
+
+static void sasem_disconnect(struct usb_interface *intf)
+{
+ struct charlcd *lcd = usb_get_intfdata(intf);
+ struct sasem *sasem = lcd->drvdata;
+
+ charlcd_unregister(lcd);
+
+ usb_kill_urb(sasem->ir_urb);
+ usb_free_urb(sasem->ir_urb);
+ usb_kill_urb(sasem->vfd_urb);
+ usb_free_urb(sasem->vfd_urb);
+
+ kfree(lcd);
+}
+
+static const struct usb_device_id sasem_table[] = {
+ /* Sasem USB Control Board */
+ { USB_DEVICE(0x11ba, 0x0101) },
+
+ {}
+};
+
+static struct usb_driver sasem_driver = {
+ .name = KBUILD_MODNAME,
+ .probe = sasem_probe,
+ .disconnect = sasem_disconnect,
+ .id_table = sasem_table
+};
+
+module_usb_driver(sasem_driver);
+
+MODULE_DESCRIPTION("Sasem Remote Controller");
+MODULE_AUTHOR("Sean Young <sean@mess.org>");
+MODULE_LICENSE("GPL");
+MODULE_DEVICE_TABLE(usb, sasem_table);