4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / sfc.c C
// SPDX-License-Identifier: Apache-2.0
/* Copyright 2013-2014 IBM Corp. */

/* Code shamelessly stolen from skiboot and then hacked to death */

#define _GNU_SOURCE
#include "ast.h"
#include "log.h"
#include "sfc.h"

#include "ccan/container_of/container_of.h"

#include <errno.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#ifndef __unused
#define __unused __attribute__((unused))
#endif

#define CALIBRATE_BUF_SIZE	16384

#define SFC_ERR(fmt, ...) loge(fmt, ##__VA_ARGS__)
#define SFC_INF(fmt, ...) logi(fmt, ##__VA_ARGS__)
#define SFC_DBG(fmt, ...) logd(fmt, ##__VA_ARGS__)

struct sfc_data {
    struct ahb *ahb;

    /* We have 2 controllers, one for the BMC flash, one for the PNOR */
    uint8_t    type;

    uint32_t type_reg;

    /* Address and previous value of the ctrl register */
    uint32_t ctl_reg;

    /* Control register value for normal commands */
    uint32_t ctl_val;

    /* Control register value for (fast) reads */
    uint32_t ctl_read_val;

    /* Flash read timing register  */
    uint32_t fread_timing_reg;
    uint32_t fread_timing_val;

    /* Address of the flash mapping */
    uint32_t flash;

    /* Current 4b mode */
    bool mode_4b;

    /* Callbacks */
    struct sfc ops;
};

static uint32_t ast_ahb_freq;

static const uint32_t ast_ct_hclk_divs[] = {
    0xf, /* HCLK */
    0x7, /* HCLK/2 */
    0xe, /* HCLK/3 */
    0x6, /* HCLK/4 */
    0xd, /* HCLK/5 */
};

static void fl_micron_status(struct sfc *ct)
{
    uint8_t flst;

    /*
     * After a success status on a write or erase, we
     * need to do that command or some chip variants will
     * lock
     */
    ct->cmd_rd(ct, CMD_MIC_RDFLST, false, 0, &flst, 1);
}

static int fl_read_stat(struct sfc *ct, uint8_t *stat)
{
    return ct->cmd_rd(ct, CMD_RDSR, false, 0, stat, 1);
}

/* Synchronous write completion, probably need a yield hook */
static int fl_sync_wait_idle(struct sfc *ct)
{
    uint8_t stat;
    int rc;

    /* XXX Add timeout */
    for (;;) {
	rc = fl_read_stat(ct, &stat);
	if (rc) return rc;
	if (!(stat & STAT_WIP)) {
	    if (ct->finfo->flags & FL_MICRON_BUGS)
		fl_micron_status(ct);
	    return 0;
	}
	usleep(100);
    }
    /* return FLASH_ERR_WIP_TIMEOUT; */
}

/* Exported for internal use */
static int fl_wren(struct sfc *ct)
{
    int i, rc;
    uint8_t stat;

    /* Some flashes need it to be hammered */
    for (i = 0; i < 1000; i++) {
	rc = ct->cmd_wr(ct, CMD_WREN, false, 0, NULL, 0);
	if (rc) return rc;
	rc = fl_read_stat(ct, &stat);
	if (rc) return rc;
	if (stat & STAT_WIP) {
	    SFC_ERR("LIBFLASH: WREN has WIP status set !\n");
	    rc = fl_sync_wait_idle(ct);
	    if (rc)
		return rc;
	    continue;
	}
	if (stat & STAT_WEN)
	    return 0;
    }
    return -ETIMEDOUT;
}

static int sfc_start_cmd(struct sfc_data *ct, uint8_t cmd)
{
    int rc;

    /* Switch to user mode, CE# dropped */
    rc = ahb_writel(ct->ahb, ct->ctl_reg, ct->ctl_val | 7);
    if (rc < 0)
	return rc;

    /* user mode, CE# active */
    rc = ahb_writel(ct->ahb, ct->ctl_reg, ct->ctl_val | 3);
    if (rc < 0)
	return rc;

    /* write cmd */
    rc = ahb_write(ct->ahb, ct->flash, &cmd, 1);

    return rc == 1 ? 0 : rc;
}

