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