/** * I/O handling lifted from drivers/spi/spi-sun6i.c: * Copyright (C) 2012 - 2014 Allwinner Tech * Pan Nan * Copyright (C) 2014 Maxime Ripard * Maxime Ripard * * RTDM integration by: * Copyright (C) 2017 Philippe Gerum * * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "spi-master.h" #define RTDM_SUBCLASS_SUN6I 2 #define SUN6I_GBL_CTL_REG 0x04 #define SUN6I_GBL_CTL_BUS_ENABLE BIT(0) #define SUN6I_GBL_CTL_MASTER BIT(1) #define SUN6I_GBL_CTL_TP BIT(7) #define SUN6I_GBL_CTL_RST BIT(31) #define SUN6I_TFR_CTL_REG 0x08 #define SUN6I_TFR_CTL_CPHA BIT(0) #define SUN6I_TFR_CTL_CPOL BIT(1) #define SUN6I_TFR_CTL_SPOL BIT(2) #define SUN6I_TFR_CTL_CS_MASK 0x30 #define SUN6I_TFR_CTL_CS(cs) (((cs) << 4) & SUN6I_TFR_CTL_CS_MASK) #define SUN6I_TFR_CTL_CS_MANUAL BIT(6) #define SUN6I_TFR_CTL_CS_LEVEL BIT(7) #define SUN6I_TFR_CTL_DHB BIT(8) #define SUN6I_TFR_CTL_FBS BIT(12) #define SUN6I_TFR_CTL_XCH BIT(31) #define SUN6I_INT_CTL_REG 0x10 #define SUN6I_INT_CTL_RX_RDY BIT(0) #define SUN6I_INT_CTL_TX_RDY BIT(4) #define SUN6I_INT_CTL_RX_OVF BIT(8) #define SUN6I_INT_CTL_TC BIT(12) #define SUN6I_INT_STA_REG 0x14 #define SUN6I_FIFO_CTL_REG 0x18 #define SUN6I_FIFO_CTL_RX_RDY_TRIG_LEVEL_MASK 0xff #define SUN6I_FIFO_CTL_RX_RDY_TRIG_LEVEL_BITS 0 #define SUN6I_FIFO_CTL_RX_RST BIT(15) #define SUN6I_FIFO_CTL_TX_RDY_TRIG_LEVEL_MASK 0xff #define SUN6I_FIFO_CTL_TX_RDY_TRIG_LEVEL_BITS 16 #define SUN6I_FIFO_CTL_TX_RST BIT(31) #define SUN6I_FIFO_STA_REG 0x1c #define SUN6I_FIFO_STA_RX_CNT(reg) (((reg) >> 0) & 0xff) #define SUN6I_FIFO_STA_TX_CNT(reg) (((reg) >> 16) & 0xff) #define SUN6I_CLK_CTL_REG 0x24 #define SUN6I_CLK_CTL_CDR2_MASK 0xff #define SUN6I_CLK_CTL_CDR2(div) (((div) & SUN6I_CLK_CTL_CDR2_MASK) << 0) #define SUN6I_CLK_CTL_CDR1_MASK 0xf #define SUN6I_CLK_CTL_CDR1(div) (((div) & SUN6I_CLK_CTL_CDR1_MASK) << 8) #define SUN6I_CLK_CTL_DRS BIT(12) #define SUN6I_MAX_XFER_SIZE 0xffffff #define SUN6I_BURST_CNT_REG 0x30 #define SUN6I_BURST_CNT(cnt) ((cnt) & SUN6I_MAX_XFER_SIZE) #define SUN6I_XMIT_CNT_REG 0x34 #define SUN6I_XMIT_CNT(cnt) ((cnt) & SUN6I_MAX_XFER_SIZE) #define SUN6I_BURST_CTL_CNT_REG 0x38 #define SUN6I_BURST_CTL_CNT_STC(cnt) ((cnt) & SUN6I_MAX_XFER_SIZE) #define SUN6I_TXDATA_REG 0x200 #define SUN6I_RXDATA_REG 0x300 #define SUN6I_SPI_MODE_BITS (SPI_CPOL | SPI_CPHA | SPI_CS_HIGH \ | SPI_LSB_FIRST) struct spi_setup_data { int fifo_depth; }; static struct spi_setup_data sun6i_data = { .fifo_depth = 128, }; static struct spi_setup_data sun8i_data = { .fifo_depth = 64, }; struct spi_master_sun6i { struct rtdm_spi_master master; void __iomem *regs; struct reset_control *rstc; struct clk *hclk; struct clk *mclk; unsigned long clk_hz; rtdm_irq_t irqh; const u8 *tx_buf; u8 *rx_buf; int tx_len; int rx_len; rtdm_event_t transfer_done; const struct spi_setup_data *setup; }; struct spi_slave_sun6i { struct rtdm_spi_remote_slave slave; void *io_virt; dma_addr_t io_dma; size_t io_len; }; static inline struct spi_slave_sun6i * to_slave_sun6i(struct rtdm_spi_remote_slave *slave) { return container_of(slave, struct spi_slave_sun6i, slave); } static inline struct spi_master_sun6i * to_master_sun6i(struct rtdm_spi_remote_slave *slave) { return container_of(slave->master, struct spi_master_sun6i, master); } static inline struct device * master_to_kdev(struct rtdm_spi_master *master) { return &master->controller->dev; } static inline u32 sun6i_rd(struct spi_master_sun6i *spim, unsigned int reg) { return readl(spim->regs + reg); } static inline void sun6i_wr(struct spi_master_sun6i *spim, unsigned int reg, u32 val) { writel(val, spim->regs + reg); } static void sun6i_rd_fifo(struct spi_master_sun6i *spim) { u32 reg; int len; u8 byte; reg = sun6i_rd(spim, SUN6I_FIFO_STA_REG); len = min((int)SUN6I_FIFO_STA_RX_CNT(reg), spim->rx_len); while (len-- > 0) { byte = sun6i_rd(spim, SUN6I_RXDATA_REG); if (spim->rx_buf) *spim->rx_buf++ = byte; spim->rx_len--; } } static void sun6i_wr_fifo(struct spi_master_sun6i *spim) { u32 reg; int len; u8 byte; reg = sun6i_rd(spim, SUN6I_FIFO_STA_REG); len = min(spim->setup->fifo_depth - (int)SUN6I_FIFO_STA_TX_CNT(reg), spim->tx_len); while (len-- > 0) { byte = spim->tx_buf ? *spim->tx_buf++ : 0; sun6i_wr(spim, SUN6I_TXDATA_REG, byte); spim->tx_len--; } } static int sun6i_spi_interrupt(rtdm_irq_t *irqh) { struct spi_master_sun6i *spim; u32 status; spim = rtdm_irq_get_arg(irqh, struct spi_master_sun6i); sun6i_rd_fifo(spim); sun6i_wr_fifo(spim); status = sun6i_rd(spim, SUN6I_INT_STA_REG); if ((status & SUN6I_INT_CTL_TC)) { sun6i_wr(spim, SUN6I_INT_STA_REG, SUN6I_INT_CTL_TC); sun6i_wr(spim, SUN6I_INT_CTL_REG, 0); rtdm_event_signal(&spim->transfer_done); } else if (status & SUN6I_INT_CTL_TX_RDY) sun6i_wr(spim, SUN6I_INT_STA_REG, SUN6I_INT_CTL_TX_RDY); return RTDM_IRQ_HANDLED; } static int sun6i_configure(struct rtdm_spi_remote_slave *slave) { struct spi_master_sun6i *spim = to_master_sun6i(slave); struct rtdm_spi_config *config = &slave->config; u32 reg, div; /* Set clock polarity and phase. */ reg = sun6i_rd(spim, SUN6I_TFR_CTL_REG); reg &= ~(SUN6I_TFR_CTL_CPOL | SUN6I_TFR_CTL_CPHA | SUN6I_TFR_CTL_FBS | SUN6I_TFR_CTL_SPOL); /* Manual CS via ->chip_select(). */ reg |= SUN6I_TFR_CTL_CS_MANUAL; if (config->mode & SPI_CPOL) reg |= SUN6I_TFR_CTL_CPOL; if (config->mode & SPI_CPHA) reg |= SUN6I_TFR_CTL_CPHA; if (config->mode & SPI_LSB_FIRST) reg |= SUN6I_TFR_CTL_FBS; if (!(config->mode & SPI_CS_HIGH)) reg |= SUN6I_TFR_CTL_SPOL; sun6i_wr(spim, SUN6I_TFR_CTL_REG, reg); /* Setup clock divider. */ div = spim->clk_hz / (2 * config->speed_hz); if (div <= SUN6I_CLK_CTL_CDR2_MASK + 1) { if (div > 0) div--; reg = SUN6I_CLK_CTL_CDR2(div) | SUN6I_CLK_CTL_DRS; } else { div = ilog2(spim->clk_hz) - ilog2(config->speed_hz); reg = SUN6I_CLK_CTL_CDR1(div); } sun6i_wr(spim, SUN6I_CLK_CTL_REG, reg); return 0; } static void sun6i_chip_select(struct rtdm_spi_remote_slave *slave, bool active) { struct spi_master_sun6i *spim = to_master_sun6i(slave); u32 reg; /* * We have no cs_gpios, so this handler will be called for * each transfer. */ reg = sun6i_rd(spim, SUN6I_TFR_CTL_REG); reg &= ~(SUN6I_TFR_CTL_CS_MASK | SUN6I_TFR_CTL_CS_LEVEL); reg |= SUN6I_TFR_CTL_CS(slave->chip_select); if (active) reg |= SUN6I_TFR_CTL_CS_LEVEL; sun6i_wr(spim, SUN6I_TFR_CTL_REG, reg); } static int do_transfer_irq(struct rtdm_spi_remote_slave *slave) { struct spi_master_sun6i *spim = to_master_sun6i(slave); u32 tx_len = 0, reg; int ret; /* Reset FIFO. */ sun6i_wr(spim, SUN6I_FIFO_CTL_REG, SUN6I_FIFO_CTL_RX_RST | SUN6I_FIFO_CTL_TX_RST); /* Set FIFO interrupt trigger level to 3/4 of the fifo depth. */ reg = spim->setup->fifo_depth / 4 * 3; sun6i_wr(spim, SUN6I_FIFO_CTL_REG, (reg << SUN6I_FIFO_CTL_RX_RDY_TRIG_LEVEL_BITS) | (reg << SUN6I_FIFO_CTL_TX_RDY_TRIG_LEVEL_BITS)); reg = sun6i_rd(spim, SUN6I_TFR_CTL_REG); reg &= ~SUN6I_TFR_CTL_DHB; /* Discard unused SPI bursts if TX only. */ if (spim->rx_buf == NULL) reg |= SUN6I_TFR_CTL_DHB; sun6i_wr(spim, SUN6I_TFR_CTL_REG, reg); if (spim->tx_buf) tx_len = spim->tx_len; /* Setup the counters. */ sun6i_wr(spim, SUN6I_BURST_CNT_REG, SUN6I_BURST_CNT(spim->tx_len)); sun6i_wr(spim, SUN6I_XMIT_CNT_REG, SUN6I_XMIT_CNT(tx_len)); sun6i_wr(spim, SUN6I_BURST_CTL_CNT_REG, SUN6I_BURST_CTL_CNT_STC(tx_len)); /* Fill the TX FIFO */ sun6i_wr_fifo(spim); /* Enable interrupts. */ reg = sun6i_rd(spim, SUN6I_INT_CTL_REG); reg |= SUN6I_INT_CTL_TC | SUN6I_INT_CTL_TX_RDY; sun6i_wr(spim, SUN6I_INT_CTL_REG, reg); /* Start the transfer. */ reg = sun6i_rd(spim, SUN6I_TFR_CTL_REG); sun6i_wr(spim, SUN6I_TFR_CTL_REG, reg | SUN6I_TFR_CTL_XCH); ret = rtdm_event_wait(&spim->transfer_done); if (ret) { sun6i_wr(spim, SUN6I_INT_CTL_REG, 0); return ret; } return 0; } static int sun6i_transfer_iobufs(struct rtdm_spi_remote_slave *slave) { struct spi_master_sun6i *spim = to_master_sun6i(slave); struct spi_slave_sun6i *sun6i = to_slave_sun6i(slave); if (sun6i->io_len == 0) return -EINVAL; /* No I/O buffers set. */ spim->tx_len = sun6i->io_len / 2; spim->rx_len = spim->tx_len; spim->tx_buf = sun6i->io_virt + spim->rx_len; spim->rx_buf = sun6i->io_virt; return do_transfer_irq(slave); } static int sun6i_transfer_iobufs_n(struct rtdm_spi_remote_slave *slave, int len) { struct spi_master_sun6i *spim = to_master_sun6i(slave); struct spi_slave_sun6i *sun6i = to_slave_sun6i(slave); if ((sun6i->io_len == 0) || (len <= 0) || (len > (sun6i->io_len / 2))) return -EINVAL; spim->tx_len = len; spim->rx_len = len; spim->tx_buf = sun6i->io_virt + sun6i->io_len / 2; spim->rx_buf = sun6i->io_virt; return do_transfer_irq(slave); } static ssize_t sun6i_read(struct rtdm_spi_remote_slave *slave, void *rx, size_t len) { struct spi_master_sun6i *spim = to_master_sun6i(slave); spim->tx_len = len; spim->rx_len = len; spim->tx_buf = NULL; spim->rx_buf = rx; return do_transfer_irq(slave) ?: len; } static ssize_t sun6i_write(struct rtdm_spi_remote_slave *slave, const void *tx, size_t len) { struct spi_master_sun6i *spim = to_master_sun6i(slave); spim->tx_len = len; spim->rx_len = len; spim->tx_buf = tx; spim->rx_buf = NULL; return do_transfer_irq(slave) ?: len; } static int set_iobufs(struct spi_slave_sun6i *sun6i, size_t len) { dma_addr_t dma; void *p; if (len == 0) return -EINVAL; len = L1_CACHE_ALIGN(len) * 2; if (len == sun6i->io_len) return 0; if (sun6i->io_len) return -EINVAL; /* I/O buffers may not be resized. */ p = dma_alloc_coherent(NULL, len, &dma, GFP_KERNEL); if (p == NULL) return -ENOMEM; sun6i->io_dma = dma; sun6i->io_virt = p; smp_mb(); sun6i->io_len = len; return 0; } static int sun6i_set_iobufs(struct rtdm_spi_remote_slave *slave, struct rtdm_spi_iobufs *p) { struct spi_slave_sun6i *sun6i = to_slave_sun6i(slave); int ret; ret = set_iobufs(sun6i, p->io_len); if (ret) return ret; p->i_offset = 0; p->o_offset = sun6i->io_len / 2; p->map_len = sun6i->io_len; return 0; } static int sun6i_mmap_iobufs(struct rtdm_spi_remote_slave *slave, struct vm_area_struct *vma) { struct spi_slave_sun6i *sun6i = to_slave_sun6i(slave); vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); return rtdm_mmap_kmem(vma, sun6i->io_virt); } static void sun6i_mmap_release(struct rtdm_spi_remote_slave *slave) { struct spi_slave_sun6i *sun6i = to_slave_sun6i(slave); dma_free_coherent(NULL, sun6i->io_len, sun6i->io_virt, sun6i->io_dma); sun6i->io_len = 0; } static struct rtdm_spi_remote_slave * sun6i_attach_slave(struct rtdm_spi_master *master, struct spi_device *spi) { struct spi_slave_sun6i *sun6i; int ret; sun6i = kzalloc(sizeof(*sun6i), GFP_KERNEL); if (sun6i == NULL) return ERR_PTR(-ENOMEM); ret = rtdm_spi_add_remote_slave(&sun6i->slave, master, spi); if (ret) { dev_err(&spi->dev, "%s: failed to attach slave\n", __func__); kfree(sun6i); return ERR_PTR(ret); } return &sun6i->slave; } static void sun6i_detach_slave(struct rtdm_spi_remote_slave *slave) { struct spi_slave_sun6i *sun6i = to_slave_sun6i(slave); rtdm_spi_remove_remote_slave(slave); kfree(sun6i); } static struct rtdm_spi_master_ops sun6i_master_ops = { .configure = sun6i_configure, .chip_select = sun6i_chip_select, .set_iobufs = sun6i_set_iobufs, .mmap_iobufs = sun6i_mmap_iobufs, .mmap_release = sun6i_mmap_release, .transfer_iobufs = sun6i_transfer_iobufs, .transfer_iobufs_n = sun6i_transfer_iobufs_n, .write = sun6i_write, .read = sun6i_read, .attach_slave = sun6i_attach_slave, .detach_slave = sun6i_detach_slave, }; static int sun6i_spi_probe(struct platform_device *pdev) { struct rtdm_spi_master *master; struct spi_master_sun6i *spim; struct spi_controller *ctlr; struct resource *r; int ret, irq; u32 clk_rate; dev_dbg(&pdev->dev, "%s: entered\n", __func__); master = rtdm_spi_alloc_master(&pdev->dev, struct spi_master_sun6i, master); if (master == NULL) return -ENOMEM; master->subclass = RTDM_SUBCLASS_SUN6I; master->ops = &sun6i_master_ops; platform_set_drvdata(pdev, master); ctlr = master->controller; ctlr->max_speed_hz = 100 * 1000 * 1000; ctlr->min_speed_hz = 3 * 1000; ctlr->mode_bits = SUN6I_SPI_MODE_BITS; ctlr->bits_per_word_mask = SPI_BPW_MASK(8); ctlr->num_chipselect = 4; ctlr->dev.of_node = pdev->dev.of_node; spim = container_of(master, struct spi_master_sun6i, master); spim->setup = of_device_get_match_data(&pdev->dev); rtdm_event_init(&spim->transfer_done, 0); r = platform_get_resource(pdev, IORESOURCE_MEM, 0); spim->regs = devm_ioremap_resource(&pdev->dev, r); if (IS_ERR(spim->regs)) { dev_err(&pdev->dev, "%s: cannot map I/O memory\n", __func__); ret = PTR_ERR(spim->regs); goto fail; } spim->hclk = devm_clk_get(&pdev->dev, "ahb"); if (IS_ERR(spim->hclk)) { dev_err(&pdev->dev, "Unable to acquire AHB clock\n"); ret = PTR_ERR(spim->hclk); goto fail; } spim->mclk = devm_clk_get(&pdev->dev, "mod"); if (IS_ERR(spim->mclk)) { dev_err(&pdev->dev, "Unable to acquire MOD clock\n"); ret = PTR_ERR(spim->mclk); goto fail; } spim->rstc = devm_reset_control_get(&pdev->dev, NULL); if (IS_ERR(spim->rstc)) { dev_err(&pdev->dev, "Couldn't get reset controller\n"); ret = PTR_ERR(spim->rstc); goto fail; } /* * Ensure that we have a parent clock fast enough to handle * the fastest transfers properly. */ clk_rate = clk_get_rate(spim->mclk); if (clk_rate < 2 * ctlr->max_speed_hz) clk_set_rate(spim->mclk, 2 * ctlr->max_speed_hz); spim->clk_hz = clk_get_rate(spim->mclk); irq = irq_of_parse_and_map(pdev->dev.of_node, 0); if (irq <= 0) { ret = irq ?: -ENODEV; goto fail; } clk_prepare_enable(spim->hclk); clk_prepare_enable(spim->mclk); ret = reset_control_deassert(spim->rstc); if (ret) goto fail_unclk; /* Enable SPI module, in master mode with smart burst. */ sun6i_wr(spim, SUN6I_GBL_CTL_REG, SUN6I_GBL_CTL_BUS_ENABLE | SUN6I_GBL_CTL_MASTER | SUN6I_GBL_CTL_TP); /* Disable and clear all interrupts. */ sun6i_wr(spim, SUN6I_INT_CTL_REG, 0); sun6i_wr(spim, SUN6I_INT_STA_REG, ~0); ret = rtdm_irq_request(&spim->irqh, irq, sun6i_spi_interrupt, 0, dev_name(&pdev->dev), spim); if (ret) { dev_err(&pdev->dev, "%s: cannot request IRQ%d\n", __func__, irq); goto fail_unclk; } ret = rtdm_spi_add_master(&spim->master); if (ret) { dev_err(&pdev->dev, "%s: failed to add master\n", __func__); goto fail_register; } return 0; fail_register: rtdm_irq_free(&spim->irqh); fail_unclk: clk_disable_unprepare(spim->mclk); clk_disable_unprepare(spim->hclk); fail: spi_controller_put(ctlr); return ret; } static int sun6i_spi_remove(struct platform_device *pdev) { struct rtdm_spi_master *master = platform_get_drvdata(pdev); struct spi_master_sun6i *spim; dev_dbg(&pdev->dev, "%s: entered\n", __func__); spim = container_of(master, struct spi_master_sun6i, master); rtdm_irq_free(&spim->irqh); clk_disable_unprepare(spim->mclk); clk_disable_unprepare(spim->hclk); rtdm_spi_remove_master(master); return 0; } static const struct of_device_id sun6i_spi_match[] = { { .compatible = "allwinner,sun6i-a31-spi", .data = &sun6i_data, }, { .compatible = "allwinner,sun8i-h3-spi", .data = &sun8i_data, }, { /* Sentinel */ }, }; MODULE_DEVICE_TABLE(of, sun6i_spi_match); static struct platform_driver sun6i_spi_driver = { .driver = { .name = "spi-sun6i", .of_match_table = sun6i_spi_match, }, .probe = sun6i_spi_probe, .remove = sun6i_spi_remove, }; module_platform_driver(sun6i_spi_driver); MODULE_LICENSE("GPL");