1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-12-18 12:28:09 +00:00

feat(core): add haptic feedback

[no changelog]
This commit is contained in:
tychovrahe 2023-08-24 11:30:29 +02:00 committed by TychoVrahe
parent a71a608ea7
commit 72dc8f57e8
15 changed files with 374 additions and 3 deletions

View File

@ -21,7 +21,7 @@ FEATURE_FLAGS = {
"SYSTEM_VIEW": False, "SYSTEM_VIEW": False,
} }
FEATURES_WANTED = ["input", "sbu", "sd_card", "rgb_led", "dma2d", "consumption_mask", "usb" ,"optiga"] FEATURES_WANTED = ["input", "sbu", "sd_card", "rgb_led", "dma2d", "consumption_mask", "usb" ,"optiga", "haptic"]
if DISABLE_OPTIGA and PYOPT == '0': if DISABLE_OPTIGA and PYOPT == '0':
FEATURES_WANTED.remove("optiga") FEATURES_WANTED.remove("optiga")

View File

@ -19,7 +19,7 @@ if TREZOR_MODEL in ('DISC1', 'DISC2'):
action=build_prodtest) action=build_prodtest)
Return() Return()
FEATURES_WANTED = ["input", "sbu", "sd_card", "rdb_led", "usb", "consumption_mask", "optiga"] FEATURES_WANTED = ["input", "sbu", "sd_card", "rdb_led", "usb", "consumption_mask", "optiga", "haptic"]
CCFLAGS_MOD = '' CCFLAGS_MOD = ''
CPPPATH_MOD = [] CPPPATH_MOD = []

View File

