Files
OpenCellular/driver/als_si114x.c
Alexandru M Stan b63595258d motion: Lower jitter of Sensor->EC timestamp
Instead getting the time for each sample in the task code, we should be
getting it as soon as the sensor reported it added it to its fifo (so
sensor just finished integration).

Because of that each sensor should provide the time when it provides a
sample, ideally from an accurate spot like an interrupt.

Deprecate motion_sense_fifo_add_unit (without a timestamp) in favour of
motion_sense_fifo_add_data (which adds the timestamps). Update all
relevant sensors to use the new api.

Note: for now I focused on the BMI160, where I actually made it get the
time in the interrupt. The other sensors were made to use the new api,
but still don't record the time in the right place (though it's not any
worse than before).

BUG=b:67743747
TEST=In the kernel, fifo_info->info.timestamp still has sane values.
TEST=CTS should still pass
BRANCH=master

Change-Id: I9829343f8702e00cc19f9c88134fa1f258c9e1e9
Signed-off-by: Alexandru M Stan <amstan@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/807331
Commit-Ready: ChromeOS CL Exonerator Bot <chromiumos-cl-exonerator@appspot.gserviceaccount.com>
Reviewed-by: Gwendal Grignou <gwendal@chromium.org>
2018-03-14 04:06:52 -07:00

591 lines
14 KiB
C

