提交 7120d6bf 编写于 作者: H Hans Verkuil 提交者: Mauro Carvalho Chehab

media: tm6000: remove deprecated driver

The tm6000 driver does not use the vb2 framework for streaming
video, instead it uses the old vb1 framework and nobody stepped in to
convert this driver to vb2.

The hardware is very old, so the decision was made to remove it
altogether since we want to get rid of the old vb1 framework.
Signed-off-by: NHans Verkuil <hverkuil-cisco@xs4all.nl>
Signed-off-by: NMauro Carvalho Chehab <mchehab@kernel.org>
上级 d2a8e92f
.. SPDX-License-Identifier: GPL-2.0
TM6000 cards list
=================
.. tabularcolumns:: |p{1.4cm}|p{11.1cm}|p{4.2cm}|
.. flat-table::
:header-rows: 1
:widths: 2 19 18
:stub-columns: 0
* - Card number
- Card name
- USB IDs
* - 0
- Unknown tm6000 video grabber
-
* - 1
- Generic tm5600 board
- 6000:0001
* - 2
- Generic tm6000 board
-
* - 3
- Generic tm6010 board
- 6000:0002
* - 4
- 10Moons UT 821
-
* - 5
- 10Moons UT 330
-
* - 6
- ADSTECH Dual TV USB
- 06e1:f332
* - 7
- Freecom Hybrid Stick / Moka DVB-T Receiver Dual
- 14aa:0620
* - 8
- ADSTECH Mini Dual TV USB
- 06e1:b339
* - 9
- Hauppauge WinTV HVR-900H / WinTV USB2-Stick
- 2040:6600, 2040:6601, 2040:6610, 2040:6611
* - 10
- Beholder Wander DVB-T/TV/FM USB2.0
- 6000:dec0
* - 11
- Beholder Voyager TV/FM USB2.0
- 6000:dec1
* - 12
- Terratec Cinergy Hybrid XE / Cinergy Hybrid-Stick
- 0ccd:0086, 0ccd:00A5
* - 13
- Twinhan TU501(704D1)
- 13d3:3240, 13d3:3241, 13d3:3243, 13d3:3264
* - 14
- Beholder Wander Lite DVB-T/TV/FM USB2.0
- 6000:dec2
* - 15
- Beholder Voyager Lite TV/FM USB2.0
- 6000:dec3
* - 16
- Terratec Grabster AV 150/250 MX
- 0ccd:0079
......@@ -92,9 +92,6 @@ pwc USB Philips Cameras
s2250 Sensoray 2250/2251
s2255drv USB Sensoray 2255 video capture device
smsusb Siano SMS1xxx based MDTV receiver
tm6000-alsa TV Master TM5600/6000/6010 audio
tm6000-dvb DVB Support for tm6000 based TV cards
tm6000 TV Master TM5600/6000/6010 driver
ttusb_dec Technotrend/Hauppauge USB DEC devices
usbtv USBTV007 video capture
uvcvideo USB Video Class (UVC)
......@@ -107,7 +104,6 @@ zd1301 ZyDAS ZD1301
au0828-cardlist
cx231xx-cardlist
em28xx-cardlist
tm6000-cardlist
siano-cardlist
gspca-cardlist
......
......@@ -20976,15 +20976,6 @@ W: http://sourceforge.net/projects/tlan/
F: Documentation/networking/device_drivers/ethernet/ti/tlan.rst
F: drivers/net/ethernet/ti/tlan.*
TM6000 VIDEO4LINUX DRIVER
M: Mauro Carvalho Chehab <mchehab@kernel.org>
L: linux-media@vger.kernel.org
S: Odd fixes
W: https://linuxtv.org
T: git git://linuxtv.org/media_tree.git
F: Documentation/admin-guide/media/tm6000*
F: drivers/staging/media/deprecated/tm6000/
TMIO/SDHI MMC DRIVER
M: Wolfram Sang <wsa+renesas@sang-engineering.com>
L: linux-mmc@vger.kernel.org
......
......@@ -53,7 +53,6 @@ menuconfig STAGING_MEDIA_DEPRECATED
if STAGING_MEDIA_DEPRECATED
source "drivers/staging/media/deprecated/atmel/Kconfig"
source "drivers/staging/media/deprecated/saa7146/Kconfig"
source "drivers/staging/media/deprecated/tm6000/Kconfig"
endif
endif
......@@ -9,5 +9,4 @@ obj-$(CONFIG_VIDEO_ROCKCHIP_VDEC) += rkvdec/
obj-$(CONFIG_VIDEO_SUNXI) += sunxi/
obj-$(CONFIG_VIDEO_TEGRA) += tegra-video/
obj-$(CONFIG_VIDEO_IPU3_IMGU) += ipu3/
obj-$(CONFIG_VIDEO_TM6000) += deprecated/tm6000/
obj-y += deprecated/saa7146/
# SPDX-License-Identifier: GPL-2.0-only
config VIDEO_TM6000
tristate "TV Master TM5600/6000/6010 driver (DEPRECATED)"
depends on VIDEO_DEV && I2C && INPUT && RC_CORE && USB
select VIDEO_TUNER
select MEDIA_TUNER_XC2028
select MEDIA_TUNER_XC5000
select VIDEOBUF_VMALLOC
help
Support for TM5600/TM6000/TM6010 USB Device
Since these cards have no MPEG decoder onboard, they transmit
only compressed MPEG data over the usb bus, so you need
an external software decoder to watch TV on your computer.
This driver is deprecated and is scheduled for removal by
the beginning of 2023. See the TODO file for more information.
Say Y if you own such a device and want to use it.
config VIDEO_TM6000_ALSA
tristate "TV Master TM5600/6000/6010 audio support"
depends on VIDEO_TM6000 && SND
select SND_PCM
help
This is a video4linux driver for direct (DMA) audio for
TM5600/TM6000/TM6010 USB Devices.
To compile this driver as a module, choose M here: the
module will be called tm6000-alsa.
config VIDEO_TM6000_DVB
tristate "DVB Support for tm6000 based TV cards"
depends on VIDEO_TM6000 && DVB_CORE && USB
select DVB_ZL10353
help
This adds support for DVB cards based on the tm5600/tm6000 chip.
# SPDX-License-Identifier: GPL-2.0
tm6000-y := tm6000-cards.o \
tm6000-core.o \
tm6000-i2c.o \
tm6000-video.o \
tm6000-stds.o \
tm6000-input.o
obj-$(CONFIG_VIDEO_TM6000) += tm6000.o
obj-$(CONFIG_VIDEO_TM6000_ALSA) += tm6000-alsa.o
obj-$(CONFIG_VIDEO_TM6000_DVB) += tm6000-dvb.o
ccflags-y += -I $(srctree)/drivers/media/tuners
ccflags-y += -I $(srctree)/drivers/media/dvb-frontends
This is one of the few drivers still not using the vb2
framework, so this driver is now deprecated with the intent of
removing it altogether by the beginning of 2023.
In order to keep this driver it has to be converted to vb2.
If someone is interested in doing this work, then contact the
linux-media mailinglist (https://linuxtv.org/lists.php).
// SPDX-License-Identifier: GPL-2.0
// Support for audio capture for tm5600/6000/6010
// Copyright (c) 2007-2008 Mauro Carvalho Chehab <mchehab@kernel.org>
//
// Based on cx88-alsa.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/usb.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/control.h>
#include <sound/initval.h>
#include "tm6000.h"
#include "tm6000-regs.h"
#undef dprintk
#define dprintk(level, fmt, arg...) do { \
if (debug >= level) \
printk(KERN_INFO "%s/1: " fmt, chip->core->name , ## arg); \
} while (0)
/****************************************************************************
Module global static vars
****************************************************************************/
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */
static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
module_param_array(enable, bool, NULL, 0444);
MODULE_PARM_DESC(enable, "Enable tm6000x soundcard. default enabled.");
module_param_array(index, int, NULL, 0444);
MODULE_PARM_DESC(index, "Index value for tm6000x capture interface(s).");
/****************************************************************************
Module macros
****************************************************************************/
MODULE_DESCRIPTION("ALSA driver module for tm5600/tm6000/tm6010 based TV cards");
MODULE_AUTHOR("Mauro Carvalho Chehab");
MODULE_LICENSE("GPL v2");
static unsigned int debug;
module_param(debug, int, 0644);
MODULE_PARM_DESC(debug, "enable debug messages");
/****************************************************************************
Module specific functions
****************************************************************************/
/*
* BOARD Specific: Sets audio DMA
*/
static int _tm6000_start_audio_dma(struct snd_tm6000_card *chip)
{
struct tm6000_core *core = chip->core;
dprintk(1, "Starting audio DMA\n");
/* Enables audio */
tm6000_set_reg_mask(core, TM6010_REQ07_RCC_ACTIVE_IF, 0x40, 0x40);
tm6000_set_audio_bitrate(core, 48000);
return 0;
}
/*
* BOARD Specific: Resets audio DMA
*/
static int _tm6000_stop_audio_dma(struct snd_tm6000_card *chip)
{
struct tm6000_core *core = chip->core;
dprintk(1, "Stopping audio DMA\n");
/* Disables audio */
tm6000_set_reg_mask(core, TM6010_REQ07_RCC_ACTIVE_IF, 0x00, 0x40);
return 0;
}
/****************************************************************************
ALSA PCM Interface
****************************************************************************/
/*
* Digital hardware definition
*/
#define DEFAULT_FIFO_SIZE 4096
static const struct snd_pcm_hardware snd_tm6000_digital_hw = {
.info = SNDRV_PCM_INFO_BATCH |
SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP_VALID,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
.rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_KNOT,
.rate_min = 48000,
.rate_max = 48000,
.channels_min = 2,
.channels_max = 2,
.period_bytes_min = 64,
.period_bytes_max = 12544,
.periods_min = 2,
.periods_max = 98,
.buffer_bytes_max = 62720 * 8,
};
/*
* audio pcm capture open callback
*/
static int snd_tm6000_pcm_open(struct snd_pcm_substream *substream)
{
struct snd_tm6000_card *chip = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
int err;
err = snd_pcm_hw_constraint_pow2(runtime, 0,
SNDRV_PCM_HW_PARAM_PERIODS);
if (err < 0)
goto _error;
chip->substream = substream;
runtime->hw = snd_tm6000_digital_hw;
snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
return 0;
_error:
dprintk(1, "Error opening PCM!\n");
return err;
}
/*
* audio close callback
*/
static int snd_tm6000_close(struct snd_pcm_substream *substream)
{
struct snd_tm6000_card *chip = snd_pcm_substream_chip(substream);
struct tm6000_core *core = chip->core;
if (atomic_read(&core->stream_started) > 0) {
atomic_set(&core->stream_started, 0);
schedule_work(&core->wq_trigger);
}
return 0;
}
static int tm6000_fillbuf(struct tm6000_core *core, char *buf, int size)
{
struct snd_tm6000_card *chip = core->adev;
struct snd_pcm_substream *substream = chip->substream;
struct snd_pcm_runtime *runtime;
int period_elapsed = 0;
unsigned int stride, buf_pos;
int length;
if (atomic_read(&core->stream_started) == 0)
return 0;
if (!size || !substream) {
dprintk(1, "substream was NULL\n");
return -EINVAL;
}
runtime = substream->runtime;
if (!runtime || !runtime->dma_area) {
dprintk(1, "runtime was NULL\n");
return -EINVAL;
}
buf_pos = chip->buf_pos;
stride = runtime->frame_bits >> 3;
if (stride == 0) {
dprintk(1, "stride is zero\n");
return -EINVAL;
}
length = size / stride;
if (length == 0) {
dprintk(1, "%s: length was zero\n", __func__);
return -EINVAL;
}
dprintk(1, "Copying %d bytes at %p[%d] - buf size=%d x %d\n", size,
runtime->dma_area, buf_pos,
(unsigned int)runtime->buffer_size, stride);
if (buf_pos + length >= runtime->buffer_size) {
unsigned int cnt = runtime->buffer_size - buf_pos;
memcpy(runtime->dma_area + buf_pos * stride, buf, cnt * stride);
memcpy(runtime->dma_area, buf + cnt * stride,
length * stride - cnt * stride);
} else
memcpy(runtime->dma_area + buf_pos * stride, buf,
length * stride);
snd_pcm_stream_lock(substream);
chip->buf_pos += length;
if (chip->buf_pos >= runtime->buffer_size)
chip->buf_pos -= runtime->buffer_size;
chip->period_pos += length;
if (chip->period_pos >= runtime->period_size) {
chip->period_pos -= runtime->period_size;
period_elapsed = 1;
}
snd_pcm_stream_unlock(substream);
if (period_elapsed)
snd_pcm_period_elapsed(substream);
return 0;
}
/*
* prepare callback
*/
static int snd_tm6000_prepare(struct snd_pcm_substream *substream)
{
struct snd_tm6000_card *chip = snd_pcm_substream_chip(substream);
chip->buf_pos = 0;
chip->period_pos = 0;
return 0;
}
/*
* trigger callback
*/
static void audio_trigger(struct work_struct *work)
{
struct tm6000_core *core = container_of(work, struct tm6000_core,
wq_trigger);
struct snd_tm6000_card *chip = core->adev;
if (atomic_read(&core->stream_started)) {
dprintk(1, "starting capture");
_tm6000_start_audio_dma(chip);
} else {
dprintk(1, "stopping capture");
_tm6000_stop_audio_dma(chip);
}
}
static int snd_tm6000_card_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct snd_tm6000_card *chip = snd_pcm_substream_chip(substream);
struct tm6000_core *core = chip->core;
int err = 0;
switch (cmd) {
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_START:
atomic_set(&core->stream_started, 1);
break;
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_STOP:
atomic_set(&core->stream_started, 0);
break;
default:
err = -EINVAL;
break;
}
schedule_work(&core->wq_trigger);
return err;
}
/*
* pointer callback
*/
static snd_pcm_uframes_t snd_tm6000_pointer(struct snd_pcm_substream *substream)
{
struct snd_tm6000_card *chip = snd_pcm_substream_chip(substream);
return chip->buf_pos;
}
/*
* operators
*/
static const struct snd_pcm_ops snd_tm6000_pcm_ops = {
.open = snd_tm6000_pcm_open,
.close = snd_tm6000_close,
.prepare = snd_tm6000_prepare,
.trigger = snd_tm6000_card_trigger,
.pointer = snd_tm6000_pointer,
};
/*
* create a PCM device
*/
/* FIXME: Control interface - How to control volume/mute? */
/****************************************************************************
Basic Flow for Sound Devices
****************************************************************************/
/*
* Alsa Constructor - Component probe
*/
static int tm6000_audio_init(struct tm6000_core *dev)
{
struct snd_card *card;
struct snd_tm6000_card *chip;
int rc;
static int devnr;
char component[14];
struct snd_pcm *pcm;
if (!dev)
return 0;
if (devnr >= SNDRV_CARDS)
return -ENODEV;
if (!enable[devnr])
return -ENOENT;
rc = snd_card_new(&dev->udev->dev, index[devnr], "tm6000",
THIS_MODULE, 0, &card);
if (rc < 0) {
snd_printk(KERN_ERR "cannot create card instance %d\n", devnr);
return rc;
}
strscpy(card->driver, "tm6000-alsa", sizeof(card->driver));
strscpy(card->shortname, "TM5600/60x0", sizeof(card->shortname));
sprintf(card->longname, "TM5600/60x0 Audio at bus %d device %d",
dev->udev->bus->busnum, dev->udev->devnum);
sprintf(component, "USB%04x:%04x",
le16_to_cpu(dev->udev->descriptor.idVendor),
le16_to_cpu(dev->udev->descriptor.idProduct));
snd_component_add(card, component);
chip = kzalloc(sizeof(struct snd_tm6000_card), GFP_KERNEL);
if (!chip) {
rc = -ENOMEM;
goto error;
}
chip->core = dev;
chip->card = card;
dev->adev = chip;
spin_lock_init(&chip->reg_lock);
rc = snd_pcm_new(card, "TM6000 Audio", 0, 0, 1, &pcm);
if (rc < 0)
goto error_chip;
pcm->info_flags = 0;
pcm->private_data = chip;
strscpy(pcm->name, "Trident TM5600/60x0", sizeof(pcm->name));
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_tm6000_pcm_ops);
snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_VMALLOC, NULL, 0, 0);
INIT_WORK(&dev->wq_trigger, audio_trigger);
rc = snd_card_register(card);
if (rc < 0)
goto error_chip;
dprintk(1, "Registered audio driver for %s\n", card->longname);
return 0;
error_chip:
kfree(chip);
dev->adev = NULL;
error:
snd_card_free(card);
return rc;
}
static int tm6000_audio_fini(struct tm6000_core *dev)
{
struct snd_tm6000_card *chip;
if (!dev)
return 0;
chip = dev->adev;
if (!chip)
return 0;
if (!chip->card)
return 0;
snd_card_free(chip->card);
chip->card = NULL;
kfree(chip);
dev->adev = NULL;
return 0;
}
static struct tm6000_ops audio_ops = {
.type = TM6000_AUDIO,
.name = "TM6000 Audio Extension",
.init = tm6000_audio_init,
.fini = tm6000_audio_fini,
.fillbuf = tm6000_fillbuf,
};
static int __init tm6000_alsa_register(void)
{
return tm6000_register_extension(&audio_ops);
}
static void __exit tm6000_alsa_unregister(void)
{
tm6000_unregister_extension(&audio_ops);
}
module_init(tm6000_alsa_register);
module_exit(tm6000_alsa_unregister);
// SPDX-License-Identifier: GPL-2.0-only
/*
* tm6000-dvb.c - dvb-t support for TM5600/TM6000/TM6010 USB video capture devices
*
* Copyright (C) 2007 Michel Ludwig <michel.ludwig@gmail.com>
*/
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/usb.h>
#include "tm6000.h"
#include "tm6000-regs.h"
#include "zl10353.h"
#include <media/tuner.h>
#include "xc2028.h"
#include "xc5000.h"
MODULE_DESCRIPTION("DVB driver extension module for tm5600/6000/6010 based TV cards");
MODULE_AUTHOR("Mauro Carvalho Chehab");
MODULE_LICENSE("GPL");
static int debug;
module_param(debug, int, 0644);
MODULE_PARM_DESC(debug, "enable debug message");
static inline void print_err_status(struct tm6000_core *dev,
int packet, int status)
{
char *errmsg = "Unknown";
switch (status) {
case -ENOENT:
errmsg = "unlinked synchronously";
break;
case -ECONNRESET:
errmsg = "unlinked asynchronously";
break;
case -ENOSR:
errmsg = "Buffer error (overrun)";
break;
case -EPIPE:
errmsg = "Stalled (device not responding)";
break;
case -EOVERFLOW:
errmsg = "Babble (bad cable?)";
break;
case -EPROTO:
errmsg = "Bit-stuff error (bad cable?)";
break;
case -EILSEQ:
errmsg = "CRC/Timeout (could be anything)";
break;
case -ETIME:
errmsg = "Device does not respond";
break;
}
if (packet < 0) {
dprintk(dev, 1, "URB status %d [%s].\n",
status, errmsg);
} else {
dprintk(dev, 1, "URB packet %d, status %d [%s].\n",
packet, status, errmsg);
}
}
static void tm6000_urb_received(struct urb *urb)
{
int ret;
struct tm6000_core *dev = urb->context;
switch (urb->status) {
case 0:
case -ETIMEDOUT:
break;
case -ENOENT:
case -ECONNRESET:
case -ESHUTDOWN:
return;
default:
print_err_status(dev, 0, urb->status);
}
if (urb->actual_length > 0)
dvb_dmx_swfilter(&dev->dvb->demux, urb->transfer_buffer,
urb->actual_length);
if (dev->dvb->streams > 0) {
ret = usb_submit_urb(urb, GFP_ATOMIC);
if (ret < 0) {
printk(KERN_ERR "tm6000: error %s\n", __func__);
kfree(urb->transfer_buffer);
usb_free_urb(urb);
dev->dvb->bulk_urb = NULL;
}
}
}
static int tm6000_start_stream(struct tm6000_core *dev)
{
int ret;
unsigned int pipe, size;
struct tm6000_dvb *dvb = dev->dvb;
printk(KERN_INFO "tm6000: got start stream request %s\n", __func__);
if (dev->mode != TM6000_MODE_DIGITAL) {
tm6000_init_digital_mode(dev);
dev->mode = TM6000_MODE_DIGITAL;
}
dvb->bulk_urb = usb_alloc_urb(0, GFP_KERNEL);
if (!dvb->bulk_urb)
return -ENOMEM;
pipe = usb_rcvbulkpipe(dev->udev, dev->bulk_in.endp->desc.bEndpointAddress
& USB_ENDPOINT_NUMBER_MASK);
size = usb_maxpacket(dev->udev, pipe);
size = size * 15; /* 512 x 8 or 12 or 15 */
dvb->bulk_urb->transfer_buffer = kzalloc(size, GFP_KERNEL);
if (!dvb->bulk_urb->transfer_buffer) {
usb_free_urb(dvb->bulk_urb);
dvb->bulk_urb = NULL;
return -ENOMEM;
}
usb_fill_bulk_urb(dvb->bulk_urb, dev->udev, pipe,
dvb->bulk_urb->transfer_buffer,
size,
tm6000_urb_received, dev);
ret = usb_clear_halt(dev->udev, pipe);
if (ret < 0) {
printk(KERN_ERR "tm6000: error %i in %s during pipe reset\n",
ret, __func__);
kfree(dvb->bulk_urb->transfer_buffer);
usb_free_urb(dvb->bulk_urb);
dvb->bulk_urb = NULL;
return ret;
} else
printk(KERN_ERR "tm6000: pipe reset\n");
/* mutex_lock(&tm6000_driver.open_close_mutex); */
ret = usb_submit_urb(dvb->bulk_urb, GFP_ATOMIC);
/* mutex_unlock(&tm6000_driver.open_close_mutex); */
if (ret) {
printk(KERN_ERR "tm6000: submit of urb failed (error=%i)\n",
ret);
kfree(dvb->bulk_urb->transfer_buffer);
usb_free_urb(dvb->bulk_urb);
dvb->bulk_urb = NULL;
return ret;
}
return 0;
}
static void tm6000_stop_stream(struct tm6000_core *dev)
{
struct tm6000_dvb *dvb = dev->dvb;
if (dvb->bulk_urb) {
printk(KERN_INFO "urb killing\n");
usb_kill_urb(dvb->bulk_urb);
printk(KERN_INFO "urb buffer free\n");
kfree(dvb->bulk_urb->transfer_buffer);
usb_free_urb(dvb->bulk_urb);
dvb->bulk_urb = NULL;
}
}
static int tm6000_start_feed(struct dvb_demux_feed *feed)
{
struct dvb_demux *demux = feed->demux;
struct tm6000_core *dev = demux->priv;
struct tm6000_dvb *dvb = dev->dvb;
printk(KERN_INFO "tm6000: got start feed request %s\n", __func__);
mutex_lock(&dvb->mutex);
if (dvb->streams == 0) {
dvb->streams = 1;
/* mutex_init(&tm6000_dev->streming_mutex); */
tm6000_start_stream(dev);
} else
++(dvb->streams);
mutex_unlock(&dvb->mutex);
return 0;
}
static int tm6000_stop_feed(struct dvb_demux_feed *feed)
{
struct dvb_demux *demux = feed->demux;
struct tm6000_core *dev = demux->priv;
struct tm6000_dvb *dvb = dev->dvb;
printk(KERN_INFO "tm6000: got stop feed request %s\n", __func__);
mutex_lock(&dvb->mutex);
printk(KERN_INFO "stream %#x\n", dvb->streams);
--(dvb->streams);
if (dvb->streams == 0) {
printk(KERN_INFO "stop stream\n");
tm6000_stop_stream(dev);
/* mutex_destroy(&tm6000_dev->streaming_mutex); */
}
mutex_unlock(&dvb->mutex);
/* mutex_destroy(&tm6000_dev->streaming_mutex); */
return 0;
}
static int tm6000_dvb_attach_frontend(struct tm6000_core *dev)
{
struct tm6000_dvb *dvb = dev->dvb;
if (dev->caps.has_zl10353) {
struct zl10353_config config = {
.demod_address = dev->demod_addr,
.no_tuner = 1,
.parallel_ts = 1,
.if2 = 45700,
.disable_i2c_gate_ctrl = 1,
};
dvb->frontend = dvb_attach(zl10353_attach, &config,
&dev->i2c_adap);
} else {
printk(KERN_ERR "tm6000: no frontend defined for the device!\n");
return -1;
}
return (!dvb->frontend) ? -1 : 0;
}
DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
static int register_dvb(struct tm6000_core *dev)
{
int ret = -1;
struct tm6000_dvb *dvb = dev->dvb;
mutex_init(&dvb->mutex);
dvb->streams = 0;
/* attach the frontend */
ret = tm6000_dvb_attach_frontend(dev);
if (ret < 0) {
printk(KERN_ERR "tm6000: couldn't attach the frontend!\n");
goto err;
}
ret = dvb_register_adapter(&dvb->adapter, "Trident TVMaster 6000 DVB-T",
THIS_MODULE, &dev->udev->dev, adapter_nr);
if (ret < 0) {
pr_err("tm6000: couldn't register the adapter!\n");
goto err;
}
dvb->adapter.priv = dev;
if (dvb->frontend) {
switch (dev->tuner_type) {
case TUNER_XC2028: {
struct xc2028_config cfg = {
.i2c_adap = &dev->i2c_adap,
.i2c_addr = dev->tuner_addr,
};
dvb->frontend->callback = tm6000_tuner_callback;
ret = dvb_register_frontend(&dvb->adapter, dvb->frontend);
if (ret < 0) {
printk(KERN_ERR
"tm6000: couldn't register frontend\n");
goto adapter_err;
}
if (!dvb_attach(xc2028_attach, dvb->frontend, &cfg)) {
printk(KERN_ERR "tm6000: couldn't register frontend (xc3028)\n");
ret = -EINVAL;
goto frontend_err;
}
printk(KERN_INFO "tm6000: XC2028/3028 asked to be attached to frontend!\n");
break;
}
case TUNER_XC5000: {
struct xc5000_config cfg = {
.i2c_address = dev->tuner_addr,
};
dvb->frontend->callback = tm6000_xc5000_callback;
ret = dvb_register_frontend(&dvb->adapter, dvb->frontend);
if (ret < 0) {
printk(KERN_ERR
"tm6000: couldn't register frontend\n");
goto adapter_err;
}
if (!dvb_attach(xc5000_attach, dvb->frontend, &dev->i2c_adap, &cfg)) {
printk(KERN_ERR "tm6000: couldn't register frontend (xc5000)\n");
ret = -EINVAL;
goto frontend_err;
}
printk(KERN_INFO "tm6000: XC5000 asked to be attached to frontend!\n");
break;
}
}
} else
printk(KERN_ERR "tm6000: no frontend found\n");
dvb->demux.dmx.capabilities = DMX_TS_FILTERING | DMX_SECTION_FILTERING
| DMX_MEMORY_BASED_FILTERING;
dvb->demux.priv = dev;
dvb->demux.filternum = 8;
dvb->demux.feednum = 8;
dvb->demux.start_feed = tm6000_start_feed;
dvb->demux.stop_feed = tm6000_stop_feed;
dvb->demux.write_to_decoder = NULL;
ret = dvb_dmx_init(&dvb->demux);
if (ret < 0) {
printk(KERN_ERR "tm6000: dvb_dmx_init failed (errno = %d)\n", ret);
goto frontend_err;
}
dvb->dmxdev.filternum = dev->dvb->demux.filternum;
dvb->dmxdev.demux = &dev->dvb->demux.dmx;
dvb->dmxdev.capabilities = 0;
ret = dvb_dmxdev_init(&dvb->dmxdev, &dvb->adapter);
if (ret < 0) {
printk(KERN_ERR "tm6000: dvb_dmxdev_init failed (errno = %d)\n", ret);
goto dvb_dmx_err;
}
return 0;
dvb_dmx_err:
dvb_dmx_release(&dvb->demux);
frontend_err:
if (dvb->frontend) {
dvb_unregister_frontend(dvb->frontend);
dvb_frontend_detach(dvb->frontend);
}
adapter_err:
dvb_unregister_adapter(&dvb->adapter);
err:
return ret;
}
static void unregister_dvb(struct tm6000_core *dev)
{
struct tm6000_dvb *dvb = dev->dvb;
if (dvb->bulk_urb) {
struct urb *bulk_urb = dvb->bulk_urb;
kfree(bulk_urb->transfer_buffer);
bulk_urb->transfer_buffer = NULL;
usb_unlink_urb(bulk_urb);
usb_free_urb(bulk_urb);
}
/* mutex_lock(&tm6000_driver.open_close_mutex); */
if (dvb->frontend) {
dvb_unregister_frontend(dvb->frontend);
dvb_frontend_detach(dvb->frontend);
}
dvb_dmxdev_release(&dvb->dmxdev);
dvb_dmx_release(&dvb->demux);
dvb_unregister_adapter(&dvb->adapter);
mutex_destroy(&dvb->mutex);
/* mutex_unlock(&tm6000_driver.open_close_mutex); */
}
static int dvb_init(struct tm6000_core *dev)
{
struct tm6000_dvb *dvb;
int rc;
if (!dev)
return 0;
if (!dev->caps.has_dvb)
return 0;
if (dev->udev->speed == USB_SPEED_FULL) {
printk(KERN_INFO "This USB2.0 device cannot be run on a USB1.1 port. (it lacks a hardware PID filter)\n");
return 0;
}
dvb = kzalloc(sizeof(struct tm6000_dvb), GFP_KERNEL);
if (!dvb)
return -ENOMEM;
dev->dvb = dvb;
rc = register_dvb(dev);
if (rc < 0) {
kfree(dvb);
dev->dvb = NULL;
return 0;
}
return 0;
}
static int dvb_fini(struct tm6000_core *dev)
{
if (!dev)
return 0;
if (!dev->caps.has_dvb)
return 0;
if (dev->dvb) {
unregister_dvb(dev);
kfree(dev->dvb);
dev->dvb = NULL;
}
return 0;
}
static struct tm6000_ops dvb_ops = {
.type = TM6000_DVB,
.name = "TM6000 dvb Extension",
.init = dvb_init,
.fini = dvb_fini,
};
static int __init tm6000_dvb_register(void)
{
return tm6000_register_extension(&dvb_ops);
}
static void __exit tm6000_dvb_unregister(void)
{
tm6000_unregister_extension(&dvb_ops);
}
module_init(tm6000_dvb_register);
module_exit(tm6000_dvb_unregister);
// SPDX-License-Identifier: GPL-2.0
// tm6000-i2c.c - driver for TM5600/TM6000/TM6010 USB video capture devices
//
// Copyright (c) 2006-2007 Mauro Carvalho Chehab <mchehab@kernel.org>
//
// Copyright (c) 2007 Michel Ludwig <michel.ludwig@gmail.com>
// - Fix SMBus Read Byte command
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/usb.h>
#include <linux/i2c.h>
#include "tm6000.h"
#include "tm6000-regs.h"
#include <media/v4l2-common.h>
#include <media/tuner.h>
#include "xc2028.h"
/* ----------------------------------------------------------- */
static unsigned int i2c_debug;
module_param(i2c_debug, int, 0644);
MODULE_PARM_DESC(i2c_debug, "enable debug messages [i2c]");
#define i2c_dprintk(lvl, fmt, args...) if (i2c_debug >= lvl) do { \
printk(KERN_DEBUG "%s at %s: " fmt, \
dev->name, __func__, ##args); } while (0)
static int tm6000_i2c_send_regs(struct tm6000_core *dev, unsigned char addr,
__u8 reg, char *buf, int len)
{
int rc;
unsigned int i2c_packet_limit = 16;
if (dev->dev_type == TM6010)
i2c_packet_limit = 80;
if (!buf)
return -1;
if (len < 1 || len > i2c_packet_limit) {
printk(KERN_ERR "Incorrect length of i2c packet = %d, limit set to %d\n",
len, i2c_packet_limit);
return -1;
}
/* capture mutex */
rc = tm6000_read_write_usb(dev, USB_DIR_OUT | USB_TYPE_VENDOR |
USB_RECIP_DEVICE, REQ_16_SET_GET_I2C_WR1_RDN,
addr | reg << 8, 0, buf, len);
if (rc < 0) {
/* release mutex */
return rc;
}
/* release mutex */
return rc;
}
/* Generic read - doesn't work fine with 16bit registers */
static int tm6000_i2c_recv_regs(struct tm6000_core *dev, unsigned char addr,
__u8 reg, char *buf, int len)
{
int rc;
u8 b[2];
unsigned int i2c_packet_limit = 16;
if (dev->dev_type == TM6010)
i2c_packet_limit = 64;
if (!buf)
return -1;
if (len < 1 || len > i2c_packet_limit) {
printk(KERN_ERR "Incorrect length of i2c packet = %d, limit set to %d\n",
len, i2c_packet_limit);
return -1;
}
/* capture mutex */
if ((dev->caps.has_zl10353) && (dev->demod_addr << 1 == addr) && (reg % 2 == 0)) {
/*
* Workaround an I2C bug when reading from zl10353
*/
reg -= 1;
len += 1;
rc = tm6000_read_write_usb(dev, USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
REQ_16_SET_GET_I2C_WR1_RDN, addr | reg << 8, 0, b, len);
*buf = b[1];
} else {
rc = tm6000_read_write_usb(dev, USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
REQ_16_SET_GET_I2C_WR1_RDN, addr | reg << 8, 0, buf, len);
}
/* release mutex */
return rc;
}
/*
* read from a 16bit register
* for example xc2028, xc3028 or xc3028L
*/
static int tm6000_i2c_recv_regs16(struct tm6000_core *dev, unsigned char addr,
__u16 reg, char *buf, int len)
{
int rc;
unsigned char ureg;
if (!buf || len != 2)
return -1;
/* capture mutex */
if (dev->dev_type == TM6010) {
ureg = reg & 0xFF;
rc = tm6000_read_write_usb(dev, USB_DIR_OUT | USB_TYPE_VENDOR |
USB_RECIP_DEVICE, REQ_16_SET_GET_I2C_WR1_RDN,
addr | (reg & 0xFF00), 0, &ureg, 1);
if (rc < 0) {
/* release mutex */
return rc;
}
rc = tm6000_read_write_usb(dev, USB_DIR_IN | USB_TYPE_VENDOR |
USB_RECIP_DEVICE, REQ_35_AFTEK_TUNER_READ,
reg, 0, buf, len);
} else {
rc = tm6000_read_write_usb(dev, USB_DIR_IN | USB_TYPE_VENDOR |
USB_RECIP_DEVICE, REQ_14_SET_GET_I2C_WR2_RDN,
addr, reg, buf, len);
}
/* release mutex */
return rc;
}
static int tm6000_i2c_xfer(struct i2c_adapter *i2c_adap,
struct i2c_msg msgs[], int num)
{
struct tm6000_core *dev = i2c_adap->algo_data;
int addr, rc, i, byte;
for (i = 0; i < num; i++) {
addr = (msgs[i].addr << 1) & 0xff;
i2c_dprintk(2, "%s %s addr=0x%x len=%d:",
(msgs[i].flags & I2C_M_RD) ? "read" : "write",
i == num - 1 ? "stop" : "nonstop", addr, msgs[i].len);
if (msgs[i].flags & I2C_M_RD) {
/* read request without preceding register selection */
/*
* The TM6000 only supports a read transaction
* immediately after a 1 or 2 byte write to select
* a register. We cannot fulfill this request.
*/
i2c_dprintk(2, " read without preceding write not supported");
rc = -EOPNOTSUPP;
goto err;
} else if (i + 1 < num && msgs[i].len <= 2 &&
(msgs[i + 1].flags & I2C_M_RD) &&
msgs[i].addr == msgs[i + 1].addr) {
/* 1 or 2 byte write followed by a read */
if (i2c_debug >= 2)
for (byte = 0; byte < msgs[i].len; byte++)
printk(KERN_CONT " %02x", msgs[i].buf[byte]);
i2c_dprintk(2, "; joined to read %s len=%d:",
i == num - 2 ? "stop" : "nonstop",
msgs[i + 1].len);
if (msgs[i].len == 2) {
rc = tm6000_i2c_recv_regs16(dev, addr,
msgs[i].buf[0] << 8 | msgs[i].buf[1],
msgs[i + 1].buf, msgs[i + 1].len);
} else {
rc = tm6000_i2c_recv_regs(dev, addr, msgs[i].buf[0],
msgs[i + 1].buf, msgs[i + 1].len);
}
i++;
if (addr == dev->tuner_addr << 1) {
tm6000_set_reg(dev, REQ_50_SET_START, 0, 0);
tm6000_set_reg(dev, REQ_51_SET_STOP, 0, 0);
}
if (i2c_debug >= 2)
for (byte = 0; byte < msgs[i].len; byte++)
printk(KERN_CONT " %02x", msgs[i].buf[byte]);
} else {
/* write bytes */
if (i2c_debug >= 2)
for (byte = 0; byte < msgs[i].len; byte++)
printk(KERN_CONT " %02x", msgs[i].buf[byte]);
rc = tm6000_i2c_send_regs(dev, addr, msgs[i].buf[0],
msgs[i].buf + 1, msgs[i].len - 1);
}
if (i2c_debug >= 2)
printk(KERN_CONT "\n");
if (rc < 0)
goto err;
}
return num;
err:
i2c_dprintk(2, " ERROR: %i\n", rc);
return rc;
}
static int tm6000_i2c_eeprom(struct tm6000_core *dev)
{
int i, rc;
unsigned char *p = dev->eedata;
unsigned char bytes[17];
dev->i2c_client.addr = 0xa0 >> 1;
dev->eedata_size = 0;
bytes[16] = '\0';
for (i = 0; i < sizeof(dev->eedata); ) {
*p = i;
rc = tm6000_i2c_recv_regs(dev, 0xa0, i, p, 1);
if (rc < 1) {
if (p == dev->eedata)
goto noeeprom;
else {
printk(KERN_WARNING
"%s: i2c eeprom read error (err=%d)\n",
dev->name, rc);
}
return -EINVAL;
}
dev->eedata_size++;
p++;
if (0 == (i % 16))
printk(KERN_INFO "%s: i2c eeprom %02x:", dev->name, i);
printk(KERN_CONT " %02x", dev->eedata[i]);
if ((dev->eedata[i] >= ' ') && (dev->eedata[i] <= 'z'))
bytes[i%16] = dev->eedata[i];
else
bytes[i%16] = '.';
i++;
if (0 == (i % 16)) {
bytes[16] = '\0';
printk(KERN_CONT " %s\n", bytes);
}
}
if (0 != (i%16)) {
bytes[i%16] = '\0';
for (i %= 16; i < 16; i++)
printk(KERN_CONT " ");
printk(KERN_CONT " %s\n", bytes);
}
return 0;
noeeprom:
printk(KERN_INFO "%s: Huh, no eeprom present (err=%d)?\n",
dev->name, rc);
return -EINVAL;
}
/* ----------------------------------------------------------- */
/*
* functionality()
*/
static u32 functionality(struct i2c_adapter *adap)
{
return I2C_FUNC_SMBUS_EMUL;
}
static const struct i2c_algorithm tm6000_algo = {
.master_xfer = tm6000_i2c_xfer,
.functionality = functionality,
};
/* ----------------------------------------------------------- */
/*
* tm6000_i2c_register()
* register i2c bus
*/
int tm6000_i2c_register(struct tm6000_core *dev)
{
int rc;
dev->i2c_adap.owner = THIS_MODULE;
dev->i2c_adap.algo = &tm6000_algo;
dev->i2c_adap.dev.parent = &dev->udev->dev;
strscpy(dev->i2c_adap.name, dev->name, sizeof(dev->i2c_adap.name));
dev->i2c_adap.algo_data = dev;
i2c_set_adapdata(&dev->i2c_adap, &dev->v4l2_dev);
rc = i2c_add_adapter(&dev->i2c_adap);
if (rc)
return rc;
dev->i2c_client.adapter = &dev->i2c_adap;
strscpy(dev->i2c_client.name, "tm6000 internal", I2C_NAME_SIZE);
tm6000_i2c_eeprom(dev);
return 0;
}
/*
* tm6000_i2c_unregister()
* unregister i2c_bus
*/
int tm6000_i2c_unregister(struct tm6000_core *dev)
{
i2c_del_adapter(&dev->i2c_adap);
return 0;
}
// SPDX-License-Identifier: GPL-2.0-only
/*
* tm6000-input.c - driver for TM5600/TM6000/TM6010 USB video capture devices
*
* Copyright (C) 2010 Stefan Ringel <stefan.ringel@arcor.de>
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/input.h>
#include <linux/usb.h>
#include <media/rc-core.h>
#include "tm6000.h"
#include "tm6000-regs.h"
static unsigned int ir_debug;
module_param(ir_debug, int, 0644);
MODULE_PARM_DESC(ir_debug, "debug message level");
static unsigned int enable_ir = 1;
module_param(enable_ir, int, 0644);
MODULE_PARM_DESC(enable_ir, "enable ir (default is enable)");
static unsigned int ir_clock_mhz = 12;
module_param(ir_clock_mhz, int, 0644);
MODULE_PARM_DESC(ir_clock_mhz, "ir clock, in MHz");
#define URB_SUBMIT_DELAY 100 /* ms - Delay to submit an URB request on retrial and init */
#define URB_INT_LED_DELAY 100 /* ms - Delay to turn led on again on int mode */
#undef dprintk
#define dprintk(level, fmt, arg...) do {\
if (ir_debug >= level) \
printk(KERN_DEBUG "%s/ir: " fmt, ir->name , ## arg); \
} while (0)
struct tm6000_ir_poll_result {
u16 rc_data;
};
struct tm6000_IR {
struct tm6000_core *dev;
struct rc_dev *rc;
char name[32];
char phys[32];
/* poll expernal decoder */
int polling;
struct delayed_work work;
u8 wait:1;
u8 pwled:2;
u8 submit_urb:1;
struct urb *int_urb;
/* IR device properties */
u64 rc_proto;
};
void tm6000_ir_wait(struct tm6000_core *dev, u8 state)
{
struct tm6000_IR *ir = dev->ir;
if (!dev->ir)
return;
dprintk(2, "%s: %i\n",__func__, ir->wait);
if (state)
ir->wait = 1;
else
ir->wait = 0;
}
static int tm6000_ir_config(struct tm6000_IR *ir)
{
struct tm6000_core *dev = ir->dev;
u32 pulse = 0, leader = 0;
dprintk(2, "%s\n",__func__);
/*
* The IR decoder supports RC-5 or NEC, with a configurable timing.
* The timing configuration there is not that accurate, as it uses
* approximate values. The NEC spec mentions a 562.5 unit period,
* and RC-5 uses a 888.8 period.
* Currently, driver assumes a clock provided by a 12 MHz XTAL, but
* a modprobe parameter can adjust it.
* Adjustments are required for other timings.
* It seems that the 900ms timing for NEC is used to detect a RC-5
* IR, in order to discard such decoding
*/
switch (ir->rc_proto) {
case RC_PROTO_BIT_NEC:
leader = 900; /* ms */
pulse = 700; /* ms - the actual value would be 562 */
break;
default:
case RC_PROTO_BIT_RC5:
leader = 900; /* ms - from the NEC decoding */
pulse = 1780; /* ms - The actual value would be 1776 */
break;
}
pulse = ir_clock_mhz * pulse;
leader = ir_clock_mhz * leader;
if (ir->rc_proto == RC_PROTO_BIT_NEC)
leader = leader | 0x8000;
dprintk(2, "%s: %s, %d MHz, leader = 0x%04x, pulse = 0x%06x \n",
__func__,
(ir->rc_proto == RC_PROTO_BIT_NEC) ? "NEC" : "RC-5",
ir_clock_mhz, leader, pulse);
/* Remote WAKEUP = enable, normal mode, from IR decoder output */
tm6000_set_reg(dev, TM6010_REQ07_RE5_REMOTE_WAKEUP, 0xfe);
/* Enable IR reception on non-busrt mode */
tm6000_set_reg(dev, TM6010_REQ07_RD8_IR, 0x2f);
/* IR_WKUP_SEL = Low byte in decoded IR data */
tm6000_set_reg(dev, TM6010_REQ07_RDA_IR_WAKEUP_SEL, 0xff);
/* IR_WKU_ADD code */
tm6000_set_reg(dev, TM6010_REQ07_RDB_IR_WAKEUP_ADD, 0xff);
tm6000_set_reg(dev, TM6010_REQ07_RDC_IR_LEADER1, leader >> 8);
tm6000_set_reg(dev, TM6010_REQ07_RDD_IR_LEADER0, leader);
tm6000_set_reg(dev, TM6010_REQ07_RDE_IR_PULSE_CNT1, pulse >> 8);
tm6000_set_reg(dev, TM6010_REQ07_RDF_IR_PULSE_CNT0, pulse);
if (!ir->polling)
tm6000_set_reg(dev, REQ_04_EN_DISABLE_MCU_INT, 2, 0);
else
tm6000_set_reg(dev, REQ_04_EN_DISABLE_MCU_INT, 2, 1);
msleep(10);
/* Shows that IR is working via the LED */
tm6000_flash_led(dev, 0);
msleep(100);
tm6000_flash_led(dev, 1);
ir->pwled = 1;
return 0;
}
static void tm6000_ir_keydown(struct tm6000_IR *ir,
const char *buf, unsigned int len)
{
u8 device, command;
u32 scancode;
enum rc_proto protocol;
if (len < 1)
return;
command = buf[0];
device = (len > 1 ? buf[1] : 0x0);
switch (ir->rc_proto) {
case RC_PROTO_BIT_RC5:
protocol = RC_PROTO_RC5;
scancode = RC_SCANCODE_RC5(device, command);
break;
case RC_PROTO_BIT_NEC:
protocol = RC_PROTO_NEC;
scancode = RC_SCANCODE_NEC(device, command);
break;
default:
protocol = RC_PROTO_OTHER;
scancode = RC_SCANCODE_OTHER(device << 8 | command);
break;
}
dprintk(1, "%s, protocol: 0x%04x, scancode: 0x%08x\n",
__func__, protocol, scancode);
rc_keydown(ir->rc, protocol, scancode, 0);
}
static void tm6000_ir_urb_received(struct urb *urb)
{
struct tm6000_core *dev = urb->context;
struct tm6000_IR *ir = dev->ir;
char *buf;
dprintk(2, "%s\n",__func__);
if (urb->status < 0 || urb->actual_length <= 0) {
printk(KERN_INFO "tm6000: IR URB failure: status: %i, length %i\n",
urb->status, urb->actual_length);
ir->submit_urb = 1;
schedule_delayed_work(&ir->work, msecs_to_jiffies(URB_SUBMIT_DELAY));
return;
}
buf = urb->transfer_buffer;
if (ir_debug)
print_hex_dump(KERN_DEBUG, "tm6000: IR data: ",
DUMP_PREFIX_OFFSET,16, 1,
buf, urb->actual_length, false);
tm6000_ir_keydown(ir, urb->transfer_buffer, urb->actual_length);
usb_submit_urb(urb, GFP_ATOMIC);
/*
* Flash the led. We can't do it here, as it is running on IRQ context.
* So, use the scheduler to do it, in a few ms.
*/
ir->pwled = 2;
schedule_delayed_work(&ir->work, msecs_to_jiffies(10));
}
static void tm6000_ir_handle_key(struct work_struct *work)
{
struct tm6000_IR *ir = container_of(work, struct tm6000_IR, work.work);
struct tm6000_core *dev = ir->dev;
int rc;
u8 buf[2];
if (ir->wait)
return;
dprintk(3, "%s\n",__func__);
rc = tm6000_read_write_usb(dev, USB_DIR_IN |
USB_TYPE_VENDOR | USB_RECIP_DEVICE,
REQ_02_GET_IR_CODE, 0, 0, buf, 2);
if (rc < 0)
return;
/* Check if something was read */
if ((buf[0] & 0xff) == 0xff) {
if (!ir->pwled) {
tm6000_flash_led(dev, 1);
ir->pwled = 1;
}
return;
}
tm6000_ir_keydown(ir, buf, rc);
tm6000_flash_led(dev, 0);
ir->pwled = 0;
/* Re-schedule polling */
schedule_delayed_work(&ir->work, msecs_to_jiffies(ir->polling));
}
static void tm6000_ir_int_work(struct work_struct *work)
{
struct tm6000_IR *ir = container_of(work, struct tm6000_IR, work.work);
struct tm6000_core *dev = ir->dev;
int rc;
dprintk(3, "%s, submit_urb = %d, pwled = %d\n",__func__, ir->submit_urb,
ir->pwled);
if (ir->submit_urb) {
dprintk(3, "Resubmit urb\n");
tm6000_set_reg(dev, REQ_04_EN_DISABLE_MCU_INT, 2, 0);
rc = usb_submit_urb(ir->int_urb, GFP_ATOMIC);
if (rc < 0) {
printk(KERN_ERR "tm6000: Can't submit an IR interrupt. Error %i\n",
rc);
/* Retry in 100 ms */
schedule_delayed_work(&ir->work, msecs_to_jiffies(URB_SUBMIT_DELAY));
return;
}
ir->submit_urb = 0;
}
/* Led is enabled only if USB submit doesn't fail */
if (ir->pwled == 2) {
tm6000_flash_led(dev, 0);
ir->pwled = 0;
schedule_delayed_work(&ir->work, msecs_to_jiffies(URB_INT_LED_DELAY));
} else if (!ir->pwled) {
tm6000_flash_led(dev, 1);
ir->pwled = 1;
}
}
static int tm6000_ir_start(struct rc_dev *rc)
{
struct tm6000_IR *ir = rc->priv;
dprintk(2, "%s\n",__func__);
schedule_delayed_work(&ir->work, 0);
return 0;
}
static void tm6000_ir_stop(struct rc_dev *rc)
{
struct tm6000_IR *ir = rc->priv;
dprintk(2, "%s\n",__func__);
cancel_delayed_work_sync(&ir->work);
}
static int tm6000_ir_change_protocol(struct rc_dev *rc, u64 *rc_proto)
{
struct tm6000_IR *ir = rc->priv;
if (!ir)
return 0;
dprintk(2, "%s\n",__func__);
ir->rc_proto = *rc_proto;
tm6000_ir_config(ir);
/* TODO */
return 0;
}
static int __tm6000_ir_int_start(struct rc_dev *rc)
{
struct tm6000_IR *ir = rc->priv;
struct tm6000_core *dev;
int pipe, size;
int err = -ENOMEM;
if (!ir)
return -ENODEV;
dev = ir->dev;
dprintk(2, "%s\n",__func__);
ir->int_urb = usb_alloc_urb(0, GFP_ATOMIC);
if (!ir->int_urb)
return -ENOMEM;
pipe = usb_rcvintpipe(dev->udev,
dev->int_in.endp->desc.bEndpointAddress
& USB_ENDPOINT_NUMBER_MASK);
size = usb_maxpacket(dev->udev, pipe);
dprintk(1, "IR max size: %d\n", size);
ir->int_urb->transfer_buffer = kzalloc(size, GFP_ATOMIC);
if (!ir->int_urb->transfer_buffer) {
usb_free_urb(ir->int_urb);
return err;
}
dprintk(1, "int interval: %d\n", dev->int_in.endp->desc.bInterval);
usb_fill_int_urb(ir->int_urb, dev->udev, pipe,
ir->int_urb->transfer_buffer, size,
tm6000_ir_urb_received, dev,
dev->int_in.endp->desc.bInterval);
ir->submit_urb = 1;
schedule_delayed_work(&ir->work, msecs_to_jiffies(URB_SUBMIT_DELAY));
return 0;
}
static void __tm6000_ir_int_stop(struct rc_dev *rc)
{
struct tm6000_IR *ir = rc->priv;
if (!ir || !ir->int_urb)
return;
dprintk(2, "%s\n",__func__);
usb_kill_urb(ir->int_urb);
kfree(ir->int_urb->transfer_buffer);
usb_free_urb(ir->int_urb);
ir->int_urb = NULL;
}
int tm6000_ir_int_start(struct tm6000_core *dev)
{
struct tm6000_IR *ir = dev->ir;
if (!ir)
return 0;
return __tm6000_ir_int_start(ir->rc);
}
void tm6000_ir_int_stop(struct tm6000_core *dev)
{
struct tm6000_IR *ir = dev->ir;
if (!ir || !ir->rc)
return;
__tm6000_ir_int_stop(ir->rc);
}
int tm6000_ir_init(struct tm6000_core *dev)
{
struct tm6000_IR *ir;
struct rc_dev *rc;
int err = -ENOMEM;
u64 rc_proto;
if (!enable_ir)
return -ENODEV;
if (!dev->caps.has_remote)
return 0;
if (!dev->ir_codes)
return 0;
ir = kzalloc(sizeof(*ir), GFP_ATOMIC);
rc = rc_allocate_device(RC_DRIVER_SCANCODE);
if (!ir || !rc)
goto out;
dprintk(2, "%s\n", __func__);
/* record handles to ourself */
ir->dev = dev;
dev->ir = ir;
ir->rc = rc;
/* input setup */
rc->allowed_protocols = RC_PROTO_BIT_RC5 | RC_PROTO_BIT_NEC;
/* Needed, in order to support NEC remotes with 24 or 32 bits */
rc->scancode_mask = 0xffff;
rc->priv = ir;
rc->change_protocol = tm6000_ir_change_protocol;
if (dev->int_in.endp) {
rc->open = __tm6000_ir_int_start;
rc->close = __tm6000_ir_int_stop;
INIT_DELAYED_WORK(&ir->work, tm6000_ir_int_work);
} else {
rc->open = tm6000_ir_start;
rc->close = tm6000_ir_stop;
ir->polling = 50;
INIT_DELAYED_WORK(&ir->work, tm6000_ir_handle_key);
}
snprintf(ir->name, sizeof(ir->name), "tm5600/60x0 IR (%s)",
dev->name);
usb_make_path(dev->udev, ir->phys, sizeof(ir->phys));
strlcat(ir->phys, "/input0", sizeof(ir->phys));
rc_proto = RC_PROTO_BIT_UNKNOWN;
tm6000_ir_change_protocol(rc, &rc_proto);
rc->device_name = ir->name;
rc->input_phys = ir->phys;
rc->input_id.bustype = BUS_USB;
rc->input_id.version = 1;
rc->input_id.vendor = le16_to_cpu(dev->udev->descriptor.idVendor);
rc->input_id.product = le16_to_cpu(dev->udev->descriptor.idProduct);
rc->map_name = dev->ir_codes;
rc->driver_name = "tm6000";
rc->dev.parent = &dev->udev->dev;
/* ir register */
err = rc_register_device(rc);
if (err)
goto out;
return 0;
out:
dev->ir = NULL;
rc_free_device(rc);
kfree(ir);
return err;
}
int tm6000_ir_fini(struct tm6000_core *dev)
{
struct tm6000_IR *ir = dev->ir;
/* skip detach on non attached board */
if (!ir)
return 0;
dprintk(2, "%s\n",__func__);
if (!ir->polling)
__tm6000_ir_int_stop(ir->rc);
tm6000_ir_stop(ir->rc);
/* Turn off the led */
tm6000_flash_led(dev, 0);
ir->pwled = 0;
rc_unregister_device(ir->rc);
kfree(ir);
dev->ir = NULL;
return 0;
}
/* SPDX-License-Identifier: GPL-2.0 */
/*
* tm6000-buf.c - driver for TM5600/TM6000/TM6010 USB video capture devices
*
* Copyright (c) 2006-2007 Mauro Carvalho Chehab <mchehab@kernel.org>
*/
#include <linux/videodev2.h>
#define TM6000_URB_MSG_LEN 180
struct usb_isoc_ctl {
/* max packet size of isoc transaction */
int max_pkt_size;
/* number of allocated urbs */
int num_bufs;
/* urb for isoc transfers */
struct urb **urb;
/* transfer buffers for isoc transfer */
char **transfer_buffer;
/* Last buffer command and region */
u8 cmd;
int pos, size, pktsize;
/* Last field: ODD or EVEN? */
int vfield, field;
/* Stores incomplete commands */
u32 tmp_buf;
int tmp_buf_len;
/* Stores already requested buffers */
struct tm6000_buffer *buf;
};
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册