diff options
Diffstat (limited to 'target/linux/cns21xx/patches-3.3/105-cns21xx-spi-driver.patch')
-rw-r--r-- | target/linux/cns21xx/patches-3.3/105-cns21xx-spi-driver.patch | 578 |
1 files changed, 578 insertions, 0 deletions
diff --git a/target/linux/cns21xx/patches-3.3/105-cns21xx-spi-driver.patch b/target/linux/cns21xx/patches-3.3/105-cns21xx-spi-driver.patch new file mode 100644 index 0000000..575cc7f --- /dev/null +++ b/target/linux/cns21xx/patches-3.3/105-cns21xx-spi-driver.patch @@ -0,0 +1,578 @@ +--- a/include/linux/spi/spi.h ++++ b/include/linux/spi/spi.h +@@ -456,6 +456,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 +@@ -163,6 +163,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 +@@ -18,6 +18,7 @@ obj-$(CONFIG_SPI_BFIN) += spi-bfin5xx. + obj-$(CONFIG_SPI_BFIN_SPORT) += spi-bfin-sport.o + obj-$(CONFIG_SPI_BITBANG) += spi-bitbang.o + obj-$(CONFIG_SPI_BUTTERFLY) += spi-butterfly.o ++obj-$(CONFIG_SPI_CNS21XX) += spi-cns21xx.o + obj-$(CONFIG_SPI_COLDFIRE_QSPI) += spi-coldfire-qspi.o + obj-$(CONFIG_SPI_DAVINCI) += spi-davinci.o + obj-$(CONFIG_SPI_DESIGNWARE) += spi-dw.o +--- a/drivers/spi/spi-bitbang.c ++++ b/drivers/spi/spi-bitbang.c +@@ -330,6 +330,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,521 @@ ++/* ++ * Copyright (c) 2008 Cavium Networks ++ * Copyright (c) 2010-2012 Gabor Juhos <juhosg@openwrt.org> ++ * ++ * 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 <linux/init.h> ++#include <linux/module.h> ++#include <linux/spinlock.h> ++#include <linux/workqueue.h> ++#include <linux/interrupt.h> ++#include <linux/delay.h> ++#include <linux/errno.h> ++#include <linux/platform_device.h> ++#include <linux/io.h> ++#include <linux/spi/spi.h> ++#include <linux/spi/spi_bitbang.h> ++ ++#include <mach/hardware.h> ++#include <mach/cns21xx.h> ++ ++#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(hw, t, SPI_REG_CFG); ++ ++ pclk = cns21xx_get_apb_freq(); ++ hw->freq_max = pclk; ++ hw->freq_min = pclk / (1 << BIT_RATE_DIV_128); ++} ++ ++static int __init cns21xx_spi_probe(struct platform_device *pdev) ++{ ++ struct cns21xx_spi *hw; ++ struct spi_master *master; ++ struct resource *res; ++ int err = 0; ++ ++ master = spi_alloc_master(&pdev->dev, sizeof(struct cns21xx_spi)); ++ if (!master) { ++ dev_err(&pdev->dev, "No memory for spi_master\n"); ++ return -ENOMEM; ++ } ++ ++ hw = spi_master_get_devdata(master); ++ ++ platform_set_drvdata(pdev, hw); ++ hw->master = spi_master_get(master); ++ hw->dev = &pdev->dev; ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ if (!res) { ++ dev_dbg(&pdev->dev, "no MEM resource found\n"); ++ err = -ENOENT; ++ goto err_put_master; ++ } ++ ++ hw->region = request_mem_region(res->start, resource_size(res), ++ dev_name(&pdev->dev)); ++ if (!hw->region) { ++ dev_err(&pdev->dev, "unable to reserve iomem region\n"); ++ err = -ENXIO; ++ goto err_put_master; ++ } ++ ++ hw->base = ioremap(res->start, resource_size(res)); ++ if (!hw->base) { ++ dev_err(&pdev->dev, "ioremap failed\n"); ++ err = -ENOENT; ++ goto err_release_region; ++ } ++ ++ cns21xx_spi_hw_init(hw); ++ ++ master->bus_num = pdev->id; ++ if (master->bus_num == -1) ++ master->bus_num = 0; ++ ++ master->num_chipselect = 4; ++ master->setup = cns21xx_spi_setup; ++ ++ hw->bitbang.master = hw->master; ++ hw->bitbang.chipselect = cns21xx_spi_chipselect; ++ hw->bitbang.txrx_bufs = cns21xx_spi_txrx; ++ hw->bitbang.setup_transfer = cns21xx_spi_setup_transfer; ++ ++ err = spi_bitbang_start(&hw->bitbang); ++ if (err) { ++ dev_err(hw->dev, "unable to register SPI master\n"); ++ goto err_unmap; ++ } ++ ++ dev_info(hw->dev, "iomem at %08x\n", res->start); ++ ++ return 0; ++ ++ err_unmap: ++ iounmap(hw->base); ++ ++ err_release_region: ++ release_resource(hw->region); ++ kfree(hw->region); ++ ++ err_put_master: ++ spi_master_put(hw->bitbang.master); ++ platform_set_drvdata(pdev, NULL); ++ ++ return err; ++} ++ ++static int __devexit cns21xx_spi_remove(struct platform_device *pdev) ++{ ++ struct cns21xx_spi *hw = platform_get_drvdata(pdev); ++ ++ spi_bitbang_stop(&hw->bitbang); ++ iounmap(hw->base); ++ release_resource(hw->region); ++ kfree(hw->region); ++ spi_master_put(hw->bitbang.master); ++ platform_set_drvdata(pdev, NULL); ++ ++ return 0; ++} ++ ++static struct platform_driver cns21xx_spi_driver = { ++ .remove = __devexit_p(cns21xx_spi_remove), ++ .driver = { ++ .name = DRIVER_NAME, ++ .owner = THIS_MODULE, ++ }, ++}; ++ ++static int __init cns21xx_spi_init(void) ++{ ++ return platform_driver_probe(&cns21xx_spi_driver, cns21xx_spi_probe); ++} ++ ++static void __exit cns21xx_spi_exit(void) ++{ ++ platform_driver_unregister(&cns21xx_spi_driver); ++} ++ ++module_init(cns21xx_spi_init); ++module_exit(cns21xx_spi_exit); ++ ++MODULE_DESCRIPTION("Cavium Networks CNS21xx SPI Controller driver"); ++MODULE_AUTHOR("STAR Semi Corp."); ++MODULE_AUTHOR("Gabor Juhos <juhosg@openwrt.org>"); ++MODULE_LICENSE("GPL v2"); ++MODULE_ALIAS("platform:" DRIVER_NAME); |