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> | |
17 | ||
06458e27 JC |
18 | #include <linux/iio/iio.h> |
19 | #include <linux/iio/trigger.h> | |
ea5dbf96 MH |
20 | |
21 | struct bfin_timer { | |
22 | unsigned short id, bit; | |
23 | unsigned long irqbit; | |
24 | int irq; | |
25 | }; | |
26 | ||
27 | /* | |
28 | * this covers all hardware timer configurations on | |
29 | * all Blackfin derivatives out there today | |
30 | */ | |
31 | ||
32 | static struct bfin_timer iio_bfin_timer_code[MAX_BLACKFIN_GPTIMERS] = { | |
33 | {TIMER0_id, TIMER0bit, TIMER_STATUS_TIMIL0, IRQ_TIMER0}, | |
34 | {TIMER1_id, TIMER1bit, TIMER_STATUS_TIMIL1, IRQ_TIMER1}, | |
35 | {TIMER2_id, TIMER2bit, TIMER_STATUS_TIMIL2, IRQ_TIMER2}, | |
36 | #if (MAX_BLACKFIN_GPTIMERS > 3) | |
37 | {TIMER3_id, TIMER3bit, TIMER_STATUS_TIMIL3, IRQ_TIMER3}, | |
38 | {TIMER4_id, TIMER4bit, TIMER_STATUS_TIMIL4, IRQ_TIMER4}, | |
39 | {TIMER5_id, TIMER5bit, TIMER_STATUS_TIMIL5, IRQ_TIMER5}, | |
40 | {TIMER6_id, TIMER6bit, TIMER_STATUS_TIMIL6, IRQ_TIMER6}, | |
41 | {TIMER7_id, TIMER7bit, TIMER_STATUS_TIMIL7, IRQ_TIMER7}, | |
42 | #endif | |
43 | #if (MAX_BLACKFIN_GPTIMERS > 8) | |
44 | {TIMER8_id, TIMER8bit, TIMER_STATUS_TIMIL8, IRQ_TIMER8}, | |
45 | {TIMER9_id, TIMER9bit, TIMER_STATUS_TIMIL9, IRQ_TIMER9}, | |
46 | {TIMER10_id, TIMER10bit, TIMER_STATUS_TIMIL10, IRQ_TIMER10}, | |
47 | #if (MAX_BLACKFIN_GPTIMERS > 11) | |
48 | {TIMER11_id, TIMER11bit, TIMER_STATUS_TIMIL11, IRQ_TIMER11}, | |
49 | #endif | |
50 | #endif | |
51 | }; | |
52 | ||
53 | struct bfin_tmr_state { | |
54 | struct iio_trigger *trig; | |
55 | struct bfin_timer *t; | |
56 | unsigned timer_num; | |
57 | int irq; | |
58 | }; | |
59 | ||
60 | static ssize_t iio_bfin_tmr_frequency_store(struct device *dev, | |
61 | struct device_attribute *attr, const char *buf, size_t count) | |
62 | { | |
63 | struct iio_trigger *trig = dev_get_drvdata(dev); | |
64 | struct bfin_tmr_state *st = trig->private_data; | |
65 | long val; | |
66 | int ret; | |
67 | ||
68 | ret = strict_strtoul(buf, 10, &val); | |
69 | if (ret) | |
70 | goto error_ret; | |
71 | ||
72 | if (val > 100000) { | |
73 | ret = -EINVAL; | |
74 | goto error_ret; | |
75 | } | |
76 | ||
77 | disable_gptimers(st->t->bit); | |
78 | ||
79 | if (!val) | |
80 | goto error_ret; | |
81 | ||
82 | val = get_sclk() / val; | |
83 | if (val <= 4) { | |
84 | ret = -EINVAL; | |
85 | goto error_ret; | |
86 | } | |
87 | ||
88 | set_gptimer_period(st->t->id, val); | |
89 | set_gptimer_pwidth(st->t->id, 1); | |
90 | enable_gptimers(st->t->bit); | |
91 | ||
92 | error_ret: | |
93 | return ret ? ret : count; | |
94 | } | |
95 | ||
96 | static ssize_t iio_bfin_tmr_frequency_show(struct device *dev, | |
97 | struct device_attribute *attr, | |
98 | char *buf) | |
99 | { | |
100 | struct iio_trigger *trig = dev_get_drvdata(dev); | |
101 | struct bfin_tmr_state *st = trig->private_data; | |
102 | ||
103 | return sprintf(buf, "%lu\n", | |
104 | get_sclk() / get_gptimer_period(st->t->id)); | |
105 | } | |
106 | ||
107 | static DEVICE_ATTR(frequency, S_IRUGO | S_IWUSR, iio_bfin_tmr_frequency_show, | |
108 | iio_bfin_tmr_frequency_store); | |
ea5dbf96 MH |
109 | |
110 | static struct attribute *iio_bfin_tmr_trigger_attrs[] = { | |
111 | &dev_attr_frequency.attr, | |
ea5dbf96 MH |
112 | NULL, |
113 | }; | |
114 | ||
115 | static const struct attribute_group iio_bfin_tmr_trigger_attr_group = { | |
116 | .attrs = iio_bfin_tmr_trigger_attrs, | |
117 | }; | |
118 | ||
59c85e82 JC |
119 | static const struct attribute_group *iio_bfin_tmr_trigger_attr_groups[] = { |
120 | &iio_bfin_tmr_trigger_attr_group, | |
121 | NULL | |
122 | }; | |
123 | ||
ea5dbf96 MH |
124 | |
125 | static irqreturn_t iio_bfin_tmr_trigger_isr(int irq, void *devid) | |
126 | { | |
127 | struct bfin_tmr_state *st = devid; | |
128 | ||
129 | clear_gptimer_intr(st->t->id); | |
130 | iio_trigger_poll(st->trig, 0); | |
131 | ||
132 | return IRQ_HANDLED; | |
133 | } | |
134 | ||
135 | static int iio_bfin_tmr_get_number(int irq) | |
136 | { | |
137 | int i; | |
138 | ||
139 | for (i = 0; i < MAX_BLACKFIN_GPTIMERS; i++) | |
140 | if (iio_bfin_timer_code[i].irq == irq) | |
141 | return i; | |
142 | ||
143 | return -ENODEV; | |
144 | } | |
145 | ||
d29f73db | 146 | static const struct iio_trigger_ops iio_bfin_tmr_trigger_ops = { |
dafb7d1b | 147 | .owner = THIS_MODULE, |
d29f73db JC |
148 | }; |
149 | ||
ea5dbf96 MH |
150 | static int __devinit iio_bfin_tmr_trigger_probe(struct platform_device *pdev) |
151 | { | |
152 | struct bfin_tmr_state *st; | |
153 | int ret; | |
154 | ||
155 | st = kzalloc(sizeof(*st), GFP_KERNEL); | |
156 | if (st == NULL) { | |
157 | ret = -ENOMEM; | |
158 | goto out; | |
159 | } | |
160 | ||
161 | st->irq = platform_get_irq(pdev, 0); | |
162 | if (!st->irq) { | |
163 | dev_err(&pdev->dev, "No IRQs specified"); | |
164 | ret = -ENODEV; | |
165 | goto out1; | |
166 | } | |
167 | ||
168 | ret = iio_bfin_tmr_get_number(st->irq); | |
169 | if (ret < 0) | |
170 | goto out1; | |
171 | ||
172 | st->timer_num = ret; | |
173 | st->t = &iio_bfin_timer_code[st->timer_num]; | |
174 | ||
7cbb7537 | 175 | st->trig = iio_trigger_alloc("bfintmr%d", st->timer_num); |
ea5dbf96 MH |
176 | if (!st->trig) { |
177 | ret = -ENOMEM; | |
178 | goto out1; | |
179 | } | |
180 | ||
181 | st->trig->private_data = st; | |
d29f73db | 182 | st->trig->ops = &iio_bfin_tmr_trigger_ops; |
59c85e82 | 183 | st->trig->dev.groups = iio_bfin_tmr_trigger_attr_groups; |
ea5dbf96 MH |
184 | ret = iio_trigger_register(st->trig); |
185 | if (ret) | |
59c85e82 | 186 | goto out2; |
ea5dbf96 MH |
187 | |
188 | ret = request_irq(st->irq, iio_bfin_tmr_trigger_isr, | |
189 | 0, st->trig->name, st); | |
190 | if (ret) { | |
191 | dev_err(&pdev->dev, | |
192 | "request IRQ-%d failed", st->irq); | |
193 | goto out4; | |
194 | } | |
195 | ||
196 | set_gptimer_config(st->t->id, OUT_DIS | PWM_OUT | PERIOD_CNT | IRQ_ENA); | |
197 | ||
198 | dev_info(&pdev->dev, "iio trigger Blackfin TMR%d, IRQ-%d", | |
199 | st->timer_num, st->irq); | |
200 | platform_set_drvdata(pdev, st); | |
201 | ||
202 | return 0; | |
203 | out4: | |
204 | iio_trigger_unregister(st->trig); | |
ea5dbf96 | 205 | out2: |
7cbb7537 | 206 | iio_trigger_put(st->trig); |
ea5dbf96 MH |
207 | out1: |
208 | kfree(st); | |
209 | out: | |
210 | return ret; | |
211 | } | |
212 | ||
213 | static int __devexit iio_bfin_tmr_trigger_remove(struct platform_device *pdev) | |
214 | { | |
215 | struct bfin_tmr_state *st = platform_get_drvdata(pdev); | |
216 | ||
217 | disable_gptimers(st->t->bit); | |
218 | free_irq(st->irq, st); | |
219 | iio_trigger_unregister(st->trig); | |
7cbb7537 | 220 | iio_trigger_put(st->trig); |
ea5dbf96 MH |
221 | kfree(st); |
222 | ||
223 | return 0; | |
224 | } | |
225 | ||
226 | static struct platform_driver iio_bfin_tmr_trigger_driver = { | |
227 | .driver = { | |
228 | .name = "iio_bfin_tmr_trigger", | |
229 | .owner = THIS_MODULE, | |
230 | }, | |
231 | .probe = iio_bfin_tmr_trigger_probe, | |
232 | .remove = __devexit_p(iio_bfin_tmr_trigger_remove), | |
233 | }; | |
234 | ||
5f953732 | 235 | module_platform_driver(iio_bfin_tmr_trigger_driver); |
ea5dbf96 MH |
236 | |
237 | MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); | |
238 | MODULE_DESCRIPTION("Blackfin system timer based trigger for the iio subsystem"); | |
239 | MODULE_LICENSE("GPL v2"); | |
240 | MODULE_ALIAS("platform:iio-trig-bfin-timer"); |