xref: /openbmc/linux/drivers/media/rc/lirc_dev.c (revision 3381b779)
132cf86f6SMauro Carvalho Chehab /*
232cf86f6SMauro Carvalho Chehab  * LIRC base driver
332cf86f6SMauro Carvalho Chehab  *
432cf86f6SMauro Carvalho Chehab  * by Artur Lipowski <alipowski@interia.pl>
532cf86f6SMauro Carvalho Chehab  *
632cf86f6SMauro Carvalho Chehab  *  This program is free software; you can redistribute it and/or modify
732cf86f6SMauro Carvalho Chehab  *  it under the terms of the GNU General Public License as published by
832cf86f6SMauro Carvalho Chehab  *  the Free Software Foundation; either version 2 of the License, or
932cf86f6SMauro Carvalho Chehab  *  (at your option) any later version.
1032cf86f6SMauro Carvalho Chehab  *
1132cf86f6SMauro Carvalho Chehab  *  This program is distributed in the hope that it will be useful,
1232cf86f6SMauro Carvalho Chehab  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
1332cf86f6SMauro Carvalho Chehab  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1432cf86f6SMauro Carvalho Chehab  *  GNU General Public License for more details.
1532cf86f6SMauro Carvalho Chehab  *
1632cf86f6SMauro Carvalho Chehab  */
1732cf86f6SMauro Carvalho Chehab 
183fac0314SAndi Shyti #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
193fac0314SAndi Shyti 
2032cf86f6SMauro Carvalho Chehab #include <linux/module.h>
21174cd4b1SIngo Molnar #include <linux/sched/signal.h>
2232cf86f6SMauro Carvalho Chehab #include <linux/ioctl.h>
2332cf86f6SMauro Carvalho Chehab #include <linux/poll.h>
2432cf86f6SMauro Carvalho Chehab #include <linux/mutex.h>
2532cf86f6SMauro Carvalho Chehab #include <linux/device.h>
2632cf86f6SMauro Carvalho Chehab #include <linux/cdev.h>
2732cf86f6SMauro Carvalho Chehab 
28ca7a722dSSrinivas Kandagatla #include <media/rc-core.h>
2932cf86f6SMauro Carvalho Chehab #include <media/lirc.h>
3032cf86f6SMauro Carvalho Chehab #include <media/lirc_dev.h>
3132cf86f6SMauro Carvalho Chehab 
3232cf86f6SMauro Carvalho Chehab #define LOGHEAD		"lirc_dev (%s[%d]): "
3332cf86f6SMauro Carvalho Chehab 
3432cf86f6SMauro Carvalho Chehab static dev_t lirc_base_dev;
3532cf86f6SMauro Carvalho Chehab 
3632cf86f6SMauro Carvalho Chehab struct irctl {
3732cf86f6SMauro Carvalho Chehab 	struct lirc_driver d;
383bce5572SDavid Härdeman 	bool attached;
3932cf86f6SMauro Carvalho Chehab 	int open;
4032cf86f6SMauro Carvalho Chehab 
413381b779SDavid Härdeman 	struct mutex mutex;	/* protect from simultaneous accesses */
4232cf86f6SMauro Carvalho Chehab 	struct lirc_buffer *buf;
430f7c4063SDavid Härdeman 	bool buf_internal;
4432cf86f6SMauro Carvalho Chehab 
4574c839b2SSean Young 	struct device dev;
4674c839b2SSean Young 	struct cdev cdev;
4732cf86f6SMauro Carvalho Chehab };
4832cf86f6SMauro Carvalho Chehab 
493381b779SDavid Härdeman /* This mutex protects the irctls array */
5032cf86f6SMauro Carvalho Chehab static DEFINE_MUTEX(lirc_dev_lock);
5132cf86f6SMauro Carvalho Chehab 
5232cf86f6SMauro Carvalho Chehab static struct irctl *irctls[MAX_IRCTL_DEVICES];
5332cf86f6SMauro Carvalho Chehab 
5432cf86f6SMauro Carvalho Chehab /* Only used for sysfs but defined to void otherwise */
5532cf86f6SMauro Carvalho Chehab static struct class *lirc_class;
5632cf86f6SMauro Carvalho Chehab 
573381b779SDavid Härdeman static void lirc_free_buffer(struct irctl *ir)
5832cf86f6SMauro Carvalho Chehab {
59a607f51eSSean Young 	put_device(ir->dev.parent);
60a607f51eSSean Young 
610f7c4063SDavid Härdeman 	if (ir->buf_internal) {
6232cf86f6SMauro Carvalho Chehab 		lirc_buffer_free(ir->buf);
6332cf86f6SMauro Carvalho Chehab 		kfree(ir->buf);
643381b779SDavid Härdeman 		ir->buf = NULL;
6532cf86f6SMauro Carvalho Chehab 	}
663381b779SDavid Härdeman }
673381b779SDavid Härdeman 
683381b779SDavid Härdeman static void lirc_release(struct device *ld)
693381b779SDavid Härdeman {
703381b779SDavid Härdeman 	struct irctl *ir = container_of(ld, struct irctl, dev);
7174c839b2SSean Young 
7274c839b2SSean Young 	mutex_lock(&lirc_dev_lock);
7374c839b2SSean Young 	irctls[ir->d.minor] = NULL;
7474c839b2SSean Young 	mutex_unlock(&lirc_dev_lock);
753381b779SDavid Härdeman 	lirc_free_buffer(ir);
7674c839b2SSean Young 	kfree(ir);
7732cf86f6SMauro Carvalho Chehab }
7832cf86f6SMauro Carvalho Chehab 
796fa99e1aSAndi Shyti static int lirc_allocate_buffer(struct irctl *ir)
806fa99e1aSAndi Shyti {
8170143984SAndi Shyti 	int err = 0;
826fa99e1aSAndi Shyti 	struct lirc_driver *d = &ir->d;
836fa99e1aSAndi Shyti 
846fa99e1aSAndi Shyti 	if (d->rbuf) {
856fa99e1aSAndi Shyti 		ir->buf = d->rbuf;
860f7c4063SDavid Härdeman 		ir->buf_internal = false;
876fa99e1aSAndi Shyti 	} else {
886fa99e1aSAndi Shyti 		ir->buf = kmalloc(sizeof(struct lirc_buffer), GFP_KERNEL);
8970143984SAndi Shyti 		if (!ir->buf) {
9070143984SAndi Shyti 			err = -ENOMEM;
9170143984SAndi Shyti 			goto out;
9270143984SAndi Shyti 		}
936fa99e1aSAndi Shyti 
94b145ef94SDavid Härdeman 		err = lirc_buffer_init(ir->buf, d->chunk_size, d->buffer_size);
956fa99e1aSAndi Shyti 		if (err) {
966fa99e1aSAndi Shyti 			kfree(ir->buf);
970f7c4063SDavid Härdeman 			ir->buf = NULL;
9870143984SAndi Shyti 			goto out;
996fa99e1aSAndi Shyti 		}
1000f7c4063SDavid Härdeman 
1010f7c4063SDavid Härdeman 		ir->buf_internal = true;
10256481f00SDavid Härdeman 		d->rbuf = ir->buf;
1036fa99e1aSAndi Shyti 	}
1046fa99e1aSAndi Shyti 
10570143984SAndi Shyti out:
10670143984SAndi Shyti 	return err;
1076fa99e1aSAndi Shyti }
1086fa99e1aSAndi Shyti 
10956481f00SDavid Härdeman int lirc_register_driver(struct lirc_driver *d)
11032cf86f6SMauro Carvalho Chehab {
11132cf86f6SMauro Carvalho Chehab 	struct irctl *ir;
112c3c6dd75SDavid Härdeman 	unsigned int minor;
11332cf86f6SMauro Carvalho Chehab 	int err;
11432cf86f6SMauro Carvalho Chehab 
11532cf86f6SMauro Carvalho Chehab 	if (!d) {
1163fac0314SAndi Shyti 		pr_err("driver pointer must be not NULL!\n");
11754fcecafSAndi Shyti 		return -EBADRQC;
11832cf86f6SMauro Carvalho Chehab 	}
11932cf86f6SMauro Carvalho Chehab 
12032cf86f6SMauro Carvalho Chehab 	if (!d->dev) {
1213fac0314SAndi Shyti 		pr_err("dev pointer not filled in!\n");
12254fcecafSAndi Shyti 		return -EINVAL;
12332cf86f6SMauro Carvalho Chehab 	}
12432cf86f6SMauro Carvalho Chehab 
125712551f0SDavid Härdeman 	if (!d->fops) {
126712551f0SDavid Härdeman 		pr_err("fops pointer not filled in!\n");
127712551f0SDavid Härdeman 		return -EINVAL;
128712551f0SDavid Härdeman 	}
129712551f0SDavid Härdeman 
130b145ef94SDavid Härdeman 	if (!d->rbuf && d->chunk_size < 1) {
131b145ef94SDavid Härdeman 		pr_err("chunk_size must be set!\n");
132b145ef94SDavid Härdeman 		return -EINVAL;
133b145ef94SDavid Härdeman 	}
134b145ef94SDavid Härdeman 
135b145ef94SDavid Härdeman 	if (!d->rbuf && d->buffer_size < 1) {
136b145ef94SDavid Härdeman 		pr_err("buffer_size must be set!\n");
137b145ef94SDavid Härdeman 		return -EINVAL;
138b145ef94SDavid Härdeman 	}
139b145ef94SDavid Härdeman 
1409675ee5aSAndi Shyti 	if (d->code_length < 1 || d->code_length > (BUFLEN * 8)) {
1413fac0314SAndi Shyti 		dev_err(d->dev, "code length must be less than %d bits\n",
1423fac0314SAndi Shyti 								BUFLEN * 8);
14354fcecafSAndi Shyti 		return -EBADRQC;
14432cf86f6SMauro Carvalho Chehab 	}
14532cf86f6SMauro Carvalho Chehab 
146c3104e1bSDavid Härdeman 	if (!d->rbuf && !(d->fops && d->fops->read &&
14714db9fc2SAndi Shyti 			  d->fops->poll && d->fops->unlocked_ioctl)) {
1483fac0314SAndi Shyti 		dev_err(d->dev, "undefined read, poll, ioctl\n");
14954fcecafSAndi Shyti 		return -EBADRQC;
15032cf86f6SMauro Carvalho Chehab 	}
15132cf86f6SMauro Carvalho Chehab 
1523381b779SDavid Härdeman 	/* some safety check 8-) */
1533381b779SDavid Härdeman 	d->name[sizeof(d->name) - 1] = '\0';
1543381b779SDavid Härdeman 
1553381b779SDavid Härdeman 	if (d->features == 0)
1563381b779SDavid Härdeman 		d->features = LIRC_CAN_REC_LIRCCODE;
1573381b779SDavid Härdeman 
1583381b779SDavid Härdeman 	ir = kzalloc(sizeof(*ir), GFP_KERNEL);
1593381b779SDavid Härdeman 	if (!ir)
1603381b779SDavid Härdeman 		return -ENOMEM;
1613381b779SDavid Härdeman 
1623381b779SDavid Härdeman 	mutex_init(&ir->mutex);
1633381b779SDavid Härdeman 	ir->d = *d;
1643381b779SDavid Härdeman 
1653381b779SDavid Härdeman 	if (LIRC_CAN_REC(d->features)) {
1663381b779SDavid Härdeman 		err = lirc_allocate_buffer(ir);
1673381b779SDavid Härdeman 		if (err) {
1683381b779SDavid Härdeman 			kfree(ir);
1693381b779SDavid Härdeman 			return err;
1703381b779SDavid Härdeman 		}
1713381b779SDavid Härdeman 		d->rbuf = ir->buf;
1723381b779SDavid Härdeman 	}
1733381b779SDavid Härdeman 
17432cf86f6SMauro Carvalho Chehab 	mutex_lock(&lirc_dev_lock);
17532cf86f6SMauro Carvalho Chehab 
17632cf86f6SMauro Carvalho Chehab 	/* find first free slot for driver */
17732cf86f6SMauro Carvalho Chehab 	for (minor = 0; minor < MAX_IRCTL_DEVICES; minor++)
17832cf86f6SMauro Carvalho Chehab 		if (!irctls[minor])
17932cf86f6SMauro Carvalho Chehab 			break;
180c3c6dd75SDavid Härdeman 
1819675ee5aSAndi Shyti 	if (minor == MAX_IRCTL_DEVICES) {
1823fac0314SAndi Shyti 		dev_err(d->dev, "no free slots for drivers!\n");
1833381b779SDavid Härdeman 		mutex_unlock(&lirc_dev_lock);
1843381b779SDavid Härdeman 		lirc_free_buffer(ir);
1853381b779SDavid Härdeman 		kfree(ir);
1863381b779SDavid Härdeman 		return -ENOMEM;
18732cf86f6SMauro Carvalho Chehab 	}
18832cf86f6SMauro Carvalho Chehab 
18932cf86f6SMauro Carvalho Chehab 	irctls[minor] = ir;
190c3c6dd75SDavid Härdeman 	d->irctl = ir;
19132cf86f6SMauro Carvalho Chehab 	d->minor = minor;
1923381b779SDavid Härdeman 	ir->d.minor = minor;
19332cf86f6SMauro Carvalho Chehab 
1943381b779SDavid Härdeman 	mutex_unlock(&lirc_dev_lock);
19556481f00SDavid Härdeman 
19625823226SDavid Härdeman 	device_initialize(&ir->dev);
19774c839b2SSean Young 	ir->dev.devt = MKDEV(MAJOR(lirc_base_dev), ir->d.minor);
19874c839b2SSean Young 	ir->dev.class = lirc_class;
19974c839b2SSean Young 	ir->dev.parent = d->dev;
20074c839b2SSean Young 	ir->dev.release = lirc_release;
20174c839b2SSean Young 	dev_set_name(&ir->dev, "lirc%d", ir->d.minor);
20232cf86f6SMauro Carvalho Chehab 
203712551f0SDavid Härdeman 	cdev_init(&ir->cdev, d->fops);
204712551f0SDavid Härdeman 	ir->cdev.owner = ir->d.owner;
2053bce5572SDavid Härdeman 	ir->attached = true;
20674c839b2SSean Young 
2070510d810SDavid Härdeman 	err = cdev_device_add(&ir->cdev, &ir->dev);
2083381b779SDavid Härdeman 	if (err) {
2093381b779SDavid Härdeman 		put_device(&ir->dev);
2103381b779SDavid Härdeman 		return err;
2113381b779SDavid Härdeman 	}
21232cf86f6SMauro Carvalho Chehab 
213a607f51eSSean Young 	get_device(ir->dev.parent);
214a607f51eSSean Young 
21532cf86f6SMauro Carvalho Chehab 	dev_info(ir->d.dev, "lirc_dev: driver %s registered at minor = %d\n",
21632cf86f6SMauro Carvalho Chehab 		 ir->d.name, ir->d.minor);
21756481f00SDavid Härdeman 
218c3c6dd75SDavid Härdeman 	return 0;
21932cf86f6SMauro Carvalho Chehab }
22032cf86f6SMauro Carvalho Chehab EXPORT_SYMBOL(lirc_register_driver);
22132cf86f6SMauro Carvalho Chehab 
222c3c6dd75SDavid Härdeman void lirc_unregister_driver(struct lirc_driver *d)
22332cf86f6SMauro Carvalho Chehab {
22432cf86f6SMauro Carvalho Chehab 	struct irctl *ir;
22532cf86f6SMauro Carvalho Chehab 
226c3c6dd75SDavid Härdeman 	if (!d || !d->irctl)
227c3c6dd75SDavid Härdeman 		return;
22832cf86f6SMauro Carvalho Chehab 
229c3c6dd75SDavid Härdeman 	ir = d->irctl;
23032cf86f6SMauro Carvalho Chehab 
23132cf86f6SMauro Carvalho Chehab 	dev_dbg(ir->d.dev, "lirc_dev: driver %s unregistered from minor = %d\n",
232c3c6dd75SDavid Härdeman 		d->name, d->minor);
23332cf86f6SMauro Carvalho Chehab 
2343381b779SDavid Härdeman 	cdev_device_del(&ir->cdev, &ir->dev);
2353381b779SDavid Härdeman 
2363381b779SDavid Härdeman 	mutex_lock(&ir->mutex);
2373381b779SDavid Härdeman 
2383bce5572SDavid Härdeman 	ir->attached = false;
23932cf86f6SMauro Carvalho Chehab 	if (ir->open) {
24032cf86f6SMauro Carvalho Chehab 		dev_dbg(ir->d.dev, LOGHEAD "releasing opened driver\n",
241c3c6dd75SDavid Härdeman 			d->name, d->minor);
24232cf86f6SMauro Carvalho Chehab 		wake_up_interruptible(&ir->buf->wait_poll);
24374c839b2SSean Young 	}
24474c839b2SSean Young 
2453381b779SDavid Härdeman 	mutex_unlock(&ir->mutex);
24632cf86f6SMauro Carvalho Chehab 
24774c839b2SSean Young 	put_device(&ir->dev);
24832cf86f6SMauro Carvalho Chehab }
24932cf86f6SMauro Carvalho Chehab EXPORT_SYMBOL(lirc_unregister_driver);
25032cf86f6SMauro Carvalho Chehab 
25132cf86f6SMauro Carvalho Chehab int lirc_dev_fop_open(struct inode *inode, struct file *file)
25232cf86f6SMauro Carvalho Chehab {
253615cd3feSDavid Härdeman 	struct irctl *ir = container_of(inode->i_cdev, struct irctl, cdev);
254de226ec8SDavid Härdeman 	int retval;
25532cf86f6SMauro Carvalho Chehab 
25632cf86f6SMauro Carvalho Chehab 	dev_dbg(ir->d.dev, LOGHEAD "open called\n", ir->d.name, ir->d.minor);
25732cf86f6SMauro Carvalho Chehab 
2583381b779SDavid Härdeman 	retval = mutex_lock_interruptible(&ir->mutex);
2593381b779SDavid Härdeman 	if (retval)
2603381b779SDavid Härdeman 		return retval;
2613381b779SDavid Härdeman 
2623381b779SDavid Härdeman 	if (!ir->attached) {
2633381b779SDavid Härdeman 		retval = -ENODEV;
2643381b779SDavid Härdeman 		goto out;
2653381b779SDavid Härdeman 	}
2663381b779SDavid Härdeman 
2673381b779SDavid Härdeman 	if (ir->open) {
2683381b779SDavid Härdeman 		retval = -EBUSY;
2693381b779SDavid Härdeman 		goto out;
2703381b779SDavid Härdeman 	}
27132cf86f6SMauro Carvalho Chehab 
272ca7a722dSSrinivas Kandagatla 	if (ir->d.rdev) {
273ca7a722dSSrinivas Kandagatla 		retval = rc_open(ir->d.rdev);
274ca7a722dSSrinivas Kandagatla 		if (retval)
2753381b779SDavid Härdeman 			goto out;
276ca7a722dSSrinivas Kandagatla 	}
277ca7a722dSSrinivas Kandagatla 
27874c839b2SSean Young 	if (ir->buf)
27932cf86f6SMauro Carvalho Chehab 		lirc_buffer_clear(ir->buf);
2802c5a1f44SDavid Härdeman 
2812c5a1f44SDavid Härdeman 	ir->open++;
28232cf86f6SMauro Carvalho Chehab 
283615cd3feSDavid Härdeman 	lirc_init_pdata(inode, file);
28432cf86f6SMauro Carvalho Chehab 	nonseekable_open(inode, file);
2853381b779SDavid Härdeman 	mutex_unlock(&ir->mutex);
28632cf86f6SMauro Carvalho Chehab 
287de226ec8SDavid Härdeman 	return 0;
2883381b779SDavid Härdeman 
2893381b779SDavid Härdeman out:
2903381b779SDavid Härdeman 	mutex_unlock(&ir->mutex);
2913381b779SDavid Härdeman 	return retval;
29232cf86f6SMauro Carvalho Chehab }
29332cf86f6SMauro Carvalho Chehab EXPORT_SYMBOL(lirc_dev_fop_open);
29432cf86f6SMauro Carvalho Chehab 
29532cf86f6SMauro Carvalho Chehab int lirc_dev_fop_close(struct inode *inode, struct file *file)
29632cf86f6SMauro Carvalho Chehab {
297615cd3feSDavid Härdeman 	struct irctl *ir = file->private_data;
29832cf86f6SMauro Carvalho Chehab 
2993381b779SDavid Härdeman 	mutex_lock(&ir->mutex);
30032cf86f6SMauro Carvalho Chehab 
301ca7a722dSSrinivas Kandagatla 	rc_close(ir->d.rdev);
30232cf86f6SMauro Carvalho Chehab 	ir->open--;
3033381b779SDavid Härdeman 
3043381b779SDavid Härdeman 	mutex_unlock(&ir->mutex);
30532cf86f6SMauro Carvalho Chehab 
30632cf86f6SMauro Carvalho Chehab 	return 0;
30732cf86f6SMauro Carvalho Chehab }
30832cf86f6SMauro Carvalho Chehab EXPORT_SYMBOL(lirc_dev_fop_close);
30932cf86f6SMauro Carvalho Chehab 
31032cf86f6SMauro Carvalho Chehab unsigned int lirc_dev_fop_poll(struct file *file, poll_table *wait)
31132cf86f6SMauro Carvalho Chehab {
312615cd3feSDavid Härdeman 	struct irctl *ir = file->private_data;
31332cf86f6SMauro Carvalho Chehab 	unsigned int ret;
31432cf86f6SMauro Carvalho Chehab 
31532cf86f6SMauro Carvalho Chehab 	if (!ir->attached)
31629debf3dSDavid Härdeman 		return POLLHUP | POLLERR;
31732cf86f6SMauro Carvalho Chehab 
3183656cdddSAndy Shevchenko 	if (ir->buf) {
31932cf86f6SMauro Carvalho Chehab 		poll_wait(file, &ir->buf->wait_poll, wait);
32032cf86f6SMauro Carvalho Chehab 
32132cf86f6SMauro Carvalho Chehab 		if (lirc_buffer_empty(ir->buf))
32232cf86f6SMauro Carvalho Chehab 			ret = 0;
32332cf86f6SMauro Carvalho Chehab 		else
32432cf86f6SMauro Carvalho Chehab 			ret = POLLIN | POLLRDNORM;
3253656cdddSAndy Shevchenko 	} else
32632cf86f6SMauro Carvalho Chehab 		ret = POLLERR;
32732cf86f6SMauro Carvalho Chehab 
32832cf86f6SMauro Carvalho Chehab 	dev_dbg(ir->d.dev, LOGHEAD "poll result = %d\n",
32932cf86f6SMauro Carvalho Chehab 		ir->d.name, ir->d.minor, ret);
33032cf86f6SMauro Carvalho Chehab 
33132cf86f6SMauro Carvalho Chehab 	return ret;
33232cf86f6SMauro Carvalho Chehab }
33332cf86f6SMauro Carvalho Chehab EXPORT_SYMBOL(lirc_dev_fop_poll);
33432cf86f6SMauro Carvalho Chehab 
33532cf86f6SMauro Carvalho Chehab long lirc_dev_fop_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
33632cf86f6SMauro Carvalho Chehab {
337615cd3feSDavid Härdeman 	struct irctl *ir = file->private_data;
33832cf86f6SMauro Carvalho Chehab 	__u32 mode;
3393381b779SDavid Härdeman 	int result;
34032cf86f6SMauro Carvalho Chehab 
34132cf86f6SMauro Carvalho Chehab 	dev_dbg(ir->d.dev, LOGHEAD "ioctl called (0x%x)\n",
34232cf86f6SMauro Carvalho Chehab 		ir->d.name, ir->d.minor, cmd);
34332cf86f6SMauro Carvalho Chehab 
3443381b779SDavid Härdeman 	result = mutex_lock_interruptible(&ir->mutex);
3453381b779SDavid Härdeman 	if (result)
3463381b779SDavid Härdeman 		return result;
34732cf86f6SMauro Carvalho Chehab 
3483381b779SDavid Härdeman 	if (!ir->attached) {
3493381b779SDavid Härdeman 		result = -ENODEV;
3503381b779SDavid Härdeman 		goto out;
3513381b779SDavid Härdeman 	}
35232cf86f6SMauro Carvalho Chehab 
35332cf86f6SMauro Carvalho Chehab 	switch (cmd) {
35432cf86f6SMauro Carvalho Chehab 	case LIRC_GET_FEATURES:
35560519af3SHans Verkuil 		result = put_user(ir->d.features, (__u32 __user *)arg);
35632cf86f6SMauro Carvalho Chehab 		break;
35732cf86f6SMauro Carvalho Chehab 	case LIRC_GET_REC_MODE:
358bd291208SSean Young 		if (!LIRC_CAN_REC(ir->d.features)) {
359b4088094SAndi Shyti 			result = -ENOTTY;
36032cf86f6SMauro Carvalho Chehab 			break;
36132cf86f6SMauro Carvalho Chehab 		}
36232cf86f6SMauro Carvalho Chehab 
36332cf86f6SMauro Carvalho Chehab 		result = put_user(LIRC_REC2MODE
36432cf86f6SMauro Carvalho Chehab 				  (ir->d.features & LIRC_CAN_REC_MASK),
36560519af3SHans Verkuil 				  (__u32 __user *)arg);
36632cf86f6SMauro Carvalho Chehab 		break;
36732cf86f6SMauro Carvalho Chehab 	case LIRC_SET_REC_MODE:
368bd291208SSean Young 		if (!LIRC_CAN_REC(ir->d.features)) {
369b4088094SAndi Shyti 			result = -ENOTTY;
37032cf86f6SMauro Carvalho Chehab 			break;
37132cf86f6SMauro Carvalho Chehab 		}
37232cf86f6SMauro Carvalho Chehab 
37360519af3SHans Verkuil 		result = get_user(mode, (__u32 __user *)arg);
37432cf86f6SMauro Carvalho Chehab 		if (!result && !(LIRC_MODE2REC(mode) & ir->d.features))
37532cf86f6SMauro Carvalho Chehab 			result = -EINVAL;
37632cf86f6SMauro Carvalho Chehab 		/*
37732cf86f6SMauro Carvalho Chehab 		 * FIXME: We should actually set the mode somehow but
37832cf86f6SMauro Carvalho Chehab 		 * for now, lirc_serial doesn't support mode changing either
37932cf86f6SMauro Carvalho Chehab 		 */
38032cf86f6SMauro Carvalho Chehab 		break;
38132cf86f6SMauro Carvalho Chehab 	case LIRC_GET_LENGTH:
38260519af3SHans Verkuil 		result = put_user(ir->d.code_length, (__u32 __user *)arg);
38332cf86f6SMauro Carvalho Chehab 		break;
38432cf86f6SMauro Carvalho Chehab 	case LIRC_GET_MIN_TIMEOUT:
38532cf86f6SMauro Carvalho Chehab 		if (!(ir->d.features & LIRC_CAN_SET_REC_TIMEOUT) ||
38632cf86f6SMauro Carvalho Chehab 		    ir->d.min_timeout == 0) {
387b4088094SAndi Shyti 			result = -ENOTTY;
38832cf86f6SMauro Carvalho Chehab 			break;
38932cf86f6SMauro Carvalho Chehab 		}
39032cf86f6SMauro Carvalho Chehab 
39160519af3SHans Verkuil 		result = put_user(ir->d.min_timeout, (__u32 __user *)arg);
39232cf86f6SMauro Carvalho Chehab 		break;
39332cf86f6SMauro Carvalho Chehab 	case LIRC_GET_MAX_TIMEOUT:
39432cf86f6SMauro Carvalho Chehab 		if (!(ir->d.features & LIRC_CAN_SET_REC_TIMEOUT) ||
39532cf86f6SMauro Carvalho Chehab 		    ir->d.max_timeout == 0) {
396b4088094SAndi Shyti 			result = -ENOTTY;
39732cf86f6SMauro Carvalho Chehab 			break;
39832cf86f6SMauro Carvalho Chehab 		}
39932cf86f6SMauro Carvalho Chehab 
40060519af3SHans Verkuil 		result = put_user(ir->d.max_timeout, (__u32 __user *)arg);
40132cf86f6SMauro Carvalho Chehab 		break;
40232cf86f6SMauro Carvalho Chehab 	default:
4035c862758SSean Young 		result = -ENOTTY;
40432cf86f6SMauro Carvalho Chehab 	}
40532cf86f6SMauro Carvalho Chehab 
4063381b779SDavid Härdeman out:
4073381b779SDavid Härdeman 	mutex_unlock(&ir->mutex);
40832cf86f6SMauro Carvalho Chehab 	return result;
40932cf86f6SMauro Carvalho Chehab }
41032cf86f6SMauro Carvalho Chehab EXPORT_SYMBOL(lirc_dev_fop_ioctl);
41132cf86f6SMauro Carvalho Chehab 
41232cf86f6SMauro Carvalho Chehab ssize_t lirc_dev_fop_read(struct file *file,
4130e835087SDan Carpenter 			  char __user *buffer,
41432cf86f6SMauro Carvalho Chehab 			  size_t length,
41532cf86f6SMauro Carvalho Chehab 			  loff_t *ppos)
41632cf86f6SMauro Carvalho Chehab {
417615cd3feSDavid Härdeman 	struct irctl *ir = file->private_data;
41832cf86f6SMauro Carvalho Chehab 	unsigned char *buf;
4193381b779SDavid Härdeman 	int ret, written = 0;
42032cf86f6SMauro Carvalho Chehab 	DECLARE_WAITQUEUE(wait, current);
42132cf86f6SMauro Carvalho Chehab 
42232cf86f6SMauro Carvalho Chehab 	dev_dbg(ir->d.dev, LOGHEAD "read called\n", ir->d.name, ir->d.minor);
42332cf86f6SMauro Carvalho Chehab 
424b145ef94SDavid Härdeman 	buf = kzalloc(ir->buf->chunk_size, GFP_KERNEL);
42532cf86f6SMauro Carvalho Chehab 	if (!buf)
42632cf86f6SMauro Carvalho Chehab 		return -ENOMEM;
42732cf86f6SMauro Carvalho Chehab 
4283381b779SDavid Härdeman 	ret = mutex_lock_interruptible(&ir->mutex);
4293381b779SDavid Härdeman 	if (ret) {
4303381b779SDavid Härdeman 		kfree(buf);
4313381b779SDavid Härdeman 		return ret;
43232cf86f6SMauro Carvalho Chehab 	}
4333381b779SDavid Härdeman 
43432cf86f6SMauro Carvalho Chehab 	if (!ir->attached) {
43532cf86f6SMauro Carvalho Chehab 		ret = -ENODEV;
43632cf86f6SMauro Carvalho Chehab 		goto out_locked;
43732cf86f6SMauro Carvalho Chehab 	}
43832cf86f6SMauro Carvalho Chehab 
4393381b779SDavid Härdeman 	if (!LIRC_CAN_REC(ir->d.features)) {
4403381b779SDavid Härdeman 		ret = -EINVAL;
4413381b779SDavid Härdeman 		goto out_locked;
4423381b779SDavid Härdeman 	}
4433381b779SDavid Härdeman 
444b145ef94SDavid Härdeman 	if (length % ir->buf->chunk_size) {
44532cf86f6SMauro Carvalho Chehab 		ret = -EINVAL;
44632cf86f6SMauro Carvalho Chehab 		goto out_locked;
44732cf86f6SMauro Carvalho Chehab 	}
44832cf86f6SMauro Carvalho Chehab 
44932cf86f6SMauro Carvalho Chehab 	/*
45032cf86f6SMauro Carvalho Chehab 	 * we add ourselves to the task queue before buffer check
45132cf86f6SMauro Carvalho Chehab 	 * to avoid losing scan code (in case when queue is awaken somewhere
45232cf86f6SMauro Carvalho Chehab 	 * between while condition checking and scheduling)
45332cf86f6SMauro Carvalho Chehab 	 */
45432cf86f6SMauro Carvalho Chehab 	add_wait_queue(&ir->buf->wait_poll, &wait);
45532cf86f6SMauro Carvalho Chehab 
45632cf86f6SMauro Carvalho Chehab 	/*
45732cf86f6SMauro Carvalho Chehab 	 * while we didn't provide 'length' bytes, device is opened in blocking
45832cf86f6SMauro Carvalho Chehab 	 * mode and 'copy_to_user' is happy, wait for data.
45932cf86f6SMauro Carvalho Chehab 	 */
46032cf86f6SMauro Carvalho Chehab 	while (written < length && ret == 0) {
46132cf86f6SMauro Carvalho Chehab 		if (lirc_buffer_empty(ir->buf)) {
46232cf86f6SMauro Carvalho Chehab 			/* According to the read(2) man page, 'written' can be
46332cf86f6SMauro Carvalho Chehab 			 * returned as less than 'length', instead of blocking
46432cf86f6SMauro Carvalho Chehab 			 * again, returning -EWOULDBLOCK, or returning
46562e92682SAndi Shyti 			 * -ERESTARTSYS
46662e92682SAndi Shyti 			 */
46732cf86f6SMauro Carvalho Chehab 			if (written)
46832cf86f6SMauro Carvalho Chehab 				break;
46932cf86f6SMauro Carvalho Chehab 			if (file->f_flags & O_NONBLOCK) {
47032cf86f6SMauro Carvalho Chehab 				ret = -EWOULDBLOCK;
47132cf86f6SMauro Carvalho Chehab 				break;
47232cf86f6SMauro Carvalho Chehab 			}
47332cf86f6SMauro Carvalho Chehab 			if (signal_pending(current)) {
47432cf86f6SMauro Carvalho Chehab 				ret = -ERESTARTSYS;
47532cf86f6SMauro Carvalho Chehab 				break;
47632cf86f6SMauro Carvalho Chehab 			}
47732cf86f6SMauro Carvalho Chehab 
4783381b779SDavid Härdeman 			mutex_unlock(&ir->mutex);
47932cf86f6SMauro Carvalho Chehab 			set_current_state(TASK_INTERRUPTIBLE);
48012accdcbSSean Young 			schedule();
48112accdcbSSean Young 			set_current_state(TASK_RUNNING);
48232cf86f6SMauro Carvalho Chehab 
4833381b779SDavid Härdeman 			ret = mutex_lock_interruptible(&ir->mutex);
4843381b779SDavid Härdeman 			if (ret) {
48532cf86f6SMauro Carvalho Chehab 				remove_wait_queue(&ir->buf->wait_poll, &wait);
48632cf86f6SMauro Carvalho Chehab 				goto out_unlocked;
48732cf86f6SMauro Carvalho Chehab 			}
48832cf86f6SMauro Carvalho Chehab 
48932cf86f6SMauro Carvalho Chehab 			if (!ir->attached) {
49032cf86f6SMauro Carvalho Chehab 				ret = -ENODEV;
491c77d17c0SSean Young 				goto out_locked;
49232cf86f6SMauro Carvalho Chehab 			}
49332cf86f6SMauro Carvalho Chehab 		} else {
49432cf86f6SMauro Carvalho Chehab 			lirc_buffer_read(ir->buf, buf);
49560519af3SHans Verkuil 			ret = copy_to_user((void __user *)buffer+written, buf,
49632cf86f6SMauro Carvalho Chehab 					   ir->buf->chunk_size);
49732cf86f6SMauro Carvalho Chehab 			if (!ret)
49832cf86f6SMauro Carvalho Chehab 				written += ir->buf->chunk_size;
49932cf86f6SMauro Carvalho Chehab 			else
50032cf86f6SMauro Carvalho Chehab 				ret = -EFAULT;
50132cf86f6SMauro Carvalho Chehab 		}
50232cf86f6SMauro Carvalho Chehab 	}
50332cf86f6SMauro Carvalho Chehab 
50432cf86f6SMauro Carvalho Chehab 	remove_wait_queue(&ir->buf->wait_poll, &wait);
50532cf86f6SMauro Carvalho Chehab 
50632cf86f6SMauro Carvalho Chehab out_locked:
5073381b779SDavid Härdeman 	mutex_unlock(&ir->mutex);
50832cf86f6SMauro Carvalho Chehab 
50932cf86f6SMauro Carvalho Chehab out_unlocked:
51032cf86f6SMauro Carvalho Chehab 	kfree(buf);
51132cf86f6SMauro Carvalho Chehab 
51232cf86f6SMauro Carvalho Chehab 	return ret ? ret : written;
51332cf86f6SMauro Carvalho Chehab }
51432cf86f6SMauro Carvalho Chehab EXPORT_SYMBOL(lirc_dev_fop_read);
51532cf86f6SMauro Carvalho Chehab 
516615cd3feSDavid Härdeman void lirc_init_pdata(struct inode *inode, struct file *file)
517615cd3feSDavid Härdeman {
518615cd3feSDavid Härdeman 	struct irctl *ir = container_of(inode->i_cdev, struct irctl, cdev);
519615cd3feSDavid Härdeman 
520615cd3feSDavid Härdeman 	file->private_data = ir;
521615cd3feSDavid Härdeman }
522615cd3feSDavid Härdeman EXPORT_SYMBOL(lirc_init_pdata);
523615cd3feSDavid Härdeman 
52432cf86f6SMauro Carvalho Chehab void *lirc_get_pdata(struct file *file)
52532cf86f6SMauro Carvalho Chehab {
526615cd3feSDavid Härdeman 	struct irctl *ir = file->private_data;
527615cd3feSDavid Härdeman 
528615cd3feSDavid Härdeman 	return ir->d.data;
52932cf86f6SMauro Carvalho Chehab }
53032cf86f6SMauro Carvalho Chehab EXPORT_SYMBOL(lirc_get_pdata);
53132cf86f6SMauro Carvalho Chehab 
53232cf86f6SMauro Carvalho Chehab 
53332cf86f6SMauro Carvalho Chehab static int __init lirc_dev_init(void)
53432cf86f6SMauro Carvalho Chehab {
53532cf86f6SMauro Carvalho Chehab 	int retval;
53632cf86f6SMauro Carvalho Chehab 
53732cf86f6SMauro Carvalho Chehab 	lirc_class = class_create(THIS_MODULE, "lirc");
53832cf86f6SMauro Carvalho Chehab 	if (IS_ERR(lirc_class)) {
5393fac0314SAndi Shyti 		pr_err("class_create failed\n");
54054fcecafSAndi Shyti 		return PTR_ERR(lirc_class);
54132cf86f6SMauro Carvalho Chehab 	}
54232cf86f6SMauro Carvalho Chehab 
54332cf86f6SMauro Carvalho Chehab 	retval = alloc_chrdev_region(&lirc_base_dev, 0, MAX_IRCTL_DEVICES,
544463015ddSDavid Härdeman 				     "BaseRemoteCtl");
54532cf86f6SMauro Carvalho Chehab 	if (retval) {
54632cf86f6SMauro Carvalho Chehab 		class_destroy(lirc_class);
5473fac0314SAndi Shyti 		pr_err("alloc_chrdev_region failed\n");
54854fcecafSAndi Shyti 		return retval;
54932cf86f6SMauro Carvalho Chehab 	}
55032cf86f6SMauro Carvalho Chehab 
5513fac0314SAndi Shyti 	pr_info("IR Remote Control driver registered, major %d\n",
5523fac0314SAndi Shyti 						MAJOR(lirc_base_dev));
55332cf86f6SMauro Carvalho Chehab 
55454fcecafSAndi Shyti 	return 0;
55532cf86f6SMauro Carvalho Chehab }
55632cf86f6SMauro Carvalho Chehab 
55732cf86f6SMauro Carvalho Chehab static void __exit lirc_dev_exit(void)
55832cf86f6SMauro Carvalho Chehab {
55932cf86f6SMauro Carvalho Chehab 	class_destroy(lirc_class);
56032cf86f6SMauro Carvalho Chehab 	unregister_chrdev_region(lirc_base_dev, MAX_IRCTL_DEVICES);
5613fac0314SAndi Shyti 	pr_info("module unloaded\n");
56232cf86f6SMauro Carvalho Chehab }
56332cf86f6SMauro Carvalho Chehab 
56432cf86f6SMauro Carvalho Chehab module_init(lirc_dev_init);
56532cf86f6SMauro Carvalho Chehab module_exit(lirc_dev_exit);
56632cf86f6SMauro Carvalho Chehab 
56732cf86f6SMauro Carvalho Chehab MODULE_DESCRIPTION("LIRC base driver module");
56832cf86f6SMauro Carvalho Chehab MODULE_AUTHOR("Artur Lipowski");
56932cf86f6SMauro Carvalho Chehab MODULE_LICENSE("GPL");
570