diff options
Diffstat (limited to 'drivers/media')
-rw-r--r-- | drivers/media/rc/Kconfig | 9 | ||||
-rw-r--r-- | drivers/media/rc/Makefile | 1 | ||||
-rw-r--r-- | drivers/media/rc/ir-spi.c | 301 |
3 files changed, 311 insertions, 0 deletions
diff --git a/drivers/media/rc/Kconfig b/drivers/media/rc/Kconfig index ddfab256b9a5..f72c48db6f3d 100644 --- a/drivers/media/rc/Kconfig +++ b/drivers/media/rc/Kconfig @@ -260,6 +260,15 @@ config IR_REDRAT3 To compile this driver as a module, choose M here: the module will be called redrat3. +config IR_SPI + tristate "SPI connected IR LED" + depends on SPI && LIRC + ---help--- + Say Y if you want to use an IR LED connected through SPI bus. + + To compile this driver as a module, choose M here: the module will be + called ir-spi. + config IR_STREAMZAP tristate "Streamzap PC Remote IR Receiver" depends on USB_ARCH_HAS_HCD diff --git a/drivers/media/rc/Makefile b/drivers/media/rc/Makefile index 379a5c0f1379..1417c8ddb7da 100644 --- a/drivers/media/rc/Makefile +++ b/drivers/media/rc/Makefile @@ -27,6 +27,7 @@ obj-$(CONFIG_IR_NUVOTON) += nuvoton-cir.o obj-$(CONFIG_IR_ENE) += ene_ir.o obj-$(CONFIG_IR_REDRAT3) += redrat3.o obj-$(CONFIG_IR_RX51) += ir-rx51.o +obj-$(CONFIG_IR_SPI) += ir-spi.o obj-$(CONFIG_IR_STREAMZAP) += streamzap.o obj-$(CONFIG_IR_WINBOND_CIR) += winbond-cir.o obj-$(CONFIG_RC_LOOPBACK) += rc-loopback.o diff --git a/drivers/media/rc/ir-spi.c b/drivers/media/rc/ir-spi.c new file mode 100644 index 000000000000..ee30b56b7414 --- /dev/null +++ b/drivers/media/rc/ir-spi.c @@ -0,0 +1,301 @@ +/* + * Copyright (c) 2016 Samsung Electronics Co., Ltd. + * Author: Andi Shyti <andi.shyti@samsung.it> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * SPI driven IR LED device driver + */ + +#include <linux/fs.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of_gpio.h> +#include <linux/regulator/consumer.h> +#include <linux/spi/spi.h> +#include <media/lirc_dev.h> + +#define IR_SPI_DRIVER_NAME "ir-spi" + +#define IR_SPI_DEFAULT_FREQUENCY 38000 +#define IR_SPI_BIT_PER_WORD 8 + +struct ir_spi_data { + u16 nusers; + int power_gpio; + + u8 *buffer; + + struct lirc_driver lirc_driver; + struct spi_device *spi; + struct spi_transfer xfer; + struct mutex mutex; + struct regulator *regulator; +}; + +static ssize_t ir_spi_chardev_write(struct file *file, + const char __user *buffer, + size_t length, loff_t *offset) +{ + struct ir_spi_data *idata = file->private_data; + bool please_free = false; + int ret = 0; + + if (idata->xfer.len && (idata->xfer.len != length)) + return -EINVAL; + + mutex_lock(&idata->mutex); + + if (!idata->xfer.len) { + idata->buffer = kmalloc(length, GFP_KERNEL); + + if (!idata->buffer) { + ret = -ENOMEM; + goto out_unlock; + } + + idata->xfer.len = length; + please_free = true; + } + + if (copy_from_user(idata->buffer, buffer, length)) { + ret = -EFAULT; + goto out_free; + } + + ret = regulator_enable(idata->regulator); + if (ret) { + dev_err(&idata->spi->dev, "failed to power on the LED\n"); + goto out_free; + } + + idata->xfer.tx_buf = idata->buffer; + + ret = spi_sync_transfer(idata->spi, &idata->xfer, 1); + if (ret) + dev_err(&idata->spi->dev, "unable to deliver the signal\n"); + + regulator_disable(idata->regulator); + +out_free: + if (please_free) { + kfree(idata->buffer); + idata->xfer.len = 0; + idata->buffer = NULL; + } + +out_unlock: + mutex_unlock(&idata->mutex); + + return ret ? ret : length; +} + +static int ir_spi_chardev_open(struct inode *inode, struct file *file) +{ + struct ir_spi_data *idata = lirc_get_pdata(file); + + if (unlikely(idata->nusers >= SHRT_MAX)) { + dev_err(&idata->spi->dev, "device busy\n"); + return -EBUSY; + } + + file->private_data = idata; + + mutex_lock(&idata->mutex); + idata->nusers++; + mutex_unlock(&idata->mutex); + + return 0; +} + +static int ir_spi_chardev_close(struct inode *inode, struct file *file) +{ + struct ir_spi_data *idata = lirc_get_pdata(file); + + mutex_lock(&idata->mutex); + idata->nusers--; + + /* + * check if someone else is using the driver, + * if not, then: + * + * - reset length and frequency values to default + * - shut down the LED + * - free the buffer (NULL or ZERO_SIZE_PTR are noop) + */ + if (!idata->nusers) { + idata->xfer.len = 0; + idata->xfer.speed_hz = IR_SPI_DEFAULT_FREQUENCY; + + kfree(idata->buffer); + idata->buffer = NULL; + } + + mutex_unlock(&idata->mutex); + + return 0; +} + +static long ir_spi_chardev_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + __u32 p; + s32 ret; + struct ir_spi_data *idata = file->private_data; + + switch (cmd) { + case LIRC_GET_FEATURES: + return put_user(idata->lirc_driver.features, + (__u32 __user *) arg); + + case LIRC_GET_LENGTH: + return put_user(idata->xfer.len, (__u32 __user *) arg); + + case LIRC_SET_LENGTH: { + void *new; + + ret = get_user(p, (__u32 __user *) arg); + if (ret) + return ret; + + /* + * the user is trying to set the same + * length of the current value + */ + if (idata->xfer.len == p) + return 0; + + /* + * multiple users should use the driver with the + * length, otherwise return EPERM same data + */ + if (idata->nusers > 1) + return -EPERM; + + /* + * if the buffer is already allocated, reallocate it with the + * desired value. If the desired value is 0, then the buffer is + * freed from krealloc() + */ + if (idata->xfer.len) + new = krealloc(idata->buffer, p, GFP_KERNEL); + else + new = kmalloc(p, GFP_KERNEL); + + if (!new) + return -ENOMEM; + + mutex_lock(&idata->mutex); + idata->buffer = new; + idata->xfer.len = p; + mutex_unlock(&idata->mutex); + + return 0; + } + + case LIRC_GET_FREQUENCY: + return put_user(idata->xfer.speed_hz, (__u32 __user *) arg); + + case LIRC_SET_FREQUENCY: + ret = get_user(p, (__u32 __user *) arg); + if (ret) + return ret; + + /* + * The frequency cannot be obviously set to '0', + * while, as in the case of the data length, + * multiple users should use the driver with the same + * frequency value, otherwise return EPERM + */ + if (!p || ((idata->nusers > 1) && p != idata->xfer.speed_hz)) + return -EPERM; + + mutex_lock(&idata->mutex); + idata->xfer.speed_hz = p; + mutex_unlock(&idata->mutex); + return 0; + } + + return -EINVAL; +} + +static const struct file_operations ir_spi_fops = { + .owner = THIS_MODULE, + .read = lirc_dev_fop_read, + .write = ir_spi_chardev_write, + .poll = lirc_dev_fop_poll, + .open = ir_spi_chardev_open, + .release = ir_spi_chardev_close, + .llseek = noop_llseek, + .unlocked_ioctl = ir_spi_chardev_ioctl, + .compat_ioctl = ir_spi_chardev_ioctl, +}; + +static int ir_spi_probe(struct spi_device *spi) +{ + struct ir_spi_data *idata; + + idata = devm_kzalloc(&spi->dev, sizeof(*idata), GFP_KERNEL); + if (!idata) + return -ENOMEM; + + idata->regulator = devm_regulator_get(&spi->dev, "irda_regulator"); + if (IS_ERR(idata->regulator)) + return PTR_ERR(idata->regulator); + + snprintf(idata->lirc_driver.name, sizeof(idata->lirc_driver.name), + IR_SPI_DRIVER_NAME); + idata->lirc_driver.features = LIRC_CAN_SEND_RAW; + idata->lirc_driver.code_length = 1; + idata->lirc_driver.fops = &ir_spi_fops; + idata->lirc_driver.dev = &spi->dev; + idata->lirc_driver.data = idata; + idata->lirc_driver.owner = THIS_MODULE; + idata->lirc_driver.minor = -1; + + idata->lirc_driver.minor = lirc_register_driver(&idata->lirc_driver); + if (idata->lirc_driver.minor < 0) { + dev_err(&spi->dev, "unable to generate character device\n"); + return idata->lirc_driver.minor; + } + + mutex_init(&idata->mutex); + + idata->spi = spi; + + idata->xfer.bits_per_word = IR_SPI_BIT_PER_WORD; + idata->xfer.speed_hz = IR_SPI_DEFAULT_FREQUENCY; + + return 0; +} + +static int ir_spi_remove(struct spi_device *spi) +{ + struct ir_spi_data *idata = spi_get_drvdata(spi); + + lirc_unregister_driver(idata->lirc_driver.minor); + + return 0; +} + +static const struct of_device_id ir_spi_of_match[] = { + { .compatible = "ir-spi" }, + {}, +}; + +static struct spi_driver ir_spi_driver = { + .probe = ir_spi_probe, + .remove = ir_spi_remove, + .driver = { + .name = IR_SPI_DRIVER_NAME, + .of_match_table = ir_spi_of_match, + }, +}; + +module_spi_driver(ir_spi_driver); + +MODULE_AUTHOR("Andi Shyti <andi.shyti@samsung.com>"); +MODULE_DESCRIPTION("SPI IR LED"); +MODULE_LICENSE("GPL v2"); |