diff options
author | Kozlov Sergey <serjk@netup.ru> | 2015-07-28 11:33:04 -0300 |
---|---|---|
committer | Mauro Carvalho Chehab <mchehab@osg.samsung.com> | 2015-08-11 15:16:40 -0300 |
commit | 52b1eaf4c59a3bbd07afbb4ab4f43418a807d02e (patch) | |
tree | 042e8ebad3e20290e88ad6c7bb713e4aa8237aae /drivers/media/pci/netup_unidvb/netup_unidvb_spi.c | |
parent | c8946c8d5ab8725bd763fc98c0ec6e1e94e6f6a0 (diff) |
[media] netup_unidvb: NetUP Universal DVB-S/S2/T/T2/C PCI-E card driver
Add NetUP Dual Universal CI PCIe board driver.
The board has
- two CI slots
- two I2C adapters
- SPI master bus for accessing flash memory containing
FPGA firmware
No changes required.
Signed-off-by: Kozlov Sergey <serjk@netup.ru>
Signed-off-by: Mauro Carvalho Chehab <mchehab@osg.samsung.com>
Diffstat (limited to 'drivers/media/pci/netup_unidvb/netup_unidvb_spi.c')
-rw-r--r-- | drivers/media/pci/netup_unidvb/netup_unidvb_spi.c | 252 |
1 files changed, 252 insertions, 0 deletions
diff --git a/drivers/media/pci/netup_unidvb/netup_unidvb_spi.c b/drivers/media/pci/netup_unidvb/netup_unidvb_spi.c new file mode 100644 index 000000000000..f55b3276f28d --- /dev/null +++ b/drivers/media/pci/netup_unidvb/netup_unidvb_spi.c @@ -0,0 +1,252 @@ +/* + * 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 *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) { + dev_dbg(&spi->master->dev, + "%s(): SPI not initialized\n", __func__); + 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 *)(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) { + dev_dbg(&spi->master->dev, + "%s(): SPI not initialized\n", __func__); + 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; +} + + |