Commit | Line | Data |
---|---|---|
48329582 MH |
1 | /* |
2 | * Rotary counter driver for Analog Devices Blackfin Processors | |
3 | * | |
4 | * Copyright 2008-2009 Analog Devices Inc. | |
5 | * Licensed under the GPL-2 or later. | |
6 | */ | |
7 | ||
8 | #include <linux/module.h> | |
48329582 | 9 | #include <linux/interrupt.h> |
71adf22f | 10 | #include <linux/io.h> |
48329582 MH |
11 | #include <linux/irq.h> |
12 | #include <linux/pm.h> | |
13 | #include <linux/platform_device.h> | |
14 | #include <linux/input.h> | |
5a0e3ad6 | 15 | #include <linux/slab.h> |
1ea74014 | 16 | #include <linux/platform_data/bfin_rotary.h> |
48329582 MH |
17 | |
18 | #include <asm/portmux.h> | |
48329582 | 19 | |
71adf22f SZ |
20 | #define CNT_CONFIG_OFF 0 /* CNT Config Offset */ |
21 | #define CNT_IMASK_OFF 4 /* CNT Interrupt Mask Offset */ | |
22 | #define CNT_STATUS_OFF 8 /* CNT Status Offset */ | |
23 | #define CNT_COMMAND_OFF 12 /* CNT Command Offset */ | |
24 | #define CNT_DEBOUNCE_OFF 16 /* CNT Debounce Offset */ | |
25 | #define CNT_COUNTER_OFF 20 /* CNT Counter Offset */ | |
26 | #define CNT_MAX_OFF 24 /* CNT Maximum Count Offset */ | |
27 | #define CNT_MIN_OFF 28 /* CNT Minimum Count Offset */ | |
28 | ||
48329582 MH |
29 | struct bfin_rot { |
30 | struct input_dev *input; | |
71adf22f | 31 | void __iomem *base; |
48329582 MH |
32 | int irq; |
33 | unsigned int up_key; | |
34 | unsigned int down_key; | |
35 | unsigned int button_key; | |
36 | unsigned int rel_code; | |
c8af781e DT |
37 | |
38 | unsigned short mode; | |
39 | unsigned short debounce; | |
40 | ||
48329582 MH |
41 | unsigned short cnt_config; |
42 | unsigned short cnt_imask; | |
43 | unsigned short cnt_debounce; | |
44 | }; | |
45 | ||
46 | static void report_key_event(struct input_dev *input, int keycode) | |
47 | { | |
48 | /* simulate a press-n-release */ | |
49 | input_report_key(input, keycode, 1); | |
50 | input_sync(input); | |
51 | input_report_key(input, keycode, 0); | |
52 | input_sync(input); | |
53 | } | |
54 | ||
55 | static void report_rotary_event(struct bfin_rot *rotary, int delta) | |
56 | { | |
57 | struct input_dev *input = rotary->input; | |
58 | ||
59 | if (rotary->up_key) { | |
60 | report_key_event(input, | |
61 | delta > 0 ? rotary->up_key : rotary->down_key); | |
62 | } else { | |
63 | input_report_rel(input, rotary->rel_code, delta); | |
64 | input_sync(input); | |
65 | } | |
66 | } | |
67 | ||
68 | static irqreturn_t bfin_rotary_isr(int irq, void *dev_id) | |
69 | { | |
7694f44d | 70 | struct bfin_rot *rotary = dev_id; |
48329582 MH |
71 | int delta; |
72 | ||
71adf22f | 73 | switch (readw(rotary->base + CNT_STATUS_OFF)) { |
48329582 MH |
74 | |
75 | case ICII: | |
76 | break; | |
77 | ||
78 | case UCII: | |
79 | case DCII: | |
71adf22f | 80 | delta = readl(rotary->base + CNT_COUNTER_OFF); |
48329582 MH |
81 | if (delta) |
82 | report_rotary_event(rotary, delta); | |
83 | break; | |
84 | ||
85 | case CZMII: | |
86 | report_key_event(rotary->input, rotary->button_key); | |
87 | break; | |
88 | ||
89 | default: | |
90 | break; | |
91 | } | |
92 | ||
71adf22f SZ |
93 | writew(W1LCNT_ZERO, rotary->base + CNT_COMMAND_OFF); /* Clear COUNTER */ |
94 | writew(-1, rotary->base + CNT_STATUS_OFF); /* Clear STATUS */ | |
48329582 MH |
95 | |
96 | return IRQ_HANDLED; | |
97 | } | |
98 | ||
c8af781e DT |
99 | static int bfin_rotary_open(struct input_dev *input) |
100 | { | |
101 | struct bfin_rot *rotary = input_get_drvdata(input); | |
102 | unsigned short val; | |
103 | ||
104 | if (rotary->mode & ROT_DEBE) | |
105 | writew(rotary->debounce & DPRESCALE, | |
106 | rotary->base + CNT_DEBOUNCE_OFF); | |
107 | ||
108 | writew(rotary->mode & ~CNTE, rotary->base + CNT_CONFIG_OFF); | |
109 | ||
110 | val = UCIE | DCIE; | |
111 | if (rotary->button_key) | |
112 | val |= CZMIE; | |
113 | writew(val, rotary->base + CNT_IMASK_OFF); | |
114 | ||
115 | writew(rotary->mode | CNTE, rotary->base + CNT_CONFIG_OFF); | |
116 | ||
117 | return 0; | |
118 | } | |
119 | ||
120 | static void bfin_rotary_close(struct input_dev *input) | |
121 | { | |
122 | struct bfin_rot *rotary = input_get_drvdata(input); | |
123 | ||
124 | writew(0, rotary->base + CNT_CONFIG_OFF); | |
125 | writew(0, rotary->base + CNT_IMASK_OFF); | |
126 | } | |
127 | ||
f14d4df9 SZ |
128 | static void bfin_rotary_free_action(void *data) |
129 | { | |
130 | peripheral_free_list(data); | |
131 | } | |
132 | ||
5298cc4c | 133 | static int bfin_rotary_probe(struct platform_device *pdev) |
48329582 | 134 | { |
71adf22f | 135 | struct device *dev = &pdev->dev; |
f14d4df9 | 136 | const struct bfin_rotary_platform_data *pdata = dev_get_platdata(dev); |
48329582 | 137 | struct bfin_rot *rotary; |
71adf22f | 138 | struct resource *res; |
48329582 MH |
139 | struct input_dev *input; |
140 | int error; | |
141 | ||
142 | /* Basic validation */ | |
143 | if ((pdata->rotary_up_key && !pdata->rotary_down_key) || | |
144 | (!pdata->rotary_up_key && pdata->rotary_down_key)) { | |
145 | return -EINVAL; | |
146 | } | |
147 | ||
5ea0699a SZ |
148 | if (pdata->pin_list) { |
149 | error = peripheral_request_list(pdata->pin_list, | |
150 | dev_name(&pdev->dev)); | |
151 | if (error) { | |
f14d4df9 SZ |
152 | dev_err(dev, "requesting peripherals failed: %d\n", |
153 | error); | |
5ea0699a SZ |
154 | return error; |
155 | } | |
48329582 | 156 | |
f14d4df9 SZ |
157 | error = devm_add_action(dev, bfin_rotary_free_action, |
158 | pdata->pin_list); | |
159 | if (error) { | |
160 | dev_err(dev, "setting cleanup action failed: %d\n", | |
161 | error); | |
162 | peripheral_free_list(pdata->pin_list); | |
163 | return error; | |
164 | } | |
48329582 MH |
165 | } |
166 | ||
f14d4df9 SZ |
167 | rotary = devm_kzalloc(dev, sizeof(struct bfin_rot), GFP_KERNEL); |
168 | if (!rotary) | |
169 | return -ENOMEM; | |
170 | ||
71adf22f SZ |
171 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
172 | rotary->base = devm_ioremap_resource(dev, res); | |
f14d4df9 SZ |
173 | if (IS_ERR(rotary->base)) |
174 | return PTR_ERR(rotary->base); | |
175 | ||
176 | input = devm_input_allocate_device(dev); | |
177 | if (!input) | |
178 | return -ENOMEM; | |
71adf22f | 179 | |
48329582 MH |
180 | rotary->input = input; |
181 | ||
182 | rotary->up_key = pdata->rotary_up_key; | |
183 | rotary->down_key = pdata->rotary_down_key; | |
184 | rotary->button_key = pdata->rotary_button_key; | |
185 | rotary->rel_code = pdata->rotary_rel_code; | |
186 | ||
c8af781e DT |
187 | rotary->mode = pdata->mode; |
188 | rotary->debounce = pdata->debounce; | |
189 | ||
48329582 MH |
190 | input->name = pdev->name; |
191 | input->phys = "bfin-rotary/input0"; | |
192 | input->dev.parent = &pdev->dev; | |
193 | ||
194 | input_set_drvdata(input, rotary); | |
195 | ||
196 | input->id.bustype = BUS_HOST; | |
197 | input->id.vendor = 0x0001; | |
198 | input->id.product = 0x0001; | |
199 | input->id.version = 0x0100; | |
200 | ||
c8af781e DT |
201 | input->open = bfin_rotary_open; |
202 | input->close = bfin_rotary_close; | |
203 | ||
48329582 MH |
204 | if (rotary->up_key) { |
205 | __set_bit(EV_KEY, input->evbit); | |
206 | __set_bit(rotary->up_key, input->keybit); | |
207 | __set_bit(rotary->down_key, input->keybit); | |
208 | } else { | |
209 | __set_bit(EV_REL, input->evbit); | |
210 | __set_bit(rotary->rel_code, input->relbit); | |
211 | } | |
212 | ||
213 | if (rotary->button_key) { | |
214 | __set_bit(EV_KEY, input->evbit); | |
215 | __set_bit(rotary->button_key, input->keybit); | |
216 | } | |
217 | ||
c8af781e DT |
218 | /* Quiesce the device before requesting irq */ |
219 | bfin_rotary_close(input); | |
220 | ||
f14d4df9 SZ |
221 | rotary->irq = platform_get_irq(pdev, 0); |
222 | if (rotary->irq < 0) { | |
223 | dev_err(dev, "No rotary IRQ specified\n"); | |
224 | return -ENOENT; | |
225 | } | |
226 | ||
227 | error = devm_request_irq(dev, rotary->irq, bfin_rotary_isr, | |
228 | 0, dev_name(dev), rotary); | |
48329582 | 229 | if (error) { |
f14d4df9 | 230 | dev_err(dev, "unable to claim irq %d; error %d\n", |
48329582 | 231 | rotary->irq, error); |
f14d4df9 | 232 | return error; |
48329582 MH |
233 | } |
234 | ||
235 | error = input_register_device(input); | |
236 | if (error) { | |
f14d4df9 SZ |
237 | dev_err(dev, "unable to register input device (%d)\n", error); |
238 | return error; | |
48329582 MH |
239 | } |
240 | ||
48329582 MH |
241 | platform_set_drvdata(pdev, rotary); |
242 | device_init_wakeup(&pdev->dev, 1); | |
243 | ||
244 | return 0; | |
48329582 MH |
245 | } |
246 | ||
5ec662e7 | 247 | static int __maybe_unused bfin_rotary_suspend(struct device *dev) |
48329582 MH |
248 | { |
249 | struct platform_device *pdev = to_platform_device(dev); | |
250 | struct bfin_rot *rotary = platform_get_drvdata(pdev); | |
251 | ||
71adf22f SZ |
252 | rotary->cnt_config = readw(rotary->base + CNT_CONFIG_OFF); |
253 | rotary->cnt_imask = readw(rotary->base + CNT_IMASK_OFF); | |
254 | rotary->cnt_debounce = readw(rotary->base + CNT_DEBOUNCE_OFF); | |
48329582 MH |
255 | |
256 | if (device_may_wakeup(&pdev->dev)) | |
257 | enable_irq_wake(rotary->irq); | |
258 | ||
259 | return 0; | |
260 | } | |
261 | ||
5ec662e7 | 262 | static int __maybe_unused bfin_rotary_resume(struct device *dev) |
48329582 MH |
263 | { |
264 | struct platform_device *pdev = to_platform_device(dev); | |
265 | struct bfin_rot *rotary = platform_get_drvdata(pdev); | |
266 | ||
71adf22f SZ |
267 | writew(rotary->cnt_debounce, rotary->base + CNT_DEBOUNCE_OFF); |
268 | writew(rotary->cnt_imask, rotary->base + CNT_IMASK_OFF); | |
269 | writew(rotary->cnt_config & ~CNTE, rotary->base + CNT_CONFIG_OFF); | |
48329582 MH |
270 | |
271 | if (device_may_wakeup(&pdev->dev)) | |
272 | disable_irq_wake(rotary->irq); | |
273 | ||
274 | if (rotary->cnt_config & CNTE) | |
71adf22f | 275 | writew(rotary->cnt_config, rotary->base + CNT_CONFIG_OFF); |
48329582 MH |
276 | |
277 | return 0; | |
278 | } | |
279 | ||
5ec662e7 DT |
280 | static SIMPLE_DEV_PM_OPS(bfin_rotary_pm_ops, |
281 | bfin_rotary_suspend, bfin_rotary_resume); | |
48329582 MH |
282 | |
283 | static struct platform_driver bfin_rotary_device_driver = { | |
284 | .probe = bfin_rotary_probe, | |
48329582 MH |
285 | .driver = { |
286 | .name = "bfin-rotary", | |
48329582 | 287 | .pm = &bfin_rotary_pm_ops, |
48329582 MH |
288 | }, |
289 | }; | |
840a746b | 290 | module_platform_driver(bfin_rotary_device_driver); |
48329582 MH |
291 | |
292 | MODULE_LICENSE("GPL"); | |
293 | MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); | |
294 | MODULE_DESCRIPTION("Rotary Counter driver for Blackfin Processors"); | |
295 | MODULE_ALIAS("platform:bfin-rotary"); |