--- a/include/linux/spi/spi.h +++ b/include/linux/spi/spi.h @@ -445,6 +445,8 @@ struct spi_transfer { u16 delay_usecs; u32 speed_hz; + unsigned last_in_message_list; + struct list_head transfer_list; }; --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -167,6 +167,14 @@ config SPI_GPIO_OLD If unsure, say N. +config SPI_CNS21XX + tristate "Cavium Netowrks CNS21xx SPI master" + depends on ARCH_CNS21XX && EXPERIMENTAL + select SPI_BITBANG + help + This driver supports the buil-in SPI controller of the Cavium Networks + CNS21xx SoCs. + config SPI_IMX tristate "Freescale i.MX SPI controllers" depends on ARCH_MXC --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -50,6 +50,7 @@ obj-$(CONFIG_SPI_SH_SCI) += spi_sh_sci. obj-$(CONFIG_SPI_SH_MSIOF) += spi_sh_msiof.o obj-$(CONFIG_SPI_STMP3XXX) += spi_stmp.o obj-$(CONFIG_SPI_NUC900) += spi_nuc900.o +obj-$(CONFIG_SPI_CNS21XX) += spi_cns21xx.o # special build for s3c24xx spi driver with fiq support spi_s3c24xx_hw-y := spi_s3c24xx.o --- a/drivers/spi/spi_bitbang.c +++ b/drivers/spi/spi_bitbang.c @@ -337,6 +337,13 @@ static void bitbang_work(struct work_str */ if (!m->is_dma_mapped) t->rx_dma = t->tx_dma = 0; + + if (t->transfer_list.next == &m->transfers) { + t->last_in_message_list = 1; + } else { + t->last_in_message_list = 0; + } + status = bitbang->txrx_bufs(spi, t); } if (status > 0) --- /dev/null +++ b/drivers/spi/spi_cns21xx.c @@ -0,0 +1,520 @@ +/* + * Copyright (c) 2008 Cavium Networks + * Copyright (c) 2010 Gabor Juhos + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, Version 2, as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define DRIVER_NAME "cns21xx-spi" + +#ifdef CONFIG_CNS21XX_SPI_DEBUG +#define DBG(fmt, args...) pr_info("[CNS21XX_SPI_DEBUG]" fmt, ## args) +#else +#define DBG(fmt, args...) do {} while (0) +#endif /* CNS21XX_SPI_DEBUG */ + +#define SPI_REG_CFG 0x40 +#define SPI_REG_STAT 0x44 +#define SPI_REG_BIT_RATE 0x48 +#define SPI_REG_TX_CTRL 0x4c +#define SPI_REG_TX_DATA 0x50 +#define SPI_REG_RX_CTRL 0x54 +#define SPI_REG_RX_DATA 0x58 +#define SPI_REG_FIFO_TX_CFG 0x5c +#define SPI_REG_FIFO_TX_CTRL 0x60 +#define SPI_REG_FIFO_RX_CFG 0x64 +#define SPI_REG_INTR_STAT 0x68 +#define SPI_REG_INTR_ENA 0x6c + +#define CFG_SPI_EN BIT(31) +#define CFG_SPI_CLKPOL BIT(14) +#define CFG_SPI_CLKPHA BIT(13) +#define CFG_SPI_MASTER_EN BIT(11) +#define CFG_SPI_CHAR_LEN_M 0x3 +#define CFG_SPI_CHAR_LEN_8BITS 0 +#define CFG_SPI_CHAR_LEN_16BITS 1 +#define CFG_SPI_CHAR_LEN_24BITS 2 +#define CFG_SPI_CHAR_LEN_32BITS 3 + +#define STAT_SPI_BUSY_STA BIT(1) + +#define BIT_RATE_DIV_1 0 +#define BIT_RATE_DIV_2 1 +#define BIT_RATE_DIV_4 2 +#define BIT_RATE_DIV_8 3 +#define BIT_RATE_DIV_16 4 +#define BIT_RATE_DIV_32 5 +#define BIT_RATE_DIV_64 6 +#define BIT_RATE_DIV_128 7 + +#define TX_CTRL_SPI_TXDAT_EOF BIT(2) +#define TX_CTRL_SPI_TXCH_NUM_M 0x3 +#define TX_CTRL_CLEAR_MASK (TX_CTRL_SPI_TXDAT_EOF | \ + TX_CTRL_SPI_TXCH_NUM_M) + +#define RX_CTRL_SPI_RXDAT_EOF BIT(2) +#define RX_CTRL_SPI_RXCH_NUM_M 0x3 + +#define INTR_STAT_SPI_TXBF_UNRN_FG BIT(7) +#define INTR_STAT_SPI_RXBF_OVRN_FG BIT(6) +#define INTR_STAT_SPI_TXFF_UNRN_FG BIT(5) +#define INTR_STAT_SPI_RXFF_OVRN_FG BIT(4) +#define INTR_STAT_SPI_TXBUF_FG BIT(3) +#define INTR_STAT_SPI_RXBUF_FG BIT(2) +#define INTR_STAT_SPI_TXFF_FG BIT(1) +#define INTR_STAT_SPI_RXFF_FG BIT(0) + +#define INTR_STAT_CLEAR_MASK (INTR_STAT_SPI_TXBF_UNRN_FG | \ + INTR_STAT_SPI_RXBF_OVRN_FG | \ + INTR_STAT_SPI_TXFF_UNRN_FG | \ + INTR_STAT_SPI_RXFF_OVRN_FG) + +#define FIFO_TX_CFG_SPI_TXFF_THRED_M 0x3 +#define FIFO_TX_CFG_SPI_TXFF_THRED_S 4 +#define FIFO_TX_CFG_SPI_TXFF_THRED_2 0 +#define FIFO_TX_CFG_SPI_TXFF_THRED_4 1 +#define FIFO_TX_CFG_SPI_TXFF_THRED_6 0 +#define FIFO_TX_CFG_SPI_TXFF_STATUS_M 0xf + +#define FIFO_RX_CFG_SPI_RXFF_THRED_M 0x3 +#define FIFO_RX_CFG_SPI_RXFF_THRED_S 4 +#define FIFO_RX_CFG_SPI_RXFF_THRED_2 0 +#define FIFO_RX_CFG_SPI_RXFF_THRED_4 1 +#define FIFO_RX_CFG_SPI_RXFF_THRED_6 0 +#define FIFO_RX_CFG_SPI_RXFF_STATUS_M 0xf + +#define CNS21XX_SPI_NUM_BIT_RATES 8 + +struct cns21xx_spi { + struct spi_bitbang bitbang; + + struct spi_master *master; + struct device *dev; + void __iomem *base; + struct resource *region; + + unsigned freq_max; + unsigned freq_min; + +}; + +static inline struct cns21xx_spi *to_hw(struct spi_device *spi) +{ + return spi_master_get_devdata(spi->master); +} + +static inline u32 cns21xx_spi_rr(struct cns21xx_spi *hw, unsigned int reg) +{ + return __raw_readl(hw->base + reg); +} + +static inline void cns21xx_spi_wr(struct cns21xx_spi *hw, u32 val, + unsigned int reg) +{ + __raw_writel(val, hw->base + reg); +} + +#define CNS21XX_SPI_RETRY_COUNT 100 +static inline int cns21xx_spi_wait(struct cns21xx_spi *hw, unsigned int reg, + u32 mask, u32 val) +{ + int retry_cnt = 0; + + do { + if ((cns21xx_spi_rr(hw, reg) & mask) == val) + break; + + if (++retry_cnt > CNS21XX_SPI_RETRY_COUNT) { + dev_err(hw->dev, "timeout waiting on register %02x\n", + reg); + return -EIO; + } + } while (1); + + return 0; +} + +static int cns21xx_spi_txrx_word(struct cns21xx_spi *hw, u8 tx_channel, + u8 tx_eof_flag, u32 tx_data, u32 *rx_data) +{ + unsigned int tx_ctrl; + u8 rx_channel; + u8 rx_eof_flag; + int err = 0; + + err = cns21xx_spi_wait(hw, SPI_REG_STAT, STAT_SPI_BUSY_STA, 0); + if (err) + return err; + + err = cns21xx_spi_wait(hw, SPI_REG_INTR_STAT, INTR_STAT_SPI_TXBUF_FG, + INTR_STAT_SPI_TXBUF_FG); + if (err) + return err; + + tx_ctrl = cns21xx_spi_rr(hw, SPI_REG_TX_CTRL); + tx_ctrl &= ~(TX_CTRL_CLEAR_MASK); + tx_ctrl |= (tx_channel & TX_CTRL_SPI_TXCH_NUM_M); + tx_ctrl |= (tx_eof_flag) ? TX_CTRL_SPI_TXDAT_EOF : 0; + cns21xx_spi_wr(hw, tx_ctrl, SPI_REG_TX_CTRL); + + cns21xx_spi_wr(hw, tx_data, SPI_REG_TX_DATA); + + err = cns21xx_spi_wait(hw, SPI_REG_INTR_STAT, INTR_STAT_SPI_RXBUF_FG, + INTR_STAT_SPI_RXBUF_FG); + if (err) + return err; + + rx_channel = cns21xx_spi_rr(hw, SPI_REG_RX_CTRL) & + RX_CTRL_SPI_RXCH_NUM_M; + + rx_eof_flag = (cns21xx_spi_rr(hw, SPI_REG_RX_CTRL) & + RX_CTRL_SPI_RXDAT_EOF) ? 1 : 0; + + *rx_data = cns21xx_spi_rr(hw, SPI_REG_RX_DATA); + + if ((tx_channel != rx_channel) || (tx_eof_flag != rx_eof_flag)) + return -EPROTO; + + return 0; +} + +static void cns21xx_spi_chipselect(struct spi_device *spi, int value) +{ + struct cns21xx_spi *hw = to_hw(spi); + unsigned int spi_config; + unsigned int tx_ctrl; + + switch (value) { + case BITBANG_CS_INACTIVE: + break; + + case BITBANG_CS_ACTIVE: + spi_config = cns21xx_spi_rr(hw, SPI_REG_CFG); + + if (spi->mode & SPI_CPHA) + spi_config |= CFG_SPI_CLKPHA; + else + spi_config &= ~CFG_SPI_CLKPHA; + + if (spi->mode & SPI_CPOL) + spi_config |= CFG_SPI_CLKPOL; + else + spi_config &= ~CFG_SPI_CLKPOL; + + cns21xx_spi_wr(hw, spi_config, SPI_REG_CFG); + + tx_ctrl = cns21xx_spi_rr(hw, SPI_REG_TX_CTRL); + tx_ctrl &= ~(TX_CTRL_CLEAR_MASK); + tx_ctrl |= (spi->chip_select & TX_CTRL_SPI_TXCH_NUM_M); + cns21xx_spi_wr(hw, tx_ctrl, SPI_REG_TX_CTRL); + + break; + } +} + +static int cns21xx_spi_setup(struct spi_device *spi) +{ + struct cns21xx_spi *hw = to_hw(spi); + + if (spi->bits_per_word != 8) { + dev_err(&spi->dev, "%s: invalid bits_per_word=%u\n", + __func__, spi->bits_per_word); + return -EINVAL; + } + + if (spi->max_speed_hz == 0) + spi->max_speed_hz = hw->freq_max; + + if (spi->max_speed_hz > hw->freq_max || + spi->max_speed_hz < hw->freq_min) { + dev_err(&spi->dev, "%s: max_speed_hz=%u out of range\n", + __func__, spi->max_speed_hz); + return -EINVAL; + } + + return 0; +} + +static int cns21xx_spi_setup_transfer(struct spi_device *spi, + struct spi_transfer *t) +{ + struct cns21xx_spi *hw = to_hw(spi); + u8 bits_per_word; + u32 hz; + int i; + + bits_per_word = (t) ? t->bits_per_word : spi->bits_per_word; + hz = t ? t->speed_hz : spi->max_speed_hz; + + if (!bits_per_word) + bits_per_word = spi->bits_per_word; + + if (!hz) + hz = spi->max_speed_hz; + + if (bits_per_word != 8) { + dev_err(&spi->dev, "%s: invalid bits_per_word=%u\n", + __func__, bits_per_word); + return -EINVAL; + } + + if (hz > spi->max_speed_hz || hz > hw->freq_max || hz < hw->freq_min) { + dev_err(&spi->dev, "%s: max_speed_hz=%u out of range\n", + __func__, hz); + return -EINVAL; + } + + for (i = 0; i < CNS21XX_SPI_NUM_BIT_RATES; i++) + if (spi->max_speed_hz > (cns21xx_get_apb_freq() >> i)) + break; + + DBG("max_speed:%uHz, curr_speed:%luHz, rate_index=%d\n", + spi->max_speed_hz, cns21xx_get_apb_freq() / (1 << i), i); + + cns21xx_spi_wr(hw, i, SPI_REG_BIT_RATE); + + return 0; +} + +static int cns21xx_spi_txrx(struct spi_device *spi, struct spi_transfer *t) +{ + struct cns21xx_spi *hw = to_hw(spi); + const unsigned char *tx_buf; + unsigned char *rx_buf; + u32 rx_data; + int tx_eof; + int err = 0; + int i; + + tx_buf = t->tx_buf; + rx_buf = t->rx_buf; + tx_eof = t->last_in_message_list; + + DBG("txrx: tx %p, rx %p, len %d\n", tx_buf, rx_buf, t->len); + + if (tx_buf) { + for (i = 0; i < t->len; i++) + DBG("tx_buf[%02d]: 0x%02x\n", i, tx_buf[i]); + + for (i = 0; i < (t->len - 1); i++) { + err = cns21xx_spi_txrx_word(hw, spi->chip_select, 0, + tx_buf[i], &rx_data); + if (err) + goto done; + + if (rx_buf) { + rx_buf[i] = rx_data; + DBG("rx_buf[%02d]:0x%02x\n", i, rx_buf[i]); + } + } + + err = cns21xx_spi_txrx_word(hw, spi->chip_select, tx_eof, + tx_buf[i], &rx_data); + if (err) + goto done; + + if ((tx_eof) && rx_buf) { + rx_buf[i] = rx_data; + DBG("rx_buf[%02d]:0x%02x\n", i, rx_buf[i]); + } + } else if (rx_buf) { + for (i = 0; i < (t->len - 1); i++) { + err = cns21xx_spi_txrx_word(hw, spi->chip_select, 0, + 0xff, &rx_data); + if (err) + goto done; + + rx_buf[i] = rx_data; + DBG("rx_buf[%02d]:0x%02x\n", i, rx_buf[i]); + } + + err = cns21xx_spi_txrx_word(hw, spi->chip_select, tx_eof, + 0xff, &rx_data); + if (err) + goto done; + + rx_buf[i] = rx_data; + DBG("rx_buf[%02d]:0x%02x\n", i, rx_buf[i]); + } + + done: + return (err) ? err : t->len; +} + +static void __init cns21xx_spi_hw_init(struct cns21xx_spi *hw) +{ + u32 t; + u32 pclk; + + /* Setup configuration register */ + cns21xx_spi_wr(hw, CFG_SPI_MASTER_EN, SPI_REG_CFG); + + /* Set default clock to PCLK/2 */ + cns21xx_spi_wr(hw, BIT_RATE_DIV_2, SPI_REG_BIT_RATE); + + /* Configure SPI's Tx channel */ + cns21xx_spi_wr(hw, 0, SPI_REG_TX_CTRL); + + /* Configure Tx FIFO Threshold */ + t = cns21xx_spi_rr(hw, SPI_REG_FIFO_TX_CFG); + t &= ~(FIFO_TX_CFG_SPI_TXFF_THRED_M << FIFO_TX_CFG_SPI_TXFF_THRED_S); + t |= (FIFO_TX_CFG_SPI_TXFF_THRED_2 << FIFO_TX_CFG_SPI_TXFF_THRED_S); + cns21xx_spi_wr(hw, t, SPI_REG_FIFO_TX_CFG); + + /* Configure Rx FIFO Threshold */ + t = cns21xx_spi_rr(hw, SPI_REG_FIFO_RX_CFG); + t &= ~(FIFO_RX_CFG_SPI_RXFF_THRED_M << FIFO_RX_CFG_SPI_RXFF_THRED_S); + t |= (FIFO_RX_CFG_SPI_RXFF_THRED_2 << FIFO_RX_CFG_SPI_RXFF_THRED_S); + cns21xx_spi_wr(hw, t, SPI_REG_FIFO_RX_CFG); + + /* Disable interrupts, and clear interrupt status */ + cns21xx_spi_wr(hw, 0, SPI_REG_INTR_ENA); + cns21xx_spi_wr(hw, INTR_STAT_CLEAR_MASK, SPI_REG_INTR_STAT); + + (void) cns21xx_spi_rr(hw, SPI_REG_RX_DATA); + + /* Enable SPI */ + t = cns21xx_spi_rr(hw, SPI_REG_CFG); + t |= CFG_SPI_EN; + cns21xx_spi_wr(