Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+31
View File
@@ -0,0 +1,31 @@
# SPDX-License-Identifier: GPL-2.0-only
# Used by common drivers, when they need to ask questions
config MEDIA_COMMON_OPTIONS
bool
comment "common driver options"
depends on MEDIA_COMMON_OPTIONS
config CYPRESS_FIRMWARE
tristate
depends on USB
config TTPCI_EEPROM
tristate
depends on I2C
config UVC_COMMON
tristate
config VIDEO_CX2341X
tristate
config VIDEO_TVEEPROM
tristate
depends on I2C
source "drivers/media/common/b2c2/Kconfig"
source "drivers/media/common/saa7146/Kconfig"
source "drivers/media/common/siano/Kconfig"
source "drivers/media/common/v4l2-tpg/Kconfig"
source "drivers/media/common/videobuf2/Kconfig"
+10
View File
@@ -0,0 +1,10 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-y += b2c2/ saa7146/ siano/ v4l2-tpg/ videobuf2/
# Please keep it alphabetically sorted by Kconfig name
# (e. g. LC_ALL=C sort Makefile)
obj-$(CONFIG_CYPRESS_FIRMWARE) += cypress_firmware.o
obj-$(CONFIG_TTPCI_EEPROM) += ttpci-eeprom.o
obj-$(CONFIG_UVC_COMMON) += uvc.o
obj-$(CONFIG_VIDEO_CX2341X) += cx2341x.o
obj-$(CONFIG_VIDEO_TVEEPROM) += tveeprom.o
@@ -0,0 +1,25 @@
# SPDX-License-Identifier: GPL-2.0-only
config DVB_B2C2_FLEXCOP
tristate
depends on DVB_CORE && I2C
depends on DVB_B2C2_FLEXCOP_PCI || DVB_B2C2_FLEXCOP_USB
default y
select DVB_PLL if MEDIA_SUBDRV_AUTOSELECT
select DVB_STV0299 if MEDIA_SUBDRV_AUTOSELECT
select DVB_MT352 if MEDIA_SUBDRV_AUTOSELECT
select DVB_MT312 if MEDIA_SUBDRV_AUTOSELECT
select DVB_NXT200X if MEDIA_SUBDRV_AUTOSELECT
select DVB_STV0297 if MEDIA_SUBDRV_AUTOSELECT
select DVB_BCM3510 if MEDIA_SUBDRV_AUTOSELECT
select DVB_LGDT330X if MEDIA_SUBDRV_AUTOSELECT
select DVB_S5H1420 if MEDIA_SUBDRV_AUTOSELECT
select DVB_TUNER_ITD1000 if MEDIA_SUBDRV_AUTOSELECT
select DVB_ISL6421 if MEDIA_SUBDRV_AUTOSELECT
select DVB_CX24120 if MEDIA_SUBDRV_AUTOSELECT
select DVB_CX24123 if MEDIA_SUBDRV_AUTOSELECT
select MEDIA_TUNER_SIMPLE if MEDIA_SUBDRV_AUTOSELECT
select DVB_TUNER_CX24113 if MEDIA_SUBDRV_AUTOSELECT
# Selected via the PCI or USB flexcop drivers
config DVB_B2C2_FLEXCOP_DEBUG
bool
@@ -0,0 +1,8 @@
# SPDX-License-Identifier: GPL-2.0
b2c2-flexcop-objs += flexcop.o flexcop-fe-tuner.o flexcop-i2c.o
b2c2-flexcop-objs += flexcop-sram.o flexcop-eeprom.o flexcop-misc.o
b2c2-flexcop-objs += flexcop-hw-filter.o
obj-$(CONFIG_DVB_B2C2_FLEXCOP) += b2c2-flexcop.o
ccflags-y += -I $(srctree)/drivers/media/dvb-frontends/
ccflags-y += -I $(srctree)/drivers/media/tuners/
@@ -0,0 +1,182 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Linux driver for digital TV devices equipped with B2C2 FlexcopII(b)/III
* flexcop-common.h - common header file for device-specific source files
* see flexcop.c for copyright information
*/
#ifndef __FLEXCOP_COMMON_H__
#define __FLEXCOP_COMMON_H__
#include <linux/interrupt.h>
#include <linux/pci.h>
#include <linux/mutex.h>
#include "flexcop-reg.h"
#include <media/dmxdev.h>
#include <media/dvb_demux.h>
#include <media/dvb_net.h>
#include <media/dvb_frontend.h>
#define FC_MAX_FEED 256
#ifndef FC_LOG_PREFIX
#warning please define a log prefix for your file, using a default one
#define FC_LOG_PREFIX "b2c2-undef"
#endif
/* Steal from usb.h */
#undef err
#define err(format, arg...) \
printk(KERN_ERR FC_LOG_PREFIX ": " format "\n" , ## arg)
#undef info
#define info(format, arg...) \
printk(KERN_INFO FC_LOG_PREFIX ": " format "\n" , ## arg)
#undef warn
#define warn(format, arg...) \
printk(KERN_WARNING FC_LOG_PREFIX ": " format "\n" , ## arg)
struct flexcop_dma {
struct pci_dev *pdev;
u8 *cpu_addr0;
dma_addr_t dma_addr0;
u8 *cpu_addr1;
dma_addr_t dma_addr1;
u32 size; /* size of each address in bytes */
};
struct flexcop_i2c_adapter {
struct flexcop_device *fc;
struct i2c_adapter i2c_adap;
u8 no_base_addr;
flexcop_i2c_port_t port;
};
/* Control structure for data definitions that are common to
* the B2C2-based PCI and USB devices.
*/
struct flexcop_device {
/* general */
struct device *dev; /* for firmware_class */
#define FC_STATE_DVB_INIT 0x01
#define FC_STATE_I2C_INIT 0x02
#define FC_STATE_FE_INIT 0x04
int init_state;
/* device information */
int has_32_hw_pid_filter;
flexcop_revision_t rev;
flexcop_device_type_t dev_type;
flexcop_bus_t bus_type;
/* dvb stuff */
struct dvb_adapter dvb_adapter;
struct dvb_frontend *fe;
struct dvb_net dvbnet;
struct dvb_demux demux;
struct dmxdev dmxdev;
struct dmx_frontend hw_frontend;
struct dmx_frontend mem_frontend;
int (*fe_sleep) (struct dvb_frontend *);
struct flexcop_i2c_adapter fc_i2c_adap[3];
struct mutex i2c_mutex;
struct module *owner;
/* options and status */
int extra_feedcount;
int feedcount;
int pid_filtering;
int fullts_streaming_state;
int skip_6_hw_pid_filter;
/* bus specific callbacks */
flexcop_ibi_value(*read_ibi_reg) (struct flexcop_device *,
flexcop_ibi_register);
int (*write_ibi_reg) (struct flexcop_device *,
flexcop_ibi_register, flexcop_ibi_value);
int (*i2c_request) (struct flexcop_i2c_adapter *,
flexcop_access_op_t, u8 chipaddr, u8 addr, u8 *buf, u16 len);
int (*stream_control) (struct flexcop_device *, int);
int (*get_mac_addr) (struct flexcop_device *fc, int extended);
void *bus_specific;
};
/* exported prototypes */
/* from flexcop.c */
void flexcop_pass_dmx_data(struct flexcop_device *fc, u8 *buf, u32 len);
void flexcop_pass_dmx_packets(struct flexcop_device *fc, u8 *buf, u32 no);
struct flexcop_device *flexcop_device_kmalloc(size_t bus_specific_len);
void flexcop_device_kfree(struct flexcop_device *);
int flexcop_device_initialize(struct flexcop_device *);
void flexcop_device_exit(struct flexcop_device *fc);
void flexcop_reset_block_300(struct flexcop_device *fc);
/* from flexcop-dma.c */
int flexcop_dma_allocate(struct pci_dev *pdev,
struct flexcop_dma *dma, u32 size);
void flexcop_dma_free(struct flexcop_dma *dma);
int flexcop_dma_control_timer_irq(struct flexcop_device *fc,
flexcop_dma_index_t no, int onoff);
int flexcop_dma_config(struct flexcop_device *fc, struct flexcop_dma *dma,
flexcop_dma_index_t dma_idx);
int flexcop_dma_xfer_control(struct flexcop_device *fc,
flexcop_dma_index_t dma_idx, flexcop_dma_addr_index_t index,
int onoff);
int flexcop_dma_config_timer(struct flexcop_device *fc,
flexcop_dma_index_t dma_idx, u8 cycles);
/* from flexcop-eeprom.c */
/* the PCI part uses this call to get the MAC address, the USB part has its own */
int flexcop_eeprom_check_mac_addr(struct flexcop_device *fc, int extended);
/* from flexcop-i2c.c */
/* the PCI part uses this a i2c_request callback, whereas the usb part has its own
* one. We have it in flexcop-i2c.c, because it is going via the actual
* I2C-channel of the flexcop.
*/
int flexcop_i2c_request(struct flexcop_i2c_adapter*, flexcop_access_op_t,
u8 chipaddr, u8 addr, u8 *buf, u16 len);
/* from flexcop-sram.c */
int flexcop_sram_set_dest(struct flexcop_device *fc, flexcop_sram_dest_t dest,
flexcop_sram_dest_target_t target);
void flexcop_wan_set_speed(struct flexcop_device *fc, flexcop_wan_speed_t s);
void flexcop_sram_ctrl(struct flexcop_device *fc,
int usb_wan, int sramdma, int maximumfill);
/* global prototypes for the flexcop-chip */
/* from flexcop-fe-tuner.c */
int flexcop_frontend_init(struct flexcop_device *fc);
void flexcop_frontend_exit(struct flexcop_device *fc);
/* from flexcop-i2c.c */
int flexcop_i2c_init(struct flexcop_device *fc);
void flexcop_i2c_exit(struct flexcop_device *fc);
/* from flexcop-sram.c */
int flexcop_sram_init(struct flexcop_device *fc);
/* from flexcop-misc.c */
void flexcop_determine_revision(struct flexcop_device *fc);
void flexcop_device_name(struct flexcop_device *fc,
const char *prefix, const char *suffix);
/* from flexcop-hw-filter.c */
int flexcop_pid_feed_control(struct flexcop_device *fc,
struct dvb_demux_feed *dvbdmxfeed, int onoff);
void flexcop_hw_filter_init(struct flexcop_device *fc);
void flexcop_smc_ctrl(struct flexcop_device *fc, int onoff);
void flexcop_set_mac_filter(struct flexcop_device *fc, u8 mac[6]);
void flexcop_mac_filter_ctrl(struct flexcop_device *fc, int onoff);
#endif
@@ -0,0 +1,147 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Linux driver for digital TV devices equipped with B2C2 FlexcopII(b)/III
* flexcop-eeprom.c - eeprom access methods (currently only MAC address reading)
* see flexcop.c for copyright information
*/
#include "flexcop.h"
#if 0
/*EEPROM (Skystar2 has one "24LC08B" chip on board) */
static int eeprom_write(struct adapter *adapter, u16 addr, u8 *buf, u16 len)
{
return flex_i2c_write(adapter, 0x20000000, 0x50, addr, buf, len);
}
static int eeprom_lrc_write(struct adapter *adapter, u32 addr,
u32 len, u8 *wbuf, u8 *rbuf, int retries)
{
int i;
for (i = 0; i < retries; i++) {
if (eeprom_write(adapter, addr, wbuf, len) == len) {
if (eeprom_lrc_read(adapter, addr, len, rbuf, retries) == 1)
return 1;
}
}
return 0;
}
/* These functions could be used to unlock SkyStar2 cards. */
static int eeprom_writeKey(struct adapter *adapter, u8 *key, u32 len)
{
u8 rbuf[20];
u8 wbuf[20];
if (len != 16)
return 0;
memcpy(wbuf, key, len);
wbuf[16] = 0;
wbuf[17] = 0;
wbuf[18] = 0;
wbuf[19] = calc_lrc(wbuf, 19);
return eeprom_lrc_write(adapter, 0x3e4, 20, wbuf, rbuf, 4);
}
static int eeprom_readKey(struct adapter *adapter, u8 *key, u32 len)
{
u8 buf[20];
if (len != 16)
return 0;
if (eeprom_lrc_read(adapter, 0x3e4, 20, buf, 4) == 0)
return 0;
memcpy(key, buf, len);
return 1;
}
static char eeprom_set_mac_addr(struct adapter *adapter, char type, u8 *mac)
{
u8 tmp[8];
if (type != 0) {
tmp[0] = mac[0];
tmp[1] = mac[1];
tmp[2] = mac[2];
tmp[3] = mac[5];
tmp[4] = mac[6];
tmp[5] = mac[7];
} else {
tmp[0] = mac[0];
tmp[1] = mac[1];
tmp[2] = mac[2];
tmp[3] = mac[3];
tmp[4] = mac[4];
tmp[5] = mac[5];
}
tmp[6] = 0;
tmp[7] = calc_lrc(tmp, 7);
if (eeprom_write(adapter, 0x3f8, tmp, 8) == 8)
return 1;
return 0;
}
static int flexcop_eeprom_read(struct flexcop_device *fc,
u16 addr, u8 *buf, u16 len)
{
return fc->i2c_request(fc,FC_READ,FC_I2C_PORT_EEPROM,0x50,addr,buf,len);
}
#endif
static u8 calc_lrc(u8 *buf, int len)
{
int i;
u8 sum = 0;
for (i = 0; i < len; i++)
sum = sum ^ buf[i];
return sum;
}
static int flexcop_eeprom_request(struct flexcop_device *fc,
flexcop_access_op_t op, u16 addr, u8 *buf, u16 len, int retries)
{
int i,ret = 0;
u8 chipaddr = 0x50 | ((addr >> 8) & 3);
for (i = 0; i < retries; i++) {
ret = fc->i2c_request(&fc->fc_i2c_adap[1], op, chipaddr,
addr & 0xff, buf, len);
if (ret == 0)
break;
}
return ret;
}
static int flexcop_eeprom_lrc_read(struct flexcop_device *fc, u16 addr,
u8 *buf, u16 len, int retries)
{
int ret = flexcop_eeprom_request(fc, FC_READ, addr, buf, len, retries);
if (ret == 0)
if (calc_lrc(buf, len - 1) != buf[len - 1])
ret = -EINVAL;
return ret;
}
/* JJ's comment about extended == 1: it is not presently used anywhere but was
* added to the low-level functions for possible support of EUI64 */
int flexcop_eeprom_check_mac_addr(struct flexcop_device *fc, int extended)
{
u8 buf[8];
int ret = 0;
if ((ret = flexcop_eeprom_lrc_read(fc,0x3f8,buf,8,4)) == 0) {
if (extended != 0) {
err("TODO: extended (EUI64) MAC addresses aren't completely supported yet");
ret = -EINVAL;
} else
memcpy(fc->dvb_adapter.proposed_mac,buf,6);
}
return ret;
}
EXPORT_SYMBOL(flexcop_eeprom_check_mac_addr);
@@ -0,0 +1,721 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Linux driver for digital TV devices equipped with B2C2 FlexcopII(b)/III
* flexcop-fe-tuner.c - methods for frontend attachment and DiSEqC controlling
* see flexcop.c for copyright information
*/
#include <media/tuner.h>
#include "flexcop.h"
#include "mt312.h"
#include "stv0299.h"
#include "s5h1420.h"
#include "itd1000.h"
#include "cx24113.h"
#include "cx24123.h"
#include "isl6421.h"
#include "cx24120.h"
#include "mt352.h"
#include "bcm3510.h"
#include "nxt200x.h"
#include "dvb-pll.h"
#include "lgdt330x.h"
#include "tuner-simple.h"
#include "stv0297.h"
/* Can we use the specified front-end? Remember that if we are compiled
* into the kernel we can't call code that's in modules. */
#define FE_SUPPORTED(fe) IS_REACHABLE(CONFIG_DVB_ ## fe)
#if FE_SUPPORTED(BCM3510) || (FE_SUPPORTED(CX24120) && FE_SUPPORTED(ISL6421))
static int flexcop_fe_request_firmware(struct dvb_frontend *fe,
const struct firmware **fw, char *name)
{
struct flexcop_device *fc = fe->dvb->priv;
return request_firmware(fw, name, fc->dev);
}
#endif
/* lnb control */
#if (FE_SUPPORTED(MT312) || FE_SUPPORTED(STV0299)) && FE_SUPPORTED(PLL)
static int flexcop_set_voltage(struct dvb_frontend *fe,
enum fe_sec_voltage voltage)
{
struct flexcop_device *fc = fe->dvb->priv;
flexcop_ibi_value v;
deb_tuner("polarity/voltage = %u\n", voltage);
v = fc->read_ibi_reg(fc, misc_204);
switch (voltage) {
case SEC_VOLTAGE_OFF:
v.misc_204.ACPI1_sig = 1;
break;
case SEC_VOLTAGE_13:
v.misc_204.ACPI1_sig = 0;
v.misc_204.LNB_L_H_sig = 0;
break;
case SEC_VOLTAGE_18:
v.misc_204.ACPI1_sig = 0;
v.misc_204.LNB_L_H_sig = 1;
break;
default:
err("unknown SEC_VOLTAGE value");
return -EINVAL;
}
return fc->write_ibi_reg(fc, misc_204, v);
}
#endif
#if FE_SUPPORTED(S5H1420) || FE_SUPPORTED(STV0299) || FE_SUPPORTED(MT312)
static int __maybe_unused flexcop_sleep(struct dvb_frontend* fe)
{
struct flexcop_device *fc = fe->dvb->priv;
if (fc->fe_sleep)
return fc->fe_sleep(fe);
return 0;
}
#endif
/* SkyStar2 DVB-S rev 2.3 */
#if FE_SUPPORTED(MT312) && FE_SUPPORTED(PLL)
static int flexcop_set_tone(struct dvb_frontend *fe, enum fe_sec_tone_mode tone)
{
/* u16 wz_half_period_for_45_mhz[] = { 0x01ff, 0x0154, 0x00ff, 0x00cc }; */
struct flexcop_device *fc = fe->dvb->priv;
flexcop_ibi_value v;
u16 ax;
v.raw = 0;
deb_tuner("tone = %u\n",tone);
switch (tone) {
case SEC_TONE_ON:
ax = 0x01ff;
break;
case SEC_TONE_OFF:
ax = 0;
break;
default:
err("unknown SEC_TONE value");
return -EINVAL;
}
v.lnb_switch_freq_200.LNB_CTLPrescaler_sig = 1; /* divide by 2 */
v.lnb_switch_freq_200.LNB_CTLHighCount_sig = ax;
v.lnb_switch_freq_200.LNB_CTLLowCount_sig = ax == 0 ? 0x1ff : ax;
return fc->write_ibi_reg(fc,lnb_switch_freq_200,v);
}
static void flexcop_diseqc_send_bit(struct dvb_frontend* fe, int data)
{
flexcop_set_tone(fe, SEC_TONE_ON);
udelay(data ? 500 : 1000);
flexcop_set_tone(fe, SEC_TONE_OFF);
udelay(data ? 1000 : 500);
}
static void flexcop_diseqc_send_byte(struct dvb_frontend* fe, int data)
{
int i, par = 1, d;
for (i = 7; i >= 0; i--) {
d = (data >> i) & 1;
par ^= d;
flexcop_diseqc_send_bit(fe, d);
}
flexcop_diseqc_send_bit(fe, par);
}
static int flexcop_send_diseqc_msg(struct dvb_frontend *fe,
int len, u8 *msg, unsigned long burst)
{
int i;
flexcop_set_tone(fe, SEC_TONE_OFF);
mdelay(16);
for (i = 0; i < len; i++)
flexcop_diseqc_send_byte(fe,msg[i]);
mdelay(16);
if (burst != -1) {
if (burst)
flexcop_diseqc_send_byte(fe, 0xff);
else {
flexcop_set_tone(fe, SEC_TONE_ON);
mdelay(12);
udelay(500);
flexcop_set_tone(fe, SEC_TONE_OFF);
}
msleep(20);
}
return 0;
}
static int flexcop_diseqc_send_master_cmd(struct dvb_frontend *fe,
struct dvb_diseqc_master_cmd *cmd)
{
return flexcop_send_diseqc_msg(fe, cmd->msg_len, cmd->msg, 0);
}
static int flexcop_diseqc_send_burst(struct dvb_frontend *fe,
enum fe_sec_mini_cmd minicmd)
{
return flexcop_send_diseqc_msg(fe, 0, NULL, minicmd);
}
static struct mt312_config skystar23_samsung_tbdu18132_config = {
.demod_address = 0x0e,
};
static int skystar2_rev23_attach(struct flexcop_device *fc,
struct i2c_adapter *i2c)
{
struct dvb_frontend_ops *ops;
fc->fe = dvb_attach(mt312_attach, &skystar23_samsung_tbdu18132_config, i2c);
if (!fc->fe)
return 0;
if (!dvb_attach(dvb_pll_attach, fc->fe, 0x61, i2c,
DVB_PLL_SAMSUNG_TBDU18132))
return 0;
ops = &fc->fe->ops;
ops->diseqc_send_master_cmd = flexcop_diseqc_send_master_cmd;
ops->diseqc_send_burst = flexcop_diseqc_send_burst;
ops->set_tone = flexcop_set_tone;
ops->set_voltage = flexcop_set_voltage;
fc->fe_sleep = ops->sleep;
ops->sleep = flexcop_sleep;
return 1;
}
#else
#define skystar2_rev23_attach NULL
#endif
/* SkyStar2 DVB-S rev 2.6 */
#if FE_SUPPORTED(STV0299) && FE_SUPPORTED(PLL)
static int samsung_tbmu24112_set_symbol_rate(struct dvb_frontend *fe,
u32 srate, u32 ratio)
{
u8 aclk = 0;
u8 bclk = 0;
if (srate < 1500000) {
aclk = 0xb7; bclk = 0x47;
} else if (srate < 3000000) {
aclk = 0xb7; bclk = 0x4b;
} else if (srate < 7000000) {
aclk = 0xb7; bclk = 0x4f;
} else if (srate < 14000000) {
aclk = 0xb7; bclk = 0x53;
} else if (srate < 30000000) {
aclk = 0xb6; bclk = 0x53;
} else if (srate < 45000000) {
aclk = 0xb4; bclk = 0x51;
}
stv0299_writereg(fe, 0x13, aclk);
stv0299_writereg(fe, 0x14, bclk);
stv0299_writereg(fe, 0x1f, (ratio >> 16) & 0xff);
stv0299_writereg(fe, 0x20, (ratio >> 8) & 0xff);
stv0299_writereg(fe, 0x21, ratio & 0xf0);
return 0;
}
static u8 samsung_tbmu24112_inittab[] = {
0x01, 0x15,
0x02, 0x30,
0x03, 0x00,
0x04, 0x7D,
0x05, 0x35,
0x06, 0x02,
0x07, 0x00,
0x08, 0xC3,
0x0C, 0x00,
0x0D, 0x81,
0x0E, 0x23,
0x0F, 0x12,
0x10, 0x7E,
0x11, 0x84,
0x12, 0xB9,
0x13, 0x88,
0x14, 0x89,
0x15, 0xC9,
0x16, 0x00,
0x17, 0x5C,
0x18, 0x00,
0x19, 0x00,
0x1A, 0x00,
0x1C, 0x00,
0x1D, 0x00,
0x1E, 0x00,
0x1F, 0x3A,
0x20, 0x2E,
0x21, 0x80,
0x22, 0xFF,
0x23, 0xC1,
0x28, 0x00,
0x29, 0x1E,
0x2A, 0x14,
0x2B, 0x0F,
0x2C, 0x09,
0x2D, 0x05,
0x31, 0x1F,
0x32, 0x19,
0x33, 0xFE,
0x34, 0x93,
0xff, 0xff,
};
static struct stv0299_config samsung_tbmu24112_config = {
.demod_address = 0x68,
.inittab = samsung_tbmu24112_inittab,
.mclk = 88000000UL,
.invert = 0,
.skip_reinit = 0,
.lock_output = STV0299_LOCKOUTPUT_LK,
.volt13_op0_op1 = STV0299_VOLT13_OP1,
.min_delay_ms = 100,
.set_symbol_rate = samsung_tbmu24112_set_symbol_rate,
};
static int skystar2_rev26_attach(struct flexcop_device *fc,
struct i2c_adapter *i2c)
{
fc->fe = dvb_attach(stv0299_attach, &samsung_tbmu24112_config, i2c);
if (!fc->fe)
return 0;
if (!dvb_attach(dvb_pll_attach, fc->fe, 0x61, i2c,
DVB_PLL_SAMSUNG_TBMU24112))
return 0;
fc->fe->ops.set_voltage = flexcop_set_voltage;
fc->fe_sleep = fc->fe->ops.sleep;
fc->fe->ops.sleep = flexcop_sleep;
return 1;
}
#else
#define skystar2_rev26_attach NULL
#endif
/* SkyStar2 DVB-S rev 2.7 */
#if FE_SUPPORTED(S5H1420) && FE_SUPPORTED(ISL6421) && FE_SUPPORTED(TUNER_ITD1000)
static struct s5h1420_config skystar2_rev2_7_s5h1420_config = {
.demod_address = 0x53,
.invert = 1,
.repeated_start_workaround = 1,
.serial_mpeg = 1,
};
static struct itd1000_config skystar2_rev2_7_itd1000_config = {
.i2c_address = 0x61,
};
static int skystar2_rev27_attach(struct flexcop_device *fc,
struct i2c_adapter *i2c)
{
flexcop_ibi_value r108;
struct i2c_adapter *i2c_tuner;
/* enable no_base_addr - no repeated start when reading */
fc->fc_i2c_adap[0].no_base_addr = 1;
fc->fe = dvb_attach(s5h1420_attach, &skystar2_rev2_7_s5h1420_config,
i2c);
if (!fc->fe)
goto fail;
i2c_tuner = s5h1420_get_tuner_i2c_adapter(fc->fe);
if (!i2c_tuner)
goto fail;
fc->fe_sleep = fc->fe->ops.sleep;
fc->fe->ops.sleep = flexcop_sleep;
/* enable no_base_addr - no repeated start when reading */
fc->fc_i2c_adap[2].no_base_addr = 1;
if (!dvb_attach(isl6421_attach, fc->fe, &fc->fc_i2c_adap[2].i2c_adap,
0x08, 1, 1, false)) {
err("ISL6421 could NOT be attached");
goto fail_isl;
}
info("ISL6421 successfully attached");
/* the ITD1000 requires a lower i2c clock - is it a problem ? */
r108.raw = 0x00000506;
fc->write_ibi_reg(fc, tw_sm_c_108, r108);
if (!dvb_attach(itd1000_attach, fc->fe, i2c_tuner,
&skystar2_rev2_7_itd1000_config)) {
err("ITD1000 could NOT be attached");
/* Should i2c clock be restored? */
goto fail_isl;
}
info("ITD1000 successfully attached");
return 1;
fail_isl:
fc->fc_i2c_adap[2].no_base_addr = 0;
fail:
/* for the next devices we need it again */
fc->fc_i2c_adap[0].no_base_addr = 0;
return 0;
}
#else
#define skystar2_rev27_attach NULL
#endif
/* SkyStar2 rev 2.8 */
#if FE_SUPPORTED(CX24123) && FE_SUPPORTED(ISL6421) && FE_SUPPORTED(TUNER_CX24113)
static struct cx24123_config skystar2_rev2_8_cx24123_config = {
.demod_address = 0x55,
.dont_use_pll = 1,
.agc_callback = cx24113_agc_callback,
};
static const struct cx24113_config skystar2_rev2_8_cx24113_config = {
.i2c_addr = 0x54,
.xtal_khz = 10111,
};
static int skystar2_rev28_attach(struct flexcop_device *fc,
struct i2c_adapter *i2c)
{
struct i2c_adapter *i2c_tuner;
fc->fe = dvb_attach(cx24123_attach, &skystar2_rev2_8_cx24123_config,
i2c);
if (!fc->fe)
return 0;
i2c_tuner = cx24123_get_tuner_i2c_adapter(fc->fe);
if (!i2c_tuner)
return 0;
if (!dvb_attach(cx24113_attach, fc->fe, &skystar2_rev2_8_cx24113_config,
i2c_tuner)) {
err("CX24113 could NOT be attached");
return 0;
}
info("CX24113 successfully attached");
fc->fc_i2c_adap[2].no_base_addr = 1;
if (!dvb_attach(isl6421_attach, fc->fe, &fc->fc_i2c_adap[2].i2c_adap,
0x08, 0, 0, false)) {
err("ISL6421 could NOT be attached");
fc->fc_i2c_adap[2].no_base_addr = 0;
return 0;
}
info("ISL6421 successfully attached");
/* TODO on i2c_adap[1] addr 0x11 (EEPROM) there seems to be an
* IR-receiver (PIC16F818) - but the card has no input for that ??? */
return 1;
}
#else
#define skystar2_rev28_attach NULL
#endif
/* AirStar DVB-T */
#if FE_SUPPORTED(MT352) && FE_SUPPORTED(PLL)
static int samsung_tdtc9251dh0_demod_init(struct dvb_frontend *fe)
{
static u8 mt352_clock_config[] = { 0x89, 0x18, 0x2d };
static u8 mt352_reset[] = { 0x50, 0x80 };
static u8 mt352_adc_ctl_1_cfg[] = { 0x8E, 0x40 };
static u8 mt352_agc_cfg[] = { 0x67, 0x28, 0xa1 };
static u8 mt352_capt_range_cfg[] = { 0x75, 0x32 };
mt352_write(fe, mt352_clock_config, sizeof(mt352_clock_config));
udelay(2000);
mt352_write(fe, mt352_reset, sizeof(mt352_reset));
mt352_write(fe, mt352_adc_ctl_1_cfg, sizeof(mt352_adc_ctl_1_cfg));
mt352_write(fe, mt352_agc_cfg, sizeof(mt352_agc_cfg));
mt352_write(fe, mt352_capt_range_cfg, sizeof(mt352_capt_range_cfg));
return 0;
}
static struct mt352_config samsung_tdtc9251dh0_config = {
.demod_address = 0x0f,
.demod_init = samsung_tdtc9251dh0_demod_init,
};
static int airstar_dvbt_attach(struct flexcop_device *fc,
struct i2c_adapter *i2c)
{
fc->fe = dvb_attach(mt352_attach, &samsung_tdtc9251dh0_config, i2c);
if (!fc->fe)
return 0;
return !!dvb_attach(dvb_pll_attach, fc->fe, 0x61, NULL,
DVB_PLL_SAMSUNG_TDTC9251DH0);
}
#else
#define airstar_dvbt_attach NULL
#endif
/* AirStar ATSC 1st generation */
#if FE_SUPPORTED(BCM3510)
static struct bcm3510_config air2pc_atsc_first_gen_config = {
.demod_address = 0x0f,
.request_firmware = flexcop_fe_request_firmware,
};
static int airstar_atsc1_attach(struct flexcop_device *fc,
struct i2c_adapter *i2c)
{
fc->fe = dvb_attach(bcm3510_attach, &air2pc_atsc_first_gen_config, i2c);
return fc->fe != NULL;
}
#else
#define airstar_atsc1_attach NULL
#endif
/* AirStar ATSC 2nd generation */
#if FE_SUPPORTED(NXT200X) && FE_SUPPORTED(PLL)
static const struct nxt200x_config samsung_tbmv_config = {
.demod_address = 0x0a,
};
static int airstar_atsc2_attach(struct flexcop_device *fc,
struct i2c_adapter *i2c)
{
fc->fe = dvb_attach(nxt200x_attach, &samsung_tbmv_config, i2c);
if (!fc->fe)
return 0;
return !!dvb_attach(dvb_pll_attach, fc->fe, 0x61, NULL,
DVB_PLL_SAMSUNG_TBMV);
}
#else
#define airstar_atsc2_attach NULL
#endif
/* AirStar ATSC 3rd generation */
#if FE_SUPPORTED(LGDT330X)
static struct lgdt330x_config air2pc_atsc_hd5000_config = {
.demod_chip = LGDT3303,
.serial_mpeg = 0x04,
.clock_polarity_flip = 1,
};
static int airstar_atsc3_attach(struct flexcop_device *fc,
struct i2c_adapter *i2c)
{
fc->fe = dvb_attach(lgdt330x_attach, &air2pc_atsc_hd5000_config,
0x59, i2c);
if (!fc->fe)
return 0;
return !!dvb_attach(simple_tuner_attach, fc->fe, i2c, 0x61,
TUNER_LG_TDVS_H06XF);
}
#else
#define airstar_atsc3_attach NULL
#endif
/* CableStar2 DVB-C */
#if FE_SUPPORTED(STV0297) && FE_SUPPORTED(PLL)
static u8 alps_tdee4_stv0297_inittab[] = {
0x80, 0x01,
0x80, 0x00,
0x81, 0x01,
0x81, 0x00,
0x00, 0x48,
0x01, 0x58,
0x03, 0x00,
0x04, 0x00,
0x07, 0x00,
0x08, 0x00,
0x30, 0xff,
0x31, 0x9d,
0x32, 0xff,
0x33, 0x00,
0x34, 0x29,
0x35, 0x55,
0x36, 0x80,
0x37, 0x6e,
0x38, 0x9c,
0x40, 0x1a,
0x41, 0xfe,
0x42, 0x33,
0x43, 0x00,
0x44, 0xff,
0x45, 0x00,
0x46, 0x00,
0x49, 0x04,
0x4a, 0x51,
0x4b, 0xf8,
0x52, 0x30,
0x53, 0x06,
0x59, 0x06,
0x5a, 0x5e,
0x5b, 0x04,
0x61, 0x49,
0x62, 0x0a,
0x70, 0xff,
0x71, 0x04,
0x72, 0x00,
0x73, 0x00,
0x74, 0x0c,
0x80, 0x20,
0x81, 0x00,
0x82, 0x30,
0x83, 0x00,
0x84, 0x04,
0x85, 0x22,
0x86, 0x08,
0x87, 0x1b,
0x88, 0x00,
0x89, 0x00,
0x90, 0x00,
0x91, 0x04,
0xa0, 0x86,
0xa1, 0x00,
0xa2, 0x00,
0xb0, 0x91,
0xb1, 0x0b,
0xc0, 0x5b,
0xc1, 0x10,
0xc2, 0x12,
0xd0, 0x02,
0xd1, 0x00,
0xd2, 0x00,
0xd3, 0x00,
0xd4, 0x02,
0xd5, 0x00,
0xde, 0x00,
0xdf, 0x01,
0xff, 0xff,
};
static struct stv0297_config alps_tdee4_stv0297_config = {
.demod_address = 0x1c,
.inittab = alps_tdee4_stv0297_inittab,
};
static int cablestar2_attach(struct flexcop_device *fc,
struct i2c_adapter *i2c)
{
fc->fc_i2c_adap[0].no_base_addr = 1;
fc->fe = dvb_attach(stv0297_attach, &alps_tdee4_stv0297_config, i2c);
if (!fc->fe)
goto fail;
/* This tuner doesn't use the stv0297's I2C gate, but instead the
* tuner is connected to a different flexcop I2C adapter. */
if (fc->fe->ops.i2c_gate_ctrl)
fc->fe->ops.i2c_gate_ctrl(fc->fe, 0);
fc->fe->ops.i2c_gate_ctrl = NULL;
if (!dvb_attach(dvb_pll_attach, fc->fe, 0x61,
&fc->fc_i2c_adap[2].i2c_adap, DVB_PLL_TDEE4))
goto fail;
return 1;
fail:
/* Reset for next frontend to try */
fc->fc_i2c_adap[0].no_base_addr = 0;
return 0;
}
#else
#define cablestar2_attach NULL
#endif
/* SkyStar S2 PCI DVB-S/S2 card based on Conexant cx24120/cx24118 */
#if FE_SUPPORTED(CX24120) && FE_SUPPORTED(ISL6421)
static const struct cx24120_config skystar2_rev3_3_cx24120_config = {
.i2c_addr = 0x55,
.xtal_khz = 10111,
.initial_mpeg_config = { 0xa1, 0x76, 0x07 },
.request_firmware = flexcop_fe_request_firmware,
.i2c_wr_max = 4,
};
static int skystarS2_rev33_attach(struct flexcop_device *fc,
struct i2c_adapter *i2c)
{
fc->fe = dvb_attach(cx24120_attach,
&skystar2_rev3_3_cx24120_config, i2c);
if (!fc->fe)
return 0;
fc->dev_type = FC_SKYS2_REV33;
fc->fc_i2c_adap[2].no_base_addr = 1;
if (!dvb_attach(isl6421_attach, fc->fe, &fc->fc_i2c_adap[2].i2c_adap,
0x08, 0, 0, false)) {
err("ISL6421 could NOT be attached!");
fc->fc_i2c_adap[2].no_base_addr = 0;
return 0;
}
info("ISL6421 successfully attached.");
if (fc->has_32_hw_pid_filter)
fc->skip_6_hw_pid_filter = 1;
return 1;
}
#else
#define skystarS2_rev33_attach NULL
#endif
static struct {
flexcop_device_type_t type;
int (*attach)(struct flexcop_device *, struct i2c_adapter *);
} flexcop_frontends[] = {
{ FC_SKY_REV27, skystar2_rev27_attach },
{ FC_SKY_REV28, skystar2_rev28_attach },
{ FC_SKY_REV26, skystar2_rev26_attach },
{ FC_AIR_DVBT, airstar_dvbt_attach },
{ FC_AIR_ATSC2, airstar_atsc2_attach },
{ FC_AIR_ATSC3, airstar_atsc3_attach },
{ FC_AIR_ATSC1, airstar_atsc1_attach },
{ FC_CABLE, cablestar2_attach },
{ FC_SKY_REV23, skystar2_rev23_attach },
{ FC_SKYS2_REV33, skystarS2_rev33_attach },
};
/* try to figure out the frontend */
int flexcop_frontend_init(struct flexcop_device *fc)
{
int i;
for (i = 0; i < ARRAY_SIZE(flexcop_frontends); i++) {
if (!flexcop_frontends[i].attach)
continue;
/* type needs to be set before, because of some workarounds
* done based on the probed card type */
fc->dev_type = flexcop_frontends[i].type;
if (flexcop_frontends[i].attach(fc, &fc->fc_i2c_adap[0].i2c_adap))
goto fe_found;
/* Clean up partially attached frontend */
if (fc->fe) {
dvb_frontend_detach(fc->fe);
fc->fe = NULL;
}
}
fc->dev_type = FC_UNK;
err("no frontend driver found for this B2C2/FlexCop adapter");
return -ENODEV;
fe_found:
info("found '%s' .", fc->fe->ops.info.name);
if (dvb_register_frontend(&fc->dvb_adapter, fc->fe)) {
err("frontend registration failed!");
dvb_frontend_detach(fc->fe);
fc->fe = NULL;
return -EINVAL;
}
fc->init_state |= FC_STATE_FE_INIT;
return 0;
}
void flexcop_frontend_exit(struct flexcop_device *fc)
{
if (fc->init_state & FC_STATE_FE_INIT) {
dvb_unregister_frontend(fc->fe);
dvb_frontend_detach(fc->fe);
}
fc->init_state &= ~FC_STATE_FE_INIT;
}
@@ -0,0 +1,245 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Linux driver for digital TV devices equipped with B2C2 FlexcopII(b)/III
* flexcop-hw-filter.c - pid and mac address filtering and control functions
* see flexcop.c for copyright information
*/
#include "flexcop.h"
static void flexcop_rcv_data_ctrl(struct flexcop_device *fc, int onoff)
{
flexcop_set_ibi_value(ctrl_208, Rcv_Data_sig, onoff);
deb_ts("rcv_data is now: '%s'\n", onoff ? "on" : "off");
}
void flexcop_smc_ctrl(struct flexcop_device *fc, int onoff)
{
flexcop_set_ibi_value(ctrl_208, SMC_Enable_sig, onoff);
}
static void flexcop_null_filter_ctrl(struct flexcop_device *fc, int onoff)
{
flexcop_set_ibi_value(ctrl_208, Null_filter_sig, onoff);
}
void flexcop_set_mac_filter(struct flexcop_device *fc, u8 mac[6])
{
flexcop_ibi_value v418, v41c;
v41c = fc->read_ibi_reg(fc, mac_address_41c);
v418.mac_address_418.MAC1 = mac[0];
v418.mac_address_418.MAC2 = mac[1];
v418.mac_address_418.MAC3 = mac[2];
v418.mac_address_418.MAC6 = mac[3];
v41c.mac_address_41c.MAC7 = mac[4];
v41c.mac_address_41c.MAC8 = mac[5];
fc->write_ibi_reg(fc, mac_address_418, v418);
fc->write_ibi_reg(fc, mac_address_41c, v41c);
}
void flexcop_mac_filter_ctrl(struct flexcop_device *fc, int onoff)
{
flexcop_set_ibi_value(ctrl_208, MAC_filter_Mode_sig, onoff);
}
static void flexcop_pid_group_filter(struct flexcop_device *fc,
u16 pid, u16 mask)
{
/* index_reg_310.extra_index_reg need to 0 or 7 to work */
flexcop_ibi_value v30c;
v30c.pid_filter_30c_ext_ind_0_7.Group_PID = pid;
v30c.pid_filter_30c_ext_ind_0_7.Group_mask = mask;
fc->write_ibi_reg(fc, pid_filter_30c, v30c);
}
static void flexcop_pid_group_filter_ctrl(struct flexcop_device *fc, int onoff)
{
flexcop_set_ibi_value(ctrl_208, Mask_filter_sig, onoff);
}
/* this fancy define reduces the code size of the quite similar PID controlling of
* the first 6 PIDs
*/
#define pid_ctrl(vregname,field,enablefield,trans_field,transval) \
flexcop_ibi_value vpid = fc->read_ibi_reg(fc, vregname), \
v208 = fc->read_ibi_reg(fc, ctrl_208); \
vpid.vregname.field = onoff ? pid : 0x1fff; \
vpid.vregname.trans_field = transval; \
v208.ctrl_208.enablefield = onoff; \
fc->write_ibi_reg(fc, vregname, vpid); \
fc->write_ibi_reg(fc, ctrl_208, v208)
static void flexcop_pid_Stream1_PID_ctrl(struct flexcop_device *fc,
u16 pid, int onoff)
{
pid_ctrl(pid_filter_300, Stream1_PID, Stream1_filter_sig,
Stream1_trans, 0);
}
static void flexcop_pid_Stream2_PID_ctrl(struct flexcop_device *fc,
u16 pid, int onoff)
{
pid_ctrl(pid_filter_300, Stream2_PID, Stream2_filter_sig,
Stream2_trans, 0);
}
static void flexcop_pid_PCR_PID_ctrl(struct flexcop_device *fc,
u16 pid, int onoff)
{
pid_ctrl(pid_filter_304, PCR_PID, PCR_filter_sig, PCR_trans, 0);
}
static void flexcop_pid_PMT_PID_ctrl(struct flexcop_device *fc,
u16 pid, int onoff)
{
pid_ctrl(pid_filter_304, PMT_PID, PMT_filter_sig, PMT_trans, 0);
}
static void flexcop_pid_EMM_PID_ctrl(struct flexcop_device *fc,
u16 pid, int onoff)
{
pid_ctrl(pid_filter_308, EMM_PID, EMM_filter_sig, EMM_trans, 0);
}
static void flexcop_pid_ECM_PID_ctrl(struct flexcop_device *fc,
u16 pid, int onoff)
{
pid_ctrl(pid_filter_308, ECM_PID, ECM_filter_sig, ECM_trans, 0);
}
static void flexcop_pid_control(struct flexcop_device *fc,
int index, u16 pid, int onoff)
{
if (pid == 0x2000)
return;
deb_ts("setting pid: %5d %04x at index %d '%s'\n",
pid, pid, index, onoff ? "on" : "off");
/* First 6 can be buggy - skip over them if option set */
if (fc->skip_6_hw_pid_filter)
index += 6;
/* We could use bit magic here to reduce source code size.
* I decided against it, but to use the real register names */
switch (index) {
case 0:
flexcop_pid_Stream1_PID_ctrl(fc, pid, onoff);
break;
case 1:
flexcop_pid_Stream2_PID_ctrl(fc, pid, onoff);
break;
case 2:
flexcop_pid_PCR_PID_ctrl(fc, pid, onoff);
break;
case 3:
flexcop_pid_PMT_PID_ctrl(fc, pid, onoff);
break;
case 4:
flexcop_pid_EMM_PID_ctrl(fc, pid, onoff);
break;
case 5:
flexcop_pid_ECM_PID_ctrl(fc, pid, onoff);
break;
default:
if (fc->has_32_hw_pid_filter && index < 38) {
flexcop_ibi_value vpid, vid;
/* set the index */
vid = fc->read_ibi_reg(fc, index_reg_310);
vid.index_reg_310.index_reg = index - 6;
fc->write_ibi_reg(fc, index_reg_310, vid);
vpid = fc->read_ibi_reg(fc, pid_n_reg_314);
vpid.pid_n_reg_314.PID = onoff ? pid : 0x1fff;
vpid.pid_n_reg_314.PID_enable_bit = onoff;
fc->write_ibi_reg(fc, pid_n_reg_314, vpid);
}
break;
}
}
static int flexcop_toggle_fullts_streaming(struct flexcop_device *fc, int onoff)
{
if (fc->fullts_streaming_state != onoff) {
deb_ts("%s full TS transfer\n",onoff ? "enabling" : "disabling");
flexcop_pid_group_filter(fc, 0, 0x1fe0 * (!onoff));
flexcop_pid_group_filter_ctrl(fc, onoff);
fc->fullts_streaming_state = onoff;
}
return 0;
}
int flexcop_pid_feed_control(struct flexcop_device *fc,
struct dvb_demux_feed *dvbdmxfeed, int onoff)
{
int max_pid_filter = 6;
max_pid_filter -= 6 * fc->skip_6_hw_pid_filter;
max_pid_filter += 32 * fc->has_32_hw_pid_filter;
fc->feedcount += onoff ? 1 : -1; /* the number of PIDs/Feed currently requested */
if (dvbdmxfeed->index >= max_pid_filter)
fc->extra_feedcount += onoff ? 1 : -1;
/* toggle complete-TS-streaming when:
* - pid_filtering is not enabled and it is the first or last feed requested
* - pid_filtering is enabled,
* - but the number of requested feeds is exceeded
* - or the requested pid is 0x2000 */
if (!fc->pid_filtering && fc->feedcount == onoff)
flexcop_toggle_fullts_streaming(fc, onoff);
if (fc->pid_filtering) {
flexcop_pid_control \
(fc, dvbdmxfeed->index, dvbdmxfeed->pid, onoff);
if (fc->extra_feedcount > 0)
flexcop_toggle_fullts_streaming(fc, 1);
else if (dvbdmxfeed->pid == 0x2000)
flexcop_toggle_fullts_streaming(fc, onoff);
else
flexcop_toggle_fullts_streaming(fc, 0);
}
/* if it was the first or last feed request change the stream-status */
if (fc->feedcount == onoff) {
flexcop_rcv_data_ctrl(fc, onoff);
if (fc->stream_control) /* device specific stream control */
fc->stream_control(fc, onoff);
/* feeding stopped -> reset the flexcop filter*/
if (onoff == 0) {
flexcop_reset_block_300(fc);
flexcop_hw_filter_init(fc);
}
}
return 0;
}
EXPORT_SYMBOL(flexcop_pid_feed_control);
void flexcop_hw_filter_init(struct flexcop_device *fc)
{
int i;
flexcop_ibi_value v;
int max_pid_filter = 6;
max_pid_filter -= 6 * fc->skip_6_hw_pid_filter;
max_pid_filter += 32 * fc->has_32_hw_pid_filter;
for (i = 0; i < max_pid_filter; i++)
flexcop_pid_control(fc, i, 0x1fff, 0);
flexcop_pid_group_filter(fc, 0, 0x1fe0);
flexcop_pid_group_filter_ctrl(fc, 0);
v = fc->read_ibi_reg(fc, pid_filter_308);
v.pid_filter_308.EMM_filter_4 = 1;
v.pid_filter_308.EMM_filter_6 = 0;
fc->write_ibi_reg(fc, pid_filter_308, v);
flexcop_null_filter_ctrl(fc, 1);
}
@@ -0,0 +1,280 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Linux driver for digital TV devices equipped with B2C2 FlexcopII(b)/III
* flexcop-i2c.c - flexcop internal 2Wire bus (I2C) and dvb i2c initialization
* see flexcop.c for copyright information
*/
#include "flexcop.h"
#define FC_MAX_I2C_RETRIES 100000
static int flexcop_i2c_operation(struct flexcop_device *fc,
flexcop_ibi_value *r100)
{
int i;
flexcop_ibi_value r;
r100->tw_sm_c_100.working_start = 1;
deb_i2c("r100 before: %08x\n",r100->raw);
fc->write_ibi_reg(fc, tw_sm_c_100, ibi_zero);
fc->write_ibi_reg(fc, tw_sm_c_100, *r100); /* initiating i2c operation */
for (i = 0; i < FC_MAX_I2C_RETRIES; i++) {
r = fc->read_ibi_reg(fc, tw_sm_c_100);
if (!r.tw_sm_c_100.no_base_addr_ack_error) {
if (r.tw_sm_c_100.st_done) {
*r100 = r;
deb_i2c("i2c success\n");
return 0;
}
} else {
deb_i2c("suffering from an i2c ack_error\n");
return -EREMOTEIO;
}
}
deb_i2c("tried %d times i2c operation, never finished or too many ack errors.\n",
i);
return -EREMOTEIO;
}
static int flexcop_i2c_read4(struct flexcop_i2c_adapter *i2c,
flexcop_ibi_value r100, u8 *buf)
{
flexcop_ibi_value r104;
int len = r100.tw_sm_c_100.total_bytes,
/* remember total_bytes is buflen-1 */
ret;
/* work-around to have CableStar2 and SkyStar2 rev 2.7 work
* correctly:
*
* the ITD1000 is behind an i2c-gate which closes automatically
* after an i2c-transaction the STV0297 needs 2 consecutive reads
* one with no_base_addr = 0 and one with 1
*
* those two work-arounds are conflictin: we check for the card
* type, it is set when probing the ITD1000 */
if (i2c->fc->dev_type == FC_SKY_REV27)
r100.tw_sm_c_100.no_base_addr_ack_error = i2c->no_base_addr;
ret = flexcop_i2c_operation(i2c->fc, &r100);
if (ret != 0) {
deb_i2c("Retrying operation\n");
r100.tw_sm_c_100.no_base_addr_ack_error = i2c->no_base_addr;
ret = flexcop_i2c_operation(i2c->fc, &r100);
}
if (ret != 0) {
deb_i2c("read failed. %d\n", ret);
return ret;
}
buf[0] = r100.tw_sm_c_100.data1_reg;
if (len > 0) {
r104 = i2c->fc->read_ibi_reg(i2c->fc, tw_sm_c_104);
deb_i2c("read: r100: %08x, r104: %08x\n", r100.raw, r104.raw);
/* there is at least one more byte, otherwise we wouldn't be here */
buf[1] = r104.tw_sm_c_104.data2_reg;
if (len > 1) buf[2] = r104.tw_sm_c_104.data3_reg;
if (len > 2) buf[3] = r104.tw_sm_c_104.data4_reg;
}
return 0;
}
static int flexcop_i2c_write4(struct flexcop_device *fc,
flexcop_ibi_value r100, u8 *buf)
{
flexcop_ibi_value r104;
int len = r100.tw_sm_c_100.total_bytes; /* remember total_bytes is buflen-1 */
r104.raw = 0;
/* there is at least one byte, otherwise we wouldn't be here */
r100.tw_sm_c_100.data1_reg = buf[0];
r104.tw_sm_c_104.data2_reg = len > 0 ? buf[1] : 0;
r104.tw_sm_c_104.data3_reg = len > 1 ? buf[2] : 0;
r104.tw_sm_c_104.data4_reg = len > 2 ? buf[3] : 0;
deb_i2c("write: r100: %08x, r104: %08x\n", r100.raw, r104.raw);
/* write the additional i2c data before doing the actual i2c operation */
fc->write_ibi_reg(fc, tw_sm_c_104, r104);
return flexcop_i2c_operation(fc, &r100);
}
int flexcop_i2c_request(struct flexcop_i2c_adapter *i2c,
flexcop_access_op_t op, u8 chipaddr,
u8 start_addr, u8 *buf, u16 size)
{
int ret;
int len = size;
u8 *p;
u8 addr = start_addr;
u16 bytes_to_transfer;
flexcop_ibi_value r100;
deb_i2c("port %d %s(%02x): register %02x, size: %d\n",
i2c->port,
op == FC_READ ? "rd" : "wr",
chipaddr, start_addr, size);
r100.raw = 0;
r100.tw_sm_c_100.chipaddr = chipaddr;
r100.tw_sm_c_100.twoWS_rw = op;
r100.tw_sm_c_100.twoWS_port_reg = i2c->port;
/* in that case addr is the only value ->
* we write it twice as baseaddr and val0
* BBTI is doing it like that for ISL6421 at least */
if (i2c->no_base_addr && len == 0 && op == FC_WRITE) {
buf = &start_addr;
len = 1;
}
p = buf;
while (len != 0) {
bytes_to_transfer = len > 4 ? 4 : len;
r100.tw_sm_c_100.total_bytes = bytes_to_transfer - 1;
r100.tw_sm_c_100.baseaddr = addr;
if (op == FC_READ)
ret = flexcop_i2c_read4(i2c, r100, p);
else
ret = flexcop_i2c_write4(i2c->fc, r100, p);
if (ret < 0)
return ret;
p += bytes_to_transfer;
addr += bytes_to_transfer;
len -= bytes_to_transfer;
}
deb_i2c_dump("port %d %s(%02x): register %02x: %*ph\n",
i2c->port,
op == FC_READ ? "rd" : "wr",
chipaddr, start_addr, size, buf);
return 0;
}
/* exported for PCI i2c */
EXPORT_SYMBOL(flexcop_i2c_request);
/* master xfer callback for demodulator */
static int flexcop_master_xfer(struct i2c_adapter *i2c_adap,
struct i2c_msg msgs[], int num)
{
struct flexcop_i2c_adapter *i2c = i2c_get_adapdata(i2c_adap);
int i, ret = 0;
/* Some drivers use 1 byte or 0 byte reads as probes, which this
* driver doesn't support. These probes will always fail, so this
* hack makes them always succeed. If one knew how, it would of
* course be better to actually do the read. */
if (num == 1 && msgs[0].flags == I2C_M_RD && msgs[0].len <= 1)
return 1;
if (mutex_lock_interruptible(&i2c->fc->i2c_mutex))
return -ERESTARTSYS;
for (i = 0; i < num; i++) {
/* reading */
if (i+1 < num && (msgs[i+1].flags == I2C_M_RD)) {
ret = i2c->fc->i2c_request(i2c, FC_READ, msgs[i].addr,
msgs[i].buf[0], msgs[i+1].buf,
msgs[i+1].len);
i++; /* skip the following message */
} else /* writing */
ret = i2c->fc->i2c_request(i2c, FC_WRITE, msgs[i].addr,
msgs[i].buf[0], &msgs[i].buf[1],
msgs[i].len - 1);
if (ret < 0) {
deb_i2c("i2c master_xfer failed");
break;
}
}
mutex_unlock(&i2c->fc->i2c_mutex);
if (ret == 0)
ret = num;
return ret;
}
static u32 flexcop_i2c_func(struct i2c_adapter *adapter)
{
return I2C_FUNC_I2C;
}
static const struct i2c_algorithm flexcop_algo = {
.master_xfer = flexcop_master_xfer,
.functionality = flexcop_i2c_func,
};
int flexcop_i2c_init(struct flexcop_device *fc)
{
int ret;
mutex_init(&fc->i2c_mutex);
fc->fc_i2c_adap[0].fc = fc;
fc->fc_i2c_adap[1].fc = fc;
fc->fc_i2c_adap[2].fc = fc;
fc->fc_i2c_adap[0].port = FC_I2C_PORT_DEMOD;
fc->fc_i2c_adap[1].port = FC_I2C_PORT_EEPROM;
fc->fc_i2c_adap[2].port = FC_I2C_PORT_TUNER;
strscpy(fc->fc_i2c_adap[0].i2c_adap.name, "B2C2 FlexCop I2C to demod",
sizeof(fc->fc_i2c_adap[0].i2c_adap.name));
strscpy(fc->fc_i2c_adap[1].i2c_adap.name, "B2C2 FlexCop I2C to eeprom",
sizeof(fc->fc_i2c_adap[1].i2c_adap.name));
strscpy(fc->fc_i2c_adap[2].i2c_adap.name, "B2C2 FlexCop I2C to tuner",
sizeof(fc->fc_i2c_adap[2].i2c_adap.name));
i2c_set_adapdata(&fc->fc_i2c_adap[0].i2c_adap, &fc->fc_i2c_adap[0]);
i2c_set_adapdata(&fc->fc_i2c_adap[1].i2c_adap, &fc->fc_i2c_adap[1]);
i2c_set_adapdata(&fc->fc_i2c_adap[2].i2c_adap, &fc->fc_i2c_adap[2]);
fc->fc_i2c_adap[0].i2c_adap.algo =
fc->fc_i2c_adap[1].i2c_adap.algo =
fc->fc_i2c_adap[2].i2c_adap.algo = &flexcop_algo;
fc->fc_i2c_adap[0].i2c_adap.algo_data =
fc->fc_i2c_adap[1].i2c_adap.algo_data =
fc->fc_i2c_adap[2].i2c_adap.algo_data = NULL;
fc->fc_i2c_adap[0].i2c_adap.dev.parent =
fc->fc_i2c_adap[1].i2c_adap.dev.parent =
fc->fc_i2c_adap[2].i2c_adap.dev.parent = fc->dev;
ret = i2c_add_adapter(&fc->fc_i2c_adap[0].i2c_adap);
if (ret < 0)
return ret;
ret = i2c_add_adapter(&fc->fc_i2c_adap[1].i2c_adap);
if (ret < 0)
goto adap_1_failed;
ret = i2c_add_adapter(&fc->fc_i2c_adap[2].i2c_adap);
if (ret < 0)
goto adap_2_failed;
fc->init_state |= FC_STATE_I2C_INIT;
return 0;
adap_2_failed:
i2c_del_adapter(&fc->fc_i2c_adap[1].i2c_adap);
adap_1_failed:
i2c_del_adapter(&fc->fc_i2c_adap[0].i2c_adap);
return ret;
}
void flexcop_i2c_exit(struct flexcop_device *fc)
{
if (fc->init_state & FC_STATE_I2C_INIT) {
i2c_del_adapter(&fc->fc_i2c_adap[2].i2c_adap);
i2c_del_adapter(&fc->fc_i2c_adap[1].i2c_adap);
i2c_del_adapter(&fc->fc_i2c_adap[0].i2c_adap);
}
fc->init_state &= ~FC_STATE_I2C_INIT;
}
@@ -0,0 +1,72 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Linux driver for digital TV devices equipped with B2C2 FlexcopII(b)/III
* flexcop-misc.c - miscellaneous functions
* see flexcop.c for copyright information
*/
#include "flexcop.h"
void flexcop_determine_revision(struct flexcop_device *fc)
{
flexcop_ibi_value v = fc->read_ibi_reg(fc,misc_204);
switch (v.misc_204.Rev_N_sig_revision_hi) {
case 0x2:
deb_info("found a FlexCopII.\n");
fc->rev = FLEXCOP_II;
break;
case 0x3:
deb_info("found a FlexCopIIb.\n");
fc->rev = FLEXCOP_IIB;
break;
case 0x0:
deb_info("found a FlexCopIII.\n");
fc->rev = FLEXCOP_III;
break;
default:
err("unknown FlexCop Revision: %x. Please report this to linux-dvb@linuxtv.org.",
v.misc_204.Rev_N_sig_revision_hi);
break;
}
if ((fc->has_32_hw_pid_filter = v.misc_204.Rev_N_sig_caps))
deb_info("this FlexCop has the additional 32 hardware pid filter.\n");
else
deb_info("this FlexCop has the 6 basic main hardware pid filter.\n");
/* bus parts have to decide if hw pid filtering is used or not. */
}
static const char *flexcop_revision_names[] = {
"Unknown chip",
"FlexCopII",
"FlexCopIIb",
"FlexCopIII",
};
static const char *flexcop_device_names[] = {
[FC_UNK] = "Unknown device",
[FC_CABLE] = "Cable2PC/CableStar 2 DVB-C",
[FC_AIR_DVBT] = "Air2PC/AirStar 2 DVB-T",
[FC_AIR_ATSC1] = "Air2PC/AirStar 2 ATSC 1st generation",
[FC_AIR_ATSC2] = "Air2PC/AirStar 2 ATSC 2nd generation",
[FC_AIR_ATSC3] = "Air2PC/AirStar 2 ATSC 3rd generation (HD5000)",
[FC_SKY_REV23] = "Sky2PC/SkyStar 2 DVB-S rev 2.3 (old version)",
[FC_SKY_REV26] = "Sky2PC/SkyStar 2 DVB-S rev 2.6",
[FC_SKY_REV27] = "Sky2PC/SkyStar 2 DVB-S rev 2.7a/u",
[FC_SKY_REV28] = "Sky2PC/SkyStar 2 DVB-S rev 2.8",
[FC_SKYS2_REV33] = "Sky2PC/SkyStar S2 DVB-S/S2 rev 3.3",
};
static const char *flexcop_bus_names[] = {
"USB",
"PCI",
};
void flexcop_device_name(struct flexcop_device *fc,
const char *prefix, const char *suffix)
{
info("%s '%s' at the '%s' bus controlled by a '%s' %s",
prefix, flexcop_device_names[fc->dev_type],
flexcop_bus_names[fc->bus_type],
flexcop_revision_names[fc->rev], suffix);
}
@@ -0,0 +1,168 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Linux driver for digital TV devices equipped with B2C2 FlexcopII(b)/III
* flexcop-reg.h - register abstraction for FlexCopII, FlexCopIIb and FlexCopIII
* see flexcop.c for copyright information
*/
#ifndef __FLEXCOP_REG_H__
#define __FLEXCOP_REG_H__
typedef enum {
FLEXCOP_UNK = 0,
FLEXCOP_II,
FLEXCOP_IIB,
FLEXCOP_III,
} flexcop_revision_t;
typedef enum {
FC_UNK = 0,
FC_CABLE,
FC_AIR_DVBT,
FC_AIR_ATSC1,
FC_AIR_ATSC2,
FC_AIR_ATSC3,
FC_SKY_REV23,
FC_SKY_REV26,
FC_SKY_REV27,
FC_SKY_REV28,
FC_SKYS2_REV33,
} flexcop_device_type_t;
typedef enum {
FC_USB = 0,
FC_PCI,
} flexcop_bus_t;
/* FlexCop IBI Registers */
#if defined(__LITTLE_ENDIAN)
#include "flexcop_ibi_value_le.h"
#else
#if defined(__BIG_ENDIAN)
#include "flexcop_ibi_value_be.h"
#else
#error no endian defined
#endif
#endif
#define fc_data_Tag_ID_DVB 0x3e
#define fc_data_Tag_ID_ATSC 0x3f
#define fc_data_Tag_ID_IDSB 0x8b
#define fc_key_code_default 0x1
#define fc_key_code_even 0x2
#define fc_key_code_odd 0x3
extern flexcop_ibi_value ibi_zero;
typedef enum {
FC_I2C_PORT_DEMOD = 1,
FC_I2C_PORT_EEPROM = 2,
FC_I2C_PORT_TUNER = 3,
} flexcop_i2c_port_t;
typedef enum {
FC_WRITE = 0,
FC_READ = 1,
} flexcop_access_op_t;
typedef enum {
FC_SRAM_DEST_NET = 1,
FC_SRAM_DEST_CAI = 2,
FC_SRAM_DEST_CAO = 4,
FC_SRAM_DEST_MEDIA = 8
} flexcop_sram_dest_t;
typedef enum {
FC_SRAM_DEST_TARGET_WAN_USB = 0,
FC_SRAM_DEST_TARGET_DMA1 = 1,
FC_SRAM_DEST_TARGET_DMA2 = 2,
FC_SRAM_DEST_TARGET_FC3_CA = 3
} flexcop_sram_dest_target_t;
typedef enum {
FC_SRAM_2_32KB = 0, /* 64KB */
FC_SRAM_1_32KB = 1, /* 32KB - default fow FCII */
FC_SRAM_1_128KB = 2, /* 128KB */
FC_SRAM_1_48KB = 3, /* 48KB - default for FCIII */
} flexcop_sram_type_t;
typedef enum {
FC_WAN_SPEED_4MBITS = 0,
FC_WAN_SPEED_8MBITS = 1,
FC_WAN_SPEED_12MBITS = 2,
FC_WAN_SPEED_16MBITS = 3,
} flexcop_wan_speed_t;
typedef enum {
FC_DMA_1 = 1,
FC_DMA_2 = 2,
} flexcop_dma_index_t;
typedef enum {
FC_DMA_SUBADDR_0 = 1,
FC_DMA_SUBADDR_1 = 2,
} flexcop_dma_addr_index_t;
/* names of the particular registers */
typedef enum {
dma1_000 = 0x000,
dma1_004 = 0x004,
dma1_008 = 0x008,
dma1_00c = 0x00c,
dma2_010 = 0x010,
dma2_014 = 0x014,
dma2_018 = 0x018,
dma2_01c = 0x01c,
tw_sm_c_100 = 0x100,
tw_sm_c_104 = 0x104,
tw_sm_c_108 = 0x108,
tw_sm_c_10c = 0x10c,
tw_sm_c_110 = 0x110,
lnb_switch_freq_200 = 0x200,
misc_204 = 0x204,
ctrl_208 = 0x208,
irq_20c = 0x20c,
sw_reset_210 = 0x210,
misc_214 = 0x214,
mbox_v8_to_host_218 = 0x218,
mbox_host_to_v8_21c = 0x21c,
pid_filter_300 = 0x300,
pid_filter_304 = 0x304,
pid_filter_308 = 0x308,
pid_filter_30c = 0x30c,
index_reg_310 = 0x310,
pid_n_reg_314 = 0x314,
mac_low_reg_318 = 0x318,
mac_high_reg_31c = 0x31c,
data_tag_400 = 0x400,
card_id_408 = 0x408,
card_id_40c = 0x40c,
mac_address_418 = 0x418,
mac_address_41c = 0x41c,
ci_600 = 0x600,
pi_604 = 0x604,
pi_608 = 0x608,
dvb_reg_60c = 0x60c,
sram_ctrl_reg_700 = 0x700,
net_buf_reg_704 = 0x704,
cai_buf_reg_708 = 0x708,
cao_buf_reg_70c = 0x70c,
media_buf_reg_710 = 0x710,
sram_dest_reg_714 = 0x714,
net_buf_reg_718 = 0x718,
wan_ctrl_reg_71c = 0x71c,
} flexcop_ibi_register;
#define flexcop_set_ibi_value(reg,attr,val) { \
flexcop_ibi_value v = fc->read_ibi_reg(fc,reg); \
v.reg.attr = val; \
fc->write_ibi_reg(fc,reg,v); \
}
#endif
@@ -0,0 +1,364 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Linux driver for digital TV devices equipped with B2C2 FlexcopII(b)/III
* flexcop-sram.c - functions for controlling the SRAM
* see flexcop.c for copyright information
*/
#include "flexcop.h"
static void flexcop_sram_set_chip(struct flexcop_device *fc,
flexcop_sram_type_t type)
{
flexcop_set_ibi_value(wan_ctrl_reg_71c, sram_chip, type);
}
int flexcop_sram_init(struct flexcop_device *fc)
{
switch (fc->rev) {
case FLEXCOP_II:
case FLEXCOP_IIB:
flexcop_sram_set_chip(fc, FC_SRAM_1_32KB);
break;
case FLEXCOP_III:
flexcop_sram_set_chip(fc, FC_SRAM_1_48KB);
break;
default:
return -EINVAL;
}
return 0;
}
int flexcop_sram_set_dest(struct flexcop_device *fc, flexcop_sram_dest_t dest,
flexcop_sram_dest_target_t target)
{
flexcop_ibi_value v;
v = fc->read_ibi_reg(fc, sram_dest_reg_714);
if (fc->rev != FLEXCOP_III && target == FC_SRAM_DEST_TARGET_FC3_CA) {
err("SRAM destination target to available on FlexCopII(b)\n");
return -EINVAL;
}
deb_sram("sram dest: %x target: %x\n", dest, target);
if (dest & FC_SRAM_DEST_NET)
v.sram_dest_reg_714.NET_Dest = target;
if (dest & FC_SRAM_DEST_CAI)
v.sram_dest_reg_714.CAI_Dest = target;
if (dest & FC_SRAM_DEST_CAO)
v.sram_dest_reg_714.CAO_Dest = target;
if (dest & FC_SRAM_DEST_MEDIA)
v.sram_dest_reg_714.MEDIA_Dest = target;
fc->write_ibi_reg(fc,sram_dest_reg_714,v);
udelay(1000); /* TODO delay really necessary */
return 0;
}
EXPORT_SYMBOL(flexcop_sram_set_dest);
void flexcop_wan_set_speed(struct flexcop_device *fc, flexcop_wan_speed_t s)
{
flexcop_set_ibi_value(wan_ctrl_reg_71c,wan_speed_sig,s);
}
EXPORT_SYMBOL(flexcop_wan_set_speed);
void flexcop_sram_ctrl(struct flexcop_device *fc, int usb_wan, int sramdma, int maximumfill)
{
flexcop_ibi_value v = fc->read_ibi_reg(fc,sram_dest_reg_714);
v.sram_dest_reg_714.ctrl_usb_wan = usb_wan;
v.sram_dest_reg_714.ctrl_sramdma = sramdma;
v.sram_dest_reg_714.ctrl_maximumfill = maximumfill;
fc->write_ibi_reg(fc,sram_dest_reg_714,v);
}
EXPORT_SYMBOL(flexcop_sram_ctrl);
#if 0
static void flexcop_sram_write(struct adapter *adapter, u32 bank, u32 addr, u8 *buf, u32 len)
{
int i, retries;
u32 command;
for (i = 0; i < len; i++) {
command = bank | addr | 0x04000000 | (*buf << 0x10);
retries = 2;
while (((read_reg_dw(adapter, 0x700) & 0x80000000) != 0) && (retries > 0)) {
mdelay(1);
retries--;
}
if (retries == 0)
printk("%s: SRAM timeout\n", __func__);
write_reg_dw(adapter, 0x700, command);
buf++;
addr++;
}
}
static void flex_sram_read(struct adapter *adapter, u32 bank, u32 addr, u8 *buf, u32 len)
{
int i, retries;
u32 command, value;
for (i = 0; i < len; i++) {
command = bank | addr | 0x04008000;
retries = 10000;
while (((read_reg_dw(adapter, 0x700) & 0x80000000) != 0) && (retries > 0)) {
mdelay(1);
retries--;
}
if (retries == 0)
printk("%s: SRAM timeout\n", __func__);
write_reg_dw(adapter, 0x700, command);
retries = 10000;
while (((read_reg_dw(adapter, 0x700) & 0x80000000) != 0) && (retries > 0)) {
mdelay(1);
retries--;
}
if (retries == 0)
printk("%s: SRAM timeout\n", __func__);
value = read_reg_dw(adapter, 0x700) >> 0x10;
*buf = (value & 0xff);
addr++;
buf++;
}
}
static void sram_write_chunk(struct adapter *adapter, u32 addr, u8 *buf, u16 len)
{
u32 bank;
bank = 0;
if (adapter->dw_sram_type == 0x20000) {
bank = (addr & 0x18000) << 0x0d;
}
if (adapter->dw_sram_type == 0x00000) {
if ((addr >> 0x0f) == 0)
bank = 0x20000000;
else
bank = 0x10000000;
}
flex_sram_write(adapter, bank, addr & 0x7fff, buf, len);
}
static void sram_read_chunk(struct adapter *adapter, u32 addr, u8 *buf, u16 len)
{
u32 bank;
bank = 0;
if (adapter->dw_sram_type == 0x20000) {
bank = (addr & 0x18000) << 0x0d;
}
if (adapter->dw_sram_type == 0x00000) {
if ((addr >> 0x0f) == 0)
bank = 0x20000000;
else
bank = 0x10000000;
}
flex_sram_read(adapter, bank, addr & 0x7fff, buf, len);
}
static void sram_read(struct adapter *adapter, u32 addr, u8 *buf, u32 len)
{
u32 length;
while (len != 0) {
length = len;
/* check if the address range belongs to the same
* 32K memory chip. If not, the data is read
* from one chip at a time */
if ((addr >> 0x0f) != ((addr + len - 1) >> 0x0f)) {
length = (((addr >> 0x0f) + 1) << 0x0f) - addr;
}
sram_read_chunk(adapter, addr, buf, length);
addr = addr + length;
buf = buf + length;
len = len - length;
}
}
static void sram_write(struct adapter *adapter, u32 addr, u8 *buf, u32 len)
{
u32 length;
while (len != 0) {
length = len;
/* check if the address range belongs to the same
* 32K memory chip. If not, the data is
* written to one chip at a time */
if ((addr >> 0x0f) != ((addr + len - 1) >> 0x0f)) {
length = (((addr >> 0x0f) + 1) << 0x0f) - addr;
}
sram_write_chunk(adapter, addr, buf, length);
addr = addr + length;
buf = buf + length;
len = len - length;
}
}
static void sram_set_size(struct adapter *adapter, u32 mask)
{
write_reg_dw(adapter, 0x71c,
(mask | (~0x30000 & read_reg_dw(adapter, 0x71c))));
}
static void sram_init(struct adapter *adapter)
{
u32 tmp;
tmp = read_reg_dw(adapter, 0x71c);
write_reg_dw(adapter, 0x71c, 1);
if (read_reg_dw(adapter, 0x71c) != 0) {
write_reg_dw(adapter, 0x71c, tmp);
adapter->dw_sram_type = tmp & 0x30000;
ddprintk("%s: dw_sram_type = %x\n", __func__, adapter->dw_sram_type);
} else {
adapter->dw_sram_type = 0x10000;
ddprintk("%s: dw_sram_type = %x\n", __func__, adapter->dw_sram_type);
}
}
static int sram_test_location(struct adapter *adapter, u32 mask, u32 addr)
{
u8 tmp1, tmp2;
dprintk("%s: mask = %x, addr = %x\n", __func__, mask, addr);
sram_set_size(adapter, mask);
sram_init(adapter);
tmp2 = 0xa5;
tmp1 = 0x4f;
sram_write(adapter, addr, &tmp2, 1);
sram_write(adapter, addr + 4, &tmp1, 1);
tmp2 = 0;
mdelay(20);
sram_read(adapter, addr, &tmp2, 1);
sram_read(adapter, addr, &tmp2, 1);
dprintk("%s: wrote 0xa5, read 0x%2x\n", __func__, tmp2);
if (tmp2 != 0xa5)
return 0;
tmp2 = 0x5a;
tmp1 = 0xf4;
sram_write(adapter, addr, &tmp2, 1);
sram_write(adapter, addr + 4, &tmp1, 1);
tmp2 = 0;
mdelay(20);
sram_read(adapter, addr, &tmp2, 1);
sram_read(adapter, addr, &tmp2, 1);
dprintk("%s: wrote 0x5a, read 0x%2x\n", __func__, tmp2);
if (tmp2 != 0x5a)
return 0;
return 1;
}
static u32 sram_length(struct adapter *adapter)
{
if (adapter->dw_sram_type == 0x10000)
return 32768; /* 32K */
if (adapter->dw_sram_type == 0x00000)
return 65536; /* 64K */
if (adapter->dw_sram_type == 0x20000)
return 131072; /* 128K */
return 32768; /* 32K */
}
/* FlexcopII can work with 32K, 64K or 128K of external SRAM memory.
- for 128K there are 4x32K chips at bank 0,1,2,3.
- for 64K there are 2x32K chips at bank 1,2.
- for 32K there is one 32K chip at bank 0.
FlexCop works only with one bank at a time. The bank is selected
by bits 28-29 of the 0x700 register.
bank 0 covers addresses 0x00000-0x07fff
bank 1 covers addresses 0x08000-0x0ffff
bank 2 covers addresses 0x10000-0x17fff
bank 3 covers addresses 0x18000-0x1ffff */
static int flexcop_sram_detect(struct flexcop_device *fc)
{
flexcop_ibi_value r208, r71c_0, vr71c_1;
r208 = fc->read_ibi_reg(fc, ctrl_208);
fc->write_ibi_reg(fc, ctrl_208, ibi_zero);
r71c_0 = fc->read_ibi_reg(fc, wan_ctrl_reg_71c);
write_reg_dw(adapter, 0x71c, 1);
tmp3 = read_reg_dw(adapter, 0x71c);
dprintk("%s: tmp3 = %x\n", __func__, tmp3);
write_reg_dw(adapter, 0x71c, tmp2);
// check for internal SRAM ???
tmp3--;
if (tmp3 != 0) {
sram_set_size(adapter, 0x10000);
sram_init(adapter);
write_reg_dw(adapter, 0x208, tmp);
dprintk("%s: sram size = 32K\n", __func__);
return 32;
}
if (sram_test_location(adapter, 0x20000, 0x18000) != 0) {
sram_set_size(adapter, 0x20000);
sram_init(adapter);
write_reg_dw(adapter, 0x208, tmp);
dprintk("%s: sram size = 128K\n", __func__);
return 128;
}
if (sram_test_location(adapter, 0x00000, 0x10000) != 0) {
sram_set_size(adapter, 0x00000);
sram_init(adapter);
write_reg_dw(adapter, 0x208, tmp);
dprintk("%s: sram size = 64K\n", __func__);
return 64;
}
if (sram_test_location(adapter, 0x10000, 0x00000) != 0) {
sram_set_size(adapter, 0x10000);
sram_init(adapter);
write_reg_dw(adapter, 0x208, tmp);
dprintk("%s: sram size = 32K\n", __func__);
return 32;
}
sram_set_size(adapter, 0x10000);
sram_init(adapter);
write_reg_dw(adapter, 0x208, tmp);
dprintk("%s: SRAM detection failed. Set to 32K\n", __func__);
return 0;
}
static void sll_detect_sram_size(struct adapter *adapter)
{
sram_detect_for_flex2(adapter);
}
#endif
@@ -0,0 +1,296 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
/*
* Linux driver for digital TV devices equipped with B2C2 FlexcopII(b)/III
* flexcop.c - main module part
* Copyright (C) 2004-9 Patrick Boettcher <patrick.boettcher@posteo.de>
* based on skystar2-driver Copyright (C) 2003 Vadim Catana, skystar@moldova.cc
*
* Acknowledgements:
* John Jurrius from BBTI, Inc. for extensive support
* with code examples and data books
* Bjarne Steinsbo, bjarne at steinsbo.com (some ideas for rewriting)
*
* Contributions to the skystar2-driver have been done by
* Vincenzo Di Massa, hawk.it at tiscalinet.it (several DiSEqC fixes)
* Roberto Ragusa, r.ragusa at libero.it (polishing, restyling the code)
* Uwe Bugla, uwe.bugla at gmx.de (doing tests, restyling code, writing docu)
* Niklas Peinecke, peinecke at gdv.uni-hannover.de (hardware pid/mac
* filtering)
*/
#include "flexcop.h"
#define DRIVER_NAME "B2C2 FlexcopII/II(b)/III digital TV receiver chip"
#define DRIVER_AUTHOR "Patrick Boettcher <patrick.boettcher@posteo.de"
#ifdef CONFIG_DVB_B2C2_FLEXCOP_DEBUG
#define DEBSTATUS ""
#else
#define DEBSTATUS " (debugging is not enabled)"
#endif
int b2c2_flexcop_debug;
EXPORT_SYMBOL_GPL(b2c2_flexcop_debug);
module_param_named(debug, b2c2_flexcop_debug, int, 0644);
MODULE_PARM_DESC(debug,
"set debug level (1=info,2=tuner,4=i2c,8=ts,16=sram,32=reg,64=i2cdump (|-able))."
DEBSTATUS);
#undef DEBSTATUS
DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
/* global zero for ibi values */
flexcop_ibi_value ibi_zero;
static int flexcop_dvb_start_feed(struct dvb_demux_feed *dvbdmxfeed)
{
struct flexcop_device *fc = dvbdmxfeed->demux->priv;
return flexcop_pid_feed_control(fc, dvbdmxfeed, 1);
}
static int flexcop_dvb_stop_feed(struct dvb_demux_feed *dvbdmxfeed)
{
struct flexcop_device *fc = dvbdmxfeed->demux->priv;
return flexcop_pid_feed_control(fc, dvbdmxfeed, 0);
}
static int flexcop_dvb_init(struct flexcop_device *fc)
{
int ret = dvb_register_adapter(&fc->dvb_adapter,
"FlexCop Digital TV device", fc->owner,
fc->dev, adapter_nr);
if (ret < 0) {
err("error registering DVB adapter");
return ret;
}
fc->dvb_adapter.priv = fc;
fc->demux.dmx.capabilities = (DMX_TS_FILTERING | DMX_SECTION_FILTERING
| DMX_MEMORY_BASED_FILTERING);
fc->demux.priv = fc;
fc->demux.filternum = fc->demux.feednum = FC_MAX_FEED;
fc->demux.start_feed = flexcop_dvb_start_feed;
fc->demux.stop_feed = flexcop_dvb_stop_feed;
fc->demux.write_to_decoder = NULL;
ret = dvb_dmx_init(&fc->demux);
if (ret < 0) {
err("dvb_dmx failed: error %d", ret);
goto err_dmx;
}
fc->hw_frontend.source = DMX_FRONTEND_0;
fc->dmxdev.filternum = fc->demux.feednum;
fc->dmxdev.demux = &fc->demux.dmx;
fc->dmxdev.capabilities = 0;
ret = dvb_dmxdev_init(&fc->dmxdev, &fc->dvb_adapter);
if (ret < 0) {
err("dvb_dmxdev_init failed: error %d", ret);
goto err_dmx_dev;
}
ret = fc->demux.dmx.add_frontend(&fc->demux.dmx, &fc->hw_frontend);
if (ret < 0) {
err("adding hw_frontend to dmx failed: error %d", ret);
goto err_dmx_add_hw_frontend;
}
fc->mem_frontend.source = DMX_MEMORY_FE;
ret = fc->demux.dmx.add_frontend(&fc->demux.dmx, &fc->mem_frontend);
if (ret < 0) {
err("adding mem_frontend to dmx failed: error %d", ret);
goto err_dmx_add_mem_frontend;
}
ret = fc->demux.dmx.connect_frontend(&fc->demux.dmx, &fc->hw_frontend);
if (ret < 0) {
err("connect frontend failed: error %d", ret);
goto err_connect_frontend;
}
ret = dvb_net_init(&fc->dvb_adapter, &fc->dvbnet, &fc->demux.dmx);
if (ret < 0) {
err("dvb_net_init failed: error %d", ret);
goto err_net;
}
fc->init_state |= FC_STATE_DVB_INIT;
return 0;
err_net:
fc->demux.dmx.disconnect_frontend(&fc->demux.dmx);
err_connect_frontend:
fc->demux.dmx.remove_frontend(&fc->demux.dmx, &fc->mem_frontend);
err_dmx_add_mem_frontend:
fc->demux.dmx.remove_frontend(&fc->demux.dmx, &fc->hw_frontend);
err_dmx_add_hw_frontend:
dvb_dmxdev_release(&fc->dmxdev);
err_dmx_dev:
dvb_dmx_release(&fc->demux);
err_dmx:
dvb_unregister_adapter(&fc->dvb_adapter);
return ret;
}
static void flexcop_dvb_exit(struct flexcop_device *fc)
{
if (fc->init_state & FC_STATE_DVB_INIT) {
dvb_net_release(&fc->dvbnet);
fc->demux.dmx.close(&fc->demux.dmx);
fc->demux.dmx.remove_frontend(&fc->demux.dmx,
&fc->mem_frontend);
fc->demux.dmx.remove_frontend(&fc->demux.dmx,
&fc->hw_frontend);
dvb_dmxdev_release(&fc->dmxdev);
dvb_dmx_release(&fc->demux);
dvb_unregister_adapter(&fc->dvb_adapter);
deb_info("deinitialized dvb stuff\n");
}
fc->init_state &= ~FC_STATE_DVB_INIT;
}
/* these methods are necessary to achieve the long-term-goal of hiding the
* struct flexcop_device from the bus-parts */
void flexcop_pass_dmx_data(struct flexcop_device *fc, u8 *buf, u32 len)
{
dvb_dmx_swfilter(&fc->demux, buf, len);
}
EXPORT_SYMBOL(flexcop_pass_dmx_data);
void flexcop_pass_dmx_packets(struct flexcop_device *fc, u8 *buf, u32 no)
{
dvb_dmx_swfilter_packets(&fc->demux, buf, no);
}
EXPORT_SYMBOL(flexcop_pass_dmx_packets);
static void flexcop_reset(struct flexcop_device *fc)
{
flexcop_ibi_value v210, v204;
/* reset the flexcop itself */
fc->write_ibi_reg(fc, ctrl_208, ibi_zero);
v210.raw = 0;
v210.sw_reset_210.reset_block_000 = 1;
v210.sw_reset_210.reset_block_100 = 1;
v210.sw_reset_210.reset_block_200 = 1;
v210.sw_reset_210.reset_block_300 = 1;
v210.sw_reset_210.reset_block_400 = 1;
v210.sw_reset_210.reset_block_500 = 1;
v210.sw_reset_210.reset_block_600 = 1;
v210.sw_reset_210.reset_block_700 = 1;
v210.sw_reset_210.Block_reset_enable = 0xb2;
v210.sw_reset_210.Special_controls = 0xc259;
fc->write_ibi_reg(fc, sw_reset_210, v210);
msleep(1);
/* reset the periphical devices */
v204 = fc->read_ibi_reg(fc, misc_204);
v204.misc_204.Per_reset_sig = 0;
fc->write_ibi_reg(fc, misc_204, v204);
msleep(1);
v204.misc_204.Per_reset_sig = 1;
fc->write_ibi_reg(fc, misc_204, v204);
}
void flexcop_reset_block_300(struct flexcop_device *fc)
{
flexcop_ibi_value v208_save = fc->read_ibi_reg(fc, ctrl_208),
v210 = fc->read_ibi_reg(fc, sw_reset_210);
deb_rdump("208: %08x, 210: %08x\n", v208_save.raw, v210.raw);
fc->write_ibi_reg(fc, ctrl_208, ibi_zero);
v210.sw_reset_210.reset_block_300 = 1;
v210.sw_reset_210.Block_reset_enable = 0xb2;
fc->write_ibi_reg(fc, sw_reset_210, v210);
fc->write_ibi_reg(fc, ctrl_208, v208_save);
}
struct flexcop_device *flexcop_device_kmalloc(size_t bus_specific_len)
{
void *bus;
struct flexcop_device *fc = kzalloc_obj(struct flexcop_device);
if (!fc) {
err("no memory");
return NULL;
}
bus = kzalloc(bus_specific_len, GFP_KERNEL);
if (!bus) {
err("no memory");
kfree(fc);
return NULL;
}
fc->bus_specific = bus;
return fc;
}
EXPORT_SYMBOL(flexcop_device_kmalloc);
void flexcop_device_kfree(struct flexcop_device *fc)
{
kfree(fc->bus_specific);
kfree(fc);
}
EXPORT_SYMBOL(flexcop_device_kfree);
int flexcop_device_initialize(struct flexcop_device *fc)
{
int ret;
ibi_zero.raw = 0;
flexcop_reset(fc);
flexcop_determine_revision(fc);
flexcop_sram_init(fc);
flexcop_hw_filter_init(fc);
flexcop_smc_ctrl(fc, 0);
ret = flexcop_dvb_init(fc);
if (ret)
goto error;
/* i2c has to be done before doing EEProm stuff -
* because the EEProm is accessed via i2c */
ret = flexcop_i2c_init(fc);
if (ret)
goto error;
/* do the MAC address reading after initializing the dvb_adapter */
if (fc->get_mac_addr(fc, 0) == 0) {
u8 *b = fc->dvb_adapter.proposed_mac;
info("MAC address = %pM", b);
flexcop_set_mac_filter(fc, b);
flexcop_mac_filter_ctrl(fc, 1);
} else
warn("reading of MAC address failed.\n");
ret = flexcop_frontend_init(fc);
if (ret)
goto error;
flexcop_device_name(fc, "initialization of", "complete");
return 0;
error:
flexcop_device_exit(fc);
return ret;
}
EXPORT_SYMBOL(flexcop_device_initialize);
void flexcop_device_exit(struct flexcop_device *fc)
{
flexcop_frontend_exit(fc);
flexcop_i2c_exit(fc);
flexcop_dvb_exit(fc);
}
EXPORT_SYMBOL(flexcop_device_exit);
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_NAME);
MODULE_LICENSE("GPL");
@@ -0,0 +1,31 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Linux driver for digital TV devices equipped with B2C2 FlexcopII(b)/III
* flexcop.h - private header file for all flexcop-chip-source files
* see flexcop.c for copyright information
*/
#ifndef __FLEXCOP_H__
#define __FLEXCOP_H__
#define FC_LOG_PREFIX "b2c2-flexcop"
#include "flexcop-common.h"
extern int b2c2_flexcop_debug;
/* debug */
#ifdef CONFIG_DVB_B2C2_FLEXCOP_DEBUG
#define dprintk(level, args...) \
do { if ((b2c2_flexcop_debug & (level))) printk(args); } while (0)
#else
#define dprintk(level, args...) no_printk(args)
#endif
#define deb_info(args...) dprintk(0x01, args)
#define deb_tuner(args...) dprintk(0x02, args)
#define deb_i2c(args...) dprintk(0x04, args)
#define deb_ts(args...) dprintk(0x08, args)
#define deb_sram(args...) dprintk(0x10, args)
#define deb_rdump(args...) dprintk(0x20, args)
#define deb_i2c_dump(args...) dprintk(0x40, args)
#endif
@@ -0,0 +1,456 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Linux driver for digital TV devices equipped with B2C2 FlexcopII(b)/III
* register descriptions
* see flexcop.c for copyright information
*/
/* This file is automatically generated, do not edit things here. */
#ifndef __FLEXCOP_IBI_VALUE_INCLUDED__
#define __FLEXCOP_IBI_VALUE_INCLUDED__
typedef union {
u32 raw;
struct {
u32 dma_address0 :30;
u32 dma_0No_update : 1;
u32 dma_0start : 1;
} dma_0x0;
struct {
u32 dma_addr_size :24;
u32 DMA_maxpackets : 8;
} dma_0x4_remap;
struct {
u32 dma_addr_size :24;
u32 unused : 1;
u32 dma1timer : 7;
} dma_0x4_read;
struct {
u32 dma_addr_size :24;
u32 dmatimer : 7;
u32 unused : 1;
} dma_0x4_write;
struct {
u32 dma_cur_addr :30;
u32 unused : 2;
} dma_0x8;
struct {
u32 dma_address1 :30;
u32 remap_enable : 1;
u32 dma_1start : 1;
} dma_0xc;
struct {
u32 st_done : 1;
u32 no_base_addr_ack_error : 1;
u32 twoWS_port_reg : 2;
u32 total_bytes : 2;
u32 twoWS_rw : 1;
u32 working_start : 1;
u32 data1_reg : 8;
u32 baseaddr : 8;
u32 reserved1 : 1;
u32 chipaddr : 7;
} tw_sm_c_100;
struct {
u32 unused : 6;
u32 force_stop : 1;
u32 exlicit_stops : 1;
u32 data4_reg : 8;
u32 data3_reg : 8;
u32 data2_reg : 8;
} tw_sm_c_104;
struct {
u32 reserved2 :19;
u32 tlo1 : 5;
u32 reserved1 : 2;
u32 thi1 : 6;
} tw_sm_c_108;
struct {
u32 reserved2 :19;
u32 tlo1 : 5;
u32 reserved1 : 2;
u32 thi1 : 6;
} tw_sm_c_10c;
struct {
u32 reserved2 :19;
u32 tlo1 : 5;
u32 reserved1 : 2;
u32 thi1 : 6;
} tw_sm_c_110;
struct {
u32 LNB_CTLPrescaler_sig : 2;
u32 LNB_CTLLowCount_sig :15;
u32 LNB_CTLHighCount_sig :15;
} lnb_switch_freq_200;
struct {
u32 Rev_N_sig_reserved2 : 1;
u32 Rev_N_sig_caps : 1;
u32 Rev_N_sig_reserved1 : 2;
u32 Rev_N_sig_revision_hi : 4;
u32 reserved :20;
u32 Per_reset_sig : 1;
u32 LNB_L_H_sig : 1;
u32 ACPI3_sig : 1;
u32 ACPI1_sig : 1;
} misc_204;
struct {
u32 unused : 9;
u32 Mailbox_from_V8_Enable_sig : 1;
u32 DMA2_Size_IRQ_Enable_sig : 1;
u32 DMA1_Size_IRQ_Enable_sig : 1;
u32 DMA2_Timer_Enable_sig : 1;
u32 DMA2_IRQ_Enable_sig : 1;
u32 DMA1_Timer_Enable_sig : 1;
u32 DMA1_IRQ_Enable_sig : 1;
u32 Rcv_Data_sig : 1;
u32 MAC_filter_Mode_sig : 1;
u32 Multi2_Enable_sig : 1;
u32 Per_CA_Enable_sig : 1;
u32 SMC_Enable_sig : 1;
u32 CA_Enable_sig : 1;
u32 WAN_CA_Enable_sig : 1;
u32 WAN_Enable_sig : 1;
u32 Mask_filter_sig : 1;
u32 Null_filter_sig : 1;
u32 ECM_filter_sig : 1;
u32 EMM_filter_sig : 1;
u32 PMT_filter_sig : 1;
u32 PCR_filter_sig : 1;
u32 Stream2_filter_sig : 1;
u32 Stream1_filter_sig : 1;
} ctrl_208;
struct {
u32 reserved :21;
u32 Transport_Error : 1;
u32 LLC_SNAP_FLAG_set : 1;
u32 Continuity_error_flag : 1;
u32 Data_receiver_error : 1;
u32 Mailbox_from_V8_Status_sig : 1;
u32 DMA2_Size_IRQ_Status : 1;
u32 DMA1_Size_IRQ_Status : 1;
u32 DMA2_Timer_Status : 1;
u32 DMA2_IRQ_Status : 1;
u32 DMA1_Timer_Status : 1;
u32 DMA1_IRQ_Status : 1;
} irq_20c;
struct {
u32 Special_controls :16;
u32 Block_reset_enable : 8;
u32 reset_block_700 : 1;
u32 reset_block_600 : 1;
u32 reset_block_500 : 1;
u32 reset_block_400 : 1;
u32 reset_block_300 : 1;
u32 reset_block_200 : 1;
u32 reset_block_100 : 1;
u32 reset_block_000 : 1;
} sw_reset_210;
struct {
u32 unused2 :20;
u32 polarity_PS_ERR_sig : 1;
u32 polarity_PS_SYNC_sig : 1;
u32 polarity_PS_VALID_sig : 1;
u32 polarity_PS_CLK_sig : 1;
u32 unused1 : 3;
u32 s2p_sel_sig : 1;
u32 section_pkg_enable_sig : 1;
u32 halt_V8_sig : 1;
u32 v2WS_oe_sig : 1;
u32 vuart_oe_sig : 1;
} misc_214;
struct {
u32 Mailbox_from_V8 :32;
} mbox_v8_to_host_218;
struct {
u32 sysramaccess_busmuster : 1;
u32 sysramaccess_write : 1;
u32 unused : 7;
u32 sysramaccess_addr :15;
u32 sysramaccess_data : 8;
} mbox_host_to_v8_21c;
struct {
u32 debug_fifo_problem : 1;
u32 debug_flag_write_status00 : 1;
u32 Stream2_trans : 1;
u32 Stream2_PID :13;
u32 debug_flag_pid_saved : 1;
u32 MAC_Multicast_filter : 1;
u32 Stream1_trans : 1;
u32 Stream1_PID :13;
} pid_filter_300;
struct {
u32 reserved : 2;
u32 PMT_trans : 1;
u32 PMT_PID :13;
u32 debug_overrun2 : 1;
u32 debug_overrun3 : 1;
u32 PCR_trans : 1;
u32 PCR_PID :13;
} pid_filter_304;
struct {
u32 reserved : 2;
u32 ECM_trans : 1;
u32 ECM_PID :13;
u32 EMM_filter_6 : 1;
u32 EMM_filter_4 : 1;
u32 EMM_trans : 1;
u32 EMM_PID :13;
} pid_filter_308;
struct {
u32 unused2 : 3;
u32 Group_mask :13;
u32 unused1 : 2;
u32 Group_trans : 1;
u32 Group_PID :13;
} pid_filter_30c_ext_ind_0_7;
struct {
u32 unused :15;
u32 net_master_read :17;
} pid_filter_30c_ext_ind_1;
struct {
u32 unused :15;
u32 net_master_write :17;
} pid_filter_30c_ext_ind_2;
struct {
u32 unused :15;
u32 next_net_master_write :17;
} pid_filter_30c_ext_ind_3;
struct {
u32 reserved2 : 5;
u32 stack_read :10;
u32 reserved1 : 6;
u32 state_write :10;
u32 unused1 : 1;
} pid_filter_30c_ext_ind_4;
struct {
u32 unused :22;
u32 stack_cnt :10;
} pid_filter_30c_ext_ind_5;
struct {
u32 unused : 4;
u32 data_size_reg :12;
u32 write_status4 : 2;
u32 write_status1 : 2;
u32 pid_fsm_save_reg300 : 2;
u32 pid_fsm_save_reg4 : 2;
u32 pid_fsm_save_reg3 : 2;
u32 pid_fsm_save_reg2 : 2;
u32 pid_fsm_save_reg1 : 2;
u32 pid_fsm_save_reg0 : 2;
} pid_filter_30c_ext_ind_6;
struct {
u32 unused :22;
u32 pass_alltables : 1;
u32 AB_select : 1;
u32 extra_index_reg : 3;
u32 index_reg : 5;
} index_reg_310;
struct {
u32 reserved :17;
u32 PID_enable_bit : 1;
u32 PID_trans : 1;
u32 PID :13;
} pid_n_reg_314;
struct {
u32 reserved : 6;
u32 HighAB_bit : 1;
u32 Enable_bit : 1;
u32 A6_byte : 8;
u32 A5_byte : 8;
u32 A4_byte : 8;
} mac_low_reg_318;
struct {
u32 reserved : 8;
u32 A3_byte : 8;
u32 A2_byte : 8;
u32 A1_byte : 8;
} mac_high_reg_31c;
struct {
u32 data_Tag_ID :16;
u32 reserved :16;
} data_tag_400;
struct {
u32 Card_IDbyte3 : 8;
u32 Card_IDbyte4 : 8;
u32 Card_IDbyte5 : 8;
u32 Card_IDbyte6 : 8;
} card_id_408;
struct {
u32 Card_IDbyte1 : 8;
u32 Card_IDbyte2 : 8;
} card_id_40c;
struct {
u32 MAC6 : 8;
u32 MAC3 : 8;
u32 MAC2 : 8;
u32 MAC1 : 8;
} mac_address_418;
struct {
u32 reserved :16;
u32 MAC8 : 8;
u32 MAC7 : 8;
} mac_address_41c;
struct {
u32 reserved :21;
u32 txbuffempty : 1;
u32 ReceiveByteFrameError : 1;
u32 ReceiveDataReady : 1;
u32 transmitter_data_byte : 8;
} ci_600;
struct {
u32 pi_component_reg : 3;
u32 pi_rw : 1;
u32 pi_ha :20;
u32 pi_d : 8;
} pi_604;
struct {
u32 pi_busy_n : 1;
u32 pi_wait_n : 1;
u32 pi_timeout_status : 1;
u32 pi_CiMax_IRQ_n : 1;
u32 config_cclk : 1;
u32 config_cs_n : 1;
u32 config_wr_n : 1;
u32 config_Prog_n : 1;
u32 config_Init_stat : 1;
u32 config_Done_stat : 1;
u32 pcmcia_b_mod_pwr_n : 1;
u32 pcmcia_a_mod_pwr_n : 1;
u32 reserved : 3;
u32 Timer_addr : 5;
u32 unused : 1;
u32 timer_data : 7;
u32 Timer_Load_req : 1;
u32 Timer_Read_req : 1;
u32 oncecycle_read : 1;
u32 serialReset : 1;
} pi_608;
struct {
u32 reserved : 6;
u32 rw_flag : 1;
u32 dvb_en : 1;
u32 key_array_row : 5;
u32 key_array_col : 3;
u32 key_code : 2;
u32 key_enable : 1;
u32 PID :13;
} dvb_reg_60c;
struct {
u32 start_sram_ibi : 1;
u32 reserved2 : 1;
u32 ce_pin_reg : 1;
u32 oe_pin_reg : 1;
u32 reserved1 : 3;
u32 sc_xfer_bit : 1;
u32 sram_data : 8;
u32 sram_rw : 1;
u32 sram_addr :15;
} sram_ctrl_reg_700;
struct {
u32 net_addr_write :16;
u32 net_addr_read :16;
} net_buf_reg_704;
struct {
u32 cai_cnt : 4;
u32 reserved2 : 6;
u32 cai_write :11;
u32 reserved1 : 5;
u32 cai_read :11;
} cai_buf_reg_708;
struct {
u32 cao_cnt : 4;
u32 reserved2 : 6;
u32 cap_write :11;
u32 reserved1 : 5;
u32 cao_read :11;
} cao_buf_reg_70c;
struct {
u32 media_cnt : 4;
u32 reserved2 : 6;
u32 media_write :11;
u32 reserved1 : 5;
u32 media_read :11;
} media_buf_reg_710;
struct {
u32 reserved :17;
u32 ctrl_maximumfill : 1;
u32 ctrl_sramdma : 1;
u32 ctrl_usb_wan : 1;
u32 cao_ovflow_error : 1;
u32 cai_ovflow_error : 1;
u32 media_ovflow_error : 1;
u32 net_ovflow_error : 1;
u32 MEDIA_Dest : 2;
u32 CAO_Dest : 2;
u32 CAI_Dest : 2;
u32 NET_Dest : 2;
} sram_dest_reg_714;
struct {
u32 reserved3 :11;
u32 net_addr_write : 1;
u32 reserved2 : 3;
u32 net_addr_read : 1;
u32 reserved1 : 4;
u32 net_cnt :12;
} net_buf_reg_718;
struct {
u32 reserved3 : 4;
u32 wan_pkt_frame : 4;
u32 reserved2 : 4;
u32 sram_memmap : 2;
u32 sram_chip : 2;
u32 wan_wait_state : 8;
u32 reserved1 : 6;
u32 wan_speed_sig : 2;
} wan_ctrl_reg_71c;
} flexcop_ibi_value;
#endif
@@ -0,0 +1,456 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Linux driver for digital TV devices equipped with B2C2 FlexcopII(b)/III
* register descriptions
* see flexcop.c for copyright information
*/
/* This file is automatically generated, do not edit things here. */
#ifndef __FLEXCOP_IBI_VALUE_INCLUDED__
#define __FLEXCOP_IBI_VALUE_INCLUDED__
typedef union {
u32 raw;
struct {
u32 dma_0start : 1;
u32 dma_0No_update : 1;
u32 dma_address0 :30;
} dma_0x0;
struct {
u32 DMA_maxpackets : 8;
u32 dma_addr_size :24;
} dma_0x4_remap;
struct {
u32 dma1timer : 7;
u32 unused : 1;
u32 dma_addr_size :24;
} dma_0x4_read;
struct {
u32 unused : 1;
u32 dmatimer : 7;
u32 dma_addr_size :24;
} dma_0x4_write;
struct {
u32 unused : 2;
u32 dma_cur_addr :30;
} dma_0x8;
struct {
u32 dma_1start : 1;
u32 remap_enable : 1;
u32 dma_address1 :30;
} dma_0xc;
struct {
u32 chipaddr : 7;
u32 reserved1 : 1;
u32 baseaddr : 8;
u32 data1_reg : 8;
u32 working_start : 1;
u32 twoWS_rw : 1;
u32 total_bytes : 2;
u32 twoWS_port_reg : 2;
u32 no_base_addr_ack_error : 1;
u32 st_done : 1;
} tw_sm_c_100;
struct {
u32 data2_reg : 8;
u32 data3_reg : 8;
u32 data4_reg : 8;
u32 exlicit_stops : 1;
u32 force_stop : 1;
u32 unused : 6;
} tw_sm_c_104;
struct {
u32 thi1 : 6;
u32 reserved1 : 2;
u32 tlo1 : 5;
u32 reserved2 :19;
} tw_sm_c_108;
struct {
u32 thi1 : 6;
u32 reserved1 : 2;
u32 tlo1 : 5;
u32 reserved2 :19;
} tw_sm_c_10c;
struct {
u32 thi1 : 6;
u32 reserved1 : 2;
u32 tlo1 : 5;
u32 reserved2 :19;
} tw_sm_c_110;
struct {
u32 LNB_CTLHighCount_sig :15;
u32 LNB_CTLLowCount_sig :15;
u32 LNB_CTLPrescaler_sig : 2;
} lnb_switch_freq_200;
struct {
u32 ACPI1_sig : 1;
u32 ACPI3_sig : 1;
u32 LNB_L_H_sig : 1;
u32 Per_reset_sig : 1;
u32 reserved :20;
u32 Rev_N_sig_revision_hi : 4;
u32 Rev_N_sig_reserved1 : 2;
u32 Rev_N_sig_caps : 1;
u32 Rev_N_sig_reserved2 : 1;
} misc_204;
struct {
u32 Stream1_filter_sig : 1;
u32 Stream2_filter_sig : 1;
u32 PCR_filter_sig : 1;
u32 PMT_filter_sig : 1;
u32 EMM_filter_sig : 1;
u32 ECM_filter_sig : 1;
u32 Null_filter_sig : 1;
u32 Mask_filter_sig : 1;
u32 WAN_Enable_sig : 1;
u32 WAN_CA_Enable_sig : 1;
u32 CA_Enable_sig : 1;
u32 SMC_Enable_sig : 1;
u32 Per_CA_Enable_sig : 1;
u32 Multi2_Enable_sig : 1;
u32 MAC_filter_Mode_sig : 1;
u32 Rcv_Data_sig : 1;
u32 DMA1_IRQ_Enable_sig : 1;
u32 DMA1_Timer_Enable_sig : 1;
u32 DMA2_IRQ_Enable_sig : 1;
u32 DMA2_Timer_Enable_sig : 1;
u32 DMA1_Size_IRQ_Enable_sig : 1;
u32 DMA2_Size_IRQ_Enable_sig : 1;
u32 Mailbox_from_V8_Enable_sig : 1;
u32 unused : 9;
} ctrl_208;
struct {
u32 DMA1_IRQ_Status : 1;
u32 DMA1_Timer_Status : 1;
u32 DMA2_IRQ_Status : 1;
u32 DMA2_Timer_Status : 1;
u32 DMA1_Size_IRQ_Status : 1;
u32 DMA2_Size_IRQ_Status : 1;
u32 Mailbox_from_V8_Status_sig : 1;
u32 Data_receiver_error : 1;
u32 Continuity_error_flag : 1;
u32 LLC_SNAP_FLAG_set : 1;
u32 Transport_Error : 1;
u32 reserved :21;
} irq_20c;
struct {
u32 reset_block_000 : 1;
u32 reset_block_100 : 1;
u32 reset_block_200 : 1;
u32 reset_block_300 : 1;
u32 reset_block_400 : 1;
u32 reset_block_500 : 1;
u32 reset_block_600 : 1;
u32 reset_block_700 : 1;
u32 Block_reset_enable : 8;
u32 Special_controls :16;
} sw_reset_210;
struct {
u32 vuart_oe_sig : 1;
u32 v2WS_oe_sig : 1;
u32 halt_V8_sig : 1;
u32 section_pkg_enable_sig : 1;
u32 s2p_sel_sig : 1;
u32 unused1 : 3;
u32 polarity_PS_CLK_sig : 1;
u32 polarity_PS_VALID_sig : 1;
u32 polarity_PS_SYNC_sig : 1;
u32 polarity_PS_ERR_sig : 1;
u32 unused2 :20;
} misc_214;
struct {
u32 Mailbox_from_V8 :32;
} mbox_v8_to_host_218;
struct {
u32 sysramaccess_data : 8;
u32 sysramaccess_addr :15;
u32 unused : 7;
u32 sysramaccess_write : 1;
u32 sysramaccess_busmuster : 1;
} mbox_host_to_v8_21c;
struct {
u32 Stream1_PID :13;
u32 Stream1_trans : 1;
u32 MAC_Multicast_filter : 1;
u32 debug_flag_pid_saved : 1;
u32 Stream2_PID :13;
u32 Stream2_trans : 1;
u32 debug_flag_write_status00 : 1;
u32 debug_fifo_problem : 1;
} pid_filter_300;
struct {
u32 PCR_PID :13;
u32 PCR_trans : 1;
u32 debug_overrun3 : 1;
u32 debug_overrun2 : 1;
u32 PMT_PID :13;
u32 PMT_trans : 1;
u32 reserved : 2;
} pid_filter_304;
struct {
u32 EMM_PID :13;
u32 EMM_trans : 1;
u32 EMM_filter_4 : 1;
u32 EMM_filter_6 : 1;
u32 ECM_PID :13;
u32 ECM_trans : 1;
u32 reserved : 2;
} pid_filter_308;
struct {
u32 Group_PID :13;
u32 Group_trans : 1;
u32 unused1 : 2;
u32 Group_mask :13;
u32 unused2 : 3;
} pid_filter_30c_ext_ind_0_7;
struct {
u32 net_master_read :17;
u32 unused :15;
} pid_filter_30c_ext_ind_1;
struct {
u32 net_master_write :17;
u32 unused :15;
} pid_filter_30c_ext_ind_2;
struct {
u32 next_net_master_write :17;
u32 unused :15;
} pid_filter_30c_ext_ind_3;
struct {
u32 unused1 : 1;
u32 state_write :10;
u32 reserved1 : 6;
u32 stack_read :10;
u32 reserved2 : 5;
} pid_filter_30c_ext_ind_4;
struct {
u32 stack_cnt :10;
u32 unused :22;
} pid_filter_30c_ext_ind_5;
struct {
u32 pid_fsm_save_reg0 : 2;
u32 pid_fsm_save_reg1 : 2;
u32 pid_fsm_save_reg2 : 2;
u32 pid_fsm_save_reg3 : 2;
u32 pid_fsm_save_reg4 : 2;
u32 pid_fsm_save_reg300 : 2;
u32 write_status1 : 2;
u32 write_status4 : 2;
u32 data_size_reg :12;
u32 unused : 4;
} pid_filter_30c_ext_ind_6;
struct {
u32 index_reg : 5;
u32 extra_index_reg : 3;
u32 AB_select : 1;
u32 pass_alltables : 1;
u32 unused :22;
} index_reg_310;
struct {
u32 PID :13;
u32 PID_trans : 1;
u32 PID_enable_bit : 1;
u32 reserved :17;
} pid_n_reg_314;
struct {
u32 A4_byte : 8;
u32 A5_byte : 8;
u32 A6_byte : 8;
u32 Enable_bit : 1;
u32 HighAB_bit : 1;
u32 reserved : 6;
} mac_low_reg_318;
struct {
u32 A1_byte : 8;
u32 A2_byte : 8;
u32 A3_byte : 8;
u32 reserved : 8;
} mac_high_reg_31c;
struct {
u32 reserved :16;
u32 data_Tag_ID :16;
} data_tag_400;
struct {
u32 Card_IDbyte6 : 8;
u32 Card_IDbyte5 : 8;
u32 Card_IDbyte4 : 8;
u32 Card_IDbyte3 : 8;
} card_id_408;
struct {
u32 Card_IDbyte2 : 8;
u32 Card_IDbyte1 : 8;
} card_id_40c;
struct {
u32 MAC1 : 8;
u32 MAC2 : 8;
u32 MAC3 : 8;
u32 MAC6 : 8;
} mac_address_418;
struct {
u32 MAC7 : 8;
u32 MAC8 : 8;
u32 reserved :16;
} mac_address_41c;
struct {
u32 transmitter_data_byte : 8;
u32 ReceiveDataReady : 1;
u32 ReceiveByteFrameError : 1;
u32 txbuffempty : 1;
u32 reserved :21;
} ci_600;
struct {
u32 pi_d : 8;
u32 pi_ha :20;
u32 pi_rw : 1;
u32 pi_component_reg : 3;
} pi_604;
struct {
u32 serialReset : 1;
u32 oncecycle_read : 1;
u32 Timer_Read_req : 1;
u32 Timer_Load_req : 1;
u32 timer_data : 7;
u32 unused : 1;
u32 Timer_addr : 5;
u32 reserved : 3;
u32 pcmcia_a_mod_pwr_n : 1;
u32 pcmcia_b_mod_pwr_n : 1;
u32 config_Done_stat : 1;
u32 config_Init_stat : 1;
u32 config_Prog_n : 1;
u32 config_wr_n : 1;
u32 config_cs_n : 1;
u32 config_cclk : 1;
u32 pi_CiMax_IRQ_n : 1;
u32 pi_timeout_status : 1;
u32 pi_wait_n : 1;
u32 pi_busy_n : 1;
} pi_608;
struct {
u32 PID :13;
u32 key_enable : 1;
u32 key_code : 2;
u32 key_array_col : 3;
u32 key_array_row : 5;
u32 dvb_en : 1;
u32 rw_flag : 1;
u32 reserved : 6;
} dvb_reg_60c;
struct {
u32 sram_addr :15;
u32 sram_rw : 1;
u32 sram_data : 8;
u32 sc_xfer_bit : 1;
u32 reserved1 : 3;
u32 oe_pin_reg : 1;
u32 ce_pin_reg : 1;
u32 reserved2 : 1;
u32 start_sram_ibi : 1;
} sram_ctrl_reg_700;
struct {
u32 net_addr_read :16;
u32 net_addr_write :16;
} net_buf_reg_704;
struct {
u32 cai_read :11;
u32 reserved1 : 5;
u32 cai_write :11;
u32 reserved2 : 6;
u32 cai_cnt : 4;
} cai_buf_reg_708;
struct {
u32 cao_read :11;
u32 reserved1 : 5;
u32 cap_write :11;
u32 reserved2 : 6;
u32 cao_cnt : 4;
} cao_buf_reg_70c;
struct {
u32 media_read :11;
u32 reserved1 : 5;
u32 media_write :11;
u32 reserved2 : 6;
u32 media_cnt : 4;
} media_buf_reg_710;
struct {
u32 NET_Dest : 2;
u32 CAI_Dest : 2;
u32 CAO_Dest : 2;
u32 MEDIA_Dest : 2;
u32 net_ovflow_error : 1;
u32 media_ovflow_error : 1;
u32 cai_ovflow_error : 1;
u32 cao_ovflow_error : 1;
u32 ctrl_usb_wan : 1;
u32 ctrl_sramdma : 1;
u32 ctrl_maximumfill : 1;
u32 reserved :17;
} sram_dest_reg_714;
struct {
u32 net_cnt :12;
u32 reserved1 : 4;
u32 net_addr_read : 1;
u32 reserved2 : 3;
u32 net_addr_write : 1;
u32 reserved3 :11;
} net_buf_reg_718;
struct {
u32 wan_speed_sig : 2;
u32 reserved1 : 6;
u32 wan_wait_state : 8;
u32 sram_chip : 2;
u32 sram_memmap : 2;
u32 reserved2 : 4;
u32 wan_pkt_frame : 4;
u32 reserved3 : 4;
} wan_ctrl_reg_71c;
} flexcop_ibi_value;
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,131 @@
// SPDX-License-Identifier: GPL-2.0-only
/* cypress_firmware.c is part of the DVB USB library.
*
* Copyright (C) 2004-6 Patrick Boettcher (patrick.boettcher@posteo.de)
* see dvb-usb-init.c for copyright information.
*
* This file contains functions for downloading the firmware to Cypress FX 1
* and 2 based devices.
*
*/
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/usb.h>
#include <linux/firmware.h>
#include "cypress_firmware.h"
struct usb_cypress_controller {
u8 id;
const char *name; /* name of the usb controller */
u16 cs_reg; /* needs to be restarted,
* when the firmware has been downloaded */
};
static const struct usb_cypress_controller cypress[] = {
{ .id = CYPRESS_AN2135, .name = "Cypress AN2135", .cs_reg = 0x7f92 },
{ .id = CYPRESS_AN2235, .name = "Cypress AN2235", .cs_reg = 0x7f92 },
{ .id = CYPRESS_FX2, .name = "Cypress FX2", .cs_reg = 0xe600 },
};
/*
* load a firmware packet to the device
*/
static int usb_cypress_writemem(struct usb_device *udev, u16 addr, u8 *data,
u8 len)
{
return usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
0xa0, USB_TYPE_VENDOR, addr, 0x00, data, len, 5000);
}
static int cypress_get_hexline(const struct firmware *fw,
struct hexline *hx, int *pos)
{
u8 *b = (u8 *) &fw->data[*pos];
int data_offs = 4;
if (*pos >= fw->size)
return 0;
memset(hx, 0, sizeof(struct hexline));
hx->len = b[0];
if ((*pos + hx->len + 4) >= fw->size)
return -EINVAL;
hx->addr = b[1] | (b[2] << 8);
hx->type = b[3];
if (hx->type == 0x04) {
/* b[4] and b[5] are the Extended linear address record data
* field */
hx->addr |= (b[4] << 24) | (b[5] << 16);
}
memcpy(hx->data, &b[data_offs], hx->len);
hx->chk = b[hx->len + data_offs];
*pos += hx->len + 5;
return *pos;
}
int cypress_load_firmware(struct usb_device *udev,
const struct firmware *fw, int type)
{
struct hexline *hx;
int ret, pos = 0;
hx = kmalloc_obj(*hx);
if (!hx)
return -ENOMEM;
/* stop the CPU */
hx->data[0] = 1;
ret = usb_cypress_writemem(udev, cypress[type].cs_reg, hx->data, 1);
if (ret != 1) {
dev_err(&udev->dev, "%s: CPU stop failed=%d\n",
KBUILD_MODNAME, ret);
ret = -EIO;
goto err_kfree;
}
/* write firmware to memory */
for (;;) {
ret = cypress_get_hexline(fw, hx, &pos);
if (ret < 0)
goto err_kfree;
else if (ret == 0)
break;
ret = usb_cypress_writemem(udev, hx->addr, hx->data, hx->len);
if (ret < 0) {
goto err_kfree;
} else if (ret != hx->len) {
dev_err(&udev->dev,
"%s: error while transferring firmware (transferred size=%d, block size=%d)\n",
KBUILD_MODNAME, ret, hx->len);
ret = -EIO;
goto err_kfree;
}
}
/* start the CPU */
hx->data[0] = 0;
ret = usb_cypress_writemem(udev, cypress[type].cs_reg, hx->data, 1);
if (ret != 1) {
dev_err(&udev->dev, "%s: CPU start failed=%d\n",
KBUILD_MODNAME, ret);
ret = -EIO;
goto err_kfree;
}
ret = 0;
err_kfree:
kfree(hx);
return ret;
}
EXPORT_SYMBOL(cypress_load_firmware);
MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>");
MODULE_DESCRIPTION("Cypress firmware download");
MODULE_LICENSE("GPL");
@@ -0,0 +1,29 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2004-6 Patrick Boettcher (patrick.boettcher@posteo.de)
* see dvb-usb-init.c for copyright information.
*
* This file contains functions for downloading the firmware to Cypress FX 1
* and 2 based devices.
*
*/
#ifndef CYPRESS_FIRMWARE_H
#define CYPRESS_FIRMWARE_H
#define CYPRESS_AN2135 0
#define CYPRESS_AN2235 1
#define CYPRESS_FX2 2
/* commonly used firmware download types and function */
struct hexline {
u8 len;
u32 addr;
u8 type;
u8 data[255];
u8 chk;
};
int cypress_load_firmware(struct usb_device *, const struct firmware *, int);
#endif
@@ -0,0 +1,10 @@
# SPDX-License-Identifier: GPL-2.0-only
config VIDEO_SAA7146
tristate
depends on I2C && PCI
config VIDEO_SAA7146_VV
tristate
depends on VIDEO_DEV
select VIDEOBUF2_DMA_SG
select VIDEO_SAA7146
@@ -0,0 +1,6 @@
# SPDX-License-Identifier: GPL-2.0-only
saa7146-objs := saa7146_i2c.o saa7146_core.o
saa7146_vv-objs := saa7146_fops.o saa7146_video.o saa7146_hlp.o saa7146_vbi.o
obj-$(CONFIG_VIDEO_SAA7146) += saa7146.o
obj-$(CONFIG_VIDEO_SAA7146_VV) += saa7146_vv.o
@@ -0,0 +1,566 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
saa7146.o - driver for generic saa7146-based hardware
Copyright (C) 1998-2003 Michael Hunold <michael@mihu.de>
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <media/drv-intf/saa7146.h>
#include <linux/module.h>
static int saa7146_num;
unsigned int saa7146_debug;
module_param(saa7146_debug, uint, 0644);
MODULE_PARM_DESC(saa7146_debug, "debug level (default: 0)");
#if 0
static void dump_registers(struct saa7146_dev* dev)
{
int i = 0;
pr_info(" @ %li jiffies:\n", jiffies);
for (i = 0; i <= 0x148; i += 4)
pr_info("0x%03x: 0x%08x\n", i, saa7146_read(dev, i));
}
#endif
/****************************************************************************
* gpio and debi helper functions
****************************************************************************/
void saa7146_setgpio(struct saa7146_dev *dev, int port, u32 data)
{
u32 value = 0;
if (WARN_ON(port > 3))
return;
value = saa7146_read(dev, GPIO_CTRL);
value &= ~(0xff << (8*port));
value |= (data << (8*port));
saa7146_write(dev, GPIO_CTRL, value);
}
/* This DEBI code is based on the saa7146 Stradis driver by Nathan Laredo */
static inline int saa7146_wait_for_debi_done_sleep(struct saa7146_dev *dev,
unsigned long us1, unsigned long us2)
{
unsigned long timeout;
int err;
/* wait for registers to be programmed */
timeout = jiffies + usecs_to_jiffies(us1);
while (1) {
err = time_after(jiffies, timeout);
if (saa7146_read(dev, MC2) & 2)
break;
if (err) {
pr_debug("%s: %s timed out while waiting for registers getting programmed\n",
dev->name, __func__);
return -ETIMEDOUT;
}
msleep(1);
}
/* wait for transfer to complete */
timeout = jiffies + usecs_to_jiffies(us2);
while (1) {
err = time_after(jiffies, timeout);
if (!(saa7146_read(dev, PSR) & SPCI_DEBI_S))
break;
saa7146_read(dev, MC2);
if (err) {
DEB_S("%s: %s timed out while waiting for transfer completion\n",
dev->name, __func__);
return -ETIMEDOUT;
}
msleep(1);
}
return 0;
}
static inline int saa7146_wait_for_debi_done_busyloop(struct saa7146_dev *dev,
unsigned long us1, unsigned long us2)
{
unsigned long loops;
/* wait for registers to be programmed */
loops = us1;
while (1) {
if (saa7146_read(dev, MC2) & 2)
break;
if (!loops--) {
pr_err("%s: %s timed out while waiting for registers getting programmed\n",
dev->name, __func__);
return -ETIMEDOUT;
}
udelay(1);
}
/* wait for transfer to complete */
loops = us2 / 5;
while (1) {
if (!(saa7146_read(dev, PSR) & SPCI_DEBI_S))
break;
saa7146_read(dev, MC2);
if (!loops--) {
DEB_S("%s: %s timed out while waiting for transfer completion\n",
dev->name, __func__);
return -ETIMEDOUT;
}
udelay(5);
}
return 0;
}
int saa7146_wait_for_debi_done(struct saa7146_dev *dev, int nobusyloop)
{
if (nobusyloop)
return saa7146_wait_for_debi_done_sleep(dev, 50000, 250000);
else
return saa7146_wait_for_debi_done_busyloop(dev, 50000, 250000);
}
/****************************************************************************
* general helper functions
****************************************************************************/
/* this is videobuf_vmalloc_to_sg() from videobuf-dma-sg.c
make sure virt has been allocated with vmalloc_32(), otherwise return NULL
on highmem machines */
static struct scatterlist* vmalloc_to_sg(unsigned char *virt, int nr_pages)
{
struct scatterlist *sglist;
struct page *pg;
int i;
sglist = kmalloc_objs(struct scatterlist, nr_pages);
if (NULL == sglist)
return NULL;
sg_init_table(sglist, nr_pages);
for (i = 0; i < nr_pages; i++, virt += PAGE_SIZE) {
pg = vmalloc_to_page(virt);
if (NULL == pg)
goto err;
if (WARN_ON(PageHighMem(pg)))
goto err;
sg_set_page(&sglist[i], pg, PAGE_SIZE, 0);
}
return sglist;
err:
kfree(sglist);
return NULL;
}
/********************************************************************************/
/* common page table functions */
void *saa7146_vmalloc_build_pgtable(struct pci_dev *pci, long length, struct saa7146_pgtable *pt)
{
int pages = (length+PAGE_SIZE-1)/PAGE_SIZE;
void *mem = vmalloc_32(length);
int slen = 0;
if (NULL == mem)
goto err_null;
if (!(pt->slist = vmalloc_to_sg(mem, pages)))
goto err_free_mem;
if (saa7146_pgtable_alloc(pci, pt))
goto err_free_slist;
pt->nents = pages;
slen = dma_map_sg(&pci->dev, pt->slist, pt->nents, DMA_FROM_DEVICE);
if (0 == slen)
goto err_free_pgtable;
if (0 != saa7146_pgtable_build_single(pci, pt, pt->slist, slen))
goto err_unmap_sg;
return mem;
err_unmap_sg:
dma_unmap_sg(&pci->dev, pt->slist, pt->nents, DMA_FROM_DEVICE);
err_free_pgtable:
saa7146_pgtable_free(pci, pt);
err_free_slist:
kfree(pt->slist);
pt->slist = NULL;
err_free_mem:
vfree(mem);
err_null:
return NULL;
}
void saa7146_vfree_destroy_pgtable(struct pci_dev *pci, void *mem, struct saa7146_pgtable *pt)
{
dma_unmap_sg(&pci->dev, pt->slist, pt->nents, DMA_FROM_DEVICE);
saa7146_pgtable_free(pci, pt);
kfree(pt->slist);
pt->slist = NULL;
vfree(mem);
}
void saa7146_pgtable_free(struct pci_dev *pci, struct saa7146_pgtable *pt)
{
if (NULL == pt->cpu)
return;
dma_free_coherent(&pci->dev, pt->size, pt->cpu, pt->dma);
pt->cpu = NULL;
}
int saa7146_pgtable_alloc(struct pci_dev *pci, struct saa7146_pgtable *pt)
{
__le32 *cpu;
dma_addr_t dma_addr = 0;
cpu = dma_alloc_coherent(&pci->dev, PAGE_SIZE, &dma_addr, GFP_KERNEL);
if (NULL == cpu) {
return -ENOMEM;
}
pt->size = PAGE_SIZE;
pt->cpu = cpu;
pt->dma = dma_addr;
return 0;
}
int saa7146_pgtable_build_single(struct pci_dev *pci, struct saa7146_pgtable *pt,
struct scatterlist *list, int sglen)
{
struct sg_dma_page_iter dma_iter;
__le32 *ptr, fill;
int nr_pages = 0;
int i;
if (WARN_ON(!sglen) ||
WARN_ON(list->offset > PAGE_SIZE))
return -EIO;
/* if we have a user buffer, the first page may not be
aligned to a page boundary. */
pt->offset = list->offset;
ptr = pt->cpu;
for_each_sg_dma_page(list, &dma_iter, sglen, 0) {
*ptr++ = cpu_to_le32(sg_page_iter_dma_address(&dma_iter));
nr_pages++;
}
/* safety; fill the page table up with the last valid page */
fill = *(ptr-1);
for (i = nr_pages; i < 1024; i++)
*ptr++ = fill;
return 0;
}
/********************************************************************************/
/* interrupt handler */
static irqreturn_t interrupt_hw(int irq, void *dev_id)
{
struct saa7146_dev *dev = dev_id;
u32 isr;
u32 ack_isr;
/* read out the interrupt status register */
ack_isr = isr = saa7146_read(dev, ISR);
/* is this our interrupt? */
if ( 0 == isr ) {
/* nope, some other device */
return IRQ_NONE;
}
if (dev->ext) {
if (dev->ext->irq_mask & isr) {
if (dev->ext->irq_func)
dev->ext->irq_func(dev, &isr);
isr &= ~dev->ext->irq_mask;
}
}
if (0 != (isr & (MASK_27))) {
DEB_INT("irq: RPS0 (0x%08x)\n", isr);
if (dev->vv_data && dev->vv_callback)
dev->vv_callback(dev,isr);
isr &= ~MASK_27;
}
if (0 != (isr & (MASK_28))) {
if (dev->vv_data && dev->vv_callback)
dev->vv_callback(dev,isr);
isr &= ~MASK_28;
}
if (0 != (isr & (MASK_16|MASK_17))) {
SAA7146_IER_DISABLE(dev, MASK_16|MASK_17);
/* only wake up if we expect something */
if (0 != dev->i2c_op) {
dev->i2c_op = 0;
wake_up(&dev->i2c_wq);
} else {
u32 psr = saa7146_read(dev, PSR);
u32 ssr = saa7146_read(dev, SSR);
pr_warn("%s: unexpected i2c irq: isr %08x psr %08x ssr %08x\n",
dev->name, isr, psr, ssr);
}
isr &= ~(MASK_16|MASK_17);
}
if( 0 != isr ) {
ERR("warning: interrupt enabled, but not handled properly.(0x%08x)\n",
isr);
ERR("disabling interrupt source(s)!\n");
SAA7146_IER_DISABLE(dev,isr);
}
saa7146_write(dev, ISR, ack_isr);
return IRQ_HANDLED;
}
/*********************************************************************************/
/* configuration-functions */
static int saa7146_init_one(struct pci_dev *pci, const struct pci_device_id *ent)
{
struct saa7146_pci_extension_data *pci_ext = (struct saa7146_pci_extension_data *)ent->driver_data;
struct saa7146_extension *ext = pci_ext->ext;
struct saa7146_dev *dev;
int err = -ENOMEM;
/* clear out mem for sure */
dev = kzalloc_obj(struct saa7146_dev);
if (!dev) {
ERR("out of memory\n");
goto out;
}
/* create a nice device name */
sprintf(dev->name, "saa7146 (%d)", saa7146_num);
DEB_EE("pci:%p\n", pci);
err = pci_enable_device(pci);
if (err < 0) {
ERR("pci_enable_device() failed\n");
goto err_free;
}
/* enable bus-mastering */
pci_set_master(pci);
dev->pci = pci;
/* get chip-revision; this is needed to enable bug-fixes */
dev->revision = pci->revision;
/* remap the memory from virtual to physical address */
err = pci_request_region(pci, 0, "saa7146");
if (err < 0)
goto err_disable;
dev->mem = ioremap(pci_resource_start(pci, 0),
pci_resource_len(pci, 0));
if (!dev->mem) {
ERR("ioremap() failed\n");
err = -ENODEV;
goto err_release;
}
/* we don't do a master reset here anymore, it screws up
some boards that don't have an i2c-eeprom for configuration
values */
/*
saa7146_write(dev, MC1, MASK_31);
*/
/* disable all irqs */
saa7146_write(dev, IER, 0);
/* shut down all dma transfers and rps tasks */
saa7146_write(dev, MC1, 0x30ff0000);
/* clear out any rps-signals pending */
saa7146_write(dev, MC2, 0xf8000000);
/* request an interrupt for the saa7146 */
err = request_irq(pci->irq, interrupt_hw, IRQF_SHARED,
dev->name, dev);
if (err < 0) {
ERR("request_irq() failed\n");
goto err_unmap;
}
err = -ENOMEM;
/* get memory for various stuff */
dev->d_rps0.cpu_addr = dma_alloc_coherent(&pci->dev, SAA7146_RPS_MEM,
&dev->d_rps0.dma_handle,
GFP_KERNEL);
if (!dev->d_rps0.cpu_addr)
goto err_free_irq;
dev->d_rps1.cpu_addr = dma_alloc_coherent(&pci->dev, SAA7146_RPS_MEM,
&dev->d_rps1.dma_handle,
GFP_KERNEL);
if (!dev->d_rps1.cpu_addr)
goto err_free_rps0;
dev->d_i2c.cpu_addr = dma_alloc_coherent(&pci->dev, SAA7146_RPS_MEM,
&dev->d_i2c.dma_handle, GFP_KERNEL);
if (!dev->d_i2c.cpu_addr)
goto err_free_rps1;
/* the rest + print status message */
pr_info("found saa7146 @ mem %p (revision %d, irq %d) (0x%04x,0x%04x)\n",
dev->mem, dev->revision, pci->irq,
pci->subsystem_vendor, pci->subsystem_device);
dev->ext = ext;
mutex_init(&dev->v4l2_lock);
spin_lock_init(&dev->int_slock);
spin_lock_init(&dev->slock);
mutex_init(&dev->i2c_lock);
dev->module = THIS_MODULE;
init_waitqueue_head(&dev->i2c_wq);
/* set some sane pci arbitrition values */
saa7146_write(dev, PCI_BT_V1, 0x1c00101f);
/* TODO: use the status code of the callback */
err = -ENODEV;
if (ext->probe && ext->probe(dev)) {
DEB_D("ext->probe() failed for %p. skipping device.\n", dev);
goto err_free_i2c;
}
if (ext->attach(dev, pci_ext)) {
DEB_D("ext->attach() failed for %p. skipping device.\n", dev);
goto err_free_i2c;
}
/* V4L extensions will set the pci drvdata to the v4l2_device in the
attach() above. So for those cards that do not use V4L we have to
set it explicitly. */
pci_set_drvdata(pci, &dev->v4l2_dev);
saa7146_num++;
err = 0;
out:
return err;
err_free_i2c:
dma_free_coherent(&pci->dev, SAA7146_RPS_MEM, dev->d_i2c.cpu_addr,
dev->d_i2c.dma_handle);
err_free_rps1:
dma_free_coherent(&pci->dev, SAA7146_RPS_MEM, dev->d_rps1.cpu_addr,
dev->d_rps1.dma_handle);
err_free_rps0:
dma_free_coherent(&pci->dev, SAA7146_RPS_MEM, dev->d_rps0.cpu_addr,
dev->d_rps0.dma_handle);
err_free_irq:
free_irq(pci->irq, (void *)dev);
err_unmap:
iounmap(dev->mem);
err_release:
pci_release_region(pci, 0);
err_disable:
pci_disable_device(pci);
err_free:
kfree(dev);
goto out;
}
static void saa7146_remove_one(struct pci_dev *pdev)
{
struct v4l2_device *v4l2_dev = pci_get_drvdata(pdev);
struct saa7146_dev *dev = to_saa7146_dev(v4l2_dev);
struct {
void *addr;
dma_addr_t dma;
} dev_map[] = {
{ dev->d_i2c.cpu_addr, dev->d_i2c.dma_handle },
{ dev->d_rps1.cpu_addr, dev->d_rps1.dma_handle },
{ dev->d_rps0.cpu_addr, dev->d_rps0.dma_handle },
{ NULL, 0 }
}, *p;
DEB_EE("dev:%p\n", dev);
dev->ext->detach(dev);
/* shut down all video dma transfers */
saa7146_write(dev, MC1, 0x00ff0000);
/* disable all irqs, release irq-routine */
saa7146_write(dev, IER, 0);
free_irq(pdev->irq, dev);
for (p = dev_map; p->addr; p++)
dma_free_coherent(&pdev->dev, SAA7146_RPS_MEM, p->addr,
p->dma);
iounmap(dev->mem);
pci_release_region(pdev, 0);
pci_disable_device(pdev);
kfree(dev);
saa7146_num--;
}
/*********************************************************************************/
/* extension handling functions */
int saa7146_register_extension(struct saa7146_extension* ext)
{
DEB_EE("ext:%p\n", ext);
ext->driver.name = ext->name;
ext->driver.id_table = ext->pci_tbl;
ext->driver.probe = saa7146_init_one;
ext->driver.remove = saa7146_remove_one;
pr_info("register extension '%s'\n", ext->name);
return pci_register_driver(&ext->driver);
}
int saa7146_unregister_extension(struct saa7146_extension* ext)
{
DEB_EE("ext:%p\n", ext);
pr_info("unregister extension '%s'\n", ext->name);
pci_unregister_driver(&ext->driver);
return 0;
}
EXPORT_SYMBOL_GPL(saa7146_register_extension);
EXPORT_SYMBOL_GPL(saa7146_unregister_extension);
/* misc functions used by extension modules */
EXPORT_SYMBOL_GPL(saa7146_pgtable_alloc);
EXPORT_SYMBOL_GPL(saa7146_pgtable_free);
EXPORT_SYMBOL_GPL(saa7146_pgtable_build_single);
EXPORT_SYMBOL_GPL(saa7146_vmalloc_build_pgtable);
EXPORT_SYMBOL_GPL(saa7146_vfree_destroy_pgtable);
EXPORT_SYMBOL_GPL(saa7146_wait_for_debi_done);
EXPORT_SYMBOL_GPL(saa7146_setgpio);
EXPORT_SYMBOL_GPL(saa7146_i2c_adapter_prepare);
EXPORT_SYMBOL_GPL(saa7146_debug);
MODULE_AUTHOR("Michael Hunold <michael@mihu.de>");
MODULE_DESCRIPTION("driver for generic saa7146-based hardware");
MODULE_LICENSE("GPL");
@@ -0,0 +1,422 @@
// SPDX-License-Identifier: GPL-2.0-only
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <media/drv-intf/saa7146_vv.h>
#include <linux/module.h>
/****************************************************************************/
/* resource management functions, shamelessly stolen from saa7134 driver */
int saa7146_res_get(struct saa7146_dev *dev, unsigned int bit)
{
struct saa7146_vv *vv = dev->vv_data;
if (vv->resources & bit) {
DEB_D("already allocated! want: 0x%02x, cur:0x%02x\n",
bit, vv->resources);
/* have it already allocated */
return 1;
}
/* is it free? */
if (vv->resources & bit) {
DEB_D("locked! vv->resources:0x%02x, we want:0x%02x\n",
vv->resources, bit);
/* no, someone else uses it */
return 0;
}
/* it's free, grab it */
vv->resources |= bit;
DEB_D("res: get 0x%02x, cur:0x%02x\n", bit, vv->resources);
return 1;
}
void saa7146_res_free(struct saa7146_dev *dev, unsigned int bits)
{
struct saa7146_vv *vv = dev->vv_data;
WARN_ON((vv->resources & bits) != bits);
vv->resources &= ~bits;
DEB_D("res: put 0x%02x, cur:0x%02x\n", bits, vv->resources);
}
/********************************************************************************/
/* common buffer functions */
int saa7146_buffer_queue(struct saa7146_dev *dev,
struct saa7146_dmaqueue *q,
struct saa7146_buf *buf)
{
assert_spin_locked(&dev->slock);
DEB_EE("dev:%p, dmaq:%p, buf:%p\n", dev, q, buf);
if (WARN_ON(!q))
return -EIO;
if (NULL == q->curr) {
q->curr = buf;
DEB_D("immediately activating buffer %p\n", buf);
buf->activate(dev,buf,NULL);
} else {
list_add_tail(&buf->list, &q->queue);
DEB_D("adding buffer %p to queue. (active buffer present)\n",
buf);
}
return 0;
}
void saa7146_buffer_finish(struct saa7146_dev *dev,
struct saa7146_dmaqueue *q,
int state)
{
struct saa7146_vv *vv = dev->vv_data;
struct saa7146_buf *buf = q->curr;
assert_spin_locked(&dev->slock);
DEB_EE("dev:%p, dmaq:%p, state:%d\n", dev, q, state);
DEB_EE("q->curr:%p\n", q->curr);
/* finish current buffer */
if (!buf) {
DEB_D("aiii. no current buffer\n");
return;
}
q->curr = NULL;
buf->vb.vb2_buf.timestamp = ktime_get_ns();
if (vv->video_fmt.field == V4L2_FIELD_ALTERNATE)
buf->vb.field = vv->last_field;
else if (vv->video_fmt.field == V4L2_FIELD_ANY)
buf->vb.field = (vv->video_fmt.height > vv->standard->v_max_out / 2)
? V4L2_FIELD_INTERLACED
: V4L2_FIELD_BOTTOM;
else
buf->vb.field = vv->video_fmt.field;
buf->vb.sequence = vv->seqnr++;
vb2_buffer_done(&buf->vb.vb2_buf, state);
}
void saa7146_buffer_next(struct saa7146_dev *dev,
struct saa7146_dmaqueue *q, int vbi)
{
struct saa7146_buf *buf,*next = NULL;
if (WARN_ON(!q))
return;
DEB_INT("dev:%p, dmaq:%p, vbi:%d\n", dev, q, vbi);
assert_spin_locked(&dev->slock);
if (!list_empty(&q->queue)) {
/* activate next one from queue */
buf = list_entry(q->queue.next, struct saa7146_buf, list);
list_del(&buf->list);
if (!list_empty(&q->queue))
next = list_entry(q->queue.next, struct saa7146_buf, list);
q->curr = buf;
DEB_INT("next buffer: buf:%p, prev:%p, next:%p\n",
buf, q->queue.prev, q->queue.next);
buf->activate(dev,buf,next);
} else {
DEB_INT("no next buffer. stopping.\n");
if( 0 != vbi ) {
/* turn off video-dma3 */
saa7146_write(dev,MC1, MASK_20);
} else {
/* nothing to do -- just prevent next video-dma1 transfer
by lowering the protection address */
// fixme: fix this for vflip != 0
saa7146_write(dev, PROT_ADDR1, 0);
saa7146_write(dev, MC2, (MASK_02|MASK_18));
/* write the address of the rps-program */
saa7146_write(dev, RPS_ADDR0, dev->d_rps0.dma_handle);
/* turn on rps */
saa7146_write(dev, MC1, (MASK_12 | MASK_28));
/*
printk("vdma%d.base_even: 0x%08x\n", 1,saa7146_read(dev,BASE_EVEN1));
printk("vdma%d.base_odd: 0x%08x\n", 1,saa7146_read(dev,BASE_ODD1));
printk("vdma%d.prot_addr: 0x%08x\n", 1,saa7146_read(dev,PROT_ADDR1));
printk("vdma%d.base_page: 0x%08x\n", 1,saa7146_read(dev,BASE_PAGE1));
printk("vdma%d.pitch: 0x%08x\n", 1,saa7146_read(dev,PITCH1));
printk("vdma%d.num_line_byte: 0x%08x\n", 1,saa7146_read(dev,NUM_LINE_BYTE1));
*/
}
timer_delete(&q->timeout);
}
}
void saa7146_buffer_timeout(struct timer_list *t)
{
struct saa7146_dmaqueue *q = timer_container_of(q, t, timeout);
struct saa7146_dev *dev = q->dev;
unsigned long flags;
DEB_EE("dev:%p, dmaq:%p\n", dev, q);
spin_lock_irqsave(&dev->slock,flags);
if (q->curr) {
DEB_D("timeout on %p\n", q->curr);
saa7146_buffer_finish(dev, q, VB2_BUF_STATE_ERROR);
}
/* we don't restart the transfer here like other drivers do. when
a streaming capture is disabled, the timeout function will be
called for the current buffer. if we activate the next buffer now,
we mess up our capture logic. if a timeout occurs on another buffer,
then something is seriously broken before, so no need to buffer the
next capture IMHO... */
saa7146_buffer_next(dev, q, 0);
spin_unlock_irqrestore(&dev->slock,flags);
}
/********************************************************************************/
/* file operations */
static ssize_t fops_write(struct file *file, const char __user *data, size_t count, loff_t *ppos)
{
struct video_device *vdev = video_devdata(file);
struct saa7146_dev *dev = video_drvdata(file);
int ret;
if (vdev->vfl_type != VFL_TYPE_VBI || !dev->ext_vv_data->vbi_write)
return -EINVAL;
if (mutex_lock_interruptible(vdev->lock))
return -ERESTARTSYS;
ret = dev->ext_vv_data->vbi_write(file, data, count, ppos);
mutex_unlock(vdev->lock);
return ret;
}
static const struct v4l2_file_operations video_fops =
{
.owner = THIS_MODULE,
.open = v4l2_fh_open,
.release = vb2_fop_release,
.read = vb2_fop_read,
.write = fops_write,
.poll = vb2_fop_poll,
.mmap = vb2_fop_mmap,
.unlocked_ioctl = video_ioctl2,
};
static void vv_callback(struct saa7146_dev *dev, unsigned long status)
{
u32 isr = status;
DEB_INT("dev:%p, isr:0x%08x\n", dev, (u32)status);
if (0 != (isr & (MASK_27))) {
DEB_INT("irq: RPS0 (0x%08x)\n", isr);
saa7146_video_uops.irq_done(dev,isr);
}
if (0 != (isr & (MASK_28))) {
u32 mc2 = saa7146_read(dev, MC2);
if( 0 != (mc2 & MASK_15)) {
DEB_INT("irq: RPS1 vbi workaround (0x%08x)\n", isr);
wake_up(&dev->vv_data->vbi_wq);
saa7146_write(dev,MC2, MASK_31);
return;
}
DEB_INT("irq: RPS1 (0x%08x)\n", isr);
saa7146_vbi_uops.irq_done(dev,isr);
}
}
static const struct v4l2_ctrl_ops saa7146_ctrl_ops = {
.s_ctrl = saa7146_s_ctrl,
};
int saa7146_vv_init(struct saa7146_dev* dev, struct saa7146_ext_vv *ext_vv)
{
struct v4l2_ctrl_handler *hdl = &dev->ctrl_handler;
struct v4l2_pix_format *fmt;
struct v4l2_vbi_format *vbi;
struct saa7146_vv *vv;
int err;
err = v4l2_device_register(&dev->pci->dev, &dev->v4l2_dev);
if (err)
return err;
v4l2_ctrl_handler_init(hdl, 6);
v4l2_ctrl_new_std(hdl, &saa7146_ctrl_ops,
V4L2_CID_BRIGHTNESS, 0, 255, 1, 128);
v4l2_ctrl_new_std(hdl, &saa7146_ctrl_ops,
V4L2_CID_CONTRAST, 0, 127, 1, 64);
v4l2_ctrl_new_std(hdl, &saa7146_ctrl_ops,
V4L2_CID_SATURATION, 0, 127, 1, 64);
v4l2_ctrl_new_std(hdl, &saa7146_ctrl_ops,
V4L2_CID_VFLIP, 0, 1, 1, 0);
v4l2_ctrl_new_std(hdl, &saa7146_ctrl_ops,
V4L2_CID_HFLIP, 0, 1, 1, 0);
if (hdl->error) {
err = hdl->error;
v4l2_ctrl_handler_free(hdl);
v4l2_device_unregister(&dev->v4l2_dev);
return err;
}
dev->v4l2_dev.ctrl_handler = hdl;
vv = kzalloc_obj(struct saa7146_vv);
if (vv == NULL) {
ERR("out of memory. aborting.\n");
v4l2_ctrl_handler_free(hdl);
v4l2_device_unregister(&dev->v4l2_dev);
return -ENOMEM;
}
ext_vv->vid_ops = saa7146_video_ioctl_ops;
ext_vv->vbi_ops = saa7146_vbi_ioctl_ops;
ext_vv->core_ops = &saa7146_video_ioctl_ops;
DEB_EE("dev:%p\n", dev);
/* set default values for video parts of the saa7146 */
saa7146_write(dev, BCS_CTRL, 0x80400040);
/* enable video-port pins */
saa7146_write(dev, MC1, (MASK_10 | MASK_26));
/* save per-device extension data (one extension can
handle different devices that might need different
configuration data) */
dev->ext_vv_data = ext_vv;
saa7146_video_uops.init(dev,vv);
if (dev->ext_vv_data->capabilities & V4L2_CAP_VBI_CAPTURE)
saa7146_vbi_uops.init(dev,vv);
fmt = &vv->video_fmt;
fmt->width = 384;
fmt->height = 288;
fmt->pixelformat = V4L2_PIX_FMT_BGR24;
fmt->field = V4L2_FIELD_INTERLACED;
fmt->colorspace = V4L2_COLORSPACE_SMPTE170M;
fmt->bytesperline = 3 * fmt->width;
fmt->sizeimage = fmt->bytesperline * fmt->height;
vbi = &vv->vbi_fmt;
vbi->sampling_rate = 27000000;
vbi->offset = 248; /* todo */
vbi->samples_per_line = 720 * 2;
vbi->sample_format = V4L2_PIX_FMT_GREY;
/* fixme: this only works for PAL */
vbi->start[0] = 5;
vbi->count[0] = 16;
vbi->start[1] = 312;
vbi->count[1] = 16;
timer_setup(&vv->vbi_read_timeout, NULL, 0);
dev->vv_data = vv;
dev->vv_callback = &vv_callback;
return 0;
}
EXPORT_SYMBOL_GPL(saa7146_vv_init);
int saa7146_vv_release(struct saa7146_dev* dev)
{
struct saa7146_vv *vv = dev->vv_data;
DEB_EE("dev:%p\n", dev);
v4l2_device_unregister(&dev->v4l2_dev);
v4l2_ctrl_handler_free(&dev->ctrl_handler);
kfree(vv);
dev->vv_data = NULL;
dev->vv_callback = NULL;
return 0;
}
EXPORT_SYMBOL_GPL(saa7146_vv_release);
int saa7146_register_device(struct video_device *vfd, struct saa7146_dev *dev,
char *name, int type)
{
struct vb2_queue *q;
int err;
int i;
DEB_EE("dev:%p, name:'%s', type:%d\n", dev, name, type);
vfd->fops = &video_fops;
if (type == VFL_TYPE_VIDEO) {
vfd->ioctl_ops = &dev->ext_vv_data->vid_ops;
q = &dev->vv_data->video_dmaq.q;
} else {
vfd->ioctl_ops = &dev->ext_vv_data->vbi_ops;
q = &dev->vv_data->vbi_dmaq.q;
}
vfd->release = video_device_release_empty;
vfd->lock = &dev->v4l2_lock;
vfd->v4l2_dev = &dev->v4l2_dev;
vfd->tvnorms = 0;
for (i = 0; i < dev->ext_vv_data->num_stds; i++)
vfd->tvnorms |= dev->ext_vv_data->stds[i].id;
strscpy(vfd->name, name, sizeof(vfd->name));
vfd->device_caps = V4L2_CAP_VIDEO_CAPTURE |
V4L2_CAP_READWRITE | V4L2_CAP_STREAMING;
vfd->device_caps |= dev->ext_vv_data->capabilities;
if (type == VFL_TYPE_VIDEO) {
vfd->device_caps &=
~(V4L2_CAP_VBI_CAPTURE | V4L2_CAP_SLICED_VBI_OUTPUT);
} else if (vfd->device_caps & V4L2_CAP_SLICED_VBI_OUTPUT) {
vfd->vfl_dir = VFL_DIR_TX;
vfd->device_caps &= ~(V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING |
V4L2_CAP_AUDIO | V4L2_CAP_TUNER);
} else {
vfd->device_caps &= ~V4L2_CAP_VIDEO_CAPTURE;
}
q->type = type == VFL_TYPE_VIDEO ? V4L2_BUF_TYPE_VIDEO_CAPTURE : V4L2_BUF_TYPE_VBI_CAPTURE;
q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
q->io_modes = VB2_MMAP | VB2_READ | VB2_DMABUF;
q->ops = type == VFL_TYPE_VIDEO ? &video_qops : &vbi_qops;
q->mem_ops = &vb2_dma_sg_memops;
q->drv_priv = dev;
q->gfp_flags = __GFP_DMA32;
q->buf_struct_size = sizeof(struct saa7146_buf);
q->lock = &dev->v4l2_lock;
q->min_queued_buffers = 2;
q->dev = &dev->pci->dev;
err = vb2_queue_init(q);
if (err)
return err;
vfd->queue = q;
video_set_drvdata(vfd, dev);
err = video_register_device(vfd, type, -1);
if (err < 0) {
ERR("cannot register v4l2 device. skipping.\n");
return err;
}
pr_info("%s: registered device %s [v4l2]\n",
dev->name, video_device_node_name(vfd));
return 0;
}
EXPORT_SYMBOL_GPL(saa7146_register_device);
int saa7146_unregister_device(struct video_device *vfd, struct saa7146_dev *dev)
{
DEB_EE("dev:%p\n", dev);
video_unregister_device(vfd);
return 0;
}
EXPORT_SYMBOL_GPL(saa7146_unregister_device);
MODULE_AUTHOR("Michael Hunold <michael@mihu.de>");
MODULE_DESCRIPTION("video4linux driver for saa7146-based hardware");
MODULE_LICENSE("GPL");
@@ -0,0 +1,771 @@
// SPDX-License-Identifier: GPL-2.0-only
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/kernel.h>
#include <linux/export.h>
#include <media/drv-intf/saa7146_vv.h>
static void calculate_output_format_register(struct saa7146_dev* saa, u32 palette, u32* clip_format)
{
/* clear out the necessary bits */
*clip_format &= 0x0000ffff;
/* set these bits new */
*clip_format |= (( ((palette&0xf00)>>8) << 30) | ((palette&0x00f) << 24) | (((palette&0x0f0)>>4) << 16));
}
static void calculate_hps_source_and_sync(struct saa7146_dev *dev, int source, int sync, u32* hps_ctrl)
{
*hps_ctrl &= ~(MASK_30 | MASK_31 | MASK_28);
*hps_ctrl |= (source << 30) | (sync << 28);
}
static void calculate_hxo_and_hyo(struct saa7146_vv *vv, u32* hps_h_scale, u32* hps_ctrl)
{
int hyo = 0, hxo = 0;
hyo = vv->standard->v_offset;
hxo = vv->standard->h_offset;
*hps_h_scale &= ~(MASK_B0 | 0xf00);
*hps_h_scale |= (hxo << 0);
*hps_ctrl &= ~(MASK_W0 | MASK_B2);
*hps_ctrl |= (hyo << 12);
}
/* helper functions for the calculation of the horizontal- and vertical
scaling registers, clip-format-register etc ...
these functions take pointers to the (most-likely read-out
original-values) and manipulate them according to the requested
changes.
*/
/* hps_coeff used for CXY and CXUV; scale 1/1 -> scale 1/64 */
static struct {
u16 hps_coeff;
u16 weight_sum;
} hps_h_coeff_tab [] = {
{0x00, 2}, {0x02, 4}, {0x00, 4}, {0x06, 8}, {0x02, 8},
{0x08, 8}, {0x00, 8}, {0x1E, 16}, {0x0E, 8}, {0x26, 8},
{0x06, 8}, {0x42, 8}, {0x02, 8}, {0x80, 8}, {0x00, 8},
{0xFE, 16}, {0xFE, 8}, {0x7E, 8}, {0x7E, 8}, {0x3E, 8},
{0x3E, 8}, {0x1E, 8}, {0x1E, 8}, {0x0E, 8}, {0x0E, 8},
{0x06, 8}, {0x06, 8}, {0x02, 8}, {0x02, 8}, {0x00, 8},
{0x00, 8}, {0xFE, 16}, {0xFE, 8}, {0xFE, 8}, {0xFE, 8},
{0xFE, 8}, {0xFE, 8}, {0xFE, 8}, {0xFE, 8}, {0xFE, 8},
{0xFE, 8}, {0xFE, 8}, {0xFE, 8}, {0xFE, 8}, {0xFE, 8},
{0xFE, 8}, {0xFE, 8}, {0xFE, 8}, {0xFE, 8}, {0x7E, 8},
{0x7E, 8}, {0x3E, 8}, {0x3E, 8}, {0x1E, 8}, {0x1E, 8},
{0x0E, 8}, {0x0E, 8}, {0x06, 8}, {0x06, 8}, {0x02, 8},
{0x02, 8}, {0x00, 8}, {0x00, 8}, {0xFE, 16}
};
/* table of attenuation values for horizontal scaling */
static u8 h_attenuation[] = { 1, 2, 4, 8, 2, 4, 8, 16, 0};
/* calculate horizontal scale registers */
static int calculate_h_scale_registers(struct saa7146_dev *dev,
int in_x, int out_x, int flip_lr,
u32* hps_ctrl, u32* hps_v_gain, u32* hps_h_prescale, u32* hps_h_scale)
{
/* horizontal prescaler */
u32 dcgx = 0, xpsc = 0, xacm = 0, cxy = 0, cxuv = 0;
/* horizontal scaler */
u32 xim = 0, xp = 0, xsci =0;
/* vertical scale & gain */
u32 pfuv = 0;
/* helper variables */
u32 h_atten = 0, i = 0;
if ( 0 == out_x ) {
return -EINVAL;
}
/* mask out vanity-bit */
*hps_ctrl &= ~MASK_29;
/* calculate prescale-(xspc)-value: [n .. 1/2) : 1
[1/2 .. 1/3) : 2
[1/3 .. 1/4) : 3
... */
if (in_x > out_x) {
xpsc = in_x / out_x;
}
else {
/* zooming */
xpsc = 1;
}
/* if flip_lr-bit is set, number of pixels after
horizontal prescaling must be < 384 */
if ( 0 != flip_lr ) {
/* set vanity bit */
*hps_ctrl |= MASK_29;
while (in_x / xpsc >= 384 )
xpsc++;
}
/* if zooming is wanted, number of pixels after
horizontal prescaling must be < 768 */
else {
while ( in_x / xpsc >= 768 )
xpsc++;
}
/* maximum prescale is 64 (p.69) */
if ( xpsc > 64 )
xpsc = 64;
/* keep xacm clear*/
xacm = 0;
/* set horizontal filter parameters (CXY = CXUV) */
cxy = hps_h_coeff_tab[min(xpsc - 1, 63)].hps_coeff;
cxuv = cxy;
/* calculate and set horizontal fine scale (xsci) */
/* bypass the horizontal scaler ? */
if ( (in_x == out_x) && ( 1 == xpsc ) )
xsci = 0x400;
else
xsci = ( (1024 * in_x) / (out_x * xpsc) ) + xpsc;
/* set start phase for horizontal fine scale (xp) to 0 */
xp = 0;
/* set xim, if we bypass the horizontal scaler */
if ( 0x400 == xsci )
xim = 1;
else
xim = 0;
/* if the prescaler is bypassed, enable horizontal
accumulation mode (xacm) and clear dcgx */
if( 1 == xpsc ) {
xacm = 1;
dcgx = 0;
} else {
xacm = 0;
/* get best match in the table of attenuations
for horizontal scaling */
h_atten = hps_h_coeff_tab[min(xpsc - 1, 63)].weight_sum;
for (i = 0; h_attenuation[i] != 0; i++) {
if (h_attenuation[i] >= h_atten)
break;
}
dcgx = i;
}
/* the horizontal scaling increment controls the UV filter
to reduce the bandwidth to improve the display quality,
so set it ... */
if ( xsci == 0x400)
pfuv = 0x00;
else if ( xsci < 0x600)
pfuv = 0x01;
else if ( xsci < 0x680)
pfuv = 0x11;
else if ( xsci < 0x700)
pfuv = 0x22;
else
pfuv = 0x33;
*hps_v_gain &= MASK_W0|MASK_B2;
*hps_v_gain |= (pfuv << 24);
*hps_h_scale &= ~(MASK_W1 | 0xf000);
*hps_h_scale |= (xim << 31) | (xp << 24) | (xsci << 12);
*hps_h_prescale |= (dcgx << 27) | ((xpsc-1) << 18) | (xacm << 17) | (cxy << 8) | (cxuv << 0);
return 0;
}
static struct {
u16 hps_coeff;
u16 weight_sum;
} hps_v_coeff_tab [] = {
{0x0100, 2}, {0x0102, 4}, {0x0300, 4}, {0x0106, 8}, {0x0502, 8},
{0x0708, 8}, {0x0F00, 8}, {0x011E, 16}, {0x110E, 16}, {0x1926, 16},
{0x3906, 16}, {0x3D42, 16}, {0x7D02, 16}, {0x7F80, 16}, {0xFF00, 16},
{0x01FE, 32}, {0x01FE, 32}, {0x817E, 32}, {0x817E, 32}, {0xC13E, 32},
{0xC13E, 32}, {0xE11E, 32}, {0xE11E, 32}, {0xF10E, 32}, {0xF10E, 32},
{0xF906, 32}, {0xF906, 32}, {0xFD02, 32}, {0xFD02, 32}, {0xFF00, 32},
{0xFF00, 32}, {0x01FE, 64}, {0x01FE, 64}, {0x01FE, 64}, {0x01FE, 64},
{0x01FE, 64}, {0x01FE, 64}, {0x01FE, 64}, {0x01FE, 64}, {0x01FE, 64},
{0x01FE, 64}, {0x01FE, 64}, {0x01FE, 64}, {0x01FE, 64}, {0x01FE, 64},
{0x01FE, 64}, {0x01FE, 64}, {0x01FE, 64}, {0x01FE, 64}, {0x817E, 64},
{0x817E, 64}, {0xC13E, 64}, {0xC13E, 64}, {0xE11E, 64}, {0xE11E, 64},
{0xF10E, 64}, {0xF10E, 64}, {0xF906, 64}, {0xF906, 64}, {0xFD02, 64},
{0xFD02, 64}, {0xFF00, 64}, {0xFF00, 64}, {0x01FE, 128}
};
/* table of attenuation values for vertical scaling */
static u16 v_attenuation[] = { 2, 4, 8, 16, 32, 64, 128, 256, 0};
/* calculate vertical scale registers */
static int calculate_v_scale_registers(struct saa7146_dev *dev, enum v4l2_field field,
int in_y, int out_y, u32* hps_v_scale, u32* hps_v_gain)
{
int lpi = 0;
/* vertical scaling */
u32 yacm = 0, ysci = 0, yacl = 0, ypo = 0, ype = 0;
/* vertical scale & gain */
u32 dcgy = 0, cya_cyb = 0;
/* helper variables */
u32 v_atten = 0, i = 0;
/* error, if vertical zooming */
if ( in_y < out_y ) {
return -EINVAL;
}
/* linear phase interpolation may be used
if scaling is between 1 and 1/2 (both fields used)
or scaling is between 1/2 and 1/4 (if only one field is used) */
if (V4L2_FIELD_HAS_BOTH(field)) {
if( 2*out_y >= in_y) {
lpi = 1;
}
} else if (field == V4L2_FIELD_TOP
|| field == V4L2_FIELD_ALTERNATE
|| field == V4L2_FIELD_BOTTOM) {
if( 4*out_y >= in_y ) {
lpi = 1;
}
out_y *= 2;
}
if( 0 != lpi ) {
yacm = 0;
yacl = 0;
cya_cyb = 0x00ff;
/* calculate scaling increment */
if ( in_y > out_y )
ysci = ((1024 * in_y) / (out_y + 1)) - 1024;
else
ysci = 0;
dcgy = 0;
/* calculate ype and ypo */
ype = ysci / 16;
ypo = ype + (ysci / 64);
} else {
yacm = 1;
/* calculate scaling increment */
ysci = (((10 * 1024 * (in_y - out_y - 1)) / in_y) + 9) / 10;
/* calculate ype and ypo */
ypo = ype = ((ysci + 15) / 16);
/* the sequence length interval (yacl) has to be set according
to the prescale value, e.g. [n .. 1/2) : 0
[1/2 .. 1/3) : 1
[1/3 .. 1/4) : 2
... */
if ( ysci < 512) {
yacl = 0;
} else {
yacl = ( ysci / (1024 - ysci) );
}
/* get filter coefficients for cya, cyb from table hps_v_coeff_tab */
cya_cyb = hps_v_coeff_tab[min(yacl, 63)].hps_coeff;
/* get best match in the table of attenuations for vertical scaling */
v_atten = hps_v_coeff_tab[min(yacl, 63)].weight_sum;
for (i = 0; v_attenuation[i] != 0; i++) {
if (v_attenuation[i] >= v_atten)
break;
}
dcgy = i;
}
/* ypo and ype swapped in spec ? */
*hps_v_scale |= (yacm << 31) | (ysci << 21) | (yacl << 15) | (ypo << 8 ) | (ype << 1);
*hps_v_gain &= ~(MASK_W0|MASK_B2);
*hps_v_gain |= (dcgy << 16) | (cya_cyb << 0);
return 0;
}
/* simple bubble-sort algorithm with duplicate elimination */
static void saa7146_set_window(struct saa7146_dev *dev, int width, int height, enum v4l2_field field)
{
struct saa7146_vv *vv = dev->vv_data;
int source = vv->current_hps_source;
int sync = vv->current_hps_sync;
u32 hps_v_scale = 0, hps_v_gain = 0, hps_ctrl = 0, hps_h_prescale = 0, hps_h_scale = 0;
/* set vertical scale */
hps_v_scale = 0; /* all bits get set by the function-call */
hps_v_gain = 0; /* fixme: saa7146_read(dev, HPS_V_GAIN);*/
calculate_v_scale_registers(dev, field, vv->standard->v_field*2, height, &hps_v_scale, &hps_v_gain);
/* set horizontal scale */
hps_ctrl = 0;
hps_h_prescale = 0; /* all bits get set in the function */
hps_h_scale = 0;
calculate_h_scale_registers(dev, vv->standard->h_pixels, width, vv->hflip, &hps_ctrl, &hps_v_gain, &hps_h_prescale, &hps_h_scale);
/* set hyo and hxo */
calculate_hxo_and_hyo(vv, &hps_h_scale, &hps_ctrl);
calculate_hps_source_and_sync(dev, source, sync, &hps_ctrl);
/* write out new register contents */
saa7146_write(dev, HPS_V_SCALE, hps_v_scale);
saa7146_write(dev, HPS_V_GAIN, hps_v_gain);
saa7146_write(dev, HPS_CTRL, hps_ctrl);
saa7146_write(dev, HPS_H_PRESCALE,hps_h_prescale);
saa7146_write(dev, HPS_H_SCALE, hps_h_scale);
/* upload shadow-ram registers */
saa7146_write(dev, MC2, (MASK_05 | MASK_06 | MASK_21 | MASK_22) );
}
static void saa7146_set_output_format(struct saa7146_dev *dev, unsigned long palette)
{
u32 clip_format = saa7146_read(dev, CLIP_FORMAT_CTRL);
/* call helper function */
calculate_output_format_register(dev,palette,&clip_format);
/* update the hps registers */
saa7146_write(dev, CLIP_FORMAT_CTRL, clip_format);
saa7146_write(dev, MC2, (MASK_05 | MASK_21));
}
/* select input-source */
void saa7146_set_hps_source_and_sync(struct saa7146_dev *dev, int source, int sync)
{
struct saa7146_vv *vv = dev->vv_data;
u32 hps_ctrl = 0;
/* read old state */
hps_ctrl = saa7146_read(dev, HPS_CTRL);
hps_ctrl &= ~( MASK_31 | MASK_30 | MASK_28 );
hps_ctrl |= (source << 30) | (sync << 28);
/* write back & upload register */
saa7146_write(dev, HPS_CTRL, hps_ctrl);
saa7146_write(dev, MC2, (MASK_05 | MASK_21));
vv->current_hps_source = source;
vv->current_hps_sync = sync;
}
EXPORT_SYMBOL_GPL(saa7146_set_hps_source_and_sync);
void saa7146_write_out_dma(struct saa7146_dev* dev, int which, struct saa7146_video_dma* vdma)
{
int where = 0;
if( which < 1 || which > 3) {
return;
}
/* calculate starting address */
where = (which-1)*0x18;
saa7146_write(dev, where, vdma->base_odd);
saa7146_write(dev, where+0x04, vdma->base_even);
saa7146_write(dev, where+0x08, vdma->prot_addr);
saa7146_write(dev, where+0x0c, vdma->pitch);
saa7146_write(dev, where+0x10, vdma->base_page);
saa7146_write(dev, where+0x14, vdma->num_line_byte);
/* upload */
saa7146_write(dev, MC2, (MASK_02<<(which-1))|(MASK_18<<(which-1)));
/*
printk("vdma%d.base_even: 0x%08x\n", which,vdma->base_even);
printk("vdma%d.base_odd: 0x%08x\n", which,vdma->base_odd);
printk("vdma%d.prot_addr: 0x%08x\n", which,vdma->prot_addr);
printk("vdma%d.base_page: 0x%08x\n", which,vdma->base_page);
printk("vdma%d.pitch: 0x%08x\n", which,vdma->pitch);
printk("vdma%d.num_line_byte: 0x%08x\n", which,vdma->num_line_byte);
*/
}
static int calculate_video_dma_grab_packed(struct saa7146_dev* dev, struct saa7146_buf *buf)
{
struct saa7146_vv *vv = dev->vv_data;
struct v4l2_pix_format *pix = &vv->video_fmt;
struct saa7146_video_dma vdma1;
struct saa7146_format *sfmt = saa7146_format_by_fourcc(dev, pix->pixelformat);
int width = pix->width;
int height = pix->height;
int bytesperline = pix->bytesperline;
enum v4l2_field field = pix->field;
int depth = sfmt->depth;
DEB_CAP("[size=%dx%d,fields=%s]\n",
width, height, v4l2_field_names[field]);
if( bytesperline != 0) {
vdma1.pitch = bytesperline*2;
} else {
vdma1.pitch = (width*depth*2)/8;
}
vdma1.num_line_byte = ((vv->standard->v_field<<16) + vv->standard->h_pixels);
vdma1.base_page = buf->pt[0].dma | ME1 | sfmt->swap;
if( 0 != vv->vflip ) {
vdma1.prot_addr = buf->pt[0].offset;
vdma1.base_even = buf->pt[0].offset+(vdma1.pitch/2)*height;
vdma1.base_odd = vdma1.base_even - (vdma1.pitch/2);
} else {
vdma1.base_even = buf->pt[0].offset;
vdma1.base_odd = vdma1.base_even + (vdma1.pitch/2);
vdma1.prot_addr = buf->pt[0].offset+(vdma1.pitch/2)*height;
}
if (V4L2_FIELD_HAS_BOTH(field)) {
} else if (field == V4L2_FIELD_ALTERNATE) {
/* fixme */
if ( vv->last_field == V4L2_FIELD_TOP ) {
vdma1.base_odd = vdma1.prot_addr;
vdma1.pitch /= 2;
} else if ( vv->last_field == V4L2_FIELD_BOTTOM ) {
vdma1.base_odd = vdma1.base_even;
vdma1.base_even = vdma1.prot_addr;
vdma1.pitch /= 2;
}
} else if (field == V4L2_FIELD_TOP) {
vdma1.base_odd = vdma1.prot_addr;
vdma1.pitch /= 2;
} else if (field == V4L2_FIELD_BOTTOM) {
vdma1.base_odd = vdma1.base_even;
vdma1.base_even = vdma1.prot_addr;
vdma1.pitch /= 2;
}
if( 0 != vv->vflip ) {
vdma1.pitch *= -1;
}
saa7146_write_out_dma(dev, 1, &vdma1);
return 0;
}
static int calc_planar_422(struct saa7146_vv *vv, struct saa7146_buf *buf, struct saa7146_video_dma *vdma2, struct saa7146_video_dma *vdma3)
{
struct v4l2_pix_format *pix = &vv->video_fmt;
int height = pix->height;
int width = pix->width;
vdma2->pitch = width;
vdma3->pitch = width;
/* fixme: look at bytesperline! */
if( 0 != vv->vflip ) {
vdma2->prot_addr = buf->pt[1].offset;
vdma2->base_even = ((vdma2->pitch/2)*height)+buf->pt[1].offset;
vdma2->base_odd = vdma2->base_even - (vdma2->pitch/2);
vdma3->prot_addr = buf->pt[2].offset;
vdma3->base_even = ((vdma3->pitch/2)*height)+buf->pt[2].offset;
vdma3->base_odd = vdma3->base_even - (vdma3->pitch/2);
} else {
vdma3->base_even = buf->pt[2].offset;
vdma3->base_odd = vdma3->base_even + (vdma3->pitch/2);
vdma3->prot_addr = (vdma3->pitch/2)*height+buf->pt[2].offset;
vdma2->base_even = buf->pt[1].offset;
vdma2->base_odd = vdma2->base_even + (vdma2->pitch/2);
vdma2->prot_addr = (vdma2->pitch/2)*height+buf->pt[1].offset;
}
return 0;
}
static int calc_planar_420(struct saa7146_vv *vv, struct saa7146_buf *buf, struct saa7146_video_dma *vdma2, struct saa7146_video_dma *vdma3)
{
struct v4l2_pix_format *pix = &vv->video_fmt;
int height = pix->height;
int width = pix->width;
vdma2->pitch = width/2;
vdma3->pitch = width/2;
if( 0 != vv->vflip ) {
vdma2->prot_addr = buf->pt[2].offset;
vdma2->base_even = ((vdma2->pitch/2)*height)+buf->pt[2].offset;
vdma2->base_odd = vdma2->base_even - (vdma2->pitch/2);
vdma3->prot_addr = buf->pt[1].offset;
vdma3->base_even = ((vdma3->pitch/2)*height)+buf->pt[1].offset;
vdma3->base_odd = vdma3->base_even - (vdma3->pitch/2);
} else {
vdma3->base_even = buf->pt[2].offset;
vdma3->base_odd = vdma3->base_even + (vdma3->pitch);
vdma3->prot_addr = (vdma3->pitch/2)*height+buf->pt[2].offset;
vdma2->base_even = buf->pt[1].offset;
vdma2->base_odd = vdma2->base_even + (vdma2->pitch);
vdma2->prot_addr = (vdma2->pitch/2)*height+buf->pt[1].offset;
}
return 0;
}
static int calculate_video_dma_grab_planar(struct saa7146_dev* dev, struct saa7146_buf *buf)
{
struct saa7146_vv *vv = dev->vv_data;
struct v4l2_pix_format *pix = &vv->video_fmt;
struct saa7146_video_dma vdma1;
struct saa7146_video_dma vdma2;
struct saa7146_video_dma vdma3;
struct saa7146_format *sfmt = saa7146_format_by_fourcc(dev, pix->pixelformat);
int width = pix->width;
int height = pix->height;
enum v4l2_field field = pix->field;
if (WARN_ON(!buf->pt[0].dma) ||
WARN_ON(!buf->pt[1].dma) ||
WARN_ON(!buf->pt[2].dma))
return -1;
DEB_CAP("[size=%dx%d,fields=%s]\n",
width, height, v4l2_field_names[field]);
/* fixme: look at bytesperline! */
/* fixme: what happens for user space buffers here?. The offsets are
most likely wrong, this version here only works for page-aligned
buffers, modifications to the pagetable-functions are necessary...*/
vdma1.pitch = width*2;
vdma1.num_line_byte = ((vv->standard->v_field<<16) + vv->standard->h_pixels);
vdma1.base_page = buf->pt[0].dma | ME1;
if( 0 != vv->vflip ) {
vdma1.prot_addr = buf->pt[0].offset;
vdma1.base_even = ((vdma1.pitch/2)*height)+buf->pt[0].offset;
vdma1.base_odd = vdma1.base_even - (vdma1.pitch/2);
} else {
vdma1.base_even = buf->pt[0].offset;
vdma1.base_odd = vdma1.base_even + (vdma1.pitch/2);
vdma1.prot_addr = (vdma1.pitch/2)*height+buf->pt[0].offset;
}
vdma2.num_line_byte = 0; /* unused */
vdma2.base_page = buf->pt[1].dma | ME1;
vdma3.num_line_byte = 0; /* unused */
vdma3.base_page = buf->pt[2].dma | ME1;
switch( sfmt->depth ) {
case 12: {
calc_planar_420(vv,buf,&vdma2,&vdma3);
break;
}
case 16: {
calc_planar_422(vv,buf,&vdma2,&vdma3);
break;
}
default: {
return -1;
}
}
if (V4L2_FIELD_HAS_BOTH(field)) {
} else if (field == V4L2_FIELD_ALTERNATE) {
/* fixme */
vdma1.base_odd = vdma1.prot_addr;
vdma1.pitch /= 2;
vdma2.base_odd = vdma2.prot_addr;
vdma2.pitch /= 2;
vdma3.base_odd = vdma3.prot_addr;
vdma3.pitch /= 2;
} else if (field == V4L2_FIELD_TOP) {
vdma1.base_odd = vdma1.prot_addr;
vdma1.pitch /= 2;
vdma2.base_odd = vdma2.prot_addr;
vdma2.pitch /= 2;
vdma3.base_odd = vdma3.prot_addr;
vdma3.pitch /= 2;
} else if (field == V4L2_FIELD_BOTTOM) {
vdma1.base_odd = vdma1.base_even;
vdma1.base_even = vdma1.prot_addr;
vdma1.pitch /= 2;
vdma2.base_odd = vdma2.base_even;
vdma2.base_even = vdma2.prot_addr;
vdma2.pitch /= 2;
vdma3.base_odd = vdma3.base_even;
vdma3.base_even = vdma3.prot_addr;
vdma3.pitch /= 2;
}
if( 0 != vv->vflip ) {
vdma1.pitch *= -1;
vdma2.pitch *= -1;
vdma3.pitch *= -1;
}
saa7146_write_out_dma(dev, 1, &vdma1);
if( (sfmt->flags & FORMAT_BYTE_SWAP) != 0 ) {
saa7146_write_out_dma(dev, 3, &vdma2);
saa7146_write_out_dma(dev, 2, &vdma3);
} else {
saa7146_write_out_dma(dev, 2, &vdma2);
saa7146_write_out_dma(dev, 3, &vdma3);
}
return 0;
}
static void program_capture_engine(struct saa7146_dev *dev, int planar)
{
struct saa7146_vv *vv = dev->vv_data;
int count = 0;
unsigned long e_wait = vv->current_hps_sync == SAA7146_HPS_SYNC_PORT_A ? CMD_E_FID_A : CMD_E_FID_B;
unsigned long o_wait = vv->current_hps_sync == SAA7146_HPS_SYNC_PORT_A ? CMD_O_FID_A : CMD_O_FID_B;
/* wait for o_fid_a/b / e_fid_a/b toggle only if rps register 0 is not set*/
WRITE_RPS0(CMD_PAUSE | CMD_OAN | CMD_SIG0 | o_wait);
WRITE_RPS0(CMD_PAUSE | CMD_OAN | CMD_SIG0 | e_wait);
/* set rps register 0 */
WRITE_RPS0(CMD_WR_REG | (1 << 8) | (MC2/4));
WRITE_RPS0(MASK_27 | MASK_11);
/* turn on video-dma1 */
WRITE_RPS0(CMD_WR_REG_MASK | (MC1/4));
WRITE_RPS0(MASK_06 | MASK_22); /* => mask */
WRITE_RPS0(MASK_06 | MASK_22); /* => values */
if( 0 != planar ) {
/* turn on video-dma2 */
WRITE_RPS0(CMD_WR_REG_MASK | (MC1/4));
WRITE_RPS0(MASK_05 | MASK_21); /* => mask */
WRITE_RPS0(MASK_05 | MASK_21); /* => values */
/* turn on video-dma3 */
WRITE_RPS0(CMD_WR_REG_MASK | (MC1/4));
WRITE_RPS0(MASK_04 | MASK_20); /* => mask */
WRITE_RPS0(MASK_04 | MASK_20); /* => values */
}
/* wait for o_fid_a/b / e_fid_a/b toggle */
if ( vv->last_field == V4L2_FIELD_INTERLACED ) {
WRITE_RPS0(CMD_PAUSE | o_wait);
WRITE_RPS0(CMD_PAUSE | e_wait);
} else if ( vv->last_field == V4L2_FIELD_TOP ) {
WRITE_RPS0(CMD_PAUSE | (vv->current_hps_sync == SAA7146_HPS_SYNC_PORT_A ? MASK_10 : MASK_09));
WRITE_RPS0(CMD_PAUSE | o_wait);
} else if ( vv->last_field == V4L2_FIELD_BOTTOM ) {
WRITE_RPS0(CMD_PAUSE | (vv->current_hps_sync == SAA7146_HPS_SYNC_PORT_A ? MASK_10 : MASK_09));
WRITE_RPS0(CMD_PAUSE | e_wait);
}
/* turn off video-dma1 */
WRITE_RPS0(CMD_WR_REG_MASK | (MC1/4));
WRITE_RPS0(MASK_22 | MASK_06); /* => mask */
WRITE_RPS0(MASK_22); /* => values */
if( 0 != planar ) {
/* turn off video-dma2 */
WRITE_RPS0(CMD_WR_REG_MASK | (MC1/4));
WRITE_RPS0(MASK_05 | MASK_21); /* => mask */
WRITE_RPS0(MASK_21); /* => values */
/* turn off video-dma3 */
WRITE_RPS0(CMD_WR_REG_MASK | (MC1/4));
WRITE_RPS0(MASK_04 | MASK_20); /* => mask */
WRITE_RPS0(MASK_20); /* => values */
}
/* generate interrupt */
WRITE_RPS0(CMD_INTERRUPT);
/* stop */
WRITE_RPS0(CMD_STOP);
}
/* disable clipping */
static void saa7146_disable_clipping(struct saa7146_dev *dev)
{
u32 clip_format = saa7146_read(dev, CLIP_FORMAT_CTRL);
/* mask out relevant bits (=lower word)*/
clip_format &= MASK_W1;
/* upload clipping-registers*/
saa7146_write(dev, CLIP_FORMAT_CTRL, clip_format);
saa7146_write(dev, MC2, (MASK_05 | MASK_21));
/* disable video dma2 */
saa7146_write(dev, MC1, MASK_21);
}
void saa7146_set_capture(struct saa7146_dev *dev, struct saa7146_buf *buf, struct saa7146_buf *next)
{
struct saa7146_vv *vv = dev->vv_data;
struct v4l2_pix_format *pix = &vv->video_fmt;
struct saa7146_format *sfmt = saa7146_format_by_fourcc(dev, pix->pixelformat);
u32 vdma1_prot_addr;
DEB_CAP("buf:%p, next:%p\n", buf, next);
vdma1_prot_addr = saa7146_read(dev, PROT_ADDR1);
if( 0 == vdma1_prot_addr ) {
/* clear out beginning of streaming bit (rps register 0)*/
DEB_CAP("forcing sync to new frame\n");
saa7146_write(dev, MC2, MASK_27 );
}
saa7146_set_window(dev, pix->width, pix->height, pix->field);
saa7146_set_output_format(dev, sfmt->trans);
saa7146_disable_clipping(dev);
if ( vv->last_field == V4L2_FIELD_INTERLACED ) {
} else if ( vv->last_field == V4L2_FIELD_TOP ) {
vv->last_field = V4L2_FIELD_BOTTOM;
} else if ( vv->last_field == V4L2_FIELD_BOTTOM ) {
vv->last_field = V4L2_FIELD_TOP;
}
if( 0 != IS_PLANAR(sfmt->trans)) {
calculate_video_dma_grab_planar(dev, buf);
program_capture_engine(dev,1);
} else {
calculate_video_dma_grab_packed(dev, buf);
program_capture_engine(dev,0);
}
/*
printk("vdma%d.base_even: 0x%08x\n", 1,saa7146_read(dev,BASE_EVEN1));
printk("vdma%d.base_odd: 0x%08x\n", 1,saa7146_read(dev,BASE_ODD1));
printk("vdma%d.prot_addr: 0x%08x\n", 1,saa7146_read(dev,PROT_ADDR1));
printk("vdma%d.base_page: 0x%08x\n", 1,saa7146_read(dev,BASE_PAGE1));
printk("vdma%d.pitch: 0x%08x\n", 1,saa7146_read(dev,PITCH1));
printk("vdma%d.num_line_byte: 0x%08x\n", 1,saa7146_read(dev,NUM_LINE_BYTE1));
printk("vdma%d => vptr : 0x%08x\n", 1,saa7146_read(dev,PCI_VDP1));
*/
/* write the address of the rps-program */
saa7146_write(dev, RPS_ADDR0, dev->d_rps0.dma_handle);
/* turn on rps */
saa7146_write(dev, MC1, (MASK_12 | MASK_28));
}
@@ -0,0 +1,421 @@
// SPDX-License-Identifier: GPL-2.0
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <media/drv-intf/saa7146_vv.h>
static u32 saa7146_i2c_func(struct i2c_adapter *adapter)
{
/* DEB_I2C("'%s'\n", adapter->name); */
return I2C_FUNC_I2C
| I2C_FUNC_SMBUS_QUICK
| I2C_FUNC_SMBUS_READ_BYTE | I2C_FUNC_SMBUS_WRITE_BYTE
| I2C_FUNC_SMBUS_READ_BYTE_DATA | I2C_FUNC_SMBUS_WRITE_BYTE_DATA;
}
/* this function returns the status-register of our i2c-device */
static inline u32 saa7146_i2c_status(struct saa7146_dev *dev)
{
u32 iicsta = saa7146_read(dev, I2C_STATUS);
/* DEB_I2C("status: 0x%08x\n", iicsta); */
return iicsta;
}
/* this function runs through the i2c-messages and prepares the data to be
sent through the saa7146. have a look at the specifications p. 122 ff
to understand this. it returns the number of u32s to send, or -1
in case of an error. */
static int saa7146_i2c_msg_prepare(const struct i2c_msg *m, int num, __le32 *op)
{
int h1, h2;
int i, j, addr;
int mem = 0, op_count = 0;
/* first determine size of needed memory */
for(i = 0; i < num; i++) {
mem += m[i].len + 1;
}
/* worst case: we need one u32 for three bytes to be send
plus one extra byte to address the device */
mem = 1 + ((mem-1) / 3);
/* we assume that op points to a memory of at least
* SAA7146_I2C_MEM bytes size. if we exceed this limit...
*/
if ((4 * mem) > SAA7146_I2C_MEM) {
/* DEB_I2C("cannot prepare i2c-message\n"); */
return -ENOMEM;
}
/* be careful: clear out the i2c-mem first */
memset(op,0,sizeof(__le32)*mem);
/* loop through all messages */
for(i = 0; i < num; i++) {
addr = i2c_8bit_addr_from_msg(&m[i]);
h1 = op_count/3; h2 = op_count%3;
op[h1] |= cpu_to_le32( (u8)addr << ((3-h2)*8));
op[h1] |= cpu_to_le32(SAA7146_I2C_START << ((3-h2)*2));
op_count++;
/* loop through all bytes of message i */
for(j = 0; j < m[i].len; j++) {
/* insert the data bytes */
h1 = op_count/3; h2 = op_count%3;
op[h1] |= cpu_to_le32( (u32)((u8)m[i].buf[j]) << ((3-h2)*8));
op[h1] |= cpu_to_le32( SAA7146_I2C_CONT << ((3-h2)*2));
op_count++;
}
}
/* have a look at the last byte inserted:
if it was: ...CONT change it to ...STOP */
h1 = (op_count-1)/3; h2 = (op_count-1)%3;
if ( SAA7146_I2C_CONT == (0x3 & (le32_to_cpu(op[h1]) >> ((3-h2)*2))) ) {
op[h1] &= ~cpu_to_le32(0x2 << ((3-h2)*2));
op[h1] |= cpu_to_le32(SAA7146_I2C_STOP << ((3-h2)*2));
}
/* return the number of u32s to send */
return mem;
}
/* this functions loops through all i2c-messages. normally, it should determine
which bytes were read through the adapter and write them back to the corresponding
i2c-message. but instead, we simply write back all bytes.
fixme: this could be improved. */
static int saa7146_i2c_msg_cleanup(const struct i2c_msg *m, int num, __le32 *op)
{
int i, j;
int op_count = 0;
/* loop through all messages */
for(i = 0; i < num; i++) {
op_count++;
/* loop through all bytes of message i */
for(j = 0; j < m[i].len; j++) {
/* write back all bytes that could have been read */
m[i].buf[j] = (le32_to_cpu(op[op_count/3]) >> ((3-(op_count%3))*8));
op_count++;
}
}
return 0;
}
/* this functions resets the i2c-device and returns 0 if everything was fine, otherwise -1 */
static int saa7146_i2c_reset(struct saa7146_dev *dev)
{
/* get current status */
u32 status = saa7146_i2c_status(dev);
/* clear registers for sure */
saa7146_write(dev, I2C_STATUS, dev->i2c_bitrate);
saa7146_write(dev, I2C_TRANSFER, 0);
/* check if any operation is still in progress */
if ( 0 != ( status & SAA7146_I2C_BUSY) ) {
/* yes, kill ongoing operation */
DEB_I2C("busy_state detected\n");
/* set "ABORT-OPERATION"-bit (bit 7)*/
saa7146_write(dev, I2C_STATUS, (dev->i2c_bitrate | MASK_07));
saa7146_write(dev, MC2, (MASK_00 | MASK_16));
msleep(SAA7146_I2C_DELAY);
/* clear all error-bits pending; this is needed because p.123, note 1 */
saa7146_write(dev, I2C_STATUS, dev->i2c_bitrate);
saa7146_write(dev, MC2, (MASK_00 | MASK_16));
msleep(SAA7146_I2C_DELAY);
}
/* check if any error is (still) present. (this can be necessary because p.123, note 1) */
status = saa7146_i2c_status(dev);
if ( dev->i2c_bitrate != status ) {
DEB_I2C("error_state detected. status:0x%08x\n", status);
/* Repeat the abort operation. This seems to be necessary
after serious protocol errors caused by e.g. the SAA7740 */
saa7146_write(dev, I2C_STATUS, (dev->i2c_bitrate | MASK_07));
saa7146_write(dev, MC2, (MASK_00 | MASK_16));
msleep(SAA7146_I2C_DELAY);
/* clear all error-bits pending */
saa7146_write(dev, I2C_STATUS, dev->i2c_bitrate);
saa7146_write(dev, MC2, (MASK_00 | MASK_16));
msleep(SAA7146_I2C_DELAY);
/* the data sheet says it might be necessary to clear the status
twice after an abort */
saa7146_write(dev, I2C_STATUS, dev->i2c_bitrate);
saa7146_write(dev, MC2, (MASK_00 | MASK_16));
msleep(SAA7146_I2C_DELAY);
}
/* if any error is still present, a fatal error has occurred ... */
status = saa7146_i2c_status(dev);
if ( dev->i2c_bitrate != status ) {
DEB_I2C("fatal error. status:0x%08x\n", status);
return -1;
}
return 0;
}
/* this functions writes out the data-byte 'dword' to the i2c-device.
it returns 0 if ok, -1 if the transfer failed, -2 if the transfer
failed badly (e.g. address error) */
static int saa7146_i2c_writeout(struct saa7146_dev *dev, __le32 *dword, int short_delay)
{
u32 status = 0, mc2 = 0;
int trial = 0;
unsigned long timeout;
/* write out i2c-command */
DEB_I2C("before: 0x%08x (status: 0x%08x), %d\n",
*dword, saa7146_read(dev, I2C_STATUS), dev->i2c_op);
if( 0 != (SAA7146_USE_I2C_IRQ & dev->ext->flags)) {
saa7146_write(dev, I2C_STATUS, dev->i2c_bitrate);
saa7146_write(dev, I2C_TRANSFER, le32_to_cpu(*dword));
dev->i2c_op = 1;
SAA7146_ISR_CLEAR(dev, MASK_16|MASK_17);
SAA7146_IER_ENABLE(dev, MASK_16|MASK_17);
saa7146_write(dev, MC2, (MASK_00 | MASK_16));
timeout = HZ/100 + 1; /* 10ms */
timeout = wait_event_interruptible_timeout(dev->i2c_wq, dev->i2c_op == 0, timeout);
if (timeout == -ERESTARTSYS || dev->i2c_op) {
SAA7146_IER_DISABLE(dev, MASK_16|MASK_17);
SAA7146_ISR_CLEAR(dev, MASK_16|MASK_17);
if (timeout == -ERESTARTSYS)
/* a signal arrived */
return -ERESTARTSYS;
pr_warn("%s %s [irq]: timed out waiting for end of xfer\n",
dev->name, __func__);
return -EIO;
}
status = saa7146_read(dev, I2C_STATUS);
} else {
saa7146_write(dev, I2C_STATUS, dev->i2c_bitrate);
saa7146_write(dev, I2C_TRANSFER, le32_to_cpu(*dword));
saa7146_write(dev, MC2, (MASK_00 | MASK_16));
/* do not poll for i2c-status before upload is complete */
timeout = jiffies + HZ/100 + 1; /* 10ms */
while(1) {
mc2 = (saa7146_read(dev, MC2) & 0x1);
if( 0 != mc2 ) {
break;
}
if (time_after(jiffies,timeout)) {
pr_warn("%s %s: timed out waiting for MC2\n",
dev->name, __func__);
return -EIO;
}
}
/* wait until we get a transfer done or error */
timeout = jiffies + HZ/100 + 1; /* 10ms */
/* first read usually delivers bogus results... */
saa7146_i2c_status(dev);
while(1) {
status = saa7146_i2c_status(dev);
if ((status & 0x3) != 1)
break;
if (time_after(jiffies,timeout)) {
/* this is normal when probing the bus
* (no answer from nonexisistant device...)
*/
pr_warn("%s %s [poll]: timed out waiting for end of xfer\n",
dev->name, __func__);
return -EIO;
}
if (++trial < 50 && short_delay)
udelay(10);
else
msleep(1);
}
}
/* give a detailed status report */
if ( 0 != (status & (SAA7146_I2C_SPERR | SAA7146_I2C_APERR |
SAA7146_I2C_DTERR | SAA7146_I2C_DRERR |
SAA7146_I2C_AL | SAA7146_I2C_ERR |
SAA7146_I2C_BUSY)) ) {
if ( 0 == (status & SAA7146_I2C_ERR) ||
0 == (status & SAA7146_I2C_BUSY) ) {
/* it may take some time until ERR goes high - ignore */
DEB_I2C("unexpected i2c status %04x\n", status);
}
if( 0 != (status & SAA7146_I2C_SPERR) ) {
DEB_I2C("error due to invalid start/stop condition\n");
}
if( 0 != (status & SAA7146_I2C_DTERR) ) {
DEB_I2C("error in data transmission\n");
}
if( 0 != (status & SAA7146_I2C_DRERR) ) {
DEB_I2C("error when receiving data\n");
}
if( 0 != (status & SAA7146_I2C_AL) ) {
DEB_I2C("error because arbitration lost\n");
}
/* we handle address-errors here */
if( 0 != (status & SAA7146_I2C_APERR) ) {
DEB_I2C("error in address phase\n");
return -EREMOTEIO;
}
return -EIO;
}
/* read back data, just in case we were reading ... */
*dword = cpu_to_le32(saa7146_read(dev, I2C_TRANSFER));
DEB_I2C("after: 0x%08x\n", *dword);
return 0;
}
static int saa7146_i2c_transfer(struct saa7146_dev *dev, const struct i2c_msg *msgs, int num, int retries)
{
int i = 0, count = 0;
__le32 *buffer = dev->d_i2c.cpu_addr;
int err = 0;
int short_delay = 0;
if (mutex_lock_interruptible(&dev->i2c_lock))
return -ERESTARTSYS;
for(i=0;i<num;i++) {
DEB_I2C("msg:%d/%d\n", i+1, num);
}
/* prepare the message(s), get number of u32s to transfer */
count = saa7146_i2c_msg_prepare(msgs, num, buffer);
if ( 0 > count ) {
err = -EIO;
goto out;
}
if ( count > 3 || 0 != (SAA7146_I2C_SHORT_DELAY & dev->ext->flags) )
short_delay = 1;
do {
/* reset the i2c-device if necessary */
err = saa7146_i2c_reset(dev);
if ( 0 > err ) {
DEB_I2C("could not reset i2c-device\n");
goto out;
}
/* write out the u32s one after another */
for(i = 0; i < count; i++) {
err = saa7146_i2c_writeout(dev, &buffer[i], short_delay);
if ( 0 != err) {
/* this one is unsatisfying: some i2c slaves on some
dvb cards don't acknowledge correctly, so the saa7146
thinks that an address error occurred. in that case, the
transaction should be retrying, even if an address error
occurred. analog saa7146 based cards extensively rely on
i2c address probing, however, and address errors indicate that a
device is really *not* there. retrying in that case
increases the time the device needs to probe greatly, so
it should be avoided. So we bail out in irq mode after an
address error and trust the saa7146 address error detection. */
if (-EREMOTEIO == err && 0 != (SAA7146_USE_I2C_IRQ & dev->ext->flags))
goto out;
DEB_I2C("error while sending message(s). starting again\n");
break;
}
}
if( 0 == err ) {
err = num;
break;
}
/* delay a bit before retrying */
msleep(10);
} while (err != num && retries--);
/* quit if any error occurred */
if (err != num)
goto out;
/* if any things had to be read, get the results */
if ( 0 != saa7146_i2c_msg_cleanup(msgs, num, buffer)) {
DEB_I2C("could not cleanup i2c-message\n");
err = -EIO;
goto out;
}
/* return the number of delivered messages */
DEB_I2C("transmission successful. (msg:%d)\n", err);
out:
/* another bug in revision 0: the i2c-registers get uploaded randomly by other
uploads, so we better clear them out before continuing */
if( 0 == dev->revision ) {
__le32 zero = 0;
saa7146_i2c_reset(dev);
if( 0 != saa7146_i2c_writeout(dev, &zero, short_delay)) {
pr_info("revision 0 error. this should never happen\n");
}
}
mutex_unlock(&dev->i2c_lock);
return err;
}
/* utility functions */
static int saa7146_i2c_xfer(struct i2c_adapter* adapter, struct i2c_msg *msg, int num)
{
struct v4l2_device *v4l2_dev = i2c_get_adapdata(adapter);
struct saa7146_dev *dev = to_saa7146_dev(v4l2_dev);
/* use helper function to transfer data */
return saa7146_i2c_transfer(dev, msg, num, adapter->retries);
}
/*****************************************************************************/
/* i2c-adapter helper functions */
/* exported algorithm data */
static const struct i2c_algorithm saa7146_algo = {
.master_xfer = saa7146_i2c_xfer,
.functionality = saa7146_i2c_func,
};
int saa7146_i2c_adapter_prepare(struct saa7146_dev *dev, struct i2c_adapter *i2c_adapter, u32 bitrate)
{
DEB_EE("bitrate: 0x%08x\n", bitrate);
/* enable i2c-port pins */
saa7146_write(dev, MC1, (MASK_08 | MASK_24));
dev->i2c_bitrate = bitrate;
saa7146_i2c_reset(dev);
if (i2c_adapter) {
i2c_set_adapdata(i2c_adapter, &dev->v4l2_dev);
i2c_adapter->dev.parent = &dev->pci->dev;
i2c_adapter->algo = &saa7146_algo;
i2c_adapter->algo_data = NULL;
i2c_adapter->timeout = SAA7146_I2C_TIMEOUT;
i2c_adapter->retries = SAA7146_I2C_RETRIES;
}
return 0;
}
@@ -0,0 +1,445 @@
// SPDX-License-Identifier: GPL-2.0
#include <media/drv-intf/saa7146_vv.h>
static int vbi_pixel_to_capture = 720 * 2;
static int vbi_workaround(struct saa7146_dev *dev)
{
struct saa7146_vv *vv = dev->vv_data;
u32 *cpu;
dma_addr_t dma_addr;
int count = 0;
int i;
DECLARE_WAITQUEUE(wait, current);
DEB_VBI("dev:%p\n", dev);
/* once again, a bug in the saa7146: the brs acquisition
is buggy and especially the BXO-counter does not work
as specified. there is this workaround, but please
don't let me explain it. ;-) */
cpu = dma_alloc_coherent(&dev->pci->dev, 4096, &dma_addr, GFP_KERNEL);
if (NULL == cpu)
return -ENOMEM;
/* setup some basic programming, just for the workaround */
saa7146_write(dev, BASE_EVEN3, dma_addr);
saa7146_write(dev, BASE_ODD3, dma_addr+vbi_pixel_to_capture);
saa7146_write(dev, PROT_ADDR3, dma_addr+4096);
saa7146_write(dev, PITCH3, vbi_pixel_to_capture);
saa7146_write(dev, BASE_PAGE3, 0x0);
saa7146_write(dev, NUM_LINE_BYTE3, (2<<16)|((vbi_pixel_to_capture)<<0));
saa7146_write(dev, MC2, MASK_04|MASK_20);
/* load brs-control register */
WRITE_RPS1(CMD_WR_REG | (1 << 8) | (BRS_CTRL/4));
/* BXO = 1h, BRS to outbound */
WRITE_RPS1(0xc000008c);
/* wait for vbi_a or vbi_b*/
if ( 0 != (SAA7146_USE_PORT_B_FOR_VBI & dev->ext_vv_data->flags)) {
DEB_D("...using port b\n");
WRITE_RPS1(CMD_PAUSE | CMD_OAN | CMD_SIG1 | CMD_E_FID_B);
WRITE_RPS1(CMD_PAUSE | CMD_OAN | CMD_SIG1 | CMD_O_FID_B);
/*
WRITE_RPS1(CMD_PAUSE | MASK_09);
*/
} else {
DEB_D("...using port a\n");
WRITE_RPS1(CMD_PAUSE | MASK_10);
}
/* upload brs */
WRITE_RPS1(CMD_UPLOAD | MASK_08);
/* load brs-control register */
WRITE_RPS1(CMD_WR_REG | (1 << 8) | (BRS_CTRL/4));
/* BYO = 1, BXO = NQBIL (=1728 for PAL, for NTSC this is 858*2) - NumByte3 (=1440) = 288 */
WRITE_RPS1(((1728-(vbi_pixel_to_capture)) << 7) | MASK_19);
/* wait for brs_done */
WRITE_RPS1(CMD_PAUSE | MASK_08);
/* upload brs */
WRITE_RPS1(CMD_UPLOAD | MASK_08);
/* load video-dma3 NumLines3 and NumBytes3 */
WRITE_RPS1(CMD_WR_REG | (1 << 8) | (NUM_LINE_BYTE3/4));
/* dev->vbi_count*2 lines, 720 pixel (= 1440 Bytes) */
WRITE_RPS1((2 << 16) | (vbi_pixel_to_capture));
/* load brs-control register */
WRITE_RPS1(CMD_WR_REG | (1 << 8) | (BRS_CTRL/4));
/* Set BRS right: note: this is an experimental value for BXO (=> PAL!) */
WRITE_RPS1((540 << 7) | (5 << 19)); // 5 == vbi_start
/* wait for brs_done */
WRITE_RPS1(CMD_PAUSE | MASK_08);
/* upload brs and video-dma3*/
WRITE_RPS1(CMD_UPLOAD | MASK_08 | MASK_04);
/* load mc2 register: enable dma3 */
WRITE_RPS1(CMD_WR_REG | (1 << 8) | (MC1/4));
WRITE_RPS1(MASK_20 | MASK_04);
/* generate interrupt */
WRITE_RPS1(CMD_INTERRUPT);
/* stop rps1 */
WRITE_RPS1(CMD_STOP);
/* we have to do the workaround twice to be sure that
everything is ok */
for(i = 0; i < 2; i++) {
/* indicate to the irq handler that we do the workaround */
saa7146_write(dev, MC2, MASK_31|MASK_15);
saa7146_write(dev, NUM_LINE_BYTE3, (1<<16)|(2<<0));
saa7146_write(dev, MC2, MASK_04|MASK_20);
/* enable rps1 irqs */
SAA7146_IER_ENABLE(dev,MASK_28);
/* prepare to wait to be woken up by the irq-handler */
add_wait_queue(&vv->vbi_wq, &wait);
set_current_state(TASK_INTERRUPTIBLE);
/* start rps1 to enable workaround */
saa7146_write(dev, RPS_ADDR1, dev->d_rps1.dma_handle);
saa7146_write(dev, MC1, (MASK_13 | MASK_29));
schedule();
DEB_VBI("brs bug workaround %d/1\n", i);
remove_wait_queue(&vv->vbi_wq, &wait);
__set_current_state(TASK_RUNNING);
/* disable rps1 irqs */
SAA7146_IER_DISABLE(dev,MASK_28);
/* stop video-dma3 */
saa7146_write(dev, MC1, MASK_20);
if(signal_pending(current)) {
DEB_VBI("aborted (rps:0x%08x)\n",
saa7146_read(dev, RPS_ADDR1));
/* stop rps1 for sure */
saa7146_write(dev, MC1, MASK_29);
dma_free_coherent(&dev->pci->dev, 4096, cpu, dma_addr);
return -EINTR;
}
}
dma_free_coherent(&dev->pci->dev, 4096, cpu, dma_addr);
return 0;
}
static void saa7146_set_vbi_capture(struct saa7146_dev *dev, struct saa7146_buf *buf, struct saa7146_buf *next)
{
struct saa7146_vv *vv = dev->vv_data;
struct saa7146_video_dma vdma3;
int count = 0;
unsigned long e_wait = vv->current_hps_sync == SAA7146_HPS_SYNC_PORT_A ? CMD_E_FID_A : CMD_E_FID_B;
unsigned long o_wait = vv->current_hps_sync == SAA7146_HPS_SYNC_PORT_A ? CMD_O_FID_A : CMD_O_FID_B;
/*
vdma3.base_even = 0xc8000000+2560*70;
vdma3.base_odd = 0xc8000000;
vdma3.prot_addr = 0xc8000000+2560*164;
vdma3.pitch = 2560;
vdma3.base_page = 0;
vdma3.num_line_byte = (64<<16)|((vbi_pixel_to_capture)<<0); // set above!
*/
vdma3.base_even = buf->pt[2].offset;
vdma3.base_odd = buf->pt[2].offset + 16 * vbi_pixel_to_capture;
vdma3.prot_addr = buf->pt[2].offset + 16 * 2 * vbi_pixel_to_capture;
vdma3.pitch = vbi_pixel_to_capture;
vdma3.base_page = buf->pt[2].dma | ME1;
vdma3.num_line_byte = (16 << 16) | vbi_pixel_to_capture;
saa7146_write_out_dma(dev, 3, &vdma3);
/* write beginning of rps-program */
count = 0;
/* wait for o_fid_a/b / e_fid_a/b toggle only if bit 1 is not set */
/* we don't wait here for the first field anymore. this is different from the video
capture and might cause that the first buffer is only half filled (with only
one field). but since this is some sort of streaming data, this is not that negative.
but by doing this, we can use the whole engine from videobuf-dma-sg.c... */
/*
WRITE_RPS1(CMD_PAUSE | CMD_OAN | CMD_SIG1 | e_wait);
WRITE_RPS1(CMD_PAUSE | CMD_OAN | CMD_SIG1 | o_wait);
*/
/* set bit 1 */
WRITE_RPS1(CMD_WR_REG | (1 << 8) | (MC2/4));
WRITE_RPS1(MASK_28 | MASK_12);
/* turn on video-dma3 */
WRITE_RPS1(CMD_WR_REG_MASK | (MC1/4));
WRITE_RPS1(MASK_04 | MASK_20); /* => mask */
WRITE_RPS1(MASK_04 | MASK_20); /* => values */
/* wait for o_fid_a/b / e_fid_a/b toggle */
WRITE_RPS1(CMD_PAUSE | o_wait);
WRITE_RPS1(CMD_PAUSE | e_wait);
/* generate interrupt */
WRITE_RPS1(CMD_INTERRUPT);
/* stop */
WRITE_RPS1(CMD_STOP);
/* enable rps1 irqs */
SAA7146_IER_ENABLE(dev, MASK_28);
/* write the address of the rps-program */
saa7146_write(dev, RPS_ADDR1, dev->d_rps1.dma_handle);
/* turn on rps */
saa7146_write(dev, MC1, (MASK_13 | MASK_29));
}
static int buffer_activate(struct saa7146_dev *dev,
struct saa7146_buf *buf,
struct saa7146_buf *next)
{
struct saa7146_vv *vv = dev->vv_data;
DEB_VBI("dev:%p, buf:%p, next:%p\n", dev, buf, next);
saa7146_set_vbi_capture(dev,buf,next);
mod_timer(&vv->vbi_dmaq.timeout, jiffies+BUFFER_TIMEOUT);
return 0;
}
/* ------------------------------------------------------------------ */
static int queue_setup(struct vb2_queue *q,
unsigned int *num_buffers, unsigned int *num_planes,
unsigned int sizes[], struct device *alloc_devs[])
{
unsigned int size = 16 * 2 * vbi_pixel_to_capture;
if (*num_planes)
return sizes[0] < size ? -EINVAL : 0;
*num_planes = 1;
sizes[0] = size;
return 0;
}
static void buf_queue(struct vb2_buffer *vb)
{
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
struct vb2_queue *vq = vb->vb2_queue;
struct saa7146_dev *dev = vb2_get_drv_priv(vq);
struct saa7146_buf *buf = container_of(vbuf, struct saa7146_buf, vb);
unsigned long flags;
spin_lock_irqsave(&dev->slock, flags);
saa7146_buffer_queue(dev, &dev->vv_data->vbi_dmaq, buf);
spin_unlock_irqrestore(&dev->slock, flags);
}
static int buf_init(struct vb2_buffer *vb)
{
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
struct saa7146_buf *buf = container_of(vbuf, struct saa7146_buf, vb);
struct sg_table *sgt = vb2_dma_sg_plane_desc(&buf->vb.vb2_buf, 0);
struct scatterlist *list = sgt->sgl;
int length = sgt->nents;
struct vb2_queue *vq = vb->vb2_queue;
struct saa7146_dev *dev = vb2_get_drv_priv(vq);
int ret;
buf->activate = buffer_activate;
saa7146_pgtable_alloc(dev->pci, &buf->pt[2]);
ret = saa7146_pgtable_build_single(dev->pci, &buf->pt[2],
list, length);
if (ret)
saa7146_pgtable_free(dev->pci, &buf->pt[2]);
return ret;
}
static int buf_prepare(struct vb2_buffer *vb)
{
unsigned int size = 16 * 2 * vbi_pixel_to_capture;
if (vb2_plane_size(vb, 0) < size)
return -EINVAL;
vb2_set_plane_payload(vb, 0, size);
return 0;
}
static void buf_cleanup(struct vb2_buffer *vb)
{
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
struct saa7146_buf *buf = container_of(vbuf, struct saa7146_buf, vb);
struct vb2_queue *vq = vb->vb2_queue;
struct saa7146_dev *dev = vb2_get_drv_priv(vq);
saa7146_pgtable_free(dev->pci, &buf->pt[2]);
}
static void return_buffers(struct vb2_queue *q, int state)
{
struct saa7146_dev *dev = vb2_get_drv_priv(q);
struct saa7146_dmaqueue *dq = &dev->vv_data->vbi_dmaq;
struct saa7146_buf *buf;
if (dq->curr) {
buf = dq->curr;
dq->curr = NULL;
vb2_buffer_done(&buf->vb.vb2_buf, state);
}
while (!list_empty(&dq->queue)) {
buf = list_entry(dq->queue.next, struct saa7146_buf, list);
list_del(&buf->list);
vb2_buffer_done(&buf->vb.vb2_buf, state);
}
}
static void vbi_stop(struct saa7146_dev *dev)
{
struct saa7146_vv *vv = dev->vv_data;
unsigned long flags;
DEB_VBI("dev:%p\n", dev);
spin_lock_irqsave(&dev->slock,flags);
/* disable rps1 */
saa7146_write(dev, MC1, MASK_29);
/* disable rps1 irqs */
SAA7146_IER_DISABLE(dev, MASK_28);
/* shut down dma 3 transfers */
saa7146_write(dev, MC1, MASK_20);
timer_delete(&vv->vbi_dmaq.timeout);
timer_delete(&vv->vbi_read_timeout);
spin_unlock_irqrestore(&dev->slock, flags);
}
static void vbi_read_timeout(struct timer_list *t)
{
struct saa7146_vv *vv = timer_container_of(vv, t, vbi_read_timeout);
struct saa7146_dev *dev = vv->vbi_dmaq.dev;
DEB_VBI("dev:%p\n", dev);
vbi_stop(dev);
}
static int vbi_begin(struct saa7146_dev *dev)
{
struct saa7146_vv *vv = dev->vv_data;
u32 arbtr_ctrl = saa7146_read(dev, PCI_BT_V1);
int ret = 0;
DEB_VBI("dev:%p\n", dev);
ret = saa7146_res_get(dev, RESOURCE_DMA3_BRS);
if (0 == ret) {
DEB_S("cannot get vbi RESOURCE_DMA3_BRS resource\n");
return -EBUSY;
}
/* adjust arbitrition control for video dma 3 */
arbtr_ctrl &= ~0x1f0000;
arbtr_ctrl |= 0x1d0000;
saa7146_write(dev, PCI_BT_V1, arbtr_ctrl);
saa7146_write(dev, MC2, (MASK_04|MASK_20));
vv->vbi_read_timeout.function = vbi_read_timeout;
/* initialize the brs */
if ( 0 != (SAA7146_USE_PORT_B_FOR_VBI & dev->ext_vv_data->flags)) {
saa7146_write(dev, BRS_CTRL, MASK_30|MASK_29 | (7 << 19));
} else {
saa7146_write(dev, BRS_CTRL, 0x00000001);
if (0 != (ret = vbi_workaround(dev))) {
DEB_VBI("vbi workaround failed!\n");
/* return ret;*/
}
}
/* upload brs register */
saa7146_write(dev, MC2, (MASK_08|MASK_24));
return 0;
}
static int start_streaming(struct vb2_queue *q, unsigned int count)
{
struct saa7146_dev *dev = vb2_get_drv_priv(q);
int ret;
if (!vb2_is_streaming(&dev->vv_data->vbi_dmaq.q))
dev->vv_data->seqnr = 0;
ret = vbi_begin(dev);
if (ret)
return_buffers(q, VB2_BUF_STATE_QUEUED);
return ret;
}
static void stop_streaming(struct vb2_queue *q)
{
struct saa7146_dev *dev = vb2_get_drv_priv(q);
vbi_stop(dev);
return_buffers(q, VB2_BUF_STATE_ERROR);
saa7146_res_free(dev, RESOURCE_DMA3_BRS);
}
const struct vb2_ops vbi_qops = {
.queue_setup = queue_setup,
.buf_queue = buf_queue,
.buf_init = buf_init,
.buf_prepare = buf_prepare,
.buf_cleanup = buf_cleanup,
.start_streaming = start_streaming,
.stop_streaming = stop_streaming,
};
/* ------------------------------------------------------------------ */
static void vbi_init(struct saa7146_dev *dev, struct saa7146_vv *vv)
{
DEB_VBI("dev:%p\n", dev);
INIT_LIST_HEAD(&vv->vbi_dmaq.queue);
timer_setup(&vv->vbi_dmaq.timeout, saa7146_buffer_timeout, 0);
vv->vbi_dmaq.dev = dev;
init_waitqueue_head(&vv->vbi_wq);
}
static void vbi_irq_done(struct saa7146_dev *dev, unsigned long status)
{
struct saa7146_vv *vv = dev->vv_data;
spin_lock(&dev->slock);
if (vv->vbi_dmaq.curr) {
DEB_VBI("dev:%p, curr:%p\n", dev, vv->vbi_dmaq.curr);
saa7146_buffer_finish(dev, &vv->vbi_dmaq, VB2_BUF_STATE_DONE);
} else {
DEB_VBI("dev:%p\n", dev);
}
saa7146_buffer_next(dev, &vv->vbi_dmaq, 1);
spin_unlock(&dev->slock);
}
const struct saa7146_use_ops saa7146_vbi_uops = {
.init = vbi_init,
.irq_done = vbi_irq_done,
};
@@ -0,0 +1,723 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <media/drv-intf/saa7146_vv.h>
#include <media/v4l2-event.h>
#include <media/v4l2-ctrls.h>
#include <linux/module.h>
#include <linux/kernel.h>
/* format descriptions for capture and preview */
static struct saa7146_format formats[] = {
{
.pixelformat = V4L2_PIX_FMT_RGB332,
.trans = RGB08_COMPOSED,
.depth = 8,
.flags = 0,
}, {
.pixelformat = V4L2_PIX_FMT_RGB565,
.trans = RGB16_COMPOSED,
.depth = 16,
.flags = 0,
}, {
.pixelformat = V4L2_PIX_FMT_BGR24,
.trans = RGB24_COMPOSED,
.depth = 24,
.flags = 0,
}, {
.pixelformat = V4L2_PIX_FMT_BGR32,
.trans = RGB32_COMPOSED,
.depth = 32,
.flags = 0,
}, {
.pixelformat = V4L2_PIX_FMT_RGB32,
.trans = RGB32_COMPOSED,
.depth = 32,
.flags = 0,
.swap = 0x2,
}, {
.pixelformat = V4L2_PIX_FMT_GREY,
.trans = Y8,
.depth = 8,
.flags = 0,
}, {
.pixelformat = V4L2_PIX_FMT_YUV422P,
.trans = YUV422_DECOMPOSED,
.depth = 16,
.flags = FORMAT_IS_PLANAR,
}, {
.pixelformat = V4L2_PIX_FMT_YVU420,
.trans = YUV420_DECOMPOSED,
.depth = 12,
.flags = FORMAT_BYTE_SWAP|FORMAT_IS_PLANAR,
}, {
.pixelformat = V4L2_PIX_FMT_YUV420,
.trans = YUV420_DECOMPOSED,
.depth = 12,
.flags = FORMAT_IS_PLANAR,
}, {
.pixelformat = V4L2_PIX_FMT_UYVY,
.trans = YUV422_COMPOSED,
.depth = 16,
.flags = 0,
}
};
/* unfortunately, the saa7146 contains a bug which prevents it from doing on-the-fly byte swaps.
due to this, it's impossible to provide additional *packed* formats, which are simply byte swapped
(like V4L2_PIX_FMT_YUYV) ... 8-( */
struct saa7146_format* saa7146_format_by_fourcc(struct saa7146_dev *dev, int fourcc)
{
int i;
for (i = 0; i < ARRAY_SIZE(formats); i++) {
if (formats[i].pixelformat == fourcc) {
return formats+i;
}
}
DEB_D("unknown pixelformat:'%4.4s'\n", (char *)&fourcc);
return NULL;
}
/********************************************************************************/
/* common pagetable functions */
static int saa7146_pgtable_build(struct saa7146_dev *dev, struct saa7146_buf *buf)
{
struct saa7146_vv *vv = dev->vv_data;
struct pci_dev *pci = dev->pci;
struct sg_table *sgt = vb2_dma_sg_plane_desc(&buf->vb.vb2_buf, 0);
struct scatterlist *list = sgt->sgl;
int length = sgt->nents;
struct v4l2_pix_format *pix = &vv->video_fmt;
struct saa7146_format *sfmt = saa7146_format_by_fourcc(dev, pix->pixelformat);
DEB_EE("dev:%p, buf:%p, sg_len:%d\n", dev, buf, length);
if( 0 != IS_PLANAR(sfmt->trans)) {
struct saa7146_pgtable *pt1 = &buf->pt[0];
struct saa7146_pgtable *pt2 = &buf->pt[1];
struct saa7146_pgtable *pt3 = &buf->pt[2];
struct sg_dma_page_iter dma_iter;
__le32 *ptr1, *ptr2, *ptr3;
__le32 fill;
int size = pix->width * pix->height;
int i, m1, m2, m3, o1, o2;
switch( sfmt->depth ) {
case 12: {
/* create some offsets inside the page table */
m1 = ((size + PAGE_SIZE) / PAGE_SIZE) - 1;
m2 = ((size + (size / 4) + PAGE_SIZE) / PAGE_SIZE) - 1;
m3 = ((size + (size / 2) + PAGE_SIZE) / PAGE_SIZE) - 1;
o1 = size % PAGE_SIZE;
o2 = (size + (size / 4)) % PAGE_SIZE;
DEB_CAP("size:%d, m1:%d, m2:%d, m3:%d, o1:%d, o2:%d\n",
size, m1, m2, m3, o1, o2);
break;
}
case 16: {
/* create some offsets inside the page table */
m1 = ((size + PAGE_SIZE) / PAGE_SIZE) - 1;
m2 = ((size + (size / 2) + PAGE_SIZE) / PAGE_SIZE) - 1;
m3 = ((2 * size + PAGE_SIZE) / PAGE_SIZE) - 1;
o1 = size % PAGE_SIZE;
o2 = (size + (size / 2)) % PAGE_SIZE;
DEB_CAP("size:%d, m1:%d, m2:%d, m3:%d, o1:%d, o2:%d\n",
size, m1, m2, m3, o1, o2);
break;
}
default: {
return -1;
}
}
ptr1 = pt1->cpu;
ptr2 = pt2->cpu;
ptr3 = pt3->cpu;
for_each_sg_dma_page(list, &dma_iter, length, 0)
*ptr1++ = cpu_to_le32(sg_page_iter_dma_address(&dma_iter) - list->offset);
/* if we have a user buffer, the first page may not be
aligned to a page boundary. */
pt1->offset = sgt->sgl->offset;
pt2->offset = pt1->offset + o1;
pt3->offset = pt1->offset + o2;
/* create video-dma2 page table */
ptr1 = pt1->cpu;
for (i = m1; i <= m2; i++, ptr2++)
*ptr2 = ptr1[i];
fill = *(ptr2 - 1);
for (; i < 1024; i++, ptr2++)
*ptr2 = fill;
/* create video-dma3 page table */
ptr1 = pt1->cpu;
for (i = m2; i <= m3; i++, ptr3++)
*ptr3 = ptr1[i];
fill = *(ptr3 - 1);
for (; i < 1024; i++, ptr3++)
*ptr3 = fill;
/* finally: finish up video-dma1 page table */
ptr1 = pt1->cpu + m1;
fill = pt1->cpu[m1];
for (i = m1; i < 1024; i++, ptr1++)
*ptr1 = fill;
} else {
struct saa7146_pgtable *pt = &buf->pt[0];
return saa7146_pgtable_build_single(pci, pt, list, length);
}
return 0;
}
/********************************************************************************/
/* file operations */
static int video_begin(struct saa7146_dev *dev)
{
struct saa7146_vv *vv = dev->vv_data;
struct saa7146_format *fmt = NULL;
unsigned int resource;
int ret = 0;
DEB_EE("dev:%p\n", dev);
fmt = saa7146_format_by_fourcc(dev, vv->video_fmt.pixelformat);
/* we need to have a valid format set here */
if (!fmt)
return -EINVAL;
if (0 != (fmt->flags & FORMAT_IS_PLANAR)) {
resource = RESOURCE_DMA1_HPS|RESOURCE_DMA2_CLP|RESOURCE_DMA3_BRS;
} else {
resource = RESOURCE_DMA1_HPS;
}
ret = saa7146_res_get(dev, resource);
if (0 == ret) {
DEB_S("cannot get capture resource %d\n", resource);
return -EBUSY;
}
/* clear out beginning of streaming bit (rps register 0)*/
saa7146_write(dev, MC2, MASK_27 );
/* enable rps0 irqs */
SAA7146_IER_ENABLE(dev, MASK_27);
return 0;
}
static void video_end(struct saa7146_dev *dev)
{
struct saa7146_vv *vv = dev->vv_data;
struct saa7146_format *fmt = NULL;
unsigned long flags;
unsigned int resource;
u32 dmas = 0;
DEB_EE("dev:%p\n", dev);
fmt = saa7146_format_by_fourcc(dev, vv->video_fmt.pixelformat);
/* we need to have a valid format set here */
if (!fmt)
return;
if (0 != (fmt->flags & FORMAT_IS_PLANAR)) {
resource = RESOURCE_DMA1_HPS|RESOURCE_DMA2_CLP|RESOURCE_DMA3_BRS;
dmas = MASK_22 | MASK_21 | MASK_20;
} else {
resource = RESOURCE_DMA1_HPS;
dmas = MASK_22;
}
spin_lock_irqsave(&dev->slock,flags);
/* disable rps0 */
saa7146_write(dev, MC1, MASK_28);
/* disable rps0 irqs */
SAA7146_IER_DISABLE(dev, MASK_27);
/* shut down all used video dma transfers */
saa7146_write(dev, MC1, dmas);
spin_unlock_irqrestore(&dev->slock, flags);
saa7146_res_free(dev, resource);
}
static int vidioc_querycap(struct file *file, void *fh, struct v4l2_capability *cap)
{
struct saa7146_dev *dev = video_drvdata(file);
strscpy((char *)cap->driver, "saa7146 v4l2", sizeof(cap->driver));
strscpy((char *)cap->card, dev->ext->name, sizeof(cap->card));
cap->capabilities = V4L2_CAP_VIDEO_CAPTURE |
V4L2_CAP_READWRITE | V4L2_CAP_STREAMING |
V4L2_CAP_DEVICE_CAPS;
cap->capabilities |= dev->ext_vv_data->capabilities;
return 0;
}
static int vidioc_enum_fmt_vid_cap(struct file *file, void *fh, struct v4l2_fmtdesc *f)
{
if (f->index >= ARRAY_SIZE(formats))
return -EINVAL;
f->pixelformat = formats[f->index].pixelformat;
return 0;
}
int saa7146_s_ctrl(struct v4l2_ctrl *ctrl)
{
struct saa7146_dev *dev = container_of(ctrl->handler,
struct saa7146_dev, ctrl_handler);
struct saa7146_vv *vv = dev->vv_data;
u32 val;
switch (ctrl->id) {
case V4L2_CID_BRIGHTNESS:
val = saa7146_read(dev, BCS_CTRL);
val &= 0x00ffffff;
val |= (ctrl->val << 24);
saa7146_write(dev, BCS_CTRL, val);
saa7146_write(dev, MC2, MASK_22 | MASK_06);
break;
case V4L2_CID_CONTRAST:
val = saa7146_read(dev, BCS_CTRL);
val &= 0xff00ffff;
val |= (ctrl->val << 16);
saa7146_write(dev, BCS_CTRL, val);
saa7146_write(dev, MC2, MASK_22 | MASK_06);
break;
case V4L2_CID_SATURATION:
val = saa7146_read(dev, BCS_CTRL);
val &= 0xffffff00;
val |= (ctrl->val << 0);
saa7146_write(dev, BCS_CTRL, val);
saa7146_write(dev, MC2, MASK_22 | MASK_06);
break;
case V4L2_CID_HFLIP:
/* fixme: we can support changing VFLIP and HFLIP here... */
if (vb2_is_busy(&vv->video_dmaq.q))
return -EBUSY;
vv->hflip = ctrl->val;
break;
case V4L2_CID_VFLIP:
if (vb2_is_busy(&vv->video_dmaq.q))
return -EBUSY;
vv->vflip = ctrl->val;
break;
default:
return -EINVAL;
}
return 0;
}
static int vidioc_g_parm(struct file *file, void *fh,
struct v4l2_streamparm *parm)
{
struct saa7146_dev *dev = video_drvdata(file);
struct saa7146_vv *vv = dev->vv_data;
if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
return -EINVAL;
parm->parm.capture.readbuffers = 1;
v4l2_video_std_frame_period(vv->standard->id,
&parm->parm.capture.timeperframe);
return 0;
}
static int vidioc_g_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *f)
{
struct saa7146_dev *dev = video_drvdata(file);
struct saa7146_vv *vv = dev->vv_data;
f->fmt.pix = vv->video_fmt;
return 0;
}
static int vidioc_g_fmt_vbi_cap(struct file *file, void *fh, struct v4l2_format *f)
{
struct saa7146_dev *dev = video_drvdata(file);
struct saa7146_vv *vv = dev->vv_data;
f->fmt.vbi = vv->vbi_fmt;
return 0;
}
static int vidioc_try_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *f)
{
struct saa7146_dev *dev = video_drvdata(file);
struct saa7146_vv *vv = dev->vv_data;
struct saa7146_format *fmt;
enum v4l2_field field;
int maxw, maxh;
int calc_bpl;
DEB_EE("V4L2_BUF_TYPE_VIDEO_CAPTURE: dev:%p, fh:%p\n", dev, fh);
fmt = saa7146_format_by_fourcc(dev, f->fmt.pix.pixelformat);
if (NULL == fmt)
return -EINVAL;
field = f->fmt.pix.field;
maxw = vv->standard->h_max_out;
maxh = vv->standard->v_max_out;
if (V4L2_FIELD_ANY == field) {
field = (f->fmt.pix.height > maxh / 2)
? V4L2_FIELD_INTERLACED
: V4L2_FIELD_BOTTOM;
}
switch (field) {
case V4L2_FIELD_ALTERNATE:
case V4L2_FIELD_TOP:
case V4L2_FIELD_BOTTOM:
maxh = maxh / 2;
break;
default:
field = V4L2_FIELD_INTERLACED;
break;
}
f->fmt.pix.field = field;
f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
if (f->fmt.pix.width < 48)
f->fmt.pix.width = 48;
if (f->fmt.pix.height < 32)
f->fmt.pix.height = 32;
if (f->fmt.pix.width > maxw)
f->fmt.pix.width = maxw;
if (f->fmt.pix.height > maxh)
f->fmt.pix.height = maxh;
calc_bpl = (f->fmt.pix.width * fmt->depth) / 8;
if (f->fmt.pix.bytesperline < calc_bpl)
f->fmt.pix.bytesperline = calc_bpl;
if (f->fmt.pix.bytesperline > (2 * PAGE_SIZE * fmt->depth) / 8) /* arbitrary constraint */
f->fmt.pix.bytesperline = calc_bpl;
f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * f->fmt.pix.height;
DEB_D("w:%d, h:%d, bytesperline:%d, sizeimage:%d\n",
f->fmt.pix.width, f->fmt.pix.height,
f->fmt.pix.bytesperline, f->fmt.pix.sizeimage);
return 0;
}
static int vidioc_s_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *f)
{
struct saa7146_dev *dev = video_drvdata(file);
struct saa7146_vv *vv = dev->vv_data;
int err;
DEB_EE("V4L2_BUF_TYPE_VIDEO_CAPTURE: dev:%p\n", dev);
if (vb2_is_busy(&vv->video_dmaq.q)) {
DEB_EE("streaming capture is active\n");
return -EBUSY;
}
err = vidioc_try_fmt_vid_cap(file, fh, f);
if (0 != err)
return err;
switch (f->fmt.pix.field) {
case V4L2_FIELD_ALTERNATE:
vv->last_field = V4L2_FIELD_TOP;
break;
default:
vv->last_field = V4L2_FIELD_INTERLACED;
break;
}
vv->video_fmt = f->fmt.pix;
DEB_EE("set to pixelformat '%4.4s'\n",
(char *)&vv->video_fmt.pixelformat);
return 0;
}
static int vidioc_g_std(struct file *file, void *fh, v4l2_std_id *norm)
{
struct saa7146_dev *dev = video_drvdata(file);
struct saa7146_vv *vv = dev->vv_data;
*norm = vv->standard->id;
return 0;
}
static int vidioc_s_std(struct file *file, void *fh, v4l2_std_id id)
{
struct saa7146_dev *dev = video_drvdata(file);
struct saa7146_vv *vv = dev->vv_data;
int found = 0;
int i;
DEB_EE("VIDIOC_S_STD\n");
for (i = 0; i < dev->ext_vv_data->num_stds; i++)
if (id & dev->ext_vv_data->stds[i].id)
break;
if (i != dev->ext_vv_data->num_stds &&
vv->standard == &dev->ext_vv_data->stds[i])
return 0;
if (vb2_is_busy(&vv->video_dmaq.q) || vb2_is_busy(&vv->vbi_dmaq.q)) {
DEB_D("cannot change video standard while streaming capture is active\n");
return -EBUSY;
}
if (i != dev->ext_vv_data->num_stds) {
vv->standard = &dev->ext_vv_data->stds[i];
if (NULL != dev->ext_vv_data->std_callback)
dev->ext_vv_data->std_callback(dev, vv->standard);
found = 1;
}
if (!found) {
DEB_EE("VIDIOC_S_STD: standard not found\n");
return -EINVAL;
}
DEB_EE("VIDIOC_S_STD: set to standard to '%s'\n", vv->standard->name);
return 0;
}
const struct v4l2_ioctl_ops saa7146_video_ioctl_ops = {
.vidioc_querycap = vidioc_querycap,
.vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap,
.vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap,
.vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap,
.vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap,
.vidioc_g_std = vidioc_g_std,
.vidioc_s_std = vidioc_s_std,
.vidioc_g_parm = vidioc_g_parm,
.vidioc_reqbufs = vb2_ioctl_reqbufs,
.vidioc_create_bufs = vb2_ioctl_create_bufs,
.vidioc_querybuf = vb2_ioctl_querybuf,
.vidioc_qbuf = vb2_ioctl_qbuf,
.vidioc_dqbuf = vb2_ioctl_dqbuf,
.vidioc_streamon = vb2_ioctl_streamon,
.vidioc_streamoff = vb2_ioctl_streamoff,
.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
};
const struct v4l2_ioctl_ops saa7146_vbi_ioctl_ops = {
.vidioc_querycap = vidioc_querycap,
.vidioc_g_fmt_vbi_cap = vidioc_g_fmt_vbi_cap,
.vidioc_try_fmt_vbi_cap = vidioc_g_fmt_vbi_cap,
.vidioc_s_fmt_vbi_cap = vidioc_g_fmt_vbi_cap,
.vidioc_g_std = vidioc_g_std,
.vidioc_s_std = vidioc_s_std,
.vidioc_g_parm = vidioc_g_parm,
.vidioc_reqbufs = vb2_ioctl_reqbufs,
.vidioc_create_bufs = vb2_ioctl_create_bufs,
.vidioc_querybuf = vb2_ioctl_querybuf,
.vidioc_qbuf = vb2_ioctl_qbuf,
.vidioc_dqbuf = vb2_ioctl_dqbuf,
.vidioc_streamon = vb2_ioctl_streamon,
.vidioc_streamoff = vb2_ioctl_streamoff,
.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
};
/*********************************************************************************/
/* buffer handling functions */
static int buffer_activate (struct saa7146_dev *dev,
struct saa7146_buf *buf,
struct saa7146_buf *next)
{
struct saa7146_vv *vv = dev->vv_data;
saa7146_set_capture(dev,buf,next);
mod_timer(&vv->video_dmaq.timeout, jiffies+BUFFER_TIMEOUT);
return 0;
}
static void release_all_pagetables(struct saa7146_dev *dev, struct saa7146_buf *buf)
{
saa7146_pgtable_free(dev->pci, &buf->pt[0]);
saa7146_pgtable_free(dev->pci, &buf->pt[1]);
saa7146_pgtable_free(dev->pci, &buf->pt[2]);
}
static int queue_setup(struct vb2_queue *q,
unsigned int *num_buffers, unsigned int *num_planes,
unsigned int sizes[], struct device *alloc_devs[])
{
struct saa7146_dev *dev = vb2_get_drv_priv(q);
unsigned int size = dev->vv_data->video_fmt.sizeimage;
if (*num_planes)
return sizes[0] < size ? -EINVAL : 0;
*num_planes = 1;
sizes[0] = size;
return 0;
}
static void buf_queue(struct vb2_buffer *vb)
{
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
struct vb2_queue *vq = vb->vb2_queue;
struct saa7146_dev *dev = vb2_get_drv_priv(vq);
struct saa7146_buf *buf = container_of(vbuf, struct saa7146_buf, vb);
unsigned long flags;
spin_lock_irqsave(&dev->slock, flags);
saa7146_buffer_queue(dev, &dev->vv_data->video_dmaq, buf);
spin_unlock_irqrestore(&dev->slock, flags);
}
static int buf_init(struct vb2_buffer *vb)
{
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
struct saa7146_buf *buf = container_of(vbuf, struct saa7146_buf, vb);
struct vb2_queue *vq = vb->vb2_queue;
struct saa7146_dev *dev = vb2_get_drv_priv(vq);
struct saa7146_vv *vv = dev->vv_data;
struct saa7146_format *sfmt;
int ret;
buf->activate = buffer_activate;
sfmt = saa7146_format_by_fourcc(dev, vv->video_fmt.pixelformat);
if (IS_PLANAR(sfmt->trans)) {
saa7146_pgtable_alloc(dev->pci, &buf->pt[0]);
saa7146_pgtable_alloc(dev->pci, &buf->pt[1]);
saa7146_pgtable_alloc(dev->pci, &buf->pt[2]);
} else {
saa7146_pgtable_alloc(dev->pci, &buf->pt[0]);
}
ret = saa7146_pgtable_build(dev, buf);
if (ret)
release_all_pagetables(dev, buf);
return ret;
}
static int buf_prepare(struct vb2_buffer *vb)
{
struct vb2_queue *vq = vb->vb2_queue;
struct saa7146_dev *dev = vb2_get_drv_priv(vq);
struct saa7146_vv *vv = dev->vv_data;
unsigned int size = vv->video_fmt.sizeimage;
if (vb2_plane_size(vb, 0) < size)
return -EINVAL;
vb2_set_plane_payload(vb, 0, size);
return 0;
}
static void buf_cleanup(struct vb2_buffer *vb)
{
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
struct saa7146_buf *buf = container_of(vbuf, struct saa7146_buf, vb);
struct vb2_queue *vq = vb->vb2_queue;
struct saa7146_dev *dev = vb2_get_drv_priv(vq);
release_all_pagetables(dev, buf);
}
static void return_buffers(struct vb2_queue *q, int state)
{
struct saa7146_dev *dev = vb2_get_drv_priv(q);
struct saa7146_dmaqueue *dq = &dev->vv_data->video_dmaq;
struct saa7146_buf *buf;
if (dq->curr) {
buf = dq->curr;
dq->curr = NULL;
vb2_buffer_done(&buf->vb.vb2_buf, state);
}
while (!list_empty(&dq->queue)) {
buf = list_entry(dq->queue.next, struct saa7146_buf, list);
list_del(&buf->list);
vb2_buffer_done(&buf->vb.vb2_buf, state);
}
}
static int start_streaming(struct vb2_queue *q, unsigned int count)
{
struct saa7146_dev *dev = vb2_get_drv_priv(q);
int ret;
if (!vb2_is_streaming(&dev->vv_data->video_dmaq.q))
dev->vv_data->seqnr = 0;
ret = video_begin(dev);
if (ret)
return_buffers(q, VB2_BUF_STATE_QUEUED);
return ret;
}
static void stop_streaming(struct vb2_queue *q)
{
struct saa7146_dev *dev = vb2_get_drv_priv(q);
struct saa7146_dmaqueue *dq = &dev->vv_data->video_dmaq;
timer_delete(&dq->timeout);
video_end(dev);
return_buffers(q, VB2_BUF_STATE_ERROR);
}
const struct vb2_ops video_qops = {
.queue_setup = queue_setup,
.buf_queue = buf_queue,
.buf_init = buf_init,
.buf_prepare = buf_prepare,
.buf_cleanup = buf_cleanup,
.start_streaming = start_streaming,
.stop_streaming = stop_streaming,
};
/********************************************************************************/
/* file operations */
static void video_init(struct saa7146_dev *dev, struct saa7146_vv *vv)
{
INIT_LIST_HEAD(&vv->video_dmaq.queue);
timer_setup(&vv->video_dmaq.timeout, saa7146_buffer_timeout, 0);
vv->video_dmaq.dev = dev;
/* set some default values */
vv->standard = &dev->ext_vv_data->stds[0];
/* FIXME: what's this? */
vv->current_hps_source = SAA7146_HPS_SOURCE_PORT_A;
vv->current_hps_sync = SAA7146_HPS_SYNC_PORT_A;
}
static void video_irq_done(struct saa7146_dev *dev, unsigned long st)
{
struct saa7146_vv *vv = dev->vv_data;
struct saa7146_dmaqueue *q = &vv->video_dmaq;
spin_lock(&dev->slock);
DEB_CAP("called\n");
/* only finish the buffer if we have one... */
if (q->curr)
saa7146_buffer_finish(dev, q, VB2_BUF_STATE_DONE);
saa7146_buffer_next(dev,q,0);
spin_unlock(&dev->slock);
}
const struct saa7146_use_ops saa7146_video_uops = {
.init = video_init,
.irq_done = video_irq_done,
};
@@ -0,0 +1,33 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Siano Mobile Silicon Digital TV device configuration
#
config SMS_SIANO_MDTV
tristate
depends on DVB_CORE && HAS_DMA
depends on !RC_CORE || RC_CORE
depends on SMS_USB_DRV || SMS_SDIO_DRV
default y
config SMS_SIANO_RC
bool "Enable Remote Controller support for Siano devices"
depends on SMS_SIANO_MDTV && RC_CORE
depends on SMS_USB_DRV || SMS_SDIO_DRV
depends on MEDIA_COMMON_OPTIONS
default y
help
Choose Y to select Remote Controller support for Siano driver.
config SMS_SIANO_DEBUGFS
bool "Enable debugfs for smsdvb"
depends on SMS_SIANO_MDTV
depends on DEBUG_FS
depends on SMS_USB_DRV = SMS_SDIO_DRV
help
Choose Y to enable visualizing a dump of the frontend
statistics response packets via debugfs. Currently, works
only with Siano USB devices.
Useful only for developers. In doubt, say N.
@@ -0,0 +1,13 @@
# SPDX-License-Identifier: GPL-2.0
smsmdtv-objs := smscoreapi.o sms-cards.o smsendian.o
smsdvb-objs := smsdvb-main.o
obj-$(CONFIG_SMS_SIANO_MDTV) += smsmdtv.o smsdvb.o
ifeq ($(CONFIG_SMS_SIANO_RC),y)
smsmdtv-objs += smsir.o
endif
ifeq ($(CONFIG_SMS_SIANO_DEBUGFS),y)
smsdvb-objs += smsdvb-debugfs.o
endif
@@ -0,0 +1,348 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Card-specific functions for the Siano SMS1xxx USB dongle
*
* Copyright (c) 2008 Michael Krufky <mkrufky@linuxtv.org>
*/
#include "sms-cards.h"
#include "smsir.h"
#include <linux/module.h>
static struct sms_board sms_boards[] = {
[SMS_BOARD_UNKNOWN] = {
.name = "Unknown board",
.type = SMS_UNKNOWN_TYPE,
.default_mode = DEVICE_MODE_NONE,
},
[SMS1XXX_BOARD_SIANO_STELLAR] = {
.name = "Siano Stellar Digital Receiver",
.type = SMS_STELLAR,
.default_mode = DEVICE_MODE_DVBT_BDA,
},
[SMS1XXX_BOARD_SIANO_NOVA_A] = {
.name = "Siano Nova A Digital Receiver",
.type = SMS_NOVA_A0,
.default_mode = DEVICE_MODE_DVBT_BDA,
},
[SMS1XXX_BOARD_SIANO_NOVA_B] = {
.name = "Siano Nova B Digital Receiver",
.type = SMS_NOVA_B0,
.default_mode = DEVICE_MODE_DVBT_BDA,
},
[SMS1XXX_BOARD_SIANO_VEGA] = {
.name = "Siano Vega Digital Receiver",
.type = SMS_VEGA,
.default_mode = DEVICE_MODE_CMMB,
},
[SMS1XXX_BOARD_HAUPPAUGE_CATAMOUNT] = {
.name = "Hauppauge Catamount",
.type = SMS_STELLAR,
.fw[DEVICE_MODE_DVBT_BDA] = SMS_FW_DVBT_STELLAR,
.default_mode = DEVICE_MODE_DVBT_BDA,
},
[SMS1XXX_BOARD_HAUPPAUGE_OKEMO_A] = {
.name = "Hauppauge Okemo-A",
.type = SMS_NOVA_A0,
.fw[DEVICE_MODE_DVBT_BDA] = SMS_FW_DVBT_NOVA_A,
.default_mode = DEVICE_MODE_DVBT_BDA,
},
[SMS1XXX_BOARD_HAUPPAUGE_OKEMO_B] = {
.name = "Hauppauge Okemo-B",
.type = SMS_NOVA_B0,
.fw[DEVICE_MODE_DVBT_BDA] = SMS_FW_DVBT_NOVA_B,
.default_mode = DEVICE_MODE_DVBT_BDA,
},
[SMS1XXX_BOARD_HAUPPAUGE_WINDHAM] = {
.name = "Hauppauge WinTV MiniStick",
.type = SMS_NOVA_B0,
.fw[DEVICE_MODE_ISDBT_BDA] = SMS_FW_ISDBT_HCW_55XXX,
.fw[DEVICE_MODE_DVBT_BDA] = SMS_FW_DVBT_HCW_55XXX,
.default_mode = DEVICE_MODE_DVBT_BDA,
.rc_codes = RC_MAP_HAUPPAUGE,
.board_cfg.leds_power = 26,
.board_cfg.led0 = 27,
.board_cfg.led1 = 28,
.board_cfg.ir = 9,
.led_power = 26,
.led_lo = 27,
.led_hi = 28,
},
[SMS1XXX_BOARD_HAUPPAUGE_TIGER_MINICARD] = {
.name = "Hauppauge WinTV MiniCard",
.type = SMS_NOVA_B0,
.fw[DEVICE_MODE_DVBT_BDA] = SMS_FW_DVBT_HCW_55XXX,
.default_mode = DEVICE_MODE_DVBT_BDA,
.lna_ctrl = 29,
.board_cfg.foreign_lna0_ctrl = 29,
.rf_switch = 17,
.board_cfg.rf_switch_uhf = 17,
},
[SMS1XXX_BOARD_HAUPPAUGE_TIGER_MINICARD_R2] = {
.name = "Hauppauge WinTV MiniCard Rev 2",
.type = SMS_NOVA_B0,
.fw[DEVICE_MODE_DVBT_BDA] = SMS_FW_DVBT_HCW_55XXX,
.default_mode = DEVICE_MODE_DVBT_BDA,
.lna_ctrl = -1,
},
[SMS1XXX_BOARD_SIANO_NICE] = {
.name = "Siano Nice Digital Receiver",
.type = SMS_NOVA_B0,
.default_mode = DEVICE_MODE_DVBT_BDA,
},
[SMS1XXX_BOARD_SIANO_VENICE] = {
.name = "Siano Venice Digital Receiver",
.type = SMS_VEGA,
.default_mode = DEVICE_MODE_CMMB,
},
[SMS1XXX_BOARD_SIANO_STELLAR_ROM] = {
.name = "Siano Stellar Digital Receiver ROM",
.type = SMS_STELLAR,
.default_mode = DEVICE_MODE_DVBT_BDA,
.intf_num = 1,
},
[SMS1XXX_BOARD_ZTE_DVB_DATA_CARD] = {
.name = "ZTE Data Card Digital Receiver",
.type = SMS_NOVA_B0,
.default_mode = DEVICE_MODE_DVBT_BDA,
.intf_num = 5,
.mtu = 15792,
},
[SMS1XXX_BOARD_ONDA_MDTV_DATA_CARD] = {
.name = "ONDA Data Card Digital Receiver",
.type = SMS_NOVA_B0,
.default_mode = DEVICE_MODE_DVBT_BDA,
.intf_num = 6,
.mtu = 15792,
},
[SMS1XXX_BOARD_SIANO_MING] = {
.name = "Siano Ming Digital Receiver",
.type = SMS_MING,
.default_mode = DEVICE_MODE_CMMB,
},
[SMS1XXX_BOARD_SIANO_PELE] = {
.name = "Siano Pele Digital Receiver",
.type = SMS_PELE,
.default_mode = DEVICE_MODE_ISDBT_BDA,
},
[SMS1XXX_BOARD_SIANO_RIO] = {
.name = "Siano Rio Digital Receiver",
.type = SMS_RIO,
.default_mode = DEVICE_MODE_ISDBT_BDA,
},
[SMS1XXX_BOARD_SIANO_DENVER_1530] = {
.name = "Siano Denver (ATSC-M/H) Digital Receiver",
.type = SMS_DENVER_1530,
.default_mode = DEVICE_MODE_ATSC,
.crystal = 2400,
},
[SMS1XXX_BOARD_SIANO_DENVER_2160] = {
.name = "Siano Denver (TDMB) Digital Receiver",
.type = SMS_DENVER_2160,
.default_mode = DEVICE_MODE_DAB_TDMB,
},
[SMS1XXX_BOARD_PCTV_77E] = {
.name = "Hauppauge microStick 77e",
.type = SMS_NOVA_B0,
.fw[DEVICE_MODE_DVBT_BDA] = SMS_FW_DVB_NOVA_12MHZ_B0,
.default_mode = DEVICE_MODE_DVBT_BDA,
},
};
struct sms_board *sms_get_board(unsigned id)
{
BUG_ON(id >= ARRAY_SIZE(sms_boards));
return &sms_boards[id];
}
EXPORT_SYMBOL_GPL(sms_get_board);
static inline void sms_gpio_assign_11xx_default_led_config(
struct smscore_config_gpio *p_gpio_config) {
p_gpio_config->direction = SMS_GPIO_DIRECTION_OUTPUT;
p_gpio_config->inputcharacteristics =
SMS_GPIO_INPUTCHARACTERISTICS_NORMAL;
p_gpio_config->outputdriving = SMS_GPIO_OUTPUTDRIVING_4mA;
p_gpio_config->outputslewrate = SMS_GPIO_OUTPUT_SLEW_RATE_0_45_V_NS;
p_gpio_config->pullupdown = SMS_GPIO_PULLUPDOWN_NONE;
}
int sms_board_event(struct smscore_device_t *coredev,
enum SMS_BOARD_EVENTS gevent)
{
struct smscore_config_gpio my_gpio_config;
sms_gpio_assign_11xx_default_led_config(&my_gpio_config);
switch (gevent) {
case BOARD_EVENT_POWER_INIT: /* including hotplug */
break; /* BOARD_EVENT_BIND */
case BOARD_EVENT_POWER_SUSPEND:
break; /* BOARD_EVENT_POWER_SUSPEND */
case BOARD_EVENT_POWER_RESUME:
break; /* BOARD_EVENT_POWER_RESUME */
case BOARD_EVENT_BIND:
break; /* BOARD_EVENT_BIND */
case BOARD_EVENT_SCAN_PROG:
break; /* BOARD_EVENT_SCAN_PROG */
case BOARD_EVENT_SCAN_COMP:
break; /* BOARD_EVENT_SCAN_COMP */
case BOARD_EVENT_EMERGENCY_WARNING_SIGNAL:
break; /* BOARD_EVENT_EMERGENCY_WARNING_SIGNAL */
case BOARD_EVENT_FE_LOCK:
break; /* BOARD_EVENT_FE_LOCK */
case BOARD_EVENT_FE_UNLOCK:
break; /* BOARD_EVENT_FE_UNLOCK */
case BOARD_EVENT_DEMOD_LOCK:
break; /* BOARD_EVENT_DEMOD_LOCK */
case BOARD_EVENT_DEMOD_UNLOCK:
break; /* BOARD_EVENT_DEMOD_UNLOCK */
case BOARD_EVENT_RECEPTION_MAX_4:
break; /* BOARD_EVENT_RECEPTION_MAX_4 */
case BOARD_EVENT_RECEPTION_3:
break; /* BOARD_EVENT_RECEPTION_3 */
case BOARD_EVENT_RECEPTION_2:
break; /* BOARD_EVENT_RECEPTION_2 */
case BOARD_EVENT_RECEPTION_1:
break; /* BOARD_EVENT_RECEPTION_1 */
case BOARD_EVENT_RECEPTION_LOST_0:
break; /* BOARD_EVENT_RECEPTION_LOST_0 */
case BOARD_EVENT_MULTIPLEX_OK:
break; /* BOARD_EVENT_MULTIPLEX_OK */
case BOARD_EVENT_MULTIPLEX_ERRORS:
break; /* BOARD_EVENT_MULTIPLEX_ERRORS */
default:
pr_err("Unknown SMS board event\n");
break;
}
return 0;
}
EXPORT_SYMBOL_GPL(sms_board_event);
static int sms_set_gpio(struct smscore_device_t *coredev, int pin, int enable)
{
int lvl, ret;
u32 gpio;
struct smscore_config_gpio gpioconfig = {
.direction = SMS_GPIO_DIRECTION_OUTPUT,
.pullupdown = SMS_GPIO_PULLUPDOWN_NONE,
.inputcharacteristics = SMS_GPIO_INPUTCHARACTERISTICS_NORMAL,
.outputslewrate = SMS_GPIO_OUTPUT_SLEW_RATE_FAST,
.outputdriving = SMS_GPIO_OUTPUTDRIVING_S_4mA,
};
if (pin == 0)
return -EINVAL;
if (pin < 0) {
/* inverted gpio */
gpio = pin * -1;
lvl = enable ? 0 : 1;
} else {
gpio = pin;
lvl = enable ? 1 : 0;
}
ret = smscore_configure_gpio(coredev, gpio, &gpioconfig);
if (ret < 0)
return ret;
return smscore_set_gpio(coredev, gpio, lvl);
}
int sms_board_setup(struct smscore_device_t *coredev)
{
int board_id = smscore_get_board_id(coredev);
struct sms_board *board = sms_get_board(board_id);
switch (board_id) {
case SMS1XXX_BOARD_HAUPPAUGE_WINDHAM:
/* turn off all LEDs */
sms_set_gpio(coredev, board->led_power, 0);
sms_set_gpio(coredev, board->led_hi, 0);
sms_set_gpio(coredev, board->led_lo, 0);
break;
case SMS1XXX_BOARD_HAUPPAUGE_TIGER_MINICARD_R2:
case SMS1XXX_BOARD_HAUPPAUGE_TIGER_MINICARD:
/* turn off LNA */
sms_set_gpio(coredev, board->lna_ctrl, 0);
break;
}
return 0;
}
EXPORT_SYMBOL_GPL(sms_board_setup);
int sms_board_power(struct smscore_device_t *coredev, int onoff)
{
int board_id = smscore_get_board_id(coredev);
struct sms_board *board = sms_get_board(board_id);
switch (board_id) {
case SMS1XXX_BOARD_HAUPPAUGE_WINDHAM:
/* power LED */
sms_set_gpio(coredev,
board->led_power, onoff ? 1 : 0);
break;
case SMS1XXX_BOARD_HAUPPAUGE_TIGER_MINICARD_R2:
case SMS1XXX_BOARD_HAUPPAUGE_TIGER_MINICARD:
/* LNA */
if (!onoff)
sms_set_gpio(coredev, board->lna_ctrl, 0);
break;
}
return 0;
}
EXPORT_SYMBOL_GPL(sms_board_power);
int sms_board_led_feedback(struct smscore_device_t *coredev, int led)
{
int board_id = smscore_get_board_id(coredev);
struct sms_board *board = sms_get_board(board_id);
/* don't touch GPIO if LEDs are already set */
if (smscore_led_state(coredev, -1) == led)
return 0;
switch (board_id) {
case SMS1XXX_BOARD_HAUPPAUGE_WINDHAM:
sms_set_gpio(coredev,
board->led_lo, (led & SMS_LED_LO) ? 1 : 0);
sms_set_gpio(coredev,
board->led_hi, (led & SMS_LED_HI) ? 1 : 0);
smscore_led_state(coredev, led);
break;
}
return 0;
}
EXPORT_SYMBOL_GPL(sms_board_led_feedback);
int sms_board_lna_control(struct smscore_device_t *coredev, int onoff)
{
int board_id = smscore_get_board_id(coredev);
struct sms_board *board = sms_get_board(board_id);
pr_debug("%s: LNA %s\n", __func__, onoff ? "enabled" : "disabled");
switch (board_id) {
case SMS1XXX_BOARD_HAUPPAUGE_TIGER_MINICARD_R2:
case SMS1XXX_BOARD_HAUPPAUGE_TIGER_MINICARD:
sms_set_gpio(coredev,
board->rf_switch, onoff ? 1 : 0);
return sms_set_gpio(coredev,
board->lna_ctrl, onoff ? 1 : 0);
}
return -EINVAL;
}
EXPORT_SYMBOL_GPL(sms_board_lna_control);
int sms_board_load_modules(int id)
{
request_module("smsdvb");
return 0;
}
EXPORT_SYMBOL_GPL(sms_board_load_modules);
@@ -0,0 +1,127 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Card-specific functions for the Siano SMS1xxx USB dongle
*
* Copyright (c) 2008 Michael Krufky <mkrufky@linuxtv.org>
*/
#ifndef __SMS_CARDS_H__
#define __SMS_CARDS_H__
#include "smscoreapi.h"
#include <linux/usb.h>
#include "smsir.h"
#define SMS_BOARD_UNKNOWN 0
#define SMS1XXX_BOARD_SIANO_STELLAR 1
#define SMS1XXX_BOARD_SIANO_NOVA_A 2
#define SMS1XXX_BOARD_SIANO_NOVA_B 3
#define SMS1XXX_BOARD_SIANO_VEGA 4
#define SMS1XXX_BOARD_HAUPPAUGE_CATAMOUNT 5
#define SMS1XXX_BOARD_HAUPPAUGE_OKEMO_A 6
#define SMS1XXX_BOARD_HAUPPAUGE_OKEMO_B 7
#define SMS1XXX_BOARD_HAUPPAUGE_WINDHAM 8
#define SMS1XXX_BOARD_HAUPPAUGE_TIGER_MINICARD 9
#define SMS1XXX_BOARD_HAUPPAUGE_TIGER_MINICARD_R2 10
#define SMS1XXX_BOARD_SIANO_NICE 11
#define SMS1XXX_BOARD_SIANO_VENICE 12
#define SMS1XXX_BOARD_SIANO_STELLAR_ROM 13
#define SMS1XXX_BOARD_ZTE_DVB_DATA_CARD 14
#define SMS1XXX_BOARD_ONDA_MDTV_DATA_CARD 15
#define SMS1XXX_BOARD_SIANO_MING 16
#define SMS1XXX_BOARD_SIANO_PELE 17
#define SMS1XXX_BOARD_SIANO_RIO 18
#define SMS1XXX_BOARD_SIANO_DENVER_1530 19
#define SMS1XXX_BOARD_SIANO_DENVER_2160 20
#define SMS1XXX_BOARD_PCTV_77E 21
struct sms_board_gpio_cfg {
int lna_vhf_exist;
int lna_vhf_ctrl;
int lna_uhf_exist;
int lna_uhf_ctrl;
int lna_uhf_d_ctrl;
int lna_sband_exist;
int lna_sband_ctrl;
int lna_sband_d_ctrl;
int foreign_lna0_ctrl;
int foreign_lna1_ctrl;
int foreign_lna2_ctrl;
int rf_switch_vhf;
int rf_switch_uhf;
int rf_switch_sband;
int leds_power;
int led0;
int led1;
int led2;
int led3;
int led4;
int ir;
int eeprom_wp;
int mrc_sense;
int mrc_pdn_resetn;
int mrc_gp0; /* mrcs spi int */
int mrc_gp1;
int mrc_gp2;
int mrc_gp3;
int mrc_gp4;
int host_spi_gsp_ts_int;
};
struct sms_board {
enum sms_device_type_st type;
char *name, *fw[DEVICE_MODE_MAX];
struct sms_board_gpio_cfg board_cfg;
char *rc_codes; /* Name of IR codes table */
/* gpios */
int led_power, led_hi, led_lo, lna_ctrl, rf_switch;
char intf_num;
int default_mode;
unsigned int mtu;
unsigned int crystal;
struct sms_antenna_config_ST *antenna_config;
};
struct sms_board *sms_get_board(unsigned id);
extern struct smscore_device_t *coredev;
enum SMS_BOARD_EVENTS {
BOARD_EVENT_POWER_INIT,
BOARD_EVENT_POWER_SUSPEND,
BOARD_EVENT_POWER_RESUME,
BOARD_EVENT_BIND,
BOARD_EVENT_SCAN_PROG,
BOARD_EVENT_SCAN_COMP,
BOARD_EVENT_EMERGENCY_WARNING_SIGNAL,
BOARD_EVENT_FE_LOCK,
BOARD_EVENT_FE_UNLOCK,
BOARD_EVENT_DEMOD_LOCK,
BOARD_EVENT_DEMOD_UNLOCK,
BOARD_EVENT_RECEPTION_MAX_4,
BOARD_EVENT_RECEPTION_3,
BOARD_EVENT_RECEPTION_2,
BOARD_EVENT_RECEPTION_1,
BOARD_EVENT_RECEPTION_LOST_0,
BOARD_EVENT_MULTIPLEX_OK,
BOARD_EVENT_MULTIPLEX_ERRORS
};
int sms_board_event(struct smscore_device_t *coredev,
enum SMS_BOARD_EVENTS gevent);
int sms_board_setup(struct smscore_device_t *coredev);
#define SMS_LED_OFF 0
#define SMS_LED_LO 1
#define SMS_LED_HI 2
int sms_board_led_feedback(struct smscore_device_t *coredev, int led);
int sms_board_power(struct smscore_device_t *coredev, int onoff);
int sms_board_lna_control(struct smscore_device_t *coredev, int onoff);
extern int sms_board_load_modules(int id);
#endif /* __SMS_CARDS_H__ */
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,420 @@
// SPDX-License-Identifier: GPL-2.0+
//
// Copyright(c) 2013 Mauro Carvalho Chehab
#include "smscoreapi.h"
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/debugfs.h>
#include <linux/spinlock.h>
#include <linux/usb.h>
#include <media/dmxdev.h>
#include <media/dvbdev.h>
#include <media/dvb_demux.h>
#include <media/dvb_frontend.h>
#include "smsdvb.h"
static struct dentry *smsdvb_debugfs_usb_root;
struct smsdvb_debugfs {
struct kref refcount;
spinlock_t lock;
char stats_data[PAGE_SIZE];
unsigned stats_count;
bool stats_was_read;
wait_queue_head_t stats_queue;
};
static void smsdvb_print_dvb_stats(struct smsdvb_debugfs *debug_data,
struct sms_stats *p)
{
int n = 0;
char *buf;
spin_lock(&debug_data->lock);
if (debug_data->stats_count) {
spin_unlock(&debug_data->lock);
return;
}
buf = debug_data->stats_data;
n += sysfs_emit_at(buf, n, "is_rf_locked = %d\n", p->is_rf_locked);
n += sysfs_emit_at(buf, n, "is_demod_locked = %d\n", p->is_demod_locked);
n += sysfs_emit_at(buf, n, "is_external_lna_on = %d\n", p->is_external_lna_on);
n += sysfs_emit_at(buf, n, "SNR = %d\n", p->SNR);
n += sysfs_emit_at(buf, n, "ber = %d\n", p->ber);
n += sysfs_emit_at(buf, n, "FIB_CRC = %d\n", p->FIB_CRC);
n += sysfs_emit_at(buf, n, "ts_per = %d\n", p->ts_per);
n += sysfs_emit_at(buf, n, "MFER = %d\n", p->MFER);
n += sysfs_emit_at(buf, n, "RSSI = %d\n", p->RSSI);
n += sysfs_emit_at(buf, n, "in_band_pwr = %d\n", p->in_band_pwr);
n += sysfs_emit_at(buf, n, "carrier_offset = %d\n", p->carrier_offset);
n += sysfs_emit_at(buf, n, "modem_state = %d\n", p->modem_state);
n += sysfs_emit_at(buf, n, "frequency = %d\n", p->frequency);
n += sysfs_emit_at(buf, n, "bandwidth = %d\n", p->bandwidth);
n += sysfs_emit_at(buf, n, "transmission_mode = %d\n", p->transmission_mode);
n += sysfs_emit_at(buf, n, "modem_state = %d\n", p->modem_state);
n += sysfs_emit_at(buf, n, "guard_interval = %d\n", p->guard_interval);
n += sysfs_emit_at(buf, n, "code_rate = %d\n", p->code_rate);
n += sysfs_emit_at(buf, n, "lp_code_rate = %d\n", p->lp_code_rate);
n += sysfs_emit_at(buf, n, "hierarchy = %d\n", p->hierarchy);
n += sysfs_emit_at(buf, n, "constellation = %d\n", p->constellation);
n += sysfs_emit_at(buf, n, "burst_size = %d\n", p->burst_size);
n += sysfs_emit_at(buf, n, "burst_duration = %d\n", p->burst_duration);
n += sysfs_emit_at(buf, n, "burst_cycle_time = %d\n", p->burst_cycle_time);
n += sysfs_emit_at(buf, n, "calc_burst_cycle_time = %d\n", p->calc_burst_cycle_time);
n += sysfs_emit_at(buf, n, "num_of_rows = %d\n", p->num_of_rows);
n += sysfs_emit_at(buf, n, "num_of_padd_cols = %d\n", p->num_of_padd_cols);
n += sysfs_emit_at(buf, n, "num_of_punct_cols = %d\n", p->num_of_punct_cols);
n += sysfs_emit_at(buf, n, "error_ts_packets = %d\n", p->error_ts_packets);
n += sysfs_emit_at(buf, n, "total_ts_packets = %d\n", p->total_ts_packets);
n += sysfs_emit_at(buf, n, "num_of_valid_mpe_tlbs = %d\n", p->num_of_valid_mpe_tlbs);
n += sysfs_emit_at(buf, n, "num_of_invalid_mpe_tlbs = %d\n", p->num_of_invalid_mpe_tlbs);
n += sysfs_emit_at(buf, n, "num_of_corrected_mpe_tlbs = %d\n",
p->num_of_corrected_mpe_tlbs);
n += sysfs_emit_at(buf, n, "ber_error_count = %d\n", p->ber_error_count);
n += sysfs_emit_at(buf, n, "ber_bit_count = %d\n", p->ber_bit_count);
n += sysfs_emit_at(buf, n, "sms_to_host_tx_errors = %d\n", p->sms_to_host_tx_errors);
n += sysfs_emit_at(buf, n, "pre_ber = %d\n", p->pre_ber);
n += sysfs_emit_at(buf, n, "cell_id = %d\n", p->cell_id);
n += sysfs_emit_at(buf, n, "dvbh_srv_ind_hp = %d\n", p->dvbh_srv_ind_hp);
n += sysfs_emit_at(buf, n, "dvbh_srv_ind_lp = %d\n", p->dvbh_srv_ind_lp);
n += sysfs_emit_at(buf, n, "num_mpe_received = %d\n", p->num_mpe_received);
debug_data->stats_count = n;
spin_unlock(&debug_data->lock);
wake_up(&debug_data->stats_queue);
}
static void smsdvb_print_isdb_stats(struct smsdvb_debugfs *debug_data,
struct sms_isdbt_stats *p)
{
int i, n = 0;
char *buf;
spin_lock(&debug_data->lock);
if (debug_data->stats_count) {
spin_unlock(&debug_data->lock);
return;
}
buf = debug_data->stats_data;
n += sysfs_emit_at(buf, n, "statistics_type = %d\t", p->statistics_type);
n += sysfs_emit_at(buf, n, "full_size = %d\n", p->full_size);
n += sysfs_emit_at(buf, n, "is_rf_locked = %d\t\t", p->is_rf_locked);
n += sysfs_emit_at(buf, n, "is_demod_locked = %d\t", p->is_demod_locked);
n += sysfs_emit_at(buf, n, "is_external_lna_on = %d\n", p->is_external_lna_on);
n += sysfs_emit_at(buf, n, "SNR = %d dB\t\t", p->SNR);
n += sysfs_emit_at(buf, n, "RSSI = %d dBm\t\t", p->RSSI);
n += sysfs_emit_at(buf, n, "in_band_pwr = %d dBm\n", p->in_band_pwr);
n += sysfs_emit_at(buf, n, "carrier_offset = %d\t", p->carrier_offset);
n += sysfs_emit_at(buf, n, "bandwidth = %d\t\t", p->bandwidth);
n += sysfs_emit_at(buf, n, "frequency = %d Hz\n", p->frequency);
n += sysfs_emit_at(buf, n, "transmission_mode = %d\t", p->transmission_mode);
n += sysfs_emit_at(buf, n, "modem_state = %d\t\t", p->modem_state);
n += sysfs_emit_at(buf, n, "guard_interval = %d\n", p->guard_interval);
n += sysfs_emit_at(buf, n, "system_type = %d\t\t", p->system_type);
n += sysfs_emit_at(buf, n, "partial_reception = %d\t", p->partial_reception);
n += sysfs_emit_at(buf, n, "num_of_layers = %d\n", p->num_of_layers);
n += sysfs_emit_at(buf, n, "sms_to_host_tx_errors = %d\n", p->sms_to_host_tx_errors);
for (i = 0; i < 3; i++) {
if (p->layer_info[i].number_of_segments < 1 ||
p->layer_info[i].number_of_segments > 13)
continue;
n += sysfs_emit_at(buf, n, "\nLayer %d\n", i);
n += sysfs_emit_at(buf, n, "\tcode_rate = %d\t", p->layer_info[i].code_rate);
n += sysfs_emit_at(buf, n, "constellation = %d\n", p->layer_info[i].constellation);
n += sysfs_emit_at(buf, n, "\tber = %-5d\t", p->layer_info[i].ber);
n += sysfs_emit_at(buf, n, "\tber_error_count = %-5d\t",
p->layer_info[i].ber_error_count);
n += sysfs_emit_at(buf, n, "ber_bit_count = %-5d\n",
p->layer_info[i].ber_bit_count);
n += sysfs_emit_at(buf, n, "\tpre_ber = %-5d\t", p->layer_info[i].pre_ber);
n += sysfs_emit_at(buf, n, "\tts_per = %-5d\n", p->layer_info[i].ts_per);
n += sysfs_emit_at(buf, n, "\terror_ts_packets = %-5d\t",
p->layer_info[i].error_ts_packets);
n += sysfs_emit_at(buf, n, "total_ts_packets = %-5d\t",
p->layer_info[i].total_ts_packets);
n += sysfs_emit_at(buf, n, "ti_ldepth_i = %d\n", p->layer_info[i].ti_ldepth_i);
n += sysfs_emit_at(buf, n, "\tnumber_of_segments = %d\t",
p->layer_info[i].number_of_segments);
n += sysfs_emit_at(buf, n, "tmcc_errors = %d\n", p->layer_info[i].tmcc_errors);
}
debug_data->stats_count = n;
spin_unlock(&debug_data->lock);
wake_up(&debug_data->stats_queue);
}
static void smsdvb_print_isdb_stats_ex(struct smsdvb_debugfs *debug_data,
struct sms_isdbt_stats_ex *p)
{
int i, n = 0;
char *buf;
spin_lock(&debug_data->lock);
if (debug_data->stats_count) {
spin_unlock(&debug_data->lock);
return;
}
buf = debug_data->stats_data;
n += sysfs_emit_at(buf, n, "statistics_type = %d\t", p->statistics_type);
n += sysfs_emit_at(buf, n, "full_size = %d\n", p->full_size);
n += sysfs_emit_at(buf, n, "is_rf_locked = %d\t\t", p->is_rf_locked);
n += sysfs_emit_at(buf, n, "is_demod_locked = %d\t", p->is_demod_locked);
n += sysfs_emit_at(buf, n, "is_external_lna_on = %d\n", p->is_external_lna_on);
n += sysfs_emit_at(buf, n, "SNR = %d dB\t\t", p->SNR);
n += sysfs_emit_at(buf, n, "RSSI = %d dBm\t\t", p->RSSI);
n += sysfs_emit_at(buf, n, "in_band_pwr = %d dBm\n", p->in_band_pwr);
n += sysfs_emit_at(buf, n, "carrier_offset = %d\t", p->carrier_offset);
n += sysfs_emit_at(buf, n, "bandwidth = %d\t\t", p->bandwidth);
n += sysfs_emit_at(buf, n, "frequency = %d Hz\n", p->frequency);
n += sysfs_emit_at(buf, n, "transmission_mode = %d\t", p->transmission_mode);
n += sysfs_emit_at(buf, n, "modem_state = %d\t\t", p->modem_state);
n += sysfs_emit_at(buf, n, "guard_interval = %d\n", p->guard_interval);
n += sysfs_emit_at(buf, n, "system_type = %d\t\t", p->system_type);
n += sysfs_emit_at(buf, n, "partial_reception = %d\t", p->partial_reception);
n += sysfs_emit_at(buf, n, "num_of_layers = %d\n", p->num_of_layers);
n += sysfs_emit_at(buf, n, "segment_number = %d\t", p->segment_number);
n += sysfs_emit_at(buf, n, "tune_bw = %d\n", p->tune_bw);
for (i = 0; i < 3; i++) {
if (p->layer_info[i].number_of_segments < 1 ||
p->layer_info[i].number_of_segments > 13)
continue;
n += sysfs_emit_at(buf, n, "\nLayer %d\n", i);
n += sysfs_emit_at(buf, n, "\tcode_rate = %d\t", p->layer_info[i].code_rate);
n += sysfs_emit_at(buf, n, "constellation = %d\n", p->layer_info[i].constellation);
n += sysfs_emit_at(buf, n, "\tber = %-5d\t", p->layer_info[i].ber);
n += sysfs_emit_at(buf, n, "\tber_error_count = %-5d\t",
p->layer_info[i].ber_error_count);
n += sysfs_emit_at(buf, n, "ber_bit_count = %-5d\n",
p->layer_info[i].ber_bit_count);
n += sysfs_emit_at(buf, n, "\tpre_ber = %-5d\t", p->layer_info[i].pre_ber);
n += sysfs_emit_at(buf, n, "\tts_per = %-5d\n", p->layer_info[i].ts_per);
n += sysfs_emit_at(buf, n, "\terror_ts_packets = %-5d\t",
p->layer_info[i].error_ts_packets);
n += sysfs_emit_at(buf, n, "total_ts_packets = %-5d\t",
p->layer_info[i].total_ts_packets);
n += sysfs_emit_at(buf, n, "ti_ldepth_i = %d\n", p->layer_info[i].ti_ldepth_i);
n += sysfs_emit_at(buf, n, "\tnumber_of_segments = %d\t",
p->layer_info[i].number_of_segments);
n += sysfs_emit_at(buf, n, "tmcc_errors = %d\n", p->layer_info[i].tmcc_errors);
}
debug_data->stats_count = n;
spin_unlock(&debug_data->lock);
wake_up(&debug_data->stats_queue);
}
static int smsdvb_stats_open(struct inode *inode, struct file *file)
{
struct smsdvb_client_t *client = inode->i_private;
struct smsdvb_debugfs *debug_data = client->debug_data;
kref_get(&debug_data->refcount);
spin_lock(&debug_data->lock);
debug_data->stats_count = 0;
debug_data->stats_was_read = false;
spin_unlock(&debug_data->lock);
file->private_data = debug_data;
return 0;
}
static void smsdvb_debugfs_data_release(struct kref *ref)
{
struct smsdvb_debugfs *debug_data;
debug_data = container_of(ref, struct smsdvb_debugfs, refcount);
kfree(debug_data);
}
static int smsdvb_stats_wait_read(struct smsdvb_debugfs *debug_data)
{
int rc = 1;
spin_lock(&debug_data->lock);
if (debug_data->stats_was_read)
goto exit;
rc = debug_data->stats_count;
exit:
spin_unlock(&debug_data->lock);
return rc;
}
static __poll_t smsdvb_stats_poll(struct file *file, poll_table *wait)
{
struct smsdvb_debugfs *debug_data = file->private_data;
int rc;
kref_get(&debug_data->refcount);
poll_wait(file, &debug_data->stats_queue, wait);
rc = smsdvb_stats_wait_read(debug_data);
kref_put(&debug_data->refcount, smsdvb_debugfs_data_release);
return rc > 0 ? EPOLLIN | EPOLLRDNORM : 0;
}
static ssize_t smsdvb_stats_read(struct file *file, char __user *user_buf,
size_t nbytes, loff_t *ppos)
{
int rc = 0, len;
struct smsdvb_debugfs *debug_data = file->private_data;
kref_get(&debug_data->refcount);
if (file->f_flags & O_NONBLOCK) {
rc = smsdvb_stats_wait_read(debug_data);
if (!rc) {
rc = -EWOULDBLOCK;
goto ret;
}
} else {
rc = wait_event_interruptible(debug_data->stats_queue,
smsdvb_stats_wait_read(debug_data));
if (rc < 0)
goto ret;
}
if (debug_data->stats_was_read) {
rc = 0; /* EOF */
goto ret;
}
len = debug_data->stats_count - *ppos;
if (len >= 0)
rc = simple_read_from_buffer(user_buf, nbytes, ppos,
debug_data->stats_data, len);
else
rc = 0;
if (*ppos >= debug_data->stats_count) {
spin_lock(&debug_data->lock);
debug_data->stats_was_read = true;
spin_unlock(&debug_data->lock);
}
ret:
kref_put(&debug_data->refcount, smsdvb_debugfs_data_release);
return rc;
}
static int smsdvb_stats_release(struct inode *inode, struct file *file)
{
struct smsdvb_debugfs *debug_data = file->private_data;
spin_lock(&debug_data->lock);
debug_data->stats_was_read = true; /* return EOF to read() */
spin_unlock(&debug_data->lock);
wake_up_interruptible_sync(&debug_data->stats_queue);
kref_put(&debug_data->refcount, smsdvb_debugfs_data_release);
file->private_data = NULL;
return 0;
}
static const struct file_operations debugfs_stats_ops = {
.open = smsdvb_stats_open,
.poll = smsdvb_stats_poll,
.read = smsdvb_stats_read,
.release = smsdvb_stats_release,
.llseek = generic_file_llseek,
};
/*
* Functions used by smsdvb, in order to create the interfaces
*/
int smsdvb_debugfs_create(struct smsdvb_client_t *client)
{
struct smscore_device_t *coredev = client->coredev;
struct smsdvb_debugfs *debug_data;
if (!smsdvb_debugfs_usb_root || !coredev->is_usb_device)
return -ENODEV;
debug_data = kzalloc_obj(*client->debug_data);
if (!debug_data)
return -ENOMEM;
client->debugfs = debugfs_create_dir(coredev->devpath,
smsdvb_debugfs_usb_root);
debugfs_create_file("stats", S_IRUGO | S_IWUSR, client->debugfs,
client, &debugfs_stats_ops);
client->debug_data = debug_data;
client->prt_dvb_stats = smsdvb_print_dvb_stats;
client->prt_isdb_stats = smsdvb_print_isdb_stats;
client->prt_isdb_stats_ex = smsdvb_print_isdb_stats_ex;
init_waitqueue_head(&debug_data->stats_queue);
spin_lock_init(&debug_data->lock);
kref_init(&debug_data->refcount);
return 0;
}
void smsdvb_debugfs_release(struct smsdvb_client_t *client)
{
if (!client->debugfs)
return;
client->prt_dvb_stats = NULL;
client->prt_isdb_stats = NULL;
client->prt_isdb_stats_ex = NULL;
debugfs_remove_recursive(client->debugfs);
kref_put(&client->debug_data->refcount, smsdvb_debugfs_data_release);
client->debug_data = NULL;
client->debugfs = NULL;
}
void smsdvb_debugfs_register(void)
{
/*
* FIXME: This was written to debug Siano USB devices. So, it creates
* the debugfs node under <debugfs>/usb.
* A similar logic would be needed for Siano sdio devices, but, in that
* case, usb_debug_root is not a good choice.
*
* Perhaps the right fix here would be to create another sysfs root
* node for sdio-based boards, but this may need some logic at sdio
* subsystem.
*/
smsdvb_debugfs_usb_root = debugfs_create_dir("smsdvb", usb_debug_root);
}
void smsdvb_debugfs_unregister(void)
{
if (!smsdvb_debugfs_usb_root)
return;
debugfs_remove_recursive(smsdvb_debugfs_usb_root);
smsdvb_debugfs_usb_root = NULL;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,115 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/***********************************************************************
*
***********************************************************************/
struct smsdvb_debugfs;
struct smsdvb_client_t;
typedef void (*sms_prt_dvb_stats_t)(struct smsdvb_debugfs *debug_data,
struct sms_stats *p);
typedef void (*sms_prt_isdb_stats_t)(struct smsdvb_debugfs *debug_data,
struct sms_isdbt_stats *p);
typedef void (*sms_prt_isdb_stats_ex_t)
(struct smsdvb_debugfs *debug_data,
struct sms_isdbt_stats_ex *p);
struct smsdvb_client_t {
struct list_head entry;
struct smscore_device_t *coredev;
struct smscore_client_t *smsclient;
struct dvb_adapter adapter;
struct dvb_demux demux;
struct dmxdev dmxdev;
struct dvb_frontend frontend;
enum fe_status fe_status;
struct completion tune_done;
struct completion stats_done;
int last_per;
int legacy_ber, legacy_per;
int event_fe_state;
int event_unc_state;
unsigned long get_stats_jiffies;
int feed_users;
bool has_tuned;
/* stats debugfs data */
struct dentry *debugfs;
struct smsdvb_debugfs *debug_data;
sms_prt_dvb_stats_t prt_dvb_stats;
sms_prt_isdb_stats_t prt_isdb_stats;
sms_prt_isdb_stats_ex_t prt_isdb_stats_ex;
};
/*
* This struct is a mix of struct sms_rx_stats_ex and
* struct sms_srvm_signal_status.
* It was obtained by comparing the way it was filled by the original code
*/
struct RECEPTION_STATISTICS_PER_SLICES_S {
u32 result;
u32 snr;
s32 in_band_power;
u32 ts_packets;
u32 ets_packets;
u32 constellation;
u32 hp_code;
u32 tps_srv_ind_lp;
u32 tps_srv_ind_hp;
u32 cell_id;
u32 reason;
u32 request_id;
u32 modem_state; /* from SMSHOSTLIB_DVB_MODEM_STATE_ET */
u32 ber; /* Post Viterbi BER [1E-5] */
s32 RSSI; /* dBm */
s32 carrier_offset; /* Carrier Offset in bin/1024 */
u32 is_rf_locked; /* 0 - not locked, 1 - locked */
u32 is_demod_locked; /* 0 - not locked, 1 - locked */
u32 ber_bit_count; /* Total number of SYNC bits. */
u32 ber_error_count; /* Number of erroneous SYNC bits. */
s32 MRC_SNR; /* dB */
s32 mrc_in_band_pwr; /* In band power in dBM */
s32 MRC_RSSI; /* dBm */
};
/* From smsdvb-debugfs.c */
#ifdef CONFIG_SMS_SIANO_DEBUGFS
int smsdvb_debugfs_create(struct smsdvb_client_t *client);
void smsdvb_debugfs_release(struct smsdvb_client_t *client);
void smsdvb_debugfs_register(void);
void smsdvb_debugfs_unregister(void);
#else
static inline int smsdvb_debugfs_create(struct smsdvb_client_t *client)
{
return 0;
}
static inline void smsdvb_debugfs_release(struct smsdvb_client_t *client) {}
static inline void smsdvb_debugfs_register(void) {}
static inline void smsdvb_debugfs_unregister(void) {};
#endif
@@ -0,0 +1,94 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/****************************************************************
Siano Mobile Silicon, Inc.
MDTV receiver kernel modules.
Copyright (C) 2006-2009, Uri Shkolnik
****************************************************************/
#include <linux/export.h>
#include <asm/byteorder.h>
#include "smsendian.h"
#include "smscoreapi.h"
void smsendian_handle_tx_message(void *buffer)
{
#ifdef __BIG_ENDIAN
struct sms_msg_data *msg = buffer;
int i;
int msg_words;
u32 *msg_data = &msg->msg_data;
switch (msg->x_msg_header.msg_type) {
case MSG_SMS_DATA_DOWNLOAD_REQ:
{
msg->msg_data = le32_to_cpu((__force __le32)(msg->msg_data));
break;
}
default:
msg_words = (msg->x_msg_header.msg_length -
sizeof(struct sms_msg_hdr))/4;
for (i = 0; i < msg_words; i++)
msg_data[i] = le32_to_cpu((__force __le32)msg_data[i]);
break;
}
#endif /* __BIG_ENDIAN */
}
EXPORT_SYMBOL_GPL(smsendian_handle_tx_message);
void smsendian_handle_rx_message(void *buffer)
{
#ifdef __BIG_ENDIAN
struct sms_msg_data *msg = (struct sms_msg_data *)buffer;
int i;
int msg_words;
switch (msg->x_msg_header.msg_type) {
case MSG_SMS_GET_VERSION_EX_RES:
{
struct sms_version_res *ver =
(struct sms_version_res *) msg;
ver->chip_model = le16_to_cpu((__force __le16)ver->chip_model);
break;
}
case MSG_SMS_DVBT_BDA_DATA:
case MSG_SMS_DAB_CHANNEL:
case MSG_SMS_DATA_MSG:
{
break;
}
default:
{
u32 *msg_data = &msg->msg_data;
msg_words = (msg->x_msg_header.msg_length -
sizeof(struct sms_msg_hdr))/4;
for (i = 0; i < msg_words; i++)
msg_data[i] = le32_to_cpu((__force __le32)msg_data[i]);
break;
}
}
#endif /* __BIG_ENDIAN */
}
EXPORT_SYMBOL_GPL(smsendian_handle_rx_message);
void smsendian_handle_message_header(void *msg)
{
#ifdef __BIG_ENDIAN
struct sms_msg_hdr *phdr = (struct sms_msg_hdr *)msg;
phdr->msg_type = le16_to_cpu((__force __le16)phdr->msg_type);
phdr->msg_length = le16_to_cpu((__force __le16)phdr->msg_length);
phdr->msg_flags = le16_to_cpu((__force __le16)phdr->msg_flags);
#endif /* __BIG_ENDIAN */
}
EXPORT_SYMBOL_GPL(smsendian_handle_message_header);
@@ -0,0 +1,21 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/****************************************************************
Siano Mobile Silicon, Inc.
MDTV receiver kernel modules.
Copyright (C) 2006-2009, Uri Shkolnik
****************************************************************/
#ifndef __SMS_ENDIAN_H__
#define __SMS_ENDIAN_H__
#include <asm/byteorder.h>
extern void smsendian_handle_tx_message(void *buffer);
extern void smsendian_handle_rx_message(void *buffer);
extern void smsendian_handle_message_header(void *msg);
#endif /* __SMS_ENDIAN_H__ */
@@ -0,0 +1,98 @@
// SPDX-License-Identifier: GPL-2.0+
//
// Siano Mobile Silicon, Inc.
// MDTV receiver kernel modules.
// Copyright (C) 2006-2009, Uri Shkolnik
//
// Copyright (c) 2010 - Mauro Carvalho Chehab
// - Ported the driver to use rc-core
// - IR raw event decoding is now done at rc-core
// - Code almost re-written
#include "smscoreapi.h"
#include <linux/types.h>
#include <linux/input.h>
#include "smsir.h"
#include "sms-cards.h"
#define MODULE_NAME "smsmdtv"
void sms_ir_event(struct smscore_device_t *coredev, const char *buf, int len)
{
int i;
const s32 *samples = (const void *)buf;
for (i = 0; i < len >> 2; i++) {
struct ir_raw_event ev = {
.duration = abs(samples[i]),
.pulse = samples[i] <= 0
};
ir_raw_event_store(coredev->ir.dev, &ev);
}
ir_raw_event_handle(coredev->ir.dev);
}
int sms_ir_init(struct smscore_device_t *coredev)
{
int err;
int board_id = smscore_get_board_id(coredev);
struct rc_dev *dev;
pr_debug("Allocating rc device\n");
dev = rc_allocate_device(RC_DRIVER_IR_RAW);
if (!dev)
return -ENOMEM;
coredev->ir.controller = 0; /* Todo: vega/nova SPI number */
coredev->ir.timeout = US_TO_NS(IR_DEFAULT_TIMEOUT);
pr_debug("IR port %d, timeout %d ms\n",
coredev->ir.controller, coredev->ir.timeout);
snprintf(coredev->ir.name, sizeof(coredev->ir.name),
"SMS IR (%s)", sms_get_board(board_id)->name);
strscpy(coredev->ir.phys, coredev->devpath, sizeof(coredev->ir.phys));
strlcat(coredev->ir.phys, "/ir0", sizeof(coredev->ir.phys));
dev->device_name = coredev->ir.name;
dev->input_phys = coredev->ir.phys;
dev->dev.parent = coredev->device;
#if 0
/* TODO: properly initialize the parameters below */
dev->input_id.bustype = BUS_USB;
dev->input_id.version = 1;
dev->input_id.vendor = le16_to_cpu(dev->udev->descriptor.idVendor);
dev->input_id.product = le16_to_cpu(dev->udev->descriptor.idProduct);
#endif
dev->priv = coredev;
dev->allowed_protocols = RC_PROTO_BIT_ALL_IR_DECODER;
dev->map_name = sms_get_board(board_id)->rc_codes;
dev->driver_name = MODULE_NAME;
pr_debug("Input device (IR) %s is set for key events\n",
dev->device_name);
err = rc_register_device(dev);
if (err < 0) {
pr_err("Failed to register device\n");
rc_free_device(dev);
return err;
}
coredev->ir.dev = dev;
return 0;
}
void sms_ir_exit(struct smscore_device_t *coredev)
{
rc_unregister_device(coredev->ir.dev);
rc_free_device(coredev->ir.dev);
pr_debug("\n");
}
@@ -0,0 +1,48 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
*
* Siano Mobile Silicon, Inc.
* MDTV receiver kernel modules.
* Copyright (C) 2006-2009, Uri Shkolnik
*
* Copyright (c) 2010 - Mauro Carvalho Chehab
* - Ported the driver to use rc-core
* - IR raw event decoding is now done at rc-core
* - Code almost re-written
*/
#ifndef __SMS_IR_H__
#define __SMS_IR_H__
#include <linux/input.h>
#include <media/rc-core.h>
struct smscore_device_t;
struct ir_t {
struct rc_dev *dev;
char name[40];
char phys[32];
char *rc_codes;
u32 timeout;
u32 controller;
};
#ifdef CONFIG_SMS_SIANO_RC
int sms_ir_init(struct smscore_device_t *coredev);
void sms_ir_exit(struct smscore_device_t *coredev);
void sms_ir_event(struct smscore_device_t *coredev,
const char *buf, int len);
#else
inline static int sms_ir_init(struct smscore_device_t *coredev) {
return 0;
}
inline static void sms_ir_exit(struct smscore_device_t *coredev) {};
inline static void sms_ir_event(struct smscore_device_t *coredev,
const char *buf, int len) {};
#endif
#endif /* __SMS_IR_H__ */
@@ -0,0 +1,159 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
Retrieve encoded MAC address from 24C16 serial 2-wire EEPROM,
decode it and store it in the associated adapter struct for
use by dvb_net.c
This card appear to have the 24C16 write protect held to ground,
thus permitting normal read/write operation. Theoretically it
would be possible to write routines to burn a different (encoded)
MAC address into the EEPROM.
Robert Schlabbach GMX
Michael Glaum KVH Industries
Holger Waechtler Convergence
Copyright (C) 2002-2003 Ralph Metzler <rjkm@metzlerbros.de>
Metzler Brothers Systementwicklung GbR
*/
#include <asm/errno.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/string.h>
#include <linux/i2c.h>
#include <linux/etherdevice.h>
#include "ttpci-eeprom.h"
#if 1
#define dprintk(x...) do { printk(x); } while (0)
#else
#define dprintk(x...) do { } while (0)
#endif
static int check_mac_tt(u8 *buf)
{
int i;
u16 tmp = 0xffff;
for (i = 0; i < 8; i++) {
tmp = (tmp << 8) | ((tmp >> 8) ^ buf[i]);
tmp ^= (tmp >> 4) & 0x0f;
tmp ^= (tmp << 12) ^ ((tmp & 0xff) << 5);
}
tmp ^= 0xffff;
return (((tmp >> 8) ^ buf[8]) | ((tmp & 0xff) ^ buf[9]));
}
static int getmac_tt(u8 * decodedMAC, u8 * encodedMAC)
{
u8 xor[20] = { 0x72, 0x23, 0x68, 0x19, 0x5c, 0xa8, 0x71, 0x2c,
0x54, 0xd3, 0x7b, 0xf1, 0x9E, 0x23, 0x16, 0xf6,
0x1d, 0x36, 0x64, 0x78};
u8 data[20];
int i;
/* In case there is a sig check failure have the orig contents available */
memcpy(data, encodedMAC, 20);
for (i = 0; i < 20; i++)
data[i] ^= xor[i];
for (i = 0; i < 10; i++)
data[i] = ((data[2 * i + 1] << 8) | data[2 * i])
>> ((data[2 * i + 1] >> 6) & 3);
if (check_mac_tt(data))
return -ENODEV;
decodedMAC[0] = data[2]; decodedMAC[1] = data[1]; decodedMAC[2] = data[0];
decodedMAC[3] = data[6]; decodedMAC[4] = data[5]; decodedMAC[5] = data[4];
return 0;
}
int ttpci_eeprom_decode_mac(u8 *decodedMAC, u8 *encodedMAC)
{
u8 xor[20] = { 0x72, 0x23, 0x68, 0x19, 0x5c, 0xa8, 0x71, 0x2c,
0x54, 0xd3, 0x7b, 0xf1, 0x9E, 0x23, 0x16, 0xf6,
0x1d, 0x36, 0x64, 0x78};
u8 data[20];
int i;
memcpy(data, encodedMAC, 20);
for (i = 0; i < 20; i++)
data[i] ^= xor[i];
for (i = 0; i < 10; i++)
data[i] = ((data[2 * i + 1] << 8) | data[2 * i])
>> ((data[2 * i + 1] >> 6) & 3);
if (check_mac_tt(data))
return -ENODEV;
decodedMAC[0] = data[2];
decodedMAC[1] = data[1];
decodedMAC[2] = data[0];
decodedMAC[3] = data[6];
decodedMAC[4] = data[5];
decodedMAC[5] = data[4];
return 0;
}
EXPORT_SYMBOL(ttpci_eeprom_decode_mac);
static int ttpci_eeprom_read_encodedMAC(struct i2c_adapter *adapter, u8 * encodedMAC)
{
int ret;
u8 b0[] = { 0xcc };
struct i2c_msg msg[] = {
{ .addr = 0x50, .flags = 0, .buf = b0, .len = 1 },
{ .addr = 0x50, .flags = I2C_M_RD, .buf = encodedMAC, .len = 20 }
};
/* dprintk("%s\n", __func__); */
ret = i2c_transfer(adapter, msg, 2);
if (ret != 2) /* Assume EEPROM isn't there */
return (-ENODEV);
return 0;
}
int ttpci_eeprom_parse_mac(struct i2c_adapter *adapter, u8 *proposed_mac)
{
int ret;
u8 encodedMAC[20];
u8 decodedMAC[6];
ret = ttpci_eeprom_read_encodedMAC(adapter, encodedMAC);
if (ret != 0) { /* Will only be -ENODEV */
dprintk("Couldn't read from EEPROM: not there?\n");
eth_zero_addr(proposed_mac);
return ret;
}
ret = getmac_tt(decodedMAC, encodedMAC);
if( ret != 0 ) {
dprintk("adapter failed MAC signature check\n");
dprintk("encoded MAC from EEPROM was %*phC",
(int)sizeof(encodedMAC), &encodedMAC);
eth_zero_addr(proposed_mac);
return ret;
}
memcpy(proposed_mac, decodedMAC, 6);
dprintk("adapter has MAC addr = %pM\n", decodedMAC);
return 0;
}
EXPORT_SYMBOL(ttpci_eeprom_parse_mac);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Ralph Metzler, Marcus Metzler, others");
MODULE_DESCRIPTION("Decode dvb_net MAC address from EEPROM of PCI DVB cards made by Siemens, Technotrend, Hauppauge");
@@ -0,0 +1,22 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
Retrieve encoded MAC address from ATMEL ttpci_eeprom serial 2-wire EEPROM,
decode it and store it in associated adapter net device
Robert Schlabbach GMX
Michael Glaum KVH Industries
Holger Waechtler Convergence
*/
#ifndef __TTPCI_EEPROM_H__
#define __TTPCI_EEPROM_H__
#include <linux/types.h>
#include <linux/i2c.h>
extern int ttpci_eeprom_decode_mac(u8 *decodedMAC, u8 *encodedMAC);
extern int ttpci_eeprom_parse_mac(struct i2c_adapter *adapter, u8 *propsed_mac);
#endif
+759
View File
@@ -0,0 +1,759 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* tveeprom - eeprom decoder for tvcard configuration eeproms
*
* Data and decoding routines shamelessly borrowed from bttv-cards.c
* eeprom access routine shamelessly borrowed from bttv-if.c
* which are:
Copyright (C) 1996,97,98 Ralph Metzler (rjkm@thp.uni-koeln.de)
& Marcus Metzler (mocm@thp.uni-koeln.de)
(c) 1999-2001 Gerd Knorr <kraxel@goldbach.in-berlin.de>
* Adjustments to fit a more general model and all bugs:
Copyright (C) 2003 John Klar <linpvr at projectplasma.com>
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/videodev2.h>
#include <linux/i2c.h>
#include <media/tuner.h>
#include <media/tveeprom.h>
#include <media/v4l2-common.h>
MODULE_DESCRIPTION("i2c Hauppauge eeprom decoder driver");
MODULE_AUTHOR("John Klar");
MODULE_LICENSE("GPL");
#define STRM(array, i) \
(i < sizeof(array) / sizeof(char *) ? array[i] : "unknown")
/*
* The Hauppauge eeprom uses an 8bit field to determine which
* tuner formats the tuner supports.
*/
static const struct {
int id;
const char * const name;
} hauppauge_tuner_fmt[] = {
{ V4L2_STD_UNKNOWN, " UNKNOWN" },
{ V4L2_STD_UNKNOWN, " FM" },
{ V4L2_STD_B|V4L2_STD_GH, " PAL(B/G)" },
{ V4L2_STD_MN, " NTSC(M)" },
{ V4L2_STD_PAL_I, " PAL(I)" },
{ V4L2_STD_SECAM_L|V4L2_STD_SECAM_LC, " SECAM(L/L')" },
{ V4L2_STD_DK, " PAL(D/D1/K)" },
{ V4L2_STD_ATSC, " ATSC/DVB Digital" },
};
/* This is the full list of possible tuners. Many thanks to Hauppauge for
supplying this information. Note that many tuners where only used for
testing and never made it to the outside world. So you will only see
a subset in actual produced cards. */
static const struct {
int id;
const char * const name;
} hauppauge_tuner[] = {
/* 0-9 */
{ TUNER_ABSENT, "None" },
{ TUNER_ABSENT, "External" },
{ TUNER_ABSENT, "Unspecified" },
{ TUNER_PHILIPS_PAL, "Philips FI1216" },
{ TUNER_PHILIPS_SECAM, "Philips FI1216MF" },
{ TUNER_PHILIPS_NTSC, "Philips FI1236" },
{ TUNER_PHILIPS_PAL_I, "Philips FI1246" },
{ TUNER_PHILIPS_PAL_DK, "Philips FI1256" },
{ TUNER_PHILIPS_PAL, "Philips FI1216 MK2" },
{ TUNER_PHILIPS_SECAM, "Philips FI1216MF MK2" },
/* 10-19 */
{ TUNER_PHILIPS_NTSC, "Philips FI1236 MK2" },
{ TUNER_PHILIPS_PAL_I, "Philips FI1246 MK2" },
{ TUNER_PHILIPS_PAL_DK, "Philips FI1256 MK2" },
{ TUNER_TEMIC_NTSC, "Temic 4032FY5" },
{ TUNER_TEMIC_PAL, "Temic 4002FH5" },
{ TUNER_TEMIC_PAL_I, "Temic 4062FY5" },
{ TUNER_PHILIPS_PAL, "Philips FR1216 MK2" },
{ TUNER_PHILIPS_SECAM, "Philips FR1216MF MK2" },
{ TUNER_PHILIPS_NTSC, "Philips FR1236 MK2" },
{ TUNER_PHILIPS_PAL_I, "Philips FR1246 MK2" },
/* 20-29 */
{ TUNER_PHILIPS_PAL_DK, "Philips FR1256 MK2" },
{ TUNER_PHILIPS_PAL, "Philips FM1216" },
{ TUNER_PHILIPS_SECAM, "Philips FM1216MF" },
{ TUNER_PHILIPS_NTSC, "Philips FM1236" },
{ TUNER_PHILIPS_PAL_I, "Philips FM1246" },
{ TUNER_PHILIPS_PAL_DK, "Philips FM1256" },
{ TUNER_TEMIC_4036FY5_NTSC, "Temic 4036FY5" },
{ TUNER_ABSENT, "Samsung TCPN9082D" },
{ TUNER_ABSENT, "Samsung TCPM9092P" },
{ TUNER_TEMIC_4006FH5_PAL, "Temic 4006FH5" },
/* 30-39 */
{ TUNER_ABSENT, "Samsung TCPN9085D" },
{ TUNER_ABSENT, "Samsung TCPB9085P" },
{ TUNER_ABSENT, "Samsung TCPL9091P" },
{ TUNER_TEMIC_4039FR5_NTSC, "Temic 4039FR5" },
{ TUNER_PHILIPS_FQ1216ME, "Philips FQ1216 ME" },
{ TUNER_TEMIC_4066FY5_PAL_I, "Temic 4066FY5" },
{ TUNER_PHILIPS_NTSC, "Philips TD1536" },
{ TUNER_PHILIPS_NTSC, "Philips TD1536D" },
{ TUNER_PHILIPS_NTSC, "Philips FMR1236" }, /* mono radio */
{ TUNER_ABSENT, "Philips FI1256MP" },
/* 40-49 */
{ TUNER_ABSENT, "Samsung TCPQ9091P" },
{ TUNER_TEMIC_4006FN5_MULTI_PAL,"Temic 4006FN5" },
{ TUNER_TEMIC_4009FR5_PAL, "Temic 4009FR5" },
{ TUNER_TEMIC_4046FM5, "Temic 4046FM5" },
{ TUNER_TEMIC_4009FN5_MULTI_PAL_FM, "Temic 4009FN5" },
{ TUNER_ABSENT, "Philips TD1536D FH 44"},
{ TUNER_LG_NTSC_FM, "LG TP18NSR01F"},
{ TUNER_LG_PAL_FM, "LG TP18PSB01D"},
{ TUNER_LG_PAL, "LG TP18PSB11D"},
{ TUNER_LG_PAL_I_FM, "LG TAPC-I001D"},
/* 50-59 */
{ TUNER_LG_PAL_I, "LG TAPC-I701D"},
{ TUNER_ABSENT, "Temic 4042FI5"},
{ TUNER_MICROTUNE_4049FM5, "Microtune 4049 FM5"},
{ TUNER_ABSENT, "LG TPI8NSR11F"},
{ TUNER_ABSENT, "Microtune 4049 FM5 Alt I2C"},
{ TUNER_PHILIPS_FM1216ME_MK3, "Philips FQ1216ME MK3"},
{ TUNER_ABSENT, "Philips FI1236 MK3"},
{ TUNER_PHILIPS_FM1216ME_MK3, "Philips FM1216 ME MK3"},
{ TUNER_PHILIPS_FM1236_MK3, "Philips FM1236 MK3"},
{ TUNER_ABSENT, "Philips FM1216MP MK3"},
/* 60-69 */
{ TUNER_PHILIPS_FM1216ME_MK3, "LG S001D MK3"},
{ TUNER_ABSENT, "LG M001D MK3"},
{ TUNER_PHILIPS_FM1216ME_MK3, "LG S701D MK3"},
{ TUNER_ABSENT, "LG M701D MK3"},
{ TUNER_ABSENT, "Temic 4146FM5"},
{ TUNER_ABSENT, "Temic 4136FY5"},
{ TUNER_ABSENT, "Temic 4106FH5"},
{ TUNER_ABSENT, "Philips FQ1216LMP MK3"},
{ TUNER_LG_NTSC_TAPE, "LG TAPE H001F MK3"},
{ TUNER_LG_NTSC_TAPE, "LG TAPE H701F MK3"},
/* 70-79 */
{ TUNER_ABSENT, "LG TALN H200T"},
{ TUNER_ABSENT, "LG TALN H250T"},
{ TUNER_ABSENT, "LG TALN M200T"},
{ TUNER_ABSENT, "LG TALN Z200T"},
{ TUNER_ABSENT, "LG TALN S200T"},
{ TUNER_ABSENT, "Thompson DTT7595"},
{ TUNER_ABSENT, "Thompson DTT7592"},
{ TUNER_ABSENT, "Silicon TDA8275C1 8290"},
{ TUNER_ABSENT, "Silicon TDA8275C1 8290 FM"},
{ TUNER_ABSENT, "Thompson DTT757"},
/* 80-89 */
{ TUNER_PHILIPS_FQ1216LME_MK3, "Philips FQ1216LME MK3"},
{ TUNER_LG_PAL_NEW_TAPC, "LG TAPC G701D"},
{ TUNER_LG_NTSC_NEW_TAPC, "LG TAPC H791F"},
{ TUNER_LG_PAL_NEW_TAPC, "TCL 2002MB 3"},
{ TUNER_LG_PAL_NEW_TAPC, "TCL 2002MI 3"},
{ TUNER_TCL_2002N, "TCL 2002N 6A"},
{ TUNER_PHILIPS_FM1236_MK3, "Philips FQ1236 MK3"},
{ TUNER_SAMSUNG_TCPN_2121P30A, "Samsung TCPN 2121P30A"},
{ TUNER_ABSENT, "Samsung TCPE 4121P30A"},
{ TUNER_PHILIPS_FM1216ME_MK3, "TCL MFPE05 2"},
/* 90-99 */
{ TUNER_ABSENT, "LG TALN H202T"},
{ TUNER_PHILIPS_FQ1216AME_MK4, "Philips FQ1216AME MK4"},
{ TUNER_PHILIPS_FQ1236A_MK4, "Philips FQ1236A MK4"},
{ TUNER_ABSENT, "Philips FQ1286A MK4"},
{ TUNER_ABSENT, "Philips FQ1216ME MK5"},
{ TUNER_ABSENT, "Philips FQ1236 MK5"},
{ TUNER_SAMSUNG_TCPG_6121P30A, "Samsung TCPG 6121P30A"},
{ TUNER_TCL_2002MB, "TCL 2002MB_3H"},
{ TUNER_ABSENT, "TCL 2002MI_3H"},
{ TUNER_TCL_2002N, "TCL 2002N 5H"},
/* 100-109 */
{ TUNER_PHILIPS_FMD1216ME_MK3, "Philips FMD1216ME"},
{ TUNER_TEA5767, "Philips TEA5768HL FM Radio"},
{ TUNER_ABSENT, "Panasonic ENV57H12D5"},
{ TUNER_PHILIPS_FM1236_MK3, "TCL MFNM05-4"},
{ TUNER_PHILIPS_FM1236_MK3, "TCL MNM05-4"},
{ TUNER_PHILIPS_FM1216ME_MK3, "TCL MPE05-2"},
{ TUNER_ABSENT, "TCL MQNM05-4"},
{ TUNER_ABSENT, "LG TAPC-W701D"},
{ TUNER_ABSENT, "TCL 9886P-WM"},
{ TUNER_ABSENT, "TCL 1676NM-WM"},
/* 110-119 */
{ TUNER_ABSENT, "Thompson DTT75105"},
{ TUNER_ABSENT, "Conexant_CX24109"},
{ TUNER_TCL_2002N, "TCL M2523_5N_E"},
{ TUNER_TCL_2002MB, "TCL M2523_3DB_E"},
{ TUNER_ABSENT, "Philips 8275A"},
{ TUNER_ABSENT, "Microtune MT2060"},
{ TUNER_PHILIPS_FM1236_MK3, "Philips FM1236 MK5"},
{ TUNER_PHILIPS_FM1216ME_MK3, "Philips FM1216ME MK5"},
{ TUNER_ABSENT, "TCL M2523_3DI_E"},
{ TUNER_ABSENT, "Samsung THPD5222FG30A"},
/* 120-129 */
{ TUNER_XC2028, "Xceive XC3028"},
{ TUNER_PHILIPS_FQ1216LME_MK3, "Philips FQ1216LME MK5"},
{ TUNER_ABSENT, "Philips FQD1216LME"},
{ TUNER_ABSENT, "Conexant CX24118A"},
{ TUNER_ABSENT, "TCL DMF11WIP"},
{ TUNER_ABSENT, "TCL MFNM05_4H_E"},
{ TUNER_ABSENT, "TCL MNM05_4H_E"},
{ TUNER_ABSENT, "TCL MPE05_2H_E"},
{ TUNER_ABSENT, "TCL MQNM05_4_U"},
{ TUNER_ABSENT, "TCL M2523_5NH_E"},
/* 130-139 */
{ TUNER_ABSENT, "TCL M2523_3DBH_E"},
{ TUNER_ABSENT, "TCL M2523_3DIH_E"},
{ TUNER_ABSENT, "TCL MFPE05_2_U"},
{ TUNER_PHILIPS_FMD1216MEX_MK3, "Philips FMD1216MEX"},
{ TUNER_ABSENT, "Philips FRH2036B"},
{ TUNER_ABSENT, "Panasonic ENGF75_01GF"},
{ TUNER_ABSENT, "MaxLinear MXL5005"},
{ TUNER_ABSENT, "MaxLinear MXL5003"},
{ TUNER_ABSENT, "Xceive XC2028"},
{ TUNER_ABSENT, "Microtune MT2131"},
/* 140-149 */
{ TUNER_ABSENT, "Philips 8275A_8295"},
{ TUNER_ABSENT, "TCL MF02GIP_5N_E"},
{ TUNER_ABSENT, "TCL MF02GIP_3DB_E"},
{ TUNER_ABSENT, "TCL MF02GIP_3DI_E"},
{ TUNER_ABSENT, "Microtune MT2266"},
{ TUNER_ABSENT, "TCL MF10WPP_4N_E"},
{ TUNER_ABSENT, "LG TAPQ_H702F"},
{ TUNER_ABSENT, "TCL M09WPP_4N_E"},
{ TUNER_ABSENT, "MaxLinear MXL5005_v2"},
{ TUNER_PHILIPS_TDA8290, "Philips 18271_8295"},
/* 150-159 */
{ TUNER_XC5000, "Xceive XC5000"},
{ TUNER_ABSENT, "Xceive XC3028L"},
{ TUNER_ABSENT, "NXP 18271C2_716x"},
{ TUNER_ABSENT, "Xceive XC4000"},
{ TUNER_ABSENT, "Dibcom 7070"},
{ TUNER_PHILIPS_TDA8290, "NXP 18271C2"},
{ TUNER_ABSENT, "Siano SMS1010"},
{ TUNER_ABSENT, "Siano SMS1150"},
{ TUNER_ABSENT, "MaxLinear 5007"},
{ TUNER_ABSENT, "TCL M09WPP_2P_E"},
/* 160-169 */
{ TUNER_ABSENT, "Siano SMS1180"},
{ TUNER_ABSENT, "Maxim_MAX2165"},
{ TUNER_ABSENT, "Siano SMS1140"},
{ TUNER_ABSENT, "Siano SMS1150 B1"},
{ TUNER_ABSENT, "MaxLinear 111"},
{ TUNER_ABSENT, "Dibcom 7770"},
{ TUNER_ABSENT, "Siano SMS1180VNS"},
{ TUNER_ABSENT, "Siano SMS1184"},
{ TUNER_PHILIPS_FQ1236_MK5, "TCL M30WTP-4N-E"},
{ TUNER_ABSENT, "TCL_M11WPP_2PN_E"},
/* 170-179 */
{ TUNER_ABSENT, "MaxLinear 301"},
{ TUNER_ABSENT, "Mirics MSi001"},
{ TUNER_ABSENT, "MaxLinear MxL241SF"},
{ TUNER_XC5000C, "Xceive XC5000C"},
{ TUNER_ABSENT, "Montage M68TS2020"},
{ TUNER_ABSENT, "Siano SMS1530"},
{ TUNER_ABSENT, "Dibcom 7090"},
{ TUNER_ABSENT, "Xceive XC5200C"},
{ TUNER_ABSENT, "NXP 18273"},
{ TUNER_ABSENT, "Montage M88TS2022"},
/* 180-188 */
{ TUNER_ABSENT, "NXP 18272M"},
{ TUNER_ABSENT, "NXP 18272S"},
{ TUNER_ABSENT, "Mirics MSi003"},
{ TUNER_ABSENT, "MaxLinear MxL256"},
{ TUNER_ABSENT, "SiLabs Si2158"},
{ TUNER_ABSENT, "SiLabs Si2178"},
{ TUNER_ABSENT, "SiLabs Si2157"},
{ TUNER_ABSENT, "SiLabs Si2177"},
{ TUNER_ABSENT, "ITE IT9137FN"},
};
/* Use TVEEPROM_AUDPROC_INTERNAL for those audio 'chips' that are
* internal to a video chip, i.e. not a separate audio chip. */
static const struct {
u32 id;
const char * const name;
} audio_ic[] = {
/* 0-4 */
{ TVEEPROM_AUDPROC_NONE, "None" },
{ TVEEPROM_AUDPROC_OTHER, "TEA6300" },
{ TVEEPROM_AUDPROC_OTHER, "TEA6320" },
{ TVEEPROM_AUDPROC_OTHER, "TDA9850" },
{ TVEEPROM_AUDPROC_MSP, "MSP3400C" },
/* 5-9 */
{ TVEEPROM_AUDPROC_MSP, "MSP3410D" },
{ TVEEPROM_AUDPROC_MSP, "MSP3415" },
{ TVEEPROM_AUDPROC_MSP, "MSP3430" },
{ TVEEPROM_AUDPROC_MSP, "MSP3438" },
{ TVEEPROM_AUDPROC_OTHER, "CS5331" },
/* 10-14 */
{ TVEEPROM_AUDPROC_MSP, "MSP3435" },
{ TVEEPROM_AUDPROC_MSP, "MSP3440" },
{ TVEEPROM_AUDPROC_MSP, "MSP3445" },
{ TVEEPROM_AUDPROC_MSP, "MSP3411" },
{ TVEEPROM_AUDPROC_MSP, "MSP3416" },
/* 15-19 */
{ TVEEPROM_AUDPROC_MSP, "MSP3425" },
{ TVEEPROM_AUDPROC_MSP, "MSP3451" },
{ TVEEPROM_AUDPROC_MSP, "MSP3418" },
{ TVEEPROM_AUDPROC_OTHER, "Type 0x12" },
{ TVEEPROM_AUDPROC_OTHER, "OKI7716" },
/* 20-24 */
{ TVEEPROM_AUDPROC_MSP, "MSP4410" },
{ TVEEPROM_AUDPROC_MSP, "MSP4420" },
{ TVEEPROM_AUDPROC_MSP, "MSP4440" },
{ TVEEPROM_AUDPROC_MSP, "MSP4450" },
{ TVEEPROM_AUDPROC_MSP, "MSP4408" },
/* 25-29 */
{ TVEEPROM_AUDPROC_MSP, "MSP4418" },
{ TVEEPROM_AUDPROC_MSP, "MSP4428" },
{ TVEEPROM_AUDPROC_MSP, "MSP4448" },
{ TVEEPROM_AUDPROC_MSP, "MSP4458" },
{ TVEEPROM_AUDPROC_MSP, "Type 0x1d" },
/* 30-34 */
{ TVEEPROM_AUDPROC_INTERNAL, "CX880" },
{ TVEEPROM_AUDPROC_INTERNAL, "CX881" },
{ TVEEPROM_AUDPROC_INTERNAL, "CX883" },
{ TVEEPROM_AUDPROC_INTERNAL, "CX882" },
{ TVEEPROM_AUDPROC_INTERNAL, "CX25840" },
/* 35-39 */
{ TVEEPROM_AUDPROC_INTERNAL, "CX25841" },
{ TVEEPROM_AUDPROC_INTERNAL, "CX25842" },
{ TVEEPROM_AUDPROC_INTERNAL, "CX25843" },
{ TVEEPROM_AUDPROC_INTERNAL, "CX23418" },
{ TVEEPROM_AUDPROC_INTERNAL, "CX23885" },
/* 40-44 */
{ TVEEPROM_AUDPROC_INTERNAL, "CX23888" },
{ TVEEPROM_AUDPROC_INTERNAL, "SAA7131" },
{ TVEEPROM_AUDPROC_INTERNAL, "CX23887" },
{ TVEEPROM_AUDPROC_INTERNAL, "SAA7164" },
{ TVEEPROM_AUDPROC_INTERNAL, "AU8522" },
/* 45-49 */
{ TVEEPROM_AUDPROC_INTERNAL, "AVF4910B" },
{ TVEEPROM_AUDPROC_INTERNAL, "SAA7231" },
{ TVEEPROM_AUDPROC_INTERNAL, "CX23102" },
{ TVEEPROM_AUDPROC_INTERNAL, "SAA7163" },
{ TVEEPROM_AUDPROC_OTHER, "AK4113" },
/* 50-52 */
{ TVEEPROM_AUDPROC_OTHER, "CS5340" },
{ TVEEPROM_AUDPROC_OTHER, "CS8416" },
{ TVEEPROM_AUDPROC_OTHER, "CX20810" },
};
/* This list is supplied by Hauppauge. Thanks! */
static const char *decoderIC[] = {
/* 0-4 */
"None", "BT815", "BT817", "BT819", "BT815A",
/* 5-9 */
"BT817A", "BT819A", "BT827", "BT829", "BT848",
/* 10-14 */
"BT848A", "BT849A", "BT829A", "BT827A", "BT878",
/* 15-19 */
"BT879", "BT880", "VPX3226E", "SAA7114", "SAA7115",
/* 20-24 */
"CX880", "CX881", "CX883", "SAA7111", "SAA7113",
/* 25-29 */
"CX882", "TVP5150A", "CX25840", "CX25841", "CX25842",
/* 30-34 */
"CX25843", "CX23418", "NEC61153", "CX23885", "CX23888",
/* 35-39 */
"SAA7131", "CX25837", "CX23887", "CX23885A", "CX23887A",
/* 40-44 */
"SAA7164", "CX23885B", "AU8522", "ADV7401", "AVF4910B",
/* 45-49 */
"SAA7231", "CX23102", "SAA7163", "ADV7441A", "ADV7181C",
/* 50-53 */
"CX25836", "TDA9955", "TDA19977", "ADV7842"
};
static int hasRadioTuner(int tunerType)
{
switch (tunerType) {
case 18: /* PNPEnv_TUNER_FR1236_MK2 */
case 23: /* PNPEnv_TUNER_FM1236 */
case 38: /* PNPEnv_TUNER_FMR1236 */
case 16: /* PNPEnv_TUNER_FR1216_MK2 */
case 19: /* PNPEnv_TUNER_FR1246_MK2 */
case 21: /* PNPEnv_TUNER_FM1216 */
case 24: /* PNPEnv_TUNER_FM1246 */
case 17: /* PNPEnv_TUNER_FR1216MF_MK2 */
case 22: /* PNPEnv_TUNER_FM1216MF */
case 20: /* PNPEnv_TUNER_FR1256_MK2 */
case 25: /* PNPEnv_TUNER_FM1256 */
case 33: /* PNPEnv_TUNER_4039FR5 */
case 42: /* PNPEnv_TUNER_4009FR5 */
case 52: /* PNPEnv_TUNER_4049FM5 */
case 54: /* PNPEnv_TUNER_4049FM5_AltI2C */
case 44: /* PNPEnv_TUNER_4009FN5 */
case 31: /* PNPEnv_TUNER_TCPB9085P */
case 30: /* PNPEnv_TUNER_TCPN9085D */
case 46: /* PNPEnv_TUNER_TP18NSR01F */
case 47: /* PNPEnv_TUNER_TP18PSB01D */
case 49: /* PNPEnv_TUNER_TAPC_I001D */
case 60: /* PNPEnv_TUNER_TAPE_S001D_MK3 */
case 57: /* PNPEnv_TUNER_FM1216ME_MK3 */
case 59: /* PNPEnv_TUNER_FM1216MP_MK3 */
case 58: /* PNPEnv_TUNER_FM1236_MK3 */
case 68: /* PNPEnv_TUNER_TAPE_H001F_MK3 */
case 61: /* PNPEnv_TUNER_TAPE_M001D_MK3 */
case 78: /* PNPEnv_TUNER_TDA8275C1_8290_FM */
case 89: /* PNPEnv_TUNER_TCL_MFPE05_2 */
case 92: /* PNPEnv_TUNER_PHILIPS_FQ1236A_MK4 */
case 105:
return 1;
}
return 0;
}
void tveeprom_hauppauge_analog(struct tveeprom *tvee,
unsigned char *eeprom_data)
{
/* ----------------------------------------------
** The hauppauge eeprom format is tagged
**
** if packet[0] == 0x84, then packet[0..1] == length
** else length = packet[0] & 3f;
** if packet[0] & f8 == f8, then EOD and packet[1] == checksum
**
** In our (ivtv) case we're interested in the following:
** tuner type: tag [00].05 or [0a].01 (index into hauppauge_tuner)
** tuner fmts: tag [00].04 or [0a].00 (bitmask index into
** hauppauge_tuner_fmt)
** radio: tag [00].{last} or [0e].00 (bitmask. bit2=FM)
** audio proc: tag [02].01 or [05].00 (mask with 0x7f)
** decoder proc: tag [09].01)
** Fun info:
** model: tag [00].07-08 or [06].00-01
** revision: tag [00].09-0b or [06].04-06
** serial#: tag [01].05-07 or [04].04-06
** # of inputs/outputs ???
*/
int i, j, len, done, beenhere, tag, start;
int tuner1 = 0, t_format1 = 0, audioic = -1;
const char *t_name1 = NULL;
const char *t_fmt_name1[8] = { " none", "", "", "", "", "", "", "" };
int tuner2 = 0, t_format2 = 0;
const char *t_name2 = NULL;
const char *t_fmt_name2[8] = { " none", "", "", "", "", "", "", "" };
memset(tvee, 0, sizeof(*tvee));
tvee->tuner_type = TUNER_ABSENT;
tvee->tuner2_type = TUNER_ABSENT;
done = len = beenhere = 0;
/* Different eeprom start offsets for em28xx, cx2388x and cx23418 */
if (eeprom_data[0] == 0x1a &&
eeprom_data[1] == 0xeb &&
eeprom_data[2] == 0x67 &&
eeprom_data[3] == 0x95)
start = 0xa0; /* Generic em28xx offset */
else if ((eeprom_data[0] & 0xe1) == 0x01 &&
eeprom_data[1] == 0x00 &&
eeprom_data[2] == 0x00 &&
eeprom_data[8] == 0x84)
start = 8; /* Generic cx2388x offset */
else if (eeprom_data[1] == 0x70 &&
eeprom_data[2] == 0x00 &&
eeprom_data[4] == 0x74 &&
eeprom_data[8] == 0x84)
start = 8; /* Generic cx23418 offset (models 74xxx) */
else
start = 0;
for (i = start; !done && i < 256; i += len) {
if (eeprom_data[i] == 0x84) {
len = eeprom_data[i + 1] + (eeprom_data[i + 2] << 8);
i += 3;
} else if ((eeprom_data[i] & 0xf0) == 0x70) {
if (eeprom_data[i] & 0x08) {
/* verify checksum! */
done = 1;
break;
}
len = eeprom_data[i] & 0x07;
++i;
} else {
pr_warn("Encountered bad packet header [%02x]. Corrupt or not a Hauppauge eeprom.\n",
eeprom_data[i]);
return;
}
pr_debug("Tag [%02x] + %d bytes: %*ph\n",
eeprom_data[i], len - 1, len, &eeprom_data[i]);
/* process by tag */
tag = eeprom_data[i];
switch (tag) {
case 0x00:
/* tag: 'Comprehensive' */
tuner1 = eeprom_data[i+6];
t_format1 = eeprom_data[i+5];
tvee->has_radio = eeprom_data[i+len-1];
/* old style tag, don't know how to detect
IR presence, mark as unknown. */
tvee->has_ir = 0;
tvee->model =
eeprom_data[i+8] +
(eeprom_data[i+9] << 8);
tvee->revision = eeprom_data[i+10] +
(eeprom_data[i+11] << 8) +
(eeprom_data[i+12] << 16);
break;
case 0x01:
/* tag: 'SerialID' */
tvee->serial_number =
eeprom_data[i+6] +
(eeprom_data[i+7] << 8) +
(eeprom_data[i+8] << 16);
break;
case 0x02:
/* tag 'AudioInfo'
Note mask with 0x7F, high bit used on some older models
to indicate 4052 mux was removed in favor of using MSP
inputs directly. */
audioic = eeprom_data[i+2] & 0x7f;
if (audioic < ARRAY_SIZE(audio_ic))
tvee->audio_processor = audio_ic[audioic].id;
else
tvee->audio_processor = TVEEPROM_AUDPROC_OTHER;
break;
/* case 0x03: tag 'EEInfo' */
case 0x04:
/* tag 'SerialID2' */
tvee->serial_number =
eeprom_data[i+5] +
(eeprom_data[i+6] << 8) +
(eeprom_data[i+7] << 16)+
(eeprom_data[i+8] << 24);
if (eeprom_data[i + 8] == 0xf0) {
tvee->MAC_address[0] = 0x00;
tvee->MAC_address[1] = 0x0D;
tvee->MAC_address[2] = 0xFE;
tvee->MAC_address[3] = eeprom_data[i + 7];
tvee->MAC_address[4] = eeprom_data[i + 6];
tvee->MAC_address[5] = eeprom_data[i + 5];
tvee->has_MAC_address = 1;
}
break;
case 0x05:
/* tag 'Audio2'
Note mask with 0x7F, high bit used on some older models
to indicate 4052 mux was removed in favor of using MSP
inputs directly. */
audioic = eeprom_data[i+1] & 0x7f;
if (audioic < ARRAY_SIZE(audio_ic))
tvee->audio_processor = audio_ic[audioic].id;
else
tvee->audio_processor = TVEEPROM_AUDPROC_OTHER;
break;
case 0x06:
/* tag 'ModelRev' */
tvee->model =
eeprom_data[i + 1] +
(eeprom_data[i + 2] << 8) +
(eeprom_data[i + 3] << 16) +
(eeprom_data[i + 4] << 24);
tvee->revision =
eeprom_data[i + 5] +
(eeprom_data[i + 6] << 8) +
(eeprom_data[i + 7] << 16);
break;
case 0x07:
/* tag 'Details': according to Hauppauge not interesting
on any PCI-era or later boards. */
break;
/* there is no tag 0x08 defined */
case 0x09:
/* tag 'Video' */
tvee->decoder_processor = eeprom_data[i + 1];
break;
case 0x0a:
/* tag 'Tuner' */
if (beenhere == 0) {
tuner1 = eeprom_data[i + 2];
t_format1 = eeprom_data[i + 1];
beenhere = 1;
} else {
/* a second (radio) tuner may be present */
tuner2 = eeprom_data[i + 2];
t_format2 = eeprom_data[i + 1];
/* not a TV tuner? */
if (t_format2 == 0)
tvee->has_radio = 1; /* must be radio */
}
break;
case 0x0b:
/* tag 'Inputs': according to Hauppauge this is specific
to each driver family, so no good assumptions can be
made. */
break;
/* case 0x0c: tag 'Balun' */
/* case 0x0d: tag 'Teletext' */
case 0x0e:
/* tag: 'Radio' */
tvee->has_radio = eeprom_data[i+1];
break;
case 0x0f:
/* tag 'IRInfo' */
tvee->has_ir = 1 | (eeprom_data[i+1] << 1);
break;
/* case 0x10: tag 'VBIInfo' */
/* case 0x11: tag 'QCInfo' */
/* case 0x12: tag 'InfoBits' */
default:
pr_debug("Not sure what to do with tag [%02x]\n",
tag);
/* dump the rest of the packet? */
}
}
if (!done) {
pr_warn("Ran out of data!\n");
return;
}
if (tvee->revision != 0) {
tvee->rev_str[0] = 32 + ((tvee->revision >> 18) & 0x3f);
tvee->rev_str[1] = 32 + ((tvee->revision >> 12) & 0x3f);
tvee->rev_str[2] = 32 + ((tvee->revision >> 6) & 0x3f);
tvee->rev_str[3] = 32 + (tvee->revision & 0x3f);
tvee->rev_str[4] = 0;
}
if (hasRadioTuner(tuner1) && !tvee->has_radio) {
pr_info("The eeprom says no radio is present, but the tuner type\n");
pr_info("indicates otherwise. I will assume that radio is present.\n");
tvee->has_radio = 1;
}
if (tuner1 < ARRAY_SIZE(hauppauge_tuner)) {
tvee->tuner_type = hauppauge_tuner[tuner1].id;
t_name1 = hauppauge_tuner[tuner1].name;
} else {
t_name1 = "unknown";
}
if (tuner2 < ARRAY_SIZE(hauppauge_tuner)) {
tvee->tuner2_type = hauppauge_tuner[tuner2].id;
t_name2 = hauppauge_tuner[tuner2].name;
} else {
t_name2 = "unknown";
}
tvee->tuner_hauppauge_model = tuner1;
tvee->tuner2_hauppauge_model = tuner2;
tvee->tuner_formats = 0;
tvee->tuner2_formats = 0;
for (i = j = 0; i < 8; i++) {
if (t_format1 & (1 << i)) {
tvee->tuner_formats |= hauppauge_tuner_fmt[i].id;
t_fmt_name1[j++] = hauppauge_tuner_fmt[i].name;
}
}
for (i = j = 0; i < 8; i++) {
if (t_format2 & (1 << i)) {
tvee->tuner2_formats |= hauppauge_tuner_fmt[i].id;
t_fmt_name2[j++] = hauppauge_tuner_fmt[i].name;
}
}
pr_info("Hauppauge model %d, rev %s, serial# %u\n",
tvee->model, tvee->rev_str, tvee->serial_number);
if (tvee->has_MAC_address == 1)
pr_info("MAC address is %pM\n", tvee->MAC_address);
pr_info("tuner model is %s (idx %d, type %d)\n",
t_name1, tuner1, tvee->tuner_type);
pr_info("TV standards%s%s%s%s%s%s%s%s (eeprom 0x%02x)\n",
t_fmt_name1[0], t_fmt_name1[1], t_fmt_name1[2],
t_fmt_name1[3], t_fmt_name1[4], t_fmt_name1[5],
t_fmt_name1[6], t_fmt_name1[7], t_format1);
if (tuner2)
pr_info("second tuner model is %s (idx %d, type %d)\n",
t_name2, tuner2, tvee->tuner2_type);
if (t_format2)
pr_info("TV standards%s%s%s%s%s%s%s%s (eeprom 0x%02x)\n",
t_fmt_name2[0], t_fmt_name2[1], t_fmt_name2[2],
t_fmt_name2[3], t_fmt_name2[4], t_fmt_name2[5],
t_fmt_name2[6], t_fmt_name2[7], t_format2);
if (audioic < 0) {
pr_info("audio processor is unknown (no idx)\n");
tvee->audio_processor = TVEEPROM_AUDPROC_OTHER;
} else {
if (audioic < ARRAY_SIZE(audio_ic))
pr_info("audio processor is %s (idx %d)\n",
audio_ic[audioic].name, audioic);
else
pr_info("audio processor is unknown (idx %d)\n",
audioic);
}
if (tvee->decoder_processor)
pr_info("decoder processor is %s (idx %d)\n",
STRM(decoderIC, tvee->decoder_processor),
tvee->decoder_processor);
if (tvee->has_ir)
pr_info("has %sradio, has %sIR receiver, has %sIR transmitter\n",
tvee->has_radio ? "" : "no ",
(tvee->has_ir & 2) ? "" : "no ",
(tvee->has_ir & 4) ? "" : "no ");
else
pr_info("has %sradio\n",
tvee->has_radio ? "" : "no ");
}
EXPORT_SYMBOL(tveeprom_hauppauge_analog);
/* ----------------------------------------------------------------------- */
/* generic helper functions */
int tveeprom_read(struct i2c_client *c, unsigned char *eedata, int len)
{
unsigned char buf;
int err;
buf = 0;
err = i2c_master_send(c, &buf, 1);
if (err != 1) {
pr_info("Huh, no eeprom present (err=%d)?\n", err);
return -1;
}
err = i2c_master_recv(c, eedata, len);
if (err != len) {
pr_warn("i2c eeprom read error (err=%d)\n", err);
return -1;
}
print_hex_dump_debug("full 256-byte eeprom dump:", DUMP_PREFIX_NONE,
16, 1, eedata, len, true);
return 0;
}
EXPORT_SYMBOL(tveeprom_read);
+196
View File
@@ -0,0 +1,196 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/string.h>
#include <linux/types.h>
#include <linux/usb/uvc.h>
#include <linux/videodev2.h>
/* ------------------------------------------------------------------------
* Video formats
*/
static const struct uvc_format_desc uvc_fmts[] = {
{
.guid = UVC_GUID_FORMAT_YUY2,
.fcc = V4L2_PIX_FMT_YUYV,
},
{
.guid = UVC_GUID_FORMAT_YUY2_ISIGHT,
.fcc = V4L2_PIX_FMT_YUYV,
},
{
.guid = UVC_GUID_FORMAT_NV12,
.fcc = V4L2_PIX_FMT_NV12,
},
{
.guid = UVC_GUID_FORMAT_MJPEG,
.fcc = V4L2_PIX_FMT_MJPEG,
},
{
.guid = UVC_GUID_FORMAT_YV12,
.fcc = V4L2_PIX_FMT_YVU420,
},
{
.guid = UVC_GUID_FORMAT_I420,
.fcc = V4L2_PIX_FMT_YUV420,
},
{
.guid = UVC_GUID_FORMAT_M420,
.fcc = V4L2_PIX_FMT_M420,
},
{
.guid = UVC_GUID_FORMAT_P010,
.fcc = V4L2_PIX_FMT_P010,
},
{
.guid = UVC_GUID_FORMAT_UYVY,
.fcc = V4L2_PIX_FMT_UYVY,
},
{
.guid = UVC_GUID_FORMAT_Y800,
.fcc = V4L2_PIX_FMT_GREY,
},
{
.guid = UVC_GUID_FORMAT_Y8,
.fcc = V4L2_PIX_FMT_GREY,
},
{
.guid = UVC_GUID_FORMAT_D3DFMT_L8,
.fcc = V4L2_PIX_FMT_GREY,
},
{
.guid = UVC_GUID_FORMAT_KSMEDIA_L8_IR,
.fcc = V4L2_PIX_FMT_GREY,
},
{
.guid = UVC_GUID_FORMAT_Y10,
.fcc = V4L2_PIX_FMT_Y10,
},
{
.guid = UVC_GUID_FORMAT_Y12,
.fcc = V4L2_PIX_FMT_Y12,
},
{
.guid = UVC_GUID_FORMAT_Y16,
.fcc = V4L2_PIX_FMT_Y16,
},
{
.guid = UVC_GUID_FORMAT_BY8,
.fcc = V4L2_PIX_FMT_SBGGR8,
},
{
.guid = UVC_GUID_FORMAT_BA81,
.fcc = V4L2_PIX_FMT_SBGGR8,
},
{
.guid = UVC_GUID_FORMAT_GBRG,
.fcc = V4L2_PIX_FMT_SGBRG8,
},
{
.guid = UVC_GUID_FORMAT_GRBG,
.fcc = V4L2_PIX_FMT_SGRBG8,
},
{
.guid = UVC_GUID_FORMAT_RGGB,
.fcc = V4L2_PIX_FMT_SRGGB8,
},
{
.guid = UVC_GUID_FORMAT_RGBP,
.fcc = V4L2_PIX_FMT_RGB565,
},
{
.guid = UVC_GUID_FORMAT_D3DFMT_R5G6B5,
.fcc = V4L2_PIX_FMT_RGB565,
},
{
.guid = UVC_GUID_FORMAT_BGR3,
.fcc = V4L2_PIX_FMT_BGR24,
},
{
.guid = UVC_GUID_FORMAT_BGR4,
.fcc = V4L2_PIX_FMT_XBGR32,
},
{
.guid = UVC_GUID_FORMAT_H264,
.fcc = V4L2_PIX_FMT_H264,
},
{
.guid = UVC_GUID_FORMAT_H265,
.fcc = V4L2_PIX_FMT_HEVC,
},
{
.guid = UVC_GUID_FORMAT_Y8I,
.fcc = V4L2_PIX_FMT_Y8I,
},
{
.guid = UVC_GUID_FORMAT_Y12I,
.fcc = V4L2_PIX_FMT_Y12I,
},
{
.guid = UVC_GUID_FORMAT_Y16I,
.fcc = V4L2_PIX_FMT_Y16I,
},
{
.guid = UVC_GUID_FORMAT_Z16,
.fcc = V4L2_PIX_FMT_Z16,
},
{
.guid = UVC_GUID_FORMAT_RW10,
.fcc = V4L2_PIX_FMT_SRGGB10P,
},
{
.guid = UVC_GUID_FORMAT_BG16,
.fcc = V4L2_PIX_FMT_SBGGR16,
},
{
.guid = UVC_GUID_FORMAT_GB16,
.fcc = V4L2_PIX_FMT_SGBRG16,
},
{
.guid = UVC_GUID_FORMAT_RG16,
.fcc = V4L2_PIX_FMT_SRGGB16,
},
{
.guid = UVC_GUID_FORMAT_GR16,
.fcc = V4L2_PIX_FMT_SGRBG16,
},
{
.guid = UVC_GUID_FORMAT_INVZ,
.fcc = V4L2_PIX_FMT_Z16,
},
{
.guid = UVC_GUID_FORMAT_INVI,
.fcc = V4L2_PIX_FMT_Y10,
},
{
.guid = UVC_GUID_FORMAT_INZI,
.fcc = V4L2_PIX_FMT_INZI,
},
{
.guid = UVC_GUID_FORMAT_CNF4,
.fcc = V4L2_PIX_FMT_CNF4,
},
{
.guid = UVC_GUID_FORMAT_HEVC,
.fcc = V4L2_PIX_FMT_HEVC,
},
};
const struct uvc_format_desc *uvc_format_by_guid(const u8 guid[16])
{
unsigned int len = ARRAY_SIZE(uvc_fmts);
unsigned int i;
for (i = 0; i < len; ++i) {
if (memcmp(guid, uvc_fmts[i].guid, 16) == 0)
return &uvc_fmts[i];
}
return NULL;
}
EXPORT_SYMBOL_GPL(uvc_format_by_guid);
MODULE_DESCRIPTION("USB Video Class common code");
MODULE_LICENSE("GPL");
@@ -0,0 +1,3 @@
# SPDX-License-Identifier: GPL-2.0-only
config VIDEO_V4L2_TPG
tristate
@@ -0,0 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-only
v4l2-tpg-objs := v4l2-tpg-core.o v4l2-tpg-colors.o
obj-$(CONFIG_VIDEO_V4L2_TPG) += v4l2-tpg.o
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,32 @@
# SPDX-License-Identifier: GPL-2.0-only
# Used by drivers that need Videobuf2 modules
config VIDEOBUF2_CORE
select DMA_SHARED_BUFFER
tristate
config VIDEOBUF2_V4L2
tristate
config VIDEOBUF2_MEMOPS
tristate
config VIDEOBUF2_DMA_CONTIG
tristate
select VIDEOBUF2_CORE
select VIDEOBUF2_MEMOPS
select DMA_SHARED_BUFFER
config VIDEOBUF2_VMALLOC
tristate
select VIDEOBUF2_CORE
select VIDEOBUF2_MEMOPS
select DMA_SHARED_BUFFER
config VIDEOBUF2_DMA_SG
tristate
select VIDEOBUF2_CORE
select VIDEOBUF2_MEMOPS
config VIDEOBUF2_DVB
tristate
select VIDEOBUF2_CORE
@@ -0,0 +1,17 @@
# SPDX-License-Identifier: GPL-2.0
videobuf2-common-objs := videobuf2-core.o
videobuf2-common-objs += frame_vector.o
ifeq ($(CONFIG_TRACEPOINTS),y)
videobuf2-common-objs += vb2-trace.o
endif
# Please keep it alphabetically sorted by Kconfig name
# (e. g. LC_ALL=C sort Makefile)
obj-$(CONFIG_VIDEOBUF2_CORE) += videobuf2-common.o
obj-$(CONFIG_VIDEOBUF2_DMA_CONTIG) += videobuf2-dma-contig.o
obj-$(CONFIG_VIDEOBUF2_DMA_SG) += videobuf2-dma-sg.o
obj-$(CONFIG_VIDEOBUF2_DVB) += videobuf2-dvb.o
obj-$(CONFIG_VIDEOBUF2_MEMOPS) += videobuf2-memops.o
obj-$(CONFIG_VIDEOBUF2_V4L2) += videobuf2-v4l2.o
obj-$(CONFIG_VIDEOBUF2_VMALLOC) += videobuf2-vmalloc.o
@@ -0,0 +1,197 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/err.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/pagemap.h>
#include <linux/sched.h>
#include <media/frame_vector.h>
/**
* get_vaddr_frames() - map virtual addresses to pfns
* @start: starting user address
* @nr_frames: number of pages / pfns from start to map
* @write: the mapped address has write permission
* @vec: structure which receives pages / pfns of the addresses mapped.
* It should have space for at least nr_frames entries.
*
* This function maps virtual addresses from @start and fills @vec structure
* with page frame numbers or page pointers to corresponding pages (choice
* depends on the type of the vma underlying the virtual address). If @start
* belongs to a normal vma, the function grabs reference to each of the pages
* to pin them in memory. If @start belongs to VM_IO | VM_PFNMAP vma, we don't
* touch page structures and the caller must make sure pfns aren't reused for
* anything else while he is using them.
*
* The function returns number of pages mapped which may be less than
* @nr_frames. In particular we stop mapping if there are more vmas of
* different type underlying the specified range of virtual addresses.
* When the function isn't able to map a single page, it returns error.
*
* Note that get_vaddr_frames() cannot follow VM_IO mappings. It used
* to be able to do that, but that could (racily) return non-refcounted
* pfns.
*
* This function takes care of grabbing mmap_lock as necessary.
*/
int get_vaddr_frames(unsigned long start, unsigned int nr_frames, bool write,
struct frame_vector *vec)
{
int ret;
unsigned int gup_flags = FOLL_LONGTERM;
if (nr_frames == 0)
return 0;
if (WARN_ON_ONCE(nr_frames > vec->nr_allocated))
nr_frames = vec->nr_allocated;
start = untagged_addr(start);
if (write)
gup_flags |= FOLL_WRITE;
ret = pin_user_pages_fast(start, nr_frames, gup_flags,
(struct page **)(vec->ptrs));
vec->got_ref = true;
vec->is_pfns = false;
vec->nr_frames = ret;
if (likely(ret > 0))
return ret;
vec->nr_frames = 0;
return ret ? ret : -EFAULT;
}
EXPORT_SYMBOL(get_vaddr_frames);
/**
* put_vaddr_frames() - drop references to pages if get_vaddr_frames() acquired
* them
* @vec: frame vector to put
*
* Drop references to pages if get_vaddr_frames() acquired them. We also
* invalidate the frame vector so that it is prepared for the next call into
* get_vaddr_frames().
*/
void put_vaddr_frames(struct frame_vector *vec)
{
struct page **pages;
if (!vec->got_ref)
goto out;
pages = frame_vector_pages(vec);
/*
* frame_vector_pages() might needed to do a conversion when
* get_vaddr_frames() got pages but vec was later converted to pfns.
* But it shouldn't really fail to convert pfns back...
*/
if (WARN_ON(IS_ERR(pages)))
goto out;
unpin_user_pages(pages, vec->nr_frames);
vec->got_ref = false;
out:
vec->nr_frames = 0;
}
EXPORT_SYMBOL(put_vaddr_frames);
/**
* frame_vector_to_pages - convert frame vector to contain page pointers
* @vec: frame vector to convert
*
* Convert @vec to contain array of page pointers. If the conversion is
* successful, return 0. Otherwise return an error. Note that we do not grab
* page references for the page structures.
*/
int frame_vector_to_pages(struct frame_vector *vec)
{
int i;
unsigned long *nums;
struct page **pages;
if (!vec->is_pfns)
return 0;
nums = frame_vector_pfns(vec);
for (i = 0; i < vec->nr_frames; i++)
if (!pfn_valid(nums[i]))
return -EINVAL;
pages = (struct page **)nums;
for (i = 0; i < vec->nr_frames; i++)
pages[i] = pfn_to_page(nums[i]);
vec->is_pfns = false;
return 0;
}
EXPORT_SYMBOL(frame_vector_to_pages);
/**
* frame_vector_to_pfns - convert frame vector to contain pfns
* @vec: frame vector to convert
*
* Convert @vec to contain array of pfns.
*/
void frame_vector_to_pfns(struct frame_vector *vec)
{
int i;
unsigned long *nums;
struct page **pages;
if (vec->is_pfns)
return;
pages = (struct page **)(vec->ptrs);
nums = (unsigned long *)pages;
for (i = 0; i < vec->nr_frames; i++)
nums[i] = page_to_pfn(pages[i]);
vec->is_pfns = true;
}
EXPORT_SYMBOL(frame_vector_to_pfns);
/**
* frame_vector_create() - allocate & initialize structure for pinned pfns
* @nr_frames: number of pfns slots we should reserve
*
* Allocate and initialize struct pinned_pfns to be able to hold @nr_pfns
* pfns.
*/
struct frame_vector *frame_vector_create(unsigned int nr_frames)
{
struct frame_vector *vec;
int size = struct_size(vec, ptrs, nr_frames);
if (WARN_ON_ONCE(nr_frames == 0))
return NULL;
/*
* This is absurdly high. It's here just to avoid strange effects when
* arithmetics overflows.
*/
if (WARN_ON_ONCE(nr_frames > INT_MAX / sizeof(void *) / 2))
return NULL;
/*
* Avoid higher order allocations, use vmalloc instead. It should
* be rare anyway.
*/
vec = kvmalloc(size, GFP_KERNEL);
if (!vec)
return NULL;
vec->nr_allocated = nr_frames;
vec->nr_frames = 0;
return vec;
}
EXPORT_SYMBOL(frame_vector_create);
/**
* frame_vector_destroy() - free memory allocated to carry frame vector
* @vec: Frame vector to free
*
* Free structure allocated by frame_vector_create() to carry frames.
*/
void frame_vector_destroy(struct frame_vector *vec)
{
/* Make sure put_vaddr_frames() got called properly... */
VM_BUG_ON(vec->nr_frames > 0);
kvfree(vec);
}
EXPORT_SYMBOL(frame_vector_destroy);
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: GPL-2.0
#include <media/videobuf2-core.h>
#define CREATE_TRACE_POINTS
#include <trace/events/vb2.h>
EXPORT_TRACEPOINT_SYMBOL_GPL(vb2_buf_done);
EXPORT_TRACEPOINT_SYMBOL_GPL(vb2_buf_queue);
EXPORT_TRACEPOINT_SYMBOL_GPL(vb2_dqbuf);
EXPORT_TRACEPOINT_SYMBOL_GPL(vb2_qbuf);
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,866 @@
/*
* videobuf2-dma-contig.c - DMA contig memory allocator for videobuf2
*
* Copyright (C) 2010 Samsung Electronics
*
* Author: Pawel Osciak <pawel@osciak.com>
*
* 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.
*/
#include <linux/dma-buf.h>
#include <linux/module.h>
#include <linux/refcount.h>
#include <linux/scatterlist.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/dma-mapping.h>
#include <linux/highmem.h>
#include <media/videobuf2-v4l2.h>
#include <media/videobuf2-dma-contig.h>
#include <media/videobuf2-memops.h>
struct vb2_dc_buf {
struct device *dev;
void *vaddr;
unsigned long size;
void *cookie;
dma_addr_t dma_addr;
unsigned long attrs;
enum dma_data_direction dma_dir;
struct sg_table *dma_sgt;
struct frame_vector *vec;
/* MMAP related */
struct vb2_vmarea_handler handler;
refcount_t refcount;
struct sg_table *sgt_base;
/* DMABUF related */
struct dma_buf_attachment *db_attach;
struct vb2_buffer *vb;
bool non_coherent_mem;
};
/*********************************************/
/* scatterlist table functions */
/*********************************************/
static unsigned long vb2_dc_get_contiguous_size(struct sg_table *sgt)
{
struct scatterlist *s;
dma_addr_t expected = sg_dma_address(sgt->sgl);
unsigned int i;
unsigned long size = 0;
for_each_sgtable_dma_sg(sgt, s, i) {
if (sg_dma_address(s) != expected)
break;
expected += sg_dma_len(s);
size += sg_dma_len(s);
}
return size;
}
/*********************************************/
/* callbacks for all buffers */
/*********************************************/
static void *vb2_dc_cookie(struct vb2_buffer *vb, void *buf_priv)
{
struct vb2_dc_buf *buf = buf_priv;
return &buf->dma_addr;
}
/*
* This function may fail if:
*
* - dma_buf_vmap() fails
* E.g. due to lack of virtual mapping address space, or due to
* dmabuf->ops misconfiguration.
*
* - dma_vmap_noncontiguous() fails
* For instance, when requested buffer size is larger than totalram_pages().
* Relevant for buffers that use non-coherent memory.
*
* - Queue DMA attrs have DMA_ATTR_NO_KERNEL_MAPPING set
* Relevant for buffers that use coherent memory.
*/
static void *vb2_dc_vaddr(struct vb2_buffer *vb, void *buf_priv)
{
struct vb2_dc_buf *buf = buf_priv;
if (buf->vaddr)
return buf->vaddr;
if (buf->db_attach) {
struct iosys_map map;
if (!dma_buf_vmap_unlocked(buf->db_attach->dmabuf, &map))
buf->vaddr = map.vaddr;
return buf->vaddr;
}
if (buf->non_coherent_mem)
buf->vaddr = dma_vmap_noncontiguous(buf->dev, buf->size,
buf->dma_sgt);
return buf->vaddr;
}
static unsigned int vb2_dc_num_users(void *buf_priv)
{
struct vb2_dc_buf *buf = buf_priv;
return refcount_read(&buf->refcount);
}
static void vb2_dc_prepare(void *buf_priv)
{
struct vb2_dc_buf *buf = buf_priv;
struct sg_table *sgt = buf->dma_sgt;
/* This takes care of DMABUF and user-enforced cache sync hint */
if (buf->vb->skip_cache_sync_on_prepare)
return;
if (!buf->non_coherent_mem)
return;
/* Non-coherent MMAP only */
if (buf->vaddr)
flush_kernel_vmap_range(buf->vaddr, buf->size);
/* For both USERPTR and non-coherent MMAP */
dma_sync_sgtable_for_device(buf->dev, sgt, buf->dma_dir);
}
static void vb2_dc_finish(void *buf_priv)
{
struct vb2_dc_buf *buf = buf_priv;
struct sg_table *sgt = buf->dma_sgt;
/* This takes care of DMABUF and user-enforced cache sync hint */
if (buf->vb->skip_cache_sync_on_finish)
return;
if (!buf->non_coherent_mem)
return;
/* Non-coherent MMAP only */
if (buf->vaddr)
invalidate_kernel_vmap_range(buf->vaddr, buf->size);
/* For both USERPTR and non-coherent MMAP */
dma_sync_sgtable_for_cpu(buf->dev, sgt, buf->dma_dir);
}
/*********************************************/
/* callbacks for MMAP buffers */
/*********************************************/
static void vb2_dc_put(void *buf_priv)
{
struct vb2_dc_buf *buf = buf_priv;
if (!refcount_dec_and_test(&buf->refcount))
return;
if (buf->non_coherent_mem) {
if (buf->vaddr)
dma_vunmap_noncontiguous(buf->dev, buf->vaddr);
dma_free_noncontiguous(buf->dev, buf->size,
buf->dma_sgt, buf->dma_dir);
} else {
if (buf->sgt_base) {
sg_free_table(buf->sgt_base);
kfree(buf->sgt_base);
}
dma_free_attrs(buf->dev, buf->size, buf->cookie,
buf->dma_addr, buf->attrs);
}
put_device(buf->dev);
kfree(buf);
}
static int vb2_dc_alloc_coherent(struct vb2_dc_buf *buf)
{
struct vb2_queue *q = buf->vb->vb2_queue;
buf->cookie = dma_alloc_attrs(buf->dev,
buf->size,
&buf->dma_addr,
GFP_KERNEL | q->gfp_flags,
buf->attrs);
if (!buf->cookie)
return -ENOMEM;
if (q->dma_attrs & DMA_ATTR_NO_KERNEL_MAPPING)
return 0;
buf->vaddr = buf->cookie;
return 0;
}
static int vb2_dc_alloc_non_coherent(struct vb2_dc_buf *buf)
{
struct vb2_queue *q = buf->vb->vb2_queue;
buf->dma_sgt = dma_alloc_noncontiguous(buf->dev,
buf->size,
buf->dma_dir,
GFP_KERNEL | q->gfp_flags,
buf->attrs);
if (!buf->dma_sgt)
return -ENOMEM;
buf->dma_addr = sg_dma_address(buf->dma_sgt->sgl);
/*
* For non-coherent buffers the kernel mapping is created on demand
* in vb2_dc_vaddr().
*/
return 0;
}
static void *vb2_dc_alloc(struct vb2_buffer *vb,
struct device *dev,
unsigned long size)
{
struct vb2_dc_buf *buf;
int ret;
if (WARN_ON(!dev))
return ERR_PTR(-EINVAL);
buf = kzalloc_obj(*buf);
if (!buf)
return ERR_PTR(-ENOMEM);
buf->attrs = vb->vb2_queue->dma_attrs;
buf->dma_dir = vb->vb2_queue->dma_dir;
buf->vb = vb;
buf->non_coherent_mem = vb->vb2_queue->non_coherent_mem;
buf->size = size;
/* Prevent the device from being released while the buffer is used */
buf->dev = get_device(dev);
if (buf->non_coherent_mem)
ret = vb2_dc_alloc_non_coherent(buf);
else
ret = vb2_dc_alloc_coherent(buf);
if (ret) {
dev_err(dev, "dma alloc of size %lu failed\n", size);
put_device(buf->dev);
kfree(buf);
return ERR_PTR(-ENOMEM);
}
buf->handler.refcount = &buf->refcount;
buf->handler.put = vb2_dc_put;
buf->handler.arg = buf;
refcount_set(&buf->refcount, 1);
return buf;
}
static int vb2_dc_mmap(void *buf_priv, struct vm_area_struct *vma)
{
struct vb2_dc_buf *buf = buf_priv;
int ret;
if (!buf) {
printk(KERN_ERR "No buffer to map\n");
return -EINVAL;
}
if (buf->non_coherent_mem)
ret = dma_mmap_noncontiguous(buf->dev, vma, buf->size,
buf->dma_sgt);
else
ret = dma_mmap_attrs(buf->dev, vma, buf->cookie, buf->dma_addr,
buf->size, buf->attrs);
if (ret) {
pr_err("Remapping memory failed, error: %d\n", ret);
return ret;
}
vm_flags_set(vma, VM_DONTEXPAND | VM_DONTDUMP);
vma->vm_private_data = &buf->handler;
vma->vm_ops = &vb2_common_vm_ops;
vma->vm_ops->open(vma);
pr_debug("%s: mapped dma addr 0x%08lx at 0x%08lx, size %lu\n",
__func__, (unsigned long)buf->dma_addr, vma->vm_start,
buf->size);
return 0;
}
/*********************************************/
/* DMABUF ops for exporters */
/*********************************************/
struct vb2_dc_attachment {
struct sg_table sgt;
enum dma_data_direction dma_dir;
};
static int vb2_dc_dmabuf_ops_attach(struct dma_buf *dbuf,
struct dma_buf_attachment *dbuf_attach)
{
struct vb2_dc_attachment *attach;
unsigned int i;
struct scatterlist *rd, *wr;
struct sg_table *sgt;
struct vb2_dc_buf *buf = dbuf->priv;
int ret;
attach = kzalloc_obj(*attach);
if (!attach)
return -ENOMEM;
sgt = &attach->sgt;
/* Copy the buf->base_sgt scatter list to the attachment, as we can't
* map the same scatter list to multiple attachments at the same time.
*/
ret = sg_alloc_table(sgt, buf->sgt_base->orig_nents, GFP_KERNEL);
if (ret) {
kfree(attach);
return -ENOMEM;
}
rd = buf->sgt_base->sgl;
wr = sgt->sgl;
for (i = 0; i < sgt->orig_nents; ++i) {
sg_set_page(wr, sg_page(rd), rd->length, rd->offset);
rd = sg_next(rd);
wr = sg_next(wr);
}
attach->dma_dir = DMA_NONE;
dbuf_attach->priv = attach;
return 0;
}
static void vb2_dc_dmabuf_ops_detach(struct dma_buf *dbuf,
struct dma_buf_attachment *db_attach)
{
struct vb2_dc_attachment *attach = db_attach->priv;
struct sg_table *sgt;
if (!attach)
return;
sgt = &attach->sgt;
/* release the scatterlist cache */
if (attach->dma_dir != DMA_NONE)
/*
* Cache sync can be skipped here, as the vb2_dc memory is
* allocated from device coherent memory, which means the
* memory locations do not require any explicit cache
* maintenance prior or after being used by the device.
*/
dma_unmap_sgtable(db_attach->dev, sgt, attach->dma_dir,
DMA_ATTR_SKIP_CPU_SYNC);
sg_free_table(sgt);
kfree(attach);
db_attach->priv = NULL;
}
static struct sg_table *vb2_dc_dmabuf_ops_map(
struct dma_buf_attachment *db_attach, enum dma_data_direction dma_dir)
{
struct vb2_dc_attachment *attach = db_attach->priv;
struct sg_table *sgt;
sgt = &attach->sgt;
/* return previously mapped sg table */
if (attach->dma_dir == dma_dir)
return sgt;
/* release any previous cache */
if (attach->dma_dir != DMA_NONE) {
dma_unmap_sgtable(db_attach->dev, sgt, attach->dma_dir,
DMA_ATTR_SKIP_CPU_SYNC);
attach->dma_dir = DMA_NONE;
}
/*
* mapping to the client with new direction, no cache sync
* required see comment in vb2_dc_dmabuf_ops_detach()
*/
if (dma_map_sgtable(db_attach->dev, sgt, dma_dir,
DMA_ATTR_SKIP_CPU_SYNC)) {
pr_err("failed to map scatterlist\n");
return ERR_PTR(-EIO);
}
attach->dma_dir = dma_dir;
return sgt;
}
static void vb2_dc_dmabuf_ops_unmap(struct dma_buf_attachment *db_attach,
struct sg_table *sgt, enum dma_data_direction dma_dir)
{
/* nothing to be done here */
}
static void vb2_dc_dmabuf_ops_release(struct dma_buf *dbuf)
{
/* drop reference obtained in vb2_dc_get_dmabuf */
vb2_dc_put(dbuf->priv);
}
static int
vb2_dc_dmabuf_ops_begin_cpu_access(struct dma_buf *dbuf,
enum dma_data_direction direction)
{
return 0;
}
static int
vb2_dc_dmabuf_ops_end_cpu_access(struct dma_buf *dbuf,
enum dma_data_direction direction)
{
return 0;
}
static int vb2_dc_dmabuf_ops_vmap(struct dma_buf *dbuf, struct iosys_map *map)
{
struct vb2_dc_buf *buf;
void *vaddr;
buf = dbuf->priv;
vaddr = vb2_dc_vaddr(buf->vb, buf);
if (!vaddr)
return -EINVAL;
iosys_map_set_vaddr(map, vaddr);
return 0;
}
static int vb2_dc_dmabuf_ops_mmap(struct dma_buf *dbuf,
struct vm_area_struct *vma)
{
return vb2_dc_mmap(dbuf->priv, vma);
}
static const struct dma_buf_ops vb2_dc_dmabuf_ops = {
.attach = vb2_dc_dmabuf_ops_attach,
.detach = vb2_dc_dmabuf_ops_detach,
.map_dma_buf = vb2_dc_dmabuf_ops_map,
.unmap_dma_buf = vb2_dc_dmabuf_ops_unmap,
.begin_cpu_access = vb2_dc_dmabuf_ops_begin_cpu_access,
.end_cpu_access = vb2_dc_dmabuf_ops_end_cpu_access,
.vmap = vb2_dc_dmabuf_ops_vmap,
.mmap = vb2_dc_dmabuf_ops_mmap,
.release = vb2_dc_dmabuf_ops_release,
};
static struct sg_table *vb2_dc_get_base_sgt(struct vb2_dc_buf *buf)
{
int ret;
struct sg_table *sgt;
if (buf->non_coherent_mem)
return buf->dma_sgt;
sgt = kmalloc_obj(*sgt);
if (!sgt) {
dev_err(buf->dev, "failed to alloc sg table\n");
return NULL;
}
ret = dma_get_sgtable_attrs(buf->dev, sgt, buf->cookie, buf->dma_addr,
buf->size, buf->attrs);
if (ret < 0) {
dev_err(buf->dev, "failed to get scatterlist from DMA API\n");
kfree(sgt);
return NULL;
}
return sgt;
}
static struct dma_buf *vb2_dc_get_dmabuf(struct vb2_buffer *vb,
void *buf_priv,
unsigned long flags)
{
struct vb2_dc_buf *buf = buf_priv;
struct dma_buf *dbuf;
DEFINE_DMA_BUF_EXPORT_INFO(exp_info);
exp_info.ops = &vb2_dc_dmabuf_ops;
exp_info.size = buf->size;
exp_info.flags = flags;
exp_info.priv = buf;
if (!buf->sgt_base)
buf->sgt_base = vb2_dc_get_base_sgt(buf);
if (WARN_ON(!buf->sgt_base))
return NULL;
dbuf = dma_buf_export(&exp_info);
if (IS_ERR(dbuf))
return NULL;
/* dmabuf keeps reference to vb2 buffer */
refcount_inc(&buf->refcount);
return dbuf;
}
/*********************************************/
/* callbacks for USERPTR buffers */
/*********************************************/
static void vb2_dc_put_userptr(void *buf_priv)
{
struct vb2_dc_buf *buf = buf_priv;
struct sg_table *sgt = buf->dma_sgt;
int i;
struct page **pages;
if (sgt) {
/*
* No need to sync to CPU, it's already synced to the CPU
* since the finish() memop will have been called before this.
*/
dma_unmap_sgtable(buf->dev, sgt, buf->dma_dir,
DMA_ATTR_SKIP_CPU_SYNC);
if (buf->dma_dir == DMA_FROM_DEVICE ||
buf->dma_dir == DMA_BIDIRECTIONAL) {
pages = frame_vector_pages(buf->vec);
/* sgt should exist only if vector contains pages... */
if (!WARN_ON_ONCE(IS_ERR(pages)))
for (i = 0; i < frame_vector_count(buf->vec); i++)
set_page_dirty_lock(pages[i]);
}
sg_free_table(sgt);
kfree(sgt);
} else {
dma_unmap_resource(buf->dev, buf->dma_addr, buf->size,
buf->dma_dir, 0);
}
vb2_destroy_framevec(buf->vec);
kfree(buf);
}
static void *vb2_dc_get_userptr(struct vb2_buffer *vb, struct device *dev,
unsigned long vaddr, unsigned long size)
{
struct vb2_dc_buf *buf;
struct frame_vector *vec;
unsigned int offset;
int n_pages, i;
int ret = 0;
struct sg_table *sgt;
unsigned long contig_size;
unsigned long dma_align = dma_get_cache_alignment();
/* Only cache aligned DMA transfers are reliable */
if (!IS_ALIGNED(vaddr | size, dma_align)) {
pr_debug("user data must be aligned to %lu bytes\n", dma_align);
return ERR_PTR(-EINVAL);
}
if (!size) {
pr_debug("size is zero\n");
return ERR_PTR(-EINVAL);
}
if (WARN_ON(!dev))
return ERR_PTR(-EINVAL);
buf = kzalloc_obj(*buf);
if (!buf)
return ERR_PTR(-ENOMEM);
buf->dev = dev;
buf->dma_dir = vb->vb2_queue->dma_dir;
buf->vb = vb;
offset = lower_32_bits(offset_in_page(vaddr));
vec = vb2_create_framevec(vaddr, size, buf->dma_dir == DMA_FROM_DEVICE ||
buf->dma_dir == DMA_BIDIRECTIONAL);
if (IS_ERR(vec)) {
ret = PTR_ERR(vec);
goto fail_buf;
}
buf->vec = vec;
n_pages = frame_vector_count(vec);
ret = frame_vector_to_pages(vec);
if (ret < 0) {
unsigned long *nums = frame_vector_pfns(vec);
/*
* Failed to convert to pages... Check the memory is physically
* contiguous and use direct mapping
*/
for (i = 1; i < n_pages; i++)
if (nums[i-1] + 1 != nums[i])
goto fail_pfnvec;
buf->dma_addr = dma_map_resource(buf->dev,
__pfn_to_phys(nums[0]), size, buf->dma_dir, 0);
if (dma_mapping_error(buf->dev, buf->dma_addr)) {
ret = -ENOMEM;
goto fail_pfnvec;
}
goto out;
}
sgt = kzalloc_obj(*sgt);
if (!sgt) {
pr_err("failed to allocate sg table\n");
ret = -ENOMEM;
goto fail_pfnvec;
}
ret = sg_alloc_table_from_pages(sgt, frame_vector_pages(vec), n_pages,
offset, size, GFP_KERNEL);
if (ret) {
pr_err("failed to initialize sg table\n");
goto fail_sgt;
}
/*
* No need to sync to the device, this will happen later when the
* prepare() memop is called.
*/
if (dma_map_sgtable(buf->dev, sgt, buf->dma_dir,
DMA_ATTR_SKIP_CPU_SYNC)) {
pr_err("failed to map scatterlist\n");
ret = -EIO;
goto fail_sgt_init;
}
contig_size = vb2_dc_get_contiguous_size(sgt);
if (contig_size < size) {
pr_err("contiguous mapping is too small %lu/%lu\n",
contig_size, size);
ret = -EFAULT;
goto fail_map_sg;
}
buf->dma_addr = sg_dma_address(sgt->sgl);
buf->dma_sgt = sgt;
buf->non_coherent_mem = 1;
out:
buf->size = size;
return buf;
fail_map_sg:
dma_unmap_sgtable(buf->dev, sgt, buf->dma_dir, DMA_ATTR_SKIP_CPU_SYNC);
fail_sgt_init:
sg_free_table(sgt);
fail_sgt:
kfree(sgt);
fail_pfnvec:
vb2_destroy_framevec(vec);
fail_buf:
kfree(buf);
return ERR_PTR(ret);
}
/*********************************************/
/* callbacks for DMABUF buffers */
/*********************************************/
static int vb2_dc_map_dmabuf(void *mem_priv)
{
struct vb2_dc_buf *buf = mem_priv;
struct sg_table *sgt;
unsigned long contig_size;
if (WARN_ON(!buf->db_attach)) {
pr_err("trying to pin a non attached buffer\n");
return -EINVAL;
}
if (WARN_ON(buf->dma_sgt)) {
pr_err("dmabuf buffer is already pinned\n");
return 0;
}
/* get the associated scatterlist for this buffer */
sgt = dma_buf_map_attachment_unlocked(buf->db_attach, buf->dma_dir);
if (IS_ERR(sgt)) {
pr_err("Error getting dmabuf scatterlist\n");
return -EINVAL;
}
/* checking if dmabuf is big enough to store contiguous chunk */
contig_size = vb2_dc_get_contiguous_size(sgt);
if (contig_size < buf->size) {
pr_err("contiguous chunk is too small %lu/%lu\n",
contig_size, buf->size);
dma_buf_unmap_attachment_unlocked(buf->db_attach, sgt,
buf->dma_dir);
return -EFAULT;
}
buf->dma_addr = sg_dma_address(sgt->sgl);
buf->dma_sgt = sgt;
buf->vaddr = NULL;
return 0;
}
static void vb2_dc_unmap_dmabuf(void *mem_priv)
{
struct vb2_dc_buf *buf = mem_priv;
struct sg_table *sgt = buf->dma_sgt;
struct iosys_map map = IOSYS_MAP_INIT_VADDR(buf->vaddr);
if (WARN_ON(!buf->db_attach)) {
pr_err("trying to unpin a not attached buffer\n");
return;
}
if (WARN_ON(!sgt)) {
pr_err("dmabuf buffer is already unpinned\n");
return;
}
if (buf->vaddr) {
dma_buf_vunmap_unlocked(buf->db_attach->dmabuf, &map);
buf->vaddr = NULL;
}
dma_buf_unmap_attachment_unlocked(buf->db_attach, sgt, buf->dma_dir);
buf->dma_addr = 0;
buf->dma_sgt = NULL;
}
static void vb2_dc_detach_dmabuf(void *mem_priv)
{
struct vb2_dc_buf *buf = mem_priv;
/* if vb2 works correctly you should never detach mapped buffer */
if (WARN_ON(buf->dma_addr))
vb2_dc_unmap_dmabuf(buf);
/* detach this attachment */
dma_buf_detach(buf->db_attach->dmabuf, buf->db_attach);
kfree(buf);
}
static void *vb2_dc_attach_dmabuf(struct vb2_buffer *vb, struct device *dev,
struct dma_buf *dbuf, unsigned long size)
{
struct vb2_dc_buf *buf;
struct dma_buf_attachment *dba;
if (dbuf->size < size)
return ERR_PTR(-EFAULT);
if (WARN_ON(!dev))
return ERR_PTR(-EINVAL);
buf = kzalloc_obj(*buf);
if (!buf)
return ERR_PTR(-ENOMEM);
buf->dev = dev;
buf->vb = vb;
/* create attachment for the dmabuf with the user device */
dba = dma_buf_attach(dbuf, buf->dev);
if (IS_ERR(dba)) {
pr_err("failed to attach dmabuf\n");
kfree(buf);
return dba;
}
buf->dma_dir = vb->vb2_queue->dma_dir;
buf->size = size;
buf->db_attach = dba;
return buf;
}
/*********************************************/
/* DMA CONTIG exported functions */
/*********************************************/
const struct vb2_mem_ops vb2_dma_contig_memops = {
.alloc = vb2_dc_alloc,
.put = vb2_dc_put,
.get_dmabuf = vb2_dc_get_dmabuf,
.cookie = vb2_dc_cookie,
.vaddr = vb2_dc_vaddr,
.mmap = vb2_dc_mmap,
.get_userptr = vb2_dc_get_userptr,
.put_userptr = vb2_dc_put_userptr,
.prepare = vb2_dc_prepare,
.finish = vb2_dc_finish,
.map_dmabuf = vb2_dc_map_dmabuf,
.unmap_dmabuf = vb2_dc_unmap_dmabuf,
.attach_dmabuf = vb2_dc_attach_dmabuf,
.detach_dmabuf = vb2_dc_detach_dmabuf,
.num_users = vb2_dc_num_users,
};
EXPORT_SYMBOL_GPL(vb2_dma_contig_memops);
/**
* vb2_dma_contig_set_max_seg_size() - configure DMA max segment size
* @dev: device for configuring DMA parameters
* @size: size of DMA max segment size to set
*
* To allow mapping the scatter-list into a single chunk in the DMA
* address space, the device is required to have the DMA max segment
* size parameter set to a value larger than the buffer size. Otherwise,
* the DMA-mapping subsystem will split the mapping into max segment
* size chunks. This function sets the DMA max segment size
* parameter to let DMA-mapping map a buffer as a single chunk in DMA
* address space.
* This code assumes that the DMA-mapping subsystem will merge all
* scatterlist segments if this is really possible (for example when
* an IOMMU is available and enabled).
* Ideally, this parameter should be set by the generic bus code, but it
* is left with the default 64KiB value due to historical litmiations in
* other subsystems (like limited USB host drivers) and there no good
* place to set it to the proper value.
* This function should be called from the drivers, which are known to
* operate on platforms with IOMMU and provide access to shared buffers
* (either USERPTR or DMABUF). This should be done before initializing
* videobuf2 queue.
*/
int vb2_dma_contig_set_max_seg_size(struct device *dev, unsigned int size)
{
if (!dev->dma_parms) {
dev_err(dev, "Failed to set max_seg_size: dma_parms is NULL\n");
return -ENODEV;
}
if (dma_get_max_seg_size(dev) < size)
dma_set_max_seg_size(dev, size);
return 0;
}
EXPORT_SYMBOL_GPL(vb2_dma_contig_set_max_seg_size);
MODULE_DESCRIPTION("DMA-contig memory handling routines for videobuf2");
MODULE_AUTHOR("Pawel Osciak <pawel@osciak.com>");
MODULE_LICENSE("GPL");
MODULE_IMPORT_NS("DMA_BUF");
@@ -0,0 +1,680 @@
/*
* videobuf2-dma-sg.c - dma scatter/gather memory allocator for videobuf2
*
* Copyright (C) 2010 Samsung Electronics
*
* Author: Andrzej Pietrasiewicz <andrzejtp2010@gmail.com>
*
* 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.
*/
#include <linux/module.h>
#include <linux/mm.h>
#include <linux/refcount.h>
#include <linux/scatterlist.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <media/videobuf2-v4l2.h>
#include <media/videobuf2-memops.h>
#include <media/videobuf2-dma-sg.h>
static int debug;
module_param(debug, int, 0644);
#define dprintk(level, fmt, arg...) \
do { \
if (debug >= level) \
printk(KERN_DEBUG "vb2-dma-sg: " fmt, ## arg); \
} while (0)
struct vb2_dma_sg_buf {
struct device *dev;
void *vaddr;
struct page **pages;
struct frame_vector *vec;
int offset;
enum dma_data_direction dma_dir;
struct sg_table sg_table;
/*
* This will point to sg_table when used with the MMAP or USERPTR
* memory model, and to the dma_buf sglist when used with the
* DMABUF memory model.
*/
struct sg_table *dma_sgt;
size_t size;
unsigned int num_pages;
refcount_t refcount;
struct vb2_vmarea_handler handler;
struct dma_buf_attachment *db_attach;
struct vb2_buffer *vb;
};
static void vb2_dma_sg_put(void *buf_priv);
static int vb2_dma_sg_alloc_compacted(struct vb2_dma_sg_buf *buf,
gfp_t gfp_flags)
{
unsigned int last_page = 0;
unsigned long size = buf->size;
while (size > 0) {
struct page *pages;
int order;
int i;
order = get_order(size);
/* Don't over allocate*/
if ((PAGE_SIZE << order) > size)
order--;
pages = NULL;
while (!pages) {
pages = alloc_pages(GFP_KERNEL | __GFP_ZERO |
__GFP_NOWARN | gfp_flags, order);
if (pages)
break;
if (order == 0) {
while (last_page--)
__free_page(buf->pages[last_page]);
return -ENOMEM;
}
order--;
}
split_page(pages, order);
for (i = 0; i < (1 << order); i++)
buf->pages[last_page++] = &pages[i];
size -= PAGE_SIZE << order;
}
return 0;
}
static void *vb2_dma_sg_alloc(struct vb2_buffer *vb, struct device *dev,
unsigned long size)
{
struct vb2_dma_sg_buf *buf;
struct sg_table *sgt;
int ret;
int num_pages;
if (WARN_ON(!dev) || WARN_ON(!size))
return ERR_PTR(-EINVAL);
buf = kzalloc_obj(*buf);
if (!buf)
return ERR_PTR(-ENOMEM);
buf->vaddr = NULL;
buf->dma_dir = vb->vb2_queue->dma_dir;
buf->offset = 0;
buf->size = size;
/* size is already page aligned */
buf->num_pages = size >> PAGE_SHIFT;
buf->dma_sgt = &buf->sg_table;
/*
* NOTE: dma-sg allocates memory using the page allocator directly, so
* there is no memory consistency guarantee, hence dma-sg ignores DMA
* attributes passed from the upper layer.
*/
buf->pages = kvzalloc_objs(struct page *, buf->num_pages);
if (!buf->pages)
goto fail_pages_array_alloc;
ret = vb2_dma_sg_alloc_compacted(buf, vb->vb2_queue->gfp_flags);
if (ret)
goto fail_pages_alloc;
ret = sg_alloc_table_from_pages(buf->dma_sgt, buf->pages,
buf->num_pages, 0, size, GFP_KERNEL);
if (ret)
goto fail_table_alloc;
/* Prevent the device from being released while the buffer is used */
buf->dev = get_device(dev);
sgt = &buf->sg_table;
/*
* No need to sync to the device, this will happen later when the
* prepare() memop is called.
*/
if (dma_map_sgtable(buf->dev, sgt, buf->dma_dir,
DMA_ATTR_SKIP_CPU_SYNC))
goto fail_map;
buf->handler.refcount = &buf->refcount;
buf->handler.put = vb2_dma_sg_put;
buf->handler.arg = buf;
buf->vb = vb;
refcount_set(&buf->refcount, 1);
dprintk(1, "%s: Allocated buffer of %d pages\n",
__func__, buf->num_pages);
return buf;
fail_map:
put_device(buf->dev);
sg_free_table(buf->dma_sgt);
fail_table_alloc:
num_pages = buf->num_pages;
while (num_pages--)
__free_page(buf->pages[num_pages]);
fail_pages_alloc:
kvfree(buf->pages);
fail_pages_array_alloc:
kfree(buf);
return ERR_PTR(-ENOMEM);
}
static void vb2_dma_sg_put(void *buf_priv)
{
struct vb2_dma_sg_buf *buf = buf_priv;
struct sg_table *sgt = &buf->sg_table;
int i = buf->num_pages;
if (refcount_dec_and_test(&buf->refcount)) {
dprintk(1, "%s: Freeing buffer of %d pages\n", __func__,
buf->num_pages);
dma_unmap_sgtable(buf->dev, sgt, buf->dma_dir,
DMA_ATTR_SKIP_CPU_SYNC);
if (buf->vaddr)
vm_unmap_ram(buf->vaddr, buf->num_pages);
sg_free_table(buf->dma_sgt);
while (--i >= 0)
__free_page(buf->pages[i]);
kvfree(buf->pages);
put_device(buf->dev);
kfree(buf);
}
}
static void vb2_dma_sg_prepare(void *buf_priv)
{
struct vb2_dma_sg_buf *buf = buf_priv;
struct sg_table *sgt = buf->dma_sgt;
if (buf->vb->skip_cache_sync_on_prepare)
return;
dma_sync_sgtable_for_device(buf->dev, sgt, buf->dma_dir);
}
static void vb2_dma_sg_finish(void *buf_priv)
{
struct vb2_dma_sg_buf *buf = buf_priv;
struct sg_table *sgt = buf->dma_sgt;
if (buf->vb->skip_cache_sync_on_finish)
return;
dma_sync_sgtable_for_cpu(buf->dev, sgt, buf->dma_dir);
}
static void *vb2_dma_sg_get_userptr(struct vb2_buffer *vb, struct device *dev,
unsigned long vaddr, unsigned long size)
{
struct vb2_dma_sg_buf *buf;
struct sg_table *sgt;
struct frame_vector *vec;
if (WARN_ON(!dev))
return ERR_PTR(-EINVAL);
buf = kzalloc_obj(*buf);
if (!buf)
return ERR_PTR(-ENOMEM);
buf->vaddr = NULL;
buf->dev = dev;
buf->dma_dir = vb->vb2_queue->dma_dir;
buf->offset = vaddr & ~PAGE_MASK;
buf->size = size;
buf->dma_sgt = &buf->sg_table;
buf->vb = vb;
vec = vb2_create_framevec(vaddr, size,
buf->dma_dir == DMA_FROM_DEVICE ||
buf->dma_dir == DMA_BIDIRECTIONAL);
if (IS_ERR(vec))
goto userptr_fail_pfnvec;
buf->vec = vec;
buf->pages = frame_vector_pages(vec);
if (IS_ERR(buf->pages))
goto userptr_fail_sgtable;
buf->num_pages = frame_vector_count(vec);
if (sg_alloc_table_from_pages(buf->dma_sgt, buf->pages,
buf->num_pages, buf->offset, size, 0))
goto userptr_fail_sgtable;
sgt = &buf->sg_table;
/*
* No need to sync to the device, this will happen later when the
* prepare() memop is called.
*/
if (dma_map_sgtable(buf->dev, sgt, buf->dma_dir,
DMA_ATTR_SKIP_CPU_SYNC))
goto userptr_fail_map;
return buf;
userptr_fail_map:
sg_free_table(&buf->sg_table);
userptr_fail_sgtable:
vb2_destroy_framevec(vec);
userptr_fail_pfnvec:
kfree(buf);
return ERR_PTR(-ENOMEM);
}
/*
* @put_userptr: inform the allocator that a USERPTR buffer will no longer
* be used
*/
static void vb2_dma_sg_put_userptr(void *buf_priv)
{
struct vb2_dma_sg_buf *buf = buf_priv;
struct sg_table *sgt = &buf->sg_table;
int i = buf->num_pages;
dprintk(1, "%s: Releasing userspace buffer of %d pages\n",
__func__, buf->num_pages);
dma_unmap_sgtable(buf->dev, sgt, buf->dma_dir, DMA_ATTR_SKIP_CPU_SYNC);
if (buf->vaddr)
vm_unmap_ram(buf->vaddr, buf->num_pages);
sg_free_table(buf->dma_sgt);
if (buf->dma_dir == DMA_FROM_DEVICE ||
buf->dma_dir == DMA_BIDIRECTIONAL)
while (--i >= 0)
set_page_dirty_lock(buf->pages[i]);
vb2_destroy_framevec(buf->vec);
kfree(buf);
}
static void *vb2_dma_sg_vaddr(struct vb2_buffer *vb, void *buf_priv)
{
struct vb2_dma_sg_buf *buf = buf_priv;
struct iosys_map map;
int ret;
BUG_ON(!buf);
if (!buf->vaddr) {
if (buf->db_attach) {
ret = dma_buf_vmap_unlocked(buf->db_attach->dmabuf, &map);
buf->vaddr = ret ? NULL : map.vaddr;
} else {
buf->vaddr = vm_map_ram(buf->pages, buf->num_pages, -1);
}
}
/* add offset in case userptr is not page-aligned */
return buf->vaddr ? buf->vaddr + buf->offset : NULL;
}
static unsigned int vb2_dma_sg_num_users(void *buf_priv)
{
struct vb2_dma_sg_buf *buf = buf_priv;
return refcount_read(&buf->refcount);
}
static int vb2_dma_sg_mmap(void *buf_priv, struct vm_area_struct *vma)
{
struct vb2_dma_sg_buf *buf = buf_priv;
int err;
if (!buf) {
printk(KERN_ERR "No memory to map\n");
return -EINVAL;
}
err = vm_map_pages(vma, buf->pages, buf->num_pages);
if (err) {
printk(KERN_ERR "Remapping memory, error: %d\n", err);
return err;
}
vm_flags_set(vma, VM_DONTEXPAND | VM_DONTDUMP);
/*
* Use common vm_area operations to track buffer refcount.
*/
vma->vm_private_data = &buf->handler;
vma->vm_ops = &vb2_common_vm_ops;
vma->vm_ops->open(vma);
return 0;
}
/*********************************************/
/* DMABUF ops for exporters */
/*********************************************/
struct vb2_dma_sg_attachment {
struct sg_table sgt;
enum dma_data_direction dma_dir;
};
static int vb2_dma_sg_dmabuf_ops_attach(struct dma_buf *dbuf,
struct dma_buf_attachment *dbuf_attach)
{
struct vb2_dma_sg_attachment *attach;
unsigned int i;
struct scatterlist *rd, *wr;
struct sg_table *sgt;
struct vb2_dma_sg_buf *buf = dbuf->priv;
int ret;
attach = kzalloc_obj(*attach);
if (!attach)
return -ENOMEM;
sgt = &attach->sgt;
/* Copy the buf->base_sgt scatter list to the attachment, as we can't
* map the same scatter list to multiple attachments at the same time.
*/
ret = sg_alloc_table(sgt, buf->dma_sgt->orig_nents, GFP_KERNEL);
if (ret) {
kfree(attach);
return -ENOMEM;
}
rd = buf->dma_sgt->sgl;
wr = sgt->sgl;
for (i = 0; i < sgt->orig_nents; ++i) {
sg_set_page(wr, sg_page(rd), rd->length, rd->offset);
rd = sg_next(rd);
wr = sg_next(wr);
}
attach->dma_dir = DMA_NONE;
dbuf_attach->priv = attach;
return 0;
}
static void vb2_dma_sg_dmabuf_ops_detach(struct dma_buf *dbuf,
struct dma_buf_attachment *db_attach)
{
struct vb2_dma_sg_attachment *attach = db_attach->priv;
struct sg_table *sgt;
if (!attach)
return;
sgt = &attach->sgt;
/* release the scatterlist cache */
if (attach->dma_dir != DMA_NONE)
dma_unmap_sgtable(db_attach->dev, sgt, attach->dma_dir, 0);
sg_free_table(sgt);
kfree(attach);
db_attach->priv = NULL;
}
static struct sg_table *vb2_dma_sg_dmabuf_ops_map(
struct dma_buf_attachment *db_attach, enum dma_data_direction dma_dir)
{
struct vb2_dma_sg_attachment *attach = db_attach->priv;
struct sg_table *sgt;
sgt = &attach->sgt;
/* return previously mapped sg table */
if (attach->dma_dir == dma_dir)
return sgt;
/* release any previous cache */
if (attach->dma_dir != DMA_NONE) {
dma_unmap_sgtable(db_attach->dev, sgt, attach->dma_dir, 0);
attach->dma_dir = DMA_NONE;
}
/* mapping to the client with new direction */
if (dma_map_sgtable(db_attach->dev, sgt, dma_dir, 0)) {
pr_err("failed to map scatterlist\n");
return ERR_PTR(-EIO);
}
attach->dma_dir = dma_dir;
return sgt;
}
static void vb2_dma_sg_dmabuf_ops_unmap(struct dma_buf_attachment *db_attach,
struct sg_table *sgt, enum dma_data_direction dma_dir)
{
/* nothing to be done here */
}
static void vb2_dma_sg_dmabuf_ops_release(struct dma_buf *dbuf)
{
/* drop reference obtained in vb2_dma_sg_get_dmabuf */
vb2_dma_sg_put(dbuf->priv);
}
static int
vb2_dma_sg_dmabuf_ops_begin_cpu_access(struct dma_buf *dbuf,
enum dma_data_direction direction)
{
struct vb2_dma_sg_buf *buf = dbuf->priv;
struct sg_table *sgt = buf->dma_sgt;
dma_sync_sgtable_for_cpu(buf->dev, sgt, buf->dma_dir);
return 0;
}
static int
vb2_dma_sg_dmabuf_ops_end_cpu_access(struct dma_buf *dbuf,
enum dma_data_direction direction)
{
struct vb2_dma_sg_buf *buf = dbuf->priv;
struct sg_table *sgt = buf->dma_sgt;
dma_sync_sgtable_for_device(buf->dev, sgt, buf->dma_dir);
return 0;
}
static int vb2_dma_sg_dmabuf_ops_vmap(struct dma_buf *dbuf,
struct iosys_map *map)
{
struct vb2_dma_sg_buf *buf;
void *vaddr;
buf = dbuf->priv;
vaddr = vb2_dma_sg_vaddr(buf->vb, buf);
if (!vaddr)
return -EINVAL;
iosys_map_set_vaddr(map, vaddr);
return 0;
}
static int vb2_dma_sg_dmabuf_ops_mmap(struct dma_buf *dbuf,
struct vm_area_struct *vma)
{
return vb2_dma_sg_mmap(dbuf->priv, vma);
}
static const struct dma_buf_ops vb2_dma_sg_dmabuf_ops = {
.attach = vb2_dma_sg_dmabuf_ops_attach,
.detach = vb2_dma_sg_dmabuf_ops_detach,
.map_dma_buf = vb2_dma_sg_dmabuf_ops_map,
.unmap_dma_buf = vb2_dma_sg_dmabuf_ops_unmap,
.begin_cpu_access = vb2_dma_sg_dmabuf_ops_begin_cpu_access,
.end_cpu_access = vb2_dma_sg_dmabuf_ops_end_cpu_access,
.vmap = vb2_dma_sg_dmabuf_ops_vmap,
.mmap = vb2_dma_sg_dmabuf_ops_mmap,
.release = vb2_dma_sg_dmabuf_ops_release,
};
static struct dma_buf *vb2_dma_sg_get_dmabuf(struct vb2_buffer *vb,
void *buf_priv,
unsigned long flags)
{
struct vb2_dma_sg_buf *buf = buf_priv;
struct dma_buf *dbuf;
DEFINE_DMA_BUF_EXPORT_INFO(exp_info);
exp_info.ops = &vb2_dma_sg_dmabuf_ops;
exp_info.size = buf->size;
exp_info.flags = flags;
exp_info.priv = buf;
if (WARN_ON(!buf->dma_sgt))
return NULL;
dbuf = dma_buf_export(&exp_info);
if (IS_ERR(dbuf))
return NULL;
/* dmabuf keeps reference to vb2 buffer */
refcount_inc(&buf->refcount);
return dbuf;
}
/*********************************************/
/* callbacks for DMABUF buffers */
/*********************************************/
static int vb2_dma_sg_map_dmabuf(void *mem_priv)
{
struct vb2_dma_sg_buf *buf = mem_priv;
struct sg_table *sgt;
if (WARN_ON(!buf->db_attach)) {
pr_err("trying to pin a non attached buffer\n");
return -EINVAL;
}
if (WARN_ON(buf->dma_sgt)) {
pr_err("dmabuf buffer is already pinned\n");
return 0;
}
/* get the associated scatterlist for this buffer */
sgt = dma_buf_map_attachment_unlocked(buf->db_attach, buf->dma_dir);
if (IS_ERR(sgt)) {
pr_err("Error getting dmabuf scatterlist\n");
return -EINVAL;
}
buf->dma_sgt = sgt;
buf->vaddr = NULL;
return 0;
}
static void vb2_dma_sg_unmap_dmabuf(void *mem_priv)
{
struct vb2_dma_sg_buf *buf = mem_priv;
struct sg_table *sgt = buf->dma_sgt;
struct iosys_map map = IOSYS_MAP_INIT_VADDR(buf->vaddr);
if (WARN_ON(!buf->db_attach)) {
pr_err("trying to unpin a not attached buffer\n");
return;
}
if (WARN_ON(!sgt)) {
pr_err("dmabuf buffer is already unpinned\n");
return;
}
if (buf->vaddr) {
dma_buf_vunmap_unlocked(buf->db_attach->dmabuf, &map);
buf->vaddr = NULL;
}
dma_buf_unmap_attachment_unlocked(buf->db_attach, sgt, buf->dma_dir);
buf->dma_sgt = NULL;
}
static void vb2_dma_sg_detach_dmabuf(void *mem_priv)
{
struct vb2_dma_sg_buf *buf = mem_priv;
/* if vb2 works correctly you should never detach mapped buffer */
if (WARN_ON(buf->dma_sgt))
vb2_dma_sg_unmap_dmabuf(buf);
/* detach this attachment */
dma_buf_detach(buf->db_attach->dmabuf, buf->db_attach);
kfree(buf);
}
static void *vb2_dma_sg_attach_dmabuf(struct vb2_buffer *vb, struct device *dev,
struct dma_buf *dbuf, unsigned long size)
{
struct vb2_dma_sg_buf *buf;
struct dma_buf_attachment *dba;
if (WARN_ON(!dev))
return ERR_PTR(-EINVAL);
if (dbuf->size < size)
return ERR_PTR(-EFAULT);
buf = kzalloc_obj(*buf);
if (!buf)
return ERR_PTR(-ENOMEM);
buf->dev = dev;
/* create attachment for the dmabuf with the user device */
dba = dma_buf_attach(dbuf, buf->dev);
if (IS_ERR(dba)) {
pr_err("failed to attach dmabuf\n");
kfree(buf);
return dba;
}
buf->dma_dir = vb->vb2_queue->dma_dir;
buf->size = size;
buf->db_attach = dba;
buf->vb = vb;
return buf;
}
static void *vb2_dma_sg_cookie(struct vb2_buffer *vb, void *buf_priv)
{
struct vb2_dma_sg_buf *buf = buf_priv;
return buf->dma_sgt;
}
const struct vb2_mem_ops vb2_dma_sg_memops = {
.alloc = vb2_dma_sg_alloc,
.put = vb2_dma_sg_put,
.get_userptr = vb2_dma_sg_get_userptr,
.put_userptr = vb2_dma_sg_put_userptr,
.prepare = vb2_dma_sg_prepare,
.finish = vb2_dma_sg_finish,
.vaddr = vb2_dma_sg_vaddr,
.mmap = vb2_dma_sg_mmap,
.num_users = vb2_dma_sg_num_users,
.get_dmabuf = vb2_dma_sg_get_dmabuf,
.map_dmabuf = vb2_dma_sg_map_dmabuf,
.unmap_dmabuf = vb2_dma_sg_unmap_dmabuf,
.attach_dmabuf = vb2_dma_sg_attach_dmabuf,
.detach_dmabuf = vb2_dma_sg_detach_dmabuf,
.cookie = vb2_dma_sg_cookie,
};
EXPORT_SYMBOL_GPL(vb2_dma_sg_memops);
MODULE_DESCRIPTION("dma scatter/gather memory handling routines for videobuf2");
MODULE_AUTHOR("Andrzej Pietrasiewicz");
MODULE_LICENSE("GPL");
MODULE_IMPORT_NS("DMA_BUF");
@@ -0,0 +1,342 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
*
* some helper function for simple DVB cards which simply DMA the
* complete transport stream and let the computer sort everything else
* (i.e. we are using the software demux, ...). Also uses vb2
* to manage DMA buffers.
*
* (c) 2004 Gerd Knorr <kraxel@bytesex.org> [SUSE Labs]
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <media/videobuf2-dvb.h>
/* ------------------------------------------------------------------ */
MODULE_AUTHOR("Gerd Knorr <kraxel@bytesex.org> [SuSE Labs]");
MODULE_DESCRIPTION("Videobuf2 helper library for simple DVB cards");
MODULE_LICENSE("GPL");
/* ------------------------------------------------------------------ */
static int dvb_fnc(struct vb2_buffer *vb, void *priv)
{
struct vb2_dvb *dvb = priv;
dvb_dmx_swfilter(&dvb->demux, vb2_plane_vaddr(vb, 0),
vb2_get_plane_payload(vb, 0));
return 0;
}
static int vb2_dvb_start_feed(struct dvb_demux_feed *feed)
{
struct dvb_demux *demux = feed->demux;
struct vb2_dvb *dvb = demux->priv;
int rc = 0;
if (!demux->dmx.frontend)
return -EINVAL;
mutex_lock(&dvb->lock);
dvb->nfeeds++;
if (!dvb->dvbq.threadio) {
rc = vb2_thread_start(&dvb->dvbq, dvb_fnc, dvb, dvb->name);
if (rc)
dvb->nfeeds--;
}
if (!rc)
rc = dvb->nfeeds;
mutex_unlock(&dvb->lock);
return rc;
}
static int vb2_dvb_stop_feed(struct dvb_demux_feed *feed)
{
struct dvb_demux *demux = feed->demux;
struct vb2_dvb *dvb = demux->priv;
int err = 0;
mutex_lock(&dvb->lock);
dvb->nfeeds--;
if (0 == dvb->nfeeds)
err = vb2_thread_stop(&dvb->dvbq);
mutex_unlock(&dvb->lock);
return err;
}
static int vb2_dvb_register_adapter(struct vb2_dvb_frontends *fe,
struct module *module,
void *adapter_priv,
struct device *device,
struct media_device *mdev,
char *adapter_name,
short *adapter_nr,
int mfe_shared)
{
int result;
mutex_init(&fe->lock);
/* register adapter */
result = dvb_register_adapter(&fe->adapter, adapter_name, module,
device, adapter_nr);
if (result < 0) {
pr_warn("%s: dvb_register_adapter failed (errno = %d)\n",
adapter_name, result);
}
fe->adapter.priv = adapter_priv;
fe->adapter.mfe_shared = mfe_shared;
#ifdef CONFIG_MEDIA_CONTROLLER_DVB
if (mdev)
fe->adapter.mdev = mdev;
#endif
return result;
}
static int vb2_dvb_register_frontend(struct dvb_adapter *adapter,
struct vb2_dvb *dvb)
{
int result;
/* register frontend */
result = dvb_register_frontend(adapter, dvb->frontend);
if (result < 0) {
pr_warn("%s: dvb_register_frontend failed (errno = %d)\n",
dvb->name, result);
goto fail_frontend;
}
/* register demux stuff */
dvb->demux.dmx.capabilities =
DMX_TS_FILTERING | DMX_SECTION_FILTERING |
DMX_MEMORY_BASED_FILTERING;
dvb->demux.priv = dvb;
dvb->demux.filternum = 256;
dvb->demux.feednum = 256;
dvb->demux.start_feed = vb2_dvb_start_feed;
dvb->demux.stop_feed = vb2_dvb_stop_feed;
result = dvb_dmx_init(&dvb->demux);
if (result < 0) {
pr_warn("%s: dvb_dmx_init failed (errno = %d)\n",
dvb->name, result);
goto fail_dmx;
}
dvb->dmxdev.filternum = 256;
dvb->dmxdev.demux = &dvb->demux.dmx;
dvb->dmxdev.capabilities = 0;
result = dvb_dmxdev_init(&dvb->dmxdev, adapter);
if (result < 0) {
pr_warn("%s: dvb_dmxdev_init failed (errno = %d)\n",
dvb->name, result);
goto fail_dmxdev;
}
dvb->fe_hw.source = DMX_FRONTEND_0;
result = dvb->demux.dmx.add_frontend(&dvb->demux.dmx, &dvb->fe_hw);
if (result < 0) {
pr_warn("%s: add_frontend failed (DMX_FRONTEND_0, errno = %d)\n",
dvb->name, result);
goto fail_fe_hw;
}
dvb->fe_mem.source = DMX_MEMORY_FE;
result = dvb->demux.dmx.add_frontend(&dvb->demux.dmx, &dvb->fe_mem);
if (result < 0) {
pr_warn("%s: add_frontend failed (DMX_MEMORY_FE, errno = %d)\n",
dvb->name, result);
goto fail_fe_mem;
}
result = dvb->demux.dmx.connect_frontend(&dvb->demux.dmx, &dvb->fe_hw);
if (result < 0) {
pr_warn("%s: connect_frontend failed (errno = %d)\n",
dvb->name, result);
goto fail_fe_conn;
}
/* register network adapter */
result = dvb_net_init(adapter, &dvb->net, &dvb->demux.dmx);
if (result < 0) {
pr_warn("%s: dvb_net_init failed (errno = %d)\n",
dvb->name, result);
goto fail_fe_conn;
}
return 0;
fail_fe_conn:
dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, &dvb->fe_mem);
fail_fe_mem:
dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, &dvb->fe_hw);
fail_fe_hw:
dvb_dmxdev_release(&dvb->dmxdev);
fail_dmxdev:
dvb_dmx_release(&dvb->demux);
fail_dmx:
dvb_unregister_frontend(dvb->frontend);
fail_frontend:
dvb_frontend_detach(dvb->frontend);
dvb->frontend = NULL;
return result;
}
/* ------------------------------------------------------------------ */
/* Register a single adapter and one or more frontends */
int vb2_dvb_register_bus(struct vb2_dvb_frontends *f,
struct module *module,
void *adapter_priv,
struct device *device,
struct media_device *mdev,
short *adapter_nr,
int mfe_shared)
{
struct list_head *list, *q;
struct vb2_dvb_frontend *fe;
int res;
fe = vb2_dvb_get_frontend(f, 1);
if (!fe) {
pr_warn("Unable to register the adapter which has no frontends\n");
return -EINVAL;
}
/* Bring up the adapter */
res = vb2_dvb_register_adapter(f, module, adapter_priv, device, mdev,
fe->dvb.name, adapter_nr, mfe_shared);
if (res < 0) {
pr_warn("vb2_dvb_register_adapter failed (errno = %d)\n", res);
return res;
}
/* Attach all of the frontends to the adapter */
mutex_lock(&f->lock);
list_for_each_safe(list, q, &f->felist) {
fe = list_entry(list, struct vb2_dvb_frontend, felist);
res = vb2_dvb_register_frontend(&f->adapter, &fe->dvb);
if (res < 0) {
pr_warn("%s: vb2_dvb_register_frontend failed (errno = %d)\n",
fe->dvb.name, res);
goto err;
}
res = dvb_create_media_graph(&f->adapter, false);
if (res < 0)
goto err;
}
mutex_unlock(&f->lock);
return 0;
err:
mutex_unlock(&f->lock);
vb2_dvb_unregister_bus(f);
return res;
}
EXPORT_SYMBOL(vb2_dvb_register_bus);
void vb2_dvb_unregister_bus(struct vb2_dvb_frontends *f)
{
vb2_dvb_dealloc_frontends(f);
dvb_unregister_adapter(&f->adapter);
}
EXPORT_SYMBOL(vb2_dvb_unregister_bus);
struct vb2_dvb_frontend *vb2_dvb_get_frontend(
struct vb2_dvb_frontends *f, int id)
{
struct list_head *list, *q;
struct vb2_dvb_frontend *fe, *ret = NULL;
mutex_lock(&f->lock);
list_for_each_safe(list, q, &f->felist) {
fe = list_entry(list, struct vb2_dvb_frontend, felist);
if (fe->id == id) {
ret = fe;
break;
}
}
mutex_unlock(&f->lock);
return ret;
}
EXPORT_SYMBOL(vb2_dvb_get_frontend);
int vb2_dvb_find_frontend(struct vb2_dvb_frontends *f,
struct dvb_frontend *p)
{
struct list_head *list, *q;
struct vb2_dvb_frontend *fe = NULL;
int ret = 0;
mutex_lock(&f->lock);
list_for_each_safe(list, q, &f->felist) {
fe = list_entry(list, struct vb2_dvb_frontend, felist);
if (fe->dvb.frontend == p) {
ret = fe->id;
break;
}
}
mutex_unlock(&f->lock);
return ret;
}
EXPORT_SYMBOL(vb2_dvb_find_frontend);
struct vb2_dvb_frontend *vb2_dvb_alloc_frontend(
struct vb2_dvb_frontends *f, int id)
{
struct vb2_dvb_frontend *fe;
fe = kzalloc_obj(struct vb2_dvb_frontend);
if (fe == NULL)
return NULL;
fe->id = id;
mutex_init(&fe->dvb.lock);
mutex_lock(&f->lock);
list_add_tail(&fe->felist, &f->felist);
mutex_unlock(&f->lock);
return fe;
}
EXPORT_SYMBOL(vb2_dvb_alloc_frontend);
void vb2_dvb_dealloc_frontends(struct vb2_dvb_frontends *f)
{
struct list_head *list, *q;
struct vb2_dvb_frontend *fe;
mutex_lock(&f->lock);
list_for_each_safe(list, q, &f->felist) {
fe = list_entry(list, struct vb2_dvb_frontend, felist);
if (fe->dvb.net.dvbdev) {
dvb_net_release(&fe->dvb.net);
fe->dvb.demux.dmx.remove_frontend(&fe->dvb.demux.dmx,
&fe->dvb.fe_mem);
fe->dvb.demux.dmx.remove_frontend(&fe->dvb.demux.dmx,
&fe->dvb.fe_hw);
dvb_dmxdev_release(&fe->dvb.dmxdev);
dvb_dmx_release(&fe->dvb.demux);
dvb_unregister_frontend(fe->dvb.frontend);
}
if (fe->dvb.frontend)
/* always allocated, may have been reset */
dvb_frontend_detach(fe->dvb.frontend);
list_del(list); /* remove list entry */
kfree(fe); /* free frontend allocation */
}
mutex_unlock(&f->lock);
}
EXPORT_SYMBOL(vb2_dvb_dealloc_frontends);
@@ -0,0 +1,131 @@
/*
* videobuf2-memops.c - generic memory handling routines for videobuf2
*
* Copyright (C) 2010 Samsung Electronics
*
* Author: Pawel Osciak <pawel@osciak.com>
* Marek Szyprowski <m.szyprowski@samsung.com>
*
* 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.
*/
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/dma-mapping.h>
#include <linux/vmalloc.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/file.h>
#include <media/videobuf2-v4l2.h>
#include <media/videobuf2-memops.h>
/**
* vb2_create_framevec() - map virtual addresses to pfns
* @start: Virtual user address where we start mapping
* @length: Length of a range to map
* @write: Should we map for writing into the area
*
* This function allocates and fills in a vector with pfns corresponding to
* virtual address range passed in arguments. If pfns have corresponding pages,
* page references are also grabbed to pin pages in memory. The function
* returns pointer to the vector on success and error pointer in case of
* failure. Returned vector needs to be freed via vb2_destroy_pfnvec().
*/
struct frame_vector *vb2_create_framevec(unsigned long start,
unsigned long length,
bool write)
{
int ret;
unsigned long first, last;
unsigned long nr;
struct frame_vector *vec;
first = start >> PAGE_SHIFT;
last = (start + length - 1) >> PAGE_SHIFT;
nr = last - first + 1;
vec = frame_vector_create(nr);
if (!vec)
return ERR_PTR(-ENOMEM);
ret = get_vaddr_frames(start & PAGE_MASK, nr, write, vec);
if (ret < 0)
goto out_destroy;
/* We accept only complete set of PFNs */
if (ret != nr) {
ret = -EFAULT;
goto out_release;
}
return vec;
out_release:
put_vaddr_frames(vec);
out_destroy:
frame_vector_destroy(vec);
return ERR_PTR(ret);
}
EXPORT_SYMBOL(vb2_create_framevec);
/**
* vb2_destroy_framevec() - release vector of mapped pfns
* @vec: vector of pfns / pages to release
*
* This releases references to all pages in the vector @vec (if corresponding
* pfns are backed by pages) and frees the passed vector.
*/
void vb2_destroy_framevec(struct frame_vector *vec)
{
put_vaddr_frames(vec);
frame_vector_destroy(vec);
}
EXPORT_SYMBOL(vb2_destroy_framevec);
/**
* vb2_common_vm_open() - increase refcount of the vma
* @vma: virtual memory region for the mapping
*
* This function adds another user to the provided vma. It expects
* struct vb2_vmarea_handler pointer in vma->vm_private_data.
*/
static void vb2_common_vm_open(struct vm_area_struct *vma)
{
struct vb2_vmarea_handler *h = vma->vm_private_data;
pr_debug("%s: %p, refcount: %d, vma: %08lx-%08lx\n",
__func__, h, refcount_read(h->refcount), vma->vm_start,
vma->vm_end);
refcount_inc(h->refcount);
}
/**
* vb2_common_vm_close() - decrease refcount of the vma
* @vma: virtual memory region for the mapping
*
* This function releases the user from the provided vma. It expects
* struct vb2_vmarea_handler pointer in vma->vm_private_data.
*/
static void vb2_common_vm_close(struct vm_area_struct *vma)
{
struct vb2_vmarea_handler *h = vma->vm_private_data;
pr_debug("%s: %p, refcount: %d, vma: %08lx-%08lx\n",
__func__, h, refcount_read(h->refcount), vma->vm_start,
vma->vm_end);
h->put(h->arg);
}
/*
* vb2_common_vm_ops - common vm_ops used for tracking refcount of mmapped
* video buffers
*/
const struct vm_operations_struct vb2_common_vm_ops = {
.open = vb2_common_vm_open,
.close = vb2_common_vm_close,
};
EXPORT_SYMBOL_GPL(vb2_common_vm_ops);
MODULE_DESCRIPTION("common memory handling routines for videobuf2");
MODULE_AUTHOR("Pawel Osciak <pawel@osciak.com>");
MODULE_LICENSE("GPL");
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,445 @@
/*
* videobuf2-vmalloc.c - vmalloc memory allocator for videobuf2
*
* Copyright (C) 2010 Samsung Electronics
*
* Author: Pawel Osciak <pawel@osciak.com>
*
* 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.
*/
#include <linux/io.h>
#include <linux/module.h>
#include <linux/mm.h>
#include <linux/refcount.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <media/videobuf2-v4l2.h>
#include <media/videobuf2-vmalloc.h>
#include <media/videobuf2-memops.h>
struct vb2_vmalloc_buf {
void *vaddr;
struct frame_vector *vec;
enum dma_data_direction dma_dir;
unsigned long size;
refcount_t refcount;
struct vb2_vmarea_handler handler;
struct dma_buf *dbuf;
};
static void vb2_vmalloc_put(void *buf_priv);
static void *vb2_vmalloc_alloc(struct vb2_buffer *vb, struct device *dev,
unsigned long size)
{
struct vb2_vmalloc_buf *buf;
buf = kzalloc_obj(*buf, GFP_KERNEL | vb->vb2_queue->gfp_flags);
if (!buf)
return ERR_PTR(-ENOMEM);
buf->size = size;
buf->vaddr = vmalloc_user(buf->size);
if (!buf->vaddr) {
pr_debug("vmalloc of size %ld failed\n", buf->size);
kfree(buf);
return ERR_PTR(-ENOMEM);
}
buf->dma_dir = vb->vb2_queue->dma_dir;
buf->handler.refcount = &buf->refcount;
buf->handler.put = vb2_vmalloc_put;
buf->handler.arg = buf;
refcount_set(&buf->refcount, 1);
return buf;
}
static void vb2_vmalloc_put(void *buf_priv)
{
struct vb2_vmalloc_buf *buf = buf_priv;
if (refcount_dec_and_test(&buf->refcount)) {
vfree(buf->vaddr);
kfree(buf);
}
}
static void *vb2_vmalloc_get_userptr(struct vb2_buffer *vb, struct device *dev,
unsigned long vaddr, unsigned long size)
{
struct vb2_vmalloc_buf *buf;
struct frame_vector *vec;
int n_pages, offset, i;
int ret = -ENOMEM;
buf = kzalloc_obj(*buf);
if (!buf)
return ERR_PTR(-ENOMEM);
buf->dma_dir = vb->vb2_queue->dma_dir;
offset = vaddr & ~PAGE_MASK;
buf->size = size;
vec = vb2_create_framevec(vaddr, size,
buf->dma_dir == DMA_FROM_DEVICE ||
buf->dma_dir == DMA_BIDIRECTIONAL);
if (IS_ERR(vec)) {
ret = PTR_ERR(vec);
goto fail_pfnvec_create;
}
buf->vec = vec;
n_pages = frame_vector_count(vec);
if (frame_vector_to_pages(vec) < 0) {
unsigned long *nums = frame_vector_pfns(vec);
/*
* We cannot get page pointers for these pfns. Check memory is
* physically contiguous and use direct mapping.
*/
for (i = 1; i < n_pages; i++)
if (nums[i-1] + 1 != nums[i])
goto fail_map;
buf->vaddr = (__force void *)
ioremap(__pfn_to_phys(nums[0]), size + offset);
} else {
buf->vaddr = vm_map_ram(frame_vector_pages(vec), n_pages, -1);
}
if (!buf->vaddr)
goto fail_map;
buf->vaddr += offset;
return buf;
fail_map:
vb2_destroy_framevec(vec);
fail_pfnvec_create:
kfree(buf);
return ERR_PTR(ret);
}
static void vb2_vmalloc_put_userptr(void *buf_priv)
{
struct vb2_vmalloc_buf *buf = buf_priv;
unsigned long vaddr = (unsigned long)buf->vaddr & PAGE_MASK;
unsigned int i;
struct page **pages;
unsigned int n_pages;
if (!buf->vec->is_pfns) {
n_pages = frame_vector_count(buf->vec);
if (vaddr)
vm_unmap_ram((void *)vaddr, n_pages);
if (buf->dma_dir == DMA_FROM_DEVICE ||
buf->dma_dir == DMA_BIDIRECTIONAL) {
pages = frame_vector_pages(buf->vec);
if (!WARN_ON_ONCE(IS_ERR(pages)))
for (i = 0; i < n_pages; i++)
set_page_dirty_lock(pages[i]);
}
} else {
iounmap((__force void __iomem *)buf->vaddr);
}
vb2_destroy_framevec(buf->vec);
kfree(buf);
}
static void *vb2_vmalloc_vaddr(struct vb2_buffer *vb, void *buf_priv)
{
struct vb2_vmalloc_buf *buf = buf_priv;
if (!buf->vaddr) {
pr_err("Address of an unallocated plane requested or cannot map user pointer\n");
return NULL;
}
return buf->vaddr;
}
static unsigned int vb2_vmalloc_num_users(void *buf_priv)
{
struct vb2_vmalloc_buf *buf = buf_priv;
return refcount_read(&buf->refcount);
}
static int vb2_vmalloc_mmap(void *buf_priv, struct vm_area_struct *vma)
{
struct vb2_vmalloc_buf *buf = buf_priv;
int ret;
if (!buf) {
pr_err("No memory to map\n");
return -EINVAL;
}
ret = remap_vmalloc_range(vma, buf->vaddr, 0);
if (ret) {
pr_err("Remapping vmalloc memory, error: %d\n", ret);
return ret;
}
/*
* Make sure that vm_areas for 2 buffers won't be merged together
*/
vm_flags_set(vma, VM_DONTEXPAND);
/*
* Use common vm_area operations to track buffer refcount.
*/
vma->vm_private_data = &buf->handler;
vma->vm_ops = &vb2_common_vm_ops;
vma->vm_ops->open(vma);
return 0;
}
#ifdef CONFIG_HAS_DMA
/*********************************************/
/* DMABUF ops for exporters */
/*********************************************/
struct vb2_vmalloc_attachment {
struct sg_table sgt;
enum dma_data_direction dma_dir;
};
static int vb2_vmalloc_dmabuf_ops_attach(struct dma_buf *dbuf,
struct dma_buf_attachment *dbuf_attach)
{
struct vb2_vmalloc_attachment *attach;
struct vb2_vmalloc_buf *buf = dbuf->priv;
int num_pages = PAGE_ALIGN(buf->size) / PAGE_SIZE;
struct sg_table *sgt;
struct scatterlist *sg;
void *vaddr = buf->vaddr;
int ret;
int i;
attach = kzalloc_obj(*attach);
if (!attach)
return -ENOMEM;
sgt = &attach->sgt;
ret = sg_alloc_table(sgt, num_pages, GFP_KERNEL);
if (ret) {
kfree(attach);
return ret;
}
for_each_sgtable_sg(sgt, sg, i) {
struct page *page = vmalloc_to_page(vaddr);
if (!page) {
sg_free_table(sgt);
kfree(attach);
return -ENOMEM;
}
sg_set_page(sg, page, PAGE_SIZE, 0);
vaddr += PAGE_SIZE;
}
attach->dma_dir = DMA_NONE;
dbuf_attach->priv = attach;
return 0;
}
static void vb2_vmalloc_dmabuf_ops_detach(struct dma_buf *dbuf,
struct dma_buf_attachment *db_attach)
{
struct vb2_vmalloc_attachment *attach = db_attach->priv;
struct sg_table *sgt;
if (!attach)
return;
sgt = &attach->sgt;
/* release the scatterlist cache */
if (attach->dma_dir != DMA_NONE)
dma_unmap_sgtable(db_attach->dev, sgt, attach->dma_dir, 0);
sg_free_table(sgt);
kfree(attach);
db_attach->priv = NULL;
}
static struct sg_table *vb2_vmalloc_dmabuf_ops_map(
struct dma_buf_attachment *db_attach, enum dma_data_direction dma_dir)
{
struct vb2_vmalloc_attachment *attach = db_attach->priv;
struct sg_table *sgt;
sgt = &attach->sgt;
/* return previously mapped sg table */
if (attach->dma_dir == dma_dir)
return sgt;
/* release any previous cache */
if (attach->dma_dir != DMA_NONE) {
dma_unmap_sgtable(db_attach->dev, sgt, attach->dma_dir, 0);
attach->dma_dir = DMA_NONE;
}
/* mapping to the client with new direction */
if (dma_map_sgtable(db_attach->dev, sgt, dma_dir, 0)) {
pr_err("failed to map scatterlist\n");
return ERR_PTR(-EIO);
}
attach->dma_dir = dma_dir;
return sgt;
}
static void vb2_vmalloc_dmabuf_ops_unmap(struct dma_buf_attachment *db_attach,
struct sg_table *sgt, enum dma_data_direction dma_dir)
{
/* nothing to be done here */
}
static void vb2_vmalloc_dmabuf_ops_release(struct dma_buf *dbuf)
{
/* drop reference obtained in vb2_vmalloc_get_dmabuf */
vb2_vmalloc_put(dbuf->priv);
}
static int vb2_vmalloc_dmabuf_ops_vmap(struct dma_buf *dbuf,
struct iosys_map *map)
{
struct vb2_vmalloc_buf *buf = dbuf->priv;
iosys_map_set_vaddr(map, buf->vaddr);
return 0;
}
static int vb2_vmalloc_dmabuf_ops_mmap(struct dma_buf *dbuf,
struct vm_area_struct *vma)
{
return vb2_vmalloc_mmap(dbuf->priv, vma);
}
static const struct dma_buf_ops vb2_vmalloc_dmabuf_ops = {
.attach = vb2_vmalloc_dmabuf_ops_attach,
.detach = vb2_vmalloc_dmabuf_ops_detach,
.map_dma_buf = vb2_vmalloc_dmabuf_ops_map,
.unmap_dma_buf = vb2_vmalloc_dmabuf_ops_unmap,
.vmap = vb2_vmalloc_dmabuf_ops_vmap,
.mmap = vb2_vmalloc_dmabuf_ops_mmap,
.release = vb2_vmalloc_dmabuf_ops_release,
};
static struct dma_buf *vb2_vmalloc_get_dmabuf(struct vb2_buffer *vb,
void *buf_priv,
unsigned long flags)
{
struct vb2_vmalloc_buf *buf = buf_priv;
struct dma_buf *dbuf;
DEFINE_DMA_BUF_EXPORT_INFO(exp_info);
exp_info.ops = &vb2_vmalloc_dmabuf_ops;
exp_info.size = buf->size;
exp_info.flags = flags;
exp_info.priv = buf;
if (WARN_ON(!buf->vaddr))
return NULL;
dbuf = dma_buf_export(&exp_info);
if (IS_ERR(dbuf))
return NULL;
/* dmabuf keeps reference to vb2 buffer */
refcount_inc(&buf->refcount);
return dbuf;
}
#endif /* CONFIG_HAS_DMA */
/*********************************************/
/* callbacks for DMABUF buffers */
/*********************************************/
static int vb2_vmalloc_map_dmabuf(void *mem_priv)
{
struct vb2_vmalloc_buf *buf = mem_priv;
struct iosys_map map;
int ret;
ret = dma_buf_vmap_unlocked(buf->dbuf, &map);
if (ret)
return -EFAULT;
buf->vaddr = map.vaddr;
return 0;
}
static void vb2_vmalloc_unmap_dmabuf(void *mem_priv)
{
struct vb2_vmalloc_buf *buf = mem_priv;
struct iosys_map map = IOSYS_MAP_INIT_VADDR(buf->vaddr);
dma_buf_vunmap_unlocked(buf->dbuf, &map);
buf->vaddr = NULL;
}
static void vb2_vmalloc_detach_dmabuf(void *mem_priv)
{
struct vb2_vmalloc_buf *buf = mem_priv;
struct iosys_map map = IOSYS_MAP_INIT_VADDR(buf->vaddr);
if (buf->vaddr)
dma_buf_vunmap_unlocked(buf->dbuf, &map);
kfree(buf);
}
static void *vb2_vmalloc_attach_dmabuf(struct vb2_buffer *vb,
struct device *dev,
struct dma_buf *dbuf,
unsigned long size)
{
struct vb2_vmalloc_buf *buf;
if (dbuf->size < size)
return ERR_PTR(-EFAULT);
buf = kzalloc_obj(*buf);
if (!buf)
return ERR_PTR(-ENOMEM);
buf->dbuf = dbuf;
buf->dma_dir = vb->vb2_queue->dma_dir;
buf->size = size;
return buf;
}
const struct vb2_mem_ops vb2_vmalloc_memops = {
.alloc = vb2_vmalloc_alloc,
.put = vb2_vmalloc_put,
.get_userptr = vb2_vmalloc_get_userptr,
.put_userptr = vb2_vmalloc_put_userptr,
#ifdef CONFIG_HAS_DMA
.get_dmabuf = vb2_vmalloc_get_dmabuf,
#endif
.map_dmabuf = vb2_vmalloc_map_dmabuf,
.unmap_dmabuf = vb2_vmalloc_unmap_dmabuf,
.attach_dmabuf = vb2_vmalloc_attach_dmabuf,
.detach_dmabuf = vb2_vmalloc_detach_dmabuf,
.vaddr = vb2_vmalloc_vaddr,
.mmap = vb2_vmalloc_mmap,
.num_users = vb2_vmalloc_num_users,
};
EXPORT_SYMBOL_GPL(vb2_vmalloc_memops);
MODULE_DESCRIPTION("vmalloc memory handling routines for videobuf2");
MODULE_AUTHOR("Pawel Osciak <pawel@osciak.com>");
MODULE_LICENSE("GPL");
MODULE_IMPORT_NS("DMA_BUF");