aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/misc/inv_mpu/compass/yas530.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/misc/inv_mpu/compass/yas530.c')
-rw-r--r--drivers/misc/inv_mpu/compass/yas530.c596
1 files changed, 596 insertions, 0 deletions
diff --git a/drivers/misc/inv_mpu/compass/yas530.c b/drivers/misc/inv_mpu/compass/yas530.c
new file mode 100644
index 0000000..263990f
--- /dev/null
+++ b/drivers/misc/inv_mpu/compass/yas530.c
@@ -0,0 +1,596 @@
+/*
+ $License:
+ Copyright (C) 2011 InvenSense Corporation, All Rights Reserved.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ $
+ */
+
+/**
+ * @addtogroup COMPASSDL
+ *
+ * @{
+ * @file yas530.c
+ * @brief Magnetometer setup and handling methods for Yamaha YAS530
+ * compass when used in a user-space solution (no kernel driver).
+ */
+
+/* -------------------------------------------------------------------------- */
+
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include "mpu-dev.h"
+
+#include "log.h"
+#include <linux/mpu_411.h>
+#include "mlsl.h"
+#include "mldl_cfg.h"
+#undef MPL_LOG_TAG
+#define MPL_LOG_TAG "MPL-compass"
+
+/* -------------------------------------------------------------------------- */
+#define YAS530_REGADDR_DEVICE_ID (0x80)
+#define YAS530_REGADDR_ACTUATE_INIT_COIL (0x81)
+#define YAS530_REGADDR_MEASURE_COMMAND (0x82)
+#define YAS530_REGADDR_CONFIG (0x83)
+#define YAS530_REGADDR_MEASURE_INTERVAL (0x84)
+#define YAS530_REGADDR_OFFSET_X (0x85)
+#define YAS530_REGADDR_OFFSET_Y1 (0x86)
+#define YAS530_REGADDR_OFFSET_Y2 (0x87)
+#define YAS530_REGADDR_TEST1 (0x88)
+#define YAS530_REGADDR_TEST2 (0x89)
+#define YAS530_REGADDR_CAL (0x90)
+#define YAS530_REGADDR_MEASURE_DATA (0xb0)
+
+/* -------------------------------------------------------------------------- */
+static int Cx, Cy1, Cy2;
+static int /*a1, */ a2, a3, a4, a5, a6, a7, a8, a9;
+static int k;
+
+static unsigned char dx, dy1, dy2;
+static unsigned char d2, d3, d4, d5, d6, d7, d8, d9, d0;
+static unsigned char dck;
+
+/* -------------------------------------------------------------------------- */
+
+static int set_hardware_offset(void *mlsl_handle,
+ struct ext_slave_descr *slave,
+ struct ext_slave_platform_data *pdata,
+ char offset_x, char offset_y1, char offset_y2)
+{
+ char data;
+ int result = INV_SUCCESS;
+
+ data = offset_x & 0x3f;
+ result = inv_serial_single_write(mlsl_handle, pdata->address,
+ YAS530_REGADDR_OFFSET_X, data);
+ if (result) {
+ LOG_RESULT_LOCATION(result);
+ return result;
+ }
+
+ data = offset_y1 & 0x3f;
+ result = inv_serial_single_write(mlsl_handle, pdata->address,
+ YAS530_REGADDR_OFFSET_Y1, data);
+ if (result) {
+ LOG_RESULT_LOCATION(result);
+ return result;
+ }
+
+ data = offset_y2 & 0x3f;
+ result = inv_serial_single_write(mlsl_handle, pdata->address,
+ YAS530_REGADDR_OFFSET_Y2, data);
+ if (result) {
+ LOG_RESULT_LOCATION(result);
+ return result;
+ }
+
+ return result;
+}
+
+static int set_measure_command(void *mlsl_handle,
+ struct ext_slave_descr *slave,
+ struct ext_slave_platform_data *pdata,
+ int ldtc, int fors, int dlymes)
+{
+ int result = INV_SUCCESS;
+
+ result = inv_serial_single_write(mlsl_handle, pdata->address,
+ YAS530_REGADDR_MEASURE_COMMAND, 0x01);
+ if (result) {
+ LOG_RESULT_LOCATION(result);
+ return result;
+ }
+
+ return result;
+}
+
+static int measure_normal(void *mlsl_handle,
+ struct ext_slave_descr *slave,
+ struct ext_slave_platform_data *pdata,
+ int *busy, unsigned short *t,
+ unsigned short *x, unsigned short *y1,
+ unsigned short *y2)
+{
+ unsigned char data[8];
+ unsigned short b, to, xo, y1o, y2o;
+ int result;
+ ktime_t sleeptime;
+ result = set_measure_command(mlsl_handle, slave, pdata, 0, 0, 0);
+ sleeptime = ktime_set(0, 2 * NSEC_PER_MSEC);
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_hrtimeout(&sleeptime, HRTIMER_MODE_REL);
+
+ result = inv_serial_read(mlsl_handle, pdata->address,
+ YAS530_REGADDR_MEASURE_DATA, 8, data);
+ if (result) {
+ LOG_RESULT_LOCATION(result);
+ return result;
+ }
+
+ b = (data[0] >> 7) & 0x01;
+ to = ((data[0] << 2) & 0x1fc) | ((data[1] >> 6) & 0x03);
+ xo = ((data[2] << 5) & 0xfe0) | ((data[3] >> 3) & 0x1f);
+ y1o = ((data[4] << 5) & 0xfe0) | ((data[5] >> 3) & 0x1f);
+ y2o = ((data[6] << 5) & 0xfe0) | ((data[7] >> 3) & 0x1f);
+
+ *busy = b;
+ *t = to;
+ *x = xo;
+ *y1 = y1o;
+ *y2 = y2o;
+
+ return result;
+}
+
+static int check_offset(void *mlsl_handle,
+ struct ext_slave_descr *slave,
+ struct ext_slave_platform_data *pdata,
+ char offset_x, char offset_y1, char offset_y2,
+ int *flag_x, int *flag_y1, int *flag_y2)
+{
+ int result;
+ int busy;
+ short t, x, y1, y2;
+
+ result = set_hardware_offset(mlsl_handle, slave, pdata,
+ offset_x, offset_y1, offset_y2);
+ if (result) {
+ LOG_RESULT_LOCATION(result);
+ return result;
+ }
+
+ result = measure_normal(mlsl_handle, slave, pdata,
+ &busy, &t, &x, &y1, &y2);
+ if (result) {
+ LOG_RESULT_LOCATION(result);
+ return result;
+ }
+
+ *flag_x = 0;
+ *flag_y1 = 0;
+ *flag_y2 = 0;
+
+ if (x > 2048)
+ *flag_x = 1;
+ if (y1 > 2048)
+ *flag_y1 = 1;
+ if (y2 > 2048)
+ *flag_y2 = 1;
+ if (x < 2048)
+ *flag_x = -1;
+ if (y1 < 2048)
+ *flag_y1 = -1;
+ if (y2 < 2048)
+ *flag_y2 = -1;
+
+ return result;
+}
+
+static int measure_and_set_offset(void *mlsl_handle,
+ struct ext_slave_descr *slave,
+ struct ext_slave_platform_data *pdata,
+ char *offset)
+{
+ int i;
+ int result = INV_SUCCESS;
+ char offset_x = 0, offset_y1 = 0, offset_y2 = 0;
+ int flag_x = 0, flag_y1 = 0, flag_y2 = 0;
+ static const int correct[5] = { 16, 8, 4, 2, 1 };
+
+ for (i = 0; i < 5; i++) {
+ result = check_offset(mlsl_handle, slave, pdata,
+ offset_x, offset_y1, offset_y2,
+ &flag_x, &flag_y1, &flag_y2);
+ if (result) {
+ LOG_RESULT_LOCATION(result);
+ return result;
+ }
+
+ if (flag_x)
+ offset_x += flag_x * correct[i];
+ if (flag_y1)
+ offset_y1 += flag_y1 * correct[i];
+ if (flag_y2)
+ offset_y2 += flag_y2 * correct[i];
+ }
+
+ result = set_hardware_offset(mlsl_handle, slave, pdata,
+ offset_x, offset_y1, offset_y2);
+ if (result) {
+ LOG_RESULT_LOCATION(result);
+ return result;
+ }
+
+ offset[0] = offset_x;
+ offset[1] = offset_y1;
+ offset[2] = offset_y2;
+
+ return result;
+}
+
+static void coordinate_conversion(short x, short y1, short y2, short t,
+ int32_t *xo, int32_t *yo, int32_t *zo)
+{
+ int32_t sx, sy1, sy2, sy, sz;
+ int32_t hx, hy, hz;
+
+ sx = x - (Cx * t) / 100;
+ sy1 = y1 - (Cy1 * t) / 100;
+ sy2 = y2 - (Cy2 * t) / 100;
+
+ sy = sy1 - sy2;
+ sz = -sy1 - sy2;
+
+ hx = k * ((100 * sx + a2 * sy + a3 * sz) / 10);
+ hy = k * ((a4 * sx + a5 * sy + a6 * sz) / 10);
+ hz = k * ((a7 * sx + a8 * sy + a9 * sz) / 10);
+
+ *xo = hx;
+ *yo = hy;
+ *zo = hz;
+}
+
+static int yas530_suspend(void *mlsl_handle,
+ struct ext_slave_descr *slave,
+ struct ext_slave_platform_data *pdata)
+{
+ int result = INV_SUCCESS;
+
+ return result;
+}
+
+static int yas530_resume(void *mlsl_handle,
+ struct ext_slave_descr *slave,
+ struct ext_slave_platform_data *pdata)
+{
+ int result = INV_SUCCESS;
+
+ unsigned char dummyData = 0x00;
+ char offset[3] = { 0, 0, 0 };
+ unsigned char data[16];
+ unsigned char read_reg[1];
+
+ /* =============================================== */
+
+ /* Step 1 - Test register initialization */
+ dummyData = 0x00;
+ result = inv_serial_single_write(mlsl_handle, pdata->address,
+ YAS530_REGADDR_TEST1, dummyData);
+ if (result) {
+ LOG_RESULT_LOCATION(result);
+ return result;
+ }
+
+ result =
+ inv_serial_single_write(mlsl_handle, pdata->address,
+ YAS530_REGADDR_TEST2, dummyData);
+ if (result) {
+ LOG_RESULT_LOCATION(result);
+ return result;
+ }
+
+ /* Device ID read */
+ result = inv_serial_read(mlsl_handle, pdata->address,
+ YAS530_REGADDR_DEVICE_ID, 1, read_reg);
+
+ /*Step 2 Read the CAL register */
+ /* CAL data read */
+ result = inv_serial_read(mlsl_handle, pdata->address,
+ YAS530_REGADDR_CAL, 16, data);
+ if (result) {
+ LOG_RESULT_LOCATION(result);
+ return result;
+ }
+ /* CAL data Second Read */
+ result = inv_serial_read(mlsl_handle, pdata->address,
+ YAS530_REGADDR_CAL, 16, data);
+ if (result) {
+ LOG_RESULT_LOCATION(result);
+ return result;
+ }
+
+ /*Cal data */
+ dx = data[0];
+ dy1 = data[1];
+ dy2 = data[2];
+ d2 = (data[3] >> 2) & 0x03f;
+ d3 = ((data[3] << 2) & 0x0c) | ((data[4] >> 6) & 0x03);
+ d4 = data[4] & 0x3f;
+ d5 = (data[5] >> 2) & 0x3f;
+ d6 = ((data[5] << 4) & 0x30) | ((data[6] >> 4) & 0x0f);
+ d7 = ((data[6] << 3) & 0x78) | ((data[7] >> 5) & 0x07);
+ d8 = ((data[7] << 1) & 0x3e) | ((data[8] >> 7) & 0x01);
+ d9 = ((data[8] << 1) & 0xfe) | ((data[9] >> 7) & 0x01);
+ d0 = (data[9] >> 2) & 0x1f;
+ dck = ((data[9] << 1) & 0x06) | ((data[10] >> 7) & 0x01);
+
+ /*Correction Data */
+ Cx = (int)dx * 6 - 768;
+ Cy1 = (int)dy1 * 6 - 768;
+ Cy2 = (int)dy2 * 6 - 768;
+ a2 = (int)d2 - 32;
+ a3 = (int)d3 - 8;
+ a4 = (int)d4 - 32;
+ a5 = (int)d5 + 38;
+ a6 = (int)d6 - 32;
+ a7 = (int)d7 - 64;
+ a8 = (int)d8 - 32;
+ a9 = (int)d9;
+ k = (int)d0 + 10;
+
+ /*Obtain the [49:47] bits */
+ dck &= 0x07;
+
+ /*Step 3 : Storing the CONFIG with the CLK value */
+ dummyData = 0x00 | (dck << 2);
+ result = inv_serial_single_write(mlsl_handle, pdata->address,
+ YAS530_REGADDR_CONFIG, dummyData);
+ if (result) {
+ LOG_RESULT_LOCATION(result);
+ return result;
+ }
+
+ /*Step 4 : Set Acquisition Interval Register */
+ dummyData = 0x00;
+ result = inv_serial_single_write(mlsl_handle, pdata->address,
+ YAS530_REGADDR_MEASURE_INTERVAL,
+ dummyData);
+ if (result) {
+ LOG_RESULT_LOCATION(result);
+ return result;
+ }
+
+ /*Step 5 : Reset Coil */
+ dummyData = 0x00;
+ result = inv_serial_single_write(mlsl_handle, pdata->address,
+ YAS530_REGADDR_ACTUATE_INIT_COIL,
+ dummyData);
+ if (result) {
+ LOG_RESULT_LOCATION(result);
+ return result;
+ }
+
+ /* Offset Measurement and Set */
+ result = measure_and_set_offset(mlsl_handle, slave, pdata, offset);
+ if (result) {
+ LOG_RESULT_LOCATION(result);
+ return result;
+ }
+
+ return result;
+}
+
+static int yas530_read(void *mlsl_handle,
+ struct ext_slave_descr *slave,
+ struct ext_slave_platform_data *pdata,
+ unsigned char *data)
+{
+ int result = INV_SUCCESS;
+
+ int busy;
+ short t, x, y1, y2;
+ int32_t xyz[3];
+ short rawfixed[3];
+
+ result = measure_normal(mlsl_handle, slave, pdata,
+ &busy, &t, &x, &y1, &y2);
+ if (result) {
+ LOG_RESULT_LOCATION(result);
+ return result;
+ }
+
+ coordinate_conversion(x, y1, y2, t, &xyz[0], &xyz[1], &xyz[2]);
+
+ rawfixed[0] = (short)(xyz[0] / 100);
+ rawfixed[1] = (short)(xyz[1] / 100);
+ rawfixed[2] = (short)(xyz[2] / 100);
+
+ data[0] = rawfixed[0] >> 8;
+ data[1] = rawfixed[0] & 0xFF;
+ data[2] = rawfixed[1] >> 8;
+ data[3] = rawfixed[1] & 0xFF;
+ data[4] = rawfixed[2] >> 8;
+ data[5] = rawfixed[2] & 0xFF;
+
+ if (busy)
+ return INV_ERROR_COMPASS_DATA_NOT_READY;
+ return result;
+}
+
+static int yas530_get_config(void *mlsl_handle,
+ struct ext_slave_descr *slave,
+ struct ext_slave_platform_data *pdata,
+ struct ext_slave_config *data)
+{
+ int result = INV_SUCCESS;
+ //struct yas530_private_data *private_data = pdata->private_data;
+
+ switch (data->key)
+ {
+ default:
+ return INV_ERROR_FEATURE_NOT_IMPLEMENTED;
+ }
+ return result;
+}
+
+static struct ext_slave_descr yas530_descr = {
+ .init = NULL,
+ .exit = NULL,
+ .suspend = yas530_suspend,
+ .resume = yas530_resume,
+ .read = yas530_read,
+ .config = NULL,
+ .get_config = yas530_get_config,
+ .name = "yas530",
+ .type = EXT_SLAVE_TYPE_COMPASS,
+ .id = COMPASS_ID_YAS530,
+ .read_reg = 0x06,
+ .read_len = 6,
+ .endian = EXT_SLAVE_BIG_ENDIAN,
+ .range = {3276, 8001},
+ .trigger = NULL,
+};
+
+static
+struct ext_slave_descr *yas530_get_slave_descr(void)
+{
+ return &yas530_descr;
+}
+
+/* -------------------------------------------------------------------------- */
+struct yas530_mod_private_data {
+ struct i2c_client *client;
+ struct ext_slave_platform_data *pdata;
+};
+
+static unsigned short normal_i2c[] = { I2C_CLIENT_END };
+
+static int yas530_mod_probe(struct i2c_client *client,
+ const struct i2c_device_id *devid)
+{
+ struct ext_slave_platform_data *pdata;
+ struct yas530_mod_private_data *private_data;
+ int result = 0;
+
+ dev_info(&client->adapter->dev, "%s: %s\n", __func__, devid->name);
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ result = -ENODEV;
+ goto out_no_free;
+ }
+
+ pdata = client->dev.platform_data;
+ if (!pdata) {
+ dev_err(&client->adapter->dev,
+ "Missing platform data for slave %s\n", devid->name);
+ result = -EFAULT;
+ goto out_no_free;
+ }
+
+ private_data = kzalloc(sizeof(*private_data), GFP_KERNEL);
+ if (!private_data) {
+ result = -ENOMEM;
+ goto out_no_free;
+ }
+
+ i2c_set_clientdata(client, private_data);
+ private_data->client = client;
+ private_data->pdata = pdata;
+
+ result = inv_mpu_register_slave(THIS_MODULE, client, pdata,
+ yas530_get_slave_descr);
+ if (result) {
+ dev_err(&client->adapter->dev,
+ "Slave registration failed: %s, %d\n",
+ devid->name, result);
+ goto out_free_memory;
+ }
+
+ return result;
+
+out_free_memory:
+ kfree(private_data);
+out_no_free:
+ dev_err(&client->adapter->dev, "%s failed %d\n", __func__, result);
+ return result;
+
+}
+
+static int yas530_mod_remove(struct i2c_client *client)
+{
+ struct yas530_mod_private_data *private_data =
+ i2c_get_clientdata(client);
+
+ dev_dbg(&client->adapter->dev, "%s\n", __func__);
+
+ inv_mpu_unregister_slave(client, private_data->pdata,
+ yas530_get_slave_descr);
+
+ kfree(private_data);
+ return 0;
+}
+
+static const struct i2c_device_id yas530_mod_id[] = {
+ { "yas530", COMPASS_ID_YAS530 },
+ {}
+};
+
+MODULE_DEVICE_TABLE(i2c, yas530_mod_id);
+
+static struct i2c_driver yas530_mod_driver = {
+ .class = I2C_CLASS_HWMON,
+ .probe = yas530_mod_probe,
+ .remove = yas530_mod_remove,
+ .id_table = yas530_mod_id,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "yas530_mod",
+ },
+ .address_list = normal_i2c,
+};
+
+static int __init yas530_mod_init(void)
+{
+ int res = i2c_add_driver(&yas530_mod_driver);
+ pr_info("%s: Probe name %s\n", __func__, "yas530_mod");
+ if (res)
+ pr_err("%s failed\n", __func__);
+ return res;
+}
+
+static void __exit yas530_mod_exit(void)
+{
+ pr_info("%s\n", __func__);
+ i2c_del_driver(&yas530_mod_driver);
+}
+
+module_init(yas530_mod_init);
+module_exit(yas530_mod_exit);
+
+MODULE_AUTHOR("Invensense Corporation");
+MODULE_DESCRIPTION("Driver to integrate YAS530 sensor with the MPU");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("yas530_mod");
+
+/**
+ * @}
+ */