aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/macintosh/ams
diff options
context:
space:
mode:
authorJean Delvare <khali@linux-fr.org>2010-10-28 14:31:50 -0400
committerJean Delvare <khali@endymion.delvare>2010-10-28 14:31:50 -0400
commitbd5f47ec961594b1091839333600008f8166fd00 (patch)
treeaa4296edffd81e2976d20d3ea30ea18108b2a4de /drivers/macintosh/ams
parent6dfee85397a47063291fe199eaf950bee7944454 (diff)
Move ams driver to macintosh
The ams driver isn't a hardware monitoring driver, so it shouldn't live under driver/hwmon. drivers/macintosh seems much more appropriate, as the driver is only useful on PowerBooks and iBooks. Signed-off-by: Jean Delvare <khali@linux-fr.org> Cc: Guenter Roeck <guenter.roeck@ericsson.com> Cc: Stelian Pop <stelian@popies.net> Cc: Michael Hanselmann <linux-kernel@hansmi.ch> Cc: Benjamin Herrenschmidt <benh@kernel.crashing.org> Cc: Grant Likely <grant.likely@secretlab.ca>
Diffstat (limited to 'drivers/macintosh/ams')
-rw-r--r--drivers/macintosh/ams/Makefile8
-rw-r--r--drivers/macintosh/ams/ams-core.c250
-rw-r--r--drivers/macintosh/ams/ams-i2c.c277
-rw-r--r--drivers/macintosh/ams/ams-input.c157
-rw-r--r--drivers/macintosh/ams/ams-pmu.c201
-rw-r--r--drivers/macintosh/ams/ams.h70
6 files changed, 963 insertions, 0 deletions
diff --git a/drivers/macintosh/ams/Makefile b/drivers/macintosh/ams/Makefile
new file mode 100644
index 000000000000..41c95b2089dc
--- /dev/null
+++ b/drivers/macintosh/ams/Makefile
@@ -0,0 +1,8 @@
1#
2# Makefile for Apple Motion Sensor driver
3#
4
5ams-y := ams-core.o ams-input.o
6ams-$(CONFIG_SENSORS_AMS_PMU) += ams-pmu.o
7ams-$(CONFIG_SENSORS_AMS_I2C) += ams-i2c.o
8obj-$(CONFIG_SENSORS_AMS) += ams.o
diff --git a/drivers/macintosh/ams/ams-core.c b/drivers/macintosh/ams/ams-core.c
new file mode 100644
index 000000000000..2ad62c339cd2
--- /dev/null
+++ b/drivers/macintosh/ams/ams-core.c
@@ -0,0 +1,250 @@
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>
26#include <linux/of_platform.h>
27#include <asm/pmac_pfunc.h>
28
29#include "ams.h"
30
31/* There is only one motion sensor per machine */
32struct ams ams_info;
33
34static unsigned int verbose;
35module_param(verbose, bool, 0644);
36MODULE_PARM_DESC(verbose, "Show free falls and shocks in kernel output");
37
38/* Call with ams_info.lock held! */
39void 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
57static 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
69static DEVICE_ATTR(current, S_IRUGO, ams_show_current, NULL);
70
71static 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
83static enum ams_irq ams_freefall_irq_data = AMS_IRQ_FREEFALL;
84static struct pmf_irq_client ams_freefall_client = {
85 .owner = THIS_MODULE,
86 .handler = ams_handle_irq,
87 .data = &ams_freefall_irq_data,
88};
89
90static enum ams_irq ams_shock_irq_data = AMS_IRQ_SHOCK;
91static 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 */
100static void ams_worker(struct work_struct *work)
101{
102 unsigned long flags;
103 u8 irqs_to_clear;
104
105 mutex_lock(&ams_info.lock);
106
107 spin_lock_irqsave(&ams_info.irq_lock, flags);
108 irqs_to_clear = ams_info.worker_irqs;
109
110 if (ams_info.worker_irqs & AMS_IRQ_FREEFALL) {
111 if (verbose)
112 printk(KERN_INFO "ams: freefall detected!\n");
113
114 ams_info.worker_irqs &= ~AMS_IRQ_FREEFALL;
115 }
116
117 if (ams_info.worker_irqs & AMS_IRQ_SHOCK) {
118 if (verbose)
119 printk(KERN_INFO "ams: shock detected!\n");
120
121 ams_info.worker_irqs &= ~AMS_IRQ_SHOCK;
122 }
123
124 spin_unlock_irqrestore(&ams_info.irq_lock, flags);
125
126 ams_info.clear_irq(irqs_to_clear);
127
128 mutex_unlock(&ams_info.lock);
129}
130
131/* Call with ams_info.lock held! */
132int ams_sensor_attach(void)
133{
134 int result;
135 const u32 *prop;
136
137 /* Get orientation */
138 prop = of_get_property(ams_info.of_node, "orientation", NULL);
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;
181release_device_file:
182 device_remove_file(&ams_info.of_dev->dev, &dev_attr_current);
183release_of:
184 of_device_unregister(ams_info.of_dev);
185release_shock:
186 pmf_unregister_irq_client(&ams_shock_client);
187release_freefall:
188 pmf_unregister_irq_client(&ams_freefall_client);
189 return result;
190}
191
192int __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");
202 if (np && of_device_is_compatible(np, "AAPL,accelerometer_1"))
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");
209 if (np && of_device_is_compatible(np, "sms"))
210 /* Found PMU motion sensor */
211 return ams_pmu_init(np);
212#endif
213 return -ENODEV;
214}
215
216void ams_sensor_detach(void)
217{
218 /* Remove input device */
219 ams_input_exit();
220
221 /* Remove attributes */
222 device_remove_file(&ams_info.of_dev->dev, &dev_attr_current);
223
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();
230
231 /* Remove device */
232 of_device_unregister(ams_info.of_dev);
233
234 /* Remove handler */
235 pmf_unregister_irq_client(&ams_shock_client);
236 pmf_unregister_irq_client(&ams_freefall_client);
237}
238
239static void __exit ams_exit(void)
240{
241 /* Shut down implementation */
242 ams_info.exit();
243}
244
245MODULE_AUTHOR("Stelian Pop, Michael Hanselmann");
246MODULE_DESCRIPTION("Apple Motion Sensor driver");
247MODULE_LICENSE("GPL");
248
249module_init(ams_init);
250module_exit(ams_exit);
diff --git a/drivers/macintosh/ams/ams-i2c.c b/drivers/macintosh/ams/ams-i2c.c
new file mode 100644
index 000000000000..abeecd27b484
--- /dev/null
+++ b/drivers/macintosh/ams/ams-i2c.c
@@ -0,0 +1,277 @@
1/*
2 * Apple Motion Sensor driver (I2C variant)
3 *
4 * Copyright (C) 2005 Stelian Pop (stelian@popies.net)
5 * Copyright (C) 2006 Michael Hanselmann (linux-kernel@hansmi.ch)
6 *
7 * Clean room implementation based on the reverse engineered Mac OS X driver by
8 * Johannes Berg <johannes@sipsolutions.net>, documentation available at
9 * http://johannes.sipsolutions.net/PowerBook/Apple_Motion_Sensor_Specification
10 *
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 */
16
17#include <linux/module.h>
18#include <linux/types.h>
19#include <linux/errno.h>
20#include <linux/init.h>
21#include <linux/delay.h>
22
23#include "ams.h"
24
25/* AMS registers */
26#define AMS_COMMAND 0x00 /* command register */
27#define AMS_STATUS 0x01 /* status register */
28#define AMS_CTRL1 0x02 /* read control 1 (number of values) */
29#define AMS_CTRL2 0x03 /* read control 2 (offset?) */
30#define AMS_CTRL3 0x04 /* read control 3 (size of each value?) */
31#define AMS_DATA1 0x05 /* read data 1 */
32#define AMS_DATA2 0x06 /* read data 2 */
33#define AMS_DATA3 0x07 /* read data 3 */
34#define AMS_DATA4 0x08 /* read data 4 */
35#define AMS_DATAX 0x20 /* data X */
36#define AMS_DATAY 0x21 /* data Y */
37#define AMS_DATAZ 0x22 /* data Z */
38#define AMS_FREEFALL 0x24 /* freefall int control */
39#define AMS_SHOCK 0x25 /* shock int control */
40#define AMS_SENSLOW 0x26 /* sensitivity low limit */
41#define AMS_SENSHIGH 0x27 /* sensitivity high limit */
42#define AMS_CTRLX 0x28 /* control X */
43#define AMS_CTRLY 0x29 /* control Y */
44#define AMS_CTRLZ 0x2A /* control Z */
45#define AMS_UNKNOWN1 0x2B /* unknown 1 */
46#define AMS_UNKNOWN2 0x2C /* unknown 2 */
47#define AMS_UNKNOWN3 0x2D /* unknown 3 */
48#define AMS_VENDOR 0x2E /* vendor */
49
50/* AMS commands - use with the AMS_COMMAND register */
51enum ams_i2c_cmd {
52 AMS_CMD_NOOP = 0,
53 AMS_CMD_VERSION,
54 AMS_CMD_READMEM,
55 AMS_CMD_WRITEMEM,
56 AMS_CMD_ERASEMEM,
57 AMS_CMD_READEE,
58 AMS_CMD_WRITEEE,
59 AMS_CMD_RESET,
60 AMS_CMD_START,
61};
62
63static int ams_i2c_probe(struct i2c_client *client,
64 const struct i2c_device_id *id);
65static int ams_i2c_remove(struct i2c_client *client);
66
67static const struct i2c_device_id ams_id[] = {
68 { "ams", 0 },
69 { }
70};
71MODULE_DEVICE_TABLE(i2c, ams_id);
72
73static struct i2c_driver ams_i2c_driver = {
74 .driver = {
75 .name = "ams",
76 .owner = THIS_MODULE,
77 },
78 .probe = ams_i2c_probe,
79 .remove = ams_i2c_remove,
80 .id_table = ams_id,
81};
82
83static s32 ams_i2c_read(u8 reg)
84{
85 return i2c_smbus_read_byte_data(ams_info.i2c_client, reg);
86}
87
88static int ams_i2c_write(u8 reg, u8 value)
89{
90 return i2c_smbus_write_byte_data(ams_info.i2c_client, reg, value);
91}
92
93static int ams_i2c_cmd(enum ams_i2c_cmd cmd)
94{
95 s32 result;
96 int count = 3;
97
98 ams_i2c_write(AMS_COMMAND, cmd);
99 msleep(5);
100
101 while (count--) {
102 result = ams_i2c_read(AMS_COMMAND);
103 if (result == 0 || result & 0x80)
104 return 0;
105
106 schedule_timeout_uninterruptible(HZ / 20);
107 }
108
109 return -1;
110}
111
112static void ams_i2c_set_irq(enum ams_irq reg, char enable)
113{
114 if (reg & AMS_IRQ_FREEFALL) {
115 u8 val = ams_i2c_read(AMS_CTRLX);
116 if (enable)
117 val |= 0x80;
118 else
119 val &= ~0x80;
120 ams_i2c_write(AMS_CTRLX, val);
121 }
122
123 if (reg & AMS_IRQ_SHOCK) {
124 u8 val = ams_i2c_read(AMS_CTRLY);
125 if (enable)
126 val |= 0x80;
127 else
128 val &= ~0x80;
129 ams_i2c_write(AMS_CTRLY, val);
130 }
131
132 if (reg & AMS_IRQ_GLOBAL) {
133 u8 val = ams_i2c_read(AMS_CTRLZ);
134 if (enable)
135 val |= 0x80;
136 else
137 val &= ~0x80;
138 ams_i2c_write(AMS_CTRLZ, val);
139 }
140}
141
142static void ams_i2c_clear_irq(enum ams_irq reg)
143{
144 if (reg & AMS_IRQ_FREEFALL)
145 ams_i2c_write(AMS_FREEFALL, 0);
146
147 if (reg & AMS_IRQ_SHOCK)
148 ams_i2c_write(AMS_SHOCK, 0);
149}
150
151static u8 ams_i2c_get_vendor(void)
152{
153 return ams_i2c_read(AMS_VENDOR);
154}
155
156static void ams_i2c_get_xyz(s8 *x, s8 *y, s8 *z)
157{
158 *x = ams_i2c_read(AMS_DATAX);
159 *y = ams_i2c_read(AMS_DATAY);
160 *z = ams_i2c_read(AMS_DATAZ);
161}
162
163static int ams_i2c_probe(struct i2c_client *client,
164 const struct i2c_device_id *id)
165{
166 int vmaj, vmin;
167 int result;
168
169 /* There can be only one */
170 if (unlikely(ams_info.has_device))
171 return -ENODEV;
172
173 ams_info.i2c_client = client;
174
175 if (ams_i2c_cmd(AMS_CMD_RESET)) {
176 printk(KERN_INFO "ams: Failed to reset the device\n");
177 return -ENODEV;
178 }
179
180 if (ams_i2c_cmd(AMS_CMD_START)) {
181 printk(KERN_INFO "ams: Failed to start the device\n");
182 return -ENODEV;
183 }
184
185 /* get version/vendor information */
186 ams_i2c_write(AMS_CTRL1, 0x02);
187 ams_i2c_write(AMS_CTRL2, 0x85);
188 ams_i2c_write(AMS_CTRL3, 0x01);
189
190 ams_i2c_cmd(AMS_CMD_READMEM);
191
192 vmaj = ams_i2c_read(AMS_DATA1);
193 vmin = ams_i2c_read(AMS_DATA2);
194 if (vmaj != 1 || vmin != 52) {
195 printk(KERN_INFO "ams: Incorrect device version (%d.%d)\n",
196 vmaj, vmin);
197 return -ENODEV;
198 }
199
200 ams_i2c_cmd(AMS_CMD_VERSION);
201
202 vmaj = ams_i2c_read(AMS_DATA1);
203 vmin = ams_i2c_read(AMS_DATA2);
204 if (vmaj != 0 || vmin != 1) {
205 printk(KERN_INFO "ams: Incorrect firmware version (%d.%d)\n",
206 vmaj, vmin);
207 return -ENODEV;
208 }
209
210 /* Disable interrupts */
211 ams_i2c_set_irq(AMS_IRQ_ALL, 0);
212
213 result = ams_sensor_attach();
214 if (result < 0)
215 return result;
216
217 /* Set default values */
218 ams_i2c_write(AMS_SENSLOW, 0x15);
219 ams_i2c_write(AMS_SENSHIGH, 0x60);
220 ams_i2c_write(AMS_CTRLX, 0x08);
221 ams_i2c_write(AMS_CTRLY, 0x0F);
222 ams_i2c_write(AMS_CTRLZ, 0x4F);
223 ams_i2c_write(AMS_UNKNOWN1, 0x14);
224
225 /* Clear interrupts */
226 ams_i2c_clear_irq(AMS_IRQ_ALL);
227
228 ams_info.has_device = 1;
229
230 /* Enable interrupts */
231 ams_i2c_set_irq(AMS_IRQ_ALL, 1);
232
233 printk(KERN_INFO "ams: Found I2C based motion sensor\n");
234
235 return 0;
236}
237
238static int ams_i2c_remove(struct i2c_client *client)
239{
240 if (ams_info.has_device) {
241 ams_sensor_detach();
242
243 /* Disable interrupts */
244 ams_i2c_set_irq(AMS_IRQ_ALL, 0);
245
246 /* Clear interrupts */
247 ams_i2c_clear_irq(AMS_IRQ_ALL);
248
249 printk(KERN_INFO "ams: Unloading\n");
250
251 ams_info.has_device = 0;
252 }
253
254 return 0;
255}
256
257static void ams_i2c_exit(void)
258{
259 i2c_del_driver(&ams_i2c_driver);
260}
261
262int __init ams_i2c_init(struct device_node *np)
263{
264 int result;
265
266 /* Set implementation stuff */
267 ams_info.of_node = np;
268 ams_info.exit = ams_i2c_exit;
269 ams_info.get_vendor = ams_i2c_get_vendor;
270 ams_info.get_xyz = ams_i2c_get_xyz;
271 ams_info.clear_irq = ams_i2c_clear_irq;
272 ams_info.bustype = BUS_I2C;
273
274 result = i2c_add_driver(&ams_i2c_driver);
275
276 return result;
277}
diff --git a/drivers/macintosh/ams/ams-input.c b/drivers/macintosh/ams/ams-input.c
new file mode 100644
index 000000000000..8a712392cd38
--- /dev/null
+++ b/drivers/macintosh/ams/ams-input.c
@@ -0,0 +1,157 @@
1/*
2 * Apple Motion Sensor driver (joystick emulation)
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
13#include <linux/module.h>
14
15#include <linux/types.h>
16#include <linux/errno.h>
17#include <linux/init.h>
18#include <linux/delay.h>
19
20#include "ams.h"
21
22static unsigned int joystick;
23module_param(joystick, bool, S_IRUGO);
24MODULE_PARM_DESC(joystick, "Enable the input class device on module load");
25
26static unsigned int invert;
27module_param(invert, bool, S_IWUSR | S_IRUGO);
28MODULE_PARM_DESC(invert, "Invert input data on X and Y axis");
29
30static DEFINE_MUTEX(ams_input_mutex);
31
32static void ams_idev_poll(struct input_polled_dev *dev)
33{
34 struct input_dev *idev = dev->input;
35 s8 x, y, z;
36
37 mutex_lock(&ams_info.lock);
38
39 ams_sensors(&x, &y, &z);
40
41 x -= ams_info.xcalib;
42 y -= ams_info.ycalib;
43 z -= ams_info.zcalib;
44
45 input_report_abs(idev, ABS_X, invert ? -x : x);
46 input_report_abs(idev, ABS_Y, invert ? -y : y);
47 input_report_abs(idev, ABS_Z, z);
48
49 input_sync(idev);
50
51 mutex_unlock(&ams_info.lock);
52}
53
54/* Call with ams_info.lock held! */
55static int ams_input_enable(void)
56{
57 struct input_dev *input;
58 s8 x, y, z;
59 int error;
60
61 ams_sensors(&x, &y, &z);
62 ams_info.xcalib = x;
63 ams_info.ycalib = y;
64 ams_info.zcalib = z;
65
66 ams_info.idev = input_allocate_polled_device();
67 if (!ams_info.idev)
68 return -ENOMEM;
69
70 ams_info.idev->poll = ams_idev_poll;
71 ams_info.idev->poll_interval = 25;
72
73 input = ams_info.idev->input;
74 input->name = "Apple Motion Sensor";
75 input->id.bustype = ams_info.bustype;
76 input->id.vendor = 0;
77 input->dev.parent = &ams_info.of_dev->dev;
78
79 input_set_abs_params(input, ABS_X, -50, 50, 3, 0);
80 input_set_abs_params(input, ABS_Y, -50, 50, 3, 0);
81 input_set_abs_params(input, ABS_Z, -50, 50, 3, 0);
82
83 set_bit(EV_ABS, input->evbit);
84 set_bit(EV_KEY, input->evbit);
85 set_bit(BTN_TOUCH, input->keybit);
86
87 error = input_register_polled_device(ams_info.idev);
88 if (error) {
89 input_free_polled_device(ams_info.idev);
90 ams_info.idev = NULL;
91 return error;
92 }
93
94 joystick = 1;
95
96 return 0;
97}
98
99static void ams_input_disable(void)
100{
101 if (ams_info.idev) {
102 input_unregister_polled_device(ams_info.idev);
103 input_free_polled_device(ams_info.idev);
104 ams_info.idev = NULL;
105 }
106
107 joystick = 0;
108}
109
110static ssize_t ams_input_show_joystick(struct device *dev,
111 struct device_attribute *attr, char *buf)
112{
113 return sprintf(buf, "%d\n", joystick);
114}
115
116static ssize_t ams_input_store_joystick(struct device *dev,
117 struct device_attribute *attr, const char *buf, size_t count)
118{
119 unsigned long enable;
120 int error = 0;
121
122 if (strict_strtoul(buf, 0, &enable) || enable > 1)
123 return -EINVAL;
124
125 mutex_lock(&ams_input_mutex);
126
127 if (enable != joystick) {
128 if (enable)
129 error = ams_input_enable();
130 else
131 ams_input_disable();
132 }
133
134 mutex_unlock(&ams_input_mutex);
135
136 return error ? error : count;
137}
138
139static DEVICE_ATTR(joystick, S_IRUGO | S_IWUSR,
140 ams_input_show_joystick, ams_input_store_joystick);
141
142int ams_input_init(void)
143{
144 if (joystick)
145 ams_input_enable();
146
147 return device_create_file(&ams_info.of_dev->dev, &dev_attr_joystick);
148}
149
150void ams_input_exit(void)
151{
152 device_remove_file(&ams_info.of_dev->dev, &dev_attr_joystick);
153
154 mutex_lock(&ams_input_mutex);
155 ams_input_disable();
156 mutex_unlock(&ams_input_mutex);
157}
diff --git a/drivers/macintosh/ams/ams-pmu.c b/drivers/macintosh/ams/ams-pmu.c
new file mode 100644
index 000000000000..4f61b3ee1b08
--- /dev/null
+++ b/drivers/macintosh/ams/ams-pmu.c
@@ -0,0 +1,201 @@
1/*
2 * Apple Motion Sensor driver (PMU variant)
3 *
4 * Copyright (C) 2006 Michael Hanselmann (linux-kernel@hansmi.ch)
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 */
11
12#include <linux/module.h>
13#include <linux/types.h>
14#include <linux/errno.h>
15#include <linux/init.h>
16#include <linux/adb.h>
17#include <linux/pmu.h>
18
19#include "ams.h"
20
21/* Attitude */
22#define AMS_X 0x00
23#define AMS_Y 0x01
24#define AMS_Z 0x02
25
26/* Not exactly known, maybe chip vendor */
27#define AMS_VENDOR 0x03
28
29/* Freefall registers */
30#define AMS_FF_CLEAR 0x04
31#define AMS_FF_ENABLE 0x05
32#define AMS_FF_LOW_LIMIT 0x06
33#define AMS_FF_DEBOUNCE 0x07
34
35/* Shock registers */
36#define AMS_SHOCK_CLEAR 0x08
37#define AMS_SHOCK_ENABLE 0x09
38#define AMS_SHOCK_HIGH_LIMIT 0x0a
39#define AMS_SHOCK_DEBOUNCE 0x0b
40
41/* Global interrupt and power control register */
42#define AMS_CONTROL 0x0c
43
44static u8 ams_pmu_cmd;
45
46static void ams_pmu_req_complete(struct adb_request *req)
47{
48 complete((struct completion *)req->arg);
49}
50
51/* Only call this function from task context */
52static void ams_pmu_set_register(u8 reg, u8 value)
53{
54 static struct adb_request req;
55 DECLARE_COMPLETION(req_complete);
56
57 req.arg = &req_complete;
58 if (pmu_request(&req, ams_pmu_req_complete, 4, ams_pmu_cmd, 0x00, reg, value))
59 return;
60
61 wait_for_completion(&req_complete);
62}
63
64/* Only call this function from task context */
65static u8 ams_pmu_get_register(u8 reg)
66{
67 static struct adb_request req;
68 DECLARE_COMPLETION(req_complete);
69
70 req.arg = &req_complete;
71 if (pmu_request(&req, ams_pmu_req_complete, 3, ams_pmu_cmd, 0x01, reg))
72 return 0;
73
74 wait_for_completion(&req_complete);
75
76 if (req.reply_len > 0)
77 return req.reply[0];
78 else
79 return 0;
80}
81
82/* Enables or disables the specified interrupts */
83static void ams_pmu_set_irq(enum ams_irq reg, char enable)
84{
85 if (reg & AMS_IRQ_FREEFALL) {
86 u8 val = ams_pmu_get_register(AMS_FF_ENABLE);
87 if (enable)
88 val |= 0x80;
89 else
90 val &= ~0x80;
91 ams_pmu_set_register(AMS_FF_ENABLE, val);
92 }
93
94 if (reg & AMS_IRQ_SHOCK) {
95 u8 val = ams_pmu_get_register(AMS_SHOCK_ENABLE);
96 if (enable)
97 val |= 0x80;
98 else
99 val &= ~0x80;
100 ams_pmu_set_register(AMS_SHOCK_ENABLE, val);
101 }
102
103 if (reg & AMS_IRQ_GLOBAL) {
104 u8 val = ams_pmu_get_register(AMS_CONTROL);
105 if (enable)
106 val |= 0x80;
107 else
108 val &= ~0x80;
109 ams_pmu_set_register(AMS_CONTROL, val);
110 }
111}
112
113static void ams_pmu_clear_irq(enum ams_irq reg)
114{
115 if (reg & AMS_IRQ_FREEFALL)
116 ams_pmu_set_register(AMS_FF_CLEAR, 0x00);
117
118 if (reg & AMS_IRQ_SHOCK)
119 ams_pmu_set_register(AMS_SHOCK_CLEAR, 0x00);
120}
121
122static u8 ams_pmu_get_vendor(void)
123{
124 return ams_pmu_get_register(AMS_VENDOR);
125}
126
127static void ams_pmu_get_xyz(s8 *x, s8 *y, s8 *z)
128{
129 *x = ams_pmu_get_register(AMS_X);
130 *y = ams_pmu_get_register(AMS_Y);
131 *z = ams_pmu_get_register(AMS_Z);
132}
133
134static void ams_pmu_exit(void)
135{
136 ams_sensor_detach();
137
138 /* Disable interrupts */
139 ams_pmu_set_irq(AMS_IRQ_ALL, 0);
140
141 /* Clear interrupts */
142 ams_pmu_clear_irq(AMS_IRQ_ALL);
143
144 ams_info.has_device = 0;
145
146 printk(KERN_INFO "ams: Unloading\n");
147}
148
149int __init ams_pmu_init(struct device_node *np)
150{
151 const u32 *prop;
152 int result;
153
154 /* Set implementation stuff */
155 ams_info.of_node = np;
156 ams_info.exit = ams_pmu_exit;
157 ams_info.get_vendor = ams_pmu_get_vendor;
158 ams_info.get_xyz = ams_pmu_get_xyz;
159 ams_info.clear_irq = ams_pmu_clear_irq;
160 ams_info.bustype = BUS_HOST;
161
162 /* Get PMU command, should be 0x4e, but we can never know */
163 prop = of_get_property(ams_info.of_node, "reg", NULL);
164 if (!prop)
165 return -ENODEV;
166
167 ams_pmu_cmd = ((*prop) >> 8) & 0xff;
168
169 /* Disable interrupts */
170 ams_pmu_set_irq(AMS_IRQ_ALL, 0);
171
172 /* Clear interrupts */
173 ams_pmu_clear_irq(AMS_IRQ_ALL);
174
175 result = ams_sensor_attach();
176 if (result < 0)
177 return result;
178
179 /* Set default values */
180 ams_pmu_set_register(AMS_FF_LOW_LIMIT, 0x15);
181 ams_pmu_set_register(AMS_FF_ENABLE, 0x08);
182 ams_pmu_set_register(AMS_FF_DEBOUNCE, 0x14);
183
184 ams_pmu_set_register(AMS_SHOCK_HIGH_LIMIT, 0x60);
185 ams_pmu_set_register(AMS_SHOCK_ENABLE, 0x0f);
186 ams_pmu_set_register(AMS_SHOCK_DEBOUNCE, 0x14);
187
188 ams_pmu_set_register(AMS_CONTROL, 0x4f);
189
190 /* Clear interrupts */
191 ams_pmu_clear_irq(AMS_IRQ_ALL);
192
193 ams_info.has_device = 1;
194
195 /* Enable interrupts */
196 ams_pmu_set_irq(AMS_IRQ_ALL, 1);
197
198 printk(KERN_INFO "ams: Found PMU based motion sensor\n");
199
200 return 0;
201}
diff --git a/drivers/macintosh/ams/ams.h b/drivers/macintosh/ams/ams.h
new file mode 100644
index 000000000000..90f094d45450
--- /dev/null
+++ b/drivers/macintosh/ams/ams.h
@@ -0,0 +1,70 @@
1#include <linux/i2c.h>
2#include <linux/input-polldev.h>
3#include <linux/kthread.h>
4#include <linux/mutex.h>
5#include <linux/spinlock.h>
6#include <linux/types.h>
7#include <linux/of_device.h>
8
9enum ams_irq {
10 AMS_IRQ_FREEFALL = 0x01,
11 AMS_IRQ_SHOCK = 0x02,
12 AMS_IRQ_GLOBAL = 0x04,
13 AMS_IRQ_ALL =
14 AMS_IRQ_FREEFALL |
15 AMS_IRQ_SHOCK |
16 AMS_IRQ_GLOBAL,
17};
18
19struct ams {
20 /* Locks */
21 spinlock_t irq_lock;
22 struct mutex lock;
23
24 /* General properties */
25 struct device_node *of_node;
26 struct platform_device *of_dev;
27 char has_device;
28 char vflag;
29 u32 orient1;
30 u32 orient2;
31
32 /* Interrupt worker */
33 struct work_struct worker;
34 u8 worker_irqs;
35
36 /* Implementation
37 *
38 * Only call these functions with the main lock held.
39 */
40 void (*exit)(void);
41
42 void (*get_xyz)(s8 *x, s8 *y, s8 *z);
43 u8 (*get_vendor)(void);
44
45 void (*clear_irq)(enum ams_irq reg);
46
47#ifdef CONFIG_SENSORS_AMS_I2C
48 /* I2C properties */
49 struct i2c_client *i2c_client;
50#endif
51
52 /* Joystick emulation */
53 struct input_polled_dev *idev;
54 __u16 bustype;
55
56 /* calibrated null values */
57 int xcalib, ycalib, zcalib;
58};
59
60extern struct ams ams_info;
61
62extern void ams_sensors(s8 *x, s8 *y, s8 *z);
63extern int ams_sensor_attach(void);
64extern void ams_sensor_detach(void);
65
66extern int ams_pmu_init(struct device_node *np);
67extern int ams_i2c_init(struct device_node *np);
68
69extern int ams_input_init(void);
70extern void ams_input_exit(void);