static void sfc_end_cmd(struct sfc_data *ct)
{
    int rc;

    /* clear CE# */
    rc = ahb_writel(ct->ahb, ct->ctl_reg, ct->ctl_val | 7);
    if (rc < 0) { errno = -rc; perror("ahb_writel"); return; }

    /* Switch back to read mode */
    rc = ahb_writel(ct->ahb, ct->ctl_reg, ct->ctl_read_val);
    if (rc < 0) { errno = -rc; perror("ahb_writel"); return; }
}

static int sfc_send_addr(struct sfc_data *ct, uint32_t addr)
{
    const void *ap;
    int rc, len;

    /* Layout address MSB first in memory */
    addr = htobe32(addr);

    /* Send the right amount of bytes */
    ap = (char *)&addr;

    if (ct->mode_4b) {
	len = 4;
	rc = ahb_write(ct->ahb, ct->flash, ap, len);
    } else {
	len = 3;
	rc = ahb_write(ct->ahb, ct->flash, ap + 1, len);
    }

    return rc == len ? 0 : rc;
}

static int sfc_cmd_rd(struct sfc *ctrl, uint8_t cmd,
			 bool has_addr, uint32_t addr, void *buffer,
			 uint32_t size)
{
    struct sfc_data *ct = container_of(ctrl, struct sfc_data, ops);
    int rc;

    rc = sfc_start_cmd(ct, cmd);
    if (rc)
	goto bail;
    if (has_addr) {
	rc = sfc_send_addr(ct, addr);
	if (rc)
	    goto bail;
    }
    if (buffer && size) {
	rc = ahb_read(ct->ahb, ct->flash, buffer, size);
	if (rc < 0)
	    goto bail;
	rc = 0;
    }

bail:
    sfc_end_cmd(ct);

    return rc;
}

static int sfc_cmd_wr(struct sfc *ctrl, uint8_t cmd,
			 bool has_addr, uint32_t addr, const void *buffer,
			 uint32_t size)
{
    struct sfc_data *ct = container_of(ctrl, struct sfc_data, ops);
    int rc;

    rc = sfc_start_cmd(ct, cmd);
    if (rc)
	goto bail;
    if (has_addr) {
	rc = sfc_send_addr(ct, addr);
	if (rc)
	    goto bail;
    }
    if (buffer && size)
	rc = ahb_write(ct->ahb, ct->flash, buffer, size);
bail:
    sfc_end_cmd(ct);

    return rc == size ? 0 : rc;
}

static int sfc_set_4b(struct sfc *ctrl, bool enable)
{
    struct sfc_data *ct = container_of(ctrl, struct sfc_data, ops);
    uint32_t ce_ctrl = 0;
    int rc;

    if (ct->type == SFC_TYPE_FMC && ct->ops.finfo->size > 0x1000000) {
	rc = ahb_readl(ct->ahb, AST_G5_FMC | FMC_CE_CTRL, &ce_ctrl);
	if (rc < 0)
	    return rc;
    } else if (ct->type != SFC_TYPE_SMC)
	return enable ? -EIO : 0;

    /*
     * We update the "old" value as well since when quitting
     * we don't restore the mode of the flash itself so we need
     * to leave the controller in a compatible setup
     */
    if (enable) {
	ct->ctl_val |= 0x2000;
	ct->ctl_read_val |= 0x2000;
	ce_ctrl |= 0x1;
    } else {
	ct->ctl_val &= ~0x2000;
	ct->ctl_read_val &= ~0x2000;
	ce_ctrl &= ~0x1;
    }
    ct->mode_4b = enable;

    /* Update read mode */
    rc = ahb_writel(ct->ahb, ct->ctl_reg, ct->ctl_read_val);
    if (rc < 0)
	return rc;

    if (ce_ctrl && ct->type == SFC_TYPE_FMC)
	return ahb_writel(ct->ahb, AST_G5_FMC | FMC_CE_CTRL, ce_ctrl);

    return 0;
}

