Module: xenomai-3
Branch: wip/drivers
Commit: 9ead115b6a370acce038b4d73ac517f7f1ac2035
URL:    
http://git.xenomai.org/?p=xenomai-3.git;a=commit;h=9ead115b6a370acce038b4d73ac517f7f1ac2035

Author: Philippe Gerum <r...@xenomai.org>
Date:   Fri Jun 17 16:28:25 2016 +0200

drivers/spi: introduce real-time SPI support

---

 include/rtdm/Makefile.am           |    1 +
 include/rtdm/spi.h                 |   24 ++
 include/rtdm/uapi/Makefile.am      |    1 +
 include/rtdm/uapi/rtdm.h           |    9 +-
 include/rtdm/uapi/spi.h            |   40 +++
 kernel/drivers/Kconfig             |    1 +
 kernel/drivers/Makefile            |    2 +-
 kernel/drivers/gpio/gpio-bcm2835.c |   18 +-
 kernel/drivers/spi/Kconfig         |   20 ++
 kernel/drivers/spi/Makefile        |    8 +
 kernel/drivers/spi/spi-bcm2835.c   |  691 ++++++++++++++++++++++++++++++++++++
 kernel/drivers/spi/spi-device.c    |  180 ++++++++++
 kernel/drivers/spi/spi-device.h    |   53 +++
 kernel/drivers/spi/spi-master.c    |  423 ++++++++++++++++++++++
 kernel/drivers/spi/spi-master.h    |   81 +++++
 15 files changed, 1535 insertions(+), 17 deletions(-)

diff --git a/include/rtdm/Makefile.am b/include/rtdm/Makefile.am
index ad2c342..df5b551 100644
--- a/include/rtdm/Makefile.am
+++ b/include/rtdm/Makefile.am
@@ -8,6 +8,7 @@ includesub_HEADERS =    \
        ipc.h           \
        rtdm.h          \
        serial.h        \
+       spi.h           \
        testing.h       \
        udd.h
 
diff --git a/include/rtdm/spi.h b/include/rtdm/spi.h
new file mode 100644
index 0000000..339a862
--- /dev/null
+++ b/include/rtdm/spi.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2016 Philippe Gerum <r...@xenomai.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
+ */
+#ifndef _RTDM_SPI_H
+#define _RTDM_SPI_H
+
+#include <rtdm/rtdm.h>
+#include <rtdm/uapi/spi.h>
+
+#endif /* !_RTDM_SPI_H */
diff --git a/include/rtdm/uapi/Makefile.am b/include/rtdm/uapi/Makefile.am
index d53f10c..96de2c3 100644
--- a/include/rtdm/uapi/Makefile.am
+++ b/include/rtdm/uapi/Makefile.am
@@ -8,5 +8,6 @@ includesub_HEADERS =    \
        ipc.h           \
        rtdm.h          \
        serial.h        \
+       spi.h           \
        testing.h       \
        udd.h
diff --git a/include/rtdm/uapi/rtdm.h b/include/rtdm/uapi/rtdm.h
index c49378c..ee6de65 100644
--- a/include/rtdm/uapi/rtdm.h
+++ b/include/rtdm/uapi/rtdm.h
@@ -80,13 +80,8 @@ typedef int64_t nanosecs_rel_t;
 #define RTDM_CLASS_UDD                 9
 #define RTDM_CLASS_MEMORY              10
 #define RTDM_CLASS_GPIO                        11
-/*
-#define RTDM_CLASS_USB                 ?
-#define RTDM_CLASS_FIREWIRE            ?
-#define RTDM_CLASS_INTERBUS            ?
-#define RTDM_CLASS_PROFIBUS            ?
-#define ...
-*/
+#define RTDM_CLASS_SPI                 12
+
 #define RTDM_CLASS_MISC                        223
 #define RTDM_CLASS_EXPERIMENTAL                224
 #define RTDM_CLASS_MAX                 255
diff --git a/include/rtdm/uapi/spi.h b/include/rtdm/uapi/spi.h
new file mode 100644
index 0000000..45bc92d
--- /dev/null
+++ b/include/rtdm/uapi/spi.h
@@ -0,0 +1,40 @@
+/**
+ * @note Copyright (C) 2016 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.
+ */
+#ifndef _RTDM_UAPI_SPI_H
+#define _RTDM_UAPI_SPI_H
+
+#include <linux/types.h>
+
+struct rtdm_spi_config {
+       __u32 speed_hz;
+       __u16 mode;
+       __u8 bits_per_word;
+};
+
+struct rtdm_spi_iobufs {
+       __u32 io_len;
+       __u32 i_offset;
+       __u32 o_offset;
+};
+
+#define SPI_RTIOC_SET_CONFIG           _IOW(RTDM_CLASS_SPI, 0, struct 
rtdm_spi_config)
+#define SPI_RTIOC_GET_CONFIG           _IOR(RTDM_CLASS_SPI, 1, struct 
rtdm_spi_config)
+#define SPI_RTIOC_SET_IOBUFS           _IOR(RTDM_CLASS_SPI, 2, struct 
rtdm_spi_iobufs)
+#define SPI_RTIOC_TRANSFER             _IO(RTDM_CLASS_SPI, 3)
+
+#endif /* !_RTDM_UAPI_SPI_H */
diff --git a/kernel/drivers/Kconfig b/kernel/drivers/Kconfig
index cbb6222..c2bc904 100644
--- a/kernel/drivers/Kconfig
+++ b/kernel/drivers/Kconfig
@@ -29,5 +29,6 @@ source "drivers/xenomai/analogy/Kconfig"
 source "drivers/xenomai/ipc/Kconfig"
 source "drivers/xenomai/udd/Kconfig"
 source "drivers/xenomai/gpio/Kconfig"
+source "drivers/xenomai/spi/Kconfig"
 
 endmenu
diff --git a/kernel/drivers/Makefile b/kernel/drivers/Makefile
index c9cb567..1402094 100644
--- a/kernel/drivers/Makefile
+++ b/kernel/drivers/Makefile
@@ -1 +1 @@
-obj-$(CONFIG_XENOMAI) += autotune/ serial/ testing/ can/ net/ analogy/ ipc/ 
udd/ gpio/
+obj-$(CONFIG_XENOMAI) += autotune/ serial/ testing/ can/ net/ analogy/ ipc/ 
udd/ gpio/ spi/
diff --git a/kernel/drivers/gpio/gpio-bcm2835.c 
b/kernel/drivers/gpio/gpio-bcm2835.c
index 2b5b6ac..772298b 100644
--- a/kernel/drivers/gpio/gpio-bcm2835.c
+++ b/kernel/drivers/gpio/gpio-bcm2835.c
@@ -18,22 +18,22 @@
 #include <linux/module.h>
 #include "gpio-core.h"
 
-#define RTDM_SUBCLASS_BCM2708  1
+#define RTDM_SUBCLASS_BCM2835  1
 
-static struct rtdm_gpio_chip bcm2708_gpio_chip;
+static struct rtdm_gpio_chip bcm2835_gpio_chip;
 
