604 lines
15 KiB
C
604 lines
15 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 "motion_sense_fifo.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 uint16_t i2c_addr_flags,
|
|
const int reg, int *data_ptr)
|
|
{
|
|
return i2c_read8(port, i2c_addr_flags, reg, data_ptr);
|
|
}
|
|
|
|
/**
|
|
* Write 8bit register from device.
|
|
*/
|
|
static inline int raw_write8(const int port, const uint16_t i2c_addr_flags,
|
|
const int reg, int data)
|
|
{
|
|
return i2c_write8(port, i2c_addr_flags, reg, data);
|
|
}
|
|
|
|
/**
|
|
* Read 16bit register from device.
|
|
*/
|
|
static inline int raw_read16(const int port, const uint16_t i2c_addr_flags,
|
|
const int reg, int *data_ptr)
|
|
{
|
|
return i2c_read16(port, i2c_addr_flags, 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->i2c_spi_addr_flags,
|
|
SI114X_REG_PARAM_WR, *value);
|
|
if (ret != EC_SUCCESS)
|
|
goto error;
|
|
}
|
|
|
|
ret = raw_write8(s->port, s->i2c_spi_addr_flags,
|
|
SI114X_REG_COMMAND, op | (param & 0x1F));
|
|
if (ret != EC_SUCCESS)
|
|
goto error;
|
|
|
|
ret = raw_read8(s->port, s->i2c_spi_addr_flags,
|
|
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);
|
|
|
|
/* Read ALX result */
|
|
for (i = 0; i < nb; i++) {
|
|
ret = raw_read16(s->port, s->i2c_spi_addr_flags,
|
|
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;
|
|
|
|
if (IS_ENABLED(CONFIG_ACCEL_FIFO)) {
|
|
struct ec_response_motion_sensor_data vector;
|
|
|
|
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_stage_data(&vector, s, nb,
|
|
__hw_clock_source_read());
|
|
motion_sense_fifo_commit_data();
|
|
/*
|
|
* TODO: get time at a more accurate spot.
|
|
* Like in si114x_interrupt
|
|
*/
|
|
}
|
|
/* Otherwise, we need to copy raw_xyz into xyz with mutex */
|
|
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->i2c_spi_addr_flags,
|
|
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->i2c_spi_addr_flags,
|
|
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->i2c_spi_addr_flags,
|
|
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->i2c_spi_addr_flags,
|
|
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, intv3_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->i2c_spi_addr_flags,
|
|
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 0 /* This code is incorrect https://crbug.com/956569 */
|
|
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);
|
|
}
|
|
}
|
|
#endif
|
|
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, ®);
|
|
}
|
|
|
|
#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->i2c_spi_addr_flags,
|
|
SI114X_REG_COMMAND, SI114X_CMD_RESET);
|
|
if (ret != EC_SUCCESS)
|
|
return ret;
|
|
msleep(20);
|
|
|
|
/* hardware key, magic value */
|
|
ret = raw_write8(s->port, s->i2c_spi_addr_flags,
|
|
SI114X_REG_HW_KEY, 0x17);
|
|
if (ret != EC_SUCCESS)
|
|
return ret;
|
|
msleep(20);
|
|
|
|
/* interrupt configuration, interrupt output enable */
|
|
ret = raw_write8(s->port, s->i2c_spi_addr_flags,
|
|
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->i2c_spi_addr_flags,
|
|
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->i2c_spi_addr_flags,
|
|
SI114X_REG_MEAS_RATE, 0);
|
|
if (ret != EC_SUCCESS)
|
|
return ret;
|
|
|
|
/* measure ALS every time device wakes up */
|
|
ret = raw_write8(s->port, s->i2c_spi_addr_flags,
|
|
SI114X_REG_ALS_RATE, 0);
|
|
if (ret != EC_SUCCESS)
|
|
return ret;
|
|
|
|
/* measure proximity every time device wakes up */
|
|
ret = raw_write8(s->port, s->i2c_spi_addr_flags,
|
|
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->i2c_spi_addr_flags,
|
|
SI114X_REG_PS_LED3, 0x0f);
|
|
if (ret != EC_SUCCESS)
|
|
return ret;
|
|
ret = raw_write8(s->port, s->i2c_spi_addr_flags,
|
|
SI114X_REG_PS_LED21, 0xff);
|
|
break;
|
|
case 2:
|
|
ret = raw_write8(s->port, s->i2c_spi_addr_flags,
|
|
SI114X_REG_PS_LED21, 0xff);
|
|
break;
|
|
case 1:
|
|
ret = raw_write8(s->port, s->i2c_spi_addr_flags,
|
|
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,
|
|
#ifdef CONFIG_ACCEL_INTERRUPTS
|
|
.irq_handler = irq_handler,
|
|
#endif
|
|
};
|