static int sfc_read(struct sfc *ctrl, uint32_t pos,
		       void *buf, uint32_t len)
{
    ssize_t rc;

    struct sfc_data *ct = container_of(ctrl, struct sfc_data, ops);

    /*
     * We are in read mode by default. We don't yet support fancy
     * things like fast read or X2 mode
     */
    rc = ahb_read(ct->ahb, ct->flash + pos, buf, len);

    return rc == len ? 0 : rc;
}

static void ast_get_ahb_freq(struct sfc_data *ct)
{
    static const uint32_t cpu_freqs_24_48[] = {
	384000000,
	360000000,
	336000000,
	408000000
    };
    static const uint32_t cpu_freqs_25[] = {
	400000000,
	375000000,
	350000000,
	425000000
    };
    static const uint32_t ahb_div[] = { 1, 2, 4, 3 };
    uint32_t strap, cpu_clk, div;
    int rc;

    if (ast_ahb_freq)
	return;

    /* HW strapping gives us the CPU freq and AHB divisor */
    rc = ahb_readl(ct->ahb, AST_G5_SCU | SCU_HW_STRAP, &strap);
    if (rc < 0) { errno = -rc; perror("ahb_readl"); return; }

    if (strap & 0x00800000) {
	SFC_INF("AST: CLKIN 25Mhz\n");
	cpu_clk = cpu_freqs_25[(strap >> 8) & 3];
    } else {
	SFC_INF("AST: CLKIN 24/48Mhz\n");
	cpu_clk = cpu_freqs_24_48[(strap >> 8) & 3];
    }
    SFC_INF("AST: CPU frequency: %d Mhz\n", cpu_clk / 1000000);
    div = ahb_div[(strap >> 10) & 3];
    ast_ahb_freq = cpu_clk / div;
    SFC_INF("AST: AHB frequency: %d Mhz\n", ast_ahb_freq / 1000000);
}

static int sfc_check_reads(struct sfc_data *ct,
			      const uint8_t *golden_buf, uint8_t *test_buf)
{
    int i, rc;

    for (i = 0; i < 10; i++) {
	rc = ahb_read(ct->ahb, ct->flash, test_buf, CALIBRATE_BUF_SIZE);
	if (rc)
	    return rc;
	if (memcmp(test_buf, golden_buf, CALIBRATE_BUF_SIZE) != 0)
	    return -EREMOTEIO;
    }
    return 0;
}

static int sfc_calibrate_reads(struct sfc_data *ct, uint32_t hdiv,
				  const uint8_t *golden_buf, uint8_t *test_buf)
{
    int i, rc;
    int good_pass = -1, pass_count = 0;
    uint32_t shift = (hdiv - 1) << 2;
    uint32_t mask = ~(0xfu << shift);

#define FREAD_TPASS(i)	(((i) / 2) | (((i) & 1) ? 0 : 8))

    /* Try HCLK delay 0..5, each one with/without delay and look for a
     * good pair.
     */
    for (i = 0; i < 12; i++) {
	bool pass;

	ct->fread_timing_val &= mask;
	ct->fread_timing_val |= FREAD_TPASS(i) << shift;
	rc = ahb_writel(ct->ahb, ct->fread_timing_reg, ct->fread_timing_val);
	if (rc < 0)
	    return rc;

	rc = sfc_check_reads(ct, golden_buf, test_buf);
	if (rc && rc != -EREMOTEIO)
	    return rc;
	pass = (rc == 0);
	SFC_DBG("  * [%08x] %d HCLK delay, %dns DI delay : %s\n",
		ct->fread_timing_val, i/2, (i & 1) ? 0 : 4, pass ? "PASS" : "FAIL");
	if (pass) {
	    pass_count++;
	    if (pass_count == 3) {
		good_pass = i - 1;
		break;
	    }
	} else
	    pass_count = 0;
    }

    /* No good setting for this frequency */
    if (good_pass < 0)
	return -EREMOTEIO;

    /* We have at least one pass of margin, let's use first pass */
    ct->fread_timing_val &= mask;
    ct->fread_timing_val |= FREAD_TPASS(good_pass) << shift;
    rc = ahb_writel(ct->ahb, ct->fread_timing_reg, ct->fread_timing_val);
    if (rc < 0)
	return rc;

    SFC_DBG("AST:  * -> good is pass %d [0x%08x]\n",
	    good_pass, ct->fread_timing_val);
    return 0;
}

