aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/misc/inv_mpu/compass/ak8975.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/ak8975.c
parent8dea78da5cee153b8af9c07a2745f6c55057fe12 (diff)
Added missing tegra files.HEADmaster
Diffstat (limited to 'drivers/misc/inv_mpu/compass/ak8975.c')
-rw-r--r--drivers/misc/inv_mpu/compass/ak8975.c500
1 files changed, 500 insertions, 0 deletions
diff --git a/drivers/misc/inv_mpu/compass/ak8975.c b/drivers/misc/inv_mpu/compass/ak8975.c
new file mode 100644
index 00000000000..3642e29e89a
--- /dev/null
+++ b/drivers/misc/inv_mpu/compass/ak8975.c
@@ -0,0 +1,500 @@
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 ak8975.c
25 * @brief Magnetometer setup and handling methods for the AKM AK8975,
26 * AKM AK8975B, and AKM AK8975C 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 AK8975_REG_ST1 (0x02)
49#define AK8975_REG_HXL (0x03)
50#define AK8975_REG_ST2 (0x09)
51
52#define AK8975_REG_CNTL (0x0A)
53#define AK8975_REG_ASAX (0x10)
54#define AK8975_REG_ASAY (0x11)
55#define AK8975_REG_ASAZ (0x12)
56
57#define AK8975_CNTL_MODE_POWER_DOWN (0x00)
58#define AK8975_CNTL_MODE_SINGLE_MEASUREMENT (0x01)
59#define AK8975_CNTL_MODE_FUSE_ROM_ACCESS (0x0f)
60
61/* -------------------------------------------------------------------------- */
62struct ak8975_config {
63 char asa[COMPASS_NUM_AXES]; /* axis sensitivity adjustment */
64};
65
66struct ak8975_private_data {
67 struct ak8975_config init;
68};
69
70/* -------------------------------------------------------------------------- */
71static int ak8975_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 ak8975_private_data *private_data;
79 private_data = (struct ak8975_private_data *)
80 kzalloc(sizeof(struct ak8975_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 AK8975_REG_CNTL,
87 AK8975_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 AK8975_REG_CNTL,
97 AK8975_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 AK8975_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 AK8975_REG_CNTL,
122 AK8975_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 ak8975_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 ak8975_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 AK8975_REG_CNTL,
148 AK8975_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 ak8975_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 AK8975_REG_CNTL,
165 AK8975_CNTL_MODE_SINGLE_MEASUREMENT);
166 if (result) {
167 LOG_RESULT_LOCATION(result);
168 return result;
169 }
170 return result;
171}
172
173static int ak8975_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, AK8975_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 if (*stat2 & 0x04)
214 status = INV_ERROR_COMPASS_DATA_ERROR;
215 /*
216 * ST2 : overflow -
217 * the sum of the absolute values of all axis |X|+|Y|+|Z| < 2400uT.
218 * This is likely to happen in presence of an external magnetic
219 * disturbance; it indicates, the sensor data is incorrect and should
220 * be ignored.
221 * An error is returned.
222 * HOFL bit clears when a new measurement starts.
223 */
224 if (*stat2 & 0x08)
225 status = INV_ERROR_COMPASS_DATA_OVERFLOW;
226 /*
227 * ST : overrun -
228 * the previous sample was not fetched and lost.
229 * Valid in continuous measurement mode only.
230 * In single measurement mode this error should not occour and we
231 * don't consider this condition an error.
232 * DOR bit is self-clearing when ST2 or any meas. data register is
233 * read.
234 */
235 if (*stat & 0x02) {
236 /* status = INV_ERROR_COMPASS_DATA_UNDERFLOW; */
237 status = INV_SUCCESS;
238 }
239
240 /*
241 * trigger next measurement if:
242 * - stat is non zero;
243 * - if stat is zero and stat2 is non zero.
244 * Won't trigger if data is not ready and there was no error.
245 */
246 if (*stat != 0x00 || *stat2 != 0x00) {
247 result = inv_serial_single_write(
248 mlsl_handle, pdata->address,
249 AK8975_REG_CNTL, AK8975_CNTL_MODE_SINGLE_MEASUREMENT);
250 if (result) {
251 LOG_RESULT_LOCATION(result);
252 return result;
253 }
254 }
255
256 return status;
257}
258
259static int ak8975_config(void *mlsl_handle,
260 struct ext_slave_descr *slave,
261 struct ext_slave_platform_data *pdata,
262 struct ext_slave_config *data)
263{
264 int result;
265 if (!data->data)
266 return INV_ERROR_INVALID_PARAMETER;
267
268 switch (data->key) {
269 case MPU_SLAVE_WRITE_REGISTERS:
270 result = inv_serial_write(mlsl_handle, pdata->address,
271 data->len,
272 (unsigned char *)data->data);
273 if (result) {
274 LOG_RESULT_LOCATION(result);
275 return result;
276 }
277 break;
278 case MPU_SLAVE_CONFIG_ODR_SUSPEND:
279 case MPU_SLAVE_CONFIG_ODR_RESUME:
280 case MPU_SLAVE_CONFIG_FSR_SUSPEND:
281 case MPU_SLAVE_CONFIG_FSR_RESUME:
282 case MPU_SLAVE_CONFIG_MOT_THS:
283 case MPU_SLAVE_CONFIG_NMOT_THS:
284 case MPU_SLAVE_CONFIG_MOT_DUR:
285 case MPU_SLAVE_CONFIG_NMOT_DUR:
286 case MPU_SLAVE_CONFIG_IRQ_SUSPEND:
287 case MPU_SLAVE_CONFIG_IRQ_RESUME:
288 default:
289 return INV_ERROR_FEATURE_NOT_IMPLEMENTED;
290 };
291
292 return INV_SUCCESS;
293}
294
295static int ak8975_get_config(void *mlsl_handle,
296 struct ext_slave_descr *slave,
297 struct ext_slave_platform_data *pdata,
298 struct ext_slave_config *data)
299{
300 struct ak8975_private_data *private_data = pdata->private_data;
301 int result;
302 if (!data->data)
303 return INV_ERROR_INVALID_PARAMETER;
304
305 switch (data->key) {
306 case MPU_SLAVE_READ_REGISTERS:
307 {
308 unsigned char *serial_data =
309 (unsigned char *)data->data;
310 result =
311 inv_serial_read(mlsl_handle, pdata->address,
312 serial_data[0], data->len - 1,
313 &serial_data[1]);
314 if (result) {
315 LOG_RESULT_LOCATION(result);
316 return result;
317 }
318 break;
319 }
320 case MPU_SLAVE_READ_SCALE:
321 {
322 unsigned char *serial_data =
323 (unsigned char *)data->data;
324 serial_data[0] = private_data->init.asa[0];
325 serial_data[1] = private_data->init.asa[1];
326 serial_data[2] = private_data->init.asa[2];
327 result = INV_SUCCESS;
328 if (result) {
329 LOG_RESULT_LOCATION(result);
330 return result;
331 }
332 break;
333 }
334 case MPU_SLAVE_CONFIG_ODR_SUSPEND:
335 (*(unsigned long *)data->data) = 0;
336 break;
337 case MPU_SLAVE_CONFIG_ODR_RESUME:
338 (*(unsigned long *)data->data) = 8000;
339 break;
340 case MPU_SLAVE_CONFIG_FSR_SUSPEND:
341 case MPU_SLAVE_CONFIG_FSR_RESUME:
342 case MPU_SLAVE_CONFIG_MOT_THS:
343 case MPU_SLAVE_CONFIG_NMOT_THS:
344 case MPU_SLAVE_CONFIG_MOT_DUR:
345 case MPU_SLAVE_CONFIG_NMOT_DUR:
346 case MPU_SLAVE_CONFIG_IRQ_SUSPEND:
347 case MPU_SLAVE_CONFIG_IRQ_RESUME:
348 default:
349 return INV_ERROR_FEATURE_NOT_IMPLEMENTED;
350 };
351
352 return INV_SUCCESS;
353}
354
355static struct ext_slave_read_trigger ak8975_read_trigger = {
356 /*.reg = */ 0x0A,
357 /*.value = */ 0x01
358};
359
360static struct ext_slave_descr ak8975_descr = {
361 .init = ak8975_init,
362 .exit = ak8975_exit,
363 .suspend = ak8975_suspend,
364 .resume = ak8975_resume,
365 .read = ak8975_read,
366 .config = ak8975_config,
367 .get_config = ak8975_get_config,
368 .name = "ak8975",
369 .type = EXT_SLAVE_TYPE_COMPASS,
370 .id = COMPASS_ID_AK8975,
371 .read_reg = 0x01,
372 .read_len = 10,
373 .endian = EXT_SLAVE_LITTLE_ENDIAN,
374 .range = {9830, 4000},
375 .trigger = &ak8975_read_trigger,
376};
377
378static
379struct ext_slave_descr *ak8975_get_slave_descr(void)
380{
381 return &ak8975_descr;
382}
383
384/* -------------------------------------------------------------------------- */
385struct ak8975_mod_private_data {
386 struct i2c_client *client;
387 struct ext_slave_platform_data *pdata;
388};
389
390static unsigned short normal_i2c[] = { I2C_CLIENT_END };
391
392static int ak8975_mod_probe(struct i2c_client *client,
393 const struct i2c_device_id *devid)
394{
395 struct ext_slave_platform_data *pdata;
396 struct ak8975_mod_private_data *private_data;
397 int result = 0;
398
399 dev_info(&client->adapter->dev, "%s: %s\n", __func__, devid->name);
400
401 if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
402 result = -ENODEV;
403 goto out_no_free;
404 }
405
406 pdata = client->dev.platform_data;
407 if (!pdata) {
408 dev_err(&client->adapter->dev,
409 "Missing platform data for slave %s\n", devid->name);
410 result = -EFAULT;
411 goto out_no_free;
412 }
413
414 private_data = kzalloc(sizeof(*private_data), GFP_KERNEL);
415 if (!private_data) {
416 result = -ENOMEM;
417 goto out_no_free;
418 }
419
420 i2c_set_clientdata(client, private_data);
421 private_data->client = client;
422 private_data->pdata = pdata;
423
424 result = inv_mpu_register_slave(THIS_MODULE, client, pdata,
425 ak8975_get_slave_descr);
426 if (result) {
427 dev_err(&client->adapter->dev,
428 "Slave registration failed: %s, %d\n",
429 devid->name, result);
430 goto out_free_memory;
431 }
432
433 return result;
434
435out_free_memory:
436 kfree(private_data);
437out_no_free:
438 dev_err(&client->adapter->dev, "%s failed %d\n", __func__, result);
439 return result;
440
441}
442
443static int ak8975_mod_remove(struct i2c_client *client)
444{
445 struct ak8975_mod_private_data *private_data =
446 i2c_get_clientdata(client);
447
448 dev_dbg(&client->adapter->dev, "%s\n", __func__);
449 inv_mpu_unregister_slave(client, private_data->pdata,
450 ak8975_get_slave_descr);
451
452 kfree(private_data);
453 return 0;
454}
455
456static const struct i2c_device_id ak8975_mod_id[] = {
457 { "ak8975", COMPASS_ID_AK8975 },
458 {}
459};
460
461MODULE_DEVICE_TABLE(i2c, ak8975_mod_id);
462
463static struct i2c_driver ak8975_mod_driver = {
464 .class = I2C_CLASS_HWMON,
465 .probe = ak8975_mod_probe,
466 .remove = ak8975_mod_remove,
467 .id_table = ak8975_mod_id,
468 .driver = {
469 .owner = THIS_MODULE,
470 .name = "ak8975_mod",
471 },
472 .address_list = normal_i2c,
473};
474
475static int __init ak8975_mod_init(void)
476{
477 int res = i2c_add_driver(&ak8975_mod_driver);
478 pr_info("%s: Probe name %s\n", __func__, "ak8975_mod");
479 if (res)
480 pr_err("%s failed\n", __func__);
481 return res;
482}
483
484static void __exit ak8975_mod_exit(void)
485{
486 pr_info("%s\n", __func__);
487 i2c_del_driver(&ak8975_mod_driver);
488}
489
490module_init(ak8975_mod_init);
491module_exit(ak8975_mod_exit);
492
493MODULE_AUTHOR("Invensense Corporation");
494MODULE_DESCRIPTION("Driver to integrate AK8975 sensor with the MPU");
495MODULE_LICENSE("GPL");
496MODULE_ALIAS("ak8975_mod");
497
498/**
499 * @}
500 */