diff options
author | Benjamin Herrenschmidt <benh@kernel.crashing.org> | 2014-07-02 15:36:20 +1000 |
---|---|---|
committer | Benjamin Herrenschmidt <benh@kernel.crashing.org> | 2014-07-02 15:36:20 +1000 |
commit | 1d880992fd8c8457a2d990ac6622cfd58fb1b261 (patch) | |
tree | c4c843b12e96b5612c315db5a23c5da1a900618c /hw/ast-bmc | |
download | skiboot-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.inc | 5 | ||||
-rw-r--r-- | hw/ast-bmc/ast-io.c | 324 | ||||
-rw-r--r-- | hw/ast-bmc/ast-sf-ctrl.c | 412 |
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); +} + |