static bool ast_calib_data_usable(const uint8_t *test_buf, uint32_t size)
{
    const uint32_t *tb32 = (const uint32_t *)test_buf;
    uint32_t i, cnt = 0;

    /* We check if we have enough words that are neither all 0
     * nor all 1's so the calibration can be considered valid.
     *
     * I use an arbitrary threshold for now of 64
     */
    size >>= 2;
    for (i = 0; i < size; i++) {
	if (tb32[i] != 0 && tb32[i] != 0xffffffff)
	    cnt++;
    }
    return cnt >= 64;
}

static int sfc_optimize_reads(struct sfc_data *ct,
				 struct flash_info *info __unused,
				 uint32_t max_freq)
{
    uint8_t *golden_buf, *test_buf;
    int i, rc, best_div = -1;
    uint32_t save_read_val = ct->ctl_read_val;

    test_buf = malloc(CALIBRATE_BUF_SIZE * 2);
    golden_buf = test_buf + CALIBRATE_BUF_SIZE;

    /* We start with the dumbest setting and read some data */
    ct->ctl_read_val = (ct->ctl_read_val & 0x2000) |
	(0x00 << 28) | /* Single bit */
	(0x00 << 24) | /* CE# max */
	(0x03 << 16) | /* use normal reads */
	(0x00 <<  8) | /* HCLK/16 */
	(0x00 <<  6) | /* no dummy cycle */
	(0x00);        /* normal read */
    ahb_writel(ct->ahb, ct->ctl_reg, ct->ctl_read_val);

    rc = ahb_read(ct->ahb, ct->flash, golden_buf, CALIBRATE_BUF_SIZE);
    if (rc) {
	free(test_buf);
	return rc;
    }

    /* Establish our read mode with freq field set to 0 */
    ct->ctl_read_val = save_read_val & 0xfffff0ff;

    /* Check if calibration data is suitable */
    if (!ast_calib_data_usable(golden_buf, CALIBRATE_BUF_SIZE)) {
	SFC_DBG("AST: Calibration area too uniform, "
		"using low speed\n");
	rc = ahb_writel(ct->ahb, ct->ctl_reg, ct->ctl_read_val);
	free(test_buf);
	return rc;
    }

    /* Now we iterate the HCLK dividers until we find our breaking point */
    for (i = 5; i > 0; i--) {
	uint32_t tv, freq;

	/* Compare timing to max */
	freq = ast_ahb_freq / i;
	if (freq >= max_freq)
	    continue;

	/* Set the timing */
	tv = ct->ctl_read_val | (ast_ct_hclk_divs[i - 1] << 8);
	rc = ahb_writel(ct->ahb, ct->ctl_reg, tv);
	if (rc < 0) {
	    free(test_buf);
	    return rc;
	}
	SFC_DBG("AST: Trying HCLK/%d...\n", i);
	rc = sfc_calibrate_reads(ct, i, golden_buf, test_buf);

	/* Some other error occurred, bail out */
	if (rc && rc != -EREMOTEIO) {
	    free(test_buf);
	    return rc;
	}
	if (rc == 0)
	    best_div = i;
    }
    free(test_buf);

    /* Nothing found ? */
    if (best_div < 0)
	SFC_INF("AST: No good frequency, using dumb slow\n");
    else {
	SFC_INF("AST: Found good read timings at HCLK/%d\n", best_div);
	ct->ctl_read_val |= (ast_ct_hclk_divs[best_div - 1] << 8);
    }
    return ahb_writel(ct->ahb, ct->ctl_reg, ct->ctl_read_val);
}

