diff options
author | Srinivas Kandagatla <srinivas.kandagatla@st.com> | 2013-10-18 05:01:14 -0400 |
---|---|---|
committer | Mauro Carvalho Chehab <m.chehab@samsung.com> | 2013-10-31 06:20:08 -0400 |
commit | 80f93c7b0f4599ffbdac8d964ecd1162b8b618b9 (patch) | |
tree | 09c46fbdf9d901d4b049af57e699581359625b2d | |
parent | 8ab1aa87f3f7381be195efcabf08dbc74626f25d (diff) |
[media] media: st-rc: Add ST remote control driver
This patch adds support to ST RC driver, which is basically a IR/UHF
receiver and transmitter. This IP (IRB) is common across all the ST
parts for settop box platforms. IRB is embedded in ST COMMS IP block.
It supports both Rx & Tx functionality.
This driver adds only Rx functionality via LIRC codec.
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@st.com>
Acked-by: Sean Young <sean@mess.org>
Signed-off-by: Mauro Carvalho Chehab <m.chehab@samsung.com>
-rw-r--r-- | Documentation/devicetree/bindings/media/st-rc.txt | 29 | ||||
-rw-r--r-- | drivers/media/rc/Kconfig | 10 | ||||
-rw-r--r-- | drivers/media/rc/Makefile | 1 | ||||
-rw-r--r-- | drivers/media/rc/st_rc.c | 395 |
4 files changed, 435 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/media/st-rc.txt b/Documentation/devicetree/bindings/media/st-rc.txt new file mode 100644 index 000000000000..05c432d08bca --- /dev/null +++ b/Documentation/devicetree/bindings/media/st-rc.txt | |||
@@ -0,0 +1,29 @@ | |||
1 | Device-Tree bindings for ST IRB IP | ||
2 | |||
3 | Required properties: | ||
4 | - compatible: Should contain "st,comms-irb". | ||
5 | - reg: Base physical address of the controller and length of memory | ||
6 | mapped region. | ||
7 | - interrupts: interrupt-specifier for the sole interrupt generated by | ||
8 | the device. The interrupt specifier format depends on the interrupt | ||
9 | controller parent. | ||
10 | - rx-mode: can be "infrared" or "uhf". This property specifies the L1 | ||
11 | protocol used for receiving remote control signals. rx-mode should | ||
12 | be present iff the rx pins are wired up. | ||
13 | - tx-mode: should be "infrared". This property specifies the L1 | ||
14 | protocol used for transmitting remote control signals. tx-mode should | ||
15 | be present iff the tx pins are wired up. | ||
16 | |||
17 | Optional properties: | ||
18 | - pinctrl-names, pinctrl-0: the pincontrol settings to configure muxing | ||
19 | properly for IRB pins. | ||
20 | - clocks : phandle with clock-specifier pair for IRB. | ||
21 | |||
22 | Example node: | ||
23 | |||
24 | rc: rc@fe518000 { | ||
25 | compatible = "st,comms-irb"; | ||
26 | reg = <0xfe518000 0x234>; | ||
27 | interrupts = <0 203 0>; | ||
28 | rx-mode = "infrared"; | ||
29 | }; | ||
diff --git a/drivers/media/rc/Kconfig b/drivers/media/rc/Kconfig index 11e84bcc23a1..904f11367c29 100644 --- a/drivers/media/rc/Kconfig +++ b/drivers/media/rc/Kconfig | |||
@@ -322,4 +322,14 @@ config IR_GPIO_CIR | |||
322 | To compile this driver as a module, choose M here: the module will | 322 | To compile this driver as a module, choose M here: the module will |
323 | be called gpio-ir-recv. | 323 | be called gpio-ir-recv. |
324 | 324 | ||
325 | config RC_ST | ||
326 | tristate "ST remote control receiver" | ||
327 | depends on ARCH_STI && RC_CORE | ||
328 | help | ||
329 | Say Y here if you want support for ST remote control driver | ||
330 | which allows both IR and UHF RX. | ||
331 | The driver passes raw pulse and space information to the LIRC decoder. | ||
332 | |||
333 | If you're not sure, select N here. | ||
334 | |||
325 | endif #RC_DEVICES | 335 | endif #RC_DEVICES |
diff --git a/drivers/media/rc/Makefile b/drivers/media/rc/Makefile index 56bacf07b361..f4eb32c0a455 100644 --- a/drivers/media/rc/Makefile +++ b/drivers/media/rc/Makefile | |||
@@ -30,3 +30,4 @@ obj-$(CONFIG_RC_LOOPBACK) += rc-loopback.o | |||
30 | obj-$(CONFIG_IR_GPIO_CIR) += gpio-ir-recv.o | 30 | obj-$(CONFIG_IR_GPIO_CIR) += gpio-ir-recv.o |
31 | obj-$(CONFIG_IR_IGUANA) += iguanair.o | 31 | obj-$(CONFIG_IR_IGUANA) += iguanair.o |
32 | obj-$(CONFIG_IR_TTUSBIR) += ttusbir.o | 32 | obj-$(CONFIG_IR_TTUSBIR) += ttusbir.o |
33 | obj-$(CONFIG_RC_ST) += st_rc.o | ||
diff --git a/drivers/media/rc/st_rc.c b/drivers/media/rc/st_rc.c new file mode 100644 index 000000000000..65120c2d47ad --- /dev/null +++ b/drivers/media/rc/st_rc.c | |||
@@ -0,0 +1,395 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2013 STMicroelectronics Limited | ||
3 | * Author: Srinivas Kandagatla <srinivas.kandagatla@st.com> | ||
4 | * | ||
5 | * This program is free software; you can redistribute it and/or modify | ||
6 | * it under the terms of the GNU General Public License as published by | ||
7 | * the Free Software Foundation; either version 2 of the License, or | ||
8 | * (at your option) any later version. | ||
9 | */ | ||
10 | #include <linux/kernel.h> | ||
11 | #include <linux/clk.h> | ||
12 | #include <linux/interrupt.h> | ||
13 | #include <linux/module.h> | ||
14 | #include <linux/of.h> | ||
15 | #include <linux/platform_device.h> | ||
16 | #include <media/rc-core.h> | ||
17 | #include <linux/pinctrl/consumer.h> | ||
18 | |||
19 | struct st_rc_device { | ||
20 | struct device *dev; | ||
21 | int irq; | ||
22 | int irq_wake; | ||
23 | struct clk *sys_clock; | ||
24 | void *base; /* Register base address */ | ||
25 | void *rx_base;/* RX Register base address */ | ||
26 | struct rc_dev *rdev; | ||
27 | bool overclocking; | ||
28 | int sample_mult; | ||
29 | int sample_div; | ||
30 | bool rxuhfmode; | ||
31 | }; | ||
32 | |||
33 | /* Registers */ | ||
34 | #define IRB_SAMPLE_RATE_COMM 0x64 /* sample freq divisor*/ | ||
35 | #define IRB_CLOCK_SEL 0x70 /* clock select */ | ||
36 | #define IRB_CLOCK_SEL_STATUS 0x74 /* clock status */ | ||
37 | /* IRB IR/UHF receiver registers */ | ||
38 | #define IRB_RX_ON 0x40 /* pulse time capture */ | ||
39 | #define IRB_RX_SYS 0X44 /* sym period capture */ | ||
40 | #define IRB_RX_INT_EN 0x48 /* IRQ enable (R/W) */ | ||
41 | #define IRB_RX_INT_STATUS 0x4c /* IRQ status (R/W) */ | ||
42 | #define IRB_RX_EN 0x50 /* Receive enable */ | ||
43 | #define IRB_MAX_SYM_PERIOD 0x54 /* max sym value */ | ||
44 | #define IRB_RX_INT_CLEAR 0x58 /* overrun status */ | ||
45 | #define IRB_RX_STATUS 0x6c /* receive status */ | ||
46 | #define IRB_RX_NOISE_SUPPR 0x5c /* noise suppression */ | ||
47 | #define IRB_RX_POLARITY_INV 0x68 /* polarity inverter */ | ||
48 | |||
49 | /** | ||
50 | * IRQ set: Enable full FIFO 1 -> bit 3; | ||
51 | * Enable overrun IRQ 1 -> bit 2; | ||
52 | * Enable last symbol IRQ 1 -> bit 1: | ||
53 | * Enable RX interrupt 1 -> bit 0; | ||
54 | */ | ||
55 | #define IRB_RX_INTS 0x0f | ||
56 | #define IRB_RX_OVERRUN_INT 0x04 | ||
57 | /* maximum symbol period (microsecs),timeout to detect end of symbol train */ | ||
58 | #define MAX_SYMB_TIME 0x5000 | ||
59 | #define IRB_SAMPLE_FREQ 10000000 | ||
60 | #define IRB_FIFO_NOT_EMPTY 0xff00 | ||
61 | #define IRB_OVERFLOW 0x4 | ||
62 | #define IRB_TIMEOUT 0xffff | ||
63 | #define IR_ST_NAME "st-rc" | ||
64 | |||
65 | static void st_rc_send_lirc_timeout(struct rc_dev *rdev) | ||
66 | { | ||
67 | DEFINE_IR_RAW_EVENT(ev); | ||
68 | ev.timeout = true; | ||
69 | ir_raw_event_store(rdev, &ev); | ||
70 | } | ||
71 | |||
72 | /** | ||
73 | * RX graphical example to better understand the difference between ST IR block | ||
74 | * output and standard definition used by LIRC (and most of the world!) | ||
75 | * | ||
76 | * mark mark | ||
77 | * |-IRB_RX_ON-| |-IRB_RX_ON-| | ||
78 | * ___ ___ ___ ___ ___ ___ _ | ||
79 | * | | | | | | | | | | | | | | ||
80 | * | | | | | | space 0 | | | | | | space 1 | | ||
81 | * _____| |__| |__| |____________________________| |__| |__| |_____________| | ||
82 | * | ||
83 | * |--------------- IRB_RX_SYS -------------|------ IRB_RX_SYS -------| | ||
84 | * | ||
85 | * |------------- encoding bit 0 -----------|---- encoding bit 1 -----| | ||
86 | * | ||
87 | * ST hardware returns mark (IRB_RX_ON) and total symbol time (IRB_RX_SYS), so | ||
88 | * convert to standard mark/space we have to calculate space=(IRB_RX_SYS-mark) | ||
89 | * The mark time represents the amount of time the carrier (usually 36-40kHz) | ||
90 | * is detected.The above examples shows Pulse Width Modulation encoding where | ||
91 | * bit 0 is represented by space>mark. | ||
92 | */ | ||
93 | |||
94 | static irqreturn_t st_rc_rx_interrupt(int irq, void *data) | ||
95 | { | ||
96 | unsigned int symbol, mark = 0; | ||
97 | struct st_rc_device *dev = data; | ||
98 | int last_symbol = 0; | ||
99 | u32 status; | ||
100 | DEFINE_IR_RAW_EVENT(ev); | ||
101 | |||
102 | if (dev->irq_wake) | ||
103 | pm_wakeup_event(dev->dev, 0); | ||
104 | |||
105 | status = readl(dev->rx_base + IRB_RX_STATUS); | ||
106 | |||
107 | while (status & (IRB_FIFO_NOT_EMPTY | IRB_OVERFLOW)) { | ||
108 | u32 int_status = readl(dev->rx_base + IRB_RX_INT_STATUS); | ||
109 | if (unlikely(int_status & IRB_RX_OVERRUN_INT)) { | ||
110 | /* discard the entire collection in case of errors! */ | ||
111 | ir_raw_event_reset(dev->rdev); | ||
112 | dev_info(dev->dev, "IR RX overrun\n"); | ||
113 | writel(IRB_RX_OVERRUN_INT, | ||
114 | dev->rx_base + IRB_RX_INT_CLEAR); | ||
115 | continue; | ||
116 | } | ||
117 | |||
118 | symbol = readl(dev->rx_base + IRB_RX_SYS); | ||
119 | mark = readl(dev->rx_base + IRB_RX_ON); | ||
120 | |||
121 | if (symbol == IRB_TIMEOUT) | ||
122 | last_symbol = 1; | ||
123 | |||
124 | /* Ignore any noise */ | ||
125 | if ((mark > 2) && (symbol > 1)) { | ||
126 | symbol -= mark; | ||
127 | if (dev->overclocking) { /* adjustments to timings */ | ||
128 | symbol *= dev->sample_mult; | ||
129 | symbol /= dev->sample_div; | ||
130 | mark *= dev->sample_mult; | ||
131 | mark /= dev->sample_div; | ||
132 | } | ||
133 | |||
134 | ev.duration = US_TO_NS(mark); | ||
135 | ev.pulse = true; | ||
136 | ir_raw_event_store(dev->rdev, &ev); | ||
137 | |||
138 | if (!last_symbol) { | ||
139 | ev.duration = US_TO_NS(symbol); | ||
140 | ev.pulse = false; | ||
141 | ir_raw_event_store(dev->rdev, &ev); | ||
142 | } else { | ||
143 | st_rc_send_lirc_timeout(dev->rdev); | ||
144 | } | ||
145 | |||
146 | } | ||
147 | last_symbol = 0; | ||
148 | status = readl(dev->rx_base + IRB_RX_STATUS); | ||
149 | } | ||
150 | |||
151 | writel(IRB_RX_INTS, dev->rx_base + IRB_RX_INT_CLEAR); | ||
152 | |||
153 | /* Empty software fifo */ | ||
154 | ir_raw_event_handle(dev->rdev); | ||
155 | return IRQ_HANDLED; | ||
156 | } | ||
157 | |||
158 | static void st_rc_hardware_init(struct st_rc_device *dev) | ||
159 | { | ||
160 | int baseclock, freqdiff; | ||
161 | unsigned int rx_max_symbol_per = MAX_SYMB_TIME; | ||
162 | unsigned int rx_sampling_freq_div; | ||
163 | |||
164 | clk_prepare_enable(dev->sys_clock); | ||
165 | baseclock = clk_get_rate(dev->sys_clock); | ||
166 | |||
167 | /* IRB input pins are inverted internally from high to low. */ | ||
168 | writel(1, dev->rx_base + IRB_RX_POLARITY_INV); | ||
169 | |||
170 | rx_sampling_freq_div = baseclock / IRB_SAMPLE_FREQ; | ||
171 | writel(rx_sampling_freq_div, dev->base + IRB_SAMPLE_RATE_COMM); | ||
172 | |||
173 | freqdiff = baseclock - (rx_sampling_freq_div * IRB_SAMPLE_FREQ); | ||
174 | if (freqdiff) { /* over clocking, workout the adjustment factors */ | ||
175 | dev->overclocking = true; | ||
176 | dev->sample_mult = 1000; | ||
177 | dev->sample_div = baseclock / (10000 * rx_sampling_freq_div); | ||
178 | rx_max_symbol_per = (rx_max_symbol_per * 1000)/dev->sample_div; | ||
179 | } | ||
180 | |||
181 | writel(rx_max_symbol_per, dev->rx_base + IRB_MAX_SYM_PERIOD); | ||
182 | } | ||
183 | |||
184 | static int st_rc_remove(struct platform_device *pdev) | ||
185 | { | ||
186 | struct st_rc_device *rc_dev = platform_get_drvdata(pdev); | ||
187 | clk_disable_unprepare(rc_dev->sys_clock); | ||
188 | rc_unregister_device(rc_dev->rdev); | ||
189 | return 0; | ||
190 | } | ||
191 | |||
192 | static int st_rc_open(struct rc_dev *rdev) | ||
193 | { | ||
194 | struct st_rc_device *dev = rdev->priv; | ||
195 | unsigned long flags; | ||
196 | local_irq_save(flags); | ||
197 | /* enable interrupts and receiver */ | ||
198 | writel(IRB_RX_INTS, dev->rx_base + IRB_RX_INT_EN); | ||
199 | writel(0x01, dev->rx_base + IRB_RX_EN); | ||
200 | local_irq_restore(flags); | ||
201 | |||
202 | return 0; | ||
203 | } | ||
204 | |||
205 | static void st_rc_close(struct rc_dev *rdev) | ||
206 | { | ||
207 | struct st_rc_device *dev = rdev->priv; | ||
208 | /* disable interrupts and receiver */ | ||
209 | writel(0x00, dev->rx_base + IRB_RX_EN); | ||
210 | writel(0x00, dev->rx_base + IRB_RX_INT_EN); | ||
211 | } | ||
212 | |||
213 | static int st_rc_probe(struct platform_device *pdev) | ||
214 | { | ||
215 | int ret = -EINVAL; | ||
216 | struct rc_dev *rdev; | ||
217 | struct device *dev = &pdev->dev; | ||
218 | struct resource *res; | ||
219 | struct st_rc_device *rc_dev; | ||
220 | struct device_node *np = pdev->dev.of_node; | ||
221 | const char *rx_mode; | ||
222 | |||
223 | rc_dev = devm_kzalloc(dev, sizeof(struct st_rc_device), GFP_KERNEL); | ||
224 | |||
225 | if (!rc_dev) | ||
226 | return -ENOMEM; | ||
227 | |||
228 | rdev = rc_allocate_device(); | ||
229 | |||
230 | if (!rdev) | ||
231 | return -ENOMEM; | ||
232 | |||
233 | if (np && !of_property_read_string(np, "rx-mode", &rx_mode)) { | ||
234 | |||
235 | if (!strcmp(rx_mode, "uhf")) { | ||
236 | rc_dev->rxuhfmode = true; | ||
237 | } else if (!strcmp(rx_mode, "infrared")) { | ||
238 | rc_dev->rxuhfmode = false; | ||
239 | } else { | ||
240 | dev_err(dev, "Unsupported rx mode [%s]\n", rx_mode); | ||
241 | goto err; | ||
242 | } | ||
243 | |||
244 | } else { | ||
245 | goto err; | ||
246 | } | ||
247 | |||
248 | rc_dev->sys_clock = devm_clk_get(dev, NULL); | ||
249 | if (IS_ERR(rc_dev->sys_clock)) { | ||
250 | dev_err(dev, "System clock not found\n"); | ||
251 | ret = PTR_ERR(rc_dev->sys_clock); | ||
252 | goto err; | ||
253 | } | ||
254 | |||
255 | rc_dev->irq = platform_get_irq(pdev, 0); | ||
256 | if (rc_dev->irq < 0) { | ||
257 | ret = rc_dev->irq; | ||
258 | goto err; | ||
259 | } | ||
260 | |||
261 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
262 | |||
263 | rc_dev->base = devm_ioremap_resource(dev, res); | ||
264 | if (IS_ERR(rc_dev->base)) { | ||
265 | ret = PTR_ERR(rc_dev->base); | ||
266 | goto err; | ||
267 | } | ||
268 | |||
269 | if (rc_dev->rxuhfmode) | ||
270 | rc_dev->rx_base = rc_dev->base + 0x40; | ||
271 | else | ||
272 | rc_dev->rx_base = rc_dev->base; | ||
273 | |||
274 | rc_dev->dev = dev; | ||
275 | platform_set_drvdata(pdev, rc_dev); | ||
276 | st_rc_hardware_init(rc_dev); | ||
277 | |||
278 | rdev->driver_type = RC_DRIVER_IR_RAW; | ||
279 | rdev->allowed_protos = RC_BIT_ALL; | ||
280 | /* rx sampling rate is 10Mhz */ | ||
281 | rdev->rx_resolution = 100; | ||
282 | rdev->timeout = US_TO_NS(MAX_SYMB_TIME); | ||
283 | rdev->priv = rc_dev; | ||
284 | rdev->open = st_rc_open; | ||
285 | rdev->close = st_rc_close; | ||
286 | rdev->driver_name = IR_ST_NAME; | ||
287 | rdev->map_name = RC_MAP_LIRC; | ||
288 | rdev->input_name = "ST Remote Control Receiver"; | ||
289 | |||
290 | /* enable wake via this device */ | ||
291 | device_set_wakeup_capable(dev, true); | ||
292 | device_set_wakeup_enable(dev, true); | ||
293 | |||
294 | ret = rc_register_device(rdev); | ||
295 | if (ret < 0) | ||
296 | goto clkerr; | ||
297 | |||
298 | rc_dev->rdev = rdev; | ||
299 | if (devm_request_irq(dev, rc_dev->irq, st_rc_rx_interrupt, | ||
300 | IRQF_NO_SUSPEND, IR_ST_NAME, rc_dev) < 0) { | ||
301 | dev_err(dev, "IRQ %d register failed\n", rc_dev->irq); | ||
302 | ret = -EINVAL; | ||
303 | goto rcerr; | ||
304 | } | ||
305 | |||
306 | /** | ||
307 | * for LIRC_MODE_MODE2 or LIRC_MODE_PULSE or LIRC_MODE_RAW | ||
308 | * lircd expects a long space first before a signal train to sync. | ||
309 | */ | ||
310 | st_rc_send_lirc_timeout(rdev); | ||
311 | |||
312 | dev_info(dev, "setup in %s mode\n", rc_dev->rxuhfmode ? "UHF" : "IR"); | ||
313 | |||
314 | return ret; | ||
315 | rcerr: | ||
316 | rc_unregister_device(rdev); | ||
317 | rdev = NULL; | ||
318 | clkerr: | ||
319 | clk_disable_unprepare(rc_dev->sys_clock); | ||
320 | err: | ||
321 | rc_free_device(rdev); | ||
322 | dev_err(dev, "Unable to register device (%d)\n", ret); | ||
323 | return ret; | ||
324 | } | ||
325 | |||
326 | #ifdef CONFIG_PM | ||
327 | static int st_rc_suspend(struct device *dev) | ||
328 | { | ||
329 | struct st_rc_device *rc_dev = dev_get_drvdata(dev); | ||
330 | |||
331 | if (device_may_wakeup(dev)) { | ||
332 | if (!enable_irq_wake(rc_dev->irq)) | ||
333 | rc_dev->irq_wake = 1; | ||
334 | else | ||
335 | return -EINVAL; | ||
336 | } else { | ||
337 | pinctrl_pm_select_sleep_state(dev); | ||
338 | writel(0x00, rc_dev->rx_base + IRB_RX_EN); | ||
339 | writel(0x00, rc_dev->rx_base + IRB_RX_INT_EN); | ||
340 | clk_disable_unprepare(rc_dev->sys_clock); | ||
341 | } | ||
342 | |||
343 | return 0; | ||
344 | } | ||
345 | |||
346 | static int st_rc_resume(struct device *dev) | ||
347 | { | ||
348 | struct st_rc_device *rc_dev = dev_get_drvdata(dev); | ||
349 | struct rc_dev *rdev = rc_dev->rdev; | ||
350 | |||
351 | if (rc_dev->irq_wake) { | ||
352 | disable_irq_wake(rc_dev->irq); | ||
353 | rc_dev->irq_wake = 0; | ||
354 | } else { | ||
355 | pinctrl_pm_select_default_state(dev); | ||
356 | st_rc_hardware_init(rc_dev); | ||
357 | if (rdev->users) { | ||
358 | writel(IRB_RX_INTS, rc_dev->rx_base + IRB_RX_INT_EN); | ||
359 | writel(0x01, rc_dev->rx_base + IRB_RX_EN); | ||
360 | } | ||
361 | } | ||
362 | |||
363 | return 0; | ||
364 | } | ||
365 | |||
366 | static SIMPLE_DEV_PM_OPS(st_rc_pm_ops, st_rc_suspend, st_rc_resume); | ||
367 | #endif | ||
368 | |||
369 | #ifdef CONFIG_OF | ||
370 | static struct of_device_id st_rc_match[] = { | ||
371 | { .compatible = "st,comms-irb", }, | ||
372 | {}, | ||
373 | }; | ||
374 | |||
375 | MODULE_DEVICE_TABLE(of, st_rc_match); | ||
376 | #endif | ||
377 | |||
378 | static struct platform_driver st_rc_driver = { | ||
379 | .driver = { | ||
380 | .name = IR_ST_NAME, | ||
381 | .owner = THIS_MODULE, | ||
382 | .of_match_table = of_match_ptr(st_rc_match), | ||
383 | #ifdef CONFIG_PM | ||
384 | .pm = &st_rc_pm_ops, | ||
385 | #endif | ||
386 | }, | ||
387 | .probe = st_rc_probe, | ||
388 | .remove = st_rc_remove, | ||
389 | }; | ||
390 | |||
391 | module_platform_driver(st_rc_driver); | ||
392 | |||
393 | MODULE_DESCRIPTION("RC Transceiver driver for STMicroelectronics platforms"); | ||
394 | MODULE_AUTHOR("STMicroelectronics (R&D) Ltd"); | ||
395 | MODULE_LICENSE("GPL"); | ||