/* * 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 #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 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_tclk_get(); 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(const struct spi_slave *slave, size_t 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; } static int spi_ctrlr_claim_bus(const struct spi_slave *slave) { mv_spi_cs_set(slave->bus, slave->cs); mv_spi_cs_assert(slave->bus); return 0; } static void spi_ctrlr_release_bus(const struct spi_slave *slave) { mv_spi_cs_deassert(slave->bus); } static int spi_ctrlr_xfer(const struct spi_slave *slave, const void *dout, size_t out_bytes, void *din, size_t 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; } static const spi_ctrlr spi_ctrlr = { .claim_bus = spi_ctrlr_claim_bus, .release_bus = spi_ctrlr_release_bus, .xfer = spi_ctrlr_xfer, .max_xfer_size = SPI_CTRLR_DEFAULT_MAX_XFER_SIZE, }; int spi_setup_slave(unsigned int bus, unsigned int cs, struct spi_slave *slave) { slave->bus = bus; slave->cs = cs; slave->ctrlr = &spi_ctrlr; mv_spi_sys_init(bus, cs, CONFIG_SF_DEFAULT_SPEED); return 0; }