@ -93,6 +93,9 @@
#ifdef USE_SECP256K1_ZKP #ifdef USE_SECP256K1_ZKP
#include "zkp_context.h" #include "zkp_context.h"
#endif #endif
#ifdef USE_HAPTIC
#include "haptic.h"
#endif
// from util.s // from util.s
extern void shutdown_privileged(void); extern void shutdown_privileged(void);
@ -183,6 +186,10 @@ int main(void) {
sdcard_init(); sdcard_init();
#endif #endif
#ifdef USE_HAPTIC
haptic_init();
#endif
#ifdef USE_OPTIGA #ifdef USE_OPTIGA
optiga_init(); optiga_init();
optiga_open_application(); optiga_open_application();

View File

@ -28,6 +28,7 @@ disp_i8080_8bit_dw = [] # write pixels directly to peripheral
disp_i8080_16bit_dw = [] # write pixels directly to peripheral disp_i8080_16bit_dw = [] # write pixels directly to peripheral
debug = ["ui_debug"] debug = ["ui_debug"]
sbu = [] sbu = []
haptic = []
sd_card = [] sd_card = []
rgb_led = [] rgb_led = []
backlight = [] backlight = []

View File

@ -404,7 +404,10 @@ fn generate_trezorhal_bindings() {
// touch // touch
.allowlist_function("touch_read") .allowlist_function("touch_read")
// button // button
.allowlist_function("button_read"); .allowlist_function("button_read")
// haptic
.allowlist_type("haptic_effect_t")
.allowlist_function("haptic_play");
// Write the bindings to a file in the OUR_DIR. // Write the bindings to a file in the OUR_DIR.
bindings bindings

View File

@ -0,0 +1,13 @@
use super::ffi;
#[derive(PartialEq, Debug, Eq, FromPrimitive, Clone, Copy)]
pub enum HapticEffect {
ButtonPress = ffi::haptic_effect_t_HAPTIC_BUTTON_PRESS as _,
HoldToConfirm = ffi::haptic_effect_t_HAPTIC_HOLD_TO_CONFIRM as _,
}
pub fn play(effect: HapticEffect) {
unsafe {
ffi::haptic_play(effect as _);
}
}

View File

@ -7,6 +7,8 @@ pub mod display;
#[cfg(feature = "dma2d")] #[cfg(feature = "dma2d")]
pub mod dma2d; pub mod dma2d;
mod ffi; mod ffi;
#[cfg(feature = "haptic")]
pub mod haptic;
pub mod io; pub mod io;
pub mod model; pub mod model;
pub mod random; pub mod random;

View File

@ -1,3 +1,5 @@
#[cfg(feature = "haptic")]
use crate::trezorhal::haptic::{play, HapticEffect};
use crate::{ use crate::{
time::Duration, time::Duration,
ui::{ ui::{
@ -256,6 +258,8 @@ where
_ => { _ => {
// Touch started in our area, transform to `Pressed` state. // Touch started in our area, transform to `Pressed` state.
if touch_area.contains(pos) { if touch_area.contains(pos) {
#[cfg(feature = "haptic")]
play(HapticEffect::ButtonPress);
self.set(ctx, State::Pressed); self.set(ctx, State::Pressed);
if let Some(duration) = self.long_press { if let Some(duration) = self.long_press {
self.long_timer = Some(ctx.request_timer(duration)); self.long_timer = Some(ctx.request_timer(duration));
@ -298,6 +302,8 @@ where
if self.long_timer == Some(token) { if self.long_timer == Some(token) {
self.long_timer = None; self.long_timer = None;
if matches!(self.state, State::Pressed) { if matches!(self.state, State::Pressed) {
#[cfg(feature = "haptic")]
play(HapticEffect::ButtonPress);
self.set(ctx, State::Initial); self.set(ctx, State::Initial);
return Some(ButtonMsg::LongPressed); return Some(ButtonMsg::LongPressed);
} }

View File

@ -1,3 +1,5 @@
#[cfg(feature = "haptic")]
use crate::trezorhal::haptic::{play, HapticEffect};
use crate::{ use crate::{
time::{Duration, Instant}, time::{Duration, Instant},
ui::{ ui::{
@ -165,6 +167,8 @@ impl Component for Loader {
} }
if self.is_completely_grown(now) { if self.is_completely_grown(now) {
#[cfg(feature = "haptic")]
play(HapticEffect::HoldToConfirm);
return Some(LoaderMsg::GrownCompletely); return Some(LoaderMsg::GrownCompletely);
} else if self.is_completely_shrunk(now) { } else if self.is_completely_shrunk(now) {
return Some(LoaderMsg::ShrunkCompletely); return Some(LoaderMsg::ShrunkCompletely);

View File

@ -7,6 +7,7 @@
#include "dma2d.h" #include "dma2d.h"
#include "flash.h" #include "flash.h"
#include "fonts/fonts.h" #include "fonts/fonts.h"
#include "haptic.h"
#include "model.h" #include "model.h"
#include "rgb_led.h" #include "rgb_led.h"
#include "secbool.h" #include "secbool.h"

View File

@ -73,6 +73,9 @@
#define TOUCH_ON_PORT GPIOB #define TOUCH_ON_PORT GPIOB
#define TOUCH_ON_PIN GPIO_PIN_0 #define TOUCH_ON_PIN GPIO_PIN_0
#define DRV2625_I2C_INSTANCE 1
#define HAPTIC_ACTUATOR "actuators/vg1040003d.h"
#define OPTIGA_I2C_INSTANCE 1 #define OPTIGA_I2C_INSTANCE 1
#define SD_DETECT_PORT GPIOC #define SD_DETECT_PORT GPIOC

View File

@ -0,0 +1,20 @@
#ifndef TREZORHAL_HAPTIC_H
#define TREZORHAL_HAPTIC_H
typedef enum {
HAPTIC_BUTTON_PRESS = 0,
HAPTIC_ALERT = 1,
HAPTIC_HOLD_TO_CONFIRM = 2,
} haptic_effect_t;
// Initialize haptic driver
void haptic_init(void);
// Calibrate haptic driver
void haptic_calibrate(void);
// Play haptic effect
void haptic_play(haptic_effect_t effect);
#endif

View File

@ -0,0 +1,6 @@
#define ACTUATOR_LRA
#define ACTUATOR_OPEN_LOOP
#define ACTUATOR_LRA_PERIOD (239)
#define ACTUATOR_OD_CLAMP (126)

View File

@ -0,0 +1,175 @@
#include "drv2625_lib.h"
#include "haptic.h"
#include STM32_HAL_H
#include "i2c.h"
#include TREZOR_BOARD
#include HAPTIC_ACTUATOR
#define DRV2625_I2C_ADDRESS (0x5A << 1)
#define DRV2625_REG_CHIPID 0x00
#define DRV2625_REG_STATUS 0x01
#define DRV2625_REG_MODE 0x07
#define DRV2625_REG_MODE_RTP 0
#define DRV2625_REG_MODE_WAVEFORM 0x01
#define DRV2625_REG_MODE_DIAG 0x02
#define DRV2625_REG_MODE_AUTOCAL 0x03
#define DRV2625_REG_MODE_TRGFUNC_PULSE 0x00
#define DRV2625_REG_MODE_TRGFUNC_ENABLE 0x04
#define DRV2625_REG_MODE_TRGFUNC_INTERRUPT 0x08
#define DRV2625_REG_LRAERM 0x08
#define DRV2625_REG_LRAERM_LRA 0x80
#define DRV2625_REG_LRAERM_OPENLOOP 0x40
#define DRV2625_REG_LRAERM_AUTO_BRK_OL 0x10
#define DRV2625_REG_LRAERM_AUTO_BRK_STBY 0x08
#define DRV2625_REG_LIBRARY 0x0D ///< Waveform library selection register
#define DRV2625_REG_LIBRARY_OPENLOOP 0x40
#define DRV2625_REG_LIBRARY_GAIN_100 0x00
#define DRV2625_REG_LIBRARY_GAIN_75 0x01
#define DRV2625_REG_LIBRARY_GAIN_50 0x02
#define DRV2625_REG_LIBRARY_GAIN_25 0x03
#define DRV2625_REG_RTP 0x0E ///< RTP input register
#define DRV2625_REG_WAVESEQ1 0x0F ///< Waveform sequence register 1
#define DRV2625_REG_WAVESEQ2 0x10 ///< Waveform sequence register 2
#define DRV2625_REG_WAVESEQ3 0x11 ///< Waveform sequence register 3
#define DRV2625_REG_WAVESEQ4 0x12 ///< Waveform sequence register 4
#define DRV2625_REG_WAVESEQ5 0x13 ///< Waveform sequence register 5
#define DRV2625_REG_WAVESEQ6 0x14 ///< Waveform sequence register 6
#define DRV2625_REG_WAVESEQ7 0x15 ///< Waveform sequence register 7
#define DRV2625_REG_WAVESEQ8 0x16 ///< Waveform sequence register 8
#define DRV2625_REG_GO 0x0C ///< Go register
#define DRV2625_REG_GO_GO 0x01
#define DRV2625_REG_OD_CLAMP 0x20
#define DRV2625_REG_LRA_WAVE_SHAPE 0x2C
#define DRV2625_REG_LRA_WAVE_SHAPE_SINE 0x01
#define DRV2625_REG_OL_LRA_PERIOD_LO 0x2F
#define DRV2625_REG_OL_LRA_PERIOD_HI 0x2E
#if defined ACTUATOR_CLOSED_LOOP
#define LIB_SEL 0x00
#define LOOP_SEL 0x00
#elif defined ACTUATOR_OPEN_LOOP
#define LIB_SEL DRV2625_REG_LIBRARY_OPENLOOP
#define LOOP_SEL DRV2625_REG_LRAERM_OPENLOOP
#else
#error "Must define either CLOSED_LOOP or OPEN_LOOP"
#endif
#if defined ACTUATOR_LRA
#define LRA_ERM_SEL DRV2625_REG_LRAERM_LRA
#elif defined ACTUATOR_ERM
#define LRA_ERM_SEL 0x00
#else
#error "Must define either LRA or ERM"
#endif
#define PRESS_EFFECT_AMPLITUDE 25
#define PRESS_EFFECT_DURATION 10
static void set_reg(uint8_t addr, uint8_t value) {
uint8_t data[] = {addr, value};
i2c_transmit(DRV2625_I2C_INSTANCE, DRV2625_I2C_ADDRESS, data, sizeof(data),
1);
}
void haptic_calibrate(void) {
set_reg(DRV2625_REG_MODE, DRV2625_REG_MODE_AUTOCAL);
HAL_Delay(1);
set_reg(DRV2625_REG_GO, DRV2625_REG_GO_GO);
HAL_Delay(3000);
}
void haptic_init(void) {
// select library
set_reg(DRV2625_REG_LIBRARY, LIB_SEL | DRV2625_REG_LIBRARY_GAIN_25);
set_reg(DRV2625_REG_LRAERM,
LRA_ERM_SEL | LOOP_SEL | DRV2625_REG_LRAERM_AUTO_BRK_OL);
set_reg(DRV2625_REG_OD_CLAMP, ACTUATOR_OD_CLAMP);
set_reg(DRV2625_REG_LRA_WAVE_SHAPE, DRV2625_REG_LRA_WAVE_SHAPE_SINE);
set_reg(DRV2625_REG_OL_LRA_PERIOD_LO, ACTUATOR_LRA_PERIOD & 0xFF);
set_reg(DRV2625_REG_OL_LRA_PERIOD_HI, ACTUATOR_LRA_PERIOD >> 8);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.Mode = GPIO_MODE_AF_PP;
GPIO_InitStructure.Pull = GPIO_PULLDOWN;
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStructure.Pin = GPIO_PIN_8;
GPIO_InitStructure.Alternate = GPIO_AF14_TIM16;
HAL_GPIO_Init(GPIOB, &GPIO_InitStructure);
TIM_HandleTypeDef TIM_Handle;
__HAL_RCC_TIM16_CLK_ENABLE();
TIM_Handle.State = HAL_TIM_STATE_RESET;
TIM_Handle.Instance = TIM16;
TIM_Handle.Init.Period = 0;
TIM_Handle.Init.Prescaler = SystemCoreClock / 10000;
TIM_Handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
TIM_Handle.Init.CounterMode = TIM_COUNTERMODE_UP;
TIM_Handle.Init.RepetitionCounter = 0;
HAL_TIM_PWM_Init(&TIM_Handle);
TIM_OC_InitTypeDef TIM_OC_InitStructure;
TIM_OC_InitStructure.OCMode = TIM_OCMODE_PWM2;
TIM_OC_InitStructure.OCPolarity = TIM_OCPOLARITY_HIGH;
TIM_OC_InitStructure.Pulse = 1;
TIM_OC_InitStructure.OCNPolarity = TIM_OCNPOLARITY_HIGH;
TIM_OC_InitStructure.OCFastMode = TIM_OCFAST_DISABLE;
HAL_TIM_PWM_ConfigChannel(&TIM_Handle, &TIM_OC_InitStructure, TIM_CHANNEL_1);
HAL_TIM_OC_Start(&TIM_Handle, TIM_CHANNEL_1);
TIM16->CR1 |= TIM_CR1_OPM;
TIM16->BDTR |= TIM_BDTR_MOE;
}
static void haptic_play_RTP(int8_t amplitude, uint16_t duration_ms) {
set_reg(DRV2625_REG_MODE,
DRV2625_REG_MODE_RTP | DRV2625_REG_MODE_TRGFUNC_ENABLE);
set_reg(DRV2625_REG_RTP, (uint8_t)amplitude);
if (duration_ms > 6500) {
duration_ms = 6500;
}
TIM16->CNT = 1;
TIM16->CCR1 = 1;
TIM16->ARR = duration_ms * 10;
TIM16->CR1 |= TIM_CR1_CEN;
}
static void haptic_play_lib(drv2625_lib_effect_t effect) {
set_reg(DRV2625_REG_MODE, DRV2625_REG_MODE_WAVEFORM);
set_reg(DRV2625_REG_WAVESEQ1, effect);
set_reg(DRV2625_REG_WAVESEQ2, 0);
set_reg(DRV2625_REG_GO, DRV2625_REG_GO_GO);
}
void haptic_play(haptic_effect_t effect) {
switch (effect) {
case HAPTIC_BUTTON_PRESS:
haptic_play_RTP(PRESS_EFFECT_AMPLITUDE, PRESS_EFFECT_DURATION);
break;
case HAPTIC_ALERT:
haptic_play_lib(ALERT_750MS_100);
break;
case HAPTIC_HOLD_TO_CONFIRM:
haptic_play_lib(TRANSITION_RAMP_UP_SHORT_SMOOTH_1);
break;
default:
break;
}
}

View File

@ -0,0 +1,130 @@
#ifndef __DRV_2625_LIB_H__
#define __DRV_2625_LIB_H__
typedef enum {
STRONG_CLICK_100 = 1,
STRONG_CLICK_60 = 2,
STRONG_CLICK_30 = 3,
SHARP_CLICK_100 = 4,
SHARP_CLICK_60 = 5,
SHARP_CLICK_30 = 6,
SOFT_BUMP_100 = 7,
SOFT_BUMP_60 = 8,
SOFT_BUMP_30 = 9,
DOUBLE_CLICK_100 = 10,
DOUBLE_CLICK_60 = 11,
TRIPLE_CLICK_100 = 12,
SOFT_FUZZ_60 = 13,
STRONG_BUZZ_100 = 14,
ALERT_750MS_100 = 15,
ALERT_1000MS_100 = 16,
STRONG_CLICK_1_100 = 17,
STRONG_CLICK_2_80 = 18,
STRONG_CLICK_3_60 = 19,
STRONG_CLICK_4_30 = 20,
MEDIUM_CLICK_1_100 = 21,
MEDIUM_CLICK_2_80 = 22,
MEDIUM_CLICK_3_60 = 23,
SHARP_TICK_1_100 = 24,
SHARP_TICK_2_80 = 25,
SHARP_TICK_3_60 = 26,
SHORT_DOUBLE_CLICK_STRONG_1_100 = 27,
SHORT_DOUBLE_CLICK_STRONG_2_80 = 28,
SHORT_DOUBLE_CLICK_STRONG_3_60 = 29,
SHORT_DOUBLE_CLICK_STRONG_4_30 = 30,
SHORT_DOUBLE_CLICK_MEDIUM_1_100 = 31,
SHORT_DOUBLE_CLICK_MEDIUM_2_80 = 32,
SHORT_DOUBLE_CLICK_MEDIUM_3_60 = 33,
SHORT_DOUBLE_SHARP_TICK_1_100 = 34,
SHORT_DOUBLE_SHARP_TICK_2_80 = 35,
SHORT_DOUBLE_SHARP_TICK_3_60 = 36,
LONG_DOUBLE_SHARP_TICK_STRONG_1_100 = 37,
LONG_DOUBLE_SHARP_TICK_STRONG_2_80 = 38,
LONG_DOUBLE_SHARP_TICK_STRONG_3_60 = 39,
LONG_DOUBLE_SHARP_TICK_STRONG_4_30 = 40,
LONG_DOUBLE_SHARP_TICK_MEDIUM_1_100 = 41,
LONG_DOUBLE_SHARP_TICK_MEDIUM_2_80 = 42,
LONG_DOUBLE_SHARP_TICK_MEDIUM_3_60 = 43,
LONG_DOUBLE_SHARP_TICK_1_100 = 44,
LONG_DOUBLE_SHARP_TICK_2_80 = 45,
LONG_DOUBLE_SHARP_TICK_3_60 = 46,
BUZZ_1_100 = 47,
BUZZ_2_80 = 48,
BUZZ_3_60 = 49,
BUZZ_4_40 = 50,
BUZZ_5_20 = 51,
PULSING_STRONG_1_100 = 52,
PULSING_STRONG_2_60 = 53,
PULSING_MEDIUM_1_100 = 54,
PULSING_MEDIUM_2_60 = 55,
PULSING_SHARP_1_100 = 56,
PULSING_SHARP_2_60 = 57,
TRANSITION_CLICK_1_100 = 58,
TRANSITION_CLICK_2_80 = 59,
TRANSITION_CLICK_3_60 = 60,
TRANSITION_CLICK_4_40 = 61,
TRANSITION_CLICK_5_20 = 62,
TRANSITION_CLICK_6_10 = 63,
TRANSITION_HUM_1_100 = 64,
TRANSITION_HUM_2_80 = 65,
TRANSITION_HUM_3_60 = 66,
TRANSITION_HUM_4_40 = 67,
TRANSITION_HUM_5_20 = 68,
TRANSITION_HUM_6_10 = 69,
TRANSITION_RAMP_DOWN_LONG_SMOOTH_1 = 70,
TRANSITION_RAMP_DOWN_LONG_SMOOTH_2 = 71,
TRANSITION_RAMP_DOWN_MEDIUM_SMOOTH_1 = 72,
TRANSITION_RAMP_DOWN_MEDIUM_SMOOTH_2 = 73,
TRANSITION_RAMP_DOWN_SHORT_SMOOTH_1 = 74,
TRANSITION_RAMP_DOWN_SHORT_SMOOTH_2 = 75,
TRANSITION_RAMP_DOWN_LONG_SHARP_1 = 76,
TRANSITION_RAMP_DOWN_LONG_SHARP_2 = 77,
TRANSITION_RAMP_DOWN_MEDIUM_SHARP_1 = 78,
TRANSITION_RAMP_DOWN_MEDIUM_SHARP_2 = 79,
TRANSITION_RAMP_DOWN_SHORT_SHARP_1 = 80,
TRANSITION_RAMP_DOWN_SHORT_SHARP_2 = 81,
TRANSITION_RAMP_UP_LONG_SMOOTH_1 = 82,
TRANSITION_RAMP_UP_LONG_SMOOTH_2 = 83,
TRANSITION_RAMP_UP_MEDIUM_SMOOTH_1 = 84,
TRANSITION_RAMP_UP_MEDIUM_SMOOTH_2 = 85,
TRANSITION_RAMP_UP_SHORT_SMOOTH_1 = 86,
TRANSITION_RAMP_UP_SHORT_SMOOTH_2 = 87,
TRANSITION_RAMP_UP_LONG_SHARP_1 = 88,
TRANSITION_RAMP_UP_LONG_SHARP_2 = 89,
TRANSITION_RAMP_UP_MEDIUM_SHARP_1 = 90,
TRANSITION_RAMP_UP_MEDIUM_SHARP_2 = 91,
TRANSITION_RAMP_UP_SHORT_SHARP_1 = 92,
TRANSITION_RAMP_UP_SHORT_SHARP_2 = 93,
TRANSITION_RAMP_DOWN_LONG_SMOOTH_1_50 = 94,
TRANSITION_RAMP_DOWN_LONG_SMOOTH_2_50 = 95,
TRANSITION_RAMP_DOWN_MEDIUM_SMOOTH_1_50 = 96,
TRANSITION_RAMP_DOWN_MEDIUM_SMOOTH_2_50 = 97,
TRANSITION_RAMP_DOWN_SHORT_SMOOTH_1_50 = 98,
TRANSITION_RAMP_DOWN_SHORT_SMOOTH_2_50 = 99,
TRANSITION_RAMP_DOWN_LONG_SHARP_1_50 = 100,
TRANSITION_RAMP_DOWN_LONG_SHARP_2_50 = 101,
TRANSITION_RAMP_DOWN_MEDIUM_SHARP_1_50 = 102,
TRANSITION_RAMP_DOWN_MEDIUM_SHARP_2_50 = 103,
TRANSITION_RAMP_DOWN_SHORT_SHARP_1_50 = 104,
TRANSITION_RAMP_DOWN_SHORT_SHARP_2_50 = 105,
TRANSITION_RAMP_UP_LONG_SMOOTH_1_50 = 106,
TRANSITION_RAMP_UP_LONG_SMOOTH_2_50 = 107,
TRANSITION_RAMP_UP_MEDIUM_SMOOTH_1_50 = 108,
TRANSITION_RAMP_UP_MEDIUM_SMOOTH_2_50 = 109,
TRANSITION_RAMP_UP_SHORT_SMOOTH_1_50 = 110,
TRANSITION_RAMP_UP_SHORT_SMOOTH_2_50 = 111,
TRANSITION_RAMP_UP_LONG_SHARP_1_50 = 112,
TRANSITION_RAMP_UP_LONG_SHARP_2_50 = 113,
TRANSITION_RAMP_UP_MEDIUM_SHARP_1_50 = 114,
TRANSITION_RAMP_UP_MEDIUM_SHARP_2_50 = 115,
TRANSITION_RAMP_UP_SHORT_SHARP_1_50 = 116,
TRANSITION_RAMP_UP_SHORT_SHARP_2_50 = 117,
LONG_BUZZ_FROM_PROGRAMMATIC_STOPPING = 118,
SMOOTH_HUM_1_100 = 119,
SMOOTH_HUM_2_80 = 120,
SMOOTH_HUM_3_60 = 121,
SMOOTH_HUM_4_40 = 122,
SMOOTH_HUM_5_20 = 123,
} drv2625_lib_effect_t;
#endif