diff options
Diffstat (limited to 'drivers/media/rc/ir-spi.c')
-rw-r--r-- | drivers/media/rc/ir-spi.c | 199 |
1 files changed, 199 insertions, 0 deletions
diff --git a/drivers/media/rc/ir-spi.c b/drivers/media/rc/ir-spi.c new file mode 100644 index 000000000000..c8863f36686a --- /dev/null +++ b/drivers/media/rc/ir-spi.c @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2016 Samsung Electronics Co., Ltd. + * Author: Andi Shyti <andi.shyti@samsung.com> + * + * 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/delay.h> +#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/rc-core.h> + +#define IR_SPI_DRIVER_NAME "ir-spi" + +/* pulse value for different duty cycles */ +#define IR_SPI_PULSE_DC_50 0xff00 +#define IR_SPI_PULSE_DC_60 0xfc00 +#define IR_SPI_PULSE_DC_70 0xf800 +#define IR_SPI_PULSE_DC_75 0xf000 +#define IR_SPI_PULSE_DC_80 0xc000 +#define IR_SPI_PULSE_DC_90 0x8000 + +#define IR_SPI_DEFAULT_FREQUENCY 38000 +#define IR_SPI_BIT_PER_WORD 8 +#define IR_SPI_MAX_BUFSIZE 4096 + +struct ir_spi_data { + u32 freq; + u8 duty_cycle; + bool negated; + + u16 tx_buf[IR_SPI_MAX_BUFSIZE]; + u16 pulse; + u16 space; + + struct rc_dev *rc; + struct spi_device *spi; + struct regulator *regulator; +}; + +static int ir_spi_tx(struct rc_dev *dev, + unsigned int *buffer, unsigned int count) +{ + int i; + int ret; + unsigned int len = 0; + struct ir_spi_data *idata = dev->priv; + struct spi_transfer xfer; + + /* 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; + + if (len + buffer[i] >= IR_SPI_MAX_BUFSIZE) + return -EINVAL; + + /* + * the first value in buffer is a pulse, so that 0, 2, 4, ... + * contain a pulse duration. On the contrary, 1, 3, 5, ... + * contain a space duration. + */ + val = (i % 2) ? idata->space : idata->pulse; + for (j = 0; j < buffer[i]; j++) + idata->tx_buf[len++] = val; + } + + memset(&xfer, 0, sizeof(xfer)); + + xfer.speed_hz = idata->freq; + xfer.len = len * sizeof(*idata->tx_buf); + xfer.tx_buf = idata->tx_buf; + + ret = regulator_enable(idata->regulator); + if (ret) + return ret; + + ret = spi_sync_transfer(idata->spi, &xfer, 1); + if (ret) + dev_err(&idata->spi->dev, "unable to deliver the signal\n"); + + regulator_disable(idata->regulator); + + return ret ? ret : count; +} + +static int ir_spi_set_tx_carrier(struct rc_dev *dev, u32 carrier) +{ + struct ir_spi_data *idata = dev->priv; + + if (!carrier) + return -EINVAL; + + idata->freq = carrier; + + return 0; +} + +static int ir_spi_set_duty_cycle(struct rc_dev *dev, u32 duty_cycle) +{ + struct ir_spi_data *idata = dev->priv; + + if (duty_cycle >= 90) + idata->pulse = IR_SPI_PULSE_DC_90; + else if (duty_cycle >= 80) + idata->pulse = IR_SPI_PULSE_DC_80; + else if (duty_cycle >= 75) + idata->pulse = IR_SPI_PULSE_DC_75; + else if (duty_cycle >= 70) + idata->pulse = IR_SPI_PULSE_DC_70; + else if (duty_cycle >= 60) + idata->pulse = IR_SPI_PULSE_DC_60; + else + idata->pulse = IR_SPI_PULSE_DC_50; + + if (idata->negated) { + idata->pulse = ~idata->pulse; + idata->space = 0xffff; + } else { + idata->space = 0; + } + + return 0; +} + +static int ir_spi_probe(struct spi_device *spi) +{ + int ret; + u8 dc; + 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); + + idata->rc = devm_rc_allocate_device(&spi->dev, RC_DRIVER_IR_RAW_TX); + if (!idata->rc) + return -ENOMEM; + + idata->rc->tx_ir = ir_spi_tx; + idata->rc->s_tx_carrier = ir_spi_set_tx_carrier; + idata->rc->s_tx_duty_cycle = ir_spi_set_duty_cycle; + idata->rc->driver_name = IR_SPI_DRIVER_NAME; + idata->rc->priv = idata; + idata->spi = spi; + + idata->negated = of_property_read_bool(spi->dev.of_node, + "led-active-low"); + ret = of_property_read_u8(spi->dev.of_node, "duty-cycle", &dc); + if (ret) + dc = 50; + + /* ir_spi_set_duty_cycle cannot fail, + * it returns int to be compatible with the + * rc->s_tx_duty_cycle function + */ + ir_spi_set_duty_cycle(idata->rc, dc); + + idata->freq = IR_SPI_DEFAULT_FREQUENCY; + + return devm_rc_register_device(&spi->dev, idata->rc); +} + +static int ir_spi_remove(struct spi_device *spi) +{ + return 0; +} + +static const struct of_device_id ir_spi_of_match[] = { + { .compatible = "ir-spi-led" }, + {}, +}; + +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"); |