int flash_info_read(uint32_t offset, uint32_t *dst) { int retval; /* Make sure flash controller is awake. */ retval = _check_flash_is_awake(); if (retval) return retval; GWRITE_FIELD(FLASH, FSH_TRANS, OFFSET, offset); GWRITE_FIELD(FLASH, FSH_TRANS, MAINB, 1); GWRITE_FIELD(FLASH, FSH_TRANS, SIZE, 1); retval = _flash_cmd(1, FSH_OP_READ); if (retval) return retval; if (_flash_error()) return E_FL_ERROR; if (!retval) *dst = GREG32(FLASH, FSH_DOUT_VAL1); return retval; }
void rbox_init(void) { /* Enable RBOX */ clock_enable_module(MODULE_RBOX, 1); /* Clear existing interrupts */ GWRITE(RBOX, WAKEUP_CLEAR, 1); GWRITE(RBOX, INT_STATE, 1); /* Make sure fuse override is not already enabled */ GWRITE(RBOX, FUSE_CTRL, 0); /* Block output from key0 and 1 when power button is pressed */ GWRITE_FIELD(RBOX, DEBUG_BLOCK_OUTPUT, KEY0_SEL, 1); GWRITE_FIELD(RBOX, DEBUG_BLOCK_OUTPUT, KEY1_SEL, 1); /* Increase debounce */ GWRITE_FIELD(RBOX, DEBUG_DEBOUNCE, PERIOD, 15); /* Enable debug override */ GWRITE_FIELD(RBOX, FUSE_CTRL, OVERRIDE_FUSE, 1); GWRITE_FIELD(RBOX, FUSE_CTRL, OVERRIDE_FUSE_READY, 1); #ifdef CONFIG_RBOX_DEBUG enable_interrupts(); #endif }
/** Configure the data transmission format * * @param mode Clock polarity and phase mode (0 - 3) * */ static void sps_configure(enum sps_mode mode, enum spi_clock_mode clk_mode) { /* Disable All Interrupts */ GREG32(SPS, ICTRL) = 0; GWRITE_FIELD(SPS, CTRL, MODE, mode); GWRITE_FIELD(SPS, CTRL, IDLE_LVL, 0); GWRITE_FIELD(SPS, CTRL, CPHA, clk_mode & 1); GWRITE_FIELD(SPS, CTRL, CPOL, (clk_mode >> 1) & 1); GWRITE_FIELD(SPS, CTRL, TXBITOR, 1); /* MSB first */ GWRITE_FIELD(SPS, CTRL, RXBITOR, 1); /* MSB first */ /* xfer 0xff when tx fifo is empty */ GREG32(SPS, DUMMY_WORD) = GC_SPS_DUMMY_WORD_DEFAULT; /* [5,4,3] [2,1,0] * RX{DIS, EN, RST} TX{DIS, EN, RST} */ GREG32(SPS, FIFO_CTRL) = 0x9; /* wait for reset to self clear. */ while (GREG32(SPS, FIFO_CTRL) & 9) ; /* Do not enable TX FIFO until we have something to send. */ GWRITE_FIELD(SPS, FIFO_CTRL, RXFIFO_EN, 1); GREG32(SPS, RXFIFO_THRESHOLD) = 8; GWRITE_FIELD(SPS, ICTRL, RXFIFO_LVL, 1); /* Use CS_DEASSERT to retrieve all remaining bytes from RX FIFO. */ GWRITE_FIELD(SPS, ISTATE_CLR, CS_DEASSERT, 1); GWRITE_FIELD(SPS, ICTRL, CS_DEASSERT, 1); }
static void sps_cs_deassert_interrupt(uint32_t port) { /* Make sure the receive FIFO is drained. */ sps_rx_interrupt(port, 1); GWRITE_FIELD(SPS, ISTATE_CLR, CS_DEASSERT, 1); GWRITE_FIELD(SPS, FIFO_CTRL, TXFIFO_EN, 0); /* * And transmit FIFO is emptied, so the next transaction doesn't start * by clocking out any bytes left over from this one. */ GREG32(SPS, TXFIFO_WPTR) = GREG32(SPS, TXFIFO_RPTR); }
void unlockFlashForRW(void) { uint32_t text_end = ((uint32_t)(&__ro_end) + (uint32_t)(&__data_end) - (uint32_t)(&__data_start) + CONFIG_FLASH_BANK_SIZE) & ~(CONFIG_FLASH_BANK_SIZE - 1); GREG32(GLOBALSEC, FLASH_REGION1_BASE_ADDR) = text_end; GREG32(GLOBALSEC, FLASH_REGION1_SIZE) = CONFIG_FLASH_SIZE - text_end - 1; GWRITE_FIELD(GLOBALSEC, FLASH_REGION1_CTRL, EN, 1); GWRITE_FIELD(GLOBALSEC, FLASH_REGION1_CTRL, RD_EN, 1); GWRITE_FIELD(GLOBALSEC, FLASH_REGION1_CTRL, WR_EN, 0); }
void usb_spi_board_enable(struct usb_spi_config const *config) { hook_call_deferred(&update_finished_data, -1); update_in_progress = 1; disable_ec_ap_spi(); if (config->state->enabled_host == USB_SPI_EC) enable_ec_spi(); else if (config->state->enabled_host == USB_SPI_AP) enable_ap_spi(); else { CPRINTS("DEVICE NOT SUPPORTED"); return; } /* Connect DIO A4, A8, and A14 to the SPI peripheral */ GWRITE(PINMUX, DIOA4_SEL, 0); /* SPI_MOSI */ GWRITE(PINMUX, DIOA8_SEL, 0); /* SPI_CS_L */ GWRITE(PINMUX, DIOA14_SEL, 0); /* SPI_CLK */ /* Set SPI_CS to be an internal pull up */ GWRITE_FIELD(PINMUX, DIOA14_CTL, PU, 1); CPRINTS("usb_spi enable %s", gpio_get_level(GPIO_AP_FLASH_SELECT) ? "AP" : "EC"); spi_enable(CONFIG_SPI_FLASH_PORT, 1); }
void usb_spi_board_disable(struct usb_spi_config const *config) { CPRINTS("usb_spi disable"); spi_enable(CONFIG_SPI_FLASH_PORT, 0); disable_ec_ap_spi(); /* Disconnect SPI peripheral to tri-state pads */ /* Disable internal pull up */ GWRITE_FIELD(PINMUX, DIOA14_CTL, PU, 0); /* TODO: Implement way to get the gpio */ ASSERT(GREAD(PINMUX, GPIO0_GPIO7_SEL) == GC_PINMUX_DIOA4_SEL); ASSERT(GREAD(PINMUX, GPIO0_GPIO8_SEL) == GC_PINMUX_DIOA8_SEL); ASSERT(GREAD(PINMUX, GPIO0_GPIO9_SEL) == GC_PINMUX_DIOA14_SEL); /* Set SPI MOSI, CLK, and CS_L as inputs */ GWRITE(PINMUX, DIOA4_SEL, GC_PINMUX_GPIO0_GPIO7_SEL); GWRITE(PINMUX, DIOA8_SEL, GC_PINMUX_GPIO0_GPIO8_SEL); GWRITE(PINMUX, DIOA14_SEL, GC_PINMUX_GPIO0_GPIO9_SEL); /* * TODO(crosbug.com/p/52366): remove once sys_rst just resets the TPM * instead of cr50. * Resetting the EC and AP cause sys_rst to be asserted currently that * will cause cr50 to do a soft reset. Delay the end of the transaction * to prevent cr50 from resetting during a series of usb_spi calls. */ hook_call_deferred(&update_finished_data, 1 * SECOND); }
void init_jittery_clock(int highsec) { unsigned trimfast = GR_FUSE(RC_JTR_OSC60_CC_TRIM); unsigned trim48 = GR_FUSE(RC_JTR_OSC48_CC_TRIM); unsigned delta = (trim48 - trimfast); /* For metastability reasons, avoid clk_jtr ~= clk_timer, make * a keepout region around 24MHz of about 0.75MHz, about 3/16 of the * the delta from trimfast and trim48 */ unsigned skiplow = (trim48 << 4) - (delta * 6); unsigned skiphigh = (trim48 << 4) + (delta * 6); unsigned setting = trimfast << 4; unsigned stepx16; unsigned bankval; int bank; if (highsec) stepx16 = 0xff - trimfast; else stepx16 = 2 * (trim48 - trimfast); for (bank = 0; bank < 16; bank++) { /* saturate at 0xff */ bankval = (setting > 0xfff) ? 0xff : (setting >> 4); GR_XO_JTR_JITTERY_TRIM_BANK(bank) = bankval; setting += stepx16; if ((setting > skiplow) && (setting < skiphigh)) setting = skiphigh; } GWRITE_FIELD(XO, CLK_JTR_TRIM_CTRL, RC_COARSE_TRIM_SRC, 2); GWRITE_FIELD(XO, CLK_JTR_TRIM_CTRL, RC_INITIAL_TRIM_PERIOD, 100); GWRITE_FIELD(XO, CLK_JTR_TRIM_CTRL, RC_TRIM_EN, 1); GREG32(XO, CLK_JTR_JITTERY_TRIM_EN) = 1; GREG32(XO, CLK_JTR_SYNC_CONTENTS) = 0; /* Writing any value locks things until the next hard reboot */ GREG32(XO, CFG_WR_EN) = 0; GREG32(XO, JTR_CTRL_EN) = 0; }
void disarmRAMGuards(void) { GWRITE_FIELD(GLOBALSEC, CPU0_D_REGION0_CTRL, EN, 1); GWRITE_FIELD(GLOBALSEC, CPU0_D_REGION0_CTRL, RD_EN, 1); GWRITE_FIELD(GLOBALSEC, CPU0_D_REGION0_CTRL, WR_EN, 1); GWRITE_FIELD(GLOBALSEC, CPU0_D_REGION1_CTRL, EN, 1); GWRITE_FIELD(GLOBALSEC, CPU0_D_REGION1_CTRL, RD_EN, 1); GWRITE_FIELD(GLOBALSEC, CPU0_D_REGION1_CTRL, WR_EN, 1); }
static void usb_reset(void) { CPRINTS("%s, status %x", __func__, GR_USB_GINTSTS); print_later("usb_reset()", 0, 0, 0, 0, 0); /* Clear our internal state */ device_state = DS_DEFAULT; configuration_value = 0; /* Clear the device address */ GWRITE_FIELD(USB, DCFG, DEVADDR, 0); /* Reinitialize all the endpoints */ usb_init_endpoints(); }
static int do_flash_op(enum flash_op op, int is_info_bank, int byte_offset, int words) { volatile uint32_t *fsh_pe_control; uint32_t opcode, tmp, errors; int retry_count, max_attempts, extra_prog_pulse, i; int timedelay_us = 100; uint32_t prev_error = 0; /* Make sure the smart program/erase algorithms are enabled. */ if (!GREAD(FLASH, FSH_TIMING_PROG_SMART_ALGO_ON) || !GREAD(FLASH, FSH_TIMING_ERASE_SMART_ALGO_ON)) { CPRINTF("%s:%d\n", __func__, __LINE__); return EC_ERROR_UNIMPLEMENTED; } /* Error status is self-clearing. Read it until it does (we hope). */ for (i = 0; i < 50; i++) { tmp = GREAD(FLASH, FSH_ERROR); if (!tmp) break; usleep(timedelay_us); } /* If we can't clear the error status register then something is wrong. */ if (tmp) { CPRINTF("%s:%d\n", __func__, __LINE__); return EC_ERROR_UNKNOWN; } /* We have two flash banks. Adjust offset and registers accordingly. */ if (is_info_bank) { /* Only INFO bank operations are supported. */ fsh_pe_control = GREG32_ADDR(FLASH, FSH_PE_CONTROL1); } else if (byte_offset >= CFG_FLASH_HALF) { byte_offset -= CFG_FLASH_HALF; fsh_pe_control = GREG32_ADDR(FLASH, FSH_PE_CONTROL1); } else { fsh_pe_control = GREG32_ADDR(FLASH, FSH_PE_CONTROL0); } /* What are we doing? */ switch (op) { case OP_ERASE_BLOCK: if (is_info_bank) /* Erasing the INFO bank from the RW section is * unsupported. */ return EC_ERROR_INVAL; opcode = 0x31415927; words = 0; /* don't care, really */ /* This number is based on the TSMC spec Nme=Terase/Tsme */ max_attempts = 45; break; case OP_WRITE_BLOCK: opcode = 0x27182818; words--; /* count register is zero-based */ /* This number is based on the TSMC spec Nmp=Tprog/Tsmp */ max_attempts = 9; break; case OP_READ_BLOCK: if (!is_info_bank) /* This code path only supports reading from * the INFO bank. */ return EC_ERROR_INVAL; opcode = 0x16021765; words = 1; max_attempts = 9; break; default: return EC_ERROR_INVAL; } /* * Set the parameters. For writes, we assume the write buffer is * already filled before we call this function. */ GWRITE_FIELD(FLASH, FSH_TRANS, OFFSET, byte_offset / 4); /* word offset */ GWRITE_FIELD(FLASH, FSH_TRANS, MAINB, is_info_bank ? 1 : 0); GWRITE_FIELD(FLASH, FSH_TRANS, SIZE, words); /* TODO: Make sure this function isn't getting called "too often" in * between erases. */ extra_prog_pulse = 0; for (retry_count = 0; retry_count < max_attempts; retry_count++) { /* Kick it off */ GWRITE(FLASH, FSH_PE_EN, 0xb11924e1); *fsh_pe_control = opcode; /* Wait for completion. 150ms should be enough * (crosbug.com/p/45366). */ for (i = 0; i < 1500; i++) { tmp = *fsh_pe_control; if (!tmp) break; usleep(timedelay_us); } /* Timed out waiting for control register to clear */ if (tmp) { CPRINTF("%s:%d\n", __func__, __LINE__); return EC_ERROR_UNKNOWN; } /* Check error status */ errors = GREAD(FLASH, FSH_ERROR); if (errors && (errors != prev_error)) { prev_error = errors; CPRINTF("%s:%d errors %x fsh_pe_control %p\n", __func__, __LINE__, errors, fsh_pe_control); } /* Error status is self-clearing. Read it until it does * (we hope). */ for (i = 0; i < 50; i++) { tmp = GREAD(FLASH, FSH_ERROR); if (!tmp) break; usleep(timedelay_us); } /* If we can't clear the error status register then something * is wrong. */ if (tmp) { CPRINTF("%s:%d\n", __func__, __LINE__); return EC_ERROR_UNKNOWN; } /* The operation was successful. */ if (!errors) { /* From the spec: * "In addition, one more program pulse is needed after * program verification is passed." */ if (op == OP_WRITE_BLOCK && !extra_prog_pulse) { extra_prog_pulse = 1; max_attempts++; continue; } return EC_SUCCESS; } /* If there were errors after completion retry. */ watchdog_reload(); } CPRINTF("%s:%d, retry count %d\n", __func__, __LINE__, retry_count); return EC_ERROR_UNKNOWN; }
/* * Push data to the SPS TX FIFO * @param data Pointer to 8-bit data * @param data_size Number of bytes to transmit * @return : actual number of bytes placed into tx fifo */ int sps_transmit(uint8_t *data, size_t data_size) { volatile uint32_t *sps_tx_fifo; uint32_t rptr; uint32_t wptr; uint32_t fifo_room; int bytes_sent; int inst = 0; if (GREAD_FIELD_I(SPS, inst, ISTATE, TXFIFO_EMPTY)) tx_empty_count++; /* Inside packet this means underrun. */ sps_tx_fifo = (volatile uint32_t *)SPS_TX_FIFO_BASE_ADDR; wptr = GREG32_I(SPS, inst, TXFIFO_WPTR); rptr = GREG32_I(SPS, inst, TXFIFO_RPTR); fifo_room = (rptr - wptr - 1) & SPS_FIFO_MASK; if (fifo_room < data_size) { bytes_sent = fifo_room; data_size = fifo_room; } else { bytes_sent = data_size; } sps_tx_fifo += (wptr & SPS_FIFO_MASK) / sizeof(*sps_tx_fifo); while (data_size) { if ((wptr & 3) || (data_size < 4) || ((uintptr_t)data & 3)) { /* * Either we have less then 4 bytes to send, or one of * the pointers is not 4 byte aligned. Need to go byte * by byte. */ uint32_t fifo_contents; int bit_shift; fifo_contents = *sps_tx_fifo; do { /* * CR50 SPS controller does not allow byte * accesses for writes into the FIFO, so read * modify/write is requred. Tracked uder * http://b/20894727 */ bit_shift = 8 * (wptr & 3); fifo_contents &= ~(0xff << bit_shift); fifo_contents |= (((uint32_t)(*data++)) << bit_shift); data_size--; wptr++; } while (data_size && (wptr & 3)); *sps_tx_fifo++ = fifo_contents; } else { /* * Both fifo wptr and data are aligned and there is * plenty to send. */ *sps_tx_fifo++ = *((uint32_t *)data); data += 4; data_size -= 4; wptr += 4; } GREG32_I(SPS, inst, TXFIFO_WPTR) = wptr & SPS_FIFO_PTR_MASK; /* Make sure FIFO pointer wraps along with the index. */ if (!(wptr & SPS_FIFO_MASK)) sps_tx_fifo = (volatile uint32_t *) SPS_TX_FIFO_BASE_ADDR; } /* * Start TX if necessary. This happens after FIFO is primed, which * helps aleviate TX underrun problems but introduces delay before * data starts coming out. */ if (!GREAD_FIELD(SPS, FIFO_CTRL, TXFIFO_EN)) GWRITE_FIELD(SPS, FIFO_CTRL, TXFIFO_EN, 1); sps_tx_count += bytes_sent; return bytes_sent; }
void init_sof_clock(void) { /* Copy fuse value into software registers, both coarse and fine */ unsigned coarseTrimVal = GR_FUSE(RC_TIMER_OSC48_CC_TRIM); unsigned fineTrimVal = GR_FUSE(RC_TIMER_OSC48_FC_TRIM); /* We think SOF toggle happens once every mS, or ~24000 clock ticks */ unsigned targetCnt = PCLK_FREQ / 1000; /* The possible operations of a particular calibration bucket */ unsigned binaryDnOp = 0x1 | 0x1 << 4; unsigned binaryUpOp = 0x1 | 0x0 << 4; unsigned subOp = 0x3 | 0x1 << 4; unsigned addOp = 0x2 | 0x1 << 4; unsigned nop = 0; GREG32(XO, CLK_TIMER_RC_COARSE_ATE_TRIM) = coarseTrimVal; GREG32(XO, CLK_TIMER_RC_FINE_ATE_TRIM) = fineTrimVal; /* Coarse trim values come from software */ GWRITE_FIELD(XO, CLK_TIMER_TRIM_CTRL, RC_COARSE_TRIM_SRC, 0); /* enable error interrupts * This enables underrun and overflow interrupts */ GREG32(XO, DXO_INT_ENABLE) = 0xC; /* Setup SOF calibration buckets and associated operations */ GREG32(XO, CLK_TIMER_SLOW_CALIB0) = targetCnt * 70 / 100; GREG32(XO, CLK_TIMER_SLOW_CALIB1) = targetCnt * 80 / 100; GREG32(XO, CLK_TIMER_SLOW_CALIB2) = targetCnt * 90 / 100; GREG32(XO, CLK_TIMER_SLOW_CALIB3) = targetCnt * (1000000 - 1250) / 1000000; GREG32(XO, CLK_TIMER_SLOW_CALIB4) = targetCnt; GREG32(XO, CLK_TIMER_SLOW_CALIB5) = targetCnt * (1000000 + 1250) / 1000000; GREG32(XO, CLK_TIMER_SLOW_CALIB6) = targetCnt * 110 / 100; GREG32(XO, CLK_TIMER_SLOW_CALIB7) = targetCnt * 120 / 100; /* This is a work-around for the screwy SOF */ GREG32(XO, CLK_TIMER_SLOW_CALIB_CTRL0) = nop; GREG32(XO, CLK_TIMER_SLOW_CALIB_CTRL1) = binaryDnOp; GREG32(XO, CLK_TIMER_SLOW_CALIB_CTRL2) = binaryDnOp; GREG32(XO, CLK_TIMER_SLOW_CALIB_CTRL3) = subOp; GREG32(XO, CLK_TIMER_SLOW_CALIB_CTRL4) = nop; GREG32(XO, CLK_TIMER_SLOW_CALIB_CTRL5) = nop; GREG32(XO, CLK_TIMER_SLOW_CALIB_CTRL6) = addOp; GREG32(XO, CLK_TIMER_SLOW_CALIB_CTRL7) = binaryUpOp; GREG32(XO, CLK_TIMER_SLOW_CALIB_CTRL8) = binaryUpOp; /* Set the calibration mode */ GWRITE_FIELD(XO, CLK_TIMER_CALIB_TRIM_CTRL, ENABLE_FAST, 0); GWRITE_FIELD(XO, CLK_TIMER_CALIB_TRIM_CTRL, ENABLE_SLOW, 1); GWRITE_FIELD(XO, CLK_TIMER_CALIB_TRIM_CTRL, SLOW_MODE_SEL, 0); /* SOF */ GWRITE_FIELD(XO, CLK_TIMER_CALIB_TRIM_CTRL, MAX_TRIM_SEL, 1); /* Don't stop when a NOP operation is seen, keep on calibrating */ GWRITE_FIELD(XO, CLK_TIMER_CALIB_TRIM_CTRL, STOP_ON_NOP, 0); /* Set source of trim codes: * coarse trim comes from software * fine trim comes from calibration engine */ GWRITE_FIELD(XO, CLK_TIMER_TRIM_CTRL, RC_COARSE_TRIM_SRC, 0); GWRITE_FIELD(XO, CLK_TIMER_TRIM_CTRL, RC_FINE_TRIM_SRC, 1); /* Enable dynamic trim */ GWRITE_FIELD(XO, CLK_TIMER_TRIM_CTRL, RC_TRIM_EN, 1); /* Sync everything! */ GREG32(XO, CLK_TIMER_SYNC_CONTENTS) = 1; /* Enable interrupts */ task_enable_irq(GC_IRQNUM_XO0_SLOW_CALIB_UNDERRUN_INT); task_enable_irq(GC_IRQNUM_XO0_SLOW_CALIB_OVERFLOW_INT); }
/* Some Setup packets don't have a data stage at all. */ static int handle_setup_with_no_data_stage(enum table_case tc, struct usb_setup_packet *req) { uint8_t set_addr; print_later("handle_setup_with_no_data_stage(%c)", "0ABCDE67"[tc], 0, 0, 0, 0); switch (req->bRequest) { case USB_REQ_SET_ADDRESS: /* * Set the address after the IN packet handshake. * * From the USB 2.0 spec, section 9.4.6: * * As noted elsewhere, requests actually may result in * up to three stages. In the first stage, the Setup * packet is sent to the device. In the optional second * stage, data is transferred between the host and the * device. In the final stage, status is transferred * between the host and the device. The direction of * data and status transfer depends on whether the host * is sending data to the device or the device is * sending data to the host. The Status stage transfer * is always in the opposite direction of the Data * stage. If there is no Data stage, the Status stage * is from the device to the host. * * Stages after the initial Setup packet assume the * same device address as the Setup packet. The USB * device does not change its device address until * after the Status stage of this request is completed * successfully. Note that this is a difference between * this request and all other requests. For all other * requests, the operation indicated must be completed * before the Status stage */ set_addr = req->wValue & 0xff; /* * NOTE: Now that we've said that, we don't do it. The * hardware for this SoC knows that an IN packet will * be following the SET ADDRESS, so it waits until it * sees that happen before the address change takes * effect. If we wait until after the IN packet to * change the register, the hardware gets confused and * doesn't respond to anything. */ GWRITE_FIELD(USB, DCFG, DEVADDR, set_addr); CPRINTS("SETAD 0x%02x (%d)", set_addr, set_addr); print_later("SETAD 0x%02x (%d)", set_addr, set_addr, 0, 0, 0); device_state = DS_ADDRESS; processed_update_counter = 1; break; case USB_REQ_SET_CONFIGURATION: print_later("SETCFG 0x%x", req->wValue, 0, 0, 0, 0); switch (req->wValue) { case 0: configuration_value = req->wValue; device_state = DS_ADDRESS; break; case 1: /* Caution: Only one config descriptor TODAY */ /* TODO: All endpoints set to DATA0 toggle state */ configuration_value = req->wValue; device_state = DS_CONFIGURED; break; default: /* Nope. That's a paddlin. */ return -1; } break; case USB_REQ_CLEAR_FEATURE: case USB_REQ_SET_FEATURE: /* TODO: Handle DEVICE_REMOTE_WAKEUP, ENDPOINT_HALT? */ print_later("SET_FEATURE/CLEAR_FEATURE. Whatever...", 0, 0, 0, 0, 0); break; default: /* Anything else is unsupported */ return -1; } /* No data to transfer, go straight to the Status phase. */ return 0; }