/*
$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 .
$
*/
/**
* @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
#include
#include
#include
#include
#include
#include
#include
#include "mpu-dev.h"
#include "log.h"
#include
#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 struct ext_slave_descr yas530_descr = {
.init = NULL,
.exit = NULL,
.suspend = yas530_suspend,
.resume = yas530_resume,
.read = yas530_read,
.config = NULL,
.get_config = NULL,
.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");
/**
* @}
*/