1 // SPDX-License-Identifier: GPL-2.0-or-later
3 * Apple Motion Sensor driver
5 * Copyright (C) 2005 Stelian Pop (stelian@popies.net)
6 * Copyright (C) 2006 Michael Hanselmann (linux-kernel@hansmi.ch)
9 #include <linux/module.h>
10 #include <linux/types.h>
11 #include <linux/errno.h>
12 #include <linux/init.h>
13 #include <linux/of_platform.h>
14 #include <asm/pmac_pfunc.h>
18 /* There is only one motion sensor per machine */
22 module_param(verbose
, bool, 0644);
23 MODULE_PARM_DESC(verbose
, "Show free falls and shocks in kernel output");
25 /* Call with ams_info.lock held! */
26 void ams_sensors(s8
*x
, s8
*y
, s8
*z
)
28 u32 orient
= ams_info
.vflag
? ams_info
.orient1
: ams_info
.orient2
;
32 ams_info
.get_xyz(y
, x
, z
);
34 ams_info
.get_xyz(x
, y
, z
);
44 static ssize_t
ams_show_current(struct device
*dev
,
45 struct device_attribute
*attr
, char *buf
)
49 mutex_lock(&ams_info
.lock
);
50 ams_sensors(&x
, &y
, &z
);
51 mutex_unlock(&ams_info
.lock
);
53 return snprintf(buf
, PAGE_SIZE
, "%d %d %d\n", x
, y
, z
);
56 static DEVICE_ATTR(current
, S_IRUGO
, ams_show_current
, NULL
);
58 static void ams_handle_irq(void *data
)
60 enum ams_irq irq
= *((enum ams_irq
*)data
);
62 spin_lock(&ams_info
.irq_lock
);
64 ams_info
.worker_irqs
|= irq
;
65 schedule_work(&ams_info
.worker
);
67 spin_unlock(&ams_info
.irq_lock
);
70 static enum ams_irq ams_freefall_irq_data
= AMS_IRQ_FREEFALL
;
71 static struct pmf_irq_client ams_freefall_client
= {
73 .handler
= ams_handle_irq
,
74 .data
= &ams_freefall_irq_data
,
77 static enum ams_irq ams_shock_irq_data
= AMS_IRQ_SHOCK
;
78 static struct pmf_irq_client ams_shock_client
= {
80 .handler
= ams_handle_irq
,
81 .data
= &ams_shock_irq_data
,
84 /* Once hard disk parking is implemented in the kernel, this function can
87 static void ams_worker(struct work_struct
*work
)
92 mutex_lock(&ams_info
.lock
);
94 spin_lock_irqsave(&ams_info
.irq_lock
, flags
);
95 irqs_to_clear
= ams_info
.worker_irqs
;
97 if (ams_info
.worker_irqs
& AMS_IRQ_FREEFALL
) {
99 printk(KERN_INFO
"ams: freefall detected!\n");
101 ams_info
.worker_irqs
&= ~AMS_IRQ_FREEFALL
;
104 if (ams_info
.worker_irqs
& AMS_IRQ_SHOCK
) {
106 printk(KERN_INFO
"ams: shock detected!\n");
108 ams_info
.worker_irqs
&= ~AMS_IRQ_SHOCK
;
111 spin_unlock_irqrestore(&ams_info
.irq_lock
, flags
);
113 ams_info
.clear_irq(irqs_to_clear
);
115 mutex_unlock(&ams_info
.lock
);
118 /* Call with ams_info.lock held! */
119 int ams_sensor_attach(void)
124 /* Get orientation */
125 prop
= of_get_property(ams_info
.of_node
, "orientation", NULL
);
128 ams_info
.orient1
= *prop
;
129 ams_info
.orient2
= *(prop
+ 1);
131 /* Register freefall interrupt handler */
132 result
= pmf_register_irq_client(ams_info
.of_node
,
134 &ams_freefall_client
);
138 /* Reset saved irqs */
139 ams_info
.worker_irqs
= 0;
141 /* Register shock interrupt handler */
142 result
= pmf_register_irq_client(ams_info
.of_node
,
146 goto release_freefall
;
149 ams_info
.of_dev
= of_platform_device_create(ams_info
.of_node
, "ams", NULL
);
150 if (!ams_info
.of_dev
) {
155 /* Create attributes */
156 result
= device_create_file(&ams_info
.of_dev
->dev
, &dev_attr_current
);
160 ams_info
.vflag
= !!(ams_info
.get_vendor() & 0x10);
162 /* Init input device */
163 result
= ams_input_init();
165 goto release_device_file
;
169 device_remove_file(&ams_info
.of_dev
->dev
, &dev_attr_current
);
171 of_device_unregister(ams_info
.of_dev
);
173 pmf_unregister_irq_client(&ams_shock_client
);
175 pmf_unregister_irq_client(&ams_freefall_client
);
179 int __init
ams_init(void)
181 struct device_node
*np
;
183 spin_lock_init(&ams_info
.irq_lock
);
184 mutex_init(&ams_info
.lock
);
185 INIT_WORK(&ams_info
.worker
, ams_worker
);
187 #ifdef CONFIG_SENSORS_AMS_I2C
188 np
= of_find_node_by_name(NULL
, "accelerometer");
189 if (np
&& of_device_is_compatible(np
, "AAPL,accelerometer_1"))
190 /* Found I2C motion sensor */
191 return ams_i2c_init(np
);
194 #ifdef CONFIG_SENSORS_AMS_PMU
195 np
= of_find_node_by_name(NULL
, "sms");
196 if (np
&& of_device_is_compatible(np
, "sms"))
197 /* Found PMU motion sensor */
198 return ams_pmu_init(np
);
203 void ams_sensor_detach(void)
205 /* Remove input device */
208 /* Remove attributes */
209 device_remove_file(&ams_info
.of_dev
->dev
, &dev_attr_current
);
211 /* Flush interrupt worker
213 * We do this after ams_info.exit(), because an interrupt might
214 * have arrived before disabling them.
216 flush_work(&ams_info
.worker
);
219 of_device_unregister(ams_info
.of_dev
);
222 pmf_unregister_irq_client(&ams_shock_client
);
223 pmf_unregister_irq_client(&ams_freefall_client
);
226 static void __exit
ams_exit(void)
228 /* Shut down implementation */
232 MODULE_AUTHOR("Stelian Pop, Michael Hanselmann");
233 MODULE_DESCRIPTION("Apple Motion Sensor driver");
234 MODULE_LICENSE("GPL");
236 module_init(ams_init
);
237 module_exit(ams_exit
);