mirror of
https://github.com/google/pebble.git
synced 2025-11-22 23:40:54 -05:00
Import of the watch repository from Pebble
This commit is contained in:
311
platform/silk/boot/src/drivers/pmic/as3701b.c
Normal file
311
platform/silk/boot/src/drivers/pmic/as3701b.c
Normal file
@@ -0,0 +1,311 @@
|
||||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* 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 "drivers/pmic.h"
|
||||
|
||||
#include "board/board.h"
|
||||
#include "drivers/gpio.h"
|
||||
#include "drivers/i2c.h"
|
||||
#include "system/passert.h"
|
||||
|
||||
#include "stm32f4xx_rcc.h"
|
||||
#include "stm32f4xx_gpio.h"
|
||||
#include "stm32f4xx_adc.h"
|
||||
|
||||
typedef enum {
|
||||
PmicRegisters_SD1_VOLTAGE = 0x01,
|
||||
PmicRegisters_LDO1_VOLTAGE = 0x02,
|
||||
PmicRegisters_LDO2_VOLTAGE = 0x03,
|
||||
|
||||
PmicRegisters_GPIO1_CNTL = 0x09,
|
||||
PmicRegisters_GPIO2_CNTL = 0x0a,
|
||||
PmicRegisters_GPIO3_CNTL = 0x0b,
|
||||
PmicRegisters_GPIO4_CNTL = 0x0c,
|
||||
PmicRegisters_GPIO5_CNTL = 0x0d,
|
||||
PmicRegisters_GPIO_SIG_OUT = 0x20,
|
||||
PmicRegisters_GPIO_SIG_IN = 0x21,
|
||||
|
||||
PmicRegisters_REG1_VOLTAGE = 0x22,
|
||||
PmicRegisters_REG2_VOLTAGE = 0x23,
|
||||
PmicRegisters_REG_CNTL = 0x24,
|
||||
|
||||
PmicRegisters_GPIO_CNTL1 = 0x25,
|
||||
PmicRegisters_GPIO_CNTL2 = 0x26,
|
||||
PmicRegisters_SD_CNTL1 = 0x30,
|
||||
|
||||
PmicRegisters_BATT_VOLTAGE_MON = 0x32,
|
||||
PmicRegisters_STARTUP_CNTL = 0x33,
|
||||
PmicRegisters_REFERENCE_CNTL = 0x35,
|
||||
PmicRegisters_RESET_CNTL = 0x36,
|
||||
PmicRegisters_OVERTEMP_CNTL = 0x37,
|
||||
PmicRegisters_REG_STANDBY_MOD1 = 0x39,
|
||||
|
||||
PmicRegisters_PWM_CNTL_L = 0x41,
|
||||
PmicRegisters_PWM_CNTL_H = 0x42,
|
||||
|
||||
PmicRegisters_CURR1_VAL = 0x43,
|
||||
PmicRegisters_CURR2_VAL = 0x44,
|
||||
|
||||
PmicRegisters_REG_STATUS = 0x73,
|
||||
PmicRegisters_INT_MASK_1 = 0x74,
|
||||
PmicRegisters_INT_MASK_2 = 0x75,
|
||||
PmicRegisters_INT_STATUS_1 = 0x77,
|
||||
PmicRegisters_INT_STATUS_2 = 0x78,
|
||||
PmicRegisters_CHARGE_CNTL = 0x80,
|
||||
PmicRegisters_CHARGE_VOLTAGE_CNTL = 0x81,
|
||||
PmicRegisters_CHARGE_CURRENT_CNTL = 0x82,
|
||||
PmicRegisters_CHARGE_CONFIG_1 = 0x83,
|
||||
PmicRegisters_CHARGE_CONFIG_2 = 0x84,
|
||||
PmicRegisters_CHARGE_SUPERVISION = 0x85,
|
||||
PmicRegisters_CHARGE_STATUS_1 = 0x86,
|
||||
PmicRegisters_CHARGE_STATUS_2 = 0x87,
|
||||
|
||||
PmicRegisters_LOCK_REG = 0x8e,
|
||||
|
||||
PmicRegisters_CHIP_ID = 0x90,
|
||||
PmicRegisters_CHIP_REV = 0x91,
|
||||
|
||||
PmicRegisters_FUSE_5 = 0xa5,
|
||||
PmicRegisters_FUSE_6 = 0xa6,
|
||||
PmicRegisters_FUSE_7 = 0xa7,
|
||||
PmicRegisters_FUSE_8 = 0xa8,
|
||||
PmicRegisters_FUSE_9 = 0xa9,
|
||||
PmicRegisters_FUSE_10 = 0xaa,
|
||||
PmicRegisters_FUSE_11 = 0xab,
|
||||
PmicRegisters_FUSE_12 = 0xac,
|
||||
PmicRegisters_FUSE_13 = 0xad,
|
||||
PmicRegisters_FUSE_14 = 0xae,
|
||||
PmicRegisters_FUSE_15 = 0xaf,
|
||||
} PmicRegisters;
|
||||
|
||||
// These are values for the reset_reason field of the ResetControl register.
|
||||
// None of these values should ever be changed, as conversions are done on
|
||||
// readings dont directly out of the ResetControl register.
|
||||
// See Figure 79 of the AS3701B datasheet for more information.
|
||||
typedef enum {
|
||||
PmicResetReason_PowerUpFromScratch = 0x00, //!< Battery or charger insertion from scratch
|
||||
PmicResetReason_ResVoltFall = 0x01, //!< Battery voltage drop below 2.75V
|
||||
|
||||
PmicResetReason_ForcedReset = 0x02, //!< sw force_reset
|
||||
|
||||
PmicResetReason_OnPulledHigh = 0x03, //!< Force sw power_off, ON pulled high
|
||||
PmicResetReason_Charger = 0x04, //!< Forced sw power_off, Charger detected.
|
||||
|
||||
PmicResetReason_XRES = 0x05, //!< External trigger through XRES
|
||||
PmicResetReason_OverTemperature = 0x06, //!< Reset caused by overtemperature
|
||||
|
||||
PmicResetReason_OnKeyHold = 0x08, //!< Reset for holding down on key
|
||||
|
||||
PmicResetReason_StandbyInterrupt = 0x0b, //!< Reset for interrupt in standby
|
||||
PmicResetReason_StandbyOnPulledHigh = 0x0c, //!< Reset for ON pulled high in standby
|
||||
|
||||
PmicResetReason_Unknown,
|
||||
} PmicResetReason;
|
||||
|
||||
static void prv_start_120hz_clock(void);
|
||||
|
||||
static bool prv_init_gpio(void) {
|
||||
return true;
|
||||
}
|
||||
|
||||
//! Interrupt masks for InterruptStatus1 and InterruptMask1 registers
|
||||
enum PmicInt1 {
|
||||
PmicInt1_Trickle = (1 << 0), //!< Trickle charge
|
||||
PmicInt1_NoBat = (1 << 1), //!< Battery detached
|
||||
PmicInt1_Resume = (1 << 2), //!< Resuming charge on drop after full
|
||||
PmicInt1_EOC = (1 << 3), //!< End of charge
|
||||
PmicInt1_ChDet = (1 << 4), //!< Charger detected
|
||||
PmicInt1_OnKey = (1 << 5), //!< On Key held
|
||||
PmicInt1_OvTemp = (1 << 6), //!< Set when 110deg is exceeded
|
||||
PmicInt1_LowBat = (1 << 7), //!< Low Battery detected. Set when BSUP drops below ResVoltFall
|
||||
};
|
||||
|
||||
enum PmicRail {
|
||||
PmicRail_SD1, //!< 1.8V
|
||||
PmicRail_LDO1, //!< 3.0V
|
||||
PmicRail_LDO2, //!< 2.0V
|
||||
};
|
||||
|
||||
#define AS3701B_CHIP_ID 0x11
|
||||
#define AS3701B_WRITE_ADDR 0x80
|
||||
#define AS3701B_READ_ADDR 0x81
|
||||
|
||||
static bool prv_read_register(uint8_t register_address, uint8_t *result) {
|
||||
i2c_use(I2C_DEVICE_AS3701B);
|
||||
bool rv = i2c_read_register(I2C_DEVICE_AS3701B, AS3701B_READ_ADDR, register_address, result);
|
||||
i2c_release(I2C_DEVICE_AS3701B);
|
||||
return rv;
|
||||
}
|
||||
|
||||
static bool prv_write_register(uint8_t register_address, uint8_t value) {
|
||||
i2c_use(I2C_DEVICE_AS3701B);
|
||||
bool rv = i2c_write_register(I2C_DEVICE_AS3701B, AS3701B_WRITE_ADDR, register_address, value);
|
||||
i2c_release(I2C_DEVICE_AS3701B);
|
||||
return rv;
|
||||
}
|
||||
|
||||
static bool prv_register_set_bit(uint8_t register_address, uint8_t bit) {
|
||||
uint8_t val;
|
||||
if (!prv_read_register(register_address, &val)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
val |= (1 << bit);
|
||||
return prv_write_register(register_address, val);
|
||||
}
|
||||
|
||||
static bool prv_register_clear_bit(uint8_t register_address, uint8_t bit) {
|
||||
uint8_t val;
|
||||
if (!prv_read_register(register_address, &val)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
val &= ~(1 << bit);
|
||||
return prv_write_register(register_address, val);
|
||||
}
|
||||
|
||||
// Read the interrupt status registers to clear pending bits.
|
||||
static void prv_clear_pending_interrupts(void) {
|
||||
uint8_t throwaway_read_result;
|
||||
prv_read_register(PmicRegisters_INT_STATUS_1, &throwaway_read_result);
|
||||
prv_read_register(PmicRegisters_INT_STATUS_2, &throwaway_read_result);
|
||||
}
|
||||
|
||||
// Set up 120Hz clock which is used for VCOM.
|
||||
// Slowest possible setting, with divisor of 16 and high/low duration of 256us.
|
||||
void prv_start_120hz_clock(void) {
|
||||
const uint8_t pwm_high_low_time_us = (256 - 1);
|
||||
prv_write_register(PmicRegisters_PWM_CNTL_H, pwm_high_low_time_us);
|
||||
prv_write_register(PmicRegisters_PWM_CNTL_L, pwm_high_low_time_us);
|
||||
|
||||
bool success = false;
|
||||
uint8_t ref_cntl;
|
||||
if (prv_read_register(PmicRegisters_REFERENCE_CNTL, &ref_cntl)) {
|
||||
ref_cntl |= 0x3; // Divisor of 16
|
||||
prv_write_register(PmicRegisters_REFERENCE_CNTL, ref_cntl);
|
||||
|
||||
// Enable PWM Output on GPIO2 (Fig. 64)
|
||||
// Bits 6-4: Mode, 0x1 = Output
|
||||
// Bits 0-3: iosf, 0xe = PWM
|
||||
uint8_t val = (1 << 4) | 0x0e;
|
||||
success = prv_write_register(PmicRegisters_GPIO2_CNTL, val);
|
||||
}
|
||||
PBL_ASSERT(success, "Failed to start PMIC 120Hz PWM");
|
||||
}
|
||||
|
||||
static bool prv_is_alive(void) {
|
||||
uint8_t chip_id;
|
||||
if (!prv_read_register(PmicRegisters_CHIP_ID, &chip_id)) {
|
||||
return false;
|
||||
}
|
||||
const bool found = (chip_id == AS3701B_CHIP_ID);
|
||||
if (found) {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Found the as3701b");
|
||||
} else {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Error: read as3701b whoami byte 0x%x, expecting 0x11", chip_id);
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
bool pmic_init(void) {
|
||||
prv_init_gpio();
|
||||
if (!prv_is_alive()) {
|
||||
return false;
|
||||
}
|
||||
prv_start_120hz_clock();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool pmic_is_usb_connected(void) {
|
||||
uint8_t status;
|
||||
if (!prv_read_register(PmicRegisters_CHARGE_STATUS_2, &status)) {
|
||||
return false;
|
||||
}
|
||||
// ChargerStatus2 (Fig. 98)
|
||||
// Bit 2: Charger detected
|
||||
return !!(status & (1 << 2));
|
||||
}
|
||||
|
||||
static PmicResetReason prv_reset_reason(void) {
|
||||
uint8_t val;
|
||||
if (!prv_read_register(PmicRegisters_RESET_CNTL, &val)) {
|
||||
return PmicResetReason_Unknown;
|
||||
}
|
||||
return (val & 0xf0) >> 4;
|
||||
}
|
||||
|
||||
// If the pmic indicates that we were reset due to a charger interrupt, but the
|
||||
// charger is currently disconnected, then we know we were woken by a disconnect event.
|
||||
bool pmic_boot_due_to_charger_disconnect(void) {
|
||||
if (prv_reset_reason() != PmicResetReason_StandbyInterrupt) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t int_status;
|
||||
if (!prv_read_register(PmicRegisters_INT_STATUS_1, &int_status)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ((int_status & PmicInt1_ChDet) && !pmic_is_usb_connected());
|
||||
}
|
||||
|
||||
// This is a hard power off, resulting in all rails being disabled.
|
||||
bool pmic_full_power_off(void) {
|
||||
// ResetControl (Fig. 79)
|
||||
// Bit 1: power_off - Start a reset cycle, and wait for ON or charger to complete the reset.
|
||||
if (prv_register_set_bit(PmicRegisters_RESET_CNTL, 1)) {
|
||||
while (1) {}
|
||||
__builtin_unreachable();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// On the as3701b, a power_off will cut power to all rails. We want to keep the
|
||||
// RTC alive, so rather than performing a sw_power_off, enter the pmic's standby
|
||||
// mode, powering down all but LDO2.
|
||||
bool pmic_power_off(void) {
|
||||
// Only enable interrupts that should be able to wake us out of standby
|
||||
// - Wake on charger detect
|
||||
const uint8_t int_mask = ~((uint8_t)PmicInt1_ChDet);
|
||||
prv_write_register(PmicRegisters_INT_MASK_1, int_mask);
|
||||
prv_write_register(PmicRegisters_INT_MASK_2, 0xff);
|
||||
|
||||
// Clear interrupt status so we're not woken immediately (read the regs)
|
||||
prv_clear_pending_interrupts();
|
||||
|
||||
// Set Reg_Standby_mod1 to specify which rails to turn off / keep on
|
||||
// - SD1, LDO1 off
|
||||
// - LDO2 on
|
||||
// - Disable regulator pulldowns
|
||||
prv_write_register(PmicRegisters_REG_STANDBY_MOD1, 0xa);
|
||||
|
||||
// Set standby_mode_on (bit 4) in ReferenceControl to 1 (See Fig. 78)
|
||||
if (prv_register_set_bit(PmicRegisters_REFERENCE_CNTL, 4)) {
|
||||
while (1) {}
|
||||
__builtin_unreachable();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void set_ldo3_power_state(bool enabled) {
|
||||
}
|
||||
|
||||
void set_4V5_power_state(bool enabled) {
|
||||
}
|
||||
|
||||
void set_6V6_power_state(bool enabled) {
|
||||
}
|
||||
Reference in New Issue
Block a user