static int sfc_get_hclk(uint32_t *ctl_val, uint32_t max_freq)
{
    int i;

    /* It appears that running commands at HCLK/2 on some micron
     * chips results in occasionally reads of bogus status (that
     * or unrelated chip hangs).
     *
     * Since we cannot calibrate properly the reads for commands,
     * instead, let's limit our SPI frequency to HCLK/4 to stay
     * on the safe side of things
     */
#define MIN_CMD_FREQ	4
    for (i = MIN_CMD_FREQ; i <= 5; i++) {
	uint32_t freq = ast_ahb_freq / i;
	if (freq >= max_freq)
	    continue;
	*ctl_val |= (ast_ct_hclk_divs[i - 1] << 8);
	return i;
    }
    return 0;
}

static int sfc_setup_macronix(struct sfc_data *ct, struct flash_info *info)
{
    int rc, div __unused;
    uint8_t srcr[2];

    /*
     * Those Macronix chips support dual reads at 104Mhz
     * and dual IO at 84Mhz with 4 dummies.
     *
     * Our calibration algo should give us something along
     * the lines of HCLK/3 (HCLK/2 seems to work sometimes
     * but appears to be fairly unreliable) which is 64Mhz
     *
     * So we chose dual IO mode.
     *
     * The CE# inactive width for reads must be 7ns, we set it
     * to 3T which is about 15ns at the fastest speed we support
     * HCLK/2) as I've had issue with smaller values.
     *
     * For write and program it's 30ns so let's set the value
     * for normal ops to 6T.
     *
     * Preserve the current 4b mode.
     */
    SFC_DBG("AST: Setting up Macronix...\n");

    /*
     * Read the status and config registers
     */
    rc = sfc_cmd_rd(&ct->ops, CMD_RDSR, false, 0, &srcr[0], 1);
    if (rc != 0) {
	SFC_ERR("AST: Failed to read status\n");
	return rc;
    }
    rc = sfc_cmd_rd(&ct->ops, CMD_RDCR, false, 0, &srcr[1], 1);
    if (rc != 0) {
	SFC_ERR("AST: Failed to read configuration\n");
	return rc;
    }

    SFC_DBG("AST: Macronix SR:CR: 0x%02x:%02x\n", srcr[0], srcr[1]);

    /* Switch to 8 dummy cycles to enable 104Mhz operations */
    srcr[1] = (srcr[1] & 0x3f) | 0x80;

    rc = fl_wren(&ct->ops);
    if (rc) {
	SFC_ERR("AST: Failed to WREN for Macronix config\n");
	return rc;
    }

    rc = sfc_cmd_wr(&ct->ops, CMD_WRSR, false, 0, srcr, 2);
    if (rc != 0) {
	SFC_ERR("AST: Failed to write Macronix config\n");
	return rc;
    }
    rc = fl_sync_wait_idle(&ct->ops);;
    if (rc != 0) {
	SFC_ERR("AST: Failed waiting for config write\n");
	return rc;
    }

    SFC_DBG("AST: Macronix SR:CR: 0x%02x:%02x\n", srcr[0], srcr[1]);

    /* Use 2READ */
    ct->ctl_read_val = (ct->ctl_read_val & 0x2000) |
	(0x03 << 28) | /* Dual IO */
	(0x0d << 24) | /* CE# width 3T */
	(0xbb << 16) | /* 2READ command */
	(0x00 <<  8) | /* HCLK/16 (optimize later) */
	(0x02 <<  6) | /* 2 bytes dummy cycle (8 clocks) */
	(0x01);	       /* fast read */

    /* Configure SPI flash read timing */
    rc = sfc_optimize_reads(ct, info, 104000000);
    if (rc) {
	SFC_ERR("AST: Failed to setup proper read timings, rc=%d\n", rc);
	return rc;
    }

    /*
     * For other commands and writes also increase the SPI clock
     * to HCLK/2 since the chip supports up to 133Mhz and set
     * CE# inactive to 6T. We request a timing that is 20% below
     * the limit of the chip, so about 106Mhz which should fit.
     */
    ct->ctl_val = (ct->ctl_val & 0x2000) |
	(0x00 << 28) | /* Single bit */
	(0x0a << 24) | /* CE# width 6T (b1010) */
	(0x00 << 16) | /* no command */
	(0x00 <<  8) | /* HCLK/16 (done later) */
	(0x00 <<  6) | /* no dummy cycle */
	(0x00);	       /* normal read */

    div = sfc_get_hclk(&ct->ctl_val, 106000000);
    SFC_INF("AST: Command timing set to HCLK/%d\n", div);

    /* Update chip with current read config */
    return ahb_writel(ct->ahb, ct->ctl_reg, ct->ctl_read_val);
}

