2007-10-02 21:57:03 +07:00
|
|
|
/* tuner-xc2028
|
|
|
|
*
|
2008-04-21 16:58:48 +07:00
|
|
|
* Copyright (c) 2007-2008 Mauro Carvalho Chehab (mchehab@infradead.org)
|
2007-10-30 09:44:18 +07:00
|
|
|
*
|
2007-07-18 20:29:10 +07:00
|
|
|
* Copyright (c) 2007 Michel Ludwig (michel.ludwig@gmail.com)
|
|
|
|
* - frontend interface
|
2007-10-30 09:44:18 +07:00
|
|
|
*
|
2007-10-02 21:57:03 +07:00
|
|
|
* This code is placed under the terms of the GNU General Public License v2
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/i2c.h>
|
|
|
|
#include <asm/div64.h>
|
|
|
|
#include <linux/firmware.h>
|
2007-11-02 03:47:42 +07:00
|
|
|
#include <linux/videodev2.h>
|
2007-10-02 21:57:03 +07:00
|
|
|
#include <linux/delay.h>
|
2007-07-18 20:29:10 +07:00
|
|
|
#include <media/tuner.h>
|
2007-09-28 04:27:03 +07:00
|
|
|
#include <linux/mutex.h>
|
include cleanup: Update gfp.h and slab.h includes to prepare for breaking implicit slab.h inclusion from percpu.h
percpu.h is included by sched.h and module.h and thus ends up being
included when building most .c files. percpu.h includes slab.h which
in turn includes gfp.h making everything defined by the two files
universally available and complicating inclusion dependencies.
percpu.h -> slab.h dependency is about to be removed. Prepare for
this change by updating users of gfp and slab facilities include those
headers directly instead of assuming availability. As this conversion
needs to touch large number of source files, the following script is
used as the basis of conversion.
http://userweb.kernel.org/~tj/misc/slabh-sweep.py
The script does the followings.
* Scan files for gfp and slab usages and update includes such that
only the necessary includes are there. ie. if only gfp is used,
gfp.h, if slab is used, slab.h.
* When the script inserts a new include, it looks at the include
blocks and try to put the new include such that its order conforms
to its surrounding. It's put in the include block which contains
core kernel includes, in the same order that the rest are ordered -
alphabetical, Christmas tree, rev-Xmas-tree or at the end if there
doesn't seem to be any matching order.
* If the script can't find a place to put a new include (mostly
because the file doesn't have fitting include block), it prints out
an error message indicating which .h file needs to be added to the
file.
The conversion was done in the following steps.
1. The initial automatic conversion of all .c files updated slightly
over 4000 files, deleting around 700 includes and adding ~480 gfp.h
and ~3000 slab.h inclusions. The script emitted errors for ~400
files.
2. Each error was manually checked. Some didn't need the inclusion,
some needed manual addition while adding it to implementation .h or
embedding .c file was more appropriate for others. This step added
inclusions to around 150 files.
3. The script was run again and the output was compared to the edits
from #2 to make sure no file was left behind.
4. Several build tests were done and a couple of problems were fixed.
e.g. lib/decompress_*.c used malloc/free() wrappers around slab
APIs requiring slab.h to be added manually.
5. The script was run on all .h files but without automatically
editing them as sprinkling gfp.h and slab.h inclusions around .h
files could easily lead to inclusion dependency hell. Most gfp.h
inclusion directives were ignored as stuff from gfp.h was usually
wildly available and often used in preprocessor macros. Each
slab.h inclusion directive was examined and added manually as
necessary.
6. percpu.h was updated not to include slab.h.
7. Build test were done on the following configurations and failures
were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my
distributed build env didn't work with gcov compiles) and a few
more options had to be turned off depending on archs to make things
build (like ipr on powerpc/64 which failed due to missing writeq).
* x86 and x86_64 UP and SMP allmodconfig and a custom test config.
* powerpc and powerpc64 SMP allmodconfig
* sparc and sparc64 SMP allmodconfig
* ia64 SMP allmodconfig
* s390 SMP allmodconfig
* alpha SMP allmodconfig
* um on x86_64 SMP allmodconfig
8. percpu.h modifications were reverted so that it could be applied as
a separate patch and serve as bisection point.
Given the fact that I had only a couple of failures from tests on step
6, I'm fairly confident about the coverage of this conversion patch.
If there is a breakage, it's likely to be something in one of the arch
headers which should be easily discoverable easily on most builds of
the specific arch.
Signed-off-by: Tejun Heo <tj@kernel.org>
Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
2010-03-24 15:04:11 +07:00
|
|
|
#include <linux/slab.h>
|
2008-06-23 00:19:29 +07:00
|
|
|
#include <asm/unaligned.h>
|
2007-10-24 01:24:06 +07:00
|
|
|
#include "tuner-i2c.h"
|
2007-10-02 21:57:03 +07:00
|
|
|
#include "tuner-xc2028.h"
|
2007-10-24 19:22:08 +07:00
|
|
|
#include "tuner-xc2028-types.h"
|
2007-10-02 21:57:03 +07:00
|
|
|
|
2007-07-18 20:29:10 +07:00
|
|
|
#include <linux/dvb/frontend.h>
|
|
|
|
#include "dvb_frontend.h"
|
|
|
|
|
2007-11-17 02:28:21 +07:00
|
|
|
|
2007-11-15 19:44:30 +07:00
|
|
|
static int debug;
|
|
|
|
module_param(debug, int, 0644);
|
|
|
|
MODULE_PARM_DESC(debug, "enable verbose debug messages");
|
|
|
|
|
2008-12-05 20:31:16 +07:00
|
|
|
static int no_poweroff;
|
|
|
|
module_param(no_poweroff, int, 0644);
|
2009-04-29 02:22:47 +07:00
|
|
|
MODULE_PARM_DESC(no_poweroff, "0 (default) powers device off when not used.\n"
|
2008-12-05 20:31:16 +07:00
|
|
|
"1 keep device energized and with tuner ready all the times.\n"
|
|
|
|
" Faster, but consumes more power and keeps the device hotter\n");
|
|
|
|
|
2007-11-15 21:58:00 +07:00
|
|
|
static char audio_std[8];
|
|
|
|
module_param_string(audio_std, audio_std, sizeof(audio_std), 0);
|
|
|
|
MODULE_PARM_DESC(audio_std,
|
|
|
|
"Audio standard. XC3028 audio decoder explicitly "
|
|
|
|
"needs to know what audio\n"
|
|
|
|
"standard is needed for some video standards with audio A2 or NICAM.\n"
|
|
|
|
"The valid values are:\n"
|
|
|
|
"A2\n"
|
|
|
|
"A2/A\n"
|
|
|
|
"A2/B\n"
|
|
|
|
"NICAM\n"
|
|
|
|
"NICAM/A\n"
|
|
|
|
"NICAM/B\n");
|
|
|
|
|
2009-05-27 05:49:33 +07:00
|
|
|
static char firmware_name[30];
|
2008-04-23 00:46:24 +07:00
|
|
|
module_param_string(firmware_name, firmware_name, sizeof(firmware_name), 0);
|
|
|
|
MODULE_PARM_DESC(firmware_name, "Firmware file name. Allows overriding the "
|
|
|
|
"default firmware name\n");
|
|
|
|
|
2008-04-19 07:22:50 +07:00
|
|
|
static LIST_HEAD(hybrid_tuner_instance_list);
|
2007-11-19 14:45:38 +07:00
|
|
|
static DEFINE_MUTEX(xc2028_list_mutex);
|
|
|
|
|
2007-10-24 19:22:08 +07:00
|
|
|
/* struct for storing firmware table */
|
|
|
|
struct firmware_description {
|
|
|
|
unsigned int type;
|
|
|
|
v4l2_std_id id;
|
2007-11-26 05:26:36 +07:00
|
|
|
__u16 int_freq;
|
2007-10-24 19:22:08 +07:00
|
|
|
unsigned char *ptr;
|
|
|
|
unsigned int size;
|
|
|
|
};
|
2007-10-02 21:57:03 +07:00
|
|
|
|
2007-11-19 21:22:03 +07:00
|
|
|
struct firmware_properties {
|
|
|
|
unsigned int type;
|
|
|
|
v4l2_std_id id;
|
|
|
|
v4l2_std_id std_req;
|
2007-11-26 05:26:36 +07:00
|
|
|
__u16 int_freq;
|
2007-11-19 21:22:03 +07:00
|
|
|
unsigned int scode_table;
|
|
|
|
int scode_nr;
|
|
|
|
};
|
|
|
|
|
2007-10-02 21:57:03 +07:00
|
|
|
struct xc2028_data {
|
2008-04-19 07:22:50 +07:00
|
|
|
struct list_head hybrid_tuner_instance_list;
|
2007-10-24 01:24:06 +07:00
|
|
|
struct tuner_i2c_props i2c_props;
|
2007-10-24 19:22:08 +07:00
|
|
|
__u32 frequency;
|
|
|
|
|
|
|
|
struct firmware_description *firm;
|
|
|
|
int firm_size;
|
2007-11-19 16:06:08 +07:00
|
|
|
__u16 firm_version;
|
2007-10-24 19:22:08 +07:00
|
|
|
|
2007-11-19 21:35:45 +07:00
|
|
|
__u16 hwmodel;
|
|
|
|
__u16 hwvers;
|
|
|
|
|
2007-10-24 19:22:08 +07:00
|
|
|
struct xc2028_ctrl ctrl;
|
2007-10-24 01:24:06 +07:00
|
|
|
|
2007-11-19 21:22:03 +07:00
|
|
|
struct firmware_properties cur_fw;
|
2007-10-24 01:24:06 +07:00
|
|
|
|
|
|
|
struct mutex lock;
|
2007-10-02 21:57:03 +07:00
|
|
|
};
|
|
|
|
|
2007-11-19 14:14:23 +07:00
|
|
|
#define i2c_send(priv, buf, size) ({ \
|
|
|
|
int _rc; \
|
|
|
|
_rc = tuner_i2c_xfer_send(&priv->i2c_props, buf, size); \
|
|
|
|
if (size != _rc) \
|
|
|
|
tuner_info("i2c output error: rc = %d (should be %d)\n",\
|
|
|
|
_rc, (int)size); \
|
2010-01-19 11:38:45 +07:00
|
|
|
if (priv->ctrl.msleep) \
|
|
|
|
msleep(priv->ctrl.msleep); \
|
2007-11-19 14:14:23 +07:00
|
|
|
_rc; \
|
|
|
|
})
|
|
|
|
|
|
|
|
#define i2c_rcv(priv, buf, size) ({ \
|
|
|
|
int _rc; \
|
|
|
|
_rc = tuner_i2c_xfer_recv(&priv->i2c_props, buf, size); \
|
|
|
|
if (size != _rc) \
|
2007-11-15 19:44:30 +07:00
|
|
|
tuner_err("i2c input error: rc = %d (should be %d)\n", \
|
2007-11-19 14:14:23 +07:00
|
|
|
_rc, (int)size); \
|
|
|
|
_rc; \
|
|
|
|
})
|
2007-11-02 03:47:42 +07:00
|
|
|
|
2007-11-19 14:31:58 +07:00
|
|
|
#define i2c_send_recv(priv, obuf, osize, ibuf, isize) ({ \
|
|
|
|
int _rc; \
|
|
|
|
_rc = tuner_i2c_xfer_send_recv(&priv->i2c_props, obuf, osize, \
|
|
|
|
ibuf, isize); \
|
|
|
|
if (isize != _rc) \
|
|
|
|
tuner_err("i2c input error: rc = %d (should be %d)\n", \
|
|
|
|
_rc, (int)isize); \
|
2010-01-19 11:38:45 +07:00
|
|
|
if (priv->ctrl.msleep) \
|
|
|
|
msleep(priv->ctrl.msleep); \
|
2007-11-19 14:31:58 +07:00
|
|
|
_rc; \
|
|
|
|
})
|
|
|
|
|
2007-11-19 14:14:23 +07:00
|
|
|
#define send_seq(priv, data...) ({ \
|
2007-10-24 01:24:06 +07:00
|
|
|
static u8 _val[] = data; \
|
2007-11-19 14:14:23 +07:00
|
|
|
int _rc; \
|
2007-10-02 21:57:03 +07:00
|
|
|
if (sizeof(_val) != \
|
2007-11-19 14:14:23 +07:00
|
|
|
(_rc = tuner_i2c_xfer_send(&priv->i2c_props, \
|
2007-10-24 01:24:06 +07:00
|
|
|
_val, sizeof(_val)))) { \
|
2007-11-19 14:14:23 +07:00
|
|
|
tuner_err("Error on line %d: %d\n", __LINE__, _rc); \
|
2010-01-19 11:38:45 +07:00
|
|
|
} else if (priv->ctrl.msleep) \
|
2008-01-08 21:26:59 +07:00
|
|
|
msleep(priv->ctrl.msleep); \
|
2007-11-19 14:14:23 +07:00
|
|
|
_rc; \
|
|
|
|
})
|
2007-10-02 21:57:03 +07:00
|
|
|
|
2008-04-18 07:41:16 +07:00
|
|
|
static int xc2028_get_reg(struct xc2028_data *priv, u16 reg, u16 *val)
|
2007-10-02 21:57:03 +07:00
|
|
|
{
|
2007-11-05 18:41:50 +07:00
|
|
|
unsigned char buf[2];
|
2007-11-19 14:31:58 +07:00
|
|
|
unsigned char ibuf[2];
|
2007-10-24 01:24:06 +07:00
|
|
|
|
2008-04-09 09:20:00 +07:00
|
|
|
tuner_dbg("%s %04x called\n", __func__, reg);
|
2007-10-02 21:57:03 +07:00
|
|
|
|
2007-11-19 14:31:58 +07:00
|
|
|
buf[0] = reg >> 8;
|
2007-11-05 19:07:13 +07:00
|
|
|
buf[1] = (unsigned char) reg;
|
2007-10-02 21:57:03 +07:00
|
|
|
|
2007-11-19 14:31:58 +07:00
|
|
|
if (i2c_send_recv(priv, buf, 2, ibuf, 2) != 2)
|
|
|
|
return -EIO;
|
2007-10-02 21:57:03 +07:00
|
|
|
|
2007-11-19 14:31:58 +07:00
|
|
|
*val = (ibuf[1]) | (ibuf[0] << 8);
|
|
|
|
return 0;
|
2007-10-02 21:57:03 +07:00
|
|
|
}
|
|
|
|
|
2007-12-02 16:30:50 +07:00
|
|
|
#define dump_firm_type(t) dump_firm_type_and_int_freq(t, 0)
|
2008-04-23 00:41:45 +07:00
|
|
|
static void dump_firm_type_and_int_freq(unsigned int type, u16 int_freq)
|
2007-11-15 05:30:28 +07:00
|
|
|
{
|
|
|
|
if (type & BASE)
|
|
|
|
printk("BASE ");
|
2007-11-15 18:43:53 +07:00
|
|
|
if (type & INIT1)
|
|
|
|
printk("INIT1 ");
|
2007-11-15 05:30:28 +07:00
|
|
|
if (type & F8MHZ)
|
|
|
|
printk("F8MHZ ");
|
|
|
|
if (type & MTS)
|
|
|
|
printk("MTS ");
|
|
|
|
if (type & D2620)
|
|
|
|
printk("D2620 ");
|
|
|
|
if (type & D2633)
|
|
|
|
printk("D2633 ");
|
|
|
|
if (type & DTV6)
|
|
|
|
printk("DTV6 ");
|
|
|
|
if (type & QAM)
|
|
|
|
printk("QAM ");
|
|
|
|
if (type & DTV7)
|
|
|
|
printk("DTV7 ");
|
|
|
|
if (type & DTV78)
|
|
|
|
printk("DTV78 ");
|
|
|
|
if (type & DTV8)
|
|
|
|
printk("DTV8 ");
|
|
|
|
if (type & FM)
|
|
|
|
printk("FM ");
|
|
|
|
if (type & INPUT1)
|
|
|
|
printk("INPUT1 ");
|
|
|
|
if (type & LCD)
|
|
|
|
printk("LCD ");
|
|
|
|
if (type & NOGD)
|
|
|
|
printk("NOGD ");
|
|
|
|
if (type & MONO)
|
|
|
|
printk("MONO ");
|
|
|
|
if (type & ATSC)
|
|
|
|
printk("ATSC ");
|
|
|
|
if (type & IF)
|
|
|
|
printk("IF ");
|
|
|
|
if (type & LG60)
|
|
|
|
printk("LG60 ");
|
|
|
|
if (type & ATI638)
|
|
|
|
printk("ATI638 ");
|
|
|
|
if (type & OREN538)
|
|
|
|
printk("OREN538 ");
|
|
|
|
if (type & OREN36)
|
|
|
|
printk("OREN36 ");
|
|
|
|
if (type & TOYOTA388)
|
|
|
|
printk("TOYOTA388 ");
|
|
|
|
if (type & TOYOTA794)
|
|
|
|
printk("TOYOTA794 ");
|
|
|
|
if (type & DIBCOM52)
|
|
|
|
printk("DIBCOM52 ");
|
|
|
|
if (type & ZARLINK456)
|
|
|
|
printk("ZARLINK456 ");
|
|
|
|
if (type & CHINA)
|
|
|
|
printk("CHINA ");
|
|
|
|
if (type & F6MHZ)
|
|
|
|
printk("F6MHZ ");
|
|
|
|
if (type & INPUT2)
|
|
|
|
printk("INPUT2 ");
|
|
|
|
if (type & SCODE)
|
|
|
|
printk("SCODE ");
|
2007-12-02 16:30:50 +07:00
|
|
|
if (type & HAS_IF)
|
|
|
|
printk("HAS_IF_%d ", int_freq);
|
2007-11-15 05:30:28 +07:00
|
|
|
}
|
|
|
|
|
2007-11-17 02:28:21 +07:00
|
|
|
static v4l2_std_id parse_audio_std_option(void)
|
2007-11-15 21:58:00 +07:00
|
|
|
{
|
2007-11-19 14:16:47 +07:00
|
|
|
if (strcasecmp(audio_std, "A2") == 0)
|
2007-11-15 21:58:00 +07:00
|
|
|
return V4L2_STD_A2;
|
2007-11-19 14:16:47 +07:00
|
|
|
if (strcasecmp(audio_std, "A2/A") == 0)
|
2007-11-15 21:58:00 +07:00
|
|
|
return V4L2_STD_A2_A;
|
2007-11-19 14:16:47 +07:00
|
|
|
if (strcasecmp(audio_std, "A2/B") == 0)
|
2007-11-15 21:58:00 +07:00
|
|
|
return V4L2_STD_A2_B;
|
2007-11-19 14:16:47 +07:00
|
|
|
if (strcasecmp(audio_std, "NICAM") == 0)
|
2007-11-15 21:58:00 +07:00
|
|
|
return V4L2_STD_NICAM;
|
2007-11-19 14:16:47 +07:00
|
|
|
if (strcasecmp(audio_std, "NICAM/A") == 0)
|
2007-11-15 21:58:00 +07:00
|
|
|
return V4L2_STD_NICAM_A;
|
2007-11-19 14:16:47 +07:00
|
|
|
if (strcasecmp(audio_std, "NICAM/B") == 0)
|
2007-11-15 21:58:00 +07:00
|
|
|
return V4L2_STD_NICAM_B;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-11-02 03:47:42 +07:00
|
|
|
static void free_firmware(struct xc2028_data *priv)
|
2007-10-02 21:57:03 +07:00
|
|
|
{
|
2007-10-24 19:22:08 +07:00
|
|
|
int i;
|
2008-04-18 07:40:53 +07:00
|
|
|
tuner_dbg("%s called\n", __func__);
|
2007-10-24 19:22:08 +07:00
|
|
|
|
|
|
|
if (!priv->firm)
|
|
|
|
return;
|
|
|
|
|
2007-11-02 03:47:42 +07:00
|
|
|
for (i = 0; i < priv->firm_size; i++)
|
|
|
|
kfree(priv->firm[i].ptr);
|
|
|
|
|
2007-10-24 19:22:08 +07:00
|
|
|
kfree(priv->firm);
|
|
|
|
|
2007-11-02 03:47:42 +07:00
|
|
|
priv->firm = NULL;
|
2007-11-19 16:06:08 +07:00
|
|
|
priv->firm_size = 0;
|
2007-11-19 21:22:03 +07:00
|
|
|
|
|
|
|
memset(&priv->cur_fw, 0, sizeof(priv->cur_fw));
|
2007-10-24 19:22:08 +07:00
|
|
|
}
|
|
|
|
|
2007-11-02 03:47:42 +07:00
|
|
|
static int load_all_firmwares(struct dvb_frontend *fe)
|
2007-10-24 19:22:08 +07:00
|
|
|
{
|
|
|
|
struct xc2028_data *priv = fe->tuner_priv;
|
2007-11-02 03:47:42 +07:00
|
|
|
const struct firmware *fw = NULL;
|
2008-05-24 06:13:34 +07:00
|
|
|
const unsigned char *p, *endp;
|
2007-11-02 03:47:42 +07:00
|
|
|
int rc = 0;
|
|
|
|
int n, n_array;
|
2007-10-24 19:22:08 +07:00
|
|
|
char name[33];
|
2008-04-23 00:46:24 +07:00
|
|
|
char *fname;
|
2007-10-02 21:57:03 +07:00
|
|
|
|
2008-04-09 09:20:00 +07:00
|
|
|
tuner_dbg("%s called\n", __func__);
|
2007-10-24 01:24:06 +07:00
|
|
|
|
2008-04-23 00:46:24 +07:00
|
|
|
if (!firmware_name[0])
|
|
|
|
fname = priv->ctrl.fname;
|
|
|
|
else
|
|
|
|
fname = firmware_name;
|
|
|
|
|
|
|
|
tuner_dbg("Reading firmware %s\n", fname);
|
2009-04-26 15:43:59 +07:00
|
|
|
rc = request_firmware(&fw, fname, priv->i2c_props.adap->dev.parent);
|
2007-10-02 21:57:03 +07:00
|
|
|
if (rc < 0) {
|
2007-11-02 03:47:42 +07:00
|
|
|
if (rc == -ENOENT)
|
2007-11-15 19:44:30 +07:00
|
|
|
tuner_err("Error: firmware %s not found.\n",
|
2008-04-23 00:46:24 +07:00
|
|
|
fname);
|
2007-07-18 23:33:23 +07:00
|
|
|
else
|
2007-11-15 19:44:30 +07:00
|
|
|
tuner_err("Error %d while requesting firmware %s \n",
|
2008-04-23 00:46:24 +07:00
|
|
|
rc, fname);
|
2007-07-18 23:33:23 +07:00
|
|
|
|
2007-10-02 21:57:03 +07:00
|
|
|
return rc;
|
|
|
|
}
|
2007-11-02 03:47:42 +07:00
|
|
|
p = fw->data;
|
|
|
|
endp = p + fw->size;
|
2007-10-02 21:57:03 +07:00
|
|
|
|
2007-11-19 16:06:08 +07:00
|
|
|
if (fw->size < sizeof(name) - 1 + 2 + 2) {
|
|
|
|
tuner_err("Error: firmware file %s has invalid size!\n",
|
2008-04-23 00:46:24 +07:00
|
|
|
fname);
|
2007-11-19 16:06:08 +07:00
|
|
|
goto corrupt;
|
2007-10-02 21:57:03 +07:00
|
|
|
}
|
2007-10-24 19:22:08 +07:00
|
|
|
|
2007-11-02 03:47:42 +07:00
|
|
|
memcpy(name, p, sizeof(name) - 1);
|
|
|
|
name[sizeof(name) - 1] = 0;
|
|
|
|
p += sizeof(name) - 1;
|
2007-10-24 19:22:08 +07:00
|
|
|
|
2008-06-23 00:19:29 +07:00
|
|
|
priv->firm_version = get_unaligned_le16(p);
|
2007-10-24 19:22:08 +07:00
|
|
|
p += 2;
|
|
|
|
|
2008-06-23 00:19:29 +07:00
|
|
|
n_array = get_unaligned_le16(p);
|
2007-10-24 19:22:08 +07:00
|
|
|
p += 2;
|
|
|
|
|
2007-11-19 16:06:08 +07:00
|
|
|
tuner_info("Loading %d firmware images from %s, type: %s, ver %d.%d\n",
|
2008-04-23 00:46:24 +07:00
|
|
|
n_array, fname, name,
|
2007-11-19 16:06:08 +07:00
|
|
|
priv->firm_version >> 8, priv->firm_version & 0xff);
|
2007-10-24 19:22:08 +07:00
|
|
|
|
2007-11-02 03:47:42 +07:00
|
|
|
priv->firm = kzalloc(sizeof(*priv->firm) * n_array, GFP_KERNEL);
|
2007-11-19 16:06:08 +07:00
|
|
|
if (priv->firm == NULL) {
|
|
|
|
tuner_err("Not enough memory to load firmware file.\n");
|
2007-11-02 03:47:42 +07:00
|
|
|
rc = -ENOMEM;
|
2007-11-19 16:06:08 +07:00
|
|
|
goto err;
|
2007-10-02 21:57:03 +07:00
|
|
|
}
|
2007-10-24 19:22:08 +07:00
|
|
|
priv->firm_size = n_array;
|
2007-11-19 16:06:08 +07:00
|
|
|
|
2007-11-02 03:47:42 +07:00
|
|
|
n = -1;
|
|
|
|
while (p < endp) {
|
2007-10-24 19:22:08 +07:00
|
|
|
__u32 type, size;
|
|
|
|
v4l2_std_id id;
|
2007-11-26 05:26:36 +07:00
|
|
|
__u16 int_freq = 0;
|
2007-10-24 19:22:08 +07:00
|
|
|
|
|
|
|
n++;
|
|
|
|
if (n >= n_array) {
|
2007-11-19 16:06:08 +07:00
|
|
|
tuner_err("More firmware images in file than "
|
|
|
|
"were expected!\n");
|
2007-10-24 19:22:08 +07:00
|
|
|
goto corrupt;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Checks if there's enough bytes to read */
|
2008-06-23 00:19:29 +07:00
|
|
|
if (endp - p < sizeof(type) + sizeof(id) + sizeof(size))
|
|
|
|
goto header;
|
2007-10-24 19:22:08 +07:00
|
|
|
|
2008-06-23 00:19:29 +07:00
|
|
|
type = get_unaligned_le32(p);
|
2007-10-24 19:22:08 +07:00
|
|
|
p += sizeof(type);
|
|
|
|
|
2008-06-23 00:19:29 +07:00
|
|
|
id = get_unaligned_le64(p);
|
2007-10-24 19:22:08 +07:00
|
|
|
p += sizeof(id);
|
|
|
|
|
2007-11-26 05:26:36 +07:00
|
|
|
if (type & HAS_IF) {
|
2008-06-23 00:19:29 +07:00
|
|
|
int_freq = get_unaligned_le16(p);
|
2007-11-26 05:26:36 +07:00
|
|
|
p += sizeof(int_freq);
|
2008-06-23 00:19:29 +07:00
|
|
|
if (endp - p < sizeof(size))
|
|
|
|
goto header;
|
2007-11-26 05:26:36 +07:00
|
|
|
}
|
|
|
|
|
2008-06-23 00:19:29 +07:00
|
|
|
size = get_unaligned_le32(p);
|
2007-10-24 19:22:08 +07:00
|
|
|
p += sizeof(size);
|
|
|
|
|
2008-06-23 00:19:29 +07:00
|
|
|
if (!size || size > endp - p) {
|
2007-11-15 19:44:30 +07:00
|
|
|
tuner_err("Firmware type ");
|
2007-11-15 05:30:28 +07:00
|
|
|
dump_firm_type(type);
|
2007-11-17 02:28:21 +07:00
|
|
|
printk("(%x), id %llx is corrupted "
|
|
|
|
"(size=%d, expected %d)\n",
|
2007-11-19 14:38:53 +07:00
|
|
|
type, (unsigned long long)id,
|
2007-11-17 02:28:21 +07:00
|
|
|
(unsigned)(endp - p), size);
|
2007-10-24 19:22:08 +07:00
|
|
|
goto corrupt;
|
|
|
|
}
|
|
|
|
|
2007-11-02 03:47:42 +07:00
|
|
|
priv->firm[n].ptr = kzalloc(size, GFP_KERNEL);
|
2007-11-19 16:06:08 +07:00
|
|
|
if (priv->firm[n].ptr == NULL) {
|
|
|
|
tuner_err("Not enough memory to load firmware file.\n");
|
2007-11-02 03:47:42 +07:00
|
|
|
rc = -ENOMEM;
|
2007-10-24 19:22:08 +07:00
|
|
|
goto err;
|
|
|
|
}
|
2007-11-19 16:06:08 +07:00
|
|
|
tuner_dbg("Reading firmware type ");
|
|
|
|
if (debug) {
|
2007-12-02 16:30:50 +07:00
|
|
|
dump_firm_type_and_int_freq(type, int_freq);
|
2007-11-19 16:06:08 +07:00
|
|
|
printk("(%x), id %llx, size=%d.\n",
|
2007-12-02 16:30:50 +07:00
|
|
|
type, (unsigned long long)id, size);
|
2007-11-19 16:06:08 +07:00
|
|
|
}
|
2007-10-24 19:22:08 +07:00
|
|
|
|
|
|
|
memcpy(priv->firm[n].ptr, p, size);
|
|
|
|
priv->firm[n].type = type;
|
|
|
|
priv->firm[n].id = id;
|
|
|
|
priv->firm[n].size = size;
|
2007-11-26 05:26:36 +07:00
|
|
|
priv->firm[n].int_freq = int_freq;
|
2007-10-24 19:22:08 +07:00
|
|
|
|
|
|
|
p += size;
|
|
|
|
}
|
|
|
|
|
2007-11-02 03:47:42 +07:00
|
|
|
if (n + 1 != priv->firm_size) {
|
2007-11-15 19:44:30 +07:00
|
|
|
tuner_err("Firmware file is incomplete!\n");
|
2007-10-24 19:22:08 +07:00
|
|
|
goto corrupt;
|
|
|
|
}
|
|
|
|
|
|
|
|
goto done;
|
|
|
|
|
2008-06-23 00:19:29 +07:00
|
|
|
header:
|
|
|
|
tuner_err("Firmware header is incomplete!\n");
|
2007-10-24 19:22:08 +07:00
|
|
|
corrupt:
|
2007-11-02 03:47:42 +07:00
|
|
|
rc = -EINVAL;
|
2007-11-15 19:44:30 +07:00
|
|
|
tuner_err("Error: firmware file is corrupted!\n");
|
2007-10-24 19:22:08 +07:00
|
|
|
|
|
|
|
err:
|
2007-11-19 16:06:08 +07:00
|
|
|
tuner_info("Releasing partially loaded firmware file.\n");
|
2007-10-24 19:22:08 +07:00
|
|
|
free_firmware(priv);
|
|
|
|
|
|
|
|
done:
|
|
|
|
release_firmware(fw);
|
2007-11-19 16:06:08 +07:00
|
|
|
if (rc == 0)
|
|
|
|
tuner_dbg("Firmware files loaded.\n");
|
2007-10-24 19:22:08 +07:00
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2007-11-15 18:43:53 +07:00
|
|
|
static int seek_firmware(struct dvb_frontend *fe, unsigned int type,
|
|
|
|
v4l2_std_id *id)
|
2007-10-24 19:22:08 +07:00
|
|
|
{
|
|
|
|
struct xc2028_data *priv = fe->tuner_priv;
|
2007-11-19 20:04:06 +07:00
|
|
|
int i, best_i = -1, best_nr_matches = 0;
|
2008-04-21 16:58:48 +07:00
|
|
|
unsigned int type_mask = 0;
|
2007-10-24 19:22:08 +07:00
|
|
|
|
2008-04-09 09:20:00 +07:00
|
|
|
tuner_dbg("%s called, want type=", __func__);
|
2007-11-19 20:04:06 +07:00
|
|
|
if (debug) {
|
|
|
|
dump_firm_type(type);
|
|
|
|
printk("(%x), id %016llx.\n", type, (unsigned long long)*id);
|
|
|
|
}
|
2007-10-24 19:22:08 +07:00
|
|
|
|
|
|
|
if (!priv->firm) {
|
2007-11-15 19:44:30 +07:00
|
|
|
tuner_err("Error! firmware not loaded\n");
|
2007-10-24 19:22:08 +07:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2007-11-15 18:43:53 +07:00
|
|
|
if (((type & ~SCODE) == 0) && (*id == 0))
|
2007-11-02 03:47:42 +07:00
|
|
|
*id = V4L2_STD_PAL;
|
2007-10-24 19:22:08 +07:00
|
|
|
|
2007-11-19 21:22:03 +07:00
|
|
|
if (type & BASE)
|
2008-04-21 16:58:48 +07:00
|
|
|
type_mask = BASE_TYPES;
|
2007-12-02 19:30:55 +07:00
|
|
|
else if (type & SCODE) {
|
2007-11-19 21:22:03 +07:00
|
|
|
type &= SCODE_TYPES;
|
2008-04-21 16:58:48 +07:00
|
|
|
type_mask = SCODE_TYPES & ~HAS_IF;
|
2007-12-02 19:30:55 +07:00
|
|
|
} else if (type & DTV_TYPES)
|
2008-04-21 16:58:48 +07:00
|
|
|
type_mask = DTV_TYPES;
|
2007-11-20 09:18:36 +07:00
|
|
|
else if (type & STD_SPECIFIC_TYPES)
|
2008-04-21 16:58:48 +07:00
|
|
|
type_mask = STD_SPECIFIC_TYPES;
|
|
|
|
|
|
|
|
type &= type_mask;
|
|
|
|
|
2008-04-25 11:28:10 +07:00
|
|
|
if (!(type & SCODE))
|
2008-04-21 16:58:48 +07:00
|
|
|
type_mask = ~0;
|
2007-11-19 21:22:03 +07:00
|
|
|
|
2007-10-24 19:22:08 +07:00
|
|
|
/* Seek for exact match */
|
2007-11-02 03:47:42 +07:00
|
|
|
for (i = 0; i < priv->firm_size; i++) {
|
2008-04-21 16:58:48 +07:00
|
|
|
if ((type == (priv->firm[i].type & type_mask)) &&
|
2007-12-02 19:30:55 +07:00
|
|
|
(*id == priv->firm[i].id))
|
2007-10-24 19:22:08 +07:00
|
|
|
goto found;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Seek for generic video standard match */
|
2007-11-02 03:47:42 +07:00
|
|
|
for (i = 0; i < priv->firm_size; i++) {
|
2007-11-19 20:04:06 +07:00
|
|
|
v4l2_std_id match_mask;
|
|
|
|
int nr_matches;
|
|
|
|
|
2008-04-21 16:58:48 +07:00
|
|
|
if (type != (priv->firm[i].type & type_mask))
|
2007-11-19 20:04:06 +07:00
|
|
|
continue;
|
|
|
|
|
|
|
|
match_mask = *id & priv->firm[i].id;
|
|
|
|
if (!match_mask)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if ((*id & match_mask) == *id)
|
|
|
|
goto found; /* Supports all the requested standards */
|
|
|
|
|
|
|
|
nr_matches = hweight64(match_mask);
|
|
|
|
if (nr_matches > best_nr_matches) {
|
|
|
|
best_nr_matches = nr_matches;
|
|
|
|
best_i = i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (best_nr_matches > 0) {
|
|
|
|
tuner_dbg("Selecting best matching firmware (%d bits) for "
|
|
|
|
"type=", best_nr_matches);
|
|
|
|
dump_firm_type(type);
|
|
|
|
printk("(%x), id %016llx:\n", type, (unsigned long long)*id);
|
|
|
|
i = best_i;
|
|
|
|
goto found;
|
2007-10-24 19:22:08 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
/*FIXME: Would make sense to seek for type "hint" match ? */
|
|
|
|
|
2007-11-19 20:04:06 +07:00
|
|
|
i = -ENOENT;
|
2007-11-15 18:43:53 +07:00
|
|
|
goto ret;
|
2007-10-24 19:22:08 +07:00
|
|
|
|
|
|
|
found:
|
|
|
|
*id = priv->firm[i].id;
|
|
|
|
|
2007-11-15 18:43:53 +07:00
|
|
|
ret:
|
2007-11-19 20:04:06 +07:00
|
|
|
tuner_dbg("%s firmware for type=", (i < 0) ? "Can't find" : "Found");
|
2007-11-15 19:44:30 +07:00
|
|
|
if (debug) {
|
|
|
|
dump_firm_type(type);
|
2007-11-19 14:38:53 +07:00
|
|
|
printk("(%x), id %016llx.\n", type, (unsigned long long)*id);
|
2007-11-15 19:44:30 +07:00
|
|
|
}
|
2007-11-15 18:43:53 +07:00
|
|
|
return i;
|
|
|
|
}
|
|
|
|
|
2008-09-12 23:31:45 +07:00
|
|
|
static inline int do_tuner_callback(struct dvb_frontend *fe, int cmd, int arg)
|
|
|
|
{
|
|
|
|
struct xc2028_data *priv = fe->tuner_priv;
|
|
|
|
|
|
|
|
/* analog side (tuner-core) uses i2c_adap->algo_data.
|
|
|
|
* digital side is not guaranteed to have algo_data defined.
|
|
|
|
*
|
|
|
|
* digital side will always have fe->dvb defined.
|
|
|
|
* analog side (tuner-core) doesn't (yet) define fe->dvb.
|
|
|
|
*/
|
|
|
|
|
|
|
|
return (!fe->callback) ? -EINVAL :
|
|
|
|
fe->callback(((fe->dvb) && (fe->dvb->priv)) ?
|
|
|
|
fe->dvb->priv : priv->i2c_props.adap->algo_data,
|
|
|
|
DVB_FRONTEND_COMPONENT_TUNER, cmd, arg);
|
|
|
|
}
|
|
|
|
|
2007-11-15 18:43:53 +07:00
|
|
|
static int load_firmware(struct dvb_frontend *fe, unsigned int type,
|
|
|
|
v4l2_std_id *id)
|
|
|
|
{
|
|
|
|
struct xc2028_data *priv = fe->tuner_priv;
|
|
|
|
int pos, rc;
|
2007-11-19 19:29:59 +07:00
|
|
|
unsigned char *p, *endp, buf[priv->ctrl.max_len];
|
2007-11-15 18:43:53 +07:00
|
|
|
|
2008-04-09 09:20:00 +07:00
|
|
|
tuner_dbg("%s called\n", __func__);
|
2007-11-15 18:43:53 +07:00
|
|
|
|
|
|
|
pos = seek_firmware(fe, type, id);
|
|
|
|
if (pos < 0)
|
|
|
|
return pos;
|
|
|
|
|
2007-11-15 19:44:30 +07:00
|
|
|
tuner_info("Loading firmware for type=");
|
2007-11-19 20:04:06 +07:00
|
|
|
dump_firm_type(priv->firm[pos].type);
|
|
|
|
printk("(%x), id %016llx.\n", priv->firm[pos].type,
|
|
|
|
(unsigned long long)*id);
|
2007-11-15 19:44:30 +07:00
|
|
|
|
2007-11-15 18:43:53 +07:00
|
|
|
p = priv->firm[pos].ptr;
|
|
|
|
endp = p + priv->firm[pos].size;
|
2007-10-02 21:57:03 +07:00
|
|
|
|
2007-11-02 03:47:42 +07:00
|
|
|
while (p < endp) {
|
2007-10-24 19:22:08 +07:00
|
|
|
__u16 size;
|
|
|
|
|
|
|
|
/* Checks if there's enough bytes to read */
|
2007-11-02 03:47:42 +07:00
|
|
|
if (p + sizeof(size) > endp) {
|
2007-11-15 19:44:30 +07:00
|
|
|
tuner_err("Firmware chunk size is wrong\n");
|
2007-10-24 19:22:08 +07:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2007-11-02 03:47:42 +07:00
|
|
|
size = le16_to_cpu(*(__u16 *) p);
|
2007-10-24 19:22:08 +07:00
|
|
|
p += sizeof(size);
|
|
|
|
|
|
|
|
if (size == 0xffff)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (!size) {
|
2007-10-02 21:57:03 +07:00
|
|
|
/* Special callback command received */
|
2008-09-12 23:31:45 +07:00
|
|
|
rc = do_tuner_callback(fe, XC2028_TUNER_RESET, 0);
|
2007-11-02 03:47:42 +07:00
|
|
|
if (rc < 0) {
|
2007-11-15 19:44:30 +07:00
|
|
|
tuner_err("Error at RESET code %d\n",
|
2007-11-02 03:47:42 +07:00
|
|
|
(*p) & 0x7f);
|
2007-10-24 19:22:08 +07:00
|
|
|
return -EINVAL;
|
2007-10-02 21:57:03 +07:00
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
2007-11-16 17:49:49 +07:00
|
|
|
if (size >= 0xff00) {
|
|
|
|
switch (size) {
|
|
|
|
case 0xff00:
|
2008-09-12 23:31:45 +07:00
|
|
|
rc = do_tuner_callback(fe, XC2028_RESET_CLK, 0);
|
2007-11-16 17:49:49 +07:00
|
|
|
if (rc < 0) {
|
|
|
|
tuner_err("Error at RESET code %d\n",
|
|
|
|
(*p) & 0x7f);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
2007-11-19 14:53:50 +07:00
|
|
|
break;
|
2007-11-16 17:49:49 +07:00
|
|
|
default:
|
|
|
|
tuner_info("Invalid RESET code %d\n",
|
|
|
|
size & 0x7f);
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
}
|
2007-11-16 19:43:19 +07:00
|
|
|
continue;
|
2007-11-16 17:49:49 +07:00
|
|
|
}
|
2007-10-24 19:22:08 +07:00
|
|
|
|
|
|
|
/* Checks for a sleep command */
|
|
|
|
if (size & 0x8000) {
|
2007-11-02 03:47:42 +07:00
|
|
|
msleep(size & 0x7fff);
|
2007-10-24 19:22:08 +07:00
|
|
|
continue;
|
2007-10-02 21:57:03 +07:00
|
|
|
}
|
|
|
|
|
2007-10-24 19:22:08 +07:00
|
|
|
if ((size + p > endp)) {
|
2007-11-15 19:44:30 +07:00
|
|
|
tuner_err("missing bytes: need %d, have %d\n",
|
2007-11-02 03:47:42 +07:00
|
|
|
size, (int)(endp - p));
|
2007-10-24 19:22:08 +07:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
2007-10-02 21:57:03 +07:00
|
|
|
|
2007-10-24 19:22:08 +07:00
|
|
|
buf[0] = *p;
|
2007-10-02 21:57:03 +07:00
|
|
|
p++;
|
2007-10-24 19:22:08 +07:00
|
|
|
size--;
|
2007-10-02 21:57:03 +07:00
|
|
|
|
2007-10-24 19:22:08 +07:00
|
|
|
/* Sends message chunks */
|
2007-11-02 03:47:42 +07:00
|
|
|
while (size > 0) {
|
2007-11-19 19:29:59 +07:00
|
|
|
int len = (size < priv->ctrl.max_len - 1) ?
|
|
|
|
size : priv->ctrl.max_len - 1;
|
2007-10-02 21:57:03 +07:00
|
|
|
|
2007-11-02 03:47:42 +07:00
|
|
|
memcpy(buf + 1, p, len);
|
2007-10-02 21:57:03 +07:00
|
|
|
|
2007-11-19 14:14:23 +07:00
|
|
|
rc = i2c_send(priv, buf, len + 1);
|
2007-11-02 03:47:42 +07:00
|
|
|
if (rc < 0) {
|
2007-11-15 19:44:30 +07:00
|
|
|
tuner_err("%d returned from send\n", rc);
|
2007-10-24 19:22:08 +07:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
p += len;
|
|
|
|
size -= len;
|
|
|
|
}
|
2011-08-04 14:13:59 +07:00
|
|
|
|
|
|
|
/* silently fail if the frontend doesn't support I2C flush */
|
|
|
|
rc = do_tuner_callback(fe, XC2028_I2C_FLUSH, 0);
|
|
|
|
if ((rc < 0) && (rc != -EINVAL)) {
|
|
|
|
tuner_err("error executing flush: %d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
2007-10-24 19:22:08 +07:00
|
|
|
}
|
2007-11-15 05:30:28 +07:00
|
|
|
return 0;
|
2007-10-02 21:57:03 +07:00
|
|
|
}
|
|
|
|
|
2007-11-15 18:43:53 +07:00
|
|
|
static int load_scode(struct dvb_frontend *fe, unsigned int type,
|
2007-11-26 05:26:36 +07:00
|
|
|
v4l2_std_id *id, __u16 int_freq, int scode)
|
2007-11-15 18:43:53 +07:00
|
|
|
{
|
|
|
|
struct xc2028_data *priv = fe->tuner_priv;
|
|
|
|
int pos, rc;
|
|
|
|
unsigned char *p;
|
|
|
|
|
2008-04-09 09:20:00 +07:00
|
|
|
tuner_dbg("%s called\n", __func__);
|
2007-11-15 18:43:53 +07:00
|
|
|
|
2007-11-26 05:26:36 +07:00
|
|
|
if (!int_freq) {
|
|
|
|
pos = seek_firmware(fe, type, id);
|
|
|
|
if (pos < 0)
|
|
|
|
return pos;
|
|
|
|
} else {
|
|
|
|
for (pos = 0; pos < priv->firm_size; pos++) {
|
|
|
|
if ((priv->firm[pos].int_freq == int_freq) &&
|
2007-12-02 16:54:17 +07:00
|
|
|
(priv->firm[pos].type & HAS_IF))
|
2007-11-26 05:26:36 +07:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (pos == priv->firm_size)
|
|
|
|
return -ENOENT;
|
|
|
|
}
|
2007-11-15 18:43:53 +07:00
|
|
|
|
|
|
|
p = priv->firm[pos].ptr;
|
|
|
|
|
2007-12-02 16:54:17 +07:00
|
|
|
if (priv->firm[pos].type & HAS_IF) {
|
2007-11-26 05:26:36 +07:00
|
|
|
if (priv->firm[pos].size != 12 * 16 || scode >= 16)
|
|
|
|
return -EINVAL;
|
|
|
|
p += 12 * scode;
|
|
|
|
} else {
|
|
|
|
/* 16 SCODE entries per file; each SCODE entry is 12 bytes and
|
|
|
|
* has a 2-byte size header in the firmware format. */
|
|
|
|
if (priv->firm[pos].size != 14 * 16 || scode >= 16 ||
|
|
|
|
le16_to_cpu(*(__u16 *)(p + 14 * scode)) != 12)
|
|
|
|
return -EINVAL;
|
|
|
|
p += 14 * scode + 2;
|
|
|
|
}
|
2007-11-15 18:43:53 +07:00
|
|
|
|
2007-11-19 20:12:45 +07:00
|
|
|
tuner_info("Loading SCODE for type=");
|
2007-12-02 16:30:50 +07:00
|
|
|
dump_firm_type_and_int_freq(priv->firm[pos].type,
|
|
|
|
priv->firm[pos].int_freq);
|
2007-11-19 20:12:45 +07:00
|
|
|
printk("(%x), id %016llx.\n", priv->firm[pos].type,
|
|
|
|
(unsigned long long)*id);
|
|
|
|
|
2007-11-19 16:06:08 +07:00
|
|
|
if (priv->firm_version < 0x0202)
|
2007-11-19 14:14:23 +07:00
|
|
|
rc = send_seq(priv, {0x20, 0x00, 0x00, 0x00});
|
|
|
|
else
|
|
|
|
rc = send_seq(priv, {0xa0, 0x00, 0x00, 0x00});
|
|
|
|
if (rc < 0)
|
|
|
|
return -EIO;
|
2007-11-15 18:43:53 +07:00
|
|
|
|
2007-11-26 05:26:36 +07:00
|
|
|
rc = i2c_send(priv, p, 12);
|
2007-11-19 14:14:23 +07:00
|
|
|
if (rc < 0)
|
|
|
|
return -EIO;
|
2007-11-15 18:43:53 +07:00
|
|
|
|
2007-11-19 14:14:23 +07:00
|
|
|
rc = send_seq(priv, {0x00, 0x8c});
|
|
|
|
if (rc < 0)
|
|
|
|
return -EIO;
|
2007-11-15 18:43:53 +07:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-11-24 20:13:42 +07:00
|
|
|
static int check_firmware(struct dvb_frontend *fe, unsigned int type,
|
2007-11-26 05:26:36 +07:00
|
|
|
v4l2_std_id std, __u16 int_freq)
|
2007-10-02 21:57:03 +07:00
|
|
|
{
|
2007-11-24 20:13:42 +07:00
|
|
|
struct xc2028_data *priv = fe->tuner_priv;
|
2007-11-19 21:22:03 +07:00
|
|
|
struct firmware_properties new_fw;
|
2011-01-23 22:27:05 +07:00
|
|
|
int rc = 0, retry_count = 0;
|
2007-11-24 20:13:42 +07:00
|
|
|
u16 version, hwmodel;
|
|
|
|
v4l2_std_id std0;
|
2007-10-02 21:57:03 +07:00
|
|
|
|
2008-04-09 09:20:00 +07:00
|
|
|
tuner_dbg("%s called\n", __func__);
|
2007-10-02 21:57:03 +07:00
|
|
|
|
2007-10-24 19:22:08 +07:00
|
|
|
if (!priv->firm) {
|
2007-11-16 17:46:14 +07:00
|
|
|
if (!priv->ctrl.fname) {
|
|
|
|
tuner_info("xc2028/3028 firmware name not set!\n");
|
2007-10-24 19:22:08 +07:00
|
|
|
return -EINVAL;
|
2007-11-16 17:46:14 +07:00
|
|
|
}
|
2007-10-24 19:22:08 +07:00
|
|
|
|
2007-11-02 03:47:42 +07:00
|
|
|
rc = load_all_firmwares(fe);
|
|
|
|
if (rc < 0)
|
2007-10-24 19:22:08 +07:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2008-01-06 02:47:16 +07:00
|
|
|
if (priv->ctrl.mts && !(type & FM))
|
2007-11-19 21:22:03 +07:00
|
|
|
type |= MTS;
|
2007-10-02 21:57:03 +07:00
|
|
|
|
2007-11-19 21:35:45 +07:00
|
|
|
retry:
|
2007-11-19 21:22:03 +07:00
|
|
|
new_fw.type = type;
|
|
|
|
new_fw.id = std;
|
|
|
|
new_fw.std_req = std;
|
|
|
|
new_fw.scode_table = SCODE | priv->ctrl.scode_table;
|
|
|
|
new_fw.scode_nr = 0;
|
2007-11-26 05:26:36 +07:00
|
|
|
new_fw.int_freq = int_freq;
|
2007-11-19 21:22:03 +07:00
|
|
|
|
|
|
|
tuner_dbg("checking firmware, user requested type=");
|
|
|
|
if (debug) {
|
|
|
|
dump_firm_type(new_fw.type);
|
2007-12-02 16:30:50 +07:00
|
|
|
printk("(%x), id %016llx, ", new_fw.type,
|
2007-11-19 21:22:03 +07:00
|
|
|
(unsigned long long)new_fw.std_req);
|
2007-12-02 16:30:50 +07:00
|
|
|
if (!int_freq) {
|
|
|
|
printk("scode_tbl ");
|
|
|
|
dump_firm_type(priv->ctrl.scode_table);
|
|
|
|
printk("(%x), ", priv->ctrl.scode_table);
|
|
|
|
} else
|
|
|
|
printk("int_freq %d, ", new_fw.int_freq);
|
|
|
|
printk("scode_nr %d\n", new_fw.scode_nr);
|
2007-11-19 21:22:03 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
/* No need to reload base firmware if it matches */
|
|
|
|
if (((BASE | new_fw.type) & BASE_TYPES) ==
|
|
|
|
(priv->cur_fw.type & BASE_TYPES)) {
|
|
|
|
tuner_dbg("BASE firmware not changed.\n");
|
|
|
|
goto skip_base;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Updating BASE - forget about all currently loaded firmware */
|
|
|
|
memset(&priv->cur_fw, 0, sizeof(priv->cur_fw));
|
|
|
|
|
|
|
|
/* Reset is needed before loading firmware */
|
2008-09-12 23:31:45 +07:00
|
|
|
rc = do_tuner_callback(fe, XC2028_TUNER_RESET, 0);
|
2007-11-19 21:22:03 +07:00
|
|
|
if (rc < 0)
|
|
|
|
goto fail;
|
|
|
|
|
2007-11-20 09:11:37 +07:00
|
|
|
/* BASE firmwares are all std0 */
|
|
|
|
std0 = 0;
|
|
|
|
rc = load_firmware(fe, BASE | new_fw.type, &std0);
|
2007-11-19 21:22:03 +07:00
|
|
|
if (rc < 0) {
|
|
|
|
tuner_err("Error %d while loading base firmware\n",
|
|
|
|
rc);
|
|
|
|
goto fail;
|
|
|
|
}
|
2007-11-16 17:49:49 +07:00
|
|
|
|
2007-10-24 19:22:08 +07:00
|
|
|
/* Load INIT1, if needed */
|
2007-11-15 19:44:30 +07:00
|
|
|
tuner_dbg("Load init1 firmware, if exists\n");
|
2007-10-24 19:22:08 +07:00
|
|
|
|
2007-11-20 09:11:37 +07:00
|
|
|
rc = load_firmware(fe, BASE | INIT1 | new_fw.type, &std0);
|
2007-11-20 09:43:13 +07:00
|
|
|
if (rc == -ENOENT)
|
|
|
|
rc = load_firmware(fe, (BASE | INIT1 | new_fw.type) & ~F8MHZ,
|
|
|
|
&std0);
|
2007-11-19 21:22:03 +07:00
|
|
|
if (rc < 0 && rc != -ENOENT) {
|
|
|
|
tuner_err("Error %d while loading init1 firmware\n",
|
|
|
|
rc);
|
|
|
|
goto fail;
|
|
|
|
}
|
2007-10-24 19:22:08 +07:00
|
|
|
|
2007-11-19 21:22:03 +07:00
|
|
|
skip_base:
|
|
|
|
/*
|
|
|
|
* No need to reload standard specific firmware if base firmware
|
|
|
|
* was not reloaded and requested video standards have not changed.
|
2007-10-24 19:22:08 +07:00
|
|
|
*/
|
2007-11-19 21:22:03 +07:00
|
|
|
if (priv->cur_fw.type == (BASE | new_fw.type) &&
|
|
|
|
priv->cur_fw.std_req == std) {
|
2007-11-15 19:44:30 +07:00
|
|
|
tuner_dbg("Std-specific firmware already loaded.\n");
|
2007-11-19 21:22:03 +07:00
|
|
|
goto skip_std_specific;
|
2007-07-18 23:33:23 +07:00
|
|
|
}
|
2007-10-02 21:57:03 +07:00
|
|
|
|
2007-11-19 21:22:03 +07:00
|
|
|
/* Reloading std-specific firmware forces a SCODE update */
|
|
|
|
priv->cur_fw.scode_table = 0;
|
|
|
|
|
|
|
|
rc = load_firmware(fe, new_fw.type, &new_fw.id);
|
2007-11-22 21:47:18 +07:00
|
|
|
if (rc == -ENOENT)
|
|
|
|
rc = load_firmware(fe, new_fw.type & ~F8MHZ, &new_fw.id);
|
|
|
|
|
2007-11-02 03:47:42 +07:00
|
|
|
if (rc < 0)
|
2007-11-19 21:22:03 +07:00
|
|
|
goto fail;
|
|
|
|
|
|
|
|
skip_std_specific:
|
|
|
|
if (priv->cur_fw.scode_table == new_fw.scode_table &&
|
|
|
|
priv->cur_fw.scode_nr == new_fw.scode_nr) {
|
|
|
|
tuner_dbg("SCODE firmware already loaded.\n");
|
|
|
|
goto check_device;
|
|
|
|
}
|
2007-10-02 21:57:03 +07:00
|
|
|
|
2008-02-14 11:52:48 +07:00
|
|
|
if (new_fw.type & FM)
|
|
|
|
goto check_device;
|
|
|
|
|
2007-11-15 18:43:53 +07:00
|
|
|
/* Load SCODE firmware, if exists */
|
2007-11-19 21:22:03 +07:00
|
|
|
tuner_dbg("Trying to load scode %d\n", new_fw.scode_nr);
|
2007-11-15 18:43:53 +07:00
|
|
|
|
2007-11-26 05:26:36 +07:00
|
|
|
rc = load_scode(fe, new_fw.type | new_fw.scode_table, &new_fw.id,
|
|
|
|
new_fw.int_freq, new_fw.scode_nr);
|
2007-11-15 05:30:28 +07:00
|
|
|
|
2007-11-19 21:22:03 +07:00
|
|
|
check_device:
|
2007-11-19 21:35:45 +07:00
|
|
|
if (xc2028_get_reg(priv, 0x0004, &version) < 0 ||
|
|
|
|
xc2028_get_reg(priv, 0x0008, &hwmodel) < 0) {
|
|
|
|
tuner_err("Unable to read tuner registers.\n");
|
|
|
|
goto fail;
|
|
|
|
}
|
2007-11-05 19:07:13 +07:00
|
|
|
|
2008-04-21 17:02:09 +07:00
|
|
|
tuner_dbg("Device is Xceive %d version %d.%d, "
|
|
|
|
"firmware version %d.%d\n",
|
|
|
|
hwmodel, (version & 0xf000) >> 12, (version & 0xf00) >> 8,
|
|
|
|
(version & 0xf0) >> 4, version & 0xf);
|
2007-10-02 21:57:03 +07:00
|
|
|
|
2008-12-02 18:30:16 +07:00
|
|
|
|
|
|
|
if (priv->ctrl.read_not_reliable)
|
|
|
|
goto read_not_reliable;
|
|
|
|
|
2007-11-19 21:35:45 +07:00
|
|
|
/* Check firmware version against what we downloaded. */
|
|
|
|
if (priv->firm_version != ((version & 0xf0) << 4 | (version & 0x0f))) {
|
2009-09-14 20:23:20 +07:00
|
|
|
if (!priv->ctrl.read_not_reliable) {
|
|
|
|
tuner_err("Incorrect readback of firmware version.\n");
|
|
|
|
goto fail;
|
|
|
|
} else {
|
|
|
|
tuner_err("Returned an incorrect version. However, "
|
|
|
|
"read is not reliable enough. Ignoring it.\n");
|
|
|
|
hwmodel = 3028;
|
|
|
|
}
|
2007-11-19 21:35:45 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Check that the tuner hardware model remains consistent over time. */
|
|
|
|
if (priv->hwmodel == 0 && (hwmodel == 2028 || hwmodel == 3028)) {
|
|
|
|
priv->hwmodel = hwmodel;
|
|
|
|
priv->hwvers = version & 0xff00;
|
|
|
|
} else if (priv->hwmodel == 0 || priv->hwmodel != hwmodel ||
|
|
|
|
priv->hwvers != (version & 0xff00)) {
|
|
|
|
tuner_err("Read invalid device hardware information - tuner "
|
|
|
|
"hung?\n");
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
2008-12-02 18:30:16 +07:00
|
|
|
read_not_reliable:
|
2007-11-19 21:22:03 +07:00
|
|
|
memcpy(&priv->cur_fw, &new_fw, sizeof(priv->cur_fw));
|
|
|
|
|
|
|
|
/*
|
|
|
|
* By setting BASE in cur_fw.type only after successfully loading all
|
|
|
|
* firmwares, we can:
|
|
|
|
* 1. Identify that BASE firmware with type=0 has been loaded;
|
|
|
|
* 2. Tell whether BASE firmware was just changed the next time through.
|
|
|
|
*/
|
|
|
|
priv->cur_fw.type |= BASE;
|
2007-10-02 21:57:03 +07:00
|
|
|
|
|
|
|
return 0;
|
2007-11-19 21:22:03 +07:00
|
|
|
|
|
|
|
fail:
|
|
|
|
memset(&priv->cur_fw, 0, sizeof(priv->cur_fw));
|
2011-01-23 22:27:05 +07:00
|
|
|
if (retry_count < 8) {
|
2007-11-19 21:35:45 +07:00
|
|
|
msleep(50);
|
2011-01-23 22:27:05 +07:00
|
|
|
retry_count++;
|
2007-11-19 21:35:45 +07:00
|
|
|
tuner_dbg("Retrying firmware load\n");
|
|
|
|
goto retry;
|
|
|
|
}
|
|
|
|
|
2007-11-19 21:22:03 +07:00
|
|
|
if (rc == -ENOENT)
|
|
|
|
rc = -EINVAL;
|
|
|
|
return rc;
|
2007-10-02 21:57:03 +07:00
|
|
|
}
|
|
|
|
|
2007-10-24 01:24:06 +07:00
|
|
|
static int xc2028_signal(struct dvb_frontend *fe, u16 *strength)
|
2007-10-02 21:57:03 +07:00
|
|
|
{
|
2007-10-24 01:24:06 +07:00
|
|
|
struct xc2028_data *priv = fe->tuner_priv;
|
2007-11-19 14:31:58 +07:00
|
|
|
u16 frq_lock, signal = 0;
|
|
|
|
int rc;
|
2007-09-28 04:27:03 +07:00
|
|
|
|
2008-04-09 09:20:00 +07:00
|
|
|
tuner_dbg("%s called\n", __func__);
|
2007-10-02 21:57:03 +07:00
|
|
|
|
2007-10-24 01:24:06 +07:00
|
|
|
mutex_lock(&priv->lock);
|
2007-10-02 21:57:03 +07:00
|
|
|
|
2007-11-05 19:07:13 +07:00
|
|
|
/* Sync Lock Indicator */
|
2007-11-19 14:31:58 +07:00
|
|
|
rc = xc2028_get_reg(priv, 0x0002, &frq_lock);
|
2008-04-24 21:19:55 +07:00
|
|
|
if (rc < 0)
|
2007-09-28 04:27:03 +07:00
|
|
|
goto ret;
|
2007-10-02 21:57:03 +07:00
|
|
|
|
2008-04-24 21:19:55 +07:00
|
|
|
/* Frequency is locked */
|
|
|
|
if (frq_lock == 1)
|
|
|
|
signal = 32768;
|
2007-10-02 21:57:03 +07:00
|
|
|
|
2007-11-05 19:07:13 +07:00
|
|
|
/* Get SNR of the video signal */
|
2007-11-19 14:31:58 +07:00
|
|
|
rc = xc2028_get_reg(priv, 0x0040, &signal);
|
|
|
|
if (rc < 0)
|
2008-04-24 21:19:55 +07:00
|
|
|
goto ret;
|
|
|
|
|
|
|
|
/* Use both frq_lock and signal to generate the result */
|
|
|
|
signal = signal || ((signal & 0x07) << 12);
|
2007-09-28 04:27:03 +07:00
|
|
|
|
|
|
|
ret:
|
2007-10-24 01:24:06 +07:00
|
|
|
mutex_unlock(&priv->lock);
|
|
|
|
|
|
|
|
*strength = signal;
|
2007-10-02 21:57:03 +07:00
|
|
|
|
2008-04-24 21:19:55 +07:00
|
|
|
tuner_dbg("signal strength is %d\n", signal);
|
|
|
|
|
2007-11-19 14:31:58 +07:00
|
|
|
return rc;
|
2007-10-02 21:57:03 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
#define DIV 15625
|
|
|
|
|
2007-11-24 20:13:42 +07:00
|
|
|
static int generic_set_freq(struct dvb_frontend *fe, u32 freq /* in HZ */,
|
2011-03-06 19:24:32 +07:00
|
|
|
enum v4l2_tuner_type new_type,
|
2007-11-26 05:26:36 +07:00
|
|
|
unsigned int type,
|
|
|
|
v4l2_std_id std,
|
|
|
|
u16 int_freq)
|
2007-10-02 21:57:03 +07:00
|
|
|
{
|
2007-10-24 01:24:06 +07:00
|
|
|
struct xc2028_data *priv = fe->tuner_priv;
|
2007-11-02 03:47:42 +07:00
|
|
|
int rc = -EINVAL;
|
2007-11-19 16:20:17 +07:00
|
|
|
unsigned char buf[4];
|
2007-11-02 03:47:42 +07:00
|
|
|
u32 div, offset = 0;
|
2007-10-02 21:57:03 +07:00
|
|
|
|
2008-04-09 09:20:00 +07:00
|
|
|
tuner_dbg("%s called\n", __func__);
|
2007-10-24 01:24:06 +07:00
|
|
|
|
2007-10-24 19:22:08 +07:00
|
|
|
mutex_lock(&priv->lock);
|
|
|
|
|
2007-11-19 16:20:17 +07:00
|
|
|
tuner_dbg("should set frequency %d kHz\n", freq / 1000);
|
2007-10-02 21:57:03 +07:00
|
|
|
|
2007-11-26 05:26:36 +07:00
|
|
|
if (check_firmware(fe, type, std, int_freq) < 0)
|
2007-09-28 04:27:03 +07:00
|
|
|
goto ret;
|
2007-07-18 23:33:23 +07:00
|
|
|
|
2007-11-22 22:48:04 +07:00
|
|
|
/* On some cases xc2028 can disable video output, if
|
|
|
|
* very weak signals are received. By sending a soft
|
|
|
|
* reset, this is re-enabled. So, it is better to always
|
|
|
|
* send a soft reset before changing channels, to be sure
|
|
|
|
* that xc2028 will be in a safe state.
|
|
|
|
* Maybe this might also be needed for DTV.
|
|
|
|
*/
|
2011-09-01 01:12:45 +07:00
|
|
|
switch (new_type) {
|
|
|
|
case V4L2_TUNER_ANALOG_TV:
|
2007-11-22 22:48:04 +07:00
|
|
|
rc = send_seq(priv, {0x00, 0x00});
|
2009-06-01 22:18:10 +07:00
|
|
|
|
2011-09-01 01:12:45 +07:00
|
|
|
/* Analog mode requires offset = 0 */
|
|
|
|
break;
|
|
|
|
case V4L2_TUNER_RADIO:
|
|
|
|
/* Radio mode requires offset = 0 */
|
|
|
|
break;
|
|
|
|
case V4L2_TUNER_DIGITAL_TV:
|
2010-02-19 11:45:00 +07:00
|
|
|
/*
|
|
|
|
* Digital modes require an offset to adjust to the
|
|
|
|
* proper frequency. The offset depends on what
|
|
|
|
* firmware version is used.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Adjust to the center frequency. This is calculated by the
|
|
|
|
* formula: offset = 1.25MHz - BW/2
|
|
|
|
* For DTV 7/8, the firmware uses BW = 8000, so it needs a
|
|
|
|
* further adjustment to get the frequency center on VHF
|
|
|
|
*/
|
2009-06-01 22:18:10 +07:00
|
|
|
if (priv->cur_fw.type & DTV6)
|
|
|
|
offset = 1750000;
|
|
|
|
else if (priv->cur_fw.type & DTV7)
|
|
|
|
offset = 2250000;
|
|
|
|
else /* DTV8 or DTV78 */
|
|
|
|
offset = 2750000;
|
2010-02-19 11:45:00 +07:00
|
|
|
if ((priv->cur_fw.type & DTV78) && freq < 470000000)
|
|
|
|
offset -= 500000;
|
2009-06-01 22:18:10 +07:00
|
|
|
|
2007-12-02 19:39:18 +07:00
|
|
|
/*
|
2010-02-19 11:45:00 +07:00
|
|
|
* xc3028 additional "magic"
|
|
|
|
* Depending on the firmware version, it needs some adjustments
|
|
|
|
* to properly centralize the frequency. This seems to be
|
|
|
|
* needed to compensate the SCODE table adjustments made by
|
|
|
|
* newer firmwares
|
2007-12-02 19:39:18 +07:00
|
|
|
*/
|
2010-02-19 11:45:00 +07:00
|
|
|
|
|
|
|
#if 1
|
|
|
|
/*
|
|
|
|
* The proper adjustment would be to do it at s-code table.
|
|
|
|
* However, this didn't work, as reported by
|
|
|
|
* Robert Lowery <rglowery@exemail.com.au>
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (priv->cur_fw.type & DTV7)
|
|
|
|
offset += 500000;
|
|
|
|
|
|
|
|
#else
|
|
|
|
/*
|
|
|
|
* Still need tests for XC3028L (firmware 3.2 or upper)
|
|
|
|
* So, for now, let's just comment the per-firmware
|
|
|
|
* version of this change. Reports with xc3028l working
|
|
|
|
* with and without the lines bellow are welcome
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (priv->firm_version < 0x0302) {
|
|
|
|
if (priv->cur_fw.type & DTV7)
|
|
|
|
offset += 500000;
|
|
|
|
} else {
|
|
|
|
if (priv->cur_fw.type & DTV7)
|
|
|
|
offset -= 300000;
|
|
|
|
else if (type != ATSC) /* DVB @6MHz, DTV 8 and DTV 7/8 */
|
|
|
|
offset += 200000;
|
|
|
|
}
|
|
|
|
#endif
|
2007-11-19 16:35:26 +07:00
|
|
|
}
|
2007-07-18 23:33:23 +07:00
|
|
|
|
2007-11-02 03:47:42 +07:00
|
|
|
div = (freq - offset + DIV / 2) / DIV;
|
2007-07-18 23:33:23 +07:00
|
|
|
|
2007-10-02 21:57:03 +07:00
|
|
|
/* CMD= Set frequency */
|
2007-11-19 16:06:08 +07:00
|
|
|
if (priv->firm_version < 0x0202)
|
2007-11-19 14:14:23 +07:00
|
|
|
rc = send_seq(priv, {0x00, 0x02, 0x00, 0x00});
|
|
|
|
else
|
|
|
|
rc = send_seq(priv, {0x80, 0x02, 0x00, 0x00});
|
|
|
|
if (rc < 0)
|
|
|
|
goto ret;
|
2007-10-24 19:22:08 +07:00
|
|
|
|
2008-04-23 00:45:20 +07:00
|
|
|
/* Return code shouldn't be checked.
|
|
|
|
The reset CLK is needed only with tm6000.
|
|
|
|
Driver should work fine even if this fails.
|
|
|
|
*/
|
2010-01-19 11:38:45 +07:00
|
|
|
if (priv->ctrl.msleep)
|
|
|
|
msleep(priv->ctrl.msleep);
|
2008-09-12 23:31:45 +07:00
|
|
|
do_tuner_callback(fe, XC2028_RESET_CLK, 1);
|
2007-10-02 21:57:03 +07:00
|
|
|
|
|
|
|
msleep(10);
|
2007-07-18 20:29:10 +07:00
|
|
|
|
2007-11-02 03:47:42 +07:00
|
|
|
buf[0] = 0xff & (div >> 24);
|
|
|
|
buf[1] = 0xff & (div >> 16);
|
|
|
|
buf[2] = 0xff & (div >> 8);
|
|
|
|
buf[3] = 0xff & (div);
|
2007-10-02 21:57:03 +07:00
|
|
|
|
2007-11-19 14:14:23 +07:00
|
|
|
rc = i2c_send(priv, buf, sizeof(buf));
|
2007-11-02 03:47:42 +07:00
|
|
|
if (rc < 0)
|
2007-09-28 04:27:03 +07:00
|
|
|
goto ret;
|
2007-10-02 21:57:03 +07:00
|
|
|
msleep(100);
|
|
|
|
|
2007-11-02 03:47:42 +07:00
|
|
|
priv->frequency = freq;
|
2007-10-24 01:24:06 +07:00
|
|
|
|
2007-11-19 16:20:17 +07:00
|
|
|
tuner_dbg("divisor= %02x %02x %02x %02x (freq=%d.%03d)\n",
|
|
|
|
buf[0], buf[1], buf[2], buf[3],
|
|
|
|
freq / 1000000, (freq % 1000000) / 1000);
|
2007-09-28 04:27:03 +07:00
|
|
|
|
2007-11-02 03:47:42 +07:00
|
|
|
rc = 0;
|
2007-10-02 21:57:03 +07:00
|
|
|
|
2007-10-24 01:24:06 +07:00
|
|
|
ret:
|
|
|
|
mutex_unlock(&priv->lock);
|
2007-10-02 21:57:03 +07:00
|
|
|
|
2007-10-24 01:24:06 +07:00
|
|
|
return rc;
|
2007-07-18 20:29:10 +07:00
|
|
|
}
|
|
|
|
|
2007-11-24 20:13:42 +07:00
|
|
|
static int xc2028_set_analog_freq(struct dvb_frontend *fe,
|
2007-11-02 03:47:42 +07:00
|
|
|
struct analog_parameters *p)
|
2007-10-02 21:57:03 +07:00
|
|
|
{
|
2007-10-24 01:24:06 +07:00
|
|
|
struct xc2028_data *priv = fe->tuner_priv;
|
2007-11-24 20:13:42 +07:00
|
|
|
unsigned int type=0;
|
|
|
|
|
2008-04-09 09:20:00 +07:00
|
|
|
tuner_dbg("%s called\n", __func__);
|
2007-11-22 21:47:18 +07:00
|
|
|
|
2007-11-24 20:20:15 +07:00
|
|
|
if (p->mode == V4L2_TUNER_RADIO) {
|
|
|
|
type |= FM;
|
|
|
|
if (priv->ctrl.input1)
|
|
|
|
type |= INPUT1;
|
|
|
|
return generic_set_freq(fe, (625l * p->frequency) / 10,
|
2011-02-22 07:03:59 +07:00
|
|
|
V4L2_TUNER_RADIO, type, 0, 0);
|
2007-11-24 20:20:15 +07:00
|
|
|
}
|
|
|
|
|
2007-11-23 21:36:18 +07:00
|
|
|
/* if std is not defined, choose one */
|
|
|
|
if (!p->std)
|
|
|
|
p->std = V4L2_STD_MN;
|
|
|
|
|
|
|
|
/* PAL/M, PAL/N, PAL/Nc and NTSC variants should use 6MHz firmware */
|
2007-11-24 20:13:42 +07:00
|
|
|
if (!(p->std & V4L2_STD_MN))
|
|
|
|
type |= F8MHZ;
|
2007-10-02 21:57:03 +07:00
|
|
|
|
2007-11-24 20:13:42 +07:00
|
|
|
/* Add audio hack to std mask */
|
|
|
|
p->std |= parse_audio_std_option();
|
2007-10-02 21:57:03 +07:00
|
|
|
|
2007-11-24 20:13:42 +07:00
|
|
|
return generic_set_freq(fe, 62500l * p->frequency,
|
2011-02-22 07:03:59 +07:00
|
|
|
V4L2_TUNER_ANALOG_TV, type, p->std, 0);
|
2007-10-24 01:24:06 +07:00
|
|
|
}
|
2007-10-02 21:57:03 +07:00
|
|
|
|
2007-10-24 01:24:06 +07:00
|
|
|
static int xc2028_set_params(struct dvb_frontend *fe,
|
|
|
|
struct dvb_frontend_parameters *p)
|
2007-10-02 21:57:03 +07:00
|
|
|
{
|
2007-10-24 01:24:06 +07:00
|
|
|
struct xc2028_data *priv = fe->tuner_priv;
|
2007-11-24 20:13:42 +07:00
|
|
|
unsigned int type=0;
|
2007-11-24 20:47:03 +07:00
|
|
|
fe_bandwidth_t bw = BANDWIDTH_8_MHZ;
|
2007-12-02 16:36:42 +07:00
|
|
|
u16 demod = 0;
|
2007-10-02 21:57:03 +07:00
|
|
|
|
2008-04-09 09:20:00 +07:00
|
|
|
tuner_dbg("%s called\n", __func__);
|
2007-07-18 20:29:10 +07:00
|
|
|
|
2007-11-24 20:47:03 +07:00
|
|
|
switch(fe->ops.info.type) {
|
|
|
|
case FE_OFDM:
|
|
|
|
bw = p->u.ofdm.bandwidth;
|
2009-06-01 21:46:08 +07:00
|
|
|
/*
|
|
|
|
* The only countries with 6MHz seem to be Taiwan/Uruguay.
|
|
|
|
* Both seem to require QAM firmware for OFDM decoding
|
|
|
|
* Tested in Taiwan by Terry Wu <terrywu2009@gmail.com>
|
|
|
|
*/
|
|
|
|
if (bw == BANDWIDTH_6_MHZ)
|
|
|
|
type |= QAM;
|
2007-11-24 20:47:03 +07:00
|
|
|
break;
|
|
|
|
case FE_ATSC:
|
|
|
|
bw = BANDWIDTH_6_MHZ;
|
2008-09-28 12:24:44 +07:00
|
|
|
/* The only ATSC firmware (at least on v2.7) is D2633 */
|
|
|
|
type |= ATSC | D2633;
|
2007-11-24 20:47:03 +07:00
|
|
|
break;
|
2009-06-01 21:46:08 +07:00
|
|
|
/* DVB-S and pure QAM (FE_QAM) are not supported */
|
2007-12-02 10:02:18 +07:00
|
|
|
default:
|
|
|
|
return -EINVAL;
|
2007-11-24 20:47:03 +07:00
|
|
|
}
|
|
|
|
|
2007-11-24 20:13:42 +07:00
|
|
|
switch (bw) {
|
|
|
|
case BANDWIDTH_8_MHZ:
|
2007-12-02 20:07:06 +07:00
|
|
|
if (p->frequency < 470000000)
|
|
|
|
priv->ctrl.vhfbw7 = 0;
|
|
|
|
else
|
|
|
|
priv->ctrl.uhfbw8 = 1;
|
|
|
|
type |= (priv->ctrl.vhfbw7 && priv->ctrl.uhfbw8) ? DTV78 : DTV8;
|
|
|
|
type |= F8MHZ;
|
2007-11-24 20:13:42 +07:00
|
|
|
break;
|
|
|
|
case BANDWIDTH_7_MHZ:
|
2007-12-02 20:07:06 +07:00
|
|
|
if (p->frequency < 470000000)
|
|
|
|
priv->ctrl.vhfbw7 = 1;
|
|
|
|
else
|
|
|
|
priv->ctrl.uhfbw8 = 0;
|
|
|
|
type |= (priv->ctrl.vhfbw7 && priv->ctrl.uhfbw8) ? DTV78 : DTV7;
|
|
|
|
type |= F8MHZ;
|
2007-11-24 20:13:42 +07:00
|
|
|
break;
|
|
|
|
case BANDWIDTH_6_MHZ:
|
2007-12-02 20:07:06 +07:00
|
|
|
type |= DTV6;
|
|
|
|
priv->ctrl.vhfbw7 = 0;
|
|
|
|
priv->ctrl.uhfbw8 = 0;
|
2007-11-24 20:13:42 +07:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
tuner_err("error: bandwidth not supported.\n");
|
|
|
|
};
|
|
|
|
|
2008-09-28 12:24:44 +07:00
|
|
|
/*
|
|
|
|
Selects between D2633 or D2620 firmware.
|
|
|
|
It doesn't make sense for ATSC, since it should be D2633 on all cases
|
|
|
|
*/
|
|
|
|
if (fe->ops.info.type != FE_ATSC) {
|
|
|
|
switch (priv->ctrl.type) {
|
|
|
|
case XC2028_D2633:
|
|
|
|
type |= D2633;
|
|
|
|
break;
|
|
|
|
case XC2028_D2620:
|
|
|
|
type |= D2620;
|
|
|
|
break;
|
|
|
|
case XC2028_AUTO:
|
|
|
|
default:
|
|
|
|
/* Zarlink seems to need D2633 */
|
|
|
|
if (priv->ctrl.demod == XC3028_FE_ZARLINK456)
|
|
|
|
type |= D2633;
|
|
|
|
else
|
|
|
|
type |= D2620;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-11-26 05:26:36 +07:00
|
|
|
/* All S-code tables need a 200kHz shift */
|
2009-06-11 17:57:50 +07:00
|
|
|
if (priv->ctrl.demod) {
|
2010-02-20 05:08:06 +07:00
|
|
|
demod = priv->ctrl.demod;
|
|
|
|
|
2010-02-19 11:45:00 +07:00
|
|
|
/*
|
|
|
|
* Newer firmwares require a 200 kHz offset only for ATSC
|
|
|
|
*/
|
|
|
|
if (type == ATSC || priv->firm_version < 0x0302)
|
2010-02-20 05:08:06 +07:00
|
|
|
demod += 200;
|
2009-06-11 17:57:50 +07:00
|
|
|
/*
|
|
|
|
* The DTV7 S-code table needs a 700 kHz shift.
|
|
|
|
*
|
|
|
|
* DTV7 is only used in Australia. Germany or Italy may also
|
|
|
|
* use this firmware after initialization, but a tune to a UHF
|
|
|
|
* channel should then cause DTV78 to be used.
|
2010-02-19 11:45:00 +07:00
|
|
|
*
|
|
|
|
* Unfortunately, on real-field tests, the s-code offset
|
|
|
|
* didn't work as expected, as reported by
|
|
|
|
* Robert Lowery <rglowery@exemail.com.au>
|
2009-06-11 17:57:50 +07:00
|
|
|
*/
|
|
|
|
}
|
2007-11-24 21:07:12 +07:00
|
|
|
|
2007-11-24 20:13:42 +07:00
|
|
|
return generic_set_freq(fe, p->frequency,
|
2011-02-22 07:03:59 +07:00
|
|
|
V4L2_TUNER_DIGITAL_TV, type, 0, demod);
|
2007-10-02 21:57:03 +07:00
|
|
|
}
|
2007-07-18 20:29:10 +07:00
|
|
|
|
2008-12-05 20:31:16 +07:00
|
|
|
static int xc2028_sleep(struct dvb_frontend *fe)
|
|
|
|
{
|
|
|
|
struct xc2028_data *priv = fe->tuner_priv;
|
|
|
|
int rc = 0;
|
|
|
|
|
2009-08-04 08:52:59 +07:00
|
|
|
/* Avoid firmware reload on slow devices or if PM disabled */
|
|
|
|
if (no_poweroff || priv->ctrl.disable_power_mgmt)
|
2008-12-05 20:49:53 +07:00
|
|
|
return 0;
|
2008-12-05 20:31:16 +07:00
|
|
|
|
|
|
|
tuner_dbg("Putting xc2028/3028 into poweroff mode.\n");
|
2008-12-18 16:00:25 +07:00
|
|
|
if (debug > 1) {
|
|
|
|
tuner_dbg("Printing sleep stack trace:\n");
|
|
|
|
dump_stack();
|
|
|
|
}
|
2008-12-05 20:31:16 +07:00
|
|
|
|
|
|
|
mutex_lock(&priv->lock);
|
|
|
|
|
|
|
|
if (priv->firm_version < 0x0202)
|
|
|
|
rc = send_seq(priv, {0x00, 0x08, 0x00, 0x00});
|
|
|
|
else
|
|
|
|
rc = send_seq(priv, {0x80, 0x08, 0x00, 0x00});
|
|
|
|
|
|
|
|
priv->cur_fw.type = 0; /* need firmware reload */
|
|
|
|
|
|
|
|
mutex_unlock(&priv->lock);
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
2007-11-19 21:41:20 +07:00
|
|
|
|
2007-10-24 01:24:06 +07:00
|
|
|
static int xc2028_dvb_release(struct dvb_frontend *fe)
|
2007-07-18 20:29:10 +07:00
|
|
|
{
|
2007-10-24 01:24:06 +07:00
|
|
|
struct xc2028_data *priv = fe->tuner_priv;
|
|
|
|
|
2008-04-09 09:20:00 +07:00
|
|
|
tuner_dbg("%s called\n", __func__);
|
2007-07-18 20:29:10 +07:00
|
|
|
|
2007-11-19 14:45:38 +07:00
|
|
|
mutex_lock(&xc2028_list_mutex);
|
|
|
|
|
2008-04-19 07:22:50 +07:00
|
|
|
/* only perform final cleanup if this is the last instance */
|
|
|
|
if (hybrid_tuner_report_instance_count(priv) == 1) {
|
2007-11-02 03:47:42 +07:00
|
|
|
kfree(priv->ctrl.fname);
|
2007-10-24 19:22:08 +07:00
|
|
|
free_firmware(priv);
|
|
|
|
}
|
2007-07-18 20:29:10 +07:00
|
|
|
|
2008-04-19 07:22:50 +07:00
|
|
|
if (priv)
|
|
|
|
hybrid_tuner_release_state(priv);
|
|
|
|
|
2007-11-19 14:45:38 +07:00
|
|
|
mutex_unlock(&xc2028_list_mutex);
|
|
|
|
|
2008-04-19 07:22:50 +07:00
|
|
|
fe->tuner_priv = NULL;
|
|
|
|
|
2007-07-18 20:29:10 +07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-10-24 01:24:06 +07:00
|
|
|
static int xc2028_get_frequency(struct dvb_frontend *fe, u32 *frequency)
|
2007-07-18 20:29:10 +07:00
|
|
|
{
|
2007-10-24 01:24:06 +07:00
|
|
|
struct xc2028_data *priv = fe->tuner_priv;
|
2007-07-18 20:29:10 +07:00
|
|
|
|
2008-04-09 09:20:00 +07:00
|
|
|
tuner_dbg("%s called\n", __func__);
|
2007-07-18 20:29:10 +07:00
|
|
|
|
2007-10-24 01:24:06 +07:00
|
|
|
*frequency = priv->frequency;
|
2007-07-18 20:29:10 +07:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-11-02 03:47:42 +07:00
|
|
|
static int xc2028_set_config(struct dvb_frontend *fe, void *priv_cfg)
|
2007-10-24 19:22:08 +07:00
|
|
|
{
|
|
|
|
struct xc2028_data *priv = fe->tuner_priv;
|
|
|
|
struct xc2028_ctrl *p = priv_cfg;
|
2007-11-19 19:29:59 +07:00
|
|
|
int rc = 0;
|
2007-10-24 19:22:08 +07:00
|
|
|
|
2008-04-09 09:20:00 +07:00
|
|
|
tuner_dbg("%s called\n", __func__);
|
2007-10-24 19:22:08 +07:00
|
|
|
|
2007-11-19 16:06:08 +07:00
|
|
|
mutex_lock(&priv->lock);
|
|
|
|
|
2007-11-19 19:29:59 +07:00
|
|
|
memcpy(&priv->ctrl, p, sizeof(priv->ctrl));
|
2008-04-18 07:40:53 +07:00
|
|
|
if (priv->ctrl.max_len < 9)
|
|
|
|
priv->ctrl.max_len = 13;
|
2007-10-24 19:22:08 +07:00
|
|
|
|
2007-11-19 19:29:59 +07:00
|
|
|
if (p->fname) {
|
2008-04-18 07:40:53 +07:00
|
|
|
if (priv->ctrl.fname && strcmp(p->fname, priv->ctrl.fname)) {
|
|
|
|
kfree(priv->ctrl.fname);
|
|
|
|
free_firmware(priv);
|
|
|
|
}
|
|
|
|
|
2007-11-19 19:29:59 +07:00
|
|
|
priv->ctrl.fname = kstrdup(p->fname, GFP_KERNEL);
|
|
|
|
if (priv->ctrl.fname == NULL)
|
|
|
|
rc = -ENOMEM;
|
2007-10-24 19:22:08 +07:00
|
|
|
}
|
|
|
|
|
2007-11-19 16:06:08 +07:00
|
|
|
mutex_unlock(&priv->lock);
|
|
|
|
|
2007-11-19 19:29:59 +07:00
|
|
|
return rc;
|
2007-10-24 19:22:08 +07:00
|
|
|
}
|
|
|
|
|
2007-10-24 01:24:06 +07:00
|
|
|
static const struct dvb_tuner_ops xc2028_dvb_tuner_ops = {
|
2007-07-18 20:29:10 +07:00
|
|
|
.info = {
|
2007-11-02 03:47:42 +07:00
|
|
|
.name = "Xceive XC3028",
|
|
|
|
.frequency_min = 42000000,
|
|
|
|
.frequency_max = 864000000,
|
|
|
|
.frequency_step = 50000,
|
|
|
|
},
|
2007-07-18 20:29:10 +07:00
|
|
|
|
2007-10-24 19:22:08 +07:00
|
|
|
.set_config = xc2028_set_config,
|
2007-11-24 20:13:42 +07:00
|
|
|
.set_analog_params = xc2028_set_analog_freq,
|
2007-10-24 01:24:06 +07:00
|
|
|
.release = xc2028_dvb_release,
|
|
|
|
.get_frequency = xc2028_get_frequency,
|
|
|
|
.get_rf_strength = xc2028_signal,
|
|
|
|
.set_params = xc2028_set_params,
|
2008-12-05 20:31:16 +07:00
|
|
|
.sleep = xc2028_sleep,
|
2007-07-18 20:29:10 +07:00
|
|
|
};
|
|
|
|
|
2007-12-22 02:12:09 +07:00
|
|
|
struct dvb_frontend *xc2028_attach(struct dvb_frontend *fe,
|
|
|
|
struct xc2028_config *cfg)
|
2007-07-18 20:29:10 +07:00
|
|
|
{
|
2007-10-24 01:24:06 +07:00
|
|
|
struct xc2028_data *priv;
|
2008-04-19 07:22:50 +07:00
|
|
|
int instance;
|
2007-07-18 20:29:10 +07:00
|
|
|
|
2007-11-15 19:44:30 +07:00
|
|
|
if (debug)
|
2008-04-23 00:41:53 +07:00
|
|
|
printk(KERN_DEBUG "xc2028: Xcv2028/3028 init called!\n");
|
2007-07-18 20:29:10 +07:00
|
|
|
|
2008-04-23 00:46:11 +07:00
|
|
|
if (NULL == cfg)
|
2007-11-16 17:46:14 +07:00
|
|
|
return NULL;
|
2007-10-24 01:24:06 +07:00
|
|
|
|
2007-11-16 17:46:14 +07:00
|
|
|
if (!fe) {
|
2008-04-23 00:41:53 +07:00
|
|
|
printk(KERN_ERR "xc2028: No frontend!\n");
|
2007-11-16 17:46:14 +07:00
|
|
|
return NULL;
|
2007-10-24 01:24:06 +07:00
|
|
|
}
|
|
|
|
|
2007-11-19 14:45:38 +07:00
|
|
|
mutex_lock(&xc2028_list_mutex);
|
|
|
|
|
2008-04-19 07:22:50 +07:00
|
|
|
instance = hybrid_tuner_request_state(struct xc2028_data, priv,
|
|
|
|
hybrid_tuner_instance_list,
|
|
|
|
cfg->i2c_adap, cfg->i2c_addr,
|
|
|
|
"xc2028");
|
|
|
|
switch (instance) {
|
|
|
|
case 0:
|
|
|
|
/* memory allocation failure */
|
|
|
|
goto fail;
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
/* new tuner instance */
|
2007-11-19 19:29:59 +07:00
|
|
|
priv->ctrl.max_len = 13;
|
2007-10-24 19:22:08 +07:00
|
|
|
|
2007-10-24 01:24:06 +07:00
|
|
|
mutex_init(&priv->lock);
|
|
|
|
|
2008-04-19 07:22:50 +07:00
|
|
|
fe->tuner_priv = priv;
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
/* existing tuner instance */
|
|
|
|
fe->tuner_priv = priv;
|
|
|
|
break;
|
|
|
|
}
|
2008-04-23 00:46:11 +07:00
|
|
|
|
2007-10-24 01:24:06 +07:00
|
|
|
memcpy(&fe->ops.tuner_ops, &xc2028_dvb_tuner_ops,
|
2007-11-02 03:47:42 +07:00
|
|
|
sizeof(xc2028_dvb_tuner_ops));
|
2007-10-24 01:24:06 +07:00
|
|
|
|
|
|
|
tuner_info("type set to %s\n", "XCeive xc2028/xc3028 tuner");
|
|
|
|
|
2007-11-22 22:19:37 +07:00
|
|
|
if (cfg->ctrl)
|
|
|
|
xc2028_set_config(fe, cfg->ctrl);
|
|
|
|
|
2007-11-19 14:45:38 +07:00
|
|
|
mutex_unlock(&xc2028_list_mutex);
|
|
|
|
|
2007-11-16 17:46:14 +07:00
|
|
|
return fe;
|
2008-04-19 07:22:50 +07:00
|
|
|
fail:
|
|
|
|
mutex_unlock(&xc2028_list_mutex);
|
|
|
|
|
|
|
|
xc2028_dvb_release(fe);
|
|
|
|
return NULL;
|
2007-10-24 01:24:06 +07:00
|
|
|
}
|
2007-11-16 17:46:14 +07:00
|
|
|
|
2007-07-18 20:29:10 +07:00
|
|
|
EXPORT_SYMBOL(xc2028_attach);
|
|
|
|
|
2007-10-24 01:24:06 +07:00
|
|
|
MODULE_DESCRIPTION("Xceive xc2028/xc3028 tuner driver");
|
2007-10-30 09:44:18 +07:00
|
|
|
MODULE_AUTHOR("Michel Ludwig <michel.ludwig@gmail.com>");
|
2007-10-24 01:24:06 +07:00
|
|
|
MODULE_AUTHOR("Mauro Carvalho Chehab <mchehab@infradead.org>");
|
|
|
|
MODULE_LICENSE("GPL");
|