1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Driver for Allwinner sunXi IR controller |
4 | * |
5 | * Copyright (C) 2014 Alexsey Shestacov <wingrime@linux-sunxi.org> |
6 | * Copyright (C) 2014 Alexander Bersenev <bay@hackerdom.ru> |
7 | * |
8 | * Based on sun5i-ir.c: |
9 | * Copyright (C) 2007-2012 Daniel Wang |
10 | * Allwinner Technology Co., Ltd. <www.allwinnertech.com> |
11 | */ |
12 | |
13 | #include <linux/clk.h> |
14 | #include <linux/interrupt.h> |
15 | #include <linux/module.h> |
16 | #include <linux/of.h> |
17 | #include <linux/platform_device.h> |
18 | #include <linux/reset.h> |
19 | #include <media/rc-core.h> |
20 | |
21 | #define SUNXI_IR_DEV "sunxi-ir" |
22 | |
23 | /* Registers */ |
24 | /* IR Control */ |
25 | #define SUNXI_IR_CTL_REG 0x00 |
26 | /* Global Enable */ |
27 | #define REG_CTL_GEN BIT(0) |
28 | /* RX block enable */ |
29 | #define REG_CTL_RXEN BIT(1) |
30 | /* CIR mode */ |
31 | #define REG_CTL_MD (BIT(4) | BIT(5)) |
32 | |
33 | /* Rx Config */ |
34 | #define SUNXI_IR_RXCTL_REG 0x10 |
35 | /* Pulse Polarity Invert flag */ |
36 | #define REG_RXCTL_RPPI BIT(2) |
37 | |
38 | /* Rx Data */ |
39 | #define SUNXI_IR_RXFIFO_REG 0x20 |
40 | |
41 | /* Rx Interrupt Enable */ |
42 | #define SUNXI_IR_RXINT_REG 0x2C |
43 | /* Rx FIFO Overflow Interrupt Enable */ |
44 | #define REG_RXINT_ROI_EN BIT(0) |
45 | /* Rx Packet End Interrupt Enable */ |
46 | #define REG_RXINT_RPEI_EN BIT(1) |
47 | /* Rx FIFO Data Available Interrupt Enable */ |
48 | #define REG_RXINT_RAI_EN BIT(4) |
49 | |
50 | /* Rx FIFO available byte level */ |
51 | #define REG_RXINT_RAL(val) ((val) << 8) |
52 | |
53 | /* Rx Interrupt Status */ |
54 | #define SUNXI_IR_RXSTA_REG 0x30 |
55 | /* Rx FIFO Overflow */ |
56 | #define REG_RXSTA_ROI REG_RXINT_ROI_EN |
57 | /* Rx Packet End */ |
58 | #define REG_RXSTA_RPE REG_RXINT_RPEI_EN |
59 | /* Rx FIFO Data Available */ |
60 | #define REG_RXSTA_RA REG_RXINT_RAI_EN |
61 | /* RX FIFO Get Available Counter */ |
62 | #define REG_RXSTA_GET_AC(val) (((val) >> 8) & (ir->fifo_size * 2 - 1)) |
63 | /* Clear all interrupt status value */ |
64 | #define REG_RXSTA_CLEARALL 0xff |
65 | |
66 | /* IR Sample Config */ |
67 | #define SUNXI_IR_CIR_REG 0x34 |
68 | /* CIR_REG register noise threshold */ |
69 | #define REG_CIR_NTHR(val) (((val) << 2) & (GENMASK(7, 2))) |
70 | /* CIR_REG register idle threshold */ |
71 | #define REG_CIR_ITHR(val) (((val) << 8) & (GENMASK(15, 8))) |
72 | |
73 | /* Required frequency for IR0 or IR1 clock in CIR mode (default) */ |
74 | #define SUNXI_IR_BASE_CLK 8000000 |
75 | /* Noise threshold in samples */ |
76 | #define SUNXI_IR_RXNOISE 1 |
77 | |
78 | /** |
79 | * struct sunxi_ir_quirks - Differences between SoC variants. |
80 | * |
81 | * @has_reset: SoC needs reset deasserted. |
82 | * @fifo_size: size of the fifo. |
83 | */ |
84 | struct sunxi_ir_quirks { |
85 | bool has_reset; |
86 | int fifo_size; |
87 | }; |
88 | |
89 | struct sunxi_ir { |
90 | struct rc_dev *rc; |
91 | void __iomem *base; |
92 | int irq; |
93 | int fifo_size; |
94 | struct clk *clk; |
95 | struct clk *apb_clk; |
96 | struct reset_control *rst; |
97 | const char *map_name; |
98 | }; |
99 | |
100 | static irqreturn_t sunxi_ir_irq(int irqno, void *dev_id) |
101 | { |
102 | unsigned long status; |
103 | unsigned char dt; |
104 | unsigned int cnt, rc; |
105 | struct sunxi_ir *ir = dev_id; |
106 | struct ir_raw_event rawir = {}; |
107 | |
108 | status = readl(addr: ir->base + SUNXI_IR_RXSTA_REG); |
109 | |
110 | /* clean all pending statuses */ |
111 | writel(val: status | REG_RXSTA_CLEARALL, addr: ir->base + SUNXI_IR_RXSTA_REG); |
112 | |
113 | if (status & (REG_RXSTA_RA | REG_RXSTA_RPE)) { |
114 | /* How many messages in fifo */ |
115 | rc = REG_RXSTA_GET_AC(status); |
116 | /* Sanity check */ |
117 | rc = rc > ir->fifo_size ? ir->fifo_size : rc; |
118 | /* If we have data */ |
119 | for (cnt = 0; cnt < rc; cnt++) { |
120 | /* for each bit in fifo */ |
121 | dt = readb(addr: ir->base + SUNXI_IR_RXFIFO_REG); |
122 | rawir.pulse = (dt & 0x80) != 0; |
123 | rawir.duration = ((dt & 0x7f) + 1) * |
124 | ir->rc->rx_resolution; |
125 | ir_raw_event_store_with_filter(dev: ir->rc, ev: &rawir); |
126 | } |
127 | } |
128 | |
129 | if (status & REG_RXSTA_ROI) { |
130 | ir_raw_event_overflow(dev: ir->rc); |
131 | } else if (status & REG_RXSTA_RPE) { |
132 | ir_raw_event_set_idle(dev: ir->rc, idle: true); |
133 | ir_raw_event_handle(dev: ir->rc); |
134 | } else { |
135 | ir_raw_event_handle(dev: ir->rc); |
136 | } |
137 | |
138 | return IRQ_HANDLED; |
139 | } |
140 | |
141 | /* Convert idle threshold to usec */ |
142 | static unsigned int sunxi_ithr_to_usec(unsigned int base_clk, unsigned int ithr) |
143 | { |
144 | return DIV_ROUND_CLOSEST(USEC_PER_SEC * (ithr + 1), |
145 | base_clk / (128 * 64)); |
146 | } |
147 | |
148 | /* Convert usec to idle threshold */ |
149 | static unsigned int sunxi_usec_to_ithr(unsigned int base_clk, unsigned int usec) |
150 | { |
151 | /* make sure we don't end up with a timeout less than requested */ |
152 | return DIV_ROUND_UP((base_clk / (128 * 64)) * usec, USEC_PER_SEC) - 1; |
153 | } |
154 | |
155 | static int sunxi_ir_set_timeout(struct rc_dev *rc_dev, unsigned int timeout) |
156 | { |
157 | struct sunxi_ir *ir = rc_dev->priv; |
158 | unsigned int base_clk = clk_get_rate(clk: ir->clk); |
159 | |
160 | unsigned int ithr = sunxi_usec_to_ithr(base_clk, usec: timeout); |
161 | |
162 | dev_dbg(rc_dev->dev.parent, "setting idle threshold to %u\n" , ithr); |
163 | |
164 | /* Set noise threshold and idle threshold */ |
165 | writel(REG_CIR_NTHR(SUNXI_IR_RXNOISE) | REG_CIR_ITHR(ithr), |
166 | addr: ir->base + SUNXI_IR_CIR_REG); |
167 | |
168 | rc_dev->timeout = sunxi_ithr_to_usec(base_clk, ithr); |
169 | |
170 | return 0; |
171 | } |
172 | |
173 | static int sunxi_ir_hw_init(struct device *dev) |
174 | { |
175 | struct sunxi_ir *ir = dev_get_drvdata(dev); |
176 | u32 tmp; |
177 | int ret; |
178 | |
179 | ret = reset_control_deassert(rstc: ir->rst); |
180 | if (ret) |
181 | return ret; |
182 | |
183 | ret = clk_prepare_enable(clk: ir->apb_clk); |
184 | if (ret) { |
185 | dev_err(dev, "failed to enable apb clk\n" ); |
186 | goto exit_assert_reset; |
187 | } |
188 | |
189 | ret = clk_prepare_enable(clk: ir->clk); |
190 | if (ret) { |
191 | dev_err(dev, "failed to enable ir clk\n" ); |
192 | goto exit_disable_apb_clk; |
193 | } |
194 | |
195 | /* Enable CIR Mode */ |
196 | writel(REG_CTL_MD, addr: ir->base + SUNXI_IR_CTL_REG); |
197 | |
198 | /* Set noise threshold and idle threshold */ |
199 | sunxi_ir_set_timeout(rc_dev: ir->rc, timeout: ir->rc->timeout); |
200 | |
201 | /* Invert Input Signal */ |
202 | writel(REG_RXCTL_RPPI, addr: ir->base + SUNXI_IR_RXCTL_REG); |
203 | |
204 | /* Clear All Rx Interrupt Status */ |
205 | writel(REG_RXSTA_CLEARALL, addr: ir->base + SUNXI_IR_RXSTA_REG); |
206 | |
207 | /* |
208 | * Enable IRQ on overflow, packet end, FIFO available with trigger |
209 | * level |
210 | */ |
211 | writel(REG_RXINT_ROI_EN | REG_RXINT_RPEI_EN | |
212 | REG_RXINT_RAI_EN | REG_RXINT_RAL(ir->fifo_size / 2 - 1), |
213 | addr: ir->base + SUNXI_IR_RXINT_REG); |
214 | |
215 | /* Enable IR Module */ |
216 | tmp = readl(addr: ir->base + SUNXI_IR_CTL_REG); |
217 | writel(val: tmp | REG_CTL_GEN | REG_CTL_RXEN, addr: ir->base + SUNXI_IR_CTL_REG); |
218 | |
219 | return 0; |
220 | |
221 | exit_disable_apb_clk: |
222 | clk_disable_unprepare(clk: ir->apb_clk); |
223 | exit_assert_reset: |
224 | reset_control_assert(rstc: ir->rst); |
225 | |
226 | return ret; |
227 | } |
228 | |
229 | static void sunxi_ir_hw_exit(struct device *dev) |
230 | { |
231 | struct sunxi_ir *ir = dev_get_drvdata(dev); |
232 | |
233 | clk_disable_unprepare(clk: ir->clk); |
234 | clk_disable_unprepare(clk: ir->apb_clk); |
235 | reset_control_assert(rstc: ir->rst); |
236 | } |
237 | |
238 | static int __maybe_unused sunxi_ir_suspend(struct device *dev) |
239 | { |
240 | sunxi_ir_hw_exit(dev); |
241 | |
242 | return 0; |
243 | } |
244 | |
245 | static int __maybe_unused sunxi_ir_resume(struct device *dev) |
246 | { |
247 | return sunxi_ir_hw_init(dev); |
248 | } |
249 | |
250 | static SIMPLE_DEV_PM_OPS(sunxi_ir_pm_ops, sunxi_ir_suspend, sunxi_ir_resume); |
251 | |
252 | static int sunxi_ir_probe(struct platform_device *pdev) |
253 | { |
254 | int ret = 0; |
255 | |
256 | struct device *dev = &pdev->dev; |
257 | struct device_node *dn = dev->of_node; |
258 | const struct sunxi_ir_quirks *quirks; |
259 | struct sunxi_ir *ir; |
260 | u32 b_clk_freq = SUNXI_IR_BASE_CLK; |
261 | |
262 | ir = devm_kzalloc(dev, size: sizeof(struct sunxi_ir), GFP_KERNEL); |
263 | if (!ir) |
264 | return -ENOMEM; |
265 | |
266 | quirks = of_device_get_match_data(dev: &pdev->dev); |
267 | if (!quirks) { |
268 | dev_err(&pdev->dev, "Failed to determine the quirks to use\n" ); |
269 | return -ENODEV; |
270 | } |
271 | |
272 | ir->fifo_size = quirks->fifo_size; |
273 | |
274 | /* Clock */ |
275 | ir->apb_clk = devm_clk_get(dev, id: "apb" ); |
276 | if (IS_ERR(ptr: ir->apb_clk)) { |
277 | dev_err(dev, "failed to get a apb clock.\n" ); |
278 | return PTR_ERR(ptr: ir->apb_clk); |
279 | } |
280 | ir->clk = devm_clk_get(dev, id: "ir" ); |
281 | if (IS_ERR(ptr: ir->clk)) { |
282 | dev_err(dev, "failed to get a ir clock.\n" ); |
283 | return PTR_ERR(ptr: ir->clk); |
284 | } |
285 | |
286 | /* Base clock frequency (optional) */ |
287 | of_property_read_u32(np: dn, propname: "clock-frequency" , out_value: &b_clk_freq); |
288 | |
289 | /* Reset */ |
290 | if (quirks->has_reset) { |
291 | ir->rst = devm_reset_control_get_exclusive(dev, NULL); |
292 | if (IS_ERR(ptr: ir->rst)) |
293 | return PTR_ERR(ptr: ir->rst); |
294 | } |
295 | |
296 | ret = clk_set_rate(clk: ir->clk, rate: b_clk_freq); |
297 | if (ret) { |
298 | dev_err(dev, "set ir base clock failed!\n" ); |
299 | return ret; |
300 | } |
301 | dev_dbg(dev, "set base clock frequency to %d Hz.\n" , b_clk_freq); |
302 | |
303 | /* IO */ |
304 | ir->base = devm_platform_ioremap_resource(pdev, index: 0); |
305 | if (IS_ERR(ptr: ir->base)) { |
306 | return PTR_ERR(ptr: ir->base); |
307 | } |
308 | |
309 | ir->rc = rc_allocate_device(RC_DRIVER_IR_RAW); |
310 | if (!ir->rc) { |
311 | dev_err(dev, "failed to allocate device\n" ); |
312 | return -ENOMEM; |
313 | } |
314 | |
315 | ir->rc->priv = ir; |
316 | ir->rc->device_name = SUNXI_IR_DEV; |
317 | ir->rc->input_phys = "sunxi-ir/input0" ; |
318 | ir->rc->input_id.bustype = BUS_HOST; |
319 | ir->rc->input_id.vendor = 0x0001; |
320 | ir->rc->input_id.product = 0x0001; |
321 | ir->rc->input_id.version = 0x0100; |
322 | ir->map_name = of_get_property(node: dn, name: "linux,rc-map-name" , NULL); |
323 | ir->rc->map_name = ir->map_name ?: RC_MAP_EMPTY; |
324 | ir->rc->dev.parent = dev; |
325 | ir->rc->allowed_protocols = RC_PROTO_BIT_ALL_IR_DECODER; |
326 | /* Frequency after IR internal divider with sample period in us */ |
327 | ir->rc->rx_resolution = (USEC_PER_SEC / (b_clk_freq / 64)); |
328 | ir->rc->timeout = IR_DEFAULT_TIMEOUT; |
329 | ir->rc->min_timeout = sunxi_ithr_to_usec(base_clk: b_clk_freq, ithr: 0); |
330 | ir->rc->max_timeout = sunxi_ithr_to_usec(base_clk: b_clk_freq, ithr: 255); |
331 | ir->rc->s_timeout = sunxi_ir_set_timeout; |
332 | ir->rc->driver_name = SUNXI_IR_DEV; |
333 | |
334 | ret = rc_register_device(dev: ir->rc); |
335 | if (ret) { |
336 | dev_err(dev, "failed to register rc device\n" ); |
337 | goto exit_free_dev; |
338 | } |
339 | |
340 | platform_set_drvdata(pdev, data: ir); |
341 | |
342 | /* IRQ */ |
343 | ir->irq = platform_get_irq(pdev, 0); |
344 | if (ir->irq < 0) { |
345 | ret = ir->irq; |
346 | goto exit_free_dev; |
347 | } |
348 | |
349 | ret = devm_request_irq(dev, irq: ir->irq, handler: sunxi_ir_irq, irqflags: 0, SUNXI_IR_DEV, dev_id: ir); |
350 | if (ret) { |
351 | dev_err(dev, "failed request irq\n" ); |
352 | goto exit_free_dev; |
353 | } |
354 | |
355 | ret = sunxi_ir_hw_init(dev); |
356 | if (ret) |
357 | goto exit_free_dev; |
358 | |
359 | dev_info(dev, "initialized sunXi IR driver\n" ); |
360 | return 0; |
361 | |
362 | exit_free_dev: |
363 | rc_free_device(dev: ir->rc); |
364 | |
365 | return ret; |
366 | } |
367 | |
368 | static void sunxi_ir_remove(struct platform_device *pdev) |
369 | { |
370 | struct sunxi_ir *ir = platform_get_drvdata(pdev); |
371 | |
372 | rc_unregister_device(dev: ir->rc); |
373 | sunxi_ir_hw_exit(dev: &pdev->dev); |
374 | } |
375 | |
376 | static void sunxi_ir_shutdown(struct platform_device *pdev) |
377 | { |
378 | sunxi_ir_hw_exit(dev: &pdev->dev); |
379 | } |
380 | |
381 | static const struct sunxi_ir_quirks sun4i_a10_ir_quirks = { |
382 | .has_reset = false, |
383 | .fifo_size = 16, |
384 | }; |
385 | |
386 | static const struct sunxi_ir_quirks sun5i_a13_ir_quirks = { |
387 | .has_reset = false, |
388 | .fifo_size = 64, |
389 | }; |
390 | |
391 | static const struct sunxi_ir_quirks sun6i_a31_ir_quirks = { |
392 | .has_reset = true, |
393 | .fifo_size = 64, |
394 | }; |
395 | |
396 | static const struct of_device_id sunxi_ir_match[] = { |
397 | { |
398 | .compatible = "allwinner,sun4i-a10-ir" , |
399 | .data = &sun4i_a10_ir_quirks, |
400 | }, |
401 | { |
402 | .compatible = "allwinner,sun5i-a13-ir" , |
403 | .data = &sun5i_a13_ir_quirks, |
404 | }, |
405 | { |
406 | .compatible = "allwinner,sun6i-a31-ir" , |
407 | .data = &sun6i_a31_ir_quirks, |
408 | }, |
409 | {} |
410 | }; |
411 | MODULE_DEVICE_TABLE(of, sunxi_ir_match); |
412 | |
413 | static struct platform_driver sunxi_ir_driver = { |
414 | .probe = sunxi_ir_probe, |
415 | .remove_new = sunxi_ir_remove, |
416 | .shutdown = sunxi_ir_shutdown, |
417 | .driver = { |
418 | .name = SUNXI_IR_DEV, |
419 | .of_match_table = sunxi_ir_match, |
420 | .pm = &sunxi_ir_pm_ops, |
421 | }, |
422 | }; |
423 | |
424 | module_platform_driver(sunxi_ir_driver); |
425 | |
426 | MODULE_DESCRIPTION("Allwinner sunXi IR controller driver" ); |
427 | MODULE_AUTHOR("Alexsey Shestacov <wingrime@linux-sunxi.org>" ); |
428 | MODULE_LICENSE("GPL" ); |
429 | |