aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/misc/inv_mpu/compass/ak8972.c
diff options
context:
space:
mode:
authorJonathan Herman <hermanjl@cs.unc.edu>2013-01-22 10:38:37 -0500
committerJonathan Herman <hermanjl@cs.unc.edu>2013-01-22 10:38:37 -0500
commitfcc9d2e5a6c89d22b8b773a64fb4ad21ac318446 (patch)
treea57612d1888735a2ec7972891b68c1ac5ec8faea /drivers/misc/inv_mpu/compass/ak8972.c
parent8dea78da5cee153b8af9c07a2745f6c55057fe12 (diff)
Added missing tegra files.HEADmaster
Diffstat (limited to 'drivers/misc/inv_mpu/compass/ak8972.c')
-rw-r--r--drivers/misc/inv_mpu/compass/ak8972.c499
1 files changed, 499 insertions, 0 deletions
diff --git a/drivers/misc/inv_mpu/compass/ak8972.c b/drivers/misc/inv_mpu/compass/ak8972.c
new file mode 100644
index 00000000000..7eb15b44039
--- /dev/null
+++ b/drivers/misc/inv_mpu/compass/ak8972.c
@@ -0,0 +1,499 @@
1/*
2 $License:
3 Copyright (C) 2011 InvenSense Corporation, All Rights Reserved.
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17 $
18 */
19
20/**
21 * @addtogroup COMPASSDL
22 *
23 * @{
24 * @file ak8972.c
25 * @brief Magnetometer setup and handling methods for the AKM AK8972 compass device.
26 */
27
28/* -------------------------------------------------------------------------- */
29
30#include <linux/i2c.h>
31#include <linux/module.h>
32#include <linux/moduleparam.h>
33#include <linux/kernel.h>
34#include <linux/errno.h>
35#include <linux/slab.h>
36#include <linux/delay.h>
37#include "mpu-dev.h"
38
39#include <log.h>
40#include <linux/mpu.h>
41#include "mlsl.h"
42#include "mldl_cfg.h"
43#undef MPL_LOG_TAG
44#define MPL_LOG_TAG "MPL-compass"
45
46/* -------------------------------------------------------------------------- */
47#define AK8972_REG_ST1 (0x02)
48#define AK8972_REG_HXL (0x03)
49#define AK8972_REG_ST2 (0x09)
50
51#define AK8972_REG_CNTL (0x0A)
52#define AK8972_REG_ASAX (0x10)
53#define AK8972_REG_ASAY (0x11)
54#define AK8972_REG_ASAZ (0x12)
55
56#define AK8972_CNTL_MODE_POWER_DOWN (0x00)
57#define AK8972_CNTL_MODE_SINGLE_MEASUREMENT (0x01)
58#define AK8972_CNTL_MODE_FUSE_ROM_ACCESS (0x0f)
59
60/* -------------------------------------------------------------------------- */
61struct ak8972_config {
62 char asa[COMPASS_NUM_AXES]; /* axis sensitivity adjustment */
63};
64
65struct ak8972_private_data {
66 struct ak8972_config init;
67};
68
69/* -------------------------------------------------------------------------- */
70static int ak8972_init(void *mlsl_handle,
71 struct ext_slave_descr *slave,
72 struct ext_slave_platform_data *pdata)
73{
74 int result;
75 unsigned char serial_data[COMPASS_NUM_AXES];
76
77 struct ak8972_private_data *private_data;
78 private_data = (struct ak8972_private_data *)
79 kzalloc(sizeof(struct ak8972_private_data), GFP_KERNEL);
80
81 if (!private_data)
82 return INV_ERROR_MEMORY_EXAUSTED;
83
84 result = inv_serial_single_write(mlsl_handle, pdata->address,
85 AK8972_REG_CNTL,
86 AK8972_CNTL_MODE_POWER_DOWN);
87 if (result) {
88 LOG_RESULT_LOCATION(result);
89 return result;
90 }
91 /* Wait at least 100us */
92 udelay(100);
93
94 result = inv_serial_single_write(mlsl_handle, pdata->address,
95 AK8972_REG_CNTL,
96 AK8972_CNTL_MODE_FUSE_ROM_ACCESS);
97 if (result) {
98 LOG_RESULT_LOCATION(result);
99 return result;
100 }
101
102 /* Wait at least 200us */
103 udelay(200);
104
105 result = inv_serial_read(mlsl_handle, pdata->address,
106 AK8972_REG_ASAX,
107 COMPASS_NUM_AXES, serial_data);
108 if (result) {
109 LOG_RESULT_LOCATION(result);
110 return result;
111 }
112
113 pdata->private_data = private_data;
114
115 private_data->init.asa[0] = serial_data[0];
116 private_data->init.asa[1] = serial_data[1];
117 private_data->init.asa[2] = serial_data[2];
118
119 result = inv_serial_single_write(mlsl_handle, pdata->address,
120 AK8972_REG_CNTL,
121 AK8972_CNTL_MODE_POWER_DOWN);
122 if (result) {
123 LOG_RESULT_LOCATION(result);
124 return result;
125 }
126
127 udelay(100);
128 return INV_SUCCESS;
129}
130
131static int ak8972_exit(void *mlsl_handle,
132 struct ext_slave_descr *slave,
133 struct ext_slave_platform_data *pdata)
134{
135 kfree(pdata->private_data);
136 return INV_SUCCESS;
137}
138
139static int ak8972_suspend(void *mlsl_handle,
140 struct ext_slave_descr *slave,
141 struct ext_slave_platform_data *pdata)
142{
143 int result = INV_SUCCESS;
144 result =
145 inv_serial_single_write(mlsl_handle, pdata->address,
146 AK8972_REG_CNTL,
147 AK8972_CNTL_MODE_POWER_DOWN);
148 msleep(1); /* wait at least 100us */
149 if (result) {
150 LOG_RESULT_LOCATION(result);
151 return result;
152 }
153 return result;
154}
155
156static int ak8972_resume(void *mlsl_handle,
157 struct ext_slave_descr *slave,
158 struct ext_slave_platform_data *pdata)
159{
160 int result = INV_SUCCESS;
161 result =
162 inv_serial_single_write(mlsl_handle, pdata->address,
163 AK8972_REG_CNTL,
164 AK8972_CNTL_MODE_SINGLE_MEASUREMENT);
165 if (result) {
166 LOG_RESULT_LOCATION(result);
167 return result;
168 }
169 return result;
170}
171
172static int ak8972_read(void *mlsl_handle,
173 struct ext_slave_descr *slave,
174 struct ext_slave_platform_data *pdata, unsigned char *data)
175{
176 unsigned char regs[8];
177 unsigned char *stat = &regs[0];
178 unsigned char *stat2 = &regs[7];
179 int result = INV_SUCCESS;
180 int status = INV_SUCCESS;
181
182 result =
183 inv_serial_read(mlsl_handle, pdata->address, AK8972_REG_ST1,
184 8, regs);
185 if (result) {
186 LOG_RESULT_LOCATION(result);
187 return result;
188 }
189
190 /* Always return the data and the status registers */
191 memcpy(data, &regs[1], 6);
192 data[6] = regs[0];
193 data[7] = regs[7];
194
195 /*
196 * ST : data ready -
197 * Measurement has been completed and data is ready to be read.
198 */
199 if (*stat & 0x01)
200 status = INV_SUCCESS;
201
202 /*
203 * ST2 : data error -
204 * occurs when data read is started outside of a readable period;
205 * data read would not be correct.
206 * Valid in continuous measurement mode only.
207 * In single measurement mode this error should not occour but we
208 * stil account for it and return an error, since the data would be
209 * corrupted.
210 * DERR bit is self-clearing when ST2 register is read.
211 */
212 if (*stat2 & 0x04)
213 status = INV_ERROR_COMPASS_DATA_ERROR;
214 /*
215 * ST2 : overflow -
216 * the sum of the absolute values of all axis |X|+|Y|+|Z| < 2400uT.
217 * This is likely to happen in presence of an external magnetic
218 * disturbance; it indicates, the sensor data is incorrect and should
219 * be ignored.
220 * An error is returned.
221 * HOFL bit clears when a new measurement starts.
222 */
223 if (*stat2 & 0x08)
224 status = INV_ERROR_COMPASS_DATA_OVERFLOW;
225 /*
226 * ST : overrun -
227 * the previous sample was not fetched and lost.
228 * Valid in continuous measurement mode only.
229 * In single measurement mode this error should not occour and we
230 * don't consider this condition an error.
231 * DOR bit is self-clearing when ST2 or any meas. data register is
232 * read.
233 */
234 if (*stat & 0x02) {
235 /* status = INV_ERROR_COMPASS_DATA_UNDERFLOW; */
236 status = INV_SUCCESS;
237 }
238
239 /*
240 * trigger next measurement if:
241 * - stat is non zero;
242 * - if stat is zero and stat2 is non zero.
243 * Won't trigger if data is not ready and there was no error.
244 */
245 if (*stat != 0x00 || *stat2 != 0x00) {
246 result = inv_serial_single_write(
247 mlsl_handle, pdata->address,
248 AK8972_REG_CNTL, AK8972_CNTL_MODE_SINGLE_MEASUREMENT);
249 if (result) {
250 LOG_RESULT_LOCATION(result);
251 return result;
252 }
253 }
254
255 return status;
256}
257
258static int ak8972_config(void *mlsl_handle,
259 struct ext_slave_descr *slave,
260 struct ext_slave_platform_data *pdata,
261 struct ext_slave_config *data)
262{
263 int result;
264 if (!data->data)
265 return INV_ERROR_INVALID_PARAMETER;
266
267 switch (data->key) {
268 case MPU_SLAVE_WRITE_REGISTERS:
269 result = inv_serial_write(mlsl_handle, pdata->address,
270 data->len,
271 (unsigned char *)data->data);
272 if (result) {
273 LOG_RESULT_LOCATION(result);
274 return result;
275 }
276 break;
277 case MPU_SLAVE_CONFIG_ODR_SUSPEND:
278 case MPU_SLAVE_CONFIG_ODR_RESUME:
279 case MPU_SLAVE_CONFIG_FSR_SUSPEND:
280 case MPU_SLAVE_CONFIG_FSR_RESUME:
281 case MPU_SLAVE_CONFIG_MOT_THS:
282 case MPU_SLAVE_CONFIG_NMOT_THS:
283 case MPU_SLAVE_CONFIG_MOT_DUR:
284 case MPU_SLAVE_CONFIG_NMOT_DUR:
285 case MPU_SLAVE_CONFIG_IRQ_SUSPEND:
286 case MPU_SLAVE_CONFIG_IRQ_RESUME:
287 default:
288 return INV_ERROR_FEATURE_NOT_IMPLEMENTED;
289 };
290
291 return INV_SUCCESS;
292}
293
294static int ak8972_get_config(void *mlsl_handle,
295 struct ext_slave_descr *slave,
296 struct ext_slave_platform_data *pdata,
297 struct ext_slave_config *data)
298{
299 struct ak8972_private_data *private_data = pdata->private_data;
300 int result;
301 if (!data->data)
302 return INV_ERROR_INVALID_PARAMETER;
303
304 switch (data->key) {
305 case MPU_SLAVE_READ_REGISTERS:
306 {
307 unsigned char *serial_data =
308 (unsigned char *)data->data;
309 result =
310 inv_serial_read(mlsl_handle, pdata->address,
311 serial_data[0], data->len - 1,
312 &serial_data[1]);
313 if (result) {
314 LOG_RESULT_LOCATION(result);
315 return result;
316 }
317 break;
318 }
319 case MPU_SLAVE_READ_SCALE:
320 {
321 unsigned char *serial_data =
322 (unsigned char *)data->data;
323 serial_data[0] = private_data->init.asa[0];
324 serial_data[1] = private_data->init.asa[1];
325 serial_data[2] = private_data->init.asa[2];
326 result = INV_SUCCESS;
327 if (result) {
328 LOG_RESULT_LOCATION(result);
329 return result;
330 }
331 break;
332 }
333 case MPU_SLAVE_CONFIG_ODR_SUSPEND:
334 (*(unsigned long *)data->data) = 0;
335 break;
336 case MPU_SLAVE_CONFIG_ODR_RESUME:
337 (*(unsigned long *)data->data) = 8000;
338 break;
339 case MPU_SLAVE_CONFIG_FSR_SUSPEND:
340 case MPU_SLAVE_CONFIG_FSR_RESUME:
341 case MPU_SLAVE_CONFIG_MOT_THS:
342 case MPU_SLAVE_CONFIG_NMOT_THS:
343 case MPU_SLAVE_CONFIG_MOT_DUR:
344 case MPU_SLAVE_CONFIG_NMOT_DUR:
345 case MPU_SLAVE_CONFIG_IRQ_SUSPEND:
346 case MPU_SLAVE_CONFIG_IRQ_RESUME:
347 default:
348 return INV_ERROR_FEATURE_NOT_IMPLEMENTED;
349 };
350
351 return INV_SUCCESS;
352}
353
354static struct ext_slave_read_trigger ak8972_read_trigger = {
355 /*.reg = */ 0x0A,
356 /*.value = */ 0x01
357};
358
359static struct ext_slave_descr ak8972_descr = {
360 .init = ak8972_init,
361 .exit = ak8972_exit,
362 .suspend = ak8972_suspend,
363 .resume = ak8972_resume,
364 .read = ak8972_read,
365 .config = ak8972_config,
366 .get_config = ak8972_get_config,
367 .name = "ak8972",
368 .type = EXT_SLAVE_TYPE_COMPASS,
369 .id = COMPASS_ID_AK8972,
370 .read_reg = 0x01,
371 .read_len = 9,
372 .endian = EXT_SLAVE_LITTLE_ENDIAN,
373 .range = {39321, 6000},
374 .trigger = &ak8972_read_trigger,
375};
376
377static
378struct ext_slave_descr *ak8972_get_slave_descr(void)
379{
380 return &ak8972_descr;
381}
382
383/* -------------------------------------------------------------------------- */
384struct ak8972_mod_private_data {
385 struct i2c_client *client;
386 struct ext_slave_platform_data *pdata;
387};
388
389static unsigned short normal_i2c[] = { I2C_CLIENT_END };
390
391static int ak8972_mod_probe(struct i2c_client *client,
392 const struct i2c_device_id *devid)
393{
394 struct ext_slave_platform_data *pdata;
395 struct ak8972_mod_private_data *private_data;
396 int result = 0;
397
398 dev_info(&client->adapter->dev, "%s: %s\n", __func__, devid->name);
399
400 if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
401 result = -ENODEV;
402 goto out_no_free;
403 }
404
405 pdata = client->dev.platform_data;
406 if (!pdata) {
407 dev_err(&client->adapter->dev,
408 "Missing platform data for slave %s\n", devid->name);
409 result = -EFAULT;
410 goto out_no_free;
411 }
412
413 private_data = kzalloc(sizeof(*private_data), GFP_KERNEL);
414 if (!private_data) {
415 result = -ENOMEM;
416 goto out_no_free;
417 }
418
419 i2c_set_clientdata(client, private_data);
420 private_data->client = client;
421 private_data->pdata = pdata;
422
423 result = inv_mpu_register_slave(THIS_MODULE, client, pdata,
424 ak8972_get_slave_descr);
425 if (result) {
426 dev_err(&client->adapter->dev,
427 "Slave registration failed: %s, %d\n",
428 devid->name, result);
429 goto out_free_memory;
430 }
431
432 return result;
433
434out_free_memory:
435 kfree(private_data);
436out_no_free:
437 dev_err(&client->adapter->dev, "%s failed %d\n", __func__, result);
438 return result;
439
440}
441
442static int ak8972_mod_remove(struct i2c_client *client)
443{
444 struct ak8972_mod_private_data *private_data =
445 i2c_get_clientdata(client);
446
447 dev_dbg(&client->adapter->dev, "%s\n", __func__);
448 inv_mpu_unregister_slave(client, private_data->pdata,
449 ak8972_get_slave_descr);
450
451 kfree(private_data);
452 return 0;
453}
454
455static const struct i2c_device_id ak8972_mod_id[] = {
456 { "ak8972", COMPASS_ID_AK8972 },
457 {}
458};
459
460MODULE_DEVICE_TABLE(i2c, ak8972_mod_id);
461
462static struct i2c_driver ak8972_mod_driver = {
463 .class = I2C_CLASS_HWMON,
464 .probe = ak8972_mod_probe,
465 .remove = ak8972_mod_remove,
466 .id_table = ak8972_mod_id,
467 .driver = {
468 .owner = THIS_MODULE,
469 .name = "ak8972_mod",
470 },
471 .address_list = normal_i2c,
472};
473
474static int __init ak8972_mod_init(void)
475{
476 int res = i2c_add_driver(&ak8972_mod_driver);
477 pr_info("%s: Probe name %s\n", __func__, "ak8972_mod");
478 if (res)
479 pr_err("%s failed\n", __func__);
480 return res;
481}
482
483static void __exit ak8972_mod_exit(void)
484{
485 pr_info("%s\n", __func__);
486 i2c_del_driver(&ak8972_mod_driver);
487}
488
489module_init(ak8972_mod_init);
490module_exit(ak8972_mod_exit);
491
492MODULE_AUTHOR("Invensense Corporation");
493MODULE_DESCRIPTION("Driver to integrate AK8972 sensor with the MPU");
494MODULE_LICENSE("GPL");
495MODULE_ALIAS("ak8972_mod");
496
497/**
498 * @}
499 */