/* * Broadcom BCM63xx SPI controller support * * Copyright (C) 2009-2011 Florian Fainelli * Copyright (C) 2010 Tanguy Bouzeloc * * 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 #define PFX KBUILD_MODNAME #define DRV_VER "0.1.2" struct bcm63xx_spi { spinlock_t lock; int stopping; 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; /* data iomem */ u8 __iomem *tx_io; const u8 __iomem *rx_io; int remaining_bytes; struct clk *clk; struct platform_device *pdev; }; static inline u8 bcm_spi_readb(struct bcm63xx_spi *bs, unsigned int offset) { return bcm_readb(bs->regs + bcm63xx_spireg(offset)); } static inline u16 bcm_spi_readw(struct bcm63xx_spi *bs, unsigned int offset) { return bcm_readw(bs->regs + bcm63xx_spireg(offset)); } static inline void bcm_spi_writeb(struct bcm63xx_spi *bs, u8 value, unsigned int offset) { bcm_writeb(value, bs->regs + bcm63xx_spireg(offset)); } static inline void bcm_spi_writew(struct bcm63xx_spi *bs, u16 value, unsigned int offset) { bcm_writew(value, bs->regs + bcm63xx_spireg(offset)); } static const unsigned bcm63xx_spi_freq_table[SPI_CLK_MASK][2] = { { 20000000, SPI_CLK_20MHZ }, { 12500000, SPI_CLK_12_50MHZ }, { 6250000, SPI_CLK_6_250MHZ }, { 3125000, SPI_CLK_3_125MHZ }, { 1563000, SPI_CLK_1_563MHZ }, { 781000, SPI_CLK_0_781MHZ }, { 391000, SPI_CLK_0_391MHZ } }; static int bcm63xx_spi_setup_transfer(struct spi_device *spi, struct spi_transfer *t) { struct bcm63xx_spi *bs = spi_master_get_devdata(spi->master); u8 bits_per_word; u8 clk_cfg, reg; 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 != 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; } /* Find the closest clock configuration */ for (i = 0; i < SPI_CLK_MASK; i++) { if (hz <= bcm63xx_spi_freq_table[i][0]) { clk_cfg = bcm63xx_spi_freq_table[i][1]; break; } } /* No matching configuration found, default to lowest */ if (i == SPI_CLK_MASK) clk_cfg = SPI_CLK_0_391MHZ; /* clear existing clock configuration bits of the register */ reg = bcm_spi_readb(bs, SPI_CLK_CFG); reg &= ~SPI_CLK_MASK; reg |= clk_cfg; bcm_spi_writeb(bs, reg, SPI_CLK_CFG); dev_dbg(&spi->dev, "Setting clock register to %02x (hz %d)\n", clk_cfg, hz); 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 bcm63xx_spi *bs; int ret; bs = spi_master_get_devdata(spi->master); if (bs->stopping) return -ESHUTDOWN; 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; } ret = bcm63xx_spi_setup_transfer(spi, NULL); if (ret < 0) { dev_err(&spi->dev, "setup: unsupported mode bits %x\n", spi->mode & ~MODEBITS); return ret; } 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 size; /* Fill the Tx FIFO with as many bytes as possible */ size = bs->remaining_bytes < bs->fifo_size ? bs->remaining_bytes : bs->fifo_size; memcpy_toio(bs->tx_io, bs->tx_ptr, size); bs->remaining_bytes -= size; } static int bcm63xx_txrx_bufs(struct spi_device *spi, struct spi_transfer *t) { struct bcm63xx_spi *bs = spi_master_get_devdata(spi->master); u16 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; init_completion(&bs->done); if (t->tx_buf) { bs->remaining_bytes = t->len; bcm63xx_spi_fill_tx_fifo(bs); } /* Enable the command done interrupt which * we use to determine completion of a command */ bcm_spi_writeb(bs, SPI_INTR_CMD_DONE, SPI_INT_MASK); /* Fill in the Message control register */ msg_ctl = (t->len << SPI_BYTE_CNT_SHIFT); if (t->rx_buf && t->tx_buf) msg_ctl |= (SPI_FD_RW << SPI_MSG_TYPE_SHIFT); else if (t->rx_buf) msg_ctl |= (SPI_HD_R << SPI_MSG_TYPE_SHIFT); else if (t->tx_buf) msg_ctl |= (SPI_HD_W << SPI_MSG_TYPE_SHIFT); bcm_spi_writew(bs, msg_ctl, SPI_MSG_CTL); /* Issue the transfer */ cmd = SPI_CMD_START_IMMEDIATE; cmd |= (0 << SPI_CMD_PREPEND_BYTE_CNT_SHIFT); cmd |= (spi->chip_select << SPI_CMD_DEVICE_ID_SHIFT); bcm_spi_writew(bs, cmd, SPI_CMD); wait_for_completion(&bs->done); /* Disable the CMD_DONE interrupt */ bcm_spi_writeb(bs, 0, SPI_INT_MASK); return t->len - bs->remaining_bytes; } static int bcm63xx_transfer(struct spi_device *spi, struct spi_message *m) { struct bcm63xx_spi *bs = spi_master_get_devdata(spi->master); struct spi_transfer *t; int ret = 0; if (unlikely(list_empty(&m->transfers))) return -EINVAL; if (bs->stopping) return -ESHUTDOWN; list_for_each_entry(t, &m->transfers, transfer_list) { ret += bcm63xx_txrx_bufs(spi, t); } m->complete(m->context); return ret; } /* 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, SPI_INT_STATUS); bcm_spi_writeb(bs, SPI_INTR_CLEAR_ALL, SPI_INT_STATUS); bcm_spi_writeb(bs, 0, SPI_INT_MASK); /* A tansfer completed */ if (intr & SPI_INTR_CMD_DONE) { u8 rx_tail; rx_tail = bcm_spi_readb(bs, SPI_RX_TAIL); /* Read out all the data */ if (rx_tail) memcpy_fromio(bs->rx_ptr, bs->rx_io, rx_tail); /* See if there is more data to send */ if (bs->remaining_bytes > 0) { bcm63xx_spi_fill_tx_fifo(bs); /* Start the transfer */ bcm_spi_writew(bs, SPI_HD_W << SPI_MSG_TYPE_SHIFT, SPI_MSG_CTL); cmd = bcm_spi_readw(bs, SPI_CMD); cmd |= SPI_CMD_START_IMMEDIATE; cmd |= (0 << SPI_CMD_PREPEND_BYTE_CNT_SHIFT); bcm_spi_writeb(bs, SPI_INTR_CMD_DONE, SPI_INT_MASK); bcm_spi_writew(bs, cmd, SPI_CMD); } else { complete(&bs->done); } } return IRQ_HANDLED; } static int __devinit bcm63xx_spi_probe(struct platform_device *pdev) { struct resource *r; struct device *dev = &pdev->dev; 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) { dev_err(dev, "no iomem\n"); ret = -ENXIO; goto out; } irq = platform_get_irq(pdev, 0); if (irq < 0) { dev_err(dev, "no irq\n"); ret = -ENXIO; goto out; } clk = clk_get(dev, "spi"); if (IS_ERR(clk)) { dev_err(dev, "no clock for device\n"); ret = PTR_ERR(clk); goto out; } master = spi_alloc_master(dev, sizeof(*bs)); if (!master) { dev_err(dev, "out of memory\n"); ret = -ENOMEM; goto out_clk; } bs = spi_master_get_devdata(master); init_completion(&bs->done); platform_set_drvdata(pdev, master); bs->pdev = pdev; if (!devm_request_mem_region(&pdev->dev, r->start, resource_size(r), PFX)) { dev_err(dev, "iomem request failed\n"); ret = -ENXIO; goto out_err; } bs->regs = devm_ioremap_nocache(&pdev->dev, r->start, resource_size(r)); if (!bs->regs) { dev_err(dev, "unable to ioremap regs\n"); ret = -ENOMEM; goto out_err; } bs->irq = irq; bs->clk = clk; bs->fifo_size = pdata->fifo_size; ret = devm_request_irq(&pdev->dev, irq, bcm63xx_spi_interrupt, 0, pdev->name, master); if (ret) { dev_err(dev, "unable to request irq\n"); goto out_err; } master->bus_num = pdata->bus_num; master->num_chipselect = pdata->num_chipselect; master->setup = bcm63xx_spi_setup; master->transfer = bcm63xx_transfer; bs->speed_hz = pdata->speed_hz; bs->stopping = 0; bs->tx_io = (u8 *)(bs->regs + bcm63xx_spireg(SPI_MSG_DATA)); bs->rx_io = (const u8 *)(bs->regs + bcm63xx_spireg(SPI_RX_DATA)); spin_lock_init(&bs->lock); /* Initialize hardware */ clk_enable(bs->clk); bcm_spi_writeb(bs, SPI_INTR_CLEAR_ALL, SPI_INT_STATUS); /* register and we are done */ ret = spi_register_master(master); if (ret) { dev_err(dev, "spi register failed\n"); goto out_clk_disable; } dev_info(dev, "at 0x%08x (irq %d, FIFOs size %d) v%s\n", r->start, irq, bs->fifo_size, DRV_VER); return 0; out_clk_disable: clk_disable(clk); out_err: platform_set_drvdata(pdev, NULL); spi_master_put(master); out_clk: clk_put(clk); out: return ret; } static int __devexit bcm63xx_spi_remove(struct platform_device *pdev) { struct spi_master *master = platform_get_drvdata(pdev); struct bcm63xx_spi *bs = spi_master_get_devdata(master); /* reset spi block */ bcm_spi_writeb(bs, 0, SPI_INT_MASK); spin_lock(&bs->lock); bs->stopping = 1; /* HW shutdown */ clk_disable(bs->clk); clk_put(bs->clk); spin_unlock(&bs->lock); platform_set_drvdata(pdev, 0); spi_unregister_master(master); return 0; } #ifdef CONFIG_PM static int bcm63xx_spi_suspend(struct device *dev) { struct spi_master *master = platform_get_drvdata(to_platform_device(dev)); struct bcm63xx_spi *bs = spi_master_get_devdata(master); clk_disable(bs->clk); return 0; } static int bcm63xx_spi_resume(struct device *dev) { struct spi_master *master = platform_get_drvdata(to_platform_device(dev)); struct bcm63xx_spi *bs = spi_master_get_devdata(master); clk_enable(bs->clk); return 0; } static const struct dev_pm_ops bcm63xx_spi_pm_ops = { .suspend = bcm63xx_spi_suspend, .resume = bcm63xx_spi_resume, }; #define BCM63XX_SPI_PM_OPS (&bcm63xx_spi_pm_ops) #else #define BCM63XX_SPI_PM_OPS NULL #endif static struct platform_driver bcm63xx_spi_driver = { .driver = { .name = "bcm63xx-spi", .owner = THIS_MODULE, .pm = BCM63XX_SPI_PM_OPS, }, .probe = bcm63xx_spi_probe, .remove = __devexit_p(bcm63xx_spi_remove), }; module_platform_driver(bcm63xx_spi_driver); MODULE_ALIAS("platform:bcm63xx_spi"); MODULE_AUTHOR("Florian Fainelli "); MODULE_AUTHOR("Tanguy Bouzeloc "); MODULE_DESCRIPTION("Broadcom BCM63xx SPI Controller driver"); MODULE_LICENSE("GPL");