diff options
author | Nick Vaccaro <nvaccaro@google.com> | 2019-03-27 13:58:07 -0700 |
---|---|---|
committer | chrome-bot <chrome-bot@chromium.org> | 2019-05-06 19:14:50 -0700 |
commit | 106c145b07042ba39860f0ef9b1f379f896f5b5f (patch) | |
tree | 26d2fe31c630368a2ee25cfb405141615da578b5 | |
parent | b817b8dec0924050d746dc4257971ee522886b86 (diff) | |
download | chrome-ec-106c145b07042ba39860f0ef9b1f379f896f5b5f.tar.gz |
driver: add tcs3400 ALS sensor chip driver
Implements a MOTIONSENSE_TYPE_LIGHT sensor using
the clear channel of the tcs3400.
BUG=b:124512628
BRANCH=master
TEST=cherry-pick CLs to enable tcs3400 for flapjack and to add
alslog, build and flash to flapjack; boot flapjack, from ec console,
execute 'sysjump rw', then execute "alslog" to enable logging of als
data. Verify als data is generated and logged to ec console.
Change-Id: I918cbf5513fb5eba20a27705c47545d3c0b3ca91
Signed-off-by: Nick Vaccaro <nvaccaro@google.com>
Reviewed-on: https://chromium-review.googlesource.com/1541955
Commit-Ready: ChromeOS CL Exonerator Bot <chromiumos-cl-exonerator@appspot.gserviceaccount.com>
Tested-by: Gwendal Grignou <gwendal@chromium.org>
Reviewed-by: Gwendal Grignou <gwendal@chromium.org>
-rw-r--r-- | driver/als_tcs3400.c | 312 | ||||
-rw-r--r-- | driver/als_tcs3400.h | 96 | ||||
-rw-r--r-- | driver/build.mk | 1 | ||||
-rw-r--r-- | include/accelgyro.h | 13 | ||||
-rw-r--r-- | include/config.h | 4 |
5 files changed, 426 insertions, 0 deletions
diff --git a/driver/als_tcs3400.c b/driver/als_tcs3400.c new file mode 100644 index 0000000000..f63b7156ae --- /dev/null +++ b/driver/als_tcs3400.c @@ -0,0 +1,312 @@ +/* Copyright 2019 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. + * + * AMS TCS3400 light sensor driver + */ + +#include "accelgyro.h" +#include "common.h" +#include "console.h" +#include "driver/als_tcs3400.h" +#include "hwtimer.h" +#include "i2c.h" +#include "math_util.h" +#include "task.h" + +#define CPRINTS(fmt, args...) cprints(CC_ACCEL, "%s "fmt, __func__, ## args) + +#ifdef CONFIG_ACCEL_FIFO +static volatile uint32_t last_interrupt_timestamp; +#endif + +static inline int tcs3400_i2c_read8(const struct motion_sensor_t *s, + int reg, int *data) +{ + return i2c_read8(s->port, s->addr, reg, data); +} + +static inline int tcs3400_i2c_write8(const struct motion_sensor_t *s, + int reg, int data) +{ + return i2c_write8(s->port, s->addr, reg, data); +} + +static int tcs3400_read(const struct motion_sensor_t *s, intv3_t v) +{ + int ret; + int data; + + /* Enable the ADC to start cycle */ + ret = tcs3400_i2c_read8(s, TCS_I2C_ENABLE, &data); + if (ret) + return ret; + + /* mask value to assure writing 0 to reserved bits */ + data = (data & ~TCS_I2C_ENABLE_MASK) | TCS3400_MODE_COLLECTING; + ret = tcs3400_i2c_write8(s, TCS_I2C_ENABLE, data); + + /* + * If write succeeded, we've started the read process, but can't + * complete it yet until data is ready, so pass back EC_RES_IN_PROGRESS + * to inform upper level that read data process is under way and data + * will be delivered when available. + */ + if (ret == EC_SUCCESS) + ret = EC_RES_IN_PROGRESS; + + return ret; +} + +static int tcs3400_post_events(struct motion_sensor_t *s, uint32_t last_ts) +{ + struct tcs3400_drv_data_t *drv_data = TCS3400_DRV_DATA(s); + struct ec_response_motion_sensor_data vector; + uint8_t light_data[TCS_RGBC_DATA_SIZE]; + int *v = s->raw_xyz; + int retries = 20; /* 400 ms max */ + int data = 0; + int ret; + + /* Make sure data is valid */ + do { + ret = tcs3400_i2c_read8(s, TCS_I2C_STATUS, &data); + if (ret) + return ret; + if (!(data & TCS_I2C_STATUS_RGBC_VALID)) { + retries--; + if (retries == 0) + return EC_ERROR_UNCHANGED; + CPRINTS("RGBC not valid (0x%x)", data); + msleep(20); + } + } while (!(data & TCS_I2C_STATUS_RGBC_VALID)); + + /* Read the light registers */ + ret = i2c_read_block(s->port, s->addr, TCS_DATA_START_LOCATION, + light_data, sizeof(light_data)); + if (ret) + return ret; + + /* Transfer Clear data into sensor struct and into fifo */ + data = (light_data[1] << 8) | light_data[0]; + data += drv_data->als_cal.offset; + data = data * drv_data->als_cal.scale + + data * drv_data->als_cal.uscale / 10000; + + if (data < 0) { + CPRINTS("Negative clear val 0x%x set to 0", data); + data = 0; + } + + if (data != drv_data->last_value) { + drv_data->last_value = data; + vector.flags = 0; +#ifdef CONFIG_ACCEL_SPOOF_MODE + if (s->in_spoof_mode) { + for (i = 0; i < 3; i++) + vector.data[i] = v[i] = s->spoof_xyz[i]; + goto skip_clear_vector_load; + } +#endif /* defined(CONFIG_ACCEL_SPOOF_MODE) */ + + vector.data[X] = v[X] = data; + vector.data[Y] = v[Y] = 0; + vector.data[Z] = v[Z] = 0; + +#ifdef CONFIG_ACCEL_SPOOF_MODE +skip_clear_vector_load: +#endif + +#ifdef CONFIG_ACCEL_FIFO + vector.sensor_num = s - motion_sensors; + motion_sense_fifo_add_data(&vector, s, 3, last_ts); +#endif + } + + return EC_SUCCESS; +} + +void tcs3400_interrupt(enum gpio_signal signal) +{ +#ifdef CONFIG_ACCEL_FIFO + last_interrupt_timestamp = __hw_clock_source_read(); +#endif + task_set_event(TASK_ID_MOTIONSENSE, + CONFIG_ALS_TCS3400_INT_EVENT, 0); +} + +/** + * tcs3400_irq_handler - bottom half of the interrupt stack. + * Ran from the motion_sense task, finds the events that raised the interrupt, + * and posts those events via motion_sense_fifo_add_data().. + */ +static int tcs3400_irq_handler(struct motion_sensor_t *s, uint32_t *event) +{ + int status = 0; + int ret = EC_SUCCESS; + + if (!(*event & CONFIG_ALS_TCS3400_INT_EVENT)) + return EC_ERROR_NOT_HANDLED; + + ret = tcs3400_i2c_read8(s, TCS_I2C_STATUS, &status); + if (ret) + return ret; + + /* Disable future interrupts */ + ret = tcs3400_i2c_read8(s, TCS_I2C_ENABLE, &status); + if (ret) + return ret; + + ret = tcs3400_i2c_write8(s, TCS_I2C_ENABLE, + (status & ~TCS_I2C_ENABLE_INT_ENABLE)); + + if ((status & TCS_I2C_STATUS_RGBC_VALID) || + ((status & TCS_I2C_STATUS_ALS_IRQ) && + (status & TCS_I2C_STATUS_ALS_VALID))) { + + ret = tcs3400_post_events(s, last_interrupt_timestamp); + if (ret) + return ret; + } + + ret = tcs3400_i2c_write8(s, TCS_I2C_AICLEAR, 0); + + return ret; +} + +static int tcs3400_get_range(const struct motion_sensor_t *s) +{ + return (TCS3400_DRV_DATA(s)->als_cal.scale << 16) | + (TCS3400_DRV_DATA(s)->als_cal.uscale); +} + +static int tcs3400_set_range(const struct motion_sensor_t *s, + int range, + int rnd) +{ + TCS3400_DRV_DATA(s)->als_cal.scale = range >> 16; + TCS3400_DRV_DATA(s)->als_cal.uscale = range & 0xffff; + return EC_SUCCESS; +} + +static int tcs3400_get_offset(const struct motion_sensor_t *s, + int16_t *offset, + int16_t *temp) +{ + offset[X] = TCS3400_DRV_DATA(s)->als_cal.offset; + offset[Y] = 0; + offset[Z] = 0; + *temp = EC_MOTION_SENSE_INVALID_CALIB_TEMP; + return EC_SUCCESS; +} + +static int tcs3400_set_offset(const struct motion_sensor_t *s, + const int16_t *offset, + int16_t temp) +{ + TCS3400_DRV_DATA(s)->als_cal.offset = offset[X]; + return EC_SUCCESS; +} + +static int tcs3400_get_data_rate(const struct motion_sensor_t *s) +{ + return TCS3400_DRV_DATA(s)->rate; +} + +static int tcs3400_set_data_rate(const struct motion_sensor_t *s, + int rate, + int rnd) +{ + enum tcs3400_mode mode; + int data; + int ret; + + if (rate == 0) { + /* Suspend driver */ + mode = TCS3400_MODE_SUSPEND; + } else { + /* + * We set the sensor for continuous mode, + * integrating over 800ms. + * Do not allow range higher than 1Hz. + */ + if (rate > 1000) + rate = 1000; + mode = TCS3400_MODE_COLLECTING; + } + TCS3400_DRV_DATA(s)->rate = rate; + + ret = tcs3400_i2c_read8(s, TCS_I2C_ENABLE, &data); + if (ret) + return ret; + + data = (data & TCS_I2C_ENABLE_MASK) | mode; + ret = tcs3400_i2c_write8(s, TCS_I2C_ENABLE, data); + + return ret; +} + +/** + * Initialise TCS3400 light sensor. + */ +static int tcs3400_init(const struct motion_sensor_t *s) +{ + /* + * These are default power-on register values with two exceptions: + * Set ATIME = 0x4 (700.88ms) + * Set AGAIN = 16 (0x10) (AGAIN is in CONTROL register) + */ + const struct reg_data { + uint8_t reg; + uint8_t data; + } defaults[] = { { TCS_I2C_ENABLE, 0 }, + { TCS_I2C_ATIME, 0x4 }, + { TCS_I2C_WTIME, 0xFF }, + { TCS_I2C_AILTL, 0 }, + { TCS_I2C_AILTH, 0 }, + { TCS_I2C_AIHTL, 0 }, + { TCS_I2C_AIHTH, 0 }, + { TCS_I2C_PERS, 0 }, + { TCS_I2C_CONFIG, 0x40 }, + { TCS_I2C_CONTROL, 0x10 }, + { TCS_I2C_AUX, 0 }, + { TCS_I2C_IR, 0 }, + { TCS_I2C_CICLEAR, 0 }, + { TCS_I2C_AICLEAR, 0 } }; + int data = 0; + int ret; + + ret = tcs3400_i2c_read8(s, TCS_I2C_ID, &data); + if (ret) { + CPRINTS("failed reading ID reg 0x%x, ret=%d", TCS_I2C_ID, ret); + return ret; + } + if ((data != TCS340015_DEVICE_ID) && (data != TCS340037_DEVICE_ID)) { + CPRINTS("no ID match, data = 0x%x", data); + return EC_ERROR_ACCESS_DENIED; + } + + /* reset chip to default power-on settings, changes ATIME and CONTROL */ + for (int x = 0; x < ARRAY_SIZE(defaults); x++) { + ret = tcs3400_i2c_write8(s, defaults[x].reg, defaults[x].data); + if (ret) + return ret; + } + + return sensor_init_done(s); +} + +const struct accelgyro_drv tcs3400_drv = { + .init = tcs3400_init, + .read = tcs3400_read, + .set_range = tcs3400_set_range, + .get_range = tcs3400_get_range, + .set_offset = tcs3400_set_offset, + .get_offset = tcs3400_get_offset, + .set_data_rate = tcs3400_set_data_rate, + .get_data_rate = tcs3400_get_data_rate, +#ifdef CONFIG_ACCEL_INTERRUPTS + .irq_handler = tcs3400_irq_handler, +#endif +}; diff --git a/driver/als_tcs3400.h b/driver/als_tcs3400.h new file mode 100644 index 0000000000..dff3d5cdb7 --- /dev/null +++ b/driver/als_tcs3400.h @@ -0,0 +1,96 @@ +/* Copyright 2019 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. + * + * AMS TCS3400 light sensor driver + */ + +#ifndef __CROS_EC_ALS_TCS3400_H +#define __CROS_EC_ALS_TCS3400_H + +/* I2C Interface */ +#define TCS3400_I2C_ADDR 0x72 + +/* ID for TCS34001 and TCS34005 */ +#define TCS340015_DEVICE_ID 0x90 + +/* ID for TCS34003 and TCS34007 */ +#define TCS340037_DEVICE_ID 0x93 + +/* Register Map */ +#define TCS_I2C_ENABLE 0x80 /* R/W Enables states and interrupts */ +#define TCS_I2C_ATIME 0x81 /* R/W RGBC integration time */ +#define TCS_I2C_WTIME 0x83 /* R/W Wait time */ +#define TCS_I2C_AILTL 0x84 /* R/W Clear irq low threshold low byte */ +#define TCS_I2C_AILTH 0x85 /* R/W Clear irq low threshold high byte */ +#define TCS_I2C_AIHTL 0x86 /* R/W Clear irq high threshold low byte */ +#define TCS_I2C_AIHTH 0x87 /* R/W Clear irq high threshold high byte */ +#define TCS_I2C_PERS 0x8C /* R/W Interrupt persistence filter */ +#define TCS_I2C_CONFIG 0x8D /* R/W Configuration */ +#define TCS_I2C_CONTROL 0x8F /* R/W Gain control register */ +#define TCS_I2C_AUX 0x90 /* R/W Auxiliary control register */ +#define TCS_I2C_REVID 0x91 /* R Revision ID */ +#define TCS_I2C_ID 0x92 /* R Device ID */ +#define TCS_I2C_STATUS 0x93 /* R Device status */ +#define TCS_I2C_CDATAL 0x94 /* R Clear / IR channel low data register */ +#define TCS_I2C_CDATAH 0x95 /* R Clear / IR channel high data register */ +#define TCS_I2C_RDATAL 0x96 /* R Red ADC low data register */ +#define TCS_I2C_RDATAH 0x97 /* R Red ADC high data register */ +#define TCS_I2C_GDATAL 0x98 /* R Green ADC low data register */ +#define TCS_I2C_GDATAH 0x99 /* R Green ADC high data register */ +#define TCS_I2C_BDATAL 0x9A /* R Blue ADC low data register */ +#define TCS_I2C_BDATAH 0x9B /* R Blue ADC high data register */ +#define TCS_I2C_IR 0xC0 /* R/W Access IR Channel */ +#define TCS_I2C_IFORCE 0xE4 /* W Force Interrupt */ +#define TCS_I2C_CICLEAR 0xE6 /* W Clear channel interrupt clear */ +#define TCS_I2C_AICLEAR 0xE7 /* W Clear all interrupts */ + +#define TCS_I2C_ENABLE_POWER_ON BIT(0) +#define TCS_I2C_ENABLE_ADC_ENABLE BIT(1) +#define TCS_I2C_ENABLE_WAIT_ENABLE BIT(3) +#define TCS_I2C_ENABLE_INT_ENABLE BIT(4) +#define TCS_I2C_ENABLE_SLEEP_AFTER_INT BIT(6) +#define TCS_I2C_ENABLE_MASK (TCS_I2C_ENABLE_POWER_ON | \ + TCS_I2C_ENABLE_ADC_ENABLE | \ + TCS_I2C_ENABLE_WAIT_ENABLE | \ + TCS_I2C_ENABLE_INT_ENABLE | \ + TCS_I2C_ENABLE_SLEEP_AFTER_INT) + +enum tcs3400_mode { + TCS3400_MODE_SUSPEND = 0, + TCS3400_MODE_COLLECTING = (TCS_I2C_ENABLE_POWER_ON | + TCS_I2C_ENABLE_ADC_ENABLE | + TCS_I2C_ENABLE_INT_ENABLE), +}; + +#define TCS_I2C_STATUS_RGBC_VALID BIT(0) +#define TCS_I2C_STATUS_ALS_IRQ BIT(4) +#define TCS_I2C_STATUS_ALS_VALID BIT(7) + +#define TCS_I2C_AUX_ASL_INT_ENABLE BIT(5) + +/* Light data resides at 0x94 thru 0x98 */ +#define TCS_DATA_START_LOCATION TCS_I2C_CDATAL +#define TCS_CLEAR_DATA_SIZE 2 +#define TCS_RGBC_DATA_SIZE 8 + +/* Min and Max sampling frequency in mHz */ +#define TCS3400_LIGHT_MIN_FREQ 149 +#define TCS3400_LIGHT_MAX_FREQ 10000 +#if (CONFIG_EC_MAX_SENSOR_FREQ_MILLIHZ <= TCS3400_LIGHT_MAX_FREQ) +#error "EC too slow for light sensor" +#endif + +#define TCS3400_DRV_DATA(_s) ((struct tcs3400_drv_data_t *)(_s)->drv_data) + +/* Private tcs3400 driver data */ +struct tcs3400_drv_data_t { + int rate; /* holds current sensor rate */ + int last_value; /* holds last als clear channel value */ + struct als_calibration_t als_cal; /* calibration data */ +}; + +extern const struct accelgyro_drv tcs3400_drv; + +void tcs3400_interrupt(enum gpio_signal signal); +#endif /* __CROS_EC_ALS_TCS3400_H */ diff --git a/driver/build.mk b/driver/build.mk index 5945c281eb..956035bc97 100644 --- a/driver/build.mk +++ b/driver/build.mk @@ -34,6 +34,7 @@ driver-$(CONFIG_ALS_ISL29035)+=als_isl29035.o driver-$(CONFIG_ALS_OPT3001)+=als_opt3001.o driver-$(CONFIG_ALS_SI114X)+=als_si114x.o driver-$(CONFIG_ALS_BH1730)+=als_bh1730.o +driver-$(CONFIG_ALS_TCS3400)+=als_tcs3400.o # Barometers driver-$(CONFIG_BARO_BMP280)+=baro_bmp280.o diff --git a/include/accelgyro.h b/include/accelgyro.h index 2d415621d1..0d00116ed6 100644 --- a/include/accelgyro.h +++ b/include/accelgyro.h @@ -152,6 +152,19 @@ struct accelgyro_saved_data_t { uint16_t scale[3]; }; +/* Calibration data */ +struct als_calibration_t { + /* + * Scale, uscale, and offset are used to correct the raw 16 bit ALS + * data and then to convert it to 32 bit using the following equations: + * raw_value += offset; + * adjusted_value = raw_value * scale + raw_value * uscale / 10000; + */ + uint16_t scale; + uint16_t uscale; + int16_t offset; +}; + #define SENSOR_APPLY_SCALE(_input, _scale) \ (((_input) * (_scale)) / MOTION_SENSE_DEFAULT_SCALE) diff --git a/include/config.h b/include/config.h index 86f6447157..116b8d962c 100644 --- a/include/config.h +++ b/include/config.h @@ -257,6 +257,9 @@ /* Check if the device revision is supported */ #undef CONFIG_ALS_SI114X_CHECK_REVISION +/* Define to include the clear channel driver for the tcs3400 light sensor */ +#undef CONFIG_ALS_TCS3400 + /* * Define the event to raise when a sensor interrupt triggers. * Must be within TASK_EVENT_MOTION_INTERRUPT_MASK. @@ -265,6 +268,7 @@ #undef CONFIG_ACCEL_LSM6DSM_INT_EVENT #undef CONFIG_ACCEL_LSM6DSO_INT_EVENT #undef CONFIG_ALS_SI114X_INT_EVENT +#undef CONFIG_ALS_TCS3400_INT_EVENT /* * Enable Si114x to operate in polling mode. This config is used in conjunction |