These changes are the raw update to linux-4.4.6-rt14. Kernel sources
[kvmfornfv.git] / kernel / drivers / media / pci / netup_unidvb / netup_unidvb_spi.c
diff --git a/kernel/drivers/media/pci/netup_unidvb/netup_unidvb_spi.c b/kernel/drivers/media/pci/netup_unidvb/netup_unidvb_spi.c
new file mode 100644 (file)
index 0000000..f33c0de
--- /dev/null
@@ -0,0 +1,248 @@
+/*
+ * netup_unidvb_spi.c
+ *
+ * Internal SPI driver for NetUP Universal Dual DVB-CI
+ *
+ * Copyright (C) 2014 NetUP Inc.
+ * Copyright (C) 2014 Sergey Kozlov <serjk@netup.ru>
+ * Copyright (C) 2014 Abylay Ospan <aospan@netup.ru>
+ *
+ * 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.
+ */
+
+#include "netup_unidvb.h"
+#include <linux/spi/spi.h>
+#include <linux/spi/flash.h>
+#include <linux/mtd/partitions.h>
+#include <mtd/mtd-abi.h>
+
+#define NETUP_SPI_CTRL_IRQ     0x1000
+#define NETUP_SPI_CTRL_IMASK   0x2000
+#define NETUP_SPI_CTRL_START   0x8000
+#define NETUP_SPI_CTRL_LAST_CS 0x4000
+
+#define NETUP_SPI_TIMEOUT      6000
+
+enum netup_spi_state {
+       SPI_STATE_START,
+       SPI_STATE_DONE,
+};
+
+struct netup_spi_regs {
+       __u8    data[1024];
+       __le16  control_stat;
+       __le16  clock_divider;
+} __packed __aligned(1);
+
+struct netup_spi {
+       struct device                   *dev;
+       struct spi_master               *master;
+       struct netup_spi_regs __iomem   *regs;
+       u8 __iomem                      *mmio;
+       spinlock_t                      lock;
+       wait_queue_head_t               waitq;
+       enum netup_spi_state            state;
+};
+
+static char netup_spi_name[64] = "fpga";
+
+static struct mtd_partition netup_spi_flash_partitions = {
+       .name = netup_spi_name,
+       .size = 0x1000000, /* 16MB */
+       .offset = 0,
+       .mask_flags = MTD_CAP_ROM
+};
+
+static struct flash_platform_data spi_flash_data = {
+       .name = "netup0_m25p128",
+       .parts = &netup_spi_flash_partitions,
+       .nr_parts = 1,
+};
+
+static struct spi_board_info netup_spi_board = {
+       .modalias = "m25p128",
+       .max_speed_hz = 11000000,
+       .chip_select = 0,
+       .mode = SPI_MODE_0,
+       .platform_data = &spi_flash_data,
+};
+
+irqreturn_t netup_spi_interrupt(struct netup_spi *spi)
+{
+       u16 reg;
+       unsigned long flags;
+
+       if (!spi)
+               return IRQ_NONE;
+
+       spin_lock_irqsave(&spi->lock, flags);
+       reg = readw(&spi->regs->control_stat);
+       if (!(reg & NETUP_SPI_CTRL_IRQ)) {
+               spin_unlock_irqrestore(&spi->lock, flags);
+               dev_dbg(&spi->master->dev,
+                       "%s(): not mine interrupt\n", __func__);
+               return IRQ_NONE;
+       }
+       writew(reg | NETUP_SPI_CTRL_IRQ, &spi->regs->control_stat);
+       reg = readw(&spi->regs->control_stat);
+       writew(reg & ~NETUP_SPI_CTRL_IMASK, &spi->regs->control_stat);
+       spi->state = SPI_STATE_DONE;
+       wake_up(&spi->waitq);
+       spin_unlock_irqrestore(&spi->lock, flags);
+       dev_dbg(&spi->master->dev,
+               "%s(): SPI interrupt handled\n", __func__);
+       return IRQ_HANDLED;
+}
+
+static int netup_spi_transfer(struct spi_master *master,
+                             struct spi_message *msg)
+{
+       struct netup_spi *spi = spi_master_get_devdata(master);
+       struct spi_transfer *t;
+       int result = 0;
+       u32 tr_size;
+
+       /* reset CS */
+       writew(NETUP_SPI_CTRL_LAST_CS, &spi->regs->control_stat);
+       writew(0, &spi->regs->control_stat);
+       list_for_each_entry(t, &msg->transfers, transfer_list) {
+               tr_size = t->len;
+               while (tr_size) {
+                       u32 frag_offset = t->len - tr_size;
+                       u32 frag_size = (tr_size > sizeof(spi->regs->data)) ?
+                                       sizeof(spi->regs->data) : tr_size;
+                       int frag_last = 0;
+
+                       if (list_is_last(&t->transfer_list,
+                                       &msg->transfers) &&
+                                       frag_offset + frag_size == t->len) {
+                               frag_last = 1;
+                       }
+                       if (t->tx_buf) {
+                               memcpy_toio(spi->regs->data,
+                                       t->tx_buf + frag_offset,
+                                       frag_size);
+                       } else {
+                               memset_io(spi->regs->data,
+                                       0, frag_size);
+                       }
+                       spi->state = SPI_STATE_START;
+                       writew((frag_size & 0x3ff) |
+                               NETUP_SPI_CTRL_IMASK |
+                               NETUP_SPI_CTRL_START |
+                               (frag_last ? NETUP_SPI_CTRL_LAST_CS : 0),
+                               &spi->regs->control_stat);
+                       dev_dbg(&spi->master->dev,
+                               "%s(): control_stat 0x%04x\n",
+                               __func__, readw(&spi->regs->control_stat));
+                       wait_event_timeout(spi->waitq,
+                               spi->state != SPI_STATE_START,
+                               msecs_to_jiffies(NETUP_SPI_TIMEOUT));
+                       if (spi->state == SPI_STATE_DONE) {
+                               if (t->rx_buf) {
+                                       memcpy_fromio(t->rx_buf + frag_offset,
+                                               spi->regs->data, frag_size);
+                               }
+                       } else {
+                               if (spi->state == SPI_STATE_START) {
+                                       dev_dbg(&spi->master->dev,
+                                               "%s(): transfer timeout\n",
+                                               __func__);
+                               } else {
+                                       dev_dbg(&spi->master->dev,
+                                               "%s(): invalid state %d\n",
+                                               __func__, spi->state);
+                               }
+                               result = -EIO;
+                               goto done;
+                       }
+                       tr_size -= frag_size;
+                       msg->actual_length += frag_size;
+               }
+       }
+done:
+       msg->status = result;
+       spi_finalize_current_message(master);
+       return result;
+}
+
+static int netup_spi_setup(struct spi_device *spi)
+{
+       return 0;
+}
+
+int netup_spi_init(struct netup_unidvb_dev *ndev)
+{
+       struct spi_master *master;
+       struct netup_spi *nspi;
+
+       master = spi_alloc_master(&ndev->pci_dev->dev,
+               sizeof(struct netup_spi));
+       if (!master) {
+               dev_err(&ndev->pci_dev->dev,
+                       "%s(): unable to alloc SPI master\n", __func__);
+               return -EINVAL;
+       }
+       nspi = spi_master_get_devdata(master);
+       master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_LSB_FIRST;
+       master->bus_num = -1;
+       master->num_chipselect = 1;
+       master->transfer_one_message = netup_spi_transfer;
+       master->setup = netup_spi_setup;
+       spin_lock_init(&nspi->lock);
+       init_waitqueue_head(&nspi->waitq);
+       nspi->master = master;
+       nspi->regs = (struct netup_spi_regs __iomem *)(ndev->bmmio0 + 0x4000);
+       writew(2, &nspi->regs->clock_divider);
+       writew(NETUP_UNIDVB_IRQ_SPI, ndev->bmmio0 + REG_IMASK_SET);
+       ndev->spi = nspi;
+       if (spi_register_master(master)) {
+               ndev->spi = NULL;
+               dev_err(&ndev->pci_dev->dev,
+                       "%s(): unable to register SPI bus\n", __func__);
+               return -EINVAL;
+       }
+       snprintf(netup_spi_name,
+               sizeof(netup_spi_name),
+               "fpga_%02x:%02x.%01x",
+               ndev->pci_bus,
+               ndev->pci_slot,
+               ndev->pci_func);
+       if (!spi_new_device(master, &netup_spi_board)) {
+               ndev->spi = NULL;
+               dev_err(&ndev->pci_dev->dev,
+                       "%s(): unable to create SPI device\n", __func__);
+               return -EINVAL;
+       }
+       dev_dbg(&ndev->pci_dev->dev, "%s(): SPI init OK\n", __func__);
+       return 0;
+}
+
+void netup_spi_release(struct netup_unidvb_dev *ndev)
+{
+       u16 reg;
+       unsigned long flags;
+       struct netup_spi *spi = ndev->spi;
+
+       if (!spi)
+               return;
+
+       spin_lock_irqsave(&spi->lock, flags);
+       reg = readw(&spi->regs->control_stat);
+       writew(reg | NETUP_SPI_CTRL_IRQ, &spi->regs->control_stat);
+       reg = readw(&spi->regs->control_stat);
+       writew(reg & ~NETUP_SPI_CTRL_IMASK, &spi->regs->control_stat);
+       spin_unlock_irqrestore(&spi->lock, flags);
+       spi_unregister_master(spi->master);
+       ndev->spi = NULL;
+}
+
+