Module: xenomai-3
Branch: stable-3.0.x
Commit: 050e201d39e6072cff5791956f7eccbedbe27946
URL:    
http://git.xenomai.org/?p=xenomai-3.git;a=commit;h=050e201d39e6072cff5791956f7eccbedbe27946

Author: Philippe Gerum <r...@xenomai.org>
Date:   Sun Aug 20 19:22:31 2017 +0200

drivers/spi: add support for Allwinner's A31, H3 SoCs

---

 kernel/drivers/spi/Kconfig     |    9 +
 kernel/drivers/spi/Makefile    |    2 +
 kernel/drivers/spi/spi-sun6i.c |  655 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 666 insertions(+)

diff --git a/kernel/drivers/spi/Kconfig b/kernel/drivers/spi/Kconfig
index 9f14985..c2d47aa 100644
--- a/kernel/drivers/spi/Kconfig
+++ b/kernel/drivers/spi/Kconfig
@@ -13,6 +13,15 @@ config XENO_DRIVERS_SPI_BCM2835
        Enables support for the SPI0 controller available from
        Broadcom's BCM2835 SoC.
 
+config XENO_DRIVERS_SPI_SUN6I
+       depends on MACH_SUN6I || MACH_SUN8I
+       select XENO_DRIVERS_SPI
+       tristate "Support for A31/H3 SoC SPI"
+       help
+
+       Enables support for the SPI controller available from
+       Allwinner's A31, H3 SoCs.
+
 config XENO_DRIVERS_SPI_DEBUG
        depends on XENO_DRIVERS_SPI
        bool "Enable SPI core debugging features"
diff --git a/kernel/drivers/spi/Makefile b/kernel/drivers/spi/Makefile
index 2a00284..075641f 100644
--- a/kernel/drivers/spi/Makefile
+++ b/kernel/drivers/spi/Makefile
@@ -6,5 +6,7 @@ obj-$(CONFIG_XENO_DRIVERS_SPI) += xeno_spi.o
 xeno_spi-y := spi-master.o spi-device.o
 
 obj-$(CONFIG_XENO_DRIVERS_SPI_BCM2835) += xeno_spi_bcm2835.o
+obj-$(CONFIG_XENO_DRIVERS_SPI_SUN6I) += xeno_spi_sun6i.o
 
 xeno_spi_bcm2835-y := spi-bcm2835.o
+xeno_spi_sun6i-y := spi-sun6i.o
diff --git a/kernel/drivers/spi/spi-sun6i.c b/kernel/drivers/spi/spi-sun6i.c
new file mode 100644
index 0000000..ec08c98
--- /dev/null
+++ b/kernel/drivers/spi/spi-sun6i.c
@@ -0,0 +1,655 @@
+/**
+ * I/O handling lifted from drivers/spi/spi-sun6i.c:
+ * Copyright (C) 2012 - 2014 Allwinner Tech
+ * Pan Nan <pan...@allwinnertech.com>
+ * Copyright (C) 2014 Maxime Ripard
+ * Maxime Ripard <maxime.rip...@free-electrons.com>
+ *
+ * RTDM integration by:
+ * Copyright (C) 2017 Philippe Gerum <r...@xenomai.org>
+ *
+ * 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 <linux/module.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+#include <linux/spi/spi.h>
+#include <linux/of_irq.h>
+#include <linux/of_device.h>
+#include <linux/reset.h>
+#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->kmaster->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 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,
+       .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_master *kmaster;
+       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);
+
+       kmaster = master->kmaster;
+       kmaster->max_speed_hz = 100 * 1000 * 1000;
+       kmaster->min_speed_hz = 3 * 1000;
+       kmaster->mode_bits = SUN6I_SPI_MODE_BITS;
+       kmaster->bits_per_word_mask = SPI_BPW_MASK(8);
+       kmaster->num_chipselect = 4;
+       kmaster->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 * kmaster->max_speed_hz)
+               clk_set_rate(spim->mclk, 2 * kmaster->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_master_put(kmaster);
+
+       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");


_______________________________________________
Xenomai-git mailing list
Xenomai-git@xenomai.org
https://xenomai.org/mailman/listinfo/xenomai-git

Reply via email to