static int sfc_setup_winbond(struct sfc_data *ct, struct flash_info *info)
{
    int rc, div __unused;

    SFC_DBG("AST: Setting up Windbond...\n");

    /*
     * This Windbond chip support dual reads at 104Mhz
     * with 8 dummy cycles.
     *
     * The CE# inactive width for reads must be 10ns, we set it
     * to 3T which is about 15.6ns.
     */
    ct->ctl_read_val = (ct->ctl_read_val & 0x2000) |
	(0x02 << 28) | /* Dual bit data only */
	(0x0e << 24) | /* CE# width 2T (b1110) */
	(0x3b << 16) | /* DREAD command */
	(0x00 <<  8) | /* HCLK/16 */
	(0x01 <<  6) | /* 1-byte dummy cycle */
	(0x01);	       /* fast read */

    /* Configure SPI flash read timing */
    rc = sfc_optimize_reads(ct, info, 104000000);
    if (rc) {
	SFC_ERR("AST: Failed to setup proper read timings, rc=%d\n", rc);
	return rc;
    }

    /*
     * For other commands and writes also increase the SPI clock
     * to HCLK/2 since the chip supports up to 133Mhz. CE# inactive
     * for write and erase is 50ns so let's set it to 10T.
     */
    ct->ctl_val = (ct->ctl_read_val & 0x2000) |
	(0x00 << 28) | /* Single bit */
	(0x06 << 24) | /* CE# width 10T (b0110) */
	(0x00 << 16) | /* no command */
	(0x00 <<  8) | /* HCLK/16 */
	(0x00 <<  6) | /* no dummy cycle */
	(0x01);	       /* fast read */

    div = sfc_get_hclk(&ct->ctl_val, 106000000);
    SFC_ERR("AST: Command timing set to HCLK/%d\n", div);

    /* Update chip with current read config */
    return ahb_writel(ct->ahb, ct->ctl_reg, ct->ctl_read_val);
}

