--- a/arch/mips/bcm63xx/cpu.c +++ b/arch/mips/bcm63xx/cpu.c @@ -55,6 +55,7 @@ static const unsigned long bcm96338_regs static const int bcm96338_irqs[] = { [IRQ_TIMER] = BCM_6338_TIMER_IRQ, + [IRQ_SPI] = BCM_6338_SPI_IRQ, [IRQ_UART0] = BCM_6338_UART0_IRQ, [IRQ_DSL] = BCM_6338_DSL_IRQ, [IRQ_ENET0] = BCM_6338_ENET0_IRQ, @@ -127,6 +128,7 @@ static const unsigned long bcm96348_regs static const int bcm96348_irqs[] = { [IRQ_TIMER] = BCM_6348_TIMER_IRQ, + [IRQ_SPI] = BCM_6348_SPI_IRQ, [IRQ_UART0] = BCM_6348_UART0_IRQ, [IRQ_DSL] = BCM_6348_DSL_IRQ, [IRQ_ENET0] = BCM_6348_ENET0_IRQ, @@ -169,6 +171,7 @@ static const unsigned long bcm96358_regs static const int bcm96358_irqs[] = { [IRQ_TIMER] = BCM_6358_TIMER_IRQ, + [IRQ_SPI] = BCM_6358_SPI_IRQ, [IRQ_UART0] = BCM_6358_UART0_IRQ, [IRQ_DSL] = BCM_6358_DSL_IRQ, [IRQ_ENET0] = BCM_6358_ENET0_IRQ, --- /dev/null +++ b/arch/mips/bcm63xx/dev-spi.c @@ -0,0 +1,60 @@ +/* + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Copyright (C) 2009 Florian Fainelli + */ + +#include +#include +#include + +#include +#include +#include + +static struct resource spi_resources[] = { + { + .start = -1, /* filled at runtime */ + .end = -1, /* filled at runtime */ + .flags = IORESOURCE_MEM, + }, + { + .start = -1, /* filled at runtime */ + .flags = IORESOURCE_IRQ, + }, +}; + +static struct bcm63xx_spi_pdata spi_pdata = { + .bus_num = 0, + .num_chipselect = 4, + .speed_hz = 50000000, /* Fclk */ +}; + +static struct platform_device bcm63xx_spi_device = { + .name = "bcm63xx-spi", + .id = 0, + .num_resources = ARRAY_SIZE(spi_resources), + .resource = spi_resources, + .dev = { + .platform_data = &spi_pdata, + }, +}; + +int __init bcm63xx_spi_register(void) +{ + spi_resources[0].start = bcm63xx_regset_address(RSET_SPI); + spi_resources[0].end = spi_resources[0].start; + spi_resources[0].end += RSET_SPI_SIZE - 1; + spi_resources[1].start = bcm63xx_get_irq_number(IRQ_SPI); + + /* Fill in platform data */ + if (BCMCPU_IS_6338() || BCMCPU_IS_6348()) + spi_pdata.fifo_size = SPI_BCM_6338_SPI_MSG_DATA_SIZE; + + if (BCMCPU_IS_6358()) + spi_pdata.fifo_size = SPI_BCM_6358_SPI_MSG_DATA_SIZE; + + return platform_device_register(&bcm63xx_spi_device); +} --- a/arch/mips/include/asm/mach-bcm63xx/bcm63xx_cpu.h +++ b/arch/mips/include/asm/mach-bcm63xx/bcm63xx_cpu.h @@ -108,6 +108,7 @@ enum bcm63xx_regs_set { #define RSET_WDT_SIZE 12 #define RSET_ENET_SIZE 2048 #define RSET_ENETDMA_SIZE 2048 +#define RSET_SPI_SIZE 256 #define RSET_UART_SIZE 24 #define RSET_UDC_SIZE 256 #define RSET_OHCI_SIZE 256 @@ -209,7 +210,7 @@ enum bcm63xx_regs_set { #define BCM_6358_WDT_BASE (0xfffe005c) #define BCM_6358_UART0_BASE (0xfffe0100) #define BCM_6358_GPIO_BASE (0xfffe0080) -#define BCM_6358_SPI_BASE (0xdeadbeef) +#define BCM_6358_SPI_BASE (0xfffe0800) #define BCM_6358_UDC0_BASE (0xfffe0400) #define BCM_6358_OHCI0_BASE (0xfffe1400) #define BCM_6358_OHCI_PRIV_BASE (0xdeadbeef) @@ -428,6 +429,7 @@ static inline unsigned long bcm63xx_regs */ enum bcm63xx_irq { IRQ_TIMER = 0, + IRQ_SPI, IRQ_UART0, IRQ_DSL, IRQ_UDC0, @@ -493,6 +495,7 @@ enum bcm63xx_irq { * 6348 irqs */ #define BCM_6348_TIMER_IRQ (IRQ_INTERNAL_BASE + 0) +#define BCM_6348_SPI_IRQ (IRQ_INTERNAL_BASE + 1) #define BCM_6348_UART0_IRQ (IRQ_INTERNAL_BASE + 2) #define BCM_6348_DSL_IRQ (IRQ_INTERNAL_BASE + 4) #define BCM_6348_UDC0_IRQ (IRQ_INTERNAL_BASE + 6) @@ -517,6 +520,7 @@ enum bcm63xx_irq { * 6358 irqs */ #define BCM_6358_TIMER_IRQ (IRQ_INTERNAL_BASE + 0) +#define BCM_6358_SPI_IRQ (IRQ_INTERNAL_BASE + 1) #define BCM_6358_UART0_IRQ (IRQ_INTERNAL_BASE + 2) #define BCM_6358_OHCI0_IRQ (IRQ_INTERNAL_BASE + 5) #define BCM_6358_ENET1_IRQ (IRQ_INTERNAL_BASE + 6) --- a/arch/mips/include/asm/mach-bcm63xx/bcm63xx_regs.h +++ b/arch/mips/include/asm/mach-bcm63xx/bcm63xx_regs.h @@ -769,5 +769,117 @@ #define DMIPSPLLCFG_N2_SHIFT 29 #define DMIPSPLLCFG_N2_MASK (0x7 << DMIPSPLLCFG_N2_SHIFT) +/************************************************************************* + * _REG relative to RSET_SPI + *************************************************************************/ + +/* BCM 6338 SPI core */ +#define SPI_BCM_6338_SPI_CMD 0x00 /* 16-bits register */ +#define SPI_BCM_6338_SPI_INT_STATUS 0x02 +#define SPI_BCM_6338_SPI_MASK_INT_ST 0x03 +#define SPI_BCM_6338_SPI_INT_MASK 0x04 +#define SPI_BCM_6338_SPI_ST 0x05 +#define SPI_BCM_6338_SPI_CLK_CFG 0x06 +#define SPI_BCM_6338_SPI_FILL_BYTE 0x07 +#define SPI_BCM_6338_SPI_MSG_TAIL 0x09 +#define SPI_BCM_6338_SPI_RX_TAIL 0x0b +#define SPI_BCM_6338_SPI_MSG_CTL 0x40 +#define SPI_BCM_6338_SPI_MSG_DATA 0x41 +#define SPI_BCM_6338_SPI_MSG_DATA_SIZE 0x3f +#define SPI_BCM_6338_SPI_RX_DATA 0x80 +#define SPI_BCM_6338_SPI_RX_DATA_SIZE 0x3f + +/* BCM 6348 SPI core */ +#define SPI_BCM_6348_SPI_MASK_INT_ST 0x00 +#define SPI_BCM_6348_SPI_INT_STATUS 0x01 +#define SPI_BCM_6348_SPI_CMD 0x02 /* 16-bits register */ +#define SPI_BCM_6348_SPI_FILL_BYTE 0x04 +#define SPI_BCM_6348_SPI_CLK_CFG 0x05 +#define SPI_BCM_6348_SPI_ST 0x06 +#define SPI_BCM_6348_SPI_INT_MASK 0x07 +#define SPI_BCM_6348_SPI_RX_TAIL 0x08 +#define SPI_BCM_6348_SPI_MSG_TAIL 0x10 +#define SPI_BCM_6348_SPI_MSG_DATA 0x40 +#define SPI_BCM_6348_SPI_MSG_CTL 0x42 +#define SPI_BCM_6348_SPI_MSG_DATA_SIZE 0x3f +#define SPI_BCM_6348_SPI_RX_DATA 0x80 +#define SPI_BCM_6348_SPI_RX_DATA_SIZE 0x3f + +/* BCM 6358 SPI core */ +#define SPI_BCM_6358_MSG_CTL 0x00 /* 16-bits register */ + +#define SPI_BCM_6358_SPI_MSG_DATA 0x02 +#define SPI_BCM_6358_SPI_MSG_DATA_SIZE 0x21e + +#define SPI_BCM_6358_SPI_RX_DATA 0x400 +#define SPI_BCM_6358_SPI_RX_DATA_SIZE 0x220 + +#define SPI_BCM_6358_SPI_CMD 0x700 /* 16-bits register */ + +#define SPI_BCM_6358_SPI_INT_STATUS 0x702 +#define SPI_BCM_6358_SPI_MASK_INT_ST 0x703 + +#define SPI_BCM_6358_SPI_INT_MASK 0x704 + +#define SPI_BCM_6358_SPI_STATUS 0x705 + +#define SPI_BCM_6358_SPI_CLK_CFG 0x706 + +#define SPI_BCM_6358_SPI_FILL_BYTE 0x707 +#define SPI_BCM_6358_SPI_MSG_TAIL 0x709 +#define SPI_BCM_6358_SPI_RX_TAIL 0x70B + +/* Shared SPI definitions */ + +/* Message configuration */ +#define SPI_FD_RW 0x00 +#define SPI_HD_W 0x01 +#define SPI_HD_R 0x02 +#define SPI_BYTE_CNT_SHIFT 0 +#define SPI_MSG_TYPE_SHIFT 14 + +/* Command */ +#define SPI_CMD_NOOP 0x01 +#define SPI_CMD_SOFT_RESET 0x02 +#define SPI_CMD_HARD_RESET 0x04 +#define SPI_CMD_START_IMMEDIATE 0x08 +#define SPI_CMD_COMMAND_SHIFT 0 +#define SPI_CMD_COMMAND_MASK 0x000f +#define SPI_CMD_DEVICE_ID_SHIFT 4 +#define SPI_CMD_PREPEND_BYTE_CNT_SHIFT 8 +#define SPI_CMD_ONE_BYTE_SHIFT 11 +#define SPI_CMD_ONE_WIRE_SHIFT 12 +#define SPI_DEV_ID_0 0 +#define SPI_DEV_ID_1 1 +#define SPI_DEV_ID_2 2 +#define SPI_DEV_ID_3 3 + +/* Interrupt mask */ +#define SPI_INTR_CMD_DONE 0x01 +#define SPI_INTR_RX_OVERFLOW 0x02 +#define SPI_INTR_TX_UNDERFLOW 0x04 +#define SPI_INTR_TX_OVERFLOW 0x08 +#define SPI_INTR_RX_UNDERFLOW 0x10 +#define SPI_INTR_CLEAR_ALL 0x1f + +/* Status */ +#define SPI_RX_EMPTY 0x02 +#define SPI_CMD_BUSY 0x04 +#define SPI_SERIAL_BUSY 0x08 + +/* Clock configuration */ +#define SPI_CLK_20MHZ 0x00 +#define SPI_CLK_0_391MHZ 0x01 +#define SPI_CLK_0_781MHZ 0x02 /* default */ +#define SPI_CLK_1_563MHZ 0x03 +#define SPI_CLK_3_125MHZ 0x04 +#define SPI_CLK_6_250MHZ 0x05 +#define SPI_CLK_12_50MHZ 0x06 +#define SPI_CLK_25MHZ 0x07 +#define SPI_CLK_MASK 0x07 +#define SPI_SSOFFTIME_MASK 0x38 +#define SPI_SSOFFTIME_SHIFT 3 +#define SPI_BYTE_SWAP 0x80 + #endif /* BCM63XX_REGS_H_ */ --- /dev/null +++ b/drivers/spi/bcm63xx_spi.c @@ -0,0 +1,628 @@ +/* + * Broadcom BCM63xx SPI controller support + * + * Copyright (C) 2009 Florian Fainelli + * + * 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., 51 Franklin Street, Fifth Floor, + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define PFX KBUILD_MODNAME +#define DRV_VER "0.1.2" + +enum bcm63xx_regs_spi { + SPI_CMD, + SPI_INT_STATUS, + SPI_INT_MASK_ST, + SPI_INT_MASK, + SPI_ST, + SPI_CLK_CFG, + SPI_FILL_BYTE, + SPI_MSG_TAIL, + SPI_RX_TAIL, + SPI_MSG_CTL, + SPI_MSG_DATA, + SPI_RX_DATA, +}; + +/* + * register offsets + */ +static const unsigned long bcm96338_regs_spi[] = { + [SPI_CMD] = SPI_BCM_6338_SPI_CMD, + [SPI_INT_STATUS] = SPI_BCM_6338_SPI_INT_STATUS, + [SPI_INT_MASK_ST] = SPI_BCM_6338_SPI_MASK_INT_ST, + [SPI_INT_MASK] = SPI_BCM_6338_SPI_INT_MASK, + [SPI_ST] = SPI_BCM_6338_SPI_ST, + [SPI_CLK_CFG] = SPI_BCM_6338_SPI_CLK_CFG, + [SPI_FILL_BYTE] = SPI_BCM_6338_SPI_FILL_BYTE, + [SPI_MSG_TAIL] = SPI_BCM_6338_SPI_MSG_TAIL, + [SPI_RX_TAIL] = SPI_BCM_6338_SPI_RX_TAIL, + [SPI_MSG_CTL] = SPI_BCM_6338_SPI_MSG_CTL, + [SPI_MSG_DATA] = SPI_BCM_6338_SPI_MSG_DATA, + [SPI_RX_DATA] = SPI_BCM_6338_SPI_RX_DATA, +}; + +static const unsigned long bcm96348_regs_spi[] = { + [SPI_CMD] = SPI_BCM_6348_SPI_CMD, + [SPI_INT_STATUS] = SPI_BCM_6348_SPI_INT_STATUS, + [SPI_INT_MASK_ST] = SPI_BCM_6348_SPI_MASK_INT_ST, + [SPI_INT_MASK] = SPI_BCM_6348_SPI_INT_MASK, + [SPI_ST] = SPI_BCM_6348_SPI_ST, + [SPI_CLK_CFG] = SPI_BCM_6348_SPI_CLK_CFG, + [SPI_FILL_BYTE] = SPI_BCM_6348_SPI_FILL_BYTE, + [SPI_MSG_TAIL] = SPI_BCM_6348_SPI_MSG_TAIL, + [SPI_RX_TAIL] = SPI_BCM_6348_SPI_RX_TAIL, + [SPI_MSG_CTL] = SPI_BCM_6348_SPI_MSG_CTL, + [SPI_MSG_DATA] = SPI_BCM_6348_SPI_MSG_DATA, + [SPI_RX_DATA] = SPI_BCM_6348_SPI_RX_DATA, +}; + +static const unsigned long bcm96358_regs_spi[] = { + [SPI_CMD] = SPI_BCM_6358_SPI_CMD, + [SPI_INT_STATUS] = SPI_BCM_6358_SPI_INT_STATUS, + [SPI_INT_MASK_ST] = SPI_BCM_6358_SPI_MASK_INT_ST, + [SPI_INT_MASK] = SPI_BCM_6358_SPI_INT_MASK, + [SPI_ST] = SPI_BCM_6358_SPI_STATUS, + [SPI_CLK_CFG] = SPI_BCM_6358_SPI_CLK_CFG, + [SPI_FILL_BYTE] = SPI_BCM_6358_SPI_FILL_BYTE, + [SPI_MSG_TAIL] = SPI_BCM_6358_SPI_MSG_TAIL, + [SPI_RX_TAIL] = SPI_BCM_6358_SPI_RX_TAIL, + [SPI_MSG_CTL] = SPI_BCM_6358_MSG_CTL, + [SPI_MSG_DATA] = SPI_BCM_6358_SPI_MSG_DATA, + [SPI_RX_DATA] = SPI_BCM_6358_SPI_RX_DATA, +}; + + +#ifdef BCMCPU_RUNTIME_DETECT +static const unsigned long *bcm63xx_regs_spi; + +static __init void bcm63xx_spi_regs_init(void) +{ + if (BCMCPU_IS_6338()) + bcm63xx_regs_spi = bcm96338_regs_spi; + if (BCMCPU_IS_6348()) + bcm63xx_regs_spi = bcm96348_regs_spi; + if (BCMCPU_IS_6358()) + bcm63xx_regs_spi = bcm96358_regs_spi; +} +#else +static __init void bcm63xx_spi_regs_init(void) { } +#endif + +static inline unsigned long bcm63xx_spireg(enum bcm63xx_regs_spi reg) +{ +#ifdef BCMCPU_RUNTIME_DETECT + return bcm63xx_regs_spi[reg]; +#else +#ifdef CONFIG_BCM63XX_CPU_6338 +switch (reg) { + case SPI_CMD: + return SPI_BCM_6338_SPI_CMD; + case SPI_INT_STATUS: + return SPI_BCM_6338_SPI_INT_STATUS; + case SPI_INT_MASK_ST: + return SPI_BCM_6338_SPI_MASK_INT_ST; + case SPI_INT_MASK: + return SPI_BCM_6338_SPI_INT_MASK; + case SPI_ST: + return SPI_BCM_6338_SPI_ST; + case SPI_CLK_CFG: + return SPI_BCM_6338_SPI_CLK_CFG; + case SPI_FILL_BYTE: + return SPI_BCM_6338_SPI_FILL_BYTE; + case SPI_MSG_TAIL: + return SPI_BCM_6338_SPI_MSG_TAIL; + case SPI_RX_TAIL: + return SPI_BCM_6338_SPI_RX_TAIL; + case SPI_MSG_CTL: + return SPI_BCM_6338_SPI_MSG_CTL; + case SPI_MSG_DATA: + return SPI_BCM_6338_SPI_MSG_DATA; + case SPI_RX_DATA: + return SPI_BCM_6338_SPI_RX_DATA; +} +#endif +#ifdef CONFIG_BCM63XX_CPU_6348 +switch (reg) { + case SPI_CMD: + return SPI_BCM_6348_SPI_CMD; + case SPI_INT_MASK_ST: + return SPI_BCM_6348_SPI_MASK_INT_ST; + case SPI_INT_MASK: + return SPI_BCM_6348_SPI_INT_MASK; + case SPI_INT_STATUS: + return SPI_BCM_6348_SPI_INT_STATUS; + case SPI_ST: + return SPI_BCM_6348_SPI_ST; + case SPI_CLK_CFG: + return SPI_BCM_6348_SPI_CLK_CFG; + case SPI_FILL_BYTE: + return SPI_BCM_6348_SPI_FILL_BYTE; + case SPI_MSG_TAIL: + return SPI_BCM_6348_SPI_MSG_TAIL; + case SPI_RX_TAIL: + return SPI_BCM_6348_SPI_RX_TAIL; + case SPI_MSG_CTL: + return SPI_BCM_6348_SPI_MSG_CTL; + case SPI_MSG_DATA: + return SPI_BCM_6348_SPI_MSG_DATA; + case SPI_RX_DATA: + return SPI_BCM_6348_SPI_RX_DATA; +} +#endif +#ifdef CONFIG_BCM63XX_CPU_6358 +switch (reg) { + case SPI_CMD: + return SPI_BCM_6358_SPI_CMD; + case SPI_INT_STATUS: + return SPI_BCM_6358_SPI_INT_STATUS; + case SPI_INT_MASK_ST: + return SPI_BCM_6358_SPI_MASK_INT_ST; + case SPI_INT_MASK: + return SPI_BCM_6358_SPI_INT_MASK; + case SPI_ST: + return SPI_BCM_6358_SPI_STATUS; + case SPI_CLK_CFG: + return SPI_BCM_6358_SPI_CLK_CFG; + case SPI_FILL_BYTE: + return SPI_BCM_6358_SPI_FILL_BYTE; + case SPI_MSG_TAIL: + return SPI_BCM_6358_SPI_MSG_TAIL; + case SPI_RX_TAIL: + return SPI_BCM_6358_SPI_RX_TAIL; + case SPI_MSG_CTL: + return SPI_BCM_6358_MSG_CTL; + case SPI_MSG_DATA: + return SPI_BCM_6358_SPI_MSG_DATA; + case SPI_RX_DATA: + return SPI_BCM_6358_SPI_RX_DATA; +} +#endif +#endif + return 0; +} + +/* + * helpers for the SPI register sets + */ +#define bcm_spi_readb(b,o) bcm_readb((b) + bcm63xx_spireg(o)) +#define bcm_spi_readw(b,o) bcm_readw((b) + bcm63xx_spireg(o)) +#define bcm_spi_writeb(v,b,o) bcm_writeb((v), (b) + bcm63xx_spireg(o)) +#define bcm_spi_writew(v,b,o) bcm_writew((v), (b) + bcm63xx_spireg(o)) + +struct bcm63xx_spi { + /* bitbang has to be first */ + struct spi_bitbang bitbang; + struct completion done; + + void __iomem *regs; + int irq; + + /* Platform data */ + u32 speed_hz; + unsigned fifo_size; + + /* Data buffers */ + const unsigned char *tx_ptr; + unsigned char *rx_ptr; + int remaining_bytes; + + struct clk *clk; + struct resource *ioarea; + struct platform_device *pdev; +}; + +static void bcm63xx_spi_chipselect(struct spi_device *spi, int is_on) +{ + struct bcm63xx_spi *bs = spi_master_get_devdata(spi->master); + u16 val; + + val = bcm_spi_readw(bs->regs, SPI_CMD); + if (is_on == BITBANG_CS_INACTIVE) + val |= SPI_CMD_NOOP; + else if (is_on == BITBANG_CS_ACTIVE) + val |= (spi->chip_select << SPI_CMD_DEVICE_ID_SHIFT); + + bcm_spi_writew(val, bs->regs, SPI_CMD); +} + +static int bcm63xx_spi_setup_transfer(struct spi_device *spi, + struct spi_transfer *t) +{ + u8 bits_per_word; + u8 clk_cfg; + u32 hz; + unsigned int div; + + struct bcm63xx_spi *bs = spi_master_get_devdata(spi->master); + + 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 != 8) { + dev_err(&spi->dev, "%s, unsupported bits_per_word=%d\n", + __func__, bits_per_word); + return -EINVAL; + } + + if (spi->chip_select > spi->master->num_chipselect) { + dev_err(&spi->dev, "%s, unsupported slave %d\n", + __func__, spi->chip_select); + return -EINVAL; + } + + /* Check clock setting */ + div = (bs->speed_hz / hz); + switch (div) { + case 2: + clk_cfg = SPI_CLK_25MHZ; + break; + case 4: + clk_cfg = SPI_CLK_12_50MHZ; + break; + case 8: + clk_cfg = SPI_CLK_6_250MHZ; + break; + case 16: + clk_cfg = SPI_CLK_3_125MHZ; + break; + case 32: + clk_cfg = SPI_CLK_1_563MHZ; + break; + case 128: + clk_cfg = SPI_CLK_0_781MHZ; + break; + case 64: + default: + /* Set to slowest mode for compatibility */ + clk_cfg = SPI_CLK_0_781MHZ; + break; + } + + bcm_spi_writeb(clk_cfg, bs->regs, SPI_CLK_CFG); + dev_dbg(&spi->dev, "Setting clock register to %d (hz %d, cmd %02x)\n", + div, hz, clk_cfg); + + return 0; +} + +/* the spi->mode bits understood by this driver: */ +#define MODEBITS (SPI_CPOL | SPI_CPHA) + +static int bcm63xx_spi_setup(struct spi_device *spi) +{ + struct spi_bitbang *bitbang; + struct bcm63xx_spi *bs; + int retval; + + bs = spi_master_get_devdata(spi->master); + bitbang = &bs->bitbang; + + if (!spi->bits_per_word) + spi->bits_per_word = 8; + + if (spi->mode & ~MODEBITS) { + dev_err(&spi->dev, "%s, unsupported mode bits %x\n", + __func__, spi->mode & ~MODEBITS); + return -EINVAL; + } + + retval = bcm63xx_spi_setup_transfer(spi, NULL); + if (retval < 0) { + dev_err(&spi->dev, "setup: unsupported mode bits %x\n", + spi->mode & ~MODEBITS); + return retval; + } + + dev_dbg(&spi->dev, "%s, mode %d, %u bits/w, %u nsec/bit\n", + __func__, spi->mode & MODEBITS, spi->bits_per_word, 0); + + return 0; +} + +/* Fill the TX FIFO with as many bytes as possible */ +static void bcm63xx_spi_fill_tx_fifo(struct bcm63xx_spi *bs) +{ + u8 tail; + + /* Fill the Tx FIFO with as many bytes as possible */ + tail = bcm_spi_readb(bs->regs, SPI_MSG_TAIL); + while ((tail < bs->fifo_size) && (bs->remaining_bytes > 0)) { + if (bs->tx_ptr) + bcm_spi_writeb(*bs->tx_ptr++, bs->regs, SPI_MSG_DATA); + else + bcm_spi_writeb(0, bs->regs, SPI_MSG_DATA); + bs->remaining_bytes--; + tail = bcm_spi_readb(bs->regs, SPI_MSG_TAIL); + } +} + +static int bcm63xx_txrx_bufs(struct spi_device *spi, struct spi_transfer *t) +{ + struct bcm63xx_spi *bs = spi_master_get_devdata(spi->master); + u8 msg_ctl; + u16 cmd; + + dev_dbg(&spi->dev, "txrx: tx %p, rx %p, len %d\n", + t->tx_buf, t->rx_buf, t->len); + + /* Transmitter is inhibited */ + bs->tx_ptr = t->tx_buf; + bs->rx_ptr = t->rx_buf; + bs->remaining_bytes = t->len; + init_completion(&bs->done); + + bcm63xx_spi_fill_tx_fifo(bs); + + /* Enable the command done interrupt which + * we use to determine completion of a command */ + bcm_spi_writeb(SPI_INTR_CMD_DONE, bs->regs, SPI_INT_MASK); + + /* Fill in the Message control register */ + msg_ctl = bcm_spi_readb(bs->regs, SPI_MSG_CTL); + msg_ctl |= (t->len << SPI_BYTE_CNT_SHIFT); + msg_ctl |= (SPI_FD_RW << SPI_MSG_TYPE_SHIFT); + bcm_spi_writeb(msg_ctl, bs->regs, SPI_MSG_CTL); + + /* Issue the transfer */ + cmd = bcm_spi_readw(bs->regs, SPI_CMD); + cmd |= SPI_CMD_START_IMMEDIATE; + cmd |= (0 << SPI_CMD_PREPEND_BYTE_CNT_SHIFT); + bcm_spi_writew(cmd, bs->regs, SPI_CMD); + + wait_for_completion(&bs->done); + + /* Disable the CMD_DONE interrupt */ + bcm_spi_writeb(~(SPI_INTR_CMD_DONE), bs->regs, SPI_INT_MASK); + + return t->len - bs->remaining_bytes; +} + +/* This driver supports single master mode only. Hence + * CMD_DONE is the only interrupt we care about + */ +static irqreturn_t bcm63xx_spi_interrupt(int irq, void *dev_id) +{ + struct spi_master *master = (struct spi_master *)dev_id; + struct bcm63xx_spi *bs = spi_master_get_devdata(master); + u8 intr; + u16 cmd; + + /* Read interupts and clear them immediately */ + intr = bcm_spi_readb(bs->regs, SPI_INT_STATUS); + bcm_spi_writeb(SPI_INTR_CLEAR_ALL, bs->regs, SPI_INT_MASK); + + /* A tansfer completed */ + if (intr & SPI_INTR_CMD_DONE) { + u8 rx_empty; + + rx_empty = bcm_spi_readb(bs->regs, SPI_ST); + /* Read out all the data */ + while ((rx_empty & SPI_RX_EMPTY) == 0) { + u8 data; + + data = bcm_spi_readb(bs->regs, SPI_RX_DATA); + if (bs->rx_ptr) + *bs->rx_ptr++ = data; + + rx_empty = bcm_spi_readb(bs->regs, SPI_RX_EMPTY); + } + + /* See if there is more data to send */ + if (bs->remaining_bytes > 0) { + bcm63xx_spi_fill_tx_fifo(bs); + + /* Start the transfer */ + cmd = bcm_spi_readw(bs->regs, SPI_CMD); + cmd |= SPI_CMD_START_IMMEDIATE; + cmd |= (0 << SPI_CMD_PREPEND_BYTE_CNT_SHIFT); + bcm_spi_writew(cmd, bs->regs, SPI_CMD); + } else + complete(&bs->done); + } + + return IRQ_HANDLED; +} + + +static int __init bcm63xx_spi_probe(struct platform_device *pdev) +{ + struct resource *r; + struct bcm63xx_spi_pdata *pdata = pdev->dev.platform_data; + int irq; + struct spi_master *master; + struct clk *clk; + struct bcm63xx_spi *bs; + int ret; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r) { + ret = -ENXIO; + goto out; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + ret = -ENXIO; + goto out; + } + + bcm63xx_spi_regs_init(); + + clk = clk_get(&pdev->dev, "spi"); + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "No clock for device\n"); + ret = -ENODEV; + goto out; + } + + master = spi_alloc_master(&pdev->dev, sizeof(struct bcm63xx_spi)); + if (!master) { + ret = -ENOMEM; + goto out_free; + } + + bs = spi_master_get_devdata(master); + bs->bitbang.master = spi_master_get(master); + bs->bitbang.chipselect = bcm63xx_spi_chipselect; + bs->bitbang.setup_transfer = bcm63xx_spi_setup_transfer; + bs->bitbang.txrx_bufs = bcm63xx_txrx_bufs; + bs->bitbang.master->setup = bcm63xx_spi_setup; + init_completion(&bs->done); + + platform_set_drvdata(pdev, master); + bs->pdev = pdev; + + if (!request_mem_region(r->start, + r->end - r->start, PFX)) { + ret = -ENXIO; + goto out_free; + } + + bs->regs = ioremap_nocache(r->start, r->end - r->start); + if (!bs->regs) { + printk(KERN_ERR PFX " unable to ioremap regs\n"); + ret = -ENOMEM; + goto out_free; + } + bs->irq = irq; + bs->clk = clk; + bs->fifo_size = pdata->fifo_size; + + ret = request_irq(irq, bcm63xx_spi_interrupt, 0, + pdev->name, master); + if (ret) { + printk(KERN_ERR PFX " unable to request irq\n"); + goto out_unmap; + } + + master->bus_num = pdata->bus_num; + master->num_chipselect = pdata->num_chipselect; + bs->speed_hz = pdata->speed_hz; + + /* Initialize hardware */ + clk_enable(bs->clk); + bcm_spi_writeb(SPI_INTR_CLEAR_ALL, bs->regs, SPI_INT_MASK); + + dev_info(&pdev->dev, " at 0x%08x (irq %d, FIFOs size %d) v%s\n", + r->start, irq, bs->fifo_size, DRV_VER); + + ret = spi_bitbang_start(&bs->bitbang); + if (ret) { + dev_err(&pdev->dev, "spi_bitbang_start FAILED\n"); + goto out_reset_hw; + } + + return ret; + +out_reset_hw: + clk_disable(clk); + free_irq(irq, master); +out_unmap: + iounmap(bs->regs); +out_free: + clk_put(clk); + spi_master_put(master); +out: + return ret; +} + +static int __exit bcm63xx_spi_remove(struct platform_device *pdev) +{ + struct spi_master *master = platform_get_drvdata(pdev); + struct bcm63xx_spi *bs = spi_master_get_devdata(master); + + spi_bitbang_stop(&bs->bitbang); + clk_disable(bs->clk); + clk_put(bs->clk); + free_irq(bs->irq, master); + iounmap(bs->regs); + platform_set_drvdata(pdev, 0); + spi_master_put(bs->bitbang.master); + + return 0; +} + +#ifdef CONFIG_PM +static int bcm63xx_spi_suspend(struct platform_device *pdev, pm_message_t mesg) +{ + struct spi_master *master = platform_get_drvdata(pdev); + struct bcm63xx_spi *bs = spi_master_get_devdata(master); + + clk_disable(bs->clk); + + return 0; +} + +static int bcm63xx_spi_resume(struct platform_device *pdev) +{ + struct bcm63xx_spi *bs = spi_master_get_devdata(master); + struct bcm63xx_spi *bs = spi_master_get_devdata(master); + + clk_enable(bs->clk); + + return 0; +} +#else +#define bcm63xx_spi_suspend NULL +#define bcm63xx_spi_resume NULL +#endif + +static struct platform_driver bcm63xx_spi_driver = { + .driver = { + .name = "bcm63xx-spi", + .owner = THIS_MODULE, + }, + .probe = bcm63xx_spi_probe, + .remove = bcm63xx_spi_remove, + .suspend = bcm63xx_spi_suspend, + .resume = bcm63xx_spi_resume, +}; + + +static int __init bcm63xx_spi_init(void) +{ + return platform_driver_register(&bcm63xx_spi_driver); +} + +static void __exit bcm63xx_spi_exit(void) +{ + platform_driver_unregister(&bcm63xx_spi_driver); +} + +module_init(bcm63xx_spi_init); +module_exit(bcm63xx_spi_exit); + +MODULE_ALIAS("platform:bcm63xx_spi"); +MODULE_AUTHOR("Florian Fainelli "); +MODULE_DESCRIPTION("Broadcom BCM63xx SPI Controller driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(DRV_VER); --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -60,6 +60,13 @@ config SPI_ATMEL This selects a driver for the Atmel SPI Controller, present on many AT32 (AVR32) and AT91 (ARM) chips. +config SPI_BCM63XX + tristate "Broadcom BCM63xx SPI controller" + depends on BCM63XX + select SPI_BITBANG + help + This is the SPI controller master driver for Broadcom BCM63xx SoC. + config SPI_BFIN tristate "SPI controller driver for ADI Blackfin5xx" depends on BLACKFIN --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -34,6 +34,7 @@ obj-$(CONFIG_SPI_TXX9) += spi_txx9.o obj-$(CONFIG_SPI_XILINX) += xilinx_spi.o obj-$(CONFIG_SPI_SH_SCI) += spi_sh_sci.o obj-$(CONFIG_SPI_STMP3XXX) += spi_stmp.o +obj-$(CONFIG_SPI_BCM63XX) += bcm63xx_spi.o # ... add above this line ... # SPI protocol drivers (device/link on bus) --- /dev/null +++ b/arch/mips/include/asm/mach-bcm63xx/bcm63xx_dev_spi.h @@ -0,0 +1,15 @@ +#ifndef BCM63XX_DEV_SPI_H +#define BCM63XX_DEV_SPI_H + +#include + +int __init bcm63xx_spi_register(void); + +struct bcm63xx_spi_pdata { + unsigned int fifo_size; + int bus_num; + int num_chipselect; + u32 speed_hz; +}; + +#endif /* BCM63XX_DEV_SPI_H */ --- a/arch/mips/bcm63xx/Makefile +++ b/arch/mips/bcm63xx/Makefile @@ -1,6 +1,6 @@ obj-y += clk.o cpu.o cs.o gpio.o irq.o prom.o setup.o timer.o \ dev-dsp.o dev-enet.o dev-pcmcia.o dev-uart.o dev-wdt.o \ - dev-usb-ohci.o dev-usb-ehci.o dev-usb-udc.o + dev-usb-ohci.o dev-usb-ehci.o dev-usb-udc.o dev-spi.o obj-$(CONFIG_EARLY_PRINTK) += early_printk.o obj-y += boards/ --- a/arch/mips/bcm63xx/boards/board_bcm963xx.c +++ b/arch/mips/bcm63xx/boards/board_bcm963xx.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #define PFX "board_bcm963xx: " @@ -1495,6 +1496,9 @@ int __init board_register_devices(void) if (board.has_udc0) bcm63xx_udc_register(); + if (!BCMCPU_IS_6345()) + bcm63xx_spi_register(); + /* Generate MAC address for WLAN and * register our SPROM */ #ifdef CONFIG_SSB_PCIHOST