// SPDX-License-Identifier: GPL-2.0-or-later /* * imon.c: input and display driver for SoundGraph iMON IR/VFD/LCD * * Copyright(C) 2010 Jarod Wilson <jarod@wilsonet.com> * Portions based on the original lirc_imon driver, * Copyright(C) 2004 Venky Raju(dev@venky.ws) * * Huge thanks to R. Geoff Newbury for invaluable debugging on the * 0xffdc iMON devices, and for sending me one to hack on, without * which the support for them wouldn't be nearly as good. Thanks * also to the numerous 0xffdc device owners that tested auto-config * support for me and provided debug dumps from their devices.
*/
struct tx_t { unsignedchar data_buf[35]; /* user data buffer */ struct completion finished; /* wait for write to finish */ bool busy; /* write in progress */ int status; /* status of tx completion */
} tx;
u16 vendor; /* usb vendor ID */
u16 product; /* usb product ID */
struct rc_dev *rdev; /* rc-core device for remote */ struct input_dev *idev; /* input device for panel & IR mouse */ struct input_dev *touch; /* input device for touchscreen */
spinlock_t kc_lock; /* make sure we get keycodes right */
u32 kc; /* current input keycode */
u32 last_keycode; /* last reported input keycode */
u32 rc_scancode; /* the computed remote scancode */
u8 rc_toggle; /* the computed remote toggle bit */
u64 rc_proto; /* iMON or MCE (RC6) IR protocol? */ bool release_code; /* some keys send a release code */
u8 display_type; /* store the display type */ bool pad_mouse; /* toggle kbd(0)/mouse(1) mode */
char name_touch[128]; /* touch screen name */ char phys_touch[64]; /* touch screen phys path */ struct timer_list ttimer; /* touch screen timer */ int touch_x; /* x coordinate on touchscreen */ int touch_y; /* y coordinate on touchscreen */ conststruct imon_usb_dev_descr *dev_descr; /* device description with key */ /* table for front panels */ /* * Fields for deferring free_imon_context(). * * Since reference to "struct imon_context" is stored into * "struct file"->private_data, we need to remember * how many file descriptors might access this "struct imon_context".
*/
refcount_t users; /* * Use a flag for telling display_open()/vfd_write()/lcd_write() that * imon_disconnect() was already called.
*/ bool disconnected; /* * We need to wait for RCU grace period in order to allow * display_open() to safely check ->disconnected and increment ->users.
*/ struct rcu_head rcu;
};
/* * USB Device ID for iMON USB Control Boards * * The Windows drivers contain 6 different inf files, more or less one for * each new device until the 0x0034-0x0046 devices, which all use the same * driver. Some of the devices in the 34-46 range haven't been definitively * identified yet. Early devices have either a TriGem Computer, Inc. or a * Samsung vendor ID (0x0aa8 and 0x04e8 respectively), while all later * devices use the SoundGraph vendor ID (0x15c2). This driver only supports * the ffdc and later devices, which do onboard decoding.
*/ staticconststruct usb_device_id imon_usb_id_table[] = { /* * Several devices with this same device ID, all use iMON_PAD.inf * SoundGraph iMON PAD (IR & VFD) * SoundGraph iMON PAD (IR & LCD) * SoundGraph iMON Knob (IR only)
*/
{ USB_DEVICE(0x15c2, 0xffdc),
.driver_info = (unsignedlong)&imon_default_table },
/* * Newer devices, all driven by the latest iMON Windows driver, full * list of device IDs extracted via 'strings Setup/data1.hdr |grep 15c2' * Need user input to fill in details on unknown devices.
*/ /* SoundGraph iMON OEM Touch LCD (IR & 7" VGA LCD) */
{ USB_DEVICE(0x15c2, 0x0034),
.driver_info = (unsignedlong)&imon_DH102 }, /* SoundGraph iMON OEM Touch LCD (IR & 4.3" VGA LCD) */
{ USB_DEVICE(0x15c2, 0x0035),
.driver_info = (unsignedlong)&imon_default_table}, /* SoundGraph iMON OEM VFD (IR & VFD) */
{ USB_DEVICE(0x15c2, 0x0036),
.driver_info = (unsignedlong)&imon_OEM_VFD }, /* device specifics unknown */
{ USB_DEVICE(0x15c2, 0x0037),
.driver_info = (unsignedlong)&imon_default_table}, /* SoundGraph iMON OEM LCD (IR & LCD) */
{ USB_DEVICE(0x15c2, 0x0038),
.driver_info = (unsignedlong)&imon_default_table}, /* SoundGraph iMON UltraBay (IR & LCD) */
{ USB_DEVICE(0x15c2, 0x0039),
.driver_info = (unsignedlong)&imon_default_table}, /* device specifics unknown */
{ USB_DEVICE(0x15c2, 0x003a),
.driver_info = (unsignedlong)&imon_default_table}, /* device specifics unknown */
{ USB_DEVICE(0x15c2, 0x003b),
.driver_info = (unsignedlong)&imon_default_table}, /* SoundGraph iMON OEM Inside (IR only) */
{ USB_DEVICE(0x15c2, 0x003c),
.driver_info = (unsignedlong)&imon_default_table}, /* device specifics unknown */
{ USB_DEVICE(0x15c2, 0x003d),
.driver_info = (unsignedlong)&imon_default_table}, /* device specifics unknown */
{ USB_DEVICE(0x15c2, 0x003e),
.driver_info = (unsignedlong)&imon_default_table}, /* device specifics unknown */
{ USB_DEVICE(0x15c2, 0x003f),
.driver_info = (unsignedlong)&imon_default_table}, /* device specifics unknown */
{ USB_DEVICE(0x15c2, 0x0040),
.driver_info = (unsignedlong)&imon_default_table}, /* SoundGraph iMON MINI (IR only) */
{ USB_DEVICE(0x15c2, 0x0041),
.driver_info = (unsignedlong)&imon_default_table}, /* Antec Veris Multimedia Station EZ External (IR only) */
{ USB_DEVICE(0x15c2, 0x0042),
.driver_info = (unsignedlong)&imon_default_table}, /* Antec Veris Multimedia Station Basic Internal (IR only) */
{ USB_DEVICE(0x15c2, 0x0043),
.driver_info = (unsignedlong)&imon_default_table}, /* Antec Veris Multimedia Station Elite (IR & VFD) */
{ USB_DEVICE(0x15c2, 0x0044),
.driver_info = (unsignedlong)&imon_default_table}, /* Antec Veris Multimedia Station Premiere (IR & LCD) */
{ USB_DEVICE(0x15c2, 0x0045),
.driver_info = (unsignedlong)&imon_default_table}, /* device specifics unknown */
{ USB_DEVICE(0x15c2, 0x0046),
.driver_info = (unsignedlong)&imon_default_table},
{}
};
/* lcd, vfd, vga or none? should be auto-detected, but can be overridden... */ staticint display_type;
module_param(display_type, int, S_IRUGO);
MODULE_PARM_DESC(display_type, "Type of attached display. 0=autodetect, 1=vfd, 2=lcd, 3=vga, 4=none (default: autodetect)");
staticint pad_stabilize = 1;
module_param(pad_stabilize, int, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(pad_stabilize, "Apply stabilization algorithm to iMON PAD presses in arrow key mode. 0=disable, 1=enable (default).");
/* * In certain use cases, mouse mode isn't really helpful, and could actually * cause confusion, so allow disabling it when the IR device is open.
*/ staticbool nomouse;
module_param(nomouse, bool, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(nomouse, "Disable mouse input device mode when IR device is open. 0=don't disable, 1=disable. (default: don't disable)");
/* threshold at which a pad push registers as an arrow key in kbd mode */ staticint pad_thresh;
module_param(pad_thresh, int, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(pad_thresh, "Threshold at which a pad push registers as an arrow key in kbd mode (default: 28)");
/* * Called when the Display device (e.g. /dev/lcd0) * is opened by the application.
*/ staticint display_open(struct inode *inode, struct file *file)
{ struct usb_interface *interface; struct imon_context *ictx = NULL; int subminor; int retval = 0;
subminor = iminor(inode);
interface = usb_find_interface(&imon_driver, subminor); if (!interface) {
pr_err("could not find interface for minor %d\n", subminor);
retval = -ENODEV; gotoexit;
}
rcu_read_lock();
ictx = usb_get_intfdata(interface); if (!ictx || ictx->disconnected || !refcount_inc_not_zero(&ictx->users)) {
rcu_read_unlock();
pr_err("no context found for minor %d\n", subminor);
retval = -ENODEV; gotoexit;
}
rcu_read_unlock();
mutex_lock(&ictx->lock);
if (ictx->disconnected) {
retval = -ENODEV;
} elseif (!ictx->display_supported) {
pr_err("display not supported by device\n");
retval = -ENODEV;
} elseif (ictx->display_isopen) {
pr_err("display port is already open\n");
retval = -EBUSY;
} else {
ictx->display_isopen = true;
file->private_data = ictx;
dev_dbg(ictx->dev, "display port opened\n");
}
mutex_unlock(&ictx->lock);
if (retval && refcount_dec_and_test(&ictx->users))
free_imon_context(ictx);
exit: return retval;
}
/* * Called when the display device (e.g. /dev/lcd0) * is closed by the application.
*/ staticint display_close(struct inode *inode, struct file *file)
{ struct imon_context *ictx = file->private_data; int retval = 0;
mutex_lock(&ictx->lock);
if (!ictx->display_supported) {
pr_err("display not supported by device\n");
retval = -ENODEV;
} elseif (!ictx->display_isopen) {
pr_err("display is not open\n");
retval = -EIO;
} else {
ictx->display_isopen = false;
dev_dbg(ictx->dev, "display port closed\n");
}
mutex_unlock(&ictx->lock); if (refcount_dec_and_test(&ictx->users))
free_imon_context(ictx); return retval;
}
/* * Sends a packet to the device -- this function must be called with * ictx->lock held, or its unlock/lock sequence while waiting for tx * to complete can/will lead to a deadlock.
*/ staticint send_packet(struct imon_context *ictx)
{ unsignedint pipe; unsignedlong timeout; int interval = 0; int retval = 0; struct usb_ctrlrequest *control_req = NULL;
if (ictx->disconnected) return -ENODEV;
/* Check if we need to use control or interrupt urb */ if (!ictx->tx_control) {
pipe = usb_sndintpipe(ictx->usbdev_intf0,
ictx->tx_endpoint->bEndpointAddress);
interval = ictx->tx_endpoint->bInterval;
/* * Induce a mandatory delay before returning, as otherwise, * send_packet can get called so rapidly as to overwhelm the device, * particularly on faster systems and/or those with quirky usb.
*/
timeout = msecs_to_jiffies(ictx->send_packet_delay);
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout(timeout);
return retval;
}
/* * Sends an associate packet to the iMON 2.4G. * * This might not be such a good idea, since it has an id collision with * some versions of the "IR & VFD" combo. The only way to determine if it * is an RF version is to look at the product description string. (Which * we currently do not fetch).
*/ staticint send_associate_24g(struct imon_context *ictx)
{ constunsignedchar packet[8] = { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20 };
if (!ictx) {
pr_err("no context for device\n"); return -ENODEV;
}
if (!ictx->dev_present_intf0) {
pr_err("no iMON device present\n"); return -ENODEV;
}
memcpy(ictx->usb_tx_buf, packet, sizeof(packet));
return send_packet(ictx);
}
/* * Sends packets to setup and show clock on iMON display * * Arguments: year - last 2 digits of year, month - 1..12, * day - 1..31, dow - day of the week (0-Sun...6-Sat), * hour - 0..23, minute - 0..59, second - 0..59
*/ staticint send_set_imon_clock(struct imon_context *ictx, unsignedint year, unsignedint month, unsignedint day, unsignedint dow, unsignedint hour, unsignedint minute, unsignedint second)
{ unsignedchar clock_enable_pkt[IMON_CLOCK_ENABLE_PACKETS][8]; int retval = 0; int i;
if (!ictx) {
pr_err("no context for device\n"); return -ENODEV;
}
for (i = 0; i < IMON_CLOCK_ENABLE_PACKETS; i++) {
memcpy(ictx->usb_tx_buf, clock_enable_pkt[i], 8);
retval = send_packet(ictx); if (retval) {
pr_err("send_packet failed for packet %d\n", i); break;
}
}
return retval;
}
/* * These are the sysfs functions to handle the association on the iMON 2.4G LT.
*/ static ssize_t associate_remote_show(struct device *d, struct device_attribute *attr, char *buf)
{ struct imon_context *ictx = dev_get_drvdata(d);
if (!ictx) return -ENODEV;
mutex_lock(&ictx->lock); if (ictx->rf_isassociating)
strscpy(buf, "associating\n", PAGE_SIZE); else
strscpy(buf, "closed\n", PAGE_SIZE);
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");
mutex_unlock(&ictx->lock); return strlen(buf);
}
if (!ictx->display_supported) {
len = sysfs_emit(buf, "Not supported.");
} else {
len = sysfs_emit(buf, "To set the clock on your iMON display:\n" "# date \"+%%y %%m %%d %%w %%H %%M %%S\" > imon_clock\n" "%s", ictx->display_isopen ? "\nNOTE: imon device must be closed\n" : "");
}
/* * Writes data to the VFD. The iMON VFD is 2x16 characters * and requires data in 5 consecutive USB interrupt packets, * each packet but the last carrying 7 bytes. * * I don't know if the VFD board supports features such as * scrolling, clearing rows, blanking, etc. so at * the caller must provide a full screen of data. If fewer * than 32 bytes are provided spaces will be appended to * generate a full screen.
*/ static ssize_t vfd_write(struct file *file, constchar __user *buf,
size_t n_bytes, loff_t *pos)
{ int i; int offset; int seq; int retval = 0; struct imon_context *ictx = file->private_data; staticconstunsignedchar vfd_packet6[] = { 0x01, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF };
if (mutex_lock_interruptible(&ictx->lock)) return -ERESTARTSYS;
if (ictx->disconnected) {
retval = -ENODEV; gotoexit;
}
/* * Writes data to the LCD. The iMON OEM LCD screen expects 8-byte * packets. We accept data as 16 hexadecimal digits, followed by a * newline (to make it easy to drive the device from a command-line * -- even though the actual binary data is a bit complicated). * * The device itself is not a "traditional" text-mode display. It's * actually a 16x96 pixel bitmap display. That means if you want to * display text, you've got to have your own "font" and translate the * text into bitmaps for display. This is really flexible (you can * display whatever diacritics you need, and so on), but it's also * a lot more complicated than most LCDs...
*/ static ssize_t lcd_write(struct file *file, constchar __user *buf,
size_t n_bytes, loff_t *pos)
{ int retval = 0; struct imon_context *ictx = file->private_data;
mutex_lock(&ictx->lock);
if (ictx->disconnected) {
retval = -ENODEV; gotoexit;
}
/* * iMON IR receivers support two different signal sets -- those used by * the iMON remotes, and those used by the Windows MCE remotes (which is * really just RC-6), but only one or the other at a time, as the signals * are decoded onboard the receiver. * * This function gets called two different ways, one way is from * rc_register_device, for initial protocol selection/setup, and the other is * via a userspace-initiated protocol change request, either by direct sysfs * prodding or by something like ir-keytable. In the rc_register_device case, * the imon context lock is already held, but when initiated from userspace, * it is not, so we must acquire it prior to calling send_packet, which * requires that the lock is held.
*/ staticint imon_ir_change_protocol(struct rc_dev *rc, u64 *rc_proto)
{ int retval; struct imon_context *ictx = rc->priv; struct device *dev = ictx->dev; bool unlock = false; unsignedchar ir_proto_packet[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x86 };
if (*rc_proto && !(*rc_proto & rc->allowed_protocols))
dev_warn(dev, "Looks like you're trying to use an IR protocol this device does not support\n");
if (*rc_proto & RC_PROTO_BIT_RC6_MCE) {
dev_dbg(dev, "Configuring IR receiver for MCE protocol\n");
ir_proto_packet[0] = 0x01;
*rc_proto = RC_PROTO_BIT_RC6_MCE;
} elseif (*rc_proto & RC_PROTO_BIT_IMON) {
dev_dbg(dev, "Configuring IR receiver for iMON protocol\n"); if (!pad_stabilize)
dev_dbg(dev, "PAD stabilize functionality disabled\n"); /* ir_proto_packet[0] = 0x00; // already the default */
*rc_proto = RC_PROTO_BIT_IMON;
} else {
dev_warn(dev, "Unsupported IR protocol specified, overriding to iMON IR protocol\n"); if (!pad_stabilize)
dev_dbg(dev, "PAD stabilize functionality disabled\n"); /* ir_proto_packet[0] = 0x00; // already the default */
*rc_proto = RC_PROTO_BIT_IMON;
}
/* * The directional pad behaves a bit differently, depending on whether this is * one of the older ffdc devices or a newer device. Newer devices appear to * have a higher resolution matrix for more precise mouse movement, but it * makes things overly sensitive in keyboard mode, so we do some interesting * contortions to make it less touchy. Older devices run through the same * routine with shorter timeout and a smaller threshold.
*/ staticint stabilize(int a, int b, u16 timeout, u16 threshold)
{
ktime_t ct; static ktime_t prev_time; static ktime_t hit_time; staticint x, y, prev_result, hits; int result = 0; long msec, msec_hit;
/* Look for the initial press of a button */
keycode = rc_g_keycode_from_table(ictx->rdev, scancode);
ictx->rc_toggle = 0x0;
ictx->rc_scancode = scancode;
/* Look for the release of a button */ if (keycode == KEY_RESERVED) {
release = scancode & ~0x4000;
keycode = rc_g_keycode_from_table(ictx->rdev, release); if (keycode != KEY_RESERVED)
is_release_code = true;
}
/* * On some receivers, mce keys decode to 0x8000f04xx and 0x8000f84xx * (the toggle bit flipping between alternating key presses), while * on other receivers, we see 0x8000f74xx and 0x8000ff4xx. To keep * the table trim, we always or in the bits to look up 0x8000ff4xx, * but we can't or them into all codes, as some keys are decoded in * a different way w/o the same use of the toggle bit...
*/ if (scancode & 0x80000000)
scancode = scancode | MCE_KEY_MASK | MCE_TOGGLE_BIT;
/* * The imon directional pad functions more like a touchpad. Bytes 3 & 4 * contain a position coordinate (x,y), with each component ranging * from -14 to 14. We want to down-sample this to only 4 discrete values * for up/down/left/right arrow keys. Also, when you get too close to * diagonals, it has a tendency to jump back and forth, so lets try to * ignore when they get too close.
*/ if (ictx->product != 0xffdc) { /* first, pad to 8 bytes so it conforms with everything else */
buf[5] = buf[6] = buf[7] = 0;
timeout = 500; /* in msecs */ /* (2*threshold) x (2*threshold) square */
threshold = pad_thresh ? pad_thresh : 28;
rel_x = buf[2];
rel_y = buf[3];
/* * Handle on-board decoded pad events for e.g. older VFD/iMON-Pad * device (15c2:ffdc). The remote generates various codes from * 0x68nnnnB7 to 0x6AnnnnB7, the left mouse button generates * 0x688301b7 and the right one 0x688481b7. All other keys generate * 0x2nnnnnnn. Position coordinate is encoded in buf[1] and buf[2] with * reversed endianness. Extract direction from buffer, rotate endianness, * adjust sign and feed the values into stabilize(). The resulting codes * will be 0x01008000, 0x01007F00, which match the newer devices.
*/
} else {
timeout = 10; /* in msecs */ /* (2*threshold) x (2*threshold) square */
threshold = pad_thresh ? pad_thresh : 15;
/* * figure out if these is a press or a release. We don't actually * care about repeats, as those will be auto-generated within the IR * subsystem for repeating scancodes.
*/ staticint imon_parse_press_type(struct imon_context *ictx, unsignedchar *buf, u8 ktype)
{ int press_type = 0; unsignedlong flags;
/* send touchscreen events through input subsystem if touchpad data */ if (ictx->touch && len == 8 && buf[7] == 0x86) {
imon_touch_event(ictx, buf); return;
/* look for mouse events with pad in mouse mode */
} elseif (ictx->pad_mouse) { if (imon_mouse_event(ictx, buf, len)) return;
}
/* Now for some special handling to convert pad input to arrow keys */ if (((len == 5) && (buf[0] == 0x01) && (buf[4] == 0x00)) ||
((len == 8) && (buf[0] & 0x40) &&
!(buf[1] & 0x1 || buf[1] >> 2 & 0x1))) {
len = 8;
imon_pad_to_keys(ictx, buf);
}
/* Only panel type events left to process now */
spin_lock_irqsave(&ictx->kc_lock, flags);
t = ktime_get(); /* KEY repeats from knob and panel that need to be suppressed */ if (ictx->kc == KEY_MUTE ||
ictx->dev_descr->flags & IMON_SUPPRESS_REPEATED_KEYS) { if (ictx->kc == ictx->last_keycode &&
ktime_ms_delta(t, prev_time) < ictx->idev->rep[REP_DELAY]) {
spin_unlock_irqrestore(&ictx->kc_lock, flags); return;
}
}
/* * Callback function for USB core API: receive data
*/ staticvoid usb_rx_callback_intf0(struct urb *urb)
{ struct imon_context *ictx; int intfnum = 0;
if (!urb) return;
ictx = (struct imon_context *)urb->context; if (!ictx) return;
case -ESHUTDOWN: /* transport endpoint was shut down */ break;
case0: /* * if we get a callback before we're done configuring the hardware, we * can't yet process the data, as there's nowhere to send it, but we * still need to submit a new rx URB to avoid wedging the hardware
*/ if (ictx->dev_present_intf0)
imon_incoming_packet(ictx, urb, intfnum); break;
case -ECONNRESET: case -EILSEQ: case -EPROTO: case -EPIPE:
dev_warn(ictx->dev, "imon %s: status(%d)\n",
__func__, urb->status); return;
case -ESHUTDOWN: /* transport endpoint was shut down */ break;
case0: /* * if we get a callback before we're done configuring the hardware, we * can't yet process the data, as there's nowhere to send it, but we * still need to submit a new rx URB to avoid wedging the hardware
*/ if (ictx->dev_present_intf1)
imon_incoming_packet(ictx, urb, intfnum); break;
case -ECONNRESET: case -EILSEQ: case -EPROTO: case -EPIPE:
dev_warn(ictx->dev, "imon %s: status(%d)\n",
__func__, urb->status); return;
/* * The 0x15c2:0xffdc device ID was used for umpteen different imon * devices, and all of them constantly spew interrupts, even when there * is no actual data to report. However, byte 6 of this buffer looks like * its unique across device variants, so we're trying to key off that to * figure out which display type (if any) and what IR protocol the device * actually supports. These devices have their IR protocol hard-coded into * their firmware, they can't be changed on the fly like the newer hardware.
*/ staticvoid imon_get_ffdc_type(struct imon_context *ictx)
{
u8 ffdc_cfg_byte = ictx->usb_rx_buf[6];
u8 detected_display_type = IMON_DISPLAY_TYPE_NONE;
u64 allowed_protos = RC_PROTO_BIT_IMON;
default:
dev_info(ictx->dev, "Unknown 0xffdc device, defaulting to VFD and iMON IR");
detected_display_type = IMON_DISPLAY_TYPE_VFD; /* * We don't know which one it is, allow user to set the * RC6 one from userspace if IMON wasn't correct.
*/
allowed_protos |= RC_PROTO_BIT_RC6_MCE; break;
}
printk(KERN_CONT " (id 0x%02x)\n", ffdc_cfg_byte);
rdev->priv = ictx; /* iMON PAD or MCE */
rdev->allowed_protocols = RC_PROTO_BIT_IMON | RC_PROTO_BIT_RC6_MCE;
rdev->change_protocol = imon_ir_change_protocol;
rdev->driver_name = MOD_NAME;
/* Enable front-panel buttons and/or knobs */
memcpy(ictx->usb_tx_buf, &fp_packet, sizeof(fp_packet));
ret = send_packet(ictx); /* Not fatal, but warn about it */ if (ret)
dev_info(ictx->dev, "panel buttons/knobs setup failed\n");
if (ictx->product == 0xffdc) {
imon_get_ffdc_type(ictx);
rdev->allowed_protocols = ictx->rc_proto;
}
/* * Scan the endpoint list and set: * first input endpoint = IR endpoint * first output endpoint = display endpoint
*/ for (i = 0; i < num_endpts && !(ir_ep_found && display_ep_found); ++i) {
ep = &iface_desc->endpoint[i].desc;
ep_dir = ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK;
ep_type = usb_endpoint_type(ep);
if (ifnum == 0) {
ictx->rx_endpoint_intf0 = rx_endpoint; /* * tx is used to send characters to lcd/vfd, associate RF * remotes, set IR protocol, and maybe more...
*/
ictx->tx_endpoint = tx_endpoint;
} else {
ictx->rx_endpoint_intf1 = rx_endpoint;
}
/* * If we didn't find a display endpoint, this is probably one of the * newer iMON devices that use control urb instead of interrupt
*/ if (!display_ep_found) {
tx_control = true;
display_ep_found = true;
dev_dbg(ictx->dev, "%s: device uses control endpoint, not interface OUT endpoint\n",
__func__);
}
/* * Some iMON receivers have no display. Unfortunately, it seems * that SoundGraph recycles device IDs between devices both with * and without... :\
*/ if (ictx->display_type == IMON_DISPLAY_TYPE_NONE) {
display_ep_found = false;
dev_dbg(ictx->dev, "%s: device has no display\n", __func__);
}
/* * iMON Touch devices have a VGA touchscreen, but no "display", as * that refers to e.g. /dev/lcd0 (a character device LCD or VFD).
*/ if (ictx->display_type == IMON_DISPLAY_TYPE_VGA) {
display_ep_found = false;
dev_dbg(ictx->dev, "%s: iMON Touch device found\n", __func__);
}
/* Input endpoint is mandatory */ if (!ir_ep_found)
pr_err("no valid input (IR) endpoint found\n");
ictx->tx_control = tx_control;
if (display_ep_found)
ictx->display_supported = true;
/* save drive info for later accessing the panel/knob key table */
ictx->dev_descr = (struct imon_usb_dev_descr *)id->driver_info; /* default send_packet delay is 5ms but some devices need more */
ictx->send_packet_delay = ictx->dev_descr->flags &
IMON_NEED_20MS_PKT_DELAY ? 20 : 5;
ret = -ENODEV;
iface_desc = intf->cur_altsetting; if (!imon_find_endpoints(ictx, iface_desc)) { goto find_endpoint_failed;
}
staticvoid imon_init_display(struct imon_context *ictx, struct usb_interface *intf)
{ int ret;
dev_dbg(ictx->dev, "Registering iMON display with sysfs\n");
/* set up sysfs entry for built-in clock */
ret = sysfs_create_group(&intf->dev.kobj, &imon_display_attr_group); if (ret)
dev_err(ictx->dev, "Could not create display sysfs entries(%d)",
ret);
if (ictx->display_type == IMON_DISPLAY_TYPE_LCD)
ret = usb_register_dev(intf, &imon_lcd_class); else
ret = usb_register_dev(intf, &imon_vfd_class); if (ret) /* Not a fatal error, so ignore */
dev_info(ictx->dev, "could not get a minor number for display\n");
}
/* * Callback function for USB core API: Probe
*/ staticint imon_probe(struct usb_interface *interface, conststruct usb_device_id *id)
{ struct usb_device *usbdev = NULL; struct usb_host_interface *iface_desc = NULL; struct usb_interface *first_if; struct device *dev = &interface->dev; int ifnum, sysfs_err; int ret = 0; struct imon_context *ictx = NULL;
u16 vendor, product;
first_if = usb_ifnum_to_if(usbdev, 0); if (!first_if) {
ret = -ENODEV; goto fail;
}
if (first_if->dev.driver != interface->dev.driver) {
dev_err(&interface->dev, "inconsistent driver matching\n");
ret = -EINVAL; goto fail;
}
if (ifnum == 0) {
ictx = imon_init_intf0(interface, id); if (!ictx) {
pr_err("failed to initialize context!\n");
ret = -ENODEV; goto fail;
}
refcount_set(&ictx->users, 1);
} else { /* this is the secondary interface on the device */ struct imon_context *first_if_ctx = usb_get_intfdata(first_if);
/* fail early if first intf failed to register */ if (!first_if_ctx) {
ret = -ENODEV; goto fail;
}
ictx = imon_init_intf1(interface, first_if_ctx); if (!ictx) {
pr_err("failed to attach to context!\n");
ret = -ENODEV; goto fail;
}
refcount_inc(&ictx->users);
}
usb_set_intfdata(interface, ictx);
if (ifnum == 0) { if (product == 0xffdc && ictx->rf_device) {
sysfs_err = sysfs_create_group(&interface->dev.kobj,
&imon_rf_attr_group); if (sysfs_err)
pr_err("Could not create RF sysfs entries(%d)\n",
sysfs_err);
}
if (ictx->display_supported)
imon_init_display(ictx, interface);
}
dev = ictx->dev;
ifnum = interface->cur_altsetting->desc.bInterfaceNumber;
/* * sysfs_remove_group is safe to call even if sysfs_create_group * hasn't been called
*/
sysfs_remove_group(&interface->dev.kobj, &imon_display_attr_group);
sysfs_remove_group(&interface->dev.kobj, &imon_rf_attr_group);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.