aboutsummaryrefslogtreecommitdiff
path: root/hw/ast-bmc
diff options
context:
space:
mode:
authorBenjamin Herrenschmidt <benh@kernel.crashing.org>2014-07-02 15:36:20 +1000
committerBenjamin Herrenschmidt <benh@kernel.crashing.org>2014-07-02 15:36:20 +1000
commit1d880992fd8c8457a2d990ac6622cfd58fb1b261 (patch)
treec4c843b12e96b5612c315db5a23c5da1a900618c /hw/ast-bmc
downloadskiboot-1d880992fd8c8457a2d990ac6622cfd58fb1b261.zip
skiboot-1d880992fd8c8457a2d990ac6622cfd58fb1b261.tar.gz
skiboot-1d880992fd8c8457a2d990ac6622cfd58fb1b261.tar.bz2
Initial commit of Open Source release
Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Diffstat (limited to 'hw/ast-bmc')
-rw-r--r--hw/ast-bmc/Makefile.inc5
-rw-r--r--hw/ast-bmc/ast-io.c324
-rw-r--r--hw/ast-bmc/ast-sf-ctrl.c412
3 files changed, 741 insertions, 0 deletions
diff --git a/hw/ast-bmc/Makefile.inc b/hw/ast-bmc/Makefile.inc
new file mode 100644
index 0000000..a97c0db
--- /dev/null
+++ b/hw/ast-bmc/Makefile.inc
@@ -0,0 +1,5 @@
+SUBDIRS += hw/ast-bmc
+
+AST_BMC_OBJS = ast-io.o ast-sf-ctrl.o
+AST_BMC = hw/ast-bmc/built-in.o
+$(AST_BMC): $(AST_BMC_OBJS:%=hw/ast-bmc/%)
diff --git a/hw/ast-bmc/ast-io.c b/hw/ast-bmc/ast-io.c
new file mode 100644
index 0000000..e89bf7f
--- /dev/null
+++ b/hw/ast-bmc/ast-io.c
@@ -0,0 +1,324 @@
+/* Copyright 2013-2014 IBM Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ * implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/*
+ * Note about accesses to the AST2400 internal memory map:
+ *
+ * There are two ways to genrate accesses to the AHB bus of the AST2400
+ * from the host. The LPC->AHB bridge and the iLPC->AHB bridge.
+ *
+ * LPC->AHB bridge
+ * ---------------
+ *
+ * This bridge directly converts memory or firmware accesses using
+ * a set of registers for establishing a remapping window. We prefer
+ * using FW space as normal memory space is limited to byte accesses
+ * to a fixed 256M window, while FW space allows us to use different
+ * access sizes and to control the IDSEL bits which essentially enable
+ * a full 4G addres space.
+ *
+ * The way FW accesses map onto AHB is controlled via two registers
+ * in the BMC's LPC host controller:
+ *
+ * HICR7 at 0x1e789088 [31:16] : ADRBASE
+ * [15:00] : HWMBASE
+ *
+ * HICR8 at 0x1e78908c [31:16] : ADRMASK
+ * [15:00] : HWNCARE
+ *
+ * All decoding/remapping happens on the top 16 bits of the LPC address
+ * named LPC_ADDR as follow:
+ *
+ * - For decoding, LPC_ADDR bits are compared with HWMBASE if the
+ * corresponding bit in HWNCARE is 0.
+ *
+ * - For remapping, the AHB address is constructed by taking bits
+ * from LPC_ADDR if the corresponding bit in ADRMASK is 0 or in
+ * ADRBASE if the corresponding bit in ADRMASK is 1
+ *
+ * Example of 2MB SPI flash, LPC 0xFCE00000~0xFCFFFFFF onto
+ * AHB 0x30000000~0x301FFFFF (SPI flash)
+ *
+ * ADRBASE=0x3000 HWMBASE=0xFCE0
+ * ADRMASK=0xFFE0 HWNCARE=0x001F
+ *
+ * This comes pre-configured by the BMC or HostBoot to access the PNOR
+ * flash from IDSEL 0 as follow:
+ *
+ * ADRBASE=0x3000 HWMBASE=0x0e00
+ * ADRMASK=0xfe00 HWNCARE=0x01ff
+ *
+ * Which means mapping of LPC 0x0e000000..0x0fffffff onto
+ * AHB 0x30000000..0x31ffffff
+ *
+ * iLPC->AHB bridge
+ * ---------------
+ *
+ * This bridge is hosted in the SuperIO part of the BMC and is
+ * controlled by a series of byte-sized registers accessed indirectly
+ * via IO ports 0x2e and 0x2f.
+ *
+ * Via these, byte by byte, we can construct an AHB address and
+ * fill a data buffer to trigger a write cycle, or we can do a
+ * read cycle and read back the data, byte after byte.
+ *
+ * This is fairly convoluted and slow but works regardless of what
+ * mapping was established in the LPC->AHB bridge.
+ *
+ * For the time being, we use the iLPC->AHB for everything except
+ * pnor accesses. In the long run, we will reconfigure the LPC->AHB
+ * to provide more direct access to all of the BMC addres space but
+ * we'll only do that after the boot script/program on the BMC is
+ * updated to restore the bridge to a state compatible with the SBE
+ * expectations on boot.
+ */
+
+#include <skiboot.h>
+#include <lpc.h>
+#include <lock.h>
+
+#include "ast.h"
+
+static struct lock bmc_sio_lock = LOCK_UNLOCKED;
+
+/*
+ * SuperIO indirect accesses
+ */
+static void bmc_sio_outb(uint8_t val, uint8_t reg)
+{
+ lpc_outb(reg, 0x2e);
+ lpc_outb(val, 0x2f);
+}
+
+static uint8_t bmc_sio_inb(uint8_t reg)
+{
+ lpc_outb(reg, 0x2e);
+ return lpc_inb(0x2f);
+}
+
+/*
+ * AHB accesses via iLPC->AHB in SuperIO. Works on byteswapped
+ * values (ie. Little Endian registers)
+ */
+static void bmc_sio_ahb_prep(uint32_t reg, uint8_t type)
+{
+ /* Address */
+ bmc_sio_outb((reg >> 24) & 0xff, 0xf0);
+ bmc_sio_outb((reg >> 16) & 0xff, 0xf1);
+ bmc_sio_outb((reg >> 8) & 0xff, 0xf2);
+ bmc_sio_outb((reg ) & 0xff, 0xf3);
+
+ /* bytes cycle type */
+ bmc_sio_outb(type, 0xf8);
+}
+
+static void bmc_sio_ahb_writel(uint32_t val, uint32_t reg)
+{
+ lock(&bmc_sio_lock);
+
+ bmc_sio_ahb_prep(reg, 2);
+
+ /* Write data */
+ bmc_sio_outb(val >> 24, 0xf4);
+ bmc_sio_outb(val >> 16, 0xf5);
+ bmc_sio_outb(val >> 8, 0xf6);
+ bmc_sio_outb(val , 0xf7);
+
+ /* Trigger */
+ bmc_sio_outb(0xcf, 0xfe);
+
+ unlock(&bmc_sio_lock);
+}
+
+static uint32_t bmc_sio_ahb_readl(uint32_t reg)
+{
+ uint32_t val = 0;
+
+ lock(&bmc_sio_lock);
+
+ bmc_sio_ahb_prep(reg, 2);
+
+ /* Trigger */
+ bmc_sio_inb(0xfe);
+
+ /* Read results */
+ val = (val << 8) | bmc_sio_inb(0xf4);
+ val = (val << 8) | bmc_sio_inb(0xf5);
+ val = (val << 8) | bmc_sio_inb(0xf6);
+ val = (val << 8) | bmc_sio_inb(0xf7);
+
+ unlock(&bmc_sio_lock);
+
+ return val;
+}
+
+static void bmc_sio_ahb_init(void)
+{
+ /* Send SuperIO password */
+ lpc_outb(0xa5, 0x2e);
+ lpc_outb(0xa5, 0x2e);
+
+ /* Select logical dev d */
+ bmc_sio_outb(0x0d, 0x07);
+
+ /* Enable iLPC->AHB */
+ bmc_sio_outb(0x01, 0x30);
+
+ /* We leave the SuperIO enabled and unlocked for
+ * subsequent accesses.
+ */
+}
+
+/*
+ * External API
+ *
+ * We only support 4-byte accesses to all of AHB. We additionally
+ * support 1-byte accesses to the flash area only.
+ *
+ * We could support all access sizes via iLPC but we don't need
+ * that for now.
+ */
+#define PNOR_AHB_ADDR 0x30000000
+#define PNOR_LPC_OFFSET 0x0e000000
+
+void ast_ahb_writel(uint32_t val, uint32_t reg)
+{
+ /* For now, always use iLPC->AHB, it will byteswap */
+ bmc_sio_ahb_writel(val, reg);
+}
+
+uint32_t ast_ahb_readl(uint32_t reg)
+{
+ /* For now, always use iLPC->AHB, it will byteswap */
+ return bmc_sio_ahb_readl(reg);
+}
+
+int ast_copy_to_ahb(uint32_t reg, const void *src, uint32_t len)
+{
+ /* Check we don't cross IDSEL segments */
+ if ((reg ^ (reg + len - 1)) >> 28)
+ return -EINVAL;
+
+ /* SPI flash, use LPC->AHB bridge */
+ if ((reg >> 28) == (PNOR_AHB_ADDR >> 28)) {
+ uint32_t chunk, off = reg - PNOR_AHB_ADDR + PNOR_LPC_OFFSET;
+ int64_t rc;
+
+ while(len) {
+ /* Chose access size */
+ if (len > 3 && !(off & 3)) {
+ rc = lpc_write(OPAL_LPC_FW, off,
+ *(uint32_t *)src, 4);
+ chunk = 4;
+ } else {
+ rc = lpc_write(OPAL_LPC_FW, off,
+ *(uint8_t *)src, 1);
+ chunk = 1;
+ }
+ if (rc) {
+ prerror("AST_IO: lpc_write.sb failure %lld"
+ " to FW 0x%08x\n", rc, off);
+ return rc;
+ }
+ len -= chunk;
+ off += chunk;
+ src += chunk;
+ }
+ return 0;
+ }
+
+ /* Otherwise we don't do byte access (... yet) */
+ prerror("AST_IO: Attempted write bytes access to %08x\n", reg);
+ return -EINVAL;
+}
+
+int ast_copy_from_ahb(void *dst, uint32_t reg, uint32_t len)
+{
+ /* Check we don't cross IDSEL segments */
+ if ((reg ^ (reg + len - 1)) >> 28)
+ return -EINVAL;
+
+ /* SPI flash, use LPC->AHB bridge */
+ if ((reg >> 28) == (PNOR_AHB_ADDR >> 28)) {
+ uint32_t chunk, off = reg - PNOR_AHB_ADDR + PNOR_LPC_OFFSET;
+ int64_t rc;
+
+ while(len) {
+ uint32_t dat;
+
+ /* Chose access size */
+ if (len > 3 && !(off & 3)) {
+ rc = lpc_read(OPAL_LPC_FW, off, &dat, 4);
+ if (!rc)
+ *(uint32_t *)dst = dat;
+ chunk = 4;
+ } else {
+ rc = lpc_read(OPAL_LPC_FW, off, &dat, 1);
+ if (!rc)
+ *(uint8_t *)dst = dat;
+ chunk = 1;
+ }
+ if (rc) {
+ prerror("AST_IO: lpc_read.sb failure %lld"
+ " to FW 0x%08x\n", rc, off);
+ return rc;
+ }
+ len -= chunk;
+ off += chunk;
+ dst += chunk;
+ }
+ return 0;
+ }
+ /* Otherwise we don't do byte access (... yet) */
+ prerror("AST_IO: Attempted read bytes access to %08x\n", reg);
+ return -EINVAL;
+}
+
+void ast_io_init(void)
+{
+ /* Initialize iLPC->AHB bridge */
+ bmc_sio_ahb_init();
+
+ /* Configure the LPC->AHB bridge for PNOR access (just in case) */
+ bmc_sio_ahb_writel(0x30000e00, LPC_HICR7);
+ bmc_sio_ahb_writel(0xfe0001ff, LPC_HICR8);
+ bmc_sio_ahb_writel(0x00000500, LPC_HICR6);
+}
+
+/* Setup SuperIO UART 1*/
+void ast_setup_uart1(uint16_t io_base, uint8_t irq)
+{
+ /* Send SuperIO password */
+ lpc_outb(0xa5, 0x2e);
+ lpc_outb(0xa5, 0x2e);
+
+ /* Select logical dev 2 */
+ bmc_sio_outb(0x02, 0x07);
+
+ /* Disable UART1 for configuration */
+ bmc_sio_outb(0x01, 0x30);
+
+ /* Configure base and interrupt */
+ bmc_sio_outb(io_base >> 8, 0x60);
+ bmc_sio_outb(io_base & 0xff, 0x61);
+ bmc_sio_outb(irq, 0x70);
+ bmc_sio_outb(0x01, 0x71); /* level low */
+
+ /* Enable UART1 */
+ bmc_sio_outb(0x01, 0x30);
+
+ /* Re-lock SuperIO */
+ lpc_outb(0xaa, 0x2e);
+}
diff --git a/hw/ast-bmc/ast-sf-ctrl.c b/hw/ast-bmc/ast-sf-ctrl.c
new file mode 100644
index 0000000..e0d5fcc
--- /dev/null
+++ b/hw/ast-bmc/ast-sf-ctrl.c
@@ -0,0 +1,412 @@
+/* Copyright 2013-2014 IBM Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ * implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <libflash/libflash.h>
+#include <libflash/libflash-priv.h>
+
+#include "ast.h"
+
+#ifndef __unused
+#define __unused __attribute__((unused))
+#endif
+
+struct ast_sf_ctrl {
+ /* We have 2 controllers, one for the BMC flash, one for the PNOR */
+ uint8_t type;
+
+ /* 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;
+
+ /* Address of the flash mapping */
+ uint32_t flash;
+
+ /* Current 4b mode */
+ bool mode_4b;
+
+ /* Callbacks */
+ struct spi_flash_ctrl ops;
+};
+
+static int ast_sf_start_cmd(struct ast_sf_ctrl *ct, uint8_t cmd)
+{
+ /* Switch to user mode, CE# dropped */
+ ast_ahb_writel(ct->ctl_val | 7, ct->ctl_reg);
+
+ /* user mode, CE# active */
+ ast_ahb_writel(ct->ctl_val | 3, ct->ctl_reg);
+
+ /* write cmd */
+ return ast_copy_to_ahb(ct->flash, &cmd, 1);
+}
+
+static void ast_sf_end_cmd(struct ast_sf_ctrl *ct)
+{
+ /* clear CE# */
+ ast_ahb_writel(ct->ctl_val | 7, ct->ctl_reg);
+
+ /* Switch back to read mode */
+ ast_ahb_writel(ct->ctl_read_val, ct->ctl_reg);
+}
+
+static int ast_sf_send_addr(struct ast_sf_ctrl *ct, uint32_t addr)
+{
+ const void *ap;
+
+ /* Layout address MSB first in memory */
+ addr = cpu_to_be32(addr);
+
+ /* Send the right amount of bytes */
+ ap = (char *)&addr;
+
+ if (ct->mode_4b)
+ return ast_copy_to_ahb(ct->flash, ap, 4);
+ else
+ return ast_copy_to_ahb(ct->flash, ap + 1, 3);
+}
+
+static int ast_sf_cmd_rd(struct spi_flash_ctrl *ctrl, uint8_t cmd,
+ bool has_addr, uint32_t addr, void *buffer,
+ uint32_t size)
+{
+ struct ast_sf_ctrl *ct = container_of(ctrl, struct ast_sf_ctrl, ops);
+ int rc;
+
+ rc = ast_sf_start_cmd(ct, cmd);
+ if (rc)
+ goto bail;
+ if (has_addr) {
+ rc = ast_sf_send_addr(ct, addr);
+ if (rc)
+ goto bail;
+ }
+ if (buffer && size)
+ rc = ast_copy_from_ahb(buffer, ct->flash, size);
+ bail:
+ ast_sf_end_cmd(ct);
+ return rc;
+}
+
+static int ast_sf_cmd_wr(struct spi_flash_ctrl *ctrl, uint8_t cmd,
+ bool has_addr, uint32_t addr, const void *buffer,
+ uint32_t size)
+{
+ struct ast_sf_ctrl *ct = container_of(ctrl, struct ast_sf_ctrl, ops);
+ int rc;
+
+ rc = ast_sf_start_cmd(ct, cmd);
+ if (rc)
+ goto bail;
+ if (has_addr) {
+ rc = ast_sf_send_addr(ct, addr);
+ if (rc)
+ goto bail;
+ }
+ if (buffer && size)
+ rc = ast_copy_to_ahb(ct->flash, buffer, size);
+ bail:
+ ast_sf_end_cmd(ct);
+ return rc;
+}
+
+static int ast_sf_set_4b(struct spi_flash_ctrl *ctrl, bool enable)
+{
+ struct ast_sf_ctrl *ct = container_of(ctrl, struct ast_sf_ctrl, ops);
+
+ if (ct->type != AST_SF_TYPE_PNOR)
+ return enable ? FLASH_ERR_4B_NOT_SUPPORTED : 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;
+ } else {
+ ct->ctl_val &= ~0x2000;
+ ct->ctl_read_val &= ~0x2000;
+ }
+ ct->mode_4b = enable;
+
+ /* Update read mode */
+ ast_ahb_writel(ct->ctl_read_val, ct->ctl_reg);
+
+ return 0;
+}
+
+static int ast_sf_read(struct spi_flash_ctrl *ctrl, uint32_t pos,
+ void *buf, uint32_t len)
+{
+ struct ast_sf_ctrl *ct = container_of(ctrl, struct ast_sf_ctrl, ops);
+
+ /*
+ * We are in read mode by default. We don't yet support fancy
+ * things like fast read or X2 mode
+ */
+ return ast_copy_from_ahb(buf, ct->flash + pos, len);
+}
+
+static int ast_sf_setup(struct spi_flash_ctrl *ctrl, struct flash_info *info,
+ uint32_t *tsize)
+{
+ struct ast_sf_ctrl *ct = container_of(ctrl, struct ast_sf_ctrl, ops);
+
+ (void)tsize;
+
+ /*
+ * Configure better timings and read mode for known
+ * flash chips
+ */
+ switch(info->id) {
+ case 0xc22019: /* MX25L25635F */
+ case 0xc2201a: /* MX66L51235F */
+ /*
+ * Those Macronix chips support dual IO reads at 104Mhz
+ * with 8 dummy cycles so let's use HCLK/2 which is 96Mhz.
+ *
+ * We use DREAD (dual read) for now as it defaults to 8
+ * dummy cycles. Eventually we'd like to use 2READ (which
+ * also has the address using 2 IOs) but that defaults
+ * to 6 dummy cycles and we can only do a multiple of bytes
+ * (Note: I think that accounts for the dual IO so a byte is
+ * probably 4 clocks in that mode, but I need to dlb check).
+ *
+ * We can change the configuration of the flash so we can
+ * do that later, it's a bit more complex.
+ *
+ * The CE# inactive width for reads must be 7ns, we set it
+ * to 2T which is about 10.4ns.
+ *
+ * For write and program it's 30ns so let's set the value
+ * for normal ops to 6T.
+ *
+ * Preserve the current 4b mode.
+ */
+ 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 */
+ (0x07 << 8) | /* HCLK/2 */
+ (0x01 << 6) | /* 1-byte dummy cycle */
+ (0x01); /* fast read */
+
+ /* Configure SPI flash read timing ? */
+
+ /*
+ * 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
+ */
+ ct->ctl_val = (ct->ctl_val & 0x2000) |
+ (0x00 << 28) | /* Single bit */
+ (0x0a << 24) | /* CE# width 6T (b1010) */
+ (0x00 << 16) | /* no command */
+ (0x07 << 8) | /* HCLK/2 */
+ (0x00 << 6) | /* no dummy cycle */
+ (0x00); /* normal read */
+
+ /* Update chip with current read config */
+ ast_ahb_writel(ct->ctl_read_val, ct->ctl_reg);
+ break;
+ case 0xef4018: /* W25Q128BV */
+ /*
+ * This Windbond chip support dual IO reads at 104Mhz
+ * with 8 dummy cycles so let's use HCLK/2.
+ *
+ * The CE# inactive width for reads must be 10ns, we set it
+ * to 3T which is about 15.6ns.
+ */
+ ct->ctl_read_val =
+ (0x02 << 28) | /* Dual bit data only */
+ (0x0e << 24) | /* CE# width 2T (b1110) */
+ (0x3b << 16) | /* DREAD command */
+ (0x07 << 8) | /* HCLK/2 */
+ (0x01 << 6) | /* 1-byte dummy cycle */
+ (0x01); /* fast read */
+
+ /* Configure SPI flash read timing ? */
+
+ /*
+ * 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 =
+ (0x00 << 28) | /* Single bit */
+ (0x06 << 24) | /* CE# width 10T (b0110) */
+ (0x00 << 16) | /* no command */
+ (0x07 << 8) | /* HCLK/2 */
+ (0x00 << 6) | /* no dummy cycle */
+ (0x01); /* fast read */
+
+ /* Update chip with current read config */
+ ast_ahb_writel(ct->ctl_read_val, ct->ctl_reg);
+ break;
+ }
+ return 0;
+}
+
+static bool ast_sf_init_pnor(struct ast_sf_ctrl *ct)
+{
+ uint32_t reg;
+
+ ct->ctl_reg = PNOR_SPI_FCTL_CTRL;
+ ct->flash = PNOR_FLASH_BASE;
+
+ /* Enable writing to the controller */
+ reg = ast_ahb_readl(PNOR_SPI_FCTL_CONF);
+ if (reg == 0xffffffff) {
+ FL_ERR("AST_SF: Failed read from controller config\n");
+ return false;
+ }
+ ast_ahb_writel(reg | 1, PNOR_SPI_FCTL_CONF);
+
+ /*
+ * 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)
+ */
+ ct->ctl_val = ast_ahb_readl(ct->ctl_reg);
+ if (ct->ctl_val == 0xffffffff) {
+ FL_ERR("AST_SF: Failed read from controller control\n");
+ return false;
+ }
+
+ ct->ctl_val = (ct->ctl_val & 0x2000) |
+ (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;
+
+ /* Configure for read */
+ ast_ahb_writel(ct->ctl_read_val, ct->ctl_reg);
+
+ if (ct->ctl_val & 0x2000)
+ ct->mode_4b = true;
+ else
+ ct->mode_4b = false;
+
+ return true;
+}
+
+static bool ast_sf_init_bmc(struct ast_sf_ctrl *ct)
+{
+ ct->ctl_reg = BMC_SPI_FCTL_CTRL;
+ ct->flash = BMC_FLASH_BASE;
+
+ /*
+ * 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)
+ */
+ 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;
+
+ /* Configure for read */
+ ast_ahb_writel(ct->ctl_read_val, ct->ctl_reg);
+
+ ct->mode_4b = false;
+
+ return true;
+}
+
+int ast_sf_open(uint8_t type, struct spi_flash_ctrl **ctrl)
+{
+ struct ast_sf_ctrl *ct;
+
+ if (type != AST_SF_TYPE_PNOR && type != AST_SF_TYPE_BMC)
+ return -EINVAL;
+
+ *ctrl = NULL;
+ ct = malloc(sizeof(*ct));
+ if (!ct) {
+ FL_ERR("AST_SF: Failed to allocate\n");
+ return -ENOMEM;
+ }
+ memset(ct, 0, sizeof(*ct));
+ ct->type = type;
+ ct->ops.cmd_wr = ast_sf_cmd_wr;
+ ct->ops.cmd_rd = ast_sf_cmd_rd;
+ ct->ops.set_4b = ast_sf_set_4b;
+ ct->ops.read = ast_sf_read;
+ ct->ops.setup = ast_sf_setup;
+
+ if (type == AST_SF_TYPE_PNOR) {
+ if (!ast_sf_init_pnor(ct))
+ goto fail;
+ } else {
+ if (!ast_sf_init_bmc(ct))
+ goto fail;
+ }
+
+ *ctrl = &ct->ops;
+
+ return 0;
+ fail:
+ free(ct);
+ return -EIO;
+}
+
+void ast_sf_close(struct spi_flash_ctrl *ctrl)
+{
+ struct ast_sf_ctrl *ct = container_of(ctrl, struct ast_sf_ctrl, ops);
+
+ /* Restore control reg to read */
+ ast_ahb_writel(ct->ctl_read_val, ct->ctl_reg);
+
+ /* Additional cleanup */
+ if (ct->type == AST_SF_TYPE_PNOR) {
+ uint32_t reg = ast_ahb_readl(PNOR_SPI_FCTL_CONF);
+ if (reg != 0xffffffff)
+ ast_ahb_writel(reg & ~1, PNOR_SPI_FCTL_CONF);
+ }
+
+ /* Free the whole lot */
+ free(ct);
+}
+