aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/media/rc
diff options
context:
space:
mode:
authorGuoxiong Yan <yanguoxiong@huawei.com>2014-08-30 22:39:10 -0400
committerMauro Carvalho Chehab <mchehab@osg.samsung.com>2014-09-23 16:03:57 -0400
commita84fcdaa905862b09482544d190c94a8436e4334 (patch)
tree08656eb37edaf65aebcd2dccc227085ba8cad7fb /drivers/media/rc
parent1a3920e86e0291c46db8b5b81ed31a98141e9670 (diff)
[media] rc: Introduce hix5hd2 IR transmitter driver
IR transmitter driver for Hisilicon hix5hd2 soc By default all protocols are disabled. For example nec decoder can be enabled by either 1. ir-keytable -p nec 2. echo nec > /sys/class/rc/rc0/protocols See see Documentation/ABI/testing/sysfs-class-rc [mchehab@osg.samsung.com: Add a fixup for the driver to compile on archs that don't provide writel_relaxed() macro] Signed-off-by: Guoxiong Yan <yanguoxiong@huawei.com> Signed-off-by: Zhangfei Gao <zhangfei.gao@linaro.org> Signed-off-by: Mauro Carvalho Chehab <mchehab@osg.samsung.com>
Diffstat (limited to 'drivers/media/rc')
-rw-r--r--drivers/media/rc/Kconfig10
-rw-r--r--drivers/media/rc/Makefile1
-rw-r--r--drivers/media/rc/ir-hix5hd2.c348
3 files changed, 359 insertions, 0 deletions
diff --git a/drivers/media/rc/Kconfig b/drivers/media/rc/Kconfig
index 2b0cc4a98e88..8ce08107a69d 100644
--- a/drivers/media/rc/Kconfig
+++ b/drivers/media/rc/Kconfig
@@ -164,6 +164,16 @@ config IR_ENE
164 To compile this driver as a module, choose M here: the 164 To compile this driver as a module, choose M here: the
165 module will be called ene_ir. 165 module will be called ene_ir.
166 166
167config IR_HIX5HD2
168 tristate "Hisilicon hix5hd2 IR remote control"
169 depends on RC_CORE
170 help
171 Say Y here if you want to use hisilicon hix5hd2 remote control.
172 To compile this driver as a module, choose M here: the module will be
173 called ir-hix5hd2.
174
175 If you're not sure, select N here
176
167config IR_IMON 177config IR_IMON
168 tristate "SoundGraph iMON Receiver and Display" 178 tristate "SoundGraph iMON Receiver and Display"
169 depends on USB_ARCH_HAS_HCD 179 depends on USB_ARCH_HAS_HCD
diff --git a/drivers/media/rc/Makefile b/drivers/media/rc/Makefile
index 9f9843a1af5f..0989f940e9cf 100644
--- a/drivers/media/rc/Makefile
+++ b/drivers/media/rc/Makefile
@@ -17,6 +17,7 @@ obj-$(CONFIG_IR_XMP_DECODER) += ir-xmp-decoder.o
17 17
18# stand-alone IR receivers/transmitters 18# stand-alone IR receivers/transmitters
19obj-$(CONFIG_RC_ATI_REMOTE) += ati_remote.o 19obj-$(CONFIG_RC_ATI_REMOTE) += ati_remote.o
20obj-$(CONFIG_IR_HIX5HD2) += ir-hix5hd2.o
20obj-$(CONFIG_IR_IMON) += imon.o 21obj-$(CONFIG_IR_IMON) += imon.o
21obj-$(CONFIG_IR_ITE_CIR) += ite-cir.o 22obj-$(CONFIG_IR_ITE_CIR) += ite-cir.o
22obj-$(CONFIG_IR_MCEUSB) += mceusb.o 23obj-$(CONFIG_IR_MCEUSB) += mceusb.o
diff --git a/drivers/media/rc/ir-hix5hd2.c b/drivers/media/rc/ir-hix5hd2.c
new file mode 100644
index 000000000000..94967d0e0478
--- /dev/null
+++ b/drivers/media/rc/ir-hix5hd2.c
@@ -0,0 +1,348 @@
1/*
2 * Copyright (c) 2014 Linaro Ltd.
3 * Copyright (c) 2014 Hisilicon Limited.
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms and conditions of the GNU General Public License,
7 * version 2, as published by the Free Software Foundation.
8 */
9
10#include <linux/clk.h>
11#include <linux/delay.h>
12#include <linux/interrupt.h>
13#include <linux/mfd/syscon.h>
14#include <linux/module.h>
15#include <linux/of_device.h>
16#include <linux/regmap.h>
17#include <media/rc-core.h>
18
19/* Allow the driver to compile on all architectures */
20#ifndef writel_relaxed
21# define writel_relaxed writel
22#endif
23
24#define IR_ENABLE 0x00
25#define IR_CONFIG 0x04
26#define CNT_LEADS 0x08
27#define CNT_LEADE 0x0c
28#define CNT_SLEADE 0x10
29#define CNT0_B 0x14
30#define CNT1_B 0x18
31#define IR_BUSY 0x1c
32#define IR_DATAH 0x20
33#define IR_DATAL 0x24
34#define IR_INTM 0x28
35#define IR_INTS 0x2c
36#define IR_INTC 0x30
37#define IR_START 0x34
38
39/* interrupt mask */
40#define INTMS_SYMBRCV (BIT(24) | BIT(8))
41#define INTMS_TIMEOUT (BIT(25) | BIT(9))
42#define INTMS_OVERFLOW (BIT(26) | BIT(10))
43#define INT_CLR_OVERFLOW BIT(18)
44#define INT_CLR_TIMEOUT BIT(17)
45#define INT_CLR_RCV BIT(16)
46#define INT_CLR_RCVTIMEOUT (BIT(16) | BIT(17))
47
48#define IR_CLK 0x48
49#define IR_CLK_ENABLE BIT(4)
50#define IR_CLK_RESET BIT(5)
51
52#define IR_CFG_WIDTH_MASK 0xffff
53#define IR_CFG_WIDTH_SHIFT 16
54#define IR_CFG_FORMAT_MASK 0x3
55#define IR_CFG_FORMAT_SHIFT 14
56#define IR_CFG_INT_LEVEL_MASK 0x3f
57#define IR_CFG_INT_LEVEL_SHIFT 8
58/* only support raw mode */
59#define IR_CFG_MODE_RAW BIT(7)
60#define IR_CFG_FREQ_MASK 0x7f
61#define IR_CFG_FREQ_SHIFT 0
62#define IR_CFG_INT_THRESHOLD 1
63/* symbol start from low to high, symbol stream end at high*/
64#define IR_CFG_SYMBOL_FMT 0
65#define IR_CFG_SYMBOL_MAXWIDTH 0x3e80
66
67#define IR_HIX5HD2_NAME "hix5hd2-ir"
68
69struct hix5hd2_ir_priv {
70 int irq;
71 void *base;
72 struct device *dev;
73 struct rc_dev *rdev;
74 struct regmap *regmap;
75 struct clk *clock;
76 unsigned long rate;
77};
78
79static void hix5hd2_ir_enable(struct hix5hd2_ir_priv *dev, bool on)
80{
81 u32 val;
82
83 regmap_read(dev->regmap, IR_CLK, &val);
84 if (on) {
85 val &= ~IR_CLK_RESET;
86 val |= IR_CLK_ENABLE;
87 } else {
88 val &= ~IR_CLK_ENABLE;
89 val |= IR_CLK_RESET;
90 }
91 regmap_write(dev->regmap, IR_CLK, val);
92}
93
94static int hix5hd2_ir_config(struct hix5hd2_ir_priv *priv)
95{
96 int timeout = 10000;
97 u32 val, rate;
98
99 writel_relaxed(0x01, priv->base + IR_ENABLE);
100 while (readl_relaxed(priv->base + IR_BUSY)) {
101 if (timeout--) {
102 udelay(1);
103 } else {
104 dev_err(priv->dev, "IR_BUSY timeout\n");
105 return -ETIMEDOUT;
106 }
107 }
108
109 /* Now only support raw mode, with symbol start from low to high */
110 rate = DIV_ROUND_CLOSEST(priv->rate, 1000000);
111 val = IR_CFG_SYMBOL_MAXWIDTH & IR_CFG_WIDTH_MASK << IR_CFG_WIDTH_SHIFT;
112 val |= IR_CFG_SYMBOL_FMT & IR_CFG_FORMAT_MASK << IR_CFG_FORMAT_SHIFT;
113 val |= (IR_CFG_INT_THRESHOLD - 1) & IR_CFG_INT_LEVEL_MASK
114 << IR_CFG_INT_LEVEL_SHIFT;
115 val |= IR_CFG_MODE_RAW;
116 val |= (rate - 1) & IR_CFG_FREQ_MASK << IR_CFG_FREQ_SHIFT;
117 writel_relaxed(val, priv->base + IR_CONFIG);
118
119 writel_relaxed(0x00, priv->base + IR_INTM);
120 /* write arbitrary value to start */
121 writel_relaxed(0x01, priv->base + IR_START);
122 return 0;
123}
124
125static int hix5hd2_ir_open(struct rc_dev *rdev)
126{
127 struct hix5hd2_ir_priv *priv = rdev->priv;
128
129 hix5hd2_ir_enable(priv, true);
130 return hix5hd2_ir_config(priv);
131}
132
133static void hix5hd2_ir_close(struct rc_dev *rdev)
134{
135 struct hix5hd2_ir_priv *priv = rdev->priv;
136
137 hix5hd2_ir_enable(priv, false);
138}
139
140static irqreturn_t hix5hd2_ir_rx_interrupt(int irq, void *data)
141{
142 u32 symb_num, symb_val, symb_time;
143 u32 data_l, data_h;
144 u32 irq_sr, i;
145 struct hix5hd2_ir_priv *priv = data;
146
147 irq_sr = readl_relaxed(priv->base + IR_INTS);
148 if (irq_sr & INTMS_OVERFLOW) {
149 /*
150 * we must read IR_DATAL first, then we can clean up
151 * IR_INTS availably since logic would not clear
152 * fifo when overflow, drv do the job
153 */
154 ir_raw_event_reset(priv->rdev);
155 symb_num = readl_relaxed(priv->base + IR_DATAH);
156 for (i = 0; i < symb_num; i++)
157 readl_relaxed(priv->base + IR_DATAL);
158
159 writel_relaxed(INT_CLR_OVERFLOW, priv->base + IR_INTC);
160 dev_info(priv->dev, "overflow, level=%d\n",
161 IR_CFG_INT_THRESHOLD);
162 }
163
164 if ((irq_sr & INTMS_SYMBRCV) || (irq_sr & INTMS_TIMEOUT)) {
165 DEFINE_IR_RAW_EVENT(ev);
166
167 symb_num = readl_relaxed(priv->base + IR_DATAH);
168 for (i = 0; i < symb_num; i++) {
169 symb_val = readl_relaxed(priv->base + IR_DATAL);
170 data_l = ((symb_val & 0xffff) * 10);
171 data_h = ((symb_val >> 16) & 0xffff) * 10;
172 symb_time = (data_l + data_h) / 10;
173
174 ev.duration = US_TO_NS(data_l);
175 ev.pulse = true;
176 ir_raw_event_store(priv->rdev, &ev);
177
178 if (symb_time < IR_CFG_SYMBOL_MAXWIDTH) {
179 ev.duration = US_TO_NS(data_h);
180 ev.pulse = false;
181 ir_raw_event_store(priv->rdev, &ev);
182 } else {
183 ir_raw_event_set_idle(priv->rdev, true);
184 }
185 }
186
187 if (irq_sr & INTMS_SYMBRCV)
188 writel_relaxed(INT_CLR_RCV, priv->base + IR_INTC);
189 if (irq_sr & INTMS_TIMEOUT)
190 writel_relaxed(INT_CLR_TIMEOUT, priv->base + IR_INTC);
191 }
192
193 /* Empty software fifo */
194 ir_raw_event_handle(priv->rdev);
195 return IRQ_HANDLED;
196}
197
198static int hix5hd2_ir_probe(struct platform_device *pdev)
199{
200 struct rc_dev *rdev;
201 struct device *dev = &pdev->dev;
202 struct resource *res;
203 struct hix5hd2_ir_priv *priv;
204 struct device_node *node = pdev->dev.of_node;
205 const char *map_name;
206 int ret;
207
208 priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
209 if (!priv)
210 return -ENOMEM;
211
212 priv->regmap = syscon_regmap_lookup_by_phandle(node,
213 "hisilicon,power-syscon");
214 if (IS_ERR(priv->regmap)) {
215 dev_err(dev, "no power-reg\n");
216 return -EINVAL;
217 }
218
219 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
220 priv->base = devm_ioremap_resource(dev, res);
221 if (IS_ERR(priv->base))
222 return PTR_ERR(priv->base);
223
224 priv->irq = platform_get_irq(pdev, 0);
225 if (priv->irq < 0) {
226 dev_err(dev, "irq can not get\n");
227 return priv->irq;
228 }
229
230 rdev = rc_allocate_device();
231 if (!rdev)
232 return -ENOMEM;
233
234 priv->clock = devm_clk_get(dev, NULL);
235 if (IS_ERR(priv->clock)) {
236 dev_err(dev, "clock not found\n");
237 ret = PTR_ERR(priv->clock);
238 goto err;
239 }
240 clk_prepare_enable(priv->clock);
241 priv->rate = clk_get_rate(priv->clock);
242
243 rdev->driver_type = RC_DRIVER_IR_RAW;
244 rdev->allowed_protocols = RC_BIT_ALL;
245 rdev->priv = priv;
246 rdev->open = hix5hd2_ir_open;
247 rdev->close = hix5hd2_ir_close;
248 rdev->driver_name = IR_HIX5HD2_NAME;
249 map_name = of_get_property(node, "linux,rc-map-name", NULL);
250 rdev->map_name = map_name ?: RC_MAP_EMPTY;
251 rdev->input_name = IR_HIX5HD2_NAME;
252 rdev->input_phys = IR_HIX5HD2_NAME "/input0";
253 rdev->input_id.bustype = BUS_HOST;
254 rdev->input_id.vendor = 0x0001;
255 rdev->input_id.product = 0x0001;
256 rdev->input_id.version = 0x0100;
257 rdev->rx_resolution = US_TO_NS(10);
258 rdev->timeout = US_TO_NS(IR_CFG_SYMBOL_MAXWIDTH * 10);
259
260 ret = rc_register_device(rdev);
261 if (ret < 0)
262 goto clkerr;
263
264 if (devm_request_irq(dev, priv->irq, hix5hd2_ir_rx_interrupt,
265 IRQF_NO_SUSPEND, pdev->name, priv) < 0) {
266 dev_err(dev, "IRQ %d register failed\n", priv->irq);
267 ret = -EINVAL;
268 goto regerr;
269 }
270
271 priv->rdev = rdev;
272 priv->dev = dev;
273 platform_set_drvdata(pdev, priv);
274
275 return ret;
276
277regerr:
278 rc_unregister_device(rdev);
279 rdev = NULL;
280clkerr:
281 clk_disable_unprepare(priv->clock);
282err:
283 rc_free_device(rdev);
284 dev_err(dev, "Unable to register device (%d)\n", ret);
285 return ret;
286}
287
288static int hix5hd2_ir_remove(struct platform_device *pdev)
289{
290 struct hix5hd2_ir_priv *priv = platform_get_drvdata(pdev);
291
292 clk_disable_unprepare(priv->clock);
293 rc_unregister_device(priv->rdev);
294 return 0;
295}
296
297#ifdef CONFIG_PM
298static int hix5hd2_ir_suspend(struct device *dev)
299{
300 struct hix5hd2_ir_priv *priv = dev_get_drvdata(dev);
301
302 clk_disable_unprepare(priv->clock);
303 hix5hd2_ir_enable(priv, false);
304
305 return 0;
306}
307
308static int hix5hd2_ir_resume(struct device *dev)
309{
310 struct hix5hd2_ir_priv *priv = dev_get_drvdata(dev);
311
312 hix5hd2_ir_enable(priv, true);
313 clk_prepare_enable(priv->clock);
314
315 writel_relaxed(0x01, priv->base + IR_ENABLE);
316 writel_relaxed(0x00, priv->base + IR_INTM);
317 writel_relaxed(0xff, priv->base + IR_INTC);
318 writel_relaxed(0x01, priv->base + IR_START);
319
320 return 0;
321}
322#endif
323
324static SIMPLE_DEV_PM_OPS(hix5hd2_ir_pm_ops, hix5hd2_ir_suspend,
325 hix5hd2_ir_resume);
326
327static struct of_device_id hix5hd2_ir_table[] = {
328 { .compatible = "hisilicon,hix5hd2-ir", },
329 {},
330};
331MODULE_DEVICE_TABLE(of, hix5hd2_ir_table);
332
333static struct platform_driver hix5hd2_ir_driver = {
334 .driver = {
335 .name = IR_HIX5HD2_NAME,
336 .of_match_table = hix5hd2_ir_table,
337 .pm = &hix5hd2_ir_pm_ops,
338 },
339 .probe = hix5hd2_ir_probe,
340 .remove = hix5hd2_ir_remove,
341};
342
343module_platform_driver(hix5hd2_ir_driver);
344
345MODULE_DESCRIPTION("IR controller driver for hix5hd2 platforms");
346MODULE_AUTHOR("Guoxiong Yan <yanguoxiong@huawei.com>");
347MODULE_LICENSE("GPL v2");
348MODULE_ALIAS("platform:hix5hd2-ir");