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