/* $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 ACCELDL * @brief Provides the interface to setup and handle an accelerometer. * * @{ * @file mpu6050.c * @brief Accelerometer setup and handling methods for Invensense MPU6050 */ /* -------------------------------------------------------------------------- */ #include #include #include #include #include #include #include #include "mpu-dev.h" #include #include #include "mpu6050b1.h" #include "mlsl.h" #include "mldl_cfg.h" #undef MPL_LOG_TAG #define MPL_LOG_TAG "MPL-acc" /* -------------------------------------------------------------------------- */ struct mpu6050_config { unsigned int odr; /**< output data rate 1/1000 Hz */ unsigned int fsr; /**< full scale range mg */ unsigned int ths; /**< mot/no-mot thseshold mg */ unsigned int dur; /**< mot/no-mot duration ms */ unsigned int irq_type; /**< irq type */ }; struct mpu6050_private_data { struct mpu6050_config suspend; struct mpu6050_config resume; struct mldl_cfg *mldl_cfg_ref; }; /* -------------------------------------------------------------------------- */ static int mpu6050_set_mldl_cfg_ref(void *mlsl_handle, struct ext_slave_platform_data *pdata, struct mldl_cfg *mldl_cfg_ref) { struct mpu6050_private_data *private_data = (struct mpu6050_private_data *)pdata->private_data; private_data->mldl_cfg_ref = mldl_cfg_ref; return 0; } static int mpu6050_set_lp_mode(void *mlsl_handle, struct ext_slave_platform_data *pdata, unsigned char lpa_freq) { unsigned char b = 0; /* Reducing the duration setting for lp mode */ b = 1; inv_serial_single_write(mlsl_handle, pdata->address, MPUREG_ACCEL_MOT_DUR, b); /* Setting the cycle bit and LPA wake up freq */ inv_serial_read(mlsl_handle, pdata->address, MPUREG_PWR_MGMT_1, 1, &b); b |= BIT_CYCLE | BIT_PD_PTAT; inv_serial_single_write(mlsl_handle, pdata->address, MPUREG_PWR_MGMT_1, b); inv_serial_read(mlsl_handle, pdata->address, MPUREG_PWR_MGMT_2, 1, &b); b |= lpa_freq & BITS_LPA_WAKE_CTRL; inv_serial_single_write(mlsl_handle, pdata->address, MPUREG_PWR_MGMT_2, b); return INV_SUCCESS; } static int mpu6050_set_fp_mode(void *mlsl_handle, struct ext_slave_platform_data *pdata) { unsigned char b; struct mpu6050_private_data *private_data = (struct mpu6050_private_data *)pdata->private_data; /* Resetting the cycle bit and LPA wake up freq */ inv_serial_read(mlsl_handle, pdata->address, MPUREG_PWR_MGMT_1, 1, &b); b &= ~BIT_CYCLE & ~BIT_PD_PTAT; inv_serial_single_write(mlsl_handle, pdata->address, MPUREG_PWR_MGMT_1, b); inv_serial_read(mlsl_handle, pdata->address, MPUREG_PWR_MGMT_2, 1, &b); b &= ~BITS_LPA_WAKE_CTRL; inv_serial_single_write(mlsl_handle, pdata->address, MPUREG_PWR_MGMT_2, b); /* Resetting the duration setting for fp mode */ b = (unsigned char)private_data->suspend.ths / ACCEL_MOT_DUR_LSB; inv_serial_single_write(mlsl_handle, pdata->address, MPUREG_ACCEL_MOT_DUR, b); return INV_SUCCESS; } /** * Record the odr for use in computing duration values. * * @param config Config to set, suspend or resume structure * @param odr output data rate in 1/1000 hz */ static int mpu6050_set_odr(void *mlsl_handle, struct ext_slave_platform_data *pdata, struct mpu6050_config *config, long apply, long odr) { int result; unsigned char b; unsigned char lpa_freq = 1; /* Default value */ long base; int total_divider; struct mpu6050_private_data *private_data = (struct mpu6050_private_data *)pdata->private_data; struct mldl_cfg *mldl_cfg_ref = (struct mldl_cfg *)private_data->mldl_cfg_ref; if (mldl_cfg_ref) { base = 1000 * inv_mpu_get_sampling_rate_hz(mldl_cfg_ref->mpu_gyro_cfg) * (mldl_cfg_ref->mpu_gyro_cfg->divider + 1); } else { /* have no reference to mldl_cfg => assume base rate is 1000 */ base = 1000000L; } if (odr != 0) { total_divider = (base / odr) - 1; /* final odr MAY be different from requested odr due to integer truncation */ config->odr = base / (total_divider + 1); } else { config->odr = 0; return 0; } /* if the DMP and/or gyros are on, don't set the ODR => the DMP/gyro mldl_cfg->divider setting will handle it */ if (apply && (mldl_cfg_ref && !(mldl_cfg_ref->inv_mpu_cfg->requested_sensors & INV_DMP_PROCESSOR))) { result = inv_serial_single_write(mlsl_handle, pdata->address, MPUREG_SMPLRT_DIV, (unsigned char)total_divider); if (result) { LOG_RESULT_LOCATION(result); return result; } MPL_LOGI("ODR : %d mHz\n", config->odr); } /* Decide whether to put accel in LP mode or pull out of LP mode based on the odr. */ switch (odr) { case 1000: lpa_freq = BITS_LPA_WAKE_1HZ; break; case 2000: lpa_freq = BITS_LPA_WAKE_2HZ; break; case 10000: lpa_freq = BITS_LPA_WAKE_10HZ; break; case 40000: lpa_freq = BITS_LPA_WAKE_40HZ; break; default: inv_serial_read(mlsl_handle, pdata->address, MPUREG_PWR_MGMT_1, 1, &b); b &= BIT_CYCLE; if (b == BIT_CYCLE) { MPL_LOGI(" Accel LP - > FP mode. \n "); mpu6050_set_fp_mode(mlsl_handle, pdata); } } /* If lpa_freq default value was changed, set into LP mode */ if (lpa_freq != 1) { MPL_LOGI(" Accel FP - > LP mode. \n "); mpu6050_set_lp_mode(mlsl_handle, pdata, lpa_freq); } return 0; } static int mpu6050_set_fsr(void *mlsl_handle, struct ext_slave_platform_data *pdata, struct mpu6050_config *config, long apply, long fsr) { unsigned char fsr_mask; int result; if (fsr <= 2000) { config->fsr = 2000; fsr_mask = 0x00; } else if (fsr <= 4000) { config->fsr = 4000; fsr_mask = 0x08; } else if (fsr <= 8000) { config->fsr = 8000; fsr_mask = 0x10; } else { /* fsr = [8001, oo) */ config->fsr = 16000; fsr_mask = 0x18; } if (apply) { unsigned char reg; result = inv_serial_read(mlsl_handle, pdata->address, MPUREG_ACCEL_CONFIG, 1, ®); if (result) { LOG_RESULT_LOCATION(result); return result; } result = inv_serial_single_write(mlsl_handle, pdata->address, MPUREG_ACCEL_CONFIG, reg | fsr_mask); if (result) { LOG_RESULT_LOCATION(result); return result; } MPL_LOGV("FSR: %d\n", config->fsr); } return 0; } static int mpu6050_set_irq(void *mlsl_handle, struct ext_slave_platform_data *pdata, struct mpu6050_config *config, long apply, long irq_type) { int result; unsigned char reg_int_cfg; switch (irq_type) { case MPU_SLAVE_IRQ_TYPE_DATA_READY: config->irq_type = irq_type; reg_int_cfg = BIT_RAW_RDY_EN; break; /* todo: add MOTION, NO_MOTION, and FREEFALL */ case MPU_SLAVE_IRQ_TYPE_NONE: /* Do nothing, not even set the interrupt because it is shared with the gyro */ config->irq_type = irq_type; return 0; default: return INV_ERROR_INVALID_PARAMETER; } if (apply) { result = inv_serial_single_write(mlsl_handle, pdata->address, MPUREG_INT_ENABLE, reg_int_cfg); if (result) { LOG_RESULT_LOCATION(result); return result; } MPL_LOGV("irq_type: %d\n", config->irq_type); } return 0; } static int mpu6050_set_ths(void *mlsl_handle, struct ext_slave_platform_data *slave, struct mpu6050_config *config, long apply, long ths) { if (ths < 0) ths = 0; config->ths = ths; MPL_LOGV("THS: %d\n", config->ths); return 0; } static int mpu6050_set_dur(void *mlsl_handle, struct ext_slave_platform_data *slave, struct mpu6050_config *config, long apply, long dur) { if (dur < 0) dur = 0; config->dur = dur; MPL_LOGV("DUR: %d\n", config->dur); return 0; } static int mpu6050_init(void *mlsl_handle, struct ext_slave_descr *slave, struct ext_slave_platform_data *pdata) { int result; struct mpu6050_private_data *private_data; private_data = kzalloc(sizeof(*private_data), GFP_KERNEL); if (!private_data) return INV_ERROR_MEMORY_EXAUSTED; pdata->private_data = private_data; result = mpu6050_set_odr(mlsl_handle, pdata, &private_data->suspend, false, 0); if (result) { LOG_RESULT_LOCATION(result); return result; } result = mpu6050_set_odr(mlsl_handle, pdata, &private_data->resume, false, 200000); if (result) { LOG_RESULT_LOCATION(result); return result; } result = mpu6050_set_fsr(mlsl_handle, pdata, &private_data->suspend, false, 2000); if (result) { LOG_RESULT_LOCATION(result); return result; } result = mpu6050_set_fsr(mlsl_handle, pdata, &private_data->resume, false, 2000); if (result) { LOG_RESULT_LOCATION(result); return result; } result = mpu6050_set_irq(mlsl_handle, pdata, &private_data->suspend, false, MPU_SLAVE_IRQ_TYPE_NONE); if (result) { LOG_RESULT_LOCATION(result); return result; } result = mpu6050_set_irq(mlsl_handle, pdata, &private_data->resume, false, MPU_SLAVE_IRQ_TYPE_NONE); if (result) { LOG_RESULT_LOCATION(result); return result; } result = mpu6050_set_ths(mlsl_handle, pdata, &private_data->suspend, false, 80); if (result) { LOG_RESULT_LOCATION(result); return result; } result = mpu6050_set_ths(mlsl_handle, pdata, &private_data->resume, false, 40); if (result) { LOG_RESULT_LOCATION(result); return result; } result = mpu6050_set_dur(mlsl_handle, pdata, &private_data->suspend, false, 1000); if (result) { LOG_RESULT_LOCATION(result); return result; } result = mpu6050_set_dur(mlsl_handle, pdata, &private_data->resume, false, 2540); if (result) { LOG_RESULT_LOCATION(result); return result; } return 0; } static int mpu6050_exit(void *mlsl_handle, struct ext_slave_descr *slave, struct ext_slave_platform_data *pdata) { kfree(pdata->private_data); pdata->private_data = NULL; return 0; } static int mpu6050_suspend(void *mlsl_handle, struct ext_slave_descr *slave, struct ext_slave_platform_data *pdata) { unsigned char reg; int result; struct mpu6050_private_data *private_data = (struct mpu6050_private_data *)pdata->private_data; result = mpu6050_set_odr(mlsl_handle, pdata, &private_data->suspend, true, private_data->suspend.odr); if (result) { LOG_RESULT_LOCATION(result); return result; } result = mpu6050_set_irq(mlsl_handle, pdata, &private_data->suspend, true, private_data->suspend.irq_type); if (result) { LOG_RESULT_LOCATION(result); return result; } result = inv_serial_read(mlsl_handle, pdata->address, MPUREG_PWR_MGMT_2, 1, ®); if (result) { LOG_RESULT_LOCATION(result); return result; } reg |= (BIT_STBY_XA | BIT_STBY_YA | BIT_STBY_ZA); result = inv_serial_single_write(mlsl_handle, pdata->address, MPUREG_PWR_MGMT_2, reg); if (result) { LOG_RESULT_LOCATION(result); return result; } return 0; } static int mpu6050_resume(void *mlsl_handle, struct ext_slave_descr *slave, struct ext_slave_platform_data *pdata) { int result; unsigned char reg; struct mpu6050_private_data *private_data = (struct mpu6050_private_data *)pdata->private_data; result = inv_serial_read(mlsl_handle, pdata->address, MPUREG_PWR_MGMT_1, 1, ®); if (result) { LOG_RESULT_LOCATION(result); return result; } if (reg & BIT_SLEEP) { result = inv_serial_single_write(mlsl_handle, pdata->address, MPUREG_PWR_MGMT_1, reg & ~BIT_SLEEP); if (result) { LOG_RESULT_LOCATION(result); return result; } } msleep(2); result = inv_serial_read(mlsl_handle, pdata->address, MPUREG_PWR_MGMT_2, 1, ®); if (result) { LOG_RESULT_LOCATION(result); return result; } reg &= ~(BIT_STBY_XA | BIT_STBY_YA | BIT_STBY_ZA); result = inv_serial_single_write(mlsl_handle, pdata->address, MPUREG_PWR_MGMT_2, reg); if (result) { LOG_RESULT_LOCATION(result); return result; } /* settings */ result = mpu6050_set_fsr(mlsl_handle, pdata, &private_data->resume, true, private_data->resume.fsr); if (result) { LOG_RESULT_LOCATION(result); return result; } result = mpu6050_set_odr(mlsl_handle, pdata, &private_data->resume, true, private_data->resume.odr); if (result) { LOG_RESULT_LOCATION(result); return result; } result = mpu6050_set_irq(mlsl_handle, pdata, &private_data->resume, true, private_data->resume.irq_type); /* motion, no_motion */ /* TODO : port these in their respective _set_thrs and _set_dur functions and use the APPLY paremeter to apply just like _set_odr, _set_irq, and _set_fsr. */ reg = (unsigned char)private_data->suspend.ths / ACCEL_MOT_THR_LSB; result = inv_serial_single_write(mlsl_handle, pdata->address, MPUREG_ACCEL_MOT_THR, reg); if (result) { LOG_RESULT_LOCATION(result); return result; } reg = (unsigned char) ACCEL_ZRMOT_THR_LSB_CONVERSION(private_data->resume.ths); result = inv_serial_single_write(mlsl_handle, pdata->address, MPUREG_ACCEL_ZRMOT_THR, reg); if (result) { LOG_RESULT_LOCATION(result); return result; } reg = (unsigned char)private_data->suspend.ths / ACCEL_MOT_DUR_LSB; result = inv_serial_single_write(mlsl_handle, pdata->address, MPUREG_ACCEL_MOT_DUR, reg); if (result) { LOG_RESULT_LOCATION(result); return result; } reg = (unsigned char)private_data->resume.ths / ACCEL_ZRMOT_DUR_LSB; result = inv_serial_single_write(mlsl_handle, pdata->address, MPUREG_ACCEL_ZRMOT_DUR, reg); if (result) { LOG_RESULT_LOCATION(result); return result; } return 0; } static int mpu6050_read(void *mlsl_handle, struct ext_slave_descr *slave, struct ext_slave_platform_data *pdata, unsigned char *data) { int result; result = inv_serial_read(mlsl_handle, pdata->address, slave->read_reg, slave->read_len, data); return result; } static int mpu6050_config(void *mlsl_handle, struct ext_slave_descr *slave, struct ext_slave_platform_data *pdata, struct ext_slave_config *data) { struct mpu6050_private_data *private_data = (struct mpu6050_private_data *)pdata->private_data; if (!data->data) return INV_ERROR_INVALID_PARAMETER; switch (data->key) { case MPU_SLAVE_CONFIG_ODR_SUSPEND: return mpu6050_set_odr(mlsl_handle, pdata, &private_data->suspend, data->apply, *((long *)data->data)); case MPU_SLAVE_CONFIG_ODR_RESUME: return mpu6050_set_odr(mlsl_handle, pdata, &private_data->resume, data->apply, *((long *)data->data)); case MPU_SLAVE_CONFIG_FSR_SUSPEND: return mpu6050_set_fsr(mlsl_handle, pdata, &private_data->suspend, data->apply, *((long *)data->data)); case MPU_SLAVE_CONFIG_FSR_RESUME: return mpu6050_set_fsr(mlsl_handle, pdata, &private_data->resume, data->apply, *((long *)data->data)); case MPU_SLAVE_CONFIG_MOT_THS: return mpu6050_set_ths(mlsl_handle, pdata, &private_data->suspend, data->apply, *((long *)data->data)); case MPU_SLAVE_CONFIG_NMOT_THS: return mpu6050_set_ths(mlsl_handle, pdata, &private_data->resume, data->apply, *((long *)data->data)); case MPU_SLAVE_CONFIG_MOT_DUR: return mpu6050_set_dur(mlsl_handle, pdata, &private_data->suspend, data->apply, *((long *)data->data)); case MPU_SLAVE_CONFIG_NMOT_DUR: return mpu6050_set_dur(mlsl_handle, pdata, &private_data->resume, data->apply, *((long *)data->data)); case MPU_SLAVE_CONFIG_IRQ_SUSPEND: return mpu6050_set_irq(mlsl_handle, pdata, &private_data->suspend, data->apply, *((long *)data->data)); break; case MPU_SLAVE_CONFIG_IRQ_RESUME: return mpu6050_set_irq(mlsl_handle, pdata, &private_data->resume, data->apply, *((long *)data->data)); case MPU_SLAVE_CONFIG_INTERNAL_REFERENCE: return mpu6050_set_mldl_cfg_ref(mlsl_handle, pdata, (struct mldl_cfg *)data->data); break; default: return INV_ERROR_FEATURE_NOT_IMPLEMENTED; }; return 0; } static int mpu6050_get_config(void *mlsl_handle, struct ext_slave_descr *slave, struct ext_slave_platform_data *pdata, struct ext_slave_config *data) { struct mpu6050_private_data *private_data = (struct mpu6050_private_data *)pdata->private_data; if (!data->data) return INV_ERROR_INVALID_PARAMETER; switch (data->key) { case MPU_SLAVE_CONFIG_ODR_SUSPEND: (*(unsigned long *)data->data) = (unsigned long)private_data->suspend.odr; break; case MPU_SLAVE_CONFIG_ODR_RESUME: (*(unsigned long *)data->data) = (unsigned long)private_data->resume.odr; break; case MPU_SLAVE_CONFIG_FSR_SUSPEND: (*(unsigned long *)data->data) = (unsigned long)private_data->suspend.fsr; break; case MPU_SLAVE_CONFIG_FSR_RESUME: (*(unsigned long *)data->data) = (unsigned long)private_data->resume.fsr; break; case MPU_SLAVE_CONFIG_MOT_THS: (*(unsigned long *)data->data) = (unsigned long)private_data->suspend.ths; break; case MPU_SLAVE_CONFIG_NMOT_THS: (*(unsigned long *)data->data) = (unsigned long)private_data->resume.ths; break; case MPU_SLAVE_CONFIG_MOT_DUR: (*(unsigned long *)data->data) = (unsigned long)private_data->suspend.dur; break; case MPU_SLAVE_CONFIG_NMOT_DUR: (*(unsigned long *)data->data) = (unsigned long)private_data->resume.dur; break; case MPU_SLAVE_CONFIG_IRQ_SUSPEND: (*(unsigned long *)data->data) = (unsigned long)private_data->suspend.irq_type; break; case MPU_SLAVE_CONFIG_IRQ_RESUME: (*(unsigned long *)data->data) = (unsigned long)private_data->resume.irq_type; break; default: return INV_ERROR_FEATURE_NOT_IMPLEMENTED; }; return 0; } static struct ext_slave_descr mpu6050_descr = { .init = mpu6050_init, .exit = mpu6050_exit, .suspend = mpu6050_suspend, .resume = mpu6050_resume, .read = mpu6050_read, .config = mpu6050_config, .get_config = mpu6050_get_config, .name = "mpu6050", .type = EXT_SLAVE_TYPE_ACCEL, .id = ACCEL_ID_MPU6050, .read_reg = 0x3B, .read_len = 6, .endian = EXT_SLAVE_BIG_ENDIAN, .range = {2, 0}, .trigger = NULL, }; struct ext_slave_descr *mpu6050_get_slave_descr(void) { return &mpu6050_descr; } /** * @} */