blob: db27d870e5a83491488b607f5b6885735befa882 [file] [log] [blame]
/* Copyright 2019 The ChromiumOS Authors
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
/**
* LIS2DS Accel module for Chrome EC
* MEMS digital output motion sensor:
* ultra-low-power high-performance 3-axis "pico" accelerometer
*
* For any details on driver implementation please
* Refer to AN4748 Application Note on www.st.com
*/
#include "accelgyro.h"
#include "common.h"
#include "console.h"
#include "driver/accel_lis2ds.h"
#include "hooks.h"
#include "hwtimer.h"
#include "i2c.h"
#include "math_util.h"
#include "motion_sense_fifo.h"
#include "task.h"
#include "timer.h"
#include "util.h"
#ifdef CONFIG_ACCEL_LIS2DS_INT_EVENT
#define ACCEL_LIS2DS_INT_ENABLE
#endif
#define CPRINTS(format, args...) cprints(CC_ACCEL, format, ##args)
STATIC_IF(ACCEL_LIS2DS_INT_ENABLE)
volatile uint32_t last_interrupt_timestamp;
/**
* lis2ds_enable_fifo - Enable/Disable FIFO in LIS2DS12
* @s: Motion sensor pointer
* @mode: fifo_modes
*/
static int lis2ds_enable_fifo(const struct motion_sensor_t *s, int mode)
{
return st_write_data_with_mask(s, LIS2DS_FIFO_CTRL_ADDR,
LIS2DS_FIFO_MODE_MASK, mode);
}
static int lis2ds_config_interrupt(const struct motion_sensor_t *s)
{
int ret = EC_SUCCESS;
/* Interrupt trigger level of power-on-reset is HIGH */
RETURN_ERROR(st_write_data_with_mask(
s, LIS2DS_H_ACTIVE_ADDR, LIS2DS_H_ACTIVE_MASK, LIS2DS_EN_BIT));
/*
* Configure FIFO threshold to 1 sample: interrupt on watermark
* will be generated every time a new data sample will be stored
* in FIFO. The interrupr on watermark is cleared only when the
* number or samples still present in FIFO exceeds the
* configured threshold.
*/
ret = st_raw_write8(s->port, s->i2c_spi_addr_flags,
LIS2DS_FIFO_THS_ADDR, 1);
if (ret != EC_SUCCESS)
return ret;
/* Enable interrupt on FIFO watermark and route to int1. */
ret = st_write_data_with_mask(s, LIS2DS_CTRL4_ADDR, LIS2DS_INT1_FTH,
LIS2DS_EN_BIT);
return ret;
}
#ifdef ACCEL_LIS2DS_INT_ENABLE
/**
* Load data from internal sensor FIFO
* DIFF8 bits set means FIFO Full because 256 samples -> 0x100
*/
static int lis2ds_load_fifo(struct motion_sensor_t *s, uint16_t nsamples,
uint32_t saved_ts)
{
int ret, read_len, fifo_len, chunk_len, i;
int *axis = s->raw_xyz;
uint8_t fifo[FIFO_READ_LEN];
fifo_len = nsamples * OUT_XYZ_SIZE;
read_len = 0;
while (read_len < fifo_len) {
chunk_len = GENERIC_MIN(fifo_len - read_len, sizeof(fifo));
ret = st_raw_read_n_noinc(s->port, s->i2c_spi_addr_flags,
LIS2DS_OUT_X_L_ADDR, fifo, chunk_len);
if (ret != EC_SUCCESS)
return ret;
for (i = 0; i < chunk_len; i += OUT_XYZ_SIZE) {
/* Apply precision, sensitivity and rotation vector */
st_normalize(s, axis, &fifo[i]);
if (IS_ENABLED(CONFIG_ACCEL_SPOOF_MODE) &&
s->flags & MOTIONSENSE_FLAG_IN_SPOOF_MODE)
axis = s->spoof_xyz;
if (IS_ENABLED(CONFIG_ACCEL_FIFO)) {
struct ec_response_motion_sensor_data vect;
/* Fill vector array */
vect.data[0] = axis[0];
vect.data[1] = axis[1];
vect.data[2] = axis[2];
vect.flags = 0;
vect.sensor_num = s - motion_sensors;
motion_sense_fifo_stage_data(&vect, s, 3,
saved_ts);
} else {
motion_sense_push_raw_xyz(s);
}
}
read_len += chunk_len;
};
if (read_len > 0)
motion_sense_fifo_commit_data();
return EC_SUCCESS;
}
/**
* lis2ds_interrupt - interrupt from int1 pin of sensor
* Schedule Motion Sense Task to manage Interrupts
*/
void lis2ds_interrupt(enum gpio_signal signal)
{
last_interrupt_timestamp = __hw_clock_source_read();
task_set_event(TASK_ID_MOTIONSENSE, CONFIG_ACCEL_LIS2DS_INT_EVENT);
}
/**
* lis2ds_irq_handler - bottom half of the interrupt stack.
*/
static int lis2ds_irq_handler(struct motion_sensor_t *s, uint32_t *event)
{
int ret = EC_SUCCESS;
uint16_t nsamples = 0;
uint8_t fifo_src_samples[2];
if ((s->type != MOTIONSENSE_TYPE_ACCEL) ||
(!(*event & CONFIG_ACCEL_LIS2DS_INT_EVENT)))
return EC_ERROR_NOT_HANDLED;
ret = st_raw_read_n_noinc(s->port, s->i2c_spi_addr_flags,
LIS2DS_FIFO_SRC_ADDR,
(uint8_t *)fifo_src_samples,
sizeof(fifo_src_samples));
if (ret != EC_SUCCESS)
return ret;
/* Check if FIFO is full. */
if (fifo_src_samples[0] & LIS2DS_FIFO_OVR_MASK)
CPRINTS("%s FIFO Overrun", s->name);
/* DIFF8 = 1 FIFO FULL, 256 unread samples. */
nsamples = fifo_src_samples[1] & LIS2DS_FIFO_DIFF_MASK;
if (fifo_src_samples[0] & LIS2DS_FIFO_DIFF8_MASK)
nsamples = 256;
return lis2ds_load_fifo(s, nsamples, last_interrupt_timestamp);
}
#endif /* ACCEL_LIS2DS_INT_ENABLE */
/**
* set_range - set full scale range
* @s: Motion sensor pointer
* @range: Range
* @rnd: Round up/down flag
*/
static int set_range(struct motion_sensor_t *s, int range, int rnd)
{
int err;
uint8_t reg_val;
int newrange = ST_NORMALIZE_RATE(range);
/* Adjust and check rounded value */
if (rnd && (newrange < range))
newrange <<= 1;
if (newrange > LIS2DS_ACCEL_FS_MAX_VAL)
newrange = LIS2DS_ACCEL_FS_MAX_VAL;
else if (newrange < LIS2DS_ACCEL_FS_MIN_VAL)
newrange = LIS2DS_ACCEL_FS_MIN_VAL;
reg_val = LIS2DS_FS_REG(newrange);
mutex_lock(s->mutex);
err = st_write_data_with_mask(s, LIS2DS_FS_ADDR, LIS2DS_FS_MASK,
reg_val);
if (err == EC_SUCCESS)
/* Save internally gain for speed optimization. */
s->current_range = newrange;
mutex_unlock(s->mutex);
return EC_SUCCESS;
}
static int set_data_rate(const struct motion_sensor_t *s, int rate, int rnd)
{
int ret, normalized_rate = 0;
struct stprivate_data *data = s->drv_data;
uint8_t reg_val = 0;
mutex_lock(s->mutex);
if (IS_ENABLED(ACCEL_LIS2DS_INT_ENABLE)) {
/* FIFO stop collecting events. Restart FIFO in Bypass mode */
ret = lis2ds_enable_fifo(s, LIS2DS_FIFO_BYPASS_MODE);
if (ret != EC_SUCCESS)
goto unlock_rate;
}
/* Avoid LIS2DS_ODR_TO_REG to manage 0 mHz rate */
if (rate > 0) {
reg_val = LIS2DS_ODR_TO_REG(rate);
normalized_rate = LIS2DS_REG_TO_ODR(reg_val);
if (rnd && (normalized_rate < rate)) {
reg_val++;
normalized_rate = LIS2DS_REG_TO_ODR(rate);
}
if (normalized_rate < LIS2DS_ODR_MIN_VAL ||
normalized_rate > LIS2DS_ODR_MAX_VAL) {
ret = EC_RES_INVALID_PARAM;
goto unlock_rate;
}
}
ret = st_write_data_with_mask(s, LIS2DS_ACC_ODR_ADDR,
LIS2DS_ACC_ODR_MASK, reg_val);
if (ret == EC_SUCCESS) {
data->base.odr = normalized_rate;
if (IS_ENABLED(ACCEL_LIS2DS_INT_ENABLE)) {
/* FIFO restart collecting events in Cont. mode. */
ret = lis2ds_enable_fifo(s, LIS2DS_FIFO_CONT_MODE);
}
}
unlock_rate:
mutex_unlock(s->mutex);
return ret;
}
static int is_data_ready(const struct motion_sensor_t *s, int *ready)
{
int ret, tmp;
ret = st_raw_read8(s->port, s->i2c_spi_addr_flags, LIS2DS_STATUS_REG,
&tmp);
if (ret != EC_SUCCESS) {
CPRINTS("%s: type:0x%X RD XYZ Error %d", s->name, s->type, ret);
return ret;
}
*ready = (LIS2DS_STS_XLDA_UP == (tmp & LIS2DS_STS_XLDA_UP));
return EC_SUCCESS;
}
static int read(const struct motion_sensor_t *s, intv3_t v)
{
uint8_t raw[OUT_XYZ_SIZE];
int ret, tmp = 0;
ret = is_data_ready(s, &tmp);
if (ret != EC_SUCCESS)
return ret;
/*
* If sensor data is not ready, return the previous read data.
* Note: return success so that motion senor task can read again
* to get the latest updated sensor data quickly.
*/
if (!tmp) {
if (v != s->raw_xyz)
memcpy(v, s->raw_xyz, sizeof(s->raw_xyz));
return EC_SUCCESS;
}
/* Read 6 bytes starting at xyz_reg */
ret = st_raw_read_n_noinc(s->port, s->i2c_spi_addr_flags,
LIS2DS_OUT_X_L_ADDR, raw,
LIS2DS_OUT_XYZ_SIZE);
if (ret != EC_SUCCESS) {
CPRINTS("%s: type:0x%X RD XYZ Error %d", s->name, s->type, ret);
return ret;
}
/* Transform from LSB to real data with rotation and gain */
st_normalize(s, v, raw);
return EC_SUCCESS;
}
static int init(struct motion_sensor_t *s)
{
int ret = 0, tmp;
struct stprivate_data *data = s->drv_data;
ret = st_raw_read8(s->port, s->i2c_spi_addr_flags, LIS2DS_WHO_AM_I_REG,
&tmp);
if (ret != EC_SUCCESS)
return EC_ERROR_UNKNOWN;
if (tmp != LIS2DS_WHO_AM_I)
return EC_ERROR_ACCESS_DENIED;
/*
* This sensor can be powered through an EC reboot, so the state of
* the sensor is unknown here. Initiate software reset to restore
* sensor to default.
*/
mutex_lock(s->mutex);
ret = st_raw_write8(s->port, s->i2c_spi_addr_flags,
LIS2DS_SOFT_RESET_ADDR, LIS2DS_SOFT_RESET_MASK);
if (ret != EC_SUCCESS)
goto err_unlock;
msleep(20);
/* Enable BDU */
ret = st_write_data_with_mask(s, LIS2DS_BDU_ADDR, LIS2DS_BDU_MASK,
LIS2DS_EN_BIT);
if (ret != EC_SUCCESS)
goto err_unlock;
ret = st_write_data_with_mask(s, LIS2DS_LIR_ADDR, LIS2DS_LIR_MASK,
LIS2DS_EN_BIT);
if (ret != EC_SUCCESS)
goto err_unlock;
ret = st_write_data_with_mask(s, LIS2DS_INT2_ON_INT1_ADDR,
LIS2DS_INT2_ON_INT1_MASK, LIS2DS_EN_BIT);
if (ret != EC_SUCCESS)
goto err_unlock;
if (IS_ENABLED(ACCEL_LIS2DS_INT_ENABLE))
ret = lis2ds_config_interrupt(s);
if (ret != EC_SUCCESS)
goto err_unlock;
mutex_unlock(s->mutex);
/* Set default resolution */
data->resol = LIS2DS_RESOLUTION;
return sensor_init_done(s);
err_unlock:
mutex_unlock(s->mutex);
CPRINTS("%s: MS Init type:0x%X Error", s->name, s->type);
return ret;
}
const struct accelgyro_drv lis2ds_drv = {
.init = init,
.read = read,
.set_range = set_range,
.get_resolution = st_get_resolution,
.set_data_rate = set_data_rate,
.get_data_rate = st_get_data_rate,
.set_offset = st_set_offset,
.get_offset = st_get_offset,
#ifdef ACCEL_LIS2DS_INT_ENABLE
.irq_handler = lis2ds_irq_handler,
#endif /* CONFIG_ACCEL_INTERRUPTS */
};