-static int __init bcm2708_gpio_init(void)
+static int __init bcm2835_gpio_init(void)
 {
-       return rtdm_gpiochip_add_by_name(&bcm2708_gpio_chip, "bcm2708_gpio",
-                                        RTDM_SUBCLASS_BCM2708);
+       return rtdm_gpiochip_add_by_name(&bcm2835_gpio_chip, "bcm2835_gpio",
+                                        RTDM_SUBCLASS_BCM2835);
 }
 
-static void __exit bcm2708_gpio_exit(void)
+static void __exit bcm2835_gpio_exit(void)
 {
-       rtdm_gpiochip_remove(&bcm2708_gpio_chip);
+       rtdm_gpiochip_remove(&bcm2835_gpio_chip);
 }
 
-module_init(bcm2708_gpio_init);
-module_exit(bcm2708_gpio_exit);
+module_init(bcm2835_gpio_init);
+module_exit(bcm2835_gpio_exit);
 
 MODULE_LICENSE("GPL");
diff --git a/kernel/drivers/spi/Kconfig b/kernel/drivers/spi/Kconfig
new file mode 100644
index 0000000..817e9b9
--- /dev/null
+++ b/kernel/drivers/spi/Kconfig
@@ -0,0 +1,20 @@
+menu "Real-time SPI master drivers"
+
+config XENO_DRIVERS_SPI
+       depends on SPI
+       tristate
+
+config XENO_DRIVERS_SPI_BCM2835
+       depends on ARCH_BCM2708
+       select XENO_DRIVERS_SPI
+       tristate "Support for BCM2835 SPI"
+       help
+
+       Enables support for the SPI0 controller available from
+       Broadcom's BCM2835 SoC.
+
+config XENO_DRIVERS_SPI_DEBUG
+       depends on XENO_DRIVERS_SPI
+       bool "Enable SPI core debugging features"
+       
+endmenu
diff --git a/kernel/drivers/spi/Makefile b/kernel/drivers/spi/Makefile
new file mode 100644
index 0000000..6c78a2e
--- /dev/null
+++ b/kernel/drivers/spi/Makefile
@@ -0,0 +1,8 @@
+
+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
+
+xeno_spi_bcm2835-y := spi-bcm2835.o
diff --git a/kernel/drivers/spi/spi-bcm2835.c b/kernel/drivers/spi/spi-bcm2835.c
new file mode 100644
index 0000000..02ebecd
--- /dev/null
+++ b/kernel/drivers/spi/spi-bcm2835.c
@@ -0,0 +1,691 @@
+/**
+ * I/O handling lifted from drivers/spi/spi-bcm2835.c:
+ * Copyright (C) 2012 Chris Boot
+ * Copyright (C) 2013 Stephen Warren
+ * Copyright (C) 2015 Martin Sperl
+ *
+ * RTDM integration by:
+ * Copyright (C) 2016 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.
+ */
+#ifdef CONFIG_XENO_DRIVERS_SPI_DEBUG
+#define DEBUG
+#endif
+#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_gpio.h>
+#include "spi-master.h"
+
+#define RTDM_SUBCLASS_BCM2835  1
+
+/* SPI register offsets */
+#define BCM2835_SPI_CS                 0x00
+#define BCM2835_SPI_FIFO               0x04
+#define BCM2835_SPI_CLK                        0x08
+#define BCM2835_SPI_DLEN               0x0c
+#define BCM2835_SPI_LTOH               0x10
+#define BCM2835_SPI_DC                 0x14
+
+/* Bitfields in CS */
+#define BCM2835_SPI_CS_LEN_LONG                0x02000000
+#define BCM2835_SPI_CS_DMA_LEN         0x01000000
+#define BCM2835_SPI_CS_CSPOL2          0x00800000
+#define BCM2835_SPI_CS_CSPOL1          0x00400000
+#define BCM2835_SPI_CS_CSPOL0          0x00200000
+#define BCM2835_SPI_CS_RXF             0x00100000
+#define BCM2835_SPI_CS_RXR             0x00080000
+#define BCM2835_SPI_CS_TXD             0x00040000
+#define BCM2835_SPI_CS_RXD             0x00020000
+#define BCM2835_SPI_CS_DONE            0x00010000
+#define BCM2835_SPI_CS_LEN             0x00002000
+#define BCM2835_SPI_CS_REN             0x00001000
+#define BCM2835_SPI_CS_ADCS            0x00000800
+#define BCM2835_SPI_CS_INTR            0x00000400
+#define BCM2835_SPI_CS_INTD            0x00000200
+#define BCM2835_SPI_CS_DMAEN           0x00000100
+#define BCM2835_SPI_CS_TA              0x00000080
+#define BCM2835_SPI_CS_CSPOL           0x00000040
+#define BCM2835_SPI_CS_CLEAR_RX                0x00000020
+#define BCM2835_SPI_CS_CLEAR_TX                0x00000010
+#define BCM2835_SPI_CS_CPOL            0x00000008
+#define BCM2835_SPI_CS_CPHA            0x00000004
+#define BCM2835_SPI_CS_CS_10           0x00000002
+#define BCM2835_SPI_CS_CS_01           0x00000001
+
+#define BCM2835_SPI_POLLING_LIMIT_US   30
+#define BCM2835_SPI_POLLING_JIFFIES    2
+#define BCM2835_SPI_DMA_MIN_LENGTH     96
+#define BCM2835_SPI_MODE_BITS  (SPI_CPOL | SPI_CPHA | SPI_CS_HIGH \
+                               | SPI_NO_CS | SPI_3WIRE)
+
+struct spi_master_bcm2835 {
+       struct rtdm_spi_master master;
+       void __iomem *regs;
+       struct clk *clk;
+       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;
+};
+
+struct spi_slave_bcm2835 {
+       struct rtdm_spi_remote_slave slave;
+       void *io_virt;
+       dma_addr_t io_dma;
+       size_t io_len;
+};
+
+static inline struct spi_slave_bcm2835 *
+to_slave_bcm2835(struct rtdm_spi_remote_slave *slave)
+{
+       return container_of(slave, struct spi_slave_bcm2835, slave);
+}
+
+static inline struct spi_master_bcm2835 *
+to_master_bcm2835(struct rtdm_spi_remote_slave *slave)
+{
+       return container_of(slave->master, struct spi_master_bcm2835, master);
+}
+
+static inline struct device *
+master_to_kdev(struct rtdm_spi_master *master)
+{
+       return &master->kmaster->dev;
+}
+
+static inline u32 bcm2835_rd(struct spi_master_bcm2835 *spim,
+                            unsigned int reg)
+{
+       return readl(spim->regs + reg);
+}
+
+static inline void bcm2835_wr(struct spi_master_bcm2835 *spim,
+                             unsigned int reg, u32 val)
+{
+       writel(val, spim->regs + reg);
+}
+
+static inline void bcm2835_rd_fifo(struct spi_master_bcm2835 *spim)
+{
+       u8 byte;
+
+       while (spim->rx_len > 0 &&
+              (bcm2835_rd(spim, BCM2835_SPI_CS) & BCM2835_SPI_CS_RXD)) {
+               byte = bcm2835_rd(spim, BCM2835_SPI_FIFO);
+               if (spim->rx_buf)
+                       *spim->rx_buf++ = byte;
+               spim->rx_len--;
+       }
+}
+
+static inline void bcm2835_wr_fifo(struct spi_master_bcm2835 *spim)
+{
+       u8 byte;
+
+       while (spim->tx_len > 0 &&
+              (bcm2835_rd(spim, BCM2835_SPI_CS) & BCM2835_SPI_CS_TXD)) {
+               byte = spim->tx_buf ? *spim->tx_buf++ : 0;
+               bcm2835_wr(spim, BCM2835_SPI_FIFO, byte);
+               spim->tx_len--;
+       }
+}
+
+static void bcm2835_reset_hw(struct spi_master_bcm2835 *spim)
+{
+       u32 cs = bcm2835_rd(spim, BCM2835_SPI_CS);
+
+       cs &= ~(BCM2835_SPI_CS_INTR |
+               BCM2835_SPI_CS_INTD |
+               BCM2835_SPI_CS_DMAEN |
+               BCM2835_SPI_CS_TA);
+       cs |= BCM2835_SPI_CS_CLEAR_RX | BCM2835_SPI_CS_CLEAR_TX;
+
+       /* Reset the SPI block. */
+       bcm2835_wr(spim, BCM2835_SPI_CS, cs);
+       bcm2835_wr(spim, BCM2835_SPI_DLEN, 0);
+}
+
+static int bcm2835_spi_interrupt(rtdm_irq_t *irqh)
+{
+       struct spi_master_bcm2835 *spim;
+
+       spim = rtdm_irq_get_arg(irqh, struct spi_master_bcm2835);
+
+       bcm2835_rd_fifo(spim);
+       bcm2835_wr_fifo(spim);
+
+       if (bcm2835_rd(spim, BCM2835_SPI_CS) & BCM2835_SPI_CS_DONE) {
+               bcm2835_reset_hw(spim);
+               rtdm_event_signal(&spim->transfer_done);
+       }
+
+       return RTDM_IRQ_HANDLED;
+}
+
+static int bcm2835_configure(struct rtdm_spi_remote_slave *slave)
+{
+       struct spi_master_bcm2835 *spim = to_master_bcm2835(slave);
+       struct rtdm_spi_config *config = &slave->config;
+       unsigned long spi_hz, cdiv;
+       u32 cs;
+
+       /* Set clock polarity and phase. */
+
+       cs = bcm2835_rd(spim, BCM2835_SPI_CS);
+
+       cs &= ~(BCM2835_SPI_CS_CPOL | BCM2835_SPI_CS_CPHA);
+       if (config->mode & SPI_CPOL)
+               cs |= BCM2835_SPI_CS_CPOL;
+       if (config->mode & SPI_CPHA)
+               cs |= BCM2835_SPI_CS_CPHA;
+
+       bcm2835_wr(spim, BCM2835_SPI_CS, cs);
+       
+       /* Set clock frequency. */
+
+       spi_hz = config->speed_hz;
+
+       /*
+        * Fastest clock rate is of the APB clock, which is close to
+        * clk_hz / 2.
+        */
+       if (spi_hz >= spim->clk_hz / 2)
+               cdiv = 2;
+       else if (spi_hz) {
+               cdiv = DIV_ROUND_UP(spim->clk_hz, spi_hz); /* Multiple of 2. */
+               cdiv += (cdiv % 2);
+               if (cdiv >= 65536)
+                       cdiv = 0;
+       } else
+               cdiv = 0;
+
+       bcm2835_wr(spim, BCM2835_SPI_CLK, cdiv);
+       
+       return 0;
+}
+
+static void bcm2835_chip_select(struct rtdm_spi_remote_slave *slave,
+                               bool active)
+{
+       struct spi_master_bcm2835 *spim = to_master_bcm2835(slave);
+       struct rtdm_spi_config *config = &slave->config;
+       u32 cs;
+
+       cs = bcm2835_rd(spim, BCM2835_SPI_CS);
+
+       if (config->mode & SPI_CS_HIGH) {
+               cs |= BCM2835_SPI_CS_CSPOL;
+               cs |= BCM2835_SPI_CS_CSPOL0 << slave->chip_select;
+       } else {
+               cs &= ~BCM2835_SPI_CS_CSPOL;
+               cs &= ~(BCM2835_SPI_CS_CSPOL0 << slave->chip_select);
+       }
+
+       /* "active" is the logical state, not the impedance level. */
+
+       if (active) {
+               if (config->mode & SPI_NO_CS)
+                       cs |= BCM2835_SPI_CS_CS_10 | BCM2835_SPI_CS_CS_01;
+               else {
+                       cs &= ~(BCM2835_SPI_CS_CS_10 | BCM2835_SPI_CS_CS_01);
+                       cs |= slave->chip_select;
+               }
+       } else {
+               /* Put HW-CS into deselected state. */
+               cs &= ~BCM2835_SPI_CS_CSPOL;
+               /* Use the "undefined" chip-select as precaution. */
+               cs |= BCM2835_SPI_CS_CS_10 | BCM2835_SPI_CS_CS_01;
+       }
+
+       bcm2835_wr(spim, BCM2835_SPI_CS, cs);
+}
+
+static int do_transfer_irq(struct rtdm_spi_remote_slave *slave)
+{
+       struct spi_master_bcm2835 *spim = to_master_bcm2835(slave);
+       int ret;
+       u32 cs;
+       
+       cs = bcm2835_rd(spim, BCM2835_SPI_CS);
+
+       cs &= ~BCM2835_SPI_CS_REN;
+       if ((slave->config.mode & SPI_3WIRE) && spim->rx_buf)
+               cs |= BCM2835_SPI_CS_REN;
+
+       cs |= BCM2835_SPI_CS_TA;
+
+       /*
+        * Fill in fifo if we have gpio-cs note that there have been
+        * rare events where the native-CS flapped for <1us which may
+        * change the behaviour with gpio-cs this does not happen, so
+        * it is implemented only for this case.
+        */
+       if (gpio_is_valid(slave->cs_gpio)) {
+               /* Set dummy CS, ->chip_select() was not called. */
+               cs |= BCM2835_SPI_CS_CS_10 | BCM2835_SPI_CS_CS_01;
+               /* Enable SPI block, before filling FIFO. */
+               bcm2835_wr(spim, BCM2835_SPI_CS, cs);
+               bcm2835_wr_fifo(spim);
+       }
+
+       /* Enable interrupts last, wait for transfer completion. */
+       cs |= BCM2835_SPI_CS_INTR | BCM2835_SPI_CS_INTD;
+       bcm2835_wr(spim, BCM2835_SPI_CS, cs);
+
+       ret = rtdm_event_wait(&spim->transfer_done);
+       if (ret) {
+               bcm2835_reset_hw(spim);
+               return ret;
+       }
+
+       return 0;
+}
+
+static int bcm2835_transfer_iobufs(struct rtdm_spi_remote_slave *slave)
+{
+       struct spi_master_bcm2835 *spim = to_master_bcm2835(slave);
+       struct spi_slave_bcm2835 *bcm = to_slave_bcm2835(slave);
+
+       if (bcm->io_len == 0)
+               return -EINVAL; /* No I/O buffers set. */
+       
+       spim->tx_len = bcm->io_len / 2;
+       spim->rx_len = spim->tx_len;
+       spim->tx_buf = bcm->io_virt + spim->rx_len;
+       spim->rx_buf = bcm->io_virt;
+
+       return do_transfer_irq(slave);
+}
+
+static ssize_t bcm2835_read(struct rtdm_spi_remote_slave *slave,
+                           void *rx, size_t len)
+{
+       struct spi_master_bcm2835 *spim = to_master_bcm2835(slave);
+       struct spi_slave_bcm2835 *bcm = to_slave_bcm2835(slave);
+
+       if (bcm->io_len == 0)
+               return -EINVAL; /* No I/O buffers set. */
+       
+       spim->tx_len = bcm->io_len / 2;
+       spim->rx_len = spim->tx_len;
+       spim->tx_buf = NULL;
+       spim->rx_buf = bcm->io_virt;
+
+       return do_transfer_irq(slave) ?: len;
+}
+
+static ssize_t bcm2835_write(struct rtdm_spi_remote_slave *slave,
+                            const void *tx, size_t len)
+{
+       struct spi_master_bcm2835 *spim = to_master_bcm2835(slave);
+       struct spi_slave_bcm2835 *bcm = to_slave_bcm2835(slave);
+
+       if (bcm->io_len == 0)
+               return -EINVAL; /* No I/O buffers set. */
+       
+       spim->tx_len = bcm->io_len / 2;
+       spim->rx_len = spim->tx_len;
+       spim->tx_buf = bcm->io_virt + bcm->io_len / 2;
+       spim->rx_buf = NULL;
+
+       return do_transfer_irq(slave) ?: len;
+}
+
+static int set_iobufs(struct spi_slave_bcm2835 *bcm, size_t len)
+{
+       dma_addr_t dma;
+       void *p;
+
+       if (len == 0)
+               return -EINVAL;
+       
+       len = L1_CACHE_ALIGN(len) * 2;
+       if (len == bcm->io_len)
+               return 0;
+
+       if (bcm->io_len)
+               return -EINVAL; /* I/O buffers may not be resized. */
+
+       /*
+        * Since we need the I/O buffers to be set for starting a
+        * transfer, there is no need for serializing this routine and
+        * transfer_iobufs(), provided io_len is set last.
+        *
+        * NOTE: We don't need coherent memory until we actually get
+        * DMA transfers working, this code is a bit ahead of
+        * schedule.
+        *
+        * Revisit: this assumes DMA mask is 4Gb.
+        */
+       p = dma_alloc_coherent(NULL, len, &dma, GFP_KERNEL);
+       if (p == NULL)
+               return -ENOMEM;
+
+       bcm->io_dma = dma;
+       bcm->io_virt = p;
+       smp_mb();
+       /*
+        * May race with transfer_iobufs(), must be assigned after all
+        * the rest is set up, enforcing a membar.
+        */
+       bcm->io_len = len;
+       
+       return 0;
+}
+
+static int bcm2835_set_iobufs(struct rtdm_spi_remote_slave *slave,
+                             struct rtdm_spi_iobufs *p)
+{
+       struct spi_slave_bcm2835 *bcm = to_slave_bcm2835(slave);
+       int ret;
+
+       ret = set_iobufs(bcm, p->io_len);
+       if (ret)
+               return ret;
+
+       p->i_offset = 0;
+       p->o_offset = bcm->io_len / 2;
+       p->io_len = bcm->io_len;
+       
+       return 0;
+}
+
+static int bcm2835_mmap_iobufs(struct rtdm_spi_remote_slave *slave,
+                              struct vm_area_struct *vma)
+{
+       struct spi_slave_bcm2835 *bcm = to_slave_bcm2835(slave);
+
+       /*
+        * dma_alloc_coherent() delivers non-cached memory, make sure
+        * to return consistent mapping attributes. Typically, mixing
+        * memory attributes across address spaces referring to the
+        * same physical area is architecturally wrong on ARM.
+        */
+       vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
+
+       return rtdm_mmap_kmem(vma, bcm->io_virt);
+}
+
+static void bcm2835_mmap_release(struct rtdm_spi_remote_slave *slave)
+{
+       struct spi_slave_bcm2835 *bcm = to_slave_bcm2835(slave);
+
+       dma_free_coherent(NULL, bcm->io_len,
+                         bcm->io_virt, bcm->io_dma);
+       bcm->io_len = 0;
+}
+
+static int gpio_match_name(struct gpio_chip *chip, void *data)
+{
+       return !strcmp(chip->label, data);
+}
+
+static int find_cs_gpio(struct spi_device *spi)
+{
+       struct spi_master *kmaster = spi->master;
+       u32 pingroup_index, pin, pin_index;
+       struct device_node *pins;
+       struct gpio_chip *chip;
+       int ret;
+
+       if (gpio_is_valid(spi->cs_gpio)) {
+               dev_info(&spi->dev, "using GPIO%i for CS%d\n",
+                        spi->cs_gpio, spi->chip_select);
+               return 0;
+       }
+
+       /* Translate native CS to GPIO. */
+
+       for (pingroup_index = 0;
+            (pins = of_parse_phandle(kmaster->dev.of_node,
+                    "pinctrl-0", pingroup_index)) != 0; pingroup_index++) {
+               for (pin_index = 0;
+                    of_property_read_u32_index(pins, "brcm,pins",
+                               pin_index, &pin) == 0; pin_index++) {
+                       if ((spi->chip_select == 0 &&
+                            (pin == 8 || pin == 36 || pin == 46)) ||
+                           (spi->chip_select == 1 &&
+                            (pin == 7 || pin == 35))) {
+                               spi->cs_gpio = pin;
+                               break;
+                       }
+               }
+               of_node_put(pins);
+       }
+
+       /* If that failed, assume GPIOs 7-11 are used */
+       if (!gpio_is_valid(spi->cs_gpio) ) {
+               chip = gpiochip_find("pinctrl-bcm2835", gpio_match_name);
+               if (chip == NULL)
+                       return 0;
+
+               spi->cs_gpio = chip->base + 8 - spi->chip_select;
+       }
+
+       dev_info(&spi->dev,
+                "setting up native-CS%i as GPIO %i\n",
+                spi->chip_select, spi->cs_gpio);
+
+       ret = gpio_direction_output(spi->cs_gpio,
+                           (spi->mode & SPI_CS_HIGH) ? 0 : 1);
+       if (ret) {
+               dev_err(&spi->dev,
+                       "could not set CS%i gpio %i as output: %i",
+                       spi->chip_select, spi->cs_gpio, ret);
+               return ret;
+       }
+
+       /*
+        * Force value on GPIO in case the pin controller does not
+        * handle that properly when switching to output mode.
+        */
+       gpio_set_value(spi->cs_gpio, (spi->mode & SPI_CS_HIGH) ? 0 : 1);
+
+       return 0;
+}
+
+static struct rtdm_spi_remote_slave *
+bcm2835_attach_slave(struct rtdm_spi_master *master, struct spi_device *spi)
+{
+       struct spi_slave_bcm2835 *bcm;
+       int ret;
+
+       if (spi->chip_select > 1) {
+               /*
+                * Error in the case of native CS requested with CS >
+                * 1 officially there is a CS2, but it is not
+                * documented which GPIO is connected with that...
+                */
+               dev_err(&spi->dev,
+                       "%s: only two native chip-selects are supported\n",
+                       __func__);
+               return ERR_PTR(-EINVAL);
+       }
+
+       ret = find_cs_gpio(spi);
+       if (ret)
+               return ERR_PTR(ret);
+       
+       bcm = kzalloc(sizeof(*bcm), GFP_KERNEL);
+       if (bcm == NULL)
+               return ERR_PTR(-ENOMEM);
+
+       ret = rtdm_spi_add_remote_slave(&bcm->slave, master, spi);
+       if (ret) {
+               dev_err(&spi->dev,
+                       "%s: failed to attach slave\n", __func__);
+               kfree(bcm);
+               return ERR_PTR(ret);
+       }
+
+       return &bcm->slave;
+}
+
+static void bcm2835_detach_slave(struct rtdm_spi_remote_slave *slave)
+{
+       struct spi_slave_bcm2835 *bcm = to_slave_bcm2835(slave);
+
+       rtdm_spi_remove_remote_slave(slave);
+       kfree(bcm);
+}
+
+static struct rtdm_spi_master_ops bcm2835_master_ops = {
+       .configure = bcm2835_configure,
+       .chip_select = bcm2835_chip_select,
+       .set_iobufs = bcm2835_set_iobufs,
+       .mmap_iobufs = bcm2835_mmap_iobufs,
+       .mmap_release = bcm2835_mmap_release,
+       .transfer_iobufs = bcm2835_transfer_iobufs,
+       .write = bcm2835_write,
+       .read = bcm2835_read,
+       .attach_slave = bcm2835_attach_slave,
+       .detach_slave = bcm2835_detach_slave,
+};
+
+static int bcm2835_spi_probe(struct platform_device *pdev)
+{
+       struct spi_master_bcm2835 *spim;
+       struct rtdm_spi_master *master;
+       struct spi_master *kmaster;
+       struct resource *r;
+       int ret, irq;
+
+       dev_dbg(&pdev->dev, "%s: entered\n", __func__);
+
+       master = rtdm_spi_alloc_master(&pdev->dev,
+                  struct spi_master_bcm2835, master);
+       if (master == NULL)
+               return -ENOMEM;
+
+       master->subclass = RTDM_SUBCLASS_BCM2835;
+       master->ops = &bcm2835_master_ops;
+       platform_set_drvdata(pdev, master);
+
+       kmaster = master->kmaster;
+       kmaster->mode_bits = BCM2835_SPI_MODE_BITS;
+       kmaster->bits_per_word_mask = SPI_BPW_MASK(8);
+       kmaster->num_chipselect = 2;
+       kmaster->dev.of_node = pdev->dev.of_node;
+
+       spim = container_of(master, struct spi_master_bcm2835, master);
+       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->clk = devm_clk_get(&pdev->dev, NULL);
+       if (IS_ERR(spim->clk)) {
+               ret = PTR_ERR(spim->clk);
+               goto fail;
+       }
+
+       spim->clk_hz = clk_get_rate(spim->clk);
+
+       irq = irq_of_parse_and_map(pdev->dev.of_node, 0);
+       if (irq <= 0) {
+               ret = irq ?: -ENODEV;
+               goto fail;
+       }
+
+       clk_prepare_enable(spim->clk);
+
+       /* Initialise the hardware with the default polarities */
+       bcm2835_wr(spim, BCM2835_SPI_CS,
+                  BCM2835_SPI_CS_CLEAR_RX | BCM2835_SPI_CS_CLEAR_TX);
+
+       ret = rtdm_irq_request(&spim->irqh, irq,
+                              bcm2835_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_unclk;
+       }
+
+       return 0;
+
+fail_unclk:
+       clk_disable_unprepare(spim->clk);
+fail:
+       spi_master_put(kmaster);
+
+       return ret;
+}
+
+static int bcm2835_spi_remove(struct platform_device *pdev)
+{
+       struct rtdm_spi_master *master = platform_get_drvdata(pdev);
+       struct spi_master_bcm2835 *spim;
+
+       dev_dbg(&pdev->dev, "%s: entered\n", __func__);
+
+       spim = container_of(master, struct spi_master_bcm2835, master);
+
+       /* Clear FIFOs, and disable the HW block */
+       bcm2835_wr(spim, BCM2835_SPI_CS,
+                  BCM2835_SPI_CS_CLEAR_RX | BCM2835_SPI_CS_CLEAR_TX);
+
+       rtdm_irq_free(&spim->irqh);
+
+       clk_disable_unprepare(spim->clk);
+
+       rtdm_spi_remove_master(master);
+
+       return 0;
+}
+
+static const struct of_device_id bcm2835_spi_match[] = {
+       {
+               .compatible = "brcm,bcm2835-spi",
+       },
+       { /* Sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, bcm2835_spi_match);
+
+static struct platform_driver bcm2835_spi_driver = {
+       .driver         = {
+               .name           = "spi-bcm2835",
+               .of_match_table = bcm2835_spi_match,
+       },
+       .probe          = bcm2835_spi_probe,
+       .remove         = bcm2835_spi_remove,
+};
+module_platform_driver(bcm2835_spi_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/kernel/drivers/spi/spi-device.c b/kernel/drivers/spi/spi-device.c
new file mode 100644
index 0000000..62482b2
--- /dev/null
+++ b/kernel/drivers/spi/spi-device.c
@@ -0,0 +1,180 @@
+/**
+ * @note Copyright (C) 2016 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.
+ */
+#ifdef CONFIG_XENO_DRIVERS_SPI_DEBUG
+#define DEBUG
+#endif
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <linux/err.h>
+#include <linux/gpio.h>
+#include <linux/spi/spi.h>
+#include "spi-master.h"
+
+int rtdm_spi_add_remote_slave(struct rtdm_spi_remote_slave *slave,
+                             struct rtdm_spi_master *master,
+                             struct spi_device *spi)
+{
+       struct spi_master *kmaster = master->kmaster;
+       struct rtdm_device *dev;
+       rtdm_lockctx_t c;
+       int ret;
+
+       memset(slave, 0, sizeof(*slave));
+       slave->chip_select = spi->chip_select;
+       slave->config.bits_per_word = spi->bits_per_word;
+       slave->config.speed_hz = spi->max_speed_hz;
+       slave->config.mode = spi->mode;
+       slave->master = master;
+       mutex_init(&slave->ctl_lock);
+       
+       dev = &slave->dev;
+       dev->driver = &master->driver;
+       dev->label = kasprintf(GFP_KERNEL, "%s/slave%d.%%d",
+                              dev_name(&kmaster->dev),
+                              kmaster->bus_num);
+       if (dev->label == NULL) {
+               ret = -ENOMEM;
+               goto fail_label;
+       }
+
+       dev->device_data = master;
+       ret = rtdm_dev_register(dev);
+       if (ret)
+               goto fail_register;
+
+       if (gpio_is_valid(spi->cs_gpio))
+               slave->cs_gpio = spi->cs_gpio;
+       else {
+               slave->cs_gpio = -ENOENT;
+               if (kmaster->cs_gpios)
+                       slave->cs_gpio = kmaster->cs_gpios[spi->chip_select];
+       }
+
+       if (gpio_is_valid(slave->cs_gpio))
+               dev_dbg(slave_to_kdev(slave), "using CS GPIO%d\n",
+                       slave->cs_gpio);
+
+       rtdm_lock_get_irqsave(&master->lock, c);
+       list_add_tail(&slave->next, &master->slaves);
+       rtdm_lock_put_irqrestore(&master->lock, c);
+
+       return 0;
+
+fail_register:
+       kfree(dev->label);
+
+fail_label:
+
+       return ret;
+}
+EXPORT_SYMBOL_GPL(rtdm_spi_add_remote_slave);
+
+void rtdm_spi_remove_remote_slave(struct rtdm_spi_remote_slave *slave)
+{
+       struct rtdm_spi_master *master = slave->master;
+       struct rtdm_device *dev;
+       rtdm_lockctx_t c;
+       
+       mutex_destroy(&slave->ctl_lock);
+       rtdm_lock_get_irqsave(&master->lock, c);
+       list_del(&slave->next);
+       rtdm_lock_put_irqrestore(&master->lock, c);
+       dev = &slave->dev;
+       rtdm_dev_unregister(dev);
+       kfree(dev->label);
+}
+EXPORT_SYMBOL_GPL(rtdm_spi_remove_remote_slave);
+
+static int spi_device_probe(struct spi_device *spi)
+{
+       struct rtdm_spi_remote_slave *slave;
+       struct rtdm_spi_master *master;
+       int ret;
+
+       /*
+        * Chicken and egg issue: we want the RTDM device class name
+        * to duplicate the SPI master name, but that information is
+        * only available after spi_register_master() has returned. We
+        * solve this by initializing the RTDM driver descriptor on
+        * the fly when the first SPI device on the bus is advertised
+        * on behalf of spi_register_master().
+        *
+        * NOTE: the driver core guarantees serialization.
+        */
+       master = spi_master_get_devdata(spi->master);
+       if (master->devclass == NULL) {
+               ret = __rtdm_spi_setup_driver(master);
+               if (ret)
+                       return ret;
+       }
+
+       slave = master->ops->attach_slave(master, spi);
+       if (IS_ERR(slave))
+               return PTR_ERR(slave);
+
+       spi_set_drvdata(spi, slave);
+
+       return 0;
+}
+
+static int spi_device_remove(struct spi_device *spi)
+{
+       struct rtdm_spi_remote_slave *slave = spi_get_drvdata(spi);
+
+       slave->master->ops->detach_slave(slave);
+
+       return 0;
+}
+
+static const struct of_device_id spi_device_match[] = {
+       {
+               .compatible = "rtdm-spidev",
+       },
+       { /* Sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, spi_device_match);
+
+static struct spi_driver spi_device_driver = {
+       .driver = {
+               .name = "rtdm_spi_device",
+               .owner = THIS_MODULE,
+               .of_match_table = spi_device_match,
+       },
+       .probe  = spi_device_probe,
+       .remove = spi_device_remove,
+};
+
+static int __init spi_device_init(void)
+{
+       int ret;
+
+       ret = spi_register_driver(&spi_device_driver);
+
+       return ret;
+}
+module_init(spi_device_init);
+
+static void __exit spi_device_exit(void)
+{
+       spi_unregister_driver(&spi_device_driver);
+
+}
+module_exit(spi_device_exit);
diff --git a/kernel/drivers/spi/spi-device.h b/kernel/drivers/spi/spi-device.h
new file mode 100644
index 0000000..fcfca90
--- /dev/null
+++ b/kernel/drivers/spi/spi-device.h
@@ -0,0 +1,53 @@
+/**
+ * @note Copyright (C) 2016 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.
+ */
+#ifndef _RTDM_SPI_DEVICE_H
+#define _RTDM_SPI_DEVICE_H
+
+#include <linux/list.h>
+#include <linux/atomic.h>
+#include <linux/mutex.h>
+#include <rtdm/driver.h>
+#include <rtdm/uapi/spi.h>
+
+struct class;
+struct rtdm_spi_master;
+
+struct rtdm_spi_remote_slave {
+       u8 chip_select;
+       int cs_gpio;
+       struct rtdm_device dev;
+       struct list_head next;
+       struct rtdm_spi_config config;
+       struct rtdm_spi_master *master;
+       atomic_t mmap_refs;
+       struct mutex ctl_lock;
+};
+
+static inline struct device *
+slave_to_kdev(struct rtdm_spi_remote_slave *slave)
+{
+       return rtdm_dev_to_kdev(&slave->dev);
+}
+
+int rtdm_spi_add_remote_slave(struct rtdm_spi_remote_slave *slave,
+                             struct rtdm_spi_master *spim,
+                             struct spi_device *spi);
+
+void rtdm_spi_remove_remote_slave(struct rtdm_spi_remote_slave *slave);
+
+#endif /* !_RTDM_SPI_DEVICE_H */
diff --git a/kernel/drivers/spi/spi-master.c b/kernel/drivers/spi/spi-master.c
new file mode 100644
index 0000000..132a3b9
--- /dev/null
+++ b/kernel/drivers/spi/spi-master.c
@@ -0,0 +1,423 @@
+/**
+ * @note Copyright (C) 2016 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.
+ */
+#ifdef CONFIG_XENO_DRIVERS_SPI_DEBUG
+#define DEBUG
+#endif
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <linux/err.h>
+#include <linux/spi/spi.h>
+#include "spi-master.h"
+
+static inline
+struct device *to_kdev(struct rtdm_spi_remote_slave *slave)
+{
+       return rtdm_dev_to_kdev(&slave->dev);
+}
+
+static inline struct rtdm_spi_remote_slave *fd_to_slave(struct rtdm_fd *fd)
+{
+       struct rtdm_device *dev = rtdm_fd_device(fd);
+
+       return container_of(dev, struct rtdm_spi_remote_slave, dev);
+}
+
+static int update_slave_config(struct rtdm_spi_remote_slave *slave,
+                              struct rtdm_spi_config *config)
+{
+       struct rtdm_spi_config old_config;
+       struct rtdm_spi_master *master = slave->master;
+       int ret;
+
+       rtdm_mutex_lock(&master->bus_lock);
+
+       old_config = slave->config;
+       slave->config = *config;
+       ret = slave->master->ops->configure(slave);
+       if (ret) {
+               slave->config = old_config;
+               rtdm_mutex_unlock(&master->bus_lock);
+               return ret;
+       }
+
+       rtdm_mutex_unlock(&master->bus_lock);
+       
+       dev_info(to_kdev(slave),
+                "configured mode %d, %s%s%s%s%u bits/w, %u Hz max\n",
+                (int) (slave->config.mode & (SPI_CPOL | SPI_CPHA)),
+                (slave->config.mode & SPI_CS_HIGH) ? "cs_high, " : "",
+                (slave->config.mode & SPI_LSB_FIRST) ? "lsb, " : "",
+                (slave->config.mode & SPI_3WIRE) ? "3wire, " : "",
+                (slave->config.mode & SPI_LOOP) ? "loopback, " : "",
+                slave->config.bits_per_word,
+                slave->config.speed_hz);
+       
+       return 0;
+}
+
+static int spi_master_open(struct rtdm_fd *fd, int oflags)
+{
+       struct rtdm_spi_remote_slave *slave = fd_to_slave(fd);
+       struct rtdm_spi_master *master = slave->master;
+
+       if (master->ops->open)
+               return master->ops->open(slave);
+               
+       return 0;
+}
+
+static void spi_master_close(struct rtdm_fd *fd)
+{
+       struct rtdm_spi_remote_slave *slave = fd_to_slave(fd);
+       struct rtdm_spi_master *master = slave->master;
+       rtdm_lockctx_t c;
+
+       rtdm_lock_get_irqsave(&master->lock, c);
+
+       if (master->cs == slave)
+               master->cs = NULL;
+
+       rtdm_lock_put_irqrestore(&master->lock, c);
+
+       if (master->ops->close)
+               master->ops->close(slave);
+}
+
+static int do_chip_select(struct rtdm_spi_remote_slave *slave)
+{                              /* master->bus_lock held */
+       struct rtdm_spi_master *master = slave->master;
+       rtdm_lockctx_t c;
+
+       if (slave->config.speed_hz == 0)
+               return -EINVAL; /* Setup is missing. */
+
+       /* Serialize with spi_master_close() */
+       rtdm_lock_get_irqsave(&master->lock, c);
+       
+       if (master->cs != slave) {
+               master->ops->chip_select(slave, true);
+               master->cs = slave;
+       }
+
+       rtdm_lock_put_irqrestore(&master->lock, c);
+
+       return 0;
+}
+
+static void do_chip_deselect(struct rtdm_spi_remote_slave *slave)
+{                              /* master->bus_lock held */
+       struct rtdm_spi_master *master = slave->master;
+       rtdm_lockctx_t c;
+
+       rtdm_lock_get_irqsave(&master->lock, c);
+       master->ops->chip_select(slave, false);
+       master->cs = NULL;
+       rtdm_lock_put_irqrestore(&master->lock, c);
+}
+
+static int spi_master_ioctl_rt(struct rtdm_fd *fd,
+                              unsigned int request, void *arg)
+{
+       struct rtdm_spi_remote_slave *slave = fd_to_slave(fd);
+       struct rtdm_spi_master *master = slave->master;
+       struct rtdm_spi_config config;
+       int ret;
+
+       switch (request) {
+       case SPI_RTIOC_SET_CONFIG:
+               ret = rtdm_safe_copy_from_user(fd, &config,
+                                              arg, sizeof(config));
+               if (ret == 0)
+                       ret = update_slave_config(slave, &config);
+               break;
+       case SPI_RTIOC_GET_CONFIG:
+               rtdm_mutex_lock(&master->bus_lock);
+               config = slave->config;
+               rtdm_mutex_unlock(&master->bus_lock);
+               ret = rtdm_safe_copy_to_user(fd, arg,
+                                            &config, sizeof(config));
+               break;
+       case SPI_RTIOC_TRANSFER:
+               ret = -EINVAL;
+               if (master->ops->transfer_iobufs) {
+                       rtdm_mutex_lock(&master->bus_lock);
+                       ret = do_chip_select(slave);
+                       if (ret == 0) {
+                               ret = master->ops->transfer_iobufs(slave);
+                               do_chip_deselect(slave);
+                       }
+                       rtdm_mutex_unlock(&master->bus_lock);
+               }
+               break;
+       default:
+               ret = -ENOSYS;
+       }
+
+       return ret;
+}
+
+static int spi_master_ioctl_nrt(struct rtdm_fd *fd,
+                               unsigned int request, void *arg)
+{
+       struct rtdm_spi_remote_slave *slave = fd_to_slave(fd);
+       struct rtdm_spi_master *master = slave->master;
+       struct rtdm_spi_iobufs iobufs;
+       int ret;
+
+       switch (request) {
+       case SPI_RTIOC_SET_IOBUFS:
+               ret = rtdm_safe_copy_from_user(fd, &iobufs,
+                                              arg, sizeof(iobufs));
+               if (ret)
+                       break;
+               /*
+                * No transfer can happen without I/O buffers being
+                * set, and I/O buffers cannot be reset, therefore we
+                * need no serialization with the transfer code here.
+                */
+               mutex_lock(&slave->ctl_lock);
+               ret = master->ops->set_iobufs(slave, &iobufs);
+               mutex_unlock(&slave->ctl_lock);
+               if (ret == 0)
+                       ret = rtdm_safe_copy_to_user(fd, arg,
+                                            &iobufs, sizeof(iobufs));
+               break;
+       default:
+               ret = -EINVAL;
+       }
+
+       return ret;
+}
+
+static ssize_t spi_master_read_rt(struct rtdm_fd *fd,
+                                 void __user *u_buf, size_t len)
+{
+       struct rtdm_spi_remote_slave *slave = fd_to_slave(fd);
+       struct rtdm_spi_master *master = slave->master;
+       void *rx;
+       int ret;
+
+       if (len == 0)
+               return 0;
+
+       rx = xnmalloc(len);
+       if (rx == NULL)
+               return -ENOMEM;
+
+       rtdm_mutex_lock(&master->bus_lock);
+       ret = do_chip_select(slave);
+       if (ret == 0) {
+               ret = master->ops->read(slave, rx, len);
+               do_chip_deselect(slave);
+       }
+       rtdm_mutex_unlock(&master->bus_lock);
+       if (ret == 0)
+               ret = rtdm_safe_copy_to_user(fd, u_buf, rx, len);
+       
+       xnfree(rx);
+       
+       return ret;
+}
+
+static ssize_t spi_master_write_rt(struct rtdm_fd *fd,
+                                  const void __user *u_buf, size_t len)
+{
+       struct rtdm_spi_remote_slave *slave = fd_to_slave(fd);
+       struct rtdm_spi_master *master = slave->master;
+       void *tx;
+       int ret;
+
+       if (len == 0)
+               return 0;
+
+       tx = xnmalloc(len);
+       if (tx == NULL)
+               return -ENOMEM;
+
+       ret = rtdm_safe_copy_from_user(fd, tx, u_buf, len);
+       if (ret == 0) {
+               rtdm_mutex_lock(&master->bus_lock);
+               ret = do_chip_select(slave);
+               if (ret == 0) {
+                       ret = master->ops->write(slave, tx, len);
+                       do_chip_deselect(slave);
+               }
+               rtdm_mutex_unlock(&master->bus_lock);
+       }
+       
+       xnfree(tx);
+
+       return ret;
+}
+
+static void iobufs_vmopen(struct vm_area_struct *vma)
+{
+       struct rtdm_spi_remote_slave *slave = vma->vm_private_data;
+
+       atomic_inc(&slave->mmap_refs);
+       dev_dbg(slave_to_kdev(slave), "mapping added\n");
+}
+
+static void iobufs_vmclose(struct vm_area_struct *vma)
+{
+       struct rtdm_spi_remote_slave *slave = vma->vm_private_data;
+
+       if (atomic_dec_and_test(&slave->mmap_refs)) {
+               slave->master->ops->mmap_release(slave);
+               dev_dbg(slave_to_kdev(slave), "mapping released\n");
+       }
+}
+
+static struct vm_operations_struct iobufs_vmops = {
+       .open = iobufs_vmopen,
+       .close = iobufs_vmclose,
+};
+
+static int spi_master_mmap(struct rtdm_fd *fd, struct vm_area_struct *vma)
+{
+       struct rtdm_spi_remote_slave *slave = fd_to_slave(fd);
+       int ret;
+
+       if (slave->master->ops->mmap_iobufs == NULL)
+               return -EINVAL;
+
+       ret = slave->master->ops->mmap_iobufs(slave, vma);
+       if (ret)
+               return ret;
+
+       dev_dbg(slave_to_kdev(slave), "mapping created\n");
+       atomic_inc(&slave->mmap_refs);
+
+       if (slave->master->ops->mmap_release) {
+               vma->vm_ops = &iobufs_vmops;
+               vma->vm_private_data = slave;
+       }
+
+       return 0;
+}
+
+static char *spi_slave_devnode(struct device *dev, umode_t *mode)
+{
+       return kasprintf(GFP_KERNEL, "rtdm/%s/%s",
+                        dev->class->name,
+                        dev_name(dev));
+}
+
+struct rtdm_spi_master *
+__rtdm_spi_alloc_master(struct device *dev, size_t size, int off)
+{
+       struct rtdm_spi_master *master;
+       struct spi_master *kmaster;
+
+       kmaster = spi_alloc_master(dev, size);
+       if (kmaster == NULL)
+               return NULL;
+       
+       master = (void *)(kmaster + 1) + off;
+       master->kmaster = kmaster;
+       spi_master_set_devdata(kmaster, master);
+
+       return master;
+}
+EXPORT_SYMBOL_GPL(__rtdm_spi_alloc_master);
+
+int __rtdm_spi_setup_driver(struct rtdm_spi_master *master)
+{
+       master->classname = kstrdup(
+               dev_name(&master->kmaster->dev), GFP_KERNEL);
+       master->devclass = class_create(THIS_MODULE,
+               master->classname);
+       if (IS_ERR(master->devclass)) {
+               kfree(master->classname);
+               printk(XENO_ERR "cannot create sysfs class\n");
+               return PTR_ERR(master->devclass);
+       }
+
+       master->devclass->devnode = spi_slave_devnode;
+       master->cs = NULL;
+
+       master->driver.profile_info = (struct rtdm_profile_info)
+               RTDM_PROFILE_INFO(rtdm_spi_master,
+                                 RTDM_CLASS_SPI,
+                                 master->subclass,
+                                 0);
+       master->driver.device_flags = RTDM_NAMED_DEVICE;
+       master->driver.base_minor = 0;
+       master->driver.device_count = 256;
+       master->driver.context_size = 0;
+       master->driver.ops = (struct rtdm_fd_ops){
+               .open           =       spi_master_open,
+               .close          =       spi_master_close,
+               .read_rt        =       spi_master_read_rt,
+               .write_rt       =       spi_master_write_rt,
+               .ioctl_rt       =       spi_master_ioctl_rt,
+               .ioctl_nrt      =       spi_master_ioctl_nrt,
+               .mmap           =       spi_master_mmap,
+       };
+       
+       rtdm_drv_set_sysclass(&master->driver, master->devclass);
+
+       INIT_LIST_HEAD(&master->slaves);
+       rtdm_lock_init(&master->lock);
+       rtdm_mutex_init(&master->bus_lock);
+
+       return 0;
+}
+
+static int spi_transfer_one_unimp(struct spi_master *master,
+                                 struct spi_device *spi,
+                                 struct spi_transfer *tfr)
+{
+       return -ENODEV;
+}
+
+int rtdm_spi_add_master(struct rtdm_spi_master *master)
+{
+       struct spi_master *kmaster = master->kmaster;
+
+       /*
+        * Prevent the transfer handler to be called from the regular
+        * SPI stack, just in case.
+        */
+       kmaster->transfer_one = spi_transfer_one_unimp;
+       master->devclass = NULL;
+
+       /*
+        * Add the core SPI driver, devices on the bus will be
+        * enumerated, handed to spi_device_probe().
+        */
+       return spi_register_master(kmaster);
+}
+EXPORT_SYMBOL_GPL(rtdm_spi_add_master);
+
+void rtdm_spi_remove_master(struct rtdm_spi_master *master)
+{
+       struct class *class = master->devclass;
+       char *classname = master->classname;
+       
+       rtdm_mutex_destroy(&master->bus_lock);
+       spi_unregister_master(master->kmaster);
+       class_destroy(class);
+       kfree(classname);
+}
+EXPORT_SYMBOL_GPL(rtdm_spi_remove_master);
+
+MODULE_LICENSE("GPL");
diff --git a/kernel/drivers/spi/spi-master.h b/kernel/drivers/spi/spi-master.h
new file mode 100644
index 0000000..b5a9ce7
--- /dev/null
+++ b/kernel/drivers/spi/spi-master.h
@@ -0,0 +1,81 @@
+/**
+ * @note Copyright (C) 2016 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.
+ */
+#ifndef _RTDM_SPI_MASTER_H
+#define _RTDM_SPI_MASTER_H
+
+#include <rtdm/driver.h>
+#include <rtdm/uapi/spi.h>
+#include "spi-device.h"
+
+struct class;
+struct device_node;
+struct rtdm_spi_master;
+struct spi_master;
+
+struct rtdm_spi_master_ops {
+       int (*open)(struct rtdm_spi_remote_slave *slave);
+       void (*close)(struct rtdm_spi_remote_slave *slave);
+       int (*configure)(struct rtdm_spi_remote_slave *slave);
+       void (*chip_select)(struct rtdm_spi_remote_slave *slave,
+                           bool active);
+       int (*set_iobufs)(struct rtdm_spi_remote_slave *slave,
+                         struct rtdm_spi_iobufs *p);
+       int (*mmap_iobufs)(struct rtdm_spi_remote_slave *slave,
+                          struct vm_area_struct *vma);
+       void (*mmap_release)(struct rtdm_spi_remote_slave *slave);
+       int (*transfer_iobufs)(struct rtdm_spi_remote_slave *slave);
+       ssize_t (*write)(struct rtdm_spi_remote_slave *slave,
+                        const void *tx, size_t len);
+       ssize_t (*read)(struct rtdm_spi_remote_slave *slave,
+                        void *rx, size_t len);
+       struct rtdm_spi_remote_slave *(*attach_slave)
+               (struct rtdm_spi_master *master,
+                       struct spi_device *spi);
+       void (*detach_slave)(struct rtdm_spi_remote_slave *slave);
+};
+
+struct rtdm_spi_master {
+       int subclass;
+       const struct rtdm_spi_master_ops *ops;
+       struct spi_master *kmaster;
+       struct {        /* Internal */
+               struct rtdm_driver driver;
+               struct class *devclass;
+               char *classname;
+               struct list_head slaves;
+               struct list_head next;
+               rtdm_lock_t lock;
+               rtdm_mutex_t bus_lock;
+               struct rtdm_spi_remote_slave *cs;
+       };
+};
+
+#define rtdm_spi_alloc_master(__dev, __type, __mptr)                   \
+       __rtdm_spi_alloc_master(__dev, sizeof(__type),                  \
+                               offsetof(__type, __mptr))               \
+
+struct rtdm_spi_master *
+__rtdm_spi_alloc_master(struct device *dev, size_t size, int off);
+
+int __rtdm_spi_setup_driver(struct rtdm_spi_master *master);
+
+int rtdm_spi_add_master(struct rtdm_spi_master *master);
+
+void rtdm_spi_remove_master(struct rtdm_spi_master *master);
+
+#endif /* !_RTDM_SPI_MASTER_H */


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

Reply via email to