/* $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 ak89xx.c * @brief Magnetometer setup and handling methods for the AKM * compass devices. */ /* -------------------------------------------------------------------------- */ #include #include #include #include #include #include #include #include "mpu-dev.h" #include #include #include "mlsl.h" #include "mldl_cfg.h" #undef MPL_LOG_TAG #define MPL_LOG_TAG "MPL-compass" /* -------------------------------------------------------------------------- */ #define AK89XX_REG_ST1 (0x02) #define AK89XX_REG_HXL (0x03) #define AK89XX_REG_ST2 (0x09) #define AK89XX_REG_CNTL (0x0A) #define AK89XX_REG_ASAX (0x10) #define AK89XX_REG_ASAY (0x11) #define AK89XX_REG_ASAZ (0x12) #define AK89XX_CNTL_MODE_POWER_DOWN (0x00) #define AK89XX_CNTL_MODE_SINGLE_MEASUREMENT (0x01) #define AK89XX_CNTL_MODE_FUSE_ROM_ACCESS (0x0f) /* -------------------------------------------------------------------------- */ struct ak89xx_config { char asa[COMPASS_NUM_AXES]; /* axis sensitivity adjustment */ }; struct ak89xx_private_data { struct ak89xx_config init; }; /* -------------------------------------------------------------------------- */ static int ak89xx_init(void *mlsl_handle, struct ext_slave_descr *slave, struct ext_slave_platform_data *pdata) { int result; unsigned char serial_data[COMPASS_NUM_AXES]; struct ak89xx_private_data *private_data; private_data = (struct ak89xx_private_data *) kzalloc(sizeof(struct ak89xx_private_data), GFP_KERNEL); if (!private_data) return INV_ERROR_MEMORY_EXAUSTED; result = inv_serial_single_write(mlsl_handle, pdata->address, AK89XX_REG_CNTL, AK89XX_CNTL_MODE_POWER_DOWN); if (result) { LOG_RESULT_LOCATION(result); return result; } /* Wait at least 100us */ udelay(100); result = inv_serial_single_write(mlsl_handle, pdata->address, AK89XX_REG_CNTL, AK89XX_CNTL_MODE_FUSE_ROM_ACCESS); if (result) { LOG_RESULT_LOCATION(result); return result; } /* Wait at least 200us */ udelay(200); result = inv_serial_read(mlsl_handle, pdata->address, AK89XX_REG_ASAX, COMPASS_NUM_AXES, serial_data); if (result) { LOG_RESULT_LOCATION(result); return result; } pdata->private_data = private_data; private_data->init.asa[0] = serial_data[0]; private_data->init.asa[1] = serial_data[1]; private_data->init.asa[2] = serial_data[2]; result = inv_serial_single_write(mlsl_handle, pdata->address, AK89XX_REG_CNTL, AK89XX_CNTL_MODE_POWER_DOWN); if (result) { LOG_RESULT_LOCATION(result); return result; } udelay(100); return INV_SUCCESS; } static int ak89xx_exit(void *mlsl_handle, struct ext_slave_descr *slave, struct ext_slave_platform_data *pdata) { kfree(pdata->private_data); return INV_SUCCESS; } static int ak89xx_suspend(void *mlsl_handle, struct ext_slave_descr *slave, struct ext_slave_platform_data *pdata) { int result = INV_SUCCESS; result = inv_serial_single_write(mlsl_handle, pdata->address, AK89XX_REG_CNTL, AK89XX_CNTL_MODE_POWER_DOWN); msleep(1); /* wait at least 100us */ if (result) { LOG_RESULT_LOCATION(result); return result; } return result; } static int ak89xx_resume(void *mlsl_handle, struct ext_slave_descr *slave, struct ext_slave_platform_data *pdata) { int result = INV_SUCCESS; result = inv_serial_single_write(mlsl_handle, pdata->address, AK89XX_REG_CNTL, AK89XX_CNTL_MODE_SINGLE_MEASUREMENT); if (result) { LOG_RESULT_LOCATION(result); return result; } return result; } static int ak89xx_read(void *mlsl_handle, struct ext_slave_descr *slave, struct ext_slave_platform_data *pdata, unsigned char *data) { unsigned char regs[8]; unsigned char *stat = ®s[0]; unsigned char *stat2 = ®s[7]; int result = INV_SUCCESS; int status = INV_SUCCESS; result = inv_serial_read(mlsl_handle, pdata->address, AK89XX_REG_ST1, 8, regs); if (result) { LOG_RESULT_LOCATION(result); return result; } /* Always return the data and the status registers */ memcpy(data, ®s[1], 6); data[6] = regs[0]; data[7] = regs[7]; /* * ST : data ready - * Measurement has been completed and data is ready to be read. */ if (*stat & 0x01) status = INV_SUCCESS; /* * ST2 : data error - * occurs when data read is started outside of a readable period; * data read would not be correct. * Valid in continuous measurement mode only. * In single measurement mode this error should not occour but we * stil account for it and return an error, since the data would be * corrupted. * DERR bit is self-clearing when ST2 register is read. * * This bit is always zero on the AK8963. */ if (*stat2 & 0x04) status = INV_ERROR_COMPASS_DATA_ERROR; /* * ST2 : overflow - * the sum of the absolute values of all axis |X|+|Y|+|Z| < 2400uT. * This is likely to happen in presence of an external magnetic * disturbance; it indicates, the sensor data is incorrect and should * be ignored. * An error is returned. * HOFL bit clears when a new measurement starts. */ if (*stat2 & 0x08) status = INV_ERROR_COMPASS_DATA_OVERFLOW; /* * ST : overrun - * the previous sample was not fetched and lost. * Valid in continuous measurement mode only. * In single measurement mode this error should not occour and we * don't consider this condition an error. * DOR bit is self-clearing when ST2 or any meas. data register is * read. */ if (*stat & 0x02) { /* status = INV_ERROR_COMPASS_DATA_UNDERFLOW; */ status = INV_SUCCESS; } /* * trigger next measurement if: * - stat is non zero; * - if stat is zero and stat2 is non zero. * Won't trigger if data is not ready and there was no error. */ if (*stat != 0x00 || *stat2 != 0x00) { result = inv_serial_single_write( mlsl_handle, pdata->address, AK89XX_REG_CNTL, AK89XX_CNTL_MODE_SINGLE_MEASUREMENT); if (result) { LOG_RESULT_LOCATION(result); return result; } } return status; } static int ak89xx_config(void *mlsl_handle, struct ext_slave_descr *slave, struct ext_slave_platform_data *pdata, struct ext_slave_config *data) { int result; if (!data->data) return INV_ERROR_INVALID_PARAMETER; switch (data->key) { case MPU_SLAVE_WRITE_REGISTERS: result = inv_serial_write(mlsl_handle, pdata->address, data->len, (unsigned char *)data->data); if (result) { LOG_RESULT_LOCATION(result); return result; } break; case MPU_SLAVE_CONFIG_ODR_SUSPEND: case MPU_SLAVE_CONFIG_ODR_RESUME: case MPU_SLAVE_CONFIG_FSR_SUSPEND: case MPU_SLAVE_CONFIG_FSR_RESUME: case MPU_SLAVE_CONFIG_MOT_THS: case MPU_SLAVE_CONFIG_NMOT_THS: case MPU_SLAVE_CONFIG_MOT_DUR: case MPU_SLAVE_CONFIG_NMOT_DUR: case MPU_SLAVE_CONFIG_IRQ_SUSPEND: case MPU_SLAVE_CONFIG_IRQ_RESUME: default: return INV_ERROR_FEATURE_NOT_IMPLEMENTED; }; return INV_SUCCESS; } static int ak89xx_get_config(void *mlsl_handle, struct ext_slave_descr *slave, struct ext_slave_platform_data *pdata, struct ext_slave_config *data) { struct ak89xx_private_data *private_data = pdata->private_data; int result; if (!data->data) return INV_ERROR_INVALID_PARAMETER; switch (data->key) { case MPU_SLAVE_READ_REGISTERS: { unsigned char *serial_data = (unsigned char *)data->data; result = inv_serial_read(mlsl_handle, pdata->address, serial_data[0], data->len - 1, &serial_data[1]); if (result) { LOG_RESULT_LOCATION(result); return result; } break; } case MPU_SLAVE_READ_SCALE: { unsigned char *serial_data = (unsigned char *)data->data; serial_data[0] = private_data->init.asa[0]; serial_data[1] = private_data->init.asa[1]; serial_data[2] = private_data->init.asa[2]; result = INV_SUCCESS; if (result) { LOG_RESULT_LOCATION(result); return result; } break; } case MPU_SLAVE_CONFIG_ODR_SUSPEND: (*(unsigned long *)data->data) = 0; break; case MPU_SLAVE_CONFIG_ODR_RESUME: (*(unsigned long *)data->data) = 8000; break; case MPU_SLAVE_CONFIG_FSR_SUSPEND: case MPU_SLAVE_CONFIG_FSR_RESUME: case MPU_SLAVE_CONFIG_MOT_THS: case MPU_SLAVE_CONFIG_NMOT_THS: case MPU_SLAVE_CONFIG_MOT_DUR: case MPU_SLAVE_CONFIG_NMOT_DUR: case MPU_SLAVE_CONFIG_IRQ_SUSPEND: case MPU_SLAVE_CONFIG_IRQ_RESUME: default: return INV_ERROR_FEATURE_NOT_IMPLEMENTED; }; return INV_SUCCESS; } static struct ext_slave_read_trigger ak89xx_read_trigger = { /*.reg = */ 0x0A, /*.value = */ 0x01 }; static struct ext_slave_descr ak89xx_descr = { .init = ak89xx_init, .exit = ak89xx_exit, .suspend = ak89xx_suspend, .resume = ak89xx_resume, .read = ak89xx_read, .config = ak89xx_config, .get_config = ak89xx_get_config, .name = "ak89xx", .type = EXT_SLAVE_TYPE_COMPASS, .id = COMPASS_ID_AK8963, .read_reg = 0x01, .read_len = 10, .endian = EXT_SLAVE_LITTLE_ENDIAN, .range = {9830, 4000}, .trigger = &ak89xx_read_trigger, }; static struct ext_slave_descr *ak89xx_get_slave_descr(void) { return &ak89xx_descr; } /* -------------------------------------------------------------------------- */ struct ak89xx_mod_private_data { struct i2c_client *client; struct ext_slave_platform_data *pdata; }; static unsigned short normal_i2c[] = { I2C_CLIENT_END }; static int ak89xx_mod_probe(struct i2c_client *client, const struct i2c_device_id *devid) { struct ext_slave_platform_data *pdata; struct ak89xx_mod_private_data *private_data; int result = 0; dev_info(&client->adapter->dev, "%s: %s\n", __func__, devid->name); /* Override default values based on model. */ ak89xx_descr.id = devid->driver_data; /* ak89xx_descr.name = devid->name; */ switch (ak89xx_descr.id) { case COMPASS_ID_AK8963: break; case COMPASS_ID_AK8972: ak89xx_descr.read_len = 9; ak89xx_descr.range.mantissa = 39321; ak89xx_descr.range.fraction = 6000; break; case COMPASS_ID_AK8975: break; default: result = -EFAULT; goto out_no_free; } 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, ak89xx_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 ak89xx_mod_remove(struct i2c_client *client) { struct ak89xx_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, ak89xx_get_slave_descr); kfree(private_data); return 0; } static const struct i2c_device_id ak89xx_mod_id[] = { { "ak8963", COMPASS_ID_AK8963 }, { "ak8972", COMPASS_ID_AK8972 }, { "ak8975", COMPASS_ID_AK8975 }, {} }; MODULE_DEVICE_TABLE(i2c, ak89xx_mod_id); static struct i2c_driver ak89xx_mod_driver = { .class = I2C_CLASS_HWMON, .probe = ak89xx_mod_probe, .remove = ak89xx_mod_remove, .id_table = ak89xx_mod_id, .driver = { .owner = THIS_MODULE, .name = "ak89xx_mod", }, .address_list = normal_i2c, }; static int __init ak89xx_mod_init(void) { int res = i2c_add_driver(&ak89xx_mod_driver); pr_info("%s: Probe name %s\n", __func__, "ak89xx_mod"); if (res) pr_err("%s failed\n", __func__); return res; } static void __exit ak89xx_mod_exit(void) { pr_info("%s\n", __func__); i2c_del_driver(&ak89xx_mod_driver); } module_init(ak89xx_mod_init); module_exit(ak89xx_mod_exit); MODULE_AUTHOR("Invensense Corporation"); MODULE_DESCRIPTION("Driver to integrate AKM AK89XX sensor with the MPU"); MODULE_LICENSE("GPL"); MODULE_ALIAS("ak89xx_mod"); /** * @} */