Commit | Line | Data |
---|---|---|
dcb69dd0 SP |
1 | /* |
2 | * Apple Motion Sensor driver | |
3 | * | |
4 | * Copyright (C) 2005 Stelian Pop (stelian@popies.net) | |
5 | * Copyright (C) 2006 Michael Hanselmann (linux-kernel@hansmi.ch) | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or modify | |
8 | * it under the terms of the GNU General Public License as published by | |
9 | * the Free Software Foundation; either version 2 of the License, or | |
10 | * (at your option) any later version. | |
11 | * | |
12 | * This program is distributed in the hope that it will be useful, | |
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 | * GNU General Public License for more details. | |
16 | * | |
17 | * You should have received a copy of the GNU General Public License | |
18 | * along with this program; if not, write to the Free Software | |
19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |
20 | */ | |
21 | ||
22 | #include <linux/module.h> | |
23 | #include <linux/types.h> | |
24 | #include <linux/errno.h> | |
25 | #include <linux/init.h> | |
7587cb2f | 26 | #include <linux/of_platform.h> |
dcb69dd0 | 27 | #include <asm/pmac_pfunc.h> |
dcb69dd0 SP |
28 | |
29 | #include "ams.h" | |
30 | ||
31 | /* There is only one motion sensor per machine */ | |
32 | struct ams ams_info; | |
33 | ||
34 | static unsigned int verbose; | |
35 | module_param(verbose, bool, 0644); | |
36 | MODULE_PARM_DESC(verbose, "Show free falls and shocks in kernel output"); | |
37 | ||
38 | /* Call with ams_info.lock held! */ | |
39 | void ams_sensors(s8 *x, s8 *y, s8 *z) | |
40 | { | |
41 | u32 orient = ams_info.vflag? ams_info.orient1 : ams_info.orient2; | |
42 | ||
43 | if (orient & 0x80) | |
44 | /* X and Y swapped */ | |
45 | ams_info.get_xyz(y, x, z); | |
46 | else | |
47 | ams_info.get_xyz(x, y, z); | |
48 | ||
49 | if (orient & 0x04) | |
50 | *z = ~(*z); | |
51 | if (orient & 0x02) | |
52 | *y = ~(*y); | |
53 | if (orient & 0x01) | |
54 | *x = ~(*x); | |
55 | } | |
56 | ||
57 | static ssize_t ams_show_current(struct device *dev, | |
58 | struct device_attribute *attr, char *buf) | |
59 | { | |
60 | s8 x, y, z; | |
61 | ||
62 | mutex_lock(&ams_info.lock); | |
63 | ams_sensors(&x, &y, &z); | |
64 | mutex_unlock(&ams_info.lock); | |
65 | ||
66 | return snprintf(buf, PAGE_SIZE, "%d %d %d\n", x, y, z); | |
67 | } | |
68 | ||
69 | static DEVICE_ATTR(current, S_IRUGO, ams_show_current, NULL); | |
70 | ||
71 | static void ams_handle_irq(void *data) | |
72 | { | |
73 | enum ams_irq irq = *((enum ams_irq *)data); | |
74 | ||
75 | spin_lock(&ams_info.irq_lock); | |
76 | ||
77 | ams_info.worker_irqs |= irq; | |
78 | schedule_work(&ams_info.worker); | |
79 | ||
80 | spin_unlock(&ams_info.irq_lock); | |
81 | } | |
82 | ||
83 | static enum ams_irq ams_freefall_irq_data = AMS_IRQ_FREEFALL; | |
84 | static struct pmf_irq_client ams_freefall_client = { | |
85 | .owner = THIS_MODULE, | |
86 | .handler = ams_handle_irq, | |
87 | .data = &ams_freefall_irq_data, | |
88 | }; | |
89 | ||
90 | static enum ams_irq ams_shock_irq_data = AMS_IRQ_SHOCK; | |
91 | static struct pmf_irq_client ams_shock_client = { | |
92 | .owner = THIS_MODULE, | |
93 | .handler = ams_handle_irq, | |
94 | .data = &ams_shock_irq_data, | |
95 | }; | |
96 | ||
97 | /* Once hard disk parking is implemented in the kernel, this function can | |
98 | * trigger it. | |
99 | */ | |
100 | static void ams_worker(struct work_struct *work) | |
101 | { | |
dbee3562 DT |
102 | unsigned long flags; |
103 | u8 irqs_to_clear; | |
dcb69dd0 | 104 | |
dbee3562 | 105 | mutex_lock(&ams_info.lock); |
dcb69dd0 | 106 | |
dbee3562 DT |
107 | spin_lock_irqsave(&ams_info.irq_lock, flags); |
108 | irqs_to_clear = ams_info.worker_irqs; | |
dcb69dd0 | 109 | |
dbee3562 DT |
110 | if (ams_info.worker_irqs & AMS_IRQ_FREEFALL) { |
111 | if (verbose) | |
112 | printk(KERN_INFO "ams: freefall detected!\n"); | |
dcb69dd0 | 113 | |
dbee3562 DT |
114 | ams_info.worker_irqs &= ~AMS_IRQ_FREEFALL; |
115 | } | |
dcb69dd0 | 116 | |
dbee3562 DT |
117 | if (ams_info.worker_irqs & AMS_IRQ_SHOCK) { |
118 | if (verbose) | |
119 | printk(KERN_INFO "ams: shock detected!\n"); | |
dcb69dd0 | 120 | |
dbee3562 DT |
121 | ams_info.worker_irqs &= ~AMS_IRQ_SHOCK; |
122 | } | |
dcb69dd0 | 123 | |
dbee3562 | 124 | spin_unlock_irqrestore(&ams_info.irq_lock, flags); |
dcb69dd0 | 125 | |
dbee3562 | 126 | ams_info.clear_irq(irqs_to_clear); |
dcb69dd0 SP |
127 | |
128 | mutex_unlock(&ams_info.lock); | |
129 | } | |
130 | ||
131 | /* Call with ams_info.lock held! */ | |
132 | int ams_sensor_attach(void) | |
133 | { | |
134 | int result; | |
a7edd0e6 | 135 | const u32 *prop; |
dcb69dd0 SP |
136 | |
137 | /* Get orientation */ | |
40cd3a45 | 138 | prop = of_get_property(ams_info.of_node, "orientation", NULL); |
dcb69dd0 SP |
139 | if (!prop) |
140 | return -ENODEV; | |
141 | ams_info.orient1 = *prop; | |
142 | ams_info.orient2 = *(prop + 1); | |
143 | ||
144 | /* Register freefall interrupt handler */ | |
145 | result = pmf_register_irq_client(ams_info.of_node, | |
146 | "accel-int-1", | |
147 | &ams_freefall_client); | |
148 | if (result < 0) | |
149 | return -ENODEV; | |
150 | ||
151 | /* Reset saved irqs */ | |
152 | ams_info.worker_irqs = 0; | |
153 | ||
154 | /* Register shock interrupt handler */ | |
155 | result = pmf_register_irq_client(ams_info.of_node, | |
156 | "accel-int-2", | |
157 | &ams_shock_client); | |
158 | if (result < 0) | |
159 | goto release_freefall; | |
160 | ||
161 | /* Create device */ | |
162 | ams_info.of_dev = of_platform_device_create(ams_info.of_node, "ams", NULL); | |
163 | if (!ams_info.of_dev) { | |
164 | result = -ENODEV; | |
165 | goto release_shock; | |
166 | } | |
167 | ||
168 | /* Create attributes */ | |
169 | result = device_create_file(&ams_info.of_dev->dev, &dev_attr_current); | |
170 | if (result) | |
171 | goto release_of; | |
172 | ||
173 | ams_info.vflag = !!(ams_info.get_vendor() & 0x10); | |
174 | ||
175 | /* Init input device */ | |
176 | result = ams_input_init(); | |
177 | if (result) | |
178 | goto release_device_file; | |
179 | ||
180 | return result; | |
181 | release_device_file: | |
182 | device_remove_file(&ams_info.of_dev->dev, &dev_attr_current); | |
183 | release_of: | |
184 | of_device_unregister(ams_info.of_dev); | |
185 | release_shock: | |
186 | pmf_unregister_irq_client(&ams_shock_client); | |
187 | release_freefall: | |
188 | pmf_unregister_irq_client(&ams_freefall_client); | |
189 | return result; | |
190 | } | |
191 | ||
192 | int __init ams_init(void) | |
193 | { | |
194 | struct device_node *np; | |
195 | ||
196 | spin_lock_init(&ams_info.irq_lock); | |
197 | mutex_init(&ams_info.lock); | |
198 | INIT_WORK(&ams_info.worker, ams_worker); | |
199 | ||
200 | #ifdef CONFIG_SENSORS_AMS_I2C | |
201 | np = of_find_node_by_name(NULL, "accelerometer"); | |
55b61fec | 202 | if (np && of_device_is_compatible(np, "AAPL,accelerometer_1")) |
dcb69dd0 SP |
203 | /* Found I2C motion sensor */ |
204 | return ams_i2c_init(np); | |
205 | #endif | |
206 | ||
207 | #ifdef CONFIG_SENSORS_AMS_PMU | |
208 | np = of_find_node_by_name(NULL, "sms"); | |
55b61fec | 209 | if (np && of_device_is_compatible(np, "sms")) |
dcb69dd0 SP |
210 | /* Found PMU motion sensor */ |
211 | return ams_pmu_init(np); | |
212 | #endif | |
dcb69dd0 SP |
213 | return -ENODEV; |
214 | } | |
215 | ||
98ceb75c | 216 | void ams_sensor_detach(void) |
dcb69dd0 | 217 | { |
ee4cd32e DT |
218 | /* Remove input device */ |
219 | ams_input_exit(); | |
dcb69dd0 | 220 | |
ee4cd32e DT |
221 | /* Remove attributes */ |
222 | device_remove_file(&ams_info.of_dev->dev, &dev_attr_current); | |
dcb69dd0 | 223 | |
ee4cd32e DT |
224 | /* Flush interrupt worker |
225 | * | |
226 | * We do this after ams_info.exit(), because an interrupt might | |
227 | * have arrived before disabling them. | |
228 | */ | |
229 | flush_scheduled_work(); | |
dcb69dd0 | 230 | |
ee4cd32e DT |
231 | /* Remove device */ |
232 | of_device_unregister(ams_info.of_dev); | |
dcb69dd0 | 233 | |
ee4cd32e DT |
234 | /* Remove handler */ |
235 | pmf_unregister_irq_client(&ams_shock_client); | |
236 | pmf_unregister_irq_client(&ams_freefall_client); | |
dcb69dd0 SP |
237 | } |
238 | ||
98ceb75c JD |
239 | static void __exit ams_exit(void) |
240 | { | |
241 | /* Shut down implementation */ | |
242 | ams_info.exit(); | |
243 | } | |
244 | ||
dcb69dd0 SP |
245 | MODULE_AUTHOR("Stelian Pop, Michael Hanselmann"); |
246 | MODULE_DESCRIPTION("Apple Motion Sensor driver"); | |
247 | MODULE_LICENSE("GPL"); | |
248 | ||
249 | module_init(ams_init); | |
250 | module_exit(ams_exit); |