Contributors: 7
Author Tokens Token Proportion Commits Commit Proportion
Andi Shyti 653 78.39% 3 20.00%
Cosmin Tanislav 78 9.36% 3 20.00%
Andy Shevchenko 33 3.96% 4 26.67%
Sean Young 24 2.88% 2 13.33%
Wei Yongjun 23 2.76% 1 6.67%
Anton Blanchard 13 1.56% 1 6.67%
Daniel Gomez 9 1.08% 1 6.67%
Total 833 15


// SPDX-License-Identifier: GPL-2.0
// SPI driven IR LED device driver
//
// Copyright (c) 2016 Samsung Electronics Co., Ltd.
// Copyright (c) Andi Shyti <andi@etezian.org>

#include <linux/bits.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/math.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/property.h>
#include <linux/regulator/consumer.h>
#include <linux/spi/spi.h>
#include <linux/string.h>
#include <linux/types.h>

#include <media/rc-core.h>

#define IR_SPI_DRIVER_NAME		"ir-spi"

#define IR_SPI_DEFAULT_FREQUENCY	38000
#define IR_SPI_BITS_PER_PULSE		16

struct ir_spi_data {
	u32 freq;
	bool negated;

	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;
	u16 *tx_buf;

	/* convert the pulse/space signal to raw binary signal */
	for (i = 0; i < count; i++) {
		buffer[i] = DIV_ROUND_CLOSEST_ULL((u64)buffer[i] * idata->freq,
						  1000000);
		len += buffer[i];
	}

	tx_buf = kmalloc_array(len, sizeof(*tx_buf), GFP_KERNEL);
	if (!tx_buf)
		return -ENOMEM;

	len = 0;
	for (i = 0; i < count; i++) {
		int j;
		u16 val;

		/*
		 * 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++)
			tx_buf[len++] = val;
	}

	memset(&xfer, 0, sizeof(xfer));

	xfer.speed_hz = idata->freq * IR_SPI_BITS_PER_PULSE;
	xfer.len = len * sizeof(*tx_buf);
	xfer.tx_buf = tx_buf;

	ret = regulator_enable(idata->regulator);
	if (ret)
		goto err_free_tx_buf;

	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);

err_free_tx_buf:

	kfree(tx_buf);

	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;

	if (carrier > idata->spi->max_speed_hz / IR_SPI_BITS_PER_PULSE)
		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;
	int bits = (duty_cycle * 15) / 100;

	idata->pulse = GENMASK(bits, 0);

	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)
{
	struct device *dev = &spi->dev;
	int ret;
	u8 dc;
	struct ir_spi_data *idata;

	idata = devm_kzalloc(dev, sizeof(*idata), GFP_KERNEL);
	if (!idata)
		return -ENOMEM;

	idata->regulator = devm_regulator_get(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->device_name	   = "IR SPI";
	idata->rc->driver_name     = IR_SPI_DRIVER_NAME;
	idata->rc->priv            = idata;
	idata->spi                 = spi;

	idata->negated = device_property_read_bool(dev, "led-active-low");
	ret = device_property_read_u8(dev, "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(dev, idata->rc);
}

static const struct of_device_id ir_spi_of_match[] = {
	{ .compatible = "ir-spi-led" },
	{}
};
MODULE_DEVICE_TABLE(of, ir_spi_of_match);

static const struct spi_device_id ir_spi_ids[] = {
	{ "ir-spi-led" },
	{}
};
MODULE_DEVICE_TABLE(spi, ir_spi_ids);

static struct spi_driver ir_spi_driver = {
	.probe = ir_spi_probe,
	.id_table = ir_spi_ids,
	.driver = {
		.name = IR_SPI_DRIVER_NAME,
		.of_match_table = ir_spi_of_match,
	},
};
module_spi_driver(ir_spi_driver);

MODULE_AUTHOR("Andi Shyti <andi@etezian.org>");
MODULE_DESCRIPTION("SPI IR LED");
MODULE_LICENSE("GPL v2");