From 5b429ac2b26cdd487841ef6c45d03ffe7b048f04 Mon Sep 17 00:00:00 2001 From: Ruilin Hao Date: Tue, 10 Nov 2015 00:12:08 -0800 Subject: soc/marvell/armada38x: Add spi driver for armada38x Port spi driver from uboot to coreboot BUG=chrome-os-partner:47462 TEST=None BRANCH=tot Change-Id: I747be7001f4cfb8eec33e8e5bdef3fe5bb0eb2ca Signed-off-by: Patrick Georgi Original-Commit-Id: 9fbc5c2feb6ffacb54ed94e5c7b94b38be2b2ded Original-Change-Id: Ibea9a050ac8bdab6ce4eeb07accde53aeadade5f Original-Signed-off-by: Ruilin Hao Original-Reviewed-on: https://chromium-review.googlesource.com/313340 Original-Commit-Ready: Kan Yan Original-Tested-by: Kan Yan Original-Reviewed-by: Furquan Shaikh Original-Reviewed-by: Kan Yan Original-Reviewed-by: Yuji Sasaki Reviewed-on: https://review.coreboot.org/13111 Tested-by: build bot (Jenkins) Reviewed-by: Stefan Reinauer --- src/soc/marvell/armada38x/Makefile.inc | 4 + src/soc/marvell/armada38x/spi.c | 488 +++++++++++++++++++++++++++++++++ 2 files changed, 492 insertions(+) create mode 100644 src/soc/marvell/armada38x/spi.c (limited to 'src') diff --git a/src/soc/marvell/armada38x/Makefile.inc b/src/soc/marvell/armada38x/Makefile.inc index 416ad74d87..18f5491d53 100644 --- a/src/soc/marvell/armada38x/Makefile.inc +++ b/src/soc/marvell/armada38x/Makefile.inc @@ -2,6 +2,7 @@ ifeq ($(CONFIG_SOC_MARVELL_ARMADA38X),y) bootblock-y += bootblock.c bootblock-y += bootblock_asm.S +bootblock-y += spi.c bootblock-y += monotonic_timer.c ifeq ($(CONFIG_BOOTBLOCK_CONSOLE),y) bootblock-$(CONFIG_DRIVERS_UART) += uart.c @@ -9,11 +10,14 @@ endif verstage-$(CONFIG_DRIVERS_UART) += uart.c verstage-y += monotonic_timer.c +verstage-y += spi.c +romstage-y += spi.c romstage-y += cbmem.c romstage-y += monotonic_timer.c romstage-$(CONFIG_DRIVERS_UART) += uart.c +ramstage-y += spi.c ramstage-y += cbmem.c ramstage-y += monotonic_timer.c ramstage-y += soc.c diff --git a/src/soc/marvell/armada38x/spi.c b/src/soc/marvell/armada38x/spi.c new file mode 100644 index 0000000000..ed5d519dd0 --- /dev/null +++ b/src/soc/marvell/armada38x/spi.c @@ -0,0 +1,488 @@ +/* + * This file is part of the coreboot project. + * + * Copyright 2015 Marvell Inc. + * + * 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; version 2 of the License. + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/****************************************************************************** +base type define +*******************************************************************************/ +#define MV_SPI_REG_READ mrvl_reg_read +#define MV_SPI_REG_WRITE mrvl_reg_write +#define MV_SPI_REG_BIT_SET mrvl_reg_bit_set +#define MV_SPI_REG_BIT_RESET mrvl_reg_bit_reset + +#define MV_SPI_REGS_OFFSET(unit) (0x10600 + (unit * 0x80)) +#define MV_SPI_REGS_BASE(unit) (MV_SPI_REGS_OFFSET(unit)) +#define MV_SPI_IF_CONFIG_REG(spi_id) (MV_SPI_REGS_BASE(spi_id) + 0x04) +#define MV_SPI_SPR_OFFSET 0 +#define MV_SPI_SPR_MASK (0xF << MV_SPI_SPR_OFFSET) +#define MV_SPI_SPPR_0_OFFSET 4 +#define MV_SPI_SPPR_0_MASK (0x1 << MV_SPI_SPPR_0_OFFSET) +#define MV_SPI_SPPR_HI_OFFSET 6 +#define MV_SPI_SPPR_HI_MASK (0x3 << MV_SPI_SPPR_HI_OFFSET) + +#define MV_SPI_BYTE_LENGTH_OFFSET 5 /* bit 5 */ +#define MV_SPI_BYTE_LENGTH_MASK (0x1 << MV_SPI_BYTE_LENGTH_OFFSET) + +#define MV_SPI_IF_CTRL_REG(spi_id) (MV_SPI_REGS_BASE(spi_id) + 0x00) +#define MV_SPI_CS_ENABLE_OFFSET 0 /* bit 0 */ +#define MV_SPI_CS_ENABLE_MASK (0x1 << MV_SPI_CS_ENABLE_OFFSET) + +#define MV_SPI_CS_NUM_OFFSET 2 +#define MV_SPI_CS_NUM_MASK (0x7 << MV_SPI_CS_NUM_OFFSET) +#define MV_SPI_CPOL_OFFSET 11 +#define MV_SPI_CPOL_MASK (0x1 << MV_SPI_CPOL_OFFSET) +#define MV_SPI_CPHA_OFFSET 12 +#define MV_SPI_CPHA_MASK (0x1 << MV_SPI_CPHA_OFFSET) +#define MV_SPI_TXLSBF_OFFSET 13 +#define MV_SPI_TXLSBF_MASK (0x1 << MV_SPI_TXLSBF_OFFSET) +#define MV_SPI_RXLSBF_OFFSET 14 +#define MV_SPI_RXLSBF_MASK (0x1 << MV_SPI_RXLSBF_OFFSET) + +/* SPI transfer flags */ +#define SPI_XFER_BEGIN 0x01 +#define SPI_XFER_END 0x02 + +#define MV_SPI_INT_CAUSE_REG(spi_id) (MV_SPI_REGS_BASE(spi_id) + 0x10) +#define MV_SPI_DATA_OUT_REG(spi_id) (MV_SPI_REGS_BASE(spi_id) + 0x08) +#define MV_SPI_WAIT_RDY_MAX_LOOP 100000 +#define MV_SPI_DATA_IN_REG(spi_id) (MV_SPI_REGS_BASE(spi_id) + 0x0c) + +#define MV_SPI_TMNG_PARAMS_REG(spi_id) (MV_SPI_REGS_BASE(spi_id) + 0x18) +#define MV_SPI_TMISO_SAMPLE_OFFSET 6 +#define MV_SPI_TMISO_SAMPLE_MASK (0x3 << MV_SPI_TMISO_SAMPLE_OFFSET) + +#define CONFIG_ENV_SPI_MAX_HZ 50000000 +#define CONFIG_SF_DEFAULT_SPEED CONFIG_ENV_SPI_MAX_HZ + +#define CMD_READ_ARRAY_FAST 0x0b + +/****************************************************************************** +base type define end +*******************************************************************************/ + +/****************************************************************************** +struct define +*******************************************************************************/ +typedef enum { + SPI_TYPE_FLASH = 0, + SPI_TYPE_SLIC_ZARLINK_SILABS, + SPI_TYPE_SLIC_LANTIQ, + SPI_TYPE_SLIC_ZSI, + SPI_TYPE_SLIC_ISI +} MV_SPI_TYPE; + +typedef struct { + unsigned short ctrl_model; + unsigned int tclk; +} MV_SPI_HAL_DATA; + +typedef struct { + int clock_pol_low; + enum { SPI_CLK_HALF_CYC, SPI_CLK_BEGIN_CYC } clock_phase; + int tx_msb_first; + int rx_msb_first; +} MV_SPI_IF_PARAMS; + +typedef struct { + /* Does this device support 16 bits access */ + int en16_bit; + /* should we assert / disassert CS for each byte we read / write */ + int byte_cs_asrt; + int clock_pol_low; + unsigned int baud_rate; + unsigned int clk_phase; +} MV_SPI_TYPE_INFO; + +/****************************************************************************** +struct define end +*******************************************************************************/ + +/****************************************************************************** +param define +*******************************************************************************/ +static MV_SPI_HAL_DATA spi_hal_data; +static MV_SPI_TYPE_INFO *curr_spi_info = NULL; +static MV_SPI_TYPE_INFO spi_types[] = { {.en16_bit = MV_TRUE, + .clock_pol_low = MV_TRUE, + .byte_cs_asrt = MV_FALSE, + .baud_rate = (20 << 20), /* 20_m */ + .clk_phase = SPI_CLK_BEGIN_CYC}, + {.en16_bit = MV_FALSE, + .clock_pol_low = MV_TRUE, + .byte_cs_asrt = MV_TRUE, + .baud_rate = 0x00800000, + .clk_phase = SPI_CLK_BEGIN_CYC}, + {.en16_bit = MV_FALSE, + .clock_pol_low = MV_TRUE, + .byte_cs_asrt = MV_FALSE, + .baud_rate = 0x00800000, + .clk_phase = SPI_CLK_BEGIN_CYC}, + {.en16_bit = MV_FALSE, + .clock_pol_low = MV_TRUE, + .byte_cs_asrt = MV_TRUE, + .baud_rate = 0x00800000, + .clk_phase = SPI_CLK_HALF_CYC}, + {.en16_bit = MV_FALSE, + .clock_pol_low = MV_FALSE, + .byte_cs_asrt = MV_TRUE, + .baud_rate = 0x00200000, + .clk_phase = SPI_CLK_HALF_CYC} }; + +/****************************************************************************** +param define end +*******************************************************************************/ + +static struct spi_slave s_spi; + +static int mv_spi_baud_rate_set(unsigned char spi_id, + unsigned int serial_baud_rate); +static void mv_spi_cs_deassert(unsigned char spi_id); +static int mv_spi_cs_set(unsigned char spi_id, unsigned char cs_id); +static int mv_spi_if_config_set(unsigned char spi_id, + MV_SPI_IF_PARAMS *if_params); +static int mv_spi_params_set(unsigned char spi_id, + unsigned char cs_id, + MV_SPI_TYPE type); +static int mv_spi_init(unsigned char spi_id, + unsigned char cs_id, + unsigned int serial_baud_rate, + MV_SPI_HAL_DATA *hal_data); +static int mv_spi_sys_init(unsigned char spi_id, + unsigned char cs_id, + unsigned int serial_baud_rate); +static void mv_spi_cs_assert(unsigned char spi_id); +static int mv_spi_8bit_data_tx_rx(unsigned char spi_id, + unsigned char tx_data, + unsigned char *p_rx_data); + +int mv_spi_baud_rate_set(unsigned char spi_id, unsigned int serial_baud_rate) +{ + unsigned int spr, sppr; + unsigned int divider; + unsigned int best_spr = 0, best_sppr = 0; + unsigned char exact_match = 0; + unsigned int min_baud_offset = 0xFFFFFFFF; + unsigned int cpu_clk = spi_hal_data.tclk; /*mv_cpu_pclk_get();*/ + unsigned int temp_reg; + + assert(cpu_clk != serial_baud_rate); + /* Find the best prescale configuration - less or equal */ + for (spr = 1; spr <= 15; spr++) { + for (sppr = 0; sppr <= 7; sppr++) { + divider = spr * (1 << sppr); + /* check for higher - irrelevant */ + if ((cpu_clk / divider) > serial_baud_rate) + continue; + + /* check for exact fit */ + if ((cpu_clk / divider) == serial_baud_rate) { + best_spr = spr; + best_sppr = sppr; + exact_match = 1; + break; + } + + /* check if this is better than the previous one */ + if ((serial_baud_rate - (cpu_clk / divider)) < + min_baud_offset) { + min_baud_offset = + serial_baud_rate - cpu_clk / divider; + best_spr = spr; + best_sppr = sppr; + } + } + + if (exact_match == 1) + break; + } + + if (best_spr == 0) { + printk(BIOS_INFO, "%s ERROR: SPI baud rate prescale error!\n", + __func__); + return MV_OUT_OF_RANGE; + } + + /* configure the Prescale */ + temp_reg = MV_SPI_REG_READ(MV_SPI_IF_CONFIG_REG(spi_id)) & + ~(MV_SPI_SPR_MASK | MV_SPI_SPPR_0_MASK | MV_SPI_SPPR_HI_MASK); + temp_reg |= ((best_spr << MV_SPI_SPR_OFFSET) | + ((best_sppr & 0x1) << MV_SPI_SPPR_0_OFFSET) | + ((best_sppr >> 1) << MV_SPI_SPPR_HI_OFFSET)); + MV_SPI_REG_WRITE(MV_SPI_IF_CONFIG_REG(spi_id), temp_reg); + + return MV_OK; +} + +void mv_spi_cs_deassert(unsigned char spi_id) +{ + MV_SPI_REG_BIT_RESET(MV_SPI_IF_CTRL_REG(spi_id), MV_SPI_CS_ENABLE_MASK); +} + +int mv_spi_cs_set(unsigned char spi_id, unsigned char cs_id) +{ + unsigned int ctrl_reg; + static unsigned char last_cs_id = 0xFF; + static unsigned char last_spi_id = 0xFF; + + if (cs_id > 7) + return MV_BAD_PARAM; + + if ((last_spi_id == spi_id) && (last_cs_id == cs_id)) + return MV_OK; + + ctrl_reg = MV_SPI_REG_READ(MV_SPI_IF_CTRL_REG(spi_id)); + ctrl_reg &= ~MV_SPI_CS_NUM_MASK; + ctrl_reg |= (cs_id << MV_SPI_CS_NUM_OFFSET); + MV_SPI_REG_WRITE(MV_SPI_IF_CTRL_REG(spi_id), ctrl_reg); + + last_spi_id = spi_id; + last_cs_id = cs_id; + + return MV_OK; +} + +int mv_spi_if_config_set(unsigned char spi_id, MV_SPI_IF_PARAMS *if_params) +{ + unsigned int ctrl_reg; + + ctrl_reg = MV_SPI_REG_READ(MV_SPI_IF_CONFIG_REG(spi_id)); + + /* Set Clock Polarity */ + ctrl_reg &= ~(MV_SPI_CPOL_MASK | MV_SPI_CPHA_MASK | MV_SPI_TXLSBF_MASK | + MV_SPI_RXLSBF_MASK); + if (if_params->clock_pol_low) + ctrl_reg |= MV_SPI_CPOL_MASK; + + if (if_params->clock_phase == SPI_CLK_BEGIN_CYC) + ctrl_reg |= MV_SPI_CPHA_MASK; + + if (if_params->tx_msb_first) + ctrl_reg |= MV_SPI_TXLSBF_MASK; + + if (if_params->rx_msb_first) + ctrl_reg |= MV_SPI_RXLSBF_MASK; + + MV_SPI_REG_WRITE(MV_SPI_IF_CONFIG_REG(spi_id), ctrl_reg); + + return MV_OK; +} + +int mv_spi_params_set(unsigned char spi_id, + unsigned char cs_id, + MV_SPI_TYPE type) +{ + MV_SPI_IF_PARAMS if_params; + + if (MV_OK != mv_spi_cs_set(spi_id, cs_id)) { + printk(BIOS_INFO, "Error, setting SPI CS failed\n"); + return MV_ERROR; + } + + if (curr_spi_info != (&(spi_types[type]))) { + curr_spi_info = &(spi_types[type]); + mv_spi_baud_rate_set(spi_id, curr_spi_info->baud_rate); + + if_params.clock_pol_low = curr_spi_info->clock_pol_low; + if_params.clock_phase = curr_spi_info->clk_phase; + if_params.tx_msb_first = MV_FALSE; + if_params.rx_msb_first = MV_FALSE; + mv_spi_if_config_set(spi_id, &if_params); + } + + return MV_OK; +} + +int mv_spi_init(unsigned char spi_id, + unsigned char cs_id, + unsigned int serial_baud_rate, + MV_SPI_HAL_DATA *hal_data) +{ + int ret; + unsigned int timing_reg; + + spi_hal_data.ctrl_model = hal_data->ctrl_model; + spi_hal_data.tclk = hal_data->tclk; + + /* Set the serial clock */ + ret = mv_spi_baud_rate_set(spi_id, serial_baud_rate); + if (ret != MV_OK) + return ret; + + /* Configure the default SPI mode to be 8bit */ + MV_SPI_REG_BIT_RESET(MV_SPI_IF_CONFIG_REG(spi_id), + MV_SPI_BYTE_LENGTH_MASK); + + timing_reg = MV_SPI_REG_READ(MV_SPI_TMNG_PARAMS_REG(spi_id)); + timing_reg &= ~MV_SPI_TMISO_SAMPLE_MASK; + timing_reg |= (0x2) << MV_SPI_TMISO_SAMPLE_OFFSET; + MV_SPI_REG_WRITE(MV_SPI_TMNG_PARAMS_REG(spi_id), timing_reg); + + /* Verify that the CS is deasserted */ + mv_spi_cs_deassert(spi_id); + + mv_spi_params_set(spi_id, cs_id, SPI_TYPE_FLASH); + + return MV_OK; +} + +int mv_spi_sys_init(unsigned char spi_id, + unsigned char cs_id, + unsigned int serial_baud_rate) +{ + MV_SPI_HAL_DATA hal_data; + + hal_data.ctrl_model = MV_6810_DEV_ID; + hal_data.tclk = MV_BOARD_TCLK_250MHZ; + + return mv_spi_init(spi_id, cs_id, serial_baud_rate, &hal_data); +} + +void mv_spi_cs_assert(unsigned char spi_id) +{ + MV_SPI_REG_BIT_SET(MV_SPI_IF_CTRL_REG(spi_id), MV_SPI_CS_ENABLE_MASK); +} + +int mv_spi_8bit_data_tx_rx(unsigned char spi_id, + unsigned char tx_data, + unsigned char *p_rx_data) +{ + unsigned int i; + int ready = MV_FALSE; + + if (curr_spi_info->byte_cs_asrt) + mv_spi_cs_assert(spi_id); + + /* First clear the bit in the interrupt cause register */ + MV_SPI_REG_WRITE(MV_SPI_INT_CAUSE_REG(spi_id), 0x0); + + /* Transmit data */ + MV_SPI_REG_WRITE(MV_SPI_DATA_OUT_REG(spi_id), tx_data); + + /* wait with timeout for memory ready */ + for (i = 0; i < MV_SPI_WAIT_RDY_MAX_LOOP; i++) { + if (MV_SPI_REG_READ(MV_SPI_INT_CAUSE_REG(spi_id))) { + ready = MV_TRUE; + break; + } + } + + if (!ready) { + if (curr_spi_info->byte_cs_asrt) { + mv_spi_cs_deassert(spi_id); + /* WA to compansate Zarlink SLIC CS off time */ + udelay(4); + } + return MV_TIMEOUT; + } + + /* check that the RX data is needed */ + if (p_rx_data) + *p_rx_data = MV_SPI_REG_READ(MV_SPI_DATA_IN_REG(spi_id)); + + if (curr_spi_info->byte_cs_asrt) { + mv_spi_cs_deassert(spi_id); + /* WA to compansate Zarlink SLIC CS off time */ + udelay(4); + } + + return MV_OK; +} + +static int mrvl_spi_xfer(struct spi_slave *slave, + unsigned int bitlen, + const void *dout, + void *din) +{ + int ret; + unsigned char *pdout = (unsigned char *)dout; + unsigned char *pdin = (unsigned char *)din; + int tmp_bitlen = bitlen; + unsigned char tmp_dout = 0; + + /* Verify that the SPI mode is in 8bit mode */ + MV_SPI_REG_BIT_RESET(MV_SPI_IF_CONFIG_REG(slave->bus), + MV_SPI_BYTE_LENGTH_MASK); + + while (tmp_bitlen > 0) { + if (pdout) + tmp_dout = (*pdout) & 0xff; + + /* Transmitted and wait for the transfer to be completed */ + ret = mv_spi_8bit_data_tx_rx(slave->bus, tmp_dout, pdin); + if (ret != MV_OK) + return ret; + + /* increment the pointers */ + if (pdin) + pdin++; + if (pdout) + pdout++; + + tmp_bitlen -= 8; + } + return 0; +} + +struct spi_slave *spi_setup_slave(unsigned int bus, unsigned int cs) +{ + struct spi_slave *slave = &s_spi; + + slave->bus = bus; + slave->cs = cs; + mv_spi_sys_init(bus, cs, CONFIG_SF_DEFAULT_SPEED); + return slave; +} + +int spi_claim_bus(struct spi_slave *slave) +{ + mv_spi_cs_set(slave->bus, slave->cs); + mv_spi_cs_assert(slave->bus); + return 0; +} + +void spi_release_bus(struct spi_slave *slave) +{ + mv_spi_cs_deassert(slave->bus); +} + +unsigned int spi_crop_chunk(unsigned int cmd_len, unsigned int buf_len) +{ + return buf_len; +} + +int spi_xfer(struct spi_slave *slave, + const void *dout, + unsigned out_bytes, + void *din, + unsigned in_bytes) +{ + int ret = -1; + + if (out_bytes) + ret = mrvl_spi_xfer(slave, out_bytes * 8, dout, din); + else if (in_bytes) + ret = mrvl_spi_xfer(slave, in_bytes * 8, dout, din); + else + die("Unexpected condition in spi_xfer\n"); + return ret; +} -- cgit v1.2.3