| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * 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> |
| */ |
| |
| #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; |
| } |
| |
| |