[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>
This commit is contained in:
parent
c8946c8d5a
commit
52b1eaf4c5
|
@ -6637,6 +6637,15 @@ T: git git://linuxtv.org/media_tree.git
|
|||
S: Supported
|
||||
F: drivers/media/dvb-frontends/lnbh25*
|
||||
|
||||
MEDIA DRIVERS FOR NETUP PCI UNIVERSAL DVB devices
|
||||
M: Sergey Kozlov <serjk@netup.ru>
|
||||
L: linux-media@vger.kernel.org
|
||||
W: http://linuxtv.org/
|
||||
W: http://netup.tv/
|
||||
T: git git://linuxtv.org/media_tree.git
|
||||
S: Supported
|
||||
F: drivers/media/pci/netup_unidvb/*
|
||||
|
||||
MEDIA INPUT INFRASTRUCTURE (V4L/DVB)
|
||||
M: Mauro Carvalho Chehab <mchehab@osg.samsung.com>
|
||||
P: LinuxTV.org Project
|
||||
|
|
|
@ -49,6 +49,7 @@ source "drivers/media/pci/mantis/Kconfig"
|
|||
source "drivers/media/pci/ngene/Kconfig"
|
||||
source "drivers/media/pci/ddbridge/Kconfig"
|
||||
source "drivers/media/pci/smipcie/Kconfig"
|
||||
source "drivers/media/pci/netup_unidvb/Kconfig"
|
||||
endif
|
||||
|
||||
endif #MEDIA_PCI_SUPPORT
|
||||
|
|
|
@ -12,7 +12,8 @@ obj-y += ttpci/ \
|
|||
ngene/ \
|
||||
ddbridge/ \
|
||||
saa7146/ \
|
||||
smipcie/
|
||||
smipcie/ \
|
||||
netup_unidvb/
|
||||
|
||||
obj-$(CONFIG_VIDEO_IVTV) += ivtv/
|
||||
obj-$(CONFIG_VIDEO_ZORAN) += zoran/
|
||||
|
|
12
drivers/media/pci/netup_unidvb/Kconfig
Normal file
12
drivers/media/pci/netup_unidvb/Kconfig
Normal file
|
@ -0,0 +1,12 @@
|
|||
config DVB_NETUP_UNIDVB
|
||||
tristate "NetUP Universal DVB card support"
|
||||
depends on DVB_CORE && VIDEO_DEV && PCI && I2C && SPI_MASTER
|
||||
select VIDEOBUF2_DVB
|
||||
select VIDEOBUF2_VMALLOC
|
||||
select DVB_HORUS3A if MEDIA_SUBDRV_AUTOSELECT
|
||||
select DVB_ASCOT2E if MEDIA_SUBDRV_AUTOSELECT
|
||||
select DVB_LNBH25 if MEDIA_SUBDRV_AUTOSELECT
|
||||
select DVB_CXD2841ER if MEDIA_SUBDRV_AUTOSELECT
|
||||
---help---
|
||||
Support for NetUP PCI express Universal DVB card.
|
||||
|
9
drivers/media/pci/netup_unidvb/Makefile
Normal file
9
drivers/media/pci/netup_unidvb/Makefile
Normal file
|
@ -0,0 +1,9 @@
|
|||
netup-unidvb-objs += netup_unidvb_core.o
|
||||
netup-unidvb-objs += netup_unidvb_i2c.o
|
||||
netup-unidvb-objs += netup_unidvb_ci.o
|
||||
netup-unidvb-objs += netup_unidvb_spi.o
|
||||
|
||||
obj-$(CONFIG_DVB_NETUP_UNIDVB) += netup-unidvb.o
|
||||
|
||||
ccflags-y += -Idrivers/media/dvb-core
|
||||
ccflags-y += -Idrivers/media/dvb-frontends
|
133
drivers/media/pci/netup_unidvb/netup_unidvb.h
Normal file
133
drivers/media/pci/netup_unidvb/netup_unidvb.h
Normal file
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
* netup_unidvb.h
|
||||
*
|
||||
* Data type definitions 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 <linux/pci.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <media/v4l2-common.h>
|
||||
#include <media/v4l2-device.h>
|
||||
#include <media/videobuf2-dvb.h>
|
||||
#include <dvb_ca_en50221.h>
|
||||
|
||||
#define NETUP_UNIDVB_NAME "netup_unidvb"
|
||||
#define NETUP_UNIDVB_VERSION "0.0.1"
|
||||
#define NETUP_VENDOR_ID 0x1b55
|
||||
#define NETUP_PCI_DEV_REVISION 0x2
|
||||
|
||||
/* IRQ-related regisers */
|
||||
#define REG_ISR 0x4890
|
||||
#define REG_ISR_MASKED 0x4892
|
||||
#define REG_IMASK_SET 0x4894
|
||||
#define REG_IMASK_CLEAR 0x4896
|
||||
/* REG_ISR register bits */
|
||||
#define NETUP_UNIDVB_IRQ_SPI (1 << 0)
|
||||
#define NETUP_UNIDVB_IRQ_I2C0 (1 << 1)
|
||||
#define NETUP_UNIDVB_IRQ_I2C1 (1 << 2)
|
||||
#define NETUP_UNIDVB_IRQ_FRA0 (1 << 4)
|
||||
#define NETUP_UNIDVB_IRQ_FRA1 (1 << 5)
|
||||
#define NETUP_UNIDVB_IRQ_FRB0 (1 << 6)
|
||||
#define NETUP_UNIDVB_IRQ_FRB1 (1 << 7)
|
||||
#define NETUP_UNIDVB_IRQ_DMA1 (1 << 8)
|
||||
#define NETUP_UNIDVB_IRQ_DMA2 (1 << 9)
|
||||
#define NETUP_UNIDVB_IRQ_CI (1 << 10)
|
||||
#define NETUP_UNIDVB_IRQ_CAM0 (1 << 11)
|
||||
#define NETUP_UNIDVB_IRQ_CAM1 (1 << 12)
|
||||
|
||||
struct netup_dma {
|
||||
u8 num;
|
||||
spinlock_t lock;
|
||||
struct netup_unidvb_dev *ndev;
|
||||
struct netup_dma_regs *regs;
|
||||
u32 ring_buffer_size;
|
||||
u8 *addr_virt;
|
||||
dma_addr_t addr_phys;
|
||||
u64 addr_last;
|
||||
u32 high_addr;
|
||||
u32 data_offset;
|
||||
u32 data_size;
|
||||
struct list_head free_buffers;
|
||||
struct work_struct work;
|
||||
struct timer_list timeout;
|
||||
};
|
||||
|
||||
enum netup_i2c_state {
|
||||
STATE_DONE,
|
||||
STATE_WAIT,
|
||||
STATE_WANT_READ,
|
||||
STATE_WANT_WRITE,
|
||||
STATE_ERROR
|
||||
};
|
||||
|
||||
struct netup_i2c_regs;
|
||||
|
||||
struct netup_i2c {
|
||||
spinlock_t lock;
|
||||
wait_queue_head_t wq;
|
||||
struct i2c_adapter adap;
|
||||
struct netup_unidvb_dev *dev;
|
||||
struct netup_i2c_regs *regs;
|
||||
struct i2c_msg *msg;
|
||||
enum netup_i2c_state state;
|
||||
u32 xmit_size;
|
||||
};
|
||||
|
||||
struct netup_ci_state {
|
||||
struct dvb_ca_en50221 ca;
|
||||
u8 __iomem *membase8_config;
|
||||
u8 __iomem *membase8_io;
|
||||
struct netup_unidvb_dev *dev;
|
||||
int status;
|
||||
int nr;
|
||||
};
|
||||
|
||||
struct netup_spi;
|
||||
|
||||
struct netup_unidvb_dev {
|
||||
struct pci_dev *pci_dev;
|
||||
int pci_bus;
|
||||
int pci_slot;
|
||||
int pci_func;
|
||||
int board_num;
|
||||
int old_fw;
|
||||
u32 __iomem *lmmio0;
|
||||
u8 __iomem *bmmio0;
|
||||
u32 __iomem *lmmio1;
|
||||
u8 __iomem *bmmio1;
|
||||
u8 *dma_virt;
|
||||
dma_addr_t dma_phys;
|
||||
u32 dma_size;
|
||||
struct vb2_dvb_frontends frontends[2];
|
||||
struct netup_i2c i2c[2];
|
||||
struct workqueue_struct *wq;
|
||||
struct netup_dma dma[2];
|
||||
struct netup_ci_state ci[2];
|
||||
struct netup_spi *spi;
|
||||
};
|
||||
|
||||
int netup_i2c_register(struct netup_unidvb_dev *ndev);
|
||||
void netup_i2c_unregister(struct netup_unidvb_dev *ndev);
|
||||
irqreturn_t netup_ci_interrupt(struct netup_unidvb_dev *ndev);
|
||||
irqreturn_t netup_i2c_interrupt(struct netup_i2c *i2c);
|
||||
irqreturn_t netup_spi_interrupt(struct netup_spi *spi);
|
||||
int netup_unidvb_ci_register(struct netup_unidvb_dev *dev,
|
||||
int num, struct pci_dev *pci_dev);
|
||||
void netup_unidvb_ci_unregister(struct netup_unidvb_dev *dev, int num);
|
||||
int netup_spi_init(struct netup_unidvb_dev *ndev);
|
||||
void netup_spi_release(struct netup_unidvb_dev *ndev);
|
248
drivers/media/pci/netup_unidvb/netup_unidvb_ci.c
Normal file
248
drivers/media/pci/netup_unidvb/netup_unidvb_ci.c
Normal file
|
@ -0,0 +1,248 @@
|
|||
/*
|
||||
* netup_unidvb_ci.c
|
||||
*
|
||||
* DVB CAM support 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 <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/kmod.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/delay.h>
|
||||
#include "netup_unidvb.h"
|
||||
|
||||
/* CI slot 0 base address */
|
||||
#define CAM0_CONFIG 0x0
|
||||
#define CAM0_IO 0x8000
|
||||
#define CAM0_MEM 0x10000
|
||||
#define CAM0_SZ 32
|
||||
/* CI slot 1 base address */
|
||||
#define CAM1_CONFIG 0x20000
|
||||
#define CAM1_IO 0x28000
|
||||
#define CAM1_MEM 0x30000
|
||||
#define CAM1_SZ 32
|
||||
/* ctrlstat registers */
|
||||
#define CAM_CTRLSTAT_READ_SET 0x4980
|
||||
#define CAM_CTRLSTAT_CLR 0x4982
|
||||
/* register bits */
|
||||
#define BIT_CAM_STCHG (1<<0)
|
||||
#define BIT_CAM_PRESENT (1<<1)
|
||||
#define BIT_CAM_RESET (1<<2)
|
||||
#define BIT_CAM_BYPASS (1<<3)
|
||||
#define BIT_CAM_READY (1<<4)
|
||||
#define BIT_CAM_ERROR (1<<5)
|
||||
#define BIT_CAM_OVERCURR (1<<6)
|
||||
/* BIT_CAM_BYPASS bit shift for SLOT 1 */
|
||||
#define CAM1_SHIFT 8
|
||||
|
||||
irqreturn_t netup_ci_interrupt(struct netup_unidvb_dev *ndev)
|
||||
{
|
||||
writew(0x101, ndev->bmmio0 + CAM_CTRLSTAT_CLR);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int netup_unidvb_ci_slot_ts_ctl(struct dvb_ca_en50221 *en50221,
|
||||
int slot)
|
||||
{
|
||||
struct netup_ci_state *state = en50221->data;
|
||||
struct netup_unidvb_dev *dev = state->dev;
|
||||
u16 shift = (state->nr == 1) ? CAM1_SHIFT : 0;
|
||||
|
||||
dev_dbg(&dev->pci_dev->dev, "%s(): CAM_CTRLSTAT=0x%x\n",
|
||||
__func__, readw(dev->bmmio0 + CAM_CTRLSTAT_READ_SET));
|
||||
if (slot != 0)
|
||||
return -EINVAL;
|
||||
/* pass data to CAM module */
|
||||
writew(BIT_CAM_BYPASS << shift, dev->bmmio0 + CAM_CTRLSTAT_CLR);
|
||||
dev_dbg(&dev->pci_dev->dev, "%s(): CAM_CTRLSTAT=0x%x done\n",
|
||||
__func__, readw(dev->bmmio0 + CAM_CTRLSTAT_READ_SET));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int netup_unidvb_ci_slot_shutdown(struct dvb_ca_en50221 *en50221,
|
||||
int slot)
|
||||
{
|
||||
struct netup_ci_state *state = en50221->data;
|
||||
struct netup_unidvb_dev *dev = state->dev;
|
||||
|
||||
dev_dbg(&dev->pci_dev->dev, "%s()\n", __func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int netup_unidvb_ci_slot_reset(struct dvb_ca_en50221 *en50221,
|
||||
int slot)
|
||||
{
|
||||
struct netup_ci_state *state = en50221->data;
|
||||
struct netup_unidvb_dev *dev = state->dev;
|
||||
unsigned long timeout = 0;
|
||||
u16 shift = (state->nr == 1) ? CAM1_SHIFT : 0;
|
||||
u16 ci_stat = 0;
|
||||
int reset_counter = 3;
|
||||
|
||||
dev_dbg(&dev->pci_dev->dev, "%s(): CAM_CTRLSTAT_READ_SET=0x%x\n",
|
||||
__func__, readw(dev->bmmio0 + CAM_CTRLSTAT_READ_SET));
|
||||
reset:
|
||||
timeout = jiffies + msecs_to_jiffies(5000);
|
||||
/* start reset */
|
||||
writew(BIT_CAM_RESET << shift, dev->bmmio0 + CAM_CTRLSTAT_READ_SET);
|
||||
dev_dbg(&dev->pci_dev->dev, "%s(): waiting for reset\n", __func__);
|
||||
/* wait until reset done */
|
||||
while (time_before(jiffies, timeout)) {
|
||||
ci_stat = readw(dev->bmmio0 + CAM_CTRLSTAT_READ_SET);
|
||||
if (ci_stat & (BIT_CAM_READY << shift))
|
||||
break;
|
||||
udelay(1000);
|
||||
}
|
||||
if (!(ci_stat & (BIT_CAM_READY << shift)) && reset_counter > 0) {
|
||||
dev_dbg(&dev->pci_dev->dev,
|
||||
"%s(): CAMP reset timeout! Will try again..\n",
|
||||
__func__);
|
||||
reset_counter--;
|
||||
goto reset;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int netup_unidvb_poll_ci_slot_status(struct dvb_ca_en50221 *en50221,
|
||||
int slot, int open)
|
||||
{
|
||||
struct netup_ci_state *state = en50221->data;
|
||||
struct netup_unidvb_dev *dev = state->dev;
|
||||
u16 shift = (state->nr == 1) ? CAM1_SHIFT : 0;
|
||||
u16 ci_stat = 0;
|
||||
|
||||
dev_dbg(&dev->pci_dev->dev, "%s(): CAM_CTRLSTAT_READ_SET=0x%x\n",
|
||||
__func__, readw(dev->bmmio0 + CAM_CTRLSTAT_READ_SET));
|
||||
ci_stat = readw(dev->bmmio0 + CAM_CTRLSTAT_READ_SET);
|
||||
if (ci_stat & (BIT_CAM_READY << shift)) {
|
||||
state->status = DVB_CA_EN50221_POLL_CAM_PRESENT |
|
||||
DVB_CA_EN50221_POLL_CAM_READY;
|
||||
} else if (ci_stat & (BIT_CAM_PRESENT << shift)) {
|
||||
state->status = DVB_CA_EN50221_POLL_CAM_PRESENT;
|
||||
} else {
|
||||
state->status = 0;
|
||||
}
|
||||
return state->status;
|
||||
}
|
||||
|
||||
static int netup_unidvb_ci_read_attribute_mem(struct dvb_ca_en50221 *en50221,
|
||||
int slot, int addr)
|
||||
{
|
||||
struct netup_ci_state *state = en50221->data;
|
||||
struct netup_unidvb_dev *dev = state->dev;
|
||||
u8 val = state->membase8_config[addr];
|
||||
|
||||
dev_dbg(&dev->pci_dev->dev,
|
||||
"%s(): addr=0x%x val=0x%x\n", __func__, addr, val);
|
||||
return val;
|
||||
}
|
||||
|
||||
static int netup_unidvb_ci_write_attribute_mem(struct dvb_ca_en50221 *en50221,
|
||||
int slot, int addr, u8 data)
|
||||
{
|
||||
struct netup_ci_state *state = en50221->data;
|
||||
struct netup_unidvb_dev *dev = state->dev;
|
||||
|
||||
dev_dbg(&dev->pci_dev->dev,
|
||||
"%s(): addr=0x%x data=0x%x\n", __func__, addr, data);
|
||||
state->membase8_config[addr] = data;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int netup_unidvb_ci_read_cam_ctl(struct dvb_ca_en50221 *en50221,
|
||||
int slot, u8 addr)
|
||||
{
|
||||
struct netup_ci_state *state = en50221->data;
|
||||
struct netup_unidvb_dev *dev = state->dev;
|
||||
u8 val = state->membase8_io[addr];
|
||||
|
||||
dev_dbg(&dev->pci_dev->dev,
|
||||
"%s(): addr=0x%x val=0x%x\n", __func__, addr, val);
|
||||
return val;
|
||||
}
|
||||
|
||||
static int netup_unidvb_ci_write_cam_ctl(struct dvb_ca_en50221 *en50221,
|
||||
int slot, u8 addr, u8 data)
|
||||
{
|
||||
struct netup_ci_state *state = en50221->data;
|
||||
struct netup_unidvb_dev *dev = state->dev;
|
||||
|
||||
dev_dbg(&dev->pci_dev->dev,
|
||||
"%s(): addr=0x%x data=0x%x\n", __func__, addr, data);
|
||||
state->membase8_io[addr] = data;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int netup_unidvb_ci_register(struct netup_unidvb_dev *dev,
|
||||
int num, struct pci_dev *pci_dev)
|
||||
{
|
||||
int result;
|
||||
struct netup_ci_state *state;
|
||||
|
||||
if (num < 0 || num > 1) {
|
||||
dev_err(&pci_dev->dev, "%s(): invalid CI adapter %d\n",
|
||||
__func__, num);
|
||||
return -EINVAL;
|
||||
}
|
||||
state = &dev->ci[num];
|
||||
state->nr = num;
|
||||
state->membase8_config = dev->bmmio1 +
|
||||
((num == 0) ? CAM0_CONFIG : CAM1_CONFIG);
|
||||
state->membase8_io = dev->bmmio1 +
|
||||
((num == 0) ? CAM0_IO : CAM1_IO);
|
||||
state->dev = dev;
|
||||
state->ca.owner = THIS_MODULE;
|
||||
state->ca.read_attribute_mem = netup_unidvb_ci_read_attribute_mem;
|
||||
state->ca.write_attribute_mem = netup_unidvb_ci_write_attribute_mem;
|
||||
state->ca.read_cam_control = netup_unidvb_ci_read_cam_ctl;
|
||||
state->ca.write_cam_control = netup_unidvb_ci_write_cam_ctl;
|
||||
state->ca.slot_reset = netup_unidvb_ci_slot_reset;
|
||||
state->ca.slot_shutdown = netup_unidvb_ci_slot_shutdown;
|
||||
state->ca.slot_ts_enable = netup_unidvb_ci_slot_ts_ctl;
|
||||
state->ca.poll_slot_status = netup_unidvb_poll_ci_slot_status;
|
||||
state->ca.data = state;
|
||||
result = dvb_ca_en50221_init(&dev->frontends[num].adapter,
|
||||
&state->ca, 0, 1);
|
||||
if (result < 0) {
|
||||
dev_err(&pci_dev->dev,
|
||||
"%s(): dvb_ca_en50221_init result %d\n",
|
||||
__func__, result);
|
||||
return result;
|
||||
}
|
||||
writew(NETUP_UNIDVB_IRQ_CI, (u16 *)(dev->bmmio0 + REG_IMASK_SET));
|
||||
dev_info(&pci_dev->dev,
|
||||
"%s(): CI adapter %d init done\n", __func__, num);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void netup_unidvb_ci_unregister(struct netup_unidvb_dev *dev, int num)
|
||||
{
|
||||
struct netup_ci_state *state;
|
||||
|
||||
dev_dbg(&dev->pci_dev->dev, "%s()\n", __func__);
|
||||
if (num < 0 || num > 1) {
|
||||
dev_err(&dev->pci_dev->dev, "%s(): invalid CI adapter %d\n",
|
||||
__func__, num);
|
||||
return;
|
||||
}
|
||||
state = &dev->ci[num];
|
||||
dvb_ca_en50221_release(&state->ca);
|
||||
}
|
||||
|
1001
drivers/media/pci/netup_unidvb/netup_unidvb_core.c
Normal file
1001
drivers/media/pci/netup_unidvb/netup_unidvb_core.c
Normal file
File diff suppressed because it is too large
Load diff
381
drivers/media/pci/netup_unidvb/netup_unidvb_i2c.c
Normal file
381
drivers/media/pci/netup_unidvb/netup_unidvb_i2c.c
Normal file
|
@ -0,0 +1,381 @@
|
|||
/*
|
||||
* netup_unidvb_i2c.c
|
||||
*
|
||||
* Internal I2C bus 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 <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/delay.h>
|
||||
#include "netup_unidvb.h"
|
||||
|
||||
#define NETUP_I2C_BUS0_ADDR 0x4800
|
||||
#define NETUP_I2C_BUS1_ADDR 0x4840
|
||||
#define NETUP_I2C_TIMEOUT 1000
|
||||
|
||||
/* twi_ctrl0_stat reg bits */
|
||||
#define TWI_IRQEN_COMPL 0x1
|
||||
#define TWI_IRQEN_ANACK 0x2
|
||||
#define TWI_IRQEN_DNACK 0x4
|
||||
#define TWI_IRQ_COMPL (TWI_IRQEN_COMPL << 8)
|
||||
#define TWI_IRQ_ANACK (TWI_IRQEN_ANACK << 8)
|
||||
#define TWI_IRQ_DNACK (TWI_IRQEN_DNACK << 8)
|
||||
#define TWI_IRQ_TX 0x800
|
||||
#define TWI_IRQ_RX 0x1000
|
||||
#define TWI_IRQEN (TWI_IRQEN_COMPL | TWI_IRQEN_ANACK | TWI_IRQEN_DNACK)
|
||||
/* twi_addr_ctrl1 reg bits*/
|
||||
#define TWI_TRANSFER 0x100
|
||||
#define TWI_NOSTOP 0x200
|
||||
#define TWI_SOFT_RESET 0x2000
|
||||
/* twi_clkdiv reg value */
|
||||
#define TWI_CLKDIV 156
|
||||
/* fifo_stat_ctrl reg bits */
|
||||
#define FIFO_IRQEN 0x8000
|
||||
#define FIFO_RESET 0x4000
|
||||
/* FIFO size */
|
||||
#define FIFO_SIZE 16
|
||||
|
||||
struct netup_i2c_fifo_regs {
|
||||
union {
|
||||
__u8 data8;
|
||||
__le16 data16;
|
||||
__le32 data32;
|
||||
};
|
||||
__u8 padding[4];
|
||||
__le16 stat_ctrl;
|
||||
} __packed __aligned(1);
|
||||
|
||||
struct netup_i2c_regs {
|
||||
__le16 clkdiv;
|
||||
__le16 twi_ctrl0_stat;
|
||||
__le16 twi_addr_ctrl1;
|
||||
__le16 length;
|
||||
__u8 padding1[8];
|
||||
struct netup_i2c_fifo_regs tx_fifo;
|
||||
__u8 padding2[6];
|
||||
struct netup_i2c_fifo_regs rx_fifo;
|
||||
} __packed __aligned(1);
|
||||
|
||||
irqreturn_t netup_i2c_interrupt(struct netup_i2c *i2c)
|
||||
{
|
||||
u16 reg, tmp;
|
||||
unsigned long flags;
|
||||
irqreturn_t iret = IRQ_HANDLED;
|
||||
|
||||
spin_lock_irqsave(&i2c->lock, flags);
|
||||
reg = readw(&i2c->regs->twi_ctrl0_stat);
|
||||
writew(reg & ~TWI_IRQEN, &i2c->regs->twi_ctrl0_stat);
|
||||
dev_dbg(i2c->adap.dev.parent,
|
||||
"%s(): twi_ctrl0_state 0x%x\n", __func__, reg);
|
||||
if ((reg & TWI_IRQEN_COMPL) != 0 && (reg & TWI_IRQ_COMPL)) {
|
||||
dev_dbg(i2c->adap.dev.parent,
|
||||
"%s(): TWI_IRQEN_COMPL\n", __func__);
|
||||
i2c->state = STATE_DONE;
|
||||
goto irq_ok;
|
||||
}
|
||||
if ((reg & TWI_IRQEN_ANACK) != 0 && (reg & TWI_IRQ_ANACK)) {
|
||||
dev_dbg(i2c->adap.dev.parent,
|
||||
"%s(): TWI_IRQEN_ANACK\n", __func__);
|
||||
i2c->state = STATE_ERROR;
|
||||
goto irq_ok;
|
||||
}
|
||||
if ((reg & TWI_IRQEN_DNACK) != 0 && (reg & TWI_IRQ_DNACK)) {
|
||||
dev_dbg(i2c->adap.dev.parent,
|
||||
"%s(): TWI_IRQEN_DNACK\n", __func__);
|
||||
i2c->state = STATE_ERROR;
|
||||
goto irq_ok;
|
||||
}
|
||||
if ((reg & TWI_IRQ_RX) != 0) {
|
||||
tmp = readw(&i2c->regs->rx_fifo.stat_ctrl);
|
||||
writew(tmp & ~FIFO_IRQEN, &i2c->regs->rx_fifo.stat_ctrl);
|
||||
i2c->state = STATE_WANT_READ;
|
||||
dev_dbg(i2c->adap.dev.parent,
|
||||
"%s(): want read\n", __func__);
|
||||
goto irq_ok;
|
||||
}
|
||||
if ((reg & TWI_IRQ_TX) != 0) {
|
||||
tmp = readw(&i2c->regs->tx_fifo.stat_ctrl);
|
||||
writew(tmp & ~FIFO_IRQEN, &i2c->regs->tx_fifo.stat_ctrl);
|
||||
i2c->state = STATE_WANT_WRITE;
|
||||
dev_dbg(i2c->adap.dev.parent,
|
||||
"%s(): want write\n", __func__);
|
||||
goto irq_ok;
|
||||
}
|
||||
dev_warn(&i2c->adap.dev, "%s(): not mine interrupt\n", __func__);
|
||||
iret = IRQ_NONE;
|
||||
irq_ok:
|
||||
spin_unlock_irqrestore(&i2c->lock, flags);
|
||||
if (iret == IRQ_HANDLED)
|
||||
wake_up(&i2c->wq);
|
||||
return iret;
|
||||
}
|
||||
|
||||
static void netup_i2c_reset(struct netup_i2c *i2c)
|
||||
{
|
||||
dev_dbg(i2c->adap.dev.parent, "%s()\n", __func__);
|
||||
i2c->state = STATE_DONE;
|
||||
writew(TWI_SOFT_RESET, &i2c->regs->twi_addr_ctrl1);
|
||||
writew(TWI_CLKDIV, &i2c->regs->clkdiv);
|
||||
writew(FIFO_RESET, &i2c->regs->tx_fifo.stat_ctrl);
|
||||
writew(FIFO_RESET, &i2c->regs->rx_fifo.stat_ctrl);
|
||||
writew(0x800, &i2c->regs->tx_fifo.stat_ctrl);
|
||||
writew(0x800, &i2c->regs->rx_fifo.stat_ctrl);
|
||||
}
|
||||
|
||||
static void netup_i2c_fifo_tx(struct netup_i2c *i2c)
|
||||
{
|
||||
u8 data;
|
||||
u32 fifo_space = FIFO_SIZE -
|
||||
(readw(&i2c->regs->tx_fifo.stat_ctrl) & 0x3f);
|
||||
u32 msg_length = i2c->msg->len - i2c->xmit_size;
|
||||
|
||||
msg_length = (msg_length < fifo_space ? msg_length : fifo_space);
|
||||
while (msg_length--) {
|
||||
data = i2c->msg->buf[i2c->xmit_size++];
|
||||
writeb(data, &i2c->regs->tx_fifo.data8);
|
||||
dev_dbg(i2c->adap.dev.parent,
|
||||
"%s(): write 0x%02x\n", __func__, data);
|
||||
}
|
||||
if (i2c->xmit_size < i2c->msg->len) {
|
||||
dev_dbg(i2c->adap.dev.parent,
|
||||
"%s(): TX IRQ enabled\n", __func__);
|
||||
writew(readw(&i2c->regs->tx_fifo.stat_ctrl) | FIFO_IRQEN,
|
||||
&i2c->regs->tx_fifo.stat_ctrl);
|
||||
}
|
||||
}
|
||||
|
||||
static void netup_i2c_fifo_rx(struct netup_i2c *i2c)
|
||||
{
|
||||
u8 data;
|
||||
u32 fifo_size = readw(&i2c->regs->rx_fifo.stat_ctrl) & 0x3f;
|
||||
|
||||
dev_dbg(i2c->adap.dev.parent,
|
||||
"%s(): RX fifo size %d\n", __func__, fifo_size);
|
||||
while (fifo_size--) {
|
||||
data = readb(&i2c->regs->rx_fifo.data8);
|
||||
if ((i2c->msg->flags & I2C_M_RD) != 0 &&
|
||||
i2c->xmit_size < i2c->msg->len) {
|
||||
i2c->msg->buf[i2c->xmit_size++] = data;
|
||||
dev_dbg(i2c->adap.dev.parent,
|
||||
"%s(): read 0x%02x\n", __func__, data);
|
||||
}
|
||||
}
|
||||
if (i2c->xmit_size < i2c->msg->len) {
|
||||
dev_dbg(i2c->adap.dev.parent,
|
||||
"%s(): RX IRQ enabled\n", __func__);
|
||||
writew(readw(&i2c->regs->rx_fifo.stat_ctrl) | FIFO_IRQEN,
|
||||
&i2c->regs->rx_fifo.stat_ctrl);
|
||||
}
|
||||
}
|
||||
|
||||
static void netup_i2c_start_xfer(struct netup_i2c *i2c)
|
||||
{
|
||||
u16 rdflag = ((i2c->msg->flags & I2C_M_RD) ? 1 : 0);
|
||||
u16 reg = readw(&i2c->regs->twi_ctrl0_stat);
|
||||
|
||||
writew(TWI_IRQEN | reg, &i2c->regs->twi_ctrl0_stat);
|
||||
writew(i2c->msg->len, &i2c->regs->length);
|
||||
writew(TWI_TRANSFER | (i2c->msg->addr << 1) | rdflag,
|
||||
&i2c->regs->twi_addr_ctrl1);
|
||||
dev_dbg(i2c->adap.dev.parent,
|
||||
"%s(): length %d twi_addr_ctrl1 0x%x twi_ctrl0_stat 0x%x\n",
|
||||
__func__, readw(&i2c->regs->length),
|
||||
readw(&i2c->regs->twi_addr_ctrl1),
|
||||
readw(&i2c->regs->twi_ctrl0_stat));
|
||||
i2c->state = STATE_WAIT;
|
||||
i2c->xmit_size = 0;
|
||||
if (!rdflag)
|
||||
netup_i2c_fifo_tx(i2c);
|
||||
else
|
||||
writew(FIFO_IRQEN | readw(&i2c->regs->rx_fifo.stat_ctrl),
|
||||
&i2c->regs->rx_fifo.stat_ctrl);
|
||||
}
|
||||
|
||||
static int netup_i2c_xfer(struct i2c_adapter *adap,
|
||||
struct i2c_msg *msgs, int num)
|
||||
{
|
||||
unsigned long flags;
|
||||
int i, trans_done, res = num;
|
||||
struct netup_i2c *i2c = i2c_get_adapdata(adap);
|
||||
u16 reg;
|
||||
|
||||
if (num <= 0) {
|
||||
dev_dbg(i2c->adap.dev.parent,
|
||||
"%s(): num == %d\n", __func__, num);
|
||||
return -EINVAL;
|
||||
}
|
||||
spin_lock_irqsave(&i2c->lock, flags);
|
||||
if (i2c->state != STATE_DONE) {
|
||||
dev_dbg(i2c->adap.dev.parent,
|
||||
"%s(): i2c->state == %d, resetting I2C\n",
|
||||
__func__, i2c->state);
|
||||
netup_i2c_reset(i2c);
|
||||
}
|
||||
dev_dbg(i2c->adap.dev.parent, "%s() num %d\n", __func__, num);
|
||||
for (i = 0; i < num; i++) {
|
||||
i2c->msg = &msgs[i];
|
||||
netup_i2c_start_xfer(i2c);
|
||||
trans_done = 0;
|
||||
while (!trans_done) {
|
||||
spin_unlock_irqrestore(&i2c->lock, flags);
|
||||
if (wait_event_timeout(i2c->wq,
|
||||
i2c->state != STATE_WAIT,
|
||||
msecs_to_jiffies(NETUP_I2C_TIMEOUT))) {
|
||||
spin_lock_irqsave(&i2c->lock, flags);
|
||||
switch (i2c->state) {
|
||||
case STATE_WANT_READ:
|
||||
netup_i2c_fifo_rx(i2c);
|
||||
break;
|
||||
case STATE_WANT_WRITE:
|
||||
netup_i2c_fifo_tx(i2c);
|
||||
break;
|
||||
case STATE_DONE:
|
||||
if ((i2c->msg->flags & I2C_M_RD) != 0 &&
|
||||
i2c->xmit_size != i2c->msg->len)
|
||||
netup_i2c_fifo_rx(i2c);
|
||||
dev_dbg(i2c->adap.dev.parent,
|
||||
"%s(): msg %d OK\n",
|
||||
__func__, i);
|
||||
trans_done = 1;
|
||||
break;
|
||||
case STATE_ERROR:
|
||||
res = -EIO;
|
||||
dev_dbg(i2c->adap.dev.parent,
|
||||
"%s(): error state\n",
|
||||
__func__);
|
||||
goto done;
|
||||
default:
|
||||
dev_dbg(i2c->adap.dev.parent,
|
||||
"%s(): invalid state %d\n",
|
||||
__func__, i2c->state);
|
||||
res = -EINVAL;
|
||||
goto done;
|
||||
}
|
||||
if (!trans_done) {
|
||||
i2c->state = STATE_WAIT;
|
||||
reg = readw(
|
||||
&i2c->regs->twi_ctrl0_stat);
|
||||
writew(TWI_IRQEN | reg,
|
||||
&i2c->regs->twi_ctrl0_stat);
|
||||
}
|
||||
spin_unlock_irqrestore(&i2c->lock, flags);
|
||||
} else {
|
||||
spin_lock_irqsave(&i2c->lock, flags);
|
||||
dev_dbg(i2c->adap.dev.parent,
|
||||
"%s(): wait timeout\n", __func__);
|
||||
res = -ETIMEDOUT;
|
||||
goto done;
|
||||
}
|
||||
spin_lock_irqsave(&i2c->lock, flags);
|
||||
}
|
||||
}
|
||||
done:
|
||||
spin_unlock_irqrestore(&i2c->lock, flags);
|
||||
dev_dbg(i2c->adap.dev.parent, "%s(): result %d\n", __func__, res);
|
||||
return res;
|
||||
}
|
||||
|
||||
static u32 netup_i2c_func(struct i2c_adapter *adap)
|
||||
{
|
||||
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
|
||||
}
|
||||
|
||||
static const struct i2c_algorithm netup_i2c_algorithm = {
|
||||
.master_xfer = netup_i2c_xfer,
|
||||
.functionality = netup_i2c_func,
|
||||
};
|
||||
|
||||
static struct i2c_adapter netup_i2c_adapter = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = NETUP_UNIDVB_NAME,
|
||||
.class = I2C_CLASS_HWMON | I2C_CLASS_SPD,
|
||||
.algo = &netup_i2c_algorithm,
|
||||
};
|
||||
|
||||
static int netup_i2c_init(struct netup_unidvb_dev *ndev, int bus_num)
|
||||
{
|
||||
int ret;
|
||||
struct netup_i2c *i2c;
|
||||
|
||||
if (bus_num < 0 || bus_num > 1) {
|
||||
dev_err(&ndev->pci_dev->dev,
|
||||
"%s(): invalid bus_num %d\n", __func__, bus_num);
|
||||
return -EINVAL;
|
||||
}
|
||||
i2c = &ndev->i2c[bus_num];
|
||||
spin_lock_init(&i2c->lock);
|
||||
init_waitqueue_head(&i2c->wq);
|
||||
i2c->regs = (struct netup_i2c_regs *)(ndev->bmmio0 +
|
||||
(bus_num == 0 ? NETUP_I2C_BUS0_ADDR : NETUP_I2C_BUS1_ADDR));
|
||||
netup_i2c_reset(i2c);
|
||||
i2c->adap = netup_i2c_adapter;
|
||||
i2c->adap.dev.parent = &ndev->pci_dev->dev;
|
||||
i2c_set_adapdata(&i2c->adap, i2c);
|
||||
ret = i2c_add_adapter(&i2c->adap);
|
||||
if (ret) {
|
||||
dev_err(&ndev->pci_dev->dev,
|
||||
"%s(): failed to add I2C adapter\n", __func__);
|
||||
return ret;
|
||||
}
|
||||
dev_info(&ndev->pci_dev->dev,
|
||||
"%s(): registered I2C bus %d at 0x%x\n",
|
||||
__func__,
|
||||
bus_num, (bus_num == 0 ?
|
||||
NETUP_I2C_BUS0_ADDR :
|
||||
NETUP_I2C_BUS1_ADDR));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void netup_i2c_remove(struct netup_unidvb_dev *ndev, int bus_num)
|
||||
{
|
||||
struct netup_i2c *i2c;
|
||||
|
||||
if (bus_num < 0 || bus_num > 1) {
|
||||
dev_err(&ndev->pci_dev->dev,
|
||||
"%s(): invalid bus number %d\n", __func__, bus_num);
|
||||
return;
|
||||
}
|
||||
i2c = &ndev->i2c[bus_num];
|
||||
netup_i2c_reset(i2c);
|
||||
/* remove adapter */
|
||||
i2c_del_adapter(&i2c->adap);
|
||||
dev_info(&ndev->pci_dev->dev,
|
||||
"netup_i2c_remove: unregistered I2C bus %d\n", bus_num);
|
||||
}
|
||||
|
||||
int netup_i2c_register(struct netup_unidvb_dev *ndev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = netup_i2c_init(ndev, 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = netup_i2c_init(ndev, 1);
|
||||
if (ret) {
|
||||
netup_i2c_remove(ndev, 0);
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void netup_i2c_unregister(struct netup_unidvb_dev *ndev)
|
||||
{
|
||||
netup_i2c_remove(ndev, 0);
|
||||
netup_i2c_remove(ndev, 1);
|
||||
}
|
||||
|
252
drivers/media/pci/netup_unidvb/netup_unidvb_spi.c
Normal file
252
drivers/media/pci/netup_unidvb/netup_unidvb_spi.c
Normal file
|
@ -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;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in a new issue