diff options
Diffstat (limited to 'drivers/media/rc')
-rw-r--r-- | drivers/media/rc/Kconfig | 8 | ||||
-rw-r--r-- | drivers/media/rc/ati_remote.c | 3 | ||||
-rw-r--r-- | drivers/media/rc/iguanair.c | 1 | ||||
-rw-r--r-- | drivers/media/rc/img-ir/img-ir-hw.c | 4 | ||||
-rw-r--r-- | drivers/media/rc/imon.c | 2 | ||||
-rw-r--r-- | drivers/media/rc/ir-lirc-codec.c | 37 | ||||
-rw-r--r-- | drivers/media/rc/ir-spi.c | 2 | ||||
-rw-r--r-- | drivers/media/rc/lirc_dev.c | 254 | ||||
-rw-r--r-- | drivers/media/rc/mceusb.c | 153 | ||||
-rw-r--r-- | drivers/media/rc/meson-ir.c | 89 | ||||
-rw-r--r-- | drivers/media/rc/rc-main.c | 20 | ||||
-rw-r--r-- | drivers/media/rc/sir_ir.c | 96 |
12 files changed, 274 insertions, 395 deletions
diff --git a/drivers/media/rc/Kconfig b/drivers/media/rc/Kconfig index e422f3d56f76..5e83b76495f7 100644 --- a/drivers/media/rc/Kconfig +++ b/drivers/media/rc/Kconfig @@ -169,11 +169,11 @@ config IR_HIX5HD2 tristate "Hisilicon hix5hd2 IR remote control" depends on RC_CORE help - Say Y here if you want to use hisilicon hix5hd2 remote control. - To compile this driver as a module, choose M here: the module will be - called ir-hix5hd2. + Say Y here if you want to use hisilicon hix5hd2 remote control. + To compile this driver as a module, choose M here: the module will be + called ir-hix5hd2. - If you're not sure, select N here + If you're not sure, select N here config IR_IMON tristate "SoundGraph iMON Receiver and Display" diff --git a/drivers/media/rc/ati_remote.c b/drivers/media/rc/ati_remote.c index 9cf3e69de16a..a4c6ad4f67c1 100644 --- a/drivers/media/rc/ati_remote.c +++ b/drivers/media/rc/ati_remote.c @@ -904,9 +904,6 @@ static int ati_remote_probe(struct usb_interface *interface, if (err) goto exit_kill_urbs; - /* use our delay for rc_dev */ - ati_remote->rdev->input_dev->rep[REP_DELAY] = repeat_delay; - /* Set up and register mouse input device */ if (mouse) { input_dev = input_allocate_device(); diff --git a/drivers/media/rc/iguanair.c b/drivers/media/rc/iguanair.c index ccf24fd7ec1b..8711a7ff55cc 100644 --- a/drivers/media/rc/iguanair.c +++ b/drivers/media/rc/iguanair.c @@ -113,6 +113,7 @@ static void process_ir_data(struct iguanair *ir, unsigned len) break; case CMD_TX_OVERFLOW: ir->tx_overflow = true; + /* fall through */ case CMD_RECEIVER_OFF: case CMD_RECEIVER_ON: case CMD_SEND: diff --git a/drivers/media/rc/img-ir/img-ir-hw.c b/drivers/media/rc/img-ir/img-ir-hw.c index 431d33b36fb0..8d1439622533 100644 --- a/drivers/media/rc/img-ir/img-ir-hw.c +++ b/drivers/media/rc/img-ir/img-ir-hw.c @@ -702,10 +702,6 @@ static void img_ir_set_protocol(struct img_ir_priv *priv, u64 proto) { struct rc_dev *rdev = priv->hw.rdev; - spin_lock_irq(&rdev->rc_map.lock); - rdev->rc_map.rc_type = __ffs64(proto); - spin_unlock_irq(&rdev->rc_map.lock); - mutex_lock(&rdev->lock); rdev->enabled_protocols = proto; rdev->allowed_wakeup_protocols = proto; diff --git a/drivers/media/rc/imon.c b/drivers/media/rc/imon.c index 3489010601b5..bd76534a2749 100644 --- a/drivers/media/rc/imon.c +++ b/drivers/media/rc/imon.c @@ -1722,7 +1722,7 @@ static void imon_incoming_scancode(struct imon_context *ictx, if (kc == KEY_KEYBOARD && !ictx->release_code) { ictx->last_keycode = kc; if (!nomouse) { - ictx->pad_mouse = ~(ictx->pad_mouse) & 0x1; + ictx->pad_mouse = !ictx->pad_mouse; dev_dbg(dev, "toggling to %s mode\n", ictx->pad_mouse ? "mouse" : "keyboard"); spin_unlock_irqrestore(&ictx->kc_lock, flags); diff --git a/drivers/media/rc/ir-lirc-codec.c b/drivers/media/rc/ir-lirc-codec.c index de85f1d7ce43..a30af91710fe 100644 --- a/drivers/media/rc/ir-lirc-codec.c +++ b/drivers/media/rc/ir-lirc-codec.c @@ -327,16 +327,6 @@ static long ir_lirc_ioctl(struct file *filep, unsigned int cmd, return ret; } -static int ir_lirc_open(void *data) -{ - return 0; -} - -static void ir_lirc_close(void *data) -{ - return; -} - static const struct file_operations lirc_fops = { .owner = THIS_MODULE, .write = ir_lirc_transmit_ir, @@ -354,7 +344,6 @@ static const struct file_operations lirc_fops = { static int ir_lirc_register(struct rc_dev *dev) { struct lirc_driver *drv; - struct lirc_buffer *rbuf; int rc = -ENOMEM; unsigned long features = 0; @@ -362,19 +351,12 @@ static int ir_lirc_register(struct rc_dev *dev) if (!drv) return rc; - rbuf = kzalloc(sizeof(struct lirc_buffer), GFP_KERNEL); - if (!rbuf) - goto rbuf_alloc_failed; - - rc = lirc_buffer_init(rbuf, sizeof(int), LIRCBUF_SIZE); - if (rc) - goto rbuf_init_failed; - if (dev->driver_type != RC_DRIVER_IR_RAW_TX) { features |= LIRC_CAN_REC_MODE2; if (dev->rx_resolution) features |= LIRC_CAN_GET_REC_RESOLUTION; } + if (dev->tx_ir) { features |= LIRC_CAN_SEND_PULSE; if (dev->s_tx_mask) @@ -403,10 +385,10 @@ static int ir_lirc_register(struct rc_dev *dev) drv->minor = -1; drv->features = features; drv->data = &dev->raw->lirc; - drv->rbuf = rbuf; - drv->set_use_inc = &ir_lirc_open; - drv->set_use_dec = &ir_lirc_close; + drv->rbuf = NULL; drv->code_length = sizeof(struct ir_raw_event) * 8; + drv->chunk_size = sizeof(int); + drv->buffer_size = LIRCBUF_SIZE; drv->fops = &lirc_fops; drv->dev = &dev->dev; drv->rdev = dev; @@ -415,19 +397,15 @@ static int ir_lirc_register(struct rc_dev *dev) drv->minor = lirc_register_driver(drv); if (drv->minor < 0) { rc = -ENODEV; - goto lirc_register_failed; + goto out; } dev->raw->lirc.drv = drv; dev->raw->lirc.dev = dev; return 0; -lirc_register_failed: -rbuf_init_failed: - kfree(rbuf); -rbuf_alloc_failed: +out: kfree(drv); - return rc; } @@ -436,9 +414,8 @@ static int ir_lirc_unregister(struct rc_dev *dev) struct lirc_codec *lirc = &dev->raw->lirc; lirc_unregister_driver(lirc->drv->minor); - lirc_buffer_free(lirc->drv->rbuf); - kfree(lirc->drv->rbuf); kfree(lirc->drv); + lirc->drv = NULL; return 0; } diff --git a/drivers/media/rc/ir-spi.c b/drivers/media/rc/ir-spi.c index c8863f36686a..4ca43383a8e8 100644 --- a/drivers/media/rc/ir-spi.c +++ b/drivers/media/rc/ir-spi.c @@ -58,7 +58,7 @@ static int ir_spi_tx(struct rc_dev *dev, /* convert the pulse/space signal to raw binary signal */ for (i = 0; i < count; i++) { int j; - u16 val = ((i + 1) % 2) ? idata->pulse : idata->space; + u16 val; if (len + buffer[i] >= IR_SPI_MAX_BUFSIZE) return -EINVAL; diff --git a/drivers/media/rc/lirc_dev.c b/drivers/media/rc/lirc_dev.c index 8d60c9f00df9..db1e7b70c998 100644 --- a/drivers/media/rc/lirc_dev.c +++ b/drivers/media/rc/lirc_dev.c @@ -18,18 +18,10 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/module.h> -#include <linux/kernel.h> #include <linux/sched/signal.h> -#include <linux/errno.h> #include <linux/ioctl.h> -#include <linux/fs.h> #include <linux/poll.h> -#include <linux/completion.h> #include <linux/mutex.h> -#include <linux/wait.h> -#include <linux/unistd.h> -#include <linux/kthread.h> -#include <linux/bitops.h> #include <linux/device.h> #include <linux/cdev.h> @@ -37,9 +29,6 @@ #include <media/lirc.h> #include <media/lirc_dev.h> -static bool debug; - -#define IRCTL_DEV_NAME "BaseRemoteCtl" #define NOPLUG -1 #define LOGHEAD "lirc_dev (%s[%d]): " @@ -52,13 +41,11 @@ struct irctl { struct mutex irctl_lock; struct lirc_buffer *buf; + bool buf_internal; unsigned int chunk_size; struct device dev; struct cdev cdev; - - struct task_struct *task; - long jiffies_to_wait; }; static DEFINE_MUTEX(lirc_dev_lock); @@ -68,22 +55,11 @@ static struct irctl *irctls[MAX_IRCTL_DEVICES]; /* Only used for sysfs but defined to void otherwise */ static struct class *lirc_class; -/* helper function - * initializes the irctl structure - */ -static void lirc_irctl_init(struct irctl *ir) -{ - mutex_init(&ir->irctl_lock); - ir->d.minor = NOPLUG; -} - static void lirc_release(struct device *ld) { struct irctl *ir = container_of(ld, struct irctl, dev); - put_device(ir->dev.parent); - - if (ir->buf != ir->d.rbuf) { + if (ir->buf_internal) { lirc_buffer_free(ir->buf); kfree(ir->buf); } @@ -94,93 +70,6 @@ static void lirc_release(struct device *ld) kfree(ir); } -/* helper function - * reads key codes from driver and puts them into buffer - * returns 0 on success - */ -static int lirc_add_to_buf(struct irctl *ir) -{ - int res; - int got_data = -1; - - if (!ir->d.add_to_buf) - return 0; - - /* - * service the device as long as it is returning - * data and we have space - */ - do { - got_data++; - res = ir->d.add_to_buf(ir->d.data, ir->buf); - } while (!res); - - if (res == -ENODEV) - kthread_stop(ir->task); - - return got_data ? 0 : res; -} - -/* main function of the polling thread - */ -static int lirc_thread(void *irctl) -{ - struct irctl *ir = irctl; - - do { - if (ir->open) { - if (ir->jiffies_to_wait) { - set_current_state(TASK_INTERRUPTIBLE); - schedule_timeout(ir->jiffies_to_wait); - } - if (kthread_should_stop()) - break; - if (!lirc_add_to_buf(ir)) - wake_up_interruptible(&ir->buf->wait_poll); - } else { - set_current_state(TASK_INTERRUPTIBLE); - schedule(); - } - } while (!kthread_should_stop()); - - return 0; -} - - -static const struct file_operations lirc_dev_fops = { - .owner = THIS_MODULE, - .read = lirc_dev_fop_read, - .write = lirc_dev_fop_write, - .poll = lirc_dev_fop_poll, - .unlocked_ioctl = lirc_dev_fop_ioctl, - .open = lirc_dev_fop_open, - .release = lirc_dev_fop_close, - .llseek = noop_llseek, -}; - -static int lirc_cdev_add(struct irctl *ir) -{ - struct lirc_driver *d = &ir->d; - struct cdev *cdev; - int retval; - - cdev = &ir->cdev; - - if (d->fops) { - cdev_init(cdev, d->fops); - cdev->owner = d->owner; - } else { - cdev_init(cdev, &lirc_dev_fops); - cdev->owner = THIS_MODULE; - } - retval = kobject_set_name(&cdev->kobj, "lirc%d", d->minor); - if (retval) - return retval; - - cdev->kobj.parent = &ir->dev.kobj; - return cdev_add(cdev, ir->dev.devt, 1); -} - static int lirc_allocate_buffer(struct irctl *ir) { int err = 0; @@ -189,8 +78,6 @@ static int lirc_allocate_buffer(struct irctl *ir) unsigned int buffer_size; struct lirc_driver *d = &ir->d; - mutex_lock(&lirc_dev_lock); - bytes_in_key = BITS_TO_LONGS(d->code_length) + (d->code_length % 8 ? 1 : 0); buffer_size = d->buffer_size ? d->buffer_size : BUFLEN / bytes_in_key; @@ -198,6 +85,7 @@ static int lirc_allocate_buffer(struct irctl *ir) if (d->rbuf) { ir->buf = d->rbuf; + ir->buf_internal = false; } else { ir->buf = kmalloc(sizeof(struct lirc_buffer), GFP_KERNEL); if (!ir->buf) { @@ -208,18 +96,20 @@ static int lirc_allocate_buffer(struct irctl *ir) err = lirc_buffer_init(ir->buf, chunk_size, buffer_size); if (err) { kfree(ir->buf); + ir->buf = NULL; goto out; } + + ir->buf_internal = true; + d->rbuf = ir->buf; } ir->chunk_size = ir->buf->chunk_size; out: - mutex_unlock(&lirc_dev_lock); - return err; } -static int lirc_allocate_driver(struct lirc_driver *d) +int lirc_register_driver(struct lirc_driver *d) { struct irctl *ir; int minor; @@ -235,6 +125,11 @@ static int lirc_allocate_driver(struct lirc_driver *d) return -EINVAL; } + if (!d->fops) { + pr_err("fops pointer not filled in!\n"); + return -EINVAL; + } + if (d->minor >= MAX_IRCTL_DEVICES) { dev_err(d->dev, "minor must be between 0 and %d!\n", MAX_IRCTL_DEVICES - 1); @@ -247,18 +142,8 @@ static int lirc_allocate_driver(struct lirc_driver *d) return -EBADRQC; } - if (d->sample_rate) { - if (2 > d->sample_rate || HZ < d->sample_rate) { - dev_err(d->dev, "invalid %d sample rate\n", - d->sample_rate); - return -EBADRQC; - } - if (!d->add_to_buf) { - dev_err(d->dev, "add_to_buf not set\n"); - return -EBADRQC; - } - } else if (!d->rbuf && !(d->fops && d->fops->read && - d->fops->poll && d->fops->unlocked_ioctl)) { + if (!d->rbuf && !(d->fops && d->fops->read && + d->fops->poll && d->fops->unlocked_ioctl)) { dev_err(d->dev, "undefined read, poll, ioctl\n"); return -EBADRQC; } @@ -288,7 +173,8 @@ static int lirc_allocate_driver(struct lirc_driver *d) err = -ENOMEM; goto out_lock; } - lirc_irctl_init(ir); + + mutex_init(&ir->irctl_lock); irctls[minor] = ir; d->minor = minor; @@ -300,32 +186,29 @@ static int lirc_allocate_driver(struct lirc_driver *d) ir->d = *d; + if (LIRC_CAN_REC(d->features)) { + err = lirc_allocate_buffer(irctls[minor]); + if (err) { + kfree(ir); + goto out_lock; + } + d->rbuf = ir->buf; + } + + device_initialize(&ir->dev); ir->dev.devt = MKDEV(MAJOR(lirc_base_dev), ir->d.minor); ir->dev.class = lirc_class; ir->dev.parent = d->dev; ir->dev.release = lirc_release; dev_set_name(&ir->dev, "lirc%d", ir->d.minor); - device_initialize(&ir->dev); - if (d->sample_rate) { - ir->jiffies_to_wait = HZ / d->sample_rate; + cdev_init(&ir->cdev, d->fops); + ir->cdev.owner = ir->d.owner; + ir->cdev.kobj.parent = &ir->dev.kobj; - /* try to fire up polling thread */ - ir->task = kthread_run(lirc_thread, (void *)ir, "lirc_dev"); - if (IS_ERR(ir->task)) { - dev_err(d->dev, "cannot run thread for minor = %d\n", - d->minor); - err = -ECHILD; - goto out_sysfs; - } - } else { - /* it means - wait for external event in task queue */ - ir->jiffies_to_wait = 0; - } - - err = lirc_cdev_add(ir); + err = cdev_add(&ir->cdev, ir->dev.devt, 1); if (err) - goto out_sysfs; + goto out_free_dev; ir->attached = 1; @@ -335,37 +218,20 @@ static int lirc_allocate_driver(struct lirc_driver *d) mutex_unlock(&lirc_dev_lock); - get_device(ir->dev.parent); - dev_info(ir->d.dev, "lirc_dev: driver %s registered at minor = %d\n", ir->d.name, ir->d.minor); + return minor; + out_cdev: cdev_del(&ir->cdev); -out_sysfs: +out_free_dev: put_device(&ir->dev); out_lock: mutex_unlock(&lirc_dev_lock); return err; } - -int lirc_register_driver(struct lirc_driver *d) -{ - int minor, err = 0; - - minor = lirc_allocate_driver(d); - if (minor < 0) - return minor; - - if (LIRC_CAN_REC(d->features)) { - err = lirc_allocate_buffer(irctls[minor]); - if (err) - lirc_unregister_driver(minor); - } - - return err ? err : minor; -} EXPORT_SYMBOL(lirc_register_driver); int lirc_unregister_driver(int minor) @@ -393,10 +259,6 @@ int lirc_unregister_driver(int minor) return -ENOENT; } - /* end up polling thread */ - if (ir->task) - kthread_stop(ir->task); - dev_dbg(ir->d.dev, "lirc_dev: driver %s unregistered from minor = %d\n", ir->d.name, ir->d.minor); @@ -407,12 +269,6 @@ int lirc_unregister_driver(int minor) wake_up_interruptible(&ir->buf->wait_poll); } - mutex_lock(&ir->irctl_lock); - - if (ir->d.set_use_dec) - ir->d.set_use_dec(ir->d.data); - - mutex_unlock(&ir->irctl_lock); mutex_unlock(&lirc_dev_lock); device_del(&ir->dev); @@ -462,17 +318,10 @@ int lirc_dev_fop_open(struct inode *inode, struct file *file) goto error; } + if (ir->buf) + lirc_buffer_clear(ir->buf); + ir->open++; - if (ir->d.set_use_inc) - retval = ir->d.set_use_inc(ir->d.data); - if (retval) { - ir->open--; - } else { - if (ir->buf) - lirc_buffer_clear(ir->buf); - if (ir->task) - wake_up_process(ir->task); - } error: nonseekable_open(inode, file); @@ -497,8 +346,6 @@ int lirc_dev_fop_close(struct inode *inode, struct file *file) rc_close(ir->d.rdev); ir->open--; - if (ir->d.set_use_dec) - ir->d.set_use_dec(ir->d.data); if (!ret) mutex_unlock(&lirc_dev_lock); @@ -517,7 +364,7 @@ unsigned int lirc_dev_fop_poll(struct file *file, poll_table *wait) } if (!ir->attached) - return POLLERR; + return POLLHUP | POLLERR; if (ir->buf) { poll_wait(file, &ir->buf->wait_poll, wait); @@ -729,24 +576,6 @@ void *lirc_get_pdata(struct file *file) EXPORT_SYMBOL(lirc_get_pdata); -ssize_t lirc_dev_fop_write(struct file *file, const char __user *buffer, - size_t length, loff_t *ppos) -{ - struct irctl *ir = irctls[iminor(file_inode(file))]; - - if (!ir) { - pr_err("called with invalid irctl\n"); - return -ENODEV; - } - - if (!ir->attached) - return -ENODEV; - - return -EINVAL; -} -EXPORT_SYMBOL(lirc_dev_fop_write); - - static int __init lirc_dev_init(void) { int retval; @@ -758,7 +587,7 @@ static int __init lirc_dev_init(void) } retval = alloc_chrdev_region(&lirc_base_dev, 0, MAX_IRCTL_DEVICES, - IRCTL_DEV_NAME); + "BaseRemoteCtl"); if (retval) { class_destroy(lirc_class); pr_err("alloc_chrdev_region failed\n"); @@ -784,6 +613,3 @@ module_exit(lirc_dev_exit); MODULE_DESCRIPTION("LIRC base driver module"); MODULE_AUTHOR("Artur Lipowski"); MODULE_LICENSE("GPL"); - -module_param(debug, bool, S_IRUGO | S_IWUSR); -MODULE_PARM_DESC(debug, "Enable debugging messages"); diff --git a/drivers/media/rc/mceusb.c b/drivers/media/rc/mceusb.c index 93b16fe3ab38..4530237cbb67 100644 --- a/drivers/media/rc/mceusb.c +++ b/drivers/media/rc/mceusb.c @@ -36,18 +36,18 @@ #include <linux/device.h> #include <linux/module.h> #include <linux/slab.h> +#include <linux/workqueue.h> #include <linux/usb.h> #include <linux/usb/input.h> #include <linux/pm_wakeup.h> #include <media/rc-core.h> -#define DRIVER_VERSION "1.92" +#define DRIVER_VERSION "1.93" #define DRIVER_AUTHOR "Jarod Wilson <jarod@redhat.com>" #define DRIVER_DESC "Windows Media Center Ed. eHome Infrared Transceiver " \ "device driver" #define DRIVER_NAME "mceusb" -#define USB_BUFLEN 32 /* USB reception buffer length */ #define USB_CTRL_MSG_SZ 2 /* Size of usb ctrl msg on gen1 hw */ #define MCE_G1_INIT_MSGS 40 /* Init messages on gen1 hw to throw out */ @@ -417,7 +417,9 @@ struct mceusb_dev { /* usb */ struct usb_device *usbdev; struct urb *urb_in; + unsigned int pipe_in; struct usb_endpoint_descriptor *usb_ep_out; + unsigned int pipe_out; /* buffers and dma */ unsigned char *buf_in; @@ -454,6 +456,16 @@ struct mceusb_dev { u8 num_rxports; /* number of receive sensors */ u8 txports_cabled; /* bitmask of transmitters with cable */ u8 rxports_active; /* bitmask of active receive sensors */ + + /* + * support for async error handler mceusb_deferred_kevent() + * where usb_clear_halt(), usb_reset_configuration(), + * usb_reset_device(), etc. must be done in process context + */ + struct work_struct kevent; + unsigned long kevent_flags; +# define EVENT_TX_HALT 0 +# define EVENT_RX_HALT 1 }; /* MCE Device Command Strings, generally a port and command pair */ @@ -527,7 +539,7 @@ static int mceusb_cmd_datasize(u8 cmd, u8 subcmd) } static void mceusb_dev_printdata(struct mceusb_dev *ir, char *buf, - int offset, int len, bool out) + int buf_len, int offset, int len, bool out) { #if defined(DEBUG) || defined(CONFIG_DYNAMIC_DEBUG) char *inout; @@ -544,7 +556,8 @@ static void mceusb_dev_printdata(struct mceusb_dev *ir, char *buf, return; dev_dbg(dev, "%cx data: %*ph (length=%d)", - (out ? 't' : 'r'), min(len, USB_BUFLEN), buf, len); + (out ? 't' : 'r'), + min(len, buf_len - offset), buf + offset, len); inout = out ? "Request" : "Got"; @@ -686,6 +699,21 @@ static void mceusb_dev_printdata(struct mceusb_dev *ir, char *buf, #endif } +/* + * Schedule work that can't be done in interrupt handlers + * (mceusb_dev_recv() and mce_async_callback()) nor tasklets. + * Invokes mceusb_deferred_kevent() for recovering from + * error events specified by the kevent bit field. + */ +static void mceusb_defer_kevent(struct mceusb_dev *ir, int kevent) +{ + set_bit(kevent, &ir->kevent_flags); + if (!schedule_work(&ir->kevent)) + dev_err(ir->dev, "kevent %d may have been dropped", kevent); + else + dev_dbg(ir->dev, "kevent %d scheduled", kevent); +} + static void mce_async_callback(struct urb *urb) { struct mceusb_dev *ir; @@ -701,7 +729,8 @@ static void mce_async_callback(struct urb *urb) case 0: len = urb->actual_length; - mceusb_dev_printdata(ir, urb->transfer_buffer, 0, len, true); + mceusb_dev_printdata(ir, urb->transfer_buffer, len, + 0, len, true); break; case -ECONNRESET: @@ -711,6 +740,11 @@ static void mce_async_callback(struct urb *urb) break; case -EPIPE: + dev_err(ir->dev, "Error: request urb status = %d (TX HALT)", + urb->status); + mceusb_defer_kevent(ir, EVENT_TX_HALT); + break; + default: dev_err(ir->dev, "Error: request urb status = %d", urb->status); break; @@ -721,18 +755,18 @@ static void mce_async_callback(struct urb *urb) usb_free_urb(urb); } -/* request incoming or send outgoing usb packet - used to initialize remote */ +/* request outgoing (send) usb packet - used to initialize remote */ static void mce_request_packet(struct mceusb_dev *ir, unsigned char *data, int size) { - int res, pipe; + int res; struct urb *async_urb; struct device *dev = ir->dev; unsigned char *async_buf; async_urb = usb_alloc_urb(0, GFP_KERNEL); if (unlikely(!async_urb)) { - dev_err(dev, "Error, couldn't allocate urb!\n"); + dev_err(dev, "Error, couldn't allocate urb!"); return; } @@ -743,32 +777,27 @@ static void mce_request_packet(struct mceusb_dev *ir, unsigned char *data, } /* outbound data */ - if (usb_endpoint_xfer_int(ir->usb_ep_out)) { - pipe = usb_sndintpipe(ir->usbdev, - ir->usb_ep_out->bEndpointAddress); - usb_fill_int_urb(async_urb, ir->usbdev, pipe, async_buf, - size, mce_async_callback, ir, + if (usb_endpoint_xfer_int(ir->usb_ep_out)) + usb_fill_int_urb(async_urb, ir->usbdev, ir->pipe_out, + async_buf, size, mce_async_callback, ir, ir->usb_ep_out->bInterval); - } else { - pipe = usb_sndbulkpipe(ir->usbdev, - ir->usb_ep_out->bEndpointAddress); - usb_fill_bulk_urb(async_urb, ir->usbdev, pipe, - async_buf, size, mce_async_callback, - ir); - } + else + usb_fill_bulk_urb(async_urb, ir->usbdev, ir->pipe_out, + async_buf, size, mce_async_callback, ir); + memcpy(async_buf, data, size); - dev_dbg(dev, "receive request called (size=%#x)", size); + dev_dbg(dev, "send request called (size=%#x)", size); async_urb->transfer_buffer_length = size; async_urb->dev = ir->usbdev; res = usb_submit_urb(async_urb, GFP_ATOMIC); if (res) { - dev_err(dev, "receive request FAILED! (res=%d)", res); + dev_err(dev, "send request FAILED! (res=%d)", res); return; } - dev_dbg(dev, "receive request complete (res=%d)", res); + dev_dbg(dev, "send request complete (res=%d)", res); } static void mce_async_out(struct mceusb_dev *ir, unsigned char *data, int size) @@ -974,7 +1003,7 @@ static void mceusb_process_ir_data(struct mceusb_dev *ir, int buf_len) switch (ir->parser_state) { case SUBCMD: ir->rem = mceusb_cmd_datasize(ir->cmd, ir->buf_in[i]); - mceusb_dev_printdata(ir, ir->buf_in, i - 1, + mceusb_dev_printdata(ir, ir->buf_in, buf_len, i - 1, ir->rem + 2, false); mceusb_handle_command(ir, i); ir->parser_state = CMD_DATA; @@ -986,7 +1015,7 @@ static void mceusb_process_ir_data(struct mceusb_dev *ir, int buf_len) rawir.duration = (ir->buf_in[i] & MCE_PULSE_MASK) * US_TO_NS(MCE_TIME_UNIT); - dev_dbg(ir->dev, "Storing %s with duration %d", + dev_dbg(ir->dev, "Storing %s with duration %u", rawir.pulse ? "pulse" : "space", rawir.duration); @@ -1007,7 +1036,7 @@ static void mceusb_process_ir_data(struct mceusb_dev *ir, int buf_len) continue; } ir->rem = (ir->cmd & MCE_PACKET_LENGTH_MASK); - mceusb_dev_printdata(ir, ir->buf_in, + mceusb_dev_printdata(ir, ir->buf_in, buf_len, i, ir->rem + 1, false); if (ir->rem) ir->parser_state = PARSE_IRDATA; @@ -1052,6 +1081,11 @@ static void mceusb_dev_recv(struct urb *urb) return; case -EPIPE: + dev_err(ir->dev, "Error: urb status = %d (RX HALT)", + urb->status); + mceusb_defer_kevent(ir, EVENT_RX_HALT); + return; + default: dev_err(ir->dev, "Error: urb status = %d", urb->status); break; @@ -1170,6 +1204,45 @@ static void mceusb_flash_led(struct mceusb_dev *ir) mce_async_out(ir, FLASH_LED, sizeof(FLASH_LED)); } +/* + * Workqueue function + * for resetting or recovering device after occurrence of error events + * specified in ir->kevent bit field. + * Function runs (via schedule_work()) in non-interrupt context, for + * calls here (such as usb_clear_halt()) requiring non-interrupt context. + */ +static void mceusb_deferred_kevent(struct work_struct *work) +{ + struct mceusb_dev *ir = + container_of(work, struct mceusb_dev, kevent); + int status; + + if (test_bit(EVENT_RX_HALT, &ir->kevent_flags)) { + usb_unlink_urb(ir->urb_in); + status = usb_clear_halt(ir->usbdev, ir->pipe_in); + if (status < 0) { + dev_err(ir->dev, "rx clear halt error %d", + status); + } + clear_bit(EVENT_RX_HALT, &ir->kevent_flags); + if (status == 0) { + status = usb_submit_urb(ir->urb_in, GFP_KERNEL); + if (status < 0) { + dev_err(ir->dev, + "rx unhalt submit urb error %d", + status); + } + } + } + + if (test_bit(EVENT_TX_HALT, &ir->kevent_flags)) { + status = usb_clear_halt(ir->usbdev, ir->pipe_out); + if (status < 0) + dev_err(ir->dev, "tx clear halt error %d", status); + clear_bit(EVENT_TX_HALT, &ir->kevent_flags); + } +} + static struct rc_dev *mceusb_init_rc_dev(struct mceusb_dev *ir) { struct usb_device *udev = ir->usbdev; @@ -1303,6 +1376,7 @@ static int mceusb_dev_probe(struct usb_interface *intf, if (!ir) goto mem_alloc_fail; + ir->pipe_in = pipe; ir->buf_in = usb_alloc_coherent(dev, maxp, GFP_ATOMIC, &ir->dma_in); if (!ir->buf_in) goto buf_in_alloc_fail; @@ -1321,6 +1395,12 @@ static int mceusb_dev_probe(struct usb_interface *intf, /* Saving usb interface data for use by the transmitter routine */ ir->usb_ep_out = ep_out; + if (usb_endpoint_xfer_int(ep_out)) + ir->pipe_out = usb_sndintpipe(ir->usbdev, + ep_out->bEndpointAddress); + else + ir->pipe_out = usb_sndbulkpipe(ir->usbdev, + ep_out->bEndpointAddress); if (dev->descriptor.iManufacturer && usb_string(dev, dev->descriptor.iManufacturer, @@ -1332,21 +1412,32 @@ static int mceusb_dev_probe(struct usb_interface *intf, snprintf(name + strlen(name), sizeof(name) - strlen(name), " %s", buf); + /* + * Initialize async USB error handler before registering + * or activating any mceusb RX and TX functions + */ + INIT_WORK(&ir->kevent, mceusb_deferred_kevent); + ir->rc = mceusb_init_rc_dev(ir); if (!ir->rc) goto rc_dev_fail; /* wire up inbound data handler */ - usb_fill_int_urb(ir->urb_in, dev, pipe, ir->buf_in, maxp, - mceusb_dev_recv, ir, ep_in->bInterval); + if (usb_endpoint_xfer_int(ep_in)) + usb_fill_int_urb(ir->urb_in, dev, pipe, ir->buf_in, maxp, + mceusb_dev_recv, ir, ep_in->bInterval); + else + usb_fill_bulk_urb(ir->urb_in, dev, pipe, ir->buf_in, maxp, + mceusb_dev_recv, ir); + ir->urb_in->transfer_dma = ir->dma_in; ir->urb_in->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; /* flush buffers on the device */ - dev_dbg(&intf->dev, "Flushing receive buffers\n"); + dev_dbg(&intf->dev, "Flushing receive buffers"); res = usb_submit_urb(ir->urb_in, GFP_KERNEL); if (res) - dev_err(&intf->dev, "failed to flush buffers: %d\n", res); + dev_err(&intf->dev, "failed to flush buffers: %d", res); /* figure out which firmware/emulator version this hardware has */ mceusb_get_emulator_version(ir); @@ -1380,6 +1471,7 @@ static int mceusb_dev_probe(struct usb_interface *intf, /* Error-handling path */ rc_dev_fail: + cancel_work_sync(&ir->kevent); usb_put_dev(ir->usbdev); usb_kill_urb(ir->urb_in); usb_free_urb(ir->urb_in); @@ -1405,6 +1497,7 @@ static void mceusb_dev_disconnect(struct usb_interface *intf) return; ir->usbdev = NULL; + cancel_work_sync(&ir->kevent); rc_unregister_device(ir->rc); usb_kill_urb(ir->urb_in); usb_free_urb(ir->urb_in); diff --git a/drivers/media/rc/meson-ir.c b/drivers/media/rc/meson-ir.c index 5576dbd6b1a4..65566d569cb1 100644 --- a/drivers/media/rc/meson-ir.c +++ b/drivers/media/rc/meson-ir.c @@ -19,6 +19,7 @@ #include <linux/of_platform.h> #include <linux/platform_device.h> #include <linux/spinlock.h> +#include <linux/bitfield.h> #include <media/rc-core.h> @@ -36,7 +37,7 @@ /* only available on Meson 8b and newer */ #define IR_DEC_REG2 0x20 -#define REG0_RATE_MASK (BIT(11) - 1) +#define REG0_RATE_MASK GENMASK(11, 0) #define DECODE_MODE_NEC 0x0 #define DECODE_MODE_RAW 0x2 @@ -49,14 +50,13 @@ #define REG2_MODE_MASK GENMASK(3, 0) #define REG2_MODE_SHIFT 0 -#define REG1_TIME_IV_SHIFT 16 -#define REG1_TIME_IV_MASK ((BIT(13) - 1) << REG1_TIME_IV_SHIFT) +#define REG1_TIME_IV_MASK GENMASK(28, 16) -#define REG1_IRQSEL_MASK (BIT(2) | BIT(3)) -#define REG1_IRQSEL_NEC_MODE (0 << 2) -#define REG1_IRQSEL_RISE_FALL (1 << 2) -#define REG1_IRQSEL_FALL (2 << 2) -#define REG1_IRQSEL_RISE (3 << 2) +#define REG1_IRQSEL_MASK GENMASK(3, 2) +#define REG1_IRQSEL_NEC_MODE 0 +#define REG1_IRQSEL_RISE_FALL 1 +#define REG1_IRQSEL_FALL 2 +#define REG1_IRQSEL_RISE 3 #define REG1_RESET BIT(0) #define REG1_ENABLE BIT(15) @@ -68,7 +68,6 @@ struct meson_ir { void __iomem *reg; struct rc_dev *rc; - int irq; spinlock_t lock; }; @@ -86,18 +85,19 @@ static void meson_ir_set_mask(struct meson_ir *ir, unsigned int reg, static irqreturn_t meson_ir_irq(int irqno, void *dev_id) { struct meson_ir *ir = dev_id; - u32 duration; + u32 duration, status; DEFINE_IR_RAW_EVENT(rawir); spin_lock(&ir->lock); - duration = readl(ir->reg + IR_DEC_REG1); - duration = (duration & REG1_TIME_IV_MASK) >> REG1_TIME_IV_SHIFT; + duration = readl_relaxed(ir->reg + IR_DEC_REG1); + duration = FIELD_GET(REG1_TIME_IV_MASK, duration); rawir.duration = US_TO_NS(duration * MESON_TRATE); - rawir.pulse = !!(readl(ir->reg + IR_DEC_STATUS) & STATUS_IR_DEC_IN); + status = readl_relaxed(ir->reg + IR_DEC_STATUS); + rawir.pulse = !!(status & STATUS_IR_DEC_IN); - ir_raw_event_store_with_filter(ir->rc, &rawir); + ir_raw_event_store(ir->rc, &rawir); ir_raw_event_handle(ir->rc); spin_unlock(&ir->lock); @@ -112,7 +112,7 @@ static int meson_ir_probe(struct platform_device *pdev) struct resource *res; const char *map_name; struct meson_ir *ir; - int ret; + int irq, ret; ir = devm_kzalloc(dev, sizeof(struct meson_ir), GFP_KERNEL); if (!ir) @@ -125,13 +125,13 @@ static int meson_ir_probe(struct platform_device *pdev) return PTR_ERR(ir->reg); } - ir->irq = platform_get_irq(pdev, 0); - if (ir->irq < 0) { + irq = platform_get_irq(pdev, 0); + if (irq < 0) { dev_err(dev, "no irq resource\n"); - return ir->irq; + return irq; } - ir->rc = rc_allocate_device(RC_DRIVER_IR_RAW); + ir->rc = devm_rc_allocate_device(dev, RC_DRIVER_IR_RAW); if (!ir->rc) { dev_err(dev, "failed to allocate rc device\n"); return -ENOMEM; @@ -143,7 +143,6 @@ static int meson_ir_probe(struct platform_device *pdev) ir->rc->input_id.bustype = BUS_HOST; map_name = of_get_property(node, "linux,rc-map-name", NULL); ir->rc->map_name = map_name ? map_name : RC_MAP_EMPTY; - ir->rc->dev.parent = dev; ir->rc->allowed_protocols = RC_BIT_ALL_IR_DECODER; ir->rc->rx_resolution = US_TO_NS(MESON_TRATE); ir->rc->timeout = MS_TO_NS(200); @@ -152,16 +151,16 @@ static int meson_ir_probe(struct platform_device *pdev) spin_lock_init(&ir->lock); platform_set_drvdata(pdev, ir); - ret = rc_register_device(ir->rc); + ret = devm_rc_register_device(dev, ir->rc); if (ret) { dev_err(dev, "failed to register rc device\n"); - goto out_free; + return ret; } - ret = devm_request_irq(dev, ir->irq, meson_ir_irq, 0, "ir-meson", ir); + ret = devm_request_irq(dev, irq, meson_ir_irq, 0, NULL, ir); if (ret) { dev_err(dev, "failed to request irq\n"); - goto out_unreg; + return ret; } /* Reset the decoder */ @@ -171,29 +170,22 @@ static int meson_ir_probe(struct platform_device *pdev) /* Set general operation mode (= raw/software decoding) */ if (of_device_is_compatible(node, "amlogic,meson6-ir")) meson_ir_set_mask(ir, IR_DEC_REG1, REG1_MODE_MASK, - DECODE_MODE_RAW << REG1_MODE_SHIFT); + FIELD_PREP(REG1_MODE_MASK, DECODE_MODE_RAW)); else meson_ir_set_mask(ir, IR_DEC_REG2, REG2_MODE_MASK, - DECODE_MODE_RAW << REG2_MODE_SHIFT); + FIELD_PREP(REG2_MODE_MASK, DECODE_MODE_RAW)); /* Set rate */ meson_ir_set_mask(ir, IR_DEC_REG0, REG0_RATE_MASK, MESON_TRATE - 1); /* IRQ on rising and falling edges */ meson_ir_set_mask(ir, IR_DEC_REG1, REG1_IRQSEL_MASK, - REG1_IRQSEL_RISE_FALL); + FIELD_PREP(REG1_IRQSEL_MASK, REG1_IRQSEL_RISE_FALL)); /* Enable the decoder */ meson_ir_set_mask(ir, IR_DEC_REG1, REG1_ENABLE, REG1_ENABLE); dev_info(dev, "receiver initialized\n"); return 0; -out_unreg: - rc_unregister_device(ir->rc); - ir->rc = NULL; -out_free: - rc_free_device(ir->rc); - - return ret; } static int meson_ir_remove(struct platform_device *pdev) @@ -206,11 +198,35 @@ static int meson_ir_remove(struct platform_device *pdev) meson_ir_set_mask(ir, IR_DEC_REG1, REG1_ENABLE, 0); spin_unlock_irqrestore(&ir->lock, flags); - rc_unregister_device(ir->rc); - return 0; } +static void meson_ir_shutdown(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + struct meson_ir *ir = platform_get_drvdata(pdev); + unsigned long flags; + + spin_lock_irqsave(&ir->lock, flags); + + /* + * Set operation mode to NEC/hardware decoding to give + * bootloader a chance to power the system back on + */ + if (of_device_is_compatible(node, "amlogic,meson6-ir")) + meson_ir_set_mask(ir, IR_DEC_REG1, REG1_MODE_MASK, + DECODE_MODE_NEC << REG1_MODE_SHIFT); + else + meson_ir_set_mask(ir, IR_DEC_REG2, REG2_MODE_MASK, + DECODE_MODE_NEC << REG2_MODE_SHIFT); + + /* Set rate to default value */ + meson_ir_set_mask(ir, IR_DEC_REG0, REG0_RATE_MASK, 0x13); + + spin_unlock_irqrestore(&ir->lock, flags); +} + static const struct of_device_id meson_ir_match[] = { { .compatible = "amlogic,meson6-ir" }, { .compatible = "amlogic,meson8b-ir" }, @@ -222,6 +238,7 @@ MODULE_DEVICE_TABLE(of, meson_ir_match); static struct platform_driver meson_ir_driver = { .probe = meson_ir_probe, .remove = meson_ir_remove, + .shutdown = meson_ir_shutdown, .driver = { .name = DRIVER_NAME, .of_match_table = meson_ir_match, diff --git a/drivers/media/rc/rc-main.c b/drivers/media/rc/rc-main.c index 6ec73357fa47..802e559cc30e 100644 --- a/drivers/media/rc/rc-main.c +++ b/drivers/media/rc/rc-main.c @@ -1703,6 +1703,16 @@ static int rc_setup_rx_device(struct rc_dev *dev) if (dev->close) dev->input_dev->close = ir_close; + dev->input_dev->dev.parent = &dev->dev; + memcpy(&dev->input_dev->id, &dev->input_id, sizeof(dev->input_id)); + dev->input_dev->phys = dev->input_phys; + dev->input_dev->name = dev->input_name; + + /* rc_open will be called here */ + rc = input_register_device(dev->input_dev); + if (rc) + goto out_table; + /* * Default delay of 250ms is too short for some protocols, especially * since the timeout is currently set to 250ms. Increase it to 500ms, @@ -1718,16 +1728,6 @@ static int rc_setup_rx_device(struct rc_dev *dev) */ dev->input_dev->rep[REP_PERIOD] = 125; - dev->input_dev->dev.parent = &dev->dev; - memcpy(&dev->input_dev->id, &dev->input_id, sizeof(dev->input_id)); - dev->input_dev->phys = dev->input_phys; - dev->input_dev->name = dev->input_name; - - /* rc_open will be called here */ - rc = input_register_device(dev->input_dev); - if (rc) - goto out_table; - return 0; out_table: diff --git a/drivers/media/rc/sir_ir.c b/drivers/media/rc/sir_ir.c index e12ec50bf0bf..5ee3a2301c05 100644 --- a/drivers/media/rc/sir_ir.c +++ b/drivers/media/rc/sir_ir.c @@ -53,16 +53,13 @@ static DEFINE_SPINLOCK(hardware_lock); /* Communication with user-space */ static void add_read_queue(int flag, unsigned long val); -static int init_chrdev(void); /* Hardware */ static irqreturn_t sir_interrupt(int irq, void *dev_id); static void send_space(unsigned long len); static void send_pulse(unsigned long len); -static int init_hardware(void); +static void init_hardware(void); static void drop_hardware(void); /* Initialisation */ -static int init_port(void); -static void drop_port(void); static inline unsigned int sinp(int offset) { @@ -122,28 +119,6 @@ static void add_read_queue(int flag, unsigned long val) ir_raw_event_store_with_filter(rcdev, &ev); } -static int init_chrdev(void) -{ - rcdev = devm_rc_allocate_device(&sir_ir_dev->dev, RC_DRIVER_IR_RAW); - if (!rcdev) - return -ENOMEM; - - rcdev->input_name = "SIR IrDA port"; - rcdev->input_phys = KBUILD_MODNAME "/input0"; - rcdev->input_id.bustype = BUS_HOST; - rcdev->input_id.vendor = 0x0001; - rcdev->input_id.product = 0x0001; - rcdev->input_id.version = 0x0100; - rcdev->tx_ir = sir_tx_ir; - rcdev->allowed_protocols = RC_BIT_ALL_IR_DECODER; - rcdev->driver_name = KBUILD_MODNAME; - rcdev->map_name = RC_MAP_RC6_MCE; - rcdev->timeout = IR_DEFAULT_TIMEOUT; - rcdev->dev.parent = &sir_ir_dev->dev; - - return devm_rc_register_device(&sir_ir_dev->dev, rcdev); -} - /* SECTION: Hardware */ static void sir_timeout(unsigned long data) { @@ -183,9 +158,15 @@ static irqreturn_t sir_interrupt(int irq, void *dev_id) static unsigned long delt; unsigned long deltintr; unsigned long flags; + int counter = 0; int iir, lsr; while ((iir = inb(io + UART_IIR) & UART_IIR_ID)) { + if (++counter > 256) { + dev_err(&sir_ir_dev->dev, "Trapped in interrupt"); + break; + } + switch (iir & UART_IIR_ID) { /* FIXME toto treba preriedit */ case UART_IIR_MSI: (void)inb(io + UART_MSR); @@ -282,7 +263,7 @@ static void send_pulse(unsigned long len) } } -static int init_hardware(void) +static void init_hardware(void) { unsigned long flags; @@ -304,7 +285,6 @@ static int init_hardware(void) /* turn on UART */ outb(UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2, io + UART_MCR); spin_unlock_irqrestore(&hardware_lock, flags); - return 0; } static void drop_hardware(void) @@ -320,61 +300,55 @@ static void drop_hardware(void) } /* SECTION: Initialisation */ - -static int init_port(void) +static int sir_ir_probe(struct platform_device *dev) { int retval; + rcdev = devm_rc_allocate_device(&sir_ir_dev->dev, RC_DRIVER_IR_RAW); + if (!rcdev) + return -ENOMEM; + + rcdev->input_name = "SIR IrDA port"; + rcdev->input_phys = KBUILD_MODNAME "/input0"; + rcdev->input_id.bustype = BUS_HOST; + rcdev->input_id.vendor = 0x0001; + rcdev->input_id.product = 0x0001; + rcdev->input_id.version = 0x0100; + rcdev->tx_ir = sir_tx_ir; + rcdev->allowed_protocols = RC_BIT_ALL_IR_DECODER; + rcdev->driver_name = KBUILD_MODNAME; + rcdev->map_name = RC_MAP_RC6_MCE; + rcdev->timeout = IR_DEFAULT_TIMEOUT; + rcdev->dev.parent = &sir_ir_dev->dev; + setup_timer(&timerlist, sir_timeout, 0); /* get I/O port access and IRQ line */ - if (!request_region(io, 8, KBUILD_MODNAME)) { + if (!devm_request_region(&sir_ir_dev->dev, io, 8, KBUILD_MODNAME)) { pr_err("i/o port 0x%.4x already in use.\n", io); return -EBUSY; } - retval = request_irq(irq, sir_interrupt, 0, - KBUILD_MODNAME, NULL); + retval = devm_request_irq(&sir_ir_dev->dev, irq, sir_interrupt, 0, + KBUILD_MODNAME, NULL); if (retval < 0) { - release_region(io, 8); pr_err("IRQ %d already in use.\n", irq); return retval; } pr_info("I/O port 0x%.4x, IRQ %d.\n", io, irq); - return 0; -} - -static void drop_port(void) -{ - free_irq(irq, NULL); - del_timer_sync(&timerlist); - release_region(io, 8); -} - -static int init_sir_ir(void) -{ - int retval; - - retval = init_port(); + retval = devm_rc_register_device(&sir_ir_dev->dev, rcdev); if (retval < 0) return retval; - init_hardware(); - return 0; -} - -static int sir_ir_probe(struct platform_device *dev) -{ - int retval; - retval = init_chrdev(); - if (retval < 0) - return retval; + init_hardware(); - return init_sir_ir(); + return 0; } static int sir_ir_remove(struct platform_device *dev) { + drop_hardware(); + del_timer_sync(&timerlist); return 0; } @@ -415,8 +389,6 @@ pdev_alloc_fail: static void __exit sir_ir_exit(void) { - drop_hardware(); - drop_port(); platform_device_unregister(sir_ir_dev); platform_driver_unregister(&sir_ir_driver); } |