18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci *   imon.c:	input and display driver for SoundGraph iMON IR/VFD/LCD
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci *   Copyright(C) 2010  Jarod Wilson <jarod@wilsonet.com>
68c2ecf20Sopenharmony_ci *   Portions based on the original lirc_imon driver,
78c2ecf20Sopenharmony_ci *	Copyright(C) 2004  Venky Raju(dev@venky.ws)
88c2ecf20Sopenharmony_ci *
98c2ecf20Sopenharmony_ci *   Huge thanks to R. Geoff Newbury for invaluable debugging on the
108c2ecf20Sopenharmony_ci *   0xffdc iMON devices, and for sending me one to hack on, without
118c2ecf20Sopenharmony_ci *   which the support for them wouldn't be nearly as good. Thanks
128c2ecf20Sopenharmony_ci *   also to the numerous 0xffdc device owners that tested auto-config
138c2ecf20Sopenharmony_ci *   support for me and provided debug dumps from their devices.
148c2ecf20Sopenharmony_ci */
158c2ecf20Sopenharmony_ci
168c2ecf20Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ":%s: " fmt, __func__
178c2ecf20Sopenharmony_ci
188c2ecf20Sopenharmony_ci#include <linux/errno.h>
198c2ecf20Sopenharmony_ci#include <linux/init.h>
208c2ecf20Sopenharmony_ci#include <linux/kernel.h>
218c2ecf20Sopenharmony_ci#include <linux/ktime.h>
228c2ecf20Sopenharmony_ci#include <linux/module.h>
238c2ecf20Sopenharmony_ci#include <linux/slab.h>
248c2ecf20Sopenharmony_ci#include <linux/uaccess.h>
258c2ecf20Sopenharmony_ci#include <linux/ratelimit.h>
268c2ecf20Sopenharmony_ci
278c2ecf20Sopenharmony_ci#include <linux/input.h>
288c2ecf20Sopenharmony_ci#include <linux/usb.h>
298c2ecf20Sopenharmony_ci#include <linux/usb/input.h>
308c2ecf20Sopenharmony_ci#include <media/rc-core.h>
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_ci#include <linux/timer.h>
338c2ecf20Sopenharmony_ci
348c2ecf20Sopenharmony_ci#define MOD_AUTHOR	"Jarod Wilson <jarod@wilsonet.com>"
358c2ecf20Sopenharmony_ci#define MOD_DESC	"Driver for SoundGraph iMON MultiMedia IR/Display"
368c2ecf20Sopenharmony_ci#define MOD_NAME	"imon"
378c2ecf20Sopenharmony_ci#define MOD_VERSION	"0.9.4"
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_ci#define DISPLAY_MINOR_BASE	144
408c2ecf20Sopenharmony_ci#define DEVICE_NAME	"lcd%d"
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci#define BUF_CHUNK_SIZE	8
438c2ecf20Sopenharmony_ci#define BUF_SIZE	128
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_ci#define BIT_DURATION	250	/* each bit received is 250us */
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_ci#define IMON_CLOCK_ENABLE_PACKETS	2
488c2ecf20Sopenharmony_ci
498c2ecf20Sopenharmony_ci/*** P R O T O T Y P E S ***/
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_ci/* USB Callback prototypes */
528c2ecf20Sopenharmony_cistatic int imon_probe(struct usb_interface *interface,
538c2ecf20Sopenharmony_ci		      const struct usb_device_id *id);
548c2ecf20Sopenharmony_cistatic void imon_disconnect(struct usb_interface *interface);
558c2ecf20Sopenharmony_cistatic void usb_rx_callback_intf0(struct urb *urb);
568c2ecf20Sopenharmony_cistatic void usb_rx_callback_intf1(struct urb *urb);
578c2ecf20Sopenharmony_cistatic void usb_tx_callback(struct urb *urb);
588c2ecf20Sopenharmony_ci
598c2ecf20Sopenharmony_ci/* suspend/resume support */
608c2ecf20Sopenharmony_cistatic int imon_resume(struct usb_interface *intf);
618c2ecf20Sopenharmony_cistatic int imon_suspend(struct usb_interface *intf, pm_message_t message);
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_ci/* Display file_operations function prototypes */
648c2ecf20Sopenharmony_cistatic int display_open(struct inode *inode, struct file *file);
658c2ecf20Sopenharmony_cistatic int display_close(struct inode *inode, struct file *file);
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_ci/* VFD write operation */
688c2ecf20Sopenharmony_cistatic ssize_t vfd_write(struct file *file, const char __user *buf,
698c2ecf20Sopenharmony_ci			 size_t n_bytes, loff_t *pos);
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci/* LCD file_operations override function prototypes */
728c2ecf20Sopenharmony_cistatic ssize_t lcd_write(struct file *file, const char __user *buf,
738c2ecf20Sopenharmony_ci			 size_t n_bytes, loff_t *pos);
748c2ecf20Sopenharmony_ci
758c2ecf20Sopenharmony_ci/*** G L O B A L S ***/
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_cistruct imon_panel_key_table {
788c2ecf20Sopenharmony_ci	u64 hw_code;
798c2ecf20Sopenharmony_ci	u32 keycode;
808c2ecf20Sopenharmony_ci};
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_cistruct imon_usb_dev_descr {
838c2ecf20Sopenharmony_ci	__u16 flags;
848c2ecf20Sopenharmony_ci#define IMON_NO_FLAGS 0
858c2ecf20Sopenharmony_ci#define IMON_NEED_20MS_PKT_DELAY 1
868c2ecf20Sopenharmony_ci#define IMON_SUPPRESS_REPEATED_KEYS 2
878c2ecf20Sopenharmony_ci	struct imon_panel_key_table key_table[];
888c2ecf20Sopenharmony_ci};
898c2ecf20Sopenharmony_ci
908c2ecf20Sopenharmony_cistruct imon_context {
918c2ecf20Sopenharmony_ci	struct device *dev;
928c2ecf20Sopenharmony_ci	/* Newer devices have two interfaces */
938c2ecf20Sopenharmony_ci	struct usb_device *usbdev_intf0;
948c2ecf20Sopenharmony_ci	struct usb_device *usbdev_intf1;
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_ci	bool display_supported;		/* not all controllers do */
978c2ecf20Sopenharmony_ci	bool display_isopen;		/* display port has been opened */
988c2ecf20Sopenharmony_ci	bool rf_device;			/* true if iMON 2.4G LT/DT RF device */
998c2ecf20Sopenharmony_ci	bool rf_isassociating;		/* RF remote associating */
1008c2ecf20Sopenharmony_ci	bool dev_present_intf0;		/* USB device presence, interface 0 */
1018c2ecf20Sopenharmony_ci	bool dev_present_intf1;		/* USB device presence, interface 1 */
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ci	struct mutex lock;		/* to lock this object */
1048c2ecf20Sopenharmony_ci	wait_queue_head_t remove_ok;	/* For unexpected USB disconnects */
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_ci	struct usb_endpoint_descriptor *rx_endpoint_intf0;
1078c2ecf20Sopenharmony_ci	struct usb_endpoint_descriptor *rx_endpoint_intf1;
1088c2ecf20Sopenharmony_ci	struct usb_endpoint_descriptor *tx_endpoint;
1098c2ecf20Sopenharmony_ci	struct urb *rx_urb_intf0;
1108c2ecf20Sopenharmony_ci	struct urb *rx_urb_intf1;
1118c2ecf20Sopenharmony_ci	struct urb *tx_urb;
1128c2ecf20Sopenharmony_ci	bool tx_control;
1138c2ecf20Sopenharmony_ci	unsigned char usb_rx_buf[8];
1148c2ecf20Sopenharmony_ci	unsigned char usb_tx_buf[8];
1158c2ecf20Sopenharmony_ci	unsigned int send_packet_delay;
1168c2ecf20Sopenharmony_ci
1178c2ecf20Sopenharmony_ci	struct tx_t {
1188c2ecf20Sopenharmony_ci		unsigned char data_buf[35];	/* user data buffer */
1198c2ecf20Sopenharmony_ci		struct completion finished;	/* wait for write to finish */
1208c2ecf20Sopenharmony_ci		bool busy;			/* write in progress */
1218c2ecf20Sopenharmony_ci		int status;			/* status of tx completion */
1228c2ecf20Sopenharmony_ci	} tx;
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_ci	u16 vendor;			/* usb vendor ID */
1258c2ecf20Sopenharmony_ci	u16 product;			/* usb product ID */
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_ci	struct rc_dev *rdev;		/* rc-core device for remote */
1288c2ecf20Sopenharmony_ci	struct input_dev *idev;		/* input device for panel & IR mouse */
1298c2ecf20Sopenharmony_ci	struct input_dev *touch;	/* input device for touchscreen */
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_ci	spinlock_t kc_lock;		/* make sure we get keycodes right */
1328c2ecf20Sopenharmony_ci	u32 kc;				/* current input keycode */
1338c2ecf20Sopenharmony_ci	u32 last_keycode;		/* last reported input keycode */
1348c2ecf20Sopenharmony_ci	u32 rc_scancode;		/* the computed remote scancode */
1358c2ecf20Sopenharmony_ci	u8 rc_toggle;			/* the computed remote toggle bit */
1368c2ecf20Sopenharmony_ci	u64 rc_proto;			/* iMON or MCE (RC6) IR protocol? */
1378c2ecf20Sopenharmony_ci	bool release_code;		/* some keys send a release code */
1388c2ecf20Sopenharmony_ci
1398c2ecf20Sopenharmony_ci	u8 display_type;		/* store the display type */
1408c2ecf20Sopenharmony_ci	bool pad_mouse;			/* toggle kbd(0)/mouse(1) mode */
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_ci	char name_rdev[128];		/* rc input device name */
1438c2ecf20Sopenharmony_ci	char phys_rdev[64];		/* rc input device phys path */
1448c2ecf20Sopenharmony_ci
1458c2ecf20Sopenharmony_ci	char name_idev[128];		/* input device name */
1468c2ecf20Sopenharmony_ci	char phys_idev[64];		/* input device phys path */
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_ci	char name_touch[128];		/* touch screen name */
1498c2ecf20Sopenharmony_ci	char phys_touch[64];		/* touch screen phys path */
1508c2ecf20Sopenharmony_ci	struct timer_list ttimer;	/* touch screen timer */
1518c2ecf20Sopenharmony_ci	int touch_x;			/* x coordinate on touchscreen */
1528c2ecf20Sopenharmony_ci	int touch_y;			/* y coordinate on touchscreen */
1538c2ecf20Sopenharmony_ci	const struct imon_usb_dev_descr *dev_descr;
1548c2ecf20Sopenharmony_ci					/* device description with key */
1558c2ecf20Sopenharmony_ci					/* table for front panels */
1568c2ecf20Sopenharmony_ci	/*
1578c2ecf20Sopenharmony_ci	 * Fields for deferring free_imon_context().
1588c2ecf20Sopenharmony_ci	 *
1598c2ecf20Sopenharmony_ci	 * Since reference to "struct imon_context" is stored into
1608c2ecf20Sopenharmony_ci	 * "struct file"->private_data, we need to remember
1618c2ecf20Sopenharmony_ci	 * how many file descriptors might access this "struct imon_context".
1628c2ecf20Sopenharmony_ci	 */
1638c2ecf20Sopenharmony_ci	refcount_t users;
1648c2ecf20Sopenharmony_ci	/*
1658c2ecf20Sopenharmony_ci	 * Use a flag for telling display_open()/vfd_write()/lcd_write() that
1668c2ecf20Sopenharmony_ci	 * imon_disconnect() was already called.
1678c2ecf20Sopenharmony_ci	 */
1688c2ecf20Sopenharmony_ci	bool disconnected;
1698c2ecf20Sopenharmony_ci	/*
1708c2ecf20Sopenharmony_ci	 * We need to wait for RCU grace period in order to allow
1718c2ecf20Sopenharmony_ci	 * display_open() to safely check ->disconnected and increment ->users.
1728c2ecf20Sopenharmony_ci	 */
1738c2ecf20Sopenharmony_ci	struct rcu_head rcu;
1748c2ecf20Sopenharmony_ci};
1758c2ecf20Sopenharmony_ci
1768c2ecf20Sopenharmony_ci#define TOUCH_TIMEOUT	(HZ/30)
1778c2ecf20Sopenharmony_ci
1788c2ecf20Sopenharmony_ci/* vfd character device file operations */
1798c2ecf20Sopenharmony_cistatic const struct file_operations vfd_fops = {
1808c2ecf20Sopenharmony_ci	.owner		= THIS_MODULE,
1818c2ecf20Sopenharmony_ci	.open		= display_open,
1828c2ecf20Sopenharmony_ci	.write		= vfd_write,
1838c2ecf20Sopenharmony_ci	.release	= display_close,
1848c2ecf20Sopenharmony_ci	.llseek		= noop_llseek,
1858c2ecf20Sopenharmony_ci};
1868c2ecf20Sopenharmony_ci
1878c2ecf20Sopenharmony_ci/* lcd character device file operations */
1888c2ecf20Sopenharmony_cistatic const struct file_operations lcd_fops = {
1898c2ecf20Sopenharmony_ci	.owner		= THIS_MODULE,
1908c2ecf20Sopenharmony_ci	.open		= display_open,
1918c2ecf20Sopenharmony_ci	.write		= lcd_write,
1928c2ecf20Sopenharmony_ci	.release	= display_close,
1938c2ecf20Sopenharmony_ci	.llseek		= noop_llseek,
1948c2ecf20Sopenharmony_ci};
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_cienum {
1978c2ecf20Sopenharmony_ci	IMON_DISPLAY_TYPE_AUTO = 0,
1988c2ecf20Sopenharmony_ci	IMON_DISPLAY_TYPE_VFD  = 1,
1998c2ecf20Sopenharmony_ci	IMON_DISPLAY_TYPE_LCD  = 2,
2008c2ecf20Sopenharmony_ci	IMON_DISPLAY_TYPE_VGA  = 3,
2018c2ecf20Sopenharmony_ci	IMON_DISPLAY_TYPE_NONE = 4,
2028c2ecf20Sopenharmony_ci};
2038c2ecf20Sopenharmony_ci
2048c2ecf20Sopenharmony_cienum {
2058c2ecf20Sopenharmony_ci	IMON_KEY_IMON	= 0,
2068c2ecf20Sopenharmony_ci	IMON_KEY_MCE	= 1,
2078c2ecf20Sopenharmony_ci	IMON_KEY_PANEL	= 2,
2088c2ecf20Sopenharmony_ci};
2098c2ecf20Sopenharmony_ci
2108c2ecf20Sopenharmony_cistatic struct usb_class_driver imon_vfd_class = {
2118c2ecf20Sopenharmony_ci	.name		= DEVICE_NAME,
2128c2ecf20Sopenharmony_ci	.fops		= &vfd_fops,
2138c2ecf20Sopenharmony_ci	.minor_base	= DISPLAY_MINOR_BASE,
2148c2ecf20Sopenharmony_ci};
2158c2ecf20Sopenharmony_ci
2168c2ecf20Sopenharmony_cistatic struct usb_class_driver imon_lcd_class = {
2178c2ecf20Sopenharmony_ci	.name		= DEVICE_NAME,
2188c2ecf20Sopenharmony_ci	.fops		= &lcd_fops,
2198c2ecf20Sopenharmony_ci	.minor_base	= DISPLAY_MINOR_BASE,
2208c2ecf20Sopenharmony_ci};
2218c2ecf20Sopenharmony_ci
2228c2ecf20Sopenharmony_ci/* imon receiver front panel/knob key table */
2238c2ecf20Sopenharmony_cistatic const struct imon_usb_dev_descr imon_default_table = {
2248c2ecf20Sopenharmony_ci	.flags = IMON_NO_FLAGS,
2258c2ecf20Sopenharmony_ci	.key_table = {
2268c2ecf20Sopenharmony_ci		{ 0x000000000f00ffeell, KEY_MEDIA }, /* Go */
2278c2ecf20Sopenharmony_ci		{ 0x000000001200ffeell, KEY_UP },
2288c2ecf20Sopenharmony_ci		{ 0x000000001300ffeell, KEY_DOWN },
2298c2ecf20Sopenharmony_ci		{ 0x000000001400ffeell, KEY_LEFT },
2308c2ecf20Sopenharmony_ci		{ 0x000000001500ffeell, KEY_RIGHT },
2318c2ecf20Sopenharmony_ci		{ 0x000000001600ffeell, KEY_ENTER },
2328c2ecf20Sopenharmony_ci		{ 0x000000001700ffeell, KEY_ESC },
2338c2ecf20Sopenharmony_ci		{ 0x000000001f00ffeell, KEY_AUDIO },
2348c2ecf20Sopenharmony_ci		{ 0x000000002000ffeell, KEY_VIDEO },
2358c2ecf20Sopenharmony_ci		{ 0x000000002100ffeell, KEY_CAMERA },
2368c2ecf20Sopenharmony_ci		{ 0x000000002700ffeell, KEY_DVD },
2378c2ecf20Sopenharmony_ci		{ 0x000000002300ffeell, KEY_TV },
2388c2ecf20Sopenharmony_ci		{ 0x000000002b00ffeell, KEY_EXIT },
2398c2ecf20Sopenharmony_ci		{ 0x000000002c00ffeell, KEY_SELECT },
2408c2ecf20Sopenharmony_ci		{ 0x000000002d00ffeell, KEY_MENU },
2418c2ecf20Sopenharmony_ci		{ 0x000000000500ffeell, KEY_PREVIOUS },
2428c2ecf20Sopenharmony_ci		{ 0x000000000700ffeell, KEY_REWIND },
2438c2ecf20Sopenharmony_ci		{ 0x000000000400ffeell, KEY_STOP },
2448c2ecf20Sopenharmony_ci		{ 0x000000003c00ffeell, KEY_PLAYPAUSE },
2458c2ecf20Sopenharmony_ci		{ 0x000000000800ffeell, KEY_FASTFORWARD },
2468c2ecf20Sopenharmony_ci		{ 0x000000000600ffeell, KEY_NEXT },
2478c2ecf20Sopenharmony_ci		{ 0x000000010000ffeell, KEY_RIGHT },
2488c2ecf20Sopenharmony_ci		{ 0x000001000000ffeell, KEY_LEFT },
2498c2ecf20Sopenharmony_ci		{ 0x000000003d00ffeell, KEY_SELECT },
2508c2ecf20Sopenharmony_ci		{ 0x000100000000ffeell, KEY_VOLUMEUP },
2518c2ecf20Sopenharmony_ci		{ 0x010000000000ffeell, KEY_VOLUMEDOWN },
2528c2ecf20Sopenharmony_ci		{ 0x000000000100ffeell, KEY_MUTE },
2538c2ecf20Sopenharmony_ci		/* 0xffdc iMON MCE VFD */
2548c2ecf20Sopenharmony_ci		{ 0x00010000ffffffeell, KEY_VOLUMEUP },
2558c2ecf20Sopenharmony_ci		{ 0x01000000ffffffeell, KEY_VOLUMEDOWN },
2568c2ecf20Sopenharmony_ci		{ 0x00000001ffffffeell, KEY_MUTE },
2578c2ecf20Sopenharmony_ci		{ 0x0000000fffffffeell, KEY_MEDIA },
2588c2ecf20Sopenharmony_ci		{ 0x00000012ffffffeell, KEY_UP },
2598c2ecf20Sopenharmony_ci		{ 0x00000013ffffffeell, KEY_DOWN },
2608c2ecf20Sopenharmony_ci		{ 0x00000014ffffffeell, KEY_LEFT },
2618c2ecf20Sopenharmony_ci		{ 0x00000015ffffffeell, KEY_RIGHT },
2628c2ecf20Sopenharmony_ci		{ 0x00000016ffffffeell, KEY_ENTER },
2638c2ecf20Sopenharmony_ci		{ 0x00000017ffffffeell, KEY_ESC },
2648c2ecf20Sopenharmony_ci		/* iMON Knob values */
2658c2ecf20Sopenharmony_ci		{ 0x000100ffffffffeell, KEY_VOLUMEUP },
2668c2ecf20Sopenharmony_ci		{ 0x010000ffffffffeell, KEY_VOLUMEDOWN },
2678c2ecf20Sopenharmony_ci		{ 0x000008ffffffffeell, KEY_MUTE },
2688c2ecf20Sopenharmony_ci		{ 0, KEY_RESERVED },
2698c2ecf20Sopenharmony_ci	}
2708c2ecf20Sopenharmony_ci};
2718c2ecf20Sopenharmony_ci
2728c2ecf20Sopenharmony_cistatic const struct imon_usb_dev_descr imon_OEM_VFD = {
2738c2ecf20Sopenharmony_ci	.flags = IMON_NEED_20MS_PKT_DELAY,
2748c2ecf20Sopenharmony_ci	.key_table = {
2758c2ecf20Sopenharmony_ci		{ 0x000000000f00ffeell, KEY_MEDIA }, /* Go */
2768c2ecf20Sopenharmony_ci		{ 0x000000001200ffeell, KEY_UP },
2778c2ecf20Sopenharmony_ci		{ 0x000000001300ffeell, KEY_DOWN },
2788c2ecf20Sopenharmony_ci		{ 0x000000001400ffeell, KEY_LEFT },
2798c2ecf20Sopenharmony_ci		{ 0x000000001500ffeell, KEY_RIGHT },
2808c2ecf20Sopenharmony_ci		{ 0x000000001600ffeell, KEY_ENTER },
2818c2ecf20Sopenharmony_ci		{ 0x000000001700ffeell, KEY_ESC },
2828c2ecf20Sopenharmony_ci		{ 0x000000001f00ffeell, KEY_AUDIO },
2838c2ecf20Sopenharmony_ci		{ 0x000000002b00ffeell, KEY_EXIT },
2848c2ecf20Sopenharmony_ci		{ 0x000000002c00ffeell, KEY_SELECT },
2858c2ecf20Sopenharmony_ci		{ 0x000000002d00ffeell, KEY_MENU },
2868c2ecf20Sopenharmony_ci		{ 0x000000000500ffeell, KEY_PREVIOUS },
2878c2ecf20Sopenharmony_ci		{ 0x000000000700ffeell, KEY_REWIND },
2888c2ecf20Sopenharmony_ci		{ 0x000000000400ffeell, KEY_STOP },
2898c2ecf20Sopenharmony_ci		{ 0x000000003c00ffeell, KEY_PLAYPAUSE },
2908c2ecf20Sopenharmony_ci		{ 0x000000000800ffeell, KEY_FASTFORWARD },
2918c2ecf20Sopenharmony_ci		{ 0x000000000600ffeell, KEY_NEXT },
2928c2ecf20Sopenharmony_ci		{ 0x000000010000ffeell, KEY_RIGHT },
2938c2ecf20Sopenharmony_ci		{ 0x000001000000ffeell, KEY_LEFT },
2948c2ecf20Sopenharmony_ci		{ 0x000000003d00ffeell, KEY_SELECT },
2958c2ecf20Sopenharmony_ci		{ 0x000100000000ffeell, KEY_VOLUMEUP },
2968c2ecf20Sopenharmony_ci		{ 0x010000000000ffeell, KEY_VOLUMEDOWN },
2978c2ecf20Sopenharmony_ci		{ 0x000000000100ffeell, KEY_MUTE },
2988c2ecf20Sopenharmony_ci		/* 0xffdc iMON MCE VFD */
2998c2ecf20Sopenharmony_ci		{ 0x00010000ffffffeell, KEY_VOLUMEUP },
3008c2ecf20Sopenharmony_ci		{ 0x01000000ffffffeell, KEY_VOLUMEDOWN },
3018c2ecf20Sopenharmony_ci		{ 0x00000001ffffffeell, KEY_MUTE },
3028c2ecf20Sopenharmony_ci		{ 0x0000000fffffffeell, KEY_MEDIA },
3038c2ecf20Sopenharmony_ci		{ 0x00000012ffffffeell, KEY_UP },
3048c2ecf20Sopenharmony_ci		{ 0x00000013ffffffeell, KEY_DOWN },
3058c2ecf20Sopenharmony_ci		{ 0x00000014ffffffeell, KEY_LEFT },
3068c2ecf20Sopenharmony_ci		{ 0x00000015ffffffeell, KEY_RIGHT },
3078c2ecf20Sopenharmony_ci		{ 0x00000016ffffffeell, KEY_ENTER },
3088c2ecf20Sopenharmony_ci		{ 0x00000017ffffffeell, KEY_ESC },
3098c2ecf20Sopenharmony_ci		/* iMON Knob values */
3108c2ecf20Sopenharmony_ci		{ 0x000100ffffffffeell, KEY_VOLUMEUP },
3118c2ecf20Sopenharmony_ci		{ 0x010000ffffffffeell, KEY_VOLUMEDOWN },
3128c2ecf20Sopenharmony_ci		{ 0x000008ffffffffeell, KEY_MUTE },
3138c2ecf20Sopenharmony_ci		{ 0, KEY_RESERVED },
3148c2ecf20Sopenharmony_ci	}
3158c2ecf20Sopenharmony_ci};
3168c2ecf20Sopenharmony_ci
3178c2ecf20Sopenharmony_ci/* imon receiver front panel/knob key table for DH102*/
3188c2ecf20Sopenharmony_cistatic const struct imon_usb_dev_descr imon_DH102 = {
3198c2ecf20Sopenharmony_ci	.flags = IMON_NO_FLAGS,
3208c2ecf20Sopenharmony_ci	.key_table = {
3218c2ecf20Sopenharmony_ci		{ 0x000100000000ffeell, KEY_VOLUMEUP },
3228c2ecf20Sopenharmony_ci		{ 0x010000000000ffeell, KEY_VOLUMEDOWN },
3238c2ecf20Sopenharmony_ci		{ 0x000000010000ffeell, KEY_MUTE },
3248c2ecf20Sopenharmony_ci		{ 0x0000000f0000ffeell, KEY_MEDIA },
3258c2ecf20Sopenharmony_ci		{ 0x000000120000ffeell, KEY_UP },
3268c2ecf20Sopenharmony_ci		{ 0x000000130000ffeell, KEY_DOWN },
3278c2ecf20Sopenharmony_ci		{ 0x000000140000ffeell, KEY_LEFT },
3288c2ecf20Sopenharmony_ci		{ 0x000000150000ffeell, KEY_RIGHT },
3298c2ecf20Sopenharmony_ci		{ 0x000000160000ffeell, KEY_ENTER },
3308c2ecf20Sopenharmony_ci		{ 0x000000170000ffeell, KEY_ESC },
3318c2ecf20Sopenharmony_ci		{ 0x0000002b0000ffeell, KEY_EXIT },
3328c2ecf20Sopenharmony_ci		{ 0x0000002c0000ffeell, KEY_SELECT },
3338c2ecf20Sopenharmony_ci		{ 0x0000002d0000ffeell, KEY_MENU },
3348c2ecf20Sopenharmony_ci		{ 0, KEY_RESERVED }
3358c2ecf20Sopenharmony_ci	}
3368c2ecf20Sopenharmony_ci};
3378c2ecf20Sopenharmony_ci
3388c2ecf20Sopenharmony_ci/* imon ultrabay front panel key table */
3398c2ecf20Sopenharmony_cistatic const struct imon_usb_dev_descr ultrabay_table = {
3408c2ecf20Sopenharmony_ci	.flags = IMON_SUPPRESS_REPEATED_KEYS,
3418c2ecf20Sopenharmony_ci	.key_table = {
3428c2ecf20Sopenharmony_ci		{ 0x0000000f0000ffeell, KEY_MEDIA },      /* Go */
3438c2ecf20Sopenharmony_ci		{ 0x000000000100ffeell, KEY_UP },
3448c2ecf20Sopenharmony_ci		{ 0x000000000001ffeell, KEY_DOWN },
3458c2ecf20Sopenharmony_ci		{ 0x000000160000ffeell, KEY_ENTER },
3468c2ecf20Sopenharmony_ci		{ 0x0000001f0000ffeell, KEY_AUDIO },      /* Music */
3478c2ecf20Sopenharmony_ci		{ 0x000000200000ffeell, KEY_VIDEO },      /* Movie */
3488c2ecf20Sopenharmony_ci		{ 0x000000210000ffeell, KEY_CAMERA },     /* Photo */
3498c2ecf20Sopenharmony_ci		{ 0x000000270000ffeell, KEY_DVD },        /* DVD */
3508c2ecf20Sopenharmony_ci		{ 0x000000230000ffeell, KEY_TV },         /* TV */
3518c2ecf20Sopenharmony_ci		{ 0x000000050000ffeell, KEY_PREVIOUS },   /* Previous */
3528c2ecf20Sopenharmony_ci		{ 0x000000070000ffeell, KEY_REWIND },
3538c2ecf20Sopenharmony_ci		{ 0x000000040000ffeell, KEY_STOP },
3548c2ecf20Sopenharmony_ci		{ 0x000000020000ffeell, KEY_PLAYPAUSE },
3558c2ecf20Sopenharmony_ci		{ 0x000000080000ffeell, KEY_FASTFORWARD },
3568c2ecf20Sopenharmony_ci		{ 0x000000060000ffeell, KEY_NEXT },       /* Next */
3578c2ecf20Sopenharmony_ci		{ 0x000100000000ffeell, KEY_VOLUMEUP },
3588c2ecf20Sopenharmony_ci		{ 0x010000000000ffeell, KEY_VOLUMEDOWN },
3598c2ecf20Sopenharmony_ci		{ 0x000000010000ffeell, KEY_MUTE },
3608c2ecf20Sopenharmony_ci		{ 0, KEY_RESERVED },
3618c2ecf20Sopenharmony_ci	}
3628c2ecf20Sopenharmony_ci};
3638c2ecf20Sopenharmony_ci
3648c2ecf20Sopenharmony_ci/*
3658c2ecf20Sopenharmony_ci * USB Device ID for iMON USB Control Boards
3668c2ecf20Sopenharmony_ci *
3678c2ecf20Sopenharmony_ci * The Windows drivers contain 6 different inf files, more or less one for
3688c2ecf20Sopenharmony_ci * each new device until the 0x0034-0x0046 devices, which all use the same
3698c2ecf20Sopenharmony_ci * driver. Some of the devices in the 34-46 range haven't been definitively
3708c2ecf20Sopenharmony_ci * identified yet. Early devices have either a TriGem Computer, Inc. or a
3718c2ecf20Sopenharmony_ci * Samsung vendor ID (0x0aa8 and 0x04e8 respectively), while all later
3728c2ecf20Sopenharmony_ci * devices use the SoundGraph vendor ID (0x15c2). This driver only supports
3738c2ecf20Sopenharmony_ci * the ffdc and later devices, which do onboard decoding.
3748c2ecf20Sopenharmony_ci */
3758c2ecf20Sopenharmony_cistatic const struct usb_device_id imon_usb_id_table[] = {
3768c2ecf20Sopenharmony_ci	/*
3778c2ecf20Sopenharmony_ci	 * Several devices with this same device ID, all use iMON_PAD.inf
3788c2ecf20Sopenharmony_ci	 * SoundGraph iMON PAD (IR & VFD)
3798c2ecf20Sopenharmony_ci	 * SoundGraph iMON PAD (IR & LCD)
3808c2ecf20Sopenharmony_ci	 * SoundGraph iMON Knob (IR only)
3818c2ecf20Sopenharmony_ci	 */
3828c2ecf20Sopenharmony_ci	{ USB_DEVICE(0x15c2, 0xffdc),
3838c2ecf20Sopenharmony_ci	  .driver_info = (unsigned long)&imon_default_table },
3848c2ecf20Sopenharmony_ci
3858c2ecf20Sopenharmony_ci	/*
3868c2ecf20Sopenharmony_ci	 * Newer devices, all driven by the latest iMON Windows driver, full
3878c2ecf20Sopenharmony_ci	 * list of device IDs extracted via 'strings Setup/data1.hdr |grep 15c2'
3888c2ecf20Sopenharmony_ci	 * Need user input to fill in details on unknown devices.
3898c2ecf20Sopenharmony_ci	 */
3908c2ecf20Sopenharmony_ci	/* SoundGraph iMON OEM Touch LCD (IR & 7" VGA LCD) */
3918c2ecf20Sopenharmony_ci	{ USB_DEVICE(0x15c2, 0x0034),
3928c2ecf20Sopenharmony_ci	  .driver_info = (unsigned long)&imon_DH102 },
3938c2ecf20Sopenharmony_ci	/* SoundGraph iMON OEM Touch LCD (IR & 4.3" VGA LCD) */
3948c2ecf20Sopenharmony_ci	{ USB_DEVICE(0x15c2, 0x0035),
3958c2ecf20Sopenharmony_ci	  .driver_info = (unsigned long)&imon_default_table},
3968c2ecf20Sopenharmony_ci	/* SoundGraph iMON OEM VFD (IR & VFD) */
3978c2ecf20Sopenharmony_ci	{ USB_DEVICE(0x15c2, 0x0036),
3988c2ecf20Sopenharmony_ci	  .driver_info = (unsigned long)&imon_OEM_VFD },
3998c2ecf20Sopenharmony_ci	/* device specifics unknown */
4008c2ecf20Sopenharmony_ci	{ USB_DEVICE(0x15c2, 0x0037),
4018c2ecf20Sopenharmony_ci	  .driver_info = (unsigned long)&imon_default_table},
4028c2ecf20Sopenharmony_ci	/* SoundGraph iMON OEM LCD (IR & LCD) */
4038c2ecf20Sopenharmony_ci	{ USB_DEVICE(0x15c2, 0x0038),
4048c2ecf20Sopenharmony_ci	  .driver_info = (unsigned long)&imon_default_table},
4058c2ecf20Sopenharmony_ci	/* SoundGraph iMON UltraBay (IR & LCD) */
4068c2ecf20Sopenharmony_ci	{ USB_DEVICE(0x15c2, 0x0039),
4078c2ecf20Sopenharmony_ci	  .driver_info = (unsigned long)&imon_default_table},
4088c2ecf20Sopenharmony_ci	/* device specifics unknown */
4098c2ecf20Sopenharmony_ci	{ USB_DEVICE(0x15c2, 0x003a),
4108c2ecf20Sopenharmony_ci	  .driver_info = (unsigned long)&imon_default_table},
4118c2ecf20Sopenharmony_ci	/* device specifics unknown */
4128c2ecf20Sopenharmony_ci	{ USB_DEVICE(0x15c2, 0x003b),
4138c2ecf20Sopenharmony_ci	  .driver_info = (unsigned long)&imon_default_table},
4148c2ecf20Sopenharmony_ci	/* SoundGraph iMON OEM Inside (IR only) */
4158c2ecf20Sopenharmony_ci	{ USB_DEVICE(0x15c2, 0x003c),
4168c2ecf20Sopenharmony_ci	  .driver_info = (unsigned long)&imon_default_table},
4178c2ecf20Sopenharmony_ci	/* device specifics unknown */
4188c2ecf20Sopenharmony_ci	{ USB_DEVICE(0x15c2, 0x003d),
4198c2ecf20Sopenharmony_ci	  .driver_info = (unsigned long)&imon_default_table},
4208c2ecf20Sopenharmony_ci	/* device specifics unknown */
4218c2ecf20Sopenharmony_ci	{ USB_DEVICE(0x15c2, 0x003e),
4228c2ecf20Sopenharmony_ci	  .driver_info = (unsigned long)&imon_default_table},
4238c2ecf20Sopenharmony_ci	/* device specifics unknown */
4248c2ecf20Sopenharmony_ci	{ USB_DEVICE(0x15c2, 0x003f),
4258c2ecf20Sopenharmony_ci	  .driver_info = (unsigned long)&imon_default_table},
4268c2ecf20Sopenharmony_ci	/* device specifics unknown */
4278c2ecf20Sopenharmony_ci	{ USB_DEVICE(0x15c2, 0x0040),
4288c2ecf20Sopenharmony_ci	  .driver_info = (unsigned long)&imon_default_table},
4298c2ecf20Sopenharmony_ci	/* SoundGraph iMON MINI (IR only) */
4308c2ecf20Sopenharmony_ci	{ USB_DEVICE(0x15c2, 0x0041),
4318c2ecf20Sopenharmony_ci	  .driver_info = (unsigned long)&imon_default_table},
4328c2ecf20Sopenharmony_ci	/* Antec Veris Multimedia Station EZ External (IR only) */
4338c2ecf20Sopenharmony_ci	{ USB_DEVICE(0x15c2, 0x0042),
4348c2ecf20Sopenharmony_ci	  .driver_info = (unsigned long)&imon_default_table},
4358c2ecf20Sopenharmony_ci	/* Antec Veris Multimedia Station Basic Internal (IR only) */
4368c2ecf20Sopenharmony_ci	{ USB_DEVICE(0x15c2, 0x0043),
4378c2ecf20Sopenharmony_ci	  .driver_info = (unsigned long)&imon_default_table},
4388c2ecf20Sopenharmony_ci	/* Antec Veris Multimedia Station Elite (IR & VFD) */
4398c2ecf20Sopenharmony_ci	{ USB_DEVICE(0x15c2, 0x0044),
4408c2ecf20Sopenharmony_ci	  .driver_info = (unsigned long)&imon_default_table},
4418c2ecf20Sopenharmony_ci	/* Antec Veris Multimedia Station Premiere (IR & LCD) */
4428c2ecf20Sopenharmony_ci	{ USB_DEVICE(0x15c2, 0x0045),
4438c2ecf20Sopenharmony_ci	  .driver_info = (unsigned long)&imon_default_table},
4448c2ecf20Sopenharmony_ci	/* device specifics unknown */
4458c2ecf20Sopenharmony_ci	{ USB_DEVICE(0x15c2, 0x0046),
4468c2ecf20Sopenharmony_ci	  .driver_info = (unsigned long)&imon_default_table},
4478c2ecf20Sopenharmony_ci	{}
4488c2ecf20Sopenharmony_ci};
4498c2ecf20Sopenharmony_ci
4508c2ecf20Sopenharmony_ci/* USB Device data */
4518c2ecf20Sopenharmony_cistatic struct usb_driver imon_driver = {
4528c2ecf20Sopenharmony_ci	.name		= MOD_NAME,
4538c2ecf20Sopenharmony_ci	.probe		= imon_probe,
4548c2ecf20Sopenharmony_ci	.disconnect	= imon_disconnect,
4558c2ecf20Sopenharmony_ci	.suspend	= imon_suspend,
4568c2ecf20Sopenharmony_ci	.resume		= imon_resume,
4578c2ecf20Sopenharmony_ci	.id_table	= imon_usb_id_table,
4588c2ecf20Sopenharmony_ci};
4598c2ecf20Sopenharmony_ci
4608c2ecf20Sopenharmony_ci/* Module bookkeeping bits */
4618c2ecf20Sopenharmony_ciMODULE_AUTHOR(MOD_AUTHOR);
4628c2ecf20Sopenharmony_ciMODULE_DESCRIPTION(MOD_DESC);
4638c2ecf20Sopenharmony_ciMODULE_VERSION(MOD_VERSION);
4648c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
4658c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(usb, imon_usb_id_table);
4668c2ecf20Sopenharmony_ci
4678c2ecf20Sopenharmony_cistatic bool debug;
4688c2ecf20Sopenharmony_cimodule_param(debug, bool, S_IRUGO | S_IWUSR);
4698c2ecf20Sopenharmony_ciMODULE_PARM_DESC(debug, "Debug messages: 0=no, 1=yes (default: no)");
4708c2ecf20Sopenharmony_ci
4718c2ecf20Sopenharmony_ci/* lcd, vfd, vga or none? should be auto-detected, but can be overridden... */
4728c2ecf20Sopenharmony_cistatic int display_type;
4738c2ecf20Sopenharmony_cimodule_param(display_type, int, S_IRUGO);
4748c2ecf20Sopenharmony_ciMODULE_PARM_DESC(display_type, "Type of attached display. 0=autodetect, 1=vfd, 2=lcd, 3=vga, 4=none (default: autodetect)");
4758c2ecf20Sopenharmony_ci
4768c2ecf20Sopenharmony_cistatic int pad_stabilize = 1;
4778c2ecf20Sopenharmony_cimodule_param(pad_stabilize, int, S_IRUGO | S_IWUSR);
4788c2ecf20Sopenharmony_ciMODULE_PARM_DESC(pad_stabilize, "Apply stabilization algorithm to iMON PAD presses in arrow key mode. 0=disable, 1=enable (default).");
4798c2ecf20Sopenharmony_ci
4808c2ecf20Sopenharmony_ci/*
4818c2ecf20Sopenharmony_ci * In certain use cases, mouse mode isn't really helpful, and could actually
4828c2ecf20Sopenharmony_ci * cause confusion, so allow disabling it when the IR device is open.
4838c2ecf20Sopenharmony_ci */
4848c2ecf20Sopenharmony_cistatic bool nomouse;
4858c2ecf20Sopenharmony_cimodule_param(nomouse, bool, S_IRUGO | S_IWUSR);
4868c2ecf20Sopenharmony_ciMODULE_PARM_DESC(nomouse, "Disable mouse input device mode when IR device is open. 0=don't disable, 1=disable. (default: don't disable)");
4878c2ecf20Sopenharmony_ci
4888c2ecf20Sopenharmony_ci/* threshold at which a pad push registers as an arrow key in kbd mode */
4898c2ecf20Sopenharmony_cistatic int pad_thresh;
4908c2ecf20Sopenharmony_cimodule_param(pad_thresh, int, S_IRUGO | S_IWUSR);
4918c2ecf20Sopenharmony_ciMODULE_PARM_DESC(pad_thresh, "Threshold at which a pad push registers as an arrow key in kbd mode (default: 28)");
4928c2ecf20Sopenharmony_ci
4938c2ecf20Sopenharmony_ci
4948c2ecf20Sopenharmony_cistatic void free_imon_context(struct imon_context *ictx)
4958c2ecf20Sopenharmony_ci{
4968c2ecf20Sopenharmony_ci	struct device *dev = ictx->dev;
4978c2ecf20Sopenharmony_ci
4988c2ecf20Sopenharmony_ci	usb_free_urb(ictx->tx_urb);
4998c2ecf20Sopenharmony_ci	WARN_ON(ictx->dev_present_intf0);
5008c2ecf20Sopenharmony_ci	usb_free_urb(ictx->rx_urb_intf0);
5018c2ecf20Sopenharmony_ci	WARN_ON(ictx->dev_present_intf1);
5028c2ecf20Sopenharmony_ci	usb_free_urb(ictx->rx_urb_intf1);
5038c2ecf20Sopenharmony_ci	kfree_rcu(ictx, rcu);
5048c2ecf20Sopenharmony_ci
5058c2ecf20Sopenharmony_ci	dev_dbg(dev, "%s: iMON context freed\n", __func__);
5068c2ecf20Sopenharmony_ci}
5078c2ecf20Sopenharmony_ci
5088c2ecf20Sopenharmony_ci/*
5098c2ecf20Sopenharmony_ci * Called when the Display device (e.g. /dev/lcd0)
5108c2ecf20Sopenharmony_ci * is opened by the application.
5118c2ecf20Sopenharmony_ci */
5128c2ecf20Sopenharmony_cistatic int display_open(struct inode *inode, struct file *file)
5138c2ecf20Sopenharmony_ci{
5148c2ecf20Sopenharmony_ci	struct usb_interface *interface;
5158c2ecf20Sopenharmony_ci	struct imon_context *ictx = NULL;
5168c2ecf20Sopenharmony_ci	int subminor;
5178c2ecf20Sopenharmony_ci	int retval = 0;
5188c2ecf20Sopenharmony_ci
5198c2ecf20Sopenharmony_ci	subminor = iminor(inode);
5208c2ecf20Sopenharmony_ci	interface = usb_find_interface(&imon_driver, subminor);
5218c2ecf20Sopenharmony_ci	if (!interface) {
5228c2ecf20Sopenharmony_ci		pr_err("could not find interface for minor %d\n", subminor);
5238c2ecf20Sopenharmony_ci		retval = -ENODEV;
5248c2ecf20Sopenharmony_ci		goto exit;
5258c2ecf20Sopenharmony_ci	}
5268c2ecf20Sopenharmony_ci
5278c2ecf20Sopenharmony_ci	rcu_read_lock();
5288c2ecf20Sopenharmony_ci	ictx = usb_get_intfdata(interface);
5298c2ecf20Sopenharmony_ci	if (!ictx || ictx->disconnected || !refcount_inc_not_zero(&ictx->users)) {
5308c2ecf20Sopenharmony_ci		rcu_read_unlock();
5318c2ecf20Sopenharmony_ci		pr_err("no context found for minor %d\n", subminor);
5328c2ecf20Sopenharmony_ci		retval = -ENODEV;
5338c2ecf20Sopenharmony_ci		goto exit;
5348c2ecf20Sopenharmony_ci	}
5358c2ecf20Sopenharmony_ci	rcu_read_unlock();
5368c2ecf20Sopenharmony_ci
5378c2ecf20Sopenharmony_ci	mutex_lock(&ictx->lock);
5388c2ecf20Sopenharmony_ci
5398c2ecf20Sopenharmony_ci	if (!ictx->display_supported) {
5408c2ecf20Sopenharmony_ci		pr_err("display not supported by device\n");
5418c2ecf20Sopenharmony_ci		retval = -ENODEV;
5428c2ecf20Sopenharmony_ci	} else if (ictx->display_isopen) {
5438c2ecf20Sopenharmony_ci		pr_err("display port is already open\n");
5448c2ecf20Sopenharmony_ci		retval = -EBUSY;
5458c2ecf20Sopenharmony_ci	} else {
5468c2ecf20Sopenharmony_ci		ictx->display_isopen = true;
5478c2ecf20Sopenharmony_ci		file->private_data = ictx;
5488c2ecf20Sopenharmony_ci		dev_dbg(ictx->dev, "display port opened\n");
5498c2ecf20Sopenharmony_ci	}
5508c2ecf20Sopenharmony_ci
5518c2ecf20Sopenharmony_ci	mutex_unlock(&ictx->lock);
5528c2ecf20Sopenharmony_ci
5538c2ecf20Sopenharmony_ci	if (retval && refcount_dec_and_test(&ictx->users))
5548c2ecf20Sopenharmony_ci		free_imon_context(ictx);
5558c2ecf20Sopenharmony_ci
5568c2ecf20Sopenharmony_ciexit:
5578c2ecf20Sopenharmony_ci	return retval;
5588c2ecf20Sopenharmony_ci}
5598c2ecf20Sopenharmony_ci
5608c2ecf20Sopenharmony_ci/*
5618c2ecf20Sopenharmony_ci * Called when the display device (e.g. /dev/lcd0)
5628c2ecf20Sopenharmony_ci * is closed by the application.
5638c2ecf20Sopenharmony_ci */
5648c2ecf20Sopenharmony_cistatic int display_close(struct inode *inode, struct file *file)
5658c2ecf20Sopenharmony_ci{
5668c2ecf20Sopenharmony_ci	struct imon_context *ictx = file->private_data;
5678c2ecf20Sopenharmony_ci	int retval = 0;
5688c2ecf20Sopenharmony_ci
5698c2ecf20Sopenharmony_ci	mutex_lock(&ictx->lock);
5708c2ecf20Sopenharmony_ci
5718c2ecf20Sopenharmony_ci	if (!ictx->display_supported) {
5728c2ecf20Sopenharmony_ci		pr_err("display not supported by device\n");
5738c2ecf20Sopenharmony_ci		retval = -ENODEV;
5748c2ecf20Sopenharmony_ci	} else if (!ictx->display_isopen) {
5758c2ecf20Sopenharmony_ci		pr_err("display is not open\n");
5768c2ecf20Sopenharmony_ci		retval = -EIO;
5778c2ecf20Sopenharmony_ci	} else {
5788c2ecf20Sopenharmony_ci		ictx->display_isopen = false;
5798c2ecf20Sopenharmony_ci		dev_dbg(ictx->dev, "display port closed\n");
5808c2ecf20Sopenharmony_ci	}
5818c2ecf20Sopenharmony_ci
5828c2ecf20Sopenharmony_ci	mutex_unlock(&ictx->lock);
5838c2ecf20Sopenharmony_ci	if (refcount_dec_and_test(&ictx->users))
5848c2ecf20Sopenharmony_ci		free_imon_context(ictx);
5858c2ecf20Sopenharmony_ci	return retval;
5868c2ecf20Sopenharmony_ci}
5878c2ecf20Sopenharmony_ci
5888c2ecf20Sopenharmony_ci/*
5898c2ecf20Sopenharmony_ci * Sends a packet to the device -- this function must be called with
5908c2ecf20Sopenharmony_ci * ictx->lock held, or its unlock/lock sequence while waiting for tx
5918c2ecf20Sopenharmony_ci * to complete can/will lead to a deadlock.
5928c2ecf20Sopenharmony_ci */
5938c2ecf20Sopenharmony_cistatic int send_packet(struct imon_context *ictx)
5948c2ecf20Sopenharmony_ci{
5958c2ecf20Sopenharmony_ci	unsigned int pipe;
5968c2ecf20Sopenharmony_ci	unsigned long timeout;
5978c2ecf20Sopenharmony_ci	int interval = 0;
5988c2ecf20Sopenharmony_ci	int retval = 0;
5998c2ecf20Sopenharmony_ci	struct usb_ctrlrequest *control_req = NULL;
6008c2ecf20Sopenharmony_ci
6018c2ecf20Sopenharmony_ci	/* Check if we need to use control or interrupt urb */
6028c2ecf20Sopenharmony_ci	if (!ictx->tx_control) {
6038c2ecf20Sopenharmony_ci		pipe = usb_sndintpipe(ictx->usbdev_intf0,
6048c2ecf20Sopenharmony_ci				      ictx->tx_endpoint->bEndpointAddress);
6058c2ecf20Sopenharmony_ci		interval = ictx->tx_endpoint->bInterval;
6068c2ecf20Sopenharmony_ci
6078c2ecf20Sopenharmony_ci		usb_fill_int_urb(ictx->tx_urb, ictx->usbdev_intf0, pipe,
6088c2ecf20Sopenharmony_ci				 ictx->usb_tx_buf,
6098c2ecf20Sopenharmony_ci				 sizeof(ictx->usb_tx_buf),
6108c2ecf20Sopenharmony_ci				 usb_tx_callback, ictx, interval);
6118c2ecf20Sopenharmony_ci
6128c2ecf20Sopenharmony_ci		ictx->tx_urb->actual_length = 0;
6138c2ecf20Sopenharmony_ci	} else {
6148c2ecf20Sopenharmony_ci		/* fill request into kmalloc'ed space: */
6158c2ecf20Sopenharmony_ci		control_req = kmalloc(sizeof(*control_req), GFP_KERNEL);
6168c2ecf20Sopenharmony_ci		if (control_req == NULL)
6178c2ecf20Sopenharmony_ci			return -ENOMEM;
6188c2ecf20Sopenharmony_ci
6198c2ecf20Sopenharmony_ci		/* setup packet is '21 09 0200 0001 0008' */
6208c2ecf20Sopenharmony_ci		control_req->bRequestType = 0x21;
6218c2ecf20Sopenharmony_ci		control_req->bRequest = 0x09;
6228c2ecf20Sopenharmony_ci		control_req->wValue = cpu_to_le16(0x0200);
6238c2ecf20Sopenharmony_ci		control_req->wIndex = cpu_to_le16(0x0001);
6248c2ecf20Sopenharmony_ci		control_req->wLength = cpu_to_le16(0x0008);
6258c2ecf20Sopenharmony_ci
6268c2ecf20Sopenharmony_ci		/* control pipe is endpoint 0x00 */
6278c2ecf20Sopenharmony_ci		pipe = usb_sndctrlpipe(ictx->usbdev_intf0, 0);
6288c2ecf20Sopenharmony_ci
6298c2ecf20Sopenharmony_ci		/* build the control urb */
6308c2ecf20Sopenharmony_ci		usb_fill_control_urb(ictx->tx_urb, ictx->usbdev_intf0,
6318c2ecf20Sopenharmony_ci				     pipe, (unsigned char *)control_req,
6328c2ecf20Sopenharmony_ci				     ictx->usb_tx_buf,
6338c2ecf20Sopenharmony_ci				     sizeof(ictx->usb_tx_buf),
6348c2ecf20Sopenharmony_ci				     usb_tx_callback, ictx);
6358c2ecf20Sopenharmony_ci		ictx->tx_urb->actual_length = 0;
6368c2ecf20Sopenharmony_ci	}
6378c2ecf20Sopenharmony_ci
6388c2ecf20Sopenharmony_ci	reinit_completion(&ictx->tx.finished);
6398c2ecf20Sopenharmony_ci	ictx->tx.busy = true;
6408c2ecf20Sopenharmony_ci	smp_rmb(); /* ensure later readers know we're busy */
6418c2ecf20Sopenharmony_ci
6428c2ecf20Sopenharmony_ci	retval = usb_submit_urb(ictx->tx_urb, GFP_KERNEL);
6438c2ecf20Sopenharmony_ci	if (retval) {
6448c2ecf20Sopenharmony_ci		ictx->tx.busy = false;
6458c2ecf20Sopenharmony_ci		smp_rmb(); /* ensure later readers know we're not busy */
6468c2ecf20Sopenharmony_ci		pr_err_ratelimited("error submitting urb(%d)\n", retval);
6478c2ecf20Sopenharmony_ci	} else {
6488c2ecf20Sopenharmony_ci		/* Wait for transmission to complete (or abort) */
6498c2ecf20Sopenharmony_ci		retval = wait_for_completion_interruptible(
6508c2ecf20Sopenharmony_ci				&ictx->tx.finished);
6518c2ecf20Sopenharmony_ci		if (retval) {
6528c2ecf20Sopenharmony_ci			usb_kill_urb(ictx->tx_urb);
6538c2ecf20Sopenharmony_ci			pr_err_ratelimited("task interrupted\n");
6548c2ecf20Sopenharmony_ci		}
6558c2ecf20Sopenharmony_ci
6568c2ecf20Sopenharmony_ci		ictx->tx.busy = false;
6578c2ecf20Sopenharmony_ci		retval = ictx->tx.status;
6588c2ecf20Sopenharmony_ci		if (retval)
6598c2ecf20Sopenharmony_ci			pr_err_ratelimited("packet tx failed (%d)\n", retval);
6608c2ecf20Sopenharmony_ci	}
6618c2ecf20Sopenharmony_ci
6628c2ecf20Sopenharmony_ci	kfree(control_req);
6638c2ecf20Sopenharmony_ci
6648c2ecf20Sopenharmony_ci	/*
6658c2ecf20Sopenharmony_ci	 * Induce a mandatory delay before returning, as otherwise,
6668c2ecf20Sopenharmony_ci	 * send_packet can get called so rapidly as to overwhelm the device,
6678c2ecf20Sopenharmony_ci	 * particularly on faster systems and/or those with quirky usb.
6688c2ecf20Sopenharmony_ci	 */
6698c2ecf20Sopenharmony_ci	timeout = msecs_to_jiffies(ictx->send_packet_delay);
6708c2ecf20Sopenharmony_ci	set_current_state(TASK_INTERRUPTIBLE);
6718c2ecf20Sopenharmony_ci	schedule_timeout(timeout);
6728c2ecf20Sopenharmony_ci
6738c2ecf20Sopenharmony_ci	return retval;
6748c2ecf20Sopenharmony_ci}
6758c2ecf20Sopenharmony_ci
6768c2ecf20Sopenharmony_ci/*
6778c2ecf20Sopenharmony_ci * Sends an associate packet to the iMON 2.4G.
6788c2ecf20Sopenharmony_ci *
6798c2ecf20Sopenharmony_ci * This might not be such a good idea, since it has an id collision with
6808c2ecf20Sopenharmony_ci * some versions of the "IR & VFD" combo. The only way to determine if it
6818c2ecf20Sopenharmony_ci * is an RF version is to look at the product description string. (Which
6828c2ecf20Sopenharmony_ci * we currently do not fetch).
6838c2ecf20Sopenharmony_ci */
6848c2ecf20Sopenharmony_cistatic int send_associate_24g(struct imon_context *ictx)
6858c2ecf20Sopenharmony_ci{
6868c2ecf20Sopenharmony_ci	int retval;
6878c2ecf20Sopenharmony_ci	const unsigned char packet[8] = { 0x01, 0x00, 0x00, 0x00,
6888c2ecf20Sopenharmony_ci					  0x00, 0x00, 0x00, 0x20 };
6898c2ecf20Sopenharmony_ci
6908c2ecf20Sopenharmony_ci	if (!ictx) {
6918c2ecf20Sopenharmony_ci		pr_err("no context for device\n");
6928c2ecf20Sopenharmony_ci		return -ENODEV;
6938c2ecf20Sopenharmony_ci	}
6948c2ecf20Sopenharmony_ci
6958c2ecf20Sopenharmony_ci	if (!ictx->dev_present_intf0) {
6968c2ecf20Sopenharmony_ci		pr_err("no iMON device present\n");
6978c2ecf20Sopenharmony_ci		return -ENODEV;
6988c2ecf20Sopenharmony_ci	}
6998c2ecf20Sopenharmony_ci
7008c2ecf20Sopenharmony_ci	memcpy(ictx->usb_tx_buf, packet, sizeof(packet));
7018c2ecf20Sopenharmony_ci	retval = send_packet(ictx);
7028c2ecf20Sopenharmony_ci
7038c2ecf20Sopenharmony_ci	return retval;
7048c2ecf20Sopenharmony_ci}
7058c2ecf20Sopenharmony_ci
7068c2ecf20Sopenharmony_ci/*
7078c2ecf20Sopenharmony_ci * Sends packets to setup and show clock on iMON display
7088c2ecf20Sopenharmony_ci *
7098c2ecf20Sopenharmony_ci * Arguments: year - last 2 digits of year, month - 1..12,
7108c2ecf20Sopenharmony_ci * day - 1..31, dow - day of the week (0-Sun...6-Sat),
7118c2ecf20Sopenharmony_ci * hour - 0..23, minute - 0..59, second - 0..59
7128c2ecf20Sopenharmony_ci */
7138c2ecf20Sopenharmony_cistatic int send_set_imon_clock(struct imon_context *ictx,
7148c2ecf20Sopenharmony_ci			       unsigned int year, unsigned int month,
7158c2ecf20Sopenharmony_ci			       unsigned int day, unsigned int dow,
7168c2ecf20Sopenharmony_ci			       unsigned int hour, unsigned int minute,
7178c2ecf20Sopenharmony_ci			       unsigned int second)
7188c2ecf20Sopenharmony_ci{
7198c2ecf20Sopenharmony_ci	unsigned char clock_enable_pkt[IMON_CLOCK_ENABLE_PACKETS][8];
7208c2ecf20Sopenharmony_ci	int retval = 0;
7218c2ecf20Sopenharmony_ci	int i;
7228c2ecf20Sopenharmony_ci
7238c2ecf20Sopenharmony_ci	if (!ictx) {
7248c2ecf20Sopenharmony_ci		pr_err("no context for device\n");
7258c2ecf20Sopenharmony_ci		return -ENODEV;
7268c2ecf20Sopenharmony_ci	}
7278c2ecf20Sopenharmony_ci
7288c2ecf20Sopenharmony_ci	switch (ictx->display_type) {
7298c2ecf20Sopenharmony_ci	case IMON_DISPLAY_TYPE_LCD:
7308c2ecf20Sopenharmony_ci		clock_enable_pkt[0][0] = 0x80;
7318c2ecf20Sopenharmony_ci		clock_enable_pkt[0][1] = year;
7328c2ecf20Sopenharmony_ci		clock_enable_pkt[0][2] = month-1;
7338c2ecf20Sopenharmony_ci		clock_enable_pkt[0][3] = day;
7348c2ecf20Sopenharmony_ci		clock_enable_pkt[0][4] = hour;
7358c2ecf20Sopenharmony_ci		clock_enable_pkt[0][5] = minute;
7368c2ecf20Sopenharmony_ci		clock_enable_pkt[0][6] = second;
7378c2ecf20Sopenharmony_ci
7388c2ecf20Sopenharmony_ci		clock_enable_pkt[1][0] = 0x80;
7398c2ecf20Sopenharmony_ci		clock_enable_pkt[1][1] = 0;
7408c2ecf20Sopenharmony_ci		clock_enable_pkt[1][2] = 0;
7418c2ecf20Sopenharmony_ci		clock_enable_pkt[1][3] = 0;
7428c2ecf20Sopenharmony_ci		clock_enable_pkt[1][4] = 0;
7438c2ecf20Sopenharmony_ci		clock_enable_pkt[1][5] = 0;
7448c2ecf20Sopenharmony_ci		clock_enable_pkt[1][6] = 0;
7458c2ecf20Sopenharmony_ci
7468c2ecf20Sopenharmony_ci		if (ictx->product == 0xffdc) {
7478c2ecf20Sopenharmony_ci			clock_enable_pkt[0][7] = 0x50;
7488c2ecf20Sopenharmony_ci			clock_enable_pkt[1][7] = 0x51;
7498c2ecf20Sopenharmony_ci		} else {
7508c2ecf20Sopenharmony_ci			clock_enable_pkt[0][7] = 0x88;
7518c2ecf20Sopenharmony_ci			clock_enable_pkt[1][7] = 0x8a;
7528c2ecf20Sopenharmony_ci		}
7538c2ecf20Sopenharmony_ci
7548c2ecf20Sopenharmony_ci		break;
7558c2ecf20Sopenharmony_ci
7568c2ecf20Sopenharmony_ci	case IMON_DISPLAY_TYPE_VFD:
7578c2ecf20Sopenharmony_ci		clock_enable_pkt[0][0] = year;
7588c2ecf20Sopenharmony_ci		clock_enable_pkt[0][1] = month-1;
7598c2ecf20Sopenharmony_ci		clock_enable_pkt[0][2] = day;
7608c2ecf20Sopenharmony_ci		clock_enable_pkt[0][3] = dow;
7618c2ecf20Sopenharmony_ci		clock_enable_pkt[0][4] = hour;
7628c2ecf20Sopenharmony_ci		clock_enable_pkt[0][5] = minute;
7638c2ecf20Sopenharmony_ci		clock_enable_pkt[0][6] = second;
7648c2ecf20Sopenharmony_ci		clock_enable_pkt[0][7] = 0x40;
7658c2ecf20Sopenharmony_ci
7668c2ecf20Sopenharmony_ci		clock_enable_pkt[1][0] = 0;
7678c2ecf20Sopenharmony_ci		clock_enable_pkt[1][1] = 0;
7688c2ecf20Sopenharmony_ci		clock_enable_pkt[1][2] = 1;
7698c2ecf20Sopenharmony_ci		clock_enable_pkt[1][3] = 0;
7708c2ecf20Sopenharmony_ci		clock_enable_pkt[1][4] = 0;
7718c2ecf20Sopenharmony_ci		clock_enable_pkt[1][5] = 0;
7728c2ecf20Sopenharmony_ci		clock_enable_pkt[1][6] = 0;
7738c2ecf20Sopenharmony_ci		clock_enable_pkt[1][7] = 0x42;
7748c2ecf20Sopenharmony_ci
7758c2ecf20Sopenharmony_ci		break;
7768c2ecf20Sopenharmony_ci
7778c2ecf20Sopenharmony_ci	default:
7788c2ecf20Sopenharmony_ci		return -ENODEV;
7798c2ecf20Sopenharmony_ci	}
7808c2ecf20Sopenharmony_ci
7818c2ecf20Sopenharmony_ci	for (i = 0; i < IMON_CLOCK_ENABLE_PACKETS; i++) {
7828c2ecf20Sopenharmony_ci		memcpy(ictx->usb_tx_buf, clock_enable_pkt[i], 8);
7838c2ecf20Sopenharmony_ci		retval = send_packet(ictx);
7848c2ecf20Sopenharmony_ci		if (retval) {
7858c2ecf20Sopenharmony_ci			pr_err("send_packet failed for packet %d\n", i);
7868c2ecf20Sopenharmony_ci			break;
7878c2ecf20Sopenharmony_ci		}
7888c2ecf20Sopenharmony_ci	}
7898c2ecf20Sopenharmony_ci
7908c2ecf20Sopenharmony_ci	return retval;
7918c2ecf20Sopenharmony_ci}
7928c2ecf20Sopenharmony_ci
7938c2ecf20Sopenharmony_ci/*
7948c2ecf20Sopenharmony_ci * These are the sysfs functions to handle the association on the iMON 2.4G LT.
7958c2ecf20Sopenharmony_ci */
7968c2ecf20Sopenharmony_cistatic ssize_t show_associate_remote(struct device *d,
7978c2ecf20Sopenharmony_ci				     struct device_attribute *attr,
7988c2ecf20Sopenharmony_ci				     char *buf)
7998c2ecf20Sopenharmony_ci{
8008c2ecf20Sopenharmony_ci	struct imon_context *ictx = dev_get_drvdata(d);
8018c2ecf20Sopenharmony_ci
8028c2ecf20Sopenharmony_ci	if (!ictx)
8038c2ecf20Sopenharmony_ci		return -ENODEV;
8048c2ecf20Sopenharmony_ci
8058c2ecf20Sopenharmony_ci	mutex_lock(&ictx->lock);
8068c2ecf20Sopenharmony_ci	if (ictx->rf_isassociating)
8078c2ecf20Sopenharmony_ci		strscpy(buf, "associating\n", PAGE_SIZE);
8088c2ecf20Sopenharmony_ci	else
8098c2ecf20Sopenharmony_ci		strscpy(buf, "closed\n", PAGE_SIZE);
8108c2ecf20Sopenharmony_ci
8118c2ecf20Sopenharmony_ci	dev_info(d, "Visit https://www.lirc.org/html/imon-24g.html for instructions on how to associate your iMON 2.4G DT/LT remote\n");
8128c2ecf20Sopenharmony_ci	mutex_unlock(&ictx->lock);
8138c2ecf20Sopenharmony_ci	return strlen(buf);
8148c2ecf20Sopenharmony_ci}
8158c2ecf20Sopenharmony_ci
8168c2ecf20Sopenharmony_cistatic ssize_t store_associate_remote(struct device *d,
8178c2ecf20Sopenharmony_ci				      struct device_attribute *attr,
8188c2ecf20Sopenharmony_ci				      const char *buf, size_t count)
8198c2ecf20Sopenharmony_ci{
8208c2ecf20Sopenharmony_ci	struct imon_context *ictx;
8218c2ecf20Sopenharmony_ci
8228c2ecf20Sopenharmony_ci	ictx = dev_get_drvdata(d);
8238c2ecf20Sopenharmony_ci
8248c2ecf20Sopenharmony_ci	if (!ictx)
8258c2ecf20Sopenharmony_ci		return -ENODEV;
8268c2ecf20Sopenharmony_ci
8278c2ecf20Sopenharmony_ci	mutex_lock(&ictx->lock);
8288c2ecf20Sopenharmony_ci	ictx->rf_isassociating = true;
8298c2ecf20Sopenharmony_ci	send_associate_24g(ictx);
8308c2ecf20Sopenharmony_ci	mutex_unlock(&ictx->lock);
8318c2ecf20Sopenharmony_ci
8328c2ecf20Sopenharmony_ci	return count;
8338c2ecf20Sopenharmony_ci}
8348c2ecf20Sopenharmony_ci
8358c2ecf20Sopenharmony_ci/*
8368c2ecf20Sopenharmony_ci * sysfs functions to control internal imon clock
8378c2ecf20Sopenharmony_ci */
8388c2ecf20Sopenharmony_cistatic ssize_t show_imon_clock(struct device *d,
8398c2ecf20Sopenharmony_ci			       struct device_attribute *attr, char *buf)
8408c2ecf20Sopenharmony_ci{
8418c2ecf20Sopenharmony_ci	struct imon_context *ictx = dev_get_drvdata(d);
8428c2ecf20Sopenharmony_ci	size_t len;
8438c2ecf20Sopenharmony_ci
8448c2ecf20Sopenharmony_ci	if (!ictx)
8458c2ecf20Sopenharmony_ci		return -ENODEV;
8468c2ecf20Sopenharmony_ci
8478c2ecf20Sopenharmony_ci	mutex_lock(&ictx->lock);
8488c2ecf20Sopenharmony_ci
8498c2ecf20Sopenharmony_ci	if (!ictx->display_supported) {
8508c2ecf20Sopenharmony_ci		len = snprintf(buf, PAGE_SIZE, "Not supported.");
8518c2ecf20Sopenharmony_ci	} else {
8528c2ecf20Sopenharmony_ci		len = snprintf(buf, PAGE_SIZE,
8538c2ecf20Sopenharmony_ci			"To set the clock on your iMON display:\n"
8548c2ecf20Sopenharmony_ci			"# date \"+%%y %%m %%d %%w %%H %%M %%S\" > imon_clock\n"
8558c2ecf20Sopenharmony_ci			"%s", ictx->display_isopen ?
8568c2ecf20Sopenharmony_ci			"\nNOTE: imon device must be closed\n" : "");
8578c2ecf20Sopenharmony_ci	}
8588c2ecf20Sopenharmony_ci
8598c2ecf20Sopenharmony_ci	mutex_unlock(&ictx->lock);
8608c2ecf20Sopenharmony_ci
8618c2ecf20Sopenharmony_ci	return len;
8628c2ecf20Sopenharmony_ci}
8638c2ecf20Sopenharmony_ci
8648c2ecf20Sopenharmony_cistatic ssize_t store_imon_clock(struct device *d,
8658c2ecf20Sopenharmony_ci				struct device_attribute *attr,
8668c2ecf20Sopenharmony_ci				const char *buf, size_t count)
8678c2ecf20Sopenharmony_ci{
8688c2ecf20Sopenharmony_ci	struct imon_context *ictx = dev_get_drvdata(d);
8698c2ecf20Sopenharmony_ci	ssize_t retval;
8708c2ecf20Sopenharmony_ci	unsigned int year, month, day, dow, hour, minute, second;
8718c2ecf20Sopenharmony_ci
8728c2ecf20Sopenharmony_ci	if (!ictx)
8738c2ecf20Sopenharmony_ci		return -ENODEV;
8748c2ecf20Sopenharmony_ci
8758c2ecf20Sopenharmony_ci	mutex_lock(&ictx->lock);
8768c2ecf20Sopenharmony_ci
8778c2ecf20Sopenharmony_ci	if (!ictx->display_supported) {
8788c2ecf20Sopenharmony_ci		retval = -ENODEV;
8798c2ecf20Sopenharmony_ci		goto exit;
8808c2ecf20Sopenharmony_ci	} else if (ictx->display_isopen) {
8818c2ecf20Sopenharmony_ci		retval = -EBUSY;
8828c2ecf20Sopenharmony_ci		goto exit;
8838c2ecf20Sopenharmony_ci	}
8848c2ecf20Sopenharmony_ci
8858c2ecf20Sopenharmony_ci	if (sscanf(buf, "%u %u %u %u %u %u %u",	&year, &month, &day, &dow,
8868c2ecf20Sopenharmony_ci		   &hour, &minute, &second) != 7) {
8878c2ecf20Sopenharmony_ci		retval = -EINVAL;
8888c2ecf20Sopenharmony_ci		goto exit;
8898c2ecf20Sopenharmony_ci	}
8908c2ecf20Sopenharmony_ci
8918c2ecf20Sopenharmony_ci	if ((month < 1 || month > 12) ||
8928c2ecf20Sopenharmony_ci	    (day < 1 || day > 31) || (dow > 6) ||
8938c2ecf20Sopenharmony_ci	    (hour > 23) || (minute > 59) || (second > 59)) {
8948c2ecf20Sopenharmony_ci		retval = -EINVAL;
8958c2ecf20Sopenharmony_ci		goto exit;
8968c2ecf20Sopenharmony_ci	}
8978c2ecf20Sopenharmony_ci
8988c2ecf20Sopenharmony_ci	retval = send_set_imon_clock(ictx, year, month, day, dow,
8998c2ecf20Sopenharmony_ci				     hour, minute, second);
9008c2ecf20Sopenharmony_ci	if (retval)
9018c2ecf20Sopenharmony_ci		goto exit;
9028c2ecf20Sopenharmony_ci
9038c2ecf20Sopenharmony_ci	retval = count;
9048c2ecf20Sopenharmony_ciexit:
9058c2ecf20Sopenharmony_ci	mutex_unlock(&ictx->lock);
9068c2ecf20Sopenharmony_ci
9078c2ecf20Sopenharmony_ci	return retval;
9088c2ecf20Sopenharmony_ci}
9098c2ecf20Sopenharmony_ci
9108c2ecf20Sopenharmony_ci
9118c2ecf20Sopenharmony_cistatic DEVICE_ATTR(imon_clock, S_IWUSR | S_IRUGO, show_imon_clock,
9128c2ecf20Sopenharmony_ci		   store_imon_clock);
9138c2ecf20Sopenharmony_ci
9148c2ecf20Sopenharmony_cistatic DEVICE_ATTR(associate_remote, S_IWUSR | S_IRUGO, show_associate_remote,
9158c2ecf20Sopenharmony_ci		   store_associate_remote);
9168c2ecf20Sopenharmony_ci
9178c2ecf20Sopenharmony_cistatic struct attribute *imon_display_sysfs_entries[] = {
9188c2ecf20Sopenharmony_ci	&dev_attr_imon_clock.attr,
9198c2ecf20Sopenharmony_ci	NULL
9208c2ecf20Sopenharmony_ci};
9218c2ecf20Sopenharmony_ci
9228c2ecf20Sopenharmony_cistatic const struct attribute_group imon_display_attr_group = {
9238c2ecf20Sopenharmony_ci	.attrs = imon_display_sysfs_entries
9248c2ecf20Sopenharmony_ci};
9258c2ecf20Sopenharmony_ci
9268c2ecf20Sopenharmony_cistatic struct attribute *imon_rf_sysfs_entries[] = {
9278c2ecf20Sopenharmony_ci	&dev_attr_associate_remote.attr,
9288c2ecf20Sopenharmony_ci	NULL
9298c2ecf20Sopenharmony_ci};
9308c2ecf20Sopenharmony_ci
9318c2ecf20Sopenharmony_cistatic const struct attribute_group imon_rf_attr_group = {
9328c2ecf20Sopenharmony_ci	.attrs = imon_rf_sysfs_entries
9338c2ecf20Sopenharmony_ci};
9348c2ecf20Sopenharmony_ci
9358c2ecf20Sopenharmony_ci/*
9368c2ecf20Sopenharmony_ci * Writes data to the VFD.  The iMON VFD is 2x16 characters
9378c2ecf20Sopenharmony_ci * and requires data in 5 consecutive USB interrupt packets,
9388c2ecf20Sopenharmony_ci * each packet but the last carrying 7 bytes.
9398c2ecf20Sopenharmony_ci *
9408c2ecf20Sopenharmony_ci * I don't know if the VFD board supports features such as
9418c2ecf20Sopenharmony_ci * scrolling, clearing rows, blanking, etc. so at
9428c2ecf20Sopenharmony_ci * the caller must provide a full screen of data.  If fewer
9438c2ecf20Sopenharmony_ci * than 32 bytes are provided spaces will be appended to
9448c2ecf20Sopenharmony_ci * generate a full screen.
9458c2ecf20Sopenharmony_ci */
9468c2ecf20Sopenharmony_cistatic ssize_t vfd_write(struct file *file, const char __user *buf,
9478c2ecf20Sopenharmony_ci			 size_t n_bytes, loff_t *pos)
9488c2ecf20Sopenharmony_ci{
9498c2ecf20Sopenharmony_ci	int i;
9508c2ecf20Sopenharmony_ci	int offset;
9518c2ecf20Sopenharmony_ci	int seq;
9528c2ecf20Sopenharmony_ci	int retval = 0;
9538c2ecf20Sopenharmony_ci	struct imon_context *ictx = file->private_data;
9548c2ecf20Sopenharmony_ci	static const unsigned char vfd_packet6[] = {
9558c2ecf20Sopenharmony_ci		0x01, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF };
9568c2ecf20Sopenharmony_ci
9578c2ecf20Sopenharmony_ci	if (ictx->disconnected)
9588c2ecf20Sopenharmony_ci		return -ENODEV;
9598c2ecf20Sopenharmony_ci
9608c2ecf20Sopenharmony_ci	if (mutex_lock_interruptible(&ictx->lock))
9618c2ecf20Sopenharmony_ci		return -ERESTARTSYS;
9628c2ecf20Sopenharmony_ci
9638c2ecf20Sopenharmony_ci	if (!ictx->dev_present_intf0) {
9648c2ecf20Sopenharmony_ci		pr_err_ratelimited("no iMON device present\n");
9658c2ecf20Sopenharmony_ci		retval = -ENODEV;
9668c2ecf20Sopenharmony_ci		goto exit;
9678c2ecf20Sopenharmony_ci	}
9688c2ecf20Sopenharmony_ci
9698c2ecf20Sopenharmony_ci	if (n_bytes <= 0 || n_bytes > 32) {
9708c2ecf20Sopenharmony_ci		pr_err_ratelimited("invalid payload size\n");
9718c2ecf20Sopenharmony_ci		retval = -EINVAL;
9728c2ecf20Sopenharmony_ci		goto exit;
9738c2ecf20Sopenharmony_ci	}
9748c2ecf20Sopenharmony_ci
9758c2ecf20Sopenharmony_ci	if (copy_from_user(ictx->tx.data_buf, buf, n_bytes)) {
9768c2ecf20Sopenharmony_ci		retval = -EFAULT;
9778c2ecf20Sopenharmony_ci		goto exit;
9788c2ecf20Sopenharmony_ci	}
9798c2ecf20Sopenharmony_ci
9808c2ecf20Sopenharmony_ci	/* Pad with spaces */
9818c2ecf20Sopenharmony_ci	for (i = n_bytes; i < 32; ++i)
9828c2ecf20Sopenharmony_ci		ictx->tx.data_buf[i] = ' ';
9838c2ecf20Sopenharmony_ci
9848c2ecf20Sopenharmony_ci	for (i = 32; i < 35; ++i)
9858c2ecf20Sopenharmony_ci		ictx->tx.data_buf[i] = 0xFF;
9868c2ecf20Sopenharmony_ci
9878c2ecf20Sopenharmony_ci	offset = 0;
9888c2ecf20Sopenharmony_ci	seq = 0;
9898c2ecf20Sopenharmony_ci
9908c2ecf20Sopenharmony_ci	do {
9918c2ecf20Sopenharmony_ci		memcpy(ictx->usb_tx_buf, ictx->tx.data_buf + offset, 7);
9928c2ecf20Sopenharmony_ci		ictx->usb_tx_buf[7] = (unsigned char) seq;
9938c2ecf20Sopenharmony_ci
9948c2ecf20Sopenharmony_ci		retval = send_packet(ictx);
9958c2ecf20Sopenharmony_ci		if (retval) {
9968c2ecf20Sopenharmony_ci			pr_err_ratelimited("send packet #%d failed\n", seq / 2);
9978c2ecf20Sopenharmony_ci			goto exit;
9988c2ecf20Sopenharmony_ci		} else {
9998c2ecf20Sopenharmony_ci			seq += 2;
10008c2ecf20Sopenharmony_ci			offset += 7;
10018c2ecf20Sopenharmony_ci		}
10028c2ecf20Sopenharmony_ci
10038c2ecf20Sopenharmony_ci	} while (offset < 35);
10048c2ecf20Sopenharmony_ci
10058c2ecf20Sopenharmony_ci	/* Send packet #6 */
10068c2ecf20Sopenharmony_ci	memcpy(ictx->usb_tx_buf, &vfd_packet6, sizeof(vfd_packet6));
10078c2ecf20Sopenharmony_ci	ictx->usb_tx_buf[7] = (unsigned char) seq;
10088c2ecf20Sopenharmony_ci	retval = send_packet(ictx);
10098c2ecf20Sopenharmony_ci	if (retval)
10108c2ecf20Sopenharmony_ci		pr_err_ratelimited("send packet #%d failed\n", seq / 2);
10118c2ecf20Sopenharmony_ci
10128c2ecf20Sopenharmony_ciexit:
10138c2ecf20Sopenharmony_ci	mutex_unlock(&ictx->lock);
10148c2ecf20Sopenharmony_ci
10158c2ecf20Sopenharmony_ci	return (!retval) ? n_bytes : retval;
10168c2ecf20Sopenharmony_ci}
10178c2ecf20Sopenharmony_ci
10188c2ecf20Sopenharmony_ci/*
10198c2ecf20Sopenharmony_ci * Writes data to the LCD.  The iMON OEM LCD screen expects 8-byte
10208c2ecf20Sopenharmony_ci * packets. We accept data as 16 hexadecimal digits, followed by a
10218c2ecf20Sopenharmony_ci * newline (to make it easy to drive the device from a command-line
10228c2ecf20Sopenharmony_ci * -- even though the actual binary data is a bit complicated).
10238c2ecf20Sopenharmony_ci *
10248c2ecf20Sopenharmony_ci * The device itself is not a "traditional" text-mode display. It's
10258c2ecf20Sopenharmony_ci * actually a 16x96 pixel bitmap display. That means if you want to
10268c2ecf20Sopenharmony_ci * display text, you've got to have your own "font" and translate the
10278c2ecf20Sopenharmony_ci * text into bitmaps for display. This is really flexible (you can
10288c2ecf20Sopenharmony_ci * display whatever diacritics you need, and so on), but it's also
10298c2ecf20Sopenharmony_ci * a lot more complicated than most LCDs...
10308c2ecf20Sopenharmony_ci */
10318c2ecf20Sopenharmony_cistatic ssize_t lcd_write(struct file *file, const char __user *buf,
10328c2ecf20Sopenharmony_ci			 size_t n_bytes, loff_t *pos)
10338c2ecf20Sopenharmony_ci{
10348c2ecf20Sopenharmony_ci	int retval = 0;
10358c2ecf20Sopenharmony_ci	struct imon_context *ictx = file->private_data;
10368c2ecf20Sopenharmony_ci
10378c2ecf20Sopenharmony_ci	if (ictx->disconnected)
10388c2ecf20Sopenharmony_ci		return -ENODEV;
10398c2ecf20Sopenharmony_ci
10408c2ecf20Sopenharmony_ci	mutex_lock(&ictx->lock);
10418c2ecf20Sopenharmony_ci
10428c2ecf20Sopenharmony_ci	if (!ictx->display_supported) {
10438c2ecf20Sopenharmony_ci		pr_err_ratelimited("no iMON display present\n");
10448c2ecf20Sopenharmony_ci		retval = -ENODEV;
10458c2ecf20Sopenharmony_ci		goto exit;
10468c2ecf20Sopenharmony_ci	}
10478c2ecf20Sopenharmony_ci
10488c2ecf20Sopenharmony_ci	if (n_bytes != 8) {
10498c2ecf20Sopenharmony_ci		pr_err_ratelimited("invalid payload size: %d (expected 8)\n",
10508c2ecf20Sopenharmony_ci				   (int)n_bytes);
10518c2ecf20Sopenharmony_ci		retval = -EINVAL;
10528c2ecf20Sopenharmony_ci		goto exit;
10538c2ecf20Sopenharmony_ci	}
10548c2ecf20Sopenharmony_ci
10558c2ecf20Sopenharmony_ci	if (copy_from_user(ictx->usb_tx_buf, buf, 8)) {
10568c2ecf20Sopenharmony_ci		retval = -EFAULT;
10578c2ecf20Sopenharmony_ci		goto exit;
10588c2ecf20Sopenharmony_ci	}
10598c2ecf20Sopenharmony_ci
10608c2ecf20Sopenharmony_ci	retval = send_packet(ictx);
10618c2ecf20Sopenharmony_ci	if (retval) {
10628c2ecf20Sopenharmony_ci		pr_err_ratelimited("send packet failed!\n");
10638c2ecf20Sopenharmony_ci		goto exit;
10648c2ecf20Sopenharmony_ci	} else {
10658c2ecf20Sopenharmony_ci		dev_dbg(ictx->dev, "%s: write %d bytes to LCD\n",
10668c2ecf20Sopenharmony_ci			__func__, (int) n_bytes);
10678c2ecf20Sopenharmony_ci	}
10688c2ecf20Sopenharmony_ciexit:
10698c2ecf20Sopenharmony_ci	mutex_unlock(&ictx->lock);
10708c2ecf20Sopenharmony_ci	return (!retval) ? n_bytes : retval;
10718c2ecf20Sopenharmony_ci}
10728c2ecf20Sopenharmony_ci
10738c2ecf20Sopenharmony_ci/*
10748c2ecf20Sopenharmony_ci * Callback function for USB core API: transmit data
10758c2ecf20Sopenharmony_ci */
10768c2ecf20Sopenharmony_cistatic void usb_tx_callback(struct urb *urb)
10778c2ecf20Sopenharmony_ci{
10788c2ecf20Sopenharmony_ci	struct imon_context *ictx;
10798c2ecf20Sopenharmony_ci
10808c2ecf20Sopenharmony_ci	if (!urb)
10818c2ecf20Sopenharmony_ci		return;
10828c2ecf20Sopenharmony_ci	ictx = (struct imon_context *)urb->context;
10838c2ecf20Sopenharmony_ci	if (!ictx)
10848c2ecf20Sopenharmony_ci		return;
10858c2ecf20Sopenharmony_ci
10868c2ecf20Sopenharmony_ci	ictx->tx.status = urb->status;
10878c2ecf20Sopenharmony_ci
10888c2ecf20Sopenharmony_ci	/* notify waiters that write has finished */
10898c2ecf20Sopenharmony_ci	ictx->tx.busy = false;
10908c2ecf20Sopenharmony_ci	smp_rmb(); /* ensure later readers know we're not busy */
10918c2ecf20Sopenharmony_ci	complete(&ictx->tx.finished);
10928c2ecf20Sopenharmony_ci}
10938c2ecf20Sopenharmony_ci
10948c2ecf20Sopenharmony_ci/*
10958c2ecf20Sopenharmony_ci * report touchscreen input
10968c2ecf20Sopenharmony_ci */
10978c2ecf20Sopenharmony_cistatic void imon_touch_display_timeout(struct timer_list *t)
10988c2ecf20Sopenharmony_ci{
10998c2ecf20Sopenharmony_ci	struct imon_context *ictx = from_timer(ictx, t, ttimer);
11008c2ecf20Sopenharmony_ci
11018c2ecf20Sopenharmony_ci	if (ictx->display_type != IMON_DISPLAY_TYPE_VGA)
11028c2ecf20Sopenharmony_ci		return;
11038c2ecf20Sopenharmony_ci
11048c2ecf20Sopenharmony_ci	input_report_abs(ictx->touch, ABS_X, ictx->touch_x);
11058c2ecf20Sopenharmony_ci	input_report_abs(ictx->touch, ABS_Y, ictx->touch_y);
11068c2ecf20Sopenharmony_ci	input_report_key(ictx->touch, BTN_TOUCH, 0x00);
11078c2ecf20Sopenharmony_ci	input_sync(ictx->touch);
11088c2ecf20Sopenharmony_ci}
11098c2ecf20Sopenharmony_ci
11108c2ecf20Sopenharmony_ci/*
11118c2ecf20Sopenharmony_ci * iMON IR receivers support two different signal sets -- those used by
11128c2ecf20Sopenharmony_ci * the iMON remotes, and those used by the Windows MCE remotes (which is
11138c2ecf20Sopenharmony_ci * really just RC-6), but only one or the other at a time, as the signals
11148c2ecf20Sopenharmony_ci * are decoded onboard the receiver.
11158c2ecf20Sopenharmony_ci *
11168c2ecf20Sopenharmony_ci * This function gets called two different ways, one way is from
11178c2ecf20Sopenharmony_ci * rc_register_device, for initial protocol selection/setup, and the other is
11188c2ecf20Sopenharmony_ci * via a userspace-initiated protocol change request, either by direct sysfs
11198c2ecf20Sopenharmony_ci * prodding or by something like ir-keytable. In the rc_register_device case,
11208c2ecf20Sopenharmony_ci * the imon context lock is already held, but when initiated from userspace,
11218c2ecf20Sopenharmony_ci * it is not, so we must acquire it prior to calling send_packet, which
11228c2ecf20Sopenharmony_ci * requires that the lock is held.
11238c2ecf20Sopenharmony_ci */
11248c2ecf20Sopenharmony_cistatic int imon_ir_change_protocol(struct rc_dev *rc, u64 *rc_proto)
11258c2ecf20Sopenharmony_ci{
11268c2ecf20Sopenharmony_ci	int retval;
11278c2ecf20Sopenharmony_ci	struct imon_context *ictx = rc->priv;
11288c2ecf20Sopenharmony_ci	struct device *dev = ictx->dev;
11298c2ecf20Sopenharmony_ci	bool unlock = false;
11308c2ecf20Sopenharmony_ci	unsigned char ir_proto_packet[] = {
11318c2ecf20Sopenharmony_ci		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x86 };
11328c2ecf20Sopenharmony_ci
11338c2ecf20Sopenharmony_ci	if (*rc_proto && !(*rc_proto & rc->allowed_protocols))
11348c2ecf20Sopenharmony_ci		dev_warn(dev, "Looks like you're trying to use an IR protocol this device does not support\n");
11358c2ecf20Sopenharmony_ci
11368c2ecf20Sopenharmony_ci	if (*rc_proto & RC_PROTO_BIT_RC6_MCE) {
11378c2ecf20Sopenharmony_ci		dev_dbg(dev, "Configuring IR receiver for MCE protocol\n");
11388c2ecf20Sopenharmony_ci		ir_proto_packet[0] = 0x01;
11398c2ecf20Sopenharmony_ci		*rc_proto = RC_PROTO_BIT_RC6_MCE;
11408c2ecf20Sopenharmony_ci	} else if (*rc_proto & RC_PROTO_BIT_IMON) {
11418c2ecf20Sopenharmony_ci		dev_dbg(dev, "Configuring IR receiver for iMON protocol\n");
11428c2ecf20Sopenharmony_ci		if (!pad_stabilize)
11438c2ecf20Sopenharmony_ci			dev_dbg(dev, "PAD stabilize functionality disabled\n");
11448c2ecf20Sopenharmony_ci		/* ir_proto_packet[0] = 0x00; // already the default */
11458c2ecf20Sopenharmony_ci		*rc_proto = RC_PROTO_BIT_IMON;
11468c2ecf20Sopenharmony_ci	} else {
11478c2ecf20Sopenharmony_ci		dev_warn(dev, "Unsupported IR protocol specified, overriding to iMON IR protocol\n");
11488c2ecf20Sopenharmony_ci		if (!pad_stabilize)
11498c2ecf20Sopenharmony_ci			dev_dbg(dev, "PAD stabilize functionality disabled\n");
11508c2ecf20Sopenharmony_ci		/* ir_proto_packet[0] = 0x00; // already the default */
11518c2ecf20Sopenharmony_ci		*rc_proto = RC_PROTO_BIT_IMON;
11528c2ecf20Sopenharmony_ci	}
11538c2ecf20Sopenharmony_ci
11548c2ecf20Sopenharmony_ci	memcpy(ictx->usb_tx_buf, &ir_proto_packet, sizeof(ir_proto_packet));
11558c2ecf20Sopenharmony_ci
11568c2ecf20Sopenharmony_ci	if (!mutex_is_locked(&ictx->lock)) {
11578c2ecf20Sopenharmony_ci		unlock = true;
11588c2ecf20Sopenharmony_ci		mutex_lock(&ictx->lock);
11598c2ecf20Sopenharmony_ci	}
11608c2ecf20Sopenharmony_ci
11618c2ecf20Sopenharmony_ci	retval = send_packet(ictx);
11628c2ecf20Sopenharmony_ci	if (retval)
11638c2ecf20Sopenharmony_ci		goto out;
11648c2ecf20Sopenharmony_ci
11658c2ecf20Sopenharmony_ci	ictx->rc_proto = *rc_proto;
11668c2ecf20Sopenharmony_ci	ictx->pad_mouse = false;
11678c2ecf20Sopenharmony_ci
11688c2ecf20Sopenharmony_ciout:
11698c2ecf20Sopenharmony_ci	if (unlock)
11708c2ecf20Sopenharmony_ci		mutex_unlock(&ictx->lock);
11718c2ecf20Sopenharmony_ci
11728c2ecf20Sopenharmony_ci	return retval;
11738c2ecf20Sopenharmony_ci}
11748c2ecf20Sopenharmony_ci
11758c2ecf20Sopenharmony_ci/*
11768c2ecf20Sopenharmony_ci * The directional pad behaves a bit differently, depending on whether this is
11778c2ecf20Sopenharmony_ci * one of the older ffdc devices or a newer device. Newer devices appear to
11788c2ecf20Sopenharmony_ci * have a higher resolution matrix for more precise mouse movement, but it
11798c2ecf20Sopenharmony_ci * makes things overly sensitive in keyboard mode, so we do some interesting
11808c2ecf20Sopenharmony_ci * contortions to make it less touchy. Older devices run through the same
11818c2ecf20Sopenharmony_ci * routine with shorter timeout and a smaller threshold.
11828c2ecf20Sopenharmony_ci */
11838c2ecf20Sopenharmony_cistatic int stabilize(int a, int b, u16 timeout, u16 threshold)
11848c2ecf20Sopenharmony_ci{
11858c2ecf20Sopenharmony_ci	ktime_t ct;
11868c2ecf20Sopenharmony_ci	static ktime_t prev_time;
11878c2ecf20Sopenharmony_ci	static ktime_t hit_time;
11888c2ecf20Sopenharmony_ci	static int x, y, prev_result, hits;
11898c2ecf20Sopenharmony_ci	int result = 0;
11908c2ecf20Sopenharmony_ci	long msec, msec_hit;
11918c2ecf20Sopenharmony_ci
11928c2ecf20Sopenharmony_ci	ct = ktime_get();
11938c2ecf20Sopenharmony_ci	msec = ktime_ms_delta(ct, prev_time);
11948c2ecf20Sopenharmony_ci	msec_hit = ktime_ms_delta(ct, hit_time);
11958c2ecf20Sopenharmony_ci
11968c2ecf20Sopenharmony_ci	if (msec > 100) {
11978c2ecf20Sopenharmony_ci		x = 0;
11988c2ecf20Sopenharmony_ci		y = 0;
11998c2ecf20Sopenharmony_ci		hits = 0;
12008c2ecf20Sopenharmony_ci	}
12018c2ecf20Sopenharmony_ci
12028c2ecf20Sopenharmony_ci	x += a;
12038c2ecf20Sopenharmony_ci	y += b;
12048c2ecf20Sopenharmony_ci
12058c2ecf20Sopenharmony_ci	prev_time = ct;
12068c2ecf20Sopenharmony_ci
12078c2ecf20Sopenharmony_ci	if (abs(x) > threshold || abs(y) > threshold) {
12088c2ecf20Sopenharmony_ci		if (abs(y) > abs(x))
12098c2ecf20Sopenharmony_ci			result = (y > 0) ? 0x7F : 0x80;
12108c2ecf20Sopenharmony_ci		else
12118c2ecf20Sopenharmony_ci			result = (x > 0) ? 0x7F00 : 0x8000;
12128c2ecf20Sopenharmony_ci
12138c2ecf20Sopenharmony_ci		x = 0;
12148c2ecf20Sopenharmony_ci		y = 0;
12158c2ecf20Sopenharmony_ci
12168c2ecf20Sopenharmony_ci		if (result == prev_result) {
12178c2ecf20Sopenharmony_ci			hits++;
12188c2ecf20Sopenharmony_ci
12198c2ecf20Sopenharmony_ci			if (hits > 3) {
12208c2ecf20Sopenharmony_ci				switch (result) {
12218c2ecf20Sopenharmony_ci				case 0x7F:
12228c2ecf20Sopenharmony_ci					y = 17 * threshold / 30;
12238c2ecf20Sopenharmony_ci					break;
12248c2ecf20Sopenharmony_ci				case 0x80:
12258c2ecf20Sopenharmony_ci					y -= 17 * threshold / 30;
12268c2ecf20Sopenharmony_ci					break;
12278c2ecf20Sopenharmony_ci				case 0x7F00:
12288c2ecf20Sopenharmony_ci					x = 17 * threshold / 30;
12298c2ecf20Sopenharmony_ci					break;
12308c2ecf20Sopenharmony_ci				case 0x8000:
12318c2ecf20Sopenharmony_ci					x -= 17 * threshold / 30;
12328c2ecf20Sopenharmony_ci					break;
12338c2ecf20Sopenharmony_ci				}
12348c2ecf20Sopenharmony_ci			}
12358c2ecf20Sopenharmony_ci
12368c2ecf20Sopenharmony_ci			if (hits == 2 && msec_hit < timeout) {
12378c2ecf20Sopenharmony_ci				result = 0;
12388c2ecf20Sopenharmony_ci				hits = 1;
12398c2ecf20Sopenharmony_ci			}
12408c2ecf20Sopenharmony_ci		} else {
12418c2ecf20Sopenharmony_ci			prev_result = result;
12428c2ecf20Sopenharmony_ci			hits = 1;
12438c2ecf20Sopenharmony_ci			hit_time = ct;
12448c2ecf20Sopenharmony_ci		}
12458c2ecf20Sopenharmony_ci	}
12468c2ecf20Sopenharmony_ci
12478c2ecf20Sopenharmony_ci	return result;
12488c2ecf20Sopenharmony_ci}
12498c2ecf20Sopenharmony_ci
12508c2ecf20Sopenharmony_cistatic u32 imon_remote_key_lookup(struct imon_context *ictx, u32 scancode)
12518c2ecf20Sopenharmony_ci{
12528c2ecf20Sopenharmony_ci	u32 keycode;
12538c2ecf20Sopenharmony_ci	u32 release;
12548c2ecf20Sopenharmony_ci	bool is_release_code = false;
12558c2ecf20Sopenharmony_ci
12568c2ecf20Sopenharmony_ci	/* Look for the initial press of a button */
12578c2ecf20Sopenharmony_ci	keycode = rc_g_keycode_from_table(ictx->rdev, scancode);
12588c2ecf20Sopenharmony_ci	ictx->rc_toggle = 0x0;
12598c2ecf20Sopenharmony_ci	ictx->rc_scancode = scancode;
12608c2ecf20Sopenharmony_ci
12618c2ecf20Sopenharmony_ci	/* Look for the release of a button */
12628c2ecf20Sopenharmony_ci	if (keycode == KEY_RESERVED) {
12638c2ecf20Sopenharmony_ci		release = scancode & ~0x4000;
12648c2ecf20Sopenharmony_ci		keycode = rc_g_keycode_from_table(ictx->rdev, release);
12658c2ecf20Sopenharmony_ci		if (keycode != KEY_RESERVED)
12668c2ecf20Sopenharmony_ci			is_release_code = true;
12678c2ecf20Sopenharmony_ci	}
12688c2ecf20Sopenharmony_ci
12698c2ecf20Sopenharmony_ci	ictx->release_code = is_release_code;
12708c2ecf20Sopenharmony_ci
12718c2ecf20Sopenharmony_ci	return keycode;
12728c2ecf20Sopenharmony_ci}
12738c2ecf20Sopenharmony_ci
12748c2ecf20Sopenharmony_cistatic u32 imon_mce_key_lookup(struct imon_context *ictx, u32 scancode)
12758c2ecf20Sopenharmony_ci{
12768c2ecf20Sopenharmony_ci	u32 keycode;
12778c2ecf20Sopenharmony_ci
12788c2ecf20Sopenharmony_ci#define MCE_KEY_MASK 0x7000
12798c2ecf20Sopenharmony_ci#define MCE_TOGGLE_BIT 0x8000
12808c2ecf20Sopenharmony_ci
12818c2ecf20Sopenharmony_ci	/*
12828c2ecf20Sopenharmony_ci	 * On some receivers, mce keys decode to 0x8000f04xx and 0x8000f84xx
12838c2ecf20Sopenharmony_ci	 * (the toggle bit flipping between alternating key presses), while
12848c2ecf20Sopenharmony_ci	 * on other receivers, we see 0x8000f74xx and 0x8000ff4xx. To keep
12858c2ecf20Sopenharmony_ci	 * the table trim, we always or in the bits to look up 0x8000ff4xx,
12868c2ecf20Sopenharmony_ci	 * but we can't or them into all codes, as some keys are decoded in
12878c2ecf20Sopenharmony_ci	 * a different way w/o the same use of the toggle bit...
12888c2ecf20Sopenharmony_ci	 */
12898c2ecf20Sopenharmony_ci	if (scancode & 0x80000000)
12908c2ecf20Sopenharmony_ci		scancode = scancode | MCE_KEY_MASK | MCE_TOGGLE_BIT;
12918c2ecf20Sopenharmony_ci
12928c2ecf20Sopenharmony_ci	ictx->rc_scancode = scancode;
12938c2ecf20Sopenharmony_ci	keycode = rc_g_keycode_from_table(ictx->rdev, scancode);
12948c2ecf20Sopenharmony_ci
12958c2ecf20Sopenharmony_ci	/* not used in mce mode, but make sure we know its false */
12968c2ecf20Sopenharmony_ci	ictx->release_code = false;
12978c2ecf20Sopenharmony_ci
12988c2ecf20Sopenharmony_ci	return keycode;
12998c2ecf20Sopenharmony_ci}
13008c2ecf20Sopenharmony_ci
13018c2ecf20Sopenharmony_cistatic u32 imon_panel_key_lookup(struct imon_context *ictx, u64 code)
13028c2ecf20Sopenharmony_ci{
13038c2ecf20Sopenharmony_ci	const struct imon_panel_key_table *key_table;
13048c2ecf20Sopenharmony_ci	u32 keycode = KEY_RESERVED;
13058c2ecf20Sopenharmony_ci	int i;
13068c2ecf20Sopenharmony_ci
13078c2ecf20Sopenharmony_ci	key_table = ictx->dev_descr->key_table;
13088c2ecf20Sopenharmony_ci
13098c2ecf20Sopenharmony_ci	for (i = 0; key_table[i].hw_code != 0; i++) {
13108c2ecf20Sopenharmony_ci		if (key_table[i].hw_code == (code | 0xffee)) {
13118c2ecf20Sopenharmony_ci			keycode = key_table[i].keycode;
13128c2ecf20Sopenharmony_ci			break;
13138c2ecf20Sopenharmony_ci		}
13148c2ecf20Sopenharmony_ci	}
13158c2ecf20Sopenharmony_ci	ictx->release_code = false;
13168c2ecf20Sopenharmony_ci	return keycode;
13178c2ecf20Sopenharmony_ci}
13188c2ecf20Sopenharmony_ci
13198c2ecf20Sopenharmony_cistatic bool imon_mouse_event(struct imon_context *ictx,
13208c2ecf20Sopenharmony_ci			     unsigned char *buf, int len)
13218c2ecf20Sopenharmony_ci{
13228c2ecf20Sopenharmony_ci	signed char rel_x = 0x00, rel_y = 0x00;
13238c2ecf20Sopenharmony_ci	u8 right_shift = 1;
13248c2ecf20Sopenharmony_ci	bool mouse_input = true;
13258c2ecf20Sopenharmony_ci	int dir = 0;
13268c2ecf20Sopenharmony_ci	unsigned long flags;
13278c2ecf20Sopenharmony_ci
13288c2ecf20Sopenharmony_ci	spin_lock_irqsave(&ictx->kc_lock, flags);
13298c2ecf20Sopenharmony_ci
13308c2ecf20Sopenharmony_ci	/* newer iMON device PAD or mouse button */
13318c2ecf20Sopenharmony_ci	if (ictx->product != 0xffdc && (buf[0] & 0x01) && len == 5) {
13328c2ecf20Sopenharmony_ci		rel_x = buf[2];
13338c2ecf20Sopenharmony_ci		rel_y = buf[3];
13348c2ecf20Sopenharmony_ci		right_shift = 1;
13358c2ecf20Sopenharmony_ci	/* 0xffdc iMON PAD or mouse button input */
13368c2ecf20Sopenharmony_ci	} else if (ictx->product == 0xffdc && (buf[0] & 0x40) &&
13378c2ecf20Sopenharmony_ci			!((buf[1] & 0x01) || ((buf[1] >> 2) & 0x01))) {
13388c2ecf20Sopenharmony_ci		rel_x = (buf[1] & 0x08) | (buf[1] & 0x10) >> 2 |
13398c2ecf20Sopenharmony_ci			(buf[1] & 0x20) >> 4 | (buf[1] & 0x40) >> 6;
13408c2ecf20Sopenharmony_ci		if (buf[0] & 0x02)
13418c2ecf20Sopenharmony_ci			rel_x |= ~0x0f;
13428c2ecf20Sopenharmony_ci		rel_x = rel_x + rel_x / 2;
13438c2ecf20Sopenharmony_ci		rel_y = (buf[2] & 0x08) | (buf[2] & 0x10) >> 2 |
13448c2ecf20Sopenharmony_ci			(buf[2] & 0x20) >> 4 | (buf[2] & 0x40) >> 6;
13458c2ecf20Sopenharmony_ci		if (buf[0] & 0x01)
13468c2ecf20Sopenharmony_ci			rel_y |= ~0x0f;
13478c2ecf20Sopenharmony_ci		rel_y = rel_y + rel_y / 2;
13488c2ecf20Sopenharmony_ci		right_shift = 2;
13498c2ecf20Sopenharmony_ci	/* some ffdc devices decode mouse buttons differently... */
13508c2ecf20Sopenharmony_ci	} else if (ictx->product == 0xffdc && (buf[0] == 0x68)) {
13518c2ecf20Sopenharmony_ci		right_shift = 2;
13528c2ecf20Sopenharmony_ci	/* ch+/- buttons, which we use for an emulated scroll wheel */
13538c2ecf20Sopenharmony_ci	} else if (ictx->kc == KEY_CHANNELUP && (buf[2] & 0x40) != 0x40) {
13548c2ecf20Sopenharmony_ci		dir = 1;
13558c2ecf20Sopenharmony_ci	} else if (ictx->kc == KEY_CHANNELDOWN && (buf[2] & 0x40) != 0x40) {
13568c2ecf20Sopenharmony_ci		dir = -1;
13578c2ecf20Sopenharmony_ci	} else
13588c2ecf20Sopenharmony_ci		mouse_input = false;
13598c2ecf20Sopenharmony_ci
13608c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&ictx->kc_lock, flags);
13618c2ecf20Sopenharmony_ci
13628c2ecf20Sopenharmony_ci	if (mouse_input) {
13638c2ecf20Sopenharmony_ci		dev_dbg(ictx->dev, "sending mouse data via input subsystem\n");
13648c2ecf20Sopenharmony_ci
13658c2ecf20Sopenharmony_ci		if (dir) {
13668c2ecf20Sopenharmony_ci			input_report_rel(ictx->idev, REL_WHEEL, dir);
13678c2ecf20Sopenharmony_ci		} else if (rel_x || rel_y) {
13688c2ecf20Sopenharmony_ci			input_report_rel(ictx->idev, REL_X, rel_x);
13698c2ecf20Sopenharmony_ci			input_report_rel(ictx->idev, REL_Y, rel_y);
13708c2ecf20Sopenharmony_ci		} else {
13718c2ecf20Sopenharmony_ci			input_report_key(ictx->idev, BTN_LEFT, buf[1] & 0x1);
13728c2ecf20Sopenharmony_ci			input_report_key(ictx->idev, BTN_RIGHT,
13738c2ecf20Sopenharmony_ci					 buf[1] >> right_shift & 0x1);
13748c2ecf20Sopenharmony_ci		}
13758c2ecf20Sopenharmony_ci		input_sync(ictx->idev);
13768c2ecf20Sopenharmony_ci		spin_lock_irqsave(&ictx->kc_lock, flags);
13778c2ecf20Sopenharmony_ci		ictx->last_keycode = ictx->kc;
13788c2ecf20Sopenharmony_ci		spin_unlock_irqrestore(&ictx->kc_lock, flags);
13798c2ecf20Sopenharmony_ci	}
13808c2ecf20Sopenharmony_ci
13818c2ecf20Sopenharmony_ci	return mouse_input;
13828c2ecf20Sopenharmony_ci}
13838c2ecf20Sopenharmony_ci
13848c2ecf20Sopenharmony_cistatic void imon_touch_event(struct imon_context *ictx, unsigned char *buf)
13858c2ecf20Sopenharmony_ci{
13868c2ecf20Sopenharmony_ci	mod_timer(&ictx->ttimer, jiffies + TOUCH_TIMEOUT);
13878c2ecf20Sopenharmony_ci	ictx->touch_x = (buf[0] << 4) | (buf[1] >> 4);
13888c2ecf20Sopenharmony_ci	ictx->touch_y = 0xfff - ((buf[2] << 4) | (buf[1] & 0xf));
13898c2ecf20Sopenharmony_ci	input_report_abs(ictx->touch, ABS_X, ictx->touch_x);
13908c2ecf20Sopenharmony_ci	input_report_abs(ictx->touch, ABS_Y, ictx->touch_y);
13918c2ecf20Sopenharmony_ci	input_report_key(ictx->touch, BTN_TOUCH, 0x01);
13928c2ecf20Sopenharmony_ci	input_sync(ictx->touch);
13938c2ecf20Sopenharmony_ci}
13948c2ecf20Sopenharmony_ci
13958c2ecf20Sopenharmony_cistatic void imon_pad_to_keys(struct imon_context *ictx, unsigned char *buf)
13968c2ecf20Sopenharmony_ci{
13978c2ecf20Sopenharmony_ci	int dir = 0;
13988c2ecf20Sopenharmony_ci	signed char rel_x = 0x00, rel_y = 0x00;
13998c2ecf20Sopenharmony_ci	u16 timeout, threshold;
14008c2ecf20Sopenharmony_ci	u32 scancode = KEY_RESERVED;
14018c2ecf20Sopenharmony_ci	unsigned long flags;
14028c2ecf20Sopenharmony_ci
14038c2ecf20Sopenharmony_ci	/*
14048c2ecf20Sopenharmony_ci	 * The imon directional pad functions more like a touchpad. Bytes 3 & 4
14058c2ecf20Sopenharmony_ci	 * contain a position coordinate (x,y), with each component ranging
14068c2ecf20Sopenharmony_ci	 * from -14 to 14. We want to down-sample this to only 4 discrete values
14078c2ecf20Sopenharmony_ci	 * for up/down/left/right arrow keys. Also, when you get too close to
14088c2ecf20Sopenharmony_ci	 * diagonals, it has a tendency to jump back and forth, so lets try to
14098c2ecf20Sopenharmony_ci	 * ignore when they get too close.
14108c2ecf20Sopenharmony_ci	 */
14118c2ecf20Sopenharmony_ci	if (ictx->product != 0xffdc) {
14128c2ecf20Sopenharmony_ci		/* first, pad to 8 bytes so it conforms with everything else */
14138c2ecf20Sopenharmony_ci		buf[5] = buf[6] = buf[7] = 0;
14148c2ecf20Sopenharmony_ci		timeout = 500;	/* in msecs */
14158c2ecf20Sopenharmony_ci		/* (2*threshold) x (2*threshold) square */
14168c2ecf20Sopenharmony_ci		threshold = pad_thresh ? pad_thresh : 28;
14178c2ecf20Sopenharmony_ci		rel_x = buf[2];
14188c2ecf20Sopenharmony_ci		rel_y = buf[3];
14198c2ecf20Sopenharmony_ci
14208c2ecf20Sopenharmony_ci		if (ictx->rc_proto == RC_PROTO_BIT_IMON && pad_stabilize) {
14218c2ecf20Sopenharmony_ci			if ((buf[1] == 0) && ((rel_x != 0) || (rel_y != 0))) {
14228c2ecf20Sopenharmony_ci				dir = stabilize((int)rel_x, (int)rel_y,
14238c2ecf20Sopenharmony_ci						timeout, threshold);
14248c2ecf20Sopenharmony_ci				if (!dir) {
14258c2ecf20Sopenharmony_ci					spin_lock_irqsave(&ictx->kc_lock,
14268c2ecf20Sopenharmony_ci							  flags);
14278c2ecf20Sopenharmony_ci					ictx->kc = KEY_UNKNOWN;
14288c2ecf20Sopenharmony_ci					spin_unlock_irqrestore(&ictx->kc_lock,
14298c2ecf20Sopenharmony_ci							       flags);
14308c2ecf20Sopenharmony_ci					return;
14318c2ecf20Sopenharmony_ci				}
14328c2ecf20Sopenharmony_ci				buf[2] = dir & 0xFF;
14338c2ecf20Sopenharmony_ci				buf[3] = (dir >> 8) & 0xFF;
14348c2ecf20Sopenharmony_ci				scancode = be32_to_cpu(*((__be32 *)buf));
14358c2ecf20Sopenharmony_ci			}
14368c2ecf20Sopenharmony_ci		} else {
14378c2ecf20Sopenharmony_ci			/*
14388c2ecf20Sopenharmony_ci			 * Hack alert: instead of using keycodes, we have
14398c2ecf20Sopenharmony_ci			 * to use hard-coded scancodes here...
14408c2ecf20Sopenharmony_ci			 */
14418c2ecf20Sopenharmony_ci			if (abs(rel_y) > abs(rel_x)) {
14428c2ecf20Sopenharmony_ci				buf[2] = (rel_y > 0) ? 0x7F : 0x80;
14438c2ecf20Sopenharmony_ci				buf[3] = 0;
14448c2ecf20Sopenharmony_ci				if (rel_y > 0)
14458c2ecf20Sopenharmony_ci					scancode = 0x01007f00; /* KEY_DOWN */
14468c2ecf20Sopenharmony_ci				else
14478c2ecf20Sopenharmony_ci					scancode = 0x01008000; /* KEY_UP */
14488c2ecf20Sopenharmony_ci			} else {
14498c2ecf20Sopenharmony_ci				buf[2] = 0;
14508c2ecf20Sopenharmony_ci				buf[3] = (rel_x > 0) ? 0x7F : 0x80;
14518c2ecf20Sopenharmony_ci				if (rel_x > 0)
14528c2ecf20Sopenharmony_ci					scancode = 0x0100007f; /* KEY_RIGHT */
14538c2ecf20Sopenharmony_ci				else
14548c2ecf20Sopenharmony_ci					scancode = 0x01000080; /* KEY_LEFT */
14558c2ecf20Sopenharmony_ci			}
14568c2ecf20Sopenharmony_ci		}
14578c2ecf20Sopenharmony_ci
14588c2ecf20Sopenharmony_ci	/*
14598c2ecf20Sopenharmony_ci	 * Handle on-board decoded pad events for e.g. older VFD/iMON-Pad
14608c2ecf20Sopenharmony_ci	 * device (15c2:ffdc). The remote generates various codes from
14618c2ecf20Sopenharmony_ci	 * 0x68nnnnB7 to 0x6AnnnnB7, the left mouse button generates
14628c2ecf20Sopenharmony_ci	 * 0x688301b7 and the right one 0x688481b7. All other keys generate
14638c2ecf20Sopenharmony_ci	 * 0x2nnnnnnn. Position coordinate is encoded in buf[1] and buf[2] with
14648c2ecf20Sopenharmony_ci	 * reversed endianness. Extract direction from buffer, rotate endianness,
14658c2ecf20Sopenharmony_ci	 * adjust sign and feed the values into stabilize(). The resulting codes
14668c2ecf20Sopenharmony_ci	 * will be 0x01008000, 0x01007F00, which match the newer devices.
14678c2ecf20Sopenharmony_ci	 */
14688c2ecf20Sopenharmony_ci	} else {
14698c2ecf20Sopenharmony_ci		timeout = 10;	/* in msecs */
14708c2ecf20Sopenharmony_ci		/* (2*threshold) x (2*threshold) square */
14718c2ecf20Sopenharmony_ci		threshold = pad_thresh ? pad_thresh : 15;
14728c2ecf20Sopenharmony_ci
14738c2ecf20Sopenharmony_ci		/* buf[1] is x */
14748c2ecf20Sopenharmony_ci		rel_x = (buf[1] & 0x08) | (buf[1] & 0x10) >> 2 |
14758c2ecf20Sopenharmony_ci			(buf[1] & 0x20) >> 4 | (buf[1] & 0x40) >> 6;
14768c2ecf20Sopenharmony_ci		if (buf[0] & 0x02)
14778c2ecf20Sopenharmony_ci			rel_x |= ~0x10+1;
14788c2ecf20Sopenharmony_ci		/* buf[2] is y */
14798c2ecf20Sopenharmony_ci		rel_y = (buf[2] & 0x08) | (buf[2] & 0x10) >> 2 |
14808c2ecf20Sopenharmony_ci			(buf[2] & 0x20) >> 4 | (buf[2] & 0x40) >> 6;
14818c2ecf20Sopenharmony_ci		if (buf[0] & 0x01)
14828c2ecf20Sopenharmony_ci			rel_y |= ~0x10+1;
14838c2ecf20Sopenharmony_ci
14848c2ecf20Sopenharmony_ci		buf[0] = 0x01;
14858c2ecf20Sopenharmony_ci		buf[1] = buf[4] = buf[5] = buf[6] = buf[7] = 0;
14868c2ecf20Sopenharmony_ci
14878c2ecf20Sopenharmony_ci		if (ictx->rc_proto == RC_PROTO_BIT_IMON && pad_stabilize) {
14888c2ecf20Sopenharmony_ci			dir = stabilize((int)rel_x, (int)rel_y,
14898c2ecf20Sopenharmony_ci					timeout, threshold);
14908c2ecf20Sopenharmony_ci			if (!dir) {
14918c2ecf20Sopenharmony_ci				spin_lock_irqsave(&ictx->kc_lock, flags);
14928c2ecf20Sopenharmony_ci				ictx->kc = KEY_UNKNOWN;
14938c2ecf20Sopenharmony_ci				spin_unlock_irqrestore(&ictx->kc_lock, flags);
14948c2ecf20Sopenharmony_ci				return;
14958c2ecf20Sopenharmony_ci			}
14968c2ecf20Sopenharmony_ci			buf[2] = dir & 0xFF;
14978c2ecf20Sopenharmony_ci			buf[3] = (dir >> 8) & 0xFF;
14988c2ecf20Sopenharmony_ci			scancode = be32_to_cpu(*((__be32 *)buf));
14998c2ecf20Sopenharmony_ci		} else {
15008c2ecf20Sopenharmony_ci			/*
15018c2ecf20Sopenharmony_ci			 * Hack alert: instead of using keycodes, we have
15028c2ecf20Sopenharmony_ci			 * to use hard-coded scancodes here...
15038c2ecf20Sopenharmony_ci			 */
15048c2ecf20Sopenharmony_ci			if (abs(rel_y) > abs(rel_x)) {
15058c2ecf20Sopenharmony_ci				buf[2] = (rel_y > 0) ? 0x7F : 0x80;
15068c2ecf20Sopenharmony_ci				buf[3] = 0;
15078c2ecf20Sopenharmony_ci				if (rel_y > 0)
15088c2ecf20Sopenharmony_ci					scancode = 0x01007f00; /* KEY_DOWN */
15098c2ecf20Sopenharmony_ci				else
15108c2ecf20Sopenharmony_ci					scancode = 0x01008000; /* KEY_UP */
15118c2ecf20Sopenharmony_ci			} else {
15128c2ecf20Sopenharmony_ci				buf[2] = 0;
15138c2ecf20Sopenharmony_ci				buf[3] = (rel_x > 0) ? 0x7F : 0x80;
15148c2ecf20Sopenharmony_ci				if (rel_x > 0)
15158c2ecf20Sopenharmony_ci					scancode = 0x0100007f; /* KEY_RIGHT */
15168c2ecf20Sopenharmony_ci				else
15178c2ecf20Sopenharmony_ci					scancode = 0x01000080; /* KEY_LEFT */
15188c2ecf20Sopenharmony_ci			}
15198c2ecf20Sopenharmony_ci		}
15208c2ecf20Sopenharmony_ci	}
15218c2ecf20Sopenharmony_ci
15228c2ecf20Sopenharmony_ci	if (scancode) {
15238c2ecf20Sopenharmony_ci		spin_lock_irqsave(&ictx->kc_lock, flags);
15248c2ecf20Sopenharmony_ci		ictx->kc = imon_remote_key_lookup(ictx, scancode);
15258c2ecf20Sopenharmony_ci		spin_unlock_irqrestore(&ictx->kc_lock, flags);
15268c2ecf20Sopenharmony_ci	}
15278c2ecf20Sopenharmony_ci}
15288c2ecf20Sopenharmony_ci
15298c2ecf20Sopenharmony_ci/*
15308c2ecf20Sopenharmony_ci * figure out if these is a press or a release. We don't actually
15318c2ecf20Sopenharmony_ci * care about repeats, as those will be auto-generated within the IR
15328c2ecf20Sopenharmony_ci * subsystem for repeating scancodes.
15338c2ecf20Sopenharmony_ci */
15348c2ecf20Sopenharmony_cistatic int imon_parse_press_type(struct imon_context *ictx,
15358c2ecf20Sopenharmony_ci				 unsigned char *buf, u8 ktype)
15368c2ecf20Sopenharmony_ci{
15378c2ecf20Sopenharmony_ci	int press_type = 0;
15388c2ecf20Sopenharmony_ci	unsigned long flags;
15398c2ecf20Sopenharmony_ci
15408c2ecf20Sopenharmony_ci	spin_lock_irqsave(&ictx->kc_lock, flags);
15418c2ecf20Sopenharmony_ci
15428c2ecf20Sopenharmony_ci	/* key release of 0x02XXXXXX key */
15438c2ecf20Sopenharmony_ci	if (ictx->kc == KEY_RESERVED && buf[0] == 0x02 && buf[3] == 0x00)
15448c2ecf20Sopenharmony_ci		ictx->kc = ictx->last_keycode;
15458c2ecf20Sopenharmony_ci
15468c2ecf20Sopenharmony_ci	/* mouse button release on (some) 0xffdc devices */
15478c2ecf20Sopenharmony_ci	else if (ictx->kc == KEY_RESERVED && buf[0] == 0x68 && buf[1] == 0x82 &&
15488c2ecf20Sopenharmony_ci		 buf[2] == 0x81 && buf[3] == 0xb7)
15498c2ecf20Sopenharmony_ci		ictx->kc = ictx->last_keycode;
15508c2ecf20Sopenharmony_ci
15518c2ecf20Sopenharmony_ci	/* mouse button release on (some other) 0xffdc devices */
15528c2ecf20Sopenharmony_ci	else if (ictx->kc == KEY_RESERVED && buf[0] == 0x01 && buf[1] == 0x00 &&
15538c2ecf20Sopenharmony_ci		 buf[2] == 0x81 && buf[3] == 0xb7)
15548c2ecf20Sopenharmony_ci		ictx->kc = ictx->last_keycode;
15558c2ecf20Sopenharmony_ci
15568c2ecf20Sopenharmony_ci	/* mce-specific button handling, no keyup events */
15578c2ecf20Sopenharmony_ci	else if (ktype == IMON_KEY_MCE) {
15588c2ecf20Sopenharmony_ci		ictx->rc_toggle = buf[2];
15598c2ecf20Sopenharmony_ci		press_type = 1;
15608c2ecf20Sopenharmony_ci
15618c2ecf20Sopenharmony_ci	/* incoherent or irrelevant data */
15628c2ecf20Sopenharmony_ci	} else if (ictx->kc == KEY_RESERVED)
15638c2ecf20Sopenharmony_ci		press_type = -EINVAL;
15648c2ecf20Sopenharmony_ci
15658c2ecf20Sopenharmony_ci	/* key release of 0xXXXXXXb7 key */
15668c2ecf20Sopenharmony_ci	else if (ictx->release_code)
15678c2ecf20Sopenharmony_ci		press_type = 0;
15688c2ecf20Sopenharmony_ci
15698c2ecf20Sopenharmony_ci	/* this is a button press */
15708c2ecf20Sopenharmony_ci	else
15718c2ecf20Sopenharmony_ci		press_type = 1;
15728c2ecf20Sopenharmony_ci
15738c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&ictx->kc_lock, flags);
15748c2ecf20Sopenharmony_ci
15758c2ecf20Sopenharmony_ci	return press_type;
15768c2ecf20Sopenharmony_ci}
15778c2ecf20Sopenharmony_ci
15788c2ecf20Sopenharmony_ci/*
15798c2ecf20Sopenharmony_ci * Process the incoming packet
15808c2ecf20Sopenharmony_ci */
15818c2ecf20Sopenharmony_cistatic void imon_incoming_packet(struct imon_context *ictx,
15828c2ecf20Sopenharmony_ci				 struct urb *urb, int intf)
15838c2ecf20Sopenharmony_ci{
15848c2ecf20Sopenharmony_ci	int len = urb->actual_length;
15858c2ecf20Sopenharmony_ci	unsigned char *buf = urb->transfer_buffer;
15868c2ecf20Sopenharmony_ci	struct device *dev = ictx->dev;
15878c2ecf20Sopenharmony_ci	unsigned long flags;
15888c2ecf20Sopenharmony_ci	u32 kc;
15898c2ecf20Sopenharmony_ci	u64 scancode;
15908c2ecf20Sopenharmony_ci	int press_type = 0;
15918c2ecf20Sopenharmony_ci	ktime_t t;
15928c2ecf20Sopenharmony_ci	static ktime_t prev_time;
15938c2ecf20Sopenharmony_ci	u8 ktype;
15948c2ecf20Sopenharmony_ci
15958c2ecf20Sopenharmony_ci	/* filter out junk data on the older 0xffdc imon devices */
15968c2ecf20Sopenharmony_ci	if ((buf[0] == 0xff) && (buf[1] == 0xff) && (buf[2] == 0xff))
15978c2ecf20Sopenharmony_ci		return;
15988c2ecf20Sopenharmony_ci
15998c2ecf20Sopenharmony_ci	/* Figure out what key was pressed */
16008c2ecf20Sopenharmony_ci	if (len == 8 && buf[7] == 0xee) {
16018c2ecf20Sopenharmony_ci		scancode = be64_to_cpu(*((__be64 *)buf));
16028c2ecf20Sopenharmony_ci		ktype = IMON_KEY_PANEL;
16038c2ecf20Sopenharmony_ci		kc = imon_panel_key_lookup(ictx, scancode);
16048c2ecf20Sopenharmony_ci		ictx->release_code = false;
16058c2ecf20Sopenharmony_ci	} else {
16068c2ecf20Sopenharmony_ci		scancode = be32_to_cpu(*((__be32 *)buf));
16078c2ecf20Sopenharmony_ci		if (ictx->rc_proto == RC_PROTO_BIT_RC6_MCE) {
16088c2ecf20Sopenharmony_ci			ktype = IMON_KEY_IMON;
16098c2ecf20Sopenharmony_ci			if (buf[0] == 0x80)
16108c2ecf20Sopenharmony_ci				ktype = IMON_KEY_MCE;
16118c2ecf20Sopenharmony_ci			kc = imon_mce_key_lookup(ictx, scancode);
16128c2ecf20Sopenharmony_ci		} else {
16138c2ecf20Sopenharmony_ci			ktype = IMON_KEY_IMON;
16148c2ecf20Sopenharmony_ci			kc = imon_remote_key_lookup(ictx, scancode);
16158c2ecf20Sopenharmony_ci		}
16168c2ecf20Sopenharmony_ci	}
16178c2ecf20Sopenharmony_ci
16188c2ecf20Sopenharmony_ci	spin_lock_irqsave(&ictx->kc_lock, flags);
16198c2ecf20Sopenharmony_ci	/* keyboard/mouse mode toggle button */
16208c2ecf20Sopenharmony_ci	if (kc == KEY_KEYBOARD && !ictx->release_code) {
16218c2ecf20Sopenharmony_ci		ictx->last_keycode = kc;
16228c2ecf20Sopenharmony_ci		if (!nomouse) {
16238c2ecf20Sopenharmony_ci			ictx->pad_mouse = !ictx->pad_mouse;
16248c2ecf20Sopenharmony_ci			dev_dbg(dev, "toggling to %s mode\n",
16258c2ecf20Sopenharmony_ci				ictx->pad_mouse ? "mouse" : "keyboard");
16268c2ecf20Sopenharmony_ci			spin_unlock_irqrestore(&ictx->kc_lock, flags);
16278c2ecf20Sopenharmony_ci			return;
16288c2ecf20Sopenharmony_ci		} else {
16298c2ecf20Sopenharmony_ci			ictx->pad_mouse = false;
16308c2ecf20Sopenharmony_ci			dev_dbg(dev, "mouse mode disabled, passing key value\n");
16318c2ecf20Sopenharmony_ci		}
16328c2ecf20Sopenharmony_ci	}
16338c2ecf20Sopenharmony_ci
16348c2ecf20Sopenharmony_ci	ictx->kc = kc;
16358c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&ictx->kc_lock, flags);
16368c2ecf20Sopenharmony_ci
16378c2ecf20Sopenharmony_ci	/* send touchscreen events through input subsystem if touchpad data */
16388c2ecf20Sopenharmony_ci	if (ictx->touch && len == 8 && buf[7] == 0x86) {
16398c2ecf20Sopenharmony_ci		imon_touch_event(ictx, buf);
16408c2ecf20Sopenharmony_ci		return;
16418c2ecf20Sopenharmony_ci
16428c2ecf20Sopenharmony_ci	/* look for mouse events with pad in mouse mode */
16438c2ecf20Sopenharmony_ci	} else if (ictx->pad_mouse) {
16448c2ecf20Sopenharmony_ci		if (imon_mouse_event(ictx, buf, len))
16458c2ecf20Sopenharmony_ci			return;
16468c2ecf20Sopenharmony_ci	}
16478c2ecf20Sopenharmony_ci
16488c2ecf20Sopenharmony_ci	/* Now for some special handling to convert pad input to arrow keys */
16498c2ecf20Sopenharmony_ci	if (((len == 5) && (buf[0] == 0x01) && (buf[4] == 0x00)) ||
16508c2ecf20Sopenharmony_ci	    ((len == 8) && (buf[0] & 0x40) &&
16518c2ecf20Sopenharmony_ci	     !(buf[1] & 0x1 || buf[1] >> 2 & 0x1))) {
16528c2ecf20Sopenharmony_ci		len = 8;
16538c2ecf20Sopenharmony_ci		imon_pad_to_keys(ictx, buf);
16548c2ecf20Sopenharmony_ci	}
16558c2ecf20Sopenharmony_ci
16568c2ecf20Sopenharmony_ci	if (debug) {
16578c2ecf20Sopenharmony_ci		printk(KERN_INFO "intf%d decoded packet: %*ph\n",
16588c2ecf20Sopenharmony_ci		       intf, len, buf);
16598c2ecf20Sopenharmony_ci	}
16608c2ecf20Sopenharmony_ci
16618c2ecf20Sopenharmony_ci	press_type = imon_parse_press_type(ictx, buf, ktype);
16628c2ecf20Sopenharmony_ci	if (press_type < 0)
16638c2ecf20Sopenharmony_ci		goto not_input_data;
16648c2ecf20Sopenharmony_ci
16658c2ecf20Sopenharmony_ci	if (ktype != IMON_KEY_PANEL) {
16668c2ecf20Sopenharmony_ci		if (press_type == 0)
16678c2ecf20Sopenharmony_ci			rc_keyup(ictx->rdev);
16688c2ecf20Sopenharmony_ci		else {
16698c2ecf20Sopenharmony_ci			enum rc_proto proto;
16708c2ecf20Sopenharmony_ci
16718c2ecf20Sopenharmony_ci			if (ictx->rc_proto == RC_PROTO_BIT_RC6_MCE)
16728c2ecf20Sopenharmony_ci				proto = RC_PROTO_RC6_MCE;
16738c2ecf20Sopenharmony_ci			else if (ictx->rc_proto == RC_PROTO_BIT_IMON)
16748c2ecf20Sopenharmony_ci				proto = RC_PROTO_IMON;
16758c2ecf20Sopenharmony_ci			else
16768c2ecf20Sopenharmony_ci				return;
16778c2ecf20Sopenharmony_ci
16788c2ecf20Sopenharmony_ci			rc_keydown(ictx->rdev, proto, ictx->rc_scancode,
16798c2ecf20Sopenharmony_ci				   ictx->rc_toggle);
16808c2ecf20Sopenharmony_ci
16818c2ecf20Sopenharmony_ci			spin_lock_irqsave(&ictx->kc_lock, flags);
16828c2ecf20Sopenharmony_ci			ictx->last_keycode = ictx->kc;
16838c2ecf20Sopenharmony_ci			spin_unlock_irqrestore(&ictx->kc_lock, flags);
16848c2ecf20Sopenharmony_ci		}
16858c2ecf20Sopenharmony_ci		return;
16868c2ecf20Sopenharmony_ci	}
16878c2ecf20Sopenharmony_ci
16888c2ecf20Sopenharmony_ci	/* Only panel type events left to process now */
16898c2ecf20Sopenharmony_ci	spin_lock_irqsave(&ictx->kc_lock, flags);
16908c2ecf20Sopenharmony_ci
16918c2ecf20Sopenharmony_ci	t = ktime_get();
16928c2ecf20Sopenharmony_ci	/* KEY repeats from knob and panel that need to be suppressed */
16938c2ecf20Sopenharmony_ci	if (ictx->kc == KEY_MUTE ||
16948c2ecf20Sopenharmony_ci	    ictx->dev_descr->flags & IMON_SUPPRESS_REPEATED_KEYS) {
16958c2ecf20Sopenharmony_ci		if (ictx->kc == ictx->last_keycode &&
16968c2ecf20Sopenharmony_ci		    ktime_ms_delta(t, prev_time) < ictx->idev->rep[REP_DELAY]) {
16978c2ecf20Sopenharmony_ci			spin_unlock_irqrestore(&ictx->kc_lock, flags);
16988c2ecf20Sopenharmony_ci			return;
16998c2ecf20Sopenharmony_ci		}
17008c2ecf20Sopenharmony_ci	}
17018c2ecf20Sopenharmony_ci
17028c2ecf20Sopenharmony_ci	prev_time = t;
17038c2ecf20Sopenharmony_ci	kc = ictx->kc;
17048c2ecf20Sopenharmony_ci
17058c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&ictx->kc_lock, flags);
17068c2ecf20Sopenharmony_ci
17078c2ecf20Sopenharmony_ci	input_report_key(ictx->idev, kc, press_type);
17088c2ecf20Sopenharmony_ci	input_sync(ictx->idev);
17098c2ecf20Sopenharmony_ci
17108c2ecf20Sopenharmony_ci	/* panel keys don't generate a release */
17118c2ecf20Sopenharmony_ci	input_report_key(ictx->idev, kc, 0);
17128c2ecf20Sopenharmony_ci	input_sync(ictx->idev);
17138c2ecf20Sopenharmony_ci
17148c2ecf20Sopenharmony_ci	spin_lock_irqsave(&ictx->kc_lock, flags);
17158c2ecf20Sopenharmony_ci	ictx->last_keycode = kc;
17168c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&ictx->kc_lock, flags);
17178c2ecf20Sopenharmony_ci
17188c2ecf20Sopenharmony_ci	return;
17198c2ecf20Sopenharmony_ci
17208c2ecf20Sopenharmony_cinot_input_data:
17218c2ecf20Sopenharmony_ci	if (len != 8) {
17228c2ecf20Sopenharmony_ci		dev_warn(dev, "imon %s: invalid incoming packet size (len = %d, intf%d)\n",
17238c2ecf20Sopenharmony_ci			 __func__, len, intf);
17248c2ecf20Sopenharmony_ci		return;
17258c2ecf20Sopenharmony_ci	}
17268c2ecf20Sopenharmony_ci
17278c2ecf20Sopenharmony_ci	/* iMON 2.4G associate frame */
17288c2ecf20Sopenharmony_ci	if (buf[0] == 0x00 &&
17298c2ecf20Sopenharmony_ci	    buf[2] == 0xFF &&				/* REFID */
17308c2ecf20Sopenharmony_ci	    buf[3] == 0xFF &&
17318c2ecf20Sopenharmony_ci	    buf[4] == 0xFF &&
17328c2ecf20Sopenharmony_ci	    buf[5] == 0xFF &&				/* iMON 2.4G */
17338c2ecf20Sopenharmony_ci	   ((buf[6] == 0x4E && buf[7] == 0xDF) ||	/* LT */
17348c2ecf20Sopenharmony_ci	    (buf[6] == 0x5E && buf[7] == 0xDF))) {	/* DT */
17358c2ecf20Sopenharmony_ci		dev_warn(dev, "%s: remote associated refid=%02X\n",
17368c2ecf20Sopenharmony_ci			 __func__, buf[1]);
17378c2ecf20Sopenharmony_ci		ictx->rf_isassociating = false;
17388c2ecf20Sopenharmony_ci	}
17398c2ecf20Sopenharmony_ci}
17408c2ecf20Sopenharmony_ci
17418c2ecf20Sopenharmony_ci/*
17428c2ecf20Sopenharmony_ci * Callback function for USB core API: receive data
17438c2ecf20Sopenharmony_ci */
17448c2ecf20Sopenharmony_cistatic void usb_rx_callback_intf0(struct urb *urb)
17458c2ecf20Sopenharmony_ci{
17468c2ecf20Sopenharmony_ci	struct imon_context *ictx;
17478c2ecf20Sopenharmony_ci	int intfnum = 0;
17488c2ecf20Sopenharmony_ci
17498c2ecf20Sopenharmony_ci	if (!urb)
17508c2ecf20Sopenharmony_ci		return;
17518c2ecf20Sopenharmony_ci
17528c2ecf20Sopenharmony_ci	ictx = (struct imon_context *)urb->context;
17538c2ecf20Sopenharmony_ci	if (!ictx)
17548c2ecf20Sopenharmony_ci		return;
17558c2ecf20Sopenharmony_ci
17568c2ecf20Sopenharmony_ci	/*
17578c2ecf20Sopenharmony_ci	 * if we get a callback before we're done configuring the hardware, we
17588c2ecf20Sopenharmony_ci	 * can't yet process the data, as there's nowhere to send it, but we
17598c2ecf20Sopenharmony_ci	 * still need to submit a new rx URB to avoid wedging the hardware
17608c2ecf20Sopenharmony_ci	 */
17618c2ecf20Sopenharmony_ci	if (!ictx->dev_present_intf0)
17628c2ecf20Sopenharmony_ci		goto out;
17638c2ecf20Sopenharmony_ci
17648c2ecf20Sopenharmony_ci	switch (urb->status) {
17658c2ecf20Sopenharmony_ci	case -ENOENT:		/* usbcore unlink successful! */
17668c2ecf20Sopenharmony_ci		return;
17678c2ecf20Sopenharmony_ci
17688c2ecf20Sopenharmony_ci	case -ESHUTDOWN:	/* transport endpoint was shut down */
17698c2ecf20Sopenharmony_ci		break;
17708c2ecf20Sopenharmony_ci
17718c2ecf20Sopenharmony_ci	case 0:
17728c2ecf20Sopenharmony_ci		imon_incoming_packet(ictx, urb, intfnum);
17738c2ecf20Sopenharmony_ci		break;
17748c2ecf20Sopenharmony_ci
17758c2ecf20Sopenharmony_ci	default:
17768c2ecf20Sopenharmony_ci		dev_warn(ictx->dev, "imon %s: status(%d): ignored\n",
17778c2ecf20Sopenharmony_ci			 __func__, urb->status);
17788c2ecf20Sopenharmony_ci		break;
17798c2ecf20Sopenharmony_ci	}
17808c2ecf20Sopenharmony_ci
17818c2ecf20Sopenharmony_ciout:
17828c2ecf20Sopenharmony_ci	usb_submit_urb(ictx->rx_urb_intf0, GFP_ATOMIC);
17838c2ecf20Sopenharmony_ci}
17848c2ecf20Sopenharmony_ci
17858c2ecf20Sopenharmony_cistatic void usb_rx_callback_intf1(struct urb *urb)
17868c2ecf20Sopenharmony_ci{
17878c2ecf20Sopenharmony_ci	struct imon_context *ictx;
17888c2ecf20Sopenharmony_ci	int intfnum = 1;
17898c2ecf20Sopenharmony_ci
17908c2ecf20Sopenharmony_ci	if (!urb)
17918c2ecf20Sopenharmony_ci		return;
17928c2ecf20Sopenharmony_ci
17938c2ecf20Sopenharmony_ci	ictx = (struct imon_context *)urb->context;
17948c2ecf20Sopenharmony_ci	if (!ictx)
17958c2ecf20Sopenharmony_ci		return;
17968c2ecf20Sopenharmony_ci
17978c2ecf20Sopenharmony_ci	/*
17988c2ecf20Sopenharmony_ci	 * if we get a callback before we're done configuring the hardware, we
17998c2ecf20Sopenharmony_ci	 * can't yet process the data, as there's nowhere to send it, but we
18008c2ecf20Sopenharmony_ci	 * still need to submit a new rx URB to avoid wedging the hardware
18018c2ecf20Sopenharmony_ci	 */
18028c2ecf20Sopenharmony_ci	if (!ictx->dev_present_intf1)
18038c2ecf20Sopenharmony_ci		goto out;
18048c2ecf20Sopenharmony_ci
18058c2ecf20Sopenharmony_ci	switch (urb->status) {
18068c2ecf20Sopenharmony_ci	case -ENOENT:		/* usbcore unlink successful! */
18078c2ecf20Sopenharmony_ci		return;
18088c2ecf20Sopenharmony_ci
18098c2ecf20Sopenharmony_ci	case -ESHUTDOWN:	/* transport endpoint was shut down */
18108c2ecf20Sopenharmony_ci		break;
18118c2ecf20Sopenharmony_ci
18128c2ecf20Sopenharmony_ci	case 0:
18138c2ecf20Sopenharmony_ci		imon_incoming_packet(ictx, urb, intfnum);
18148c2ecf20Sopenharmony_ci		break;
18158c2ecf20Sopenharmony_ci
18168c2ecf20Sopenharmony_ci	default:
18178c2ecf20Sopenharmony_ci		dev_warn(ictx->dev, "imon %s: status(%d): ignored\n",
18188c2ecf20Sopenharmony_ci			 __func__, urb->status);
18198c2ecf20Sopenharmony_ci		break;
18208c2ecf20Sopenharmony_ci	}
18218c2ecf20Sopenharmony_ci
18228c2ecf20Sopenharmony_ciout:
18238c2ecf20Sopenharmony_ci	usb_submit_urb(ictx->rx_urb_intf1, GFP_ATOMIC);
18248c2ecf20Sopenharmony_ci}
18258c2ecf20Sopenharmony_ci
18268c2ecf20Sopenharmony_ci/*
18278c2ecf20Sopenharmony_ci * The 0x15c2:0xffdc device ID was used for umpteen different imon
18288c2ecf20Sopenharmony_ci * devices, and all of them constantly spew interrupts, even when there
18298c2ecf20Sopenharmony_ci * is no actual data to report. However, byte 6 of this buffer looks like
18308c2ecf20Sopenharmony_ci * its unique across device variants, so we're trying to key off that to
18318c2ecf20Sopenharmony_ci * figure out which display type (if any) and what IR protocol the device
18328c2ecf20Sopenharmony_ci * actually supports. These devices have their IR protocol hard-coded into
18338c2ecf20Sopenharmony_ci * their firmware, they can't be changed on the fly like the newer hardware.
18348c2ecf20Sopenharmony_ci */
18358c2ecf20Sopenharmony_cistatic void imon_get_ffdc_type(struct imon_context *ictx)
18368c2ecf20Sopenharmony_ci{
18378c2ecf20Sopenharmony_ci	u8 ffdc_cfg_byte = ictx->usb_rx_buf[6];
18388c2ecf20Sopenharmony_ci	u8 detected_display_type = IMON_DISPLAY_TYPE_NONE;
18398c2ecf20Sopenharmony_ci	u64 allowed_protos = RC_PROTO_BIT_IMON;
18408c2ecf20Sopenharmony_ci
18418c2ecf20Sopenharmony_ci	switch (ffdc_cfg_byte) {
18428c2ecf20Sopenharmony_ci	/* iMON Knob, no display, iMON IR + vol knob */
18438c2ecf20Sopenharmony_ci	case 0x21:
18448c2ecf20Sopenharmony_ci		dev_info(ictx->dev, "0xffdc iMON Knob, iMON IR");
18458c2ecf20Sopenharmony_ci		ictx->display_supported = false;
18468c2ecf20Sopenharmony_ci		break;
18478c2ecf20Sopenharmony_ci	/* iMON 2.4G LT (usb stick), no display, iMON RF */
18488c2ecf20Sopenharmony_ci	case 0x4e:
18498c2ecf20Sopenharmony_ci		dev_info(ictx->dev, "0xffdc iMON 2.4G LT, iMON RF");
18508c2ecf20Sopenharmony_ci		ictx->display_supported = false;
18518c2ecf20Sopenharmony_ci		ictx->rf_device = true;
18528c2ecf20Sopenharmony_ci		break;
18538c2ecf20Sopenharmony_ci	/* iMON VFD, no IR (does have vol knob tho) */
18548c2ecf20Sopenharmony_ci	case 0x35:
18558c2ecf20Sopenharmony_ci		dev_info(ictx->dev, "0xffdc iMON VFD + knob, no IR");
18568c2ecf20Sopenharmony_ci		detected_display_type = IMON_DISPLAY_TYPE_VFD;
18578c2ecf20Sopenharmony_ci		break;
18588c2ecf20Sopenharmony_ci	/* iMON VFD, iMON IR */
18598c2ecf20Sopenharmony_ci	case 0x24:
18608c2ecf20Sopenharmony_ci	case 0x30:
18618c2ecf20Sopenharmony_ci	case 0x85:
18628c2ecf20Sopenharmony_ci		dev_info(ictx->dev, "0xffdc iMON VFD, iMON IR");
18638c2ecf20Sopenharmony_ci		detected_display_type = IMON_DISPLAY_TYPE_VFD;
18648c2ecf20Sopenharmony_ci		break;
18658c2ecf20Sopenharmony_ci	/* iMON VFD, MCE IR */
18668c2ecf20Sopenharmony_ci	case 0x46:
18678c2ecf20Sopenharmony_ci	case 0x9e:
18688c2ecf20Sopenharmony_ci		dev_info(ictx->dev, "0xffdc iMON VFD, MCE IR");
18698c2ecf20Sopenharmony_ci		detected_display_type = IMON_DISPLAY_TYPE_VFD;
18708c2ecf20Sopenharmony_ci		allowed_protos = RC_PROTO_BIT_RC6_MCE;
18718c2ecf20Sopenharmony_ci		break;
18728c2ecf20Sopenharmony_ci	/* iMON VFD, iMON or MCE IR */
18738c2ecf20Sopenharmony_ci	case 0x7e:
18748c2ecf20Sopenharmony_ci		dev_info(ictx->dev, "0xffdc iMON VFD, iMON or MCE IR");
18758c2ecf20Sopenharmony_ci		detected_display_type = IMON_DISPLAY_TYPE_VFD;
18768c2ecf20Sopenharmony_ci		allowed_protos |= RC_PROTO_BIT_RC6_MCE;
18778c2ecf20Sopenharmony_ci		break;
18788c2ecf20Sopenharmony_ci	/* iMON LCD, MCE IR */
18798c2ecf20Sopenharmony_ci	case 0x9f:
18808c2ecf20Sopenharmony_ci		dev_info(ictx->dev, "0xffdc iMON LCD, MCE IR");
18818c2ecf20Sopenharmony_ci		detected_display_type = IMON_DISPLAY_TYPE_LCD;
18828c2ecf20Sopenharmony_ci		allowed_protos = RC_PROTO_BIT_RC6_MCE;
18838c2ecf20Sopenharmony_ci		break;
18848c2ecf20Sopenharmony_ci	/* no display, iMON IR */
18858c2ecf20Sopenharmony_ci	case 0x26:
18868c2ecf20Sopenharmony_ci		dev_info(ictx->dev, "0xffdc iMON Inside, iMON IR");
18878c2ecf20Sopenharmony_ci		ictx->display_supported = false;
18888c2ecf20Sopenharmony_ci		break;
18898c2ecf20Sopenharmony_ci	/* Soundgraph iMON UltraBay */
18908c2ecf20Sopenharmony_ci	case 0x98:
18918c2ecf20Sopenharmony_ci		dev_info(ictx->dev, "0xffdc iMON UltraBay, LCD + IR");
18928c2ecf20Sopenharmony_ci		detected_display_type = IMON_DISPLAY_TYPE_LCD;
18938c2ecf20Sopenharmony_ci		allowed_protos = RC_PROTO_BIT_IMON | RC_PROTO_BIT_RC6_MCE;
18948c2ecf20Sopenharmony_ci		ictx->dev_descr = &ultrabay_table;
18958c2ecf20Sopenharmony_ci		break;
18968c2ecf20Sopenharmony_ci
18978c2ecf20Sopenharmony_ci	default:
18988c2ecf20Sopenharmony_ci		dev_info(ictx->dev, "Unknown 0xffdc device, defaulting to VFD and iMON IR");
18998c2ecf20Sopenharmony_ci		detected_display_type = IMON_DISPLAY_TYPE_VFD;
19008c2ecf20Sopenharmony_ci		/*
19018c2ecf20Sopenharmony_ci		 * We don't know which one it is, allow user to set the
19028c2ecf20Sopenharmony_ci		 * RC6 one from userspace if IMON wasn't correct.
19038c2ecf20Sopenharmony_ci		 */
19048c2ecf20Sopenharmony_ci		allowed_protos |= RC_PROTO_BIT_RC6_MCE;
19058c2ecf20Sopenharmony_ci		break;
19068c2ecf20Sopenharmony_ci	}
19078c2ecf20Sopenharmony_ci
19088c2ecf20Sopenharmony_ci	printk(KERN_CONT " (id 0x%02x)\n", ffdc_cfg_byte);
19098c2ecf20Sopenharmony_ci
19108c2ecf20Sopenharmony_ci	ictx->display_type = detected_display_type;
19118c2ecf20Sopenharmony_ci	ictx->rc_proto = allowed_protos;
19128c2ecf20Sopenharmony_ci}
19138c2ecf20Sopenharmony_ci
19148c2ecf20Sopenharmony_cistatic void imon_set_display_type(struct imon_context *ictx)
19158c2ecf20Sopenharmony_ci{
19168c2ecf20Sopenharmony_ci	u8 configured_display_type = IMON_DISPLAY_TYPE_VFD;
19178c2ecf20Sopenharmony_ci
19188c2ecf20Sopenharmony_ci	/*
19198c2ecf20Sopenharmony_ci	 * Try to auto-detect the type of display if the user hasn't set
19208c2ecf20Sopenharmony_ci	 * it by hand via the display_type modparam. Default is VFD.
19218c2ecf20Sopenharmony_ci	 */
19228c2ecf20Sopenharmony_ci
19238c2ecf20Sopenharmony_ci	if (display_type == IMON_DISPLAY_TYPE_AUTO) {
19248c2ecf20Sopenharmony_ci		switch (ictx->product) {
19258c2ecf20Sopenharmony_ci		case 0xffdc:
19268c2ecf20Sopenharmony_ci			/* set in imon_get_ffdc_type() */
19278c2ecf20Sopenharmony_ci			configured_display_type = ictx->display_type;
19288c2ecf20Sopenharmony_ci			break;
19298c2ecf20Sopenharmony_ci		case 0x0034:
19308c2ecf20Sopenharmony_ci		case 0x0035:
19318c2ecf20Sopenharmony_ci			configured_display_type = IMON_DISPLAY_TYPE_VGA;
19328c2ecf20Sopenharmony_ci			break;
19338c2ecf20Sopenharmony_ci		case 0x0038:
19348c2ecf20Sopenharmony_ci		case 0x0039:
19358c2ecf20Sopenharmony_ci		case 0x0045:
19368c2ecf20Sopenharmony_ci			configured_display_type = IMON_DISPLAY_TYPE_LCD;
19378c2ecf20Sopenharmony_ci			break;
19388c2ecf20Sopenharmony_ci		case 0x003c:
19398c2ecf20Sopenharmony_ci		case 0x0041:
19408c2ecf20Sopenharmony_ci		case 0x0042:
19418c2ecf20Sopenharmony_ci		case 0x0043:
19428c2ecf20Sopenharmony_ci			configured_display_type = IMON_DISPLAY_TYPE_NONE;
19438c2ecf20Sopenharmony_ci			ictx->display_supported = false;
19448c2ecf20Sopenharmony_ci			break;
19458c2ecf20Sopenharmony_ci		case 0x0036:
19468c2ecf20Sopenharmony_ci		case 0x0044:
19478c2ecf20Sopenharmony_ci		default:
19488c2ecf20Sopenharmony_ci			configured_display_type = IMON_DISPLAY_TYPE_VFD;
19498c2ecf20Sopenharmony_ci			break;
19508c2ecf20Sopenharmony_ci		}
19518c2ecf20Sopenharmony_ci	} else {
19528c2ecf20Sopenharmony_ci		configured_display_type = display_type;
19538c2ecf20Sopenharmony_ci		if (display_type == IMON_DISPLAY_TYPE_NONE)
19548c2ecf20Sopenharmony_ci			ictx->display_supported = false;
19558c2ecf20Sopenharmony_ci		else
19568c2ecf20Sopenharmony_ci			ictx->display_supported = true;
19578c2ecf20Sopenharmony_ci		dev_info(ictx->dev, "%s: overriding display type to %d via modparam\n",
19588c2ecf20Sopenharmony_ci			 __func__, display_type);
19598c2ecf20Sopenharmony_ci	}
19608c2ecf20Sopenharmony_ci
19618c2ecf20Sopenharmony_ci	ictx->display_type = configured_display_type;
19628c2ecf20Sopenharmony_ci}
19638c2ecf20Sopenharmony_ci
19648c2ecf20Sopenharmony_cistatic struct rc_dev *imon_init_rdev(struct imon_context *ictx)
19658c2ecf20Sopenharmony_ci{
19668c2ecf20Sopenharmony_ci	struct rc_dev *rdev;
19678c2ecf20Sopenharmony_ci	int ret;
19688c2ecf20Sopenharmony_ci	static const unsigned char fp_packet[] = {
19698c2ecf20Sopenharmony_ci		0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88 };
19708c2ecf20Sopenharmony_ci
19718c2ecf20Sopenharmony_ci	rdev = rc_allocate_device(RC_DRIVER_SCANCODE);
19728c2ecf20Sopenharmony_ci	if (!rdev) {
19738c2ecf20Sopenharmony_ci		dev_err(ictx->dev, "remote control dev allocation failed\n");
19748c2ecf20Sopenharmony_ci		goto out;
19758c2ecf20Sopenharmony_ci	}
19768c2ecf20Sopenharmony_ci
19778c2ecf20Sopenharmony_ci	snprintf(ictx->name_rdev, sizeof(ictx->name_rdev),
19788c2ecf20Sopenharmony_ci		 "iMON Remote (%04x:%04x)", ictx->vendor, ictx->product);
19798c2ecf20Sopenharmony_ci	usb_make_path(ictx->usbdev_intf0, ictx->phys_rdev,
19808c2ecf20Sopenharmony_ci		      sizeof(ictx->phys_rdev));
19818c2ecf20Sopenharmony_ci	strlcat(ictx->phys_rdev, "/input0", sizeof(ictx->phys_rdev));
19828c2ecf20Sopenharmony_ci
19838c2ecf20Sopenharmony_ci	rdev->device_name = ictx->name_rdev;
19848c2ecf20Sopenharmony_ci	rdev->input_phys = ictx->phys_rdev;
19858c2ecf20Sopenharmony_ci	usb_to_input_id(ictx->usbdev_intf0, &rdev->input_id);
19868c2ecf20Sopenharmony_ci	rdev->dev.parent = ictx->dev;
19878c2ecf20Sopenharmony_ci
19888c2ecf20Sopenharmony_ci	rdev->priv = ictx;
19898c2ecf20Sopenharmony_ci	/* iMON PAD or MCE */
19908c2ecf20Sopenharmony_ci	rdev->allowed_protocols = RC_PROTO_BIT_IMON | RC_PROTO_BIT_RC6_MCE;
19918c2ecf20Sopenharmony_ci	rdev->change_protocol = imon_ir_change_protocol;
19928c2ecf20Sopenharmony_ci	rdev->driver_name = MOD_NAME;
19938c2ecf20Sopenharmony_ci
19948c2ecf20Sopenharmony_ci	/* Enable front-panel buttons and/or knobs */
19958c2ecf20Sopenharmony_ci	memcpy(ictx->usb_tx_buf, &fp_packet, sizeof(fp_packet));
19968c2ecf20Sopenharmony_ci	ret = send_packet(ictx);
19978c2ecf20Sopenharmony_ci	/* Not fatal, but warn about it */
19988c2ecf20Sopenharmony_ci	if (ret)
19998c2ecf20Sopenharmony_ci		dev_info(ictx->dev, "panel buttons/knobs setup failed\n");
20008c2ecf20Sopenharmony_ci
20018c2ecf20Sopenharmony_ci	if (ictx->product == 0xffdc) {
20028c2ecf20Sopenharmony_ci		imon_get_ffdc_type(ictx);
20038c2ecf20Sopenharmony_ci		rdev->allowed_protocols = ictx->rc_proto;
20048c2ecf20Sopenharmony_ci	}
20058c2ecf20Sopenharmony_ci
20068c2ecf20Sopenharmony_ci	imon_set_display_type(ictx);
20078c2ecf20Sopenharmony_ci
20088c2ecf20Sopenharmony_ci	if (ictx->rc_proto == RC_PROTO_BIT_RC6_MCE)
20098c2ecf20Sopenharmony_ci		rdev->map_name = RC_MAP_IMON_MCE;
20108c2ecf20Sopenharmony_ci	else
20118c2ecf20Sopenharmony_ci		rdev->map_name = RC_MAP_IMON_PAD;
20128c2ecf20Sopenharmony_ci
20138c2ecf20Sopenharmony_ci	ret = rc_register_device(rdev);
20148c2ecf20Sopenharmony_ci	if (ret < 0) {
20158c2ecf20Sopenharmony_ci		dev_err(ictx->dev, "remote input dev register failed\n");
20168c2ecf20Sopenharmony_ci		goto out;
20178c2ecf20Sopenharmony_ci	}
20188c2ecf20Sopenharmony_ci
20198c2ecf20Sopenharmony_ci	return rdev;
20208c2ecf20Sopenharmony_ci
20218c2ecf20Sopenharmony_ciout:
20228c2ecf20Sopenharmony_ci	rc_free_device(rdev);
20238c2ecf20Sopenharmony_ci	return NULL;
20248c2ecf20Sopenharmony_ci}
20258c2ecf20Sopenharmony_ci
20268c2ecf20Sopenharmony_cistatic struct input_dev *imon_init_idev(struct imon_context *ictx)
20278c2ecf20Sopenharmony_ci{
20288c2ecf20Sopenharmony_ci	const struct imon_panel_key_table *key_table;
20298c2ecf20Sopenharmony_ci	struct input_dev *idev;
20308c2ecf20Sopenharmony_ci	int ret, i;
20318c2ecf20Sopenharmony_ci
20328c2ecf20Sopenharmony_ci	key_table = ictx->dev_descr->key_table;
20338c2ecf20Sopenharmony_ci
20348c2ecf20Sopenharmony_ci	idev = input_allocate_device();
20358c2ecf20Sopenharmony_ci	if (!idev)
20368c2ecf20Sopenharmony_ci		goto out;
20378c2ecf20Sopenharmony_ci
20388c2ecf20Sopenharmony_ci	snprintf(ictx->name_idev, sizeof(ictx->name_idev),
20398c2ecf20Sopenharmony_ci		 "iMON Panel, Knob and Mouse(%04x:%04x)",
20408c2ecf20Sopenharmony_ci		 ictx->vendor, ictx->product);
20418c2ecf20Sopenharmony_ci	idev->name = ictx->name_idev;
20428c2ecf20Sopenharmony_ci
20438c2ecf20Sopenharmony_ci	usb_make_path(ictx->usbdev_intf0, ictx->phys_idev,
20448c2ecf20Sopenharmony_ci		      sizeof(ictx->phys_idev));
20458c2ecf20Sopenharmony_ci	strlcat(ictx->phys_idev, "/input1", sizeof(ictx->phys_idev));
20468c2ecf20Sopenharmony_ci	idev->phys = ictx->phys_idev;
20478c2ecf20Sopenharmony_ci
20488c2ecf20Sopenharmony_ci	idev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP) | BIT_MASK(EV_REL);
20498c2ecf20Sopenharmony_ci
20508c2ecf20Sopenharmony_ci	idev->keybit[BIT_WORD(BTN_MOUSE)] =
20518c2ecf20Sopenharmony_ci		BIT_MASK(BTN_LEFT) | BIT_MASK(BTN_RIGHT);
20528c2ecf20Sopenharmony_ci	idev->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y) |
20538c2ecf20Sopenharmony_ci		BIT_MASK(REL_WHEEL);
20548c2ecf20Sopenharmony_ci
20558c2ecf20Sopenharmony_ci	/* panel and/or knob code support */
20568c2ecf20Sopenharmony_ci	for (i = 0; key_table[i].hw_code != 0; i++) {
20578c2ecf20Sopenharmony_ci		u32 kc = key_table[i].keycode;
20588c2ecf20Sopenharmony_ci		__set_bit(kc, idev->keybit);
20598c2ecf20Sopenharmony_ci	}
20608c2ecf20Sopenharmony_ci
20618c2ecf20Sopenharmony_ci	usb_to_input_id(ictx->usbdev_intf0, &idev->id);
20628c2ecf20Sopenharmony_ci	idev->dev.parent = ictx->dev;
20638c2ecf20Sopenharmony_ci	input_set_drvdata(idev, ictx);
20648c2ecf20Sopenharmony_ci
20658c2ecf20Sopenharmony_ci	ret = input_register_device(idev);
20668c2ecf20Sopenharmony_ci	if (ret < 0) {
20678c2ecf20Sopenharmony_ci		dev_err(ictx->dev, "input dev register failed\n");
20688c2ecf20Sopenharmony_ci		goto out;
20698c2ecf20Sopenharmony_ci	}
20708c2ecf20Sopenharmony_ci
20718c2ecf20Sopenharmony_ci	return idev;
20728c2ecf20Sopenharmony_ci
20738c2ecf20Sopenharmony_ciout:
20748c2ecf20Sopenharmony_ci	input_free_device(idev);
20758c2ecf20Sopenharmony_ci	return NULL;
20768c2ecf20Sopenharmony_ci}
20778c2ecf20Sopenharmony_ci
20788c2ecf20Sopenharmony_cistatic struct input_dev *imon_init_touch(struct imon_context *ictx)
20798c2ecf20Sopenharmony_ci{
20808c2ecf20Sopenharmony_ci	struct input_dev *touch;
20818c2ecf20Sopenharmony_ci	int ret;
20828c2ecf20Sopenharmony_ci
20838c2ecf20Sopenharmony_ci	touch = input_allocate_device();
20848c2ecf20Sopenharmony_ci	if (!touch)
20858c2ecf20Sopenharmony_ci		goto touch_alloc_failed;
20868c2ecf20Sopenharmony_ci
20878c2ecf20Sopenharmony_ci	snprintf(ictx->name_touch, sizeof(ictx->name_touch),
20888c2ecf20Sopenharmony_ci		 "iMON USB Touchscreen (%04x:%04x)",
20898c2ecf20Sopenharmony_ci		 ictx->vendor, ictx->product);
20908c2ecf20Sopenharmony_ci	touch->name = ictx->name_touch;
20918c2ecf20Sopenharmony_ci
20928c2ecf20Sopenharmony_ci	usb_make_path(ictx->usbdev_intf1, ictx->phys_touch,
20938c2ecf20Sopenharmony_ci		      sizeof(ictx->phys_touch));
20948c2ecf20Sopenharmony_ci	strlcat(ictx->phys_touch, "/input2", sizeof(ictx->phys_touch));
20958c2ecf20Sopenharmony_ci	touch->phys = ictx->phys_touch;
20968c2ecf20Sopenharmony_ci
20978c2ecf20Sopenharmony_ci	touch->evbit[0] =
20988c2ecf20Sopenharmony_ci		BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
20998c2ecf20Sopenharmony_ci	touch->keybit[BIT_WORD(BTN_TOUCH)] =
21008c2ecf20Sopenharmony_ci		BIT_MASK(BTN_TOUCH);
21018c2ecf20Sopenharmony_ci	input_set_abs_params(touch, ABS_X,
21028c2ecf20Sopenharmony_ci			     0x00, 0xfff, 0, 0);
21038c2ecf20Sopenharmony_ci	input_set_abs_params(touch, ABS_Y,
21048c2ecf20Sopenharmony_ci			     0x00, 0xfff, 0, 0);
21058c2ecf20Sopenharmony_ci
21068c2ecf20Sopenharmony_ci	input_set_drvdata(touch, ictx);
21078c2ecf20Sopenharmony_ci
21088c2ecf20Sopenharmony_ci	usb_to_input_id(ictx->usbdev_intf1, &touch->id);
21098c2ecf20Sopenharmony_ci	touch->dev.parent = ictx->dev;
21108c2ecf20Sopenharmony_ci	ret = input_register_device(touch);
21118c2ecf20Sopenharmony_ci	if (ret <  0) {
21128c2ecf20Sopenharmony_ci		dev_info(ictx->dev, "touchscreen input dev register failed\n");
21138c2ecf20Sopenharmony_ci		goto touch_register_failed;
21148c2ecf20Sopenharmony_ci	}
21158c2ecf20Sopenharmony_ci
21168c2ecf20Sopenharmony_ci	return touch;
21178c2ecf20Sopenharmony_ci
21188c2ecf20Sopenharmony_citouch_register_failed:
21198c2ecf20Sopenharmony_ci	input_free_device(touch);
21208c2ecf20Sopenharmony_ci
21218c2ecf20Sopenharmony_citouch_alloc_failed:
21228c2ecf20Sopenharmony_ci	return NULL;
21238c2ecf20Sopenharmony_ci}
21248c2ecf20Sopenharmony_ci
21258c2ecf20Sopenharmony_cistatic bool imon_find_endpoints(struct imon_context *ictx,
21268c2ecf20Sopenharmony_ci				struct usb_host_interface *iface_desc)
21278c2ecf20Sopenharmony_ci{
21288c2ecf20Sopenharmony_ci	struct usb_endpoint_descriptor *ep;
21298c2ecf20Sopenharmony_ci	struct usb_endpoint_descriptor *rx_endpoint = NULL;
21308c2ecf20Sopenharmony_ci	struct usb_endpoint_descriptor *tx_endpoint = NULL;
21318c2ecf20Sopenharmony_ci	int ifnum = iface_desc->desc.bInterfaceNumber;
21328c2ecf20Sopenharmony_ci	int num_endpts = iface_desc->desc.bNumEndpoints;
21338c2ecf20Sopenharmony_ci	int i, ep_dir, ep_type;
21348c2ecf20Sopenharmony_ci	bool ir_ep_found = false;
21358c2ecf20Sopenharmony_ci	bool display_ep_found = false;
21368c2ecf20Sopenharmony_ci	bool tx_control = false;
21378c2ecf20Sopenharmony_ci
21388c2ecf20Sopenharmony_ci	/*
21398c2ecf20Sopenharmony_ci	 * Scan the endpoint list and set:
21408c2ecf20Sopenharmony_ci	 *	first input endpoint = IR endpoint
21418c2ecf20Sopenharmony_ci	 *	first output endpoint = display endpoint
21428c2ecf20Sopenharmony_ci	 */
21438c2ecf20Sopenharmony_ci	for (i = 0; i < num_endpts && !(ir_ep_found && display_ep_found); ++i) {
21448c2ecf20Sopenharmony_ci		ep = &iface_desc->endpoint[i].desc;
21458c2ecf20Sopenharmony_ci		ep_dir = ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK;
21468c2ecf20Sopenharmony_ci		ep_type = usb_endpoint_type(ep);
21478c2ecf20Sopenharmony_ci
21488c2ecf20Sopenharmony_ci		if (!ir_ep_found && ep_dir == USB_DIR_IN &&
21498c2ecf20Sopenharmony_ci		    ep_type == USB_ENDPOINT_XFER_INT) {
21508c2ecf20Sopenharmony_ci
21518c2ecf20Sopenharmony_ci			rx_endpoint = ep;
21528c2ecf20Sopenharmony_ci			ir_ep_found = true;
21538c2ecf20Sopenharmony_ci			dev_dbg(ictx->dev, "%s: found IR endpoint\n", __func__);
21548c2ecf20Sopenharmony_ci
21558c2ecf20Sopenharmony_ci		} else if (!display_ep_found && ep_dir == USB_DIR_OUT &&
21568c2ecf20Sopenharmony_ci			   ep_type == USB_ENDPOINT_XFER_INT) {
21578c2ecf20Sopenharmony_ci			tx_endpoint = ep;
21588c2ecf20Sopenharmony_ci			display_ep_found = true;
21598c2ecf20Sopenharmony_ci			dev_dbg(ictx->dev, "%s: found display endpoint\n", __func__);
21608c2ecf20Sopenharmony_ci		}
21618c2ecf20Sopenharmony_ci	}
21628c2ecf20Sopenharmony_ci
21638c2ecf20Sopenharmony_ci	if (ifnum == 0) {
21648c2ecf20Sopenharmony_ci		ictx->rx_endpoint_intf0 = rx_endpoint;
21658c2ecf20Sopenharmony_ci		/*
21668c2ecf20Sopenharmony_ci		 * tx is used to send characters to lcd/vfd, associate RF
21678c2ecf20Sopenharmony_ci		 * remotes, set IR protocol, and maybe more...
21688c2ecf20Sopenharmony_ci		 */
21698c2ecf20Sopenharmony_ci		ictx->tx_endpoint = tx_endpoint;
21708c2ecf20Sopenharmony_ci	} else {
21718c2ecf20Sopenharmony_ci		ictx->rx_endpoint_intf1 = rx_endpoint;
21728c2ecf20Sopenharmony_ci	}
21738c2ecf20Sopenharmony_ci
21748c2ecf20Sopenharmony_ci	/*
21758c2ecf20Sopenharmony_ci	 * If we didn't find a display endpoint, this is probably one of the
21768c2ecf20Sopenharmony_ci	 * newer iMON devices that use control urb instead of interrupt
21778c2ecf20Sopenharmony_ci	 */
21788c2ecf20Sopenharmony_ci	if (!display_ep_found) {
21798c2ecf20Sopenharmony_ci		tx_control = true;
21808c2ecf20Sopenharmony_ci		display_ep_found = true;
21818c2ecf20Sopenharmony_ci		dev_dbg(ictx->dev, "%s: device uses control endpoint, not interface OUT endpoint\n",
21828c2ecf20Sopenharmony_ci			__func__);
21838c2ecf20Sopenharmony_ci	}
21848c2ecf20Sopenharmony_ci
21858c2ecf20Sopenharmony_ci	/*
21868c2ecf20Sopenharmony_ci	 * Some iMON receivers have no display. Unfortunately, it seems
21878c2ecf20Sopenharmony_ci	 * that SoundGraph recycles device IDs between devices both with
21888c2ecf20Sopenharmony_ci	 * and without... :\
21898c2ecf20Sopenharmony_ci	 */
21908c2ecf20Sopenharmony_ci	if (ictx->display_type == IMON_DISPLAY_TYPE_NONE) {
21918c2ecf20Sopenharmony_ci		display_ep_found = false;
21928c2ecf20Sopenharmony_ci		dev_dbg(ictx->dev, "%s: device has no display\n", __func__);
21938c2ecf20Sopenharmony_ci	}
21948c2ecf20Sopenharmony_ci
21958c2ecf20Sopenharmony_ci	/*
21968c2ecf20Sopenharmony_ci	 * iMON Touch devices have a VGA touchscreen, but no "display", as
21978c2ecf20Sopenharmony_ci	 * that refers to e.g. /dev/lcd0 (a character device LCD or VFD).
21988c2ecf20Sopenharmony_ci	 */
21998c2ecf20Sopenharmony_ci	if (ictx->display_type == IMON_DISPLAY_TYPE_VGA) {
22008c2ecf20Sopenharmony_ci		display_ep_found = false;
22018c2ecf20Sopenharmony_ci		dev_dbg(ictx->dev, "%s: iMON Touch device found\n", __func__);
22028c2ecf20Sopenharmony_ci	}
22038c2ecf20Sopenharmony_ci
22048c2ecf20Sopenharmony_ci	/* Input endpoint is mandatory */
22058c2ecf20Sopenharmony_ci	if (!ir_ep_found)
22068c2ecf20Sopenharmony_ci		pr_err("no valid input (IR) endpoint found\n");
22078c2ecf20Sopenharmony_ci
22088c2ecf20Sopenharmony_ci	ictx->tx_control = tx_control;
22098c2ecf20Sopenharmony_ci
22108c2ecf20Sopenharmony_ci	if (display_ep_found)
22118c2ecf20Sopenharmony_ci		ictx->display_supported = true;
22128c2ecf20Sopenharmony_ci
22138c2ecf20Sopenharmony_ci	return ir_ep_found;
22148c2ecf20Sopenharmony_ci
22158c2ecf20Sopenharmony_ci}
22168c2ecf20Sopenharmony_ci
22178c2ecf20Sopenharmony_cistatic struct imon_context *imon_init_intf0(struct usb_interface *intf,
22188c2ecf20Sopenharmony_ci					    const struct usb_device_id *id)
22198c2ecf20Sopenharmony_ci{
22208c2ecf20Sopenharmony_ci	struct imon_context *ictx;
22218c2ecf20Sopenharmony_ci	struct urb *rx_urb;
22228c2ecf20Sopenharmony_ci	struct urb *tx_urb;
22238c2ecf20Sopenharmony_ci	struct device *dev = &intf->dev;
22248c2ecf20Sopenharmony_ci	struct usb_host_interface *iface_desc;
22258c2ecf20Sopenharmony_ci	int ret = -ENOMEM;
22268c2ecf20Sopenharmony_ci
22278c2ecf20Sopenharmony_ci	ictx = kzalloc(sizeof(*ictx), GFP_KERNEL);
22288c2ecf20Sopenharmony_ci	if (!ictx)
22298c2ecf20Sopenharmony_ci		goto exit;
22308c2ecf20Sopenharmony_ci
22318c2ecf20Sopenharmony_ci	rx_urb = usb_alloc_urb(0, GFP_KERNEL);
22328c2ecf20Sopenharmony_ci	if (!rx_urb)
22338c2ecf20Sopenharmony_ci		goto rx_urb_alloc_failed;
22348c2ecf20Sopenharmony_ci	tx_urb = usb_alloc_urb(0, GFP_KERNEL);
22358c2ecf20Sopenharmony_ci	if (!tx_urb)
22368c2ecf20Sopenharmony_ci		goto tx_urb_alloc_failed;
22378c2ecf20Sopenharmony_ci
22388c2ecf20Sopenharmony_ci	mutex_init(&ictx->lock);
22398c2ecf20Sopenharmony_ci	spin_lock_init(&ictx->kc_lock);
22408c2ecf20Sopenharmony_ci
22418c2ecf20Sopenharmony_ci	mutex_lock(&ictx->lock);
22428c2ecf20Sopenharmony_ci
22438c2ecf20Sopenharmony_ci	ictx->dev = dev;
22448c2ecf20Sopenharmony_ci	ictx->usbdev_intf0 = usb_get_dev(interface_to_usbdev(intf));
22458c2ecf20Sopenharmony_ci	ictx->rx_urb_intf0 = rx_urb;
22468c2ecf20Sopenharmony_ci	ictx->tx_urb = tx_urb;
22478c2ecf20Sopenharmony_ci	ictx->rf_device = false;
22488c2ecf20Sopenharmony_ci
22498c2ecf20Sopenharmony_ci	init_completion(&ictx->tx.finished);
22508c2ecf20Sopenharmony_ci
22518c2ecf20Sopenharmony_ci	ictx->vendor  = le16_to_cpu(ictx->usbdev_intf0->descriptor.idVendor);
22528c2ecf20Sopenharmony_ci	ictx->product = le16_to_cpu(ictx->usbdev_intf0->descriptor.idProduct);
22538c2ecf20Sopenharmony_ci
22548c2ecf20Sopenharmony_ci	/* save drive info for later accessing the panel/knob key table */
22558c2ecf20Sopenharmony_ci	ictx->dev_descr = (struct imon_usb_dev_descr *)id->driver_info;
22568c2ecf20Sopenharmony_ci	/* default send_packet delay is 5ms but some devices need more */
22578c2ecf20Sopenharmony_ci	ictx->send_packet_delay = ictx->dev_descr->flags &
22588c2ecf20Sopenharmony_ci				  IMON_NEED_20MS_PKT_DELAY ? 20 : 5;
22598c2ecf20Sopenharmony_ci
22608c2ecf20Sopenharmony_ci	ret = -ENODEV;
22618c2ecf20Sopenharmony_ci	iface_desc = intf->cur_altsetting;
22628c2ecf20Sopenharmony_ci	if (!imon_find_endpoints(ictx, iface_desc)) {
22638c2ecf20Sopenharmony_ci		goto find_endpoint_failed;
22648c2ecf20Sopenharmony_ci	}
22658c2ecf20Sopenharmony_ci
22668c2ecf20Sopenharmony_ci	usb_fill_int_urb(ictx->rx_urb_intf0, ictx->usbdev_intf0,
22678c2ecf20Sopenharmony_ci		usb_rcvintpipe(ictx->usbdev_intf0,
22688c2ecf20Sopenharmony_ci			ictx->rx_endpoint_intf0->bEndpointAddress),
22698c2ecf20Sopenharmony_ci		ictx->usb_rx_buf, sizeof(ictx->usb_rx_buf),
22708c2ecf20Sopenharmony_ci		usb_rx_callback_intf0, ictx,
22718c2ecf20Sopenharmony_ci		ictx->rx_endpoint_intf0->bInterval);
22728c2ecf20Sopenharmony_ci
22738c2ecf20Sopenharmony_ci	ret = usb_submit_urb(ictx->rx_urb_intf0, GFP_KERNEL);
22748c2ecf20Sopenharmony_ci	if (ret) {
22758c2ecf20Sopenharmony_ci		pr_err("usb_submit_urb failed for intf0 (%d)\n", ret);
22768c2ecf20Sopenharmony_ci		goto urb_submit_failed;
22778c2ecf20Sopenharmony_ci	}
22788c2ecf20Sopenharmony_ci
22798c2ecf20Sopenharmony_ci	ictx->idev = imon_init_idev(ictx);
22808c2ecf20Sopenharmony_ci	if (!ictx->idev) {
22818c2ecf20Sopenharmony_ci		dev_err(dev, "%s: input device setup failed\n", __func__);
22828c2ecf20Sopenharmony_ci		goto idev_setup_failed;
22838c2ecf20Sopenharmony_ci	}
22848c2ecf20Sopenharmony_ci
22858c2ecf20Sopenharmony_ci	ictx->rdev = imon_init_rdev(ictx);
22868c2ecf20Sopenharmony_ci	if (!ictx->rdev) {
22878c2ecf20Sopenharmony_ci		dev_err(dev, "%s: rc device setup failed\n", __func__);
22888c2ecf20Sopenharmony_ci		goto rdev_setup_failed;
22898c2ecf20Sopenharmony_ci	}
22908c2ecf20Sopenharmony_ci
22918c2ecf20Sopenharmony_ci	ictx->dev_present_intf0 = true;
22928c2ecf20Sopenharmony_ci
22938c2ecf20Sopenharmony_ci	mutex_unlock(&ictx->lock);
22948c2ecf20Sopenharmony_ci	return ictx;
22958c2ecf20Sopenharmony_ci
22968c2ecf20Sopenharmony_cirdev_setup_failed:
22978c2ecf20Sopenharmony_ci	input_unregister_device(ictx->idev);
22988c2ecf20Sopenharmony_ciidev_setup_failed:
22998c2ecf20Sopenharmony_ci	usb_kill_urb(ictx->rx_urb_intf0);
23008c2ecf20Sopenharmony_ciurb_submit_failed:
23018c2ecf20Sopenharmony_cifind_endpoint_failed:
23028c2ecf20Sopenharmony_ci	usb_put_dev(ictx->usbdev_intf0);
23038c2ecf20Sopenharmony_ci	mutex_unlock(&ictx->lock);
23048c2ecf20Sopenharmony_ci	usb_free_urb(tx_urb);
23058c2ecf20Sopenharmony_citx_urb_alloc_failed:
23068c2ecf20Sopenharmony_ci	usb_free_urb(rx_urb);
23078c2ecf20Sopenharmony_cirx_urb_alloc_failed:
23088c2ecf20Sopenharmony_ci	kfree(ictx);
23098c2ecf20Sopenharmony_ciexit:
23108c2ecf20Sopenharmony_ci	dev_err(dev, "unable to initialize intf0, err %d\n", ret);
23118c2ecf20Sopenharmony_ci
23128c2ecf20Sopenharmony_ci	return NULL;
23138c2ecf20Sopenharmony_ci}
23148c2ecf20Sopenharmony_ci
23158c2ecf20Sopenharmony_cistatic struct imon_context *imon_init_intf1(struct usb_interface *intf,
23168c2ecf20Sopenharmony_ci					    struct imon_context *ictx)
23178c2ecf20Sopenharmony_ci{
23188c2ecf20Sopenharmony_ci	struct urb *rx_urb;
23198c2ecf20Sopenharmony_ci	struct usb_host_interface *iface_desc;
23208c2ecf20Sopenharmony_ci	int ret = -ENOMEM;
23218c2ecf20Sopenharmony_ci
23228c2ecf20Sopenharmony_ci	rx_urb = usb_alloc_urb(0, GFP_KERNEL);
23238c2ecf20Sopenharmony_ci	if (!rx_urb)
23248c2ecf20Sopenharmony_ci		goto rx_urb_alloc_failed;
23258c2ecf20Sopenharmony_ci
23268c2ecf20Sopenharmony_ci	mutex_lock(&ictx->lock);
23278c2ecf20Sopenharmony_ci
23288c2ecf20Sopenharmony_ci	if (ictx->display_type == IMON_DISPLAY_TYPE_VGA) {
23298c2ecf20Sopenharmony_ci		timer_setup(&ictx->ttimer, imon_touch_display_timeout, 0);
23308c2ecf20Sopenharmony_ci	}
23318c2ecf20Sopenharmony_ci
23328c2ecf20Sopenharmony_ci	ictx->usbdev_intf1 = usb_get_dev(interface_to_usbdev(intf));
23338c2ecf20Sopenharmony_ci	ictx->rx_urb_intf1 = rx_urb;
23348c2ecf20Sopenharmony_ci
23358c2ecf20Sopenharmony_ci	ret = -ENODEV;
23368c2ecf20Sopenharmony_ci	iface_desc = intf->cur_altsetting;
23378c2ecf20Sopenharmony_ci	if (!imon_find_endpoints(ictx, iface_desc))
23388c2ecf20Sopenharmony_ci		goto find_endpoint_failed;
23398c2ecf20Sopenharmony_ci
23408c2ecf20Sopenharmony_ci	if (ictx->display_type == IMON_DISPLAY_TYPE_VGA) {
23418c2ecf20Sopenharmony_ci		ictx->touch = imon_init_touch(ictx);
23428c2ecf20Sopenharmony_ci		if (!ictx->touch)
23438c2ecf20Sopenharmony_ci			goto touch_setup_failed;
23448c2ecf20Sopenharmony_ci	} else
23458c2ecf20Sopenharmony_ci		ictx->touch = NULL;
23468c2ecf20Sopenharmony_ci
23478c2ecf20Sopenharmony_ci	usb_fill_int_urb(ictx->rx_urb_intf1, ictx->usbdev_intf1,
23488c2ecf20Sopenharmony_ci		usb_rcvintpipe(ictx->usbdev_intf1,
23498c2ecf20Sopenharmony_ci			ictx->rx_endpoint_intf1->bEndpointAddress),
23508c2ecf20Sopenharmony_ci		ictx->usb_rx_buf, sizeof(ictx->usb_rx_buf),
23518c2ecf20Sopenharmony_ci		usb_rx_callback_intf1, ictx,
23528c2ecf20Sopenharmony_ci		ictx->rx_endpoint_intf1->bInterval);
23538c2ecf20Sopenharmony_ci
23548c2ecf20Sopenharmony_ci	ret = usb_submit_urb(ictx->rx_urb_intf1, GFP_KERNEL);
23558c2ecf20Sopenharmony_ci
23568c2ecf20Sopenharmony_ci	if (ret) {
23578c2ecf20Sopenharmony_ci		pr_err("usb_submit_urb failed for intf1 (%d)\n", ret);
23588c2ecf20Sopenharmony_ci		goto urb_submit_failed;
23598c2ecf20Sopenharmony_ci	}
23608c2ecf20Sopenharmony_ci
23618c2ecf20Sopenharmony_ci	ictx->dev_present_intf1 = true;
23628c2ecf20Sopenharmony_ci
23638c2ecf20Sopenharmony_ci	mutex_unlock(&ictx->lock);
23648c2ecf20Sopenharmony_ci	return ictx;
23658c2ecf20Sopenharmony_ci
23668c2ecf20Sopenharmony_ciurb_submit_failed:
23678c2ecf20Sopenharmony_ci	if (ictx->touch)
23688c2ecf20Sopenharmony_ci		input_unregister_device(ictx->touch);
23698c2ecf20Sopenharmony_citouch_setup_failed:
23708c2ecf20Sopenharmony_cifind_endpoint_failed:
23718c2ecf20Sopenharmony_ci	usb_put_dev(ictx->usbdev_intf1);
23728c2ecf20Sopenharmony_ci	mutex_unlock(&ictx->lock);
23738c2ecf20Sopenharmony_ci	usb_free_urb(rx_urb);
23748c2ecf20Sopenharmony_cirx_urb_alloc_failed:
23758c2ecf20Sopenharmony_ci	dev_err(ictx->dev, "unable to initialize intf1, err %d\n", ret);
23768c2ecf20Sopenharmony_ci
23778c2ecf20Sopenharmony_ci	return NULL;
23788c2ecf20Sopenharmony_ci}
23798c2ecf20Sopenharmony_ci
23808c2ecf20Sopenharmony_cistatic void imon_init_display(struct imon_context *ictx,
23818c2ecf20Sopenharmony_ci			      struct usb_interface *intf)
23828c2ecf20Sopenharmony_ci{
23838c2ecf20Sopenharmony_ci	int ret;
23848c2ecf20Sopenharmony_ci
23858c2ecf20Sopenharmony_ci	dev_dbg(ictx->dev, "Registering iMON display with sysfs\n");
23868c2ecf20Sopenharmony_ci
23878c2ecf20Sopenharmony_ci	/* set up sysfs entry for built-in clock */
23888c2ecf20Sopenharmony_ci	ret = sysfs_create_group(&intf->dev.kobj, &imon_display_attr_group);
23898c2ecf20Sopenharmony_ci	if (ret)
23908c2ecf20Sopenharmony_ci		dev_err(ictx->dev, "Could not create display sysfs entries(%d)",
23918c2ecf20Sopenharmony_ci			ret);
23928c2ecf20Sopenharmony_ci
23938c2ecf20Sopenharmony_ci	if (ictx->display_type == IMON_DISPLAY_TYPE_LCD)
23948c2ecf20Sopenharmony_ci		ret = usb_register_dev(intf, &imon_lcd_class);
23958c2ecf20Sopenharmony_ci	else
23968c2ecf20Sopenharmony_ci		ret = usb_register_dev(intf, &imon_vfd_class);
23978c2ecf20Sopenharmony_ci	if (ret)
23988c2ecf20Sopenharmony_ci		/* Not a fatal error, so ignore */
23998c2ecf20Sopenharmony_ci		dev_info(ictx->dev, "could not get a minor number for display\n");
24008c2ecf20Sopenharmony_ci
24018c2ecf20Sopenharmony_ci}
24028c2ecf20Sopenharmony_ci
24038c2ecf20Sopenharmony_ci/*
24048c2ecf20Sopenharmony_ci * Callback function for USB core API: Probe
24058c2ecf20Sopenharmony_ci */
24068c2ecf20Sopenharmony_cistatic int imon_probe(struct usb_interface *interface,
24078c2ecf20Sopenharmony_ci		      const struct usb_device_id *id)
24088c2ecf20Sopenharmony_ci{
24098c2ecf20Sopenharmony_ci	struct usb_device *usbdev = NULL;
24108c2ecf20Sopenharmony_ci	struct usb_host_interface *iface_desc = NULL;
24118c2ecf20Sopenharmony_ci	struct usb_interface *first_if;
24128c2ecf20Sopenharmony_ci	struct device *dev = &interface->dev;
24138c2ecf20Sopenharmony_ci	int ifnum, sysfs_err;
24148c2ecf20Sopenharmony_ci	int ret = 0;
24158c2ecf20Sopenharmony_ci	struct imon_context *ictx = NULL;
24168c2ecf20Sopenharmony_ci	u16 vendor, product;
24178c2ecf20Sopenharmony_ci
24188c2ecf20Sopenharmony_ci	usbdev     = usb_get_dev(interface_to_usbdev(interface));
24198c2ecf20Sopenharmony_ci	iface_desc = interface->cur_altsetting;
24208c2ecf20Sopenharmony_ci	ifnum      = iface_desc->desc.bInterfaceNumber;
24218c2ecf20Sopenharmony_ci	vendor     = le16_to_cpu(usbdev->descriptor.idVendor);
24228c2ecf20Sopenharmony_ci	product    = le16_to_cpu(usbdev->descriptor.idProduct);
24238c2ecf20Sopenharmony_ci
24248c2ecf20Sopenharmony_ci	dev_dbg(dev, "%s: found iMON device (%04x:%04x, intf%d)\n",
24258c2ecf20Sopenharmony_ci		__func__, vendor, product, ifnum);
24268c2ecf20Sopenharmony_ci
24278c2ecf20Sopenharmony_ci	first_if = usb_ifnum_to_if(usbdev, 0);
24288c2ecf20Sopenharmony_ci	if (!first_if) {
24298c2ecf20Sopenharmony_ci		ret = -ENODEV;
24308c2ecf20Sopenharmony_ci		goto fail;
24318c2ecf20Sopenharmony_ci	}
24328c2ecf20Sopenharmony_ci
24338c2ecf20Sopenharmony_ci	if (first_if->dev.driver != interface->dev.driver) {
24348c2ecf20Sopenharmony_ci		dev_err(&interface->dev, "inconsistent driver matching\n");
24358c2ecf20Sopenharmony_ci		ret = -EINVAL;
24368c2ecf20Sopenharmony_ci		goto fail;
24378c2ecf20Sopenharmony_ci	}
24388c2ecf20Sopenharmony_ci
24398c2ecf20Sopenharmony_ci	if (ifnum == 0) {
24408c2ecf20Sopenharmony_ci		ictx = imon_init_intf0(interface, id);
24418c2ecf20Sopenharmony_ci		if (!ictx) {
24428c2ecf20Sopenharmony_ci			pr_err("failed to initialize context!\n");
24438c2ecf20Sopenharmony_ci			ret = -ENODEV;
24448c2ecf20Sopenharmony_ci			goto fail;
24458c2ecf20Sopenharmony_ci		}
24468c2ecf20Sopenharmony_ci		refcount_set(&ictx->users, 1);
24478c2ecf20Sopenharmony_ci
24488c2ecf20Sopenharmony_ci	} else {
24498c2ecf20Sopenharmony_ci		/* this is the secondary interface on the device */
24508c2ecf20Sopenharmony_ci		struct imon_context *first_if_ctx = usb_get_intfdata(first_if);
24518c2ecf20Sopenharmony_ci
24528c2ecf20Sopenharmony_ci		/* fail early if first intf failed to register */
24538c2ecf20Sopenharmony_ci		if (!first_if_ctx) {
24548c2ecf20Sopenharmony_ci			ret = -ENODEV;
24558c2ecf20Sopenharmony_ci			goto fail;
24568c2ecf20Sopenharmony_ci		}
24578c2ecf20Sopenharmony_ci
24588c2ecf20Sopenharmony_ci		ictx = imon_init_intf1(interface, first_if_ctx);
24598c2ecf20Sopenharmony_ci		if (!ictx) {
24608c2ecf20Sopenharmony_ci			pr_err("failed to attach to context!\n");
24618c2ecf20Sopenharmony_ci			ret = -ENODEV;
24628c2ecf20Sopenharmony_ci			goto fail;
24638c2ecf20Sopenharmony_ci		}
24648c2ecf20Sopenharmony_ci		refcount_inc(&ictx->users);
24658c2ecf20Sopenharmony_ci
24668c2ecf20Sopenharmony_ci	}
24678c2ecf20Sopenharmony_ci
24688c2ecf20Sopenharmony_ci	usb_set_intfdata(interface, ictx);
24698c2ecf20Sopenharmony_ci
24708c2ecf20Sopenharmony_ci	if (ifnum == 0) {
24718c2ecf20Sopenharmony_ci		if (product == 0xffdc && ictx->rf_device) {
24728c2ecf20Sopenharmony_ci			sysfs_err = sysfs_create_group(&interface->dev.kobj,
24738c2ecf20Sopenharmony_ci						       &imon_rf_attr_group);
24748c2ecf20Sopenharmony_ci			if (sysfs_err)
24758c2ecf20Sopenharmony_ci				pr_err("Could not create RF sysfs entries(%d)\n",
24768c2ecf20Sopenharmony_ci				       sysfs_err);
24778c2ecf20Sopenharmony_ci		}
24788c2ecf20Sopenharmony_ci
24798c2ecf20Sopenharmony_ci		if (ictx->display_supported)
24808c2ecf20Sopenharmony_ci			imon_init_display(ictx, interface);
24818c2ecf20Sopenharmony_ci	}
24828c2ecf20Sopenharmony_ci
24838c2ecf20Sopenharmony_ci	dev_info(dev, "iMON device (%04x:%04x, intf%d) on usb<%d:%d> initialized\n",
24848c2ecf20Sopenharmony_ci		 vendor, product, ifnum,
24858c2ecf20Sopenharmony_ci		 usbdev->bus->busnum, usbdev->devnum);
24868c2ecf20Sopenharmony_ci
24878c2ecf20Sopenharmony_ci	usb_put_dev(usbdev);
24888c2ecf20Sopenharmony_ci
24898c2ecf20Sopenharmony_ci	return 0;
24908c2ecf20Sopenharmony_ci
24918c2ecf20Sopenharmony_cifail:
24928c2ecf20Sopenharmony_ci	usb_put_dev(usbdev);
24938c2ecf20Sopenharmony_ci	dev_err(dev, "unable to register, err %d\n", ret);
24948c2ecf20Sopenharmony_ci
24958c2ecf20Sopenharmony_ci	return ret;
24968c2ecf20Sopenharmony_ci}
24978c2ecf20Sopenharmony_ci
24988c2ecf20Sopenharmony_ci/*
24998c2ecf20Sopenharmony_ci * Callback function for USB core API: disconnect
25008c2ecf20Sopenharmony_ci */
25018c2ecf20Sopenharmony_cistatic void imon_disconnect(struct usb_interface *interface)
25028c2ecf20Sopenharmony_ci{
25038c2ecf20Sopenharmony_ci	struct imon_context *ictx;
25048c2ecf20Sopenharmony_ci	struct device *dev;
25058c2ecf20Sopenharmony_ci	int ifnum;
25068c2ecf20Sopenharmony_ci
25078c2ecf20Sopenharmony_ci	ictx = usb_get_intfdata(interface);
25088c2ecf20Sopenharmony_ci	ictx->disconnected = true;
25098c2ecf20Sopenharmony_ci	dev = ictx->dev;
25108c2ecf20Sopenharmony_ci	ifnum = interface->cur_altsetting->desc.bInterfaceNumber;
25118c2ecf20Sopenharmony_ci
25128c2ecf20Sopenharmony_ci	/*
25138c2ecf20Sopenharmony_ci	 * sysfs_remove_group is safe to call even if sysfs_create_group
25148c2ecf20Sopenharmony_ci	 * hasn't been called
25158c2ecf20Sopenharmony_ci	 */
25168c2ecf20Sopenharmony_ci	sysfs_remove_group(&interface->dev.kobj, &imon_display_attr_group);
25178c2ecf20Sopenharmony_ci	sysfs_remove_group(&interface->dev.kobj, &imon_rf_attr_group);
25188c2ecf20Sopenharmony_ci
25198c2ecf20Sopenharmony_ci	usb_set_intfdata(interface, NULL);
25208c2ecf20Sopenharmony_ci
25218c2ecf20Sopenharmony_ci	/* Abort ongoing write */
25228c2ecf20Sopenharmony_ci	if (ictx->tx.busy) {
25238c2ecf20Sopenharmony_ci		usb_kill_urb(ictx->tx_urb);
25248c2ecf20Sopenharmony_ci		complete(&ictx->tx.finished);
25258c2ecf20Sopenharmony_ci	}
25268c2ecf20Sopenharmony_ci
25278c2ecf20Sopenharmony_ci	if (ifnum == 0) {
25288c2ecf20Sopenharmony_ci		ictx->dev_present_intf0 = false;
25298c2ecf20Sopenharmony_ci		usb_kill_urb(ictx->rx_urb_intf0);
25308c2ecf20Sopenharmony_ci		usb_put_dev(ictx->usbdev_intf0);
25318c2ecf20Sopenharmony_ci		input_unregister_device(ictx->idev);
25328c2ecf20Sopenharmony_ci		rc_unregister_device(ictx->rdev);
25338c2ecf20Sopenharmony_ci		if (ictx->display_supported) {
25348c2ecf20Sopenharmony_ci			if (ictx->display_type == IMON_DISPLAY_TYPE_LCD)
25358c2ecf20Sopenharmony_ci				usb_deregister_dev(interface, &imon_lcd_class);
25368c2ecf20Sopenharmony_ci			else if (ictx->display_type == IMON_DISPLAY_TYPE_VFD)
25378c2ecf20Sopenharmony_ci				usb_deregister_dev(interface, &imon_vfd_class);
25388c2ecf20Sopenharmony_ci		}
25398c2ecf20Sopenharmony_ci	} else {
25408c2ecf20Sopenharmony_ci		ictx->dev_present_intf1 = false;
25418c2ecf20Sopenharmony_ci		usb_kill_urb(ictx->rx_urb_intf1);
25428c2ecf20Sopenharmony_ci		usb_put_dev(ictx->usbdev_intf1);
25438c2ecf20Sopenharmony_ci		if (ictx->display_type == IMON_DISPLAY_TYPE_VGA) {
25448c2ecf20Sopenharmony_ci			input_unregister_device(ictx->touch);
25458c2ecf20Sopenharmony_ci			del_timer_sync(&ictx->ttimer);
25468c2ecf20Sopenharmony_ci		}
25478c2ecf20Sopenharmony_ci	}
25488c2ecf20Sopenharmony_ci
25498c2ecf20Sopenharmony_ci	if (refcount_dec_and_test(&ictx->users))
25508c2ecf20Sopenharmony_ci		free_imon_context(ictx);
25518c2ecf20Sopenharmony_ci
25528c2ecf20Sopenharmony_ci	dev_dbg(dev, "%s: iMON device (intf%d) disconnected\n",
25538c2ecf20Sopenharmony_ci		__func__, ifnum);
25548c2ecf20Sopenharmony_ci}
25558c2ecf20Sopenharmony_ci
25568c2ecf20Sopenharmony_cistatic int imon_suspend(struct usb_interface *intf, pm_message_t message)
25578c2ecf20Sopenharmony_ci{
25588c2ecf20Sopenharmony_ci	struct imon_context *ictx = usb_get_intfdata(intf);
25598c2ecf20Sopenharmony_ci	int ifnum = intf->cur_altsetting->desc.bInterfaceNumber;
25608c2ecf20Sopenharmony_ci
25618c2ecf20Sopenharmony_ci	if (ifnum == 0)
25628c2ecf20Sopenharmony_ci		usb_kill_urb(ictx->rx_urb_intf0);
25638c2ecf20Sopenharmony_ci	else
25648c2ecf20Sopenharmony_ci		usb_kill_urb(ictx->rx_urb_intf1);
25658c2ecf20Sopenharmony_ci
25668c2ecf20Sopenharmony_ci	return 0;
25678c2ecf20Sopenharmony_ci}
25688c2ecf20Sopenharmony_ci
25698c2ecf20Sopenharmony_cistatic int imon_resume(struct usb_interface *intf)
25708c2ecf20Sopenharmony_ci{
25718c2ecf20Sopenharmony_ci	int rc = 0;
25728c2ecf20Sopenharmony_ci	struct imon_context *ictx = usb_get_intfdata(intf);
25738c2ecf20Sopenharmony_ci	int ifnum = intf->cur_altsetting->desc.bInterfaceNumber;
25748c2ecf20Sopenharmony_ci
25758c2ecf20Sopenharmony_ci	if (ifnum == 0) {
25768c2ecf20Sopenharmony_ci		usb_fill_int_urb(ictx->rx_urb_intf0, ictx->usbdev_intf0,
25778c2ecf20Sopenharmony_ci			usb_rcvintpipe(ictx->usbdev_intf0,
25788c2ecf20Sopenharmony_ci				ictx->rx_endpoint_intf0->bEndpointAddress),
25798c2ecf20Sopenharmony_ci			ictx->usb_rx_buf, sizeof(ictx->usb_rx_buf),
25808c2ecf20Sopenharmony_ci			usb_rx_callback_intf0, ictx,
25818c2ecf20Sopenharmony_ci			ictx->rx_endpoint_intf0->bInterval);
25828c2ecf20Sopenharmony_ci
25838c2ecf20Sopenharmony_ci		rc = usb_submit_urb(ictx->rx_urb_intf0, GFP_ATOMIC);
25848c2ecf20Sopenharmony_ci
25858c2ecf20Sopenharmony_ci	} else {
25868c2ecf20Sopenharmony_ci		usb_fill_int_urb(ictx->rx_urb_intf1, ictx->usbdev_intf1,
25878c2ecf20Sopenharmony_ci			usb_rcvintpipe(ictx->usbdev_intf1,
25888c2ecf20Sopenharmony_ci				ictx->rx_endpoint_intf1->bEndpointAddress),
25898c2ecf20Sopenharmony_ci			ictx->usb_rx_buf, sizeof(ictx->usb_rx_buf),
25908c2ecf20Sopenharmony_ci			usb_rx_callback_intf1, ictx,
25918c2ecf20Sopenharmony_ci			ictx->rx_endpoint_intf1->bInterval);
25928c2ecf20Sopenharmony_ci
25938c2ecf20Sopenharmony_ci		rc = usb_submit_urb(ictx->rx_urb_intf1, GFP_ATOMIC);
25948c2ecf20Sopenharmony_ci	}
25958c2ecf20Sopenharmony_ci
25968c2ecf20Sopenharmony_ci	return rc;
25978c2ecf20Sopenharmony_ci}
25988c2ecf20Sopenharmony_ci
25998c2ecf20Sopenharmony_cimodule_usb_driver(imon_driver);
2600