Commit | Line | Data |
---|---|---|
ea5dbf96 MH |
1 | /* |
2 | * Copyright 2011 Analog Devices Inc. | |
3 | * | |
4 | * Licensed under the GPL-2. | |
5 | * | |
6 | */ | |
7 | ||
8 | #include <linux/kernel.h> | |
9 | #include <linux/module.h> | |
10 | #include <linux/platform_device.h> | |
11 | #include <linux/slab.h> | |
12 | #include <linux/interrupt.h> | |
13 | #include <linux/irq.h> | |
14 | #include <linux/delay.h> | |
15 | ||
16 | #include <asm/gptimers.h> | |
587a5124 | 17 | #include <asm/portmux.h> |
ea5dbf96 | 18 | |
06458e27 JC |
19 | #include <linux/iio/iio.h> |
20 | #include <linux/iio/trigger.h> | |
ea5dbf96 | 21 | |
587a5124 LPC |
22 | #include "iio-trig-bfin-timer.h" |
23 | ||
ea5dbf96 MH |
24 | struct bfin_timer { |
25 | unsigned short id, bit; | |
26 | unsigned long irqbit; | |
27 | int irq; | |
587a5124 | 28 | int pin; |
ea5dbf96 MH |
29 | }; |
30 | ||
31 | /* | |
32 | * this covers all hardware timer configurations on | |
33 | * all Blackfin derivatives out there today | |
34 | */ | |
35 | ||
36 | static struct bfin_timer iio_bfin_timer_code[MAX_BLACKFIN_GPTIMERS] = { | |
587a5124 LPC |
37 | {TIMER0_id, TIMER0bit, TIMER_STATUS_TIMIL0, IRQ_TIMER0, P_TMR0}, |
38 | {TIMER1_id, TIMER1bit, TIMER_STATUS_TIMIL1, IRQ_TIMER1, P_TMR1}, | |
39 | {TIMER2_id, TIMER2bit, TIMER_STATUS_TIMIL2, IRQ_TIMER2, P_TMR2}, | |
ea5dbf96 | 40 | #if (MAX_BLACKFIN_GPTIMERS > 3) |
587a5124 LPC |
41 | {TIMER3_id, TIMER3bit, TIMER_STATUS_TIMIL3, IRQ_TIMER3, P_TMR3}, |
42 | {TIMER4_id, TIMER4bit, TIMER_STATUS_TIMIL4, IRQ_TIMER4, P_TMR4}, | |
43 | {TIMER5_id, TIMER5bit, TIMER_STATUS_TIMIL5, IRQ_TIMER5, P_TMR5}, | |
44 | {TIMER6_id, TIMER6bit, TIMER_STATUS_TIMIL6, IRQ_TIMER6, P_TMR6}, | |
45 | {TIMER7_id, TIMER7bit, TIMER_STATUS_TIMIL7, IRQ_TIMER7, P_TMR7}, | |
ea5dbf96 MH |
46 | #endif |
47 | #if (MAX_BLACKFIN_GPTIMERS > 8) | |
587a5124 LPC |
48 | {TIMER8_id, TIMER8bit, TIMER_STATUS_TIMIL8, IRQ_TIMER8, P_TMR8}, |
49 | {TIMER9_id, TIMER9bit, TIMER_STATUS_TIMIL9, IRQ_TIMER9, P_TMR9}, | |
50 | {TIMER10_id, TIMER10bit, TIMER_STATUS_TIMIL10, IRQ_TIMER10, P_TMR10}, | |
ea5dbf96 | 51 | #if (MAX_BLACKFIN_GPTIMERS > 11) |
587a5124 | 52 | {TIMER11_id, TIMER11bit, TIMER_STATUS_TIMIL11, IRQ_TIMER11, P_TMR11}, |
ea5dbf96 MH |
53 | #endif |
54 | #endif | |
55 | }; | |
56 | ||
57 | struct bfin_tmr_state { | |
58 | struct iio_trigger *trig; | |
59 | struct bfin_timer *t; | |
60 | unsigned timer_num; | |
587a5124 LPC |
61 | bool output_enable; |
62 | unsigned int duty; | |
ea5dbf96 MH |
63 | int irq; |
64 | }; | |
65 | ||
2aecc5b9 LPC |
66 | static int iio_bfin_tmr_set_state(struct iio_trigger *trig, bool state) |
67 | { | |
1e9663c6 | 68 | struct bfin_tmr_state *st = iio_trigger_get_drvdata(trig); |
2aecc5b9 LPC |
69 | |
70 | if (get_gptimer_period(st->t->id) == 0) | |
71 | return -EINVAL; | |
72 | ||
73 | if (state) | |
74 | enable_gptimers(st->t->bit); | |
75 | else | |
76 | disable_gptimers(st->t->bit); | |
77 | ||
78 | return 0; | |
79 | } | |
80 | ||
ea5dbf96 | 81 | static ssize_t iio_bfin_tmr_frequency_store(struct device *dev, |
8463f6fb CO |
82 | struct device_attribute *attr, |
83 | const char *buf, size_t count) | |
ea5dbf96 | 84 | { |
4bf81727 | 85 | struct iio_trigger *trig = to_iio_trigger(dev); |
1e9663c6 | 86 | struct bfin_tmr_state *st = iio_trigger_get_drvdata(trig); |
e5e26dd5 | 87 | unsigned int val; |
2aecc5b9 | 88 | bool enabled; |
ea5dbf96 MH |
89 | int ret; |
90 | ||
e5e26dd5 | 91 | ret = kstrtouint(buf, 10, &val); |
ea5dbf96 | 92 | if (ret) |
e5e26dd5 | 93 | return ret; |
ea5dbf96 | 94 | |
85528e88 | 95 | if (val > 100000) |
e5e26dd5 | 96 | return -EINVAL; |
ea5dbf96 | 97 | |
2aecc5b9 LPC |
98 | enabled = get_enabled_gptimers() & st->t->bit; |
99 | ||
100 | if (enabled) | |
101 | disable_gptimers(st->t->bit); | |
ea5dbf96 | 102 | |
e5e26dd5 JH |
103 | if (val == 0) |
104 | return count; | |
ea5dbf96 MH |
105 | |
106 | val = get_sclk() / val; | |
e5e26dd5 JH |
107 | if (val <= 4 || val <= st->duty) |
108 | return -EINVAL; | |
ea5dbf96 MH |
109 | |
110 | set_gptimer_period(st->t->id, val); | |
587a5124 | 111 | set_gptimer_pwidth(st->t->id, val - st->duty); |
2aecc5b9 LPC |
112 | |
113 | if (enabled) | |
114 | enable_gptimers(st->t->bit); | |
ea5dbf96 | 115 | |
e5e26dd5 | 116 | return count; |
ea5dbf96 MH |
117 | } |
118 | ||
119 | static ssize_t iio_bfin_tmr_frequency_show(struct device *dev, | |
8463f6fb CO |
120 | struct device_attribute *attr, |
121 | char *buf) | |
ea5dbf96 | 122 | { |
4bf81727 | 123 | struct iio_trigger *trig = to_iio_trigger(dev); |
1e9663c6 | 124 | struct bfin_tmr_state *st = iio_trigger_get_drvdata(trig); |
37812d2e LPC |
125 | unsigned int period = get_gptimer_period(st->t->id); |
126 | unsigned long val; | |
ea5dbf96 | 127 | |
37812d2e LPC |
128 | if (period == 0) |
129 | val = 0; | |
130 | else | |
131 | val = get_sclk() / get_gptimer_period(st->t->id); | |
132 | ||
133 | return sprintf(buf, "%lu\n", val); | |
ea5dbf96 MH |
134 | } |
135 | ||
136 | static DEVICE_ATTR(frequency, S_IRUGO | S_IWUSR, iio_bfin_tmr_frequency_show, | |
137 | iio_bfin_tmr_frequency_store); | |
ea5dbf96 MH |
138 | |
139 | static struct attribute *iio_bfin_tmr_trigger_attrs[] = { | |
140 | &dev_attr_frequency.attr, | |
ea5dbf96 MH |
141 | NULL, |
142 | }; | |
143 | ||
144 | static const struct attribute_group iio_bfin_tmr_trigger_attr_group = { | |
145 | .attrs = iio_bfin_tmr_trigger_attrs, | |
146 | }; | |
147 | ||
59c85e82 JC |
148 | static const struct attribute_group *iio_bfin_tmr_trigger_attr_groups[] = { |
149 | &iio_bfin_tmr_trigger_attr_group, | |
150 | NULL | |
151 | }; | |
152 | ||
ea5dbf96 MH |
153 | static irqreturn_t iio_bfin_tmr_trigger_isr(int irq, void *devid) |
154 | { | |
155 | struct bfin_tmr_state *st = devid; | |
156 | ||
157 | clear_gptimer_intr(st->t->id); | |
398fd22b | 158 | iio_trigger_poll(st->trig); |
ea5dbf96 MH |
159 | |
160 | return IRQ_HANDLED; | |
161 | } | |
162 | ||
163 | static int iio_bfin_tmr_get_number(int irq) | |
164 | { | |
165 | int i; | |
166 | ||
167 | for (i = 0; i < MAX_BLACKFIN_GPTIMERS; i++) | |
168 | if (iio_bfin_timer_code[i].irq == irq) | |
169 | return i; | |
170 | ||
171 | return -ENODEV; | |
172 | } | |
173 | ||
d29f73db | 174 | static const struct iio_trigger_ops iio_bfin_tmr_trigger_ops = { |
dafb7d1b | 175 | .owner = THIS_MODULE, |
2aecc5b9 | 176 | .set_trigger_state = iio_bfin_tmr_set_state, |
d29f73db JC |
177 | }; |
178 | ||
4ae1c61f | 179 | static int iio_bfin_tmr_trigger_probe(struct platform_device *pdev) |
ea5dbf96 | 180 | { |
587a5124 | 181 | struct iio_bfin_timer_trigger_pdata *pdata = pdev->dev.platform_data; |
ea5dbf96 | 182 | struct bfin_tmr_state *st; |
587a5124 | 183 | unsigned int config; |
ea5dbf96 MH |
184 | int ret; |
185 | ||
f64c3052 | 186 | st = devm_kzalloc(&pdev->dev, sizeof(*st), GFP_KERNEL); |
dc6ed26d | 187 | if (!st) |
f64c3052 | 188 | return -ENOMEM; |
ea5dbf96 MH |
189 | |
190 | st->irq = platform_get_irq(pdev, 0); | |
191 | if (!st->irq) { | |
192 | dev_err(&pdev->dev, "No IRQs specified"); | |
f64c3052 | 193 | return -ENODEV; |
ea5dbf96 MH |
194 | } |
195 | ||
196 | ret = iio_bfin_tmr_get_number(st->irq); | |
197 | if (ret < 0) | |
f64c3052 | 198 | return ret; |
ea5dbf96 MH |
199 | |
200 | st->timer_num = ret; | |
201 | st->t = &iio_bfin_timer_code[st->timer_num]; | |
202 | ||
7cbb7537 | 203 | st->trig = iio_trigger_alloc("bfintmr%d", st->timer_num); |
f64c3052 HS |
204 | if (!st->trig) |
205 | return -ENOMEM; | |
ea5dbf96 | 206 | |
d29f73db | 207 | st->trig->ops = &iio_bfin_tmr_trigger_ops; |
59c85e82 | 208 | st->trig->dev.groups = iio_bfin_tmr_trigger_attr_groups; |
1e9663c6 | 209 | iio_trigger_set_drvdata(st->trig, st); |
ea5dbf96 MH |
210 | ret = iio_trigger_register(st->trig); |
211 | if (ret) | |
f64c3052 | 212 | goto out; |
ea5dbf96 MH |
213 | |
214 | ret = request_irq(st->irq, iio_bfin_tmr_trigger_isr, | |
215 | 0, st->trig->name, st); | |
216 | if (ret) { | |
217 | dev_err(&pdev->dev, | |
218 | "request IRQ-%d failed", st->irq); | |
f64c3052 | 219 | goto out1; |
ea5dbf96 MH |
220 | } |
221 | ||
587a5124 LPC |
222 | config = PWM_OUT | PERIOD_CNT | IRQ_ENA; |
223 | ||
224 | if (pdata && pdata->output_enable) { | |
225 | unsigned long long val; | |
226 | ||
227 | st->output_enable = true; | |
228 | ||
229 | ret = peripheral_request(st->t->pin, st->trig->name); | |
230 | if (ret) | |
231 | goto out_free_irq; | |
232 | ||
233 | val = (unsigned long long)get_sclk() * pdata->duty_ns; | |
234 | do_div(val, NSEC_PER_SEC); | |
235 | st->duty = val; | |
236 | ||
237 | /** | |
238 | * The interrupt will be generated at the end of the period, | |
239 | * since we want the interrupt to be generated at end of the | |
240 | * pulse we invert both polarity and duty cycle, so that the | |
241 | * pulse will be generated directly before the interrupt. | |
242 | */ | |
243 | if (pdata->active_low) | |
244 | config |= PULSE_HI; | |
245 | } else { | |
246 | st->duty = 1; | |
247 | config |= OUT_DIS; | |
248 | } | |
249 | ||
250 | set_gptimer_config(st->t->id, config); | |
ea5dbf96 MH |
251 | |
252 | dev_info(&pdev->dev, "iio trigger Blackfin TMR%d, IRQ-%d", | |
253 | st->timer_num, st->irq); | |
254 | platform_set_drvdata(pdev, st); | |
255 | ||
256 | return 0; | |
587a5124 LPC |
257 | out_free_irq: |
258 | free_irq(st->irq, st); | |
ea5dbf96 | 259 | out1: |
f64c3052 | 260 | iio_trigger_unregister(st->trig); |
ea5dbf96 | 261 | out: |
f64c3052 | 262 | iio_trigger_put(st->trig); |
ea5dbf96 MH |
263 | return ret; |
264 | } | |
265 | ||
447d4f29 | 266 | static int iio_bfin_tmr_trigger_remove(struct platform_device *pdev) |
ea5dbf96 MH |
267 | { |
268 | struct bfin_tmr_state *st = platform_get_drvdata(pdev); | |
269 | ||
270 | disable_gptimers(st->t->bit); | |
587a5124 LPC |
271 | if (st->output_enable) |
272 | peripheral_free(st->t->pin); | |
ea5dbf96 MH |
273 | free_irq(st->irq, st); |
274 | iio_trigger_unregister(st->trig); | |
7cbb7537 | 275 | iio_trigger_put(st->trig); |
ea5dbf96 MH |
276 | |
277 | return 0; | |
278 | } | |
279 | ||
280 | static struct platform_driver iio_bfin_tmr_trigger_driver = { | |
281 | .driver = { | |
282 | .name = "iio_bfin_tmr_trigger", | |
ea5dbf96 MH |
283 | }, |
284 | .probe = iio_bfin_tmr_trigger_probe, | |
e543acf0 | 285 | .remove = iio_bfin_tmr_trigger_remove, |
ea5dbf96 MH |
286 | }; |
287 | ||
5f953732 | 288 | module_platform_driver(iio_bfin_tmr_trigger_driver); |
ea5dbf96 MH |
289 | |
290 | MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); | |
291 | MODULE_DESCRIPTION("Blackfin system timer based trigger for the iio subsystem"); | |
292 | MODULE_LICENSE("GPL v2"); | |
293 | MODULE_ALIAS("platform:iio-trig-bfin-timer"); |