From patchwork Thu May 7 13:53:35 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Sean Young X-Patchwork-Id: 11533845 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 2BA3F1862 for ; Thu, 7 May 2020 13:53:41 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 19D1220857 for ; Thu, 7 May 2020 13:53:41 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1725939AbgEGNxj (ORCPT ); Thu, 7 May 2020 09:53:39 -0400 Received: from gofer.mess.org ([88.97.38.141]:44657 "EHLO gofer.mess.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1725953AbgEGNxj (ORCPT ); Thu, 7 May 2020 09:53:39 -0400 Received: by gofer.mess.org (Postfix, from userid 1000) id B8616C6419; Thu, 7 May 2020 14:53:37 +0100 (BST) From: Sean Young To: Dmitry Torokhov , linux-input@vger.kernel.org, linux-media@vger.kernel.org Subject: [PATCH 1/3] input: add support for the USB IR Toy and USB IR Droid Date: Thu, 7 May 2020 14:53:35 +0100 Message-Id: <20200507135337.2343-1-sean@mess.org> X-Mailer: git-send-email 2.20.1 MIME-Version: 1.0 Sender: linux-input-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org Add new serio ID for the USB IR Toy and IR Droid IR transceivers. More information about the hardware: http://dangerousprototypes.com/docs/USB_Infrared_Toy https://www.irdroid.com/irdroid-usb-ir-transceiver/ Signed-off-by: Sean Young --- include/uapi/linux/serio.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/uapi/linux/serio.h b/include/uapi/linux/serio.h index ed2a96f43ce4f..00c0be2546e93 100644 --- a/include/uapi/linux/serio.h +++ b/include/uapi/linux/serio.h @@ -83,5 +83,6 @@ #define SERIO_PULSE8_CEC 0x40 #define SERIO_RAINSHADOW_CEC 0x41 #define SERIO_FSIA6B 0x42 +#define SERIO_IRTOY 0x43 #endif /* _UAPI_SERIO_H */ From patchwork Thu May 7 13:53:36 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Sean Young X-Patchwork-Id: 11533841 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id A0DC5139A for ; Thu, 7 May 2020 13:53:40 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 929B92073A for ; Thu, 7 May 2020 13:53:40 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726134AbgEGNxj (ORCPT ); Thu, 7 May 2020 09:53:39 -0400 Received: from gofer.mess.org ([88.97.38.141]:34379 "EHLO gofer.mess.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1725939AbgEGNxj (ORCPT ); Thu, 7 May 2020 09:53:39 -0400 Received: by gofer.mess.org (Postfix, from userid 1000) id D4542C6424; Thu, 7 May 2020 14:53:37 +0100 (BST) From: Sean Young To: Dmitry Torokhov , linux-input@vger.kernel.org, linux-media@vger.kernel.org Subject: [PATCH 2/3] input: serio: allow more than one byte to be sent at once Date: Thu, 7 May 2020 14:53:36 +0100 Message-Id: <20200507135337.2343-2-sean@mess.org> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20200507135337.2343-1-sean@mess.org> References: <20200507135337.2343-1-sean@mess.org> MIME-Version: 1.0 Sender: linux-input-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org serio drivers can only send one byte at a time. If the underlying tty is a usb serial port, then each byte will be put into separate usb urbs, which is not efficient. Additionally, the Infrared Toy device refuses to transmit IR if the IR data is sent one byte at a time. IR data is formatted in u16 values, and the firmware expects complete u16 values in the packet. https://github.com/DangerousPrototypes/USB_IR_Toy/blob/master/Firmware-main/IRs.c#L240 Signed-off-by: Sean Young --- drivers/input/serio/serport.c | 9 +++++++++ include/linux/serio.h | 23 ++++++++++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/drivers/input/serio/serport.c b/drivers/input/serio/serport.c index 8ac970a423de6..887801691dddc 100644 --- a/drivers/input/serio/serport.c +++ b/drivers/input/serio/serport.c @@ -47,6 +47,14 @@ static int serport_serio_write(struct serio *serio, unsigned char data) return -(serport->tty->ops->write(serport->tty, &data, 1) != 1); } +static int serport_serio_write_buf(struct serio *serio, unsigned char *data, + uint count) +{ + struct serport *serport = serio->port_data; + + return -(serport->tty->ops->write(serport->tty, data, count) != count); +} + static int serport_serio_open(struct serio *serio) { struct serport *serport = serio->port_data; @@ -173,6 +181,7 @@ static ssize_t serport_ldisc_read(struct tty_struct * tty, struct file * file, u serio->id = serport->id; serio->id.type = SERIO_RS232; serio->write = serport_serio_write; + serio->write_buf = serport_serio_write_buf; serio->open = serport_serio_open; serio->close = serport_serio_close; serio->port_data = serport; diff --git a/include/linux/serio.h b/include/linux/serio.h index 6c27d413da921..3918e56aec51c 100644 --- a/include/linux/serio.h +++ b/include/linux/serio.h @@ -32,6 +32,7 @@ struct serio { spinlock_t lock; int (*write)(struct serio *, unsigned char); + int (*write_buf)(struct serio *serio, unsigned char *buf, uint size); int (*open)(struct serio *); void (*close)(struct serio *); int (*start)(struct serio *); @@ -121,12 +122,32 @@ void serio_unregister_driver(struct serio_driver *drv); static inline int serio_write(struct serio *serio, unsigned char data) { - if (serio->write) + if (serio->write_buf) + return serio->write_buf(serio, &data, 1); + else if (serio->write) return serio->write(serio, data); else return -1; } +static inline int serio_write_buf(struct serio *serio, unsigned char *data, + uint size) +{ + if (serio->write_buf) { + return serio->write_buf(serio, data, size); + } else if (serio->write) { + int ret; + + do { + ret = serio->write(serio, *data++); + } while (ret == 0 && --size); + + return ret; + } else { + return -1; + } +} + static inline void serio_drv_write_wakeup(struct serio *serio) { if (serio->drv && serio->drv->write_wakeup) From patchwork Thu May 7 13:53:37 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Sean Young X-Patchwork-Id: 11533851 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 39DD0139F for ; Thu, 7 May 2020 13:53:42 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 27B032083B for ; Thu, 7 May 2020 13:53:42 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1725953AbgEGNxk (ORCPT ); Thu, 7 May 2020 09:53:40 -0400 Received: from gofer.mess.org ([88.97.38.141]:55479 "EHLO gofer.mess.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1725985AbgEGNxj (ORCPT ); Thu, 7 May 2020 09:53:39 -0400 Received: by gofer.mess.org (Postfix, from userid 1000) id 00995C643A; Thu, 7 May 2020 14:53:37 +0100 (BST) From: Sean Young To: Dmitry Torokhov , linux-input@vger.kernel.org, linux-media@vger.kernel.org Subject: [PATCH 3/3] media: rc: add support for Infrared Toy and IR Droid devices Date: Thu, 7 May 2020 14:53:37 +0100 Message-Id: <20200507135337.2343-3-sean@mess.org> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20200507135337.2343-1-sean@mess.org> References: <20200507135337.2343-1-sean@mess.org> MIME-Version: 1.0 Sender: linux-input-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org These devices are connected via an usb serial port, and need to be attached using "inputattach -irtoy /dev/ttyACM[0-9]" (patches will be sent once this is merged). http://dangerousprototypes.com/docs/USB_Infrared_Toy https://www.irdroid.com/irdroid-usb-ir-transceiver/ Signed-off-by: Sean Young --- drivers/media/rc/Kconfig | 15 ++ drivers/media/rc/Makefile | 1 + drivers/media/rc/ir_toy.c | 448 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 464 insertions(+) create mode 100644 drivers/media/rc/ir_toy.c diff --git a/drivers/media/rc/Kconfig b/drivers/media/rc/Kconfig index c18dee6482536..aa690ba6a981a 100644 --- a/drivers/media/rc/Kconfig +++ b/drivers/media/rc/Kconfig @@ -530,6 +530,21 @@ config IR_ZX To compile this driver as a module, choose M here: the module will be called zx-irdec. +config IR_TOY + tristate "Infrared Toy and IR Droid" + depends on RC_CORE + select USB + select USB_ACM + select SERIO + select SERIO_SERPORT + help + Say Y here if you want to use the Infrared Toy or IR Droid. This + is a serio driver which needs to be attached to the usb serial + port using inputattach. + + To compile this driver as a module, choose M here: the module will be + called ir_toy. + endif #RC_DEVICES endif #RC_CORE diff --git a/drivers/media/rc/Makefile b/drivers/media/rc/Makefile index 48d23433b3c06..5bb2932ab1195 100644 --- a/drivers/media/rc/Makefile +++ b/drivers/media/rc/Makefile @@ -50,3 +50,4 @@ obj-$(CONFIG_IR_MTK) += mtk-cir.o obj-$(CONFIG_IR_ZX) += zx-irdec.o obj-$(CONFIG_IR_TANGO) += tango-ir.o obj-$(CONFIG_RC_XBOX_DVD) += xbox_remote.o +obj-$(CONFIG_IR_TOY) += ir_toy.o diff --git a/drivers/media/rc/ir_toy.c b/drivers/media/rc/ir_toy.c new file mode 100644 index 0000000000000..8c9dffa002f44 --- /dev/null +++ b/drivers/media/rc/ir_toy.c @@ -0,0 +1,448 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +/* + * Infrared Toy and IR Droid RC core driver + * + * Copyright (C) 2020 Sean Young + + * This driver is based on the lirc driver which can be found here: + * https://sourceforge.net/p/lirc/git/ci/master/tree/plugins/irtoy.c + * Copyright (C) 2011 Peter Kooiman + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +static u8 COMMAND_VERSION[] = { 'v' }; +// End transmit and repeat reset command so we exit sump mode +static u8 COMMAND_RESET[] = { 0xff, 0xff, 0, 0, 0, 0, 0 }; +static u8 COMMAND_SMODE_ENTER[] = { 's' }; +static u8 COMMAND_TXSTART[] = { 0x26, 0x24, 0x25, 0x03 }; + +#define COMMAND_IO_WRITE 0x30 +#define COMMAND_IO_DIRECTION 0x31 + +#define REPLY_XMITCOUNT 't' +#define REPLY_XMITSUCCESS 'C' +#define REPLY_VERSION 'V' +#define REPLY_SAMPLEMODEPROTO 'S' + +#define TIMEOUT_READYFORDATA 1000000 +#define TIMEOUT_FLUSH 20000 +#define TIMEOUT_SMODE_ENTER 500000 +#define TIMEOUT_VERSION 500000 + +#define LEN_XMITRES 4 +#define LEN_VERSION 4 +#define LEN_SAMPLEMODEPROTO 3 + +#define MAX_MSG_SIZE 6 + +#define MIN_FW_SUPPORTING_SETPIN 22 +#define MIN_FW_VERSION 20 +#define UNIT_NS 21333 +#define MAX_TIMEOUT_NS (UNIT_NS * U16_MAX) + +#define OPEN_PIN 5 // RA5 +#define RECEIVE_PIN 3 // RA3 +#define SENDING_PIN 4 // RA4 + +struct irtoy { + struct device *dev; + struct rc_dev *rc; + struct serio *serio; + + u8 rx[MAX_MSG_SIZE]; + u8 rx_len; + u8 rx_needed; + bool pulse; + struct completion rx_done; + + uint hw_version; + uint sw_version; + uint proto_version; +}; + +static irqreturn_t irtoy_interrupt(struct serio *serio, unsigned char data, + unsigned int flags) +{ + struct irtoy *irtoy = serio_get_drvdata(serio); + + // add byte to buffer + if (irtoy->rx_len < MAX_MSG_SIZE) + irtoy->rx[irtoy->rx_len++] = data; + + if (irtoy->rx_needed == 0 && irtoy->rx_len == 2) { + u32 v = be16_to_cpup((__be16 *)irtoy->rx); + + if (v != 0xffff) { + struct ir_raw_event rawir = { + .pulse = irtoy->pulse, + .duration = v * UNIT_NS, + }; + + ir_raw_event_store_with_timeout(irtoy->rc, &rawir); + ir_raw_event_handle(irtoy->rc); + irtoy->pulse = !irtoy->pulse; + } else { + irtoy->pulse = true; + } + + irtoy->rx_len = 0; + } else if (irtoy->rx_len == irtoy->rx_needed) { + complete(&irtoy->rx_done); + } + + return IRQ_HANDLED; +} + +// Send a command and wait for a response of rx_len bytes, for at most +// timeout microseconds. If rxlen is 0, then wait for timeout microseconds +// and read all response bytes within that period +static int irtoy_send_and_recv(struct irtoy *irtoy, u8 *cmd, int cmd_len, + int rx_len, int timeout) +{ + int err; + + serio_pause_rx(irtoy->serio); + init_completion(&irtoy->rx_done); + irtoy->rx_len = 0; + irtoy->rx_needed = rx_len; + serio_continue_rx(irtoy->serio); + + err = serio_write_buf(irtoy->serio, cmd, cmd_len); + if (err) + return err; + + if (!wait_for_completion_timeout(&irtoy->rx_done, + usecs_to_jiffies(timeout))) + return -ETIMEDOUT; + + return 0; +} + +// Wait for timeout for rx_len bytes to arrive +static int irtoy_recv(struct irtoy *irtoy, int rx_len, int timeout) +{ + bool done; + + serio_pause_rx(irtoy->serio); + if (irtoy->rx_len >= rx_len) { + done = true; + } else { + init_completion(&irtoy->rx_done); + irtoy->rx_needed = rx_len; + done = false; + } + serio_continue_rx(irtoy->serio); + + if (!done && !wait_for_completion_timeout(&irtoy->rx_done, + usecs_to_jiffies(timeout))) + return -ETIMEDOUT; + + return 0; +} + +static int irtoy_send_tx(struct irtoy *irtoy, void *buf, uint size) +{ + uint buf_len, buf_size, count, emitted; + int err; + + err = irtoy_send_and_recv(irtoy, COMMAND_TXSTART, + sizeof(COMMAND_TXSTART), + 1, TIMEOUT_READYFORDATA); + if (err) { + dev_err(irtoy->dev, "failed to send tx start command: %d\n", + err); + return err; + } + + buf_len = irtoy->rx[0]; + count = size; + + while (count) { + dev_dbg(irtoy->dev, "ready to receive: 0x%02x\n", buf_len); + + if (buf_len == 0) { + dev_err(irtoy->dev, "not enough buffer space\n"); + return -EINVAL; + } + + buf_size = min(buf_len, count); + + err = irtoy_send_and_recv(irtoy, buf, buf_size, 1, + TIMEOUT_READYFORDATA); + if (err) { + dev_err(irtoy->dev, "failed to send tx buffer: %d\n", + err); + return err; + } + + buf_len = irtoy->rx[0]; + + buf += buf_size; + count -= buf_size; + } + + err = irtoy_recv(irtoy, 1 + LEN_XMITRES, TIMEOUT_READYFORDATA); + if (err) { + dev_err(irtoy->dev, "failed to receive tx result: %d\n", err); + return err; + } + + dev_dbg(irtoy->dev, "tx result: %*phN", LEN_XMITRES, irtoy->rx + 1); + + if (irtoy->rx[1] != REPLY_XMITCOUNT) { + dev_err(irtoy->dev, "invalid byte count indicator\n"); + return -EINVAL; + } + + emitted = be16_to_cpup((__be16 *)(irtoy->rx + 2)); + + if (size != emitted) { + dev_err(irtoy->dev, "expected %u emitted, got %u\n", count, + emitted); + return -EINVAL; + } + + if (irtoy->rx[4] != REPLY_XMITSUCCESS) { + dev_err(irtoy->dev, "invalid byte count indicator\n"); + return -EINVAL; + } + + // switch to raw IR mode + irtoy->rx_needed = 0; + irtoy->rx_len = 0; + + return 0; +} + +static int irtoy_tx(struct rc_dev *rc, uint *txbuf, uint count) +{ + struct irtoy *irtoy = rc->priv; + unsigned int i; + size_t size; + __be16 *buf; + int err; + + size = sizeof(u16) * (count + 1); + buf = kmalloc(size, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + for (i = 0; i < count; i++) { + u16 v = DIV_ROUND_CLOSEST(US_TO_NS(txbuf[i]), UNIT_NS); + + if (!v) + v = 1; + buf[i] = cpu_to_be16(v); + } + + buf[i] = cpu_to_be16(0xffff); + + err = irtoy_send_tx(irtoy, buf, size); + kfree(buf); + + return err == 0 ? count : err; +} + +static int irtoy_reset(struct irtoy *irtoy) +{ + int err; + + err = serio_write_buf(irtoy->serio, COMMAND_RESET, + sizeof(COMMAND_RESET)); + if (err != 0) { + dev_err(irtoy->dev, "could not write reset command: %d\n", + err); + return err; + } + + usleep_range(50, 50); + + return 0; +} + +static int irtoy_setup(struct irtoy *irtoy) +{ + uint version; + int err; + + // reset device + err = irtoy_reset(irtoy); + if (err) + return err; + + // get version + err = irtoy_send_and_recv(irtoy, COMMAND_VERSION, + sizeof(COMMAND_VERSION), + LEN_VERSION, TIMEOUT_VERSION); + if (err) { + dev_err(irtoy->dev, "could not write version command: %d\n", + err); + return err; + } + + irtoy->rx[LEN_VERSION] = 0; + + if (irtoy->rx[0] != REPLY_VERSION || + kstrtouint(irtoy->rx + 1, 10, &version)) { + dev_err(irtoy->dev, "invalid version %*phN. Please make sure you are using firmware v20 or higher", + LEN_VERSION, irtoy->rx); + return -ENODEV; + } + + dev_dbg(irtoy->dev, "version %s\n", irtoy->rx); + + irtoy->hw_version = version / 100; + irtoy->sw_version = version % 100; + + // enter sample mode + err = irtoy_send_and_recv(irtoy, COMMAND_SMODE_ENTER, + sizeof(COMMAND_SMODE_ENTER), + LEN_SAMPLEMODEPROTO, TIMEOUT_SMODE_ENTER); + if (err) { + dev_err(irtoy->dev, "could not write version command: %d\n", + err); + return err; + } + + irtoy->rx[LEN_SAMPLEMODEPROTO] = 0; + + if (irtoy->rx[0] != REPLY_SAMPLEMODEPROTO || + kstrtouint(irtoy->rx + 1, 10, &version)) { + dev_err(irtoy->dev, "invalid sample mode response %*phN", + LEN_SAMPLEMODEPROTO, irtoy->rx); + return -ENODEV; + } + + dev_dbg(irtoy->dev, "protocol %s\n", irtoy->rx); + + irtoy->proto_version = version; + + return 0; +} + +static int irtoy_connect(struct serio *serio, struct serio_driver *drv) +{ + struct irtoy *irtoy; + struct rc_dev *rc; + int err; + + irtoy = kzalloc(sizeof(*irtoy), GFP_KERNEL); + if (!irtoy) + return -ENOMEM; + + rc = rc_allocate_device(RC_DRIVER_IR_RAW); + if (!rc) { + err = -ENOMEM; + goto free_irtoy; + } + + irtoy->serio = serio; + irtoy->dev = &serio->dev; + irtoy->rc = rc; + irtoy->pulse = true; + + serio_set_drvdata(serio, irtoy); + + err = serio_open(serio, drv); + if (err) + goto free_device; + + err = irtoy_setup(irtoy); + if (err) + goto close_serio; + + dev_info(irtoy->dev, "version hardware %u, firmware %u, protocol %u", + irtoy->hw_version, irtoy->sw_version, irtoy->proto_version); + + if (irtoy->sw_version < MIN_FW_VERSION) { + dev_err(irtoy->dev, "need firmware V%02u or higher", + MIN_FW_VERSION); + err = -ENODEV; + goto close_serio; + } + + rc->device_name = dev_name(&serio->dev); + rc->driver_name = KBUILD_MODNAME; + rc->input_phys = serio->phys; + rc->priv = irtoy; + rc->dev.parent = &serio->dev; + rc->tx_ir = irtoy_tx; + rc->allowed_protocols = RC_PROTO_BIT_ALL_IR_DECODER; + rc->map_name = RC_MAP_RC6_MCE; + rc->rx_resolution = UNIT_NS; + rc->timeout = IR_DEFAULT_TIMEOUT; + rc->min_timeout = US_TO_NS(1); + rc->max_timeout = MAX_TIMEOUT_NS; + + err = rc_register_device(rc); + if (err) + goto close_serio; + + // switch to raw IR mode + irtoy->rx_needed = 0; + irtoy->rx_len = 0; + + return 0; + +close_serio: + serio_close(serio); +free_device: + serio_set_drvdata(serio, NULL); + rc_free_device(rc); +free_irtoy: + kfree(irtoy); + return err; +} + +static void irtoy_disconnect(struct serio *serio) +{ + struct irtoy *irtoy = serio_get_drvdata(serio); + + rc_unregister_device(irtoy->rc); + + irtoy->serio = NULL; + + serio_close(serio); + serio_set_drvdata(serio, NULL); + kfree(irtoy); +} + +static const struct serio_device_id irtoy_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_IRTOY, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { } +}; + +MODULE_DEVICE_TABLE(serio, irtoy_serio_ids); + +static struct serio_driver irtoy_drv = { + .driver = { + .name = "irtoy", + }, + .description = "Infrared Toy and IR Droid RC driver", + .id_table = irtoy_serio_ids, + .interrupt = irtoy_interrupt, + .connect = irtoy_connect, + .disconnect = irtoy_disconnect, +}; + +module_serio_driver(irtoy_drv); + +MODULE_AUTHOR("Sean Young "); +MODULE_DESCRIPTION("Infrared Toy and IR Droid driver"); +MODULE_LICENSE("GPL");