| /* Copyright (c) 2014 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. |
| */ |
| |
| /* Flash memory module for Chrome EC */ |
| |
| #include "flash.h" |
| #include "registers.h" |
| #include "switch.h" |
| #include "system.h" |
| #include "timer.h" |
| #include "util.h" |
| #include "task.h" |
| #include "watchdog.h" |
| #include "console.h" |
| #include "hwtimer_chip.h" |
| |
| int all_protected; /* Has all-flash protection been requested? */ |
| int addr_prot_start; |
| int addr_prot_length; |
| |
| #define FLASH_ABORT_TIMEOUT 10000 |
| |
| #ifdef CONFIG_CODERAM_ARCH |
| #define TRISTATE_FLASH(x) |
| #else |
| #define TRISTATE_FLASH(x) flash_tristate(x) |
| #endif |
| /*****************************************************************************/ |
| /* flash internal functions */ |
| void flash_pinmux(int enable) |
| { |
| /* Select pin-mux for FIU*/ |
| UPDATE_BIT(NPCX_DEVALT(0), NPCX_DEVALT0_NO_F_SPI, !enable); |
| |
| /* CS0/1 pinmux */ |
| if (enable) { |
| #if (FIU_CHIP_SELECT == 1) |
| SET_BIT(NPCX_DEVALT(0), NPCX_DEVALT0_F_SPI_CS1_1); |
| #elif (FIU_CHIP_SELECT == 2) |
| SET_BIT(NPCX_DEVALT(0), NPCX_DEVALT0_F_SPI_CS1_2); |
| #endif |
| } else { |
| CLEAR_BIT(NPCX_DEVALT(0), NPCX_DEVALT0_F_SPI_CS1_1); |
| CLEAR_BIT(NPCX_DEVALT(0), NPCX_DEVALT0_F_SPI_CS1_2); |
| } |
| } |
| |
| void flash_tristate(int enable) |
| { |
| /* Enable/Disable FIU pins to tri-state */ |
| UPDATE_BIT(NPCX_DEVCNT, NPCX_DEVCNT_F_SPI_TRIS, enable); |
| } |
| |
| void flash_execute_cmd(uint8_t code, uint8_t cts) |
| { |
| /* set UMA_CODE */ |
| NPCX_UMA_CODE = code; |
| /* execute UMA flash transaction */ |
| NPCX_UMA_CTS = cts; |
| while (IS_BIT_SET(NPCX_UMA_CTS, NPCX_UMA_CTS_EXEC_DONE)) |
| ; |
| } |
| |
| void flash_cs_level(int level) |
| { |
| /* Set chip select to high/low level */ |
| UPDATE_BIT(NPCX_UMA_ECTS, NPCX_UMA_ECTS_SW_CS1, level); |
| } |
| |
| void flash_wait_ready(void) |
| { |
| uint8_t mask = SPI_FLASH_SR1_BUSY; |
| uint16_t timeout = FLASH_ABORT_TIMEOUT; |
| |
| /* Chip Select down. */ |
| flash_cs_level(0); |
| /* Command for Read status register */ |
| flash_execute_cmd(CMD_READ_STATUS_REG, MASK_CMD_ONLY); |
| while (--timeout) { |
| /* Read status register */ |
| NPCX_UMA_CTS = MASK_RD_1BYTE; |
| while (IS_BIT_SET(NPCX_UMA_CTS, NPCX_UMA_CTS_EXEC_DONE)) |
| ; |
| /* Busy bit is clear */ |
| if ((NPCX_UMA_DB0 & mask) == 0) |
| break; |
| |
| /* check task scheduling has started to prevent infinite loop */ |
| if (task_start_called()) |
| msleep(1); |
| }; /* Wait for Busy clear */ |
| /* Chip Select high. */ |
| flash_cs_level(1); |
| } |
| |
| int flash_write_enable(void) |
| { |
| uint8_t mask = SPI_FLASH_SR1_WEL; |
| /* Write enable command */ |
| flash_execute_cmd(CMD_WRITE_EN, MASK_CMD_ONLY); |
| /* Wait for flash is not busy */ |
| flash_wait_ready(); |
| |
| if (NPCX_UMA_DB0 & mask) |
| return 1; |
| else |
| return 0; |
| } |
| |
| void flash_set_address(uint32_t dest_addr) |
| { |
| uint8_t *addr = (uint8_t *)&dest_addr; |
| /* Write address */ |
| NPCX_UMA_AB2 = addr[2]; |
| NPCX_UMA_AB1 = addr[1]; |
| NPCX_UMA_AB0 = addr[0]; |
| } |
| |
| uint8_t flash_get_status1(void) |
| { |
| /* Disable tri-state */ |
| TRISTATE_FLASH(0); |
| /* Read status register1 */ |
| flash_execute_cmd(CMD_READ_STATUS_REG, MASK_CMD_RD_1BYTE); |
| /* Enable tri-state */ |
| TRISTATE_FLASH(1); |
| return NPCX_UMA_DB0; |
| } |
| |
| uint8_t flash_get_status2(void) |
| { |
| /* Disable tri-state */ |
| TRISTATE_FLASH(0); |
| /* Read status register2 */ |
| flash_execute_cmd(CMD_READ_STATUS_REG2, MASK_CMD_RD_1BYTE); |
| /* Enable tri-state */ |
| TRISTATE_FLASH(1); |
| return NPCX_UMA_DB0; |
| } |
| |
| /*****************************************************************************/ |
| /* flash protection functions */ |
| /* Use a copy function of spi_flash.c in flash driver */ |
| /** |
| * Computes block write protection range from registers |
| * Returns start == len == 0 for no protection |
| * |
| * @param sr1 Status register 1 |
| * @param sr2 Status register 2 |
| * @param start Output pointer for protection start offset |
| * @param len Output pointer for protection length |
| * |
| * @return EC_SUCCESS, or non-zero if any error. |
| */ |
| static int reg_to_protect(uint8_t sr1, uint8_t sr2, unsigned int *start, |
| unsigned int *len) |
| { |
| int blocks; |
| int size; |
| uint8_t cmp; |
| uint8_t sec; |
| uint8_t tb; |
| uint8_t bp; |
| |
| /* Determine flags */ |
| cmp = (sr2 & SPI_FLASH_SR2_CMP) ? 1 : 0; |
| sec = (sr1 & SPI_FLASH_SR1_SEC) ? 1 : 0; |
| tb = (sr1 & SPI_FLASH_SR1_TB) ? 1 : 0; |
| bp = (sr1 & (SPI_FLASH_SR1_BP2 | SPI_FLASH_SR1_BP1 | SPI_FLASH_SR1_BP0)) |
| >> 2; |
| |
| /* Bad pointers or invalid data */ |
| if (!start || !len || sr1 == -1 || sr2 == -1) |
| return EC_ERROR_INVAL; |
| |
| /* Not defined by datasheet */ |
| if (sec && bp == 6) |
| return EC_ERROR_INVAL; |
| |
| /* |
| * If SRP0 is not set, flash is not protected because status register |
| * can be rewritten. |
| */ |
| if (!(sr1 & SPI_FLASH_SR1_SRP0)) { |
| *start = *len = 0; |
| return EC_SUCCESS; |
| } |
| |
| /* Determine granularity (4kb sector or 64kb block) */ |
| /* Computation using 2 * 1024 is correct */ |
| size = sec ? (2 * 1024) : (64 * 1024); |
| |
| /* Determine number of blocks */ |
| /* Equivalent to pow(2, bp) with pow(2, 0) = 0 */ |
| blocks = bp ? (1 << bp) : 0; |
| /* Datasheet specifies don't care for BP == 4, BP == 5 */ |
| if (sec && bp == 5) |
| blocks = (1 << 4); |
| |
| /* Determine number of bytes */ |
| *len = size * blocks; |
| |
| /* Determine bottom/top of memory to protect */ |
| *start = tb ? 0 : |
| (CONFIG_FLASH_SIZE - *len) % CONFIG_FLASH_SIZE; |
| |
| /* Reverse computations if complement set */ |
| if (cmp) { |
| *start = (*start + *len) % CONFIG_FLASH_SIZE; |
| *len = CONFIG_FLASH_SIZE - *len; |
| } |
| |
| return EC_SUCCESS; |
| } |
| |
| /** |
| * Computes block write protection registers from range |
| * |
| * @param start Desired protection start offset |
| * @param len Desired protection length |
| * @param sr1 Output pointer for status register 1 |
| * @param sr2 Output pointer for status register 2 |
| * |
| * @return EC_SUCCESS, or non-zero if any error. |
| */ |
| static int protect_to_reg(unsigned int start, unsigned int len, |
| uint8_t *sr1, uint8_t *sr2) |
| { |
| char cmp = 0; |
| char sec = 0; |
| char tb = 0; |
| char bp = 0; |
| int blocks; |
| int size; |
| |
| /* Bad pointers */ |
| if (!sr1 || !sr2 || *sr1 == -1 || *sr2 == -1) |
| return EC_ERROR_INVAL; |
| |
| /* Invalid data */ |
| if ((start && !len) || start + len > CONFIG_FLASH_SIZE) |
| return EC_ERROR_INVAL; |
| |
| /* Set complement bit based on whether length is power of 2 */ |
| if ((len & (len - 1)) != 0) { |
| cmp = 1; |
| start = (start + len) % CONFIG_FLASH_SIZE; |
| len = CONFIG_FLASH_SIZE - len; |
| } |
| |
| /* Set bottom/top bit based on start address */ |
| /* Do not set if len == 0 or len == CONFIG_FLASH_SIZE */ |
| if (!start && (len % CONFIG_FLASH_SIZE)) |
| tb = 1; |
| |
| /* Set sector bit and determine block length based on protect length */ |
| if (len == 0 || len >= 128 * 1024) { |
| sec = 0; |
| size = 64 * 1024; |
| } else if (len >= 4 * 1024 && len <= 32 * 1024) { |
| sec = 1; |
| size = 2 * 1024; |
| } else |
| return EC_ERROR_INVAL; |
| |
| /* Determine number of blocks */ |
| if (len % size != 0) |
| return EC_ERROR_INVAL; |
| blocks = len / size; |
| |
| /* Determine bp = log2(blocks) with log2(0) = 0 */ |
| bp = blocks ? (31 - __builtin_clz(blocks)) : 0; |
| |
| /* Clear bits */ |
| *sr1 &= ~(SPI_FLASH_SR1_SEC | SPI_FLASH_SR1_TB |
| | SPI_FLASH_SR1_BP2 | SPI_FLASH_SR1_BP1 | SPI_FLASH_SR1_BP0); |
| *sr2 &= ~SPI_FLASH_SR2_CMP; |
| |
| /* Set bits */ |
| *sr1 |= (sec ? SPI_FLASH_SR1_SEC : 0) | (tb ? SPI_FLASH_SR1_TB : 0) |
| | (bp << 2); |
| *sr2 |= (cmp ? SPI_FLASH_SR2_CMP : 0); |
| |
| /* Set SRP0 so status register can't be changed */ |
| *sr1 |= SPI_FLASH_SR1_SRP0; |
| |
| return EC_SUCCESS; |
| } |
| |
| int flash_set_status_for_prot(int reg1, int reg2) |
| { |
| /* Disable tri-state */ |
| TRISTATE_FLASH(0); |
| /* Enable write */ |
| flash_write_enable(); |
| |
| NPCX_UMA_DB0 = reg1; |
| NPCX_UMA_DB1 = reg2; |
| |
| /* Write status register 1/2 */ |
| flash_execute_cmd(CMD_WRITE_STATUS_REG, MASK_CMD_WR_2BYTE); |
| /* Enable tri-state */ |
| TRISTATE_FLASH(1); |
| |
| reg_to_protect(reg1, reg2, &addr_prot_start, &addr_prot_length); |
| |
| return EC_SUCCESS; |
| } |
| |
| int flash_check_prot_range(unsigned int offset, unsigned int bytes) |
| { |
| /* Invalid value */ |
| if (offset + bytes > CONFIG_FLASH_PHYSICAL_SIZE) |
| return EC_ERROR_INVAL; |
| /* Check if ranges overlap */ |
| if (MAX(addr_prot_start, offset) < MIN(addr_prot_start + |
| addr_prot_length, offset + bytes)) |
| return EC_ERROR_ACCESS_DENIED; |
| |
| return EC_SUCCESS; |
| } |
| |
| int flash_check_prot_reg(unsigned int offset, unsigned int bytes) |
| { |
| unsigned int start; |
| unsigned int len; |
| uint8_t sr1 = 0, sr2 = 0; |
| int rv = EC_SUCCESS; |
| |
| sr1 = flash_get_status1(); |
| sr2 = flash_get_status2(); |
| |
| /* Invalid value */ |
| if (offset + bytes > CONFIG_FLASH_PHYSICAL_SIZE) |
| return EC_ERROR_INVAL; |
| |
| /* Compute current protect range */ |
| rv = reg_to_protect(sr1, sr2, &start, &len); |
| if (rv) |
| return rv; |
| |
| /* Check if ranges overlap */ |
| if (MAX(start, offset) < MIN(start + len, offset + bytes)) |
| return EC_ERROR_ACCESS_DENIED; |
| |
| return EC_SUCCESS; |
| |
| } |
| |
| int flash_write_prot_reg(unsigned int offset, unsigned int bytes) |
| { |
| int rv; |
| uint8_t sr1 = flash_get_status1(); |
| uint8_t sr2 = flash_get_status2(); |
| |
| /* Invalid values */ |
| if (offset + bytes > CONFIG_FLASH_SIZE) |
| return EC_ERROR_INVAL; |
| |
| /* Compute desired protect range */ |
| rv = protect_to_reg(offset, bytes, &sr1, &sr2); |
| if (rv) |
| return rv; |
| |
| return flash_set_status_for_prot(sr1, sr2); |
| } |
| |
| void flash_burst_write(unsigned int dest_addr, unsigned int bytes, |
| const char *data) |
| { |
| unsigned int i; |
| /* Chip Select down. */ |
| flash_cs_level(0); |
| /* Set erase address */ |
| flash_set_address(dest_addr); |
| /* Start write */ |
| flash_execute_cmd(CMD_FLASH_PROGRAM, MASK_CMD_WR_ADR); |
| for (i = 0; i < bytes; i++) { |
| flash_execute_cmd(*data, MASK_CMD_WR_ONLY); |
| data++; |
| } |
| /* Chip Select up */ |
| flash_cs_level(1); |
| } |
| |
| /*****************************************************************************/ |
| /* Physical layer APIs */ |
| |
| int flash_physical_read(int offset, int size, char *data) |
| { |
| int dest_addr = offset; |
| uint32_t idx; |
| |
| /* Disable tri-state */ |
| TRISTATE_FLASH(0); |
| /* Chip Select down. */ |
| flash_cs_level(0); |
| |
| /* Set read address */ |
| flash_set_address(dest_addr); |
| /* Start fast read - 1110 1001 - EXEC, WR, CMD, ADDR */ |
| flash_execute_cmd(CMD_FAST_READ, MASK_CMD_ADR_WR); |
| |
| /* Burst read transaction */ |
| for (idx = 0; idx < size; idx++) { |
| /* 1101 0101 - EXEC, RD, NO CMD, NO ADDR, 4 bytes */ |
| NPCX_UMA_CTS = MASK_RD_1BYTE; |
| /* wait for UMA to complete */ |
| while (IS_BIT_SET(NPCX_UMA_CTS, EXEC_DONE)) |
| ; |
| /* Get read transaction results*/ |
| data[idx] = NPCX_UMA_DB0; |
| } |
| |
| /* Chip Select up */ |
| flash_cs_level(1); |
| /* Enable tri-state */ |
| TRISTATE_FLASH(1); |
| return EC_SUCCESS; |
| } |
| |
| int flash_physical_read_image_size(int offset, int size) |
| { |
| int dest_addr = offset; |
| uint8_t temp; |
| uint32_t idx; |
| uint32_t image_size = 0; |
| |
| /* Disable tri-state */ |
| TRISTATE_FLASH(0); |
| /* Chip Select down. */ |
| flash_cs_level(0); |
| |
| /* Set read address */ |
| flash_set_address(dest_addr); |
| /* Start fast read - 1110 1001 - EXEC, WR, CMD, ADDR */ |
| flash_execute_cmd(CMD_FAST_READ, MASK_CMD_ADR_WR); |
| |
| /* Burst read transaction */ |
| for (idx = 0; idx < size; idx++) { |
| /* 1101 0101 - EXEC, RD, NO CMD, NO ADDR, 4 bytes */ |
| NPCX_UMA_CTS = MASK_RD_1BYTE; |
| /* wait for UMA to complete */ |
| while (IS_BIT_SET(NPCX_UMA_CTS, EXEC_DONE)) |
| ; |
| /* Find eof of image */ |
| temp = NPCX_UMA_DB0; |
| if (temp == 0xea) |
| image_size = idx; |
| } |
| |
| /* Chip Select up */ |
| flash_cs_level(1); |
| /* Enable tri-state */ |
| TRISTATE_FLASH(1); |
| return image_size; |
| } |
| |
| int flash_physical_is_erased(uint32_t offset, int size) |
| { |
| int dest_addr = offset; |
| uint32_t idx; |
| uint8_t temp; |
| |
| /* Chip Select down. */ |
| flash_cs_level(0); |
| |
| /* Set read address */ |
| flash_set_address(dest_addr); |
| /* Start fast read -1110 1001 - EXEC, WR, CMD, ADDR */ |
| flash_execute_cmd(CMD_FAST_READ, MASK_CMD_ADR_WR); |
| |
| /* Burst read transaction */ |
| for (idx = 0; idx < size; idx++) { |
| /* 1101 0101 - EXEC, RD, NO CMD, NO ADDR, 4 bytes */ |
| NPCX_UMA_CTS = MASK_RD_1BYTE; |
| /* Wait for UMA to complete */ |
| while (IS_BIT_SET(NPCX_UMA_CTS, EXEC_DONE)) |
| ; |
| /* Get read transaction results */ |
| temp = NPCX_UMA_DB0; |
| if (temp != 0xFF) |
| break; |
| } |
| |
| /* Chip Select up */ |
| flash_cs_level(1); |
| |
| if (idx == size) |
| return 1; |
| else |
| return 0; |
| } |
| |
| int flash_physical_write(int offset, int size, const char *data) |
| { |
| int dest_addr = offset; |
| const int sz_page = CONFIG_FLASH_WRITE_IDEAL_SIZE; |
| |
| /* Fail if offset, size, and data aren't at least word-aligned */ |
| if ((offset | size |
| | (uint32_t)(uintptr_t)data) & (CONFIG_FLASH_WRITE_SIZE - 1)) |
| return EC_ERROR_INVAL; |
| |
| /* check protection */ |
| if (all_protected) |
| return EC_ERROR_ACCESS_DENIED; |
| |
| /* Disable tri-state */ |
| TRISTATE_FLASH(0); |
| |
| /* Write the data per CONFIG_FLASH_WRITE_IDEAL_SIZE bytes */ |
| for (; size >= sz_page; size -= sz_page) { |
| |
| /* check protection */ |
| if (flash_check_prot_range(dest_addr, sz_page)) |
| return EC_ERROR_ACCESS_DENIED; |
| |
| /* Enable write */ |
| flash_write_enable(); |
| /* Burst UMA transaction */ |
| flash_burst_write(dest_addr, sz_page, data); |
| /* Wait write completed */ |
| flash_wait_ready(); |
| |
| data += sz_page; |
| dest_addr += sz_page; |
| } |
| |
| /* Handle final partial page, if any */ |
| if (size != 0) { |
| /* check protection */ |
| if (flash_check_prot_range(dest_addr, size)) |
| return EC_ERROR_ACCESS_DENIED; |
| |
| /* Enable write */ |
| flash_write_enable(); |
| /* Burst UMA transaction */ |
| flash_burst_write(dest_addr, size, data); |
| /* Wait write completed */ |
| flash_wait_ready(); |
| } |
| |
| /* Enable tri-state */ |
| TRISTATE_FLASH(1); |
| return EC_SUCCESS; |
| } |
| |
| int flash_physical_erase(int offset, int size) |
| { |
| /* check protection */ |
| if (all_protected) |
| return EC_ERROR_ACCESS_DENIED; |
| |
| /* Disable tri-state */ |
| TRISTATE_FLASH(0); |
| |
| /* Alignment has been checked in upper layer */ |
| for (; size > 0; size -= CONFIG_FLASH_ERASE_SIZE, |
| offset += CONFIG_FLASH_ERASE_SIZE) { |
| |
| /* Do nothing if already erased */ |
| if (flash_is_erased(offset, CONFIG_FLASH_ERASE_SIZE)) |
| continue; |
| |
| /* check protection */ |
| if (flash_check_prot_range(offset, CONFIG_FLASH_ERASE_SIZE)) |
| return EC_ERROR_ACCESS_DENIED; |
| |
| /* |
| * Reload the watchdog timer, so that erasing many flash pages |
| * doesn't cause a watchdog reset. May not need this now that |
| * we're using msleep() below. |
| */ |
| watchdog_reload(); |
| |
| /* Enable write */ |
| flash_write_enable(); |
| /* Set erase address */ |
| flash_set_address(offset); |
| /* Start erase */ |
| flash_execute_cmd(CMD_SECTOR_ERASE, MASK_CMD_ADR); |
| |
| /* Wait erase completed */ |
| flash_wait_ready(); |
| } |
| |
| /* Enable tri-state */ |
| TRISTATE_FLASH(1); |
| return EC_SUCCESS; |
| } |
| |
| int flash_physical_get_protect(int bank) |
| { |
| uint32_t addr = bank * CONFIG_FLASH_BANK_SIZE; |
| return flash_check_prot_reg(addr, CONFIG_FLASH_BANK_SIZE); |
| } |
| |
| uint32_t flash_physical_get_protect_flags(void) |
| { |
| uint32_t flags = 0; |
| |
| /* Check if WP region is protected in status register */ |
| if (flash_check_prot_reg(WP_BANK_OFFSET*CONFIG_FLASH_BANK_SIZE, |
| WP_BANK_COUNT*CONFIG_FLASH_BANK_SIZE)) |
| flags |= EC_FLASH_PROTECT_RO_AT_BOOT; |
| |
| /* |
| * TODO: If status register protects a range, but SRP0 is not set, |
| * flags should indicate EC_FLASH_PROTECT_ERROR_INCONSISTENT. |
| */ |
| |
| /* Read all-protected state from our shadow copy */ |
| if (all_protected) |
| flags |= EC_FLASH_PROTECT_ALL_NOW; |
| |
| return flags; |
| } |
| |
| int flash_physical_protect_now(int all) |
| { |
| if (all) |
| all_protected = 1; |
| |
| /* TODO: if all, disable SPI interface */ |
| |
| return EC_SUCCESS; |
| } |
| |
| |
| int flash_physical_protect_at_boot(enum flash_wp_range range) |
| { |
| switch (range) { |
| case FLASH_WP_NONE: |
| /* Clear protection bits in status register */ |
| return flash_set_status_for_prot(0, 0); |
| case FLASH_WP_RO: |
| /* Protect read-only */ |
| return flash_write_prot_reg( |
| WP_BANK_OFFSET*CONFIG_FLASH_BANK_SIZE, |
| WP_BANK_COUNT*CONFIG_FLASH_BANK_SIZE); |
| case FLASH_WP_ALL: |
| default: |
| return EC_ERROR_INVAL; |
| } |
| } |
| |
| uint32_t flash_physical_get_valid_flags(void) |
| { |
| return EC_FLASH_PROTECT_RO_AT_BOOT | |
| EC_FLASH_PROTECT_RO_NOW | |
| EC_FLASH_PROTECT_ALL_NOW; |
| } |
| |
| uint32_t flash_physical_get_writable_flags(uint32_t cur_flags) |
| { |
| uint32_t ret = 0; |
| |
| /* If RO protection isn't enabled, its at-boot state can be changed. */ |
| if (!(cur_flags & EC_FLASH_PROTECT_RO_NOW)) |
| ret |= EC_FLASH_PROTECT_RO_AT_BOOT; |
| |
| /* |
| * If entire flash isn't protected at this boot, it can be enabled if |
| * the WP GPIO is asserted. |
| */ |
| if (!(cur_flags & EC_FLASH_PROTECT_ALL_NOW) && |
| (cur_flags & EC_FLASH_PROTECT_GPIO_ASSERTED)) |
| ret |= EC_FLASH_PROTECT_ALL_NOW; |
| |
| return ret; |
| } |
| |
| /*****************************************************************************/ |
| /* High-level APIs */ |
| |
| int flash_pre_init(void) |
| { |
| /* Enable FIU interface */ |
| flash_pinmux(1); |
| |
| #ifdef CONFIG_CODERAM_ARCH |
| /* Disable tristate all the time */ |
| CLEAR_BIT(NPCX_DEVCNT, NPCX_DEVCNT_F_SPI_TRIS); |
| #endif |
| |
| return EC_SUCCESS; |
| } |