From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1754637Ab1GACZP (ORCPT ); Thu, 30 Jun 2011 22:25:15 -0400 Received: from na3sys009aog104.obsmtp.com ([74.125.149.73]:54312 "HELO na3sys009aog104.obsmtp.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with SMTP id S1754479Ab1GACZO (ORCPT ); Thu, 30 Jun 2011 22:25:14 -0400 From: Nathan Royer To: Nathan Royer Cc: Andrew Morton , Greg Kroah-Hartman , Jonathan Cameron , Jiri Kosina , Alan Cox , Jean Delvare , linux-kernel@vger.kernel.org, linux-input@vger.kernel.org Subject: [PATCH 10/11] misc: Add slave driver for ak8975 compass driver Date: Thu, 30 Jun 2011 19:18:26 -0700 Message-Id: <1309486707-1658-10-git-send-email-nroyer@invensense.com> X-Mailer: git-send-email 1.7.4.1 In-Reply-To: <1309486707-1658-1-git-send-email-nroyer@invensense.com> References: <1309486707-1658-1-git-send-email-nroyer@invensense.com> Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Signed-off-by: Nathan Royer --- drivers/misc/inv_mpu/Kconfig | 1 + drivers/misc/inv_mpu/Makefile | 1 + drivers/misc/inv_mpu/compass/Kconfig | 21 ++ drivers/misc/inv_mpu/compass/Makefile | 7 + drivers/misc/inv_mpu/compass/ak8975.c | 500 +++++++++++++++++++++++++++++++++ 5 files changed, 530 insertions(+), 0 deletions(-) create mode 100644 drivers/misc/inv_mpu/compass/Kconfig create mode 100644 drivers/misc/inv_mpu/compass/Makefile create mode 100644 drivers/misc/inv_mpu/compass/ak8975.c diff --git a/drivers/misc/inv_mpu/Kconfig b/drivers/misc/inv_mpu/Kconfig index 6116010..7d9f2c3 100644 --- a/drivers/misc/inv_mpu/Kconfig +++ b/drivers/misc/inv_mpu/Kconfig @@ -56,5 +56,6 @@ config MPU_SENSORS_MPU6050_GYRO endchoice source "drivers/misc/inv_mpu/accel/Kconfig" +source "drivers/misc/inv_mpu/compass/Kconfig" endif #INV_SENSORS diff --git a/drivers/misc/inv_mpu/Makefile b/drivers/misc/inv_mpu/Makefile index d2c2c9a..dc77a1b 100644 --- a/drivers/misc/inv_mpu/Makefile +++ b/drivers/misc/inv_mpu/Makefile @@ -17,3 +17,4 @@ mpu3050-objs += mldl_print_cfg.o EXTRA_CFLAGS += -Idrivers/misc/inv_mpu obj-y += accel/ +obj-y += compass/ diff --git a/drivers/misc/inv_mpu/compass/Kconfig b/drivers/misc/inv_mpu/compass/Kconfig new file mode 100644 index 0000000..f4106d8 --- /dev/null +++ b/drivers/misc/inv_mpu/compass/Kconfig @@ -0,0 +1,21 @@ +menuconfig INV_SENSORS_COMPASS + bool "Compass Slave Sensors" + default y + ---help--- + Say Y here to get to see options for device drivers for various + compasses. This option alone does not add any kernel code. + + If you say N, all options in this submenu will be skipped and disabled. + +if INV_SENSORS_COMPASS + +config MPU_SENSORS_AK8975 + tristate "AKM ak8975" + help + This enables support for the AKM ak8975 compass + This support is for integration with the MPU3050 or MPU6050 gyroscope + device driver. Only one compass can be registered at a time. + Specifying more that one compass in the board file will result + in runtime errors. + +endif diff --git a/drivers/misc/inv_mpu/compass/Makefile b/drivers/misc/inv_mpu/compass/Makefile new file mode 100644 index 0000000..a529476 --- /dev/null +++ b/drivers/misc/inv_mpu/compass/Makefile @@ -0,0 +1,7 @@ +# +# Compass Slaves MPUxxxx +# +obj-$(CONFIG_MPU_SENSORS_AK8975) += inv_mpu_ak8975.o +inv_mpu_ak8975-objs += ak8975.o + +EXTRA_CFLAGS += -Idrivers/misc/inv_mpu diff --git a/drivers/misc/inv_mpu/compass/ak8975.c b/drivers/misc/inv_mpu/compass/ak8975.c new file mode 100644 index 0000000..1d43d5a --- /dev/null +++ b/drivers/misc/inv_mpu/compass/ak8975.c @@ -0,0 +1,500 @@ +/* + $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 ak8975.c + * @brief Magnetometer setup and handling methods for the AKM AK8975, + * AKM AK8975B, and AKM AK8975C 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 AK8975_REG_ST1 (0x02) +#define AK8975_REG_HXL (0x03) +#define AK8975_REG_ST2 (0x09) + +#define AK8975_REG_CNTL (0x0A) +#define AK8975_REG_ASAX (0x10) +#define AK8975_REG_ASAY (0x11) +#define AK8975_REG_ASAZ (0x12) + +#define AK8975_CNTL_MODE_POWER_DOWN (0x00) +#define AK8975_CNTL_MODE_SINGLE_MEASUREMENT (0x01) +#define AK8975_CNTL_MODE_FUSE_ROM_ACCESS (0x0f) + +/* -------------------------------------------------------------------------- */ +struct ak8975_config { + char asa[COMPASS_NUM_AXES]; /* axis sensitivity adjustment */ +}; + +struct ak8975_private_data { + struct ak8975_config init; +}; + +/* -------------------------------------------------------------------------- */ +static int ak8975_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 ak8975_private_data *private_data; + private_data = (struct ak8975_private_data *) + kzalloc(sizeof(struct ak8975_private_data), GFP_KERNEL); + + if (!private_data) + return INV_ERROR_MEMORY_EXAUSTED; + + result = inv_serial_single_write(mlsl_handle, pdata->address, + AK8975_REG_CNTL, + AK8975_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, + AK8975_REG_CNTL, + AK8975_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, + AK8975_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, + AK8975_REG_CNTL, + AK8975_CNTL_MODE_POWER_DOWN); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + + udelay(100); + return INV_SUCCESS; +} + +static int ak8975_exit(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata) +{ + kfree(pdata->private_data); + return INV_SUCCESS; +} + +static int ak8975_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, + AK8975_REG_CNTL, + AK8975_CNTL_MODE_POWER_DOWN); + msleep(1); /* wait at least 100us */ + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + return result; +} + +static int ak8975_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, + AK8975_REG_CNTL, + AK8975_CNTL_MODE_SINGLE_MEASUREMENT); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + return result; +} + +static int ak8975_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, AK8975_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. + */ + 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, + AK8975_REG_CNTL, AK8975_CNTL_MODE_SINGLE_MEASUREMENT); + if (result) { + LOG_RESULT_LOCATION(result); + return result; + } + } + + return status; +} + +static int ak8975_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 ak8975_get_config(void *mlsl_handle, + struct ext_slave_descr *slave, + struct ext_slave_platform_data *pdata, + struct ext_slave_config *data) +{ + struct ak8975_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 ak8975_read_trigger = { + /*.reg = */ 0x0A, + /*.value = */ 0x01 +}; + +static struct ext_slave_descr ak8975_descr = { + .init = ak8975_init, + .exit = ak8975_exit, + .suspend = ak8975_suspend, + .resume = ak8975_resume, + .read = ak8975_read, + .config = ak8975_config, + .get_config = ak8975_get_config, + .name = "ak8975", + .type = EXT_SLAVE_TYPE_COMPASS, + .id = COMPASS_ID_AK8975, + .read_reg = 0x01, + .read_len = 9, + .endian = EXT_SLAVE_LITTLE_ENDIAN, + .range = {9830, 4000}, + .trigger = &ak8975_read_trigger, +}; + +static +struct ext_slave_descr *ak8975_get_slave_descr(void) +{ + return &ak8975_descr; +} + +/* -------------------------------------------------------------------------- */ +struct ak8975_mod_private_data { + struct i2c_client *client; + struct ext_slave_platform_data *pdata; +}; + +static unsigned short normal_i2c[] = { I2C_CLIENT_END }; + +static int ak8975_mod_probe(struct i2c_client *client, + const struct i2c_device_id *devid) +{ + struct ext_slave_platform_data *pdata; + struct ak8975_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, + ak8975_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 ak8975_mod_remove(struct i2c_client *client) +{ + struct ak8975_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, + ak8975_get_slave_descr); + + kfree(private_data); + return 0; +} + +static const struct i2c_device_id ak8975_mod_id[] = { + { "ak8975", COMPASS_ID_AK8975 }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, ak8975_mod_id); + +static struct i2c_driver ak8975_mod_driver = { + .class = I2C_CLASS_HWMON, + .probe = ak8975_mod_probe, + .remove = ak8975_mod_remove, + .id_table = ak8975_mod_id, + .driver = { + .owner = THIS_MODULE, + .name = "ak8975_mod", + }, + .address_list = normal_i2c, +}; + +static int __init ak8975_mod_init(void) +{ + int res = i2c_add_driver(&ak8975_mod_driver); + pr_info("%s: Probe name %s\n", __func__, "ak8975_mod"); + if (res) + pr_err("%s failed\n", __func__); + return res; +} + +static void __exit ak8975_mod_exit(void) +{ + pr_info("%s\n", __func__); + i2c_del_driver(&ak8975_mod_driver); +} + +module_init(ak8975_mod_init); +module_exit(ak8975_mod_exit); + +MODULE_AUTHOR("Invensense Corporation"); +MODULE_DESCRIPTION("Driver to integrate AK8975 sensor with the MPU"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("ak8975_mod"); + +/** + * @} + */ -- 1.7.4.1