static int sfc_setup_micron(struct sfc_data *ct, struct flash_info *info)
{
    uint8_t	vconf, ext_id[6];
    int rc, div __unused;

    SFC_DBG("AST: Setting up Micron...\n");

    /*
     * Read the extended chip ID to try to detect old vs. new
     * flashes since old Micron flashes have a lot of issues
     */
    rc = sfc_cmd_rd(&ct->ops, CMD_RDID, false, 0, ext_id, 6);
    if (rc != 0) {
	SFC_ERR("AST: Failed to read Micron ext ID, sticking to dumb speed\n");
	return 0;
    }
    /* Check ID matches expectations */
    if (ext_id[0] != ((info->id >> 16) & 0xff) ||
	    ext_id[1] != ((info->id >>	8) & 0xff) ||
	    ext_id[2] != ((info->id	 ) & 0xff)) {
	SFC_ERR("AST: Micron ext ID mismatch, sticking to dumb speed\n");
	return 0;
    }
    SFC_DBG("AST: Micron ext ID byte: 0x%02x\n", ext_id[4]);

    /* Check for old (<45nm) chips, don't try to be fancy on those */
    if (!(ext_id[4] & 0x40)) {
	SFC_DBG("AST: Old chip, using dumb timings\n");
	goto dumb;
    }

    /*
     * Read the micron specific volatile configuration reg
     */
    rc = sfc_cmd_rd(&ct->ops, CMD_MIC_RDVCONF, false, 0, &vconf, 1);
    if (rc != 0) {
	SFC_ERR("AST: Failed to read Micron vconf, sticking to dumb speed\n");
	goto dumb;
    }
    SFC_DBG("AST: Micron VCONF: 0x%02x\n", vconf);

    /* Switch to 8 dummy cycles (we might be able to operate with 4
     * but let's keep some margin
     */
    vconf = (vconf & 0x0f) | 0x80;

    rc = sfc_cmd_wr(&ct->ops, CMD_MIC_WRVCONF, false, 0, &vconf, 1);
    if (rc != 0) {
	SFC_ERR("AST: Failed to write Micron vconf, "
		" sticking to dumb speed\n");
	goto dumb;
    }
    rc = fl_sync_wait_idle(&ct->ops);;
    if (rc != 0) {
	SFC_ERR("AST: Failed waiting for config write\n");
	return rc;
    }
    SFC_DBG("AST: Updated to  : 0x%02x\n", vconf);

    /*
     * Try to do full dual IO, with 8 dummy cycles it supports 133Mhz
     *
     * The CE# inactive width for reads must be 20ns, we set it
     * to 4T which is about 20.8ns.
     */
    ct->ctl_read_val = (ct->ctl_read_val & 0x2000) |
	(0x03 << 28) | /* Single bit */
	(0x0c << 24) | /* CE# 4T */
	(0xbb << 16) | /* 2READ command */
	(0x00 <<  8) | /* HCLK/16 (optimize later) */
	(0x02 <<  6) | /* 8 dummy cycles (2 bytes) */
	(0x01);	       /* fast read */

    /* Configure SPI flash read timing */
    rc = sfc_optimize_reads(ct, info, 133000000);
    if (rc) {
	SFC_ERR("AST: Failed to setup proper read timings, rc=%d\n", rc);
	return rc;
    }

    /*
     * For other commands and writes also increase the SPI clock
     * to HCLK/2 since the chip supports up to 133Mhz. CE# inactive
     * for write and erase is 50ns so let's set it to 10T.
     */
    ct->ctl_val = (ct->ctl_read_val & 0x2000) |
	(0x00 << 28) | /* Single bit */
	(0x06 << 24) | /* CE# width 10T (b0110) */
	(0x00 << 16) | /* no command */
	(0x00 <<  8) | /* HCLK/16 */
	(0x00 <<  6) | /* no dummy cycle */
	(0x00);	       /* norm read */

    div = sfc_get_hclk(&ct->ctl_val, 133000000);
    SFC_INF("AST: Command timing set to HCLK/%d\n", div);

    /* Update chip with current read config */
    return ahb_writel(ct->ahb, ct->ctl_reg, ct->ctl_read_val);

dumb:
    ct->ctl_val = ct->ctl_read_val = (ct->ctl_read_val & 0x2000) |
	(0x00 << 28) | /* Single bit */
	(0x00 << 24) | /* CE# max */
	(0x03 << 16) | /* use normal reads */
	(0x06 <<  8) | /* HCLK/4 */
	(0x00 <<  6) | /* no dummy cycle */
	(0x00);	       /* normal read */

    /* Update chip with current read config */
    return ahb_writel(ct->ahb, ct->ctl_reg, ct->ctl_read_val);
}

static int sfc_setup(struct sfc *ctrl, uint32_t *tsize)
{
    struct sfc_data *ct = container_of(ctrl, struct sfc_data, ops);
    struct flash_info *info = ctrl->finfo;

    (void)tsize;

    return 0;

    /*
     * Configure better timings and read mode for known
     * flash chips
     */
    switch(info->id) {
    case 0xc22018: /* MX25L12835F */
    case 0xc22019: /* MX25L25635F */
    case 0xc2201a: /* MX66L51235F */
    case 0xc2201b: /* MX66L1G45G */
	return sfc_setup_macronix(ct, info);
    case 0xef4018: /* W25Q128BV */
	return sfc_setup_winbond(ct, info);
    case 0x20ba20: /* MT25Qx512xx */
	return sfc_setup_micron(ct, info);
    }
    /* No special tuning */
    return 0;
}

