The PIC32 SPI driver is capable of performing SPI transfers using PIO or external DMA engine. GPIO controlled /CS support is made default in the driver for correct operation of the controller. This can be enabled by adding "cs-gpios" property of the SPI node in board dts file. Signed-off-by: Purna Chandra Mandal <purna.mandal@xxxxxxxxxxxxx> --- Changes in v2: - drop internal function drain_rx_fifo() - merge wrapper functions with callers wherever applicable - sort includes alphabetically - Kconfig: sort SPI_PIC32 alphabetically and add || COMPILE_TEST - Makefile: sort SPI_PIC32 alphabetically - replace cpu_relax() with ndelay() in disable_chip() - drop spi controller driven /CS management completely, use only GPIO controller /CS. - rename function names starting with 'spi' to avoid namespace conflict - use .one_transfer() callback instead of .one_message(). - drop /CS assert-deassert functions as core provides those. - fix race while completing transfer before disabling interrupt. drivers/spi/Kconfig | 6 + drivers/spi/Makefile | 1 + drivers/spi/spi-pic32.c | 1004 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1011 insertions(+) create mode 100644 drivers/spi/spi-pic32.c diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 7706416..22f973f 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -396,6 +396,12 @@ config SPI_ORION help This enables using the SPI master controller on the Orion chips. +config SPI_PIC32 + tristate "Microchip PIC32 series SPI" + depends on MACH_PIC32 || COMPILE_TEST + help + SPI driver for Microchip PIC32 SPI master controller. + config SPI_PL022 tristate "ARM AMBA PL022 SSP controller" depends on ARM_AMBA diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 8991ffc..1bcb417 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -60,6 +60,7 @@ obj-$(CONFIG_SPI_OMAP_100K) += spi-omap-100k.o obj-$(CONFIG_SPI_OMAP24XX) += spi-omap2-mcspi.o obj-$(CONFIG_SPI_TI_QSPI) += spi-ti-qspi.o obj-$(CONFIG_SPI_ORION) += spi-orion.o +obj-$(CONFIG_SPI_PIC32) += spi-pic32.o obj-$(CONFIG_SPI_PL022) += spi-pl022.o obj-$(CONFIG_SPI_PPC4xx) += spi-ppc4xx.o spi-pxa2xx-platform-objs := spi-pxa2xx.o diff --git a/drivers/spi/spi-pic32.c b/drivers/spi/spi-pic32.c new file mode 100644 index 0000000..8627aa1 --- /dev/null +++ b/drivers/spi/spi-pic32.c @@ -0,0 +1,1004 @@ +/* + * Microchip PIC32 SPI controller driver. + * + * Purna Chandra Mandal <purna.mandal@xxxxxxxxxxxxx> + * Copyright (c) 2016, Microchip Technology Inc. + * + * This program is free software; you can distribute it and/or modify it + * under the terms of the GNU General Public License (Version 2) as + * published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + */ + +#include <linux/clk.h> +#include <linux/clkdev.h> +#include <linux/delay.h> +#include <linux/dmaengine.h> +#include <linux/dma-mapping.h> +#include <linux/highmem.h> +#include <linux/module.h> +#include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/of.h> +#include <linux/of_irq.h> +#include <linux/of_gpio.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> +#include <linux/spi/spi.h> + +/* SPI Register offsets */ +struct pic32_spi_regs { + u32 ctrl; + u32 ctrl_clr; + u32 ctrl_set; + u32 ctrl_inv; + u32 status; + u32 status_clr; + u32 status_set; + u32 status_inv; + u32 buf; + u32 reserved[3]; + u32 baud; + u32 reserved2[3]; + u32 ctrl2; + u32 ctrl2_clr; + u32 ctrl2_set; + u32 ctrl2_inv; +}; + +/* Bit fields in SPI Control Register */ +#define CTRL_RX_INT_SHIFT 0 /* Rx interrupt generation */ +#define RX_FIFO_EMTPY 0 +#define RX_FIFO_NOT_EMPTY 1 /* not empty */ +#define RX_FIFO_HALF_FULL 2 /* full by half or more */ +#define RX_FIFO_FULL 3 /* completely full */ + +#define CTRL_TX_INT_SHIFT 2 /* TX interrupt generation */ +#define TX_FIFO_ALL_EMPTY 0 /* completely empty */ +#define TX_FIFO_EMTPY 1 /* empty */ +#define TX_FIFO_HALF_EMPTY 2 /* empty by half or more */ +#define TX_FIFO_NOT_FULL 3 /* atleast one empty */ + +#define CTRL_MSTEN BIT(5) /* enable master mode */ +#define CTRL_CKP BIT(6) /* active low */ +#define CTRL_CKE BIT(8) /* Tx on falling edge */ +#define CTRL_SMP BIT(9) /* Rx at middle or end of tx */ +#define CTRL_BPW_MASK 0x03 /* bits per word/sample */ +#define CTRL_BPW_SHIFT 10 +#define PIC32_BPW_8 0 +#define PIC32_BPW_16 1 +#define PIC32_BPW_32 2 +#define CTRL_ON BIT(15) /* enable macro */ +#define CTRL_ENHBUF BIT(16) /* enable enhanced buffering */ +#define CTRL_MCLKSEL BIT(23) /* select clock source */ +#define CTRL_MSSEN BIT(28) /* macro driven /SS */ +#define CTRL_FRMEN BIT(31) /* enable framing mode */ + +/* Bit fields in SPI Status Register */ +#define STAT_RF_EMPTY BIT(5) /* RX Fifo empty */ +#define STAT_RX_OV BIT(6) /* err, s/w needs to clear */ +#define STAT_TX_UR BIT(8) /* UR in Framed SPI modes */ +#define STAT_FRM_ERR BIT(12) /* Multiple Frame Sync pulse */ +#define STAT_TF_LVL_MASK 0x1F +#define STAT_TF_LVL_SHIFT 16 +#define STAT_RF_LVL_MASK 0x1F +#define STAT_RF_LVL_SHIFT 24 + +/* Bit fields in Baud Register */ +#define BAUD_MASK 0x1ff + +/* Bit fields in Control2 Register */ +#define CTRL2_TX_UR_EN BIT(10) /* Enable int on Tx under-run */ +#define CTRL2_RX_OV_EN BIT(11) /* Enable int on Rx over-run */ +#define CTRL2_FRM_ERR_EN BIT(12) /* Enable frame err int */ + +/* Minimum DMA transfer size */ +#define PIC32_DMA_LEN_MIN 64 + +struct pic32_spi { + dma_addr_t dma_base; + struct pic32_spi_regs __iomem *regs; + int fault_irq; + int rx_irq; + int tx_irq; + u32 fifo_n_byte; /* FIFO depth in bytes */ + struct clk *clk; + spinlock_t lock; /* lock */ + struct spi_master *master; + /* Current SPI device specific */ + struct spi_device *spi_dev; + u32 speed_hz; /* spi-clk rate */ + u32 mode; +#define PIC32F_XFER_PIO 0 /* do PIO transfer */ +#define PIC32F_DMA_PREP 1 /* DMA chnls configured */ +#define PIC32F_DMA_READY 2 /* buffer mapped and DMA issued */ + unsigned long flags; + u32 fifo_n_elm; /* FIFO depth in words */ + enum dma_slave_buswidth dma_width; + /* Current transfer state */ + struct spi_message *mesg; + const void *tx; + const void *tx_end; + const void *rx; + const void *rx_end; + int len; + struct completion xfer_done; + /* SPI FiFo accessor */ + void (*rx_fifo)(struct pic32_spi *); + void (*tx_fifo)(struct pic32_spi *); +}; + +static bool pic32_spi_debug; + +static void pic32_spi_enable_chip(struct pic32_spi *pic32s) +{ + writel(CTRL_ON, &pic32s->regs->ctrl_set); +} + +static void pic32_spi_disable_chip(struct pic32_spi *pic32s) +{ + writel(CTRL_ON, &pic32s->regs->ctrl_clr); + + /* avoid read/write to SPI register at immediate next CPU clock */ + ndelay(20); +} + +static void pic32_spi_set_clk_rate(struct pic32_spi *pic32s, u32 spi_ck) +{ + u32 div; + + /* div = (clk_in / (2 * spi_ck)) - 1 */ + div = DIV_ROUND_CLOSEST(clk_get_rate(pic32s->clk), 2 * spi_ck) - 1; + + writel(div & BAUD_MASK, &pic32s->regs->baud); +} + +static u32 pic32_rx_fifo_level(struct pic32_spi *pic32s) +{ + u32 sr = readl(&pic32s->regs->status); + + return (sr >> STAT_RF_LVL_SHIFT) & STAT_RF_LVL_MASK; +} + +static u32 pic32_tx_fifo_level(struct pic32_spi *pic32s) +{ + u32 sr = readl(&pic32s->regs->status); + + return (sr >> STAT_TF_LVL_SHIFT) & STAT_TF_LVL_MASK; +} + +/* Return the max entries we can fill into tx fifo */ +static u32 pic32_tx_max(struct pic32_spi *pic32s, int n_bytes) +{ + u32 tx_left, tx_room, rxtx_gap; + + tx_left = (pic32s->tx_end - pic32s->tx) / n_bytes; + tx_room = pic32s->fifo_n_elm - pic32_tx_fifo_level(pic32s); + + /* + * Another concern is about the tx/rx mismatch, we + * though to use (pic32s->fifo_n_byte - rxfl - txfl) as + * one maximum value for tx, but it doesn't cover the + * data which is out of tx/rx fifo and inside the + * shift registers. So a ctrl from sw point of + * view is taken. + */ + rxtx_gap = ((pic32s->rx_end - pic32s->rx) - + (pic32s->tx_end - pic32s->tx)) / n_bytes; + return min3(tx_left, tx_room, (u32)(pic32s->fifo_n_elm - rxtx_gap)); +} + +/* Return the max entries we should read out of rx fifo */ +static u32 pic32_rx_max(struct pic32_spi *pic32s, int n_bytes) +{ + u32 rx_left = (pic32s->rx_end - pic32s->rx) / n_bytes; + + return min_t(u32, rx_left, pic32_rx_fifo_level(pic32s)); +} + +#define BUILD_SPI_FIFO_RW(__name, __type, __bwl) \ +static void pic32_spi_rx_##__name(struct pic32_spi *pic32s) \ +{ \ + __type v; \ + u32 mx = pic32_rx_max(pic32s, sizeof(__type)); \ + for (; mx; mx--) { \ + v = read##__bwl(&pic32s->regs->buf); \ + if (pic32s->rx_end - pic32s->len) \ + *(__type *)(pic32s->rx) = v; \ + pic32s->rx += sizeof(__type); \ + } \ +} \ + \ +static void pic32_spi_tx_##__name(struct pic32_spi *pic32s) \ +{ \ + __type v; \ + u32 mx = pic32_tx_max(pic32s, sizeof(__type)); \ + for (; mx ; mx--) { \ + v = (__type)~0U; \ + if (pic32s->tx_end - pic32s->len) \ + v = *(__type *)(pic32s->tx); \ + write##__bwl(v, &pic32s->regs->buf); \ + pic32s->tx += sizeof(__type); \ + } \ +} + +BUILD_SPI_FIFO_RW(byte, u8, b); +BUILD_SPI_FIFO_RW(word, u16, w); +BUILD_SPI_FIFO_RW(dword, u32, l); + +static void pic32_err_stop(struct pic32_spi *pic32s, const char *msg) +{ + /* Stop h/w */ + pic32_spi_disable_chip(pic32s); + + /* disable all interrupts */ + disable_irq_nosync(pic32s->fault_irq); + disable_irq_nosync(pic32s->rx_irq); + disable_irq_nosync(pic32s->tx_irq); + + /* Show err message and abort xfer with err */ + dev_err(&pic32s->master->dev, "%s\n", msg); + pic32s->mesg->status = -EIO; + complete(&pic32s->xfer_done); + + dev_err(&pic32s->master->dev, "irq: disable all\n"); +} + +static irqreturn_t pic32_spi_fault_irq(int irq, void *dev_id) +{ + struct pic32_spi *pic32s = dev_id; + irqreturn_t ret = IRQ_HANDLED; + u32 status; + + spin_lock(&pic32s->lock); + + status = readl(&pic32s->regs->status); + + /* Error handling */ + if (status & (STAT_RX_OV | STAT_TX_UR)) { + writel(STAT_RX_OV, &pic32s->regs->status_clr); + writel(STAT_TX_UR, &pic32s->regs->status_clr); + pic32_err_stop(pic32s, "err_irq: fifo ov/ur-run\n"); + goto exit_irq; + } + + if (status & STAT_FRM_ERR) { + pic32_err_stop(pic32s, "err_irq: frame error"); + goto exit_irq; + } + + if (!pic32s->mesg) { + pic32_err_stop(pic32s, "err_irq: mesg error"); + ret = IRQ_NONE; + goto exit_irq; + } + +exit_irq: + spin_unlock(&pic32s->lock); + + return ret; +} + +static irqreturn_t pic32_spi_rx_irq(int irq, void *dev_id) +{ + struct pic32_spi *pic32s = dev_id; + + spin_lock(&pic32s->lock); + + pic32s->rx_fifo(pic32s); + + /* rx complete? */ + if (pic32s->rx_end == pic32s->rx) { + /* mask & disable all interrupts */ + disable_irq_nosync(pic32s->fault_irq); + disable_irq_nosync(pic32s->rx_irq); + + /* complete current xfer */ + complete(&pic32s->xfer_done); + } + + spin_unlock(&pic32s->lock); + + return IRQ_HANDLED; +} + +static irqreturn_t pic32_spi_tx_irq(int irq, void *dev_id) +{ + struct pic32_spi *pic32s = dev_id; + + spin_lock(&pic32s->lock); + + pic32s->tx_fifo(pic32s); + + /* tx complete? mask and disable tx interrupt */ + if (pic32s->tx_end == pic32s->tx) + disable_irq_nosync(pic32s->tx_irq); + + spin_unlock(&pic32s->lock); + + return IRQ_HANDLED; +} + +static void pic32_spi_dma_rx_notify(void *data) +{ + struct pic32_spi *pic32s = data; + + complete(&pic32s->xfer_done); +} + +static int pic32_spi_poll_transfer(struct pic32_spi *pic32s, + unsigned long timeout) +{ + unsigned long deadline; + + deadline = timeout + jiffies; + for (;;) { + pic32s->tx_fifo(pic32s); + cpu_relax(); + + pic32s->rx_fifo(pic32s); + cpu_relax(); + + /* received sufficient data */ + if (pic32s->rx >= pic32s->rx_end) + break; + + /* timedout ? */ + if (time_after_eq(jiffies, deadline)) + return -ETIMEDOUT; + } + + return 0; +} + +static int pic32_spi_dma_transfer(struct pic32_spi *pic32s, + struct spi_transfer *xfer) +{ + struct spi_master *master = pic32s->master; + struct dma_async_tx_descriptor *desc_rx; + struct dma_async_tx_descriptor *desc_tx; + dma_cookie_t cookie; + int ret; + + if (!master->dma_rx || !master->dma_tx) + return -ENODEV; + + desc_rx = dmaengine_prep_slave_sg(master->dma_rx, + xfer->rx_sg.sgl, + xfer->rx_sg.nents, + DMA_FROM_DEVICE, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!desc_rx) { + ret = -EINVAL; + goto err_dma; + } + + desc_tx = dmaengine_prep_slave_sg(master->dma_tx, + xfer->tx_sg.sgl, + xfer->tx_sg.nents, + DMA_TO_DEVICE, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!desc_tx) { + ret = -EINVAL; + goto err_dma; + } + + dev_vdbg(&master->dev, "dma_xfer %p: len %u, tx %p(%p), rx %p(%p)\n", + xfer, xfer->len, + xfer->tx_buf, (void *)xfer->tx_dma, + xfer->rx_buf, (void *)xfer->rx_dma); + + /* Put callback on the RX transfer, that should finish last */ + desc_rx->callback = pic32_spi_dma_rx_notify; + desc_rx->callback_param = pic32s; + + cookie = dmaengine_submit(desc_rx); + ret = dma_submit_error(cookie); + if (ret) + goto err_dma; + + cookie = dmaengine_submit(desc_tx); + ret = dma_submit_error(cookie); + if (ret) + goto err_dma_tx; + + dma_async_issue_pending(master->dma_rx); + dma_async_issue_pending(master->dma_tx); + set_bit(PIC32F_DMA_READY, &pic32s->flags); + + return 0; + +err_dma_tx: + dmaengine_terminate_all(master->dma_rx); +err_dma: + return ret; +} + +static int pic32_spi_dma_config(struct pic32_spi *pic32s, u32 dma_width) +{ + int buf_offset = offsetof(struct pic32_spi_regs, buf); + struct spi_master *master = pic32s->master; + struct dma_slave_config cfg; + int err; + + cfg.device_fc = true; + cfg.dst_addr = pic32s->dma_base + buf_offset; + cfg.dst_addr_width = dma_width; + cfg.dst_maxburst = pic32s->fifo_n_elm / 2; /* drain one-half */ + cfg.src_addr = pic32s->dma_base + buf_offset; + cfg.src_addr_width = dma_width; + cfg.src_maxburst = pic32s->fifo_n_elm / 2; /* fill one-half */ + cfg.slave_id = pic32s->tx_irq; + cfg.direction = DMA_MEM_TO_DEV; + err = dmaengine_slave_config(master->dma_tx, &cfg); + if (err) { + dev_err(&master->dev, "configure tx dma channel failed\n"); + return err; + } + + cfg.slave_id = pic32s->rx_irq; + cfg.direction = DMA_DEV_TO_MEM; + err = dmaengine_slave_config(master->dma_rx, &cfg); + if (err) + dev_err(&master->dev, "configure rx dma channel failed\n"); + + return err; +} + +static void pic32_spi_set_word_size(struct pic32_spi *pic32s, u8 bits_per_word) +{ + enum dma_slave_buswidth dma_buswidth; + u32 spi_buswidth, v; + + switch (bits_per_word) { + default: + case 8: + pic32s->rx_fifo = pic32_spi_rx_byte; + pic32s->tx_fifo = pic32_spi_tx_byte; + spi_buswidth = PIC32_BPW_8; + dma_buswidth = DMA_SLAVE_BUSWIDTH_1_BYTE; + break; + case 16: + pic32s->rx_fifo = pic32_spi_rx_word; + pic32s->tx_fifo = pic32_spi_tx_word; + spi_buswidth = PIC32_BPW_16; + dma_buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES; + break; + case 32: + pic32s->rx_fifo = pic32_spi_rx_dword; + pic32s->tx_fifo = pic32_spi_tx_dword; + spi_buswidth = PIC32_BPW_32; + dma_buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES; + break; + } + + /* calculate maximum number of words fifos can hold */ + pic32s->fifo_n_elm = DIV_ROUND_UP(pic32s->fifo_n_byte, + bits_per_word >> 3); + /* set word size */ + v = readl(&pic32s->regs->ctrl); + v &= ~(CTRL_BPW_MASK << CTRL_BPW_SHIFT); + v |= spi_buswidth << CTRL_BPW_SHIFT; + writel(v, &pic32s->regs->ctrl); + + /* re-configure dma width, if required */ + if (test_bit(PIC32F_DMA_PREP, &pic32s->flags) && + (dma_buswidth != pic32s->dma_width)) { + pic32_spi_dma_config(pic32s, dma_buswidth); + pic32s->dma_width = dma_buswidth; + } +} + +static int pic32_spi_prepare_message(struct spi_master *master, + struct spi_message *msg) +{ + struct spi_device *spi = msg->spi; + struct spi_transfer *xfer; + struct pic32_spi *pic32s; + + pic32s = spi_master_get_devdata(master); + + pic32s->mesg = msg; + pic32_spi_disable_chip(pic32s); + + if (!pic32_spi_debug) + return 0; + + list_for_each_entry(xfer, &msg->transfers, transfer_list) { + dev_dbg(&spi->dev, " xfer %p: len %u tx %p rx %p\n", + xfer, xfer->len, xfer->tx_buf, xfer->rx_buf); + print_hex_dump(KERN_DEBUG, "tx_buf ", + DUMP_PREFIX_ADDRESS, + 16, 1, xfer->tx_buf, + min_t(u32, xfer->len, 16), 1); + } + + return 0; +} + +static bool pic32_spi_can_dma(struct spi_master *master, + struct spi_device *spi, + struct spi_transfer *xfer) +{ + struct pic32_spi *pic32s = spi_master_get_devdata(master); + + return (xfer->len >= PIC32_DMA_LEN_MIN) && + test_bit(PIC32F_DMA_PREP, &pic32s->flags); +} + +static int pic32_spi_one_transfer(struct spi_master *master, + struct spi_device *spi, + struct spi_transfer *transfer) +{ + struct pic32_spi *pic32s; + unsigned long flags; + int ret; + + pic32s = spi_master_get_devdata(master); + + /* set current transfer information */ + pic32s->tx = (const void *)transfer->tx_buf; + pic32s->rx = (const void *)transfer->rx_buf; + pic32s->tx_end = pic32s->tx + transfer->len; + pic32s->rx_end = pic32s->rx + transfer->len; + pic32s->len = transfer->len; + + if (transfer->speed_hz && (transfer->speed_hz != pic32s->speed_hz)) { + pic32_spi_set_clk_rate(pic32s, transfer->speed_hz); + pic32s->speed_hz = transfer->speed_hz; + } + + if (transfer->bits_per_word) + pic32_spi_set_word_size(pic32s, transfer->bits_per_word); + + pic32_spi_enable_chip(pic32s); + reinit_completion(&pic32s->xfer_done); + + /* DMA transfer mode ? */ + if (transfer->rx_sg.nents && transfer->tx_sg.nents) { + ret = pic32_spi_dma_transfer(pic32s, transfer); + if (ret) { + dev_err(&spi->dev, "dma submit error\n"); + /* fallback: PIO transfer */ + } else { + goto out_wait_for_xfer; + } + } + + /* polling transfer mode */ + if (test_bit(PIC32F_XFER_PIO, &pic32s->flags)) + return pic32_spi_poll_transfer(pic32s, 2 * HZ); + + /* interrupt driven PIO transfer */ + spin_lock_irqsave(&pic32s->lock, flags); + enable_irq(pic32s->fault_irq); + enable_irq(pic32s->rx_irq); + enable_irq(pic32s->tx_irq); + spin_unlock_irqrestore(&pic32s->lock, flags); + +out_wait_for_xfer: + /* wait for completion */ + ret = wait_for_completion_timeout(&pic32s->xfer_done, 2 * HZ); + if (ret <= 0) { + dev_err(&spi->dev, "wait timedout\n"); + if (test_bit(PIC32F_DMA_READY, &pic32s->flags)) { + dmaengine_terminate_all(master->dma_rx); + dmaengine_terminate_all(master->dma_rx); + } + ret = -ETIMEDOUT; + } else { + ret = 0; + } + + clear_bit(PIC32F_DMA_READY, &pic32s->flags); + return ret; +} + +static int pic32_spi_unprepare_message(struct spi_master *master, + struct spi_message *msg) +{ + struct spi_transfer *xfer; + struct pic32_spi *pic32s; + + pic32s = spi_master_get_devdata(master); + + pic32_spi_disable_chip(pic32s); + + if (!pic32_spi_debug) + return 0; + + list_for_each_entry(xfer, &msg->transfers, transfer_list) + print_hex_dump(KERN_DEBUG, "rx_buf ", + DUMP_PREFIX_ADDRESS, + 16, 1, xfer->rx_buf, + min_t(u32, xfer->len, 32), 1); + + return 0; +} + +static void pic32_spi_cleanup(struct spi_device *spi) +{ + struct pic32_spi *pic32s; + int cs_high; + + pic32s = spi_master_get_devdata(spi->master); + + /* disable chip */ + pic32_spi_disable_chip(pic32s); + + /* release cs-gpio */ + cs_high = pic32s->mode & SPI_CS_HIGH; + gpio_direction_output(spi->cs_gpio, !cs_high); + gpio_free(spi->cs_gpio); + + /* reset reference */ + pic32s->spi_dev = NULL; + pic32s->speed_hz = 0; +} + +/* This may be called multiple times by same spi dev */ +static int pic32_spi_setup(struct spi_device *spi) +{ + struct pic32_spi *pic32s; + int cs_high, ret; + u32 v; + + pic32s = spi_master_get_devdata(spi->master); + + /* SPI master supports only one spi-device at a time. + * So multiple spi_setup() to different devices is not allowed. + */ + if (unlikely(pic32s->spi_dev && (pic32s->spi_dev != spi))) { + dev_err(&spi->dev, "spi-master already associated with %s\n", + dev_name(&pic32s->spi_dev->dev)); + return -EBUSY; + } + + /* stop current transaction & release resource */ + if (pic32s->spi_dev == spi) + pic32_spi_cleanup(spi); + + pic32s->spi_dev = spi; + + if (!spi->bits_per_word) { + dev_err(&spi->dev, "No bits_per_word defined\n"); + return -EINVAL; + } + + if (!spi->max_speed_hz) { + dev_err(&spi->dev, "No max speed HZ parameter\n"); + return -EINVAL; + } + + pic32_spi_disable_chip(pic32s); + + pic32_spi_set_word_size(pic32s, spi->bits_per_word); + + pic32s->speed_hz = spi->max_speed_hz; + pic32_spi_set_clk_rate(pic32s, spi->max_speed_hz); + + /* set spi mode */ + v = readl(&pic32s->regs->ctrl); + + /* active low */ + if (spi->mode & SPI_CPOL) + v |= CTRL_CKP; + else + v &= ~CTRL_CKP; + + /* tx on rising edge */ + if (spi->mode & SPI_CPHA) + v &= ~CTRL_CKE; + else + v |= CTRL_CKE; + + /* rx at end of tx */ + v |= CTRL_SMP; + writel(v, &pic32s->regs->ctrl); + pic32s->mode = spi->mode; + + /* PIC32 spi ctrller can drive /SS during transfer depending + * on tx fifo fill-level. SS will stay asserted as long as TX + * fifo is non-empty, else will be deasserted confirming + * completion of the ongoing transfer. + * So we will force manual /SS handling by GPIO toggling. + */ + if (!gpio_is_valid(spi->cs_gpio)) + return -EINVAL; + + cs_high = pic32s->mode & SPI_CS_HIGH; + ret = gpio_request(spi->cs_gpio, dev_name(&spi->dev)); + if (!ret) { + gpio_direction_output(spi->cs_gpio, !cs_high); + dev_vdbg(&spi->dev, + "gpio-%d configured for spics (%s)\n", + spi->cs_gpio, cs_high ? "cs_high" : "cs_low"); + } + writel(CTRL_MSSEN, &pic32s->regs->ctrl_clr); + + dev_vdbg(&spi->master->dev, + "successfully registered spi-device %s\n", + dev_name(&spi->dev)); + return 0; +} + +static int pic32_spi_dma_prep(struct pic32_spi *pic32s, struct device *dev) +{ + struct spi_master *master = pic32s->master; + dma_cap_mask_t mask; + int err; + + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + + master->dma_rx = dma_request_slave_channel_compat(mask, NULL, NULL, + dev, "spi-rx"); + if (!master->dma_rx) { + dev_err(dev, "RX channel not found, SPI unable to use DMA\n"); + err = -EBUSY; + goto out_err; + } + + master->dma_tx = dma_request_slave_channel_compat(mask, NULL, NULL, + dev, "spi-tx"); + if (!master->dma_tx) { + dev_err(dev, "TX channel not found, SPI unable to use DMA\n"); + err = -EBUSY; + goto out_err; + } + + pic32s->dma_width = DMA_SLAVE_BUSWIDTH_1_BYTE; + err = pic32_spi_dma_config(pic32s, pic32s->dma_width); + if (err) + goto out_err; + + /* DMA chnls allocated and prepared */ + set_bit(PIC32F_DMA_PREP, &pic32s->flags); + + dev_dbg(dev, "Using %s (tx) and %s (rx) for DMA transfers\n", + dma_chan_name(master->dma_tx), + dma_chan_name(master->dma_rx)); + return 0; + +out_err: + if (master->dma_rx) + dma_release_channel(master->dma_rx); + + if (master->dma_tx) + dma_release_channel(master->dma_tx); + + return err; +} + +static void pic32_spi_dma_unprep(struct pic32_spi *pic32s) +{ + if (!test_bit(PIC32F_DMA_PREP, &pic32s->flags)) + return; + + clear_bit(PIC32F_DMA_PREP, &pic32s->flags); + if (pic32s->master->dma_rx) + dma_release_channel(pic32s->master->dma_rx); + + if (pic32s->master->dma_tx) + dma_release_channel(pic32s->master->dma_tx); +} + +static void pic32_spi_hw_init(struct pic32_spi *pic32s) +{ + u32 v; + + /* disable module */ + pic32_spi_disable_chip(pic32s); + + v = readl(&pic32s->regs->ctrl); + /* enable enhanced fifo of 128bit deep */ + v |= CTRL_ENHBUF; + pic32s->fifo_n_byte = 16; + + /* disable framing mode */ + v &= ~CTRL_FRMEN; + + /* enable master mode while disabled */ + v |= CTRL_MSTEN; + + /* set tx fifo threshold interrupt */ + v &= ~(0x3 << CTRL_TX_INT_SHIFT); + v |= (TX_FIFO_HALF_EMPTY << CTRL_TX_INT_SHIFT); + + /* set rx fifo threshold interrupt */ + v &= ~(0x3 << CTRL_RX_INT_SHIFT); + v |= (RX_FIFO_NOT_EMPTY << CTRL_RX_INT_SHIFT); + + /* select clk source */ + v &= ~CTRL_MCLKSEL; + + writel(v, &pic32s->regs->ctrl); + + /* enable error reporting */ + v = CTRL2_TX_UR_EN | CTRL2_RX_OV_EN | CTRL2_FRM_ERR_EN; + writel(v, &pic32s->regs->ctrl2_set); +} + +static int pic32_spi_hw_probe(struct platform_device *pdev, + struct pic32_spi *pic32s) +{ + struct resource *mem; + int ret; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + pic32s->regs = devm_ioremap_resource(&pdev->dev, mem); + if (!pic32s->regs) { + dev_err(&pdev->dev, "mem map failed\n"); + return -ENOMEM; + } + pic32s->dma_base = mem->start; + + /* get irq resources: err-irq, rx-irq, tx-irq */ + pic32s->fault_irq = platform_get_irq_byname(pdev, "fault"); + if (pic32s->fault_irq < 0) + dev_warn(&pdev->dev, "no fault-irq found\n"); + + pic32s->rx_irq = platform_get_irq_byname(pdev, "rx"); + if (pic32s->rx_irq < 0) + dev_warn(&pdev->dev, "no rx-irq found\n"); + + pic32s->tx_irq = platform_get_irq_byname(pdev, "tx"); + if (pic32s->tx_irq < 0) + dev_warn(&pdev->dev, "no tx-irq found\n"); + + /* get clock */ + pic32s->clk = devm_clk_get(&pdev->dev, "mck0"); + if (IS_ERR(pic32s->clk)) { + dev_err(&pdev->dev, "err, no clk found\n"); + ret = PTR_ERR(pic32s->clk); + goto err_unmap_mem; + } + + ret = clk_prepare_enable(pic32s->clk); + if (ret) + goto err_unmap_mem; + + /* initialize module */ + pic32_spi_hw_init(pic32s); + + return 0; + +err_unmap_mem: + dev_err(&pdev->dev, "hw_probe failed, ret %d\n", ret); + return ret; +} + +static int pic32_spi_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct spi_master *master; + struct pic32_spi *pic32s; + int ret; + + master = spi_alloc_master(dev, sizeof(*pic32s)); + if (!master) + return -ENOMEM; + + pic32s = spi_master_get_devdata(master); + pic32s->master = master; + + ret = pic32_spi_hw_probe(pdev, pic32s); + if (ret) { + dev_err(&pdev->dev, "hw probe failed.\n"); + goto err_free_master; + } + + master->dev.of_node = of_node_get(pdev->dev.of_node); + master->mode_bits = SPI_MODE_3 | SPI_MODE_0 | SPI_CS_HIGH; + master->num_chipselect = 1; /* single chip-select */ + master->max_speed_hz = clk_get_rate(pic32s->clk); + master->setup = pic32_spi_setup; + master->cleanup = pic32_spi_cleanup; + master->flags = SPI_MASTER_MUST_TX | SPI_MASTER_MUST_RX; + master->bits_per_word_mask = SPI_BPW_RANGE_MASK(8, 32); + master->prepare_message = pic32_spi_prepare_message; + master->transfer_one = pic32_spi_one_transfer; + master->unprepare_message = pic32_spi_unprepare_message; + + /* DMA support */ + ret = pic32_spi_dma_prep(pic32s, dev); + if (test_bit(PIC32F_DMA_PREP, &pic32s->flags)) + master->can_dma = pic32_spi_can_dma; + + init_completion(&pic32s->xfer_done); + spin_lock_init(&pic32s->lock); + + /* install irq handlers (with irq-disabled) */ + irq_set_status_flags(pic32s->fault_irq, IRQ_NOAUTOEN); + ret = devm_request_irq(dev, pic32s->fault_irq, + pic32_spi_fault_irq, IRQF_NO_THREAD, + dev_name(dev), pic32s); + if (ret < 0) { + dev_warn(dev, "request fault-irq %d\n", pic32s->rx_irq); + set_bit(PIC32F_XFER_PIO, &pic32s->flags); + goto irq_request_done; + } + + /* receive interrupt handler */ + irq_set_status_flags(pic32s->rx_irq, IRQ_NOAUTOEN); + ret = devm_request_irq(dev, pic32s->rx_irq, + pic32_spi_rx_irq, IRQF_NO_THREAD, dev_name(dev), + pic32s); + if (ret < 0) { + dev_warn(dev, "request rx-irq %d\n", pic32s->rx_irq); + set_bit(PIC32F_XFER_PIO, &pic32s->flags); + goto irq_request_done; + } + + /* transmit interrupt handler */ + irq_set_status_flags(pic32s->tx_irq, IRQ_NOAUTOEN); + ret = devm_request_irq(dev, pic32s->tx_irq, + pic32_spi_tx_irq, IRQF_NO_THREAD, dev_name(dev), + pic32s); + if (ret < 0) { + dev_warn(dev, "request tx-irq %d\n", pic32s->tx_irq); + set_bit(PIC32F_XFER_PIO, &pic32s->flags); + goto irq_request_done; + } + +irq_request_done: + /* register master */ + ret = devm_spi_register_master(dev, master); + if (ret) { + dev_err(&master->dev, "failed registering spi master\n"); + goto err_hw_remove; + } + + platform_set_drvdata(pdev, pic32s); + + return 0; + +err_hw_remove: + /* disable hw */ + pic32_spi_disable_chip(pic32s); + + /* disable clk */ + if (!IS_ERR_OR_NULL(pic32s->clk)) + clk_disable_unprepare(pic32s->clk); +err_free_master: + spi_master_put(master); + return ret; +} + +static int pic32_spi_remove(struct platform_device *pdev) +{ + struct pic32_spi *pic32s; + + pic32s = platform_get_drvdata(pdev); + pic32_spi_disable_chip(pic32s); + clk_disable_unprepare(pic32s->clk); + pic32_spi_dma_unprep(pic32s); + + return 0; +} + +static const struct of_device_id pic32_spi_of_match[] = { + {.compatible = "microchip,pic32mzda-spi",}, + {}, +}; +MODULE_DEVICE_TABLE(of, pic32_spi_of_match); + +static struct platform_driver pic32_spi_driver = { + .driver = { + .name = "spi-pic32", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(pic32_spi_of_match), + }, + .probe = pic32_spi_probe, + .remove = pic32_spi_remove, +}; + +module_platform_driver(pic32_spi_driver); + +MODULE_AUTHOR("Purna Chandra Mandal <purna.mandal@xxxxxxxxxxxxx>"); +MODULE_DESCRIPTION("Microchip SPI driver for PIC32 SPI controller."); +MODULE_LICENSE("GPL v2"); -- 1.8.3.1 -- To unsubscribe from this list: send the line "unsubscribe linux-spi" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html