]>
Commit | Line | Data |
---|---|---|
2874c5fd | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
d3455273 MR |
2 | /* |
3 | * Copyright (C) 2015 Mans Rullgard <mans@mansr.com> | |
d3455273 MR |
4 | */ |
5 | ||
6 | #include <linux/input.h> | |
7 | #include <linux/module.h> | |
8 | #include <linux/platform_device.h> | |
9 | #include <linux/interrupt.h> | |
10 | #include <linux/io.h> | |
11 | #include <linux/clk.h> | |
12 | #include <linux/of.h> | |
13 | #include <media/rc-core.h> | |
14 | ||
15 | #define DRIVER_NAME "tango-ir" | |
16 | ||
17 | #define IR_NEC_CTRL 0x00 | |
18 | #define IR_NEC_DATA 0x04 | |
19 | #define IR_CTRL 0x08 | |
20 | #define IR_RC5_CLK_DIV 0x0c | |
21 | #define IR_RC5_DATA 0x10 | |
22 | #define IR_INT 0x14 | |
23 | ||
24 | #define NEC_TIME_BASE 560 | |
25 | #define RC5_TIME_BASE 1778 | |
26 | ||
27 | #define RC6_CTRL 0x00 | |
28 | #define RC6_CLKDIV 0x04 | |
29 | #define RC6_DATA0 0x08 | |
30 | #define RC6_DATA1 0x0c | |
31 | #define RC6_DATA2 0x10 | |
32 | #define RC6_DATA3 0x14 | |
33 | #define RC6_DATA4 0x18 | |
34 | ||
35 | #define RC6_CARRIER 36000 | |
36 | #define RC6_TIME_BASE 16 | |
37 | ||
38 | #define NEC_CAP(n) ((n) << 24) | |
39 | #define GPIO_SEL(n) ((n) << 16) | |
40 | #define DISABLE_NEC (BIT(4) | BIT(8)) | |
41 | #define ENABLE_RC5 (BIT(0) | BIT(9)) | |
42 | #define ENABLE_RC6 (BIT(0) | BIT(7)) | |
43 | #define ACK_IR_INT (BIT(0) | BIT(1)) | |
44 | #define ACK_RC6_INT (BIT(31)) | |
45 | ||
46 | #define NEC_ANY (RC_PROTO_BIT_NEC | RC_PROTO_BIT_NECX | RC_PROTO_BIT_NEC32) | |
47 | ||
48 | struct tango_ir { | |
49 | void __iomem *rc5_base; | |
50 | void __iomem *rc6_base; | |
51 | struct rc_dev *rc; | |
52 | struct clk *clk; | |
53 | }; | |
54 | ||
55 | static void tango_ir_handle_nec(struct tango_ir *ir) | |
56 | { | |
57 | u32 v, code; | |
58 | enum rc_proto proto; | |
59 | ||
60 | v = readl_relaxed(ir->rc5_base + IR_NEC_DATA); | |
61 | if (!v) { | |
62 | rc_repeat(ir->rc); | |
63 | return; | |
64 | } | |
65 | ||
66 | code = ir_nec_bytes_to_scancode(v, v >> 8, v >> 16, v >> 24, &proto); | |
67 | rc_keydown(ir->rc, proto, code, 0); | |
68 | } | |
69 | ||
70 | static void tango_ir_handle_rc5(struct tango_ir *ir) | |
71 | { | |
72 | u32 data, field, toggle, addr, cmd, code; | |
73 | ||
74 | data = readl_relaxed(ir->rc5_base + IR_RC5_DATA); | |
75 | if (data & BIT(31)) | |
76 | return; | |
77 | ||
78 | field = data >> 12 & 1; | |
79 | toggle = data >> 11 & 1; | |
80 | addr = data >> 6 & 0x1f; | |
81 | cmd = (data & 0x3f) | (field ^ 1) << 6; | |
82 | ||
83 | code = RC_SCANCODE_RC5(addr, cmd); | |
84 | rc_keydown(ir->rc, RC_PROTO_RC5, code, toggle); | |
85 | } | |
86 | ||
87 | static void tango_ir_handle_rc6(struct tango_ir *ir) | |
88 | { | |
89 | u32 data0, data1, toggle, mode, addr, cmd, code; | |
90 | ||
91 | data0 = readl_relaxed(ir->rc6_base + RC6_DATA0); | |
92 | data1 = readl_relaxed(ir->rc6_base + RC6_DATA1); | |
93 | ||
94 | mode = data0 >> 1 & 7; | |
95 | if (mode != 0) | |
96 | return; | |
97 | ||
98 | toggle = data0 & 1; | |
99 | addr = data0 >> 16; | |
100 | cmd = data1; | |
101 | ||
102 | code = RC_SCANCODE_RC6_0(addr, cmd); | |
103 | rc_keydown(ir->rc, RC_PROTO_RC6_0, code, toggle); | |
104 | } | |
105 | ||
106 | static irqreturn_t tango_ir_irq(int irq, void *dev_id) | |
107 | { | |
108 | struct tango_ir *ir = dev_id; | |
109 | unsigned int rc5_stat; | |
110 | unsigned int rc6_stat; | |
111 | ||
112 | rc5_stat = readl_relaxed(ir->rc5_base + IR_INT); | |
113 | writel_relaxed(rc5_stat, ir->rc5_base + IR_INT); | |
114 | ||
115 | rc6_stat = readl_relaxed(ir->rc6_base + RC6_CTRL); | |
116 | writel_relaxed(rc6_stat, ir->rc6_base + RC6_CTRL); | |
117 | ||
118 | if (!(rc5_stat & 3) && !(rc6_stat & BIT(31))) | |
119 | return IRQ_NONE; | |
120 | ||
121 | if (rc5_stat & BIT(0)) | |
122 | tango_ir_handle_rc5(ir); | |
123 | ||
124 | if (rc5_stat & BIT(1)) | |
125 | tango_ir_handle_nec(ir); | |
126 | ||
127 | if (rc6_stat & BIT(31)) | |
128 | tango_ir_handle_rc6(ir); | |
129 | ||
130 | return IRQ_HANDLED; | |
131 | } | |
132 | ||
133 | static int tango_change_protocol(struct rc_dev *dev, u64 *rc_type) | |
134 | { | |
135 | struct tango_ir *ir = dev->priv; | |
136 | u32 rc5_ctrl = DISABLE_NEC; | |
137 | u32 rc6_ctrl = 0; | |
138 | ||
139 | if (*rc_type & NEC_ANY) | |
140 | rc5_ctrl = 0; | |
141 | ||
142 | if (*rc_type & RC_PROTO_BIT_RC5) | |
143 | rc5_ctrl |= ENABLE_RC5; | |
144 | ||
145 | if (*rc_type & RC_PROTO_BIT_RC6_0) | |
146 | rc6_ctrl = ENABLE_RC6; | |
147 | ||
148 | writel_relaxed(rc5_ctrl, ir->rc5_base + IR_CTRL); | |
149 | writel_relaxed(rc6_ctrl, ir->rc6_base + RC6_CTRL); | |
150 | ||
151 | return 0; | |
152 | } | |
153 | ||
154 | static int tango_ir_probe(struct platform_device *pdev) | |
155 | { | |
156 | const char *map_name = RC_MAP_TANGO; | |
157 | struct device *dev = &pdev->dev; | |
158 | struct rc_dev *rc; | |
159 | struct tango_ir *ir; | |
160 | struct resource *rc5_res; | |
161 | struct resource *rc6_res; | |
162 | u64 clkrate, clkdiv; | |
163 | int irq, err; | |
164 | u32 val; | |
165 | ||
166 | rc5_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
167 | if (!rc5_res) | |
168 | return -EINVAL; | |
169 | ||
170 | rc6_res = platform_get_resource(pdev, IORESOURCE_MEM, 1); | |
171 | if (!rc6_res) | |
172 | return -EINVAL; | |
173 | ||
174 | irq = platform_get_irq(pdev, 0); | |
175 | if (irq <= 0) | |
176 | return -EINVAL; | |
177 | ||
178 | ir = devm_kzalloc(dev, sizeof(*ir), GFP_KERNEL); | |
179 | if (!ir) | |
180 | return -ENOMEM; | |
181 | ||
182 | ir->rc5_base = devm_ioremap_resource(dev, rc5_res); | |
183 | if (IS_ERR(ir->rc5_base)) | |
184 | return PTR_ERR(ir->rc5_base); | |
185 | ||
186 | ir->rc6_base = devm_ioremap_resource(dev, rc6_res); | |
187 | if (IS_ERR(ir->rc6_base)) | |
188 | return PTR_ERR(ir->rc6_base); | |
189 | ||
190 | ir->clk = devm_clk_get(dev, NULL); | |
191 | if (IS_ERR(ir->clk)) | |
192 | return PTR_ERR(ir->clk); | |
193 | ||
194 | rc = devm_rc_allocate_device(dev, RC_DRIVER_SCANCODE); | |
195 | if (!rc) | |
196 | return -ENOMEM; | |
197 | ||
198 | of_property_read_string(dev->of_node, "linux,rc-map-name", &map_name); | |
199 | ||
200 | rc->device_name = DRIVER_NAME; | |
201 | rc->driver_name = DRIVER_NAME; | |
202 | rc->input_phys = DRIVER_NAME "/input0"; | |
203 | rc->map_name = map_name; | |
204 | rc->allowed_protocols = NEC_ANY | RC_PROTO_BIT_RC5 | RC_PROTO_BIT_RC6_0; | |
205 | rc->change_protocol = tango_change_protocol; | |
206 | rc->priv = ir; | |
207 | ir->rc = rc; | |
208 | ||
209 | err = clk_prepare_enable(ir->clk); | |
210 | if (err) | |
211 | return err; | |
212 | ||
213 | clkrate = clk_get_rate(ir->clk); | |
214 | ||
215 | clkdiv = clkrate * NEC_TIME_BASE; | |
216 | do_div(clkdiv, 1000000); | |
217 | ||
218 | val = NEC_CAP(31) | GPIO_SEL(12) | clkdiv; | |
219 | writel_relaxed(val, ir->rc5_base + IR_NEC_CTRL); | |
220 | ||
221 | clkdiv = clkrate * RC5_TIME_BASE; | |
222 | do_div(clkdiv, 1000000); | |
223 | ||
224 | writel_relaxed(DISABLE_NEC, ir->rc5_base + IR_CTRL); | |
225 | writel_relaxed(clkdiv, ir->rc5_base + IR_RC5_CLK_DIV); | |
226 | writel_relaxed(ACK_IR_INT, ir->rc5_base + IR_INT); | |
227 | ||
228 | clkdiv = clkrate * RC6_TIME_BASE; | |
229 | do_div(clkdiv, RC6_CARRIER); | |
230 | ||
231 | writel_relaxed(ACK_RC6_INT, ir->rc6_base + RC6_CTRL); | |
232 | writel_relaxed((clkdiv >> 2) << 18 | clkdiv, ir->rc6_base + RC6_CLKDIV); | |
233 | ||
234 | err = devm_request_irq(dev, irq, tango_ir_irq, IRQF_SHARED, | |
235 | dev_name(dev), ir); | |
236 | if (err) | |
237 | goto err_clk; | |
238 | ||
239 | err = devm_rc_register_device(dev, rc); | |
240 | if (err) | |
241 | goto err_clk; | |
242 | ||
243 | platform_set_drvdata(pdev, ir); | |
244 | return 0; | |
245 | ||
246 | err_clk: | |
247 | clk_disable_unprepare(ir->clk); | |
248 | return err; | |
249 | } | |
250 | ||
251 | static int tango_ir_remove(struct platform_device *pdev) | |
252 | { | |
253 | struct tango_ir *ir = platform_get_drvdata(pdev); | |
254 | ||
255 | clk_disable_unprepare(ir->clk); | |
256 | return 0; | |
257 | } | |
258 | ||
259 | static const struct of_device_id tango_ir_dt_ids[] = { | |
260 | { .compatible = "sigma,smp8642-ir" }, | |
261 | { } | |
262 | }; | |
263 | MODULE_DEVICE_TABLE(of, tango_ir_dt_ids); | |
264 | ||
265 | static struct platform_driver tango_ir_driver = { | |
266 | .probe = tango_ir_probe, | |
267 | .remove = tango_ir_remove, | |
268 | .driver = { | |
269 | .name = DRIVER_NAME, | |
270 | .of_match_table = tango_ir_dt_ids, | |
271 | }, | |
272 | }; | |
273 | module_platform_driver(tango_ir_driver); | |
274 | ||
275 | MODULE_DESCRIPTION("SMP86xx IR decoder driver"); | |
276 | MODULE_AUTHOR("Mans Rullgard <mans@mansr.com>"); | |
277 | MODULE_LICENSE("GPL"); |