summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNick Vaccaro <nvaccaro@google.com>2019-03-27 13:58:07 -0700
committerchrome-bot <chrome-bot@chromium.org>2019-05-06 19:14:50 -0700
commit106c145b07042ba39860f0ef9b1f379f896f5b5f (patch)
tree26d2fe31c630368a2ee25cfb405141615da578b5
parentb817b8dec0924050d746dc4257971ee522886b86 (diff)
downloadchrome-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.c312
-rw-r--r--driver/als_tcs3400.h96
-rw-r--r--driver/build.mk1
-rw-r--r--include/accelgyro.h13
-rw-r--r--include/config.h4
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