241 lines
5.9 KiB
C
241 lines
5.9 KiB
C
// 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 = devm_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)) {
|
|
spi_unregister_master(master);
|
|
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;
|
|
|
|
spi_unregister_master(spi->master);
|
|
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);
|
|
ndev->spi = NULL;
|
|
}
|
|
|
|
|