Browse Source

drivers: add OPT3001 ALS [FIRM-118]

Signed-off-by: Joshua Wise <joshua@joshuawise.com>
Joshua Wise 3 months ago
parent
commit
c22facdb93

+ 2 - 0
platform/platform_capabilities.py

@@ -28,6 +28,7 @@ JAVASCRIPT_BYTECODE_VERSION = 1
 master_capability_set = {
 master_capability_set = {
     'COMPOSITOR_USES_DMA',
     'COMPOSITOR_USES_DMA',
     'HAS_ACCESSORY_CONNECTOR',
     'HAS_ACCESSORY_CONNECTOR',
+    'HAS_ALS_OPT3001',
     'HAS_APPLE_MFI',
     'HAS_APPLE_MFI',
     'HAS_APP_GLANCES',
     'HAS_APP_GLANCES',
     'HAS_BUILTIN_HRM',
     'HAS_BUILTIN_HRM',
@@ -259,6 +260,7 @@ board_capability_dicts = [
         'boards': [ 'asterix' ],
         'boards': [ 'asterix' ],
         'capabilities':
         'capabilities':
         {
         {
+            'HAS_ALS_OPT3001',
             'HAS_APP_GLANCES',
             'HAS_APP_GLANCES',
             'HAS_CORE_NAVIGATION4',
             'HAS_CORE_NAVIGATION4',
             'HAS_HEALTH_TRACKING',
             'HAS_HEALTH_TRACKING',

+ 7 - 0
src/fw/board/boards/board_asterix.c

@@ -147,6 +147,13 @@ static const I2CSlavePort I2C_SLAVE_DRV2604 = {
 
 
 I2CSlavePort *const I2C_DRV2604 = &I2C_SLAVE_DRV2604;
 I2CSlavePort *const I2C_DRV2604 = &I2C_SLAVE_DRV2604;
 
 
+static const I2CSlavePort I2C_SLAVE_OPT3001 = {
+    .bus = &I2C_IIC2_BUS,
+    .address = 0x44 << 1,
+};
+
+I2CSlavePort *const I2C_OPT3001 = &I2C_SLAVE_OPT3001;
+
 /* PERIPHERAL ID 11 */
 /* PERIPHERAL ID 11 */
 
 
 /* sensor SPI bus */
 /* sensor SPI bus */

+ 4 - 3
src/fw/board/boards/board_asterix.h

@@ -9,10 +9,10 @@
 #define BOARD_RTC_INST NRF_RTC1
 #define BOARD_RTC_INST NRF_RTC1
 
 
 static const BoardConfig BOARD_CONFIG = {
 static const BoardConfig BOARD_CONFIG = {
-  .ambient_light_dark_threshold = 150,
-  .ambient_k_delta_threshold = 50,
+  .ambient_light_dark_threshold = 100,
+  .ambient_k_delta_threshold = 30,
   .photo_en = { },
   .photo_en = { },
-  .als_always_on = true,
+  .als_always_on = true, // XXX: some day, we will probably want to poll this less frequently, but the ALS on this device is slow, so we pay the 3.7uA for now
 
 
   // new sharp display requires 30/60Hz so we feed it directly from PMIC... XXX: some day
   // new sharp display requires 30/60Hz so we feed it directly from PMIC... XXX: some day
   .lcd_com = { 0 },
   .lcd_com = { 0 },
@@ -251,3 +251,4 @@ extern MicDevice * const MIC;
 
 
 extern I2CSlavePort * const I2C_NPM1300;
 extern I2CSlavePort * const I2C_NPM1300;
 extern I2CSlavePort * const I2C_DRV2604;
 extern I2CSlavePort * const I2C_DRV2604;
+extern I2CSlavePort * const I2C_OPT3001;

+ 145 - 0
src/fw/drivers/ambient_light_opt3001.c

@@ -0,0 +1,145 @@
+/*
+ * Copyright 2025 Core Devices
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "board/board.h"
+#include "console/prompt.h"
+#include "drivers/ambient_light.h"
+#include "drivers/i2c.h"
+#include "drivers/periph_config.h"
+#include "kernel/util/sleep.h"
+#include "mfg/mfg_info.h"
+#include "system/logging.h"
+#include "system/passert.h"
+
+#include <inttypes.h>
+
+static uint32_t s_sensor_light_dark_threshold;
+static bool s_initialized = false;
+
+#define OPT3001_RESULT 0x00
+#define OPT3001_RESULT_EXPONENT_SHIFT 12
+#define OPT3001_RESULT_MANTISSA_MASK  0x0FFF
+#define OPT3001_CONFIG 0x01
+#define OPT3001_CONFIG_RANGE_AUTO       0xC000
+#define OPT3001_CONFIG_CONVTIME_100MSEC 0x0000
+#define OPT3001_CONFIG_MODE_CONTINUOUS  0x0600
+#define OPT3001_CONFIG_MODE_SINGLESHOT  0x0200
+#define OPT3001_MFGID 0x7E
+#define OPT3001_MFGID_VAL 0x5449 /* "TI" */
+#define OPT3001_DEVID 0x7F
+#define OPT3001_DEVID_VAL 0x3001
+
+static bool prv_read_register(uint8_t register_address, uint16_t *result) {
+  uint8_t buf[2];
+  i2c_use(I2C_OPT3001);
+  bool rv = i2c_read_register_block(I2C_OPT3001, register_address, 2, buf);
+  *result = buf[0] << 8 | buf[1];
+  i2c_release(I2C_OPT3001);
+  return rv;
+}
+
+static bool prv_write_register(uint8_t register_address, uint16_t datum) {
+  i2c_use(I2C_OPT3001);
+  uint8_t block[3] = { register_address, datum >> 8, datum & 0xFF };
+  bool rv = i2c_write_block(I2C_OPT3001, 3, block);
+  i2c_release(I2C_OPT3001);
+  return rv;
+}
+
+static uint32_t prv_get_default_ambient_light_dark_threshold(void) {
+  PBL_ASSERTN(BOARD_CONFIG.ambient_light_dark_threshold != 0);
+  return BOARD_CONFIG.ambient_light_dark_threshold;
+}
+
+void ambient_light_init(void) {
+  s_sensor_light_dark_threshold = prv_get_default_ambient_light_dark_threshold();
+
+  uint16_t mf, id;
+  bool ok = prv_read_register(OPT3001_MFGID, &mf) && prv_read_register(OPT3001_DEVID, &id);
+  if (!ok) {
+    PBL_LOG(LOG_LEVEL_ERROR, "failed to read OPT3001 ID registers");
+    return;
+  }
+
+  if (mf != OPT3001_MFGID_VAL || id != OPT3001_DEVID_VAL) {
+    PBL_LOG(LOG_LEVEL_INFO, "OPT3001 read successfully, but had incorrect manuf %04x, id %04x", mf, id);
+    return;
+  }
+  
+  PBL_LOG(LOG_LEVEL_INFO, "found OPT3001 with manuf %04x, id %04x", mf, id);
+
+  if (BOARD_CONFIG.als_always_on) {
+    prv_write_register(OPT3001_CONFIG, OPT3001_CONFIG_RANGE_AUTO | OPT3001_CONFIG_CONVTIME_100MSEC | OPT3001_CONFIG_MODE_CONTINUOUS);
+  }
+
+  s_initialized = true;
+}
+
+uint32_t ambient_light_get_light_level(void) {
+  if (!s_initialized) {
+    return BOARD_CONFIG.ambient_light_dark_threshold;
+  }
+  
+  if (!BOARD_CONFIG.als_always_on) {
+    prv_write_register(OPT3001_CONFIG, OPT3001_CONFIG_RANGE_AUTO | OPT3001_CONFIG_CONVTIME_100MSEC | OPT3001_CONFIG_MODE_SINGLESHOT);
+  }
+
+  uint16_t result;
+  prv_read_register(OPT3001_RESULT, &result);
+  uint32_t exp = result >> OPT3001_RESULT_EXPONENT_SHIFT;
+  uint32_t mant = result & OPT3001_RESULT_MANTISSA_MASK;
+
+  uint32_t level = mant << exp;
+
+  return level;
+}
+
+void command_als_read(void) {
+  char buffer[16];
+  prompt_send_response_fmt(buffer, sizeof(buffer), "%"PRIu32"", ambient_light_get_light_level());
+}
+
+uint32_t ambient_light_get_dark_threshold(void) {
+  return s_sensor_light_dark_threshold;
+}
+
+void ambient_light_set_dark_threshold(uint32_t new_threshold) {
+  PBL_ASSERTN(new_threshold <= AMBIENT_LIGHT_LEVEL_MAX);
+  s_sensor_light_dark_threshold = new_threshold;
+}
+
+bool ambient_light_is_light(void) {
+  // if the sensor is not enabled, always return that it is dark
+  return s_initialized && ambient_light_get_light_level() > s_sensor_light_dark_threshold;
+}
+
+AmbientLightLevel ambient_light_level_to_enum(uint32_t light_level) {
+  if (!s_initialized) {
+    // if the sensor is not enabled, always return that it is very dark
+    return AmbientLightLevelUnknown;
+  }
+
+  const uint32_t k_delta_threshold = BOARD_CONFIG.ambient_k_delta_threshold;
+  if (light_level < (s_sensor_light_dark_threshold - k_delta_threshold)) {
+    return AmbientLightLevelVeryDark;
+  } else if (light_level < s_sensor_light_dark_threshold) {
+    return AmbientLightLevelDark;
+  } else if (light_level < (s_sensor_light_dark_threshold + k_delta_threshold)) {
+    return AmbientLightLevelLight;
+  } else {
+    return AmbientLightLevelVeryLight;
+  }
+}

+ 13 - 1
src/fw/drivers/wscript_build

@@ -222,7 +222,19 @@ if bld.is_snowy_compatible() or bld.is_silk() or bld.is_cutts() or bld.is_robert
         ],
         ],
     )
     )
 
 
-if mcu_family in ('STM32F2', 'STM32F4', 'STM32F7'):
+if bld.capability('HAS_ALS_OPT3001'):
+    bld.objects(
+        name='driver_ambient_light',
+        source=[
+            'ambient_light_opt3001.c',
+        ],
+        use=[
+            'driver_i2c',
+            'fw_includes',
+            'root_includes',
+        ],
+    )
+elif mcu_family in ('STM32F2', 'STM32F4', 'STM32F7'):
     bld.objects(
     bld.objects(
         name='driver_ambient_light',
         name='driver_ambient_light',
         source=[
         source=[