From 7e494050d6ae7626a2eef06fb9bdd1d25e0e74c4 Mon Sep 17 00:00:00 2001 From: Hung-Te Lin Date: Wed, 30 Jan 2013 20:02:02 +0800 Subject: armv7: Add SPI driver for Exynos. The SPI flash driver for Exynos chipset. Verified to boot on snow/armv7. Change-Id: I7eef67a9c57f825d09f13ea44c2b59b54345fa7b Signed-off-by: Hung-Te Lin Reviewed-on: http://review.coreboot.org/2229 Tested-by: build bot (Jenkins) Reviewed-by: Ronald G. Minnich --- src/cpu/samsung/exynos5-common/spi.c | 236 +++++++++++++++++++++++++++++++- src/cpu/samsung/exynos5-common/spi.h | 13 ++ src/mainboard/google/snow/bootblock.c | 251 +--------------------------------- 3 files changed, 251 insertions(+), 249 deletions(-) diff --git a/src/cpu/samsung/exynos5-common/spi.c b/src/cpu/samsung/exynos5-common/spi.c index e13eadf3fe..90b74c018f 100644 --- a/src/cpu/samsung/exynos5-common/spi.c +++ b/src/cpu/samsung/exynos5-common/spi.c @@ -1,7 +1,237 @@ -#include +/* + * Copyright (C) 2011 Samsung Electronics + * Copyright (C) 2013 The Chromium OS Authors. All rights reserved. + * + * 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 + */ -/* TODO provide a real SPI driver here for firmware media. */ +#include + +#include +#include + +#include +#include + +#include "spi.h" + +#define OM_STAT (0x1f << 1) +#define EXYNOS_BASE_SPI1 ((void *)0x12d30000) + +#if defined(CONFIG_DEBUG_SPI) && CONFIG_DEBUG_SPI +# define DEBUG_SPI(x,...) printk(BIOS_DEBUG, "EXYNOS_SPI: " x) +#else +# define DEBUG_SPI(x,...) +#endif + +static void exynos_spi_rx_tx(struct exynos_spi *regs, int todo, + void *dinp, void const *doutp, int i) +{ + int rx_lvl, tx_lvl; + uint *rxp = (uint *)(dinp + (i * (32 * 1024))); + uint out_bytes, in_bytes; + + // TODO In currrent implementation, every read/write must be aligned to + // 4 bytes, otherwise you may get timeout or other unexpected results. + assert(todo % 4 == 0); + + out_bytes = in_bytes = todo; + setbits_le32(®s->ch_cfg, SPI_CH_RST); + clrbits_le32(®s->ch_cfg, SPI_CH_RST); + writel(((todo * 8) / 32) | SPI_PACKET_CNT_EN, ®s->pkt_cnt); + + while (in_bytes) { + uint32_t spi_sts; + int temp; + + spi_sts = readl(®s->spi_sts); + rx_lvl = ((spi_sts >> 15) & 0x7f); + tx_lvl = ((spi_sts >> 6) & 0x7f); + while (tx_lvl < 32 && out_bytes) { + // TODO The "writing" (tx) is not supported now; that's + // why we write garbage to keep driving FIFO clock. + temp = 0xffffffff; + writel(temp, ®s->tx_data); + out_bytes -= 4; + tx_lvl += 4; + } + while (rx_lvl >= 4 && in_bytes) { + temp = readl(®s->rx_data); + if (rxp) + *rxp++ = temp; + in_bytes -= 4; + rx_lvl -= 4; + } + } +} + +int exynos_spi_open(struct exynos_spi *regs) +{ + clock_set_rate(PERIPH_ID_SPI1, 50000000); /* set spi clock to 50Mhz */ + /* set the spi1 GPIO */ + + // TODO Some of these should be done in board's bootblock file. + // We should fix-up the mainboard-specific vs. exynos-specific parts in a + // follow-up CL. + +// exynos_pinmux_config(PERIPH_ID_SPI1, PINMUX_FLAG_NONE); + gpio_cfg_pin(GPIO_A24, 0x2); + gpio_cfg_pin(GPIO_A25, 0x2); + gpio_cfg_pin(GPIO_A26, 0x2); + gpio_cfg_pin(GPIO_A27, 0x2); + + /* set pktcnt and enable it */ + writel(4 | SPI_PACKET_CNT_EN, ®s->pkt_cnt); + /* set FB_CLK_SEL */ + writel(SPI_FB_DELAY_180, ®s->fb_clk); + /* set CH_WIDTH and BUS_WIDTH as word */ + setbits_le32(®s->mode_cfg, + SPI_MODE_CH_WIDTH_WORD | SPI_MODE_BUS_WIDTH_WORD); + clrbits_le32(®s->ch_cfg, SPI_CH_CPOL_L); /* CPOL: active high */ + + /* clear rx and tx channel if set priveously */ + clrbits_le32(®s->ch_cfg, SPI_RX_CH_ON | SPI_TX_CH_ON); + + setbits_le32(®s->swap_cfg, + SPI_RX_SWAP_EN | SPI_RX_BYTE_SWAP | SPI_RX_HWORD_SWAP); + + /* do a soft reset */ + setbits_le32(®s->ch_cfg, SPI_CH_RST); + clrbits_le32(®s->ch_cfg, SPI_CH_RST); + + /* now set rx and tx channel ON */ + setbits_le32(®s->ch_cfg, SPI_RX_CH_ON | SPI_TX_CH_ON | SPI_CH_HS_EN); + clrbits_le32(®s->cs_reg, SPI_SLAVE_SIG_INACT); /* CS low */ + return 0; +} + +int exynos_spi_read(struct exynos_spi *regs, void *dest, u32 len, u32 off) +{ + int upto, todo; + int i; + /* Send read instruction (0x3h) followed by a 24 bit addr */ + writel((SF_READ_DATA_CMD << 24) | off, ®s->tx_data); + + /* waiting for TX done */ + while (!(readl(®s->spi_sts) & SPI_ST_TX_DONE)); + + for (upto = 0, i = 0; upto < len; upto += todo, i++) { + todo = MIN(len - upto, (1 << 15)); + exynos_spi_rx_tx(regs, todo, dest, (void *)(off), i); + } + + setbits_le32(®s->cs_reg, SPI_SLAVE_SIG_INACT);/* make the CS high */ + + /* + * Let put controller mode to BYTE as + * SPI driver does not support WORD mode yet + */ + clrbits_le32(®s->mode_cfg, + SPI_MODE_CH_WIDTH_WORD | SPI_MODE_BUS_WIDTH_WORD); + writel(0, ®s->swap_cfg); + + return len; +} + +int exynos_spi_close(struct exynos_spi *regs) +{ + /* + * Flush spi tx, rx fifos and reset the SPI controller + * and clear rx/tx channel + */ + clrsetbits_le32(®s->ch_cfg, SPI_CH_HS_EN, SPI_CH_RST); + clrbits_le32(®s->ch_cfg, SPI_CH_RST); + clrbits_le32(®s->ch_cfg, SPI_TX_CH_ON | SPI_RX_CH_ON); + return 0; +} + +// SPI as CBFS media. +struct exynos_spi_media { + struct exynos_spi *regs; + struct cbfs_simple_buffer buffer; +}; + +static int exynos_spi_cbfs_open(struct cbfs_media *media) { + struct exynos_spi_media *spi = (struct exynos_spi_media*)media->context; + DEBUG_SPI("exynos_spi_cbfs_open\n"); + return exynos_spi_open(spi->regs); +} + +static int exynos_spi_cbfs_close(struct cbfs_media *media) { + struct exynos_spi_media *spi = (struct exynos_spi_media*)media->context; + DEBUG_SPI("exynos_spi_cbfs_close\n"); + return exynos_spi_close(spi->regs); +} + +static size_t exynos_spi_cbfs_read(struct cbfs_media *media, void *dest, + size_t offset, size_t count) { + struct exynos_spi_media *spi = (struct exynos_spi_media*)media->context; + int bytes; + DEBUG_SPI("exynos_spi_cbfs_read(%u)\n", count); + bytes = exynos_spi_read(spi->regs, dest, count, offset); + // Flush and re-open the device. + exynos_spi_close(spi->regs); + exynos_spi_open(spi->regs); + return bytes; +} + +static void *exynos_spi_cbfs_map(struct cbfs_media *media, size_t offset, + size_t count) { + struct exynos_spi_media *spi = (struct exynos_spi_media*)media->context; + DEBUG_SPI("exynos_spi_cbfs_map\n"); + // See exynos_spi_rx_tx for I/O alignment limitation. + if (count % 4) + count += 4 - (count % 4); + return cbfs_simple_buffer_map(&spi->buffer, media, offset, count); +} + +static void *exynos_spi_cbfs_unmap(struct cbfs_media *media, + const void *address) { + struct exynos_spi_media *spi = (struct exynos_spi_media*)media->context; + DEBUG_SPI("exynos_spi_cbfs_unmap\n"); + return cbfs_simple_buffer_unmap(&spi->buffer, address); +} + +int initialize_exynos_spi_cbfs_media(struct cbfs_media *media, + void *buffer_address, + size_t buffer_size) { + // TODO Replace static variable to support multiple streams. + static struct exynos_spi_media context; + DEBUG_SPI("initialize_exynos_spi_cbfs_media\n"); + + context.regs = EXYNOS_BASE_SPI1; + context.buffer.allocated = context.buffer.last_allocate = 0; + context.buffer.buffer = buffer_address; + context.buffer.size = buffer_size; + media->context = (void*)&context; + media->open = exynos_spi_cbfs_open; + media->close = exynos_spi_cbfs_close; + media->read = exynos_spi_cbfs_read; + media->map = exynos_spi_cbfs_map; + media->unmap = exynos_spi_cbfs_unmap; + + return 0; +} int init_default_cbfs_media(struct cbfs_media *media) { - return -1; + return initialize_exynos_spi_cbfs_media( + media, + (void*)CONFIG_CBFS_CACHE_ADDRESS, + CONFIG_CBFS_CACHE_SIZE); } diff --git a/src/cpu/samsung/exynos5-common/spi.h b/src/cpu/samsung/exynos5-common/spi.h index 3f3675921c..e021888614 100644 --- a/src/cpu/samsung/exynos5-common/spi.h +++ b/src/cpu/samsung/exynos5-common/spi.h @@ -22,6 +22,9 @@ #ifndef __ASSEMBLER__ +// This driver serves as a CBFS media source. +#include + /* SPI peripheral register map; padded to 64KB */ struct exynos_spi { unsigned int ch_cfg; /* 0x00 */ @@ -85,5 +88,15 @@ struct exynos_spi { #define SPI_RX_BYTE_SWAP (1 << 6) #define SPI_RX_HWORD_SWAP (1 << 7) +/* API */ +int exynos_spi_open(struct exynos_spi *regs); +int exynos_spi_read(struct exynos_spi *regs, void *dest, u32 len, u32 off); +int exynos_spi_close(struct exynos_spi *regs); + +/* Serve as CBFS Media */ +int initialize_exynos_spi_cbfs_media(struct cbfs_media *media, + void *buffer_address, + size_t buffer_size); + #endif /* __ASSEMBLER__ */ #endif diff --git a/src/mainboard/google/snow/bootblock.c b/src/mainboard/google/snow/bootblock.c index 5b3efa025c..3887e7e06d 100644 --- a/src/mainboard/google/snow/bootblock.c +++ b/src/mainboard/google/snow/bootblock.c @@ -17,11 +17,10 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -#define uchar unsigned char -#define uint unsigned int - #include #include +#include +#include #include #include "cpu/samsung/exynos5250/clk.h" #include "cpu/samsung/exynos5250/cpu.h" @@ -46,87 +45,6 @@ /* TODO Move to Makefile.inc once we support adding bootblock stage files. */ #include "cpu/samsung/exynos5-common/spi.c" -/* FIXME(dhendrix): Can we move this SPI stuff elsewhere? */ -static void spi_rx_tx(struct exynos_spi *regs, int todo, - void *dinp, void const *doutp, int i) -{ - unsigned int *rxp = (unsigned int *)(dinp + (i * (32 * 1024))); - int rx_lvl, tx_lvl; - unsigned int out_bytes, in_bytes; - - out_bytes = in_bytes = todo; - setbits_le32(®s->ch_cfg, SPI_CH_RST); - clrbits_le32(®s->ch_cfg, SPI_CH_RST); - writel(((todo * 8) / 32) | SPI_PACKET_CNT_EN, ®s->pkt_cnt); - - while (in_bytes) { - uint32_t spi_sts; - int temp; - - spi_sts = readl(®s->spi_sts); - rx_lvl = ((spi_sts >> 15) & 0x7f); - tx_lvl = ((spi_sts >> 6) & 0x7f); - while (tx_lvl < 32 && out_bytes) { - temp = 0xffffffff; - writel(temp, ®s->tx_data); - out_bytes -= 4; - tx_lvl += 4; - } - while (rx_lvl >= 4 && in_bytes) { - temp = readl(®s->rx_data); - if (rxp) - *rxp++ = temp; - in_bytes -= 4; - rx_lvl -= 4; - } - } -} - -#if 0 -void clock_ll_set_pre_ratio(enum periph_id periph_id, unsigned divisor) -{ - struct exynos5_clock *clk = - (struct exynos5_clock *)samsung_get_base_clock(); - unsigned shift; - unsigned mask = 0xff; - u32 *reg; - - /* - * For now we only handle a very small subset of peipherals here. - * Others will need to (and do) mangle the clock registers - * themselves, At some point it is hoped that this function can work - * from a table or calculated register offset / mask. For now this - * is at least better than spreading clock control code around - * U-Boot. - */ - switch (periph_id) { - case PERIPH_ID_SPI0: - reg = &clk->div_peric1; - shift = 8; - break; - case PERIPH_ID_SPI1: - reg = &clk->div_peric1; - shift = 24; - break; - case PERIPH_ID_SPI2: - reg = &clk->div_peric2; - shift = 8; - break; - case PERIPH_ID_SPI3: - reg = &clk->sclk_div_isp; - shift = 4; - break; - case PERIPH_ID_SPI4: - reg = &clk->sclk_div_isp; - shift = 16; - break; - default: - debug("%s: Unsupported peripheral ID %d\n", __func__, - periph_id); - return; - } -} -#endif void clock_ll_set_pre_ratio(enum periph_id periph_id, unsigned divisor) { struct exynos5_clock *clk = @@ -140,43 +58,6 @@ void clock_ll_set_pre_ratio(enum periph_id periph_id, unsigned divisor) clrsetbits_le32(reg, mask << shift, (divisor & mask) << shift); } -#if 0 -void clock_ll_set_ratio(enum periph_id periph_id, unsigned divisor) -{ - struct exynos5_clock *clk = - (struct exynos5_clock *)samsung_get_base_clock(); - unsigned shift; - unsigned mask = 0xff; - u32 *reg; - - switch (periph_id) { - case PERIPH_ID_SPI0: - reg = &clk->div_peric1; - shift = 0; - break; - case PERIPH_ID_SPI1: - reg = &clk->div_peric1; - shift = 16; - break; - case PERIPH_ID_SPI2: - reg = &clk->div_peric2; - shift = 0; - break; - case PERIPH_ID_SPI3: - reg = &clk->sclk_div_isp; - shift = 0; - break; - case PERIPH_ID_SPI4: - reg = &clk->sclk_div_isp; - shift = 12; - break; - default: - debug("%s: Unsupported peripheral ID %d\n", __func__, - periph_id); - return; - } -} -#endif void clock_ll_set_ratio(enum periph_id periph_id, unsigned divisor) { struct exynos5_clock *clk = @@ -253,44 +134,6 @@ static int clock_calc_best_scalar(unsigned int main_scaler_bits, return best_main_scalar; } -#if 0 -int clock_set_rate(enum periph_id periph_id, unsigned int rate) -{ - int main; - unsigned int fine; - - switch (periph_id) { - case PERIPH_ID_SPI0: - case PERIPH_ID_SPI1: - case PERIPH_ID_SPI2: - case PERIPH_ID_SPI3: - case PERIPH_ID_SPI4: - main = clock_calc_best_scalar(4, 8, 400000000, rate, &fine); - if (main < 0) { - debug("%s: Cannot set clock rate for periph %d", - __func__, periph_id); - return -1; - } - clock_ll_set_ratio(periph_id, main - 1); - clock_ll_set_pre_ratio(periph_id, fine - 1); - break; - default: - debug("%s: Unsupported peripheral ID %d\n", __func__, - periph_id); - return -1; - } - main = clock_calc_best_scalar(4, 8, 400000000, rate, &fine); - if (main < 0) { - debug("%s: Cannot set clock rate for periph %d", - __func__, periph_id); - return -1; - } - clock_ll_set_ratio(PERIPH_ID_SPI1, main - 1); - clock_ll_set_pre_ratio(PERIPH_ID_SPI1, fine - 1); - - return 0; -} -#endif int clock_set_rate(enum periph_id periph_id, unsigned int rate) { int main; @@ -362,77 +205,6 @@ void gpio_cfg_pin(int gpio, int cfg) writel(value, &bank->con); } -//static void exynos_spi_copy(unsigned int uboot_size) -static void copy_romstage(uint32_t spi_addr, uint32_t sram_addr, unsigned int len) -{ - int upto, todo; - int i; -// struct exynos_spi *regs = (struct exynos_spi *)samsung_get_base_spi1(); - struct exynos_spi *regs = (struct exynos_spi *)0x12d30000; - - clock_set_rate(PERIPH_ID_SPI1, 50000000); /* set spi clock to 50Mhz */ - /* set the spi1 GPIO */ -// exynos_pinmux_config(PERIPH_ID_SPI1, PINMUX_FLAG_NONE); - gpio_cfg_pin(GPIO_A24, 0x2); - gpio_cfg_pin(GPIO_A25, 0x2); - gpio_cfg_pin(GPIO_A26, 0x2); - gpio_cfg_pin(GPIO_A27, 0x2); - - /* set pktcnt and enable it */ - writel(4 | SPI_PACKET_CNT_EN, ®s->pkt_cnt); - /* set FB_CLK_SEL */ - writel(SPI_FB_DELAY_180, ®s->fb_clk); - /* set CH_WIDTH and BUS_WIDTH as word */ - setbits_le32(®s->mode_cfg, SPI_MODE_CH_WIDTH_WORD | - SPI_MODE_BUS_WIDTH_WORD); - clrbits_le32(®s->ch_cfg, SPI_CH_CPOL_L); /* CPOL: active high */ - - /* clear rx and tx channel if set priveously */ - clrbits_le32(®s->ch_cfg, SPI_RX_CH_ON | SPI_TX_CH_ON); - - setbits_le32(®s->swap_cfg, SPI_RX_SWAP_EN | - SPI_RX_BYTE_SWAP | - SPI_RX_HWORD_SWAP); - - /* do a soft reset */ - setbits_le32(®s->ch_cfg, SPI_CH_RST); - clrbits_le32(®s->ch_cfg, SPI_CH_RST); - - /* now set rx and tx channel ON */ - setbits_le32(®s->ch_cfg, SPI_RX_CH_ON | SPI_TX_CH_ON | SPI_CH_HS_EN); - clrbits_le32(®s->cs_reg, SPI_SLAVE_SIG_INACT); /* CS low */ - - /* Send read instruction (0x3h) followed by a 24 bit addr */ - writel((SF_READ_DATA_CMD << 24) | spi_addr, ®s->tx_data); - - /* waiting for TX done */ - while (!(readl(®s->spi_sts) & SPI_ST_TX_DONE)); - - for (upto = 0, i = 0; upto < len; upto += todo, i++) { - todo = MIN(len - upto, (1 << 15)); - spi_rx_tx(regs, todo, (void *)(sram_addr), - (void *)(spi_addr), i); - } - - setbits_le32(®s->cs_reg, SPI_SLAVE_SIG_INACT);/* make the CS high */ - - /* - * Let put controller mode to BYTE as - * SPI driver does not support WORD mode yet - */ - clrbits_le32(®s->mode_cfg, SPI_MODE_CH_WIDTH_WORD | - SPI_MODE_BUS_WIDTH_WORD); - writel(0, ®s->swap_cfg); - - /* - * Flush spi tx, rx fifos and reset the SPI controller - * and clear rx/tx channel - */ - clrsetbits_le32(®s->ch_cfg, SPI_CH_HS_EN, SPI_CH_RST); - clrbits_le32(®s->ch_cfg, SPI_CH_RST); - clrbits_le32(®s->ch_cfg, SPI_TX_CH_ON | SPI_RX_CH_ON); -} - /* Pull mode */ #define EXYNOS_GPIO_PULL_NONE 0x0 #define EXYNOS_GPIO_PULL_DOWN 0x1 @@ -854,21 +626,15 @@ void do_barriers(void) ); } -void sdelay(unsigned long loops); -void sdelay(unsigned long loops) -{ - __asm__ volatile ("1:\n" "subs %0, %1, #1\n" - "bne 1b":"=r" (loops):"0"(loops)); -} - /* is this right? meh, it seems to work well enough... */ void my_udelay(unsigned int n); void my_udelay(unsigned int n) { - sdelay(n * 1000); + n *= 1000; + __asm__ volatile ("1:\n" "subs %0, %1, #1\n" + "bne 1b":"=r" (n):"0"(n)); } - void i2c_init(int speed, int slaveadd) { struct s3c24x0_i2c_bus *i2c = &i2c0; @@ -2145,13 +1911,6 @@ void bootblock_mainboard_init(void) do_serial(); printk(BIOS_INFO, "%s: UART initialized\n", __func__); - /* Copy romstage data from SPI ROM to SRAM */ - printk(BIOS_INFO, "Copying romstage:\n" - "\tSPI offset: 0x%06x\n" - "\tiRAM offset: 0x%08x\n" - "\tSize: 0x%x\n", - 0, CONFIG_SPI_IMAGE_HACK, CONFIG_ROMSTAGE_SIZE); - copy_romstage(0x0, CONFIG_SPI_IMAGE_HACK, CONFIG_ROMSTAGE_SIZE); #if 0 /* FIXME: dump SRAM content for sanity checking */ uint32_t u; -- cgit v1.2.3