1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * meson-ir-tx.c - Amlogic Meson IR TX driver |
4 | * |
5 | * Copyright (c) 2021, SberDevices. All Rights Reserved. |
6 | * |
7 | * Author: Viktor Prutyanov <viktor.prutyanov@phystech.edu> |
8 | */ |
9 | |
10 | #include <linux/device.h> |
11 | #include <linux/module.h> |
12 | #include <linux/sched.h> |
13 | #include <linux/platform_device.h> |
14 | #include <linux/of.h> |
15 | #include <linux/interrupt.h> |
16 | #include <linux/spinlock.h> |
17 | #include <linux/of_irq.h> |
18 | #include <linux/clk.h> |
19 | #include <linux/slab.h> |
20 | #include <media/rc-core.h> |
21 | |
22 | #define DEVICE_NAME "Meson IR TX" |
23 | #define DRIVER_NAME "meson-ir-tx" |
24 | |
25 | #define MIRTX_DEFAULT_CARRIER 38000 |
26 | #define MIRTX_DEFAULT_DUTY_CYCLE 50 |
27 | #define MIRTX_FIFO_THD 32 |
28 | |
29 | #define IRB_MOD_1US_CLK_RATE 1000000 |
30 | |
31 | #define IRB_FIFO_LEN 128 |
32 | |
33 | #define IRB_ADDR0 0x0 |
34 | #define IRB_ADDR1 0x4 |
35 | #define IRB_ADDR2 0x8 |
36 | #define IRB_ADDR3 0xc |
37 | |
38 | #define IRB_MAX_DELAY (1 << 10) |
39 | #define IRB_DELAY_MASK (IRB_MAX_DELAY - 1) |
40 | |
41 | /* IRCTRL_IR_BLASTER_ADDR0 */ |
42 | #define IRB_MOD_CLK(x) ((x) << 12) |
43 | #define IRB_MOD_SYS_CLK 0 |
44 | #define IRB_MOD_XTAL3_CLK 1 |
45 | #define IRB_MOD_1US_CLK 2 |
46 | #define IRB_MOD_10US_CLK 3 |
47 | #define IRB_INIT_HIGH BIT(2) |
48 | #define IRB_ENABLE BIT(0) |
49 | |
50 | /* IRCTRL_IR_BLASTER_ADDR2 */ |
51 | #define IRB_MOD_COUNT(lo, hi) ((((lo) - 1) << 16) | ((hi) - 1)) |
52 | |
53 | /* IRCTRL_IR_BLASTER_ADDR2 */ |
54 | #define IRB_WRITE_FIFO BIT(16) |
55 | #define IRB_MOD_ENABLE BIT(12) |
56 | #define IRB_TB_1US (0x0 << 10) |
57 | #define IRB_TB_10US (0x1 << 10) |
58 | #define IRB_TB_100US (0x2 << 10) |
59 | #define IRB_TB_MOD_CLK (0x3 << 10) |
60 | |
61 | /* IRCTRL_IR_BLASTER_ADDR3 */ |
62 | #define IRB_FIFO_THD_PENDING BIT(16) |
63 | #define IRB_FIFO_IRQ_ENABLE BIT(8) |
64 | |
65 | struct meson_irtx { |
66 | struct device *dev; |
67 | void __iomem *reg_base; |
68 | u32 *buf; |
69 | unsigned int buf_len; |
70 | unsigned int buf_head; |
71 | unsigned int carrier; |
72 | unsigned int duty_cycle; |
73 | /* Locks buf */ |
74 | spinlock_t lock; |
75 | struct completion completion; |
76 | unsigned long clk_rate; |
77 | }; |
78 | |
79 | static void meson_irtx_set_mod(struct meson_irtx *ir) |
80 | { |
81 | unsigned int cnt = DIV_ROUND_CLOSEST(ir->clk_rate, ir->carrier); |
82 | unsigned int pulse_cnt = DIV_ROUND_CLOSEST(cnt * ir->duty_cycle, 100); |
83 | unsigned int space_cnt = cnt - pulse_cnt; |
84 | |
85 | dev_dbg(ir->dev, "F_mod = %uHz, T_mod = %luns, duty_cycle = %u%%\n" , |
86 | ir->carrier, NSEC_PER_SEC / ir->clk_rate * cnt, |
87 | 100 * pulse_cnt / cnt); |
88 | |
89 | writel(IRB_MOD_COUNT(pulse_cnt, space_cnt), |
90 | addr: ir->reg_base + IRB_ADDR1); |
91 | } |
92 | |
93 | static void meson_irtx_setup(struct meson_irtx *ir, unsigned int clk_nr) |
94 | { |
95 | /* |
96 | * Disable the TX, set modulator clock tick and set initialize |
97 | * output to be high. Set up carrier frequency and duty cycle. Then |
98 | * unset initialize output. Enable FIFO interrupt, set FIFO interrupt |
99 | * threshold. Finally, enable the transmitter back. |
100 | */ |
101 | writel(val: ~IRB_ENABLE & (IRB_MOD_CLK(clk_nr) | IRB_INIT_HIGH), |
102 | addr: ir->reg_base + IRB_ADDR0); |
103 | meson_irtx_set_mod(ir); |
104 | writel(readl(addr: ir->reg_base + IRB_ADDR0) & ~IRB_INIT_HIGH, |
105 | addr: ir->reg_base + IRB_ADDR0); |
106 | writel(IRB_FIFO_IRQ_ENABLE | MIRTX_FIFO_THD, |
107 | addr: ir->reg_base + IRB_ADDR3); |
108 | writel(readl(addr: ir->reg_base + IRB_ADDR0) | IRB_ENABLE, |
109 | addr: ir->reg_base + IRB_ADDR0); |
110 | } |
111 | |
112 | static u32 meson_irtx_prepare_pulse(struct meson_irtx *ir, unsigned int time) |
113 | { |
114 | unsigned int delay; |
115 | unsigned int tb = IRB_TB_MOD_CLK; |
116 | unsigned int tb_us = DIV_ROUND_CLOSEST(USEC_PER_SEC, ir->carrier); |
117 | |
118 | delay = (DIV_ROUND_CLOSEST(time, tb_us) - 1) & IRB_DELAY_MASK; |
119 | |
120 | return ((IRB_WRITE_FIFO | IRB_MOD_ENABLE) | tb | delay); |
121 | } |
122 | |
123 | static u32 meson_irtx_prepare_space(struct meson_irtx *ir, unsigned int time) |
124 | { |
125 | unsigned int delay; |
126 | unsigned int tb = IRB_TB_100US; |
127 | unsigned int tb_us = 100; |
128 | |
129 | if (time <= IRB_MAX_DELAY) { |
130 | tb = IRB_TB_1US; |
131 | tb_us = 1; |
132 | } else if (time <= 10 * IRB_MAX_DELAY) { |
133 | tb = IRB_TB_10US; |
134 | tb_us = 10; |
135 | } else if (time <= 100 * IRB_MAX_DELAY) { |
136 | tb = IRB_TB_100US; |
137 | tb_us = 100; |
138 | } |
139 | |
140 | delay = (DIV_ROUND_CLOSEST(time, tb_us) - 1) & IRB_DELAY_MASK; |
141 | |
142 | return ((IRB_WRITE_FIFO & ~IRB_MOD_ENABLE) | tb | delay); |
143 | } |
144 | |
145 | static void meson_irtx_send_buffer(struct meson_irtx *ir) |
146 | { |
147 | unsigned int nr = 0; |
148 | unsigned int max_fifo_level = IRB_FIFO_LEN - MIRTX_FIFO_THD; |
149 | |
150 | while (ir->buf_head < ir->buf_len && nr < max_fifo_level) { |
151 | writel(val: ir->buf[ir->buf_head], addr: ir->reg_base + IRB_ADDR2); |
152 | |
153 | ir->buf_head++; |
154 | nr++; |
155 | } |
156 | } |
157 | |
158 | static bool meson_irtx_check_buf(struct meson_irtx *ir, |
159 | unsigned int *buf, unsigned int len) |
160 | { |
161 | unsigned int i; |
162 | |
163 | for (i = 0; i < len; i++) { |
164 | unsigned int max_tb_us; |
165 | /* |
166 | * Max space timebase is 100 us. |
167 | * Pulse timebase equals to carrier period. |
168 | */ |
169 | if (i % 2 == 0) |
170 | max_tb_us = USEC_PER_SEC / ir->carrier; |
171 | else |
172 | max_tb_us = 100; |
173 | |
174 | if (buf[i] >= max_tb_us * IRB_MAX_DELAY) |
175 | return false; |
176 | } |
177 | |
178 | return true; |
179 | } |
180 | |
181 | static void meson_irtx_fill_buf(struct meson_irtx *ir, u32 *dst_buf, |
182 | unsigned int *src_buf, unsigned int len) |
183 | { |
184 | unsigned int i; |
185 | |
186 | for (i = 0; i < len; i++) { |
187 | if (i % 2 == 0) |
188 | dst_buf[i] = meson_irtx_prepare_pulse(ir, time: src_buf[i]); |
189 | else |
190 | dst_buf[i] = meson_irtx_prepare_space(ir, time: src_buf[i]); |
191 | } |
192 | } |
193 | |
194 | static irqreturn_t meson_irtx_irqhandler(int irq, void *data) |
195 | { |
196 | unsigned long flags; |
197 | struct meson_irtx *ir = data; |
198 | |
199 | writel(readl(addr: ir->reg_base + IRB_ADDR3) & ~IRB_FIFO_THD_PENDING, |
200 | addr: ir->reg_base + IRB_ADDR3); |
201 | |
202 | if (completion_done(x: &ir->completion)) |
203 | return IRQ_HANDLED; |
204 | |
205 | spin_lock_irqsave(&ir->lock, flags); |
206 | if (ir->buf_head < ir->buf_len) |
207 | meson_irtx_send_buffer(ir); |
208 | else |
209 | complete(&ir->completion); |
210 | spin_unlock_irqrestore(lock: &ir->lock, flags); |
211 | |
212 | return IRQ_HANDLED; |
213 | } |
214 | |
215 | static int meson_irtx_set_carrier(struct rc_dev *rc, u32 carrier) |
216 | { |
217 | struct meson_irtx *ir = rc->priv; |
218 | |
219 | if (carrier == 0) |
220 | return -EINVAL; |
221 | |
222 | ir->carrier = carrier; |
223 | meson_irtx_set_mod(ir); |
224 | |
225 | return 0; |
226 | } |
227 | |
228 | static int meson_irtx_set_duty_cycle(struct rc_dev *rc, u32 duty_cycle) |
229 | { |
230 | struct meson_irtx *ir = rc->priv; |
231 | |
232 | ir->duty_cycle = duty_cycle; |
233 | meson_irtx_set_mod(ir); |
234 | |
235 | return 0; |
236 | } |
237 | |
238 | static void meson_irtx_update_buf(struct meson_irtx *ir, u32 *buf, |
239 | unsigned int len, unsigned int head) |
240 | { |
241 | ir->buf = buf; |
242 | ir->buf_len = len; |
243 | ir->buf_head = head; |
244 | } |
245 | |
246 | static int meson_irtx_transmit(struct rc_dev *rc, unsigned int *buf, |
247 | unsigned int len) |
248 | { |
249 | unsigned long flags; |
250 | struct meson_irtx *ir = rc->priv; |
251 | u32 *tx_buf; |
252 | int ret = len; |
253 | |
254 | if (!meson_irtx_check_buf(ir, buf, len)) |
255 | return -EINVAL; |
256 | |
257 | tx_buf = kmalloc_array(n: len, size: sizeof(u32), GFP_KERNEL); |
258 | if (!tx_buf) |
259 | return -ENOMEM; |
260 | |
261 | meson_irtx_fill_buf(ir, dst_buf: tx_buf, src_buf: buf, len); |
262 | dev_dbg(ir->dev, "TX buffer filled, length = %u\n" , len); |
263 | |
264 | spin_lock_irqsave(&ir->lock, flags); |
265 | meson_irtx_update_buf(ir, buf: tx_buf, len, head: 0); |
266 | reinit_completion(x: &ir->completion); |
267 | meson_irtx_send_buffer(ir); |
268 | spin_unlock_irqrestore(lock: &ir->lock, flags); |
269 | |
270 | if (!wait_for_completion_timeout(x: &ir->completion, |
271 | timeout: usecs_to_jiffies(IR_MAX_DURATION))) |
272 | ret = -ETIMEDOUT; |
273 | |
274 | spin_lock_irqsave(&ir->lock, flags); |
275 | kfree(objp: ir->buf); |
276 | meson_irtx_update_buf(ir, NULL, len: 0, head: 0); |
277 | spin_unlock_irqrestore(lock: &ir->lock, flags); |
278 | |
279 | return ret; |
280 | } |
281 | |
282 | static int meson_irtx_mod_clock_probe(struct meson_irtx *ir, |
283 | unsigned int *clk_nr) |
284 | { |
285 | struct device_node *np = ir->dev->of_node; |
286 | struct clk *clock; |
287 | |
288 | if (!np) |
289 | return -ENODEV; |
290 | |
291 | clock = devm_clk_get(dev: ir->dev, id: "xtal" ); |
292 | if (IS_ERR(ptr: clock) || clk_prepare_enable(clk: clock)) |
293 | return -ENODEV; |
294 | |
295 | *clk_nr = IRB_MOD_XTAL3_CLK; |
296 | ir->clk_rate = clk_get_rate(clk: clock) / 3; |
297 | |
298 | if (ir->clk_rate < IRB_MOD_1US_CLK_RATE) { |
299 | *clk_nr = IRB_MOD_1US_CLK; |
300 | ir->clk_rate = IRB_MOD_1US_CLK_RATE; |
301 | } |
302 | |
303 | dev_info(ir->dev, "F_clk = %luHz\n" , ir->clk_rate); |
304 | |
305 | return 0; |
306 | } |
307 | |
308 | static int __init meson_irtx_probe(struct platform_device *pdev) |
309 | { |
310 | struct device *dev = &pdev->dev; |
311 | struct meson_irtx *ir; |
312 | struct rc_dev *rc; |
313 | int irq; |
314 | unsigned int clk_nr; |
315 | int ret; |
316 | |
317 | ir = devm_kzalloc(dev, size: sizeof(*ir), GFP_KERNEL); |
318 | if (!ir) |
319 | return -ENOMEM; |
320 | |
321 | ir->reg_base = devm_platform_ioremap_resource(pdev, index: 0); |
322 | if (IS_ERR(ptr: ir->reg_base)) |
323 | return PTR_ERR(ptr: ir->reg_base); |
324 | |
325 | irq = platform_get_irq(pdev, 0); |
326 | if (irq < 0) |
327 | return -ENODEV; |
328 | |
329 | ir->dev = dev; |
330 | ir->carrier = MIRTX_DEFAULT_CARRIER; |
331 | ir->duty_cycle = MIRTX_DEFAULT_DUTY_CYCLE; |
332 | init_completion(x: &ir->completion); |
333 | spin_lock_init(&ir->lock); |
334 | |
335 | ret = meson_irtx_mod_clock_probe(ir, clk_nr: &clk_nr); |
336 | if (ret) { |
337 | dev_err(dev, "modulator clock setup failed\n" ); |
338 | return ret; |
339 | } |
340 | meson_irtx_setup(ir, clk_nr); |
341 | |
342 | ret = devm_request_irq(dev, irq, |
343 | handler: meson_irtx_irqhandler, |
344 | IRQF_TRIGGER_RISING, |
345 | DRIVER_NAME, dev_id: ir); |
346 | if (ret) { |
347 | dev_err(dev, "irq request failed\n" ); |
348 | return ret; |
349 | } |
350 | |
351 | rc = rc_allocate_device(RC_DRIVER_IR_RAW_TX); |
352 | if (!rc) |
353 | return -ENOMEM; |
354 | |
355 | rc->driver_name = DRIVER_NAME; |
356 | rc->device_name = DEVICE_NAME; |
357 | rc->priv = ir; |
358 | |
359 | rc->tx_ir = meson_irtx_transmit; |
360 | rc->s_tx_carrier = meson_irtx_set_carrier; |
361 | rc->s_tx_duty_cycle = meson_irtx_set_duty_cycle; |
362 | |
363 | ret = rc_register_device(dev: rc); |
364 | if (ret < 0) { |
365 | dev_err(dev, "rc_dev registration failed\n" ); |
366 | rc_free_device(dev: rc); |
367 | return ret; |
368 | } |
369 | |
370 | platform_set_drvdata(pdev, data: rc); |
371 | |
372 | return 0; |
373 | } |
374 | |
375 | static void meson_irtx_remove(struct platform_device *pdev) |
376 | { |
377 | struct rc_dev *rc = platform_get_drvdata(pdev); |
378 | |
379 | rc_unregister_device(dev: rc); |
380 | } |
381 | |
382 | static const struct of_device_id meson_irtx_dt_match[] = { |
383 | { |
384 | .compatible = "amlogic,meson-g12a-ir-tx" , |
385 | }, |
386 | {}, |
387 | }; |
388 | MODULE_DEVICE_TABLE(of, meson_irtx_dt_match); |
389 | |
390 | static struct platform_driver meson_irtx_pd = { |
391 | .remove_new = meson_irtx_remove, |
392 | .driver = { |
393 | .name = DRIVER_NAME, |
394 | .of_match_table = meson_irtx_dt_match, |
395 | }, |
396 | }; |
397 | |
398 | module_platform_driver_probe(meson_irtx_pd, meson_irtx_probe); |
399 | |
400 | MODULE_DESCRIPTION("Meson IR TX driver" ); |
401 | MODULE_AUTHOR("Viktor Prutyanov <viktor.prutyanov@phystech.edu>" ); |
402 | MODULE_LICENSE("GPL" ); |
403 | |