Module: xenomai-3 Branch: master Commit: b7e3bd434ff00403bfe971c531c467e42502386f URL: http://git.xenomai.org/?p=xenomai-3.git;a=commit;h=b7e3bd434ff00403bfe971c531c467e42502386f
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 c837a05..9198595 100644 --- a/include/rtdm/Makefile.am +++ b/include/rtdm/Makefile.am @@ -10,6 +10,7 @@ includesub_HEADERS += \ gpio.h \ ipc.h \ serial.h \ + spi.h \ testing.h \ udd.h endif 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 c288ff9..fec15db 100644 --- a/include/rtdm/uapi/Makefile.am +++ b/include/rtdm/uapi/Makefile.am @@ -10,6 +10,7 @@ includesub_HEADERS += \ gpio.h \ ipc.h \ serial.h \ + spi.h \ testing.h \ udd.h endif 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