/* Copyright 2015 The Chromium OS Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*
* Silicon Image SI1141/SI1142 light sensor driver
*
* Started from linux si114x driver.
*/
#include "accelgyro.h"
#include "common.h"
#include "console.h"
#include "driver/als_si114x.h"
#include "hooks.h"
#include "hwtimer.h"
#include "i2c.h"
#include "math_util.h"
#include "task.h"
#include "timer.h"
#include "util.h"
#define CPUTS(outstr) cputs(CC_ACCEL, outstr)
#define CPRINTF(format, args...) cprintf(CC_ACCEL, format, ## args)
#define CPRINTS(format, args...) cprints(CC_ACCEL, format, ## args)
static int init(const struct motion_sensor_t *s);
/**
* Read 8bit register from device.
*/
static inline int raw_read8(const int port, const int addr, const int reg,
int *data_ptr)
{
return i2c_read8(port, addr, reg, data_ptr);
}
/**
* Write 8bit register from device.
*/
static inline int raw_write8(const int port, const int addr, const int reg,
int data)
{
return i2c_write8(port, addr, reg, data);
}
/**
* Read 16bit register from device.
*/
static inline int raw_read16(const int port, const int addr, const int reg,
int *data_ptr)
{
return i2c_read16(port, addr, reg, data_ptr);
}
/* helper function to operate on parameter values: op can be query/set/or/and */
static int si114x_param_op(const struct motion_sensor_t *s,
uint8_t op,
uint8_t param,
int *value)
{
int ret;
mutex_lock(s->mutex);
if (op != SI114X_CMD_PARAM_QUERY) {
ret = raw_write8(s->port, s->addr, SI114X_REG_PARAM_WR, *value);
if (ret != EC_SUCCESS)
goto error;
}
ret = raw_write8(s->port, s->addr, SI114X_REG_COMMAND,
op | (param & 0x1F));
if (ret != EC_SUCCESS)
goto error;
ret = raw_read8(s->port, s->addr, SI114X_REG_PARAM_RD, value);
if (ret != EC_SUCCESS)
goto error;
mutex_unlock(s->mutex);
*value &= 0xff;
return EC_SUCCESS;
error:
mutex_unlock(s->mutex);
return ret;
}
static int si114x_read_results(struct motion_sensor_t *s, int nb)
{
int i, ret, val;
struct si114x_drv_data_t *data = SI114X_GET_DATA(s);
struct si114x_typed_data_t *type_data = SI114X_GET_TYPED_DATA(s);
#ifdef CONFIG_ACCEL_FIFO
struct ec_response_motion_sensor_data vector;
#endif
/* Read ALX result */
for (i = 0; i < nb; i++) {
ret = raw_read16(s->port, s->addr,
type_data->base_data_reg + i * 2,
&val);
if (ret)
break;
if (val == SI114X_OVERFLOW) {
/* overflowing, try next time. */
return EC_SUCCESS;
} else if (val + type_data->offset <= 0) {
/* No light */
val = 1;
} else {
/* Add offset, calibration */
val += type_data->offset;
}
/*
* Proximity sensor data is inverse of the distance.
* Return back something proportional to distance,
* we correct later with the scale parameter.
*/
if (s->type == MOTIONSENSE_TYPE_PROX)
val = SI114X_PS_INVERSION(val);
val = val * type_data->scale +
val * type_data->uscale / 10000;
s->raw_xyz[i] = val;
}
if (ret != EC_SUCCESS)
return ret;
if (s->type == MOTIONSENSE_TYPE_PROX)
data->covered = (s->raw_xyz[0] < SI114X_COVERED_THRESHOLD);
else if (data->covered)
/*
* The sensor (proximity & light) is covered. The light data
* will most likely be incorrect (darker than expected), so
* ignore the measurement.
*/
return EC_SUCCESS;
/* Add in fifo if changed only */
for (i = 0; i < nb; i++) {
if (s->raw_xyz[i] != s->xyz[i])
break;
}
if (i == nb)
return EC_ERROR_UNCHANGED;
#ifdef CONFIG_ACCEL_FIFO
vector.flags = 0;
for (i = 0; i < nb; i++)
vector.data[i] = s->raw_xyz[i];
for (i = nb; i < 3; i++)
vector.data[i] = 0;
vector.sensor_num = s - motion_sensors;
motion_sense_fifo_add_data(&vector, s, nb,
__hw_clock_source_read());
/*
* TODO: get time at a more accurate spot.
* Like in si114x_interrupt
*/
#else
/* We need to copy raw_xyz into xyz with mutex */
#endif
return EC_SUCCESS;
}
void si114x_interrupt(enum gpio_signal signal)
{
task_set_event(TASK_ID_MOTIONSENSE,
CONFIG_ALS_SI114X_INT_EVENT, 0);
}
#ifdef CONFIG_ALS_SI114X_POLLING
static void si114x_read_deferred(void)
{
task_set_event(TASK_ID_MOTIONSENSE,
CONFIG_ALS_SI114X_INT_EVENT, 0);
}
DECLARE_DEFERRED(si114x_read_deferred);
#endif
/**
* irq_handler - bottom half of the interrupt stack.
* Ran from the motion_sense task, finds the events that raised the interrupt.
*
* For now, we just print out. We should set a bitmask motion sense code will
* act upon.
*/
static int irq_handler(struct motion_sensor_t *s, uint32_t *event)
{
int ret = EC_SUCCESS, val;
struct si114x_drv_data_t *data = SI114X_GET_DATA(s);
struct si114x_typed_data_t *type_data = SI114X_GET_TYPED_DATA(s);
if (!(*event & CONFIG_ALS_SI114X_INT_EVENT))
return EC_ERROR_NOT_HANDLED;
ret = raw_read8(s->port, s->addr, SI114X_REG_IRQ_STATUS, &val);
if (ret)
return ret;
if (!(val & type_data->irq_flags))
return EC_ERROR_INVAL;
/* clearing IRQ */
ret = raw_write8(s->port, s->addr, SI114X_REG_IRQ_STATUS,
val & type_data->irq_flags);
if (ret != EC_SUCCESS)
CPRINTS("clearing irq failed");
switch (data->state) {
case SI114X_ALS_IN_PROGRESS:
case SI114X_ALS_IN_PROGRESS_PS_PENDING:
/* We are only reading the visible light sensor */
ret = si114x_read_results(s, 1);
/* Fire pending requests */
if (data->state == SI114X_ALS_IN_PROGRESS_PS_PENDING) {
ret = raw_write8(s->port, s->addr, SI114X_REG_COMMAND,
SI114X_CMD_PS_FORCE);
data->state = SI114X_PS_IN_PROGRESS;
} else {
data->state = SI114X_IDLE;
}
break;
case SI114X_PS_IN_PROGRESS:
case SI114X_PS_IN_PROGRESS_ALS_PENDING:
/* Read PS results */
ret = si114x_read_results(s, SI114X_NUM_LEDS);
if (data->state == SI114X_PS_IN_PROGRESS_ALS_PENDING) {
ret = raw_write8(s->port, s->addr, SI114X_REG_COMMAND,
SI114X_CMD_ALS_FORCE);
data->state = SI114X_ALS_IN_PROGRESS;
} else {
data->state = SI114X_IDLE;
}
break;
case SI114X_IDLE:
default:
CPRINTS("Invalid state");
}
return ret;
}
/* Just trigger a measurement */
static int read(const struct motion_sensor_t *s, vector_3_t v)
{
int ret = 0;
uint8_t cmd;
struct si114x_drv_data_t *data = SI114X_GET_DATA(s);
switch (data->state) {
case SI114X_ALS_IN_PROGRESS:
if (s->type == MOTIONSENSE_TYPE_PROX)
data->state = SI114X_ALS_IN_PROGRESS_PS_PENDING;
#if 0
else
CPRINTS("Invalid state");
#endif
ret = EC_ERROR_BUSY;
break;
case SI114X_PS_IN_PROGRESS:
if (s->type == MOTIONSENSE_TYPE_LIGHT)
data->state = SI114X_PS_IN_PROGRESS_ALS_PENDING;
#if 0
else
CPRINTS("Invalid state");
#endif
ret = EC_ERROR_BUSY;
break;
case SI114X_IDLE:
switch (s->type) {
case MOTIONSENSE_TYPE_LIGHT:
cmd = SI114X_CMD_ALS_FORCE;
data->state = SI114X_ALS_IN_PROGRESS;
break;
case MOTIONSENSE_TYPE_PROX:
cmd = SI114X_CMD_PS_FORCE;
data->state = SI114X_PS_IN_PROGRESS;
break;
default:
CPRINTS("Invalid sensor type");
return EC_ERROR_INVAL;
}
ret = raw_write8(s->port, s->addr, SI114X_REG_COMMAND, cmd);
#ifdef CONFIG_ALS_SI114X_POLLING
hook_call_deferred(&si114x_read_deferred_data,
SI114x_POLLING_DELAY);
#endif
ret = EC_RES_IN_PROGRESS;
break;
case SI114X_ALS_IN_PROGRESS_PS_PENDING:
case SI114X_PS_IN_PROGRESS_ALS_PENDING:
ret = EC_ERROR_ACCESS_DENIED;
break;
case SI114X_NOT_READY:
ret = EC_ERROR_NOT_POWERED;
}
if (ret == EC_ERROR_ACCESS_DENIED &&
s->type == MOTIONSENSE_TYPE_LIGHT) {
timestamp_t ts_now = get_time();
/*
* We were unable to access the sensor for THRES time.
* We should reset the sensor to clear the interrupt register
* and the state machine.
*/
if (time_after(ts_now.le.lo,
s->last_collection + SI114X_DENIED_THRESHOLD)) {
int ret, val;
ret = raw_read8(s->port, s->addr,
SI114X_REG_IRQ_STATUS, &val);
CPRINTS("%d stuck IRQ_STATUS 0x%02x - ret %d",
s->name, val, ret);
init(s);
}
}
return ret;
}
static int si114x_set_chlist(const struct motion_sensor_t *s)
{
int reg = 0;
/* Not interested in temperature (AUX nor IR) */
reg = SI114X_CHLIST_EN_ALSVIS;
switch (SI114X_NUM_LEDS) {
case 3:
reg |= SI114X_CHLIST_EN_PS3;
case 2:
reg |= SI114X_CHLIST_EN_PS2;
case 1:
reg |= SI114X_CHLIST_EN_PS1;
break;
}
return si114x_param_op(s, SI114X_CMD_PARAM_SET,
SI114X_PARAM_CHLIST, &reg);
}
#ifdef CONFIG_ALS_SI114X_CHECK_REVISION
static int si114x_revisions(const struct motion_sensor_t *s)
{
int val;
int ret = raw_read8(s->port, s->addr, SI114X_REG_PART_ID, &val);
if (ret != EC_SUCCESS)
return ret;
if (val != CONFIG_ALS_SI114X) {
CPRINTS("invalid part");
return EC_ERROR_ACCESS_DENIED;
}
ret = raw_read8(s->port, s->port, s->addr, SI114X_REG_SEQ_ID, &val);
if (ret != EC_SUCCESS)
return ret;
if (val < SI114X_SEQ_REV_A03)
CPRINTS("WARNING: old sequencer revision");
return 0;
}
#endif
static int si114x_initialize(const struct motion_sensor_t *s)
{
int ret, val;
/* send reset command */
ret = raw_write8(s->port, s->addr, SI114X_REG_COMMAND,
SI114X_CMD_RESET);
if (ret != EC_SUCCESS)
return ret;
msleep(20);
/* hardware key, magic value */
ret = raw_write8(s->port, s->addr, SI114X_REG_HW_KEY, 0x17);
if (ret != EC_SUCCESS)
return ret;
msleep(20);
/* interrupt configuration, interrupt output enable */
ret = raw_write8(s->port, s->addr, SI114X_REG_INT_CFG,
SI114X_INT_CFG_OE);
if (ret != EC_SUCCESS)
return ret;
/* enable interrupt for certain activities */
ret = raw_write8(s->port, s->addr, SI114X_REG_IRQ_ENABLE,
SI114X_PS3_IE | SI114X_PS2_IE | SI114X_PS1_IE |
SI114X_ALS_INT0_IE);
if (ret != EC_SUCCESS)
return ret;
/* Only forced mode */
ret = raw_write8(s->port, s->addr, SI114X_REG_MEAS_RATE, 0);
if (ret != EC_SUCCESS)
return ret;
/* measure ALS every time device wakes up */
ret = raw_write8(s->port, s->addr, SI114X_REG_ALS_RATE, 0);
if (ret != EC_SUCCESS)
return ret;
/* measure proximity every time device wakes up */
ret = raw_write8(s->port, s->addr, SI114X_REG_PS_RATE, 0);
if (ret != EC_SUCCESS)
return ret;
/* set LED currents to maximum */
switch (SI114X_NUM_LEDS) {
case 3:
ret = raw_write8(s->port, s->addr,
SI114X_REG_PS_LED3, 0x0f);
if (ret != EC_SUCCESS)
return ret;
ret = raw_write8(s->port, s->addr,
SI114X_REG_PS_LED21, 0xff);
break;
case 2:
ret = raw_write8(s->port, s->addr,
SI114X_REG_PS_LED21, 0xff);
break;
case 1:
ret = raw_write8(s->port, s->addr,
SI114X_REG_PS_LED21, 0x0f);
break;
}
if (ret != EC_SUCCESS)
return ret;
ret = si114x_set_chlist(s);
if (ret != EC_SUCCESS)
return ret;
/* set normal proximity measurement mode, set high signal range
* PS measurement */
val = SI114X_PARAM_PS_ADC_MISC_NORMAL_MODE;
ret = si114x_param_op(s, SI114X_CMD_PARAM_SET,
SI114X_PARAM_PS_ADC_MISC, &val);
return ret;
}
static int set_resolution(const struct motion_sensor_t *s,
int res,
int rnd)
{
int ret, reg1, reg2, val;
/* override on resolution: set the gain. between 0 to 7 */
if (s->type == MOTIONSENSE_TYPE_PROX) {
if (res < 0 || res > 5)
return EC_ERROR_PARAM2;
reg1 = SI114X_PARAM_PS_ADC_GAIN;
reg2 = SI114X_PARAM_PS_ADC_COUNTER;
} else {
if (res < 0 || res > 7)
return EC_ERROR_PARAM2;
reg1 = SI114X_PARAM_ALSVIS_ADC_GAIN;
reg2 = SI114X_PARAM_ALSVIS_ADC_COUNTER;
}
val = res;
ret = si114x_param_op(s, SI114X_CMD_PARAM_SET, reg1, &val);
if (ret != EC_SUCCESS)
return ret;
/* set recovery period to one's complement of gain */
val = (~res & 0x07) << 4;
ret = si114x_param_op(s, SI114X_CMD_PARAM_SET, reg2, &val);
return ret;
}
static int get_resolution(const struct motion_sensor_t *s)
{
int ret, reg, val;
if (s->type == MOTIONSENSE_TYPE_PROX)
reg = SI114X_PARAM_PS_ADC_GAIN;
else
/* ignore IR led */
reg = SI114X_PARAM_ALSVIS_ADC_GAIN;
val = 0;
ret = si114x_param_op(s, SI114X_CMD_PARAM_QUERY, reg, &val);
if (ret != EC_SUCCESS)
return -1;
return val & 0x07;
}
static int set_range(const struct motion_sensor_t *s,
int range,
int rnd)
{
struct si114x_typed_data_t *data = SI114X_GET_TYPED_DATA(s);
data->scale = range >> 16;
data->uscale = range & 0xffff;
return EC_SUCCESS;
}
static int get_range(const struct motion_sensor_t *s)
{
struct si114x_typed_data_t *data = SI114X_GET_TYPED_DATA(s);
return (data->scale << 16) | (data->uscale);
}
static int get_data_rate(const struct motion_sensor_t *s)
{
/* Sensor in forced mode, rate is used by motion_sense */
struct si114x_typed_data_t *data = SI114X_GET_TYPED_DATA(s);
return data->rate;
}
static int set_data_rate(const struct motion_sensor_t *s,
int rate,
int rnd)
{
struct si114x_typed_data_t *data = SI114X_GET_TYPED_DATA(s);
data->rate = rate;
return EC_SUCCESS;
}
static int set_offset(const struct motion_sensor_t *s,
const int16_t *offset,
int16_t temp)
{
struct si114x_typed_data_t *data = SI114X_GET_TYPED_DATA(s);
data->offset = offset[X];
return EC_SUCCESS;
}
static int get_offset(const struct motion_sensor_t *s,
int16_t *offset,
int16_t *temp)
{
struct si114x_typed_data_t *data = SI114X_GET_TYPED_DATA(s);
offset[X] = data->offset;
offset[Y] = 0;
offset[Z] = 0;
*temp = EC_MOTION_SENSE_INVALID_CALIB_TEMP;
return EC_SUCCESS;
}
static int init(const struct motion_sensor_t *s)
{
int ret, resol;
struct si114x_drv_data_t *data = SI114X_GET_DATA(s);
/* initialize only once: light must be declared first. */
if (s->type == MOTIONSENSE_TYPE_LIGHT) {
#ifdef CONFIG_ALS_SI114X_CHECK_REVISION
ret = si114x_revisions(s);
if (ret != EC_SUCCESS)
return ret;
#endif
ret = si114x_initialize(s);
if (ret != EC_SUCCESS)
return ret;
data->state = SI114X_IDLE;
resol = 7;
} else {
if (data->state == SI114X_NOT_READY)
return EC_ERROR_ACCESS_DENIED;
resol = 5;
}
/*
* Sensor is most likely behind a glass.
* Max out the gain to get correct measurement
*/
set_resolution(s, resol, 0);
return sensor_init_done(s);
}
const struct accelgyro_drv si114x_drv = {
.init = init,
.read = read,
.set_range = set_range,
.get_range = get_range,
.set_resolution = set_resolution,
.get_resolution = get_resolution,
.set_data_rate = set_data_rate,
.get_data_rate = get_data_rate,
.set_offset = set_offset,
.get_offset = get_offset,
.perform_calib = NULL,
#ifdef CONFIG_ACCEL_INTERRUPTS
.irq_handler = irq_handler,
#endif
};