static bool sfc_init_device(struct sfc_data *ct)
{
    uint32_t ce_type;
    int rc;

    /*
     * Snapshot control reg and sanitize it for our
     * use, switching to 1-bit mode, clearing user
     * mode if set, etc...
     *
     * Also configure SPI clock to something safe
     * like HCLK/8 (24Mhz)
     */
    rc = ahb_readl(ct->ahb, ct->ctl_reg, &ct->ctl_val);
    if (rc < 0 || ct->ctl_val == 0xffffffff) {
	SFC_ERR("AST_SF: Failed read from controller control\n");
	return false;
    }

    /* Enable writes for user mode */
    rc = ahb_readl(ct->ahb, ct->type_reg, &ce_type);
    if (rc < 0) { errno = -rc; perror("ahb_readl"); return false; }

    rc = ahb_writel(ct->ahb, ct->type_reg, ce_type | (7 << 16));
    if (rc < 0) { errno = -rc; perror("ahb_writel"); return false; }

    ct->ctl_val =
	(0x00 << 28) | /* Single bit */
	(0x00 << 24) | /* CE# width 16T */
	(0x00 << 16) | /* no command */
	(0x04 <<  8) | /* HCLK/8 */
	(0x00 <<  6) | /* no dummy cycle */
	(0x00);	       /* normal read */

    /* Initial read mode is default */
    ct->ctl_read_val = ct->ctl_val;

    /* Initial read timings all 0 */
    ct->fread_timing_val = 0;

    /* Configure for read */
    rc = ahb_writel(ct->ahb, ct->ctl_reg, ct->ctl_read_val);
    if (rc < 0) { errno = -rc; perror("ahb_writel"); return false; }
    rc = ahb_writel(ct->ahb, ct->fread_timing_reg, ct->fread_timing_val);
    if (rc < 0) { errno = -rc; perror("ahb_writel"); return false; }

    ct->mode_4b = false;

    return true;
}

int sfc_init(struct sfc **ctrl, struct ahb *ahb, uint8_t type)
{
    struct sfc_data *ct;

    if (!(type == SFC_TYPE_SMC || type == SFC_TYPE_FMC))
	return -EINVAL;

    *ctrl = NULL;
    ct = malloc(sizeof(*ct));
    if (!ct) {
	SFC_ERR("AST_SF: Failed to allocate\n");
	return -ENOMEM;
    }
    memset(ct, 0, sizeof(*ct));
    ct->ahb = ahb;
    ct->type = type;

    /* XXX: Hack exposing the AHB instance used to the flash layer */
    ct->ops.priv = ahb;

    ct->ops.cmd_wr = sfc_cmd_wr;
    ct->ops.cmd_rd = sfc_cmd_rd;
    ct->ops.set_4b = sfc_set_4b;
    ct->ops.read = sfc_read;
    ct->ops.setup = sfc_setup;

    ast_get_ahb_freq(ct);

    if (type == SFC_TYPE_SMC) {
	ct->type_reg = AST_G5_SMC | FMC_CE_TYPE;
	ct->ctl_reg = AST_G5_SMC | SMC_CE0_CTRL;
	ct->fread_timing_reg = AST_G5_SMC | SMC_TIMING;
	ct->flash = AST_G5_HOST_FLASH;
    } else if (type == SFC_TYPE_FMC) {
	ct->type_reg = AST_G5_FMC | FMC_CE_TYPE;
	ct->ctl_reg = AST_G5_FMC | FMC_CE0_CTRL;
	ct->fread_timing_reg = AST_G5_FMC | FMC_TIMING;
	ct->flash = AST_G5_BMC_FLASH;
    }

    if (!sfc_init_device(ct))
	goto fail;

    *ctrl = &ct->ops;

    return 0;

fail:
    free(ct);
    return -EIO;
}

int sfc_destroy(struct sfc *ctrl)
{
	struct sfc_data *ct = container_of(ctrl, struct sfc_data, ops);
	int rc;

	/* Restore control reg to read */
	rc = ahb_writel(ct->ahb, ct->ctl_reg, ct->ctl_read_val);
	if (rc < 0)
		return rc;

	/* Additional cleanup */
	if (ct->type == SFC_TYPE_SMC) {
		uint32_t reg;
		rc = ahb_readl(ct->ahb, SMC_CONF, &reg);
		if (rc < 0)
			return rc;

		if (reg != 0xffffffff) {
			rc = ahb_writel(ct->ahb, SMC_CONF, reg & ~1);
			if (rc < 0)
				return rc;
		}
	}

	/* Free the whole lot */
	free(ct);

	return 0;
}