File Index
Files and directories
Sign in
FILE

ir-spi.c

File preview with syntax highlighting.

linux-6.19.14/drivers/media/rc/ir-spi.c C
  1// SPDX-License-Identifier: GPL-2.0
  2// SPI driven IR LED device driver
  3//
  4// Copyright (c) 2016 Samsung Electronics Co., Ltd.
  5// Copyright (c) Andi Shyti <andi@etezian.org>
  6
  7#include <linux/bits.h>
  8#include <linux/device.h>
  9#include <linux/err.h>
 10#include <linux/math.h>
 11#include <linux/mod_devicetable.h>
 12#include <linux/module.h>
 13#include <linux/property.h>
 14#include <linux/regulator/consumer.h>
 15#include <linux/spi/spi.h>
 16#include <linux/string.h>
 17#include <linux/types.h>
 18
 19#include <media/rc-core.h>
 20
 21#define IR_SPI_DRIVER_NAME		"ir-spi"
 22
 23#define IR_SPI_DEFAULT_FREQUENCY	38000
 24#define IR_SPI_BITS_PER_PULSE		16
 25
 26struct ir_spi_data {
 27	u32 freq;
 28	bool negated;
 29
 30	u16 pulse;
 31	u16 space;
 32
 33	struct rc_dev *rc;
 34	struct spi_device *spi;
 35	struct regulator *regulator;
 36};
 37
 38static int ir_spi_tx(struct rc_dev *dev, unsigned int *buffer, unsigned int count)
 39{
 40	int i;
 41	int ret;
 42	unsigned int len = 0;
 43	struct ir_spi_data *idata = dev->priv;
 44	struct spi_transfer xfer;
 45	u16 *tx_buf;
 46
 47	/* convert the pulse/space signal to raw binary signal */
 48	for (i = 0; i < count; i++) {
 49		buffer[i] = DIV_ROUND_CLOSEST_ULL((u64)buffer[i] * idata->freq,
 50						  1000000);
 51		len += buffer[i];
 52	}
 53
 54	tx_buf = kmalloc_array(len, sizeof(*tx_buf), GFP_KERNEL);
 55	if (!tx_buf)
 56		return -ENOMEM;
 57
 58	len = 0;
 59	for (i = 0; i < count; i++) {
 60		int j;
 61		u16 val;
 62
 63		/*
 64		 * The first value in buffer is a pulse, so that 0, 2, 4, ...
 65		 * contain a pulse duration. On the contrary, 1, 3, 5, ...
 66		 * contain a space duration.
 67		 */
 68		val = (i % 2) ? idata->space : idata->pulse;
 69		for (j = 0; j < buffer[i]; j++)
 70			tx_buf[len++] = val;
 71	}
 72
 73	memset(&xfer, 0, sizeof(xfer));
 74
 75	xfer.speed_hz = idata->freq * IR_SPI_BITS_PER_PULSE;
 76	xfer.len = len * sizeof(*tx_buf);
 77	xfer.tx_buf = tx_buf;
 78
 79	ret = regulator_enable(idata->regulator);
 80	if (ret)
 81		goto err_free_tx_buf;
 82
 83	ret = spi_sync_transfer(idata->spi, &xfer, 1);
 84	if (ret)
 85		dev_err(&idata->spi->dev, "unable to deliver the signal\n");
 86
 87	regulator_disable(idata->regulator);
 88
 89err_free_tx_buf:
 90
 91	kfree(tx_buf);
 92
 93	return ret ? ret : count;
 94}
 95
 96static int ir_spi_set_tx_carrier(struct rc_dev *dev, u32 carrier)
 97{
 98	struct ir_spi_data *idata = dev->priv;
 99
100	if (!carrier)
101		return -EINVAL;
102
103	if (carrier > idata->spi->max_speed_hz / IR_SPI_BITS_PER_PULSE)
104		return -EINVAL;
105
106	idata->freq = carrier;
107
108	return 0;
109}
110
111static int ir_spi_set_duty_cycle(struct rc_dev *dev, u32 duty_cycle)
112{
113	struct ir_spi_data *idata = dev->priv;
114	int bits = (duty_cycle * 15) / 100;
115
116	idata->pulse = GENMASK(bits, 0);
117
118	if (idata->negated) {
119		idata->pulse = ~idata->pulse;
120		idata->space = 0xffff;
121	} else {
122		idata->space = 0;
123	}
124
125	return 0;
126}
127
128static int ir_spi_probe(struct spi_device *spi)
129{
130	struct device *dev = &spi->dev;
131	int ret;
132	u8 dc;
133	struct ir_spi_data *idata;
134
135	idata = devm_kzalloc(dev, sizeof(*idata), GFP_KERNEL);
136	if (!idata)
137		return -ENOMEM;
138
139	idata->regulator = devm_regulator_get(dev, "irda_regulator");
140	if (IS_ERR(idata->regulator))
141		return PTR_ERR(idata->regulator);
142
143	idata->rc = devm_rc_allocate_device(&spi->dev, RC_DRIVER_IR_RAW_TX);
144	if (!idata->rc)
145		return -ENOMEM;
146
147	idata->rc->tx_ir           = ir_spi_tx;
148	idata->rc->s_tx_carrier    = ir_spi_set_tx_carrier;
149	idata->rc->s_tx_duty_cycle = ir_spi_set_duty_cycle;
150	idata->rc->device_name	   = "IR SPI";
151	idata->rc->driver_name     = IR_SPI_DRIVER_NAME;
152	idata->rc->priv            = idata;
153	idata->spi                 = spi;
154
155	idata->negated = device_property_read_bool(dev, "led-active-low");
156	ret = device_property_read_u8(dev, "duty-cycle", &dc);
157	if (ret)
158		dc = 50;
159
160	/*
161	 * ir_spi_set_duty_cycle() cannot fail, it returns int
162	 * to be compatible with the rc->s_tx_duty_cycle function.
163	 */
164	ir_spi_set_duty_cycle(idata->rc, dc);
165
166	idata->freq = IR_SPI_DEFAULT_FREQUENCY;
167
168	return devm_rc_register_device(dev, idata->rc);
169}
170
171static const struct of_device_id ir_spi_of_match[] = {
172	{ .compatible = "ir-spi-led" },
173	{}
174};
175MODULE_DEVICE_TABLE(of, ir_spi_of_match);
176
177static const struct spi_device_id ir_spi_ids[] = {
178	{ "ir-spi-led" },
179	{}
180};
181MODULE_DEVICE_TABLE(spi, ir_spi_ids);
182
183static struct spi_driver ir_spi_driver = {
184	.probe = ir_spi_probe,
185	.id_table = ir_spi_ids,
186	.driver = {
187		.name = IR_SPI_DRIVER_NAME,
188		.of_match_table = ir_spi_of_match,
189	},
190};
191module_spi_driver(ir_spi_driver);
192
193MODULE_AUTHOR("Andi Shyti <andi@etezian.org>");
194MODULE_DESCRIPTION("SPI IR LED");
195MODULE_LICENSE("GPL v2");
~/ Read-only file preview.