| /* |
| * (C) Copyright 2012 Samsung Electronics Co. Ltd |
| * |
| * See file CREDITS for list of people who contributed to this |
| * project. |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License as |
| * published by the Free Software Foundation; either version 2 of |
| * the License, or (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, |
| * MA 02111-1307 USA |
| */ |
| |
| #include <common.h> |
| #include <fdtdec.h> |
| #include <mmc.h> |
| #include <asm/arch/clk.h> |
| #include <asm/arch/cpu.h> |
| #include <asm/arch/gpio.h> |
| #include <asm/arch/mshc.h> |
| #include <asm/arch/pinmux.h> |
| |
| /* support 4 mmc hosts */ |
| enum { |
| MAX_MMC_HOSTS = 4 |
| }; |
| |
| static struct mmc mshci_dev[MAX_MMC_HOSTS]; |
| static struct mshci_host mshci_host[MAX_MMC_HOSTS]; |
| static int num_devs; |
| |
| #include <asm/arch/clock.h> |
| #include <asm/arch/periph.h> |
| |
| /* Struct to hold mshci register and bus width */ |
| struct fdt_mshci { |
| struct s5p_mshci *reg; /* address of registers in physical memory */ |
| int bus_width; /* bus width */ |
| int removable; /* removable device? */ |
| enum periph_id periph_id; /* Peripheral ID for this peripheral */ |
| struct fdt_gpio_state enable_gpio; /* How to enable it */ |
| }; |
| |
| /** |
| * Set bits of MSHCI host control register. |
| * |
| * @param host MSHCI host |
| * @param bits bits to be set |
| * @return 0 on success, -1 on failure |
| */ |
| static int mshci_setbits(struct mshci_host *host, unsigned int bits) |
| { |
| ulong start; |
| |
| setbits_le32(&host->reg->ctrl, bits); |
| |
| start = get_timer(0); |
| while (readl(&host->reg->ctrl) & bits) { |
| if (get_timer(start) > TIMEOUT_MS) { |
| debug("Set bits failed\n"); |
| return -1; |
| } |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * Reset MSHCI host control register. |
| * |
| * @param host MSHCI host |
| * @return 0 on success, -1 on failure |
| */ |
| static int mshci_reset_all(struct mshci_host *host) |
| { |
| ulong start; |
| |
| /* |
| * Before we reset ciu check the DATA0 line. If it is low and |
| * we resets the ciu then we might see some errors. |
| */ |
| start = get_timer(0); |
| while (readl(&host->reg->status) & DATA_BUSY) { |
| if (get_timer(start) > TIMEOUT_MS) { |
| debug("Controller did not release" |
| "data0 before ciu reset\n"); |
| return -1; |
| } |
| } |
| |
| if (mshci_setbits(host, CTRL_RESET)) { |
| debug("Fail to reset card.\n"); |
| return -1; |
| } |
| if (mshci_setbits(host, FIFO_RESET)) { |
| debug("Fail to reset fifo.\n"); |
| return -1; |
| } |
| if (mshci_setbits(host, DMA_RESET)) { |
| debug("Fail to reset dma.\n"); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| static void mshci_set_mdma_desc(u8 *desc_vir, u8 *desc_phy, |
| unsigned int des0, unsigned int des1, unsigned int des2) |
| { |
| struct mshci_idmac *desc = (struct mshci_idmac *)desc_vir; |
| |
| desc->des0 = des0; |
| desc->des1 = des1; |
| desc->des2 = des2; |
| desc->des3 = (unsigned int)desc_phy + sizeof(struct mshci_idmac); |
| } |
| |
| /* |
| * Prepare the data to be transfer |
| * |
| * @param host pointer to mshci_host |
| * @param data pointer to mmc_data |
| * |
| * Return 0 if success else -1 |
| */ |
| static int mshci_prepare_data(struct mshci_host *host, struct mmc_data *data) |
| { |
| unsigned int i; |
| unsigned int data_cnt; |
| unsigned int des_flag; |
| unsigned int blksz; |
| ulong data_start, data_end; |
| static struct mshci_idmac idmac_desc[0x10000]; |
| /* TODO(alim.akhtar@samsung.com): do we really need this big array? */ |
| |
| struct mshci_idmac *pdesc_dmac; |
| |
| if (mshci_setbits(host, FIFO_RESET)) { |
| debug("Fail to reset FIFO\n"); |
| return -1; |
| } |
| |
| pdesc_dmac = idmac_desc; |
| blksz = data->blocksize; |
| data_cnt = data->blocks; |
| |
| for (i = 0;; i++) { |
| des_flag = (MSHCI_IDMAC_OWN | MSHCI_IDMAC_CH); |
| des_flag |= (i == 0) ? MSHCI_IDMAC_FS : 0; |
| if (data_cnt <= 8) { |
| des_flag |= MSHCI_IDMAC_LD; |
| mshci_set_mdma_desc((u8 *)pdesc_dmac, |
| (u8 *)virt_to_phys(pdesc_dmac), |
| des_flag, blksz * data_cnt, |
| (unsigned int)(virt_to_phys(data->dest)) + |
| (unsigned int)(i * 0x1000)); |
| break; |
| } |
| /* max transfer size is 4KB per descriptor */ |
| mshci_set_mdma_desc((u8 *)pdesc_dmac, |
| (u8 *)virt_to_phys(pdesc_dmac), |
| des_flag, blksz * 8, |
| virt_to_phys(data->dest) + |
| (unsigned int)(i * 0x1000)); |
| |
| data_cnt -= 8; |
| pdesc_dmac++; |
| } |
| |
| data_start = (ulong)idmac_desc; |
| data_end = (ulong)pdesc_dmac; |
| flush_dcache_range(data_start, data_end + ARCH_DMA_MINALIGN); |
| |
| data_start = (ulong)data->dest; |
| data_end = (ulong)(data->dest + data->blocks * data->blocksize); |
| flush_dcache_range(data_start, data_end); |
| |
| writel((unsigned int)virt_to_phys(idmac_desc), &host->reg->dbaddr); |
| |
| /* enable the Internal DMA Controller */ |
| setbits_le32(&host->reg->ctrl, ENABLE_IDMAC | DMA_ENABLE); |
| setbits_le32(&host->reg->bmod, BMOD_IDMAC_ENABLE | BMOD_IDMAC_FB); |
| |
| writel(data->blocksize, &host->reg->blksiz); |
| writel(data->blocksize * data->blocks, &host->reg->bytcnt); |
| |
| return 0; |
| } |
| |
| static int mshci_set_transfer_mode(struct mshci_host *host, |
| struct mmc_data *data) |
| { |
| int mode = CMD_DATA_EXP_BIT; |
| |
| if (data->blocks > 1) |
| mode |= CMD_SENT_AUTO_STOP_BIT; |
| if (data->flags & MMC_DATA_WRITE) |
| mode |= CMD_RW_BIT; |
| |
| return mode; |
| } |
| |
| /* |
| * Sends a command out on the bus. |
| * |
| * @param mmc mmc device |
| * @param cmd mmc_cmd to be sent on bus |
| * @param data mmc data to be sent (optional) |
| * |
| * @return return 0 if ok, else -1 |
| */ |
| static int s5p_mshci_send_command(struct mmc *mmc, struct mmc_cmd *cmd, |
| struct mmc_data *data) |
| { |
| struct mshci_host *host = mmc->priv; |
| |
| int flags = 0, i; |
| unsigned int mask; |
| ulong start, data_start, data_end; |
| |
| /* |
| * If auto stop is enabled in the control register, ignore STOP |
| * command, as controller will internally send a STOP command after |
| * every block read/write |
| */ |
| if ((readl(&host->reg->ctrl) & SEND_AS_CCSD) && |
| (cmd->cmdidx == MMC_CMD_STOP_TRANSMISSION)) |
| return 0; |
| |
| /* |
| * We shouldn't wait for data inihibit for stop commands, even |
| * though they might use busy signaling |
| */ |
| start = get_timer(0); |
| while (readl(&host->reg->status) & DATA_BUSY) { |
| if (get_timer(start) > COMMAND_TIMEOUT) { |
| debug("timeout on data busy\n"); |
| return -1; |
| } |
| } |
| |
| if ((readl(&host->reg->rintsts) & (INTMSK_CDONE | INTMSK_ACD)) == 0) |
| debug("there are pending interrupts 0x%x\n", |
| readl(&host->reg->rintsts)); |
| |
| /* It clears all pending interrupts before sending a command*/ |
| writel(INTMSK_ALL, &host->reg->rintsts); |
| |
| if (data) { |
| if (mshci_prepare_data(host, data)) { |
| debug("fail to prepare data\n"); |
| return -1; |
| } |
| } |
| |
| writel(cmd->cmdarg, &host->reg->cmdarg); |
| |
| if (data) |
| flags = mshci_set_transfer_mode(host, data); |
| |
| if ((cmd->resp_type & MMC_RSP_136) && (cmd->resp_type & MMC_RSP_BUSY)) { |
| /* this is out of SD spec */ |
| debug("wrong response type or response busy for cmd %d\n", |
| cmd->cmdidx); |
| return -1; |
| } |
| |
| if (cmd->resp_type & MMC_RSP_PRESENT) { |
| flags |= CMD_RESP_EXP_BIT; |
| if (cmd->resp_type & MMC_RSP_136) |
| flags |= CMD_RESP_LENGTH_BIT; |
| } |
| |
| if (cmd->resp_type & MMC_RSP_CRC) |
| flags |= CMD_CHECK_CRC_BIT; |
| flags |= (cmd->cmdidx | CMD_STRT_BIT | CMD_USE_HOLD_REG | |
| CMD_WAIT_PRV_DAT_BIT); |
| |
| mask = readl(&host->reg->cmd); |
| if (mask & CMD_STRT_BIT) { |
| debug("cmd busy, current cmd: %d", cmd->cmdidx); |
| return -1; |
| } |
| |
| writel(flags, &host->reg->cmd); |
| /* wait for command complete by busy waiting. */ |
| for (i = 0; i < COMMAND_TIMEOUT; i++) { |
| mask = readl(&host->reg->rintsts); |
| if (mask & INTMSK_CDONE) { |
| if (!data) |
| writel(mask, &host->reg->rintsts); |
| break; |
| } |
| } |
| /* timeout for command complete. */ |
| if (COMMAND_TIMEOUT == i) { |
| debug("timeout waiting for status update\n"); |
| return TIMEOUT; |
| } |
| |
| if (mask & INTMSK_RTO) |
| return TIMEOUT; |
| else if (mask & INTMSK_RE) { |
| debug("response error: 0x%x cmd: %d\n", mask, cmd->cmdidx); |
| return -1; |
| } |
| if (cmd->resp_type & MMC_RSP_PRESENT) { |
| if (cmd->resp_type & MMC_RSP_136) { |
| /* CRC is stripped so we need to do some shifting. */ |
| cmd->response[0] = readl(&host->reg->resp3); |
| cmd->response[1] = readl(&host->reg->resp2); |
| cmd->response[2] = readl(&host->reg->resp1); |
| cmd->response[3] = readl(&host->reg->resp0); |
| } else { |
| cmd->response[0] = readl(&host->reg->resp0); |
| debug("\tcmd->response[0]: 0x%08x\n", cmd->response[0]); |
| } |
| } |
| |
| if (data) { |
| start = get_timer(0); |
| while (!(mask & (DATA_ERR | DATA_TOUT | INTMSK_DTO))) { |
| mask = readl(&host->reg->rintsts); |
| if (get_timer(start) > COMMAND_TIMEOUT) { |
| debug("timeout on data error\n"); |
| return -1; |
| } |
| } |
| writel(mask, &host->reg->rintsts); |
| if (data->flags & MMC_DATA_READ) { |
| data_start = (ulong)data->dest; |
| data_end = (ulong)data->dest + |
| data->blocks * data->blocksize; |
| invalidate_dcache_range(data_start, data_end); |
| } |
| /* make sure disable IDMAC and IDMAC_Interrupts */ |
| clrbits_le32(&host->reg->ctrl, DMA_ENABLE | ENABLE_IDMAC); |
| if (mask & (DATA_ERR | DATA_TOUT)) { |
| debug("error during transfer: 0x%x\n", mask); |
| /* mask all interrupt source of IDMAC */ |
| writel(0, &host->reg->idinten); |
| return -1; |
| } else if (mask & INTMSK_DTO) { |
| debug("mshci dma interrupt end\n"); |
| } else { |
| debug("unexpected condition 0x%x\n", mask); |
| return -1; |
| } |
| clrbits_le32(&host->reg->ctrl, DMA_ENABLE | ENABLE_IDMAC); |
| /* mask all interrupt source of IDMAC */ |
| writel(0, &host->reg->idinten); |
| } |
| |
| /* TODO(alim.akhtar@samsung.com): check why we need this delay */ |
| udelay(100); |
| |
| return 0; |
| } |
| |
| /* |
| * ON/OFF host controller clock |
| * |
| * @param host pointer to mshci_host |
| * @param val to enable/disable clock |
| * |
| * Return 0 if ok else -1 |
| */ |
| static int mshci_clock_onoff(struct mshci_host *host, int val) |
| { |
| ulong start; |
| |
| if (val) |
| writel(CLK_ENABLE, &host->reg->clkena); |
| else |
| writel(CLK_DISABLE, &host->reg->clkena); |
| |
| writel(0, &host->reg->cmd); |
| writel(CMD_ONLY_CLK, &host->reg->cmd); |
| |
| /* |
| * wait till command is taken by CIU, when this bit is set |
| * host should not attempted to write to any command registers. |
| */ |
| start = get_timer(0); |
| while (readl(&host->reg->cmd) & CMD_STRT_BIT) { |
| if (get_timer(start) > COMMAND_TIMEOUT) { |
| debug("Clock %s has failed.\n ", val ? "ON" : "OFF"); |
| return -1; |
| } |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * change host controller clock |
| * |
| * @param host pointer to mshci_host |
| * @param clock request clock |
| * |
| * Return 0 if ok else -1 |
| */ |
| static int mshci_change_clock(struct mshci_host *host, uint clock) |
| { |
| int div; |
| u32 sclk_mshc; |
| ulong start; |
| |
| if (clock == host->clock) |
| return 0; |
| |
| /* If Input clock is higher than maximum mshc clock */ |
| if (clock > MAX_MSHCI_CLOCK) { |
| debug("Input clock is too high\n"); |
| clock = MAX_MSHCI_CLOCK; |
| } |
| |
| /* disable the clock before changing it */ |
| if (mshci_clock_onoff(host, CLK_DISABLE)) { |
| debug("failed to DISABLE clock\n"); |
| return -1; |
| } |
| |
| /* get the clock division */ |
| sclk_mshc = clock_get_periph_rate(host->peripheral); |
| |
| /* CLKDIV */ |
| for (div = 1 ; div <= 0xff; div++) { |
| if (((sclk_mshc / 4) / (2 * div)) <= clock) { |
| writel(div, &host->reg->clkdiv); |
| break; |
| } |
| } |
| |
| writel(div, &host->reg->clkdiv); |
| |
| writel(0, &host->reg->cmd); |
| writel(CMD_ONLY_CLK, &host->reg->cmd); |
| |
| /* |
| * wait till command is taken by CIU, when this bit is set |
| * host should not attempted to write to any command registers. |
| */ |
| start = get_timer(0); |
| while (readl(&host->reg->cmd) & CMD_STRT_BIT) { |
| if (get_timer(start) > COMMAND_TIMEOUT) { |
| debug("Changing clock has timed out.\n"); |
| return -1; |
| } |
| } |
| |
| clrbits_le32(&host->reg->cmd, CMD_SEND_CLK_ONLY); |
| |
| if (mshci_clock_onoff(host, CLK_ENABLE)) { |
| debug("failed to ENABLE clock\n"); |
| return -1; |
| } |
| |
| host->clock = clock; |
| |
| return 0; |
| } |
| |
| /* |
| * Set ios for host controller clock |
| * |
| * This sets the card bus width and clksel |
| */ |
| static void s5p_mshci_set_ios(struct mmc *mmc) |
| { |
| struct mshci_host *host = mmc->priv; |
| |
| debug("bus_width: %x, clock: %d\n", mmc->bus_width, mmc->clock); |
| |
| if (mmc->clock > 0 && mshci_change_clock(host, mmc->clock)) |
| debug("mshci_change_clock failed\n"); |
| |
| if (mmc->bus_width == 8) |
| writel(PORT0_CARD_WIDTH8, &host->reg->ctype); |
| else if (mmc->bus_width == 4) |
| writel(PORT0_CARD_WIDTH4, &host->reg->ctype); |
| else |
| writel(PORT0_CARD_WIDTH1, &host->reg->ctype); |
| |
| if (host->peripheral == PERIPH_ID_SDMMC0) |
| writel(0x03030001, &host->reg->clksel); |
| if (host->peripheral == PERIPH_ID_SDMMC2) |
| writel(0x03020001, &host->reg->clksel); |
| } |
| |
| /* |
| * Fifo init for host controller |
| */ |
| static void mshci_fifo_init(struct mshci_host *host) |
| { |
| int fifo_val, fifo_depth, fifo_threshold; |
| |
| fifo_val = readl(&host->reg->fifoth); |
| |
| fifo_depth = 0x80; |
| fifo_threshold = fifo_depth / 2; |
| |
| fifo_val &= ~(RX_WMARK | TX_WMARK | MSIZE_MASK); |
| fifo_val |= (fifo_threshold | (fifo_threshold << 16) | MSIZE_8); |
| writel(fifo_val, &host->reg->fifoth); |
| } |
| |
| /* |
| * MSHCI host controller initiallization |
| * |
| * @param host pointer to mshci_host |
| * |
| * Return 0 if ok else -1 |
| */ |
| static int mshci_init(struct mshci_host *host) |
| { |
| /* power on the card */ |
| writel(POWER_ENABLE, &host->reg->pwren); |
| |
| if (mshci_reset_all(host)) { |
| debug("mshci_reset_all() failed\n"); |
| return -1; |
| } |
| |
| mshci_fifo_init(host); |
| |
| /* clear all pending interrupts */ |
| writel(INTMSK_ALL, &host->reg->rintsts); |
| |
| /* interrupts are not used, disable all */ |
| writel(0, &host->reg->intmask); |
| |
| return 0; |
| } |
| |
| static int s5p_mphci_init(struct mmc *mmc) |
| { |
| struct mshci_host *host = (struct mshci_host *)mmc->priv; |
| unsigned int ier; |
| |
| if (mshci_init(host)) { |
| debug("mshci_init() failed\n"); |
| return -1; |
| } |
| |
| /* enumerate at 400KHz */ |
| if (mshci_change_clock(host, 400000)) { |
| debug("mshci_change_clock failed\n"); |
| return -1; |
| } |
| |
| /* set auto stop command */ |
| ier = readl(&host->reg->ctrl); |
| ier |= SEND_AS_CCSD; |
| writel(ier, &host->reg->ctrl); |
| |
| /* set 1bit card mode */ |
| writel(PORT0_CARD_WIDTH1, &host->reg->ctype); |
| |
| writel(0xfffff, &host->reg->debnce); |
| |
| /* set bus mode register for IDMAC */ |
| writel(BMOD_IDMAC_RESET, &host->reg->bmod); |
| |
| writel(0x0, &host->reg->idinten); |
| |
| /* set the max timeout for data and response */ |
| writel(TMOUT_MAX, &host->reg->tmout); |
| |
| return 0; |
| } |
| |
| static int s5p_mshci_initialize(struct fdt_mshci *config) |
| { |
| struct mshci_host *mmc_host; |
| struct mmc *mmc; |
| |
| if (num_devs == MAX_MMC_HOSTS) { |
| debug("%s: Too many hosts\n", __func__); |
| return -1; |
| } |
| |
| /* set the clock for mshci controller */ |
| if (clock_set_mshci(config->periph_id)) { |
| debug("clock_set_mshci failed\n"); |
| return -1; |
| } |
| |
| mmc = &mshci_dev[num_devs]; |
| mmc_host = &mshci_host[num_devs]; |
| |
| sprintf(mmc->name, "S5P MSHC%d", num_devs); |
| num_devs++; |
| |
| mmc->priv = mmc_host; |
| mmc->send_cmd = s5p_mshci_send_command; |
| mmc->set_ios = s5p_mshci_set_ios; |
| mmc->init = s5p_mphci_init; |
| |
| mmc->voltages = MMC_VDD_32_33 | MMC_VDD_33_34; |
| mmc->host_caps = MMC_MODE_HS_52MHz | MMC_MODE_HS | MMC_MODE_HC; |
| |
| if (config->bus_width == 8) |
| mmc->host_caps |= MMC_MODE_8BIT; |
| else |
| mmc->host_caps |= MMC_MODE_4BIT; |
| |
| mmc->f_min = MIN_MSHCI_CLOCK; |
| mmc->f_max = MAX_MSHCI_CLOCK; |
| |
| exynos_pinmux_config(config->periph_id, |
| config->bus_width == 8 ? PINMUX_FLAG_8BIT_MODE : 0); |
| fdtdec_setup_gpio(&config->enable_gpio); |
| if (fdt_gpio_isvalid(&config->enable_gpio)) { |
| int pin = config->enable_gpio.gpio; |
| int err; |
| |
| err = gpio_direction_output(pin, 1); /* Power on */ |
| if (err) { |
| debug("%s: Unable to power on MSHCI\n", __func__); |
| return -1; |
| } |
| gpio_set_pull(pin, EXYNOS_GPIO_PULL_NONE); |
| gpio_set_drv(pin, EXYNOS_GPIO_DRV_4X); |
| } |
| mmc_host->clock = 0; |
| mmc_host->reg = config->reg; |
| mmc_host->peripheral = config->periph_id; |
| mmc_register(mmc); |
| mmc->block_dev.removable = config->removable; |
| debug("s5p_mshci: periph_id=%d, width=%d, reg=%p, enable=%d\n", |
| config->periph_id, config->bus_width, config->reg, |
| config->enable_gpio.gpio); |
| |
| return 0; |
| } |
| |
| #ifdef CONFIG_OF_CONTROL |
| int fdtdec_decode_mshci(const void *blob, int node, struct fdt_mshci *config) |
| { |
| config->bus_width = fdtdec_get_int(blob, node, |
| "samsung,mshci-bus-width", 4); |
| |
| config->reg = (struct s5p_mshci *)fdtdec_get_addr(blob, node, "reg"); |
| if ((fdt_addr_t)config->reg == FDT_ADDR_T_NONE) |
| return -FDT_ERR_NOTFOUND; |
| config->periph_id = clock_decode_periph_id(blob, node); |
| fdtdec_decode_gpio(blob, node, "enable-gpios", &config->enable_gpio); |
| |
| config->removable = fdtdec_get_bool(blob, node, "samsung,removable"); |
| |
| return 0; |
| } |
| #endif |
| |
| int s5p_mshci_init(const void *blob) |
| { |
| struct fdt_mshci config; |
| int ret = 0; |
| |
| #ifdef CONFIG_OF_CONTROL |
| int node_list[MAX_MMC_HOSTS]; |
| int node, i; |
| int count; |
| |
| count = fdtdec_find_aliases_for_id(blob, "sdmmc", |
| COMPAT_SAMSUNG_EXYNOS5_MSHCI, node_list, |
| MAX_MMC_HOSTS); |
| debug("%s: %d nodes\n", __func__, count); |
| for (i = 0; i < count; i++) { |
| node = node_list[i]; |
| |
| if (node < 0) |
| continue; |
| |
| if (fdtdec_decode_mshci(blob, node, &config)) |
| return -1; |
| |
| /* TODO(sjg): Move to using peripheral IDs in this driver */ |
| if (s5p_mshci_initialize(&config)) { |
| debug("%s: Failed to init MSHCI %d\n", __func__, i); |
| ret = -1; |
| continue; |
| } |
| } |
| #else |
| config.width = CONFIG_MSHCI_BUS_WIDTH; |
| config.reg = (struct s5p_mshci *)samsung_get_base_mshci(); |
| config.periph_id = CONFIG_MSHCI_PERIPH_ID; |
| config.removable = 1; |
| if (s5p_mshci_initialize(&config) { |
| debug("%s: Failed to init MSHCI %d\n", __func__, i); |
| ret = -1; |
| continue; |
| } |
| #endif |
| return ret; |
| } |