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